diff --git a/.eslintignore b/.eslintignore index 391b44cdd89..5535673df8f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,12 +6,13 @@ **/venv/** .opentrons_config **/tsconfig*.json -**/vite.config.ts +**/vite.config.mts # prettier **/package.json **/CHANGELOG.md !api/release-notes.md !app-shell/build/release-notes.md +**/.yarn-cache/** # components library storybook-static @@ -28,6 +29,9 @@ robot-server/** shared-data/python/** hardware-testing/** +# abr-testing don't format the json protocols +abr-testing/protocols/** + # analyses-snapshot-testing don't format the json protocols analyses-snapshot-testing/files # don't format the snapshots diff --git a/.eslintrc.js b/.eslintrc.js index 5af0e54a419..7de339ebde5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -13,9 +13,10 @@ module.exports = { 'prettier', 'plugin:json/recommended', 'plugin:storybook/recommended', + 'plugin:react/jsx-runtime', ], - plugins: ['react', 'react-hooks', 'json', 'testing-library'], + plugins: ['react', 'react-hooks', 'json', 'testing-library', 'opentrons'], rules: { camelcase: 'off', @@ -116,6 +117,12 @@ module.exports = { ], }, }, + { + files: ['./app/src/**/*.@(ts|tsx)'], + rules: { + 'import/no-absolute-path': 'off', + }, + }, { files: [ '**/test/**.js', @@ -161,5 +168,25 @@ module.exports = { 'no-restricted-imports': 'off', }, }, + // Apply tree-of-life import requirements to app as errors + { + files: ['./app/src/**/*.@(ts|tsx)'], + rules: { + 'opentrons/no-imports-up-the-tree-of-life': 'error', + }, + }, + { + files: ['./protocol-designer/src/**/*.@(ts|tsx)'], + rules: { + 'opentrons/no-imports-up-the-tree-of-life': 'warn', + }, + }, + // apply application structure import requirements to app + { + files: ['./app/src/**/*.@(ts|tsx)'], + rules: { + 'opentrons/no-imports-across-applications': 'error', + }, + }, ], } diff --git a/.github/actions/.gitattributes b/.github/actions/.gitattributes new file mode 100644 index 00000000000..72f4684a29d --- /dev/null +++ b/.github/actions/.gitattributes @@ -0,0 +1 @@ +odd-resource-analysis/dist/* binary \ No newline at end of file diff --git a/.github/actions/odd-resource-analysis/.gitignore b/.github/actions/odd-resource-analysis/.gitignore new file mode 100644 index 00000000000..368f5c9f9dc --- /dev/null +++ b/.github/actions/odd-resource-analysis/.gitignore @@ -0,0 +1,13 @@ +.DS_Store +.idea +*.log +tmp/ + +*.tern-port +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +*.tsbuildinfo +.npm +.eslintcache diff --git a/.github/actions/odd-resource-analysis/.prettierignore b/.github/actions/odd-resource-analysis/.prettierignore new file mode 100644 index 00000000000..763301fc002 --- /dev/null +++ b/.github/actions/odd-resource-analysis/.prettierignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ \ No newline at end of file diff --git a/.github/actions/odd-resource-analysis/.prettierrc.js b/.github/actions/odd-resource-analysis/.prettierrc.js new file mode 100644 index 00000000000..d9888254456 --- /dev/null +++ b/.github/actions/odd-resource-analysis/.prettierrc.js @@ -0,0 +1,15 @@ +'use strict' + +module.exports = { + printWidth: 80, // default + tabWidth: 2, // default + useTabs: false, // default + semi: false, + singleQuote: true, + jsxSingleQuote: false, // default + trailingComma: 'es5', + bracketSpacing: true, // default + jsxBracketSameLine: false, // default + arrowParens: 'avoid', // default + endOfLine: 'lf', +} diff --git a/.github/actions/odd-resource-analysis/action.yml b/.github/actions/odd-resource-analysis/action.yml new file mode 100644 index 00000000000..f5d6677f6f2 --- /dev/null +++ b/.github/actions/odd-resource-analysis/action.yml @@ -0,0 +1,27 @@ +name: 'ODD Memory Usage Analysis' +description: >- + Analyzes memory usage trends across ODD versions using Mixpanel data. + Note that only processes with positive correlation or explicitly whitelisted processes are shown. + +inputs: + mixpanel-user: + description: 'Mixpanel service account username' + required: true + mixpanel-secret: + description: 'Mixpanel service account password' + required: true + mixpanel-project-id: + description: 'Mixpanel project ID' + required: true + previous-version-count: + description: 'Number of previous versions to analyze' + required: false + default: '2' + +outputs: + analysis-results: + description: 'JSON string containing the complete analysis results' + +runs: + using: 'node16' + main: 'dist/index.js' diff --git a/.github/actions/odd-resource-analysis/action/analyzeMemoryTrends.js b/.github/actions/odd-resource-analysis/action/analyzeMemoryTrends.js new file mode 100644 index 00000000000..139f8719375 --- /dev/null +++ b/.github/actions/odd-resource-analysis/action/analyzeMemoryTrends.js @@ -0,0 +1,295 @@ +const { + parseMixpanelData, + getISODatesForPastMonth, + getMixpanelResourceMonitorDataFor, + downloadAppManifest, + getPrevValidVersions, + latestValidVersionFromManifest, +} = require('./lib/helpers') +const { analyzeCorrelation } = require('./lib/analysis') +const { + AGGREGATED_PROCESSES, + AGGREGATED_PROCESS_NAMES, + BLACKLISTED_PROCESSES, + MINIMUM_VALID_SAMPLE_SIZE, +} = require('./lib/constants') + +const UPTIME_BUCKETS = [ + { min: 0, max: 20, label: '0-20hrs' }, + { min: 20, max: 40, label: '20-40hrs' }, + { min: 40, max: 60, label: '40-60hrs' }, + { min: 60, max: 80, label: '60-80hrs' }, + { min: 80, max: 120, label: '80-120hrs' }, + { min: 120, max: 240, label: '120-240hrs' }, + { min: 240, max: Infinity, label: '240+hrs' }, +] + +/** + * @description Calculate average memory usage for measurements within a specific time range + * @param measurements Array of measurements with uptime and a memory metric + * @param minHours Minimum hours (inclusive) + * @param maxHours Maximum hours (exclusive) + * @param memoryMetric The field to average ('memRssMb' or 'systemAvailMemMb') + * @returns {number | null} Average memory usage or null if no measurements in range + */ +function calculateAverageMemoryForRange( + measurements, + minHours, + maxHours, + memoryMetric = 'memRssMb' +) { + const inRange = measurements.filter( + m => m.uptime >= minHours && m.uptime < maxHours + ) + + if (inRange.length === 0 || inRange.length < MINIMUM_VALID_SAMPLE_SIZE) { + return null + } + + const sum = inRange.reduce((acc, m) => acc + m[memoryMetric], 0) + return sum / inRange.length +} + +/** + * @description Calculate memory usage averages across all defined ranges + * @param measurements Array of measurements with uptime and the memory metric + * @param memoryMetric The field to average ('memRssMb' or 'systemAvailMemMb') + * @returns {Object} Contains averages for each range + */ +function calculateRangeAverages(measurements, memoryMetric = 'memRssMb') { + const averages = {} + UPTIME_BUCKETS.forEach(range => { + const avg = calculateAverageMemoryForRange( + measurements, + range.min, + range.max, + memoryMetric + ) + averages[range.label] = + avg !== null ? avg.toFixed(2) : 'N/A - Not enough data available.' + }) + return averages +} + +/** + * @description Filter the Mixpanel data for the data relevant for memory analysis, aggregating data for certain processes + * and ignoring data for blacklisted processes. + * @param data Mixpanel data. + * @return A tuple of memory data by process and general ODD system memory. + */ +function processMixpanelData(data) { + const processByName = new Map() + const systemMemory = [] + + data.forEach(entry => { + const { + systemUptimeHrs, + systemAvailMemMb, + processesDetails, + } = entry.properties + const uptime = parseFloat(systemUptimeHrs) + + // Validate uptime before adding any measurements + if (isNaN(uptime)) { + return + } + + // Ensure system mem is a valid number before adding it. + const availMemMb = parseFloat(systemAvailMemMb) + if (!isNaN(availMemMb)) { + systemMemory.push({ + uptime, + systemAvailMemMb: availMemMb, + }) + } + + processesDetails.forEach(process => { + const isBlacklisted = BLACKLISTED_PROCESSES.some(pattern => + pattern.test(process.name) + ) + + if (!isBlacklisted) { + let processKey = process.name + // Certain processes are aggregated. + for (const { pattern, key } of AGGREGATED_PROCESSES) { + if (pattern.test(process.name)) { + processKey = key + break + } + } + + const memRssMb = parseFloat(process.memRssMb) + if (!isNaN(memRssMb)) { + if (!processByName.has(processKey)) { + processByName.set(processKey, []) + } + processByName.get(processKey).push({ + memRssMb, + uptime, + }) + } + } + }) + }) + + return [processByName, systemMemory] +} + +/** + * @description Group data by process name and calculate correlation and range averages + * @param data See `analyzeMemoryTrends` + */ +function analyzeProcessMemoryTrends(data) { + const [processByName, systemMemory] = processMixpanelData(data) + + // Filter out any process that has less than the minimum sample size + for (const [processName, measurements] of processByName.entries()) { + if (measurements.length < MINIMUM_VALID_SAMPLE_SIZE) { + processByName.delete(processName) + } + } + + // Calculate correlation coefficient and range averages for each process + const results = new Map() + processByName.forEach((measurements, processName) => { + const analysis = analyzeCorrelation( + measurements.map(m => m.uptime), + measurements.map(m => m.memRssMb) + ) + + results.set(processName, { + correlation: analysis.correlation, + sampleSize: analysis.sampleSize, + interpretation: analysis.interpretation, + averageMemoryMbByUptime: calculateRangeAverages(measurements, 'memRssMb'), + }) + }) + + // Calculate system memory metrics + const systemAnalysis = analyzeCorrelation( + systemMemory.map(m => m.uptime), + systemMemory.map(m => m.systemAvailMemMb) + ) + + results.set('odd-available-memory', { + correlation: systemAnalysis.correlation, + sampleSize: systemAnalysis.sampleSize, + interpretation: systemAnalysis.interpretation, + averageMemoryMbByUptime: calculateRangeAverages( + systemMemory, + 'systemAvailMemMb' + ), + }) + + // Filter out any process with a negative correlation except for a few key ones. + for (const [processName, memResults] of results.entries()) { + if ( + memResults.correlation < 0 && + processName !== 'odd-available-memory' && + ![ + AGGREGATED_PROCESS_NAMES.APP_RENDERER, + AGGREGATED_PROCESS_NAMES.SERVER_UVICORN, + ].includes(processName) + ) { + results.delete(processName) + } + } + + return results +} + +/** + * @description Post-process mixpanel data, returning statistical summaries per process + * @param mixpanelData Each entry is expected to contain a top-level 'properties' field with relevant subfields. + */ +function analyzeMemoryTrends(mixpanelData) { + const parsedData = parseMixpanelData(mixpanelData) + const results = analyzeProcessMemoryTrends(parsedData) + + const analysis = {} + results.forEach((result, processName) => { + analysis[processName] = { + correlation: result.correlation.toFixed(4), + sampleSize: result.sampleSize, + interpretation: result.interpretation, + averageMemoryMbByUptime: result.averageMemoryMbByUptime, + } + }) + + return analysis +} + +/** + * @description The 'where' used as a segmentation expression for Mixpanel data filtering. + */ +function buildWhere(version) { + return `properties["appVersion"]=="${version}" and properties["appMode"]=="ODD"` +} + +/** + * @description Analyze memory trends across multiple versions + * @param {number} previousVersionCount Number of previous versions to analyze + * @param {string} uname Mixpanel service account username. + * @param {string} pwd Mixpanel service account password. + * @param {string} projectId Mixpanel project id. + */ +async function analyzeMemoryTrendsAcrossVersions({ + previousVersionCount, + uname, + pwd, + projectId, +}) { + const manifest = await downloadAppManifest() + const latestValidVersion = latestValidVersionFromManifest(manifest) + const prevValidVersions = getPrevValidVersions( + manifest, + latestValidVersion, + previousVersionCount + ) + const analysisPeriod = getISODatesForPastMonth() + + // Populate backup messaging if there's no data available for a specific version + const noDataAvailableStr = 'N/A - No data available' + const results = { + [latestValidVersion]: noDataAvailableStr, + } + prevValidVersions.forEach(version => { + results[version] = noDataAvailableStr + }) + + // Analyze latest version + const currentVersionData = await getMixpanelResourceMonitorDataFor({ + version: latestValidVersion, + uname, + pwd, + projectId, + fromDate: analysisPeriod.from, + toDate: analysisPeriod.to, + where: buildWhere(latestValidVersion), + }) + + if (currentVersionData) { + results[latestValidVersion] = analyzeMemoryTrends(currentVersionData) + } + + // Analyze previous versions + for (const version of prevValidVersions) { + const versionData = await getMixpanelResourceMonitorDataFor({ + version, + uname, + pwd, + projectId, + fromDate: analysisPeriod.from, + toDate: analysisPeriod.to, + where: buildWhere(version), + }) + + if (versionData) { + results[version] = analyzeMemoryTrends(versionData) + } + } + + return results +} + +module.exports = { analyzeMemoryTrendsAcrossVersions } diff --git a/.github/actions/odd-resource-analysis/action/index.js b/.github/actions/odd-resource-analysis/action/index.js new file mode 100644 index 00000000000..9ccd2f48e5a --- /dev/null +++ b/.github/actions/odd-resource-analysis/action/index.js @@ -0,0 +1,48 @@ +const core = require('@actions/core') +const { analyzeMemoryTrendsAcrossVersions } = require('./analyzeMemoryTrends') + +async function run() { + try { + const mixpanelUser = core.getInput('mixpanel-user', { required: true }) + const mixpanelSecret = core.getInput('mixpanel-secret', { required: true }) + const mixpanelProjectId = core.getInput('mixpanel-project-id', { + required: true, + }) + const previousVersionCount = parseInt( + core.getInput('previous-version-count') || '2' + ) + + core.info('Beginning analysis...') + const memoryAnalysis = await analyzeMemoryTrendsAcrossVersions({ + previousVersionCount, + uname: mixpanelUser, + pwd: mixpanelSecret, + projectId: mixpanelProjectId, + }) + + console.log( + 'ODD Available Memory and Processes with Increasing Memory Trend or Selectively Observed by Version (Rolling 1 Month Analysis Window):' + ) + console.log(JSON.stringify(memoryAnalysis, null, 2)) + + const outputText = + 'ODD Available Memory and Processes with Increasing Memory Trend or Selectively Observed by Version (Rolling 1 Month Analysis Window):\n' + + Object.entries(memoryAnalysis) + .map( + ([version, analysis]) => + `\n${version}: ${JSON.stringify(analysis, null, 2)}` + ) + .join('\n') + + core.setOutput('analysis-results', JSON.stringify(memoryAnalysis)) + + await core.summary + .addHeading('ODD Memory Usage Results') + .addCodeBlock(outputText, 'json') + .write() + } catch (error) { + core.setFailed(error.message) + } +} + +run() diff --git a/.github/actions/odd-resource-analysis/action/lib/analysis.js b/.github/actions/odd-resource-analysis/action/lib/analysis.js new file mode 100644 index 00000000000..bc672aace51 --- /dev/null +++ b/.github/actions/odd-resource-analysis/action/lib/analysis.js @@ -0,0 +1,81 @@ +const calculateCorrelation = require('calculate-correlation') +const { MINIMUM_VALID_SAMPLE_SIZE } = require('./constants') + +const P_VALUE_THRESHOLD = 0.05 + +const CORRELATION_THRESHOLDS = { + STRONG: 0.7, + MODERATE: 0.3, +} + +/** + * @description Calculate significance of Pearson correlation coefficient using t-distribution approximation + * @param {number} correlation Pearson correlation coefficient + * @param {number} sampleSize Number of samples + * @returns {number} One-tailed p-value + */ +function calculatePValue(correlation, sampleSize) { + // Convert correlation coefficient to t-statistic + const t = correlation * Math.sqrt((sampleSize - 2) / (1 - correlation ** 2)) + + // Degrees of freedom + const df = sampleSize - 2 + + const x = df / (df + t * t) + return 0.5 * Math.pow(x, df / 2) +} + +/** + * @description Determines correlation strength and direction + * @param {number} correlation Pearson correlation coefficient + * @returns {string} Human readable interpretation + */ +function getCorrelationDescription(correlation) { + const strength = Math.abs(correlation) + const direction = correlation > 0 ? 'positive' : 'negative' + + if (strength > CORRELATION_THRESHOLDS.STRONG) { + return `Strong ${direction} correlation (>${CORRELATION_THRESHOLDS.STRONG})` + } else if (strength > CORRELATION_THRESHOLDS.MODERATE) { + return `Moderate ${direction} correlation (>${CORRELATION_THRESHOLDS.MODERATE} and <${CORRELATION_THRESHOLDS.STRONG})` + } + return `Weak ${direction} correlation (<=${CORRELATION_THRESHOLDS.MODERATE})` +} + +/** + * @description Performs complete correlation analysis including significance testing + * @param {Array} x Array of numbers + * @param {Array} y Array of numbers + * @return {Object} Analysis results including correlation, significance, and interpretation + */ +function analyzeCorrelation(x, y) { + const lowestSampleSize = Math.min(x.length, y.length) + + if (lowestSampleSize < MINIMUM_VALID_SAMPLE_SIZE) { + return { + correlation: 0, + isSignificant: false, + sampleSize: lowestSampleSize, + pValue: 1, + interpretation: 'Not enough samples for analysis', + } + } + + const correlation = calculateCorrelation(x, y, { decimals: 4 }) + const pValue = calculatePValue(correlation, lowestSampleSize) + const isSignificant = pValue < P_VALUE_THRESHOLD + + return { + correlation, + isSignificant, + sampleSize: x.length, + pValue, + interpretation: isSignificant + ? getCorrelationDescription(correlation) + : 'No significant correlation found', + } +} + +module.exports = { + analyzeCorrelation, +} diff --git a/.github/actions/odd-resource-analysis/action/lib/constants.js b/.github/actions/odd-resource-analysis/action/lib/constants.js new file mode 100644 index 00000000000..e6b426422d1 --- /dev/null +++ b/.github/actions/odd-resource-analysis/action/lib/constants.js @@ -0,0 +1,49 @@ +const AGGREGATED_PROCESS_NAMES = { + APP_RENDERER: 'app-renderer-processes', + APP_ZYGOTE: 'app-zygote-processes', + SERVER_UVICORN: 'robot-server-uvicorn-processes', + APP_UTILITY: 'app-utility-processes', +} + +/** + * @description Several processes we care about execute with a lot of unique sub args determined at + * runtime. These processes are aggregated using a regex pattern. + */ +const AGGREGATED_PROCESSES = [ + { + pattern: /^\/opt\/opentrons-app\/opentrons --type=renderer/, + key: AGGREGATED_PROCESS_NAMES.APP_RENDERER, + }, + { + pattern: /^\/opt\/opentrons-app\/opentrons --type=zygote/, + key: AGGREGATED_PROCESS_NAMES.APP_ZYGOTE, + }, + { + pattern: /^python3 -m uvicorn/, + key: AGGREGATED_PROCESS_NAMES.SERVER_UVICORN, + }, + { + pattern: /^\/opt\/opentrons-app\/opentrons --type=utility/, + key: AGGREGATED_PROCESS_NAMES.APP_UTILITY, + }, +] + +/** + * @description Generally don't include any variation of external processes in analysis. + */ +const BLACKLISTED_PROCESSES = [/^nmcli/, /^\/usr\/bin\/python3/] + +/** + * @description For Pearson's, it's generally recommended to use a sample size of at least n=30. + */ +const MINIMUM_VALID_SAMPLE_SIZE = 30 + +const P_VALUE_SIGNIFICANCE_THRESHOLD = 0.05 + +module.exports = { + AGGREGATED_PROCESSES, + AGGREGATED_PROCESS_NAMES, + BLACKLISTED_PROCESSES, + MINIMUM_VALID_SAMPLE_SIZE, + P_VALUE_SIGNIFICANCE_THRESHOLD, +} diff --git a/.github/actions/odd-resource-analysis/action/lib/helpers/date.js b/.github/actions/odd-resource-analysis/action/lib/helpers/date.js new file mode 100644 index 00000000000..fa0bda62d51 --- /dev/null +++ b/.github/actions/odd-resource-analysis/action/lib/helpers/date.js @@ -0,0 +1,22 @@ +/** + * @description Get ISO date strings for the past month from yesterday. + */ +function getISODatesForPastMonth() { + const now = new Date() + // Don't use today's data, because the Mixpanel API seemingly doesn't use UTC timestamps, and + // it's easy to fail a request depending on the time of day it's made. + const yesterday = new Date(now.setDate(now.getDate() - 1)) + const formatDate = date => date.toISOString().split('T')[0] + + const monthAgo = new Date(yesterday) + monthAgo.setMonth(yesterday.getMonth() - 1) + + return { + from: formatDate(monthAgo), + to: formatDate(yesterday), + } +} + +module.exports = { + getISODatesForPastMonth, +} diff --git a/.github/actions/odd-resource-analysis/action/lib/helpers/index.js b/.github/actions/odd-resource-analysis/action/lib/helpers/index.js new file mode 100644 index 00000000000..71991e2d09f --- /dev/null +++ b/.github/actions/odd-resource-analysis/action/lib/helpers/index.js @@ -0,0 +1,5 @@ +module.exports = { + ...require('./date'), + ...require('./manifest'), + ...require('./mixpanel'), +} diff --git a/.github/actions/odd-resource-analysis/action/lib/helpers/manifest.js b/.github/actions/odd-resource-analysis/action/lib/helpers/manifest.js new file mode 100644 index 00000000000..371f5e78cd9 --- /dev/null +++ b/.github/actions/odd-resource-analysis/action/lib/helpers/manifest.js @@ -0,0 +1,52 @@ +const fetch = require('node-fetch') + +const APP_MANIFEST = 'https://builds.opentrons.com/ot3-oe/releases.json' + +async function downloadAppManifest() { + const response = await fetch(APP_MANIFEST) + return await response.json() +} + +/** + * @description Get the most recent app version that is not revoked. + * @param manifest The app manifest + */ +function latestValidVersionFromManifest(manifest) { + const versions = Object.keys(manifest.production) + const latestValidVersion = versions.findLast( + version => !('revoked' in manifest.production[version]) + ) + + if (latestValidVersion != null) { + return latestValidVersion + } else { + throw new Error('No valid versions found') + } +} + +/** + * @description Get `count` latest, previous non revoked versions relative to the latest version. + * @param manifest The app manifest + * @param latestVersion The latest valid version + * @param count Number of previous versions to return + * @returns {string[]} Array of version strings, ordered from newest to oldest + */ +function getPrevValidVersions(manifest, latestVersion, count) { + const versions = Object.keys(manifest.production) + const latestIndex = versions.indexOf(latestVersion) + + if (latestIndex === -1) { + throw new Error('Latest version not found in manifest') + } + + return versions + .slice(0, latestIndex) + .filter(version => !manifest.production[version].revoked) + .slice(-count) + .reverse() +} +module.exports = { + downloadAppManifest, + latestValidVersionFromManifest, + getPrevValidVersions, +} diff --git a/.github/actions/odd-resource-analysis/action/lib/helpers/mixpanel.js b/.github/actions/odd-resource-analysis/action/lib/helpers/mixpanel.js new file mode 100644 index 00000000000..50013d9ffd6 --- /dev/null +++ b/.github/actions/odd-resource-analysis/action/lib/helpers/mixpanel.js @@ -0,0 +1,65 @@ +const fetch = require('node-fetch') + +const MIXPANEL_URL = 'https://data.mixpanel.com/api/2.0/export' + +/** + * @description Base64 encode a username and password in + * @param uname Mixpanel service account username. + * @param pwd Mixpanel service account password. + * @return {string} + */ +function encodeCredentialsForMixpanel(uname, pwd) { + return Buffer.from(`${uname}:${pwd}`).toString('base64') +} + +/** + * @description Cleans up Mixpanel data for post-processing. + * @param data Mixpanel data + */ +function parseMixpanelData(data) { + const lines = data.split('\n').filter(line => line.trim()) + return lines.map(line => JSON.parse(line)) +} + +/** + * @description Make the network request to Mixpanel. + */ +async function getMixpanelResourceMonitorDataFor({ + uname, + pwd, + projectId, + fromDate, + toDate, + where, +}) { + const params = new URLSearchParams({ + project_id: parseInt(projectId), + from_date: fromDate, + to_date: toDate, + event: '["resourceMonitorReport"]', + where, + }) + + const options = { + method: 'GET', + headers: { + 'Accept-Encoding': 'gzip', + accept: 'text/plain', + authorization: `Basic ${encodeCredentialsForMixpanel(uname, pwd)}`, + }, + } + + const response = await fetch(`${MIXPANEL_URL}?${params}`, options) + const text = await response.text() + if (!response.ok) { + throw new Error( + `Mixpanel request failed: ${response.status}, ${response.statusText}, ${text}` + ) + } + return text +} + +module.exports = { + getMixpanelResourceMonitorDataFor, + parseMixpanelData, +} diff --git a/.github/actions/odd-resource-analysis/dist/101.index.js b/.github/actions/odd-resource-analysis/dist/101.index.js new file mode 100644 index 00000000000..acbcf4681d5 --- /dev/null +++ b/.github/actions/odd-resource-analysis/dist/101.index.js @@ -0,0 +1,469 @@ +'use strict' +exports.id = 101 +exports.ids = [101] +exports.modules = { + /***/ 9101: /***/ ( + __unused_webpack___webpack_module__, + __webpack_exports__, + __webpack_require__ + ) => { + /* harmony export */ __webpack_require__.d(__webpack_exports__, { + /* harmony export */ toFormData: () => /* binding */ toFormData, + /* harmony export */ + }) + /* harmony import */ var fetch_blob_from_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( + 9802 + ) + /* harmony import */ var formdata_polyfill_esm_min_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__( + 3018 + ) + + let s = 0 + const S = { + START_BOUNDARY: s++, + HEADER_FIELD_START: s++, + HEADER_FIELD: s++, + HEADER_VALUE_START: s++, + HEADER_VALUE: s++, + HEADER_VALUE_ALMOST_DONE: s++, + HEADERS_ALMOST_DONE: s++, + PART_DATA_START: s++, + PART_DATA: s++, + END: s++, + } + + let f = 1 + const F = { + PART_BOUNDARY: f, + LAST_BOUNDARY: (f *= 2), + } + + const LF = 10 + const CR = 13 + const SPACE = 32 + const HYPHEN = 45 + const COLON = 58 + const A = 97 + const Z = 122 + + const lower = c => c | 0x20 + + const noop = () => {} + + class MultipartParser { + /** + * @param {string} boundary + */ + constructor(boundary) { + this.index = 0 + this.flags = 0 + + this.onHeaderEnd = noop + this.onHeaderField = noop + this.onHeadersEnd = noop + this.onHeaderValue = noop + this.onPartBegin = noop + this.onPartData = noop + this.onPartEnd = noop + + this.boundaryChars = {} + + boundary = '\r\n--' + boundary + const ui8a = new Uint8Array(boundary.length) + for (let i = 0; i < boundary.length; i++) { + ui8a[i] = boundary.charCodeAt(i) + this.boundaryChars[ui8a[i]] = true + } + + this.boundary = ui8a + this.lookbehind = new Uint8Array(this.boundary.length + 8) + this.state = S.START_BOUNDARY + } + + /** + * @param {Uint8Array} data + */ + write(data) { + let i = 0 + const length_ = data.length + let previousIndex = this.index + let { lookbehind, boundary, boundaryChars, index, state, flags } = this + const boundaryLength = this.boundary.length + const boundaryEnd = boundaryLength - 1 + const bufferLength = data.length + let c + let cl + + const mark = name => { + this[name + 'Mark'] = i + } + + const clear = name => { + delete this[name + 'Mark'] + } + + const callback = (callbackSymbol, start, end, ui8a) => { + if (start === undefined || start !== end) { + this[callbackSymbol](ui8a && ui8a.subarray(start, end)) + } + } + + const dataCallback = (name, clear) => { + const markSymbol = name + 'Mark' + if (!(markSymbol in this)) { + return + } + + if (clear) { + callback(name, this[markSymbol], i, data) + delete this[markSymbol] + } else { + callback(name, this[markSymbol], data.length, data) + this[markSymbol] = 0 + } + } + + for (i = 0; i < length_; i++) { + c = data[i] + + switch (state) { + case S.START_BOUNDARY: + if (index === boundary.length - 2) { + if (c === HYPHEN) { + flags |= F.LAST_BOUNDARY + } else if (c !== CR) { + return + } + + index++ + break + } else if (index - 1 === boundary.length - 2) { + if (flags & F.LAST_BOUNDARY && c === HYPHEN) { + state = S.END + flags = 0 + } else if (!(flags & F.LAST_BOUNDARY) && c === LF) { + index = 0 + callback('onPartBegin') + state = S.HEADER_FIELD_START + } else { + return + } + + break + } + + if (c !== boundary[index + 2]) { + index = -2 + } + + if (c === boundary[index + 2]) { + index++ + } + + break + case S.HEADER_FIELD_START: + state = S.HEADER_FIELD + mark('onHeaderField') + index = 0 + // falls through + case S.HEADER_FIELD: + if (c === CR) { + clear('onHeaderField') + state = S.HEADERS_ALMOST_DONE + break + } + + index++ + if (c === HYPHEN) { + break + } + + if (c === COLON) { + if (index === 1) { + // empty header field + return + } + + dataCallback('onHeaderField', true) + state = S.HEADER_VALUE_START + break + } + + cl = lower(c) + if (cl < A || cl > Z) { + return + } + + break + case S.HEADER_VALUE_START: + if (c === SPACE) { + break + } + + mark('onHeaderValue') + state = S.HEADER_VALUE + // falls through + case S.HEADER_VALUE: + if (c === CR) { + dataCallback('onHeaderValue', true) + callback('onHeaderEnd') + state = S.HEADER_VALUE_ALMOST_DONE + } + + break + case S.HEADER_VALUE_ALMOST_DONE: + if (c !== LF) { + return + } + + state = S.HEADER_FIELD_START + break + case S.HEADERS_ALMOST_DONE: + if (c !== LF) { + return + } + + callback('onHeadersEnd') + state = S.PART_DATA_START + break + case S.PART_DATA_START: + state = S.PART_DATA + mark('onPartData') + // falls through + case S.PART_DATA: + previousIndex = index + + if (index === 0) { + // boyer-moore derrived algorithm to safely skip non-boundary data + i += boundaryEnd + while (i < bufferLength && !(data[i] in boundaryChars)) { + i += boundaryLength + } + + i -= boundaryEnd + c = data[i] + } + + if (index < boundary.length) { + if (boundary[index] === c) { + if (index === 0) { + dataCallback('onPartData', true) + } + + index++ + } else { + index = 0 + } + } else if (index === boundary.length) { + index++ + if (c === CR) { + // CR = part boundary + flags |= F.PART_BOUNDARY + } else if (c === HYPHEN) { + // HYPHEN = end boundary + flags |= F.LAST_BOUNDARY + } else { + index = 0 + } + } else if (index - 1 === boundary.length) { + if (flags & F.PART_BOUNDARY) { + index = 0 + if (c === LF) { + // unset the PART_BOUNDARY flag + flags &= ~F.PART_BOUNDARY + callback('onPartEnd') + callback('onPartBegin') + state = S.HEADER_FIELD_START + break + } + } else if (flags & F.LAST_BOUNDARY) { + if (c === HYPHEN) { + callback('onPartEnd') + state = S.END + flags = 0 + } else { + index = 0 + } + } else { + index = 0 + } + } + + if (index > 0) { + // when matching a possible boundary, keep a lookbehind reference + // in case it turns out to be a false lead + lookbehind[index - 1] = c + } else if (previousIndex > 0) { + // if our boundary turned out to be rubbish, the captured lookbehind + // belongs to partData + const _lookbehind = new Uint8Array( + lookbehind.buffer, + lookbehind.byteOffset, + lookbehind.byteLength + ) + callback('onPartData', 0, previousIndex, _lookbehind) + previousIndex = 0 + mark('onPartData') + + // reconsider the current character even so it interrupted the sequence + // it could be the beginning of a new sequence + i-- + } + + break + case S.END: + break + default: + throw new Error(`Unexpected state entered: ${state}`) + } + } + + dataCallback('onHeaderField') + dataCallback('onHeaderValue') + dataCallback('onPartData') + + // Update properties for the next call + this.index = index + this.state = state + this.flags = flags + } + + end() { + if ( + (this.state === S.HEADER_FIELD_START && this.index === 0) || + (this.state === S.PART_DATA && this.index === this.boundary.length) + ) { + this.onPartEnd() + } else if (this.state !== S.END) { + throw new Error('MultipartParser.end(): stream ended unexpectedly') + } + } + } + + function _fileName(headerValue) { + // matches either a quoted-string or a token (RFC 2616 section 19.5.1) + const m = headerValue.match( + /\bfilename=("(.*?)"|([^()<>@,;:\\"/[\]?={}\s\t]+))($|;\s)/i + ) + if (!m) { + return + } + + const match = m[2] || m[3] || '' + let filename = match.slice(match.lastIndexOf('\\') + 1) + filename = filename.replace(/%22/g, '"') + filename = filename.replace(/&#(\d{4});/g, (m, code) => { + return String.fromCharCode(code) + }) + return filename + } + + async function toFormData(Body, ct) { + if (!/multipart/i.test(ct)) { + throw new TypeError('Failed to fetch') + } + + const m = ct.match(/boundary=(?:"([^"]+)"|([^;]+))/i) + + if (!m) { + throw new TypeError( + 'no or bad content-type header, no multipart boundary' + ) + } + + const parser = new MultipartParser(m[1] || m[2]) + + let headerField + let headerValue + let entryValue + let entryName + let contentType + let filename + const entryChunks = [] + const formData = new formdata_polyfill_esm_min_js__WEBPACK_IMPORTED_MODULE_1__ /* .FormData */.fS() + + const onPartData = ui8a => { + entryValue += decoder.decode(ui8a, { stream: true }) + } + + const appendToFile = ui8a => { + entryChunks.push(ui8a) + } + + const appendFileToFormData = () => { + const file = new fetch_blob_from_js__WEBPACK_IMPORTED_MODULE_0__ /* .File */.ZH( + entryChunks, + filename, + { type: contentType } + ) + formData.append(entryName, file) + } + + const appendEntryToFormData = () => { + formData.append(entryName, entryValue) + } + + const decoder = new TextDecoder('utf-8') + decoder.decode() + + parser.onPartBegin = function () { + parser.onPartData = onPartData + parser.onPartEnd = appendEntryToFormData + + headerField = '' + headerValue = '' + entryValue = '' + entryName = '' + contentType = '' + filename = null + entryChunks.length = 0 + } + + parser.onHeaderField = function (ui8a) { + headerField += decoder.decode(ui8a, { stream: true }) + } + + parser.onHeaderValue = function (ui8a) { + headerValue += decoder.decode(ui8a, { stream: true }) + } + + parser.onHeaderEnd = function () { + headerValue += decoder.decode() + headerField = headerField.toLowerCase() + + if (headerField === 'content-disposition') { + // matches either a quoted-string or a token (RFC 2616 section 19.5.1) + const m = headerValue.match( + /\bname=("([^"]*)"|([^()<>@,;:\\"/[\]?={}\s\t]+))/i + ) + + if (m) { + entryName = m[2] || m[3] || '' + } + + filename = _fileName(headerValue) + + if (filename) { + parser.onPartData = appendToFile + parser.onPartEnd = appendFileToFormData + } + } else if (headerField === 'content-type') { + contentType = headerValue + } + + headerValue = '' + headerField = '' + } + + for await (const chunk of Body) { + parser.write(chunk) + } + + parser.end() + + return formData + } + + /***/ + }, +} diff --git a/.github/actions/odd-resource-analysis/dist/index.js b/.github/actions/odd-resource-analysis/dist/index.js new file mode 100644 index 00000000000..d1f8bd6cfff --- /dev/null +++ b/.github/actions/odd-resource-analysis/dist/index.js @@ -0,0 +1,35543 @@ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 1548: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const { + parseMixpanelData, + getISODatesForPastMonth, + getMixpanelResourceMonitorDataFor, + downloadAppManifest, + getPrevValidVersions, + latestValidVersionFromManifest, +} = __nccwpck_require__(3937) +const { analyzeCorrelation } = __nccwpck_require__(725) +const { + AGGREGATED_PROCESSES, + AGGREGATED_PROCESS_NAMES, + BLACKLISTED_PROCESSES, + MINIMUM_VALID_SAMPLE_SIZE, +} = __nccwpck_require__(2038) + +const UPTIME_BUCKETS = [ + { min: 0, max: 20, label: '0-20hrs' }, + { min: 20, max: 40, label: '20-40hrs' }, + { min: 40, max: 60, label: '40-60hrs' }, + { min: 60, max: 80, label: '60-80hrs' }, + { min: 80, max: 120, label: '80-120hrs' }, + { min: 120, max: 240, label: '120-240hrs' }, + { min: 240, max: Infinity, label: '240+hrs' }, +] + +/** + * @description Calculate average memory usage for measurements within a specific time range + * @param measurements Array of measurements with uptime and a memory metric + * @param minHours Minimum hours (inclusive) + * @param maxHours Maximum hours (exclusive) + * @param memoryMetric The field to average ('memRssMb' or 'systemAvailMemMb') + * @returns {number | null} Average memory usage or null if no measurements in range + */ +function calculateAverageMemoryForRange( + measurements, + minHours, + maxHours, + memoryMetric = 'memRssMb' +) { + const inRange = measurements.filter( + m => m.uptime >= minHours && m.uptime < maxHours + ) + + if (inRange.length === 0 || inRange.length < MINIMUM_VALID_SAMPLE_SIZE) { + return null + } + + const sum = inRange.reduce((acc, m) => acc + m[memoryMetric], 0) + return sum / inRange.length +} + +/** + * @description Calculate memory usage averages across all defined ranges + * @param measurements Array of measurements with uptime and the memory metric + * @param memoryMetric The field to average ('memRssMb' or 'systemAvailMemMb') + * @returns {Object} Contains averages for each range + */ +function calculateRangeAverages(measurements, memoryMetric = 'memRssMb') { + const averages = {} + UPTIME_BUCKETS.forEach(range => { + const avg = calculateAverageMemoryForRange( + measurements, + range.min, + range.max, + memoryMetric + ) + averages[range.label] = + avg !== null ? avg.toFixed(2) : 'N/A - Not enough data available.' + }) + return averages +} + +/** + * @description Filter the Mixpanel data for the data relevant for memory analysis, aggregating data for certain processes + * and ignoring data for blacklisted processes. + * @param data Mixpanel data. + * @return A tuple of memory data by process and general ODD system memory. + */ +function processMixpanelData(data) { + const processByName = new Map() + const systemMemory = [] + + data.forEach(entry => { + const { + systemUptimeHrs, + systemAvailMemMb, + processesDetails, + } = entry.properties + const uptime = parseFloat(systemUptimeHrs) + + // Validate uptime before adding any measurements + if (isNaN(uptime)) { + return + } + + // Ensure system mem is a valid number before adding it. + const availMemMb = parseFloat(systemAvailMemMb) + if (!isNaN(availMemMb)) { + systemMemory.push({ + uptime, + systemAvailMemMb: availMemMb, + }) + } + + processesDetails.forEach(process => { + const isBlacklisted = BLACKLISTED_PROCESSES.some(pattern => + pattern.test(process.name) + ) + + if (!isBlacklisted) { + let processKey = process.name + // Certain processes are aggregated. + for (const { pattern, key } of AGGREGATED_PROCESSES) { + if (pattern.test(process.name)) { + processKey = key + break + } + } + + const memRssMb = parseFloat(process.memRssMb) + if (!isNaN(memRssMb)) { + if (!processByName.has(processKey)) { + processByName.set(processKey, []) + } + processByName.get(processKey).push({ + memRssMb, + uptime, + }) + } + } + }) + }) + + return [processByName, systemMemory] +} + +/** + * @description Group data by process name and calculate correlation and range averages + * @param data See `analyzeMemoryTrends` + */ +function analyzeProcessMemoryTrends(data) { + const [processByName, systemMemory] = processMixpanelData(data) + + // Filter out any process that has less than the minimum sample size + for (const [processName, measurements] of processByName.entries()) { + if (measurements.length < MINIMUM_VALID_SAMPLE_SIZE) { + processByName.delete(processName) + } + } + + // Calculate correlation coefficient and range averages for each process + const results = new Map() + processByName.forEach((measurements, processName) => { + const analysis = analyzeCorrelation( + measurements.map(m => m.uptime), + measurements.map(m => m.memRssMb) + ) + + results.set(processName, { + correlation: analysis.correlation, + sampleSize: analysis.sampleSize, + interpretation: analysis.interpretation, + averageMemoryMbByUptime: calculateRangeAverages(measurements, 'memRssMb'), + }) + }) + + // Calculate system memory metrics + const systemAnalysis = analyzeCorrelation( + systemMemory.map(m => m.uptime), + systemMemory.map(m => m.systemAvailMemMb) + ) + + results.set('odd-available-memory', { + correlation: systemAnalysis.correlation, + sampleSize: systemAnalysis.sampleSize, + interpretation: systemAnalysis.interpretation, + averageMemoryMbByUptime: calculateRangeAverages( + systemMemory, + 'systemAvailMemMb' + ), + }) + + // Filter out any process with a negative correlation except for a few key ones. + for (const [processName, memResults] of results.entries()) { + if ( + memResults.correlation < 0 && + processName !== 'odd-available-memory' && + ![ + AGGREGATED_PROCESS_NAMES.APP_RENDERER, + AGGREGATED_PROCESS_NAMES.SERVER_UVICORN, + ].includes(processName) + ) { + results.delete(processName) + } + } + + return results +} + +/** + * @description Post-process mixpanel data, returning statistical summaries per process + * @param mixpanelData Each entry is expected to contain a top-level 'properties' field with relevant subfields. + */ +function analyzeMemoryTrends(mixpanelData) { + const parsedData = parseMixpanelData(mixpanelData) + const results = analyzeProcessMemoryTrends(parsedData) + + const analysis = {} + results.forEach((result, processName) => { + analysis[processName] = { + correlation: result.correlation.toFixed(4), + sampleSize: result.sampleSize, + interpretation: result.interpretation, + averageMemoryMbByUptime: result.averageMemoryMbByUptime, + } + }) + + return analysis +} + +/** + * @description The 'where' used as a segmentation expression for Mixpanel data filtering. + */ +function buildWhere(version) { + return `properties["appVersion"]=="${version}" and properties["appMode"]=="ODD"` +} + +/** + * @description Analyze memory trends across multiple versions + * @param {number} previousVersionCount Number of previous versions to analyze + * @param {string} uname Mixpanel service account username. + * @param {string} pwd Mixpanel service account password. + * @param {string} projectId Mixpanel project id. + */ +async function analyzeMemoryTrendsAcrossVersions({ + previousVersionCount, + uname, + pwd, + projectId, +}) { + const manifest = await downloadAppManifest() + const latestValidVersion = latestValidVersionFromManifest(manifest) + const prevValidVersions = getPrevValidVersions( + manifest, + latestValidVersion, + previousVersionCount + ) + const analysisPeriod = getISODatesForPastMonth() + + // Populate backup messaging if there's no data available for a specific version + const noDataAvailableStr = 'N/A - No data available' + const results = { + [latestValidVersion]: noDataAvailableStr, + } + prevValidVersions.forEach(version => { + results[version] = noDataAvailableStr + }) + + // Analyze latest version + const currentVersionData = await getMixpanelResourceMonitorDataFor({ + version: latestValidVersion, + uname, + pwd, + projectId, + fromDate: analysisPeriod.from, + toDate: analysisPeriod.to, + where: buildWhere(latestValidVersion), + }) + + if (currentVersionData) { + results[latestValidVersion] = analyzeMemoryTrends(currentVersionData) + } + + // Analyze previous versions + for (const version of prevValidVersions) { + const versionData = await getMixpanelResourceMonitorDataFor({ + version, + uname, + pwd, + projectId, + fromDate: analysisPeriod.from, + toDate: analysisPeriod.to, + where: buildWhere(version), + }) + + if (versionData) { + results[version] = analyzeMemoryTrends(versionData) + } + } + + return results +} + +module.exports = { analyzeMemoryTrendsAcrossVersions } + + +/***/ }), + +/***/ 725: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const calculateCorrelation = __nccwpck_require__(133) +const { MINIMUM_VALID_SAMPLE_SIZE } = __nccwpck_require__(2038) + +const P_VALUE_THRESHOLD = 0.05 + +const CORRELATION_THRESHOLDS = { + STRONG: 0.7, + MODERATE: 0.3, +} + +/** + * @description Calculate significance of Pearson correlation coefficient using t-distribution approximation + * @param {number} correlation Pearson correlation coefficient + * @param {number} sampleSize Number of samples + * @returns {number} One-tailed p-value + */ +function calculatePValue(correlation, sampleSize) { + // Convert correlation coefficient to t-statistic + const t = correlation * Math.sqrt((sampleSize - 2) / (1 - correlation ** 2)) + + // Degrees of freedom + const df = sampleSize - 2 + + const x = df / (df + t * t) + return 0.5 * Math.pow(x, df / 2) +} + +/** + * @description Determines correlation strength and direction + * @param {number} correlation Pearson correlation coefficient + * @returns {string} Human readable interpretation + */ +function getCorrelationDescription(correlation) { + const strength = Math.abs(correlation) + const direction = correlation > 0 ? 'positive' : 'negative' + + if (strength > CORRELATION_THRESHOLDS.STRONG) { + return `Strong ${direction} correlation (>${CORRELATION_THRESHOLDS.STRONG})` + } else if (strength > CORRELATION_THRESHOLDS.MODERATE) { + return `Moderate ${direction} correlation (>${CORRELATION_THRESHOLDS.MODERATE} and <${CORRELATION_THRESHOLDS.STRONG})` + } + return `Weak ${direction} correlation (<=${CORRELATION_THRESHOLDS.MODERATE})` +} + +/** + * @description Performs complete correlation analysis including significance testing + * @param {Array} x Array of numbers + * @param {Array} y Array of numbers + * @return {Object} Analysis results including correlation, significance, and interpretation + */ +function analyzeCorrelation(x, y) { + const lowestSampleSize = Math.min(x.length, y.length) + + if (lowestSampleSize < MINIMUM_VALID_SAMPLE_SIZE) { + return { + correlation: 0, + isSignificant: false, + sampleSize: lowestSampleSize, + pValue: 1, + interpretation: 'Not enough samples for analysis', + } + } + + const correlation = calculateCorrelation(x, y, { decimals: 4 }) + const pValue = calculatePValue(correlation, lowestSampleSize) + const isSignificant = pValue < P_VALUE_THRESHOLD + + return { + correlation, + isSignificant, + sampleSize: x.length, + pValue, + interpretation: isSignificant + ? getCorrelationDescription(correlation) + : 'No significant correlation found', + } +} + +module.exports = { + analyzeCorrelation, +} + + +/***/ }), + +/***/ 2038: +/***/ ((module) => { + +const AGGREGATED_PROCESS_NAMES = { + APP_RENDERER: 'app-renderer-processes', + APP_ZYGOTE: 'app-zygote-processes', + SERVER_UVICORN: 'robot-server-uvicorn-processes', + APP_UTILITY: 'app-utility-processes', +} + +/** + * @description Several processes we care about execute with a lot of unique sub args determined at + * runtime. These processes are aggregated using a regex pattern. + */ +const AGGREGATED_PROCESSES = [ + { + pattern: /^\/opt\/opentrons-app\/opentrons --type=renderer/, + key: AGGREGATED_PROCESS_NAMES.APP_RENDERER, + }, + { + pattern: /^\/opt\/opentrons-app\/opentrons --type=zygote/, + key: AGGREGATED_PROCESS_NAMES.APP_ZYGOTE, + }, + { + pattern: /^python3 -m uvicorn/, + key: AGGREGATED_PROCESS_NAMES.SERVER_UVICORN, + }, + { + pattern: /^\/opt\/opentrons-app\/opentrons --type=utility/, + key: AGGREGATED_PROCESS_NAMES.APP_UTILITY, + }, +] + +/** + * @description Generally don't include any variation of external processes in analysis. + */ +const BLACKLISTED_PROCESSES = [/^nmcli/, /^\/usr\/bin\/python3/] + +/** + * @description For Pearson's, it's generally recommended to use a sample size of at least n=30. + */ +const MINIMUM_VALID_SAMPLE_SIZE = 30 + +const P_VALUE_SIGNIFICANCE_THRESHOLD = 0.05 + +module.exports = { + AGGREGATED_PROCESSES, + AGGREGATED_PROCESS_NAMES, + BLACKLISTED_PROCESSES, + MINIMUM_VALID_SAMPLE_SIZE, + P_VALUE_SIGNIFICANCE_THRESHOLD, +} + + +/***/ }), + +/***/ 9417: +/***/ ((module) => { + +/** + * @description Get ISO date strings for the past month from yesterday. + */ +function getISODatesForPastMonth() { + const now = new Date() + // Don't use today's data, because the Mixpanel API seemingly doesn't use UTC timestamps, and + // it's easy to fail a request depending on the time of day it's made. + const yesterday = new Date(now.setDate(now.getDate() - 1)) + const formatDate = date => date.toISOString().split('T')[0] + + const monthAgo = new Date(yesterday) + monthAgo.setMonth(yesterday.getMonth() - 1) + + return { + from: formatDate(monthAgo), + to: formatDate(yesterday), + } +} + +module.exports = { + getISODatesForPastMonth, +} + + +/***/ }), + +/***/ 3937: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +module.exports = { + ...__nccwpck_require__(9417), + ...__nccwpck_require__(7440), + ...__nccwpck_require__(9437), +} + + +/***/ }), + +/***/ 7440: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const fetch = __nccwpck_require__(6705) + +const APP_MANIFEST = 'https://builds.opentrons.com/ot3-oe/releases.json' + +async function downloadAppManifest() { + const response = await fetch(APP_MANIFEST) + return await response.json() +} + +/** + * @description Get the most recent app version that is not revoked. + * @param manifest The app manifest + */ +function latestValidVersionFromManifest(manifest) { + const versions = Object.keys(manifest.production) + const latestValidVersion = versions.findLast( + version => !('revoked' in manifest.production[version]) + ) + + if (latestValidVersion != null) { + return latestValidVersion + } else { + throw new Error('No valid versions found') + } +} + +/** + * @description Get `count` latest, previous non revoked versions relative to the latest version. + * @param manifest The app manifest + * @param latestVersion The latest valid version + * @param count Number of previous versions to return + * @returns {string[]} Array of version strings, ordered from newest to oldest + */ +function getPrevValidVersions(manifest, latestVersion, count) { + const versions = Object.keys(manifest.production) + const latestIndex = versions.indexOf(latestVersion) + + if (latestIndex === -1) { + throw new Error('Latest version not found in manifest') + } + + return versions + .slice(0, latestIndex) + .filter(version => !manifest.production[version].revoked) + .slice(-count) + .reverse() +} +module.exports = { + downloadAppManifest, + latestValidVersionFromManifest, + getPrevValidVersions, +} + + +/***/ }), + +/***/ 9437: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const fetch = __nccwpck_require__(6705) + +const MIXPANEL_URL = 'https://data.mixpanel.com/api/2.0/export' + +/** + * @description Base64 encode a username and password in + * @param uname Mixpanel service account username. + * @param pwd Mixpanel service account password. + * @return {string} + */ +function encodeCredentialsForMixpanel(uname, pwd) { + return Buffer.from(`${uname}:${pwd}`).toString('base64') +} + +/** + * @description Cleans up Mixpanel data for post-processing. + * @param data Mixpanel data + */ +function parseMixpanelData(data) { + const lines = data.split('\n').filter(line => line.trim()) + return lines.map(line => JSON.parse(line)) +} + +/** + * @description Make the network request to Mixpanel. + */ +async function getMixpanelResourceMonitorDataFor({ + uname, + pwd, + projectId, + fromDate, + toDate, + where, +}) { + const params = new URLSearchParams({ + project_id: parseInt(projectId), + from_date: fromDate, + to_date: toDate, + event: '["resourceMonitorReport"]', + where, + }) + + const options = { + method: 'GET', + headers: { + 'Accept-Encoding': 'gzip', + accept: 'text/plain', + authorization: `Basic ${encodeCredentialsForMixpanel(uname, pwd)}`, + }, + } + + const response = await fetch(`${MIXPANEL_URL}?${params}`, options) + const text = await response.text() + if (!response.ok) { + throw new Error( + `Mixpanel request failed: ${response.status}, ${response.statusText}, ${text}` + ) + } + return text +} + +module.exports = { + getMixpanelResourceMonitorDataFor, + parseMixpanelData, +} + + +/***/ }), + +/***/ 4914: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.issue = exports.issueCommand = void 0; +const os = __importStar(__nccwpck_require__(857)); +const utils_1 = __nccwpck_require__(302); +/** + * Commands + * + * Command Format: + * ::name key=value,key=value::message + * + * Examples: + * ::warning::This is the message + * ::set-env name=MY_VAR::some value + */ +function issueCommand(command, properties, message) { + const cmd = new Command(command, properties, message); + process.stdout.write(cmd.toString() + os.EOL); +} +exports.issueCommand = issueCommand; +function issue(name, message = '') { + issueCommand(name, {}, message); +} +exports.issue = issue; +const CMD_STRING = '::'; +class Command { + constructor(command, properties, message) { + if (!command) { + command = 'missing.command'; + } + this.command = command; + this.properties = properties; + this.message = message; + } + toString() { + let cmdStr = CMD_STRING + this.command; + if (this.properties && Object.keys(this.properties).length > 0) { + cmdStr += ' '; + let first = true; + for (const key in this.properties) { + if (this.properties.hasOwnProperty(key)) { + const val = this.properties[key]; + if (val) { + if (first) { + first = false; + } + else { + cmdStr += ','; + } + cmdStr += `${key}=${escapeProperty(val)}`; + } + } + } + } + cmdStr += `${CMD_STRING}${escapeData(this.message)}`; + return cmdStr; + } +} +function escapeData(s) { + return (0, utils_1.toCommandValue)(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); +} +function escapeProperty(s) { + return (0, utils_1.toCommandValue)(s) + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A') + .replace(/:/g, '%3A') + .replace(/,/g, '%2C'); +} +//# sourceMappingURL=command.js.map + +/***/ }), + +/***/ 7484: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.platform = exports.toPlatformPath = exports.toWin32Path = exports.toPosixPath = exports.markdownSummary = exports.summary = exports.getIDToken = exports.getState = exports.saveState = exports.group = exports.endGroup = exports.startGroup = exports.info = exports.notice = exports.warning = exports.error = exports.debug = exports.isDebug = exports.setFailed = exports.setCommandEcho = exports.setOutput = exports.getBooleanInput = exports.getMultilineInput = exports.getInput = exports.addPath = exports.setSecret = exports.exportVariable = exports.ExitCode = void 0; +const command_1 = __nccwpck_require__(4914); +const file_command_1 = __nccwpck_require__(4753); +const utils_1 = __nccwpck_require__(302); +const os = __importStar(__nccwpck_require__(857)); +const path = __importStar(__nccwpck_require__(6928)); +const oidc_utils_1 = __nccwpck_require__(5306); +/** + * The code to exit an action + */ +var ExitCode; +(function (ExitCode) { + /** + * A code indicating that the action was successful + */ + ExitCode[ExitCode["Success"] = 0] = "Success"; + /** + * A code indicating that the action was a failure + */ + ExitCode[ExitCode["Failure"] = 1] = "Failure"; +})(ExitCode || (exports.ExitCode = ExitCode = {})); +//----------------------------------------------------------------------- +// Variables +//----------------------------------------------------------------------- +/** + * Sets env variable for this action and future actions in the job + * @param name the name of the variable to set + * @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function exportVariable(name, val) { + const convertedVal = (0, utils_1.toCommandValue)(val); + process.env[name] = convertedVal; + const filePath = process.env['GITHUB_ENV'] || ''; + if (filePath) { + return (0, file_command_1.issueFileCommand)('ENV', (0, file_command_1.prepareKeyValueMessage)(name, val)); + } + (0, command_1.issueCommand)('set-env', { name }, convertedVal); +} +exports.exportVariable = exportVariable; +/** + * Registers a secret which will get masked from logs + * @param secret value of the secret + */ +function setSecret(secret) { + (0, command_1.issueCommand)('add-mask', {}, secret); +} +exports.setSecret = setSecret; +/** + * Prepends inputPath to the PATH (for this action and future actions) + * @param inputPath + */ +function addPath(inputPath) { + const filePath = process.env['GITHUB_PATH'] || ''; + if (filePath) { + (0, file_command_1.issueFileCommand)('PATH', inputPath); + } + else { + (0, command_1.issueCommand)('add-path', {}, inputPath); + } + process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`; +} +exports.addPath = addPath; +/** + * Gets the value of an input. + * Unless trimWhitespace is set to false in InputOptions, the value is also trimmed. + * Returns an empty string if the value is not defined. + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns string + */ +function getInput(name, options) { + const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || ''; + if (options && options.required && !val) { + throw new Error(`Input required and not supplied: ${name}`); + } + if (options && options.trimWhitespace === false) { + return val; + } + return val.trim(); +} +exports.getInput = getInput; +/** + * Gets the values of an multiline input. Each value is also trimmed. + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns string[] + * + */ +function getMultilineInput(name, options) { + const inputs = getInput(name, options) + .split('\n') + .filter(x => x !== ''); + if (options && options.trimWhitespace === false) { + return inputs; + } + return inputs.map(input => input.trim()); +} +exports.getMultilineInput = getMultilineInput; +/** + * Gets the input value of the boolean type in the YAML 1.2 "core schema" specification. + * Support boolean input list: `true | True | TRUE | false | False | FALSE` . + * The return value is also in boolean type. + * ref: https://yaml.org/spec/1.2/spec.html#id2804923 + * + * @param name name of the input to get + * @param options optional. See InputOptions. + * @returns boolean + */ +function getBooleanInput(name, options) { + const trueValue = ['true', 'True', 'TRUE']; + const falseValue = ['false', 'False', 'FALSE']; + const val = getInput(name, options); + if (trueValue.includes(val)) + return true; + if (falseValue.includes(val)) + return false; + throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${name}\n` + + `Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); +} +exports.getBooleanInput = getBooleanInput; +/** + * Sets the value of an output. + * + * @param name name of the output to set + * @param value value to store. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function setOutput(name, value) { + const filePath = process.env['GITHUB_OUTPUT'] || ''; + if (filePath) { + return (0, file_command_1.issueFileCommand)('OUTPUT', (0, file_command_1.prepareKeyValueMessage)(name, value)); + } + process.stdout.write(os.EOL); + (0, command_1.issueCommand)('set-output', { name }, (0, utils_1.toCommandValue)(value)); +} +exports.setOutput = setOutput; +/** + * Enables or disables the echoing of commands into stdout for the rest of the step. + * Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set. + * + */ +function setCommandEcho(enabled) { + (0, command_1.issue)('echo', enabled ? 'on' : 'off'); +} +exports.setCommandEcho = setCommandEcho; +//----------------------------------------------------------------------- +// Results +//----------------------------------------------------------------------- +/** + * Sets the action status to failed. + * When the action exits it will be with an exit code of 1 + * @param message add error issue message + */ +function setFailed(message) { + process.exitCode = ExitCode.Failure; + error(message); +} +exports.setFailed = setFailed; +//----------------------------------------------------------------------- +// Logging Commands +//----------------------------------------------------------------------- +/** + * Gets whether Actions Step Debug is on or not + */ +function isDebug() { + return process.env['RUNNER_DEBUG'] === '1'; +} +exports.isDebug = isDebug; +/** + * Writes debug message to user log + * @param message debug message + */ +function debug(message) { + (0, command_1.issueCommand)('debug', {}, message); +} +exports.debug = debug; +/** + * Adds an error issue + * @param message error issue message. Errors will be converted to string via toString() + * @param properties optional properties to add to the annotation. + */ +function error(message, properties = {}) { + (0, command_1.issueCommand)('error', (0, utils_1.toCommandProperties)(properties), message instanceof Error ? message.toString() : message); +} +exports.error = error; +/** + * Adds a warning issue + * @param message warning issue message. Errors will be converted to string via toString() + * @param properties optional properties to add to the annotation. + */ +function warning(message, properties = {}) { + (0, command_1.issueCommand)('warning', (0, utils_1.toCommandProperties)(properties), message instanceof Error ? message.toString() : message); +} +exports.warning = warning; +/** + * Adds a notice issue + * @param message notice issue message. Errors will be converted to string via toString() + * @param properties optional properties to add to the annotation. + */ +function notice(message, properties = {}) { + (0, command_1.issueCommand)('notice', (0, utils_1.toCommandProperties)(properties), message instanceof Error ? message.toString() : message); +} +exports.notice = notice; +/** + * Writes info to log with console.log. + * @param message info message + */ +function info(message) { + process.stdout.write(message + os.EOL); +} +exports.info = info; +/** + * Begin an output group. + * + * Output until the next `groupEnd` will be foldable in this group + * + * @param name The name of the output group + */ +function startGroup(name) { + (0, command_1.issue)('group', name); +} +exports.startGroup = startGroup; +/** + * End an output group. + */ +function endGroup() { + (0, command_1.issue)('endgroup'); +} +exports.endGroup = endGroup; +/** + * Wrap an asynchronous function call in a group. + * + * Returns the same type as the function itself. + * + * @param name The name of the group + * @param fn The function to wrap in the group + */ +function group(name, fn) { + return __awaiter(this, void 0, void 0, function* () { + startGroup(name); + let result; + try { + result = yield fn(); + } + finally { + endGroup(); + } + return result; + }); +} +exports.group = group; +//----------------------------------------------------------------------- +// Wrapper action state +//----------------------------------------------------------------------- +/** + * Saves state for current action, the state can only be retrieved by this action's post job execution. + * + * @param name name of the state to store + * @param value value to store. Non-string values will be converted to a string via JSON.stringify + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function saveState(name, value) { + const filePath = process.env['GITHUB_STATE'] || ''; + if (filePath) { + return (0, file_command_1.issueFileCommand)('STATE', (0, file_command_1.prepareKeyValueMessage)(name, value)); + } + (0, command_1.issueCommand)('save-state', { name }, (0, utils_1.toCommandValue)(value)); +} +exports.saveState = saveState; +/** + * Gets the value of an state set by this action's main execution. + * + * @param name name of the state to get + * @returns string + */ +function getState(name) { + return process.env[`STATE_${name}`] || ''; +} +exports.getState = getState; +function getIDToken(aud) { + return __awaiter(this, void 0, void 0, function* () { + return yield oidc_utils_1.OidcClient.getIDToken(aud); + }); +} +exports.getIDToken = getIDToken; +/** + * Summary exports + */ +var summary_1 = __nccwpck_require__(1847); +Object.defineProperty(exports, "summary", ({ enumerable: true, get: function () { return summary_1.summary; } })); +/** + * @deprecated use core.summary + */ +var summary_2 = __nccwpck_require__(1847); +Object.defineProperty(exports, "markdownSummary", ({ enumerable: true, get: function () { return summary_2.markdownSummary; } })); +/** + * Path exports + */ +var path_utils_1 = __nccwpck_require__(1976); +Object.defineProperty(exports, "toPosixPath", ({ enumerable: true, get: function () { return path_utils_1.toPosixPath; } })); +Object.defineProperty(exports, "toWin32Path", ({ enumerable: true, get: function () { return path_utils_1.toWin32Path; } })); +Object.defineProperty(exports, "toPlatformPath", ({ enumerable: true, get: function () { return path_utils_1.toPlatformPath; } })); +/** + * Platform utilities exports + */ +exports.platform = __importStar(__nccwpck_require__(8968)); +//# sourceMappingURL=core.js.map + +/***/ }), + +/***/ 4753: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +// For internal use, subject to change. +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.prepareKeyValueMessage = exports.issueFileCommand = void 0; +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +const crypto = __importStar(__nccwpck_require__(6982)); +const fs = __importStar(__nccwpck_require__(9896)); +const os = __importStar(__nccwpck_require__(857)); +const utils_1 = __nccwpck_require__(302); +function issueFileCommand(command, message) { + const filePath = process.env[`GITHUB_${command}`]; + if (!filePath) { + throw new Error(`Unable to find environment variable for file command ${command}`); + } + if (!fs.existsSync(filePath)) { + throw new Error(`Missing file at path: ${filePath}`); + } + fs.appendFileSync(filePath, `${(0, utils_1.toCommandValue)(message)}${os.EOL}`, { + encoding: 'utf8' + }); +} +exports.issueFileCommand = issueFileCommand; +function prepareKeyValueMessage(key, value) { + const delimiter = `ghadelimiter_${crypto.randomUUID()}`; + const convertedValue = (0, utils_1.toCommandValue)(value); + // These should realistically never happen, but just in case someone finds a + // way to exploit uuid generation let's not allow keys or values that contain + // the delimiter. + if (key.includes(delimiter)) { + throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`); + } + if (convertedValue.includes(delimiter)) { + throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`); + } + return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`; +} +exports.prepareKeyValueMessage = prepareKeyValueMessage; +//# sourceMappingURL=file-command.js.map + +/***/ }), + +/***/ 5306: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.OidcClient = void 0; +const http_client_1 = __nccwpck_require__(4844); +const auth_1 = __nccwpck_require__(4552); +const core_1 = __nccwpck_require__(7484); +class OidcClient { + static createHttpClient(allowRetry = true, maxRetry = 10) { + const requestOptions = { + allowRetries: allowRetry, + maxRetries: maxRetry + }; + return new http_client_1.HttpClient('actions/oidc-client', [new auth_1.BearerCredentialHandler(OidcClient.getRequestToken())], requestOptions); + } + static getRequestToken() { + const token = process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN']; + if (!token) { + throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable'); + } + return token; + } + static getIDTokenUrl() { + const runtimeUrl = process.env['ACTIONS_ID_TOKEN_REQUEST_URL']; + if (!runtimeUrl) { + throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable'); + } + return runtimeUrl; + } + static getCall(id_token_url) { + var _a; + return __awaiter(this, void 0, void 0, function* () { + const httpclient = OidcClient.createHttpClient(); + const res = yield httpclient + .getJson(id_token_url) + .catch(error => { + throw new Error(`Failed to get ID Token. \n + Error Code : ${error.statusCode}\n + Error Message: ${error.message}`); + }); + const id_token = (_a = res.result) === null || _a === void 0 ? void 0 : _a.value; + if (!id_token) { + throw new Error('Response json body do not have ID Token field'); + } + return id_token; + }); + } + static getIDToken(audience) { + return __awaiter(this, void 0, void 0, function* () { + try { + // New ID Token is requested from action service + let id_token_url = OidcClient.getIDTokenUrl(); + if (audience) { + const encodedAudience = encodeURIComponent(audience); + id_token_url = `${id_token_url}&audience=${encodedAudience}`; + } + (0, core_1.debug)(`ID token url is ${id_token_url}`); + const id_token = yield OidcClient.getCall(id_token_url); + (0, core_1.setSecret)(id_token); + return id_token; + } + catch (error) { + throw new Error(`Error message: ${error.message}`); + } + }); + } +} +exports.OidcClient = OidcClient; +//# sourceMappingURL=oidc-utils.js.map + +/***/ }), + +/***/ 1976: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.toPlatformPath = exports.toWin32Path = exports.toPosixPath = void 0; +const path = __importStar(__nccwpck_require__(6928)); +/** + * toPosixPath converts the given path to the posix form. On Windows, \\ will be + * replaced with /. + * + * @param pth. Path to transform. + * @return string Posix path. + */ +function toPosixPath(pth) { + return pth.replace(/[\\]/g, '/'); +} +exports.toPosixPath = toPosixPath; +/** + * toWin32Path converts the given path to the win32 form. On Linux, / will be + * replaced with \\. + * + * @param pth. Path to transform. + * @return string Win32 path. + */ +function toWin32Path(pth) { + return pth.replace(/[/]/g, '\\'); +} +exports.toWin32Path = toWin32Path; +/** + * toPlatformPath converts the given path to a platform-specific path. It does + * this by replacing instances of / and \ with the platform-specific path + * separator. + * + * @param pth The path to platformize. + * @return string The platform-specific path. + */ +function toPlatformPath(pth) { + return pth.replace(/[/\\]/g, path.sep); +} +exports.toPlatformPath = toPlatformPath; +//# sourceMappingURL=path-utils.js.map + +/***/ }), + +/***/ 8968: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.getDetails = exports.isLinux = exports.isMacOS = exports.isWindows = exports.arch = exports.platform = void 0; +const os_1 = __importDefault(__nccwpck_require__(857)); +const exec = __importStar(__nccwpck_require__(5236)); +const getWindowsInfo = () => __awaiter(void 0, void 0, void 0, function* () { + const { stdout: version } = yield exec.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Version"', undefined, { + silent: true + }); + const { stdout: name } = yield exec.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Caption"', undefined, { + silent: true + }); + return { + name: name.trim(), + version: version.trim() + }; +}); +const getMacOsInfo = () => __awaiter(void 0, void 0, void 0, function* () { + var _a, _b, _c, _d; + const { stdout } = yield exec.getExecOutput('sw_vers', undefined, { + silent: true + }); + const version = (_b = (_a = stdout.match(/ProductVersion:\s*(.+)/)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : ''; + const name = (_d = (_c = stdout.match(/ProductName:\s*(.+)/)) === null || _c === void 0 ? void 0 : _c[1]) !== null && _d !== void 0 ? _d : ''; + return { + name, + version + }; +}); +const getLinuxInfo = () => __awaiter(void 0, void 0, void 0, function* () { + const { stdout } = yield exec.getExecOutput('lsb_release', ['-i', '-r', '-s'], { + silent: true + }); + const [name, version] = stdout.trim().split('\n'); + return { + name, + version + }; +}); +exports.platform = os_1.default.platform(); +exports.arch = os_1.default.arch(); +exports.isWindows = exports.platform === 'win32'; +exports.isMacOS = exports.platform === 'darwin'; +exports.isLinux = exports.platform === 'linux'; +function getDetails() { + return __awaiter(this, void 0, void 0, function* () { + return Object.assign(Object.assign({}, (yield (exports.isWindows + ? getWindowsInfo() + : exports.isMacOS + ? getMacOsInfo() + : getLinuxInfo()))), { platform: exports.platform, + arch: exports.arch, + isWindows: exports.isWindows, + isMacOS: exports.isMacOS, + isLinux: exports.isLinux }); + }); +} +exports.getDetails = getDetails; +//# sourceMappingURL=platform.js.map + +/***/ }), + +/***/ 1847: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.summary = exports.markdownSummary = exports.SUMMARY_DOCS_URL = exports.SUMMARY_ENV_VAR = void 0; +const os_1 = __nccwpck_require__(857); +const fs_1 = __nccwpck_require__(9896); +const { access, appendFile, writeFile } = fs_1.promises; +exports.SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY'; +exports.SUMMARY_DOCS_URL = 'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary'; +class Summary { + constructor() { + this._buffer = ''; + } + /** + * Finds the summary file path from the environment, rejects if env var is not found or file does not exist + * Also checks r/w permissions. + * + * @returns step summary file path + */ + filePath() { + return __awaiter(this, void 0, void 0, function* () { + if (this._filePath) { + return this._filePath; + } + const pathFromEnv = process.env[exports.SUMMARY_ENV_VAR]; + if (!pathFromEnv) { + throw new Error(`Unable to find environment variable for $${exports.SUMMARY_ENV_VAR}. Check if your runtime environment supports job summaries.`); + } + try { + yield access(pathFromEnv, fs_1.constants.R_OK | fs_1.constants.W_OK); + } + catch (_a) { + throw new Error(`Unable to access summary file: '${pathFromEnv}'. Check if the file has correct read/write permissions.`); + } + this._filePath = pathFromEnv; + return this._filePath; + }); + } + /** + * Wraps content in an HTML tag, adding any HTML attributes + * + * @param {string} tag HTML tag to wrap + * @param {string | null} content content within the tag + * @param {[attribute: string]: string} attrs key-value list of HTML attributes to add + * + * @returns {string} content wrapped in HTML element + */ + wrap(tag, content, attrs = {}) { + const htmlAttrs = Object.entries(attrs) + .map(([key, value]) => ` ${key}="${value}"`) + .join(''); + if (!content) { + return `<${tag}${htmlAttrs}>`; + } + return `<${tag}${htmlAttrs}>${content}`; + } + /** + * Writes text in the buffer to the summary buffer file and empties buffer. Will append by default. + * + * @param {SummaryWriteOptions} [options] (optional) options for write operation + * + * @returns {Promise} summary instance + */ + write(options) { + return __awaiter(this, void 0, void 0, function* () { + const overwrite = !!(options === null || options === void 0 ? void 0 : options.overwrite); + const filePath = yield this.filePath(); + const writeFunc = overwrite ? writeFile : appendFile; + yield writeFunc(filePath, this._buffer, { encoding: 'utf8' }); + return this.emptyBuffer(); + }); + } + /** + * Clears the summary buffer and wipes the summary file + * + * @returns {Summary} summary instance + */ + clear() { + return __awaiter(this, void 0, void 0, function* () { + return this.emptyBuffer().write({ overwrite: true }); + }); + } + /** + * Returns the current summary buffer as a string + * + * @returns {string} string of summary buffer + */ + stringify() { + return this._buffer; + } + /** + * If the summary buffer is empty + * + * @returns {boolen} true if the buffer is empty + */ + isEmptyBuffer() { + return this._buffer.length === 0; + } + /** + * Resets the summary buffer without writing to summary file + * + * @returns {Summary} summary instance + */ + emptyBuffer() { + this._buffer = ''; + return this; + } + /** + * Adds raw text to the summary buffer + * + * @param {string} text content to add + * @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false) + * + * @returns {Summary} summary instance + */ + addRaw(text, addEOL = false) { + this._buffer += text; + return addEOL ? this.addEOL() : this; + } + /** + * Adds the operating system-specific end-of-line marker to the buffer + * + * @returns {Summary} summary instance + */ + addEOL() { + return this.addRaw(os_1.EOL); + } + /** + * Adds an HTML codeblock to the summary buffer + * + * @param {string} code content to render within fenced code block + * @param {string} lang (optional) language to syntax highlight code + * + * @returns {Summary} summary instance + */ + addCodeBlock(code, lang) { + const attrs = Object.assign({}, (lang && { lang })); + const element = this.wrap('pre', this.wrap('code', code), attrs); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML list to the summary buffer + * + * @param {string[]} items list of items to render + * @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false) + * + * @returns {Summary} summary instance + */ + addList(items, ordered = false) { + const tag = ordered ? 'ol' : 'ul'; + const listItems = items.map(item => this.wrap('li', item)).join(''); + const element = this.wrap(tag, listItems); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML table to the summary buffer + * + * @param {SummaryTableCell[]} rows table rows + * + * @returns {Summary} summary instance + */ + addTable(rows) { + const tableBody = rows + .map(row => { + const cells = row + .map(cell => { + if (typeof cell === 'string') { + return this.wrap('td', cell); + } + const { header, data, colspan, rowspan } = cell; + const tag = header ? 'th' : 'td'; + const attrs = Object.assign(Object.assign({}, (colspan && { colspan })), (rowspan && { rowspan })); + return this.wrap(tag, data, attrs); + }) + .join(''); + return this.wrap('tr', cells); + }) + .join(''); + const element = this.wrap('table', tableBody); + return this.addRaw(element).addEOL(); + } + /** + * Adds a collapsable HTML details element to the summary buffer + * + * @param {string} label text for the closed state + * @param {string} content collapsable content + * + * @returns {Summary} summary instance + */ + addDetails(label, content) { + const element = this.wrap('details', this.wrap('summary', label) + content); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML image tag to the summary buffer + * + * @param {string} src path to the image you to embed + * @param {string} alt text description of the image + * @param {SummaryImageOptions} options (optional) addition image attributes + * + * @returns {Summary} summary instance + */ + addImage(src, alt, options) { + const { width, height } = options || {}; + const attrs = Object.assign(Object.assign({}, (width && { width })), (height && { height })); + const element = this.wrap('img', null, Object.assign({ src, alt }, attrs)); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML section heading element + * + * @param {string} text heading text + * @param {number | string} [level=1] (optional) the heading level, default: 1 + * + * @returns {Summary} summary instance + */ + addHeading(text, level) { + const tag = `h${level}`; + const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag) + ? tag + : 'h1'; + const element = this.wrap(allowedTag, text); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML thematic break (
) to the summary buffer + * + * @returns {Summary} summary instance + */ + addSeparator() { + const element = this.wrap('hr', null); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML line break (
) to the summary buffer + * + * @returns {Summary} summary instance + */ + addBreak() { + const element = this.wrap('br', null); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML blockquote to the summary buffer + * + * @param {string} text quote text + * @param {string} cite (optional) citation url + * + * @returns {Summary} summary instance + */ + addQuote(text, cite) { + const attrs = Object.assign({}, (cite && { cite })); + const element = this.wrap('blockquote', text, attrs); + return this.addRaw(element).addEOL(); + } + /** + * Adds an HTML anchor tag to the summary buffer + * + * @param {string} text link text/content + * @param {string} href hyperlink + * + * @returns {Summary} summary instance + */ + addLink(text, href) { + const element = this.wrap('a', text, { href }); + return this.addRaw(element).addEOL(); + } +} +const _summary = new Summary(); +/** + * @deprecated use `core.summary` + */ +exports.markdownSummary = _summary; +exports.summary = _summary; +//# sourceMappingURL=summary.js.map + +/***/ }), + +/***/ 302: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +// We use any as a valid input type +/* eslint-disable @typescript-eslint/no-explicit-any */ +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.toCommandProperties = exports.toCommandValue = void 0; +/** + * Sanitizes an input into a string so it can be passed into issueCommand safely + * @param input input to sanitize into a string + */ +function toCommandValue(input) { + if (input === null || input === undefined) { + return ''; + } + else if (typeof input === 'string' || input instanceof String) { + return input; + } + return JSON.stringify(input); +} +exports.toCommandValue = toCommandValue; +/** + * + * @param annotationProperties + * @returns The command properties to send with the actual annotation command + * See IssueCommandProperties: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionCommandManager.cs#L646 + */ +function toCommandProperties(annotationProperties) { + if (!Object.keys(annotationProperties).length) { + return {}; + } + return { + title: annotationProperties.title, + file: annotationProperties.file, + line: annotationProperties.startLine, + endLine: annotationProperties.endLine, + col: annotationProperties.startColumn, + endColumn: annotationProperties.endColumn + }; +} +exports.toCommandProperties = toCommandProperties; +//# sourceMappingURL=utils.js.map + +/***/ }), + +/***/ 5236: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.getExecOutput = exports.exec = void 0; +const string_decoder_1 = __nccwpck_require__(3193); +const tr = __importStar(__nccwpck_require__(6665)); +/** + * Exec a command. + * Output will be streamed to the live console. + * Returns promise with return code + * + * @param commandLine command to execute (can include additional args). Must be correctly escaped. + * @param args optional arguments for tool. Escaping is handled by the lib. + * @param options optional exec options. See ExecOptions + * @returns Promise exit code + */ +function exec(commandLine, args, options) { + return __awaiter(this, void 0, void 0, function* () { + const commandArgs = tr.argStringToArray(commandLine); + if (commandArgs.length === 0) { + throw new Error(`Parameter 'commandLine' cannot be null or empty.`); + } + // Path to tool to execute should be first arg + const toolPath = commandArgs[0]; + args = commandArgs.slice(1).concat(args || []); + const runner = new tr.ToolRunner(toolPath, args, options); + return runner.exec(); + }); +} +exports.exec = exec; +/** + * Exec a command and get the output. + * Output will be streamed to the live console. + * Returns promise with the exit code and collected stdout and stderr + * + * @param commandLine command to execute (can include additional args). Must be correctly escaped. + * @param args optional arguments for tool. Escaping is handled by the lib. + * @param options optional exec options. See ExecOptions + * @returns Promise exit code, stdout, and stderr + */ +function getExecOutput(commandLine, args, options) { + var _a, _b; + return __awaiter(this, void 0, void 0, function* () { + let stdout = ''; + let stderr = ''; + //Using string decoder covers the case where a mult-byte character is split + const stdoutDecoder = new string_decoder_1.StringDecoder('utf8'); + const stderrDecoder = new string_decoder_1.StringDecoder('utf8'); + const originalStdoutListener = (_a = options === null || options === void 0 ? void 0 : options.listeners) === null || _a === void 0 ? void 0 : _a.stdout; + const originalStdErrListener = (_b = options === null || options === void 0 ? void 0 : options.listeners) === null || _b === void 0 ? void 0 : _b.stderr; + const stdErrListener = (data) => { + stderr += stderrDecoder.write(data); + if (originalStdErrListener) { + originalStdErrListener(data); + } + }; + const stdOutListener = (data) => { + stdout += stdoutDecoder.write(data); + if (originalStdoutListener) { + originalStdoutListener(data); + } + }; + const listeners = Object.assign(Object.assign({}, options === null || options === void 0 ? void 0 : options.listeners), { stdout: stdOutListener, stderr: stdErrListener }); + const exitCode = yield exec(commandLine, args, Object.assign(Object.assign({}, options), { listeners })); + //flush any remaining characters + stdout += stdoutDecoder.end(); + stderr += stderrDecoder.end(); + return { + exitCode, + stdout, + stderr + }; + }); +} +exports.getExecOutput = getExecOutput; +//# sourceMappingURL=exec.js.map + +/***/ }), + +/***/ 6665: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.argStringToArray = exports.ToolRunner = void 0; +const os = __importStar(__nccwpck_require__(857)); +const events = __importStar(__nccwpck_require__(4434)); +const child = __importStar(__nccwpck_require__(5317)); +const path = __importStar(__nccwpck_require__(6928)); +const io = __importStar(__nccwpck_require__(4994)); +const ioUtil = __importStar(__nccwpck_require__(5207)); +const timers_1 = __nccwpck_require__(3557); +/* eslint-disable @typescript-eslint/unbound-method */ +const IS_WINDOWS = process.platform === 'win32'; +/* + * Class for running command line tools. Handles quoting and arg parsing in a platform agnostic way. + */ +class ToolRunner extends events.EventEmitter { + constructor(toolPath, args, options) { + super(); + if (!toolPath) { + throw new Error("Parameter 'toolPath' cannot be null or empty."); + } + this.toolPath = toolPath; + this.args = args || []; + this.options = options || {}; + } + _debug(message) { + if (this.options.listeners && this.options.listeners.debug) { + this.options.listeners.debug(message); + } + } + _getCommandString(options, noPrefix) { + const toolPath = this._getSpawnFileName(); + const args = this._getSpawnArgs(options); + let cmd = noPrefix ? '' : '[command]'; // omit prefix when piped to a second tool + if (IS_WINDOWS) { + // Windows + cmd file + if (this._isCmdFile()) { + cmd += toolPath; + for (const a of args) { + cmd += ` ${a}`; + } + } + // Windows + verbatim + else if (options.windowsVerbatimArguments) { + cmd += `"${toolPath}"`; + for (const a of args) { + cmd += ` ${a}`; + } + } + // Windows (regular) + else { + cmd += this._windowsQuoteCmdArg(toolPath); + for (const a of args) { + cmd += ` ${this._windowsQuoteCmdArg(a)}`; + } + } + } + else { + // OSX/Linux - this can likely be improved with some form of quoting. + // creating processes on Unix is fundamentally different than Windows. + // on Unix, execvp() takes an arg array. + cmd += toolPath; + for (const a of args) { + cmd += ` ${a}`; + } + } + return cmd; + } + _processLineBuffer(data, strBuffer, onLine) { + try { + let s = strBuffer + data.toString(); + let n = s.indexOf(os.EOL); + while (n > -1) { + const line = s.substring(0, n); + onLine(line); + // the rest of the string ... + s = s.substring(n + os.EOL.length); + n = s.indexOf(os.EOL); + } + return s; + } + catch (err) { + // streaming lines to console is best effort. Don't fail a build. + this._debug(`error processing line. Failed with error ${err}`); + return ''; + } + } + _getSpawnFileName() { + if (IS_WINDOWS) { + if (this._isCmdFile()) { + return process.env['COMSPEC'] || 'cmd.exe'; + } + } + return this.toolPath; + } + _getSpawnArgs(options) { + if (IS_WINDOWS) { + if (this._isCmdFile()) { + let argline = `/D /S /C "${this._windowsQuoteCmdArg(this.toolPath)}`; + for (const a of this.args) { + argline += ' '; + argline += options.windowsVerbatimArguments + ? a + : this._windowsQuoteCmdArg(a); + } + argline += '"'; + return [argline]; + } + } + return this.args; + } + _endsWith(str, end) { + return str.endsWith(end); + } + _isCmdFile() { + const upperToolPath = this.toolPath.toUpperCase(); + return (this._endsWith(upperToolPath, '.CMD') || + this._endsWith(upperToolPath, '.BAT')); + } + _windowsQuoteCmdArg(arg) { + // for .exe, apply the normal quoting rules that libuv applies + if (!this._isCmdFile()) { + return this._uvQuoteCmdArg(arg); + } + // otherwise apply quoting rules specific to the cmd.exe command line parser. + // the libuv rules are generic and are not designed specifically for cmd.exe + // command line parser. + // + // for a detailed description of the cmd.exe command line parser, refer to + // http://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/7970912#7970912 + // need quotes for empty arg + if (!arg) { + return '""'; + } + // determine whether the arg needs to be quoted + const cmdSpecialChars = [ + ' ', + '\t', + '&', + '(', + ')', + '[', + ']', + '{', + '}', + '^', + '=', + ';', + '!', + "'", + '+', + ',', + '`', + '~', + '|', + '<', + '>', + '"' + ]; + let needsQuotes = false; + for (const char of arg) { + if (cmdSpecialChars.some(x => x === char)) { + needsQuotes = true; + break; + } + } + // short-circuit if quotes not needed + if (!needsQuotes) { + return arg; + } + // the following quoting rules are very similar to the rules that by libuv applies. + // + // 1) wrap the string in quotes + // + // 2) double-up quotes - i.e. " => "" + // + // this is different from the libuv quoting rules. libuv replaces " with \", which unfortunately + // doesn't work well with a cmd.exe command line. + // + // note, replacing " with "" also works well if the arg is passed to a downstream .NET console app. + // for example, the command line: + // foo.exe "myarg:""my val""" + // is parsed by a .NET console app into an arg array: + // [ "myarg:\"my val\"" ] + // which is the same end result when applying libuv quoting rules. although the actual + // command line from libuv quoting rules would look like: + // foo.exe "myarg:\"my val\"" + // + // 3) double-up slashes that precede a quote, + // e.g. hello \world => "hello \world" + // hello\"world => "hello\\""world" + // hello\\"world => "hello\\\\""world" + // hello world\ => "hello world\\" + // + // technically this is not required for a cmd.exe command line, or the batch argument parser. + // the reasons for including this as a .cmd quoting rule are: + // + // a) this is optimized for the scenario where the argument is passed from the .cmd file to an + // external program. many programs (e.g. .NET console apps) rely on the slash-doubling rule. + // + // b) it's what we've been doing previously (by deferring to node default behavior) and we + // haven't heard any complaints about that aspect. + // + // note, a weakness of the quoting rules chosen here, is that % is not escaped. in fact, % cannot be + // escaped when used on the command line directly - even though within a .cmd file % can be escaped + // by using %%. + // + // the saving grace is, on the command line, %var% is left as-is if var is not defined. this contrasts + // the line parsing rules within a .cmd file, where if var is not defined it is replaced with nothing. + // + // one option that was explored was replacing % with ^% - i.e. %var% => ^%var^%. this hack would + // often work, since it is unlikely that var^ would exist, and the ^ character is removed when the + // variable is used. the problem, however, is that ^ is not removed when %* is used to pass the args + // to an external program. + // + // an unexplored potential solution for the % escaping problem, is to create a wrapper .cmd file. + // % can be escaped within a .cmd file. + let reverse = '"'; + let quoteHit = true; + for (let i = arg.length; i > 0; i--) { + // walk the string in reverse + reverse += arg[i - 1]; + if (quoteHit && arg[i - 1] === '\\') { + reverse += '\\'; // double the slash + } + else if (arg[i - 1] === '"') { + quoteHit = true; + reverse += '"'; // double the quote + } + else { + quoteHit = false; + } + } + reverse += '"'; + return reverse + .split('') + .reverse() + .join(''); + } + _uvQuoteCmdArg(arg) { + // Tool runner wraps child_process.spawn() and needs to apply the same quoting as + // Node in certain cases where the undocumented spawn option windowsVerbatimArguments + // is used. + // + // Since this function is a port of quote_cmd_arg from Node 4.x (technically, lib UV, + // see https://github.com/nodejs/node/blob/v4.x/deps/uv/src/win/process.c for details), + // pasting copyright notice from Node within this function: + // + // Copyright Joyent, Inc. and other Node contributors. All rights reserved. + // + // Permission is hereby granted, free of charge, to any person obtaining a copy + // of this software and associated documentation files (the "Software"), to + // deal in the Software without restriction, including without limitation the + // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + // sell copies of the Software, and to permit persons to whom the Software is + // furnished to do so, subject to the following conditions: + // + // The above copyright notice and this permission notice shall be included in + // all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + // IN THE SOFTWARE. + if (!arg) { + // Need double quotation for empty argument + return '""'; + } + if (!arg.includes(' ') && !arg.includes('\t') && !arg.includes('"')) { + // No quotation needed + return arg; + } + if (!arg.includes('"') && !arg.includes('\\')) { + // No embedded double quotes or backslashes, so I can just wrap + // quote marks around the whole thing. + return `"${arg}"`; + } + // Expected input/output: + // input : hello"world + // output: "hello\"world" + // input : hello""world + // output: "hello\"\"world" + // input : hello\world + // output: hello\world + // input : hello\\world + // output: hello\\world + // input : hello\"world + // output: "hello\\\"world" + // input : hello\\"world + // output: "hello\\\\\"world" + // input : hello world\ + // output: "hello world\\" - note the comment in libuv actually reads "hello world\" + // but it appears the comment is wrong, it should be "hello world\\" + let reverse = '"'; + let quoteHit = true; + for (let i = arg.length; i > 0; i--) { + // walk the string in reverse + reverse += arg[i - 1]; + if (quoteHit && arg[i - 1] === '\\') { + reverse += '\\'; + } + else if (arg[i - 1] === '"') { + quoteHit = true; + reverse += '\\'; + } + else { + quoteHit = false; + } + } + reverse += '"'; + return reverse + .split('') + .reverse() + .join(''); + } + _cloneExecOptions(options) { + options = options || {}; + const result = { + cwd: options.cwd || process.cwd(), + env: options.env || process.env, + silent: options.silent || false, + windowsVerbatimArguments: options.windowsVerbatimArguments || false, + failOnStdErr: options.failOnStdErr || false, + ignoreReturnCode: options.ignoreReturnCode || false, + delay: options.delay || 10000 + }; + result.outStream = options.outStream || process.stdout; + result.errStream = options.errStream || process.stderr; + return result; + } + _getSpawnOptions(options, toolPath) { + options = options || {}; + const result = {}; + result.cwd = options.cwd; + result.env = options.env; + result['windowsVerbatimArguments'] = + options.windowsVerbatimArguments || this._isCmdFile(); + if (options.windowsVerbatimArguments) { + result.argv0 = `"${toolPath}"`; + } + return result; + } + /** + * Exec a tool. + * Output will be streamed to the live console. + * Returns promise with return code + * + * @param tool path to tool to exec + * @param options optional exec options. See ExecOptions + * @returns number + */ + exec() { + return __awaiter(this, void 0, void 0, function* () { + // root the tool path if it is unrooted and contains relative pathing + if (!ioUtil.isRooted(this.toolPath) && + (this.toolPath.includes('/') || + (IS_WINDOWS && this.toolPath.includes('\\')))) { + // prefer options.cwd if it is specified, however options.cwd may also need to be rooted + this.toolPath = path.resolve(process.cwd(), this.options.cwd || process.cwd(), this.toolPath); + } + // if the tool is only a file name, then resolve it from the PATH + // otherwise verify it exists (add extension on Windows if necessary) + this.toolPath = yield io.which(this.toolPath, true); + return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { + this._debug(`exec tool: ${this.toolPath}`); + this._debug('arguments:'); + for (const arg of this.args) { + this._debug(` ${arg}`); + } + const optionsNonNull = this._cloneExecOptions(this.options); + if (!optionsNonNull.silent && optionsNonNull.outStream) { + optionsNonNull.outStream.write(this._getCommandString(optionsNonNull) + os.EOL); + } + const state = new ExecState(optionsNonNull, this.toolPath); + state.on('debug', (message) => { + this._debug(message); + }); + if (this.options.cwd && !(yield ioUtil.exists(this.options.cwd))) { + return reject(new Error(`The cwd: ${this.options.cwd} does not exist!`)); + } + const fileName = this._getSpawnFileName(); + const cp = child.spawn(fileName, this._getSpawnArgs(optionsNonNull), this._getSpawnOptions(this.options, fileName)); + let stdbuffer = ''; + if (cp.stdout) { + cp.stdout.on('data', (data) => { + if (this.options.listeners && this.options.listeners.stdout) { + this.options.listeners.stdout(data); + } + if (!optionsNonNull.silent && optionsNonNull.outStream) { + optionsNonNull.outStream.write(data); + } + stdbuffer = this._processLineBuffer(data, stdbuffer, (line) => { + if (this.options.listeners && this.options.listeners.stdline) { + this.options.listeners.stdline(line); + } + }); + }); + } + let errbuffer = ''; + if (cp.stderr) { + cp.stderr.on('data', (data) => { + state.processStderr = true; + if (this.options.listeners && this.options.listeners.stderr) { + this.options.listeners.stderr(data); + } + if (!optionsNonNull.silent && + optionsNonNull.errStream && + optionsNonNull.outStream) { + const s = optionsNonNull.failOnStdErr + ? optionsNonNull.errStream + : optionsNonNull.outStream; + s.write(data); + } + errbuffer = this._processLineBuffer(data, errbuffer, (line) => { + if (this.options.listeners && this.options.listeners.errline) { + this.options.listeners.errline(line); + } + }); + }); + } + cp.on('error', (err) => { + state.processError = err.message; + state.processExited = true; + state.processClosed = true; + state.CheckComplete(); + }); + cp.on('exit', (code) => { + state.processExitCode = code; + state.processExited = true; + this._debug(`Exit code ${code} received from tool '${this.toolPath}'`); + state.CheckComplete(); + }); + cp.on('close', (code) => { + state.processExitCode = code; + state.processExited = true; + state.processClosed = true; + this._debug(`STDIO streams have closed for tool '${this.toolPath}'`); + state.CheckComplete(); + }); + state.on('done', (error, exitCode) => { + if (stdbuffer.length > 0) { + this.emit('stdline', stdbuffer); + } + if (errbuffer.length > 0) { + this.emit('errline', errbuffer); + } + cp.removeAllListeners(); + if (error) { + reject(error); + } + else { + resolve(exitCode); + } + }); + if (this.options.input) { + if (!cp.stdin) { + throw new Error('child process missing stdin'); + } + cp.stdin.end(this.options.input); + } + })); + }); + } +} +exports.ToolRunner = ToolRunner; +/** + * Convert an arg string to an array of args. Handles escaping + * + * @param argString string of arguments + * @returns string[] array of arguments + */ +function argStringToArray(argString) { + const args = []; + let inQuotes = false; + let escaped = false; + let arg = ''; + function append(c) { + // we only escape double quotes. + if (escaped && c !== '"') { + arg += '\\'; + } + arg += c; + escaped = false; + } + for (let i = 0; i < argString.length; i++) { + const c = argString.charAt(i); + if (c === '"') { + if (!escaped) { + inQuotes = !inQuotes; + } + else { + append(c); + } + continue; + } + if (c === '\\' && escaped) { + append(c); + continue; + } + if (c === '\\' && inQuotes) { + escaped = true; + continue; + } + if (c === ' ' && !inQuotes) { + if (arg.length > 0) { + args.push(arg); + arg = ''; + } + continue; + } + append(c); + } + if (arg.length > 0) { + args.push(arg.trim()); + } + return args; +} +exports.argStringToArray = argStringToArray; +class ExecState extends events.EventEmitter { + constructor(options, toolPath) { + super(); + this.processClosed = false; // tracks whether the process has exited and stdio is closed + this.processError = ''; + this.processExitCode = 0; + this.processExited = false; // tracks whether the process has exited + this.processStderr = false; // tracks whether stderr was written to + this.delay = 10000; // 10 seconds + this.done = false; + this.timeout = null; + if (!toolPath) { + throw new Error('toolPath must not be empty'); + } + this.options = options; + this.toolPath = toolPath; + if (options.delay) { + this.delay = options.delay; + } + } + CheckComplete() { + if (this.done) { + return; + } + if (this.processClosed) { + this._setResult(); + } + else if (this.processExited) { + this.timeout = timers_1.setTimeout(ExecState.HandleTimeout, this.delay, this); + } + } + _debug(message) { + this.emit('debug', message); + } + _setResult() { + // determine whether there is an error + let error; + if (this.processExited) { + if (this.processError) { + error = new Error(`There was an error when attempting to execute the process '${this.toolPath}'. This may indicate the process failed to start. Error: ${this.processError}`); + } + else if (this.processExitCode !== 0 && !this.options.ignoreReturnCode) { + error = new Error(`The process '${this.toolPath}' failed with exit code ${this.processExitCode}`); + } + else if (this.processStderr && this.options.failOnStdErr) { + error = new Error(`The process '${this.toolPath}' failed because one or more lines were written to the STDERR stream`); + } + } + // clear the timeout + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = null; + } + this.done = true; + this.emit('done', error, this.processExitCode); + } + static HandleTimeout(state) { + if (state.done) { + return; + } + if (!state.processClosed && state.processExited) { + const message = `The STDIO streams did not close within ${state.delay / + 1000} seconds of the exit event from process '${state.toolPath}'. This may indicate a child process inherited the STDIO streams and has not yet exited.`; + state._debug(message); + } + state._setResult(); + } +} +//# sourceMappingURL=toolrunner.js.map + +/***/ }), + +/***/ 4552: +/***/ (function(__unused_webpack_module, exports) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.PersonalAccessTokenCredentialHandler = exports.BearerCredentialHandler = exports.BasicCredentialHandler = void 0; +class BasicCredentialHandler { + constructor(username, password) { + this.username = username; + this.password = password; + } + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.BasicCredentialHandler = BasicCredentialHandler; +class BearerCredentialHandler { + constructor(token) { + this.token = token; + } + // currently implements pre-authorization + // TODO: support preAuth = false where it hooks on 401 + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Bearer ${this.token}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.BearerCredentialHandler = BearerCredentialHandler; +class PersonalAccessTokenCredentialHandler { + constructor(token) { + this.token = token; + } + // currently implements pre-authorization + // TODO: support preAuth = false where it hooks on 401 + prepareRequest(options) { + if (!options.headers) { + throw Error('The request has no headers'); + } + options.headers['Authorization'] = `Basic ${Buffer.from(`PAT:${this.token}`).toString('base64')}`; + } + // This handler cannot handle 401 + canHandleAuthentication() { + return false; + } + handleAuthentication() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error('not implemented'); + }); + } +} +exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler; +//# sourceMappingURL=auth.js.map + +/***/ }), + +/***/ 4844: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.HttpClient = exports.isHttps = exports.HttpClientResponse = exports.HttpClientError = exports.getProxyUrl = exports.MediaTypes = exports.Headers = exports.HttpCodes = void 0; +const http = __importStar(__nccwpck_require__(8611)); +const https = __importStar(__nccwpck_require__(5692)); +const pm = __importStar(__nccwpck_require__(4988)); +const tunnel = __importStar(__nccwpck_require__(770)); +const undici_1 = __nccwpck_require__(6752); +var HttpCodes; +(function (HttpCodes) { + HttpCodes[HttpCodes["OK"] = 200] = "OK"; + HttpCodes[HttpCodes["MultipleChoices"] = 300] = "MultipleChoices"; + HttpCodes[HttpCodes["MovedPermanently"] = 301] = "MovedPermanently"; + HttpCodes[HttpCodes["ResourceMoved"] = 302] = "ResourceMoved"; + HttpCodes[HttpCodes["SeeOther"] = 303] = "SeeOther"; + HttpCodes[HttpCodes["NotModified"] = 304] = "NotModified"; + HttpCodes[HttpCodes["UseProxy"] = 305] = "UseProxy"; + HttpCodes[HttpCodes["SwitchProxy"] = 306] = "SwitchProxy"; + HttpCodes[HttpCodes["TemporaryRedirect"] = 307] = "TemporaryRedirect"; + HttpCodes[HttpCodes["PermanentRedirect"] = 308] = "PermanentRedirect"; + HttpCodes[HttpCodes["BadRequest"] = 400] = "BadRequest"; + HttpCodes[HttpCodes["Unauthorized"] = 401] = "Unauthorized"; + HttpCodes[HttpCodes["PaymentRequired"] = 402] = "PaymentRequired"; + HttpCodes[HttpCodes["Forbidden"] = 403] = "Forbidden"; + HttpCodes[HttpCodes["NotFound"] = 404] = "NotFound"; + HttpCodes[HttpCodes["MethodNotAllowed"] = 405] = "MethodNotAllowed"; + HttpCodes[HttpCodes["NotAcceptable"] = 406] = "NotAcceptable"; + HttpCodes[HttpCodes["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired"; + HttpCodes[HttpCodes["RequestTimeout"] = 408] = "RequestTimeout"; + HttpCodes[HttpCodes["Conflict"] = 409] = "Conflict"; + HttpCodes[HttpCodes["Gone"] = 410] = "Gone"; + HttpCodes[HttpCodes["TooManyRequests"] = 429] = "TooManyRequests"; + HttpCodes[HttpCodes["InternalServerError"] = 500] = "InternalServerError"; + HttpCodes[HttpCodes["NotImplemented"] = 501] = "NotImplemented"; + HttpCodes[HttpCodes["BadGateway"] = 502] = "BadGateway"; + HttpCodes[HttpCodes["ServiceUnavailable"] = 503] = "ServiceUnavailable"; + HttpCodes[HttpCodes["GatewayTimeout"] = 504] = "GatewayTimeout"; +})(HttpCodes || (exports.HttpCodes = HttpCodes = {})); +var Headers; +(function (Headers) { + Headers["Accept"] = "accept"; + Headers["ContentType"] = "content-type"; +})(Headers || (exports.Headers = Headers = {})); +var MediaTypes; +(function (MediaTypes) { + MediaTypes["ApplicationJson"] = "application/json"; +})(MediaTypes || (exports.MediaTypes = MediaTypes = {})); +/** + * Returns the proxy URL, depending upon the supplied url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ +function getProxyUrl(serverUrl) { + const proxyUrl = pm.getProxyUrl(new URL(serverUrl)); + return proxyUrl ? proxyUrl.href : ''; +} +exports.getProxyUrl = getProxyUrl; +const HttpRedirectCodes = [ + HttpCodes.MovedPermanently, + HttpCodes.ResourceMoved, + HttpCodes.SeeOther, + HttpCodes.TemporaryRedirect, + HttpCodes.PermanentRedirect +]; +const HttpResponseRetryCodes = [ + HttpCodes.BadGateway, + HttpCodes.ServiceUnavailable, + HttpCodes.GatewayTimeout +]; +const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD']; +const ExponentialBackoffCeiling = 10; +const ExponentialBackoffTimeSlice = 5; +class HttpClientError extends Error { + constructor(message, statusCode) { + super(message); + this.name = 'HttpClientError'; + this.statusCode = statusCode; + Object.setPrototypeOf(this, HttpClientError.prototype); + } +} +exports.HttpClientError = HttpClientError; +class HttpClientResponse { + constructor(message) { + this.message = message; + } + readBody() { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { + let output = Buffer.alloc(0); + this.message.on('data', (chunk) => { + output = Buffer.concat([output, chunk]); + }); + this.message.on('end', () => { + resolve(output.toString()); + }); + })); + }); + } + readBodyBuffer() { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { + const chunks = []; + this.message.on('data', (chunk) => { + chunks.push(chunk); + }); + this.message.on('end', () => { + resolve(Buffer.concat(chunks)); + }); + })); + }); + } +} +exports.HttpClientResponse = HttpClientResponse; +function isHttps(requestUrl) { + const parsedUrl = new URL(requestUrl); + return parsedUrl.protocol === 'https:'; +} +exports.isHttps = isHttps; +class HttpClient { + constructor(userAgent, handlers, requestOptions) { + this._ignoreSslError = false; + this._allowRedirects = true; + this._allowRedirectDowngrade = false; + this._maxRedirects = 50; + this._allowRetries = false; + this._maxRetries = 1; + this._keepAlive = false; + this._disposed = false; + this.userAgent = userAgent; + this.handlers = handlers || []; + this.requestOptions = requestOptions; + if (requestOptions) { + if (requestOptions.ignoreSslError != null) { + this._ignoreSslError = requestOptions.ignoreSslError; + } + this._socketTimeout = requestOptions.socketTimeout; + if (requestOptions.allowRedirects != null) { + this._allowRedirects = requestOptions.allowRedirects; + } + if (requestOptions.allowRedirectDowngrade != null) { + this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade; + } + if (requestOptions.maxRedirects != null) { + this._maxRedirects = Math.max(requestOptions.maxRedirects, 0); + } + if (requestOptions.keepAlive != null) { + this._keepAlive = requestOptions.keepAlive; + } + if (requestOptions.allowRetries != null) { + this._allowRetries = requestOptions.allowRetries; + } + if (requestOptions.maxRetries != null) { + this._maxRetries = requestOptions.maxRetries; + } + } + } + options(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('OPTIONS', requestUrl, null, additionalHeaders || {}); + }); + } + get(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('GET', requestUrl, null, additionalHeaders || {}); + }); + } + del(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('DELETE', requestUrl, null, additionalHeaders || {}); + }); + } + post(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('POST', requestUrl, data, additionalHeaders || {}); + }); + } + patch(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('PATCH', requestUrl, data, additionalHeaders || {}); + }); + } + put(requestUrl, data, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('PUT', requestUrl, data, additionalHeaders || {}); + }); + } + head(requestUrl, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request('HEAD', requestUrl, null, additionalHeaders || {}); + }); + } + sendStream(verb, requestUrl, stream, additionalHeaders) { + return __awaiter(this, void 0, void 0, function* () { + return this.request(verb, requestUrl, stream, additionalHeaders); + }); + } + /** + * Gets a typed object from an endpoint + * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise + */ + getJson(requestUrl, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + const res = yield this.get(requestUrl, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + postJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + const res = yield this.post(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + putJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + const res = yield this.put(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + patchJson(requestUrl, obj, additionalHeaders = {}) { + return __awaiter(this, void 0, void 0, function* () { + const data = JSON.stringify(obj, null, 2); + additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson); + additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson); + const res = yield this.patch(requestUrl, data, additionalHeaders); + return this._processResponse(res, this.requestOptions); + }); + } + /** + * Makes a raw http request. + * All other methods such as get, post, patch, and request ultimately call this. + * Prefer get, del, post and patch + */ + request(verb, requestUrl, data, headers) { + return __awaiter(this, void 0, void 0, function* () { + if (this._disposed) { + throw new Error('Client has already been disposed.'); + } + const parsedUrl = new URL(requestUrl); + let info = this._prepareRequest(verb, parsedUrl, headers); + // Only perform retries on reads since writes may not be idempotent. + const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) + ? this._maxRetries + 1 + : 1; + let numTries = 0; + let response; + do { + response = yield this.requestRaw(info, data); + // Check if it's an authentication challenge + if (response && + response.message && + response.message.statusCode === HttpCodes.Unauthorized) { + let authenticationHandler; + for (const handler of this.handlers) { + if (handler.canHandleAuthentication(response)) { + authenticationHandler = handler; + break; + } + } + if (authenticationHandler) { + return authenticationHandler.handleAuthentication(this, info, data); + } + else { + // We have received an unauthorized response but have no handlers to handle it. + // Let the response return to the caller. + return response; + } + } + let redirectsRemaining = this._maxRedirects; + while (response.message.statusCode && + HttpRedirectCodes.includes(response.message.statusCode) && + this._allowRedirects && + redirectsRemaining > 0) { + const redirectUrl = response.message.headers['location']; + if (!redirectUrl) { + // if there's no location to redirect to, we won't + break; + } + const parsedRedirectUrl = new URL(redirectUrl); + if (parsedUrl.protocol === 'https:' && + parsedUrl.protocol !== parsedRedirectUrl.protocol && + !this._allowRedirectDowngrade) { + throw new Error('Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.'); + } + // we need to finish reading the response before reassigning response + // which will leak the open socket. + yield response.readBody(); + // strip authorization header if redirected to a different hostname + if (parsedRedirectUrl.hostname !== parsedUrl.hostname) { + for (const header in headers) { + // header names are case insensitive + if (header.toLowerCase() === 'authorization') { + delete headers[header]; + } + } + } + // let's make the request with the new redirectUrl + info = this._prepareRequest(verb, parsedRedirectUrl, headers); + response = yield this.requestRaw(info, data); + redirectsRemaining--; + } + if (!response.message.statusCode || + !HttpResponseRetryCodes.includes(response.message.statusCode)) { + // If not a retry code, return immediately instead of retrying + return response; + } + numTries += 1; + if (numTries < maxTries) { + yield response.readBody(); + yield this._performExponentialBackoff(numTries); + } + } while (numTries < maxTries); + return response; + }); + } + /** + * Needs to be called if keepAlive is set to true in request options. + */ + dispose() { + if (this._agent) { + this._agent.destroy(); + } + this._disposed = true; + } + /** + * Raw request. + * @param info + * @param data + */ + requestRaw(info, data) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => { + function callbackForResult(err, res) { + if (err) { + reject(err); + } + else if (!res) { + // If `err` is not passed, then `res` must be passed. + reject(new Error('Unknown error')); + } + else { + resolve(res); + } + } + this.requestRawWithCallback(info, data, callbackForResult); + }); + }); + } + /** + * Raw request with callback. + * @param info + * @param data + * @param onResult + */ + requestRawWithCallback(info, data, onResult) { + if (typeof data === 'string') { + if (!info.options.headers) { + info.options.headers = {}; + } + info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8'); + } + let callbackCalled = false; + function handleResult(err, res) { + if (!callbackCalled) { + callbackCalled = true; + onResult(err, res); + } + } + const req = info.httpModule.request(info.options, (msg) => { + const res = new HttpClientResponse(msg); + handleResult(undefined, res); + }); + let socket; + req.on('socket', sock => { + socket = sock; + }); + // If we ever get disconnected, we want the socket to timeout eventually + req.setTimeout(this._socketTimeout || 3 * 60000, () => { + if (socket) { + socket.end(); + } + handleResult(new Error(`Request timeout: ${info.options.path}`)); + }); + req.on('error', function (err) { + // err has statusCode property + // res should have headers + handleResult(err); + }); + if (data && typeof data === 'string') { + req.write(data, 'utf8'); + } + if (data && typeof data !== 'string') { + data.on('close', function () { + req.end(); + }); + data.pipe(req); + } + else { + req.end(); + } + } + /** + * Gets an http agent. This function is useful when you need an http agent that handles + * routing through a proxy server - depending upon the url and proxy environment variables. + * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com + */ + getAgent(serverUrl) { + const parsedUrl = new URL(serverUrl); + return this._getAgent(parsedUrl); + } + getAgentDispatcher(serverUrl) { + const parsedUrl = new URL(serverUrl); + const proxyUrl = pm.getProxyUrl(parsedUrl); + const useProxy = proxyUrl && proxyUrl.hostname; + if (!useProxy) { + return; + } + return this._getProxyAgentDispatcher(parsedUrl, proxyUrl); + } + _prepareRequest(method, requestUrl, headers) { + const info = {}; + info.parsedUrl = requestUrl; + const usingSsl = info.parsedUrl.protocol === 'https:'; + info.httpModule = usingSsl ? https : http; + const defaultPort = usingSsl ? 443 : 80; + info.options = {}; + info.options.host = info.parsedUrl.hostname; + info.options.port = info.parsedUrl.port + ? parseInt(info.parsedUrl.port) + : defaultPort; + info.options.path = + (info.parsedUrl.pathname || '') + (info.parsedUrl.search || ''); + info.options.method = method; + info.options.headers = this._mergeHeaders(headers); + if (this.userAgent != null) { + info.options.headers['user-agent'] = this.userAgent; + } + info.options.agent = this._getAgent(info.parsedUrl); + // gives handlers an opportunity to participate + if (this.handlers) { + for (const handler of this.handlers) { + handler.prepareRequest(info.options); + } + } + return info; + } + _mergeHeaders(headers) { + if (this.requestOptions && this.requestOptions.headers) { + return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers || {})); + } + return lowercaseKeys(headers || {}); + } + _getExistingOrDefaultHeader(additionalHeaders, header, _default) { + let clientHeader; + if (this.requestOptions && this.requestOptions.headers) { + clientHeader = lowercaseKeys(this.requestOptions.headers)[header]; + } + return additionalHeaders[header] || clientHeader || _default; + } + _getAgent(parsedUrl) { + let agent; + const proxyUrl = pm.getProxyUrl(parsedUrl); + const useProxy = proxyUrl && proxyUrl.hostname; + if (this._keepAlive && useProxy) { + agent = this._proxyAgent; + } + if (!useProxy) { + agent = this._agent; + } + // if agent is already assigned use that agent. + if (agent) { + return agent; + } + const usingSsl = parsedUrl.protocol === 'https:'; + let maxSockets = 100; + if (this.requestOptions) { + maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets; + } + // This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis. + if (proxyUrl && proxyUrl.hostname) { + const agentOptions = { + maxSockets, + keepAlive: this._keepAlive, + proxy: Object.assign(Object.assign({}, ((proxyUrl.username || proxyUrl.password) && { + proxyAuth: `${proxyUrl.username}:${proxyUrl.password}` + })), { host: proxyUrl.hostname, port: proxyUrl.port }) + }; + let tunnelAgent; + const overHttps = proxyUrl.protocol === 'https:'; + if (usingSsl) { + tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp; + } + else { + tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp; + } + agent = tunnelAgent(agentOptions); + this._proxyAgent = agent; + } + // if tunneling agent isn't assigned create a new agent + if (!agent) { + const options = { keepAlive: this._keepAlive, maxSockets }; + agent = usingSsl ? new https.Agent(options) : new http.Agent(options); + this._agent = agent; + } + if (usingSsl && this._ignoreSslError) { + // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process + // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options + // we have to cast it to any and change it directly + agent.options = Object.assign(agent.options || {}, { + rejectUnauthorized: false + }); + } + return agent; + } + _getProxyAgentDispatcher(parsedUrl, proxyUrl) { + let proxyAgent; + if (this._keepAlive) { + proxyAgent = this._proxyAgentDispatcher; + } + // if agent is already assigned use that agent. + if (proxyAgent) { + return proxyAgent; + } + const usingSsl = parsedUrl.protocol === 'https:'; + proxyAgent = new undici_1.ProxyAgent(Object.assign({ uri: proxyUrl.href, pipelining: !this._keepAlive ? 0 : 1 }, ((proxyUrl.username || proxyUrl.password) && { + token: `Basic ${Buffer.from(`${proxyUrl.username}:${proxyUrl.password}`).toString('base64')}` + }))); + this._proxyAgentDispatcher = proxyAgent; + if (usingSsl && this._ignoreSslError) { + // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process + // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options + // we have to cast it to any and change it directly + proxyAgent.options = Object.assign(proxyAgent.options.requestTls || {}, { + rejectUnauthorized: false + }); + } + return proxyAgent; + } + _performExponentialBackoff(retryNumber) { + return __awaiter(this, void 0, void 0, function* () { + retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber); + const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber); + return new Promise(resolve => setTimeout(() => resolve(), ms)); + }); + } + _processResponse(res, options) { + return __awaiter(this, void 0, void 0, function* () { + return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { + const statusCode = res.message.statusCode || 0; + const response = { + statusCode, + result: null, + headers: {} + }; + // not found leads to null obj returned + if (statusCode === HttpCodes.NotFound) { + resolve(response); + } + // get the result from the body + function dateTimeDeserializer(key, value) { + if (typeof value === 'string') { + const a = new Date(value); + if (!isNaN(a.valueOf())) { + return a; + } + } + return value; + } + let obj; + let contents; + try { + contents = yield res.readBody(); + if (contents && contents.length > 0) { + if (options && options.deserializeDates) { + obj = JSON.parse(contents, dateTimeDeserializer); + } + else { + obj = JSON.parse(contents); + } + response.result = obj; + } + response.headers = res.message.headers; + } + catch (err) { + // Invalid resource (contents not json); leaving result obj null + } + // note that 3xx redirects are handled by the http layer. + if (statusCode > 299) { + let msg; + // if exception/error in body, attempt to get better error + if (obj && obj.message) { + msg = obj.message; + } + else if (contents && contents.length > 0) { + // it may be the case that the exception is in the body message as string + msg = contents; + } + else { + msg = `Failed request: (${statusCode})`; + } + const err = new HttpClientError(msg, statusCode); + err.result = response.result; + reject(err); + } + else { + resolve(response); + } + })); + }); + } +} +exports.HttpClient = HttpClient; +const lowercaseKeys = (obj) => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {}); +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ 4988: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.checkBypass = exports.getProxyUrl = void 0; +function getProxyUrl(reqUrl) { + const usingSsl = reqUrl.protocol === 'https:'; + if (checkBypass(reqUrl)) { + return undefined; + } + const proxyVar = (() => { + if (usingSsl) { + return process.env['https_proxy'] || process.env['HTTPS_PROXY']; + } + else { + return process.env['http_proxy'] || process.env['HTTP_PROXY']; + } + })(); + if (proxyVar) { + try { + return new DecodedURL(proxyVar); + } + catch (_a) { + if (!proxyVar.startsWith('http://') && !proxyVar.startsWith('https://')) + return new DecodedURL(`http://${proxyVar}`); + } + } + else { + return undefined; + } +} +exports.getProxyUrl = getProxyUrl; +function checkBypass(reqUrl) { + if (!reqUrl.hostname) { + return false; + } + const reqHost = reqUrl.hostname; + if (isLoopbackAddress(reqHost)) { + return true; + } + const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''; + if (!noProxy) { + return false; + } + // Determine the request port + let reqPort; + if (reqUrl.port) { + reqPort = Number(reqUrl.port); + } + else if (reqUrl.protocol === 'http:') { + reqPort = 80; + } + else if (reqUrl.protocol === 'https:') { + reqPort = 443; + } + // Format the request hostname and hostname with port + const upperReqHosts = [reqUrl.hostname.toUpperCase()]; + if (typeof reqPort === 'number') { + upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`); + } + // Compare request host against noproxy + for (const upperNoProxyItem of noProxy + .split(',') + .map(x => x.trim().toUpperCase()) + .filter(x => x)) { + if (upperNoProxyItem === '*' || + upperReqHosts.some(x => x === upperNoProxyItem || + x.endsWith(`.${upperNoProxyItem}`) || + (upperNoProxyItem.startsWith('.') && + x.endsWith(`${upperNoProxyItem}`)))) { + return true; + } + } + return false; +} +exports.checkBypass = checkBypass; +function isLoopbackAddress(host) { + const hostLower = host.toLowerCase(); + return (hostLower === 'localhost' || + hostLower.startsWith('127.') || + hostLower.startsWith('[::1]') || + hostLower.startsWith('[0:0:0:0:0:0:0:1]')); +} +class DecodedURL extends URL { + constructor(url, base) { + super(url, base); + this._decodedUsername = decodeURIComponent(super.username); + this._decodedPassword = decodeURIComponent(super.password); + } + get username() { + return this._decodedUsername; + } + get password() { + return this._decodedPassword; + } +} +//# sourceMappingURL=proxy.js.map + +/***/ }), + +/***/ 5207: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var _a; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.getCmdPath = exports.tryGetExecutablePath = exports.isRooted = exports.isDirectory = exports.exists = exports.READONLY = exports.UV_FS_O_EXLOCK = exports.IS_WINDOWS = exports.unlink = exports.symlink = exports.stat = exports.rmdir = exports.rm = exports.rename = exports.readlink = exports.readdir = exports.open = exports.mkdir = exports.lstat = exports.copyFile = exports.chmod = void 0; +const fs = __importStar(__nccwpck_require__(9896)); +const path = __importStar(__nccwpck_require__(6928)); +_a = fs.promises +// export const {open} = 'fs' +, exports.chmod = _a.chmod, exports.copyFile = _a.copyFile, exports.lstat = _a.lstat, exports.mkdir = _a.mkdir, exports.open = _a.open, exports.readdir = _a.readdir, exports.readlink = _a.readlink, exports.rename = _a.rename, exports.rm = _a.rm, exports.rmdir = _a.rmdir, exports.stat = _a.stat, exports.symlink = _a.symlink, exports.unlink = _a.unlink; +// export const {open} = 'fs' +exports.IS_WINDOWS = process.platform === 'win32'; +// See https://github.com/nodejs/node/blob/d0153aee367422d0858105abec186da4dff0a0c5/deps/uv/include/uv/win.h#L691 +exports.UV_FS_O_EXLOCK = 0x10000000; +exports.READONLY = fs.constants.O_RDONLY; +function exists(fsPath) { + return __awaiter(this, void 0, void 0, function* () { + try { + yield exports.stat(fsPath); + } + catch (err) { + if (err.code === 'ENOENT') { + return false; + } + throw err; + } + return true; + }); +} +exports.exists = exists; +function isDirectory(fsPath, useStat = false) { + return __awaiter(this, void 0, void 0, function* () { + const stats = useStat ? yield exports.stat(fsPath) : yield exports.lstat(fsPath); + return stats.isDirectory(); + }); +} +exports.isDirectory = isDirectory; +/** + * On OSX/Linux, true if path starts with '/'. On Windows, true for paths like: + * \, \hello, \\hello\share, C:, and C:\hello (and corresponding alternate separator cases). + */ +function isRooted(p) { + p = normalizeSeparators(p); + if (!p) { + throw new Error('isRooted() parameter "p" cannot be empty'); + } + if (exports.IS_WINDOWS) { + return (p.startsWith('\\') || /^[A-Z]:/i.test(p) // e.g. \ or \hello or \\hello + ); // e.g. C: or C:\hello + } + return p.startsWith('/'); +} +exports.isRooted = isRooted; +/** + * Best effort attempt to determine whether a file exists and is executable. + * @param filePath file path to check + * @param extensions additional file extensions to try + * @return if file exists and is executable, returns the file path. otherwise empty string. + */ +function tryGetExecutablePath(filePath, extensions) { + return __awaiter(this, void 0, void 0, function* () { + let stats = undefined; + try { + // test file exists + stats = yield exports.stat(filePath); + } + catch (err) { + if (err.code !== 'ENOENT') { + // eslint-disable-next-line no-console + console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`); + } + } + if (stats && stats.isFile()) { + if (exports.IS_WINDOWS) { + // on Windows, test for valid extension + const upperExt = path.extname(filePath).toUpperCase(); + if (extensions.some(validExt => validExt.toUpperCase() === upperExt)) { + return filePath; + } + } + else { + if (isUnixExecutable(stats)) { + return filePath; + } + } + } + // try each extension + const originalFilePath = filePath; + for (const extension of extensions) { + filePath = originalFilePath + extension; + stats = undefined; + try { + stats = yield exports.stat(filePath); + } + catch (err) { + if (err.code !== 'ENOENT') { + // eslint-disable-next-line no-console + console.log(`Unexpected error attempting to determine if executable file exists '${filePath}': ${err}`); + } + } + if (stats && stats.isFile()) { + if (exports.IS_WINDOWS) { + // preserve the case of the actual file (since an extension was appended) + try { + const directory = path.dirname(filePath); + const upperName = path.basename(filePath).toUpperCase(); + for (const actualName of yield exports.readdir(directory)) { + if (upperName === actualName.toUpperCase()) { + filePath = path.join(directory, actualName); + break; + } + } + } + catch (err) { + // eslint-disable-next-line no-console + console.log(`Unexpected error attempting to determine the actual case of the file '${filePath}': ${err}`); + } + return filePath; + } + else { + if (isUnixExecutable(stats)) { + return filePath; + } + } + } + } + return ''; + }); +} +exports.tryGetExecutablePath = tryGetExecutablePath; +function normalizeSeparators(p) { + p = p || ''; + if (exports.IS_WINDOWS) { + // convert slashes on Windows + p = p.replace(/\//g, '\\'); + // remove redundant slashes + return p.replace(/\\\\+/g, '\\'); + } + // remove redundant slashes + return p.replace(/\/\/+/g, '/'); +} +// on Mac/Linux, test the execute bit +// R W X R W X R W X +// 256 128 64 32 16 8 4 2 1 +function isUnixExecutable(stats) { + return ((stats.mode & 1) > 0 || + ((stats.mode & 8) > 0 && stats.gid === process.getgid()) || + ((stats.mode & 64) > 0 && stats.uid === process.getuid())); +} +// Get the path of cmd.exe in windows +function getCmdPath() { + var _a; + return (_a = process.env['COMSPEC']) !== null && _a !== void 0 ? _a : `cmd.exe`; +} +exports.getCmdPath = getCmdPath; +//# sourceMappingURL=io-util.js.map + +/***/ }), + +/***/ 4994: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.findInPath = exports.which = exports.mkdirP = exports.rmRF = exports.mv = exports.cp = void 0; +const assert_1 = __nccwpck_require__(2613); +const path = __importStar(__nccwpck_require__(6928)); +const ioUtil = __importStar(__nccwpck_require__(5207)); +/** + * Copies a file or folder. + * Based off of shelljs - https://github.com/shelljs/shelljs/blob/9237f66c52e5daa40458f94f9565e18e8132f5a6/src/cp.js + * + * @param source source path + * @param dest destination path + * @param options optional. See CopyOptions. + */ +function cp(source, dest, options = {}) { + return __awaiter(this, void 0, void 0, function* () { + const { force, recursive, copySourceDirectory } = readCopyOptions(options); + const destStat = (yield ioUtil.exists(dest)) ? yield ioUtil.stat(dest) : null; + // Dest is an existing file, but not forcing + if (destStat && destStat.isFile() && !force) { + return; + } + // If dest is an existing directory, should copy inside. + const newDest = destStat && destStat.isDirectory() && copySourceDirectory + ? path.join(dest, path.basename(source)) + : dest; + if (!(yield ioUtil.exists(source))) { + throw new Error(`no such file or directory: ${source}`); + } + const sourceStat = yield ioUtil.stat(source); + if (sourceStat.isDirectory()) { + if (!recursive) { + throw new Error(`Failed to copy. ${source} is a directory, but tried to copy without recursive flag.`); + } + else { + yield cpDirRecursive(source, newDest, 0, force); + } + } + else { + if (path.relative(source, newDest) === '') { + // a file cannot be copied to itself + throw new Error(`'${newDest}' and '${source}' are the same file`); + } + yield copyFile(source, newDest, force); + } + }); +} +exports.cp = cp; +/** + * Moves a path. + * + * @param source source path + * @param dest destination path + * @param options optional. See MoveOptions. + */ +function mv(source, dest, options = {}) { + return __awaiter(this, void 0, void 0, function* () { + if (yield ioUtil.exists(dest)) { + let destExists = true; + if (yield ioUtil.isDirectory(dest)) { + // If dest is directory copy src into dest + dest = path.join(dest, path.basename(source)); + destExists = yield ioUtil.exists(dest); + } + if (destExists) { + if (options.force == null || options.force) { + yield rmRF(dest); + } + else { + throw new Error('Destination already exists'); + } + } + } + yield mkdirP(path.dirname(dest)); + yield ioUtil.rename(source, dest); + }); +} +exports.mv = mv; +/** + * Remove a path recursively with force + * + * @param inputPath path to remove + */ +function rmRF(inputPath) { + return __awaiter(this, void 0, void 0, function* () { + if (ioUtil.IS_WINDOWS) { + // Check for invalid characters + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + if (/[*"<>|]/.test(inputPath)) { + throw new Error('File path must not contain `*`, `"`, `<`, `>` or `|` on Windows'); + } + } + try { + // note if path does not exist, error is silent + yield ioUtil.rm(inputPath, { + force: true, + maxRetries: 3, + recursive: true, + retryDelay: 300 + }); + } + catch (err) { + throw new Error(`File was unable to be removed ${err}`); + } + }); +} +exports.rmRF = rmRF; +/** + * Make a directory. Creates the full path with folders in between + * Will throw if it fails + * + * @param fsPath path to create + * @returns Promise + */ +function mkdirP(fsPath) { + return __awaiter(this, void 0, void 0, function* () { + assert_1.ok(fsPath, 'a path argument must be provided'); + yield ioUtil.mkdir(fsPath, { recursive: true }); + }); +} +exports.mkdirP = mkdirP; +/** + * Returns path of a tool had the tool actually been invoked. Resolves via paths. + * If you check and the tool does not exist, it will throw. + * + * @param tool name of the tool + * @param check whether to check if tool exists + * @returns Promise path to tool + */ +function which(tool, check) { + return __awaiter(this, void 0, void 0, function* () { + if (!tool) { + throw new Error("parameter 'tool' is required"); + } + // recursive when check=true + if (check) { + const result = yield which(tool, false); + if (!result) { + if (ioUtil.IS_WINDOWS) { + throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.`); + } + else { + throw new Error(`Unable to locate executable file: ${tool}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.`); + } + } + return result; + } + const matches = yield findInPath(tool); + if (matches && matches.length > 0) { + return matches[0]; + } + return ''; + }); +} +exports.which = which; +/** + * Returns a list of all occurrences of the given tool on the system path. + * + * @returns Promise the paths of the tool + */ +function findInPath(tool) { + return __awaiter(this, void 0, void 0, function* () { + if (!tool) { + throw new Error("parameter 'tool' is required"); + } + // build the list of extensions to try + const extensions = []; + if (ioUtil.IS_WINDOWS && process.env['PATHEXT']) { + for (const extension of process.env['PATHEXT'].split(path.delimiter)) { + if (extension) { + extensions.push(extension); + } + } + } + // if it's rooted, return it if exists. otherwise return empty. + if (ioUtil.isRooted(tool)) { + const filePath = yield ioUtil.tryGetExecutablePath(tool, extensions); + if (filePath) { + return [filePath]; + } + return []; + } + // if any path separators, return empty + if (tool.includes(path.sep)) { + return []; + } + // build the list of directories + // + // Note, technically "where" checks the current directory on Windows. From a toolkit perspective, + // it feels like we should not do this. Checking the current directory seems like more of a use + // case of a shell, and the which() function exposed by the toolkit should strive for consistency + // across platforms. + const directories = []; + if (process.env.PATH) { + for (const p of process.env.PATH.split(path.delimiter)) { + if (p) { + directories.push(p); + } + } + } + // find all matches + const matches = []; + for (const directory of directories) { + const filePath = yield ioUtil.tryGetExecutablePath(path.join(directory, tool), extensions); + if (filePath) { + matches.push(filePath); + } + } + return matches; + }); +} +exports.findInPath = findInPath; +function readCopyOptions(options) { + const force = options.force == null ? true : options.force; + const recursive = Boolean(options.recursive); + const copySourceDirectory = options.copySourceDirectory == null + ? true + : Boolean(options.copySourceDirectory); + return { force, recursive, copySourceDirectory }; +} +function cpDirRecursive(sourceDir, destDir, currentDepth, force) { + return __awaiter(this, void 0, void 0, function* () { + // Ensure there is not a run away recursive copy + if (currentDepth >= 255) + return; + currentDepth++; + yield mkdirP(destDir); + const files = yield ioUtil.readdir(sourceDir); + for (const fileName of files) { + const srcFile = `${sourceDir}/${fileName}`; + const destFile = `${destDir}/${fileName}`; + const srcFileStat = yield ioUtil.lstat(srcFile); + if (srcFileStat.isDirectory()) { + // Recurse + yield cpDirRecursive(srcFile, destFile, currentDepth, force); + } + else { + yield copyFile(srcFile, destFile, force); + } + } + // Change the mode for the newly created directory + yield ioUtil.chmod(destDir, (yield ioUtil.stat(sourceDir)).mode); + }); +} +// Buffered file copy +function copyFile(srcFile, destFile, force) { + return __awaiter(this, void 0, void 0, function* () { + if ((yield ioUtil.lstat(srcFile)).isSymbolicLink()) { + // unlink/re-link it + try { + yield ioUtil.lstat(destFile); + yield ioUtil.unlink(destFile); + } + catch (e) { + // Try to override file permission + if (e.code === 'EPERM') { + yield ioUtil.chmod(destFile, '0666'); + yield ioUtil.unlink(destFile); + } + // other errors = it doesn't exist, no work to do + } + // Copy over symlink + const symlinkFull = yield ioUtil.readlink(srcFile); + yield ioUtil.symlink(symlinkFull, destFile, ioUtil.IS_WINDOWS ? 'junction' : null); + } + else if (!(yield ioUtil.exists(destFile)) || force) { + yield ioUtil.copyFile(srcFile, destFile); + } + }); +} +//# sourceMappingURL=io.js.map + +/***/ }), + +/***/ 133: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +// export the calculator fn +module.exports = __nccwpck_require__(6737); + +// TODO maybe provide name exports of other functions for avg, std dev, variance, etc... 🤔 + + +/***/ }), + +/***/ 2214: +/***/ ((module) => { + +// will only call it with a _safe_ array of values, so no need to sanitize input here +module.exports = values => + values.reduce((sum, v) => sum + v, 0) / values.length; + + +/***/ }), + +/***/ 6737: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const calculateAverage = __nccwpck_require__(2214); +const calculateStdDev = __nccwpck_require__(825); +const checkInput = __nccwpck_require__(9039); +const preciseRound = __nccwpck_require__(2940); +const manageInput = __nccwpck_require__(9554); + +module.exports = (...args) => { + const [arrays, options] = manageInput(args); + + const isInputValid = checkInput(arrays); + if (!isInputValid) throw new Error('Input not valid'); + + const [x, y] = arrays; + + const µ = { x: calculateAverage(x), y: calculateAverage(y) }; + const s = { x: calculateStdDev(x), y: calculateStdDev(y) }; + + const addedMultipliedDifferences = x + .map((val, i) => (val - µ.x) * (y[i] - µ.y)) + .reduce((sum, v) => sum + v, 0); + + const dividedByDevs = addedMultipliedDifferences / (s.x * s.y); + + const r = dividedByDevs / (x.length - 1); + + // return string? + if (options.returnString === true) return r.toFixed(options.returnDecimals); + // default return + return preciseRound(r, options.returnDecimals); +}; + + +/***/ }), + +/***/ 825: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const calculateAverage = __nccwpck_require__(2214); + +// will only call it with a _safe_ array of values +module.exports = values => { + const µ = calculateAverage(values); + const addedSquareDiffs = values + .map(val => val - µ) + .map(diff => diff ** 2) + .reduce((sum, v) => sum + v, 0); + const variance = addedSquareDiffs / (values.length - 1); + return Math.sqrt(variance); +}; + +// TODO maybe export fns to calculate variance and std deviation too from the package? + + +/***/ }), + +/***/ 9039: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const isNumber = __nccwpck_require__(3230); + +module.exports = args => { + // only two inputs exist + if (args.length !== 2) return false; + const [x, y] = args; + // inputs are not falsy + if (!x || !y) return false; + // they are arrays + if (!Array.isArray(x) || !Array.isArray(y)) return false; + // length is not 0 + if (!x.length || !y.length) return false; + // length is the same + if (x.length !== y.length) return false; + // all the elems in the arrays are numbers + if (x.concat(y).find(el => !isNumber(el))) return false; + // 👌 all good! + return true; +}; + +// TODO maybe return some message about each problem, so it can be thrown in the Error + + +/***/ }), + +/***/ 3230: +/***/ ((module) => { + +// idea from https://codepen.io/grok/pen/LvOQbW?editors=0010 +module.exports = n => + typeof n === 'number' && n === Number(n) && Number.isFinite(n); + + +/***/ }), + +/***/ 5608: +/***/ ((module) => { + +// idea from https://stackoverflow.com/questions/8511281/check-if-a-value-is-an-object-in-javascript/8511350#8511350 +module.exports = obj => + typeof obj === 'object' && obj !== null && !Array.isArray(obj); + + +/***/ }), + +/***/ 9554: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const isObject = __nccwpck_require__(5608); + +module.exports = input => { + let arrays = input; + let options = {}; + + if (input.length > 2) { + /* eslint-disable-next-line prefer-destructuring */ + if (isObject(input[2])) options = input[2]; + arrays = input.slice(0, 2); + } + + const opts = { + returnString: options.string || false, + returnDecimals: options.decimals || 9, + }; + + return [arrays, opts]; +}; + + +/***/ }), + +/***/ 2940: +/***/ ((module) => { + +// idea from https://www.w3resource.com/javascript-exercises/javascript-math-exercise-14.php +module.exports = (num, dec) => + Math.round(num * 10 ** dec + (num >= 0 ? 1 : -1) * 0.0001) / 10 ** dec; + + +/***/ }), + +/***/ 6705: +/***/ ((module, exports, __nccwpck_require__) => { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", ({ value: true })); + +function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } + +var Stream = _interopDefault(__nccwpck_require__(2203)); +var http = _interopDefault(__nccwpck_require__(8611)); +var Url = _interopDefault(__nccwpck_require__(7016)); +var whatwgUrl = _interopDefault(__nccwpck_require__(2686)); +var https = _interopDefault(__nccwpck_require__(5692)); +var zlib = _interopDefault(__nccwpck_require__(3106)); + +// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js + +// fix for "Readable" isn't a named export issue +const Readable = Stream.Readable; + +const BUFFER = Symbol('buffer'); +const TYPE = Symbol('type'); + +class Blob { + constructor() { + this[TYPE] = ''; + + const blobParts = arguments[0]; + const options = arguments[1]; + + const buffers = []; + let size = 0; + + if (blobParts) { + const a = blobParts; + const length = Number(a.length); + for (let i = 0; i < length; i++) { + const element = a[i]; + let buffer; + if (element instanceof Buffer) { + buffer = element; + } else if (ArrayBuffer.isView(element)) { + buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength); + } else if (element instanceof ArrayBuffer) { + buffer = Buffer.from(element); + } else if (element instanceof Blob) { + buffer = element[BUFFER]; + } else { + buffer = Buffer.from(typeof element === 'string' ? element : String(element)); + } + size += buffer.length; + buffers.push(buffer); + } + } + + this[BUFFER] = Buffer.concat(buffers); + + let type = options && options.type !== undefined && String(options.type).toLowerCase(); + if (type && !/[^\u0020-\u007E]/.test(type)) { + this[TYPE] = type; + } + } + get size() { + return this[BUFFER].length; + } + get type() { + return this[TYPE]; + } + text() { + return Promise.resolve(this[BUFFER].toString()); + } + arrayBuffer() { + const buf = this[BUFFER]; + const ab = buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); + return Promise.resolve(ab); + } + stream() { + const readable = new Readable(); + readable._read = function () {}; + readable.push(this[BUFFER]); + readable.push(null); + return readable; + } + toString() { + return '[object Blob]'; + } + slice() { + const size = this.size; + + const start = arguments[0]; + const end = arguments[1]; + let relativeStart, relativeEnd; + if (start === undefined) { + relativeStart = 0; + } else if (start < 0) { + relativeStart = Math.max(size + start, 0); + } else { + relativeStart = Math.min(start, size); + } + if (end === undefined) { + relativeEnd = size; + } else if (end < 0) { + relativeEnd = Math.max(size + end, 0); + } else { + relativeEnd = Math.min(end, size); + } + const span = Math.max(relativeEnd - relativeStart, 0); + + const buffer = this[BUFFER]; + const slicedBuffer = buffer.slice(relativeStart, relativeStart + span); + const blob = new Blob([], { type: arguments[2] }); + blob[BUFFER] = slicedBuffer; + return blob; + } +} + +Object.defineProperties(Blob.prototype, { + size: { enumerable: true }, + type: { enumerable: true }, + slice: { enumerable: true } +}); + +Object.defineProperty(Blob.prototype, Symbol.toStringTag, { + value: 'Blob', + writable: false, + enumerable: false, + configurable: true +}); + +/** + * fetch-error.js + * + * FetchError interface for operational errors + */ + +/** + * Create FetchError instance + * + * @param String message Error message for human + * @param String type Error type for machine + * @param String systemError For Node.js system error + * @return FetchError + */ +function FetchError(message, type, systemError) { + Error.call(this, message); + + this.message = message; + this.type = type; + + // when err.type is `system`, err.code contains system error code + if (systemError) { + this.code = this.errno = systemError.code; + } + + // hide custom error implementation details from end-users + Error.captureStackTrace(this, this.constructor); +} + +FetchError.prototype = Object.create(Error.prototype); +FetchError.prototype.constructor = FetchError; +FetchError.prototype.name = 'FetchError'; + +let convert; +try { + convert = (__nccwpck_require__(4572)/* .convert */ .C); +} catch (e) {} + +const INTERNALS = Symbol('Body internals'); + +// fix an issue where "PassThrough" isn't a named export for node <10 +const PassThrough = Stream.PassThrough; + +/** + * Body mixin + * + * Ref: https://fetch.spec.whatwg.org/#body + * + * @param Stream body Readable stream + * @param Object opts Response options + * @return Void + */ +function Body(body) { + var _this = this; + + var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, + _ref$size = _ref.size; + + let size = _ref$size === undefined ? 0 : _ref$size; + var _ref$timeout = _ref.timeout; + let timeout = _ref$timeout === undefined ? 0 : _ref$timeout; + + if (body == null) { + // body is undefined or null + body = null; + } else if (isURLSearchParams(body)) { + // body is a URLSearchParams + body = Buffer.from(body.toString()); + } else if (isBlob(body)) ; else if (Buffer.isBuffer(body)) ; else if (Object.prototype.toString.call(body) === '[object ArrayBuffer]') { + // body is ArrayBuffer + body = Buffer.from(body); + } else if (ArrayBuffer.isView(body)) { + // body is ArrayBufferView + body = Buffer.from(body.buffer, body.byteOffset, body.byteLength); + } else if (body instanceof Stream) ; else { + // none of the above + // coerce to string then buffer + body = Buffer.from(String(body)); + } + this[INTERNALS] = { + body, + disturbed: false, + error: null + }; + this.size = size; + this.timeout = timeout; + + if (body instanceof Stream) { + body.on('error', function (err) { + const error = err.name === 'AbortError' ? err : new FetchError(`Invalid response body while trying to fetch ${_this.url}: ${err.message}`, 'system', err); + _this[INTERNALS].error = error; + }); + } +} + +Body.prototype = { + get body() { + return this[INTERNALS].body; + }, + + get bodyUsed() { + return this[INTERNALS].disturbed; + }, + + /** + * Decode response as ArrayBuffer + * + * @return Promise + */ + arrayBuffer() { + return consumeBody.call(this).then(function (buf) { + return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength); + }); + }, + + /** + * Return raw response as Blob + * + * @return Promise + */ + blob() { + let ct = this.headers && this.headers.get('content-type') || ''; + return consumeBody.call(this).then(function (buf) { + return Object.assign( + // Prevent copying + new Blob([], { + type: ct.toLowerCase() + }), { + [BUFFER]: buf + }); + }); + }, + + /** + * Decode response as json + * + * @return Promise + */ + json() { + var _this2 = this; + + return consumeBody.call(this).then(function (buffer) { + try { + return JSON.parse(buffer.toString()); + } catch (err) { + return Body.Promise.reject(new FetchError(`invalid json response body at ${_this2.url} reason: ${err.message}`, 'invalid-json')); + } + }); + }, + + /** + * Decode response as text + * + * @return Promise + */ + text() { + return consumeBody.call(this).then(function (buffer) { + return buffer.toString(); + }); + }, + + /** + * Decode response as buffer (non-spec api) + * + * @return Promise + */ + buffer() { + return consumeBody.call(this); + }, + + /** + * Decode response as text, while automatically detecting the encoding and + * trying to decode to UTF-8 (non-spec api) + * + * @return Promise + */ + textConverted() { + var _this3 = this; + + return consumeBody.call(this).then(function (buffer) { + return convertBody(buffer, _this3.headers); + }); + } +}; + +// In browsers, all properties are enumerable. +Object.defineProperties(Body.prototype, { + body: { enumerable: true }, + bodyUsed: { enumerable: true }, + arrayBuffer: { enumerable: true }, + blob: { enumerable: true }, + json: { enumerable: true }, + text: { enumerable: true } +}); + +Body.mixIn = function (proto) { + for (const name of Object.getOwnPropertyNames(Body.prototype)) { + // istanbul ignore else: future proof + if (!(name in proto)) { + const desc = Object.getOwnPropertyDescriptor(Body.prototype, name); + Object.defineProperty(proto, name, desc); + } + } +}; + +/** + * Consume and convert an entire Body to a Buffer. + * + * Ref: https://fetch.spec.whatwg.org/#concept-body-consume-body + * + * @return Promise + */ +function consumeBody() { + var _this4 = this; + + if (this[INTERNALS].disturbed) { + return Body.Promise.reject(new TypeError(`body used already for: ${this.url}`)); + } + + this[INTERNALS].disturbed = true; + + if (this[INTERNALS].error) { + return Body.Promise.reject(this[INTERNALS].error); + } + + let body = this.body; + + // body is null + if (body === null) { + return Body.Promise.resolve(Buffer.alloc(0)); + } + + // body is blob + if (isBlob(body)) { + body = body.stream(); + } + + // body is buffer + if (Buffer.isBuffer(body)) { + return Body.Promise.resolve(body); + } + + // istanbul ignore if: should never happen + if (!(body instanceof Stream)) { + return Body.Promise.resolve(Buffer.alloc(0)); + } + + // body is stream + // get ready to actually consume the body + let accum = []; + let accumBytes = 0; + let abort = false; + + return new Body.Promise(function (resolve, reject) { + let resTimeout; + + // allow timeout on slow response body + if (_this4.timeout) { + resTimeout = setTimeout(function () { + abort = true; + reject(new FetchError(`Response timeout while trying to fetch ${_this4.url} (over ${_this4.timeout}ms)`, 'body-timeout')); + }, _this4.timeout); + } + + // handle stream errors + body.on('error', function (err) { + if (err.name === 'AbortError') { + // if the request was aborted, reject with this Error + abort = true; + reject(err); + } else { + // other errors, such as incorrect content-encoding + reject(new FetchError(`Invalid response body while trying to fetch ${_this4.url}: ${err.message}`, 'system', err)); + } + }); + + body.on('data', function (chunk) { + if (abort || chunk === null) { + return; + } + + if (_this4.size && accumBytes + chunk.length > _this4.size) { + abort = true; + reject(new FetchError(`content size at ${_this4.url} over limit: ${_this4.size}`, 'max-size')); + return; + } + + accumBytes += chunk.length; + accum.push(chunk); + }); + + body.on('end', function () { + if (abort) { + return; + } + + clearTimeout(resTimeout); + + try { + resolve(Buffer.concat(accum, accumBytes)); + } catch (err) { + // handle streams that have accumulated too much data (issue #414) + reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, 'system', err)); + } + }); + }); +} + +/** + * Detect buffer encoding and convert to target encoding + * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding + * + * @param Buffer buffer Incoming buffer + * @param String encoding Target encoding + * @return String + */ +function convertBody(buffer, headers) { + if (typeof convert !== 'function') { + throw new Error('The package `encoding` must be installed to use the textConverted() function'); + } + + const ct = headers.get('content-type'); + let charset = 'utf-8'; + let res, str; + + // header + if (ct) { + res = /charset=([^;]*)/i.exec(ct); + } + + // no charset in content type, peek at response body for at most 1024 bytes + str = buffer.slice(0, 1024).toString(); + + // html5 + if (!res && str) { + res = / 0 && arguments[0] !== undefined ? arguments[0] : undefined; + + this[MAP] = Object.create(null); + + if (init instanceof Headers) { + const rawHeaders = init.raw(); + const headerNames = Object.keys(rawHeaders); + + for (const headerName of headerNames) { + for (const value of rawHeaders[headerName]) { + this.append(headerName, value); + } + } + + return; + } + + // We don't worry about converting prop to ByteString here as append() + // will handle it. + if (init == null) ; else if (typeof init === 'object') { + const method = init[Symbol.iterator]; + if (method != null) { + if (typeof method !== 'function') { + throw new TypeError('Header pairs must be iterable'); + } + + // sequence> + // Note: per spec we have to first exhaust the lists then process them + const pairs = []; + for (const pair of init) { + if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') { + throw new TypeError('Each header pair must be iterable'); + } + pairs.push(Array.from(pair)); + } + + for (const pair of pairs) { + if (pair.length !== 2) { + throw new TypeError('Each header pair must be a name/value tuple'); + } + this.append(pair[0], pair[1]); + } + } else { + // record + for (const key of Object.keys(init)) { + const value = init[key]; + this.append(key, value); + } + } + } else { + throw new TypeError('Provided initializer must be an object'); + } + } + + /** + * Return combined header value given name + * + * @param String name Header name + * @return Mixed + */ + get(name) { + name = `${name}`; + validateName(name); + const key = find(this[MAP], name); + if (key === undefined) { + return null; + } + + return this[MAP][key].join(', '); + } + + /** + * Iterate over all headers + * + * @param Function callback Executed for each item with parameters (value, name, thisArg) + * @param Boolean thisArg `this` context for callback function + * @return Void + */ + forEach(callback) { + let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined; + + let pairs = getHeaders(this); + let i = 0; + while (i < pairs.length) { + var _pairs$i = pairs[i]; + const name = _pairs$i[0], + value = _pairs$i[1]; + + callback.call(thisArg, value, name, this); + pairs = getHeaders(this); + i++; + } + } + + /** + * Overwrite header values given name + * + * @param String name Header name + * @param String value Header value + * @return Void + */ + set(name, value) { + name = `${name}`; + value = `${value}`; + validateName(name); + validateValue(value); + const key = find(this[MAP], name); + this[MAP][key !== undefined ? key : name] = [value]; + } + + /** + * Append a value onto existing header + * + * @param String name Header name + * @param String value Header value + * @return Void + */ + append(name, value) { + name = `${name}`; + value = `${value}`; + validateName(name); + validateValue(value); + const key = find(this[MAP], name); + if (key !== undefined) { + this[MAP][key].push(value); + } else { + this[MAP][name] = [value]; + } + } + + /** + * Check for header name existence + * + * @param String name Header name + * @return Boolean + */ + has(name) { + name = `${name}`; + validateName(name); + return find(this[MAP], name) !== undefined; + } + + /** + * Delete all header values given name + * + * @param String name Header name + * @return Void + */ + delete(name) { + name = `${name}`; + validateName(name); + const key = find(this[MAP], name); + if (key !== undefined) { + delete this[MAP][key]; + } + } + + /** + * Return raw headers (non-spec api) + * + * @return Object + */ + raw() { + return this[MAP]; + } + + /** + * Get an iterator on keys. + * + * @return Iterator + */ + keys() { + return createHeadersIterator(this, 'key'); + } + + /** + * Get an iterator on values. + * + * @return Iterator + */ + values() { + return createHeadersIterator(this, 'value'); + } + + /** + * Get an iterator on entries. + * + * This is the default iterator of the Headers object. + * + * @return Iterator + */ + [Symbol.iterator]() { + return createHeadersIterator(this, 'key+value'); + } +} +Headers.prototype.entries = Headers.prototype[Symbol.iterator]; + +Object.defineProperty(Headers.prototype, Symbol.toStringTag, { + value: 'Headers', + writable: false, + enumerable: false, + configurable: true +}); + +Object.defineProperties(Headers.prototype, { + get: { enumerable: true }, + forEach: { enumerable: true }, + set: { enumerable: true }, + append: { enumerable: true }, + has: { enumerable: true }, + delete: { enumerable: true }, + keys: { enumerable: true }, + values: { enumerable: true }, + entries: { enumerable: true } +}); + +function getHeaders(headers) { + let kind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'key+value'; + + const keys = Object.keys(headers[MAP]).sort(); + return keys.map(kind === 'key' ? function (k) { + return k.toLowerCase(); + } : kind === 'value' ? function (k) { + return headers[MAP][k].join(', '); + } : function (k) { + return [k.toLowerCase(), headers[MAP][k].join(', ')]; + }); +} + +const INTERNAL = Symbol('internal'); + +function createHeadersIterator(target, kind) { + const iterator = Object.create(HeadersIteratorPrototype); + iterator[INTERNAL] = { + target, + kind, + index: 0 + }; + return iterator; +} + +const HeadersIteratorPrototype = Object.setPrototypeOf({ + next() { + // istanbul ignore if + if (!this || Object.getPrototypeOf(this) !== HeadersIteratorPrototype) { + throw new TypeError('Value of `this` is not a HeadersIterator'); + } + + var _INTERNAL = this[INTERNAL]; + const target = _INTERNAL.target, + kind = _INTERNAL.kind, + index = _INTERNAL.index; + + const values = getHeaders(target, kind); + const len = values.length; + if (index >= len) { + return { + value: undefined, + done: true + }; + } + + this[INTERNAL].index = index + 1; + + return { + value: values[index], + done: false + }; + } +}, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()))); + +Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, { + value: 'HeadersIterator', + writable: false, + enumerable: false, + configurable: true +}); + +/** + * Export the Headers object in a form that Node.js can consume. + * + * @param Headers headers + * @return Object + */ +function exportNodeCompatibleHeaders(headers) { + const obj = Object.assign({ __proto__: null }, headers[MAP]); + + // http.request() only supports string as Host header. This hack makes + // specifying custom Host header possible. + const hostHeaderKey = find(headers[MAP], 'Host'); + if (hostHeaderKey !== undefined) { + obj[hostHeaderKey] = obj[hostHeaderKey][0]; + } + + return obj; +} + +/** + * Create a Headers object from an object of headers, ignoring those that do + * not conform to HTTP grammar productions. + * + * @param Object obj Object of headers + * @return Headers + */ +function createHeadersLenient(obj) { + const headers = new Headers(); + for (const name of Object.keys(obj)) { + if (invalidTokenRegex.test(name)) { + continue; + } + if (Array.isArray(obj[name])) { + for (const val of obj[name]) { + if (invalidHeaderCharRegex.test(val)) { + continue; + } + if (headers[MAP][name] === undefined) { + headers[MAP][name] = [val]; + } else { + headers[MAP][name].push(val); + } + } + } else if (!invalidHeaderCharRegex.test(obj[name])) { + headers[MAP][name] = [obj[name]]; + } + } + return headers; +} + +const INTERNALS$1 = Symbol('Response internals'); + +// fix an issue where "STATUS_CODES" aren't a named export for node <10 +const STATUS_CODES = http.STATUS_CODES; + +/** + * Response class + * + * @param Stream body Readable stream + * @param Object opts Response options + * @return Void + */ +class Response { + constructor() { + let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; + let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + Body.call(this, body, opts); + + const status = opts.status || 200; + const headers = new Headers(opts.headers); + + if (body != null && !headers.has('Content-Type')) { + const contentType = extractContentType(body); + if (contentType) { + headers.append('Content-Type', contentType); + } + } + + this[INTERNALS$1] = { + url: opts.url, + status, + statusText: opts.statusText || STATUS_CODES[status], + headers, + counter: opts.counter + }; + } + + get url() { + return this[INTERNALS$1].url || ''; + } + + get status() { + return this[INTERNALS$1].status; + } + + /** + * Convenience property representing if the request ended normally + */ + get ok() { + return this[INTERNALS$1].status >= 200 && this[INTERNALS$1].status < 300; + } + + get redirected() { + return this[INTERNALS$1].counter > 0; + } + + get statusText() { + return this[INTERNALS$1].statusText; + } + + get headers() { + return this[INTERNALS$1].headers; + } + + /** + * Clone this response + * + * @return Response + */ + clone() { + return new Response(clone(this), { + url: this.url, + status: this.status, + statusText: this.statusText, + headers: this.headers, + ok: this.ok, + redirected: this.redirected + }); + } +} + +Body.mixIn(Response.prototype); + +Object.defineProperties(Response.prototype, { + url: { enumerable: true }, + status: { enumerable: true }, + ok: { enumerable: true }, + redirected: { enumerable: true }, + statusText: { enumerable: true }, + headers: { enumerable: true }, + clone: { enumerable: true } +}); + +Object.defineProperty(Response.prototype, Symbol.toStringTag, { + value: 'Response', + writable: false, + enumerable: false, + configurable: true +}); + +const INTERNALS$2 = Symbol('Request internals'); +const URL = Url.URL || whatwgUrl.URL; + +// fix an issue where "format", "parse" aren't a named export for node <10 +const parse_url = Url.parse; +const format_url = Url.format; + +/** + * Wrapper around `new URL` to handle arbitrary URLs + * + * @param {string} urlStr + * @return {void} + */ +function parseURL(urlStr) { + /* + Check whether the URL is absolute or not + Scheme: https://tools.ietf.org/html/rfc3986#section-3.1 + Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3 + */ + if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.exec(urlStr)) { + urlStr = new URL(urlStr).toString(); + } + + // Fallback to old implementation for arbitrary URLs + return parse_url(urlStr); +} + +const streamDestructionSupported = 'destroy' in Stream.Readable.prototype; + +/** + * Check if a value is an instance of Request. + * + * @param Mixed input + * @return Boolean + */ +function isRequest(input) { + return typeof input === 'object' && typeof input[INTERNALS$2] === 'object'; +} + +function isAbortSignal(signal) { + const proto = signal && typeof signal === 'object' && Object.getPrototypeOf(signal); + return !!(proto && proto.constructor.name === 'AbortSignal'); +} + +/** + * Request class + * + * @param Mixed input Url or Request instance + * @param Object init Custom options + * @return Void + */ +class Request { + constructor(input) { + let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + let parsedURL; + + // normalize input + if (!isRequest(input)) { + if (input && input.href) { + // in order to support Node.js' Url objects; though WHATWG's URL objects + // will fall into this branch also (since their `toString()` will return + // `href` property anyway) + parsedURL = parseURL(input.href); + } else { + // coerce input to a string before attempting to parse + parsedURL = parseURL(`${input}`); + } + input = {}; + } else { + parsedURL = parseURL(input.url); + } + + let method = init.method || input.method || 'GET'; + method = method.toUpperCase(); + + if ((init.body != null || isRequest(input) && input.body !== null) && (method === 'GET' || method === 'HEAD')) { + throw new TypeError('Request with GET/HEAD method cannot have body'); + } + + let inputBody = init.body != null ? init.body : isRequest(input) && input.body !== null ? clone(input) : null; + + Body.call(this, inputBody, { + timeout: init.timeout || input.timeout || 0, + size: init.size || input.size || 0 + }); + + const headers = new Headers(init.headers || input.headers || {}); + + if (inputBody != null && !headers.has('Content-Type')) { + const contentType = extractContentType(inputBody); + if (contentType) { + headers.append('Content-Type', contentType); + } + } + + let signal = isRequest(input) ? input.signal : null; + if ('signal' in init) signal = init.signal; + + if (signal != null && !isAbortSignal(signal)) { + throw new TypeError('Expected signal to be an instanceof AbortSignal'); + } + + this[INTERNALS$2] = { + method, + redirect: init.redirect || input.redirect || 'follow', + headers, + parsedURL, + signal + }; + + // node-fetch-only options + this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20; + this.compress = init.compress !== undefined ? init.compress : input.compress !== undefined ? input.compress : true; + this.counter = init.counter || input.counter || 0; + this.agent = init.agent || input.agent; + } + + get method() { + return this[INTERNALS$2].method; + } + + get url() { + return format_url(this[INTERNALS$2].parsedURL); + } + + get headers() { + return this[INTERNALS$2].headers; + } + + get redirect() { + return this[INTERNALS$2].redirect; + } + + get signal() { + return this[INTERNALS$2].signal; + } + + /** + * Clone this request + * + * @return Request + */ + clone() { + return new Request(this); + } +} + +Body.mixIn(Request.prototype); + +Object.defineProperty(Request.prototype, Symbol.toStringTag, { + value: 'Request', + writable: false, + enumerable: false, + configurable: true +}); + +Object.defineProperties(Request.prototype, { + method: { enumerable: true }, + url: { enumerable: true }, + headers: { enumerable: true }, + redirect: { enumerable: true }, + clone: { enumerable: true }, + signal: { enumerable: true } +}); + +/** + * Convert a Request to Node.js http request options. + * + * @param Request A Request instance + * @return Object The options object to be passed to http.request + */ +function getNodeRequestOptions(request) { + const parsedURL = request[INTERNALS$2].parsedURL; + const headers = new Headers(request[INTERNALS$2].headers); + + // fetch step 1.3 + if (!headers.has('Accept')) { + headers.set('Accept', '*/*'); + } + + // Basic fetch + if (!parsedURL.protocol || !parsedURL.hostname) { + throw new TypeError('Only absolute URLs are supported'); + } + + if (!/^https?:$/.test(parsedURL.protocol)) { + throw new TypeError('Only HTTP(S) protocols are supported'); + } + + if (request.signal && request.body instanceof Stream.Readable && !streamDestructionSupported) { + throw new Error('Cancellation of streamed requests with AbortSignal is not supported in node < 8'); + } + + // HTTP-network-or-cache fetch steps 2.4-2.7 + let contentLengthValue = null; + if (request.body == null && /^(POST|PUT)$/i.test(request.method)) { + contentLengthValue = '0'; + } + if (request.body != null) { + const totalBytes = getTotalBytes(request); + if (typeof totalBytes === 'number') { + contentLengthValue = String(totalBytes); + } + } + if (contentLengthValue) { + headers.set('Content-Length', contentLengthValue); + } + + // HTTP-network-or-cache fetch step 2.11 + if (!headers.has('User-Agent')) { + headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)'); + } + + // HTTP-network-or-cache fetch step 2.15 + if (request.compress && !headers.has('Accept-Encoding')) { + headers.set('Accept-Encoding', 'gzip,deflate'); + } + + let agent = request.agent; + if (typeof agent === 'function') { + agent = agent(parsedURL); + } + + if (!headers.has('Connection') && !agent) { + headers.set('Connection', 'close'); + } + + // HTTP-network fetch step 4.2 + // chunked encoding is handled by Node.js + + return Object.assign({}, parsedURL, { + method: request.method, + headers: exportNodeCompatibleHeaders(headers), + agent + }); +} + +/** + * abort-error.js + * + * AbortError interface for cancelled requests + */ + +/** + * Create AbortError instance + * + * @param String message Error message for human + * @return AbortError + */ +function AbortError(message) { + Error.call(this, message); + + this.type = 'aborted'; + this.message = message; + + // hide custom error implementation details from end-users + Error.captureStackTrace(this, this.constructor); +} + +AbortError.prototype = Object.create(Error.prototype); +AbortError.prototype.constructor = AbortError; +AbortError.prototype.name = 'AbortError'; + +const URL$1 = Url.URL || whatwgUrl.URL; + +// fix an issue where "PassThrough", "resolve" aren't a named export for node <10 +const PassThrough$1 = Stream.PassThrough; + +const isDomainOrSubdomain = function isDomainOrSubdomain(destination, original) { + const orig = new URL$1(original).hostname; + const dest = new URL$1(destination).hostname; + + return orig === dest || orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest); +}; + +/** + * isSameProtocol reports whether the two provided URLs use the same protocol. + * + * Both domains must already be in canonical form. + * @param {string|URL} original + * @param {string|URL} destination + */ +const isSameProtocol = function isSameProtocol(destination, original) { + const orig = new URL$1(original).protocol; + const dest = new URL$1(destination).protocol; + + return orig === dest; +}; + +/** + * Fetch function + * + * @param Mixed url Absolute url or Request instance + * @param Object opts Fetch options + * @return Promise + */ +function fetch(url, opts) { + + // allow custom promise + if (!fetch.Promise) { + throw new Error('native promise missing, set fetch.Promise to your favorite alternative'); + } + + Body.Promise = fetch.Promise; + + // wrap http.request into fetch + return new fetch.Promise(function (resolve, reject) { + // build request object + const request = new Request(url, opts); + const options = getNodeRequestOptions(request); + + const send = (options.protocol === 'https:' ? https : http).request; + const signal = request.signal; + + let response = null; + + const abort = function abort() { + let error = new AbortError('The user aborted a request.'); + reject(error); + if (request.body && request.body instanceof Stream.Readable) { + destroyStream(request.body, error); + } + if (!response || !response.body) return; + response.body.emit('error', error); + }; + + if (signal && signal.aborted) { + abort(); + return; + } + + const abortAndFinalize = function abortAndFinalize() { + abort(); + finalize(); + }; + + // send request + const req = send(options); + let reqTimeout; + + if (signal) { + signal.addEventListener('abort', abortAndFinalize); + } + + function finalize() { + req.abort(); + if (signal) signal.removeEventListener('abort', abortAndFinalize); + clearTimeout(reqTimeout); + } + + if (request.timeout) { + req.once('socket', function (socket) { + reqTimeout = setTimeout(function () { + reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout')); + finalize(); + }, request.timeout); + }); + } + + req.on('error', function (err) { + reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); + + if (response && response.body) { + destroyStream(response.body, err); + } + + finalize(); + }); + + fixResponseChunkedTransferBadEnding(req, function (err) { + if (signal && signal.aborted) { + return; + } + + if (response && response.body) { + destroyStream(response.body, err); + } + }); + + /* c8 ignore next 18 */ + if (parseInt(process.version.substring(1)) < 14) { + // Before Node.js 14, pipeline() does not fully support async iterators and does not always + // properly handle when the socket close/end events are out of order. + req.on('socket', function (s) { + s.addListener('close', function (hadError) { + // if a data listener is still present we didn't end cleanly + const hasDataListener = s.listenerCount('data') > 0; + + // if end happened before close but the socket didn't emit an error, do it now + if (response && hasDataListener && !hadError && !(signal && signal.aborted)) { + const err = new Error('Premature close'); + err.code = 'ERR_STREAM_PREMATURE_CLOSE'; + response.body.emit('error', err); + } + }); + }); + } + + req.on('response', function (res) { + clearTimeout(reqTimeout); + + const headers = createHeadersLenient(res.headers); + + // HTTP fetch step 5 + if (fetch.isRedirect(res.statusCode)) { + // HTTP fetch step 5.2 + const location = headers.get('Location'); + + // HTTP fetch step 5.3 + let locationURL = null; + try { + locationURL = location === null ? null : new URL$1(location, request.url).toString(); + } catch (err) { + // error here can only be invalid URL in Location: header + // do not throw when options.redirect == manual + // let the user extract the errorneous redirect URL + if (request.redirect !== 'manual') { + reject(new FetchError(`uri requested responds with an invalid redirect URL: ${location}`, 'invalid-redirect')); + finalize(); + return; + } + } + + // HTTP fetch step 5.5 + switch (request.redirect) { + case 'error': + reject(new FetchError(`uri requested responds with a redirect, redirect mode is set to error: ${request.url}`, 'no-redirect')); + finalize(); + return; + case 'manual': + // node-fetch-specific step: make manual redirect a bit easier to use by setting the Location header value to the resolved URL. + if (locationURL !== null) { + // handle corrupted header + try { + headers.set('Location', locationURL); + } catch (err) { + // istanbul ignore next: nodejs server prevent invalid response headers, we can't test this through normal request + reject(err); + } + } + break; + case 'follow': + // HTTP-redirect fetch step 2 + if (locationURL === null) { + break; + } + + // HTTP-redirect fetch step 5 + if (request.counter >= request.follow) { + reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect')); + finalize(); + return; + } + + // HTTP-redirect fetch step 6 (counter increment) + // Create a new Request object. + const requestOpts = { + headers: new Headers(request.headers), + follow: request.follow, + counter: request.counter + 1, + agent: request.agent, + compress: request.compress, + method: request.method, + body: request.body, + signal: request.signal, + timeout: request.timeout, + size: request.size + }; + + if (!isDomainOrSubdomain(request.url, locationURL) || !isSameProtocol(request.url, locationURL)) { + for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) { + requestOpts.headers.delete(name); + } + } + + // HTTP-redirect fetch step 9 + if (res.statusCode !== 303 && request.body && getTotalBytes(request) === null) { + reject(new FetchError('Cannot follow redirect with body being a readable stream', 'unsupported-redirect')); + finalize(); + return; + } + + // HTTP-redirect fetch step 11 + if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') { + requestOpts.method = 'GET'; + requestOpts.body = undefined; + requestOpts.headers.delete('content-length'); + } + + // HTTP-redirect fetch step 15 + resolve(fetch(new Request(locationURL, requestOpts))); + finalize(); + return; + } + } + + // prepare response + res.once('end', function () { + if (signal) signal.removeEventListener('abort', abortAndFinalize); + }); + let body = res.pipe(new PassThrough$1()); + + const response_options = { + url: request.url, + status: res.statusCode, + statusText: res.statusMessage, + headers: headers, + size: request.size, + timeout: request.timeout, + counter: request.counter + }; + + // HTTP-network fetch step 12.1.1.3 + const codings = headers.get('Content-Encoding'); + + // HTTP-network fetch step 12.1.1.4: handle content codings + + // in following scenarios we ignore compression support + // 1. compression support is disabled + // 2. HEAD request + // 3. no Content-Encoding header + // 4. no content response (204) + // 5. content not modified response (304) + if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) { + response = new Response(body, response_options); + resolve(response); + return; + } + + // For Node v6+ + // Be less strict when decoding compressed responses, since sometimes + // servers send slightly invalid responses that are still accepted + // by common browsers. + // Always using Z_SYNC_FLUSH is what cURL does. + const zlibOptions = { + flush: zlib.Z_SYNC_FLUSH, + finishFlush: zlib.Z_SYNC_FLUSH + }; + + // for gzip + if (codings == 'gzip' || codings == 'x-gzip') { + body = body.pipe(zlib.createGunzip(zlibOptions)); + response = new Response(body, response_options); + resolve(response); + return; + } + + // for deflate + if (codings == 'deflate' || codings == 'x-deflate') { + // handle the infamous raw deflate response from old servers + // a hack for old IIS and Apache servers + const raw = res.pipe(new PassThrough$1()); + raw.once('data', function (chunk) { + // see http://stackoverflow.com/questions/37519828 + if ((chunk[0] & 0x0F) === 0x08) { + body = body.pipe(zlib.createInflate()); + } else { + body = body.pipe(zlib.createInflateRaw()); + } + response = new Response(body, response_options); + resolve(response); + }); + raw.on('end', function () { + // some old IIS servers return zero-length OK deflate responses, so 'data' is never emitted. + if (!response) { + response = new Response(body, response_options); + resolve(response); + } + }); + return; + } + + // for br + if (codings == 'br' && typeof zlib.createBrotliDecompress === 'function') { + body = body.pipe(zlib.createBrotliDecompress()); + response = new Response(body, response_options); + resolve(response); + return; + } + + // otherwise, use response as-is + response = new Response(body, response_options); + resolve(response); + }); + + writeToStream(req, request); + }); +} +function fixResponseChunkedTransferBadEnding(request, errorCallback) { + let socket; + + request.on('socket', function (s) { + socket = s; + }); + + request.on('response', function (response) { + const headers = response.headers; + + if (headers['transfer-encoding'] === 'chunked' && !headers['content-length']) { + response.once('close', function (hadError) { + // if a data listener is still present we didn't end cleanly + const hasDataListener = socket.listenerCount('data') > 0; + + if (hasDataListener && !hadError) { + const err = new Error('Premature close'); + err.code = 'ERR_STREAM_PREMATURE_CLOSE'; + errorCallback(err); + } + }); + } + }); +} + +function destroyStream(stream, err) { + if (stream.destroy) { + stream.destroy(err); + } else { + // node < 8 + stream.emit('error', err); + stream.end(); + } +} + +/** + * Redirect code matching + * + * @param Number code Status code + * @return Boolean + */ +fetch.isRedirect = function (code) { + return code === 301 || code === 302 || code === 303 || code === 307 || code === 308; +}; + +// expose Promise +fetch.Promise = global.Promise; + +module.exports = exports = fetch; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports["default"] = exports; +exports.Headers = Headers; +exports.Request = Request; +exports.Response = Response; +exports.FetchError = FetchError; + + +/***/ }), + +/***/ 1552: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var punycode = __nccwpck_require__(4876); +var mappingTable = __nccwpck_require__(2472); + +var PROCESSING_OPTIONS = { + TRANSITIONAL: 0, + NONTRANSITIONAL: 1 +}; + +function normalize(str) { // fix bug in v8 + return str.split('\u0000').map(function (s) { return s.normalize('NFC'); }).join('\u0000'); +} + +function findStatus(val) { + var start = 0; + var end = mappingTable.length - 1; + + while (start <= end) { + var mid = Math.floor((start + end) / 2); + + var target = mappingTable[mid]; + if (target[0][0] <= val && target[0][1] >= val) { + return target; + } else if (target[0][0] > val) { + end = mid - 1; + } else { + start = mid + 1; + } + } + + return null; +} + +var regexAstralSymbols = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g; + +function countSymbols(string) { + return string + // replace every surrogate pair with a BMP symbol + .replace(regexAstralSymbols, '_') + // then get the length + .length; +} + +function mapChars(domain_name, useSTD3, processing_option) { + var hasError = false; + var processed = ""; + + var len = countSymbols(domain_name); + for (var i = 0; i < len; ++i) { + var codePoint = domain_name.codePointAt(i); + var status = findStatus(codePoint); + + switch (status[1]) { + case "disallowed": + hasError = true; + processed += String.fromCodePoint(codePoint); + break; + case "ignored": + break; + case "mapped": + processed += String.fromCodePoint.apply(String, status[2]); + break; + case "deviation": + if (processing_option === PROCESSING_OPTIONS.TRANSITIONAL) { + processed += String.fromCodePoint.apply(String, status[2]); + } else { + processed += String.fromCodePoint(codePoint); + } + break; + case "valid": + processed += String.fromCodePoint(codePoint); + break; + case "disallowed_STD3_mapped": + if (useSTD3) { + hasError = true; + processed += String.fromCodePoint(codePoint); + } else { + processed += String.fromCodePoint.apply(String, status[2]); + } + break; + case "disallowed_STD3_valid": + if (useSTD3) { + hasError = true; + } + + processed += String.fromCodePoint(codePoint); + break; + } + } + + return { + string: processed, + error: hasError + }; +} + +var combiningMarksRegex = /[\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08E4-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C00-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0D01-\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F\u109A-\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u180B-\u180D\u18A9\u1920-\u192B\u1930-\u193B\u19B0-\u19C0\u19C8\u19C9\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F\u1AB0-\u1ABE\u1B00-\u1B04\u1B34-\u1B44\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BE6-\u1BF3\u1C24-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF2-\u1CF4\u1CF8\u1CF9\u1DC0-\u1DF5\u1DFC-\u1DFF\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA880\uA881\uA8B4-\uA8C4\uA8E0-\uA8F1\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9E5\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2D]|\uD800[\uDDFD\uDEE0\uDF76-\uDF7A]|\uD802[\uDE01-\uDE03\uDE05\uDE06\uDE0C-\uDE0F\uDE38-\uDE3A\uDE3F\uDEE5\uDEE6]|\uD804[\uDC00-\uDC02\uDC38-\uDC46\uDC7F-\uDC82\uDCB0-\uDCBA\uDD00-\uDD02\uDD27-\uDD34\uDD73\uDD80-\uDD82\uDDB3-\uDDC0\uDE2C-\uDE37\uDEDF-\uDEEA\uDF01-\uDF03\uDF3C\uDF3E-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF57\uDF62\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDCB0-\uDCC3\uDDAF-\uDDB5\uDDB8-\uDDC0\uDE30-\uDE40\uDEAB-\uDEB7]|\uD81A[\uDEF0-\uDEF4\uDF30-\uDF36]|\uD81B[\uDF51-\uDF7E\uDF8F-\uDF92]|\uD82F[\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD83A[\uDCD0-\uDCD6]|\uDB40[\uDD00-\uDDEF]/; + +function validateLabel(label, processing_option) { + if (label.substr(0, 4) === "xn--") { + label = punycode.toUnicode(label); + processing_option = PROCESSING_OPTIONS.NONTRANSITIONAL; + } + + var error = false; + + if (normalize(label) !== label || + (label[3] === "-" && label[4] === "-") || + label[0] === "-" || label[label.length - 1] === "-" || + label.indexOf(".") !== -1 || + label.search(combiningMarksRegex) === 0) { + error = true; + } + + var len = countSymbols(label); + for (var i = 0; i < len; ++i) { + var status = findStatus(label.codePointAt(i)); + if ((processing === PROCESSING_OPTIONS.TRANSITIONAL && status[1] !== "valid") || + (processing === PROCESSING_OPTIONS.NONTRANSITIONAL && + status[1] !== "valid" && status[1] !== "deviation")) { + error = true; + break; + } + } + + return { + label: label, + error: error + }; +} + +function processing(domain_name, useSTD3, processing_option) { + var result = mapChars(domain_name, useSTD3, processing_option); + result.string = normalize(result.string); + + var labels = result.string.split("."); + for (var i = 0; i < labels.length; ++i) { + try { + var validation = validateLabel(labels[i]); + labels[i] = validation.label; + result.error = result.error || validation.error; + } catch(e) { + result.error = true; + } + } + + return { + string: labels.join("."), + error: result.error + }; +} + +module.exports.toASCII = function(domain_name, useSTD3, processing_option, verifyDnsLength) { + var result = processing(domain_name, useSTD3, processing_option); + var labels = result.string.split("."); + labels = labels.map(function(l) { + try { + return punycode.toASCII(l); + } catch(e) { + result.error = true; + return l; + } + }); + + if (verifyDnsLength) { + var total = labels.slice(0, labels.length - 1).join(".").length; + if (total.length > 253 || total.length === 0) { + result.error = true; + } + + for (var i=0; i < labels.length; ++i) { + if (labels.length > 63 || labels.length === 0) { + result.error = true; + break; + } + } + } + + if (result.error) return null; + return labels.join("."); +}; + +module.exports.toUnicode = function(domain_name, useSTD3) { + var result = processing(domain_name, useSTD3, PROCESSING_OPTIONS.NONTRANSITIONAL); + + return { + domain: result.string, + error: result.error + }; +}; + +module.exports.PROCESSING_OPTIONS = PROCESSING_OPTIONS; + + +/***/ }), + +/***/ 770: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +module.exports = __nccwpck_require__(218); + + +/***/ }), + +/***/ 218: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var net = __nccwpck_require__(9278); +var tls = __nccwpck_require__(4756); +var http = __nccwpck_require__(8611); +var https = __nccwpck_require__(5692); +var events = __nccwpck_require__(4434); +var assert = __nccwpck_require__(2613); +var util = __nccwpck_require__(9023); + + +exports.httpOverHttp = httpOverHttp; +exports.httpsOverHttp = httpsOverHttp; +exports.httpOverHttps = httpOverHttps; +exports.httpsOverHttps = httpsOverHttps; + + +function httpOverHttp(options) { + var agent = new TunnelingAgent(options); + agent.request = http.request; + return agent; +} + +function httpsOverHttp(options) { + var agent = new TunnelingAgent(options); + agent.request = http.request; + agent.createSocket = createSecureSocket; + agent.defaultPort = 443; + return agent; +} + +function httpOverHttps(options) { + var agent = new TunnelingAgent(options); + agent.request = https.request; + return agent; +} + +function httpsOverHttps(options) { + var agent = new TunnelingAgent(options); + agent.request = https.request; + agent.createSocket = createSecureSocket; + agent.defaultPort = 443; + return agent; +} + + +function TunnelingAgent(options) { + var self = this; + self.options = options || {}; + self.proxyOptions = self.options.proxy || {}; + self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets; + self.requests = []; + self.sockets = []; + + self.on('free', function onFree(socket, host, port, localAddress) { + var options = toOptions(host, port, localAddress); + for (var i = 0, len = self.requests.length; i < len; ++i) { + var pending = self.requests[i]; + if (pending.host === options.host && pending.port === options.port) { + // Detect the request to connect same origin server, + // reuse the connection. + self.requests.splice(i, 1); + pending.request.onSocket(socket); + return; + } + } + socket.destroy(); + self.removeSocket(socket); + }); +} +util.inherits(TunnelingAgent, events.EventEmitter); + +TunnelingAgent.prototype.addRequest = function addRequest(req, host, port, localAddress) { + var self = this; + var options = mergeOptions({request: req}, self.options, toOptions(host, port, localAddress)); + + if (self.sockets.length >= this.maxSockets) { + // We are over limit so we'll add it to the queue. + self.requests.push(options); + return; + } + + // If we are under maxSockets create a new one. + self.createSocket(options, function(socket) { + socket.on('free', onFree); + socket.on('close', onCloseOrRemove); + socket.on('agentRemove', onCloseOrRemove); + req.onSocket(socket); + + function onFree() { + self.emit('free', socket, options); + } + + function onCloseOrRemove(err) { + self.removeSocket(socket); + socket.removeListener('free', onFree); + socket.removeListener('close', onCloseOrRemove); + socket.removeListener('agentRemove', onCloseOrRemove); + } + }); +}; + +TunnelingAgent.prototype.createSocket = function createSocket(options, cb) { + var self = this; + var placeholder = {}; + self.sockets.push(placeholder); + + var connectOptions = mergeOptions({}, self.proxyOptions, { + method: 'CONNECT', + path: options.host + ':' + options.port, + agent: false, + headers: { + host: options.host + ':' + options.port + } + }); + if (options.localAddress) { + connectOptions.localAddress = options.localAddress; + } + if (connectOptions.proxyAuth) { + connectOptions.headers = connectOptions.headers || {}; + connectOptions.headers['Proxy-Authorization'] = 'Basic ' + + new Buffer(connectOptions.proxyAuth).toString('base64'); + } + + debug('making CONNECT request'); + var connectReq = self.request(connectOptions); + connectReq.useChunkedEncodingByDefault = false; // for v0.6 + connectReq.once('response', onResponse); // for v0.6 + connectReq.once('upgrade', onUpgrade); // for v0.6 + connectReq.once('connect', onConnect); // for v0.7 or later + connectReq.once('error', onError); + connectReq.end(); + + function onResponse(res) { + // Very hacky. This is necessary to avoid http-parser leaks. + res.upgrade = true; + } + + function onUpgrade(res, socket, head) { + // Hacky. + process.nextTick(function() { + onConnect(res, socket, head); + }); + } + + function onConnect(res, socket, head) { + connectReq.removeAllListeners(); + socket.removeAllListeners(); + + if (res.statusCode !== 200) { + debug('tunneling socket could not be established, statusCode=%d', + res.statusCode); + socket.destroy(); + var error = new Error('tunneling socket could not be established, ' + + 'statusCode=' + res.statusCode); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + return; + } + if (head.length > 0) { + debug('got illegal response body from proxy'); + socket.destroy(); + var error = new Error('got illegal response body from proxy'); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + return; + } + debug('tunneling connection has established'); + self.sockets[self.sockets.indexOf(placeholder)] = socket; + return cb(socket); + } + + function onError(cause) { + connectReq.removeAllListeners(); + + debug('tunneling socket could not be established, cause=%s\n', + cause.message, cause.stack); + var error = new Error('tunneling socket could not be established, ' + + 'cause=' + cause.message); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + } +}; + +TunnelingAgent.prototype.removeSocket = function removeSocket(socket) { + var pos = this.sockets.indexOf(socket) + if (pos === -1) { + return; + } + this.sockets.splice(pos, 1); + + var pending = this.requests.shift(); + if (pending) { + // If we have pending requests and a socket gets closed a new one + // needs to be created to take over in the pool for the one that closed. + this.createSocket(pending, function(socket) { + pending.request.onSocket(socket); + }); + } +}; + +function createSecureSocket(options, cb) { + var self = this; + TunnelingAgent.prototype.createSocket.call(self, options, function(socket) { + var hostHeader = options.request.getHeader('host'); + var tlsOptions = mergeOptions({}, self.options, { + socket: socket, + servername: hostHeader ? hostHeader.replace(/:.*$/, '') : options.host + }); + + // 0 is dummy port for v0.6 + var secureSocket = tls.connect(0, tlsOptions); + self.sockets[self.sockets.indexOf(socket)] = secureSocket; + cb(secureSocket); + }); +} + + +function toOptions(host, port, localAddress) { + if (typeof host === 'string') { // since v0.10 + return { + host: host, + port: port, + localAddress: localAddress + }; + } + return host; // for v0.11 or later +} + +function mergeOptions(target) { + for (var i = 1, len = arguments.length; i < len; ++i) { + var overrides = arguments[i]; + if (typeof overrides === 'object') { + var keys = Object.keys(overrides); + for (var j = 0, keyLen = keys.length; j < keyLen; ++j) { + var k = keys[j]; + if (overrides[k] !== undefined) { + target[k] = overrides[k]; + } + } + } + } + return target; +} + + +var debug; +if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) { + debug = function() { + var args = Array.prototype.slice.call(arguments); + if (typeof args[0] === 'string') { + args[0] = 'TUNNEL: ' + args[0]; + } else { + args.unshift('TUNNEL:'); + } + console.error.apply(console, args); + } +} else { + debug = function() {}; +} +exports.debug = debug; // for test + + +/***/ }), + +/***/ 6752: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Client = __nccwpck_require__(6197) +const Dispatcher = __nccwpck_require__(992) +const errors = __nccwpck_require__(8707) +const Pool = __nccwpck_require__(5076) +const BalancedPool = __nccwpck_require__(1093) +const Agent = __nccwpck_require__(9965) +const util = __nccwpck_require__(3440) +const { InvalidArgumentError } = errors +const api = __nccwpck_require__(6615) +const buildConnector = __nccwpck_require__(9136) +const MockClient = __nccwpck_require__(7365) +const MockAgent = __nccwpck_require__(7501) +const MockPool = __nccwpck_require__(4004) +const mockErrors = __nccwpck_require__(2429) +const ProxyAgent = __nccwpck_require__(2720) +const RetryHandler = __nccwpck_require__(3573) +const { getGlobalDispatcher, setGlobalDispatcher } = __nccwpck_require__(2581) +const DecoratorHandler = __nccwpck_require__(8840) +const RedirectHandler = __nccwpck_require__(8299) +const createRedirectInterceptor = __nccwpck_require__(4415) + +let hasCrypto +try { + __nccwpck_require__(6982) + hasCrypto = true +} catch { + hasCrypto = false +} + +Object.assign(Dispatcher.prototype, api) + +module.exports.Dispatcher = Dispatcher +module.exports.Client = Client +module.exports.Pool = Pool +module.exports.BalancedPool = BalancedPool +module.exports.Agent = Agent +module.exports.ProxyAgent = ProxyAgent +module.exports.RetryHandler = RetryHandler + +module.exports.DecoratorHandler = DecoratorHandler +module.exports.RedirectHandler = RedirectHandler +module.exports.createRedirectInterceptor = createRedirectInterceptor + +module.exports.buildConnector = buildConnector +module.exports.errors = errors + +function makeDispatcher (fn) { + return (url, opts, handler) => { + if (typeof opts === 'function') { + handler = opts + opts = null + } + + if (!url || (typeof url !== 'string' && typeof url !== 'object' && !(url instanceof URL))) { + throw new InvalidArgumentError('invalid url') + } + + if (opts != null && typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + if (opts && opts.path != null) { + if (typeof opts.path !== 'string') { + throw new InvalidArgumentError('invalid opts.path') + } + + let path = opts.path + if (!opts.path.startsWith('/')) { + path = `/${path}` + } + + url = new URL(util.parseOrigin(url).origin + path) + } else { + if (!opts) { + opts = typeof url === 'object' ? url : {} + } + + url = util.parseURL(url) + } + + const { agent, dispatcher = getGlobalDispatcher() } = opts + + if (agent) { + throw new InvalidArgumentError('unsupported opts.agent. Did you mean opts.client?') + } + + return fn.call(dispatcher, { + ...opts, + origin: url.origin, + path: url.search ? `${url.pathname}${url.search}` : url.pathname, + method: opts.method || (opts.body ? 'PUT' : 'GET') + }, handler) + } +} + +module.exports.setGlobalDispatcher = setGlobalDispatcher +module.exports.getGlobalDispatcher = getGlobalDispatcher + +if (util.nodeMajor > 16 || (util.nodeMajor === 16 && util.nodeMinor >= 8)) { + let fetchImpl = null + module.exports.fetch = async function fetch (resource) { + if (!fetchImpl) { + fetchImpl = (__nccwpck_require__(2315).fetch) + } + + try { + return await fetchImpl(...arguments) + } catch (err) { + if (typeof err === 'object') { + Error.captureStackTrace(err, this) + } + + throw err + } + } + module.exports.Headers = __nccwpck_require__(6349).Headers + module.exports.Response = __nccwpck_require__(8676).Response + module.exports.Request = __nccwpck_require__(5194).Request + module.exports.FormData = __nccwpck_require__(3073).FormData + module.exports.File = __nccwpck_require__(3041).File + module.exports.FileReader = __nccwpck_require__(2160).FileReader + + const { setGlobalOrigin, getGlobalOrigin } = __nccwpck_require__(5628) + + module.exports.setGlobalOrigin = setGlobalOrigin + module.exports.getGlobalOrigin = getGlobalOrigin + + const { CacheStorage } = __nccwpck_require__(4738) + const { kConstruct } = __nccwpck_require__(296) + + // Cache & CacheStorage are tightly coupled with fetch. Even if it may run + // in an older version of Node, it doesn't have any use without fetch. + module.exports.caches = new CacheStorage(kConstruct) +} + +if (util.nodeMajor >= 16) { + const { deleteCookie, getCookies, getSetCookies, setCookie } = __nccwpck_require__(3168) + + module.exports.deleteCookie = deleteCookie + module.exports.getCookies = getCookies + module.exports.getSetCookies = getSetCookies + module.exports.setCookie = setCookie + + const { parseMIMEType, serializeAMimeType } = __nccwpck_require__(4322) + + module.exports.parseMIMEType = parseMIMEType + module.exports.serializeAMimeType = serializeAMimeType +} + +if (util.nodeMajor >= 18 && hasCrypto) { + const { WebSocket } = __nccwpck_require__(5171) + + module.exports.WebSocket = WebSocket +} + +module.exports.request = makeDispatcher(api.request) +module.exports.stream = makeDispatcher(api.stream) +module.exports.pipeline = makeDispatcher(api.pipeline) +module.exports.connect = makeDispatcher(api.connect) +module.exports.upgrade = makeDispatcher(api.upgrade) + +module.exports.MockClient = MockClient +module.exports.MockPool = MockPool +module.exports.MockAgent = MockAgent +module.exports.mockErrors = mockErrors + + +/***/ }), + +/***/ 9965: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { InvalidArgumentError } = __nccwpck_require__(8707) +const { kClients, kRunning, kClose, kDestroy, kDispatch, kInterceptors } = __nccwpck_require__(6443) +const DispatcherBase = __nccwpck_require__(1) +const Pool = __nccwpck_require__(5076) +const Client = __nccwpck_require__(6197) +const util = __nccwpck_require__(3440) +const createRedirectInterceptor = __nccwpck_require__(4415) +const { WeakRef, FinalizationRegistry } = __nccwpck_require__(3194)() + +const kOnConnect = Symbol('onConnect') +const kOnDisconnect = Symbol('onDisconnect') +const kOnConnectionError = Symbol('onConnectionError') +const kMaxRedirections = Symbol('maxRedirections') +const kOnDrain = Symbol('onDrain') +const kFactory = Symbol('factory') +const kFinalizer = Symbol('finalizer') +const kOptions = Symbol('options') + +function defaultFactory (origin, opts) { + return opts && opts.connections === 1 + ? new Client(origin, opts) + : new Pool(origin, opts) +} + +class Agent extends DispatcherBase { + constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) { + super() + + if (typeof factory !== 'function') { + throw new InvalidArgumentError('factory must be a function.') + } + + if (connect != null && typeof connect !== 'function' && typeof connect !== 'object') { + throw new InvalidArgumentError('connect must be a function or an object') + } + + if (!Number.isInteger(maxRedirections) || maxRedirections < 0) { + throw new InvalidArgumentError('maxRedirections must be a positive number') + } + + if (connect && typeof connect !== 'function') { + connect = { ...connect } + } + + this[kInterceptors] = options.interceptors && options.interceptors.Agent && Array.isArray(options.interceptors.Agent) + ? options.interceptors.Agent + : [createRedirectInterceptor({ maxRedirections })] + + this[kOptions] = { ...util.deepClone(options), connect } + this[kOptions].interceptors = options.interceptors + ? { ...options.interceptors } + : undefined + this[kMaxRedirections] = maxRedirections + this[kFactory] = factory + this[kClients] = new Map() + this[kFinalizer] = new FinalizationRegistry(/* istanbul ignore next: gc is undeterministic */ key => { + const ref = this[kClients].get(key) + if (ref !== undefined && ref.deref() === undefined) { + this[kClients].delete(key) + } + }) + + const agent = this + + this[kOnDrain] = (origin, targets) => { + agent.emit('drain', origin, [agent, ...targets]) + } + + this[kOnConnect] = (origin, targets) => { + agent.emit('connect', origin, [agent, ...targets]) + } + + this[kOnDisconnect] = (origin, targets, err) => { + agent.emit('disconnect', origin, [agent, ...targets], err) + } + + this[kOnConnectionError] = (origin, targets, err) => { + agent.emit('connectionError', origin, [agent, ...targets], err) + } + } + + get [kRunning] () { + let ret = 0 + for (const ref of this[kClients].values()) { + const client = ref.deref() + /* istanbul ignore next: gc is undeterministic */ + if (client) { + ret += client[kRunning] + } + } + return ret + } + + [kDispatch] (opts, handler) { + let key + if (opts.origin && (typeof opts.origin === 'string' || opts.origin instanceof URL)) { + key = String(opts.origin) + } else { + throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.') + } + + const ref = this[kClients].get(key) + + let dispatcher = ref ? ref.deref() : null + if (!dispatcher) { + dispatcher = this[kFactory](opts.origin, this[kOptions]) + .on('drain', this[kOnDrain]) + .on('connect', this[kOnConnect]) + .on('disconnect', this[kOnDisconnect]) + .on('connectionError', this[kOnConnectionError]) + + this[kClients].set(key, new WeakRef(dispatcher)) + this[kFinalizer].register(dispatcher, key) + } + + return dispatcher.dispatch(opts, handler) + } + + async [kClose] () { + const closePromises = [] + for (const ref of this[kClients].values()) { + const client = ref.deref() + /* istanbul ignore else: gc is undeterministic */ + if (client) { + closePromises.push(client.close()) + } + } + + await Promise.all(closePromises) + } + + async [kDestroy] (err) { + const destroyPromises = [] + for (const ref of this[kClients].values()) { + const client = ref.deref() + /* istanbul ignore else: gc is undeterministic */ + if (client) { + destroyPromises.push(client.destroy(err)) + } + } + + await Promise.all(destroyPromises) + } +} + +module.exports = Agent + + +/***/ }), + +/***/ 158: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const { addAbortListener } = __nccwpck_require__(3440) +const { RequestAbortedError } = __nccwpck_require__(8707) + +const kListener = Symbol('kListener') +const kSignal = Symbol('kSignal') + +function abort (self) { + if (self.abort) { + self.abort() + } else { + self.onError(new RequestAbortedError()) + } +} + +function addSignal (self, signal) { + self[kSignal] = null + self[kListener] = null + + if (!signal) { + return + } + + if (signal.aborted) { + abort(self) + return + } + + self[kSignal] = signal + self[kListener] = () => { + abort(self) + } + + addAbortListener(self[kSignal], self[kListener]) +} + +function removeSignal (self) { + if (!self[kSignal]) { + return + } + + if ('removeEventListener' in self[kSignal]) { + self[kSignal].removeEventListener('abort', self[kListener]) + } else { + self[kSignal].removeListener('abort', self[kListener]) + } + + self[kSignal] = null + self[kListener] = null +} + +module.exports = { + addSignal, + removeSignal +} + + +/***/ }), + +/***/ 4660: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { AsyncResource } = __nccwpck_require__(290) +const { InvalidArgumentError, RequestAbortedError, SocketError } = __nccwpck_require__(8707) +const util = __nccwpck_require__(3440) +const { addSignal, removeSignal } = __nccwpck_require__(158) + +class ConnectHandler extends AsyncResource { + constructor (opts, callback) { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + const { signal, opaque, responseHeaders } = opts + + if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { + throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') + } + + super('UNDICI_CONNECT') + + this.opaque = opaque || null + this.responseHeaders = responseHeaders || null + this.callback = callback + this.abort = null + + addSignal(this, signal) + } + + onConnect (abort, context) { + if (!this.callback) { + throw new RequestAbortedError() + } + + this.abort = abort + this.context = context + } + + onHeaders () { + throw new SocketError('bad connect', null) + } + + onUpgrade (statusCode, rawHeaders, socket) { + const { callback, opaque, context } = this + + removeSignal(this) + + this.callback = null + + let headers = rawHeaders + // Indicates is an HTTP2Session + if (headers != null) { + headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + } + + this.runInAsyncScope(callback, null, null, { + statusCode, + headers, + socket, + opaque, + context + }) + } + + onError (err) { + const { callback, opaque } = this + + removeSignal(this) + + if (callback) { + this.callback = null + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }) + }) + } + } +} + +function connect (opts, callback) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + connect.call(this, opts, (err, data) => { + return err ? reject(err) : resolve(data) + }) + }) + } + + try { + const connectHandler = new ConnectHandler(opts, callback) + this.dispatch({ ...opts, method: 'CONNECT' }, connectHandler) + } catch (err) { + if (typeof callback !== 'function') { + throw err + } + const opaque = opts && opts.opaque + queueMicrotask(() => callback(err, { opaque })) + } +} + +module.exports = connect + + +/***/ }), + +/***/ 6862: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + Readable, + Duplex, + PassThrough +} = __nccwpck_require__(2203) +const { + InvalidArgumentError, + InvalidReturnValueError, + RequestAbortedError +} = __nccwpck_require__(8707) +const util = __nccwpck_require__(3440) +const { AsyncResource } = __nccwpck_require__(290) +const { addSignal, removeSignal } = __nccwpck_require__(158) +const assert = __nccwpck_require__(2613) + +const kResume = Symbol('resume') + +class PipelineRequest extends Readable { + constructor () { + super({ autoDestroy: true }) + + this[kResume] = null + } + + _read () { + const { [kResume]: resume } = this + + if (resume) { + this[kResume] = null + resume() + } + } + + _destroy (err, callback) { + this._read() + + callback(err) + } +} + +class PipelineResponse extends Readable { + constructor (resume) { + super({ autoDestroy: true }) + this[kResume] = resume + } + + _read () { + this[kResume]() + } + + _destroy (err, callback) { + if (!err && !this._readableState.endEmitted) { + err = new RequestAbortedError() + } + + callback(err) + } +} + +class PipelineHandler extends AsyncResource { + constructor (opts, handler) { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + if (typeof handler !== 'function') { + throw new InvalidArgumentError('invalid handler') + } + + const { signal, method, opaque, onInfo, responseHeaders } = opts + + if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { + throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') + } + + if (method === 'CONNECT') { + throw new InvalidArgumentError('invalid method') + } + + if (onInfo && typeof onInfo !== 'function') { + throw new InvalidArgumentError('invalid onInfo callback') + } + + super('UNDICI_PIPELINE') + + this.opaque = opaque || null + this.responseHeaders = responseHeaders || null + this.handler = handler + this.abort = null + this.context = null + this.onInfo = onInfo || null + + this.req = new PipelineRequest().on('error', util.nop) + + this.ret = new Duplex({ + readableObjectMode: opts.objectMode, + autoDestroy: true, + read: () => { + const { body } = this + + if (body && body.resume) { + body.resume() + } + }, + write: (chunk, encoding, callback) => { + const { req } = this + + if (req.push(chunk, encoding) || req._readableState.destroyed) { + callback() + } else { + req[kResume] = callback + } + }, + destroy: (err, callback) => { + const { body, req, res, ret, abort } = this + + if (!err && !ret._readableState.endEmitted) { + err = new RequestAbortedError() + } + + if (abort && err) { + abort() + } + + util.destroy(body, err) + util.destroy(req, err) + util.destroy(res, err) + + removeSignal(this) + + callback(err) + } + }).on('prefinish', () => { + const { req } = this + + // Node < 15 does not call _final in same tick. + req.push(null) + }) + + this.res = null + + addSignal(this, signal) + } + + onConnect (abort, context) { + const { ret, res } = this + + assert(!res, 'pipeline cannot be retried') + + if (ret.destroyed) { + throw new RequestAbortedError() + } + + this.abort = abort + this.context = context + } + + onHeaders (statusCode, rawHeaders, resume) { + const { opaque, handler, context } = this + + if (statusCode < 200) { + if (this.onInfo) { + const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + this.onInfo({ statusCode, headers }) + } + return + } + + this.res = new PipelineResponse(resume) + + let body + try { + this.handler = null + const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + body = this.runInAsyncScope(handler, null, { + statusCode, + headers, + opaque, + body: this.res, + context + }) + } catch (err) { + this.res.on('error', util.nop) + throw err + } + + if (!body || typeof body.on !== 'function') { + throw new InvalidReturnValueError('expected Readable') + } + + body + .on('data', (chunk) => { + const { ret, body } = this + + if (!ret.push(chunk) && body.pause) { + body.pause() + } + }) + .on('error', (err) => { + const { ret } = this + + util.destroy(ret, err) + }) + .on('end', () => { + const { ret } = this + + ret.push(null) + }) + .on('close', () => { + const { ret } = this + + if (!ret._readableState.ended) { + util.destroy(ret, new RequestAbortedError()) + } + }) + + this.body = body + } + + onData (chunk) { + const { res } = this + return res.push(chunk) + } + + onComplete (trailers) { + const { res } = this + res.push(null) + } + + onError (err) { + const { ret } = this + this.handler = null + util.destroy(ret, err) + } +} + +function pipeline (opts, handler) { + try { + const pipelineHandler = new PipelineHandler(opts, handler) + this.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler) + return pipelineHandler.ret + } catch (err) { + return new PassThrough().destroy(err) + } +} + +module.exports = pipeline + + +/***/ }), + +/***/ 4043: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Readable = __nccwpck_require__(9927) +const { + InvalidArgumentError, + RequestAbortedError +} = __nccwpck_require__(8707) +const util = __nccwpck_require__(3440) +const { getResolveErrorBodyCallback } = __nccwpck_require__(7655) +const { AsyncResource } = __nccwpck_require__(290) +const { addSignal, removeSignal } = __nccwpck_require__(158) + +class RequestHandler extends AsyncResource { + constructor (opts, callback) { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError, highWaterMark } = opts + + try { + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + if (highWaterMark && (typeof highWaterMark !== 'number' || highWaterMark < 0)) { + throw new InvalidArgumentError('invalid highWaterMark') + } + + if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { + throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') + } + + if (method === 'CONNECT') { + throw new InvalidArgumentError('invalid method') + } + + if (onInfo && typeof onInfo !== 'function') { + throw new InvalidArgumentError('invalid onInfo callback') + } + + super('UNDICI_REQUEST') + } catch (err) { + if (util.isStream(body)) { + util.destroy(body.on('error', util.nop), err) + } + throw err + } + + this.responseHeaders = responseHeaders || null + this.opaque = opaque || null + this.callback = callback + this.res = null + this.abort = null + this.body = body + this.trailers = {} + this.context = null + this.onInfo = onInfo || null + this.throwOnError = throwOnError + this.highWaterMark = highWaterMark + + if (util.isStream(body)) { + body.on('error', (err) => { + this.onError(err) + }) + } + + addSignal(this, signal) + } + + onConnect (abort, context) { + if (!this.callback) { + throw new RequestAbortedError() + } + + this.abort = abort + this.context = context + } + + onHeaders (statusCode, rawHeaders, resume, statusMessage) { + const { callback, opaque, abort, context, responseHeaders, highWaterMark } = this + + const headers = responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + + if (statusCode < 200) { + if (this.onInfo) { + this.onInfo({ statusCode, headers }) + } + return + } + + const parsedHeaders = responseHeaders === 'raw' ? util.parseHeaders(rawHeaders) : headers + const contentType = parsedHeaders['content-type'] + const body = new Readable({ resume, abort, contentType, highWaterMark }) + + this.callback = null + this.res = body + if (callback !== null) { + if (this.throwOnError && statusCode >= 400) { + this.runInAsyncScope(getResolveErrorBodyCallback, null, + { callback, body, contentType, statusCode, statusMessage, headers } + ) + } else { + this.runInAsyncScope(callback, null, null, { + statusCode, + headers, + trailers: this.trailers, + opaque, + body, + context + }) + } + } + } + + onData (chunk) { + const { res } = this + return res.push(chunk) + } + + onComplete (trailers) { + const { res } = this + + removeSignal(this) + + util.parseHeaders(trailers, this.trailers) + + res.push(null) + } + + onError (err) { + const { res, callback, body, opaque } = this + + removeSignal(this) + + if (callback) { + // TODO: Does this need queueMicrotask? + this.callback = null + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }) + }) + } + + if (res) { + this.res = null + // Ensure all queued handlers are invoked before destroying res. + queueMicrotask(() => { + util.destroy(res, err) + }) + } + + if (body) { + this.body = null + util.destroy(body, err) + } + } +} + +function request (opts, callback) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + request.call(this, opts, (err, data) => { + return err ? reject(err) : resolve(data) + }) + }) + } + + try { + this.dispatch(opts, new RequestHandler(opts, callback)) + } catch (err) { + if (typeof callback !== 'function') { + throw err + } + const opaque = opts && opts.opaque + queueMicrotask(() => callback(err, { opaque })) + } +} + +module.exports = request +module.exports.RequestHandler = RequestHandler + + +/***/ }), + +/***/ 3560: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { finished, PassThrough } = __nccwpck_require__(2203) +const { + InvalidArgumentError, + InvalidReturnValueError, + RequestAbortedError +} = __nccwpck_require__(8707) +const util = __nccwpck_require__(3440) +const { getResolveErrorBodyCallback } = __nccwpck_require__(7655) +const { AsyncResource } = __nccwpck_require__(290) +const { addSignal, removeSignal } = __nccwpck_require__(158) + +class StreamHandler extends AsyncResource { + constructor (opts, factory, callback) { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError } = opts + + try { + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + if (typeof factory !== 'function') { + throw new InvalidArgumentError('invalid factory') + } + + if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { + throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') + } + + if (method === 'CONNECT') { + throw new InvalidArgumentError('invalid method') + } + + if (onInfo && typeof onInfo !== 'function') { + throw new InvalidArgumentError('invalid onInfo callback') + } + + super('UNDICI_STREAM') + } catch (err) { + if (util.isStream(body)) { + util.destroy(body.on('error', util.nop), err) + } + throw err + } + + this.responseHeaders = responseHeaders || null + this.opaque = opaque || null + this.factory = factory + this.callback = callback + this.res = null + this.abort = null + this.context = null + this.trailers = null + this.body = body + this.onInfo = onInfo || null + this.throwOnError = throwOnError || false + + if (util.isStream(body)) { + body.on('error', (err) => { + this.onError(err) + }) + } + + addSignal(this, signal) + } + + onConnect (abort, context) { + if (!this.callback) { + throw new RequestAbortedError() + } + + this.abort = abort + this.context = context + } + + onHeaders (statusCode, rawHeaders, resume, statusMessage) { + const { factory, opaque, context, callback, responseHeaders } = this + + const headers = responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + + if (statusCode < 200) { + if (this.onInfo) { + this.onInfo({ statusCode, headers }) + } + return + } + + this.factory = null + + let res + + if (this.throwOnError && statusCode >= 400) { + const parsedHeaders = responseHeaders === 'raw' ? util.parseHeaders(rawHeaders) : headers + const contentType = parsedHeaders['content-type'] + res = new PassThrough() + + this.callback = null + this.runInAsyncScope(getResolveErrorBodyCallback, null, + { callback, body: res, contentType, statusCode, statusMessage, headers } + ) + } else { + if (factory === null) { + return + } + + res = this.runInAsyncScope(factory, null, { + statusCode, + headers, + opaque, + context + }) + + if ( + !res || + typeof res.write !== 'function' || + typeof res.end !== 'function' || + typeof res.on !== 'function' + ) { + throw new InvalidReturnValueError('expected Writable') + } + + // TODO: Avoid finished. It registers an unnecessary amount of listeners. + finished(res, { readable: false }, (err) => { + const { callback, res, opaque, trailers, abort } = this + + this.res = null + if (err || !res.readable) { + util.destroy(res, err) + } + + this.callback = null + this.runInAsyncScope(callback, null, err || null, { opaque, trailers }) + + if (err) { + abort() + } + }) + } + + res.on('drain', resume) + + this.res = res + + const needDrain = res.writableNeedDrain !== undefined + ? res.writableNeedDrain + : res._writableState && res._writableState.needDrain + + return needDrain !== true + } + + onData (chunk) { + const { res } = this + + return res ? res.write(chunk) : true + } + + onComplete (trailers) { + const { res } = this + + removeSignal(this) + + if (!res) { + return + } + + this.trailers = util.parseHeaders(trailers) + + res.end() + } + + onError (err) { + const { res, callback, opaque, body } = this + + removeSignal(this) + + this.factory = null + + if (res) { + this.res = null + util.destroy(res, err) + } else if (callback) { + this.callback = null + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }) + }) + } + + if (body) { + this.body = null + util.destroy(body, err) + } + } +} + +function stream (opts, factory, callback) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + stream.call(this, opts, factory, (err, data) => { + return err ? reject(err) : resolve(data) + }) + }) + } + + try { + this.dispatch(opts, new StreamHandler(opts, factory, callback)) + } catch (err) { + if (typeof callback !== 'function') { + throw err + } + const opaque = opts && opts.opaque + queueMicrotask(() => callback(err, { opaque })) + } +} + +module.exports = stream + + +/***/ }), + +/***/ 1882: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { InvalidArgumentError, RequestAbortedError, SocketError } = __nccwpck_require__(8707) +const { AsyncResource } = __nccwpck_require__(290) +const util = __nccwpck_require__(3440) +const { addSignal, removeSignal } = __nccwpck_require__(158) +const assert = __nccwpck_require__(2613) + +class UpgradeHandler extends AsyncResource { + constructor (opts, callback) { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + const { signal, opaque, responseHeaders } = opts + + if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { + throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') + } + + super('UNDICI_UPGRADE') + + this.responseHeaders = responseHeaders || null + this.opaque = opaque || null + this.callback = callback + this.abort = null + this.context = null + + addSignal(this, signal) + } + + onConnect (abort, context) { + if (!this.callback) { + throw new RequestAbortedError() + } + + this.abort = abort + this.context = null + } + + onHeaders () { + throw new SocketError('bad upgrade', null) + } + + onUpgrade (statusCode, rawHeaders, socket) { + const { callback, opaque, context } = this + + assert.strictEqual(statusCode, 101) + + removeSignal(this) + + this.callback = null + const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + this.runInAsyncScope(callback, null, null, { + headers, + socket, + opaque, + context + }) + } + + onError (err) { + const { callback, opaque } = this + + removeSignal(this) + + if (callback) { + this.callback = null + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }) + }) + } + } +} + +function upgrade (opts, callback) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + upgrade.call(this, opts, (err, data) => { + return err ? reject(err) : resolve(data) + }) + }) + } + + try { + const upgradeHandler = new UpgradeHandler(opts, callback) + this.dispatch({ + ...opts, + method: opts.method || 'GET', + upgrade: opts.protocol || 'Websocket' + }, upgradeHandler) + } catch (err) { + if (typeof callback !== 'function') { + throw err + } + const opaque = opts && opts.opaque + queueMicrotask(() => callback(err, { opaque })) + } +} + +module.exports = upgrade + + +/***/ }), + +/***/ 6615: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +module.exports.request = __nccwpck_require__(4043) +module.exports.stream = __nccwpck_require__(3560) +module.exports.pipeline = __nccwpck_require__(6862) +module.exports.upgrade = __nccwpck_require__(1882) +module.exports.connect = __nccwpck_require__(4660) + + +/***/ }), + +/***/ 9927: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +// Ported from https://github.com/nodejs/undici/pull/907 + + + +const assert = __nccwpck_require__(2613) +const { Readable } = __nccwpck_require__(2203) +const { RequestAbortedError, NotSupportedError, InvalidArgumentError } = __nccwpck_require__(8707) +const util = __nccwpck_require__(3440) +const { ReadableStreamFrom, toUSVString } = __nccwpck_require__(3440) + +let Blob + +const kConsume = Symbol('kConsume') +const kReading = Symbol('kReading') +const kBody = Symbol('kBody') +const kAbort = Symbol('abort') +const kContentType = Symbol('kContentType') + +const noop = () => {} + +module.exports = class BodyReadable extends Readable { + constructor ({ + resume, + abort, + contentType = '', + highWaterMark = 64 * 1024 // Same as nodejs fs streams. + }) { + super({ + autoDestroy: true, + read: resume, + highWaterMark + }) + + this._readableState.dataEmitted = false + + this[kAbort] = abort + this[kConsume] = null + this[kBody] = null + this[kContentType] = contentType + + // Is stream being consumed through Readable API? + // This is an optimization so that we avoid checking + // for 'data' and 'readable' listeners in the hot path + // inside push(). + this[kReading] = false + } + + destroy (err) { + if (this.destroyed) { + // Node < 16 + return this + } + + if (!err && !this._readableState.endEmitted) { + err = new RequestAbortedError() + } + + if (err) { + this[kAbort]() + } + + return super.destroy(err) + } + + emit (ev, ...args) { + if (ev === 'data') { + // Node < 16.7 + this._readableState.dataEmitted = true + } else if (ev === 'error') { + // Node < 16 + this._readableState.errorEmitted = true + } + return super.emit(ev, ...args) + } + + on (ev, ...args) { + if (ev === 'data' || ev === 'readable') { + this[kReading] = true + } + return super.on(ev, ...args) + } + + addListener (ev, ...args) { + return this.on(ev, ...args) + } + + off (ev, ...args) { + const ret = super.off(ev, ...args) + if (ev === 'data' || ev === 'readable') { + this[kReading] = ( + this.listenerCount('data') > 0 || + this.listenerCount('readable') > 0 + ) + } + return ret + } + + removeListener (ev, ...args) { + return this.off(ev, ...args) + } + + push (chunk) { + if (this[kConsume] && chunk !== null && this.readableLength === 0) { + consumePush(this[kConsume], chunk) + return this[kReading] ? super.push(chunk) : true + } + return super.push(chunk) + } + + // https://fetch.spec.whatwg.org/#dom-body-text + async text () { + return consume(this, 'text') + } + + // https://fetch.spec.whatwg.org/#dom-body-json + async json () { + return consume(this, 'json') + } + + // https://fetch.spec.whatwg.org/#dom-body-blob + async blob () { + return consume(this, 'blob') + } + + // https://fetch.spec.whatwg.org/#dom-body-arraybuffer + async arrayBuffer () { + return consume(this, 'arrayBuffer') + } + + // https://fetch.spec.whatwg.org/#dom-body-formdata + async formData () { + // TODO: Implement. + throw new NotSupportedError() + } + + // https://fetch.spec.whatwg.org/#dom-body-bodyused + get bodyUsed () { + return util.isDisturbed(this) + } + + // https://fetch.spec.whatwg.org/#dom-body-body + get body () { + if (!this[kBody]) { + this[kBody] = ReadableStreamFrom(this) + if (this[kConsume]) { + // TODO: Is this the best way to force a lock? + this[kBody].getReader() // Ensure stream is locked. + assert(this[kBody].locked) + } + } + return this[kBody] + } + + dump (opts) { + let limit = opts && Number.isFinite(opts.limit) ? opts.limit : 262144 + const signal = opts && opts.signal + + if (signal) { + try { + if (typeof signal !== 'object' || !('aborted' in signal)) { + throw new InvalidArgumentError('signal must be an AbortSignal') + } + util.throwIfAborted(signal) + } catch (err) { + return Promise.reject(err) + } + } + + if (this.closed) { + return Promise.resolve(null) + } + + return new Promise((resolve, reject) => { + const signalListenerCleanup = signal + ? util.addAbortListener(signal, () => { + this.destroy() + }) + : noop + + this + .on('close', function () { + signalListenerCleanup() + if (signal && signal.aborted) { + reject(signal.reason || Object.assign(new Error('The operation was aborted'), { name: 'AbortError' })) + } else { + resolve(null) + } + }) + .on('error', noop) + .on('data', function (chunk) { + limit -= chunk.length + if (limit <= 0) { + this.destroy() + } + }) + .resume() + }) + } +} + +// https://streams.spec.whatwg.org/#readablestream-locked +function isLocked (self) { + // Consume is an implicit lock. + return (self[kBody] && self[kBody].locked === true) || self[kConsume] +} + +// https://fetch.spec.whatwg.org/#body-unusable +function isUnusable (self) { + return util.isDisturbed(self) || isLocked(self) +} + +async function consume (stream, type) { + if (isUnusable(stream)) { + throw new TypeError('unusable') + } + + assert(!stream[kConsume]) + + return new Promise((resolve, reject) => { + stream[kConsume] = { + type, + stream, + resolve, + reject, + length: 0, + body: [] + } + + stream + .on('error', function (err) { + consumeFinish(this[kConsume], err) + }) + .on('close', function () { + if (this[kConsume].body !== null) { + consumeFinish(this[kConsume], new RequestAbortedError()) + } + }) + + process.nextTick(consumeStart, stream[kConsume]) + }) +} + +function consumeStart (consume) { + if (consume.body === null) { + return + } + + const { _readableState: state } = consume.stream + + for (const chunk of state.buffer) { + consumePush(consume, chunk) + } + + if (state.endEmitted) { + consumeEnd(this[kConsume]) + } else { + consume.stream.on('end', function () { + consumeEnd(this[kConsume]) + }) + } + + consume.stream.resume() + + while (consume.stream.read() != null) { + // Loop + } +} + +function consumeEnd (consume) { + const { type, body, resolve, stream, length } = consume + + try { + if (type === 'text') { + resolve(toUSVString(Buffer.concat(body))) + } else if (type === 'json') { + resolve(JSON.parse(Buffer.concat(body))) + } else if (type === 'arrayBuffer') { + const dst = new Uint8Array(length) + + let pos = 0 + for (const buf of body) { + dst.set(buf, pos) + pos += buf.byteLength + } + + resolve(dst.buffer) + } else if (type === 'blob') { + if (!Blob) { + Blob = (__nccwpck_require__(181).Blob) + } + resolve(new Blob(body, { type: stream[kContentType] })) + } + + consumeFinish(consume) + } catch (err) { + stream.destroy(err) + } +} + +function consumePush (consume, chunk) { + consume.length += chunk.length + consume.body.push(chunk) +} + +function consumeFinish (consume, err) { + if (consume.body === null) { + return + } + + if (err) { + consume.reject(err) + } else { + consume.resolve() + } + + consume.type = null + consume.stream = null + consume.resolve = null + consume.reject = null + consume.length = 0 + consume.body = null +} + + +/***/ }), + +/***/ 7655: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const assert = __nccwpck_require__(2613) +const { + ResponseStatusCodeError +} = __nccwpck_require__(8707) +const { toUSVString } = __nccwpck_require__(3440) + +async function getResolveErrorBodyCallback ({ callback, body, contentType, statusCode, statusMessage, headers }) { + assert(body) + + let chunks = [] + let limit = 0 + + for await (const chunk of body) { + chunks.push(chunk) + limit += chunk.length + if (limit > 128 * 1024) { + chunks = null + break + } + } + + if (statusCode === 204 || !contentType || !chunks) { + process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers)) + return + } + + try { + if (contentType.startsWith('application/json')) { + const payload = JSON.parse(toUSVString(Buffer.concat(chunks))) + process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers, payload)) + return + } + + if (contentType.startsWith('text/')) { + const payload = toUSVString(Buffer.concat(chunks)) + process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers, payload)) + return + } + } catch (err) { + // Process in a fallback if error + } + + process.nextTick(callback, new ResponseStatusCodeError(`Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`, statusCode, headers)) +} + +module.exports = { getResolveErrorBodyCallback } + + +/***/ }), + +/***/ 1093: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + BalancedPoolMissingUpstreamError, + InvalidArgumentError +} = __nccwpck_require__(8707) +const { + PoolBase, + kClients, + kNeedDrain, + kAddClient, + kRemoveClient, + kGetDispatcher +} = __nccwpck_require__(8640) +const Pool = __nccwpck_require__(5076) +const { kUrl, kInterceptors } = __nccwpck_require__(6443) +const { parseOrigin } = __nccwpck_require__(3440) +const kFactory = Symbol('factory') + +const kOptions = Symbol('options') +const kGreatestCommonDivisor = Symbol('kGreatestCommonDivisor') +const kCurrentWeight = Symbol('kCurrentWeight') +const kIndex = Symbol('kIndex') +const kWeight = Symbol('kWeight') +const kMaxWeightPerServer = Symbol('kMaxWeightPerServer') +const kErrorPenalty = Symbol('kErrorPenalty') + +function getGreatestCommonDivisor (a, b) { + if (b === 0) return a + return getGreatestCommonDivisor(b, a % b) +} + +function defaultFactory (origin, opts) { + return new Pool(origin, opts) +} + +class BalancedPool extends PoolBase { + constructor (upstreams = [], { factory = defaultFactory, ...opts } = {}) { + super() + + this[kOptions] = opts + this[kIndex] = -1 + this[kCurrentWeight] = 0 + + this[kMaxWeightPerServer] = this[kOptions].maxWeightPerServer || 100 + this[kErrorPenalty] = this[kOptions].errorPenalty || 15 + + if (!Array.isArray(upstreams)) { + upstreams = [upstreams] + } + + if (typeof factory !== 'function') { + throw new InvalidArgumentError('factory must be a function.') + } + + this[kInterceptors] = opts.interceptors && opts.interceptors.BalancedPool && Array.isArray(opts.interceptors.BalancedPool) + ? opts.interceptors.BalancedPool + : [] + this[kFactory] = factory + + for (const upstream of upstreams) { + this.addUpstream(upstream) + } + this._updateBalancedPoolStats() + } + + addUpstream (upstream) { + const upstreamOrigin = parseOrigin(upstream).origin + + if (this[kClients].find((pool) => ( + pool[kUrl].origin === upstreamOrigin && + pool.closed !== true && + pool.destroyed !== true + ))) { + return this + } + const pool = this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions])) + + this[kAddClient](pool) + pool.on('connect', () => { + pool[kWeight] = Math.min(this[kMaxWeightPerServer], pool[kWeight] + this[kErrorPenalty]) + }) + + pool.on('connectionError', () => { + pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty]) + this._updateBalancedPoolStats() + }) + + pool.on('disconnect', (...args) => { + const err = args[2] + if (err && err.code === 'UND_ERR_SOCKET') { + // decrease the weight of the pool. + pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty]) + this._updateBalancedPoolStats() + } + }) + + for (const client of this[kClients]) { + client[kWeight] = this[kMaxWeightPerServer] + } + + this._updateBalancedPoolStats() + + return this + } + + _updateBalancedPoolStats () { + this[kGreatestCommonDivisor] = this[kClients].map(p => p[kWeight]).reduce(getGreatestCommonDivisor, 0) + } + + removeUpstream (upstream) { + const upstreamOrigin = parseOrigin(upstream).origin + + const pool = this[kClients].find((pool) => ( + pool[kUrl].origin === upstreamOrigin && + pool.closed !== true && + pool.destroyed !== true + )) + + if (pool) { + this[kRemoveClient](pool) + } + + return this + } + + get upstreams () { + return this[kClients] + .filter(dispatcher => dispatcher.closed !== true && dispatcher.destroyed !== true) + .map((p) => p[kUrl].origin) + } + + [kGetDispatcher] () { + // We validate that pools is greater than 0, + // otherwise we would have to wait until an upstream + // is added, which might never happen. + if (this[kClients].length === 0) { + throw new BalancedPoolMissingUpstreamError() + } + + const dispatcher = this[kClients].find(dispatcher => ( + !dispatcher[kNeedDrain] && + dispatcher.closed !== true && + dispatcher.destroyed !== true + )) + + if (!dispatcher) { + return + } + + const allClientsBusy = this[kClients].map(pool => pool[kNeedDrain]).reduce((a, b) => a && b, true) + + if (allClientsBusy) { + return + } + + let counter = 0 + + let maxWeightIndex = this[kClients].findIndex(pool => !pool[kNeedDrain]) + + while (counter++ < this[kClients].length) { + this[kIndex] = (this[kIndex] + 1) % this[kClients].length + const pool = this[kClients][this[kIndex]] + + // find pool index with the largest weight + if (pool[kWeight] > this[kClients][maxWeightIndex][kWeight] && !pool[kNeedDrain]) { + maxWeightIndex = this[kIndex] + } + + // decrease the current weight every `this[kClients].length`. + if (this[kIndex] === 0) { + // Set the current weight to the next lower weight. + this[kCurrentWeight] = this[kCurrentWeight] - this[kGreatestCommonDivisor] + + if (this[kCurrentWeight] <= 0) { + this[kCurrentWeight] = this[kMaxWeightPerServer] + } + } + if (pool[kWeight] >= this[kCurrentWeight] && (!pool[kNeedDrain])) { + return pool + } + } + + this[kCurrentWeight] = this[kClients][maxWeightIndex][kWeight] + this[kIndex] = maxWeightIndex + return this[kClients][maxWeightIndex] + } +} + +module.exports = BalancedPool + + +/***/ }), + +/***/ 479: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { kConstruct } = __nccwpck_require__(296) +const { urlEquals, fieldValues: getFieldValues } = __nccwpck_require__(3993) +const { kEnumerableProperty, isDisturbed } = __nccwpck_require__(3440) +const { kHeadersList } = __nccwpck_require__(6443) +const { webidl } = __nccwpck_require__(4222) +const { Response, cloneResponse } = __nccwpck_require__(8676) +const { Request } = __nccwpck_require__(5194) +const { kState, kHeaders, kGuard, kRealm } = __nccwpck_require__(9710) +const { fetching } = __nccwpck_require__(2315) +const { urlIsHttpHttpsScheme, createDeferredPromise, readAllBytes } = __nccwpck_require__(5523) +const assert = __nccwpck_require__(2613) +const { getGlobalDispatcher } = __nccwpck_require__(2581) + +/** + * @see https://w3c.github.io/ServiceWorker/#dfn-cache-batch-operation + * @typedef {Object} CacheBatchOperation + * @property {'delete' | 'put'} type + * @property {any} request + * @property {any} response + * @property {import('../../types/cache').CacheQueryOptions} options + */ + +/** + * @see https://w3c.github.io/ServiceWorker/#dfn-request-response-list + * @typedef {[any, any][]} requestResponseList + */ + +class Cache { + /** + * @see https://w3c.github.io/ServiceWorker/#dfn-relevant-request-response-list + * @type {requestResponseList} + */ + #relevantRequestResponseList + + constructor () { + if (arguments[0] !== kConstruct) { + webidl.illegalConstructor() + } + + this.#relevantRequestResponseList = arguments[1] + } + + async match (request, options = {}) { + webidl.brandCheck(this, Cache) + webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.match' }) + + request = webidl.converters.RequestInfo(request) + options = webidl.converters.CacheQueryOptions(options) + + const p = await this.matchAll(request, options) + + if (p.length === 0) { + return + } + + return p[0] + } + + async matchAll (request = undefined, options = {}) { + webidl.brandCheck(this, Cache) + + if (request !== undefined) request = webidl.converters.RequestInfo(request) + options = webidl.converters.CacheQueryOptions(options) + + // 1. + let r = null + + // 2. + if (request !== undefined) { + if (request instanceof Request) { + // 2.1.1 + r = request[kState] + + // 2.1.2 + if (r.method !== 'GET' && !options.ignoreMethod) { + return [] + } + } else if (typeof request === 'string') { + // 2.2.1 + r = new Request(request)[kState] + } + } + + // 5. + // 5.1 + const responses = [] + + // 5.2 + if (request === undefined) { + // 5.2.1 + for (const requestResponse of this.#relevantRequestResponseList) { + responses.push(requestResponse[1]) + } + } else { // 5.3 + // 5.3.1 + const requestResponses = this.#queryCache(r, options) + + // 5.3.2 + for (const requestResponse of requestResponses) { + responses.push(requestResponse[1]) + } + } + + // 5.4 + // We don't implement CORs so we don't need to loop over the responses, yay! + + // 5.5.1 + const responseList = [] + + // 5.5.2 + for (const response of responses) { + // 5.5.2.1 + const responseObject = new Response(response.body?.source ?? null) + const body = responseObject[kState].body + responseObject[kState] = response + responseObject[kState].body = body + responseObject[kHeaders][kHeadersList] = response.headersList + responseObject[kHeaders][kGuard] = 'immutable' + + responseList.push(responseObject) + } + + // 6. + return Object.freeze(responseList) + } + + async add (request) { + webidl.brandCheck(this, Cache) + webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.add' }) + + request = webidl.converters.RequestInfo(request) + + // 1. + const requests = [request] + + // 2. + const responseArrayPromise = this.addAll(requests) + + // 3. + return await responseArrayPromise + } + + async addAll (requests) { + webidl.brandCheck(this, Cache) + webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.addAll' }) + + requests = webidl.converters['sequence'](requests) + + // 1. + const responsePromises = [] + + // 2. + const requestList = [] + + // 3. + for (const request of requests) { + if (typeof request === 'string') { + continue + } + + // 3.1 + const r = request[kState] + + // 3.2 + if (!urlIsHttpHttpsScheme(r.url) || r.method !== 'GET') { + throw webidl.errors.exception({ + header: 'Cache.addAll', + message: 'Expected http/s scheme when method is not GET.' + }) + } + } + + // 4. + /** @type {ReturnType[]} */ + const fetchControllers = [] + + // 5. + for (const request of requests) { + // 5.1 + const r = new Request(request)[kState] + + // 5.2 + if (!urlIsHttpHttpsScheme(r.url)) { + throw webidl.errors.exception({ + header: 'Cache.addAll', + message: 'Expected http/s scheme.' + }) + } + + // 5.4 + r.initiator = 'fetch' + r.destination = 'subresource' + + // 5.5 + requestList.push(r) + + // 5.6 + const responsePromise = createDeferredPromise() + + // 5.7 + fetchControllers.push(fetching({ + request: r, + dispatcher: getGlobalDispatcher(), + processResponse (response) { + // 1. + if (response.type === 'error' || response.status === 206 || response.status < 200 || response.status > 299) { + responsePromise.reject(webidl.errors.exception({ + header: 'Cache.addAll', + message: 'Received an invalid status code or the request failed.' + })) + } else if (response.headersList.contains('vary')) { // 2. + // 2.1 + const fieldValues = getFieldValues(response.headersList.get('vary')) + + // 2.2 + for (const fieldValue of fieldValues) { + // 2.2.1 + if (fieldValue === '*') { + responsePromise.reject(webidl.errors.exception({ + header: 'Cache.addAll', + message: 'invalid vary field value' + })) + + for (const controller of fetchControllers) { + controller.abort() + } + + return + } + } + } + }, + processResponseEndOfBody (response) { + // 1. + if (response.aborted) { + responsePromise.reject(new DOMException('aborted', 'AbortError')) + return + } + + // 2. + responsePromise.resolve(response) + } + })) + + // 5.8 + responsePromises.push(responsePromise.promise) + } + + // 6. + const p = Promise.all(responsePromises) + + // 7. + const responses = await p + + // 7.1 + const operations = [] + + // 7.2 + let index = 0 + + // 7.3 + for (const response of responses) { + // 7.3.1 + /** @type {CacheBatchOperation} */ + const operation = { + type: 'put', // 7.3.2 + request: requestList[index], // 7.3.3 + response // 7.3.4 + } + + operations.push(operation) // 7.3.5 + + index++ // 7.3.6 + } + + // 7.5 + const cacheJobPromise = createDeferredPromise() + + // 7.6.1 + let errorData = null + + // 7.6.2 + try { + this.#batchCacheOperations(operations) + } catch (e) { + errorData = e + } + + // 7.6.3 + queueMicrotask(() => { + // 7.6.3.1 + if (errorData === null) { + cacheJobPromise.resolve(undefined) + } else { + // 7.6.3.2 + cacheJobPromise.reject(errorData) + } + }) + + // 7.7 + return cacheJobPromise.promise + } + + async put (request, response) { + webidl.brandCheck(this, Cache) + webidl.argumentLengthCheck(arguments, 2, { header: 'Cache.put' }) + + request = webidl.converters.RequestInfo(request) + response = webidl.converters.Response(response) + + // 1. + let innerRequest = null + + // 2. + if (request instanceof Request) { + innerRequest = request[kState] + } else { // 3. + innerRequest = new Request(request)[kState] + } + + // 4. + if (!urlIsHttpHttpsScheme(innerRequest.url) || innerRequest.method !== 'GET') { + throw webidl.errors.exception({ + header: 'Cache.put', + message: 'Expected an http/s scheme when method is not GET' + }) + } + + // 5. + const innerResponse = response[kState] + + // 6. + if (innerResponse.status === 206) { + throw webidl.errors.exception({ + header: 'Cache.put', + message: 'Got 206 status' + }) + } + + // 7. + if (innerResponse.headersList.contains('vary')) { + // 7.1. + const fieldValues = getFieldValues(innerResponse.headersList.get('vary')) + + // 7.2. + for (const fieldValue of fieldValues) { + // 7.2.1 + if (fieldValue === '*') { + throw webidl.errors.exception({ + header: 'Cache.put', + message: 'Got * vary field value' + }) + } + } + } + + // 8. + if (innerResponse.body && (isDisturbed(innerResponse.body.stream) || innerResponse.body.stream.locked)) { + throw webidl.errors.exception({ + header: 'Cache.put', + message: 'Response body is locked or disturbed' + }) + } + + // 9. + const clonedResponse = cloneResponse(innerResponse) + + // 10. + const bodyReadPromise = createDeferredPromise() + + // 11. + if (innerResponse.body != null) { + // 11.1 + const stream = innerResponse.body.stream + + // 11.2 + const reader = stream.getReader() + + // 11.3 + readAllBytes(reader).then(bodyReadPromise.resolve, bodyReadPromise.reject) + } else { + bodyReadPromise.resolve(undefined) + } + + // 12. + /** @type {CacheBatchOperation[]} */ + const operations = [] + + // 13. + /** @type {CacheBatchOperation} */ + const operation = { + type: 'put', // 14. + request: innerRequest, // 15. + response: clonedResponse // 16. + } + + // 17. + operations.push(operation) + + // 19. + const bytes = await bodyReadPromise.promise + + if (clonedResponse.body != null) { + clonedResponse.body.source = bytes + } + + // 19.1 + const cacheJobPromise = createDeferredPromise() + + // 19.2.1 + let errorData = null + + // 19.2.2 + try { + this.#batchCacheOperations(operations) + } catch (e) { + errorData = e + } + + // 19.2.3 + queueMicrotask(() => { + // 19.2.3.1 + if (errorData === null) { + cacheJobPromise.resolve() + } else { // 19.2.3.2 + cacheJobPromise.reject(errorData) + } + }) + + return cacheJobPromise.promise + } + + async delete (request, options = {}) { + webidl.brandCheck(this, Cache) + webidl.argumentLengthCheck(arguments, 1, { header: 'Cache.delete' }) + + request = webidl.converters.RequestInfo(request) + options = webidl.converters.CacheQueryOptions(options) + + /** + * @type {Request} + */ + let r = null + + if (request instanceof Request) { + r = request[kState] + + if (r.method !== 'GET' && !options.ignoreMethod) { + return false + } + } else { + assert(typeof request === 'string') + + r = new Request(request)[kState] + } + + /** @type {CacheBatchOperation[]} */ + const operations = [] + + /** @type {CacheBatchOperation} */ + const operation = { + type: 'delete', + request: r, + options + } + + operations.push(operation) + + const cacheJobPromise = createDeferredPromise() + + let errorData = null + let requestResponses + + try { + requestResponses = this.#batchCacheOperations(operations) + } catch (e) { + errorData = e + } + + queueMicrotask(() => { + if (errorData === null) { + cacheJobPromise.resolve(!!requestResponses?.length) + } else { + cacheJobPromise.reject(errorData) + } + }) + + return cacheJobPromise.promise + } + + /** + * @see https://w3c.github.io/ServiceWorker/#dom-cache-keys + * @param {any} request + * @param {import('../../types/cache').CacheQueryOptions} options + * @returns {readonly Request[]} + */ + async keys (request = undefined, options = {}) { + webidl.brandCheck(this, Cache) + + if (request !== undefined) request = webidl.converters.RequestInfo(request) + options = webidl.converters.CacheQueryOptions(options) + + // 1. + let r = null + + // 2. + if (request !== undefined) { + // 2.1 + if (request instanceof Request) { + // 2.1.1 + r = request[kState] + + // 2.1.2 + if (r.method !== 'GET' && !options.ignoreMethod) { + return [] + } + } else if (typeof request === 'string') { // 2.2 + r = new Request(request)[kState] + } + } + + // 4. + const promise = createDeferredPromise() + + // 5. + // 5.1 + const requests = [] + + // 5.2 + if (request === undefined) { + // 5.2.1 + for (const requestResponse of this.#relevantRequestResponseList) { + // 5.2.1.1 + requests.push(requestResponse[0]) + } + } else { // 5.3 + // 5.3.1 + const requestResponses = this.#queryCache(r, options) + + // 5.3.2 + for (const requestResponse of requestResponses) { + // 5.3.2.1 + requests.push(requestResponse[0]) + } + } + + // 5.4 + queueMicrotask(() => { + // 5.4.1 + const requestList = [] + + // 5.4.2 + for (const request of requests) { + const requestObject = new Request('https://a') + requestObject[kState] = request + requestObject[kHeaders][kHeadersList] = request.headersList + requestObject[kHeaders][kGuard] = 'immutable' + requestObject[kRealm] = request.client + + // 5.4.2.1 + requestList.push(requestObject) + } + + // 5.4.3 + promise.resolve(Object.freeze(requestList)) + }) + + return promise.promise + } + + /** + * @see https://w3c.github.io/ServiceWorker/#batch-cache-operations-algorithm + * @param {CacheBatchOperation[]} operations + * @returns {requestResponseList} + */ + #batchCacheOperations (operations) { + // 1. + const cache = this.#relevantRequestResponseList + + // 2. + const backupCache = [...cache] + + // 3. + const addedItems = [] + + // 4.1 + const resultList = [] + + try { + // 4.2 + for (const operation of operations) { + // 4.2.1 + if (operation.type !== 'delete' && operation.type !== 'put') { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'operation type does not match "delete" or "put"' + }) + } + + // 4.2.2 + if (operation.type === 'delete' && operation.response != null) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'delete operation should not have an associated response' + }) + } + + // 4.2.3 + if (this.#queryCache(operation.request, operation.options, addedItems).length) { + throw new DOMException('???', 'InvalidStateError') + } + + // 4.2.4 + let requestResponses + + // 4.2.5 + if (operation.type === 'delete') { + // 4.2.5.1 + requestResponses = this.#queryCache(operation.request, operation.options) + + // TODO: the spec is wrong, this is needed to pass WPTs + if (requestResponses.length === 0) { + return [] + } + + // 4.2.5.2 + for (const requestResponse of requestResponses) { + const idx = cache.indexOf(requestResponse) + assert(idx !== -1) + + // 4.2.5.2.1 + cache.splice(idx, 1) + } + } else if (operation.type === 'put') { // 4.2.6 + // 4.2.6.1 + if (operation.response == null) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'put operation should have an associated response' + }) + } + + // 4.2.6.2 + const r = operation.request + + // 4.2.6.3 + if (!urlIsHttpHttpsScheme(r.url)) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'expected http or https scheme' + }) + } + + // 4.2.6.4 + if (r.method !== 'GET') { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'not get method' + }) + } + + // 4.2.6.5 + if (operation.options != null) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'options must not be defined' + }) + } + + // 4.2.6.6 + requestResponses = this.#queryCache(operation.request) + + // 4.2.6.7 + for (const requestResponse of requestResponses) { + const idx = cache.indexOf(requestResponse) + assert(idx !== -1) + + // 4.2.6.7.1 + cache.splice(idx, 1) + } + + // 4.2.6.8 + cache.push([operation.request, operation.response]) + + // 4.2.6.10 + addedItems.push([operation.request, operation.response]) + } + + // 4.2.7 + resultList.push([operation.request, operation.response]) + } + + // 4.3 + return resultList + } catch (e) { // 5. + // 5.1 + this.#relevantRequestResponseList.length = 0 + + // 5.2 + this.#relevantRequestResponseList = backupCache + + // 5.3 + throw e + } + } + + /** + * @see https://w3c.github.io/ServiceWorker/#query-cache + * @param {any} requestQuery + * @param {import('../../types/cache').CacheQueryOptions} options + * @param {requestResponseList} targetStorage + * @returns {requestResponseList} + */ + #queryCache (requestQuery, options, targetStorage) { + /** @type {requestResponseList} */ + const resultList = [] + + const storage = targetStorage ?? this.#relevantRequestResponseList + + for (const requestResponse of storage) { + const [cachedRequest, cachedResponse] = requestResponse + if (this.#requestMatchesCachedItem(requestQuery, cachedRequest, cachedResponse, options)) { + resultList.push(requestResponse) + } + } + + return resultList + } + + /** + * @see https://w3c.github.io/ServiceWorker/#request-matches-cached-item-algorithm + * @param {any} requestQuery + * @param {any} request + * @param {any | null} response + * @param {import('../../types/cache').CacheQueryOptions | undefined} options + * @returns {boolean} + */ + #requestMatchesCachedItem (requestQuery, request, response = null, options) { + // if (options?.ignoreMethod === false && request.method === 'GET') { + // return false + // } + + const queryURL = new URL(requestQuery.url) + + const cachedURL = new URL(request.url) + + if (options?.ignoreSearch) { + cachedURL.search = '' + + queryURL.search = '' + } + + if (!urlEquals(queryURL, cachedURL, true)) { + return false + } + + if ( + response == null || + options?.ignoreVary || + !response.headersList.contains('vary') + ) { + return true + } + + const fieldValues = getFieldValues(response.headersList.get('vary')) + + for (const fieldValue of fieldValues) { + if (fieldValue === '*') { + return false + } + + const requestValue = request.headersList.get(fieldValue) + const queryValue = requestQuery.headersList.get(fieldValue) + + // If one has the header and the other doesn't, or one has + // a different value than the other, return false + if (requestValue !== queryValue) { + return false + } + } + + return true + } +} + +Object.defineProperties(Cache.prototype, { + [Symbol.toStringTag]: { + value: 'Cache', + configurable: true + }, + match: kEnumerableProperty, + matchAll: kEnumerableProperty, + add: kEnumerableProperty, + addAll: kEnumerableProperty, + put: kEnumerableProperty, + delete: kEnumerableProperty, + keys: kEnumerableProperty +}) + +const cacheQueryOptionConverters = [ + { + key: 'ignoreSearch', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'ignoreMethod', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'ignoreVary', + converter: webidl.converters.boolean, + defaultValue: false + } +] + +webidl.converters.CacheQueryOptions = webidl.dictionaryConverter(cacheQueryOptionConverters) + +webidl.converters.MultiCacheQueryOptions = webidl.dictionaryConverter([ + ...cacheQueryOptionConverters, + { + key: 'cacheName', + converter: webidl.converters.DOMString + } +]) + +webidl.converters.Response = webidl.interfaceConverter(Response) + +webidl.converters['sequence'] = webidl.sequenceConverter( + webidl.converters.RequestInfo +) + +module.exports = { + Cache +} + + +/***/ }), + +/***/ 4738: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { kConstruct } = __nccwpck_require__(296) +const { Cache } = __nccwpck_require__(479) +const { webidl } = __nccwpck_require__(4222) +const { kEnumerableProperty } = __nccwpck_require__(3440) + +class CacheStorage { + /** + * @see https://w3c.github.io/ServiceWorker/#dfn-relevant-name-to-cache-map + * @type {Map} + */ + async has (cacheName) { + webidl.brandCheck(this, CacheStorage) + webidl.argumentLengthCheck(arguments, 1, { header: 'CacheStorage.has' }) + + cacheName = webidl.converters.DOMString(cacheName) + + // 2.1.1 + // 2.2 + return this.#caches.has(cacheName) + } + + /** + * @see https://w3c.github.io/ServiceWorker/#dom-cachestorage-open + * @param {string} cacheName + * @returns {Promise} + */ + async open (cacheName) { + webidl.brandCheck(this, CacheStorage) + webidl.argumentLengthCheck(arguments, 1, { header: 'CacheStorage.open' }) + + cacheName = webidl.converters.DOMString(cacheName) + + // 2.1 + if (this.#caches.has(cacheName)) { + // await caches.open('v1') !== await caches.open('v1') + + // 2.1.1 + const cache = this.#caches.get(cacheName) + + // 2.1.1.1 + return new Cache(kConstruct, cache) + } + + // 2.2 + const cache = [] + + // 2.3 + this.#caches.set(cacheName, cache) + + // 2.4 + return new Cache(kConstruct, cache) + } + + /** + * @see https://w3c.github.io/ServiceWorker/#cache-storage-delete + * @param {string} cacheName + * @returns {Promise} + */ + async delete (cacheName) { + webidl.brandCheck(this, CacheStorage) + webidl.argumentLengthCheck(arguments, 1, { header: 'CacheStorage.delete' }) + + cacheName = webidl.converters.DOMString(cacheName) + + return this.#caches.delete(cacheName) + } + + /** + * @see https://w3c.github.io/ServiceWorker/#cache-storage-keys + * @returns {string[]} + */ + async keys () { + webidl.brandCheck(this, CacheStorage) + + // 2.1 + const keys = this.#caches.keys() + + // 2.2 + return [...keys] + } +} + +Object.defineProperties(CacheStorage.prototype, { + [Symbol.toStringTag]: { + value: 'CacheStorage', + configurable: true + }, + match: kEnumerableProperty, + has: kEnumerableProperty, + open: kEnumerableProperty, + delete: kEnumerableProperty, + keys: kEnumerableProperty +}) + +module.exports = { + CacheStorage +} + + +/***/ }), + +/***/ 296: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +module.exports = { + kConstruct: (__nccwpck_require__(6443).kConstruct) +} + + +/***/ }), + +/***/ 3993: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const assert = __nccwpck_require__(2613) +const { URLSerializer } = __nccwpck_require__(4322) +const { isValidHeaderName } = __nccwpck_require__(5523) + +/** + * @see https://url.spec.whatwg.org/#concept-url-equals + * @param {URL} A + * @param {URL} B + * @param {boolean | undefined} excludeFragment + * @returns {boolean} + */ +function urlEquals (A, B, excludeFragment = false) { + const serializedA = URLSerializer(A, excludeFragment) + + const serializedB = URLSerializer(B, excludeFragment) + + return serializedA === serializedB +} + +/** + * @see https://github.com/chromium/chromium/blob/694d20d134cb553d8d89e5500b9148012b1ba299/content/browser/cache_storage/cache_storage_cache.cc#L260-L262 + * @param {string} header + */ +function fieldValues (header) { + assert(header !== null) + + const values = [] + + for (let value of header.split(',')) { + value = value.trim() + + if (!value.length) { + continue + } else if (!isValidHeaderName(value)) { + continue + } + + values.push(value) + } + + return values +} + +module.exports = { + urlEquals, + fieldValues +} + + +/***/ }), + +/***/ 6197: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +// @ts-check + + + +/* global WebAssembly */ + +const assert = __nccwpck_require__(2613) +const net = __nccwpck_require__(9278) +const http = __nccwpck_require__(8611) +const { pipeline } = __nccwpck_require__(2203) +const util = __nccwpck_require__(3440) +const timers = __nccwpck_require__(8804) +const Request = __nccwpck_require__(4655) +const DispatcherBase = __nccwpck_require__(1) +const { + RequestContentLengthMismatchError, + ResponseContentLengthMismatchError, + InvalidArgumentError, + RequestAbortedError, + HeadersTimeoutError, + HeadersOverflowError, + SocketError, + InformationalError, + BodyTimeoutError, + HTTPParserError, + ResponseExceededMaxSizeError, + ClientDestroyedError +} = __nccwpck_require__(8707) +const buildConnector = __nccwpck_require__(9136) +const { + kUrl, + kReset, + kServerName, + kClient, + kBusy, + kParser, + kConnect, + kBlocking, + kResuming, + kRunning, + kPending, + kSize, + kWriting, + kQueue, + kConnected, + kConnecting, + kNeedDrain, + kNoRef, + kKeepAliveDefaultTimeout, + kHostHeader, + kPendingIdx, + kRunningIdx, + kError, + kPipelining, + kSocket, + kKeepAliveTimeoutValue, + kMaxHeadersSize, + kKeepAliveMaxTimeout, + kKeepAliveTimeoutThreshold, + kHeadersTimeout, + kBodyTimeout, + kStrictContentLength, + kConnector, + kMaxRedirections, + kMaxRequests, + kCounter, + kClose, + kDestroy, + kDispatch, + kInterceptors, + kLocalAddress, + kMaxResponseSize, + kHTTPConnVersion, + // HTTP2 + kHost, + kHTTP2Session, + kHTTP2SessionState, + kHTTP2BuildRequest, + kHTTP2CopyHeaders, + kHTTP1BuildRequest +} = __nccwpck_require__(6443) + +/** @type {import('http2')} */ +let http2 +try { + http2 = __nccwpck_require__(5675) +} catch { + // @ts-ignore + http2 = { constants: {} } +} + +const { + constants: { + HTTP2_HEADER_AUTHORITY, + HTTP2_HEADER_METHOD, + HTTP2_HEADER_PATH, + HTTP2_HEADER_SCHEME, + HTTP2_HEADER_CONTENT_LENGTH, + HTTP2_HEADER_EXPECT, + HTTP2_HEADER_STATUS + } +} = http2 + +// Experimental +let h2ExperimentalWarned = false + +const FastBuffer = Buffer[Symbol.species] + +const kClosedResolve = Symbol('kClosedResolve') + +const channels = {} + +try { + const diagnosticsChannel = __nccwpck_require__(1637) + channels.sendHeaders = diagnosticsChannel.channel('undici:client:sendHeaders') + channels.beforeConnect = diagnosticsChannel.channel('undici:client:beforeConnect') + channels.connectError = diagnosticsChannel.channel('undici:client:connectError') + channels.connected = diagnosticsChannel.channel('undici:client:connected') +} catch { + channels.sendHeaders = { hasSubscribers: false } + channels.beforeConnect = { hasSubscribers: false } + channels.connectError = { hasSubscribers: false } + channels.connected = { hasSubscribers: false } +} + +/** + * @type {import('../types/client').default} + */ +class Client extends DispatcherBase { + /** + * + * @param {string|URL} url + * @param {import('../types/client').Client.Options} options + */ + constructor (url, { + interceptors, + maxHeaderSize, + headersTimeout, + socketTimeout, + requestTimeout, + connectTimeout, + bodyTimeout, + idleTimeout, + keepAlive, + keepAliveTimeout, + maxKeepAliveTimeout, + keepAliveMaxTimeout, + keepAliveTimeoutThreshold, + socketPath, + pipelining, + tls, + strictContentLength, + maxCachedSessions, + maxRedirections, + connect, + maxRequestsPerClient, + localAddress, + maxResponseSize, + autoSelectFamily, + autoSelectFamilyAttemptTimeout, + // h2 + allowH2, + maxConcurrentStreams + } = {}) { + super() + + if (keepAlive !== undefined) { + throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead') + } + + if (socketTimeout !== undefined) { + throw new InvalidArgumentError('unsupported socketTimeout, use headersTimeout & bodyTimeout instead') + } + + if (requestTimeout !== undefined) { + throw new InvalidArgumentError('unsupported requestTimeout, use headersTimeout & bodyTimeout instead') + } + + if (idleTimeout !== undefined) { + throw new InvalidArgumentError('unsupported idleTimeout, use keepAliveTimeout instead') + } + + if (maxKeepAliveTimeout !== undefined) { + throw new InvalidArgumentError('unsupported maxKeepAliveTimeout, use keepAliveMaxTimeout instead') + } + + if (maxHeaderSize != null && !Number.isFinite(maxHeaderSize)) { + throw new InvalidArgumentError('invalid maxHeaderSize') + } + + if (socketPath != null && typeof socketPath !== 'string') { + throw new InvalidArgumentError('invalid socketPath') + } + + if (connectTimeout != null && (!Number.isFinite(connectTimeout) || connectTimeout < 0)) { + throw new InvalidArgumentError('invalid connectTimeout') + } + + if (keepAliveTimeout != null && (!Number.isFinite(keepAliveTimeout) || keepAliveTimeout <= 0)) { + throw new InvalidArgumentError('invalid keepAliveTimeout') + } + + if (keepAliveMaxTimeout != null && (!Number.isFinite(keepAliveMaxTimeout) || keepAliveMaxTimeout <= 0)) { + throw new InvalidArgumentError('invalid keepAliveMaxTimeout') + } + + if (keepAliveTimeoutThreshold != null && !Number.isFinite(keepAliveTimeoutThreshold)) { + throw new InvalidArgumentError('invalid keepAliveTimeoutThreshold') + } + + if (headersTimeout != null && (!Number.isInteger(headersTimeout) || headersTimeout < 0)) { + throw new InvalidArgumentError('headersTimeout must be a positive integer or zero') + } + + if (bodyTimeout != null && (!Number.isInteger(bodyTimeout) || bodyTimeout < 0)) { + throw new InvalidArgumentError('bodyTimeout must be a positive integer or zero') + } + + if (connect != null && typeof connect !== 'function' && typeof connect !== 'object') { + throw new InvalidArgumentError('connect must be a function or an object') + } + + if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) { + throw new InvalidArgumentError('maxRedirections must be a positive number') + } + + if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) { + throw new InvalidArgumentError('maxRequestsPerClient must be a positive number') + } + + if (localAddress != null && (typeof localAddress !== 'string' || net.isIP(localAddress) === 0)) { + throw new InvalidArgumentError('localAddress must be valid string IP address') + } + + if (maxResponseSize != null && (!Number.isInteger(maxResponseSize) || maxResponseSize < -1)) { + throw new InvalidArgumentError('maxResponseSize must be a positive number') + } + + if ( + autoSelectFamilyAttemptTimeout != null && + (!Number.isInteger(autoSelectFamilyAttemptTimeout) || autoSelectFamilyAttemptTimeout < -1) + ) { + throw new InvalidArgumentError('autoSelectFamilyAttemptTimeout must be a positive number') + } + + // h2 + if (allowH2 != null && typeof allowH2 !== 'boolean') { + throw new InvalidArgumentError('allowH2 must be a valid boolean value') + } + + if (maxConcurrentStreams != null && (typeof maxConcurrentStreams !== 'number' || maxConcurrentStreams < 1)) { + throw new InvalidArgumentError('maxConcurrentStreams must be a possitive integer, greater than 0') + } + + if (typeof connect !== 'function') { + connect = buildConnector({ + ...tls, + maxCachedSessions, + allowH2, + socketPath, + timeout: connectTimeout, + ...(util.nodeHasAutoSelectFamily && autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined), + ...connect + }) + } + + this[kInterceptors] = interceptors && interceptors.Client && Array.isArray(interceptors.Client) + ? interceptors.Client + : [createRedirectInterceptor({ maxRedirections })] + this[kUrl] = util.parseOrigin(url) + this[kConnector] = connect + this[kSocket] = null + this[kPipelining] = pipelining != null ? pipelining : 1 + this[kMaxHeadersSize] = maxHeaderSize || http.maxHeaderSize + this[kKeepAliveDefaultTimeout] = keepAliveTimeout == null ? 4e3 : keepAliveTimeout + this[kKeepAliveMaxTimeout] = keepAliveMaxTimeout == null ? 600e3 : keepAliveMaxTimeout + this[kKeepAliveTimeoutThreshold] = keepAliveTimeoutThreshold == null ? 1e3 : keepAliveTimeoutThreshold + this[kKeepAliveTimeoutValue] = this[kKeepAliveDefaultTimeout] + this[kServerName] = null + this[kLocalAddress] = localAddress != null ? localAddress : null + this[kResuming] = 0 // 0, idle, 1, scheduled, 2 resuming + this[kNeedDrain] = 0 // 0, idle, 1, scheduled, 2 resuming + this[kHostHeader] = `host: ${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ''}\r\n` + this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 300e3 + this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 300e3 + this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength + this[kMaxRedirections] = maxRedirections + this[kMaxRequests] = maxRequestsPerClient + this[kClosedResolve] = null + this[kMaxResponseSize] = maxResponseSize > -1 ? maxResponseSize : -1 + this[kHTTPConnVersion] = 'h1' + + // HTTP/2 + this[kHTTP2Session] = null + this[kHTTP2SessionState] = !allowH2 + ? null + : { + // streams: null, // Fixed queue of streams - For future support of `push` + openStreams: 0, // Keep track of them to decide wether or not unref the session + maxConcurrentStreams: maxConcurrentStreams != null ? maxConcurrentStreams : 100 // Max peerConcurrentStreams for a Node h2 server + } + this[kHost] = `${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ''}` + + // kQueue is built up of 3 sections separated by + // the kRunningIdx and kPendingIdx indices. + // | complete | running | pending | + // ^ kRunningIdx ^ kPendingIdx ^ kQueue.length + // kRunningIdx points to the first running element. + // kPendingIdx points to the first pending element. + // This implements a fast queue with an amortized + // time of O(1). + + this[kQueue] = [] + this[kRunningIdx] = 0 + this[kPendingIdx] = 0 + } + + get pipelining () { + return this[kPipelining] + } + + set pipelining (value) { + this[kPipelining] = value + resume(this, true) + } + + get [kPending] () { + return this[kQueue].length - this[kPendingIdx] + } + + get [kRunning] () { + return this[kPendingIdx] - this[kRunningIdx] + } + + get [kSize] () { + return this[kQueue].length - this[kRunningIdx] + } + + get [kConnected] () { + return !!this[kSocket] && !this[kConnecting] && !this[kSocket].destroyed + } + + get [kBusy] () { + const socket = this[kSocket] + return ( + (socket && (socket[kReset] || socket[kWriting] || socket[kBlocking])) || + (this[kSize] >= (this[kPipelining] || 1)) || + this[kPending] > 0 + ) + } + + /* istanbul ignore: only used for test */ + [kConnect] (cb) { + connect(this) + this.once('connect', cb) + } + + [kDispatch] (opts, handler) { + const origin = opts.origin || this[kUrl].origin + + const request = this[kHTTPConnVersion] === 'h2' + ? Request[kHTTP2BuildRequest](origin, opts, handler) + : Request[kHTTP1BuildRequest](origin, opts, handler) + + this[kQueue].push(request) + if (this[kResuming]) { + // Do nothing. + } else if (util.bodyLength(request.body) == null && util.isIterable(request.body)) { + // Wait a tick in case stream/iterator is ended in the same tick. + this[kResuming] = 1 + process.nextTick(resume, this) + } else { + resume(this, true) + } + + if (this[kResuming] && this[kNeedDrain] !== 2 && this[kBusy]) { + this[kNeedDrain] = 2 + } + + return this[kNeedDrain] < 2 + } + + async [kClose] () { + // TODO: for H2 we need to gracefully flush the remaining enqueued + // request and close each stream. + return new Promise((resolve) => { + if (!this[kSize]) { + resolve(null) + } else { + this[kClosedResolve] = resolve + } + }) + } + + async [kDestroy] (err) { + return new Promise((resolve) => { + const requests = this[kQueue].splice(this[kPendingIdx]) + for (let i = 0; i < requests.length; i++) { + const request = requests[i] + errorRequest(this, request, err) + } + + const callback = () => { + if (this[kClosedResolve]) { + // TODO (fix): Should we error here with ClientDestroyedError? + this[kClosedResolve]() + this[kClosedResolve] = null + } + resolve() + } + + if (this[kHTTP2Session] != null) { + util.destroy(this[kHTTP2Session], err) + this[kHTTP2Session] = null + this[kHTTP2SessionState] = null + } + + if (!this[kSocket]) { + queueMicrotask(callback) + } else { + util.destroy(this[kSocket].on('close', callback), err) + } + + resume(this) + }) + } +} + +function onHttp2SessionError (err) { + assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID') + + this[kSocket][kError] = err + + onError(this[kClient], err) +} + +function onHttp2FrameError (type, code, id) { + const err = new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`) + + if (id === 0) { + this[kSocket][kError] = err + onError(this[kClient], err) + } +} + +function onHttp2SessionEnd () { + util.destroy(this, new SocketError('other side closed')) + util.destroy(this[kSocket], new SocketError('other side closed')) +} + +function onHTTP2GoAway (code) { + const client = this[kClient] + const err = new InformationalError(`HTTP/2: "GOAWAY" frame received with code ${code}`) + client[kSocket] = null + client[kHTTP2Session] = null + + if (client.destroyed) { + assert(this[kPending] === 0) + + // Fail entire queue. + const requests = client[kQueue].splice(client[kRunningIdx]) + for (let i = 0; i < requests.length; i++) { + const request = requests[i] + errorRequest(this, request, err) + } + } else if (client[kRunning] > 0) { + // Fail head of pipeline. + const request = client[kQueue][client[kRunningIdx]] + client[kQueue][client[kRunningIdx]++] = null + + errorRequest(client, request, err) + } + + client[kPendingIdx] = client[kRunningIdx] + + assert(client[kRunning] === 0) + + client.emit('disconnect', + client[kUrl], + [client], + err + ) + + resume(client) +} + +const constants = __nccwpck_require__(2824) +const createRedirectInterceptor = __nccwpck_require__(4415) +const EMPTY_BUF = Buffer.alloc(0) + +async function lazyllhttp () { + const llhttpWasmData = process.env.JEST_WORKER_ID ? __nccwpck_require__(3870) : undefined + + let mod + try { + mod = await WebAssembly.compile(Buffer.from(__nccwpck_require__(3434), 'base64')) + } catch (e) { + /* istanbul ignore next */ + + // We could check if the error was caused by the simd option not + // being enabled, but the occurring of this other error + // * https://github.com/emscripten-core/emscripten/issues/11495 + // got me to remove that check to avoid breaking Node 12. + mod = await WebAssembly.compile(Buffer.from(llhttpWasmData || __nccwpck_require__(3870), 'base64')) + } + + return await WebAssembly.instantiate(mod, { + env: { + /* eslint-disable camelcase */ + + wasm_on_url: (p, at, len) => { + /* istanbul ignore next */ + return 0 + }, + wasm_on_status: (p, at, len) => { + assert.strictEqual(currentParser.ptr, p) + const start = at - currentBufferPtr + currentBufferRef.byteOffset + return currentParser.onStatus(new FastBuffer(currentBufferRef.buffer, start, len)) || 0 + }, + wasm_on_message_begin: (p) => { + assert.strictEqual(currentParser.ptr, p) + return currentParser.onMessageBegin() || 0 + }, + wasm_on_header_field: (p, at, len) => { + assert.strictEqual(currentParser.ptr, p) + const start = at - currentBufferPtr + currentBufferRef.byteOffset + return currentParser.onHeaderField(new FastBuffer(currentBufferRef.buffer, start, len)) || 0 + }, + wasm_on_header_value: (p, at, len) => { + assert.strictEqual(currentParser.ptr, p) + const start = at - currentBufferPtr + currentBufferRef.byteOffset + return currentParser.onHeaderValue(new FastBuffer(currentBufferRef.buffer, start, len)) || 0 + }, + wasm_on_headers_complete: (p, statusCode, upgrade, shouldKeepAlive) => { + assert.strictEqual(currentParser.ptr, p) + return currentParser.onHeadersComplete(statusCode, Boolean(upgrade), Boolean(shouldKeepAlive)) || 0 + }, + wasm_on_body: (p, at, len) => { + assert.strictEqual(currentParser.ptr, p) + const start = at - currentBufferPtr + currentBufferRef.byteOffset + return currentParser.onBody(new FastBuffer(currentBufferRef.buffer, start, len)) || 0 + }, + wasm_on_message_complete: (p) => { + assert.strictEqual(currentParser.ptr, p) + return currentParser.onMessageComplete() || 0 + } + + /* eslint-enable camelcase */ + } + }) +} + +let llhttpInstance = null +let llhttpPromise = lazyllhttp() +llhttpPromise.catch() + +let currentParser = null +let currentBufferRef = null +let currentBufferSize = 0 +let currentBufferPtr = null + +const TIMEOUT_HEADERS = 1 +const TIMEOUT_BODY = 2 +const TIMEOUT_IDLE = 3 + +class Parser { + constructor (client, socket, { exports }) { + assert(Number.isFinite(client[kMaxHeadersSize]) && client[kMaxHeadersSize] > 0) + + this.llhttp = exports + this.ptr = this.llhttp.llhttp_alloc(constants.TYPE.RESPONSE) + this.client = client + this.socket = socket + this.timeout = null + this.timeoutValue = null + this.timeoutType = null + this.statusCode = null + this.statusText = '' + this.upgrade = false + this.headers = [] + this.headersSize = 0 + this.headersMaxSize = client[kMaxHeadersSize] + this.shouldKeepAlive = false + this.paused = false + this.resume = this.resume.bind(this) + + this.bytesRead = 0 + + this.keepAlive = '' + this.contentLength = '' + this.connection = '' + this.maxResponseSize = client[kMaxResponseSize] + } + + setTimeout (value, type) { + this.timeoutType = type + if (value !== this.timeoutValue) { + timers.clearTimeout(this.timeout) + if (value) { + this.timeout = timers.setTimeout(onParserTimeout, value, this) + // istanbul ignore else: only for jest + if (this.timeout.unref) { + this.timeout.unref() + } + } else { + this.timeout = null + } + this.timeoutValue = value + } else if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) { + this.timeout.refresh() + } + } + } + + resume () { + if (this.socket.destroyed || !this.paused) { + return + } + + assert(this.ptr != null) + assert(currentParser == null) + + this.llhttp.llhttp_resume(this.ptr) + + assert(this.timeoutType === TIMEOUT_BODY) + if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) { + this.timeout.refresh() + } + } + + this.paused = false + this.execute(this.socket.read() || EMPTY_BUF) // Flush parser. + this.readMore() + } + + readMore () { + while (!this.paused && this.ptr) { + const chunk = this.socket.read() + if (chunk === null) { + break + } + this.execute(chunk) + } + } + + execute (data) { + assert(this.ptr != null) + assert(currentParser == null) + assert(!this.paused) + + const { socket, llhttp } = this + + if (data.length > currentBufferSize) { + if (currentBufferPtr) { + llhttp.free(currentBufferPtr) + } + currentBufferSize = Math.ceil(data.length / 4096) * 4096 + currentBufferPtr = llhttp.malloc(currentBufferSize) + } + + new Uint8Array(llhttp.memory.buffer, currentBufferPtr, currentBufferSize).set(data) + + // Call `execute` on the wasm parser. + // We pass the `llhttp_parser` pointer address, the pointer address of buffer view data, + // and finally the length of bytes to parse. + // The return value is an error code or `constants.ERROR.OK`. + try { + let ret + + try { + currentBufferRef = data + currentParser = this + ret = llhttp.llhttp_execute(this.ptr, currentBufferPtr, data.length) + /* eslint-disable-next-line no-useless-catch */ + } catch (err) { + /* istanbul ignore next: difficult to make a test case for */ + throw err + } finally { + currentParser = null + currentBufferRef = null + } + + const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr + + if (ret === constants.ERROR.PAUSED_UPGRADE) { + this.onUpgrade(data.slice(offset)) + } else if (ret === constants.ERROR.PAUSED) { + this.paused = true + socket.unshift(data.slice(offset)) + } else if (ret !== constants.ERROR.OK) { + const ptr = llhttp.llhttp_get_error_reason(this.ptr) + let message = '' + /* istanbul ignore else: difficult to make a test case for */ + if (ptr) { + const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0) + message = + 'Response does not match the HTTP/1.1 protocol (' + + Buffer.from(llhttp.memory.buffer, ptr, len).toString() + + ')' + } + throw new HTTPParserError(message, constants.ERROR[ret], data.slice(offset)) + } + } catch (err) { + util.destroy(socket, err) + } + } + + destroy () { + assert(this.ptr != null) + assert(currentParser == null) + + this.llhttp.llhttp_free(this.ptr) + this.ptr = null + + timers.clearTimeout(this.timeout) + this.timeout = null + this.timeoutValue = null + this.timeoutType = null + + this.paused = false + } + + onStatus (buf) { + this.statusText = buf.toString() + } + + onMessageBegin () { + const { socket, client } = this + + /* istanbul ignore next: difficult to make a test case for */ + if (socket.destroyed) { + return -1 + } + + const request = client[kQueue][client[kRunningIdx]] + if (!request) { + return -1 + } + } + + onHeaderField (buf) { + const len = this.headers.length + + if ((len & 1) === 0) { + this.headers.push(buf) + } else { + this.headers[len - 1] = Buffer.concat([this.headers[len - 1], buf]) + } + + this.trackHeader(buf.length) + } + + onHeaderValue (buf) { + let len = this.headers.length + + if ((len & 1) === 1) { + this.headers.push(buf) + len += 1 + } else { + this.headers[len - 1] = Buffer.concat([this.headers[len - 1], buf]) + } + + const key = this.headers[len - 2] + if (key.length === 10 && key.toString().toLowerCase() === 'keep-alive') { + this.keepAlive += buf.toString() + } else if (key.length === 10 && key.toString().toLowerCase() === 'connection') { + this.connection += buf.toString() + } else if (key.length === 14 && key.toString().toLowerCase() === 'content-length') { + this.contentLength += buf.toString() + } + + this.trackHeader(buf.length) + } + + trackHeader (len) { + this.headersSize += len + if (this.headersSize >= this.headersMaxSize) { + util.destroy(this.socket, new HeadersOverflowError()) + } + } + + onUpgrade (head) { + const { upgrade, client, socket, headers, statusCode } = this + + assert(upgrade) + + const request = client[kQueue][client[kRunningIdx]] + assert(request) + + assert(!socket.destroyed) + assert(socket === client[kSocket]) + assert(!this.paused) + assert(request.upgrade || request.method === 'CONNECT') + + this.statusCode = null + this.statusText = '' + this.shouldKeepAlive = null + + assert(this.headers.length % 2 === 0) + this.headers = [] + this.headersSize = 0 + + socket.unshift(head) + + socket[kParser].destroy() + socket[kParser] = null + + socket[kClient] = null + socket[kError] = null + socket + .removeListener('error', onSocketError) + .removeListener('readable', onSocketReadable) + .removeListener('end', onSocketEnd) + .removeListener('close', onSocketClose) + + client[kSocket] = null + client[kQueue][client[kRunningIdx]++] = null + client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade')) + + try { + request.onUpgrade(statusCode, headers, socket) + } catch (err) { + util.destroy(socket, err) + } + + resume(client) + } + + onHeadersComplete (statusCode, upgrade, shouldKeepAlive) { + const { client, socket, headers, statusText } = this + + /* istanbul ignore next: difficult to make a test case for */ + if (socket.destroyed) { + return -1 + } + + const request = client[kQueue][client[kRunningIdx]] + + /* istanbul ignore next: difficult to make a test case for */ + if (!request) { + return -1 + } + + assert(!this.upgrade) + assert(this.statusCode < 200) + + if (statusCode === 100) { + util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket))) + return -1 + } + + /* this can only happen if server is misbehaving */ + if (upgrade && !request.upgrade) { + util.destroy(socket, new SocketError('bad upgrade', util.getSocketInfo(socket))) + return -1 + } + + assert.strictEqual(this.timeoutType, TIMEOUT_HEADERS) + + this.statusCode = statusCode + this.shouldKeepAlive = ( + shouldKeepAlive || + // Override llhttp value which does not allow keepAlive for HEAD. + (request.method === 'HEAD' && !socket[kReset] && this.connection.toLowerCase() === 'keep-alive') + ) + + if (this.statusCode >= 200) { + const bodyTimeout = request.bodyTimeout != null + ? request.bodyTimeout + : client[kBodyTimeout] + this.setTimeout(bodyTimeout, TIMEOUT_BODY) + } else if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) { + this.timeout.refresh() + } + } + + if (request.method === 'CONNECT') { + assert(client[kRunning] === 1) + this.upgrade = true + return 2 + } + + if (upgrade) { + assert(client[kRunning] === 1) + this.upgrade = true + return 2 + } + + assert(this.headers.length % 2 === 0) + this.headers = [] + this.headersSize = 0 + + if (this.shouldKeepAlive && client[kPipelining]) { + const keepAliveTimeout = this.keepAlive ? util.parseKeepAliveTimeout(this.keepAlive) : null + + if (keepAliveTimeout != null) { + const timeout = Math.min( + keepAliveTimeout - client[kKeepAliveTimeoutThreshold], + client[kKeepAliveMaxTimeout] + ) + if (timeout <= 0) { + socket[kReset] = true + } else { + client[kKeepAliveTimeoutValue] = timeout + } + } else { + client[kKeepAliveTimeoutValue] = client[kKeepAliveDefaultTimeout] + } + } else { + // Stop more requests from being dispatched. + socket[kReset] = true + } + + const pause = request.onHeaders(statusCode, headers, this.resume, statusText) === false + + if (request.aborted) { + return -1 + } + + if (request.method === 'HEAD') { + return 1 + } + + if (statusCode < 200) { + return 1 + } + + if (socket[kBlocking]) { + socket[kBlocking] = false + resume(client) + } + + return pause ? constants.ERROR.PAUSED : 0 + } + + onBody (buf) { + const { client, socket, statusCode, maxResponseSize } = this + + if (socket.destroyed) { + return -1 + } + + const request = client[kQueue][client[kRunningIdx]] + assert(request) + + assert.strictEqual(this.timeoutType, TIMEOUT_BODY) + if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) { + this.timeout.refresh() + } + } + + assert(statusCode >= 200) + + if (maxResponseSize > -1 && this.bytesRead + buf.length > maxResponseSize) { + util.destroy(socket, new ResponseExceededMaxSizeError()) + return -1 + } + + this.bytesRead += buf.length + + if (request.onData(buf) === false) { + return constants.ERROR.PAUSED + } + } + + onMessageComplete () { + const { client, socket, statusCode, upgrade, headers, contentLength, bytesRead, shouldKeepAlive } = this + + if (socket.destroyed && (!statusCode || shouldKeepAlive)) { + return -1 + } + + if (upgrade) { + return + } + + const request = client[kQueue][client[kRunningIdx]] + assert(request) + + assert(statusCode >= 100) + + this.statusCode = null + this.statusText = '' + this.bytesRead = 0 + this.contentLength = '' + this.keepAlive = '' + this.connection = '' + + assert(this.headers.length % 2 === 0) + this.headers = [] + this.headersSize = 0 + + if (statusCode < 200) { + return + } + + /* istanbul ignore next: should be handled by llhttp? */ + if (request.method !== 'HEAD' && contentLength && bytesRead !== parseInt(contentLength, 10)) { + util.destroy(socket, new ResponseContentLengthMismatchError()) + return -1 + } + + request.onComplete(headers) + + client[kQueue][client[kRunningIdx]++] = null + + if (socket[kWriting]) { + assert.strictEqual(client[kRunning], 0) + // Response completed before request. + util.destroy(socket, new InformationalError('reset')) + return constants.ERROR.PAUSED + } else if (!shouldKeepAlive) { + util.destroy(socket, new InformationalError('reset')) + return constants.ERROR.PAUSED + } else if (socket[kReset] && client[kRunning] === 0) { + // Destroy socket once all requests have completed. + // The request at the tail of the pipeline is the one + // that requested reset and no further requests should + // have been queued since then. + util.destroy(socket, new InformationalError('reset')) + return constants.ERROR.PAUSED + } else if (client[kPipelining] === 1) { + // We must wait a full event loop cycle to reuse this socket to make sure + // that non-spec compliant servers are not closing the connection even if they + // said they won't. + setImmediate(resume, client) + } else { + resume(client) + } + } +} + +function onParserTimeout (parser) { + const { socket, timeoutType, client } = parser + + /* istanbul ignore else */ + if (timeoutType === TIMEOUT_HEADERS) { + if (!socket[kWriting] || socket.writableNeedDrain || client[kRunning] > 1) { + assert(!parser.paused, 'cannot be paused while waiting for headers') + util.destroy(socket, new HeadersTimeoutError()) + } + } else if (timeoutType === TIMEOUT_BODY) { + if (!parser.paused) { + util.destroy(socket, new BodyTimeoutError()) + } + } else if (timeoutType === TIMEOUT_IDLE) { + assert(client[kRunning] === 0 && client[kKeepAliveTimeoutValue]) + util.destroy(socket, new InformationalError('socket idle timeout')) + } +} + +function onSocketReadable () { + const { [kParser]: parser } = this + if (parser) { + parser.readMore() + } +} + +function onSocketError (err) { + const { [kClient]: client, [kParser]: parser } = this + + assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID') + + if (client[kHTTPConnVersion] !== 'h2') { + // On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded + // to the user. + if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) { + // We treat all incoming data so for as a valid response. + parser.onMessageComplete() + return + } + } + + this[kError] = err + + onError(this[kClient], err) +} + +function onError (client, err) { + if ( + client[kRunning] === 0 && + err.code !== 'UND_ERR_INFO' && + err.code !== 'UND_ERR_SOCKET' + ) { + // Error is not caused by running request and not a recoverable + // socket error. + + assert(client[kPendingIdx] === client[kRunningIdx]) + + const requests = client[kQueue].splice(client[kRunningIdx]) + for (let i = 0; i < requests.length; i++) { + const request = requests[i] + errorRequest(client, request, err) + } + assert(client[kSize] === 0) + } +} + +function onSocketEnd () { + const { [kParser]: parser, [kClient]: client } = this + + if (client[kHTTPConnVersion] !== 'h2') { + if (parser.statusCode && !parser.shouldKeepAlive) { + // We treat all incoming data so far as a valid response. + parser.onMessageComplete() + return + } + } + + util.destroy(this, new SocketError('other side closed', util.getSocketInfo(this))) +} + +function onSocketClose () { + const { [kClient]: client, [kParser]: parser } = this + + if (client[kHTTPConnVersion] === 'h1' && parser) { + if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) { + // We treat all incoming data so far as a valid response. + parser.onMessageComplete() + } + + this[kParser].destroy() + this[kParser] = null + } + + const err = this[kError] || new SocketError('closed', util.getSocketInfo(this)) + + client[kSocket] = null + + if (client.destroyed) { + assert(client[kPending] === 0) + + // Fail entire queue. + const requests = client[kQueue].splice(client[kRunningIdx]) + for (let i = 0; i < requests.length; i++) { + const request = requests[i] + errorRequest(client, request, err) + } + } else if (client[kRunning] > 0 && err.code !== 'UND_ERR_INFO') { + // Fail head of pipeline. + const request = client[kQueue][client[kRunningIdx]] + client[kQueue][client[kRunningIdx]++] = null + + errorRequest(client, request, err) + } + + client[kPendingIdx] = client[kRunningIdx] + + assert(client[kRunning] === 0) + + client.emit('disconnect', client[kUrl], [client], err) + + resume(client) +} + +async function connect (client) { + assert(!client[kConnecting]) + assert(!client[kSocket]) + + let { host, hostname, protocol, port } = client[kUrl] + + // Resolve ipv6 + if (hostname[0] === '[') { + const idx = hostname.indexOf(']') + + assert(idx !== -1) + const ip = hostname.substring(1, idx) + + assert(net.isIP(ip)) + hostname = ip + } + + client[kConnecting] = true + + if (channels.beforeConnect.hasSubscribers) { + channels.beforeConnect.publish({ + connectParams: { + host, + hostname, + protocol, + port, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, + connector: client[kConnector] + }) + } + + try { + const socket = await new Promise((resolve, reject) => { + client[kConnector]({ + host, + hostname, + protocol, + port, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, (err, socket) => { + if (err) { + reject(err) + } else { + resolve(socket) + } + }) + }) + + if (client.destroyed) { + util.destroy(socket.on('error', () => {}), new ClientDestroyedError()) + return + } + + client[kConnecting] = false + + assert(socket) + + const isH2 = socket.alpnProtocol === 'h2' + if (isH2) { + if (!h2ExperimentalWarned) { + h2ExperimentalWarned = true + process.emitWarning('H2 support is experimental, expect them to change at any time.', { + code: 'UNDICI-H2' + }) + } + + const session = http2.connect(client[kUrl], { + createConnection: () => socket, + peerMaxConcurrentStreams: client[kHTTP2SessionState].maxConcurrentStreams + }) + + client[kHTTPConnVersion] = 'h2' + session[kClient] = client + session[kSocket] = socket + session.on('error', onHttp2SessionError) + session.on('frameError', onHttp2FrameError) + session.on('end', onHttp2SessionEnd) + session.on('goaway', onHTTP2GoAway) + session.on('close', onSocketClose) + session.unref() + + client[kHTTP2Session] = session + socket[kHTTP2Session] = session + } else { + if (!llhttpInstance) { + llhttpInstance = await llhttpPromise + llhttpPromise = null + } + + socket[kNoRef] = false + socket[kWriting] = false + socket[kReset] = false + socket[kBlocking] = false + socket[kParser] = new Parser(client, socket, llhttpInstance) + } + + socket[kCounter] = 0 + socket[kMaxRequests] = client[kMaxRequests] + socket[kClient] = client + socket[kError] = null + + socket + .on('error', onSocketError) + .on('readable', onSocketReadable) + .on('end', onSocketEnd) + .on('close', onSocketClose) + + client[kSocket] = socket + + if (channels.connected.hasSubscribers) { + channels.connected.publish({ + connectParams: { + host, + hostname, + protocol, + port, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, + connector: client[kConnector], + socket + }) + } + client.emit('connect', client[kUrl], [client]) + } catch (err) { + if (client.destroyed) { + return + } + + client[kConnecting] = false + + if (channels.connectError.hasSubscribers) { + channels.connectError.publish({ + connectParams: { + host, + hostname, + protocol, + port, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, + connector: client[kConnector], + error: err + }) + } + + if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') { + assert(client[kRunning] === 0) + while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) { + const request = client[kQueue][client[kPendingIdx]++] + errorRequest(client, request, err) + } + } else { + onError(client, err) + } + + client.emit('connectionError', client[kUrl], [client], err) + } + + resume(client) +} + +function emitDrain (client) { + client[kNeedDrain] = 0 + client.emit('drain', client[kUrl], [client]) +} + +function resume (client, sync) { + if (client[kResuming] === 2) { + return + } + + client[kResuming] = 2 + + _resume(client, sync) + client[kResuming] = 0 + + if (client[kRunningIdx] > 256) { + client[kQueue].splice(0, client[kRunningIdx]) + client[kPendingIdx] -= client[kRunningIdx] + client[kRunningIdx] = 0 + } +} + +function _resume (client, sync) { + while (true) { + if (client.destroyed) { + assert(client[kPending] === 0) + return + } + + if (client[kClosedResolve] && !client[kSize]) { + client[kClosedResolve]() + client[kClosedResolve] = null + return + } + + const socket = client[kSocket] + + if (socket && !socket.destroyed && socket.alpnProtocol !== 'h2') { + if (client[kSize] === 0) { + if (!socket[kNoRef] && socket.unref) { + socket.unref() + socket[kNoRef] = true + } + } else if (socket[kNoRef] && socket.ref) { + socket.ref() + socket[kNoRef] = false + } + + if (client[kSize] === 0) { + if (socket[kParser].timeoutType !== TIMEOUT_IDLE) { + socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_IDLE) + } + } else if (client[kRunning] > 0 && socket[kParser].statusCode < 200) { + if (socket[kParser].timeoutType !== TIMEOUT_HEADERS) { + const request = client[kQueue][client[kRunningIdx]] + const headersTimeout = request.headersTimeout != null + ? request.headersTimeout + : client[kHeadersTimeout] + socket[kParser].setTimeout(headersTimeout, TIMEOUT_HEADERS) + } + } + } + + if (client[kBusy]) { + client[kNeedDrain] = 2 + } else if (client[kNeedDrain] === 2) { + if (sync) { + client[kNeedDrain] = 1 + process.nextTick(emitDrain, client) + } else { + emitDrain(client) + } + continue + } + + if (client[kPending] === 0) { + return + } + + if (client[kRunning] >= (client[kPipelining] || 1)) { + return + } + + const request = client[kQueue][client[kPendingIdx]] + + if (client[kUrl].protocol === 'https:' && client[kServerName] !== request.servername) { + if (client[kRunning] > 0) { + return + } + + client[kServerName] = request.servername + + if (socket && socket.servername !== request.servername) { + util.destroy(socket, new InformationalError('servername changed')) + return + } + } + + if (client[kConnecting]) { + return + } + + if (!socket && !client[kHTTP2Session]) { + connect(client) + return + } + + if (socket.destroyed || socket[kWriting] || socket[kReset] || socket[kBlocking]) { + return + } + + if (client[kRunning] > 0 && !request.idempotent) { + // Non-idempotent request cannot be retried. + // Ensure that no other requests are inflight and + // could cause failure. + return + } + + if (client[kRunning] > 0 && (request.upgrade || request.method === 'CONNECT')) { + // Don't dispatch an upgrade until all preceding requests have completed. + // A misbehaving server might upgrade the connection before all pipelined + // request has completed. + return + } + + if (client[kRunning] > 0 && util.bodyLength(request.body) !== 0 && + (util.isStream(request.body) || util.isAsyncIterable(request.body))) { + // Request with stream or iterator body can error while other requests + // are inflight and indirectly error those as well. + // Ensure this doesn't happen by waiting for inflight + // to complete before dispatching. + + // Request with stream or iterator body cannot be retried. + // Ensure that no other requests are inflight and + // could cause failure. + return + } + + if (!request.aborted && write(client, request)) { + client[kPendingIdx]++ + } else { + client[kQueue].splice(client[kPendingIdx], 1) + } + } +} + +// https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2 +function shouldSendContentLength (method) { + return method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' && method !== 'TRACE' && method !== 'CONNECT' +} + +function write (client, request) { + if (client[kHTTPConnVersion] === 'h2') { + writeH2(client, client[kHTTP2Session], request) + return + } + + const { body, method, path, host, upgrade, headers, blocking, reset } = request + + // https://tools.ietf.org/html/rfc7231#section-4.3.1 + // https://tools.ietf.org/html/rfc7231#section-4.3.2 + // https://tools.ietf.org/html/rfc7231#section-4.3.5 + + // Sending a payload body on a request that does not + // expect it can cause undefined behavior on some + // servers and corrupt connection state. Do not + // re-use the connection for further requests. + + const expectsPayload = ( + method === 'PUT' || + method === 'POST' || + method === 'PATCH' + ) + + if (body && typeof body.read === 'function') { + // Try to read EOF in order to get length. + body.read(0) + } + + const bodyLength = util.bodyLength(body) + + let contentLength = bodyLength + + if (contentLength === null) { + contentLength = request.contentLength + } + + if (contentLength === 0 && !expectsPayload) { + // https://tools.ietf.org/html/rfc7230#section-3.3.2 + // A user agent SHOULD NOT send a Content-Length header field when + // the request message does not contain a payload body and the method + // semantics do not anticipate such a body. + + contentLength = null + } + + // https://github.com/nodejs/undici/issues/2046 + // A user agent may send a Content-Length header with 0 value, this should be allowed. + if (shouldSendContentLength(method) && contentLength > 0 && request.contentLength !== null && request.contentLength !== contentLength) { + if (client[kStrictContentLength]) { + errorRequest(client, request, new RequestContentLengthMismatchError()) + return false + } + + process.emitWarning(new RequestContentLengthMismatchError()) + } + + const socket = client[kSocket] + + try { + request.onConnect((err) => { + if (request.aborted || request.completed) { + return + } + + errorRequest(client, request, err || new RequestAbortedError()) + + util.destroy(socket, new InformationalError('aborted')) + }) + } catch (err) { + errorRequest(client, request, err) + } + + if (request.aborted) { + return false + } + + if (method === 'HEAD') { + // https://github.com/mcollina/undici/issues/258 + // Close after a HEAD request to interop with misbehaving servers + // that may send a body in the response. + + socket[kReset] = true + } + + if (upgrade || method === 'CONNECT') { + // On CONNECT or upgrade, block pipeline from dispatching further + // requests on this connection. + + socket[kReset] = true + } + + if (reset != null) { + socket[kReset] = reset + } + + if (client[kMaxRequests] && socket[kCounter]++ >= client[kMaxRequests]) { + socket[kReset] = true + } + + if (blocking) { + socket[kBlocking] = true + } + + let header = `${method} ${path} HTTP/1.1\r\n` + + if (typeof host === 'string') { + header += `host: ${host}\r\n` + } else { + header += client[kHostHeader] + } + + if (upgrade) { + header += `connection: upgrade\r\nupgrade: ${upgrade}\r\n` + } else if (client[kPipelining] && !socket[kReset]) { + header += 'connection: keep-alive\r\n' + } else { + header += 'connection: close\r\n' + } + + if (headers) { + header += headers + } + + if (channels.sendHeaders.hasSubscribers) { + channels.sendHeaders.publish({ request, headers: header, socket }) + } + + /* istanbul ignore else: assertion */ + if (!body || bodyLength === 0) { + if (contentLength === 0) { + socket.write(`${header}content-length: 0\r\n\r\n`, 'latin1') + } else { + assert(contentLength === null, 'no body must not have content length') + socket.write(`${header}\r\n`, 'latin1') + } + request.onRequestSent() + } else if (util.isBuffer(body)) { + assert(contentLength === body.byteLength, 'buffer body must have content length') + + socket.cork() + socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1') + socket.write(body) + socket.uncork() + request.onBodySent(body) + request.onRequestSent() + if (!expectsPayload) { + socket[kReset] = true + } + } else if (util.isBlobLike(body)) { + if (typeof body.stream === 'function') { + writeIterable({ body: body.stream(), client, request, socket, contentLength, header, expectsPayload }) + } else { + writeBlob({ body, client, request, socket, contentLength, header, expectsPayload }) + } + } else if (util.isStream(body)) { + writeStream({ body, client, request, socket, contentLength, header, expectsPayload }) + } else if (util.isIterable(body)) { + writeIterable({ body, client, request, socket, contentLength, header, expectsPayload }) + } else { + assert(false) + } + + return true +} + +function writeH2 (client, session, request) { + const { body, method, path, host, upgrade, expectContinue, signal, headers: reqHeaders } = request + + let headers + if (typeof reqHeaders === 'string') headers = Request[kHTTP2CopyHeaders](reqHeaders.trim()) + else headers = reqHeaders + + if (upgrade) { + errorRequest(client, request, new Error('Upgrade not supported for H2')) + return false + } + + try { + // TODO(HTTP/2): Should we call onConnect immediately or on stream ready event? + request.onConnect((err) => { + if (request.aborted || request.completed) { + return + } + + errorRequest(client, request, err || new RequestAbortedError()) + }) + } catch (err) { + errorRequest(client, request, err) + } + + if (request.aborted) { + return false + } + + /** @type {import('node:http2').ClientHttp2Stream} */ + let stream + const h2State = client[kHTTP2SessionState] + + headers[HTTP2_HEADER_AUTHORITY] = host || client[kHost] + headers[HTTP2_HEADER_METHOD] = method + + if (method === 'CONNECT') { + session.ref() + // we are already connected, streams are pending, first request + // will create a new stream. We trigger a request to create the stream and wait until + // `ready` event is triggered + // We disabled endStream to allow the user to write to the stream + stream = session.request(headers, { endStream: false, signal }) + + if (stream.id && !stream.pending) { + request.onUpgrade(null, null, stream) + ++h2State.openStreams + } else { + stream.once('ready', () => { + request.onUpgrade(null, null, stream) + ++h2State.openStreams + }) + } + + stream.once('close', () => { + h2State.openStreams -= 1 + // TODO(HTTP/2): unref only if current streams count is 0 + if (h2State.openStreams === 0) session.unref() + }) + + return true + } + + // https://tools.ietf.org/html/rfc7540#section-8.3 + // :path and :scheme headers must be omited when sending CONNECT + + headers[HTTP2_HEADER_PATH] = path + headers[HTTP2_HEADER_SCHEME] = 'https' + + // https://tools.ietf.org/html/rfc7231#section-4.3.1 + // https://tools.ietf.org/html/rfc7231#section-4.3.2 + // https://tools.ietf.org/html/rfc7231#section-4.3.5 + + // Sending a payload body on a request that does not + // expect it can cause undefined behavior on some + // servers and corrupt connection state. Do not + // re-use the connection for further requests. + + const expectsPayload = ( + method === 'PUT' || + method === 'POST' || + method === 'PATCH' + ) + + if (body && typeof body.read === 'function') { + // Try to read EOF in order to get length. + body.read(0) + } + + let contentLength = util.bodyLength(body) + + if (contentLength == null) { + contentLength = request.contentLength + } + + if (contentLength === 0 || !expectsPayload) { + // https://tools.ietf.org/html/rfc7230#section-3.3.2 + // A user agent SHOULD NOT send a Content-Length header field when + // the request message does not contain a payload body and the method + // semantics do not anticipate such a body. + + contentLength = null + } + + // https://github.com/nodejs/undici/issues/2046 + // A user agent may send a Content-Length header with 0 value, this should be allowed. + if (shouldSendContentLength(method) && contentLength > 0 && request.contentLength != null && request.contentLength !== contentLength) { + if (client[kStrictContentLength]) { + errorRequest(client, request, new RequestContentLengthMismatchError()) + return false + } + + process.emitWarning(new RequestContentLengthMismatchError()) + } + + if (contentLength != null) { + assert(body, 'no body must not have content length') + headers[HTTP2_HEADER_CONTENT_LENGTH] = `${contentLength}` + } + + session.ref() + + const shouldEndStream = method === 'GET' || method === 'HEAD' + if (expectContinue) { + headers[HTTP2_HEADER_EXPECT] = '100-continue' + stream = session.request(headers, { endStream: shouldEndStream, signal }) + + stream.once('continue', writeBodyH2) + } else { + stream = session.request(headers, { + endStream: shouldEndStream, + signal + }) + writeBodyH2() + } + + // Increment counter as we have new several streams open + ++h2State.openStreams + + stream.once('response', headers => { + const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers + + if (request.onHeaders(Number(statusCode), realHeaders, stream.resume.bind(stream), '') === false) { + stream.pause() + } + }) + + stream.once('end', () => { + request.onComplete([]) + }) + + stream.on('data', (chunk) => { + if (request.onData(chunk) === false) { + stream.pause() + } + }) + + stream.once('close', () => { + h2State.openStreams -= 1 + // TODO(HTTP/2): unref only if current streams count is 0 + if (h2State.openStreams === 0) { + session.unref() + } + }) + + stream.once('error', function (err) { + if (client[kHTTP2Session] && !client[kHTTP2Session].destroyed && !this.closed && !this.destroyed) { + h2State.streams -= 1 + util.destroy(stream, err) + } + }) + + stream.once('frameError', (type, code) => { + const err = new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`) + errorRequest(client, request, err) + + if (client[kHTTP2Session] && !client[kHTTP2Session].destroyed && !this.closed && !this.destroyed) { + h2State.streams -= 1 + util.destroy(stream, err) + } + }) + + // stream.on('aborted', () => { + // // TODO(HTTP/2): Support aborted + // }) + + // stream.on('timeout', () => { + // // TODO(HTTP/2): Support timeout + // }) + + // stream.on('push', headers => { + // // TODO(HTTP/2): Suppor push + // }) + + // stream.on('trailers', headers => { + // // TODO(HTTP/2): Support trailers + // }) + + return true + + function writeBodyH2 () { + /* istanbul ignore else: assertion */ + if (!body) { + request.onRequestSent() + } else if (util.isBuffer(body)) { + assert(contentLength === body.byteLength, 'buffer body must have content length') + stream.cork() + stream.write(body) + stream.uncork() + stream.end() + request.onBodySent(body) + request.onRequestSent() + } else if (util.isBlobLike(body)) { + if (typeof body.stream === 'function') { + writeIterable({ + client, + request, + contentLength, + h2stream: stream, + expectsPayload, + body: body.stream(), + socket: client[kSocket], + header: '' + }) + } else { + writeBlob({ + body, + client, + request, + contentLength, + expectsPayload, + h2stream: stream, + header: '', + socket: client[kSocket] + }) + } + } else if (util.isStream(body)) { + writeStream({ + body, + client, + request, + contentLength, + expectsPayload, + socket: client[kSocket], + h2stream: stream, + header: '' + }) + } else if (util.isIterable(body)) { + writeIterable({ + body, + client, + request, + contentLength, + expectsPayload, + header: '', + h2stream: stream, + socket: client[kSocket] + }) + } else { + assert(false) + } + } +} + +function writeStream ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) { + assert(contentLength !== 0 || client[kRunning] === 0, 'stream body cannot be pipelined') + + if (client[kHTTPConnVersion] === 'h2') { + // For HTTP/2, is enough to pipe the stream + const pipe = pipeline( + body, + h2stream, + (err) => { + if (err) { + util.destroy(body, err) + util.destroy(h2stream, err) + } else { + request.onRequestSent() + } + } + ) + + pipe.on('data', onPipeData) + pipe.once('end', () => { + pipe.removeListener('data', onPipeData) + util.destroy(pipe) + }) + + function onPipeData (chunk) { + request.onBodySent(chunk) + } + + return + } + + let finished = false + + const writer = new AsyncWriter({ socket, request, contentLength, client, expectsPayload, header }) + + const onData = function (chunk) { + if (finished) { + return + } + + try { + if (!writer.write(chunk) && this.pause) { + this.pause() + } + } catch (err) { + util.destroy(this, err) + } + } + const onDrain = function () { + if (finished) { + return + } + + if (body.resume) { + body.resume() + } + } + const onAbort = function () { + if (finished) { + return + } + const err = new RequestAbortedError() + queueMicrotask(() => onFinished(err)) + } + const onFinished = function (err) { + if (finished) { + return + } + + finished = true + + assert(socket.destroyed || (socket[kWriting] && client[kRunning] <= 1)) + + socket + .off('drain', onDrain) + .off('error', onFinished) + + body + .removeListener('data', onData) + .removeListener('end', onFinished) + .removeListener('error', onFinished) + .removeListener('close', onAbort) + + if (!err) { + try { + writer.end() + } catch (er) { + err = er + } + } + + writer.destroy(err) + + if (err && (err.code !== 'UND_ERR_INFO' || err.message !== 'reset')) { + util.destroy(body, err) + } else { + util.destroy(body) + } + } + + body + .on('data', onData) + .on('end', onFinished) + .on('error', onFinished) + .on('close', onAbort) + + if (body.resume) { + body.resume() + } + + socket + .on('drain', onDrain) + .on('error', onFinished) +} + +async function writeBlob ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) { + assert(contentLength === body.size, 'blob body must have content length') + + const isH2 = client[kHTTPConnVersion] === 'h2' + try { + if (contentLength != null && contentLength !== body.size) { + throw new RequestContentLengthMismatchError() + } + + const buffer = Buffer.from(await body.arrayBuffer()) + + if (isH2) { + h2stream.cork() + h2stream.write(buffer) + h2stream.uncork() + } else { + socket.cork() + socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1') + socket.write(buffer) + socket.uncork() + } + + request.onBodySent(buffer) + request.onRequestSent() + + if (!expectsPayload) { + socket[kReset] = true + } + + resume(client) + } catch (err) { + util.destroy(isH2 ? h2stream : socket, err) + } +} + +async function writeIterable ({ h2stream, body, client, request, socket, contentLength, header, expectsPayload }) { + assert(contentLength !== 0 || client[kRunning] === 0, 'iterator body cannot be pipelined') + + let callback = null + function onDrain () { + if (callback) { + const cb = callback + callback = null + cb() + } + } + + const waitForDrain = () => new Promise((resolve, reject) => { + assert(callback === null) + + if (socket[kError]) { + reject(socket[kError]) + } else { + callback = resolve + } + }) + + if (client[kHTTPConnVersion] === 'h2') { + h2stream + .on('close', onDrain) + .on('drain', onDrain) + + try { + // It's up to the user to somehow abort the async iterable. + for await (const chunk of body) { + if (socket[kError]) { + throw socket[kError] + } + + const res = h2stream.write(chunk) + request.onBodySent(chunk) + if (!res) { + await waitForDrain() + } + } + } catch (err) { + h2stream.destroy(err) + } finally { + request.onRequestSent() + h2stream.end() + h2stream + .off('close', onDrain) + .off('drain', onDrain) + } + + return + } + + socket + .on('close', onDrain) + .on('drain', onDrain) + + const writer = new AsyncWriter({ socket, request, contentLength, client, expectsPayload, header }) + try { + // It's up to the user to somehow abort the async iterable. + for await (const chunk of body) { + if (socket[kError]) { + throw socket[kError] + } + + if (!writer.write(chunk)) { + await waitForDrain() + } + } + + writer.end() + } catch (err) { + writer.destroy(err) + } finally { + socket + .off('close', onDrain) + .off('drain', onDrain) + } +} + +class AsyncWriter { + constructor ({ socket, request, contentLength, client, expectsPayload, header }) { + this.socket = socket + this.request = request + this.contentLength = contentLength + this.client = client + this.bytesWritten = 0 + this.expectsPayload = expectsPayload + this.header = header + + socket[kWriting] = true + } + + write (chunk) { + const { socket, request, contentLength, client, bytesWritten, expectsPayload, header } = this + + if (socket[kError]) { + throw socket[kError] + } + + if (socket.destroyed) { + return false + } + + const len = Buffer.byteLength(chunk) + if (!len) { + return true + } + + // We should defer writing chunks. + if (contentLength !== null && bytesWritten + len > contentLength) { + if (client[kStrictContentLength]) { + throw new RequestContentLengthMismatchError() + } + + process.emitWarning(new RequestContentLengthMismatchError()) + } + + socket.cork() + + if (bytesWritten === 0) { + if (!expectsPayload) { + socket[kReset] = true + } + + if (contentLength === null) { + socket.write(`${header}transfer-encoding: chunked\r\n`, 'latin1') + } else { + socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1') + } + } + + if (contentLength === null) { + socket.write(`\r\n${len.toString(16)}\r\n`, 'latin1') + } + + this.bytesWritten += len + + const ret = socket.write(chunk) + + socket.uncork() + + request.onBodySent(chunk) + + if (!ret) { + if (socket[kParser].timeout && socket[kParser].timeoutType === TIMEOUT_HEADERS) { + // istanbul ignore else: only for jest + if (socket[kParser].timeout.refresh) { + socket[kParser].timeout.refresh() + } + } + } + + return ret + } + + end () { + const { socket, contentLength, client, bytesWritten, expectsPayload, header, request } = this + request.onRequestSent() + + socket[kWriting] = false + + if (socket[kError]) { + throw socket[kError] + } + + if (socket.destroyed) { + return + } + + if (bytesWritten === 0) { + if (expectsPayload) { + // https://tools.ietf.org/html/rfc7230#section-3.3.2 + // A user agent SHOULD send a Content-Length in a request message when + // no Transfer-Encoding is sent and the request method defines a meaning + // for an enclosed payload body. + + socket.write(`${header}content-length: 0\r\n\r\n`, 'latin1') + } else { + socket.write(`${header}\r\n`, 'latin1') + } + } else if (contentLength === null) { + socket.write('\r\n0\r\n\r\n', 'latin1') + } + + if (contentLength !== null && bytesWritten !== contentLength) { + if (client[kStrictContentLength]) { + throw new RequestContentLengthMismatchError() + } else { + process.emitWarning(new RequestContentLengthMismatchError()) + } + } + + if (socket[kParser].timeout && socket[kParser].timeoutType === TIMEOUT_HEADERS) { + // istanbul ignore else: only for jest + if (socket[kParser].timeout.refresh) { + socket[kParser].timeout.refresh() + } + } + + resume(client) + } + + destroy (err) { + const { socket, client } = this + + socket[kWriting] = false + + if (err) { + assert(client[kRunning] <= 1, 'pipeline should only contain this request') + util.destroy(socket, err) + } + } +} + +function errorRequest (client, request, err) { + try { + request.onError(err) + assert(request.aborted) + } catch (err) { + client.emit('error', err) + } +} + +module.exports = Client + + +/***/ }), + +/***/ 3194: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +/* istanbul ignore file: only for Node 12 */ + +const { kConnected, kSize } = __nccwpck_require__(6443) + +class CompatWeakRef { + constructor (value) { + this.value = value + } + + deref () { + return this.value[kConnected] === 0 && this.value[kSize] === 0 + ? undefined + : this.value + } +} + +class CompatFinalizer { + constructor (finalizer) { + this.finalizer = finalizer + } + + register (dispatcher, key) { + if (dispatcher.on) { + dispatcher.on('disconnect', () => { + if (dispatcher[kConnected] === 0 && dispatcher[kSize] === 0) { + this.finalizer(key) + } + }) + } + } +} + +module.exports = function () { + // FIXME: remove workaround when the Node bug is fixed + // https://github.com/nodejs/node/issues/49344#issuecomment-1741776308 + if (process.env.NODE_V8_COVERAGE) { + return { + WeakRef: CompatWeakRef, + FinalizationRegistry: CompatFinalizer + } + } + return { + WeakRef: global.WeakRef || CompatWeakRef, + FinalizationRegistry: global.FinalizationRegistry || CompatFinalizer + } +} + + +/***/ }), + +/***/ 9237: +/***/ ((module) => { + +"use strict"; + + +// https://wicg.github.io/cookie-store/#cookie-maximum-attribute-value-size +const maxAttributeValueSize = 1024 + +// https://wicg.github.io/cookie-store/#cookie-maximum-name-value-pair-size +const maxNameValuePairSize = 4096 + +module.exports = { + maxAttributeValueSize, + maxNameValuePairSize +} + + +/***/ }), + +/***/ 3168: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { parseSetCookie } = __nccwpck_require__(8915) +const { stringify, getHeadersList } = __nccwpck_require__(3834) +const { webidl } = __nccwpck_require__(4222) +const { Headers } = __nccwpck_require__(6349) + +/** + * @typedef {Object} Cookie + * @property {string} name + * @property {string} value + * @property {Date|number|undefined} expires + * @property {number|undefined} maxAge + * @property {string|undefined} domain + * @property {string|undefined} path + * @property {boolean|undefined} secure + * @property {boolean|undefined} httpOnly + * @property {'Strict'|'Lax'|'None'} sameSite + * @property {string[]} unparsed + */ + +/** + * @param {Headers} headers + * @returns {Record} + */ +function getCookies (headers) { + webidl.argumentLengthCheck(arguments, 1, { header: 'getCookies' }) + + webidl.brandCheck(headers, Headers, { strict: false }) + + const cookie = headers.get('cookie') + const out = {} + + if (!cookie) { + return out + } + + for (const piece of cookie.split(';')) { + const [name, ...value] = piece.split('=') + + out[name.trim()] = value.join('=') + } + + return out +} + +/** + * @param {Headers} headers + * @param {string} name + * @param {{ path?: string, domain?: string }|undefined} attributes + * @returns {void} + */ +function deleteCookie (headers, name, attributes) { + webidl.argumentLengthCheck(arguments, 2, { header: 'deleteCookie' }) + + webidl.brandCheck(headers, Headers, { strict: false }) + + name = webidl.converters.DOMString(name) + attributes = webidl.converters.DeleteCookieAttributes(attributes) + + // Matches behavior of + // https://github.com/denoland/deno_std/blob/63827b16330b82489a04614027c33b7904e08be5/http/cookie.ts#L278 + setCookie(headers, { + name, + value: '', + expires: new Date(0), + ...attributes + }) +} + +/** + * @param {Headers} headers + * @returns {Cookie[]} + */ +function getSetCookies (headers) { + webidl.argumentLengthCheck(arguments, 1, { header: 'getSetCookies' }) + + webidl.brandCheck(headers, Headers, { strict: false }) + + const cookies = getHeadersList(headers).cookies + + if (!cookies) { + return [] + } + + // In older versions of undici, cookies is a list of name:value. + return cookies.map((pair) => parseSetCookie(Array.isArray(pair) ? pair[1] : pair)) +} + +/** + * @param {Headers} headers + * @param {Cookie} cookie + * @returns {void} + */ +function setCookie (headers, cookie) { + webidl.argumentLengthCheck(arguments, 2, { header: 'setCookie' }) + + webidl.brandCheck(headers, Headers, { strict: false }) + + cookie = webidl.converters.Cookie(cookie) + + const str = stringify(cookie) + + if (str) { + headers.append('Set-Cookie', stringify(cookie)) + } +} + +webidl.converters.DeleteCookieAttributes = webidl.dictionaryConverter([ + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: 'path', + defaultValue: null + }, + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: 'domain', + defaultValue: null + } +]) + +webidl.converters.Cookie = webidl.dictionaryConverter([ + { + converter: webidl.converters.DOMString, + key: 'name' + }, + { + converter: webidl.converters.DOMString, + key: 'value' + }, + { + converter: webidl.nullableConverter((value) => { + if (typeof value === 'number') { + return webidl.converters['unsigned long long'](value) + } + + return new Date(value) + }), + key: 'expires', + defaultValue: null + }, + { + converter: webidl.nullableConverter(webidl.converters['long long']), + key: 'maxAge', + defaultValue: null + }, + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: 'domain', + defaultValue: null + }, + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: 'path', + defaultValue: null + }, + { + converter: webidl.nullableConverter(webidl.converters.boolean), + key: 'secure', + defaultValue: null + }, + { + converter: webidl.nullableConverter(webidl.converters.boolean), + key: 'httpOnly', + defaultValue: null + }, + { + converter: webidl.converters.USVString, + key: 'sameSite', + allowedValues: ['Strict', 'Lax', 'None'] + }, + { + converter: webidl.sequenceConverter(webidl.converters.DOMString), + key: 'unparsed', + defaultValue: [] + } +]) + +module.exports = { + getCookies, + deleteCookie, + getSetCookies, + setCookie +} + + +/***/ }), + +/***/ 8915: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { maxNameValuePairSize, maxAttributeValueSize } = __nccwpck_require__(9237) +const { isCTLExcludingHtab } = __nccwpck_require__(3834) +const { collectASequenceOfCodePointsFast } = __nccwpck_require__(4322) +const assert = __nccwpck_require__(2613) + +/** + * @description Parses the field-value attributes of a set-cookie header string. + * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4 + * @param {string} header + * @returns if the header is invalid, null will be returned + */ +function parseSetCookie (header) { + // 1. If the set-cookie-string contains a %x00-08 / %x0A-1F / %x7F + // character (CTL characters excluding HTAB): Abort these steps and + // ignore the set-cookie-string entirely. + if (isCTLExcludingHtab(header)) { + return null + } + + let nameValuePair = '' + let unparsedAttributes = '' + let name = '' + let value = '' + + // 2. If the set-cookie-string contains a %x3B (";") character: + if (header.includes(';')) { + // 1. The name-value-pair string consists of the characters up to, + // but not including, the first %x3B (";"), and the unparsed- + // attributes consist of the remainder of the set-cookie-string + // (including the %x3B (";") in question). + const position = { position: 0 } + + nameValuePair = collectASequenceOfCodePointsFast(';', header, position) + unparsedAttributes = header.slice(position.position) + } else { + // Otherwise: + + // 1. The name-value-pair string consists of all the characters + // contained in the set-cookie-string, and the unparsed- + // attributes is the empty string. + nameValuePair = header + } + + // 3. If the name-value-pair string lacks a %x3D ("=") character, then + // the name string is empty, and the value string is the value of + // name-value-pair. + if (!nameValuePair.includes('=')) { + value = nameValuePair + } else { + // Otherwise, the name string consists of the characters up to, but + // not including, the first %x3D ("=") character, and the (possibly + // empty) value string consists of the characters after the first + // %x3D ("=") character. + const position = { position: 0 } + name = collectASequenceOfCodePointsFast( + '=', + nameValuePair, + position + ) + value = nameValuePair.slice(position.position + 1) + } + + // 4. Remove any leading or trailing WSP characters from the name + // string and the value string. + name = name.trim() + value = value.trim() + + // 5. If the sum of the lengths of the name string and the value string + // is more than 4096 octets, abort these steps and ignore the set- + // cookie-string entirely. + if (name.length + value.length > maxNameValuePairSize) { + return null + } + + // 6. The cookie-name is the name string, and the cookie-value is the + // value string. + return { + name, value, ...parseUnparsedAttributes(unparsedAttributes) + } +} + +/** + * Parses the remaining attributes of a set-cookie header + * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4 + * @param {string} unparsedAttributes + * @param {[Object.]={}} cookieAttributeList + */ +function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {}) { + // 1. If the unparsed-attributes string is empty, skip the rest of + // these steps. + if (unparsedAttributes.length === 0) { + return cookieAttributeList + } + + // 2. Discard the first character of the unparsed-attributes (which + // will be a %x3B (";") character). + assert(unparsedAttributes[0] === ';') + unparsedAttributes = unparsedAttributes.slice(1) + + let cookieAv = '' + + // 3. If the remaining unparsed-attributes contains a %x3B (";") + // character: + if (unparsedAttributes.includes(';')) { + // 1. Consume the characters of the unparsed-attributes up to, but + // not including, the first %x3B (";") character. + cookieAv = collectASequenceOfCodePointsFast( + ';', + unparsedAttributes, + { position: 0 } + ) + unparsedAttributes = unparsedAttributes.slice(cookieAv.length) + } else { + // Otherwise: + + // 1. Consume the remainder of the unparsed-attributes. + cookieAv = unparsedAttributes + unparsedAttributes = '' + } + + // Let the cookie-av string be the characters consumed in this step. + + let attributeName = '' + let attributeValue = '' + + // 4. If the cookie-av string contains a %x3D ("=") character: + if (cookieAv.includes('=')) { + // 1. The (possibly empty) attribute-name string consists of the + // characters up to, but not including, the first %x3D ("=") + // character, and the (possibly empty) attribute-value string + // consists of the characters after the first %x3D ("=") + // character. + const position = { position: 0 } + + attributeName = collectASequenceOfCodePointsFast( + '=', + cookieAv, + position + ) + attributeValue = cookieAv.slice(position.position + 1) + } else { + // Otherwise: + + // 1. The attribute-name string consists of the entire cookie-av + // string, and the attribute-value string is empty. + attributeName = cookieAv + } + + // 5. Remove any leading or trailing WSP characters from the attribute- + // name string and the attribute-value string. + attributeName = attributeName.trim() + attributeValue = attributeValue.trim() + + // 6. If the attribute-value is longer than 1024 octets, ignore the + // cookie-av string and return to Step 1 of this algorithm. + if (attributeValue.length > maxAttributeValueSize) { + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList) + } + + // 7. Process the attribute-name and attribute-value according to the + // requirements in the following subsections. (Notice that + // attributes with unrecognized attribute-names are ignored.) + const attributeNameLowercase = attributeName.toLowerCase() + + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.1 + // If the attribute-name case-insensitively matches the string + // "Expires", the user agent MUST process the cookie-av as follows. + if (attributeNameLowercase === 'expires') { + // 1. Let the expiry-time be the result of parsing the attribute-value + // as cookie-date (see Section 5.1.1). + const expiryTime = new Date(attributeValue) + + // 2. If the attribute-value failed to parse as a cookie date, ignore + // the cookie-av. + + cookieAttributeList.expires = expiryTime + } else if (attributeNameLowercase === 'max-age') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.2 + // If the attribute-name case-insensitively matches the string "Max- + // Age", the user agent MUST process the cookie-av as follows. + + // 1. If the first character of the attribute-value is not a DIGIT or a + // "-" character, ignore the cookie-av. + const charCode = attributeValue.charCodeAt(0) + + if ((charCode < 48 || charCode > 57) && attributeValue[0] !== '-') { + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList) + } + + // 2. If the remainder of attribute-value contains a non-DIGIT + // character, ignore the cookie-av. + if (!/^\d+$/.test(attributeValue)) { + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList) + } + + // 3. Let delta-seconds be the attribute-value converted to an integer. + const deltaSeconds = Number(attributeValue) + + // 4. Let cookie-age-limit be the maximum age of the cookie (which + // SHOULD be 400 days or less, see Section 4.1.2.2). + + // 5. Set delta-seconds to the smaller of its present value and cookie- + // age-limit. + // deltaSeconds = Math.min(deltaSeconds * 1000, maxExpiresMs) + + // 6. If delta-seconds is less than or equal to zero (0), let expiry- + // time be the earliest representable date and time. Otherwise, let + // the expiry-time be the current date and time plus delta-seconds + // seconds. + // const expiryTime = deltaSeconds <= 0 ? Date.now() : Date.now() + deltaSeconds + + // 7. Append an attribute to the cookie-attribute-list with an + // attribute-name of Max-Age and an attribute-value of expiry-time. + cookieAttributeList.maxAge = deltaSeconds + } else if (attributeNameLowercase === 'domain') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.3 + // If the attribute-name case-insensitively matches the string "Domain", + // the user agent MUST process the cookie-av as follows. + + // 1. Let cookie-domain be the attribute-value. + let cookieDomain = attributeValue + + // 2. If cookie-domain starts with %x2E ("."), let cookie-domain be + // cookie-domain without its leading %x2E ("."). + if (cookieDomain[0] === '.') { + cookieDomain = cookieDomain.slice(1) + } + + // 3. Convert the cookie-domain to lower case. + cookieDomain = cookieDomain.toLowerCase() + + // 4. Append an attribute to the cookie-attribute-list with an + // attribute-name of Domain and an attribute-value of cookie-domain. + cookieAttributeList.domain = cookieDomain + } else if (attributeNameLowercase === 'path') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.4 + // If the attribute-name case-insensitively matches the string "Path", + // the user agent MUST process the cookie-av as follows. + + // 1. If the attribute-value is empty or if the first character of the + // attribute-value is not %x2F ("/"): + let cookiePath = '' + if (attributeValue.length === 0 || attributeValue[0] !== '/') { + // 1. Let cookie-path be the default-path. + cookiePath = '/' + } else { + // Otherwise: + + // 1. Let cookie-path be the attribute-value. + cookiePath = attributeValue + } + + // 2. Append an attribute to the cookie-attribute-list with an + // attribute-name of Path and an attribute-value of cookie-path. + cookieAttributeList.path = cookiePath + } else if (attributeNameLowercase === 'secure') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.5 + // If the attribute-name case-insensitively matches the string "Secure", + // the user agent MUST append an attribute to the cookie-attribute-list + // with an attribute-name of Secure and an empty attribute-value. + + cookieAttributeList.secure = true + } else if (attributeNameLowercase === 'httponly') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.6 + // If the attribute-name case-insensitively matches the string + // "HttpOnly", the user agent MUST append an attribute to the cookie- + // attribute-list with an attribute-name of HttpOnly and an empty + // attribute-value. + + cookieAttributeList.httpOnly = true + } else if (attributeNameLowercase === 'samesite') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.7 + // If the attribute-name case-insensitively matches the string + // "SameSite", the user agent MUST process the cookie-av as follows: + + // 1. Let enforcement be "Default". + let enforcement = 'Default' + + const attributeValueLowercase = attributeValue.toLowerCase() + // 2. If cookie-av's attribute-value is a case-insensitive match for + // "None", set enforcement to "None". + if (attributeValueLowercase.includes('none')) { + enforcement = 'None' + } + + // 3. If cookie-av's attribute-value is a case-insensitive match for + // "Strict", set enforcement to "Strict". + if (attributeValueLowercase.includes('strict')) { + enforcement = 'Strict' + } + + // 4. If cookie-av's attribute-value is a case-insensitive match for + // "Lax", set enforcement to "Lax". + if (attributeValueLowercase.includes('lax')) { + enforcement = 'Lax' + } + + // 5. Append an attribute to the cookie-attribute-list with an + // attribute-name of "SameSite" and an attribute-value of + // enforcement. + cookieAttributeList.sameSite = enforcement + } else { + cookieAttributeList.unparsed ??= [] + + cookieAttributeList.unparsed.push(`${attributeName}=${attributeValue}`) + } + + // 8. Return to Step 1 of this algorithm. + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList) +} + +module.exports = { + parseSetCookie, + parseUnparsedAttributes +} + + +/***/ }), + +/***/ 3834: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const assert = __nccwpck_require__(2613) +const { kHeadersList } = __nccwpck_require__(6443) + +function isCTLExcludingHtab (value) { + if (value.length === 0) { + return false + } + + for (const char of value) { + const code = char.charCodeAt(0) + + if ( + (code >= 0x00 || code <= 0x08) || + (code >= 0x0A || code <= 0x1F) || + code === 0x7F + ) { + return false + } + } +} + +/** + CHAR = + token = 1* + separators = "(" | ")" | "<" | ">" | "@" + | "," | ";" | ":" | "\" | <"> + | "/" | "[" | "]" | "?" | "=" + | "{" | "}" | SP | HT + * @param {string} name + */ +function validateCookieName (name) { + for (const char of name) { + const code = char.charCodeAt(0) + + if ( + (code <= 0x20 || code > 0x7F) || + char === '(' || + char === ')' || + char === '>' || + char === '<' || + char === '@' || + char === ',' || + char === ';' || + char === ':' || + char === '\\' || + char === '"' || + char === '/' || + char === '[' || + char === ']' || + char === '?' || + char === '=' || + char === '{' || + char === '}' + ) { + throw new Error('Invalid cookie name') + } + } +} + +/** + cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) + cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E + ; US-ASCII characters excluding CTLs, + ; whitespace DQUOTE, comma, semicolon, + ; and backslash + * @param {string} value + */ +function validateCookieValue (value) { + for (const char of value) { + const code = char.charCodeAt(0) + + if ( + code < 0x21 || // exclude CTLs (0-31) + code === 0x22 || + code === 0x2C || + code === 0x3B || + code === 0x5C || + code > 0x7E // non-ascii + ) { + throw new Error('Invalid header value') + } + } +} + +/** + * path-value = + * @param {string} path + */ +function validateCookiePath (path) { + for (const char of path) { + const code = char.charCodeAt(0) + + if (code < 0x21 || char === ';') { + throw new Error('Invalid cookie path') + } + } +} + +/** + * I have no idea why these values aren't allowed to be honest, + * but Deno tests these. - Khafra + * @param {string} domain + */ +function validateCookieDomain (domain) { + if ( + domain.startsWith('-') || + domain.endsWith('.') || + domain.endsWith('-') + ) { + throw new Error('Invalid cookie domain') + } +} + +/** + * @see https://www.rfc-editor.org/rfc/rfc7231#section-7.1.1.1 + * @param {number|Date} date + IMF-fixdate = day-name "," SP date1 SP time-of-day SP GMT + ; fixed length/zone/capitalization subset of the format + ; see Section 3.3 of [RFC5322] + + day-name = %x4D.6F.6E ; "Mon", case-sensitive + / %x54.75.65 ; "Tue", case-sensitive + / %x57.65.64 ; "Wed", case-sensitive + / %x54.68.75 ; "Thu", case-sensitive + / %x46.72.69 ; "Fri", case-sensitive + / %x53.61.74 ; "Sat", case-sensitive + / %x53.75.6E ; "Sun", case-sensitive + date1 = day SP month SP year + ; e.g., 02 Jun 1982 + + day = 2DIGIT + month = %x4A.61.6E ; "Jan", case-sensitive + / %x46.65.62 ; "Feb", case-sensitive + / %x4D.61.72 ; "Mar", case-sensitive + / %x41.70.72 ; "Apr", case-sensitive + / %x4D.61.79 ; "May", case-sensitive + / %x4A.75.6E ; "Jun", case-sensitive + / %x4A.75.6C ; "Jul", case-sensitive + / %x41.75.67 ; "Aug", case-sensitive + / %x53.65.70 ; "Sep", case-sensitive + / %x4F.63.74 ; "Oct", case-sensitive + / %x4E.6F.76 ; "Nov", case-sensitive + / %x44.65.63 ; "Dec", case-sensitive + year = 4DIGIT + + GMT = %x47.4D.54 ; "GMT", case-sensitive + + time-of-day = hour ":" minute ":" second + ; 00:00:00 - 23:59:60 (leap second) + + hour = 2DIGIT + minute = 2DIGIT + second = 2DIGIT + */ +function toIMFDate (date) { + if (typeof date === 'number') { + date = new Date(date) + } + + const days = [ + 'Sun', 'Mon', 'Tue', 'Wed', + 'Thu', 'Fri', 'Sat' + ] + + const months = [ + 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', + 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' + ] + + const dayName = days[date.getUTCDay()] + const day = date.getUTCDate().toString().padStart(2, '0') + const month = months[date.getUTCMonth()] + const year = date.getUTCFullYear() + const hour = date.getUTCHours().toString().padStart(2, '0') + const minute = date.getUTCMinutes().toString().padStart(2, '0') + const second = date.getUTCSeconds().toString().padStart(2, '0') + + return `${dayName}, ${day} ${month} ${year} ${hour}:${minute}:${second} GMT` +} + +/** + max-age-av = "Max-Age=" non-zero-digit *DIGIT + ; In practice, both expires-av and max-age-av + ; are limited to dates representable by the + ; user agent. + * @param {number} maxAge + */ +function validateCookieMaxAge (maxAge) { + if (maxAge < 0) { + throw new Error('Invalid cookie max-age') + } +} + +/** + * @see https://www.rfc-editor.org/rfc/rfc6265#section-4.1.1 + * @param {import('./index').Cookie} cookie + */ +function stringify (cookie) { + if (cookie.name.length === 0) { + return null + } + + validateCookieName(cookie.name) + validateCookieValue(cookie.value) + + const out = [`${cookie.name}=${cookie.value}`] + + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.1 + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-cookie-prefixes-00#section-3.2 + if (cookie.name.startsWith('__Secure-')) { + cookie.secure = true + } + + if (cookie.name.startsWith('__Host-')) { + cookie.secure = true + cookie.domain = null + cookie.path = '/' + } + + if (cookie.secure) { + out.push('Secure') + } + + if (cookie.httpOnly) { + out.push('HttpOnly') + } + + if (typeof cookie.maxAge === 'number') { + validateCookieMaxAge(cookie.maxAge) + out.push(`Max-Age=${cookie.maxAge}`) + } + + if (cookie.domain) { + validateCookieDomain(cookie.domain) + out.push(`Domain=${cookie.domain}`) + } + + if (cookie.path) { + validateCookiePath(cookie.path) + out.push(`Path=${cookie.path}`) + } + + if (cookie.expires && cookie.expires.toString() !== 'Invalid Date') { + out.push(`Expires=${toIMFDate(cookie.expires)}`) + } + + if (cookie.sameSite) { + out.push(`SameSite=${cookie.sameSite}`) + } + + for (const part of cookie.unparsed) { + if (!part.includes('=')) { + throw new Error('Invalid unparsed') + } + + const [key, ...value] = part.split('=') + + out.push(`${key.trim()}=${value.join('=')}`) + } + + return out.join('; ') +} + +let kHeadersListNode + +function getHeadersList (headers) { + if (headers[kHeadersList]) { + return headers[kHeadersList] + } + + if (!kHeadersListNode) { + kHeadersListNode = Object.getOwnPropertySymbols(headers).find( + (symbol) => symbol.description === 'headers list' + ) + + assert(kHeadersListNode, 'Headers cannot be parsed') + } + + const headersList = headers[kHeadersListNode] + assert(headersList) + + return headersList +} + +module.exports = { + isCTLExcludingHtab, + stringify, + getHeadersList +} + + +/***/ }), + +/***/ 9136: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const net = __nccwpck_require__(9278) +const assert = __nccwpck_require__(2613) +const util = __nccwpck_require__(3440) +const { InvalidArgumentError, ConnectTimeoutError } = __nccwpck_require__(8707) + +let tls // include tls conditionally since it is not always available + +// TODO: session re-use does not wait for the first +// connection to resolve the session and might therefore +// resolve the same servername multiple times even when +// re-use is enabled. + +let SessionCache +// FIXME: remove workaround when the Node bug is fixed +// https://github.com/nodejs/node/issues/49344#issuecomment-1741776308 +if (global.FinalizationRegistry && !process.env.NODE_V8_COVERAGE) { + SessionCache = class WeakSessionCache { + constructor (maxCachedSessions) { + this._maxCachedSessions = maxCachedSessions + this._sessionCache = new Map() + this._sessionRegistry = new global.FinalizationRegistry((key) => { + if (this._sessionCache.size < this._maxCachedSessions) { + return + } + + const ref = this._sessionCache.get(key) + if (ref !== undefined && ref.deref() === undefined) { + this._sessionCache.delete(key) + } + }) + } + + get (sessionKey) { + const ref = this._sessionCache.get(sessionKey) + return ref ? ref.deref() : null + } + + set (sessionKey, session) { + if (this._maxCachedSessions === 0) { + return + } + + this._sessionCache.set(sessionKey, new WeakRef(session)) + this._sessionRegistry.register(session, sessionKey) + } + } +} else { + SessionCache = class SimpleSessionCache { + constructor (maxCachedSessions) { + this._maxCachedSessions = maxCachedSessions + this._sessionCache = new Map() + } + + get (sessionKey) { + return this._sessionCache.get(sessionKey) + } + + set (sessionKey, session) { + if (this._maxCachedSessions === 0) { + return + } + + if (this._sessionCache.size >= this._maxCachedSessions) { + // remove the oldest session + const { value: oldestKey } = this._sessionCache.keys().next() + this._sessionCache.delete(oldestKey) + } + + this._sessionCache.set(sessionKey, session) + } + } +} + +function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, ...opts }) { + if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) { + throw new InvalidArgumentError('maxCachedSessions must be a positive integer or zero') + } + + const options = { path: socketPath, ...opts } + const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions) + timeout = timeout == null ? 10e3 : timeout + allowH2 = allowH2 != null ? allowH2 : false + return function connect ({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) { + let socket + if (protocol === 'https:') { + if (!tls) { + tls = __nccwpck_require__(4756) + } + servername = servername || options.servername || util.getServerName(host) || null + + const sessionKey = servername || hostname + const session = sessionCache.get(sessionKey) || null + + assert(sessionKey) + + socket = tls.connect({ + highWaterMark: 16384, // TLS in node can't have bigger HWM anyway... + ...options, + servername, + session, + localAddress, + // TODO(HTTP/2): Add support for h2c + ALPNProtocols: allowH2 ? ['http/1.1', 'h2'] : ['http/1.1'], + socket: httpSocket, // upgrade socket connection + port: port || 443, + host: hostname + }) + + socket + .on('session', function (session) { + // TODO (fix): Can a session become invalid once established? Don't think so? + sessionCache.set(sessionKey, session) + }) + } else { + assert(!httpSocket, 'httpSocket can only be sent on TLS update') + socket = net.connect({ + highWaterMark: 64 * 1024, // Same as nodejs fs streams. + ...options, + localAddress, + port: port || 80, + host: hostname + }) + } + + // Set TCP keep alive options on the socket here instead of in connect() for the case of assigning the socket + if (options.keepAlive == null || options.keepAlive) { + const keepAliveInitialDelay = options.keepAliveInitialDelay === undefined ? 60e3 : options.keepAliveInitialDelay + socket.setKeepAlive(true, keepAliveInitialDelay) + } + + const cancelTimeout = setupTimeout(() => onConnectTimeout(socket), timeout) + + socket + .setNoDelay(true) + .once(protocol === 'https:' ? 'secureConnect' : 'connect', function () { + cancelTimeout() + + if (callback) { + const cb = callback + callback = null + cb(null, this) + } + }) + .on('error', function (err) { + cancelTimeout() + + if (callback) { + const cb = callback + callback = null + cb(err) + } + }) + + return socket + } +} + +function setupTimeout (onConnectTimeout, timeout) { + if (!timeout) { + return () => {} + } + + let s1 = null + let s2 = null + const timeoutId = setTimeout(() => { + // setImmediate is added to make sure that we priotorise socket error events over timeouts + s1 = setImmediate(() => { + if (process.platform === 'win32') { + // Windows needs an extra setImmediate probably due to implementation differences in the socket logic + s2 = setImmediate(() => onConnectTimeout()) + } else { + onConnectTimeout() + } + }) + }, timeout) + return () => { + clearTimeout(timeoutId) + clearImmediate(s1) + clearImmediate(s2) + } +} + +function onConnectTimeout (socket) { + util.destroy(socket, new ConnectTimeoutError()) +} + +module.exports = buildConnector + + +/***/ }), + +/***/ 735: +/***/ ((module) => { + +"use strict"; + + +/** @type {Record} */ +const headerNameLowerCasedRecord = {} + +// https://developer.mozilla.org/docs/Web/HTTP/Headers +const wellknownHeaderNames = [ + 'Accept', + 'Accept-Encoding', + 'Accept-Language', + 'Accept-Ranges', + 'Access-Control-Allow-Credentials', + 'Access-Control-Allow-Headers', + 'Access-Control-Allow-Methods', + 'Access-Control-Allow-Origin', + 'Access-Control-Expose-Headers', + 'Access-Control-Max-Age', + 'Access-Control-Request-Headers', + 'Access-Control-Request-Method', + 'Age', + 'Allow', + 'Alt-Svc', + 'Alt-Used', + 'Authorization', + 'Cache-Control', + 'Clear-Site-Data', + 'Connection', + 'Content-Disposition', + 'Content-Encoding', + 'Content-Language', + 'Content-Length', + 'Content-Location', + 'Content-Range', + 'Content-Security-Policy', + 'Content-Security-Policy-Report-Only', + 'Content-Type', + 'Cookie', + 'Cross-Origin-Embedder-Policy', + 'Cross-Origin-Opener-Policy', + 'Cross-Origin-Resource-Policy', + 'Date', + 'Device-Memory', + 'Downlink', + 'ECT', + 'ETag', + 'Expect', + 'Expect-CT', + 'Expires', + 'Forwarded', + 'From', + 'Host', + 'If-Match', + 'If-Modified-Since', + 'If-None-Match', + 'If-Range', + 'If-Unmodified-Since', + 'Keep-Alive', + 'Last-Modified', + 'Link', + 'Location', + 'Max-Forwards', + 'Origin', + 'Permissions-Policy', + 'Pragma', + 'Proxy-Authenticate', + 'Proxy-Authorization', + 'RTT', + 'Range', + 'Referer', + 'Referrer-Policy', + 'Refresh', + 'Retry-After', + 'Sec-WebSocket-Accept', + 'Sec-WebSocket-Extensions', + 'Sec-WebSocket-Key', + 'Sec-WebSocket-Protocol', + 'Sec-WebSocket-Version', + 'Server', + 'Server-Timing', + 'Service-Worker-Allowed', + 'Service-Worker-Navigation-Preload', + 'Set-Cookie', + 'SourceMap', + 'Strict-Transport-Security', + 'Supports-Loading-Mode', + 'TE', + 'Timing-Allow-Origin', + 'Trailer', + 'Transfer-Encoding', + 'Upgrade', + 'Upgrade-Insecure-Requests', + 'User-Agent', + 'Vary', + 'Via', + 'WWW-Authenticate', + 'X-Content-Type-Options', + 'X-DNS-Prefetch-Control', + 'X-Frame-Options', + 'X-Permitted-Cross-Domain-Policies', + 'X-Powered-By', + 'X-Requested-With', + 'X-XSS-Protection' +] + +for (let i = 0; i < wellknownHeaderNames.length; ++i) { + const key = wellknownHeaderNames[i] + const lowerCasedKey = key.toLowerCase() + headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] = + lowerCasedKey +} + +// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`. +Object.setPrototypeOf(headerNameLowerCasedRecord, null) + +module.exports = { + wellknownHeaderNames, + headerNameLowerCasedRecord +} + + +/***/ }), + +/***/ 8707: +/***/ ((module) => { + +"use strict"; + + +class UndiciError extends Error { + constructor (message) { + super(message) + this.name = 'UndiciError' + this.code = 'UND_ERR' + } +} + +class ConnectTimeoutError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, ConnectTimeoutError) + this.name = 'ConnectTimeoutError' + this.message = message || 'Connect Timeout Error' + this.code = 'UND_ERR_CONNECT_TIMEOUT' + } +} + +class HeadersTimeoutError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, HeadersTimeoutError) + this.name = 'HeadersTimeoutError' + this.message = message || 'Headers Timeout Error' + this.code = 'UND_ERR_HEADERS_TIMEOUT' + } +} + +class HeadersOverflowError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, HeadersOverflowError) + this.name = 'HeadersOverflowError' + this.message = message || 'Headers Overflow Error' + this.code = 'UND_ERR_HEADERS_OVERFLOW' + } +} + +class BodyTimeoutError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, BodyTimeoutError) + this.name = 'BodyTimeoutError' + this.message = message || 'Body Timeout Error' + this.code = 'UND_ERR_BODY_TIMEOUT' + } +} + +class ResponseStatusCodeError extends UndiciError { + constructor (message, statusCode, headers, body) { + super(message) + Error.captureStackTrace(this, ResponseStatusCodeError) + this.name = 'ResponseStatusCodeError' + this.message = message || 'Response Status Code Error' + this.code = 'UND_ERR_RESPONSE_STATUS_CODE' + this.body = body + this.status = statusCode + this.statusCode = statusCode + this.headers = headers + } +} + +class InvalidArgumentError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, InvalidArgumentError) + this.name = 'InvalidArgumentError' + this.message = message || 'Invalid Argument Error' + this.code = 'UND_ERR_INVALID_ARG' + } +} + +class InvalidReturnValueError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, InvalidReturnValueError) + this.name = 'InvalidReturnValueError' + this.message = message || 'Invalid Return Value Error' + this.code = 'UND_ERR_INVALID_RETURN_VALUE' + } +} + +class RequestAbortedError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, RequestAbortedError) + this.name = 'AbortError' + this.message = message || 'Request aborted' + this.code = 'UND_ERR_ABORTED' + } +} + +class InformationalError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, InformationalError) + this.name = 'InformationalError' + this.message = message || 'Request information' + this.code = 'UND_ERR_INFO' + } +} + +class RequestContentLengthMismatchError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, RequestContentLengthMismatchError) + this.name = 'RequestContentLengthMismatchError' + this.message = message || 'Request body length does not match content-length header' + this.code = 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH' + } +} + +class ResponseContentLengthMismatchError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, ResponseContentLengthMismatchError) + this.name = 'ResponseContentLengthMismatchError' + this.message = message || 'Response body length does not match content-length header' + this.code = 'UND_ERR_RES_CONTENT_LENGTH_MISMATCH' + } +} + +class ClientDestroyedError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, ClientDestroyedError) + this.name = 'ClientDestroyedError' + this.message = message || 'The client is destroyed' + this.code = 'UND_ERR_DESTROYED' + } +} + +class ClientClosedError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, ClientClosedError) + this.name = 'ClientClosedError' + this.message = message || 'The client is closed' + this.code = 'UND_ERR_CLOSED' + } +} + +class SocketError extends UndiciError { + constructor (message, socket) { + super(message) + Error.captureStackTrace(this, SocketError) + this.name = 'SocketError' + this.message = message || 'Socket error' + this.code = 'UND_ERR_SOCKET' + this.socket = socket + } +} + +class NotSupportedError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, NotSupportedError) + this.name = 'NotSupportedError' + this.message = message || 'Not supported error' + this.code = 'UND_ERR_NOT_SUPPORTED' + } +} + +class BalancedPoolMissingUpstreamError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, NotSupportedError) + this.name = 'MissingUpstreamError' + this.message = message || 'No upstream has been added to the BalancedPool' + this.code = 'UND_ERR_BPL_MISSING_UPSTREAM' + } +} + +class HTTPParserError extends Error { + constructor (message, code, data) { + super(message) + Error.captureStackTrace(this, HTTPParserError) + this.name = 'HTTPParserError' + this.code = code ? `HPE_${code}` : undefined + this.data = data ? data.toString() : undefined + } +} + +class ResponseExceededMaxSizeError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, ResponseExceededMaxSizeError) + this.name = 'ResponseExceededMaxSizeError' + this.message = message || 'Response content exceeded max size' + this.code = 'UND_ERR_RES_EXCEEDED_MAX_SIZE' + } +} + +class RequestRetryError extends UndiciError { + constructor (message, code, { headers, data }) { + super(message) + Error.captureStackTrace(this, RequestRetryError) + this.name = 'RequestRetryError' + this.message = message || 'Request retry error' + this.code = 'UND_ERR_REQ_RETRY' + this.statusCode = code + this.data = data + this.headers = headers + } +} + +module.exports = { + HTTPParserError, + UndiciError, + HeadersTimeoutError, + HeadersOverflowError, + BodyTimeoutError, + RequestContentLengthMismatchError, + ConnectTimeoutError, + ResponseStatusCodeError, + InvalidArgumentError, + InvalidReturnValueError, + RequestAbortedError, + ClientDestroyedError, + ClientClosedError, + InformationalError, + SocketError, + NotSupportedError, + ResponseContentLengthMismatchError, + BalancedPoolMissingUpstreamError, + ResponseExceededMaxSizeError, + RequestRetryError +} + + +/***/ }), + +/***/ 4655: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + InvalidArgumentError, + NotSupportedError +} = __nccwpck_require__(8707) +const assert = __nccwpck_require__(2613) +const { kHTTP2BuildRequest, kHTTP2CopyHeaders, kHTTP1BuildRequest } = __nccwpck_require__(6443) +const util = __nccwpck_require__(3440) + +// tokenRegExp and headerCharRegex have been lifted from +// https://github.com/nodejs/node/blob/main/lib/_http_common.js + +/** + * Verifies that the given val is a valid HTTP token + * per the rules defined in RFC 7230 + * See https://tools.ietf.org/html/rfc7230#section-3.2.6 + */ +const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/ + +/** + * Matches if val contains an invalid field-vchar + * field-value = *( field-content / obs-fold ) + * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + * field-vchar = VCHAR / obs-text + */ +const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/ + +// Verifies that a given path is valid does not contain control chars \x00 to \x20 +const invalidPathRegex = /[^\u0021-\u00ff]/ + +const kHandler = Symbol('handler') + +const channels = {} + +let extractBody + +try { + const diagnosticsChannel = __nccwpck_require__(1637) + channels.create = diagnosticsChannel.channel('undici:request:create') + channels.bodySent = diagnosticsChannel.channel('undici:request:bodySent') + channels.headers = diagnosticsChannel.channel('undici:request:headers') + channels.trailers = diagnosticsChannel.channel('undici:request:trailers') + channels.error = diagnosticsChannel.channel('undici:request:error') +} catch { + channels.create = { hasSubscribers: false } + channels.bodySent = { hasSubscribers: false } + channels.headers = { hasSubscribers: false } + channels.trailers = { hasSubscribers: false } + channels.error = { hasSubscribers: false } +} + +class Request { + constructor (origin, { + path, + method, + body, + headers, + query, + idempotent, + blocking, + upgrade, + headersTimeout, + bodyTimeout, + reset, + throwOnError, + expectContinue + }, handler) { + if (typeof path !== 'string') { + throw new InvalidArgumentError('path must be a string') + } else if ( + path[0] !== '/' && + !(path.startsWith('http://') || path.startsWith('https://')) && + method !== 'CONNECT' + ) { + throw new InvalidArgumentError('path must be an absolute URL or start with a slash') + } else if (invalidPathRegex.exec(path) !== null) { + throw new InvalidArgumentError('invalid request path') + } + + if (typeof method !== 'string') { + throw new InvalidArgumentError('method must be a string') + } else if (tokenRegExp.exec(method) === null) { + throw new InvalidArgumentError('invalid request method') + } + + if (upgrade && typeof upgrade !== 'string') { + throw new InvalidArgumentError('upgrade must be a string') + } + + if (headersTimeout != null && (!Number.isFinite(headersTimeout) || headersTimeout < 0)) { + throw new InvalidArgumentError('invalid headersTimeout') + } + + if (bodyTimeout != null && (!Number.isFinite(bodyTimeout) || bodyTimeout < 0)) { + throw new InvalidArgumentError('invalid bodyTimeout') + } + + if (reset != null && typeof reset !== 'boolean') { + throw new InvalidArgumentError('invalid reset') + } + + if (expectContinue != null && typeof expectContinue !== 'boolean') { + throw new InvalidArgumentError('invalid expectContinue') + } + + this.headersTimeout = headersTimeout + + this.bodyTimeout = bodyTimeout + + this.throwOnError = throwOnError === true + + this.method = method + + this.abort = null + + if (body == null) { + this.body = null + } else if (util.isStream(body)) { + this.body = body + + const rState = this.body._readableState + if (!rState || !rState.autoDestroy) { + this.endHandler = function autoDestroy () { + util.destroy(this) + } + this.body.on('end', this.endHandler) + } + + this.errorHandler = err => { + if (this.abort) { + this.abort(err) + } else { + this.error = err + } + } + this.body.on('error', this.errorHandler) + } else if (util.isBuffer(body)) { + this.body = body.byteLength ? body : null + } else if (ArrayBuffer.isView(body)) { + this.body = body.buffer.byteLength ? Buffer.from(body.buffer, body.byteOffset, body.byteLength) : null + } else if (body instanceof ArrayBuffer) { + this.body = body.byteLength ? Buffer.from(body) : null + } else if (typeof body === 'string') { + this.body = body.length ? Buffer.from(body) : null + } else if (util.isFormDataLike(body) || util.isIterable(body) || util.isBlobLike(body)) { + this.body = body + } else { + throw new InvalidArgumentError('body must be a string, a Buffer, a Readable stream, an iterable, or an async iterable') + } + + this.completed = false + + this.aborted = false + + this.upgrade = upgrade || null + + this.path = query ? util.buildURL(path, query) : path + + this.origin = origin + + this.idempotent = idempotent == null + ? method === 'HEAD' || method === 'GET' + : idempotent + + this.blocking = blocking == null ? false : blocking + + this.reset = reset == null ? null : reset + + this.host = null + + this.contentLength = null + + this.contentType = null + + this.headers = '' + + // Only for H2 + this.expectContinue = expectContinue != null ? expectContinue : false + + if (Array.isArray(headers)) { + if (headers.length % 2 !== 0) { + throw new InvalidArgumentError('headers array must be even') + } + for (let i = 0; i < headers.length; i += 2) { + processHeader(this, headers[i], headers[i + 1]) + } + } else if (headers && typeof headers === 'object') { + const keys = Object.keys(headers) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + processHeader(this, key, headers[key]) + } + } else if (headers != null) { + throw new InvalidArgumentError('headers must be an object or an array') + } + + if (util.isFormDataLike(this.body)) { + if (util.nodeMajor < 16 || (util.nodeMajor === 16 && util.nodeMinor < 8)) { + throw new InvalidArgumentError('Form-Data bodies are only supported in node v16.8 and newer.') + } + + if (!extractBody) { + extractBody = (__nccwpck_require__(8923).extractBody) + } + + const [bodyStream, contentType] = extractBody(body) + if (this.contentType == null) { + this.contentType = contentType + this.headers += `content-type: ${contentType}\r\n` + } + this.body = bodyStream.stream + this.contentLength = bodyStream.length + } else if (util.isBlobLike(body) && this.contentType == null && body.type) { + this.contentType = body.type + this.headers += `content-type: ${body.type}\r\n` + } + + util.validateHandler(handler, method, upgrade) + + this.servername = util.getServerName(this.host) + + this[kHandler] = handler + + if (channels.create.hasSubscribers) { + channels.create.publish({ request: this }) + } + } + + onBodySent (chunk) { + if (this[kHandler].onBodySent) { + try { + return this[kHandler].onBodySent(chunk) + } catch (err) { + this.abort(err) + } + } + } + + onRequestSent () { + if (channels.bodySent.hasSubscribers) { + channels.bodySent.publish({ request: this }) + } + + if (this[kHandler].onRequestSent) { + try { + return this[kHandler].onRequestSent() + } catch (err) { + this.abort(err) + } + } + } + + onConnect (abort) { + assert(!this.aborted) + assert(!this.completed) + + if (this.error) { + abort(this.error) + } else { + this.abort = abort + return this[kHandler].onConnect(abort) + } + } + + onHeaders (statusCode, headers, resume, statusText) { + assert(!this.aborted) + assert(!this.completed) + + if (channels.headers.hasSubscribers) { + channels.headers.publish({ request: this, response: { statusCode, headers, statusText } }) + } + + try { + return this[kHandler].onHeaders(statusCode, headers, resume, statusText) + } catch (err) { + this.abort(err) + } + } + + onData (chunk) { + assert(!this.aborted) + assert(!this.completed) + + try { + return this[kHandler].onData(chunk) + } catch (err) { + this.abort(err) + return false + } + } + + onUpgrade (statusCode, headers, socket) { + assert(!this.aborted) + assert(!this.completed) + + return this[kHandler].onUpgrade(statusCode, headers, socket) + } + + onComplete (trailers) { + this.onFinally() + + assert(!this.aborted) + + this.completed = true + if (channels.trailers.hasSubscribers) { + channels.trailers.publish({ request: this, trailers }) + } + + try { + return this[kHandler].onComplete(trailers) + } catch (err) { + // TODO (fix): This might be a bad idea? + this.onError(err) + } + } + + onError (error) { + this.onFinally() + + if (channels.error.hasSubscribers) { + channels.error.publish({ request: this, error }) + } + + if (this.aborted) { + return + } + this.aborted = true + + return this[kHandler].onError(error) + } + + onFinally () { + if (this.errorHandler) { + this.body.off('error', this.errorHandler) + this.errorHandler = null + } + + if (this.endHandler) { + this.body.off('end', this.endHandler) + this.endHandler = null + } + } + + // TODO: adjust to support H2 + addHeader (key, value) { + processHeader(this, key, value) + return this + } + + static [kHTTP1BuildRequest] (origin, opts, handler) { + // TODO: Migrate header parsing here, to make Requests + // HTTP agnostic + return new Request(origin, opts, handler) + } + + static [kHTTP2BuildRequest] (origin, opts, handler) { + const headers = opts.headers + opts = { ...opts, headers: null } + + const request = new Request(origin, opts, handler) + + request.headers = {} + + if (Array.isArray(headers)) { + if (headers.length % 2 !== 0) { + throw new InvalidArgumentError('headers array must be even') + } + for (let i = 0; i < headers.length; i += 2) { + processHeader(request, headers[i], headers[i + 1], true) + } + } else if (headers && typeof headers === 'object') { + const keys = Object.keys(headers) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + processHeader(request, key, headers[key], true) + } + } else if (headers != null) { + throw new InvalidArgumentError('headers must be an object or an array') + } + + return request + } + + static [kHTTP2CopyHeaders] (raw) { + const rawHeaders = raw.split('\r\n') + const headers = {} + + for (const header of rawHeaders) { + const [key, value] = header.split(': ') + + if (value == null || value.length === 0) continue + + if (headers[key]) headers[key] += `,${value}` + else headers[key] = value + } + + return headers + } +} + +function processHeaderValue (key, val, skipAppend) { + if (val && typeof val === 'object') { + throw new InvalidArgumentError(`invalid ${key} header`) + } + + val = val != null ? `${val}` : '' + + if (headerCharRegex.exec(val) !== null) { + throw new InvalidArgumentError(`invalid ${key} header`) + } + + return skipAppend ? val : `${key}: ${val}\r\n` +} + +function processHeader (request, key, val, skipAppend = false) { + if (val && (typeof val === 'object' && !Array.isArray(val))) { + throw new InvalidArgumentError(`invalid ${key} header`) + } else if (val === undefined) { + return + } + + if ( + request.host === null && + key.length === 4 && + key.toLowerCase() === 'host' + ) { + if (headerCharRegex.exec(val) !== null) { + throw new InvalidArgumentError(`invalid ${key} header`) + } + // Consumed by Client + request.host = val + } else if ( + request.contentLength === null && + key.length === 14 && + key.toLowerCase() === 'content-length' + ) { + request.contentLength = parseInt(val, 10) + if (!Number.isFinite(request.contentLength)) { + throw new InvalidArgumentError('invalid content-length header') + } + } else if ( + request.contentType === null && + key.length === 12 && + key.toLowerCase() === 'content-type' + ) { + request.contentType = val + if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend) + else request.headers += processHeaderValue(key, val) + } else if ( + key.length === 17 && + key.toLowerCase() === 'transfer-encoding' + ) { + throw new InvalidArgumentError('invalid transfer-encoding header') + } else if ( + key.length === 10 && + key.toLowerCase() === 'connection' + ) { + const value = typeof val === 'string' ? val.toLowerCase() : null + if (value !== 'close' && value !== 'keep-alive') { + throw new InvalidArgumentError('invalid connection header') + } else if (value === 'close') { + request.reset = true + } + } else if ( + key.length === 10 && + key.toLowerCase() === 'keep-alive' + ) { + throw new InvalidArgumentError('invalid keep-alive header') + } else if ( + key.length === 7 && + key.toLowerCase() === 'upgrade' + ) { + throw new InvalidArgumentError('invalid upgrade header') + } else if ( + key.length === 6 && + key.toLowerCase() === 'expect' + ) { + throw new NotSupportedError('expect header not supported') + } else if (tokenRegExp.exec(key) === null) { + throw new InvalidArgumentError('invalid header key') + } else { + if (Array.isArray(val)) { + for (let i = 0; i < val.length; i++) { + if (skipAppend) { + if (request.headers[key]) request.headers[key] += `,${processHeaderValue(key, val[i], skipAppend)}` + else request.headers[key] = processHeaderValue(key, val[i], skipAppend) + } else { + request.headers += processHeaderValue(key, val[i]) + } + } + } else { + if (skipAppend) request.headers[key] = processHeaderValue(key, val, skipAppend) + else request.headers += processHeaderValue(key, val) + } + } +} + +module.exports = Request + + +/***/ }), + +/***/ 6443: +/***/ ((module) => { + +module.exports = { + kClose: Symbol('close'), + kDestroy: Symbol('destroy'), + kDispatch: Symbol('dispatch'), + kUrl: Symbol('url'), + kWriting: Symbol('writing'), + kResuming: Symbol('resuming'), + kQueue: Symbol('queue'), + kConnect: Symbol('connect'), + kConnecting: Symbol('connecting'), + kHeadersList: Symbol('headers list'), + kKeepAliveDefaultTimeout: Symbol('default keep alive timeout'), + kKeepAliveMaxTimeout: Symbol('max keep alive timeout'), + kKeepAliveTimeoutThreshold: Symbol('keep alive timeout threshold'), + kKeepAliveTimeoutValue: Symbol('keep alive timeout'), + kKeepAlive: Symbol('keep alive'), + kHeadersTimeout: Symbol('headers timeout'), + kBodyTimeout: Symbol('body timeout'), + kServerName: Symbol('server name'), + kLocalAddress: Symbol('local address'), + kHost: Symbol('host'), + kNoRef: Symbol('no ref'), + kBodyUsed: Symbol('used'), + kRunning: Symbol('running'), + kBlocking: Symbol('blocking'), + kPending: Symbol('pending'), + kSize: Symbol('size'), + kBusy: Symbol('busy'), + kQueued: Symbol('queued'), + kFree: Symbol('free'), + kConnected: Symbol('connected'), + kClosed: Symbol('closed'), + kNeedDrain: Symbol('need drain'), + kReset: Symbol('reset'), + kDestroyed: Symbol.for('nodejs.stream.destroyed'), + kMaxHeadersSize: Symbol('max headers size'), + kRunningIdx: Symbol('running index'), + kPendingIdx: Symbol('pending index'), + kError: Symbol('error'), + kClients: Symbol('clients'), + kClient: Symbol('client'), + kParser: Symbol('parser'), + kOnDestroyed: Symbol('destroy callbacks'), + kPipelining: Symbol('pipelining'), + kSocket: Symbol('socket'), + kHostHeader: Symbol('host header'), + kConnector: Symbol('connector'), + kStrictContentLength: Symbol('strict content length'), + kMaxRedirections: Symbol('maxRedirections'), + kMaxRequests: Symbol('maxRequestsPerClient'), + kProxy: Symbol('proxy agent options'), + kCounter: Symbol('socket request counter'), + kInterceptors: Symbol('dispatch interceptors'), + kMaxResponseSize: Symbol('max response size'), + kHTTP2Session: Symbol('http2Session'), + kHTTP2SessionState: Symbol('http2Session state'), + kHTTP2BuildRequest: Symbol('http2 build request'), + kHTTP1BuildRequest: Symbol('http1 build request'), + kHTTP2CopyHeaders: Symbol('http2 copy headers'), + kHTTPConnVersion: Symbol('http connection version'), + kRetryHandlerDefaultRetry: Symbol('retry agent default retry'), + kConstruct: Symbol('constructable') +} + + +/***/ }), + +/***/ 3440: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const assert = __nccwpck_require__(2613) +const { kDestroyed, kBodyUsed } = __nccwpck_require__(6443) +const { IncomingMessage } = __nccwpck_require__(8611) +const stream = __nccwpck_require__(2203) +const net = __nccwpck_require__(9278) +const { InvalidArgumentError } = __nccwpck_require__(8707) +const { Blob } = __nccwpck_require__(181) +const nodeUtil = __nccwpck_require__(9023) +const { stringify } = __nccwpck_require__(3480) +const { headerNameLowerCasedRecord } = __nccwpck_require__(735) + +const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v)) + +function nop () {} + +function isStream (obj) { + return obj && typeof obj === 'object' && typeof obj.pipe === 'function' && typeof obj.on === 'function' +} + +// based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License) +function isBlobLike (object) { + return (Blob && object instanceof Blob) || ( + object && + typeof object === 'object' && + (typeof object.stream === 'function' || + typeof object.arrayBuffer === 'function') && + /^(Blob|File)$/.test(object[Symbol.toStringTag]) + ) +} + +function buildURL (url, queryParams) { + if (url.includes('?') || url.includes('#')) { + throw new Error('Query params cannot be passed when url already contains "?" or "#".') + } + + const stringified = stringify(queryParams) + + if (stringified) { + url += '?' + stringified + } + + return url +} + +function parseURL (url) { + if (typeof url === 'string') { + url = new URL(url) + + if (!/^https?:/.test(url.origin || url.protocol)) { + throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.') + } + + return url + } + + if (!url || typeof url !== 'object') { + throw new InvalidArgumentError('Invalid URL: The URL argument must be a non-null object.') + } + + if (!/^https?:/.test(url.origin || url.protocol)) { + throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.') + } + + if (!(url instanceof URL)) { + if (url.port != null && url.port !== '' && !Number.isFinite(parseInt(url.port))) { + throw new InvalidArgumentError('Invalid URL: port must be a valid integer or a string representation of an integer.') + } + + if (url.path != null && typeof url.path !== 'string') { + throw new InvalidArgumentError('Invalid URL path: the path must be a string or null/undefined.') + } + + if (url.pathname != null && typeof url.pathname !== 'string') { + throw new InvalidArgumentError('Invalid URL pathname: the pathname must be a string or null/undefined.') + } + + if (url.hostname != null && typeof url.hostname !== 'string') { + throw new InvalidArgumentError('Invalid URL hostname: the hostname must be a string or null/undefined.') + } + + if (url.origin != null && typeof url.origin !== 'string') { + throw new InvalidArgumentError('Invalid URL origin: the origin must be a string or null/undefined.') + } + + const port = url.port != null + ? url.port + : (url.protocol === 'https:' ? 443 : 80) + let origin = url.origin != null + ? url.origin + : `${url.protocol}//${url.hostname}:${port}` + let path = url.path != null + ? url.path + : `${url.pathname || ''}${url.search || ''}` + + if (origin.endsWith('/')) { + origin = origin.substring(0, origin.length - 1) + } + + if (path && !path.startsWith('/')) { + path = `/${path}` + } + // new URL(path, origin) is unsafe when `path` contains an absolute URL + // From https://developer.mozilla.org/en-US/docs/Web/API/URL/URL: + // If first parameter is a relative URL, second param is required, and will be used as the base URL. + // If first parameter is an absolute URL, a given second param will be ignored. + url = new URL(origin + path) + } + + return url +} + +function parseOrigin (url) { + url = parseURL(url) + + if (url.pathname !== '/' || url.search || url.hash) { + throw new InvalidArgumentError('invalid url') + } + + return url +} + +function getHostname (host) { + if (host[0] === '[') { + const idx = host.indexOf(']') + + assert(idx !== -1) + return host.substring(1, idx) + } + + const idx = host.indexOf(':') + if (idx === -1) return host + + return host.substring(0, idx) +} + +// IP addresses are not valid server names per RFC6066 +// > Currently, the only server names supported are DNS hostnames +function getServerName (host) { + if (!host) { + return null + } + + assert.strictEqual(typeof host, 'string') + + const servername = getHostname(host) + if (net.isIP(servername)) { + return '' + } + + return servername +} + +function deepClone (obj) { + return JSON.parse(JSON.stringify(obj)) +} + +function isAsyncIterable (obj) { + return !!(obj != null && typeof obj[Symbol.asyncIterator] === 'function') +} + +function isIterable (obj) { + return !!(obj != null && (typeof obj[Symbol.iterator] === 'function' || typeof obj[Symbol.asyncIterator] === 'function')) +} + +function bodyLength (body) { + if (body == null) { + return 0 + } else if (isStream(body)) { + const state = body._readableState + return state && state.objectMode === false && state.ended === true && Number.isFinite(state.length) + ? state.length + : null + } else if (isBlobLike(body)) { + return body.size != null ? body.size : null + } else if (isBuffer(body)) { + return body.byteLength + } + + return null +} + +function isDestroyed (stream) { + return !stream || !!(stream.destroyed || stream[kDestroyed]) +} + +function isReadableAborted (stream) { + const state = stream && stream._readableState + return isDestroyed(stream) && state && !state.endEmitted +} + +function destroy (stream, err) { + if (stream == null || !isStream(stream) || isDestroyed(stream)) { + return + } + + if (typeof stream.destroy === 'function') { + if (Object.getPrototypeOf(stream).constructor === IncomingMessage) { + // See: https://github.com/nodejs/node/pull/38505/files + stream.socket = null + } + + stream.destroy(err) + } else if (err) { + process.nextTick((stream, err) => { + stream.emit('error', err) + }, stream, err) + } + + if (stream.destroyed !== true) { + stream[kDestroyed] = true + } +} + +const KEEPALIVE_TIMEOUT_EXPR = /timeout=(\d+)/ +function parseKeepAliveTimeout (val) { + const m = val.toString().match(KEEPALIVE_TIMEOUT_EXPR) + return m ? parseInt(m[1], 10) * 1000 : null +} + +/** + * Retrieves a header name and returns its lowercase value. + * @param {string | Buffer} value Header name + * @returns {string} + */ +function headerNameToString (value) { + return headerNameLowerCasedRecord[value] || value.toLowerCase() +} + +function parseHeaders (headers, obj = {}) { + // For H2 support + if (!Array.isArray(headers)) return headers + + for (let i = 0; i < headers.length; i += 2) { + const key = headers[i].toString().toLowerCase() + let val = obj[key] + + if (!val) { + if (Array.isArray(headers[i + 1])) { + obj[key] = headers[i + 1].map(x => x.toString('utf8')) + } else { + obj[key] = headers[i + 1].toString('utf8') + } + } else { + if (!Array.isArray(val)) { + val = [val] + obj[key] = val + } + val.push(headers[i + 1].toString('utf8')) + } + } + + // See https://github.com/nodejs/node/pull/46528 + if ('content-length' in obj && 'content-disposition' in obj) { + obj['content-disposition'] = Buffer.from(obj['content-disposition']).toString('latin1') + } + + return obj +} + +function parseRawHeaders (headers) { + const ret = [] + let hasContentLength = false + let contentDispositionIdx = -1 + + for (let n = 0; n < headers.length; n += 2) { + const key = headers[n + 0].toString() + const val = headers[n + 1].toString('utf8') + + if (key.length === 14 && (key === 'content-length' || key.toLowerCase() === 'content-length')) { + ret.push(key, val) + hasContentLength = true + } else if (key.length === 19 && (key === 'content-disposition' || key.toLowerCase() === 'content-disposition')) { + contentDispositionIdx = ret.push(key, val) - 1 + } else { + ret.push(key, val) + } + } + + // See https://github.com/nodejs/node/pull/46528 + if (hasContentLength && contentDispositionIdx !== -1) { + ret[contentDispositionIdx] = Buffer.from(ret[contentDispositionIdx]).toString('latin1') + } + + return ret +} + +function isBuffer (buffer) { + // See, https://github.com/mcollina/undici/pull/319 + return buffer instanceof Uint8Array || Buffer.isBuffer(buffer) +} + +function validateHandler (handler, method, upgrade) { + if (!handler || typeof handler !== 'object') { + throw new InvalidArgumentError('handler must be an object') + } + + if (typeof handler.onConnect !== 'function') { + throw new InvalidArgumentError('invalid onConnect method') + } + + if (typeof handler.onError !== 'function') { + throw new InvalidArgumentError('invalid onError method') + } + + if (typeof handler.onBodySent !== 'function' && handler.onBodySent !== undefined) { + throw new InvalidArgumentError('invalid onBodySent method') + } + + if (upgrade || method === 'CONNECT') { + if (typeof handler.onUpgrade !== 'function') { + throw new InvalidArgumentError('invalid onUpgrade method') + } + } else { + if (typeof handler.onHeaders !== 'function') { + throw new InvalidArgumentError('invalid onHeaders method') + } + + if (typeof handler.onData !== 'function') { + throw new InvalidArgumentError('invalid onData method') + } + + if (typeof handler.onComplete !== 'function') { + throw new InvalidArgumentError('invalid onComplete method') + } + } +} + +// A body is disturbed if it has been read from and it cannot +// be re-used without losing state or data. +function isDisturbed (body) { + return !!(body && ( + stream.isDisturbed + ? stream.isDisturbed(body) || body[kBodyUsed] // TODO (fix): Why is body[kBodyUsed] needed? + : body[kBodyUsed] || + body.readableDidRead || + (body._readableState && body._readableState.dataEmitted) || + isReadableAborted(body) + )) +} + +function isErrored (body) { + return !!(body && ( + stream.isErrored + ? stream.isErrored(body) + : /state: 'errored'/.test(nodeUtil.inspect(body) + ))) +} + +function isReadable (body) { + return !!(body && ( + stream.isReadable + ? stream.isReadable(body) + : /state: 'readable'/.test(nodeUtil.inspect(body) + ))) +} + +function getSocketInfo (socket) { + return { + localAddress: socket.localAddress, + localPort: socket.localPort, + remoteAddress: socket.remoteAddress, + remotePort: socket.remotePort, + remoteFamily: socket.remoteFamily, + timeout: socket.timeout, + bytesWritten: socket.bytesWritten, + bytesRead: socket.bytesRead + } +} + +async function * convertIterableToBuffer (iterable) { + for await (const chunk of iterable) { + yield Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk) + } +} + +let ReadableStream +function ReadableStreamFrom (iterable) { + if (!ReadableStream) { + ReadableStream = (__nccwpck_require__(3774).ReadableStream) + } + + if (ReadableStream.from) { + return ReadableStream.from(convertIterableToBuffer(iterable)) + } + + let iterator + return new ReadableStream( + { + async start () { + iterator = iterable[Symbol.asyncIterator]() + }, + async pull (controller) { + const { done, value } = await iterator.next() + if (done) { + queueMicrotask(() => { + controller.close() + }) + } else { + const buf = Buffer.isBuffer(value) ? value : Buffer.from(value) + controller.enqueue(new Uint8Array(buf)) + } + return controller.desiredSize > 0 + }, + async cancel (reason) { + await iterator.return() + } + }, + 0 + ) +} + +// The chunk should be a FormData instance and contains +// all the required methods. +function isFormDataLike (object) { + return ( + object && + typeof object === 'object' && + typeof object.append === 'function' && + typeof object.delete === 'function' && + typeof object.get === 'function' && + typeof object.getAll === 'function' && + typeof object.has === 'function' && + typeof object.set === 'function' && + object[Symbol.toStringTag] === 'FormData' + ) +} + +function throwIfAborted (signal) { + if (!signal) { return } + if (typeof signal.throwIfAborted === 'function') { + signal.throwIfAborted() + } else { + if (signal.aborted) { + // DOMException not available < v17.0.0 + const err = new Error('The operation was aborted') + err.name = 'AbortError' + throw err + } + } +} + +function addAbortListener (signal, listener) { + if ('addEventListener' in signal) { + signal.addEventListener('abort', listener, { once: true }) + return () => signal.removeEventListener('abort', listener) + } + signal.addListener('abort', listener) + return () => signal.removeListener('abort', listener) +} + +const hasToWellFormed = !!String.prototype.toWellFormed + +/** + * @param {string} val + */ +function toUSVString (val) { + if (hasToWellFormed) { + return `${val}`.toWellFormed() + } else if (nodeUtil.toUSVString) { + return nodeUtil.toUSVString(val) + } + + return `${val}` +} + +// Parsed accordingly to RFC 9110 +// https://www.rfc-editor.org/rfc/rfc9110#field.content-range +function parseRangeHeader (range) { + if (range == null || range === '') return { start: 0, end: null, size: null } + + const m = range ? range.match(/^bytes (\d+)-(\d+)\/(\d+)?$/) : null + return m + ? { + start: parseInt(m[1]), + end: m[2] ? parseInt(m[2]) : null, + size: m[3] ? parseInt(m[3]) : null + } + : null +} + +const kEnumerableProperty = Object.create(null) +kEnumerableProperty.enumerable = true + +module.exports = { + kEnumerableProperty, + nop, + isDisturbed, + isErrored, + isReadable, + toUSVString, + isReadableAborted, + isBlobLike, + parseOrigin, + parseURL, + getServerName, + isStream, + isIterable, + isAsyncIterable, + isDestroyed, + headerNameToString, + parseRawHeaders, + parseHeaders, + parseKeepAliveTimeout, + destroy, + bodyLength, + deepClone, + ReadableStreamFrom, + isBuffer, + validateHandler, + getSocketInfo, + isFormDataLike, + buildURL, + throwIfAborted, + addAbortListener, + parseRangeHeader, + nodeMajor, + nodeMinor, + nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13), + safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE'] +} + + +/***/ }), + +/***/ 1: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Dispatcher = __nccwpck_require__(992) +const { + ClientDestroyedError, + ClientClosedError, + InvalidArgumentError +} = __nccwpck_require__(8707) +const { kDestroy, kClose, kDispatch, kInterceptors } = __nccwpck_require__(6443) + +const kDestroyed = Symbol('destroyed') +const kClosed = Symbol('closed') +const kOnDestroyed = Symbol('onDestroyed') +const kOnClosed = Symbol('onClosed') +const kInterceptedDispatch = Symbol('Intercepted Dispatch') + +class DispatcherBase extends Dispatcher { + constructor () { + super() + + this[kDestroyed] = false + this[kOnDestroyed] = null + this[kClosed] = false + this[kOnClosed] = [] + } + + get destroyed () { + return this[kDestroyed] + } + + get closed () { + return this[kClosed] + } + + get interceptors () { + return this[kInterceptors] + } + + set interceptors (newInterceptors) { + if (newInterceptors) { + for (let i = newInterceptors.length - 1; i >= 0; i--) { + const interceptor = this[kInterceptors][i] + if (typeof interceptor !== 'function') { + throw new InvalidArgumentError('interceptor must be an function') + } + } + } + + this[kInterceptors] = newInterceptors + } + + close (callback) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + this.close((err, data) => { + return err ? reject(err) : resolve(data) + }) + }) + } + + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + if (this[kDestroyed]) { + queueMicrotask(() => callback(new ClientDestroyedError(), null)) + return + } + + if (this[kClosed]) { + if (this[kOnClosed]) { + this[kOnClosed].push(callback) + } else { + queueMicrotask(() => callback(null, null)) + } + return + } + + this[kClosed] = true + this[kOnClosed].push(callback) + + const onClosed = () => { + const callbacks = this[kOnClosed] + this[kOnClosed] = null + for (let i = 0; i < callbacks.length; i++) { + callbacks[i](null, null) + } + } + + // Should not error. + this[kClose]() + .then(() => this.destroy()) + .then(() => { + queueMicrotask(onClosed) + }) + } + + destroy (err, callback) { + if (typeof err === 'function') { + callback = err + err = null + } + + if (callback === undefined) { + return new Promise((resolve, reject) => { + this.destroy(err, (err, data) => { + return err ? /* istanbul ignore next: should never error */ reject(err) : resolve(data) + }) + }) + } + + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + if (this[kDestroyed]) { + if (this[kOnDestroyed]) { + this[kOnDestroyed].push(callback) + } else { + queueMicrotask(() => callback(null, null)) + } + return + } + + if (!err) { + err = new ClientDestroyedError() + } + + this[kDestroyed] = true + this[kOnDestroyed] = this[kOnDestroyed] || [] + this[kOnDestroyed].push(callback) + + const onDestroyed = () => { + const callbacks = this[kOnDestroyed] + this[kOnDestroyed] = null + for (let i = 0; i < callbacks.length; i++) { + callbacks[i](null, null) + } + } + + // Should not error. + this[kDestroy](err).then(() => { + queueMicrotask(onDestroyed) + }) + } + + [kInterceptedDispatch] (opts, handler) { + if (!this[kInterceptors] || this[kInterceptors].length === 0) { + this[kInterceptedDispatch] = this[kDispatch] + return this[kDispatch](opts, handler) + } + + let dispatch = this[kDispatch].bind(this) + for (let i = this[kInterceptors].length - 1; i >= 0; i--) { + dispatch = this[kInterceptors][i](dispatch) + } + this[kInterceptedDispatch] = dispatch + return dispatch(opts, handler) + } + + dispatch (opts, handler) { + if (!handler || typeof handler !== 'object') { + throw new InvalidArgumentError('handler must be an object') + } + + try { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('opts must be an object.') + } + + if (this[kDestroyed] || this[kOnDestroyed]) { + throw new ClientDestroyedError() + } + + if (this[kClosed]) { + throw new ClientClosedError() + } + + return this[kInterceptedDispatch](opts, handler) + } catch (err) { + if (typeof handler.onError !== 'function') { + throw new InvalidArgumentError('invalid onError method') + } + + handler.onError(err) + + return false + } + } +} + +module.exports = DispatcherBase + + +/***/ }), + +/***/ 992: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const EventEmitter = __nccwpck_require__(4434) + +class Dispatcher extends EventEmitter { + dispatch () { + throw new Error('not implemented') + } + + close () { + throw new Error('not implemented') + } + + destroy () { + throw new Error('not implemented') + } +} + +module.exports = Dispatcher + + +/***/ }), + +/***/ 8923: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Busboy = __nccwpck_require__(9581) +const util = __nccwpck_require__(3440) +const { + ReadableStreamFrom, + isBlobLike, + isReadableStreamLike, + readableStreamClose, + createDeferredPromise, + fullyReadBody +} = __nccwpck_require__(5523) +const { FormData } = __nccwpck_require__(3073) +const { kState } = __nccwpck_require__(9710) +const { webidl } = __nccwpck_require__(4222) +const { DOMException, structuredClone } = __nccwpck_require__(7326) +const { Blob, File: NativeFile } = __nccwpck_require__(181) +const { kBodyUsed } = __nccwpck_require__(6443) +const assert = __nccwpck_require__(2613) +const { isErrored } = __nccwpck_require__(3440) +const { isUint8Array, isArrayBuffer } = __nccwpck_require__(8253) +const { File: UndiciFile } = __nccwpck_require__(3041) +const { parseMIMEType, serializeAMimeType } = __nccwpck_require__(4322) + +let ReadableStream = globalThis.ReadableStream + +/** @type {globalThis['File']} */ +const File = NativeFile ?? UndiciFile +const textEncoder = new TextEncoder() +const textDecoder = new TextDecoder() + +// https://fetch.spec.whatwg.org/#concept-bodyinit-extract +function extractBody (object, keepalive = false) { + if (!ReadableStream) { + ReadableStream = (__nccwpck_require__(3774).ReadableStream) + } + + // 1. Let stream be null. + let stream = null + + // 2. If object is a ReadableStream object, then set stream to object. + if (object instanceof ReadableStream) { + stream = object + } else if (isBlobLike(object)) { + // 3. Otherwise, if object is a Blob object, set stream to the + // result of running object’s get stream. + stream = object.stream() + } else { + // 4. Otherwise, set stream to a new ReadableStream object, and set + // up stream. + stream = new ReadableStream({ + async pull (controller) { + controller.enqueue( + typeof source === 'string' ? textEncoder.encode(source) : source + ) + queueMicrotask(() => readableStreamClose(controller)) + }, + start () {}, + type: undefined + }) + } + + // 5. Assert: stream is a ReadableStream object. + assert(isReadableStreamLike(stream)) + + // 6. Let action be null. + let action = null + + // 7. Let source be null. + let source = null + + // 8. Let length be null. + let length = null + + // 9. Let type be null. + let type = null + + // 10. Switch on object: + if (typeof object === 'string') { + // Set source to the UTF-8 encoding of object. + // Note: setting source to a Uint8Array here breaks some mocking assumptions. + source = object + + // Set type to `text/plain;charset=UTF-8`. + type = 'text/plain;charset=UTF-8' + } else if (object instanceof URLSearchParams) { + // URLSearchParams + + // spec says to run application/x-www-form-urlencoded on body.list + // this is implemented in Node.js as apart of an URLSearchParams instance toString method + // See: https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/lib/internal/url.js#L490 + // and https://github.com/nodejs/node/blob/e46c680bf2b211bbd52cf959ca17ee98c7f657f5/lib/internal/url.js#L1100 + + // Set source to the result of running the application/x-www-form-urlencoded serializer with object’s list. + source = object.toString() + + // Set type to `application/x-www-form-urlencoded;charset=UTF-8`. + type = 'application/x-www-form-urlencoded;charset=UTF-8' + } else if (isArrayBuffer(object)) { + // BufferSource/ArrayBuffer + + // Set source to a copy of the bytes held by object. + source = new Uint8Array(object.slice()) + } else if (ArrayBuffer.isView(object)) { + // BufferSource/ArrayBufferView + + // Set source to a copy of the bytes held by object. + source = new Uint8Array(object.buffer.slice(object.byteOffset, object.byteOffset + object.byteLength)) + } else if (util.isFormDataLike(object)) { + const boundary = `----formdata-undici-0${`${Math.floor(Math.random() * 1e11)}`.padStart(11, '0')}` + const prefix = `--${boundary}\r\nContent-Disposition: form-data` + + /*! formdata-polyfill. MIT License. Jimmy Wärting */ + const escape = (str) => + str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22') + const normalizeLinefeeds = (value) => value.replace(/\r?\n|\r/g, '\r\n') + + // Set action to this step: run the multipart/form-data + // encoding algorithm, with object’s entry list and UTF-8. + // - This ensures that the body is immutable and can't be changed afterwords + // - That the content-length is calculated in advance. + // - And that all parts are pre-encoded and ready to be sent. + + const blobParts = [] + const rn = new Uint8Array([13, 10]) // '\r\n' + length = 0 + let hasUnknownSizeValue = false + + for (const [name, value] of object) { + if (typeof value === 'string') { + const chunk = textEncoder.encode(prefix + + `; name="${escape(normalizeLinefeeds(name))}"` + + `\r\n\r\n${normalizeLinefeeds(value)}\r\n`) + blobParts.push(chunk) + length += chunk.byteLength + } else { + const chunk = textEncoder.encode(`${prefix}; name="${escape(normalizeLinefeeds(name))}"` + + (value.name ? `; filename="${escape(value.name)}"` : '') + '\r\n' + + `Content-Type: ${ + value.type || 'application/octet-stream' + }\r\n\r\n`) + blobParts.push(chunk, value, rn) + if (typeof value.size === 'number') { + length += chunk.byteLength + value.size + rn.byteLength + } else { + hasUnknownSizeValue = true + } + } + } + + const chunk = textEncoder.encode(`--${boundary}--`) + blobParts.push(chunk) + length += chunk.byteLength + if (hasUnknownSizeValue) { + length = null + } + + // Set source to object. + source = object + + action = async function * () { + for (const part of blobParts) { + if (part.stream) { + yield * part.stream() + } else { + yield part + } + } + } + + // Set type to `multipart/form-data; boundary=`, + // followed by the multipart/form-data boundary string generated + // by the multipart/form-data encoding algorithm. + type = 'multipart/form-data; boundary=' + boundary + } else if (isBlobLike(object)) { + // Blob + + // Set source to object. + source = object + + // Set length to object’s size. + length = object.size + + // If object’s type attribute is not the empty byte sequence, set + // type to its value. + if (object.type) { + type = object.type + } + } else if (typeof object[Symbol.asyncIterator] === 'function') { + // If keepalive is true, then throw a TypeError. + if (keepalive) { + throw new TypeError('keepalive') + } + + // If object is disturbed or locked, then throw a TypeError. + if (util.isDisturbed(object) || object.locked) { + throw new TypeError( + 'Response body object should not be disturbed or locked' + ) + } + + stream = + object instanceof ReadableStream ? object : ReadableStreamFrom(object) + } + + // 11. If source is a byte sequence, then set action to a + // step that returns source and length to source’s length. + if (typeof source === 'string' || util.isBuffer(source)) { + length = Buffer.byteLength(source) + } + + // 12. If action is non-null, then run these steps in in parallel: + if (action != null) { + // Run action. + let iterator + stream = new ReadableStream({ + async start () { + iterator = action(object)[Symbol.asyncIterator]() + }, + async pull (controller) { + const { value, done } = await iterator.next() + if (done) { + // When running action is done, close stream. + queueMicrotask(() => { + controller.close() + }) + } else { + // Whenever one or more bytes are available and stream is not errored, + // enqueue a Uint8Array wrapping an ArrayBuffer containing the available + // bytes into stream. + if (!isErrored(stream)) { + controller.enqueue(new Uint8Array(value)) + } + } + return controller.desiredSize > 0 + }, + async cancel (reason) { + await iterator.return() + }, + type: undefined + }) + } + + // 13. Let body be a body whose stream is stream, source is source, + // and length is length. + const body = { stream, source, length } + + // 14. Return (body, type). + return [body, type] +} + +// https://fetch.spec.whatwg.org/#bodyinit-safely-extract +function safelyExtractBody (object, keepalive = false) { + if (!ReadableStream) { + // istanbul ignore next + ReadableStream = (__nccwpck_require__(3774).ReadableStream) + } + + // To safely extract a body and a `Content-Type` value from + // a byte sequence or BodyInit object object, run these steps: + + // 1. If object is a ReadableStream object, then: + if (object instanceof ReadableStream) { + // Assert: object is neither disturbed nor locked. + // istanbul ignore next + assert(!util.isDisturbed(object), 'The body has already been consumed.') + // istanbul ignore next + assert(!object.locked, 'The stream is locked.') + } + + // 2. Return the results of extracting object. + return extractBody(object, keepalive) +} + +function cloneBody (body) { + // To clone a body body, run these steps: + + // https://fetch.spec.whatwg.org/#concept-body-clone + + // 1. Let « out1, out2 » be the result of teeing body’s stream. + const [out1, out2] = body.stream.tee() + const out2Clone = structuredClone(out2, { transfer: [out2] }) + // This, for whatever reasons, unrefs out2Clone which allows + // the process to exit by itself. + const [, finalClone] = out2Clone.tee() + + // 2. Set body’s stream to out1. + body.stream = out1 + + // 3. Return a body whose stream is out2 and other members are copied from body. + return { + stream: finalClone, + length: body.length, + source: body.source + } +} + +async function * consumeBody (body) { + if (body) { + if (isUint8Array(body)) { + yield body + } else { + const stream = body.stream + + if (util.isDisturbed(stream)) { + throw new TypeError('The body has already been consumed.') + } + + if (stream.locked) { + throw new TypeError('The stream is locked.') + } + + // Compat. + stream[kBodyUsed] = true + + yield * stream + } + } +} + +function throwIfAborted (state) { + if (state.aborted) { + throw new DOMException('The operation was aborted.', 'AbortError') + } +} + +function bodyMixinMethods (instance) { + const methods = { + blob () { + // The blob() method steps are to return the result of + // running consume body with this and the following step + // given a byte sequence bytes: return a Blob whose + // contents are bytes and whose type attribute is this’s + // MIME type. + return specConsumeBody(this, (bytes) => { + let mimeType = bodyMimeType(this) + + if (mimeType === 'failure') { + mimeType = '' + } else if (mimeType) { + mimeType = serializeAMimeType(mimeType) + } + + // Return a Blob whose contents are bytes and type attribute + // is mimeType. + return new Blob([bytes], { type: mimeType }) + }, instance) + }, + + arrayBuffer () { + // The arrayBuffer() method steps are to return the result + // of running consume body with this and the following step + // given a byte sequence bytes: return a new ArrayBuffer + // whose contents are bytes. + return specConsumeBody(this, (bytes) => { + return new Uint8Array(bytes).buffer + }, instance) + }, + + text () { + // The text() method steps are to return the result of running + // consume body with this and UTF-8 decode. + return specConsumeBody(this, utf8DecodeBytes, instance) + }, + + json () { + // The json() method steps are to return the result of running + // consume body with this and parse JSON from bytes. + return specConsumeBody(this, parseJSONFromBytes, instance) + }, + + async formData () { + webidl.brandCheck(this, instance) + + throwIfAborted(this[kState]) + + const contentType = this.headers.get('Content-Type') + + // If mimeType’s essence is "multipart/form-data", then: + if (/multipart\/form-data/.test(contentType)) { + const headers = {} + for (const [key, value] of this.headers) headers[key.toLowerCase()] = value + + const responseFormData = new FormData() + + let busboy + + try { + busboy = new Busboy({ + headers, + preservePath: true + }) + } catch (err) { + throw new DOMException(`${err}`, 'AbortError') + } + + busboy.on('field', (name, value) => { + responseFormData.append(name, value) + }) + busboy.on('file', (name, value, filename, encoding, mimeType) => { + const chunks = [] + + if (encoding === 'base64' || encoding.toLowerCase() === 'base64') { + let base64chunk = '' + + value.on('data', (chunk) => { + base64chunk += chunk.toString().replace(/[\r\n]/gm, '') + + const end = base64chunk.length - base64chunk.length % 4 + chunks.push(Buffer.from(base64chunk.slice(0, end), 'base64')) + + base64chunk = base64chunk.slice(end) + }) + value.on('end', () => { + chunks.push(Buffer.from(base64chunk, 'base64')) + responseFormData.append(name, new File(chunks, filename, { type: mimeType })) + }) + } else { + value.on('data', (chunk) => { + chunks.push(chunk) + }) + value.on('end', () => { + responseFormData.append(name, new File(chunks, filename, { type: mimeType })) + }) + } + }) + + const busboyResolve = new Promise((resolve, reject) => { + busboy.on('finish', resolve) + busboy.on('error', (err) => reject(new TypeError(err))) + }) + + if (this.body !== null) for await (const chunk of consumeBody(this[kState].body)) busboy.write(chunk) + busboy.end() + await busboyResolve + + return responseFormData + } else if (/application\/x-www-form-urlencoded/.test(contentType)) { + // Otherwise, if mimeType’s essence is "application/x-www-form-urlencoded", then: + + // 1. Let entries be the result of parsing bytes. + let entries + try { + let text = '' + // application/x-www-form-urlencoded parser will keep the BOM. + // https://url.spec.whatwg.org/#concept-urlencoded-parser + // Note that streaming decoder is stateful and cannot be reused + const streamingDecoder = new TextDecoder('utf-8', { ignoreBOM: true }) + + for await (const chunk of consumeBody(this[kState].body)) { + if (!isUint8Array(chunk)) { + throw new TypeError('Expected Uint8Array chunk') + } + text += streamingDecoder.decode(chunk, { stream: true }) + } + text += streamingDecoder.decode() + entries = new URLSearchParams(text) + } catch (err) { + // istanbul ignore next: Unclear when new URLSearchParams can fail on a string. + // 2. If entries is failure, then throw a TypeError. + throw Object.assign(new TypeError(), { cause: err }) + } + + // 3. Return a new FormData object whose entries are entries. + const formData = new FormData() + for (const [name, value] of entries) { + formData.append(name, value) + } + return formData + } else { + // Wait a tick before checking if the request has been aborted. + // Otherwise, a TypeError can be thrown when an AbortError should. + await Promise.resolve() + + throwIfAborted(this[kState]) + + // Otherwise, throw a TypeError. + throw webidl.errors.exception({ + header: `${instance.name}.formData`, + message: 'Could not parse content as FormData.' + }) + } + } + } + + return methods +} + +function mixinBody (prototype) { + Object.assign(prototype.prototype, bodyMixinMethods(prototype)) +} + +/** + * @see https://fetch.spec.whatwg.org/#concept-body-consume-body + * @param {Response|Request} object + * @param {(value: unknown) => unknown} convertBytesToJSValue + * @param {Response|Request} instance + */ +async function specConsumeBody (object, convertBytesToJSValue, instance) { + webidl.brandCheck(object, instance) + + throwIfAborted(object[kState]) + + // 1. If object is unusable, then return a promise rejected + // with a TypeError. + if (bodyUnusable(object[kState].body)) { + throw new TypeError('Body is unusable') + } + + // 2. Let promise be a new promise. + const promise = createDeferredPromise() + + // 3. Let errorSteps given error be to reject promise with error. + const errorSteps = (error) => promise.reject(error) + + // 4. Let successSteps given a byte sequence data be to resolve + // promise with the result of running convertBytesToJSValue + // with data. If that threw an exception, then run errorSteps + // with that exception. + const successSteps = (data) => { + try { + promise.resolve(convertBytesToJSValue(data)) + } catch (e) { + errorSteps(e) + } + } + + // 5. If object’s body is null, then run successSteps with an + // empty byte sequence. + if (object[kState].body == null) { + successSteps(new Uint8Array()) + return promise.promise + } + + // 6. Otherwise, fully read object’s body given successSteps, + // errorSteps, and object’s relevant global object. + await fullyReadBody(object[kState].body, successSteps, errorSteps) + + // 7. Return promise. + return promise.promise +} + +// https://fetch.spec.whatwg.org/#body-unusable +function bodyUnusable (body) { + // An object including the Body interface mixin is + // said to be unusable if its body is non-null and + // its body’s stream is disturbed or locked. + return body != null && (body.stream.locked || util.isDisturbed(body.stream)) +} + +/** + * @see https://encoding.spec.whatwg.org/#utf-8-decode + * @param {Buffer} buffer + */ +function utf8DecodeBytes (buffer) { + if (buffer.length === 0) { + return '' + } + + // 1. Let buffer be the result of peeking three bytes from + // ioQueue, converted to a byte sequence. + + // 2. If buffer is 0xEF 0xBB 0xBF, then read three + // bytes from ioQueue. (Do nothing with those bytes.) + if (buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) { + buffer = buffer.subarray(3) + } + + // 3. Process a queue with an instance of UTF-8’s + // decoder, ioQueue, output, and "replacement". + const output = textDecoder.decode(buffer) + + // 4. Return output. + return output +} + +/** + * @see https://infra.spec.whatwg.org/#parse-json-bytes-to-a-javascript-value + * @param {Uint8Array} bytes + */ +function parseJSONFromBytes (bytes) { + return JSON.parse(utf8DecodeBytes(bytes)) +} + +/** + * @see https://fetch.spec.whatwg.org/#concept-body-mime-type + * @param {import('./response').Response|import('./request').Request} object + */ +function bodyMimeType (object) { + const { headersList } = object[kState] + const contentType = headersList.get('content-type') + + if (contentType === null) { + return 'failure' + } + + return parseMIMEType(contentType) +} + +module.exports = { + extractBody, + safelyExtractBody, + cloneBody, + mixinBody +} + + +/***/ }), + +/***/ 7326: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { MessageChannel, receiveMessageOnPort } = __nccwpck_require__(8167) + +const corsSafeListedMethods = ['GET', 'HEAD', 'POST'] +const corsSafeListedMethodsSet = new Set(corsSafeListedMethods) + +const nullBodyStatus = [101, 204, 205, 304] + +const redirectStatus = [301, 302, 303, 307, 308] +const redirectStatusSet = new Set(redirectStatus) + +// https://fetch.spec.whatwg.org/#block-bad-port +const badPorts = [ + '1', '7', '9', '11', '13', '15', '17', '19', '20', '21', '22', '23', '25', '37', '42', '43', '53', '69', '77', '79', + '87', '95', '101', '102', '103', '104', '109', '110', '111', '113', '115', '117', '119', '123', '135', '137', + '139', '143', '161', '179', '389', '427', '465', '512', '513', '514', '515', '526', '530', '531', '532', + '540', '548', '554', '556', '563', '587', '601', '636', '989', '990', '993', '995', '1719', '1720', '1723', + '2049', '3659', '4045', '5060', '5061', '6000', '6566', '6665', '6666', '6667', '6668', '6669', '6697', + '10080' +] + +const badPortsSet = new Set(badPorts) + +// https://w3c.github.io/webappsec-referrer-policy/#referrer-policies +const referrerPolicy = [ + '', + 'no-referrer', + 'no-referrer-when-downgrade', + 'same-origin', + 'origin', + 'strict-origin', + 'origin-when-cross-origin', + 'strict-origin-when-cross-origin', + 'unsafe-url' +] +const referrerPolicySet = new Set(referrerPolicy) + +const requestRedirect = ['follow', 'manual', 'error'] + +const safeMethods = ['GET', 'HEAD', 'OPTIONS', 'TRACE'] +const safeMethodsSet = new Set(safeMethods) + +const requestMode = ['navigate', 'same-origin', 'no-cors', 'cors'] + +const requestCredentials = ['omit', 'same-origin', 'include'] + +const requestCache = [ + 'default', + 'no-store', + 'reload', + 'no-cache', + 'force-cache', + 'only-if-cached' +] + +// https://fetch.spec.whatwg.org/#request-body-header-name +const requestBodyHeader = [ + 'content-encoding', + 'content-language', + 'content-location', + 'content-type', + // See https://github.com/nodejs/undici/issues/2021 + // 'Content-Length' is a forbidden header name, which is typically + // removed in the Headers implementation. However, undici doesn't + // filter out headers, so we add it here. + 'content-length' +] + +// https://fetch.spec.whatwg.org/#enumdef-requestduplex +const requestDuplex = [ + 'half' +] + +// http://fetch.spec.whatwg.org/#forbidden-method +const forbiddenMethods = ['CONNECT', 'TRACE', 'TRACK'] +const forbiddenMethodsSet = new Set(forbiddenMethods) + +const subresource = [ + 'audio', + 'audioworklet', + 'font', + 'image', + 'manifest', + 'paintworklet', + 'script', + 'style', + 'track', + 'video', + 'xslt', + '' +] +const subresourceSet = new Set(subresource) + +/** @type {globalThis['DOMException']} */ +const DOMException = globalThis.DOMException ?? (() => { + // DOMException was only made a global in Node v17.0.0, + // but fetch supports >= v16.8. + try { + atob('~') + } catch (err) { + return Object.getPrototypeOf(err).constructor + } +})() + +let channel + +/** @type {globalThis['structuredClone']} */ +const structuredClone = + globalThis.structuredClone ?? + // https://github.com/nodejs/node/blob/b27ae24dcc4251bad726d9d84baf678d1f707fed/lib/internal/structured_clone.js + // structuredClone was added in v17.0.0, but fetch supports v16.8 + function structuredClone (value, options = undefined) { + if (arguments.length === 0) { + throw new TypeError('missing argument') + } + + if (!channel) { + channel = new MessageChannel() + } + channel.port1.unref() + channel.port2.unref() + channel.port1.postMessage(value, options?.transfer) + return receiveMessageOnPort(channel.port2).message + } + +module.exports = { + DOMException, + structuredClone, + subresource, + forbiddenMethods, + requestBodyHeader, + referrerPolicy, + requestRedirect, + requestMode, + requestCredentials, + requestCache, + redirectStatus, + corsSafeListedMethods, + nullBodyStatus, + safeMethods, + badPorts, + requestDuplex, + subresourceSet, + badPortsSet, + redirectStatusSet, + corsSafeListedMethodsSet, + safeMethodsSet, + forbiddenMethodsSet, + referrerPolicySet +} + + +/***/ }), + +/***/ 4322: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const assert = __nccwpck_require__(2613) +const { atob } = __nccwpck_require__(181) +const { isomorphicDecode } = __nccwpck_require__(5523) + +const encoder = new TextEncoder() + +/** + * @see https://mimesniff.spec.whatwg.org/#http-token-code-point + */ +const HTTP_TOKEN_CODEPOINTS = /^[!#$%&'*+-.^_|~A-Za-z0-9]+$/ +const HTTP_WHITESPACE_REGEX = /(\u000A|\u000D|\u0009|\u0020)/ // eslint-disable-line +/** + * @see https://mimesniff.spec.whatwg.org/#http-quoted-string-token-code-point + */ +const HTTP_QUOTED_STRING_TOKENS = /[\u0009|\u0020-\u007E|\u0080-\u00FF]/ // eslint-disable-line + +// https://fetch.spec.whatwg.org/#data-url-processor +/** @param {URL} dataURL */ +function dataURLProcessor (dataURL) { + // 1. Assert: dataURL’s scheme is "data". + assert(dataURL.protocol === 'data:') + + // 2. Let input be the result of running the URL + // serializer on dataURL with exclude fragment + // set to true. + let input = URLSerializer(dataURL, true) + + // 3. Remove the leading "data:" string from input. + input = input.slice(5) + + // 4. Let position point at the start of input. + const position = { position: 0 } + + // 5. Let mimeType be the result of collecting a + // sequence of code points that are not equal + // to U+002C (,), given position. + let mimeType = collectASequenceOfCodePointsFast( + ',', + input, + position + ) + + // 6. Strip leading and trailing ASCII whitespace + // from mimeType. + // Undici implementation note: we need to store the + // length because if the mimetype has spaces removed, + // the wrong amount will be sliced from the input in + // step #9 + const mimeTypeLength = mimeType.length + mimeType = removeASCIIWhitespace(mimeType, true, true) + + // 7. If position is past the end of input, then + // return failure + if (position.position >= input.length) { + return 'failure' + } + + // 8. Advance position by 1. + position.position++ + + // 9. Let encodedBody be the remainder of input. + const encodedBody = input.slice(mimeTypeLength + 1) + + // 10. Let body be the percent-decoding of encodedBody. + let body = stringPercentDecode(encodedBody) + + // 11. If mimeType ends with U+003B (;), followed by + // zero or more U+0020 SPACE, followed by an ASCII + // case-insensitive match for "base64", then: + if (/;(\u0020){0,}base64$/i.test(mimeType)) { + // 1. Let stringBody be the isomorphic decode of body. + const stringBody = isomorphicDecode(body) + + // 2. Set body to the forgiving-base64 decode of + // stringBody. + body = forgivingBase64(stringBody) + + // 3. If body is failure, then return failure. + if (body === 'failure') { + return 'failure' + } + + // 4. Remove the last 6 code points from mimeType. + mimeType = mimeType.slice(0, -6) + + // 5. Remove trailing U+0020 SPACE code points from mimeType, + // if any. + mimeType = mimeType.replace(/(\u0020)+$/, '') + + // 6. Remove the last U+003B (;) code point from mimeType. + mimeType = mimeType.slice(0, -1) + } + + // 12. If mimeType starts with U+003B (;), then prepend + // "text/plain" to mimeType. + if (mimeType.startsWith(';')) { + mimeType = 'text/plain' + mimeType + } + + // 13. Let mimeTypeRecord be the result of parsing + // mimeType. + let mimeTypeRecord = parseMIMEType(mimeType) + + // 14. If mimeTypeRecord is failure, then set + // mimeTypeRecord to text/plain;charset=US-ASCII. + if (mimeTypeRecord === 'failure') { + mimeTypeRecord = parseMIMEType('text/plain;charset=US-ASCII') + } + + // 15. Return a new data: URL struct whose MIME + // type is mimeTypeRecord and body is body. + // https://fetch.spec.whatwg.org/#data-url-struct + return { mimeType: mimeTypeRecord, body } +} + +// https://url.spec.whatwg.org/#concept-url-serializer +/** + * @param {URL} url + * @param {boolean} excludeFragment + */ +function URLSerializer (url, excludeFragment = false) { + if (!excludeFragment) { + return url.href + } + + const href = url.href + const hashLength = url.hash.length + + return hashLength === 0 ? href : href.substring(0, href.length - hashLength) +} + +// https://infra.spec.whatwg.org/#collect-a-sequence-of-code-points +/** + * @param {(char: string) => boolean} condition + * @param {string} input + * @param {{ position: number }} position + */ +function collectASequenceOfCodePoints (condition, input, position) { + // 1. Let result be the empty string. + let result = '' + + // 2. While position doesn’t point past the end of input and the + // code point at position within input meets the condition condition: + while (position.position < input.length && condition(input[position.position])) { + // 1. Append that code point to the end of result. + result += input[position.position] + + // 2. Advance position by 1. + position.position++ + } + + // 3. Return result. + return result +} + +/** + * A faster collectASequenceOfCodePoints that only works when comparing a single character. + * @param {string} char + * @param {string} input + * @param {{ position: number }} position + */ +function collectASequenceOfCodePointsFast (char, input, position) { + const idx = input.indexOf(char, position.position) + const start = position.position + + if (idx === -1) { + position.position = input.length + return input.slice(start) + } + + position.position = idx + return input.slice(start, position.position) +} + +// https://url.spec.whatwg.org/#string-percent-decode +/** @param {string} input */ +function stringPercentDecode (input) { + // 1. Let bytes be the UTF-8 encoding of input. + const bytes = encoder.encode(input) + + // 2. Return the percent-decoding of bytes. + return percentDecode(bytes) +} + +// https://url.spec.whatwg.org/#percent-decode +/** @param {Uint8Array} input */ +function percentDecode (input) { + // 1. Let output be an empty byte sequence. + /** @type {number[]} */ + const output = [] + + // 2. For each byte byte in input: + for (let i = 0; i < input.length; i++) { + const byte = input[i] + + // 1. If byte is not 0x25 (%), then append byte to output. + if (byte !== 0x25) { + output.push(byte) + + // 2. Otherwise, if byte is 0x25 (%) and the next two bytes + // after byte in input are not in the ranges + // 0x30 (0) to 0x39 (9), 0x41 (A) to 0x46 (F), + // and 0x61 (a) to 0x66 (f), all inclusive, append byte + // to output. + } else if ( + byte === 0x25 && + !/^[0-9A-Fa-f]{2}$/i.test(String.fromCharCode(input[i + 1], input[i + 2])) + ) { + output.push(0x25) + + // 3. Otherwise: + } else { + // 1. Let bytePoint be the two bytes after byte in input, + // decoded, and then interpreted as hexadecimal number. + const nextTwoBytes = String.fromCharCode(input[i + 1], input[i + 2]) + const bytePoint = Number.parseInt(nextTwoBytes, 16) + + // 2. Append a byte whose value is bytePoint to output. + output.push(bytePoint) + + // 3. Skip the next two bytes in input. + i += 2 + } + } + + // 3. Return output. + return Uint8Array.from(output) +} + +// https://mimesniff.spec.whatwg.org/#parse-a-mime-type +/** @param {string} input */ +function parseMIMEType (input) { + // 1. Remove any leading and trailing HTTP whitespace + // from input. + input = removeHTTPWhitespace(input, true, true) + + // 2. Let position be a position variable for input, + // initially pointing at the start of input. + const position = { position: 0 } + + // 3. Let type be the result of collecting a sequence + // of code points that are not U+002F (/) from + // input, given position. + const type = collectASequenceOfCodePointsFast( + '/', + input, + position + ) + + // 4. If type is the empty string or does not solely + // contain HTTP token code points, then return failure. + // https://mimesniff.spec.whatwg.org/#http-token-code-point + if (type.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(type)) { + return 'failure' + } + + // 5. If position is past the end of input, then return + // failure + if (position.position > input.length) { + return 'failure' + } + + // 6. Advance position by 1. (This skips past U+002F (/).) + position.position++ + + // 7. Let subtype be the result of collecting a sequence of + // code points that are not U+003B (;) from input, given + // position. + let subtype = collectASequenceOfCodePointsFast( + ';', + input, + position + ) + + // 8. Remove any trailing HTTP whitespace from subtype. + subtype = removeHTTPWhitespace(subtype, false, true) + + // 9. If subtype is the empty string or does not solely + // contain HTTP token code points, then return failure. + if (subtype.length === 0 || !HTTP_TOKEN_CODEPOINTS.test(subtype)) { + return 'failure' + } + + const typeLowercase = type.toLowerCase() + const subtypeLowercase = subtype.toLowerCase() + + // 10. Let mimeType be a new MIME type record whose type + // is type, in ASCII lowercase, and subtype is subtype, + // in ASCII lowercase. + // https://mimesniff.spec.whatwg.org/#mime-type + const mimeType = { + type: typeLowercase, + subtype: subtypeLowercase, + /** @type {Map} */ + parameters: new Map(), + // https://mimesniff.spec.whatwg.org/#mime-type-essence + essence: `${typeLowercase}/${subtypeLowercase}` + } + + // 11. While position is not past the end of input: + while (position.position < input.length) { + // 1. Advance position by 1. (This skips past U+003B (;).) + position.position++ + + // 2. Collect a sequence of code points that are HTTP + // whitespace from input given position. + collectASequenceOfCodePoints( + // https://fetch.spec.whatwg.org/#http-whitespace + char => HTTP_WHITESPACE_REGEX.test(char), + input, + position + ) + + // 3. Let parameterName be the result of collecting a + // sequence of code points that are not U+003B (;) + // or U+003D (=) from input, given position. + let parameterName = collectASequenceOfCodePoints( + (char) => char !== ';' && char !== '=', + input, + position + ) + + // 4. Set parameterName to parameterName, in ASCII + // lowercase. + parameterName = parameterName.toLowerCase() + + // 5. If position is not past the end of input, then: + if (position.position < input.length) { + // 1. If the code point at position within input is + // U+003B (;), then continue. + if (input[position.position] === ';') { + continue + } + + // 2. Advance position by 1. (This skips past U+003D (=).) + position.position++ + } + + // 6. If position is past the end of input, then break. + if (position.position > input.length) { + break + } + + // 7. Let parameterValue be null. + let parameterValue = null + + // 8. If the code point at position within input is + // U+0022 ("), then: + if (input[position.position] === '"') { + // 1. Set parameterValue to the result of collecting + // an HTTP quoted string from input, given position + // and the extract-value flag. + parameterValue = collectAnHTTPQuotedString(input, position, true) + + // 2. Collect a sequence of code points that are not + // U+003B (;) from input, given position. + collectASequenceOfCodePointsFast( + ';', + input, + position + ) + + // 9. Otherwise: + } else { + // 1. Set parameterValue to the result of collecting + // a sequence of code points that are not U+003B (;) + // from input, given position. + parameterValue = collectASequenceOfCodePointsFast( + ';', + input, + position + ) + + // 2. Remove any trailing HTTP whitespace from parameterValue. + parameterValue = removeHTTPWhitespace(parameterValue, false, true) + + // 3. If parameterValue is the empty string, then continue. + if (parameterValue.length === 0) { + continue + } + } + + // 10. If all of the following are true + // - parameterName is not the empty string + // - parameterName solely contains HTTP token code points + // - parameterValue solely contains HTTP quoted-string token code points + // - mimeType’s parameters[parameterName] does not exist + // then set mimeType’s parameters[parameterName] to parameterValue. + if ( + parameterName.length !== 0 && + HTTP_TOKEN_CODEPOINTS.test(parameterName) && + (parameterValue.length === 0 || HTTP_QUOTED_STRING_TOKENS.test(parameterValue)) && + !mimeType.parameters.has(parameterName) + ) { + mimeType.parameters.set(parameterName, parameterValue) + } + } + + // 12. Return mimeType. + return mimeType +} + +// https://infra.spec.whatwg.org/#forgiving-base64-decode +/** @param {string} data */ +function forgivingBase64 (data) { + // 1. Remove all ASCII whitespace from data. + data = data.replace(/[\u0009\u000A\u000C\u000D\u0020]/g, '') // eslint-disable-line + + // 2. If data’s code point length divides by 4 leaving + // no remainder, then: + if (data.length % 4 === 0) { + // 1. If data ends with one or two U+003D (=) code points, + // then remove them from data. + data = data.replace(/=?=$/, '') + } + + // 3. If data’s code point length divides by 4 leaving + // a remainder of 1, then return failure. + if (data.length % 4 === 1) { + return 'failure' + } + + // 4. If data contains a code point that is not one of + // U+002B (+) + // U+002F (/) + // ASCII alphanumeric + // then return failure. + if (/[^+/0-9A-Za-z]/.test(data)) { + return 'failure' + } + + const binary = atob(data) + const bytes = new Uint8Array(binary.length) + + for (let byte = 0; byte < binary.length; byte++) { + bytes[byte] = binary.charCodeAt(byte) + } + + return bytes +} + +// https://fetch.spec.whatwg.org/#collect-an-http-quoted-string +// tests: https://fetch.spec.whatwg.org/#example-http-quoted-string +/** + * @param {string} input + * @param {{ position: number }} position + * @param {boolean?} extractValue + */ +function collectAnHTTPQuotedString (input, position, extractValue) { + // 1. Let positionStart be position. + const positionStart = position.position + + // 2. Let value be the empty string. + let value = '' + + // 3. Assert: the code point at position within input + // is U+0022 ("). + assert(input[position.position] === '"') + + // 4. Advance position by 1. + position.position++ + + // 5. While true: + while (true) { + // 1. Append the result of collecting a sequence of code points + // that are not U+0022 (") or U+005C (\) from input, given + // position, to value. + value += collectASequenceOfCodePoints( + (char) => char !== '"' && char !== '\\', + input, + position + ) + + // 2. If position is past the end of input, then break. + if (position.position >= input.length) { + break + } + + // 3. Let quoteOrBackslash be the code point at position within + // input. + const quoteOrBackslash = input[position.position] + + // 4. Advance position by 1. + position.position++ + + // 5. If quoteOrBackslash is U+005C (\), then: + if (quoteOrBackslash === '\\') { + // 1. If position is past the end of input, then append + // U+005C (\) to value and break. + if (position.position >= input.length) { + value += '\\' + break + } + + // 2. Append the code point at position within input to value. + value += input[position.position] + + // 3. Advance position by 1. + position.position++ + + // 6. Otherwise: + } else { + // 1. Assert: quoteOrBackslash is U+0022 ("). + assert(quoteOrBackslash === '"') + + // 2. Break. + break + } + } + + // 6. If the extract-value flag is set, then return value. + if (extractValue) { + return value + } + + // 7. Return the code points from positionStart to position, + // inclusive, within input. + return input.slice(positionStart, position.position) +} + +/** + * @see https://mimesniff.spec.whatwg.org/#serialize-a-mime-type + */ +function serializeAMimeType (mimeType) { + assert(mimeType !== 'failure') + const { parameters, essence } = mimeType + + // 1. Let serialization be the concatenation of mimeType’s + // type, U+002F (/), and mimeType’s subtype. + let serialization = essence + + // 2. For each name → value of mimeType’s parameters: + for (let [name, value] of parameters.entries()) { + // 1. Append U+003B (;) to serialization. + serialization += ';' + + // 2. Append name to serialization. + serialization += name + + // 3. Append U+003D (=) to serialization. + serialization += '=' + + // 4. If value does not solely contain HTTP token code + // points or value is the empty string, then: + if (!HTTP_TOKEN_CODEPOINTS.test(value)) { + // 1. Precede each occurence of U+0022 (") or + // U+005C (\) in value with U+005C (\). + value = value.replace(/(\\|")/g, '\\$1') + + // 2. Prepend U+0022 (") to value. + value = '"' + value + + // 3. Append U+0022 (") to value. + value += '"' + } + + // 5. Append value to serialization. + serialization += value + } + + // 3. Return serialization. + return serialization +} + +/** + * @see https://fetch.spec.whatwg.org/#http-whitespace + * @param {string} char + */ +function isHTTPWhiteSpace (char) { + return char === '\r' || char === '\n' || char === '\t' || char === ' ' +} + +/** + * @see https://fetch.spec.whatwg.org/#http-whitespace + * @param {string} str + */ +function removeHTTPWhitespace (str, leading = true, trailing = true) { + let lead = 0 + let trail = str.length - 1 + + if (leading) { + for (; lead < str.length && isHTTPWhiteSpace(str[lead]); lead++); + } + + if (trailing) { + for (; trail > 0 && isHTTPWhiteSpace(str[trail]); trail--); + } + + return str.slice(lead, trail + 1) +} + +/** + * @see https://infra.spec.whatwg.org/#ascii-whitespace + * @param {string} char + */ +function isASCIIWhitespace (char) { + return char === '\r' || char === '\n' || char === '\t' || char === '\f' || char === ' ' +} + +/** + * @see https://infra.spec.whatwg.org/#strip-leading-and-trailing-ascii-whitespace + */ +function removeASCIIWhitespace (str, leading = true, trailing = true) { + let lead = 0 + let trail = str.length - 1 + + if (leading) { + for (; lead < str.length && isASCIIWhitespace(str[lead]); lead++); + } + + if (trailing) { + for (; trail > 0 && isASCIIWhitespace(str[trail]); trail--); + } + + return str.slice(lead, trail + 1) +} + +module.exports = { + dataURLProcessor, + URLSerializer, + collectASequenceOfCodePoints, + collectASequenceOfCodePointsFast, + stringPercentDecode, + parseMIMEType, + collectAnHTTPQuotedString, + serializeAMimeType +} + + +/***/ }), + +/***/ 3041: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { Blob, File: NativeFile } = __nccwpck_require__(181) +const { types } = __nccwpck_require__(9023) +const { kState } = __nccwpck_require__(9710) +const { isBlobLike } = __nccwpck_require__(5523) +const { webidl } = __nccwpck_require__(4222) +const { parseMIMEType, serializeAMimeType } = __nccwpck_require__(4322) +const { kEnumerableProperty } = __nccwpck_require__(3440) +const encoder = new TextEncoder() + +class File extends Blob { + constructor (fileBits, fileName, options = {}) { + // The File constructor is invoked with two or three parameters, depending + // on whether the optional dictionary parameter is used. When the File() + // constructor is invoked, user agents must run the following steps: + webidl.argumentLengthCheck(arguments, 2, { header: 'File constructor' }) + + fileBits = webidl.converters['sequence'](fileBits) + fileName = webidl.converters.USVString(fileName) + options = webidl.converters.FilePropertyBag(options) + + // 1. Let bytes be the result of processing blob parts given fileBits and + // options. + // Note: Blob handles this for us + + // 2. Let n be the fileName argument to the constructor. + const n = fileName + + // 3. Process FilePropertyBag dictionary argument by running the following + // substeps: + + // 1. If the type member is provided and is not the empty string, let t + // be set to the type dictionary member. If t contains any characters + // outside the range U+0020 to U+007E, then set t to the empty string + // and return from these substeps. + // 2. Convert every character in t to ASCII lowercase. + let t = options.type + let d + + // eslint-disable-next-line no-labels + substep: { + if (t) { + t = parseMIMEType(t) + + if (t === 'failure') { + t = '' + // eslint-disable-next-line no-labels + break substep + } + + t = serializeAMimeType(t).toLowerCase() + } + + // 3. If the lastModified member is provided, let d be set to the + // lastModified dictionary member. If it is not provided, set d to the + // current date and time represented as the number of milliseconds since + // the Unix Epoch (which is the equivalent of Date.now() [ECMA-262]). + d = options.lastModified + } + + // 4. Return a new File object F such that: + // F refers to the bytes byte sequence. + // F.size is set to the number of total bytes in bytes. + // F.name is set to n. + // F.type is set to t. + // F.lastModified is set to d. + + super(processBlobParts(fileBits, options), { type: t }) + this[kState] = { + name: n, + lastModified: d, + type: t + } + } + + get name () { + webidl.brandCheck(this, File) + + return this[kState].name + } + + get lastModified () { + webidl.brandCheck(this, File) + + return this[kState].lastModified + } + + get type () { + webidl.brandCheck(this, File) + + return this[kState].type + } +} + +class FileLike { + constructor (blobLike, fileName, options = {}) { + // TODO: argument idl type check + + // The File constructor is invoked with two or three parameters, depending + // on whether the optional dictionary parameter is used. When the File() + // constructor is invoked, user agents must run the following steps: + + // 1. Let bytes be the result of processing blob parts given fileBits and + // options. + + // 2. Let n be the fileName argument to the constructor. + const n = fileName + + // 3. Process FilePropertyBag dictionary argument by running the following + // substeps: + + // 1. If the type member is provided and is not the empty string, let t + // be set to the type dictionary member. If t contains any characters + // outside the range U+0020 to U+007E, then set t to the empty string + // and return from these substeps. + // TODO + const t = options.type + + // 2. Convert every character in t to ASCII lowercase. + // TODO + + // 3. If the lastModified member is provided, let d be set to the + // lastModified dictionary member. If it is not provided, set d to the + // current date and time represented as the number of milliseconds since + // the Unix Epoch (which is the equivalent of Date.now() [ECMA-262]). + const d = options.lastModified ?? Date.now() + + // 4. Return a new File object F such that: + // F refers to the bytes byte sequence. + // F.size is set to the number of total bytes in bytes. + // F.name is set to n. + // F.type is set to t. + // F.lastModified is set to d. + + this[kState] = { + blobLike, + name: n, + type: t, + lastModified: d + } + } + + stream (...args) { + webidl.brandCheck(this, FileLike) + + return this[kState].blobLike.stream(...args) + } + + arrayBuffer (...args) { + webidl.brandCheck(this, FileLike) + + return this[kState].blobLike.arrayBuffer(...args) + } + + slice (...args) { + webidl.brandCheck(this, FileLike) + + return this[kState].blobLike.slice(...args) + } + + text (...args) { + webidl.brandCheck(this, FileLike) + + return this[kState].blobLike.text(...args) + } + + get size () { + webidl.brandCheck(this, FileLike) + + return this[kState].blobLike.size + } + + get type () { + webidl.brandCheck(this, FileLike) + + return this[kState].blobLike.type + } + + get name () { + webidl.brandCheck(this, FileLike) + + return this[kState].name + } + + get lastModified () { + webidl.brandCheck(this, FileLike) + + return this[kState].lastModified + } + + get [Symbol.toStringTag] () { + return 'File' + } +} + +Object.defineProperties(File.prototype, { + [Symbol.toStringTag]: { + value: 'File', + configurable: true + }, + name: kEnumerableProperty, + lastModified: kEnumerableProperty +}) + +webidl.converters.Blob = webidl.interfaceConverter(Blob) + +webidl.converters.BlobPart = function (V, opts) { + if (webidl.util.Type(V) === 'Object') { + if (isBlobLike(V)) { + return webidl.converters.Blob(V, { strict: false }) + } + + if ( + ArrayBuffer.isView(V) || + types.isAnyArrayBuffer(V) + ) { + return webidl.converters.BufferSource(V, opts) + } + } + + return webidl.converters.USVString(V, opts) +} + +webidl.converters['sequence'] = webidl.sequenceConverter( + webidl.converters.BlobPart +) + +// https://www.w3.org/TR/FileAPI/#dfn-FilePropertyBag +webidl.converters.FilePropertyBag = webidl.dictionaryConverter([ + { + key: 'lastModified', + converter: webidl.converters['long long'], + get defaultValue () { + return Date.now() + } + }, + { + key: 'type', + converter: webidl.converters.DOMString, + defaultValue: '' + }, + { + key: 'endings', + converter: (value) => { + value = webidl.converters.DOMString(value) + value = value.toLowerCase() + + if (value !== 'native') { + value = 'transparent' + } + + return value + }, + defaultValue: 'transparent' + } +]) + +/** + * @see https://www.w3.org/TR/FileAPI/#process-blob-parts + * @param {(NodeJS.TypedArray|Blob|string)[]} parts + * @param {{ type: string, endings: string }} options + */ +function processBlobParts (parts, options) { + // 1. Let bytes be an empty sequence of bytes. + /** @type {NodeJS.TypedArray[]} */ + const bytes = [] + + // 2. For each element in parts: + for (const element of parts) { + // 1. If element is a USVString, run the following substeps: + if (typeof element === 'string') { + // 1. Let s be element. + let s = element + + // 2. If the endings member of options is "native", set s + // to the result of converting line endings to native + // of element. + if (options.endings === 'native') { + s = convertLineEndingsNative(s) + } + + // 3. Append the result of UTF-8 encoding s to bytes. + bytes.push(encoder.encode(s)) + } else if ( + types.isAnyArrayBuffer(element) || + types.isTypedArray(element) + ) { + // 2. If element is a BufferSource, get a copy of the + // bytes held by the buffer source, and append those + // bytes to bytes. + if (!element.buffer) { // ArrayBuffer + bytes.push(new Uint8Array(element)) + } else { + bytes.push( + new Uint8Array(element.buffer, element.byteOffset, element.byteLength) + ) + } + } else if (isBlobLike(element)) { + // 3. If element is a Blob, append the bytes it represents + // to bytes. + bytes.push(element) + } + } + + // 3. Return bytes. + return bytes +} + +/** + * @see https://www.w3.org/TR/FileAPI/#convert-line-endings-to-native + * @param {string} s + */ +function convertLineEndingsNative (s) { + // 1. Let native line ending be be the code point U+000A LF. + let nativeLineEnding = '\n' + + // 2. If the underlying platform’s conventions are to + // represent newlines as a carriage return and line feed + // sequence, set native line ending to the code point + // U+000D CR followed by the code point U+000A LF. + if (process.platform === 'win32') { + nativeLineEnding = '\r\n' + } + + return s.replace(/\r?\n/g, nativeLineEnding) +} + +// If this function is moved to ./util.js, some tools (such as +// rollup) will warn about circular dependencies. See: +// https://github.com/nodejs/undici/issues/1629 +function isFileLike (object) { + return ( + (NativeFile && object instanceof NativeFile) || + object instanceof File || ( + object && + (typeof object.stream === 'function' || + typeof object.arrayBuffer === 'function') && + object[Symbol.toStringTag] === 'File' + ) + ) +} + +module.exports = { File, FileLike, isFileLike } + + +/***/ }), + +/***/ 3073: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { isBlobLike, toUSVString, makeIterator } = __nccwpck_require__(5523) +const { kState } = __nccwpck_require__(9710) +const { File: UndiciFile, FileLike, isFileLike } = __nccwpck_require__(3041) +const { webidl } = __nccwpck_require__(4222) +const { Blob, File: NativeFile } = __nccwpck_require__(181) + +/** @type {globalThis['File']} */ +const File = NativeFile ?? UndiciFile + +// https://xhr.spec.whatwg.org/#formdata +class FormData { + constructor (form) { + if (form !== undefined) { + throw webidl.errors.conversionFailed({ + prefix: 'FormData constructor', + argument: 'Argument 1', + types: ['undefined'] + }) + } + + this[kState] = [] + } + + append (name, value, filename = undefined) { + webidl.brandCheck(this, FormData) + + webidl.argumentLengthCheck(arguments, 2, { header: 'FormData.append' }) + + if (arguments.length === 3 && !isBlobLike(value)) { + throw new TypeError( + "Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'" + ) + } + + // 1. Let value be value if given; otherwise blobValue. + + name = webidl.converters.USVString(name) + value = isBlobLike(value) + ? webidl.converters.Blob(value, { strict: false }) + : webidl.converters.USVString(value) + filename = arguments.length === 3 + ? webidl.converters.USVString(filename) + : undefined + + // 2. Let entry be the result of creating an entry with + // name, value, and filename if given. + const entry = makeEntry(name, value, filename) + + // 3. Append entry to this’s entry list. + this[kState].push(entry) + } + + delete (name) { + webidl.brandCheck(this, FormData) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.delete' }) + + name = webidl.converters.USVString(name) + + // The delete(name) method steps are to remove all entries whose name + // is name from this’s entry list. + this[kState] = this[kState].filter(entry => entry.name !== name) + } + + get (name) { + webidl.brandCheck(this, FormData) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.get' }) + + name = webidl.converters.USVString(name) + + // 1. If there is no entry whose name is name in this’s entry list, + // then return null. + const idx = this[kState].findIndex((entry) => entry.name === name) + if (idx === -1) { + return null + } + + // 2. Return the value of the first entry whose name is name from + // this’s entry list. + return this[kState][idx].value + } + + getAll (name) { + webidl.brandCheck(this, FormData) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.getAll' }) + + name = webidl.converters.USVString(name) + + // 1. If there is no entry whose name is name in this’s entry list, + // then return the empty list. + // 2. Return the values of all entries whose name is name, in order, + // from this’s entry list. + return this[kState] + .filter((entry) => entry.name === name) + .map((entry) => entry.value) + } + + has (name) { + webidl.brandCheck(this, FormData) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.has' }) + + name = webidl.converters.USVString(name) + + // The has(name) method steps are to return true if there is an entry + // whose name is name in this’s entry list; otherwise false. + return this[kState].findIndex((entry) => entry.name === name) !== -1 + } + + set (name, value, filename = undefined) { + webidl.brandCheck(this, FormData) + + webidl.argumentLengthCheck(arguments, 2, { header: 'FormData.set' }) + + if (arguments.length === 3 && !isBlobLike(value)) { + throw new TypeError( + "Failed to execute 'set' on 'FormData': parameter 2 is not of type 'Blob'" + ) + } + + // The set(name, value) and set(name, blobValue, filename) method steps + // are: + + // 1. Let value be value if given; otherwise blobValue. + + name = webidl.converters.USVString(name) + value = isBlobLike(value) + ? webidl.converters.Blob(value, { strict: false }) + : webidl.converters.USVString(value) + filename = arguments.length === 3 + ? toUSVString(filename) + : undefined + + // 2. Let entry be the result of creating an entry with name, value, and + // filename if given. + const entry = makeEntry(name, value, filename) + + // 3. If there are entries in this’s entry list whose name is name, then + // replace the first such entry with entry and remove the others. + const idx = this[kState].findIndex((entry) => entry.name === name) + if (idx !== -1) { + this[kState] = [ + ...this[kState].slice(0, idx), + entry, + ...this[kState].slice(idx + 1).filter((entry) => entry.name !== name) + ] + } else { + // 4. Otherwise, append entry to this’s entry list. + this[kState].push(entry) + } + } + + entries () { + webidl.brandCheck(this, FormData) + + return makeIterator( + () => this[kState].map(pair => [pair.name, pair.value]), + 'FormData', + 'key+value' + ) + } + + keys () { + webidl.brandCheck(this, FormData) + + return makeIterator( + () => this[kState].map(pair => [pair.name, pair.value]), + 'FormData', + 'key' + ) + } + + values () { + webidl.brandCheck(this, FormData) + + return makeIterator( + () => this[kState].map(pair => [pair.name, pair.value]), + 'FormData', + 'value' + ) + } + + /** + * @param {(value: string, key: string, self: FormData) => void} callbackFn + * @param {unknown} thisArg + */ + forEach (callbackFn, thisArg = globalThis) { + webidl.brandCheck(this, FormData) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FormData.forEach' }) + + if (typeof callbackFn !== 'function') { + throw new TypeError( + "Failed to execute 'forEach' on 'FormData': parameter 1 is not of type 'Function'." + ) + } + + for (const [key, value] of this) { + callbackFn.apply(thisArg, [value, key, this]) + } + } +} + +FormData.prototype[Symbol.iterator] = FormData.prototype.entries + +Object.defineProperties(FormData.prototype, { + [Symbol.toStringTag]: { + value: 'FormData', + configurable: true + } +}) + +/** + * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#create-an-entry + * @param {string} name + * @param {string|Blob} value + * @param {?string} filename + * @returns + */ +function makeEntry (name, value, filename) { + // 1. Set name to the result of converting name into a scalar value string. + // "To convert a string into a scalar value string, replace any surrogates + // with U+FFFD." + // see: https://nodejs.org/dist/latest-v18.x/docs/api/buffer.html#buftostringencoding-start-end + name = Buffer.from(name).toString('utf8') + + // 2. If value is a string, then set value to the result of converting + // value into a scalar value string. + if (typeof value === 'string') { + value = Buffer.from(value).toString('utf8') + } else { + // 3. Otherwise: + + // 1. If value is not a File object, then set value to a new File object, + // representing the same bytes, whose name attribute value is "blob" + if (!isFileLike(value)) { + value = value instanceof Blob + ? new File([value], 'blob', { type: value.type }) + : new FileLike(value, 'blob', { type: value.type }) + } + + // 2. If filename is given, then set value to a new File object, + // representing the same bytes, whose name attribute is filename. + if (filename !== undefined) { + /** @type {FilePropertyBag} */ + const options = { + type: value.type, + lastModified: value.lastModified + } + + value = (NativeFile && value instanceof NativeFile) || value instanceof UndiciFile + ? new File([value], filename, options) + : new FileLike(value, filename, options) + } + } + + // 4. Return an entry whose name is name and whose value is value. + return { name, value } +} + +module.exports = { FormData } + + +/***/ }), + +/***/ 5628: +/***/ ((module) => { + +"use strict"; + + +// In case of breaking changes, increase the version +// number to avoid conflicts. +const globalOrigin = Symbol.for('undici.globalOrigin.1') + +function getGlobalOrigin () { + return globalThis[globalOrigin] +} + +function setGlobalOrigin (newOrigin) { + if (newOrigin === undefined) { + Object.defineProperty(globalThis, globalOrigin, { + value: undefined, + writable: true, + enumerable: false, + configurable: false + }) + + return + } + + const parsedURL = new URL(newOrigin) + + if (parsedURL.protocol !== 'http:' && parsedURL.protocol !== 'https:') { + throw new TypeError(`Only http & https urls are allowed, received ${parsedURL.protocol}`) + } + + Object.defineProperty(globalThis, globalOrigin, { + value: parsedURL, + writable: true, + enumerable: false, + configurable: false + }) +} + +module.exports = { + getGlobalOrigin, + setGlobalOrigin +} + + +/***/ }), + +/***/ 6349: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +// https://github.com/Ethan-Arrowood/undici-fetch + + + +const { kHeadersList, kConstruct } = __nccwpck_require__(6443) +const { kGuard } = __nccwpck_require__(9710) +const { kEnumerableProperty } = __nccwpck_require__(3440) +const { + makeIterator, + isValidHeaderName, + isValidHeaderValue +} = __nccwpck_require__(5523) +const { webidl } = __nccwpck_require__(4222) +const assert = __nccwpck_require__(2613) + +const kHeadersMap = Symbol('headers map') +const kHeadersSortedMap = Symbol('headers map sorted') + +/** + * @param {number} code + */ +function isHTTPWhiteSpaceCharCode (code) { + return code === 0x00a || code === 0x00d || code === 0x009 || code === 0x020 +} + +/** + * @see https://fetch.spec.whatwg.org/#concept-header-value-normalize + * @param {string} potentialValue + */ +function headerValueNormalize (potentialValue) { + // To normalize a byte sequence potentialValue, remove + // any leading and trailing HTTP whitespace bytes from + // potentialValue. + let i = 0; let j = potentialValue.length + + while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(j - 1))) --j + while (j > i && isHTTPWhiteSpaceCharCode(potentialValue.charCodeAt(i))) ++i + + return i === 0 && j === potentialValue.length ? potentialValue : potentialValue.substring(i, j) +} + +function fill (headers, object) { + // To fill a Headers object headers with a given object object, run these steps: + + // 1. If object is a sequence, then for each header in object: + // Note: webidl conversion to array has already been done. + if (Array.isArray(object)) { + for (let i = 0; i < object.length; ++i) { + const header = object[i] + // 1. If header does not contain exactly two items, then throw a TypeError. + if (header.length !== 2) { + throw webidl.errors.exception({ + header: 'Headers constructor', + message: `expected name/value pair to be length 2, found ${header.length}.` + }) + } + + // 2. Append (header’s first item, header’s second item) to headers. + appendHeader(headers, header[0], header[1]) + } + } else if (typeof object === 'object' && object !== null) { + // Note: null should throw + + // 2. Otherwise, object is a record, then for each key → value in object, + // append (key, value) to headers + const keys = Object.keys(object) + for (let i = 0; i < keys.length; ++i) { + appendHeader(headers, keys[i], object[keys[i]]) + } + } else { + throw webidl.errors.conversionFailed({ + prefix: 'Headers constructor', + argument: 'Argument 1', + types: ['sequence>', 'record'] + }) + } +} + +/** + * @see https://fetch.spec.whatwg.org/#concept-headers-append + */ +function appendHeader (headers, name, value) { + // 1. Normalize value. + value = headerValueNormalize(value) + + // 2. If name is not a header name or value is not a + // header value, then throw a TypeError. + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.append', + value: name, + type: 'header name' + }) + } else if (!isValidHeaderValue(value)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.append', + value, + type: 'header value' + }) + } + + // 3. If headers’s guard is "immutable", then throw a TypeError. + // 4. Otherwise, if headers’s guard is "request" and name is a + // forbidden header name, return. + // Note: undici does not implement forbidden header names + if (headers[kGuard] === 'immutable') { + throw new TypeError('immutable') + } else if (headers[kGuard] === 'request-no-cors') { + // 5. Otherwise, if headers’s guard is "request-no-cors": + // TODO + } + + // 6. Otherwise, if headers’s guard is "response" and name is a + // forbidden response-header name, return. + + // 7. Append (name, value) to headers’s header list. + return headers[kHeadersList].append(name, value) + + // 8. If headers’s guard is "request-no-cors", then remove + // privileged no-CORS request headers from headers +} + +class HeadersList { + /** @type {[string, string][]|null} */ + cookies = null + + constructor (init) { + if (init instanceof HeadersList) { + this[kHeadersMap] = new Map(init[kHeadersMap]) + this[kHeadersSortedMap] = init[kHeadersSortedMap] + this.cookies = init.cookies === null ? null : [...init.cookies] + } else { + this[kHeadersMap] = new Map(init) + this[kHeadersSortedMap] = null + } + } + + // https://fetch.spec.whatwg.org/#header-list-contains + contains (name) { + // A header list list contains a header name name if list + // contains a header whose name is a byte-case-insensitive + // match for name. + name = name.toLowerCase() + + return this[kHeadersMap].has(name) + } + + clear () { + this[kHeadersMap].clear() + this[kHeadersSortedMap] = null + this.cookies = null + } + + // https://fetch.spec.whatwg.org/#concept-header-list-append + append (name, value) { + this[kHeadersSortedMap] = null + + // 1. If list contains name, then set name to the first such + // header’s name. + const lowercaseName = name.toLowerCase() + const exists = this[kHeadersMap].get(lowercaseName) + + // 2. Append (name, value) to list. + if (exists) { + const delimiter = lowercaseName === 'cookie' ? '; ' : ', ' + this[kHeadersMap].set(lowercaseName, { + name: exists.name, + value: `${exists.value}${delimiter}${value}` + }) + } else { + this[kHeadersMap].set(lowercaseName, { name, value }) + } + + if (lowercaseName === 'set-cookie') { + this.cookies ??= [] + this.cookies.push(value) + } + } + + // https://fetch.spec.whatwg.org/#concept-header-list-set + set (name, value) { + this[kHeadersSortedMap] = null + const lowercaseName = name.toLowerCase() + + if (lowercaseName === 'set-cookie') { + this.cookies = [value] + } + + // 1. If list contains name, then set the value of + // the first such header to value and remove the + // others. + // 2. Otherwise, append header (name, value) to list. + this[kHeadersMap].set(lowercaseName, { name, value }) + } + + // https://fetch.spec.whatwg.org/#concept-header-list-delete + delete (name) { + this[kHeadersSortedMap] = null + + name = name.toLowerCase() + + if (name === 'set-cookie') { + this.cookies = null + } + + this[kHeadersMap].delete(name) + } + + // https://fetch.spec.whatwg.org/#concept-header-list-get + get (name) { + const value = this[kHeadersMap].get(name.toLowerCase()) + + // 1. If list does not contain name, then return null. + // 2. Return the values of all headers in list whose name + // is a byte-case-insensitive match for name, + // separated from each other by 0x2C 0x20, in order. + return value === undefined ? null : value.value + } + + * [Symbol.iterator] () { + // use the lowercased name + for (const [name, { value }] of this[kHeadersMap]) { + yield [name, value] + } + } + + get entries () { + const headers = {} + + if (this[kHeadersMap].size) { + for (const { name, value } of this[kHeadersMap].values()) { + headers[name] = value + } + } + + return headers + } +} + +// https://fetch.spec.whatwg.org/#headers-class +class Headers { + constructor (init = undefined) { + if (init === kConstruct) { + return + } + this[kHeadersList] = new HeadersList() + + // The new Headers(init) constructor steps are: + + // 1. Set this’s guard to "none". + this[kGuard] = 'none' + + // 2. If init is given, then fill this with init. + if (init !== undefined) { + init = webidl.converters.HeadersInit(init) + fill(this, init) + } + } + + // https://fetch.spec.whatwg.org/#dom-headers-append + append (name, value) { + webidl.brandCheck(this, Headers) + + webidl.argumentLengthCheck(arguments, 2, { header: 'Headers.append' }) + + name = webidl.converters.ByteString(name) + value = webidl.converters.ByteString(value) + + return appendHeader(this, name, value) + } + + // https://fetch.spec.whatwg.org/#dom-headers-delete + delete (name) { + webidl.brandCheck(this, Headers) + + webidl.argumentLengthCheck(arguments, 1, { header: 'Headers.delete' }) + + name = webidl.converters.ByteString(name) + + // 1. If name is not a header name, then throw a TypeError. + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.delete', + value: name, + type: 'header name' + }) + } + + // 2. If this’s guard is "immutable", then throw a TypeError. + // 3. Otherwise, if this’s guard is "request" and name is a + // forbidden header name, return. + // 4. Otherwise, if this’s guard is "request-no-cors", name + // is not a no-CORS-safelisted request-header name, and + // name is not a privileged no-CORS request-header name, + // return. + // 5. Otherwise, if this’s guard is "response" and name is + // a forbidden response-header name, return. + // Note: undici does not implement forbidden header names + if (this[kGuard] === 'immutable') { + throw new TypeError('immutable') + } else if (this[kGuard] === 'request-no-cors') { + // TODO + } + + // 6. If this’s header list does not contain name, then + // return. + if (!this[kHeadersList].contains(name)) { + return + } + + // 7. Delete name from this’s header list. + // 8. If this’s guard is "request-no-cors", then remove + // privileged no-CORS request headers from this. + this[kHeadersList].delete(name) + } + + // https://fetch.spec.whatwg.org/#dom-headers-get + get (name) { + webidl.brandCheck(this, Headers) + + webidl.argumentLengthCheck(arguments, 1, { header: 'Headers.get' }) + + name = webidl.converters.ByteString(name) + + // 1. If name is not a header name, then throw a TypeError. + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.get', + value: name, + type: 'header name' + }) + } + + // 2. Return the result of getting name from this’s header + // list. + return this[kHeadersList].get(name) + } + + // https://fetch.spec.whatwg.org/#dom-headers-has + has (name) { + webidl.brandCheck(this, Headers) + + webidl.argumentLengthCheck(arguments, 1, { header: 'Headers.has' }) + + name = webidl.converters.ByteString(name) + + // 1. If name is not a header name, then throw a TypeError. + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.has', + value: name, + type: 'header name' + }) + } + + // 2. Return true if this’s header list contains name; + // otherwise false. + return this[kHeadersList].contains(name) + } + + // https://fetch.spec.whatwg.org/#dom-headers-set + set (name, value) { + webidl.brandCheck(this, Headers) + + webidl.argumentLengthCheck(arguments, 2, { header: 'Headers.set' }) + + name = webidl.converters.ByteString(name) + value = webidl.converters.ByteString(value) + + // 1. Normalize value. + value = headerValueNormalize(value) + + // 2. If name is not a header name or value is not a + // header value, then throw a TypeError. + if (!isValidHeaderName(name)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.set', + value: name, + type: 'header name' + }) + } else if (!isValidHeaderValue(value)) { + throw webidl.errors.invalidArgument({ + prefix: 'Headers.set', + value, + type: 'header value' + }) + } + + // 3. If this’s guard is "immutable", then throw a TypeError. + // 4. Otherwise, if this’s guard is "request" and name is a + // forbidden header name, return. + // 5. Otherwise, if this’s guard is "request-no-cors" and + // name/value is not a no-CORS-safelisted request-header, + // return. + // 6. Otherwise, if this’s guard is "response" and name is a + // forbidden response-header name, return. + // Note: undici does not implement forbidden header names + if (this[kGuard] === 'immutable') { + throw new TypeError('immutable') + } else if (this[kGuard] === 'request-no-cors') { + // TODO + } + + // 7. Set (name, value) in this’s header list. + // 8. If this’s guard is "request-no-cors", then remove + // privileged no-CORS request headers from this + this[kHeadersList].set(name, value) + } + + // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie + getSetCookie () { + webidl.brandCheck(this, Headers) + + // 1. If this’s header list does not contain `Set-Cookie`, then return « ». + // 2. Return the values of all headers in this’s header list whose name is + // a byte-case-insensitive match for `Set-Cookie`, in order. + + const list = this[kHeadersList].cookies + + if (list) { + return [...list] + } + + return [] + } + + // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine + get [kHeadersSortedMap] () { + if (this[kHeadersList][kHeadersSortedMap]) { + return this[kHeadersList][kHeadersSortedMap] + } + + // 1. Let headers be an empty list of headers with the key being the name + // and value the value. + const headers = [] + + // 2. Let names be the result of convert header names to a sorted-lowercase + // set with all the names of the headers in list. + const names = [...this[kHeadersList]].sort((a, b) => a[0] < b[0] ? -1 : 1) + const cookies = this[kHeadersList].cookies + + // 3. For each name of names: + for (let i = 0; i < names.length; ++i) { + const [name, value] = names[i] + // 1. If name is `set-cookie`, then: + if (name === 'set-cookie') { + // 1. Let values be a list of all values of headers in list whose name + // is a byte-case-insensitive match for name, in order. + + // 2. For each value of values: + // 1. Append (name, value) to headers. + for (let j = 0; j < cookies.length; ++j) { + headers.push([name, cookies[j]]) + } + } else { + // 2. Otherwise: + + // 1. Let value be the result of getting name from list. + + // 2. Assert: value is non-null. + assert(value !== null) + + // 3. Append (name, value) to headers. + headers.push([name, value]) + } + } + + this[kHeadersList][kHeadersSortedMap] = headers + + // 4. Return headers. + return headers + } + + keys () { + webidl.brandCheck(this, Headers) + + if (this[kGuard] === 'immutable') { + const value = this[kHeadersSortedMap] + return makeIterator(() => value, 'Headers', + 'key') + } + + return makeIterator( + () => [...this[kHeadersSortedMap].values()], + 'Headers', + 'key' + ) + } + + values () { + webidl.brandCheck(this, Headers) + + if (this[kGuard] === 'immutable') { + const value = this[kHeadersSortedMap] + return makeIterator(() => value, 'Headers', + 'value') + } + + return makeIterator( + () => [...this[kHeadersSortedMap].values()], + 'Headers', + 'value' + ) + } + + entries () { + webidl.brandCheck(this, Headers) + + if (this[kGuard] === 'immutable') { + const value = this[kHeadersSortedMap] + return makeIterator(() => value, 'Headers', + 'key+value') + } + + return makeIterator( + () => [...this[kHeadersSortedMap].values()], + 'Headers', + 'key+value' + ) + } + + /** + * @param {(value: string, key: string, self: Headers) => void} callbackFn + * @param {unknown} thisArg + */ + forEach (callbackFn, thisArg = globalThis) { + webidl.brandCheck(this, Headers) + + webidl.argumentLengthCheck(arguments, 1, { header: 'Headers.forEach' }) + + if (typeof callbackFn !== 'function') { + throw new TypeError( + "Failed to execute 'forEach' on 'Headers': parameter 1 is not of type 'Function'." + ) + } + + for (const [key, value] of this) { + callbackFn.apply(thisArg, [value, key, this]) + } + } + + [Symbol.for('nodejs.util.inspect.custom')] () { + webidl.brandCheck(this, Headers) + + return this[kHeadersList] + } +} + +Headers.prototype[Symbol.iterator] = Headers.prototype.entries + +Object.defineProperties(Headers.prototype, { + append: kEnumerableProperty, + delete: kEnumerableProperty, + get: kEnumerableProperty, + has: kEnumerableProperty, + set: kEnumerableProperty, + getSetCookie: kEnumerableProperty, + keys: kEnumerableProperty, + values: kEnumerableProperty, + entries: kEnumerableProperty, + forEach: kEnumerableProperty, + [Symbol.iterator]: { enumerable: false }, + [Symbol.toStringTag]: { + value: 'Headers', + configurable: true + } +}) + +webidl.converters.HeadersInit = function (V) { + if (webidl.util.Type(V) === 'Object') { + if (V[Symbol.iterator]) { + return webidl.converters['sequence>'](V) + } + + return webidl.converters['record'](V) + } + + throw webidl.errors.conversionFailed({ + prefix: 'Headers constructor', + argument: 'Argument 1', + types: ['sequence>', 'record'] + }) +} + +module.exports = { + fill, + Headers, + HeadersList +} + + +/***/ }), + +/***/ 2315: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +// https://github.com/Ethan-Arrowood/undici-fetch + + + +const { + Response, + makeNetworkError, + makeAppropriateNetworkError, + filterResponse, + makeResponse +} = __nccwpck_require__(8676) +const { Headers } = __nccwpck_require__(6349) +const { Request, makeRequest } = __nccwpck_require__(5194) +const zlib = __nccwpck_require__(3106) +const { + bytesMatch, + makePolicyContainer, + clonePolicyContainer, + requestBadPort, + TAOCheck, + appendRequestOriginHeader, + responseLocationURL, + requestCurrentURL, + setRequestReferrerPolicyOnRedirect, + tryUpgradeRequestToAPotentiallyTrustworthyURL, + createOpaqueTimingInfo, + appendFetchMetadata, + corsCheck, + crossOriginResourcePolicyCheck, + determineRequestsReferrer, + coarsenedSharedCurrentTime, + createDeferredPromise, + isBlobLike, + sameOrigin, + isCancelled, + isAborted, + isErrorLike, + fullyReadBody, + readableStreamClose, + isomorphicEncode, + urlIsLocal, + urlIsHttpHttpsScheme, + urlHasHttpsScheme +} = __nccwpck_require__(5523) +const { kState, kHeaders, kGuard, kRealm } = __nccwpck_require__(9710) +const assert = __nccwpck_require__(2613) +const { safelyExtractBody } = __nccwpck_require__(8923) +const { + redirectStatusSet, + nullBodyStatus, + safeMethodsSet, + requestBodyHeader, + subresourceSet, + DOMException +} = __nccwpck_require__(7326) +const { kHeadersList } = __nccwpck_require__(6443) +const EE = __nccwpck_require__(4434) +const { Readable, pipeline } = __nccwpck_require__(2203) +const { addAbortListener, isErrored, isReadable, nodeMajor, nodeMinor } = __nccwpck_require__(3440) +const { dataURLProcessor, serializeAMimeType } = __nccwpck_require__(4322) +const { TransformStream } = __nccwpck_require__(3774) +const { getGlobalDispatcher } = __nccwpck_require__(2581) +const { webidl } = __nccwpck_require__(4222) +const { STATUS_CODES } = __nccwpck_require__(8611) +const GET_OR_HEAD = ['GET', 'HEAD'] + +/** @type {import('buffer').resolveObjectURL} */ +let resolveObjectURL +let ReadableStream = globalThis.ReadableStream + +class Fetch extends EE { + constructor (dispatcher) { + super() + + this.dispatcher = dispatcher + this.connection = null + this.dump = false + this.state = 'ongoing' + // 2 terminated listeners get added per request, + // but only 1 gets removed. If there are 20 redirects, + // 21 listeners will be added. + // See https://github.com/nodejs/undici/issues/1711 + // TODO (fix): Find and fix root cause for leaked listener. + this.setMaxListeners(21) + } + + terminate (reason) { + if (this.state !== 'ongoing') { + return + } + + this.state = 'terminated' + this.connection?.destroy(reason) + this.emit('terminated', reason) + } + + // https://fetch.spec.whatwg.org/#fetch-controller-abort + abort (error) { + if (this.state !== 'ongoing') { + return + } + + // 1. Set controller’s state to "aborted". + this.state = 'aborted' + + // 2. Let fallbackError be an "AbortError" DOMException. + // 3. Set error to fallbackError if it is not given. + if (!error) { + error = new DOMException('The operation was aborted.', 'AbortError') + } + + // 4. Let serializedError be StructuredSerialize(error). + // If that threw an exception, catch it, and let + // serializedError be StructuredSerialize(fallbackError). + + // 5. Set controller’s serialized abort reason to serializedError. + this.serializedAbortReason = error + + this.connection?.destroy(error) + this.emit('terminated', error) + } +} + +// https://fetch.spec.whatwg.org/#fetch-method +function fetch (input, init = {}) { + webidl.argumentLengthCheck(arguments, 1, { header: 'globalThis.fetch' }) + + // 1. Let p be a new promise. + const p = createDeferredPromise() + + // 2. Let requestObject be the result of invoking the initial value of + // Request as constructor with input and init as arguments. If this throws + // an exception, reject p with it and return p. + let requestObject + + try { + requestObject = new Request(input, init) + } catch (e) { + p.reject(e) + return p.promise + } + + // 3. Let request be requestObject’s request. + const request = requestObject[kState] + + // 4. If requestObject’s signal’s aborted flag is set, then: + if (requestObject.signal.aborted) { + // 1. Abort the fetch() call with p, request, null, and + // requestObject’s signal’s abort reason. + abortFetch(p, request, null, requestObject.signal.reason) + + // 2. Return p. + return p.promise + } + + // 5. Let globalObject be request’s client’s global object. + const globalObject = request.client.globalObject + + // 6. If globalObject is a ServiceWorkerGlobalScope object, then set + // request’s service-workers mode to "none". + if (globalObject?.constructor?.name === 'ServiceWorkerGlobalScope') { + request.serviceWorkers = 'none' + } + + // 7. Let responseObject be null. + let responseObject = null + + // 8. Let relevantRealm be this’s relevant Realm. + const relevantRealm = null + + // 9. Let locallyAborted be false. + let locallyAborted = false + + // 10. Let controller be null. + let controller = null + + // 11. Add the following abort steps to requestObject’s signal: + addAbortListener( + requestObject.signal, + () => { + // 1. Set locallyAborted to true. + locallyAborted = true + + // 2. Assert: controller is non-null. + assert(controller != null) + + // 3. Abort controller with requestObject’s signal’s abort reason. + controller.abort(requestObject.signal.reason) + + // 4. Abort the fetch() call with p, request, responseObject, + // and requestObject’s signal’s abort reason. + abortFetch(p, request, responseObject, requestObject.signal.reason) + } + ) + + // 12. Let handleFetchDone given response response be to finalize and + // report timing with response, globalObject, and "fetch". + const handleFetchDone = (response) => + finalizeAndReportTiming(response, 'fetch') + + // 13. Set controller to the result of calling fetch given request, + // with processResponseEndOfBody set to handleFetchDone, and processResponse + // given response being these substeps: + + const processResponse = (response) => { + // 1. If locallyAborted is true, terminate these substeps. + if (locallyAborted) { + return Promise.resolve() + } + + // 2. If response’s aborted flag is set, then: + if (response.aborted) { + // 1. Let deserializedError be the result of deserialize a serialized + // abort reason given controller’s serialized abort reason and + // relevantRealm. + + // 2. Abort the fetch() call with p, request, responseObject, and + // deserializedError. + + abortFetch(p, request, responseObject, controller.serializedAbortReason) + return Promise.resolve() + } + + // 3. If response is a network error, then reject p with a TypeError + // and terminate these substeps. + if (response.type === 'error') { + p.reject( + Object.assign(new TypeError('fetch failed'), { cause: response.error }) + ) + return Promise.resolve() + } + + // 4. Set responseObject to the result of creating a Response object, + // given response, "immutable", and relevantRealm. + responseObject = new Response() + responseObject[kState] = response + responseObject[kRealm] = relevantRealm + responseObject[kHeaders][kHeadersList] = response.headersList + responseObject[kHeaders][kGuard] = 'immutable' + responseObject[kHeaders][kRealm] = relevantRealm + + // 5. Resolve p with responseObject. + p.resolve(responseObject) + } + + controller = fetching({ + request, + processResponseEndOfBody: handleFetchDone, + processResponse, + dispatcher: init.dispatcher ?? getGlobalDispatcher() // undici + }) + + // 14. Return p. + return p.promise +} + +// https://fetch.spec.whatwg.org/#finalize-and-report-timing +function finalizeAndReportTiming (response, initiatorType = 'other') { + // 1. If response is an aborted network error, then return. + if (response.type === 'error' && response.aborted) { + return + } + + // 2. If response’s URL list is null or empty, then return. + if (!response.urlList?.length) { + return + } + + // 3. Let originalURL be response’s URL list[0]. + const originalURL = response.urlList[0] + + // 4. Let timingInfo be response’s timing info. + let timingInfo = response.timingInfo + + // 5. Let cacheState be response’s cache state. + let cacheState = response.cacheState + + // 6. If originalURL’s scheme is not an HTTP(S) scheme, then return. + if (!urlIsHttpHttpsScheme(originalURL)) { + return + } + + // 7. If timingInfo is null, then return. + if (timingInfo === null) { + return + } + + // 8. If response’s timing allow passed flag is not set, then: + if (!response.timingAllowPassed) { + // 1. Set timingInfo to a the result of creating an opaque timing info for timingInfo. + timingInfo = createOpaqueTimingInfo({ + startTime: timingInfo.startTime + }) + + // 2. Set cacheState to the empty string. + cacheState = '' + } + + // 9. Set timingInfo’s end time to the coarsened shared current time + // given global’s relevant settings object’s cross-origin isolated + // capability. + // TODO: given global’s relevant settings object’s cross-origin isolated + // capability? + timingInfo.endTime = coarsenedSharedCurrentTime() + + // 10. Set response’s timing info to timingInfo. + response.timingInfo = timingInfo + + // 11. Mark resource timing for timingInfo, originalURL, initiatorType, + // global, and cacheState. + markResourceTiming( + timingInfo, + originalURL, + initiatorType, + globalThis, + cacheState + ) +} + +// https://w3c.github.io/resource-timing/#dfn-mark-resource-timing +function markResourceTiming (timingInfo, originalURL, initiatorType, globalThis, cacheState) { + if (nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 2)) { + performance.markResourceTiming(timingInfo, originalURL.href, initiatorType, globalThis, cacheState) + } +} + +// https://fetch.spec.whatwg.org/#abort-fetch +function abortFetch (p, request, responseObject, error) { + // Note: AbortSignal.reason was added in node v17.2.0 + // which would give us an undefined error to reject with. + // Remove this once node v16 is no longer supported. + if (!error) { + error = new DOMException('The operation was aborted.', 'AbortError') + } + + // 1. Reject promise with error. + p.reject(error) + + // 2. If request’s body is not null and is readable, then cancel request’s + // body with error. + if (request.body != null && isReadable(request.body?.stream)) { + request.body.stream.cancel(error).catch((err) => { + if (err.code === 'ERR_INVALID_STATE') { + // Node bug? + return + } + throw err + }) + } + + // 3. If responseObject is null, then return. + if (responseObject == null) { + return + } + + // 4. Let response be responseObject’s response. + const response = responseObject[kState] + + // 5. If response’s body is not null and is readable, then error response’s + // body with error. + if (response.body != null && isReadable(response.body?.stream)) { + response.body.stream.cancel(error).catch((err) => { + if (err.code === 'ERR_INVALID_STATE') { + // Node bug? + return + } + throw err + }) + } +} + +// https://fetch.spec.whatwg.org/#fetching +function fetching ({ + request, + processRequestBodyChunkLength, + processRequestEndOfBody, + processResponse, + processResponseEndOfBody, + processResponseConsumeBody, + useParallelQueue = false, + dispatcher // undici +}) { + // 1. Let taskDestination be null. + let taskDestination = null + + // 2. Let crossOriginIsolatedCapability be false. + let crossOriginIsolatedCapability = false + + // 3. If request’s client is non-null, then: + if (request.client != null) { + // 1. Set taskDestination to request’s client’s global object. + taskDestination = request.client.globalObject + + // 2. Set crossOriginIsolatedCapability to request’s client’s cross-origin + // isolated capability. + crossOriginIsolatedCapability = + request.client.crossOriginIsolatedCapability + } + + // 4. If useParallelQueue is true, then set taskDestination to the result of + // starting a new parallel queue. + // TODO + + // 5. Let timingInfo be a new fetch timing info whose start time and + // post-redirect start time are the coarsened shared current time given + // crossOriginIsolatedCapability. + const currenTime = coarsenedSharedCurrentTime(crossOriginIsolatedCapability) + const timingInfo = createOpaqueTimingInfo({ + startTime: currenTime + }) + + // 6. Let fetchParams be a new fetch params whose + // request is request, + // timing info is timingInfo, + // process request body chunk length is processRequestBodyChunkLength, + // process request end-of-body is processRequestEndOfBody, + // process response is processResponse, + // process response consume body is processResponseConsumeBody, + // process response end-of-body is processResponseEndOfBody, + // task destination is taskDestination, + // and cross-origin isolated capability is crossOriginIsolatedCapability. + const fetchParams = { + controller: new Fetch(dispatcher), + request, + timingInfo, + processRequestBodyChunkLength, + processRequestEndOfBody, + processResponse, + processResponseConsumeBody, + processResponseEndOfBody, + taskDestination, + crossOriginIsolatedCapability + } + + // 7. If request’s body is a byte sequence, then set request’s body to + // request’s body as a body. + // NOTE: Since fetching is only called from fetch, body should already be + // extracted. + assert(!request.body || request.body.stream) + + // 8. If request’s window is "client", then set request’s window to request’s + // client, if request’s client’s global object is a Window object; otherwise + // "no-window". + if (request.window === 'client') { + // TODO: What if request.client is null? + request.window = + request.client?.globalObject?.constructor?.name === 'Window' + ? request.client + : 'no-window' + } + + // 9. If request’s origin is "client", then set request’s origin to request’s + // client’s origin. + if (request.origin === 'client') { + // TODO: What if request.client is null? + request.origin = request.client?.origin + } + + // 10. If all of the following conditions are true: + // TODO + + // 11. If request’s policy container is "client", then: + if (request.policyContainer === 'client') { + // 1. If request’s client is non-null, then set request’s policy + // container to a clone of request’s client’s policy container. [HTML] + if (request.client != null) { + request.policyContainer = clonePolicyContainer( + request.client.policyContainer + ) + } else { + // 2. Otherwise, set request’s policy container to a new policy + // container. + request.policyContainer = makePolicyContainer() + } + } + + // 12. If request’s header list does not contain `Accept`, then: + if (!request.headersList.contains('accept')) { + // 1. Let value be `*/*`. + const value = '*/*' + + // 2. A user agent should set value to the first matching statement, if + // any, switching on request’s destination: + // "document" + // "frame" + // "iframe" + // `text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8` + // "image" + // `image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5` + // "style" + // `text/css,*/*;q=0.1` + // TODO + + // 3. Append `Accept`/value to request’s header list. + request.headersList.append('accept', value) + } + + // 13. If request’s header list does not contain `Accept-Language`, then + // user agents should append `Accept-Language`/an appropriate value to + // request’s header list. + if (!request.headersList.contains('accept-language')) { + request.headersList.append('accept-language', '*') + } + + // 14. If request’s priority is null, then use request’s initiator and + // destination appropriately in setting request’s priority to a + // user-agent-defined object. + if (request.priority === null) { + // TODO + } + + // 15. If request is a subresource request, then: + if (subresourceSet.has(request.destination)) { + // TODO + } + + // 16. Run main fetch given fetchParams. + mainFetch(fetchParams) + .catch(err => { + fetchParams.controller.terminate(err) + }) + + // 17. Return fetchParam's controller + return fetchParams.controller +} + +// https://fetch.spec.whatwg.org/#concept-main-fetch +async function mainFetch (fetchParams, recursive = false) { + // 1. Let request be fetchParams’s request. + const request = fetchParams.request + + // 2. Let response be null. + let response = null + + // 3. If request’s local-URLs-only flag is set and request’s current URL is + // not local, then set response to a network error. + if (request.localURLsOnly && !urlIsLocal(requestCurrentURL(request))) { + response = makeNetworkError('local URLs only') + } + + // 4. Run report Content Security Policy violations for request. + // TODO + + // 5. Upgrade request to a potentially trustworthy URL, if appropriate. + tryUpgradeRequestToAPotentiallyTrustworthyURL(request) + + // 6. If should request be blocked due to a bad port, should fetching request + // be blocked as mixed content, or should request be blocked by Content + // Security Policy returns blocked, then set response to a network error. + if (requestBadPort(request) === 'blocked') { + response = makeNetworkError('bad port') + } + // TODO: should fetching request be blocked as mixed content? + // TODO: should request be blocked by Content Security Policy? + + // 7. If request’s referrer policy is the empty string, then set request’s + // referrer policy to request’s policy container’s referrer policy. + if (request.referrerPolicy === '') { + request.referrerPolicy = request.policyContainer.referrerPolicy + } + + // 8. If request’s referrer is not "no-referrer", then set request’s + // referrer to the result of invoking determine request’s referrer. + if (request.referrer !== 'no-referrer') { + request.referrer = determineRequestsReferrer(request) + } + + // 9. Set request’s current URL’s scheme to "https" if all of the following + // conditions are true: + // - request’s current URL’s scheme is "http" + // - request’s current URL’s host is a domain + // - Matching request’s current URL’s host per Known HSTS Host Domain Name + // Matching results in either a superdomain match with an asserted + // includeSubDomains directive or a congruent match (with or without an + // asserted includeSubDomains directive). [HSTS] + // TODO + + // 10. If recursive is false, then run the remaining steps in parallel. + // TODO + + // 11. If response is null, then set response to the result of running + // the steps corresponding to the first matching statement: + if (response === null) { + response = await (async () => { + const currentURL = requestCurrentURL(request) + + if ( + // - request’s current URL’s origin is same origin with request’s origin, + // and request’s response tainting is "basic" + (sameOrigin(currentURL, request.url) && request.responseTainting === 'basic') || + // request’s current URL’s scheme is "data" + (currentURL.protocol === 'data:') || + // - request’s mode is "navigate" or "websocket" + (request.mode === 'navigate' || request.mode === 'websocket') + ) { + // 1. Set request’s response tainting to "basic". + request.responseTainting = 'basic' + + // 2. Return the result of running scheme fetch given fetchParams. + return await schemeFetch(fetchParams) + } + + // request’s mode is "same-origin" + if (request.mode === 'same-origin') { + // 1. Return a network error. + return makeNetworkError('request mode cannot be "same-origin"') + } + + // request’s mode is "no-cors" + if (request.mode === 'no-cors') { + // 1. If request’s redirect mode is not "follow", then return a network + // error. + if (request.redirect !== 'follow') { + return makeNetworkError( + 'redirect mode cannot be "follow" for "no-cors" request' + ) + } + + // 2. Set request’s response tainting to "opaque". + request.responseTainting = 'opaque' + + // 3. Return the result of running scheme fetch given fetchParams. + return await schemeFetch(fetchParams) + } + + // request’s current URL’s scheme is not an HTTP(S) scheme + if (!urlIsHttpHttpsScheme(requestCurrentURL(request))) { + // Return a network error. + return makeNetworkError('URL scheme must be a HTTP(S) scheme') + } + + // - request’s use-CORS-preflight flag is set + // - request’s unsafe-request flag is set and either request’s method is + // not a CORS-safelisted method or CORS-unsafe request-header names with + // request’s header list is not empty + // 1. Set request’s response tainting to "cors". + // 2. Let corsWithPreflightResponse be the result of running HTTP fetch + // given fetchParams and true. + // 3. If corsWithPreflightResponse is a network error, then clear cache + // entries using request. + // 4. Return corsWithPreflightResponse. + // TODO + + // Otherwise + // 1. Set request’s response tainting to "cors". + request.responseTainting = 'cors' + + // 2. Return the result of running HTTP fetch given fetchParams. + return await httpFetch(fetchParams) + })() + } + + // 12. If recursive is true, then return response. + if (recursive) { + return response + } + + // 13. If response is not a network error and response is not a filtered + // response, then: + if (response.status !== 0 && !response.internalResponse) { + // If request’s response tainting is "cors", then: + if (request.responseTainting === 'cors') { + // 1. Let headerNames be the result of extracting header list values + // given `Access-Control-Expose-Headers` and response’s header list. + // TODO + // 2. If request’s credentials mode is not "include" and headerNames + // contains `*`, then set response’s CORS-exposed header-name list to + // all unique header names in response’s header list. + // TODO + // 3. Otherwise, if headerNames is not null or failure, then set + // response’s CORS-exposed header-name list to headerNames. + // TODO + } + + // Set response to the following filtered response with response as its + // internal response, depending on request’s response tainting: + if (request.responseTainting === 'basic') { + response = filterResponse(response, 'basic') + } else if (request.responseTainting === 'cors') { + response = filterResponse(response, 'cors') + } else if (request.responseTainting === 'opaque') { + response = filterResponse(response, 'opaque') + } else { + assert(false) + } + } + + // 14. Let internalResponse be response, if response is a network error, + // and response’s internal response otherwise. + let internalResponse = + response.status === 0 ? response : response.internalResponse + + // 15. If internalResponse’s URL list is empty, then set it to a clone of + // request’s URL list. + if (internalResponse.urlList.length === 0) { + internalResponse.urlList.push(...request.urlList) + } + + // 16. If request’s timing allow failed flag is unset, then set + // internalResponse’s timing allow passed flag. + if (!request.timingAllowFailed) { + response.timingAllowPassed = true + } + + // 17. If response is not a network error and any of the following returns + // blocked + // - should internalResponse to request be blocked as mixed content + // - should internalResponse to request be blocked by Content Security Policy + // - should internalResponse to request be blocked due to its MIME type + // - should internalResponse to request be blocked due to nosniff + // TODO + + // 18. If response’s type is "opaque", internalResponse’s status is 206, + // internalResponse’s range-requested flag is set, and request’s header + // list does not contain `Range`, then set response and internalResponse + // to a network error. + if ( + response.type === 'opaque' && + internalResponse.status === 206 && + internalResponse.rangeRequested && + !request.headers.contains('range') + ) { + response = internalResponse = makeNetworkError() + } + + // 19. If response is not a network error and either request’s method is + // `HEAD` or `CONNECT`, or internalResponse’s status is a null body status, + // set internalResponse’s body to null and disregard any enqueuing toward + // it (if any). + if ( + response.status !== 0 && + (request.method === 'HEAD' || + request.method === 'CONNECT' || + nullBodyStatus.includes(internalResponse.status)) + ) { + internalResponse.body = null + fetchParams.controller.dump = true + } + + // 20. If request’s integrity metadata is not the empty string, then: + if (request.integrity) { + // 1. Let processBodyError be this step: run fetch finale given fetchParams + // and a network error. + const processBodyError = (reason) => + fetchFinale(fetchParams, makeNetworkError(reason)) + + // 2. If request’s response tainting is "opaque", or response’s body is null, + // then run processBodyError and abort these steps. + if (request.responseTainting === 'opaque' || response.body == null) { + processBodyError(response.error) + return + } + + // 3. Let processBody given bytes be these steps: + const processBody = (bytes) => { + // 1. If bytes do not match request’s integrity metadata, + // then run processBodyError and abort these steps. [SRI] + if (!bytesMatch(bytes, request.integrity)) { + processBodyError('integrity mismatch') + return + } + + // 2. Set response’s body to bytes as a body. + response.body = safelyExtractBody(bytes)[0] + + // 3. Run fetch finale given fetchParams and response. + fetchFinale(fetchParams, response) + } + + // 4. Fully read response’s body given processBody and processBodyError. + await fullyReadBody(response.body, processBody, processBodyError) + } else { + // 21. Otherwise, run fetch finale given fetchParams and response. + fetchFinale(fetchParams, response) + } +} + +// https://fetch.spec.whatwg.org/#concept-scheme-fetch +// given a fetch params fetchParams +function schemeFetch (fetchParams) { + // Note: since the connection is destroyed on redirect, which sets fetchParams to a + // cancelled state, we do not want this condition to trigger *unless* there have been + // no redirects. See https://github.com/nodejs/undici/issues/1776 + // 1. If fetchParams is canceled, then return the appropriate network error for fetchParams. + if (isCancelled(fetchParams) && fetchParams.request.redirectCount === 0) { + return Promise.resolve(makeAppropriateNetworkError(fetchParams)) + } + + // 2. Let request be fetchParams’s request. + const { request } = fetchParams + + const { protocol: scheme } = requestCurrentURL(request) + + // 3. Switch on request’s current URL’s scheme and run the associated steps: + switch (scheme) { + case 'about:': { + // If request’s current URL’s path is the string "blank", then return a new response + // whose status message is `OK`, header list is « (`Content-Type`, `text/html;charset=utf-8`) », + // and body is the empty byte sequence as a body. + + // Otherwise, return a network error. + return Promise.resolve(makeNetworkError('about scheme is not supported')) + } + case 'blob:': { + if (!resolveObjectURL) { + resolveObjectURL = (__nccwpck_require__(181).resolveObjectURL) + } + + // 1. Let blobURLEntry be request’s current URL’s blob URL entry. + const blobURLEntry = requestCurrentURL(request) + + // https://github.com/web-platform-tests/wpt/blob/7b0ebaccc62b566a1965396e5be7bb2bc06f841f/FileAPI/url/resources/fetch-tests.js#L52-L56 + // Buffer.resolveObjectURL does not ignore URL queries. + if (blobURLEntry.search.length !== 0) { + return Promise.resolve(makeNetworkError('NetworkError when attempting to fetch resource.')) + } + + const blobURLEntryObject = resolveObjectURL(blobURLEntry.toString()) + + // 2. If request’s method is not `GET`, blobURLEntry is null, or blobURLEntry’s + // object is not a Blob object, then return a network error. + if (request.method !== 'GET' || !isBlobLike(blobURLEntryObject)) { + return Promise.resolve(makeNetworkError('invalid method')) + } + + // 3. Let bodyWithType be the result of safely extracting blobURLEntry’s object. + const bodyWithType = safelyExtractBody(blobURLEntryObject) + + // 4. Let body be bodyWithType’s body. + const body = bodyWithType[0] + + // 5. Let length be body’s length, serialized and isomorphic encoded. + const length = isomorphicEncode(`${body.length}`) + + // 6. Let type be bodyWithType’s type if it is non-null; otherwise the empty byte sequence. + const type = bodyWithType[1] ?? '' + + // 7. Return a new response whose status message is `OK`, header list is + // « (`Content-Length`, length), (`Content-Type`, type) », and body is body. + const response = makeResponse({ + statusText: 'OK', + headersList: [ + ['content-length', { name: 'Content-Length', value: length }], + ['content-type', { name: 'Content-Type', value: type }] + ] + }) + + response.body = body + + return Promise.resolve(response) + } + case 'data:': { + // 1. Let dataURLStruct be the result of running the + // data: URL processor on request’s current URL. + const currentURL = requestCurrentURL(request) + const dataURLStruct = dataURLProcessor(currentURL) + + // 2. If dataURLStruct is failure, then return a + // network error. + if (dataURLStruct === 'failure') { + return Promise.resolve(makeNetworkError('failed to fetch the data URL')) + } + + // 3. Let mimeType be dataURLStruct’s MIME type, serialized. + const mimeType = serializeAMimeType(dataURLStruct.mimeType) + + // 4. Return a response whose status message is `OK`, + // header list is « (`Content-Type`, mimeType) », + // and body is dataURLStruct’s body as a body. + return Promise.resolve(makeResponse({ + statusText: 'OK', + headersList: [ + ['content-type', { name: 'Content-Type', value: mimeType }] + ], + body: safelyExtractBody(dataURLStruct.body)[0] + })) + } + case 'file:': { + // For now, unfortunate as it is, file URLs are left as an exercise for the reader. + // When in doubt, return a network error. + return Promise.resolve(makeNetworkError('not implemented... yet...')) + } + case 'http:': + case 'https:': { + // Return the result of running HTTP fetch given fetchParams. + + return httpFetch(fetchParams) + .catch((err) => makeNetworkError(err)) + } + default: { + return Promise.resolve(makeNetworkError('unknown scheme')) + } + } +} + +// https://fetch.spec.whatwg.org/#finalize-response +function finalizeResponse (fetchParams, response) { + // 1. Set fetchParams’s request’s done flag. + fetchParams.request.done = true + + // 2, If fetchParams’s process response done is not null, then queue a fetch + // task to run fetchParams’s process response done given response, with + // fetchParams’s task destination. + if (fetchParams.processResponseDone != null) { + queueMicrotask(() => fetchParams.processResponseDone(response)) + } +} + +// https://fetch.spec.whatwg.org/#fetch-finale +function fetchFinale (fetchParams, response) { + // 1. If response is a network error, then: + if (response.type === 'error') { + // 1. Set response’s URL list to « fetchParams’s request’s URL list[0] ». + response.urlList = [fetchParams.request.urlList[0]] + + // 2. Set response’s timing info to the result of creating an opaque timing + // info for fetchParams’s timing info. + response.timingInfo = createOpaqueTimingInfo({ + startTime: fetchParams.timingInfo.startTime + }) + } + + // 2. Let processResponseEndOfBody be the following steps: + const processResponseEndOfBody = () => { + // 1. Set fetchParams’s request’s done flag. + fetchParams.request.done = true + + // If fetchParams’s process response end-of-body is not null, + // then queue a fetch task to run fetchParams’s process response + // end-of-body given response with fetchParams’s task destination. + if (fetchParams.processResponseEndOfBody != null) { + queueMicrotask(() => fetchParams.processResponseEndOfBody(response)) + } + } + + // 3. If fetchParams’s process response is non-null, then queue a fetch task + // to run fetchParams’s process response given response, with fetchParams’s + // task destination. + if (fetchParams.processResponse != null) { + queueMicrotask(() => fetchParams.processResponse(response)) + } + + // 4. If response’s body is null, then run processResponseEndOfBody. + if (response.body == null) { + processResponseEndOfBody() + } else { + // 5. Otherwise: + + // 1. Let transformStream be a new a TransformStream. + + // 2. Let identityTransformAlgorithm be an algorithm which, given chunk, + // enqueues chunk in transformStream. + const identityTransformAlgorithm = (chunk, controller) => { + controller.enqueue(chunk) + } + + // 3. Set up transformStream with transformAlgorithm set to identityTransformAlgorithm + // and flushAlgorithm set to processResponseEndOfBody. + const transformStream = new TransformStream({ + start () {}, + transform: identityTransformAlgorithm, + flush: processResponseEndOfBody + }, { + size () { + return 1 + } + }, { + size () { + return 1 + } + }) + + // 4. Set response’s body to the result of piping response’s body through transformStream. + response.body = { stream: response.body.stream.pipeThrough(transformStream) } + } + + // 6. If fetchParams’s process response consume body is non-null, then: + if (fetchParams.processResponseConsumeBody != null) { + // 1. Let processBody given nullOrBytes be this step: run fetchParams’s + // process response consume body given response and nullOrBytes. + const processBody = (nullOrBytes) => fetchParams.processResponseConsumeBody(response, nullOrBytes) + + // 2. Let processBodyError be this step: run fetchParams’s process + // response consume body given response and failure. + const processBodyError = (failure) => fetchParams.processResponseConsumeBody(response, failure) + + // 3. If response’s body is null, then queue a fetch task to run processBody + // given null, with fetchParams’s task destination. + if (response.body == null) { + queueMicrotask(() => processBody(null)) + } else { + // 4. Otherwise, fully read response’s body given processBody, processBodyError, + // and fetchParams’s task destination. + return fullyReadBody(response.body, processBody, processBodyError) + } + return Promise.resolve() + } +} + +// https://fetch.spec.whatwg.org/#http-fetch +async function httpFetch (fetchParams) { + // 1. Let request be fetchParams’s request. + const request = fetchParams.request + + // 2. Let response be null. + let response = null + + // 3. Let actualResponse be null. + let actualResponse = null + + // 4. Let timingInfo be fetchParams’s timing info. + const timingInfo = fetchParams.timingInfo + + // 5. If request’s service-workers mode is "all", then: + if (request.serviceWorkers === 'all') { + // TODO + } + + // 6. If response is null, then: + if (response === null) { + // 1. If makeCORSPreflight is true and one of these conditions is true: + // TODO + + // 2. If request’s redirect mode is "follow", then set request’s + // service-workers mode to "none". + if (request.redirect === 'follow') { + request.serviceWorkers = 'none' + } + + // 3. Set response and actualResponse to the result of running + // HTTP-network-or-cache fetch given fetchParams. + actualResponse = response = await httpNetworkOrCacheFetch(fetchParams) + + // 4. If request’s response tainting is "cors" and a CORS check + // for request and response returns failure, then return a network error. + if ( + request.responseTainting === 'cors' && + corsCheck(request, response) === 'failure' + ) { + return makeNetworkError('cors failure') + } + + // 5. If the TAO check for request and response returns failure, then set + // request’s timing allow failed flag. + if (TAOCheck(request, response) === 'failure') { + request.timingAllowFailed = true + } + } + + // 7. If either request’s response tainting or response’s type + // is "opaque", and the cross-origin resource policy check with + // request’s origin, request’s client, request’s destination, + // and actualResponse returns blocked, then return a network error. + if ( + (request.responseTainting === 'opaque' || response.type === 'opaque') && + crossOriginResourcePolicyCheck( + request.origin, + request.client, + request.destination, + actualResponse + ) === 'blocked' + ) { + return makeNetworkError('blocked') + } + + // 8. If actualResponse’s status is a redirect status, then: + if (redirectStatusSet.has(actualResponse.status)) { + // 1. If actualResponse’s status is not 303, request’s body is not null, + // and the connection uses HTTP/2, then user agents may, and are even + // encouraged to, transmit an RST_STREAM frame. + // See, https://github.com/whatwg/fetch/issues/1288 + if (request.redirect !== 'manual') { + fetchParams.controller.connection.destroy() + } + + // 2. Switch on request’s redirect mode: + if (request.redirect === 'error') { + // Set response to a network error. + response = makeNetworkError('unexpected redirect') + } else if (request.redirect === 'manual') { + // Set response to an opaque-redirect filtered response whose internal + // response is actualResponse. + // NOTE(spec): On the web this would return an `opaqueredirect` response, + // but that doesn't make sense server side. + // See https://github.com/nodejs/undici/issues/1193. + response = actualResponse + } else if (request.redirect === 'follow') { + // Set response to the result of running HTTP-redirect fetch given + // fetchParams and response. + response = await httpRedirectFetch(fetchParams, response) + } else { + assert(false) + } + } + + // 9. Set response’s timing info to timingInfo. + response.timingInfo = timingInfo + + // 10. Return response. + return response +} + +// https://fetch.spec.whatwg.org/#http-redirect-fetch +function httpRedirectFetch (fetchParams, response) { + // 1. Let request be fetchParams’s request. + const request = fetchParams.request + + // 2. Let actualResponse be response, if response is not a filtered response, + // and response’s internal response otherwise. + const actualResponse = response.internalResponse + ? response.internalResponse + : response + + // 3. Let locationURL be actualResponse’s location URL given request’s current + // URL’s fragment. + let locationURL + + try { + locationURL = responseLocationURL( + actualResponse, + requestCurrentURL(request).hash + ) + + // 4. If locationURL is null, then return response. + if (locationURL == null) { + return response + } + } catch (err) { + // 5. If locationURL is failure, then return a network error. + return Promise.resolve(makeNetworkError(err)) + } + + // 6. If locationURL’s scheme is not an HTTP(S) scheme, then return a network + // error. + if (!urlIsHttpHttpsScheme(locationURL)) { + return Promise.resolve(makeNetworkError('URL scheme must be a HTTP(S) scheme')) + } + + // 7. If request’s redirect count is 20, then return a network error. + if (request.redirectCount === 20) { + return Promise.resolve(makeNetworkError('redirect count exceeded')) + } + + // 8. Increase request’s redirect count by 1. + request.redirectCount += 1 + + // 9. If request’s mode is "cors", locationURL includes credentials, and + // request’s origin is not same origin with locationURL’s origin, then return + // a network error. + if ( + request.mode === 'cors' && + (locationURL.username || locationURL.password) && + !sameOrigin(request, locationURL) + ) { + return Promise.resolve(makeNetworkError('cross origin not allowed for request mode "cors"')) + } + + // 10. If request’s response tainting is "cors" and locationURL includes + // credentials, then return a network error. + if ( + request.responseTainting === 'cors' && + (locationURL.username || locationURL.password) + ) { + return Promise.resolve(makeNetworkError( + 'URL cannot contain credentials for request mode "cors"' + )) + } + + // 11. If actualResponse’s status is not 303, request’s body is non-null, + // and request’s body’s source is null, then return a network error. + if ( + actualResponse.status !== 303 && + request.body != null && + request.body.source == null + ) { + return Promise.resolve(makeNetworkError()) + } + + // 12. If one of the following is true + // - actualResponse’s status is 301 or 302 and request’s method is `POST` + // - actualResponse’s status is 303 and request’s method is not `GET` or `HEAD` + if ( + ([301, 302].includes(actualResponse.status) && request.method === 'POST') || + (actualResponse.status === 303 && + !GET_OR_HEAD.includes(request.method)) + ) { + // then: + // 1. Set request’s method to `GET` and request’s body to null. + request.method = 'GET' + request.body = null + + // 2. For each headerName of request-body-header name, delete headerName from + // request’s header list. + for (const headerName of requestBodyHeader) { + request.headersList.delete(headerName) + } + } + + // 13. If request’s current URL’s origin is not same origin with locationURL’s + // origin, then for each headerName of CORS non-wildcard request-header name, + // delete headerName from request’s header list. + if (!sameOrigin(requestCurrentURL(request), locationURL)) { + // https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name + request.headersList.delete('authorization') + + // https://fetch.spec.whatwg.org/#authentication-entries + request.headersList.delete('proxy-authorization', true) + + // "Cookie" and "Host" are forbidden request-headers, which undici doesn't implement. + request.headersList.delete('cookie') + request.headersList.delete('host') + } + + // 14. If request’s body is non-null, then set request’s body to the first return + // value of safely extracting request’s body’s source. + if (request.body != null) { + assert(request.body.source != null) + request.body = safelyExtractBody(request.body.source)[0] + } + + // 15. Let timingInfo be fetchParams’s timing info. + const timingInfo = fetchParams.timingInfo + + // 16. Set timingInfo’s redirect end time and post-redirect start time to the + // coarsened shared current time given fetchParams’s cross-origin isolated + // capability. + timingInfo.redirectEndTime = timingInfo.postRedirectStartTime = + coarsenedSharedCurrentTime(fetchParams.crossOriginIsolatedCapability) + + // 17. If timingInfo’s redirect start time is 0, then set timingInfo’s + // redirect start time to timingInfo’s start time. + if (timingInfo.redirectStartTime === 0) { + timingInfo.redirectStartTime = timingInfo.startTime + } + + // 18. Append locationURL to request’s URL list. + request.urlList.push(locationURL) + + // 19. Invoke set request’s referrer policy on redirect on request and + // actualResponse. + setRequestReferrerPolicyOnRedirect(request, actualResponse) + + // 20. Return the result of running main fetch given fetchParams and true. + return mainFetch(fetchParams, true) +} + +// https://fetch.spec.whatwg.org/#http-network-or-cache-fetch +async function httpNetworkOrCacheFetch ( + fetchParams, + isAuthenticationFetch = false, + isNewConnectionFetch = false +) { + // 1. Let request be fetchParams’s request. + const request = fetchParams.request + + // 2. Let httpFetchParams be null. + let httpFetchParams = null + + // 3. Let httpRequest be null. + let httpRequest = null + + // 4. Let response be null. + let response = null + + // 5. Let storedResponse be null. + // TODO: cache + + // 6. Let httpCache be null. + const httpCache = null + + // 7. Let the revalidatingFlag be unset. + const revalidatingFlag = false + + // 8. Run these steps, but abort when the ongoing fetch is terminated: + + // 1. If request’s window is "no-window" and request’s redirect mode is + // "error", then set httpFetchParams to fetchParams and httpRequest to + // request. + if (request.window === 'no-window' && request.redirect === 'error') { + httpFetchParams = fetchParams + httpRequest = request + } else { + // Otherwise: + + // 1. Set httpRequest to a clone of request. + httpRequest = makeRequest(request) + + // 2. Set httpFetchParams to a copy of fetchParams. + httpFetchParams = { ...fetchParams } + + // 3. Set httpFetchParams’s request to httpRequest. + httpFetchParams.request = httpRequest + } + + // 3. Let includeCredentials be true if one of + const includeCredentials = + request.credentials === 'include' || + (request.credentials === 'same-origin' && + request.responseTainting === 'basic') + + // 4. Let contentLength be httpRequest’s body’s length, if httpRequest’s + // body is non-null; otherwise null. + const contentLength = httpRequest.body ? httpRequest.body.length : null + + // 5. Let contentLengthHeaderValue be null. + let contentLengthHeaderValue = null + + // 6. If httpRequest’s body is null and httpRequest’s method is `POST` or + // `PUT`, then set contentLengthHeaderValue to `0`. + if ( + httpRequest.body == null && + ['POST', 'PUT'].includes(httpRequest.method) + ) { + contentLengthHeaderValue = '0' + } + + // 7. If contentLength is non-null, then set contentLengthHeaderValue to + // contentLength, serialized and isomorphic encoded. + if (contentLength != null) { + contentLengthHeaderValue = isomorphicEncode(`${contentLength}`) + } + + // 8. If contentLengthHeaderValue is non-null, then append + // `Content-Length`/contentLengthHeaderValue to httpRequest’s header + // list. + if (contentLengthHeaderValue != null) { + httpRequest.headersList.append('content-length', contentLengthHeaderValue) + } + + // 9. If contentLengthHeaderValue is non-null, then append (`Content-Length`, + // contentLengthHeaderValue) to httpRequest’s header list. + + // 10. If contentLength is non-null and httpRequest’s keepalive is true, + // then: + if (contentLength != null && httpRequest.keepalive) { + // NOTE: keepalive is a noop outside of browser context. + } + + // 11. If httpRequest’s referrer is a URL, then append + // `Referer`/httpRequest’s referrer, serialized and isomorphic encoded, + // to httpRequest’s header list. + if (httpRequest.referrer instanceof URL) { + httpRequest.headersList.append('referer', isomorphicEncode(httpRequest.referrer.href)) + } + + // 12. Append a request `Origin` header for httpRequest. + appendRequestOriginHeader(httpRequest) + + // 13. Append the Fetch metadata headers for httpRequest. [FETCH-METADATA] + appendFetchMetadata(httpRequest) + + // 14. If httpRequest’s header list does not contain `User-Agent`, then + // user agents should append `User-Agent`/default `User-Agent` value to + // httpRequest’s header list. + if (!httpRequest.headersList.contains('user-agent')) { + httpRequest.headersList.append('user-agent', typeof esbuildDetection === 'undefined' ? 'undici' : 'node') + } + + // 15. If httpRequest’s cache mode is "default" and httpRequest’s header + // list contains `If-Modified-Since`, `If-None-Match`, + // `If-Unmodified-Since`, `If-Match`, or `If-Range`, then set + // httpRequest’s cache mode to "no-store". + if ( + httpRequest.cache === 'default' && + (httpRequest.headersList.contains('if-modified-since') || + httpRequest.headersList.contains('if-none-match') || + httpRequest.headersList.contains('if-unmodified-since') || + httpRequest.headersList.contains('if-match') || + httpRequest.headersList.contains('if-range')) + ) { + httpRequest.cache = 'no-store' + } + + // 16. If httpRequest’s cache mode is "no-cache", httpRequest’s prevent + // no-cache cache-control header modification flag is unset, and + // httpRequest’s header list does not contain `Cache-Control`, then append + // `Cache-Control`/`max-age=0` to httpRequest’s header list. + if ( + httpRequest.cache === 'no-cache' && + !httpRequest.preventNoCacheCacheControlHeaderModification && + !httpRequest.headersList.contains('cache-control') + ) { + httpRequest.headersList.append('cache-control', 'max-age=0') + } + + // 17. If httpRequest’s cache mode is "no-store" or "reload", then: + if (httpRequest.cache === 'no-store' || httpRequest.cache === 'reload') { + // 1. If httpRequest’s header list does not contain `Pragma`, then append + // `Pragma`/`no-cache` to httpRequest’s header list. + if (!httpRequest.headersList.contains('pragma')) { + httpRequest.headersList.append('pragma', 'no-cache') + } + + // 2. If httpRequest’s header list does not contain `Cache-Control`, + // then append `Cache-Control`/`no-cache` to httpRequest’s header list. + if (!httpRequest.headersList.contains('cache-control')) { + httpRequest.headersList.append('cache-control', 'no-cache') + } + } + + // 18. If httpRequest’s header list contains `Range`, then append + // `Accept-Encoding`/`identity` to httpRequest’s header list. + if (httpRequest.headersList.contains('range')) { + httpRequest.headersList.append('accept-encoding', 'identity') + } + + // 19. Modify httpRequest’s header list per HTTP. Do not append a given + // header if httpRequest’s header list contains that header’s name. + // TODO: https://github.com/whatwg/fetch/issues/1285#issuecomment-896560129 + if (!httpRequest.headersList.contains('accept-encoding')) { + if (urlHasHttpsScheme(requestCurrentURL(httpRequest))) { + httpRequest.headersList.append('accept-encoding', 'br, gzip, deflate') + } else { + httpRequest.headersList.append('accept-encoding', 'gzip, deflate') + } + } + + httpRequest.headersList.delete('host') + + // 20. If includeCredentials is true, then: + if (includeCredentials) { + // 1. If the user agent is not configured to block cookies for httpRequest + // (see section 7 of [COOKIES]), then: + // TODO: credentials + // 2. If httpRequest’s header list does not contain `Authorization`, then: + // TODO: credentials + } + + // 21. If there’s a proxy-authentication entry, use it as appropriate. + // TODO: proxy-authentication + + // 22. Set httpCache to the result of determining the HTTP cache + // partition, given httpRequest. + // TODO: cache + + // 23. If httpCache is null, then set httpRequest’s cache mode to + // "no-store". + if (httpCache == null) { + httpRequest.cache = 'no-store' + } + + // 24. If httpRequest’s cache mode is neither "no-store" nor "reload", + // then: + if (httpRequest.mode !== 'no-store' && httpRequest.mode !== 'reload') { + // TODO: cache + } + + // 9. If aborted, then return the appropriate network error for fetchParams. + // TODO + + // 10. If response is null, then: + if (response == null) { + // 1. If httpRequest’s cache mode is "only-if-cached", then return a + // network error. + if (httpRequest.mode === 'only-if-cached') { + return makeNetworkError('only if cached') + } + + // 2. Let forwardResponse be the result of running HTTP-network fetch + // given httpFetchParams, includeCredentials, and isNewConnectionFetch. + const forwardResponse = await httpNetworkFetch( + httpFetchParams, + includeCredentials, + isNewConnectionFetch + ) + + // 3. If httpRequest’s method is unsafe and forwardResponse’s status is + // in the range 200 to 399, inclusive, invalidate appropriate stored + // responses in httpCache, as per the "Invalidation" chapter of HTTP + // Caching, and set storedResponse to null. [HTTP-CACHING] + if ( + !safeMethodsSet.has(httpRequest.method) && + forwardResponse.status >= 200 && + forwardResponse.status <= 399 + ) { + // TODO: cache + } + + // 4. If the revalidatingFlag is set and forwardResponse’s status is 304, + // then: + if (revalidatingFlag && forwardResponse.status === 304) { + // TODO: cache + } + + // 5. If response is null, then: + if (response == null) { + // 1. Set response to forwardResponse. + response = forwardResponse + + // 2. Store httpRequest and forwardResponse in httpCache, as per the + // "Storing Responses in Caches" chapter of HTTP Caching. [HTTP-CACHING] + // TODO: cache + } + } + + // 11. Set response’s URL list to a clone of httpRequest’s URL list. + response.urlList = [...httpRequest.urlList] + + // 12. If httpRequest’s header list contains `Range`, then set response’s + // range-requested flag. + if (httpRequest.headersList.contains('range')) { + response.rangeRequested = true + } + + // 13. Set response’s request-includes-credentials to includeCredentials. + response.requestIncludesCredentials = includeCredentials + + // 14. If response’s status is 401, httpRequest’s response tainting is not + // "cors", includeCredentials is true, and request’s window is an environment + // settings object, then: + // TODO + + // 15. If response’s status is 407, then: + if (response.status === 407) { + // 1. If request’s window is "no-window", then return a network error. + if (request.window === 'no-window') { + return makeNetworkError() + } + + // 2. ??? + + // 3. If fetchParams is canceled, then return the appropriate network error for fetchParams. + if (isCancelled(fetchParams)) { + return makeAppropriateNetworkError(fetchParams) + } + + // 4. Prompt the end user as appropriate in request’s window and store + // the result as a proxy-authentication entry. [HTTP-AUTH] + // TODO: Invoke some kind of callback? + + // 5. Set response to the result of running HTTP-network-or-cache fetch given + // fetchParams. + // TODO + return makeNetworkError('proxy authentication required') + } + + // 16. If all of the following are true + if ( + // response’s status is 421 + response.status === 421 && + // isNewConnectionFetch is false + !isNewConnectionFetch && + // request’s body is null, or request’s body is non-null and request’s body’s source is non-null + (request.body == null || request.body.source != null) + ) { + // then: + + // 1. If fetchParams is canceled, then return the appropriate network error for fetchParams. + if (isCancelled(fetchParams)) { + return makeAppropriateNetworkError(fetchParams) + } + + // 2. Set response to the result of running HTTP-network-or-cache + // fetch given fetchParams, isAuthenticationFetch, and true. + + // TODO (spec): The spec doesn't specify this but we need to cancel + // the active response before we can start a new one. + // https://github.com/whatwg/fetch/issues/1293 + fetchParams.controller.connection.destroy() + + response = await httpNetworkOrCacheFetch( + fetchParams, + isAuthenticationFetch, + true + ) + } + + // 17. If isAuthenticationFetch is true, then create an authentication entry + if (isAuthenticationFetch) { + // TODO + } + + // 18. Return response. + return response +} + +// https://fetch.spec.whatwg.org/#http-network-fetch +async function httpNetworkFetch ( + fetchParams, + includeCredentials = false, + forceNewConnection = false +) { + assert(!fetchParams.controller.connection || fetchParams.controller.connection.destroyed) + + fetchParams.controller.connection = { + abort: null, + destroyed: false, + destroy (err) { + if (!this.destroyed) { + this.destroyed = true + this.abort?.(err ?? new DOMException('The operation was aborted.', 'AbortError')) + } + } + } + + // 1. Let request be fetchParams’s request. + const request = fetchParams.request + + // 2. Let response be null. + let response = null + + // 3. Let timingInfo be fetchParams’s timing info. + const timingInfo = fetchParams.timingInfo + + // 4. Let httpCache be the result of determining the HTTP cache partition, + // given request. + // TODO: cache + const httpCache = null + + // 5. If httpCache is null, then set request’s cache mode to "no-store". + if (httpCache == null) { + request.cache = 'no-store' + } + + // 6. Let networkPartitionKey be the result of determining the network + // partition key given request. + // TODO + + // 7. Let newConnection be "yes" if forceNewConnection is true; otherwise + // "no". + const newConnection = forceNewConnection ? 'yes' : 'no' // eslint-disable-line no-unused-vars + + // 8. Switch on request’s mode: + if (request.mode === 'websocket') { + // Let connection be the result of obtaining a WebSocket connection, + // given request’s current URL. + // TODO + } else { + // Let connection be the result of obtaining a connection, given + // networkPartitionKey, request’s current URL’s origin, + // includeCredentials, and forceNewConnection. + // TODO + } + + // 9. Run these steps, but abort when the ongoing fetch is terminated: + + // 1. If connection is failure, then return a network error. + + // 2. Set timingInfo’s final connection timing info to the result of + // calling clamp and coarsen connection timing info with connection’s + // timing info, timingInfo’s post-redirect start time, and fetchParams’s + // cross-origin isolated capability. + + // 3. If connection is not an HTTP/2 connection, request’s body is non-null, + // and request’s body’s source is null, then append (`Transfer-Encoding`, + // `chunked`) to request’s header list. + + // 4. Set timingInfo’s final network-request start time to the coarsened + // shared current time given fetchParams’s cross-origin isolated + // capability. + + // 5. Set response to the result of making an HTTP request over connection + // using request with the following caveats: + + // - Follow the relevant requirements from HTTP. [HTTP] [HTTP-SEMANTICS] + // [HTTP-COND] [HTTP-CACHING] [HTTP-AUTH] + + // - If request’s body is non-null, and request’s body’s source is null, + // then the user agent may have a buffer of up to 64 kibibytes and store + // a part of request’s body in that buffer. If the user agent reads from + // request’s body beyond that buffer’s size and the user agent needs to + // resend request, then instead return a network error. + + // - Set timingInfo’s final network-response start time to the coarsened + // shared current time given fetchParams’s cross-origin isolated capability, + // immediately after the user agent’s HTTP parser receives the first byte + // of the response (e.g., frame header bytes for HTTP/2 or response status + // line for HTTP/1.x). + + // - Wait until all the headers are transmitted. + + // - Any responses whose status is in the range 100 to 199, inclusive, + // and is not 101, are to be ignored, except for the purposes of setting + // timingInfo’s final network-response start time above. + + // - If request’s header list contains `Transfer-Encoding`/`chunked` and + // response is transferred via HTTP/1.0 or older, then return a network + // error. + + // - If the HTTP request results in a TLS client certificate dialog, then: + + // 1. If request’s window is an environment settings object, make the + // dialog available in request’s window. + + // 2. Otherwise, return a network error. + + // To transmit request’s body body, run these steps: + let requestBody = null + // 1. If body is null and fetchParams’s process request end-of-body is + // non-null, then queue a fetch task given fetchParams’s process request + // end-of-body and fetchParams’s task destination. + if (request.body == null && fetchParams.processRequestEndOfBody) { + queueMicrotask(() => fetchParams.processRequestEndOfBody()) + } else if (request.body != null) { + // 2. Otherwise, if body is non-null: + + // 1. Let processBodyChunk given bytes be these steps: + const processBodyChunk = async function * (bytes) { + // 1. If the ongoing fetch is terminated, then abort these steps. + if (isCancelled(fetchParams)) { + return + } + + // 2. Run this step in parallel: transmit bytes. + yield bytes + + // 3. If fetchParams’s process request body is non-null, then run + // fetchParams’s process request body given bytes’s length. + fetchParams.processRequestBodyChunkLength?.(bytes.byteLength) + } + + // 2. Let processEndOfBody be these steps: + const processEndOfBody = () => { + // 1. If fetchParams is canceled, then abort these steps. + if (isCancelled(fetchParams)) { + return + } + + // 2. If fetchParams’s process request end-of-body is non-null, + // then run fetchParams’s process request end-of-body. + if (fetchParams.processRequestEndOfBody) { + fetchParams.processRequestEndOfBody() + } + } + + // 3. Let processBodyError given e be these steps: + const processBodyError = (e) => { + // 1. If fetchParams is canceled, then abort these steps. + if (isCancelled(fetchParams)) { + return + } + + // 2. If e is an "AbortError" DOMException, then abort fetchParams’s controller. + if (e.name === 'AbortError') { + fetchParams.controller.abort() + } else { + fetchParams.controller.terminate(e) + } + } + + // 4. Incrementally read request’s body given processBodyChunk, processEndOfBody, + // processBodyError, and fetchParams’s task destination. + requestBody = (async function * () { + try { + for await (const bytes of request.body.stream) { + yield * processBodyChunk(bytes) + } + processEndOfBody() + } catch (err) { + processBodyError(err) + } + })() + } + + try { + // socket is only provided for websockets + const { body, status, statusText, headersList, socket } = await dispatch({ body: requestBody }) + + if (socket) { + response = makeResponse({ status, statusText, headersList, socket }) + } else { + const iterator = body[Symbol.asyncIterator]() + fetchParams.controller.next = () => iterator.next() + + response = makeResponse({ status, statusText, headersList }) + } + } catch (err) { + // 10. If aborted, then: + if (err.name === 'AbortError') { + // 1. If connection uses HTTP/2, then transmit an RST_STREAM frame. + fetchParams.controller.connection.destroy() + + // 2. Return the appropriate network error for fetchParams. + return makeAppropriateNetworkError(fetchParams, err) + } + + return makeNetworkError(err) + } + + // 11. Let pullAlgorithm be an action that resumes the ongoing fetch + // if it is suspended. + const pullAlgorithm = () => { + fetchParams.controller.resume() + } + + // 12. Let cancelAlgorithm be an algorithm that aborts fetchParams’s + // controller with reason, given reason. + const cancelAlgorithm = (reason) => { + fetchParams.controller.abort(reason) + } + + // 13. Let highWaterMark be a non-negative, non-NaN number, chosen by + // the user agent. + // TODO + + // 14. Let sizeAlgorithm be an algorithm that accepts a chunk object + // and returns a non-negative, non-NaN, non-infinite number, chosen by the user agent. + // TODO + + // 15. Let stream be a new ReadableStream. + // 16. Set up stream with pullAlgorithm set to pullAlgorithm, + // cancelAlgorithm set to cancelAlgorithm, highWaterMark set to + // highWaterMark, and sizeAlgorithm set to sizeAlgorithm. + if (!ReadableStream) { + ReadableStream = (__nccwpck_require__(3774).ReadableStream) + } + + const stream = new ReadableStream( + { + async start (controller) { + fetchParams.controller.controller = controller + }, + async pull (controller) { + await pullAlgorithm(controller) + }, + async cancel (reason) { + await cancelAlgorithm(reason) + } + }, + { + highWaterMark: 0, + size () { + return 1 + } + } + ) + + // 17. Run these steps, but abort when the ongoing fetch is terminated: + + // 1. Set response’s body to a new body whose stream is stream. + response.body = { stream } + + // 2. If response is not a network error and request’s cache mode is + // not "no-store", then update response in httpCache for request. + // TODO + + // 3. If includeCredentials is true and the user agent is not configured + // to block cookies for request (see section 7 of [COOKIES]), then run the + // "set-cookie-string" parsing algorithm (see section 5.2 of [COOKIES]) on + // the value of each header whose name is a byte-case-insensitive match for + // `Set-Cookie` in response’s header list, if any, and request’s current URL. + // TODO + + // 18. If aborted, then: + // TODO + + // 19. Run these steps in parallel: + + // 1. Run these steps, but abort when fetchParams is canceled: + fetchParams.controller.on('terminated', onAborted) + fetchParams.controller.resume = async () => { + // 1. While true + while (true) { + // 1-3. See onData... + + // 4. Set bytes to the result of handling content codings given + // codings and bytes. + let bytes + let isFailure + try { + const { done, value } = await fetchParams.controller.next() + + if (isAborted(fetchParams)) { + break + } + + bytes = done ? undefined : value + } catch (err) { + if (fetchParams.controller.ended && !timingInfo.encodedBodySize) { + // zlib doesn't like empty streams. + bytes = undefined + } else { + bytes = err + + // err may be propagated from the result of calling readablestream.cancel, + // which might not be an error. https://github.com/nodejs/undici/issues/2009 + isFailure = true + } + } + + if (bytes === undefined) { + // 2. Otherwise, if the bytes transmission for response’s message + // body is done normally and stream is readable, then close + // stream, finalize response for fetchParams and response, and + // abort these in-parallel steps. + readableStreamClose(fetchParams.controller.controller) + + finalizeResponse(fetchParams, response) + + return + } + + // 5. Increase timingInfo’s decoded body size by bytes’s length. + timingInfo.decodedBodySize += bytes?.byteLength ?? 0 + + // 6. If bytes is failure, then terminate fetchParams’s controller. + if (isFailure) { + fetchParams.controller.terminate(bytes) + return + } + + // 7. Enqueue a Uint8Array wrapping an ArrayBuffer containing bytes + // into stream. + fetchParams.controller.controller.enqueue(new Uint8Array(bytes)) + + // 8. If stream is errored, then terminate the ongoing fetch. + if (isErrored(stream)) { + fetchParams.controller.terminate() + return + } + + // 9. If stream doesn’t need more data ask the user agent to suspend + // the ongoing fetch. + if (!fetchParams.controller.controller.desiredSize) { + return + } + } + } + + // 2. If aborted, then: + function onAborted (reason) { + // 2. If fetchParams is aborted, then: + if (isAborted(fetchParams)) { + // 1. Set response’s aborted flag. + response.aborted = true + + // 2. If stream is readable, then error stream with the result of + // deserialize a serialized abort reason given fetchParams’s + // controller’s serialized abort reason and an + // implementation-defined realm. + if (isReadable(stream)) { + fetchParams.controller.controller.error( + fetchParams.controller.serializedAbortReason + ) + } + } else { + // 3. Otherwise, if stream is readable, error stream with a TypeError. + if (isReadable(stream)) { + fetchParams.controller.controller.error(new TypeError('terminated', { + cause: isErrorLike(reason) ? reason : undefined + })) + } + } + + // 4. If connection uses HTTP/2, then transmit an RST_STREAM frame. + // 5. Otherwise, the user agent should close connection unless it would be bad for performance to do so. + fetchParams.controller.connection.destroy() + } + + // 20. Return response. + return response + + async function dispatch ({ body }) { + const url = requestCurrentURL(request) + /** @type {import('../..').Agent} */ + const agent = fetchParams.controller.dispatcher + + return new Promise((resolve, reject) => agent.dispatch( + { + path: url.pathname + url.search, + origin: url.origin, + method: request.method, + body: fetchParams.controller.dispatcher.isMockActive ? request.body && (request.body.source || request.body.stream) : body, + headers: request.headersList.entries, + maxRedirections: 0, + upgrade: request.mode === 'websocket' ? 'websocket' : undefined + }, + { + body: null, + abort: null, + + onConnect (abort) { + // TODO (fix): Do we need connection here? + const { connection } = fetchParams.controller + + if (connection.destroyed) { + abort(new DOMException('The operation was aborted.', 'AbortError')) + } else { + fetchParams.controller.on('terminated', abort) + this.abort = connection.abort = abort + } + }, + + onHeaders (status, headersList, resume, statusText) { + if (status < 200) { + return + } + + let codings = [] + let location = '' + + const headers = new Headers() + + // For H2, the headers are a plain JS object + // We distinguish between them and iterate accordingly + if (Array.isArray(headersList)) { + for (let n = 0; n < headersList.length; n += 2) { + const key = headersList[n + 0].toString('latin1') + const val = headersList[n + 1].toString('latin1') + if (key.toLowerCase() === 'content-encoding') { + // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1 + // "All content-coding values are case-insensitive..." + codings = val.toLowerCase().split(',').map((x) => x.trim()) + } else if (key.toLowerCase() === 'location') { + location = val + } + + headers[kHeadersList].append(key, val) + } + } else { + const keys = Object.keys(headersList) + for (const key of keys) { + const val = headersList[key] + if (key.toLowerCase() === 'content-encoding') { + // https://www.rfc-editor.org/rfc/rfc7231#section-3.1.2.1 + // "All content-coding values are case-insensitive..." + codings = val.toLowerCase().split(',').map((x) => x.trim()).reverse() + } else if (key.toLowerCase() === 'location') { + location = val + } + + headers[kHeadersList].append(key, val) + } + } + + this.body = new Readable({ read: resume }) + + const decoders = [] + + const willFollow = request.redirect === 'follow' && + location && + redirectStatusSet.has(status) + + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding + if (request.method !== 'HEAD' && request.method !== 'CONNECT' && !nullBodyStatus.includes(status) && !willFollow) { + for (const coding of codings) { + // https://www.rfc-editor.org/rfc/rfc9112.html#section-7.2 + if (coding === 'x-gzip' || coding === 'gzip') { + decoders.push(zlib.createGunzip({ + // Be less strict when decoding compressed responses, since sometimes + // servers send slightly invalid responses that are still accepted + // by common browsers. + // Always using Z_SYNC_FLUSH is what cURL does. + flush: zlib.constants.Z_SYNC_FLUSH, + finishFlush: zlib.constants.Z_SYNC_FLUSH + })) + } else if (coding === 'deflate') { + decoders.push(zlib.createInflate()) + } else if (coding === 'br') { + decoders.push(zlib.createBrotliDecompress()) + } else { + decoders.length = 0 + break + } + } + } + + resolve({ + status, + statusText, + headersList: headers[kHeadersList], + body: decoders.length + ? pipeline(this.body, ...decoders, () => { }) + : this.body.on('error', () => {}) + }) + + return true + }, + + onData (chunk) { + if (fetchParams.controller.dump) { + return + } + + // 1. If one or more bytes have been transmitted from response’s + // message body, then: + + // 1. Let bytes be the transmitted bytes. + const bytes = chunk + + // 2. Let codings be the result of extracting header list values + // given `Content-Encoding` and response’s header list. + // See pullAlgorithm. + + // 3. Increase timingInfo’s encoded body size by bytes’s length. + timingInfo.encodedBodySize += bytes.byteLength + + // 4. See pullAlgorithm... + + return this.body.push(bytes) + }, + + onComplete () { + if (this.abort) { + fetchParams.controller.off('terminated', this.abort) + } + + fetchParams.controller.ended = true + + this.body.push(null) + }, + + onError (error) { + if (this.abort) { + fetchParams.controller.off('terminated', this.abort) + } + + this.body?.destroy(error) + + fetchParams.controller.terminate(error) + + reject(error) + }, + + onUpgrade (status, headersList, socket) { + if (status !== 101) { + return + } + + const headers = new Headers() + + for (let n = 0; n < headersList.length; n += 2) { + const key = headersList[n + 0].toString('latin1') + const val = headersList[n + 1].toString('latin1') + + headers[kHeadersList].append(key, val) + } + + resolve({ + status, + statusText: STATUS_CODES[status], + headersList: headers[kHeadersList], + socket + }) + + return true + } + } + )) + } +} + +module.exports = { + fetch, + Fetch, + fetching, + finalizeAndReportTiming +} + + +/***/ }), + +/***/ 5194: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +/* globals AbortController */ + + + +const { extractBody, mixinBody, cloneBody } = __nccwpck_require__(8923) +const { Headers, fill: fillHeaders, HeadersList } = __nccwpck_require__(6349) +const { FinalizationRegistry } = __nccwpck_require__(3194)() +const util = __nccwpck_require__(3440) +const { + isValidHTTPToken, + sameOrigin, + normalizeMethod, + makePolicyContainer, + normalizeMethodRecord +} = __nccwpck_require__(5523) +const { + forbiddenMethodsSet, + corsSafeListedMethodsSet, + referrerPolicy, + requestRedirect, + requestMode, + requestCredentials, + requestCache, + requestDuplex +} = __nccwpck_require__(7326) +const { kEnumerableProperty } = util +const { kHeaders, kSignal, kState, kGuard, kRealm } = __nccwpck_require__(9710) +const { webidl } = __nccwpck_require__(4222) +const { getGlobalOrigin } = __nccwpck_require__(5628) +const { URLSerializer } = __nccwpck_require__(4322) +const { kHeadersList, kConstruct } = __nccwpck_require__(6443) +const assert = __nccwpck_require__(2613) +const { getMaxListeners, setMaxListeners, getEventListeners, defaultMaxListeners } = __nccwpck_require__(4434) + +let TransformStream = globalThis.TransformStream + +const kAbortController = Symbol('abortController') + +const requestFinalizer = new FinalizationRegistry(({ signal, abort }) => { + signal.removeEventListener('abort', abort) +}) + +// https://fetch.spec.whatwg.org/#request-class +class Request { + // https://fetch.spec.whatwg.org/#dom-request + constructor (input, init = {}) { + if (input === kConstruct) { + return + } + + webidl.argumentLengthCheck(arguments, 1, { header: 'Request constructor' }) + + input = webidl.converters.RequestInfo(input) + init = webidl.converters.RequestInit(init) + + // https://html.spec.whatwg.org/multipage/webappapis.html#environment-settings-object + this[kRealm] = { + settingsObject: { + baseUrl: getGlobalOrigin(), + get origin () { + return this.baseUrl?.origin + }, + policyContainer: makePolicyContainer() + } + } + + // 1. Let request be null. + let request = null + + // 2. Let fallbackMode be null. + let fallbackMode = null + + // 3. Let baseURL be this’s relevant settings object’s API base URL. + const baseUrl = this[kRealm].settingsObject.baseUrl + + // 4. Let signal be null. + let signal = null + + // 5. If input is a string, then: + if (typeof input === 'string') { + // 1. Let parsedURL be the result of parsing input with baseURL. + // 2. If parsedURL is failure, then throw a TypeError. + let parsedURL + try { + parsedURL = new URL(input, baseUrl) + } catch (err) { + throw new TypeError('Failed to parse URL from ' + input, { cause: err }) + } + + // 3. If parsedURL includes credentials, then throw a TypeError. + if (parsedURL.username || parsedURL.password) { + throw new TypeError( + 'Request cannot be constructed from a URL that includes credentials: ' + + input + ) + } + + // 4. Set request to a new request whose URL is parsedURL. + request = makeRequest({ urlList: [parsedURL] }) + + // 5. Set fallbackMode to "cors". + fallbackMode = 'cors' + } else { + // 6. Otherwise: + + // 7. Assert: input is a Request object. + assert(input instanceof Request) + + // 8. Set request to input’s request. + request = input[kState] + + // 9. Set signal to input’s signal. + signal = input[kSignal] + } + + // 7. Let origin be this’s relevant settings object’s origin. + const origin = this[kRealm].settingsObject.origin + + // 8. Let window be "client". + let window = 'client' + + // 9. If request’s window is an environment settings object and its origin + // is same origin with origin, then set window to request’s window. + if ( + request.window?.constructor?.name === 'EnvironmentSettingsObject' && + sameOrigin(request.window, origin) + ) { + window = request.window + } + + // 10. If init["window"] exists and is non-null, then throw a TypeError. + if (init.window != null) { + throw new TypeError(`'window' option '${window}' must be null`) + } + + // 11. If init["window"] exists, then set window to "no-window". + if ('window' in init) { + window = 'no-window' + } + + // 12. Set request to a new request with the following properties: + request = makeRequest({ + // URL request’s URL. + // undici implementation note: this is set as the first item in request's urlList in makeRequest + // method request’s method. + method: request.method, + // header list A copy of request’s header list. + // undici implementation note: headersList is cloned in makeRequest + headersList: request.headersList, + // unsafe-request flag Set. + unsafeRequest: request.unsafeRequest, + // client This’s relevant settings object. + client: this[kRealm].settingsObject, + // window window. + window, + // priority request’s priority. + priority: request.priority, + // origin request’s origin. The propagation of the origin is only significant for navigation requests + // being handled by a service worker. In this scenario a request can have an origin that is different + // from the current client. + origin: request.origin, + // referrer request’s referrer. + referrer: request.referrer, + // referrer policy request’s referrer policy. + referrerPolicy: request.referrerPolicy, + // mode request’s mode. + mode: request.mode, + // credentials mode request’s credentials mode. + credentials: request.credentials, + // cache mode request’s cache mode. + cache: request.cache, + // redirect mode request’s redirect mode. + redirect: request.redirect, + // integrity metadata request’s integrity metadata. + integrity: request.integrity, + // keepalive request’s keepalive. + keepalive: request.keepalive, + // reload-navigation flag request’s reload-navigation flag. + reloadNavigation: request.reloadNavigation, + // history-navigation flag request’s history-navigation flag. + historyNavigation: request.historyNavigation, + // URL list A clone of request’s URL list. + urlList: [...request.urlList] + }) + + const initHasKey = Object.keys(init).length !== 0 + + // 13. If init is not empty, then: + if (initHasKey) { + // 1. If request’s mode is "navigate", then set it to "same-origin". + if (request.mode === 'navigate') { + request.mode = 'same-origin' + } + + // 2. Unset request’s reload-navigation flag. + request.reloadNavigation = false + + // 3. Unset request’s history-navigation flag. + request.historyNavigation = false + + // 4. Set request’s origin to "client". + request.origin = 'client' + + // 5. Set request’s referrer to "client" + request.referrer = 'client' + + // 6. Set request’s referrer policy to the empty string. + request.referrerPolicy = '' + + // 7. Set request’s URL to request’s current URL. + request.url = request.urlList[request.urlList.length - 1] + + // 8. Set request’s URL list to « request’s URL ». + request.urlList = [request.url] + } + + // 14. If init["referrer"] exists, then: + if (init.referrer !== undefined) { + // 1. Let referrer be init["referrer"]. + const referrer = init.referrer + + // 2. If referrer is the empty string, then set request’s referrer to "no-referrer". + if (referrer === '') { + request.referrer = 'no-referrer' + } else { + // 1. Let parsedReferrer be the result of parsing referrer with + // baseURL. + // 2. If parsedReferrer is failure, then throw a TypeError. + let parsedReferrer + try { + parsedReferrer = new URL(referrer, baseUrl) + } catch (err) { + throw new TypeError(`Referrer "${referrer}" is not a valid URL.`, { cause: err }) + } + + // 3. If one of the following is true + // - parsedReferrer’s scheme is "about" and path is the string "client" + // - parsedReferrer’s origin is not same origin with origin + // then set request’s referrer to "client". + if ( + (parsedReferrer.protocol === 'about:' && parsedReferrer.hostname === 'client') || + (origin && !sameOrigin(parsedReferrer, this[kRealm].settingsObject.baseUrl)) + ) { + request.referrer = 'client' + } else { + // 4. Otherwise, set request’s referrer to parsedReferrer. + request.referrer = parsedReferrer + } + } + } + + // 15. If init["referrerPolicy"] exists, then set request’s referrer policy + // to it. + if (init.referrerPolicy !== undefined) { + request.referrerPolicy = init.referrerPolicy + } + + // 16. Let mode be init["mode"] if it exists, and fallbackMode otherwise. + let mode + if (init.mode !== undefined) { + mode = init.mode + } else { + mode = fallbackMode + } + + // 17. If mode is "navigate", then throw a TypeError. + if (mode === 'navigate') { + throw webidl.errors.exception({ + header: 'Request constructor', + message: 'invalid request mode navigate.' + }) + } + + // 18. If mode is non-null, set request’s mode to mode. + if (mode != null) { + request.mode = mode + } + + // 19. If init["credentials"] exists, then set request’s credentials mode + // to it. + if (init.credentials !== undefined) { + request.credentials = init.credentials + } + + // 18. If init["cache"] exists, then set request’s cache mode to it. + if (init.cache !== undefined) { + request.cache = init.cache + } + + // 21. If request’s cache mode is "only-if-cached" and request’s mode is + // not "same-origin", then throw a TypeError. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + throw new TypeError( + "'only-if-cached' can be set only with 'same-origin' mode" + ) + } + + // 22. If init["redirect"] exists, then set request’s redirect mode to it. + if (init.redirect !== undefined) { + request.redirect = init.redirect + } + + // 23. If init["integrity"] exists, then set request’s integrity metadata to it. + if (init.integrity != null) { + request.integrity = String(init.integrity) + } + + // 24. If init["keepalive"] exists, then set request’s keepalive to it. + if (init.keepalive !== undefined) { + request.keepalive = Boolean(init.keepalive) + } + + // 25. If init["method"] exists, then: + if (init.method !== undefined) { + // 1. Let method be init["method"]. + let method = init.method + + // 2. If method is not a method or method is a forbidden method, then + // throw a TypeError. + if (!isValidHTTPToken(method)) { + throw new TypeError(`'${method}' is not a valid HTTP method.`) + } + + if (forbiddenMethodsSet.has(method.toUpperCase())) { + throw new TypeError(`'${method}' HTTP method is unsupported.`) + } + + // 3. Normalize method. + method = normalizeMethodRecord[method] ?? normalizeMethod(method) + + // 4. Set request’s method to method. + request.method = method + } + + // 26. If init["signal"] exists, then set signal to it. + if (init.signal !== undefined) { + signal = init.signal + } + + // 27. Set this’s request to request. + this[kState] = request + + // 28. Set this’s signal to a new AbortSignal object with this’s relevant + // Realm. + // TODO: could this be simplified with AbortSignal.any + // (https://dom.spec.whatwg.org/#dom-abortsignal-any) + const ac = new AbortController() + this[kSignal] = ac.signal + this[kSignal][kRealm] = this[kRealm] + + // 29. If signal is not null, then make this’s signal follow signal. + if (signal != null) { + if ( + !signal || + typeof signal.aborted !== 'boolean' || + typeof signal.addEventListener !== 'function' + ) { + throw new TypeError( + "Failed to construct 'Request': member signal is not of type AbortSignal." + ) + } + + if (signal.aborted) { + ac.abort(signal.reason) + } else { + // Keep a strong ref to ac while request object + // is alive. This is needed to prevent AbortController + // from being prematurely garbage collected. + // See, https://github.com/nodejs/undici/issues/1926. + this[kAbortController] = ac + + const acRef = new WeakRef(ac) + const abort = function () { + const ac = acRef.deref() + if (ac !== undefined) { + ac.abort(this.reason) + } + } + + // Third-party AbortControllers may not work with these. + // See, https://github.com/nodejs/undici/pull/1910#issuecomment-1464495619. + try { + // If the max amount of listeners is equal to the default, increase it + // This is only available in node >= v19.9.0 + if (typeof getMaxListeners === 'function' && getMaxListeners(signal) === defaultMaxListeners) { + setMaxListeners(100, signal) + } else if (getEventListeners(signal, 'abort').length >= defaultMaxListeners) { + setMaxListeners(100, signal) + } + } catch {} + + util.addAbortListener(signal, abort) + requestFinalizer.register(ac, { signal, abort }) + } + } + + // 30. Set this’s headers to a new Headers object with this’s relevant + // Realm, whose header list is request’s header list and guard is + // "request". + this[kHeaders] = new Headers(kConstruct) + this[kHeaders][kHeadersList] = request.headersList + this[kHeaders][kGuard] = 'request' + this[kHeaders][kRealm] = this[kRealm] + + // 31. If this’s request’s mode is "no-cors", then: + if (mode === 'no-cors') { + // 1. If this’s request’s method is not a CORS-safelisted method, + // then throw a TypeError. + if (!corsSafeListedMethodsSet.has(request.method)) { + throw new TypeError( + `'${request.method} is unsupported in no-cors mode.` + ) + } + + // 2. Set this’s headers’s guard to "request-no-cors". + this[kHeaders][kGuard] = 'request-no-cors' + } + + // 32. If init is not empty, then: + if (initHasKey) { + /** @type {HeadersList} */ + const headersList = this[kHeaders][kHeadersList] + // 1. Let headers be a copy of this’s headers and its associated header + // list. + // 2. If init["headers"] exists, then set headers to init["headers"]. + const headers = init.headers !== undefined ? init.headers : new HeadersList(headersList) + + // 3. Empty this’s headers’s header list. + headersList.clear() + + // 4. If headers is a Headers object, then for each header in its header + // list, append header’s name/header’s value to this’s headers. + if (headers instanceof HeadersList) { + for (const [key, val] of headers) { + headersList.append(key, val) + } + // Note: Copy the `set-cookie` meta-data. + headersList.cookies = headers.cookies + } else { + // 5. Otherwise, fill this’s headers with headers. + fillHeaders(this[kHeaders], headers) + } + } + + // 33. Let inputBody be input’s request’s body if input is a Request + // object; otherwise null. + const inputBody = input instanceof Request ? input[kState].body : null + + // 34. If either init["body"] exists and is non-null or inputBody is + // non-null, and request’s method is `GET` or `HEAD`, then throw a + // TypeError. + if ( + (init.body != null || inputBody != null) && + (request.method === 'GET' || request.method === 'HEAD') + ) { + throw new TypeError('Request with GET/HEAD method cannot have body.') + } + + // 35. Let initBody be null. + let initBody = null + + // 36. If init["body"] exists and is non-null, then: + if (init.body != null) { + // 1. Let Content-Type be null. + // 2. Set initBody and Content-Type to the result of extracting + // init["body"], with keepalive set to request’s keepalive. + const [extractedBody, contentType] = extractBody( + init.body, + request.keepalive + ) + initBody = extractedBody + + // 3, If Content-Type is non-null and this’s headers’s header list does + // not contain `Content-Type`, then append `Content-Type`/Content-Type to + // this’s headers. + if (contentType && !this[kHeaders][kHeadersList].contains('content-type')) { + this[kHeaders].append('content-type', contentType) + } + } + + // 37. Let inputOrInitBody be initBody if it is non-null; otherwise + // inputBody. + const inputOrInitBody = initBody ?? inputBody + + // 38. If inputOrInitBody is non-null and inputOrInitBody’s source is + // null, then: + if (inputOrInitBody != null && inputOrInitBody.source == null) { + // 1. If initBody is non-null and init["duplex"] does not exist, + // then throw a TypeError. + if (initBody != null && init.duplex == null) { + throw new TypeError('RequestInit: duplex option is required when sending a body.') + } + + // 2. If this’s request’s mode is neither "same-origin" nor "cors", + // then throw a TypeError. + if (request.mode !== 'same-origin' && request.mode !== 'cors') { + throw new TypeError( + 'If request is made from ReadableStream, mode should be "same-origin" or "cors"' + ) + } + + // 3. Set this’s request’s use-CORS-preflight flag. + request.useCORSPreflightFlag = true + } + + // 39. Let finalBody be inputOrInitBody. + let finalBody = inputOrInitBody + + // 40. If initBody is null and inputBody is non-null, then: + if (initBody == null && inputBody != null) { + // 1. If input is unusable, then throw a TypeError. + if (util.isDisturbed(inputBody.stream) || inputBody.stream.locked) { + throw new TypeError( + 'Cannot construct a Request with a Request object that has already been used.' + ) + } + + // 2. Set finalBody to the result of creating a proxy for inputBody. + if (!TransformStream) { + TransformStream = (__nccwpck_require__(3774).TransformStream) + } + + // https://streams.spec.whatwg.org/#readablestream-create-a-proxy + const identityTransform = new TransformStream() + inputBody.stream.pipeThrough(identityTransform) + finalBody = { + source: inputBody.source, + length: inputBody.length, + stream: identityTransform.readable + } + } + + // 41. Set this’s request’s body to finalBody. + this[kState].body = finalBody + } + + // Returns request’s HTTP method, which is "GET" by default. + get method () { + webidl.brandCheck(this, Request) + + // The method getter steps are to return this’s request’s method. + return this[kState].method + } + + // Returns the URL of request as a string. + get url () { + webidl.brandCheck(this, Request) + + // The url getter steps are to return this’s request’s URL, serialized. + return URLSerializer(this[kState].url) + } + + // Returns a Headers object consisting of the headers associated with request. + // Note that headers added in the network layer by the user agent will not + // be accounted for in this object, e.g., the "Host" header. + get headers () { + webidl.brandCheck(this, Request) + + // The headers getter steps are to return this’s headers. + return this[kHeaders] + } + + // Returns the kind of resource requested by request, e.g., "document" + // or "script". + get destination () { + webidl.brandCheck(this, Request) + + // The destination getter are to return this’s request’s destination. + return this[kState].destination + } + + // Returns the referrer of request. Its value can be a same-origin URL if + // explicitly set in init, the empty string to indicate no referrer, and + // "about:client" when defaulting to the global’s default. This is used + // during fetching to determine the value of the `Referer` header of the + // request being made. + get referrer () { + webidl.brandCheck(this, Request) + + // 1. If this’s request’s referrer is "no-referrer", then return the + // empty string. + if (this[kState].referrer === 'no-referrer') { + return '' + } + + // 2. If this’s request’s referrer is "client", then return + // "about:client". + if (this[kState].referrer === 'client') { + return 'about:client' + } + + // Return this’s request’s referrer, serialized. + return this[kState].referrer.toString() + } + + // Returns the referrer policy associated with request. + // This is used during fetching to compute the value of the request’s + // referrer. + get referrerPolicy () { + webidl.brandCheck(this, Request) + + // The referrerPolicy getter steps are to return this’s request’s referrer policy. + return this[kState].referrerPolicy + } + + // Returns the mode associated with request, which is a string indicating + // whether the request will use CORS, or will be restricted to same-origin + // URLs. + get mode () { + webidl.brandCheck(this, Request) + + // The mode getter steps are to return this’s request’s mode. + return this[kState].mode + } + + // Returns the credentials mode associated with request, + // which is a string indicating whether credentials will be sent with the + // request always, never, or only when sent to a same-origin URL. + get credentials () { + // The credentials getter steps are to return this’s request’s credentials mode. + return this[kState].credentials + } + + // Returns the cache mode associated with request, + // which is a string indicating how the request will + // interact with the browser’s cache when fetching. + get cache () { + webidl.brandCheck(this, Request) + + // The cache getter steps are to return this’s request’s cache mode. + return this[kState].cache + } + + // Returns the redirect mode associated with request, + // which is a string indicating how redirects for the + // request will be handled during fetching. A request + // will follow redirects by default. + get redirect () { + webidl.brandCheck(this, Request) + + // The redirect getter steps are to return this’s request’s redirect mode. + return this[kState].redirect + } + + // Returns request’s subresource integrity metadata, which is a + // cryptographic hash of the resource being fetched. Its value + // consists of multiple hashes separated by whitespace. [SRI] + get integrity () { + webidl.brandCheck(this, Request) + + // The integrity getter steps are to return this’s request’s integrity + // metadata. + return this[kState].integrity + } + + // Returns a boolean indicating whether or not request can outlive the + // global in which it was created. + get keepalive () { + webidl.brandCheck(this, Request) + + // The keepalive getter steps are to return this’s request’s keepalive. + return this[kState].keepalive + } + + // Returns a boolean indicating whether or not request is for a reload + // navigation. + get isReloadNavigation () { + webidl.brandCheck(this, Request) + + // The isReloadNavigation getter steps are to return true if this’s + // request’s reload-navigation flag is set; otherwise false. + return this[kState].reloadNavigation + } + + // Returns a boolean indicating whether or not request is for a history + // navigation (a.k.a. back-foward navigation). + get isHistoryNavigation () { + webidl.brandCheck(this, Request) + + // The isHistoryNavigation getter steps are to return true if this’s request’s + // history-navigation flag is set; otherwise false. + return this[kState].historyNavigation + } + + // Returns the signal associated with request, which is an AbortSignal + // object indicating whether or not request has been aborted, and its + // abort event handler. + get signal () { + webidl.brandCheck(this, Request) + + // The signal getter steps are to return this’s signal. + return this[kSignal] + } + + get body () { + webidl.brandCheck(this, Request) + + return this[kState].body ? this[kState].body.stream : null + } + + get bodyUsed () { + webidl.brandCheck(this, Request) + + return !!this[kState].body && util.isDisturbed(this[kState].body.stream) + } + + get duplex () { + webidl.brandCheck(this, Request) + + return 'half' + } + + // Returns a clone of request. + clone () { + webidl.brandCheck(this, Request) + + // 1. If this is unusable, then throw a TypeError. + if (this.bodyUsed || this.body?.locked) { + throw new TypeError('unusable') + } + + // 2. Let clonedRequest be the result of cloning this’s request. + const clonedRequest = cloneRequest(this[kState]) + + // 3. Let clonedRequestObject be the result of creating a Request object, + // given clonedRequest, this’s headers’s guard, and this’s relevant Realm. + const clonedRequestObject = new Request(kConstruct) + clonedRequestObject[kState] = clonedRequest + clonedRequestObject[kRealm] = this[kRealm] + clonedRequestObject[kHeaders] = new Headers(kConstruct) + clonedRequestObject[kHeaders][kHeadersList] = clonedRequest.headersList + clonedRequestObject[kHeaders][kGuard] = this[kHeaders][kGuard] + clonedRequestObject[kHeaders][kRealm] = this[kHeaders][kRealm] + + // 4. Make clonedRequestObject’s signal follow this’s signal. + const ac = new AbortController() + if (this.signal.aborted) { + ac.abort(this.signal.reason) + } else { + util.addAbortListener( + this.signal, + () => { + ac.abort(this.signal.reason) + } + ) + } + clonedRequestObject[kSignal] = ac.signal + + // 4. Return clonedRequestObject. + return clonedRequestObject + } +} + +mixinBody(Request) + +function makeRequest (init) { + // https://fetch.spec.whatwg.org/#requests + const request = { + method: 'GET', + localURLsOnly: false, + unsafeRequest: false, + body: null, + client: null, + reservedClient: null, + replacesClientId: '', + window: 'client', + keepalive: false, + serviceWorkers: 'all', + initiator: '', + destination: '', + priority: null, + origin: 'client', + policyContainer: 'client', + referrer: 'client', + referrerPolicy: '', + mode: 'no-cors', + useCORSPreflightFlag: false, + credentials: 'same-origin', + useCredentials: false, + cache: 'default', + redirect: 'follow', + integrity: '', + cryptoGraphicsNonceMetadata: '', + parserMetadata: '', + reloadNavigation: false, + historyNavigation: false, + userActivation: false, + taintedOrigin: false, + redirectCount: 0, + responseTainting: 'basic', + preventNoCacheCacheControlHeaderModification: false, + done: false, + timingAllowFailed: false, + ...init, + headersList: init.headersList + ? new HeadersList(init.headersList) + : new HeadersList() + } + request.url = request.urlList[0] + return request +} + +// https://fetch.spec.whatwg.org/#concept-request-clone +function cloneRequest (request) { + // To clone a request request, run these steps: + + // 1. Let newRequest be a copy of request, except for its body. + const newRequest = makeRequest({ ...request, body: null }) + + // 2. If request’s body is non-null, set newRequest’s body to the + // result of cloning request’s body. + if (request.body != null) { + newRequest.body = cloneBody(request.body) + } + + // 3. Return newRequest. + return newRequest +} + +Object.defineProperties(Request.prototype, { + method: kEnumerableProperty, + url: kEnumerableProperty, + headers: kEnumerableProperty, + redirect: kEnumerableProperty, + clone: kEnumerableProperty, + signal: kEnumerableProperty, + duplex: kEnumerableProperty, + destination: kEnumerableProperty, + body: kEnumerableProperty, + bodyUsed: kEnumerableProperty, + isHistoryNavigation: kEnumerableProperty, + isReloadNavigation: kEnumerableProperty, + keepalive: kEnumerableProperty, + integrity: kEnumerableProperty, + cache: kEnumerableProperty, + credentials: kEnumerableProperty, + attribute: kEnumerableProperty, + referrerPolicy: kEnumerableProperty, + referrer: kEnumerableProperty, + mode: kEnumerableProperty, + [Symbol.toStringTag]: { + value: 'Request', + configurable: true + } +}) + +webidl.converters.Request = webidl.interfaceConverter( + Request +) + +// https://fetch.spec.whatwg.org/#requestinfo +webidl.converters.RequestInfo = function (V) { + if (typeof V === 'string') { + return webidl.converters.USVString(V) + } + + if (V instanceof Request) { + return webidl.converters.Request(V) + } + + return webidl.converters.USVString(V) +} + +webidl.converters.AbortSignal = webidl.interfaceConverter( + AbortSignal +) + +// https://fetch.spec.whatwg.org/#requestinit +webidl.converters.RequestInit = webidl.dictionaryConverter([ + { + key: 'method', + converter: webidl.converters.ByteString + }, + { + key: 'headers', + converter: webidl.converters.HeadersInit + }, + { + key: 'body', + converter: webidl.nullableConverter( + webidl.converters.BodyInit + ) + }, + { + key: 'referrer', + converter: webidl.converters.USVString + }, + { + key: 'referrerPolicy', + converter: webidl.converters.DOMString, + // https://w3c.github.io/webappsec-referrer-policy/#referrer-policy + allowedValues: referrerPolicy + }, + { + key: 'mode', + converter: webidl.converters.DOMString, + // https://fetch.spec.whatwg.org/#concept-request-mode + allowedValues: requestMode + }, + { + key: 'credentials', + converter: webidl.converters.DOMString, + // https://fetch.spec.whatwg.org/#requestcredentials + allowedValues: requestCredentials + }, + { + key: 'cache', + converter: webidl.converters.DOMString, + // https://fetch.spec.whatwg.org/#requestcache + allowedValues: requestCache + }, + { + key: 'redirect', + converter: webidl.converters.DOMString, + // https://fetch.spec.whatwg.org/#requestredirect + allowedValues: requestRedirect + }, + { + key: 'integrity', + converter: webidl.converters.DOMString + }, + { + key: 'keepalive', + converter: webidl.converters.boolean + }, + { + key: 'signal', + converter: webidl.nullableConverter( + (signal) => webidl.converters.AbortSignal( + signal, + { strict: false } + ) + ) + }, + { + key: 'window', + converter: webidl.converters.any + }, + { + key: 'duplex', + converter: webidl.converters.DOMString, + allowedValues: requestDuplex + } +]) + +module.exports = { Request, makeRequest } + + +/***/ }), + +/***/ 8676: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { Headers, HeadersList, fill } = __nccwpck_require__(6349) +const { extractBody, cloneBody, mixinBody } = __nccwpck_require__(8923) +const util = __nccwpck_require__(3440) +const { kEnumerableProperty } = util +const { + isValidReasonPhrase, + isCancelled, + isAborted, + isBlobLike, + serializeJavascriptValueToJSONString, + isErrorLike, + isomorphicEncode +} = __nccwpck_require__(5523) +const { + redirectStatusSet, + nullBodyStatus, + DOMException +} = __nccwpck_require__(7326) +const { kState, kHeaders, kGuard, kRealm } = __nccwpck_require__(9710) +const { webidl } = __nccwpck_require__(4222) +const { FormData } = __nccwpck_require__(3073) +const { getGlobalOrigin } = __nccwpck_require__(5628) +const { URLSerializer } = __nccwpck_require__(4322) +const { kHeadersList, kConstruct } = __nccwpck_require__(6443) +const assert = __nccwpck_require__(2613) +const { types } = __nccwpck_require__(9023) + +const ReadableStream = globalThis.ReadableStream || (__nccwpck_require__(3774).ReadableStream) +const textEncoder = new TextEncoder('utf-8') + +// https://fetch.spec.whatwg.org/#response-class +class Response { + // Creates network error Response. + static error () { + // TODO + const relevantRealm = { settingsObject: {} } + + // The static error() method steps are to return the result of creating a + // Response object, given a new network error, "immutable", and this’s + // relevant Realm. + const responseObject = new Response() + responseObject[kState] = makeNetworkError() + responseObject[kRealm] = relevantRealm + responseObject[kHeaders][kHeadersList] = responseObject[kState].headersList + responseObject[kHeaders][kGuard] = 'immutable' + responseObject[kHeaders][kRealm] = relevantRealm + return responseObject + } + + // https://fetch.spec.whatwg.org/#dom-response-json + static json (data, init = {}) { + webidl.argumentLengthCheck(arguments, 1, { header: 'Response.json' }) + + if (init !== null) { + init = webidl.converters.ResponseInit(init) + } + + // 1. Let bytes the result of running serialize a JavaScript value to JSON bytes on data. + const bytes = textEncoder.encode( + serializeJavascriptValueToJSONString(data) + ) + + // 2. Let body be the result of extracting bytes. + const body = extractBody(bytes) + + // 3. Let responseObject be the result of creating a Response object, given a new response, + // "response", and this’s relevant Realm. + const relevantRealm = { settingsObject: {} } + const responseObject = new Response() + responseObject[kRealm] = relevantRealm + responseObject[kHeaders][kGuard] = 'response' + responseObject[kHeaders][kRealm] = relevantRealm + + // 4. Perform initialize a response given responseObject, init, and (body, "application/json"). + initializeResponse(responseObject, init, { body: body[0], type: 'application/json' }) + + // 5. Return responseObject. + return responseObject + } + + // Creates a redirect Response that redirects to url with status status. + static redirect (url, status = 302) { + const relevantRealm = { settingsObject: {} } + + webidl.argumentLengthCheck(arguments, 1, { header: 'Response.redirect' }) + + url = webidl.converters.USVString(url) + status = webidl.converters['unsigned short'](status) + + // 1. Let parsedURL be the result of parsing url with current settings + // object’s API base URL. + // 2. If parsedURL is failure, then throw a TypeError. + // TODO: base-URL? + let parsedURL + try { + parsedURL = new URL(url, getGlobalOrigin()) + } catch (err) { + throw Object.assign(new TypeError('Failed to parse URL from ' + url), { + cause: err + }) + } + + // 3. If status is not a redirect status, then throw a RangeError. + if (!redirectStatusSet.has(status)) { + throw new RangeError('Invalid status code ' + status) + } + + // 4. Let responseObject be the result of creating a Response object, + // given a new response, "immutable", and this’s relevant Realm. + const responseObject = new Response() + responseObject[kRealm] = relevantRealm + responseObject[kHeaders][kGuard] = 'immutable' + responseObject[kHeaders][kRealm] = relevantRealm + + // 5. Set responseObject’s response’s status to status. + responseObject[kState].status = status + + // 6. Let value be parsedURL, serialized and isomorphic encoded. + const value = isomorphicEncode(URLSerializer(parsedURL)) + + // 7. Append `Location`/value to responseObject’s response’s header list. + responseObject[kState].headersList.append('location', value) + + // 8. Return responseObject. + return responseObject + } + + // https://fetch.spec.whatwg.org/#dom-response + constructor (body = null, init = {}) { + if (body !== null) { + body = webidl.converters.BodyInit(body) + } + + init = webidl.converters.ResponseInit(init) + + // TODO + this[kRealm] = { settingsObject: {} } + + // 1. Set this’s response to a new response. + this[kState] = makeResponse({}) + + // 2. Set this’s headers to a new Headers object with this’s relevant + // Realm, whose header list is this’s response’s header list and guard + // is "response". + this[kHeaders] = new Headers(kConstruct) + this[kHeaders][kGuard] = 'response' + this[kHeaders][kHeadersList] = this[kState].headersList + this[kHeaders][kRealm] = this[kRealm] + + // 3. Let bodyWithType be null. + let bodyWithType = null + + // 4. If body is non-null, then set bodyWithType to the result of extracting body. + if (body != null) { + const [extractedBody, type] = extractBody(body) + bodyWithType = { body: extractedBody, type } + } + + // 5. Perform initialize a response given this, init, and bodyWithType. + initializeResponse(this, init, bodyWithType) + } + + // Returns response’s type, e.g., "cors". + get type () { + webidl.brandCheck(this, Response) + + // The type getter steps are to return this’s response’s type. + return this[kState].type + } + + // Returns response’s URL, if it has one; otherwise the empty string. + get url () { + webidl.brandCheck(this, Response) + + const urlList = this[kState].urlList + + // The url getter steps are to return the empty string if this’s + // response’s URL is null; otherwise this’s response’s URL, + // serialized with exclude fragment set to true. + const url = urlList[urlList.length - 1] ?? null + + if (url === null) { + return '' + } + + return URLSerializer(url, true) + } + + // Returns whether response was obtained through a redirect. + get redirected () { + webidl.brandCheck(this, Response) + + // The redirected getter steps are to return true if this’s response’s URL + // list has more than one item; otherwise false. + return this[kState].urlList.length > 1 + } + + // Returns response’s status. + get status () { + webidl.brandCheck(this, Response) + + // The status getter steps are to return this’s response’s status. + return this[kState].status + } + + // Returns whether response’s status is an ok status. + get ok () { + webidl.brandCheck(this, Response) + + // The ok getter steps are to return true if this’s response’s status is an + // ok status; otherwise false. + return this[kState].status >= 200 && this[kState].status <= 299 + } + + // Returns response’s status message. + get statusText () { + webidl.brandCheck(this, Response) + + // The statusText getter steps are to return this’s response’s status + // message. + return this[kState].statusText + } + + // Returns response’s headers as Headers. + get headers () { + webidl.brandCheck(this, Response) + + // The headers getter steps are to return this’s headers. + return this[kHeaders] + } + + get body () { + webidl.brandCheck(this, Response) + + return this[kState].body ? this[kState].body.stream : null + } + + get bodyUsed () { + webidl.brandCheck(this, Response) + + return !!this[kState].body && util.isDisturbed(this[kState].body.stream) + } + + // Returns a clone of response. + clone () { + webidl.brandCheck(this, Response) + + // 1. If this is unusable, then throw a TypeError. + if (this.bodyUsed || (this.body && this.body.locked)) { + throw webidl.errors.exception({ + header: 'Response.clone', + message: 'Body has already been consumed.' + }) + } + + // 2. Let clonedResponse be the result of cloning this’s response. + const clonedResponse = cloneResponse(this[kState]) + + // 3. Return the result of creating a Response object, given + // clonedResponse, this’s headers’s guard, and this’s relevant Realm. + const clonedResponseObject = new Response() + clonedResponseObject[kState] = clonedResponse + clonedResponseObject[kRealm] = this[kRealm] + clonedResponseObject[kHeaders][kHeadersList] = clonedResponse.headersList + clonedResponseObject[kHeaders][kGuard] = this[kHeaders][kGuard] + clonedResponseObject[kHeaders][kRealm] = this[kHeaders][kRealm] + + return clonedResponseObject + } +} + +mixinBody(Response) + +Object.defineProperties(Response.prototype, { + type: kEnumerableProperty, + url: kEnumerableProperty, + status: kEnumerableProperty, + ok: kEnumerableProperty, + redirected: kEnumerableProperty, + statusText: kEnumerableProperty, + headers: kEnumerableProperty, + clone: kEnumerableProperty, + body: kEnumerableProperty, + bodyUsed: kEnumerableProperty, + [Symbol.toStringTag]: { + value: 'Response', + configurable: true + } +}) + +Object.defineProperties(Response, { + json: kEnumerableProperty, + redirect: kEnumerableProperty, + error: kEnumerableProperty +}) + +// https://fetch.spec.whatwg.org/#concept-response-clone +function cloneResponse (response) { + // To clone a response response, run these steps: + + // 1. If response is a filtered response, then return a new identical + // filtered response whose internal response is a clone of response’s + // internal response. + if (response.internalResponse) { + return filterResponse( + cloneResponse(response.internalResponse), + response.type + ) + } + + // 2. Let newResponse be a copy of response, except for its body. + const newResponse = makeResponse({ ...response, body: null }) + + // 3. If response’s body is non-null, then set newResponse’s body to the + // result of cloning response’s body. + if (response.body != null) { + newResponse.body = cloneBody(response.body) + } + + // 4. Return newResponse. + return newResponse +} + +function makeResponse (init) { + return { + aborted: false, + rangeRequested: false, + timingAllowPassed: false, + requestIncludesCredentials: false, + type: 'default', + status: 200, + timingInfo: null, + cacheState: '', + statusText: '', + ...init, + headersList: init.headersList + ? new HeadersList(init.headersList) + : new HeadersList(), + urlList: init.urlList ? [...init.urlList] : [] + } +} + +function makeNetworkError (reason) { + const isError = isErrorLike(reason) + return makeResponse({ + type: 'error', + status: 0, + error: isError + ? reason + : new Error(reason ? String(reason) : reason), + aborted: reason && reason.name === 'AbortError' + }) +} + +function makeFilteredResponse (response, state) { + state = { + internalResponse: response, + ...state + } + + return new Proxy(response, { + get (target, p) { + return p in state ? state[p] : target[p] + }, + set (target, p, value) { + assert(!(p in state)) + target[p] = value + return true + } + }) +} + +// https://fetch.spec.whatwg.org/#concept-filtered-response +function filterResponse (response, type) { + // Set response to the following filtered response with response as its + // internal response, depending on request’s response tainting: + if (type === 'basic') { + // A basic filtered response is a filtered response whose type is "basic" + // and header list excludes any headers in internal response’s header list + // whose name is a forbidden response-header name. + + // Note: undici does not implement forbidden response-header names + return makeFilteredResponse(response, { + type: 'basic', + headersList: response.headersList + }) + } else if (type === 'cors') { + // A CORS filtered response is a filtered response whose type is "cors" + // and header list excludes any headers in internal response’s header + // list whose name is not a CORS-safelisted response-header name, given + // internal response’s CORS-exposed header-name list. + + // Note: undici does not implement CORS-safelisted response-header names + return makeFilteredResponse(response, { + type: 'cors', + headersList: response.headersList + }) + } else if (type === 'opaque') { + // An opaque filtered response is a filtered response whose type is + // "opaque", URL list is the empty list, status is 0, status message + // is the empty byte sequence, header list is empty, and body is null. + + return makeFilteredResponse(response, { + type: 'opaque', + urlList: Object.freeze([]), + status: 0, + statusText: '', + body: null + }) + } else if (type === 'opaqueredirect') { + // An opaque-redirect filtered response is a filtered response whose type + // is "opaqueredirect", status is 0, status message is the empty byte + // sequence, header list is empty, and body is null. + + return makeFilteredResponse(response, { + type: 'opaqueredirect', + status: 0, + statusText: '', + headersList: [], + body: null + }) + } else { + assert(false) + } +} + +// https://fetch.spec.whatwg.org/#appropriate-network-error +function makeAppropriateNetworkError (fetchParams, err = null) { + // 1. Assert: fetchParams is canceled. + assert(isCancelled(fetchParams)) + + // 2. Return an aborted network error if fetchParams is aborted; + // otherwise return a network error. + return isAborted(fetchParams) + ? makeNetworkError(Object.assign(new DOMException('The operation was aborted.', 'AbortError'), { cause: err })) + : makeNetworkError(Object.assign(new DOMException('Request was cancelled.'), { cause: err })) +} + +// https://whatpr.org/fetch/1392.html#initialize-a-response +function initializeResponse (response, init, body) { + // 1. If init["status"] is not in the range 200 to 599, inclusive, then + // throw a RangeError. + if (init.status !== null && (init.status < 200 || init.status > 599)) { + throw new RangeError('init["status"] must be in the range of 200 to 599, inclusive.') + } + + // 2. If init["statusText"] does not match the reason-phrase token production, + // then throw a TypeError. + if ('statusText' in init && init.statusText != null) { + // See, https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2: + // reason-phrase = *( HTAB / SP / VCHAR / obs-text ) + if (!isValidReasonPhrase(String(init.statusText))) { + throw new TypeError('Invalid statusText') + } + } + + // 3. Set response’s response’s status to init["status"]. + if ('status' in init && init.status != null) { + response[kState].status = init.status + } + + // 4. Set response’s response’s status message to init["statusText"]. + if ('statusText' in init && init.statusText != null) { + response[kState].statusText = init.statusText + } + + // 5. If init["headers"] exists, then fill response’s headers with init["headers"]. + if ('headers' in init && init.headers != null) { + fill(response[kHeaders], init.headers) + } + + // 6. If body was given, then: + if (body) { + // 1. If response's status is a null body status, then throw a TypeError. + if (nullBodyStatus.includes(response.status)) { + throw webidl.errors.exception({ + header: 'Response constructor', + message: 'Invalid response status code ' + response.status + }) + } + + // 2. Set response's body to body's body. + response[kState].body = body.body + + // 3. If body's type is non-null and response's header list does not contain + // `Content-Type`, then append (`Content-Type`, body's type) to response's header list. + if (body.type != null && !response[kState].headersList.contains('Content-Type')) { + response[kState].headersList.append('content-type', body.type) + } + } +} + +webidl.converters.ReadableStream = webidl.interfaceConverter( + ReadableStream +) + +webidl.converters.FormData = webidl.interfaceConverter( + FormData +) + +webidl.converters.URLSearchParams = webidl.interfaceConverter( + URLSearchParams +) + +// https://fetch.spec.whatwg.org/#typedefdef-xmlhttprequestbodyinit +webidl.converters.XMLHttpRequestBodyInit = function (V) { + if (typeof V === 'string') { + return webidl.converters.USVString(V) + } + + if (isBlobLike(V)) { + return webidl.converters.Blob(V, { strict: false }) + } + + if (types.isArrayBuffer(V) || types.isTypedArray(V) || types.isDataView(V)) { + return webidl.converters.BufferSource(V) + } + + if (util.isFormDataLike(V)) { + return webidl.converters.FormData(V, { strict: false }) + } + + if (V instanceof URLSearchParams) { + return webidl.converters.URLSearchParams(V) + } + + return webidl.converters.DOMString(V) +} + +// https://fetch.spec.whatwg.org/#bodyinit +webidl.converters.BodyInit = function (V) { + if (V instanceof ReadableStream) { + return webidl.converters.ReadableStream(V) + } + + // Note: the spec doesn't include async iterables, + // this is an undici extension. + if (V?.[Symbol.asyncIterator]) { + return V + } + + return webidl.converters.XMLHttpRequestBodyInit(V) +} + +webidl.converters.ResponseInit = webidl.dictionaryConverter([ + { + key: 'status', + converter: webidl.converters['unsigned short'], + defaultValue: 200 + }, + { + key: 'statusText', + converter: webidl.converters.ByteString, + defaultValue: '' + }, + { + key: 'headers', + converter: webidl.converters.HeadersInit + } +]) + +module.exports = { + makeNetworkError, + makeResponse, + makeAppropriateNetworkError, + filterResponse, + Response, + cloneResponse +} + + +/***/ }), + +/***/ 9710: +/***/ ((module) => { + +"use strict"; + + +module.exports = { + kUrl: Symbol('url'), + kHeaders: Symbol('headers'), + kSignal: Symbol('signal'), + kState: Symbol('state'), + kGuard: Symbol('guard'), + kRealm: Symbol('realm') +} + + +/***/ }), + +/***/ 5523: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { redirectStatusSet, referrerPolicySet: referrerPolicyTokens, badPortsSet } = __nccwpck_require__(7326) +const { getGlobalOrigin } = __nccwpck_require__(5628) +const { performance } = __nccwpck_require__(2987) +const { isBlobLike, toUSVString, ReadableStreamFrom } = __nccwpck_require__(3440) +const assert = __nccwpck_require__(2613) +const { isUint8Array } = __nccwpck_require__(8253) + +let supportedHashes = [] + +// https://nodejs.org/api/crypto.html#determining-if-crypto-support-is-unavailable +/** @type {import('crypto')|undefined} */ +let crypto + +try { + crypto = __nccwpck_require__(6982) + const possibleRelevantHashes = ['sha256', 'sha384', 'sha512'] + supportedHashes = crypto.getHashes().filter((hash) => possibleRelevantHashes.includes(hash)) +/* c8 ignore next 3 */ +} catch { +} + +function responseURL (response) { + // https://fetch.spec.whatwg.org/#responses + // A response has an associated URL. It is a pointer to the last URL + // in response’s URL list and null if response’s URL list is empty. + const urlList = response.urlList + const length = urlList.length + return length === 0 ? null : urlList[length - 1].toString() +} + +// https://fetch.spec.whatwg.org/#concept-response-location-url +function responseLocationURL (response, requestFragment) { + // 1. If response’s status is not a redirect status, then return null. + if (!redirectStatusSet.has(response.status)) { + return null + } + + // 2. Let location be the result of extracting header list values given + // `Location` and response’s header list. + let location = response.headersList.get('location') + + // 3. If location is a header value, then set location to the result of + // parsing location with response’s URL. + if (location !== null && isValidHeaderValue(location)) { + location = new URL(location, responseURL(response)) + } + + // 4. If location is a URL whose fragment is null, then set location’s + // fragment to requestFragment. + if (location && !location.hash) { + location.hash = requestFragment + } + + // 5. Return location. + return location +} + +/** @returns {URL} */ +function requestCurrentURL (request) { + return request.urlList[request.urlList.length - 1] +} + +function requestBadPort (request) { + // 1. Let url be request’s current URL. + const url = requestCurrentURL(request) + + // 2. If url’s scheme is an HTTP(S) scheme and url’s port is a bad port, + // then return blocked. + if (urlIsHttpHttpsScheme(url) && badPortsSet.has(url.port)) { + return 'blocked' + } + + // 3. Return allowed. + return 'allowed' +} + +function isErrorLike (object) { + return object instanceof Error || ( + object?.constructor?.name === 'Error' || + object?.constructor?.name === 'DOMException' + ) +} + +// Check whether |statusText| is a ByteString and +// matches the Reason-Phrase token production. +// RFC 2616: https://tools.ietf.org/html/rfc2616 +// RFC 7230: https://tools.ietf.org/html/rfc7230 +// "reason-phrase = *( HTAB / SP / VCHAR / obs-text )" +// https://github.com/chromium/chromium/blob/94.0.4604.1/third_party/blink/renderer/core/fetch/response.cc#L116 +function isValidReasonPhrase (statusText) { + for (let i = 0; i < statusText.length; ++i) { + const c = statusText.charCodeAt(i) + if ( + !( + ( + c === 0x09 || // HTAB + (c >= 0x20 && c <= 0x7e) || // SP / VCHAR + (c >= 0x80 && c <= 0xff) + ) // obs-text + ) + ) { + return false + } + } + return true +} + +/** + * @see https://tools.ietf.org/html/rfc7230#section-3.2.6 + * @param {number} c + */ +function isTokenCharCode (c) { + switch (c) { + case 0x22: + case 0x28: + case 0x29: + case 0x2c: + case 0x2f: + case 0x3a: + case 0x3b: + case 0x3c: + case 0x3d: + case 0x3e: + case 0x3f: + case 0x40: + case 0x5b: + case 0x5c: + case 0x5d: + case 0x7b: + case 0x7d: + // DQUOTE and "(),/:;<=>?@[\]{}" + return false + default: + // VCHAR %x21-7E + return c >= 0x21 && c <= 0x7e + } +} + +/** + * @param {string} characters + */ +function isValidHTTPToken (characters) { + if (characters.length === 0) { + return false + } + for (let i = 0; i < characters.length; ++i) { + if (!isTokenCharCode(characters.charCodeAt(i))) { + return false + } + } + return true +} + +/** + * @see https://fetch.spec.whatwg.org/#header-name + * @param {string} potentialValue + */ +function isValidHeaderName (potentialValue) { + return isValidHTTPToken(potentialValue) +} + +/** + * @see https://fetch.spec.whatwg.org/#header-value + * @param {string} potentialValue + */ +function isValidHeaderValue (potentialValue) { + // - Has no leading or trailing HTTP tab or space bytes. + // - Contains no 0x00 (NUL) or HTTP newline bytes. + if ( + potentialValue.startsWith('\t') || + potentialValue.startsWith(' ') || + potentialValue.endsWith('\t') || + potentialValue.endsWith(' ') + ) { + return false + } + + if ( + potentialValue.includes('\0') || + potentialValue.includes('\r') || + potentialValue.includes('\n') + ) { + return false + } + + return true +} + +// https://w3c.github.io/webappsec-referrer-policy/#set-requests-referrer-policy-on-redirect +function setRequestReferrerPolicyOnRedirect (request, actualResponse) { + // Given a request request and a response actualResponse, this algorithm + // updates request’s referrer policy according to the Referrer-Policy + // header (if any) in actualResponse. + + // 1. Let policy be the result of executing § 8.1 Parse a referrer policy + // from a Referrer-Policy header on actualResponse. + + // 8.1 Parse a referrer policy from a Referrer-Policy header + // 1. Let policy-tokens be the result of extracting header list values given `Referrer-Policy` and response’s header list. + const { headersList } = actualResponse + // 2. Let policy be the empty string. + // 3. For each token in policy-tokens, if token is a referrer policy and token is not the empty string, then set policy to token. + // 4. Return policy. + const policyHeader = (headersList.get('referrer-policy') ?? '').split(',') + + // Note: As the referrer-policy can contain multiple policies + // separated by comma, we need to loop through all of them + // and pick the first valid one. + // Ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy#specify_a_fallback_policy + let policy = '' + if (policyHeader.length > 0) { + // The right-most policy takes precedence. + // The left-most policy is the fallback. + for (let i = policyHeader.length; i !== 0; i--) { + const token = policyHeader[i - 1].trim() + if (referrerPolicyTokens.has(token)) { + policy = token + break + } + } + } + + // 2. If policy is not the empty string, then set request’s referrer policy to policy. + if (policy !== '') { + request.referrerPolicy = policy + } +} + +// https://fetch.spec.whatwg.org/#cross-origin-resource-policy-check +function crossOriginResourcePolicyCheck () { + // TODO + return 'allowed' +} + +// https://fetch.spec.whatwg.org/#concept-cors-check +function corsCheck () { + // TODO + return 'success' +} + +// https://fetch.spec.whatwg.org/#concept-tao-check +function TAOCheck () { + // TODO + return 'success' +} + +function appendFetchMetadata (httpRequest) { + // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-dest-header + // TODO + + // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-mode-header + + // 1. Assert: r’s url is a potentially trustworthy URL. + // TODO + + // 2. Let header be a Structured Header whose value is a token. + let header = null + + // 3. Set header’s value to r’s mode. + header = httpRequest.mode + + // 4. Set a structured field value `Sec-Fetch-Mode`/header in r’s header list. + httpRequest.headersList.set('sec-fetch-mode', header) + + // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-site-header + // TODO + + // https://w3c.github.io/webappsec-fetch-metadata/#sec-fetch-user-header + // TODO +} + +// https://fetch.spec.whatwg.org/#append-a-request-origin-header +function appendRequestOriginHeader (request) { + // 1. Let serializedOrigin be the result of byte-serializing a request origin with request. + let serializedOrigin = request.origin + + // 2. If request’s response tainting is "cors" or request’s mode is "websocket", then append (`Origin`, serializedOrigin) to request’s header list. + if (request.responseTainting === 'cors' || request.mode === 'websocket') { + if (serializedOrigin) { + request.headersList.append('origin', serializedOrigin) + } + + // 3. Otherwise, if request’s method is neither `GET` nor `HEAD`, then: + } else if (request.method !== 'GET' && request.method !== 'HEAD') { + // 1. Switch on request’s referrer policy: + switch (request.referrerPolicy) { + case 'no-referrer': + // Set serializedOrigin to `null`. + serializedOrigin = null + break + case 'no-referrer-when-downgrade': + case 'strict-origin': + case 'strict-origin-when-cross-origin': + // If request’s origin is a tuple origin, its scheme is "https", and request’s current URL’s scheme is not "https", then set serializedOrigin to `null`. + if (request.origin && urlHasHttpsScheme(request.origin) && !urlHasHttpsScheme(requestCurrentURL(request))) { + serializedOrigin = null + } + break + case 'same-origin': + // If request’s origin is not same origin with request’s current URL’s origin, then set serializedOrigin to `null`. + if (!sameOrigin(request, requestCurrentURL(request))) { + serializedOrigin = null + } + break + default: + // Do nothing. + } + + if (serializedOrigin) { + // 2. Append (`Origin`, serializedOrigin) to request’s header list. + request.headersList.append('origin', serializedOrigin) + } + } +} + +function coarsenedSharedCurrentTime (crossOriginIsolatedCapability) { + // TODO + return performance.now() +} + +// https://fetch.spec.whatwg.org/#create-an-opaque-timing-info +function createOpaqueTimingInfo (timingInfo) { + return { + startTime: timingInfo.startTime ?? 0, + redirectStartTime: 0, + redirectEndTime: 0, + postRedirectStartTime: timingInfo.startTime ?? 0, + finalServiceWorkerStartTime: 0, + finalNetworkResponseStartTime: 0, + finalNetworkRequestStartTime: 0, + endTime: 0, + encodedBodySize: 0, + decodedBodySize: 0, + finalConnectionTimingInfo: null + } +} + +// https://html.spec.whatwg.org/multipage/origin.html#policy-container +function makePolicyContainer () { + // Note: the fetch spec doesn't make use of embedder policy or CSP list + return { + referrerPolicy: 'strict-origin-when-cross-origin' + } +} + +// https://html.spec.whatwg.org/multipage/origin.html#clone-a-policy-container +function clonePolicyContainer (policyContainer) { + return { + referrerPolicy: policyContainer.referrerPolicy + } +} + +// https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer +function determineRequestsReferrer (request) { + // 1. Let policy be request's referrer policy. + const policy = request.referrerPolicy + + // Note: policy cannot (shouldn't) be null or an empty string. + assert(policy) + + // 2. Let environment be request’s client. + + let referrerSource = null + + // 3. Switch on request’s referrer: + if (request.referrer === 'client') { + // Note: node isn't a browser and doesn't implement document/iframes, + // so we bypass this step and replace it with our own. + + const globalOrigin = getGlobalOrigin() + + if (!globalOrigin || globalOrigin.origin === 'null') { + return 'no-referrer' + } + + // note: we need to clone it as it's mutated + referrerSource = new URL(globalOrigin) + } else if (request.referrer instanceof URL) { + // Let referrerSource be request’s referrer. + referrerSource = request.referrer + } + + // 4. Let request’s referrerURL be the result of stripping referrerSource for + // use as a referrer. + let referrerURL = stripURLForReferrer(referrerSource) + + // 5. Let referrerOrigin be the result of stripping referrerSource for use as + // a referrer, with the origin-only flag set to true. + const referrerOrigin = stripURLForReferrer(referrerSource, true) + + // 6. If the result of serializing referrerURL is a string whose length is + // greater than 4096, set referrerURL to referrerOrigin. + if (referrerURL.toString().length > 4096) { + referrerURL = referrerOrigin + } + + const areSameOrigin = sameOrigin(request, referrerURL) + const isNonPotentiallyTrustWorthy = isURLPotentiallyTrustworthy(referrerURL) && + !isURLPotentiallyTrustworthy(request.url) + + // 8. Execute the switch statements corresponding to the value of policy: + switch (policy) { + case 'origin': return referrerOrigin != null ? referrerOrigin : stripURLForReferrer(referrerSource, true) + case 'unsafe-url': return referrerURL + case 'same-origin': + return areSameOrigin ? referrerOrigin : 'no-referrer' + case 'origin-when-cross-origin': + return areSameOrigin ? referrerURL : referrerOrigin + case 'strict-origin-when-cross-origin': { + const currentURL = requestCurrentURL(request) + + // 1. If the origin of referrerURL and the origin of request’s current + // URL are the same, then return referrerURL. + if (sameOrigin(referrerURL, currentURL)) { + return referrerURL + } + + // 2. If referrerURL is a potentially trustworthy URL and request’s + // current URL is not a potentially trustworthy URL, then return no + // referrer. + if (isURLPotentiallyTrustworthy(referrerURL) && !isURLPotentiallyTrustworthy(currentURL)) { + return 'no-referrer' + } + + // 3. Return referrerOrigin. + return referrerOrigin + } + case 'strict-origin': // eslint-disable-line + /** + * 1. If referrerURL is a potentially trustworthy URL and + * request’s current URL is not a potentially trustworthy URL, + * then return no referrer. + * 2. Return referrerOrigin + */ + case 'no-referrer-when-downgrade': // eslint-disable-line + /** + * 1. If referrerURL is a potentially trustworthy URL and + * request’s current URL is not a potentially trustworthy URL, + * then return no referrer. + * 2. Return referrerOrigin + */ + + default: // eslint-disable-line + return isNonPotentiallyTrustWorthy ? 'no-referrer' : referrerOrigin + } +} + +/** + * @see https://w3c.github.io/webappsec-referrer-policy/#strip-url + * @param {URL} url + * @param {boolean|undefined} originOnly + */ +function stripURLForReferrer (url, originOnly) { + // 1. Assert: url is a URL. + assert(url instanceof URL) + + // 2. If url’s scheme is a local scheme, then return no referrer. + if (url.protocol === 'file:' || url.protocol === 'about:' || url.protocol === 'blank:') { + return 'no-referrer' + } + + // 3. Set url’s username to the empty string. + url.username = '' + + // 4. Set url’s password to the empty string. + url.password = '' + + // 5. Set url’s fragment to null. + url.hash = '' + + // 6. If the origin-only flag is true, then: + if (originOnly) { + // 1. Set url’s path to « the empty string ». + url.pathname = '' + + // 2. Set url’s query to null. + url.search = '' + } + + // 7. Return url. + return url +} + +function isURLPotentiallyTrustworthy (url) { + if (!(url instanceof URL)) { + return false + } + + // If child of about, return true + if (url.href === 'about:blank' || url.href === 'about:srcdoc') { + return true + } + + // If scheme is data, return true + if (url.protocol === 'data:') return true + + // If file, return true + if (url.protocol === 'file:') return true + + return isOriginPotentiallyTrustworthy(url.origin) + + function isOriginPotentiallyTrustworthy (origin) { + // If origin is explicitly null, return false + if (origin == null || origin === 'null') return false + + const originAsURL = new URL(origin) + + // If secure, return true + if (originAsURL.protocol === 'https:' || originAsURL.protocol === 'wss:') { + return true + } + + // If localhost or variants, return true + if (/^127(?:\.[0-9]+){0,2}\.[0-9]+$|^\[(?:0*:)*?:?0*1\]$/.test(originAsURL.hostname) || + (originAsURL.hostname === 'localhost' || originAsURL.hostname.includes('localhost.')) || + (originAsURL.hostname.endsWith('.localhost'))) { + return true + } + + // If any other, return false + return false + } +} + +/** + * @see https://w3c.github.io/webappsec-subresource-integrity/#does-response-match-metadatalist + * @param {Uint8Array} bytes + * @param {string} metadataList + */ +function bytesMatch (bytes, metadataList) { + // If node is not built with OpenSSL support, we cannot check + // a request's integrity, so allow it by default (the spec will + // allow requests if an invalid hash is given, as precedence). + /* istanbul ignore if: only if node is built with --without-ssl */ + if (crypto === undefined) { + return true + } + + // 1. Let parsedMetadata be the result of parsing metadataList. + const parsedMetadata = parseMetadata(metadataList) + + // 2. If parsedMetadata is no metadata, return true. + if (parsedMetadata === 'no metadata') { + return true + } + + // 3. If response is not eligible for integrity validation, return false. + // TODO + + // 4. If parsedMetadata is the empty set, return true. + if (parsedMetadata.length === 0) { + return true + } + + // 5. Let metadata be the result of getting the strongest + // metadata from parsedMetadata. + const strongest = getStrongestMetadata(parsedMetadata) + const metadata = filterMetadataListByAlgorithm(parsedMetadata, strongest) + + // 6. For each item in metadata: + for (const item of metadata) { + // 1. Let algorithm be the alg component of item. + const algorithm = item.algo + + // 2. Let expectedValue be the val component of item. + const expectedValue = item.hash + + // See https://github.com/web-platform-tests/wpt/commit/e4c5cc7a5e48093220528dfdd1c4012dc3837a0e + // "be liberal with padding". This is annoying, and it's not even in the spec. + + // 3. Let actualValue be the result of applying algorithm to bytes. + let actualValue = crypto.createHash(algorithm).update(bytes).digest('base64') + + if (actualValue[actualValue.length - 1] === '=') { + if (actualValue[actualValue.length - 2] === '=') { + actualValue = actualValue.slice(0, -2) + } else { + actualValue = actualValue.slice(0, -1) + } + } + + // 4. If actualValue is a case-sensitive match for expectedValue, + // return true. + if (compareBase64Mixed(actualValue, expectedValue)) { + return true + } + } + + // 7. Return false. + return false +} + +// https://w3c.github.io/webappsec-subresource-integrity/#grammardef-hash-with-options +// https://www.w3.org/TR/CSP2/#source-list-syntax +// https://www.rfc-editor.org/rfc/rfc5234#appendix-B.1 +const parseHashWithOptions = /(?sha256|sha384|sha512)-((?[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i + +/** + * @see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata + * @param {string} metadata + */ +function parseMetadata (metadata) { + // 1. Let result be the empty set. + /** @type {{ algo: string, hash: string }[]} */ + const result = [] + + // 2. Let empty be equal to true. + let empty = true + + // 3. For each token returned by splitting metadata on spaces: + for (const token of metadata.split(' ')) { + // 1. Set empty to false. + empty = false + + // 2. Parse token as a hash-with-options. + const parsedToken = parseHashWithOptions.exec(token) + + // 3. If token does not parse, continue to the next token. + if ( + parsedToken === null || + parsedToken.groups === undefined || + parsedToken.groups.algo === undefined + ) { + // Note: Chromium blocks the request at this point, but Firefox + // gives a warning that an invalid integrity was given. The + // correct behavior is to ignore these, and subsequently not + // check the integrity of the resource. + continue + } + + // 4. Let algorithm be the hash-algo component of token. + const algorithm = parsedToken.groups.algo.toLowerCase() + + // 5. If algorithm is a hash function recognized by the user + // agent, add the parsed token to result. + if (supportedHashes.includes(algorithm)) { + result.push(parsedToken.groups) + } + } + + // 4. Return no metadata if empty is true, otherwise return result. + if (empty === true) { + return 'no metadata' + } + + return result +} + +/** + * @param {{ algo: 'sha256' | 'sha384' | 'sha512' }[]} metadataList + */ +function getStrongestMetadata (metadataList) { + // Let algorithm be the algo component of the first item in metadataList. + // Can be sha256 + let algorithm = metadataList[0].algo + // If the algorithm is sha512, then it is the strongest + // and we can return immediately + if (algorithm[3] === '5') { + return algorithm + } + + for (let i = 1; i < metadataList.length; ++i) { + const metadata = metadataList[i] + // If the algorithm is sha512, then it is the strongest + // and we can break the loop immediately + if (metadata.algo[3] === '5') { + algorithm = 'sha512' + break + // If the algorithm is sha384, then a potential sha256 or sha384 is ignored + } else if (algorithm[3] === '3') { + continue + // algorithm is sha256, check if algorithm is sha384 and if so, set it as + // the strongest + } else if (metadata.algo[3] === '3') { + algorithm = 'sha384' + } + } + return algorithm +} + +function filterMetadataListByAlgorithm (metadataList, algorithm) { + if (metadataList.length === 1) { + return metadataList + } + + let pos = 0 + for (let i = 0; i < metadataList.length; ++i) { + if (metadataList[i].algo === algorithm) { + metadataList[pos++] = metadataList[i] + } + } + + metadataList.length = pos + + return metadataList +} + +/** + * Compares two base64 strings, allowing for base64url + * in the second string. + * +* @param {string} actualValue always base64 + * @param {string} expectedValue base64 or base64url + * @returns {boolean} + */ +function compareBase64Mixed (actualValue, expectedValue) { + if (actualValue.length !== expectedValue.length) { + return false + } + for (let i = 0; i < actualValue.length; ++i) { + if (actualValue[i] !== expectedValue[i]) { + if ( + (actualValue[i] === '+' && expectedValue[i] === '-') || + (actualValue[i] === '/' && expectedValue[i] === '_') + ) { + continue + } + return false + } + } + + return true +} + +// https://w3c.github.io/webappsec-upgrade-insecure-requests/#upgrade-request +function tryUpgradeRequestToAPotentiallyTrustworthyURL (request) { + // TODO +} + +/** + * @link {https://html.spec.whatwg.org/multipage/origin.html#same-origin} + * @param {URL} A + * @param {URL} B + */ +function sameOrigin (A, B) { + // 1. If A and B are the same opaque origin, then return true. + if (A.origin === B.origin && A.origin === 'null') { + return true + } + + // 2. If A and B are both tuple origins and their schemes, + // hosts, and port are identical, then return true. + if (A.protocol === B.protocol && A.hostname === B.hostname && A.port === B.port) { + return true + } + + // 3. Return false. + return false +} + +function createDeferredPromise () { + let res + let rej + const promise = new Promise((resolve, reject) => { + res = resolve + rej = reject + }) + + return { promise, resolve: res, reject: rej } +} + +function isAborted (fetchParams) { + return fetchParams.controller.state === 'aborted' +} + +function isCancelled (fetchParams) { + return fetchParams.controller.state === 'aborted' || + fetchParams.controller.state === 'terminated' +} + +const normalizeMethodRecord = { + delete: 'DELETE', + DELETE: 'DELETE', + get: 'GET', + GET: 'GET', + head: 'HEAD', + HEAD: 'HEAD', + options: 'OPTIONS', + OPTIONS: 'OPTIONS', + post: 'POST', + POST: 'POST', + put: 'PUT', + PUT: 'PUT' +} + +// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`. +Object.setPrototypeOf(normalizeMethodRecord, null) + +/** + * @see https://fetch.spec.whatwg.org/#concept-method-normalize + * @param {string} method + */ +function normalizeMethod (method) { + return normalizeMethodRecord[method.toLowerCase()] ?? method +} + +// https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string +function serializeJavascriptValueToJSONString (value) { + // 1. Let result be ? Call(%JSON.stringify%, undefined, « value »). + const result = JSON.stringify(value) + + // 2. If result is undefined, then throw a TypeError. + if (result === undefined) { + throw new TypeError('Value is not JSON serializable') + } + + // 3. Assert: result is a string. + assert(typeof result === 'string') + + // 4. Return result. + return result +} + +// https://tc39.es/ecma262/#sec-%25iteratorprototype%25-object +const esIteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())) + +/** + * @see https://webidl.spec.whatwg.org/#dfn-iterator-prototype-object + * @param {() => unknown[]} iterator + * @param {string} name name of the instance + * @param {'key'|'value'|'key+value'} kind + */ +function makeIterator (iterator, name, kind) { + const object = { + index: 0, + kind, + target: iterator + } + + const i = { + next () { + // 1. Let interface be the interface for which the iterator prototype object exists. + + // 2. Let thisValue be the this value. + + // 3. Let object be ? ToObject(thisValue). + + // 4. If object is a platform object, then perform a security + // check, passing: + + // 5. If object is not a default iterator object for interface, + // then throw a TypeError. + if (Object.getPrototypeOf(this) !== i) { + throw new TypeError( + `'next' called on an object that does not implement interface ${name} Iterator.` + ) + } + + // 6. Let index be object’s index. + // 7. Let kind be object’s kind. + // 8. Let values be object’s target's value pairs to iterate over. + const { index, kind, target } = object + const values = target() + + // 9. Let len be the length of values. + const len = values.length + + // 10. If index is greater than or equal to len, then return + // CreateIterResultObject(undefined, true). + if (index >= len) { + return { value: undefined, done: true } + } + + // 11. Let pair be the entry in values at index index. + const pair = values[index] + + // 12. Set object’s index to index + 1. + object.index = index + 1 + + // 13. Return the iterator result for pair and kind. + return iteratorResult(pair, kind) + }, + // The class string of an iterator prototype object for a given interface is the + // result of concatenating the identifier of the interface and the string " Iterator". + [Symbol.toStringTag]: `${name} Iterator` + } + + // The [[Prototype]] internal slot of an iterator prototype object must be %IteratorPrototype%. + Object.setPrototypeOf(i, esIteratorPrototype) + // esIteratorPrototype needs to be the prototype of i + // which is the prototype of an empty object. Yes, it's confusing. + return Object.setPrototypeOf({}, i) +} + +// https://webidl.spec.whatwg.org/#iterator-result +function iteratorResult (pair, kind) { + let result + + // 1. Let result be a value determined by the value of kind: + switch (kind) { + case 'key': { + // 1. Let idlKey be pair’s key. + // 2. Let key be the result of converting idlKey to an + // ECMAScript value. + // 3. result is key. + result = pair[0] + break + } + case 'value': { + // 1. Let idlValue be pair’s value. + // 2. Let value be the result of converting idlValue to + // an ECMAScript value. + // 3. result is value. + result = pair[1] + break + } + case 'key+value': { + // 1. Let idlKey be pair’s key. + // 2. Let idlValue be pair’s value. + // 3. Let key be the result of converting idlKey to an + // ECMAScript value. + // 4. Let value be the result of converting idlValue to + // an ECMAScript value. + // 5. Let array be ! ArrayCreate(2). + // 6. Call ! CreateDataProperty(array, "0", key). + // 7. Call ! CreateDataProperty(array, "1", value). + // 8. result is array. + result = pair + break + } + } + + // 2. Return CreateIterResultObject(result, false). + return { value: result, done: false } +} + +/** + * @see https://fetch.spec.whatwg.org/#body-fully-read + */ +async function fullyReadBody (body, processBody, processBodyError) { + // 1. If taskDestination is null, then set taskDestination to + // the result of starting a new parallel queue. + + // 2. Let successSteps given a byte sequence bytes be to queue a + // fetch task to run processBody given bytes, with taskDestination. + const successSteps = processBody + + // 3. Let errorSteps be to queue a fetch task to run processBodyError, + // with taskDestination. + const errorSteps = processBodyError + + // 4. Let reader be the result of getting a reader for body’s stream. + // If that threw an exception, then run errorSteps with that + // exception and return. + let reader + + try { + reader = body.stream.getReader() + } catch (e) { + errorSteps(e) + return + } + + // 5. Read all bytes from reader, given successSteps and errorSteps. + try { + const result = await readAllBytes(reader) + successSteps(result) + } catch (e) { + errorSteps(e) + } +} + +/** @type {ReadableStream} */ +let ReadableStream = globalThis.ReadableStream + +function isReadableStreamLike (stream) { + if (!ReadableStream) { + ReadableStream = (__nccwpck_require__(3774).ReadableStream) + } + + return stream instanceof ReadableStream || ( + stream[Symbol.toStringTag] === 'ReadableStream' && + typeof stream.tee === 'function' + ) +} + +const MAXIMUM_ARGUMENT_LENGTH = 65535 + +/** + * @see https://infra.spec.whatwg.org/#isomorphic-decode + * @param {number[]|Uint8Array} input + */ +function isomorphicDecode (input) { + // 1. To isomorphic decode a byte sequence input, return a string whose code point + // length is equal to input’s length and whose code points have the same values + // as the values of input’s bytes, in the same order. + + if (input.length < MAXIMUM_ARGUMENT_LENGTH) { + return String.fromCharCode(...input) + } + + return input.reduce((previous, current) => previous + String.fromCharCode(current), '') +} + +/** + * @param {ReadableStreamController} controller + */ +function readableStreamClose (controller) { + try { + controller.close() + } catch (err) { + // TODO: add comment explaining why this error occurs. + if (!err.message.includes('Controller is already closed')) { + throw err + } + } +} + +/** + * @see https://infra.spec.whatwg.org/#isomorphic-encode + * @param {string} input + */ +function isomorphicEncode (input) { + // 1. Assert: input contains no code points greater than U+00FF. + for (let i = 0; i < input.length; i++) { + assert(input.charCodeAt(i) <= 0xFF) + } + + // 2. Return a byte sequence whose length is equal to input’s code + // point length and whose bytes have the same values as the + // values of input’s code points, in the same order + return input +} + +/** + * @see https://streams.spec.whatwg.org/#readablestreamdefaultreader-read-all-bytes + * @see https://streams.spec.whatwg.org/#read-loop + * @param {ReadableStreamDefaultReader} reader + */ +async function readAllBytes (reader) { + const bytes = [] + let byteLength = 0 + + while (true) { + const { done, value: chunk } = await reader.read() + + if (done) { + // 1. Call successSteps with bytes. + return Buffer.concat(bytes, byteLength) + } + + // 1. If chunk is not a Uint8Array object, call failureSteps + // with a TypeError and abort these steps. + if (!isUint8Array(chunk)) { + throw new TypeError('Received non-Uint8Array chunk') + } + + // 2. Append the bytes represented by chunk to bytes. + bytes.push(chunk) + byteLength += chunk.length + + // 3. Read-loop given reader, bytes, successSteps, and failureSteps. + } +} + +/** + * @see https://fetch.spec.whatwg.org/#is-local + * @param {URL} url + */ +function urlIsLocal (url) { + assert('protocol' in url) // ensure it's a url object + + const protocol = url.protocol + + return protocol === 'about:' || protocol === 'blob:' || protocol === 'data:' +} + +/** + * @param {string|URL} url + */ +function urlHasHttpsScheme (url) { + if (typeof url === 'string') { + return url.startsWith('https:') + } + + return url.protocol === 'https:' +} + +/** + * @see https://fetch.spec.whatwg.org/#http-scheme + * @param {URL} url + */ +function urlIsHttpHttpsScheme (url) { + assert('protocol' in url) // ensure it's a url object + + const protocol = url.protocol + + return protocol === 'http:' || protocol === 'https:' +} + +/** + * Fetch supports node >= 16.8.0, but Object.hasOwn was added in v16.9.0. + */ +const hasOwn = Object.hasOwn || ((dict, key) => Object.prototype.hasOwnProperty.call(dict, key)) + +module.exports = { + isAborted, + isCancelled, + createDeferredPromise, + ReadableStreamFrom, + toUSVString, + tryUpgradeRequestToAPotentiallyTrustworthyURL, + coarsenedSharedCurrentTime, + determineRequestsReferrer, + makePolicyContainer, + clonePolicyContainer, + appendFetchMetadata, + appendRequestOriginHeader, + TAOCheck, + corsCheck, + crossOriginResourcePolicyCheck, + createOpaqueTimingInfo, + setRequestReferrerPolicyOnRedirect, + isValidHTTPToken, + requestBadPort, + requestCurrentURL, + responseURL, + responseLocationURL, + isBlobLike, + isURLPotentiallyTrustworthy, + isValidReasonPhrase, + sameOrigin, + normalizeMethod, + serializeJavascriptValueToJSONString, + makeIterator, + isValidHeaderName, + isValidHeaderValue, + hasOwn, + isErrorLike, + fullyReadBody, + bytesMatch, + isReadableStreamLike, + readableStreamClose, + isomorphicEncode, + isomorphicDecode, + urlIsLocal, + urlHasHttpsScheme, + urlIsHttpHttpsScheme, + readAllBytes, + normalizeMethodRecord, + parseMetadata +} + + +/***/ }), + +/***/ 4222: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { types } = __nccwpck_require__(9023) +const { hasOwn, toUSVString } = __nccwpck_require__(5523) + +/** @type {import('../../types/webidl').Webidl} */ +const webidl = {} +webidl.converters = {} +webidl.util = {} +webidl.errors = {} + +webidl.errors.exception = function (message) { + return new TypeError(`${message.header}: ${message.message}`) +} + +webidl.errors.conversionFailed = function (context) { + const plural = context.types.length === 1 ? '' : ' one of' + const message = + `${context.argument} could not be converted to` + + `${plural}: ${context.types.join(', ')}.` + + return webidl.errors.exception({ + header: context.prefix, + message + }) +} + +webidl.errors.invalidArgument = function (context) { + return webidl.errors.exception({ + header: context.prefix, + message: `"${context.value}" is an invalid ${context.type}.` + }) +} + +// https://webidl.spec.whatwg.org/#implements +webidl.brandCheck = function (V, I, opts = undefined) { + if (opts?.strict !== false && !(V instanceof I)) { + throw new TypeError('Illegal invocation') + } else { + return V?.[Symbol.toStringTag] === I.prototype[Symbol.toStringTag] + } +} + +webidl.argumentLengthCheck = function ({ length }, min, ctx) { + if (length < min) { + throw webidl.errors.exception({ + message: `${min} argument${min !== 1 ? 's' : ''} required, ` + + `but${length ? ' only' : ''} ${length} found.`, + ...ctx + }) + } +} + +webidl.illegalConstructor = function () { + throw webidl.errors.exception({ + header: 'TypeError', + message: 'Illegal constructor' + }) +} + +// https://tc39.es/ecma262/#sec-ecmascript-data-types-and-values +webidl.util.Type = function (V) { + switch (typeof V) { + case 'undefined': return 'Undefined' + case 'boolean': return 'Boolean' + case 'string': return 'String' + case 'symbol': return 'Symbol' + case 'number': return 'Number' + case 'bigint': return 'BigInt' + case 'function': + case 'object': { + if (V === null) { + return 'Null' + } + + return 'Object' + } + } +} + +// https://webidl.spec.whatwg.org/#abstract-opdef-converttoint +webidl.util.ConvertToInt = function (V, bitLength, signedness, opts = {}) { + let upperBound + let lowerBound + + // 1. If bitLength is 64, then: + if (bitLength === 64) { + // 1. Let upperBound be 2^53 − 1. + upperBound = Math.pow(2, 53) - 1 + + // 2. If signedness is "unsigned", then let lowerBound be 0. + if (signedness === 'unsigned') { + lowerBound = 0 + } else { + // 3. Otherwise let lowerBound be −2^53 + 1. + lowerBound = Math.pow(-2, 53) + 1 + } + } else if (signedness === 'unsigned') { + // 2. Otherwise, if signedness is "unsigned", then: + + // 1. Let lowerBound be 0. + lowerBound = 0 + + // 2. Let upperBound be 2^bitLength − 1. + upperBound = Math.pow(2, bitLength) - 1 + } else { + // 3. Otherwise: + + // 1. Let lowerBound be -2^bitLength − 1. + lowerBound = Math.pow(-2, bitLength) - 1 + + // 2. Let upperBound be 2^bitLength − 1 − 1. + upperBound = Math.pow(2, bitLength - 1) - 1 + } + + // 4. Let x be ? ToNumber(V). + let x = Number(V) + + // 5. If x is −0, then set x to +0. + if (x === 0) { + x = 0 + } + + // 6. If the conversion is to an IDL type associated + // with the [EnforceRange] extended attribute, then: + if (opts.enforceRange === true) { + // 1. If x is NaN, +∞, or −∞, then throw a TypeError. + if ( + Number.isNaN(x) || + x === Number.POSITIVE_INFINITY || + x === Number.NEGATIVE_INFINITY + ) { + throw webidl.errors.exception({ + header: 'Integer conversion', + message: `Could not convert ${V} to an integer.` + }) + } + + // 2. Set x to IntegerPart(x). + x = webidl.util.IntegerPart(x) + + // 3. If x < lowerBound or x > upperBound, then + // throw a TypeError. + if (x < lowerBound || x > upperBound) { + throw webidl.errors.exception({ + header: 'Integer conversion', + message: `Value must be between ${lowerBound}-${upperBound}, got ${x}.` + }) + } + + // 4. Return x. + return x + } + + // 7. If x is not NaN and the conversion is to an IDL + // type associated with the [Clamp] extended + // attribute, then: + if (!Number.isNaN(x) && opts.clamp === true) { + // 1. Set x to min(max(x, lowerBound), upperBound). + x = Math.min(Math.max(x, lowerBound), upperBound) + + // 2. Round x to the nearest integer, choosing the + // even integer if it lies halfway between two, + // and choosing +0 rather than −0. + if (Math.floor(x) % 2 === 0) { + x = Math.floor(x) + } else { + x = Math.ceil(x) + } + + // 3. Return x. + return x + } + + // 8. If x is NaN, +0, +∞, or −∞, then return +0. + if ( + Number.isNaN(x) || + (x === 0 && Object.is(0, x)) || + x === Number.POSITIVE_INFINITY || + x === Number.NEGATIVE_INFINITY + ) { + return 0 + } + + // 9. Set x to IntegerPart(x). + x = webidl.util.IntegerPart(x) + + // 10. Set x to x modulo 2^bitLength. + x = x % Math.pow(2, bitLength) + + // 11. If signedness is "signed" and x ≥ 2^bitLength − 1, + // then return x − 2^bitLength. + if (signedness === 'signed' && x >= Math.pow(2, bitLength) - 1) { + return x - Math.pow(2, bitLength) + } + + // 12. Otherwise, return x. + return x +} + +// https://webidl.spec.whatwg.org/#abstract-opdef-integerpart +webidl.util.IntegerPart = function (n) { + // 1. Let r be floor(abs(n)). + const r = Math.floor(Math.abs(n)) + + // 2. If n < 0, then return -1 × r. + if (n < 0) { + return -1 * r + } + + // 3. Otherwise, return r. + return r +} + +// https://webidl.spec.whatwg.org/#es-sequence +webidl.sequenceConverter = function (converter) { + return (V) => { + // 1. If Type(V) is not Object, throw a TypeError. + if (webidl.util.Type(V) !== 'Object') { + throw webidl.errors.exception({ + header: 'Sequence', + message: `Value of type ${webidl.util.Type(V)} is not an Object.` + }) + } + + // 2. Let method be ? GetMethod(V, @@iterator). + /** @type {Generator} */ + const method = V?.[Symbol.iterator]?.() + const seq = [] + + // 3. If method is undefined, throw a TypeError. + if ( + method === undefined || + typeof method.next !== 'function' + ) { + throw webidl.errors.exception({ + header: 'Sequence', + message: 'Object is not an iterator.' + }) + } + + // https://webidl.spec.whatwg.org/#create-sequence-from-iterable + while (true) { + const { done, value } = method.next() + + if (done) { + break + } + + seq.push(converter(value)) + } + + return seq + } +} + +// https://webidl.spec.whatwg.org/#es-to-record +webidl.recordConverter = function (keyConverter, valueConverter) { + return (O) => { + // 1. If Type(O) is not Object, throw a TypeError. + if (webidl.util.Type(O) !== 'Object') { + throw webidl.errors.exception({ + header: 'Record', + message: `Value of type ${webidl.util.Type(O)} is not an Object.` + }) + } + + // 2. Let result be a new empty instance of record. + const result = {} + + if (!types.isProxy(O)) { + // Object.keys only returns enumerable properties + const keys = Object.keys(O) + + for (const key of keys) { + // 1. Let typedKey be key converted to an IDL value of type K. + const typedKey = keyConverter(key) + + // 2. Let value be ? Get(O, key). + // 3. Let typedValue be value converted to an IDL value of type V. + const typedValue = valueConverter(O[key]) + + // 4. Set result[typedKey] to typedValue. + result[typedKey] = typedValue + } + + // 5. Return result. + return result + } + + // 3. Let keys be ? O.[[OwnPropertyKeys]](). + const keys = Reflect.ownKeys(O) + + // 4. For each key of keys. + for (const key of keys) { + // 1. Let desc be ? O.[[GetOwnProperty]](key). + const desc = Reflect.getOwnPropertyDescriptor(O, key) + + // 2. If desc is not undefined and desc.[[Enumerable]] is true: + if (desc?.enumerable) { + // 1. Let typedKey be key converted to an IDL value of type K. + const typedKey = keyConverter(key) + + // 2. Let value be ? Get(O, key). + // 3. Let typedValue be value converted to an IDL value of type V. + const typedValue = valueConverter(O[key]) + + // 4. Set result[typedKey] to typedValue. + result[typedKey] = typedValue + } + } + + // 5. Return result. + return result + } +} + +webidl.interfaceConverter = function (i) { + return (V, opts = {}) => { + if (opts.strict !== false && !(V instanceof i)) { + throw webidl.errors.exception({ + header: i.name, + message: `Expected ${V} to be an instance of ${i.name}.` + }) + } + + return V + } +} + +webidl.dictionaryConverter = function (converters) { + return (dictionary) => { + const type = webidl.util.Type(dictionary) + const dict = {} + + if (type === 'Null' || type === 'Undefined') { + return dict + } else if (type !== 'Object') { + throw webidl.errors.exception({ + header: 'Dictionary', + message: `Expected ${dictionary} to be one of: Null, Undefined, Object.` + }) + } + + for (const options of converters) { + const { key, defaultValue, required, converter } = options + + if (required === true) { + if (!hasOwn(dictionary, key)) { + throw webidl.errors.exception({ + header: 'Dictionary', + message: `Missing required key "${key}".` + }) + } + } + + let value = dictionary[key] + const hasDefault = hasOwn(options, 'defaultValue') + + // Only use defaultValue if value is undefined and + // a defaultValue options was provided. + if (hasDefault && value !== null) { + value = value ?? defaultValue + } + + // A key can be optional and have no default value. + // When this happens, do not perform a conversion, + // and do not assign the key a value. + if (required || hasDefault || value !== undefined) { + value = converter(value) + + if ( + options.allowedValues && + !options.allowedValues.includes(value) + ) { + throw webidl.errors.exception({ + header: 'Dictionary', + message: `${value} is not an accepted type. Expected one of ${options.allowedValues.join(', ')}.` + }) + } + + dict[key] = value + } + } + + return dict + } +} + +webidl.nullableConverter = function (converter) { + return (V) => { + if (V === null) { + return V + } + + return converter(V) + } +} + +// https://webidl.spec.whatwg.org/#es-DOMString +webidl.converters.DOMString = function (V, opts = {}) { + // 1. If V is null and the conversion is to an IDL type + // associated with the [LegacyNullToEmptyString] + // extended attribute, then return the DOMString value + // that represents the empty string. + if (V === null && opts.legacyNullToEmptyString) { + return '' + } + + // 2. Let x be ? ToString(V). + if (typeof V === 'symbol') { + throw new TypeError('Could not convert argument of type symbol to string.') + } + + // 3. Return the IDL DOMString value that represents the + // same sequence of code units as the one the + // ECMAScript String value x represents. + return String(V) +} + +// https://webidl.spec.whatwg.org/#es-ByteString +webidl.converters.ByteString = function (V) { + // 1. Let x be ? ToString(V). + // Note: DOMString converter perform ? ToString(V) + const x = webidl.converters.DOMString(V) + + // 2. If the value of any element of x is greater than + // 255, then throw a TypeError. + for (let index = 0; index < x.length; index++) { + if (x.charCodeAt(index) > 255) { + throw new TypeError( + 'Cannot convert argument to a ByteString because the character at ' + + `index ${index} has a value of ${x.charCodeAt(index)} which is greater than 255.` + ) + } + } + + // 3. Return an IDL ByteString value whose length is the + // length of x, and where the value of each element is + // the value of the corresponding element of x. + return x +} + +// https://webidl.spec.whatwg.org/#es-USVString +webidl.converters.USVString = toUSVString + +// https://webidl.spec.whatwg.org/#es-boolean +webidl.converters.boolean = function (V) { + // 1. Let x be the result of computing ToBoolean(V). + const x = Boolean(V) + + // 2. Return the IDL boolean value that is the one that represents + // the same truth value as the ECMAScript Boolean value x. + return x +} + +// https://webidl.spec.whatwg.org/#es-any +webidl.converters.any = function (V) { + return V +} + +// https://webidl.spec.whatwg.org/#es-long-long +webidl.converters['long long'] = function (V) { + // 1. Let x be ? ConvertToInt(V, 64, "signed"). + const x = webidl.util.ConvertToInt(V, 64, 'signed') + + // 2. Return the IDL long long value that represents + // the same numeric value as x. + return x +} + +// https://webidl.spec.whatwg.org/#es-unsigned-long-long +webidl.converters['unsigned long long'] = function (V) { + // 1. Let x be ? ConvertToInt(V, 64, "unsigned"). + const x = webidl.util.ConvertToInt(V, 64, 'unsigned') + + // 2. Return the IDL unsigned long long value that + // represents the same numeric value as x. + return x +} + +// https://webidl.spec.whatwg.org/#es-unsigned-long +webidl.converters['unsigned long'] = function (V) { + // 1. Let x be ? ConvertToInt(V, 32, "unsigned"). + const x = webidl.util.ConvertToInt(V, 32, 'unsigned') + + // 2. Return the IDL unsigned long value that + // represents the same numeric value as x. + return x +} + +// https://webidl.spec.whatwg.org/#es-unsigned-short +webidl.converters['unsigned short'] = function (V, opts) { + // 1. Let x be ? ConvertToInt(V, 16, "unsigned"). + const x = webidl.util.ConvertToInt(V, 16, 'unsigned', opts) + + // 2. Return the IDL unsigned short value that represents + // the same numeric value as x. + return x +} + +// https://webidl.spec.whatwg.org/#idl-ArrayBuffer +webidl.converters.ArrayBuffer = function (V, opts = {}) { + // 1. If Type(V) is not Object, or V does not have an + // [[ArrayBufferData]] internal slot, then throw a + // TypeError. + // see: https://tc39.es/ecma262/#sec-properties-of-the-arraybuffer-instances + // see: https://tc39.es/ecma262/#sec-properties-of-the-sharedarraybuffer-instances + if ( + webidl.util.Type(V) !== 'Object' || + !types.isAnyArrayBuffer(V) + ) { + throw webidl.errors.conversionFailed({ + prefix: `${V}`, + argument: `${V}`, + types: ['ArrayBuffer'] + }) + } + + // 2. If the conversion is not to an IDL type associated + // with the [AllowShared] extended attribute, and + // IsSharedArrayBuffer(V) is true, then throw a + // TypeError. + if (opts.allowShared === false && types.isSharedArrayBuffer(V)) { + throw webidl.errors.exception({ + header: 'ArrayBuffer', + message: 'SharedArrayBuffer is not allowed.' + }) + } + + // 3. If the conversion is not to an IDL type associated + // with the [AllowResizable] extended attribute, and + // IsResizableArrayBuffer(V) is true, then throw a + // TypeError. + // Note: resizable ArrayBuffers are currently a proposal. + + // 4. Return the IDL ArrayBuffer value that is a + // reference to the same object as V. + return V +} + +webidl.converters.TypedArray = function (V, T, opts = {}) { + // 1. Let T be the IDL type V is being converted to. + + // 2. If Type(V) is not Object, or V does not have a + // [[TypedArrayName]] internal slot with a value + // equal to T’s name, then throw a TypeError. + if ( + webidl.util.Type(V) !== 'Object' || + !types.isTypedArray(V) || + V.constructor.name !== T.name + ) { + throw webidl.errors.conversionFailed({ + prefix: `${T.name}`, + argument: `${V}`, + types: [T.name] + }) + } + + // 3. If the conversion is not to an IDL type associated + // with the [AllowShared] extended attribute, and + // IsSharedArrayBuffer(V.[[ViewedArrayBuffer]]) is + // true, then throw a TypeError. + if (opts.allowShared === false && types.isSharedArrayBuffer(V.buffer)) { + throw webidl.errors.exception({ + header: 'ArrayBuffer', + message: 'SharedArrayBuffer is not allowed.' + }) + } + + // 4. If the conversion is not to an IDL type associated + // with the [AllowResizable] extended attribute, and + // IsResizableArrayBuffer(V.[[ViewedArrayBuffer]]) is + // true, then throw a TypeError. + // Note: resizable array buffers are currently a proposal + + // 5. Return the IDL value of type T that is a reference + // to the same object as V. + return V +} + +webidl.converters.DataView = function (V, opts = {}) { + // 1. If Type(V) is not Object, or V does not have a + // [[DataView]] internal slot, then throw a TypeError. + if (webidl.util.Type(V) !== 'Object' || !types.isDataView(V)) { + throw webidl.errors.exception({ + header: 'DataView', + message: 'Object is not a DataView.' + }) + } + + // 2. If the conversion is not to an IDL type associated + // with the [AllowShared] extended attribute, and + // IsSharedArrayBuffer(V.[[ViewedArrayBuffer]]) is true, + // then throw a TypeError. + if (opts.allowShared === false && types.isSharedArrayBuffer(V.buffer)) { + throw webidl.errors.exception({ + header: 'ArrayBuffer', + message: 'SharedArrayBuffer is not allowed.' + }) + } + + // 3. If the conversion is not to an IDL type associated + // with the [AllowResizable] extended attribute, and + // IsResizableArrayBuffer(V.[[ViewedArrayBuffer]]) is + // true, then throw a TypeError. + // Note: resizable ArrayBuffers are currently a proposal + + // 4. Return the IDL DataView value that is a reference + // to the same object as V. + return V +} + +// https://webidl.spec.whatwg.org/#BufferSource +webidl.converters.BufferSource = function (V, opts = {}) { + if (types.isAnyArrayBuffer(V)) { + return webidl.converters.ArrayBuffer(V, opts) + } + + if (types.isTypedArray(V)) { + return webidl.converters.TypedArray(V, V.constructor) + } + + if (types.isDataView(V)) { + return webidl.converters.DataView(V, opts) + } + + throw new TypeError(`Could not convert ${V} to a BufferSource.`) +} + +webidl.converters['sequence'] = webidl.sequenceConverter( + webidl.converters.ByteString +) + +webidl.converters['sequence>'] = webidl.sequenceConverter( + webidl.converters['sequence'] +) + +webidl.converters['record'] = webidl.recordConverter( + webidl.converters.ByteString, + webidl.converters.ByteString +) + +module.exports = { + webidl +} + + +/***/ }), + +/***/ 396: +/***/ ((module) => { + +"use strict"; + + +/** + * @see https://encoding.spec.whatwg.org/#concept-encoding-get + * @param {string|undefined} label + */ +function getEncoding (label) { + if (!label) { + return 'failure' + } + + // 1. Remove any leading and trailing ASCII whitespace from label. + // 2. If label is an ASCII case-insensitive match for any of the + // labels listed in the table below, then return the + // corresponding encoding; otherwise return failure. + switch (label.trim().toLowerCase()) { + case 'unicode-1-1-utf-8': + case 'unicode11utf8': + case 'unicode20utf8': + case 'utf-8': + case 'utf8': + case 'x-unicode20utf8': + return 'UTF-8' + case '866': + case 'cp866': + case 'csibm866': + case 'ibm866': + return 'IBM866' + case 'csisolatin2': + case 'iso-8859-2': + case 'iso-ir-101': + case 'iso8859-2': + case 'iso88592': + case 'iso_8859-2': + case 'iso_8859-2:1987': + case 'l2': + case 'latin2': + return 'ISO-8859-2' + case 'csisolatin3': + case 'iso-8859-3': + case 'iso-ir-109': + case 'iso8859-3': + case 'iso88593': + case 'iso_8859-3': + case 'iso_8859-3:1988': + case 'l3': + case 'latin3': + return 'ISO-8859-3' + case 'csisolatin4': + case 'iso-8859-4': + case 'iso-ir-110': + case 'iso8859-4': + case 'iso88594': + case 'iso_8859-4': + case 'iso_8859-4:1988': + case 'l4': + case 'latin4': + return 'ISO-8859-4' + case 'csisolatincyrillic': + case 'cyrillic': + case 'iso-8859-5': + case 'iso-ir-144': + case 'iso8859-5': + case 'iso88595': + case 'iso_8859-5': + case 'iso_8859-5:1988': + return 'ISO-8859-5' + case 'arabic': + case 'asmo-708': + case 'csiso88596e': + case 'csiso88596i': + case 'csisolatinarabic': + case 'ecma-114': + case 'iso-8859-6': + case 'iso-8859-6-e': + case 'iso-8859-6-i': + case 'iso-ir-127': + case 'iso8859-6': + case 'iso88596': + case 'iso_8859-6': + case 'iso_8859-6:1987': + return 'ISO-8859-6' + case 'csisolatingreek': + case 'ecma-118': + case 'elot_928': + case 'greek': + case 'greek8': + case 'iso-8859-7': + case 'iso-ir-126': + case 'iso8859-7': + case 'iso88597': + case 'iso_8859-7': + case 'iso_8859-7:1987': + case 'sun_eu_greek': + return 'ISO-8859-7' + case 'csiso88598e': + case 'csisolatinhebrew': + case 'hebrew': + case 'iso-8859-8': + case 'iso-8859-8-e': + case 'iso-ir-138': + case 'iso8859-8': + case 'iso88598': + case 'iso_8859-8': + case 'iso_8859-8:1988': + case 'visual': + return 'ISO-8859-8' + case 'csiso88598i': + case 'iso-8859-8-i': + case 'logical': + return 'ISO-8859-8-I' + case 'csisolatin6': + case 'iso-8859-10': + case 'iso-ir-157': + case 'iso8859-10': + case 'iso885910': + case 'l6': + case 'latin6': + return 'ISO-8859-10' + case 'iso-8859-13': + case 'iso8859-13': + case 'iso885913': + return 'ISO-8859-13' + case 'iso-8859-14': + case 'iso8859-14': + case 'iso885914': + return 'ISO-8859-14' + case 'csisolatin9': + case 'iso-8859-15': + case 'iso8859-15': + case 'iso885915': + case 'iso_8859-15': + case 'l9': + return 'ISO-8859-15' + case 'iso-8859-16': + return 'ISO-8859-16' + case 'cskoi8r': + case 'koi': + case 'koi8': + case 'koi8-r': + case 'koi8_r': + return 'KOI8-R' + case 'koi8-ru': + case 'koi8-u': + return 'KOI8-U' + case 'csmacintosh': + case 'mac': + case 'macintosh': + case 'x-mac-roman': + return 'macintosh' + case 'iso-8859-11': + case 'iso8859-11': + case 'iso885911': + case 'tis-620': + case 'windows-874': + return 'windows-874' + case 'cp1250': + case 'windows-1250': + case 'x-cp1250': + return 'windows-1250' + case 'cp1251': + case 'windows-1251': + case 'x-cp1251': + return 'windows-1251' + case 'ansi_x3.4-1968': + case 'ascii': + case 'cp1252': + case 'cp819': + case 'csisolatin1': + case 'ibm819': + case 'iso-8859-1': + case 'iso-ir-100': + case 'iso8859-1': + case 'iso88591': + case 'iso_8859-1': + case 'iso_8859-1:1987': + case 'l1': + case 'latin1': + case 'us-ascii': + case 'windows-1252': + case 'x-cp1252': + return 'windows-1252' + case 'cp1253': + case 'windows-1253': + case 'x-cp1253': + return 'windows-1253' + case 'cp1254': + case 'csisolatin5': + case 'iso-8859-9': + case 'iso-ir-148': + case 'iso8859-9': + case 'iso88599': + case 'iso_8859-9': + case 'iso_8859-9:1989': + case 'l5': + case 'latin5': + case 'windows-1254': + case 'x-cp1254': + return 'windows-1254' + case 'cp1255': + case 'windows-1255': + case 'x-cp1255': + return 'windows-1255' + case 'cp1256': + case 'windows-1256': + case 'x-cp1256': + return 'windows-1256' + case 'cp1257': + case 'windows-1257': + case 'x-cp1257': + return 'windows-1257' + case 'cp1258': + case 'windows-1258': + case 'x-cp1258': + return 'windows-1258' + case 'x-mac-cyrillic': + case 'x-mac-ukrainian': + return 'x-mac-cyrillic' + case 'chinese': + case 'csgb2312': + case 'csiso58gb231280': + case 'gb2312': + case 'gb_2312': + case 'gb_2312-80': + case 'gbk': + case 'iso-ir-58': + case 'x-gbk': + return 'GBK' + case 'gb18030': + return 'gb18030' + case 'big5': + case 'big5-hkscs': + case 'cn-big5': + case 'csbig5': + case 'x-x-big5': + return 'Big5' + case 'cseucpkdfmtjapanese': + case 'euc-jp': + case 'x-euc-jp': + return 'EUC-JP' + case 'csiso2022jp': + case 'iso-2022-jp': + return 'ISO-2022-JP' + case 'csshiftjis': + case 'ms932': + case 'ms_kanji': + case 'shift-jis': + case 'shift_jis': + case 'sjis': + case 'windows-31j': + case 'x-sjis': + return 'Shift_JIS' + case 'cseuckr': + case 'csksc56011987': + case 'euc-kr': + case 'iso-ir-149': + case 'korean': + case 'ks_c_5601-1987': + case 'ks_c_5601-1989': + case 'ksc5601': + case 'ksc_5601': + case 'windows-949': + return 'EUC-KR' + case 'csiso2022kr': + case 'hz-gb-2312': + case 'iso-2022-cn': + case 'iso-2022-cn-ext': + case 'iso-2022-kr': + case 'replacement': + return 'replacement' + case 'unicodefffe': + case 'utf-16be': + return 'UTF-16BE' + case 'csunicode': + case 'iso-10646-ucs-2': + case 'ucs-2': + case 'unicode': + case 'unicodefeff': + case 'utf-16': + case 'utf-16le': + return 'UTF-16LE' + case 'x-user-defined': + return 'x-user-defined' + default: return 'failure' + } +} + +module.exports = { + getEncoding +} + + +/***/ }), + +/***/ 2160: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + staticPropertyDescriptors, + readOperation, + fireAProgressEvent +} = __nccwpck_require__(165) +const { + kState, + kError, + kResult, + kEvents, + kAborted +} = __nccwpck_require__(6812) +const { webidl } = __nccwpck_require__(4222) +const { kEnumerableProperty } = __nccwpck_require__(3440) + +class FileReader extends EventTarget { + constructor () { + super() + + this[kState] = 'empty' + this[kResult] = null + this[kError] = null + this[kEvents] = { + loadend: null, + error: null, + abort: null, + load: null, + progress: null, + loadstart: null + } + } + + /** + * @see https://w3c.github.io/FileAPI/#dfn-readAsArrayBuffer + * @param {import('buffer').Blob} blob + */ + readAsArrayBuffer (blob) { + webidl.brandCheck(this, FileReader) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FileReader.readAsArrayBuffer' }) + + blob = webidl.converters.Blob(blob, { strict: false }) + + // The readAsArrayBuffer(blob) method, when invoked, + // must initiate a read operation for blob with ArrayBuffer. + readOperation(this, blob, 'ArrayBuffer') + } + + /** + * @see https://w3c.github.io/FileAPI/#readAsBinaryString + * @param {import('buffer').Blob} blob + */ + readAsBinaryString (blob) { + webidl.brandCheck(this, FileReader) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FileReader.readAsBinaryString' }) + + blob = webidl.converters.Blob(blob, { strict: false }) + + // The readAsBinaryString(blob) method, when invoked, + // must initiate a read operation for blob with BinaryString. + readOperation(this, blob, 'BinaryString') + } + + /** + * @see https://w3c.github.io/FileAPI/#readAsDataText + * @param {import('buffer').Blob} blob + * @param {string?} encoding + */ + readAsText (blob, encoding = undefined) { + webidl.brandCheck(this, FileReader) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FileReader.readAsText' }) + + blob = webidl.converters.Blob(blob, { strict: false }) + + if (encoding !== undefined) { + encoding = webidl.converters.DOMString(encoding) + } + + // The readAsText(blob, encoding) method, when invoked, + // must initiate a read operation for blob with Text and encoding. + readOperation(this, blob, 'Text', encoding) + } + + /** + * @see https://w3c.github.io/FileAPI/#dfn-readAsDataURL + * @param {import('buffer').Blob} blob + */ + readAsDataURL (blob) { + webidl.brandCheck(this, FileReader) + + webidl.argumentLengthCheck(arguments, 1, { header: 'FileReader.readAsDataURL' }) + + blob = webidl.converters.Blob(blob, { strict: false }) + + // The readAsDataURL(blob) method, when invoked, must + // initiate a read operation for blob with DataURL. + readOperation(this, blob, 'DataURL') + } + + /** + * @see https://w3c.github.io/FileAPI/#dfn-abort + */ + abort () { + // 1. If this's state is "empty" or if this's state is + // "done" set this's result to null and terminate + // this algorithm. + if (this[kState] === 'empty' || this[kState] === 'done') { + this[kResult] = null + return + } + + // 2. If this's state is "loading" set this's state to + // "done" and set this's result to null. + if (this[kState] === 'loading') { + this[kState] = 'done' + this[kResult] = null + } + + // 3. If there are any tasks from this on the file reading + // task source in an affiliated task queue, then remove + // those tasks from that task queue. + this[kAborted] = true + + // 4. Terminate the algorithm for the read method being processed. + // TODO + + // 5. Fire a progress event called abort at this. + fireAProgressEvent('abort', this) + + // 6. If this's state is not "loading", fire a progress + // event called loadend at this. + if (this[kState] !== 'loading') { + fireAProgressEvent('loadend', this) + } + } + + /** + * @see https://w3c.github.io/FileAPI/#dom-filereader-readystate + */ + get readyState () { + webidl.brandCheck(this, FileReader) + + switch (this[kState]) { + case 'empty': return this.EMPTY + case 'loading': return this.LOADING + case 'done': return this.DONE + } + } + + /** + * @see https://w3c.github.io/FileAPI/#dom-filereader-result + */ + get result () { + webidl.brandCheck(this, FileReader) + + // The result attribute’s getter, when invoked, must return + // this's result. + return this[kResult] + } + + /** + * @see https://w3c.github.io/FileAPI/#dom-filereader-error + */ + get error () { + webidl.brandCheck(this, FileReader) + + // The error attribute’s getter, when invoked, must return + // this's error. + return this[kError] + } + + get onloadend () { + webidl.brandCheck(this, FileReader) + + return this[kEvents].loadend + } + + set onloadend (fn) { + webidl.brandCheck(this, FileReader) + + if (this[kEvents].loadend) { + this.removeEventListener('loadend', this[kEvents].loadend) + } + + if (typeof fn === 'function') { + this[kEvents].loadend = fn + this.addEventListener('loadend', fn) + } else { + this[kEvents].loadend = null + } + } + + get onerror () { + webidl.brandCheck(this, FileReader) + + return this[kEvents].error + } + + set onerror (fn) { + webidl.brandCheck(this, FileReader) + + if (this[kEvents].error) { + this.removeEventListener('error', this[kEvents].error) + } + + if (typeof fn === 'function') { + this[kEvents].error = fn + this.addEventListener('error', fn) + } else { + this[kEvents].error = null + } + } + + get onloadstart () { + webidl.brandCheck(this, FileReader) + + return this[kEvents].loadstart + } + + set onloadstart (fn) { + webidl.brandCheck(this, FileReader) + + if (this[kEvents].loadstart) { + this.removeEventListener('loadstart', this[kEvents].loadstart) + } + + if (typeof fn === 'function') { + this[kEvents].loadstart = fn + this.addEventListener('loadstart', fn) + } else { + this[kEvents].loadstart = null + } + } + + get onprogress () { + webidl.brandCheck(this, FileReader) + + return this[kEvents].progress + } + + set onprogress (fn) { + webidl.brandCheck(this, FileReader) + + if (this[kEvents].progress) { + this.removeEventListener('progress', this[kEvents].progress) + } + + if (typeof fn === 'function') { + this[kEvents].progress = fn + this.addEventListener('progress', fn) + } else { + this[kEvents].progress = null + } + } + + get onload () { + webidl.brandCheck(this, FileReader) + + return this[kEvents].load + } + + set onload (fn) { + webidl.brandCheck(this, FileReader) + + if (this[kEvents].load) { + this.removeEventListener('load', this[kEvents].load) + } + + if (typeof fn === 'function') { + this[kEvents].load = fn + this.addEventListener('load', fn) + } else { + this[kEvents].load = null + } + } + + get onabort () { + webidl.brandCheck(this, FileReader) + + return this[kEvents].abort + } + + set onabort (fn) { + webidl.brandCheck(this, FileReader) + + if (this[kEvents].abort) { + this.removeEventListener('abort', this[kEvents].abort) + } + + if (typeof fn === 'function') { + this[kEvents].abort = fn + this.addEventListener('abort', fn) + } else { + this[kEvents].abort = null + } + } +} + +// https://w3c.github.io/FileAPI/#dom-filereader-empty +FileReader.EMPTY = FileReader.prototype.EMPTY = 0 +// https://w3c.github.io/FileAPI/#dom-filereader-loading +FileReader.LOADING = FileReader.prototype.LOADING = 1 +// https://w3c.github.io/FileAPI/#dom-filereader-done +FileReader.DONE = FileReader.prototype.DONE = 2 + +Object.defineProperties(FileReader.prototype, { + EMPTY: staticPropertyDescriptors, + LOADING: staticPropertyDescriptors, + DONE: staticPropertyDescriptors, + readAsArrayBuffer: kEnumerableProperty, + readAsBinaryString: kEnumerableProperty, + readAsText: kEnumerableProperty, + readAsDataURL: kEnumerableProperty, + abort: kEnumerableProperty, + readyState: kEnumerableProperty, + result: kEnumerableProperty, + error: kEnumerableProperty, + onloadstart: kEnumerableProperty, + onprogress: kEnumerableProperty, + onload: kEnumerableProperty, + onabort: kEnumerableProperty, + onerror: kEnumerableProperty, + onloadend: kEnumerableProperty, + [Symbol.toStringTag]: { + value: 'FileReader', + writable: false, + enumerable: false, + configurable: true + } +}) + +Object.defineProperties(FileReader, { + EMPTY: staticPropertyDescriptors, + LOADING: staticPropertyDescriptors, + DONE: staticPropertyDescriptors +}) + +module.exports = { + FileReader +} + + +/***/ }), + +/***/ 5976: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { webidl } = __nccwpck_require__(4222) + +const kState = Symbol('ProgressEvent state') + +/** + * @see https://xhr.spec.whatwg.org/#progressevent + */ +class ProgressEvent extends Event { + constructor (type, eventInitDict = {}) { + type = webidl.converters.DOMString(type) + eventInitDict = webidl.converters.ProgressEventInit(eventInitDict ?? {}) + + super(type, eventInitDict) + + this[kState] = { + lengthComputable: eventInitDict.lengthComputable, + loaded: eventInitDict.loaded, + total: eventInitDict.total + } + } + + get lengthComputable () { + webidl.brandCheck(this, ProgressEvent) + + return this[kState].lengthComputable + } + + get loaded () { + webidl.brandCheck(this, ProgressEvent) + + return this[kState].loaded + } + + get total () { + webidl.brandCheck(this, ProgressEvent) + + return this[kState].total + } +} + +webidl.converters.ProgressEventInit = webidl.dictionaryConverter([ + { + key: 'lengthComputable', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'loaded', + converter: webidl.converters['unsigned long long'], + defaultValue: 0 + }, + { + key: 'total', + converter: webidl.converters['unsigned long long'], + defaultValue: 0 + }, + { + key: 'bubbles', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'cancelable', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'composed', + converter: webidl.converters.boolean, + defaultValue: false + } +]) + +module.exports = { + ProgressEvent +} + + +/***/ }), + +/***/ 6812: +/***/ ((module) => { + +"use strict"; + + +module.exports = { + kState: Symbol('FileReader state'), + kResult: Symbol('FileReader result'), + kError: Symbol('FileReader error'), + kLastProgressEventFired: Symbol('FileReader last progress event fired timestamp'), + kEvents: Symbol('FileReader events'), + kAborted: Symbol('FileReader aborted') +} + + +/***/ }), + +/***/ 165: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + kState, + kError, + kResult, + kAborted, + kLastProgressEventFired +} = __nccwpck_require__(6812) +const { ProgressEvent } = __nccwpck_require__(5976) +const { getEncoding } = __nccwpck_require__(396) +const { DOMException } = __nccwpck_require__(7326) +const { serializeAMimeType, parseMIMEType } = __nccwpck_require__(4322) +const { types } = __nccwpck_require__(9023) +const { StringDecoder } = __nccwpck_require__(3193) +const { btoa } = __nccwpck_require__(181) + +/** @type {PropertyDescriptor} */ +const staticPropertyDescriptors = { + enumerable: true, + writable: false, + configurable: false +} + +/** + * @see https://w3c.github.io/FileAPI/#readOperation + * @param {import('./filereader').FileReader} fr + * @param {import('buffer').Blob} blob + * @param {string} type + * @param {string?} encodingName + */ +function readOperation (fr, blob, type, encodingName) { + // 1. If fr’s state is "loading", throw an InvalidStateError + // DOMException. + if (fr[kState] === 'loading') { + throw new DOMException('Invalid state', 'InvalidStateError') + } + + // 2. Set fr’s state to "loading". + fr[kState] = 'loading' + + // 3. Set fr’s result to null. + fr[kResult] = null + + // 4. Set fr’s error to null. + fr[kError] = null + + // 5. Let stream be the result of calling get stream on blob. + /** @type {import('stream/web').ReadableStream} */ + const stream = blob.stream() + + // 6. Let reader be the result of getting a reader from stream. + const reader = stream.getReader() + + // 7. Let bytes be an empty byte sequence. + /** @type {Uint8Array[]} */ + const bytes = [] + + // 8. Let chunkPromise be the result of reading a chunk from + // stream with reader. + let chunkPromise = reader.read() + + // 9. Let isFirstChunk be true. + let isFirstChunk = true + + // 10. In parallel, while true: + // Note: "In parallel" just means non-blocking + // Note 2: readOperation itself cannot be async as double + // reading the body would then reject the promise, instead + // of throwing an error. + ;(async () => { + while (!fr[kAborted]) { + // 1. Wait for chunkPromise to be fulfilled or rejected. + try { + const { done, value } = await chunkPromise + + // 2. If chunkPromise is fulfilled, and isFirstChunk is + // true, queue a task to fire a progress event called + // loadstart at fr. + if (isFirstChunk && !fr[kAborted]) { + queueMicrotask(() => { + fireAProgressEvent('loadstart', fr) + }) + } + + // 3. Set isFirstChunk to false. + isFirstChunk = false + + // 4. If chunkPromise is fulfilled with an object whose + // done property is false and whose value property is + // a Uint8Array object, run these steps: + if (!done && types.isUint8Array(value)) { + // 1. Let bs be the byte sequence represented by the + // Uint8Array object. + + // 2. Append bs to bytes. + bytes.push(value) + + // 3. If roughly 50ms have passed since these steps + // were last invoked, queue a task to fire a + // progress event called progress at fr. + if ( + ( + fr[kLastProgressEventFired] === undefined || + Date.now() - fr[kLastProgressEventFired] >= 50 + ) && + !fr[kAborted] + ) { + fr[kLastProgressEventFired] = Date.now() + queueMicrotask(() => { + fireAProgressEvent('progress', fr) + }) + } + + // 4. Set chunkPromise to the result of reading a + // chunk from stream with reader. + chunkPromise = reader.read() + } else if (done) { + // 5. Otherwise, if chunkPromise is fulfilled with an + // object whose done property is true, queue a task + // to run the following steps and abort this algorithm: + queueMicrotask(() => { + // 1. Set fr’s state to "done". + fr[kState] = 'done' + + // 2. Let result be the result of package data given + // bytes, type, blob’s type, and encodingName. + try { + const result = packageData(bytes, type, blob.type, encodingName) + + // 4. Else: + + if (fr[kAborted]) { + return + } + + // 1. Set fr’s result to result. + fr[kResult] = result + + // 2. Fire a progress event called load at the fr. + fireAProgressEvent('load', fr) + } catch (error) { + // 3. If package data threw an exception error: + + // 1. Set fr’s error to error. + fr[kError] = error + + // 2. Fire a progress event called error at fr. + fireAProgressEvent('error', fr) + } + + // 5. If fr’s state is not "loading", fire a progress + // event called loadend at the fr. + if (fr[kState] !== 'loading') { + fireAProgressEvent('loadend', fr) + } + }) + + break + } + } catch (error) { + if (fr[kAborted]) { + return + } + + // 6. Otherwise, if chunkPromise is rejected with an + // error error, queue a task to run the following + // steps and abort this algorithm: + queueMicrotask(() => { + // 1. Set fr’s state to "done". + fr[kState] = 'done' + + // 2. Set fr’s error to error. + fr[kError] = error + + // 3. Fire a progress event called error at fr. + fireAProgressEvent('error', fr) + + // 4. If fr’s state is not "loading", fire a progress + // event called loadend at fr. + if (fr[kState] !== 'loading') { + fireAProgressEvent('loadend', fr) + } + }) + + break + } + } + })() +} + +/** + * @see https://w3c.github.io/FileAPI/#fire-a-progress-event + * @see https://dom.spec.whatwg.org/#concept-event-fire + * @param {string} e The name of the event + * @param {import('./filereader').FileReader} reader + */ +function fireAProgressEvent (e, reader) { + // The progress event e does not bubble. e.bubbles must be false + // The progress event e is NOT cancelable. e.cancelable must be false + const event = new ProgressEvent(e, { + bubbles: false, + cancelable: false + }) + + reader.dispatchEvent(event) +} + +/** + * @see https://w3c.github.io/FileAPI/#blob-package-data + * @param {Uint8Array[]} bytes + * @param {string} type + * @param {string?} mimeType + * @param {string?} encodingName + */ +function packageData (bytes, type, mimeType, encodingName) { + // 1. A Blob has an associated package data algorithm, given + // bytes, a type, a optional mimeType, and a optional + // encodingName, which switches on type and runs the + // associated steps: + + switch (type) { + case 'DataURL': { + // 1. Return bytes as a DataURL [RFC2397] subject to + // the considerations below: + // * Use mimeType as part of the Data URL if it is + // available in keeping with the Data URL + // specification [RFC2397]. + // * If mimeType is not available return a Data URL + // without a media-type. [RFC2397]. + + // https://datatracker.ietf.org/doc/html/rfc2397#section-3 + // dataurl := "data:" [ mediatype ] [ ";base64" ] "," data + // mediatype := [ type "/" subtype ] *( ";" parameter ) + // data := *urlchar + // parameter := attribute "=" value + let dataURL = 'data:' + + const parsed = parseMIMEType(mimeType || 'application/octet-stream') + + if (parsed !== 'failure') { + dataURL += serializeAMimeType(parsed) + } + + dataURL += ';base64,' + + const decoder = new StringDecoder('latin1') + + for (const chunk of bytes) { + dataURL += btoa(decoder.write(chunk)) + } + + dataURL += btoa(decoder.end()) + + return dataURL + } + case 'Text': { + // 1. Let encoding be failure + let encoding = 'failure' + + // 2. If the encodingName is present, set encoding to the + // result of getting an encoding from encodingName. + if (encodingName) { + encoding = getEncoding(encodingName) + } + + // 3. If encoding is failure, and mimeType is present: + if (encoding === 'failure' && mimeType) { + // 1. Let type be the result of parse a MIME type + // given mimeType. + const type = parseMIMEType(mimeType) + + // 2. If type is not failure, set encoding to the result + // of getting an encoding from type’s parameters["charset"]. + if (type !== 'failure') { + encoding = getEncoding(type.parameters.get('charset')) + } + } + + // 4. If encoding is failure, then set encoding to UTF-8. + if (encoding === 'failure') { + encoding = 'UTF-8' + } + + // 5. Decode bytes using fallback encoding encoding, and + // return the result. + return decode(bytes, encoding) + } + case 'ArrayBuffer': { + // Return a new ArrayBuffer whose contents are bytes. + const sequence = combineByteSequences(bytes) + + return sequence.buffer + } + case 'BinaryString': { + // Return bytes as a binary string, in which every byte + // is represented by a code unit of equal value [0..255]. + let binaryString = '' + + const decoder = new StringDecoder('latin1') + + for (const chunk of bytes) { + binaryString += decoder.write(chunk) + } + + binaryString += decoder.end() + + return binaryString + } + } +} + +/** + * @see https://encoding.spec.whatwg.org/#decode + * @param {Uint8Array[]} ioQueue + * @param {string} encoding + */ +function decode (ioQueue, encoding) { + const bytes = combineByteSequences(ioQueue) + + // 1. Let BOMEncoding be the result of BOM sniffing ioQueue. + const BOMEncoding = BOMSniffing(bytes) + + let slice = 0 + + // 2. If BOMEncoding is non-null: + if (BOMEncoding !== null) { + // 1. Set encoding to BOMEncoding. + encoding = BOMEncoding + + // 2. Read three bytes from ioQueue, if BOMEncoding is + // UTF-8; otherwise read two bytes. + // (Do nothing with those bytes.) + slice = BOMEncoding === 'UTF-8' ? 3 : 2 + } + + // 3. Process a queue with an instance of encoding’s + // decoder, ioQueue, output, and "replacement". + + // 4. Return output. + + const sliced = bytes.slice(slice) + return new TextDecoder(encoding).decode(sliced) +} + +/** + * @see https://encoding.spec.whatwg.org/#bom-sniff + * @param {Uint8Array} ioQueue + */ +function BOMSniffing (ioQueue) { + // 1. Let BOM be the result of peeking 3 bytes from ioQueue, + // converted to a byte sequence. + const [a, b, c] = ioQueue + + // 2. For each of the rows in the table below, starting with + // the first one and going down, if BOM starts with the + // bytes given in the first column, then return the + // encoding given in the cell in the second column of that + // row. Otherwise, return null. + if (a === 0xEF && b === 0xBB && c === 0xBF) { + return 'UTF-8' + } else if (a === 0xFE && b === 0xFF) { + return 'UTF-16BE' + } else if (a === 0xFF && b === 0xFE) { + return 'UTF-16LE' + } + + return null +} + +/** + * @param {Uint8Array[]} sequences + */ +function combineByteSequences (sequences) { + const size = sequences.reduce((a, b) => { + return a + b.byteLength + }, 0) + + let offset = 0 + + return sequences.reduce((a, b) => { + a.set(b, offset) + offset += b.byteLength + return a + }, new Uint8Array(size)) +} + +module.exports = { + staticPropertyDescriptors, + readOperation, + fireAProgressEvent +} + + +/***/ }), + +/***/ 2581: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +// We include a version number for the Dispatcher API. In case of breaking changes, +// this version number must be increased to avoid conflicts. +const globalDispatcher = Symbol.for('undici.globalDispatcher.1') +const { InvalidArgumentError } = __nccwpck_require__(8707) +const Agent = __nccwpck_require__(9965) + +if (getGlobalDispatcher() === undefined) { + setGlobalDispatcher(new Agent()) +} + +function setGlobalDispatcher (agent) { + if (!agent || typeof agent.dispatch !== 'function') { + throw new InvalidArgumentError('Argument agent must implement Agent') + } + Object.defineProperty(globalThis, globalDispatcher, { + value: agent, + writable: true, + enumerable: false, + configurable: false + }) +} + +function getGlobalDispatcher () { + return globalThis[globalDispatcher] +} + +module.exports = { + setGlobalDispatcher, + getGlobalDispatcher +} + + +/***/ }), + +/***/ 8840: +/***/ ((module) => { + +"use strict"; + + +module.exports = class DecoratorHandler { + constructor (handler) { + this.handler = handler + } + + onConnect (...args) { + return this.handler.onConnect(...args) + } + + onError (...args) { + return this.handler.onError(...args) + } + + onUpgrade (...args) { + return this.handler.onUpgrade(...args) + } + + onHeaders (...args) { + return this.handler.onHeaders(...args) + } + + onData (...args) { + return this.handler.onData(...args) + } + + onComplete (...args) { + return this.handler.onComplete(...args) + } + + onBodySent (...args) { + return this.handler.onBodySent(...args) + } +} + + +/***/ }), + +/***/ 8299: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const util = __nccwpck_require__(3440) +const { kBodyUsed } = __nccwpck_require__(6443) +const assert = __nccwpck_require__(2613) +const { InvalidArgumentError } = __nccwpck_require__(8707) +const EE = __nccwpck_require__(4434) + +const redirectableStatusCodes = [300, 301, 302, 303, 307, 308] + +const kBody = Symbol('body') + +class BodyAsyncIterable { + constructor (body) { + this[kBody] = body + this[kBodyUsed] = false + } + + async * [Symbol.asyncIterator] () { + assert(!this[kBodyUsed], 'disturbed') + this[kBodyUsed] = true + yield * this[kBody] + } +} + +class RedirectHandler { + constructor (dispatch, maxRedirections, opts, handler) { + if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) { + throw new InvalidArgumentError('maxRedirections must be a positive number') + } + + util.validateHandler(handler, opts.method, opts.upgrade) + + this.dispatch = dispatch + this.location = null + this.abort = null + this.opts = { ...opts, maxRedirections: 0 } // opts must be a copy + this.maxRedirections = maxRedirections + this.handler = handler + this.history = [] + + if (util.isStream(this.opts.body)) { + // TODO (fix): Provide some way for the user to cache the file to e.g. /tmp + // so that it can be dispatched again? + // TODO (fix): Do we need 100-expect support to provide a way to do this properly? + if (util.bodyLength(this.opts.body) === 0) { + this.opts.body + .on('data', function () { + assert(false) + }) + } + + if (typeof this.opts.body.readableDidRead !== 'boolean') { + this.opts.body[kBodyUsed] = false + EE.prototype.on.call(this.opts.body, 'data', function () { + this[kBodyUsed] = true + }) + } + } else if (this.opts.body && typeof this.opts.body.pipeTo === 'function') { + // TODO (fix): We can't access ReadableStream internal state + // to determine whether or not it has been disturbed. This is just + // a workaround. + this.opts.body = new BodyAsyncIterable(this.opts.body) + } else if ( + this.opts.body && + typeof this.opts.body !== 'string' && + !ArrayBuffer.isView(this.opts.body) && + util.isIterable(this.opts.body) + ) { + // TODO: Should we allow re-using iterable if !this.opts.idempotent + // or through some other flag? + this.opts.body = new BodyAsyncIterable(this.opts.body) + } + } + + onConnect (abort) { + this.abort = abort + this.handler.onConnect(abort, { history: this.history }) + } + + onUpgrade (statusCode, headers, socket) { + this.handler.onUpgrade(statusCode, headers, socket) + } + + onError (error) { + this.handler.onError(error) + } + + onHeaders (statusCode, headers, resume, statusText) { + this.location = this.history.length >= this.maxRedirections || util.isDisturbed(this.opts.body) + ? null + : parseLocation(statusCode, headers) + + if (this.opts.origin) { + this.history.push(new URL(this.opts.path, this.opts.origin)) + } + + if (!this.location) { + return this.handler.onHeaders(statusCode, headers, resume, statusText) + } + + const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin))) + const path = search ? `${pathname}${search}` : pathname + + // Remove headers referring to the original URL. + // By default it is Host only, unless it's a 303 (see below), which removes also all Content-* headers. + // https://tools.ietf.org/html/rfc7231#section-6.4 + this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin) + this.opts.path = path + this.opts.origin = origin + this.opts.maxRedirections = 0 + this.opts.query = null + + // https://tools.ietf.org/html/rfc7231#section-6.4.4 + // In case of HTTP 303, always replace method to be either HEAD or GET + if (statusCode === 303 && this.opts.method !== 'HEAD') { + this.opts.method = 'GET' + this.opts.body = null + } + } + + onData (chunk) { + if (this.location) { + /* + https://tools.ietf.org/html/rfc7231#section-6.4 + + TLDR: undici always ignores 3xx response bodies. + + Redirection is used to serve the requested resource from another URL, so it is assumes that + no body is generated (and thus can be ignored). Even though generating a body is not prohibited. + + For status 301, 302, 303, 307 and 308 (the latter from RFC 7238), the specs mention that the body usually + (which means it's optional and not mandated) contain just an hyperlink to the value of + the Location response header, so the body can be ignored safely. + + For status 300, which is "Multiple Choices", the spec mentions both generating a Location + response header AND a response body with the other possible location to follow. + Since the spec explicitily chooses not to specify a format for such body and leave it to + servers and browsers implementors, we ignore the body as there is no specified way to eventually parse it. + */ + } else { + return this.handler.onData(chunk) + } + } + + onComplete (trailers) { + if (this.location) { + /* + https://tools.ietf.org/html/rfc7231#section-6.4 + + TLDR: undici always ignores 3xx response trailers as they are not expected in case of redirections + and neither are useful if present. + + See comment on onData method above for more detailed informations. + */ + + this.location = null + this.abort = null + + this.dispatch(this.opts, this) + } else { + this.handler.onComplete(trailers) + } + } + + onBodySent (chunk) { + if (this.handler.onBodySent) { + this.handler.onBodySent(chunk) + } + } +} + +function parseLocation (statusCode, headers) { + if (redirectableStatusCodes.indexOf(statusCode) === -1) { + return null + } + + for (let i = 0; i < headers.length; i += 2) { + if (headers[i].toString().toLowerCase() === 'location') { + return headers[i + 1] + } + } +} + +// https://tools.ietf.org/html/rfc7231#section-6.4.4 +function shouldRemoveHeader (header, removeContent, unknownOrigin) { + if (header.length === 4) { + return util.headerNameToString(header) === 'host' + } + if (removeContent && util.headerNameToString(header).startsWith('content-')) { + return true + } + if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) { + const name = util.headerNameToString(header) + return name === 'authorization' || name === 'cookie' || name === 'proxy-authorization' + } + return false +} + +// https://tools.ietf.org/html/rfc7231#section-6.4 +function cleanRequestHeaders (headers, removeContent, unknownOrigin) { + const ret = [] + if (Array.isArray(headers)) { + for (let i = 0; i < headers.length; i += 2) { + if (!shouldRemoveHeader(headers[i], removeContent, unknownOrigin)) { + ret.push(headers[i], headers[i + 1]) + } + } + } else if (headers && typeof headers === 'object') { + for (const key of Object.keys(headers)) { + if (!shouldRemoveHeader(key, removeContent, unknownOrigin)) { + ret.push(key, headers[key]) + } + } + } else { + assert(headers == null, 'headers must be an object or an array') + } + return ret +} + +module.exports = RedirectHandler + + +/***/ }), + +/***/ 3573: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const assert = __nccwpck_require__(2613) + +const { kRetryHandlerDefaultRetry } = __nccwpck_require__(6443) +const { RequestRetryError } = __nccwpck_require__(8707) +const { isDisturbed, parseHeaders, parseRangeHeader } = __nccwpck_require__(3440) + +function calculateRetryAfterHeader (retryAfter) { + const current = Date.now() + const diff = new Date(retryAfter).getTime() - current + + return diff +} + +class RetryHandler { + constructor (opts, handlers) { + const { retryOptions, ...dispatchOpts } = opts + const { + // Retry scoped + retry: retryFn, + maxRetries, + maxTimeout, + minTimeout, + timeoutFactor, + // Response scoped + methods, + errorCodes, + retryAfter, + statusCodes + } = retryOptions ?? {} + + this.dispatch = handlers.dispatch + this.handler = handlers.handler + this.opts = dispatchOpts + this.abort = null + this.aborted = false + this.retryOpts = { + retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry], + retryAfter: retryAfter ?? true, + maxTimeout: maxTimeout ?? 30 * 1000, // 30s, + timeout: minTimeout ?? 500, // .5s + timeoutFactor: timeoutFactor ?? 2, + maxRetries: maxRetries ?? 5, + // What errors we should retry + methods: methods ?? ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE'], + // Indicates which errors to retry + statusCodes: statusCodes ?? [500, 502, 503, 504, 429], + // List of errors to retry + errorCodes: errorCodes ?? [ + 'ECONNRESET', + 'ECONNREFUSED', + 'ENOTFOUND', + 'ENETDOWN', + 'ENETUNREACH', + 'EHOSTDOWN', + 'EHOSTUNREACH', + 'EPIPE' + ] + } + + this.retryCount = 0 + this.start = 0 + this.end = null + this.etag = null + this.resume = null + + // Handle possible onConnect duplication + this.handler.onConnect(reason => { + this.aborted = true + if (this.abort) { + this.abort(reason) + } else { + this.reason = reason + } + }) + } + + onRequestSent () { + if (this.handler.onRequestSent) { + this.handler.onRequestSent() + } + } + + onUpgrade (statusCode, headers, socket) { + if (this.handler.onUpgrade) { + this.handler.onUpgrade(statusCode, headers, socket) + } + } + + onConnect (abort) { + if (this.aborted) { + abort(this.reason) + } else { + this.abort = abort + } + } + + onBodySent (chunk) { + if (this.handler.onBodySent) return this.handler.onBodySent(chunk) + } + + static [kRetryHandlerDefaultRetry] (err, { state, opts }, cb) { + const { statusCode, code, headers } = err + const { method, retryOptions } = opts + const { + maxRetries, + timeout, + maxTimeout, + timeoutFactor, + statusCodes, + errorCodes, + methods + } = retryOptions + let { counter, currentTimeout } = state + + currentTimeout = + currentTimeout != null && currentTimeout > 0 ? currentTimeout : timeout + + // Any code that is not a Undici's originated and allowed to retry + if ( + code && + code !== 'UND_ERR_REQ_RETRY' && + code !== 'UND_ERR_SOCKET' && + !errorCodes.includes(code) + ) { + cb(err) + return + } + + // If a set of method are provided and the current method is not in the list + if (Array.isArray(methods) && !methods.includes(method)) { + cb(err) + return + } + + // If a set of status code are provided and the current status code is not in the list + if ( + statusCode != null && + Array.isArray(statusCodes) && + !statusCodes.includes(statusCode) + ) { + cb(err) + return + } + + // If we reached the max number of retries + if (counter > maxRetries) { + cb(err) + return + } + + let retryAfterHeader = headers != null && headers['retry-after'] + if (retryAfterHeader) { + retryAfterHeader = Number(retryAfterHeader) + retryAfterHeader = isNaN(retryAfterHeader) + ? calculateRetryAfterHeader(retryAfterHeader) + : retryAfterHeader * 1e3 // Retry-After is in seconds + } + + const retryTimeout = + retryAfterHeader > 0 + ? Math.min(retryAfterHeader, maxTimeout) + : Math.min(currentTimeout * timeoutFactor ** counter, maxTimeout) + + state.currentTimeout = retryTimeout + + setTimeout(() => cb(null), retryTimeout) + } + + onHeaders (statusCode, rawHeaders, resume, statusMessage) { + const headers = parseHeaders(rawHeaders) + + this.retryCount += 1 + + if (statusCode >= 300) { + this.abort( + new RequestRetryError('Request failed', statusCode, { + headers, + count: this.retryCount + }) + ) + return false + } + + // Checkpoint for resume from where we left it + if (this.resume != null) { + this.resume = null + + if (statusCode !== 206) { + return true + } + + const contentRange = parseRangeHeader(headers['content-range']) + // If no content range + if (!contentRange) { + this.abort( + new RequestRetryError('Content-Range mismatch', statusCode, { + headers, + count: this.retryCount + }) + ) + return false + } + + // Let's start with a weak etag check + if (this.etag != null && this.etag !== headers.etag) { + this.abort( + new RequestRetryError('ETag mismatch', statusCode, { + headers, + count: this.retryCount + }) + ) + return false + } + + const { start, size, end = size } = contentRange + + assert(this.start === start, 'content-range mismatch') + assert(this.end == null || this.end === end, 'content-range mismatch') + + this.resume = resume + return true + } + + if (this.end == null) { + if (statusCode === 206) { + // First time we receive 206 + const range = parseRangeHeader(headers['content-range']) + + if (range == null) { + return this.handler.onHeaders( + statusCode, + rawHeaders, + resume, + statusMessage + ) + } + + const { start, size, end = size } = range + + assert( + start != null && Number.isFinite(start) && this.start !== start, + 'content-range mismatch' + ) + assert(Number.isFinite(start)) + assert( + end != null && Number.isFinite(end) && this.end !== end, + 'invalid content-length' + ) + + this.start = start + this.end = end + } + + // We make our best to checkpoint the body for further range headers + if (this.end == null) { + const contentLength = headers['content-length'] + this.end = contentLength != null ? Number(contentLength) : null + } + + assert(Number.isFinite(this.start)) + assert( + this.end == null || Number.isFinite(this.end), + 'invalid content-length' + ) + + this.resume = resume + this.etag = headers.etag != null ? headers.etag : null + + return this.handler.onHeaders( + statusCode, + rawHeaders, + resume, + statusMessage + ) + } + + const err = new RequestRetryError('Request failed', statusCode, { + headers, + count: this.retryCount + }) + + this.abort(err) + + return false + } + + onData (chunk) { + this.start += chunk.length + + return this.handler.onData(chunk) + } + + onComplete (rawTrailers) { + this.retryCount = 0 + return this.handler.onComplete(rawTrailers) + } + + onError (err) { + if (this.aborted || isDisturbed(this.opts.body)) { + return this.handler.onError(err) + } + + this.retryOpts.retry( + err, + { + state: { counter: this.retryCount++, currentTimeout: this.retryAfter }, + opts: { retryOptions: this.retryOpts, ...this.opts } + }, + onRetry.bind(this) + ) + + function onRetry (err) { + if (err != null || this.aborted || isDisturbed(this.opts.body)) { + return this.handler.onError(err) + } + + if (this.start !== 0) { + this.opts = { + ...this.opts, + headers: { + ...this.opts.headers, + range: `bytes=${this.start}-${this.end ?? ''}` + } + } + } + + try { + this.dispatch(this.opts, this) + } catch (err) { + this.handler.onError(err) + } + } + } +} + +module.exports = RetryHandler + + +/***/ }), + +/***/ 4415: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const RedirectHandler = __nccwpck_require__(8299) + +function createRedirectInterceptor ({ maxRedirections: defaultMaxRedirections }) { + return (dispatch) => { + return function Intercept (opts, handler) { + const { maxRedirections = defaultMaxRedirections } = opts + + if (!maxRedirections) { + return dispatch(opts, handler) + } + + const redirectHandler = new RedirectHandler(dispatch, maxRedirections, opts, handler) + opts = { ...opts, maxRedirections: 0 } // Stop sub dispatcher from also redirecting. + return dispatch(opts, redirectHandler) + } + } +} + +module.exports = createRedirectInterceptor + + +/***/ }), + +/***/ 2824: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.SPECIAL_HEADERS = exports.HEADER_STATE = exports.MINOR = exports.MAJOR = exports.CONNECTION_TOKEN_CHARS = exports.HEADER_CHARS = exports.TOKEN = exports.STRICT_TOKEN = exports.HEX = exports.URL_CHAR = exports.STRICT_URL_CHAR = exports.USERINFO_CHARS = exports.MARK = exports.ALPHANUM = exports.NUM = exports.HEX_MAP = exports.NUM_MAP = exports.ALPHA = exports.FINISH = exports.H_METHOD_MAP = exports.METHOD_MAP = exports.METHODS_RTSP = exports.METHODS_ICE = exports.METHODS_HTTP = exports.METHODS = exports.LENIENT_FLAGS = exports.FLAGS = exports.TYPE = exports.ERROR = void 0; +const utils_1 = __nccwpck_require__(172); +// C headers +var ERROR; +(function (ERROR) { + ERROR[ERROR["OK"] = 0] = "OK"; + ERROR[ERROR["INTERNAL"] = 1] = "INTERNAL"; + ERROR[ERROR["STRICT"] = 2] = "STRICT"; + ERROR[ERROR["LF_EXPECTED"] = 3] = "LF_EXPECTED"; + ERROR[ERROR["UNEXPECTED_CONTENT_LENGTH"] = 4] = "UNEXPECTED_CONTENT_LENGTH"; + ERROR[ERROR["CLOSED_CONNECTION"] = 5] = "CLOSED_CONNECTION"; + ERROR[ERROR["INVALID_METHOD"] = 6] = "INVALID_METHOD"; + ERROR[ERROR["INVALID_URL"] = 7] = "INVALID_URL"; + ERROR[ERROR["INVALID_CONSTANT"] = 8] = "INVALID_CONSTANT"; + ERROR[ERROR["INVALID_VERSION"] = 9] = "INVALID_VERSION"; + ERROR[ERROR["INVALID_HEADER_TOKEN"] = 10] = "INVALID_HEADER_TOKEN"; + ERROR[ERROR["INVALID_CONTENT_LENGTH"] = 11] = "INVALID_CONTENT_LENGTH"; + ERROR[ERROR["INVALID_CHUNK_SIZE"] = 12] = "INVALID_CHUNK_SIZE"; + ERROR[ERROR["INVALID_STATUS"] = 13] = "INVALID_STATUS"; + ERROR[ERROR["INVALID_EOF_STATE"] = 14] = "INVALID_EOF_STATE"; + ERROR[ERROR["INVALID_TRANSFER_ENCODING"] = 15] = "INVALID_TRANSFER_ENCODING"; + ERROR[ERROR["CB_MESSAGE_BEGIN"] = 16] = "CB_MESSAGE_BEGIN"; + ERROR[ERROR["CB_HEADERS_COMPLETE"] = 17] = "CB_HEADERS_COMPLETE"; + ERROR[ERROR["CB_MESSAGE_COMPLETE"] = 18] = "CB_MESSAGE_COMPLETE"; + ERROR[ERROR["CB_CHUNK_HEADER"] = 19] = "CB_CHUNK_HEADER"; + ERROR[ERROR["CB_CHUNK_COMPLETE"] = 20] = "CB_CHUNK_COMPLETE"; + ERROR[ERROR["PAUSED"] = 21] = "PAUSED"; + ERROR[ERROR["PAUSED_UPGRADE"] = 22] = "PAUSED_UPGRADE"; + ERROR[ERROR["PAUSED_H2_UPGRADE"] = 23] = "PAUSED_H2_UPGRADE"; + ERROR[ERROR["USER"] = 24] = "USER"; +})(ERROR = exports.ERROR || (exports.ERROR = {})); +var TYPE; +(function (TYPE) { + TYPE[TYPE["BOTH"] = 0] = "BOTH"; + TYPE[TYPE["REQUEST"] = 1] = "REQUEST"; + TYPE[TYPE["RESPONSE"] = 2] = "RESPONSE"; +})(TYPE = exports.TYPE || (exports.TYPE = {})); +var FLAGS; +(function (FLAGS) { + FLAGS[FLAGS["CONNECTION_KEEP_ALIVE"] = 1] = "CONNECTION_KEEP_ALIVE"; + FLAGS[FLAGS["CONNECTION_CLOSE"] = 2] = "CONNECTION_CLOSE"; + FLAGS[FLAGS["CONNECTION_UPGRADE"] = 4] = "CONNECTION_UPGRADE"; + FLAGS[FLAGS["CHUNKED"] = 8] = "CHUNKED"; + FLAGS[FLAGS["UPGRADE"] = 16] = "UPGRADE"; + FLAGS[FLAGS["CONTENT_LENGTH"] = 32] = "CONTENT_LENGTH"; + FLAGS[FLAGS["SKIPBODY"] = 64] = "SKIPBODY"; + FLAGS[FLAGS["TRAILING"] = 128] = "TRAILING"; + // 1 << 8 is unused + FLAGS[FLAGS["TRANSFER_ENCODING"] = 512] = "TRANSFER_ENCODING"; +})(FLAGS = exports.FLAGS || (exports.FLAGS = {})); +var LENIENT_FLAGS; +(function (LENIENT_FLAGS) { + LENIENT_FLAGS[LENIENT_FLAGS["HEADERS"] = 1] = "HEADERS"; + LENIENT_FLAGS[LENIENT_FLAGS["CHUNKED_LENGTH"] = 2] = "CHUNKED_LENGTH"; + LENIENT_FLAGS[LENIENT_FLAGS["KEEP_ALIVE"] = 4] = "KEEP_ALIVE"; +})(LENIENT_FLAGS = exports.LENIENT_FLAGS || (exports.LENIENT_FLAGS = {})); +var METHODS; +(function (METHODS) { + METHODS[METHODS["DELETE"] = 0] = "DELETE"; + METHODS[METHODS["GET"] = 1] = "GET"; + METHODS[METHODS["HEAD"] = 2] = "HEAD"; + METHODS[METHODS["POST"] = 3] = "POST"; + METHODS[METHODS["PUT"] = 4] = "PUT"; + /* pathological */ + METHODS[METHODS["CONNECT"] = 5] = "CONNECT"; + METHODS[METHODS["OPTIONS"] = 6] = "OPTIONS"; + METHODS[METHODS["TRACE"] = 7] = "TRACE"; + /* WebDAV */ + METHODS[METHODS["COPY"] = 8] = "COPY"; + METHODS[METHODS["LOCK"] = 9] = "LOCK"; + METHODS[METHODS["MKCOL"] = 10] = "MKCOL"; + METHODS[METHODS["MOVE"] = 11] = "MOVE"; + METHODS[METHODS["PROPFIND"] = 12] = "PROPFIND"; + METHODS[METHODS["PROPPATCH"] = 13] = "PROPPATCH"; + METHODS[METHODS["SEARCH"] = 14] = "SEARCH"; + METHODS[METHODS["UNLOCK"] = 15] = "UNLOCK"; + METHODS[METHODS["BIND"] = 16] = "BIND"; + METHODS[METHODS["REBIND"] = 17] = "REBIND"; + METHODS[METHODS["UNBIND"] = 18] = "UNBIND"; + METHODS[METHODS["ACL"] = 19] = "ACL"; + /* subversion */ + METHODS[METHODS["REPORT"] = 20] = "REPORT"; + METHODS[METHODS["MKACTIVITY"] = 21] = "MKACTIVITY"; + METHODS[METHODS["CHECKOUT"] = 22] = "CHECKOUT"; + METHODS[METHODS["MERGE"] = 23] = "MERGE"; + /* upnp */ + METHODS[METHODS["M-SEARCH"] = 24] = "M-SEARCH"; + METHODS[METHODS["NOTIFY"] = 25] = "NOTIFY"; + METHODS[METHODS["SUBSCRIBE"] = 26] = "SUBSCRIBE"; + METHODS[METHODS["UNSUBSCRIBE"] = 27] = "UNSUBSCRIBE"; + /* RFC-5789 */ + METHODS[METHODS["PATCH"] = 28] = "PATCH"; + METHODS[METHODS["PURGE"] = 29] = "PURGE"; + /* CalDAV */ + METHODS[METHODS["MKCALENDAR"] = 30] = "MKCALENDAR"; + /* RFC-2068, section 19.6.1.2 */ + METHODS[METHODS["LINK"] = 31] = "LINK"; + METHODS[METHODS["UNLINK"] = 32] = "UNLINK"; + /* icecast */ + METHODS[METHODS["SOURCE"] = 33] = "SOURCE"; + /* RFC-7540, section 11.6 */ + METHODS[METHODS["PRI"] = 34] = "PRI"; + /* RFC-2326 RTSP */ + METHODS[METHODS["DESCRIBE"] = 35] = "DESCRIBE"; + METHODS[METHODS["ANNOUNCE"] = 36] = "ANNOUNCE"; + METHODS[METHODS["SETUP"] = 37] = "SETUP"; + METHODS[METHODS["PLAY"] = 38] = "PLAY"; + METHODS[METHODS["PAUSE"] = 39] = "PAUSE"; + METHODS[METHODS["TEARDOWN"] = 40] = "TEARDOWN"; + METHODS[METHODS["GET_PARAMETER"] = 41] = "GET_PARAMETER"; + METHODS[METHODS["SET_PARAMETER"] = 42] = "SET_PARAMETER"; + METHODS[METHODS["REDIRECT"] = 43] = "REDIRECT"; + METHODS[METHODS["RECORD"] = 44] = "RECORD"; + /* RAOP */ + METHODS[METHODS["FLUSH"] = 45] = "FLUSH"; +})(METHODS = exports.METHODS || (exports.METHODS = {})); +exports.METHODS_HTTP = [ + METHODS.DELETE, + METHODS.GET, + METHODS.HEAD, + METHODS.POST, + METHODS.PUT, + METHODS.CONNECT, + METHODS.OPTIONS, + METHODS.TRACE, + METHODS.COPY, + METHODS.LOCK, + METHODS.MKCOL, + METHODS.MOVE, + METHODS.PROPFIND, + METHODS.PROPPATCH, + METHODS.SEARCH, + METHODS.UNLOCK, + METHODS.BIND, + METHODS.REBIND, + METHODS.UNBIND, + METHODS.ACL, + METHODS.REPORT, + METHODS.MKACTIVITY, + METHODS.CHECKOUT, + METHODS.MERGE, + METHODS['M-SEARCH'], + METHODS.NOTIFY, + METHODS.SUBSCRIBE, + METHODS.UNSUBSCRIBE, + METHODS.PATCH, + METHODS.PURGE, + METHODS.MKCALENDAR, + METHODS.LINK, + METHODS.UNLINK, + METHODS.PRI, + // TODO(indutny): should we allow it with HTTP? + METHODS.SOURCE, +]; +exports.METHODS_ICE = [ + METHODS.SOURCE, +]; +exports.METHODS_RTSP = [ + METHODS.OPTIONS, + METHODS.DESCRIBE, + METHODS.ANNOUNCE, + METHODS.SETUP, + METHODS.PLAY, + METHODS.PAUSE, + METHODS.TEARDOWN, + METHODS.GET_PARAMETER, + METHODS.SET_PARAMETER, + METHODS.REDIRECT, + METHODS.RECORD, + METHODS.FLUSH, + // For AirPlay + METHODS.GET, + METHODS.POST, +]; +exports.METHOD_MAP = utils_1.enumToMap(METHODS); +exports.H_METHOD_MAP = {}; +Object.keys(exports.METHOD_MAP).forEach((key) => { + if (/^H/.test(key)) { + exports.H_METHOD_MAP[key] = exports.METHOD_MAP[key]; + } +}); +var FINISH; +(function (FINISH) { + FINISH[FINISH["SAFE"] = 0] = "SAFE"; + FINISH[FINISH["SAFE_WITH_CB"] = 1] = "SAFE_WITH_CB"; + FINISH[FINISH["UNSAFE"] = 2] = "UNSAFE"; +})(FINISH = exports.FINISH || (exports.FINISH = {})); +exports.ALPHA = []; +for (let i = 'A'.charCodeAt(0); i <= 'Z'.charCodeAt(0); i++) { + // Upper case + exports.ALPHA.push(String.fromCharCode(i)); + // Lower case + exports.ALPHA.push(String.fromCharCode(i + 0x20)); +} +exports.NUM_MAP = { + 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, + 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, +}; +exports.HEX_MAP = { + 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, + 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, + A: 0XA, B: 0XB, C: 0XC, D: 0XD, E: 0XE, F: 0XF, + a: 0xa, b: 0xb, c: 0xc, d: 0xd, e: 0xe, f: 0xf, +}; +exports.NUM = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', +]; +exports.ALPHANUM = exports.ALPHA.concat(exports.NUM); +exports.MARK = ['-', '_', '.', '!', '~', '*', '\'', '(', ')']; +exports.USERINFO_CHARS = exports.ALPHANUM + .concat(exports.MARK) + .concat(['%', ';', ':', '&', '=', '+', '$', ',']); +// TODO(indutny): use RFC +exports.STRICT_URL_CHAR = [ + '!', '"', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + ':', ';', '<', '=', '>', + '@', '[', '\\', ']', '^', '_', + '`', + '{', '|', '}', '~', +].concat(exports.ALPHANUM); +exports.URL_CHAR = exports.STRICT_URL_CHAR + .concat(['\t', '\f']); +// All characters with 0x80 bit set to 1 +for (let i = 0x80; i <= 0xff; i++) { + exports.URL_CHAR.push(i); +} +exports.HEX = exports.NUM.concat(['a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F']); +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +exports.STRICT_TOKEN = [ + '!', '#', '$', '%', '&', '\'', + '*', '+', '-', '.', + '^', '_', '`', + '|', '~', +].concat(exports.ALPHANUM); +exports.TOKEN = exports.STRICT_TOKEN.concat([' ']); +/* + * Verify that a char is a valid visible (printable) US-ASCII + * character or %x80-FF + */ +exports.HEADER_CHARS = ['\t']; +for (let i = 32; i <= 255; i++) { + if (i !== 127) { + exports.HEADER_CHARS.push(i); + } +} +// ',' = \x44 +exports.CONNECTION_TOKEN_CHARS = exports.HEADER_CHARS.filter((c) => c !== 44); +exports.MAJOR = exports.NUM_MAP; +exports.MINOR = exports.MAJOR; +var HEADER_STATE; +(function (HEADER_STATE) { + HEADER_STATE[HEADER_STATE["GENERAL"] = 0] = "GENERAL"; + HEADER_STATE[HEADER_STATE["CONNECTION"] = 1] = "CONNECTION"; + HEADER_STATE[HEADER_STATE["CONTENT_LENGTH"] = 2] = "CONTENT_LENGTH"; + HEADER_STATE[HEADER_STATE["TRANSFER_ENCODING"] = 3] = "TRANSFER_ENCODING"; + HEADER_STATE[HEADER_STATE["UPGRADE"] = 4] = "UPGRADE"; + HEADER_STATE[HEADER_STATE["CONNECTION_KEEP_ALIVE"] = 5] = "CONNECTION_KEEP_ALIVE"; + HEADER_STATE[HEADER_STATE["CONNECTION_CLOSE"] = 6] = "CONNECTION_CLOSE"; + HEADER_STATE[HEADER_STATE["CONNECTION_UPGRADE"] = 7] = "CONNECTION_UPGRADE"; + HEADER_STATE[HEADER_STATE["TRANSFER_ENCODING_CHUNKED"] = 8] = "TRANSFER_ENCODING_CHUNKED"; +})(HEADER_STATE = exports.HEADER_STATE || (exports.HEADER_STATE = {})); +exports.SPECIAL_HEADERS = { + 'connection': HEADER_STATE.CONNECTION, + 'content-length': HEADER_STATE.CONTENT_LENGTH, + 'proxy-connection': HEADER_STATE.CONNECTION, + 'transfer-encoding': HEADER_STATE.TRANSFER_ENCODING, + 'upgrade': HEADER_STATE.UPGRADE, +}; +//# sourceMappingURL=constants.js.map + +/***/ }), + +/***/ 3870: +/***/ ((module) => { + +module.exports = 'AGFzbQEAAAABMAhgAX8Bf2ADf39/AX9gBH9/f38Bf2AAAGADf39/AGABfwBgAn9/AGAGf39/f39/AALLAQgDZW52GHdhc21fb25faGVhZGVyc19jb21wbGV0ZQACA2VudhV3YXNtX29uX21lc3NhZ2VfYmVnaW4AAANlbnYLd2FzbV9vbl91cmwAAQNlbnYOd2FzbV9vbl9zdGF0dXMAAQNlbnYUd2FzbV9vbl9oZWFkZXJfZmllbGQAAQNlbnYUd2FzbV9vbl9oZWFkZXJfdmFsdWUAAQNlbnYMd2FzbV9vbl9ib2R5AAEDZW52GHdhc21fb25fbWVzc2FnZV9jb21wbGV0ZQAAA0ZFAwMEAAAFAAAAAAAABQEFAAUFBQAABgAAAAAGBgYGAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAAABAQcAAAUFAwABBAUBcAESEgUDAQACBggBfwFBgNQECwfRBSIGbWVtb3J5AgALX2luaXRpYWxpemUACRlfX2luZGlyZWN0X2Z1bmN0aW9uX3RhYmxlAQALbGxodHRwX2luaXQAChhsbGh0dHBfc2hvdWxkX2tlZXBfYWxpdmUAQQxsbGh0dHBfYWxsb2MADAZtYWxsb2MARgtsbGh0dHBfZnJlZQANBGZyZWUASA9sbGh0dHBfZ2V0X3R5cGUADhVsbGh0dHBfZ2V0X2h0dHBfbWFqb3IADxVsbGh0dHBfZ2V0X2h0dHBfbWlub3IAEBFsbGh0dHBfZ2V0X21ldGhvZAARFmxsaHR0cF9nZXRfc3RhdHVzX2NvZGUAEhJsbGh0dHBfZ2V0X3VwZ3JhZGUAEwxsbGh0dHBfcmVzZXQAFA5sbGh0dHBfZXhlY3V0ZQAVFGxsaHR0cF9zZXR0aW5nc19pbml0ABYNbGxodHRwX2ZpbmlzaAAXDGxsaHR0cF9wYXVzZQAYDWxsaHR0cF9yZXN1bWUAGRtsbGh0dHBfcmVzdW1lX2FmdGVyX3VwZ3JhZGUAGhBsbGh0dHBfZ2V0X2Vycm5vABsXbGxodHRwX2dldF9lcnJvcl9yZWFzb24AHBdsbGh0dHBfc2V0X2Vycm9yX3JlYXNvbgAdFGxsaHR0cF9nZXRfZXJyb3JfcG9zAB4RbGxodHRwX2Vycm5vX25hbWUAHxJsbGh0dHBfbWV0aG9kX25hbWUAIBJsbGh0dHBfc3RhdHVzX25hbWUAIRpsbGh0dHBfc2V0X2xlbmllbnRfaGVhZGVycwAiIWxsaHR0cF9zZXRfbGVuaWVudF9jaHVua2VkX2xlbmd0aAAjHWxsaHR0cF9zZXRfbGVuaWVudF9rZWVwX2FsaXZlACQkbGxodHRwX3NldF9sZW5pZW50X3RyYW5zZmVyX2VuY29kaW5nACUYbGxodHRwX21lc3NhZ2VfbmVlZHNfZW9mAD8JFwEAQQELEQECAwQFCwYHNTk3MS8tJyspCsLgAkUCAAsIABCIgICAAAsZACAAEMKAgIAAGiAAIAI2AjggACABOgAoCxwAIAAgAC8BMiAALQAuIAAQwYCAgAAQgICAgAALKgEBf0HAABDGgICAACIBEMKAgIAAGiABQYCIgIAANgI4IAEgADoAKCABCwoAIAAQyICAgAALBwAgAC0AKAsHACAALQAqCwcAIAAtACsLBwAgAC0AKQsHACAALwEyCwcAIAAtAC4LRQEEfyAAKAIYIQEgAC0ALSECIAAtACghAyAAKAI4IQQgABDCgICAABogACAENgI4IAAgAzoAKCAAIAI6AC0gACABNgIYCxEAIAAgASABIAJqEMOAgIAACxAAIABBAEHcABDMgICAABoLZwEBf0EAIQECQCAAKAIMDQACQAJAAkACQCAALQAvDgMBAAMCCyAAKAI4IgFFDQAgASgCLCIBRQ0AIAAgARGAgICAAAAiAQ0DC0EADwsQyoCAgAAACyAAQcOWgIAANgIQQQ4hAQsgAQseAAJAIAAoAgwNACAAQdGbgIAANgIQIABBFTYCDAsLFgACQCAAKAIMQRVHDQAgAEEANgIMCwsWAAJAIAAoAgxBFkcNACAAQQA2AgwLCwcAIAAoAgwLBwAgACgCEAsJACAAIAE2AhALBwAgACgCFAsiAAJAIABBJEkNABDKgICAAAALIABBAnRBoLOAgABqKAIACyIAAkAgAEEuSQ0AEMqAgIAAAAsgAEECdEGwtICAAGooAgAL7gsBAX9B66iAgAAhAQJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIABBnH9qDvQDY2IAAWFhYWFhYQIDBAVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhBgcICQoLDA0OD2FhYWFhEGFhYWFhYWFhYWFhEWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYRITFBUWFxgZGhthYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2YTc4OTphYWFhYWFhYTthYWE8YWFhYT0+P2FhYWFhYWFhQGFhQWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYUJDREVGR0hJSktMTU5PUFFSU2FhYWFhYWFhVFVWV1hZWlthXF1hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFeYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhX2BhC0Hhp4CAAA8LQaShgIAADwtBy6yAgAAPC0H+sYCAAA8LQcCkgIAADwtBq6SAgAAPC0GNqICAAA8LQeKmgIAADwtBgLCAgAAPC0G5r4CAAA8LQdekgIAADwtB75+AgAAPC0Hhn4CAAA8LQfqfgIAADwtB8qCAgAAPC0Gor4CAAA8LQa6ygIAADwtBiLCAgAAPC0Hsp4CAAA8LQYKigIAADwtBjp2AgAAPC0HQroCAAA8LQcqjgIAADwtBxbKAgAAPC0HfnICAAA8LQdKcgIAADwtBxKCAgAAPC0HXoICAAA8LQaKfgIAADwtB7a6AgAAPC0GrsICAAA8LQdSlgIAADwtBzK6AgAAPC0H6roCAAA8LQfyrgIAADwtB0rCAgAAPC0HxnYCAAA8LQbuggIAADwtB96uAgAAPC0GQsYCAAA8LQdexgIAADwtBoq2AgAAPC0HUp4CAAA8LQeCrgIAADwtBn6yAgAAPC0HrsYCAAA8LQdWfgIAADwtByrGAgAAPC0HepYCAAA8LQdSegIAADwtB9JyAgAAPC0GnsoCAAA8LQbGdgIAADwtBoJ2AgAAPC0G5sYCAAA8LQbywgIAADwtBkqGAgAAPC0GzpoCAAA8LQemsgIAADwtBrJ6AgAAPC0HUq4CAAA8LQfemgIAADwtBgKaAgAAPC0GwoYCAAA8LQf6egIAADwtBjaOAgAAPC0GJrYCAAA8LQfeigIAADwtBoLGAgAAPC0Gun4CAAA8LQcalgIAADwtB6J6AgAAPC0GTooCAAA8LQcKvgIAADwtBw52AgAAPC0GLrICAAA8LQeGdgIAADwtBja+AgAAPC0HqoYCAAA8LQbStgIAADwtB0q+AgAAPC0HfsoCAAA8LQdKygIAADwtB8LCAgAAPC0GpooCAAA8LQfmjgIAADwtBmZ6AgAAPC0G1rICAAA8LQZuwgIAADwtBkrKAgAAPC0G2q4CAAA8LQcKigIAADwtB+LKAgAAPC0GepYCAAA8LQdCigIAADwtBup6AgAAPC0GBnoCAAA8LEMqAgIAAAAtB1qGAgAAhAQsgAQsWACAAIAAtAC1B/gFxIAFBAEdyOgAtCxkAIAAgAC0ALUH9AXEgAUEAR0EBdHI6AC0LGQAgACAALQAtQfsBcSABQQBHQQJ0cjoALQsZACAAIAAtAC1B9wFxIAFBAEdBA3RyOgAtCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAgAiBEUNACAAIAQRgICAgAAAIQMLIAMLSQECf0EAIQMCQCAAKAI4IgRFDQAgBCgCBCIERQ0AIAAgASACIAFrIAQRgYCAgAAAIgNBf0cNACAAQcaRgIAANgIQQRghAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIwIgRFDQAgACAEEYCAgIAAACEDCyADC0kBAn9BACEDAkAgACgCOCIERQ0AIAQoAggiBEUNACAAIAEgAiABayAEEYGAgIAAACIDQX9HDQAgAEH2ioCAADYCEEEYIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCNCIERQ0AIAAgBBGAgICAAAAhAwsgAwtJAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIMIgRFDQAgACABIAIgAWsgBBGBgICAAAAiA0F/Rw0AIABB7ZqAgAA2AhBBGCEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAjgiBEUNACAAIAQRgICAgAAAIQMLIAMLSQECf0EAIQMCQCAAKAI4IgRFDQAgBCgCECIERQ0AIAAgASACIAFrIAQRgYCAgAAAIgNBf0cNACAAQZWQgIAANgIQQRghAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAI8IgRFDQAgACAEEYCAgIAAACEDCyADC0kBAn9BACEDAkAgACgCOCIERQ0AIAQoAhQiBEUNACAAIAEgAiABayAEEYGAgIAAACIDQX9HDQAgAEGqm4CAADYCEEEYIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCQCIERQ0AIAAgBBGAgICAAAAhAwsgAwtJAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIYIgRFDQAgACABIAIgAWsgBBGBgICAAAAiA0F/Rw0AIABB7ZOAgAA2AhBBGCEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAkQiBEUNACAAIAQRgICAgAAAIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCJCIERQ0AIAAgBBGAgICAAAAhAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIsIgRFDQAgACAEEYCAgIAAACEDCyADC0kBAn9BACEDAkAgACgCOCIERQ0AIAQoAigiBEUNACAAIAEgAiABayAEEYGAgIAAACIDQX9HDQAgAEH2iICAADYCEEEYIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCUCIERQ0AIAAgBBGAgICAAAAhAwsgAwtJAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIcIgRFDQAgACABIAIgAWsgBBGBgICAAAAiA0F/Rw0AIABBwpmAgAA2AhBBGCEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAkgiBEUNACAAIAQRgICAgAAAIQMLIAMLSQECf0EAIQMCQCAAKAI4IgRFDQAgBCgCICIERQ0AIAAgASACIAFrIAQRgYCAgAAAIgNBf0cNACAAQZSUgIAANgIQQRghAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAJMIgRFDQAgACAEEYCAgIAAACEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAlQiBEUNACAAIAQRgICAgAAAIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCWCIERQ0AIAAgBBGAgICAAAAhAwsgAwtFAQF/AkACQCAALwEwQRRxQRRHDQBBASEDIAAtAChBAUYNASAALwEyQeUARiEDDAELIAAtAClBBUYhAwsgACADOgAuQQAL/gEBA39BASEDAkAgAC8BMCIEQQhxDQAgACkDIEIAUiEDCwJAAkAgAC0ALkUNAEEBIQUgAC0AKUEFRg0BQQEhBSAEQcAAcUUgA3FBAUcNAQtBACEFIARBwABxDQBBAiEFIARB//8DcSIDQQhxDQACQCADQYAEcUUNAAJAIAAtAChBAUcNACAALQAtQQpxDQBBBQ8LQQQPCwJAIANBIHENAAJAIAAtAChBAUYNACAALwEyQf//A3EiAEGcf2pB5ABJDQAgAEHMAUYNACAAQbACRg0AQQQhBSAEQShxRQ0CIANBiARxQYAERg0CC0EADwtBAEEDIAApAyBQGyEFCyAFC2IBAn9BACEBAkAgAC0AKEEBRg0AIAAvATJB//8DcSICQZx/akHkAEkNACACQcwBRg0AIAJBsAJGDQAgAC8BMCIAQcAAcQ0AQQEhASAAQYgEcUGABEYNACAAQShxRSEBCyABC6cBAQN/AkACQAJAIAAtACpFDQAgAC0AK0UNAEEAIQMgAC8BMCIEQQJxRQ0BDAILQQAhAyAALwEwIgRBAXFFDQELQQEhAyAALQAoQQFGDQAgAC8BMkH//wNxIgVBnH9qQeQASQ0AIAVBzAFGDQAgBUGwAkYNACAEQcAAcQ0AQQAhAyAEQYgEcUGABEYNACAEQShxQQBHIQMLIABBADsBMCAAQQA6AC8gAwuZAQECfwJAAkACQCAALQAqRQ0AIAAtACtFDQBBACEBIAAvATAiAkECcUUNAQwCC0EAIQEgAC8BMCICQQFxRQ0BC0EBIQEgAC0AKEEBRg0AIAAvATJB//8DcSIAQZx/akHkAEkNACAAQcwBRg0AIABBsAJGDQAgAkHAAHENAEEAIQEgAkGIBHFBgARGDQAgAkEocUEARyEBCyABC1kAIABBGGpCADcDACAAQgA3AwAgAEE4akIANwMAIABBMGpCADcDACAAQShqQgA3AwAgAEEgakIANwMAIABBEGpCADcDACAAQQhqQgA3AwAgAEHdATYCHEEAC3sBAX8CQCAAKAIMIgMNAAJAIAAoAgRFDQAgACABNgIECwJAIAAgASACEMSAgIAAIgMNACAAKAIMDwsgACADNgIcQQAhAyAAKAIEIgFFDQAgACABIAIgACgCCBGBgICAAAAiAUUNACAAIAI2AhQgACABNgIMIAEhAwsgAwvk8wEDDn8DfgR/I4CAgIAAQRBrIgMkgICAgAAgASEEIAEhBSABIQYgASEHIAEhCCABIQkgASEKIAEhCyABIQwgASENIAEhDiABIQ8CQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgACgCHCIQQX9qDt0B2gEB2QECAwQFBgcICQoLDA0O2AEPENcBERLWARMUFRYXGBkaG+AB3wEcHR7VAR8gISIjJCXUASYnKCkqKyzTAdIBLS7RAdABLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVG2wFHSElKzwHOAUvNAUzMAU1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+f4ABgQGCAYMBhAGFAYYBhwGIAYkBigGLAYwBjQGOAY8BkAGRAZIBkwGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwHLAcoBuAHJAbkByAG6AbsBvAG9Ab4BvwHAAcEBwgHDAcQBxQHGAQDcAQtBACEQDMYBC0EOIRAMxQELQQ0hEAzEAQtBDyEQDMMBC0EQIRAMwgELQRMhEAzBAQtBFCEQDMABC0EVIRAMvwELQRYhEAy+AQtBFyEQDL0BC0EYIRAMvAELQRkhEAy7AQtBGiEQDLoBC0EbIRAMuQELQRwhEAy4AQtBCCEQDLcBC0EdIRAMtgELQSAhEAy1AQtBHyEQDLQBC0EHIRAMswELQSEhEAyyAQtBIiEQDLEBC0EeIRAMsAELQSMhEAyvAQtBEiEQDK4BC0ERIRAMrQELQSQhEAysAQtBJSEQDKsBC0EmIRAMqgELQSchEAypAQtBwwEhEAyoAQtBKSEQDKcBC0ErIRAMpgELQSwhEAylAQtBLSEQDKQBC0EuIRAMowELQS8hEAyiAQtBxAEhEAyhAQtBMCEQDKABC0E0IRAMnwELQQwhEAyeAQtBMSEQDJ0BC0EyIRAMnAELQTMhEAybAQtBOSEQDJoBC0E1IRAMmQELQcUBIRAMmAELQQshEAyXAQtBOiEQDJYBC0E2IRAMlQELQQohEAyUAQtBNyEQDJMBC0E4IRAMkgELQTwhEAyRAQtBOyEQDJABC0E9IRAMjwELQQkhEAyOAQtBKCEQDI0BC0E+IRAMjAELQT8hEAyLAQtBwAAhEAyKAQtBwQAhEAyJAQtBwgAhEAyIAQtBwwAhEAyHAQtBxAAhEAyGAQtBxQAhEAyFAQtBxgAhEAyEAQtBKiEQDIMBC0HHACEQDIIBC0HIACEQDIEBC0HJACEQDIABC0HKACEQDH8LQcsAIRAMfgtBzQAhEAx9C0HMACEQDHwLQc4AIRAMewtBzwAhEAx6C0HQACEQDHkLQdEAIRAMeAtB0gAhEAx3C0HTACEQDHYLQdQAIRAMdQtB1gAhEAx0C0HVACEQDHMLQQYhEAxyC0HXACEQDHELQQUhEAxwC0HYACEQDG8LQQQhEAxuC0HZACEQDG0LQdoAIRAMbAtB2wAhEAxrC0HcACEQDGoLQQMhEAxpC0HdACEQDGgLQd4AIRAMZwtB3wAhEAxmC0HhACEQDGULQeAAIRAMZAtB4gAhEAxjC0HjACEQDGILQQIhEAxhC0HkACEQDGALQeUAIRAMXwtB5gAhEAxeC0HnACEQDF0LQegAIRAMXAtB6QAhEAxbC0HqACEQDFoLQesAIRAMWQtB7AAhEAxYC0HtACEQDFcLQe4AIRAMVgtB7wAhEAxVC0HwACEQDFQLQfEAIRAMUwtB8gAhEAxSC0HzACEQDFELQfQAIRAMUAtB9QAhEAxPC0H2ACEQDE4LQfcAIRAMTQtB+AAhEAxMC0H5ACEQDEsLQfoAIRAMSgtB+wAhEAxJC0H8ACEQDEgLQf0AIRAMRwtB/gAhEAxGC0H/ACEQDEULQYABIRAMRAtBgQEhEAxDC0GCASEQDEILQYMBIRAMQQtBhAEhEAxAC0GFASEQDD8LQYYBIRAMPgtBhwEhEAw9C0GIASEQDDwLQYkBIRAMOwtBigEhEAw6C0GLASEQDDkLQYwBIRAMOAtBjQEhEAw3C0GOASEQDDYLQY8BIRAMNQtBkAEhEAw0C0GRASEQDDMLQZIBIRAMMgtBkwEhEAwxC0GUASEQDDALQZUBIRAMLwtBlgEhEAwuC0GXASEQDC0LQZgBIRAMLAtBmQEhEAwrC0GaASEQDCoLQZsBIRAMKQtBnAEhEAwoC0GdASEQDCcLQZ4BIRAMJgtBnwEhEAwlC0GgASEQDCQLQaEBIRAMIwtBogEhEAwiC0GjASEQDCELQaQBIRAMIAtBpQEhEAwfC0GmASEQDB4LQacBIRAMHQtBqAEhEAwcC0GpASEQDBsLQaoBIRAMGgtBqwEhEAwZC0GsASEQDBgLQa0BIRAMFwtBrgEhEAwWC0EBIRAMFQtBrwEhEAwUC0GwASEQDBMLQbEBIRAMEgtBswEhEAwRC0GyASEQDBALQbQBIRAMDwtBtQEhEAwOC0G2ASEQDA0LQbcBIRAMDAtBuAEhEAwLC0G5ASEQDAoLQboBIRAMCQtBuwEhEAwIC0HGASEQDAcLQbwBIRAMBgtBvQEhEAwFC0G+ASEQDAQLQb8BIRAMAwtBwAEhEAwCC0HCASEQDAELQcEBIRALA0ACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAQDscBAAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxweHyAhIyUoP0BBREVGR0hJSktMTU9QUVJT3gNXWVtcXWBiZWZnaGlqa2xtb3BxcnN0dXZ3eHl6e3x9foABggGFAYYBhwGJAYsBjAGNAY4BjwGQAZEBlAGVAZYBlwGYAZkBmgGbAZwBnQGeAZ8BoAGhAaIBowGkAaUBpgGnAagBqQGqAasBrAGtAa4BrwGwAbEBsgGzAbQBtQG2AbcBuAG5AboBuwG8Ab0BvgG/AcABwQHCAcMBxAHFAcYBxwHIAckBygHLAcwBzQHOAc8B0AHRAdIB0wHUAdUB1gHXAdgB2QHaAdsB3AHdAd4B4AHhAeIB4wHkAeUB5gHnAegB6QHqAesB7AHtAe4B7wHwAfEB8gHzAZkCpAKwAv4C/gILIAEiBCACRw3zAUHdASEQDP8DCyABIhAgAkcN3QFBwwEhEAz+AwsgASIBIAJHDZABQfcAIRAM/QMLIAEiASACRw2GAUHvACEQDPwDCyABIgEgAkcNf0HqACEQDPsDCyABIgEgAkcNe0HoACEQDPoDCyABIgEgAkcNeEHmACEQDPkDCyABIgEgAkcNGkEYIRAM+AMLIAEiASACRw0UQRIhEAz3AwsgASIBIAJHDVlBxQAhEAz2AwsgASIBIAJHDUpBPyEQDPUDCyABIgEgAkcNSEE8IRAM9AMLIAEiASACRw1BQTEhEAzzAwsgAC0ALkEBRg3rAwyHAgsgACABIgEgAhDAgICAAEEBRw3mASAAQgA3AyAM5wELIAAgASIBIAIQtICAgAAiEA3nASABIQEM9QILAkAgASIBIAJHDQBBBiEQDPADCyAAIAFBAWoiASACELuAgIAAIhAN6AEgASEBDDELIABCADcDIEESIRAM1QMLIAEiECACRw0rQR0hEAztAwsCQCABIgEgAkYNACABQQFqIQFBECEQDNQDC0EHIRAM7AMLIABCACAAKQMgIhEgAiABIhBrrSISfSITIBMgEVYbNwMgIBEgElYiFEUN5QFBCCEQDOsDCwJAIAEiASACRg0AIABBiYCAgAA2AgggACABNgIEIAEhAUEUIRAM0gMLQQkhEAzqAwsgASEBIAApAyBQDeQBIAEhAQzyAgsCQCABIgEgAkcNAEELIRAM6QMLIAAgAUEBaiIBIAIQtoCAgAAiEA3lASABIQEM8gILIAAgASIBIAIQuICAgAAiEA3lASABIQEM8gILIAAgASIBIAIQuICAgAAiEA3mASABIQEMDQsgACABIgEgAhC6gICAACIQDecBIAEhAQzwAgsCQCABIgEgAkcNAEEPIRAM5QMLIAEtAAAiEEE7Rg0IIBBBDUcN6AEgAUEBaiEBDO8CCyAAIAEiASACELqAgIAAIhAN6AEgASEBDPICCwNAAkAgAS0AAEHwtYCAAGotAAAiEEEBRg0AIBBBAkcN6wEgACgCBCEQIABBADYCBCAAIBAgAUEBaiIBELmAgIAAIhAN6gEgASEBDPQCCyABQQFqIgEgAkcNAAtBEiEQDOIDCyAAIAEiASACELqAgIAAIhAN6QEgASEBDAoLIAEiASACRw0GQRshEAzgAwsCQCABIgEgAkcNAEEWIRAM4AMLIABBioCAgAA2AgggACABNgIEIAAgASACELiAgIAAIhAN6gEgASEBQSAhEAzGAwsCQCABIgEgAkYNAANAAkAgAS0AAEHwt4CAAGotAAAiEEECRg0AAkAgEEF/ag4E5QHsAQDrAewBCyABQQFqIQFBCCEQDMgDCyABQQFqIgEgAkcNAAtBFSEQDN8DC0EVIRAM3gMLA0ACQCABLQAAQfC5gIAAai0AACIQQQJGDQAgEEF/ag4E3gHsAeAB6wHsAQsgAUEBaiIBIAJHDQALQRghEAzdAwsCQCABIgEgAkYNACAAQYuAgIAANgIIIAAgATYCBCABIQFBByEQDMQDC0EZIRAM3AMLIAFBAWohAQwCCwJAIAEiFCACRw0AQRohEAzbAwsgFCEBAkAgFC0AAEFzag4U3QLuAu4C7gLuAu4C7gLuAu4C7gLuAu4C7gLuAu4C7gLuAu4C7gIA7gILQQAhECAAQQA2AhwgAEGvi4CAADYCECAAQQI2AgwgACAUQQFqNgIUDNoDCwJAIAEtAAAiEEE7Rg0AIBBBDUcN6AEgAUEBaiEBDOUCCyABQQFqIQELQSIhEAy/AwsCQCABIhAgAkcNAEEcIRAM2AMLQgAhESAQIQEgEC0AAEFQag435wHmAQECAwQFBgcIAAAAAAAAAAkKCwwNDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADxAREhMUAAtBHiEQDL0DC0ICIREM5QELQgMhEQzkAQtCBCERDOMBC0IFIREM4gELQgYhEQzhAQtCByERDOABC0IIIREM3wELQgkhEQzeAQtCCiERDN0BC0ILIREM3AELQgwhEQzbAQtCDSERDNoBC0IOIREM2QELQg8hEQzYAQtCCiERDNcBC0ILIREM1gELQgwhEQzVAQtCDSERDNQBC0IOIREM0wELQg8hEQzSAQtCACERAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAQLQAAQVBqDjflAeQBAAECAwQFBgfmAeYB5gHmAeYB5gHmAQgJCgsMDeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gEODxAREhPmAQtCAiERDOQBC0IDIREM4wELQgQhEQziAQtCBSERDOEBC0IGIREM4AELQgchEQzfAQtCCCERDN4BC0IJIREM3QELQgohEQzcAQtCCyERDNsBC0IMIREM2gELQg0hEQzZAQtCDiERDNgBC0IPIREM1wELQgohEQzWAQtCCyERDNUBC0IMIREM1AELQg0hEQzTAQtCDiERDNIBC0IPIREM0QELIABCACAAKQMgIhEgAiABIhBrrSISfSITIBMgEVYbNwMgIBEgElYiFEUN0gFBHyEQDMADCwJAIAEiASACRg0AIABBiYCAgAA2AgggACABNgIEIAEhAUEkIRAMpwMLQSAhEAy/AwsgACABIhAgAhC+gICAAEF/ag4FtgEAxQIB0QHSAQtBESEQDKQDCyAAQQE6AC8gECEBDLsDCyABIgEgAkcN0gFBJCEQDLsDCyABIg0gAkcNHkHGACEQDLoDCyAAIAEiASACELKAgIAAIhAN1AEgASEBDLUBCyABIhAgAkcNJkHQACEQDLgDCwJAIAEiASACRw0AQSghEAy4AwsgAEEANgIEIABBjICAgAA2AgggACABIAEQsYCAgAAiEA3TASABIQEM2AELAkAgASIQIAJHDQBBKSEQDLcDCyAQLQAAIgFBIEYNFCABQQlHDdMBIBBBAWohAQwVCwJAIAEiASACRg0AIAFBAWohAQwXC0EqIRAMtQMLAkAgASIQIAJHDQBBKyEQDLUDCwJAIBAtAAAiAUEJRg0AIAFBIEcN1QELIAAtACxBCEYN0wEgECEBDJEDCwJAIAEiASACRw0AQSwhEAy0AwsgAS0AAEEKRw3VASABQQFqIQEMyQILIAEiDiACRw3VAUEvIRAMsgMLA0ACQCABLQAAIhBBIEYNAAJAIBBBdmoOBADcAdwBANoBCyABIQEM4AELIAFBAWoiASACRw0AC0ExIRAMsQMLQTIhECABIhQgAkYNsAMgAiAUayAAKAIAIgFqIRUgFCABa0EDaiEWAkADQCAULQAAIhdBIHIgFyAXQb9/akH/AXFBGkkbQf8BcSABQfC7gIAAai0AAEcNAQJAIAFBA0cNAEEGIQEMlgMLIAFBAWohASAUQQFqIhQgAkcNAAsgACAVNgIADLEDCyAAQQA2AgAgFCEBDNkBC0EzIRAgASIUIAJGDa8DIAIgFGsgACgCACIBaiEVIBQgAWtBCGohFgJAA0AgFC0AACIXQSByIBcgF0G/f2pB/wFxQRpJG0H/AXEgAUH0u4CAAGotAABHDQECQCABQQhHDQBBBSEBDJUDCyABQQFqIQEgFEEBaiIUIAJHDQALIAAgFTYCAAywAwsgAEEANgIAIBQhAQzYAQtBNCEQIAEiFCACRg2uAyACIBRrIAAoAgAiAWohFSAUIAFrQQVqIRYCQANAIBQtAAAiF0EgciAXIBdBv39qQf8BcUEaSRtB/wFxIAFB0MKAgABqLQAARw0BAkAgAUEFRw0AQQchAQyUAwsgAUEBaiEBIBRBAWoiFCACRw0ACyAAIBU2AgAMrwMLIABBADYCACAUIQEM1wELAkAgASIBIAJGDQADQAJAIAEtAABBgL6AgABqLQAAIhBBAUYNACAQQQJGDQogASEBDN0BCyABQQFqIgEgAkcNAAtBMCEQDK4DC0EwIRAMrQMLAkAgASIBIAJGDQADQAJAIAEtAAAiEEEgRg0AIBBBdmoOBNkB2gHaAdkB2gELIAFBAWoiASACRw0AC0E4IRAMrQMLQTghEAysAwsDQAJAIAEtAAAiEEEgRg0AIBBBCUcNAwsgAUEBaiIBIAJHDQALQTwhEAyrAwsDQAJAIAEtAAAiEEEgRg0AAkACQCAQQXZqDgTaAQEB2gEACyAQQSxGDdsBCyABIQEMBAsgAUEBaiIBIAJHDQALQT8hEAyqAwsgASEBDNsBC0HAACEQIAEiFCACRg2oAyACIBRrIAAoAgAiAWohFiAUIAFrQQZqIRcCQANAIBQtAABBIHIgAUGAwICAAGotAABHDQEgAUEGRg2OAyABQQFqIQEgFEEBaiIUIAJHDQALIAAgFjYCAAypAwsgAEEANgIAIBQhAQtBNiEQDI4DCwJAIAEiDyACRw0AQcEAIRAMpwMLIABBjICAgAA2AgggACAPNgIEIA8hASAALQAsQX9qDgTNAdUB1wHZAYcDCyABQQFqIQEMzAELAkAgASIBIAJGDQADQAJAIAEtAAAiEEEgciAQIBBBv39qQf8BcUEaSRtB/wFxIhBBCUYNACAQQSBGDQACQAJAAkACQCAQQZ1/ag4TAAMDAwMDAwMBAwMDAwMDAwMDAgMLIAFBAWohAUExIRAMkQMLIAFBAWohAUEyIRAMkAMLIAFBAWohAUEzIRAMjwMLIAEhAQzQAQsgAUEBaiIBIAJHDQALQTUhEAylAwtBNSEQDKQDCwJAIAEiASACRg0AA0ACQCABLQAAQYC8gIAAai0AAEEBRg0AIAEhAQzTAQsgAUEBaiIBIAJHDQALQT0hEAykAwtBPSEQDKMDCyAAIAEiASACELCAgIAAIhAN1gEgASEBDAELIBBBAWohAQtBPCEQDIcDCwJAIAEiASACRw0AQcIAIRAMoAMLAkADQAJAIAEtAABBd2oOGAAC/gL+AoQD/gL+Av4C/gL+Av4C/gL+Av4C/gL+Av4C/gL+Av4C/gL+Av4CAP4CCyABQQFqIgEgAkcNAAtBwgAhEAygAwsgAUEBaiEBIAAtAC1BAXFFDb0BIAEhAQtBLCEQDIUDCyABIgEgAkcN0wFBxAAhEAydAwsDQAJAIAEtAABBkMCAgABqLQAAQQFGDQAgASEBDLcCCyABQQFqIgEgAkcNAAtBxQAhEAycAwsgDS0AACIQQSBGDbMBIBBBOkcNgQMgACgCBCEBIABBADYCBCAAIAEgDRCvgICAACIBDdABIA1BAWohAQyzAgtBxwAhECABIg0gAkYNmgMgAiANayAAKAIAIgFqIRYgDSABa0EFaiEXA0AgDS0AACIUQSByIBQgFEG/f2pB/wFxQRpJG0H/AXEgAUGQwoCAAGotAABHDYADIAFBBUYN9AIgAUEBaiEBIA1BAWoiDSACRw0ACyAAIBY2AgAMmgMLQcgAIRAgASINIAJGDZkDIAIgDWsgACgCACIBaiEWIA0gAWtBCWohFwNAIA0tAAAiFEEgciAUIBRBv39qQf8BcUEaSRtB/wFxIAFBlsKAgABqLQAARw3/AgJAIAFBCUcNAEECIQEM9QILIAFBAWohASANQQFqIg0gAkcNAAsgACAWNgIADJkDCwJAIAEiDSACRw0AQckAIRAMmQMLAkACQCANLQAAIgFBIHIgASABQb9/akH/AXFBGkkbQf8BcUGSf2oOBwCAA4ADgAOAA4ADAYADCyANQQFqIQFBPiEQDIADCyANQQFqIQFBPyEQDP8CC0HKACEQIAEiDSACRg2XAyACIA1rIAAoAgAiAWohFiANIAFrQQFqIRcDQCANLQAAIhRBIHIgFCAUQb9/akH/AXFBGkkbQf8BcSABQaDCgIAAai0AAEcN/QIgAUEBRg3wAiABQQFqIQEgDUEBaiINIAJHDQALIAAgFjYCAAyXAwtBywAhECABIg0gAkYNlgMgAiANayAAKAIAIgFqIRYgDSABa0EOaiEXA0AgDS0AACIUQSByIBQgFEG/f2pB/wFxQRpJG0H/AXEgAUGiwoCAAGotAABHDfwCIAFBDkYN8AIgAUEBaiEBIA1BAWoiDSACRw0ACyAAIBY2AgAMlgMLQcwAIRAgASINIAJGDZUDIAIgDWsgACgCACIBaiEWIA0gAWtBD2ohFwNAIA0tAAAiFEEgciAUIBRBv39qQf8BcUEaSRtB/wFxIAFBwMKAgABqLQAARw37AgJAIAFBD0cNAEEDIQEM8QILIAFBAWohASANQQFqIg0gAkcNAAsgACAWNgIADJUDC0HNACEQIAEiDSACRg2UAyACIA1rIAAoAgAiAWohFiANIAFrQQVqIRcDQCANLQAAIhRBIHIgFCAUQb9/akH/AXFBGkkbQf8BcSABQdDCgIAAai0AAEcN+gICQCABQQVHDQBBBCEBDPACCyABQQFqIQEgDUEBaiINIAJHDQALIAAgFjYCAAyUAwsCQCABIg0gAkcNAEHOACEQDJQDCwJAAkACQAJAIA0tAAAiAUEgciABIAFBv39qQf8BcUEaSRtB/wFxQZ1/ag4TAP0C/QL9Av0C/QL9Av0C/QL9Av0C/QL9AgH9Av0C/QICA/0CCyANQQFqIQFBwQAhEAz9AgsgDUEBaiEBQcIAIRAM/AILIA1BAWohAUHDACEQDPsCCyANQQFqIQFBxAAhEAz6AgsCQCABIgEgAkYNACAAQY2AgIAANgIIIAAgATYCBCABIQFBxQAhEAz6AgtBzwAhEAySAwsgECEBAkACQCAQLQAAQXZqDgQBqAKoAgCoAgsgEEEBaiEBC0EnIRAM+AILAkAgASIBIAJHDQBB0QAhEAyRAwsCQCABLQAAQSBGDQAgASEBDI0BCyABQQFqIQEgAC0ALUEBcUUNxwEgASEBDIwBCyABIhcgAkcNyAFB0gAhEAyPAwtB0wAhECABIhQgAkYNjgMgAiAUayAAKAIAIgFqIRYgFCABa0EBaiEXA0AgFC0AACABQdbCgIAAai0AAEcNzAEgAUEBRg3HASABQQFqIQEgFEEBaiIUIAJHDQALIAAgFjYCAAyOAwsCQCABIgEgAkcNAEHVACEQDI4DCyABLQAAQQpHDcwBIAFBAWohAQzHAQsCQCABIgEgAkcNAEHWACEQDI0DCwJAAkAgAS0AAEF2ag4EAM0BzQEBzQELIAFBAWohAQzHAQsgAUEBaiEBQcoAIRAM8wILIAAgASIBIAIQroCAgAAiEA3LASABIQFBzQAhEAzyAgsgAC0AKUEiRg2FAwymAgsCQCABIgEgAkcNAEHbACEQDIoDC0EAIRRBASEXQQEhFkEAIRACQAJAAkACQAJAAkACQAJAAkAgAS0AAEFQag4K1AHTAQABAgMEBQYI1QELQQIhEAwGC0EDIRAMBQtBBCEQDAQLQQUhEAwDC0EGIRAMAgtBByEQDAELQQghEAtBACEXQQAhFkEAIRQMzAELQQkhEEEBIRRBACEXQQAhFgzLAQsCQCABIgEgAkcNAEHdACEQDIkDCyABLQAAQS5HDcwBIAFBAWohAQymAgsgASIBIAJHDcwBQd8AIRAMhwMLAkAgASIBIAJGDQAgAEGOgICAADYCCCAAIAE2AgQgASEBQdAAIRAM7gILQeAAIRAMhgMLQeEAIRAgASIBIAJGDYUDIAIgAWsgACgCACIUaiEWIAEgFGtBA2ohFwNAIAEtAAAgFEHiwoCAAGotAABHDc0BIBRBA0YNzAEgFEEBaiEUIAFBAWoiASACRw0ACyAAIBY2AgAMhQMLQeIAIRAgASIBIAJGDYQDIAIgAWsgACgCACIUaiEWIAEgFGtBAmohFwNAIAEtAAAgFEHmwoCAAGotAABHDcwBIBRBAkYNzgEgFEEBaiEUIAFBAWoiASACRw0ACyAAIBY2AgAMhAMLQeMAIRAgASIBIAJGDYMDIAIgAWsgACgCACIUaiEWIAEgFGtBA2ohFwNAIAEtAAAgFEHpwoCAAGotAABHDcsBIBRBA0YNzgEgFEEBaiEUIAFBAWoiASACRw0ACyAAIBY2AgAMgwMLAkAgASIBIAJHDQBB5QAhEAyDAwsgACABQQFqIgEgAhCogICAACIQDc0BIAEhAUHWACEQDOkCCwJAIAEiASACRg0AA0ACQCABLQAAIhBBIEYNAAJAAkACQCAQQbh/ag4LAAHPAc8BzwHPAc8BzwHPAc8BAs8BCyABQQFqIQFB0gAhEAztAgsgAUEBaiEBQdMAIRAM7AILIAFBAWohAUHUACEQDOsCCyABQQFqIgEgAkcNAAtB5AAhEAyCAwtB5AAhEAyBAwsDQAJAIAEtAABB8MKAgABqLQAAIhBBAUYNACAQQX5qDgPPAdAB0QHSAQsgAUEBaiIBIAJHDQALQeYAIRAMgAMLAkAgASIBIAJGDQAgAUEBaiEBDAMLQecAIRAM/wILA0ACQCABLQAAQfDEgIAAai0AACIQQQFGDQACQCAQQX5qDgTSAdMB1AEA1QELIAEhAUHXACEQDOcCCyABQQFqIgEgAkcNAAtB6AAhEAz+AgsCQCABIgEgAkcNAEHpACEQDP4CCwJAIAEtAAAiEEF2ag4augHVAdUBvAHVAdUB1QHVAdUB1QHVAdUB1QHVAdUB1QHVAdUB1QHVAdUB1QHKAdUB1QEA0wELIAFBAWohAQtBBiEQDOMCCwNAAkAgAS0AAEHwxoCAAGotAABBAUYNACABIQEMngILIAFBAWoiASACRw0AC0HqACEQDPsCCwJAIAEiASACRg0AIAFBAWohAQwDC0HrACEQDPoCCwJAIAEiASACRw0AQewAIRAM+gILIAFBAWohAQwBCwJAIAEiASACRw0AQe0AIRAM+QILIAFBAWohAQtBBCEQDN4CCwJAIAEiFCACRw0AQe4AIRAM9wILIBQhAQJAAkACQCAULQAAQfDIgIAAai0AAEF/ag4H1AHVAdYBAJwCAQLXAQsgFEEBaiEBDAoLIBRBAWohAQzNAQtBACEQIABBADYCHCAAQZuSgIAANgIQIABBBzYCDCAAIBRBAWo2AhQM9gILAkADQAJAIAEtAABB8MiAgABqLQAAIhBBBEYNAAJAAkAgEEF/ag4H0gHTAdQB2QEABAHZAQsgASEBQdoAIRAM4AILIAFBAWohAUHcACEQDN8CCyABQQFqIgEgAkcNAAtB7wAhEAz2AgsgAUEBaiEBDMsBCwJAIAEiFCACRw0AQfAAIRAM9QILIBQtAABBL0cN1AEgFEEBaiEBDAYLAkAgASIUIAJHDQBB8QAhEAz0AgsCQCAULQAAIgFBL0cNACAUQQFqIQFB3QAhEAzbAgsgAUF2aiIEQRZLDdMBQQEgBHRBiYCAAnFFDdMBDMoCCwJAIAEiASACRg0AIAFBAWohAUHeACEQDNoCC0HyACEQDPICCwJAIAEiFCACRw0AQfQAIRAM8gILIBQhAQJAIBQtAABB8MyAgABqLQAAQX9qDgPJApQCANQBC0HhACEQDNgCCwJAIAEiFCACRg0AA0ACQCAULQAAQfDKgIAAai0AACIBQQNGDQACQCABQX9qDgLLAgDVAQsgFCEBQd8AIRAM2gILIBRBAWoiFCACRw0AC0HzACEQDPECC0HzACEQDPACCwJAIAEiASACRg0AIABBj4CAgAA2AgggACABNgIEIAEhAUHgACEQDNcCC0H1ACEQDO8CCwJAIAEiASACRw0AQfYAIRAM7wILIABBj4CAgAA2AgggACABNgIEIAEhAQtBAyEQDNQCCwNAIAEtAABBIEcNwwIgAUEBaiIBIAJHDQALQfcAIRAM7AILAkAgASIBIAJHDQBB+AAhEAzsAgsgAS0AAEEgRw3OASABQQFqIQEM7wELIAAgASIBIAIQrICAgAAiEA3OASABIQEMjgILAkAgASIEIAJHDQBB+gAhEAzqAgsgBC0AAEHMAEcN0QEgBEEBaiEBQRMhEAzPAQsCQCABIgQgAkcNAEH7ACEQDOkCCyACIARrIAAoAgAiAWohFCAEIAFrQQVqIRADQCAELQAAIAFB8M6AgABqLQAARw3QASABQQVGDc4BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQfsAIRAM6AILAkAgASIEIAJHDQBB/AAhEAzoAgsCQAJAIAQtAABBvX9qDgwA0QHRAdEB0QHRAdEB0QHRAdEB0QEB0QELIARBAWohAUHmACEQDM8CCyAEQQFqIQFB5wAhEAzOAgsCQCABIgQgAkcNAEH9ACEQDOcCCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUHtz4CAAGotAABHDc8BIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEH9ACEQDOcCCyAAQQA2AgAgEEEBaiEBQRAhEAzMAQsCQCABIgQgAkcNAEH+ACEQDOYCCyACIARrIAAoAgAiAWohFCAEIAFrQQVqIRACQANAIAQtAAAgAUH2zoCAAGotAABHDc4BIAFBBUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEH+ACEQDOYCCyAAQQA2AgAgEEEBaiEBQRYhEAzLAQsCQCABIgQgAkcNAEH/ACEQDOUCCyACIARrIAAoAgAiAWohFCAEIAFrQQNqIRACQANAIAQtAAAgAUH8zoCAAGotAABHDc0BIAFBA0YNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEH/ACEQDOUCCyAAQQA2AgAgEEEBaiEBQQUhEAzKAQsCQCABIgQgAkcNAEGAASEQDOQCCyAELQAAQdkARw3LASAEQQFqIQFBCCEQDMkBCwJAIAEiBCACRw0AQYEBIRAM4wILAkACQCAELQAAQbJ/ag4DAMwBAcwBCyAEQQFqIQFB6wAhEAzKAgsgBEEBaiEBQewAIRAMyQILAkAgASIEIAJHDQBBggEhEAziAgsCQAJAIAQtAABBuH9qDggAywHLAcsBywHLAcsBAcsBCyAEQQFqIQFB6gAhEAzJAgsgBEEBaiEBQe0AIRAMyAILAkAgASIEIAJHDQBBgwEhEAzhAgsgAiAEayAAKAIAIgFqIRAgBCABa0ECaiEUAkADQCAELQAAIAFBgM+AgABqLQAARw3JASABQQJGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBA2AgBBgwEhEAzhAgtBACEQIABBADYCACAUQQFqIQEMxgELAkAgASIEIAJHDQBBhAEhEAzgAgsgAiAEayAAKAIAIgFqIRQgBCABa0EEaiEQAkADQCAELQAAIAFBg8+AgABqLQAARw3IASABQQRGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBhAEhEAzgAgsgAEEANgIAIBBBAWohAUEjIRAMxQELAkAgASIEIAJHDQBBhQEhEAzfAgsCQAJAIAQtAABBtH9qDggAyAHIAcgByAHIAcgBAcgBCyAEQQFqIQFB7wAhEAzGAgsgBEEBaiEBQfAAIRAMxQILAkAgASIEIAJHDQBBhgEhEAzeAgsgBC0AAEHFAEcNxQEgBEEBaiEBDIMCCwJAIAEiBCACRw0AQYcBIRAM3QILIAIgBGsgACgCACIBaiEUIAQgAWtBA2ohEAJAA0AgBC0AACABQYjPgIAAai0AAEcNxQEgAUEDRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQYcBIRAM3QILIABBADYCACAQQQFqIQFBLSEQDMIBCwJAIAEiBCACRw0AQYgBIRAM3AILIAIgBGsgACgCACIBaiEUIAQgAWtBCGohEAJAA0AgBC0AACABQdDPgIAAai0AAEcNxAEgAUEIRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQYgBIRAM3AILIABBADYCACAQQQFqIQFBKSEQDMEBCwJAIAEiASACRw0AQYkBIRAM2wILQQEhECABLQAAQd8ARw3AASABQQFqIQEMgQILAkAgASIEIAJHDQBBigEhEAzaAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQA0AgBC0AACABQYzPgIAAai0AAEcNwQEgAUEBRg2vAiABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGKASEQDNkCCwJAIAEiBCACRw0AQYsBIRAM2QILIAIgBGsgACgCACIBaiEUIAQgAWtBAmohEAJAA0AgBC0AACABQY7PgIAAai0AAEcNwQEgAUECRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQYsBIRAM2QILIABBADYCACAQQQFqIQFBAiEQDL4BCwJAIAEiBCACRw0AQYwBIRAM2AILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQfDPgIAAai0AAEcNwAEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQYwBIRAM2AILIABBADYCACAQQQFqIQFBHyEQDL0BCwJAIAEiBCACRw0AQY0BIRAM1wILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQfLPgIAAai0AAEcNvwEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQY0BIRAM1wILIABBADYCACAQQQFqIQFBCSEQDLwBCwJAIAEiBCACRw0AQY4BIRAM1gILAkACQCAELQAAQbd/ag4HAL8BvwG/Ab8BvwEBvwELIARBAWohAUH4ACEQDL0CCyAEQQFqIQFB+QAhEAy8AgsCQCABIgQgAkcNAEGPASEQDNUCCyACIARrIAAoAgAiAWohFCAEIAFrQQVqIRACQANAIAQtAAAgAUGRz4CAAGotAABHDb0BIAFBBUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGPASEQDNUCCyAAQQA2AgAgEEEBaiEBQRghEAy6AQsCQCABIgQgAkcNAEGQASEQDNQCCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUGXz4CAAGotAABHDbwBIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGQASEQDNQCCyAAQQA2AgAgEEEBaiEBQRchEAy5AQsCQCABIgQgAkcNAEGRASEQDNMCCyACIARrIAAoAgAiAWohFCAEIAFrQQZqIRACQANAIAQtAAAgAUGaz4CAAGotAABHDbsBIAFBBkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGRASEQDNMCCyAAQQA2AgAgEEEBaiEBQRUhEAy4AQsCQCABIgQgAkcNAEGSASEQDNICCyACIARrIAAoAgAiAWohFCAEIAFrQQVqIRACQANAIAQtAAAgAUGhz4CAAGotAABHDboBIAFBBUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGSASEQDNICCyAAQQA2AgAgEEEBaiEBQR4hEAy3AQsCQCABIgQgAkcNAEGTASEQDNECCyAELQAAQcwARw24ASAEQQFqIQFBCiEQDLYBCwJAIAQgAkcNAEGUASEQDNACCwJAAkAgBC0AAEG/f2oODwC5AbkBuQG5AbkBuQG5AbkBuQG5AbkBuQG5AQG5AQsgBEEBaiEBQf4AIRAMtwILIARBAWohAUH/ACEQDLYCCwJAIAQgAkcNAEGVASEQDM8CCwJAAkAgBC0AAEG/f2oOAwC4AQG4AQsgBEEBaiEBQf0AIRAMtgILIARBAWohBEGAASEQDLUCCwJAIAQgAkcNAEGWASEQDM4CCyACIARrIAAoAgAiAWohFCAEIAFrQQFqIRACQANAIAQtAAAgAUGnz4CAAGotAABHDbYBIAFBAUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGWASEQDM4CCyAAQQA2AgAgEEEBaiEBQQshEAyzAQsCQCAEIAJHDQBBlwEhEAzNAgsCQAJAAkACQCAELQAAQVNqDiMAuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AQG4AbgBuAG4AbgBArgBuAG4AQO4AQsgBEEBaiEBQfsAIRAMtgILIARBAWohAUH8ACEQDLUCCyAEQQFqIQRBgQEhEAy0AgsgBEEBaiEEQYIBIRAMswILAkAgBCACRw0AQZgBIRAMzAILIAIgBGsgACgCACIBaiEUIAQgAWtBBGohEAJAA0AgBC0AACABQanPgIAAai0AAEcNtAEgAUEERg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZgBIRAMzAILIABBADYCACAQQQFqIQFBGSEQDLEBCwJAIAQgAkcNAEGZASEQDMsCCyACIARrIAAoAgAiAWohFCAEIAFrQQVqIRACQANAIAQtAAAgAUGuz4CAAGotAABHDbMBIAFBBUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGZASEQDMsCCyAAQQA2AgAgEEEBaiEBQQYhEAywAQsCQCAEIAJHDQBBmgEhEAzKAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQAkADQCAELQAAIAFBtM+AgABqLQAARw2yASABQQFGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBmgEhEAzKAgsgAEEANgIAIBBBAWohAUEcIRAMrwELAkAgBCACRw0AQZsBIRAMyQILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQbbPgIAAai0AAEcNsQEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZsBIRAMyQILIABBADYCACAQQQFqIQFBJyEQDK4BCwJAIAQgAkcNAEGcASEQDMgCCwJAAkAgBC0AAEGsf2oOAgABsQELIARBAWohBEGGASEQDK8CCyAEQQFqIQRBhwEhEAyuAgsCQCAEIAJHDQBBnQEhEAzHAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQAkADQCAELQAAIAFBuM+AgABqLQAARw2vASABQQFGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBnQEhEAzHAgsgAEEANgIAIBBBAWohAUEmIRAMrAELAkAgBCACRw0AQZ4BIRAMxgILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQbrPgIAAai0AAEcNrgEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZ4BIRAMxgILIABBADYCACAQQQFqIQFBAyEQDKsBCwJAIAQgAkcNAEGfASEQDMUCCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUHtz4CAAGotAABHDa0BIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGfASEQDMUCCyAAQQA2AgAgEEEBaiEBQQwhEAyqAQsCQCAEIAJHDQBBoAEhEAzEAgsgAiAEayAAKAIAIgFqIRQgBCABa0EDaiEQAkADQCAELQAAIAFBvM+AgABqLQAARw2sASABQQNGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBoAEhEAzEAgsgAEEANgIAIBBBAWohAUENIRAMqQELAkAgBCACRw0AQaEBIRAMwwILAkACQCAELQAAQbp/ag4LAKwBrAGsAawBrAGsAawBrAGsAQGsAQsgBEEBaiEEQYsBIRAMqgILIARBAWohBEGMASEQDKkCCwJAIAQgAkcNAEGiASEQDMICCyAELQAAQdAARw2pASAEQQFqIQQM6QELAkAgBCACRw0AQaMBIRAMwQILAkACQCAELQAAQbd/ag4HAaoBqgGqAaoBqgEAqgELIARBAWohBEGOASEQDKgCCyAEQQFqIQFBIiEQDKYBCwJAIAQgAkcNAEGkASEQDMACCyACIARrIAAoAgAiAWohFCAEIAFrQQFqIRACQANAIAQtAAAgAUHAz4CAAGotAABHDagBIAFBAUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGkASEQDMACCyAAQQA2AgAgEEEBaiEBQR0hEAylAQsCQCAEIAJHDQBBpQEhEAy/AgsCQAJAIAQtAABBrn9qDgMAqAEBqAELIARBAWohBEGQASEQDKYCCyAEQQFqIQFBBCEQDKQBCwJAIAQgAkcNAEGmASEQDL4CCwJAAkACQAJAAkAgBC0AAEG/f2oOFQCqAaoBqgGqAaoBqgGqAaoBqgGqAQGqAaoBAqoBqgEDqgGqAQSqAQsgBEEBaiEEQYgBIRAMqAILIARBAWohBEGJASEQDKcCCyAEQQFqIQRBigEhEAymAgsgBEEBaiEEQY8BIRAMpQILIARBAWohBEGRASEQDKQCCwJAIAQgAkcNAEGnASEQDL0CCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUHtz4CAAGotAABHDaUBIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGnASEQDL0CCyAAQQA2AgAgEEEBaiEBQREhEAyiAQsCQCAEIAJHDQBBqAEhEAy8AgsgAiAEayAAKAIAIgFqIRQgBCABa0ECaiEQAkADQCAELQAAIAFBws+AgABqLQAARw2kASABQQJGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBqAEhEAy8AgsgAEEANgIAIBBBAWohAUEsIRAMoQELAkAgBCACRw0AQakBIRAMuwILIAIgBGsgACgCACIBaiEUIAQgAWtBBGohEAJAA0AgBC0AACABQcXPgIAAai0AAEcNowEgAUEERg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQakBIRAMuwILIABBADYCACAQQQFqIQFBKyEQDKABCwJAIAQgAkcNAEGqASEQDLoCCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUHKz4CAAGotAABHDaIBIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGqASEQDLoCCyAAQQA2AgAgEEEBaiEBQRQhEAyfAQsCQCAEIAJHDQBBqwEhEAy5AgsCQAJAAkACQCAELQAAQb5/ag4PAAECpAGkAaQBpAGkAaQBpAGkAaQBpAGkAQOkAQsgBEEBaiEEQZMBIRAMogILIARBAWohBEGUASEQDKECCyAEQQFqIQRBlQEhEAygAgsgBEEBaiEEQZYBIRAMnwILAkAgBCACRw0AQawBIRAMuAILIAQtAABBxQBHDZ8BIARBAWohBAzgAQsCQCAEIAJHDQBBrQEhEAy3AgsgAiAEayAAKAIAIgFqIRQgBCABa0ECaiEQAkADQCAELQAAIAFBzc+AgABqLQAARw2fASABQQJGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBrQEhEAy3AgsgAEEANgIAIBBBAWohAUEOIRAMnAELAkAgBCACRw0AQa4BIRAMtgILIAQtAABB0ABHDZ0BIARBAWohAUElIRAMmwELAkAgBCACRw0AQa8BIRAMtQILIAIgBGsgACgCACIBaiEUIAQgAWtBCGohEAJAA0AgBC0AACABQdDPgIAAai0AAEcNnQEgAUEIRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQa8BIRAMtQILIABBADYCACAQQQFqIQFBKiEQDJoBCwJAIAQgAkcNAEGwASEQDLQCCwJAAkAgBC0AAEGrf2oOCwCdAZ0BnQGdAZ0BnQGdAZ0BnQEBnQELIARBAWohBEGaASEQDJsCCyAEQQFqIQRBmwEhEAyaAgsCQCAEIAJHDQBBsQEhEAyzAgsCQAJAIAQtAABBv39qDhQAnAGcAZwBnAGcAZwBnAGcAZwBnAGcAZwBnAGcAZwBnAGcAZwBAZwBCyAEQQFqIQRBmQEhEAyaAgsgBEEBaiEEQZwBIRAMmQILAkAgBCACRw0AQbIBIRAMsgILIAIgBGsgACgCACIBaiEUIAQgAWtBA2ohEAJAA0AgBC0AACABQdnPgIAAai0AAEcNmgEgAUEDRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQbIBIRAMsgILIABBADYCACAQQQFqIQFBISEQDJcBCwJAIAQgAkcNAEGzASEQDLECCyACIARrIAAoAgAiAWohFCAEIAFrQQZqIRACQANAIAQtAAAgAUHdz4CAAGotAABHDZkBIAFBBkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGzASEQDLECCyAAQQA2AgAgEEEBaiEBQRohEAyWAQsCQCAEIAJHDQBBtAEhEAywAgsCQAJAAkAgBC0AAEG7f2oOEQCaAZoBmgGaAZoBmgGaAZoBmgEBmgGaAZoBmgGaAQKaAQsgBEEBaiEEQZ0BIRAMmAILIARBAWohBEGeASEQDJcCCyAEQQFqIQRBnwEhEAyWAgsCQCAEIAJHDQBBtQEhEAyvAgsgAiAEayAAKAIAIgFqIRQgBCABa0EFaiEQAkADQCAELQAAIAFB5M+AgABqLQAARw2XASABQQVGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBtQEhEAyvAgsgAEEANgIAIBBBAWohAUEoIRAMlAELAkAgBCACRw0AQbYBIRAMrgILIAIgBGsgACgCACIBaiEUIAQgAWtBAmohEAJAA0AgBC0AACABQerPgIAAai0AAEcNlgEgAUECRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQbYBIRAMrgILIABBADYCACAQQQFqIQFBByEQDJMBCwJAIAQgAkcNAEG3ASEQDK0CCwJAAkAgBC0AAEG7f2oODgCWAZYBlgGWAZYBlgGWAZYBlgGWAZYBlgEBlgELIARBAWohBEGhASEQDJQCCyAEQQFqIQRBogEhEAyTAgsCQCAEIAJHDQBBuAEhEAysAgsgAiAEayAAKAIAIgFqIRQgBCABa0ECaiEQAkADQCAELQAAIAFB7c+AgABqLQAARw2UASABQQJGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBuAEhEAysAgsgAEEANgIAIBBBAWohAUESIRAMkQELAkAgBCACRw0AQbkBIRAMqwILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQfDPgIAAai0AAEcNkwEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQbkBIRAMqwILIABBADYCACAQQQFqIQFBICEQDJABCwJAIAQgAkcNAEG6ASEQDKoCCyACIARrIAAoAgAiAWohFCAEIAFrQQFqIRACQANAIAQtAAAgAUHyz4CAAGotAABHDZIBIAFBAUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEG6ASEQDKoCCyAAQQA2AgAgEEEBaiEBQQ8hEAyPAQsCQCAEIAJHDQBBuwEhEAypAgsCQAJAIAQtAABBt39qDgcAkgGSAZIBkgGSAQGSAQsgBEEBaiEEQaUBIRAMkAILIARBAWohBEGmASEQDI8CCwJAIAQgAkcNAEG8ASEQDKgCCyACIARrIAAoAgAiAWohFCAEIAFrQQdqIRACQANAIAQtAAAgAUH0z4CAAGotAABHDZABIAFBB0YNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEG8ASEQDKgCCyAAQQA2AgAgEEEBaiEBQRshEAyNAQsCQCAEIAJHDQBBvQEhEAynAgsCQAJAAkAgBC0AAEG+f2oOEgCRAZEBkQGRAZEBkQGRAZEBkQEBkQGRAZEBkQGRAZEBApEBCyAEQQFqIQRBpAEhEAyPAgsgBEEBaiEEQacBIRAMjgILIARBAWohBEGoASEQDI0CCwJAIAQgAkcNAEG+ASEQDKYCCyAELQAAQc4ARw2NASAEQQFqIQQMzwELAkAgBCACRw0AQb8BIRAMpQILAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgBC0AAEG/f2oOFQABAgOcAQQFBpwBnAGcAQcICQoLnAEMDQ4PnAELIARBAWohAUHoACEQDJoCCyAEQQFqIQFB6QAhEAyZAgsgBEEBaiEBQe4AIRAMmAILIARBAWohAUHyACEQDJcCCyAEQQFqIQFB8wAhEAyWAgsgBEEBaiEBQfYAIRAMlQILIARBAWohAUH3ACEQDJQCCyAEQQFqIQFB+gAhEAyTAgsgBEEBaiEEQYMBIRAMkgILIARBAWohBEGEASEQDJECCyAEQQFqIQRBhQEhEAyQAgsgBEEBaiEEQZIBIRAMjwILIARBAWohBEGYASEQDI4CCyAEQQFqIQRBoAEhEAyNAgsgBEEBaiEEQaMBIRAMjAILIARBAWohBEGqASEQDIsCCwJAIAQgAkYNACAAQZCAgIAANgIIIAAgBDYCBEGrASEQDIsCC0HAASEQDKMCCyAAIAUgAhCqgICAACIBDYsBIAUhAQxcCwJAIAYgAkYNACAGQQFqIQUMjQELQcIBIRAMoQILA0ACQCAQLQAAQXZqDgSMAQAAjwEACyAQQQFqIhAgAkcNAAtBwwEhEAygAgsCQCAHIAJGDQAgAEGRgICAADYCCCAAIAc2AgQgByEBQQEhEAyHAgtBxAEhEAyfAgsCQCAHIAJHDQBBxQEhEAyfAgsCQAJAIActAABBdmoOBAHOAc4BAM4BCyAHQQFqIQYMjQELIAdBAWohBQyJAQsCQCAHIAJHDQBBxgEhEAyeAgsCQAJAIActAABBdmoOFwGPAY8BAY8BjwGPAY8BjwGPAY8BjwGPAY8BjwGPAY8BjwGPAY8BjwGPAQCPAQsgB0EBaiEHC0GwASEQDIQCCwJAIAggAkcNAEHIASEQDJ0CCyAILQAAQSBHDY0BIABBADsBMiAIQQFqIQFBswEhEAyDAgsgASEXAkADQCAXIgcgAkYNASAHLQAAQVBqQf8BcSIQQQpPDcwBAkAgAC8BMiIUQZkzSw0AIAAgFEEKbCIUOwEyIBBB//8DcyAUQf7/A3FJDQAgB0EBaiEXIAAgFCAQaiIQOwEyIBBB//8DcUHoB0kNAQsLQQAhECAAQQA2AhwgAEHBiYCAADYCECAAQQ02AgwgACAHQQFqNgIUDJwCC0HHASEQDJsCCyAAIAggAhCugICAACIQRQ3KASAQQRVHDYwBIABByAE2AhwgACAINgIUIABByZeAgAA2AhAgAEEVNgIMQQAhEAyaAgsCQCAJIAJHDQBBzAEhEAyaAgtBACEUQQEhF0EBIRZBACEQAkACQAJAAkACQAJAAkACQAJAIAktAABBUGoOCpYBlQEAAQIDBAUGCJcBC0ECIRAMBgtBAyEQDAULQQQhEAwEC0EFIRAMAwtBBiEQDAILQQchEAwBC0EIIRALQQAhF0EAIRZBACEUDI4BC0EJIRBBASEUQQAhF0EAIRYMjQELAkAgCiACRw0AQc4BIRAMmQILIAotAABBLkcNjgEgCkEBaiEJDMoBCyALIAJHDY4BQdABIRAMlwILAkAgCyACRg0AIABBjoCAgAA2AgggACALNgIEQbcBIRAM/gELQdEBIRAMlgILAkAgBCACRw0AQdIBIRAMlgILIAIgBGsgACgCACIQaiEUIAQgEGtBBGohCwNAIAQtAAAgEEH8z4CAAGotAABHDY4BIBBBBEYN6QEgEEEBaiEQIARBAWoiBCACRw0ACyAAIBQ2AgBB0gEhEAyVAgsgACAMIAIQrICAgAAiAQ2NASAMIQEMuAELAkAgBCACRw0AQdQBIRAMlAILIAIgBGsgACgCACIQaiEUIAQgEGtBAWohDANAIAQtAAAgEEGB0ICAAGotAABHDY8BIBBBAUYNjgEgEEEBaiEQIARBAWoiBCACRw0ACyAAIBQ2AgBB1AEhEAyTAgsCQCAEIAJHDQBB1gEhEAyTAgsgAiAEayAAKAIAIhBqIRQgBCAQa0ECaiELA0AgBC0AACAQQYPQgIAAai0AAEcNjgEgEEECRg2QASAQQQFqIRAgBEEBaiIEIAJHDQALIAAgFDYCAEHWASEQDJICCwJAIAQgAkcNAEHXASEQDJICCwJAAkAgBC0AAEG7f2oOEACPAY8BjwGPAY8BjwGPAY8BjwGPAY8BjwGPAY8BAY8BCyAEQQFqIQRBuwEhEAz5AQsgBEEBaiEEQbwBIRAM+AELAkAgBCACRw0AQdgBIRAMkQILIAQtAABByABHDYwBIARBAWohBAzEAQsCQCAEIAJGDQAgAEGQgICAADYCCCAAIAQ2AgRBvgEhEAz3AQtB2QEhEAyPAgsCQCAEIAJHDQBB2gEhEAyPAgsgBC0AAEHIAEYNwwEgAEEBOgAoDLkBCyAAQQI6AC8gACAEIAIQpoCAgAAiEA2NAUHCASEQDPQBCyAALQAoQX9qDgK3AbkBuAELA0ACQCAELQAAQXZqDgQAjgGOAQCOAQsgBEEBaiIEIAJHDQALQd0BIRAMiwILIABBADoALyAALQAtQQRxRQ2EAgsgAEEAOgAvIABBAToANCABIQEMjAELIBBBFUYN2gEgAEEANgIcIAAgATYCFCAAQaeOgIAANgIQIABBEjYCDEEAIRAMiAILAkAgACAQIAIQtICAgAAiBA0AIBAhAQyBAgsCQCAEQRVHDQAgAEEDNgIcIAAgEDYCFCAAQbCYgIAANgIQIABBFTYCDEEAIRAMiAILIABBADYCHCAAIBA2AhQgAEGnjoCAADYCECAAQRI2AgxBACEQDIcCCyAQQRVGDdYBIABBADYCHCAAIAE2AhQgAEHajYCAADYCECAAQRQ2AgxBACEQDIYCCyAAKAIEIRcgAEEANgIEIBAgEadqIhYhASAAIBcgECAWIBQbIhAQtYCAgAAiFEUNjQEgAEEHNgIcIAAgEDYCFCAAIBQ2AgxBACEQDIUCCyAAIAAvATBBgAFyOwEwIAEhAQtBKiEQDOoBCyAQQRVGDdEBIABBADYCHCAAIAE2AhQgAEGDjICAADYCECAAQRM2AgxBACEQDIICCyAQQRVGDc8BIABBADYCHCAAIAE2AhQgAEGaj4CAADYCECAAQSI2AgxBACEQDIECCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQt4CAgAAiEA0AIAFBAWohAQyNAQsgAEEMNgIcIAAgEDYCDCAAIAFBAWo2AhRBACEQDIACCyAQQRVGDcwBIABBADYCHCAAIAE2AhQgAEGaj4CAADYCECAAQSI2AgxBACEQDP8BCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQt4CAgAAiEA0AIAFBAWohAQyMAQsgAEENNgIcIAAgEDYCDCAAIAFBAWo2AhRBACEQDP4BCyAQQRVGDckBIABBADYCHCAAIAE2AhQgAEHGjICAADYCECAAQSM2AgxBACEQDP0BCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQuYCAgAAiEA0AIAFBAWohAQyLAQsgAEEONgIcIAAgEDYCDCAAIAFBAWo2AhRBACEQDPwBCyAAQQA2AhwgACABNgIUIABBwJWAgAA2AhAgAEECNgIMQQAhEAz7AQsgEEEVRg3FASAAQQA2AhwgACABNgIUIABBxoyAgAA2AhAgAEEjNgIMQQAhEAz6AQsgAEEQNgIcIAAgATYCFCAAIBA2AgxBACEQDPkBCyAAKAIEIQQgAEEANgIEAkAgACAEIAEQuYCAgAAiBA0AIAFBAWohAQzxAQsgAEERNgIcIAAgBDYCDCAAIAFBAWo2AhRBACEQDPgBCyAQQRVGDcEBIABBADYCHCAAIAE2AhQgAEHGjICAADYCECAAQSM2AgxBACEQDPcBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQuYCAgAAiEA0AIAFBAWohAQyIAQsgAEETNgIcIAAgEDYCDCAAIAFBAWo2AhRBACEQDPYBCyAAKAIEIQQgAEEANgIEAkAgACAEIAEQuYCAgAAiBA0AIAFBAWohAQztAQsgAEEUNgIcIAAgBDYCDCAAIAFBAWo2AhRBACEQDPUBCyAQQRVGDb0BIABBADYCHCAAIAE2AhQgAEGaj4CAADYCECAAQSI2AgxBACEQDPQBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQt4CAgAAiEA0AIAFBAWohAQyGAQsgAEEWNgIcIAAgEDYCDCAAIAFBAWo2AhRBACEQDPMBCyAAKAIEIQQgAEEANgIEAkAgACAEIAEQt4CAgAAiBA0AIAFBAWohAQzpAQsgAEEXNgIcIAAgBDYCDCAAIAFBAWo2AhRBACEQDPIBCyAAQQA2AhwgACABNgIUIABBzZOAgAA2AhAgAEEMNgIMQQAhEAzxAQtCASERCyAQQQFqIQECQCAAKQMgIhJC//////////8PVg0AIAAgEkIEhiARhDcDICABIQEMhAELIABBADYCHCAAIAE2AhQgAEGtiYCAADYCECAAQQw2AgxBACEQDO8BCyAAQQA2AhwgACAQNgIUIABBzZOAgAA2AhAgAEEMNgIMQQAhEAzuAQsgACgCBCEXIABBADYCBCAQIBGnaiIWIQEgACAXIBAgFiAUGyIQELWAgIAAIhRFDXMgAEEFNgIcIAAgEDYCFCAAIBQ2AgxBACEQDO0BCyAAQQA2AhwgACAQNgIUIABBqpyAgAA2AhAgAEEPNgIMQQAhEAzsAQsgACAQIAIQtICAgAAiAQ0BIBAhAQtBDiEQDNEBCwJAIAFBFUcNACAAQQI2AhwgACAQNgIUIABBsJiAgAA2AhAgAEEVNgIMQQAhEAzqAQsgAEEANgIcIAAgEDYCFCAAQaeOgIAANgIQIABBEjYCDEEAIRAM6QELIAFBAWohEAJAIAAvATAiAUGAAXFFDQACQCAAIBAgAhC7gICAACIBDQAgECEBDHALIAFBFUcNugEgAEEFNgIcIAAgEDYCFCAAQfmXgIAANgIQIABBFTYCDEEAIRAM6QELAkAgAUGgBHFBoARHDQAgAC0ALUECcQ0AIABBADYCHCAAIBA2AhQgAEGWk4CAADYCECAAQQQ2AgxBACEQDOkBCyAAIBAgAhC9gICAABogECEBAkACQAJAAkACQCAAIBAgAhCzgICAAA4WAgEABAQEBAQEBAQEBAQEBAQEBAQEAwQLIABBAToALgsgACAALwEwQcAAcjsBMCAQIQELQSYhEAzRAQsgAEEjNgIcIAAgEDYCFCAAQaWWgIAANgIQIABBFTYCDEEAIRAM6QELIABBADYCHCAAIBA2AhQgAEHVi4CAADYCECAAQRE2AgxBACEQDOgBCyAALQAtQQFxRQ0BQcMBIRAMzgELAkAgDSACRg0AA0ACQCANLQAAQSBGDQAgDSEBDMQBCyANQQFqIg0gAkcNAAtBJSEQDOcBC0ElIRAM5gELIAAoAgQhBCAAQQA2AgQgACAEIA0Qr4CAgAAiBEUNrQEgAEEmNgIcIAAgBDYCDCAAIA1BAWo2AhRBACEQDOUBCyAQQRVGDasBIABBADYCHCAAIAE2AhQgAEH9jYCAADYCECAAQR02AgxBACEQDOQBCyAAQSc2AhwgACABNgIUIAAgEDYCDEEAIRAM4wELIBAhAUEBIRQCQAJAAkACQAJAAkACQCAALQAsQX5qDgcGBQUDAQIABQsgACAALwEwQQhyOwEwDAMLQQIhFAwBC0EEIRQLIABBAToALCAAIAAvATAgFHI7ATALIBAhAQtBKyEQDMoBCyAAQQA2AhwgACAQNgIUIABBq5KAgAA2AhAgAEELNgIMQQAhEAziAQsgAEEANgIcIAAgATYCFCAAQeGPgIAANgIQIABBCjYCDEEAIRAM4QELIABBADoALCAQIQEMvQELIBAhAUEBIRQCQAJAAkACQAJAIAAtACxBe2oOBAMBAgAFCyAAIAAvATBBCHI7ATAMAwtBAiEUDAELQQQhFAsgAEEBOgAsIAAgAC8BMCAUcjsBMAsgECEBC0EpIRAMxQELIABBADYCHCAAIAE2AhQgAEHwlICAADYCECAAQQM2AgxBACEQDN0BCwJAIA4tAABBDUcNACAAKAIEIQEgAEEANgIEAkAgACABIA4QsYCAgAAiAQ0AIA5BAWohAQx1CyAAQSw2AhwgACABNgIMIAAgDkEBajYCFEEAIRAM3QELIAAtAC1BAXFFDQFBxAEhEAzDAQsCQCAOIAJHDQBBLSEQDNwBCwJAAkADQAJAIA4tAABBdmoOBAIAAAMACyAOQQFqIg4gAkcNAAtBLSEQDN0BCyAAKAIEIQEgAEEANgIEAkAgACABIA4QsYCAgAAiAQ0AIA4hAQx0CyAAQSw2AhwgACAONgIUIAAgATYCDEEAIRAM3AELIAAoAgQhASAAQQA2AgQCQCAAIAEgDhCxgICAACIBDQAgDkEBaiEBDHMLIABBLDYCHCAAIAE2AgwgACAOQQFqNgIUQQAhEAzbAQsgACgCBCEEIABBADYCBCAAIAQgDhCxgICAACIEDaABIA4hAQzOAQsgEEEsRw0BIAFBAWohEEEBIQECQAJAAkACQAJAIAAtACxBe2oOBAMBAgQACyAQIQEMBAtBAiEBDAELQQQhAQsgAEEBOgAsIAAgAC8BMCABcjsBMCAQIQEMAQsgACAALwEwQQhyOwEwIBAhAQtBOSEQDL8BCyAAQQA6ACwgASEBC0E0IRAMvQELIAAgAC8BMEEgcjsBMCABIQEMAgsgACgCBCEEIABBADYCBAJAIAAgBCABELGAgIAAIgQNACABIQEMxwELIABBNzYCHCAAIAE2AhQgACAENgIMQQAhEAzUAQsgAEEIOgAsIAEhAQtBMCEQDLkBCwJAIAAtAChBAUYNACABIQEMBAsgAC0ALUEIcUUNkwEgASEBDAMLIAAtADBBIHENlAFBxQEhEAy3AQsCQCAPIAJGDQACQANAAkAgDy0AAEFQaiIBQf8BcUEKSQ0AIA8hAUE1IRAMugELIAApAyAiEUKZs+bMmbPmzBlWDQEgACARQgp+IhE3AyAgESABrUL/AYMiEkJ/hVYNASAAIBEgEnw3AyAgD0EBaiIPIAJHDQALQTkhEAzRAQsgACgCBCECIABBADYCBCAAIAIgD0EBaiIEELGAgIAAIgINlQEgBCEBDMMBC0E5IRAMzwELAkAgAC8BMCIBQQhxRQ0AIAAtAChBAUcNACAALQAtQQhxRQ2QAQsgACABQff7A3FBgARyOwEwIA8hAQtBNyEQDLQBCyAAIAAvATBBEHI7ATAMqwELIBBBFUYNiwEgAEEANgIcIAAgATYCFCAAQfCOgIAANgIQIABBHDYCDEEAIRAMywELIABBwwA2AhwgACABNgIMIAAgDUEBajYCFEEAIRAMygELAkAgAS0AAEE6Rw0AIAAoAgQhECAAQQA2AgQCQCAAIBAgARCvgICAACIQDQAgAUEBaiEBDGMLIABBwwA2AhwgACAQNgIMIAAgAUEBajYCFEEAIRAMygELIABBADYCHCAAIAE2AhQgAEGxkYCAADYCECAAQQo2AgxBACEQDMkBCyAAQQA2AhwgACABNgIUIABBoJmAgAA2AhAgAEEeNgIMQQAhEAzIAQsgAEEANgIACyAAQYASOwEqIAAgF0EBaiIBIAIQqICAgAAiEA0BIAEhAQtBxwAhEAysAQsgEEEVRw2DASAAQdEANgIcIAAgATYCFCAAQeOXgIAANgIQIABBFTYCDEEAIRAMxAELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDF4LIABB0gA2AhwgACABNgIUIAAgEDYCDEEAIRAMwwELIABBADYCHCAAIBQ2AhQgAEHBqICAADYCECAAQQc2AgwgAEEANgIAQQAhEAzCAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMXQsgAEHTADYCHCAAIAE2AhQgACAQNgIMQQAhEAzBAQtBACEQIABBADYCHCAAIAE2AhQgAEGAkYCAADYCECAAQQk2AgwMwAELIBBBFUYNfSAAQQA2AhwgACABNgIUIABBlI2AgAA2AhAgAEEhNgIMQQAhEAy/AQtBASEWQQAhF0EAIRRBASEQCyAAIBA6ACsgAUEBaiEBAkACQCAALQAtQRBxDQACQAJAAkAgAC0AKg4DAQACBAsgFkUNAwwCCyAUDQEMAgsgF0UNAQsgACgCBCEQIABBADYCBAJAIAAgECABEK2AgIAAIhANACABIQEMXAsgAEHYADYCHCAAIAE2AhQgACAQNgIMQQAhEAy+AQsgACgCBCEEIABBADYCBAJAIAAgBCABEK2AgIAAIgQNACABIQEMrQELIABB2QA2AhwgACABNgIUIAAgBDYCDEEAIRAMvQELIAAoAgQhBCAAQQA2AgQCQCAAIAQgARCtgICAACIEDQAgASEBDKsBCyAAQdoANgIcIAAgATYCFCAAIAQ2AgxBACEQDLwBCyAAKAIEIQQgAEEANgIEAkAgACAEIAEQrYCAgAAiBA0AIAEhAQypAQsgAEHcADYCHCAAIAE2AhQgACAENgIMQQAhEAy7AQsCQCABLQAAQVBqIhBB/wFxQQpPDQAgACAQOgAqIAFBAWohAUHPACEQDKIBCyAAKAIEIQQgAEEANgIEAkAgACAEIAEQrYCAgAAiBA0AIAEhAQynAQsgAEHeADYCHCAAIAE2AhQgACAENgIMQQAhEAy6AQsgAEEANgIAIBdBAWohAQJAIAAtAClBI08NACABIQEMWQsgAEEANgIcIAAgATYCFCAAQdOJgIAANgIQIABBCDYCDEEAIRAMuQELIABBADYCAAtBACEQIABBADYCHCAAIAE2AhQgAEGQs4CAADYCECAAQQg2AgwMtwELIABBADYCACAXQQFqIQECQCAALQApQSFHDQAgASEBDFYLIABBADYCHCAAIAE2AhQgAEGbioCAADYCECAAQQg2AgxBACEQDLYBCyAAQQA2AgAgF0EBaiEBAkAgAC0AKSIQQV1qQQtPDQAgASEBDFULAkAgEEEGSw0AQQEgEHRBygBxRQ0AIAEhAQxVC0EAIRAgAEEANgIcIAAgATYCFCAAQfeJgIAANgIQIABBCDYCDAy1AQsgEEEVRg1xIABBADYCHCAAIAE2AhQgAEG5jYCAADYCECAAQRo2AgxBACEQDLQBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxUCyAAQeUANgIcIAAgATYCFCAAIBA2AgxBACEQDLMBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxNCyAAQdIANgIcIAAgATYCFCAAIBA2AgxBACEQDLIBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxNCyAAQdMANgIcIAAgATYCFCAAIBA2AgxBACEQDLEBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxRCyAAQeUANgIcIAAgATYCFCAAIBA2AgxBACEQDLABCyAAQQA2AhwgACABNgIUIABBxoqAgAA2AhAgAEEHNgIMQQAhEAyvAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMSQsgAEHSADYCHCAAIAE2AhQgACAQNgIMQQAhEAyuAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMSQsgAEHTADYCHCAAIAE2AhQgACAQNgIMQQAhEAytAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMTQsgAEHlADYCHCAAIAE2AhQgACAQNgIMQQAhEAysAQsgAEEANgIcIAAgATYCFCAAQdyIgIAANgIQIABBBzYCDEEAIRAMqwELIBBBP0cNASABQQFqIQELQQUhEAyQAQtBACEQIABBADYCHCAAIAE2AhQgAEH9koCAADYCECAAQQc2AgwMqAELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDEILIABB0gA2AhwgACABNgIUIAAgEDYCDEEAIRAMpwELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDEILIABB0wA2AhwgACABNgIUIAAgEDYCDEEAIRAMpgELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDEYLIABB5QA2AhwgACABNgIUIAAgEDYCDEEAIRAMpQELIAAoAgQhASAAQQA2AgQCQCAAIAEgFBCngICAACIBDQAgFCEBDD8LIABB0gA2AhwgACAUNgIUIAAgATYCDEEAIRAMpAELIAAoAgQhASAAQQA2AgQCQCAAIAEgFBCngICAACIBDQAgFCEBDD8LIABB0wA2AhwgACAUNgIUIAAgATYCDEEAIRAMowELIAAoAgQhASAAQQA2AgQCQCAAIAEgFBCngICAACIBDQAgFCEBDEMLIABB5QA2AhwgACAUNgIUIAAgATYCDEEAIRAMogELIABBADYCHCAAIBQ2AhQgAEHDj4CAADYCECAAQQc2AgxBACEQDKEBCyAAQQA2AhwgACABNgIUIABBw4+AgAA2AhAgAEEHNgIMQQAhEAygAQtBACEQIABBADYCHCAAIBQ2AhQgAEGMnICAADYCECAAQQc2AgwMnwELIABBADYCHCAAIBQ2AhQgAEGMnICAADYCECAAQQc2AgxBACEQDJ4BCyAAQQA2AhwgACAUNgIUIABB/pGAgAA2AhAgAEEHNgIMQQAhEAydAQsgAEEANgIcIAAgATYCFCAAQY6bgIAANgIQIABBBjYCDEEAIRAMnAELIBBBFUYNVyAAQQA2AhwgACABNgIUIABBzI6AgAA2AhAgAEEgNgIMQQAhEAybAQsgAEEANgIAIBBBAWohAUEkIRALIAAgEDoAKSAAKAIEIRAgAEEANgIEIAAgECABEKuAgIAAIhANVCABIQEMPgsgAEEANgIAC0EAIRAgAEEANgIcIAAgBDYCFCAAQfGbgIAANgIQIABBBjYCDAyXAQsgAUEVRg1QIABBADYCHCAAIAU2AhQgAEHwjICAADYCECAAQRs2AgxBACEQDJYBCyAAKAIEIQUgAEEANgIEIAAgBSAQEKmAgIAAIgUNASAQQQFqIQULQa0BIRAMewsgAEHBATYCHCAAIAU2AgwgACAQQQFqNgIUQQAhEAyTAQsgACgCBCEGIABBADYCBCAAIAYgEBCpgICAACIGDQEgEEEBaiEGC0GuASEQDHgLIABBwgE2AhwgACAGNgIMIAAgEEEBajYCFEEAIRAMkAELIABBADYCHCAAIAc2AhQgAEGXi4CAADYCECAAQQ02AgxBACEQDI8BCyAAQQA2AhwgACAINgIUIABB45CAgAA2AhAgAEEJNgIMQQAhEAyOAQsgAEEANgIcIAAgCDYCFCAAQZSNgIAANgIQIABBITYCDEEAIRAMjQELQQEhFkEAIRdBACEUQQEhEAsgACAQOgArIAlBAWohCAJAAkAgAC0ALUEQcQ0AAkACQAJAIAAtACoOAwEAAgQLIBZFDQMMAgsgFA0BDAILIBdFDQELIAAoAgQhECAAQQA2AgQgACAQIAgQrYCAgAAiEEUNPSAAQckBNgIcIAAgCDYCFCAAIBA2AgxBACEQDIwBCyAAKAIEIQQgAEEANgIEIAAgBCAIEK2AgIAAIgRFDXYgAEHKATYCHCAAIAg2AhQgACAENgIMQQAhEAyLAQsgACgCBCEEIABBADYCBCAAIAQgCRCtgICAACIERQ10IABBywE2AhwgACAJNgIUIAAgBDYCDEEAIRAMigELIAAoAgQhBCAAQQA2AgQgACAEIAoQrYCAgAAiBEUNciAAQc0BNgIcIAAgCjYCFCAAIAQ2AgxBACEQDIkBCwJAIAstAABBUGoiEEH/AXFBCk8NACAAIBA6ACogC0EBaiEKQbYBIRAMcAsgACgCBCEEIABBADYCBCAAIAQgCxCtgICAACIERQ1wIABBzwE2AhwgACALNgIUIAAgBDYCDEEAIRAMiAELIABBADYCHCAAIAQ2AhQgAEGQs4CAADYCECAAQQg2AgwgAEEANgIAQQAhEAyHAQsgAUEVRg0/IABBADYCHCAAIAw2AhQgAEHMjoCAADYCECAAQSA2AgxBACEQDIYBCyAAQYEEOwEoIAAoAgQhECAAQgA3AwAgACAQIAxBAWoiDBCrgICAACIQRQ04IABB0wE2AhwgACAMNgIUIAAgEDYCDEEAIRAMhQELIABBADYCAAtBACEQIABBADYCHCAAIAQ2AhQgAEHYm4CAADYCECAAQQg2AgwMgwELIAAoAgQhECAAQgA3AwAgACAQIAtBAWoiCxCrgICAACIQDQFBxgEhEAxpCyAAQQI6ACgMVQsgAEHVATYCHCAAIAs2AhQgACAQNgIMQQAhEAyAAQsgEEEVRg03IABBADYCHCAAIAQ2AhQgAEGkjICAADYCECAAQRA2AgxBACEQDH8LIAAtADRBAUcNNCAAIAQgAhC8gICAACIQRQ00IBBBFUcNNSAAQdwBNgIcIAAgBDYCFCAAQdWWgIAANgIQIABBFTYCDEEAIRAMfgtBACEQIABBADYCHCAAQa+LgIAANgIQIABBAjYCDCAAIBRBAWo2AhQMfQtBACEQDGMLQQIhEAxiC0ENIRAMYQtBDyEQDGALQSUhEAxfC0ETIRAMXgtBFSEQDF0LQRYhEAxcC0EXIRAMWwtBGCEQDFoLQRkhEAxZC0EaIRAMWAtBGyEQDFcLQRwhEAxWC0EdIRAMVQtBHyEQDFQLQSEhEAxTC0EjIRAMUgtBxgAhEAxRC0EuIRAMUAtBLyEQDE8LQTshEAxOC0E9IRAMTQtByAAhEAxMC0HJACEQDEsLQcsAIRAMSgtBzAAhEAxJC0HOACEQDEgLQdEAIRAMRwtB1QAhEAxGC0HYACEQDEULQdkAIRAMRAtB2wAhEAxDC0HkACEQDEILQeUAIRAMQQtB8QAhEAxAC0H0ACEQDD8LQY0BIRAMPgtBlwEhEAw9C0GpASEQDDwLQawBIRAMOwtBwAEhEAw6C0G5ASEQDDkLQa8BIRAMOAtBsQEhEAw3C0GyASEQDDYLQbQBIRAMNQtBtQEhEAw0C0G6ASEQDDMLQb0BIRAMMgtBvwEhEAwxC0HBASEQDDALIABBADYCHCAAIAQ2AhQgAEHpi4CAADYCECAAQR82AgxBACEQDEgLIABB2wE2AhwgACAENgIUIABB+paAgAA2AhAgAEEVNgIMQQAhEAxHCyAAQfgANgIcIAAgDDYCFCAAQcqYgIAANgIQIABBFTYCDEEAIRAMRgsgAEHRADYCHCAAIAU2AhQgAEGwl4CAADYCECAAQRU2AgxBACEQDEULIABB+QA2AhwgACABNgIUIAAgEDYCDEEAIRAMRAsgAEH4ADYCHCAAIAE2AhQgAEHKmICAADYCECAAQRU2AgxBACEQDEMLIABB5AA2AhwgACABNgIUIABB45eAgAA2AhAgAEEVNgIMQQAhEAxCCyAAQdcANgIcIAAgATYCFCAAQcmXgIAANgIQIABBFTYCDEEAIRAMQQsgAEEANgIcIAAgATYCFCAAQbmNgIAANgIQIABBGjYCDEEAIRAMQAsgAEHCADYCHCAAIAE2AhQgAEHjmICAADYCECAAQRU2AgxBACEQDD8LIABBADYCBCAAIA8gDxCxgICAACIERQ0BIABBOjYCHCAAIAQ2AgwgACAPQQFqNgIUQQAhEAw+CyAAKAIEIQQgAEEANgIEAkAgACAEIAEQsYCAgAAiBEUNACAAQTs2AhwgACAENgIMIAAgAUEBajYCFEEAIRAMPgsgAUEBaiEBDC0LIA9BAWohAQwtCyAAQQA2AhwgACAPNgIUIABB5JKAgAA2AhAgAEEENgIMQQAhEAw7CyAAQTY2AhwgACAENgIUIAAgAjYCDEEAIRAMOgsgAEEuNgIcIAAgDjYCFCAAIAQ2AgxBACEQDDkLIABB0AA2AhwgACABNgIUIABBkZiAgAA2AhAgAEEVNgIMQQAhEAw4CyANQQFqIQEMLAsgAEEVNgIcIAAgATYCFCAAQYKZgIAANgIQIABBFTYCDEEAIRAMNgsgAEEbNgIcIAAgATYCFCAAQZGXgIAANgIQIABBFTYCDEEAIRAMNQsgAEEPNgIcIAAgATYCFCAAQZGXgIAANgIQIABBFTYCDEEAIRAMNAsgAEELNgIcIAAgATYCFCAAQZGXgIAANgIQIABBFTYCDEEAIRAMMwsgAEEaNgIcIAAgATYCFCAAQYKZgIAANgIQIABBFTYCDEEAIRAMMgsgAEELNgIcIAAgATYCFCAAQYKZgIAANgIQIABBFTYCDEEAIRAMMQsgAEEKNgIcIAAgATYCFCAAQeSWgIAANgIQIABBFTYCDEEAIRAMMAsgAEEeNgIcIAAgATYCFCAAQfmXgIAANgIQIABBFTYCDEEAIRAMLwsgAEEANgIcIAAgEDYCFCAAQdqNgIAANgIQIABBFDYCDEEAIRAMLgsgAEEENgIcIAAgATYCFCAAQbCYgIAANgIQIABBFTYCDEEAIRAMLQsgAEEANgIAIAtBAWohCwtBuAEhEAwSCyAAQQA2AgAgEEEBaiEBQfUAIRAMEQsgASEBAkAgAC0AKUEFRw0AQeMAIRAMEQtB4gAhEAwQC0EAIRAgAEEANgIcIABB5JGAgAA2AhAgAEEHNgIMIAAgFEEBajYCFAwoCyAAQQA2AgAgF0EBaiEBQcAAIRAMDgtBASEBCyAAIAE6ACwgAEEANgIAIBdBAWohAQtBKCEQDAsLIAEhAQtBOCEQDAkLAkAgASIPIAJGDQADQAJAIA8tAABBgL6AgABqLQAAIgFBAUYNACABQQJHDQMgD0EBaiEBDAQLIA9BAWoiDyACRw0AC0E+IRAMIgtBPiEQDCELIABBADoALCAPIQEMAQtBCyEQDAYLQTohEAwFCyABQQFqIQFBLSEQDAQLIAAgAToALCAAQQA2AgAgFkEBaiEBQQwhEAwDCyAAQQA2AgAgF0EBaiEBQQohEAwCCyAAQQA2AgALIABBADoALCANIQFBCSEQDAALC0EAIRAgAEEANgIcIAAgCzYCFCAAQc2QgIAANgIQIABBCTYCDAwXC0EAIRAgAEEANgIcIAAgCjYCFCAAQemKgIAANgIQIABBCTYCDAwWC0EAIRAgAEEANgIcIAAgCTYCFCAAQbeQgIAANgIQIABBCTYCDAwVC0EAIRAgAEEANgIcIAAgCDYCFCAAQZyRgIAANgIQIABBCTYCDAwUC0EAIRAgAEEANgIcIAAgATYCFCAAQc2QgIAANgIQIABBCTYCDAwTC0EAIRAgAEEANgIcIAAgATYCFCAAQemKgIAANgIQIABBCTYCDAwSC0EAIRAgAEEANgIcIAAgATYCFCAAQbeQgIAANgIQIABBCTYCDAwRC0EAIRAgAEEANgIcIAAgATYCFCAAQZyRgIAANgIQIABBCTYCDAwQC0EAIRAgAEEANgIcIAAgATYCFCAAQZeVgIAANgIQIABBDzYCDAwPC0EAIRAgAEEANgIcIAAgATYCFCAAQZeVgIAANgIQIABBDzYCDAwOC0EAIRAgAEEANgIcIAAgATYCFCAAQcCSgIAANgIQIABBCzYCDAwNC0EAIRAgAEEANgIcIAAgATYCFCAAQZWJgIAANgIQIABBCzYCDAwMC0EAIRAgAEEANgIcIAAgATYCFCAAQeGPgIAANgIQIABBCjYCDAwLC0EAIRAgAEEANgIcIAAgATYCFCAAQfuPgIAANgIQIABBCjYCDAwKC0EAIRAgAEEANgIcIAAgATYCFCAAQfGZgIAANgIQIABBAjYCDAwJC0EAIRAgAEEANgIcIAAgATYCFCAAQcSUgIAANgIQIABBAjYCDAwIC0EAIRAgAEEANgIcIAAgATYCFCAAQfKVgIAANgIQIABBAjYCDAwHCyAAQQI2AhwgACABNgIUIABBnJqAgAA2AhAgAEEWNgIMQQAhEAwGC0EBIRAMBQtB1AAhECABIgQgAkYNBCADQQhqIAAgBCACQdjCgIAAQQoQxYCAgAAgAygCDCEEIAMoAggOAwEEAgALEMqAgIAAAAsgAEEANgIcIABBtZqAgAA2AhAgAEEXNgIMIAAgBEEBajYCFEEAIRAMAgsgAEEANgIcIAAgBDYCFCAAQcqagIAANgIQIABBCTYCDEEAIRAMAQsCQCABIgQgAkcNAEEiIRAMAQsgAEGJgICAADYCCCAAIAQ2AgRBISEQCyADQRBqJICAgIAAIBALrwEBAn8gASgCACEGAkACQCACIANGDQAgBCAGaiEEIAYgA2ogAmshByACIAZBf3MgBWoiBmohBQNAAkAgAi0AACAELQAARg0AQQIhBAwDCwJAIAYNAEEAIQQgBSECDAMLIAZBf2ohBiAEQQFqIQQgAkEBaiICIANHDQALIAchBiADIQILIABBATYCACABIAY2AgAgACACNgIEDwsgAUEANgIAIAAgBDYCACAAIAI2AgQLCgAgABDHgICAAAvyNgELfyOAgICAAEEQayIBJICAgIAAAkBBACgCoNCAgAANAEEAEMuAgIAAQYDUhIAAayICQdkASQ0AQQAhAwJAQQAoAuDTgIAAIgQNAEEAQn83AuzTgIAAQQBCgICEgICAwAA3AuTTgIAAQQAgAUEIakFwcUHYqtWqBXMiBDYC4NOAgABBAEEANgL004CAAEEAQQA2AsTTgIAAC0EAIAI2AszTgIAAQQBBgNSEgAA2AsjTgIAAQQBBgNSEgAA2ApjQgIAAQQAgBDYCrNCAgABBAEF/NgKo0ICAAANAIANBxNCAgABqIANBuNCAgABqIgQ2AgAgBCADQbDQgIAAaiIFNgIAIANBvNCAgABqIAU2AgAgA0HM0ICAAGogA0HA0ICAAGoiBTYCACAFIAQ2AgAgA0HU0ICAAGogA0HI0ICAAGoiBDYCACAEIAU2AgAgA0HQ0ICAAGogBDYCACADQSBqIgNBgAJHDQALQYDUhIAAQXhBgNSEgABrQQ9xQQBBgNSEgABBCGpBD3EbIgNqIgRBBGogAkFIaiIFIANrIgNBAXI2AgBBAEEAKALw04CAADYCpNCAgABBACADNgKU0ICAAEEAIAQ2AqDQgIAAQYDUhIAAIAVqQTg2AgQLAkACQAJAAkACQAJAAkACQAJAAkACQAJAIABB7AFLDQACQEEAKAKI0ICAACIGQRAgAEETakFwcSAAQQtJGyICQQN2IgR2IgNBA3FFDQACQAJAIANBAXEgBHJBAXMiBUEDdCIEQbDQgIAAaiIDIARBuNCAgABqKAIAIgQoAggiAkcNAEEAIAZBfiAFd3E2AojQgIAADAELIAMgAjYCCCACIAM2AgwLIARBCGohAyAEIAVBA3QiBUEDcjYCBCAEIAVqIgQgBCgCBEEBcjYCBAwMCyACQQAoApDQgIAAIgdNDQECQCADRQ0AAkACQCADIAR0QQIgBHQiA0EAIANrcnEiA0EAIANrcUF/aiIDIANBDHZBEHEiA3YiBEEFdkEIcSIFIANyIAQgBXYiA0ECdkEEcSIEciADIAR2IgNBAXZBAnEiBHIgAyAEdiIDQQF2QQFxIgRyIAMgBHZqIgRBA3QiA0Gw0ICAAGoiBSADQbjQgIAAaigCACIDKAIIIgBHDQBBACAGQX4gBHdxIgY2AojQgIAADAELIAUgADYCCCAAIAU2AgwLIAMgAkEDcjYCBCADIARBA3QiBGogBCACayIFNgIAIAMgAmoiACAFQQFyNgIEAkAgB0UNACAHQXhxQbDQgIAAaiECQQAoApzQgIAAIQQCQAJAIAZBASAHQQN2dCIIcQ0AQQAgBiAIcjYCiNCAgAAgAiEIDAELIAIoAgghCAsgCCAENgIMIAIgBDYCCCAEIAI2AgwgBCAINgIICyADQQhqIQNBACAANgKc0ICAAEEAIAU2ApDQgIAADAwLQQAoAozQgIAAIglFDQEgCUEAIAlrcUF/aiIDIANBDHZBEHEiA3YiBEEFdkEIcSIFIANyIAQgBXYiA0ECdkEEcSIEciADIAR2IgNBAXZBAnEiBHIgAyAEdiIDQQF2QQFxIgRyIAMgBHZqQQJ0QbjSgIAAaigCACIAKAIEQXhxIAJrIQQgACEFAkADQAJAIAUoAhAiAw0AIAVBFGooAgAiA0UNAgsgAygCBEF4cSACayIFIAQgBSAESSIFGyEEIAMgACAFGyEAIAMhBQwACwsgACgCGCEKAkAgACgCDCIIIABGDQAgACgCCCIDQQAoApjQgIAASRogCCADNgIIIAMgCDYCDAwLCwJAIABBFGoiBSgCACIDDQAgACgCECIDRQ0DIABBEGohBQsDQCAFIQsgAyIIQRRqIgUoAgAiAw0AIAhBEGohBSAIKAIQIgMNAAsgC0EANgIADAoLQX8hAiAAQb9/Sw0AIABBE2oiA0FwcSECQQAoAozQgIAAIgdFDQBBACELAkAgAkGAAkkNAEEfIQsgAkH///8HSw0AIANBCHYiAyADQYD+P2pBEHZBCHEiA3QiBCAEQYDgH2pBEHZBBHEiBHQiBSAFQYCAD2pBEHZBAnEiBXRBD3YgAyAEciAFcmsiA0EBdCACIANBFWp2QQFxckEcaiELC0EAIAJrIQQCQAJAAkACQCALQQJ0QbjSgIAAaigCACIFDQBBACEDQQAhCAwBC0EAIQMgAkEAQRkgC0EBdmsgC0EfRht0IQBBACEIA0ACQCAFKAIEQXhxIAJrIgYgBE8NACAGIQQgBSEIIAYNAEEAIQQgBSEIIAUhAwwDCyADIAVBFGooAgAiBiAGIAUgAEEddkEEcWpBEGooAgAiBUYbIAMgBhshAyAAQQF0IQAgBQ0ACwsCQCADIAhyDQBBACEIQQIgC3QiA0EAIANrciAHcSIDRQ0DIANBACADa3FBf2oiAyADQQx2QRBxIgN2IgVBBXZBCHEiACADciAFIAB2IgNBAnZBBHEiBXIgAyAFdiIDQQF2QQJxIgVyIAMgBXYiA0EBdkEBcSIFciADIAV2akECdEG40oCAAGooAgAhAwsgA0UNAQsDQCADKAIEQXhxIAJrIgYgBEkhAAJAIAMoAhAiBQ0AIANBFGooAgAhBQsgBiAEIAAbIQQgAyAIIAAbIQggBSEDIAUNAAsLIAhFDQAgBEEAKAKQ0ICAACACa08NACAIKAIYIQsCQCAIKAIMIgAgCEYNACAIKAIIIgNBACgCmNCAgABJGiAAIAM2AgggAyAANgIMDAkLAkAgCEEUaiIFKAIAIgMNACAIKAIQIgNFDQMgCEEQaiEFCwNAIAUhBiADIgBBFGoiBSgCACIDDQAgAEEQaiEFIAAoAhAiAw0ACyAGQQA2AgAMCAsCQEEAKAKQ0ICAACIDIAJJDQBBACgCnNCAgAAhBAJAAkAgAyACayIFQRBJDQAgBCACaiIAIAVBAXI2AgRBACAFNgKQ0ICAAEEAIAA2ApzQgIAAIAQgA2ogBTYCACAEIAJBA3I2AgQMAQsgBCADQQNyNgIEIAQgA2oiAyADKAIEQQFyNgIEQQBBADYCnNCAgABBAEEANgKQ0ICAAAsgBEEIaiEDDAoLAkBBACgClNCAgAAiACACTQ0AQQAoAqDQgIAAIgMgAmoiBCAAIAJrIgVBAXI2AgRBACAFNgKU0ICAAEEAIAQ2AqDQgIAAIAMgAkEDcjYCBCADQQhqIQMMCgsCQAJAQQAoAuDTgIAARQ0AQQAoAujTgIAAIQQMAQtBAEJ/NwLs04CAAEEAQoCAhICAgMAANwLk04CAAEEAIAFBDGpBcHFB2KrVqgVzNgLg04CAAEEAQQA2AvTTgIAAQQBBADYCxNOAgABBgIAEIQQLQQAhAwJAIAQgAkHHAGoiB2oiBkEAIARrIgtxIgggAksNAEEAQTA2AvjTgIAADAoLAkBBACgCwNOAgAAiA0UNAAJAQQAoArjTgIAAIgQgCGoiBSAETQ0AIAUgA00NAQtBACEDQQBBMDYC+NOAgAAMCgtBAC0AxNOAgABBBHENBAJAAkACQEEAKAKg0ICAACIERQ0AQcjTgIAAIQMDQAJAIAMoAgAiBSAESw0AIAUgAygCBGogBEsNAwsgAygCCCIDDQALC0EAEMuAgIAAIgBBf0YNBSAIIQYCQEEAKALk04CAACIDQX9qIgQgAHFFDQAgCCAAayAEIABqQQAgA2txaiEGCyAGIAJNDQUgBkH+////B0sNBQJAQQAoAsDTgIAAIgNFDQBBACgCuNOAgAAiBCAGaiIFIARNDQYgBSADSw0GCyAGEMuAgIAAIgMgAEcNAQwHCyAGIABrIAtxIgZB/v///wdLDQQgBhDLgICAACIAIAMoAgAgAygCBGpGDQMgACEDCwJAIANBf0YNACACQcgAaiAGTQ0AAkAgByAGa0EAKALo04CAACIEakEAIARrcSIEQf7///8HTQ0AIAMhAAwHCwJAIAQQy4CAgABBf0YNACAEIAZqIQYgAyEADAcLQQAgBmsQy4CAgAAaDAQLIAMhACADQX9HDQUMAwtBACEIDAcLQQAhAAwFCyAAQX9HDQILQQBBACgCxNOAgABBBHI2AsTTgIAACyAIQf7///8HSw0BIAgQy4CAgAAhAEEAEMuAgIAAIQMgAEF/Rg0BIANBf0YNASAAIANPDQEgAyAAayIGIAJBOGpNDQELQQBBACgCuNOAgAAgBmoiAzYCuNOAgAACQCADQQAoArzTgIAATQ0AQQAgAzYCvNOAgAALAkACQAJAAkBBACgCoNCAgAAiBEUNAEHI04CAACEDA0AgACADKAIAIgUgAygCBCIIakYNAiADKAIIIgMNAAwDCwsCQAJAQQAoApjQgIAAIgNFDQAgACADTw0BC0EAIAA2ApjQgIAAC0EAIQNBACAGNgLM04CAAEEAIAA2AsjTgIAAQQBBfzYCqNCAgABBAEEAKALg04CAADYCrNCAgABBAEEANgLU04CAAANAIANBxNCAgABqIANBuNCAgABqIgQ2AgAgBCADQbDQgIAAaiIFNgIAIANBvNCAgABqIAU2AgAgA0HM0ICAAGogA0HA0ICAAGoiBTYCACAFIAQ2AgAgA0HU0ICAAGogA0HI0ICAAGoiBDYCACAEIAU2AgAgA0HQ0ICAAGogBDYCACADQSBqIgNBgAJHDQALIABBeCAAa0EPcUEAIABBCGpBD3EbIgNqIgQgBkFIaiIFIANrIgNBAXI2AgRBAEEAKALw04CAADYCpNCAgABBACADNgKU0ICAAEEAIAQ2AqDQgIAAIAAgBWpBODYCBAwCCyADLQAMQQhxDQAgBCAFSQ0AIAQgAE8NACAEQXggBGtBD3FBACAEQQhqQQ9xGyIFaiIAQQAoApTQgIAAIAZqIgsgBWsiBUEBcjYCBCADIAggBmo2AgRBAEEAKALw04CAADYCpNCAgABBACAFNgKU0ICAAEEAIAA2AqDQgIAAIAQgC2pBODYCBAwBCwJAIABBACgCmNCAgAAiCE8NAEEAIAA2ApjQgIAAIAAhCAsgACAGaiEFQcjTgIAAIQMCQAJAAkACQAJAAkACQANAIAMoAgAgBUYNASADKAIIIgMNAAwCCwsgAy0ADEEIcUUNAQtByNOAgAAhAwNAAkAgAygCACIFIARLDQAgBSADKAIEaiIFIARLDQMLIAMoAgghAwwACwsgAyAANgIAIAMgAygCBCAGajYCBCAAQXggAGtBD3FBACAAQQhqQQ9xG2oiCyACQQNyNgIEIAVBeCAFa0EPcUEAIAVBCGpBD3EbaiIGIAsgAmoiAmshAwJAIAYgBEcNAEEAIAI2AqDQgIAAQQBBACgClNCAgAAgA2oiAzYClNCAgAAgAiADQQFyNgIEDAMLAkAgBkEAKAKc0ICAAEcNAEEAIAI2ApzQgIAAQQBBACgCkNCAgAAgA2oiAzYCkNCAgAAgAiADQQFyNgIEIAIgA2ogAzYCAAwDCwJAIAYoAgQiBEEDcUEBRw0AIARBeHEhBwJAAkAgBEH/AUsNACAGKAIIIgUgBEEDdiIIQQN0QbDQgIAAaiIARhoCQCAGKAIMIgQgBUcNAEEAQQAoAojQgIAAQX4gCHdxNgKI0ICAAAwCCyAEIABGGiAEIAU2AgggBSAENgIMDAELIAYoAhghCQJAAkAgBigCDCIAIAZGDQAgBigCCCIEIAhJGiAAIAQ2AgggBCAANgIMDAELAkAgBkEUaiIEKAIAIgUNACAGQRBqIgQoAgAiBQ0AQQAhAAwBCwNAIAQhCCAFIgBBFGoiBCgCACIFDQAgAEEQaiEEIAAoAhAiBQ0ACyAIQQA2AgALIAlFDQACQAJAIAYgBigCHCIFQQJ0QbjSgIAAaiIEKAIARw0AIAQgADYCACAADQFBAEEAKAKM0ICAAEF+IAV3cTYCjNCAgAAMAgsgCUEQQRQgCSgCECAGRhtqIAA2AgAgAEUNAQsgACAJNgIYAkAgBigCECIERQ0AIAAgBDYCECAEIAA2AhgLIAYoAhQiBEUNACAAQRRqIAQ2AgAgBCAANgIYCyAHIANqIQMgBiAHaiIGKAIEIQQLIAYgBEF+cTYCBCACIANqIAM2AgAgAiADQQFyNgIEAkAgA0H/AUsNACADQXhxQbDQgIAAaiEEAkACQEEAKAKI0ICAACIFQQEgA0EDdnQiA3ENAEEAIAUgA3I2AojQgIAAIAQhAwwBCyAEKAIIIQMLIAMgAjYCDCAEIAI2AgggAiAENgIMIAIgAzYCCAwDC0EfIQQCQCADQf///wdLDQAgA0EIdiIEIARBgP4/akEQdkEIcSIEdCIFIAVBgOAfakEQdkEEcSIFdCIAIABBgIAPakEQdkECcSIAdEEPdiAEIAVyIAByayIEQQF0IAMgBEEVanZBAXFyQRxqIQQLIAIgBDYCHCACQgA3AhAgBEECdEG40oCAAGohBQJAQQAoAozQgIAAIgBBASAEdCIIcQ0AIAUgAjYCAEEAIAAgCHI2AozQgIAAIAIgBTYCGCACIAI2AgggAiACNgIMDAMLIANBAEEZIARBAXZrIARBH0YbdCEEIAUoAgAhAANAIAAiBSgCBEF4cSADRg0CIARBHXYhACAEQQF0IQQgBSAAQQRxakEQaiIIKAIAIgANAAsgCCACNgIAIAIgBTYCGCACIAI2AgwgAiACNgIIDAILIABBeCAAa0EPcUEAIABBCGpBD3EbIgNqIgsgBkFIaiIIIANrIgNBAXI2AgQgACAIakE4NgIEIAQgBUE3IAVrQQ9xQQAgBUFJakEPcRtqQUFqIgggCCAEQRBqSRsiCEEjNgIEQQBBACgC8NOAgAA2AqTQgIAAQQAgAzYClNCAgABBACALNgKg0ICAACAIQRBqQQApAtDTgIAANwIAIAhBACkCyNOAgAA3AghBACAIQQhqNgLQ04CAAEEAIAY2AszTgIAAQQAgADYCyNOAgABBAEEANgLU04CAACAIQSRqIQMDQCADQQc2AgAgA0EEaiIDIAVJDQALIAggBEYNAyAIIAgoAgRBfnE2AgQgCCAIIARrIgA2AgAgBCAAQQFyNgIEAkAgAEH/AUsNACAAQXhxQbDQgIAAaiEDAkACQEEAKAKI0ICAACIFQQEgAEEDdnQiAHENAEEAIAUgAHI2AojQgIAAIAMhBQwBCyADKAIIIQULIAUgBDYCDCADIAQ2AgggBCADNgIMIAQgBTYCCAwEC0EfIQMCQCAAQf///wdLDQAgAEEIdiIDIANBgP4/akEQdkEIcSIDdCIFIAVBgOAfakEQdkEEcSIFdCIIIAhBgIAPakEQdkECcSIIdEEPdiADIAVyIAhyayIDQQF0IAAgA0EVanZBAXFyQRxqIQMLIAQgAzYCHCAEQgA3AhAgA0ECdEG40oCAAGohBQJAQQAoAozQgIAAIghBASADdCIGcQ0AIAUgBDYCAEEAIAggBnI2AozQgIAAIAQgBTYCGCAEIAQ2AgggBCAENgIMDAQLIABBAEEZIANBAXZrIANBH0YbdCEDIAUoAgAhCANAIAgiBSgCBEF4cSAARg0DIANBHXYhCCADQQF0IQMgBSAIQQRxakEQaiIGKAIAIggNAAsgBiAENgIAIAQgBTYCGCAEIAQ2AgwgBCAENgIIDAMLIAUoAggiAyACNgIMIAUgAjYCCCACQQA2AhggAiAFNgIMIAIgAzYCCAsgC0EIaiEDDAULIAUoAggiAyAENgIMIAUgBDYCCCAEQQA2AhggBCAFNgIMIAQgAzYCCAtBACgClNCAgAAiAyACTQ0AQQAoAqDQgIAAIgQgAmoiBSADIAJrIgNBAXI2AgRBACADNgKU0ICAAEEAIAU2AqDQgIAAIAQgAkEDcjYCBCAEQQhqIQMMAwtBACEDQQBBMDYC+NOAgAAMAgsCQCALRQ0AAkACQCAIIAgoAhwiBUECdEG40oCAAGoiAygCAEcNACADIAA2AgAgAA0BQQAgB0F+IAV3cSIHNgKM0ICAAAwCCyALQRBBFCALKAIQIAhGG2ogADYCACAARQ0BCyAAIAs2AhgCQCAIKAIQIgNFDQAgACADNgIQIAMgADYCGAsgCEEUaigCACIDRQ0AIABBFGogAzYCACADIAA2AhgLAkACQCAEQQ9LDQAgCCAEIAJqIgNBA3I2AgQgCCADaiIDIAMoAgRBAXI2AgQMAQsgCCACaiIAIARBAXI2AgQgCCACQQNyNgIEIAAgBGogBDYCAAJAIARB/wFLDQAgBEF4cUGw0ICAAGohAwJAAkBBACgCiNCAgAAiBUEBIARBA3Z0IgRxDQBBACAFIARyNgKI0ICAACADIQQMAQsgAygCCCEECyAEIAA2AgwgAyAANgIIIAAgAzYCDCAAIAQ2AggMAQtBHyEDAkAgBEH///8HSw0AIARBCHYiAyADQYD+P2pBEHZBCHEiA3QiBSAFQYDgH2pBEHZBBHEiBXQiAiACQYCAD2pBEHZBAnEiAnRBD3YgAyAFciACcmsiA0EBdCAEIANBFWp2QQFxckEcaiEDCyAAIAM2AhwgAEIANwIQIANBAnRBuNKAgABqIQUCQCAHQQEgA3QiAnENACAFIAA2AgBBACAHIAJyNgKM0ICAACAAIAU2AhggACAANgIIIAAgADYCDAwBCyAEQQBBGSADQQF2ayADQR9GG3QhAyAFKAIAIQICQANAIAIiBSgCBEF4cSAERg0BIANBHXYhAiADQQF0IQMgBSACQQRxakEQaiIGKAIAIgINAAsgBiAANgIAIAAgBTYCGCAAIAA2AgwgACAANgIIDAELIAUoAggiAyAANgIMIAUgADYCCCAAQQA2AhggACAFNgIMIAAgAzYCCAsgCEEIaiEDDAELAkAgCkUNAAJAAkAgACAAKAIcIgVBAnRBuNKAgABqIgMoAgBHDQAgAyAINgIAIAgNAUEAIAlBfiAFd3E2AozQgIAADAILIApBEEEUIAooAhAgAEYbaiAINgIAIAhFDQELIAggCjYCGAJAIAAoAhAiA0UNACAIIAM2AhAgAyAINgIYCyAAQRRqKAIAIgNFDQAgCEEUaiADNgIAIAMgCDYCGAsCQAJAIARBD0sNACAAIAQgAmoiA0EDcjYCBCAAIANqIgMgAygCBEEBcjYCBAwBCyAAIAJqIgUgBEEBcjYCBCAAIAJBA3I2AgQgBSAEaiAENgIAAkAgB0UNACAHQXhxQbDQgIAAaiECQQAoApzQgIAAIQMCQAJAQQEgB0EDdnQiCCAGcQ0AQQAgCCAGcjYCiNCAgAAgAiEIDAELIAIoAgghCAsgCCADNgIMIAIgAzYCCCADIAI2AgwgAyAINgIIC0EAIAU2ApzQgIAAQQAgBDYCkNCAgAALIABBCGohAwsgAUEQaiSAgICAACADCwoAIAAQyYCAgAAL4g0BB38CQCAARQ0AIABBeGoiASAAQXxqKAIAIgJBeHEiAGohAwJAIAJBAXENACACQQNxRQ0BIAEgASgCACICayIBQQAoApjQgIAAIgRJDQEgAiAAaiEAAkAgAUEAKAKc0ICAAEYNAAJAIAJB/wFLDQAgASgCCCIEIAJBA3YiBUEDdEGw0ICAAGoiBkYaAkAgASgCDCICIARHDQBBAEEAKAKI0ICAAEF+IAV3cTYCiNCAgAAMAwsgAiAGRhogAiAENgIIIAQgAjYCDAwCCyABKAIYIQcCQAJAIAEoAgwiBiABRg0AIAEoAggiAiAESRogBiACNgIIIAIgBjYCDAwBCwJAIAFBFGoiAigCACIEDQAgAUEQaiICKAIAIgQNAEEAIQYMAQsDQCACIQUgBCIGQRRqIgIoAgAiBA0AIAZBEGohAiAGKAIQIgQNAAsgBUEANgIACyAHRQ0BAkACQCABIAEoAhwiBEECdEG40oCAAGoiAigCAEcNACACIAY2AgAgBg0BQQBBACgCjNCAgABBfiAEd3E2AozQgIAADAMLIAdBEEEUIAcoAhAgAUYbaiAGNgIAIAZFDQILIAYgBzYCGAJAIAEoAhAiAkUNACAGIAI2AhAgAiAGNgIYCyABKAIUIgJFDQEgBkEUaiACNgIAIAIgBjYCGAwBCyADKAIEIgJBA3FBA0cNACADIAJBfnE2AgRBACAANgKQ0ICAACABIABqIAA2AgAgASAAQQFyNgIEDwsgASADTw0AIAMoAgQiAkEBcUUNAAJAAkAgAkECcQ0AAkAgA0EAKAKg0ICAAEcNAEEAIAE2AqDQgIAAQQBBACgClNCAgAAgAGoiADYClNCAgAAgASAAQQFyNgIEIAFBACgCnNCAgABHDQNBAEEANgKQ0ICAAEEAQQA2ApzQgIAADwsCQCADQQAoApzQgIAARw0AQQAgATYCnNCAgABBAEEAKAKQ0ICAACAAaiIANgKQ0ICAACABIABBAXI2AgQgASAAaiAANgIADwsgAkF4cSAAaiEAAkACQCACQf8BSw0AIAMoAggiBCACQQN2IgVBA3RBsNCAgABqIgZGGgJAIAMoAgwiAiAERw0AQQBBACgCiNCAgABBfiAFd3E2AojQgIAADAILIAIgBkYaIAIgBDYCCCAEIAI2AgwMAQsgAygCGCEHAkACQCADKAIMIgYgA0YNACADKAIIIgJBACgCmNCAgABJGiAGIAI2AgggAiAGNgIMDAELAkAgA0EUaiICKAIAIgQNACADQRBqIgIoAgAiBA0AQQAhBgwBCwNAIAIhBSAEIgZBFGoiAigCACIEDQAgBkEQaiECIAYoAhAiBA0ACyAFQQA2AgALIAdFDQACQAJAIAMgAygCHCIEQQJ0QbjSgIAAaiICKAIARw0AIAIgBjYCACAGDQFBAEEAKAKM0ICAAEF+IAR3cTYCjNCAgAAMAgsgB0EQQRQgBygCECADRhtqIAY2AgAgBkUNAQsgBiAHNgIYAkAgAygCECICRQ0AIAYgAjYCECACIAY2AhgLIAMoAhQiAkUNACAGQRRqIAI2AgAgAiAGNgIYCyABIABqIAA2AgAgASAAQQFyNgIEIAFBACgCnNCAgABHDQFBACAANgKQ0ICAAA8LIAMgAkF+cTYCBCABIABqIAA2AgAgASAAQQFyNgIECwJAIABB/wFLDQAgAEF4cUGw0ICAAGohAgJAAkBBACgCiNCAgAAiBEEBIABBA3Z0IgBxDQBBACAEIAByNgKI0ICAACACIQAMAQsgAigCCCEACyAAIAE2AgwgAiABNgIIIAEgAjYCDCABIAA2AggPC0EfIQICQCAAQf///wdLDQAgAEEIdiICIAJBgP4/akEQdkEIcSICdCIEIARBgOAfakEQdkEEcSIEdCIGIAZBgIAPakEQdkECcSIGdEEPdiACIARyIAZyayICQQF0IAAgAkEVanZBAXFyQRxqIQILIAEgAjYCHCABQgA3AhAgAkECdEG40oCAAGohBAJAAkBBACgCjNCAgAAiBkEBIAJ0IgNxDQAgBCABNgIAQQAgBiADcjYCjNCAgAAgASAENgIYIAEgATYCCCABIAE2AgwMAQsgAEEAQRkgAkEBdmsgAkEfRht0IQIgBCgCACEGAkADQCAGIgQoAgRBeHEgAEYNASACQR12IQYgAkEBdCECIAQgBkEEcWpBEGoiAygCACIGDQALIAMgATYCACABIAQ2AhggASABNgIMIAEgATYCCAwBCyAEKAIIIgAgATYCDCAEIAE2AgggAUEANgIYIAEgBDYCDCABIAA2AggLQQBBACgCqNCAgABBf2oiAUF/IAEbNgKo0ICAAAsLBAAAAAtOAAJAIAANAD8AQRB0DwsCQCAAQf//A3ENACAAQX9MDQACQCAAQRB2QAAiAEF/Rw0AQQBBMDYC+NOAgABBfw8LIABBEHQPCxDKgICAAAAL8gICA38BfgJAIAJFDQAgACABOgAAIAIgAGoiA0F/aiABOgAAIAJBA0kNACAAIAE6AAIgACABOgABIANBfWogAToAACADQX5qIAE6AAAgAkEHSQ0AIAAgAToAAyADQXxqIAE6AAAgAkEJSQ0AIABBACAAa0EDcSIEaiIDIAFB/wFxQYGChAhsIgE2AgAgAyACIARrQXxxIgRqIgJBfGogATYCACAEQQlJDQAgAyABNgIIIAMgATYCBCACQXhqIAE2AgAgAkF0aiABNgIAIARBGUkNACADIAE2AhggAyABNgIUIAMgATYCECADIAE2AgwgAkFwaiABNgIAIAJBbGogATYCACACQWhqIAE2AgAgAkFkaiABNgIAIAQgA0EEcUEYciIFayICQSBJDQAgAa1CgYCAgBB+IQYgAyAFaiEBA0AgASAGNwMYIAEgBjcDECABIAY3AwggASAGNwMAIAFBIGohASACQWBqIgJBH0sNAAsLIAALC45IAQBBgAgLhkgBAAAAAgAAAAMAAAAAAAAAAAAAAAQAAAAFAAAAAAAAAAAAAAAGAAAABwAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEludmFsaWQgY2hhciBpbiB1cmwgcXVlcnkAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9ib2R5AENvbnRlbnQtTGVuZ3RoIG92ZXJmbG93AENodW5rIHNpemUgb3ZlcmZsb3cAUmVzcG9uc2Ugb3ZlcmZsb3cASW52YWxpZCBtZXRob2QgZm9yIEhUVFAveC54IHJlcXVlc3QASW52YWxpZCBtZXRob2QgZm9yIFJUU1AveC54IHJlcXVlc3QARXhwZWN0ZWQgU09VUkNFIG1ldGhvZCBmb3IgSUNFL3gueCByZXF1ZXN0AEludmFsaWQgY2hhciBpbiB1cmwgZnJhZ21lbnQgc3RhcnQARXhwZWN0ZWQgZG90AFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fc3RhdHVzAEludmFsaWQgcmVzcG9uc2Ugc3RhdHVzAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMAVXNlciBjYWxsYmFjayBlcnJvcgBgb25fcmVzZXRgIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19oZWFkZXJgIGNhbGxiYWNrIGVycm9yAGBvbl9tZXNzYWdlX2JlZ2luYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfZXh0ZW5zaW9uX3ZhbHVlYCBjYWxsYmFjayBlcnJvcgBgb25fc3RhdHVzX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fdmVyc2lvbl9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX3VybF9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25faGVhZGVyX3ZhbHVlX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fbWVzc2FnZV9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX21ldGhvZF9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX2hlYWRlcl9maWVsZF9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lYCBjYWxsYmFjayBlcnJvcgBVbmV4cGVjdGVkIGNoYXIgaW4gdXJsIHNlcnZlcgBJbnZhbGlkIGhlYWRlciB2YWx1ZSBjaGFyAEludmFsaWQgaGVhZGVyIGZpZWxkIGNoYXIAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl92ZXJzaW9uAEludmFsaWQgbWlub3IgdmVyc2lvbgBJbnZhbGlkIG1ham9yIHZlcnNpb24ARXhwZWN0ZWQgc3BhY2UgYWZ0ZXIgdmVyc2lvbgBFeHBlY3RlZCBDUkxGIGFmdGVyIHZlcnNpb24ASW52YWxpZCBIVFRQIHZlcnNpb24ASW52YWxpZCBoZWFkZXIgdG9rZW4AU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl91cmwASW52YWxpZCBjaGFyYWN0ZXJzIGluIHVybABVbmV4cGVjdGVkIHN0YXJ0IGNoYXIgaW4gdXJsAERvdWJsZSBAIGluIHVybABFbXB0eSBDb250ZW50LUxlbmd0aABJbnZhbGlkIGNoYXJhY3RlciBpbiBDb250ZW50LUxlbmd0aABEdXBsaWNhdGUgQ29udGVudC1MZW5ndGgASW52YWxpZCBjaGFyIGluIHVybCBwYXRoAENvbnRlbnQtTGVuZ3RoIGNhbid0IGJlIHByZXNlbnQgd2l0aCBUcmFuc2Zlci1FbmNvZGluZwBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBzaXplAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25faGVhZGVyX3ZhbHVlAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fY2h1bmtfZXh0ZW5zaW9uX3ZhbHVlAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgdmFsdWUATWlzc2luZyBleHBlY3RlZCBMRiBhZnRlciBoZWFkZXIgdmFsdWUASW52YWxpZCBgVHJhbnNmZXItRW5jb2RpbmdgIGhlYWRlciB2YWx1ZQBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zIHF1b3RlIHZhbHVlAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgcXVvdGVkIHZhbHVlAFBhdXNlZCBieSBvbl9oZWFkZXJzX2NvbXBsZXRlAEludmFsaWQgRU9GIHN0YXRlAG9uX3Jlc2V0IHBhdXNlAG9uX2NodW5rX2hlYWRlciBwYXVzZQBvbl9tZXNzYWdlX2JlZ2luIHBhdXNlAG9uX2NodW5rX2V4dGVuc2lvbl92YWx1ZSBwYXVzZQBvbl9zdGF0dXNfY29tcGxldGUgcGF1c2UAb25fdmVyc2lvbl9jb21wbGV0ZSBwYXVzZQBvbl91cmxfY29tcGxldGUgcGF1c2UAb25fY2h1bmtfY29tcGxldGUgcGF1c2UAb25faGVhZGVyX3ZhbHVlX2NvbXBsZXRlIHBhdXNlAG9uX21lc3NhZ2VfY29tcGxldGUgcGF1c2UAb25fbWV0aG9kX2NvbXBsZXRlIHBhdXNlAG9uX2hlYWRlcl9maWVsZF9jb21wbGV0ZSBwYXVzZQBvbl9jaHVua19leHRlbnNpb25fbmFtZSBwYXVzZQBVbmV4cGVjdGVkIHNwYWNlIGFmdGVyIHN0YXJ0IGxpbmUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9jaHVua19leHRlbnNpb25fbmFtZQBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zIG5hbWUAUGF1c2Ugb24gQ09OTkVDVC9VcGdyYWRlAFBhdXNlIG9uIFBSSS9VcGdyYWRlAEV4cGVjdGVkIEhUVFAvMiBDb25uZWN0aW9uIFByZWZhY2UAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9tZXRob2QARXhwZWN0ZWQgc3BhY2UgYWZ0ZXIgbWV0aG9kAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25faGVhZGVyX2ZpZWxkAFBhdXNlZABJbnZhbGlkIHdvcmQgZW5jb3VudGVyZWQASW52YWxpZCBtZXRob2QgZW5jb3VudGVyZWQAVW5leHBlY3RlZCBjaGFyIGluIHVybCBzY2hlbWEAUmVxdWVzdCBoYXMgaW52YWxpZCBgVHJhbnNmZXItRW5jb2RpbmdgAFNXSVRDSF9QUk9YWQBVU0VfUFJPWFkATUtBQ1RJVklUWQBVTlBST0NFU1NBQkxFX0VOVElUWQBDT1BZAE1PVkVEX1BFUk1BTkVOVExZAFRPT19FQVJMWQBOT1RJRlkARkFJTEVEX0RFUEVOREVOQ1kAQkFEX0dBVEVXQVkAUExBWQBQVVQAQ0hFQ0tPVVQAR0FURVdBWV9USU1FT1VUAFJFUVVFU1RfVElNRU9VVABORVRXT1JLX0NPTk5FQ1RfVElNRU9VVABDT05ORUNUSU9OX1RJTUVPVVQATE9HSU5fVElNRU9VVABORVRXT1JLX1JFQURfVElNRU9VVABQT1NUAE1JU0RJUkVDVEVEX1JFUVVFU1QAQ0xJRU5UX0NMT1NFRF9SRVFVRVNUAENMSUVOVF9DTE9TRURfTE9BRF9CQUxBTkNFRF9SRVFVRVNUAEJBRF9SRVFVRVNUAEhUVFBfUkVRVUVTVF9TRU5UX1RPX0hUVFBTX1BPUlQAUkVQT1JUAElNX0FfVEVBUE9UAFJFU0VUX0NPTlRFTlQATk9fQ09OVEVOVABQQVJUSUFMX0NPTlRFTlQASFBFX0lOVkFMSURfQ09OU1RBTlQASFBFX0NCX1JFU0VUAEdFVABIUEVfU1RSSUNUAENPTkZMSUNUAFRFTVBPUkFSWV9SRURJUkVDVABQRVJNQU5FTlRfUkVESVJFQ1QAQ09OTkVDVABNVUxUSV9TVEFUVVMASFBFX0lOVkFMSURfU1RBVFVTAFRPT19NQU5ZX1JFUVVFU1RTAEVBUkxZX0hJTlRTAFVOQVZBSUxBQkxFX0ZPUl9MRUdBTF9SRUFTT05TAE9QVElPTlMAU1dJVENISU5HX1BST1RPQ09MUwBWQVJJQU5UX0FMU09fTkVHT1RJQVRFUwBNVUxUSVBMRV9DSE9JQ0VTAElOVEVSTkFMX1NFUlZFUl9FUlJPUgBXRUJfU0VSVkVSX1VOS05PV05fRVJST1IAUkFJTEdVTl9FUlJPUgBJREVOVElUWV9QUk9WSURFUl9BVVRIRU5USUNBVElPTl9FUlJPUgBTU0xfQ0VSVElGSUNBVEVfRVJST1IASU5WQUxJRF9YX0ZPUldBUkRFRF9GT1IAU0VUX1BBUkFNRVRFUgBHRVRfUEFSQU1FVEVSAEhQRV9VU0VSAFNFRV9PVEhFUgBIUEVfQ0JfQ0hVTktfSEVBREVSAE1LQ0FMRU5EQVIAU0VUVVAAV0VCX1NFUlZFUl9JU19ET1dOAFRFQVJET1dOAEhQRV9DTE9TRURfQ09OTkVDVElPTgBIRVVSSVNUSUNfRVhQSVJBVElPTgBESVNDT05ORUNURURfT1BFUkFUSU9OAE5PTl9BVVRIT1JJVEFUSVZFX0lORk9STUFUSU9OAEhQRV9JTlZBTElEX1ZFUlNJT04ASFBFX0NCX01FU1NBR0VfQkVHSU4AU0lURV9JU19GUk9aRU4ASFBFX0lOVkFMSURfSEVBREVSX1RPS0VOAElOVkFMSURfVE9LRU4ARk9SQklEREVOAEVOSEFOQ0VfWU9VUl9DQUxNAEhQRV9JTlZBTElEX1VSTABCTE9DS0VEX0JZX1BBUkVOVEFMX0NPTlRST0wATUtDT0wAQUNMAEhQRV9JTlRFUk5BTABSRVFVRVNUX0hFQURFUl9GSUVMRFNfVE9PX0xBUkdFX1VOT0ZGSUNJQUwASFBFX09LAFVOTElOSwBVTkxPQ0sAUFJJAFJFVFJZX1dJVEgASFBFX0lOVkFMSURfQ09OVEVOVF9MRU5HVEgASFBFX1VORVhQRUNURURfQ09OVEVOVF9MRU5HVEgARkxVU0gAUFJPUFBBVENIAE0tU0VBUkNIAFVSSV9UT09fTE9ORwBQUk9DRVNTSU5HAE1JU0NFTExBTkVPVVNfUEVSU0lTVEVOVF9XQVJOSU5HAE1JU0NFTExBTkVPVVNfV0FSTklORwBIUEVfSU5WQUxJRF9UUkFOU0ZFUl9FTkNPRElORwBFeHBlY3RlZCBDUkxGAEhQRV9JTlZBTElEX0NIVU5LX1NJWkUATU9WRQBDT05USU5VRQBIUEVfQ0JfU1RBVFVTX0NPTVBMRVRFAEhQRV9DQl9IRUFERVJTX0NPTVBMRVRFAEhQRV9DQl9WRVJTSU9OX0NPTVBMRVRFAEhQRV9DQl9VUkxfQ09NUExFVEUASFBFX0NCX0NIVU5LX0NPTVBMRVRFAEhQRV9DQl9IRUFERVJfVkFMVUVfQ09NUExFVEUASFBFX0NCX0NIVU5LX0VYVEVOU0lPTl9WQUxVRV9DT01QTEVURQBIUEVfQ0JfQ0hVTktfRVhURU5TSU9OX05BTUVfQ09NUExFVEUASFBFX0NCX01FU1NBR0VfQ09NUExFVEUASFBFX0NCX01FVEhPRF9DT01QTEVURQBIUEVfQ0JfSEVBREVSX0ZJRUxEX0NPTVBMRVRFAERFTEVURQBIUEVfSU5WQUxJRF9FT0ZfU1RBVEUASU5WQUxJRF9TU0xfQ0VSVElGSUNBVEUAUEFVU0UATk9fUkVTUE9OU0UAVU5TVVBQT1JURURfTUVESUFfVFlQRQBHT05FAE5PVF9BQ0NFUFRBQkxFAFNFUlZJQ0VfVU5BVkFJTEFCTEUAUkFOR0VfTk9UX1NBVElTRklBQkxFAE9SSUdJTl9JU19VTlJFQUNIQUJMRQBSRVNQT05TRV9JU19TVEFMRQBQVVJHRQBNRVJHRQBSRVFVRVNUX0hFQURFUl9GSUVMRFNfVE9PX0xBUkdFAFJFUVVFU1RfSEVBREVSX1RPT19MQVJHRQBQQVlMT0FEX1RPT19MQVJHRQBJTlNVRkZJQ0lFTlRfU1RPUkFHRQBIUEVfUEFVU0VEX1VQR1JBREUASFBFX1BBVVNFRF9IMl9VUEdSQURFAFNPVVJDRQBBTk5PVU5DRQBUUkFDRQBIUEVfVU5FWFBFQ1RFRF9TUEFDRQBERVNDUklCRQBVTlNVQlNDUklCRQBSRUNPUkQASFBFX0lOVkFMSURfTUVUSE9EAE5PVF9GT1VORABQUk9QRklORABVTkJJTkQAUkVCSU5EAFVOQVVUSE9SSVpFRABNRVRIT0RfTk9UX0FMTE9XRUQASFRUUF9WRVJTSU9OX05PVF9TVVBQT1JURUQAQUxSRUFEWV9SRVBPUlRFRABBQ0NFUFRFRABOT1RfSU1QTEVNRU5URUQATE9PUF9ERVRFQ1RFRABIUEVfQ1JfRVhQRUNURUQASFBFX0xGX0VYUEVDVEVEAENSRUFURUQASU1fVVNFRABIUEVfUEFVU0VEAFRJTUVPVVRfT0NDVVJFRABQQVlNRU5UX1JFUVVJUkVEAFBSRUNPTkRJVElPTl9SRVFVSVJFRABQUk9YWV9BVVRIRU5USUNBVElPTl9SRVFVSVJFRABORVRXT1JLX0FVVEhFTlRJQ0FUSU9OX1JFUVVJUkVEAExFTkdUSF9SRVFVSVJFRABTU0xfQ0VSVElGSUNBVEVfUkVRVUlSRUQAVVBHUkFERV9SRVFVSVJFRABQQUdFX0VYUElSRUQAUFJFQ09ORElUSU9OX0ZBSUxFRABFWFBFQ1RBVElPTl9GQUlMRUQAUkVWQUxJREFUSU9OX0ZBSUxFRABTU0xfSEFORFNIQUtFX0ZBSUxFRABMT0NLRUQAVFJBTlNGT1JNQVRJT05fQVBQTElFRABOT1RfTU9ESUZJRUQATk9UX0VYVEVOREVEAEJBTkRXSURUSF9MSU1JVF9FWENFRURFRABTSVRFX0lTX09WRVJMT0FERUQASEVBRABFeHBlY3RlZCBIVFRQLwAAXhMAACYTAAAwEAAA8BcAAJ0TAAAVEgAAORcAAPASAAAKEAAAdRIAAK0SAACCEwAATxQAAH8QAACgFQAAIxQAAIkSAACLFAAATRUAANQRAADPFAAAEBgAAMkWAADcFgAAwREAAOAXAAC7FAAAdBQAAHwVAADlFAAACBcAAB8QAABlFQAAoxQAACgVAAACFQAAmRUAACwQAACLGQAATw8AANQOAABqEAAAzhAAAAIXAACJDgAAbhMAABwTAABmFAAAVhcAAMETAADNEwAAbBMAAGgXAABmFwAAXxcAACITAADODwAAaQ4AANgOAABjFgAAyxMAAKoOAAAoFwAAJhcAAMUTAABdFgAA6BEAAGcTAABlEwAA8hYAAHMTAAAdFwAA+RYAAPMRAADPDgAAzhUAAAwSAACzEQAApREAAGEQAAAyFwAAuxMAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIDAgICAgIAAAICAAICAAICAgICAgICAgIABAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgACAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAICAgICAAACAgACAgACAgICAgICAgICAAMABAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAgACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbG9zZWVlcC1hbGl2ZQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBY2h1bmtlZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAQEBAQEAAAEBAAEBAAEBAQEBAQEBAQEAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABlY3Rpb25lbnQtbGVuZ3Rob25yb3h5LWNvbm5lY3Rpb24AAAAAAAAAAAAAAAAAAAByYW5zZmVyLWVuY29kaW5ncGdyYWRlDQoNCg0KU00NCg0KVFRQL0NFL1RTUC8AAAAAAAAAAAAAAAABAgABAwAAAAAAAAAAAAAAAAAAAAAAAAQBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAQIAAQMAAAAAAAAAAAAAAAAAAAAAAAAEAQEFAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAEAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAQAAAgAAAAAAAAAAAAAAAAAAAAAAAAMEAAAEBAQEBAQEBAQEBAUEBAQEBAQEBAQEBAQABAAGBwQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEAAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAIAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABOT1VOQ0VFQ0tPVVRORUNURVRFQ1JJQkVMVVNIRVRFQURTRUFSQ0hSR0VDVElWSVRZTEVOREFSVkVPVElGWVBUSU9OU0NIU0VBWVNUQVRDSEdFT1JESVJFQ1RPUlRSQ0hQQVJBTUVURVJVUkNFQlNDUklCRUFSRE9XTkFDRUlORE5LQ0tVQlNDUklCRUhUVFAvQURUUC8=' + + +/***/ }), + +/***/ 3434: +/***/ ((module) => { + +module.exports = 'AGFzbQEAAAABMAhgAX8Bf2ADf39/AX9gBH9/f38Bf2AAAGADf39/AGABfwBgAn9/AGAGf39/f39/AALLAQgDZW52GHdhc21fb25faGVhZGVyc19jb21wbGV0ZQACA2VudhV3YXNtX29uX21lc3NhZ2VfYmVnaW4AAANlbnYLd2FzbV9vbl91cmwAAQNlbnYOd2FzbV9vbl9zdGF0dXMAAQNlbnYUd2FzbV9vbl9oZWFkZXJfZmllbGQAAQNlbnYUd2FzbV9vbl9oZWFkZXJfdmFsdWUAAQNlbnYMd2FzbV9vbl9ib2R5AAEDZW52GHdhc21fb25fbWVzc2FnZV9jb21wbGV0ZQAAA0ZFAwMEAAAFAAAAAAAABQEFAAUFBQAABgAAAAAGBgYGAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAAABAQcAAAUFAwABBAUBcAESEgUDAQACBggBfwFBgNQECwfRBSIGbWVtb3J5AgALX2luaXRpYWxpemUACRlfX2luZGlyZWN0X2Z1bmN0aW9uX3RhYmxlAQALbGxodHRwX2luaXQAChhsbGh0dHBfc2hvdWxkX2tlZXBfYWxpdmUAQQxsbGh0dHBfYWxsb2MADAZtYWxsb2MARgtsbGh0dHBfZnJlZQANBGZyZWUASA9sbGh0dHBfZ2V0X3R5cGUADhVsbGh0dHBfZ2V0X2h0dHBfbWFqb3IADxVsbGh0dHBfZ2V0X2h0dHBfbWlub3IAEBFsbGh0dHBfZ2V0X21ldGhvZAARFmxsaHR0cF9nZXRfc3RhdHVzX2NvZGUAEhJsbGh0dHBfZ2V0X3VwZ3JhZGUAEwxsbGh0dHBfcmVzZXQAFA5sbGh0dHBfZXhlY3V0ZQAVFGxsaHR0cF9zZXR0aW5nc19pbml0ABYNbGxodHRwX2ZpbmlzaAAXDGxsaHR0cF9wYXVzZQAYDWxsaHR0cF9yZXN1bWUAGRtsbGh0dHBfcmVzdW1lX2FmdGVyX3VwZ3JhZGUAGhBsbGh0dHBfZ2V0X2Vycm5vABsXbGxodHRwX2dldF9lcnJvcl9yZWFzb24AHBdsbGh0dHBfc2V0X2Vycm9yX3JlYXNvbgAdFGxsaHR0cF9nZXRfZXJyb3JfcG9zAB4RbGxodHRwX2Vycm5vX25hbWUAHxJsbGh0dHBfbWV0aG9kX25hbWUAIBJsbGh0dHBfc3RhdHVzX25hbWUAIRpsbGh0dHBfc2V0X2xlbmllbnRfaGVhZGVycwAiIWxsaHR0cF9zZXRfbGVuaWVudF9jaHVua2VkX2xlbmd0aAAjHWxsaHR0cF9zZXRfbGVuaWVudF9rZWVwX2FsaXZlACQkbGxodHRwX3NldF9sZW5pZW50X3RyYW5zZmVyX2VuY29kaW5nACUYbGxodHRwX21lc3NhZ2VfbmVlZHNfZW9mAD8JFwEAQQELEQECAwQFCwYHNTk3MS8tJyspCrLgAkUCAAsIABCIgICAAAsZACAAEMKAgIAAGiAAIAI2AjggACABOgAoCxwAIAAgAC8BMiAALQAuIAAQwYCAgAAQgICAgAALKgEBf0HAABDGgICAACIBEMKAgIAAGiABQYCIgIAANgI4IAEgADoAKCABCwoAIAAQyICAgAALBwAgAC0AKAsHACAALQAqCwcAIAAtACsLBwAgAC0AKQsHACAALwEyCwcAIAAtAC4LRQEEfyAAKAIYIQEgAC0ALSECIAAtACghAyAAKAI4IQQgABDCgICAABogACAENgI4IAAgAzoAKCAAIAI6AC0gACABNgIYCxEAIAAgASABIAJqEMOAgIAACxAAIABBAEHcABDMgICAABoLZwEBf0EAIQECQCAAKAIMDQACQAJAAkACQCAALQAvDgMBAAMCCyAAKAI4IgFFDQAgASgCLCIBRQ0AIAAgARGAgICAAAAiAQ0DC0EADwsQyoCAgAAACyAAQcOWgIAANgIQQQ4hAQsgAQseAAJAIAAoAgwNACAAQdGbgIAANgIQIABBFTYCDAsLFgACQCAAKAIMQRVHDQAgAEEANgIMCwsWAAJAIAAoAgxBFkcNACAAQQA2AgwLCwcAIAAoAgwLBwAgACgCEAsJACAAIAE2AhALBwAgACgCFAsiAAJAIABBJEkNABDKgICAAAALIABBAnRBoLOAgABqKAIACyIAAkAgAEEuSQ0AEMqAgIAAAAsgAEECdEGwtICAAGooAgAL7gsBAX9B66iAgAAhAQJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIABBnH9qDvQDY2IAAWFhYWFhYQIDBAVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhBgcICQoLDA0OD2FhYWFhEGFhYWFhYWFhYWFhEWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYRITFBUWFxgZGhthYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2YTc4OTphYWFhYWFhYTthYWE8YWFhYT0+P2FhYWFhYWFhQGFhQWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYUJDREVGR0hJSktMTU5PUFFSU2FhYWFhYWFhVFVWV1hZWlthXF1hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFeYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhX2BhC0Hhp4CAAA8LQaShgIAADwtBy6yAgAAPC0H+sYCAAA8LQcCkgIAADwtBq6SAgAAPC0GNqICAAA8LQeKmgIAADwtBgLCAgAAPC0G5r4CAAA8LQdekgIAADwtB75+AgAAPC0Hhn4CAAA8LQfqfgIAADwtB8qCAgAAPC0Gor4CAAA8LQa6ygIAADwtBiLCAgAAPC0Hsp4CAAA8LQYKigIAADwtBjp2AgAAPC0HQroCAAA8LQcqjgIAADwtBxbKAgAAPC0HfnICAAA8LQdKcgIAADwtBxKCAgAAPC0HXoICAAA8LQaKfgIAADwtB7a6AgAAPC0GrsICAAA8LQdSlgIAADwtBzK6AgAAPC0H6roCAAA8LQfyrgIAADwtB0rCAgAAPC0HxnYCAAA8LQbuggIAADwtB96uAgAAPC0GQsYCAAA8LQdexgIAADwtBoq2AgAAPC0HUp4CAAA8LQeCrgIAADwtBn6yAgAAPC0HrsYCAAA8LQdWfgIAADwtByrGAgAAPC0HepYCAAA8LQdSegIAADwtB9JyAgAAPC0GnsoCAAA8LQbGdgIAADwtBoJ2AgAAPC0G5sYCAAA8LQbywgIAADwtBkqGAgAAPC0GzpoCAAA8LQemsgIAADwtBrJ6AgAAPC0HUq4CAAA8LQfemgIAADwtBgKaAgAAPC0GwoYCAAA8LQf6egIAADwtBjaOAgAAPC0GJrYCAAA8LQfeigIAADwtBoLGAgAAPC0Gun4CAAA8LQcalgIAADwtB6J6AgAAPC0GTooCAAA8LQcKvgIAADwtBw52AgAAPC0GLrICAAA8LQeGdgIAADwtBja+AgAAPC0HqoYCAAA8LQbStgIAADwtB0q+AgAAPC0HfsoCAAA8LQdKygIAADwtB8LCAgAAPC0GpooCAAA8LQfmjgIAADwtBmZ6AgAAPC0G1rICAAA8LQZuwgIAADwtBkrKAgAAPC0G2q4CAAA8LQcKigIAADwtB+LKAgAAPC0GepYCAAA8LQdCigIAADwtBup6AgAAPC0GBnoCAAA8LEMqAgIAAAAtB1qGAgAAhAQsgAQsWACAAIAAtAC1B/gFxIAFBAEdyOgAtCxkAIAAgAC0ALUH9AXEgAUEAR0EBdHI6AC0LGQAgACAALQAtQfsBcSABQQBHQQJ0cjoALQsZACAAIAAtAC1B9wFxIAFBAEdBA3RyOgAtCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAgAiBEUNACAAIAQRgICAgAAAIQMLIAMLSQECf0EAIQMCQCAAKAI4IgRFDQAgBCgCBCIERQ0AIAAgASACIAFrIAQRgYCAgAAAIgNBf0cNACAAQcaRgIAANgIQQRghAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIwIgRFDQAgACAEEYCAgIAAACEDCyADC0kBAn9BACEDAkAgACgCOCIERQ0AIAQoAggiBEUNACAAIAEgAiABayAEEYGAgIAAACIDQX9HDQAgAEH2ioCAADYCEEEYIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCNCIERQ0AIAAgBBGAgICAAAAhAwsgAwtJAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIMIgRFDQAgACABIAIgAWsgBBGBgICAAAAiA0F/Rw0AIABB7ZqAgAA2AhBBGCEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAjgiBEUNACAAIAQRgICAgAAAIQMLIAMLSQECf0EAIQMCQCAAKAI4IgRFDQAgBCgCECIERQ0AIAAgASACIAFrIAQRgYCAgAAAIgNBf0cNACAAQZWQgIAANgIQQRghAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAI8IgRFDQAgACAEEYCAgIAAACEDCyADC0kBAn9BACEDAkAgACgCOCIERQ0AIAQoAhQiBEUNACAAIAEgAiABayAEEYGAgIAAACIDQX9HDQAgAEGqm4CAADYCEEEYIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCQCIERQ0AIAAgBBGAgICAAAAhAwsgAwtJAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIYIgRFDQAgACABIAIgAWsgBBGBgICAAAAiA0F/Rw0AIABB7ZOAgAA2AhBBGCEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAkQiBEUNACAAIAQRgICAgAAAIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCJCIERQ0AIAAgBBGAgICAAAAhAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIsIgRFDQAgACAEEYCAgIAAACEDCyADC0kBAn9BACEDAkAgACgCOCIERQ0AIAQoAigiBEUNACAAIAEgAiABayAEEYGAgIAAACIDQX9HDQAgAEH2iICAADYCEEEYIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCUCIERQ0AIAAgBBGAgICAAAAhAwsgAwtJAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAIcIgRFDQAgACABIAIgAWsgBBGBgICAAAAiA0F/Rw0AIABBwpmAgAA2AhBBGCEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAkgiBEUNACAAIAQRgICAgAAAIQMLIAMLSQECf0EAIQMCQCAAKAI4IgRFDQAgBCgCICIERQ0AIAAgASACIAFrIAQRgYCAgAAAIgNBf0cNACAAQZSUgIAANgIQQRghAwsgAwsuAQJ/QQAhAwJAIAAoAjgiBEUNACAEKAJMIgRFDQAgACAEEYCAgIAAACEDCyADCy4BAn9BACEDAkAgACgCOCIERQ0AIAQoAlQiBEUNACAAIAQRgICAgAAAIQMLIAMLLgECf0EAIQMCQCAAKAI4IgRFDQAgBCgCWCIERQ0AIAAgBBGAgICAAAAhAwsgAwtFAQF/AkACQCAALwEwQRRxQRRHDQBBASEDIAAtAChBAUYNASAALwEyQeUARiEDDAELIAAtAClBBUYhAwsgACADOgAuQQAL/gEBA39BASEDAkAgAC8BMCIEQQhxDQAgACkDIEIAUiEDCwJAAkAgAC0ALkUNAEEBIQUgAC0AKUEFRg0BQQEhBSAEQcAAcUUgA3FBAUcNAQtBACEFIARBwABxDQBBAiEFIARB//8DcSIDQQhxDQACQCADQYAEcUUNAAJAIAAtAChBAUcNACAALQAtQQpxDQBBBQ8LQQQPCwJAIANBIHENAAJAIAAtAChBAUYNACAALwEyQf//A3EiAEGcf2pB5ABJDQAgAEHMAUYNACAAQbACRg0AQQQhBSAEQShxRQ0CIANBiARxQYAERg0CC0EADwtBAEEDIAApAyBQGyEFCyAFC2IBAn9BACEBAkAgAC0AKEEBRg0AIAAvATJB//8DcSICQZx/akHkAEkNACACQcwBRg0AIAJBsAJGDQAgAC8BMCIAQcAAcQ0AQQEhASAAQYgEcUGABEYNACAAQShxRSEBCyABC6cBAQN/AkACQAJAIAAtACpFDQAgAC0AK0UNAEEAIQMgAC8BMCIEQQJxRQ0BDAILQQAhAyAALwEwIgRBAXFFDQELQQEhAyAALQAoQQFGDQAgAC8BMkH//wNxIgVBnH9qQeQASQ0AIAVBzAFGDQAgBUGwAkYNACAEQcAAcQ0AQQAhAyAEQYgEcUGABEYNACAEQShxQQBHIQMLIABBADsBMCAAQQA6AC8gAwuZAQECfwJAAkACQCAALQAqRQ0AIAAtACtFDQBBACEBIAAvATAiAkECcUUNAQwCC0EAIQEgAC8BMCICQQFxRQ0BC0EBIQEgAC0AKEEBRg0AIAAvATJB//8DcSIAQZx/akHkAEkNACAAQcwBRg0AIABBsAJGDQAgAkHAAHENAEEAIQEgAkGIBHFBgARGDQAgAkEocUEARyEBCyABC0kBAXsgAEEQav0MAAAAAAAAAAAAAAAAAAAAACIB/QsDACAAIAH9CwMAIABBMGogAf0LAwAgAEEgaiAB/QsDACAAQd0BNgIcQQALewEBfwJAIAAoAgwiAw0AAkAgACgCBEUNACAAIAE2AgQLAkAgACABIAIQxICAgAAiAw0AIAAoAgwPCyAAIAM2AhxBACEDIAAoAgQiAUUNACAAIAEgAiAAKAIIEYGAgIAAACIBRQ0AIAAgAjYCFCAAIAE2AgwgASEDCyADC+TzAQMOfwN+BH8jgICAgABBEGsiAySAgICAACABIQQgASEFIAEhBiABIQcgASEIIAEhCSABIQogASELIAEhDCABIQ0gASEOIAEhDwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAAKAIcIhBBf2oO3QHaAQHZAQIDBAUGBwgJCgsMDQ7YAQ8Q1wEREtYBExQVFhcYGRob4AHfARwdHtUBHyAhIiMkJdQBJicoKSorLNMB0gEtLtEB0AEvMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUbbAUdISUrPAc4BS80BTMwBTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ent8fX5/gAGBAYIBgwGEAYUBhgGHAYgBiQGKAYsBjAGNAY4BjwGQAZEBkgGTAZQBlQGWAZcBmAGZAZoBmwGcAZ0BngGfAaABoQGiAaMBpAGlAaYBpwGoAakBqgGrAawBrQGuAa8BsAGxAbIBswG0AbUBtgG3AcsBygG4AckBuQHIAboBuwG8Ab0BvgG/AcABwQHCAcMBxAHFAcYBANwBC0EAIRAMxgELQQ4hEAzFAQtBDSEQDMQBC0EPIRAMwwELQRAhEAzCAQtBEyEQDMEBC0EUIRAMwAELQRUhEAy/AQtBFiEQDL4BC0EXIRAMvQELQRghEAy8AQtBGSEQDLsBC0EaIRAMugELQRshEAy5AQtBHCEQDLgBC0EIIRAMtwELQR0hEAy2AQtBICEQDLUBC0EfIRAMtAELQQchEAyzAQtBISEQDLIBC0EiIRAMsQELQR4hEAywAQtBIyEQDK8BC0ESIRAMrgELQREhEAytAQtBJCEQDKwBC0ElIRAMqwELQSYhEAyqAQtBJyEQDKkBC0HDASEQDKgBC0EpIRAMpwELQSshEAymAQtBLCEQDKUBC0EtIRAMpAELQS4hEAyjAQtBLyEQDKIBC0HEASEQDKEBC0EwIRAMoAELQTQhEAyfAQtBDCEQDJ4BC0ExIRAMnQELQTIhEAycAQtBMyEQDJsBC0E5IRAMmgELQTUhEAyZAQtBxQEhEAyYAQtBCyEQDJcBC0E6IRAMlgELQTYhEAyVAQtBCiEQDJQBC0E3IRAMkwELQTghEAySAQtBPCEQDJEBC0E7IRAMkAELQT0hEAyPAQtBCSEQDI4BC0EoIRAMjQELQT4hEAyMAQtBPyEQDIsBC0HAACEQDIoBC0HBACEQDIkBC0HCACEQDIgBC0HDACEQDIcBC0HEACEQDIYBC0HFACEQDIUBC0HGACEQDIQBC0EqIRAMgwELQccAIRAMggELQcgAIRAMgQELQckAIRAMgAELQcoAIRAMfwtBywAhEAx+C0HNACEQDH0LQcwAIRAMfAtBzgAhEAx7C0HPACEQDHoLQdAAIRAMeQtB0QAhEAx4C0HSACEQDHcLQdMAIRAMdgtB1AAhEAx1C0HWACEQDHQLQdUAIRAMcwtBBiEQDHILQdcAIRAMcQtBBSEQDHALQdgAIRAMbwtBBCEQDG4LQdkAIRAMbQtB2gAhEAxsC0HbACEQDGsLQdwAIRAMagtBAyEQDGkLQd0AIRAMaAtB3gAhEAxnC0HfACEQDGYLQeEAIRAMZQtB4AAhEAxkC0HiACEQDGMLQeMAIRAMYgtBAiEQDGELQeQAIRAMYAtB5QAhEAxfC0HmACEQDF4LQecAIRAMXQtB6AAhEAxcC0HpACEQDFsLQeoAIRAMWgtB6wAhEAxZC0HsACEQDFgLQe0AIRAMVwtB7gAhEAxWC0HvACEQDFULQfAAIRAMVAtB8QAhEAxTC0HyACEQDFILQfMAIRAMUQtB9AAhEAxQC0H1ACEQDE8LQfYAIRAMTgtB9wAhEAxNC0H4ACEQDEwLQfkAIRAMSwtB+gAhEAxKC0H7ACEQDEkLQfwAIRAMSAtB/QAhEAxHC0H+ACEQDEYLQf8AIRAMRQtBgAEhEAxEC0GBASEQDEMLQYIBIRAMQgtBgwEhEAxBC0GEASEQDEALQYUBIRAMPwtBhgEhEAw+C0GHASEQDD0LQYgBIRAMPAtBiQEhEAw7C0GKASEQDDoLQYsBIRAMOQtBjAEhEAw4C0GNASEQDDcLQY4BIRAMNgtBjwEhEAw1C0GQASEQDDQLQZEBIRAMMwtBkgEhEAwyC0GTASEQDDELQZQBIRAMMAtBlQEhEAwvC0GWASEQDC4LQZcBIRAMLQtBmAEhEAwsC0GZASEQDCsLQZoBIRAMKgtBmwEhEAwpC0GcASEQDCgLQZ0BIRAMJwtBngEhEAwmC0GfASEQDCULQaABIRAMJAtBoQEhEAwjC0GiASEQDCILQaMBIRAMIQtBpAEhEAwgC0GlASEQDB8LQaYBIRAMHgtBpwEhEAwdC0GoASEQDBwLQakBIRAMGwtBqgEhEAwaC0GrASEQDBkLQawBIRAMGAtBrQEhEAwXC0GuASEQDBYLQQEhEAwVC0GvASEQDBQLQbABIRAMEwtBsQEhEAwSC0GzASEQDBELQbIBIRAMEAtBtAEhEAwPC0G1ASEQDA4LQbYBIRAMDQtBtwEhEAwMC0G4ASEQDAsLQbkBIRAMCgtBugEhEAwJC0G7ASEQDAgLQcYBIRAMBwtBvAEhEAwGC0G9ASEQDAULQb4BIRAMBAtBvwEhEAwDC0HAASEQDAILQcIBIRAMAQtBwQEhEAsDQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIBAOxwEAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB4fICEjJSg/QEFERUZHSElKS0xNT1BRUlPeA1dZW1xdYGJlZmdoaWprbG1vcHFyc3R1dnd4eXp7fH1+gAGCAYUBhgGHAYkBiwGMAY0BjgGPAZABkQGUAZUBlgGXAZgBmQGaAZsBnAGdAZ4BnwGgAaEBogGjAaQBpQGmAacBqAGpAaoBqwGsAa0BrgGvAbABsQGyAbMBtAG1AbYBtwG4AbkBugG7AbwBvQG+Ab8BwAHBAcIBwwHEAcUBxgHHAcgByQHKAcsBzAHNAc4BzwHQAdEB0gHTAdQB1QHWAdcB2AHZAdoB2wHcAd0B3gHgAeEB4gHjAeQB5QHmAecB6AHpAeoB6wHsAe0B7gHvAfAB8QHyAfMBmQKkArAC/gL+AgsgASIEIAJHDfMBQd0BIRAM/wMLIAEiECACRw3dAUHDASEQDP4DCyABIgEgAkcNkAFB9wAhEAz9AwsgASIBIAJHDYYBQe8AIRAM/AMLIAEiASACRw1/QeoAIRAM+wMLIAEiASACRw17QegAIRAM+gMLIAEiASACRw14QeYAIRAM+QMLIAEiASACRw0aQRghEAz4AwsgASIBIAJHDRRBEiEQDPcDCyABIgEgAkcNWUHFACEQDPYDCyABIgEgAkcNSkE/IRAM9QMLIAEiASACRw1IQTwhEAz0AwsgASIBIAJHDUFBMSEQDPMDCyAALQAuQQFGDesDDIcCCyAAIAEiASACEMCAgIAAQQFHDeYBIABCADcDIAznAQsgACABIgEgAhC0gICAACIQDecBIAEhAQz1AgsCQCABIgEgAkcNAEEGIRAM8AMLIAAgAUEBaiIBIAIQu4CAgAAiEA3oASABIQEMMQsgAEIANwMgQRIhEAzVAwsgASIQIAJHDStBHSEQDO0DCwJAIAEiASACRg0AIAFBAWohAUEQIRAM1AMLQQchEAzsAwsgAEIAIAApAyAiESACIAEiEGutIhJ9IhMgEyARVhs3AyAgESASViIURQ3lAUEIIRAM6wMLAkAgASIBIAJGDQAgAEGJgICAADYCCCAAIAE2AgQgASEBQRQhEAzSAwtBCSEQDOoDCyABIQEgACkDIFAN5AEgASEBDPICCwJAIAEiASACRw0AQQshEAzpAwsgACABQQFqIgEgAhC2gICAACIQDeUBIAEhAQzyAgsgACABIgEgAhC4gICAACIQDeUBIAEhAQzyAgsgACABIgEgAhC4gICAACIQDeYBIAEhAQwNCyAAIAEiASACELqAgIAAIhAN5wEgASEBDPACCwJAIAEiASACRw0AQQ8hEAzlAwsgAS0AACIQQTtGDQggEEENRw3oASABQQFqIQEM7wILIAAgASIBIAIQuoCAgAAiEA3oASABIQEM8gILA0ACQCABLQAAQfC1gIAAai0AACIQQQFGDQAgEEECRw3rASAAKAIEIRAgAEEANgIEIAAgECABQQFqIgEQuYCAgAAiEA3qASABIQEM9AILIAFBAWoiASACRw0AC0ESIRAM4gMLIAAgASIBIAIQuoCAgAAiEA3pASABIQEMCgsgASIBIAJHDQZBGyEQDOADCwJAIAEiASACRw0AQRYhEAzgAwsgAEGKgICAADYCCCAAIAE2AgQgACABIAIQuICAgAAiEA3qASABIQFBICEQDMYDCwJAIAEiASACRg0AA0ACQCABLQAAQfC3gIAAai0AACIQQQJGDQACQCAQQX9qDgTlAewBAOsB7AELIAFBAWohAUEIIRAMyAMLIAFBAWoiASACRw0AC0EVIRAM3wMLQRUhEAzeAwsDQAJAIAEtAABB8LmAgABqLQAAIhBBAkYNACAQQX9qDgTeAewB4AHrAewBCyABQQFqIgEgAkcNAAtBGCEQDN0DCwJAIAEiASACRg0AIABBi4CAgAA2AgggACABNgIEIAEhAUEHIRAMxAMLQRkhEAzcAwsgAUEBaiEBDAILAkAgASIUIAJHDQBBGiEQDNsDCyAUIQECQCAULQAAQXNqDhTdAu4C7gLuAu4C7gLuAu4C7gLuAu4C7gLuAu4C7gLuAu4C7gLuAgDuAgtBACEQIABBADYCHCAAQa+LgIAANgIQIABBAjYCDCAAIBRBAWo2AhQM2gMLAkAgAS0AACIQQTtGDQAgEEENRw3oASABQQFqIQEM5QILIAFBAWohAQtBIiEQDL8DCwJAIAEiECACRw0AQRwhEAzYAwtCACERIBAhASAQLQAAQVBqDjfnAeYBAQIDBAUGBwgAAAAAAAAACQoLDA0OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPEBESExQAC0EeIRAMvQMLQgIhEQzlAQtCAyERDOQBC0IEIREM4wELQgUhEQziAQtCBiERDOEBC0IHIREM4AELQgghEQzfAQtCCSERDN4BC0IKIREM3QELQgshEQzcAQtCDCERDNsBC0INIREM2gELQg4hEQzZAQtCDyERDNgBC0IKIREM1wELQgshEQzWAQtCDCERDNUBC0INIREM1AELQg4hEQzTAQtCDyERDNIBC0IAIRECQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIBAtAABBUGoON+UB5AEAAQIDBAUGB+YB5gHmAeYB5gHmAeYBCAkKCwwN5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAeYB5gHmAQ4PEBESE+YBC0ICIREM5AELQgMhEQzjAQtCBCERDOIBC0IFIREM4QELQgYhEQzgAQtCByERDN8BC0IIIREM3gELQgkhEQzdAQtCCiERDNwBC0ILIREM2wELQgwhEQzaAQtCDSERDNkBC0IOIREM2AELQg8hEQzXAQtCCiERDNYBC0ILIREM1QELQgwhEQzUAQtCDSERDNMBC0IOIREM0gELQg8hEQzRAQsgAEIAIAApAyAiESACIAEiEGutIhJ9IhMgEyARVhs3AyAgESASViIURQ3SAUEfIRAMwAMLAkAgASIBIAJGDQAgAEGJgICAADYCCCAAIAE2AgQgASEBQSQhEAynAwtBICEQDL8DCyAAIAEiECACEL6AgIAAQX9qDgW2AQDFAgHRAdIBC0ERIRAMpAMLIABBAToALyAQIQEMuwMLIAEiASACRw3SAUEkIRAMuwMLIAEiDSACRw0eQcYAIRAMugMLIAAgASIBIAIQsoCAgAAiEA3UASABIQEMtQELIAEiECACRw0mQdAAIRAMuAMLAkAgASIBIAJHDQBBKCEQDLgDCyAAQQA2AgQgAEGMgICAADYCCCAAIAEgARCxgICAACIQDdMBIAEhAQzYAQsCQCABIhAgAkcNAEEpIRAMtwMLIBAtAAAiAUEgRg0UIAFBCUcN0wEgEEEBaiEBDBULAkAgASIBIAJGDQAgAUEBaiEBDBcLQSohEAy1AwsCQCABIhAgAkcNAEErIRAMtQMLAkAgEC0AACIBQQlGDQAgAUEgRw3VAQsgAC0ALEEIRg3TASAQIQEMkQMLAkAgASIBIAJHDQBBLCEQDLQDCyABLQAAQQpHDdUBIAFBAWohAQzJAgsgASIOIAJHDdUBQS8hEAyyAwsDQAJAIAEtAAAiEEEgRg0AAkAgEEF2ag4EANwB3AEA2gELIAEhAQzgAQsgAUEBaiIBIAJHDQALQTEhEAyxAwtBMiEQIAEiFCACRg2wAyACIBRrIAAoAgAiAWohFSAUIAFrQQNqIRYCQANAIBQtAAAiF0EgciAXIBdBv39qQf8BcUEaSRtB/wFxIAFB8LuAgABqLQAARw0BAkAgAUEDRw0AQQYhAQyWAwsgAUEBaiEBIBRBAWoiFCACRw0ACyAAIBU2AgAMsQMLIABBADYCACAUIQEM2QELQTMhECABIhQgAkYNrwMgAiAUayAAKAIAIgFqIRUgFCABa0EIaiEWAkADQCAULQAAIhdBIHIgFyAXQb9/akH/AXFBGkkbQf8BcSABQfS7gIAAai0AAEcNAQJAIAFBCEcNAEEFIQEMlQMLIAFBAWohASAUQQFqIhQgAkcNAAsgACAVNgIADLADCyAAQQA2AgAgFCEBDNgBC0E0IRAgASIUIAJGDa4DIAIgFGsgACgCACIBaiEVIBQgAWtBBWohFgJAA0AgFC0AACIXQSByIBcgF0G/f2pB/wFxQRpJG0H/AXEgAUHQwoCAAGotAABHDQECQCABQQVHDQBBByEBDJQDCyABQQFqIQEgFEEBaiIUIAJHDQALIAAgFTYCAAyvAwsgAEEANgIAIBQhAQzXAQsCQCABIgEgAkYNAANAAkAgAS0AAEGAvoCAAGotAAAiEEEBRg0AIBBBAkYNCiABIQEM3QELIAFBAWoiASACRw0AC0EwIRAMrgMLQTAhEAytAwsCQCABIgEgAkYNAANAAkAgAS0AACIQQSBGDQAgEEF2ag4E2QHaAdoB2QHaAQsgAUEBaiIBIAJHDQALQTghEAytAwtBOCEQDKwDCwNAAkAgAS0AACIQQSBGDQAgEEEJRw0DCyABQQFqIgEgAkcNAAtBPCEQDKsDCwNAAkAgAS0AACIQQSBGDQACQAJAIBBBdmoOBNoBAQHaAQALIBBBLEYN2wELIAEhAQwECyABQQFqIgEgAkcNAAtBPyEQDKoDCyABIQEM2wELQcAAIRAgASIUIAJGDagDIAIgFGsgACgCACIBaiEWIBQgAWtBBmohFwJAA0AgFC0AAEEgciABQYDAgIAAai0AAEcNASABQQZGDY4DIAFBAWohASAUQQFqIhQgAkcNAAsgACAWNgIADKkDCyAAQQA2AgAgFCEBC0E2IRAMjgMLAkAgASIPIAJHDQBBwQAhEAynAwsgAEGMgICAADYCCCAAIA82AgQgDyEBIAAtACxBf2oOBM0B1QHXAdkBhwMLIAFBAWohAQzMAQsCQCABIgEgAkYNAANAAkAgAS0AACIQQSByIBAgEEG/f2pB/wFxQRpJG0H/AXEiEEEJRg0AIBBBIEYNAAJAAkACQAJAIBBBnX9qDhMAAwMDAwMDAwEDAwMDAwMDAwMCAwsgAUEBaiEBQTEhEAyRAwsgAUEBaiEBQTIhEAyQAwsgAUEBaiEBQTMhEAyPAwsgASEBDNABCyABQQFqIgEgAkcNAAtBNSEQDKUDC0E1IRAMpAMLAkAgASIBIAJGDQADQAJAIAEtAABBgLyAgABqLQAAQQFGDQAgASEBDNMBCyABQQFqIgEgAkcNAAtBPSEQDKQDC0E9IRAMowMLIAAgASIBIAIQsICAgAAiEA3WASABIQEMAQsgEEEBaiEBC0E8IRAMhwMLAkAgASIBIAJHDQBBwgAhEAygAwsCQANAAkAgAS0AAEF3ag4YAAL+Av4ChAP+Av4C/gL+Av4C/gL+Av4C/gL+Av4C/gL+Av4C/gL+Av4C/gIA/gILIAFBAWoiASACRw0AC0HCACEQDKADCyABQQFqIQEgAC0ALUEBcUUNvQEgASEBC0EsIRAMhQMLIAEiASACRw3TAUHEACEQDJ0DCwNAAkAgAS0AAEGQwICAAGotAABBAUYNACABIQEMtwILIAFBAWoiASACRw0AC0HFACEQDJwDCyANLQAAIhBBIEYNswEgEEE6Rw2BAyAAKAIEIQEgAEEANgIEIAAgASANEK+AgIAAIgEN0AEgDUEBaiEBDLMCC0HHACEQIAEiDSACRg2aAyACIA1rIAAoAgAiAWohFiANIAFrQQVqIRcDQCANLQAAIhRBIHIgFCAUQb9/akH/AXFBGkkbQf8BcSABQZDCgIAAai0AAEcNgAMgAUEFRg30AiABQQFqIQEgDUEBaiINIAJHDQALIAAgFjYCAAyaAwtByAAhECABIg0gAkYNmQMgAiANayAAKAIAIgFqIRYgDSABa0EJaiEXA0AgDS0AACIUQSByIBQgFEG/f2pB/wFxQRpJG0H/AXEgAUGWwoCAAGotAABHDf8CAkAgAUEJRw0AQQIhAQz1AgsgAUEBaiEBIA1BAWoiDSACRw0ACyAAIBY2AgAMmQMLAkAgASINIAJHDQBByQAhEAyZAwsCQAJAIA0tAAAiAUEgciABIAFBv39qQf8BcUEaSRtB/wFxQZJ/ag4HAIADgAOAA4ADgAMBgAMLIA1BAWohAUE+IRAMgAMLIA1BAWohAUE/IRAM/wILQcoAIRAgASINIAJGDZcDIAIgDWsgACgCACIBaiEWIA0gAWtBAWohFwNAIA0tAAAiFEEgciAUIBRBv39qQf8BcUEaSRtB/wFxIAFBoMKAgABqLQAARw39AiABQQFGDfACIAFBAWohASANQQFqIg0gAkcNAAsgACAWNgIADJcDC0HLACEQIAEiDSACRg2WAyACIA1rIAAoAgAiAWohFiANIAFrQQ5qIRcDQCANLQAAIhRBIHIgFCAUQb9/akH/AXFBGkkbQf8BcSABQaLCgIAAai0AAEcN/AIgAUEORg3wAiABQQFqIQEgDUEBaiINIAJHDQALIAAgFjYCAAyWAwtBzAAhECABIg0gAkYNlQMgAiANayAAKAIAIgFqIRYgDSABa0EPaiEXA0AgDS0AACIUQSByIBQgFEG/f2pB/wFxQRpJG0H/AXEgAUHAwoCAAGotAABHDfsCAkAgAUEPRw0AQQMhAQzxAgsgAUEBaiEBIA1BAWoiDSACRw0ACyAAIBY2AgAMlQMLQc0AIRAgASINIAJGDZQDIAIgDWsgACgCACIBaiEWIA0gAWtBBWohFwNAIA0tAAAiFEEgciAUIBRBv39qQf8BcUEaSRtB/wFxIAFB0MKAgABqLQAARw36AgJAIAFBBUcNAEEEIQEM8AILIAFBAWohASANQQFqIg0gAkcNAAsgACAWNgIADJQDCwJAIAEiDSACRw0AQc4AIRAMlAMLAkACQAJAAkAgDS0AACIBQSByIAEgAUG/f2pB/wFxQRpJG0H/AXFBnX9qDhMA/QL9Av0C/QL9Av0C/QL9Av0C/QL9Av0CAf0C/QL9AgID/QILIA1BAWohAUHBACEQDP0CCyANQQFqIQFBwgAhEAz8AgsgDUEBaiEBQcMAIRAM+wILIA1BAWohAUHEACEQDPoCCwJAIAEiASACRg0AIABBjYCAgAA2AgggACABNgIEIAEhAUHFACEQDPoCC0HPACEQDJIDCyAQIQECQAJAIBAtAABBdmoOBAGoAqgCAKgCCyAQQQFqIQELQSchEAz4AgsCQCABIgEgAkcNAEHRACEQDJEDCwJAIAEtAABBIEYNACABIQEMjQELIAFBAWohASAALQAtQQFxRQ3HASABIQEMjAELIAEiFyACRw3IAUHSACEQDI8DC0HTACEQIAEiFCACRg2OAyACIBRrIAAoAgAiAWohFiAUIAFrQQFqIRcDQCAULQAAIAFB1sKAgABqLQAARw3MASABQQFGDccBIAFBAWohASAUQQFqIhQgAkcNAAsgACAWNgIADI4DCwJAIAEiASACRw0AQdUAIRAMjgMLIAEtAABBCkcNzAEgAUEBaiEBDMcBCwJAIAEiASACRw0AQdYAIRAMjQMLAkACQCABLQAAQXZqDgQAzQHNAQHNAQsgAUEBaiEBDMcBCyABQQFqIQFBygAhEAzzAgsgACABIgEgAhCugICAACIQDcsBIAEhAUHNACEQDPICCyAALQApQSJGDYUDDKYCCwJAIAEiASACRw0AQdsAIRAMigMLQQAhFEEBIRdBASEWQQAhEAJAAkACQAJAAkACQAJAAkACQCABLQAAQVBqDgrUAdMBAAECAwQFBgjVAQtBAiEQDAYLQQMhEAwFC0EEIRAMBAtBBSEQDAMLQQYhEAwCC0EHIRAMAQtBCCEQC0EAIRdBACEWQQAhFAzMAQtBCSEQQQEhFEEAIRdBACEWDMsBCwJAIAEiASACRw0AQd0AIRAMiQMLIAEtAABBLkcNzAEgAUEBaiEBDKYCCyABIgEgAkcNzAFB3wAhEAyHAwsCQCABIgEgAkYNACAAQY6AgIAANgIIIAAgATYCBCABIQFB0AAhEAzuAgtB4AAhEAyGAwtB4QAhECABIgEgAkYNhQMgAiABayAAKAIAIhRqIRYgASAUa0EDaiEXA0AgAS0AACAUQeLCgIAAai0AAEcNzQEgFEEDRg3MASAUQQFqIRQgAUEBaiIBIAJHDQALIAAgFjYCAAyFAwtB4gAhECABIgEgAkYNhAMgAiABayAAKAIAIhRqIRYgASAUa0ECaiEXA0AgAS0AACAUQebCgIAAai0AAEcNzAEgFEECRg3OASAUQQFqIRQgAUEBaiIBIAJHDQALIAAgFjYCAAyEAwtB4wAhECABIgEgAkYNgwMgAiABayAAKAIAIhRqIRYgASAUa0EDaiEXA0AgAS0AACAUQenCgIAAai0AAEcNywEgFEEDRg3OASAUQQFqIRQgAUEBaiIBIAJHDQALIAAgFjYCAAyDAwsCQCABIgEgAkcNAEHlACEQDIMDCyAAIAFBAWoiASACEKiAgIAAIhANzQEgASEBQdYAIRAM6QILAkAgASIBIAJGDQADQAJAIAEtAAAiEEEgRg0AAkACQAJAIBBBuH9qDgsAAc8BzwHPAc8BzwHPAc8BzwECzwELIAFBAWohAUHSACEQDO0CCyABQQFqIQFB0wAhEAzsAgsgAUEBaiEBQdQAIRAM6wILIAFBAWoiASACRw0AC0HkACEQDIIDC0HkACEQDIEDCwNAAkAgAS0AAEHwwoCAAGotAAAiEEEBRg0AIBBBfmoOA88B0AHRAdIBCyABQQFqIgEgAkcNAAtB5gAhEAyAAwsCQCABIgEgAkYNACABQQFqIQEMAwtB5wAhEAz/AgsDQAJAIAEtAABB8MSAgABqLQAAIhBBAUYNAAJAIBBBfmoOBNIB0wHUAQDVAQsgASEBQdcAIRAM5wILIAFBAWoiASACRw0AC0HoACEQDP4CCwJAIAEiASACRw0AQekAIRAM/gILAkAgAS0AACIQQXZqDhq6AdUB1QG8AdUB1QHVAdUB1QHVAdUB1QHVAdUB1QHVAdUB1QHVAdUB1QHVAcoB1QHVAQDTAQsgAUEBaiEBC0EGIRAM4wILA0ACQCABLQAAQfDGgIAAai0AAEEBRg0AIAEhAQyeAgsgAUEBaiIBIAJHDQALQeoAIRAM+wILAkAgASIBIAJGDQAgAUEBaiEBDAMLQesAIRAM+gILAkAgASIBIAJHDQBB7AAhEAz6AgsgAUEBaiEBDAELAkAgASIBIAJHDQBB7QAhEAz5AgsgAUEBaiEBC0EEIRAM3gILAkAgASIUIAJHDQBB7gAhEAz3AgsgFCEBAkACQAJAIBQtAABB8MiAgABqLQAAQX9qDgfUAdUB1gEAnAIBAtcBCyAUQQFqIQEMCgsgFEEBaiEBDM0BC0EAIRAgAEEANgIcIABBm5KAgAA2AhAgAEEHNgIMIAAgFEEBajYCFAz2AgsCQANAAkAgAS0AAEHwyICAAGotAAAiEEEERg0AAkACQCAQQX9qDgfSAdMB1AHZAQAEAdkBCyABIQFB2gAhEAzgAgsgAUEBaiEBQdwAIRAM3wILIAFBAWoiASACRw0AC0HvACEQDPYCCyABQQFqIQEMywELAkAgASIUIAJHDQBB8AAhEAz1AgsgFC0AAEEvRw3UASAUQQFqIQEMBgsCQCABIhQgAkcNAEHxACEQDPQCCwJAIBQtAAAiAUEvRw0AIBRBAWohAUHdACEQDNsCCyABQXZqIgRBFksN0wFBASAEdEGJgIACcUUN0wEMygILAkAgASIBIAJGDQAgAUEBaiEBQd4AIRAM2gILQfIAIRAM8gILAkAgASIUIAJHDQBB9AAhEAzyAgsgFCEBAkAgFC0AAEHwzICAAGotAABBf2oOA8kClAIA1AELQeEAIRAM2AILAkAgASIUIAJGDQADQAJAIBQtAABB8MqAgABqLQAAIgFBA0YNAAJAIAFBf2oOAssCANUBCyAUIQFB3wAhEAzaAgsgFEEBaiIUIAJHDQALQfMAIRAM8QILQfMAIRAM8AILAkAgASIBIAJGDQAgAEGPgICAADYCCCAAIAE2AgQgASEBQeAAIRAM1wILQfUAIRAM7wILAkAgASIBIAJHDQBB9gAhEAzvAgsgAEGPgICAADYCCCAAIAE2AgQgASEBC0EDIRAM1AILA0AgAS0AAEEgRw3DAiABQQFqIgEgAkcNAAtB9wAhEAzsAgsCQCABIgEgAkcNAEH4ACEQDOwCCyABLQAAQSBHDc4BIAFBAWohAQzvAQsgACABIgEgAhCsgICAACIQDc4BIAEhAQyOAgsCQCABIgQgAkcNAEH6ACEQDOoCCyAELQAAQcwARw3RASAEQQFqIQFBEyEQDM8BCwJAIAEiBCACRw0AQfsAIRAM6QILIAIgBGsgACgCACIBaiEUIAQgAWtBBWohEANAIAQtAAAgAUHwzoCAAGotAABHDdABIAFBBUYNzgEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBB+wAhEAzoAgsCQCABIgQgAkcNAEH8ACEQDOgCCwJAAkAgBC0AAEG9f2oODADRAdEB0QHRAdEB0QHRAdEB0QHRAQHRAQsgBEEBaiEBQeYAIRAMzwILIARBAWohAUHnACEQDM4CCwJAIAEiBCACRw0AQf0AIRAM5wILIAIgBGsgACgCACIBaiEUIAQgAWtBAmohEAJAA0AgBC0AACABQe3PgIAAai0AAEcNzwEgAUECRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQf0AIRAM5wILIABBADYCACAQQQFqIQFBECEQDMwBCwJAIAEiBCACRw0AQf4AIRAM5gILIAIgBGsgACgCACIBaiEUIAQgAWtBBWohEAJAA0AgBC0AACABQfbOgIAAai0AAEcNzgEgAUEFRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQf4AIRAM5gILIABBADYCACAQQQFqIQFBFiEQDMsBCwJAIAEiBCACRw0AQf8AIRAM5QILIAIgBGsgACgCACIBaiEUIAQgAWtBA2ohEAJAA0AgBC0AACABQfzOgIAAai0AAEcNzQEgAUEDRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQf8AIRAM5QILIABBADYCACAQQQFqIQFBBSEQDMoBCwJAIAEiBCACRw0AQYABIRAM5AILIAQtAABB2QBHDcsBIARBAWohAUEIIRAMyQELAkAgASIEIAJHDQBBgQEhEAzjAgsCQAJAIAQtAABBsn9qDgMAzAEBzAELIARBAWohAUHrACEQDMoCCyAEQQFqIQFB7AAhEAzJAgsCQCABIgQgAkcNAEGCASEQDOICCwJAAkAgBC0AAEG4f2oOCADLAcsBywHLAcsBywEBywELIARBAWohAUHqACEQDMkCCyAEQQFqIQFB7QAhEAzIAgsCQCABIgQgAkcNAEGDASEQDOECCyACIARrIAAoAgAiAWohECAEIAFrQQJqIRQCQANAIAQtAAAgAUGAz4CAAGotAABHDckBIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgEDYCAEGDASEQDOECC0EAIRAgAEEANgIAIBRBAWohAQzGAQsCQCABIgQgAkcNAEGEASEQDOACCyACIARrIAAoAgAiAWohFCAEIAFrQQRqIRACQANAIAQtAAAgAUGDz4CAAGotAABHDcgBIAFBBEYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGEASEQDOACCyAAQQA2AgAgEEEBaiEBQSMhEAzFAQsCQCABIgQgAkcNAEGFASEQDN8CCwJAAkAgBC0AAEG0f2oOCADIAcgByAHIAcgByAEByAELIARBAWohAUHvACEQDMYCCyAEQQFqIQFB8AAhEAzFAgsCQCABIgQgAkcNAEGGASEQDN4CCyAELQAAQcUARw3FASAEQQFqIQEMgwILAkAgASIEIAJHDQBBhwEhEAzdAgsgAiAEayAAKAIAIgFqIRQgBCABa0EDaiEQAkADQCAELQAAIAFBiM+AgABqLQAARw3FASABQQNGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBhwEhEAzdAgsgAEEANgIAIBBBAWohAUEtIRAMwgELAkAgASIEIAJHDQBBiAEhEAzcAgsgAiAEayAAKAIAIgFqIRQgBCABa0EIaiEQAkADQCAELQAAIAFB0M+AgABqLQAARw3EASABQQhGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBiAEhEAzcAgsgAEEANgIAIBBBAWohAUEpIRAMwQELAkAgASIBIAJHDQBBiQEhEAzbAgtBASEQIAEtAABB3wBHDcABIAFBAWohAQyBAgsCQCABIgQgAkcNAEGKASEQDNoCCyACIARrIAAoAgAiAWohFCAEIAFrQQFqIRADQCAELQAAIAFBjM+AgABqLQAARw3BASABQQFGDa8CIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQYoBIRAM2QILAkAgASIEIAJHDQBBiwEhEAzZAgsgAiAEayAAKAIAIgFqIRQgBCABa0ECaiEQAkADQCAELQAAIAFBjs+AgABqLQAARw3BASABQQJGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBiwEhEAzZAgsgAEEANgIAIBBBAWohAUECIRAMvgELAkAgASIEIAJHDQBBjAEhEAzYAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQAkADQCAELQAAIAFB8M+AgABqLQAARw3AASABQQFGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBjAEhEAzYAgsgAEEANgIAIBBBAWohAUEfIRAMvQELAkAgASIEIAJHDQBBjQEhEAzXAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQAkADQCAELQAAIAFB8s+AgABqLQAARw2/ASABQQFGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBjQEhEAzXAgsgAEEANgIAIBBBAWohAUEJIRAMvAELAkAgASIEIAJHDQBBjgEhEAzWAgsCQAJAIAQtAABBt39qDgcAvwG/Ab8BvwG/AQG/AQsgBEEBaiEBQfgAIRAMvQILIARBAWohAUH5ACEQDLwCCwJAIAEiBCACRw0AQY8BIRAM1QILIAIgBGsgACgCACIBaiEUIAQgAWtBBWohEAJAA0AgBC0AACABQZHPgIAAai0AAEcNvQEgAUEFRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQY8BIRAM1QILIABBADYCACAQQQFqIQFBGCEQDLoBCwJAIAEiBCACRw0AQZABIRAM1AILIAIgBGsgACgCACIBaiEUIAQgAWtBAmohEAJAA0AgBC0AACABQZfPgIAAai0AAEcNvAEgAUECRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZABIRAM1AILIABBADYCACAQQQFqIQFBFyEQDLkBCwJAIAEiBCACRw0AQZEBIRAM0wILIAIgBGsgACgCACIBaiEUIAQgAWtBBmohEAJAA0AgBC0AACABQZrPgIAAai0AAEcNuwEgAUEGRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZEBIRAM0wILIABBADYCACAQQQFqIQFBFSEQDLgBCwJAIAEiBCACRw0AQZIBIRAM0gILIAIgBGsgACgCACIBaiEUIAQgAWtBBWohEAJAA0AgBC0AACABQaHPgIAAai0AAEcNugEgAUEFRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZIBIRAM0gILIABBADYCACAQQQFqIQFBHiEQDLcBCwJAIAEiBCACRw0AQZMBIRAM0QILIAQtAABBzABHDbgBIARBAWohAUEKIRAMtgELAkAgBCACRw0AQZQBIRAM0AILAkACQCAELQAAQb9/ag4PALkBuQG5AbkBuQG5AbkBuQG5AbkBuQG5AbkBAbkBCyAEQQFqIQFB/gAhEAy3AgsgBEEBaiEBQf8AIRAMtgILAkAgBCACRw0AQZUBIRAMzwILAkACQCAELQAAQb9/ag4DALgBAbgBCyAEQQFqIQFB/QAhEAy2AgsgBEEBaiEEQYABIRAMtQILAkAgBCACRw0AQZYBIRAMzgILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQafPgIAAai0AAEcNtgEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZYBIRAMzgILIABBADYCACAQQQFqIQFBCyEQDLMBCwJAIAQgAkcNAEGXASEQDM0CCwJAAkACQAJAIAQtAABBU2oOIwC4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBuAG4AbgBAbgBuAG4AbgBuAECuAG4AbgBA7gBCyAEQQFqIQFB+wAhEAy2AgsgBEEBaiEBQfwAIRAMtQILIARBAWohBEGBASEQDLQCCyAEQQFqIQRBggEhEAyzAgsCQCAEIAJHDQBBmAEhEAzMAgsgAiAEayAAKAIAIgFqIRQgBCABa0EEaiEQAkADQCAELQAAIAFBqc+AgABqLQAARw20ASABQQRGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBmAEhEAzMAgsgAEEANgIAIBBBAWohAUEZIRAMsQELAkAgBCACRw0AQZkBIRAMywILIAIgBGsgACgCACIBaiEUIAQgAWtBBWohEAJAA0AgBC0AACABQa7PgIAAai0AAEcNswEgAUEFRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZkBIRAMywILIABBADYCACAQQQFqIQFBBiEQDLABCwJAIAQgAkcNAEGaASEQDMoCCyACIARrIAAoAgAiAWohFCAEIAFrQQFqIRACQANAIAQtAAAgAUG0z4CAAGotAABHDbIBIAFBAUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGaASEQDMoCCyAAQQA2AgAgEEEBaiEBQRwhEAyvAQsCQCAEIAJHDQBBmwEhEAzJAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQAkADQCAELQAAIAFBts+AgABqLQAARw2xASABQQFGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBmwEhEAzJAgsgAEEANgIAIBBBAWohAUEnIRAMrgELAkAgBCACRw0AQZwBIRAMyAILAkACQCAELQAAQax/ag4CAAGxAQsgBEEBaiEEQYYBIRAMrwILIARBAWohBEGHASEQDK4CCwJAIAQgAkcNAEGdASEQDMcCCyACIARrIAAoAgAiAWohFCAEIAFrQQFqIRACQANAIAQtAAAgAUG4z4CAAGotAABHDa8BIAFBAUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGdASEQDMcCCyAAQQA2AgAgEEEBaiEBQSYhEAysAQsCQCAEIAJHDQBBngEhEAzGAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQAkADQCAELQAAIAFBus+AgABqLQAARw2uASABQQFGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBngEhEAzGAgsgAEEANgIAIBBBAWohAUEDIRAMqwELAkAgBCACRw0AQZ8BIRAMxQILIAIgBGsgACgCACIBaiEUIAQgAWtBAmohEAJAA0AgBC0AACABQe3PgIAAai0AAEcNrQEgAUECRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQZ8BIRAMxQILIABBADYCACAQQQFqIQFBDCEQDKoBCwJAIAQgAkcNAEGgASEQDMQCCyACIARrIAAoAgAiAWohFCAEIAFrQQNqIRACQANAIAQtAAAgAUG8z4CAAGotAABHDawBIAFBA0YNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGgASEQDMQCCyAAQQA2AgAgEEEBaiEBQQ0hEAypAQsCQCAEIAJHDQBBoQEhEAzDAgsCQAJAIAQtAABBun9qDgsArAGsAawBrAGsAawBrAGsAawBAawBCyAEQQFqIQRBiwEhEAyqAgsgBEEBaiEEQYwBIRAMqQILAkAgBCACRw0AQaIBIRAMwgILIAQtAABB0ABHDakBIARBAWohBAzpAQsCQCAEIAJHDQBBowEhEAzBAgsCQAJAIAQtAABBt39qDgcBqgGqAaoBqgGqAQCqAQsgBEEBaiEEQY4BIRAMqAILIARBAWohAUEiIRAMpgELAkAgBCACRw0AQaQBIRAMwAILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQcDPgIAAai0AAEcNqAEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQaQBIRAMwAILIABBADYCACAQQQFqIQFBHSEQDKUBCwJAIAQgAkcNAEGlASEQDL8CCwJAAkAgBC0AAEGuf2oOAwCoAQGoAQsgBEEBaiEEQZABIRAMpgILIARBAWohAUEEIRAMpAELAkAgBCACRw0AQaYBIRAMvgILAkACQAJAAkACQCAELQAAQb9/ag4VAKoBqgGqAaoBqgGqAaoBqgGqAaoBAaoBqgECqgGqAQOqAaoBBKoBCyAEQQFqIQRBiAEhEAyoAgsgBEEBaiEEQYkBIRAMpwILIARBAWohBEGKASEQDKYCCyAEQQFqIQRBjwEhEAylAgsgBEEBaiEEQZEBIRAMpAILAkAgBCACRw0AQacBIRAMvQILIAIgBGsgACgCACIBaiEUIAQgAWtBAmohEAJAA0AgBC0AACABQe3PgIAAai0AAEcNpQEgAUECRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQacBIRAMvQILIABBADYCACAQQQFqIQFBESEQDKIBCwJAIAQgAkcNAEGoASEQDLwCCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUHCz4CAAGotAABHDaQBIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGoASEQDLwCCyAAQQA2AgAgEEEBaiEBQSwhEAyhAQsCQCAEIAJHDQBBqQEhEAy7AgsgAiAEayAAKAIAIgFqIRQgBCABa0EEaiEQAkADQCAELQAAIAFBxc+AgABqLQAARw2jASABQQRGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBqQEhEAy7AgsgAEEANgIAIBBBAWohAUErIRAMoAELAkAgBCACRw0AQaoBIRAMugILIAIgBGsgACgCACIBaiEUIAQgAWtBAmohEAJAA0AgBC0AACABQcrPgIAAai0AAEcNogEgAUECRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQaoBIRAMugILIABBADYCACAQQQFqIQFBFCEQDJ8BCwJAIAQgAkcNAEGrASEQDLkCCwJAAkACQAJAIAQtAABBvn9qDg8AAQKkAaQBpAGkAaQBpAGkAaQBpAGkAaQBA6QBCyAEQQFqIQRBkwEhEAyiAgsgBEEBaiEEQZQBIRAMoQILIARBAWohBEGVASEQDKACCyAEQQFqIQRBlgEhEAyfAgsCQCAEIAJHDQBBrAEhEAy4AgsgBC0AAEHFAEcNnwEgBEEBaiEEDOABCwJAIAQgAkcNAEGtASEQDLcCCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUHNz4CAAGotAABHDZ8BIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEGtASEQDLcCCyAAQQA2AgAgEEEBaiEBQQ4hEAycAQsCQCAEIAJHDQBBrgEhEAy2AgsgBC0AAEHQAEcNnQEgBEEBaiEBQSUhEAybAQsCQCAEIAJHDQBBrwEhEAy1AgsgAiAEayAAKAIAIgFqIRQgBCABa0EIaiEQAkADQCAELQAAIAFB0M+AgABqLQAARw2dASABQQhGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBrwEhEAy1AgsgAEEANgIAIBBBAWohAUEqIRAMmgELAkAgBCACRw0AQbABIRAMtAILAkACQCAELQAAQat/ag4LAJ0BnQGdAZ0BnQGdAZ0BnQGdAQGdAQsgBEEBaiEEQZoBIRAMmwILIARBAWohBEGbASEQDJoCCwJAIAQgAkcNAEGxASEQDLMCCwJAAkAgBC0AAEG/f2oOFACcAZwBnAGcAZwBnAGcAZwBnAGcAZwBnAGcAZwBnAGcAZwBnAEBnAELIARBAWohBEGZASEQDJoCCyAEQQFqIQRBnAEhEAyZAgsCQCAEIAJHDQBBsgEhEAyyAgsgAiAEayAAKAIAIgFqIRQgBCABa0EDaiEQAkADQCAELQAAIAFB2c+AgABqLQAARw2aASABQQNGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBsgEhEAyyAgsgAEEANgIAIBBBAWohAUEhIRAMlwELAkAgBCACRw0AQbMBIRAMsQILIAIgBGsgACgCACIBaiEUIAQgAWtBBmohEAJAA0AgBC0AACABQd3PgIAAai0AAEcNmQEgAUEGRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQbMBIRAMsQILIABBADYCACAQQQFqIQFBGiEQDJYBCwJAIAQgAkcNAEG0ASEQDLACCwJAAkACQCAELQAAQbt/ag4RAJoBmgGaAZoBmgGaAZoBmgGaAQGaAZoBmgGaAZoBApoBCyAEQQFqIQRBnQEhEAyYAgsgBEEBaiEEQZ4BIRAMlwILIARBAWohBEGfASEQDJYCCwJAIAQgAkcNAEG1ASEQDK8CCyACIARrIAAoAgAiAWohFCAEIAFrQQVqIRACQANAIAQtAAAgAUHkz4CAAGotAABHDZcBIAFBBUYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEG1ASEQDK8CCyAAQQA2AgAgEEEBaiEBQSghEAyUAQsCQCAEIAJHDQBBtgEhEAyuAgsgAiAEayAAKAIAIgFqIRQgBCABa0ECaiEQAkADQCAELQAAIAFB6s+AgABqLQAARw2WASABQQJGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBtgEhEAyuAgsgAEEANgIAIBBBAWohAUEHIRAMkwELAkAgBCACRw0AQbcBIRAMrQILAkACQCAELQAAQbt/ag4OAJYBlgGWAZYBlgGWAZYBlgGWAZYBlgGWAQGWAQsgBEEBaiEEQaEBIRAMlAILIARBAWohBEGiASEQDJMCCwJAIAQgAkcNAEG4ASEQDKwCCyACIARrIAAoAgAiAWohFCAEIAFrQQJqIRACQANAIAQtAAAgAUHtz4CAAGotAABHDZQBIAFBAkYNASABQQFqIQEgBEEBaiIEIAJHDQALIAAgFDYCAEG4ASEQDKwCCyAAQQA2AgAgEEEBaiEBQRIhEAyRAQsCQCAEIAJHDQBBuQEhEAyrAgsgAiAEayAAKAIAIgFqIRQgBCABa0EBaiEQAkADQCAELQAAIAFB8M+AgABqLQAARw2TASABQQFGDQEgAUEBaiEBIARBAWoiBCACRw0ACyAAIBQ2AgBBuQEhEAyrAgsgAEEANgIAIBBBAWohAUEgIRAMkAELAkAgBCACRw0AQboBIRAMqgILIAIgBGsgACgCACIBaiEUIAQgAWtBAWohEAJAA0AgBC0AACABQfLPgIAAai0AAEcNkgEgAUEBRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQboBIRAMqgILIABBADYCACAQQQFqIQFBDyEQDI8BCwJAIAQgAkcNAEG7ASEQDKkCCwJAAkAgBC0AAEG3f2oOBwCSAZIBkgGSAZIBAZIBCyAEQQFqIQRBpQEhEAyQAgsgBEEBaiEEQaYBIRAMjwILAkAgBCACRw0AQbwBIRAMqAILIAIgBGsgACgCACIBaiEUIAQgAWtBB2ohEAJAA0AgBC0AACABQfTPgIAAai0AAEcNkAEgAUEHRg0BIAFBAWohASAEQQFqIgQgAkcNAAsgACAUNgIAQbwBIRAMqAILIABBADYCACAQQQFqIQFBGyEQDI0BCwJAIAQgAkcNAEG9ASEQDKcCCwJAAkACQCAELQAAQb5/ag4SAJEBkQGRAZEBkQGRAZEBkQGRAQGRAZEBkQGRAZEBkQECkQELIARBAWohBEGkASEQDI8CCyAEQQFqIQRBpwEhEAyOAgsgBEEBaiEEQagBIRAMjQILAkAgBCACRw0AQb4BIRAMpgILIAQtAABBzgBHDY0BIARBAWohBAzPAQsCQCAEIAJHDQBBvwEhEAylAgsCQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAELQAAQb9/ag4VAAECA5wBBAUGnAGcAZwBBwgJCgucAQwNDg+cAQsgBEEBaiEBQegAIRAMmgILIARBAWohAUHpACEQDJkCCyAEQQFqIQFB7gAhEAyYAgsgBEEBaiEBQfIAIRAMlwILIARBAWohAUHzACEQDJYCCyAEQQFqIQFB9gAhEAyVAgsgBEEBaiEBQfcAIRAMlAILIARBAWohAUH6ACEQDJMCCyAEQQFqIQRBgwEhEAySAgsgBEEBaiEEQYQBIRAMkQILIARBAWohBEGFASEQDJACCyAEQQFqIQRBkgEhEAyPAgsgBEEBaiEEQZgBIRAMjgILIARBAWohBEGgASEQDI0CCyAEQQFqIQRBowEhEAyMAgsgBEEBaiEEQaoBIRAMiwILAkAgBCACRg0AIABBkICAgAA2AgggACAENgIEQasBIRAMiwILQcABIRAMowILIAAgBSACEKqAgIAAIgENiwEgBSEBDFwLAkAgBiACRg0AIAZBAWohBQyNAQtBwgEhEAyhAgsDQAJAIBAtAABBdmoOBIwBAACPAQALIBBBAWoiECACRw0AC0HDASEQDKACCwJAIAcgAkYNACAAQZGAgIAANgIIIAAgBzYCBCAHIQFBASEQDIcCC0HEASEQDJ8CCwJAIAcgAkcNAEHFASEQDJ8CCwJAAkAgBy0AAEF2ag4EAc4BzgEAzgELIAdBAWohBgyNAQsgB0EBaiEFDIkBCwJAIAcgAkcNAEHGASEQDJ4CCwJAAkAgBy0AAEF2ag4XAY8BjwEBjwGPAY8BjwGPAY8BjwGPAY8BjwGPAY8BjwGPAY8BjwGPAY8BAI8BCyAHQQFqIQcLQbABIRAMhAILAkAgCCACRw0AQcgBIRAMnQILIAgtAABBIEcNjQEgAEEAOwEyIAhBAWohAUGzASEQDIMCCyABIRcCQANAIBciByACRg0BIActAABBUGpB/wFxIhBBCk8NzAECQCAALwEyIhRBmTNLDQAgACAUQQpsIhQ7ATIgEEH//wNzIBRB/v8DcUkNACAHQQFqIRcgACAUIBBqIhA7ATIgEEH//wNxQegHSQ0BCwtBACEQIABBADYCHCAAQcGJgIAANgIQIABBDTYCDCAAIAdBAWo2AhQMnAILQccBIRAMmwILIAAgCCACEK6AgIAAIhBFDcoBIBBBFUcNjAEgAEHIATYCHCAAIAg2AhQgAEHJl4CAADYCECAAQRU2AgxBACEQDJoCCwJAIAkgAkcNAEHMASEQDJoCC0EAIRRBASEXQQEhFkEAIRACQAJAAkACQAJAAkACQAJAAkAgCS0AAEFQag4KlgGVAQABAgMEBQYIlwELQQIhEAwGC0EDIRAMBQtBBCEQDAQLQQUhEAwDC0EGIRAMAgtBByEQDAELQQghEAtBACEXQQAhFkEAIRQMjgELQQkhEEEBIRRBACEXQQAhFgyNAQsCQCAKIAJHDQBBzgEhEAyZAgsgCi0AAEEuRw2OASAKQQFqIQkMygELIAsgAkcNjgFB0AEhEAyXAgsCQCALIAJGDQAgAEGOgICAADYCCCAAIAs2AgRBtwEhEAz+AQtB0QEhEAyWAgsCQCAEIAJHDQBB0gEhEAyWAgsgAiAEayAAKAIAIhBqIRQgBCAQa0EEaiELA0AgBC0AACAQQfzPgIAAai0AAEcNjgEgEEEERg3pASAQQQFqIRAgBEEBaiIEIAJHDQALIAAgFDYCAEHSASEQDJUCCyAAIAwgAhCsgICAACIBDY0BIAwhAQy4AQsCQCAEIAJHDQBB1AEhEAyUAgsgAiAEayAAKAIAIhBqIRQgBCAQa0EBaiEMA0AgBC0AACAQQYHQgIAAai0AAEcNjwEgEEEBRg2OASAQQQFqIRAgBEEBaiIEIAJHDQALIAAgFDYCAEHUASEQDJMCCwJAIAQgAkcNAEHWASEQDJMCCyACIARrIAAoAgAiEGohFCAEIBBrQQJqIQsDQCAELQAAIBBBg9CAgABqLQAARw2OASAQQQJGDZABIBBBAWohECAEQQFqIgQgAkcNAAsgACAUNgIAQdYBIRAMkgILAkAgBCACRw0AQdcBIRAMkgILAkACQCAELQAAQbt/ag4QAI8BjwGPAY8BjwGPAY8BjwGPAY8BjwGPAY8BjwEBjwELIARBAWohBEG7ASEQDPkBCyAEQQFqIQRBvAEhEAz4AQsCQCAEIAJHDQBB2AEhEAyRAgsgBC0AAEHIAEcNjAEgBEEBaiEEDMQBCwJAIAQgAkYNACAAQZCAgIAANgIIIAAgBDYCBEG+ASEQDPcBC0HZASEQDI8CCwJAIAQgAkcNAEHaASEQDI8CCyAELQAAQcgARg3DASAAQQE6ACgMuQELIABBAjoALyAAIAQgAhCmgICAACIQDY0BQcIBIRAM9AELIAAtAChBf2oOArcBuQG4AQsDQAJAIAQtAABBdmoOBACOAY4BAI4BCyAEQQFqIgQgAkcNAAtB3QEhEAyLAgsgAEEAOgAvIAAtAC1BBHFFDYQCCyAAQQA6AC8gAEEBOgA0IAEhAQyMAQsgEEEVRg3aASAAQQA2AhwgACABNgIUIABBp46AgAA2AhAgAEESNgIMQQAhEAyIAgsCQCAAIBAgAhC0gICAACIEDQAgECEBDIECCwJAIARBFUcNACAAQQM2AhwgACAQNgIUIABBsJiAgAA2AhAgAEEVNgIMQQAhEAyIAgsgAEEANgIcIAAgEDYCFCAAQaeOgIAANgIQIABBEjYCDEEAIRAMhwILIBBBFUYN1gEgAEEANgIcIAAgATYCFCAAQdqNgIAANgIQIABBFDYCDEEAIRAMhgILIAAoAgQhFyAAQQA2AgQgECARp2oiFiEBIAAgFyAQIBYgFBsiEBC1gICAACIURQ2NASAAQQc2AhwgACAQNgIUIAAgFDYCDEEAIRAMhQILIAAgAC8BMEGAAXI7ATAgASEBC0EqIRAM6gELIBBBFUYN0QEgAEEANgIcIAAgATYCFCAAQYOMgIAANgIQIABBEzYCDEEAIRAMggILIBBBFUYNzwEgAEEANgIcIAAgATYCFCAAQZqPgIAANgIQIABBIjYCDEEAIRAMgQILIAAoAgQhECAAQQA2AgQCQCAAIBAgARC3gICAACIQDQAgAUEBaiEBDI0BCyAAQQw2AhwgACAQNgIMIAAgAUEBajYCFEEAIRAMgAILIBBBFUYNzAEgAEEANgIcIAAgATYCFCAAQZqPgIAANgIQIABBIjYCDEEAIRAM/wELIAAoAgQhECAAQQA2AgQCQCAAIBAgARC3gICAACIQDQAgAUEBaiEBDIwBCyAAQQ02AhwgACAQNgIMIAAgAUEBajYCFEEAIRAM/gELIBBBFUYNyQEgAEEANgIcIAAgATYCFCAAQcaMgIAANgIQIABBIzYCDEEAIRAM/QELIAAoAgQhECAAQQA2AgQCQCAAIBAgARC5gICAACIQDQAgAUEBaiEBDIsBCyAAQQ42AhwgACAQNgIMIAAgAUEBajYCFEEAIRAM/AELIABBADYCHCAAIAE2AhQgAEHAlYCAADYCECAAQQI2AgxBACEQDPsBCyAQQRVGDcUBIABBADYCHCAAIAE2AhQgAEHGjICAADYCECAAQSM2AgxBACEQDPoBCyAAQRA2AhwgACABNgIUIAAgEDYCDEEAIRAM+QELIAAoAgQhBCAAQQA2AgQCQCAAIAQgARC5gICAACIEDQAgAUEBaiEBDPEBCyAAQRE2AhwgACAENgIMIAAgAUEBajYCFEEAIRAM+AELIBBBFUYNwQEgAEEANgIcIAAgATYCFCAAQcaMgIAANgIQIABBIzYCDEEAIRAM9wELIAAoAgQhECAAQQA2AgQCQCAAIBAgARC5gICAACIQDQAgAUEBaiEBDIgBCyAAQRM2AhwgACAQNgIMIAAgAUEBajYCFEEAIRAM9gELIAAoAgQhBCAAQQA2AgQCQCAAIAQgARC5gICAACIEDQAgAUEBaiEBDO0BCyAAQRQ2AhwgACAENgIMIAAgAUEBajYCFEEAIRAM9QELIBBBFUYNvQEgAEEANgIcIAAgATYCFCAAQZqPgIAANgIQIABBIjYCDEEAIRAM9AELIAAoAgQhECAAQQA2AgQCQCAAIBAgARC3gICAACIQDQAgAUEBaiEBDIYBCyAAQRY2AhwgACAQNgIMIAAgAUEBajYCFEEAIRAM8wELIAAoAgQhBCAAQQA2AgQCQCAAIAQgARC3gICAACIEDQAgAUEBaiEBDOkBCyAAQRc2AhwgACAENgIMIAAgAUEBajYCFEEAIRAM8gELIABBADYCHCAAIAE2AhQgAEHNk4CAADYCECAAQQw2AgxBACEQDPEBC0IBIRELIBBBAWohAQJAIAApAyAiEkL//////////w9WDQAgACASQgSGIBGENwMgIAEhAQyEAQsgAEEANgIcIAAgATYCFCAAQa2JgIAANgIQIABBDDYCDEEAIRAM7wELIABBADYCHCAAIBA2AhQgAEHNk4CAADYCECAAQQw2AgxBACEQDO4BCyAAKAIEIRcgAEEANgIEIBAgEadqIhYhASAAIBcgECAWIBQbIhAQtYCAgAAiFEUNcyAAQQU2AhwgACAQNgIUIAAgFDYCDEEAIRAM7QELIABBADYCHCAAIBA2AhQgAEGqnICAADYCECAAQQ82AgxBACEQDOwBCyAAIBAgAhC0gICAACIBDQEgECEBC0EOIRAM0QELAkAgAUEVRw0AIABBAjYCHCAAIBA2AhQgAEGwmICAADYCECAAQRU2AgxBACEQDOoBCyAAQQA2AhwgACAQNgIUIABBp46AgAA2AhAgAEESNgIMQQAhEAzpAQsgAUEBaiEQAkAgAC8BMCIBQYABcUUNAAJAIAAgECACELuAgIAAIgENACAQIQEMcAsgAUEVRw26ASAAQQU2AhwgACAQNgIUIABB+ZeAgAA2AhAgAEEVNgIMQQAhEAzpAQsCQCABQaAEcUGgBEcNACAALQAtQQJxDQAgAEEANgIcIAAgEDYCFCAAQZaTgIAANgIQIABBBDYCDEEAIRAM6QELIAAgECACEL2AgIAAGiAQIQECQAJAAkACQAJAIAAgECACELOAgIAADhYCAQAEBAQEBAQEBAQEBAQEBAQEBAQDBAsgAEEBOgAuCyAAIAAvATBBwAByOwEwIBAhAQtBJiEQDNEBCyAAQSM2AhwgACAQNgIUIABBpZaAgAA2AhAgAEEVNgIMQQAhEAzpAQsgAEEANgIcIAAgEDYCFCAAQdWLgIAANgIQIABBETYCDEEAIRAM6AELIAAtAC1BAXFFDQFBwwEhEAzOAQsCQCANIAJGDQADQAJAIA0tAABBIEYNACANIQEMxAELIA1BAWoiDSACRw0AC0ElIRAM5wELQSUhEAzmAQsgACgCBCEEIABBADYCBCAAIAQgDRCvgICAACIERQ2tASAAQSY2AhwgACAENgIMIAAgDUEBajYCFEEAIRAM5QELIBBBFUYNqwEgAEEANgIcIAAgATYCFCAAQf2NgIAANgIQIABBHTYCDEEAIRAM5AELIABBJzYCHCAAIAE2AhQgACAQNgIMQQAhEAzjAQsgECEBQQEhFAJAAkACQAJAAkACQAJAIAAtACxBfmoOBwYFBQMBAgAFCyAAIAAvATBBCHI7ATAMAwtBAiEUDAELQQQhFAsgAEEBOgAsIAAgAC8BMCAUcjsBMAsgECEBC0ErIRAMygELIABBADYCHCAAIBA2AhQgAEGrkoCAADYCECAAQQs2AgxBACEQDOIBCyAAQQA2AhwgACABNgIUIABB4Y+AgAA2AhAgAEEKNgIMQQAhEAzhAQsgAEEAOgAsIBAhAQy9AQsgECEBQQEhFAJAAkACQAJAAkAgAC0ALEF7ag4EAwECAAULIAAgAC8BMEEIcjsBMAwDC0ECIRQMAQtBBCEUCyAAQQE6ACwgACAALwEwIBRyOwEwCyAQIQELQSkhEAzFAQsgAEEANgIcIAAgATYCFCAAQfCUgIAANgIQIABBAzYCDEEAIRAM3QELAkAgDi0AAEENRw0AIAAoAgQhASAAQQA2AgQCQCAAIAEgDhCxgICAACIBDQAgDkEBaiEBDHULIABBLDYCHCAAIAE2AgwgACAOQQFqNgIUQQAhEAzdAQsgAC0ALUEBcUUNAUHEASEQDMMBCwJAIA4gAkcNAEEtIRAM3AELAkACQANAAkAgDi0AAEF2ag4EAgAAAwALIA5BAWoiDiACRw0AC0EtIRAM3QELIAAoAgQhASAAQQA2AgQCQCAAIAEgDhCxgICAACIBDQAgDiEBDHQLIABBLDYCHCAAIA42AhQgACABNgIMQQAhEAzcAQsgACgCBCEBIABBADYCBAJAIAAgASAOELGAgIAAIgENACAOQQFqIQEMcwsgAEEsNgIcIAAgATYCDCAAIA5BAWo2AhRBACEQDNsBCyAAKAIEIQQgAEEANgIEIAAgBCAOELGAgIAAIgQNoAEgDiEBDM4BCyAQQSxHDQEgAUEBaiEQQQEhAQJAAkACQAJAAkAgAC0ALEF7ag4EAwECBAALIBAhAQwEC0ECIQEMAQtBBCEBCyAAQQE6ACwgACAALwEwIAFyOwEwIBAhAQwBCyAAIAAvATBBCHI7ATAgECEBC0E5IRAMvwELIABBADoALCABIQELQTQhEAy9AQsgACAALwEwQSByOwEwIAEhAQwCCyAAKAIEIQQgAEEANgIEAkAgACAEIAEQsYCAgAAiBA0AIAEhAQzHAQsgAEE3NgIcIAAgATYCFCAAIAQ2AgxBACEQDNQBCyAAQQg6ACwgASEBC0EwIRAMuQELAkAgAC0AKEEBRg0AIAEhAQwECyAALQAtQQhxRQ2TASABIQEMAwsgAC0AMEEgcQ2UAUHFASEQDLcBCwJAIA8gAkYNAAJAA0ACQCAPLQAAQVBqIgFB/wFxQQpJDQAgDyEBQTUhEAy6AQsgACkDICIRQpmz5syZs+bMGVYNASAAIBFCCn4iETcDICARIAGtQv8BgyISQn+FVg0BIAAgESASfDcDICAPQQFqIg8gAkcNAAtBOSEQDNEBCyAAKAIEIQIgAEEANgIEIAAgAiAPQQFqIgQQsYCAgAAiAg2VASAEIQEMwwELQTkhEAzPAQsCQCAALwEwIgFBCHFFDQAgAC0AKEEBRw0AIAAtAC1BCHFFDZABCyAAIAFB9/sDcUGABHI7ATAgDyEBC0E3IRAMtAELIAAgAC8BMEEQcjsBMAyrAQsgEEEVRg2LASAAQQA2AhwgACABNgIUIABB8I6AgAA2AhAgAEEcNgIMQQAhEAzLAQsgAEHDADYCHCAAIAE2AgwgACANQQFqNgIUQQAhEAzKAQsCQCABLQAAQTpHDQAgACgCBCEQIABBADYCBAJAIAAgECABEK+AgIAAIhANACABQQFqIQEMYwsgAEHDADYCHCAAIBA2AgwgACABQQFqNgIUQQAhEAzKAQsgAEEANgIcIAAgATYCFCAAQbGRgIAANgIQIABBCjYCDEEAIRAMyQELIABBADYCHCAAIAE2AhQgAEGgmYCAADYCECAAQR42AgxBACEQDMgBCyAAQQA2AgALIABBgBI7ASogACAXQQFqIgEgAhCogICAACIQDQEgASEBC0HHACEQDKwBCyAQQRVHDYMBIABB0QA2AhwgACABNgIUIABB45eAgAA2AhAgAEEVNgIMQQAhEAzEAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMXgsgAEHSADYCHCAAIAE2AhQgACAQNgIMQQAhEAzDAQsgAEEANgIcIAAgFDYCFCAAQcGogIAANgIQIABBBzYCDCAAQQA2AgBBACEQDMIBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxdCyAAQdMANgIcIAAgATYCFCAAIBA2AgxBACEQDMEBC0EAIRAgAEEANgIcIAAgATYCFCAAQYCRgIAANgIQIABBCTYCDAzAAQsgEEEVRg19IABBADYCHCAAIAE2AhQgAEGUjYCAADYCECAAQSE2AgxBACEQDL8BC0EBIRZBACEXQQAhFEEBIRALIAAgEDoAKyABQQFqIQECQAJAIAAtAC1BEHENAAJAAkACQCAALQAqDgMBAAIECyAWRQ0DDAILIBQNAQwCCyAXRQ0BCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQrYCAgAAiEA0AIAEhAQxcCyAAQdgANgIcIAAgATYCFCAAIBA2AgxBACEQDL4BCyAAKAIEIQQgAEEANgIEAkAgACAEIAEQrYCAgAAiBA0AIAEhAQytAQsgAEHZADYCHCAAIAE2AhQgACAENgIMQQAhEAy9AQsgACgCBCEEIABBADYCBAJAIAAgBCABEK2AgIAAIgQNACABIQEMqwELIABB2gA2AhwgACABNgIUIAAgBDYCDEEAIRAMvAELIAAoAgQhBCAAQQA2AgQCQCAAIAQgARCtgICAACIEDQAgASEBDKkBCyAAQdwANgIcIAAgATYCFCAAIAQ2AgxBACEQDLsBCwJAIAEtAABBUGoiEEH/AXFBCk8NACAAIBA6ACogAUEBaiEBQc8AIRAMogELIAAoAgQhBCAAQQA2AgQCQCAAIAQgARCtgICAACIEDQAgASEBDKcBCyAAQd4ANgIcIAAgATYCFCAAIAQ2AgxBACEQDLoBCyAAQQA2AgAgF0EBaiEBAkAgAC0AKUEjTw0AIAEhAQxZCyAAQQA2AhwgACABNgIUIABB04mAgAA2AhAgAEEINgIMQQAhEAy5AQsgAEEANgIAC0EAIRAgAEEANgIcIAAgATYCFCAAQZCzgIAANgIQIABBCDYCDAy3AQsgAEEANgIAIBdBAWohAQJAIAAtAClBIUcNACABIQEMVgsgAEEANgIcIAAgATYCFCAAQZuKgIAANgIQIABBCDYCDEEAIRAMtgELIABBADYCACAXQQFqIQECQCAALQApIhBBXWpBC08NACABIQEMVQsCQCAQQQZLDQBBASAQdEHKAHFFDQAgASEBDFULQQAhECAAQQA2AhwgACABNgIUIABB94mAgAA2AhAgAEEINgIMDLUBCyAQQRVGDXEgAEEANgIcIAAgATYCFCAAQbmNgIAANgIQIABBGjYCDEEAIRAMtAELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDFQLIABB5QA2AhwgACABNgIUIAAgEDYCDEEAIRAMswELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDE0LIABB0gA2AhwgACABNgIUIAAgEDYCDEEAIRAMsgELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDE0LIABB0wA2AhwgACABNgIUIAAgEDYCDEEAIRAMsQELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDFELIABB5QA2AhwgACABNgIUIAAgEDYCDEEAIRAMsAELIABBADYCHCAAIAE2AhQgAEHGioCAADYCECAAQQc2AgxBACEQDK8BCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxJCyAAQdIANgIcIAAgATYCFCAAIBA2AgxBACEQDK4BCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxJCyAAQdMANgIcIAAgATYCFCAAIBA2AgxBACEQDK0BCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxNCyAAQeUANgIcIAAgATYCFCAAIBA2AgxBACEQDKwBCyAAQQA2AhwgACABNgIUIABB3IiAgAA2AhAgAEEHNgIMQQAhEAyrAQsgEEE/Rw0BIAFBAWohAQtBBSEQDJABC0EAIRAgAEEANgIcIAAgATYCFCAAQf2SgIAANgIQIABBBzYCDAyoAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMQgsgAEHSADYCHCAAIAE2AhQgACAQNgIMQQAhEAynAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMQgsgAEHTADYCHCAAIAE2AhQgACAQNgIMQQAhEAymAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMRgsgAEHlADYCHCAAIAE2AhQgACAQNgIMQQAhEAylAQsgACgCBCEBIABBADYCBAJAIAAgASAUEKeAgIAAIgENACAUIQEMPwsgAEHSADYCHCAAIBQ2AhQgACABNgIMQQAhEAykAQsgACgCBCEBIABBADYCBAJAIAAgASAUEKeAgIAAIgENACAUIQEMPwsgAEHTADYCHCAAIBQ2AhQgACABNgIMQQAhEAyjAQsgACgCBCEBIABBADYCBAJAIAAgASAUEKeAgIAAIgENACAUIQEMQwsgAEHlADYCHCAAIBQ2AhQgACABNgIMQQAhEAyiAQsgAEEANgIcIAAgFDYCFCAAQcOPgIAANgIQIABBBzYCDEEAIRAMoQELIABBADYCHCAAIAE2AhQgAEHDj4CAADYCECAAQQc2AgxBACEQDKABC0EAIRAgAEEANgIcIAAgFDYCFCAAQYycgIAANgIQIABBBzYCDAyfAQsgAEEANgIcIAAgFDYCFCAAQYycgIAANgIQIABBBzYCDEEAIRAMngELIABBADYCHCAAIBQ2AhQgAEH+kYCAADYCECAAQQc2AgxBACEQDJ0BCyAAQQA2AhwgACABNgIUIABBjpuAgAA2AhAgAEEGNgIMQQAhEAycAQsgEEEVRg1XIABBADYCHCAAIAE2AhQgAEHMjoCAADYCECAAQSA2AgxBACEQDJsBCyAAQQA2AgAgEEEBaiEBQSQhEAsgACAQOgApIAAoAgQhECAAQQA2AgQgACAQIAEQq4CAgAAiEA1UIAEhAQw+CyAAQQA2AgALQQAhECAAQQA2AhwgACAENgIUIABB8ZuAgAA2AhAgAEEGNgIMDJcBCyABQRVGDVAgAEEANgIcIAAgBTYCFCAAQfCMgIAANgIQIABBGzYCDEEAIRAMlgELIAAoAgQhBSAAQQA2AgQgACAFIBAQqYCAgAAiBQ0BIBBBAWohBQtBrQEhEAx7CyAAQcEBNgIcIAAgBTYCDCAAIBBBAWo2AhRBACEQDJMBCyAAKAIEIQYgAEEANgIEIAAgBiAQEKmAgIAAIgYNASAQQQFqIQYLQa4BIRAMeAsgAEHCATYCHCAAIAY2AgwgACAQQQFqNgIUQQAhEAyQAQsgAEEANgIcIAAgBzYCFCAAQZeLgIAANgIQIABBDTYCDEEAIRAMjwELIABBADYCHCAAIAg2AhQgAEHjkICAADYCECAAQQk2AgxBACEQDI4BCyAAQQA2AhwgACAINgIUIABBlI2AgAA2AhAgAEEhNgIMQQAhEAyNAQtBASEWQQAhF0EAIRRBASEQCyAAIBA6ACsgCUEBaiEIAkACQCAALQAtQRBxDQACQAJAAkAgAC0AKg4DAQACBAsgFkUNAwwCCyAUDQEMAgsgF0UNAQsgACgCBCEQIABBADYCBCAAIBAgCBCtgICAACIQRQ09IABByQE2AhwgACAINgIUIAAgEDYCDEEAIRAMjAELIAAoAgQhBCAAQQA2AgQgACAEIAgQrYCAgAAiBEUNdiAAQcoBNgIcIAAgCDYCFCAAIAQ2AgxBACEQDIsBCyAAKAIEIQQgAEEANgIEIAAgBCAJEK2AgIAAIgRFDXQgAEHLATYCHCAAIAk2AhQgACAENgIMQQAhEAyKAQsgACgCBCEEIABBADYCBCAAIAQgChCtgICAACIERQ1yIABBzQE2AhwgACAKNgIUIAAgBDYCDEEAIRAMiQELAkAgCy0AAEFQaiIQQf8BcUEKTw0AIAAgEDoAKiALQQFqIQpBtgEhEAxwCyAAKAIEIQQgAEEANgIEIAAgBCALEK2AgIAAIgRFDXAgAEHPATYCHCAAIAs2AhQgACAENgIMQQAhEAyIAQsgAEEANgIcIAAgBDYCFCAAQZCzgIAANgIQIABBCDYCDCAAQQA2AgBBACEQDIcBCyABQRVGDT8gAEEANgIcIAAgDDYCFCAAQcyOgIAANgIQIABBIDYCDEEAIRAMhgELIABBgQQ7ASggACgCBCEQIABCADcDACAAIBAgDEEBaiIMEKuAgIAAIhBFDTggAEHTATYCHCAAIAw2AhQgACAQNgIMQQAhEAyFAQsgAEEANgIAC0EAIRAgAEEANgIcIAAgBDYCFCAAQdibgIAANgIQIABBCDYCDAyDAQsgACgCBCEQIABCADcDACAAIBAgC0EBaiILEKuAgIAAIhANAUHGASEQDGkLIABBAjoAKAxVCyAAQdUBNgIcIAAgCzYCFCAAIBA2AgxBACEQDIABCyAQQRVGDTcgAEEANgIcIAAgBDYCFCAAQaSMgIAANgIQIABBEDYCDEEAIRAMfwsgAC0ANEEBRw00IAAgBCACELyAgIAAIhBFDTQgEEEVRw01IABB3AE2AhwgACAENgIUIABB1ZaAgAA2AhAgAEEVNgIMQQAhEAx+C0EAIRAgAEEANgIcIABBr4uAgAA2AhAgAEECNgIMIAAgFEEBajYCFAx9C0EAIRAMYwtBAiEQDGILQQ0hEAxhC0EPIRAMYAtBJSEQDF8LQRMhEAxeC0EVIRAMXQtBFiEQDFwLQRchEAxbC0EYIRAMWgtBGSEQDFkLQRohEAxYC0EbIRAMVwtBHCEQDFYLQR0hEAxVC0EfIRAMVAtBISEQDFMLQSMhEAxSC0HGACEQDFELQS4hEAxQC0EvIRAMTwtBOyEQDE4LQT0hEAxNC0HIACEQDEwLQckAIRAMSwtBywAhEAxKC0HMACEQDEkLQc4AIRAMSAtB0QAhEAxHC0HVACEQDEYLQdgAIRAMRQtB2QAhEAxEC0HbACEQDEMLQeQAIRAMQgtB5QAhEAxBC0HxACEQDEALQfQAIRAMPwtBjQEhEAw+C0GXASEQDD0LQakBIRAMPAtBrAEhEAw7C0HAASEQDDoLQbkBIRAMOQtBrwEhEAw4C0GxASEQDDcLQbIBIRAMNgtBtAEhEAw1C0G1ASEQDDQLQboBIRAMMwtBvQEhEAwyC0G/ASEQDDELQcEBIRAMMAsgAEEANgIcIAAgBDYCFCAAQemLgIAANgIQIABBHzYCDEEAIRAMSAsgAEHbATYCHCAAIAQ2AhQgAEH6loCAADYCECAAQRU2AgxBACEQDEcLIABB+AA2AhwgACAMNgIUIABBypiAgAA2AhAgAEEVNgIMQQAhEAxGCyAAQdEANgIcIAAgBTYCFCAAQbCXgIAANgIQIABBFTYCDEEAIRAMRQsgAEH5ADYCHCAAIAE2AhQgACAQNgIMQQAhEAxECyAAQfgANgIcIAAgATYCFCAAQcqYgIAANgIQIABBFTYCDEEAIRAMQwsgAEHkADYCHCAAIAE2AhQgAEHjl4CAADYCECAAQRU2AgxBACEQDEILIABB1wA2AhwgACABNgIUIABByZeAgAA2AhAgAEEVNgIMQQAhEAxBCyAAQQA2AhwgACABNgIUIABBuY2AgAA2AhAgAEEaNgIMQQAhEAxACyAAQcIANgIcIAAgATYCFCAAQeOYgIAANgIQIABBFTYCDEEAIRAMPwsgAEEANgIEIAAgDyAPELGAgIAAIgRFDQEgAEE6NgIcIAAgBDYCDCAAIA9BAWo2AhRBACEQDD4LIAAoAgQhBCAAQQA2AgQCQCAAIAQgARCxgICAACIERQ0AIABBOzYCHCAAIAQ2AgwgACABQQFqNgIUQQAhEAw+CyABQQFqIQEMLQsgD0EBaiEBDC0LIABBADYCHCAAIA82AhQgAEHkkoCAADYCECAAQQQ2AgxBACEQDDsLIABBNjYCHCAAIAQ2AhQgACACNgIMQQAhEAw6CyAAQS42AhwgACAONgIUIAAgBDYCDEEAIRAMOQsgAEHQADYCHCAAIAE2AhQgAEGRmICAADYCECAAQRU2AgxBACEQDDgLIA1BAWohAQwsCyAAQRU2AhwgACABNgIUIABBgpmAgAA2AhAgAEEVNgIMQQAhEAw2CyAAQRs2AhwgACABNgIUIABBkZeAgAA2AhAgAEEVNgIMQQAhEAw1CyAAQQ82AhwgACABNgIUIABBkZeAgAA2AhAgAEEVNgIMQQAhEAw0CyAAQQs2AhwgACABNgIUIABBkZeAgAA2AhAgAEEVNgIMQQAhEAwzCyAAQRo2AhwgACABNgIUIABBgpmAgAA2AhAgAEEVNgIMQQAhEAwyCyAAQQs2AhwgACABNgIUIABBgpmAgAA2AhAgAEEVNgIMQQAhEAwxCyAAQQo2AhwgACABNgIUIABB5JaAgAA2AhAgAEEVNgIMQQAhEAwwCyAAQR42AhwgACABNgIUIABB+ZeAgAA2AhAgAEEVNgIMQQAhEAwvCyAAQQA2AhwgACAQNgIUIABB2o2AgAA2AhAgAEEUNgIMQQAhEAwuCyAAQQQ2AhwgACABNgIUIABBsJiAgAA2AhAgAEEVNgIMQQAhEAwtCyAAQQA2AgAgC0EBaiELC0G4ASEQDBILIABBADYCACAQQQFqIQFB9QAhEAwRCyABIQECQCAALQApQQVHDQBB4wAhEAwRC0HiACEQDBALQQAhECAAQQA2AhwgAEHkkYCAADYCECAAQQc2AgwgACAUQQFqNgIUDCgLIABBADYCACAXQQFqIQFBwAAhEAwOC0EBIQELIAAgAToALCAAQQA2AgAgF0EBaiEBC0EoIRAMCwsgASEBC0E4IRAMCQsCQCABIg8gAkYNAANAAkAgDy0AAEGAvoCAAGotAAAiAUEBRg0AIAFBAkcNAyAPQQFqIQEMBAsgD0EBaiIPIAJHDQALQT4hEAwiC0E+IRAMIQsgAEEAOgAsIA8hAQwBC0ELIRAMBgtBOiEQDAULIAFBAWohAUEtIRAMBAsgACABOgAsIABBADYCACAWQQFqIQFBDCEQDAMLIABBADYCACAXQQFqIQFBCiEQDAILIABBADYCAAsgAEEAOgAsIA0hAUEJIRAMAAsLQQAhECAAQQA2AhwgACALNgIUIABBzZCAgAA2AhAgAEEJNgIMDBcLQQAhECAAQQA2AhwgACAKNgIUIABB6YqAgAA2AhAgAEEJNgIMDBYLQQAhECAAQQA2AhwgACAJNgIUIABBt5CAgAA2AhAgAEEJNgIMDBULQQAhECAAQQA2AhwgACAINgIUIABBnJGAgAA2AhAgAEEJNgIMDBQLQQAhECAAQQA2AhwgACABNgIUIABBzZCAgAA2AhAgAEEJNgIMDBMLQQAhECAAQQA2AhwgACABNgIUIABB6YqAgAA2AhAgAEEJNgIMDBILQQAhECAAQQA2AhwgACABNgIUIABBt5CAgAA2AhAgAEEJNgIMDBELQQAhECAAQQA2AhwgACABNgIUIABBnJGAgAA2AhAgAEEJNgIMDBALQQAhECAAQQA2AhwgACABNgIUIABBl5WAgAA2AhAgAEEPNgIMDA8LQQAhECAAQQA2AhwgACABNgIUIABBl5WAgAA2AhAgAEEPNgIMDA4LQQAhECAAQQA2AhwgACABNgIUIABBwJKAgAA2AhAgAEELNgIMDA0LQQAhECAAQQA2AhwgACABNgIUIABBlYmAgAA2AhAgAEELNgIMDAwLQQAhECAAQQA2AhwgACABNgIUIABB4Y+AgAA2AhAgAEEKNgIMDAsLQQAhECAAQQA2AhwgACABNgIUIABB+4+AgAA2AhAgAEEKNgIMDAoLQQAhECAAQQA2AhwgACABNgIUIABB8ZmAgAA2AhAgAEECNgIMDAkLQQAhECAAQQA2AhwgACABNgIUIABBxJSAgAA2AhAgAEECNgIMDAgLQQAhECAAQQA2AhwgACABNgIUIABB8pWAgAA2AhAgAEECNgIMDAcLIABBAjYCHCAAIAE2AhQgAEGcmoCAADYCECAAQRY2AgxBACEQDAYLQQEhEAwFC0HUACEQIAEiBCACRg0EIANBCGogACAEIAJB2MKAgABBChDFgICAACADKAIMIQQgAygCCA4DAQQCAAsQyoCAgAAACyAAQQA2AhwgAEG1moCAADYCECAAQRc2AgwgACAEQQFqNgIUQQAhEAwCCyAAQQA2AhwgACAENgIUIABBypqAgAA2AhAgAEEJNgIMQQAhEAwBCwJAIAEiBCACRw0AQSIhEAwBCyAAQYmAgIAANgIIIAAgBDYCBEEhIRALIANBEGokgICAgAAgEAuvAQECfyABKAIAIQYCQAJAIAIgA0YNACAEIAZqIQQgBiADaiACayEHIAIgBkF/cyAFaiIGaiEFA0ACQCACLQAAIAQtAABGDQBBAiEEDAMLAkAgBg0AQQAhBCAFIQIMAwsgBkF/aiEGIARBAWohBCACQQFqIgIgA0cNAAsgByEGIAMhAgsgAEEBNgIAIAEgBjYCACAAIAI2AgQPCyABQQA2AgAgACAENgIAIAAgAjYCBAsKACAAEMeAgIAAC/I2AQt/I4CAgIAAQRBrIgEkgICAgAACQEEAKAKg0ICAAA0AQQAQy4CAgABBgNSEgABrIgJB2QBJDQBBACEDAkBBACgC4NOAgAAiBA0AQQBCfzcC7NOAgABBAEKAgISAgIDAADcC5NOAgABBACABQQhqQXBxQdiq1aoFcyIENgLg04CAAEEAQQA2AvTTgIAAQQBBADYCxNOAgAALQQAgAjYCzNOAgABBAEGA1ISAADYCyNOAgABBAEGA1ISAADYCmNCAgABBACAENgKs0ICAAEEAQX82AqjQgIAAA0AgA0HE0ICAAGogA0G40ICAAGoiBDYCACAEIANBsNCAgABqIgU2AgAgA0G80ICAAGogBTYCACADQczQgIAAaiADQcDQgIAAaiIFNgIAIAUgBDYCACADQdTQgIAAaiADQcjQgIAAaiIENgIAIAQgBTYCACADQdDQgIAAaiAENgIAIANBIGoiA0GAAkcNAAtBgNSEgABBeEGA1ISAAGtBD3FBAEGA1ISAAEEIakEPcRsiA2oiBEEEaiACQUhqIgUgA2siA0EBcjYCAEEAQQAoAvDTgIAANgKk0ICAAEEAIAM2ApTQgIAAQQAgBDYCoNCAgABBgNSEgAAgBWpBODYCBAsCQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAEHsAUsNAAJAQQAoAojQgIAAIgZBECAAQRNqQXBxIABBC0kbIgJBA3YiBHYiA0EDcUUNAAJAAkAgA0EBcSAEckEBcyIFQQN0IgRBsNCAgABqIgMgBEG40ICAAGooAgAiBCgCCCICRw0AQQAgBkF+IAV3cTYCiNCAgAAMAQsgAyACNgIIIAIgAzYCDAsgBEEIaiEDIAQgBUEDdCIFQQNyNgIEIAQgBWoiBCAEKAIEQQFyNgIEDAwLIAJBACgCkNCAgAAiB00NAQJAIANFDQACQAJAIAMgBHRBAiAEdCIDQQAgA2tycSIDQQAgA2txQX9qIgMgA0EMdkEQcSIDdiIEQQV2QQhxIgUgA3IgBCAFdiIDQQJ2QQRxIgRyIAMgBHYiA0EBdkECcSIEciADIAR2IgNBAXZBAXEiBHIgAyAEdmoiBEEDdCIDQbDQgIAAaiIFIANBuNCAgABqKAIAIgMoAggiAEcNAEEAIAZBfiAEd3EiBjYCiNCAgAAMAQsgBSAANgIIIAAgBTYCDAsgAyACQQNyNgIEIAMgBEEDdCIEaiAEIAJrIgU2AgAgAyACaiIAIAVBAXI2AgQCQCAHRQ0AIAdBeHFBsNCAgABqIQJBACgCnNCAgAAhBAJAAkAgBkEBIAdBA3Z0IghxDQBBACAGIAhyNgKI0ICAACACIQgMAQsgAigCCCEICyAIIAQ2AgwgAiAENgIIIAQgAjYCDCAEIAg2AggLIANBCGohA0EAIAA2ApzQgIAAQQAgBTYCkNCAgAAMDAtBACgCjNCAgAAiCUUNASAJQQAgCWtxQX9qIgMgA0EMdkEQcSIDdiIEQQV2QQhxIgUgA3IgBCAFdiIDQQJ2QQRxIgRyIAMgBHYiA0EBdkECcSIEciADIAR2IgNBAXZBAXEiBHIgAyAEdmpBAnRBuNKAgABqKAIAIgAoAgRBeHEgAmshBCAAIQUCQANAAkAgBSgCECIDDQAgBUEUaigCACIDRQ0CCyADKAIEQXhxIAJrIgUgBCAFIARJIgUbIQQgAyAAIAUbIQAgAyEFDAALCyAAKAIYIQoCQCAAKAIMIgggAEYNACAAKAIIIgNBACgCmNCAgABJGiAIIAM2AgggAyAINgIMDAsLAkAgAEEUaiIFKAIAIgMNACAAKAIQIgNFDQMgAEEQaiEFCwNAIAUhCyADIghBFGoiBSgCACIDDQAgCEEQaiEFIAgoAhAiAw0ACyALQQA2AgAMCgtBfyECIABBv39LDQAgAEETaiIDQXBxIQJBACgCjNCAgAAiB0UNAEEAIQsCQCACQYACSQ0AQR8hCyACQf///wdLDQAgA0EIdiIDIANBgP4/akEQdkEIcSIDdCIEIARBgOAfakEQdkEEcSIEdCIFIAVBgIAPakEQdkECcSIFdEEPdiADIARyIAVyayIDQQF0IAIgA0EVanZBAXFyQRxqIQsLQQAgAmshBAJAAkACQAJAIAtBAnRBuNKAgABqKAIAIgUNAEEAIQNBACEIDAELQQAhAyACQQBBGSALQQF2ayALQR9GG3QhAEEAIQgDQAJAIAUoAgRBeHEgAmsiBiAETw0AIAYhBCAFIQggBg0AQQAhBCAFIQggBSEDDAMLIAMgBUEUaigCACIGIAYgBSAAQR12QQRxakEQaigCACIFRhsgAyAGGyEDIABBAXQhACAFDQALCwJAIAMgCHINAEEAIQhBAiALdCIDQQAgA2tyIAdxIgNFDQMgA0EAIANrcUF/aiIDIANBDHZBEHEiA3YiBUEFdkEIcSIAIANyIAUgAHYiA0ECdkEEcSIFciADIAV2IgNBAXZBAnEiBXIgAyAFdiIDQQF2QQFxIgVyIAMgBXZqQQJ0QbjSgIAAaigCACEDCyADRQ0BCwNAIAMoAgRBeHEgAmsiBiAESSEAAkAgAygCECIFDQAgA0EUaigCACEFCyAGIAQgABshBCADIAggABshCCAFIQMgBQ0ACwsgCEUNACAEQQAoApDQgIAAIAJrTw0AIAgoAhghCwJAIAgoAgwiACAIRg0AIAgoAggiA0EAKAKY0ICAAEkaIAAgAzYCCCADIAA2AgwMCQsCQCAIQRRqIgUoAgAiAw0AIAgoAhAiA0UNAyAIQRBqIQULA0AgBSEGIAMiAEEUaiIFKAIAIgMNACAAQRBqIQUgACgCECIDDQALIAZBADYCAAwICwJAQQAoApDQgIAAIgMgAkkNAEEAKAKc0ICAACEEAkACQCADIAJrIgVBEEkNACAEIAJqIgAgBUEBcjYCBEEAIAU2ApDQgIAAQQAgADYCnNCAgAAgBCADaiAFNgIAIAQgAkEDcjYCBAwBCyAEIANBA3I2AgQgBCADaiIDIAMoAgRBAXI2AgRBAEEANgKc0ICAAEEAQQA2ApDQgIAACyAEQQhqIQMMCgsCQEEAKAKU0ICAACIAIAJNDQBBACgCoNCAgAAiAyACaiIEIAAgAmsiBUEBcjYCBEEAIAU2ApTQgIAAQQAgBDYCoNCAgAAgAyACQQNyNgIEIANBCGohAwwKCwJAAkBBACgC4NOAgABFDQBBACgC6NOAgAAhBAwBC0EAQn83AuzTgIAAQQBCgICEgICAwAA3AuTTgIAAQQAgAUEMakFwcUHYqtWqBXM2AuDTgIAAQQBBADYC9NOAgABBAEEANgLE04CAAEGAgAQhBAtBACEDAkAgBCACQccAaiIHaiIGQQAgBGsiC3EiCCACSw0AQQBBMDYC+NOAgAAMCgsCQEEAKALA04CAACIDRQ0AAkBBACgCuNOAgAAiBCAIaiIFIARNDQAgBSADTQ0BC0EAIQNBAEEwNgL404CAAAwKC0EALQDE04CAAEEEcQ0EAkACQAJAQQAoAqDQgIAAIgRFDQBByNOAgAAhAwNAAkAgAygCACIFIARLDQAgBSADKAIEaiAESw0DCyADKAIIIgMNAAsLQQAQy4CAgAAiAEF/Rg0FIAghBgJAQQAoAuTTgIAAIgNBf2oiBCAAcUUNACAIIABrIAQgAGpBACADa3FqIQYLIAYgAk0NBSAGQf7///8HSw0FAkBBACgCwNOAgAAiA0UNAEEAKAK404CAACIEIAZqIgUgBE0NBiAFIANLDQYLIAYQy4CAgAAiAyAARw0BDAcLIAYgAGsgC3EiBkH+////B0sNBCAGEMuAgIAAIgAgAygCACADKAIEakYNAyAAIQMLAkAgA0F/Rg0AIAJByABqIAZNDQACQCAHIAZrQQAoAujTgIAAIgRqQQAgBGtxIgRB/v///wdNDQAgAyEADAcLAkAgBBDLgICAAEF/Rg0AIAQgBmohBiADIQAMBwtBACAGaxDLgICAABoMBAsgAyEAIANBf0cNBQwDC0EAIQgMBwtBACEADAULIABBf0cNAgtBAEEAKALE04CAAEEEcjYCxNOAgAALIAhB/v///wdLDQEgCBDLgICAACEAQQAQy4CAgAAhAyAAQX9GDQEgA0F/Rg0BIAAgA08NASADIABrIgYgAkE4ak0NAQtBAEEAKAK404CAACAGaiIDNgK404CAAAJAIANBACgCvNOAgABNDQBBACADNgK804CAAAsCQAJAAkACQEEAKAKg0ICAACIERQ0AQcjTgIAAIQMDQCAAIAMoAgAiBSADKAIEIghqRg0CIAMoAggiAw0ADAMLCwJAAkBBACgCmNCAgAAiA0UNACAAIANPDQELQQAgADYCmNCAgAALQQAhA0EAIAY2AszTgIAAQQAgADYCyNOAgABBAEF/NgKo0ICAAEEAQQAoAuDTgIAANgKs0ICAAEEAQQA2AtTTgIAAA0AgA0HE0ICAAGogA0G40ICAAGoiBDYCACAEIANBsNCAgABqIgU2AgAgA0G80ICAAGogBTYCACADQczQgIAAaiADQcDQgIAAaiIFNgIAIAUgBDYCACADQdTQgIAAaiADQcjQgIAAaiIENgIAIAQgBTYCACADQdDQgIAAaiAENgIAIANBIGoiA0GAAkcNAAsgAEF4IABrQQ9xQQAgAEEIakEPcRsiA2oiBCAGQUhqIgUgA2siA0EBcjYCBEEAQQAoAvDTgIAANgKk0ICAAEEAIAM2ApTQgIAAQQAgBDYCoNCAgAAgACAFakE4NgIEDAILIAMtAAxBCHENACAEIAVJDQAgBCAATw0AIARBeCAEa0EPcUEAIARBCGpBD3EbIgVqIgBBACgClNCAgAAgBmoiCyAFayIFQQFyNgIEIAMgCCAGajYCBEEAQQAoAvDTgIAANgKk0ICAAEEAIAU2ApTQgIAAQQAgADYCoNCAgAAgBCALakE4NgIEDAELAkAgAEEAKAKY0ICAACIITw0AQQAgADYCmNCAgAAgACEICyAAIAZqIQVByNOAgAAhAwJAAkACQAJAAkACQAJAA0AgAygCACAFRg0BIAMoAggiAw0ADAILCyADLQAMQQhxRQ0BC0HI04CAACEDA0ACQCADKAIAIgUgBEsNACAFIAMoAgRqIgUgBEsNAwsgAygCCCEDDAALCyADIAA2AgAgAyADKAIEIAZqNgIEIABBeCAAa0EPcUEAIABBCGpBD3EbaiILIAJBA3I2AgQgBUF4IAVrQQ9xQQAgBUEIakEPcRtqIgYgCyACaiICayEDAkAgBiAERw0AQQAgAjYCoNCAgABBAEEAKAKU0ICAACADaiIDNgKU0ICAACACIANBAXI2AgQMAwsCQCAGQQAoApzQgIAARw0AQQAgAjYCnNCAgABBAEEAKAKQ0ICAACADaiIDNgKQ0ICAACACIANBAXI2AgQgAiADaiADNgIADAMLAkAgBigCBCIEQQNxQQFHDQAgBEF4cSEHAkACQCAEQf8BSw0AIAYoAggiBSAEQQN2IghBA3RBsNCAgABqIgBGGgJAIAYoAgwiBCAFRw0AQQBBACgCiNCAgABBfiAId3E2AojQgIAADAILIAQgAEYaIAQgBTYCCCAFIAQ2AgwMAQsgBigCGCEJAkACQCAGKAIMIgAgBkYNACAGKAIIIgQgCEkaIAAgBDYCCCAEIAA2AgwMAQsCQCAGQRRqIgQoAgAiBQ0AIAZBEGoiBCgCACIFDQBBACEADAELA0AgBCEIIAUiAEEUaiIEKAIAIgUNACAAQRBqIQQgACgCECIFDQALIAhBADYCAAsgCUUNAAJAAkAgBiAGKAIcIgVBAnRBuNKAgABqIgQoAgBHDQAgBCAANgIAIAANAUEAQQAoAozQgIAAQX4gBXdxNgKM0ICAAAwCCyAJQRBBFCAJKAIQIAZGG2ogADYCACAARQ0BCyAAIAk2AhgCQCAGKAIQIgRFDQAgACAENgIQIAQgADYCGAsgBigCFCIERQ0AIABBFGogBDYCACAEIAA2AhgLIAcgA2ohAyAGIAdqIgYoAgQhBAsgBiAEQX5xNgIEIAIgA2ogAzYCACACIANBAXI2AgQCQCADQf8BSw0AIANBeHFBsNCAgABqIQQCQAJAQQAoAojQgIAAIgVBASADQQN2dCIDcQ0AQQAgBSADcjYCiNCAgAAgBCEDDAELIAQoAgghAwsgAyACNgIMIAQgAjYCCCACIAQ2AgwgAiADNgIIDAMLQR8hBAJAIANB////B0sNACADQQh2IgQgBEGA/j9qQRB2QQhxIgR0IgUgBUGA4B9qQRB2QQRxIgV0IgAgAEGAgA9qQRB2QQJxIgB0QQ92IAQgBXIgAHJrIgRBAXQgAyAEQRVqdkEBcXJBHGohBAsgAiAENgIcIAJCADcCECAEQQJ0QbjSgIAAaiEFAkBBACgCjNCAgAAiAEEBIAR0IghxDQAgBSACNgIAQQAgACAIcjYCjNCAgAAgAiAFNgIYIAIgAjYCCCACIAI2AgwMAwsgA0EAQRkgBEEBdmsgBEEfRht0IQQgBSgCACEAA0AgACIFKAIEQXhxIANGDQIgBEEddiEAIARBAXQhBCAFIABBBHFqQRBqIggoAgAiAA0ACyAIIAI2AgAgAiAFNgIYIAIgAjYCDCACIAI2AggMAgsgAEF4IABrQQ9xQQAgAEEIakEPcRsiA2oiCyAGQUhqIgggA2siA0EBcjYCBCAAIAhqQTg2AgQgBCAFQTcgBWtBD3FBACAFQUlqQQ9xG2pBQWoiCCAIIARBEGpJGyIIQSM2AgRBAEEAKALw04CAADYCpNCAgABBACADNgKU0ICAAEEAIAs2AqDQgIAAIAhBEGpBACkC0NOAgAA3AgAgCEEAKQLI04CAADcCCEEAIAhBCGo2AtDTgIAAQQAgBjYCzNOAgABBACAANgLI04CAAEEAQQA2AtTTgIAAIAhBJGohAwNAIANBBzYCACADQQRqIgMgBUkNAAsgCCAERg0DIAggCCgCBEF+cTYCBCAIIAggBGsiADYCACAEIABBAXI2AgQCQCAAQf8BSw0AIABBeHFBsNCAgABqIQMCQAJAQQAoAojQgIAAIgVBASAAQQN2dCIAcQ0AQQAgBSAAcjYCiNCAgAAgAyEFDAELIAMoAgghBQsgBSAENgIMIAMgBDYCCCAEIAM2AgwgBCAFNgIIDAQLQR8hAwJAIABB////B0sNACAAQQh2IgMgA0GA/j9qQRB2QQhxIgN0IgUgBUGA4B9qQRB2QQRxIgV0IgggCEGAgA9qQRB2QQJxIgh0QQ92IAMgBXIgCHJrIgNBAXQgACADQRVqdkEBcXJBHGohAwsgBCADNgIcIARCADcCECADQQJ0QbjSgIAAaiEFAkBBACgCjNCAgAAiCEEBIAN0IgZxDQAgBSAENgIAQQAgCCAGcjYCjNCAgAAgBCAFNgIYIAQgBDYCCCAEIAQ2AgwMBAsgAEEAQRkgA0EBdmsgA0EfRht0IQMgBSgCACEIA0AgCCIFKAIEQXhxIABGDQMgA0EddiEIIANBAXQhAyAFIAhBBHFqQRBqIgYoAgAiCA0ACyAGIAQ2AgAgBCAFNgIYIAQgBDYCDCAEIAQ2AggMAwsgBSgCCCIDIAI2AgwgBSACNgIIIAJBADYCGCACIAU2AgwgAiADNgIICyALQQhqIQMMBQsgBSgCCCIDIAQ2AgwgBSAENgIIIARBADYCGCAEIAU2AgwgBCADNgIIC0EAKAKU0ICAACIDIAJNDQBBACgCoNCAgAAiBCACaiIFIAMgAmsiA0EBcjYCBEEAIAM2ApTQgIAAQQAgBTYCoNCAgAAgBCACQQNyNgIEIARBCGohAwwDC0EAIQNBAEEwNgL404CAAAwCCwJAIAtFDQACQAJAIAggCCgCHCIFQQJ0QbjSgIAAaiIDKAIARw0AIAMgADYCACAADQFBACAHQX4gBXdxIgc2AozQgIAADAILIAtBEEEUIAsoAhAgCEYbaiAANgIAIABFDQELIAAgCzYCGAJAIAgoAhAiA0UNACAAIAM2AhAgAyAANgIYCyAIQRRqKAIAIgNFDQAgAEEUaiADNgIAIAMgADYCGAsCQAJAIARBD0sNACAIIAQgAmoiA0EDcjYCBCAIIANqIgMgAygCBEEBcjYCBAwBCyAIIAJqIgAgBEEBcjYCBCAIIAJBA3I2AgQgACAEaiAENgIAAkAgBEH/AUsNACAEQXhxQbDQgIAAaiEDAkACQEEAKAKI0ICAACIFQQEgBEEDdnQiBHENAEEAIAUgBHI2AojQgIAAIAMhBAwBCyADKAIIIQQLIAQgADYCDCADIAA2AgggACADNgIMIAAgBDYCCAwBC0EfIQMCQCAEQf///wdLDQAgBEEIdiIDIANBgP4/akEQdkEIcSIDdCIFIAVBgOAfakEQdkEEcSIFdCICIAJBgIAPakEQdkECcSICdEEPdiADIAVyIAJyayIDQQF0IAQgA0EVanZBAXFyQRxqIQMLIAAgAzYCHCAAQgA3AhAgA0ECdEG40oCAAGohBQJAIAdBASADdCICcQ0AIAUgADYCAEEAIAcgAnI2AozQgIAAIAAgBTYCGCAAIAA2AgggACAANgIMDAELIARBAEEZIANBAXZrIANBH0YbdCEDIAUoAgAhAgJAA0AgAiIFKAIEQXhxIARGDQEgA0EddiECIANBAXQhAyAFIAJBBHFqQRBqIgYoAgAiAg0ACyAGIAA2AgAgACAFNgIYIAAgADYCDCAAIAA2AggMAQsgBSgCCCIDIAA2AgwgBSAANgIIIABBADYCGCAAIAU2AgwgACADNgIICyAIQQhqIQMMAQsCQCAKRQ0AAkACQCAAIAAoAhwiBUECdEG40oCAAGoiAygCAEcNACADIAg2AgAgCA0BQQAgCUF+IAV3cTYCjNCAgAAMAgsgCkEQQRQgCigCECAARhtqIAg2AgAgCEUNAQsgCCAKNgIYAkAgACgCECIDRQ0AIAggAzYCECADIAg2AhgLIABBFGooAgAiA0UNACAIQRRqIAM2AgAgAyAINgIYCwJAAkAgBEEPSw0AIAAgBCACaiIDQQNyNgIEIAAgA2oiAyADKAIEQQFyNgIEDAELIAAgAmoiBSAEQQFyNgIEIAAgAkEDcjYCBCAFIARqIAQ2AgACQCAHRQ0AIAdBeHFBsNCAgABqIQJBACgCnNCAgAAhAwJAAkBBASAHQQN2dCIIIAZxDQBBACAIIAZyNgKI0ICAACACIQgMAQsgAigCCCEICyAIIAM2AgwgAiADNgIIIAMgAjYCDCADIAg2AggLQQAgBTYCnNCAgABBACAENgKQ0ICAAAsgAEEIaiEDCyABQRBqJICAgIAAIAMLCgAgABDJgICAAAviDQEHfwJAIABFDQAgAEF4aiIBIABBfGooAgAiAkF4cSIAaiEDAkAgAkEBcQ0AIAJBA3FFDQEgASABKAIAIgJrIgFBACgCmNCAgAAiBEkNASACIABqIQACQCABQQAoApzQgIAARg0AAkAgAkH/AUsNACABKAIIIgQgAkEDdiIFQQN0QbDQgIAAaiIGRhoCQCABKAIMIgIgBEcNAEEAQQAoAojQgIAAQX4gBXdxNgKI0ICAAAwDCyACIAZGGiACIAQ2AgggBCACNgIMDAILIAEoAhghBwJAAkAgASgCDCIGIAFGDQAgASgCCCICIARJGiAGIAI2AgggAiAGNgIMDAELAkAgAUEUaiICKAIAIgQNACABQRBqIgIoAgAiBA0AQQAhBgwBCwNAIAIhBSAEIgZBFGoiAigCACIEDQAgBkEQaiECIAYoAhAiBA0ACyAFQQA2AgALIAdFDQECQAJAIAEgASgCHCIEQQJ0QbjSgIAAaiICKAIARw0AIAIgBjYCACAGDQFBAEEAKAKM0ICAAEF+IAR3cTYCjNCAgAAMAwsgB0EQQRQgBygCECABRhtqIAY2AgAgBkUNAgsgBiAHNgIYAkAgASgCECICRQ0AIAYgAjYCECACIAY2AhgLIAEoAhQiAkUNASAGQRRqIAI2AgAgAiAGNgIYDAELIAMoAgQiAkEDcUEDRw0AIAMgAkF+cTYCBEEAIAA2ApDQgIAAIAEgAGogADYCACABIABBAXI2AgQPCyABIANPDQAgAygCBCICQQFxRQ0AAkACQCACQQJxDQACQCADQQAoAqDQgIAARw0AQQAgATYCoNCAgABBAEEAKAKU0ICAACAAaiIANgKU0ICAACABIABBAXI2AgQgAUEAKAKc0ICAAEcNA0EAQQA2ApDQgIAAQQBBADYCnNCAgAAPCwJAIANBACgCnNCAgABHDQBBACABNgKc0ICAAEEAQQAoApDQgIAAIABqIgA2ApDQgIAAIAEgAEEBcjYCBCABIABqIAA2AgAPCyACQXhxIABqIQACQAJAIAJB/wFLDQAgAygCCCIEIAJBA3YiBUEDdEGw0ICAAGoiBkYaAkAgAygCDCICIARHDQBBAEEAKAKI0ICAAEF+IAV3cTYCiNCAgAAMAgsgAiAGRhogAiAENgIIIAQgAjYCDAwBCyADKAIYIQcCQAJAIAMoAgwiBiADRg0AIAMoAggiAkEAKAKY0ICAAEkaIAYgAjYCCCACIAY2AgwMAQsCQCADQRRqIgIoAgAiBA0AIANBEGoiAigCACIEDQBBACEGDAELA0AgAiEFIAQiBkEUaiICKAIAIgQNACAGQRBqIQIgBigCECIEDQALIAVBADYCAAsgB0UNAAJAAkAgAyADKAIcIgRBAnRBuNKAgABqIgIoAgBHDQAgAiAGNgIAIAYNAUEAQQAoAozQgIAAQX4gBHdxNgKM0ICAAAwCCyAHQRBBFCAHKAIQIANGG2ogBjYCACAGRQ0BCyAGIAc2AhgCQCADKAIQIgJFDQAgBiACNgIQIAIgBjYCGAsgAygCFCICRQ0AIAZBFGogAjYCACACIAY2AhgLIAEgAGogADYCACABIABBAXI2AgQgAUEAKAKc0ICAAEcNAUEAIAA2ApDQgIAADwsgAyACQX5xNgIEIAEgAGogADYCACABIABBAXI2AgQLAkAgAEH/AUsNACAAQXhxQbDQgIAAaiECAkACQEEAKAKI0ICAACIEQQEgAEEDdnQiAHENAEEAIAQgAHI2AojQgIAAIAIhAAwBCyACKAIIIQALIAAgATYCDCACIAE2AgggASACNgIMIAEgADYCCA8LQR8hAgJAIABB////B0sNACAAQQh2IgIgAkGA/j9qQRB2QQhxIgJ0IgQgBEGA4B9qQRB2QQRxIgR0IgYgBkGAgA9qQRB2QQJxIgZ0QQ92IAIgBHIgBnJrIgJBAXQgACACQRVqdkEBcXJBHGohAgsgASACNgIcIAFCADcCECACQQJ0QbjSgIAAaiEEAkACQEEAKAKM0ICAACIGQQEgAnQiA3ENACAEIAE2AgBBACAGIANyNgKM0ICAACABIAQ2AhggASABNgIIIAEgATYCDAwBCyAAQQBBGSACQQF2ayACQR9GG3QhAiAEKAIAIQYCQANAIAYiBCgCBEF4cSAARg0BIAJBHXYhBiACQQF0IQIgBCAGQQRxakEQaiIDKAIAIgYNAAsgAyABNgIAIAEgBDYCGCABIAE2AgwgASABNgIIDAELIAQoAggiACABNgIMIAQgATYCCCABQQA2AhggASAENgIMIAEgADYCCAtBAEEAKAKo0ICAAEF/aiIBQX8gARs2AqjQgIAACwsEAAAAC04AAkAgAA0APwBBEHQPCwJAIABB//8DcQ0AIABBf0wNAAJAIABBEHZAACIAQX9HDQBBAEEwNgL404CAAEF/DwsgAEEQdA8LEMqAgIAAAAvyAgIDfwF+AkAgAkUNACAAIAE6AAAgAiAAaiIDQX9qIAE6AAAgAkEDSQ0AIAAgAToAAiAAIAE6AAEgA0F9aiABOgAAIANBfmogAToAACACQQdJDQAgACABOgADIANBfGogAToAACACQQlJDQAgAEEAIABrQQNxIgRqIgMgAUH/AXFBgYKECGwiATYCACADIAIgBGtBfHEiBGoiAkF8aiABNgIAIARBCUkNACADIAE2AgggAyABNgIEIAJBeGogATYCACACQXRqIAE2AgAgBEEZSQ0AIAMgATYCGCADIAE2AhQgAyABNgIQIAMgATYCDCACQXBqIAE2AgAgAkFsaiABNgIAIAJBaGogATYCACACQWRqIAE2AgAgBCADQQRxQRhyIgVrIgJBIEkNACABrUKBgICAEH4hBiADIAVqIQEDQCABIAY3AxggASAGNwMQIAEgBjcDCCABIAY3AwAgAUEgaiEBIAJBYGoiAkEfSw0ACwsgAAsLjkgBAEGACAuGSAEAAAACAAAAAwAAAAAAAAAAAAAABAAAAAUAAAAAAAAAAAAAAAYAAAAHAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASW52YWxpZCBjaGFyIGluIHVybCBxdWVyeQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2JvZHkAQ29udGVudC1MZW5ndGggb3ZlcmZsb3cAQ2h1bmsgc2l6ZSBvdmVyZmxvdwBSZXNwb25zZSBvdmVyZmxvdwBJbnZhbGlkIG1ldGhvZCBmb3IgSFRUUC94LnggcmVxdWVzdABJbnZhbGlkIG1ldGhvZCBmb3IgUlRTUC94LnggcmVxdWVzdABFeHBlY3RlZCBTT1VSQ0UgbWV0aG9kIGZvciBJQ0UveC54IHJlcXVlc3QASW52YWxpZCBjaGFyIGluIHVybCBmcmFnbWVudCBzdGFydABFeHBlY3RlZCBkb3QAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9zdGF0dXMASW52YWxpZCByZXNwb25zZSBzdGF0dXMASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucwBVc2VyIGNhbGxiYWNrIGVycm9yAGBvbl9yZXNldGAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2hlYWRlcmAgY2FsbGJhY2sgZXJyb3IAYG9uX21lc3NhZ2VfYmVnaW5gIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19leHRlbnNpb25fdmFsdWVgIGNhbGxiYWNrIGVycm9yAGBvbl9zdGF0dXNfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl92ZXJzaW9uX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fdXJsX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9oZWFkZXJfdmFsdWVfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9tZXNzYWdlX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fbWV0aG9kX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25faGVhZGVyX2ZpZWxkX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfZXh0ZW5zaW9uX25hbWVgIGNhbGxiYWNrIGVycm9yAFVuZXhwZWN0ZWQgY2hhciBpbiB1cmwgc2VydmVyAEludmFsaWQgaGVhZGVyIHZhbHVlIGNoYXIASW52YWxpZCBoZWFkZXIgZmllbGQgY2hhcgBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3ZlcnNpb24ASW52YWxpZCBtaW5vciB2ZXJzaW9uAEludmFsaWQgbWFqb3IgdmVyc2lvbgBFeHBlY3RlZCBzcGFjZSBhZnRlciB2ZXJzaW9uAEV4cGVjdGVkIENSTEYgYWZ0ZXIgdmVyc2lvbgBJbnZhbGlkIEhUVFAgdmVyc2lvbgBJbnZhbGlkIGhlYWRlciB0b2tlbgBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3VybABJbnZhbGlkIGNoYXJhY3RlcnMgaW4gdXJsAFVuZXhwZWN0ZWQgc3RhcnQgY2hhciBpbiB1cmwARG91YmxlIEAgaW4gdXJsAEVtcHR5IENvbnRlbnQtTGVuZ3RoAEludmFsaWQgY2hhcmFjdGVyIGluIENvbnRlbnQtTGVuZ3RoAER1cGxpY2F0ZSBDb250ZW50LUxlbmd0aABJbnZhbGlkIGNoYXIgaW4gdXJsIHBhdGgAQ29udGVudC1MZW5ndGggY2FuJ3QgYmUgcHJlc2VudCB3aXRoIFRyYW5zZmVyLUVuY29kaW5nAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIHNpemUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9oZWFkZXJfdmFsdWUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9jaHVua19leHRlbnNpb25fdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyB2YWx1ZQBNaXNzaW5nIGV4cGVjdGVkIExGIGFmdGVyIGhlYWRlciB2YWx1ZQBJbnZhbGlkIGBUcmFuc2Zlci1FbmNvZGluZ2AgaGVhZGVyIHZhbHVlAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgcXVvdGUgdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyBxdW90ZWQgdmFsdWUAUGF1c2VkIGJ5IG9uX2hlYWRlcnNfY29tcGxldGUASW52YWxpZCBFT0Ygc3RhdGUAb25fcmVzZXQgcGF1c2UAb25fY2h1bmtfaGVhZGVyIHBhdXNlAG9uX21lc3NhZ2VfYmVnaW4gcGF1c2UAb25fY2h1bmtfZXh0ZW5zaW9uX3ZhbHVlIHBhdXNlAG9uX3N0YXR1c19jb21wbGV0ZSBwYXVzZQBvbl92ZXJzaW9uX2NvbXBsZXRlIHBhdXNlAG9uX3VybF9jb21wbGV0ZSBwYXVzZQBvbl9jaHVua19jb21wbGV0ZSBwYXVzZQBvbl9oZWFkZXJfdmFsdWVfY29tcGxldGUgcGF1c2UAb25fbWVzc2FnZV9jb21wbGV0ZSBwYXVzZQBvbl9tZXRob2RfY29tcGxldGUgcGF1c2UAb25faGVhZGVyX2ZpZWxkX2NvbXBsZXRlIHBhdXNlAG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lIHBhdXNlAFVuZXhwZWN0ZWQgc3BhY2UgYWZ0ZXIgc3RhcnQgbGluZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgbmFtZQBQYXVzZSBvbiBDT05ORUNUL1VwZ3JhZGUAUGF1c2Ugb24gUFJJL1VwZ3JhZGUARXhwZWN0ZWQgSFRUUC8yIENvbm5lY3Rpb24gUHJlZmFjZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX21ldGhvZABFeHBlY3RlZCBzcGFjZSBhZnRlciBtZXRob2QAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9oZWFkZXJfZmllbGQAUGF1c2VkAEludmFsaWQgd29yZCBlbmNvdW50ZXJlZABJbnZhbGlkIG1ldGhvZCBlbmNvdW50ZXJlZABVbmV4cGVjdGVkIGNoYXIgaW4gdXJsIHNjaGVtYQBSZXF1ZXN0IGhhcyBpbnZhbGlkIGBUcmFuc2Zlci1FbmNvZGluZ2AAU1dJVENIX1BST1hZAFVTRV9QUk9YWQBNS0FDVElWSVRZAFVOUFJPQ0VTU0FCTEVfRU5USVRZAENPUFkATU9WRURfUEVSTUFORU5UTFkAVE9PX0VBUkxZAE5PVElGWQBGQUlMRURfREVQRU5ERU5DWQBCQURfR0FURVdBWQBQTEFZAFBVVABDSEVDS09VVABHQVRFV0FZX1RJTUVPVVQAUkVRVUVTVF9USU1FT1VUAE5FVFdPUktfQ09OTkVDVF9USU1FT1VUAENPTk5FQ1RJT05fVElNRU9VVABMT0dJTl9USU1FT1VUAE5FVFdPUktfUkVBRF9USU1FT1VUAFBPU1QATUlTRElSRUNURURfUkVRVUVTVABDTElFTlRfQ0xPU0VEX1JFUVVFU1QAQ0xJRU5UX0NMT1NFRF9MT0FEX0JBTEFOQ0VEX1JFUVVFU1QAQkFEX1JFUVVFU1QASFRUUF9SRVFVRVNUX1NFTlRfVE9fSFRUUFNfUE9SVABSRVBPUlQASU1fQV9URUFQT1QAUkVTRVRfQ09OVEVOVABOT19DT05URU5UAFBBUlRJQUxfQ09OVEVOVABIUEVfSU5WQUxJRF9DT05TVEFOVABIUEVfQ0JfUkVTRVQAR0VUAEhQRV9TVFJJQ1QAQ09ORkxJQ1QAVEVNUE9SQVJZX1JFRElSRUNUAFBFUk1BTkVOVF9SRURJUkVDVABDT05ORUNUAE1VTFRJX1NUQVRVUwBIUEVfSU5WQUxJRF9TVEFUVVMAVE9PX01BTllfUkVRVUVTVFMARUFSTFlfSElOVFMAVU5BVkFJTEFCTEVfRk9SX0xFR0FMX1JFQVNPTlMAT1BUSU9OUwBTV0lUQ0hJTkdfUFJPVE9DT0xTAFZBUklBTlRfQUxTT19ORUdPVElBVEVTAE1VTFRJUExFX0NIT0lDRVMASU5URVJOQUxfU0VSVkVSX0VSUk9SAFdFQl9TRVJWRVJfVU5LTk9XTl9FUlJPUgBSQUlMR1VOX0VSUk9SAElERU5USVRZX1BST1ZJREVSX0FVVEhFTlRJQ0FUSU9OX0VSUk9SAFNTTF9DRVJUSUZJQ0FURV9FUlJPUgBJTlZBTElEX1hfRk9SV0FSREVEX0ZPUgBTRVRfUEFSQU1FVEVSAEdFVF9QQVJBTUVURVIASFBFX1VTRVIAU0VFX09USEVSAEhQRV9DQl9DSFVOS19IRUFERVIATUtDQUxFTkRBUgBTRVRVUABXRUJfU0VSVkVSX0lTX0RPV04AVEVBUkRPV04ASFBFX0NMT1NFRF9DT05ORUNUSU9OAEhFVVJJU1RJQ19FWFBJUkFUSU9OAERJU0NPTk5FQ1RFRF9PUEVSQVRJT04ATk9OX0FVVEhPUklUQVRJVkVfSU5GT1JNQVRJT04ASFBFX0lOVkFMSURfVkVSU0lPTgBIUEVfQ0JfTUVTU0FHRV9CRUdJTgBTSVRFX0lTX0ZST1pFTgBIUEVfSU5WQUxJRF9IRUFERVJfVE9LRU4ASU5WQUxJRF9UT0tFTgBGT1JCSURERU4ARU5IQU5DRV9ZT1VSX0NBTE0ASFBFX0lOVkFMSURfVVJMAEJMT0NLRURfQllfUEFSRU5UQUxfQ09OVFJPTABNS0NPTABBQ0wASFBFX0lOVEVSTkFMAFJFUVVFU1RfSEVBREVSX0ZJRUxEU19UT09fTEFSR0VfVU5PRkZJQ0lBTABIUEVfT0sAVU5MSU5LAFVOTE9DSwBQUkkAUkVUUllfV0lUSABIUEVfSU5WQUxJRF9DT05URU5UX0xFTkdUSABIUEVfVU5FWFBFQ1RFRF9DT05URU5UX0xFTkdUSABGTFVTSABQUk9QUEFUQ0gATS1TRUFSQ0gAVVJJX1RPT19MT05HAFBST0NFU1NJTkcATUlTQ0VMTEFORU9VU19QRVJTSVNURU5UX1dBUk5JTkcATUlTQ0VMTEFORU9VU19XQVJOSU5HAEhQRV9JTlZBTElEX1RSQU5TRkVSX0VOQ09ESU5HAEV4cGVjdGVkIENSTEYASFBFX0lOVkFMSURfQ0hVTktfU0laRQBNT1ZFAENPTlRJTlVFAEhQRV9DQl9TVEFUVVNfQ09NUExFVEUASFBFX0NCX0hFQURFUlNfQ09NUExFVEUASFBFX0NCX1ZFUlNJT05fQ09NUExFVEUASFBFX0NCX1VSTF9DT01QTEVURQBIUEVfQ0JfQ0hVTktfQ09NUExFVEUASFBFX0NCX0hFQURFUl9WQUxVRV9DT01QTEVURQBIUEVfQ0JfQ0hVTktfRVhURU5TSU9OX1ZBTFVFX0NPTVBMRVRFAEhQRV9DQl9DSFVOS19FWFRFTlNJT05fTkFNRV9DT01QTEVURQBIUEVfQ0JfTUVTU0FHRV9DT01QTEVURQBIUEVfQ0JfTUVUSE9EX0NPTVBMRVRFAEhQRV9DQl9IRUFERVJfRklFTERfQ09NUExFVEUAREVMRVRFAEhQRV9JTlZBTElEX0VPRl9TVEFURQBJTlZBTElEX1NTTF9DRVJUSUZJQ0FURQBQQVVTRQBOT19SRVNQT05TRQBVTlNVUFBPUlRFRF9NRURJQV9UWVBFAEdPTkUATk9UX0FDQ0VQVEFCTEUAU0VSVklDRV9VTkFWQUlMQUJMRQBSQU5HRV9OT1RfU0FUSVNGSUFCTEUAT1JJR0lOX0lTX1VOUkVBQ0hBQkxFAFJFU1BPTlNFX0lTX1NUQUxFAFBVUkdFAE1FUkdFAFJFUVVFU1RfSEVBREVSX0ZJRUxEU19UT09fTEFSR0UAUkVRVUVTVF9IRUFERVJfVE9PX0xBUkdFAFBBWUxPQURfVE9PX0xBUkdFAElOU1VGRklDSUVOVF9TVE9SQUdFAEhQRV9QQVVTRURfVVBHUkFERQBIUEVfUEFVU0VEX0gyX1VQR1JBREUAU09VUkNFAEFOTk9VTkNFAFRSQUNFAEhQRV9VTkVYUEVDVEVEX1NQQUNFAERFU0NSSUJFAFVOU1VCU0NSSUJFAFJFQ09SRABIUEVfSU5WQUxJRF9NRVRIT0QATk9UX0ZPVU5EAFBST1BGSU5EAFVOQklORABSRUJJTkQAVU5BVVRIT1JJWkVEAE1FVEhPRF9OT1RfQUxMT1dFRABIVFRQX1ZFUlNJT05fTk9UX1NVUFBPUlRFRABBTFJFQURZX1JFUE9SVEVEAEFDQ0VQVEVEAE5PVF9JTVBMRU1FTlRFRABMT09QX0RFVEVDVEVEAEhQRV9DUl9FWFBFQ1RFRABIUEVfTEZfRVhQRUNURUQAQ1JFQVRFRABJTV9VU0VEAEhQRV9QQVVTRUQAVElNRU9VVF9PQ0NVUkVEAFBBWU1FTlRfUkVRVUlSRUQAUFJFQ09ORElUSU9OX1JFUVVJUkVEAFBST1hZX0FVVEhFTlRJQ0FUSU9OX1JFUVVJUkVEAE5FVFdPUktfQVVUSEVOVElDQVRJT05fUkVRVUlSRUQATEVOR1RIX1JFUVVJUkVEAFNTTF9DRVJUSUZJQ0FURV9SRVFVSVJFRABVUEdSQURFX1JFUVVJUkVEAFBBR0VfRVhQSVJFRABQUkVDT05ESVRJT05fRkFJTEVEAEVYUEVDVEFUSU9OX0ZBSUxFRABSRVZBTElEQVRJT05fRkFJTEVEAFNTTF9IQU5EU0hBS0VfRkFJTEVEAExPQ0tFRABUUkFOU0ZPUk1BVElPTl9BUFBMSUVEAE5PVF9NT0RJRklFRABOT1RfRVhURU5ERUQAQkFORFdJRFRIX0xJTUlUX0VYQ0VFREVEAFNJVEVfSVNfT1ZFUkxPQURFRABIRUFEAEV4cGVjdGVkIEhUVFAvAABeEwAAJhMAADAQAADwFwAAnRMAABUSAAA5FwAA8BIAAAoQAAB1EgAArRIAAIITAABPFAAAfxAAAKAVAAAjFAAAiRIAAIsUAABNFQAA1BEAAM8UAAAQGAAAyRYAANwWAADBEQAA4BcAALsUAAB0FAAAfBUAAOUUAAAIFwAAHxAAAGUVAACjFAAAKBUAAAIVAACZFQAALBAAAIsZAABPDwAA1A4AAGoQAADOEAAAAhcAAIkOAABuEwAAHBMAAGYUAABWFwAAwRMAAM0TAABsEwAAaBcAAGYXAABfFwAAIhMAAM4PAABpDgAA2A4AAGMWAADLEwAAqg4AACgXAAAmFwAAxRMAAF0WAADoEQAAZxMAAGUTAADyFgAAcxMAAB0XAAD5FgAA8xEAAM8OAADOFQAADBIAALMRAAClEQAAYRAAADIXAAC7EwAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAgEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgMCAgICAgAAAgIAAgIAAgICAgICAgICAgAEAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAAIAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAgICAgIAAAICAAICAAICAgICAgICAgIAAwAEAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgACAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABsb3NlZWVwLWFsaXZlAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAgEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQFjaHVua2VkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQABAQEBAQAAAQEAAQEAAQEBAQEBAQEBAQAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGVjdGlvbmVudC1sZW5ndGhvbnJveHktY29ubmVjdGlvbgAAAAAAAAAAAAAAAAAAAHJhbnNmZXItZW5jb2RpbmdwZ3JhZGUNCg0KDQpTTQ0KDQpUVFAvQ0UvVFNQLwAAAAAAAAAAAAAAAAECAAEDAAAAAAAAAAAAAAAAAAAAAAAABAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAABAgABAwAAAAAAAAAAAAAAAAAAAAAAAAQBAQUBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAQAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAABAAACAAAAAAAAAAAAAAAAAAAAAAAAAwQAAAQEBAQEBAQEBAQEBQQEBAQEBAQEBAQEBAAEAAYHBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQABAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAgAAAAACAAAAAAAAAAAAAAAAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE5PVU5DRUVDS09VVE5FQ1RFVEVDUklCRUxVU0hFVEVBRFNFQVJDSFJHRUNUSVZJVFlMRU5EQVJWRU9USUZZUFRJT05TQ0hTRUFZU1RBVENIR0VPUkRJUkVDVE9SVFJDSFBBUkFNRVRFUlVSQ0VCU0NSSUJFQVJET1dOQUNFSU5ETktDS1VCU0NSSUJFSFRUUC9BRFRQLw==' + + +/***/ }), + +/***/ 172: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.enumToMap = void 0; +function enumToMap(obj) { + const res = {}; + Object.keys(obj).forEach((key) => { + const value = obj[key]; + if (typeof value === 'number') { + res[key] = value; + } + }); + return res; +} +exports.enumToMap = enumToMap; +//# sourceMappingURL=utils.js.map + +/***/ }), + +/***/ 7501: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { kClients } = __nccwpck_require__(6443) +const Agent = __nccwpck_require__(9965) +const { + kAgent, + kMockAgentSet, + kMockAgentGet, + kDispatches, + kIsMockActive, + kNetConnect, + kGetNetConnect, + kOptions, + kFactory +} = __nccwpck_require__(1117) +const MockClient = __nccwpck_require__(7365) +const MockPool = __nccwpck_require__(4004) +const { matchValue, buildMockOptions } = __nccwpck_require__(3397) +const { InvalidArgumentError, UndiciError } = __nccwpck_require__(8707) +const Dispatcher = __nccwpck_require__(992) +const Pluralizer = __nccwpck_require__(1529) +const PendingInterceptorsFormatter = __nccwpck_require__(6142) + +class FakeWeakRef { + constructor (value) { + this.value = value + } + + deref () { + return this.value + } +} + +class MockAgent extends Dispatcher { + constructor (opts) { + super(opts) + + this[kNetConnect] = true + this[kIsMockActive] = true + + // Instantiate Agent and encapsulate + if ((opts && opts.agent && typeof opts.agent.dispatch !== 'function')) { + throw new InvalidArgumentError('Argument opts.agent must implement Agent') + } + const agent = opts && opts.agent ? opts.agent : new Agent(opts) + this[kAgent] = agent + + this[kClients] = agent[kClients] + this[kOptions] = buildMockOptions(opts) + } + + get (origin) { + let dispatcher = this[kMockAgentGet](origin) + + if (!dispatcher) { + dispatcher = this[kFactory](origin) + this[kMockAgentSet](origin, dispatcher) + } + return dispatcher + } + + dispatch (opts, handler) { + // Call MockAgent.get to perform additional setup before dispatching as normal + this.get(opts.origin) + return this[kAgent].dispatch(opts, handler) + } + + async close () { + await this[kAgent].close() + this[kClients].clear() + } + + deactivate () { + this[kIsMockActive] = false + } + + activate () { + this[kIsMockActive] = true + } + + enableNetConnect (matcher) { + if (typeof matcher === 'string' || typeof matcher === 'function' || matcher instanceof RegExp) { + if (Array.isArray(this[kNetConnect])) { + this[kNetConnect].push(matcher) + } else { + this[kNetConnect] = [matcher] + } + } else if (typeof matcher === 'undefined') { + this[kNetConnect] = true + } else { + throw new InvalidArgumentError('Unsupported matcher. Must be one of String|Function|RegExp.') + } + } + + disableNetConnect () { + this[kNetConnect] = false + } + + // This is required to bypass issues caused by using global symbols - see: + // https://github.com/nodejs/undici/issues/1447 + get isMockActive () { + return this[kIsMockActive] + } + + [kMockAgentSet] (origin, dispatcher) { + this[kClients].set(origin, new FakeWeakRef(dispatcher)) + } + + [kFactory] (origin) { + const mockOptions = Object.assign({ agent: this }, this[kOptions]) + return this[kOptions] && this[kOptions].connections === 1 + ? new MockClient(origin, mockOptions) + : new MockPool(origin, mockOptions) + } + + [kMockAgentGet] (origin) { + // First check if we can immediately find it + const ref = this[kClients].get(origin) + if (ref) { + return ref.deref() + } + + // If the origin is not a string create a dummy parent pool and return to user + if (typeof origin !== 'string') { + const dispatcher = this[kFactory]('http://localhost:9999') + this[kMockAgentSet](origin, dispatcher) + return dispatcher + } + + // If we match, create a pool and assign the same dispatches + for (const [keyMatcher, nonExplicitRef] of Array.from(this[kClients])) { + const nonExplicitDispatcher = nonExplicitRef.deref() + if (nonExplicitDispatcher && typeof keyMatcher !== 'string' && matchValue(keyMatcher, origin)) { + const dispatcher = this[kFactory](origin) + this[kMockAgentSet](origin, dispatcher) + dispatcher[kDispatches] = nonExplicitDispatcher[kDispatches] + return dispatcher + } + } + } + + [kGetNetConnect] () { + return this[kNetConnect] + } + + pendingInterceptors () { + const mockAgentClients = this[kClients] + + return Array.from(mockAgentClients.entries()) + .flatMap(([origin, scope]) => scope.deref()[kDispatches].map(dispatch => ({ ...dispatch, origin }))) + .filter(({ pending }) => pending) + } + + assertNoPendingInterceptors ({ pendingInterceptorsFormatter = new PendingInterceptorsFormatter() } = {}) { + const pending = this.pendingInterceptors() + + if (pending.length === 0) { + return + } + + const pluralizer = new Pluralizer('interceptor', 'interceptors').pluralize(pending.length) + + throw new UndiciError(` +${pluralizer.count} ${pluralizer.noun} ${pluralizer.is} pending: + +${pendingInterceptorsFormatter.format(pending)} +`.trim()) + } +} + +module.exports = MockAgent + + +/***/ }), + +/***/ 7365: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { promisify } = __nccwpck_require__(9023) +const Client = __nccwpck_require__(6197) +const { buildMockDispatch } = __nccwpck_require__(3397) +const { + kDispatches, + kMockAgent, + kClose, + kOriginalClose, + kOrigin, + kOriginalDispatch, + kConnected +} = __nccwpck_require__(1117) +const { MockInterceptor } = __nccwpck_require__(1511) +const Symbols = __nccwpck_require__(6443) +const { InvalidArgumentError } = __nccwpck_require__(8707) + +/** + * MockClient provides an API that extends the Client to influence the mockDispatches. + */ +class MockClient extends Client { + constructor (origin, opts) { + super(origin, opts) + + if (!opts || !opts.agent || typeof opts.agent.dispatch !== 'function') { + throw new InvalidArgumentError('Argument opts.agent must implement Agent') + } + + this[kMockAgent] = opts.agent + this[kOrigin] = origin + this[kDispatches] = [] + this[kConnected] = 1 + this[kOriginalDispatch] = this.dispatch + this[kOriginalClose] = this.close.bind(this) + + this.dispatch = buildMockDispatch.call(this) + this.close = this[kClose] + } + + get [Symbols.kConnected] () { + return this[kConnected] + } + + /** + * Sets up the base interceptor for mocking replies from undici. + */ + intercept (opts) { + return new MockInterceptor(opts, this[kDispatches]) + } + + async [kClose] () { + await promisify(this[kOriginalClose])() + this[kConnected] = 0 + this[kMockAgent][Symbols.kClients].delete(this[kOrigin]) + } +} + +module.exports = MockClient + + +/***/ }), + +/***/ 2429: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { UndiciError } = __nccwpck_require__(8707) + +class MockNotMatchedError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, MockNotMatchedError) + this.name = 'MockNotMatchedError' + this.message = message || 'The request does not match any registered mock dispatches' + this.code = 'UND_MOCK_ERR_MOCK_NOT_MATCHED' + } +} + +module.exports = { + MockNotMatchedError +} + + +/***/ }), + +/***/ 1511: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { getResponseData, buildKey, addMockDispatch } = __nccwpck_require__(3397) +const { + kDispatches, + kDispatchKey, + kDefaultHeaders, + kDefaultTrailers, + kContentLength, + kMockDispatch +} = __nccwpck_require__(1117) +const { InvalidArgumentError } = __nccwpck_require__(8707) +const { buildURL } = __nccwpck_require__(3440) + +/** + * Defines the scope API for an interceptor reply + */ +class MockScope { + constructor (mockDispatch) { + this[kMockDispatch] = mockDispatch + } + + /** + * Delay a reply by a set amount in ms. + */ + delay (waitInMs) { + if (typeof waitInMs !== 'number' || !Number.isInteger(waitInMs) || waitInMs <= 0) { + throw new InvalidArgumentError('waitInMs must be a valid integer > 0') + } + + this[kMockDispatch].delay = waitInMs + return this + } + + /** + * For a defined reply, never mark as consumed. + */ + persist () { + this[kMockDispatch].persist = true + return this + } + + /** + * Allow one to define a reply for a set amount of matching requests. + */ + times (repeatTimes) { + if (typeof repeatTimes !== 'number' || !Number.isInteger(repeatTimes) || repeatTimes <= 0) { + throw new InvalidArgumentError('repeatTimes must be a valid integer > 0') + } + + this[kMockDispatch].times = repeatTimes + return this + } +} + +/** + * Defines an interceptor for a Mock + */ +class MockInterceptor { + constructor (opts, mockDispatches) { + if (typeof opts !== 'object') { + throw new InvalidArgumentError('opts must be an object') + } + if (typeof opts.path === 'undefined') { + throw new InvalidArgumentError('opts.path must be defined') + } + if (typeof opts.method === 'undefined') { + opts.method = 'GET' + } + // See https://github.com/nodejs/undici/issues/1245 + // As per RFC 3986, clients are not supposed to send URI + // fragments to servers when they retrieve a document, + if (typeof opts.path === 'string') { + if (opts.query) { + opts.path = buildURL(opts.path, opts.query) + } else { + // Matches https://github.com/nodejs/undici/blob/main/lib/fetch/index.js#L1811 + const parsedURL = new URL(opts.path, 'data://') + opts.path = parsedURL.pathname + parsedURL.search + } + } + if (typeof opts.method === 'string') { + opts.method = opts.method.toUpperCase() + } + + this[kDispatchKey] = buildKey(opts) + this[kDispatches] = mockDispatches + this[kDefaultHeaders] = {} + this[kDefaultTrailers] = {} + this[kContentLength] = false + } + + createMockScopeDispatchData (statusCode, data, responseOptions = {}) { + const responseData = getResponseData(data) + const contentLength = this[kContentLength] ? { 'content-length': responseData.length } : {} + const headers = { ...this[kDefaultHeaders], ...contentLength, ...responseOptions.headers } + const trailers = { ...this[kDefaultTrailers], ...responseOptions.trailers } + + return { statusCode, data, headers, trailers } + } + + validateReplyParameters (statusCode, data, responseOptions) { + if (typeof statusCode === 'undefined') { + throw new InvalidArgumentError('statusCode must be defined') + } + if (typeof data === 'undefined') { + throw new InvalidArgumentError('data must be defined') + } + if (typeof responseOptions !== 'object') { + throw new InvalidArgumentError('responseOptions must be an object') + } + } + + /** + * Mock an undici request with a defined reply. + */ + reply (replyData) { + // Values of reply aren't available right now as they + // can only be available when the reply callback is invoked. + if (typeof replyData === 'function') { + // We'll first wrap the provided callback in another function, + // this function will properly resolve the data from the callback + // when invoked. + const wrappedDefaultsCallback = (opts) => { + // Our reply options callback contains the parameter for statusCode, data and options. + const resolvedData = replyData(opts) + + // Check if it is in the right format + if (typeof resolvedData !== 'object') { + throw new InvalidArgumentError('reply options callback must return an object') + } + + const { statusCode, data = '', responseOptions = {} } = resolvedData + this.validateReplyParameters(statusCode, data, responseOptions) + // Since the values can be obtained immediately we return them + // from this higher order function that will be resolved later. + return { + ...this.createMockScopeDispatchData(statusCode, data, responseOptions) + } + } + + // Add usual dispatch data, but this time set the data parameter to function that will eventually provide data. + const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], wrappedDefaultsCallback) + return new MockScope(newMockDispatch) + } + + // We can have either one or three parameters, if we get here, + // we should have 1-3 parameters. So we spread the arguments of + // this function to obtain the parameters, since replyData will always + // just be the statusCode. + const [statusCode, data = '', responseOptions = {}] = [...arguments] + this.validateReplyParameters(statusCode, data, responseOptions) + + // Send in-already provided data like usual + const dispatchData = this.createMockScopeDispatchData(statusCode, data, responseOptions) + const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], dispatchData) + return new MockScope(newMockDispatch) + } + + /** + * Mock an undici request with a defined error. + */ + replyWithError (error) { + if (typeof error === 'undefined') { + throw new InvalidArgumentError('error must be defined') + } + + const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], { error }) + return new MockScope(newMockDispatch) + } + + /** + * Set default reply headers on the interceptor for subsequent replies + */ + defaultReplyHeaders (headers) { + if (typeof headers === 'undefined') { + throw new InvalidArgumentError('headers must be defined') + } + + this[kDefaultHeaders] = headers + return this + } + + /** + * Set default reply trailers on the interceptor for subsequent replies + */ + defaultReplyTrailers (trailers) { + if (typeof trailers === 'undefined') { + throw new InvalidArgumentError('trailers must be defined') + } + + this[kDefaultTrailers] = trailers + return this + } + + /** + * Set reply content length header for replies on the interceptor + */ + replyContentLength () { + this[kContentLength] = true + return this + } +} + +module.exports.MockInterceptor = MockInterceptor +module.exports.MockScope = MockScope + + +/***/ }), + +/***/ 4004: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { promisify } = __nccwpck_require__(9023) +const Pool = __nccwpck_require__(5076) +const { buildMockDispatch } = __nccwpck_require__(3397) +const { + kDispatches, + kMockAgent, + kClose, + kOriginalClose, + kOrigin, + kOriginalDispatch, + kConnected +} = __nccwpck_require__(1117) +const { MockInterceptor } = __nccwpck_require__(1511) +const Symbols = __nccwpck_require__(6443) +const { InvalidArgumentError } = __nccwpck_require__(8707) + +/** + * MockPool provides an API that extends the Pool to influence the mockDispatches. + */ +class MockPool extends Pool { + constructor (origin, opts) { + super(origin, opts) + + if (!opts || !opts.agent || typeof opts.agent.dispatch !== 'function') { + throw new InvalidArgumentError('Argument opts.agent must implement Agent') + } + + this[kMockAgent] = opts.agent + this[kOrigin] = origin + this[kDispatches] = [] + this[kConnected] = 1 + this[kOriginalDispatch] = this.dispatch + this[kOriginalClose] = this.close.bind(this) + + this.dispatch = buildMockDispatch.call(this) + this.close = this[kClose] + } + + get [Symbols.kConnected] () { + return this[kConnected] + } + + /** + * Sets up the base interceptor for mocking replies from undici. + */ + intercept (opts) { + return new MockInterceptor(opts, this[kDispatches]) + } + + async [kClose] () { + await promisify(this[kOriginalClose])() + this[kConnected] = 0 + this[kMockAgent][Symbols.kClients].delete(this[kOrigin]) + } +} + +module.exports = MockPool + + +/***/ }), + +/***/ 1117: +/***/ ((module) => { + +"use strict"; + + +module.exports = { + kAgent: Symbol('agent'), + kOptions: Symbol('options'), + kFactory: Symbol('factory'), + kDispatches: Symbol('dispatches'), + kDispatchKey: Symbol('dispatch key'), + kDefaultHeaders: Symbol('default headers'), + kDefaultTrailers: Symbol('default trailers'), + kContentLength: Symbol('content length'), + kMockAgent: Symbol('mock agent'), + kMockAgentSet: Symbol('mock agent set'), + kMockAgentGet: Symbol('mock agent get'), + kMockDispatch: Symbol('mock dispatch'), + kClose: Symbol('close'), + kOriginalClose: Symbol('original agent close'), + kOrigin: Symbol('origin'), + kIsMockActive: Symbol('is mock active'), + kNetConnect: Symbol('net connect'), + kGetNetConnect: Symbol('get net connect'), + kConnected: Symbol('connected') +} + + +/***/ }), + +/***/ 3397: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { MockNotMatchedError } = __nccwpck_require__(2429) +const { + kDispatches, + kMockAgent, + kOriginalDispatch, + kOrigin, + kGetNetConnect +} = __nccwpck_require__(1117) +const { buildURL, nop } = __nccwpck_require__(3440) +const { STATUS_CODES } = __nccwpck_require__(8611) +const { + types: { + isPromise + } +} = __nccwpck_require__(9023) + +function matchValue (match, value) { + if (typeof match === 'string') { + return match === value + } + if (match instanceof RegExp) { + return match.test(value) + } + if (typeof match === 'function') { + return match(value) === true + } + return false +} + +function lowerCaseEntries (headers) { + return Object.fromEntries( + Object.entries(headers).map(([headerName, headerValue]) => { + return [headerName.toLocaleLowerCase(), headerValue] + }) + ) +} + +/** + * @param {import('../../index').Headers|string[]|Record} headers + * @param {string} key + */ +function getHeaderByName (headers, key) { + if (Array.isArray(headers)) { + for (let i = 0; i < headers.length; i += 2) { + if (headers[i].toLocaleLowerCase() === key.toLocaleLowerCase()) { + return headers[i + 1] + } + } + + return undefined + } else if (typeof headers.get === 'function') { + return headers.get(key) + } else { + return lowerCaseEntries(headers)[key.toLocaleLowerCase()] + } +} + +/** @param {string[]} headers */ +function buildHeadersFromArray (headers) { // fetch HeadersList + const clone = headers.slice() + const entries = [] + for (let index = 0; index < clone.length; index += 2) { + entries.push([clone[index], clone[index + 1]]) + } + return Object.fromEntries(entries) +} + +function matchHeaders (mockDispatch, headers) { + if (typeof mockDispatch.headers === 'function') { + if (Array.isArray(headers)) { // fetch HeadersList + headers = buildHeadersFromArray(headers) + } + return mockDispatch.headers(headers ? lowerCaseEntries(headers) : {}) + } + if (typeof mockDispatch.headers === 'undefined') { + return true + } + if (typeof headers !== 'object' || typeof mockDispatch.headers !== 'object') { + return false + } + + for (const [matchHeaderName, matchHeaderValue] of Object.entries(mockDispatch.headers)) { + const headerValue = getHeaderByName(headers, matchHeaderName) + + if (!matchValue(matchHeaderValue, headerValue)) { + return false + } + } + return true +} + +function safeUrl (path) { + if (typeof path !== 'string') { + return path + } + + const pathSegments = path.split('?') + + if (pathSegments.length !== 2) { + return path + } + + const qp = new URLSearchParams(pathSegments.pop()) + qp.sort() + return [...pathSegments, qp.toString()].join('?') +} + +function matchKey (mockDispatch, { path, method, body, headers }) { + const pathMatch = matchValue(mockDispatch.path, path) + const methodMatch = matchValue(mockDispatch.method, method) + const bodyMatch = typeof mockDispatch.body !== 'undefined' ? matchValue(mockDispatch.body, body) : true + const headersMatch = matchHeaders(mockDispatch, headers) + return pathMatch && methodMatch && bodyMatch && headersMatch +} + +function getResponseData (data) { + if (Buffer.isBuffer(data)) { + return data + } else if (typeof data === 'object') { + return JSON.stringify(data) + } else { + return data.toString() + } +} + +function getMockDispatch (mockDispatches, key) { + const basePath = key.query ? buildURL(key.path, key.query) : key.path + const resolvedPath = typeof basePath === 'string' ? safeUrl(basePath) : basePath + + // Match path + let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(safeUrl(path), resolvedPath)) + if (matchedMockDispatches.length === 0) { + throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`) + } + + // Match method + matchedMockDispatches = matchedMockDispatches.filter(({ method }) => matchValue(method, key.method)) + if (matchedMockDispatches.length === 0) { + throw new MockNotMatchedError(`Mock dispatch not matched for method '${key.method}'`) + } + + // Match body + matchedMockDispatches = matchedMockDispatches.filter(({ body }) => typeof body !== 'undefined' ? matchValue(body, key.body) : true) + if (matchedMockDispatches.length === 0) { + throw new MockNotMatchedError(`Mock dispatch not matched for body '${key.body}'`) + } + + // Match headers + matchedMockDispatches = matchedMockDispatches.filter((mockDispatch) => matchHeaders(mockDispatch, key.headers)) + if (matchedMockDispatches.length === 0) { + throw new MockNotMatchedError(`Mock dispatch not matched for headers '${typeof key.headers === 'object' ? JSON.stringify(key.headers) : key.headers}'`) + } + + return matchedMockDispatches[0] +} + +function addMockDispatch (mockDispatches, key, data) { + const baseData = { timesInvoked: 0, times: 1, persist: false, consumed: false } + const replyData = typeof data === 'function' ? { callback: data } : { ...data } + const newMockDispatch = { ...baseData, ...key, pending: true, data: { error: null, ...replyData } } + mockDispatches.push(newMockDispatch) + return newMockDispatch +} + +function deleteMockDispatch (mockDispatches, key) { + const index = mockDispatches.findIndex(dispatch => { + if (!dispatch.consumed) { + return false + } + return matchKey(dispatch, key) + }) + if (index !== -1) { + mockDispatches.splice(index, 1) + } +} + +function buildKey (opts) { + const { path, method, body, headers, query } = opts + return { + path, + method, + body, + headers, + query + } +} + +function generateKeyValues (data) { + return Object.entries(data).reduce((keyValuePairs, [key, value]) => [ + ...keyValuePairs, + Buffer.from(`${key}`), + Array.isArray(value) ? value.map(x => Buffer.from(`${x}`)) : Buffer.from(`${value}`) + ], []) +} + +/** + * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status + * @param {number} statusCode + */ +function getStatusText (statusCode) { + return STATUS_CODES[statusCode] || 'unknown' +} + +async function getResponse (body) { + const buffers = [] + for await (const data of body) { + buffers.push(data) + } + return Buffer.concat(buffers).toString('utf8') +} + +/** + * Mock dispatch function used to simulate undici dispatches + */ +function mockDispatch (opts, handler) { + // Get mock dispatch from built key + const key = buildKey(opts) + const mockDispatch = getMockDispatch(this[kDispatches], key) + + mockDispatch.timesInvoked++ + + // Here's where we resolve a callback if a callback is present for the dispatch data. + if (mockDispatch.data.callback) { + mockDispatch.data = { ...mockDispatch.data, ...mockDispatch.data.callback(opts) } + } + + // Parse mockDispatch data + const { data: { statusCode, data, headers, trailers, error }, delay, persist } = mockDispatch + const { timesInvoked, times } = mockDispatch + + // If it's used up and not persistent, mark as consumed + mockDispatch.consumed = !persist && timesInvoked >= times + mockDispatch.pending = timesInvoked < times + + // If specified, trigger dispatch error + if (error !== null) { + deleteMockDispatch(this[kDispatches], key) + handler.onError(error) + return true + } + + // Handle the request with a delay if necessary + if (typeof delay === 'number' && delay > 0) { + setTimeout(() => { + handleReply(this[kDispatches]) + }, delay) + } else { + handleReply(this[kDispatches]) + } + + function handleReply (mockDispatches, _data = data) { + // fetch's HeadersList is a 1D string array + const optsHeaders = Array.isArray(opts.headers) + ? buildHeadersFromArray(opts.headers) + : opts.headers + const body = typeof _data === 'function' + ? _data({ ...opts, headers: optsHeaders }) + : _data + + // util.types.isPromise is likely needed for jest. + if (isPromise(body)) { + // If handleReply is asynchronous, throwing an error + // in the callback will reject the promise, rather than + // synchronously throw the error, which breaks some tests. + // Rather, we wait for the callback to resolve if it is a + // promise, and then re-run handleReply with the new body. + body.then((newData) => handleReply(mockDispatches, newData)) + return + } + + const responseData = getResponseData(body) + const responseHeaders = generateKeyValues(headers) + const responseTrailers = generateKeyValues(trailers) + + handler.abort = nop + handler.onHeaders(statusCode, responseHeaders, resume, getStatusText(statusCode)) + handler.onData(Buffer.from(responseData)) + handler.onComplete(responseTrailers) + deleteMockDispatch(mockDispatches, key) + } + + function resume () {} + + return true +} + +function buildMockDispatch () { + const agent = this[kMockAgent] + const origin = this[kOrigin] + const originalDispatch = this[kOriginalDispatch] + + return function dispatch (opts, handler) { + if (agent.isMockActive) { + try { + mockDispatch.call(this, opts, handler) + } catch (error) { + if (error instanceof MockNotMatchedError) { + const netConnect = agent[kGetNetConnect]() + if (netConnect === false) { + throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect disabled)`) + } + if (checkNetConnect(netConnect, origin)) { + originalDispatch.call(this, opts, handler) + } else { + throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect is not enabled for this origin)`) + } + } else { + throw error + } + } + } else { + originalDispatch.call(this, opts, handler) + } + } +} + +function checkNetConnect (netConnect, origin) { + const url = new URL(origin) + if (netConnect === true) { + return true + } else if (Array.isArray(netConnect) && netConnect.some((matcher) => matchValue(matcher, url.host))) { + return true + } + return false +} + +function buildMockOptions (opts) { + if (opts) { + const { agent, ...mockOptions } = opts + return mockOptions + } +} + +module.exports = { + getResponseData, + getMockDispatch, + addMockDispatch, + deleteMockDispatch, + buildKey, + generateKeyValues, + matchValue, + getResponse, + getStatusText, + mockDispatch, + buildMockDispatch, + checkNetConnect, + buildMockOptions, + getHeaderByName +} + + +/***/ }), + +/***/ 6142: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { Transform } = __nccwpck_require__(2203) +const { Console } = __nccwpck_require__(4236) + +/** + * Gets the output of `console.table(…)` as a string. + */ +module.exports = class PendingInterceptorsFormatter { + constructor ({ disableColors } = {}) { + this.transform = new Transform({ + transform (chunk, _enc, cb) { + cb(null, chunk) + } + }) + + this.logger = new Console({ + stdout: this.transform, + inspectOptions: { + colors: !disableColors && !process.env.CI + } + }) + } + + format (pendingInterceptors) { + const withPrettyHeaders = pendingInterceptors.map( + ({ method, path, data: { statusCode }, persist, times, timesInvoked, origin }) => ({ + Method: method, + Origin: origin, + Path: path, + 'Status code': statusCode, + Persistent: persist ? '✅' : '❌', + Invocations: timesInvoked, + Remaining: persist ? Infinity : times - timesInvoked + })) + + this.logger.table(withPrettyHeaders) + return this.transform.read().toString() + } +} + + +/***/ }), + +/***/ 1529: +/***/ ((module) => { + +"use strict"; + + +const singulars = { + pronoun: 'it', + is: 'is', + was: 'was', + this: 'this' +} + +const plurals = { + pronoun: 'they', + is: 'are', + was: 'were', + this: 'these' +} + +module.exports = class Pluralizer { + constructor (singular, plural) { + this.singular = singular + this.plural = plural + } + + pluralize (count) { + const one = count === 1 + const keys = one ? singulars : plurals + const noun = one ? this.singular : this.plural + return { ...keys, count, noun } + } +} + + +/***/ }), + +/***/ 4869: +/***/ ((module) => { + +"use strict"; +/* eslint-disable */ + + + +// Extracted from node/lib/internal/fixed_queue.js + +// Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two. +const kSize = 2048; +const kMask = kSize - 1; + +// The FixedQueue is implemented as a singly-linked list of fixed-size +// circular buffers. It looks something like this: +// +// head tail +// | | +// v v +// +-----------+ <-----\ +-----------+ <------\ +-----------+ +// | [null] | \----- | next | \------- | next | +// +-----------+ +-----------+ +-----------+ +// | item | <-- bottom | item | <-- bottom | [empty] | +// | item | | item | | [empty] | +// | item | | item | | [empty] | +// | item | | item | | [empty] | +// | item | | item | bottom --> | item | +// | item | | item | | item | +// | ... | | ... | | ... | +// | item | | item | | item | +// | item | | item | | item | +// | [empty] | <-- top | item | | item | +// | [empty] | | item | | item | +// | [empty] | | [empty] | <-- top top --> | [empty] | +// +-----------+ +-----------+ +-----------+ +// +// Or, if there is only one circular buffer, it looks something +// like either of these: +// +// head tail head tail +// | | | | +// v v v v +// +-----------+ +-----------+ +// | [null] | | [null] | +// +-----------+ +-----------+ +// | [empty] | | item | +// | [empty] | | item | +// | item | <-- bottom top --> | [empty] | +// | item | | [empty] | +// | [empty] | <-- top bottom --> | item | +// | [empty] | | item | +// +-----------+ +-----------+ +// +// Adding a value means moving `top` forward by one, removing means +// moving `bottom` forward by one. After reaching the end, the queue +// wraps around. +// +// When `top === bottom` the current queue is empty and when +// `top + 1 === bottom` it's full. This wastes a single space of storage +// but allows much quicker checks. + +class FixedCircularBuffer { + constructor() { + this.bottom = 0; + this.top = 0; + this.list = new Array(kSize); + this.next = null; + } + + isEmpty() { + return this.top === this.bottom; + } + + isFull() { + return ((this.top + 1) & kMask) === this.bottom; + } + + push(data) { + this.list[this.top] = data; + this.top = (this.top + 1) & kMask; + } + + shift() { + const nextItem = this.list[this.bottom]; + if (nextItem === undefined) + return null; + this.list[this.bottom] = undefined; + this.bottom = (this.bottom + 1) & kMask; + return nextItem; + } +} + +module.exports = class FixedQueue { + constructor() { + this.head = this.tail = new FixedCircularBuffer(); + } + + isEmpty() { + return this.head.isEmpty(); + } + + push(data) { + if (this.head.isFull()) { + // Head is full: Creates a new queue, sets the old queue's `.next` to it, + // and sets it as the new main queue. + this.head = this.head.next = new FixedCircularBuffer(); + } + this.head.push(data); + } + + shift() { + const tail = this.tail; + const next = tail.shift(); + if (tail.isEmpty() && tail.next !== null) { + // If there is another queue, it forms the new tail. + this.tail = tail.next; + } + return next; + } +}; + + +/***/ }), + +/***/ 8640: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const DispatcherBase = __nccwpck_require__(1) +const FixedQueue = __nccwpck_require__(4869) +const { kConnected, kSize, kRunning, kPending, kQueued, kBusy, kFree, kUrl, kClose, kDestroy, kDispatch } = __nccwpck_require__(6443) +const PoolStats = __nccwpck_require__(4622) + +const kClients = Symbol('clients') +const kNeedDrain = Symbol('needDrain') +const kQueue = Symbol('queue') +const kClosedResolve = Symbol('closed resolve') +const kOnDrain = Symbol('onDrain') +const kOnConnect = Symbol('onConnect') +const kOnDisconnect = Symbol('onDisconnect') +const kOnConnectionError = Symbol('onConnectionError') +const kGetDispatcher = Symbol('get dispatcher') +const kAddClient = Symbol('add client') +const kRemoveClient = Symbol('remove client') +const kStats = Symbol('stats') + +class PoolBase extends DispatcherBase { + constructor () { + super() + + this[kQueue] = new FixedQueue() + this[kClients] = [] + this[kQueued] = 0 + + const pool = this + + this[kOnDrain] = function onDrain (origin, targets) { + const queue = pool[kQueue] + + let needDrain = false + + while (!needDrain) { + const item = queue.shift() + if (!item) { + break + } + pool[kQueued]-- + needDrain = !this.dispatch(item.opts, item.handler) + } + + this[kNeedDrain] = needDrain + + if (!this[kNeedDrain] && pool[kNeedDrain]) { + pool[kNeedDrain] = false + pool.emit('drain', origin, [pool, ...targets]) + } + + if (pool[kClosedResolve] && queue.isEmpty()) { + Promise + .all(pool[kClients].map(c => c.close())) + .then(pool[kClosedResolve]) + } + } + + this[kOnConnect] = (origin, targets) => { + pool.emit('connect', origin, [pool, ...targets]) + } + + this[kOnDisconnect] = (origin, targets, err) => { + pool.emit('disconnect', origin, [pool, ...targets], err) + } + + this[kOnConnectionError] = (origin, targets, err) => { + pool.emit('connectionError', origin, [pool, ...targets], err) + } + + this[kStats] = new PoolStats(this) + } + + get [kBusy] () { + return this[kNeedDrain] + } + + get [kConnected] () { + return this[kClients].filter(client => client[kConnected]).length + } + + get [kFree] () { + return this[kClients].filter(client => client[kConnected] && !client[kNeedDrain]).length + } + + get [kPending] () { + let ret = this[kQueued] + for (const { [kPending]: pending } of this[kClients]) { + ret += pending + } + return ret + } + + get [kRunning] () { + let ret = 0 + for (const { [kRunning]: running } of this[kClients]) { + ret += running + } + return ret + } + + get [kSize] () { + let ret = this[kQueued] + for (const { [kSize]: size } of this[kClients]) { + ret += size + } + return ret + } + + get stats () { + return this[kStats] + } + + async [kClose] () { + if (this[kQueue].isEmpty()) { + return Promise.all(this[kClients].map(c => c.close())) + } else { + return new Promise((resolve) => { + this[kClosedResolve] = resolve + }) + } + } + + async [kDestroy] (err) { + while (true) { + const item = this[kQueue].shift() + if (!item) { + break + } + item.handler.onError(err) + } + + return Promise.all(this[kClients].map(c => c.destroy(err))) + } + + [kDispatch] (opts, handler) { + const dispatcher = this[kGetDispatcher]() + + if (!dispatcher) { + this[kNeedDrain] = true + this[kQueue].push({ opts, handler }) + this[kQueued]++ + } else if (!dispatcher.dispatch(opts, handler)) { + dispatcher[kNeedDrain] = true + this[kNeedDrain] = !this[kGetDispatcher]() + } + + return !this[kNeedDrain] + } + + [kAddClient] (client) { + client + .on('drain', this[kOnDrain]) + .on('connect', this[kOnConnect]) + .on('disconnect', this[kOnDisconnect]) + .on('connectionError', this[kOnConnectionError]) + + this[kClients].push(client) + + if (this[kNeedDrain]) { + process.nextTick(() => { + if (this[kNeedDrain]) { + this[kOnDrain](client[kUrl], [this, client]) + } + }) + } + + return this + } + + [kRemoveClient] (client) { + client.close(() => { + const idx = this[kClients].indexOf(client) + if (idx !== -1) { + this[kClients].splice(idx, 1) + } + }) + + this[kNeedDrain] = this[kClients].some(dispatcher => ( + !dispatcher[kNeedDrain] && + dispatcher.closed !== true && + dispatcher.destroyed !== true + )) + } +} + +module.exports = { + PoolBase, + kClients, + kNeedDrain, + kAddClient, + kRemoveClient, + kGetDispatcher +} + + +/***/ }), + +/***/ 4622: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const { kFree, kConnected, kPending, kQueued, kRunning, kSize } = __nccwpck_require__(6443) +const kPool = Symbol('pool') + +class PoolStats { + constructor (pool) { + this[kPool] = pool + } + + get connected () { + return this[kPool][kConnected] + } + + get free () { + return this[kPool][kFree] + } + + get pending () { + return this[kPool][kPending] + } + + get queued () { + return this[kPool][kQueued] + } + + get running () { + return this[kPool][kRunning] + } + + get size () { + return this[kPool][kSize] + } +} + +module.exports = PoolStats + + +/***/ }), + +/***/ 5076: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + PoolBase, + kClients, + kNeedDrain, + kAddClient, + kGetDispatcher +} = __nccwpck_require__(8640) +const Client = __nccwpck_require__(6197) +const { + InvalidArgumentError +} = __nccwpck_require__(8707) +const util = __nccwpck_require__(3440) +const { kUrl, kInterceptors } = __nccwpck_require__(6443) +const buildConnector = __nccwpck_require__(9136) + +const kOptions = Symbol('options') +const kConnections = Symbol('connections') +const kFactory = Symbol('factory') + +function defaultFactory (origin, opts) { + return new Client(origin, opts) +} + +class Pool extends PoolBase { + constructor (origin, { + connections, + factory = defaultFactory, + connect, + connectTimeout, + tls, + maxCachedSessions, + socketPath, + autoSelectFamily, + autoSelectFamilyAttemptTimeout, + allowH2, + ...options + } = {}) { + super() + + if (connections != null && (!Number.isFinite(connections) || connections < 0)) { + throw new InvalidArgumentError('invalid connections') + } + + if (typeof factory !== 'function') { + throw new InvalidArgumentError('factory must be a function.') + } + + if (connect != null && typeof connect !== 'function' && typeof connect !== 'object') { + throw new InvalidArgumentError('connect must be a function or an object') + } + + if (typeof connect !== 'function') { + connect = buildConnector({ + ...tls, + maxCachedSessions, + allowH2, + socketPath, + timeout: connectTimeout, + ...(util.nodeHasAutoSelectFamily && autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined), + ...connect + }) + } + + this[kInterceptors] = options.interceptors && options.interceptors.Pool && Array.isArray(options.interceptors.Pool) + ? options.interceptors.Pool + : [] + this[kConnections] = connections || null + this[kUrl] = util.parseOrigin(origin) + this[kOptions] = { ...util.deepClone(options), connect, allowH2 } + this[kOptions].interceptors = options.interceptors + ? { ...options.interceptors } + : undefined + this[kFactory] = factory + } + + [kGetDispatcher] () { + let dispatcher = this[kClients].find(dispatcher => !dispatcher[kNeedDrain]) + + if (dispatcher) { + return dispatcher + } + + if (!this[kConnections] || this[kClients].length < this[kConnections]) { + dispatcher = this[kFactory](this[kUrl], this[kOptions]) + this[kAddClient](dispatcher) + } + + return dispatcher + } +} + +module.exports = Pool + + +/***/ }), + +/***/ 2720: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { kProxy, kClose, kDestroy, kInterceptors } = __nccwpck_require__(6443) +const { URL } = __nccwpck_require__(7016) +const Agent = __nccwpck_require__(9965) +const Pool = __nccwpck_require__(5076) +const DispatcherBase = __nccwpck_require__(1) +const { InvalidArgumentError, RequestAbortedError } = __nccwpck_require__(8707) +const buildConnector = __nccwpck_require__(9136) + +const kAgent = Symbol('proxy agent') +const kClient = Symbol('proxy client') +const kProxyHeaders = Symbol('proxy headers') +const kRequestTls = Symbol('request tls settings') +const kProxyTls = Symbol('proxy tls settings') +const kConnectEndpoint = Symbol('connect endpoint function') + +function defaultProtocolPort (protocol) { + return protocol === 'https:' ? 443 : 80 +} + +function buildProxyOptions (opts) { + if (typeof opts === 'string') { + opts = { uri: opts } + } + + if (!opts || !opts.uri) { + throw new InvalidArgumentError('Proxy opts.uri is mandatory') + } + + return { + uri: opts.uri, + protocol: opts.protocol || 'https' + } +} + +function defaultFactory (origin, opts) { + return new Pool(origin, opts) +} + +class ProxyAgent extends DispatcherBase { + constructor (opts) { + super(opts) + this[kProxy] = buildProxyOptions(opts) + this[kAgent] = new Agent(opts) + this[kInterceptors] = opts.interceptors && opts.interceptors.ProxyAgent && Array.isArray(opts.interceptors.ProxyAgent) + ? opts.interceptors.ProxyAgent + : [] + + if (typeof opts === 'string') { + opts = { uri: opts } + } + + if (!opts || !opts.uri) { + throw new InvalidArgumentError('Proxy opts.uri is mandatory') + } + + const { clientFactory = defaultFactory } = opts + + if (typeof clientFactory !== 'function') { + throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.') + } + + this[kRequestTls] = opts.requestTls + this[kProxyTls] = opts.proxyTls + this[kProxyHeaders] = opts.headers || {} + + const resolvedUrl = new URL(opts.uri) + const { origin, port, host, username, password } = resolvedUrl + + if (opts.auth && opts.token) { + throw new InvalidArgumentError('opts.auth cannot be used in combination with opts.token') + } else if (opts.auth) { + /* @deprecated in favour of opts.token */ + this[kProxyHeaders]['proxy-authorization'] = `Basic ${opts.auth}` + } else if (opts.token) { + this[kProxyHeaders]['proxy-authorization'] = opts.token + } else if (username && password) { + this[kProxyHeaders]['proxy-authorization'] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString('base64')}` + } + + const connect = buildConnector({ ...opts.proxyTls }) + this[kConnectEndpoint] = buildConnector({ ...opts.requestTls }) + this[kClient] = clientFactory(resolvedUrl, { connect }) + this[kAgent] = new Agent({ + ...opts, + connect: async (opts, callback) => { + let requestedHost = opts.host + if (!opts.port) { + requestedHost += `:${defaultProtocolPort(opts.protocol)}` + } + try { + const { socket, statusCode } = await this[kClient].connect({ + origin, + port, + path: requestedHost, + signal: opts.signal, + headers: { + ...this[kProxyHeaders], + host + } + }) + if (statusCode !== 200) { + socket.on('error', () => {}).destroy() + callback(new RequestAbortedError(`Proxy response (${statusCode}) !== 200 when HTTP Tunneling`)) + } + if (opts.protocol !== 'https:') { + callback(null, socket) + return + } + let servername + if (this[kRequestTls]) { + servername = this[kRequestTls].servername + } else { + servername = opts.servername + } + this[kConnectEndpoint]({ ...opts, servername, httpSocket: socket }, callback) + } catch (err) { + callback(err) + } + } + }) + } + + dispatch (opts, handler) { + const { host } = new URL(opts.origin) + const headers = buildHeaders(opts.headers) + throwIfProxyAuthIsSent(headers) + return this[kAgent].dispatch( + { + ...opts, + headers: { + ...headers, + host + } + }, + handler + ) + } + + async [kClose] () { + await this[kAgent].close() + await this[kClient].close() + } + + async [kDestroy] () { + await this[kAgent].destroy() + await this[kClient].destroy() + } +} + +/** + * @param {string[] | Record} headers + * @returns {Record} + */ +function buildHeaders (headers) { + // When using undici.fetch, the headers list is stored + // as an array. + if (Array.isArray(headers)) { + /** @type {Record} */ + const headersPair = {} + + for (let i = 0; i < headers.length; i += 2) { + headersPair[headers[i]] = headers[i + 1] + } + + return headersPair + } + + return headers +} + +/** + * @param {Record} headers + * + * Previous versions of ProxyAgent suggests the Proxy-Authorization in request headers + * Nevertheless, it was changed and to avoid a security vulnerability by end users + * this check was created. + * It should be removed in the next major version for performance reasons + */ +function throwIfProxyAuthIsSent (headers) { + const existProxyAuth = headers && Object.keys(headers) + .find((key) => key.toLowerCase() === 'proxy-authorization') + if (existProxyAuth) { + throw new InvalidArgumentError('Proxy-Authorization should be sent in ProxyAgent constructor') + } +} + +module.exports = ProxyAgent + + +/***/ }), + +/***/ 8804: +/***/ ((module) => { + +"use strict"; + + +let fastNow = Date.now() +let fastNowTimeout + +const fastTimers = [] + +function onTimeout () { + fastNow = Date.now() + + let len = fastTimers.length + let idx = 0 + while (idx < len) { + const timer = fastTimers[idx] + + if (timer.state === 0) { + timer.state = fastNow + timer.delay + } else if (timer.state > 0 && fastNow >= timer.state) { + timer.state = -1 + timer.callback(timer.opaque) + } + + if (timer.state === -1) { + timer.state = -2 + if (idx !== len - 1) { + fastTimers[idx] = fastTimers.pop() + } else { + fastTimers.pop() + } + len -= 1 + } else { + idx += 1 + } + } + + if (fastTimers.length > 0) { + refreshTimeout() + } +} + +function refreshTimeout () { + if (fastNowTimeout && fastNowTimeout.refresh) { + fastNowTimeout.refresh() + } else { + clearTimeout(fastNowTimeout) + fastNowTimeout = setTimeout(onTimeout, 1e3) + if (fastNowTimeout.unref) { + fastNowTimeout.unref() + } + } +} + +class Timeout { + constructor (callback, delay, opaque) { + this.callback = callback + this.delay = delay + this.opaque = opaque + + // -2 not in timer list + // -1 in timer list but inactive + // 0 in timer list waiting for time + // > 0 in timer list waiting for time to expire + this.state = -2 + + this.refresh() + } + + refresh () { + if (this.state === -2) { + fastTimers.push(this) + if (!fastNowTimeout || fastTimers.length === 1) { + refreshTimeout() + } + } + + this.state = 0 + } + + clear () { + this.state = -1 + } +} + +module.exports = { + setTimeout (callback, delay, opaque) { + return delay < 1e3 + ? setTimeout(callback, delay, opaque) + : new Timeout(callback, delay, opaque) + }, + clearTimeout (timeout) { + if (timeout instanceof Timeout) { + timeout.clear() + } else { + clearTimeout(timeout) + } + } +} + + +/***/ }), + +/***/ 8550: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const diagnosticsChannel = __nccwpck_require__(1637) +const { uid, states } = __nccwpck_require__(5913) +const { + kReadyState, + kSentClose, + kByteParser, + kReceivedClose +} = __nccwpck_require__(2933) +const { fireEvent, failWebsocketConnection } = __nccwpck_require__(3574) +const { CloseEvent } = __nccwpck_require__(6255) +const { makeRequest } = __nccwpck_require__(5194) +const { fetching } = __nccwpck_require__(2315) +const { Headers } = __nccwpck_require__(6349) +const { getGlobalDispatcher } = __nccwpck_require__(2581) +const { kHeadersList } = __nccwpck_require__(6443) + +const channels = {} +channels.open = diagnosticsChannel.channel('undici:websocket:open') +channels.close = diagnosticsChannel.channel('undici:websocket:close') +channels.socketError = diagnosticsChannel.channel('undici:websocket:socket_error') + +/** @type {import('crypto')} */ +let crypto +try { + crypto = __nccwpck_require__(6982) +} catch { + +} + +/** + * @see https://websockets.spec.whatwg.org/#concept-websocket-establish + * @param {URL} url + * @param {string|string[]} protocols + * @param {import('./websocket').WebSocket} ws + * @param {(response: any) => void} onEstablish + * @param {Partial} options + */ +function establishWebSocketConnection (url, protocols, ws, onEstablish, options) { + // 1. Let requestURL be a copy of url, with its scheme set to "http", if url’s + // scheme is "ws", and to "https" otherwise. + const requestURL = url + + requestURL.protocol = url.protocol === 'ws:' ? 'http:' : 'https:' + + // 2. Let request be a new request, whose URL is requestURL, client is client, + // service-workers mode is "none", referrer is "no-referrer", mode is + // "websocket", credentials mode is "include", cache mode is "no-store" , + // and redirect mode is "error". + const request = makeRequest({ + urlList: [requestURL], + serviceWorkers: 'none', + referrer: 'no-referrer', + mode: 'websocket', + credentials: 'include', + cache: 'no-store', + redirect: 'error' + }) + + // Note: undici extension, allow setting custom headers. + if (options.headers) { + const headersList = new Headers(options.headers)[kHeadersList] + + request.headersList = headersList + } + + // 3. Append (`Upgrade`, `websocket`) to request’s header list. + // 4. Append (`Connection`, `Upgrade`) to request’s header list. + // Note: both of these are handled by undici currently. + // https://github.com/nodejs/undici/blob/68c269c4144c446f3f1220951338daef4a6b5ec4/lib/client.js#L1397 + + // 5. Let keyValue be a nonce consisting of a randomly selected + // 16-byte value that has been forgiving-base64-encoded and + // isomorphic encoded. + const keyValue = crypto.randomBytes(16).toString('base64') + + // 6. Append (`Sec-WebSocket-Key`, keyValue) to request’s + // header list. + request.headersList.append('sec-websocket-key', keyValue) + + // 7. Append (`Sec-WebSocket-Version`, `13`) to request’s + // header list. + request.headersList.append('sec-websocket-version', '13') + + // 8. For each protocol in protocols, combine + // (`Sec-WebSocket-Protocol`, protocol) in request’s header + // list. + for (const protocol of protocols) { + request.headersList.append('sec-websocket-protocol', protocol) + } + + // 9. Let permessageDeflate be a user-agent defined + // "permessage-deflate" extension header value. + // https://github.com/mozilla/gecko-dev/blob/ce78234f5e653a5d3916813ff990f053510227bc/netwerk/protocol/websocket/WebSocketChannel.cpp#L2673 + // TODO: enable once permessage-deflate is supported + const permessageDeflate = '' // 'permessage-deflate; 15' + + // 10. Append (`Sec-WebSocket-Extensions`, permessageDeflate) to + // request’s header list. + // request.headersList.append('sec-websocket-extensions', permessageDeflate) + + // 11. Fetch request with useParallelQueue set to true, and + // processResponse given response being these steps: + const controller = fetching({ + request, + useParallelQueue: true, + dispatcher: options.dispatcher ?? getGlobalDispatcher(), + processResponse (response) { + // 1. If response is a network error or its status is not 101, + // fail the WebSocket connection. + if (response.type === 'error' || response.status !== 101) { + failWebsocketConnection(ws, 'Received network error or non-101 status code.') + return + } + + // 2. If protocols is not the empty list and extracting header + // list values given `Sec-WebSocket-Protocol` and response’s + // header list results in null, failure, or the empty byte + // sequence, then fail the WebSocket connection. + if (protocols.length !== 0 && !response.headersList.get('Sec-WebSocket-Protocol')) { + failWebsocketConnection(ws, 'Server did not respond with sent protocols.') + return + } + + // 3. Follow the requirements stated step 2 to step 6, inclusive, + // of the last set of steps in section 4.1 of The WebSocket + // Protocol to validate response. This either results in fail + // the WebSocket connection or the WebSocket connection is + // established. + + // 2. If the response lacks an |Upgrade| header field or the |Upgrade| + // header field contains a value that is not an ASCII case- + // insensitive match for the value "websocket", the client MUST + // _Fail the WebSocket Connection_. + if (response.headersList.get('Upgrade')?.toLowerCase() !== 'websocket') { + failWebsocketConnection(ws, 'Server did not set Upgrade header to "websocket".') + return + } + + // 3. If the response lacks a |Connection| header field or the + // |Connection| header field doesn't contain a token that is an + // ASCII case-insensitive match for the value "Upgrade", the client + // MUST _Fail the WebSocket Connection_. + if (response.headersList.get('Connection')?.toLowerCase() !== 'upgrade') { + failWebsocketConnection(ws, 'Server did not set Connection header to "upgrade".') + return + } + + // 4. If the response lacks a |Sec-WebSocket-Accept| header field or + // the |Sec-WebSocket-Accept| contains a value other than the + // base64-encoded SHA-1 of the concatenation of the |Sec-WebSocket- + // Key| (as a string, not base64-decoded) with the string "258EAFA5- + // E914-47DA-95CA-C5AB0DC85B11" but ignoring any leading and + // trailing whitespace, the client MUST _Fail the WebSocket + // Connection_. + const secWSAccept = response.headersList.get('Sec-WebSocket-Accept') + const digest = crypto.createHash('sha1').update(keyValue + uid).digest('base64') + if (secWSAccept !== digest) { + failWebsocketConnection(ws, 'Incorrect hash received in Sec-WebSocket-Accept header.') + return + } + + // 5. If the response includes a |Sec-WebSocket-Extensions| header + // field and this header field indicates the use of an extension + // that was not present in the client's handshake (the server has + // indicated an extension not requested by the client), the client + // MUST _Fail the WebSocket Connection_. (The parsing of this + // header field to determine which extensions are requested is + // discussed in Section 9.1.) + const secExtension = response.headersList.get('Sec-WebSocket-Extensions') + + if (secExtension !== null && secExtension !== permessageDeflate) { + failWebsocketConnection(ws, 'Received different permessage-deflate than the one set.') + return + } + + // 6. If the response includes a |Sec-WebSocket-Protocol| header field + // and this header field indicates the use of a subprotocol that was + // not present in the client's handshake (the server has indicated a + // subprotocol not requested by the client), the client MUST _Fail + // the WebSocket Connection_. + const secProtocol = response.headersList.get('Sec-WebSocket-Protocol') + + if (secProtocol !== null && secProtocol !== request.headersList.get('Sec-WebSocket-Protocol')) { + failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.') + return + } + + response.socket.on('data', onSocketData) + response.socket.on('close', onSocketClose) + response.socket.on('error', onSocketError) + + if (channels.open.hasSubscribers) { + channels.open.publish({ + address: response.socket.address(), + protocol: secProtocol, + extensions: secExtension + }) + } + + onEstablish(response) + } + }) + + return controller +} + +/** + * @param {Buffer} chunk + */ +function onSocketData (chunk) { + if (!this.ws[kByteParser].write(chunk)) { + this.pause() + } +} + +/** + * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol + * @see https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.4 + */ +function onSocketClose () { + const { ws } = this + + // If the TCP connection was closed after the + // WebSocket closing handshake was completed, the WebSocket connection + // is said to have been closed _cleanly_. + const wasClean = ws[kSentClose] && ws[kReceivedClose] + + let code = 1005 + let reason = '' + + const result = ws[kByteParser].closingInfo + + if (result) { + code = result.code ?? 1005 + reason = result.reason + } else if (!ws[kSentClose]) { + // If _The WebSocket + // Connection is Closed_ and no Close control frame was received by the + // endpoint (such as could occur if the underlying transport connection + // is lost), _The WebSocket Connection Close Code_ is considered to be + // 1006. + code = 1006 + } + + // 1. Change the ready state to CLOSED (3). + ws[kReadyState] = states.CLOSED + + // 2. If the user agent was required to fail the WebSocket + // connection, or if the WebSocket connection was closed + // after being flagged as full, fire an event named error + // at the WebSocket object. + // TODO + + // 3. Fire an event named close at the WebSocket object, + // using CloseEvent, with the wasClean attribute + // initialized to true if the connection closed cleanly + // and false otherwise, the code attribute initialized to + // the WebSocket connection close code, and the reason + // attribute initialized to the result of applying UTF-8 + // decode without BOM to the WebSocket connection close + // reason. + fireEvent('close', ws, CloseEvent, { + wasClean, code, reason + }) + + if (channels.close.hasSubscribers) { + channels.close.publish({ + websocket: ws, + code, + reason + }) + } +} + +function onSocketError (error) { + const { ws } = this + + ws[kReadyState] = states.CLOSING + + if (channels.socketError.hasSubscribers) { + channels.socketError.publish(error) + } + + this.destroy() +} + +module.exports = { + establishWebSocketConnection +} + + +/***/ }), + +/***/ 5913: +/***/ ((module) => { + +"use strict"; + + +// This is a Globally Unique Identifier unique used +// to validate that the endpoint accepts websocket +// connections. +// See https://www.rfc-editor.org/rfc/rfc6455.html#section-1.3 +const uid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' + +/** @type {PropertyDescriptor} */ +const staticPropertyDescriptors = { + enumerable: true, + writable: false, + configurable: false +} + +const states = { + CONNECTING: 0, + OPEN: 1, + CLOSING: 2, + CLOSED: 3 +} + +const opcodes = { + CONTINUATION: 0x0, + TEXT: 0x1, + BINARY: 0x2, + CLOSE: 0x8, + PING: 0x9, + PONG: 0xA +} + +const maxUnsigned16Bit = 2 ** 16 - 1 // 65535 + +const parserStates = { + INFO: 0, + PAYLOADLENGTH_16: 2, + PAYLOADLENGTH_64: 3, + READ_DATA: 4 +} + +const emptyBuffer = Buffer.allocUnsafe(0) + +module.exports = { + uid, + staticPropertyDescriptors, + states, + opcodes, + maxUnsigned16Bit, + parserStates, + emptyBuffer +} + + +/***/ }), + +/***/ 6255: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { webidl } = __nccwpck_require__(4222) +const { kEnumerableProperty } = __nccwpck_require__(3440) +const { MessagePort } = __nccwpck_require__(8167) + +/** + * @see https://html.spec.whatwg.org/multipage/comms.html#messageevent + */ +class MessageEvent extends Event { + #eventInit + + constructor (type, eventInitDict = {}) { + webidl.argumentLengthCheck(arguments, 1, { header: 'MessageEvent constructor' }) + + type = webidl.converters.DOMString(type) + eventInitDict = webidl.converters.MessageEventInit(eventInitDict) + + super(type, eventInitDict) + + this.#eventInit = eventInitDict + } + + get data () { + webidl.brandCheck(this, MessageEvent) + + return this.#eventInit.data + } + + get origin () { + webidl.brandCheck(this, MessageEvent) + + return this.#eventInit.origin + } + + get lastEventId () { + webidl.brandCheck(this, MessageEvent) + + return this.#eventInit.lastEventId + } + + get source () { + webidl.brandCheck(this, MessageEvent) + + return this.#eventInit.source + } + + get ports () { + webidl.brandCheck(this, MessageEvent) + + if (!Object.isFrozen(this.#eventInit.ports)) { + Object.freeze(this.#eventInit.ports) + } + + return this.#eventInit.ports + } + + initMessageEvent ( + type, + bubbles = false, + cancelable = false, + data = null, + origin = '', + lastEventId = '', + source = null, + ports = [] + ) { + webidl.brandCheck(this, MessageEvent) + + webidl.argumentLengthCheck(arguments, 1, { header: 'MessageEvent.initMessageEvent' }) + + return new MessageEvent(type, { + bubbles, cancelable, data, origin, lastEventId, source, ports + }) + } +} + +/** + * @see https://websockets.spec.whatwg.org/#the-closeevent-interface + */ +class CloseEvent extends Event { + #eventInit + + constructor (type, eventInitDict = {}) { + webidl.argumentLengthCheck(arguments, 1, { header: 'CloseEvent constructor' }) + + type = webidl.converters.DOMString(type) + eventInitDict = webidl.converters.CloseEventInit(eventInitDict) + + super(type, eventInitDict) + + this.#eventInit = eventInitDict + } + + get wasClean () { + webidl.brandCheck(this, CloseEvent) + + return this.#eventInit.wasClean + } + + get code () { + webidl.brandCheck(this, CloseEvent) + + return this.#eventInit.code + } + + get reason () { + webidl.brandCheck(this, CloseEvent) + + return this.#eventInit.reason + } +} + +// https://html.spec.whatwg.org/multipage/webappapis.html#the-errorevent-interface +class ErrorEvent extends Event { + #eventInit + + constructor (type, eventInitDict) { + webidl.argumentLengthCheck(arguments, 1, { header: 'ErrorEvent constructor' }) + + super(type, eventInitDict) + + type = webidl.converters.DOMString(type) + eventInitDict = webidl.converters.ErrorEventInit(eventInitDict ?? {}) + + this.#eventInit = eventInitDict + } + + get message () { + webidl.brandCheck(this, ErrorEvent) + + return this.#eventInit.message + } + + get filename () { + webidl.brandCheck(this, ErrorEvent) + + return this.#eventInit.filename + } + + get lineno () { + webidl.brandCheck(this, ErrorEvent) + + return this.#eventInit.lineno + } + + get colno () { + webidl.brandCheck(this, ErrorEvent) + + return this.#eventInit.colno + } + + get error () { + webidl.brandCheck(this, ErrorEvent) + + return this.#eventInit.error + } +} + +Object.defineProperties(MessageEvent.prototype, { + [Symbol.toStringTag]: { + value: 'MessageEvent', + configurable: true + }, + data: kEnumerableProperty, + origin: kEnumerableProperty, + lastEventId: kEnumerableProperty, + source: kEnumerableProperty, + ports: kEnumerableProperty, + initMessageEvent: kEnumerableProperty +}) + +Object.defineProperties(CloseEvent.prototype, { + [Symbol.toStringTag]: { + value: 'CloseEvent', + configurable: true + }, + reason: kEnumerableProperty, + code: kEnumerableProperty, + wasClean: kEnumerableProperty +}) + +Object.defineProperties(ErrorEvent.prototype, { + [Symbol.toStringTag]: { + value: 'ErrorEvent', + configurable: true + }, + message: kEnumerableProperty, + filename: kEnumerableProperty, + lineno: kEnumerableProperty, + colno: kEnumerableProperty, + error: kEnumerableProperty +}) + +webidl.converters.MessagePort = webidl.interfaceConverter(MessagePort) + +webidl.converters['sequence'] = webidl.sequenceConverter( + webidl.converters.MessagePort +) + +const eventInit = [ + { + key: 'bubbles', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'cancelable', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'composed', + converter: webidl.converters.boolean, + defaultValue: false + } +] + +webidl.converters.MessageEventInit = webidl.dictionaryConverter([ + ...eventInit, + { + key: 'data', + converter: webidl.converters.any, + defaultValue: null + }, + { + key: 'origin', + converter: webidl.converters.USVString, + defaultValue: '' + }, + { + key: 'lastEventId', + converter: webidl.converters.DOMString, + defaultValue: '' + }, + { + key: 'source', + // Node doesn't implement WindowProxy or ServiceWorker, so the only + // valid value for source is a MessagePort. + converter: webidl.nullableConverter(webidl.converters.MessagePort), + defaultValue: null + }, + { + key: 'ports', + converter: webidl.converters['sequence'], + get defaultValue () { + return [] + } + } +]) + +webidl.converters.CloseEventInit = webidl.dictionaryConverter([ + ...eventInit, + { + key: 'wasClean', + converter: webidl.converters.boolean, + defaultValue: false + }, + { + key: 'code', + converter: webidl.converters['unsigned short'], + defaultValue: 0 + }, + { + key: 'reason', + converter: webidl.converters.USVString, + defaultValue: '' + } +]) + +webidl.converters.ErrorEventInit = webidl.dictionaryConverter([ + ...eventInit, + { + key: 'message', + converter: webidl.converters.DOMString, + defaultValue: '' + }, + { + key: 'filename', + converter: webidl.converters.USVString, + defaultValue: '' + }, + { + key: 'lineno', + converter: webidl.converters['unsigned long'], + defaultValue: 0 + }, + { + key: 'colno', + converter: webidl.converters['unsigned long'], + defaultValue: 0 + }, + { + key: 'error', + converter: webidl.converters.any + } +]) + +module.exports = { + MessageEvent, + CloseEvent, + ErrorEvent +} + + +/***/ }), + +/***/ 1237: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { maxUnsigned16Bit } = __nccwpck_require__(5913) + +/** @type {import('crypto')} */ +let crypto +try { + crypto = __nccwpck_require__(6982) +} catch { + +} + +class WebsocketFrameSend { + /** + * @param {Buffer|undefined} data + */ + constructor (data) { + this.frameData = data + this.maskKey = crypto.randomBytes(4) + } + + createFrame (opcode) { + const bodyLength = this.frameData?.byteLength ?? 0 + + /** @type {number} */ + let payloadLength = bodyLength // 0-125 + let offset = 6 + + if (bodyLength > maxUnsigned16Bit) { + offset += 8 // payload length is next 8 bytes + payloadLength = 127 + } else if (bodyLength > 125) { + offset += 2 // payload length is next 2 bytes + payloadLength = 126 + } + + const buffer = Buffer.allocUnsafe(bodyLength + offset) + + // Clear first 2 bytes, everything else is overwritten + buffer[0] = buffer[1] = 0 + buffer[0] |= 0x80 // FIN + buffer[0] = (buffer[0] & 0xF0) + opcode // opcode + + /*! ws. MIT License. Einar Otto Stangvik */ + buffer[offset - 4] = this.maskKey[0] + buffer[offset - 3] = this.maskKey[1] + buffer[offset - 2] = this.maskKey[2] + buffer[offset - 1] = this.maskKey[3] + + buffer[1] = payloadLength + + if (payloadLength === 126) { + buffer.writeUInt16BE(bodyLength, 2) + } else if (payloadLength === 127) { + // Clear extended payload length + buffer[2] = buffer[3] = 0 + buffer.writeUIntBE(bodyLength, 4, 6) + } + + buffer[1] |= 0x80 // MASK + + // mask body + for (let i = 0; i < bodyLength; i++) { + buffer[offset + i] = this.frameData[i] ^ this.maskKey[i % 4] + } + + return buffer + } +} + +module.exports = { + WebsocketFrameSend +} + + +/***/ }), + +/***/ 3171: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { Writable } = __nccwpck_require__(2203) +const diagnosticsChannel = __nccwpck_require__(1637) +const { parserStates, opcodes, states, emptyBuffer } = __nccwpck_require__(5913) +const { kReadyState, kSentClose, kResponse, kReceivedClose } = __nccwpck_require__(2933) +const { isValidStatusCode, failWebsocketConnection, websocketMessageReceived } = __nccwpck_require__(3574) +const { WebsocketFrameSend } = __nccwpck_require__(1237) + +// This code was influenced by ws released under the MIT license. +// Copyright (c) 2011 Einar Otto Stangvik +// Copyright (c) 2013 Arnout Kazemier and contributors +// Copyright (c) 2016 Luigi Pinca and contributors + +const channels = {} +channels.ping = diagnosticsChannel.channel('undici:websocket:ping') +channels.pong = diagnosticsChannel.channel('undici:websocket:pong') + +class ByteParser extends Writable { + #buffers = [] + #byteOffset = 0 + + #state = parserStates.INFO + + #info = {} + #fragments = [] + + constructor (ws) { + super() + + this.ws = ws + } + + /** + * @param {Buffer} chunk + * @param {() => void} callback + */ + _write (chunk, _, callback) { + this.#buffers.push(chunk) + this.#byteOffset += chunk.length + + this.run(callback) + } + + /** + * Runs whenever a new chunk is received. + * Callback is called whenever there are no more chunks buffering, + * or not enough bytes are buffered to parse. + */ + run (callback) { + while (true) { + if (this.#state === parserStates.INFO) { + // If there aren't enough bytes to parse the payload length, etc. + if (this.#byteOffset < 2) { + return callback() + } + + const buffer = this.consume(2) + + this.#info.fin = (buffer[0] & 0x80) !== 0 + this.#info.opcode = buffer[0] & 0x0F + + // If we receive a fragmented message, we use the type of the first + // frame to parse the full message as binary/text, when it's terminated + this.#info.originalOpcode ??= this.#info.opcode + + this.#info.fragmented = !this.#info.fin && this.#info.opcode !== opcodes.CONTINUATION + + if (this.#info.fragmented && this.#info.opcode !== opcodes.BINARY && this.#info.opcode !== opcodes.TEXT) { + // Only text and binary frames can be fragmented + failWebsocketConnection(this.ws, 'Invalid frame type was fragmented.') + return + } + + const payloadLength = buffer[1] & 0x7F + + if (payloadLength <= 125) { + this.#info.payloadLength = payloadLength + this.#state = parserStates.READ_DATA + } else if (payloadLength === 126) { + this.#state = parserStates.PAYLOADLENGTH_16 + } else if (payloadLength === 127) { + this.#state = parserStates.PAYLOADLENGTH_64 + } + + if (this.#info.fragmented && payloadLength > 125) { + // A fragmented frame can't be fragmented itself + failWebsocketConnection(this.ws, 'Fragmented frame exceeded 125 bytes.') + return + } else if ( + (this.#info.opcode === opcodes.PING || + this.#info.opcode === opcodes.PONG || + this.#info.opcode === opcodes.CLOSE) && + payloadLength > 125 + ) { + // Control frames can have a payload length of 125 bytes MAX + failWebsocketConnection(this.ws, 'Payload length for control frame exceeded 125 bytes.') + return + } else if (this.#info.opcode === opcodes.CLOSE) { + if (payloadLength === 1) { + failWebsocketConnection(this.ws, 'Received close frame with a 1-byte body.') + return + } + + const body = this.consume(payloadLength) + + this.#info.closeInfo = this.parseCloseBody(false, body) + + if (!this.ws[kSentClose]) { + // If an endpoint receives a Close frame and did not previously send a + // Close frame, the endpoint MUST send a Close frame in response. (When + // sending a Close frame in response, the endpoint typically echos the + // status code it received.) + const body = Buffer.allocUnsafe(2) + body.writeUInt16BE(this.#info.closeInfo.code, 0) + const closeFrame = new WebsocketFrameSend(body) + + this.ws[kResponse].socket.write( + closeFrame.createFrame(opcodes.CLOSE), + (err) => { + if (!err) { + this.ws[kSentClose] = true + } + } + ) + } + + // Upon either sending or receiving a Close control frame, it is said + // that _The WebSocket Closing Handshake is Started_ and that the + // WebSocket connection is in the CLOSING state. + this.ws[kReadyState] = states.CLOSING + this.ws[kReceivedClose] = true + + this.end() + + return + } else if (this.#info.opcode === opcodes.PING) { + // Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in + // response, unless it already received a Close frame. + // A Pong frame sent in response to a Ping frame must have identical + // "Application data" + + const body = this.consume(payloadLength) + + if (!this.ws[kReceivedClose]) { + const frame = new WebsocketFrameSend(body) + + this.ws[kResponse].socket.write(frame.createFrame(opcodes.PONG)) + + if (channels.ping.hasSubscribers) { + channels.ping.publish({ + payload: body + }) + } + } + + this.#state = parserStates.INFO + + if (this.#byteOffset > 0) { + continue + } else { + callback() + return + } + } else if (this.#info.opcode === opcodes.PONG) { + // A Pong frame MAY be sent unsolicited. This serves as a + // unidirectional heartbeat. A response to an unsolicited Pong frame is + // not expected. + + const body = this.consume(payloadLength) + + if (channels.pong.hasSubscribers) { + channels.pong.publish({ + payload: body + }) + } + + if (this.#byteOffset > 0) { + continue + } else { + callback() + return + } + } + } else if (this.#state === parserStates.PAYLOADLENGTH_16) { + if (this.#byteOffset < 2) { + return callback() + } + + const buffer = this.consume(2) + + this.#info.payloadLength = buffer.readUInt16BE(0) + this.#state = parserStates.READ_DATA + } else if (this.#state === parserStates.PAYLOADLENGTH_64) { + if (this.#byteOffset < 8) { + return callback() + } + + const buffer = this.consume(8) + const upper = buffer.readUInt32BE(0) + + // 2^31 is the maxinimum bytes an arraybuffer can contain + // on 32-bit systems. Although, on 64-bit systems, this is + // 2^53-1 bytes. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_array_length + // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/common/globals.h;drc=1946212ac0100668f14eb9e2843bdd846e510a1e;bpv=1;bpt=1;l=1275 + // https://source.chromium.org/chromium/chromium/src/+/main:v8/src/objects/js-array-buffer.h;l=34;drc=1946212ac0100668f14eb9e2843bdd846e510a1e + if (upper > 2 ** 31 - 1) { + failWebsocketConnection(this.ws, 'Received payload length > 2^31 bytes.') + return + } + + const lower = buffer.readUInt32BE(4) + + this.#info.payloadLength = (upper << 8) + lower + this.#state = parserStates.READ_DATA + } else if (this.#state === parserStates.READ_DATA) { + if (this.#byteOffset < this.#info.payloadLength) { + // If there is still more data in this chunk that needs to be read + return callback() + } else if (this.#byteOffset >= this.#info.payloadLength) { + // If the server sent multiple frames in a single chunk + + const body = this.consume(this.#info.payloadLength) + + this.#fragments.push(body) + + // If the frame is unfragmented, or a fragmented frame was terminated, + // a message was received + if (!this.#info.fragmented || (this.#info.fin && this.#info.opcode === opcodes.CONTINUATION)) { + const fullMessage = Buffer.concat(this.#fragments) + + websocketMessageReceived(this.ws, this.#info.originalOpcode, fullMessage) + + this.#info = {} + this.#fragments.length = 0 + } + + this.#state = parserStates.INFO + } + } + + if (this.#byteOffset > 0) { + continue + } else { + callback() + break + } + } + } + + /** + * Take n bytes from the buffered Buffers + * @param {number} n + * @returns {Buffer|null} + */ + consume (n) { + if (n > this.#byteOffset) { + return null + } else if (n === 0) { + return emptyBuffer + } + + if (this.#buffers[0].length === n) { + this.#byteOffset -= this.#buffers[0].length + return this.#buffers.shift() + } + + const buffer = Buffer.allocUnsafe(n) + let offset = 0 + + while (offset !== n) { + const next = this.#buffers[0] + const { length } = next + + if (length + offset === n) { + buffer.set(this.#buffers.shift(), offset) + break + } else if (length + offset > n) { + buffer.set(next.subarray(0, n - offset), offset) + this.#buffers[0] = next.subarray(n - offset) + break + } else { + buffer.set(this.#buffers.shift(), offset) + offset += next.length + } + } + + this.#byteOffset -= n + + return buffer + } + + parseCloseBody (onlyCode, data) { + // https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.5 + /** @type {number|undefined} */ + let code + + if (data.length >= 2) { + // _The WebSocket Connection Close Code_ is + // defined as the status code (Section 7.4) contained in the first Close + // control frame received by the application + code = data.readUInt16BE(0) + } + + if (onlyCode) { + if (!isValidStatusCode(code)) { + return null + } + + return { code } + } + + // https://datatracker.ietf.org/doc/html/rfc6455#section-7.1.6 + /** @type {Buffer} */ + let reason = data.subarray(2) + + // Remove BOM + if (reason[0] === 0xEF && reason[1] === 0xBB && reason[2] === 0xBF) { + reason = reason.subarray(3) + } + + if (code !== undefined && !isValidStatusCode(code)) { + return null + } + + try { + // TODO: optimize this + reason = new TextDecoder('utf-8', { fatal: true }).decode(reason) + } catch { + return null + } + + return { code, reason } + } + + get closingInfo () { + return this.#info.closeInfo + } +} + +module.exports = { + ByteParser +} + + +/***/ }), + +/***/ 2933: +/***/ ((module) => { + +"use strict"; + + +module.exports = { + kWebSocketURL: Symbol('url'), + kReadyState: Symbol('ready state'), + kController: Symbol('controller'), + kResponse: Symbol('response'), + kBinaryType: Symbol('binary type'), + kSentClose: Symbol('sent close'), + kReceivedClose: Symbol('received close'), + kByteParser: Symbol('byte parser') +} + + +/***/ }), + +/***/ 3574: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { kReadyState, kController, kResponse, kBinaryType, kWebSocketURL } = __nccwpck_require__(2933) +const { states, opcodes } = __nccwpck_require__(5913) +const { MessageEvent, ErrorEvent } = __nccwpck_require__(6255) + +/* globals Blob */ + +/** + * @param {import('./websocket').WebSocket} ws + */ +function isEstablished (ws) { + // If the server's response is validated as provided for above, it is + // said that _The WebSocket Connection is Established_ and that the + // WebSocket Connection is in the OPEN state. + return ws[kReadyState] === states.OPEN +} + +/** + * @param {import('./websocket').WebSocket} ws + */ +function isClosing (ws) { + // Upon either sending or receiving a Close control frame, it is said + // that _The WebSocket Closing Handshake is Started_ and that the + // WebSocket connection is in the CLOSING state. + return ws[kReadyState] === states.CLOSING +} + +/** + * @param {import('./websocket').WebSocket} ws + */ +function isClosed (ws) { + return ws[kReadyState] === states.CLOSED +} + +/** + * @see https://dom.spec.whatwg.org/#concept-event-fire + * @param {string} e + * @param {EventTarget} target + * @param {EventInit | undefined} eventInitDict + */ +function fireEvent (e, target, eventConstructor = Event, eventInitDict) { + // 1. If eventConstructor is not given, then let eventConstructor be Event. + + // 2. Let event be the result of creating an event given eventConstructor, + // in the relevant realm of target. + // 3. Initialize event’s type attribute to e. + const event = new eventConstructor(e, eventInitDict) // eslint-disable-line new-cap + + // 4. Initialize any other IDL attributes of event as described in the + // invocation of this algorithm. + + // 5. Return the result of dispatching event at target, with legacy target + // override flag set if set. + target.dispatchEvent(event) +} + +/** + * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol + * @param {import('./websocket').WebSocket} ws + * @param {number} type Opcode + * @param {Buffer} data application data + */ +function websocketMessageReceived (ws, type, data) { + // 1. If ready state is not OPEN (1), then return. + if (ws[kReadyState] !== states.OPEN) { + return + } + + // 2. Let dataForEvent be determined by switching on type and binary type: + let dataForEvent + + if (type === opcodes.TEXT) { + // -> type indicates that the data is Text + // a new DOMString containing data + try { + dataForEvent = new TextDecoder('utf-8', { fatal: true }).decode(data) + } catch { + failWebsocketConnection(ws, 'Received invalid UTF-8 in text frame.') + return + } + } else if (type === opcodes.BINARY) { + if (ws[kBinaryType] === 'blob') { + // -> type indicates that the data is Binary and binary type is "blob" + // a new Blob object, created in the relevant Realm of the WebSocket + // object, that represents data as its raw data + dataForEvent = new Blob([data]) + } else { + // -> type indicates that the data is Binary and binary type is "arraybuffer" + // a new ArrayBuffer object, created in the relevant Realm of the + // WebSocket object, whose contents are data + dataForEvent = new Uint8Array(data).buffer + } + } + + // 3. Fire an event named message at the WebSocket object, using MessageEvent, + // with the origin attribute initialized to the serialization of the WebSocket + // object’s url's origin, and the data attribute initialized to dataForEvent. + fireEvent('message', ws, MessageEvent, { + origin: ws[kWebSocketURL].origin, + data: dataForEvent + }) +} + +/** + * @see https://datatracker.ietf.org/doc/html/rfc6455 + * @see https://datatracker.ietf.org/doc/html/rfc2616 + * @see https://bugs.chromium.org/p/chromium/issues/detail?id=398407 + * @param {string} protocol + */ +function isValidSubprotocol (protocol) { + // If present, this value indicates one + // or more comma-separated subprotocol the client wishes to speak, + // ordered by preference. The elements that comprise this value + // MUST be non-empty strings with characters in the range U+0021 to + // U+007E not including separator characters as defined in + // [RFC2616] and MUST all be unique strings. + if (protocol.length === 0) { + return false + } + + for (const char of protocol) { + const code = char.charCodeAt(0) + + if ( + code < 0x21 || + code > 0x7E || + char === '(' || + char === ')' || + char === '<' || + char === '>' || + char === '@' || + char === ',' || + char === ';' || + char === ':' || + char === '\\' || + char === '"' || + char === '/' || + char === '[' || + char === ']' || + char === '?' || + char === '=' || + char === '{' || + char === '}' || + code === 32 || // SP + code === 9 // HT + ) { + return false + } + } + + return true +} + +/** + * @see https://datatracker.ietf.org/doc/html/rfc6455#section-7-4 + * @param {number} code + */ +function isValidStatusCode (code) { + if (code >= 1000 && code < 1015) { + return ( + code !== 1004 && // reserved + code !== 1005 && // "MUST NOT be set as a status code" + code !== 1006 // "MUST NOT be set as a status code" + ) + } + + return code >= 3000 && code <= 4999 +} + +/** + * @param {import('./websocket').WebSocket} ws + * @param {string|undefined} reason + */ +function failWebsocketConnection (ws, reason) { + const { [kController]: controller, [kResponse]: response } = ws + + controller.abort() + + if (response?.socket && !response.socket.destroyed) { + response.socket.destroy() + } + + if (reason) { + fireEvent('error', ws, ErrorEvent, { + error: new Error(reason) + }) + } +} + +module.exports = { + isEstablished, + isClosing, + isClosed, + fireEvent, + isValidSubprotocol, + isValidStatusCode, + failWebsocketConnection, + websocketMessageReceived +} + + +/***/ }), + +/***/ 5171: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { webidl } = __nccwpck_require__(4222) +const { DOMException } = __nccwpck_require__(7326) +const { URLSerializer } = __nccwpck_require__(4322) +const { getGlobalOrigin } = __nccwpck_require__(5628) +const { staticPropertyDescriptors, states, opcodes, emptyBuffer } = __nccwpck_require__(5913) +const { + kWebSocketURL, + kReadyState, + kController, + kBinaryType, + kResponse, + kSentClose, + kByteParser +} = __nccwpck_require__(2933) +const { isEstablished, isClosing, isValidSubprotocol, failWebsocketConnection, fireEvent } = __nccwpck_require__(3574) +const { establishWebSocketConnection } = __nccwpck_require__(8550) +const { WebsocketFrameSend } = __nccwpck_require__(1237) +const { ByteParser } = __nccwpck_require__(3171) +const { kEnumerableProperty, isBlobLike } = __nccwpck_require__(3440) +const { getGlobalDispatcher } = __nccwpck_require__(2581) +const { types } = __nccwpck_require__(9023) + +let experimentalWarned = false + +// https://websockets.spec.whatwg.org/#interface-definition +class WebSocket extends EventTarget { + #events = { + open: null, + error: null, + close: null, + message: null + } + + #bufferedAmount = 0 + #protocol = '' + #extensions = '' + + /** + * @param {string} url + * @param {string|string[]} protocols + */ + constructor (url, protocols = []) { + super() + + webidl.argumentLengthCheck(arguments, 1, { header: 'WebSocket constructor' }) + + if (!experimentalWarned) { + experimentalWarned = true + process.emitWarning('WebSockets are experimental, expect them to change at any time.', { + code: 'UNDICI-WS' + }) + } + + const options = webidl.converters['DOMString or sequence or WebSocketInit'](protocols) + + url = webidl.converters.USVString(url) + protocols = options.protocols + + // 1. Let baseURL be this's relevant settings object's API base URL. + const baseURL = getGlobalOrigin() + + // 1. Let urlRecord be the result of applying the URL parser to url with baseURL. + let urlRecord + + try { + urlRecord = new URL(url, baseURL) + } catch (e) { + // 3. If urlRecord is failure, then throw a "SyntaxError" DOMException. + throw new DOMException(e, 'SyntaxError') + } + + // 4. If urlRecord’s scheme is "http", then set urlRecord’s scheme to "ws". + if (urlRecord.protocol === 'http:') { + urlRecord.protocol = 'ws:' + } else if (urlRecord.protocol === 'https:') { + // 5. Otherwise, if urlRecord’s scheme is "https", set urlRecord’s scheme to "wss". + urlRecord.protocol = 'wss:' + } + + // 6. If urlRecord’s scheme is not "ws" or "wss", then throw a "SyntaxError" DOMException. + if (urlRecord.protocol !== 'ws:' && urlRecord.protocol !== 'wss:') { + throw new DOMException( + `Expected a ws: or wss: protocol, got ${urlRecord.protocol}`, + 'SyntaxError' + ) + } + + // 7. If urlRecord’s fragment is non-null, then throw a "SyntaxError" + // DOMException. + if (urlRecord.hash || urlRecord.href.endsWith('#')) { + throw new DOMException('Got fragment', 'SyntaxError') + } + + // 8. If protocols is a string, set protocols to a sequence consisting + // of just that string. + if (typeof protocols === 'string') { + protocols = [protocols] + } + + // 9. If any of the values in protocols occur more than once or otherwise + // fail to match the requirements for elements that comprise the value + // of `Sec-WebSocket-Protocol` fields as defined by The WebSocket + // protocol, then throw a "SyntaxError" DOMException. + if (protocols.length !== new Set(protocols.map(p => p.toLowerCase())).size) { + throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError') + } + + if (protocols.length > 0 && !protocols.every(p => isValidSubprotocol(p))) { + throw new DOMException('Invalid Sec-WebSocket-Protocol value', 'SyntaxError') + } + + // 10. Set this's url to urlRecord. + this[kWebSocketURL] = new URL(urlRecord.href) + + // 11. Let client be this's relevant settings object. + + // 12. Run this step in parallel: + + // 1. Establish a WebSocket connection given urlRecord, protocols, + // and client. + this[kController] = establishWebSocketConnection( + urlRecord, + protocols, + this, + (response) => this.#onConnectionEstablished(response), + options + ) + + // Each WebSocket object has an associated ready state, which is a + // number representing the state of the connection. Initially it must + // be CONNECTING (0). + this[kReadyState] = WebSocket.CONNECTING + + // The extensions attribute must initially return the empty string. + + // The protocol attribute must initially return the empty string. + + // Each WebSocket object has an associated binary type, which is a + // BinaryType. Initially it must be "blob". + this[kBinaryType] = 'blob' + } + + /** + * @see https://websockets.spec.whatwg.org/#dom-websocket-close + * @param {number|undefined} code + * @param {string|undefined} reason + */ + close (code = undefined, reason = undefined) { + webidl.brandCheck(this, WebSocket) + + if (code !== undefined) { + code = webidl.converters['unsigned short'](code, { clamp: true }) + } + + if (reason !== undefined) { + reason = webidl.converters.USVString(reason) + } + + // 1. If code is present, but is neither an integer equal to 1000 nor an + // integer in the range 3000 to 4999, inclusive, throw an + // "InvalidAccessError" DOMException. + if (code !== undefined) { + if (code !== 1000 && (code < 3000 || code > 4999)) { + throw new DOMException('invalid code', 'InvalidAccessError') + } + } + + let reasonByteLength = 0 + + // 2. If reason is present, then run these substeps: + if (reason !== undefined) { + // 1. Let reasonBytes be the result of encoding reason. + // 2. If reasonBytes is longer than 123 bytes, then throw a + // "SyntaxError" DOMException. + reasonByteLength = Buffer.byteLength(reason) + + if (reasonByteLength > 123) { + throw new DOMException( + `Reason must be less than 123 bytes; received ${reasonByteLength}`, + 'SyntaxError' + ) + } + } + + // 3. Run the first matching steps from the following list: + if (this[kReadyState] === WebSocket.CLOSING || this[kReadyState] === WebSocket.CLOSED) { + // If this's ready state is CLOSING (2) or CLOSED (3) + // Do nothing. + } else if (!isEstablished(this)) { + // If the WebSocket connection is not yet established + // Fail the WebSocket connection and set this's ready state + // to CLOSING (2). + failWebsocketConnection(this, 'Connection was closed before it was established.') + this[kReadyState] = WebSocket.CLOSING + } else if (!isClosing(this)) { + // If the WebSocket closing handshake has not yet been started + // Start the WebSocket closing handshake and set this's ready + // state to CLOSING (2). + // - If neither code nor reason is present, the WebSocket Close + // message must not have a body. + // - If code is present, then the status code to use in the + // WebSocket Close message must be the integer given by code. + // - If reason is also present, then reasonBytes must be + // provided in the Close message after the status code. + + const frame = new WebsocketFrameSend() + + // If neither code nor reason is present, the WebSocket Close + // message must not have a body. + + // If code is present, then the status code to use in the + // WebSocket Close message must be the integer given by code. + if (code !== undefined && reason === undefined) { + frame.frameData = Buffer.allocUnsafe(2) + frame.frameData.writeUInt16BE(code, 0) + } else if (code !== undefined && reason !== undefined) { + // If reason is also present, then reasonBytes must be + // provided in the Close message after the status code. + frame.frameData = Buffer.allocUnsafe(2 + reasonByteLength) + frame.frameData.writeUInt16BE(code, 0) + // the body MAY contain UTF-8-encoded data with value /reason/ + frame.frameData.write(reason, 2, 'utf-8') + } else { + frame.frameData = emptyBuffer + } + + /** @type {import('stream').Duplex} */ + const socket = this[kResponse].socket + + socket.write(frame.createFrame(opcodes.CLOSE), (err) => { + if (!err) { + this[kSentClose] = true + } + }) + + // Upon either sending or receiving a Close control frame, it is said + // that _The WebSocket Closing Handshake is Started_ and that the + // WebSocket connection is in the CLOSING state. + this[kReadyState] = states.CLOSING + } else { + // Otherwise + // Set this's ready state to CLOSING (2). + this[kReadyState] = WebSocket.CLOSING + } + } + + /** + * @see https://websockets.spec.whatwg.org/#dom-websocket-send + * @param {NodeJS.TypedArray|ArrayBuffer|Blob|string} data + */ + send (data) { + webidl.brandCheck(this, WebSocket) + + webidl.argumentLengthCheck(arguments, 1, { header: 'WebSocket.send' }) + + data = webidl.converters.WebSocketSendData(data) + + // 1. If this's ready state is CONNECTING, then throw an + // "InvalidStateError" DOMException. + if (this[kReadyState] === WebSocket.CONNECTING) { + throw new DOMException('Sent before connected.', 'InvalidStateError') + } + + // 2. Run the appropriate set of steps from the following list: + // https://datatracker.ietf.org/doc/html/rfc6455#section-6.1 + // https://datatracker.ietf.org/doc/html/rfc6455#section-5.2 + + if (!isEstablished(this) || isClosing(this)) { + return + } + + /** @type {import('stream').Duplex} */ + const socket = this[kResponse].socket + + // If data is a string + if (typeof data === 'string') { + // If the WebSocket connection is established and the WebSocket + // closing handshake has not yet started, then the user agent + // must send a WebSocket Message comprised of the data argument + // using a text frame opcode; if the data cannot be sent, e.g. + // because it would need to be buffered but the buffer is full, + // the user agent must flag the WebSocket as full and then close + // the WebSocket connection. Any invocation of this method with a + // string argument that does not throw an exception must increase + // the bufferedAmount attribute by the number of bytes needed to + // express the argument as UTF-8. + + const value = Buffer.from(data) + const frame = new WebsocketFrameSend(value) + const buffer = frame.createFrame(opcodes.TEXT) + + this.#bufferedAmount += value.byteLength + socket.write(buffer, () => { + this.#bufferedAmount -= value.byteLength + }) + } else if (types.isArrayBuffer(data)) { + // If the WebSocket connection is established, and the WebSocket + // closing handshake has not yet started, then the user agent must + // send a WebSocket Message comprised of data using a binary frame + // opcode; if the data cannot be sent, e.g. because it would need + // to be buffered but the buffer is full, the user agent must flag + // the WebSocket as full and then close the WebSocket connection. + // The data to be sent is the data stored in the buffer described + // by the ArrayBuffer object. Any invocation of this method with an + // ArrayBuffer argument that does not throw an exception must + // increase the bufferedAmount attribute by the length of the + // ArrayBuffer in bytes. + + const value = Buffer.from(data) + const frame = new WebsocketFrameSend(value) + const buffer = frame.createFrame(opcodes.BINARY) + + this.#bufferedAmount += value.byteLength + socket.write(buffer, () => { + this.#bufferedAmount -= value.byteLength + }) + } else if (ArrayBuffer.isView(data)) { + // If the WebSocket connection is established, and the WebSocket + // closing handshake has not yet started, then the user agent must + // send a WebSocket Message comprised of data using a binary frame + // opcode; if the data cannot be sent, e.g. because it would need to + // be buffered but the buffer is full, the user agent must flag the + // WebSocket as full and then close the WebSocket connection. The + // data to be sent is the data stored in the section of the buffer + // described by the ArrayBuffer object that data references. Any + // invocation of this method with this kind of argument that does + // not throw an exception must increase the bufferedAmount attribute + // by the length of data’s buffer in bytes. + + const ab = Buffer.from(data, data.byteOffset, data.byteLength) + + const frame = new WebsocketFrameSend(ab) + const buffer = frame.createFrame(opcodes.BINARY) + + this.#bufferedAmount += ab.byteLength + socket.write(buffer, () => { + this.#bufferedAmount -= ab.byteLength + }) + } else if (isBlobLike(data)) { + // If the WebSocket connection is established, and the WebSocket + // closing handshake has not yet started, then the user agent must + // send a WebSocket Message comprised of data using a binary frame + // opcode; if the data cannot be sent, e.g. because it would need to + // be buffered but the buffer is full, the user agent must flag the + // WebSocket as full and then close the WebSocket connection. The data + // to be sent is the raw data represented by the Blob object. Any + // invocation of this method with a Blob argument that does not throw + // an exception must increase the bufferedAmount attribute by the size + // of the Blob object’s raw data, in bytes. + + const frame = new WebsocketFrameSend() + + data.arrayBuffer().then((ab) => { + const value = Buffer.from(ab) + frame.frameData = value + const buffer = frame.createFrame(opcodes.BINARY) + + this.#bufferedAmount += value.byteLength + socket.write(buffer, () => { + this.#bufferedAmount -= value.byteLength + }) + }) + } + } + + get readyState () { + webidl.brandCheck(this, WebSocket) + + // The readyState getter steps are to return this's ready state. + return this[kReadyState] + } + + get bufferedAmount () { + webidl.brandCheck(this, WebSocket) + + return this.#bufferedAmount + } + + get url () { + webidl.brandCheck(this, WebSocket) + + // The url getter steps are to return this's url, serialized. + return URLSerializer(this[kWebSocketURL]) + } + + get extensions () { + webidl.brandCheck(this, WebSocket) + + return this.#extensions + } + + get protocol () { + webidl.brandCheck(this, WebSocket) + + return this.#protocol + } + + get onopen () { + webidl.brandCheck(this, WebSocket) + + return this.#events.open + } + + set onopen (fn) { + webidl.brandCheck(this, WebSocket) + + if (this.#events.open) { + this.removeEventListener('open', this.#events.open) + } + + if (typeof fn === 'function') { + this.#events.open = fn + this.addEventListener('open', fn) + } else { + this.#events.open = null + } + } + + get onerror () { + webidl.brandCheck(this, WebSocket) + + return this.#events.error + } + + set onerror (fn) { + webidl.brandCheck(this, WebSocket) + + if (this.#events.error) { + this.removeEventListener('error', this.#events.error) + } + + if (typeof fn === 'function') { + this.#events.error = fn + this.addEventListener('error', fn) + } else { + this.#events.error = null + } + } + + get onclose () { + webidl.brandCheck(this, WebSocket) + + return this.#events.close + } + + set onclose (fn) { + webidl.brandCheck(this, WebSocket) + + if (this.#events.close) { + this.removeEventListener('close', this.#events.close) + } + + if (typeof fn === 'function') { + this.#events.close = fn + this.addEventListener('close', fn) + } else { + this.#events.close = null + } + } + + get onmessage () { + webidl.brandCheck(this, WebSocket) + + return this.#events.message + } + + set onmessage (fn) { + webidl.brandCheck(this, WebSocket) + + if (this.#events.message) { + this.removeEventListener('message', this.#events.message) + } + + if (typeof fn === 'function') { + this.#events.message = fn + this.addEventListener('message', fn) + } else { + this.#events.message = null + } + } + + get binaryType () { + webidl.brandCheck(this, WebSocket) + + return this[kBinaryType] + } + + set binaryType (type) { + webidl.brandCheck(this, WebSocket) + + if (type !== 'blob' && type !== 'arraybuffer') { + this[kBinaryType] = 'blob' + } else { + this[kBinaryType] = type + } + } + + /** + * @see https://websockets.spec.whatwg.org/#feedback-from-the-protocol + */ + #onConnectionEstablished (response) { + // processResponse is called when the "response’s header list has been received and initialized." + // once this happens, the connection is open + this[kResponse] = response + + const parser = new ByteParser(this) + parser.on('drain', function onParserDrain () { + this.ws[kResponse].socket.resume() + }) + + response.socket.ws = this + this[kByteParser] = parser + + // 1. Change the ready state to OPEN (1). + this[kReadyState] = states.OPEN + + // 2. Change the extensions attribute’s value to the extensions in use, if + // it is not the null value. + // https://datatracker.ietf.org/doc/html/rfc6455#section-9.1 + const extensions = response.headersList.get('sec-websocket-extensions') + + if (extensions !== null) { + this.#extensions = extensions + } + + // 3. Change the protocol attribute’s value to the subprotocol in use, if + // it is not the null value. + // https://datatracker.ietf.org/doc/html/rfc6455#section-1.9 + const protocol = response.headersList.get('sec-websocket-protocol') + + if (protocol !== null) { + this.#protocol = protocol + } + + // 4. Fire an event named open at the WebSocket object. + fireEvent('open', this) + } +} + +// https://websockets.spec.whatwg.org/#dom-websocket-connecting +WebSocket.CONNECTING = WebSocket.prototype.CONNECTING = states.CONNECTING +// https://websockets.spec.whatwg.org/#dom-websocket-open +WebSocket.OPEN = WebSocket.prototype.OPEN = states.OPEN +// https://websockets.spec.whatwg.org/#dom-websocket-closing +WebSocket.CLOSING = WebSocket.prototype.CLOSING = states.CLOSING +// https://websockets.spec.whatwg.org/#dom-websocket-closed +WebSocket.CLOSED = WebSocket.prototype.CLOSED = states.CLOSED + +Object.defineProperties(WebSocket.prototype, { + CONNECTING: staticPropertyDescriptors, + OPEN: staticPropertyDescriptors, + CLOSING: staticPropertyDescriptors, + CLOSED: staticPropertyDescriptors, + url: kEnumerableProperty, + readyState: kEnumerableProperty, + bufferedAmount: kEnumerableProperty, + onopen: kEnumerableProperty, + onerror: kEnumerableProperty, + onclose: kEnumerableProperty, + close: kEnumerableProperty, + onmessage: kEnumerableProperty, + binaryType: kEnumerableProperty, + send: kEnumerableProperty, + extensions: kEnumerableProperty, + protocol: kEnumerableProperty, + [Symbol.toStringTag]: { + value: 'WebSocket', + writable: false, + enumerable: false, + configurable: true + } +}) + +Object.defineProperties(WebSocket, { + CONNECTING: staticPropertyDescriptors, + OPEN: staticPropertyDescriptors, + CLOSING: staticPropertyDescriptors, + CLOSED: staticPropertyDescriptors +}) + +webidl.converters['sequence'] = webidl.sequenceConverter( + webidl.converters.DOMString +) + +webidl.converters['DOMString or sequence'] = function (V) { + if (webidl.util.Type(V) === 'Object' && Symbol.iterator in V) { + return webidl.converters['sequence'](V) + } + + return webidl.converters.DOMString(V) +} + +// This implements the propsal made in https://github.com/whatwg/websockets/issues/42 +webidl.converters.WebSocketInit = webidl.dictionaryConverter([ + { + key: 'protocols', + converter: webidl.converters['DOMString or sequence'], + get defaultValue () { + return [] + } + }, + { + key: 'dispatcher', + converter: (V) => V, + get defaultValue () { + return getGlobalDispatcher() + } + }, + { + key: 'headers', + converter: webidl.nullableConverter(webidl.converters.HeadersInit) + } +]) + +webidl.converters['DOMString or sequence or WebSocketInit'] = function (V) { + if (webidl.util.Type(V) === 'Object' && !(Symbol.iterator in V)) { + return webidl.converters.WebSocketInit(V) + } + + return { protocols: webidl.converters['DOMString or sequence'](V) } +} + +webidl.converters.WebSocketSendData = function (V) { + if (webidl.util.Type(V) === 'Object') { + if (isBlobLike(V)) { + return webidl.converters.Blob(V, { strict: false }) + } + + if (ArrayBuffer.isView(V) || types.isAnyArrayBuffer(V)) { + return webidl.converters.BufferSource(V) + } + } + + return webidl.converters.USVString(V) +} + +module.exports = { + WebSocket +} + + +/***/ }), + +/***/ 7125: +/***/ ((module) => { + +"use strict"; + + +var conversions = {}; +module.exports = conversions; + +function sign(x) { + return x < 0 ? -1 : 1; +} + +function evenRound(x) { + // Round x to the nearest integer, choosing the even integer if it lies halfway between two. + if ((x % 1) === 0.5 && (x & 1) === 0) { // [even number].5; round down (i.e. floor) + return Math.floor(x); + } else { + return Math.round(x); + } +} + +function createNumberConversion(bitLength, typeOpts) { + if (!typeOpts.unsigned) { + --bitLength; + } + const lowerBound = typeOpts.unsigned ? 0 : -Math.pow(2, bitLength); + const upperBound = Math.pow(2, bitLength) - 1; + + const moduloVal = typeOpts.moduloBitLength ? Math.pow(2, typeOpts.moduloBitLength) : Math.pow(2, bitLength); + const moduloBound = typeOpts.moduloBitLength ? Math.pow(2, typeOpts.moduloBitLength - 1) : Math.pow(2, bitLength - 1); + + return function(V, opts) { + if (!opts) opts = {}; + + let x = +V; + + if (opts.enforceRange) { + if (!Number.isFinite(x)) { + throw new TypeError("Argument is not a finite number"); + } + + x = sign(x) * Math.floor(Math.abs(x)); + if (x < lowerBound || x > upperBound) { + throw new TypeError("Argument is not in byte range"); + } + + return x; + } + + if (!isNaN(x) && opts.clamp) { + x = evenRound(x); + + if (x < lowerBound) x = lowerBound; + if (x > upperBound) x = upperBound; + return x; + } + + if (!Number.isFinite(x) || x === 0) { + return 0; + } + + x = sign(x) * Math.floor(Math.abs(x)); + x = x % moduloVal; + + if (!typeOpts.unsigned && x >= moduloBound) { + return x - moduloVal; + } else if (typeOpts.unsigned) { + if (x < 0) { + x += moduloVal; + } else if (x === -0) { // don't return negative zero + return 0; + } + } + + return x; + } +} + +conversions["void"] = function () { + return undefined; +}; + +conversions["boolean"] = function (val) { + return !!val; +}; + +conversions["byte"] = createNumberConversion(8, { unsigned: false }); +conversions["octet"] = createNumberConversion(8, { unsigned: true }); + +conversions["short"] = createNumberConversion(16, { unsigned: false }); +conversions["unsigned short"] = createNumberConversion(16, { unsigned: true }); + +conversions["long"] = createNumberConversion(32, { unsigned: false }); +conversions["unsigned long"] = createNumberConversion(32, { unsigned: true }); + +conversions["long long"] = createNumberConversion(32, { unsigned: false, moduloBitLength: 64 }); +conversions["unsigned long long"] = createNumberConversion(32, { unsigned: true, moduloBitLength: 64 }); + +conversions["double"] = function (V) { + const x = +V; + + if (!Number.isFinite(x)) { + throw new TypeError("Argument is not a finite floating-point value"); + } + + return x; +}; + +conversions["unrestricted double"] = function (V) { + const x = +V; + + if (isNaN(x)) { + throw new TypeError("Argument is NaN"); + } + + return x; +}; + +// not quite valid, but good enough for JS +conversions["float"] = conversions["double"]; +conversions["unrestricted float"] = conversions["unrestricted double"]; + +conversions["DOMString"] = function (V, opts) { + if (!opts) opts = {}; + + if (opts.treatNullAsEmptyString && V === null) { + return ""; + } + + return String(V); +}; + +conversions["ByteString"] = function (V, opts) { + const x = String(V); + let c = undefined; + for (let i = 0; (c = x.codePointAt(i)) !== undefined; ++i) { + if (c > 255) { + throw new TypeError("Argument is not a valid bytestring"); + } + } + + return x; +}; + +conversions["USVString"] = function (V) { + const S = String(V); + const n = S.length; + const U = []; + for (let i = 0; i < n; ++i) { + const c = S.charCodeAt(i); + if (c < 0xD800 || c > 0xDFFF) { + U.push(String.fromCodePoint(c)); + } else if (0xDC00 <= c && c <= 0xDFFF) { + U.push(String.fromCodePoint(0xFFFD)); + } else { + if (i === n - 1) { + U.push(String.fromCodePoint(0xFFFD)); + } else { + const d = S.charCodeAt(i + 1); + if (0xDC00 <= d && d <= 0xDFFF) { + const a = c & 0x3FF; + const b = d & 0x3FF; + U.push(String.fromCodePoint((2 << 15) + (2 << 9) * a + b)); + ++i; + } else { + U.push(String.fromCodePoint(0xFFFD)); + } + } + } + } + + return U.join(''); +}; + +conversions["Date"] = function (V, opts) { + if (!(V instanceof Date)) { + throw new TypeError("Argument is not a Date object"); + } + if (isNaN(V)) { + return undefined; + } + + return V; +}; + +conversions["RegExp"] = function (V, opts) { + if (!(V instanceof RegExp)) { + V = new RegExp(V); + } + + return V; +}; + + +/***/ }), + +/***/ 3184: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +const usm = __nccwpck_require__(905); + +exports.implementation = class URLImpl { + constructor(constructorArgs) { + const url = constructorArgs[0]; + const base = constructorArgs[1]; + + let parsedBase = null; + if (base !== undefined) { + parsedBase = usm.basicURLParse(base); + if (parsedBase === "failure") { + throw new TypeError("Invalid base URL"); + } + } + + const parsedURL = usm.basicURLParse(url, { baseURL: parsedBase }); + if (parsedURL === "failure") { + throw new TypeError("Invalid URL"); + } + + this._url = parsedURL; + + // TODO: query stuff + } + + get href() { + return usm.serializeURL(this._url); + } + + set href(v) { + const parsedURL = usm.basicURLParse(v); + if (parsedURL === "failure") { + throw new TypeError("Invalid URL"); + } + + this._url = parsedURL; + } + + get origin() { + return usm.serializeURLOrigin(this._url); + } + + get protocol() { + return this._url.scheme + ":"; + } + + set protocol(v) { + usm.basicURLParse(v + ":", { url: this._url, stateOverride: "scheme start" }); + } + + get username() { + return this._url.username; + } + + set username(v) { + if (usm.cannotHaveAUsernamePasswordPort(this._url)) { + return; + } + + usm.setTheUsername(this._url, v); + } + + get password() { + return this._url.password; + } + + set password(v) { + if (usm.cannotHaveAUsernamePasswordPort(this._url)) { + return; + } + + usm.setThePassword(this._url, v); + } + + get host() { + const url = this._url; + + if (url.host === null) { + return ""; + } + + if (url.port === null) { + return usm.serializeHost(url.host); + } + + return usm.serializeHost(url.host) + ":" + usm.serializeInteger(url.port); + } + + set host(v) { + if (this._url.cannotBeABaseURL) { + return; + } + + usm.basicURLParse(v, { url: this._url, stateOverride: "host" }); + } + + get hostname() { + if (this._url.host === null) { + return ""; + } + + return usm.serializeHost(this._url.host); + } + + set hostname(v) { + if (this._url.cannotBeABaseURL) { + return; + } + + usm.basicURLParse(v, { url: this._url, stateOverride: "hostname" }); + } + + get port() { + if (this._url.port === null) { + return ""; + } + + return usm.serializeInteger(this._url.port); + } + + set port(v) { + if (usm.cannotHaveAUsernamePasswordPort(this._url)) { + return; + } + + if (v === "") { + this._url.port = null; + } else { + usm.basicURLParse(v, { url: this._url, stateOverride: "port" }); + } + } + + get pathname() { + if (this._url.cannotBeABaseURL) { + return this._url.path[0]; + } + + if (this._url.path.length === 0) { + return ""; + } + + return "/" + this._url.path.join("/"); + } + + set pathname(v) { + if (this._url.cannotBeABaseURL) { + return; + } + + this._url.path = []; + usm.basicURLParse(v, { url: this._url, stateOverride: "path start" }); + } + + get search() { + if (this._url.query === null || this._url.query === "") { + return ""; + } + + return "?" + this._url.query; + } + + set search(v) { + // TODO: query stuff + + const url = this._url; + + if (v === "") { + url.query = null; + return; + } + + const input = v[0] === "?" ? v.substring(1) : v; + url.query = ""; + usm.basicURLParse(input, { url, stateOverride: "query" }); + } + + get hash() { + if (this._url.fragment === null || this._url.fragment === "") { + return ""; + } + + return "#" + this._url.fragment; + } + + set hash(v) { + if (v === "") { + this._url.fragment = null; + return; + } + + const input = v[0] === "#" ? v.substring(1) : v; + this._url.fragment = ""; + usm.basicURLParse(input, { url: this._url, stateOverride: "fragment" }); + } + + toJSON() { + return this.href; + } +}; + + +/***/ }), + +/***/ 6633: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const conversions = __nccwpck_require__(7125); +const utils = __nccwpck_require__(9857); +const Impl = __nccwpck_require__(3184); + +const impl = utils.implSymbol; + +function URL(url) { + if (!this || this[impl] || !(this instanceof URL)) { + throw new TypeError("Failed to construct 'URL': Please use the 'new' operator, this DOM object constructor cannot be called as a function."); + } + if (arguments.length < 1) { + throw new TypeError("Failed to construct 'URL': 1 argument required, but only " + arguments.length + " present."); + } + const args = []; + for (let i = 0; i < arguments.length && i < 2; ++i) { + args[i] = arguments[i]; + } + args[0] = conversions["USVString"](args[0]); + if (args[1] !== undefined) { + args[1] = conversions["USVString"](args[1]); + } + + module.exports.setup(this, args); +} + +URL.prototype.toJSON = function toJSON() { + if (!this || !module.exports.is(this)) { + throw new TypeError("Illegal invocation"); + } + const args = []; + for (let i = 0; i < arguments.length && i < 0; ++i) { + args[i] = arguments[i]; + } + return this[impl].toJSON.apply(this[impl], args); +}; +Object.defineProperty(URL.prototype, "href", { + get() { + return this[impl].href; + }, + set(V) { + V = conversions["USVString"](V); + this[impl].href = V; + }, + enumerable: true, + configurable: true +}); + +URL.prototype.toString = function () { + if (!this || !module.exports.is(this)) { + throw new TypeError("Illegal invocation"); + } + return this.href; +}; + +Object.defineProperty(URL.prototype, "origin", { + get() { + return this[impl].origin; + }, + enumerable: true, + configurable: true +}); + +Object.defineProperty(URL.prototype, "protocol", { + get() { + return this[impl].protocol; + }, + set(V) { + V = conversions["USVString"](V); + this[impl].protocol = V; + }, + enumerable: true, + configurable: true +}); + +Object.defineProperty(URL.prototype, "username", { + get() { + return this[impl].username; + }, + set(V) { + V = conversions["USVString"](V); + this[impl].username = V; + }, + enumerable: true, + configurable: true +}); + +Object.defineProperty(URL.prototype, "password", { + get() { + return this[impl].password; + }, + set(V) { + V = conversions["USVString"](V); + this[impl].password = V; + }, + enumerable: true, + configurable: true +}); + +Object.defineProperty(URL.prototype, "host", { + get() { + return this[impl].host; + }, + set(V) { + V = conversions["USVString"](V); + this[impl].host = V; + }, + enumerable: true, + configurable: true +}); + +Object.defineProperty(URL.prototype, "hostname", { + get() { + return this[impl].hostname; + }, + set(V) { + V = conversions["USVString"](V); + this[impl].hostname = V; + }, + enumerable: true, + configurable: true +}); + +Object.defineProperty(URL.prototype, "port", { + get() { + return this[impl].port; + }, + set(V) { + V = conversions["USVString"](V); + this[impl].port = V; + }, + enumerable: true, + configurable: true +}); + +Object.defineProperty(URL.prototype, "pathname", { + get() { + return this[impl].pathname; + }, + set(V) { + V = conversions["USVString"](V); + this[impl].pathname = V; + }, + enumerable: true, + configurable: true +}); + +Object.defineProperty(URL.prototype, "search", { + get() { + return this[impl].search; + }, + set(V) { + V = conversions["USVString"](V); + this[impl].search = V; + }, + enumerable: true, + configurable: true +}); + +Object.defineProperty(URL.prototype, "hash", { + get() { + return this[impl].hash; + }, + set(V) { + V = conversions["USVString"](V); + this[impl].hash = V; + }, + enumerable: true, + configurable: true +}); + + +module.exports = { + is(obj) { + return !!obj && obj[impl] instanceof Impl.implementation; + }, + create(constructorArgs, privateData) { + let obj = Object.create(URL.prototype); + this.setup(obj, constructorArgs, privateData); + return obj; + }, + setup(obj, constructorArgs, privateData) { + if (!privateData) privateData = {}; + privateData.wrapper = obj; + + obj[impl] = new Impl.implementation(constructorArgs, privateData); + obj[impl][utils.wrapperSymbol] = obj; + }, + interface: URL, + expose: { + Window: { URL: URL }, + Worker: { URL: URL } + } +}; + + + +/***/ }), + +/***/ 2686: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +exports.URL = __nccwpck_require__(6633)["interface"]; +exports.serializeURL = __nccwpck_require__(905).serializeURL; +exports.serializeURLOrigin = __nccwpck_require__(905).serializeURLOrigin; +exports.basicURLParse = __nccwpck_require__(905).basicURLParse; +exports.setTheUsername = __nccwpck_require__(905).setTheUsername; +exports.setThePassword = __nccwpck_require__(905).setThePassword; +exports.serializeHost = __nccwpck_require__(905).serializeHost; +exports.serializeInteger = __nccwpck_require__(905).serializeInteger; +exports.parseURL = __nccwpck_require__(905).parseURL; + + +/***/ }), + +/***/ 905: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + +const punycode = __nccwpck_require__(4876); +const tr46 = __nccwpck_require__(1552); + +const specialSchemes = { + ftp: 21, + file: null, + gopher: 70, + http: 80, + https: 443, + ws: 80, + wss: 443 +}; + +const failure = Symbol("failure"); + +function countSymbols(str) { + return punycode.ucs2.decode(str).length; +} + +function at(input, idx) { + const c = input[idx]; + return isNaN(c) ? undefined : String.fromCodePoint(c); +} + +function isASCIIDigit(c) { + return c >= 0x30 && c <= 0x39; +} + +function isASCIIAlpha(c) { + return (c >= 0x41 && c <= 0x5A) || (c >= 0x61 && c <= 0x7A); +} + +function isASCIIAlphanumeric(c) { + return isASCIIAlpha(c) || isASCIIDigit(c); +} + +function isASCIIHex(c) { + return isASCIIDigit(c) || (c >= 0x41 && c <= 0x46) || (c >= 0x61 && c <= 0x66); +} + +function isSingleDot(buffer) { + return buffer === "." || buffer.toLowerCase() === "%2e"; +} + +function isDoubleDot(buffer) { + buffer = buffer.toLowerCase(); + return buffer === ".." || buffer === "%2e." || buffer === ".%2e" || buffer === "%2e%2e"; +} + +function isWindowsDriveLetterCodePoints(cp1, cp2) { + return isASCIIAlpha(cp1) && (cp2 === 58 || cp2 === 124); +} + +function isWindowsDriveLetterString(string) { + return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && (string[1] === ":" || string[1] === "|"); +} + +function isNormalizedWindowsDriveLetterString(string) { + return string.length === 2 && isASCIIAlpha(string.codePointAt(0)) && string[1] === ":"; +} + +function containsForbiddenHostCodePoint(string) { + return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|%|\/|:|\?|@|\[|\\|\]/) !== -1; +} + +function containsForbiddenHostCodePointExcludingPercent(string) { + return string.search(/\u0000|\u0009|\u000A|\u000D|\u0020|#|\/|:|\?|@|\[|\\|\]/) !== -1; +} + +function isSpecialScheme(scheme) { + return specialSchemes[scheme] !== undefined; +} + +function isSpecial(url) { + return isSpecialScheme(url.scheme); +} + +function defaultPort(scheme) { + return specialSchemes[scheme]; +} + +function percentEncode(c) { + let hex = c.toString(16).toUpperCase(); + if (hex.length === 1) { + hex = "0" + hex; + } + + return "%" + hex; +} + +function utf8PercentEncode(c) { + const buf = new Buffer(c); + + let str = ""; + + for (let i = 0; i < buf.length; ++i) { + str += percentEncode(buf[i]); + } + + return str; +} + +function utf8PercentDecode(str) { + const input = new Buffer(str); + const output = []; + for (let i = 0; i < input.length; ++i) { + if (input[i] !== 37) { + output.push(input[i]); + } else if (input[i] === 37 && isASCIIHex(input[i + 1]) && isASCIIHex(input[i + 2])) { + output.push(parseInt(input.slice(i + 1, i + 3).toString(), 16)); + i += 2; + } else { + output.push(input[i]); + } + } + return new Buffer(output).toString(); +} + +function isC0ControlPercentEncode(c) { + return c <= 0x1F || c > 0x7E; +} + +const extraPathPercentEncodeSet = new Set([32, 34, 35, 60, 62, 63, 96, 123, 125]); +function isPathPercentEncode(c) { + return isC0ControlPercentEncode(c) || extraPathPercentEncodeSet.has(c); +} + +const extraUserinfoPercentEncodeSet = + new Set([47, 58, 59, 61, 64, 91, 92, 93, 94, 124]); +function isUserinfoPercentEncode(c) { + return isPathPercentEncode(c) || extraUserinfoPercentEncodeSet.has(c); +} + +function percentEncodeChar(c, encodeSetPredicate) { + const cStr = String.fromCodePoint(c); + + if (encodeSetPredicate(c)) { + return utf8PercentEncode(cStr); + } + + return cStr; +} + +function parseIPv4Number(input) { + let R = 10; + + if (input.length >= 2 && input.charAt(0) === "0" && input.charAt(1).toLowerCase() === "x") { + input = input.substring(2); + R = 16; + } else if (input.length >= 2 && input.charAt(0) === "0") { + input = input.substring(1); + R = 8; + } + + if (input === "") { + return 0; + } + + const regex = R === 10 ? /[^0-9]/ : (R === 16 ? /[^0-9A-Fa-f]/ : /[^0-7]/); + if (regex.test(input)) { + return failure; + } + + return parseInt(input, R); +} + +function parseIPv4(input) { + const parts = input.split("."); + if (parts[parts.length - 1] === "") { + if (parts.length > 1) { + parts.pop(); + } + } + + if (parts.length > 4) { + return input; + } + + const numbers = []; + for (const part of parts) { + if (part === "") { + return input; + } + const n = parseIPv4Number(part); + if (n === failure) { + return input; + } + + numbers.push(n); + } + + for (let i = 0; i < numbers.length - 1; ++i) { + if (numbers[i] > 255) { + return failure; + } + } + if (numbers[numbers.length - 1] >= Math.pow(256, 5 - numbers.length)) { + return failure; + } + + let ipv4 = numbers.pop(); + let counter = 0; + + for (const n of numbers) { + ipv4 += n * Math.pow(256, 3 - counter); + ++counter; + } + + return ipv4; +} + +function serializeIPv4(address) { + let output = ""; + let n = address; + + for (let i = 1; i <= 4; ++i) { + output = String(n % 256) + output; + if (i !== 4) { + output = "." + output; + } + n = Math.floor(n / 256); + } + + return output; +} + +function parseIPv6(input) { + const address = [0, 0, 0, 0, 0, 0, 0, 0]; + let pieceIndex = 0; + let compress = null; + let pointer = 0; + + input = punycode.ucs2.decode(input); + + if (input[pointer] === 58) { + if (input[pointer + 1] !== 58) { + return failure; + } + + pointer += 2; + ++pieceIndex; + compress = pieceIndex; + } + + while (pointer < input.length) { + if (pieceIndex === 8) { + return failure; + } + + if (input[pointer] === 58) { + if (compress !== null) { + return failure; + } + ++pointer; + ++pieceIndex; + compress = pieceIndex; + continue; + } + + let value = 0; + let length = 0; + + while (length < 4 && isASCIIHex(input[pointer])) { + value = value * 0x10 + parseInt(at(input, pointer), 16); + ++pointer; + ++length; + } + + if (input[pointer] === 46) { + if (length === 0) { + return failure; + } + + pointer -= length; + + if (pieceIndex > 6) { + return failure; + } + + let numbersSeen = 0; + + while (input[pointer] !== undefined) { + let ipv4Piece = null; + + if (numbersSeen > 0) { + if (input[pointer] === 46 && numbersSeen < 4) { + ++pointer; + } else { + return failure; + } + } + + if (!isASCIIDigit(input[pointer])) { + return failure; + } + + while (isASCIIDigit(input[pointer])) { + const number = parseInt(at(input, pointer)); + if (ipv4Piece === null) { + ipv4Piece = number; + } else if (ipv4Piece === 0) { + return failure; + } else { + ipv4Piece = ipv4Piece * 10 + number; + } + if (ipv4Piece > 255) { + return failure; + } + ++pointer; + } + + address[pieceIndex] = address[pieceIndex] * 0x100 + ipv4Piece; + + ++numbersSeen; + + if (numbersSeen === 2 || numbersSeen === 4) { + ++pieceIndex; + } + } + + if (numbersSeen !== 4) { + return failure; + } + + break; + } else if (input[pointer] === 58) { + ++pointer; + if (input[pointer] === undefined) { + return failure; + } + } else if (input[pointer] !== undefined) { + return failure; + } + + address[pieceIndex] = value; + ++pieceIndex; + } + + if (compress !== null) { + let swaps = pieceIndex - compress; + pieceIndex = 7; + while (pieceIndex !== 0 && swaps > 0) { + const temp = address[compress + swaps - 1]; + address[compress + swaps - 1] = address[pieceIndex]; + address[pieceIndex] = temp; + --pieceIndex; + --swaps; + } + } else if (compress === null && pieceIndex !== 8) { + return failure; + } + + return address; +} + +function serializeIPv6(address) { + let output = ""; + const seqResult = findLongestZeroSequence(address); + const compress = seqResult.idx; + let ignore0 = false; + + for (let pieceIndex = 0; pieceIndex <= 7; ++pieceIndex) { + if (ignore0 && address[pieceIndex] === 0) { + continue; + } else if (ignore0) { + ignore0 = false; + } + + if (compress === pieceIndex) { + const separator = pieceIndex === 0 ? "::" : ":"; + output += separator; + ignore0 = true; + continue; + } + + output += address[pieceIndex].toString(16); + + if (pieceIndex !== 7) { + output += ":"; + } + } + + return output; +} + +function parseHost(input, isSpecialArg) { + if (input[0] === "[") { + if (input[input.length - 1] !== "]") { + return failure; + } + + return parseIPv6(input.substring(1, input.length - 1)); + } + + if (!isSpecialArg) { + return parseOpaqueHost(input); + } + + const domain = utf8PercentDecode(input); + const asciiDomain = tr46.toASCII(domain, false, tr46.PROCESSING_OPTIONS.NONTRANSITIONAL, false); + if (asciiDomain === null) { + return failure; + } + + if (containsForbiddenHostCodePoint(asciiDomain)) { + return failure; + } + + const ipv4Host = parseIPv4(asciiDomain); + if (typeof ipv4Host === "number" || ipv4Host === failure) { + return ipv4Host; + } + + return asciiDomain; +} + +function parseOpaqueHost(input) { + if (containsForbiddenHostCodePointExcludingPercent(input)) { + return failure; + } + + let output = ""; + const decoded = punycode.ucs2.decode(input); + for (let i = 0; i < decoded.length; ++i) { + output += percentEncodeChar(decoded[i], isC0ControlPercentEncode); + } + return output; +} + +function findLongestZeroSequence(arr) { + let maxIdx = null; + let maxLen = 1; // only find elements > 1 + let currStart = null; + let currLen = 0; + + for (let i = 0; i < arr.length; ++i) { + if (arr[i] !== 0) { + if (currLen > maxLen) { + maxIdx = currStart; + maxLen = currLen; + } + + currStart = null; + currLen = 0; + } else { + if (currStart === null) { + currStart = i; + } + ++currLen; + } + } + + // if trailing zeros + if (currLen > maxLen) { + maxIdx = currStart; + maxLen = currLen; + } + + return { + idx: maxIdx, + len: maxLen + }; +} + +function serializeHost(host) { + if (typeof host === "number") { + return serializeIPv4(host); + } + + // IPv6 serializer + if (host instanceof Array) { + return "[" + serializeIPv6(host) + "]"; + } + + return host; +} + +function trimControlChars(url) { + return url.replace(/^[\u0000-\u001F\u0020]+|[\u0000-\u001F\u0020]+$/g, ""); +} + +function trimTabAndNewline(url) { + return url.replace(/\u0009|\u000A|\u000D/g, ""); +} + +function shortenPath(url) { + const path = url.path; + if (path.length === 0) { + return; + } + if (url.scheme === "file" && path.length === 1 && isNormalizedWindowsDriveLetter(path[0])) { + return; + } + + path.pop(); +} + +function includesCredentials(url) { + return url.username !== "" || url.password !== ""; +} + +function cannotHaveAUsernamePasswordPort(url) { + return url.host === null || url.host === "" || url.cannotBeABaseURL || url.scheme === "file"; +} + +function isNormalizedWindowsDriveLetter(string) { + return /^[A-Za-z]:$/.test(string); +} + +function URLStateMachine(input, base, encodingOverride, url, stateOverride) { + this.pointer = 0; + this.input = input; + this.base = base || null; + this.encodingOverride = encodingOverride || "utf-8"; + this.stateOverride = stateOverride; + this.url = url; + this.failure = false; + this.parseError = false; + + if (!this.url) { + this.url = { + scheme: "", + username: "", + password: "", + host: null, + port: null, + path: [], + query: null, + fragment: null, + + cannotBeABaseURL: false + }; + + const res = trimControlChars(this.input); + if (res !== this.input) { + this.parseError = true; + } + this.input = res; + } + + const res = trimTabAndNewline(this.input); + if (res !== this.input) { + this.parseError = true; + } + this.input = res; + + this.state = stateOverride || "scheme start"; + + this.buffer = ""; + this.atFlag = false; + this.arrFlag = false; + this.passwordTokenSeenFlag = false; + + this.input = punycode.ucs2.decode(this.input); + + for (; this.pointer <= this.input.length; ++this.pointer) { + const c = this.input[this.pointer]; + const cStr = isNaN(c) ? undefined : String.fromCodePoint(c); + + // exec state machine + const ret = this["parse " + this.state](c, cStr); + if (!ret) { + break; // terminate algorithm + } else if (ret === failure) { + this.failure = true; + break; + } + } +} + +URLStateMachine.prototype["parse scheme start"] = function parseSchemeStart(c, cStr) { + if (isASCIIAlpha(c)) { + this.buffer += cStr.toLowerCase(); + this.state = "scheme"; + } else if (!this.stateOverride) { + this.state = "no scheme"; + --this.pointer; + } else { + this.parseError = true; + return failure; + } + + return true; +}; + +URLStateMachine.prototype["parse scheme"] = function parseScheme(c, cStr) { + if (isASCIIAlphanumeric(c) || c === 43 || c === 45 || c === 46) { + this.buffer += cStr.toLowerCase(); + } else if (c === 58) { + if (this.stateOverride) { + if (isSpecial(this.url) && !isSpecialScheme(this.buffer)) { + return false; + } + + if (!isSpecial(this.url) && isSpecialScheme(this.buffer)) { + return false; + } + + if ((includesCredentials(this.url) || this.url.port !== null) && this.buffer === "file") { + return false; + } + + if (this.url.scheme === "file" && (this.url.host === "" || this.url.host === null)) { + return false; + } + } + this.url.scheme = this.buffer; + this.buffer = ""; + if (this.stateOverride) { + return false; + } + if (this.url.scheme === "file") { + if (this.input[this.pointer + 1] !== 47 || this.input[this.pointer + 2] !== 47) { + this.parseError = true; + } + this.state = "file"; + } else if (isSpecial(this.url) && this.base !== null && this.base.scheme === this.url.scheme) { + this.state = "special relative or authority"; + } else if (isSpecial(this.url)) { + this.state = "special authority slashes"; + } else if (this.input[this.pointer + 1] === 47) { + this.state = "path or authority"; + ++this.pointer; + } else { + this.url.cannotBeABaseURL = true; + this.url.path.push(""); + this.state = "cannot-be-a-base-URL path"; + } + } else if (!this.stateOverride) { + this.buffer = ""; + this.state = "no scheme"; + this.pointer = -1; + } else { + this.parseError = true; + return failure; + } + + return true; +}; + +URLStateMachine.prototype["parse no scheme"] = function parseNoScheme(c) { + if (this.base === null || (this.base.cannotBeABaseURL && c !== 35)) { + return failure; + } else if (this.base.cannotBeABaseURL && c === 35) { + this.url.scheme = this.base.scheme; + this.url.path = this.base.path.slice(); + this.url.query = this.base.query; + this.url.fragment = ""; + this.url.cannotBeABaseURL = true; + this.state = "fragment"; + } else if (this.base.scheme === "file") { + this.state = "file"; + --this.pointer; + } else { + this.state = "relative"; + --this.pointer; + } + + return true; +}; + +URLStateMachine.prototype["parse special relative or authority"] = function parseSpecialRelativeOrAuthority(c) { + if (c === 47 && this.input[this.pointer + 1] === 47) { + this.state = "special authority ignore slashes"; + ++this.pointer; + } else { + this.parseError = true; + this.state = "relative"; + --this.pointer; + } + + return true; +}; + +URLStateMachine.prototype["parse path or authority"] = function parsePathOrAuthority(c) { + if (c === 47) { + this.state = "authority"; + } else { + this.state = "path"; + --this.pointer; + } + + return true; +}; + +URLStateMachine.prototype["parse relative"] = function parseRelative(c) { + this.url.scheme = this.base.scheme; + if (isNaN(c)) { + this.url.username = this.base.username; + this.url.password = this.base.password; + this.url.host = this.base.host; + this.url.port = this.base.port; + this.url.path = this.base.path.slice(); + this.url.query = this.base.query; + } else if (c === 47) { + this.state = "relative slash"; + } else if (c === 63) { + this.url.username = this.base.username; + this.url.password = this.base.password; + this.url.host = this.base.host; + this.url.port = this.base.port; + this.url.path = this.base.path.slice(); + this.url.query = ""; + this.state = "query"; + } else if (c === 35) { + this.url.username = this.base.username; + this.url.password = this.base.password; + this.url.host = this.base.host; + this.url.port = this.base.port; + this.url.path = this.base.path.slice(); + this.url.query = this.base.query; + this.url.fragment = ""; + this.state = "fragment"; + } else if (isSpecial(this.url) && c === 92) { + this.parseError = true; + this.state = "relative slash"; + } else { + this.url.username = this.base.username; + this.url.password = this.base.password; + this.url.host = this.base.host; + this.url.port = this.base.port; + this.url.path = this.base.path.slice(0, this.base.path.length - 1); + + this.state = "path"; + --this.pointer; + } + + return true; +}; + +URLStateMachine.prototype["parse relative slash"] = function parseRelativeSlash(c) { + if (isSpecial(this.url) && (c === 47 || c === 92)) { + if (c === 92) { + this.parseError = true; + } + this.state = "special authority ignore slashes"; + } else if (c === 47) { + this.state = "authority"; + } else { + this.url.username = this.base.username; + this.url.password = this.base.password; + this.url.host = this.base.host; + this.url.port = this.base.port; + this.state = "path"; + --this.pointer; + } + + return true; +}; + +URLStateMachine.prototype["parse special authority slashes"] = function parseSpecialAuthoritySlashes(c) { + if (c === 47 && this.input[this.pointer + 1] === 47) { + this.state = "special authority ignore slashes"; + ++this.pointer; + } else { + this.parseError = true; + this.state = "special authority ignore slashes"; + --this.pointer; + } + + return true; +}; + +URLStateMachine.prototype["parse special authority ignore slashes"] = function parseSpecialAuthorityIgnoreSlashes(c) { + if (c !== 47 && c !== 92) { + this.state = "authority"; + --this.pointer; + } else { + this.parseError = true; + } + + return true; +}; + +URLStateMachine.prototype["parse authority"] = function parseAuthority(c, cStr) { + if (c === 64) { + this.parseError = true; + if (this.atFlag) { + this.buffer = "%40" + this.buffer; + } + this.atFlag = true; + + // careful, this is based on buffer and has its own pointer (this.pointer != pointer) and inner chars + const len = countSymbols(this.buffer); + for (let pointer = 0; pointer < len; ++pointer) { + const codePoint = this.buffer.codePointAt(pointer); + + if (codePoint === 58 && !this.passwordTokenSeenFlag) { + this.passwordTokenSeenFlag = true; + continue; + } + const encodedCodePoints = percentEncodeChar(codePoint, isUserinfoPercentEncode); + if (this.passwordTokenSeenFlag) { + this.url.password += encodedCodePoints; + } else { + this.url.username += encodedCodePoints; + } + } + this.buffer = ""; + } else if (isNaN(c) || c === 47 || c === 63 || c === 35 || + (isSpecial(this.url) && c === 92)) { + if (this.atFlag && this.buffer === "") { + this.parseError = true; + return failure; + } + this.pointer -= countSymbols(this.buffer) + 1; + this.buffer = ""; + this.state = "host"; + } else { + this.buffer += cStr; + } + + return true; +}; + +URLStateMachine.prototype["parse hostname"] = +URLStateMachine.prototype["parse host"] = function parseHostName(c, cStr) { + if (this.stateOverride && this.url.scheme === "file") { + --this.pointer; + this.state = "file host"; + } else if (c === 58 && !this.arrFlag) { + if (this.buffer === "") { + this.parseError = true; + return failure; + } + + const host = parseHost(this.buffer, isSpecial(this.url)); + if (host === failure) { + return failure; + } + + this.url.host = host; + this.buffer = ""; + this.state = "port"; + if (this.stateOverride === "hostname") { + return false; + } + } else if (isNaN(c) || c === 47 || c === 63 || c === 35 || + (isSpecial(this.url) && c === 92)) { + --this.pointer; + if (isSpecial(this.url) && this.buffer === "") { + this.parseError = true; + return failure; + } else if (this.stateOverride && this.buffer === "" && + (includesCredentials(this.url) || this.url.port !== null)) { + this.parseError = true; + return false; + } + + const host = parseHost(this.buffer, isSpecial(this.url)); + if (host === failure) { + return failure; + } + + this.url.host = host; + this.buffer = ""; + this.state = "path start"; + if (this.stateOverride) { + return false; + } + } else { + if (c === 91) { + this.arrFlag = true; + } else if (c === 93) { + this.arrFlag = false; + } + this.buffer += cStr; + } + + return true; +}; + +URLStateMachine.prototype["parse port"] = function parsePort(c, cStr) { + if (isASCIIDigit(c)) { + this.buffer += cStr; + } else if (isNaN(c) || c === 47 || c === 63 || c === 35 || + (isSpecial(this.url) && c === 92) || + this.stateOverride) { + if (this.buffer !== "") { + const port = parseInt(this.buffer); + if (port > Math.pow(2, 16) - 1) { + this.parseError = true; + return failure; + } + this.url.port = port === defaultPort(this.url.scheme) ? null : port; + this.buffer = ""; + } + if (this.stateOverride) { + return false; + } + this.state = "path start"; + --this.pointer; + } else { + this.parseError = true; + return failure; + } + + return true; +}; + +const fileOtherwiseCodePoints = new Set([47, 92, 63, 35]); + +URLStateMachine.prototype["parse file"] = function parseFile(c) { + this.url.scheme = "file"; + + if (c === 47 || c === 92) { + if (c === 92) { + this.parseError = true; + } + this.state = "file slash"; + } else if (this.base !== null && this.base.scheme === "file") { + if (isNaN(c)) { + this.url.host = this.base.host; + this.url.path = this.base.path.slice(); + this.url.query = this.base.query; + } else if (c === 63) { + this.url.host = this.base.host; + this.url.path = this.base.path.slice(); + this.url.query = ""; + this.state = "query"; + } else if (c === 35) { + this.url.host = this.base.host; + this.url.path = this.base.path.slice(); + this.url.query = this.base.query; + this.url.fragment = ""; + this.state = "fragment"; + } else { + if (this.input.length - this.pointer - 1 === 0 || // remaining consists of 0 code points + !isWindowsDriveLetterCodePoints(c, this.input[this.pointer + 1]) || + (this.input.length - this.pointer - 1 >= 2 && // remaining has at least 2 code points + !fileOtherwiseCodePoints.has(this.input[this.pointer + 2]))) { + this.url.host = this.base.host; + this.url.path = this.base.path.slice(); + shortenPath(this.url); + } else { + this.parseError = true; + } + + this.state = "path"; + --this.pointer; + } + } else { + this.state = "path"; + --this.pointer; + } + + return true; +}; + +URLStateMachine.prototype["parse file slash"] = function parseFileSlash(c) { + if (c === 47 || c === 92) { + if (c === 92) { + this.parseError = true; + } + this.state = "file host"; + } else { + if (this.base !== null && this.base.scheme === "file") { + if (isNormalizedWindowsDriveLetterString(this.base.path[0])) { + this.url.path.push(this.base.path[0]); + } else { + this.url.host = this.base.host; + } + } + this.state = "path"; + --this.pointer; + } + + return true; +}; + +URLStateMachine.prototype["parse file host"] = function parseFileHost(c, cStr) { + if (isNaN(c) || c === 47 || c === 92 || c === 63 || c === 35) { + --this.pointer; + if (!this.stateOverride && isWindowsDriveLetterString(this.buffer)) { + this.parseError = true; + this.state = "path"; + } else if (this.buffer === "") { + this.url.host = ""; + if (this.stateOverride) { + return false; + } + this.state = "path start"; + } else { + let host = parseHost(this.buffer, isSpecial(this.url)); + if (host === failure) { + return failure; + } + if (host === "localhost") { + host = ""; + } + this.url.host = host; + + if (this.stateOverride) { + return false; + } + + this.buffer = ""; + this.state = "path start"; + } + } else { + this.buffer += cStr; + } + + return true; +}; + +URLStateMachine.prototype["parse path start"] = function parsePathStart(c) { + if (isSpecial(this.url)) { + if (c === 92) { + this.parseError = true; + } + this.state = "path"; + + if (c !== 47 && c !== 92) { + --this.pointer; + } + } else if (!this.stateOverride && c === 63) { + this.url.query = ""; + this.state = "query"; + } else if (!this.stateOverride && c === 35) { + this.url.fragment = ""; + this.state = "fragment"; + } else if (c !== undefined) { + this.state = "path"; + if (c !== 47) { + --this.pointer; + } + } + + return true; +}; + +URLStateMachine.prototype["parse path"] = function parsePath(c) { + if (isNaN(c) || c === 47 || (isSpecial(this.url) && c === 92) || + (!this.stateOverride && (c === 63 || c === 35))) { + if (isSpecial(this.url) && c === 92) { + this.parseError = true; + } + + if (isDoubleDot(this.buffer)) { + shortenPath(this.url); + if (c !== 47 && !(isSpecial(this.url) && c === 92)) { + this.url.path.push(""); + } + } else if (isSingleDot(this.buffer) && c !== 47 && + !(isSpecial(this.url) && c === 92)) { + this.url.path.push(""); + } else if (!isSingleDot(this.buffer)) { + if (this.url.scheme === "file" && this.url.path.length === 0 && isWindowsDriveLetterString(this.buffer)) { + if (this.url.host !== "" && this.url.host !== null) { + this.parseError = true; + this.url.host = ""; + } + this.buffer = this.buffer[0] + ":"; + } + this.url.path.push(this.buffer); + } + this.buffer = ""; + if (this.url.scheme === "file" && (c === undefined || c === 63 || c === 35)) { + while (this.url.path.length > 1 && this.url.path[0] === "") { + this.parseError = true; + this.url.path.shift(); + } + } + if (c === 63) { + this.url.query = ""; + this.state = "query"; + } + if (c === 35) { + this.url.fragment = ""; + this.state = "fragment"; + } + } else { + // TODO: If c is not a URL code point and not "%", parse error. + + if (c === 37 && + (!isASCIIHex(this.input[this.pointer + 1]) || + !isASCIIHex(this.input[this.pointer + 2]))) { + this.parseError = true; + } + + this.buffer += percentEncodeChar(c, isPathPercentEncode); + } + + return true; +}; + +URLStateMachine.prototype["parse cannot-be-a-base-URL path"] = function parseCannotBeABaseURLPath(c) { + if (c === 63) { + this.url.query = ""; + this.state = "query"; + } else if (c === 35) { + this.url.fragment = ""; + this.state = "fragment"; + } else { + // TODO: Add: not a URL code point + if (!isNaN(c) && c !== 37) { + this.parseError = true; + } + + if (c === 37 && + (!isASCIIHex(this.input[this.pointer + 1]) || + !isASCIIHex(this.input[this.pointer + 2]))) { + this.parseError = true; + } + + if (!isNaN(c)) { + this.url.path[0] = this.url.path[0] + percentEncodeChar(c, isC0ControlPercentEncode); + } + } + + return true; +}; + +URLStateMachine.prototype["parse query"] = function parseQuery(c, cStr) { + if (isNaN(c) || (!this.stateOverride && c === 35)) { + if (!isSpecial(this.url) || this.url.scheme === "ws" || this.url.scheme === "wss") { + this.encodingOverride = "utf-8"; + } + + const buffer = new Buffer(this.buffer); // TODO: Use encoding override instead + for (let i = 0; i < buffer.length; ++i) { + if (buffer[i] < 0x21 || buffer[i] > 0x7E || buffer[i] === 0x22 || buffer[i] === 0x23 || + buffer[i] === 0x3C || buffer[i] === 0x3E) { + this.url.query += percentEncode(buffer[i]); + } else { + this.url.query += String.fromCodePoint(buffer[i]); + } + } + + this.buffer = ""; + if (c === 35) { + this.url.fragment = ""; + this.state = "fragment"; + } + } else { + // TODO: If c is not a URL code point and not "%", parse error. + if (c === 37 && + (!isASCIIHex(this.input[this.pointer + 1]) || + !isASCIIHex(this.input[this.pointer + 2]))) { + this.parseError = true; + } + + this.buffer += cStr; + } + + return true; +}; + +URLStateMachine.prototype["parse fragment"] = function parseFragment(c) { + if (isNaN(c)) { // do nothing + } else if (c === 0x0) { + this.parseError = true; + } else { + // TODO: If c is not a URL code point and not "%", parse error. + if (c === 37 && + (!isASCIIHex(this.input[this.pointer + 1]) || + !isASCIIHex(this.input[this.pointer + 2]))) { + this.parseError = true; + } + + this.url.fragment += percentEncodeChar(c, isC0ControlPercentEncode); + } + + return true; +}; + +function serializeURL(url, excludeFragment) { + let output = url.scheme + ":"; + if (url.host !== null) { + output += "//"; + + if (url.username !== "" || url.password !== "") { + output += url.username; + if (url.password !== "") { + output += ":" + url.password; + } + output += "@"; + } + + output += serializeHost(url.host); + + if (url.port !== null) { + output += ":" + url.port; + } + } else if (url.host === null && url.scheme === "file") { + output += "//"; + } + + if (url.cannotBeABaseURL) { + output += url.path[0]; + } else { + for (const string of url.path) { + output += "/" + string; + } + } + + if (url.query !== null) { + output += "?" + url.query; + } + + if (!excludeFragment && url.fragment !== null) { + output += "#" + url.fragment; + } + + return output; +} + +function serializeOrigin(tuple) { + let result = tuple.scheme + "://"; + result += serializeHost(tuple.host); + + if (tuple.port !== null) { + result += ":" + tuple.port; + } + + return result; +} + +module.exports.serializeURL = serializeURL; + +module.exports.serializeURLOrigin = function (url) { + // https://url.spec.whatwg.org/#concept-url-origin + switch (url.scheme) { + case "blob": + try { + return module.exports.serializeURLOrigin(module.exports.parseURL(url.path[0])); + } catch (e) { + // serializing an opaque origin returns "null" + return "null"; + } + case "ftp": + case "gopher": + case "http": + case "https": + case "ws": + case "wss": + return serializeOrigin({ + scheme: url.scheme, + host: url.host, + port: url.port + }); + case "file": + // spec says "exercise to the reader", chrome says "file://" + return "file://"; + default: + // serializing an opaque origin returns "null" + return "null"; + } +}; + +module.exports.basicURLParse = function (input, options) { + if (options === undefined) { + options = {}; + } + + const usm = new URLStateMachine(input, options.baseURL, options.encodingOverride, options.url, options.stateOverride); + if (usm.failure) { + return "failure"; + } + + return usm.url; +}; + +module.exports.setTheUsername = function (url, username) { + url.username = ""; + const decoded = punycode.ucs2.decode(username); + for (let i = 0; i < decoded.length; ++i) { + url.username += percentEncodeChar(decoded[i], isUserinfoPercentEncode); + } +}; + +module.exports.setThePassword = function (url, password) { + url.password = ""; + const decoded = punycode.ucs2.decode(password); + for (let i = 0; i < decoded.length; ++i) { + url.password += percentEncodeChar(decoded[i], isUserinfoPercentEncode); + } +}; + +module.exports.serializeHost = serializeHost; + +module.exports.cannotHaveAUsernamePasswordPort = cannotHaveAUsernamePasswordPort; + +module.exports.serializeInteger = function (integer) { + return String(integer); +}; + +module.exports.parseURL = function (input, options) { + if (options === undefined) { + options = {}; + } + + // We don't handle blobs, so this just delegates: + return module.exports.basicURLParse(input, { baseURL: options.baseURL, encodingOverride: options.encodingOverride }); +}; + + +/***/ }), + +/***/ 9857: +/***/ ((module) => { + +"use strict"; + + +module.exports.mixin = function mixin(target, source) { + const keys = Object.getOwnPropertyNames(source); + for (let i = 0; i < keys.length; ++i) { + Object.defineProperty(target, keys[i], Object.getOwnPropertyDescriptor(source, keys[i])); + } +}; + +module.exports.wrapperSymbol = Symbol("wrapper"); +module.exports.implSymbol = Symbol("impl"); + +module.exports.wrapperForImpl = function (impl) { + return impl[module.exports.wrapperSymbol]; +}; + +module.exports.implForWrapper = function (wrapper) { + return wrapper[module.exports.implSymbol]; +}; + + + +/***/ }), + +/***/ 4572: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var iconvLite = __nccwpck_require__(6249); + +// Expose to the world +module.exports.C = convert; + +/** + * Convert encoding of an UTF-8 string or a buffer + * + * @param {String|Buffer} str String to be converted + * @param {String} to Encoding to be converted to + * @param {String} [from='UTF-8'] Encoding to be converted from + * @return {Buffer} Encoded string + */ +function convert(str, to, from) { + from = checkEncoding(from || 'UTF-8'); + to = checkEncoding(to || 'UTF-8'); + str = str || ''; + + var result; + + if (from !== 'UTF-8' && typeof str === 'string') { + str = Buffer.from(str, 'binary'); + } + + if (from === to) { + if (typeof str === 'string') { + result = Buffer.from(str); + } else { + result = str; + } + } else { + try { + result = convertIconvLite(str, to, from); + } catch (E) { + console.error(E); + result = str; + } + } + + if (typeof result === 'string') { + result = Buffer.from(result, 'utf-8'); + } + + return result; +} + +/** + * Convert encoding of astring with iconv-lite + * + * @param {String|Buffer} str String to be converted + * @param {String} to Encoding to be converted to + * @param {String} [from='UTF-8'] Encoding to be converted from + * @return {Buffer} Encoded string + */ +function convertIconvLite(str, to, from) { + if (to === 'UTF-8') { + return iconvLite.decode(str, from); + } else if (from === 'UTF-8') { + return iconvLite.encode(str, to); + } else { + return iconvLite.encode(iconvLite.decode(str, from), to); + } +} + +/** + * Converts charset name if needed + * + * @param {String} name Character set + * @return {String} Character set name + */ +function checkEncoding(name) { + return (name || '') + .toString() + .trim() + .replace(/^latin[\-_]?(\d+)$/i, 'ISO-8859-$1') + .replace(/^win(?:dows)?[\-_]?(\d+)$/i, 'WINDOWS-$1') + .replace(/^utf[\-_]?(\d+)$/i, 'UTF-$1') + .replace(/^ks_c_5601\-1987$/i, 'CP949') + .replace(/^us[\-_]?ascii$/i, 'ASCII') + .toUpperCase(); +} + + +/***/ }), + +/***/ 2417: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +var Buffer = (__nccwpck_require__(4199).Buffer); + +// Multibyte codec. In this scheme, a character is represented by 1 or more bytes. +// Our codec supports UTF-16 surrogates, extensions for GB18030 and unicode sequences. +// To save memory and loading time, we read table files only when requested. + +exports._dbcs = DBCSCodec; + +var UNASSIGNED = -1, + GB18030_CODE = -2, + SEQ_START = -10, + NODE_START = -1000, + UNASSIGNED_NODE = new Array(0x100), + DEF_CHAR = -1; + +for (var i = 0; i < 0x100; i++) + UNASSIGNED_NODE[i] = UNASSIGNED; + + +// Class DBCSCodec reads and initializes mapping tables. +function DBCSCodec(codecOptions, iconv) { + this.encodingName = codecOptions.encodingName; + if (!codecOptions) + throw new Error("DBCS codec is called without the data.") + if (!codecOptions.table) + throw new Error("Encoding '" + this.encodingName + "' has no data."); + + // Load tables. + var mappingTable = codecOptions.table(); + + + // Decode tables: MBCS -> Unicode. + + // decodeTables is a trie, encoded as an array of arrays of integers. Internal arrays are trie nodes and all have len = 256. + // Trie root is decodeTables[0]. + // Values: >= 0 -> unicode character code. can be > 0xFFFF + // == UNASSIGNED -> unknown/unassigned sequence. + // == GB18030_CODE -> this is the end of a GB18030 4-byte sequence. + // <= NODE_START -> index of the next node in our trie to process next byte. + // <= SEQ_START -> index of the start of a character code sequence, in decodeTableSeq. + this.decodeTables = []; + this.decodeTables[0] = UNASSIGNED_NODE.slice(0); // Create root node. + + // Sometimes a MBCS char corresponds to a sequence of unicode chars. We store them as arrays of integers here. + this.decodeTableSeq = []; + + // Actual mapping tables consist of chunks. Use them to fill up decode tables. + for (var i = 0; i < mappingTable.length; i++) + this._addDecodeChunk(mappingTable[i]); + + // Load & create GB18030 tables when needed. + if (typeof codecOptions.gb18030 === 'function') { + this.gb18030 = codecOptions.gb18030(); // Load GB18030 ranges. + + // Add GB18030 common decode nodes. + var commonThirdByteNodeIdx = this.decodeTables.length; + this.decodeTables.push(UNASSIGNED_NODE.slice(0)); + + var commonFourthByteNodeIdx = this.decodeTables.length; + this.decodeTables.push(UNASSIGNED_NODE.slice(0)); + + // Fill out the tree + var firstByteNode = this.decodeTables[0]; + for (var i = 0x81; i <= 0xFE; i++) { + var secondByteNode = this.decodeTables[NODE_START - firstByteNode[i]]; + for (var j = 0x30; j <= 0x39; j++) { + if (secondByteNode[j] === UNASSIGNED) { + secondByteNode[j] = NODE_START - commonThirdByteNodeIdx; + } else if (secondByteNode[j] > NODE_START) { + throw new Error("gb18030 decode tables conflict at byte 2"); + } + + var thirdByteNode = this.decodeTables[NODE_START - secondByteNode[j]]; + for (var k = 0x81; k <= 0xFE; k++) { + if (thirdByteNode[k] === UNASSIGNED) { + thirdByteNode[k] = NODE_START - commonFourthByteNodeIdx; + } else if (thirdByteNode[k] === NODE_START - commonFourthByteNodeIdx) { + continue; + } else if (thirdByteNode[k] > NODE_START) { + throw new Error("gb18030 decode tables conflict at byte 3"); + } + + var fourthByteNode = this.decodeTables[NODE_START - thirdByteNode[k]]; + for (var l = 0x30; l <= 0x39; l++) { + if (fourthByteNode[l] === UNASSIGNED) + fourthByteNode[l] = GB18030_CODE; + } + } + } + } + } + + this.defaultCharUnicode = iconv.defaultCharUnicode; + + + // Encode tables: Unicode -> DBCS. + + // `encodeTable` is array mapping from unicode char to encoded char. All its values are integers for performance. + // Because it can be sparse, it is represented as array of buckets by 256 chars each. Bucket can be null. + // Values: >= 0 -> it is a normal char. Write the value (if <=256 then 1 byte, if <=65536 then 2 bytes, etc.). + // == UNASSIGNED -> no conversion found. Output a default char. + // <= SEQ_START -> it's an index in encodeTableSeq, see below. The character starts a sequence. + this.encodeTable = []; + + // `encodeTableSeq` is used when a sequence of unicode characters is encoded as a single code. We use a tree of + // objects where keys correspond to characters in sequence and leafs are the encoded dbcs values. A special DEF_CHAR key + // means end of sequence (needed when one sequence is a strict subsequence of another). + // Objects are kept separately from encodeTable to increase performance. + this.encodeTableSeq = []; + + // Some chars can be decoded, but need not be encoded. + var skipEncodeChars = {}; + if (codecOptions.encodeSkipVals) + for (var i = 0; i < codecOptions.encodeSkipVals.length; i++) { + var val = codecOptions.encodeSkipVals[i]; + if (typeof val === 'number') + skipEncodeChars[val] = true; + else + for (var j = val.from; j <= val.to; j++) + skipEncodeChars[j] = true; + } + + // Use decode trie to recursively fill out encode tables. + this._fillEncodeTable(0, 0, skipEncodeChars); + + // Add more encoding pairs when needed. + if (codecOptions.encodeAdd) { + for (var uChar in codecOptions.encodeAdd) + if (Object.prototype.hasOwnProperty.call(codecOptions.encodeAdd, uChar)) + this._setEncodeChar(uChar.charCodeAt(0), codecOptions.encodeAdd[uChar]); + } + + this.defCharSB = this.encodeTable[0][iconv.defaultCharSingleByte.charCodeAt(0)]; + if (this.defCharSB === UNASSIGNED) this.defCharSB = this.encodeTable[0]['?']; + if (this.defCharSB === UNASSIGNED) this.defCharSB = "?".charCodeAt(0); +} + +DBCSCodec.prototype.encoder = DBCSEncoder; +DBCSCodec.prototype.decoder = DBCSDecoder; + +// Decoder helpers +DBCSCodec.prototype._getDecodeTrieNode = function(addr) { + var bytes = []; + for (; addr > 0; addr >>>= 8) + bytes.push(addr & 0xFF); + if (bytes.length == 0) + bytes.push(0); + + var node = this.decodeTables[0]; + for (var i = bytes.length-1; i > 0; i--) { // Traverse nodes deeper into the trie. + var val = node[bytes[i]]; + + if (val == UNASSIGNED) { // Create new node. + node[bytes[i]] = NODE_START - this.decodeTables.length; + this.decodeTables.push(node = UNASSIGNED_NODE.slice(0)); + } + else if (val <= NODE_START) { // Existing node. + node = this.decodeTables[NODE_START - val]; + } + else + throw new Error("Overwrite byte in " + this.encodingName + ", addr: " + addr.toString(16)); + } + return node; +} + + +DBCSCodec.prototype._addDecodeChunk = function(chunk) { + // First element of chunk is the hex mbcs code where we start. + var curAddr = parseInt(chunk[0], 16); + + // Choose the decoding node where we'll write our chars. + var writeTable = this._getDecodeTrieNode(curAddr); + curAddr = curAddr & 0xFF; + + // Write all other elements of the chunk to the table. + for (var k = 1; k < chunk.length; k++) { + var part = chunk[k]; + if (typeof part === "string") { // String, write as-is. + for (var l = 0; l < part.length;) { + var code = part.charCodeAt(l++); + if (0xD800 <= code && code < 0xDC00) { // Decode surrogate + var codeTrail = part.charCodeAt(l++); + if (0xDC00 <= codeTrail && codeTrail < 0xE000) + writeTable[curAddr++] = 0x10000 + (code - 0xD800) * 0x400 + (codeTrail - 0xDC00); + else + throw new Error("Incorrect surrogate pair in " + this.encodingName + " at chunk " + chunk[0]); + } + else if (0x0FF0 < code && code <= 0x0FFF) { // Character sequence (our own encoding used) + var len = 0xFFF - code + 2; + var seq = []; + for (var m = 0; m < len; m++) + seq.push(part.charCodeAt(l++)); // Simple variation: don't support surrogates or subsequences in seq. + + writeTable[curAddr++] = SEQ_START - this.decodeTableSeq.length; + this.decodeTableSeq.push(seq); + } + else + writeTable[curAddr++] = code; // Basic char + } + } + else if (typeof part === "number") { // Integer, meaning increasing sequence starting with prev character. + var charCode = writeTable[curAddr - 1] + 1; + for (var l = 0; l < part; l++) + writeTable[curAddr++] = charCode++; + } + else + throw new Error("Incorrect type '" + typeof part + "' given in " + this.encodingName + " at chunk " + chunk[0]); + } + if (curAddr > 0xFF) + throw new Error("Incorrect chunk in " + this.encodingName + " at addr " + chunk[0] + ": too long" + curAddr); +} + +// Encoder helpers +DBCSCodec.prototype._getEncodeBucket = function(uCode) { + var high = uCode >> 8; // This could be > 0xFF because of astral characters. + if (this.encodeTable[high] === undefined) + this.encodeTable[high] = UNASSIGNED_NODE.slice(0); // Create bucket on demand. + return this.encodeTable[high]; +} + +DBCSCodec.prototype._setEncodeChar = function(uCode, dbcsCode) { + var bucket = this._getEncodeBucket(uCode); + var low = uCode & 0xFF; + if (bucket[low] <= SEQ_START) + this.encodeTableSeq[SEQ_START-bucket[low]][DEF_CHAR] = dbcsCode; // There's already a sequence, set a single-char subsequence of it. + else if (bucket[low] == UNASSIGNED) + bucket[low] = dbcsCode; +} + +DBCSCodec.prototype._setEncodeSequence = function(seq, dbcsCode) { + + // Get the root of character tree according to first character of the sequence. + var uCode = seq[0]; + var bucket = this._getEncodeBucket(uCode); + var low = uCode & 0xFF; + + var node; + if (bucket[low] <= SEQ_START) { + // There's already a sequence with - use it. + node = this.encodeTableSeq[SEQ_START-bucket[low]]; + } + else { + // There was no sequence object - allocate a new one. + node = {}; + if (bucket[low] !== UNASSIGNED) node[DEF_CHAR] = bucket[low]; // If a char was set before - make it a single-char subsequence. + bucket[low] = SEQ_START - this.encodeTableSeq.length; + this.encodeTableSeq.push(node); + } + + // Traverse the character tree, allocating new nodes as needed. + for (var j = 1; j < seq.length-1; j++) { + var oldVal = node[uCode]; + if (typeof oldVal === 'object') + node = oldVal; + else { + node = node[uCode] = {} + if (oldVal !== undefined) + node[DEF_CHAR] = oldVal + } + } + + // Set the leaf to given dbcsCode. + uCode = seq[seq.length-1]; + node[uCode] = dbcsCode; +} + +DBCSCodec.prototype._fillEncodeTable = function(nodeIdx, prefix, skipEncodeChars) { + var node = this.decodeTables[nodeIdx]; + var hasValues = false; + var subNodeEmpty = {}; + for (var i = 0; i < 0x100; i++) { + var uCode = node[i]; + var mbCode = prefix + i; + if (skipEncodeChars[mbCode]) + continue; + + if (uCode >= 0) { + this._setEncodeChar(uCode, mbCode); + hasValues = true; + } else if (uCode <= NODE_START) { + var subNodeIdx = NODE_START - uCode; + if (!subNodeEmpty[subNodeIdx]) { // Skip empty subtrees (they are too large in gb18030). + var newPrefix = (mbCode << 8) >>> 0; // NOTE: '>>> 0' keeps 32-bit num positive. + if (this._fillEncodeTable(subNodeIdx, newPrefix, skipEncodeChars)) + hasValues = true; + else + subNodeEmpty[subNodeIdx] = true; + } + } else if (uCode <= SEQ_START) { + this._setEncodeSequence(this.decodeTableSeq[SEQ_START - uCode], mbCode); + hasValues = true; + } + } + return hasValues; +} + + + +// == Encoder ================================================================== + +function DBCSEncoder(options, codec) { + // Encoder state + this.leadSurrogate = -1; + this.seqObj = undefined; + + // Static data + this.encodeTable = codec.encodeTable; + this.encodeTableSeq = codec.encodeTableSeq; + this.defaultCharSingleByte = codec.defCharSB; + this.gb18030 = codec.gb18030; +} + +DBCSEncoder.prototype.write = function(str) { + var newBuf = Buffer.alloc(str.length * (this.gb18030 ? 4 : 3)), + leadSurrogate = this.leadSurrogate, + seqObj = this.seqObj, nextChar = -1, + i = 0, j = 0; + + while (true) { + // 0. Get next character. + if (nextChar === -1) { + if (i == str.length) break; + var uCode = str.charCodeAt(i++); + } + else { + var uCode = nextChar; + nextChar = -1; + } + + // 1. Handle surrogates. + if (0xD800 <= uCode && uCode < 0xE000) { // Char is one of surrogates. + if (uCode < 0xDC00) { // We've got lead surrogate. + if (leadSurrogate === -1) { + leadSurrogate = uCode; + continue; + } else { + leadSurrogate = uCode; + // Double lead surrogate found. + uCode = UNASSIGNED; + } + } else { // We've got trail surrogate. + if (leadSurrogate !== -1) { + uCode = 0x10000 + (leadSurrogate - 0xD800) * 0x400 + (uCode - 0xDC00); + leadSurrogate = -1; + } else { + // Incomplete surrogate pair - only trail surrogate found. + uCode = UNASSIGNED; + } + + } + } + else if (leadSurrogate !== -1) { + // Incomplete surrogate pair - only lead surrogate found. + nextChar = uCode; uCode = UNASSIGNED; // Write an error, then current char. + leadSurrogate = -1; + } + + // 2. Convert uCode character. + var dbcsCode = UNASSIGNED; + if (seqObj !== undefined && uCode != UNASSIGNED) { // We are in the middle of the sequence + var resCode = seqObj[uCode]; + if (typeof resCode === 'object') { // Sequence continues. + seqObj = resCode; + continue; + + } else if (typeof resCode == 'number') { // Sequence finished. Write it. + dbcsCode = resCode; + + } else if (resCode == undefined) { // Current character is not part of the sequence. + + // Try default character for this sequence + resCode = seqObj[DEF_CHAR]; + if (resCode !== undefined) { + dbcsCode = resCode; // Found. Write it. + nextChar = uCode; // Current character will be written too in the next iteration. + + } else { + // TODO: What if we have no default? (resCode == undefined) + // Then, we should write first char of the sequence as-is and try the rest recursively. + // Didn't do it for now because no encoding has this situation yet. + // Currently, just skip the sequence and write current char. + } + } + seqObj = undefined; + } + else if (uCode >= 0) { // Regular character + var subtable = this.encodeTable[uCode >> 8]; + if (subtable !== undefined) + dbcsCode = subtable[uCode & 0xFF]; + + if (dbcsCode <= SEQ_START) { // Sequence start + seqObj = this.encodeTableSeq[SEQ_START-dbcsCode]; + continue; + } + + if (dbcsCode == UNASSIGNED && this.gb18030) { + // Use GB18030 algorithm to find character(s) to write. + var idx = findIdx(this.gb18030.uChars, uCode); + if (idx != -1) { + var dbcsCode = this.gb18030.gbChars[idx] + (uCode - this.gb18030.uChars[idx]); + newBuf[j++] = 0x81 + Math.floor(dbcsCode / 12600); dbcsCode = dbcsCode % 12600; + newBuf[j++] = 0x30 + Math.floor(dbcsCode / 1260); dbcsCode = dbcsCode % 1260; + newBuf[j++] = 0x81 + Math.floor(dbcsCode / 10); dbcsCode = dbcsCode % 10; + newBuf[j++] = 0x30 + dbcsCode; + continue; + } + } + } + + // 3. Write dbcsCode character. + if (dbcsCode === UNASSIGNED) + dbcsCode = this.defaultCharSingleByte; + + if (dbcsCode < 0x100) { + newBuf[j++] = dbcsCode; + } + else if (dbcsCode < 0x10000) { + newBuf[j++] = dbcsCode >> 8; // high byte + newBuf[j++] = dbcsCode & 0xFF; // low byte + } + else if (dbcsCode < 0x1000000) { + newBuf[j++] = dbcsCode >> 16; + newBuf[j++] = (dbcsCode >> 8) & 0xFF; + newBuf[j++] = dbcsCode & 0xFF; + } else { + newBuf[j++] = dbcsCode >>> 24; + newBuf[j++] = (dbcsCode >>> 16) & 0xFF; + newBuf[j++] = (dbcsCode >>> 8) & 0xFF; + newBuf[j++] = dbcsCode & 0xFF; + } + } + + this.seqObj = seqObj; + this.leadSurrogate = leadSurrogate; + return newBuf.slice(0, j); +} + +DBCSEncoder.prototype.end = function() { + if (this.leadSurrogate === -1 && this.seqObj === undefined) + return; // All clean. Most often case. + + var newBuf = Buffer.alloc(10), j = 0; + + if (this.seqObj) { // We're in the sequence. + var dbcsCode = this.seqObj[DEF_CHAR]; + if (dbcsCode !== undefined) { // Write beginning of the sequence. + if (dbcsCode < 0x100) { + newBuf[j++] = dbcsCode; + } + else { + newBuf[j++] = dbcsCode >> 8; // high byte + newBuf[j++] = dbcsCode & 0xFF; // low byte + } + } else { + // See todo above. + } + this.seqObj = undefined; + } + + if (this.leadSurrogate !== -1) { + // Incomplete surrogate pair - only lead surrogate found. + newBuf[j++] = this.defaultCharSingleByte; + this.leadSurrogate = -1; + } + + return newBuf.slice(0, j); +} + +// Export for testing +DBCSEncoder.prototype.findIdx = findIdx; + + +// == Decoder ================================================================== + +function DBCSDecoder(options, codec) { + // Decoder state + this.nodeIdx = 0; + this.prevBytes = []; + + // Static data + this.decodeTables = codec.decodeTables; + this.decodeTableSeq = codec.decodeTableSeq; + this.defaultCharUnicode = codec.defaultCharUnicode; + this.gb18030 = codec.gb18030; +} + +DBCSDecoder.prototype.write = function(buf) { + var newBuf = Buffer.alloc(buf.length*2), + nodeIdx = this.nodeIdx, + prevBytes = this.prevBytes, prevOffset = this.prevBytes.length, + seqStart = -this.prevBytes.length, // idx of the start of current parsed sequence. + uCode; + + for (var i = 0, j = 0; i < buf.length; i++) { + var curByte = (i >= 0) ? buf[i] : prevBytes[i + prevOffset]; + + // Lookup in current trie node. + var uCode = this.decodeTables[nodeIdx][curByte]; + + if (uCode >= 0) { + // Normal character, just use it. + } + else if (uCode === UNASSIGNED) { // Unknown char. + // TODO: Callback with seq. + uCode = this.defaultCharUnicode.charCodeAt(0); + i = seqStart; // Skip one byte ('i' will be incremented by the for loop) and try to parse again. + } + else if (uCode === GB18030_CODE) { + if (i >= 3) { + var ptr = (buf[i-3]-0x81)*12600 + (buf[i-2]-0x30)*1260 + (buf[i-1]-0x81)*10 + (curByte-0x30); + } else { + var ptr = (prevBytes[i-3+prevOffset]-0x81)*12600 + + (((i-2 >= 0) ? buf[i-2] : prevBytes[i-2+prevOffset])-0x30)*1260 + + (((i-1 >= 0) ? buf[i-1] : prevBytes[i-1+prevOffset])-0x81)*10 + + (curByte-0x30); + } + var idx = findIdx(this.gb18030.gbChars, ptr); + uCode = this.gb18030.uChars[idx] + ptr - this.gb18030.gbChars[idx]; + } + else if (uCode <= NODE_START) { // Go to next trie node. + nodeIdx = NODE_START - uCode; + continue; + } + else if (uCode <= SEQ_START) { // Output a sequence of chars. + var seq = this.decodeTableSeq[SEQ_START - uCode]; + for (var k = 0; k < seq.length - 1; k++) { + uCode = seq[k]; + newBuf[j++] = uCode & 0xFF; + newBuf[j++] = uCode >> 8; + } + uCode = seq[seq.length-1]; + } + else + throw new Error("iconv-lite internal error: invalid decoding table value " + uCode + " at " + nodeIdx + "/" + curByte); + + // Write the character to buffer, handling higher planes using surrogate pair. + if (uCode >= 0x10000) { + uCode -= 0x10000; + var uCodeLead = 0xD800 | (uCode >> 10); + newBuf[j++] = uCodeLead & 0xFF; + newBuf[j++] = uCodeLead >> 8; + + uCode = 0xDC00 | (uCode & 0x3FF); + } + newBuf[j++] = uCode & 0xFF; + newBuf[j++] = uCode >> 8; + + // Reset trie node. + nodeIdx = 0; seqStart = i+1; + } + + this.nodeIdx = nodeIdx; + this.prevBytes = (seqStart >= 0) + ? Array.prototype.slice.call(buf, seqStart) + : prevBytes.slice(seqStart + prevOffset).concat(Array.prototype.slice.call(buf)); + + return newBuf.slice(0, j).toString('ucs2'); +} + +DBCSDecoder.prototype.end = function() { + var ret = ''; + + // Try to parse all remaining chars. + while (this.prevBytes.length > 0) { + // Skip 1 character in the buffer. + ret += this.defaultCharUnicode; + var bytesArr = this.prevBytes.slice(1); + + // Parse remaining as usual. + this.prevBytes = []; + this.nodeIdx = 0; + if (bytesArr.length > 0) + ret += this.write(bytesArr); + } + + this.prevBytes = []; + this.nodeIdx = 0; + return ret; +} + +// Binary search for GB18030. Returns largest i such that table[i] <= val. +function findIdx(table, val) { + if (table[0] > val) + return -1; + + var l = 0, r = table.length; + while (l < r-1) { // always table[l] <= val < table[r] + var mid = l + ((r-l+1) >> 1); + if (table[mid] <= val) + l = mid; + else + r = mid; + } + return l; +} + + + +/***/ }), + +/***/ 2435: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +// Description of supported double byte encodings and aliases. +// Tables are not require()-d until they are needed to speed up library load. +// require()-s are direct to support Browserify. + +module.exports = { + + // == Japanese/ShiftJIS ==================================================== + // All japanese encodings are based on JIS X set of standards: + // JIS X 0201 - Single-byte encoding of ASCII + ¥ + Kana chars at 0xA1-0xDF. + // JIS X 0208 - Main set of 6879 characters, placed in 94x94 plane, to be encoded by 2 bytes. + // Has several variations in 1978, 1983, 1990 and 1997. + // JIS X 0212 - Supplementary plane of 6067 chars in 94x94 plane. 1990. Effectively dead. + // JIS X 0213 - Extension and modern replacement of 0208 and 0212. Total chars: 11233. + // 2 planes, first is superset of 0208, second - revised 0212. + // Introduced in 2000, revised 2004. Some characters are in Unicode Plane 2 (0x2xxxx) + + // Byte encodings are: + // * Shift_JIS: Compatible with 0201, uses not defined chars in top half as lead bytes for double-byte + // encoding of 0208. Lead byte ranges: 0x81-0x9F, 0xE0-0xEF; Trail byte ranges: 0x40-0x7E, 0x80-0x9E, 0x9F-0xFC. + // Windows CP932 is a superset of Shift_JIS. Some companies added more chars, notably KDDI. + // * EUC-JP: Up to 3 bytes per character. Used mostly on *nixes. + // 0x00-0x7F - lower part of 0201 + // 0x8E, 0xA1-0xDF - upper part of 0201 + // (0xA1-0xFE)x2 - 0208 plane (94x94). + // 0x8F, (0xA1-0xFE)x2 - 0212 plane (94x94). + // * JIS X 208: 7-bit, direct encoding of 0208. Byte ranges: 0x21-0x7E (94 values). Uncommon. + // Used as-is in ISO2022 family. + // * ISO2022-JP: Stateful encoding, with escape sequences to switch between ASCII, + // 0201-1976 Roman, 0208-1978, 0208-1983. + // * ISO2022-JP-1: Adds esc seq for 0212-1990. + // * ISO2022-JP-2: Adds esc seq for GB2313-1980, KSX1001-1992, ISO8859-1, ISO8859-7. + // * ISO2022-JP-3: Adds esc seq for 0201-1976 Kana set, 0213-2000 Planes 1, 2. + // * ISO2022-JP-2004: Adds 0213-2004 Plane 1. + // + // After JIS X 0213 appeared, Shift_JIS-2004, EUC-JISX0213 and ISO2022-JP-2004 followed, with just changing the planes. + // + // Overall, it seems that it's a mess :( http://www8.plala.or.jp/tkubota1/unicode-symbols-map2.html + + 'shiftjis': { + type: '_dbcs', + table: function() { return __nccwpck_require__(7572) }, + encodeAdd: {'\u00a5': 0x5C, '\u203E': 0x7E}, + encodeSkipVals: [{from: 0xED40, to: 0xF940}], + }, + 'csshiftjis': 'shiftjis', + 'mskanji': 'shiftjis', + 'sjis': 'shiftjis', + 'windows31j': 'shiftjis', + 'ms31j': 'shiftjis', + 'xsjis': 'shiftjis', + 'windows932': 'shiftjis', + 'ms932': 'shiftjis', + '932': 'shiftjis', + 'cp932': 'shiftjis', + + 'eucjp': { + type: '_dbcs', + table: function() { return __nccwpck_require__(4231) }, + encodeAdd: {'\u00a5': 0x5C, '\u203E': 0x7E}, + }, + + // TODO: KDDI extension to Shift_JIS + // TODO: IBM CCSID 942 = CP932, but F0-F9 custom chars and other char changes. + // TODO: IBM CCSID 943 = Shift_JIS = CP932 with original Shift_JIS lower 128 chars. + + + // == Chinese/GBK ========================================================== + // http://en.wikipedia.org/wiki/GBK + // We mostly implement W3C recommendation: https://www.w3.org/TR/encoding/#gbk-encoder + + // Oldest GB2312 (1981, ~7600 chars) is a subset of CP936 + 'gb2312': 'cp936', + 'gb231280': 'cp936', + 'gb23121980': 'cp936', + 'csgb2312': 'cp936', + 'csiso58gb231280': 'cp936', + 'euccn': 'cp936', + + // Microsoft's CP936 is a subset and approximation of GBK. + 'windows936': 'cp936', + 'ms936': 'cp936', + '936': 'cp936', + 'cp936': { + type: '_dbcs', + table: function() { return __nccwpck_require__(8949) }, + }, + + // GBK (~22000 chars) is an extension of CP936 that added user-mapped chars and some other. + 'gbk': { + type: '_dbcs', + table: function() { return (__nccwpck_require__(8949).concat)(__nccwpck_require__(9603)) }, + }, + 'xgbk': 'gbk', + 'isoir58': 'gbk', + + // GB18030 is an algorithmic extension of GBK. + // Main source: https://www.w3.org/TR/encoding/#gbk-encoder + // http://icu-project.org/docs/papers/gb18030.html + // http://source.icu-project.org/repos/icu/data/trunk/charset/data/xml/gb-18030-2000.xml + // http://www.khngai.com/chinese/charmap/tblgbk.php?page=0 + 'gb18030': { + type: '_dbcs', + table: function() { return (__nccwpck_require__(8949).concat)(__nccwpck_require__(9603)) }, + gb18030: function() { return __nccwpck_require__(7302) }, + encodeSkipVals: [0x80], + encodeAdd: {'€': 0xA2E3}, + }, + + 'chinese': 'gb18030', + + + // == Korean =============================================================== + // EUC-KR, KS_C_5601 and KS X 1001 are exactly the same. + 'windows949': 'cp949', + 'ms949': 'cp949', + '949': 'cp949', + 'cp949': { + type: '_dbcs', + table: function() { return __nccwpck_require__(5923) }, + }, + + 'cseuckr': 'cp949', + 'csksc56011987': 'cp949', + 'euckr': 'cp949', + 'isoir149': 'cp949', + 'korean': 'cp949', + 'ksc56011987': 'cp949', + 'ksc56011989': 'cp949', + 'ksc5601': 'cp949', + + + // == Big5/Taiwan/Hong Kong ================================================ + // There are lots of tables for Big5 and cp950. Please see the following links for history: + // http://moztw.org/docs/big5/ http://www.haible.de/bruno/charsets/conversion-tables/Big5.html + // Variations, in roughly number of defined chars: + // * Windows CP 950: Microsoft variant of Big5. Canonical: http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP950.TXT + // * Windows CP 951: Microsoft variant of Big5-HKSCS-2001. Seems to be never public. http://me.abelcheung.org/articles/research/what-is-cp951/ + // * Big5-2003 (Taiwan standard) almost superset of cp950. + // * Unicode-at-on (UAO) / Mozilla 1.8. Falling out of use on the Web. Not supported by other browsers. + // * Big5-HKSCS (-2001, -2004, -2008). Hong Kong standard. + // many unicode code points moved from PUA to Supplementary plane (U+2XXXX) over the years. + // Plus, it has 4 combining sequences. + // Seems that Mozilla refused to support it for 10 yrs. https://bugzilla.mozilla.org/show_bug.cgi?id=162431 https://bugzilla.mozilla.org/show_bug.cgi?id=310299 + // because big5-hkscs is the only encoding to include astral characters in non-algorithmic way. + // Implementations are not consistent within browsers; sometimes labeled as just big5. + // MS Internet Explorer switches from big5 to big5-hkscs when a patch applied. + // Great discussion & recap of what's going on https://bugzilla.mozilla.org/show_bug.cgi?id=912470#c31 + // In the encoder, it might make sense to support encoding old PUA mappings to Big5 bytes seq-s. + // Official spec: http://www.ogcio.gov.hk/en/business/tech_promotion/ccli/terms/doc/2003cmp_2008.txt + // http://www.ogcio.gov.hk/tc/business/tech_promotion/ccli/terms/doc/hkscs-2008-big5-iso.txt + // + // Current understanding of how to deal with Big5(-HKSCS) is in the Encoding Standard, http://encoding.spec.whatwg.org/#big5-encoder + // Unicode mapping (http://www.unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/OTHER/BIG5.TXT) is said to be wrong. + + 'windows950': 'cp950', + 'ms950': 'cp950', + '950': 'cp950', + 'cp950': { + type: '_dbcs', + table: function() { return __nccwpck_require__(6517) }, + }, + + // Big5 has many variations and is an extension of cp950. We use Encoding Standard's as a consensus. + 'big5': 'big5hkscs', + 'big5hkscs': { + type: '_dbcs', + table: function() { return (__nccwpck_require__(6517).concat)(__nccwpck_require__(4244)) }, + encodeSkipVals: [ + // Although Encoding Standard says we should avoid encoding to HKSCS area (See Step 1 of + // https://encoding.spec.whatwg.org/#index-big5-pointer), we still do it to increase compatibility with ICU. + // But if a single unicode point can be encoded both as HKSCS and regular Big5, we prefer the latter. + 0x8e69, 0x8e6f, 0x8e7e, 0x8eab, 0x8eb4, 0x8ecd, 0x8ed0, 0x8f57, 0x8f69, 0x8f6e, 0x8fcb, 0x8ffe, + 0x906d, 0x907a, 0x90c4, 0x90dc, 0x90f1, 0x91bf, 0x92af, 0x92b0, 0x92b1, 0x92b2, 0x92d1, 0x9447, 0x94ca, + 0x95d9, 0x96fc, 0x9975, 0x9b76, 0x9b78, 0x9b7b, 0x9bc6, 0x9bde, 0x9bec, 0x9bf6, 0x9c42, 0x9c53, 0x9c62, + 0x9c68, 0x9c6b, 0x9c77, 0x9cbc, 0x9cbd, 0x9cd0, 0x9d57, 0x9d5a, 0x9dc4, 0x9def, 0x9dfb, 0x9ea9, 0x9eef, + 0x9efd, 0x9f60, 0x9fcb, 0xa077, 0xa0dc, 0xa0df, 0x8fcc, 0x92c8, 0x9644, 0x96ed, + + // Step 2 of https://encoding.spec.whatwg.org/#index-big5-pointer: Use last pointer for U+2550, U+255E, U+2561, U+256A, U+5341, or U+5345 + 0xa2a4, 0xa2a5, 0xa2a7, 0xa2a6, 0xa2cc, 0xa2ce, + ], + }, + + 'cnbig5': 'big5hkscs', + 'csbig5': 'big5hkscs', + 'xxbig5': 'big5hkscs', +}; + + +/***/ }), + +/***/ 5424: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +// Update this array if you add/rename/remove files in this directory. +// We support Browserify by skipping automatic module discovery and requiring modules directly. +var modules = [ + __nccwpck_require__(7799), + __nccwpck_require__(8015), + __nccwpck_require__(2402), + __nccwpck_require__(3152), + __nccwpck_require__(6146), + __nccwpck_require__(4818), + __nccwpck_require__(1566), + __nccwpck_require__(2417), + __nccwpck_require__(2435), +]; + +// Put all encoding/alias/codec definitions to single object and export it. +for (var i = 0; i < modules.length; i++) { + var module = modules[i]; + for (var enc in module) + if (Object.prototype.hasOwnProperty.call(module, enc)) + exports[enc] = module[enc]; +} + + +/***/ }), + +/***/ 7799: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + +var Buffer = (__nccwpck_require__(4199).Buffer); + +// Export Node.js internal encodings. + +module.exports = { + // Encodings + utf8: { type: "_internal", bomAware: true}, + cesu8: { type: "_internal", bomAware: true}, + unicode11utf8: "utf8", + + ucs2: { type: "_internal", bomAware: true}, + utf16le: "ucs2", + + binary: { type: "_internal" }, + base64: { type: "_internal" }, + hex: { type: "_internal" }, + + // Codec. + _internal: InternalCodec, +}; + +//------------------------------------------------------------------------------ + +function InternalCodec(codecOptions, iconv) { + this.enc = codecOptions.encodingName; + this.bomAware = codecOptions.bomAware; + + if (this.enc === "base64") + this.encoder = InternalEncoderBase64; + else if (this.enc === "cesu8") { + this.enc = "utf8"; // Use utf8 for decoding. + this.encoder = InternalEncoderCesu8; + + // Add decoder for versions of Node not supporting CESU-8 + if (Buffer.from('eda0bdedb2a9', 'hex').toString() !== '💩') { + this.decoder = InternalDecoderCesu8; + this.defaultCharUnicode = iconv.defaultCharUnicode; + } + } +} + +InternalCodec.prototype.encoder = InternalEncoder; +InternalCodec.prototype.decoder = InternalDecoder; + +//------------------------------------------------------------------------------ + +// We use node.js internal decoder. Its signature is the same as ours. +var StringDecoder = (__nccwpck_require__(3193).StringDecoder); + +if (!StringDecoder.prototype.end) // Node v0.8 doesn't have this method. + StringDecoder.prototype.end = function() {}; + + +function InternalDecoder(options, codec) { + this.decoder = new StringDecoder(codec.enc); +} + +InternalDecoder.prototype.write = function(buf) { + if (!Buffer.isBuffer(buf)) { + buf = Buffer.from(buf); + } + + return this.decoder.write(buf); +} + +InternalDecoder.prototype.end = function() { + return this.decoder.end(); +} + + +//------------------------------------------------------------------------------ +// Encoder is mostly trivial + +function InternalEncoder(options, codec) { + this.enc = codec.enc; +} + +InternalEncoder.prototype.write = function(str) { + return Buffer.from(str, this.enc); +} + +InternalEncoder.prototype.end = function() { +} + + +//------------------------------------------------------------------------------ +// Except base64 encoder, which must keep its state. + +function InternalEncoderBase64(options, codec) { + this.prevStr = ''; +} + +InternalEncoderBase64.prototype.write = function(str) { + str = this.prevStr + str; + var completeQuads = str.length - (str.length % 4); + this.prevStr = str.slice(completeQuads); + str = str.slice(0, completeQuads); + + return Buffer.from(str, "base64"); +} + +InternalEncoderBase64.prototype.end = function() { + return Buffer.from(this.prevStr, "base64"); +} + + +//------------------------------------------------------------------------------ +// CESU-8 encoder is also special. + +function InternalEncoderCesu8(options, codec) { +} + +InternalEncoderCesu8.prototype.write = function(str) { + var buf = Buffer.alloc(str.length * 3), bufIdx = 0; + for (var i = 0; i < str.length; i++) { + var charCode = str.charCodeAt(i); + // Naive implementation, but it works because CESU-8 is especially easy + // to convert from UTF-16 (which all JS strings are encoded in). + if (charCode < 0x80) + buf[bufIdx++] = charCode; + else if (charCode < 0x800) { + buf[bufIdx++] = 0xC0 + (charCode >>> 6); + buf[bufIdx++] = 0x80 + (charCode & 0x3f); + } + else { // charCode will always be < 0x10000 in javascript. + buf[bufIdx++] = 0xE0 + (charCode >>> 12); + buf[bufIdx++] = 0x80 + ((charCode >>> 6) & 0x3f); + buf[bufIdx++] = 0x80 + (charCode & 0x3f); + } + } + return buf.slice(0, bufIdx); +} + +InternalEncoderCesu8.prototype.end = function() { +} + +//------------------------------------------------------------------------------ +// CESU-8 decoder is not implemented in Node v4.0+ + +function InternalDecoderCesu8(options, codec) { + this.acc = 0; + this.contBytes = 0; + this.accBytes = 0; + this.defaultCharUnicode = codec.defaultCharUnicode; +} + +InternalDecoderCesu8.prototype.write = function(buf) { + var acc = this.acc, contBytes = this.contBytes, accBytes = this.accBytes, + res = ''; + for (var i = 0; i < buf.length; i++) { + var curByte = buf[i]; + if ((curByte & 0xC0) !== 0x80) { // Leading byte + if (contBytes > 0) { // Previous code is invalid + res += this.defaultCharUnicode; + contBytes = 0; + } + + if (curByte < 0x80) { // Single-byte code + res += String.fromCharCode(curByte); + } else if (curByte < 0xE0) { // Two-byte code + acc = curByte & 0x1F; + contBytes = 1; accBytes = 1; + } else if (curByte < 0xF0) { // Three-byte code + acc = curByte & 0x0F; + contBytes = 2; accBytes = 1; + } else { // Four or more are not supported for CESU-8. + res += this.defaultCharUnicode; + } + } else { // Continuation byte + if (contBytes > 0) { // We're waiting for it. + acc = (acc << 6) | (curByte & 0x3f); + contBytes--; accBytes++; + if (contBytes === 0) { + // Check for overlong encoding, but support Modified UTF-8 (encoding NULL as C0 80) + if (accBytes === 2 && acc < 0x80 && acc > 0) + res += this.defaultCharUnicode; + else if (accBytes === 3 && acc < 0x800) + res += this.defaultCharUnicode; + else + // Actually add character. + res += String.fromCharCode(acc); + } + } else { // Unexpected continuation byte + res += this.defaultCharUnicode; + } + } + } + this.acc = acc; this.contBytes = contBytes; this.accBytes = accBytes; + return res; +} + +InternalDecoderCesu8.prototype.end = function() { + var res = 0; + if (this.contBytes > 0) + res += this.defaultCharUnicode; + return res; +} + + +/***/ }), + +/***/ 6146: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +var Buffer = (__nccwpck_require__(4199).Buffer); + +// Single-byte codec. Needs a 'chars' string parameter that contains 256 or 128 chars that +// correspond to encoded bytes (if 128 - then lower half is ASCII). + +exports._sbcs = SBCSCodec; +function SBCSCodec(codecOptions, iconv) { + if (!codecOptions) + throw new Error("SBCS codec is called without the data.") + + // Prepare char buffer for decoding. + if (!codecOptions.chars || (codecOptions.chars.length !== 128 && codecOptions.chars.length !== 256)) + throw new Error("Encoding '"+codecOptions.type+"' has incorrect 'chars' (must be of len 128 or 256)"); + + if (codecOptions.chars.length === 128) { + var asciiString = ""; + for (var i = 0; i < 128; i++) + asciiString += String.fromCharCode(i); + codecOptions.chars = asciiString + codecOptions.chars; + } + + this.decodeBuf = Buffer.from(codecOptions.chars, 'ucs2'); + + // Encoding buffer. + var encodeBuf = Buffer.alloc(65536, iconv.defaultCharSingleByte.charCodeAt(0)); + + for (var i = 0; i < codecOptions.chars.length; i++) + encodeBuf[codecOptions.chars.charCodeAt(i)] = i; + + this.encodeBuf = encodeBuf; +} + +SBCSCodec.prototype.encoder = SBCSEncoder; +SBCSCodec.prototype.decoder = SBCSDecoder; + + +function SBCSEncoder(options, codec) { + this.encodeBuf = codec.encodeBuf; +} + +SBCSEncoder.prototype.write = function(str) { + var buf = Buffer.alloc(str.length); + for (var i = 0; i < str.length; i++) + buf[i] = this.encodeBuf[str.charCodeAt(i)]; + + return buf; +} + +SBCSEncoder.prototype.end = function() { +} + + +function SBCSDecoder(options, codec) { + this.decodeBuf = codec.decodeBuf; +} + +SBCSDecoder.prototype.write = function(buf) { + // Strings are immutable in JS -> we use ucs2 buffer to speed up computations. + var decodeBuf = this.decodeBuf; + var newBuf = Buffer.alloc(buf.length*2); + var idx1 = 0, idx2 = 0; + for (var i = 0; i < buf.length; i++) { + idx1 = buf[i]*2; idx2 = i*2; + newBuf[idx2] = decodeBuf[idx1]; + newBuf[idx2+1] = decodeBuf[idx1+1]; + } + return newBuf.toString('ucs2'); +} + +SBCSDecoder.prototype.end = function() { +} + + +/***/ }), + +/***/ 1566: +/***/ ((module) => { + +"use strict"; + + +// Generated data for sbcs codec. Don't edit manually. Regenerate using generation/gen-sbcs.js script. +module.exports = { + "437": "cp437", + "737": "cp737", + "775": "cp775", + "850": "cp850", + "852": "cp852", + "855": "cp855", + "856": "cp856", + "857": "cp857", + "858": "cp858", + "860": "cp860", + "861": "cp861", + "862": "cp862", + "863": "cp863", + "864": "cp864", + "865": "cp865", + "866": "cp866", + "869": "cp869", + "874": "windows874", + "922": "cp922", + "1046": "cp1046", + "1124": "cp1124", + "1125": "cp1125", + "1129": "cp1129", + "1133": "cp1133", + "1161": "cp1161", + "1162": "cp1162", + "1163": "cp1163", + "1250": "windows1250", + "1251": "windows1251", + "1252": "windows1252", + "1253": "windows1253", + "1254": "windows1254", + "1255": "windows1255", + "1256": "windows1256", + "1257": "windows1257", + "1258": "windows1258", + "28591": "iso88591", + "28592": "iso88592", + "28593": "iso88593", + "28594": "iso88594", + "28595": "iso88595", + "28596": "iso88596", + "28597": "iso88597", + "28598": "iso88598", + "28599": "iso88599", + "28600": "iso885910", + "28601": "iso885911", + "28603": "iso885913", + "28604": "iso885914", + "28605": "iso885915", + "28606": "iso885916", + "windows874": { + "type": "_sbcs", + "chars": "€����…�����������‘’“”•–—�������� กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู����฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛����" + }, + "win874": "windows874", + "cp874": "windows874", + "windows1250": { + "type": "_sbcs", + "chars": "€�‚�„…†‡�‰Š‹ŚŤŽŹ�‘’“”•–—�™š›śťžź ˇ˘Ł¤Ą¦§¨©Ş«¬­®Ż°±˛ł´µ¶·¸ąş»Ľ˝ľżŔÁÂĂÄĹĆÇČÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ˙" + }, + "win1250": "windows1250", + "cp1250": "windows1250", + "windows1251": { + "type": "_sbcs", + "chars": "ЂЃ‚ѓ„…†‡€‰Љ‹ЊЌЋЏђ‘’“”•–—�™љ›њќћџ ЎўЈ¤Ґ¦§Ё©Є«¬­®Ї°±Ііґµ¶·ё№є»јЅѕїАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя" + }, + "win1251": "windows1251", + "cp1251": "windows1251", + "windows1252": { + "type": "_sbcs", + "chars": "€�‚ƒ„…†‡ˆ‰Š‹Œ�Ž��‘’“”•–—˜™š›œ�žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ" + }, + "win1252": "windows1252", + "cp1252": "windows1252", + "windows1253": { + "type": "_sbcs", + "chars": "€�‚ƒ„…†‡�‰�‹�����‘’“”•–—�™�›���� ΅Ά£¤¥¦§¨©�«¬­®―°±²³΄µ¶·ΈΉΊ»Ό½ΎΏΐΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡ�ΣΤΥΦΧΨΩΪΫάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώ�" + }, + "win1253": "windows1253", + "cp1253": "windows1253", + "windows1254": { + "type": "_sbcs", + "chars": "€�‚ƒ„…†‡ˆ‰Š‹Œ����‘’“”•–—˜™š›œ��Ÿ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏĞÑÒÓÔÕÖ×ØÙÚÛÜİŞßàáâãäåæçèéêëìíîïğñòóôõö÷øùúûüışÿ" + }, + "win1254": "windows1254", + "cp1254": "windows1254", + "windows1255": { + "type": "_sbcs", + "chars": "€�‚ƒ„…†‡ˆ‰�‹�����‘’“”•–—˜™�›���� ¡¢£₪¥¦§¨©×«¬­®¯°±²³´µ¶·¸¹÷»¼½¾¿ְֱֲֳִֵֶַָֹֺֻּֽ־ֿ׀ׁׂ׃װױײ׳״�������אבגדהוזחטיךכלםמןנסעףפץצקרשת��‎‏�" + }, + "win1255": "windows1255", + "cp1255": "windows1255", + "windows1256": { + "type": "_sbcs", + "chars": "€پ‚ƒ„…†‡ˆ‰ٹ‹Œچژڈگ‘’“”•–—ک™ڑ›œ‌‍ں ،¢£¤¥¦§¨©ھ«¬­®¯°±²³´µ¶·¸¹؛»¼½¾؟ہءآأؤإئابةتثجحخدذرزسشصض×طظعغـفقكàلâمنهوçèéêëىيîïًٌٍَôُِ÷ّùْûü‎‏ے" + }, + "win1256": "windows1256", + "cp1256": "windows1256", + "windows1257": { + "type": "_sbcs", + "chars": "€�‚�„…†‡�‰�‹�¨ˇ¸�‘’“”•–—�™�›�¯˛� �¢£¤�¦§Ø©Ŗ«¬­®Æ°±²³´µ¶·ø¹ŗ»¼½¾æĄĮĀĆÄÅĘĒČÉŹĖĢĶĪĻŠŃŅÓŌÕÖ×ŲŁŚŪÜŻŽßąįāćäåęēčéźėģķīļšńņóōõö÷ųłśūüżž˙" + }, + "win1257": "windows1257", + "cp1257": "windows1257", + "windows1258": { + "type": "_sbcs", + "chars": "€�‚ƒ„…†‡ˆ‰�‹Œ����‘’“”•–—˜™�›œ��Ÿ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂĂÄÅÆÇÈÉÊË̀ÍÎÏĐÑ̉ÓÔƠÖ×ØÙÚÛÜỮßàáâăäåæçèéêë́íîïđṇ̃óôơö÷øùúûüư₫ÿ" + }, + "win1258": "windows1258", + "cp1258": "windows1258", + "iso88591": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ" + }, + "cp28591": "iso88591", + "iso88592": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ Ą˘Ł¤ĽŚ§¨ŠŞŤŹ­ŽŻ°ą˛ł´ľśˇ¸šşťź˝žżŔÁÂĂÄĹĆÇČÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢßŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ˙" + }, + "cp28592": "iso88592", + "iso88593": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ Ħ˘£¤�Ĥ§¨İŞĞĴ­�Ż°ħ²³´µĥ·¸ışğĵ½�żÀÁÂ�ÄĊĈÇÈÉÊËÌÍÎÏ�ÑÒÓÔĠÖ×ĜÙÚÛÜŬŜßàáâ�äċĉçèéêëìíîï�ñòóôġö÷ĝùúûüŭŝ˙" + }, + "cp28593": "iso88593", + "iso88594": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ĄĸŖ¤ĨĻ§¨ŠĒĢŦ­Ž¯°ą˛ŗ´ĩļˇ¸šēģŧŊžŋĀÁÂÃÄÅÆĮČÉĘËĖÍÎĪĐŅŌĶÔÕÖ×ØŲÚÛÜŨŪßāáâãäåæįčéęëėíîīđņōķôõö÷øųúûüũū˙" + }, + "cp28594": "iso88594", + "iso88595": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ЁЂЃЄЅІЇЈЉЊЋЌ­ЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя№ёђѓєѕіїјљњћќ§ўџ" + }, + "cp28595": "iso88595", + "iso88596": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ���¤�������،­�������������؛���؟�ءآأؤإئابةتثجحخدذرزسشصضطظعغ�����ـفقكلمنهوىيًٌٍَُِّْ�������������" + }, + "cp28596": "iso88596", + "iso88597": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ‘’£€₯¦§¨©ͺ«¬­�―°±²³΄΅Ά·ΈΉΊ»Ό½ΎΏΐΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡ�ΣΤΥΦΧΨΩΪΫάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώ�" + }, + "cp28597": "iso88597", + "iso88598": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ �¢£¤¥¦§¨©×«¬­®¯°±²³´µ¶·¸¹÷»¼½¾��������������������������������‗אבגדהוזחטיךכלםמןנסעףפץצקרשת��‎‏�" + }, + "cp28598": "iso88598", + "iso88599": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏĞÑÒÓÔÕÖ×ØÙÚÛÜİŞßàáâãäåæçèéêëìíîïğñòóôõö÷øùúûüışÿ" + }, + "cp28599": "iso88599", + "iso885910": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ĄĒĢĪĨĶ§ĻĐŠŦŽ­ŪŊ°ąēģīĩķ·ļđšŧž―ūŋĀÁÂÃÄÅÆĮČÉĘËĖÍÎÏÐŅŌÓÔÕÖŨØŲÚÛÜÝÞßāáâãäåæįčéęëėíîïðņōóôõöũøųúûüýþĸ" + }, + "cp28600": "iso885910", + "iso885911": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู����฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛����" + }, + "cp28601": "iso885911", + "iso885913": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ”¢£¤„¦§Ø©Ŗ«¬­®Æ°±²³“µ¶·ø¹ŗ»¼½¾æĄĮĀĆÄÅĘĒČÉŹĖĢĶĪĻŠŃŅÓŌÕÖ×ŲŁŚŪÜŻŽßąįāćäåęēčéźėģķīļšńņóōõö÷ųłśūüżž’" + }, + "cp28603": "iso885913", + "iso885914": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ Ḃḃ£ĊċḊ§Ẁ©ẂḋỲ­®ŸḞḟĠġṀṁ¶ṖẁṗẃṠỳẄẅṡÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏŴÑÒÓÔÕÖṪØÙÚÛÜÝŶßàáâãäåæçèéêëìíîïŵñòóôõöṫøùúûüýŷÿ" + }, + "cp28604": "iso885914", + "iso885915": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£€¥Š§š©ª«¬­®¯°±²³Žµ¶·ž¹º»ŒœŸ¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ" + }, + "cp28605": "iso885915", + "iso885916": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ĄąŁ€„Š§š©Ș«Ź­źŻ°±ČłŽ”¶·žčș»ŒœŸżÀÁÂĂÄĆÆÇÈÉÊËÌÍÎÏĐŃÒÓÔŐÖŚŰÙÚÛÜĘȚßàáâăäćæçèéêëìíîïđńòóôőöśűùúûüęțÿ" + }, + "cp28606": "iso885916", + "cp437": { + "type": "_sbcs", + "chars": "ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ " + }, + "ibm437": "cp437", + "csibm437": "cp437", + "cp737": { + "type": "_sbcs", + "chars": "ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩαβγδεζηθικλμνξοπρσςτυφχψ░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀ωάέήϊίόύϋώΆΈΉΊΌΎΏ±≥≤ΪΫ÷≈°∙·√ⁿ²■ " + }, + "ibm737": "cp737", + "csibm737": "cp737", + "cp775": { + "type": "_sbcs", + "chars": "ĆüéāäģåćłēŖŗīŹÄÅÉæÆōöĢ¢ŚśÖÜø£ØפĀĪóŻżź”¦©®¬½¼Ł«»░▒▓│┤ĄČĘĖ╣║╗╝ĮŠ┐└┴┬├─┼ŲŪ╚╔╩╦╠═╬Žąčęėįšųūž┘┌█▄▌▐▀ÓßŌŃõÕµńĶķĻļņĒŅ’­±“¾¶§÷„°∙·¹³²■ " + }, + "ibm775": "cp775", + "csibm775": "cp775", + "cp850": { + "type": "_sbcs", + "chars": "ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤ÁÂÀ©╣║╗╝¢¥┐└┴┬├─┼ãÃ╚╔╩╦╠═╬¤ðÐÊËÈıÍÎÏ┘┌█▄¦Ì▀ÓßÔÒõÕµþÞÚÛÙýݯ´­±‗¾¶§÷¸°¨·¹³²■ " + }, + "ibm850": "cp850", + "csibm850": "cp850", + "cp852": { + "type": "_sbcs", + "chars": "ÇüéâäůćçłëŐőîŹÄĆÉĹĺôöĽľŚśÖÜŤťŁ×čáíóúĄąŽžĘ꬟Ⱥ«»░▒▓│┤ÁÂĚŞ╣║╗╝Żż┐└┴┬├─┼Ăă╚╔╩╦╠═╬¤đĐĎËďŇÍÎě┘┌█▄ŢŮ▀ÓßÔŃńňŠšŔÚŕŰýÝţ´­˝˛ˇ˘§÷¸°¨˙űŘř■ " + }, + "ibm852": "cp852", + "csibm852": "cp852", + "cp855": { + "type": "_sbcs", + "chars": "ђЂѓЃёЁєЄѕЅіІїЇјЈљЉњЊћЋќЌўЎџЏюЮъЪаАбБцЦдДеЕфФгГ«»░▒▓│┤хХиИ╣║╗╝йЙ┐└┴┬├─┼кК╚╔╩╦╠═╬¤лЛмМнНоОп┘┌█▄Пя▀ЯрРсСтТуУжЖвВьЬ№­ыЫзЗшШэЭщЩчЧ§■ " + }, + "ibm855": "cp855", + "csibm855": "cp855", + "cp856": { + "type": "_sbcs", + "chars": "אבגדהוזחטיךכלםמןנסעףפץצקרשת�£�×����������®¬½¼�«»░▒▓│┤���©╣║╗╝¢¥┐└┴┬├─┼��╚╔╩╦╠═╬¤���������┘┌█▄¦�▀������µ�������¯´­±‗¾¶§÷¸°¨·¹³²■ " + }, + "ibm856": "cp856", + "csibm856": "cp856", + "cp857": { + "type": "_sbcs", + "chars": "ÇüéâäàåçêëèïîıÄÅÉæÆôöòûùİÖÜø£ØŞşáíóúñÑĞ𿮬½¼¡«»░▒▓│┤ÁÂÀ©╣║╗╝¢¥┐└┴┬├─┼ãÃ╚╔╩╦╠═╬¤ºªÊËÈ�ÍÎÏ┘┌█▄¦Ì▀ÓßÔÒõÕµ�×ÚÛÙìÿ¯´­±�¾¶§÷¸°¨·¹³²■ " + }, + "ibm857": "cp857", + "csibm857": "cp857", + "cp858": { + "type": "_sbcs", + "chars": "ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø׃áíóúñѪº¿®¬½¼¡«»░▒▓│┤ÁÂÀ©╣║╗╝¢¥┐└┴┬├─┼ãÃ╚╔╩╦╠═╬¤ðÐÊËÈ€ÍÎÏ┘┌█▄¦Ì▀ÓßÔÒõÕµþÞÚÛÙýݯ´­±‗¾¶§÷¸°¨·¹³²■ " + }, + "ibm858": "cp858", + "csibm858": "cp858", + "cp860": { + "type": "_sbcs", + "chars": "ÇüéâãàÁçêÊèÍÔìÃÂÉÀÈôõòÚùÌÕÜ¢£Ù₧ÓáíóúñѪº¿Ò¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ " + }, + "ibm860": "cp860", + "csibm860": "cp860", + "cp861": { + "type": "_sbcs", + "chars": "ÇüéâäàåçêëèÐðÞÄÅÉæÆôöþûÝýÖÜø£Ø₧ƒáíóúÁÍÓÚ¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ " + }, + "ibm861": "cp861", + "csibm861": "cp861", + "cp862": { + "type": "_sbcs", + "chars": "אבגדהוזחטיךכלםמןנסעףפץצקרשת¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ " + }, + "ibm862": "cp862", + "csibm862": "cp862", + "cp863": { + "type": "_sbcs", + "chars": "ÇüéâÂà¶çêëèïî‗À§ÉÈÊôËÏûù¤ÔÜ¢£ÙÛƒ¦´óú¨¸³¯Î⌐¬½¼¾«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ " + }, + "ibm863": "cp863", + "csibm863": "cp863", + "cp864": { + "type": "_sbcs", + "chars": "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#$٪&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~°·∙√▒─│┼┤┬├┴┐┌└┘β∞φ±½¼≈«»ﻷﻸ��ﻻﻼ� ­ﺂ£¤ﺄ��ﺎﺏﺕﺙ،ﺝﺡﺥ٠١٢٣٤٥٦٧٨٩ﻑ؛ﺱﺵﺹ؟¢ﺀﺁﺃﺅﻊﺋﺍﺑﺓﺗﺛﺟﺣﺧﺩﺫﺭﺯﺳﺷﺻﺿﻁﻅﻋﻏ¦¬÷×ﻉـﻓﻗﻛﻟﻣﻧﻫﻭﻯﻳﺽﻌﻎﻍﻡﹽّﻥﻩﻬﻰﻲﻐﻕﻵﻶﻝﻙﻱ■�" + }, + "ibm864": "cp864", + "csibm864": "cp864", + "cp865": { + "type": "_sbcs", + "chars": "ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜø£Ø₧ƒáíóúñѪº¿⌐¬½¼¡«¤░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ " + }, + "ibm865": "cp865", + "csibm865": "cp865", + "cp866": { + "type": "_sbcs", + "chars": "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмноп░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀рстуфхцчшщъыьэюяЁёЄєЇїЎў°∙·√№¤■ " + }, + "ibm866": "cp866", + "csibm866": "cp866", + "cp869": { + "type": "_sbcs", + "chars": "������Ά�·¬¦‘’Έ―ΉΊΪΌ��ΎΫ©Ώ²³ά£έήίϊΐόύΑΒΓΔΕΖΗ½ΘΙ«»░▒▓│┤ΚΛΜΝ╣║╗╝ΞΟ┐└┴┬├─┼ΠΡ╚╔╩╦╠═╬ΣΤΥΦΧΨΩαβγ┘┌█▄δε▀ζηθικλμνξοπρσςτ΄­±υφχ§ψ΅°¨ωϋΰώ■ " + }, + "ibm869": "cp869", + "csibm869": "cp869", + "cp922": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®‾°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏŠÑÒÓÔÕÖ×ØÙÚÛÜÝŽßàáâãäåæçèéêëìíîïšñòóôõö÷øùúûüýžÿ" + }, + "ibm922": "cp922", + "csibm922": "cp922", + "cp1046": { + "type": "_sbcs", + "chars": "ﺈ×÷ﹱˆ■│─┐┌└┘ﹹﹻﹽﹿﹷﺊﻰﻳﻲﻎﻏﻐﻶﻸﻺﻼ ¤ﺋﺑﺗﺛﺟﺣ،­ﺧﺳ٠١٢٣٤٥٦٧٨٩ﺷ؛ﺻﺿﻊ؟ﻋءآأؤإئابةتثجحخدذرزسشصضطﻇعغﻌﺂﺄﺎﻓـفقكلمنهوىيًٌٍَُِّْﻗﻛﻟﻵﻷﻹﻻﻣﻧﻬﻩ�" + }, + "ibm1046": "cp1046", + "csibm1046": "cp1046", + "cp1124": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ЁЂҐЄЅІЇЈЉЊЋЌ­ЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя№ёђґєѕіїјљњћќ§ўџ" + }, + "ibm1124": "cp1124", + "csibm1124": "cp1124", + "cp1125": { + "type": "_sbcs", + "chars": "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмноп░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀рстуфхцчшщъыьэюяЁёҐґЄєІіЇї·√№¤■ " + }, + "ibm1125": "cp1125", + "csibm1125": "cp1125", + "cp1129": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§œ©ª«¬­®¯°±²³Ÿµ¶·Œ¹º»¼½¾¿ÀÁÂĂÄÅÆÇÈÉÊË̀ÍÎÏĐÑ̉ÓÔƠÖ×ØÙÚÛÜỮßàáâăäåæçèéêë́íîïđṇ̃óôơö÷øùúûüư₫ÿ" + }, + "ibm1129": "cp1129", + "csibm1129": "cp1129", + "cp1133": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ກຂຄງຈສຊຍດຕຖທນບປຜຝພຟມຢຣລວຫອຮ���ຯະາຳິີຶືຸູຼັົຽ���ເແໂໃໄ່້໊໋໌ໍໆ�ໜໝ₭����������������໐໑໒໓໔໕໖໗໘໙��¢¬¦�" + }, + "ibm1133": "cp1133", + "csibm1133": "cp1133", + "cp1161": { + "type": "_sbcs", + "chars": "��������������������������������่กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู้๊๋€฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛¢¬¦ " + }, + "ibm1161": "cp1161", + "csibm1161": "cp1161", + "cp1162": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู����฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛����" + }, + "ibm1162": "cp1162", + "csibm1162": "cp1162", + "cp1163": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£€¥¦§œ©ª«¬­®¯°±²³Ÿµ¶·Œ¹º»¼½¾¿ÀÁÂĂÄÅÆÇÈÉÊË̀ÍÎÏĐÑ̉ÓÔƠÖ×ØÙÚÛÜỮßàáâăäåæçèéêë́íîïđṇ̃óôơö÷øùúûüư₫ÿ" + }, + "ibm1163": "cp1163", + "csibm1163": "cp1163", + "maccroatian": { + "type": "_sbcs", + "chars": "ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø¿¡¬√ƒ≈Ć«Č… ÀÃÕŒœĐ—“”‘’÷◊�©⁄¤‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ" + }, + "maccyrillic": { + "type": "_sbcs", + "chars": "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°¢£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµ∂ЈЄєЇїЉљЊњјЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю¤" + }, + "macgreek": { + "type": "_sbcs", + "chars": "Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦­ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩάΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ�" + }, + "maciceland": { + "type": "_sbcs", + "chars": "ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüÝ°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄¤ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔ�ÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ" + }, + "macroman": { + "type": "_sbcs", + "chars": "ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄¤‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔ�ÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ" + }, + "macromania": { + "type": "_sbcs", + "chars": "ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂŞ∞±≤≥¥µ∂∑∏π∫ªºΩăş¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄¤‹›Ţţ‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔ�ÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ" + }, + "macthai": { + "type": "_sbcs", + "chars": "«»…“”�•‘’� กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู​–—฿เแโใไๅๆ็่้๊๋์ํ™๏๐๑๒๓๔๕๖๗๘๙®©����" + }, + "macturkish": { + "type": "_sbcs", + "chars": "ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔ�ÒÚÛÙ�ˆ˜¯˘˙˚¸˝˛ˇ" + }, + "macukraine": { + "type": "_sbcs", + "chars": "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњјЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю¤" + }, + "koi8r": { + "type": "_sbcs", + "chars": "─│┌┐└┘├┤┬┴┼▀▄█▌▐░▒▓⌠■∙√≈≤≥ ⌡°²·÷═║╒ё╓╔╕╖╗╘╙╚╛╜╝╞╟╠╡Ё╢╣╤╥╦╧╨╩╪╫╬©юабцдефгхийклмнопярстужвьызшэщчъЮАБЦДЕФГХИЙКЛМНОПЯРСТУЖВЬЫЗШЭЩЧЪ" + }, + "koi8u": { + "type": "_sbcs", + "chars": "─│┌┐└┘├┤┬┴┼▀▄█▌▐░▒▓⌠■∙√≈≤≥ ⌡°²·÷═║╒ёє╔ії╗╘╙╚╛ґ╝╞╟╠╡ЁЄ╣ІЇ╦╧╨╩╪Ґ╬©юабцдефгхийклмнопярстужвьызшэщчъЮАБЦДЕФГХИЙКЛМНОПЯРСТУЖВЬЫЗШЭЩЧЪ" + }, + "koi8ru": { + "type": "_sbcs", + "chars": "─│┌┐└┘├┤┬┴┼▀▄█▌▐░▒▓⌠■∙√≈≤≥ ⌡°²·÷═║╒ёє╔ії╗╘╙╚╛ґў╞╟╠╡ЁЄ╣ІЇ╦╧╨╩╪ҐЎ©юабцдефгхийклмнопярстужвьызшэщчъЮАБЦДЕФГХИЙКЛМНОПЯРСТУЖВЬЫЗШЭЩЧЪ" + }, + "koi8t": { + "type": "_sbcs", + "chars": "қғ‚Ғ„…†‡�‰ҳ‹ҲҷҶ�Қ‘’“”•–—�™�›�����ӯӮё¤ӣ¦§���«¬­®�°±²Ё�Ӣ¶·�№�»���©юабцдефгхийклмнопярстужвьызшэщчъЮАБЦДЕФГХИЙКЛМНОПЯРСТУЖВЬЫЗШЭЩЧЪ" + }, + "armscii8": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ �և։)(»«—.՝,-֊…՜՛՞ԱաԲբԳգԴդԵեԶզԷէԸըԹթԺժԻիԼլԽխԾծԿկՀհՁձՂղՃճՄմՅյՆնՇշՈոՉչՊպՋջՌռՍսՎվՏտՐրՑցՒւՓփՔքՕօՖֆ՚�" + }, + "rk1048": { + "type": "_sbcs", + "chars": "ЂЃ‚ѓ„…†‡€‰Љ‹ЊҚҺЏђ‘’“”•–—�™љ›њқһџ ҰұӘ¤Ө¦§Ё©Ғ«¬­®Ү°±Ііөµ¶·ё№ғ»әҢңүАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя" + }, + "tcvn": { + "type": "_sbcs", + "chars": "\u0000ÚỤ\u0003ỪỬỮ\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010ỨỰỲỶỸÝỴ\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ÀẢÃÁẠẶẬÈẺẼÉẸỆÌỈĨÍỊÒỎÕÓỌỘỜỞỠỚỢÙỦŨ ĂÂÊÔƠƯĐăâêôơưđẶ̀̀̉̃́àảãáạẲằẳẵắẴẮẦẨẪẤỀặầẩẫấậèỂẻẽéẹềểễếệìỉỄẾỒĩíịòỔỏõóọồổỗốộờởỡớợùỖủũúụừửữứựỳỷỹýỵỐ" + }, + "georgianacademy": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿აბგდევზთიკლმნოპჟრსტუფქღყშჩცძწჭხჯჰჱჲჳჴჵჶçèéêëìíîïðñòóôõö÷øùúûüýþÿ" + }, + "georgianps": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿აბგდევზჱთიკლმნჲოპჟრსტჳუფქღყშჩცძწჭხჴჯჰჵæçèéêëìíîïðñòóôõö÷øùúûüýþÿ" + }, + "pt154": { + "type": "_sbcs", + "chars": "ҖҒӮғ„…ҶҮҲүҠӢҢҚҺҸҗ‘’“”•–—ҳҷҡӣңқһҹ ЎўЈӨҘҰ§Ё©Ә«¬ӯ®Ҝ°ұІіҙө¶·ё№ә»јҪҫҝАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя" + }, + "viscii": { + "type": "_sbcs", + "chars": "\u0000\u0001Ẳ\u0003\u0004ẴẪ\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013Ỷ\u0015\u0016\u0017\u0018Ỹ\u001a\u001b\u001c\u001dỴ\u001f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ẠẮẰẶẤẦẨẬẼẸẾỀỂỄỆỐỒỔỖỘỢỚỜỞỊỎỌỈỦŨỤỲÕắằặấầẩậẽẹếềểễệốồổỗỠƠộờởịỰỨỪỬơớƯÀÁÂÃẢĂẳẵÈÉÊẺÌÍĨỳĐứÒÓÔạỷừửÙÚỹỵÝỡưàáâãảăữẫèéêẻìíĩỉđựòóôõỏọụùúũủýợỮ" + }, + "iso646cn": { + "type": "_sbcs", + "chars": "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#¥%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}‾��������������������������������������������������������������������������������������������������������������������������������" + }, + "iso646jp": { + "type": "_sbcs", + "chars": "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\b\t\n\u000b\f\r\u000e\u000f\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[¥]^_`abcdefghijklmnopqrstuvwxyz{|}‾��������������������������������������������������������������������������������������������������������������������������������" + }, + "hproman8": { + "type": "_sbcs", + "chars": "€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ÀÂÈÊËÎÏ´ˋˆ¨˜ÙÛ₤¯Ýý°ÇçÑñ¡¿¤£¥§ƒ¢âêôûáéóúàèòùäëöüÅîØÆåíøæÄìÖÜÉïßÔÁÃãÐðÍÌÓÒÕõŠšÚŸÿÞþ·µ¶¾—¼½ªº«■»±�" + }, + "macintosh": { + "type": "_sbcs", + "chars": "ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄¤‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔ�ÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ" + }, + "ascii": { + "type": "_sbcs", + "chars": "��������������������������������������������������������������������������������������������������������������������������������" + }, + "tis620": { + "type": "_sbcs", + "chars": "���������������������������������กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำิีึืฺุู����฿เแโใไๅๆ็่้๊๋์ํ๎๏๐๑๒๓๔๕๖๗๘๙๚๛����" + } +} + +/***/ }), + +/***/ 4818: +/***/ ((module) => { + +"use strict"; + + +// Manually added data to be used by sbcs codec in addition to generated one. + +module.exports = { + // Not supported by iconv, not sure why. + "10029": "maccenteuro", + "maccenteuro": { + "type": "_sbcs", + "chars": "ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ" + }, + + "808": "cp808", + "ibm808": "cp808", + "cp808": { + "type": "_sbcs", + "chars": "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмноп░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀рстуфхцчшщъыьэюяЁёЄєЇїЎў°∙·√№€■ " + }, + + "mik": { + "type": "_sbcs", + "chars": "АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя└┴┬├─┼╣║╚╔╩╦╠═╬┐░▒▓│┤№§╗╝┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ " + }, + + "cp720": { + "type": "_sbcs", + "chars": "\x80\x81éâ\x84à\x86çêëèïî\x8d\x8e\x8f\x90\u0651\u0652ô¤ـûùءآأؤ£إئابةتثجحخدذرزسشص«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀ضطظعغفµقكلمنهوىي≡\u064b\u064c\u064d\u064e\u064f\u0650≈°∙·√ⁿ²■\u00a0" + }, + + // Aliases of generated encodings. + "ascii8bit": "ascii", + "usascii": "ascii", + "ansix34": "ascii", + "ansix341968": "ascii", + "ansix341986": "ascii", + "csascii": "ascii", + "cp367": "ascii", + "ibm367": "ascii", + "isoir6": "ascii", + "iso646us": "ascii", + "iso646irv": "ascii", + "us": "ascii", + + "latin1": "iso88591", + "latin2": "iso88592", + "latin3": "iso88593", + "latin4": "iso88594", + "latin5": "iso88599", + "latin6": "iso885910", + "latin7": "iso885913", + "latin8": "iso885914", + "latin9": "iso885915", + "latin10": "iso885916", + + "csisolatin1": "iso88591", + "csisolatin2": "iso88592", + "csisolatin3": "iso88593", + "csisolatin4": "iso88594", + "csisolatincyrillic": "iso88595", + "csisolatinarabic": "iso88596", + "csisolatingreek" : "iso88597", + "csisolatinhebrew": "iso88598", + "csisolatin5": "iso88599", + "csisolatin6": "iso885910", + + "l1": "iso88591", + "l2": "iso88592", + "l3": "iso88593", + "l4": "iso88594", + "l5": "iso88599", + "l6": "iso885910", + "l7": "iso885913", + "l8": "iso885914", + "l9": "iso885915", + "l10": "iso885916", + + "isoir14": "iso646jp", + "isoir57": "iso646cn", + "isoir100": "iso88591", + "isoir101": "iso88592", + "isoir109": "iso88593", + "isoir110": "iso88594", + "isoir144": "iso88595", + "isoir127": "iso88596", + "isoir126": "iso88597", + "isoir138": "iso88598", + "isoir148": "iso88599", + "isoir157": "iso885910", + "isoir166": "tis620", + "isoir179": "iso885913", + "isoir199": "iso885914", + "isoir203": "iso885915", + "isoir226": "iso885916", + + "cp819": "iso88591", + "ibm819": "iso88591", + + "cyrillic": "iso88595", + + "arabic": "iso88596", + "arabic8": "iso88596", + "ecma114": "iso88596", + "asmo708": "iso88596", + + "greek" : "iso88597", + "greek8" : "iso88597", + "ecma118" : "iso88597", + "elot928" : "iso88597", + + "hebrew": "iso88598", + "hebrew8": "iso88598", + + "turkish": "iso88599", + "turkish8": "iso88599", + + "thai": "iso885911", + "thai8": "iso885911", + + "celtic": "iso885914", + "celtic8": "iso885914", + "isoceltic": "iso885914", + + "tis6200": "tis620", + "tis62025291": "tis620", + "tis62025330": "tis620", + + "10000": "macroman", + "10006": "macgreek", + "10007": "maccyrillic", + "10079": "maciceland", + "10081": "macturkish", + + "cspc8codepage437": "cp437", + "cspc775baltic": "cp775", + "cspc850multilingual": "cp850", + "cspcp852": "cp852", + "cspc862latinhebrew": "cp862", + "cpgr": "cp869", + + "msee": "cp1250", + "mscyrl": "cp1251", + "msansi": "cp1252", + "msgreek": "cp1253", + "msturk": "cp1254", + "mshebr": "cp1255", + "msarab": "cp1256", + "winbaltrim": "cp1257", + + "cp20866": "koi8r", + "20866": "koi8r", + "ibm878": "koi8r", + "cskoi8r": "koi8r", + + "cp21866": "koi8u", + "21866": "koi8u", + "ibm1168": "koi8u", + + "strk10482002": "rk1048", + + "tcvn5712": "tcvn", + "tcvn57121": "tcvn", + + "gb198880": "iso646cn", + "cn": "iso646cn", + + "csiso14jisc6220ro": "iso646jp", + "jisc62201969ro": "iso646jp", + "jp": "iso646jp", + + "cshproman8": "hproman8", + "r8": "hproman8", + "roman8": "hproman8", + "xroman8": "hproman8", + "ibm1051": "hproman8", + + "mac": "macintosh", + "csmacintosh": "macintosh", +}; + + + +/***/ }), + +/***/ 2402: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +var Buffer = (__nccwpck_require__(4199).Buffer); + +// Note: UTF16-LE (or UCS2) codec is Node.js native. See encodings/internal.js + +// == UTF16-BE codec. ========================================================== + +exports.utf16be = Utf16BECodec; +function Utf16BECodec() { +} + +Utf16BECodec.prototype.encoder = Utf16BEEncoder; +Utf16BECodec.prototype.decoder = Utf16BEDecoder; +Utf16BECodec.prototype.bomAware = true; + + +// -- Encoding + +function Utf16BEEncoder() { +} + +Utf16BEEncoder.prototype.write = function(str) { + var buf = Buffer.from(str, 'ucs2'); + for (var i = 0; i < buf.length; i += 2) { + var tmp = buf[i]; buf[i] = buf[i+1]; buf[i+1] = tmp; + } + return buf; +} + +Utf16BEEncoder.prototype.end = function() { +} + + +// -- Decoding + +function Utf16BEDecoder() { + this.overflowByte = -1; +} + +Utf16BEDecoder.prototype.write = function(buf) { + if (buf.length == 0) + return ''; + + var buf2 = Buffer.alloc(buf.length + 1), + i = 0, j = 0; + + if (this.overflowByte !== -1) { + buf2[0] = buf[0]; + buf2[1] = this.overflowByte; + i = 1; j = 2; + } + + for (; i < buf.length-1; i += 2, j+= 2) { + buf2[j] = buf[i+1]; + buf2[j+1] = buf[i]; + } + + this.overflowByte = (i == buf.length-1) ? buf[buf.length-1] : -1; + + return buf2.slice(0, j).toString('ucs2'); +} + +Utf16BEDecoder.prototype.end = function() { + this.overflowByte = -1; +} + + +// == UTF-16 codec ============================================================= +// Decoder chooses automatically from UTF-16LE and UTF-16BE using BOM and space-based heuristic. +// Defaults to UTF-16LE, as it's prevalent and default in Node. +// http://en.wikipedia.org/wiki/UTF-16 and http://encoding.spec.whatwg.org/#utf-16le +// Decoder default can be changed: iconv.decode(buf, 'utf16', {defaultEncoding: 'utf-16be'}); + +// Encoder uses UTF-16LE and prepends BOM (which can be overridden with addBOM: false). + +exports.utf16 = Utf16Codec; +function Utf16Codec(codecOptions, iconv) { + this.iconv = iconv; +} + +Utf16Codec.prototype.encoder = Utf16Encoder; +Utf16Codec.prototype.decoder = Utf16Decoder; + + +// -- Encoding (pass-through) + +function Utf16Encoder(options, codec) { + options = options || {}; + if (options.addBOM === undefined) + options.addBOM = true; + this.encoder = codec.iconv.getEncoder('utf-16le', options); +} + +Utf16Encoder.prototype.write = function(str) { + return this.encoder.write(str); +} + +Utf16Encoder.prototype.end = function() { + return this.encoder.end(); +} + + +// -- Decoding + +function Utf16Decoder(options, codec) { + this.decoder = null; + this.initialBufs = []; + this.initialBufsLen = 0; + + this.options = options || {}; + this.iconv = codec.iconv; +} + +Utf16Decoder.prototype.write = function(buf) { + if (!this.decoder) { + // Codec is not chosen yet. Accumulate initial bytes. + this.initialBufs.push(buf); + this.initialBufsLen += buf.length; + + if (this.initialBufsLen < 16) // We need more bytes to use space heuristic (see below) + return ''; + + // We have enough bytes -> detect endianness. + var encoding = detectEncoding(this.initialBufs, this.options.defaultEncoding); + this.decoder = this.iconv.getDecoder(encoding, this.options); + + var resStr = ''; + for (var i = 0; i < this.initialBufs.length; i++) + resStr += this.decoder.write(this.initialBufs[i]); + + this.initialBufs.length = this.initialBufsLen = 0; + return resStr; + } + + return this.decoder.write(buf); +} + +Utf16Decoder.prototype.end = function() { + if (!this.decoder) { + var encoding = detectEncoding(this.initialBufs, this.options.defaultEncoding); + this.decoder = this.iconv.getDecoder(encoding, this.options); + + var resStr = ''; + for (var i = 0; i < this.initialBufs.length; i++) + resStr += this.decoder.write(this.initialBufs[i]); + + var trail = this.decoder.end(); + if (trail) + resStr += trail; + + this.initialBufs.length = this.initialBufsLen = 0; + return resStr; + } + return this.decoder.end(); +} + +function detectEncoding(bufs, defaultEncoding) { + var b = []; + var charsProcessed = 0; + var asciiCharsLE = 0, asciiCharsBE = 0; // Number of ASCII chars when decoded as LE or BE. + + outer_loop: + for (var i = 0; i < bufs.length; i++) { + var buf = bufs[i]; + for (var j = 0; j < buf.length; j++) { + b.push(buf[j]); + if (b.length === 2) { + if (charsProcessed === 0) { + // Check BOM first. + if (b[0] === 0xFF && b[1] === 0xFE) return 'utf-16le'; + if (b[0] === 0xFE && b[1] === 0xFF) return 'utf-16be'; + } + + if (b[0] === 0 && b[1] !== 0) asciiCharsBE++; + if (b[0] !== 0 && b[1] === 0) asciiCharsLE++; + + b.length = 0; + charsProcessed++; + + if (charsProcessed >= 100) { + break outer_loop; + } + } + } + } + + // Make decisions. + // Most of the time, the content has ASCII chars (U+00**), but the opposite (U+**00) is uncommon. + // So, we count ASCII as if it was LE or BE, and decide from that. + if (asciiCharsBE > asciiCharsLE) return 'utf-16be'; + if (asciiCharsBE < asciiCharsLE) return 'utf-16le'; + + // Couldn't decide (likely all zeros or not enough data). + return defaultEncoding || 'utf-16le'; +} + + + + +/***/ }), + +/***/ 8015: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + + +var Buffer = (__nccwpck_require__(4199).Buffer); + +// == UTF32-LE/BE codec. ========================================================== + +exports._utf32 = Utf32Codec; + +function Utf32Codec(codecOptions, iconv) { + this.iconv = iconv; + this.bomAware = true; + this.isLE = codecOptions.isLE; +} + +exports.utf32le = { type: '_utf32', isLE: true }; +exports.utf32be = { type: '_utf32', isLE: false }; + +// Aliases +exports.ucs4le = 'utf32le'; +exports.ucs4be = 'utf32be'; + +Utf32Codec.prototype.encoder = Utf32Encoder; +Utf32Codec.prototype.decoder = Utf32Decoder; + +// -- Encoding + +function Utf32Encoder(options, codec) { + this.isLE = codec.isLE; + this.highSurrogate = 0; +} + +Utf32Encoder.prototype.write = function(str) { + var src = Buffer.from(str, 'ucs2'); + var dst = Buffer.alloc(src.length * 2); + var write32 = this.isLE ? dst.writeUInt32LE : dst.writeUInt32BE; + var offset = 0; + + for (var i = 0; i < src.length; i += 2) { + var code = src.readUInt16LE(i); + var isHighSurrogate = (0xD800 <= code && code < 0xDC00); + var isLowSurrogate = (0xDC00 <= code && code < 0xE000); + + if (this.highSurrogate) { + if (isHighSurrogate || !isLowSurrogate) { + // There shouldn't be two high surrogates in a row, nor a high surrogate which isn't followed by a low + // surrogate. If this happens, keep the pending high surrogate as a stand-alone semi-invalid character + // (technically wrong, but expected by some applications, like Windows file names). + write32.call(dst, this.highSurrogate, offset); + offset += 4; + } + else { + // Create 32-bit value from high and low surrogates; + var codepoint = (((this.highSurrogate - 0xD800) << 10) | (code - 0xDC00)) + 0x10000; + + write32.call(dst, codepoint, offset); + offset += 4; + this.highSurrogate = 0; + + continue; + } + } + + if (isHighSurrogate) + this.highSurrogate = code; + else { + // Even if the current character is a low surrogate, with no previous high surrogate, we'll + // encode it as a semi-invalid stand-alone character for the same reasons expressed above for + // unpaired high surrogates. + write32.call(dst, code, offset); + offset += 4; + this.highSurrogate = 0; + } + } + + if (offset < dst.length) + dst = dst.slice(0, offset); + + return dst; +}; + +Utf32Encoder.prototype.end = function() { + // Treat any leftover high surrogate as a semi-valid independent character. + if (!this.highSurrogate) + return; + + var buf = Buffer.alloc(4); + + if (this.isLE) + buf.writeUInt32LE(this.highSurrogate, 0); + else + buf.writeUInt32BE(this.highSurrogate, 0); + + this.highSurrogate = 0; + + return buf; +}; + +// -- Decoding + +function Utf32Decoder(options, codec) { + this.isLE = codec.isLE; + this.badChar = codec.iconv.defaultCharUnicode.charCodeAt(0); + this.overflow = []; +} + +Utf32Decoder.prototype.write = function(src) { + if (src.length === 0) + return ''; + + var i = 0; + var codepoint = 0; + var dst = Buffer.alloc(src.length + 4); + var offset = 0; + var isLE = this.isLE; + var overflow = this.overflow; + var badChar = this.badChar; + + if (overflow.length > 0) { + for (; i < src.length && overflow.length < 4; i++) + overflow.push(src[i]); + + if (overflow.length === 4) { + // NOTE: codepoint is a signed int32 and can be negative. + // NOTE: We copied this block from below to help V8 optimize it (it works with array, not buffer). + if (isLE) { + codepoint = overflow[i] | (overflow[i+1] << 8) | (overflow[i+2] << 16) | (overflow[i+3] << 24); + } else { + codepoint = overflow[i+3] | (overflow[i+2] << 8) | (overflow[i+1] << 16) | (overflow[i] << 24); + } + overflow.length = 0; + + offset = _writeCodepoint(dst, offset, codepoint, badChar); + } + } + + // Main loop. Should be as optimized as possible. + for (; i < src.length - 3; i += 4) { + // NOTE: codepoint is a signed int32 and can be negative. + if (isLE) { + codepoint = src[i] | (src[i+1] << 8) | (src[i+2] << 16) | (src[i+3] << 24); + } else { + codepoint = src[i+3] | (src[i+2] << 8) | (src[i+1] << 16) | (src[i] << 24); + } + offset = _writeCodepoint(dst, offset, codepoint, badChar); + } + + // Keep overflowing bytes. + for (; i < src.length; i++) { + overflow.push(src[i]); + } + + return dst.slice(0, offset).toString('ucs2'); +}; + +function _writeCodepoint(dst, offset, codepoint, badChar) { + // NOTE: codepoint is signed int32 and can be negative. We keep it that way to help V8 with optimizations. + if (codepoint < 0 || codepoint > 0x10FFFF) { + // Not a valid Unicode codepoint + codepoint = badChar; + } + + // Ephemeral Planes: Write high surrogate. + if (codepoint >= 0x10000) { + codepoint -= 0x10000; + + var high = 0xD800 | (codepoint >> 10); + dst[offset++] = high & 0xff; + dst[offset++] = high >> 8; + + // Low surrogate is written below. + var codepoint = 0xDC00 | (codepoint & 0x3FF); + } + + // Write BMP char or low surrogate. + dst[offset++] = codepoint & 0xff; + dst[offset++] = codepoint >> 8; + + return offset; +}; + +Utf32Decoder.prototype.end = function() { + this.overflow.length = 0; +}; + +// == UTF-32 Auto codec ============================================================= +// Decoder chooses automatically from UTF-32LE and UTF-32BE using BOM and space-based heuristic. +// Defaults to UTF-32LE. http://en.wikipedia.org/wiki/UTF-32 +// Encoder/decoder default can be changed: iconv.decode(buf, 'utf32', {defaultEncoding: 'utf-32be'}); + +// Encoder prepends BOM (which can be overridden with (addBOM: false}). + +exports.utf32 = Utf32AutoCodec; +exports.ucs4 = 'utf32'; + +function Utf32AutoCodec(options, iconv) { + this.iconv = iconv; +} + +Utf32AutoCodec.prototype.encoder = Utf32AutoEncoder; +Utf32AutoCodec.prototype.decoder = Utf32AutoDecoder; + +// -- Encoding + +function Utf32AutoEncoder(options, codec) { + options = options || {}; + + if (options.addBOM === undefined) + options.addBOM = true; + + this.encoder = codec.iconv.getEncoder(options.defaultEncoding || 'utf-32le', options); +} + +Utf32AutoEncoder.prototype.write = function(str) { + return this.encoder.write(str); +}; + +Utf32AutoEncoder.prototype.end = function() { + return this.encoder.end(); +}; + +// -- Decoding + +function Utf32AutoDecoder(options, codec) { + this.decoder = null; + this.initialBufs = []; + this.initialBufsLen = 0; + this.options = options || {}; + this.iconv = codec.iconv; +} + +Utf32AutoDecoder.prototype.write = function(buf) { + if (!this.decoder) { + // Codec is not chosen yet. Accumulate initial bytes. + this.initialBufs.push(buf); + this.initialBufsLen += buf.length; + + if (this.initialBufsLen < 32) // We need more bytes to use space heuristic (see below) + return ''; + + // We have enough bytes -> detect endianness. + var encoding = detectEncoding(this.initialBufs, this.options.defaultEncoding); + this.decoder = this.iconv.getDecoder(encoding, this.options); + + var resStr = ''; + for (var i = 0; i < this.initialBufs.length; i++) + resStr += this.decoder.write(this.initialBufs[i]); + + this.initialBufs.length = this.initialBufsLen = 0; + return resStr; + } + + return this.decoder.write(buf); +}; + +Utf32AutoDecoder.prototype.end = function() { + if (!this.decoder) { + var encoding = detectEncoding(this.initialBufs, this.options.defaultEncoding); + this.decoder = this.iconv.getDecoder(encoding, this.options); + + var resStr = ''; + for (var i = 0; i < this.initialBufs.length; i++) + resStr += this.decoder.write(this.initialBufs[i]); + + var trail = this.decoder.end(); + if (trail) + resStr += trail; + + this.initialBufs.length = this.initialBufsLen = 0; + return resStr; + } + + return this.decoder.end(); +}; + +function detectEncoding(bufs, defaultEncoding) { + var b = []; + var charsProcessed = 0; + var invalidLE = 0, invalidBE = 0; // Number of invalid chars when decoded as LE or BE. + var bmpCharsLE = 0, bmpCharsBE = 0; // Number of BMP chars when decoded as LE or BE. + + outer_loop: + for (var i = 0; i < bufs.length; i++) { + var buf = bufs[i]; + for (var j = 0; j < buf.length; j++) { + b.push(buf[j]); + if (b.length === 4) { + if (charsProcessed === 0) { + // Check BOM first. + if (b[0] === 0xFF && b[1] === 0xFE && b[2] === 0 && b[3] === 0) { + return 'utf-32le'; + } + if (b[0] === 0 && b[1] === 0 && b[2] === 0xFE && b[3] === 0xFF) { + return 'utf-32be'; + } + } + + if (b[0] !== 0 || b[1] > 0x10) invalidBE++; + if (b[3] !== 0 || b[2] > 0x10) invalidLE++; + + if (b[0] === 0 && b[1] === 0 && (b[2] !== 0 || b[3] !== 0)) bmpCharsBE++; + if ((b[0] !== 0 || b[1] !== 0) && b[2] === 0 && b[3] === 0) bmpCharsLE++; + + b.length = 0; + charsProcessed++; + + if (charsProcessed >= 100) { + break outer_loop; + } + } + } + } + + // Make decisions. + if (bmpCharsBE - invalidBE > bmpCharsLE - invalidLE) return 'utf-32be'; + if (bmpCharsBE - invalidBE < bmpCharsLE - invalidLE) return 'utf-32le'; + + // Couldn't decide (likely all zeros or not enough data). + return defaultEncoding || 'utf-32le'; +} + + +/***/ }), + +/***/ 3152: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +var Buffer = (__nccwpck_require__(4199).Buffer); + +// UTF-7 codec, according to https://tools.ietf.org/html/rfc2152 +// See also below a UTF-7-IMAP codec, according to http://tools.ietf.org/html/rfc3501#section-5.1.3 + +exports.utf7 = Utf7Codec; +exports.unicode11utf7 = 'utf7'; // Alias UNICODE-1-1-UTF-7 +function Utf7Codec(codecOptions, iconv) { + this.iconv = iconv; +}; + +Utf7Codec.prototype.encoder = Utf7Encoder; +Utf7Codec.prototype.decoder = Utf7Decoder; +Utf7Codec.prototype.bomAware = true; + + +// -- Encoding + +var nonDirectChars = /[^A-Za-z0-9'\(\),-\.\/:\? \n\r\t]+/g; + +function Utf7Encoder(options, codec) { + this.iconv = codec.iconv; +} + +Utf7Encoder.prototype.write = function(str) { + // Naive implementation. + // Non-direct chars are encoded as "+-"; single "+" char is encoded as "+-". + return Buffer.from(str.replace(nonDirectChars, function(chunk) { + return "+" + (chunk === '+' ? '' : + this.iconv.encode(chunk, 'utf16-be').toString('base64').replace(/=+$/, '')) + + "-"; + }.bind(this))); +} + +Utf7Encoder.prototype.end = function() { +} + + +// -- Decoding + +function Utf7Decoder(options, codec) { + this.iconv = codec.iconv; + this.inBase64 = false; + this.base64Accum = ''; +} + +var base64Regex = /[A-Za-z0-9\/+]/; +var base64Chars = []; +for (var i = 0; i < 256; i++) + base64Chars[i] = base64Regex.test(String.fromCharCode(i)); + +var plusChar = '+'.charCodeAt(0), + minusChar = '-'.charCodeAt(0), + andChar = '&'.charCodeAt(0); + +Utf7Decoder.prototype.write = function(buf) { + var res = "", lastI = 0, + inBase64 = this.inBase64, + base64Accum = this.base64Accum; + + // The decoder is more involved as we must handle chunks in stream. + + for (var i = 0; i < buf.length; i++) { + if (!inBase64) { // We're in direct mode. + // Write direct chars until '+' + if (buf[i] == plusChar) { + res += this.iconv.decode(buf.slice(lastI, i), "ascii"); // Write direct chars. + lastI = i+1; + inBase64 = true; + } + } else { // We decode base64. + if (!base64Chars[buf[i]]) { // Base64 ended. + if (i == lastI && buf[i] == minusChar) {// "+-" -> "+" + res += "+"; + } else { + var b64str = base64Accum + this.iconv.decode(buf.slice(lastI, i), "ascii"); + res += this.iconv.decode(Buffer.from(b64str, 'base64'), "utf16-be"); + } + + if (buf[i] != minusChar) // Minus is absorbed after base64. + i--; + + lastI = i+1; + inBase64 = false; + base64Accum = ''; + } + } + } + + if (!inBase64) { + res += this.iconv.decode(buf.slice(lastI), "ascii"); // Write direct chars. + } else { + var b64str = base64Accum + this.iconv.decode(buf.slice(lastI), "ascii"); + + var canBeDecoded = b64str.length - (b64str.length % 8); // Minimal chunk: 2 quads -> 2x3 bytes -> 3 chars. + base64Accum = b64str.slice(canBeDecoded); // The rest will be decoded in future. + b64str = b64str.slice(0, canBeDecoded); + + res += this.iconv.decode(Buffer.from(b64str, 'base64'), "utf16-be"); + } + + this.inBase64 = inBase64; + this.base64Accum = base64Accum; + + return res; +} + +Utf7Decoder.prototype.end = function() { + var res = ""; + if (this.inBase64 && this.base64Accum.length > 0) + res = this.iconv.decode(Buffer.from(this.base64Accum, 'base64'), "utf16-be"); + + this.inBase64 = false; + this.base64Accum = ''; + return res; +} + + +// UTF-7-IMAP codec. +// RFC3501 Sec. 5.1.3 Modified UTF-7 (http://tools.ietf.org/html/rfc3501#section-5.1.3) +// Differences: +// * Base64 part is started by "&" instead of "+" +// * Direct characters are 0x20-0x7E, except "&" (0x26) +// * In Base64, "," is used instead of "/" +// * Base64 must not be used to represent direct characters. +// * No implicit shift back from Base64 (should always end with '-') +// * String must end in non-shifted position. +// * "-&" while in base64 is not allowed. + + +exports.utf7imap = Utf7IMAPCodec; +function Utf7IMAPCodec(codecOptions, iconv) { + this.iconv = iconv; +}; + +Utf7IMAPCodec.prototype.encoder = Utf7IMAPEncoder; +Utf7IMAPCodec.prototype.decoder = Utf7IMAPDecoder; +Utf7IMAPCodec.prototype.bomAware = true; + + +// -- Encoding + +function Utf7IMAPEncoder(options, codec) { + this.iconv = codec.iconv; + this.inBase64 = false; + this.base64Accum = Buffer.alloc(6); + this.base64AccumIdx = 0; +} + +Utf7IMAPEncoder.prototype.write = function(str) { + var inBase64 = this.inBase64, + base64Accum = this.base64Accum, + base64AccumIdx = this.base64AccumIdx, + buf = Buffer.alloc(str.length*5 + 10), bufIdx = 0; + + for (var i = 0; i < str.length; i++) { + var uChar = str.charCodeAt(i); + if (0x20 <= uChar && uChar <= 0x7E) { // Direct character or '&'. + if (inBase64) { + if (base64AccumIdx > 0) { + bufIdx += buf.write(base64Accum.slice(0, base64AccumIdx).toString('base64').replace(/\//g, ',').replace(/=+$/, ''), bufIdx); + base64AccumIdx = 0; + } + + buf[bufIdx++] = minusChar; // Write '-', then go to direct mode. + inBase64 = false; + } + + if (!inBase64) { + buf[bufIdx++] = uChar; // Write direct character + + if (uChar === andChar) // Ampersand -> '&-' + buf[bufIdx++] = minusChar; + } + + } else { // Non-direct character + if (!inBase64) { + buf[bufIdx++] = andChar; // Write '&', then go to base64 mode. + inBase64 = true; + } + if (inBase64) { + base64Accum[base64AccumIdx++] = uChar >> 8; + base64Accum[base64AccumIdx++] = uChar & 0xFF; + + if (base64AccumIdx == base64Accum.length) { + bufIdx += buf.write(base64Accum.toString('base64').replace(/\//g, ','), bufIdx); + base64AccumIdx = 0; + } + } + } + } + + this.inBase64 = inBase64; + this.base64AccumIdx = base64AccumIdx; + + return buf.slice(0, bufIdx); +} + +Utf7IMAPEncoder.prototype.end = function() { + var buf = Buffer.alloc(10), bufIdx = 0; + if (this.inBase64) { + if (this.base64AccumIdx > 0) { + bufIdx += buf.write(this.base64Accum.slice(0, this.base64AccumIdx).toString('base64').replace(/\//g, ',').replace(/=+$/, ''), bufIdx); + this.base64AccumIdx = 0; + } + + buf[bufIdx++] = minusChar; // Write '-', then go to direct mode. + this.inBase64 = false; + } + + return buf.slice(0, bufIdx); +} + + +// -- Decoding + +function Utf7IMAPDecoder(options, codec) { + this.iconv = codec.iconv; + this.inBase64 = false; + this.base64Accum = ''; +} + +var base64IMAPChars = base64Chars.slice(); +base64IMAPChars[','.charCodeAt(0)] = true; + +Utf7IMAPDecoder.prototype.write = function(buf) { + var res = "", lastI = 0, + inBase64 = this.inBase64, + base64Accum = this.base64Accum; + + // The decoder is more involved as we must handle chunks in stream. + // It is forgiving, closer to standard UTF-7 (for example, '-' is optional at the end). + + for (var i = 0; i < buf.length; i++) { + if (!inBase64) { // We're in direct mode. + // Write direct chars until '&' + if (buf[i] == andChar) { + res += this.iconv.decode(buf.slice(lastI, i), "ascii"); // Write direct chars. + lastI = i+1; + inBase64 = true; + } + } else { // We decode base64. + if (!base64IMAPChars[buf[i]]) { // Base64 ended. + if (i == lastI && buf[i] == minusChar) { // "&-" -> "&" + res += "&"; + } else { + var b64str = base64Accum + this.iconv.decode(buf.slice(lastI, i), "ascii").replace(/,/g, '/'); + res += this.iconv.decode(Buffer.from(b64str, 'base64'), "utf16-be"); + } + + if (buf[i] != minusChar) // Minus may be absorbed after base64. + i--; + + lastI = i+1; + inBase64 = false; + base64Accum = ''; + } + } + } + + if (!inBase64) { + res += this.iconv.decode(buf.slice(lastI), "ascii"); // Write direct chars. + } else { + var b64str = base64Accum + this.iconv.decode(buf.slice(lastI), "ascii").replace(/,/g, '/'); + + var canBeDecoded = b64str.length - (b64str.length % 8); // Minimal chunk: 2 quads -> 2x3 bytes -> 3 chars. + base64Accum = b64str.slice(canBeDecoded); // The rest will be decoded in future. + b64str = b64str.slice(0, canBeDecoded); + + res += this.iconv.decode(Buffer.from(b64str, 'base64'), "utf16-be"); + } + + this.inBase64 = inBase64; + this.base64Accum = base64Accum; + + return res; +} + +Utf7IMAPDecoder.prototype.end = function() { + var res = ""; + if (this.inBase64 && this.base64Accum.length > 0) + res = this.iconv.decode(Buffer.from(this.base64Accum, 'base64'), "utf16-be"); + + this.inBase64 = false; + this.base64Accum = ''; + return res; +} + + + + +/***/ }), + +/***/ 4277: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + + +var BOMChar = '\uFEFF'; + +exports.PrependBOM = PrependBOMWrapper +function PrependBOMWrapper(encoder, options) { + this.encoder = encoder; + this.addBOM = true; +} + +PrependBOMWrapper.prototype.write = function(str) { + if (this.addBOM) { + str = BOMChar + str; + this.addBOM = false; + } + + return this.encoder.write(str); +} + +PrependBOMWrapper.prototype.end = function() { + return this.encoder.end(); +} + + +//------------------------------------------------------------------------------ + +exports.StripBOM = StripBOMWrapper; +function StripBOMWrapper(decoder, options) { + this.decoder = decoder; + this.pass = false; + this.options = options || {}; +} + +StripBOMWrapper.prototype.write = function(buf) { + var res = this.decoder.write(buf); + if (this.pass || !res) + return res; + + if (res[0] === BOMChar) { + res = res.slice(1); + if (typeof this.options.stripBOM === 'function') + this.options.stripBOM(); + } + + this.pass = true; + return res; +} + +StripBOMWrapper.prototype.end = function() { + return this.decoder.end(); +} + + + +/***/ }), + +/***/ 6249: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var Buffer = (__nccwpck_require__(4199).Buffer); + +var bomHandling = __nccwpck_require__(4277), + iconv = module.exports; + +// All codecs and aliases are kept here, keyed by encoding name/alias. +// They are lazy loaded in `iconv.getCodec` from `encodings/index.js`. +iconv.encodings = null; + +// Characters emitted in case of error. +iconv.defaultCharUnicode = '�'; +iconv.defaultCharSingleByte = '?'; + +// Public API. +iconv.encode = function encode(str, encoding, options) { + str = "" + (str || ""); // Ensure string. + + var encoder = iconv.getEncoder(encoding, options); + + var res = encoder.write(str); + var trail = encoder.end(); + + return (trail && trail.length > 0) ? Buffer.concat([res, trail]) : res; +} + +iconv.decode = function decode(buf, encoding, options) { + if (typeof buf === 'string') { + if (!iconv.skipDecodeWarning) { + console.error('Iconv-lite warning: decode()-ing strings is deprecated. Refer to https://github.com/ashtuchkin/iconv-lite/wiki/Use-Buffers-when-decoding'); + iconv.skipDecodeWarning = true; + } + + buf = Buffer.from("" + (buf || ""), "binary"); // Ensure buffer. + } + + var decoder = iconv.getDecoder(encoding, options); + + var res = decoder.write(buf); + var trail = decoder.end(); + + return trail ? (res + trail) : res; +} + +iconv.encodingExists = function encodingExists(enc) { + try { + iconv.getCodec(enc); + return true; + } catch (e) { + return false; + } +} + +// Legacy aliases to convert functions +iconv.toEncoding = iconv.encode; +iconv.fromEncoding = iconv.decode; + +// Search for a codec in iconv.encodings. Cache codec data in iconv._codecDataCache. +iconv._codecDataCache = {}; +iconv.getCodec = function getCodec(encoding) { + if (!iconv.encodings) + iconv.encodings = __nccwpck_require__(5424); // Lazy load all encoding definitions. + + // Canonicalize encoding name: strip all non-alphanumeric chars and appended year. + var enc = iconv._canonicalizeEncoding(encoding); + + // Traverse iconv.encodings to find actual codec. + var codecOptions = {}; + while (true) { + var codec = iconv._codecDataCache[enc]; + if (codec) + return codec; + + var codecDef = iconv.encodings[enc]; + + switch (typeof codecDef) { + case "string": // Direct alias to other encoding. + enc = codecDef; + break; + + case "object": // Alias with options. Can be layered. + for (var key in codecDef) + codecOptions[key] = codecDef[key]; + + if (!codecOptions.encodingName) + codecOptions.encodingName = enc; + + enc = codecDef.type; + break; + + case "function": // Codec itself. + if (!codecOptions.encodingName) + codecOptions.encodingName = enc; + + // The codec function must load all tables and return object with .encoder and .decoder methods. + // It'll be called only once (for each different options object). + codec = new codecDef(codecOptions, iconv); + + iconv._codecDataCache[codecOptions.encodingName] = codec; // Save it to be reused later. + return codec; + + default: + throw new Error("Encoding not recognized: '" + encoding + "' (searched as: '"+enc+"')"); + } + } +} + +iconv._canonicalizeEncoding = function(encoding) { + // Canonicalize encoding name: strip all non-alphanumeric chars and appended year. + return (''+encoding).toLowerCase().replace(/:\d{4}$|[^0-9a-z]/g, ""); +} + +iconv.getEncoder = function getEncoder(encoding, options) { + var codec = iconv.getCodec(encoding), + encoder = new codec.encoder(options, codec); + + if (codec.bomAware && options && options.addBOM) + encoder = new bomHandling.PrependBOM(encoder, options); + + return encoder; +} + +iconv.getDecoder = function getDecoder(encoding, options) { + var codec = iconv.getCodec(encoding), + decoder = new codec.decoder(options, codec); + + if (codec.bomAware && !(options && options.stripBOM === false)) + decoder = new bomHandling.StripBOM(decoder, options); + + return decoder; +} + +// Streaming API +// NOTE: Streaming API naturally depends on 'stream' module from Node.js. Unfortunately in browser environments this module can add +// up to 100Kb to the output bundle. To avoid unnecessary code bloat, we don't enable Streaming API in browser by default. +// If you would like to enable it explicitly, please add the following code to your app: +// > iconv.enableStreamingAPI(require('stream')); +iconv.enableStreamingAPI = function enableStreamingAPI(stream_module) { + if (iconv.supportsStreams) + return; + + // Dependency-inject stream module to create IconvLite stream classes. + var streams = __nccwpck_require__(3208)(stream_module); + + // Not public API yet, but expose the stream classes. + iconv.IconvLiteEncoderStream = streams.IconvLiteEncoderStream; + iconv.IconvLiteDecoderStream = streams.IconvLiteDecoderStream; + + // Streaming API. + iconv.encodeStream = function encodeStream(encoding, options) { + return new iconv.IconvLiteEncoderStream(iconv.getEncoder(encoding, options), options); + } + + iconv.decodeStream = function decodeStream(encoding, options) { + return new iconv.IconvLiteDecoderStream(iconv.getDecoder(encoding, options), options); + } + + iconv.supportsStreams = true; +} + +// Enable Streaming API automatically if 'stream' module is available and non-empty (the majority of environments). +var stream_module; +try { + stream_module = __nccwpck_require__(2203); +} catch (e) {} + +if (stream_module && stream_module.Transform) { + iconv.enableStreamingAPI(stream_module); + +} else { + // In rare cases where 'stream' module is not available by default, throw a helpful exception. + iconv.encodeStream = iconv.decodeStream = function() { + throw new Error("iconv-lite Streaming API is not enabled. Use iconv.enableStreamingAPI(require('stream')); to enable it."); + }; +} + +if (false) {} + + +/***/ }), + +/***/ 3208: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +var Buffer = (__nccwpck_require__(4199).Buffer); + +// NOTE: Due to 'stream' module being pretty large (~100Kb, significant in browser environments), +// we opt to dependency-inject it instead of creating a hard dependency. +module.exports = function(stream_module) { + var Transform = stream_module.Transform; + + // == Encoder stream ======================================================= + + function IconvLiteEncoderStream(conv, options) { + this.conv = conv; + options = options || {}; + options.decodeStrings = false; // We accept only strings, so we don't need to decode them. + Transform.call(this, options); + } + + IconvLiteEncoderStream.prototype = Object.create(Transform.prototype, { + constructor: { value: IconvLiteEncoderStream } + }); + + IconvLiteEncoderStream.prototype._transform = function(chunk, encoding, done) { + if (typeof chunk != 'string') + return done(new Error("Iconv encoding stream needs strings as its input.")); + try { + var res = this.conv.write(chunk); + if (res && res.length) this.push(res); + done(); + } + catch (e) { + done(e); + } + } + + IconvLiteEncoderStream.prototype._flush = function(done) { + try { + var res = this.conv.end(); + if (res && res.length) this.push(res); + done(); + } + catch (e) { + done(e); + } + } + + IconvLiteEncoderStream.prototype.collect = function(cb) { + var chunks = []; + this.on('error', cb); + this.on('data', function(chunk) { chunks.push(chunk); }); + this.on('end', function() { + cb(null, Buffer.concat(chunks)); + }); + return this; + } + + + // == Decoder stream ======================================================= + + function IconvLiteDecoderStream(conv, options) { + this.conv = conv; + options = options || {}; + options.encoding = this.encoding = 'utf8'; // We output strings. + Transform.call(this, options); + } + + IconvLiteDecoderStream.prototype = Object.create(Transform.prototype, { + constructor: { value: IconvLiteDecoderStream } + }); + + IconvLiteDecoderStream.prototype._transform = function(chunk, encoding, done) { + if (!Buffer.isBuffer(chunk) && !(chunk instanceof Uint8Array)) + return done(new Error("Iconv decoding stream needs buffers as its input.")); + try { + var res = this.conv.write(chunk); + if (res && res.length) this.push(res, this.encoding); + done(); + } + catch (e) { + done(e); + } + } + + IconvLiteDecoderStream.prototype._flush = function(done) { + try { + var res = this.conv.end(); + if (res && res.length) this.push(res, this.encoding); + done(); + } + catch (e) { + done(e); + } + } + + IconvLiteDecoderStream.prototype.collect = function(cb) { + var res = ''; + this.on('error', cb); + this.on('data', function(chunk) { res += chunk; }); + this.on('end', function() { + cb(null, res); + }); + return this; + } + + return { + IconvLiteEncoderStream: IconvLiteEncoderStream, + IconvLiteDecoderStream: IconvLiteDecoderStream, + }; +}; + + +/***/ }), + +/***/ 4199: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +/* eslint-disable node/no-deprecated-api */ + + + +var buffer = __nccwpck_require__(181) +var Buffer = buffer.Buffer + +var safer = {} + +var key + +for (key in buffer) { + if (!buffer.hasOwnProperty(key)) continue + if (key === 'SlowBuffer' || key === 'Buffer') continue + safer[key] = buffer[key] +} + +var Safer = safer.Buffer = {} +for (key in Buffer) { + if (!Buffer.hasOwnProperty(key)) continue + if (key === 'allocUnsafe' || key === 'allocUnsafeSlow') continue + Safer[key] = Buffer[key] +} + +safer.Buffer.prototype = Buffer.prototype + +if (!Safer.from || Safer.from === Uint8Array.from) { + Safer.from = function (value, encodingOrOffset, length) { + if (typeof value === 'number') { + throw new TypeError('The "value" argument must not be of type number. Received type ' + typeof value) + } + if (value && typeof value.length === 'undefined') { + throw new TypeError('The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object. Received type ' + typeof value) + } + return Buffer(value, encodingOrOffset, length) + } +} + +if (!Safer.alloc) { + Safer.alloc = function (size, fill, encoding) { + if (typeof size !== 'number') { + throw new TypeError('The "size" argument must be of type number. Received type ' + typeof size) + } + if (size < 0 || size >= 2 * (1 << 30)) { + throw new RangeError('The value "' + size + '" is invalid for option "size"') + } + var buf = Buffer(size) + if (!fill || fill.length === 0) { + buf.fill(0) + } else if (typeof encoding === 'string') { + buf.fill(fill, encoding) + } else { + buf.fill(fill) + } + return buf + } +} + +if (!safer.kStringMaxLength) { + try { + safer.kStringMaxLength = process.binding('buffer').kStringMaxLength + } catch (e) { + // we can't determine kStringMaxLength in environments where process.binding + // is unsupported, so let's not set it + } +} + +if (!safer.constants) { + safer.constants = { + MAX_LENGTH: safer.kMaxLength + } + if (safer.kStringMaxLength) { + safer.constants.MAX_STRING_LENGTH = safer.kStringMaxLength + } +} + +module.exports = safer + + +/***/ }), + +/***/ 2613: +/***/ ((module) => { + +"use strict"; +module.exports = require("assert"); + +/***/ }), + +/***/ 290: +/***/ ((module) => { + +"use strict"; +module.exports = require("async_hooks"); + +/***/ }), + +/***/ 181: +/***/ ((module) => { + +"use strict"; +module.exports = require("buffer"); + +/***/ }), + +/***/ 5317: +/***/ ((module) => { + +"use strict"; +module.exports = require("child_process"); + +/***/ }), + +/***/ 4236: +/***/ ((module) => { + +"use strict"; +module.exports = require("console"); + +/***/ }), + +/***/ 6982: +/***/ ((module) => { + +"use strict"; +module.exports = require("crypto"); + +/***/ }), + +/***/ 1637: +/***/ ((module) => { + +"use strict"; +module.exports = require("diagnostics_channel"); + +/***/ }), + +/***/ 4434: +/***/ ((module) => { + +"use strict"; +module.exports = require("events"); + +/***/ }), + +/***/ 9896: +/***/ ((module) => { + +"use strict"; +module.exports = require("fs"); + +/***/ }), + +/***/ 8611: +/***/ ((module) => { + +"use strict"; +module.exports = require("http"); + +/***/ }), + +/***/ 5675: +/***/ ((module) => { + +"use strict"; +module.exports = require("http2"); + +/***/ }), + +/***/ 5692: +/***/ ((module) => { + +"use strict"; +module.exports = require("https"); + +/***/ }), + +/***/ 9278: +/***/ ((module) => { + +"use strict"; +module.exports = require("net"); + +/***/ }), + +/***/ 8474: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:events"); + +/***/ }), + +/***/ 7075: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:stream"); + +/***/ }), + +/***/ 7975: +/***/ ((module) => { + +"use strict"; +module.exports = require("node:util"); + +/***/ }), + +/***/ 857: +/***/ ((module) => { + +"use strict"; +module.exports = require("os"); + +/***/ }), + +/***/ 6928: +/***/ ((module) => { + +"use strict"; +module.exports = require("path"); + +/***/ }), + +/***/ 2987: +/***/ ((module) => { + +"use strict"; +module.exports = require("perf_hooks"); + +/***/ }), + +/***/ 4876: +/***/ ((module) => { + +"use strict"; +module.exports = require("punycode"); + +/***/ }), + +/***/ 3480: +/***/ ((module) => { + +"use strict"; +module.exports = require("querystring"); + +/***/ }), + +/***/ 2203: +/***/ ((module) => { + +"use strict"; +module.exports = require("stream"); + +/***/ }), + +/***/ 3774: +/***/ ((module) => { + +"use strict"; +module.exports = require("stream/web"); + +/***/ }), + +/***/ 3193: +/***/ ((module) => { + +"use strict"; +module.exports = require("string_decoder"); + +/***/ }), + +/***/ 3557: +/***/ ((module) => { + +"use strict"; +module.exports = require("timers"); + +/***/ }), + +/***/ 4756: +/***/ ((module) => { + +"use strict"; +module.exports = require("tls"); + +/***/ }), + +/***/ 7016: +/***/ ((module) => { + +"use strict"; +module.exports = require("url"); + +/***/ }), + +/***/ 9023: +/***/ ((module) => { + +"use strict"; +module.exports = require("util"); + +/***/ }), + +/***/ 8253: +/***/ ((module) => { + +"use strict"; +module.exports = require("util/types"); + +/***/ }), + +/***/ 8167: +/***/ ((module) => { + +"use strict"; +module.exports = require("worker_threads"); + +/***/ }), + +/***/ 3106: +/***/ ((module) => { + +"use strict"; +module.exports = require("zlib"); + +/***/ }), + +/***/ 7182: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const WritableStream = (__nccwpck_require__(7075).Writable) +const inherits = (__nccwpck_require__(7975).inherits) + +const StreamSearch = __nccwpck_require__(4136) + +const PartStream = __nccwpck_require__(612) +const HeaderParser = __nccwpck_require__(2271) + +const DASH = 45 +const B_ONEDASH = Buffer.from('-') +const B_CRLF = Buffer.from('\r\n') +const EMPTY_FN = function () {} + +function Dicer (cfg) { + if (!(this instanceof Dicer)) { return new Dicer(cfg) } + WritableStream.call(this, cfg) + + if (!cfg || (!cfg.headerFirst && typeof cfg.boundary !== 'string')) { throw new TypeError('Boundary required') } + + if (typeof cfg.boundary === 'string') { this.setBoundary(cfg.boundary) } else { this._bparser = undefined } + + this._headerFirst = cfg.headerFirst + + this._dashes = 0 + this._parts = 0 + this._finished = false + this._realFinish = false + this._isPreamble = true + this._justMatched = false + this._firstWrite = true + this._inHeader = true + this._part = undefined + this._cb = undefined + this._ignoreData = false + this._partOpts = { highWaterMark: cfg.partHwm } + this._pause = false + + const self = this + this._hparser = new HeaderParser(cfg) + this._hparser.on('header', function (header) { + self._inHeader = false + self._part.emit('header', header) + }) +} +inherits(Dicer, WritableStream) + +Dicer.prototype.emit = function (ev) { + if (ev === 'finish' && !this._realFinish) { + if (!this._finished) { + const self = this + process.nextTick(function () { + self.emit('error', new Error('Unexpected end of multipart data')) + if (self._part && !self._ignoreData) { + const type = (self._isPreamble ? 'Preamble' : 'Part') + self._part.emit('error', new Error(type + ' terminated early due to unexpected end of multipart data')) + self._part.push(null) + process.nextTick(function () { + self._realFinish = true + self.emit('finish') + self._realFinish = false + }) + return + } + self._realFinish = true + self.emit('finish') + self._realFinish = false + }) + } + } else { WritableStream.prototype.emit.apply(this, arguments) } +} + +Dicer.prototype._write = function (data, encoding, cb) { + // ignore unexpected data (e.g. extra trailer data after finished) + if (!this._hparser && !this._bparser) { return cb() } + + if (this._headerFirst && this._isPreamble) { + if (!this._part) { + this._part = new PartStream(this._partOpts) + if (this.listenerCount('preamble') !== 0) { this.emit('preamble', this._part) } else { this._ignore() } + } + const r = this._hparser.push(data) + if (!this._inHeader && r !== undefined && r < data.length) { data = data.slice(r) } else { return cb() } + } + + // allows for "easier" testing + if (this._firstWrite) { + this._bparser.push(B_CRLF) + this._firstWrite = false + } + + this._bparser.push(data) + + if (this._pause) { this._cb = cb } else { cb() } +} + +Dicer.prototype.reset = function () { + this._part = undefined + this._bparser = undefined + this._hparser = undefined +} + +Dicer.prototype.setBoundary = function (boundary) { + const self = this + this._bparser = new StreamSearch('\r\n--' + boundary) + this._bparser.on('info', function (isMatch, data, start, end) { + self._oninfo(isMatch, data, start, end) + }) +} + +Dicer.prototype._ignore = function () { + if (this._part && !this._ignoreData) { + this._ignoreData = true + this._part.on('error', EMPTY_FN) + // we must perform some kind of read on the stream even though we are + // ignoring the data, otherwise node's Readable stream will not emit 'end' + // after pushing null to the stream + this._part.resume() + } +} + +Dicer.prototype._oninfo = function (isMatch, data, start, end) { + let buf; const self = this; let i = 0; let r; let shouldWriteMore = true + + if (!this._part && this._justMatched && data) { + while (this._dashes < 2 && (start + i) < end) { + if (data[start + i] === DASH) { + ++i + ++this._dashes + } else { + if (this._dashes) { buf = B_ONEDASH } + this._dashes = 0 + break + } + } + if (this._dashes === 2) { + if ((start + i) < end && this.listenerCount('trailer') !== 0) { this.emit('trailer', data.slice(start + i, end)) } + this.reset() + this._finished = true + // no more parts will be added + if (self._parts === 0) { + self._realFinish = true + self.emit('finish') + self._realFinish = false + } + } + if (this._dashes) { return } + } + if (this._justMatched) { this._justMatched = false } + if (!this._part) { + this._part = new PartStream(this._partOpts) + this._part._read = function (n) { + self._unpause() + } + if (this._isPreamble && this.listenerCount('preamble') !== 0) { + this.emit('preamble', this._part) + } else if (this._isPreamble !== true && this.listenerCount('part') !== 0) { + this.emit('part', this._part) + } else { + this._ignore() + } + if (!this._isPreamble) { this._inHeader = true } + } + if (data && start < end && !this._ignoreData) { + if (this._isPreamble || !this._inHeader) { + if (buf) { shouldWriteMore = this._part.push(buf) } + shouldWriteMore = this._part.push(data.slice(start, end)) + if (!shouldWriteMore) { this._pause = true } + } else if (!this._isPreamble && this._inHeader) { + if (buf) { this._hparser.push(buf) } + r = this._hparser.push(data.slice(start, end)) + if (!this._inHeader && r !== undefined && r < end) { this._oninfo(false, data, start + r, end) } + } + } + if (isMatch) { + this._hparser.reset() + if (this._isPreamble) { this._isPreamble = false } else { + if (start !== end) { + ++this._parts + this._part.on('end', function () { + if (--self._parts === 0) { + if (self._finished) { + self._realFinish = true + self.emit('finish') + self._realFinish = false + } else { + self._unpause() + } + } + }) + } + } + this._part.push(null) + this._part = undefined + this._ignoreData = false + this._justMatched = true + this._dashes = 0 + } +} + +Dicer.prototype._unpause = function () { + if (!this._pause) { return } + + this._pause = false + if (this._cb) { + const cb = this._cb + this._cb = undefined + cb() + } +} + +module.exports = Dicer + + +/***/ }), + +/***/ 2271: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const EventEmitter = (__nccwpck_require__(8474).EventEmitter) +const inherits = (__nccwpck_require__(7975).inherits) +const getLimit = __nccwpck_require__(2393) + +const StreamSearch = __nccwpck_require__(4136) + +const B_DCRLF = Buffer.from('\r\n\r\n') +const RE_CRLF = /\r\n/g +const RE_HDR = /^([^:]+):[ \t]?([\x00-\xFF]+)?$/ // eslint-disable-line no-control-regex + +function HeaderParser (cfg) { + EventEmitter.call(this) + + cfg = cfg || {} + const self = this + this.nread = 0 + this.maxed = false + this.npairs = 0 + this.maxHeaderPairs = getLimit(cfg, 'maxHeaderPairs', 2000) + this.maxHeaderSize = getLimit(cfg, 'maxHeaderSize', 80 * 1024) + this.buffer = '' + this.header = {} + this.finished = false + this.ss = new StreamSearch(B_DCRLF) + this.ss.on('info', function (isMatch, data, start, end) { + if (data && !self.maxed) { + if (self.nread + end - start >= self.maxHeaderSize) { + end = self.maxHeaderSize - self.nread + start + self.nread = self.maxHeaderSize + self.maxed = true + } else { self.nread += (end - start) } + + self.buffer += data.toString('binary', start, end) + } + if (isMatch) { self._finish() } + }) +} +inherits(HeaderParser, EventEmitter) + +HeaderParser.prototype.push = function (data) { + const r = this.ss.push(data) + if (this.finished) { return r } +} + +HeaderParser.prototype.reset = function () { + this.finished = false + this.buffer = '' + this.header = {} + this.ss.reset() +} + +HeaderParser.prototype._finish = function () { + if (this.buffer) { this._parseHeader() } + this.ss.matches = this.ss.maxMatches + const header = this.header + this.header = {} + this.buffer = '' + this.finished = true + this.nread = this.npairs = 0 + this.maxed = false + this.emit('header', header) +} + +HeaderParser.prototype._parseHeader = function () { + if (this.npairs === this.maxHeaderPairs) { return } + + const lines = this.buffer.split(RE_CRLF) + const len = lines.length + let m, h + + for (var i = 0; i < len; ++i) { // eslint-disable-line no-var + if (lines[i].length === 0) { continue } + if (lines[i][0] === '\t' || lines[i][0] === ' ') { + // folded header content + // RFC2822 says to just remove the CRLF and not the whitespace following + // it, so we follow the RFC and include the leading whitespace ... + if (h) { + this.header[h][this.header[h].length - 1] += lines[i] + continue + } + } + + const posColon = lines[i].indexOf(':') + if ( + posColon === -1 || + posColon === 0 + ) { + return + } + m = RE_HDR.exec(lines[i]) + h = m[1].toLowerCase() + this.header[h] = this.header[h] || [] + this.header[h].push((m[2] || '')) + if (++this.npairs === this.maxHeaderPairs) { break } + } +} + +module.exports = HeaderParser + + +/***/ }), + +/***/ 612: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const inherits = (__nccwpck_require__(7975).inherits) +const ReadableStream = (__nccwpck_require__(7075).Readable) + +function PartStream (opts) { + ReadableStream.call(this, opts) +} +inherits(PartStream, ReadableStream) + +PartStream.prototype._read = function (n) {} + +module.exports = PartStream + + +/***/ }), + +/***/ 4136: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +/** + * Copyright Brian White. All rights reserved. + * + * @see https://github.com/mscdex/streamsearch + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + * Based heavily on the Streaming Boyer-Moore-Horspool C++ implementation + * by Hongli Lai at: https://github.com/FooBarWidget/boyer-moore-horspool + */ +const EventEmitter = (__nccwpck_require__(8474).EventEmitter) +const inherits = (__nccwpck_require__(7975).inherits) + +function SBMH (needle) { + if (typeof needle === 'string') { + needle = Buffer.from(needle) + } + + if (!Buffer.isBuffer(needle)) { + throw new TypeError('The needle has to be a String or a Buffer.') + } + + const needleLength = needle.length + + if (needleLength === 0) { + throw new Error('The needle cannot be an empty String/Buffer.') + } + + if (needleLength > 256) { + throw new Error('The needle cannot have a length bigger than 256.') + } + + this.maxMatches = Infinity + this.matches = 0 + + this._occ = new Array(256) + .fill(needleLength) // Initialize occurrence table. + this._lookbehind_size = 0 + this._needle = needle + this._bufpos = 0 + + this._lookbehind = Buffer.alloc(needleLength) + + // Populate occurrence table with analysis of the needle, + // ignoring last letter. + for (var i = 0; i < needleLength - 1; ++i) { // eslint-disable-line no-var + this._occ[needle[i]] = needleLength - 1 - i + } +} +inherits(SBMH, EventEmitter) + +SBMH.prototype.reset = function () { + this._lookbehind_size = 0 + this.matches = 0 + this._bufpos = 0 +} + +SBMH.prototype.push = function (chunk, pos) { + if (!Buffer.isBuffer(chunk)) { + chunk = Buffer.from(chunk, 'binary') + } + const chlen = chunk.length + this._bufpos = pos || 0 + let r + while (r !== chlen && this.matches < this.maxMatches) { r = this._sbmh_feed(chunk) } + return r +} + +SBMH.prototype._sbmh_feed = function (data) { + const len = data.length + const needle = this._needle + const needleLength = needle.length + const lastNeedleChar = needle[needleLength - 1] + + // Positive: points to a position in `data` + // pos == 3 points to data[3] + // Negative: points to a position in the lookbehind buffer + // pos == -2 points to lookbehind[lookbehind_size - 2] + let pos = -this._lookbehind_size + let ch + + if (pos < 0) { + // Lookbehind buffer is not empty. Perform Boyer-Moore-Horspool + // search with character lookup code that considers both the + // lookbehind buffer and the current round's haystack data. + // + // Loop until + // there is a match. + // or until + // we've moved past the position that requires the + // lookbehind buffer. In this case we switch to the + // optimized loop. + // or until + // the character to look at lies outside the haystack. + while (pos < 0 && pos <= len - needleLength) { + ch = this._sbmh_lookup_char(data, pos + needleLength - 1) + + if ( + ch === lastNeedleChar && + this._sbmh_memcmp(data, pos, needleLength - 1) + ) { + this._lookbehind_size = 0 + ++this.matches + this.emit('info', true) + + return (this._bufpos = pos + needleLength) + } + pos += this._occ[ch] + } + + // No match. + + if (pos < 0) { + // There's too few data for Boyer-Moore-Horspool to run, + // so let's use a different algorithm to skip as much as + // we can. + // Forward pos until + // the trailing part of lookbehind + data + // looks like the beginning of the needle + // or until + // pos == 0 + while (pos < 0 && !this._sbmh_memcmp(data, pos, len - pos)) { ++pos } + } + + if (pos >= 0) { + // Discard lookbehind buffer. + this.emit('info', false, this._lookbehind, 0, this._lookbehind_size) + this._lookbehind_size = 0 + } else { + // Cut off part of the lookbehind buffer that has + // been processed and append the entire haystack + // into it. + const bytesToCutOff = this._lookbehind_size + pos + if (bytesToCutOff > 0) { + // The cut off data is guaranteed not to contain the needle. + this.emit('info', false, this._lookbehind, 0, bytesToCutOff) + } + + this._lookbehind.copy(this._lookbehind, 0, bytesToCutOff, + this._lookbehind_size - bytesToCutOff) + this._lookbehind_size -= bytesToCutOff + + data.copy(this._lookbehind, this._lookbehind_size) + this._lookbehind_size += len + + this._bufpos = len + return len + } + } + + pos += (pos >= 0) * this._bufpos + + // Lookbehind buffer is now empty. We only need to check if the + // needle is in the haystack. + if (data.indexOf(needle, pos) !== -1) { + pos = data.indexOf(needle, pos) + ++this.matches + if (pos > 0) { this.emit('info', true, data, this._bufpos, pos) } else { this.emit('info', true) } + + return (this._bufpos = pos + needleLength) + } else { + pos = len - needleLength + } + + // There was no match. If there's trailing haystack data that we cannot + // match yet using the Boyer-Moore-Horspool algorithm (because the trailing + // data is less than the needle size) then match using a modified + // algorithm that starts matching from the beginning instead of the end. + // Whatever trailing data is left after running this algorithm is added to + // the lookbehind buffer. + while ( + pos < len && + ( + data[pos] !== needle[0] || + ( + (Buffer.compare( + data.subarray(pos, pos + len - pos), + needle.subarray(0, len - pos) + ) !== 0) + ) + ) + ) { + ++pos + } + if (pos < len) { + data.copy(this._lookbehind, 0, pos, pos + (len - pos)) + this._lookbehind_size = len - pos + } + + // Everything until pos is guaranteed not to contain needle data. + if (pos > 0) { this.emit('info', false, data, this._bufpos, pos < len ? pos : len) } + + this._bufpos = len + return len +} + +SBMH.prototype._sbmh_lookup_char = function (data, pos) { + return (pos < 0) + ? this._lookbehind[this._lookbehind_size + pos] + : data[pos] +} + +SBMH.prototype._sbmh_memcmp = function (data, pos, len) { + for (var i = 0; i < len; ++i) { // eslint-disable-line no-var + if (this._sbmh_lookup_char(data, pos + i) !== this._needle[i]) { return false } + } + return true +} + +module.exports = SBMH + + +/***/ }), + +/***/ 9581: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const WritableStream = (__nccwpck_require__(7075).Writable) +const { inherits } = __nccwpck_require__(7975) +const Dicer = __nccwpck_require__(7182) + +const MultipartParser = __nccwpck_require__(1192) +const UrlencodedParser = __nccwpck_require__(855) +const parseParams = __nccwpck_require__(8929) + +function Busboy (opts) { + if (!(this instanceof Busboy)) { return new Busboy(opts) } + + if (typeof opts !== 'object') { + throw new TypeError('Busboy expected an options-Object.') + } + if (typeof opts.headers !== 'object') { + throw new TypeError('Busboy expected an options-Object with headers-attribute.') + } + if (typeof opts.headers['content-type'] !== 'string') { + throw new TypeError('Missing Content-Type-header.') + } + + const { + headers, + ...streamOptions + } = opts + + this.opts = { + autoDestroy: false, + ...streamOptions + } + WritableStream.call(this, this.opts) + + this._done = false + this._parser = this.getParserByHeaders(headers) + this._finished = false +} +inherits(Busboy, WritableStream) + +Busboy.prototype.emit = function (ev) { + if (ev === 'finish') { + if (!this._done) { + this._parser?.end() + return + } else if (this._finished) { + return + } + this._finished = true + } + WritableStream.prototype.emit.apply(this, arguments) +} + +Busboy.prototype.getParserByHeaders = function (headers) { + const parsed = parseParams(headers['content-type']) + + const cfg = { + defCharset: this.opts.defCharset, + fileHwm: this.opts.fileHwm, + headers, + highWaterMark: this.opts.highWaterMark, + isPartAFile: this.opts.isPartAFile, + limits: this.opts.limits, + parsedConType: parsed, + preservePath: this.opts.preservePath + } + + if (MultipartParser.detect.test(parsed[0])) { + return new MultipartParser(this, cfg) + } + if (UrlencodedParser.detect.test(parsed[0])) { + return new UrlencodedParser(this, cfg) + } + throw new Error('Unsupported Content-Type.') +} + +Busboy.prototype._write = function (chunk, encoding, cb) { + this._parser.write(chunk, cb) +} + +module.exports = Busboy +module.exports["default"] = Busboy +module.exports.Busboy = Busboy + +module.exports.Dicer = Dicer + + +/***/ }), + +/***/ 1192: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +// TODO: +// * support 1 nested multipart level +// (see second multipart example here: +// http://www.w3.org/TR/html401/interact/forms.html#didx-multipartform-data) +// * support limits.fieldNameSize +// -- this will require modifications to utils.parseParams + +const { Readable } = __nccwpck_require__(7075) +const { inherits } = __nccwpck_require__(7975) + +const Dicer = __nccwpck_require__(7182) + +const parseParams = __nccwpck_require__(8929) +const decodeText = __nccwpck_require__(2747) +const basename = __nccwpck_require__(692) +const getLimit = __nccwpck_require__(2393) + +const RE_BOUNDARY = /^boundary$/i +const RE_FIELD = /^form-data$/i +const RE_CHARSET = /^charset$/i +const RE_FILENAME = /^filename$/i +const RE_NAME = /^name$/i + +Multipart.detect = /^multipart\/form-data/i +function Multipart (boy, cfg) { + let i + let len + const self = this + let boundary + const limits = cfg.limits + const isPartAFile = cfg.isPartAFile || ((fieldName, contentType, fileName) => (contentType === 'application/octet-stream' || fileName !== undefined)) + const parsedConType = cfg.parsedConType || [] + const defCharset = cfg.defCharset || 'utf8' + const preservePath = cfg.preservePath + const fileOpts = { highWaterMark: cfg.fileHwm } + + for (i = 0, len = parsedConType.length; i < len; ++i) { + if (Array.isArray(parsedConType[i]) && + RE_BOUNDARY.test(parsedConType[i][0])) { + boundary = parsedConType[i][1] + break + } + } + + function checkFinished () { + if (nends === 0 && finished && !boy._done) { + finished = false + self.end() + } + } + + if (typeof boundary !== 'string') { throw new Error('Multipart: Boundary not found') } + + const fieldSizeLimit = getLimit(limits, 'fieldSize', 1 * 1024 * 1024) + const fileSizeLimit = getLimit(limits, 'fileSize', Infinity) + const filesLimit = getLimit(limits, 'files', Infinity) + const fieldsLimit = getLimit(limits, 'fields', Infinity) + const partsLimit = getLimit(limits, 'parts', Infinity) + const headerPairsLimit = getLimit(limits, 'headerPairs', 2000) + const headerSizeLimit = getLimit(limits, 'headerSize', 80 * 1024) + + let nfiles = 0 + let nfields = 0 + let nends = 0 + let curFile + let curField + let finished = false + + this._needDrain = false + this._pause = false + this._cb = undefined + this._nparts = 0 + this._boy = boy + + const parserCfg = { + boundary, + maxHeaderPairs: headerPairsLimit, + maxHeaderSize: headerSizeLimit, + partHwm: fileOpts.highWaterMark, + highWaterMark: cfg.highWaterMark + } + + this.parser = new Dicer(parserCfg) + this.parser.on('drain', function () { + self._needDrain = false + if (self._cb && !self._pause) { + const cb = self._cb + self._cb = undefined + cb() + } + }).on('part', function onPart (part) { + if (++self._nparts > partsLimit) { + self.parser.removeListener('part', onPart) + self.parser.on('part', skipPart) + boy.hitPartsLimit = true + boy.emit('partsLimit') + return skipPart(part) + } + + // hack because streams2 _always_ doesn't emit 'end' until nextTick, so let + // us emit 'end' early since we know the part has ended if we are already + // seeing the next part + if (curField) { + const field = curField + field.emit('end') + field.removeAllListeners('end') + } + + part.on('header', function (header) { + let contype + let fieldname + let parsed + let charset + let encoding + let filename + let nsize = 0 + + if (header['content-type']) { + parsed = parseParams(header['content-type'][0]) + if (parsed[0]) { + contype = parsed[0].toLowerCase() + for (i = 0, len = parsed.length; i < len; ++i) { + if (RE_CHARSET.test(parsed[i][0])) { + charset = parsed[i][1].toLowerCase() + break + } + } + } + } + + if (contype === undefined) { contype = 'text/plain' } + if (charset === undefined) { charset = defCharset } + + if (header['content-disposition']) { + parsed = parseParams(header['content-disposition'][0]) + if (!RE_FIELD.test(parsed[0])) { return skipPart(part) } + for (i = 0, len = parsed.length; i < len; ++i) { + if (RE_NAME.test(parsed[i][0])) { + fieldname = parsed[i][1] + } else if (RE_FILENAME.test(parsed[i][0])) { + filename = parsed[i][1] + if (!preservePath) { filename = basename(filename) } + } + } + } else { return skipPart(part) } + + if (header['content-transfer-encoding']) { encoding = header['content-transfer-encoding'][0].toLowerCase() } else { encoding = '7bit' } + + let onData, + onEnd + + if (isPartAFile(fieldname, contype, filename)) { + // file/binary field + if (nfiles === filesLimit) { + if (!boy.hitFilesLimit) { + boy.hitFilesLimit = true + boy.emit('filesLimit') + } + return skipPart(part) + } + + ++nfiles + + if (boy.listenerCount('file') === 0) { + self.parser._ignore() + return + } + + ++nends + const file = new FileStream(fileOpts) + curFile = file + file.on('end', function () { + --nends + self._pause = false + checkFinished() + if (self._cb && !self._needDrain) { + const cb = self._cb + self._cb = undefined + cb() + } + }) + file._read = function (n) { + if (!self._pause) { return } + self._pause = false + if (self._cb && !self._needDrain) { + const cb = self._cb + self._cb = undefined + cb() + } + } + boy.emit('file', fieldname, file, filename, encoding, contype) + + onData = function (data) { + if ((nsize += data.length) > fileSizeLimit) { + const extralen = fileSizeLimit - nsize + data.length + if (extralen > 0) { file.push(data.slice(0, extralen)) } + file.truncated = true + file.bytesRead = fileSizeLimit + part.removeAllListeners('data') + file.emit('limit') + return + } else if (!file.push(data)) { self._pause = true } + + file.bytesRead = nsize + } + + onEnd = function () { + curFile = undefined + file.push(null) + } + } else { + // non-file field + if (nfields === fieldsLimit) { + if (!boy.hitFieldsLimit) { + boy.hitFieldsLimit = true + boy.emit('fieldsLimit') + } + return skipPart(part) + } + + ++nfields + ++nends + let buffer = '' + let truncated = false + curField = part + + onData = function (data) { + if ((nsize += data.length) > fieldSizeLimit) { + const extralen = (fieldSizeLimit - (nsize - data.length)) + buffer += data.toString('binary', 0, extralen) + truncated = true + part.removeAllListeners('data') + } else { buffer += data.toString('binary') } + } + + onEnd = function () { + curField = undefined + if (buffer.length) { buffer = decodeText(buffer, 'binary', charset) } + boy.emit('field', fieldname, buffer, false, truncated, encoding, contype) + --nends + checkFinished() + } + } + + /* As of node@2efe4ab761666 (v0.10.29+/v0.11.14+), busboy had become + broken. Streams2/streams3 is a huge black box of confusion, but + somehow overriding the sync state seems to fix things again (and still + seems to work for previous node versions). + */ + part._readableState.sync = false + + part.on('data', onData) + part.on('end', onEnd) + }).on('error', function (err) { + if (curFile) { curFile.emit('error', err) } + }) + }).on('error', function (err) { + boy.emit('error', err) + }).on('finish', function () { + finished = true + checkFinished() + }) +} + +Multipart.prototype.write = function (chunk, cb) { + const r = this.parser.write(chunk) + if (r && !this._pause) { + cb() + } else { + this._needDrain = !r + this._cb = cb + } +} + +Multipart.prototype.end = function () { + const self = this + + if (self.parser.writable) { + self.parser.end() + } else if (!self._boy._done) { + process.nextTick(function () { + self._boy._done = true + self._boy.emit('finish') + }) + } +} + +function skipPart (part) { + part.resume() +} + +function FileStream (opts) { + Readable.call(this, opts) + + this.bytesRead = 0 + + this.truncated = false +} + +inherits(FileStream, Readable) + +FileStream.prototype._read = function (n) {} + +module.exports = Multipart + + +/***/ }), + +/***/ 855: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Decoder = __nccwpck_require__(1496) +const decodeText = __nccwpck_require__(2747) +const getLimit = __nccwpck_require__(2393) + +const RE_CHARSET = /^charset$/i + +UrlEncoded.detect = /^application\/x-www-form-urlencoded/i +function UrlEncoded (boy, cfg) { + const limits = cfg.limits + const parsedConType = cfg.parsedConType + this.boy = boy + + this.fieldSizeLimit = getLimit(limits, 'fieldSize', 1 * 1024 * 1024) + this.fieldNameSizeLimit = getLimit(limits, 'fieldNameSize', 100) + this.fieldsLimit = getLimit(limits, 'fields', Infinity) + + let charset + for (var i = 0, len = parsedConType.length; i < len; ++i) { // eslint-disable-line no-var + if (Array.isArray(parsedConType[i]) && + RE_CHARSET.test(parsedConType[i][0])) { + charset = parsedConType[i][1].toLowerCase() + break + } + } + + if (charset === undefined) { charset = cfg.defCharset || 'utf8' } + + this.decoder = new Decoder() + this.charset = charset + this._fields = 0 + this._state = 'key' + this._checkingBytes = true + this._bytesKey = 0 + this._bytesVal = 0 + this._key = '' + this._val = '' + this._keyTrunc = false + this._valTrunc = false + this._hitLimit = false +} + +UrlEncoded.prototype.write = function (data, cb) { + if (this._fields === this.fieldsLimit) { + if (!this.boy.hitFieldsLimit) { + this.boy.hitFieldsLimit = true + this.boy.emit('fieldsLimit') + } + return cb() + } + + let idxeq; let idxamp; let i; let p = 0; const len = data.length + + while (p < len) { + if (this._state === 'key') { + idxeq = idxamp = undefined + for (i = p; i < len; ++i) { + if (!this._checkingBytes) { ++p } + if (data[i] === 0x3D/* = */) { + idxeq = i + break + } else if (data[i] === 0x26/* & */) { + idxamp = i + break + } + if (this._checkingBytes && this._bytesKey === this.fieldNameSizeLimit) { + this._hitLimit = true + break + } else if (this._checkingBytes) { ++this._bytesKey } + } + + if (idxeq !== undefined) { + // key with assignment + if (idxeq > p) { this._key += this.decoder.write(data.toString('binary', p, idxeq)) } + this._state = 'val' + + this._hitLimit = false + this._checkingBytes = true + this._val = '' + this._bytesVal = 0 + this._valTrunc = false + this.decoder.reset() + + p = idxeq + 1 + } else if (idxamp !== undefined) { + // key with no assignment + ++this._fields + let key; const keyTrunc = this._keyTrunc + if (idxamp > p) { key = (this._key += this.decoder.write(data.toString('binary', p, idxamp))) } else { key = this._key } + + this._hitLimit = false + this._checkingBytes = true + this._key = '' + this._bytesKey = 0 + this._keyTrunc = false + this.decoder.reset() + + if (key.length) { + this.boy.emit('field', decodeText(key, 'binary', this.charset), + '', + keyTrunc, + false) + } + + p = idxamp + 1 + if (this._fields === this.fieldsLimit) { return cb() } + } else if (this._hitLimit) { + // we may not have hit the actual limit if there are encoded bytes... + if (i > p) { this._key += this.decoder.write(data.toString('binary', p, i)) } + p = i + if ((this._bytesKey = this._key.length) === this.fieldNameSizeLimit) { + // yep, we actually did hit the limit + this._checkingBytes = false + this._keyTrunc = true + } + } else { + if (p < len) { this._key += this.decoder.write(data.toString('binary', p)) } + p = len + } + } else { + idxamp = undefined + for (i = p; i < len; ++i) { + if (!this._checkingBytes) { ++p } + if (data[i] === 0x26/* & */) { + idxamp = i + break + } + if (this._checkingBytes && this._bytesVal === this.fieldSizeLimit) { + this._hitLimit = true + break + } else if (this._checkingBytes) { ++this._bytesVal } + } + + if (idxamp !== undefined) { + ++this._fields + if (idxamp > p) { this._val += this.decoder.write(data.toString('binary', p, idxamp)) } + this.boy.emit('field', decodeText(this._key, 'binary', this.charset), + decodeText(this._val, 'binary', this.charset), + this._keyTrunc, + this._valTrunc) + this._state = 'key' + + this._hitLimit = false + this._checkingBytes = true + this._key = '' + this._bytesKey = 0 + this._keyTrunc = false + this.decoder.reset() + + p = idxamp + 1 + if (this._fields === this.fieldsLimit) { return cb() } + } else if (this._hitLimit) { + // we may not have hit the actual limit if there are encoded bytes... + if (i > p) { this._val += this.decoder.write(data.toString('binary', p, i)) } + p = i + if ((this._val === '' && this.fieldSizeLimit === 0) || + (this._bytesVal = this._val.length) === this.fieldSizeLimit) { + // yep, we actually did hit the limit + this._checkingBytes = false + this._valTrunc = true + } + } else { + if (p < len) { this._val += this.decoder.write(data.toString('binary', p)) } + p = len + } + } + } + cb() +} + +UrlEncoded.prototype.end = function () { + if (this.boy._done) { return } + + if (this._state === 'key' && this._key.length > 0) { + this.boy.emit('field', decodeText(this._key, 'binary', this.charset), + '', + this._keyTrunc, + false) + } else if (this._state === 'val') { + this.boy.emit('field', decodeText(this._key, 'binary', this.charset), + decodeText(this._val, 'binary', this.charset), + this._keyTrunc, + this._valTrunc) + } + this.boy._done = true + this.boy.emit('finish') +} + +module.exports = UrlEncoded + + +/***/ }), + +/***/ 1496: +/***/ ((module) => { + +"use strict"; + + +const RE_PLUS = /\+/g + +const HEX = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +] + +function Decoder () { + this.buffer = undefined +} +Decoder.prototype.write = function (str) { + // Replace '+' with ' ' before decoding + str = str.replace(RE_PLUS, ' ') + let res = '' + let i = 0; let p = 0; const len = str.length + for (; i < len; ++i) { + if (this.buffer !== undefined) { + if (!HEX[str.charCodeAt(i)]) { + res += '%' + this.buffer + this.buffer = undefined + --i // retry character + } else { + this.buffer += str[i] + ++p + if (this.buffer.length === 2) { + res += String.fromCharCode(parseInt(this.buffer, 16)) + this.buffer = undefined + } + } + } else if (str[i] === '%') { + if (i > p) { + res += str.substring(p, i) + p = i + } + this.buffer = '' + ++p + } + } + if (p < len && this.buffer === undefined) { res += str.substring(p) } + return res +} +Decoder.prototype.reset = function () { + this.buffer = undefined +} + +module.exports = Decoder + + +/***/ }), + +/***/ 692: +/***/ ((module) => { + +"use strict"; + + +module.exports = function basename (path) { + if (typeof path !== 'string') { return '' } + for (var i = path.length - 1; i >= 0; --i) { // eslint-disable-line no-var + switch (path.charCodeAt(i)) { + case 0x2F: // '/' + case 0x5C: // '\' + path = path.slice(i + 1) + return (path === '..' || path === '.' ? '' : path) + } + } + return (path === '..' || path === '.' ? '' : path) +} + + +/***/ }), + +/***/ 2747: +/***/ (function(module) { + +"use strict"; + + +// Node has always utf-8 +const utf8Decoder = new TextDecoder('utf-8') +const textDecoders = new Map([ + ['utf-8', utf8Decoder], + ['utf8', utf8Decoder] +]) + +function getDecoder (charset) { + let lc + while (true) { + switch (charset) { + case 'utf-8': + case 'utf8': + return decoders.utf8 + case 'latin1': + case 'ascii': // TODO: Make these a separate, strict decoder? + case 'us-ascii': + case 'iso-8859-1': + case 'iso8859-1': + case 'iso88591': + case 'iso_8859-1': + case 'windows-1252': + case 'iso_8859-1:1987': + case 'cp1252': + case 'x-cp1252': + return decoders.latin1 + case 'utf16le': + case 'utf-16le': + case 'ucs2': + case 'ucs-2': + return decoders.utf16le + case 'base64': + return decoders.base64 + default: + if (lc === undefined) { + lc = true + charset = charset.toLowerCase() + continue + } + return decoders.other.bind(charset) + } + } +} + +const decoders = { + utf8: (data, sourceEncoding) => { + if (data.length === 0) { + return '' + } + if (typeof data === 'string') { + data = Buffer.from(data, sourceEncoding) + } + return data.utf8Slice(0, data.length) + }, + + latin1: (data, sourceEncoding) => { + if (data.length === 0) { + return '' + } + if (typeof data === 'string') { + return data + } + return data.latin1Slice(0, data.length) + }, + + utf16le: (data, sourceEncoding) => { + if (data.length === 0) { + return '' + } + if (typeof data === 'string') { + data = Buffer.from(data, sourceEncoding) + } + return data.ucs2Slice(0, data.length) + }, + + base64: (data, sourceEncoding) => { + if (data.length === 0) { + return '' + } + if (typeof data === 'string') { + data = Buffer.from(data, sourceEncoding) + } + return data.base64Slice(0, data.length) + }, + + other: (data, sourceEncoding) => { + if (data.length === 0) { + return '' + } + if (typeof data === 'string') { + data = Buffer.from(data, sourceEncoding) + } + + if (textDecoders.has(this.toString())) { + try { + return textDecoders.get(this).decode(data) + } catch {} + } + return typeof data === 'string' + ? data + : data.toString() + } +} + +function decodeText (text, sourceEncoding, destEncoding) { + if (text) { + return getDecoder(destEncoding)(text, sourceEncoding) + } + return text +} + +module.exports = decodeText + + +/***/ }), + +/***/ 2393: +/***/ ((module) => { + +"use strict"; + + +module.exports = function getLimit (limits, name, defaultLimit) { + if ( + !limits || + limits[name] === undefined || + limits[name] === null + ) { return defaultLimit } + + if ( + typeof limits[name] !== 'number' || + isNaN(limits[name]) + ) { throw new TypeError('Limit ' + name + ' is not a valid number') } + + return limits[name] +} + + +/***/ }), + +/***/ 8929: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +/* eslint-disable object-property-newline */ + + +const decodeText = __nccwpck_require__(2747) + +const RE_ENCODED = /%[a-fA-F0-9][a-fA-F0-9]/g + +const EncodedLookup = { + '%00': '\x00', '%01': '\x01', '%02': '\x02', '%03': '\x03', '%04': '\x04', + '%05': '\x05', '%06': '\x06', '%07': '\x07', '%08': '\x08', '%09': '\x09', + '%0a': '\x0a', '%0A': '\x0a', '%0b': '\x0b', '%0B': '\x0b', '%0c': '\x0c', + '%0C': '\x0c', '%0d': '\x0d', '%0D': '\x0d', '%0e': '\x0e', '%0E': '\x0e', + '%0f': '\x0f', '%0F': '\x0f', '%10': '\x10', '%11': '\x11', '%12': '\x12', + '%13': '\x13', '%14': '\x14', '%15': '\x15', '%16': '\x16', '%17': '\x17', + '%18': '\x18', '%19': '\x19', '%1a': '\x1a', '%1A': '\x1a', '%1b': '\x1b', + '%1B': '\x1b', '%1c': '\x1c', '%1C': '\x1c', '%1d': '\x1d', '%1D': '\x1d', + '%1e': '\x1e', '%1E': '\x1e', '%1f': '\x1f', '%1F': '\x1f', '%20': '\x20', + '%21': '\x21', '%22': '\x22', '%23': '\x23', '%24': '\x24', '%25': '\x25', + '%26': '\x26', '%27': '\x27', '%28': '\x28', '%29': '\x29', '%2a': '\x2a', + '%2A': '\x2a', '%2b': '\x2b', '%2B': '\x2b', '%2c': '\x2c', '%2C': '\x2c', + '%2d': '\x2d', '%2D': '\x2d', '%2e': '\x2e', '%2E': '\x2e', '%2f': '\x2f', + '%2F': '\x2f', '%30': '\x30', '%31': '\x31', '%32': '\x32', '%33': '\x33', + '%34': '\x34', '%35': '\x35', '%36': '\x36', '%37': '\x37', '%38': '\x38', + '%39': '\x39', '%3a': '\x3a', '%3A': '\x3a', '%3b': '\x3b', '%3B': '\x3b', + '%3c': '\x3c', '%3C': '\x3c', '%3d': '\x3d', '%3D': '\x3d', '%3e': '\x3e', + '%3E': '\x3e', '%3f': '\x3f', '%3F': '\x3f', '%40': '\x40', '%41': '\x41', + '%42': '\x42', '%43': '\x43', '%44': '\x44', '%45': '\x45', '%46': '\x46', + '%47': '\x47', '%48': '\x48', '%49': '\x49', '%4a': '\x4a', '%4A': '\x4a', + '%4b': '\x4b', '%4B': '\x4b', '%4c': '\x4c', '%4C': '\x4c', '%4d': '\x4d', + '%4D': '\x4d', '%4e': '\x4e', '%4E': '\x4e', '%4f': '\x4f', '%4F': '\x4f', + '%50': '\x50', '%51': '\x51', '%52': '\x52', '%53': '\x53', '%54': '\x54', + '%55': '\x55', '%56': '\x56', '%57': '\x57', '%58': '\x58', '%59': '\x59', + '%5a': '\x5a', '%5A': '\x5a', '%5b': '\x5b', '%5B': '\x5b', '%5c': '\x5c', + '%5C': '\x5c', '%5d': '\x5d', '%5D': '\x5d', '%5e': '\x5e', '%5E': '\x5e', + '%5f': '\x5f', '%5F': '\x5f', '%60': '\x60', '%61': '\x61', '%62': '\x62', + '%63': '\x63', '%64': '\x64', '%65': '\x65', '%66': '\x66', '%67': '\x67', + '%68': '\x68', '%69': '\x69', '%6a': '\x6a', '%6A': '\x6a', '%6b': '\x6b', + '%6B': '\x6b', '%6c': '\x6c', '%6C': '\x6c', '%6d': '\x6d', '%6D': '\x6d', + '%6e': '\x6e', '%6E': '\x6e', '%6f': '\x6f', '%6F': '\x6f', '%70': '\x70', + '%71': '\x71', '%72': '\x72', '%73': '\x73', '%74': '\x74', '%75': '\x75', + '%76': '\x76', '%77': '\x77', '%78': '\x78', '%79': '\x79', '%7a': '\x7a', + '%7A': '\x7a', '%7b': '\x7b', '%7B': '\x7b', '%7c': '\x7c', '%7C': '\x7c', + '%7d': '\x7d', '%7D': '\x7d', '%7e': '\x7e', '%7E': '\x7e', '%7f': '\x7f', + '%7F': '\x7f', '%80': '\x80', '%81': '\x81', '%82': '\x82', '%83': '\x83', + '%84': '\x84', '%85': '\x85', '%86': '\x86', '%87': '\x87', '%88': '\x88', + '%89': '\x89', '%8a': '\x8a', '%8A': '\x8a', '%8b': '\x8b', '%8B': '\x8b', + '%8c': '\x8c', '%8C': '\x8c', '%8d': '\x8d', '%8D': '\x8d', '%8e': '\x8e', + '%8E': '\x8e', '%8f': '\x8f', '%8F': '\x8f', '%90': '\x90', '%91': '\x91', + '%92': '\x92', '%93': '\x93', '%94': '\x94', '%95': '\x95', '%96': '\x96', + '%97': '\x97', '%98': '\x98', '%99': '\x99', '%9a': '\x9a', '%9A': '\x9a', + '%9b': '\x9b', '%9B': '\x9b', '%9c': '\x9c', '%9C': '\x9c', '%9d': '\x9d', + '%9D': '\x9d', '%9e': '\x9e', '%9E': '\x9e', '%9f': '\x9f', '%9F': '\x9f', + '%a0': '\xa0', '%A0': '\xa0', '%a1': '\xa1', '%A1': '\xa1', '%a2': '\xa2', + '%A2': '\xa2', '%a3': '\xa3', '%A3': '\xa3', '%a4': '\xa4', '%A4': '\xa4', + '%a5': '\xa5', '%A5': '\xa5', '%a6': '\xa6', '%A6': '\xa6', '%a7': '\xa7', + '%A7': '\xa7', '%a8': '\xa8', '%A8': '\xa8', '%a9': '\xa9', '%A9': '\xa9', + '%aa': '\xaa', '%Aa': '\xaa', '%aA': '\xaa', '%AA': '\xaa', '%ab': '\xab', + '%Ab': '\xab', '%aB': '\xab', '%AB': '\xab', '%ac': '\xac', '%Ac': '\xac', + '%aC': '\xac', '%AC': '\xac', '%ad': '\xad', '%Ad': '\xad', '%aD': '\xad', + '%AD': '\xad', '%ae': '\xae', '%Ae': '\xae', '%aE': '\xae', '%AE': '\xae', + '%af': '\xaf', '%Af': '\xaf', '%aF': '\xaf', '%AF': '\xaf', '%b0': '\xb0', + '%B0': '\xb0', '%b1': '\xb1', '%B1': '\xb1', '%b2': '\xb2', '%B2': '\xb2', + '%b3': '\xb3', '%B3': '\xb3', '%b4': '\xb4', '%B4': '\xb4', '%b5': '\xb5', + '%B5': '\xb5', '%b6': '\xb6', '%B6': '\xb6', '%b7': '\xb7', '%B7': '\xb7', + '%b8': '\xb8', '%B8': '\xb8', '%b9': '\xb9', '%B9': '\xb9', '%ba': '\xba', + '%Ba': '\xba', '%bA': '\xba', '%BA': '\xba', '%bb': '\xbb', '%Bb': '\xbb', + '%bB': '\xbb', '%BB': '\xbb', '%bc': '\xbc', '%Bc': '\xbc', '%bC': '\xbc', + '%BC': '\xbc', '%bd': '\xbd', '%Bd': '\xbd', '%bD': '\xbd', '%BD': '\xbd', + '%be': '\xbe', '%Be': '\xbe', '%bE': '\xbe', '%BE': '\xbe', '%bf': '\xbf', + '%Bf': '\xbf', '%bF': '\xbf', '%BF': '\xbf', '%c0': '\xc0', '%C0': '\xc0', + '%c1': '\xc1', '%C1': '\xc1', '%c2': '\xc2', '%C2': '\xc2', '%c3': '\xc3', + '%C3': '\xc3', '%c4': '\xc4', '%C4': '\xc4', '%c5': '\xc5', '%C5': '\xc5', + '%c6': '\xc6', '%C6': '\xc6', '%c7': '\xc7', '%C7': '\xc7', '%c8': '\xc8', + '%C8': '\xc8', '%c9': '\xc9', '%C9': '\xc9', '%ca': '\xca', '%Ca': '\xca', + '%cA': '\xca', '%CA': '\xca', '%cb': '\xcb', '%Cb': '\xcb', '%cB': '\xcb', + '%CB': '\xcb', '%cc': '\xcc', '%Cc': '\xcc', '%cC': '\xcc', '%CC': '\xcc', + '%cd': '\xcd', '%Cd': '\xcd', '%cD': '\xcd', '%CD': '\xcd', '%ce': '\xce', + '%Ce': '\xce', '%cE': '\xce', '%CE': '\xce', '%cf': '\xcf', '%Cf': '\xcf', + '%cF': '\xcf', '%CF': '\xcf', '%d0': '\xd0', '%D0': '\xd0', '%d1': '\xd1', + '%D1': '\xd1', '%d2': '\xd2', '%D2': '\xd2', '%d3': '\xd3', '%D3': '\xd3', + '%d4': '\xd4', '%D4': '\xd4', '%d5': '\xd5', '%D5': '\xd5', '%d6': '\xd6', + '%D6': '\xd6', '%d7': '\xd7', '%D7': '\xd7', '%d8': '\xd8', '%D8': '\xd8', + '%d9': '\xd9', '%D9': '\xd9', '%da': '\xda', '%Da': '\xda', '%dA': '\xda', + '%DA': '\xda', '%db': '\xdb', '%Db': '\xdb', '%dB': '\xdb', '%DB': '\xdb', + '%dc': '\xdc', '%Dc': '\xdc', '%dC': '\xdc', '%DC': '\xdc', '%dd': '\xdd', + '%Dd': '\xdd', '%dD': '\xdd', '%DD': '\xdd', '%de': '\xde', '%De': '\xde', + '%dE': '\xde', '%DE': '\xde', '%df': '\xdf', '%Df': '\xdf', '%dF': '\xdf', + '%DF': '\xdf', '%e0': '\xe0', '%E0': '\xe0', '%e1': '\xe1', '%E1': '\xe1', + '%e2': '\xe2', '%E2': '\xe2', '%e3': '\xe3', '%E3': '\xe3', '%e4': '\xe4', + '%E4': '\xe4', '%e5': '\xe5', '%E5': '\xe5', '%e6': '\xe6', '%E6': '\xe6', + '%e7': '\xe7', '%E7': '\xe7', '%e8': '\xe8', '%E8': '\xe8', '%e9': '\xe9', + '%E9': '\xe9', '%ea': '\xea', '%Ea': '\xea', '%eA': '\xea', '%EA': '\xea', + '%eb': '\xeb', '%Eb': '\xeb', '%eB': '\xeb', '%EB': '\xeb', '%ec': '\xec', + '%Ec': '\xec', '%eC': '\xec', '%EC': '\xec', '%ed': '\xed', '%Ed': '\xed', + '%eD': '\xed', '%ED': '\xed', '%ee': '\xee', '%Ee': '\xee', '%eE': '\xee', + '%EE': '\xee', '%ef': '\xef', '%Ef': '\xef', '%eF': '\xef', '%EF': '\xef', + '%f0': '\xf0', '%F0': '\xf0', '%f1': '\xf1', '%F1': '\xf1', '%f2': '\xf2', + '%F2': '\xf2', '%f3': '\xf3', '%F3': '\xf3', '%f4': '\xf4', '%F4': '\xf4', + '%f5': '\xf5', '%F5': '\xf5', '%f6': '\xf6', '%F6': '\xf6', '%f7': '\xf7', + '%F7': '\xf7', '%f8': '\xf8', '%F8': '\xf8', '%f9': '\xf9', '%F9': '\xf9', + '%fa': '\xfa', '%Fa': '\xfa', '%fA': '\xfa', '%FA': '\xfa', '%fb': '\xfb', + '%Fb': '\xfb', '%fB': '\xfb', '%FB': '\xfb', '%fc': '\xfc', '%Fc': '\xfc', + '%fC': '\xfc', '%FC': '\xfc', '%fd': '\xfd', '%Fd': '\xfd', '%fD': '\xfd', + '%FD': '\xfd', '%fe': '\xfe', '%Fe': '\xfe', '%fE': '\xfe', '%FE': '\xfe', + '%ff': '\xff', '%Ff': '\xff', '%fF': '\xff', '%FF': '\xff' +} + +function encodedReplacer (match) { + return EncodedLookup[match] +} + +const STATE_KEY = 0 +const STATE_VALUE = 1 +const STATE_CHARSET = 2 +const STATE_LANG = 3 + +function parseParams (str) { + const res = [] + let state = STATE_KEY + let charset = '' + let inquote = false + let escaping = false + let p = 0 + let tmp = '' + const len = str.length + + for (var i = 0; i < len; ++i) { // eslint-disable-line no-var + const char = str[i] + if (char === '\\' && inquote) { + if (escaping) { escaping = false } else { + escaping = true + continue + } + } else if (char === '"') { + if (!escaping) { + if (inquote) { + inquote = false + state = STATE_KEY + } else { inquote = true } + continue + } else { escaping = false } + } else { + if (escaping && inquote) { tmp += '\\' } + escaping = false + if ((state === STATE_CHARSET || state === STATE_LANG) && char === "'") { + if (state === STATE_CHARSET) { + state = STATE_LANG + charset = tmp.substring(1) + } else { state = STATE_VALUE } + tmp = '' + continue + } else if (state === STATE_KEY && + (char === '*' || char === '=') && + res.length) { + state = char === '*' + ? STATE_CHARSET + : STATE_VALUE + res[p] = [tmp, undefined] + tmp = '' + continue + } else if (!inquote && char === ';') { + state = STATE_KEY + if (charset) { + if (tmp.length) { + tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer), + 'binary', + charset) + } + charset = '' + } else if (tmp.length) { + tmp = decodeText(tmp, 'binary', 'utf8') + } + if (res[p] === undefined) { res[p] = tmp } else { res[p][1] = tmp } + tmp = '' + ++p + continue + } else if (!inquote && (char === ' ' || char === '\t')) { continue } + } + tmp += char + } + if (charset && tmp.length) { + tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer), + 'binary', + charset) + } else if (tmp) { + tmp = decodeText(tmp, 'binary', 'utf8') + } + + if (res[p] === undefined) { + if (tmp) { res[p] = tmp } + } else { res[p][1] = tmp } + + return res +} + +module.exports = parseParams + + +/***/ }), + +/***/ 2472: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('[[[0,44],"disallowed_STD3_valid"],[[45,46],"valid"],[[47,47],"disallowed_STD3_valid"],[[48,57],"valid"],[[58,64],"disallowed_STD3_valid"],[[65,65],"mapped",[97]],[[66,66],"mapped",[98]],[[67,67],"mapped",[99]],[[68,68],"mapped",[100]],[[69,69],"mapped",[101]],[[70,70],"mapped",[102]],[[71,71],"mapped",[103]],[[72,72],"mapped",[104]],[[73,73],"mapped",[105]],[[74,74],"mapped",[106]],[[75,75],"mapped",[107]],[[76,76],"mapped",[108]],[[77,77],"mapped",[109]],[[78,78],"mapped",[110]],[[79,79],"mapped",[111]],[[80,80],"mapped",[112]],[[81,81],"mapped",[113]],[[82,82],"mapped",[114]],[[83,83],"mapped",[115]],[[84,84],"mapped",[116]],[[85,85],"mapped",[117]],[[86,86],"mapped",[118]],[[87,87],"mapped",[119]],[[88,88],"mapped",[120]],[[89,89],"mapped",[121]],[[90,90],"mapped",[122]],[[91,96],"disallowed_STD3_valid"],[[97,122],"valid"],[[123,127],"disallowed_STD3_valid"],[[128,159],"disallowed"],[[160,160],"disallowed_STD3_mapped",[32]],[[161,167],"valid",[],"NV8"],[[168,168],"disallowed_STD3_mapped",[32,776]],[[169,169],"valid",[],"NV8"],[[170,170],"mapped",[97]],[[171,172],"valid",[],"NV8"],[[173,173],"ignored"],[[174,174],"valid",[],"NV8"],[[175,175],"disallowed_STD3_mapped",[32,772]],[[176,177],"valid",[],"NV8"],[[178,178],"mapped",[50]],[[179,179],"mapped",[51]],[[180,180],"disallowed_STD3_mapped",[32,769]],[[181,181],"mapped",[956]],[[182,182],"valid",[],"NV8"],[[183,183],"valid"],[[184,184],"disallowed_STD3_mapped",[32,807]],[[185,185],"mapped",[49]],[[186,186],"mapped",[111]],[[187,187],"valid",[],"NV8"],[[188,188],"mapped",[49,8260,52]],[[189,189],"mapped",[49,8260,50]],[[190,190],"mapped",[51,8260,52]],[[191,191],"valid",[],"NV8"],[[192,192],"mapped",[224]],[[193,193],"mapped",[225]],[[194,194],"mapped",[226]],[[195,195],"mapped",[227]],[[196,196],"mapped",[228]],[[197,197],"mapped",[229]],[[198,198],"mapped",[230]],[[199,199],"mapped",[231]],[[200,200],"mapped",[232]],[[201,201],"mapped",[233]],[[202,202],"mapped",[234]],[[203,203],"mapped",[235]],[[204,204],"mapped",[236]],[[205,205],"mapped",[237]],[[206,206],"mapped",[238]],[[207,207],"mapped",[239]],[[208,208],"mapped",[240]],[[209,209],"mapped",[241]],[[210,210],"mapped",[242]],[[211,211],"mapped",[243]],[[212,212],"mapped",[244]],[[213,213],"mapped",[245]],[[214,214],"mapped",[246]],[[215,215],"valid",[],"NV8"],[[216,216],"mapped",[248]],[[217,217],"mapped",[249]],[[218,218],"mapped",[250]],[[219,219],"mapped",[251]],[[220,220],"mapped",[252]],[[221,221],"mapped",[253]],[[222,222],"mapped",[254]],[[223,223],"deviation",[115,115]],[[224,246],"valid"],[[247,247],"valid",[],"NV8"],[[248,255],"valid"],[[256,256],"mapped",[257]],[[257,257],"valid"],[[258,258],"mapped",[259]],[[259,259],"valid"],[[260,260],"mapped",[261]],[[261,261],"valid"],[[262,262],"mapped",[263]],[[263,263],"valid"],[[264,264],"mapped",[265]],[[265,265],"valid"],[[266,266],"mapped",[267]],[[267,267],"valid"],[[268,268],"mapped",[269]],[[269,269],"valid"],[[270,270],"mapped",[271]],[[271,271],"valid"],[[272,272],"mapped",[273]],[[273,273],"valid"],[[274,274],"mapped",[275]],[[275,275],"valid"],[[276,276],"mapped",[277]],[[277,277],"valid"],[[278,278],"mapped",[279]],[[279,279],"valid"],[[280,280],"mapped",[281]],[[281,281],"valid"],[[282,282],"mapped",[283]],[[283,283],"valid"],[[284,284],"mapped",[285]],[[285,285],"valid"],[[286,286],"mapped",[287]],[[287,287],"valid"],[[288,288],"mapped",[289]],[[289,289],"valid"],[[290,290],"mapped",[291]],[[291,291],"valid"],[[292,292],"mapped",[293]],[[293,293],"valid"],[[294,294],"mapped",[295]],[[295,295],"valid"],[[296,296],"mapped",[297]],[[297,297],"valid"],[[298,298],"mapped",[299]],[[299,299],"valid"],[[300,300],"mapped",[301]],[[301,301],"valid"],[[302,302],"mapped",[303]],[[303,303],"valid"],[[304,304],"mapped",[105,775]],[[305,305],"valid"],[[306,307],"mapped",[105,106]],[[308,308],"mapped",[309]],[[309,309],"valid"],[[310,310],"mapped",[311]],[[311,312],"valid"],[[313,313],"mapped",[314]],[[314,314],"valid"],[[315,315],"mapped",[316]],[[316,316],"valid"],[[317,317],"mapped",[318]],[[318,318],"valid"],[[319,320],"mapped",[108,183]],[[321,321],"mapped",[322]],[[322,322],"valid"],[[323,323],"mapped",[324]],[[324,324],"valid"],[[325,325],"mapped",[326]],[[326,326],"valid"],[[327,327],"mapped",[328]],[[328,328],"valid"],[[329,329],"mapped",[700,110]],[[330,330],"mapped",[331]],[[331,331],"valid"],[[332,332],"mapped",[333]],[[333,333],"valid"],[[334,334],"mapped",[335]],[[335,335],"valid"],[[336,336],"mapped",[337]],[[337,337],"valid"],[[338,338],"mapped",[339]],[[339,339],"valid"],[[340,340],"mapped",[341]],[[341,341],"valid"],[[342,342],"mapped",[343]],[[343,343],"valid"],[[344,344],"mapped",[345]],[[345,345],"valid"],[[346,346],"mapped",[347]],[[347,347],"valid"],[[348,348],"mapped",[349]],[[349,349],"valid"],[[350,350],"mapped",[351]],[[351,351],"valid"],[[352,352],"mapped",[353]],[[353,353],"valid"],[[354,354],"mapped",[355]],[[355,355],"valid"],[[356,356],"mapped",[357]],[[357,357],"valid"],[[358,358],"mapped",[359]],[[359,359],"valid"],[[360,360],"mapped",[361]],[[361,361],"valid"],[[362,362],"mapped",[363]],[[363,363],"valid"],[[364,364],"mapped",[365]],[[365,365],"valid"],[[366,366],"mapped",[367]],[[367,367],"valid"],[[368,368],"mapped",[369]],[[369,369],"valid"],[[370,370],"mapped",[371]],[[371,371],"valid"],[[372,372],"mapped",[373]],[[373,373],"valid"],[[374,374],"mapped",[375]],[[375,375],"valid"],[[376,376],"mapped",[255]],[[377,377],"mapped",[378]],[[378,378],"valid"],[[379,379],"mapped",[380]],[[380,380],"valid"],[[381,381],"mapped",[382]],[[382,382],"valid"],[[383,383],"mapped",[115]],[[384,384],"valid"],[[385,385],"mapped",[595]],[[386,386],"mapped",[387]],[[387,387],"valid"],[[388,388],"mapped",[389]],[[389,389],"valid"],[[390,390],"mapped",[596]],[[391,391],"mapped",[392]],[[392,392],"valid"],[[393,393],"mapped",[598]],[[394,394],"mapped",[599]],[[395,395],"mapped",[396]],[[396,397],"valid"],[[398,398],"mapped",[477]],[[399,399],"mapped",[601]],[[400,400],"mapped",[603]],[[401,401],"mapped",[402]],[[402,402],"valid"],[[403,403],"mapped",[608]],[[404,404],"mapped",[611]],[[405,405],"valid"],[[406,406],"mapped",[617]],[[407,407],"mapped",[616]],[[408,408],"mapped",[409]],[[409,411],"valid"],[[412,412],"mapped",[623]],[[413,413],"mapped",[626]],[[414,414],"valid"],[[415,415],"mapped",[629]],[[416,416],"mapped",[417]],[[417,417],"valid"],[[418,418],"mapped",[419]],[[419,419],"valid"],[[420,420],"mapped",[421]],[[421,421],"valid"],[[422,422],"mapped",[640]],[[423,423],"mapped",[424]],[[424,424],"valid"],[[425,425],"mapped",[643]],[[426,427],"valid"],[[428,428],"mapped",[429]],[[429,429],"valid"],[[430,430],"mapped",[648]],[[431,431],"mapped",[432]],[[432,432],"valid"],[[433,433],"mapped",[650]],[[434,434],"mapped",[651]],[[435,435],"mapped",[436]],[[436,436],"valid"],[[437,437],"mapped",[438]],[[438,438],"valid"],[[439,439],"mapped",[658]],[[440,440],"mapped",[441]],[[441,443],"valid"],[[444,444],"mapped",[445]],[[445,451],"valid"],[[452,454],"mapped",[100,382]],[[455,457],"mapped",[108,106]],[[458,460],"mapped",[110,106]],[[461,461],"mapped",[462]],[[462,462],"valid"],[[463,463],"mapped",[464]],[[464,464],"valid"],[[465,465],"mapped",[466]],[[466,466],"valid"],[[467,467],"mapped",[468]],[[468,468],"valid"],[[469,469],"mapped",[470]],[[470,470],"valid"],[[471,471],"mapped",[472]],[[472,472],"valid"],[[473,473],"mapped",[474]],[[474,474],"valid"],[[475,475],"mapped",[476]],[[476,477],"valid"],[[478,478],"mapped",[479]],[[479,479],"valid"],[[480,480],"mapped",[481]],[[481,481],"valid"],[[482,482],"mapped",[483]],[[483,483],"valid"],[[484,484],"mapped",[485]],[[485,485],"valid"],[[486,486],"mapped",[487]],[[487,487],"valid"],[[488,488],"mapped",[489]],[[489,489],"valid"],[[490,490],"mapped",[491]],[[491,491],"valid"],[[492,492],"mapped",[493]],[[493,493],"valid"],[[494,494],"mapped",[495]],[[495,496],"valid"],[[497,499],"mapped",[100,122]],[[500,500],"mapped",[501]],[[501,501],"valid"],[[502,502],"mapped",[405]],[[503,503],"mapped",[447]],[[504,504],"mapped",[505]],[[505,505],"valid"],[[506,506],"mapped",[507]],[[507,507],"valid"],[[508,508],"mapped",[509]],[[509,509],"valid"],[[510,510],"mapped",[511]],[[511,511],"valid"],[[512,512],"mapped",[513]],[[513,513],"valid"],[[514,514],"mapped",[515]],[[515,515],"valid"],[[516,516],"mapped",[517]],[[517,517],"valid"],[[518,518],"mapped",[519]],[[519,519],"valid"],[[520,520],"mapped",[521]],[[521,521],"valid"],[[522,522],"mapped",[523]],[[523,523],"valid"],[[524,524],"mapped",[525]],[[525,525],"valid"],[[526,526],"mapped",[527]],[[527,527],"valid"],[[528,528],"mapped",[529]],[[529,529],"valid"],[[530,530],"mapped",[531]],[[531,531],"valid"],[[532,532],"mapped",[533]],[[533,533],"valid"],[[534,534],"mapped",[535]],[[535,535],"valid"],[[536,536],"mapped",[537]],[[537,537],"valid"],[[538,538],"mapped",[539]],[[539,539],"valid"],[[540,540],"mapped",[541]],[[541,541],"valid"],[[542,542],"mapped",[543]],[[543,543],"valid"],[[544,544],"mapped",[414]],[[545,545],"valid"],[[546,546],"mapped",[547]],[[547,547],"valid"],[[548,548],"mapped",[549]],[[549,549],"valid"],[[550,550],"mapped",[551]],[[551,551],"valid"],[[552,552],"mapped",[553]],[[553,553],"valid"],[[554,554],"mapped",[555]],[[555,555],"valid"],[[556,556],"mapped",[557]],[[557,557],"valid"],[[558,558],"mapped",[559]],[[559,559],"valid"],[[560,560],"mapped",[561]],[[561,561],"valid"],[[562,562],"mapped",[563]],[[563,563],"valid"],[[564,566],"valid"],[[567,569],"valid"],[[570,570],"mapped",[11365]],[[571,571],"mapped",[572]],[[572,572],"valid"],[[573,573],"mapped",[410]],[[574,574],"mapped",[11366]],[[575,576],"valid"],[[577,577],"mapped",[578]],[[578,578],"valid"],[[579,579],"mapped",[384]],[[580,580],"mapped",[649]],[[581,581],"mapped",[652]],[[582,582],"mapped",[583]],[[583,583],"valid"],[[584,584],"mapped",[585]],[[585,585],"valid"],[[586,586],"mapped",[587]],[[587,587],"valid"],[[588,588],"mapped",[589]],[[589,589],"valid"],[[590,590],"mapped",[591]],[[591,591],"valid"],[[592,680],"valid"],[[681,685],"valid"],[[686,687],"valid"],[[688,688],"mapped",[104]],[[689,689],"mapped",[614]],[[690,690],"mapped",[106]],[[691,691],"mapped",[114]],[[692,692],"mapped",[633]],[[693,693],"mapped",[635]],[[694,694],"mapped",[641]],[[695,695],"mapped",[119]],[[696,696],"mapped",[121]],[[697,705],"valid"],[[706,709],"valid",[],"NV8"],[[710,721],"valid"],[[722,727],"valid",[],"NV8"],[[728,728],"disallowed_STD3_mapped",[32,774]],[[729,729],"disallowed_STD3_mapped",[32,775]],[[730,730],"disallowed_STD3_mapped",[32,778]],[[731,731],"disallowed_STD3_mapped",[32,808]],[[732,732],"disallowed_STD3_mapped",[32,771]],[[733,733],"disallowed_STD3_mapped",[32,779]],[[734,734],"valid",[],"NV8"],[[735,735],"valid",[],"NV8"],[[736,736],"mapped",[611]],[[737,737],"mapped",[108]],[[738,738],"mapped",[115]],[[739,739],"mapped",[120]],[[740,740],"mapped",[661]],[[741,745],"valid",[],"NV8"],[[746,747],"valid",[],"NV8"],[[748,748],"valid"],[[749,749],"valid",[],"NV8"],[[750,750],"valid"],[[751,767],"valid",[],"NV8"],[[768,831],"valid"],[[832,832],"mapped",[768]],[[833,833],"mapped",[769]],[[834,834],"valid"],[[835,835],"mapped",[787]],[[836,836],"mapped",[776,769]],[[837,837],"mapped",[953]],[[838,846],"valid"],[[847,847],"ignored"],[[848,855],"valid"],[[856,860],"valid"],[[861,863],"valid"],[[864,865],"valid"],[[866,866],"valid"],[[867,879],"valid"],[[880,880],"mapped",[881]],[[881,881],"valid"],[[882,882],"mapped",[883]],[[883,883],"valid"],[[884,884],"mapped",[697]],[[885,885],"valid"],[[886,886],"mapped",[887]],[[887,887],"valid"],[[888,889],"disallowed"],[[890,890],"disallowed_STD3_mapped",[32,953]],[[891,893],"valid"],[[894,894],"disallowed_STD3_mapped",[59]],[[895,895],"mapped",[1011]],[[896,899],"disallowed"],[[900,900],"disallowed_STD3_mapped",[32,769]],[[901,901],"disallowed_STD3_mapped",[32,776,769]],[[902,902],"mapped",[940]],[[903,903],"mapped",[183]],[[904,904],"mapped",[941]],[[905,905],"mapped",[942]],[[906,906],"mapped",[943]],[[907,907],"disallowed"],[[908,908],"mapped",[972]],[[909,909],"disallowed"],[[910,910],"mapped",[973]],[[911,911],"mapped",[974]],[[912,912],"valid"],[[913,913],"mapped",[945]],[[914,914],"mapped",[946]],[[915,915],"mapped",[947]],[[916,916],"mapped",[948]],[[917,917],"mapped",[949]],[[918,918],"mapped",[950]],[[919,919],"mapped",[951]],[[920,920],"mapped",[952]],[[921,921],"mapped",[953]],[[922,922],"mapped",[954]],[[923,923],"mapped",[955]],[[924,924],"mapped",[956]],[[925,925],"mapped",[957]],[[926,926],"mapped",[958]],[[927,927],"mapped",[959]],[[928,928],"mapped",[960]],[[929,929],"mapped",[961]],[[930,930],"disallowed"],[[931,931],"mapped",[963]],[[932,932],"mapped",[964]],[[933,933],"mapped",[965]],[[934,934],"mapped",[966]],[[935,935],"mapped",[967]],[[936,936],"mapped",[968]],[[937,937],"mapped",[969]],[[938,938],"mapped",[970]],[[939,939],"mapped",[971]],[[940,961],"valid"],[[962,962],"deviation",[963]],[[963,974],"valid"],[[975,975],"mapped",[983]],[[976,976],"mapped",[946]],[[977,977],"mapped",[952]],[[978,978],"mapped",[965]],[[979,979],"mapped",[973]],[[980,980],"mapped",[971]],[[981,981],"mapped",[966]],[[982,982],"mapped",[960]],[[983,983],"valid"],[[984,984],"mapped",[985]],[[985,985],"valid"],[[986,986],"mapped",[987]],[[987,987],"valid"],[[988,988],"mapped",[989]],[[989,989],"valid"],[[990,990],"mapped",[991]],[[991,991],"valid"],[[992,992],"mapped",[993]],[[993,993],"valid"],[[994,994],"mapped",[995]],[[995,995],"valid"],[[996,996],"mapped",[997]],[[997,997],"valid"],[[998,998],"mapped",[999]],[[999,999],"valid"],[[1000,1000],"mapped",[1001]],[[1001,1001],"valid"],[[1002,1002],"mapped",[1003]],[[1003,1003],"valid"],[[1004,1004],"mapped",[1005]],[[1005,1005],"valid"],[[1006,1006],"mapped",[1007]],[[1007,1007],"valid"],[[1008,1008],"mapped",[954]],[[1009,1009],"mapped",[961]],[[1010,1010],"mapped",[963]],[[1011,1011],"valid"],[[1012,1012],"mapped",[952]],[[1013,1013],"mapped",[949]],[[1014,1014],"valid",[],"NV8"],[[1015,1015],"mapped",[1016]],[[1016,1016],"valid"],[[1017,1017],"mapped",[963]],[[1018,1018],"mapped",[1019]],[[1019,1019],"valid"],[[1020,1020],"valid"],[[1021,1021],"mapped",[891]],[[1022,1022],"mapped",[892]],[[1023,1023],"mapped",[893]],[[1024,1024],"mapped",[1104]],[[1025,1025],"mapped",[1105]],[[1026,1026],"mapped",[1106]],[[1027,1027],"mapped",[1107]],[[1028,1028],"mapped",[1108]],[[1029,1029],"mapped",[1109]],[[1030,1030],"mapped",[1110]],[[1031,1031],"mapped",[1111]],[[1032,1032],"mapped",[1112]],[[1033,1033],"mapped",[1113]],[[1034,1034],"mapped",[1114]],[[1035,1035],"mapped",[1115]],[[1036,1036],"mapped",[1116]],[[1037,1037],"mapped",[1117]],[[1038,1038],"mapped",[1118]],[[1039,1039],"mapped",[1119]],[[1040,1040],"mapped",[1072]],[[1041,1041],"mapped",[1073]],[[1042,1042],"mapped",[1074]],[[1043,1043],"mapped",[1075]],[[1044,1044],"mapped",[1076]],[[1045,1045],"mapped",[1077]],[[1046,1046],"mapped",[1078]],[[1047,1047],"mapped",[1079]],[[1048,1048],"mapped",[1080]],[[1049,1049],"mapped",[1081]],[[1050,1050],"mapped",[1082]],[[1051,1051],"mapped",[1083]],[[1052,1052],"mapped",[1084]],[[1053,1053],"mapped",[1085]],[[1054,1054],"mapped",[1086]],[[1055,1055],"mapped",[1087]],[[1056,1056],"mapped",[1088]],[[1057,1057],"mapped",[1089]],[[1058,1058],"mapped",[1090]],[[1059,1059],"mapped",[1091]],[[1060,1060],"mapped",[1092]],[[1061,1061],"mapped",[1093]],[[1062,1062],"mapped",[1094]],[[1063,1063],"mapped",[1095]],[[1064,1064],"mapped",[1096]],[[1065,1065],"mapped",[1097]],[[1066,1066],"mapped",[1098]],[[1067,1067],"mapped",[1099]],[[1068,1068],"mapped",[1100]],[[1069,1069],"mapped",[1101]],[[1070,1070],"mapped",[1102]],[[1071,1071],"mapped",[1103]],[[1072,1103],"valid"],[[1104,1104],"valid"],[[1105,1116],"valid"],[[1117,1117],"valid"],[[1118,1119],"valid"],[[1120,1120],"mapped",[1121]],[[1121,1121],"valid"],[[1122,1122],"mapped",[1123]],[[1123,1123],"valid"],[[1124,1124],"mapped",[1125]],[[1125,1125],"valid"],[[1126,1126],"mapped",[1127]],[[1127,1127],"valid"],[[1128,1128],"mapped",[1129]],[[1129,1129],"valid"],[[1130,1130],"mapped",[1131]],[[1131,1131],"valid"],[[1132,1132],"mapped",[1133]],[[1133,1133],"valid"],[[1134,1134],"mapped",[1135]],[[1135,1135],"valid"],[[1136,1136],"mapped",[1137]],[[1137,1137],"valid"],[[1138,1138],"mapped",[1139]],[[1139,1139],"valid"],[[1140,1140],"mapped",[1141]],[[1141,1141],"valid"],[[1142,1142],"mapped",[1143]],[[1143,1143],"valid"],[[1144,1144],"mapped",[1145]],[[1145,1145],"valid"],[[1146,1146],"mapped",[1147]],[[1147,1147],"valid"],[[1148,1148],"mapped",[1149]],[[1149,1149],"valid"],[[1150,1150],"mapped",[1151]],[[1151,1151],"valid"],[[1152,1152],"mapped",[1153]],[[1153,1153],"valid"],[[1154,1154],"valid",[],"NV8"],[[1155,1158],"valid"],[[1159,1159],"valid"],[[1160,1161],"valid",[],"NV8"],[[1162,1162],"mapped",[1163]],[[1163,1163],"valid"],[[1164,1164],"mapped",[1165]],[[1165,1165],"valid"],[[1166,1166],"mapped",[1167]],[[1167,1167],"valid"],[[1168,1168],"mapped",[1169]],[[1169,1169],"valid"],[[1170,1170],"mapped",[1171]],[[1171,1171],"valid"],[[1172,1172],"mapped",[1173]],[[1173,1173],"valid"],[[1174,1174],"mapped",[1175]],[[1175,1175],"valid"],[[1176,1176],"mapped",[1177]],[[1177,1177],"valid"],[[1178,1178],"mapped",[1179]],[[1179,1179],"valid"],[[1180,1180],"mapped",[1181]],[[1181,1181],"valid"],[[1182,1182],"mapped",[1183]],[[1183,1183],"valid"],[[1184,1184],"mapped",[1185]],[[1185,1185],"valid"],[[1186,1186],"mapped",[1187]],[[1187,1187],"valid"],[[1188,1188],"mapped",[1189]],[[1189,1189],"valid"],[[1190,1190],"mapped",[1191]],[[1191,1191],"valid"],[[1192,1192],"mapped",[1193]],[[1193,1193],"valid"],[[1194,1194],"mapped",[1195]],[[1195,1195],"valid"],[[1196,1196],"mapped",[1197]],[[1197,1197],"valid"],[[1198,1198],"mapped",[1199]],[[1199,1199],"valid"],[[1200,1200],"mapped",[1201]],[[1201,1201],"valid"],[[1202,1202],"mapped",[1203]],[[1203,1203],"valid"],[[1204,1204],"mapped",[1205]],[[1205,1205],"valid"],[[1206,1206],"mapped",[1207]],[[1207,1207],"valid"],[[1208,1208],"mapped",[1209]],[[1209,1209],"valid"],[[1210,1210],"mapped",[1211]],[[1211,1211],"valid"],[[1212,1212],"mapped",[1213]],[[1213,1213],"valid"],[[1214,1214],"mapped",[1215]],[[1215,1215],"valid"],[[1216,1216],"disallowed"],[[1217,1217],"mapped",[1218]],[[1218,1218],"valid"],[[1219,1219],"mapped",[1220]],[[1220,1220],"valid"],[[1221,1221],"mapped",[1222]],[[1222,1222],"valid"],[[1223,1223],"mapped",[1224]],[[1224,1224],"valid"],[[1225,1225],"mapped",[1226]],[[1226,1226],"valid"],[[1227,1227],"mapped",[1228]],[[1228,1228],"valid"],[[1229,1229],"mapped",[1230]],[[1230,1230],"valid"],[[1231,1231],"valid"],[[1232,1232],"mapped",[1233]],[[1233,1233],"valid"],[[1234,1234],"mapped",[1235]],[[1235,1235],"valid"],[[1236,1236],"mapped",[1237]],[[1237,1237],"valid"],[[1238,1238],"mapped",[1239]],[[1239,1239],"valid"],[[1240,1240],"mapped",[1241]],[[1241,1241],"valid"],[[1242,1242],"mapped",[1243]],[[1243,1243],"valid"],[[1244,1244],"mapped",[1245]],[[1245,1245],"valid"],[[1246,1246],"mapped",[1247]],[[1247,1247],"valid"],[[1248,1248],"mapped",[1249]],[[1249,1249],"valid"],[[1250,1250],"mapped",[1251]],[[1251,1251],"valid"],[[1252,1252],"mapped",[1253]],[[1253,1253],"valid"],[[1254,1254],"mapped",[1255]],[[1255,1255],"valid"],[[1256,1256],"mapped",[1257]],[[1257,1257],"valid"],[[1258,1258],"mapped",[1259]],[[1259,1259],"valid"],[[1260,1260],"mapped",[1261]],[[1261,1261],"valid"],[[1262,1262],"mapped",[1263]],[[1263,1263],"valid"],[[1264,1264],"mapped",[1265]],[[1265,1265],"valid"],[[1266,1266],"mapped",[1267]],[[1267,1267],"valid"],[[1268,1268],"mapped",[1269]],[[1269,1269],"valid"],[[1270,1270],"mapped",[1271]],[[1271,1271],"valid"],[[1272,1272],"mapped",[1273]],[[1273,1273],"valid"],[[1274,1274],"mapped",[1275]],[[1275,1275],"valid"],[[1276,1276],"mapped",[1277]],[[1277,1277],"valid"],[[1278,1278],"mapped",[1279]],[[1279,1279],"valid"],[[1280,1280],"mapped",[1281]],[[1281,1281],"valid"],[[1282,1282],"mapped",[1283]],[[1283,1283],"valid"],[[1284,1284],"mapped",[1285]],[[1285,1285],"valid"],[[1286,1286],"mapped",[1287]],[[1287,1287],"valid"],[[1288,1288],"mapped",[1289]],[[1289,1289],"valid"],[[1290,1290],"mapped",[1291]],[[1291,1291],"valid"],[[1292,1292],"mapped",[1293]],[[1293,1293],"valid"],[[1294,1294],"mapped",[1295]],[[1295,1295],"valid"],[[1296,1296],"mapped",[1297]],[[1297,1297],"valid"],[[1298,1298],"mapped",[1299]],[[1299,1299],"valid"],[[1300,1300],"mapped",[1301]],[[1301,1301],"valid"],[[1302,1302],"mapped",[1303]],[[1303,1303],"valid"],[[1304,1304],"mapped",[1305]],[[1305,1305],"valid"],[[1306,1306],"mapped",[1307]],[[1307,1307],"valid"],[[1308,1308],"mapped",[1309]],[[1309,1309],"valid"],[[1310,1310],"mapped",[1311]],[[1311,1311],"valid"],[[1312,1312],"mapped",[1313]],[[1313,1313],"valid"],[[1314,1314],"mapped",[1315]],[[1315,1315],"valid"],[[1316,1316],"mapped",[1317]],[[1317,1317],"valid"],[[1318,1318],"mapped",[1319]],[[1319,1319],"valid"],[[1320,1320],"mapped",[1321]],[[1321,1321],"valid"],[[1322,1322],"mapped",[1323]],[[1323,1323],"valid"],[[1324,1324],"mapped",[1325]],[[1325,1325],"valid"],[[1326,1326],"mapped",[1327]],[[1327,1327],"valid"],[[1328,1328],"disallowed"],[[1329,1329],"mapped",[1377]],[[1330,1330],"mapped",[1378]],[[1331,1331],"mapped",[1379]],[[1332,1332],"mapped",[1380]],[[1333,1333],"mapped",[1381]],[[1334,1334],"mapped",[1382]],[[1335,1335],"mapped",[1383]],[[1336,1336],"mapped",[1384]],[[1337,1337],"mapped",[1385]],[[1338,1338],"mapped",[1386]],[[1339,1339],"mapped",[1387]],[[1340,1340],"mapped",[1388]],[[1341,1341],"mapped",[1389]],[[1342,1342],"mapped",[1390]],[[1343,1343],"mapped",[1391]],[[1344,1344],"mapped",[1392]],[[1345,1345],"mapped",[1393]],[[1346,1346],"mapped",[1394]],[[1347,1347],"mapped",[1395]],[[1348,1348],"mapped",[1396]],[[1349,1349],"mapped",[1397]],[[1350,1350],"mapped",[1398]],[[1351,1351],"mapped",[1399]],[[1352,1352],"mapped",[1400]],[[1353,1353],"mapped",[1401]],[[1354,1354],"mapped",[1402]],[[1355,1355],"mapped",[1403]],[[1356,1356],"mapped",[1404]],[[1357,1357],"mapped",[1405]],[[1358,1358],"mapped",[1406]],[[1359,1359],"mapped",[1407]],[[1360,1360],"mapped",[1408]],[[1361,1361],"mapped",[1409]],[[1362,1362],"mapped",[1410]],[[1363,1363],"mapped",[1411]],[[1364,1364],"mapped",[1412]],[[1365,1365],"mapped",[1413]],[[1366,1366],"mapped",[1414]],[[1367,1368],"disallowed"],[[1369,1369],"valid"],[[1370,1375],"valid",[],"NV8"],[[1376,1376],"disallowed"],[[1377,1414],"valid"],[[1415,1415],"mapped",[1381,1410]],[[1416,1416],"disallowed"],[[1417,1417],"valid",[],"NV8"],[[1418,1418],"valid",[],"NV8"],[[1419,1420],"disallowed"],[[1421,1422],"valid",[],"NV8"],[[1423,1423],"valid",[],"NV8"],[[1424,1424],"disallowed"],[[1425,1441],"valid"],[[1442,1442],"valid"],[[1443,1455],"valid"],[[1456,1465],"valid"],[[1466,1466],"valid"],[[1467,1469],"valid"],[[1470,1470],"valid",[],"NV8"],[[1471,1471],"valid"],[[1472,1472],"valid",[],"NV8"],[[1473,1474],"valid"],[[1475,1475],"valid",[],"NV8"],[[1476,1476],"valid"],[[1477,1477],"valid"],[[1478,1478],"valid",[],"NV8"],[[1479,1479],"valid"],[[1480,1487],"disallowed"],[[1488,1514],"valid"],[[1515,1519],"disallowed"],[[1520,1524],"valid"],[[1525,1535],"disallowed"],[[1536,1539],"disallowed"],[[1540,1540],"disallowed"],[[1541,1541],"disallowed"],[[1542,1546],"valid",[],"NV8"],[[1547,1547],"valid",[],"NV8"],[[1548,1548],"valid",[],"NV8"],[[1549,1551],"valid",[],"NV8"],[[1552,1557],"valid"],[[1558,1562],"valid"],[[1563,1563],"valid",[],"NV8"],[[1564,1564],"disallowed"],[[1565,1565],"disallowed"],[[1566,1566],"valid",[],"NV8"],[[1567,1567],"valid",[],"NV8"],[[1568,1568],"valid"],[[1569,1594],"valid"],[[1595,1599],"valid"],[[1600,1600],"valid",[],"NV8"],[[1601,1618],"valid"],[[1619,1621],"valid"],[[1622,1624],"valid"],[[1625,1630],"valid"],[[1631,1631],"valid"],[[1632,1641],"valid"],[[1642,1645],"valid",[],"NV8"],[[1646,1647],"valid"],[[1648,1652],"valid"],[[1653,1653],"mapped",[1575,1652]],[[1654,1654],"mapped",[1608,1652]],[[1655,1655],"mapped",[1735,1652]],[[1656,1656],"mapped",[1610,1652]],[[1657,1719],"valid"],[[1720,1721],"valid"],[[1722,1726],"valid"],[[1727,1727],"valid"],[[1728,1742],"valid"],[[1743,1743],"valid"],[[1744,1747],"valid"],[[1748,1748],"valid",[],"NV8"],[[1749,1756],"valid"],[[1757,1757],"disallowed"],[[1758,1758],"valid",[],"NV8"],[[1759,1768],"valid"],[[1769,1769],"valid",[],"NV8"],[[1770,1773],"valid"],[[1774,1775],"valid"],[[1776,1785],"valid"],[[1786,1790],"valid"],[[1791,1791],"valid"],[[1792,1805],"valid",[],"NV8"],[[1806,1806],"disallowed"],[[1807,1807],"disallowed"],[[1808,1836],"valid"],[[1837,1839],"valid"],[[1840,1866],"valid"],[[1867,1868],"disallowed"],[[1869,1871],"valid"],[[1872,1901],"valid"],[[1902,1919],"valid"],[[1920,1968],"valid"],[[1969,1969],"valid"],[[1970,1983],"disallowed"],[[1984,2037],"valid"],[[2038,2042],"valid",[],"NV8"],[[2043,2047],"disallowed"],[[2048,2093],"valid"],[[2094,2095],"disallowed"],[[2096,2110],"valid",[],"NV8"],[[2111,2111],"disallowed"],[[2112,2139],"valid"],[[2140,2141],"disallowed"],[[2142,2142],"valid",[],"NV8"],[[2143,2207],"disallowed"],[[2208,2208],"valid"],[[2209,2209],"valid"],[[2210,2220],"valid"],[[2221,2226],"valid"],[[2227,2228],"valid"],[[2229,2274],"disallowed"],[[2275,2275],"valid"],[[2276,2302],"valid"],[[2303,2303],"valid"],[[2304,2304],"valid"],[[2305,2307],"valid"],[[2308,2308],"valid"],[[2309,2361],"valid"],[[2362,2363],"valid"],[[2364,2381],"valid"],[[2382,2382],"valid"],[[2383,2383],"valid"],[[2384,2388],"valid"],[[2389,2389],"valid"],[[2390,2391],"valid"],[[2392,2392],"mapped",[2325,2364]],[[2393,2393],"mapped",[2326,2364]],[[2394,2394],"mapped",[2327,2364]],[[2395,2395],"mapped",[2332,2364]],[[2396,2396],"mapped",[2337,2364]],[[2397,2397],"mapped",[2338,2364]],[[2398,2398],"mapped",[2347,2364]],[[2399,2399],"mapped",[2351,2364]],[[2400,2403],"valid"],[[2404,2405],"valid",[],"NV8"],[[2406,2415],"valid"],[[2416,2416],"valid",[],"NV8"],[[2417,2418],"valid"],[[2419,2423],"valid"],[[2424,2424],"valid"],[[2425,2426],"valid"],[[2427,2428],"valid"],[[2429,2429],"valid"],[[2430,2431],"valid"],[[2432,2432],"valid"],[[2433,2435],"valid"],[[2436,2436],"disallowed"],[[2437,2444],"valid"],[[2445,2446],"disallowed"],[[2447,2448],"valid"],[[2449,2450],"disallowed"],[[2451,2472],"valid"],[[2473,2473],"disallowed"],[[2474,2480],"valid"],[[2481,2481],"disallowed"],[[2482,2482],"valid"],[[2483,2485],"disallowed"],[[2486,2489],"valid"],[[2490,2491],"disallowed"],[[2492,2492],"valid"],[[2493,2493],"valid"],[[2494,2500],"valid"],[[2501,2502],"disallowed"],[[2503,2504],"valid"],[[2505,2506],"disallowed"],[[2507,2509],"valid"],[[2510,2510],"valid"],[[2511,2518],"disallowed"],[[2519,2519],"valid"],[[2520,2523],"disallowed"],[[2524,2524],"mapped",[2465,2492]],[[2525,2525],"mapped",[2466,2492]],[[2526,2526],"disallowed"],[[2527,2527],"mapped",[2479,2492]],[[2528,2531],"valid"],[[2532,2533],"disallowed"],[[2534,2545],"valid"],[[2546,2554],"valid",[],"NV8"],[[2555,2555],"valid",[],"NV8"],[[2556,2560],"disallowed"],[[2561,2561],"valid"],[[2562,2562],"valid"],[[2563,2563],"valid"],[[2564,2564],"disallowed"],[[2565,2570],"valid"],[[2571,2574],"disallowed"],[[2575,2576],"valid"],[[2577,2578],"disallowed"],[[2579,2600],"valid"],[[2601,2601],"disallowed"],[[2602,2608],"valid"],[[2609,2609],"disallowed"],[[2610,2610],"valid"],[[2611,2611],"mapped",[2610,2620]],[[2612,2612],"disallowed"],[[2613,2613],"valid"],[[2614,2614],"mapped",[2616,2620]],[[2615,2615],"disallowed"],[[2616,2617],"valid"],[[2618,2619],"disallowed"],[[2620,2620],"valid"],[[2621,2621],"disallowed"],[[2622,2626],"valid"],[[2627,2630],"disallowed"],[[2631,2632],"valid"],[[2633,2634],"disallowed"],[[2635,2637],"valid"],[[2638,2640],"disallowed"],[[2641,2641],"valid"],[[2642,2648],"disallowed"],[[2649,2649],"mapped",[2582,2620]],[[2650,2650],"mapped",[2583,2620]],[[2651,2651],"mapped",[2588,2620]],[[2652,2652],"valid"],[[2653,2653],"disallowed"],[[2654,2654],"mapped",[2603,2620]],[[2655,2661],"disallowed"],[[2662,2676],"valid"],[[2677,2677],"valid"],[[2678,2688],"disallowed"],[[2689,2691],"valid"],[[2692,2692],"disallowed"],[[2693,2699],"valid"],[[2700,2700],"valid"],[[2701,2701],"valid"],[[2702,2702],"disallowed"],[[2703,2705],"valid"],[[2706,2706],"disallowed"],[[2707,2728],"valid"],[[2729,2729],"disallowed"],[[2730,2736],"valid"],[[2737,2737],"disallowed"],[[2738,2739],"valid"],[[2740,2740],"disallowed"],[[2741,2745],"valid"],[[2746,2747],"disallowed"],[[2748,2757],"valid"],[[2758,2758],"disallowed"],[[2759,2761],"valid"],[[2762,2762],"disallowed"],[[2763,2765],"valid"],[[2766,2767],"disallowed"],[[2768,2768],"valid"],[[2769,2783],"disallowed"],[[2784,2784],"valid"],[[2785,2787],"valid"],[[2788,2789],"disallowed"],[[2790,2799],"valid"],[[2800,2800],"valid",[],"NV8"],[[2801,2801],"valid",[],"NV8"],[[2802,2808],"disallowed"],[[2809,2809],"valid"],[[2810,2816],"disallowed"],[[2817,2819],"valid"],[[2820,2820],"disallowed"],[[2821,2828],"valid"],[[2829,2830],"disallowed"],[[2831,2832],"valid"],[[2833,2834],"disallowed"],[[2835,2856],"valid"],[[2857,2857],"disallowed"],[[2858,2864],"valid"],[[2865,2865],"disallowed"],[[2866,2867],"valid"],[[2868,2868],"disallowed"],[[2869,2869],"valid"],[[2870,2873],"valid"],[[2874,2875],"disallowed"],[[2876,2883],"valid"],[[2884,2884],"valid"],[[2885,2886],"disallowed"],[[2887,2888],"valid"],[[2889,2890],"disallowed"],[[2891,2893],"valid"],[[2894,2901],"disallowed"],[[2902,2903],"valid"],[[2904,2907],"disallowed"],[[2908,2908],"mapped",[2849,2876]],[[2909,2909],"mapped",[2850,2876]],[[2910,2910],"disallowed"],[[2911,2913],"valid"],[[2914,2915],"valid"],[[2916,2917],"disallowed"],[[2918,2927],"valid"],[[2928,2928],"valid",[],"NV8"],[[2929,2929],"valid"],[[2930,2935],"valid",[],"NV8"],[[2936,2945],"disallowed"],[[2946,2947],"valid"],[[2948,2948],"disallowed"],[[2949,2954],"valid"],[[2955,2957],"disallowed"],[[2958,2960],"valid"],[[2961,2961],"disallowed"],[[2962,2965],"valid"],[[2966,2968],"disallowed"],[[2969,2970],"valid"],[[2971,2971],"disallowed"],[[2972,2972],"valid"],[[2973,2973],"disallowed"],[[2974,2975],"valid"],[[2976,2978],"disallowed"],[[2979,2980],"valid"],[[2981,2983],"disallowed"],[[2984,2986],"valid"],[[2987,2989],"disallowed"],[[2990,2997],"valid"],[[2998,2998],"valid"],[[2999,3001],"valid"],[[3002,3005],"disallowed"],[[3006,3010],"valid"],[[3011,3013],"disallowed"],[[3014,3016],"valid"],[[3017,3017],"disallowed"],[[3018,3021],"valid"],[[3022,3023],"disallowed"],[[3024,3024],"valid"],[[3025,3030],"disallowed"],[[3031,3031],"valid"],[[3032,3045],"disallowed"],[[3046,3046],"valid"],[[3047,3055],"valid"],[[3056,3058],"valid",[],"NV8"],[[3059,3066],"valid",[],"NV8"],[[3067,3071],"disallowed"],[[3072,3072],"valid"],[[3073,3075],"valid"],[[3076,3076],"disallowed"],[[3077,3084],"valid"],[[3085,3085],"disallowed"],[[3086,3088],"valid"],[[3089,3089],"disallowed"],[[3090,3112],"valid"],[[3113,3113],"disallowed"],[[3114,3123],"valid"],[[3124,3124],"valid"],[[3125,3129],"valid"],[[3130,3132],"disallowed"],[[3133,3133],"valid"],[[3134,3140],"valid"],[[3141,3141],"disallowed"],[[3142,3144],"valid"],[[3145,3145],"disallowed"],[[3146,3149],"valid"],[[3150,3156],"disallowed"],[[3157,3158],"valid"],[[3159,3159],"disallowed"],[[3160,3161],"valid"],[[3162,3162],"valid"],[[3163,3167],"disallowed"],[[3168,3169],"valid"],[[3170,3171],"valid"],[[3172,3173],"disallowed"],[[3174,3183],"valid"],[[3184,3191],"disallowed"],[[3192,3199],"valid",[],"NV8"],[[3200,3200],"disallowed"],[[3201,3201],"valid"],[[3202,3203],"valid"],[[3204,3204],"disallowed"],[[3205,3212],"valid"],[[3213,3213],"disallowed"],[[3214,3216],"valid"],[[3217,3217],"disallowed"],[[3218,3240],"valid"],[[3241,3241],"disallowed"],[[3242,3251],"valid"],[[3252,3252],"disallowed"],[[3253,3257],"valid"],[[3258,3259],"disallowed"],[[3260,3261],"valid"],[[3262,3268],"valid"],[[3269,3269],"disallowed"],[[3270,3272],"valid"],[[3273,3273],"disallowed"],[[3274,3277],"valid"],[[3278,3284],"disallowed"],[[3285,3286],"valid"],[[3287,3293],"disallowed"],[[3294,3294],"valid"],[[3295,3295],"disallowed"],[[3296,3297],"valid"],[[3298,3299],"valid"],[[3300,3301],"disallowed"],[[3302,3311],"valid"],[[3312,3312],"disallowed"],[[3313,3314],"valid"],[[3315,3328],"disallowed"],[[3329,3329],"valid"],[[3330,3331],"valid"],[[3332,3332],"disallowed"],[[3333,3340],"valid"],[[3341,3341],"disallowed"],[[3342,3344],"valid"],[[3345,3345],"disallowed"],[[3346,3368],"valid"],[[3369,3369],"valid"],[[3370,3385],"valid"],[[3386,3386],"valid"],[[3387,3388],"disallowed"],[[3389,3389],"valid"],[[3390,3395],"valid"],[[3396,3396],"valid"],[[3397,3397],"disallowed"],[[3398,3400],"valid"],[[3401,3401],"disallowed"],[[3402,3405],"valid"],[[3406,3406],"valid"],[[3407,3414],"disallowed"],[[3415,3415],"valid"],[[3416,3422],"disallowed"],[[3423,3423],"valid"],[[3424,3425],"valid"],[[3426,3427],"valid"],[[3428,3429],"disallowed"],[[3430,3439],"valid"],[[3440,3445],"valid",[],"NV8"],[[3446,3448],"disallowed"],[[3449,3449],"valid",[],"NV8"],[[3450,3455],"valid"],[[3456,3457],"disallowed"],[[3458,3459],"valid"],[[3460,3460],"disallowed"],[[3461,3478],"valid"],[[3479,3481],"disallowed"],[[3482,3505],"valid"],[[3506,3506],"disallowed"],[[3507,3515],"valid"],[[3516,3516],"disallowed"],[[3517,3517],"valid"],[[3518,3519],"disallowed"],[[3520,3526],"valid"],[[3527,3529],"disallowed"],[[3530,3530],"valid"],[[3531,3534],"disallowed"],[[3535,3540],"valid"],[[3541,3541],"disallowed"],[[3542,3542],"valid"],[[3543,3543],"disallowed"],[[3544,3551],"valid"],[[3552,3557],"disallowed"],[[3558,3567],"valid"],[[3568,3569],"disallowed"],[[3570,3571],"valid"],[[3572,3572],"valid",[],"NV8"],[[3573,3584],"disallowed"],[[3585,3634],"valid"],[[3635,3635],"mapped",[3661,3634]],[[3636,3642],"valid"],[[3643,3646],"disallowed"],[[3647,3647],"valid",[],"NV8"],[[3648,3662],"valid"],[[3663,3663],"valid",[],"NV8"],[[3664,3673],"valid"],[[3674,3675],"valid",[],"NV8"],[[3676,3712],"disallowed"],[[3713,3714],"valid"],[[3715,3715],"disallowed"],[[3716,3716],"valid"],[[3717,3718],"disallowed"],[[3719,3720],"valid"],[[3721,3721],"disallowed"],[[3722,3722],"valid"],[[3723,3724],"disallowed"],[[3725,3725],"valid"],[[3726,3731],"disallowed"],[[3732,3735],"valid"],[[3736,3736],"disallowed"],[[3737,3743],"valid"],[[3744,3744],"disallowed"],[[3745,3747],"valid"],[[3748,3748],"disallowed"],[[3749,3749],"valid"],[[3750,3750],"disallowed"],[[3751,3751],"valid"],[[3752,3753],"disallowed"],[[3754,3755],"valid"],[[3756,3756],"disallowed"],[[3757,3762],"valid"],[[3763,3763],"mapped",[3789,3762]],[[3764,3769],"valid"],[[3770,3770],"disallowed"],[[3771,3773],"valid"],[[3774,3775],"disallowed"],[[3776,3780],"valid"],[[3781,3781],"disallowed"],[[3782,3782],"valid"],[[3783,3783],"disallowed"],[[3784,3789],"valid"],[[3790,3791],"disallowed"],[[3792,3801],"valid"],[[3802,3803],"disallowed"],[[3804,3804],"mapped",[3755,3737]],[[3805,3805],"mapped",[3755,3745]],[[3806,3807],"valid"],[[3808,3839],"disallowed"],[[3840,3840],"valid"],[[3841,3850],"valid",[],"NV8"],[[3851,3851],"valid"],[[3852,3852],"mapped",[3851]],[[3853,3863],"valid",[],"NV8"],[[3864,3865],"valid"],[[3866,3871],"valid",[],"NV8"],[[3872,3881],"valid"],[[3882,3892],"valid",[],"NV8"],[[3893,3893],"valid"],[[3894,3894],"valid",[],"NV8"],[[3895,3895],"valid"],[[3896,3896],"valid",[],"NV8"],[[3897,3897],"valid"],[[3898,3901],"valid",[],"NV8"],[[3902,3906],"valid"],[[3907,3907],"mapped",[3906,4023]],[[3908,3911],"valid"],[[3912,3912],"disallowed"],[[3913,3916],"valid"],[[3917,3917],"mapped",[3916,4023]],[[3918,3921],"valid"],[[3922,3922],"mapped",[3921,4023]],[[3923,3926],"valid"],[[3927,3927],"mapped",[3926,4023]],[[3928,3931],"valid"],[[3932,3932],"mapped",[3931,4023]],[[3933,3944],"valid"],[[3945,3945],"mapped",[3904,4021]],[[3946,3946],"valid"],[[3947,3948],"valid"],[[3949,3952],"disallowed"],[[3953,3954],"valid"],[[3955,3955],"mapped",[3953,3954]],[[3956,3956],"valid"],[[3957,3957],"mapped",[3953,3956]],[[3958,3958],"mapped",[4018,3968]],[[3959,3959],"mapped",[4018,3953,3968]],[[3960,3960],"mapped",[4019,3968]],[[3961,3961],"mapped",[4019,3953,3968]],[[3962,3968],"valid"],[[3969,3969],"mapped",[3953,3968]],[[3970,3972],"valid"],[[3973,3973],"valid",[],"NV8"],[[3974,3979],"valid"],[[3980,3983],"valid"],[[3984,3986],"valid"],[[3987,3987],"mapped",[3986,4023]],[[3988,3989],"valid"],[[3990,3990],"valid"],[[3991,3991],"valid"],[[3992,3992],"disallowed"],[[3993,3996],"valid"],[[3997,3997],"mapped",[3996,4023]],[[3998,4001],"valid"],[[4002,4002],"mapped",[4001,4023]],[[4003,4006],"valid"],[[4007,4007],"mapped",[4006,4023]],[[4008,4011],"valid"],[[4012,4012],"mapped",[4011,4023]],[[4013,4013],"valid"],[[4014,4016],"valid"],[[4017,4023],"valid"],[[4024,4024],"valid"],[[4025,4025],"mapped",[3984,4021]],[[4026,4028],"valid"],[[4029,4029],"disallowed"],[[4030,4037],"valid",[],"NV8"],[[4038,4038],"valid"],[[4039,4044],"valid",[],"NV8"],[[4045,4045],"disallowed"],[[4046,4046],"valid",[],"NV8"],[[4047,4047],"valid",[],"NV8"],[[4048,4049],"valid",[],"NV8"],[[4050,4052],"valid",[],"NV8"],[[4053,4056],"valid",[],"NV8"],[[4057,4058],"valid",[],"NV8"],[[4059,4095],"disallowed"],[[4096,4129],"valid"],[[4130,4130],"valid"],[[4131,4135],"valid"],[[4136,4136],"valid"],[[4137,4138],"valid"],[[4139,4139],"valid"],[[4140,4146],"valid"],[[4147,4149],"valid"],[[4150,4153],"valid"],[[4154,4159],"valid"],[[4160,4169],"valid"],[[4170,4175],"valid",[],"NV8"],[[4176,4185],"valid"],[[4186,4249],"valid"],[[4250,4253],"valid"],[[4254,4255],"valid",[],"NV8"],[[4256,4293],"disallowed"],[[4294,4294],"disallowed"],[[4295,4295],"mapped",[11559]],[[4296,4300],"disallowed"],[[4301,4301],"mapped",[11565]],[[4302,4303],"disallowed"],[[4304,4342],"valid"],[[4343,4344],"valid"],[[4345,4346],"valid"],[[4347,4347],"valid",[],"NV8"],[[4348,4348],"mapped",[4316]],[[4349,4351],"valid"],[[4352,4441],"valid",[],"NV8"],[[4442,4446],"valid",[],"NV8"],[[4447,4448],"disallowed"],[[4449,4514],"valid",[],"NV8"],[[4515,4519],"valid",[],"NV8"],[[4520,4601],"valid",[],"NV8"],[[4602,4607],"valid",[],"NV8"],[[4608,4614],"valid"],[[4615,4615],"valid"],[[4616,4678],"valid"],[[4679,4679],"valid"],[[4680,4680],"valid"],[[4681,4681],"disallowed"],[[4682,4685],"valid"],[[4686,4687],"disallowed"],[[4688,4694],"valid"],[[4695,4695],"disallowed"],[[4696,4696],"valid"],[[4697,4697],"disallowed"],[[4698,4701],"valid"],[[4702,4703],"disallowed"],[[4704,4742],"valid"],[[4743,4743],"valid"],[[4744,4744],"valid"],[[4745,4745],"disallowed"],[[4746,4749],"valid"],[[4750,4751],"disallowed"],[[4752,4782],"valid"],[[4783,4783],"valid"],[[4784,4784],"valid"],[[4785,4785],"disallowed"],[[4786,4789],"valid"],[[4790,4791],"disallowed"],[[4792,4798],"valid"],[[4799,4799],"disallowed"],[[4800,4800],"valid"],[[4801,4801],"disallowed"],[[4802,4805],"valid"],[[4806,4807],"disallowed"],[[4808,4814],"valid"],[[4815,4815],"valid"],[[4816,4822],"valid"],[[4823,4823],"disallowed"],[[4824,4846],"valid"],[[4847,4847],"valid"],[[4848,4878],"valid"],[[4879,4879],"valid"],[[4880,4880],"valid"],[[4881,4881],"disallowed"],[[4882,4885],"valid"],[[4886,4887],"disallowed"],[[4888,4894],"valid"],[[4895,4895],"valid"],[[4896,4934],"valid"],[[4935,4935],"valid"],[[4936,4954],"valid"],[[4955,4956],"disallowed"],[[4957,4958],"valid"],[[4959,4959],"valid"],[[4960,4960],"valid",[],"NV8"],[[4961,4988],"valid",[],"NV8"],[[4989,4991],"disallowed"],[[4992,5007],"valid"],[[5008,5017],"valid",[],"NV8"],[[5018,5023],"disallowed"],[[5024,5108],"valid"],[[5109,5109],"valid"],[[5110,5111],"disallowed"],[[5112,5112],"mapped",[5104]],[[5113,5113],"mapped",[5105]],[[5114,5114],"mapped",[5106]],[[5115,5115],"mapped",[5107]],[[5116,5116],"mapped",[5108]],[[5117,5117],"mapped",[5109]],[[5118,5119],"disallowed"],[[5120,5120],"valid",[],"NV8"],[[5121,5740],"valid"],[[5741,5742],"valid",[],"NV8"],[[5743,5750],"valid"],[[5751,5759],"valid"],[[5760,5760],"disallowed"],[[5761,5786],"valid"],[[5787,5788],"valid",[],"NV8"],[[5789,5791],"disallowed"],[[5792,5866],"valid"],[[5867,5872],"valid",[],"NV8"],[[5873,5880],"valid"],[[5881,5887],"disallowed"],[[5888,5900],"valid"],[[5901,5901],"disallowed"],[[5902,5908],"valid"],[[5909,5919],"disallowed"],[[5920,5940],"valid"],[[5941,5942],"valid",[],"NV8"],[[5943,5951],"disallowed"],[[5952,5971],"valid"],[[5972,5983],"disallowed"],[[5984,5996],"valid"],[[5997,5997],"disallowed"],[[5998,6000],"valid"],[[6001,6001],"disallowed"],[[6002,6003],"valid"],[[6004,6015],"disallowed"],[[6016,6067],"valid"],[[6068,6069],"disallowed"],[[6070,6099],"valid"],[[6100,6102],"valid",[],"NV8"],[[6103,6103],"valid"],[[6104,6107],"valid",[],"NV8"],[[6108,6108],"valid"],[[6109,6109],"valid"],[[6110,6111],"disallowed"],[[6112,6121],"valid"],[[6122,6127],"disallowed"],[[6128,6137],"valid",[],"NV8"],[[6138,6143],"disallowed"],[[6144,6149],"valid",[],"NV8"],[[6150,6150],"disallowed"],[[6151,6154],"valid",[],"NV8"],[[6155,6157],"ignored"],[[6158,6158],"disallowed"],[[6159,6159],"disallowed"],[[6160,6169],"valid"],[[6170,6175],"disallowed"],[[6176,6263],"valid"],[[6264,6271],"disallowed"],[[6272,6313],"valid"],[[6314,6314],"valid"],[[6315,6319],"disallowed"],[[6320,6389],"valid"],[[6390,6399],"disallowed"],[[6400,6428],"valid"],[[6429,6430],"valid"],[[6431,6431],"disallowed"],[[6432,6443],"valid"],[[6444,6447],"disallowed"],[[6448,6459],"valid"],[[6460,6463],"disallowed"],[[6464,6464],"valid",[],"NV8"],[[6465,6467],"disallowed"],[[6468,6469],"valid",[],"NV8"],[[6470,6509],"valid"],[[6510,6511],"disallowed"],[[6512,6516],"valid"],[[6517,6527],"disallowed"],[[6528,6569],"valid"],[[6570,6571],"valid"],[[6572,6575],"disallowed"],[[6576,6601],"valid"],[[6602,6607],"disallowed"],[[6608,6617],"valid"],[[6618,6618],"valid",[],"XV8"],[[6619,6621],"disallowed"],[[6622,6623],"valid",[],"NV8"],[[6624,6655],"valid",[],"NV8"],[[6656,6683],"valid"],[[6684,6685],"disallowed"],[[6686,6687],"valid",[],"NV8"],[[6688,6750],"valid"],[[6751,6751],"disallowed"],[[6752,6780],"valid"],[[6781,6782],"disallowed"],[[6783,6793],"valid"],[[6794,6799],"disallowed"],[[6800,6809],"valid"],[[6810,6815],"disallowed"],[[6816,6822],"valid",[],"NV8"],[[6823,6823],"valid"],[[6824,6829],"valid",[],"NV8"],[[6830,6831],"disallowed"],[[6832,6845],"valid"],[[6846,6846],"valid",[],"NV8"],[[6847,6911],"disallowed"],[[6912,6987],"valid"],[[6988,6991],"disallowed"],[[6992,7001],"valid"],[[7002,7018],"valid",[],"NV8"],[[7019,7027],"valid"],[[7028,7036],"valid",[],"NV8"],[[7037,7039],"disallowed"],[[7040,7082],"valid"],[[7083,7085],"valid"],[[7086,7097],"valid"],[[7098,7103],"valid"],[[7104,7155],"valid"],[[7156,7163],"disallowed"],[[7164,7167],"valid",[],"NV8"],[[7168,7223],"valid"],[[7224,7226],"disallowed"],[[7227,7231],"valid",[],"NV8"],[[7232,7241],"valid"],[[7242,7244],"disallowed"],[[7245,7293],"valid"],[[7294,7295],"valid",[],"NV8"],[[7296,7359],"disallowed"],[[7360,7367],"valid",[],"NV8"],[[7368,7375],"disallowed"],[[7376,7378],"valid"],[[7379,7379],"valid",[],"NV8"],[[7380,7410],"valid"],[[7411,7414],"valid"],[[7415,7415],"disallowed"],[[7416,7417],"valid"],[[7418,7423],"disallowed"],[[7424,7467],"valid"],[[7468,7468],"mapped",[97]],[[7469,7469],"mapped",[230]],[[7470,7470],"mapped",[98]],[[7471,7471],"valid"],[[7472,7472],"mapped",[100]],[[7473,7473],"mapped",[101]],[[7474,7474],"mapped",[477]],[[7475,7475],"mapped",[103]],[[7476,7476],"mapped",[104]],[[7477,7477],"mapped",[105]],[[7478,7478],"mapped",[106]],[[7479,7479],"mapped",[107]],[[7480,7480],"mapped",[108]],[[7481,7481],"mapped",[109]],[[7482,7482],"mapped",[110]],[[7483,7483],"valid"],[[7484,7484],"mapped",[111]],[[7485,7485],"mapped",[547]],[[7486,7486],"mapped",[112]],[[7487,7487],"mapped",[114]],[[7488,7488],"mapped",[116]],[[7489,7489],"mapped",[117]],[[7490,7490],"mapped",[119]],[[7491,7491],"mapped",[97]],[[7492,7492],"mapped",[592]],[[7493,7493],"mapped",[593]],[[7494,7494],"mapped",[7426]],[[7495,7495],"mapped",[98]],[[7496,7496],"mapped",[100]],[[7497,7497],"mapped",[101]],[[7498,7498],"mapped",[601]],[[7499,7499],"mapped",[603]],[[7500,7500],"mapped",[604]],[[7501,7501],"mapped",[103]],[[7502,7502],"valid"],[[7503,7503],"mapped",[107]],[[7504,7504],"mapped",[109]],[[7505,7505],"mapped",[331]],[[7506,7506],"mapped",[111]],[[7507,7507],"mapped",[596]],[[7508,7508],"mapped",[7446]],[[7509,7509],"mapped",[7447]],[[7510,7510],"mapped",[112]],[[7511,7511],"mapped",[116]],[[7512,7512],"mapped",[117]],[[7513,7513],"mapped",[7453]],[[7514,7514],"mapped",[623]],[[7515,7515],"mapped",[118]],[[7516,7516],"mapped",[7461]],[[7517,7517],"mapped",[946]],[[7518,7518],"mapped",[947]],[[7519,7519],"mapped",[948]],[[7520,7520],"mapped",[966]],[[7521,7521],"mapped",[967]],[[7522,7522],"mapped",[105]],[[7523,7523],"mapped",[114]],[[7524,7524],"mapped",[117]],[[7525,7525],"mapped",[118]],[[7526,7526],"mapped",[946]],[[7527,7527],"mapped",[947]],[[7528,7528],"mapped",[961]],[[7529,7529],"mapped",[966]],[[7530,7530],"mapped",[967]],[[7531,7531],"valid"],[[7532,7543],"valid"],[[7544,7544],"mapped",[1085]],[[7545,7578],"valid"],[[7579,7579],"mapped",[594]],[[7580,7580],"mapped",[99]],[[7581,7581],"mapped",[597]],[[7582,7582],"mapped",[240]],[[7583,7583],"mapped",[604]],[[7584,7584],"mapped",[102]],[[7585,7585],"mapped",[607]],[[7586,7586],"mapped",[609]],[[7587,7587],"mapped",[613]],[[7588,7588],"mapped",[616]],[[7589,7589],"mapped",[617]],[[7590,7590],"mapped",[618]],[[7591,7591],"mapped",[7547]],[[7592,7592],"mapped",[669]],[[7593,7593],"mapped",[621]],[[7594,7594],"mapped",[7557]],[[7595,7595],"mapped",[671]],[[7596,7596],"mapped",[625]],[[7597,7597],"mapped",[624]],[[7598,7598],"mapped",[626]],[[7599,7599],"mapped",[627]],[[7600,7600],"mapped",[628]],[[7601,7601],"mapped",[629]],[[7602,7602],"mapped",[632]],[[7603,7603],"mapped",[642]],[[7604,7604],"mapped",[643]],[[7605,7605],"mapped",[427]],[[7606,7606],"mapped",[649]],[[7607,7607],"mapped",[650]],[[7608,7608],"mapped",[7452]],[[7609,7609],"mapped",[651]],[[7610,7610],"mapped",[652]],[[7611,7611],"mapped",[122]],[[7612,7612],"mapped",[656]],[[7613,7613],"mapped",[657]],[[7614,7614],"mapped",[658]],[[7615,7615],"mapped",[952]],[[7616,7619],"valid"],[[7620,7626],"valid"],[[7627,7654],"valid"],[[7655,7669],"valid"],[[7670,7675],"disallowed"],[[7676,7676],"valid"],[[7677,7677],"valid"],[[7678,7679],"valid"],[[7680,7680],"mapped",[7681]],[[7681,7681],"valid"],[[7682,7682],"mapped",[7683]],[[7683,7683],"valid"],[[7684,7684],"mapped",[7685]],[[7685,7685],"valid"],[[7686,7686],"mapped",[7687]],[[7687,7687],"valid"],[[7688,7688],"mapped",[7689]],[[7689,7689],"valid"],[[7690,7690],"mapped",[7691]],[[7691,7691],"valid"],[[7692,7692],"mapped",[7693]],[[7693,7693],"valid"],[[7694,7694],"mapped",[7695]],[[7695,7695],"valid"],[[7696,7696],"mapped",[7697]],[[7697,7697],"valid"],[[7698,7698],"mapped",[7699]],[[7699,7699],"valid"],[[7700,7700],"mapped",[7701]],[[7701,7701],"valid"],[[7702,7702],"mapped",[7703]],[[7703,7703],"valid"],[[7704,7704],"mapped",[7705]],[[7705,7705],"valid"],[[7706,7706],"mapped",[7707]],[[7707,7707],"valid"],[[7708,7708],"mapped",[7709]],[[7709,7709],"valid"],[[7710,7710],"mapped",[7711]],[[7711,7711],"valid"],[[7712,7712],"mapped",[7713]],[[7713,7713],"valid"],[[7714,7714],"mapped",[7715]],[[7715,7715],"valid"],[[7716,7716],"mapped",[7717]],[[7717,7717],"valid"],[[7718,7718],"mapped",[7719]],[[7719,7719],"valid"],[[7720,7720],"mapped",[7721]],[[7721,7721],"valid"],[[7722,7722],"mapped",[7723]],[[7723,7723],"valid"],[[7724,7724],"mapped",[7725]],[[7725,7725],"valid"],[[7726,7726],"mapped",[7727]],[[7727,7727],"valid"],[[7728,7728],"mapped",[7729]],[[7729,7729],"valid"],[[7730,7730],"mapped",[7731]],[[7731,7731],"valid"],[[7732,7732],"mapped",[7733]],[[7733,7733],"valid"],[[7734,7734],"mapped",[7735]],[[7735,7735],"valid"],[[7736,7736],"mapped",[7737]],[[7737,7737],"valid"],[[7738,7738],"mapped",[7739]],[[7739,7739],"valid"],[[7740,7740],"mapped",[7741]],[[7741,7741],"valid"],[[7742,7742],"mapped",[7743]],[[7743,7743],"valid"],[[7744,7744],"mapped",[7745]],[[7745,7745],"valid"],[[7746,7746],"mapped",[7747]],[[7747,7747],"valid"],[[7748,7748],"mapped",[7749]],[[7749,7749],"valid"],[[7750,7750],"mapped",[7751]],[[7751,7751],"valid"],[[7752,7752],"mapped",[7753]],[[7753,7753],"valid"],[[7754,7754],"mapped",[7755]],[[7755,7755],"valid"],[[7756,7756],"mapped",[7757]],[[7757,7757],"valid"],[[7758,7758],"mapped",[7759]],[[7759,7759],"valid"],[[7760,7760],"mapped",[7761]],[[7761,7761],"valid"],[[7762,7762],"mapped",[7763]],[[7763,7763],"valid"],[[7764,7764],"mapped",[7765]],[[7765,7765],"valid"],[[7766,7766],"mapped",[7767]],[[7767,7767],"valid"],[[7768,7768],"mapped",[7769]],[[7769,7769],"valid"],[[7770,7770],"mapped",[7771]],[[7771,7771],"valid"],[[7772,7772],"mapped",[7773]],[[7773,7773],"valid"],[[7774,7774],"mapped",[7775]],[[7775,7775],"valid"],[[7776,7776],"mapped",[7777]],[[7777,7777],"valid"],[[7778,7778],"mapped",[7779]],[[7779,7779],"valid"],[[7780,7780],"mapped",[7781]],[[7781,7781],"valid"],[[7782,7782],"mapped",[7783]],[[7783,7783],"valid"],[[7784,7784],"mapped",[7785]],[[7785,7785],"valid"],[[7786,7786],"mapped",[7787]],[[7787,7787],"valid"],[[7788,7788],"mapped",[7789]],[[7789,7789],"valid"],[[7790,7790],"mapped",[7791]],[[7791,7791],"valid"],[[7792,7792],"mapped",[7793]],[[7793,7793],"valid"],[[7794,7794],"mapped",[7795]],[[7795,7795],"valid"],[[7796,7796],"mapped",[7797]],[[7797,7797],"valid"],[[7798,7798],"mapped",[7799]],[[7799,7799],"valid"],[[7800,7800],"mapped",[7801]],[[7801,7801],"valid"],[[7802,7802],"mapped",[7803]],[[7803,7803],"valid"],[[7804,7804],"mapped",[7805]],[[7805,7805],"valid"],[[7806,7806],"mapped",[7807]],[[7807,7807],"valid"],[[7808,7808],"mapped",[7809]],[[7809,7809],"valid"],[[7810,7810],"mapped",[7811]],[[7811,7811],"valid"],[[7812,7812],"mapped",[7813]],[[7813,7813],"valid"],[[7814,7814],"mapped",[7815]],[[7815,7815],"valid"],[[7816,7816],"mapped",[7817]],[[7817,7817],"valid"],[[7818,7818],"mapped",[7819]],[[7819,7819],"valid"],[[7820,7820],"mapped",[7821]],[[7821,7821],"valid"],[[7822,7822],"mapped",[7823]],[[7823,7823],"valid"],[[7824,7824],"mapped",[7825]],[[7825,7825],"valid"],[[7826,7826],"mapped",[7827]],[[7827,7827],"valid"],[[7828,7828],"mapped",[7829]],[[7829,7833],"valid"],[[7834,7834],"mapped",[97,702]],[[7835,7835],"mapped",[7777]],[[7836,7837],"valid"],[[7838,7838],"mapped",[115,115]],[[7839,7839],"valid"],[[7840,7840],"mapped",[7841]],[[7841,7841],"valid"],[[7842,7842],"mapped",[7843]],[[7843,7843],"valid"],[[7844,7844],"mapped",[7845]],[[7845,7845],"valid"],[[7846,7846],"mapped",[7847]],[[7847,7847],"valid"],[[7848,7848],"mapped",[7849]],[[7849,7849],"valid"],[[7850,7850],"mapped",[7851]],[[7851,7851],"valid"],[[7852,7852],"mapped",[7853]],[[7853,7853],"valid"],[[7854,7854],"mapped",[7855]],[[7855,7855],"valid"],[[7856,7856],"mapped",[7857]],[[7857,7857],"valid"],[[7858,7858],"mapped",[7859]],[[7859,7859],"valid"],[[7860,7860],"mapped",[7861]],[[7861,7861],"valid"],[[7862,7862],"mapped",[7863]],[[7863,7863],"valid"],[[7864,7864],"mapped",[7865]],[[7865,7865],"valid"],[[7866,7866],"mapped",[7867]],[[7867,7867],"valid"],[[7868,7868],"mapped",[7869]],[[7869,7869],"valid"],[[7870,7870],"mapped",[7871]],[[7871,7871],"valid"],[[7872,7872],"mapped",[7873]],[[7873,7873],"valid"],[[7874,7874],"mapped",[7875]],[[7875,7875],"valid"],[[7876,7876],"mapped",[7877]],[[7877,7877],"valid"],[[7878,7878],"mapped",[7879]],[[7879,7879],"valid"],[[7880,7880],"mapped",[7881]],[[7881,7881],"valid"],[[7882,7882],"mapped",[7883]],[[7883,7883],"valid"],[[7884,7884],"mapped",[7885]],[[7885,7885],"valid"],[[7886,7886],"mapped",[7887]],[[7887,7887],"valid"],[[7888,7888],"mapped",[7889]],[[7889,7889],"valid"],[[7890,7890],"mapped",[7891]],[[7891,7891],"valid"],[[7892,7892],"mapped",[7893]],[[7893,7893],"valid"],[[7894,7894],"mapped",[7895]],[[7895,7895],"valid"],[[7896,7896],"mapped",[7897]],[[7897,7897],"valid"],[[7898,7898],"mapped",[7899]],[[7899,7899],"valid"],[[7900,7900],"mapped",[7901]],[[7901,7901],"valid"],[[7902,7902],"mapped",[7903]],[[7903,7903],"valid"],[[7904,7904],"mapped",[7905]],[[7905,7905],"valid"],[[7906,7906],"mapped",[7907]],[[7907,7907],"valid"],[[7908,7908],"mapped",[7909]],[[7909,7909],"valid"],[[7910,7910],"mapped",[7911]],[[7911,7911],"valid"],[[7912,7912],"mapped",[7913]],[[7913,7913],"valid"],[[7914,7914],"mapped",[7915]],[[7915,7915],"valid"],[[7916,7916],"mapped",[7917]],[[7917,7917],"valid"],[[7918,7918],"mapped",[7919]],[[7919,7919],"valid"],[[7920,7920],"mapped",[7921]],[[7921,7921],"valid"],[[7922,7922],"mapped",[7923]],[[7923,7923],"valid"],[[7924,7924],"mapped",[7925]],[[7925,7925],"valid"],[[7926,7926],"mapped",[7927]],[[7927,7927],"valid"],[[7928,7928],"mapped",[7929]],[[7929,7929],"valid"],[[7930,7930],"mapped",[7931]],[[7931,7931],"valid"],[[7932,7932],"mapped",[7933]],[[7933,7933],"valid"],[[7934,7934],"mapped",[7935]],[[7935,7935],"valid"],[[7936,7943],"valid"],[[7944,7944],"mapped",[7936]],[[7945,7945],"mapped",[7937]],[[7946,7946],"mapped",[7938]],[[7947,7947],"mapped",[7939]],[[7948,7948],"mapped",[7940]],[[7949,7949],"mapped",[7941]],[[7950,7950],"mapped",[7942]],[[7951,7951],"mapped",[7943]],[[7952,7957],"valid"],[[7958,7959],"disallowed"],[[7960,7960],"mapped",[7952]],[[7961,7961],"mapped",[7953]],[[7962,7962],"mapped",[7954]],[[7963,7963],"mapped",[7955]],[[7964,7964],"mapped",[7956]],[[7965,7965],"mapped",[7957]],[[7966,7967],"disallowed"],[[7968,7975],"valid"],[[7976,7976],"mapped",[7968]],[[7977,7977],"mapped",[7969]],[[7978,7978],"mapped",[7970]],[[7979,7979],"mapped",[7971]],[[7980,7980],"mapped",[7972]],[[7981,7981],"mapped",[7973]],[[7982,7982],"mapped",[7974]],[[7983,7983],"mapped",[7975]],[[7984,7991],"valid"],[[7992,7992],"mapped",[7984]],[[7993,7993],"mapped",[7985]],[[7994,7994],"mapped",[7986]],[[7995,7995],"mapped",[7987]],[[7996,7996],"mapped",[7988]],[[7997,7997],"mapped",[7989]],[[7998,7998],"mapped",[7990]],[[7999,7999],"mapped",[7991]],[[8000,8005],"valid"],[[8006,8007],"disallowed"],[[8008,8008],"mapped",[8000]],[[8009,8009],"mapped",[8001]],[[8010,8010],"mapped",[8002]],[[8011,8011],"mapped",[8003]],[[8012,8012],"mapped",[8004]],[[8013,8013],"mapped",[8005]],[[8014,8015],"disallowed"],[[8016,8023],"valid"],[[8024,8024],"disallowed"],[[8025,8025],"mapped",[8017]],[[8026,8026],"disallowed"],[[8027,8027],"mapped",[8019]],[[8028,8028],"disallowed"],[[8029,8029],"mapped",[8021]],[[8030,8030],"disallowed"],[[8031,8031],"mapped",[8023]],[[8032,8039],"valid"],[[8040,8040],"mapped",[8032]],[[8041,8041],"mapped",[8033]],[[8042,8042],"mapped",[8034]],[[8043,8043],"mapped",[8035]],[[8044,8044],"mapped",[8036]],[[8045,8045],"mapped",[8037]],[[8046,8046],"mapped",[8038]],[[8047,8047],"mapped",[8039]],[[8048,8048],"valid"],[[8049,8049],"mapped",[940]],[[8050,8050],"valid"],[[8051,8051],"mapped",[941]],[[8052,8052],"valid"],[[8053,8053],"mapped",[942]],[[8054,8054],"valid"],[[8055,8055],"mapped",[943]],[[8056,8056],"valid"],[[8057,8057],"mapped",[972]],[[8058,8058],"valid"],[[8059,8059],"mapped",[973]],[[8060,8060],"valid"],[[8061,8061],"mapped",[974]],[[8062,8063],"disallowed"],[[8064,8064],"mapped",[7936,953]],[[8065,8065],"mapped",[7937,953]],[[8066,8066],"mapped",[7938,953]],[[8067,8067],"mapped",[7939,953]],[[8068,8068],"mapped",[7940,953]],[[8069,8069],"mapped",[7941,953]],[[8070,8070],"mapped",[7942,953]],[[8071,8071],"mapped",[7943,953]],[[8072,8072],"mapped",[7936,953]],[[8073,8073],"mapped",[7937,953]],[[8074,8074],"mapped",[7938,953]],[[8075,8075],"mapped",[7939,953]],[[8076,8076],"mapped",[7940,953]],[[8077,8077],"mapped",[7941,953]],[[8078,8078],"mapped",[7942,953]],[[8079,8079],"mapped",[7943,953]],[[8080,8080],"mapped",[7968,953]],[[8081,8081],"mapped",[7969,953]],[[8082,8082],"mapped",[7970,953]],[[8083,8083],"mapped",[7971,953]],[[8084,8084],"mapped",[7972,953]],[[8085,8085],"mapped",[7973,953]],[[8086,8086],"mapped",[7974,953]],[[8087,8087],"mapped",[7975,953]],[[8088,8088],"mapped",[7968,953]],[[8089,8089],"mapped",[7969,953]],[[8090,8090],"mapped",[7970,953]],[[8091,8091],"mapped",[7971,953]],[[8092,8092],"mapped",[7972,953]],[[8093,8093],"mapped",[7973,953]],[[8094,8094],"mapped",[7974,953]],[[8095,8095],"mapped",[7975,953]],[[8096,8096],"mapped",[8032,953]],[[8097,8097],"mapped",[8033,953]],[[8098,8098],"mapped",[8034,953]],[[8099,8099],"mapped",[8035,953]],[[8100,8100],"mapped",[8036,953]],[[8101,8101],"mapped",[8037,953]],[[8102,8102],"mapped",[8038,953]],[[8103,8103],"mapped",[8039,953]],[[8104,8104],"mapped",[8032,953]],[[8105,8105],"mapped",[8033,953]],[[8106,8106],"mapped",[8034,953]],[[8107,8107],"mapped",[8035,953]],[[8108,8108],"mapped",[8036,953]],[[8109,8109],"mapped",[8037,953]],[[8110,8110],"mapped",[8038,953]],[[8111,8111],"mapped",[8039,953]],[[8112,8113],"valid"],[[8114,8114],"mapped",[8048,953]],[[8115,8115],"mapped",[945,953]],[[8116,8116],"mapped",[940,953]],[[8117,8117],"disallowed"],[[8118,8118],"valid"],[[8119,8119],"mapped",[8118,953]],[[8120,8120],"mapped",[8112]],[[8121,8121],"mapped",[8113]],[[8122,8122],"mapped",[8048]],[[8123,8123],"mapped",[940]],[[8124,8124],"mapped",[945,953]],[[8125,8125],"disallowed_STD3_mapped",[32,787]],[[8126,8126],"mapped",[953]],[[8127,8127],"disallowed_STD3_mapped",[32,787]],[[8128,8128],"disallowed_STD3_mapped",[32,834]],[[8129,8129],"disallowed_STD3_mapped",[32,776,834]],[[8130,8130],"mapped",[8052,953]],[[8131,8131],"mapped",[951,953]],[[8132,8132],"mapped",[942,953]],[[8133,8133],"disallowed"],[[8134,8134],"valid"],[[8135,8135],"mapped",[8134,953]],[[8136,8136],"mapped",[8050]],[[8137,8137],"mapped",[941]],[[8138,8138],"mapped",[8052]],[[8139,8139],"mapped",[942]],[[8140,8140],"mapped",[951,953]],[[8141,8141],"disallowed_STD3_mapped",[32,787,768]],[[8142,8142],"disallowed_STD3_mapped",[32,787,769]],[[8143,8143],"disallowed_STD3_mapped",[32,787,834]],[[8144,8146],"valid"],[[8147,8147],"mapped",[912]],[[8148,8149],"disallowed"],[[8150,8151],"valid"],[[8152,8152],"mapped",[8144]],[[8153,8153],"mapped",[8145]],[[8154,8154],"mapped",[8054]],[[8155,8155],"mapped",[943]],[[8156,8156],"disallowed"],[[8157,8157],"disallowed_STD3_mapped",[32,788,768]],[[8158,8158],"disallowed_STD3_mapped",[32,788,769]],[[8159,8159],"disallowed_STD3_mapped",[32,788,834]],[[8160,8162],"valid"],[[8163,8163],"mapped",[944]],[[8164,8167],"valid"],[[8168,8168],"mapped",[8160]],[[8169,8169],"mapped",[8161]],[[8170,8170],"mapped",[8058]],[[8171,8171],"mapped",[973]],[[8172,8172],"mapped",[8165]],[[8173,8173],"disallowed_STD3_mapped",[32,776,768]],[[8174,8174],"disallowed_STD3_mapped",[32,776,769]],[[8175,8175],"disallowed_STD3_mapped",[96]],[[8176,8177],"disallowed"],[[8178,8178],"mapped",[8060,953]],[[8179,8179],"mapped",[969,953]],[[8180,8180],"mapped",[974,953]],[[8181,8181],"disallowed"],[[8182,8182],"valid"],[[8183,8183],"mapped",[8182,953]],[[8184,8184],"mapped",[8056]],[[8185,8185],"mapped",[972]],[[8186,8186],"mapped",[8060]],[[8187,8187],"mapped",[974]],[[8188,8188],"mapped",[969,953]],[[8189,8189],"disallowed_STD3_mapped",[32,769]],[[8190,8190],"disallowed_STD3_mapped",[32,788]],[[8191,8191],"disallowed"],[[8192,8202],"disallowed_STD3_mapped",[32]],[[8203,8203],"ignored"],[[8204,8205],"deviation",[]],[[8206,8207],"disallowed"],[[8208,8208],"valid",[],"NV8"],[[8209,8209],"mapped",[8208]],[[8210,8214],"valid",[],"NV8"],[[8215,8215],"disallowed_STD3_mapped",[32,819]],[[8216,8227],"valid",[],"NV8"],[[8228,8230],"disallowed"],[[8231,8231],"valid",[],"NV8"],[[8232,8238],"disallowed"],[[8239,8239],"disallowed_STD3_mapped",[32]],[[8240,8242],"valid",[],"NV8"],[[8243,8243],"mapped",[8242,8242]],[[8244,8244],"mapped",[8242,8242,8242]],[[8245,8245],"valid",[],"NV8"],[[8246,8246],"mapped",[8245,8245]],[[8247,8247],"mapped",[8245,8245,8245]],[[8248,8251],"valid",[],"NV8"],[[8252,8252],"disallowed_STD3_mapped",[33,33]],[[8253,8253],"valid",[],"NV8"],[[8254,8254],"disallowed_STD3_mapped",[32,773]],[[8255,8262],"valid",[],"NV8"],[[8263,8263],"disallowed_STD3_mapped",[63,63]],[[8264,8264],"disallowed_STD3_mapped",[63,33]],[[8265,8265],"disallowed_STD3_mapped",[33,63]],[[8266,8269],"valid",[],"NV8"],[[8270,8274],"valid",[],"NV8"],[[8275,8276],"valid",[],"NV8"],[[8277,8278],"valid",[],"NV8"],[[8279,8279],"mapped",[8242,8242,8242,8242]],[[8280,8286],"valid",[],"NV8"],[[8287,8287],"disallowed_STD3_mapped",[32]],[[8288,8288],"ignored"],[[8289,8291],"disallowed"],[[8292,8292],"ignored"],[[8293,8293],"disallowed"],[[8294,8297],"disallowed"],[[8298,8303],"disallowed"],[[8304,8304],"mapped",[48]],[[8305,8305],"mapped",[105]],[[8306,8307],"disallowed"],[[8308,8308],"mapped",[52]],[[8309,8309],"mapped",[53]],[[8310,8310],"mapped",[54]],[[8311,8311],"mapped",[55]],[[8312,8312],"mapped",[56]],[[8313,8313],"mapped",[57]],[[8314,8314],"disallowed_STD3_mapped",[43]],[[8315,8315],"mapped",[8722]],[[8316,8316],"disallowed_STD3_mapped",[61]],[[8317,8317],"disallowed_STD3_mapped",[40]],[[8318,8318],"disallowed_STD3_mapped",[41]],[[8319,8319],"mapped",[110]],[[8320,8320],"mapped",[48]],[[8321,8321],"mapped",[49]],[[8322,8322],"mapped",[50]],[[8323,8323],"mapped",[51]],[[8324,8324],"mapped",[52]],[[8325,8325],"mapped",[53]],[[8326,8326],"mapped",[54]],[[8327,8327],"mapped",[55]],[[8328,8328],"mapped",[56]],[[8329,8329],"mapped",[57]],[[8330,8330],"disallowed_STD3_mapped",[43]],[[8331,8331],"mapped",[8722]],[[8332,8332],"disallowed_STD3_mapped",[61]],[[8333,8333],"disallowed_STD3_mapped",[40]],[[8334,8334],"disallowed_STD3_mapped",[41]],[[8335,8335],"disallowed"],[[8336,8336],"mapped",[97]],[[8337,8337],"mapped",[101]],[[8338,8338],"mapped",[111]],[[8339,8339],"mapped",[120]],[[8340,8340],"mapped",[601]],[[8341,8341],"mapped",[104]],[[8342,8342],"mapped",[107]],[[8343,8343],"mapped",[108]],[[8344,8344],"mapped",[109]],[[8345,8345],"mapped",[110]],[[8346,8346],"mapped",[112]],[[8347,8347],"mapped",[115]],[[8348,8348],"mapped",[116]],[[8349,8351],"disallowed"],[[8352,8359],"valid",[],"NV8"],[[8360,8360],"mapped",[114,115]],[[8361,8362],"valid",[],"NV8"],[[8363,8363],"valid",[],"NV8"],[[8364,8364],"valid",[],"NV8"],[[8365,8367],"valid",[],"NV8"],[[8368,8369],"valid",[],"NV8"],[[8370,8373],"valid",[],"NV8"],[[8374,8376],"valid",[],"NV8"],[[8377,8377],"valid",[],"NV8"],[[8378,8378],"valid",[],"NV8"],[[8379,8381],"valid",[],"NV8"],[[8382,8382],"valid",[],"NV8"],[[8383,8399],"disallowed"],[[8400,8417],"valid",[],"NV8"],[[8418,8419],"valid",[],"NV8"],[[8420,8426],"valid",[],"NV8"],[[8427,8427],"valid",[],"NV8"],[[8428,8431],"valid",[],"NV8"],[[8432,8432],"valid",[],"NV8"],[[8433,8447],"disallowed"],[[8448,8448],"disallowed_STD3_mapped",[97,47,99]],[[8449,8449],"disallowed_STD3_mapped",[97,47,115]],[[8450,8450],"mapped",[99]],[[8451,8451],"mapped",[176,99]],[[8452,8452],"valid",[],"NV8"],[[8453,8453],"disallowed_STD3_mapped",[99,47,111]],[[8454,8454],"disallowed_STD3_mapped",[99,47,117]],[[8455,8455],"mapped",[603]],[[8456,8456],"valid",[],"NV8"],[[8457,8457],"mapped",[176,102]],[[8458,8458],"mapped",[103]],[[8459,8462],"mapped",[104]],[[8463,8463],"mapped",[295]],[[8464,8465],"mapped",[105]],[[8466,8467],"mapped",[108]],[[8468,8468],"valid",[],"NV8"],[[8469,8469],"mapped",[110]],[[8470,8470],"mapped",[110,111]],[[8471,8472],"valid",[],"NV8"],[[8473,8473],"mapped",[112]],[[8474,8474],"mapped",[113]],[[8475,8477],"mapped",[114]],[[8478,8479],"valid",[],"NV8"],[[8480,8480],"mapped",[115,109]],[[8481,8481],"mapped",[116,101,108]],[[8482,8482],"mapped",[116,109]],[[8483,8483],"valid",[],"NV8"],[[8484,8484],"mapped",[122]],[[8485,8485],"valid",[],"NV8"],[[8486,8486],"mapped",[969]],[[8487,8487],"valid",[],"NV8"],[[8488,8488],"mapped",[122]],[[8489,8489],"valid",[],"NV8"],[[8490,8490],"mapped",[107]],[[8491,8491],"mapped",[229]],[[8492,8492],"mapped",[98]],[[8493,8493],"mapped",[99]],[[8494,8494],"valid",[],"NV8"],[[8495,8496],"mapped",[101]],[[8497,8497],"mapped",[102]],[[8498,8498],"disallowed"],[[8499,8499],"mapped",[109]],[[8500,8500],"mapped",[111]],[[8501,8501],"mapped",[1488]],[[8502,8502],"mapped",[1489]],[[8503,8503],"mapped",[1490]],[[8504,8504],"mapped",[1491]],[[8505,8505],"mapped",[105]],[[8506,8506],"valid",[],"NV8"],[[8507,8507],"mapped",[102,97,120]],[[8508,8508],"mapped",[960]],[[8509,8510],"mapped",[947]],[[8511,8511],"mapped",[960]],[[8512,8512],"mapped",[8721]],[[8513,8516],"valid",[],"NV8"],[[8517,8518],"mapped",[100]],[[8519,8519],"mapped",[101]],[[8520,8520],"mapped",[105]],[[8521,8521],"mapped",[106]],[[8522,8523],"valid",[],"NV8"],[[8524,8524],"valid",[],"NV8"],[[8525,8525],"valid",[],"NV8"],[[8526,8526],"valid"],[[8527,8527],"valid",[],"NV8"],[[8528,8528],"mapped",[49,8260,55]],[[8529,8529],"mapped",[49,8260,57]],[[8530,8530],"mapped",[49,8260,49,48]],[[8531,8531],"mapped",[49,8260,51]],[[8532,8532],"mapped",[50,8260,51]],[[8533,8533],"mapped",[49,8260,53]],[[8534,8534],"mapped",[50,8260,53]],[[8535,8535],"mapped",[51,8260,53]],[[8536,8536],"mapped",[52,8260,53]],[[8537,8537],"mapped",[49,8260,54]],[[8538,8538],"mapped",[53,8260,54]],[[8539,8539],"mapped",[49,8260,56]],[[8540,8540],"mapped",[51,8260,56]],[[8541,8541],"mapped",[53,8260,56]],[[8542,8542],"mapped",[55,8260,56]],[[8543,8543],"mapped",[49,8260]],[[8544,8544],"mapped",[105]],[[8545,8545],"mapped",[105,105]],[[8546,8546],"mapped",[105,105,105]],[[8547,8547],"mapped",[105,118]],[[8548,8548],"mapped",[118]],[[8549,8549],"mapped",[118,105]],[[8550,8550],"mapped",[118,105,105]],[[8551,8551],"mapped",[118,105,105,105]],[[8552,8552],"mapped",[105,120]],[[8553,8553],"mapped",[120]],[[8554,8554],"mapped",[120,105]],[[8555,8555],"mapped",[120,105,105]],[[8556,8556],"mapped",[108]],[[8557,8557],"mapped",[99]],[[8558,8558],"mapped",[100]],[[8559,8559],"mapped",[109]],[[8560,8560],"mapped",[105]],[[8561,8561],"mapped",[105,105]],[[8562,8562],"mapped",[105,105,105]],[[8563,8563],"mapped",[105,118]],[[8564,8564],"mapped",[118]],[[8565,8565],"mapped",[118,105]],[[8566,8566],"mapped",[118,105,105]],[[8567,8567],"mapped",[118,105,105,105]],[[8568,8568],"mapped",[105,120]],[[8569,8569],"mapped",[120]],[[8570,8570],"mapped",[120,105]],[[8571,8571],"mapped",[120,105,105]],[[8572,8572],"mapped",[108]],[[8573,8573],"mapped",[99]],[[8574,8574],"mapped",[100]],[[8575,8575],"mapped",[109]],[[8576,8578],"valid",[],"NV8"],[[8579,8579],"disallowed"],[[8580,8580],"valid"],[[8581,8584],"valid",[],"NV8"],[[8585,8585],"mapped",[48,8260,51]],[[8586,8587],"valid",[],"NV8"],[[8588,8591],"disallowed"],[[8592,8682],"valid",[],"NV8"],[[8683,8691],"valid",[],"NV8"],[[8692,8703],"valid",[],"NV8"],[[8704,8747],"valid",[],"NV8"],[[8748,8748],"mapped",[8747,8747]],[[8749,8749],"mapped",[8747,8747,8747]],[[8750,8750],"valid",[],"NV8"],[[8751,8751],"mapped",[8750,8750]],[[8752,8752],"mapped",[8750,8750,8750]],[[8753,8799],"valid",[],"NV8"],[[8800,8800],"disallowed_STD3_valid"],[[8801,8813],"valid",[],"NV8"],[[8814,8815],"disallowed_STD3_valid"],[[8816,8945],"valid",[],"NV8"],[[8946,8959],"valid",[],"NV8"],[[8960,8960],"valid",[],"NV8"],[[8961,8961],"valid",[],"NV8"],[[8962,9000],"valid",[],"NV8"],[[9001,9001],"mapped",[12296]],[[9002,9002],"mapped",[12297]],[[9003,9082],"valid",[],"NV8"],[[9083,9083],"valid",[],"NV8"],[[9084,9084],"valid",[],"NV8"],[[9085,9114],"valid",[],"NV8"],[[9115,9166],"valid",[],"NV8"],[[9167,9168],"valid",[],"NV8"],[[9169,9179],"valid",[],"NV8"],[[9180,9191],"valid",[],"NV8"],[[9192,9192],"valid",[],"NV8"],[[9193,9203],"valid",[],"NV8"],[[9204,9210],"valid",[],"NV8"],[[9211,9215],"disallowed"],[[9216,9252],"valid",[],"NV8"],[[9253,9254],"valid",[],"NV8"],[[9255,9279],"disallowed"],[[9280,9290],"valid",[],"NV8"],[[9291,9311],"disallowed"],[[9312,9312],"mapped",[49]],[[9313,9313],"mapped",[50]],[[9314,9314],"mapped",[51]],[[9315,9315],"mapped",[52]],[[9316,9316],"mapped",[53]],[[9317,9317],"mapped",[54]],[[9318,9318],"mapped",[55]],[[9319,9319],"mapped",[56]],[[9320,9320],"mapped",[57]],[[9321,9321],"mapped",[49,48]],[[9322,9322],"mapped",[49,49]],[[9323,9323],"mapped",[49,50]],[[9324,9324],"mapped",[49,51]],[[9325,9325],"mapped",[49,52]],[[9326,9326],"mapped",[49,53]],[[9327,9327],"mapped",[49,54]],[[9328,9328],"mapped",[49,55]],[[9329,9329],"mapped",[49,56]],[[9330,9330],"mapped",[49,57]],[[9331,9331],"mapped",[50,48]],[[9332,9332],"disallowed_STD3_mapped",[40,49,41]],[[9333,9333],"disallowed_STD3_mapped",[40,50,41]],[[9334,9334],"disallowed_STD3_mapped",[40,51,41]],[[9335,9335],"disallowed_STD3_mapped",[40,52,41]],[[9336,9336],"disallowed_STD3_mapped",[40,53,41]],[[9337,9337],"disallowed_STD3_mapped",[40,54,41]],[[9338,9338],"disallowed_STD3_mapped",[40,55,41]],[[9339,9339],"disallowed_STD3_mapped",[40,56,41]],[[9340,9340],"disallowed_STD3_mapped",[40,57,41]],[[9341,9341],"disallowed_STD3_mapped",[40,49,48,41]],[[9342,9342],"disallowed_STD3_mapped",[40,49,49,41]],[[9343,9343],"disallowed_STD3_mapped",[40,49,50,41]],[[9344,9344],"disallowed_STD3_mapped",[40,49,51,41]],[[9345,9345],"disallowed_STD3_mapped",[40,49,52,41]],[[9346,9346],"disallowed_STD3_mapped",[40,49,53,41]],[[9347,9347],"disallowed_STD3_mapped",[40,49,54,41]],[[9348,9348],"disallowed_STD3_mapped",[40,49,55,41]],[[9349,9349],"disallowed_STD3_mapped",[40,49,56,41]],[[9350,9350],"disallowed_STD3_mapped",[40,49,57,41]],[[9351,9351],"disallowed_STD3_mapped",[40,50,48,41]],[[9352,9371],"disallowed"],[[9372,9372],"disallowed_STD3_mapped",[40,97,41]],[[9373,9373],"disallowed_STD3_mapped",[40,98,41]],[[9374,9374],"disallowed_STD3_mapped",[40,99,41]],[[9375,9375],"disallowed_STD3_mapped",[40,100,41]],[[9376,9376],"disallowed_STD3_mapped",[40,101,41]],[[9377,9377],"disallowed_STD3_mapped",[40,102,41]],[[9378,9378],"disallowed_STD3_mapped",[40,103,41]],[[9379,9379],"disallowed_STD3_mapped",[40,104,41]],[[9380,9380],"disallowed_STD3_mapped",[40,105,41]],[[9381,9381],"disallowed_STD3_mapped",[40,106,41]],[[9382,9382],"disallowed_STD3_mapped",[40,107,41]],[[9383,9383],"disallowed_STD3_mapped",[40,108,41]],[[9384,9384],"disallowed_STD3_mapped",[40,109,41]],[[9385,9385],"disallowed_STD3_mapped",[40,110,41]],[[9386,9386],"disallowed_STD3_mapped",[40,111,41]],[[9387,9387],"disallowed_STD3_mapped",[40,112,41]],[[9388,9388],"disallowed_STD3_mapped",[40,113,41]],[[9389,9389],"disallowed_STD3_mapped",[40,114,41]],[[9390,9390],"disallowed_STD3_mapped",[40,115,41]],[[9391,9391],"disallowed_STD3_mapped",[40,116,41]],[[9392,9392],"disallowed_STD3_mapped",[40,117,41]],[[9393,9393],"disallowed_STD3_mapped",[40,118,41]],[[9394,9394],"disallowed_STD3_mapped",[40,119,41]],[[9395,9395],"disallowed_STD3_mapped",[40,120,41]],[[9396,9396],"disallowed_STD3_mapped",[40,121,41]],[[9397,9397],"disallowed_STD3_mapped",[40,122,41]],[[9398,9398],"mapped",[97]],[[9399,9399],"mapped",[98]],[[9400,9400],"mapped",[99]],[[9401,9401],"mapped",[100]],[[9402,9402],"mapped",[101]],[[9403,9403],"mapped",[102]],[[9404,9404],"mapped",[103]],[[9405,9405],"mapped",[104]],[[9406,9406],"mapped",[105]],[[9407,9407],"mapped",[106]],[[9408,9408],"mapped",[107]],[[9409,9409],"mapped",[108]],[[9410,9410],"mapped",[109]],[[9411,9411],"mapped",[110]],[[9412,9412],"mapped",[111]],[[9413,9413],"mapped",[112]],[[9414,9414],"mapped",[113]],[[9415,9415],"mapped",[114]],[[9416,9416],"mapped",[115]],[[9417,9417],"mapped",[116]],[[9418,9418],"mapped",[117]],[[9419,9419],"mapped",[118]],[[9420,9420],"mapped",[119]],[[9421,9421],"mapped",[120]],[[9422,9422],"mapped",[121]],[[9423,9423],"mapped",[122]],[[9424,9424],"mapped",[97]],[[9425,9425],"mapped",[98]],[[9426,9426],"mapped",[99]],[[9427,9427],"mapped",[100]],[[9428,9428],"mapped",[101]],[[9429,9429],"mapped",[102]],[[9430,9430],"mapped",[103]],[[9431,9431],"mapped",[104]],[[9432,9432],"mapped",[105]],[[9433,9433],"mapped",[106]],[[9434,9434],"mapped",[107]],[[9435,9435],"mapped",[108]],[[9436,9436],"mapped",[109]],[[9437,9437],"mapped",[110]],[[9438,9438],"mapped",[111]],[[9439,9439],"mapped",[112]],[[9440,9440],"mapped",[113]],[[9441,9441],"mapped",[114]],[[9442,9442],"mapped",[115]],[[9443,9443],"mapped",[116]],[[9444,9444],"mapped",[117]],[[9445,9445],"mapped",[118]],[[9446,9446],"mapped",[119]],[[9447,9447],"mapped",[120]],[[9448,9448],"mapped",[121]],[[9449,9449],"mapped",[122]],[[9450,9450],"mapped",[48]],[[9451,9470],"valid",[],"NV8"],[[9471,9471],"valid",[],"NV8"],[[9472,9621],"valid",[],"NV8"],[[9622,9631],"valid",[],"NV8"],[[9632,9711],"valid",[],"NV8"],[[9712,9719],"valid",[],"NV8"],[[9720,9727],"valid",[],"NV8"],[[9728,9747],"valid",[],"NV8"],[[9748,9749],"valid",[],"NV8"],[[9750,9751],"valid",[],"NV8"],[[9752,9752],"valid",[],"NV8"],[[9753,9753],"valid",[],"NV8"],[[9754,9839],"valid",[],"NV8"],[[9840,9841],"valid",[],"NV8"],[[9842,9853],"valid",[],"NV8"],[[9854,9855],"valid",[],"NV8"],[[9856,9865],"valid",[],"NV8"],[[9866,9873],"valid",[],"NV8"],[[9874,9884],"valid",[],"NV8"],[[9885,9885],"valid",[],"NV8"],[[9886,9887],"valid",[],"NV8"],[[9888,9889],"valid",[],"NV8"],[[9890,9905],"valid",[],"NV8"],[[9906,9906],"valid",[],"NV8"],[[9907,9916],"valid",[],"NV8"],[[9917,9919],"valid",[],"NV8"],[[9920,9923],"valid",[],"NV8"],[[9924,9933],"valid",[],"NV8"],[[9934,9934],"valid",[],"NV8"],[[9935,9953],"valid",[],"NV8"],[[9954,9954],"valid",[],"NV8"],[[9955,9955],"valid",[],"NV8"],[[9956,9959],"valid",[],"NV8"],[[9960,9983],"valid",[],"NV8"],[[9984,9984],"valid",[],"NV8"],[[9985,9988],"valid",[],"NV8"],[[9989,9989],"valid",[],"NV8"],[[9990,9993],"valid",[],"NV8"],[[9994,9995],"valid",[],"NV8"],[[9996,10023],"valid",[],"NV8"],[[10024,10024],"valid",[],"NV8"],[[10025,10059],"valid",[],"NV8"],[[10060,10060],"valid",[],"NV8"],[[10061,10061],"valid",[],"NV8"],[[10062,10062],"valid",[],"NV8"],[[10063,10066],"valid",[],"NV8"],[[10067,10069],"valid",[],"NV8"],[[10070,10070],"valid",[],"NV8"],[[10071,10071],"valid",[],"NV8"],[[10072,10078],"valid",[],"NV8"],[[10079,10080],"valid",[],"NV8"],[[10081,10087],"valid",[],"NV8"],[[10088,10101],"valid",[],"NV8"],[[10102,10132],"valid",[],"NV8"],[[10133,10135],"valid",[],"NV8"],[[10136,10159],"valid",[],"NV8"],[[10160,10160],"valid",[],"NV8"],[[10161,10174],"valid",[],"NV8"],[[10175,10175],"valid",[],"NV8"],[[10176,10182],"valid",[],"NV8"],[[10183,10186],"valid",[],"NV8"],[[10187,10187],"valid",[],"NV8"],[[10188,10188],"valid",[],"NV8"],[[10189,10189],"valid",[],"NV8"],[[10190,10191],"valid",[],"NV8"],[[10192,10219],"valid",[],"NV8"],[[10220,10223],"valid",[],"NV8"],[[10224,10239],"valid",[],"NV8"],[[10240,10495],"valid",[],"NV8"],[[10496,10763],"valid",[],"NV8"],[[10764,10764],"mapped",[8747,8747,8747,8747]],[[10765,10867],"valid",[],"NV8"],[[10868,10868],"disallowed_STD3_mapped",[58,58,61]],[[10869,10869],"disallowed_STD3_mapped",[61,61]],[[10870,10870],"disallowed_STD3_mapped",[61,61,61]],[[10871,10971],"valid",[],"NV8"],[[10972,10972],"mapped",[10973,824]],[[10973,11007],"valid",[],"NV8"],[[11008,11021],"valid",[],"NV8"],[[11022,11027],"valid",[],"NV8"],[[11028,11034],"valid",[],"NV8"],[[11035,11039],"valid",[],"NV8"],[[11040,11043],"valid",[],"NV8"],[[11044,11084],"valid",[],"NV8"],[[11085,11087],"valid",[],"NV8"],[[11088,11092],"valid",[],"NV8"],[[11093,11097],"valid",[],"NV8"],[[11098,11123],"valid",[],"NV8"],[[11124,11125],"disallowed"],[[11126,11157],"valid",[],"NV8"],[[11158,11159],"disallowed"],[[11160,11193],"valid",[],"NV8"],[[11194,11196],"disallowed"],[[11197,11208],"valid",[],"NV8"],[[11209,11209],"disallowed"],[[11210,11217],"valid",[],"NV8"],[[11218,11243],"disallowed"],[[11244,11247],"valid",[],"NV8"],[[11248,11263],"disallowed"],[[11264,11264],"mapped",[11312]],[[11265,11265],"mapped",[11313]],[[11266,11266],"mapped",[11314]],[[11267,11267],"mapped",[11315]],[[11268,11268],"mapped",[11316]],[[11269,11269],"mapped",[11317]],[[11270,11270],"mapped",[11318]],[[11271,11271],"mapped",[11319]],[[11272,11272],"mapped",[11320]],[[11273,11273],"mapped",[11321]],[[11274,11274],"mapped",[11322]],[[11275,11275],"mapped",[11323]],[[11276,11276],"mapped",[11324]],[[11277,11277],"mapped",[11325]],[[11278,11278],"mapped",[11326]],[[11279,11279],"mapped",[11327]],[[11280,11280],"mapped",[11328]],[[11281,11281],"mapped",[11329]],[[11282,11282],"mapped",[11330]],[[11283,11283],"mapped",[11331]],[[11284,11284],"mapped",[11332]],[[11285,11285],"mapped",[11333]],[[11286,11286],"mapped",[11334]],[[11287,11287],"mapped",[11335]],[[11288,11288],"mapped",[11336]],[[11289,11289],"mapped",[11337]],[[11290,11290],"mapped",[11338]],[[11291,11291],"mapped",[11339]],[[11292,11292],"mapped",[11340]],[[11293,11293],"mapped",[11341]],[[11294,11294],"mapped",[11342]],[[11295,11295],"mapped",[11343]],[[11296,11296],"mapped",[11344]],[[11297,11297],"mapped",[11345]],[[11298,11298],"mapped",[11346]],[[11299,11299],"mapped",[11347]],[[11300,11300],"mapped",[11348]],[[11301,11301],"mapped",[11349]],[[11302,11302],"mapped",[11350]],[[11303,11303],"mapped",[11351]],[[11304,11304],"mapped",[11352]],[[11305,11305],"mapped",[11353]],[[11306,11306],"mapped",[11354]],[[11307,11307],"mapped",[11355]],[[11308,11308],"mapped",[11356]],[[11309,11309],"mapped",[11357]],[[11310,11310],"mapped",[11358]],[[11311,11311],"disallowed"],[[11312,11358],"valid"],[[11359,11359],"disallowed"],[[11360,11360],"mapped",[11361]],[[11361,11361],"valid"],[[11362,11362],"mapped",[619]],[[11363,11363],"mapped",[7549]],[[11364,11364],"mapped",[637]],[[11365,11366],"valid"],[[11367,11367],"mapped",[11368]],[[11368,11368],"valid"],[[11369,11369],"mapped",[11370]],[[11370,11370],"valid"],[[11371,11371],"mapped",[11372]],[[11372,11372],"valid"],[[11373,11373],"mapped",[593]],[[11374,11374],"mapped",[625]],[[11375,11375],"mapped",[592]],[[11376,11376],"mapped",[594]],[[11377,11377],"valid"],[[11378,11378],"mapped",[11379]],[[11379,11379],"valid"],[[11380,11380],"valid"],[[11381,11381],"mapped",[11382]],[[11382,11383],"valid"],[[11384,11387],"valid"],[[11388,11388],"mapped",[106]],[[11389,11389],"mapped",[118]],[[11390,11390],"mapped",[575]],[[11391,11391],"mapped",[576]],[[11392,11392],"mapped",[11393]],[[11393,11393],"valid"],[[11394,11394],"mapped",[11395]],[[11395,11395],"valid"],[[11396,11396],"mapped",[11397]],[[11397,11397],"valid"],[[11398,11398],"mapped",[11399]],[[11399,11399],"valid"],[[11400,11400],"mapped",[11401]],[[11401,11401],"valid"],[[11402,11402],"mapped",[11403]],[[11403,11403],"valid"],[[11404,11404],"mapped",[11405]],[[11405,11405],"valid"],[[11406,11406],"mapped",[11407]],[[11407,11407],"valid"],[[11408,11408],"mapped",[11409]],[[11409,11409],"valid"],[[11410,11410],"mapped",[11411]],[[11411,11411],"valid"],[[11412,11412],"mapped",[11413]],[[11413,11413],"valid"],[[11414,11414],"mapped",[11415]],[[11415,11415],"valid"],[[11416,11416],"mapped",[11417]],[[11417,11417],"valid"],[[11418,11418],"mapped",[11419]],[[11419,11419],"valid"],[[11420,11420],"mapped",[11421]],[[11421,11421],"valid"],[[11422,11422],"mapped",[11423]],[[11423,11423],"valid"],[[11424,11424],"mapped",[11425]],[[11425,11425],"valid"],[[11426,11426],"mapped",[11427]],[[11427,11427],"valid"],[[11428,11428],"mapped",[11429]],[[11429,11429],"valid"],[[11430,11430],"mapped",[11431]],[[11431,11431],"valid"],[[11432,11432],"mapped",[11433]],[[11433,11433],"valid"],[[11434,11434],"mapped",[11435]],[[11435,11435],"valid"],[[11436,11436],"mapped",[11437]],[[11437,11437],"valid"],[[11438,11438],"mapped",[11439]],[[11439,11439],"valid"],[[11440,11440],"mapped",[11441]],[[11441,11441],"valid"],[[11442,11442],"mapped",[11443]],[[11443,11443],"valid"],[[11444,11444],"mapped",[11445]],[[11445,11445],"valid"],[[11446,11446],"mapped",[11447]],[[11447,11447],"valid"],[[11448,11448],"mapped",[11449]],[[11449,11449],"valid"],[[11450,11450],"mapped",[11451]],[[11451,11451],"valid"],[[11452,11452],"mapped",[11453]],[[11453,11453],"valid"],[[11454,11454],"mapped",[11455]],[[11455,11455],"valid"],[[11456,11456],"mapped",[11457]],[[11457,11457],"valid"],[[11458,11458],"mapped",[11459]],[[11459,11459],"valid"],[[11460,11460],"mapped",[11461]],[[11461,11461],"valid"],[[11462,11462],"mapped",[11463]],[[11463,11463],"valid"],[[11464,11464],"mapped",[11465]],[[11465,11465],"valid"],[[11466,11466],"mapped",[11467]],[[11467,11467],"valid"],[[11468,11468],"mapped",[11469]],[[11469,11469],"valid"],[[11470,11470],"mapped",[11471]],[[11471,11471],"valid"],[[11472,11472],"mapped",[11473]],[[11473,11473],"valid"],[[11474,11474],"mapped",[11475]],[[11475,11475],"valid"],[[11476,11476],"mapped",[11477]],[[11477,11477],"valid"],[[11478,11478],"mapped",[11479]],[[11479,11479],"valid"],[[11480,11480],"mapped",[11481]],[[11481,11481],"valid"],[[11482,11482],"mapped",[11483]],[[11483,11483],"valid"],[[11484,11484],"mapped",[11485]],[[11485,11485],"valid"],[[11486,11486],"mapped",[11487]],[[11487,11487],"valid"],[[11488,11488],"mapped",[11489]],[[11489,11489],"valid"],[[11490,11490],"mapped",[11491]],[[11491,11492],"valid"],[[11493,11498],"valid",[],"NV8"],[[11499,11499],"mapped",[11500]],[[11500,11500],"valid"],[[11501,11501],"mapped",[11502]],[[11502,11505],"valid"],[[11506,11506],"mapped",[11507]],[[11507,11507],"valid"],[[11508,11512],"disallowed"],[[11513,11519],"valid",[],"NV8"],[[11520,11557],"valid"],[[11558,11558],"disallowed"],[[11559,11559],"valid"],[[11560,11564],"disallowed"],[[11565,11565],"valid"],[[11566,11567],"disallowed"],[[11568,11621],"valid"],[[11622,11623],"valid"],[[11624,11630],"disallowed"],[[11631,11631],"mapped",[11617]],[[11632,11632],"valid",[],"NV8"],[[11633,11646],"disallowed"],[[11647,11647],"valid"],[[11648,11670],"valid"],[[11671,11679],"disallowed"],[[11680,11686],"valid"],[[11687,11687],"disallowed"],[[11688,11694],"valid"],[[11695,11695],"disallowed"],[[11696,11702],"valid"],[[11703,11703],"disallowed"],[[11704,11710],"valid"],[[11711,11711],"disallowed"],[[11712,11718],"valid"],[[11719,11719],"disallowed"],[[11720,11726],"valid"],[[11727,11727],"disallowed"],[[11728,11734],"valid"],[[11735,11735],"disallowed"],[[11736,11742],"valid"],[[11743,11743],"disallowed"],[[11744,11775],"valid"],[[11776,11799],"valid",[],"NV8"],[[11800,11803],"valid",[],"NV8"],[[11804,11805],"valid",[],"NV8"],[[11806,11822],"valid",[],"NV8"],[[11823,11823],"valid"],[[11824,11824],"valid",[],"NV8"],[[11825,11825],"valid",[],"NV8"],[[11826,11835],"valid",[],"NV8"],[[11836,11842],"valid",[],"NV8"],[[11843,11903],"disallowed"],[[11904,11929],"valid",[],"NV8"],[[11930,11930],"disallowed"],[[11931,11934],"valid",[],"NV8"],[[11935,11935],"mapped",[27597]],[[11936,12018],"valid",[],"NV8"],[[12019,12019],"mapped",[40863]],[[12020,12031],"disallowed"],[[12032,12032],"mapped",[19968]],[[12033,12033],"mapped",[20008]],[[12034,12034],"mapped",[20022]],[[12035,12035],"mapped",[20031]],[[12036,12036],"mapped",[20057]],[[12037,12037],"mapped",[20101]],[[12038,12038],"mapped",[20108]],[[12039,12039],"mapped",[20128]],[[12040,12040],"mapped",[20154]],[[12041,12041],"mapped",[20799]],[[12042,12042],"mapped",[20837]],[[12043,12043],"mapped",[20843]],[[12044,12044],"mapped",[20866]],[[12045,12045],"mapped",[20886]],[[12046,12046],"mapped",[20907]],[[12047,12047],"mapped",[20960]],[[12048,12048],"mapped",[20981]],[[12049,12049],"mapped",[20992]],[[12050,12050],"mapped",[21147]],[[12051,12051],"mapped",[21241]],[[12052,12052],"mapped",[21269]],[[12053,12053],"mapped",[21274]],[[12054,12054],"mapped",[21304]],[[12055,12055],"mapped",[21313]],[[12056,12056],"mapped",[21340]],[[12057,12057],"mapped",[21353]],[[12058,12058],"mapped",[21378]],[[12059,12059],"mapped",[21430]],[[12060,12060],"mapped",[21448]],[[12061,12061],"mapped",[21475]],[[12062,12062],"mapped",[22231]],[[12063,12063],"mapped",[22303]],[[12064,12064],"mapped",[22763]],[[12065,12065],"mapped",[22786]],[[12066,12066],"mapped",[22794]],[[12067,12067],"mapped",[22805]],[[12068,12068],"mapped",[22823]],[[12069,12069],"mapped",[22899]],[[12070,12070],"mapped",[23376]],[[12071,12071],"mapped",[23424]],[[12072,12072],"mapped",[23544]],[[12073,12073],"mapped",[23567]],[[12074,12074],"mapped",[23586]],[[12075,12075],"mapped",[23608]],[[12076,12076],"mapped",[23662]],[[12077,12077],"mapped",[23665]],[[12078,12078],"mapped",[24027]],[[12079,12079],"mapped",[24037]],[[12080,12080],"mapped",[24049]],[[12081,12081],"mapped",[24062]],[[12082,12082],"mapped",[24178]],[[12083,12083],"mapped",[24186]],[[12084,12084],"mapped",[24191]],[[12085,12085],"mapped",[24308]],[[12086,12086],"mapped",[24318]],[[12087,12087],"mapped",[24331]],[[12088,12088],"mapped",[24339]],[[12089,12089],"mapped",[24400]],[[12090,12090],"mapped",[24417]],[[12091,12091],"mapped",[24435]],[[12092,12092],"mapped",[24515]],[[12093,12093],"mapped",[25096]],[[12094,12094],"mapped",[25142]],[[12095,12095],"mapped",[25163]],[[12096,12096],"mapped",[25903]],[[12097,12097],"mapped",[25908]],[[12098,12098],"mapped",[25991]],[[12099,12099],"mapped",[26007]],[[12100,12100],"mapped",[26020]],[[12101,12101],"mapped",[26041]],[[12102,12102],"mapped",[26080]],[[12103,12103],"mapped",[26085]],[[12104,12104],"mapped",[26352]],[[12105,12105],"mapped",[26376]],[[12106,12106],"mapped",[26408]],[[12107,12107],"mapped",[27424]],[[12108,12108],"mapped",[27490]],[[12109,12109],"mapped",[27513]],[[12110,12110],"mapped",[27571]],[[12111,12111],"mapped",[27595]],[[12112,12112],"mapped",[27604]],[[12113,12113],"mapped",[27611]],[[12114,12114],"mapped",[27663]],[[12115,12115],"mapped",[27668]],[[12116,12116],"mapped",[27700]],[[12117,12117],"mapped",[28779]],[[12118,12118],"mapped",[29226]],[[12119,12119],"mapped",[29238]],[[12120,12120],"mapped",[29243]],[[12121,12121],"mapped",[29247]],[[12122,12122],"mapped",[29255]],[[12123,12123],"mapped",[29273]],[[12124,12124],"mapped",[29275]],[[12125,12125],"mapped",[29356]],[[12126,12126],"mapped",[29572]],[[12127,12127],"mapped",[29577]],[[12128,12128],"mapped",[29916]],[[12129,12129],"mapped",[29926]],[[12130,12130],"mapped",[29976]],[[12131,12131],"mapped",[29983]],[[12132,12132],"mapped",[29992]],[[12133,12133],"mapped",[30000]],[[12134,12134],"mapped",[30091]],[[12135,12135],"mapped",[30098]],[[12136,12136],"mapped",[30326]],[[12137,12137],"mapped",[30333]],[[12138,12138],"mapped",[30382]],[[12139,12139],"mapped",[30399]],[[12140,12140],"mapped",[30446]],[[12141,12141],"mapped",[30683]],[[12142,12142],"mapped",[30690]],[[12143,12143],"mapped",[30707]],[[12144,12144],"mapped",[31034]],[[12145,12145],"mapped",[31160]],[[12146,12146],"mapped",[31166]],[[12147,12147],"mapped",[31348]],[[12148,12148],"mapped",[31435]],[[12149,12149],"mapped",[31481]],[[12150,12150],"mapped",[31859]],[[12151,12151],"mapped",[31992]],[[12152,12152],"mapped",[32566]],[[12153,12153],"mapped",[32593]],[[12154,12154],"mapped",[32650]],[[12155,12155],"mapped",[32701]],[[12156,12156],"mapped",[32769]],[[12157,12157],"mapped",[32780]],[[12158,12158],"mapped",[32786]],[[12159,12159],"mapped",[32819]],[[12160,12160],"mapped",[32895]],[[12161,12161],"mapped",[32905]],[[12162,12162],"mapped",[33251]],[[12163,12163],"mapped",[33258]],[[12164,12164],"mapped",[33267]],[[12165,12165],"mapped",[33276]],[[12166,12166],"mapped",[33292]],[[12167,12167],"mapped",[33307]],[[12168,12168],"mapped",[33311]],[[12169,12169],"mapped",[33390]],[[12170,12170],"mapped",[33394]],[[12171,12171],"mapped",[33400]],[[12172,12172],"mapped",[34381]],[[12173,12173],"mapped",[34411]],[[12174,12174],"mapped",[34880]],[[12175,12175],"mapped",[34892]],[[12176,12176],"mapped",[34915]],[[12177,12177],"mapped",[35198]],[[12178,12178],"mapped",[35211]],[[12179,12179],"mapped",[35282]],[[12180,12180],"mapped",[35328]],[[12181,12181],"mapped",[35895]],[[12182,12182],"mapped",[35910]],[[12183,12183],"mapped",[35925]],[[12184,12184],"mapped",[35960]],[[12185,12185],"mapped",[35997]],[[12186,12186],"mapped",[36196]],[[12187,12187],"mapped",[36208]],[[12188,12188],"mapped",[36275]],[[12189,12189],"mapped",[36523]],[[12190,12190],"mapped",[36554]],[[12191,12191],"mapped",[36763]],[[12192,12192],"mapped",[36784]],[[12193,12193],"mapped",[36789]],[[12194,12194],"mapped",[37009]],[[12195,12195],"mapped",[37193]],[[12196,12196],"mapped",[37318]],[[12197,12197],"mapped",[37324]],[[12198,12198],"mapped",[37329]],[[12199,12199],"mapped",[38263]],[[12200,12200],"mapped",[38272]],[[12201,12201],"mapped",[38428]],[[12202,12202],"mapped",[38582]],[[12203,12203],"mapped",[38585]],[[12204,12204],"mapped",[38632]],[[12205,12205],"mapped",[38737]],[[12206,12206],"mapped",[38750]],[[12207,12207],"mapped",[38754]],[[12208,12208],"mapped",[38761]],[[12209,12209],"mapped",[38859]],[[12210,12210],"mapped",[38893]],[[12211,12211],"mapped",[38899]],[[12212,12212],"mapped",[38913]],[[12213,12213],"mapped",[39080]],[[12214,12214],"mapped",[39131]],[[12215,12215],"mapped",[39135]],[[12216,12216],"mapped",[39318]],[[12217,12217],"mapped",[39321]],[[12218,12218],"mapped",[39340]],[[12219,12219],"mapped",[39592]],[[12220,12220],"mapped",[39640]],[[12221,12221],"mapped",[39647]],[[12222,12222],"mapped",[39717]],[[12223,12223],"mapped",[39727]],[[12224,12224],"mapped",[39730]],[[12225,12225],"mapped",[39740]],[[12226,12226],"mapped",[39770]],[[12227,12227],"mapped",[40165]],[[12228,12228],"mapped",[40565]],[[12229,12229],"mapped",[40575]],[[12230,12230],"mapped",[40613]],[[12231,12231],"mapped",[40635]],[[12232,12232],"mapped",[40643]],[[12233,12233],"mapped",[40653]],[[12234,12234],"mapped",[40657]],[[12235,12235],"mapped",[40697]],[[12236,12236],"mapped",[40701]],[[12237,12237],"mapped",[40718]],[[12238,12238],"mapped",[40723]],[[12239,12239],"mapped",[40736]],[[12240,12240],"mapped",[40763]],[[12241,12241],"mapped",[40778]],[[12242,12242],"mapped",[40786]],[[12243,12243],"mapped",[40845]],[[12244,12244],"mapped",[40860]],[[12245,12245],"mapped",[40864]],[[12246,12271],"disallowed"],[[12272,12283],"disallowed"],[[12284,12287],"disallowed"],[[12288,12288],"disallowed_STD3_mapped",[32]],[[12289,12289],"valid",[],"NV8"],[[12290,12290],"mapped",[46]],[[12291,12292],"valid",[],"NV8"],[[12293,12295],"valid"],[[12296,12329],"valid",[],"NV8"],[[12330,12333],"valid"],[[12334,12341],"valid",[],"NV8"],[[12342,12342],"mapped",[12306]],[[12343,12343],"valid",[],"NV8"],[[12344,12344],"mapped",[21313]],[[12345,12345],"mapped",[21316]],[[12346,12346],"mapped",[21317]],[[12347,12347],"valid",[],"NV8"],[[12348,12348],"valid"],[[12349,12349],"valid",[],"NV8"],[[12350,12350],"valid",[],"NV8"],[[12351,12351],"valid",[],"NV8"],[[12352,12352],"disallowed"],[[12353,12436],"valid"],[[12437,12438],"valid"],[[12439,12440],"disallowed"],[[12441,12442],"valid"],[[12443,12443],"disallowed_STD3_mapped",[32,12441]],[[12444,12444],"disallowed_STD3_mapped",[32,12442]],[[12445,12446],"valid"],[[12447,12447],"mapped",[12424,12426]],[[12448,12448],"valid",[],"NV8"],[[12449,12542],"valid"],[[12543,12543],"mapped",[12467,12488]],[[12544,12548],"disallowed"],[[12549,12588],"valid"],[[12589,12589],"valid"],[[12590,12592],"disallowed"],[[12593,12593],"mapped",[4352]],[[12594,12594],"mapped",[4353]],[[12595,12595],"mapped",[4522]],[[12596,12596],"mapped",[4354]],[[12597,12597],"mapped",[4524]],[[12598,12598],"mapped",[4525]],[[12599,12599],"mapped",[4355]],[[12600,12600],"mapped",[4356]],[[12601,12601],"mapped",[4357]],[[12602,12602],"mapped",[4528]],[[12603,12603],"mapped",[4529]],[[12604,12604],"mapped",[4530]],[[12605,12605],"mapped",[4531]],[[12606,12606],"mapped",[4532]],[[12607,12607],"mapped",[4533]],[[12608,12608],"mapped",[4378]],[[12609,12609],"mapped",[4358]],[[12610,12610],"mapped",[4359]],[[12611,12611],"mapped",[4360]],[[12612,12612],"mapped",[4385]],[[12613,12613],"mapped",[4361]],[[12614,12614],"mapped",[4362]],[[12615,12615],"mapped",[4363]],[[12616,12616],"mapped",[4364]],[[12617,12617],"mapped",[4365]],[[12618,12618],"mapped",[4366]],[[12619,12619],"mapped",[4367]],[[12620,12620],"mapped",[4368]],[[12621,12621],"mapped",[4369]],[[12622,12622],"mapped",[4370]],[[12623,12623],"mapped",[4449]],[[12624,12624],"mapped",[4450]],[[12625,12625],"mapped",[4451]],[[12626,12626],"mapped",[4452]],[[12627,12627],"mapped",[4453]],[[12628,12628],"mapped",[4454]],[[12629,12629],"mapped",[4455]],[[12630,12630],"mapped",[4456]],[[12631,12631],"mapped",[4457]],[[12632,12632],"mapped",[4458]],[[12633,12633],"mapped",[4459]],[[12634,12634],"mapped",[4460]],[[12635,12635],"mapped",[4461]],[[12636,12636],"mapped",[4462]],[[12637,12637],"mapped",[4463]],[[12638,12638],"mapped",[4464]],[[12639,12639],"mapped",[4465]],[[12640,12640],"mapped",[4466]],[[12641,12641],"mapped",[4467]],[[12642,12642],"mapped",[4468]],[[12643,12643],"mapped",[4469]],[[12644,12644],"disallowed"],[[12645,12645],"mapped",[4372]],[[12646,12646],"mapped",[4373]],[[12647,12647],"mapped",[4551]],[[12648,12648],"mapped",[4552]],[[12649,12649],"mapped",[4556]],[[12650,12650],"mapped",[4558]],[[12651,12651],"mapped",[4563]],[[12652,12652],"mapped",[4567]],[[12653,12653],"mapped",[4569]],[[12654,12654],"mapped",[4380]],[[12655,12655],"mapped",[4573]],[[12656,12656],"mapped",[4575]],[[12657,12657],"mapped",[4381]],[[12658,12658],"mapped",[4382]],[[12659,12659],"mapped",[4384]],[[12660,12660],"mapped",[4386]],[[12661,12661],"mapped",[4387]],[[12662,12662],"mapped",[4391]],[[12663,12663],"mapped",[4393]],[[12664,12664],"mapped",[4395]],[[12665,12665],"mapped",[4396]],[[12666,12666],"mapped",[4397]],[[12667,12667],"mapped",[4398]],[[12668,12668],"mapped",[4399]],[[12669,12669],"mapped",[4402]],[[12670,12670],"mapped",[4406]],[[12671,12671],"mapped",[4416]],[[12672,12672],"mapped",[4423]],[[12673,12673],"mapped",[4428]],[[12674,12674],"mapped",[4593]],[[12675,12675],"mapped",[4594]],[[12676,12676],"mapped",[4439]],[[12677,12677],"mapped",[4440]],[[12678,12678],"mapped",[4441]],[[12679,12679],"mapped",[4484]],[[12680,12680],"mapped",[4485]],[[12681,12681],"mapped",[4488]],[[12682,12682],"mapped",[4497]],[[12683,12683],"mapped",[4498]],[[12684,12684],"mapped",[4500]],[[12685,12685],"mapped",[4510]],[[12686,12686],"mapped",[4513]],[[12687,12687],"disallowed"],[[12688,12689],"valid",[],"NV8"],[[12690,12690],"mapped",[19968]],[[12691,12691],"mapped",[20108]],[[12692,12692],"mapped",[19977]],[[12693,12693],"mapped",[22235]],[[12694,12694],"mapped",[19978]],[[12695,12695],"mapped",[20013]],[[12696,12696],"mapped",[19979]],[[12697,12697],"mapped",[30002]],[[12698,12698],"mapped",[20057]],[[12699,12699],"mapped",[19993]],[[12700,12700],"mapped",[19969]],[[12701,12701],"mapped",[22825]],[[12702,12702],"mapped",[22320]],[[12703,12703],"mapped",[20154]],[[12704,12727],"valid"],[[12728,12730],"valid"],[[12731,12735],"disallowed"],[[12736,12751],"valid",[],"NV8"],[[12752,12771],"valid",[],"NV8"],[[12772,12783],"disallowed"],[[12784,12799],"valid"],[[12800,12800],"disallowed_STD3_mapped",[40,4352,41]],[[12801,12801],"disallowed_STD3_mapped",[40,4354,41]],[[12802,12802],"disallowed_STD3_mapped",[40,4355,41]],[[12803,12803],"disallowed_STD3_mapped",[40,4357,41]],[[12804,12804],"disallowed_STD3_mapped",[40,4358,41]],[[12805,12805],"disallowed_STD3_mapped",[40,4359,41]],[[12806,12806],"disallowed_STD3_mapped",[40,4361,41]],[[12807,12807],"disallowed_STD3_mapped",[40,4363,41]],[[12808,12808],"disallowed_STD3_mapped",[40,4364,41]],[[12809,12809],"disallowed_STD3_mapped",[40,4366,41]],[[12810,12810],"disallowed_STD3_mapped",[40,4367,41]],[[12811,12811],"disallowed_STD3_mapped",[40,4368,41]],[[12812,12812],"disallowed_STD3_mapped",[40,4369,41]],[[12813,12813],"disallowed_STD3_mapped",[40,4370,41]],[[12814,12814],"disallowed_STD3_mapped",[40,44032,41]],[[12815,12815],"disallowed_STD3_mapped",[40,45208,41]],[[12816,12816],"disallowed_STD3_mapped",[40,45796,41]],[[12817,12817],"disallowed_STD3_mapped",[40,46972,41]],[[12818,12818],"disallowed_STD3_mapped",[40,47560,41]],[[12819,12819],"disallowed_STD3_mapped",[40,48148,41]],[[12820,12820],"disallowed_STD3_mapped",[40,49324,41]],[[12821,12821],"disallowed_STD3_mapped",[40,50500,41]],[[12822,12822],"disallowed_STD3_mapped",[40,51088,41]],[[12823,12823],"disallowed_STD3_mapped",[40,52264,41]],[[12824,12824],"disallowed_STD3_mapped",[40,52852,41]],[[12825,12825],"disallowed_STD3_mapped",[40,53440,41]],[[12826,12826],"disallowed_STD3_mapped",[40,54028,41]],[[12827,12827],"disallowed_STD3_mapped",[40,54616,41]],[[12828,12828],"disallowed_STD3_mapped",[40,51452,41]],[[12829,12829],"disallowed_STD3_mapped",[40,50724,51204,41]],[[12830,12830],"disallowed_STD3_mapped",[40,50724,54980,41]],[[12831,12831],"disallowed"],[[12832,12832],"disallowed_STD3_mapped",[40,19968,41]],[[12833,12833],"disallowed_STD3_mapped",[40,20108,41]],[[12834,12834],"disallowed_STD3_mapped",[40,19977,41]],[[12835,12835],"disallowed_STD3_mapped",[40,22235,41]],[[12836,12836],"disallowed_STD3_mapped",[40,20116,41]],[[12837,12837],"disallowed_STD3_mapped",[40,20845,41]],[[12838,12838],"disallowed_STD3_mapped",[40,19971,41]],[[12839,12839],"disallowed_STD3_mapped",[40,20843,41]],[[12840,12840],"disallowed_STD3_mapped",[40,20061,41]],[[12841,12841],"disallowed_STD3_mapped",[40,21313,41]],[[12842,12842],"disallowed_STD3_mapped",[40,26376,41]],[[12843,12843],"disallowed_STD3_mapped",[40,28779,41]],[[12844,12844],"disallowed_STD3_mapped",[40,27700,41]],[[12845,12845],"disallowed_STD3_mapped",[40,26408,41]],[[12846,12846],"disallowed_STD3_mapped",[40,37329,41]],[[12847,12847],"disallowed_STD3_mapped",[40,22303,41]],[[12848,12848],"disallowed_STD3_mapped",[40,26085,41]],[[12849,12849],"disallowed_STD3_mapped",[40,26666,41]],[[12850,12850],"disallowed_STD3_mapped",[40,26377,41]],[[12851,12851],"disallowed_STD3_mapped",[40,31038,41]],[[12852,12852],"disallowed_STD3_mapped",[40,21517,41]],[[12853,12853],"disallowed_STD3_mapped",[40,29305,41]],[[12854,12854],"disallowed_STD3_mapped",[40,36001,41]],[[12855,12855],"disallowed_STD3_mapped",[40,31069,41]],[[12856,12856],"disallowed_STD3_mapped",[40,21172,41]],[[12857,12857],"disallowed_STD3_mapped",[40,20195,41]],[[12858,12858],"disallowed_STD3_mapped",[40,21628,41]],[[12859,12859],"disallowed_STD3_mapped",[40,23398,41]],[[12860,12860],"disallowed_STD3_mapped",[40,30435,41]],[[12861,12861],"disallowed_STD3_mapped",[40,20225,41]],[[12862,12862],"disallowed_STD3_mapped",[40,36039,41]],[[12863,12863],"disallowed_STD3_mapped",[40,21332,41]],[[12864,12864],"disallowed_STD3_mapped",[40,31085,41]],[[12865,12865],"disallowed_STD3_mapped",[40,20241,41]],[[12866,12866],"disallowed_STD3_mapped",[40,33258,41]],[[12867,12867],"disallowed_STD3_mapped",[40,33267,41]],[[12868,12868],"mapped",[21839]],[[12869,12869],"mapped",[24188]],[[12870,12870],"mapped",[25991]],[[12871,12871],"mapped",[31631]],[[12872,12879],"valid",[],"NV8"],[[12880,12880],"mapped",[112,116,101]],[[12881,12881],"mapped",[50,49]],[[12882,12882],"mapped",[50,50]],[[12883,12883],"mapped",[50,51]],[[12884,12884],"mapped",[50,52]],[[12885,12885],"mapped",[50,53]],[[12886,12886],"mapped",[50,54]],[[12887,12887],"mapped",[50,55]],[[12888,12888],"mapped",[50,56]],[[12889,12889],"mapped",[50,57]],[[12890,12890],"mapped",[51,48]],[[12891,12891],"mapped",[51,49]],[[12892,12892],"mapped",[51,50]],[[12893,12893],"mapped",[51,51]],[[12894,12894],"mapped",[51,52]],[[12895,12895],"mapped",[51,53]],[[12896,12896],"mapped",[4352]],[[12897,12897],"mapped",[4354]],[[12898,12898],"mapped",[4355]],[[12899,12899],"mapped",[4357]],[[12900,12900],"mapped",[4358]],[[12901,12901],"mapped",[4359]],[[12902,12902],"mapped",[4361]],[[12903,12903],"mapped",[4363]],[[12904,12904],"mapped",[4364]],[[12905,12905],"mapped",[4366]],[[12906,12906],"mapped",[4367]],[[12907,12907],"mapped",[4368]],[[12908,12908],"mapped",[4369]],[[12909,12909],"mapped",[4370]],[[12910,12910],"mapped",[44032]],[[12911,12911],"mapped",[45208]],[[12912,12912],"mapped",[45796]],[[12913,12913],"mapped",[46972]],[[12914,12914],"mapped",[47560]],[[12915,12915],"mapped",[48148]],[[12916,12916],"mapped",[49324]],[[12917,12917],"mapped",[50500]],[[12918,12918],"mapped",[51088]],[[12919,12919],"mapped",[52264]],[[12920,12920],"mapped",[52852]],[[12921,12921],"mapped",[53440]],[[12922,12922],"mapped",[54028]],[[12923,12923],"mapped",[54616]],[[12924,12924],"mapped",[52280,44256]],[[12925,12925],"mapped",[51452,51032]],[[12926,12926],"mapped",[50864]],[[12927,12927],"valid",[],"NV8"],[[12928,12928],"mapped",[19968]],[[12929,12929],"mapped",[20108]],[[12930,12930],"mapped",[19977]],[[12931,12931],"mapped",[22235]],[[12932,12932],"mapped",[20116]],[[12933,12933],"mapped",[20845]],[[12934,12934],"mapped",[19971]],[[12935,12935],"mapped",[20843]],[[12936,12936],"mapped",[20061]],[[12937,12937],"mapped",[21313]],[[12938,12938],"mapped",[26376]],[[12939,12939],"mapped",[28779]],[[12940,12940],"mapped",[27700]],[[12941,12941],"mapped",[26408]],[[12942,12942],"mapped",[37329]],[[12943,12943],"mapped",[22303]],[[12944,12944],"mapped",[26085]],[[12945,12945],"mapped",[26666]],[[12946,12946],"mapped",[26377]],[[12947,12947],"mapped",[31038]],[[12948,12948],"mapped",[21517]],[[12949,12949],"mapped",[29305]],[[12950,12950],"mapped",[36001]],[[12951,12951],"mapped",[31069]],[[12952,12952],"mapped",[21172]],[[12953,12953],"mapped",[31192]],[[12954,12954],"mapped",[30007]],[[12955,12955],"mapped",[22899]],[[12956,12956],"mapped",[36969]],[[12957,12957],"mapped",[20778]],[[12958,12958],"mapped",[21360]],[[12959,12959],"mapped",[27880]],[[12960,12960],"mapped",[38917]],[[12961,12961],"mapped",[20241]],[[12962,12962],"mapped",[20889]],[[12963,12963],"mapped",[27491]],[[12964,12964],"mapped",[19978]],[[12965,12965],"mapped",[20013]],[[12966,12966],"mapped",[19979]],[[12967,12967],"mapped",[24038]],[[12968,12968],"mapped",[21491]],[[12969,12969],"mapped",[21307]],[[12970,12970],"mapped",[23447]],[[12971,12971],"mapped",[23398]],[[12972,12972],"mapped",[30435]],[[12973,12973],"mapped",[20225]],[[12974,12974],"mapped",[36039]],[[12975,12975],"mapped",[21332]],[[12976,12976],"mapped",[22812]],[[12977,12977],"mapped",[51,54]],[[12978,12978],"mapped",[51,55]],[[12979,12979],"mapped",[51,56]],[[12980,12980],"mapped",[51,57]],[[12981,12981],"mapped",[52,48]],[[12982,12982],"mapped",[52,49]],[[12983,12983],"mapped",[52,50]],[[12984,12984],"mapped",[52,51]],[[12985,12985],"mapped",[52,52]],[[12986,12986],"mapped",[52,53]],[[12987,12987],"mapped",[52,54]],[[12988,12988],"mapped",[52,55]],[[12989,12989],"mapped",[52,56]],[[12990,12990],"mapped",[52,57]],[[12991,12991],"mapped",[53,48]],[[12992,12992],"mapped",[49,26376]],[[12993,12993],"mapped",[50,26376]],[[12994,12994],"mapped",[51,26376]],[[12995,12995],"mapped",[52,26376]],[[12996,12996],"mapped",[53,26376]],[[12997,12997],"mapped",[54,26376]],[[12998,12998],"mapped",[55,26376]],[[12999,12999],"mapped",[56,26376]],[[13000,13000],"mapped",[57,26376]],[[13001,13001],"mapped",[49,48,26376]],[[13002,13002],"mapped",[49,49,26376]],[[13003,13003],"mapped",[49,50,26376]],[[13004,13004],"mapped",[104,103]],[[13005,13005],"mapped",[101,114,103]],[[13006,13006],"mapped",[101,118]],[[13007,13007],"mapped",[108,116,100]],[[13008,13008],"mapped",[12450]],[[13009,13009],"mapped",[12452]],[[13010,13010],"mapped",[12454]],[[13011,13011],"mapped",[12456]],[[13012,13012],"mapped",[12458]],[[13013,13013],"mapped",[12459]],[[13014,13014],"mapped",[12461]],[[13015,13015],"mapped",[12463]],[[13016,13016],"mapped",[12465]],[[13017,13017],"mapped",[12467]],[[13018,13018],"mapped",[12469]],[[13019,13019],"mapped",[12471]],[[13020,13020],"mapped",[12473]],[[13021,13021],"mapped",[12475]],[[13022,13022],"mapped",[12477]],[[13023,13023],"mapped",[12479]],[[13024,13024],"mapped",[12481]],[[13025,13025],"mapped",[12484]],[[13026,13026],"mapped",[12486]],[[13027,13027],"mapped",[12488]],[[13028,13028],"mapped",[12490]],[[13029,13029],"mapped",[12491]],[[13030,13030],"mapped",[12492]],[[13031,13031],"mapped",[12493]],[[13032,13032],"mapped",[12494]],[[13033,13033],"mapped",[12495]],[[13034,13034],"mapped",[12498]],[[13035,13035],"mapped",[12501]],[[13036,13036],"mapped",[12504]],[[13037,13037],"mapped",[12507]],[[13038,13038],"mapped",[12510]],[[13039,13039],"mapped",[12511]],[[13040,13040],"mapped",[12512]],[[13041,13041],"mapped",[12513]],[[13042,13042],"mapped",[12514]],[[13043,13043],"mapped",[12516]],[[13044,13044],"mapped",[12518]],[[13045,13045],"mapped",[12520]],[[13046,13046],"mapped",[12521]],[[13047,13047],"mapped",[12522]],[[13048,13048],"mapped",[12523]],[[13049,13049],"mapped",[12524]],[[13050,13050],"mapped",[12525]],[[13051,13051],"mapped",[12527]],[[13052,13052],"mapped",[12528]],[[13053,13053],"mapped",[12529]],[[13054,13054],"mapped",[12530]],[[13055,13055],"disallowed"],[[13056,13056],"mapped",[12450,12497,12540,12488]],[[13057,13057],"mapped",[12450,12523,12501,12449]],[[13058,13058],"mapped",[12450,12531,12506,12450]],[[13059,13059],"mapped",[12450,12540,12523]],[[13060,13060],"mapped",[12452,12491,12531,12464]],[[13061,13061],"mapped",[12452,12531,12481]],[[13062,13062],"mapped",[12454,12457,12531]],[[13063,13063],"mapped",[12456,12473,12463,12540,12489]],[[13064,13064],"mapped",[12456,12540,12459,12540]],[[13065,13065],"mapped",[12458,12531,12473]],[[13066,13066],"mapped",[12458,12540,12512]],[[13067,13067],"mapped",[12459,12452,12522]],[[13068,13068],"mapped",[12459,12521,12483,12488]],[[13069,13069],"mapped",[12459,12525,12522,12540]],[[13070,13070],"mapped",[12460,12525,12531]],[[13071,13071],"mapped",[12460,12531,12510]],[[13072,13072],"mapped",[12462,12460]],[[13073,13073],"mapped",[12462,12491,12540]],[[13074,13074],"mapped",[12461,12517,12522,12540]],[[13075,13075],"mapped",[12462,12523,12480,12540]],[[13076,13076],"mapped",[12461,12525]],[[13077,13077],"mapped",[12461,12525,12464,12521,12512]],[[13078,13078],"mapped",[12461,12525,12513,12540,12488,12523]],[[13079,13079],"mapped",[12461,12525,12527,12483,12488]],[[13080,13080],"mapped",[12464,12521,12512]],[[13081,13081],"mapped",[12464,12521,12512,12488,12531]],[[13082,13082],"mapped",[12463,12523,12476,12452,12525]],[[13083,13083],"mapped",[12463,12525,12540,12493]],[[13084,13084],"mapped",[12465,12540,12473]],[[13085,13085],"mapped",[12467,12523,12490]],[[13086,13086],"mapped",[12467,12540,12509]],[[13087,13087],"mapped",[12469,12452,12463,12523]],[[13088,13088],"mapped",[12469,12531,12481,12540,12512]],[[13089,13089],"mapped",[12471,12522,12531,12464]],[[13090,13090],"mapped",[12475,12531,12481]],[[13091,13091],"mapped",[12475,12531,12488]],[[13092,13092],"mapped",[12480,12540,12473]],[[13093,13093],"mapped",[12487,12471]],[[13094,13094],"mapped",[12489,12523]],[[13095,13095],"mapped",[12488,12531]],[[13096,13096],"mapped",[12490,12494]],[[13097,13097],"mapped",[12494,12483,12488]],[[13098,13098],"mapped",[12495,12452,12484]],[[13099,13099],"mapped",[12497,12540,12475,12531,12488]],[[13100,13100],"mapped",[12497,12540,12484]],[[13101,13101],"mapped",[12496,12540,12524,12523]],[[13102,13102],"mapped",[12500,12450,12473,12488,12523]],[[13103,13103],"mapped",[12500,12463,12523]],[[13104,13104],"mapped",[12500,12467]],[[13105,13105],"mapped",[12499,12523]],[[13106,13106],"mapped",[12501,12449,12521,12483,12489]],[[13107,13107],"mapped",[12501,12451,12540,12488]],[[13108,13108],"mapped",[12502,12483,12471,12455,12523]],[[13109,13109],"mapped",[12501,12521,12531]],[[13110,13110],"mapped",[12504,12463,12479,12540,12523]],[[13111,13111],"mapped",[12506,12477]],[[13112,13112],"mapped",[12506,12491,12498]],[[13113,13113],"mapped",[12504,12523,12484]],[[13114,13114],"mapped",[12506,12531,12473]],[[13115,13115],"mapped",[12506,12540,12472]],[[13116,13116],"mapped",[12505,12540,12479]],[[13117,13117],"mapped",[12509,12452,12531,12488]],[[13118,13118],"mapped",[12508,12523,12488]],[[13119,13119],"mapped",[12507,12531]],[[13120,13120],"mapped",[12509,12531,12489]],[[13121,13121],"mapped",[12507,12540,12523]],[[13122,13122],"mapped",[12507,12540,12531]],[[13123,13123],"mapped",[12510,12452,12463,12525]],[[13124,13124],"mapped",[12510,12452,12523]],[[13125,13125],"mapped",[12510,12483,12495]],[[13126,13126],"mapped",[12510,12523,12463]],[[13127,13127],"mapped",[12510,12531,12471,12519,12531]],[[13128,13128],"mapped",[12511,12463,12525,12531]],[[13129,13129],"mapped",[12511,12522]],[[13130,13130],"mapped",[12511,12522,12496,12540,12523]],[[13131,13131],"mapped",[12513,12460]],[[13132,13132],"mapped",[12513,12460,12488,12531]],[[13133,13133],"mapped",[12513,12540,12488,12523]],[[13134,13134],"mapped",[12516,12540,12489]],[[13135,13135],"mapped",[12516,12540,12523]],[[13136,13136],"mapped",[12518,12450,12531]],[[13137,13137],"mapped",[12522,12483,12488,12523]],[[13138,13138],"mapped",[12522,12521]],[[13139,13139],"mapped",[12523,12500,12540]],[[13140,13140],"mapped",[12523,12540,12502,12523]],[[13141,13141],"mapped",[12524,12512]],[[13142,13142],"mapped",[12524,12531,12488,12466,12531]],[[13143,13143],"mapped",[12527,12483,12488]],[[13144,13144],"mapped",[48,28857]],[[13145,13145],"mapped",[49,28857]],[[13146,13146],"mapped",[50,28857]],[[13147,13147],"mapped",[51,28857]],[[13148,13148],"mapped",[52,28857]],[[13149,13149],"mapped",[53,28857]],[[13150,13150],"mapped",[54,28857]],[[13151,13151],"mapped",[55,28857]],[[13152,13152],"mapped",[56,28857]],[[13153,13153],"mapped",[57,28857]],[[13154,13154],"mapped",[49,48,28857]],[[13155,13155],"mapped",[49,49,28857]],[[13156,13156],"mapped",[49,50,28857]],[[13157,13157],"mapped",[49,51,28857]],[[13158,13158],"mapped",[49,52,28857]],[[13159,13159],"mapped",[49,53,28857]],[[13160,13160],"mapped",[49,54,28857]],[[13161,13161],"mapped",[49,55,28857]],[[13162,13162],"mapped",[49,56,28857]],[[13163,13163],"mapped",[49,57,28857]],[[13164,13164],"mapped",[50,48,28857]],[[13165,13165],"mapped",[50,49,28857]],[[13166,13166],"mapped",[50,50,28857]],[[13167,13167],"mapped",[50,51,28857]],[[13168,13168],"mapped",[50,52,28857]],[[13169,13169],"mapped",[104,112,97]],[[13170,13170],"mapped",[100,97]],[[13171,13171],"mapped",[97,117]],[[13172,13172],"mapped",[98,97,114]],[[13173,13173],"mapped",[111,118]],[[13174,13174],"mapped",[112,99]],[[13175,13175],"mapped",[100,109]],[[13176,13176],"mapped",[100,109,50]],[[13177,13177],"mapped",[100,109,51]],[[13178,13178],"mapped",[105,117]],[[13179,13179],"mapped",[24179,25104]],[[13180,13180],"mapped",[26157,21644]],[[13181,13181],"mapped",[22823,27491]],[[13182,13182],"mapped",[26126,27835]],[[13183,13183],"mapped",[26666,24335,20250,31038]],[[13184,13184],"mapped",[112,97]],[[13185,13185],"mapped",[110,97]],[[13186,13186],"mapped",[956,97]],[[13187,13187],"mapped",[109,97]],[[13188,13188],"mapped",[107,97]],[[13189,13189],"mapped",[107,98]],[[13190,13190],"mapped",[109,98]],[[13191,13191],"mapped",[103,98]],[[13192,13192],"mapped",[99,97,108]],[[13193,13193],"mapped",[107,99,97,108]],[[13194,13194],"mapped",[112,102]],[[13195,13195],"mapped",[110,102]],[[13196,13196],"mapped",[956,102]],[[13197,13197],"mapped",[956,103]],[[13198,13198],"mapped",[109,103]],[[13199,13199],"mapped",[107,103]],[[13200,13200],"mapped",[104,122]],[[13201,13201],"mapped",[107,104,122]],[[13202,13202],"mapped",[109,104,122]],[[13203,13203],"mapped",[103,104,122]],[[13204,13204],"mapped",[116,104,122]],[[13205,13205],"mapped",[956,108]],[[13206,13206],"mapped",[109,108]],[[13207,13207],"mapped",[100,108]],[[13208,13208],"mapped",[107,108]],[[13209,13209],"mapped",[102,109]],[[13210,13210],"mapped",[110,109]],[[13211,13211],"mapped",[956,109]],[[13212,13212],"mapped",[109,109]],[[13213,13213],"mapped",[99,109]],[[13214,13214],"mapped",[107,109]],[[13215,13215],"mapped",[109,109,50]],[[13216,13216],"mapped",[99,109,50]],[[13217,13217],"mapped",[109,50]],[[13218,13218],"mapped",[107,109,50]],[[13219,13219],"mapped",[109,109,51]],[[13220,13220],"mapped",[99,109,51]],[[13221,13221],"mapped",[109,51]],[[13222,13222],"mapped",[107,109,51]],[[13223,13223],"mapped",[109,8725,115]],[[13224,13224],"mapped",[109,8725,115,50]],[[13225,13225],"mapped",[112,97]],[[13226,13226],"mapped",[107,112,97]],[[13227,13227],"mapped",[109,112,97]],[[13228,13228],"mapped",[103,112,97]],[[13229,13229],"mapped",[114,97,100]],[[13230,13230],"mapped",[114,97,100,8725,115]],[[13231,13231],"mapped",[114,97,100,8725,115,50]],[[13232,13232],"mapped",[112,115]],[[13233,13233],"mapped",[110,115]],[[13234,13234],"mapped",[956,115]],[[13235,13235],"mapped",[109,115]],[[13236,13236],"mapped",[112,118]],[[13237,13237],"mapped",[110,118]],[[13238,13238],"mapped",[956,118]],[[13239,13239],"mapped",[109,118]],[[13240,13240],"mapped",[107,118]],[[13241,13241],"mapped",[109,118]],[[13242,13242],"mapped",[112,119]],[[13243,13243],"mapped",[110,119]],[[13244,13244],"mapped",[956,119]],[[13245,13245],"mapped",[109,119]],[[13246,13246],"mapped",[107,119]],[[13247,13247],"mapped",[109,119]],[[13248,13248],"mapped",[107,969]],[[13249,13249],"mapped",[109,969]],[[13250,13250],"disallowed"],[[13251,13251],"mapped",[98,113]],[[13252,13252],"mapped",[99,99]],[[13253,13253],"mapped",[99,100]],[[13254,13254],"mapped",[99,8725,107,103]],[[13255,13255],"disallowed"],[[13256,13256],"mapped",[100,98]],[[13257,13257],"mapped",[103,121]],[[13258,13258],"mapped",[104,97]],[[13259,13259],"mapped",[104,112]],[[13260,13260],"mapped",[105,110]],[[13261,13261],"mapped",[107,107]],[[13262,13262],"mapped",[107,109]],[[13263,13263],"mapped",[107,116]],[[13264,13264],"mapped",[108,109]],[[13265,13265],"mapped",[108,110]],[[13266,13266],"mapped",[108,111,103]],[[13267,13267],"mapped",[108,120]],[[13268,13268],"mapped",[109,98]],[[13269,13269],"mapped",[109,105,108]],[[13270,13270],"mapped",[109,111,108]],[[13271,13271],"mapped",[112,104]],[[13272,13272],"disallowed"],[[13273,13273],"mapped",[112,112,109]],[[13274,13274],"mapped",[112,114]],[[13275,13275],"mapped",[115,114]],[[13276,13276],"mapped",[115,118]],[[13277,13277],"mapped",[119,98]],[[13278,13278],"mapped",[118,8725,109]],[[13279,13279],"mapped",[97,8725,109]],[[13280,13280],"mapped",[49,26085]],[[13281,13281],"mapped",[50,26085]],[[13282,13282],"mapped",[51,26085]],[[13283,13283],"mapped",[52,26085]],[[13284,13284],"mapped",[53,26085]],[[13285,13285],"mapped",[54,26085]],[[13286,13286],"mapped",[55,26085]],[[13287,13287],"mapped",[56,26085]],[[13288,13288],"mapped",[57,26085]],[[13289,13289],"mapped",[49,48,26085]],[[13290,13290],"mapped",[49,49,26085]],[[13291,13291],"mapped",[49,50,26085]],[[13292,13292],"mapped",[49,51,26085]],[[13293,13293],"mapped",[49,52,26085]],[[13294,13294],"mapped",[49,53,26085]],[[13295,13295],"mapped",[49,54,26085]],[[13296,13296],"mapped",[49,55,26085]],[[13297,13297],"mapped",[49,56,26085]],[[13298,13298],"mapped",[49,57,26085]],[[13299,13299],"mapped",[50,48,26085]],[[13300,13300],"mapped",[50,49,26085]],[[13301,13301],"mapped",[50,50,26085]],[[13302,13302],"mapped",[50,51,26085]],[[13303,13303],"mapped",[50,52,26085]],[[13304,13304],"mapped",[50,53,26085]],[[13305,13305],"mapped",[50,54,26085]],[[13306,13306],"mapped",[50,55,26085]],[[13307,13307],"mapped",[50,56,26085]],[[13308,13308],"mapped",[50,57,26085]],[[13309,13309],"mapped",[51,48,26085]],[[13310,13310],"mapped",[51,49,26085]],[[13311,13311],"mapped",[103,97,108]],[[13312,19893],"valid"],[[19894,19903],"disallowed"],[[19904,19967],"valid",[],"NV8"],[[19968,40869],"valid"],[[40870,40891],"valid"],[[40892,40899],"valid"],[[40900,40907],"valid"],[[40908,40908],"valid"],[[40909,40917],"valid"],[[40918,40959],"disallowed"],[[40960,42124],"valid"],[[42125,42127],"disallowed"],[[42128,42145],"valid",[],"NV8"],[[42146,42147],"valid",[],"NV8"],[[42148,42163],"valid",[],"NV8"],[[42164,42164],"valid",[],"NV8"],[[42165,42176],"valid",[],"NV8"],[[42177,42177],"valid",[],"NV8"],[[42178,42180],"valid",[],"NV8"],[[42181,42181],"valid",[],"NV8"],[[42182,42182],"valid",[],"NV8"],[[42183,42191],"disallowed"],[[42192,42237],"valid"],[[42238,42239],"valid",[],"NV8"],[[42240,42508],"valid"],[[42509,42511],"valid",[],"NV8"],[[42512,42539],"valid"],[[42540,42559],"disallowed"],[[42560,42560],"mapped",[42561]],[[42561,42561],"valid"],[[42562,42562],"mapped",[42563]],[[42563,42563],"valid"],[[42564,42564],"mapped",[42565]],[[42565,42565],"valid"],[[42566,42566],"mapped",[42567]],[[42567,42567],"valid"],[[42568,42568],"mapped",[42569]],[[42569,42569],"valid"],[[42570,42570],"mapped",[42571]],[[42571,42571],"valid"],[[42572,42572],"mapped",[42573]],[[42573,42573],"valid"],[[42574,42574],"mapped",[42575]],[[42575,42575],"valid"],[[42576,42576],"mapped",[42577]],[[42577,42577],"valid"],[[42578,42578],"mapped",[42579]],[[42579,42579],"valid"],[[42580,42580],"mapped",[42581]],[[42581,42581],"valid"],[[42582,42582],"mapped",[42583]],[[42583,42583],"valid"],[[42584,42584],"mapped",[42585]],[[42585,42585],"valid"],[[42586,42586],"mapped",[42587]],[[42587,42587],"valid"],[[42588,42588],"mapped",[42589]],[[42589,42589],"valid"],[[42590,42590],"mapped",[42591]],[[42591,42591],"valid"],[[42592,42592],"mapped",[42593]],[[42593,42593],"valid"],[[42594,42594],"mapped",[42595]],[[42595,42595],"valid"],[[42596,42596],"mapped",[42597]],[[42597,42597],"valid"],[[42598,42598],"mapped",[42599]],[[42599,42599],"valid"],[[42600,42600],"mapped",[42601]],[[42601,42601],"valid"],[[42602,42602],"mapped",[42603]],[[42603,42603],"valid"],[[42604,42604],"mapped",[42605]],[[42605,42607],"valid"],[[42608,42611],"valid",[],"NV8"],[[42612,42619],"valid"],[[42620,42621],"valid"],[[42622,42622],"valid",[],"NV8"],[[42623,42623],"valid"],[[42624,42624],"mapped",[42625]],[[42625,42625],"valid"],[[42626,42626],"mapped",[42627]],[[42627,42627],"valid"],[[42628,42628],"mapped",[42629]],[[42629,42629],"valid"],[[42630,42630],"mapped",[42631]],[[42631,42631],"valid"],[[42632,42632],"mapped",[42633]],[[42633,42633],"valid"],[[42634,42634],"mapped",[42635]],[[42635,42635],"valid"],[[42636,42636],"mapped",[42637]],[[42637,42637],"valid"],[[42638,42638],"mapped",[42639]],[[42639,42639],"valid"],[[42640,42640],"mapped",[42641]],[[42641,42641],"valid"],[[42642,42642],"mapped",[42643]],[[42643,42643],"valid"],[[42644,42644],"mapped",[42645]],[[42645,42645],"valid"],[[42646,42646],"mapped",[42647]],[[42647,42647],"valid"],[[42648,42648],"mapped",[42649]],[[42649,42649],"valid"],[[42650,42650],"mapped",[42651]],[[42651,42651],"valid"],[[42652,42652],"mapped",[1098]],[[42653,42653],"mapped",[1100]],[[42654,42654],"valid"],[[42655,42655],"valid"],[[42656,42725],"valid"],[[42726,42735],"valid",[],"NV8"],[[42736,42737],"valid"],[[42738,42743],"valid",[],"NV8"],[[42744,42751],"disallowed"],[[42752,42774],"valid",[],"NV8"],[[42775,42778],"valid"],[[42779,42783],"valid"],[[42784,42785],"valid",[],"NV8"],[[42786,42786],"mapped",[42787]],[[42787,42787],"valid"],[[42788,42788],"mapped",[42789]],[[42789,42789],"valid"],[[42790,42790],"mapped",[42791]],[[42791,42791],"valid"],[[42792,42792],"mapped",[42793]],[[42793,42793],"valid"],[[42794,42794],"mapped",[42795]],[[42795,42795],"valid"],[[42796,42796],"mapped",[42797]],[[42797,42797],"valid"],[[42798,42798],"mapped",[42799]],[[42799,42801],"valid"],[[42802,42802],"mapped",[42803]],[[42803,42803],"valid"],[[42804,42804],"mapped",[42805]],[[42805,42805],"valid"],[[42806,42806],"mapped",[42807]],[[42807,42807],"valid"],[[42808,42808],"mapped",[42809]],[[42809,42809],"valid"],[[42810,42810],"mapped",[42811]],[[42811,42811],"valid"],[[42812,42812],"mapped",[42813]],[[42813,42813],"valid"],[[42814,42814],"mapped",[42815]],[[42815,42815],"valid"],[[42816,42816],"mapped",[42817]],[[42817,42817],"valid"],[[42818,42818],"mapped",[42819]],[[42819,42819],"valid"],[[42820,42820],"mapped",[42821]],[[42821,42821],"valid"],[[42822,42822],"mapped",[42823]],[[42823,42823],"valid"],[[42824,42824],"mapped",[42825]],[[42825,42825],"valid"],[[42826,42826],"mapped",[42827]],[[42827,42827],"valid"],[[42828,42828],"mapped",[42829]],[[42829,42829],"valid"],[[42830,42830],"mapped",[42831]],[[42831,42831],"valid"],[[42832,42832],"mapped",[42833]],[[42833,42833],"valid"],[[42834,42834],"mapped",[42835]],[[42835,42835],"valid"],[[42836,42836],"mapped",[42837]],[[42837,42837],"valid"],[[42838,42838],"mapped",[42839]],[[42839,42839],"valid"],[[42840,42840],"mapped",[42841]],[[42841,42841],"valid"],[[42842,42842],"mapped",[42843]],[[42843,42843],"valid"],[[42844,42844],"mapped",[42845]],[[42845,42845],"valid"],[[42846,42846],"mapped",[42847]],[[42847,42847],"valid"],[[42848,42848],"mapped",[42849]],[[42849,42849],"valid"],[[42850,42850],"mapped",[42851]],[[42851,42851],"valid"],[[42852,42852],"mapped",[42853]],[[42853,42853],"valid"],[[42854,42854],"mapped",[42855]],[[42855,42855],"valid"],[[42856,42856],"mapped",[42857]],[[42857,42857],"valid"],[[42858,42858],"mapped",[42859]],[[42859,42859],"valid"],[[42860,42860],"mapped",[42861]],[[42861,42861],"valid"],[[42862,42862],"mapped",[42863]],[[42863,42863],"valid"],[[42864,42864],"mapped",[42863]],[[42865,42872],"valid"],[[42873,42873],"mapped",[42874]],[[42874,42874],"valid"],[[42875,42875],"mapped",[42876]],[[42876,42876],"valid"],[[42877,42877],"mapped",[7545]],[[42878,42878],"mapped",[42879]],[[42879,42879],"valid"],[[42880,42880],"mapped",[42881]],[[42881,42881],"valid"],[[42882,42882],"mapped",[42883]],[[42883,42883],"valid"],[[42884,42884],"mapped",[42885]],[[42885,42885],"valid"],[[42886,42886],"mapped",[42887]],[[42887,42888],"valid"],[[42889,42890],"valid",[],"NV8"],[[42891,42891],"mapped",[42892]],[[42892,42892],"valid"],[[42893,42893],"mapped",[613]],[[42894,42894],"valid"],[[42895,42895],"valid"],[[42896,42896],"mapped",[42897]],[[42897,42897],"valid"],[[42898,42898],"mapped",[42899]],[[42899,42899],"valid"],[[42900,42901],"valid"],[[42902,42902],"mapped",[42903]],[[42903,42903],"valid"],[[42904,42904],"mapped",[42905]],[[42905,42905],"valid"],[[42906,42906],"mapped",[42907]],[[42907,42907],"valid"],[[42908,42908],"mapped",[42909]],[[42909,42909],"valid"],[[42910,42910],"mapped",[42911]],[[42911,42911],"valid"],[[42912,42912],"mapped",[42913]],[[42913,42913],"valid"],[[42914,42914],"mapped",[42915]],[[42915,42915],"valid"],[[42916,42916],"mapped",[42917]],[[42917,42917],"valid"],[[42918,42918],"mapped",[42919]],[[42919,42919],"valid"],[[42920,42920],"mapped",[42921]],[[42921,42921],"valid"],[[42922,42922],"mapped",[614]],[[42923,42923],"mapped",[604]],[[42924,42924],"mapped",[609]],[[42925,42925],"mapped",[620]],[[42926,42927],"disallowed"],[[42928,42928],"mapped",[670]],[[42929,42929],"mapped",[647]],[[42930,42930],"mapped",[669]],[[42931,42931],"mapped",[43859]],[[42932,42932],"mapped",[42933]],[[42933,42933],"valid"],[[42934,42934],"mapped",[42935]],[[42935,42935],"valid"],[[42936,42998],"disallowed"],[[42999,42999],"valid"],[[43000,43000],"mapped",[295]],[[43001,43001],"mapped",[339]],[[43002,43002],"valid"],[[43003,43007],"valid"],[[43008,43047],"valid"],[[43048,43051],"valid",[],"NV8"],[[43052,43055],"disallowed"],[[43056,43065],"valid",[],"NV8"],[[43066,43071],"disallowed"],[[43072,43123],"valid"],[[43124,43127],"valid",[],"NV8"],[[43128,43135],"disallowed"],[[43136,43204],"valid"],[[43205,43213],"disallowed"],[[43214,43215],"valid",[],"NV8"],[[43216,43225],"valid"],[[43226,43231],"disallowed"],[[43232,43255],"valid"],[[43256,43258],"valid",[],"NV8"],[[43259,43259],"valid"],[[43260,43260],"valid",[],"NV8"],[[43261,43261],"valid"],[[43262,43263],"disallowed"],[[43264,43309],"valid"],[[43310,43311],"valid",[],"NV8"],[[43312,43347],"valid"],[[43348,43358],"disallowed"],[[43359,43359],"valid",[],"NV8"],[[43360,43388],"valid",[],"NV8"],[[43389,43391],"disallowed"],[[43392,43456],"valid"],[[43457,43469],"valid",[],"NV8"],[[43470,43470],"disallowed"],[[43471,43481],"valid"],[[43482,43485],"disallowed"],[[43486,43487],"valid",[],"NV8"],[[43488,43518],"valid"],[[43519,43519],"disallowed"],[[43520,43574],"valid"],[[43575,43583],"disallowed"],[[43584,43597],"valid"],[[43598,43599],"disallowed"],[[43600,43609],"valid"],[[43610,43611],"disallowed"],[[43612,43615],"valid",[],"NV8"],[[43616,43638],"valid"],[[43639,43641],"valid",[],"NV8"],[[43642,43643],"valid"],[[43644,43647],"valid"],[[43648,43714],"valid"],[[43715,43738],"disallowed"],[[43739,43741],"valid"],[[43742,43743],"valid",[],"NV8"],[[43744,43759],"valid"],[[43760,43761],"valid",[],"NV8"],[[43762,43766],"valid"],[[43767,43776],"disallowed"],[[43777,43782],"valid"],[[43783,43784],"disallowed"],[[43785,43790],"valid"],[[43791,43792],"disallowed"],[[43793,43798],"valid"],[[43799,43807],"disallowed"],[[43808,43814],"valid"],[[43815,43815],"disallowed"],[[43816,43822],"valid"],[[43823,43823],"disallowed"],[[43824,43866],"valid"],[[43867,43867],"valid",[],"NV8"],[[43868,43868],"mapped",[42791]],[[43869,43869],"mapped",[43831]],[[43870,43870],"mapped",[619]],[[43871,43871],"mapped",[43858]],[[43872,43875],"valid"],[[43876,43877],"valid"],[[43878,43887],"disallowed"],[[43888,43888],"mapped",[5024]],[[43889,43889],"mapped",[5025]],[[43890,43890],"mapped",[5026]],[[43891,43891],"mapped",[5027]],[[43892,43892],"mapped",[5028]],[[43893,43893],"mapped",[5029]],[[43894,43894],"mapped",[5030]],[[43895,43895],"mapped",[5031]],[[43896,43896],"mapped",[5032]],[[43897,43897],"mapped",[5033]],[[43898,43898],"mapped",[5034]],[[43899,43899],"mapped",[5035]],[[43900,43900],"mapped",[5036]],[[43901,43901],"mapped",[5037]],[[43902,43902],"mapped",[5038]],[[43903,43903],"mapped",[5039]],[[43904,43904],"mapped",[5040]],[[43905,43905],"mapped",[5041]],[[43906,43906],"mapped",[5042]],[[43907,43907],"mapped",[5043]],[[43908,43908],"mapped",[5044]],[[43909,43909],"mapped",[5045]],[[43910,43910],"mapped",[5046]],[[43911,43911],"mapped",[5047]],[[43912,43912],"mapped",[5048]],[[43913,43913],"mapped",[5049]],[[43914,43914],"mapped",[5050]],[[43915,43915],"mapped",[5051]],[[43916,43916],"mapped",[5052]],[[43917,43917],"mapped",[5053]],[[43918,43918],"mapped",[5054]],[[43919,43919],"mapped",[5055]],[[43920,43920],"mapped",[5056]],[[43921,43921],"mapped",[5057]],[[43922,43922],"mapped",[5058]],[[43923,43923],"mapped",[5059]],[[43924,43924],"mapped",[5060]],[[43925,43925],"mapped",[5061]],[[43926,43926],"mapped",[5062]],[[43927,43927],"mapped",[5063]],[[43928,43928],"mapped",[5064]],[[43929,43929],"mapped",[5065]],[[43930,43930],"mapped",[5066]],[[43931,43931],"mapped",[5067]],[[43932,43932],"mapped",[5068]],[[43933,43933],"mapped",[5069]],[[43934,43934],"mapped",[5070]],[[43935,43935],"mapped",[5071]],[[43936,43936],"mapped",[5072]],[[43937,43937],"mapped",[5073]],[[43938,43938],"mapped",[5074]],[[43939,43939],"mapped",[5075]],[[43940,43940],"mapped",[5076]],[[43941,43941],"mapped",[5077]],[[43942,43942],"mapped",[5078]],[[43943,43943],"mapped",[5079]],[[43944,43944],"mapped",[5080]],[[43945,43945],"mapped",[5081]],[[43946,43946],"mapped",[5082]],[[43947,43947],"mapped",[5083]],[[43948,43948],"mapped",[5084]],[[43949,43949],"mapped",[5085]],[[43950,43950],"mapped",[5086]],[[43951,43951],"mapped",[5087]],[[43952,43952],"mapped",[5088]],[[43953,43953],"mapped",[5089]],[[43954,43954],"mapped",[5090]],[[43955,43955],"mapped",[5091]],[[43956,43956],"mapped",[5092]],[[43957,43957],"mapped",[5093]],[[43958,43958],"mapped",[5094]],[[43959,43959],"mapped",[5095]],[[43960,43960],"mapped",[5096]],[[43961,43961],"mapped",[5097]],[[43962,43962],"mapped",[5098]],[[43963,43963],"mapped",[5099]],[[43964,43964],"mapped",[5100]],[[43965,43965],"mapped",[5101]],[[43966,43966],"mapped",[5102]],[[43967,43967],"mapped",[5103]],[[43968,44010],"valid"],[[44011,44011],"valid",[],"NV8"],[[44012,44013],"valid"],[[44014,44015],"disallowed"],[[44016,44025],"valid"],[[44026,44031],"disallowed"],[[44032,55203],"valid"],[[55204,55215],"disallowed"],[[55216,55238],"valid",[],"NV8"],[[55239,55242],"disallowed"],[[55243,55291],"valid",[],"NV8"],[[55292,55295],"disallowed"],[[55296,57343],"disallowed"],[[57344,63743],"disallowed"],[[63744,63744],"mapped",[35912]],[[63745,63745],"mapped",[26356]],[[63746,63746],"mapped",[36554]],[[63747,63747],"mapped",[36040]],[[63748,63748],"mapped",[28369]],[[63749,63749],"mapped",[20018]],[[63750,63750],"mapped",[21477]],[[63751,63752],"mapped",[40860]],[[63753,63753],"mapped",[22865]],[[63754,63754],"mapped",[37329]],[[63755,63755],"mapped",[21895]],[[63756,63756],"mapped",[22856]],[[63757,63757],"mapped",[25078]],[[63758,63758],"mapped",[30313]],[[63759,63759],"mapped",[32645]],[[63760,63760],"mapped",[34367]],[[63761,63761],"mapped",[34746]],[[63762,63762],"mapped",[35064]],[[63763,63763],"mapped",[37007]],[[63764,63764],"mapped",[27138]],[[63765,63765],"mapped",[27931]],[[63766,63766],"mapped",[28889]],[[63767,63767],"mapped",[29662]],[[63768,63768],"mapped",[33853]],[[63769,63769],"mapped",[37226]],[[63770,63770],"mapped",[39409]],[[63771,63771],"mapped",[20098]],[[63772,63772],"mapped",[21365]],[[63773,63773],"mapped",[27396]],[[63774,63774],"mapped",[29211]],[[63775,63775],"mapped",[34349]],[[63776,63776],"mapped",[40478]],[[63777,63777],"mapped",[23888]],[[63778,63778],"mapped",[28651]],[[63779,63779],"mapped",[34253]],[[63780,63780],"mapped",[35172]],[[63781,63781],"mapped",[25289]],[[63782,63782],"mapped",[33240]],[[63783,63783],"mapped",[34847]],[[63784,63784],"mapped",[24266]],[[63785,63785],"mapped",[26391]],[[63786,63786],"mapped",[28010]],[[63787,63787],"mapped",[29436]],[[63788,63788],"mapped",[37070]],[[63789,63789],"mapped",[20358]],[[63790,63790],"mapped",[20919]],[[63791,63791],"mapped",[21214]],[[63792,63792],"mapped",[25796]],[[63793,63793],"mapped",[27347]],[[63794,63794],"mapped",[29200]],[[63795,63795],"mapped",[30439]],[[63796,63796],"mapped",[32769]],[[63797,63797],"mapped",[34310]],[[63798,63798],"mapped",[34396]],[[63799,63799],"mapped",[36335]],[[63800,63800],"mapped",[38706]],[[63801,63801],"mapped",[39791]],[[63802,63802],"mapped",[40442]],[[63803,63803],"mapped",[30860]],[[63804,63804],"mapped",[31103]],[[63805,63805],"mapped",[32160]],[[63806,63806],"mapped",[33737]],[[63807,63807],"mapped",[37636]],[[63808,63808],"mapped",[40575]],[[63809,63809],"mapped",[35542]],[[63810,63810],"mapped",[22751]],[[63811,63811],"mapped",[24324]],[[63812,63812],"mapped",[31840]],[[63813,63813],"mapped",[32894]],[[63814,63814],"mapped",[29282]],[[63815,63815],"mapped",[30922]],[[63816,63816],"mapped",[36034]],[[63817,63817],"mapped",[38647]],[[63818,63818],"mapped",[22744]],[[63819,63819],"mapped",[23650]],[[63820,63820],"mapped",[27155]],[[63821,63821],"mapped",[28122]],[[63822,63822],"mapped",[28431]],[[63823,63823],"mapped",[32047]],[[63824,63824],"mapped",[32311]],[[63825,63825],"mapped",[38475]],[[63826,63826],"mapped",[21202]],[[63827,63827],"mapped",[32907]],[[63828,63828],"mapped",[20956]],[[63829,63829],"mapped",[20940]],[[63830,63830],"mapped",[31260]],[[63831,63831],"mapped",[32190]],[[63832,63832],"mapped",[33777]],[[63833,63833],"mapped",[38517]],[[63834,63834],"mapped",[35712]],[[63835,63835],"mapped",[25295]],[[63836,63836],"mapped",[27138]],[[63837,63837],"mapped",[35582]],[[63838,63838],"mapped",[20025]],[[63839,63839],"mapped",[23527]],[[63840,63840],"mapped",[24594]],[[63841,63841],"mapped",[29575]],[[63842,63842],"mapped",[30064]],[[63843,63843],"mapped",[21271]],[[63844,63844],"mapped",[30971]],[[63845,63845],"mapped",[20415]],[[63846,63846],"mapped",[24489]],[[63847,63847],"mapped",[19981]],[[63848,63848],"mapped",[27852]],[[63849,63849],"mapped",[25976]],[[63850,63850],"mapped",[32034]],[[63851,63851],"mapped",[21443]],[[63852,63852],"mapped",[22622]],[[63853,63853],"mapped",[30465]],[[63854,63854],"mapped",[33865]],[[63855,63855],"mapped",[35498]],[[63856,63856],"mapped",[27578]],[[63857,63857],"mapped",[36784]],[[63858,63858],"mapped",[27784]],[[63859,63859],"mapped",[25342]],[[63860,63860],"mapped",[33509]],[[63861,63861],"mapped",[25504]],[[63862,63862],"mapped",[30053]],[[63863,63863],"mapped",[20142]],[[63864,63864],"mapped",[20841]],[[63865,63865],"mapped",[20937]],[[63866,63866],"mapped",[26753]],[[63867,63867],"mapped",[31975]],[[63868,63868],"mapped",[33391]],[[63869,63869],"mapped",[35538]],[[63870,63870],"mapped",[37327]],[[63871,63871],"mapped",[21237]],[[63872,63872],"mapped",[21570]],[[63873,63873],"mapped",[22899]],[[63874,63874],"mapped",[24300]],[[63875,63875],"mapped",[26053]],[[63876,63876],"mapped",[28670]],[[63877,63877],"mapped",[31018]],[[63878,63878],"mapped",[38317]],[[63879,63879],"mapped",[39530]],[[63880,63880],"mapped",[40599]],[[63881,63881],"mapped",[40654]],[[63882,63882],"mapped",[21147]],[[63883,63883],"mapped",[26310]],[[63884,63884],"mapped",[27511]],[[63885,63885],"mapped",[36706]],[[63886,63886],"mapped",[24180]],[[63887,63887],"mapped",[24976]],[[63888,63888],"mapped",[25088]],[[63889,63889],"mapped",[25754]],[[63890,63890],"mapped",[28451]],[[63891,63891],"mapped",[29001]],[[63892,63892],"mapped",[29833]],[[63893,63893],"mapped",[31178]],[[63894,63894],"mapped",[32244]],[[63895,63895],"mapped",[32879]],[[63896,63896],"mapped",[36646]],[[63897,63897],"mapped",[34030]],[[63898,63898],"mapped",[36899]],[[63899,63899],"mapped",[37706]],[[63900,63900],"mapped",[21015]],[[63901,63901],"mapped",[21155]],[[63902,63902],"mapped",[21693]],[[63903,63903],"mapped",[28872]],[[63904,63904],"mapped",[35010]],[[63905,63905],"mapped",[35498]],[[63906,63906],"mapped",[24265]],[[63907,63907],"mapped",[24565]],[[63908,63908],"mapped",[25467]],[[63909,63909],"mapped",[27566]],[[63910,63910],"mapped",[31806]],[[63911,63911],"mapped",[29557]],[[63912,63912],"mapped",[20196]],[[63913,63913],"mapped",[22265]],[[63914,63914],"mapped",[23527]],[[63915,63915],"mapped",[23994]],[[63916,63916],"mapped",[24604]],[[63917,63917],"mapped",[29618]],[[63918,63918],"mapped",[29801]],[[63919,63919],"mapped",[32666]],[[63920,63920],"mapped",[32838]],[[63921,63921],"mapped",[37428]],[[63922,63922],"mapped",[38646]],[[63923,63923],"mapped",[38728]],[[63924,63924],"mapped",[38936]],[[63925,63925],"mapped",[20363]],[[63926,63926],"mapped",[31150]],[[63927,63927],"mapped",[37300]],[[63928,63928],"mapped",[38584]],[[63929,63929],"mapped",[24801]],[[63930,63930],"mapped",[20102]],[[63931,63931],"mapped",[20698]],[[63932,63932],"mapped",[23534]],[[63933,63933],"mapped",[23615]],[[63934,63934],"mapped",[26009]],[[63935,63935],"mapped",[27138]],[[63936,63936],"mapped",[29134]],[[63937,63937],"mapped",[30274]],[[63938,63938],"mapped",[34044]],[[63939,63939],"mapped",[36988]],[[63940,63940],"mapped",[40845]],[[63941,63941],"mapped",[26248]],[[63942,63942],"mapped",[38446]],[[63943,63943],"mapped",[21129]],[[63944,63944],"mapped",[26491]],[[63945,63945],"mapped",[26611]],[[63946,63946],"mapped",[27969]],[[63947,63947],"mapped",[28316]],[[63948,63948],"mapped",[29705]],[[63949,63949],"mapped",[30041]],[[63950,63950],"mapped",[30827]],[[63951,63951],"mapped",[32016]],[[63952,63952],"mapped",[39006]],[[63953,63953],"mapped",[20845]],[[63954,63954],"mapped",[25134]],[[63955,63955],"mapped",[38520]],[[63956,63956],"mapped",[20523]],[[63957,63957],"mapped",[23833]],[[63958,63958],"mapped",[28138]],[[63959,63959],"mapped",[36650]],[[63960,63960],"mapped",[24459]],[[63961,63961],"mapped",[24900]],[[63962,63962],"mapped",[26647]],[[63963,63963],"mapped",[29575]],[[63964,63964],"mapped",[38534]],[[63965,63965],"mapped",[21033]],[[63966,63966],"mapped",[21519]],[[63967,63967],"mapped",[23653]],[[63968,63968],"mapped",[26131]],[[63969,63969],"mapped",[26446]],[[63970,63970],"mapped",[26792]],[[63971,63971],"mapped",[27877]],[[63972,63972],"mapped",[29702]],[[63973,63973],"mapped",[30178]],[[63974,63974],"mapped",[32633]],[[63975,63975],"mapped",[35023]],[[63976,63976],"mapped",[35041]],[[63977,63977],"mapped",[37324]],[[63978,63978],"mapped",[38626]],[[63979,63979],"mapped",[21311]],[[63980,63980],"mapped",[28346]],[[63981,63981],"mapped",[21533]],[[63982,63982],"mapped",[29136]],[[63983,63983],"mapped",[29848]],[[63984,63984],"mapped",[34298]],[[63985,63985],"mapped",[38563]],[[63986,63986],"mapped",[40023]],[[63987,63987],"mapped",[40607]],[[63988,63988],"mapped",[26519]],[[63989,63989],"mapped",[28107]],[[63990,63990],"mapped",[33256]],[[63991,63991],"mapped",[31435]],[[63992,63992],"mapped",[31520]],[[63993,63993],"mapped",[31890]],[[63994,63994],"mapped",[29376]],[[63995,63995],"mapped",[28825]],[[63996,63996],"mapped",[35672]],[[63997,63997],"mapped",[20160]],[[63998,63998],"mapped",[33590]],[[63999,63999],"mapped",[21050]],[[64000,64000],"mapped",[20999]],[[64001,64001],"mapped",[24230]],[[64002,64002],"mapped",[25299]],[[64003,64003],"mapped",[31958]],[[64004,64004],"mapped",[23429]],[[64005,64005],"mapped",[27934]],[[64006,64006],"mapped",[26292]],[[64007,64007],"mapped",[36667]],[[64008,64008],"mapped",[34892]],[[64009,64009],"mapped",[38477]],[[64010,64010],"mapped",[35211]],[[64011,64011],"mapped",[24275]],[[64012,64012],"mapped",[20800]],[[64013,64013],"mapped",[21952]],[[64014,64015],"valid"],[[64016,64016],"mapped",[22618]],[[64017,64017],"valid"],[[64018,64018],"mapped",[26228]],[[64019,64020],"valid"],[[64021,64021],"mapped",[20958]],[[64022,64022],"mapped",[29482]],[[64023,64023],"mapped",[30410]],[[64024,64024],"mapped",[31036]],[[64025,64025],"mapped",[31070]],[[64026,64026],"mapped",[31077]],[[64027,64027],"mapped",[31119]],[[64028,64028],"mapped",[38742]],[[64029,64029],"mapped",[31934]],[[64030,64030],"mapped",[32701]],[[64031,64031],"valid"],[[64032,64032],"mapped",[34322]],[[64033,64033],"valid"],[[64034,64034],"mapped",[35576]],[[64035,64036],"valid"],[[64037,64037],"mapped",[36920]],[[64038,64038],"mapped",[37117]],[[64039,64041],"valid"],[[64042,64042],"mapped",[39151]],[[64043,64043],"mapped",[39164]],[[64044,64044],"mapped",[39208]],[[64045,64045],"mapped",[40372]],[[64046,64046],"mapped",[37086]],[[64047,64047],"mapped",[38583]],[[64048,64048],"mapped",[20398]],[[64049,64049],"mapped",[20711]],[[64050,64050],"mapped",[20813]],[[64051,64051],"mapped",[21193]],[[64052,64052],"mapped",[21220]],[[64053,64053],"mapped",[21329]],[[64054,64054],"mapped",[21917]],[[64055,64055],"mapped",[22022]],[[64056,64056],"mapped",[22120]],[[64057,64057],"mapped",[22592]],[[64058,64058],"mapped",[22696]],[[64059,64059],"mapped",[23652]],[[64060,64060],"mapped",[23662]],[[64061,64061],"mapped",[24724]],[[64062,64062],"mapped",[24936]],[[64063,64063],"mapped",[24974]],[[64064,64064],"mapped",[25074]],[[64065,64065],"mapped",[25935]],[[64066,64066],"mapped",[26082]],[[64067,64067],"mapped",[26257]],[[64068,64068],"mapped",[26757]],[[64069,64069],"mapped",[28023]],[[64070,64070],"mapped",[28186]],[[64071,64071],"mapped",[28450]],[[64072,64072],"mapped",[29038]],[[64073,64073],"mapped",[29227]],[[64074,64074],"mapped",[29730]],[[64075,64075],"mapped",[30865]],[[64076,64076],"mapped",[31038]],[[64077,64077],"mapped",[31049]],[[64078,64078],"mapped",[31048]],[[64079,64079],"mapped",[31056]],[[64080,64080],"mapped",[31062]],[[64081,64081],"mapped",[31069]],[[64082,64082],"mapped",[31117]],[[64083,64083],"mapped",[31118]],[[64084,64084],"mapped",[31296]],[[64085,64085],"mapped",[31361]],[[64086,64086],"mapped",[31680]],[[64087,64087],"mapped",[32244]],[[64088,64088],"mapped",[32265]],[[64089,64089],"mapped",[32321]],[[64090,64090],"mapped",[32626]],[[64091,64091],"mapped",[32773]],[[64092,64092],"mapped",[33261]],[[64093,64094],"mapped",[33401]],[[64095,64095],"mapped",[33879]],[[64096,64096],"mapped",[35088]],[[64097,64097],"mapped",[35222]],[[64098,64098],"mapped",[35585]],[[64099,64099],"mapped",[35641]],[[64100,64100],"mapped",[36051]],[[64101,64101],"mapped",[36104]],[[64102,64102],"mapped",[36790]],[[64103,64103],"mapped",[36920]],[[64104,64104],"mapped",[38627]],[[64105,64105],"mapped",[38911]],[[64106,64106],"mapped",[38971]],[[64107,64107],"mapped",[24693]],[[64108,64108],"mapped",[148206]],[[64109,64109],"mapped",[33304]],[[64110,64111],"disallowed"],[[64112,64112],"mapped",[20006]],[[64113,64113],"mapped",[20917]],[[64114,64114],"mapped",[20840]],[[64115,64115],"mapped",[20352]],[[64116,64116],"mapped",[20805]],[[64117,64117],"mapped",[20864]],[[64118,64118],"mapped",[21191]],[[64119,64119],"mapped",[21242]],[[64120,64120],"mapped",[21917]],[[64121,64121],"mapped",[21845]],[[64122,64122],"mapped",[21913]],[[64123,64123],"mapped",[21986]],[[64124,64124],"mapped",[22618]],[[64125,64125],"mapped",[22707]],[[64126,64126],"mapped",[22852]],[[64127,64127],"mapped",[22868]],[[64128,64128],"mapped",[23138]],[[64129,64129],"mapped",[23336]],[[64130,64130],"mapped",[24274]],[[64131,64131],"mapped",[24281]],[[64132,64132],"mapped",[24425]],[[64133,64133],"mapped",[24493]],[[64134,64134],"mapped",[24792]],[[64135,64135],"mapped",[24910]],[[64136,64136],"mapped",[24840]],[[64137,64137],"mapped",[24974]],[[64138,64138],"mapped",[24928]],[[64139,64139],"mapped",[25074]],[[64140,64140],"mapped",[25140]],[[64141,64141],"mapped",[25540]],[[64142,64142],"mapped",[25628]],[[64143,64143],"mapped",[25682]],[[64144,64144],"mapped",[25942]],[[64145,64145],"mapped",[26228]],[[64146,64146],"mapped",[26391]],[[64147,64147],"mapped",[26395]],[[64148,64148],"mapped",[26454]],[[64149,64149],"mapped",[27513]],[[64150,64150],"mapped",[27578]],[[64151,64151],"mapped",[27969]],[[64152,64152],"mapped",[28379]],[[64153,64153],"mapped",[28363]],[[64154,64154],"mapped",[28450]],[[64155,64155],"mapped",[28702]],[[64156,64156],"mapped",[29038]],[[64157,64157],"mapped",[30631]],[[64158,64158],"mapped",[29237]],[[64159,64159],"mapped",[29359]],[[64160,64160],"mapped",[29482]],[[64161,64161],"mapped",[29809]],[[64162,64162],"mapped",[29958]],[[64163,64163],"mapped",[30011]],[[64164,64164],"mapped",[30237]],[[64165,64165],"mapped",[30239]],[[64166,64166],"mapped",[30410]],[[64167,64167],"mapped",[30427]],[[64168,64168],"mapped",[30452]],[[64169,64169],"mapped",[30538]],[[64170,64170],"mapped",[30528]],[[64171,64171],"mapped",[30924]],[[64172,64172],"mapped",[31409]],[[64173,64173],"mapped",[31680]],[[64174,64174],"mapped",[31867]],[[64175,64175],"mapped",[32091]],[[64176,64176],"mapped",[32244]],[[64177,64177],"mapped",[32574]],[[64178,64178],"mapped",[32773]],[[64179,64179],"mapped",[33618]],[[64180,64180],"mapped",[33775]],[[64181,64181],"mapped",[34681]],[[64182,64182],"mapped",[35137]],[[64183,64183],"mapped",[35206]],[[64184,64184],"mapped",[35222]],[[64185,64185],"mapped",[35519]],[[64186,64186],"mapped",[35576]],[[64187,64187],"mapped",[35531]],[[64188,64188],"mapped",[35585]],[[64189,64189],"mapped",[35582]],[[64190,64190],"mapped",[35565]],[[64191,64191],"mapped",[35641]],[[64192,64192],"mapped",[35722]],[[64193,64193],"mapped",[36104]],[[64194,64194],"mapped",[36664]],[[64195,64195],"mapped",[36978]],[[64196,64196],"mapped",[37273]],[[64197,64197],"mapped",[37494]],[[64198,64198],"mapped",[38524]],[[64199,64199],"mapped",[38627]],[[64200,64200],"mapped",[38742]],[[64201,64201],"mapped",[38875]],[[64202,64202],"mapped",[38911]],[[64203,64203],"mapped",[38923]],[[64204,64204],"mapped",[38971]],[[64205,64205],"mapped",[39698]],[[64206,64206],"mapped",[40860]],[[64207,64207],"mapped",[141386]],[[64208,64208],"mapped",[141380]],[[64209,64209],"mapped",[144341]],[[64210,64210],"mapped",[15261]],[[64211,64211],"mapped",[16408]],[[64212,64212],"mapped",[16441]],[[64213,64213],"mapped",[152137]],[[64214,64214],"mapped",[154832]],[[64215,64215],"mapped",[163539]],[[64216,64216],"mapped",[40771]],[[64217,64217],"mapped",[40846]],[[64218,64255],"disallowed"],[[64256,64256],"mapped",[102,102]],[[64257,64257],"mapped",[102,105]],[[64258,64258],"mapped",[102,108]],[[64259,64259],"mapped",[102,102,105]],[[64260,64260],"mapped",[102,102,108]],[[64261,64262],"mapped",[115,116]],[[64263,64274],"disallowed"],[[64275,64275],"mapped",[1396,1398]],[[64276,64276],"mapped",[1396,1381]],[[64277,64277],"mapped",[1396,1387]],[[64278,64278],"mapped",[1406,1398]],[[64279,64279],"mapped",[1396,1389]],[[64280,64284],"disallowed"],[[64285,64285],"mapped",[1497,1460]],[[64286,64286],"valid"],[[64287,64287],"mapped",[1522,1463]],[[64288,64288],"mapped",[1506]],[[64289,64289],"mapped",[1488]],[[64290,64290],"mapped",[1491]],[[64291,64291],"mapped",[1492]],[[64292,64292],"mapped",[1499]],[[64293,64293],"mapped",[1500]],[[64294,64294],"mapped",[1501]],[[64295,64295],"mapped",[1512]],[[64296,64296],"mapped",[1514]],[[64297,64297],"disallowed_STD3_mapped",[43]],[[64298,64298],"mapped",[1513,1473]],[[64299,64299],"mapped",[1513,1474]],[[64300,64300],"mapped",[1513,1468,1473]],[[64301,64301],"mapped",[1513,1468,1474]],[[64302,64302],"mapped",[1488,1463]],[[64303,64303],"mapped",[1488,1464]],[[64304,64304],"mapped",[1488,1468]],[[64305,64305],"mapped",[1489,1468]],[[64306,64306],"mapped",[1490,1468]],[[64307,64307],"mapped",[1491,1468]],[[64308,64308],"mapped",[1492,1468]],[[64309,64309],"mapped",[1493,1468]],[[64310,64310],"mapped",[1494,1468]],[[64311,64311],"disallowed"],[[64312,64312],"mapped",[1496,1468]],[[64313,64313],"mapped",[1497,1468]],[[64314,64314],"mapped",[1498,1468]],[[64315,64315],"mapped",[1499,1468]],[[64316,64316],"mapped",[1500,1468]],[[64317,64317],"disallowed"],[[64318,64318],"mapped",[1502,1468]],[[64319,64319],"disallowed"],[[64320,64320],"mapped",[1504,1468]],[[64321,64321],"mapped",[1505,1468]],[[64322,64322],"disallowed"],[[64323,64323],"mapped",[1507,1468]],[[64324,64324],"mapped",[1508,1468]],[[64325,64325],"disallowed"],[[64326,64326],"mapped",[1510,1468]],[[64327,64327],"mapped",[1511,1468]],[[64328,64328],"mapped",[1512,1468]],[[64329,64329],"mapped",[1513,1468]],[[64330,64330],"mapped",[1514,1468]],[[64331,64331],"mapped",[1493,1465]],[[64332,64332],"mapped",[1489,1471]],[[64333,64333],"mapped",[1499,1471]],[[64334,64334],"mapped",[1508,1471]],[[64335,64335],"mapped",[1488,1500]],[[64336,64337],"mapped",[1649]],[[64338,64341],"mapped",[1659]],[[64342,64345],"mapped",[1662]],[[64346,64349],"mapped",[1664]],[[64350,64353],"mapped",[1658]],[[64354,64357],"mapped",[1663]],[[64358,64361],"mapped",[1657]],[[64362,64365],"mapped",[1700]],[[64366,64369],"mapped",[1702]],[[64370,64373],"mapped",[1668]],[[64374,64377],"mapped",[1667]],[[64378,64381],"mapped",[1670]],[[64382,64385],"mapped",[1671]],[[64386,64387],"mapped",[1677]],[[64388,64389],"mapped",[1676]],[[64390,64391],"mapped",[1678]],[[64392,64393],"mapped",[1672]],[[64394,64395],"mapped",[1688]],[[64396,64397],"mapped",[1681]],[[64398,64401],"mapped",[1705]],[[64402,64405],"mapped",[1711]],[[64406,64409],"mapped",[1715]],[[64410,64413],"mapped",[1713]],[[64414,64415],"mapped",[1722]],[[64416,64419],"mapped",[1723]],[[64420,64421],"mapped",[1728]],[[64422,64425],"mapped",[1729]],[[64426,64429],"mapped",[1726]],[[64430,64431],"mapped",[1746]],[[64432,64433],"mapped",[1747]],[[64434,64449],"valid",[],"NV8"],[[64450,64466],"disallowed"],[[64467,64470],"mapped",[1709]],[[64471,64472],"mapped",[1735]],[[64473,64474],"mapped",[1734]],[[64475,64476],"mapped",[1736]],[[64477,64477],"mapped",[1735,1652]],[[64478,64479],"mapped",[1739]],[[64480,64481],"mapped",[1733]],[[64482,64483],"mapped",[1737]],[[64484,64487],"mapped",[1744]],[[64488,64489],"mapped",[1609]],[[64490,64491],"mapped",[1574,1575]],[[64492,64493],"mapped",[1574,1749]],[[64494,64495],"mapped",[1574,1608]],[[64496,64497],"mapped",[1574,1735]],[[64498,64499],"mapped",[1574,1734]],[[64500,64501],"mapped",[1574,1736]],[[64502,64504],"mapped",[1574,1744]],[[64505,64507],"mapped",[1574,1609]],[[64508,64511],"mapped",[1740]],[[64512,64512],"mapped",[1574,1580]],[[64513,64513],"mapped",[1574,1581]],[[64514,64514],"mapped",[1574,1605]],[[64515,64515],"mapped",[1574,1609]],[[64516,64516],"mapped",[1574,1610]],[[64517,64517],"mapped",[1576,1580]],[[64518,64518],"mapped",[1576,1581]],[[64519,64519],"mapped",[1576,1582]],[[64520,64520],"mapped",[1576,1605]],[[64521,64521],"mapped",[1576,1609]],[[64522,64522],"mapped",[1576,1610]],[[64523,64523],"mapped",[1578,1580]],[[64524,64524],"mapped",[1578,1581]],[[64525,64525],"mapped",[1578,1582]],[[64526,64526],"mapped",[1578,1605]],[[64527,64527],"mapped",[1578,1609]],[[64528,64528],"mapped",[1578,1610]],[[64529,64529],"mapped",[1579,1580]],[[64530,64530],"mapped",[1579,1605]],[[64531,64531],"mapped",[1579,1609]],[[64532,64532],"mapped",[1579,1610]],[[64533,64533],"mapped",[1580,1581]],[[64534,64534],"mapped",[1580,1605]],[[64535,64535],"mapped",[1581,1580]],[[64536,64536],"mapped",[1581,1605]],[[64537,64537],"mapped",[1582,1580]],[[64538,64538],"mapped",[1582,1581]],[[64539,64539],"mapped",[1582,1605]],[[64540,64540],"mapped",[1587,1580]],[[64541,64541],"mapped",[1587,1581]],[[64542,64542],"mapped",[1587,1582]],[[64543,64543],"mapped",[1587,1605]],[[64544,64544],"mapped",[1589,1581]],[[64545,64545],"mapped",[1589,1605]],[[64546,64546],"mapped",[1590,1580]],[[64547,64547],"mapped",[1590,1581]],[[64548,64548],"mapped",[1590,1582]],[[64549,64549],"mapped",[1590,1605]],[[64550,64550],"mapped",[1591,1581]],[[64551,64551],"mapped",[1591,1605]],[[64552,64552],"mapped",[1592,1605]],[[64553,64553],"mapped",[1593,1580]],[[64554,64554],"mapped",[1593,1605]],[[64555,64555],"mapped",[1594,1580]],[[64556,64556],"mapped",[1594,1605]],[[64557,64557],"mapped",[1601,1580]],[[64558,64558],"mapped",[1601,1581]],[[64559,64559],"mapped",[1601,1582]],[[64560,64560],"mapped",[1601,1605]],[[64561,64561],"mapped",[1601,1609]],[[64562,64562],"mapped",[1601,1610]],[[64563,64563],"mapped",[1602,1581]],[[64564,64564],"mapped",[1602,1605]],[[64565,64565],"mapped",[1602,1609]],[[64566,64566],"mapped",[1602,1610]],[[64567,64567],"mapped",[1603,1575]],[[64568,64568],"mapped",[1603,1580]],[[64569,64569],"mapped",[1603,1581]],[[64570,64570],"mapped",[1603,1582]],[[64571,64571],"mapped",[1603,1604]],[[64572,64572],"mapped",[1603,1605]],[[64573,64573],"mapped",[1603,1609]],[[64574,64574],"mapped",[1603,1610]],[[64575,64575],"mapped",[1604,1580]],[[64576,64576],"mapped",[1604,1581]],[[64577,64577],"mapped",[1604,1582]],[[64578,64578],"mapped",[1604,1605]],[[64579,64579],"mapped",[1604,1609]],[[64580,64580],"mapped",[1604,1610]],[[64581,64581],"mapped",[1605,1580]],[[64582,64582],"mapped",[1605,1581]],[[64583,64583],"mapped",[1605,1582]],[[64584,64584],"mapped",[1605,1605]],[[64585,64585],"mapped",[1605,1609]],[[64586,64586],"mapped",[1605,1610]],[[64587,64587],"mapped",[1606,1580]],[[64588,64588],"mapped",[1606,1581]],[[64589,64589],"mapped",[1606,1582]],[[64590,64590],"mapped",[1606,1605]],[[64591,64591],"mapped",[1606,1609]],[[64592,64592],"mapped",[1606,1610]],[[64593,64593],"mapped",[1607,1580]],[[64594,64594],"mapped",[1607,1605]],[[64595,64595],"mapped",[1607,1609]],[[64596,64596],"mapped",[1607,1610]],[[64597,64597],"mapped",[1610,1580]],[[64598,64598],"mapped",[1610,1581]],[[64599,64599],"mapped",[1610,1582]],[[64600,64600],"mapped",[1610,1605]],[[64601,64601],"mapped",[1610,1609]],[[64602,64602],"mapped",[1610,1610]],[[64603,64603],"mapped",[1584,1648]],[[64604,64604],"mapped",[1585,1648]],[[64605,64605],"mapped",[1609,1648]],[[64606,64606],"disallowed_STD3_mapped",[32,1612,1617]],[[64607,64607],"disallowed_STD3_mapped",[32,1613,1617]],[[64608,64608],"disallowed_STD3_mapped",[32,1614,1617]],[[64609,64609],"disallowed_STD3_mapped",[32,1615,1617]],[[64610,64610],"disallowed_STD3_mapped",[32,1616,1617]],[[64611,64611],"disallowed_STD3_mapped",[32,1617,1648]],[[64612,64612],"mapped",[1574,1585]],[[64613,64613],"mapped",[1574,1586]],[[64614,64614],"mapped",[1574,1605]],[[64615,64615],"mapped",[1574,1606]],[[64616,64616],"mapped",[1574,1609]],[[64617,64617],"mapped",[1574,1610]],[[64618,64618],"mapped",[1576,1585]],[[64619,64619],"mapped",[1576,1586]],[[64620,64620],"mapped",[1576,1605]],[[64621,64621],"mapped",[1576,1606]],[[64622,64622],"mapped",[1576,1609]],[[64623,64623],"mapped",[1576,1610]],[[64624,64624],"mapped",[1578,1585]],[[64625,64625],"mapped",[1578,1586]],[[64626,64626],"mapped",[1578,1605]],[[64627,64627],"mapped",[1578,1606]],[[64628,64628],"mapped",[1578,1609]],[[64629,64629],"mapped",[1578,1610]],[[64630,64630],"mapped",[1579,1585]],[[64631,64631],"mapped",[1579,1586]],[[64632,64632],"mapped",[1579,1605]],[[64633,64633],"mapped",[1579,1606]],[[64634,64634],"mapped",[1579,1609]],[[64635,64635],"mapped",[1579,1610]],[[64636,64636],"mapped",[1601,1609]],[[64637,64637],"mapped",[1601,1610]],[[64638,64638],"mapped",[1602,1609]],[[64639,64639],"mapped",[1602,1610]],[[64640,64640],"mapped",[1603,1575]],[[64641,64641],"mapped",[1603,1604]],[[64642,64642],"mapped",[1603,1605]],[[64643,64643],"mapped",[1603,1609]],[[64644,64644],"mapped",[1603,1610]],[[64645,64645],"mapped",[1604,1605]],[[64646,64646],"mapped",[1604,1609]],[[64647,64647],"mapped",[1604,1610]],[[64648,64648],"mapped",[1605,1575]],[[64649,64649],"mapped",[1605,1605]],[[64650,64650],"mapped",[1606,1585]],[[64651,64651],"mapped",[1606,1586]],[[64652,64652],"mapped",[1606,1605]],[[64653,64653],"mapped",[1606,1606]],[[64654,64654],"mapped",[1606,1609]],[[64655,64655],"mapped",[1606,1610]],[[64656,64656],"mapped",[1609,1648]],[[64657,64657],"mapped",[1610,1585]],[[64658,64658],"mapped",[1610,1586]],[[64659,64659],"mapped",[1610,1605]],[[64660,64660],"mapped",[1610,1606]],[[64661,64661],"mapped",[1610,1609]],[[64662,64662],"mapped",[1610,1610]],[[64663,64663],"mapped",[1574,1580]],[[64664,64664],"mapped",[1574,1581]],[[64665,64665],"mapped",[1574,1582]],[[64666,64666],"mapped",[1574,1605]],[[64667,64667],"mapped",[1574,1607]],[[64668,64668],"mapped",[1576,1580]],[[64669,64669],"mapped",[1576,1581]],[[64670,64670],"mapped",[1576,1582]],[[64671,64671],"mapped",[1576,1605]],[[64672,64672],"mapped",[1576,1607]],[[64673,64673],"mapped",[1578,1580]],[[64674,64674],"mapped",[1578,1581]],[[64675,64675],"mapped",[1578,1582]],[[64676,64676],"mapped",[1578,1605]],[[64677,64677],"mapped",[1578,1607]],[[64678,64678],"mapped",[1579,1605]],[[64679,64679],"mapped",[1580,1581]],[[64680,64680],"mapped",[1580,1605]],[[64681,64681],"mapped",[1581,1580]],[[64682,64682],"mapped",[1581,1605]],[[64683,64683],"mapped",[1582,1580]],[[64684,64684],"mapped",[1582,1605]],[[64685,64685],"mapped",[1587,1580]],[[64686,64686],"mapped",[1587,1581]],[[64687,64687],"mapped",[1587,1582]],[[64688,64688],"mapped",[1587,1605]],[[64689,64689],"mapped",[1589,1581]],[[64690,64690],"mapped",[1589,1582]],[[64691,64691],"mapped",[1589,1605]],[[64692,64692],"mapped",[1590,1580]],[[64693,64693],"mapped",[1590,1581]],[[64694,64694],"mapped",[1590,1582]],[[64695,64695],"mapped",[1590,1605]],[[64696,64696],"mapped",[1591,1581]],[[64697,64697],"mapped",[1592,1605]],[[64698,64698],"mapped",[1593,1580]],[[64699,64699],"mapped",[1593,1605]],[[64700,64700],"mapped",[1594,1580]],[[64701,64701],"mapped",[1594,1605]],[[64702,64702],"mapped",[1601,1580]],[[64703,64703],"mapped",[1601,1581]],[[64704,64704],"mapped",[1601,1582]],[[64705,64705],"mapped",[1601,1605]],[[64706,64706],"mapped",[1602,1581]],[[64707,64707],"mapped",[1602,1605]],[[64708,64708],"mapped",[1603,1580]],[[64709,64709],"mapped",[1603,1581]],[[64710,64710],"mapped",[1603,1582]],[[64711,64711],"mapped",[1603,1604]],[[64712,64712],"mapped",[1603,1605]],[[64713,64713],"mapped",[1604,1580]],[[64714,64714],"mapped",[1604,1581]],[[64715,64715],"mapped",[1604,1582]],[[64716,64716],"mapped",[1604,1605]],[[64717,64717],"mapped",[1604,1607]],[[64718,64718],"mapped",[1605,1580]],[[64719,64719],"mapped",[1605,1581]],[[64720,64720],"mapped",[1605,1582]],[[64721,64721],"mapped",[1605,1605]],[[64722,64722],"mapped",[1606,1580]],[[64723,64723],"mapped",[1606,1581]],[[64724,64724],"mapped",[1606,1582]],[[64725,64725],"mapped",[1606,1605]],[[64726,64726],"mapped",[1606,1607]],[[64727,64727],"mapped",[1607,1580]],[[64728,64728],"mapped",[1607,1605]],[[64729,64729],"mapped",[1607,1648]],[[64730,64730],"mapped",[1610,1580]],[[64731,64731],"mapped",[1610,1581]],[[64732,64732],"mapped",[1610,1582]],[[64733,64733],"mapped",[1610,1605]],[[64734,64734],"mapped",[1610,1607]],[[64735,64735],"mapped",[1574,1605]],[[64736,64736],"mapped",[1574,1607]],[[64737,64737],"mapped",[1576,1605]],[[64738,64738],"mapped",[1576,1607]],[[64739,64739],"mapped",[1578,1605]],[[64740,64740],"mapped",[1578,1607]],[[64741,64741],"mapped",[1579,1605]],[[64742,64742],"mapped",[1579,1607]],[[64743,64743],"mapped",[1587,1605]],[[64744,64744],"mapped",[1587,1607]],[[64745,64745],"mapped",[1588,1605]],[[64746,64746],"mapped",[1588,1607]],[[64747,64747],"mapped",[1603,1604]],[[64748,64748],"mapped",[1603,1605]],[[64749,64749],"mapped",[1604,1605]],[[64750,64750],"mapped",[1606,1605]],[[64751,64751],"mapped",[1606,1607]],[[64752,64752],"mapped",[1610,1605]],[[64753,64753],"mapped",[1610,1607]],[[64754,64754],"mapped",[1600,1614,1617]],[[64755,64755],"mapped",[1600,1615,1617]],[[64756,64756],"mapped",[1600,1616,1617]],[[64757,64757],"mapped",[1591,1609]],[[64758,64758],"mapped",[1591,1610]],[[64759,64759],"mapped",[1593,1609]],[[64760,64760],"mapped",[1593,1610]],[[64761,64761],"mapped",[1594,1609]],[[64762,64762],"mapped",[1594,1610]],[[64763,64763],"mapped",[1587,1609]],[[64764,64764],"mapped",[1587,1610]],[[64765,64765],"mapped",[1588,1609]],[[64766,64766],"mapped",[1588,1610]],[[64767,64767],"mapped",[1581,1609]],[[64768,64768],"mapped",[1581,1610]],[[64769,64769],"mapped",[1580,1609]],[[64770,64770],"mapped",[1580,1610]],[[64771,64771],"mapped",[1582,1609]],[[64772,64772],"mapped",[1582,1610]],[[64773,64773],"mapped",[1589,1609]],[[64774,64774],"mapped",[1589,1610]],[[64775,64775],"mapped",[1590,1609]],[[64776,64776],"mapped",[1590,1610]],[[64777,64777],"mapped",[1588,1580]],[[64778,64778],"mapped",[1588,1581]],[[64779,64779],"mapped",[1588,1582]],[[64780,64780],"mapped",[1588,1605]],[[64781,64781],"mapped",[1588,1585]],[[64782,64782],"mapped",[1587,1585]],[[64783,64783],"mapped",[1589,1585]],[[64784,64784],"mapped",[1590,1585]],[[64785,64785],"mapped",[1591,1609]],[[64786,64786],"mapped",[1591,1610]],[[64787,64787],"mapped",[1593,1609]],[[64788,64788],"mapped",[1593,1610]],[[64789,64789],"mapped",[1594,1609]],[[64790,64790],"mapped",[1594,1610]],[[64791,64791],"mapped",[1587,1609]],[[64792,64792],"mapped",[1587,1610]],[[64793,64793],"mapped",[1588,1609]],[[64794,64794],"mapped",[1588,1610]],[[64795,64795],"mapped",[1581,1609]],[[64796,64796],"mapped",[1581,1610]],[[64797,64797],"mapped",[1580,1609]],[[64798,64798],"mapped",[1580,1610]],[[64799,64799],"mapped",[1582,1609]],[[64800,64800],"mapped",[1582,1610]],[[64801,64801],"mapped",[1589,1609]],[[64802,64802],"mapped",[1589,1610]],[[64803,64803],"mapped",[1590,1609]],[[64804,64804],"mapped",[1590,1610]],[[64805,64805],"mapped",[1588,1580]],[[64806,64806],"mapped",[1588,1581]],[[64807,64807],"mapped",[1588,1582]],[[64808,64808],"mapped",[1588,1605]],[[64809,64809],"mapped",[1588,1585]],[[64810,64810],"mapped",[1587,1585]],[[64811,64811],"mapped",[1589,1585]],[[64812,64812],"mapped",[1590,1585]],[[64813,64813],"mapped",[1588,1580]],[[64814,64814],"mapped",[1588,1581]],[[64815,64815],"mapped",[1588,1582]],[[64816,64816],"mapped",[1588,1605]],[[64817,64817],"mapped",[1587,1607]],[[64818,64818],"mapped",[1588,1607]],[[64819,64819],"mapped",[1591,1605]],[[64820,64820],"mapped",[1587,1580]],[[64821,64821],"mapped",[1587,1581]],[[64822,64822],"mapped",[1587,1582]],[[64823,64823],"mapped",[1588,1580]],[[64824,64824],"mapped",[1588,1581]],[[64825,64825],"mapped",[1588,1582]],[[64826,64826],"mapped",[1591,1605]],[[64827,64827],"mapped",[1592,1605]],[[64828,64829],"mapped",[1575,1611]],[[64830,64831],"valid",[],"NV8"],[[64832,64847],"disallowed"],[[64848,64848],"mapped",[1578,1580,1605]],[[64849,64850],"mapped",[1578,1581,1580]],[[64851,64851],"mapped",[1578,1581,1605]],[[64852,64852],"mapped",[1578,1582,1605]],[[64853,64853],"mapped",[1578,1605,1580]],[[64854,64854],"mapped",[1578,1605,1581]],[[64855,64855],"mapped",[1578,1605,1582]],[[64856,64857],"mapped",[1580,1605,1581]],[[64858,64858],"mapped",[1581,1605,1610]],[[64859,64859],"mapped",[1581,1605,1609]],[[64860,64860],"mapped",[1587,1581,1580]],[[64861,64861],"mapped",[1587,1580,1581]],[[64862,64862],"mapped",[1587,1580,1609]],[[64863,64864],"mapped",[1587,1605,1581]],[[64865,64865],"mapped",[1587,1605,1580]],[[64866,64867],"mapped",[1587,1605,1605]],[[64868,64869],"mapped",[1589,1581,1581]],[[64870,64870],"mapped",[1589,1605,1605]],[[64871,64872],"mapped",[1588,1581,1605]],[[64873,64873],"mapped",[1588,1580,1610]],[[64874,64875],"mapped",[1588,1605,1582]],[[64876,64877],"mapped",[1588,1605,1605]],[[64878,64878],"mapped",[1590,1581,1609]],[[64879,64880],"mapped",[1590,1582,1605]],[[64881,64882],"mapped",[1591,1605,1581]],[[64883,64883],"mapped",[1591,1605,1605]],[[64884,64884],"mapped",[1591,1605,1610]],[[64885,64885],"mapped",[1593,1580,1605]],[[64886,64887],"mapped",[1593,1605,1605]],[[64888,64888],"mapped",[1593,1605,1609]],[[64889,64889],"mapped",[1594,1605,1605]],[[64890,64890],"mapped",[1594,1605,1610]],[[64891,64891],"mapped",[1594,1605,1609]],[[64892,64893],"mapped",[1601,1582,1605]],[[64894,64894],"mapped",[1602,1605,1581]],[[64895,64895],"mapped",[1602,1605,1605]],[[64896,64896],"mapped",[1604,1581,1605]],[[64897,64897],"mapped",[1604,1581,1610]],[[64898,64898],"mapped",[1604,1581,1609]],[[64899,64900],"mapped",[1604,1580,1580]],[[64901,64902],"mapped",[1604,1582,1605]],[[64903,64904],"mapped",[1604,1605,1581]],[[64905,64905],"mapped",[1605,1581,1580]],[[64906,64906],"mapped",[1605,1581,1605]],[[64907,64907],"mapped",[1605,1581,1610]],[[64908,64908],"mapped",[1605,1580,1581]],[[64909,64909],"mapped",[1605,1580,1605]],[[64910,64910],"mapped",[1605,1582,1580]],[[64911,64911],"mapped",[1605,1582,1605]],[[64912,64913],"disallowed"],[[64914,64914],"mapped",[1605,1580,1582]],[[64915,64915],"mapped",[1607,1605,1580]],[[64916,64916],"mapped",[1607,1605,1605]],[[64917,64917],"mapped",[1606,1581,1605]],[[64918,64918],"mapped",[1606,1581,1609]],[[64919,64920],"mapped",[1606,1580,1605]],[[64921,64921],"mapped",[1606,1580,1609]],[[64922,64922],"mapped",[1606,1605,1610]],[[64923,64923],"mapped",[1606,1605,1609]],[[64924,64925],"mapped",[1610,1605,1605]],[[64926,64926],"mapped",[1576,1582,1610]],[[64927,64927],"mapped",[1578,1580,1610]],[[64928,64928],"mapped",[1578,1580,1609]],[[64929,64929],"mapped",[1578,1582,1610]],[[64930,64930],"mapped",[1578,1582,1609]],[[64931,64931],"mapped",[1578,1605,1610]],[[64932,64932],"mapped",[1578,1605,1609]],[[64933,64933],"mapped",[1580,1605,1610]],[[64934,64934],"mapped",[1580,1581,1609]],[[64935,64935],"mapped",[1580,1605,1609]],[[64936,64936],"mapped",[1587,1582,1609]],[[64937,64937],"mapped",[1589,1581,1610]],[[64938,64938],"mapped",[1588,1581,1610]],[[64939,64939],"mapped",[1590,1581,1610]],[[64940,64940],"mapped",[1604,1580,1610]],[[64941,64941],"mapped",[1604,1605,1610]],[[64942,64942],"mapped",[1610,1581,1610]],[[64943,64943],"mapped",[1610,1580,1610]],[[64944,64944],"mapped",[1610,1605,1610]],[[64945,64945],"mapped",[1605,1605,1610]],[[64946,64946],"mapped",[1602,1605,1610]],[[64947,64947],"mapped",[1606,1581,1610]],[[64948,64948],"mapped",[1602,1605,1581]],[[64949,64949],"mapped",[1604,1581,1605]],[[64950,64950],"mapped",[1593,1605,1610]],[[64951,64951],"mapped",[1603,1605,1610]],[[64952,64952],"mapped",[1606,1580,1581]],[[64953,64953],"mapped",[1605,1582,1610]],[[64954,64954],"mapped",[1604,1580,1605]],[[64955,64955],"mapped",[1603,1605,1605]],[[64956,64956],"mapped",[1604,1580,1605]],[[64957,64957],"mapped",[1606,1580,1581]],[[64958,64958],"mapped",[1580,1581,1610]],[[64959,64959],"mapped",[1581,1580,1610]],[[64960,64960],"mapped",[1605,1580,1610]],[[64961,64961],"mapped",[1601,1605,1610]],[[64962,64962],"mapped",[1576,1581,1610]],[[64963,64963],"mapped",[1603,1605,1605]],[[64964,64964],"mapped",[1593,1580,1605]],[[64965,64965],"mapped",[1589,1605,1605]],[[64966,64966],"mapped",[1587,1582,1610]],[[64967,64967],"mapped",[1606,1580,1610]],[[64968,64975],"disallowed"],[[64976,65007],"disallowed"],[[65008,65008],"mapped",[1589,1604,1746]],[[65009,65009],"mapped",[1602,1604,1746]],[[65010,65010],"mapped",[1575,1604,1604,1607]],[[65011,65011],"mapped",[1575,1603,1576,1585]],[[65012,65012],"mapped",[1605,1581,1605,1583]],[[65013,65013],"mapped",[1589,1604,1593,1605]],[[65014,65014],"mapped",[1585,1587,1608,1604]],[[65015,65015],"mapped",[1593,1604,1610,1607]],[[65016,65016],"mapped",[1608,1587,1604,1605]],[[65017,65017],"mapped",[1589,1604,1609]],[[65018,65018],"disallowed_STD3_mapped",[1589,1604,1609,32,1575,1604,1604,1607,32,1593,1604,1610,1607,32,1608,1587,1604,1605]],[[65019,65019],"disallowed_STD3_mapped",[1580,1604,32,1580,1604,1575,1604,1607]],[[65020,65020],"mapped",[1585,1740,1575,1604]],[[65021,65021],"valid",[],"NV8"],[[65022,65023],"disallowed"],[[65024,65039],"ignored"],[[65040,65040],"disallowed_STD3_mapped",[44]],[[65041,65041],"mapped",[12289]],[[65042,65042],"disallowed"],[[65043,65043],"disallowed_STD3_mapped",[58]],[[65044,65044],"disallowed_STD3_mapped",[59]],[[65045,65045],"disallowed_STD3_mapped",[33]],[[65046,65046],"disallowed_STD3_mapped",[63]],[[65047,65047],"mapped",[12310]],[[65048,65048],"mapped",[12311]],[[65049,65049],"disallowed"],[[65050,65055],"disallowed"],[[65056,65059],"valid"],[[65060,65062],"valid"],[[65063,65069],"valid"],[[65070,65071],"valid"],[[65072,65072],"disallowed"],[[65073,65073],"mapped",[8212]],[[65074,65074],"mapped",[8211]],[[65075,65076],"disallowed_STD3_mapped",[95]],[[65077,65077],"disallowed_STD3_mapped",[40]],[[65078,65078],"disallowed_STD3_mapped",[41]],[[65079,65079],"disallowed_STD3_mapped",[123]],[[65080,65080],"disallowed_STD3_mapped",[125]],[[65081,65081],"mapped",[12308]],[[65082,65082],"mapped",[12309]],[[65083,65083],"mapped",[12304]],[[65084,65084],"mapped",[12305]],[[65085,65085],"mapped",[12298]],[[65086,65086],"mapped",[12299]],[[65087,65087],"mapped",[12296]],[[65088,65088],"mapped",[12297]],[[65089,65089],"mapped",[12300]],[[65090,65090],"mapped",[12301]],[[65091,65091],"mapped",[12302]],[[65092,65092],"mapped",[12303]],[[65093,65094],"valid",[],"NV8"],[[65095,65095],"disallowed_STD3_mapped",[91]],[[65096,65096],"disallowed_STD3_mapped",[93]],[[65097,65100],"disallowed_STD3_mapped",[32,773]],[[65101,65103],"disallowed_STD3_mapped",[95]],[[65104,65104],"disallowed_STD3_mapped",[44]],[[65105,65105],"mapped",[12289]],[[65106,65106],"disallowed"],[[65107,65107],"disallowed"],[[65108,65108],"disallowed_STD3_mapped",[59]],[[65109,65109],"disallowed_STD3_mapped",[58]],[[65110,65110],"disallowed_STD3_mapped",[63]],[[65111,65111],"disallowed_STD3_mapped",[33]],[[65112,65112],"mapped",[8212]],[[65113,65113],"disallowed_STD3_mapped",[40]],[[65114,65114],"disallowed_STD3_mapped",[41]],[[65115,65115],"disallowed_STD3_mapped",[123]],[[65116,65116],"disallowed_STD3_mapped",[125]],[[65117,65117],"mapped",[12308]],[[65118,65118],"mapped",[12309]],[[65119,65119],"disallowed_STD3_mapped",[35]],[[65120,65120],"disallowed_STD3_mapped",[38]],[[65121,65121],"disallowed_STD3_mapped",[42]],[[65122,65122],"disallowed_STD3_mapped",[43]],[[65123,65123],"mapped",[45]],[[65124,65124],"disallowed_STD3_mapped",[60]],[[65125,65125],"disallowed_STD3_mapped",[62]],[[65126,65126],"disallowed_STD3_mapped",[61]],[[65127,65127],"disallowed"],[[65128,65128],"disallowed_STD3_mapped",[92]],[[65129,65129],"disallowed_STD3_mapped",[36]],[[65130,65130],"disallowed_STD3_mapped",[37]],[[65131,65131],"disallowed_STD3_mapped",[64]],[[65132,65135],"disallowed"],[[65136,65136],"disallowed_STD3_mapped",[32,1611]],[[65137,65137],"mapped",[1600,1611]],[[65138,65138],"disallowed_STD3_mapped",[32,1612]],[[65139,65139],"valid"],[[65140,65140],"disallowed_STD3_mapped",[32,1613]],[[65141,65141],"disallowed"],[[65142,65142],"disallowed_STD3_mapped",[32,1614]],[[65143,65143],"mapped",[1600,1614]],[[65144,65144],"disallowed_STD3_mapped",[32,1615]],[[65145,65145],"mapped",[1600,1615]],[[65146,65146],"disallowed_STD3_mapped",[32,1616]],[[65147,65147],"mapped",[1600,1616]],[[65148,65148],"disallowed_STD3_mapped",[32,1617]],[[65149,65149],"mapped",[1600,1617]],[[65150,65150],"disallowed_STD3_mapped",[32,1618]],[[65151,65151],"mapped",[1600,1618]],[[65152,65152],"mapped",[1569]],[[65153,65154],"mapped",[1570]],[[65155,65156],"mapped",[1571]],[[65157,65158],"mapped",[1572]],[[65159,65160],"mapped",[1573]],[[65161,65164],"mapped",[1574]],[[65165,65166],"mapped",[1575]],[[65167,65170],"mapped",[1576]],[[65171,65172],"mapped",[1577]],[[65173,65176],"mapped",[1578]],[[65177,65180],"mapped",[1579]],[[65181,65184],"mapped",[1580]],[[65185,65188],"mapped",[1581]],[[65189,65192],"mapped",[1582]],[[65193,65194],"mapped",[1583]],[[65195,65196],"mapped",[1584]],[[65197,65198],"mapped",[1585]],[[65199,65200],"mapped",[1586]],[[65201,65204],"mapped",[1587]],[[65205,65208],"mapped",[1588]],[[65209,65212],"mapped",[1589]],[[65213,65216],"mapped",[1590]],[[65217,65220],"mapped",[1591]],[[65221,65224],"mapped",[1592]],[[65225,65228],"mapped",[1593]],[[65229,65232],"mapped",[1594]],[[65233,65236],"mapped",[1601]],[[65237,65240],"mapped",[1602]],[[65241,65244],"mapped",[1603]],[[65245,65248],"mapped",[1604]],[[65249,65252],"mapped",[1605]],[[65253,65256],"mapped",[1606]],[[65257,65260],"mapped",[1607]],[[65261,65262],"mapped",[1608]],[[65263,65264],"mapped",[1609]],[[65265,65268],"mapped",[1610]],[[65269,65270],"mapped",[1604,1570]],[[65271,65272],"mapped",[1604,1571]],[[65273,65274],"mapped",[1604,1573]],[[65275,65276],"mapped",[1604,1575]],[[65277,65278],"disallowed"],[[65279,65279],"ignored"],[[65280,65280],"disallowed"],[[65281,65281],"disallowed_STD3_mapped",[33]],[[65282,65282],"disallowed_STD3_mapped",[34]],[[65283,65283],"disallowed_STD3_mapped",[35]],[[65284,65284],"disallowed_STD3_mapped",[36]],[[65285,65285],"disallowed_STD3_mapped",[37]],[[65286,65286],"disallowed_STD3_mapped",[38]],[[65287,65287],"disallowed_STD3_mapped",[39]],[[65288,65288],"disallowed_STD3_mapped",[40]],[[65289,65289],"disallowed_STD3_mapped",[41]],[[65290,65290],"disallowed_STD3_mapped",[42]],[[65291,65291],"disallowed_STD3_mapped",[43]],[[65292,65292],"disallowed_STD3_mapped",[44]],[[65293,65293],"mapped",[45]],[[65294,65294],"mapped",[46]],[[65295,65295],"disallowed_STD3_mapped",[47]],[[65296,65296],"mapped",[48]],[[65297,65297],"mapped",[49]],[[65298,65298],"mapped",[50]],[[65299,65299],"mapped",[51]],[[65300,65300],"mapped",[52]],[[65301,65301],"mapped",[53]],[[65302,65302],"mapped",[54]],[[65303,65303],"mapped",[55]],[[65304,65304],"mapped",[56]],[[65305,65305],"mapped",[57]],[[65306,65306],"disallowed_STD3_mapped",[58]],[[65307,65307],"disallowed_STD3_mapped",[59]],[[65308,65308],"disallowed_STD3_mapped",[60]],[[65309,65309],"disallowed_STD3_mapped",[61]],[[65310,65310],"disallowed_STD3_mapped",[62]],[[65311,65311],"disallowed_STD3_mapped",[63]],[[65312,65312],"disallowed_STD3_mapped",[64]],[[65313,65313],"mapped",[97]],[[65314,65314],"mapped",[98]],[[65315,65315],"mapped",[99]],[[65316,65316],"mapped",[100]],[[65317,65317],"mapped",[101]],[[65318,65318],"mapped",[102]],[[65319,65319],"mapped",[103]],[[65320,65320],"mapped",[104]],[[65321,65321],"mapped",[105]],[[65322,65322],"mapped",[106]],[[65323,65323],"mapped",[107]],[[65324,65324],"mapped",[108]],[[65325,65325],"mapped",[109]],[[65326,65326],"mapped",[110]],[[65327,65327],"mapped",[111]],[[65328,65328],"mapped",[112]],[[65329,65329],"mapped",[113]],[[65330,65330],"mapped",[114]],[[65331,65331],"mapped",[115]],[[65332,65332],"mapped",[116]],[[65333,65333],"mapped",[117]],[[65334,65334],"mapped",[118]],[[65335,65335],"mapped",[119]],[[65336,65336],"mapped",[120]],[[65337,65337],"mapped",[121]],[[65338,65338],"mapped",[122]],[[65339,65339],"disallowed_STD3_mapped",[91]],[[65340,65340],"disallowed_STD3_mapped",[92]],[[65341,65341],"disallowed_STD3_mapped",[93]],[[65342,65342],"disallowed_STD3_mapped",[94]],[[65343,65343],"disallowed_STD3_mapped",[95]],[[65344,65344],"disallowed_STD3_mapped",[96]],[[65345,65345],"mapped",[97]],[[65346,65346],"mapped",[98]],[[65347,65347],"mapped",[99]],[[65348,65348],"mapped",[100]],[[65349,65349],"mapped",[101]],[[65350,65350],"mapped",[102]],[[65351,65351],"mapped",[103]],[[65352,65352],"mapped",[104]],[[65353,65353],"mapped",[105]],[[65354,65354],"mapped",[106]],[[65355,65355],"mapped",[107]],[[65356,65356],"mapped",[108]],[[65357,65357],"mapped",[109]],[[65358,65358],"mapped",[110]],[[65359,65359],"mapped",[111]],[[65360,65360],"mapped",[112]],[[65361,65361],"mapped",[113]],[[65362,65362],"mapped",[114]],[[65363,65363],"mapped",[115]],[[65364,65364],"mapped",[116]],[[65365,65365],"mapped",[117]],[[65366,65366],"mapped",[118]],[[65367,65367],"mapped",[119]],[[65368,65368],"mapped",[120]],[[65369,65369],"mapped",[121]],[[65370,65370],"mapped",[122]],[[65371,65371],"disallowed_STD3_mapped",[123]],[[65372,65372],"disallowed_STD3_mapped",[124]],[[65373,65373],"disallowed_STD3_mapped",[125]],[[65374,65374],"disallowed_STD3_mapped",[126]],[[65375,65375],"mapped",[10629]],[[65376,65376],"mapped",[10630]],[[65377,65377],"mapped",[46]],[[65378,65378],"mapped",[12300]],[[65379,65379],"mapped",[12301]],[[65380,65380],"mapped",[12289]],[[65381,65381],"mapped",[12539]],[[65382,65382],"mapped",[12530]],[[65383,65383],"mapped",[12449]],[[65384,65384],"mapped",[12451]],[[65385,65385],"mapped",[12453]],[[65386,65386],"mapped",[12455]],[[65387,65387],"mapped",[12457]],[[65388,65388],"mapped",[12515]],[[65389,65389],"mapped",[12517]],[[65390,65390],"mapped",[12519]],[[65391,65391],"mapped",[12483]],[[65392,65392],"mapped",[12540]],[[65393,65393],"mapped",[12450]],[[65394,65394],"mapped",[12452]],[[65395,65395],"mapped",[12454]],[[65396,65396],"mapped",[12456]],[[65397,65397],"mapped",[12458]],[[65398,65398],"mapped",[12459]],[[65399,65399],"mapped",[12461]],[[65400,65400],"mapped",[12463]],[[65401,65401],"mapped",[12465]],[[65402,65402],"mapped",[12467]],[[65403,65403],"mapped",[12469]],[[65404,65404],"mapped",[12471]],[[65405,65405],"mapped",[12473]],[[65406,65406],"mapped",[12475]],[[65407,65407],"mapped",[12477]],[[65408,65408],"mapped",[12479]],[[65409,65409],"mapped",[12481]],[[65410,65410],"mapped",[12484]],[[65411,65411],"mapped",[12486]],[[65412,65412],"mapped",[12488]],[[65413,65413],"mapped",[12490]],[[65414,65414],"mapped",[12491]],[[65415,65415],"mapped",[12492]],[[65416,65416],"mapped",[12493]],[[65417,65417],"mapped",[12494]],[[65418,65418],"mapped",[12495]],[[65419,65419],"mapped",[12498]],[[65420,65420],"mapped",[12501]],[[65421,65421],"mapped",[12504]],[[65422,65422],"mapped",[12507]],[[65423,65423],"mapped",[12510]],[[65424,65424],"mapped",[12511]],[[65425,65425],"mapped",[12512]],[[65426,65426],"mapped",[12513]],[[65427,65427],"mapped",[12514]],[[65428,65428],"mapped",[12516]],[[65429,65429],"mapped",[12518]],[[65430,65430],"mapped",[12520]],[[65431,65431],"mapped",[12521]],[[65432,65432],"mapped",[12522]],[[65433,65433],"mapped",[12523]],[[65434,65434],"mapped",[12524]],[[65435,65435],"mapped",[12525]],[[65436,65436],"mapped",[12527]],[[65437,65437],"mapped",[12531]],[[65438,65438],"mapped",[12441]],[[65439,65439],"mapped",[12442]],[[65440,65440],"disallowed"],[[65441,65441],"mapped",[4352]],[[65442,65442],"mapped",[4353]],[[65443,65443],"mapped",[4522]],[[65444,65444],"mapped",[4354]],[[65445,65445],"mapped",[4524]],[[65446,65446],"mapped",[4525]],[[65447,65447],"mapped",[4355]],[[65448,65448],"mapped",[4356]],[[65449,65449],"mapped",[4357]],[[65450,65450],"mapped",[4528]],[[65451,65451],"mapped",[4529]],[[65452,65452],"mapped",[4530]],[[65453,65453],"mapped",[4531]],[[65454,65454],"mapped",[4532]],[[65455,65455],"mapped",[4533]],[[65456,65456],"mapped",[4378]],[[65457,65457],"mapped",[4358]],[[65458,65458],"mapped",[4359]],[[65459,65459],"mapped",[4360]],[[65460,65460],"mapped",[4385]],[[65461,65461],"mapped",[4361]],[[65462,65462],"mapped",[4362]],[[65463,65463],"mapped",[4363]],[[65464,65464],"mapped",[4364]],[[65465,65465],"mapped",[4365]],[[65466,65466],"mapped",[4366]],[[65467,65467],"mapped",[4367]],[[65468,65468],"mapped",[4368]],[[65469,65469],"mapped",[4369]],[[65470,65470],"mapped",[4370]],[[65471,65473],"disallowed"],[[65474,65474],"mapped",[4449]],[[65475,65475],"mapped",[4450]],[[65476,65476],"mapped",[4451]],[[65477,65477],"mapped",[4452]],[[65478,65478],"mapped",[4453]],[[65479,65479],"mapped",[4454]],[[65480,65481],"disallowed"],[[65482,65482],"mapped",[4455]],[[65483,65483],"mapped",[4456]],[[65484,65484],"mapped",[4457]],[[65485,65485],"mapped",[4458]],[[65486,65486],"mapped",[4459]],[[65487,65487],"mapped",[4460]],[[65488,65489],"disallowed"],[[65490,65490],"mapped",[4461]],[[65491,65491],"mapped",[4462]],[[65492,65492],"mapped",[4463]],[[65493,65493],"mapped",[4464]],[[65494,65494],"mapped",[4465]],[[65495,65495],"mapped",[4466]],[[65496,65497],"disallowed"],[[65498,65498],"mapped",[4467]],[[65499,65499],"mapped",[4468]],[[65500,65500],"mapped",[4469]],[[65501,65503],"disallowed"],[[65504,65504],"mapped",[162]],[[65505,65505],"mapped",[163]],[[65506,65506],"mapped",[172]],[[65507,65507],"disallowed_STD3_mapped",[32,772]],[[65508,65508],"mapped",[166]],[[65509,65509],"mapped",[165]],[[65510,65510],"mapped",[8361]],[[65511,65511],"disallowed"],[[65512,65512],"mapped",[9474]],[[65513,65513],"mapped",[8592]],[[65514,65514],"mapped",[8593]],[[65515,65515],"mapped",[8594]],[[65516,65516],"mapped",[8595]],[[65517,65517],"mapped",[9632]],[[65518,65518],"mapped",[9675]],[[65519,65528],"disallowed"],[[65529,65531],"disallowed"],[[65532,65532],"disallowed"],[[65533,65533],"disallowed"],[[65534,65535],"disallowed"],[[65536,65547],"valid"],[[65548,65548],"disallowed"],[[65549,65574],"valid"],[[65575,65575],"disallowed"],[[65576,65594],"valid"],[[65595,65595],"disallowed"],[[65596,65597],"valid"],[[65598,65598],"disallowed"],[[65599,65613],"valid"],[[65614,65615],"disallowed"],[[65616,65629],"valid"],[[65630,65663],"disallowed"],[[65664,65786],"valid"],[[65787,65791],"disallowed"],[[65792,65794],"valid",[],"NV8"],[[65795,65798],"disallowed"],[[65799,65843],"valid",[],"NV8"],[[65844,65846],"disallowed"],[[65847,65855],"valid",[],"NV8"],[[65856,65930],"valid",[],"NV8"],[[65931,65932],"valid",[],"NV8"],[[65933,65935],"disallowed"],[[65936,65947],"valid",[],"NV8"],[[65948,65951],"disallowed"],[[65952,65952],"valid",[],"NV8"],[[65953,65999],"disallowed"],[[66000,66044],"valid",[],"NV8"],[[66045,66045],"valid"],[[66046,66175],"disallowed"],[[66176,66204],"valid"],[[66205,66207],"disallowed"],[[66208,66256],"valid"],[[66257,66271],"disallowed"],[[66272,66272],"valid"],[[66273,66299],"valid",[],"NV8"],[[66300,66303],"disallowed"],[[66304,66334],"valid"],[[66335,66335],"valid"],[[66336,66339],"valid",[],"NV8"],[[66340,66351],"disallowed"],[[66352,66368],"valid"],[[66369,66369],"valid",[],"NV8"],[[66370,66377],"valid"],[[66378,66378],"valid",[],"NV8"],[[66379,66383],"disallowed"],[[66384,66426],"valid"],[[66427,66431],"disallowed"],[[66432,66461],"valid"],[[66462,66462],"disallowed"],[[66463,66463],"valid",[],"NV8"],[[66464,66499],"valid"],[[66500,66503],"disallowed"],[[66504,66511],"valid"],[[66512,66517],"valid",[],"NV8"],[[66518,66559],"disallowed"],[[66560,66560],"mapped",[66600]],[[66561,66561],"mapped",[66601]],[[66562,66562],"mapped",[66602]],[[66563,66563],"mapped",[66603]],[[66564,66564],"mapped",[66604]],[[66565,66565],"mapped",[66605]],[[66566,66566],"mapped",[66606]],[[66567,66567],"mapped",[66607]],[[66568,66568],"mapped",[66608]],[[66569,66569],"mapped",[66609]],[[66570,66570],"mapped",[66610]],[[66571,66571],"mapped",[66611]],[[66572,66572],"mapped",[66612]],[[66573,66573],"mapped",[66613]],[[66574,66574],"mapped",[66614]],[[66575,66575],"mapped",[66615]],[[66576,66576],"mapped",[66616]],[[66577,66577],"mapped",[66617]],[[66578,66578],"mapped",[66618]],[[66579,66579],"mapped",[66619]],[[66580,66580],"mapped",[66620]],[[66581,66581],"mapped",[66621]],[[66582,66582],"mapped",[66622]],[[66583,66583],"mapped",[66623]],[[66584,66584],"mapped",[66624]],[[66585,66585],"mapped",[66625]],[[66586,66586],"mapped",[66626]],[[66587,66587],"mapped",[66627]],[[66588,66588],"mapped",[66628]],[[66589,66589],"mapped",[66629]],[[66590,66590],"mapped",[66630]],[[66591,66591],"mapped",[66631]],[[66592,66592],"mapped",[66632]],[[66593,66593],"mapped",[66633]],[[66594,66594],"mapped",[66634]],[[66595,66595],"mapped",[66635]],[[66596,66596],"mapped",[66636]],[[66597,66597],"mapped",[66637]],[[66598,66598],"mapped",[66638]],[[66599,66599],"mapped",[66639]],[[66600,66637],"valid"],[[66638,66717],"valid"],[[66718,66719],"disallowed"],[[66720,66729],"valid"],[[66730,66815],"disallowed"],[[66816,66855],"valid"],[[66856,66863],"disallowed"],[[66864,66915],"valid"],[[66916,66926],"disallowed"],[[66927,66927],"valid",[],"NV8"],[[66928,67071],"disallowed"],[[67072,67382],"valid"],[[67383,67391],"disallowed"],[[67392,67413],"valid"],[[67414,67423],"disallowed"],[[67424,67431],"valid"],[[67432,67583],"disallowed"],[[67584,67589],"valid"],[[67590,67591],"disallowed"],[[67592,67592],"valid"],[[67593,67593],"disallowed"],[[67594,67637],"valid"],[[67638,67638],"disallowed"],[[67639,67640],"valid"],[[67641,67643],"disallowed"],[[67644,67644],"valid"],[[67645,67646],"disallowed"],[[67647,67647],"valid"],[[67648,67669],"valid"],[[67670,67670],"disallowed"],[[67671,67679],"valid",[],"NV8"],[[67680,67702],"valid"],[[67703,67711],"valid",[],"NV8"],[[67712,67742],"valid"],[[67743,67750],"disallowed"],[[67751,67759],"valid",[],"NV8"],[[67760,67807],"disallowed"],[[67808,67826],"valid"],[[67827,67827],"disallowed"],[[67828,67829],"valid"],[[67830,67834],"disallowed"],[[67835,67839],"valid",[],"NV8"],[[67840,67861],"valid"],[[67862,67865],"valid",[],"NV8"],[[67866,67867],"valid",[],"NV8"],[[67868,67870],"disallowed"],[[67871,67871],"valid",[],"NV8"],[[67872,67897],"valid"],[[67898,67902],"disallowed"],[[67903,67903],"valid",[],"NV8"],[[67904,67967],"disallowed"],[[67968,68023],"valid"],[[68024,68027],"disallowed"],[[68028,68029],"valid",[],"NV8"],[[68030,68031],"valid"],[[68032,68047],"valid",[],"NV8"],[[68048,68049],"disallowed"],[[68050,68095],"valid",[],"NV8"],[[68096,68099],"valid"],[[68100,68100],"disallowed"],[[68101,68102],"valid"],[[68103,68107],"disallowed"],[[68108,68115],"valid"],[[68116,68116],"disallowed"],[[68117,68119],"valid"],[[68120,68120],"disallowed"],[[68121,68147],"valid"],[[68148,68151],"disallowed"],[[68152,68154],"valid"],[[68155,68158],"disallowed"],[[68159,68159],"valid"],[[68160,68167],"valid",[],"NV8"],[[68168,68175],"disallowed"],[[68176,68184],"valid",[],"NV8"],[[68185,68191],"disallowed"],[[68192,68220],"valid"],[[68221,68223],"valid",[],"NV8"],[[68224,68252],"valid"],[[68253,68255],"valid",[],"NV8"],[[68256,68287],"disallowed"],[[68288,68295],"valid"],[[68296,68296],"valid",[],"NV8"],[[68297,68326],"valid"],[[68327,68330],"disallowed"],[[68331,68342],"valid",[],"NV8"],[[68343,68351],"disallowed"],[[68352,68405],"valid"],[[68406,68408],"disallowed"],[[68409,68415],"valid",[],"NV8"],[[68416,68437],"valid"],[[68438,68439],"disallowed"],[[68440,68447],"valid",[],"NV8"],[[68448,68466],"valid"],[[68467,68471],"disallowed"],[[68472,68479],"valid",[],"NV8"],[[68480,68497],"valid"],[[68498,68504],"disallowed"],[[68505,68508],"valid",[],"NV8"],[[68509,68520],"disallowed"],[[68521,68527],"valid",[],"NV8"],[[68528,68607],"disallowed"],[[68608,68680],"valid"],[[68681,68735],"disallowed"],[[68736,68736],"mapped",[68800]],[[68737,68737],"mapped",[68801]],[[68738,68738],"mapped",[68802]],[[68739,68739],"mapped",[68803]],[[68740,68740],"mapped",[68804]],[[68741,68741],"mapped",[68805]],[[68742,68742],"mapped",[68806]],[[68743,68743],"mapped",[68807]],[[68744,68744],"mapped",[68808]],[[68745,68745],"mapped",[68809]],[[68746,68746],"mapped",[68810]],[[68747,68747],"mapped",[68811]],[[68748,68748],"mapped",[68812]],[[68749,68749],"mapped",[68813]],[[68750,68750],"mapped",[68814]],[[68751,68751],"mapped",[68815]],[[68752,68752],"mapped",[68816]],[[68753,68753],"mapped",[68817]],[[68754,68754],"mapped",[68818]],[[68755,68755],"mapped",[68819]],[[68756,68756],"mapped",[68820]],[[68757,68757],"mapped",[68821]],[[68758,68758],"mapped",[68822]],[[68759,68759],"mapped",[68823]],[[68760,68760],"mapped",[68824]],[[68761,68761],"mapped",[68825]],[[68762,68762],"mapped",[68826]],[[68763,68763],"mapped",[68827]],[[68764,68764],"mapped",[68828]],[[68765,68765],"mapped",[68829]],[[68766,68766],"mapped",[68830]],[[68767,68767],"mapped",[68831]],[[68768,68768],"mapped",[68832]],[[68769,68769],"mapped",[68833]],[[68770,68770],"mapped",[68834]],[[68771,68771],"mapped",[68835]],[[68772,68772],"mapped",[68836]],[[68773,68773],"mapped",[68837]],[[68774,68774],"mapped",[68838]],[[68775,68775],"mapped",[68839]],[[68776,68776],"mapped",[68840]],[[68777,68777],"mapped",[68841]],[[68778,68778],"mapped",[68842]],[[68779,68779],"mapped",[68843]],[[68780,68780],"mapped",[68844]],[[68781,68781],"mapped",[68845]],[[68782,68782],"mapped",[68846]],[[68783,68783],"mapped",[68847]],[[68784,68784],"mapped",[68848]],[[68785,68785],"mapped",[68849]],[[68786,68786],"mapped",[68850]],[[68787,68799],"disallowed"],[[68800,68850],"valid"],[[68851,68857],"disallowed"],[[68858,68863],"valid",[],"NV8"],[[68864,69215],"disallowed"],[[69216,69246],"valid",[],"NV8"],[[69247,69631],"disallowed"],[[69632,69702],"valid"],[[69703,69709],"valid",[],"NV8"],[[69710,69713],"disallowed"],[[69714,69733],"valid",[],"NV8"],[[69734,69743],"valid"],[[69744,69758],"disallowed"],[[69759,69759],"valid"],[[69760,69818],"valid"],[[69819,69820],"valid",[],"NV8"],[[69821,69821],"disallowed"],[[69822,69825],"valid",[],"NV8"],[[69826,69839],"disallowed"],[[69840,69864],"valid"],[[69865,69871],"disallowed"],[[69872,69881],"valid"],[[69882,69887],"disallowed"],[[69888,69940],"valid"],[[69941,69941],"disallowed"],[[69942,69951],"valid"],[[69952,69955],"valid",[],"NV8"],[[69956,69967],"disallowed"],[[69968,70003],"valid"],[[70004,70005],"valid",[],"NV8"],[[70006,70006],"valid"],[[70007,70015],"disallowed"],[[70016,70084],"valid"],[[70085,70088],"valid",[],"NV8"],[[70089,70089],"valid",[],"NV8"],[[70090,70092],"valid"],[[70093,70093],"valid",[],"NV8"],[[70094,70095],"disallowed"],[[70096,70105],"valid"],[[70106,70106],"valid"],[[70107,70107],"valid",[],"NV8"],[[70108,70108],"valid"],[[70109,70111],"valid",[],"NV8"],[[70112,70112],"disallowed"],[[70113,70132],"valid",[],"NV8"],[[70133,70143],"disallowed"],[[70144,70161],"valid"],[[70162,70162],"disallowed"],[[70163,70199],"valid"],[[70200,70205],"valid",[],"NV8"],[[70206,70271],"disallowed"],[[70272,70278],"valid"],[[70279,70279],"disallowed"],[[70280,70280],"valid"],[[70281,70281],"disallowed"],[[70282,70285],"valid"],[[70286,70286],"disallowed"],[[70287,70301],"valid"],[[70302,70302],"disallowed"],[[70303,70312],"valid"],[[70313,70313],"valid",[],"NV8"],[[70314,70319],"disallowed"],[[70320,70378],"valid"],[[70379,70383],"disallowed"],[[70384,70393],"valid"],[[70394,70399],"disallowed"],[[70400,70400],"valid"],[[70401,70403],"valid"],[[70404,70404],"disallowed"],[[70405,70412],"valid"],[[70413,70414],"disallowed"],[[70415,70416],"valid"],[[70417,70418],"disallowed"],[[70419,70440],"valid"],[[70441,70441],"disallowed"],[[70442,70448],"valid"],[[70449,70449],"disallowed"],[[70450,70451],"valid"],[[70452,70452],"disallowed"],[[70453,70457],"valid"],[[70458,70459],"disallowed"],[[70460,70468],"valid"],[[70469,70470],"disallowed"],[[70471,70472],"valid"],[[70473,70474],"disallowed"],[[70475,70477],"valid"],[[70478,70479],"disallowed"],[[70480,70480],"valid"],[[70481,70486],"disallowed"],[[70487,70487],"valid"],[[70488,70492],"disallowed"],[[70493,70499],"valid"],[[70500,70501],"disallowed"],[[70502,70508],"valid"],[[70509,70511],"disallowed"],[[70512,70516],"valid"],[[70517,70783],"disallowed"],[[70784,70853],"valid"],[[70854,70854],"valid",[],"NV8"],[[70855,70855],"valid"],[[70856,70863],"disallowed"],[[70864,70873],"valid"],[[70874,71039],"disallowed"],[[71040,71093],"valid"],[[71094,71095],"disallowed"],[[71096,71104],"valid"],[[71105,71113],"valid",[],"NV8"],[[71114,71127],"valid",[],"NV8"],[[71128,71133],"valid"],[[71134,71167],"disallowed"],[[71168,71232],"valid"],[[71233,71235],"valid",[],"NV8"],[[71236,71236],"valid"],[[71237,71247],"disallowed"],[[71248,71257],"valid"],[[71258,71295],"disallowed"],[[71296,71351],"valid"],[[71352,71359],"disallowed"],[[71360,71369],"valid"],[[71370,71423],"disallowed"],[[71424,71449],"valid"],[[71450,71452],"disallowed"],[[71453,71467],"valid"],[[71468,71471],"disallowed"],[[71472,71481],"valid"],[[71482,71487],"valid",[],"NV8"],[[71488,71839],"disallowed"],[[71840,71840],"mapped",[71872]],[[71841,71841],"mapped",[71873]],[[71842,71842],"mapped",[71874]],[[71843,71843],"mapped",[71875]],[[71844,71844],"mapped",[71876]],[[71845,71845],"mapped",[71877]],[[71846,71846],"mapped",[71878]],[[71847,71847],"mapped",[71879]],[[71848,71848],"mapped",[71880]],[[71849,71849],"mapped",[71881]],[[71850,71850],"mapped",[71882]],[[71851,71851],"mapped",[71883]],[[71852,71852],"mapped",[71884]],[[71853,71853],"mapped",[71885]],[[71854,71854],"mapped",[71886]],[[71855,71855],"mapped",[71887]],[[71856,71856],"mapped",[71888]],[[71857,71857],"mapped",[71889]],[[71858,71858],"mapped",[71890]],[[71859,71859],"mapped",[71891]],[[71860,71860],"mapped",[71892]],[[71861,71861],"mapped",[71893]],[[71862,71862],"mapped",[71894]],[[71863,71863],"mapped",[71895]],[[71864,71864],"mapped",[71896]],[[71865,71865],"mapped",[71897]],[[71866,71866],"mapped",[71898]],[[71867,71867],"mapped",[71899]],[[71868,71868],"mapped",[71900]],[[71869,71869],"mapped",[71901]],[[71870,71870],"mapped",[71902]],[[71871,71871],"mapped",[71903]],[[71872,71913],"valid"],[[71914,71922],"valid",[],"NV8"],[[71923,71934],"disallowed"],[[71935,71935],"valid"],[[71936,72383],"disallowed"],[[72384,72440],"valid"],[[72441,73727],"disallowed"],[[73728,74606],"valid"],[[74607,74648],"valid"],[[74649,74649],"valid"],[[74650,74751],"disallowed"],[[74752,74850],"valid",[],"NV8"],[[74851,74862],"valid",[],"NV8"],[[74863,74863],"disallowed"],[[74864,74867],"valid",[],"NV8"],[[74868,74868],"valid",[],"NV8"],[[74869,74879],"disallowed"],[[74880,75075],"valid"],[[75076,77823],"disallowed"],[[77824,78894],"valid"],[[78895,82943],"disallowed"],[[82944,83526],"valid"],[[83527,92159],"disallowed"],[[92160,92728],"valid"],[[92729,92735],"disallowed"],[[92736,92766],"valid"],[[92767,92767],"disallowed"],[[92768,92777],"valid"],[[92778,92781],"disallowed"],[[92782,92783],"valid",[],"NV8"],[[92784,92879],"disallowed"],[[92880,92909],"valid"],[[92910,92911],"disallowed"],[[92912,92916],"valid"],[[92917,92917],"valid",[],"NV8"],[[92918,92927],"disallowed"],[[92928,92982],"valid"],[[92983,92991],"valid",[],"NV8"],[[92992,92995],"valid"],[[92996,92997],"valid",[],"NV8"],[[92998,93007],"disallowed"],[[93008,93017],"valid"],[[93018,93018],"disallowed"],[[93019,93025],"valid",[],"NV8"],[[93026,93026],"disallowed"],[[93027,93047],"valid"],[[93048,93052],"disallowed"],[[93053,93071],"valid"],[[93072,93951],"disallowed"],[[93952,94020],"valid"],[[94021,94031],"disallowed"],[[94032,94078],"valid"],[[94079,94094],"disallowed"],[[94095,94111],"valid"],[[94112,110591],"disallowed"],[[110592,110593],"valid"],[[110594,113663],"disallowed"],[[113664,113770],"valid"],[[113771,113775],"disallowed"],[[113776,113788],"valid"],[[113789,113791],"disallowed"],[[113792,113800],"valid"],[[113801,113807],"disallowed"],[[113808,113817],"valid"],[[113818,113819],"disallowed"],[[113820,113820],"valid",[],"NV8"],[[113821,113822],"valid"],[[113823,113823],"valid",[],"NV8"],[[113824,113827],"ignored"],[[113828,118783],"disallowed"],[[118784,119029],"valid",[],"NV8"],[[119030,119039],"disallowed"],[[119040,119078],"valid",[],"NV8"],[[119079,119080],"disallowed"],[[119081,119081],"valid",[],"NV8"],[[119082,119133],"valid",[],"NV8"],[[119134,119134],"mapped",[119127,119141]],[[119135,119135],"mapped",[119128,119141]],[[119136,119136],"mapped",[119128,119141,119150]],[[119137,119137],"mapped",[119128,119141,119151]],[[119138,119138],"mapped",[119128,119141,119152]],[[119139,119139],"mapped",[119128,119141,119153]],[[119140,119140],"mapped",[119128,119141,119154]],[[119141,119154],"valid",[],"NV8"],[[119155,119162],"disallowed"],[[119163,119226],"valid",[],"NV8"],[[119227,119227],"mapped",[119225,119141]],[[119228,119228],"mapped",[119226,119141]],[[119229,119229],"mapped",[119225,119141,119150]],[[119230,119230],"mapped",[119226,119141,119150]],[[119231,119231],"mapped",[119225,119141,119151]],[[119232,119232],"mapped",[119226,119141,119151]],[[119233,119261],"valid",[],"NV8"],[[119262,119272],"valid",[],"NV8"],[[119273,119295],"disallowed"],[[119296,119365],"valid",[],"NV8"],[[119366,119551],"disallowed"],[[119552,119638],"valid",[],"NV8"],[[119639,119647],"disallowed"],[[119648,119665],"valid",[],"NV8"],[[119666,119807],"disallowed"],[[119808,119808],"mapped",[97]],[[119809,119809],"mapped",[98]],[[119810,119810],"mapped",[99]],[[119811,119811],"mapped",[100]],[[119812,119812],"mapped",[101]],[[119813,119813],"mapped",[102]],[[119814,119814],"mapped",[103]],[[119815,119815],"mapped",[104]],[[119816,119816],"mapped",[105]],[[119817,119817],"mapped",[106]],[[119818,119818],"mapped",[107]],[[119819,119819],"mapped",[108]],[[119820,119820],"mapped",[109]],[[119821,119821],"mapped",[110]],[[119822,119822],"mapped",[111]],[[119823,119823],"mapped",[112]],[[119824,119824],"mapped",[113]],[[119825,119825],"mapped",[114]],[[119826,119826],"mapped",[115]],[[119827,119827],"mapped",[116]],[[119828,119828],"mapped",[117]],[[119829,119829],"mapped",[118]],[[119830,119830],"mapped",[119]],[[119831,119831],"mapped",[120]],[[119832,119832],"mapped",[121]],[[119833,119833],"mapped",[122]],[[119834,119834],"mapped",[97]],[[119835,119835],"mapped",[98]],[[119836,119836],"mapped",[99]],[[119837,119837],"mapped",[100]],[[119838,119838],"mapped",[101]],[[119839,119839],"mapped",[102]],[[119840,119840],"mapped",[103]],[[119841,119841],"mapped",[104]],[[119842,119842],"mapped",[105]],[[119843,119843],"mapped",[106]],[[119844,119844],"mapped",[107]],[[119845,119845],"mapped",[108]],[[119846,119846],"mapped",[109]],[[119847,119847],"mapped",[110]],[[119848,119848],"mapped",[111]],[[119849,119849],"mapped",[112]],[[119850,119850],"mapped",[113]],[[119851,119851],"mapped",[114]],[[119852,119852],"mapped",[115]],[[119853,119853],"mapped",[116]],[[119854,119854],"mapped",[117]],[[119855,119855],"mapped",[118]],[[119856,119856],"mapped",[119]],[[119857,119857],"mapped",[120]],[[119858,119858],"mapped",[121]],[[119859,119859],"mapped",[122]],[[119860,119860],"mapped",[97]],[[119861,119861],"mapped",[98]],[[119862,119862],"mapped",[99]],[[119863,119863],"mapped",[100]],[[119864,119864],"mapped",[101]],[[119865,119865],"mapped",[102]],[[119866,119866],"mapped",[103]],[[119867,119867],"mapped",[104]],[[119868,119868],"mapped",[105]],[[119869,119869],"mapped",[106]],[[119870,119870],"mapped",[107]],[[119871,119871],"mapped",[108]],[[119872,119872],"mapped",[109]],[[119873,119873],"mapped",[110]],[[119874,119874],"mapped",[111]],[[119875,119875],"mapped",[112]],[[119876,119876],"mapped",[113]],[[119877,119877],"mapped",[114]],[[119878,119878],"mapped",[115]],[[119879,119879],"mapped",[116]],[[119880,119880],"mapped",[117]],[[119881,119881],"mapped",[118]],[[119882,119882],"mapped",[119]],[[119883,119883],"mapped",[120]],[[119884,119884],"mapped",[121]],[[119885,119885],"mapped",[122]],[[119886,119886],"mapped",[97]],[[119887,119887],"mapped",[98]],[[119888,119888],"mapped",[99]],[[119889,119889],"mapped",[100]],[[119890,119890],"mapped",[101]],[[119891,119891],"mapped",[102]],[[119892,119892],"mapped",[103]],[[119893,119893],"disallowed"],[[119894,119894],"mapped",[105]],[[119895,119895],"mapped",[106]],[[119896,119896],"mapped",[107]],[[119897,119897],"mapped",[108]],[[119898,119898],"mapped",[109]],[[119899,119899],"mapped",[110]],[[119900,119900],"mapped",[111]],[[119901,119901],"mapped",[112]],[[119902,119902],"mapped",[113]],[[119903,119903],"mapped",[114]],[[119904,119904],"mapped",[115]],[[119905,119905],"mapped",[116]],[[119906,119906],"mapped",[117]],[[119907,119907],"mapped",[118]],[[119908,119908],"mapped",[119]],[[119909,119909],"mapped",[120]],[[119910,119910],"mapped",[121]],[[119911,119911],"mapped",[122]],[[119912,119912],"mapped",[97]],[[119913,119913],"mapped",[98]],[[119914,119914],"mapped",[99]],[[119915,119915],"mapped",[100]],[[119916,119916],"mapped",[101]],[[119917,119917],"mapped",[102]],[[119918,119918],"mapped",[103]],[[119919,119919],"mapped",[104]],[[119920,119920],"mapped",[105]],[[119921,119921],"mapped",[106]],[[119922,119922],"mapped",[107]],[[119923,119923],"mapped",[108]],[[119924,119924],"mapped",[109]],[[119925,119925],"mapped",[110]],[[119926,119926],"mapped",[111]],[[119927,119927],"mapped",[112]],[[119928,119928],"mapped",[113]],[[119929,119929],"mapped",[114]],[[119930,119930],"mapped",[115]],[[119931,119931],"mapped",[116]],[[119932,119932],"mapped",[117]],[[119933,119933],"mapped",[118]],[[119934,119934],"mapped",[119]],[[119935,119935],"mapped",[120]],[[119936,119936],"mapped",[121]],[[119937,119937],"mapped",[122]],[[119938,119938],"mapped",[97]],[[119939,119939],"mapped",[98]],[[119940,119940],"mapped",[99]],[[119941,119941],"mapped",[100]],[[119942,119942],"mapped",[101]],[[119943,119943],"mapped",[102]],[[119944,119944],"mapped",[103]],[[119945,119945],"mapped",[104]],[[119946,119946],"mapped",[105]],[[119947,119947],"mapped",[106]],[[119948,119948],"mapped",[107]],[[119949,119949],"mapped",[108]],[[119950,119950],"mapped",[109]],[[119951,119951],"mapped",[110]],[[119952,119952],"mapped",[111]],[[119953,119953],"mapped",[112]],[[119954,119954],"mapped",[113]],[[119955,119955],"mapped",[114]],[[119956,119956],"mapped",[115]],[[119957,119957],"mapped",[116]],[[119958,119958],"mapped",[117]],[[119959,119959],"mapped",[118]],[[119960,119960],"mapped",[119]],[[119961,119961],"mapped",[120]],[[119962,119962],"mapped",[121]],[[119963,119963],"mapped",[122]],[[119964,119964],"mapped",[97]],[[119965,119965],"disallowed"],[[119966,119966],"mapped",[99]],[[119967,119967],"mapped",[100]],[[119968,119969],"disallowed"],[[119970,119970],"mapped",[103]],[[119971,119972],"disallowed"],[[119973,119973],"mapped",[106]],[[119974,119974],"mapped",[107]],[[119975,119976],"disallowed"],[[119977,119977],"mapped",[110]],[[119978,119978],"mapped",[111]],[[119979,119979],"mapped",[112]],[[119980,119980],"mapped",[113]],[[119981,119981],"disallowed"],[[119982,119982],"mapped",[115]],[[119983,119983],"mapped",[116]],[[119984,119984],"mapped",[117]],[[119985,119985],"mapped",[118]],[[119986,119986],"mapped",[119]],[[119987,119987],"mapped",[120]],[[119988,119988],"mapped",[121]],[[119989,119989],"mapped",[122]],[[119990,119990],"mapped",[97]],[[119991,119991],"mapped",[98]],[[119992,119992],"mapped",[99]],[[119993,119993],"mapped",[100]],[[119994,119994],"disallowed"],[[119995,119995],"mapped",[102]],[[119996,119996],"disallowed"],[[119997,119997],"mapped",[104]],[[119998,119998],"mapped",[105]],[[119999,119999],"mapped",[106]],[[120000,120000],"mapped",[107]],[[120001,120001],"mapped",[108]],[[120002,120002],"mapped",[109]],[[120003,120003],"mapped",[110]],[[120004,120004],"disallowed"],[[120005,120005],"mapped",[112]],[[120006,120006],"mapped",[113]],[[120007,120007],"mapped",[114]],[[120008,120008],"mapped",[115]],[[120009,120009],"mapped",[116]],[[120010,120010],"mapped",[117]],[[120011,120011],"mapped",[118]],[[120012,120012],"mapped",[119]],[[120013,120013],"mapped",[120]],[[120014,120014],"mapped",[121]],[[120015,120015],"mapped",[122]],[[120016,120016],"mapped",[97]],[[120017,120017],"mapped",[98]],[[120018,120018],"mapped",[99]],[[120019,120019],"mapped",[100]],[[120020,120020],"mapped",[101]],[[120021,120021],"mapped",[102]],[[120022,120022],"mapped",[103]],[[120023,120023],"mapped",[104]],[[120024,120024],"mapped",[105]],[[120025,120025],"mapped",[106]],[[120026,120026],"mapped",[107]],[[120027,120027],"mapped",[108]],[[120028,120028],"mapped",[109]],[[120029,120029],"mapped",[110]],[[120030,120030],"mapped",[111]],[[120031,120031],"mapped",[112]],[[120032,120032],"mapped",[113]],[[120033,120033],"mapped",[114]],[[120034,120034],"mapped",[115]],[[120035,120035],"mapped",[116]],[[120036,120036],"mapped",[117]],[[120037,120037],"mapped",[118]],[[120038,120038],"mapped",[119]],[[120039,120039],"mapped",[120]],[[120040,120040],"mapped",[121]],[[120041,120041],"mapped",[122]],[[120042,120042],"mapped",[97]],[[120043,120043],"mapped",[98]],[[120044,120044],"mapped",[99]],[[120045,120045],"mapped",[100]],[[120046,120046],"mapped",[101]],[[120047,120047],"mapped",[102]],[[120048,120048],"mapped",[103]],[[120049,120049],"mapped",[104]],[[120050,120050],"mapped",[105]],[[120051,120051],"mapped",[106]],[[120052,120052],"mapped",[107]],[[120053,120053],"mapped",[108]],[[120054,120054],"mapped",[109]],[[120055,120055],"mapped",[110]],[[120056,120056],"mapped",[111]],[[120057,120057],"mapped",[112]],[[120058,120058],"mapped",[113]],[[120059,120059],"mapped",[114]],[[120060,120060],"mapped",[115]],[[120061,120061],"mapped",[116]],[[120062,120062],"mapped",[117]],[[120063,120063],"mapped",[118]],[[120064,120064],"mapped",[119]],[[120065,120065],"mapped",[120]],[[120066,120066],"mapped",[121]],[[120067,120067],"mapped",[122]],[[120068,120068],"mapped",[97]],[[120069,120069],"mapped",[98]],[[120070,120070],"disallowed"],[[120071,120071],"mapped",[100]],[[120072,120072],"mapped",[101]],[[120073,120073],"mapped",[102]],[[120074,120074],"mapped",[103]],[[120075,120076],"disallowed"],[[120077,120077],"mapped",[106]],[[120078,120078],"mapped",[107]],[[120079,120079],"mapped",[108]],[[120080,120080],"mapped",[109]],[[120081,120081],"mapped",[110]],[[120082,120082],"mapped",[111]],[[120083,120083],"mapped",[112]],[[120084,120084],"mapped",[113]],[[120085,120085],"disallowed"],[[120086,120086],"mapped",[115]],[[120087,120087],"mapped",[116]],[[120088,120088],"mapped",[117]],[[120089,120089],"mapped",[118]],[[120090,120090],"mapped",[119]],[[120091,120091],"mapped",[120]],[[120092,120092],"mapped",[121]],[[120093,120093],"disallowed"],[[120094,120094],"mapped",[97]],[[120095,120095],"mapped",[98]],[[120096,120096],"mapped",[99]],[[120097,120097],"mapped",[100]],[[120098,120098],"mapped",[101]],[[120099,120099],"mapped",[102]],[[120100,120100],"mapped",[103]],[[120101,120101],"mapped",[104]],[[120102,120102],"mapped",[105]],[[120103,120103],"mapped",[106]],[[120104,120104],"mapped",[107]],[[120105,120105],"mapped",[108]],[[120106,120106],"mapped",[109]],[[120107,120107],"mapped",[110]],[[120108,120108],"mapped",[111]],[[120109,120109],"mapped",[112]],[[120110,120110],"mapped",[113]],[[120111,120111],"mapped",[114]],[[120112,120112],"mapped",[115]],[[120113,120113],"mapped",[116]],[[120114,120114],"mapped",[117]],[[120115,120115],"mapped",[118]],[[120116,120116],"mapped",[119]],[[120117,120117],"mapped",[120]],[[120118,120118],"mapped",[121]],[[120119,120119],"mapped",[122]],[[120120,120120],"mapped",[97]],[[120121,120121],"mapped",[98]],[[120122,120122],"disallowed"],[[120123,120123],"mapped",[100]],[[120124,120124],"mapped",[101]],[[120125,120125],"mapped",[102]],[[120126,120126],"mapped",[103]],[[120127,120127],"disallowed"],[[120128,120128],"mapped",[105]],[[120129,120129],"mapped",[106]],[[120130,120130],"mapped",[107]],[[120131,120131],"mapped",[108]],[[120132,120132],"mapped",[109]],[[120133,120133],"disallowed"],[[120134,120134],"mapped",[111]],[[120135,120137],"disallowed"],[[120138,120138],"mapped",[115]],[[120139,120139],"mapped",[116]],[[120140,120140],"mapped",[117]],[[120141,120141],"mapped",[118]],[[120142,120142],"mapped",[119]],[[120143,120143],"mapped",[120]],[[120144,120144],"mapped",[121]],[[120145,120145],"disallowed"],[[120146,120146],"mapped",[97]],[[120147,120147],"mapped",[98]],[[120148,120148],"mapped",[99]],[[120149,120149],"mapped",[100]],[[120150,120150],"mapped",[101]],[[120151,120151],"mapped",[102]],[[120152,120152],"mapped",[103]],[[120153,120153],"mapped",[104]],[[120154,120154],"mapped",[105]],[[120155,120155],"mapped",[106]],[[120156,120156],"mapped",[107]],[[120157,120157],"mapped",[108]],[[120158,120158],"mapped",[109]],[[120159,120159],"mapped",[110]],[[120160,120160],"mapped",[111]],[[120161,120161],"mapped",[112]],[[120162,120162],"mapped",[113]],[[120163,120163],"mapped",[114]],[[120164,120164],"mapped",[115]],[[120165,120165],"mapped",[116]],[[120166,120166],"mapped",[117]],[[120167,120167],"mapped",[118]],[[120168,120168],"mapped",[119]],[[120169,120169],"mapped",[120]],[[120170,120170],"mapped",[121]],[[120171,120171],"mapped",[122]],[[120172,120172],"mapped",[97]],[[120173,120173],"mapped",[98]],[[120174,120174],"mapped",[99]],[[120175,120175],"mapped",[100]],[[120176,120176],"mapped",[101]],[[120177,120177],"mapped",[102]],[[120178,120178],"mapped",[103]],[[120179,120179],"mapped",[104]],[[120180,120180],"mapped",[105]],[[120181,120181],"mapped",[106]],[[120182,120182],"mapped",[107]],[[120183,120183],"mapped",[108]],[[120184,120184],"mapped",[109]],[[120185,120185],"mapped",[110]],[[120186,120186],"mapped",[111]],[[120187,120187],"mapped",[112]],[[120188,120188],"mapped",[113]],[[120189,120189],"mapped",[114]],[[120190,120190],"mapped",[115]],[[120191,120191],"mapped",[116]],[[120192,120192],"mapped",[117]],[[120193,120193],"mapped",[118]],[[120194,120194],"mapped",[119]],[[120195,120195],"mapped",[120]],[[120196,120196],"mapped",[121]],[[120197,120197],"mapped",[122]],[[120198,120198],"mapped",[97]],[[120199,120199],"mapped",[98]],[[120200,120200],"mapped",[99]],[[120201,120201],"mapped",[100]],[[120202,120202],"mapped",[101]],[[120203,120203],"mapped",[102]],[[120204,120204],"mapped",[103]],[[120205,120205],"mapped",[104]],[[120206,120206],"mapped",[105]],[[120207,120207],"mapped",[106]],[[120208,120208],"mapped",[107]],[[120209,120209],"mapped",[108]],[[120210,120210],"mapped",[109]],[[120211,120211],"mapped",[110]],[[120212,120212],"mapped",[111]],[[120213,120213],"mapped",[112]],[[120214,120214],"mapped",[113]],[[120215,120215],"mapped",[114]],[[120216,120216],"mapped",[115]],[[120217,120217],"mapped",[116]],[[120218,120218],"mapped",[117]],[[120219,120219],"mapped",[118]],[[120220,120220],"mapped",[119]],[[120221,120221],"mapped",[120]],[[120222,120222],"mapped",[121]],[[120223,120223],"mapped",[122]],[[120224,120224],"mapped",[97]],[[120225,120225],"mapped",[98]],[[120226,120226],"mapped",[99]],[[120227,120227],"mapped",[100]],[[120228,120228],"mapped",[101]],[[120229,120229],"mapped",[102]],[[120230,120230],"mapped",[103]],[[120231,120231],"mapped",[104]],[[120232,120232],"mapped",[105]],[[120233,120233],"mapped",[106]],[[120234,120234],"mapped",[107]],[[120235,120235],"mapped",[108]],[[120236,120236],"mapped",[109]],[[120237,120237],"mapped",[110]],[[120238,120238],"mapped",[111]],[[120239,120239],"mapped",[112]],[[120240,120240],"mapped",[113]],[[120241,120241],"mapped",[114]],[[120242,120242],"mapped",[115]],[[120243,120243],"mapped",[116]],[[120244,120244],"mapped",[117]],[[120245,120245],"mapped",[118]],[[120246,120246],"mapped",[119]],[[120247,120247],"mapped",[120]],[[120248,120248],"mapped",[121]],[[120249,120249],"mapped",[122]],[[120250,120250],"mapped",[97]],[[120251,120251],"mapped",[98]],[[120252,120252],"mapped",[99]],[[120253,120253],"mapped",[100]],[[120254,120254],"mapped",[101]],[[120255,120255],"mapped",[102]],[[120256,120256],"mapped",[103]],[[120257,120257],"mapped",[104]],[[120258,120258],"mapped",[105]],[[120259,120259],"mapped",[106]],[[120260,120260],"mapped",[107]],[[120261,120261],"mapped",[108]],[[120262,120262],"mapped",[109]],[[120263,120263],"mapped",[110]],[[120264,120264],"mapped",[111]],[[120265,120265],"mapped",[112]],[[120266,120266],"mapped",[113]],[[120267,120267],"mapped",[114]],[[120268,120268],"mapped",[115]],[[120269,120269],"mapped",[116]],[[120270,120270],"mapped",[117]],[[120271,120271],"mapped",[118]],[[120272,120272],"mapped",[119]],[[120273,120273],"mapped",[120]],[[120274,120274],"mapped",[121]],[[120275,120275],"mapped",[122]],[[120276,120276],"mapped",[97]],[[120277,120277],"mapped",[98]],[[120278,120278],"mapped",[99]],[[120279,120279],"mapped",[100]],[[120280,120280],"mapped",[101]],[[120281,120281],"mapped",[102]],[[120282,120282],"mapped",[103]],[[120283,120283],"mapped",[104]],[[120284,120284],"mapped",[105]],[[120285,120285],"mapped",[106]],[[120286,120286],"mapped",[107]],[[120287,120287],"mapped",[108]],[[120288,120288],"mapped",[109]],[[120289,120289],"mapped",[110]],[[120290,120290],"mapped",[111]],[[120291,120291],"mapped",[112]],[[120292,120292],"mapped",[113]],[[120293,120293],"mapped",[114]],[[120294,120294],"mapped",[115]],[[120295,120295],"mapped",[116]],[[120296,120296],"mapped",[117]],[[120297,120297],"mapped",[118]],[[120298,120298],"mapped",[119]],[[120299,120299],"mapped",[120]],[[120300,120300],"mapped",[121]],[[120301,120301],"mapped",[122]],[[120302,120302],"mapped",[97]],[[120303,120303],"mapped",[98]],[[120304,120304],"mapped",[99]],[[120305,120305],"mapped",[100]],[[120306,120306],"mapped",[101]],[[120307,120307],"mapped",[102]],[[120308,120308],"mapped",[103]],[[120309,120309],"mapped",[104]],[[120310,120310],"mapped",[105]],[[120311,120311],"mapped",[106]],[[120312,120312],"mapped",[107]],[[120313,120313],"mapped",[108]],[[120314,120314],"mapped",[109]],[[120315,120315],"mapped",[110]],[[120316,120316],"mapped",[111]],[[120317,120317],"mapped",[112]],[[120318,120318],"mapped",[113]],[[120319,120319],"mapped",[114]],[[120320,120320],"mapped",[115]],[[120321,120321],"mapped",[116]],[[120322,120322],"mapped",[117]],[[120323,120323],"mapped",[118]],[[120324,120324],"mapped",[119]],[[120325,120325],"mapped",[120]],[[120326,120326],"mapped",[121]],[[120327,120327],"mapped",[122]],[[120328,120328],"mapped",[97]],[[120329,120329],"mapped",[98]],[[120330,120330],"mapped",[99]],[[120331,120331],"mapped",[100]],[[120332,120332],"mapped",[101]],[[120333,120333],"mapped",[102]],[[120334,120334],"mapped",[103]],[[120335,120335],"mapped",[104]],[[120336,120336],"mapped",[105]],[[120337,120337],"mapped",[106]],[[120338,120338],"mapped",[107]],[[120339,120339],"mapped",[108]],[[120340,120340],"mapped",[109]],[[120341,120341],"mapped",[110]],[[120342,120342],"mapped",[111]],[[120343,120343],"mapped",[112]],[[120344,120344],"mapped",[113]],[[120345,120345],"mapped",[114]],[[120346,120346],"mapped",[115]],[[120347,120347],"mapped",[116]],[[120348,120348],"mapped",[117]],[[120349,120349],"mapped",[118]],[[120350,120350],"mapped",[119]],[[120351,120351],"mapped",[120]],[[120352,120352],"mapped",[121]],[[120353,120353],"mapped",[122]],[[120354,120354],"mapped",[97]],[[120355,120355],"mapped",[98]],[[120356,120356],"mapped",[99]],[[120357,120357],"mapped",[100]],[[120358,120358],"mapped",[101]],[[120359,120359],"mapped",[102]],[[120360,120360],"mapped",[103]],[[120361,120361],"mapped",[104]],[[120362,120362],"mapped",[105]],[[120363,120363],"mapped",[106]],[[120364,120364],"mapped",[107]],[[120365,120365],"mapped",[108]],[[120366,120366],"mapped",[109]],[[120367,120367],"mapped",[110]],[[120368,120368],"mapped",[111]],[[120369,120369],"mapped",[112]],[[120370,120370],"mapped",[113]],[[120371,120371],"mapped",[114]],[[120372,120372],"mapped",[115]],[[120373,120373],"mapped",[116]],[[120374,120374],"mapped",[117]],[[120375,120375],"mapped",[118]],[[120376,120376],"mapped",[119]],[[120377,120377],"mapped",[120]],[[120378,120378],"mapped",[121]],[[120379,120379],"mapped",[122]],[[120380,120380],"mapped",[97]],[[120381,120381],"mapped",[98]],[[120382,120382],"mapped",[99]],[[120383,120383],"mapped",[100]],[[120384,120384],"mapped",[101]],[[120385,120385],"mapped",[102]],[[120386,120386],"mapped",[103]],[[120387,120387],"mapped",[104]],[[120388,120388],"mapped",[105]],[[120389,120389],"mapped",[106]],[[120390,120390],"mapped",[107]],[[120391,120391],"mapped",[108]],[[120392,120392],"mapped",[109]],[[120393,120393],"mapped",[110]],[[120394,120394],"mapped",[111]],[[120395,120395],"mapped",[112]],[[120396,120396],"mapped",[113]],[[120397,120397],"mapped",[114]],[[120398,120398],"mapped",[115]],[[120399,120399],"mapped",[116]],[[120400,120400],"mapped",[117]],[[120401,120401],"mapped",[118]],[[120402,120402],"mapped",[119]],[[120403,120403],"mapped",[120]],[[120404,120404],"mapped",[121]],[[120405,120405],"mapped",[122]],[[120406,120406],"mapped",[97]],[[120407,120407],"mapped",[98]],[[120408,120408],"mapped",[99]],[[120409,120409],"mapped",[100]],[[120410,120410],"mapped",[101]],[[120411,120411],"mapped",[102]],[[120412,120412],"mapped",[103]],[[120413,120413],"mapped",[104]],[[120414,120414],"mapped",[105]],[[120415,120415],"mapped",[106]],[[120416,120416],"mapped",[107]],[[120417,120417],"mapped",[108]],[[120418,120418],"mapped",[109]],[[120419,120419],"mapped",[110]],[[120420,120420],"mapped",[111]],[[120421,120421],"mapped",[112]],[[120422,120422],"mapped",[113]],[[120423,120423],"mapped",[114]],[[120424,120424],"mapped",[115]],[[120425,120425],"mapped",[116]],[[120426,120426],"mapped",[117]],[[120427,120427],"mapped",[118]],[[120428,120428],"mapped",[119]],[[120429,120429],"mapped",[120]],[[120430,120430],"mapped",[121]],[[120431,120431],"mapped",[122]],[[120432,120432],"mapped",[97]],[[120433,120433],"mapped",[98]],[[120434,120434],"mapped",[99]],[[120435,120435],"mapped",[100]],[[120436,120436],"mapped",[101]],[[120437,120437],"mapped",[102]],[[120438,120438],"mapped",[103]],[[120439,120439],"mapped",[104]],[[120440,120440],"mapped",[105]],[[120441,120441],"mapped",[106]],[[120442,120442],"mapped",[107]],[[120443,120443],"mapped",[108]],[[120444,120444],"mapped",[109]],[[120445,120445],"mapped",[110]],[[120446,120446],"mapped",[111]],[[120447,120447],"mapped",[112]],[[120448,120448],"mapped",[113]],[[120449,120449],"mapped",[114]],[[120450,120450],"mapped",[115]],[[120451,120451],"mapped",[116]],[[120452,120452],"mapped",[117]],[[120453,120453],"mapped",[118]],[[120454,120454],"mapped",[119]],[[120455,120455],"mapped",[120]],[[120456,120456],"mapped",[121]],[[120457,120457],"mapped",[122]],[[120458,120458],"mapped",[97]],[[120459,120459],"mapped",[98]],[[120460,120460],"mapped",[99]],[[120461,120461],"mapped",[100]],[[120462,120462],"mapped",[101]],[[120463,120463],"mapped",[102]],[[120464,120464],"mapped",[103]],[[120465,120465],"mapped",[104]],[[120466,120466],"mapped",[105]],[[120467,120467],"mapped",[106]],[[120468,120468],"mapped",[107]],[[120469,120469],"mapped",[108]],[[120470,120470],"mapped",[109]],[[120471,120471],"mapped",[110]],[[120472,120472],"mapped",[111]],[[120473,120473],"mapped",[112]],[[120474,120474],"mapped",[113]],[[120475,120475],"mapped",[114]],[[120476,120476],"mapped",[115]],[[120477,120477],"mapped",[116]],[[120478,120478],"mapped",[117]],[[120479,120479],"mapped",[118]],[[120480,120480],"mapped",[119]],[[120481,120481],"mapped",[120]],[[120482,120482],"mapped",[121]],[[120483,120483],"mapped",[122]],[[120484,120484],"mapped",[305]],[[120485,120485],"mapped",[567]],[[120486,120487],"disallowed"],[[120488,120488],"mapped",[945]],[[120489,120489],"mapped",[946]],[[120490,120490],"mapped",[947]],[[120491,120491],"mapped",[948]],[[120492,120492],"mapped",[949]],[[120493,120493],"mapped",[950]],[[120494,120494],"mapped",[951]],[[120495,120495],"mapped",[952]],[[120496,120496],"mapped",[953]],[[120497,120497],"mapped",[954]],[[120498,120498],"mapped",[955]],[[120499,120499],"mapped",[956]],[[120500,120500],"mapped",[957]],[[120501,120501],"mapped",[958]],[[120502,120502],"mapped",[959]],[[120503,120503],"mapped",[960]],[[120504,120504],"mapped",[961]],[[120505,120505],"mapped",[952]],[[120506,120506],"mapped",[963]],[[120507,120507],"mapped",[964]],[[120508,120508],"mapped",[965]],[[120509,120509],"mapped",[966]],[[120510,120510],"mapped",[967]],[[120511,120511],"mapped",[968]],[[120512,120512],"mapped",[969]],[[120513,120513],"mapped",[8711]],[[120514,120514],"mapped",[945]],[[120515,120515],"mapped",[946]],[[120516,120516],"mapped",[947]],[[120517,120517],"mapped",[948]],[[120518,120518],"mapped",[949]],[[120519,120519],"mapped",[950]],[[120520,120520],"mapped",[951]],[[120521,120521],"mapped",[952]],[[120522,120522],"mapped",[953]],[[120523,120523],"mapped",[954]],[[120524,120524],"mapped",[955]],[[120525,120525],"mapped",[956]],[[120526,120526],"mapped",[957]],[[120527,120527],"mapped",[958]],[[120528,120528],"mapped",[959]],[[120529,120529],"mapped",[960]],[[120530,120530],"mapped",[961]],[[120531,120532],"mapped",[963]],[[120533,120533],"mapped",[964]],[[120534,120534],"mapped",[965]],[[120535,120535],"mapped",[966]],[[120536,120536],"mapped",[967]],[[120537,120537],"mapped",[968]],[[120538,120538],"mapped",[969]],[[120539,120539],"mapped",[8706]],[[120540,120540],"mapped",[949]],[[120541,120541],"mapped",[952]],[[120542,120542],"mapped",[954]],[[120543,120543],"mapped",[966]],[[120544,120544],"mapped",[961]],[[120545,120545],"mapped",[960]],[[120546,120546],"mapped",[945]],[[120547,120547],"mapped",[946]],[[120548,120548],"mapped",[947]],[[120549,120549],"mapped",[948]],[[120550,120550],"mapped",[949]],[[120551,120551],"mapped",[950]],[[120552,120552],"mapped",[951]],[[120553,120553],"mapped",[952]],[[120554,120554],"mapped",[953]],[[120555,120555],"mapped",[954]],[[120556,120556],"mapped",[955]],[[120557,120557],"mapped",[956]],[[120558,120558],"mapped",[957]],[[120559,120559],"mapped",[958]],[[120560,120560],"mapped",[959]],[[120561,120561],"mapped",[960]],[[120562,120562],"mapped",[961]],[[120563,120563],"mapped",[952]],[[120564,120564],"mapped",[963]],[[120565,120565],"mapped",[964]],[[120566,120566],"mapped",[965]],[[120567,120567],"mapped",[966]],[[120568,120568],"mapped",[967]],[[120569,120569],"mapped",[968]],[[120570,120570],"mapped",[969]],[[120571,120571],"mapped",[8711]],[[120572,120572],"mapped",[945]],[[120573,120573],"mapped",[946]],[[120574,120574],"mapped",[947]],[[120575,120575],"mapped",[948]],[[120576,120576],"mapped",[949]],[[120577,120577],"mapped",[950]],[[120578,120578],"mapped",[951]],[[120579,120579],"mapped",[952]],[[120580,120580],"mapped",[953]],[[120581,120581],"mapped",[954]],[[120582,120582],"mapped",[955]],[[120583,120583],"mapped",[956]],[[120584,120584],"mapped",[957]],[[120585,120585],"mapped",[958]],[[120586,120586],"mapped",[959]],[[120587,120587],"mapped",[960]],[[120588,120588],"mapped",[961]],[[120589,120590],"mapped",[963]],[[120591,120591],"mapped",[964]],[[120592,120592],"mapped",[965]],[[120593,120593],"mapped",[966]],[[120594,120594],"mapped",[967]],[[120595,120595],"mapped",[968]],[[120596,120596],"mapped",[969]],[[120597,120597],"mapped",[8706]],[[120598,120598],"mapped",[949]],[[120599,120599],"mapped",[952]],[[120600,120600],"mapped",[954]],[[120601,120601],"mapped",[966]],[[120602,120602],"mapped",[961]],[[120603,120603],"mapped",[960]],[[120604,120604],"mapped",[945]],[[120605,120605],"mapped",[946]],[[120606,120606],"mapped",[947]],[[120607,120607],"mapped",[948]],[[120608,120608],"mapped",[949]],[[120609,120609],"mapped",[950]],[[120610,120610],"mapped",[951]],[[120611,120611],"mapped",[952]],[[120612,120612],"mapped",[953]],[[120613,120613],"mapped",[954]],[[120614,120614],"mapped",[955]],[[120615,120615],"mapped",[956]],[[120616,120616],"mapped",[957]],[[120617,120617],"mapped",[958]],[[120618,120618],"mapped",[959]],[[120619,120619],"mapped",[960]],[[120620,120620],"mapped",[961]],[[120621,120621],"mapped",[952]],[[120622,120622],"mapped",[963]],[[120623,120623],"mapped",[964]],[[120624,120624],"mapped",[965]],[[120625,120625],"mapped",[966]],[[120626,120626],"mapped",[967]],[[120627,120627],"mapped",[968]],[[120628,120628],"mapped",[969]],[[120629,120629],"mapped",[8711]],[[120630,120630],"mapped",[945]],[[120631,120631],"mapped",[946]],[[120632,120632],"mapped",[947]],[[120633,120633],"mapped",[948]],[[120634,120634],"mapped",[949]],[[120635,120635],"mapped",[950]],[[120636,120636],"mapped",[951]],[[120637,120637],"mapped",[952]],[[120638,120638],"mapped",[953]],[[120639,120639],"mapped",[954]],[[120640,120640],"mapped",[955]],[[120641,120641],"mapped",[956]],[[120642,120642],"mapped",[957]],[[120643,120643],"mapped",[958]],[[120644,120644],"mapped",[959]],[[120645,120645],"mapped",[960]],[[120646,120646],"mapped",[961]],[[120647,120648],"mapped",[963]],[[120649,120649],"mapped",[964]],[[120650,120650],"mapped",[965]],[[120651,120651],"mapped",[966]],[[120652,120652],"mapped",[967]],[[120653,120653],"mapped",[968]],[[120654,120654],"mapped",[969]],[[120655,120655],"mapped",[8706]],[[120656,120656],"mapped",[949]],[[120657,120657],"mapped",[952]],[[120658,120658],"mapped",[954]],[[120659,120659],"mapped",[966]],[[120660,120660],"mapped",[961]],[[120661,120661],"mapped",[960]],[[120662,120662],"mapped",[945]],[[120663,120663],"mapped",[946]],[[120664,120664],"mapped",[947]],[[120665,120665],"mapped",[948]],[[120666,120666],"mapped",[949]],[[120667,120667],"mapped",[950]],[[120668,120668],"mapped",[951]],[[120669,120669],"mapped",[952]],[[120670,120670],"mapped",[953]],[[120671,120671],"mapped",[954]],[[120672,120672],"mapped",[955]],[[120673,120673],"mapped",[956]],[[120674,120674],"mapped",[957]],[[120675,120675],"mapped",[958]],[[120676,120676],"mapped",[959]],[[120677,120677],"mapped",[960]],[[120678,120678],"mapped",[961]],[[120679,120679],"mapped",[952]],[[120680,120680],"mapped",[963]],[[120681,120681],"mapped",[964]],[[120682,120682],"mapped",[965]],[[120683,120683],"mapped",[966]],[[120684,120684],"mapped",[967]],[[120685,120685],"mapped",[968]],[[120686,120686],"mapped",[969]],[[120687,120687],"mapped",[8711]],[[120688,120688],"mapped",[945]],[[120689,120689],"mapped",[946]],[[120690,120690],"mapped",[947]],[[120691,120691],"mapped",[948]],[[120692,120692],"mapped",[949]],[[120693,120693],"mapped",[950]],[[120694,120694],"mapped",[951]],[[120695,120695],"mapped",[952]],[[120696,120696],"mapped",[953]],[[120697,120697],"mapped",[954]],[[120698,120698],"mapped",[955]],[[120699,120699],"mapped",[956]],[[120700,120700],"mapped",[957]],[[120701,120701],"mapped",[958]],[[120702,120702],"mapped",[959]],[[120703,120703],"mapped",[960]],[[120704,120704],"mapped",[961]],[[120705,120706],"mapped",[963]],[[120707,120707],"mapped",[964]],[[120708,120708],"mapped",[965]],[[120709,120709],"mapped",[966]],[[120710,120710],"mapped",[967]],[[120711,120711],"mapped",[968]],[[120712,120712],"mapped",[969]],[[120713,120713],"mapped",[8706]],[[120714,120714],"mapped",[949]],[[120715,120715],"mapped",[952]],[[120716,120716],"mapped",[954]],[[120717,120717],"mapped",[966]],[[120718,120718],"mapped",[961]],[[120719,120719],"mapped",[960]],[[120720,120720],"mapped",[945]],[[120721,120721],"mapped",[946]],[[120722,120722],"mapped",[947]],[[120723,120723],"mapped",[948]],[[120724,120724],"mapped",[949]],[[120725,120725],"mapped",[950]],[[120726,120726],"mapped",[951]],[[120727,120727],"mapped",[952]],[[120728,120728],"mapped",[953]],[[120729,120729],"mapped",[954]],[[120730,120730],"mapped",[955]],[[120731,120731],"mapped",[956]],[[120732,120732],"mapped",[957]],[[120733,120733],"mapped",[958]],[[120734,120734],"mapped",[959]],[[120735,120735],"mapped",[960]],[[120736,120736],"mapped",[961]],[[120737,120737],"mapped",[952]],[[120738,120738],"mapped",[963]],[[120739,120739],"mapped",[964]],[[120740,120740],"mapped",[965]],[[120741,120741],"mapped",[966]],[[120742,120742],"mapped",[967]],[[120743,120743],"mapped",[968]],[[120744,120744],"mapped",[969]],[[120745,120745],"mapped",[8711]],[[120746,120746],"mapped",[945]],[[120747,120747],"mapped",[946]],[[120748,120748],"mapped",[947]],[[120749,120749],"mapped",[948]],[[120750,120750],"mapped",[949]],[[120751,120751],"mapped",[950]],[[120752,120752],"mapped",[951]],[[120753,120753],"mapped",[952]],[[120754,120754],"mapped",[953]],[[120755,120755],"mapped",[954]],[[120756,120756],"mapped",[955]],[[120757,120757],"mapped",[956]],[[120758,120758],"mapped",[957]],[[120759,120759],"mapped",[958]],[[120760,120760],"mapped",[959]],[[120761,120761],"mapped",[960]],[[120762,120762],"mapped",[961]],[[120763,120764],"mapped",[963]],[[120765,120765],"mapped",[964]],[[120766,120766],"mapped",[965]],[[120767,120767],"mapped",[966]],[[120768,120768],"mapped",[967]],[[120769,120769],"mapped",[968]],[[120770,120770],"mapped",[969]],[[120771,120771],"mapped",[8706]],[[120772,120772],"mapped",[949]],[[120773,120773],"mapped",[952]],[[120774,120774],"mapped",[954]],[[120775,120775],"mapped",[966]],[[120776,120776],"mapped",[961]],[[120777,120777],"mapped",[960]],[[120778,120779],"mapped",[989]],[[120780,120781],"disallowed"],[[120782,120782],"mapped",[48]],[[120783,120783],"mapped",[49]],[[120784,120784],"mapped",[50]],[[120785,120785],"mapped",[51]],[[120786,120786],"mapped",[52]],[[120787,120787],"mapped",[53]],[[120788,120788],"mapped",[54]],[[120789,120789],"mapped",[55]],[[120790,120790],"mapped",[56]],[[120791,120791],"mapped",[57]],[[120792,120792],"mapped",[48]],[[120793,120793],"mapped",[49]],[[120794,120794],"mapped",[50]],[[120795,120795],"mapped",[51]],[[120796,120796],"mapped",[52]],[[120797,120797],"mapped",[53]],[[120798,120798],"mapped",[54]],[[120799,120799],"mapped",[55]],[[120800,120800],"mapped",[56]],[[120801,120801],"mapped",[57]],[[120802,120802],"mapped",[48]],[[120803,120803],"mapped",[49]],[[120804,120804],"mapped",[50]],[[120805,120805],"mapped",[51]],[[120806,120806],"mapped",[52]],[[120807,120807],"mapped",[53]],[[120808,120808],"mapped",[54]],[[120809,120809],"mapped",[55]],[[120810,120810],"mapped",[56]],[[120811,120811],"mapped",[57]],[[120812,120812],"mapped",[48]],[[120813,120813],"mapped",[49]],[[120814,120814],"mapped",[50]],[[120815,120815],"mapped",[51]],[[120816,120816],"mapped",[52]],[[120817,120817],"mapped",[53]],[[120818,120818],"mapped",[54]],[[120819,120819],"mapped",[55]],[[120820,120820],"mapped",[56]],[[120821,120821],"mapped",[57]],[[120822,120822],"mapped",[48]],[[120823,120823],"mapped",[49]],[[120824,120824],"mapped",[50]],[[120825,120825],"mapped",[51]],[[120826,120826],"mapped",[52]],[[120827,120827],"mapped",[53]],[[120828,120828],"mapped",[54]],[[120829,120829],"mapped",[55]],[[120830,120830],"mapped",[56]],[[120831,120831],"mapped",[57]],[[120832,121343],"valid",[],"NV8"],[[121344,121398],"valid"],[[121399,121402],"valid",[],"NV8"],[[121403,121452],"valid"],[[121453,121460],"valid",[],"NV8"],[[121461,121461],"valid"],[[121462,121475],"valid",[],"NV8"],[[121476,121476],"valid"],[[121477,121483],"valid",[],"NV8"],[[121484,121498],"disallowed"],[[121499,121503],"valid"],[[121504,121504],"disallowed"],[[121505,121519],"valid"],[[121520,124927],"disallowed"],[[124928,125124],"valid"],[[125125,125126],"disallowed"],[[125127,125135],"valid",[],"NV8"],[[125136,125142],"valid"],[[125143,126463],"disallowed"],[[126464,126464],"mapped",[1575]],[[126465,126465],"mapped",[1576]],[[126466,126466],"mapped",[1580]],[[126467,126467],"mapped",[1583]],[[126468,126468],"disallowed"],[[126469,126469],"mapped",[1608]],[[126470,126470],"mapped",[1586]],[[126471,126471],"mapped",[1581]],[[126472,126472],"mapped",[1591]],[[126473,126473],"mapped",[1610]],[[126474,126474],"mapped",[1603]],[[126475,126475],"mapped",[1604]],[[126476,126476],"mapped",[1605]],[[126477,126477],"mapped",[1606]],[[126478,126478],"mapped",[1587]],[[126479,126479],"mapped",[1593]],[[126480,126480],"mapped",[1601]],[[126481,126481],"mapped",[1589]],[[126482,126482],"mapped",[1602]],[[126483,126483],"mapped",[1585]],[[126484,126484],"mapped",[1588]],[[126485,126485],"mapped",[1578]],[[126486,126486],"mapped",[1579]],[[126487,126487],"mapped",[1582]],[[126488,126488],"mapped",[1584]],[[126489,126489],"mapped",[1590]],[[126490,126490],"mapped",[1592]],[[126491,126491],"mapped",[1594]],[[126492,126492],"mapped",[1646]],[[126493,126493],"mapped",[1722]],[[126494,126494],"mapped",[1697]],[[126495,126495],"mapped",[1647]],[[126496,126496],"disallowed"],[[126497,126497],"mapped",[1576]],[[126498,126498],"mapped",[1580]],[[126499,126499],"disallowed"],[[126500,126500],"mapped",[1607]],[[126501,126502],"disallowed"],[[126503,126503],"mapped",[1581]],[[126504,126504],"disallowed"],[[126505,126505],"mapped",[1610]],[[126506,126506],"mapped",[1603]],[[126507,126507],"mapped",[1604]],[[126508,126508],"mapped",[1605]],[[126509,126509],"mapped",[1606]],[[126510,126510],"mapped",[1587]],[[126511,126511],"mapped",[1593]],[[126512,126512],"mapped",[1601]],[[126513,126513],"mapped",[1589]],[[126514,126514],"mapped",[1602]],[[126515,126515],"disallowed"],[[126516,126516],"mapped",[1588]],[[126517,126517],"mapped",[1578]],[[126518,126518],"mapped",[1579]],[[126519,126519],"mapped",[1582]],[[126520,126520],"disallowed"],[[126521,126521],"mapped",[1590]],[[126522,126522],"disallowed"],[[126523,126523],"mapped",[1594]],[[126524,126529],"disallowed"],[[126530,126530],"mapped",[1580]],[[126531,126534],"disallowed"],[[126535,126535],"mapped",[1581]],[[126536,126536],"disallowed"],[[126537,126537],"mapped",[1610]],[[126538,126538],"disallowed"],[[126539,126539],"mapped",[1604]],[[126540,126540],"disallowed"],[[126541,126541],"mapped",[1606]],[[126542,126542],"mapped",[1587]],[[126543,126543],"mapped",[1593]],[[126544,126544],"disallowed"],[[126545,126545],"mapped",[1589]],[[126546,126546],"mapped",[1602]],[[126547,126547],"disallowed"],[[126548,126548],"mapped",[1588]],[[126549,126550],"disallowed"],[[126551,126551],"mapped",[1582]],[[126552,126552],"disallowed"],[[126553,126553],"mapped",[1590]],[[126554,126554],"disallowed"],[[126555,126555],"mapped",[1594]],[[126556,126556],"disallowed"],[[126557,126557],"mapped",[1722]],[[126558,126558],"disallowed"],[[126559,126559],"mapped",[1647]],[[126560,126560],"disallowed"],[[126561,126561],"mapped",[1576]],[[126562,126562],"mapped",[1580]],[[126563,126563],"disallowed"],[[126564,126564],"mapped",[1607]],[[126565,126566],"disallowed"],[[126567,126567],"mapped",[1581]],[[126568,126568],"mapped",[1591]],[[126569,126569],"mapped",[1610]],[[126570,126570],"mapped",[1603]],[[126571,126571],"disallowed"],[[126572,126572],"mapped",[1605]],[[126573,126573],"mapped",[1606]],[[126574,126574],"mapped",[1587]],[[126575,126575],"mapped",[1593]],[[126576,126576],"mapped",[1601]],[[126577,126577],"mapped",[1589]],[[126578,126578],"mapped",[1602]],[[126579,126579],"disallowed"],[[126580,126580],"mapped",[1588]],[[126581,126581],"mapped",[1578]],[[126582,126582],"mapped",[1579]],[[126583,126583],"mapped",[1582]],[[126584,126584],"disallowed"],[[126585,126585],"mapped",[1590]],[[126586,126586],"mapped",[1592]],[[126587,126587],"mapped",[1594]],[[126588,126588],"mapped",[1646]],[[126589,126589],"disallowed"],[[126590,126590],"mapped",[1697]],[[126591,126591],"disallowed"],[[126592,126592],"mapped",[1575]],[[126593,126593],"mapped",[1576]],[[126594,126594],"mapped",[1580]],[[126595,126595],"mapped",[1583]],[[126596,126596],"mapped",[1607]],[[126597,126597],"mapped",[1608]],[[126598,126598],"mapped",[1586]],[[126599,126599],"mapped",[1581]],[[126600,126600],"mapped",[1591]],[[126601,126601],"mapped",[1610]],[[126602,126602],"disallowed"],[[126603,126603],"mapped",[1604]],[[126604,126604],"mapped",[1605]],[[126605,126605],"mapped",[1606]],[[126606,126606],"mapped",[1587]],[[126607,126607],"mapped",[1593]],[[126608,126608],"mapped",[1601]],[[126609,126609],"mapped",[1589]],[[126610,126610],"mapped",[1602]],[[126611,126611],"mapped",[1585]],[[126612,126612],"mapped",[1588]],[[126613,126613],"mapped",[1578]],[[126614,126614],"mapped",[1579]],[[126615,126615],"mapped",[1582]],[[126616,126616],"mapped",[1584]],[[126617,126617],"mapped",[1590]],[[126618,126618],"mapped",[1592]],[[126619,126619],"mapped",[1594]],[[126620,126624],"disallowed"],[[126625,126625],"mapped",[1576]],[[126626,126626],"mapped",[1580]],[[126627,126627],"mapped",[1583]],[[126628,126628],"disallowed"],[[126629,126629],"mapped",[1608]],[[126630,126630],"mapped",[1586]],[[126631,126631],"mapped",[1581]],[[126632,126632],"mapped",[1591]],[[126633,126633],"mapped",[1610]],[[126634,126634],"disallowed"],[[126635,126635],"mapped",[1604]],[[126636,126636],"mapped",[1605]],[[126637,126637],"mapped",[1606]],[[126638,126638],"mapped",[1587]],[[126639,126639],"mapped",[1593]],[[126640,126640],"mapped",[1601]],[[126641,126641],"mapped",[1589]],[[126642,126642],"mapped",[1602]],[[126643,126643],"mapped",[1585]],[[126644,126644],"mapped",[1588]],[[126645,126645],"mapped",[1578]],[[126646,126646],"mapped",[1579]],[[126647,126647],"mapped",[1582]],[[126648,126648],"mapped",[1584]],[[126649,126649],"mapped",[1590]],[[126650,126650],"mapped",[1592]],[[126651,126651],"mapped",[1594]],[[126652,126703],"disallowed"],[[126704,126705],"valid",[],"NV8"],[[126706,126975],"disallowed"],[[126976,127019],"valid",[],"NV8"],[[127020,127023],"disallowed"],[[127024,127123],"valid",[],"NV8"],[[127124,127135],"disallowed"],[[127136,127150],"valid",[],"NV8"],[[127151,127152],"disallowed"],[[127153,127166],"valid",[],"NV8"],[[127167,127167],"valid",[],"NV8"],[[127168,127168],"disallowed"],[[127169,127183],"valid",[],"NV8"],[[127184,127184],"disallowed"],[[127185,127199],"valid",[],"NV8"],[[127200,127221],"valid",[],"NV8"],[[127222,127231],"disallowed"],[[127232,127232],"disallowed"],[[127233,127233],"disallowed_STD3_mapped",[48,44]],[[127234,127234],"disallowed_STD3_mapped",[49,44]],[[127235,127235],"disallowed_STD3_mapped",[50,44]],[[127236,127236],"disallowed_STD3_mapped",[51,44]],[[127237,127237],"disallowed_STD3_mapped",[52,44]],[[127238,127238],"disallowed_STD3_mapped",[53,44]],[[127239,127239],"disallowed_STD3_mapped",[54,44]],[[127240,127240],"disallowed_STD3_mapped",[55,44]],[[127241,127241],"disallowed_STD3_mapped",[56,44]],[[127242,127242],"disallowed_STD3_mapped",[57,44]],[[127243,127244],"valid",[],"NV8"],[[127245,127247],"disallowed"],[[127248,127248],"disallowed_STD3_mapped",[40,97,41]],[[127249,127249],"disallowed_STD3_mapped",[40,98,41]],[[127250,127250],"disallowed_STD3_mapped",[40,99,41]],[[127251,127251],"disallowed_STD3_mapped",[40,100,41]],[[127252,127252],"disallowed_STD3_mapped",[40,101,41]],[[127253,127253],"disallowed_STD3_mapped",[40,102,41]],[[127254,127254],"disallowed_STD3_mapped",[40,103,41]],[[127255,127255],"disallowed_STD3_mapped",[40,104,41]],[[127256,127256],"disallowed_STD3_mapped",[40,105,41]],[[127257,127257],"disallowed_STD3_mapped",[40,106,41]],[[127258,127258],"disallowed_STD3_mapped",[40,107,41]],[[127259,127259],"disallowed_STD3_mapped",[40,108,41]],[[127260,127260],"disallowed_STD3_mapped",[40,109,41]],[[127261,127261],"disallowed_STD3_mapped",[40,110,41]],[[127262,127262],"disallowed_STD3_mapped",[40,111,41]],[[127263,127263],"disallowed_STD3_mapped",[40,112,41]],[[127264,127264],"disallowed_STD3_mapped",[40,113,41]],[[127265,127265],"disallowed_STD3_mapped",[40,114,41]],[[127266,127266],"disallowed_STD3_mapped",[40,115,41]],[[127267,127267],"disallowed_STD3_mapped",[40,116,41]],[[127268,127268],"disallowed_STD3_mapped",[40,117,41]],[[127269,127269],"disallowed_STD3_mapped",[40,118,41]],[[127270,127270],"disallowed_STD3_mapped",[40,119,41]],[[127271,127271],"disallowed_STD3_mapped",[40,120,41]],[[127272,127272],"disallowed_STD3_mapped",[40,121,41]],[[127273,127273],"disallowed_STD3_mapped",[40,122,41]],[[127274,127274],"mapped",[12308,115,12309]],[[127275,127275],"mapped",[99]],[[127276,127276],"mapped",[114]],[[127277,127277],"mapped",[99,100]],[[127278,127278],"mapped",[119,122]],[[127279,127279],"disallowed"],[[127280,127280],"mapped",[97]],[[127281,127281],"mapped",[98]],[[127282,127282],"mapped",[99]],[[127283,127283],"mapped",[100]],[[127284,127284],"mapped",[101]],[[127285,127285],"mapped",[102]],[[127286,127286],"mapped",[103]],[[127287,127287],"mapped",[104]],[[127288,127288],"mapped",[105]],[[127289,127289],"mapped",[106]],[[127290,127290],"mapped",[107]],[[127291,127291],"mapped",[108]],[[127292,127292],"mapped",[109]],[[127293,127293],"mapped",[110]],[[127294,127294],"mapped",[111]],[[127295,127295],"mapped",[112]],[[127296,127296],"mapped",[113]],[[127297,127297],"mapped",[114]],[[127298,127298],"mapped",[115]],[[127299,127299],"mapped",[116]],[[127300,127300],"mapped",[117]],[[127301,127301],"mapped",[118]],[[127302,127302],"mapped",[119]],[[127303,127303],"mapped",[120]],[[127304,127304],"mapped",[121]],[[127305,127305],"mapped",[122]],[[127306,127306],"mapped",[104,118]],[[127307,127307],"mapped",[109,118]],[[127308,127308],"mapped",[115,100]],[[127309,127309],"mapped",[115,115]],[[127310,127310],"mapped",[112,112,118]],[[127311,127311],"mapped",[119,99]],[[127312,127318],"valid",[],"NV8"],[[127319,127319],"valid",[],"NV8"],[[127320,127326],"valid",[],"NV8"],[[127327,127327],"valid",[],"NV8"],[[127328,127337],"valid",[],"NV8"],[[127338,127338],"mapped",[109,99]],[[127339,127339],"mapped",[109,100]],[[127340,127343],"disallowed"],[[127344,127352],"valid",[],"NV8"],[[127353,127353],"valid",[],"NV8"],[[127354,127354],"valid",[],"NV8"],[[127355,127356],"valid",[],"NV8"],[[127357,127358],"valid",[],"NV8"],[[127359,127359],"valid",[],"NV8"],[[127360,127369],"valid",[],"NV8"],[[127370,127373],"valid",[],"NV8"],[[127374,127375],"valid",[],"NV8"],[[127376,127376],"mapped",[100,106]],[[127377,127386],"valid",[],"NV8"],[[127387,127461],"disallowed"],[[127462,127487],"valid",[],"NV8"],[[127488,127488],"mapped",[12411,12363]],[[127489,127489],"mapped",[12467,12467]],[[127490,127490],"mapped",[12469]],[[127491,127503],"disallowed"],[[127504,127504],"mapped",[25163]],[[127505,127505],"mapped",[23383]],[[127506,127506],"mapped",[21452]],[[127507,127507],"mapped",[12487]],[[127508,127508],"mapped",[20108]],[[127509,127509],"mapped",[22810]],[[127510,127510],"mapped",[35299]],[[127511,127511],"mapped",[22825]],[[127512,127512],"mapped",[20132]],[[127513,127513],"mapped",[26144]],[[127514,127514],"mapped",[28961]],[[127515,127515],"mapped",[26009]],[[127516,127516],"mapped",[21069]],[[127517,127517],"mapped",[24460]],[[127518,127518],"mapped",[20877]],[[127519,127519],"mapped",[26032]],[[127520,127520],"mapped",[21021]],[[127521,127521],"mapped",[32066]],[[127522,127522],"mapped",[29983]],[[127523,127523],"mapped",[36009]],[[127524,127524],"mapped",[22768]],[[127525,127525],"mapped",[21561]],[[127526,127526],"mapped",[28436]],[[127527,127527],"mapped",[25237]],[[127528,127528],"mapped",[25429]],[[127529,127529],"mapped",[19968]],[[127530,127530],"mapped",[19977]],[[127531,127531],"mapped",[36938]],[[127532,127532],"mapped",[24038]],[[127533,127533],"mapped",[20013]],[[127534,127534],"mapped",[21491]],[[127535,127535],"mapped",[25351]],[[127536,127536],"mapped",[36208]],[[127537,127537],"mapped",[25171]],[[127538,127538],"mapped",[31105]],[[127539,127539],"mapped",[31354]],[[127540,127540],"mapped",[21512]],[[127541,127541],"mapped",[28288]],[[127542,127542],"mapped",[26377]],[[127543,127543],"mapped",[26376]],[[127544,127544],"mapped",[30003]],[[127545,127545],"mapped",[21106]],[[127546,127546],"mapped",[21942]],[[127547,127551],"disallowed"],[[127552,127552],"mapped",[12308,26412,12309]],[[127553,127553],"mapped",[12308,19977,12309]],[[127554,127554],"mapped",[12308,20108,12309]],[[127555,127555],"mapped",[12308,23433,12309]],[[127556,127556],"mapped",[12308,28857,12309]],[[127557,127557],"mapped",[12308,25171,12309]],[[127558,127558],"mapped",[12308,30423,12309]],[[127559,127559],"mapped",[12308,21213,12309]],[[127560,127560],"mapped",[12308,25943,12309]],[[127561,127567],"disallowed"],[[127568,127568],"mapped",[24471]],[[127569,127569],"mapped",[21487]],[[127570,127743],"disallowed"],[[127744,127776],"valid",[],"NV8"],[[127777,127788],"valid",[],"NV8"],[[127789,127791],"valid",[],"NV8"],[[127792,127797],"valid",[],"NV8"],[[127798,127798],"valid",[],"NV8"],[[127799,127868],"valid",[],"NV8"],[[127869,127869],"valid",[],"NV8"],[[127870,127871],"valid",[],"NV8"],[[127872,127891],"valid",[],"NV8"],[[127892,127903],"valid",[],"NV8"],[[127904,127940],"valid",[],"NV8"],[[127941,127941],"valid",[],"NV8"],[[127942,127946],"valid",[],"NV8"],[[127947,127950],"valid",[],"NV8"],[[127951,127955],"valid",[],"NV8"],[[127956,127967],"valid",[],"NV8"],[[127968,127984],"valid",[],"NV8"],[[127985,127991],"valid",[],"NV8"],[[127992,127999],"valid",[],"NV8"],[[128000,128062],"valid",[],"NV8"],[[128063,128063],"valid",[],"NV8"],[[128064,128064],"valid",[],"NV8"],[[128065,128065],"valid",[],"NV8"],[[128066,128247],"valid",[],"NV8"],[[128248,128248],"valid",[],"NV8"],[[128249,128252],"valid",[],"NV8"],[[128253,128254],"valid",[],"NV8"],[[128255,128255],"valid",[],"NV8"],[[128256,128317],"valid",[],"NV8"],[[128318,128319],"valid",[],"NV8"],[[128320,128323],"valid",[],"NV8"],[[128324,128330],"valid",[],"NV8"],[[128331,128335],"valid",[],"NV8"],[[128336,128359],"valid",[],"NV8"],[[128360,128377],"valid",[],"NV8"],[[128378,128378],"disallowed"],[[128379,128419],"valid",[],"NV8"],[[128420,128420],"disallowed"],[[128421,128506],"valid",[],"NV8"],[[128507,128511],"valid",[],"NV8"],[[128512,128512],"valid",[],"NV8"],[[128513,128528],"valid",[],"NV8"],[[128529,128529],"valid",[],"NV8"],[[128530,128532],"valid",[],"NV8"],[[128533,128533],"valid",[],"NV8"],[[128534,128534],"valid",[],"NV8"],[[128535,128535],"valid",[],"NV8"],[[128536,128536],"valid",[],"NV8"],[[128537,128537],"valid",[],"NV8"],[[128538,128538],"valid",[],"NV8"],[[128539,128539],"valid",[],"NV8"],[[128540,128542],"valid",[],"NV8"],[[128543,128543],"valid",[],"NV8"],[[128544,128549],"valid",[],"NV8"],[[128550,128551],"valid",[],"NV8"],[[128552,128555],"valid",[],"NV8"],[[128556,128556],"valid",[],"NV8"],[[128557,128557],"valid",[],"NV8"],[[128558,128559],"valid",[],"NV8"],[[128560,128563],"valid",[],"NV8"],[[128564,128564],"valid",[],"NV8"],[[128565,128576],"valid",[],"NV8"],[[128577,128578],"valid",[],"NV8"],[[128579,128580],"valid",[],"NV8"],[[128581,128591],"valid",[],"NV8"],[[128592,128639],"valid",[],"NV8"],[[128640,128709],"valid",[],"NV8"],[[128710,128719],"valid",[],"NV8"],[[128720,128720],"valid",[],"NV8"],[[128721,128735],"disallowed"],[[128736,128748],"valid",[],"NV8"],[[128749,128751],"disallowed"],[[128752,128755],"valid",[],"NV8"],[[128756,128767],"disallowed"],[[128768,128883],"valid",[],"NV8"],[[128884,128895],"disallowed"],[[128896,128980],"valid",[],"NV8"],[[128981,129023],"disallowed"],[[129024,129035],"valid",[],"NV8"],[[129036,129039],"disallowed"],[[129040,129095],"valid",[],"NV8"],[[129096,129103],"disallowed"],[[129104,129113],"valid",[],"NV8"],[[129114,129119],"disallowed"],[[129120,129159],"valid",[],"NV8"],[[129160,129167],"disallowed"],[[129168,129197],"valid",[],"NV8"],[[129198,129295],"disallowed"],[[129296,129304],"valid",[],"NV8"],[[129305,129407],"disallowed"],[[129408,129412],"valid",[],"NV8"],[[129413,129471],"disallowed"],[[129472,129472],"valid",[],"NV8"],[[129473,131069],"disallowed"],[[131070,131071],"disallowed"],[[131072,173782],"valid"],[[173783,173823],"disallowed"],[[173824,177972],"valid"],[[177973,177983],"disallowed"],[[177984,178205],"valid"],[[178206,178207],"disallowed"],[[178208,183969],"valid"],[[183970,194559],"disallowed"],[[194560,194560],"mapped",[20029]],[[194561,194561],"mapped",[20024]],[[194562,194562],"mapped",[20033]],[[194563,194563],"mapped",[131362]],[[194564,194564],"mapped",[20320]],[[194565,194565],"mapped",[20398]],[[194566,194566],"mapped",[20411]],[[194567,194567],"mapped",[20482]],[[194568,194568],"mapped",[20602]],[[194569,194569],"mapped",[20633]],[[194570,194570],"mapped",[20711]],[[194571,194571],"mapped",[20687]],[[194572,194572],"mapped",[13470]],[[194573,194573],"mapped",[132666]],[[194574,194574],"mapped",[20813]],[[194575,194575],"mapped",[20820]],[[194576,194576],"mapped",[20836]],[[194577,194577],"mapped",[20855]],[[194578,194578],"mapped",[132380]],[[194579,194579],"mapped",[13497]],[[194580,194580],"mapped",[20839]],[[194581,194581],"mapped",[20877]],[[194582,194582],"mapped",[132427]],[[194583,194583],"mapped",[20887]],[[194584,194584],"mapped",[20900]],[[194585,194585],"mapped",[20172]],[[194586,194586],"mapped",[20908]],[[194587,194587],"mapped",[20917]],[[194588,194588],"mapped",[168415]],[[194589,194589],"mapped",[20981]],[[194590,194590],"mapped",[20995]],[[194591,194591],"mapped",[13535]],[[194592,194592],"mapped",[21051]],[[194593,194593],"mapped",[21062]],[[194594,194594],"mapped",[21106]],[[194595,194595],"mapped",[21111]],[[194596,194596],"mapped",[13589]],[[194597,194597],"mapped",[21191]],[[194598,194598],"mapped",[21193]],[[194599,194599],"mapped",[21220]],[[194600,194600],"mapped",[21242]],[[194601,194601],"mapped",[21253]],[[194602,194602],"mapped",[21254]],[[194603,194603],"mapped",[21271]],[[194604,194604],"mapped",[21321]],[[194605,194605],"mapped",[21329]],[[194606,194606],"mapped",[21338]],[[194607,194607],"mapped",[21363]],[[194608,194608],"mapped",[21373]],[[194609,194611],"mapped",[21375]],[[194612,194612],"mapped",[133676]],[[194613,194613],"mapped",[28784]],[[194614,194614],"mapped",[21450]],[[194615,194615],"mapped",[21471]],[[194616,194616],"mapped",[133987]],[[194617,194617],"mapped",[21483]],[[194618,194618],"mapped",[21489]],[[194619,194619],"mapped",[21510]],[[194620,194620],"mapped",[21662]],[[194621,194621],"mapped",[21560]],[[194622,194622],"mapped",[21576]],[[194623,194623],"mapped",[21608]],[[194624,194624],"mapped",[21666]],[[194625,194625],"mapped",[21750]],[[194626,194626],"mapped",[21776]],[[194627,194627],"mapped",[21843]],[[194628,194628],"mapped",[21859]],[[194629,194630],"mapped",[21892]],[[194631,194631],"mapped",[21913]],[[194632,194632],"mapped",[21931]],[[194633,194633],"mapped",[21939]],[[194634,194634],"mapped",[21954]],[[194635,194635],"mapped",[22294]],[[194636,194636],"mapped",[22022]],[[194637,194637],"mapped",[22295]],[[194638,194638],"mapped",[22097]],[[194639,194639],"mapped",[22132]],[[194640,194640],"mapped",[20999]],[[194641,194641],"mapped",[22766]],[[194642,194642],"mapped",[22478]],[[194643,194643],"mapped",[22516]],[[194644,194644],"mapped",[22541]],[[194645,194645],"mapped",[22411]],[[194646,194646],"mapped",[22578]],[[194647,194647],"mapped",[22577]],[[194648,194648],"mapped",[22700]],[[194649,194649],"mapped",[136420]],[[194650,194650],"mapped",[22770]],[[194651,194651],"mapped",[22775]],[[194652,194652],"mapped",[22790]],[[194653,194653],"mapped",[22810]],[[194654,194654],"mapped",[22818]],[[194655,194655],"mapped",[22882]],[[194656,194656],"mapped",[136872]],[[194657,194657],"mapped",[136938]],[[194658,194658],"mapped",[23020]],[[194659,194659],"mapped",[23067]],[[194660,194660],"mapped",[23079]],[[194661,194661],"mapped",[23000]],[[194662,194662],"mapped",[23142]],[[194663,194663],"mapped",[14062]],[[194664,194664],"disallowed"],[[194665,194665],"mapped",[23304]],[[194666,194667],"mapped",[23358]],[[194668,194668],"mapped",[137672]],[[194669,194669],"mapped",[23491]],[[194670,194670],"mapped",[23512]],[[194671,194671],"mapped",[23527]],[[194672,194672],"mapped",[23539]],[[194673,194673],"mapped",[138008]],[[194674,194674],"mapped",[23551]],[[194675,194675],"mapped",[23558]],[[194676,194676],"disallowed"],[[194677,194677],"mapped",[23586]],[[194678,194678],"mapped",[14209]],[[194679,194679],"mapped",[23648]],[[194680,194680],"mapped",[23662]],[[194681,194681],"mapped",[23744]],[[194682,194682],"mapped",[23693]],[[194683,194683],"mapped",[138724]],[[194684,194684],"mapped",[23875]],[[194685,194685],"mapped",[138726]],[[194686,194686],"mapped",[23918]],[[194687,194687],"mapped",[23915]],[[194688,194688],"mapped",[23932]],[[194689,194689],"mapped",[24033]],[[194690,194690],"mapped",[24034]],[[194691,194691],"mapped",[14383]],[[194692,194692],"mapped",[24061]],[[194693,194693],"mapped",[24104]],[[194694,194694],"mapped",[24125]],[[194695,194695],"mapped",[24169]],[[194696,194696],"mapped",[14434]],[[194697,194697],"mapped",[139651]],[[194698,194698],"mapped",[14460]],[[194699,194699],"mapped",[24240]],[[194700,194700],"mapped",[24243]],[[194701,194701],"mapped",[24246]],[[194702,194702],"mapped",[24266]],[[194703,194703],"mapped",[172946]],[[194704,194704],"mapped",[24318]],[[194705,194706],"mapped",[140081]],[[194707,194707],"mapped",[33281]],[[194708,194709],"mapped",[24354]],[[194710,194710],"mapped",[14535]],[[194711,194711],"mapped",[144056]],[[194712,194712],"mapped",[156122]],[[194713,194713],"mapped",[24418]],[[194714,194714],"mapped",[24427]],[[194715,194715],"mapped",[14563]],[[194716,194716],"mapped",[24474]],[[194717,194717],"mapped",[24525]],[[194718,194718],"mapped",[24535]],[[194719,194719],"mapped",[24569]],[[194720,194720],"mapped",[24705]],[[194721,194721],"mapped",[14650]],[[194722,194722],"mapped",[14620]],[[194723,194723],"mapped",[24724]],[[194724,194724],"mapped",[141012]],[[194725,194725],"mapped",[24775]],[[194726,194726],"mapped",[24904]],[[194727,194727],"mapped",[24908]],[[194728,194728],"mapped",[24910]],[[194729,194729],"mapped",[24908]],[[194730,194730],"mapped",[24954]],[[194731,194731],"mapped",[24974]],[[194732,194732],"mapped",[25010]],[[194733,194733],"mapped",[24996]],[[194734,194734],"mapped",[25007]],[[194735,194735],"mapped",[25054]],[[194736,194736],"mapped",[25074]],[[194737,194737],"mapped",[25078]],[[194738,194738],"mapped",[25104]],[[194739,194739],"mapped",[25115]],[[194740,194740],"mapped",[25181]],[[194741,194741],"mapped",[25265]],[[194742,194742],"mapped",[25300]],[[194743,194743],"mapped",[25424]],[[194744,194744],"mapped",[142092]],[[194745,194745],"mapped",[25405]],[[194746,194746],"mapped",[25340]],[[194747,194747],"mapped",[25448]],[[194748,194748],"mapped",[25475]],[[194749,194749],"mapped",[25572]],[[194750,194750],"mapped",[142321]],[[194751,194751],"mapped",[25634]],[[194752,194752],"mapped",[25541]],[[194753,194753],"mapped",[25513]],[[194754,194754],"mapped",[14894]],[[194755,194755],"mapped",[25705]],[[194756,194756],"mapped",[25726]],[[194757,194757],"mapped",[25757]],[[194758,194758],"mapped",[25719]],[[194759,194759],"mapped",[14956]],[[194760,194760],"mapped",[25935]],[[194761,194761],"mapped",[25964]],[[194762,194762],"mapped",[143370]],[[194763,194763],"mapped",[26083]],[[194764,194764],"mapped",[26360]],[[194765,194765],"mapped",[26185]],[[194766,194766],"mapped",[15129]],[[194767,194767],"mapped",[26257]],[[194768,194768],"mapped",[15112]],[[194769,194769],"mapped",[15076]],[[194770,194770],"mapped",[20882]],[[194771,194771],"mapped",[20885]],[[194772,194772],"mapped",[26368]],[[194773,194773],"mapped",[26268]],[[194774,194774],"mapped",[32941]],[[194775,194775],"mapped",[17369]],[[194776,194776],"mapped",[26391]],[[194777,194777],"mapped",[26395]],[[194778,194778],"mapped",[26401]],[[194779,194779],"mapped",[26462]],[[194780,194780],"mapped",[26451]],[[194781,194781],"mapped",[144323]],[[194782,194782],"mapped",[15177]],[[194783,194783],"mapped",[26618]],[[194784,194784],"mapped",[26501]],[[194785,194785],"mapped",[26706]],[[194786,194786],"mapped",[26757]],[[194787,194787],"mapped",[144493]],[[194788,194788],"mapped",[26766]],[[194789,194789],"mapped",[26655]],[[194790,194790],"mapped",[26900]],[[194791,194791],"mapped",[15261]],[[194792,194792],"mapped",[26946]],[[194793,194793],"mapped",[27043]],[[194794,194794],"mapped",[27114]],[[194795,194795],"mapped",[27304]],[[194796,194796],"mapped",[145059]],[[194797,194797],"mapped",[27355]],[[194798,194798],"mapped",[15384]],[[194799,194799],"mapped",[27425]],[[194800,194800],"mapped",[145575]],[[194801,194801],"mapped",[27476]],[[194802,194802],"mapped",[15438]],[[194803,194803],"mapped",[27506]],[[194804,194804],"mapped",[27551]],[[194805,194805],"mapped",[27578]],[[194806,194806],"mapped",[27579]],[[194807,194807],"mapped",[146061]],[[194808,194808],"mapped",[138507]],[[194809,194809],"mapped",[146170]],[[194810,194810],"mapped",[27726]],[[194811,194811],"mapped",[146620]],[[194812,194812],"mapped",[27839]],[[194813,194813],"mapped",[27853]],[[194814,194814],"mapped",[27751]],[[194815,194815],"mapped",[27926]],[[194816,194816],"mapped",[27966]],[[194817,194817],"mapped",[28023]],[[194818,194818],"mapped",[27969]],[[194819,194819],"mapped",[28009]],[[194820,194820],"mapped",[28024]],[[194821,194821],"mapped",[28037]],[[194822,194822],"mapped",[146718]],[[194823,194823],"mapped",[27956]],[[194824,194824],"mapped",[28207]],[[194825,194825],"mapped",[28270]],[[194826,194826],"mapped",[15667]],[[194827,194827],"mapped",[28363]],[[194828,194828],"mapped",[28359]],[[194829,194829],"mapped",[147153]],[[194830,194830],"mapped",[28153]],[[194831,194831],"mapped",[28526]],[[194832,194832],"mapped",[147294]],[[194833,194833],"mapped",[147342]],[[194834,194834],"mapped",[28614]],[[194835,194835],"mapped",[28729]],[[194836,194836],"mapped",[28702]],[[194837,194837],"mapped",[28699]],[[194838,194838],"mapped",[15766]],[[194839,194839],"mapped",[28746]],[[194840,194840],"mapped",[28797]],[[194841,194841],"mapped",[28791]],[[194842,194842],"mapped",[28845]],[[194843,194843],"mapped",[132389]],[[194844,194844],"mapped",[28997]],[[194845,194845],"mapped",[148067]],[[194846,194846],"mapped",[29084]],[[194847,194847],"disallowed"],[[194848,194848],"mapped",[29224]],[[194849,194849],"mapped",[29237]],[[194850,194850],"mapped",[29264]],[[194851,194851],"mapped",[149000]],[[194852,194852],"mapped",[29312]],[[194853,194853],"mapped",[29333]],[[194854,194854],"mapped",[149301]],[[194855,194855],"mapped",[149524]],[[194856,194856],"mapped",[29562]],[[194857,194857],"mapped",[29579]],[[194858,194858],"mapped",[16044]],[[194859,194859],"mapped",[29605]],[[194860,194861],"mapped",[16056]],[[194862,194862],"mapped",[29767]],[[194863,194863],"mapped",[29788]],[[194864,194864],"mapped",[29809]],[[194865,194865],"mapped",[29829]],[[194866,194866],"mapped",[29898]],[[194867,194867],"mapped",[16155]],[[194868,194868],"mapped",[29988]],[[194869,194869],"mapped",[150582]],[[194870,194870],"mapped",[30014]],[[194871,194871],"mapped",[150674]],[[194872,194872],"mapped",[30064]],[[194873,194873],"mapped",[139679]],[[194874,194874],"mapped",[30224]],[[194875,194875],"mapped",[151457]],[[194876,194876],"mapped",[151480]],[[194877,194877],"mapped",[151620]],[[194878,194878],"mapped",[16380]],[[194879,194879],"mapped",[16392]],[[194880,194880],"mapped",[30452]],[[194881,194881],"mapped",[151795]],[[194882,194882],"mapped",[151794]],[[194883,194883],"mapped",[151833]],[[194884,194884],"mapped",[151859]],[[194885,194885],"mapped",[30494]],[[194886,194887],"mapped",[30495]],[[194888,194888],"mapped",[30538]],[[194889,194889],"mapped",[16441]],[[194890,194890],"mapped",[30603]],[[194891,194891],"mapped",[16454]],[[194892,194892],"mapped",[16534]],[[194893,194893],"mapped",[152605]],[[194894,194894],"mapped",[30798]],[[194895,194895],"mapped",[30860]],[[194896,194896],"mapped",[30924]],[[194897,194897],"mapped",[16611]],[[194898,194898],"mapped",[153126]],[[194899,194899],"mapped",[31062]],[[194900,194900],"mapped",[153242]],[[194901,194901],"mapped",[153285]],[[194902,194902],"mapped",[31119]],[[194903,194903],"mapped",[31211]],[[194904,194904],"mapped",[16687]],[[194905,194905],"mapped",[31296]],[[194906,194906],"mapped",[31306]],[[194907,194907],"mapped",[31311]],[[194908,194908],"mapped",[153980]],[[194909,194910],"mapped",[154279]],[[194911,194911],"disallowed"],[[194912,194912],"mapped",[16898]],[[194913,194913],"mapped",[154539]],[[194914,194914],"mapped",[31686]],[[194915,194915],"mapped",[31689]],[[194916,194916],"mapped",[16935]],[[194917,194917],"mapped",[154752]],[[194918,194918],"mapped",[31954]],[[194919,194919],"mapped",[17056]],[[194920,194920],"mapped",[31976]],[[194921,194921],"mapped",[31971]],[[194922,194922],"mapped",[32000]],[[194923,194923],"mapped",[155526]],[[194924,194924],"mapped",[32099]],[[194925,194925],"mapped",[17153]],[[194926,194926],"mapped",[32199]],[[194927,194927],"mapped",[32258]],[[194928,194928],"mapped",[32325]],[[194929,194929],"mapped",[17204]],[[194930,194930],"mapped",[156200]],[[194931,194931],"mapped",[156231]],[[194932,194932],"mapped",[17241]],[[194933,194933],"mapped",[156377]],[[194934,194934],"mapped",[32634]],[[194935,194935],"mapped",[156478]],[[194936,194936],"mapped",[32661]],[[194937,194937],"mapped",[32762]],[[194938,194938],"mapped",[32773]],[[194939,194939],"mapped",[156890]],[[194940,194940],"mapped",[156963]],[[194941,194941],"mapped",[32864]],[[194942,194942],"mapped",[157096]],[[194943,194943],"mapped",[32880]],[[194944,194944],"mapped",[144223]],[[194945,194945],"mapped",[17365]],[[194946,194946],"mapped",[32946]],[[194947,194947],"mapped",[33027]],[[194948,194948],"mapped",[17419]],[[194949,194949],"mapped",[33086]],[[194950,194950],"mapped",[23221]],[[194951,194951],"mapped",[157607]],[[194952,194952],"mapped",[157621]],[[194953,194953],"mapped",[144275]],[[194954,194954],"mapped",[144284]],[[194955,194955],"mapped",[33281]],[[194956,194956],"mapped",[33284]],[[194957,194957],"mapped",[36766]],[[194958,194958],"mapped",[17515]],[[194959,194959],"mapped",[33425]],[[194960,194960],"mapped",[33419]],[[194961,194961],"mapped",[33437]],[[194962,194962],"mapped",[21171]],[[194963,194963],"mapped",[33457]],[[194964,194964],"mapped",[33459]],[[194965,194965],"mapped",[33469]],[[194966,194966],"mapped",[33510]],[[194967,194967],"mapped",[158524]],[[194968,194968],"mapped",[33509]],[[194969,194969],"mapped",[33565]],[[194970,194970],"mapped",[33635]],[[194971,194971],"mapped",[33709]],[[194972,194972],"mapped",[33571]],[[194973,194973],"mapped",[33725]],[[194974,194974],"mapped",[33767]],[[194975,194975],"mapped",[33879]],[[194976,194976],"mapped",[33619]],[[194977,194977],"mapped",[33738]],[[194978,194978],"mapped",[33740]],[[194979,194979],"mapped",[33756]],[[194980,194980],"mapped",[158774]],[[194981,194981],"mapped",[159083]],[[194982,194982],"mapped",[158933]],[[194983,194983],"mapped",[17707]],[[194984,194984],"mapped",[34033]],[[194985,194985],"mapped",[34035]],[[194986,194986],"mapped",[34070]],[[194987,194987],"mapped",[160714]],[[194988,194988],"mapped",[34148]],[[194989,194989],"mapped",[159532]],[[194990,194990],"mapped",[17757]],[[194991,194991],"mapped",[17761]],[[194992,194992],"mapped",[159665]],[[194993,194993],"mapped",[159954]],[[194994,194994],"mapped",[17771]],[[194995,194995],"mapped",[34384]],[[194996,194996],"mapped",[34396]],[[194997,194997],"mapped",[34407]],[[194998,194998],"mapped",[34409]],[[194999,194999],"mapped",[34473]],[[195000,195000],"mapped",[34440]],[[195001,195001],"mapped",[34574]],[[195002,195002],"mapped",[34530]],[[195003,195003],"mapped",[34681]],[[195004,195004],"mapped",[34600]],[[195005,195005],"mapped",[34667]],[[195006,195006],"mapped",[34694]],[[195007,195007],"disallowed"],[[195008,195008],"mapped",[34785]],[[195009,195009],"mapped",[34817]],[[195010,195010],"mapped",[17913]],[[195011,195011],"mapped",[34912]],[[195012,195012],"mapped",[34915]],[[195013,195013],"mapped",[161383]],[[195014,195014],"mapped",[35031]],[[195015,195015],"mapped",[35038]],[[195016,195016],"mapped",[17973]],[[195017,195017],"mapped",[35066]],[[195018,195018],"mapped",[13499]],[[195019,195019],"mapped",[161966]],[[195020,195020],"mapped",[162150]],[[195021,195021],"mapped",[18110]],[[195022,195022],"mapped",[18119]],[[195023,195023],"mapped",[35488]],[[195024,195024],"mapped",[35565]],[[195025,195025],"mapped",[35722]],[[195026,195026],"mapped",[35925]],[[195027,195027],"mapped",[162984]],[[195028,195028],"mapped",[36011]],[[195029,195029],"mapped",[36033]],[[195030,195030],"mapped",[36123]],[[195031,195031],"mapped",[36215]],[[195032,195032],"mapped",[163631]],[[195033,195033],"mapped",[133124]],[[195034,195034],"mapped",[36299]],[[195035,195035],"mapped",[36284]],[[195036,195036],"mapped",[36336]],[[195037,195037],"mapped",[133342]],[[195038,195038],"mapped",[36564]],[[195039,195039],"mapped",[36664]],[[195040,195040],"mapped",[165330]],[[195041,195041],"mapped",[165357]],[[195042,195042],"mapped",[37012]],[[195043,195043],"mapped",[37105]],[[195044,195044],"mapped",[37137]],[[195045,195045],"mapped",[165678]],[[195046,195046],"mapped",[37147]],[[195047,195047],"mapped",[37432]],[[195048,195048],"mapped",[37591]],[[195049,195049],"mapped",[37592]],[[195050,195050],"mapped",[37500]],[[195051,195051],"mapped",[37881]],[[195052,195052],"mapped",[37909]],[[195053,195053],"mapped",[166906]],[[195054,195054],"mapped",[38283]],[[195055,195055],"mapped",[18837]],[[195056,195056],"mapped",[38327]],[[195057,195057],"mapped",[167287]],[[195058,195058],"mapped",[18918]],[[195059,195059],"mapped",[38595]],[[195060,195060],"mapped",[23986]],[[195061,195061],"mapped",[38691]],[[195062,195062],"mapped",[168261]],[[195063,195063],"mapped",[168474]],[[195064,195064],"mapped",[19054]],[[195065,195065],"mapped",[19062]],[[195066,195066],"mapped",[38880]],[[195067,195067],"mapped",[168970]],[[195068,195068],"mapped",[19122]],[[195069,195069],"mapped",[169110]],[[195070,195071],"mapped",[38923]],[[195072,195072],"mapped",[38953]],[[195073,195073],"mapped",[169398]],[[195074,195074],"mapped",[39138]],[[195075,195075],"mapped",[19251]],[[195076,195076],"mapped",[39209]],[[195077,195077],"mapped",[39335]],[[195078,195078],"mapped",[39362]],[[195079,195079],"mapped",[39422]],[[195080,195080],"mapped",[19406]],[[195081,195081],"mapped",[170800]],[[195082,195082],"mapped",[39698]],[[195083,195083],"mapped",[40000]],[[195084,195084],"mapped",[40189]],[[195085,195085],"mapped",[19662]],[[195086,195086],"mapped",[19693]],[[195087,195087],"mapped",[40295]],[[195088,195088],"mapped",[172238]],[[195089,195089],"mapped",[19704]],[[195090,195090],"mapped",[172293]],[[195091,195091],"mapped",[172558]],[[195092,195092],"mapped",[172689]],[[195093,195093],"mapped",[40635]],[[195094,195094],"mapped",[19798]],[[195095,195095],"mapped",[40697]],[[195096,195096],"mapped",[40702]],[[195097,195097],"mapped",[40709]],[[195098,195098],"mapped",[40719]],[[195099,195099],"mapped",[40726]],[[195100,195100],"mapped",[40763]],[[195101,195101],"mapped",[173568]],[[195102,196605],"disallowed"],[[196606,196607],"disallowed"],[[196608,262141],"disallowed"],[[262142,262143],"disallowed"],[[262144,327677],"disallowed"],[[327678,327679],"disallowed"],[[327680,393213],"disallowed"],[[393214,393215],"disallowed"],[[393216,458749],"disallowed"],[[458750,458751],"disallowed"],[[458752,524285],"disallowed"],[[524286,524287],"disallowed"],[[524288,589821],"disallowed"],[[589822,589823],"disallowed"],[[589824,655357],"disallowed"],[[655358,655359],"disallowed"],[[655360,720893],"disallowed"],[[720894,720895],"disallowed"],[[720896,786429],"disallowed"],[[786430,786431],"disallowed"],[[786432,851965],"disallowed"],[[851966,851967],"disallowed"],[[851968,917501],"disallowed"],[[917502,917503],"disallowed"],[[917504,917504],"disallowed"],[[917505,917505],"disallowed"],[[917506,917535],"disallowed"],[[917536,917631],"disallowed"],[[917632,917759],"disallowed"],[[917760,917999],"ignored"],[[918000,983037],"disallowed"],[[983038,983039],"disallowed"],[[983040,1048573],"disallowed"],[[1048574,1048575],"disallowed"],[[1048576,1114109],"disallowed"],[[1114110,1114111],"disallowed"]]'); + +/***/ }), + +/***/ 4244: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('[["8740","䏰䰲䘃䖦䕸𧉧䵷䖳𧲱䳢𧳅㮕䜶䝄䱇䱀𤊿𣘗𧍒𦺋𧃒䱗𪍑䝏䗚䲅𧱬䴇䪤䚡𦬣爥𥩔𡩣𣸆𣽡晍囻"],["8767","綕夝𨮹㷴霴𧯯寛𡵞媤㘥𩺰嫑宷峼杮薓𩥅瑡璝㡵𡵓𣚞𦀡㻬"],["87a1","𥣞㫵竼龗𤅡𨤍𣇪𠪊𣉞䌊蒄龖鐯䤰蘓墖靊鈘秐稲晠権袝瑌篅枂稬剏遆㓦珄𥶹瓆鿇垳䤯呌䄱𣚎堘穲𧭥讏䚮𦺈䆁𥶙箮𢒼鿈𢓁𢓉𢓌鿉蔄𣖻䂴鿊䓡𪷿拁灮鿋"],["8840","㇀",4,"𠄌㇅𠃑𠃍㇆㇇𠃋𡿨㇈𠃊㇉㇊㇋㇌𠄎㇍㇎ĀÁǍÀĒÉĚÈŌÓǑÒ࿿Ê̄Ế࿿Ê̌ỀÊāáǎàɑēéěèīíǐìōóǒòūúǔùǖǘǚ"],["88a1","ǜü࿿ê̄ế࿿ê̌ềêɡ⏚⏛"],["8940","𪎩𡅅"],["8943","攊"],["8946","丽滝鵎釟"],["894c","𧜵撑会伨侨兖兴农凤务动医华发变团声处备夲头学实実岚庆总斉柾栄桥济炼电纤纬纺织经统缆缷艺苏药视设询车轧轮"],["89a1","琑糼緍楆竉刧"],["89ab","醌碸酞肼"],["89b0","贋胶𠧧"],["89b5","肟黇䳍鷉鸌䰾𩷶𧀎鸊𪄳㗁"],["89c1","溚舾甙"],["89c5","䤑马骏龙禇𨑬𡷊𠗐𢫦两亁亀亇亿仫伷㑌侽㹈倃傈㑽㒓㒥円夅凛凼刅争剹劐匧㗇厩㕑厰㕓参吣㕭㕲㚁咓咣咴咹哐哯唘唣唨㖘唿㖥㖿嗗㗅"],["8a40","𧶄唥"],["8a43","𠱂𠴕𥄫喐𢳆㧬𠍁蹆𤶸𩓥䁓𨂾睺𢰸㨴䟕𨅝𦧲𤷪擝𠵼𠾴𠳕𡃴撍蹾𠺖𠰋𠽤𢲩𨉖𤓓"],["8a64","𠵆𩩍𨃩䟴𤺧𢳂骲㩧𩗴㿭㔆𥋇𩟔𧣈𢵄鵮頕"],["8a76","䏙𦂥撴哣𢵌𢯊𡁷㧻𡁯"],["8aa1","𦛚𦜖𧦠擪𥁒𠱃蹨𢆡𨭌𠜱"],["8aac","䠋𠆩㿺塳𢶍"],["8ab2","𤗈𠓼𦂗𠽌𠶖啹䂻䎺"],["8abb","䪴𢩦𡂝膪飵𠶜捹㧾𢝵跀嚡摼㹃"],["8ac9","𪘁𠸉𢫏𢳉"],["8ace","𡃈𣧂㦒㨆𨊛㕸𥹉𢃇噒𠼱𢲲𩜠㒼氽𤸻"],["8adf","𧕴𢺋𢈈𪙛𨳍𠹺𠰴𦠜羓𡃏𢠃𢤹㗻𥇣𠺌𠾍𠺪㾓𠼰𠵇𡅏𠹌"],["8af6","𠺫𠮩𠵈𡃀𡄽㿹𢚖搲𠾭"],["8b40","𣏴𧘹𢯎𠵾𠵿𢱑𢱕㨘𠺘𡃇𠼮𪘲𦭐𨳒𨶙𨳊閪哌苄喹"],["8b55","𩻃鰦骶𧝞𢷮煀腭胬尜𦕲脴㞗卟𨂽醶𠻺𠸏𠹷𠻻㗝𤷫㘉𠳖嚯𢞵𡃉𠸐𠹸𡁸𡅈𨈇𡑕𠹹𤹐𢶤婔𡀝𡀞𡃵𡃶垜𠸑"],["8ba1","𧚔𨋍𠾵𠹻𥅾㜃𠾶𡆀𥋘𪊽𤧚𡠺𤅷𨉼墙剨㘚𥜽箲孨䠀䬬鼧䧧鰟鮍𥭴𣄽嗻㗲嚉丨夂𡯁屮靑𠂆乛亻㔾尣彑忄㣺扌攵歺氵氺灬爫丬犭𤣩罒礻糹罓𦉪㓁"],["8bde","𦍋耂肀𦘒𦥑卝衤见𧢲讠贝钅镸长门𨸏韦页风飞饣𩠐鱼鸟黄歯龜丷𠂇阝户钢"],["8c40","倻淾𩱳龦㷉袏𤅎灷峵䬠𥇍㕙𥴰愢𨨲辧釶熑朙玺𣊁𪄇㲋𡦀䬐磤琂冮𨜏䀉橣𪊺䈣蘏𠩯稪𩥇𨫪靕灍匤𢁾鏴盙𨧣龧矝亣俰傼丯众龨吴綋墒壐𡶶庒庙忂𢜒斋"],["8ca1","𣏹椙橃𣱣泿"],["8ca7","爀𤔅玌㻛𤨓嬕璹讃𥲤𥚕窓篬糃繬苸薗龩袐龪躹龫迏蕟駠鈡龬𨶹𡐿䁱䊢娚"],["8cc9","顨杫䉶圽"],["8cce","藖𤥻芿𧄍䲁𦵴嵻𦬕𦾾龭龮宖龯曧繛湗秊㶈䓃𣉖𢞖䎚䔶"],["8ce6","峕𣬚諹屸㴒𣕑嵸龲煗䕘𤃬𡸣䱷㥸㑊𠆤𦱁諌侴𠈹妿腬顖𩣺弻"],["8d40","𠮟"],["8d42","𢇁𨥭䄂䚻𩁹㼇龳𪆵䃸㟖䛷𦱆䅼𨚲𧏿䕭㣔𥒚䕡䔛䶉䱻䵶䗪㿈𤬏㙡䓞䒽䇭崾嵈嵖㷼㠏嶤嶹㠠㠸幂庽弥徃㤈㤔㤿㥍惗愽峥㦉憷憹懏㦸戬抐拥挘㧸嚱"],["8da1","㨃揢揻搇摚㩋擀崕嘡龟㪗斆㪽旿晓㫲暒㬢朖㭂枤栀㭘桊梄㭲㭱㭻椉楃牜楤榟榅㮼槖㯝橥橴橱檂㯬檙㯲檫檵櫔櫶殁毁毪汵沪㳋洂洆洦涁㳯涤涱渕渘温溆𨧀溻滢滚齿滨滩漤漴㵆𣽁澁澾㵪㵵熷岙㶊瀬㶑灐灔灯灿炉𠌥䏁㗱𠻘"],["8e40","𣻗垾𦻓焾𥟠㙎榢𨯩孴穉𥣡𩓙穥穽𥦬窻窰竂竃燑𦒍䇊竚竝竪䇯咲𥰁笋筕笩𥌎𥳾箢筯莜𥮴𦱿篐萡箒箸𥴠㶭𥱥蒒篺簆簵𥳁籄粃𤢂粦晽𤕸糉糇糦籴糳糵糎"],["8ea1","繧䔝𦹄絝𦻖璍綉綫焵綳緒𤁗𦀩緤㴓緵𡟹緥𨍭縝𦄡𦅚繮纒䌫鑬縧罀罁罇礶𦋐駡羗𦍑羣𡙡𠁨䕜𣝦䔃𨌺翺𦒉者耈耝耨耯𪂇𦳃耻耼聡𢜔䦉𦘦𣷣𦛨朥肧𨩈脇脚墰𢛶汿𦒘𤾸擧𡒊舘𡡞橓𤩥𤪕䑺舩𠬍𦩒𣵾俹𡓽蓢荢𦬊𤦧𣔰𡝳𣷸芪椛芳䇛"],["8f40","蕋苐茚𠸖𡞴㛁𣅽𣕚艻苢茘𣺋𦶣𦬅𦮗𣗎㶿茝嗬莅䔋𦶥莬菁菓㑾𦻔橗蕚㒖𦹂𢻯葘𥯤葱㷓䓤檧葊𣲵祘蒨𦮖𦹷𦹃蓞萏莑䒠蒓蓤𥲑䉀𥳀䕃蔴嫲𦺙䔧蕳䔖枿蘖"],["8fa1","𨘥𨘻藁𧂈蘂𡖂𧃍䕫䕪蘨㙈𡢢号𧎚虾蝱𪃸蟮𢰧螱蟚蠏噡虬桖䘏衅衆𧗠𣶹𧗤衞袜䙛袴袵揁装睷𧜏覇覊覦覩覧覼𨨥觧𧤤𧪽誜瞓釾誐𧩙竩𧬺𣾏䜓𧬸煼謌謟𥐰𥕥謿譌譍誩𤩺讐讛誯𡛟䘕衏貛𧵔𧶏貫㜥𧵓賖𧶘𧶽贒贃𡤐賛灜贑𤳉㻐起"],["9040","趩𨀂𡀔𤦊㭼𨆼𧄌竧躭躶軃鋔輙輭𨍥𨐒辥錃𪊟𠩐辳䤪𨧞𨔽𣶻廸𣉢迹𪀔𨚼𨔁𢌥㦀𦻗逷𨔼𧪾遡𨕬𨘋邨𨜓郄𨛦邮都酧㫰醩釄粬𨤳𡺉鈎沟鉁鉢𥖹銹𨫆𣲛𨬌𥗛"],["90a1","𠴱錬鍫𨫡𨯫炏嫃𨫢𨫥䥥鉄𨯬𨰹𨯿鍳鑛躼閅閦鐦閠濶䊹𢙺𨛘𡉼𣸮䧟氜陻隖䅬隣𦻕懚隶磵𨫠隽双䦡𦲸𠉴𦐐𩂯𩃥𤫑𡤕𣌊霱虂霶䨏䔽䖅𤫩灵孁霛靜𩇕靗孊𩇫靟鐥僐𣂷𣂼鞉鞟鞱鞾韀韒韠𥑬韮琜𩐳響韵𩐝𧥺䫑頴頳顋顦㬎𧅵㵑𠘰𤅜"],["9140","𥜆飊颷飈飇䫿𦴧𡛓喰飡飦飬鍸餹𤨩䭲𩡗𩤅駵騌騻騐驘𥜥㛄𩂱𩯕髠髢𩬅髴䰎鬔鬭𨘀倴鬴𦦨㣃𣁽魐魀𩴾婅𡡣鮎𤉋鰂鯿鰌𩹨鷔𩾷𪆒𪆫𪃡𪄣𪇟鵾鶃𪄴鸎梈"],["91a1","鷄𢅛𪆓𪈠𡤻𪈳鴹𪂹𪊴麐麕麞麢䴴麪麯𤍤黁㭠㧥㴝伲㞾𨰫鼂鼈䮖鐤𦶢鼗鼖鼹嚟嚊齅馸𩂋韲葿齢齩竜龎爖䮾𤥵𤦻煷𤧸𤍈𤩑玞𨯚𡣺禟𨥾𨸶鍩鏳𨩄鋬鎁鏋𨥬𤒹爗㻫睲穃烐𤑳𤏸煾𡟯炣𡢾𣖙㻇𡢅𥐯𡟸㜢𡛻𡠹㛡𡝴𡣑𥽋㜣𡛀坛𤨥𡏾𡊨"],["9240","𡏆𡒶蔃𣚦蔃葕𤦔𧅥𣸱𥕜𣻻𧁒䓴𣛮𩦝𦼦柹㜳㰕㷧塬𡤢栐䁗𣜿𤃡𤂋𤄏𦰡哋嚞𦚱嚒𠿟𠮨𠸍鏆𨬓鎜仸儫㠙𤐶亼𠑥𠍿佋侊𥙑婨𠆫𠏋㦙𠌊𠐔㐵伩𠋀𨺳𠉵諚𠈌亘"],["92a1","働儍侢伃𤨎𣺊佂倮偬傁俌俥偘僼兙兛兝兞湶𣖕𣸹𣺿浲𡢄𣺉冨凃𠗠䓝𠒣𠒒𠒑赺𨪜𠜎剙劤𠡳勡鍮䙺熌𤎌𠰠𤦬𡃤槑𠸝瑹㻞璙琔瑖玘䮎𤪼𤂍叐㖄爏𤃉喴𠍅响𠯆圝鉝雴鍦埝垍坿㘾壋媙𨩆𡛺𡝯𡜐娬妸銏婾嫏娒𥥆𡧳𡡡𤊕㛵洅瑃娡𥺃"],["9340","媁𨯗𠐓鏠璌𡌃焅䥲鐈𨧻鎽㞠尞岞幞幈𡦖𡥼𣫮廍孏𡤃𡤄㜁𡢠㛝𡛾㛓脪𨩇𡶺𣑲𨦨弌弎𡤧𡞫婫𡜻孄蘔𧗽衠恾𢡠𢘫忛㺸𢖯𢖾𩂈𦽳懀𠀾𠁆𢘛憙憘恵𢲛𢴇𤛔𩅍"],["93a1","摱𤙥𢭪㨩𢬢𣑐𩣪𢹸挷𪑛撶挱揑𤧣𢵧护𢲡搻敫楲㯴𣂎𣊭𤦉𣊫唍𣋠𡣙𩐿曎𣊉𣆳㫠䆐𥖄𨬢𥖏𡛼𥕛𥐥磮𣄃𡠪𣈴㑤𣈏𣆂𤋉暎𦴤晫䮓昰𧡰𡷫晣𣋒𣋡昞𥡲㣑𣠺𣞼㮙𣞢𣏾瓐㮖枏𤘪梶栞㯄檾㡣𣟕𤒇樳橒櫉欅𡤒攑梘橌㯗橺歗𣿀𣲚鎠鋲𨯪𨫋"],["9440","銉𨀞𨧜鑧涥漋𤧬浧𣽿㶏渄𤀼娽渊塇洤硂焻𤌚𤉶烱牐犇犔𤞏𤜥兹𤪤𠗫瑺𣻸𣙟𤩊𤤗𥿡㼆㺱𤫟𨰣𣼵悧㻳瓌琼鎇琷䒟𦷪䕑疃㽣𤳙𤴆㽘畕癳𪗆㬙瑨𨫌𤦫𤦎㫻"],["94a1","㷍𤩎㻿𤧅𤣳釺圲鍂𨫣𡡤僟𥈡𥇧睸𣈲眎眏睻𤚗𣞁㩞𤣰琸璛㺿𤪺𤫇䃈𤪖𦆮錇𥖁砞碍碈磒珐祙𧝁𥛣䄎禛蒖禥樭𣻺稺秴䅮𡛦䄲鈵秱𠵌𤦌𠊙𣶺𡝮㖗啫㕰㚪𠇔𠰍竢婙𢛵𥪯𥪜娍𠉛磰娪𥯆竾䇹籝籭䈑𥮳𥺼𥺦糍𤧹𡞰粎籼粮檲緜縇緓罎𦉡"],["9540","𦅜𧭈綗𥺂䉪𦭵𠤖柖𠁎𣗏埄𦐒𦏸𤥢翝笧𠠬𥫩𥵃笌𥸎駦虅驣樜𣐿㧢𤧷𦖭騟𦖠蒀𧄧𦳑䓪脷䐂胆脉腂𦞴飃𦩂艢艥𦩑葓𦶧蘐𧈛媆䅿𡡀嬫𡢡嫤𡣘蚠蜨𣶏蠭𧐢娂"],["95a1","衮佅袇袿裦襥襍𥚃襔𧞅𧞄𨯵𨯙𨮜𨧹㺭蒣䛵䛏㟲訽訜𩑈彍鈫𤊄旔焩烄𡡅鵭貟賩𧷜妚矃姰䍮㛔踪躧𤰉輰轊䋴汘澻𢌡䢛潹溋𡟚鯩㚵𤤯邻邗啱䤆醻鐄𨩋䁢𨫼鐧𨰝𨰻蓥訫閙閧閗閖𨴴瑅㻂𤣿𤩂𤏪㻧𣈥随𨻧𨹦𨹥㻌𤧭𤩸𣿮琒瑫㻼靁𩂰"],["9640","桇䨝𩂓𥟟靝鍨𨦉𨰦𨬯𦎾銺嬑譩䤼珹𤈛鞛靱餸𠼦巁𨯅𤪲頟𩓚鋶𩗗釥䓀𨭐𤩧𨭤飜𨩅㼀鈪䤥萔餻饍𧬆㷽馛䭯馪驜𨭥𥣈檏騡嫾騯𩣱䮐𩥈馼䮽䮗鍽塲𡌂堢𤦸"],["96a1","𡓨硄𢜟𣶸棅㵽鑘㤧慐𢞁𢥫愇鱏鱓鱻鰵鰐魿鯏𩸭鮟𪇵𪃾鴡䲮𤄄鸘䲰鴌𪆴𪃭𪃳𩤯鶥蒽𦸒𦿟𦮂藼䔳𦶤𦺄𦷰萠藮𦸀𣟗𦁤秢𣖜𣙀䤭𤧞㵢鏛銾鍈𠊿碹鉷鑍俤㑀遤𥕝砽硔碶硋𡝗𣇉𤥁㚚佲濚濙瀞瀞吔𤆵垻壳垊鴖埗焴㒯𤆬燫𦱀𤾗嬨𡞵𨩉"],["9740","愌嫎娋䊼𤒈㜬䭻𨧼鎻鎸𡣖𠼝葲𦳀𡐓𤋺𢰦𤏁妔𣶷𦝁綨𦅛𦂤𤦹𤦋𨧺鋥珢㻩璴𨭣𡢟㻡𤪳櫘珳珻㻖𤨾𤪔𡟙𤩦𠎧𡐤𤧥瑈𤤖炥𤥶銄珦鍟𠓾錱𨫎𨨖鎆𨯧𥗕䤵𨪂煫"],["97a1","𤥃𠳿嚤𠘚𠯫𠲸唂秄𡟺緾𡛂𤩐𡡒䔮鐁㜊𨫀𤦭妰𡢿𡢃𧒄媡㛢𣵛㚰鉟婹𨪁𡡢鍴㳍𠪴䪖㦊僴㵩㵌𡎜煵䋻𨈘渏𩃤䓫浗𧹏灧沯㳖𣿭𣸭渂漌㵯𠏵畑㚼㓈䚀㻚䡱姄鉮䤾轁𨰜𦯀堒埈㛖𡑒烾𤍢𤩱𢿣𡊰𢎽梹楧𡎘𣓥𧯴𣛟𨪃𣟖𣏺𤲟樚𣚭𦲷萾䓟䓎"],["9840","𦴦𦵑𦲂𦿞漗𧄉茽𡜺菭𦲀𧁓𡟛妉媂𡞳婡婱𡤅𤇼㜭姯𡜼㛇熎鎐暚𤊥婮娫𤊓樫𣻹𧜶𤑛𤋊焝𤉙𨧡侰𦴨峂𤓎𧹍𤎽樌𤉖𡌄炦焳𤏩㶥泟勇𤩏繥姫崯㷳彜𤩝𡟟綤萦"],["98a1","咅𣫺𣌀𠈔坾𠣕𠘙㿥𡾞𪊶瀃𩅛嵰玏糓𨩙𩐠俈翧狍猐𧫴猸猹𥛶獁獈㺩𧬘遬燵𤣲珡臶㻊県㻑沢国琙琞琟㻢㻰㻴㻺瓓㼎㽓畂畭畲疍㽼痈痜㿀癍㿗癴㿜発𤽜熈嘣覀塩䀝睃䀹条䁅㗛瞘䁪䁯属瞾矋売砘点砜䂨砹硇硑硦葈𥔵礳栃礲䄃"],["9940","䄉禑禙辻稆込䅧窑䆲窼艹䇄竏竛䇏両筢筬筻簒簛䉠䉺类粜䊌粸䊔糭输烀𠳏総緔緐緽羮羴犟䎗耠耥笹耮耱联㷌垴炠肷胩䏭脌猪脎脒畠脔䐁㬹腖腙腚"],["99a1","䐓堺腼膄䐥膓䐭膥埯臁臤艔䒏芦艶苊苘苿䒰荗险榊萅烵葤惣蒈䔄蒾蓡蓸蔐蔸蕒䔻蕯蕰藠䕷虲蚒蚲蛯际螋䘆䘗袮裿褤襇覑𧥧訩訸誔誴豑賔賲贜䞘塟跃䟭仮踺嗘坔蹱嗵躰䠷軎転軤軭軲辷迁迊迌逳駄䢭飠鈓䤞鈨鉘鉫銱銮銿"],["9a40","鋣鋫鋳鋴鋽鍃鎄鎭䥅䥑麿鐗匁鐝鐭鐾䥪鑔鑹锭関䦧间阳䧥枠䨤靀䨵鞲韂噔䫤惨颹䬙飱塄餎餙冴餜餷饂饝饢䭰駅䮝騼鬏窃魩鮁鯝鯱鯴䱭鰠㝯𡯂鵉鰺"],["9aa1","黾噐鶓鶽鷀鷼银辶鹻麬麱麽黆铜黢黱黸竈齄𠂔𠊷𠎠椚铃妬𠓗塀铁㞹𠗕𠘕𠙶𡚺块煳𠫂𠫍𠮿呪吆𠯋咞𠯻𠰻𠱓𠱥𠱼惧𠲍噺𠲵𠳝𠳭𠵯𠶲𠷈楕鰯螥𠸄𠸎𠻗𠾐𠼭𠹳尠𠾼帋𡁜𡁏𡁶朞𡁻𡂈𡂖㙇𡂿𡃓𡄯𡄻卤蒭𡋣𡍵𡌶讁𡕷𡘙𡟃𡟇乸炻𡠭𡥪"],["9b40","𡨭𡩅𡰪𡱰𡲬𡻈拃𡻕𡼕熘桕𢁅槩㛈𢉼𢏗𢏺𢜪𢡱𢥏苽𢥧𢦓𢫕覥𢫨辠𢬎鞸𢬿顇骽𢱌"],["9b62","𢲈𢲷𥯨𢴈𢴒𢶷𢶕𢹂𢽴𢿌𣀳𣁦𣌟𣏞徱晈暿𧩹𣕧𣗳爁𤦺矗𣘚𣜖纇𠍆墵朎"],["9ba1","椘𣪧𧙗𥿢𣸑𣺹𧗾𢂚䣐䪸𤄙𨪚𤋮𤌍𤀻𤌴𤎖𤩅𠗊凒𠘑妟𡺨㮾𣳿𤐄𤓖垈𤙴㦛𤜯𨗨𩧉㝢𢇃譞𨭎駖𤠒𤣻𤨕爉𤫀𠱸奥𤺥𤾆𠝹軚𥀬劏圿煱𥊙𥐙𣽊𤪧喼𥑆𥑮𦭒釔㑳𥔿𧘲𥕞䜘𥕢𥕦𥟇𤤿𥡝偦㓻𣏌惞𥤃䝼𨥈𥪮𥮉𥰆𡶐垡煑澶𦄂𧰒遖𦆲𤾚譢𦐂𦑊"],["9c40","嵛𦯷輶𦒄𡤜諪𤧶𦒈𣿯𦔒䯀𦖿𦚵𢜛鑥𥟡憕娧晉侻嚹𤔡𦛼乪𤤴陖涏𦲽㘘襷𦞙𦡮𦐑𦡞營𦣇筂𩃀𠨑𦤦鄄𦤹穅鷰𦧺騦𦨭㙟𦑩𠀡禃𦨴𦭛崬𣔙菏𦮝䛐𦲤画补𦶮墶"],["9ca1","㜜𢖍𧁋𧇍㱔𧊀𧊅銁𢅺𧊋錰𧋦𤧐氹钟𧑐𠻸蠧裵𢤦𨑳𡞱溸𤨪𡠠㦤㚹尐秣䔿暶𩲭𩢤襃𧟌𧡘囖䃟𡘊㦡𣜯𨃨𡏅熭荦𧧝𩆨婧䲷𧂯𨦫𧧽𧨊𧬋𧵦𤅺筃祾𨀉澵𪋟樃𨌘厢𦸇鎿栶靝𨅯𨀣𦦵𡏭𣈯𨁈嶅𨰰𨂃圕頣𨥉嶫𤦈斾槕叒𤪥𣾁㰑朶𨂐𨃴𨄮𡾡𨅏"],["9d40","𨆉𨆯𨈚𨌆𨌯𨎊㗊𨑨𨚪䣺揦𨥖砈鉕𨦸䏲𨧧䏟𨧨𨭆𨯔姸𨰉輋𨿅𩃬筑𩄐𩄼㷷𩅞𤫊运犏嚋𩓧𩗩𩖰𩖸𩜲𩣑𩥉𩥪𩧃𩨨𩬎𩵚𩶛纟𩻸𩼣䲤镇𪊓熢𪋿䶑递𪗋䶜𠲜达嗁"],["9da1","辺𢒰边𤪓䔉繿潖檱仪㓤𨬬𧢝㜺躀𡟵𨀤𨭬𨮙𧨾𦚯㷫𧙕𣲷𥘵𥥖亚𥺁𦉘嚿𠹭踎孭𣺈𤲞揞拐𡟶𡡻攰嘭𥱊吚𥌑㷆𩶘䱽嘢嘞罉𥻘奵𣵀蝰东𠿪𠵉𣚺脗鵞贘瘻鱅癎瞹鍅吲腈苷嘥脲萘肽嗪祢噃吖𠺝㗎嘅嗱曱𨋢㘭甴嗰喺咗啲𠱁𠲖廐𥅈𠹶𢱢"],["9e40","𠺢麫絚嗞𡁵抝靭咔賍燶酶揼掹揾啩𢭃鱲𢺳冚㓟𠶧冧呍唞唓癦踭𦢊疱肶蠄螆裇膶萜𡃁䓬猄𤜆宐茋𦢓噻𢛴𧴯𤆣𧵳𦻐𧊶酰𡇙鈈𣳼𪚩𠺬𠻹牦𡲢䝎𤿂𧿹𠿫䃺"],["9ea1","鱝攟𢶠䣳𤟠𩵼𠿬𠸊恢𧖣𠿭"],["9ead","𦁈𡆇熣纎鵐业丄㕷嬍沲卧㚬㧜卽㚥𤘘墚𤭮舭呋垪𥪕𠥹"],["9ec5","㩒𢑥獴𩺬䴉鯭𣳾𩼰䱛𤾩𩖞𩿞葜𣶶𧊲𦞳𣜠挮紥𣻷𣸬㨪逈勌㹴㙺䗩𠒎癀嫰𠺶硺𧼮墧䂿噼鮋嵴癔𪐴麅䳡痹㟻愙𣃚𤏲"],["9ef5","噝𡊩垧𤥣𩸆刴𧂮㖭汊鵼"],["9f40","籖鬹埞𡝬屓擓𩓐𦌵𧅤蚭𠴨𦴢𤫢𠵱"],["9f4f","凾𡼏嶎霃𡷑麁遌笟鬂峑箣扨挵髿篏鬪籾鬮籂粆鰕篼鬉鼗鰛𤤾齚啳寃俽麘俲剠㸆勑坧偖妷帒韈鶫轜呩鞴饀鞺匬愰"],["9fa1","椬叚鰊鴂䰻陁榀傦畆𡝭駚剳"],["9fae","酙隁酜"],["9fb2","酑𨺗捿𦴣櫊嘑醎畺抅𠏼獏籰𥰡𣳽"],["9fc1","𤤙盖鮝个𠳔莾衂"],["9fc9","届槀僭坺刟巵从氱𠇲伹咜哚劚趂㗾弌㗳"],["9fdb","歒酼龥鮗頮颴骺麨麄煺笔"],["9fe7","毺蠘罸"],["9feb","嘠𪙊蹷齓"],["9ff0","跔蹏鸜踁抂𨍽踨蹵竓𤩷稾磘泪詧瘇"],["a040","𨩚鼦泎蟖痃𪊲硓咢贌狢獱謭猂瓱賫𤪻蘯徺袠䒷"],["a055","𡠻𦸅"],["a058","詾𢔛"],["a05b","惽癧髗鵄鍮鮏蟵"],["a063","蠏賷猬霡鮰㗖犲䰇籑饊𦅙慙䰄麖慽"],["a073","坟慯抦戹拎㩜懢厪𣏵捤栂㗒"],["a0a1","嵗𨯂迚𨸹"],["a0a6","僙𡵆礆匲阸𠼻䁥"],["a0ae","矾"],["a0b0","糂𥼚糚稭聦聣絍甅瓲覔舚朌聢𧒆聛瓰脃眤覉𦟌畓𦻑螩蟎臈螌詉貭譃眫瓸蓚㘵榲趦"],["a0d4","覩瑨涹蟁𤀑瓧㷛煶悤憜㳑煢恷"],["a0e2","罱𨬭牐惩䭾删㰘𣳇𥻗𧙖𥔱𡥄𡋾𩤃𦷜𧂭峁𦆭𨨏𣙷𠃮𦡆𤼎䕢嬟𦍌齐麦𦉫"],["a3c0","␀",31,"␡"],["c6a1","①",9,"⑴",9,"ⅰ",9,"丶丿亅亠冂冖冫勹匸卩厶夊宀巛⼳广廴彐彡攴无疒癶辵隶¨ˆヽヾゝゞ〃仝々〆〇ー[]✽ぁ",23],["c740","す",58,"ァアィイ"],["c7a1","ゥ",81,"А",5,"ЁЖ",4],["c840","Л",26,"ёж",25,"⇧↸↹㇏𠃌乚𠂊刂䒑"],["c8a1","龰冈龱𧘇"],["c8cd","¬¦'"㈱№℡゛゜⺀⺄⺆⺇⺈⺊⺌⺍⺕⺜⺝⺥⺧⺪⺬⺮⺶⺼⺾⻆⻊⻌⻍⻏⻖⻗⻞⻣"],["c8f5","ʃɐɛɔɵœøŋʊɪ"],["f9fe","■"],["fa40","𠕇鋛𠗟𣿅蕌䊵珯况㙉𤥂𨧤鍄𡧛苮𣳈砼杄拟𤤳𨦪𠊠𦮳𡌅侫𢓭倈𦴩𧪄𣘀𤪱𢔓倩𠍾徤𠎀𠍇滛𠐟偽儁㑺儎顬㝃萖𤦤𠒇兠𣎴兪𠯿𢃼𠋥𢔰𠖎𣈳𡦃宂蝽𠖳𣲙冲冸"],["faa1","鴴凉减凑㳜凓𤪦决凢卂凭菍椾𣜭彻刋刦刼劵剗劔効勅簕蕂勠蘍𦬓包𨫞啉滙𣾀𠥔𣿬匳卄𠯢泋𡜦栛珕恊㺪㣌𡛨燝䒢卭却𨚫卾卿𡖖𡘓矦厓𨪛厠厫厮玧𥝲㽙玜叁叅汉义埾叙㪫𠮏叠𣿫𢶣叶𠱷吓灹唫晗浛呭𦭓𠵴啝咏咤䞦𡜍𠻝㶴𠵍"],["fb40","𨦼𢚘啇䳭启琗喆喩嘅𡣗𤀺䕒𤐵暳𡂴嘷曍𣊊暤暭噍噏磱囱鞇叾圀囯园𨭦㘣𡉏坆𤆥汮炋坂㚱𦱾埦𡐖堃𡑔𤍣堦𤯵塜墪㕡壠壜𡈼壻寿坃𪅐𤉸鏓㖡够梦㛃湙"],["fba1","𡘾娤啓𡚒蔅姉𠵎𦲁𦴪𡟜姙𡟻𡞲𦶦浱𡠨𡛕姹𦹅媫婣㛦𤦩婷㜈媖瑥嫓𦾡𢕔㶅𡤑㜲𡚸広勐孶斈孼𧨎䀄䡝𠈄寕慠𡨴𥧌𠖥寳宝䴐尅𡭄尓珎尔𡲥𦬨屉䣝岅峩峯嶋𡷹𡸷崐崘嵆𡺤岺巗苼㠭𤤁𢁉𢅳芇㠶㯂帮檊幵幺𤒼𠳓厦亷廐厨𡝱帉廴𨒂"],["fc40","廹廻㢠廼栾鐛弍𠇁弢㫞䢮𡌺强𦢈𢏐彘𢑱彣鞽𦹮彲鍀𨨶徧嶶㵟𥉐𡽪𧃸𢙨釖𠊞𨨩怱暅𡡷㥣㷇㘹垐𢞴祱㹀悞悤悳𤦂𤦏𧩓璤僡媠慤萤慂慈𦻒憁凴𠙖憇宪𣾷"],["fca1","𢡟懓𨮝𩥝懐㤲𢦀𢣁怣慜攞掋𠄘担𡝰拕𢸍捬𤧟㨗搸揸𡎎𡟼撐澊𢸶頔𤂌𥜝擡擥鑻㩦携㩗敍漖𤨨𤨣斅敭敟𣁾斵𤥀䬷旑䃘𡠩无旣忟𣐀昘𣇷𣇸晄𣆤𣆥晋𠹵晧𥇦晳晴𡸽𣈱𨗴𣇈𥌓矅𢣷馤朂𤎜𤨡㬫槺𣟂杞杧杢𤇍𩃭柗䓩栢湐鈼栁𣏦𦶠桝"],["fd40","𣑯槡樋𨫟楳棃𣗍椁椀㴲㨁𣘼㮀枬楡𨩊䋼椶榘㮡𠏉荣傐槹𣙙𢄪橅𣜃檝㯳枱櫈𩆜㰍欝𠤣惞欵歴𢟍溵𣫛𠎵𡥘㝀吡𣭚毡𣻼毜氷𢒋𤣱𦭑汚舦汹𣶼䓅𣶽𤆤𤤌𤤀"],["fda1","𣳉㛥㳫𠴲鮃𣇹𢒑羏样𦴥𦶡𦷫涖浜湼漄𤥿𤂅𦹲蔳𦽴凇沜渝萮𨬡港𣸯瑓𣾂秌湏媑𣁋濸㜍澝𣸰滺𡒗𤀽䕕鏰潄潜㵎潴𩅰㴻澟𤅄濓𤂑𤅕𤀹𣿰𣾴𤄿凟𤅖𤅗𤅀𦇝灋灾炧炁烌烕烖烟䄄㷨熴熖𤉷焫煅媈煊煮岜𤍥煏鍢𤋁焬𤑚𤨧𤨢熺𨯨炽爎"],["fe40","鑂爕夑鑃爤鍁𥘅爮牀𤥴梽牕牗㹕𣁄栍漽犂猪猫𤠣𨠫䣭𨠄猨献珏玪𠰺𦨮珉瑉𤇢𡛧𤨤昣㛅𤦷𤦍𤧻珷琕椃𤨦琹𠗃㻗瑜𢢭瑠𨺲瑇珤瑶莹瑬㜰瑴鏱樬璂䥓𤪌"],["fea1","𤅟𤩹𨮏孆𨰃𡢞瓈𡦈甎瓩甞𨻙𡩋寗𨺬鎅畍畊畧畮𤾂㼄𤴓疎瑝疞疴瘂瘬癑癏癯癶𦏵皐臯㟸𦤑𦤎皡皥皷盌𦾟葢𥂝𥅽𡸜眞眦着撯𥈠睘𣊬瞯𨥤𨥨𡛁矴砉𡍶𤨒棊碯磇磓隥礮𥗠磗礴碱𧘌辸袄𨬫𦂃𢘜禆褀椂禀𥡗禝𧬹礼禩渪𧄦㺨秆𩄍秔"]]'); + +/***/ }), + +/***/ 8949: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('[["0","\\u0000",127,"€"],["8140","丂丄丅丆丏丒丗丟丠両丣並丩丮丯丱丳丵丷丼乀乁乂乄乆乊乑乕乗乚乛乢乣乤乥乧乨乪",5,"乲乴",9,"乿",6,"亇亊"],["8180","亐亖亗亙亜亝亞亣亪亯亰亱亴亶亷亸亹亼亽亾仈仌仏仐仒仚仛仜仠仢仦仧仩仭仮仯仱仴仸仹仺仼仾伀伂",6,"伋伌伒",4,"伜伝伡伣伨伩伬伭伮伱伳伵伷伹伻伾",4,"佄佅佇",5,"佒佔佖佡佢佦佨佪佫佭佮佱佲併佷佸佹佺佽侀侁侂侅來侇侊侌侎侐侒侓侕侖侘侙侚侜侞侟価侢"],["8240","侤侫侭侰",4,"侶",8,"俀俁係俆俇俈俉俋俌俍俒",4,"俙俛俠俢俤俥俧俫俬俰俲俴俵俶俷俹俻俼俽俿",11],["8280","個倎倐們倓倕倖倗倛倝倞倠倢倣値倧倫倯",10,"倻倽倿偀偁偂偄偅偆偉偊偋偍偐",4,"偖偗偘偙偛偝",7,"偦",5,"偭",8,"偸偹偺偼偽傁傂傃傄傆傇傉傊傋傌傎",20,"傤傦傪傫傭",4,"傳",6,"傼"],["8340","傽",17,"僐",5,"僗僘僙僛",10,"僨僩僪僫僯僰僱僲僴僶",4,"僼",9,"儈"],["8380","儉儊儌",5,"儓",13,"儢",28,"兂兇兊兌兎兏児兒兓兗兘兙兛兝",4,"兣兤兦內兩兪兯兲兺兾兿冃冄円冇冊冋冎冏冐冑冓冔冘冚冝冞冟冡冣冦",4,"冭冮冴冸冹冺冾冿凁凂凃凅凈凊凍凎凐凒",5],["8440","凘凙凚凜凞凟凢凣凥",5,"凬凮凱凲凴凷凾刄刅刉刋刌刏刐刓刔刕刜刞刟刡刢刣別刦刧刪刬刯刱刲刴刵刼刾剄",5,"剋剎剏剒剓剕剗剘"],["8480","剙剚剛剝剟剠剢剣剤剦剨剫剬剭剮剰剱剳",9,"剾劀劃",4,"劉",6,"劑劒劔",6,"劜劤劥劦劧劮劯劰労",9,"勀勁勂勄勅勆勈勊勌勍勎勏勑勓勔動勗務",5,"勠勡勢勣勥",10,"勱",7,"勻勼勽匁匂匃匄匇匉匊匋匌匎"],["8540","匑匒匓匔匘匛匜匞匟匢匤匥匧匨匩匫匬匭匯",9,"匼匽區卂卄卆卋卌卍卐協単卙卛卝卥卨卪卬卭卲卶卹卻卼卽卾厀厁厃厇厈厊厎厏"],["8580","厐",4,"厖厗厙厛厜厞厠厡厤厧厪厫厬厭厯",6,"厷厸厹厺厼厽厾叀參",4,"収叏叐叒叓叕叚叜叝叞叡叢叧叴叺叾叿吀吂吅吇吋吔吘吙吚吜吢吤吥吪吰吳吶吷吺吽吿呁呂呄呅呇呉呌呍呎呏呑呚呝",4,"呣呥呧呩",7,"呴呹呺呾呿咁咃咅咇咈咉咊咍咑咓咗咘咜咞咟咠咡"],["8640","咢咥咮咰咲咵咶咷咹咺咼咾哃哅哊哋哖哘哛哠",4,"哫哬哯哰哱哴",5,"哻哾唀唂唃唄唅唈唊",4,"唒唓唕",5,"唜唝唞唟唡唥唦"],["8680","唨唩唫唭唲唴唵唶唸唹唺唻唽啀啂啅啇啈啋",4,"啑啒啓啔啗",4,"啝啞啟啠啢啣啨啩啫啯",5,"啹啺啽啿喅喆喌喍喎喐喒喓喕喖喗喚喛喞喠",6,"喨",8,"喲喴営喸喺喼喿",4,"嗆嗇嗈嗊嗋嗎嗏嗐嗕嗗",4,"嗞嗠嗢嗧嗩嗭嗮嗰嗱嗴嗶嗸",4,"嗿嘂嘃嘄嘅"],["8740","嘆嘇嘊嘋嘍嘐",7,"嘙嘚嘜嘝嘠嘡嘢嘥嘦嘨嘩嘪嘫嘮嘯嘰嘳嘵嘷嘸嘺嘼嘽嘾噀",11,"噏",4,"噕噖噚噛噝",4],["8780","噣噥噦噧噭噮噯噰噲噳噴噵噷噸噹噺噽",7,"嚇",6,"嚐嚑嚒嚔",14,"嚤",10,"嚰",6,"嚸嚹嚺嚻嚽",12,"囋",8,"囕囖囘囙囜団囥",5,"囬囮囯囲図囶囷囸囻囼圀圁圂圅圇國",6],["8840","園",9,"圝圞圠圡圢圤圥圦圧圫圱圲圴",4,"圼圽圿坁坃坄坅坆坈坉坋坒",4,"坘坙坢坣坥坧坬坮坰坱坲坴坵坸坹坺坽坾坿垀"],["8880","垁垇垈垉垊垍",4,"垔",6,"垜垝垞垟垥垨垪垬垯垰垱垳垵垶垷垹",8,"埄",6,"埌埍埐埑埓埖埗埛埜埞埡埢埣埥",7,"埮埰埱埲埳埵埶執埻埼埾埿堁堃堄堅堈堉堊堌堎堏堐堒堓堔堖堗堘堚堛堜堝堟堢堣堥",4,"堫",4,"報堲堳場堶",7],["8940","堾",5,"塅",6,"塎塏塐塒塓塕塖塗塙",4,"塟",5,"塦",4,"塭",16,"塿墂墄墆墇墈墊墋墌"],["8980","墍",4,"墔",4,"墛墜墝墠",7,"墪",17,"墽墾墿壀壂壃壄壆",10,"壒壓壔壖",13,"壥",5,"壭壯壱売壴壵壷壸壺",7,"夃夅夆夈",4,"夎夐夑夒夓夗夘夛夝夞夠夡夢夣夦夨夬夰夲夳夵夶夻"],["8a40","夽夾夿奀奃奅奆奊奌奍奐奒奓奙奛",4,"奡奣奤奦",12,"奵奷奺奻奼奾奿妀妅妉妋妌妎妏妐妑妔妕妘妚妛妜妝妟妠妡妢妦"],["8a80","妧妬妭妰妱妳",5,"妺妼妽妿",6,"姇姈姉姌姍姎姏姕姖姙姛姞",4,"姤姦姧姩姪姫姭",11,"姺姼姽姾娀娂娊娋娍娎娏娐娒娔娕娖娗娙娚娛娝娞娡娢娤娦娧娨娪",6,"娳娵娷",4,"娽娾娿婁",4,"婇婈婋",9,"婖婗婘婙婛",5],["8b40","婡婣婤婥婦婨婩婫",8,"婸婹婻婼婽婾媀",17,"媓",6,"媜",13,"媫媬"],["8b80","媭",4,"媴媶媷媹",4,"媿嫀嫃",5,"嫊嫋嫍",4,"嫓嫕嫗嫙嫚嫛嫝嫞嫟嫢嫤嫥嫧嫨嫪嫬",4,"嫲",22,"嬊",11,"嬘",25,"嬳嬵嬶嬸",7,"孁",6],["8c40","孈",7,"孒孖孞孠孡孧孨孫孭孮孯孲孴孶孷學孹孻孼孾孿宂宆宊宍宎宐宑宒宔宖実宧宨宩宬宭宮宯宱宲宷宺宻宼寀寁寃寈寉寊寋寍寎寏"],["8c80","寑寔",8,"寠寢寣實寧審",4,"寯寱",6,"寽対尀専尃尅將專尋尌對導尐尒尓尗尙尛尞尟尠尡尣尦尨尩尪尫尭尮尯尰尲尳尵尶尷屃屄屆屇屌屍屒屓屔屖屗屘屚屛屜屝屟屢層屧",6,"屰屲",6,"屻屼屽屾岀岃",4,"岉岊岋岎岏岒岓岕岝",4,"岤",4],["8d40","岪岮岯岰岲岴岶岹岺岻岼岾峀峂峃峅",5,"峌",5,"峓",5,"峚",6,"峢峣峧峩峫峬峮峯峱",9,"峼",4],["8d80","崁崄崅崈",5,"崏",4,"崕崗崘崙崚崜崝崟",4,"崥崨崪崫崬崯",4,"崵",7,"崿",7,"嵈嵉嵍",10,"嵙嵚嵜嵞",10,"嵪嵭嵮嵰嵱嵲嵳嵵",12,"嶃",21,"嶚嶛嶜嶞嶟嶠"],["8e40","嶡",21,"嶸",12,"巆",6,"巎",12,"巜巟巠巣巤巪巬巭"],["8e80","巰巵巶巸",4,"巿帀帄帇帉帊帋帍帎帒帓帗帞",7,"帨",4,"帯帰帲",4,"帹帺帾帿幀幁幃幆",5,"幍",6,"幖",4,"幜幝幟幠幣",14,"幵幷幹幾庁庂広庅庈庉庌庍庎庒庘庛庝庡庢庣庤庨",4,"庮",4,"庴庺庻庼庽庿",6],["8f40","廆廇廈廋",5,"廔廕廗廘廙廚廜",11,"廩廫",8,"廵廸廹廻廼廽弅弆弇弉弌弍弎弐弒弔弖弙弚弜弝弞弡弢弣弤"],["8f80","弨弫弬弮弰弲",6,"弻弽弾弿彁",14,"彑彔彙彚彛彜彞彟彠彣彥彧彨彫彮彯彲彴彵彶彸彺彽彾彿徃徆徍徎徏徑従徔徖徚徛徝從徟徠徢",5,"復徫徬徯",5,"徶徸徹徺徻徾",4,"忇忈忊忋忎忓忔忕忚忛応忞忟忢忣忥忦忨忩忬忯忰忲忳忴忶忷忹忺忼怇"],["9040","怈怉怋怌怐怑怓怗怘怚怞怟怢怣怤怬怭怮怰",4,"怶",4,"怽怾恀恄",6,"恌恎恏恑恓恔恖恗恘恛恜恞恟恠恡恥恦恮恱恲恴恵恷恾悀"],["9080","悁悂悅悆悇悈悊悋悎悏悐悑悓悕悗悘悙悜悞悡悢悤悥悧悩悪悮悰悳悵悶悷悹悺悽",7,"惇惈惉惌",4,"惒惓惔惖惗惙惛惞惡",4,"惪惱惲惵惷惸惻",4,"愂愃愄愅愇愊愋愌愐",4,"愖愗愘愙愛愜愝愞愡愢愥愨愩愪愬",18,"慀",6],["9140","慇慉態慍慏慐慒慓慔慖",6,"慞慟慠慡慣慤慥慦慩",6,"慱慲慳慴慶慸",18,"憌憍憏",4,"憕"],["9180","憖",6,"憞",8,"憪憫憭",9,"憸",5,"憿懀懁懃",4,"應懌",4,"懓懕",16,"懧",13,"懶",8,"戀",5,"戇戉戓戔戙戜戝戞戠戣戦戧戨戩戫戭戯戰戱戲戵戶戸",4,"扂扄扅扆扊"],["9240","扏扐払扖扗扙扚扜",6,"扤扥扨扱扲扴扵扷扸扺扻扽抁抂抃抅抆抇抈抋",5,"抔抙抜抝択抣抦抧抩抪抭抮抯抰抲抳抴抶抷抸抺抾拀拁"],["9280","拃拋拏拑拕拝拞拠拡拤拪拫拰拲拵拸拹拺拻挀挃挄挅挆挊挋挌挍挏挐挒挓挔挕挗挘挙挜挦挧挩挬挭挮挰挱挳",5,"挻挼挾挿捀捁捄捇捈捊捑捒捓捔捖",7,"捠捤捥捦捨捪捫捬捯捰捲捳捴捵捸捹捼捽捾捿掁掃掄掅掆掋掍掑掓掔掕掗掙",6,"採掤掦掫掯掱掲掵掶掹掻掽掿揀"],["9340","揁揂揃揅揇揈揊揋揌揑揓揔揕揗",6,"揟揢揤",4,"揫揬揮揯揰揱揳揵揷揹揺揻揼揾搃搄搆",4,"損搎搑搒搕",5,"搝搟搢搣搤"],["9380","搥搧搨搩搫搮",5,"搵",4,"搻搼搾摀摂摃摉摋",6,"摓摕摖摗摙",4,"摟",7,"摨摪摫摬摮",9,"摻",6,"撃撆撈",8,"撓撔撗撘撚撛撜撝撟",4,"撥撦撧撨撪撫撯撱撲撳撴撶撹撻撽撾撿擁擃擄擆",6,"擏擑擓擔擕擖擙據"],["9440","擛擜擝擟擠擡擣擥擧",24,"攁",7,"攊",7,"攓",4,"攙",8],["9480","攢攣攤攦",4,"攬攭攰攱攲攳攷攺攼攽敀",4,"敆敇敊敋敍敎敐敒敓敔敗敘敚敜敟敠敡敤敥敧敨敩敪敭敮敯敱敳敵敶數",14,"斈斉斊斍斎斏斒斔斕斖斘斚斝斞斠斢斣斦斨斪斬斮斱",7,"斺斻斾斿旀旂旇旈旉旊旍旐旑旓旔旕旘",7,"旡旣旤旪旫"],["9540","旲旳旴旵旸旹旻",4,"昁昄昅昇昈昉昋昍昐昑昒昖昗昘昚昛昜昞昡昢昣昤昦昩昪昫昬昮昰昲昳昷",4,"昽昿晀時晄",6,"晍晎晐晑晘"],["9580","晙晛晜晝晞晠晢晣晥晧晩",4,"晱晲晳晵晸晹晻晼晽晿暀暁暃暅暆暈暉暊暋暍暎暏暐暒暓暔暕暘",4,"暞",8,"暩",4,"暯",4,"暵暶暷暸暺暻暼暽暿",25,"曚曞",7,"曧曨曪",5,"曱曵曶書曺曻曽朁朂會"],["9640","朄朅朆朇朌朎朏朑朒朓朖朘朙朚朜朞朠",5,"朧朩朮朰朲朳朶朷朸朹朻朼朾朿杁杄杅杇杊杋杍杒杔杕杗",4,"杝杢杣杤杦杧杫杬杮東杴杶"],["9680","杸杹杺杻杽枀枂枃枅枆枈枊枌枍枎枏枑枒枓枔枖枙枛枟枠枡枤枦枩枬枮枱枲枴枹",7,"柂柅",9,"柕柖柗柛柟柡柣柤柦柧柨柪柫柭柮柲柵",7,"柾栁栂栃栄栆栍栐栒栔栕栘",4,"栞栟栠栢",6,"栫",6,"栴栵栶栺栻栿桇桋桍桏桒桖",5],["9740","桜桝桞桟桪桬",7,"桵桸",8,"梂梄梇",7,"梐梑梒梔梕梖梘",9,"梣梤梥梩梪梫梬梮梱梲梴梶梷梸"],["9780","梹",6,"棁棃",5,"棊棌棎棏棐棑棓棔棖棗棙棛",4,"棡棢棤",9,"棯棲棳棴棶棷棸棻棽棾棿椀椂椃椄椆",4,"椌椏椑椓",11,"椡椢椣椥",7,"椮椯椱椲椳椵椶椷椸椺椻椼椾楀楁楃",16,"楕楖楘楙楛楜楟"],["9840","楡楢楤楥楧楨楩楪楬業楯楰楲",4,"楺楻楽楾楿榁榃榅榊榋榌榎",5,"榖榗榙榚榝",9,"榩榪榬榮榯榰榲榳榵榶榸榹榺榼榽"],["9880","榾榿槀槂",7,"構槍槏槑槒槓槕",5,"槜槝槞槡",11,"槮槯槰槱槳",9,"槾樀",9,"樋",11,"標",5,"樠樢",5,"権樫樬樭樮樰樲樳樴樶",6,"樿",4,"橅橆橈",7,"橑",6,"橚"],["9940","橜",4,"橢橣橤橦",10,"橲",6,"橺橻橽橾橿檁檂檃檅",8,"檏檒",4,"檘",7,"檡",5],["9980","檧檨檪檭",114,"欥欦欨",6],["9a40","欯欰欱欳欴欵欶欸欻欼欽欿歀歁歂歄歅歈歊歋歍",11,"歚",7,"歨歩歫",13,"歺歽歾歿殀殅殈"],["9a80","殌殎殏殐殑殔殕殗殘殙殜",4,"殢",7,"殫",7,"殶殸",6,"毀毃毄毆",4,"毌毎毐毑毘毚毜",4,"毢",7,"毬毭毮毰毱毲毴毶毷毸毺毻毼毾",6,"氈",4,"氎氒気氜氝氞氠氣氥氫氬氭氱氳氶氷氹氺氻氼氾氿汃汄汅汈汋",4,"汑汒汓汖汘"],["9b40","汙汚汢汣汥汦汧汫",4,"汱汳汵汷汸決汻汼汿沀沄沇沊沋沍沎沑沒沕沖沗沘沚沜沝沞沠沢沨沬沯沰沴沵沶沷沺泀況泂泃泆泇泈泋泍泎泏泑泒泘"],["9b80","泙泚泜泝泟泤泦泧泩泬泭泲泴泹泿洀洂洃洅洆洈洉洊洍洏洐洑洓洔洕洖洘洜洝洟",5,"洦洨洩洬洭洯洰洴洶洷洸洺洿浀浂浄浉浌浐浕浖浗浘浛浝浟浡浢浤浥浧浨浫浬浭浰浱浲浳浵浶浹浺浻浽",4,"涃涄涆涇涊涋涍涏涐涒涖",4,"涜涢涥涬涭涰涱涳涴涶涷涹",5,"淁淂淃淈淉淊"],["9c40","淍淎淏淐淒淓淔淕淗淚淛淜淟淢淣淥淧淨淩淪淭淯淰淲淴淵淶淸淺淽",7,"渆渇済渉渋渏渒渓渕渘渙減渜渞渟渢渦渧渨渪測渮渰渱渳渵"],["9c80","渶渷渹渻",7,"湅",7,"湏湐湑湒湕湗湙湚湜湝湞湠",10,"湬湭湯",14,"満溁溂溄溇溈溊",4,"溑",6,"溙溚溛溝溞溠溡溣溤溦溨溩溫溬溭溮溰溳溵溸溹溼溾溿滀滃滄滅滆滈滉滊滌滍滎滐滒滖滘滙滛滜滝滣滧滪",5],["9d40","滰滱滲滳滵滶滷滸滺",7,"漃漄漅漇漈漊",4,"漐漑漒漖",9,"漡漢漣漥漦漧漨漬漮漰漲漴漵漷",6,"漿潀潁潂"],["9d80","潃潄潅潈潉潊潌潎",9,"潙潚潛潝潟潠潡潣潤潥潧",5,"潯潰潱潳潵潶潷潹潻潽",6,"澅澆澇澊澋澏",12,"澝澞澟澠澢",4,"澨",10,"澴澵澷澸澺",5,"濁濃",5,"濊",6,"濓",10,"濟濢濣濤濥"],["9e40","濦",7,"濰",32,"瀒",7,"瀜",6,"瀤",6],["9e80","瀫",9,"瀶瀷瀸瀺",17,"灍灎灐",13,"灟",11,"灮灱灲灳灴灷灹灺灻災炁炂炃炄炆炇炈炋炌炍炏炐炑炓炗炘炚炛炞",12,"炰炲炴炵炶為炾炿烄烅烆烇烉烋",12,"烚"],["9f40","烜烝烞烠烡烢烣烥烪烮烰",6,"烸烺烻烼烾",10,"焋",4,"焑焒焔焗焛",10,"焧",7,"焲焳焴"],["9f80","焵焷",13,"煆煇煈煉煋煍煏",12,"煝煟",4,"煥煩",4,"煯煰煱煴煵煶煷煹煻煼煾",5,"熅",4,"熋熌熍熎熐熑熒熓熕熖熗熚",4,"熡",6,"熩熪熫熭",5,"熴熶熷熸熺",8,"燄",9,"燏",4],["a040","燖",9,"燡燢燣燤燦燨",5,"燯",9,"燺",11,"爇",19],["a080","爛爜爞",9,"爩爫爭爮爯爲爳爴爺爼爾牀",6,"牉牊牋牎牏牐牑牓牔牕牗牘牚牜牞牠牣牤牥牨牪牫牬牭牰牱牳牴牶牷牸牻牼牽犂犃犅",4,"犌犎犐犑犓",11,"犠",11,"犮犱犲犳犵犺",6,"狅狆狇狉狊狋狌狏狑狓狔狕狖狘狚狛"],["a1a1"," 、。·ˉˇ¨〃々—~‖…‘’“”〔〕〈",7,"〖〗【】±×÷∶∧∨∑∏∪∩∈∷√⊥∥∠⌒⊙∫∮≡≌≈∽∝≠≮≯≤≥∞∵∴♂♀°′″℃$¤¢£‰§№☆★○●◎◇◆□■△▲※→←↑↓〓"],["a2a1","ⅰ",9],["a2b1","⒈",19,"⑴",19,"①",9],["a2e5","㈠",9],["a2f1","Ⅰ",11],["a3a1","!"#¥%",88," ̄"],["a4a1","ぁ",82],["a5a1","ァ",85],["a6a1","Α",16,"Σ",6],["a6c1","α",16,"σ",6],["a6e0","︵︶︹︺︿﹀︽︾﹁﹂﹃﹄"],["a6ee","︻︼︷︸︱"],["a6f4","︳︴"],["a7a1","А",5,"ЁЖ",25],["a7d1","а",5,"ёж",25],["a840","ˊˋ˙–―‥‵℅℉↖↗↘↙∕∟∣≒≦≧⊿═",35,"▁",6],["a880","█",7,"▓▔▕▼▽◢◣◤◥☉⊕〒〝〞"],["a8a1","āáǎàēéěèīíǐìōóǒòūúǔùǖǘǚǜüêɑ"],["a8bd","ńň"],["a8c0","ɡ"],["a8c5","ㄅ",36],["a940","〡",8,"㊣㎎㎏㎜㎝㎞㎡㏄㏎㏑㏒㏕︰¬¦"],["a959","℡㈱"],["a95c","‐"],["a960","ー゛゜ヽヾ〆ゝゞ﹉",9,"﹔﹕﹖﹗﹙",8],["a980","﹢",4,"﹨﹩﹪﹫"],["a996","〇"],["a9a4","─",75],["aa40","狜狝狟狢",5,"狪狫狵狶狹狽狾狿猀猂猄",5,"猋猌猍猏猐猑猒猔猘猙猚猟猠猣猤猦猧猨猭猯猰猲猳猵猶猺猻猼猽獀",8],["aa80","獉獊獋獌獎獏獑獓獔獕獖獘",7,"獡",10,"獮獰獱"],["ab40","獲",11,"獿",4,"玅玆玈玊玌玍玏玐玒玓玔玕玗玘玙玚玜玝玞玠玡玣",5,"玪玬玭玱玴玵玶玸玹玼玽玾玿珁珃",4],["ab80","珋珌珎珒",6,"珚珛珜珝珟珡珢珣珤珦珨珪珫珬珮珯珰珱珳",4],["ac40","珸",10,"琄琇琈琋琌琍琎琑",8,"琜",5,"琣琤琧琩琫琭琯琱琲琷",4,"琽琾琿瑀瑂",11],["ac80","瑎",6,"瑖瑘瑝瑠",12,"瑮瑯瑱",4,"瑸瑹瑺"],["ad40","瑻瑼瑽瑿璂璄璅璆璈璉璊璌璍璏璑",10,"璝璟",7,"璪",15,"璻",12],["ad80","瓈",9,"瓓",8,"瓝瓟瓡瓥瓧",6,"瓰瓱瓲"],["ae40","瓳瓵瓸",6,"甀甁甂甃甅",7,"甎甐甒甔甕甖甗甛甝甞甠",4,"甦甧甪甮甴甶甹甼甽甿畁畂畃畄畆畇畉畊畍畐畑畒畓畕畖畗畘"],["ae80","畝",7,"畧畨畩畫",6,"畳畵當畷畺",4,"疀疁疂疄疅疇"],["af40","疈疉疊疌疍疎疐疓疕疘疛疜疞疢疦",4,"疭疶疷疺疻疿痀痁痆痋痌痎痏痐痑痓痗痙痚痜痝痟痠痡痥痩痬痭痮痯痲痳痵痶痷痸痺痻痽痾瘂瘄瘆瘇"],["af80","瘈瘉瘋瘍瘎瘏瘑瘒瘓瘔瘖瘚瘜瘝瘞瘡瘣瘧瘨瘬瘮瘯瘱瘲瘶瘷瘹瘺瘻瘽癁療癄"],["b040","癅",6,"癎",5,"癕癗",4,"癝癟癠癡癢癤",6,"癬癭癮癰",7,"癹発發癿皀皁皃皅皉皊皌皍皏皐皒皔皕皗皘皚皛"],["b080","皜",7,"皥",8,"皯皰皳皵",9,"盀盁盃啊阿埃挨哎唉哀皑癌蔼矮艾碍爱隘鞍氨安俺按暗岸胺案肮昂盎凹敖熬翱袄傲奥懊澳芭捌扒叭吧笆八疤巴拔跋靶把耙坝霸罢爸白柏百摆佰败拜稗斑班搬扳般颁板版扮拌伴瓣半办绊邦帮梆榜膀绑棒磅蚌镑傍谤苞胞包褒剥"],["b140","盄盇盉盋盌盓盕盙盚盜盝盞盠",4,"盦",7,"盰盳盵盶盷盺盻盽盿眀眂眃眅眆眊県眎",10,"眛眜眝眞眡眣眤眥眧眪眫"],["b180","眬眮眰",4,"眹眻眽眾眿睂睄睅睆睈",7,"睒",7,"睜薄雹保堡饱宝抱报暴豹鲍爆杯碑悲卑北辈背贝钡倍狈备惫焙被奔苯本笨崩绷甭泵蹦迸逼鼻比鄙笔彼碧蓖蔽毕毙毖币庇痹闭敝弊必辟壁臂避陛鞭边编贬扁便变卞辨辩辫遍标彪膘表鳖憋别瘪彬斌濒滨宾摈兵冰柄丙秉饼炳"],["b240","睝睞睟睠睤睧睩睪睭",11,"睺睻睼瞁瞂瞃瞆",5,"瞏瞐瞓",11,"瞡瞣瞤瞦瞨瞫瞭瞮瞯瞱瞲瞴瞶",4],["b280","瞼瞾矀",12,"矎",8,"矘矙矚矝",4,"矤病并玻菠播拨钵波博勃搏铂箔伯帛舶脖膊渤泊驳捕卜哺补埠不布步簿部怖擦猜裁材才财睬踩采彩菜蔡餐参蚕残惭惨灿苍舱仓沧藏操糙槽曹草厕策侧册测层蹭插叉茬茶查碴搽察岔差诧拆柴豺搀掺蝉馋谗缠铲产阐颤昌猖"],["b340","矦矨矪矯矰矱矲矴矵矷矹矺矻矼砃",5,"砊砋砎砏砐砓砕砙砛砞砠砡砢砤砨砪砫砮砯砱砲砳砵砶砽砿硁硂硃硄硆硈硉硊硋硍硏硑硓硔硘硙硚"],["b380","硛硜硞",11,"硯",7,"硸硹硺硻硽",6,"场尝常长偿肠厂敞畅唱倡超抄钞朝嘲潮巢吵炒车扯撤掣彻澈郴臣辰尘晨忱沉陈趁衬撑称城橙成呈乘程惩澄诚承逞骋秤吃痴持匙池迟弛驰耻齿侈尺赤翅斥炽充冲虫崇宠抽酬畴踌稠愁筹仇绸瞅丑臭初出橱厨躇锄雏滁除楚"],["b440","碄碅碆碈碊碋碏碐碒碔碕碖碙碝碞碠碢碤碦碨",7,"碵碶碷碸確碻碼碽碿磀磂磃磄磆磇磈磌磍磎磏磑磒磓磖磗磘磚",9],["b480","磤磥磦磧磩磪磫磭",4,"磳磵磶磸磹磻",5,"礂礃礄礆",6,"础储矗搐触处揣川穿椽传船喘串疮窗幢床闯创吹炊捶锤垂春椿醇唇淳纯蠢戳绰疵茨磁雌辞慈瓷词此刺赐次聪葱囱匆从丛凑粗醋簇促蹿篡窜摧崔催脆瘁粹淬翠村存寸磋撮搓措挫错搭达答瘩打大呆歹傣戴带殆代贷袋待逮"],["b540","礍",5,"礔",9,"礟",4,"礥",14,"礵",4,"礽礿祂祃祄祅祇祊",8,"祔祕祘祙祡祣"],["b580","祤祦祩祪祫祬祮祰",6,"祹祻",4,"禂禃禆禇禈禉禋禌禍禎禐禑禒怠耽担丹单郸掸胆旦氮但惮淡诞弹蛋当挡党荡档刀捣蹈倒岛祷导到稻悼道盗德得的蹬灯登等瞪凳邓堤低滴迪敌笛狄涤翟嫡抵底地蒂第帝弟递缔颠掂滇碘点典靛垫电佃甸店惦奠淀殿碉叼雕凋刁掉吊钓调跌爹碟蝶迭谍叠"],["b640","禓",6,"禛",11,"禨",10,"禴",4,"禼禿秂秄秅秇秈秊秌秎秏秐秓秔秖秗秙",5,"秠秡秢秥秨秪"],["b680","秬秮秱",6,"秹秺秼秾秿稁稄稅稇稈稉稊稌稏",4,"稕稖稘稙稛稜丁盯叮钉顶鼎锭定订丢东冬董懂动栋侗恫冻洞兜抖斗陡豆逗痘都督毒犊独读堵睹赌杜镀肚度渡妒端短锻段断缎堆兑队对墩吨蹲敦顿囤钝盾遁掇哆多夺垛躲朵跺舵剁惰堕蛾峨鹅俄额讹娥恶厄扼遏鄂饿恩而儿耳尔饵洱二"],["b740","稝稟稡稢稤",14,"稴稵稶稸稺稾穀",5,"穇",9,"穒",4,"穘",16],["b780","穩",6,"穱穲穳穵穻穼穽穾窂窅窇窉窊窋窌窎窏窐窓窔窙窚窛窞窡窢贰发罚筏伐乏阀法珐藩帆番翻樊矾钒繁凡烦反返范贩犯饭泛坊芳方肪房防妨仿访纺放菲非啡飞肥匪诽吠肺废沸费芬酚吩氛分纷坟焚汾粉奋份忿愤粪丰封枫蜂峰锋风疯烽逢冯缝讽奉凤佛否夫敷肤孵扶拂辐幅氟符伏俘服"],["b840","窣窤窧窩窪窫窮",4,"窴",10,"竀",10,"竌",9,"竗竘竚竛竜竝竡竢竤竧",5,"竮竰竱竲竳"],["b880","竴",4,"竻竼竾笀笁笂笅笇笉笌笍笎笐笒笓笖笗笘笚笜笝笟笡笢笣笧笩笭浮涪福袱弗甫抚辅俯釜斧脯腑府腐赴副覆赋复傅付阜父腹负富讣附妇缚咐噶嘎该改概钙盖溉干甘杆柑竿肝赶感秆敢赣冈刚钢缸肛纲岗港杠篙皋高膏羔糕搞镐稿告哥歌搁戈鸽胳疙割革葛格蛤阁隔铬个各给根跟耕更庚羹"],["b940","笯笰笲笴笵笶笷笹笻笽笿",5,"筆筈筊筍筎筓筕筗筙筜筞筟筡筣",10,"筯筰筳筴筶筸筺筼筽筿箁箂箃箄箆",6,"箎箏"],["b980","箑箒箓箖箘箙箚箛箞箟箠箣箤箥箮箯箰箲箳箵箶箷箹",7,"篂篃範埂耿梗工攻功恭龚供躬公宫弓巩汞拱贡共钩勾沟苟狗垢构购够辜菇咕箍估沽孤姑鼓古蛊骨谷股故顾固雇刮瓜剐寡挂褂乖拐怪棺关官冠观管馆罐惯灌贯光广逛瑰规圭硅归龟闺轨鬼诡癸桂柜跪贵刽辊滚棍锅郭国果裹过哈"],["ba40","篅篈築篊篋篍篎篏篐篒篔",4,"篛篜篞篟篠篢篣篤篧篨篩篫篬篭篯篰篲",4,"篸篹篺篻篽篿",7,"簈簉簊簍簎簐",5,"簗簘簙"],["ba80","簚",4,"簠",5,"簨簩簫",12,"簹",5,"籂骸孩海氦亥害骇酣憨邯韩含涵寒函喊罕翰撼捍旱憾悍焊汗汉夯杭航壕嚎豪毫郝好耗号浩呵喝荷菏核禾和何合盒貉阂河涸赫褐鹤贺嘿黑痕很狠恨哼亨横衡恒轰哄烘虹鸿洪宏弘红喉侯猴吼厚候后呼乎忽瑚壶葫胡蝴狐糊湖"],["bb40","籃",9,"籎",36,"籵",5,"籾",9],["bb80","粈粊",6,"粓粔粖粙粚粛粠粡粣粦粧粨粩粫粬粭粯粰粴",4,"粺粻弧虎唬护互沪户花哗华猾滑画划化话槐徊怀淮坏欢环桓还缓换患唤痪豢焕涣宦幻荒慌黄磺蝗簧皇凰惶煌晃幌恍谎灰挥辉徽恢蛔回毁悔慧卉惠晦贿秽会烩汇讳诲绘荤昏婚魂浑混豁活伙火获或惑霍货祸击圾基机畸稽积箕"],["bc40","粿糀糂糃糄糆糉糋糎",6,"糘糚糛糝糞糡",6,"糩",5,"糰",7,"糹糺糼",13,"紋",5],["bc80","紑",14,"紡紣紤紥紦紨紩紪紬紭紮細",6,"肌饥迹激讥鸡姬绩缉吉极棘辑籍集及急疾汲即嫉级挤几脊己蓟技冀季伎祭剂悸济寄寂计记既忌际妓继纪嘉枷夹佳家加荚颊贾甲钾假稼价架驾嫁歼监坚尖笺间煎兼肩艰奸缄茧检柬碱硷拣捡简俭剪减荐槛鉴践贱见键箭件"],["bd40","紷",54,"絯",7],["bd80","絸",32,"健舰剑饯渐溅涧建僵姜将浆江疆蒋桨奖讲匠酱降蕉椒礁焦胶交郊浇骄娇嚼搅铰矫侥脚狡角饺缴绞剿教酵轿较叫窖揭接皆秸街阶截劫节桔杰捷睫竭洁结解姐戒藉芥界借介疥诫届巾筋斤金今津襟紧锦仅谨进靳晋禁近烬浸"],["be40","継",12,"綧",6,"綯",42],["be80","線",32,"尽劲荆兢茎睛晶鲸京惊精粳经井警景颈静境敬镜径痉靖竟竞净炯窘揪究纠玖韭久灸九酒厩救旧臼舅咎就疚鞠拘狙疽居驹菊局咀矩举沮聚拒据巨具距踞锯俱句惧炬剧捐鹃娟倦眷卷绢撅攫抉掘倔爵觉决诀绝均菌钧军君峻"],["bf40","緻",62],["bf80","縺縼",4,"繂",4,"繈",21,"俊竣浚郡骏喀咖卡咯开揩楷凯慨刊堪勘坎砍看康慷糠扛抗亢炕考拷烤靠坷苛柯棵磕颗科壳咳可渴克刻客课肯啃垦恳坑吭空恐孔控抠口扣寇枯哭窟苦酷库裤夸垮挎跨胯块筷侩快宽款匡筐狂框矿眶旷况亏盔岿窥葵奎魁傀"],["c040","繞",35,"纃",23,"纜纝纞"],["c080","纮纴纻纼绖绤绬绹缊缐缞缷缹缻",6,"罃罆",9,"罒罓馈愧溃坤昆捆困括扩廓阔垃拉喇蜡腊辣啦莱来赖蓝婪栏拦篮阑兰澜谰揽览懒缆烂滥琅榔狼廊郎朗浪捞劳牢老佬姥酪烙涝勒乐雷镭蕾磊累儡垒擂肋类泪棱楞冷厘梨犁黎篱狸离漓理李里鲤礼莉荔吏栗丽厉励砾历利傈例俐"],["c140","罖罙罛罜罝罞罠罣",4,"罫罬罭罯罰罳罵罶罷罸罺罻罼罽罿羀羂",7,"羋羍羏",4,"羕",4,"羛羜羠羢羣羥羦羨",6,"羱"],["c180","羳",4,"羺羻羾翀翂翃翄翆翇翈翉翋翍翏",4,"翖翗翙",5,"翢翣痢立粒沥隶力璃哩俩联莲连镰廉怜涟帘敛脸链恋炼练粮凉梁粱良两辆量晾亮谅撩聊僚疗燎寥辽潦了撂镣廖料列裂烈劣猎琳林磷霖临邻鳞淋凛赁吝拎玲菱零龄铃伶羚凌灵陵岭领另令溜琉榴硫馏留刘瘤流柳六龙聋咙笼窿"],["c240","翤翧翨翪翫翬翭翯翲翴",6,"翽翾翿耂耇耈耉耊耎耏耑耓耚耛耝耞耟耡耣耤耫",5,"耲耴耹耺耼耾聀聁聄聅聇聈聉聎聏聐聑聓聕聖聗"],["c280","聙聛",13,"聫",5,"聲",11,"隆垄拢陇楼娄搂篓漏陋芦卢颅庐炉掳卤虏鲁麓碌露路赂鹿潞禄录陆戮驴吕铝侣旅履屡缕虑氯律率滤绿峦挛孪滦卵乱掠略抡轮伦仑沦纶论萝螺罗逻锣箩骡裸落洛骆络妈麻玛码蚂马骂嘛吗埋买麦卖迈脉瞒馒蛮满蔓曼慢漫"],["c340","聾肁肂肅肈肊肍",5,"肔肕肗肙肞肣肦肧肨肬肰肳肵肶肸肹肻胅胇",4,"胏",6,"胘胟胠胢胣胦胮胵胷胹胻胾胿脀脁脃脄脅脇脈脋"],["c380","脌脕脗脙脛脜脝脟",12,"脭脮脰脳脴脵脷脹",4,"脿谩芒茫盲氓忙莽猫茅锚毛矛铆卯茂冒帽貌贸么玫枚梅酶霉煤没眉媒镁每美昧寐妹媚门闷们萌蒙檬盟锰猛梦孟眯醚靡糜迷谜弥米秘觅泌蜜密幂棉眠绵冕免勉娩缅面苗描瞄藐秒渺庙妙蔑灭民抿皿敏悯闽明螟鸣铭名命谬摸"],["c440","腀",5,"腇腉腍腎腏腒腖腗腘腛",4,"腡腢腣腤腦腨腪腫腬腯腲腳腵腶腷腸膁膃",4,"膉膋膌膍膎膐膒",5,"膙膚膞",4,"膤膥"],["c480","膧膩膫",7,"膴",5,"膼膽膾膿臄臅臇臈臉臋臍",6,"摹蘑模膜磨摩魔抹末莫墨默沫漠寞陌谋牟某拇牡亩姆母墓暮幕募慕木目睦牧穆拿哪呐钠那娜纳氖乃奶耐奈南男难囊挠脑恼闹淖呢馁内嫩能妮霓倪泥尼拟你匿腻逆溺蔫拈年碾撵捻念娘酿鸟尿捏聂孽啮镊镍涅您柠狞凝宁"],["c540","臔",14,"臤臥臦臨臩臫臮",4,"臵",5,"臽臿舃與",4,"舎舏舑舓舕",5,"舝舠舤舥舦舧舩舮舲舺舼舽舿"],["c580","艀艁艂艃艅艆艈艊艌艍艎艐",7,"艙艛艜艝艞艠",7,"艩拧泞牛扭钮纽脓浓农弄奴努怒女暖虐疟挪懦糯诺哦欧鸥殴藕呕偶沤啪趴爬帕怕琶拍排牌徘湃派攀潘盘磐盼畔判叛乓庞旁耪胖抛咆刨炮袍跑泡呸胚培裴赔陪配佩沛喷盆砰抨烹澎彭蓬棚硼篷膨朋鹏捧碰坯砒霹批披劈琵毗"],["c640","艪艫艬艭艱艵艶艷艸艻艼芀芁芃芅芆芇芉芌芐芓芔芕芖芚芛芞芠芢芣芧芲芵芶芺芻芼芿苀苂苃苅苆苉苐苖苙苚苝苢苧苨苩苪苬苭苮苰苲苳苵苶苸"],["c680","苺苼",4,"茊茋茍茐茒茓茖茘茙茝",9,"茩茪茮茰茲茷茻茽啤脾疲皮匹痞僻屁譬篇偏片骗飘漂瓢票撇瞥拼频贫品聘乒坪苹萍平凭瓶评屏坡泼颇婆破魄迫粕剖扑铺仆莆葡菩蒲埔朴圃普浦谱曝瀑期欺栖戚妻七凄漆柒沏其棋奇歧畦崎脐齐旗祈祁骑起岂乞企启契砌器气迄弃汽泣讫掐"],["c740","茾茿荁荂荄荅荈荊",4,"荓荕",4,"荝荢荰",6,"荹荺荾",6,"莇莈莊莋莌莍莏莐莑莔莕莖莗莙莚莝莟莡",6,"莬莭莮"],["c780","莯莵莻莾莿菂菃菄菆菈菉菋菍菎菐菑菒菓菕菗菙菚菛菞菢菣菤菦菧菨菫菬菭恰洽牵扦钎铅千迁签仟谦乾黔钱钳前潜遣浅谴堑嵌欠歉枪呛腔羌墙蔷强抢橇锹敲悄桥瞧乔侨巧鞘撬翘峭俏窍切茄且怯窃钦侵亲秦琴勤芹擒禽寝沁青轻氢倾卿清擎晴氰情顷请庆琼穷秋丘邱球求囚酋泅趋区蛆曲躯屈驱渠"],["c840","菮華菳",4,"菺菻菼菾菿萀萂萅萇萈萉萊萐萒",5,"萙萚萛萞",5,"萩",7,"萲",5,"萹萺萻萾",7,"葇葈葉"],["c880","葊",6,"葒",4,"葘葝葞葟葠葢葤",4,"葪葮葯葰葲葴葷葹葻葼取娶龋趣去圈颧权醛泉全痊拳犬券劝缺炔瘸却鹊榷确雀裙群然燃冉染瓤壤攘嚷让饶扰绕惹热壬仁人忍韧任认刃妊纫扔仍日戎茸蓉荣融熔溶容绒冗揉柔肉茹蠕儒孺如辱乳汝入褥软阮蕊瑞锐闰润若弱撒洒萨腮鳃塞赛三叁"],["c940","葽",4,"蒃蒄蒅蒆蒊蒍蒏",7,"蒘蒚蒛蒝蒞蒟蒠蒢",12,"蒰蒱蒳蒵蒶蒷蒻蒼蒾蓀蓂蓃蓅蓆蓇蓈蓋蓌蓎蓏蓒蓔蓕蓗"],["c980","蓘",4,"蓞蓡蓢蓤蓧",4,"蓭蓮蓯蓱",10,"蓽蓾蔀蔁蔂伞散桑嗓丧搔骚扫嫂瑟色涩森僧莎砂杀刹沙纱傻啥煞筛晒珊苫杉山删煽衫闪陕擅赡膳善汕扇缮墒伤商赏晌上尚裳梢捎稍烧芍勺韶少哨邵绍奢赊蛇舌舍赦摄射慑涉社设砷申呻伸身深娠绅神沈审婶甚肾慎渗声生甥牲升绳"],["ca40","蔃",8,"蔍蔎蔏蔐蔒蔔蔕蔖蔘蔙蔛蔜蔝蔞蔠蔢",8,"蔭",9,"蔾",4,"蕄蕅蕆蕇蕋",10],["ca80","蕗蕘蕚蕛蕜蕝蕟",4,"蕥蕦蕧蕩",8,"蕳蕵蕶蕷蕸蕼蕽蕿薀薁省盛剩胜圣师失狮施湿诗尸虱十石拾时什食蚀实识史矢使屎驶始式示士世柿事拭誓逝势是嗜噬适仕侍释饰氏市恃室视试收手首守寿授售受瘦兽蔬枢梳殊抒输叔舒淑疏书赎孰熟薯暑曙署蜀黍鼠属术述树束戍竖墅庶数漱"],["cb40","薂薃薆薈",6,"薐",10,"薝",6,"薥薦薧薩薫薬薭薱",5,"薸薺",6,"藂",6,"藊",4,"藑藒"],["cb80","藔藖",5,"藝",6,"藥藦藧藨藪",14,"恕刷耍摔衰甩帅栓拴霜双爽谁水睡税吮瞬顺舜说硕朔烁斯撕嘶思私司丝死肆寺嗣四伺似饲巳松耸怂颂送宋讼诵搜艘擞嗽苏酥俗素速粟僳塑溯宿诉肃酸蒜算虽隋随绥髓碎岁穗遂隧祟孙损笋蓑梭唆缩琐索锁所塌他它她塔"],["cc40","藹藺藼藽藾蘀",4,"蘆",10,"蘒蘓蘔蘕蘗",15,"蘨蘪",13,"蘹蘺蘻蘽蘾蘿虀"],["cc80","虁",11,"虒虓處",4,"虛虜虝號虠虡虣",7,"獭挞蹋踏胎苔抬台泰酞太态汰坍摊贪瘫滩坛檀痰潭谭谈坦毯袒碳探叹炭汤塘搪堂棠膛唐糖倘躺淌趟烫掏涛滔绦萄桃逃淘陶讨套特藤腾疼誊梯剔踢锑提题蹄啼体替嚏惕涕剃屉天添填田甜恬舔腆挑条迢眺跳贴铁帖厅听烃"],["cd40","虭虯虰虲",6,"蚃",6,"蚎",4,"蚔蚖",5,"蚞",4,"蚥蚦蚫蚭蚮蚲蚳蚷蚸蚹蚻",4,"蛁蛂蛃蛅蛈蛌蛍蛒蛓蛕蛖蛗蛚蛜"],["cd80","蛝蛠蛡蛢蛣蛥蛦蛧蛨蛪蛫蛬蛯蛵蛶蛷蛺蛻蛼蛽蛿蜁蜄蜅蜆蜋蜌蜎蜏蜐蜑蜔蜖汀廷停亭庭挺艇通桐酮瞳同铜彤童桶捅筒统痛偷投头透凸秃突图徒途涂屠土吐兔湍团推颓腿蜕褪退吞屯臀拖托脱鸵陀驮驼椭妥拓唾挖哇蛙洼娃瓦袜歪外豌弯湾玩顽丸烷完碗挽晚皖惋宛婉万腕汪王亡枉网往旺望忘妄威"],["ce40","蜙蜛蜝蜟蜠蜤蜦蜧蜨蜪蜫蜬蜭蜯蜰蜲蜳蜵蜶蜸蜹蜺蜼蜽蝀",6,"蝊蝋蝍蝏蝐蝑蝒蝔蝕蝖蝘蝚",5,"蝡蝢蝦",7,"蝯蝱蝲蝳蝵"],["ce80","蝷蝸蝹蝺蝿螀螁螄螆螇螉螊螌螎",4,"螔螕螖螘",6,"螠",4,"巍微危韦违桅围唯惟为潍维苇萎委伟伪尾纬未蔚味畏胃喂魏位渭谓尉慰卫瘟温蚊文闻纹吻稳紊问嗡翁瓮挝蜗涡窝我斡卧握沃巫呜钨乌污诬屋无芜梧吾吴毋武五捂午舞伍侮坞戊雾晤物勿务悟误昔熙析西硒矽晰嘻吸锡牺"],["cf40","螥螦螧螩螪螮螰螱螲螴螶螷螸螹螻螼螾螿蟁",4,"蟇蟈蟉蟌",4,"蟔",6,"蟜蟝蟞蟟蟡蟢蟣蟤蟦蟧蟨蟩蟫蟬蟭蟯",9],["cf80","蟺蟻蟼蟽蟿蠀蠁蠂蠄",5,"蠋",7,"蠔蠗蠘蠙蠚蠜",4,"蠣稀息希悉膝夕惜熄烯溪汐犀檄袭席习媳喜铣洗系隙戏细瞎虾匣霞辖暇峡侠狭下厦夏吓掀锨先仙鲜纤咸贤衔舷闲涎弦嫌显险现献县腺馅羡宪陷限线相厢镶香箱襄湘乡翔祥详想响享项巷橡像向象萧硝霄削哮嚣销消宵淆晓"],["d040","蠤",13,"蠳",5,"蠺蠻蠽蠾蠿衁衂衃衆",5,"衎",5,"衕衖衘衚",6,"衦衧衪衭衯衱衳衴衵衶衸衹衺"],["d080","衻衼袀袃袆袇袉袊袌袎袏袐袑袓袔袕袗",4,"袝",4,"袣袥",5,"小孝校肖啸笑效楔些歇蝎鞋协挟携邪斜胁谐写械卸蟹懈泄泻谢屑薪芯锌欣辛新忻心信衅星腥猩惺兴刑型形邢行醒幸杏性姓兄凶胸匈汹雄熊休修羞朽嗅锈秀袖绣墟戌需虚嘘须徐许蓄酗叙旭序畜恤絮婿绪续轩喧宣悬旋玄"],["d140","袬袮袯袰袲",4,"袸袹袺袻袽袾袿裀裃裄裇裈裊裋裌裍裏裐裑裓裖裗裚",4,"裠裡裦裧裩",6,"裲裵裶裷裺裻製裿褀褁褃",5],["d180","褉褋",4,"褑褔",4,"褜",4,"褢褣褤褦褧褨褩褬褭褮褯褱褲褳褵褷选癣眩绚靴薛学穴雪血勋熏循旬询寻驯巡殉汛训讯逊迅压押鸦鸭呀丫芽牙蚜崖衙涯雅哑亚讶焉咽阉烟淹盐严研蜒岩延言颜阎炎沿奄掩眼衍演艳堰燕厌砚雁唁彦焰宴谚验殃央鸯秧杨扬佯疡羊洋阳氧仰痒养样漾邀腰妖瑶"],["d240","褸",8,"襂襃襅",24,"襠",5,"襧",19,"襼"],["d280","襽襾覀覂覄覅覇",26,"摇尧遥窑谣姚咬舀药要耀椰噎耶爷野冶也页掖业叶曳腋夜液一壹医揖铱依伊衣颐夷遗移仪胰疑沂宜姨彝椅蚁倚已乙矣以艺抑易邑屹亿役臆逸肄疫亦裔意毅忆义益溢诣议谊译异翼翌绎茵荫因殷音阴姻吟银淫寅饮尹引隐"],["d340","覢",30,"觃觍觓觔觕觗觘觙觛觝觟觠觡觢觤觧觨觩觪觬觭觮觰觱觲觴",6],["d380","觻",4,"訁",5,"計",21,"印英樱婴鹰应缨莹萤营荧蝇迎赢盈影颖硬映哟拥佣臃痈庸雍踊蛹咏泳涌永恿勇用幽优悠忧尤由邮铀犹油游酉有友右佑釉诱又幼迂淤于盂榆虞愚舆余俞逾鱼愉渝渔隅予娱雨与屿禹宇语羽玉域芋郁吁遇喻峪御愈欲狱育誉"],["d440","訞",31,"訿",8,"詉",21],["d480","詟",25,"詺",6,"浴寓裕预豫驭鸳渊冤元垣袁原援辕园员圆猿源缘远苑愿怨院曰约越跃钥岳粤月悦阅耘云郧匀陨允运蕴酝晕韵孕匝砸杂栽哉灾宰载再在咱攒暂赞赃脏葬遭糟凿藻枣早澡蚤躁噪造皂灶燥责择则泽贼怎增憎曾赠扎喳渣札轧"],["d540","誁",7,"誋",7,"誔",46],["d580","諃",32,"铡闸眨栅榨咋乍炸诈摘斋宅窄债寨瞻毡詹粘沾盏斩辗崭展蘸栈占战站湛绽樟章彰漳张掌涨杖丈帐账仗胀瘴障招昭找沼赵照罩兆肇召遮折哲蛰辙者锗蔗这浙珍斟真甄砧臻贞针侦枕疹诊震振镇阵蒸挣睁征狰争怔整拯正政"],["d640","諤",34,"謈",27],["d680","謤謥謧",30,"帧症郑证芝枝支吱蜘知肢脂汁之织职直植殖执值侄址指止趾只旨纸志挚掷至致置帜峙制智秩稚质炙痔滞治窒中盅忠钟衷终种肿重仲众舟周州洲诌粥轴肘帚咒皱宙昼骤珠株蛛朱猪诸诛逐竹烛煮拄瞩嘱主著柱助蛀贮铸筑"],["d740","譆",31,"譧",4,"譭",25],["d780","讇",24,"讬讱讻诇诐诪谉谞住注祝驻抓爪拽专砖转撰赚篆桩庄装妆撞壮状椎锥追赘坠缀谆准捉拙卓桌琢茁酌啄着灼浊兹咨资姿滋淄孜紫仔籽滓子自渍字鬃棕踪宗综总纵邹走奏揍租足卒族祖诅阻组钻纂嘴醉最罪尊遵昨左佐柞做作坐座"],["d840","谸",8,"豂豃豄豅豈豊豋豍",7,"豖豗豘豙豛",5,"豣",6,"豬",6,"豴豵豶豷豻",6,"貃貄貆貇"],["d880","貈貋貍",6,"貕貖貗貙",20,"亍丌兀丐廿卅丕亘丞鬲孬噩丨禺丿匕乇夭爻卮氐囟胤馗毓睾鼗丶亟鼐乜乩亓芈孛啬嘏仄厍厝厣厥厮靥赝匚叵匦匮匾赜卦卣刂刈刎刭刳刿剀剌剞剡剜蒯剽劂劁劐劓冂罔亻仃仉仂仨仡仫仞伛仳伢佤仵伥伧伉伫佞佧攸佚佝"],["d940","貮",62],["d980","賭",32,"佟佗伲伽佶佴侑侉侃侏佾佻侪佼侬侔俦俨俪俅俚俣俜俑俟俸倩偌俳倬倏倮倭俾倜倌倥倨偾偃偕偈偎偬偻傥傧傩傺僖儆僭僬僦僮儇儋仝氽佘佥俎龠汆籴兮巽黉馘冁夔勹匍訇匐凫夙兕亠兖亳衮袤亵脔裒禀嬴蠃羸冫冱冽冼"],["da40","贎",14,"贠赑赒赗赟赥赨赩赪赬赮赯赱赲赸",8,"趂趃趆趇趈趉趌",4,"趒趓趕",9,"趠趡"],["da80","趢趤",12,"趲趶趷趹趻趽跀跁跂跅跇跈跉跊跍跐跒跓跔凇冖冢冥讠讦讧讪讴讵讷诂诃诋诏诎诒诓诔诖诘诙诜诟诠诤诨诩诮诰诳诶诹诼诿谀谂谄谇谌谏谑谒谔谕谖谙谛谘谝谟谠谡谥谧谪谫谮谯谲谳谵谶卩卺阝阢阡阱阪阽阼陂陉陔陟陧陬陲陴隈隍隗隰邗邛邝邙邬邡邴邳邶邺"],["db40","跕跘跙跜跠跡跢跥跦跧跩跭跮跰跱跲跴跶跼跾",6,"踆踇踈踋踍踎踐踑踒踓踕",7,"踠踡踤",4,"踫踭踰踲踳踴踶踷踸踻踼踾"],["db80","踿蹃蹅蹆蹌",4,"蹓",5,"蹚",11,"蹧蹨蹪蹫蹮蹱邸邰郏郅邾郐郄郇郓郦郢郜郗郛郫郯郾鄄鄢鄞鄣鄱鄯鄹酃酆刍奂劢劬劭劾哿勐勖勰叟燮矍廴凵凼鬯厶弁畚巯坌垩垡塾墼壅壑圩圬圪圳圹圮圯坜圻坂坩垅坫垆坼坻坨坭坶坳垭垤垌垲埏垧垴垓垠埕埘埚埙埒垸埴埯埸埤埝"],["dc40","蹳蹵蹷",4,"蹽蹾躀躂躃躄躆躈",6,"躑躒躓躕",6,"躝躟",11,"躭躮躰躱躳",6,"躻",7],["dc80","軃",10,"軏",21,"堋堍埽埭堀堞堙塄堠塥塬墁墉墚墀馨鼙懿艹艽艿芏芊芨芄芎芑芗芙芫芸芾芰苈苊苣芘芷芮苋苌苁芩芴芡芪芟苄苎芤苡茉苷苤茏茇苜苴苒苘茌苻苓茑茚茆茔茕苠苕茜荑荛荜茈莒茼茴茱莛荞茯荏荇荃荟荀茗荠茭茺茳荦荥"],["dd40","軥",62],["dd80","輤",32,"荨茛荩荬荪荭荮莰荸莳莴莠莪莓莜莅荼莶莩荽莸荻莘莞莨莺莼菁萁菥菘堇萘萋菝菽菖萜萸萑萆菔菟萏萃菸菹菪菅菀萦菰菡葜葑葚葙葳蒇蒈葺蒉葸萼葆葩葶蒌蒎萱葭蓁蓍蓐蓦蒽蓓蓊蒿蒺蓠蒡蒹蒴蒗蓥蓣蔌甍蔸蓰蔹蔟蔺"],["de40","轅",32,"轪辀辌辒辝辠辡辢辤辥辦辧辪辬辭辮辯農辳辴辵辷辸辺辻込辿迀迃迆"],["de80","迉",4,"迏迒迖迗迚迠迡迣迧迬迯迱迲迴迵迶迺迻迼迾迿逇逈逌逎逓逕逘蕖蔻蓿蓼蕙蕈蕨蕤蕞蕺瞢蕃蕲蕻薤薨薇薏蕹薮薜薅薹薷薰藓藁藜藿蘧蘅蘩蘖蘼廾弈夼奁耷奕奚奘匏尢尥尬尴扌扪抟抻拊拚拗拮挢拶挹捋捃掭揶捱捺掎掴捭掬掊捩掮掼揲揸揠揿揄揞揎摒揆掾摅摁搋搛搠搌搦搡摞撄摭撖"],["df40","這逜連逤逥逧",5,"逰",4,"逷逹逺逽逿遀遃遅遆遈",4,"過達違遖遙遚遜",5,"遤遦遧適遪遫遬遯",4,"遶",6,"遾邁"],["df80","還邅邆邇邉邊邌",4,"邒邔邖邘邚邜邞邟邠邤邥邧邨邩邫邭邲邷邼邽邿郀摺撷撸撙撺擀擐擗擤擢攉攥攮弋忒甙弑卟叱叽叩叨叻吒吖吆呋呒呓呔呖呃吡呗呙吣吲咂咔呷呱呤咚咛咄呶呦咝哐咭哂咴哒咧咦哓哔呲咣哕咻咿哌哙哚哜咩咪咤哝哏哞唛哧唠哽唔哳唢唣唏唑唧唪啧喏喵啉啭啁啕唿啐唼"],["e040","郂郃郆郈郉郋郌郍郒郔郕郖郘郙郚郞郟郠郣郤郥郩郪郬郮郰郱郲郳郵郶郷郹郺郻郼郿鄀鄁鄃鄅",19,"鄚鄛鄜"],["e080","鄝鄟鄠鄡鄤",10,"鄰鄲",6,"鄺",8,"酄唷啖啵啶啷唳唰啜喋嗒喃喱喹喈喁喟啾嗖喑啻嗟喽喾喔喙嗪嗷嗉嘟嗑嗫嗬嗔嗦嗝嗄嗯嗥嗲嗳嗌嗍嗨嗵嗤辔嘞嘈嘌嘁嘤嘣嗾嘀嘧嘭噘嘹噗嘬噍噢噙噜噌噔嚆噤噱噫噻噼嚅嚓嚯囔囗囝囡囵囫囹囿圄圊圉圜帏帙帔帑帱帻帼"],["e140","酅酇酈酑酓酔酕酖酘酙酛酜酟酠酦酧酨酫酭酳酺酻酼醀",4,"醆醈醊醎醏醓",6,"醜",5,"醤",5,"醫醬醰醱醲醳醶醷醸醹醻"],["e180","醼",10,"釈釋釐釒",9,"針",8,"帷幄幔幛幞幡岌屺岍岐岖岈岘岙岑岚岜岵岢岽岬岫岱岣峁岷峄峒峤峋峥崂崃崧崦崮崤崞崆崛嵘崾崴崽嵬嵛嵯嵝嵫嵋嵊嵩嵴嶂嶙嶝豳嶷巅彳彷徂徇徉後徕徙徜徨徭徵徼衢彡犭犰犴犷犸狃狁狎狍狒狨狯狩狲狴狷猁狳猃狺"],["e240","釦",62],["e280","鈥",32,"狻猗猓猡猊猞猝猕猢猹猥猬猸猱獐獍獗獠獬獯獾舛夥飧夤夂饣饧",5,"饴饷饽馀馄馇馊馍馐馑馓馔馕庀庑庋庖庥庠庹庵庾庳赓廒廑廛廨廪膺忄忉忖忏怃忮怄忡忤忾怅怆忪忭忸怙怵怦怛怏怍怩怫怊怿怡恸恹恻恺恂"],["e340","鉆",45,"鉵",16],["e380","銆",7,"銏",24,"恪恽悖悚悭悝悃悒悌悛惬悻悱惝惘惆惚悴愠愦愕愣惴愀愎愫慊慵憬憔憧憷懔懵忝隳闩闫闱闳闵闶闼闾阃阄阆阈阊阋阌阍阏阒阕阖阗阙阚丬爿戕氵汔汜汊沣沅沐沔沌汨汩汴汶沆沩泐泔沭泷泸泱泗沲泠泖泺泫泮沱泓泯泾"],["e440","銨",5,"銯",24,"鋉",31],["e480","鋩",32,"洹洧洌浃浈洇洄洙洎洫浍洮洵洚浏浒浔洳涑浯涞涠浞涓涔浜浠浼浣渚淇淅淞渎涿淠渑淦淝淙渖涫渌涮渫湮湎湫溲湟溆湓湔渲渥湄滟溱溘滠漭滢溥溧溽溻溷滗溴滏溏滂溟潢潆潇漤漕滹漯漶潋潴漪漉漩澉澍澌潸潲潼潺濑"],["e540","錊",51,"錿",10],["e580","鍊",31,"鍫濉澧澹澶濂濡濮濞濠濯瀚瀣瀛瀹瀵灏灞宀宄宕宓宥宸甯骞搴寤寮褰寰蹇謇辶迓迕迥迮迤迩迦迳迨逅逄逋逦逑逍逖逡逵逶逭逯遄遑遒遐遨遘遢遛暹遴遽邂邈邃邋彐彗彖彘尻咫屐屙孱屣屦羼弪弩弭艴弼鬻屮妁妃妍妩妪妣"],["e640","鍬",34,"鎐",27],["e680","鎬",29,"鏋鏌鏍妗姊妫妞妤姒妲妯姗妾娅娆姝娈姣姘姹娌娉娲娴娑娣娓婀婧婊婕娼婢婵胬媪媛婷婺媾嫫媲嫒嫔媸嫠嫣嫱嫖嫦嫘嫜嬉嬗嬖嬲嬷孀尕尜孚孥孳孑孓孢驵驷驸驺驿驽骀骁骅骈骊骐骒骓骖骘骛骜骝骟骠骢骣骥骧纟纡纣纥纨纩"],["e740","鏎",7,"鏗",54],["e780","鐎",32,"纭纰纾绀绁绂绉绋绌绐绔绗绛绠绡绨绫绮绯绱绲缍绶绺绻绾缁缂缃缇缈缋缌缏缑缒缗缙缜缛缟缡",6,"缪缫缬缭缯",4,"缵幺畿巛甾邕玎玑玮玢玟珏珂珑玷玳珀珉珈珥珙顼琊珩珧珞玺珲琏琪瑛琦琥琨琰琮琬"],["e840","鐯",14,"鐿",43,"鑬鑭鑮鑯"],["e880","鑰",20,"钑钖钘铇铏铓铔铚铦铻锜锠琛琚瑁瑜瑗瑕瑙瑷瑭瑾璜璎璀璁璇璋璞璨璩璐璧瓒璺韪韫韬杌杓杞杈杩枥枇杪杳枘枧杵枨枞枭枋杷杼柰栉柘栊柩枰栌柙枵柚枳柝栀柃枸柢栎柁柽栲栳桠桡桎桢桄桤梃栝桕桦桁桧桀栾桊桉栩梵梏桴桷梓桫棂楮棼椟椠棹"],["e940","锧锳锽镃镈镋镕镚镠镮镴镵長",7,"門",42],["e980","閫",32,"椤棰椋椁楗棣椐楱椹楠楂楝榄楫榀榘楸椴槌榇榈槎榉楦楣楹榛榧榻榫榭槔榱槁槊槟榕槠榍槿樯槭樗樘橥槲橄樾檠橐橛樵檎橹樽樨橘橼檑檐檩檗檫猷獒殁殂殇殄殒殓殍殚殛殡殪轫轭轱轲轳轵轶轸轷轹轺轼轾辁辂辄辇辋"],["ea40","闌",27,"闬闿阇阓阘阛阞阠阣",6,"阫阬阭阯阰阷阸阹阺阾陁陃陊陎陏陑陒陓陖陗"],["ea80","陘陙陚陜陝陞陠陣陥陦陫陭",4,"陳陸",12,"隇隉隊辍辎辏辘辚軎戋戗戛戟戢戡戥戤戬臧瓯瓴瓿甏甑甓攴旮旯旰昊昙杲昃昕昀炅曷昝昴昱昶昵耆晟晔晁晏晖晡晗晷暄暌暧暝暾曛曜曦曩贲贳贶贻贽赀赅赆赈赉赇赍赕赙觇觊觋觌觎觏觐觑牮犟牝牦牯牾牿犄犋犍犏犒挈挲掰"],["eb40","隌階隑隒隓隕隖隚際隝",9,"隨",7,"隱隲隴隵隷隸隺隻隿雂雃雈雊雋雐雑雓雔雖",9,"雡",6,"雫"],["eb80","雬雭雮雰雱雲雴雵雸雺電雼雽雿霂霃霅霊霋霌霐霑霒霔霕霗",4,"霝霟霠搿擘耄毪毳毽毵毹氅氇氆氍氕氘氙氚氡氩氤氪氲攵敕敫牍牒牖爰虢刖肟肜肓肼朊肽肱肫肭肴肷胧胨胩胪胛胂胄胙胍胗朐胝胫胱胴胭脍脎胲胼朕脒豚脶脞脬脘脲腈腌腓腴腙腚腱腠腩腼腽腭腧塍媵膈膂膑滕膣膪臌朦臊膻"],["ec40","霡",8,"霫霬霮霯霱霳",4,"霺霻霼霽霿",18,"靔靕靗靘靚靜靝靟靣靤靦靧靨靪",7],["ec80","靲靵靷",4,"靽",7,"鞆",4,"鞌鞎鞏鞐鞓鞕鞖鞗鞙",4,"臁膦欤欷欹歃歆歙飑飒飓飕飙飚殳彀毂觳斐齑斓於旆旄旃旌旎旒旖炀炜炖炝炻烀炷炫炱烨烊焐焓焖焯焱煳煜煨煅煲煊煸煺熘熳熵熨熠燠燔燧燹爝爨灬焘煦熹戾戽扃扈扉礻祀祆祉祛祜祓祚祢祗祠祯祧祺禅禊禚禧禳忑忐"],["ed40","鞞鞟鞡鞢鞤",6,"鞬鞮鞰鞱鞳鞵",46],["ed80","韤韥韨韮",4,"韴韷",23,"怼恝恚恧恁恙恣悫愆愍慝憩憝懋懑戆肀聿沓泶淼矶矸砀砉砗砘砑斫砭砜砝砹砺砻砟砼砥砬砣砩硎硭硖硗砦硐硇硌硪碛碓碚碇碜碡碣碲碹碥磔磙磉磬磲礅磴礓礤礞礴龛黹黻黼盱眄眍盹眇眈眚眢眙眭眦眵眸睐睑睇睃睚睨"],["ee40","頏",62],["ee80","顎",32,"睢睥睿瞍睽瞀瞌瞑瞟瞠瞰瞵瞽町畀畎畋畈畛畲畹疃罘罡罟詈罨罴罱罹羁罾盍盥蠲钅钆钇钋钊钌钍钏钐钔钗钕钚钛钜钣钤钫钪钭钬钯钰钲钴钶",4,"钼钽钿铄铈",6,"铐铑铒铕铖铗铙铘铛铞铟铠铢铤铥铧铨铪"],["ef40","顯",5,"颋颎颒颕颙颣風",37,"飏飐飔飖飗飛飜飝飠",4],["ef80","飥飦飩",30,"铩铫铮铯铳铴铵铷铹铼铽铿锃锂锆锇锉锊锍锎锏锒",4,"锘锛锝锞锟锢锪锫锩锬锱锲锴锶锷锸锼锾锿镂锵镄镅镆镉镌镎镏镒镓镔镖镗镘镙镛镞镟镝镡镢镤",8,"镯镱镲镳锺矧矬雉秕秭秣秫稆嵇稃稂稞稔"],["f040","餈",4,"餎餏餑",28,"餯",26],["f080","饊",9,"饖",12,"饤饦饳饸饹饻饾馂馃馉稹稷穑黏馥穰皈皎皓皙皤瓞瓠甬鸠鸢鸨",4,"鸲鸱鸶鸸鸷鸹鸺鸾鹁鹂鹄鹆鹇鹈鹉鹋鹌鹎鹑鹕鹗鹚鹛鹜鹞鹣鹦",6,"鹱鹭鹳疒疔疖疠疝疬疣疳疴疸痄疱疰痃痂痖痍痣痨痦痤痫痧瘃痱痼痿瘐瘀瘅瘌瘗瘊瘥瘘瘕瘙"],["f140","馌馎馚",10,"馦馧馩",47],["f180","駙",32,"瘛瘼瘢瘠癀瘭瘰瘿瘵癃瘾瘳癍癞癔癜癖癫癯翊竦穸穹窀窆窈窕窦窠窬窨窭窳衤衩衲衽衿袂袢裆袷袼裉裢裎裣裥裱褚裼裨裾裰褡褙褓褛褊褴褫褶襁襦襻疋胥皲皴矜耒耔耖耜耠耢耥耦耧耩耨耱耋耵聃聆聍聒聩聱覃顸颀颃"],["f240","駺",62],["f280","騹",32,"颉颌颍颏颔颚颛颞颟颡颢颥颦虍虔虬虮虿虺虼虻蚨蚍蚋蚬蚝蚧蚣蚪蚓蚩蚶蛄蚵蛎蚰蚺蚱蚯蛉蛏蚴蛩蛱蛲蛭蛳蛐蜓蛞蛴蛟蛘蛑蜃蜇蛸蜈蜊蜍蜉蜣蜻蜞蜥蜮蜚蜾蝈蜴蜱蜩蜷蜿螂蜢蝽蝾蝻蝠蝰蝌蝮螋蝓蝣蝼蝤蝙蝥螓螯螨蟒"],["f340","驚",17,"驲骃骉骍骎骔骕骙骦骩",6,"骲骳骴骵骹骻骽骾骿髃髄髆",4,"髍髎髏髐髒體髕髖髗髙髚髛髜"],["f380","髝髞髠髢髣髤髥髧髨髩髪髬髮髰",8,"髺髼",6,"鬄鬅鬆蟆螈螅螭螗螃螫蟥螬螵螳蟋蟓螽蟑蟀蟊蟛蟪蟠蟮蠖蠓蟾蠊蠛蠡蠹蠼缶罂罄罅舐竺竽笈笃笄笕笊笫笏筇笸笪笙笮笱笠笥笤笳笾笞筘筚筅筵筌筝筠筮筻筢筲筱箐箦箧箸箬箝箨箅箪箜箢箫箴篑篁篌篝篚篥篦篪簌篾篼簏簖簋"],["f440","鬇鬉",5,"鬐鬑鬒鬔",10,"鬠鬡鬢鬤",10,"鬰鬱鬳",7,"鬽鬾鬿魀魆魊魋魌魎魐魒魓魕",5],["f480","魛",32,"簟簪簦簸籁籀臾舁舂舄臬衄舡舢舣舭舯舨舫舸舻舳舴舾艄艉艋艏艚艟艨衾袅袈裘裟襞羝羟羧羯羰羲籼敉粑粝粜粞粢粲粼粽糁糇糌糍糈糅糗糨艮暨羿翎翕翥翡翦翩翮翳糸絷綦綮繇纛麸麴赳趄趔趑趱赧赭豇豉酊酐酎酏酤"],["f540","魼",62],["f580","鮻",32,"酢酡酰酩酯酽酾酲酴酹醌醅醐醍醑醢醣醪醭醮醯醵醴醺豕鹾趸跫踅蹙蹩趵趿趼趺跄跖跗跚跞跎跏跛跆跬跷跸跣跹跻跤踉跽踔踝踟踬踮踣踯踺蹀踹踵踽踱蹉蹁蹂蹑蹒蹊蹰蹶蹼蹯蹴躅躏躔躐躜躞豸貂貊貅貘貔斛觖觞觚觜"],["f640","鯜",62],["f680","鰛",32,"觥觫觯訾謦靓雩雳雯霆霁霈霏霎霪霭霰霾龀龃龅",5,"龌黾鼋鼍隹隼隽雎雒瞿雠銎銮鋈錾鍪鏊鎏鐾鑫鱿鲂鲅鲆鲇鲈稣鲋鲎鲐鲑鲒鲔鲕鲚鲛鲞",5,"鲥",4,"鲫鲭鲮鲰",7,"鲺鲻鲼鲽鳄鳅鳆鳇鳊鳋"],["f740","鰼",62],["f780","鱻鱽鱾鲀鲃鲄鲉鲊鲌鲏鲓鲖鲗鲘鲙鲝鲪鲬鲯鲹鲾",4,"鳈鳉鳑鳒鳚鳛鳠鳡鳌",4,"鳓鳔鳕鳗鳘鳙鳜鳝鳟鳢靼鞅鞑鞒鞔鞯鞫鞣鞲鞴骱骰骷鹘骶骺骼髁髀髅髂髋髌髑魅魃魇魉魈魍魑飨餍餮饕饔髟髡髦髯髫髻髭髹鬈鬏鬓鬟鬣麽麾縻麂麇麈麋麒鏖麝麟黛黜黝黠黟黢黩黧黥黪黯鼢鼬鼯鼹鼷鼽鼾齄"],["f840","鳣",62],["f880","鴢",32],["f940","鵃",62],["f980","鶂",32],["fa40","鶣",62],["fa80","鷢",32],["fb40","鸃",27,"鸤鸧鸮鸰鸴鸻鸼鹀鹍鹐鹒鹓鹔鹖鹙鹝鹟鹠鹡鹢鹥鹮鹯鹲鹴",9,"麀"],["fb80","麁麃麄麅麆麉麊麌",5,"麔",8,"麞麠",5,"麧麨麩麪"],["fc40","麫",8,"麵麶麷麹麺麼麿",4,"黅黆黇黈黊黋黌黐黒黓黕黖黗黙黚點黡黣黤黦黨黫黬黭黮黰",8,"黺黽黿",6],["fc80","鼆",4,"鼌鼏鼑鼒鼔鼕鼖鼘鼚",5,"鼡鼣",8,"鼭鼮鼰鼱"],["fd40","鼲",4,"鼸鼺鼼鼿",4,"齅",10,"齒",38],["fd80","齹",5,"龁龂龍",11,"龜龝龞龡",4,"郎凉秊裏隣"],["fe40","兀嗀﨎﨏﨑﨓﨔礼﨟蘒﨡﨣﨤﨧﨨﨩"]]'); + +/***/ }), + +/***/ 5923: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('[["0","\\u0000",127],["8141","갂갃갅갆갋",4,"갘갞갟갡갢갣갥",6,"갮갲갳갴"],["8161","갵갶갷갺갻갽갾갿걁",9,"걌걎",5,"걕"],["8181","걖걗걙걚걛걝",18,"걲걳걵걶걹걻",4,"겂겇겈겍겎겏겑겒겓겕",6,"겞겢",5,"겫겭겮겱",6,"겺겾겿곀곂곃곅곆곇곉곊곋곍",7,"곖곘",7,"곢곣곥곦곩곫곭곮곲곴곷",4,"곾곿괁괂괃괅괇",4,"괎괐괒괓"],["8241","괔괕괖괗괙괚괛괝괞괟괡",7,"괪괫괮",5],["8261","괶괷괹괺괻괽",6,"굆굈굊",5,"굑굒굓굕굖굗"],["8281","굙",7,"굢굤",7,"굮굯굱굲굷굸굹굺굾궀궃",4,"궊궋궍궎궏궑",10,"궞",5,"궥",17,"궸",7,"귂귃귅귆귇귉",6,"귒귔",7,"귝귞귟귡귢귣귥",18],["8341","귺귻귽귾긂",5,"긊긌긎",5,"긕",7],["8361","긝",18,"긲긳긵긶긹긻긼"],["8381","긽긾긿깂깄깇깈깉깋깏깑깒깓깕깗",4,"깞깢깣깤깦깧깪깫깭깮깯깱",6,"깺깾",5,"꺆",5,"꺍",46,"꺿껁껂껃껅",6,"껎껒",5,"껚껛껝",8],["8441","껦껧껩껪껬껮",5,"껵껶껷껹껺껻껽",8],["8461","꼆꼉꼊꼋꼌꼎꼏꼑",18],["8481","꼤",7,"꼮꼯꼱꼳꼵",6,"꼾꽀꽄꽅꽆꽇꽊",5,"꽑",10,"꽞",5,"꽦",18,"꽺",5,"꾁꾂꾃꾅꾆꾇꾉",6,"꾒꾓꾔꾖",5,"꾝",26,"꾺꾻꾽꾾"],["8541","꾿꿁",5,"꿊꿌꿏",4,"꿕",6,"꿝",4],["8561","꿢",5,"꿪",5,"꿲꿳꿵꿶꿷꿹",6,"뀂뀃"],["8581","뀅",6,"뀍뀎뀏뀑뀒뀓뀕",6,"뀞",9,"뀩",26,"끆끇끉끋끍끏끐끑끒끖끘끚끛끜끞",29,"끾끿낁낂낃낅",6,"낎낐낒",5,"낛낝낞낣낤"],["8641","낥낦낧낪낰낲낶낷낹낺낻낽",6,"냆냊",5,"냒"],["8661","냓냕냖냗냙",6,"냡냢냣냤냦",10],["8681","냱",22,"넊넍넎넏넑넔넕넖넗넚넞",4,"넦넧넩넪넫넭",6,"넶넺",5,"녂녃녅녆녇녉",6,"녒녓녖녗녙녚녛녝녞녟녡",22,"녺녻녽녾녿놁놃",4,"놊놌놎놏놐놑놕놖놗놙놚놛놝"],["8741","놞",9,"놩",15],["8761","놹",18,"뇍뇎뇏뇑뇒뇓뇕"],["8781","뇖",5,"뇞뇠",7,"뇪뇫뇭뇮뇯뇱",7,"뇺뇼뇾",5,"눆눇눉눊눍",6,"눖눘눚",5,"눡",18,"눵",6,"눽",26,"뉙뉚뉛뉝뉞뉟뉡",6,"뉪",4],["8841","뉯",4,"뉶",5,"뉽",6,"늆늇늈늊",4],["8861","늏늒늓늕늖늗늛",4,"늢늤늧늨늩늫늭늮늯늱늲늳늵늶늷"],["8881","늸",15,"닊닋닍닎닏닑닓",4,"닚닜닞닟닠닡닣닧닩닪닰닱닲닶닼닽닾댂댃댅댆댇댉",6,"댒댖",5,"댝",54,"덗덙덚덝덠덡덢덣"],["8941","덦덨덪덬덭덯덲덳덵덶덷덹",6,"뎂뎆",5,"뎍"],["8961","뎎뎏뎑뎒뎓뎕",10,"뎢",5,"뎩뎪뎫뎭"],["8981","뎮",21,"돆돇돉돊돍돏돑돒돓돖돘돚돜돞돟돡돢돣돥돦돧돩",18,"돽",18,"됑",6,"됙됚됛됝됞됟됡",6,"됪됬",7,"됵",15],["8a41","둅",10,"둒둓둕둖둗둙",6,"둢둤둦"],["8a61","둧",4,"둭",18,"뒁뒂"],["8a81","뒃",4,"뒉",19,"뒞",5,"뒥뒦뒧뒩뒪뒫뒭",7,"뒶뒸뒺",5,"듁듂듃듅듆듇듉",6,"듑듒듓듔듖",5,"듞듟듡듢듥듧",4,"듮듰듲",5,"듹",26,"딖딗딙딚딝"],["8b41","딞",5,"딦딫",4,"딲딳딵딶딷딹",6,"땂땆"],["8b61","땇땈땉땊땎땏땑땒땓땕",6,"땞땢",8],["8b81","땫",52,"떢떣떥떦떧떩떬떭떮떯떲떶",4,"떾떿뗁뗂뗃뗅",6,"뗎뗒",5,"뗙",18,"뗭",18],["8c41","똀",15,"똒똓똕똖똗똙",4],["8c61","똞",6,"똦",5,"똭",6,"똵",5],["8c81","똻",12,"뙉",26,"뙥뙦뙧뙩",50,"뚞뚟뚡뚢뚣뚥",5,"뚭뚮뚯뚰뚲",16],["8d41","뛃",16,"뛕",8],["8d61","뛞",17,"뛱뛲뛳뛵뛶뛷뛹뛺"],["8d81","뛻",4,"뜂뜃뜄뜆",33,"뜪뜫뜭뜮뜱",6,"뜺뜼",7,"띅띆띇띉띊띋띍",6,"띖",9,"띡띢띣띥띦띧띩",6,"띲띴띶",5,"띾띿랁랂랃랅",6,"랎랓랔랕랚랛랝랞"],["8e41","랟랡",6,"랪랮",5,"랶랷랹",8],["8e61","럂",4,"럈럊",19],["8e81","럞",13,"럮럯럱럲럳럵",6,"럾렂",4,"렊렋렍렎렏렑",6,"렚렜렞",5,"렦렧렩렪렫렭",6,"렶렺",5,"롁롂롃롅",11,"롒롔",7,"롞롟롡롢롣롥",6,"롮롰롲",5,"롹롺롻롽",7],["8f41","뢅",7,"뢎",17],["8f61","뢠",7,"뢩",6,"뢱뢲뢳뢵뢶뢷뢹",4],["8f81","뢾뢿룂룄룆",5,"룍룎룏룑룒룓룕",7,"룞룠룢",5,"룪룫룭룮룯룱",6,"룺룼룾",5,"뤅",18,"뤙",6,"뤡",26,"뤾뤿륁륂륃륅",6,"륍륎륐륒",5],["9041","륚륛륝륞륟륡",6,"륪륬륮",5,"륶륷륹륺륻륽"],["9061","륾",5,"릆릈릋릌릏",15],["9081","릟",12,"릮릯릱릲릳릵",6,"릾맀맂",5,"맊맋맍맓",4,"맚맜맟맠맢맦맧맩맪맫맭",6,"맶맻",4,"먂",5,"먉",11,"먖",33,"먺먻먽먾먿멁멃멄멅멆"],["9141","멇멊멌멏멐멑멒멖멗멙멚멛멝",6,"멦멪",5],["9161","멲멳멵멶멷멹",9,"몆몈몉몊몋몍",5],["9181","몓",20,"몪몭몮몯몱몳",4,"몺몼몾",5,"뫅뫆뫇뫉",14,"뫚",33,"뫽뫾뫿묁묂묃묅",7,"묎묐묒",5,"묙묚묛묝묞묟묡",6],["9241","묨묪묬",7,"묷묹묺묿",4,"뭆뭈뭊뭋뭌뭎뭑뭒"],["9261","뭓뭕뭖뭗뭙",7,"뭢뭤",7,"뭭",4],["9281","뭲",21,"뮉뮊뮋뮍뮎뮏뮑",18,"뮥뮦뮧뮩뮪뮫뮭",6,"뮵뮶뮸",7,"믁믂믃믅믆믇믉",6,"믑믒믔",35,"믺믻믽믾밁"],["9341","밃",4,"밊밎밐밒밓밙밚밠밡밢밣밦밨밪밫밬밮밯밲밳밵"],["9361","밶밷밹",6,"뱂뱆뱇뱈뱊뱋뱎뱏뱑",8],["9381","뱚뱛뱜뱞",37,"벆벇벉벊벍벏",4,"벖벘벛",4,"벢벣벥벦벩",6,"벲벶",5,"벾벿볁볂볃볅",7,"볎볒볓볔볖볗볙볚볛볝",22,"볷볹볺볻볽"],["9441","볾",5,"봆봈봊",5,"봑봒봓봕",8],["9461","봞",5,"봥",6,"봭",12],["9481","봺",5,"뵁",6,"뵊뵋뵍뵎뵏뵑",6,"뵚",9,"뵥뵦뵧뵩",22,"붂붃붅붆붋",4,"붒붔붖붗붘붛붝",6,"붥",10,"붱",6,"붹",24],["9541","뷒뷓뷖뷗뷙뷚뷛뷝",11,"뷪",5,"뷱"],["9561","뷲뷳뷵뷶뷷뷹",6,"븁븂븄븆",5,"븎븏븑븒븓"],["9581","븕",6,"븞븠",35,"빆빇빉빊빋빍빏",4,"빖빘빜빝빞빟빢빣빥빦빧빩빫",4,"빲빶",4,"빾빿뺁뺂뺃뺅",6,"뺎뺒",5,"뺚",13,"뺩",14],["9641","뺸",23,"뻒뻓"],["9661","뻕뻖뻙",6,"뻡뻢뻦",5,"뻭",8],["9681","뻶",10,"뼂",5,"뼊",13,"뼚뼞",33,"뽂뽃뽅뽆뽇뽉",6,"뽒뽓뽔뽖",44],["9741","뾃",16,"뾕",8],["9761","뾞",17,"뾱",7],["9781","뾹",11,"뿆",5,"뿎뿏뿑뿒뿓뿕",6,"뿝뿞뿠뿢",89,"쀽쀾쀿"],["9841","쁀",16,"쁒",5,"쁙쁚쁛"],["9861","쁝쁞쁟쁡",6,"쁪",15],["9881","쁺",21,"삒삓삕삖삗삙",6,"삢삤삦",5,"삮삱삲삷",4,"삾샂샃샄샆샇샊샋샍샎샏샑",6,"샚샞",5,"샦샧샩샪샫샭",6,"샶샸샺",5,"섁섂섃섅섆섇섉",6,"섑섒섓섔섖",5,"섡섢섥섨섩섪섫섮"],["9941","섲섳섴섵섷섺섻섽섾섿셁",6,"셊셎",5,"셖셗"],["9961","셙셚셛셝",6,"셦셪",5,"셱셲셳셵셶셷셹셺셻"],["9981","셼",8,"솆",5,"솏솑솒솓솕솗",4,"솞솠솢솣솤솦솧솪솫솭솮솯솱",11,"솾",5,"쇅쇆쇇쇉쇊쇋쇍",6,"쇕쇖쇙",6,"쇡쇢쇣쇥쇦쇧쇩",6,"쇲쇴",7,"쇾쇿숁숂숃숅",6,"숎숐숒",5,"숚숛숝숞숡숢숣"],["9a41","숤숥숦숧숪숬숮숰숳숵",16],["9a61","쉆쉇쉉",6,"쉒쉓쉕쉖쉗쉙",6,"쉡쉢쉣쉤쉦"],["9a81","쉧",4,"쉮쉯쉱쉲쉳쉵",6,"쉾슀슂",5,"슊",5,"슑",6,"슙슚슜슞",5,"슦슧슩슪슫슮",5,"슶슸슺",33,"싞싟싡싢싥",5,"싮싰싲싳싴싵싷싺싽싾싿쌁",6,"쌊쌋쌎쌏"],["9b41","쌐쌑쌒쌖쌗쌙쌚쌛쌝",6,"쌦쌧쌪",8],["9b61","쌳",17,"썆",7],["9b81","썎",25,"썪썫썭썮썯썱썳",4,"썺썻썾",5,"쎅쎆쎇쎉쎊쎋쎍",50,"쏁",22,"쏚"],["9c41","쏛쏝쏞쏡쏣",4,"쏪쏫쏬쏮",5,"쏶쏷쏹",5],["9c61","쏿",8,"쐉",6,"쐑",9],["9c81","쐛",8,"쐥",6,"쐭쐮쐯쐱쐲쐳쐵",6,"쐾",9,"쑉",26,"쑦쑧쑩쑪쑫쑭",6,"쑶쑷쑸쑺",5,"쒁",18,"쒕",6,"쒝",12],["9d41","쒪",13,"쒹쒺쒻쒽",8],["9d61","쓆",25],["9d81","쓠",8,"쓪",5,"쓲쓳쓵쓶쓷쓹쓻쓼쓽쓾씂",9,"씍씎씏씑씒씓씕",6,"씝",10,"씪씫씭씮씯씱",6,"씺씼씾",5,"앆앇앋앏앐앑앒앖앚앛앜앟앢앣앥앦앧앩",6,"앲앶",5,"앾앿얁얂얃얅얆얈얉얊얋얎얐얒얓얔"],["9e41","얖얙얚얛얝얞얟얡",7,"얪",9,"얶"],["9e61","얷얺얿",4,"엋엍엏엒엓엕엖엗엙",6,"엢엤엦엧"],["9e81","엨엩엪엫엯엱엲엳엵엸엹엺엻옂옃옄옉옊옋옍옎옏옑",6,"옚옝",6,"옦옧옩옪옫옯옱옲옶옸옺옼옽옾옿왂왃왅왆왇왉",6,"왒왖",5,"왞왟왡",10,"왭왮왰왲",5,"왺왻왽왾왿욁",6,"욊욌욎",5,"욖욗욙욚욛욝",6,"욦"],["9f41","욨욪",5,"욲욳욵욶욷욻",4,"웂웄웆",5,"웎"],["9f61","웏웑웒웓웕",6,"웞웟웢",5,"웪웫웭웮웯웱웲"],["9f81","웳",4,"웺웻웼웾",5,"윆윇윉윊윋윍",6,"윖윘윚",5,"윢윣윥윦윧윩",6,"윲윴윶윸윹윺윻윾윿읁읂읃읅",4,"읋읎읐읙읚읛읝읞읟읡",6,"읩읪읬",7,"읶읷읹읺읻읿잀잁잂잆잋잌잍잏잒잓잕잙잛",4,"잢잧",4,"잮잯잱잲잳잵잶잷"],["a041","잸잹잺잻잾쟂",5,"쟊쟋쟍쟏쟑",6,"쟙쟚쟛쟜"],["a061","쟞",5,"쟥쟦쟧쟩쟪쟫쟭",13],["a081","쟻",4,"젂젃젅젆젇젉젋",4,"젒젔젗",4,"젞젟젡젢젣젥",6,"젮젰젲",5,"젹젺젻젽젾젿졁",6,"졊졋졎",5,"졕",26,"졲졳졵졶졷졹졻",4,"좂좄좈좉좊좎",5,"좕",7,"좞좠좢좣좤"],["a141","좥좦좧좩",18,"좾좿죀죁"],["a161","죂죃죅죆죇죉죊죋죍",6,"죖죘죚",5,"죢죣죥"],["a181","죦",14,"죶",5,"죾죿줁줂줃줇",4,"줎 、。·‥…¨〃­―∥\∼‘’“”〔〕〈",9,"±×÷≠≤≥∞∴°′″℃Å¢£¥♂♀∠⊥⌒∂∇≡≒§※☆★○●◎◇◆□■△▲▽▼→←↑↓↔〓≪≫√∽∝∵∫∬∈∋⊆⊇⊂⊃∪∩∧∨¬"],["a241","줐줒",5,"줙",18],["a261","줭",6,"줵",18],["a281","쥈",7,"쥒쥓쥕쥖쥗쥙",6,"쥢쥤",7,"쥭쥮쥯⇒⇔∀∃´~ˇ˘˝˚˙¸˛¡¿ː∮∑∏¤℉‰◁◀▷▶♤♠♡♥♧♣⊙◈▣◐◑▒▤▥▨▧▦▩♨☏☎☜☞¶†‡↕↗↙↖↘♭♩♪♬㉿㈜№㏇™㏂㏘℡€®"],["a341","쥱쥲쥳쥵",6,"쥽",10,"즊즋즍즎즏"],["a361","즑",6,"즚즜즞",16],["a381","즯",16,"짂짃짅짆짉짋",4,"짒짔짗짘짛!",58,"₩]",32," ̄"],["a441","짞짟짡짣짥짦짨짩짪짫짮짲",5,"짺짻짽짾짿쨁쨂쨃쨄"],["a461","쨅쨆쨇쨊쨎",5,"쨕쨖쨗쨙",12],["a481","쨦쨧쨨쨪",28,"ㄱ",93],["a541","쩇",4,"쩎쩏쩑쩒쩓쩕",6,"쩞쩢",5,"쩩쩪"],["a561","쩫",17,"쩾",5,"쪅쪆"],["a581","쪇",16,"쪙",14,"ⅰ",9],["a5b0","Ⅰ",9],["a5c1","Α",16,"Σ",6],["a5e1","α",16,"σ",6],["a641","쪨",19,"쪾쪿쫁쫂쫃쫅"],["a661","쫆",5,"쫎쫐쫒쫔쫕쫖쫗쫚",5,"쫡",6],["a681","쫨쫩쫪쫫쫭",6,"쫵",18,"쬉쬊─│┌┐┘└├┬┤┴┼━┃┏┓┛┗┣┳┫┻╋┠┯┨┷┿┝┰┥┸╂┒┑┚┙┖┕┎┍┞┟┡┢┦┧┩┪┭┮┱┲┵┶┹┺┽┾╀╁╃",7],["a741","쬋",4,"쬑쬒쬓쬕쬖쬗쬙",6,"쬢",7],["a761","쬪",22,"쭂쭃쭄"],["a781","쭅쭆쭇쭊쭋쭍쭎쭏쭑",6,"쭚쭛쭜쭞",5,"쭥",7,"㎕㎖㎗ℓ㎘㏄㎣㎤㎥㎦㎙",9,"㏊㎍㎎㎏㏏㎈㎉㏈㎧㎨㎰",9,"㎀",4,"㎺",5,"㎐",4,"Ω㏀㏁㎊㎋㎌㏖㏅㎭㎮㎯㏛㎩㎪㎫㎬㏝㏐㏓㏃㏉㏜㏆"],["a841","쭭",10,"쭺",14],["a861","쮉",18,"쮝",6],["a881","쮤",19,"쮹",11,"ÆЪĦ"],["a8a6","IJ"],["a8a8","ĿŁØŒºÞŦŊ"],["a8b1","㉠",27,"ⓐ",25,"①",14,"½⅓⅔¼¾⅛⅜⅝⅞"],["a941","쯅",14,"쯕",10],["a961","쯠쯡쯢쯣쯥쯦쯨쯪",18],["a981","쯽",14,"찎찏찑찒찓찕",6,"찞찟찠찣찤æđðħıijĸŀłøœßþŧŋʼn㈀",27,"⒜",25,"⑴",14,"¹²³⁴ⁿ₁₂₃₄"],["aa41","찥찦찪찫찭찯찱",6,"찺찿",4,"챆챇챉챊챋챍챎"],["aa61","챏",4,"챖챚",5,"챡챢챣챥챧챩",6,"챱챲"],["aa81","챳챴챶",29,"ぁ",82],["ab41","첔첕첖첗첚첛첝첞첟첡",6,"첪첮",5,"첶첷첹"],["ab61","첺첻첽",6,"쳆쳈쳊",5,"쳑쳒쳓쳕",5],["ab81","쳛",8,"쳥",6,"쳭쳮쳯쳱",12,"ァ",85],["ac41","쳾쳿촀촂",5,"촊촋촍촎촏촑",6,"촚촜촞촟촠"],["ac61","촡촢촣촥촦촧촩촪촫촭",11,"촺",4],["ac81","촿",28,"쵝쵞쵟А",5,"ЁЖ",25],["acd1","а",5,"ёж",25],["ad41","쵡쵢쵣쵥",6,"쵮쵰쵲",5,"쵹",7],["ad61","춁",6,"춉",10,"춖춗춙춚춛춝춞춟"],["ad81","춠춡춢춣춦춨춪",5,"춱",18,"췅"],["ae41","췆",5,"췍췎췏췑",16],["ae61","췢",5,"췩췪췫췭췮췯췱",6,"췺췼췾",4],["ae81","츃츅츆츇츉츊츋츍",6,"츕츖츗츘츚",5,"츢츣츥츦츧츩츪츫"],["af41","츬츭츮츯츲츴츶",19],["af61","칊",13,"칚칛칝칞칢",5,"칪칬"],["af81","칮",5,"칶칷칹칺칻칽",6,"캆캈캊",5,"캒캓캕캖캗캙"],["b041","캚",5,"캢캦",5,"캮",12],["b061","캻",5,"컂",19],["b081","컖",13,"컦컧컩컪컭",6,"컶컺",5,"가각간갇갈갉갊감",7,"같",4,"갠갤갬갭갯갰갱갸갹갼걀걋걍걔걘걜거걱건걷걸걺검겁것겄겅겆겉겊겋게겐겔겜겝겟겠겡겨격겪견겯결겸겹겻겼경곁계곈곌곕곗고곡곤곧골곪곬곯곰곱곳공곶과곽관괄괆"],["b141","켂켃켅켆켇켉",6,"켒켔켖",5,"켝켞켟켡켢켣"],["b161","켥",6,"켮켲",5,"켹",11],["b181","콅",14,"콖콗콙콚콛콝",6,"콦콨콪콫콬괌괍괏광괘괜괠괩괬괭괴괵괸괼굄굅굇굉교굔굘굡굣구국군굳굴굵굶굻굼굽굿궁궂궈궉권궐궜궝궤궷귀귁귄귈귐귑귓규균귤그극근귿글긁금급긋긍긔기긱긴긷길긺김깁깃깅깆깊까깍깎깐깔깖깜깝깟깠깡깥깨깩깬깰깸"],["b241","콭콮콯콲콳콵콶콷콹",6,"쾁쾂쾃쾄쾆",5,"쾍"],["b261","쾎",18,"쾢",5,"쾩"],["b281","쾪",5,"쾱",18,"쿅",6,"깹깻깼깽꺄꺅꺌꺼꺽꺾껀껄껌껍껏껐껑께껙껜껨껫껭껴껸껼꼇꼈꼍꼐꼬꼭꼰꼲꼴꼼꼽꼿꽁꽂꽃꽈꽉꽐꽜꽝꽤꽥꽹꾀꾄꾈꾐꾑꾕꾜꾸꾹꾼꿀꿇꿈꿉꿋꿍꿎꿔꿜꿨꿩꿰꿱꿴꿸뀀뀁뀄뀌뀐뀔뀜뀝뀨끄끅끈끊끌끎끓끔끕끗끙"],["b341","쿌",19,"쿢쿣쿥쿦쿧쿩"],["b361","쿪",5,"쿲쿴쿶",5,"쿽쿾쿿퀁퀂퀃퀅",5],["b381","퀋",5,"퀒",5,"퀙",19,"끝끼끽낀낄낌낍낏낑나낙낚난낟날낡낢남납낫",4,"낱낳내낵낸낼냄냅냇냈냉냐냑냔냘냠냥너넉넋넌널넒넓넘넙넛넜넝넣네넥넨넬넴넵넷넸넹녀녁년녈념녑녔녕녘녜녠노녹논놀놂놈놉놋농높놓놔놘놜놨뇌뇐뇔뇜뇝"],["b441","퀮",5,"퀶퀷퀹퀺퀻퀽",6,"큆큈큊",5],["b461","큑큒큓큕큖큗큙",6,"큡",10,"큮큯"],["b481","큱큲큳큵",6,"큾큿킀킂",18,"뇟뇨뇩뇬뇰뇹뇻뇽누눅눈눋눌눔눕눗눙눠눴눼뉘뉜뉠뉨뉩뉴뉵뉼늄늅늉느늑는늘늙늚늠늡늣능늦늪늬늰늴니닉닌닐닒님닙닛닝닢다닥닦단닫",4,"닳담답닷",4,"닿대댁댄댈댐댑댓댔댕댜더덕덖던덛덜덞덟덤덥"],["b541","킕",14,"킦킧킩킪킫킭",5],["b561","킳킶킸킺",5,"탂탃탅탆탇탊",5,"탒탖",4],["b581","탛탞탟탡탢탣탥",6,"탮탲",5,"탹",11,"덧덩덫덮데덱덴델뎀뎁뎃뎄뎅뎌뎐뎔뎠뎡뎨뎬도독돈돋돌돎돐돔돕돗동돛돝돠돤돨돼됐되된될됨됩됫됴두둑둔둘둠둡둣둥둬뒀뒈뒝뒤뒨뒬뒵뒷뒹듀듄듈듐듕드득든듣들듦듬듭듯등듸디딕딘딛딜딤딥딧딨딩딪따딱딴딸"],["b641","턅",7,"턎",17],["b661","턠",15,"턲턳턵턶턷턹턻턼턽턾"],["b681","턿텂텆",5,"텎텏텑텒텓텕",6,"텞텠텢",5,"텩텪텫텭땀땁땃땄땅땋때땍땐땔땜땝땟땠땡떠떡떤떨떪떫떰떱떳떴떵떻떼떽뗀뗄뗌뗍뗏뗐뗑뗘뗬또똑똔똘똥똬똴뙈뙤뙨뚜뚝뚠뚤뚫뚬뚱뛔뛰뛴뛸뜀뜁뜅뜨뜩뜬뜯뜰뜸뜹뜻띄띈띌띔띕띠띤띨띰띱띳띵라락란랄람랍랏랐랑랒랖랗"],["b741","텮",13,"텽",6,"톅톆톇톉톊"],["b761","톋",20,"톢톣톥톦톧"],["b781","톩",6,"톲톴톶톷톸톹톻톽톾톿퇁",14,"래랙랜랠램랩랫랬랭랴략랸럇량러럭런럴럼럽럿렀렁렇레렉렌렐렘렙렛렝려력련렬렴렵렷렸령례롄롑롓로록론롤롬롭롯롱롸롼뢍뢨뢰뢴뢸룀룁룃룅료룐룔룝룟룡루룩룬룰룸룹룻룽뤄뤘뤠뤼뤽륀륄륌륏륑류륙륜률륨륩"],["b841","퇐",7,"퇙",17],["b861","퇫",8,"퇵퇶퇷퇹",13],["b881","툈툊",5,"툑",24,"륫륭르륵른를름릅릇릉릊릍릎리릭린릴림립릿링마막만많",4,"맘맙맛망맞맡맣매맥맨맬맴맵맷맸맹맺먀먁먈먕머먹먼멀멂멈멉멋멍멎멓메멕멘멜멤멥멧멨멩며멱면멸몃몄명몇몌모목몫몬몰몲몸몹못몽뫄뫈뫘뫙뫼"],["b941","툪툫툮툯툱툲툳툵",6,"툾퉀퉂",5,"퉉퉊퉋퉌"],["b961","퉍",14,"퉝",6,"퉥퉦퉧퉨"],["b981","퉩",22,"튂튃튅튆튇튉튊튋튌묀묄묍묏묑묘묜묠묩묫무묵묶문묻물묽묾뭄뭅뭇뭉뭍뭏뭐뭔뭘뭡뭣뭬뮈뮌뮐뮤뮨뮬뮴뮷므믄믈믐믓미믹민믿밀밂밈밉밋밌밍및밑바",4,"받",4,"밤밥밧방밭배백밴밸뱀뱁뱃뱄뱅뱉뱌뱍뱐뱝버벅번벋벌벎범법벗"],["ba41","튍튎튏튒튓튔튖",5,"튝튞튟튡튢튣튥",6,"튭"],["ba61","튮튯튰튲",5,"튺튻튽튾틁틃",4,"틊틌",5],["ba81","틒틓틕틖틗틙틚틛틝",6,"틦",9,"틲틳틵틶틷틹틺벙벚베벡벤벧벨벰벱벳벴벵벼벽변별볍볏볐병볕볘볜보복볶본볼봄봅봇봉봐봔봤봬뵀뵈뵉뵌뵐뵘뵙뵤뵨부북분붇불붉붊붐붑붓붕붙붚붜붤붰붸뷔뷕뷘뷜뷩뷰뷴뷸븀븃븅브븍븐블븜븝븟비빅빈빌빎빔빕빗빙빚빛빠빡빤"],["bb41","틻",4,"팂팄팆",5,"팏팑팒팓팕팗",4,"팞팢팣"],["bb61","팤팦팧팪팫팭팮팯팱",6,"팺팾",5,"퍆퍇퍈퍉"],["bb81","퍊",31,"빨빪빰빱빳빴빵빻빼빽뺀뺄뺌뺍뺏뺐뺑뺘뺙뺨뻐뻑뻔뻗뻘뻠뻣뻤뻥뻬뼁뼈뼉뼘뼙뼛뼜뼝뽀뽁뽄뽈뽐뽑뽕뾔뾰뿅뿌뿍뿐뿔뿜뿟뿡쀼쁑쁘쁜쁠쁨쁩삐삑삔삘삠삡삣삥사삭삯산삳살삵삶삼삽삿샀상샅새색샌샐샘샙샛샜생샤"],["bc41","퍪",17,"퍾퍿펁펂펃펅펆펇"],["bc61","펈펉펊펋펎펒",5,"펚펛펝펞펟펡",6,"펪펬펮"],["bc81","펯",4,"펵펶펷펹펺펻펽",6,"폆폇폊",5,"폑",5,"샥샨샬샴샵샷샹섀섄섈섐섕서",4,"섣설섦섧섬섭섯섰성섶세섹센셀셈셉셋셌셍셔셕션셜셤셥셧셨셩셰셴셸솅소속솎손솔솖솜솝솟송솥솨솩솬솰솽쇄쇈쇌쇔쇗쇘쇠쇤쇨쇰쇱쇳쇼쇽숀숄숌숍숏숑수숙순숟술숨숩숫숭"],["bd41","폗폙",7,"폢폤",7,"폮폯폱폲폳폵폶폷"],["bd61","폸폹폺폻폾퐀퐂",5,"퐉",13],["bd81","퐗",5,"퐞",25,"숯숱숲숴쉈쉐쉑쉔쉘쉠쉥쉬쉭쉰쉴쉼쉽쉿슁슈슉슐슘슛슝스슥슨슬슭슴습슷승시식신싣실싫심십싯싱싶싸싹싻싼쌀쌈쌉쌌쌍쌓쌔쌕쌘쌜쌤쌥쌨쌩썅써썩썬썰썲썸썹썼썽쎄쎈쎌쏀쏘쏙쏜쏟쏠쏢쏨쏩쏭쏴쏵쏸쐈쐐쐤쐬쐰"],["be41","퐸",7,"푁푂푃푅",14],["be61","푔",7,"푝푞푟푡푢푣푥",7,"푮푰푱푲"],["be81","푳",4,"푺푻푽푾풁풃",4,"풊풌풎",5,"풕",8,"쐴쐼쐽쑈쑤쑥쑨쑬쑴쑵쑹쒀쒔쒜쒸쒼쓩쓰쓱쓴쓸쓺쓿씀씁씌씐씔씜씨씩씬씰씸씹씻씽아악안앉않알앍앎앓암압앗았앙앝앞애액앤앨앰앱앳앴앵야약얀얄얇얌얍얏양얕얗얘얜얠얩어억언얹얻얼얽얾엄",6,"엌엎"],["bf41","풞",10,"풪",14],["bf61","풹",18,"퓍퓎퓏퓑퓒퓓퓕"],["bf81","퓖",5,"퓝퓞퓠",7,"퓩퓪퓫퓭퓮퓯퓱",6,"퓹퓺퓼에엑엔엘엠엡엣엥여역엮연열엶엷염",5,"옅옆옇예옌옐옘옙옛옜오옥온올옭옮옰옳옴옵옷옹옻와왁완왈왐왑왓왔왕왜왝왠왬왯왱외왹왼욀욈욉욋욍요욕욘욜욤욥욧용우욱운울욹욺움웁웃웅워웍원월웜웝웠웡웨"],["c041","퓾",5,"픅픆픇픉픊픋픍",6,"픖픘",5],["c061","픞",25],["c081","픸픹픺픻픾픿핁핂핃핅",6,"핎핐핒",5,"핚핛핝핞핟핡핢핣웩웬웰웸웹웽위윅윈윌윔윕윗윙유육윤율윰윱윳융윷으윽은을읊음읍읏응",7,"읜읠읨읫이익인일읽읾잃임입잇있잉잊잎자작잔잖잗잘잚잠잡잣잤장잦재잭잰잴잼잽잿쟀쟁쟈쟉쟌쟎쟐쟘쟝쟤쟨쟬저적전절젊"],["c141","핤핦핧핪핬핮",5,"핶핷핹핺핻핽",6,"햆햊햋"],["c161","햌햍햎햏햑",19,"햦햧"],["c181","햨",31,"점접젓정젖제젝젠젤젬젭젯젱져젼졀졈졉졌졍졔조족존졸졺좀좁좃종좆좇좋좌좍좔좝좟좡좨좼좽죄죈죌죔죕죗죙죠죡죤죵주죽준줄줅줆줌줍줏중줘줬줴쥐쥑쥔쥘쥠쥡쥣쥬쥰쥴쥼즈즉즌즐즘즙즛증지직진짇질짊짐집짓"],["c241","헊헋헍헎헏헑헓",4,"헚헜헞",5,"헦헧헩헪헫헭헮"],["c261","헯",4,"헶헸헺",5,"혂혃혅혆혇혉",6,"혒"],["c281","혖",5,"혝혞혟혡혢혣혥",7,"혮",9,"혺혻징짖짙짚짜짝짠짢짤짧짬짭짯짰짱째짹짼쨀쨈쨉쨋쨌쨍쨔쨘쨩쩌쩍쩐쩔쩜쩝쩟쩠쩡쩨쩽쪄쪘쪼쪽쫀쫄쫌쫍쫏쫑쫓쫘쫙쫠쫬쫴쬈쬐쬔쬘쬠쬡쭁쭈쭉쭌쭐쭘쭙쭝쭤쭸쭹쮜쮸쯔쯤쯧쯩찌찍찐찔찜찝찡찢찧차착찬찮찰참찹찻"],["c341","혽혾혿홁홂홃홄홆홇홊홌홎홏홐홒홓홖홗홙홚홛홝",4],["c361","홢",4,"홨홪",5,"홲홳홵",11],["c381","횁횂횄횆",5,"횎횏횑횒횓횕",7,"횞횠횢",5,"횩횪찼창찾채책챈챌챔챕챗챘챙챠챤챦챨챰챵처척천철첨첩첫첬청체첵첸첼쳄쳅쳇쳉쳐쳔쳤쳬쳰촁초촉촌촐촘촙촛총촤촨촬촹최쵠쵤쵬쵭쵯쵱쵸춈추축춘출춤춥춧충춰췄췌췐취췬췰췸췹췻췽츄츈츌츔츙츠측츤츨츰츱츳층"],["c441","횫횭횮횯횱",7,"횺횼",7,"훆훇훉훊훋"],["c461","훍훎훏훐훒훓훕훖훘훚",5,"훡훢훣훥훦훧훩",4],["c481","훮훯훱훲훳훴훶",5,"훾훿휁휂휃휅",11,"휒휓휔치칙친칟칠칡침칩칫칭카칵칸칼캄캅캇캉캐캑캔캘캠캡캣캤캥캬캭컁커컥컨컫컬컴컵컷컸컹케켁켄켈켐켑켓켕켜켠켤켬켭켯켰켱켸코콕콘콜콤콥콧콩콰콱콴콸쾀쾅쾌쾡쾨쾰쿄쿠쿡쿤쿨쿰쿱쿳쿵쿼퀀퀄퀑퀘퀭퀴퀵퀸퀼"],["c541","휕휖휗휚휛휝휞휟휡",6,"휪휬휮",5,"휶휷휹"],["c561","휺휻휽",6,"흅흆흈흊",5,"흒흓흕흚",4],["c581","흟흢흤흦흧흨흪흫흭흮흯흱흲흳흵",6,"흾흿힀힂",5,"힊힋큄큅큇큉큐큔큘큠크큭큰클큼큽킁키킥킨킬킴킵킷킹타탁탄탈탉탐탑탓탔탕태택탠탤탬탭탯탰탱탸턍터턱턴털턺텀텁텃텄텅테텍텐텔템텝텟텡텨텬텼톄톈토톡톤톨톰톱톳통톺톼퇀퇘퇴퇸툇툉툐투툭툰툴툼툽툿퉁퉈퉜"],["c641","힍힎힏힑",6,"힚힜힞",5],["c6a1","퉤튀튁튄튈튐튑튕튜튠튤튬튱트특튼튿틀틂틈틉틋틔틘틜틤틥티틱틴틸팀팁팃팅파팍팎판팔팖팜팝팟팠팡팥패팩팬팰팸팹팻팼팽퍄퍅퍼퍽펀펄펌펍펏펐펑페펙펜펠펨펩펫펭펴편펼폄폅폈평폐폘폡폣포폭폰폴폼폽폿퐁"],["c7a1","퐈퐝푀푄표푠푤푭푯푸푹푼푿풀풂품풉풋풍풔풩퓌퓐퓔퓜퓟퓨퓬퓰퓸퓻퓽프픈플픔픕픗피픽핀필핌핍핏핑하학한할핥함합핫항해핵핸핼햄햅햇했행햐향허헉헌헐헒험헙헛헝헤헥헨헬헴헵헷헹혀혁현혈혐협혓혔형혜혠"],["c8a1","혤혭호혹혼홀홅홈홉홋홍홑화확환활홧황홰홱홴횃횅회획횐횔횝횟횡효횬횰횹횻후훅훈훌훑훔훗훙훠훤훨훰훵훼훽휀휄휑휘휙휜휠휨휩휫휭휴휵휸휼흄흇흉흐흑흔흖흗흘흙흠흡흣흥흩희흰흴흼흽힁히힉힌힐힘힙힛힝"],["caa1","伽佳假價加可呵哥嘉嫁家暇架枷柯歌珂痂稼苛茄街袈訶賈跏軻迦駕刻却各恪慤殼珏脚覺角閣侃刊墾奸姦干幹懇揀杆柬桿澗癎看磵稈竿簡肝艮艱諫間乫喝曷渴碣竭葛褐蝎鞨勘坎堪嵌感憾戡敢柑橄減甘疳監瞰紺邯鑑鑒龕"],["cba1","匣岬甲胛鉀閘剛堈姜岡崗康强彊慷江畺疆糠絳綱羌腔舡薑襁講鋼降鱇介价個凱塏愷愾慨改槪漑疥皆盖箇芥蓋豈鎧開喀客坑更粳羹醵倨去居巨拒据據擧渠炬祛距踞車遽鉅鋸乾件健巾建愆楗腱虔蹇鍵騫乞傑杰桀儉劍劒檢"],["cca1","瞼鈐黔劫怯迲偈憩揭擊格檄激膈覡隔堅牽犬甄絹繭肩見譴遣鵑抉決潔結缺訣兼慊箝謙鉗鎌京俓倞傾儆勁勍卿坰境庚徑慶憬擎敬景暻更梗涇炅烱璟璥瓊痙硬磬竟競絅經耕耿脛莖警輕逕鏡頃頸驚鯨係啓堺契季屆悸戒桂械"],["cda1","棨溪界癸磎稽系繫繼計誡谿階鷄古叩告呱固姑孤尻庫拷攷故敲暠枯槁沽痼皐睾稿羔考股膏苦苽菰藁蠱袴誥賈辜錮雇顧高鼓哭斛曲梏穀谷鵠困坤崑昆梱棍滾琨袞鯤汨滑骨供公共功孔工恐恭拱控攻珙空蚣貢鞏串寡戈果瓜"],["cea1","科菓誇課跨過鍋顆廓槨藿郭串冠官寬慣棺款灌琯瓘管罐菅觀貫關館刮恝括适侊光匡壙廣曠洸炚狂珖筐胱鑛卦掛罫乖傀塊壞怪愧拐槐魁宏紘肱轟交僑咬喬嬌嶠巧攪敎校橋狡皎矯絞翹膠蕎蛟較轎郊餃驕鮫丘久九仇俱具勾"],["cfa1","區口句咎嘔坵垢寇嶇廐懼拘救枸柩構歐毆毬求溝灸狗玖球瞿矩究絿耉臼舅舊苟衢謳購軀逑邱鉤銶駒驅鳩鷗龜國局菊鞠鞫麴君窘群裙軍郡堀屈掘窟宮弓穹窮芎躬倦券勸卷圈拳捲權淃眷厥獗蕨蹶闕机櫃潰詭軌饋句晷歸貴"],["d0a1","鬼龜叫圭奎揆槻珪硅窺竅糾葵規赳逵閨勻均畇筠菌鈞龜橘克剋劇戟棘極隙僅劤勤懃斤根槿瑾筋芹菫覲謹近饉契今妗擒昑檎琴禁禽芩衾衿襟金錦伋及急扱汲級給亘兢矜肯企伎其冀嗜器圻基埼夔奇妓寄岐崎己幾忌技旗旣"],["d1a1","朞期杞棋棄機欺氣汽沂淇玘琦琪璂璣畸畿碁磯祁祇祈祺箕紀綺羈耆耭肌記譏豈起錡錤飢饑騎騏驥麒緊佶吉拮桔金喫儺喇奈娜懦懶拏拿癩",5,"那樂",4,"諾酪駱亂卵暖欄煖爛蘭難鸞捏捺南嵐枏楠湳濫男藍襤拉"],["d2a1","納臘蠟衲囊娘廊",4,"乃來內奈柰耐冷女年撚秊念恬拈捻寧寗努勞奴弩怒擄櫓爐瑙盧",5,"駑魯",10,"濃籠聾膿農惱牢磊腦賂雷尿壘",7,"嫩訥杻紐勒",5,"能菱陵尼泥匿溺多茶"],["d3a1","丹亶但單團壇彖斷旦檀段湍短端簞緞蛋袒鄲鍛撻澾獺疸達啖坍憺擔曇淡湛潭澹痰聃膽蕁覃談譚錟沓畓答踏遝唐堂塘幢戇撞棠當糖螳黨代垈坮大對岱帶待戴擡玳臺袋貸隊黛宅德悳倒刀到圖堵塗導屠島嶋度徒悼挑掉搗桃"],["d4a1","棹櫂淘渡滔濤燾盜睹禱稻萄覩賭跳蹈逃途道都鍍陶韜毒瀆牘犢獨督禿篤纛讀墩惇敦旽暾沌焞燉豚頓乭突仝冬凍動同憧東桐棟洞潼疼瞳童胴董銅兜斗杜枓痘竇荳讀豆逗頭屯臀芚遁遯鈍得嶝橙燈登等藤謄鄧騰喇懶拏癩羅"],["d5a1","蘿螺裸邏樂洛烙珞絡落諾酪駱丹亂卵欄欒瀾爛蘭鸞剌辣嵐擥攬欖濫籃纜藍襤覽拉臘蠟廊朗浪狼琅瑯螂郞來崍徠萊冷掠略亮倆兩凉梁樑粮粱糧良諒輛量侶儷勵呂廬慮戾旅櫚濾礪藜蠣閭驢驪麗黎力曆歷瀝礫轢靂憐戀攣漣"],["d6a1","煉璉練聯蓮輦連鍊冽列劣洌烈裂廉斂殮濂簾獵令伶囹寧岺嶺怜玲笭羚翎聆逞鈴零靈領齡例澧禮醴隷勞怒撈擄櫓潞瀘爐盧老蘆虜路輅露魯鷺鹵碌祿綠菉錄鹿麓論壟弄朧瀧瓏籠聾儡瀨牢磊賂賚賴雷了僚寮廖料燎療瞭聊蓼"],["d7a1","遼鬧龍壘婁屢樓淚漏瘻累縷蔞褸鏤陋劉旒柳榴流溜瀏琉瑠留瘤硫謬類六戮陸侖倫崙淪綸輪律慄栗率隆勒肋凜凌楞稜綾菱陵俚利厘吏唎履悧李梨浬犁狸理璃異痢籬罹羸莉裏裡里釐離鯉吝潾燐璘藺躪隣鱗麟林淋琳臨霖砬"],["d8a1","立笠粒摩瑪痲碼磨馬魔麻寞幕漠膜莫邈万卍娩巒彎慢挽晩曼滿漫灣瞞萬蔓蠻輓饅鰻唜抹末沫茉襪靺亡妄忘忙望網罔芒茫莽輞邙埋妹媒寐昧枚梅每煤罵買賣邁魅脈貊陌驀麥孟氓猛盲盟萌冪覓免冕勉棉沔眄眠綿緬面麵滅"],["d9a1","蔑冥名命明暝椧溟皿瞑茗蓂螟酩銘鳴袂侮冒募姆帽慕摸摹暮某模母毛牟牡瑁眸矛耗芼茅謀謨貌木沐牧目睦穆鶩歿沒夢朦蒙卯墓妙廟描昴杳渺猫竗苗錨務巫憮懋戊拇撫无楙武毋無珷畝繆舞茂蕪誣貿霧鵡墨默們刎吻問文"],["daa1","汶紊紋聞蚊門雯勿沕物味媚尾嵋彌微未梶楣渼湄眉米美薇謎迷靡黴岷悶愍憫敏旻旼民泯玟珉緡閔密蜜謐剝博拍搏撲朴樸泊珀璞箔粕縛膊舶薄迫雹駁伴半反叛拌搬攀斑槃泮潘班畔瘢盤盼磐磻礬絆般蟠返頒飯勃拔撥渤潑"],["dba1","發跋醱鉢髮魃倣傍坊妨尨幇彷房放方旁昉枋榜滂磅紡肪膀舫芳蒡蚌訪謗邦防龐倍俳北培徘拜排杯湃焙盃背胚裴裵褙賠輩配陪伯佰帛柏栢白百魄幡樊煩燔番磻繁蕃藩飜伐筏罰閥凡帆梵氾汎泛犯範范法琺僻劈壁擘檗璧癖"],["dca1","碧蘗闢霹便卞弁變辨辯邊別瞥鱉鼈丙倂兵屛幷昞昺柄棅炳甁病秉竝輧餠騈保堡報寶普步洑湺潽珤甫菩補褓譜輔伏僕匐卜宓復服福腹茯蔔複覆輹輻馥鰒本乶俸奉封峯峰捧棒烽熢琫縫蓬蜂逢鋒鳳不付俯傅剖副否咐埠夫婦"],["dda1","孚孵富府復扶敷斧浮溥父符簿缶腐腑膚艀芙莩訃負賦賻赴趺部釜阜附駙鳧北分吩噴墳奔奮忿憤扮昐汾焚盆粉糞紛芬賁雰不佛弗彿拂崩朋棚硼繃鵬丕備匕匪卑妃婢庇悲憊扉批斐枇榧比毖毗毘沸泌琵痺砒碑秕秘粃緋翡肥"],["dea1","脾臂菲蜚裨誹譬費鄙非飛鼻嚬嬪彬斌檳殯浜濱瀕牝玭貧賓頻憑氷聘騁乍事些仕伺似使俟僿史司唆嗣四士奢娑寫寺射巳師徙思捨斜斯柶査梭死沙泗渣瀉獅砂社祀祠私篩紗絲肆舍莎蓑蛇裟詐詞謝賜赦辭邪飼駟麝削數朔索"],["dfa1","傘刪山散汕珊産疝算蒜酸霰乷撒殺煞薩三參杉森渗芟蔘衫揷澁鈒颯上傷像償商喪嘗孀尙峠常床庠廂想桑橡湘爽牀狀相祥箱翔裳觴詳象賞霜塞璽賽嗇塞穡索色牲生甥省笙墅壻嶼序庶徐恕抒捿敍暑曙書栖棲犀瑞筮絮緖署"],["e0a1","胥舒薯西誓逝鋤黍鼠夕奭席惜昔晳析汐淅潟石碩蓆釋錫仙僊先善嬋宣扇敾旋渲煽琁瑄璇璿癬禪線繕羨腺膳船蘚蟬詵跣選銑鐥饍鮮卨屑楔泄洩渫舌薛褻設說雪齧剡暹殲纖蟾贍閃陝攝涉燮葉城姓宬性惺成星晟猩珹盛省筬"],["e1a1","聖聲腥誠醒世勢歲洗稅笹細說貰召嘯塑宵小少巢所掃搔昭梳沼消溯瀟炤燒甦疏疎瘙笑篠簫素紹蔬蕭蘇訴逍遡邵銷韶騷俗屬束涑粟續謖贖速孫巽損蓀遜飡率宋悚松淞訟誦送頌刷殺灑碎鎖衰釗修受嗽囚垂壽嫂守岫峀帥愁"],["e2a1","戍手授搜收數樹殊水洙漱燧狩獸琇璲瘦睡秀穗竪粹綏綬繡羞脩茱蒐蓚藪袖誰讐輸遂邃酬銖銹隋隧隨雖需須首髓鬚叔塾夙孰宿淑潚熟琡璹肅菽巡徇循恂旬栒楯橓殉洵淳珣盾瞬筍純脣舜荀蓴蕣詢諄醇錞順馴戌術述鉥崇崧"],["e3a1","嵩瑟膝蝨濕拾習褶襲丞乘僧勝升承昇繩蠅陞侍匙嘶始媤尸屎屍市弑恃施是時枾柴猜矢示翅蒔蓍視試詩諡豕豺埴寔式息拭植殖湜熄篒蝕識軾食飾伸侁信呻娠宸愼新晨燼申神紳腎臣莘薪藎蜃訊身辛辰迅失室實悉審尋心沁"],["e4a1","沈深瀋甚芯諶什十拾雙氏亞俄兒啞娥峨我牙芽莪蛾衙訝阿雅餓鴉鵝堊岳嶽幄惡愕握樂渥鄂鍔顎鰐齷安岸按晏案眼雁鞍顔鮟斡謁軋閼唵岩巖庵暗癌菴闇壓押狎鴨仰央怏昻殃秧鴦厓哀埃崖愛曖涯碍艾隘靄厄扼掖液縊腋額"],["e5a1","櫻罌鶯鸚也倻冶夜惹揶椰爺耶若野弱掠略約若葯蒻藥躍亮佯兩凉壤孃恙揚攘敭暘梁楊樣洋瀁煬痒瘍禳穰糧羊良襄諒讓釀陽量養圄御於漁瘀禦語馭魚齬億憶抑檍臆偃堰彦焉言諺孼蘖俺儼嚴奄掩淹嶪業円予余勵呂女如廬"],["e6a1","旅歟汝濾璵礖礪與艅茹輿轝閭餘驪麗黎亦力域役易曆歷疫繹譯轢逆驛嚥堧姸娟宴年延憐戀捐挻撚椽沇沿涎涓淵演漣烟然煙煉燃燕璉硏硯秊筵緣練縯聯衍軟輦蓮連鉛鍊鳶列劣咽悅涅烈熱裂說閱厭廉念捻染殮炎焰琰艶苒"],["e7a1","簾閻髥鹽曄獵燁葉令囹塋寧嶺嶸影怜映暎楹榮永泳渶潁濚瀛瀯煐營獰玲瑛瑩瓔盈穎纓羚聆英詠迎鈴鍈零霙靈領乂倪例刈叡曳汭濊猊睿穢芮藝蘂禮裔詣譽豫醴銳隸霓預五伍俉傲午吾吳嗚塢墺奧娛寤悟惡懊敖旿晤梧汚澳"],["e8a1","烏熬獒筽蜈誤鰲鼇屋沃獄玉鈺溫瑥瘟穩縕蘊兀壅擁瓮甕癰翁邕雍饔渦瓦窩窪臥蛙蝸訛婉完宛梡椀浣玩琓琬碗緩翫脘腕莞豌阮頑曰往旺枉汪王倭娃歪矮外嵬巍猥畏了僚僥凹堯夭妖姚寥寮尿嶢拗搖撓擾料曜樂橈燎燿瑤療"],["e9a1","窈窯繇繞耀腰蓼蟯要謠遙遼邀饒慾欲浴縟褥辱俑傭冗勇埇墉容庸慂榕涌湧溶熔瑢用甬聳茸蓉踊鎔鏞龍于佑偶優又友右宇寓尤愚憂旴牛玗瑀盂祐禑禹紆羽芋藕虞迂遇郵釪隅雨雩勖彧旭昱栯煜稶郁頊云暈橒殞澐熉耘芸蕓"],["eaa1","運隕雲韻蔚鬱亐熊雄元原員圓園垣媛嫄寃怨愿援沅洹湲源爰猿瑗苑袁轅遠阮院願鴛月越鉞位偉僞危圍委威尉慰暐渭爲瑋緯胃萎葦蔿蝟衛褘謂違韋魏乳侑儒兪劉唯喩孺宥幼幽庾悠惟愈愉揄攸有杻柔柚柳楡楢油洧流游溜"],["eba1","濡猶猷琉瑜由留癒硫紐維臾萸裕誘諛諭踰蹂遊逾遺酉釉鍮類六堉戮毓肉育陸倫允奫尹崙淪潤玧胤贇輪鈗閏律慄栗率聿戎瀜絨融隆垠恩慇殷誾銀隱乙吟淫蔭陰音飮揖泣邑凝應膺鷹依倚儀宜意懿擬椅毅疑矣義艤薏蟻衣誼"],["eca1","議醫二以伊利吏夷姨履已弛彛怡易李梨泥爾珥理異痍痢移罹而耳肄苡荑裏裡貽貳邇里離飴餌匿溺瀷益翊翌翼謚人仁刃印吝咽因姻寅引忍湮燐璘絪茵藺蚓認隣靭靷鱗麟一佚佾壹日溢逸鎰馹任壬妊姙恁林淋稔臨荏賃入卄"],["eda1","立笠粒仍剩孕芿仔刺咨姉姿子字孜恣慈滋炙煮玆瓷疵磁紫者自茨蔗藉諮資雌作勺嚼斫昨灼炸爵綽芍酌雀鵲孱棧殘潺盞岑暫潛箴簪蠶雜丈仗匠場墻壯奬將帳庄張掌暲杖樟檣欌漿牆狀獐璋章粧腸臟臧莊葬蔣薔藏裝贓醬長"],["eea1","障再哉在宰才材栽梓渽滓災縡裁財載齋齎爭箏諍錚佇低儲咀姐底抵杵楮樗沮渚狙猪疽箸紵苧菹著藷詛貯躇這邸雎齟勣吊嫡寂摘敵滴狄炙的積笛籍績翟荻謫賊赤跡蹟迪迹適鏑佃佺傳全典前剪塡塼奠專展廛悛戰栓殿氈澱"],["efa1","煎琠田甸畑癲筌箋箭篆纏詮輾轉鈿銓錢鐫電顚顫餞切截折浙癤竊節絶占岾店漸点粘霑鮎點接摺蝶丁井亭停偵呈姃定幀庭廷征情挺政整旌晶晸柾楨檉正汀淀淨渟湞瀞炡玎珽町睛碇禎程穽精綎艇訂諪貞鄭酊釘鉦鋌錠霆靖"],["f0a1","靜頂鼎制劑啼堤帝弟悌提梯濟祭第臍薺製諸蹄醍除際霽題齊俎兆凋助嘲弔彫措操早晁曺曹朝條棗槽漕潮照燥爪璪眺祖祚租稠窕粗糟組繰肇藻蚤詔調趙躁造遭釣阻雕鳥族簇足鏃存尊卒拙猝倧宗從悰慫棕淙琮種終綜縱腫"],["f1a1","踪踵鍾鐘佐坐左座挫罪主住侏做姝胄呪周嗾奏宙州廚晝朱柱株注洲湊澍炷珠疇籌紂紬綢舟蛛註誅走躊輳週酎酒鑄駐竹粥俊儁准埈寯峻晙樽浚準濬焌畯竣蠢逡遵雋駿茁中仲衆重卽櫛楫汁葺增憎曾拯烝甑症繒蒸證贈之只"],["f2a1","咫地址志持指摯支旨智枝枳止池沚漬知砥祉祗紙肢脂至芝芷蜘誌識贄趾遲直稙稷織職唇嗔塵振搢晉晋桭榛殄津溱珍瑨璡畛疹盡眞瞋秦縉縝臻蔯袗診賑軫辰進鎭陣陳震侄叱姪嫉帙桎瓆疾秩窒膣蛭質跌迭斟朕什執潗緝輯"],["f3a1","鏶集徵懲澄且侘借叉嗟嵯差次此磋箚茶蹉車遮捉搾着窄錯鑿齪撰澯燦璨瓚竄簒纂粲纘讚贊鑽餐饌刹察擦札紮僭參塹慘慙懺斬站讒讖倉倡創唱娼廠彰愴敞昌昶暢槍滄漲猖瘡窓脹艙菖蒼債埰寀寨彩採砦綵菜蔡采釵冊柵策"],["f4a1","責凄妻悽處倜刺剔尺慽戚拓擲斥滌瘠脊蹠陟隻仟千喘天川擅泉淺玔穿舛薦賤踐遷釧闡阡韆凸哲喆徹撤澈綴輟轍鐵僉尖沾添甛瞻簽籤詹諂堞妾帖捷牒疊睫諜貼輒廳晴淸聽菁請靑鯖切剃替涕滯締諦逮遞體初剿哨憔抄招梢"],["f5a1","椒楚樵炒焦硝礁礎秒稍肖艸苕草蕉貂超酢醋醮促囑燭矗蜀觸寸忖村邨叢塚寵悤憁摠總聰蔥銃撮催崔最墜抽推椎楸樞湫皺秋芻萩諏趨追鄒酋醜錐錘鎚雛騶鰍丑畜祝竺筑築縮蓄蹙蹴軸逐春椿瑃出朮黜充忠沖蟲衝衷悴膵萃"],["f6a1","贅取吹嘴娶就炊翠聚脆臭趣醉驟鷲側仄厠惻測層侈値嗤峙幟恥梔治淄熾痔痴癡稚穉緇緻置致蚩輜雉馳齒則勅飭親七柒漆侵寢枕沈浸琛砧針鍼蟄秤稱快他咤唾墮妥惰打拖朶楕舵陀馱駝倬卓啄坼度托拓擢晫柝濁濯琢琸託"],["f7a1","鐸呑嘆坦彈憚歎灘炭綻誕奪脫探眈耽貪塔搭榻宕帑湯糖蕩兌台太怠態殆汰泰笞胎苔跆邰颱宅擇澤撑攄兎吐土討慟桶洞痛筒統通堆槌腿褪退頹偸套妬投透鬪慝特闖坡婆巴把播擺杷波派爬琶破罷芭跛頗判坂板版瓣販辦鈑"],["f8a1","阪八叭捌佩唄悖敗沛浿牌狽稗覇貝彭澎烹膨愎便偏扁片篇編翩遍鞭騙貶坪平枰萍評吠嬖幣廢弊斃肺蔽閉陛佈包匍匏咆哺圃布怖抛抱捕暴泡浦疱砲胞脯苞葡蒲袍褒逋鋪飽鮑幅暴曝瀑爆輻俵剽彪慓杓標漂瓢票表豹飇飄驃"],["f9a1","品稟楓諷豊風馮彼披疲皮被避陂匹弼必泌珌畢疋筆苾馝乏逼下何厦夏廈昰河瑕荷蝦賀遐霞鰕壑學虐謔鶴寒恨悍旱汗漢澣瀚罕翰閑閒限韓割轄函含咸啣喊檻涵緘艦銜陷鹹合哈盒蛤閤闔陜亢伉姮嫦巷恒抗杭桁沆港缸肛航"],["faa1","行降項亥偕咳垓奚孩害懈楷海瀣蟹解該諧邂駭骸劾核倖幸杏荇行享向嚮珦鄕響餉饗香噓墟虛許憲櫶獻軒歇險驗奕爀赫革俔峴弦懸晛泫炫玄玹現眩睍絃絢縣舷衒見賢鉉顯孑穴血頁嫌俠協夾峽挾浹狹脅脇莢鋏頰亨兄刑型"],["fba1","形泂滎瀅灐炯熒珩瑩荊螢衡逈邢鎣馨兮彗惠慧暳蕙蹊醯鞋乎互呼壕壺好岵弧戶扈昊晧毫浩淏湖滸澔濠濩灝狐琥瑚瓠皓祜糊縞胡芦葫蒿虎號蝴護豪鎬頀顥惑或酷婚昏混渾琿魂忽惚笏哄弘汞泓洪烘紅虹訌鴻化和嬅樺火畵"],["fca1","禍禾花華話譁貨靴廓擴攫確碻穫丸喚奐宦幻患換歡晥桓渙煥環紈還驩鰥活滑猾豁闊凰幌徨恍惶愰慌晃晄榥況湟滉潢煌璜皇篁簧荒蝗遑隍黃匯回廻徊恢悔懷晦會檜淮澮灰獪繪膾茴蛔誨賄劃獲宖橫鐄哮嚆孝效斅曉梟涍淆"],["fda1","爻肴酵驍侯候厚后吼喉嗅帿後朽煦珝逅勛勳塤壎焄熏燻薰訓暈薨喧暄煊萱卉喙毁彙徽揮暉煇諱輝麾休携烋畦虧恤譎鷸兇凶匈洶胸黑昕欣炘痕吃屹紇訖欠欽歆吸恰洽翕興僖凞喜噫囍姬嬉希憙憘戱晞曦熙熹熺犧禧稀羲詰"]]'); + +/***/ }), + +/***/ 6517: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('[["0","\\u0000",127],["a140"," ,、。.‧;:?!︰…‥﹐﹑﹒·﹔﹕﹖﹗|–︱—︳╴︴﹏()︵︶{}︷︸〔〕︹︺【】︻︼《》︽︾〈〉︿﹀「」﹁﹂『』﹃﹄﹙﹚"],["a1a1","﹛﹜﹝﹞‘’“”〝〞‵′#&*※§〃○●△▲◎☆★◇◆□■▽▼㊣℅¯ ̄_ˍ﹉﹊﹍﹎﹋﹌﹟﹠﹡+-×÷±√<>=≦≧≠∞≒≡﹢",4,"~∩∪⊥∠∟⊿㏒㏑∫∮∵∴♀♂⊕⊙↑↓←→↖↗↙↘∥∣/"],["a240","\∕﹨$¥〒¢£%@℃℉﹩﹪﹫㏕㎜㎝㎞㏎㎡㎎㎏㏄°兙兛兞兝兡兣嗧瓩糎▁",7,"▏▎▍▌▋▊▉┼┴┬┤├▔─│▕┌┐└┘╭"],["a2a1","╮╰╯═╞╪╡◢◣◥◤╱╲╳0",9,"Ⅰ",9,"〡",8,"十卄卅A",25,"a",21],["a340","wxyzΑ",16,"Σ",6,"α",16,"σ",6,"ㄅ",10],["a3a1","ㄐ",25,"˙ˉˊˇˋ"],["a3e1","€"],["a440","一乙丁七乃九了二人儿入八几刀刁力匕十卜又三下丈上丫丸凡久么也乞于亡兀刃勺千叉口土士夕大女子孑孓寸小尢尸山川工己已巳巾干廾弋弓才"],["a4a1","丑丐不中丰丹之尹予云井互五亢仁什仃仆仇仍今介仄元允內六兮公冗凶分切刈勻勾勿化匹午升卅卞厄友及反壬天夫太夭孔少尤尺屯巴幻廿弔引心戈戶手扎支文斗斤方日曰月木欠止歹毋比毛氏水火爪父爻片牙牛犬王丙"],["a540","世丕且丘主乍乏乎以付仔仕他仗代令仙仞充兄冉冊冬凹出凸刊加功包匆北匝仟半卉卡占卯卮去可古右召叮叩叨叼司叵叫另只史叱台句叭叻四囚外"],["a5a1","央失奴奶孕它尼巨巧左市布平幼弁弘弗必戊打扔扒扑斥旦朮本未末札正母民氐永汁汀氾犯玄玉瓜瓦甘生用甩田由甲申疋白皮皿目矛矢石示禾穴立丞丟乒乓乩亙交亦亥仿伉伙伊伕伍伐休伏仲件任仰仳份企伋光兇兆先全"],["a640","共再冰列刑划刎刖劣匈匡匠印危吉吏同吊吐吁吋各向名合吃后吆吒因回囝圳地在圭圬圯圩夙多夷夸妄奸妃好她如妁字存宇守宅安寺尖屹州帆并年"],["a6a1","式弛忙忖戎戌戍成扣扛托收早旨旬旭曲曳有朽朴朱朵次此死氖汝汗汙江池汐汕污汛汍汎灰牟牝百竹米糸缶羊羽老考而耒耳聿肉肋肌臣自至臼舌舛舟艮色艾虫血行衣西阡串亨位住佇佗佞伴佛何估佐佑伽伺伸佃佔似但佣"],["a740","作你伯低伶余佝佈佚兌克免兵冶冷別判利刪刨劫助努劬匣即卵吝吭吞吾否呎吧呆呃吳呈呂君吩告吹吻吸吮吵吶吠吼呀吱含吟听囪困囤囫坊坑址坍"],["a7a1","均坎圾坐坏圻壯夾妝妒妨妞妣妙妖妍妤妓妊妥孝孜孚孛完宋宏尬局屁尿尾岐岑岔岌巫希序庇床廷弄弟彤形彷役忘忌志忍忱快忸忪戒我抄抗抖技扶抉扭把扼找批扳抒扯折扮投抓抑抆改攻攸旱更束李杏材村杜杖杞杉杆杠"],["a840","杓杗步每求汞沙沁沈沉沅沛汪決沐汰沌汨沖沒汽沃汲汾汴沆汶沍沔沘沂灶灼災灸牢牡牠狄狂玖甬甫男甸皂盯矣私秀禿究系罕肖肓肝肘肛肚育良芒"],["a8a1","芋芍見角言谷豆豕貝赤走足身車辛辰迂迆迅迄巡邑邢邪邦那酉釆里防阮阱阪阬並乖乳事些亞享京佯依侍佳使佬供例來侃佰併侈佩佻侖佾侏侑佺兔兒兕兩具其典冽函刻券刷刺到刮制剁劾劻卒協卓卑卦卷卸卹取叔受味呵"],["a940","咖呸咕咀呻呷咄咒咆呼咐呱呶和咚呢周咋命咎固垃坷坪坩坡坦坤坼夜奉奇奈奄奔妾妻委妹妮姑姆姐姍始姓姊妯妳姒姅孟孤季宗定官宜宙宛尚屈居"],["a9a1","屆岷岡岸岩岫岱岳帘帚帖帕帛帑幸庚店府底庖延弦弧弩往征彿彼忝忠忽念忿怏怔怯怵怖怪怕怡性怩怫怛或戕房戾所承拉拌拄抿拂抹拒招披拓拔拋拈抨抽押拐拙拇拍抵拚抱拘拖拗拆抬拎放斧於旺昔易昌昆昂明昀昏昕昊"],["aa40","昇服朋杭枋枕東果杳杷枇枝林杯杰板枉松析杵枚枓杼杪杲欣武歧歿氓氛泣注泳沱泌泥河沽沾沼波沫法泓沸泄油況沮泗泅泱沿治泡泛泊沬泯泜泖泠"],["aaa1","炕炎炒炊炙爬爭爸版牧物狀狎狙狗狐玩玨玟玫玥甽疝疙疚的盂盲直知矽社祀祁秉秈空穹竺糾罔羌羋者肺肥肢肱股肫肩肴肪肯臥臾舍芳芝芙芭芽芟芹花芬芥芯芸芣芰芾芷虎虱初表軋迎返近邵邸邱邶采金長門阜陀阿阻附"],["ab40","陂隹雨青非亟亭亮信侵侯便俠俑俏保促侶俘俟俊俗侮俐俄係俚俎俞侷兗冒冑冠剎剃削前剌剋則勇勉勃勁匍南卻厚叛咬哀咨哎哉咸咦咳哇哂咽咪品"],["aba1","哄哈咯咫咱咻咩咧咿囿垂型垠垣垢城垮垓奕契奏奎奐姜姘姿姣姨娃姥姪姚姦威姻孩宣宦室客宥封屎屏屍屋峙峒巷帝帥帟幽庠度建弈弭彥很待徊律徇後徉怒思怠急怎怨恍恰恨恢恆恃恬恫恪恤扁拜挖按拼拭持拮拽指拱拷"],["ac40","拯括拾拴挑挂政故斫施既春昭映昧是星昨昱昤曷柿染柱柔某柬架枯柵柩柯柄柑枴柚查枸柏柞柳枰柙柢柝柒歪殃殆段毒毗氟泉洋洲洪流津洌洱洞洗"],["aca1","活洽派洶洛泵洹洧洸洩洮洵洎洫炫為炳炬炯炭炸炮炤爰牲牯牴狩狠狡玷珊玻玲珍珀玳甚甭畏界畎畋疫疤疥疢疣癸皆皇皈盈盆盃盅省盹相眉看盾盼眇矜砂研砌砍祆祉祈祇禹禺科秒秋穿突竿竽籽紂紅紀紉紇約紆缸美羿耄"],["ad40","耐耍耑耶胖胥胚胃胄背胡胛胎胞胤胝致舢苧范茅苣苛苦茄若茂茉苒苗英茁苜苔苑苞苓苟苯茆虐虹虻虺衍衫要觔計訂訃貞負赴赳趴軍軌述迦迢迪迥"],["ada1","迭迫迤迨郊郎郁郃酋酊重閂限陋陌降面革韋韭音頁風飛食首香乘亳倌倍倣俯倦倥俸倩倖倆值借倚倒們俺倀倔倨俱倡個候倘俳修倭倪俾倫倉兼冤冥冢凍凌准凋剖剜剔剛剝匪卿原厝叟哨唐唁唷哼哥哲唆哺唔哩哭員唉哮哪"],["ae40","哦唧唇哽唏圃圄埂埔埋埃堉夏套奘奚娑娘娜娟娛娓姬娠娣娩娥娌娉孫屘宰害家宴宮宵容宸射屑展屐峭峽峻峪峨峰島崁峴差席師庫庭座弱徒徑徐恙"],["aea1","恣恥恐恕恭恩息悄悟悚悍悔悌悅悖扇拳挈拿捎挾振捕捂捆捏捉挺捐挽挪挫挨捍捌效敉料旁旅時晉晏晃晒晌晅晁書朔朕朗校核案框桓根桂桔栩梳栗桌桑栽柴桐桀格桃株桅栓栘桁殊殉殷氣氧氨氦氤泰浪涕消涇浦浸海浙涓"],["af40","浬涉浮浚浴浩涌涊浹涅浥涔烊烘烤烙烈烏爹特狼狹狽狸狷玆班琉珮珠珪珞畔畝畜畚留疾病症疲疳疽疼疹痂疸皋皰益盍盎眩真眠眨矩砰砧砸砝破砷"],["afa1","砥砭砠砟砲祕祐祠祟祖神祝祗祚秤秣秧租秦秩秘窄窈站笆笑粉紡紗紋紊素索純紐紕級紜納紙紛缺罟羔翅翁耆耘耕耙耗耽耿胱脂胰脅胭胴脆胸胳脈能脊胼胯臭臬舀舐航舫舨般芻茫荒荔荊茸荐草茵茴荏茲茹茶茗荀茱茨荃"],["b040","虔蚊蚪蚓蚤蚩蚌蚣蚜衰衷袁袂衽衹記訐討訌訕訊託訓訖訏訑豈豺豹財貢起躬軒軔軏辱送逆迷退迺迴逃追逅迸邕郡郝郢酒配酌釘針釗釜釙閃院陣陡"],["b0a1","陛陝除陘陞隻飢馬骨高鬥鬲鬼乾偺偽停假偃偌做偉健偶偎偕偵側偷偏倏偯偭兜冕凰剪副勒務勘動匐匏匙匿區匾參曼商啪啦啄啞啡啃啊唱啖問啕唯啤唸售啜唬啣唳啁啗圈國圉域堅堊堆埠埤基堂堵執培夠奢娶婁婉婦婪婀"],["b140","娼婢婚婆婊孰寇寅寄寂宿密尉專將屠屜屝崇崆崎崛崖崢崑崩崔崙崤崧崗巢常帶帳帷康庸庶庵庾張強彗彬彩彫得徙從徘御徠徜恿患悉悠您惋悴惦悽"],["b1a1","情悻悵惜悼惘惕惆惟悸惚惇戚戛扈掠控捲掖探接捷捧掘措捱掩掉掃掛捫推掄授掙採掬排掏掀捻捩捨捺敝敖救教敗啟敏敘敕敔斜斛斬族旋旌旎晝晚晤晨晦晞曹勗望梁梯梢梓梵桿桶梱梧梗械梃棄梭梆梅梔條梨梟梡梂欲殺"],["b240","毫毬氫涎涼淳淙液淡淌淤添淺清淇淋涯淑涮淞淹涸混淵淅淒渚涵淚淫淘淪深淮淨淆淄涪淬涿淦烹焉焊烽烯爽牽犁猜猛猖猓猙率琅琊球理現琍瓠瓶"],["b2a1","瓷甜產略畦畢異疏痔痕疵痊痍皎盔盒盛眷眾眼眶眸眺硫硃硎祥票祭移窒窕笠笨笛第符笙笞笮粒粗粕絆絃統紮紹紼絀細紳組累終紲紱缽羞羚翌翎習耜聊聆脯脖脣脫脩脰脤舂舵舷舶船莎莞莘荸莢莖莽莫莒莊莓莉莠荷荻荼"],["b340","莆莧處彪蛇蛀蚶蛄蚵蛆蛋蚱蚯蛉術袞袈被袒袖袍袋覓規訪訝訣訥許設訟訛訢豉豚販責貫貨貪貧赧赦趾趺軛軟這逍通逗連速逝逐逕逞造透逢逖逛途"],["b3a1","部郭都酗野釵釦釣釧釭釩閉陪陵陳陸陰陴陶陷陬雀雪雩章竟頂頃魚鳥鹵鹿麥麻傢傍傅備傑傀傖傘傚最凱割剴創剩勞勝勛博厥啻喀喧啼喊喝喘喂喜喪喔喇喋喃喳單喟唾喲喚喻喬喱啾喉喫喙圍堯堪場堤堰報堡堝堠壹壺奠"],["b440","婷媚婿媒媛媧孳孱寒富寓寐尊尋就嵌嵐崴嵇巽幅帽幀幃幾廊廁廂廄弼彭復循徨惑惡悲悶惠愜愣惺愕惰惻惴慨惱愎惶愉愀愒戟扉掣掌描揀揩揉揆揍"],["b4a1","插揣提握揖揭揮捶援揪換摒揚揹敞敦敢散斑斐斯普晰晴晶景暑智晾晷曾替期朝棺棕棠棘棗椅棟棵森棧棹棒棲棣棋棍植椒椎棉棚楮棻款欺欽殘殖殼毯氮氯氬港游湔渡渲湧湊渠渥渣減湛湘渤湖湮渭渦湯渴湍渺測湃渝渾滋"],["b540","溉渙湎湣湄湲湩湟焙焚焦焰無然煮焜牌犄犀猶猥猴猩琺琪琳琢琥琵琶琴琯琛琦琨甥甦畫番痢痛痣痙痘痞痠登發皖皓皴盜睏短硝硬硯稍稈程稅稀窘"],["b5a1","窗窖童竣等策筆筐筒答筍筋筏筑粟粥絞結絨絕紫絮絲絡給絢絰絳善翔翕耋聒肅腕腔腋腑腎脹腆脾腌腓腴舒舜菩萃菸萍菠菅萋菁華菱菴著萊菰萌菌菽菲菊萸萎萄菜萇菔菟虛蛟蛙蛭蛔蛛蛤蛐蛞街裁裂袱覃視註詠評詞証詁"],["b640","詔詛詐詆訴診訶詖象貂貯貼貳貽賁費賀貴買貶貿貸越超趁跎距跋跚跑跌跛跆軻軸軼辜逮逵週逸進逶鄂郵鄉郾酣酥量鈔鈕鈣鈉鈞鈍鈐鈇鈑閔閏開閑"],["b6a1","間閒閎隊階隋陽隅隆隍陲隄雁雅雄集雇雯雲韌項順須飧飪飯飩飲飭馮馭黃黍黑亂傭債傲傳僅傾催傷傻傯僇剿剷剽募勦勤勢勣匯嗟嗨嗓嗦嗎嗜嗇嗑嗣嗤嗯嗚嗡嗅嗆嗥嗉園圓塞塑塘塗塚塔填塌塭塊塢塒塋奧嫁嫉嫌媾媽媼"],["b740","媳嫂媲嵩嵯幌幹廉廈弒彙徬微愚意慈感想愛惹愁愈慎慌慄慍愾愴愧愍愆愷戡戢搓搾搞搪搭搽搬搏搜搔損搶搖搗搆敬斟新暗暉暇暈暖暄暘暍會榔業"],["b7a1","楚楷楠楔極椰概楊楨楫楞楓楹榆楝楣楛歇歲毀殿毓毽溢溯滓溶滂源溝滇滅溥溘溼溺溫滑準溜滄滔溪溧溴煎煙煩煤煉照煜煬煦煌煥煞煆煨煖爺牒猷獅猿猾瑯瑚瑕瑟瑞瑁琿瑙瑛瑜當畸瘀痰瘁痲痱痺痿痴痳盞盟睛睫睦睞督"],["b840","睹睪睬睜睥睨睢矮碎碰碗碘碌碉硼碑碓硿祺祿禁萬禽稜稚稠稔稟稞窟窠筷節筠筮筧粱粳粵經絹綑綁綏絛置罩罪署義羨群聖聘肆肄腱腰腸腥腮腳腫"],["b8a1","腹腺腦舅艇蒂葷落萱葵葦葫葉葬葛萼萵葡董葩葭葆虞虜號蛹蜓蜈蜇蜀蛾蛻蜂蜃蜆蜊衙裟裔裙補裘裝裡裊裕裒覜解詫該詳試詩詰誇詼詣誠話誅詭詢詮詬詹詻訾詨豢貊貉賊資賈賄貲賃賂賅跡跟跨路跳跺跪跤跦躲較載軾輊"],["b940","辟農運遊道遂達逼違遐遇遏過遍遑逾遁鄒鄗酬酪酩釉鈷鉗鈸鈽鉀鈾鉛鉋鉤鉑鈴鉉鉍鉅鈹鈿鉚閘隘隔隕雍雋雉雊雷電雹零靖靴靶預頑頓頊頒頌飼飴"],["b9a1","飽飾馳馱馴髡鳩麂鼎鼓鼠僧僮僥僖僭僚僕像僑僱僎僩兢凳劃劂匱厭嗾嘀嘛嘗嗽嘔嘆嘉嘍嘎嗷嘖嘟嘈嘐嗶團圖塵塾境墓墊塹墅塽壽夥夢夤奪奩嫡嫦嫩嫗嫖嫘嫣孵寞寧寡寥實寨寢寤察對屢嶄嶇幛幣幕幗幔廓廖弊彆彰徹慇"],["ba40","愿態慷慢慣慟慚慘慵截撇摘摔撤摸摟摺摑摧搴摭摻敲斡旗旖暢暨暝榜榨榕槁榮槓構榛榷榻榫榴槐槍榭槌榦槃榣歉歌氳漳演滾漓滴漩漾漠漬漏漂漢"],["baa1","滿滯漆漱漸漲漣漕漫漯澈漪滬漁滲滌滷熔熙煽熊熄熒爾犒犖獄獐瑤瑣瑪瑰瑭甄疑瘧瘍瘋瘉瘓盡監瞄睽睿睡磁碟碧碳碩碣禎福禍種稱窪窩竭端管箕箋筵算箝箔箏箸箇箄粹粽精綻綰綜綽綾綠緊綴網綱綺綢綿綵綸維緒緇綬"],["bb40","罰翠翡翟聞聚肇腐膀膏膈膊腿膂臧臺與舔舞艋蓉蒿蓆蓄蒙蒞蒲蒜蓋蒸蓀蓓蒐蒼蓑蓊蜿蜜蜻蜢蜥蜴蜘蝕蜷蜩裳褂裴裹裸製裨褚裯誦誌語誣認誡誓誤"],["bba1","說誥誨誘誑誚誧豪貍貌賓賑賒赫趙趕跼輔輒輕輓辣遠遘遜遣遙遞遢遝遛鄙鄘鄞酵酸酷酴鉸銀銅銘銖鉻銓銜銨鉼銑閡閨閩閣閥閤隙障際雌雒需靼鞅韶頗領颯颱餃餅餌餉駁骯骰髦魁魂鳴鳶鳳麼鼻齊億儀僻僵價儂儈儉儅凜"],["bc40","劇劈劉劍劊勰厲嘮嘻嘹嘲嘿嘴嘩噓噎噗噴嘶嘯嘰墀墟增墳墜墮墩墦奭嬉嫻嬋嫵嬌嬈寮寬審寫層履嶝嶔幢幟幡廢廚廟廝廣廠彈影德徵慶慧慮慝慕憂"],["bca1","慼慰慫慾憧憐憫憎憬憚憤憔憮戮摩摯摹撞撲撈撐撰撥撓撕撩撒撮播撫撚撬撙撢撳敵敷數暮暫暴暱樣樟槨樁樞標槽模樓樊槳樂樅槭樑歐歎殤毅毆漿潼澄潑潦潔澆潭潛潸潮澎潺潰潤澗潘滕潯潠潟熟熬熱熨牖犛獎獗瑩璋璃"],["bd40","瑾璀畿瘠瘩瘟瘤瘦瘡瘢皚皺盤瞎瞇瞌瞑瞋磋磅確磊碾磕碼磐稿稼穀稽稷稻窯窮箭箱範箴篆篇篁箠篌糊締練緯緻緘緬緝編緣線緞緩綞緙緲緹罵罷羯"],["bda1","翩耦膛膜膝膠膚膘蔗蔽蔚蓮蔬蔭蔓蔑蔣蔡蔔蓬蔥蓿蔆螂蝴蝶蝠蝦蝸蝨蝙蝗蝌蝓衛衝褐複褒褓褕褊誼諒談諄誕請諸課諉諂調誰論諍誶誹諛豌豎豬賠賞賦賤賬賭賢賣賜質賡赭趟趣踫踐踝踢踏踩踟踡踞躺輝輛輟輩輦輪輜輞"],["be40","輥適遮遨遭遷鄰鄭鄧鄱醇醉醋醃鋅銻銷鋪銬鋤鋁銳銼鋒鋇鋰銲閭閱霄霆震霉靠鞍鞋鞏頡頫頜颳養餓餒餘駝駐駟駛駑駕駒駙骷髮髯鬧魅魄魷魯鴆鴉"],["bea1","鴃麩麾黎墨齒儒儘儔儐儕冀冪凝劑劓勳噙噫噹噩噤噸噪器噥噱噯噬噢噶壁墾壇壅奮嬝嬴學寰導彊憲憑憩憊懍憶憾懊懈戰擅擁擋撻撼據擄擇擂操撿擒擔撾整曆曉暹曄曇暸樽樸樺橙橫橘樹橄橢橡橋橇樵機橈歙歷氅濂澱澡"],["bf40","濃澤濁澧澳激澹澶澦澠澴熾燉燐燒燈燕熹燎燙燜燃燄獨璜璣璘璟璞瓢甌甍瘴瘸瘺盧盥瞠瞞瞟瞥磨磚磬磧禦積穎穆穌穋窺篙簑築篤篛篡篩篦糕糖縊"],["bfa1","縑縈縛縣縞縝縉縐罹羲翰翱翮耨膳膩膨臻興艘艙蕊蕙蕈蕨蕩蕃蕉蕭蕪蕞螃螟螞螢融衡褪褲褥褫褡親覦諦諺諫諱謀諜諧諮諾謁謂諷諭諳諶諼豫豭貓賴蹄踱踴蹂踹踵輻輯輸輳辨辦遵遴選遲遼遺鄴醒錠錶鋸錳錯錢鋼錫錄錚"],["c040","錐錦錡錕錮錙閻隧隨險雕霎霑霖霍霓霏靛靜靦鞘頰頸頻頷頭頹頤餐館餞餛餡餚駭駢駱骸骼髻髭鬨鮑鴕鴣鴦鴨鴒鴛默黔龍龜優償儡儲勵嚎嚀嚐嚅嚇"],["c0a1","嚏壕壓壑壎嬰嬪嬤孺尷屨嶼嶺嶽嶸幫彌徽應懂懇懦懋戲戴擎擊擘擠擰擦擬擱擢擭斂斃曙曖檀檔檄檢檜櫛檣橾檗檐檠歜殮毚氈濘濱濟濠濛濤濫濯澀濬濡濩濕濮濰燧營燮燦燥燭燬燴燠爵牆獰獲璩環璦璨癆療癌盪瞳瞪瞰瞬"],["c140","瞧瞭矯磷磺磴磯礁禧禪穗窿簇簍篾篷簌篠糠糜糞糢糟糙糝縮績繆縷縲繃縫總縱繅繁縴縹繈縵縿縯罄翳翼聱聲聰聯聳臆臃膺臂臀膿膽臉膾臨舉艱薪"],["c1a1","薄蕾薜薑薔薯薛薇薨薊虧蟀蟑螳蟒蟆螫螻螺蟈蟋褻褶襄褸褽覬謎謗謙講謊謠謝謄謐豁谿豳賺賽購賸賻趨蹉蹋蹈蹊轄輾轂轅輿避遽還邁邂邀鄹醣醞醜鍍鎂錨鍵鍊鍥鍋錘鍾鍬鍛鍰鍚鍔闊闋闌闈闆隱隸雖霜霞鞠韓顆颶餵騁"],["c240","駿鮮鮫鮪鮭鴻鴿麋黏點黜黝黛鼾齋叢嚕嚮壙壘嬸彝懣戳擴擲擾攆擺擻擷斷曜朦檳檬櫃檻檸櫂檮檯歟歸殯瀉瀋濾瀆濺瀑瀏燻燼燾燸獷獵璧璿甕癖癘"],["c2a1","癒瞽瞿瞻瞼礎禮穡穢穠竄竅簫簧簪簞簣簡糧織繕繞繚繡繒繙罈翹翻職聶臍臏舊藏薩藍藐藉薰薺薹薦蟯蟬蟲蟠覆覲觴謨謹謬謫豐贅蹙蹣蹦蹤蹟蹕軀轉轍邇邃邈醫醬釐鎔鎊鎖鎢鎳鎮鎬鎰鎘鎚鎗闔闖闐闕離雜雙雛雞霤鞣鞦"],["c340","鞭韹額顏題顎顓颺餾餿餽餮馥騎髁鬃鬆魏魎魍鯊鯉鯽鯈鯀鵑鵝鵠黠鼕鼬儳嚥壞壟壢寵龐廬懲懷懶懵攀攏曠曝櫥櫝櫚櫓瀛瀟瀨瀚瀝瀕瀘爆爍牘犢獸"],["c3a1","獺璽瓊瓣疇疆癟癡矇礙禱穫穩簾簿簸簽簷籀繫繭繹繩繪羅繳羶羹羸臘藩藝藪藕藤藥藷蟻蠅蠍蟹蟾襠襟襖襞譁譜識證譚譎譏譆譙贈贊蹼蹲躇蹶蹬蹺蹴轔轎辭邊邋醱醮鏡鏑鏟鏃鏈鏜鏝鏖鏢鏍鏘鏤鏗鏨關隴難霪霧靡韜韻類"],["c440","願顛颼饅饉騖騙鬍鯨鯧鯖鯛鶉鵡鵲鵪鵬麒麗麓麴勸嚨嚷嚶嚴嚼壤孀孃孽寶巉懸懺攘攔攙曦朧櫬瀾瀰瀲爐獻瓏癢癥礦礪礬礫竇競籌籃籍糯糰辮繽繼"],["c4a1","纂罌耀臚艦藻藹蘑藺蘆蘋蘇蘊蠔蠕襤覺觸議譬警譯譟譫贏贍躉躁躅躂醴釋鐘鐃鏽闡霰飄饒饑馨騫騰騷騵鰓鰍鹹麵黨鼯齟齣齡儷儸囁囀囂夔屬巍懼懾攝攜斕曩櫻欄櫺殲灌爛犧瓖瓔癩矓籐纏續羼蘗蘭蘚蠣蠢蠡蠟襪襬覽譴"],["c540","護譽贓躊躍躋轟辯醺鐮鐳鐵鐺鐸鐲鐫闢霸霹露響顧顥饗驅驃驀騾髏魔魑鰭鰥鶯鶴鷂鶸麝黯鼙齜齦齧儼儻囈囊囉孿巔巒彎懿攤權歡灑灘玀瓤疊癮癬"],["c5a1","禳籠籟聾聽臟襲襯觼讀贖贗躑躓轡酈鑄鑑鑒霽霾韃韁顫饕驕驍髒鬚鱉鰱鰾鰻鷓鷗鼴齬齪龔囌巖戀攣攫攪曬欐瓚竊籤籣籥纓纖纔臢蘸蘿蠱變邐邏鑣鑠鑤靨顯饜驚驛驗髓體髑鱔鱗鱖鷥麟黴囑壩攬灞癱癲矗罐羈蠶蠹衢讓讒"],["c640","讖艷贛釀鑪靂靈靄韆顰驟鬢魘鱟鷹鷺鹼鹽鼇齷齲廳欖灣籬籮蠻觀躡釁鑲鑰顱饞髖鬣黌灤矚讚鑷韉驢驥纜讜躪釅鑽鑾鑼鱷鱸黷豔鑿鸚爨驪鬱鸛鸞籲"],["c940","乂乜凵匚厂万丌乇亍囗兀屮彳丏冇与丮亓仂仉仈冘勼卬厹圠夃夬尐巿旡殳毌气爿丱丼仨仜仩仡仝仚刌匜卌圢圣夗夯宁宄尒尻屴屳帄庀庂忉戉扐氕"],["c9a1","氶汃氿氻犮犰玊禸肊阞伎优伬仵伔仱伀价伈伝伂伅伢伓伄仴伒冱刓刉刐劦匢匟卍厊吇囡囟圮圪圴夼妀奼妅奻奾奷奿孖尕尥屼屺屻屾巟幵庄异弚彴忕忔忏扜扞扤扡扦扢扙扠扚扥旯旮朾朹朸朻机朿朼朳氘汆汒汜汏汊汔汋"],["ca40","汌灱牞犴犵玎甪癿穵网艸艼芀艽艿虍襾邙邗邘邛邔阢阤阠阣佖伻佢佉体佤伾佧佒佟佁佘伭伳伿佡冏冹刜刞刡劭劮匉卣卲厎厏吰吷吪呔呅吙吜吥吘"],["caa1","吽呏呁吨吤呇囮囧囥坁坅坌坉坋坒夆奀妦妘妠妗妎妢妐妏妧妡宎宒尨尪岍岏岈岋岉岒岊岆岓岕巠帊帎庋庉庌庈庍弅弝彸彶忒忑忐忭忨忮忳忡忤忣忺忯忷忻怀忴戺抃抌抎抏抔抇扱扻扺扰抁抈扷扽扲扴攷旰旴旳旲旵杅杇"],["cb40","杙杕杌杈杝杍杚杋毐氙氚汸汧汫沄沋沏汱汯汩沚汭沇沕沜汦汳汥汻沎灴灺牣犿犽狃狆狁犺狅玕玗玓玔玒町甹疔疕皁礽耴肕肙肐肒肜芐芏芅芎芑芓"],["cba1","芊芃芄豸迉辿邟邡邥邞邧邠阰阨阯阭丳侘佼侅佽侀侇佶佴侉侄佷佌侗佪侚佹侁佸侐侜侔侞侒侂侕佫佮冞冼冾刵刲刳剆刱劼匊匋匼厒厔咇呿咁咑咂咈呫呺呾呥呬呴呦咍呯呡呠咘呣呧呤囷囹坯坲坭坫坱坰坶垀坵坻坳坴坢"],["cc40","坨坽夌奅妵妺姏姎妲姌姁妶妼姃姖妱妽姀姈妴姇孢孥宓宕屄屇岮岤岠岵岯岨岬岟岣岭岢岪岧岝岥岶岰岦帗帔帙弨弢弣弤彔徂彾彽忞忥怭怦怙怲怋"],["cca1","怴怊怗怳怚怞怬怢怍怐怮怓怑怌怉怜戔戽抭抴拑抾抪抶拊抮抳抯抻抩抰抸攽斨斻昉旼昄昒昈旻昃昋昍昅旽昑昐曶朊枅杬枎枒杶杻枘枆构杴枍枌杺枟枑枙枃杽极杸杹枔欥殀歾毞氝沓泬泫泮泙沶泔沭泧沷泐泂沺泃泆泭泲"],["cd40","泒泝沴沊沝沀泞泀洰泍泇沰泹泏泩泑炔炘炅炓炆炄炑炖炂炚炃牪狖狋狘狉狜狒狔狚狌狑玤玡玭玦玢玠玬玝瓝瓨甿畀甾疌疘皯盳盱盰盵矸矼矹矻矺"],["cda1","矷祂礿秅穸穻竻籵糽耵肏肮肣肸肵肭舠芠苀芫芚芘芛芵芧芮芼芞芺芴芨芡芩苂芤苃芶芢虰虯虭虮豖迒迋迓迍迖迕迗邲邴邯邳邰阹阽阼阺陃俍俅俓侲俉俋俁俔俜俙侻侳俛俇俖侺俀侹俬剄剉勀勂匽卼厗厖厙厘咺咡咭咥哏"],["ce40","哃茍咷咮哖咶哅哆咠呰咼咢咾呲哞咰垵垞垟垤垌垗垝垛垔垘垏垙垥垚垕壴复奓姡姞姮娀姱姝姺姽姼姶姤姲姷姛姩姳姵姠姾姴姭宨屌峐峘峌峗峋峛"],["cea1","峞峚峉峇峊峖峓峔峏峈峆峎峟峸巹帡帢帣帠帤庰庤庢庛庣庥弇弮彖徆怷怹恔恲恞恅恓恇恉恛恌恀恂恟怤恄恘恦恮扂扃拏挍挋拵挎挃拫拹挏挌拸拶挀挓挔拺挕拻拰敁敃斪斿昶昡昲昵昜昦昢昳昫昺昝昴昹昮朏朐柁柲柈枺"],["cf40","柜枻柸柘柀枷柅柫柤柟枵柍枳柷柶柮柣柂枹柎柧柰枲柼柆柭柌枮柦柛柺柉柊柃柪柋欨殂殄殶毖毘毠氠氡洨洴洭洟洼洿洒洊泚洳洄洙洺洚洑洀洝浂"],["cfa1","洁洘洷洃洏浀洇洠洬洈洢洉洐炷炟炾炱炰炡炴炵炩牁牉牊牬牰牳牮狊狤狨狫狟狪狦狣玅珌珂珈珅玹玶玵玴珫玿珇玾珃珆玸珋瓬瓮甮畇畈疧疪癹盄眈眃眄眅眊盷盻盺矧矨砆砑砒砅砐砏砎砉砃砓祊祌祋祅祄秕种秏秖秎窀"],["d040","穾竑笀笁籺籸籹籿粀粁紃紈紁罘羑羍羾耇耎耏耔耷胘胇胠胑胈胂胐胅胣胙胜胊胕胉胏胗胦胍臿舡芔苙苾苹茇苨茀苕茺苫苖苴苬苡苲苵茌苻苶苰苪"],["d0a1","苤苠苺苳苭虷虴虼虳衁衎衧衪衩觓訄訇赲迣迡迮迠郱邽邿郕郅邾郇郋郈釔釓陔陏陑陓陊陎倞倅倇倓倢倰倛俵俴倳倷倬俶俷倗倜倠倧倵倯倱倎党冔冓凊凄凅凈凎剡剚剒剞剟剕剢勍匎厞唦哢唗唒哧哳哤唚哿唄唈哫唑唅哱"],["d140","唊哻哷哸哠唎唃唋圁圂埌堲埕埒垺埆垽垼垸垶垿埇埐垹埁夎奊娙娖娭娮娕娏娗娊娞娳孬宧宭宬尃屖屔峬峿峮峱峷崀峹帩帨庨庮庪庬弳弰彧恝恚恧"],["d1a1","恁悢悈悀悒悁悝悃悕悛悗悇悜悎戙扆拲挐捖挬捄捅挶捃揤挹捋捊挼挩捁挴捘捔捙挭捇挳捚捑挸捗捀捈敊敆旆旃旄旂晊晟晇晑朒朓栟栚桉栲栳栻桋桏栖栱栜栵栫栭栯桎桄栴栝栒栔栦栨栮桍栺栥栠欬欯欭欱欴歭肂殈毦毤"],["d240","毨毣毢毧氥浺浣浤浶洍浡涒浘浢浭浯涑涍淯浿涆浞浧浠涗浰浼浟涂涘洯浨涋浾涀涄洖涃浻浽浵涐烜烓烑烝烋缹烢烗烒烞烠烔烍烅烆烇烚烎烡牂牸"],["d2a1","牷牶猀狺狴狾狶狳狻猁珓珙珥珖玼珧珣珩珜珒珛珔珝珚珗珘珨瓞瓟瓴瓵甡畛畟疰痁疻痄痀疿疶疺皊盉眝眛眐眓眒眣眑眕眙眚眢眧砣砬砢砵砯砨砮砫砡砩砳砪砱祔祛祏祜祓祒祑秫秬秠秮秭秪秜秞秝窆窉窅窋窌窊窇竘笐"],["d340","笄笓笅笏笈笊笎笉笒粄粑粊粌粈粍粅紞紝紑紎紘紖紓紟紒紏紌罜罡罞罠罝罛羖羒翃翂翀耖耾耹胺胲胹胵脁胻脀舁舯舥茳茭荄茙荑茥荖茿荁茦茜茢"],["d3a1","荂荎茛茪茈茼荍茖茤茠茷茯茩荇荅荌荓茞茬荋茧荈虓虒蚢蚨蚖蚍蚑蚞蚇蚗蚆蚋蚚蚅蚥蚙蚡蚧蚕蚘蚎蚝蚐蚔衃衄衭衵衶衲袀衱衿衯袃衾衴衼訒豇豗豻貤貣赶赸趵趷趶軑軓迾迵适迿迻逄迼迶郖郠郙郚郣郟郥郘郛郗郜郤酐"],["d440","酎酏釕釢釚陜陟隼飣髟鬯乿偰偪偡偞偠偓偋偝偲偈偍偁偛偊偢倕偅偟偩偫偣偤偆偀偮偳偗偑凐剫剭剬剮勖勓匭厜啵啶唼啍啐唴唪啑啢唶唵唰啒啅"],["d4a1","唌唲啥啎唹啈唭唻啀啋圊圇埻堔埢埶埜埴堀埭埽堈埸堋埳埏堇埮埣埲埥埬埡堎埼堐埧堁堌埱埩埰堍堄奜婠婘婕婧婞娸娵婭婐婟婥婬婓婤婗婃婝婒婄婛婈媎娾婍娹婌婰婩婇婑婖婂婜孲孮寁寀屙崞崋崝崚崠崌崨崍崦崥崏"],["d540","崰崒崣崟崮帾帴庱庴庹庲庳弶弸徛徖徟悊悐悆悾悰悺惓惔惏惤惙惝惈悱惛悷惊悿惃惍惀挲捥掊掂捽掽掞掭掝掗掫掎捯掇掐据掯捵掜捭掮捼掤挻掟"],["d5a1","捸掅掁掑掍捰敓旍晥晡晛晙晜晢朘桹梇梐梜桭桮梮梫楖桯梣梬梩桵桴梲梏桷梒桼桫桲梪梀桱桾梛梖梋梠梉梤桸桻梑梌梊桽欶欳欷欸殑殏殍殎殌氪淀涫涴涳湴涬淩淢涷淶淔渀淈淠淟淖涾淥淜淝淛淴淊涽淭淰涺淕淂淏淉"],["d640","淐淲淓淽淗淍淣涻烺焍烷焗烴焌烰焄烳焐烼烿焆焓焀烸烶焋焂焎牾牻牼牿猝猗猇猑猘猊猈狿猏猞玈珶珸珵琄琁珽琇琀珺珼珿琌琋珴琈畤畣痎痒痏"],["d6a1","痋痌痑痐皏皉盓眹眯眭眱眲眴眳眽眥眻眵硈硒硉硍硊硌砦硅硐祤祧祩祪祣祫祡离秺秸秶秷窏窔窐笵筇笴笥笰笢笤笳笘笪笝笱笫笭笯笲笸笚笣粔粘粖粣紵紽紸紶紺絅紬紩絁絇紾紿絊紻紨罣羕羜羝羛翊翋翍翐翑翇翏翉耟"],["d740","耞耛聇聃聈脘脥脙脛脭脟脬脞脡脕脧脝脢舑舸舳舺舴舲艴莐莣莨莍荺荳莤荴莏莁莕莙荵莔莩荽莃莌莝莛莪莋荾莥莯莈莗莰荿莦莇莮荶莚虙虖蚿蚷"],["d7a1","蛂蛁蛅蚺蚰蛈蚹蚳蚸蛌蚴蚻蚼蛃蚽蚾衒袉袕袨袢袪袚袑袡袟袘袧袙袛袗袤袬袌袓袎覂觖觙觕訰訧訬訞谹谻豜豝豽貥赽赻赹趼跂趹趿跁軘軞軝軜軗軠軡逤逋逑逜逌逡郯郪郰郴郲郳郔郫郬郩酖酘酚酓酕釬釴釱釳釸釤釹釪"],["d840","釫釷釨釮镺閆閈陼陭陫陱陯隿靪頄飥馗傛傕傔傞傋傣傃傌傎傝偨傜傒傂傇兟凔匒匑厤厧喑喨喥喭啷噅喢喓喈喏喵喁喣喒喤啽喌喦啿喕喡喎圌堩堷"],["d8a1","堙堞堧堣堨埵塈堥堜堛堳堿堶堮堹堸堭堬堻奡媯媔媟婺媢媞婸媦婼媥媬媕媮娷媄媊媗媃媋媩婻婽媌媜媏媓媝寪寍寋寔寑寊寎尌尰崷嵃嵫嵁嵋崿崵嵑嵎嵕崳崺嵒崽崱嵙嵂崹嵉崸崼崲崶嵀嵅幄幁彘徦徥徫惉悹惌惢惎惄愔"],["d940","惲愊愖愅惵愓惸惼惾惁愃愘愝愐惿愄愋扊掔掱掰揎揥揨揯揃撝揳揊揠揶揕揲揵摡揟掾揝揜揄揘揓揂揇揌揋揈揰揗揙攲敧敪敤敜敨敥斌斝斞斮旐旒"],["d9a1","晼晬晻暀晱晹晪晲朁椌棓椄棜椪棬棪棱椏棖棷棫棤棶椓椐棳棡椇棌椈楰梴椑棯棆椔棸棐棽棼棨椋椊椗棎棈棝棞棦棴棑椆棔棩椕椥棇欹欻欿欼殔殗殙殕殽毰毲毳氰淼湆湇渟湉溈渼渽湅湢渫渿湁湝湳渜渳湋湀湑渻渃渮湞"],["da40","湨湜湡渱渨湠湱湫渹渢渰湓湥渧湸湤湷湕湹湒湦渵渶湚焠焞焯烻焮焱焣焥焢焲焟焨焺焛牋牚犈犉犆犅犋猒猋猰猢猱猳猧猲猭猦猣猵猌琮琬琰琫琖"],["daa1","琚琡琭琱琤琣琝琩琠琲瓻甯畯畬痧痚痡痦痝痟痤痗皕皒盚睆睇睄睍睅睊睎睋睌矞矬硠硤硥硜硭硱硪确硰硩硨硞硢祴祳祲祰稂稊稃稌稄窙竦竤筊笻筄筈筌筎筀筘筅粢粞粨粡絘絯絣絓絖絧絪絏絭絜絫絒絔絩絑絟絎缾缿罥"],["db40","罦羢羠羡翗聑聏聐胾胔腃腊腒腏腇脽腍脺臦臮臷臸臹舄舼舽舿艵茻菏菹萣菀菨萒菧菤菼菶萐菆菈菫菣莿萁菝菥菘菿菡菋菎菖菵菉萉萏菞萑萆菂菳"],["dba1","菕菺菇菑菪萓菃菬菮菄菻菗菢萛菛菾蛘蛢蛦蛓蛣蛚蛪蛝蛫蛜蛬蛩蛗蛨蛑衈衖衕袺裗袹袸裀袾袶袼袷袽袲褁裉覕覘覗觝觚觛詎詍訹詙詀詗詘詄詅詒詈詑詊詌詏豟貁貀貺貾貰貹貵趄趀趉跘跓跍跇跖跜跏跕跙跈跗跅軯軷軺"],["dc40","軹軦軮軥軵軧軨軶軫軱軬軴軩逭逴逯鄆鄬鄄郿郼鄈郹郻鄁鄀鄇鄅鄃酡酤酟酢酠鈁鈊鈥鈃鈚鈦鈏鈌鈀鈒釿釽鈆鈄鈧鈂鈜鈤鈙鈗鈅鈖镻閍閌閐隇陾隈"],["dca1","隉隃隀雂雈雃雱雰靬靰靮頇颩飫鳦黹亃亄亶傽傿僆傮僄僊傴僈僂傰僁傺傱僋僉傶傸凗剺剸剻剼嗃嗛嗌嗐嗋嗊嗝嗀嗔嗄嗩喿嗒喍嗏嗕嗢嗖嗈嗲嗍嗙嗂圔塓塨塤塏塍塉塯塕塎塝塙塥塛堽塣塱壼嫇嫄嫋媺媸媱媵媰媿嫈媻嫆"],["dd40","媷嫀嫊媴媶嫍媹媐寖寘寙尟尳嵱嵣嵊嵥嵲嵬嵞嵨嵧嵢巰幏幎幊幍幋廅廌廆廋廇彀徯徭惷慉慊愫慅愶愲愮慆愯慏愩慀戠酨戣戥戤揅揱揫搐搒搉搠搤"],["dda1","搳摃搟搕搘搹搷搢搣搌搦搰搨摁搵搯搊搚摀搥搧搋揧搛搮搡搎敯斒旓暆暌暕暐暋暊暙暔晸朠楦楟椸楎楢楱椿楅楪椹楂楗楙楺楈楉椵楬椳椽楥棰楸椴楩楀楯楄楶楘楁楴楌椻楋椷楜楏楑椲楒椯楻椼歆歅歃歂歈歁殛嗀毻毼"],["de40","毹毷毸溛滖滈溏滀溟溓溔溠溱溹滆滒溽滁溞滉溷溰滍溦滏溲溾滃滜滘溙溒溎溍溤溡溿溳滐滊溗溮溣煇煔煒煣煠煁煝煢煲煸煪煡煂煘煃煋煰煟煐煓"],["dea1","煄煍煚牏犍犌犑犐犎猼獂猻猺獀獊獉瑄瑊瑋瑒瑑瑗瑀瑏瑐瑎瑂瑆瑍瑔瓡瓿瓾瓽甝畹畷榃痯瘏瘃痷痾痼痹痸瘐痻痶痭痵痽皙皵盝睕睟睠睒睖睚睩睧睔睙睭矠碇碚碔碏碄碕碅碆碡碃硹碙碀碖硻祼禂祽祹稑稘稙稒稗稕稢稓"],["df40","稛稐窣窢窞竫筦筤筭筴筩筲筥筳筱筰筡筸筶筣粲粴粯綈綆綀綍絿綅絺綎絻綃絼綌綔綄絽綒罭罫罧罨罬羦羥羧翛翜耡腤腠腷腜腩腛腢腲朡腞腶腧腯"],["dfa1","腄腡舝艉艄艀艂艅蓱萿葖葶葹蒏蒍葥葑葀蒆葧萰葍葽葚葙葴葳葝蔇葞萷萺萴葺葃葸萲葅萩菙葋萯葂萭葟葰萹葎葌葒葯蓅蒎萻葇萶萳葨葾葄萫葠葔葮葐蜋蜄蛷蜌蛺蛖蛵蝍蛸蜎蜉蜁蛶蜍蜅裖裋裍裎裞裛裚裌裐覅覛觟觥觤"],["e040","觡觠觢觜触詶誆詿詡訿詷誂誄詵誃誁詴詺谼豋豊豥豤豦貆貄貅賌赨赩趑趌趎趏趍趓趔趐趒跰跠跬跱跮跐跩跣跢跧跲跫跴輆軿輁輀輅輇輈輂輋遒逿"],["e0a1","遄遉逽鄐鄍鄏鄑鄖鄔鄋鄎酮酯鉈鉒鈰鈺鉦鈳鉥鉞銃鈮鉊鉆鉭鉬鉏鉠鉧鉯鈶鉡鉰鈱鉔鉣鉐鉲鉎鉓鉌鉖鈲閟閜閞閛隒隓隑隗雎雺雽雸雵靳靷靸靲頏頍頎颬飶飹馯馲馰馵骭骫魛鳪鳭鳧麀黽僦僔僗僨僳僛僪僝僤僓僬僰僯僣僠"],["e140","凘劀劁勩勫匰厬嘧嘕嘌嘒嗼嘏嘜嘁嘓嘂嗺嘝嘄嗿嗹墉塼墐墘墆墁塿塴墋塺墇墑墎塶墂墈塻墔墏壾奫嫜嫮嫥嫕嫪嫚嫭嫫嫳嫢嫠嫛嫬嫞嫝嫙嫨嫟孷寠"],["e1a1","寣屣嶂嶀嵽嶆嵺嶁嵷嶊嶉嶈嵾嵼嶍嵹嵿幘幙幓廘廑廗廎廜廕廙廒廔彄彃彯徶愬愨慁慞慱慳慒慓慲慬憀慴慔慺慛慥愻慪慡慖戩戧戫搫摍摛摝摴摶摲摳摽摵摦撦摎撂摞摜摋摓摠摐摿搿摬摫摙摥摷敳斠暡暠暟朅朄朢榱榶槉"],["e240","榠槎榖榰榬榼榑榙榎榧榍榩榾榯榿槄榽榤槔榹槊榚槏榳榓榪榡榞槙榗榐槂榵榥槆歊歍歋殞殟殠毃毄毾滎滵滱漃漥滸漷滻漮漉潎漙漚漧漘漻漒滭漊"],["e2a1","漶潳滹滮漭潀漰漼漵滫漇漎潃漅滽滶漹漜滼漺漟漍漞漈漡熇熐熉熀熅熂熏煻熆熁熗牄牓犗犕犓獃獍獑獌瑢瑳瑱瑵瑲瑧瑮甀甂甃畽疐瘖瘈瘌瘕瘑瘊瘔皸瞁睼瞅瞂睮瞀睯睾瞃碲碪碴碭碨硾碫碞碥碠碬碢碤禘禊禋禖禕禔禓"],["e340","禗禈禒禐稫穊稰稯稨稦窨窫窬竮箈箜箊箑箐箖箍箌箛箎箅箘劄箙箤箂粻粿粼粺綧綷緂綣綪緁緀緅綝緎緄緆緋緌綯綹綖綼綟綦綮綩綡緉罳翢翣翥翞"],["e3a1","耤聝聜膉膆膃膇膍膌膋舕蒗蒤蒡蒟蒺蓎蓂蒬蒮蒫蒹蒴蓁蓍蒪蒚蒱蓐蒝蒧蒻蒢蒔蓇蓌蒛蒩蒯蒨蓖蒘蒶蓏蒠蓗蓔蓒蓛蒰蒑虡蜳蜣蜨蝫蝀蜮蜞蜡蜙蜛蝃蜬蝁蜾蝆蜠蜲蜪蜭蜼蜒蜺蜱蜵蝂蜦蜧蜸蜤蜚蜰蜑裷裧裱裲裺裾裮裼裶裻"],["e440","裰裬裫覝覡覟覞觩觫觨誫誙誋誒誏誖谽豨豩賕賏賗趖踉踂跿踍跽踊踃踇踆踅跾踀踄輐輑輎輍鄣鄜鄠鄢鄟鄝鄚鄤鄡鄛酺酲酹酳銥銤鉶銛鉺銠銔銪銍"],["e4a1","銦銚銫鉹銗鉿銣鋮銎銂銕銢鉽銈銡銊銆銌銙銧鉾銇銩銝銋鈭隞隡雿靘靽靺靾鞃鞀鞂靻鞄鞁靿韎韍頖颭颮餂餀餇馝馜駃馹馻馺駂馽駇骱髣髧鬾鬿魠魡魟鳱鳲鳵麧僿儃儰僸儆儇僶僾儋儌僽儊劋劌勱勯噈噂噌嘵噁噊噉噆噘"],["e540","噚噀嘳嘽嘬嘾嘸嘪嘺圚墫墝墱墠墣墯墬墥墡壿嫿嫴嫽嫷嫶嬃嫸嬂嫹嬁嬇嬅嬏屧嶙嶗嶟嶒嶢嶓嶕嶠嶜嶡嶚嶞幩幝幠幜緳廛廞廡彉徲憋憃慹憱憰憢憉"],["e5a1","憛憓憯憭憟憒憪憡憍慦憳戭摮摰撖撠撅撗撜撏撋撊撌撣撟摨撱撘敶敺敹敻斲斳暵暰暩暲暷暪暯樀樆樗槥槸樕槱槤樠槿槬槢樛樝槾樧槲槮樔槷槧橀樈槦槻樍槼槫樉樄樘樥樏槶樦樇槴樖歑殥殣殢殦氁氀毿氂潁漦潾澇濆澒"],["e640","澍澉澌潢潏澅潚澖潶潬澂潕潲潒潐潗澔澓潝漀潡潫潽潧澐潓澋潩潿澕潣潷潪潻熲熯熛熰熠熚熩熵熝熥熞熤熡熪熜熧熳犘犚獘獒獞獟獠獝獛獡獚獙"],["e6a1","獢璇璉璊璆璁瑽璅璈瑼瑹甈甇畾瘥瘞瘙瘝瘜瘣瘚瘨瘛皜皝皞皛瞍瞏瞉瞈磍碻磏磌磑磎磔磈磃磄磉禚禡禠禜禢禛歶稹窲窴窳箷篋箾箬篎箯箹篊箵糅糈糌糋緷緛緪緧緗緡縃緺緦緶緱緰緮緟罶羬羰羭翭翫翪翬翦翨聤聧膣膟"],["e740","膞膕膢膙膗舖艏艓艒艐艎艑蔤蔻蔏蔀蔩蔎蔉蔍蔟蔊蔧蔜蓻蔫蓺蔈蔌蓴蔪蓲蔕蓷蓫蓳蓼蔒蓪蓩蔖蓾蔨蔝蔮蔂蓽蔞蓶蔱蔦蓧蓨蓰蓯蓹蔘蔠蔰蔋蔙蔯虢"],["e7a1","蝖蝣蝤蝷蟡蝳蝘蝔蝛蝒蝡蝚蝑蝞蝭蝪蝐蝎蝟蝝蝯蝬蝺蝮蝜蝥蝏蝻蝵蝢蝧蝩衚褅褌褔褋褗褘褙褆褖褑褎褉覢覤覣觭觰觬諏諆誸諓諑諔諕誻諗誾諀諅諘諃誺誽諙谾豍貏賥賟賙賨賚賝賧趠趜趡趛踠踣踥踤踮踕踛踖踑踙踦踧"],["e840","踔踒踘踓踜踗踚輬輤輘輚輠輣輖輗遳遰遯遧遫鄯鄫鄩鄪鄲鄦鄮醅醆醊醁醂醄醀鋐鋃鋄鋀鋙銶鋏鋱鋟鋘鋩鋗鋝鋌鋯鋂鋨鋊鋈鋎鋦鋍鋕鋉鋠鋞鋧鋑鋓"],["e8a1","銵鋡鋆銴镼閬閫閮閰隤隢雓霅霈霂靚鞊鞎鞈韐韏頞頝頦頩頨頠頛頧颲餈飺餑餔餖餗餕駜駍駏駓駔駎駉駖駘駋駗駌骳髬髫髳髲髱魆魃魧魴魱魦魶魵魰魨魤魬鳼鳺鳽鳿鳷鴇鴀鳹鳻鴈鴅鴄麃黓鼏鼐儜儓儗儚儑凞匴叡噰噠噮"],["e940","噳噦噣噭噲噞噷圜圛壈墽壉墿墺壂墼壆嬗嬙嬛嬡嬔嬓嬐嬖嬨嬚嬠嬞寯嶬嶱嶩嶧嶵嶰嶮嶪嶨嶲嶭嶯嶴幧幨幦幯廩廧廦廨廥彋徼憝憨憖懅憴懆懁懌憺"],["e9a1","憿憸憌擗擖擐擏擉撽撉擃擛擳擙攳敿敼斢曈暾曀曊曋曏暽暻暺曌朣樴橦橉橧樲橨樾橝橭橶橛橑樨橚樻樿橁橪橤橐橏橔橯橩橠樼橞橖橕橍橎橆歕歔歖殧殪殫毈毇氄氃氆澭濋澣濇澼濎濈潞濄澽澞濊澨瀄澥澮澺澬澪濏澿澸"],["ea40","澢濉澫濍澯澲澰燅燂熿熸燖燀燁燋燔燊燇燏熽燘熼燆燚燛犝犞獩獦獧獬獥獫獪瑿璚璠璔璒璕璡甋疀瘯瘭瘱瘽瘳瘼瘵瘲瘰皻盦瞚瞝瞡瞜瞛瞢瞣瞕瞙"],["eaa1","瞗磝磩磥磪磞磣磛磡磢磭磟磠禤穄穈穇窶窸窵窱窷篞篣篧篝篕篥篚篨篹篔篪篢篜篫篘篟糒糔糗糐糑縒縡縗縌縟縠縓縎縜縕縚縢縋縏縖縍縔縥縤罃罻罼罺羱翯耪耩聬膱膦膮膹膵膫膰膬膴膲膷膧臲艕艖艗蕖蕅蕫蕍蕓蕡蕘"],["eb40","蕀蕆蕤蕁蕢蕄蕑蕇蕣蔾蕛蕱蕎蕮蕵蕕蕧蕠薌蕦蕝蕔蕥蕬虣虥虤螛螏螗螓螒螈螁螖螘蝹螇螣螅螐螑螝螄螔螜螚螉褞褦褰褭褮褧褱褢褩褣褯褬褟觱諠"],["eba1","諢諲諴諵諝謔諤諟諰諈諞諡諨諿諯諻貑貒貐賵賮賱賰賳赬赮趥趧踳踾踸蹀蹅踶踼踽蹁踰踿躽輶輮輵輲輹輷輴遶遹遻邆郺鄳鄵鄶醓醐醑醍醏錧錞錈錟錆錏鍺錸錼錛錣錒錁鍆錭錎錍鋋錝鋺錥錓鋹鋷錴錂錤鋿錩錹錵錪錔錌"],["ec40","錋鋾錉錀鋻錖閼闍閾閹閺閶閿閵閽隩雔霋霒霐鞙鞗鞔韰韸頵頯頲餤餟餧餩馞駮駬駥駤駰駣駪駩駧骹骿骴骻髶髺髹髷鬳鮀鮅鮇魼魾魻鮂鮓鮒鮐魺鮕"],["eca1","魽鮈鴥鴗鴠鴞鴔鴩鴝鴘鴢鴐鴙鴟麈麆麇麮麭黕黖黺鼒鼽儦儥儢儤儠儩勴嚓嚌嚍嚆嚄嚃噾嚂噿嚁壖壔壏壒嬭嬥嬲嬣嬬嬧嬦嬯嬮孻寱寲嶷幬幪徾徻懃憵憼懧懠懥懤懨懞擯擩擣擫擤擨斁斀斶旚曒檍檖檁檥檉檟檛檡檞檇檓檎"],["ed40","檕檃檨檤檑橿檦檚檅檌檒歛殭氉濌澩濴濔濣濜濭濧濦濞濲濝濢濨燡燱燨燲燤燰燢獳獮獯璗璲璫璐璪璭璱璥璯甐甑甒甏疄癃癈癉癇皤盩瞵瞫瞲瞷瞶"],["eda1","瞴瞱瞨矰磳磽礂磻磼磲礅磹磾礄禫禨穜穛穖穘穔穚窾竀竁簅簏篲簀篿篻簎篴簋篳簂簉簃簁篸篽簆篰篱簐簊糨縭縼繂縳顈縸縪繉繀繇縩繌縰縻縶繄縺罅罿罾罽翴翲耬膻臄臌臊臅臇膼臩艛艚艜薃薀薏薧薕薠薋薣蕻薤薚薞"],["ee40","蕷蕼薉薡蕺蕸蕗薎薖薆薍薙薝薁薢薂薈薅蕹蕶薘薐薟虨螾螪螭蟅螰螬螹螵螼螮蟉蟃蟂蟌螷螯蟄蟊螴螶螿螸螽蟞螲褵褳褼褾襁襒褷襂覭覯覮觲觳謞"],["eea1","謘謖謑謅謋謢謏謒謕謇謍謈謆謜謓謚豏豰豲豱豯貕貔賹赯蹎蹍蹓蹐蹌蹇轃轀邅遾鄸醚醢醛醙醟醡醝醠鎡鎃鎯鍤鍖鍇鍼鍘鍜鍶鍉鍐鍑鍠鍭鎏鍌鍪鍹鍗鍕鍒鍏鍱鍷鍻鍡鍞鍣鍧鎀鍎鍙闇闀闉闃闅閷隮隰隬霠霟霘霝霙鞚鞡鞜"],["ef40","鞞鞝韕韔韱顁顄顊顉顅顃餥餫餬餪餳餲餯餭餱餰馘馣馡騂駺駴駷駹駸駶駻駽駾駼騃骾髾髽鬁髼魈鮚鮨鮞鮛鮦鮡鮥鮤鮆鮢鮠鮯鴳鵁鵧鴶鴮鴯鴱鴸鴰"],["efa1","鵅鵂鵃鴾鴷鵀鴽翵鴭麊麉麍麰黈黚黻黿鼤鼣鼢齔龠儱儭儮嚘嚜嚗嚚嚝嚙奰嬼屩屪巀幭幮懘懟懭懮懱懪懰懫懖懩擿攄擽擸攁攃擼斔旛曚曛曘櫅檹檽櫡櫆檺檶檷櫇檴檭歞毉氋瀇瀌瀍瀁瀅瀔瀎濿瀀濻瀦濼濷瀊爁燿燹爃燽獶"],["f040","璸瓀璵瓁璾璶璻瓂甔甓癜癤癙癐癓癗癚皦皽盬矂瞺磿礌礓礔礉礐礒礑禭禬穟簜簩簙簠簟簭簝簦簨簢簥簰繜繐繖繣繘繢繟繑繠繗繓羵羳翷翸聵臑臒"],["f0a1","臐艟艞薴藆藀藃藂薳薵薽藇藄薿藋藎藈藅薱薶藒蘤薸薷薾虩蟧蟦蟢蟛蟫蟪蟥蟟蟳蟤蟔蟜蟓蟭蟘蟣螤蟗蟙蠁蟴蟨蟝襓襋襏襌襆襐襑襉謪謧謣謳謰謵譇謯謼謾謱謥謷謦謶謮謤謻謽謺豂豵貙貘貗賾贄贂贀蹜蹢蹠蹗蹖蹞蹥蹧"],["f140","蹛蹚蹡蹝蹩蹔轆轇轈轋鄨鄺鄻鄾醨醥醧醯醪鎵鎌鎒鎷鎛鎝鎉鎧鎎鎪鎞鎦鎕鎈鎙鎟鎍鎱鎑鎲鎤鎨鎴鎣鎥闒闓闑隳雗雚巂雟雘雝霣霢霥鞬鞮鞨鞫鞤鞪"],["f1a1","鞢鞥韗韙韖韘韺顐顑顒颸饁餼餺騏騋騉騍騄騑騊騅騇騆髀髜鬈鬄鬅鬩鬵魊魌魋鯇鯆鯃鮿鯁鮵鮸鯓鮶鯄鮹鮽鵜鵓鵏鵊鵛鵋鵙鵖鵌鵗鵒鵔鵟鵘鵚麎麌黟鼁鼀鼖鼥鼫鼪鼩鼨齌齕儴儵劖勷厴嚫嚭嚦嚧嚪嚬壚壝壛夒嬽嬾嬿巃幰"],["f240","徿懻攇攐攍攉攌攎斄旞旝曞櫧櫠櫌櫑櫙櫋櫟櫜櫐櫫櫏櫍櫞歠殰氌瀙瀧瀠瀖瀫瀡瀢瀣瀩瀗瀤瀜瀪爌爊爇爂爅犥犦犤犣犡瓋瓅璷瓃甖癠矉矊矄矱礝礛"],["f2a1","礡礜礗礞禰穧穨簳簼簹簬簻糬糪繶繵繸繰繷繯繺繲繴繨罋罊羃羆羷翽翾聸臗臕艤艡艣藫藱藭藙藡藨藚藗藬藲藸藘藟藣藜藑藰藦藯藞藢蠀蟺蠃蟶蟷蠉蠌蠋蠆蟼蠈蟿蠊蠂襢襚襛襗襡襜襘襝襙覈覷覶觶譐譈譊譀譓譖譔譋譕"],["f340","譑譂譒譗豃豷豶貚贆贇贉趬趪趭趫蹭蹸蹳蹪蹯蹻軂轒轑轏轐轓辴酀鄿醰醭鏞鏇鏏鏂鏚鏐鏹鏬鏌鏙鎩鏦鏊鏔鏮鏣鏕鏄鏎鏀鏒鏧镽闚闛雡霩霫霬霨霦"],["f3a1","鞳鞷鞶韝韞韟顜顙顝顗颿颽颻颾饈饇饃馦馧騚騕騥騝騤騛騢騠騧騣騞騜騔髂鬋鬊鬎鬌鬷鯪鯫鯠鯞鯤鯦鯢鯰鯔鯗鯬鯜鯙鯥鯕鯡鯚鵷鶁鶊鶄鶈鵱鶀鵸鶆鶋鶌鵽鵫鵴鵵鵰鵩鶅鵳鵻鶂鵯鵹鵿鶇鵨麔麑黀黼鼭齀齁齍齖齗齘匷嚲"],["f440","嚵嚳壣孅巆巇廮廯忀忁懹攗攖攕攓旟曨曣曤櫳櫰櫪櫨櫹櫱櫮櫯瀼瀵瀯瀷瀴瀱灂瀸瀿瀺瀹灀瀻瀳灁爓爔犨獽獼璺皫皪皾盭矌矎矏矍矲礥礣礧礨礤礩"],["f4a1","禲穮穬穭竷籉籈籊籇籅糮繻繾纁纀羺翿聹臛臙舋艨艩蘢藿蘁藾蘛蘀藶蘄蘉蘅蘌藽蠙蠐蠑蠗蠓蠖襣襦覹觷譠譪譝譨譣譥譧譭趮躆躈躄轙轖轗轕轘轚邍酃酁醷醵醲醳鐋鐓鏻鐠鐏鐔鏾鐕鐐鐨鐙鐍鏵鐀鏷鐇鐎鐖鐒鏺鐉鏸鐊鏿"],["f540","鏼鐌鏶鐑鐆闞闠闟霮霯鞹鞻韽韾顠顢顣顟飁飂饐饎饙饌饋饓騲騴騱騬騪騶騩騮騸騭髇髊髆鬐鬒鬑鰋鰈鯷鰅鰒鯸鱀鰇鰎鰆鰗鰔鰉鶟鶙鶤鶝鶒鶘鶐鶛"],["f5a1","鶠鶔鶜鶪鶗鶡鶚鶢鶨鶞鶣鶿鶩鶖鶦鶧麙麛麚黥黤黧黦鼰鼮齛齠齞齝齙龑儺儹劘劗囃嚽嚾孈孇巋巏廱懽攛欂櫼欃櫸欀灃灄灊灈灉灅灆爝爚爙獾甗癪矐礭礱礯籔籓糲纊纇纈纋纆纍罍羻耰臝蘘蘪蘦蘟蘣蘜蘙蘧蘮蘡蘠蘩蘞蘥"],["f640","蠩蠝蠛蠠蠤蠜蠫衊襭襩襮襫觺譹譸譅譺譻贐贔趯躎躌轞轛轝酆酄酅醹鐿鐻鐶鐩鐽鐼鐰鐹鐪鐷鐬鑀鐱闥闤闣霵霺鞿韡顤飉飆飀饘饖騹騽驆驄驂驁騺"],["f6a1","騿髍鬕鬗鬘鬖鬺魒鰫鰝鰜鰬鰣鰨鰩鰤鰡鶷鶶鶼鷁鷇鷊鷏鶾鷅鷃鶻鶵鷎鶹鶺鶬鷈鶱鶭鷌鶳鷍鶲鹺麜黫黮黭鼛鼘鼚鼱齎齥齤龒亹囆囅囋奱孋孌巕巑廲攡攠攦攢欋欈欉氍灕灖灗灒爞爟犩獿瓘瓕瓙瓗癭皭礵禴穰穱籗籜籙籛籚"],["f740","糴糱纑罏羇臞艫蘴蘵蘳蘬蘲蘶蠬蠨蠦蠪蠥襱覿覾觻譾讄讂讆讅譿贕躕躔躚躒躐躖躗轠轢酇鑌鑐鑊鑋鑏鑇鑅鑈鑉鑆霿韣顪顩飋饔饛驎驓驔驌驏驈驊"],["f7a1","驉驒驐髐鬙鬫鬻魖魕鱆鱈鰿鱄鰹鰳鱁鰼鰷鰴鰲鰽鰶鷛鷒鷞鷚鷋鷐鷜鷑鷟鷩鷙鷘鷖鷵鷕鷝麶黰鼵鼳鼲齂齫龕龢儽劙壨壧奲孍巘蠯彏戁戃戄攩攥斖曫欑欒欏毊灛灚爢玂玁玃癰矔籧籦纕艬蘺虀蘹蘼蘱蘻蘾蠰蠲蠮蠳襶襴襳觾"],["f840","讌讎讋讈豅贙躘轤轣醼鑢鑕鑝鑗鑞韄韅頀驖驙鬞鬟鬠鱒鱘鱐鱊鱍鱋鱕鱙鱌鱎鷻鷷鷯鷣鷫鷸鷤鷶鷡鷮鷦鷲鷰鷢鷬鷴鷳鷨鷭黂黐黲黳鼆鼜鼸鼷鼶齃齏"],["f8a1","齱齰齮齯囓囍孎屭攭曭曮欓灟灡灝灠爣瓛瓥矕礸禷禶籪纗羉艭虃蠸蠷蠵衋讔讕躞躟躠躝醾醽釂鑫鑨鑩雥靆靃靇韇韥驞髕魙鱣鱧鱦鱢鱞鱠鸂鷾鸇鸃鸆鸅鸀鸁鸉鷿鷽鸄麠鼞齆齴齵齶囔攮斸欘欙欗欚灢爦犪矘矙礹籩籫糶纚"],["f940","纘纛纙臠臡虆虇虈襹襺襼襻觿讘讙躥躤躣鑮鑭鑯鑱鑳靉顲饟鱨鱮鱭鸋鸍鸐鸏鸒鸑麡黵鼉齇齸齻齺齹圞灦籯蠼趲躦釃鑴鑸鑶鑵驠鱴鱳鱱鱵鸔鸓黶鼊"],["f9a1","龤灨灥糷虪蠾蠽蠿讞貜躩軉靋顳顴飌饡馫驤驦驧鬤鸕鸗齈戇欞爧虌躨钂钀钁驩驨鬮鸙爩虋讟钃鱹麷癵驫鱺鸝灩灪麤齾齉龘碁銹裏墻恒粧嫺╔╦╗╠╬╣╚╩╝╒╤╕╞╪╡╘╧╛╓╥╖╟╫╢╙╨╜║═╭╮╰╯▓"]]'); + +/***/ }), + +/***/ 4231: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('[["0","\\u0000",127],["8ea1","。",62],["a1a1"," 、。,.・:;?!゛゜´`¨^ ̄_ヽヾゝゞ〃仝々〆〇ー―‐/\~∥|…‥‘’“”()〔〕[]{}〈",9,"+-±×÷=≠<>≦≧∞∴♂♀°′″℃¥$¢£%#&*@§☆★○●◎◇"],["a2a1","◆□■△▲▽▼※〒→←↑↓〓"],["a2ba","∈∋⊆⊇⊂⊃∪∩"],["a2ca","∧∨¬⇒⇔∀∃"],["a2dc","∠⊥⌒∂∇≡≒≪≫√∽∝∵∫∬"],["a2f2","ʼn♯♭♪†‡¶"],["a2fe","◯"],["a3b0","0",9],["a3c1","A",25],["a3e1","a",25],["a4a1","ぁ",82],["a5a1","ァ",85],["a6a1","Α",16,"Σ",6],["a6c1","α",16,"σ",6],["a7a1","А",5,"ЁЖ",25],["a7d1","а",5,"ёж",25],["a8a1","─│┌┐┘└├┬┤┴┼━┃┏┓┛┗┣┳┫┻╋┠┯┨┷┿┝┰┥┸╂"],["ada1","①",19,"Ⅰ",9],["adc0","㍉㌔㌢㍍㌘㌧㌃㌶㍑㍗㌍㌦㌣㌫㍊㌻㎜㎝㎞㎎㎏㏄㎡"],["addf","㍻〝〟№㏍℡㊤",4,"㈱㈲㈹㍾㍽㍼≒≡∫∮∑√⊥∠∟⊿∵∩∪"],["b0a1","亜唖娃阿哀愛挨姶逢葵茜穐悪握渥旭葦芦鯵梓圧斡扱宛姐虻飴絢綾鮎或粟袷安庵按暗案闇鞍杏以伊位依偉囲夷委威尉惟意慰易椅為畏異移維緯胃萎衣謂違遺医井亥域育郁磯一壱溢逸稲茨芋鰯允印咽員因姻引飲淫胤蔭"],["b1a1","院陰隠韻吋右宇烏羽迂雨卯鵜窺丑碓臼渦嘘唄欝蔚鰻姥厩浦瓜閏噂云運雲荏餌叡営嬰影映曳栄永泳洩瑛盈穎頴英衛詠鋭液疫益駅悦謁越閲榎厭円園堰奄宴延怨掩援沿演炎焔煙燕猿縁艶苑薗遠鉛鴛塩於汚甥凹央奥往応"],["b2a1","押旺横欧殴王翁襖鴬鴎黄岡沖荻億屋憶臆桶牡乙俺卸恩温穏音下化仮何伽価佳加可嘉夏嫁家寡科暇果架歌河火珂禍禾稼箇花苛茄荷華菓蝦課嘩貨迦過霞蚊俄峨我牙画臥芽蛾賀雅餓駕介会解回塊壊廻快怪悔恢懐戒拐改"],["b3a1","魁晦械海灰界皆絵芥蟹開階貝凱劾外咳害崖慨概涯碍蓋街該鎧骸浬馨蛙垣柿蛎鈎劃嚇各廓拡撹格核殻獲確穫覚角赫較郭閣隔革学岳楽額顎掛笠樫橿梶鰍潟割喝恰括活渇滑葛褐轄且鰹叶椛樺鞄株兜竃蒲釜鎌噛鴨栢茅萱"],["b4a1","粥刈苅瓦乾侃冠寒刊勘勧巻喚堪姦完官寛干幹患感慣憾換敢柑桓棺款歓汗漢澗潅環甘監看竿管簡緩缶翰肝艦莞観諌貫還鑑間閑関陥韓館舘丸含岸巌玩癌眼岩翫贋雁頑顔願企伎危喜器基奇嬉寄岐希幾忌揮机旗既期棋棄"],["b5a1","機帰毅気汽畿祈季稀紀徽規記貴起軌輝飢騎鬼亀偽儀妓宜戯技擬欺犠疑祇義蟻誼議掬菊鞠吉吃喫桔橘詰砧杵黍却客脚虐逆丘久仇休及吸宮弓急救朽求汲泣灸球究窮笈級糾給旧牛去居巨拒拠挙渠虚許距鋸漁禦魚亨享京"],["b6a1","供侠僑兇競共凶協匡卿叫喬境峡強彊怯恐恭挟教橋況狂狭矯胸脅興蕎郷鏡響饗驚仰凝尭暁業局曲極玉桐粁僅勤均巾錦斤欣欽琴禁禽筋緊芹菌衿襟謹近金吟銀九倶句区狗玖矩苦躯駆駈駒具愚虞喰空偶寓遇隅串櫛釧屑屈"],["b7a1","掘窟沓靴轡窪熊隈粂栗繰桑鍬勲君薫訓群軍郡卦袈祁係傾刑兄啓圭珪型契形径恵慶慧憩掲携敬景桂渓畦稽系経継繋罫茎荊蛍計詣警軽頚鶏芸迎鯨劇戟撃激隙桁傑欠決潔穴結血訣月件倹倦健兼券剣喧圏堅嫌建憲懸拳捲"],["b8a1","検権牽犬献研硯絹県肩見謙賢軒遣鍵険顕験鹸元原厳幻弦減源玄現絃舷言諺限乎個古呼固姑孤己庫弧戸故枯湖狐糊袴股胡菰虎誇跨鈷雇顧鼓五互伍午呉吾娯後御悟梧檎瑚碁語誤護醐乞鯉交佼侯候倖光公功効勾厚口向"],["b9a1","后喉坑垢好孔孝宏工巧巷幸広庚康弘恒慌抗拘控攻昂晃更杭校梗構江洪浩港溝甲皇硬稿糠紅紘絞綱耕考肯肱腔膏航荒行衡講貢購郊酵鉱砿鋼閤降項香高鴻剛劫号合壕拷濠豪轟麹克刻告国穀酷鵠黒獄漉腰甑忽惚骨狛込"],["baa1","此頃今困坤墾婚恨懇昏昆根梱混痕紺艮魂些佐叉唆嵯左差査沙瑳砂詐鎖裟坐座挫債催再最哉塞妻宰彩才採栽歳済災采犀砕砦祭斎細菜裁載際剤在材罪財冴坂阪堺榊肴咲崎埼碕鷺作削咋搾昨朔柵窄策索錯桜鮭笹匙冊刷"],["bba1","察拶撮擦札殺薩雑皐鯖捌錆鮫皿晒三傘参山惨撒散桟燦珊産算纂蚕讃賛酸餐斬暫残仕仔伺使刺司史嗣四士始姉姿子屍市師志思指支孜斯施旨枝止死氏獅祉私糸紙紫肢脂至視詞詩試誌諮資賜雌飼歯事似侍児字寺慈持時"],["bca1","次滋治爾璽痔磁示而耳自蒔辞汐鹿式識鴫竺軸宍雫七叱執失嫉室悉湿漆疾質実蔀篠偲柴芝屡蕊縞舎写射捨赦斜煮社紗者謝車遮蛇邪借勺尺杓灼爵酌釈錫若寂弱惹主取守手朱殊狩珠種腫趣酒首儒受呪寿授樹綬需囚収周"],["bda1","宗就州修愁拾洲秀秋終繍習臭舟蒐衆襲讐蹴輯週酋酬集醜什住充十従戎柔汁渋獣縦重銃叔夙宿淑祝縮粛塾熟出術述俊峻春瞬竣舜駿准循旬楯殉淳準潤盾純巡遵醇順処初所暑曙渚庶緒署書薯藷諸助叙女序徐恕鋤除傷償"],["bea1","勝匠升召哨商唱嘗奨妾娼宵将小少尚庄床廠彰承抄招掌捷昇昌昭晶松梢樟樵沼消渉湘焼焦照症省硝礁祥称章笑粧紹肖菖蒋蕉衝裳訟証詔詳象賞醤鉦鍾鐘障鞘上丈丞乗冗剰城場壌嬢常情擾条杖浄状畳穣蒸譲醸錠嘱埴飾"],["bfa1","拭植殖燭織職色触食蝕辱尻伸信侵唇娠寝審心慎振新晋森榛浸深申疹真神秦紳臣芯薪親診身辛進針震人仁刃塵壬尋甚尽腎訊迅陣靭笥諏須酢図厨逗吹垂帥推水炊睡粋翠衰遂酔錐錘随瑞髄崇嵩数枢趨雛据杉椙菅頗雀裾"],["c0a1","澄摺寸世瀬畝是凄制勢姓征性成政整星晴棲栖正清牲生盛精聖声製西誠誓請逝醒青静斉税脆隻席惜戚斥昔析石積籍績脊責赤跡蹟碩切拙接摂折設窃節説雪絶舌蝉仙先千占宣専尖川戦扇撰栓栴泉浅洗染潜煎煽旋穿箭線"],["c1a1","繊羨腺舛船薦詮賎践選遷銭銑閃鮮前善漸然全禅繕膳糎噌塑岨措曾曽楚狙疏疎礎祖租粗素組蘇訴阻遡鼠僧創双叢倉喪壮奏爽宋層匝惣想捜掃挿掻操早曹巣槍槽漕燥争痩相窓糟総綜聡草荘葬蒼藻装走送遭鎗霜騒像増憎"],["c2a1","臓蔵贈造促側則即息捉束測足速俗属賊族続卒袖其揃存孫尊損村遜他多太汰詑唾堕妥惰打柁舵楕陀駄騨体堆対耐岱帯待怠態戴替泰滞胎腿苔袋貸退逮隊黛鯛代台大第醍題鷹滝瀧卓啄宅托択拓沢濯琢託鐸濁諾茸凧蛸只"],["c3a1","叩但達辰奪脱巽竪辿棚谷狸鱈樽誰丹単嘆坦担探旦歎淡湛炭短端箪綻耽胆蛋誕鍛団壇弾断暖檀段男談値知地弛恥智池痴稚置致蜘遅馳築畜竹筑蓄逐秩窒茶嫡着中仲宙忠抽昼柱注虫衷註酎鋳駐樗瀦猪苧著貯丁兆凋喋寵"],["c4a1","帖帳庁弔張彫徴懲挑暢朝潮牒町眺聴脹腸蝶調諜超跳銚長頂鳥勅捗直朕沈珍賃鎮陳津墜椎槌追鎚痛通塚栂掴槻佃漬柘辻蔦綴鍔椿潰坪壷嬬紬爪吊釣鶴亭低停偵剃貞呈堤定帝底庭廷弟悌抵挺提梯汀碇禎程締艇訂諦蹄逓"],["c5a1","邸鄭釘鼎泥摘擢敵滴的笛適鏑溺哲徹撤轍迭鉄典填天展店添纏甜貼転顛点伝殿澱田電兎吐堵塗妬屠徒斗杜渡登菟賭途都鍍砥砺努度土奴怒倒党冬凍刀唐塔塘套宕島嶋悼投搭東桃梼棟盗淘湯涛灯燈当痘祷等答筒糖統到"],["c6a1","董蕩藤討謄豆踏逃透鐙陶頭騰闘働動同堂導憧撞洞瞳童胴萄道銅峠鴇匿得徳涜特督禿篤毒独読栃橡凸突椴届鳶苫寅酉瀞噸屯惇敦沌豚遁頓呑曇鈍奈那内乍凪薙謎灘捺鍋楢馴縄畷南楠軟難汝二尼弐迩匂賑肉虹廿日乳入"],["c7a1","如尿韮任妊忍認濡禰祢寧葱猫熱年念捻撚燃粘乃廼之埜嚢悩濃納能脳膿農覗蚤巴把播覇杷波派琶破婆罵芭馬俳廃拝排敗杯盃牌背肺輩配倍培媒梅楳煤狽買売賠陪這蝿秤矧萩伯剥博拍柏泊白箔粕舶薄迫曝漠爆縛莫駁麦"],["c8a1","函箱硲箸肇筈櫨幡肌畑畠八鉢溌発醗髪伐罰抜筏閥鳩噺塙蛤隼伴判半反叛帆搬斑板氾汎版犯班畔繁般藩販範釆煩頒飯挽晩番盤磐蕃蛮匪卑否妃庇彼悲扉批披斐比泌疲皮碑秘緋罷肥被誹費避非飛樋簸備尾微枇毘琵眉美"],["c9a1","鼻柊稗匹疋髭彦膝菱肘弼必畢筆逼桧姫媛紐百謬俵彪標氷漂瓢票表評豹廟描病秒苗錨鋲蒜蛭鰭品彬斌浜瀕貧賓頻敏瓶不付埠夫婦富冨布府怖扶敷斧普浮父符腐膚芙譜負賦赴阜附侮撫武舞葡蕪部封楓風葺蕗伏副復幅服"],["caa1","福腹複覆淵弗払沸仏物鮒分吻噴墳憤扮焚奮粉糞紛雰文聞丙併兵塀幣平弊柄並蔽閉陛米頁僻壁癖碧別瞥蔑箆偏変片篇編辺返遍便勉娩弁鞭保舗鋪圃捕歩甫補輔穂募墓慕戊暮母簿菩倣俸包呆報奉宝峰峯崩庖抱捧放方朋"],["cba1","法泡烹砲縫胞芳萌蓬蜂褒訪豊邦鋒飽鳳鵬乏亡傍剖坊妨帽忘忙房暴望某棒冒紡肪膨謀貌貿鉾防吠頬北僕卜墨撲朴牧睦穆釦勃没殆堀幌奔本翻凡盆摩磨魔麻埋妹昧枚毎哩槙幕膜枕鮪柾鱒桝亦俣又抹末沫迄侭繭麿万慢満"],["cca1","漫蔓味未魅巳箕岬密蜜湊蓑稔脈妙粍民眠務夢無牟矛霧鵡椋婿娘冥名命明盟迷銘鳴姪牝滅免棉綿緬面麺摸模茂妄孟毛猛盲網耗蒙儲木黙目杢勿餅尤戻籾貰問悶紋門匁也冶夜爺耶野弥矢厄役約薬訳躍靖柳薮鑓愉愈油癒"],["cda1","諭輸唯佑優勇友宥幽悠憂揖有柚湧涌猶猷由祐裕誘遊邑郵雄融夕予余与誉輿預傭幼妖容庸揚揺擁曜楊様洋溶熔用窯羊耀葉蓉要謡踊遥陽養慾抑欲沃浴翌翼淀羅螺裸来莱頼雷洛絡落酪乱卵嵐欄濫藍蘭覧利吏履李梨理璃"],["cea1","痢裏裡里離陸律率立葎掠略劉流溜琉留硫粒隆竜龍侶慮旅虜了亮僚両凌寮料梁涼猟療瞭稜糧良諒遼量陵領力緑倫厘林淋燐琳臨輪隣鱗麟瑠塁涙累類令伶例冷励嶺怜玲礼苓鈴隷零霊麗齢暦歴列劣烈裂廉恋憐漣煉簾練聯"],["cfa1","蓮連錬呂魯櫓炉賂路露労婁廊弄朗楼榔浪漏牢狼篭老聾蝋郎六麓禄肋録論倭和話歪賄脇惑枠鷲亙亘鰐詫藁蕨椀湾碗腕"],["d0a1","弌丐丕个丱丶丼丿乂乖乘亂亅豫亊舒弍于亞亟亠亢亰亳亶从仍仄仆仂仗仞仭仟价伉佚估佛佝佗佇佶侈侏侘佻佩佰侑佯來侖儘俔俟俎俘俛俑俚俐俤俥倚倨倔倪倥倅伜俶倡倩倬俾俯們倆偃假會偕偐偈做偖偬偸傀傚傅傴傲"],["d1a1","僉僊傳僂僖僞僥僭僣僮價僵儉儁儂儖儕儔儚儡儺儷儼儻儿兀兒兌兔兢竸兩兪兮冀冂囘册冉冏冑冓冕冖冤冦冢冩冪冫决冱冲冰况冽凅凉凛几處凩凭凰凵凾刄刋刔刎刧刪刮刳刹剏剄剋剌剞剔剪剴剩剳剿剽劍劔劒剱劈劑辨"],["d2a1","辧劬劭劼劵勁勍勗勞勣勦飭勠勳勵勸勹匆匈甸匍匐匏匕匚匣匯匱匳匸區卆卅丗卉卍凖卞卩卮夘卻卷厂厖厠厦厥厮厰厶參簒雙叟曼燮叮叨叭叺吁吽呀听吭吼吮吶吩吝呎咏呵咎呟呱呷呰咒呻咀呶咄咐咆哇咢咸咥咬哄哈咨"],["d3a1","咫哂咤咾咼哘哥哦唏唔哽哮哭哺哢唹啀啣啌售啜啅啖啗唸唳啝喙喀咯喊喟啻啾喘喞單啼喃喩喇喨嗚嗅嗟嗄嗜嗤嗔嘔嗷嘖嗾嗽嘛嗹噎噐營嘴嘶嘲嘸噫噤嘯噬噪嚆嚀嚊嚠嚔嚏嚥嚮嚶嚴囂嚼囁囃囀囈囎囑囓囗囮囹圀囿圄圉"],["d4a1","圈國圍圓團圖嗇圜圦圷圸坎圻址坏坩埀垈坡坿垉垓垠垳垤垪垰埃埆埔埒埓堊埖埣堋堙堝塲堡塢塋塰毀塒堽塹墅墹墟墫墺壞墻墸墮壅壓壑壗壙壘壥壜壤壟壯壺壹壻壼壽夂夊夐夛梦夥夬夭夲夸夾竒奕奐奎奚奘奢奠奧奬奩"],["d5a1","奸妁妝佞侫妣妲姆姨姜妍姙姚娥娟娑娜娉娚婀婬婉娵娶婢婪媚媼媾嫋嫂媽嫣嫗嫦嫩嫖嫺嫻嬌嬋嬖嬲嫐嬪嬶嬾孃孅孀孑孕孚孛孥孩孰孳孵學斈孺宀它宦宸寃寇寉寔寐寤實寢寞寥寫寰寶寳尅將專對尓尠尢尨尸尹屁屆屎屓"],["d6a1","屐屏孱屬屮乢屶屹岌岑岔妛岫岻岶岼岷峅岾峇峙峩峽峺峭嶌峪崋崕崗嵜崟崛崑崔崢崚崙崘嵌嵒嵎嵋嵬嵳嵶嶇嶄嶂嶢嶝嶬嶮嶽嶐嶷嶼巉巍巓巒巖巛巫已巵帋帚帙帑帛帶帷幄幃幀幎幗幔幟幢幤幇幵并幺麼广庠廁廂廈廐廏"],["d7a1","廖廣廝廚廛廢廡廨廩廬廱廳廰廴廸廾弃弉彝彜弋弑弖弩弭弸彁彈彌彎弯彑彖彗彙彡彭彳彷徃徂彿徊很徑徇從徙徘徠徨徭徼忖忻忤忸忱忝悳忿怡恠怙怐怩怎怱怛怕怫怦怏怺恚恁恪恷恟恊恆恍恣恃恤恂恬恫恙悁悍惧悃悚"],["d8a1","悄悛悖悗悒悧悋惡悸惠惓悴忰悽惆悵惘慍愕愆惶惷愀惴惺愃愡惻惱愍愎慇愾愨愧慊愿愼愬愴愽慂慄慳慷慘慙慚慫慴慯慥慱慟慝慓慵憙憖憇憬憔憚憊憑憫憮懌懊應懷懈懃懆憺懋罹懍懦懣懶懺懴懿懽懼懾戀戈戉戍戌戔戛"],["d9a1","戞戡截戮戰戲戳扁扎扞扣扛扠扨扼抂抉找抒抓抖拔抃抔拗拑抻拏拿拆擔拈拜拌拊拂拇抛拉挌拮拱挧挂挈拯拵捐挾捍搜捏掖掎掀掫捶掣掏掉掟掵捫捩掾揩揀揆揣揉插揶揄搖搴搆搓搦搶攝搗搨搏摧摯摶摎攪撕撓撥撩撈撼"],["daa1","據擒擅擇撻擘擂擱擧舉擠擡抬擣擯攬擶擴擲擺攀擽攘攜攅攤攣攫攴攵攷收攸畋效敖敕敍敘敞敝敲數斂斃變斛斟斫斷旃旆旁旄旌旒旛旙无旡旱杲昊昃旻杳昵昶昴昜晏晄晉晁晞晝晤晧晨晟晢晰暃暈暎暉暄暘暝曁暹曉暾暼"],["dba1","曄暸曖曚曠昿曦曩曰曵曷朏朖朞朦朧霸朮朿朶杁朸朷杆杞杠杙杣杤枉杰枩杼杪枌枋枦枡枅枷柯枴柬枳柩枸柤柞柝柢柮枹柎柆柧檜栞框栩桀桍栲桎梳栫桙档桷桿梟梏梭梔條梛梃檮梹桴梵梠梺椏梍桾椁棊椈棘椢椦棡椌棍"],["dca1","棔棧棕椶椒椄棗棣椥棹棠棯椨椪椚椣椡棆楹楷楜楸楫楔楾楮椹楴椽楙椰楡楞楝榁楪榲榮槐榿槁槓榾槎寨槊槝榻槃榧樮榑榠榜榕榴槞槨樂樛槿權槹槲槧樅榱樞槭樔槫樊樒櫁樣樓橄樌橲樶橸橇橢橙橦橈樸樢檐檍檠檄檢檣"],["dda1","檗蘗檻櫃櫂檸檳檬櫞櫑櫟檪櫚櫪櫻欅蘖櫺欒欖鬱欟欸欷盜欹飮歇歃歉歐歙歔歛歟歡歸歹歿殀殄殃殍殘殕殞殤殪殫殯殲殱殳殷殼毆毋毓毟毬毫毳毯麾氈氓气氛氤氣汞汕汢汪沂沍沚沁沛汾汨汳沒沐泄泱泓沽泗泅泝沮沱沾"],["dea1","沺泛泯泙泪洟衍洶洫洽洸洙洵洳洒洌浣涓浤浚浹浙涎涕濤涅淹渕渊涵淇淦涸淆淬淞淌淨淒淅淺淙淤淕淪淮渭湮渮渙湲湟渾渣湫渫湶湍渟湃渺湎渤滿渝游溂溪溘滉溷滓溽溯滄溲滔滕溏溥滂溟潁漑灌滬滸滾漿滲漱滯漲滌"],["dfa1","漾漓滷澆潺潸澁澀潯潛濳潭澂潼潘澎澑濂潦澳澣澡澤澹濆澪濟濕濬濔濘濱濮濛瀉瀋濺瀑瀁瀏濾瀛瀚潴瀝瀘瀟瀰瀾瀲灑灣炙炒炯烱炬炸炳炮烟烋烝烙焉烽焜焙煥煕熈煦煢煌煖煬熏燻熄熕熨熬燗熹熾燒燉燔燎燠燬燧燵燼"],["e0a1","燹燿爍爐爛爨爭爬爰爲爻爼爿牀牆牋牘牴牾犂犁犇犒犖犢犧犹犲狃狆狄狎狒狢狠狡狹狷倏猗猊猜猖猝猴猯猩猥猾獎獏默獗獪獨獰獸獵獻獺珈玳珎玻珀珥珮珞璢琅瑯琥珸琲琺瑕琿瑟瑙瑁瑜瑩瑰瑣瑪瑶瑾璋璞璧瓊瓏瓔珱"],["e1a1","瓠瓣瓧瓩瓮瓲瓰瓱瓸瓷甄甃甅甌甎甍甕甓甞甦甬甼畄畍畊畉畛畆畚畩畤畧畫畭畸當疆疇畴疊疉疂疔疚疝疥疣痂疳痃疵疽疸疼疱痍痊痒痙痣痞痾痿痼瘁痰痺痲痳瘋瘍瘉瘟瘧瘠瘡瘢瘤瘴瘰瘻癇癈癆癜癘癡癢癨癩癪癧癬癰"],["e2a1","癲癶癸發皀皃皈皋皎皖皓皙皚皰皴皸皹皺盂盍盖盒盞盡盥盧盪蘯盻眈眇眄眩眤眞眥眦眛眷眸睇睚睨睫睛睥睿睾睹瞎瞋瞑瞠瞞瞰瞶瞹瞿瞼瞽瞻矇矍矗矚矜矣矮矼砌砒礦砠礪硅碎硴碆硼碚碌碣碵碪碯磑磆磋磔碾碼磅磊磬"],["e3a1","磧磚磽磴礇礒礑礙礬礫祀祠祗祟祚祕祓祺祿禊禝禧齋禪禮禳禹禺秉秕秧秬秡秣稈稍稘稙稠稟禀稱稻稾稷穃穗穉穡穢穩龝穰穹穽窈窗窕窘窖窩竈窰窶竅竄窿邃竇竊竍竏竕竓站竚竝竡竢竦竭竰笂笏笊笆笳笘笙笞笵笨笶筐"],["e4a1","筺笄筍笋筌筅筵筥筴筧筰筱筬筮箝箘箟箍箜箚箋箒箏筝箙篋篁篌篏箴篆篝篩簑簔篦篥籠簀簇簓篳篷簗簍篶簣簧簪簟簷簫簽籌籃籔籏籀籐籘籟籤籖籥籬籵粃粐粤粭粢粫粡粨粳粲粱粮粹粽糀糅糂糘糒糜糢鬻糯糲糴糶糺紆"],["e5a1","紂紜紕紊絅絋紮紲紿紵絆絳絖絎絲絨絮絏絣經綉絛綏絽綛綺綮綣綵緇綽綫總綢綯緜綸綟綰緘緝緤緞緻緲緡縅縊縣縡縒縱縟縉縋縢繆繦縻縵縹繃縷縲縺繧繝繖繞繙繚繹繪繩繼繻纃緕繽辮繿纈纉續纒纐纓纔纖纎纛纜缸缺"],["e6a1","罅罌罍罎罐网罕罔罘罟罠罨罩罧罸羂羆羃羈羇羌羔羞羝羚羣羯羲羹羮羶羸譱翅翆翊翕翔翡翦翩翳翹飜耆耄耋耒耘耙耜耡耨耿耻聊聆聒聘聚聟聢聨聳聲聰聶聹聽聿肄肆肅肛肓肚肭冐肬胛胥胙胝胄胚胖脉胯胱脛脩脣脯腋"],["e7a1","隋腆脾腓腑胼腱腮腥腦腴膃膈膊膀膂膠膕膤膣腟膓膩膰膵膾膸膽臀臂膺臉臍臑臙臘臈臚臟臠臧臺臻臾舁舂舅與舊舍舐舖舩舫舸舳艀艙艘艝艚艟艤艢艨艪艫舮艱艷艸艾芍芒芫芟芻芬苡苣苟苒苴苳苺莓范苻苹苞茆苜茉苙"],["e8a1","茵茴茖茲茱荀茹荐荅茯茫茗茘莅莚莪莟莢莖茣莎莇莊荼莵荳荵莠莉莨菴萓菫菎菽萃菘萋菁菷萇菠菲萍萢萠莽萸蔆菻葭萪萼蕚蒄葷葫蒭葮蒂葩葆萬葯葹萵蓊葢蒹蒿蒟蓙蓍蒻蓚蓐蓁蓆蓖蒡蔡蓿蓴蔗蔘蔬蔟蔕蔔蓼蕀蕣蕘蕈"],["e9a1","蕁蘂蕋蕕薀薤薈薑薊薨蕭薔薛藪薇薜蕷蕾薐藉薺藏薹藐藕藝藥藜藹蘊蘓蘋藾藺蘆蘢蘚蘰蘿虍乕虔號虧虱蚓蚣蚩蚪蚋蚌蚶蚯蛄蛆蚰蛉蠣蚫蛔蛞蛩蛬蛟蛛蛯蜒蜆蜈蜀蜃蛻蜑蜉蜍蛹蜊蜴蜿蜷蜻蜥蜩蜚蝠蝟蝸蝌蝎蝴蝗蝨蝮蝙"],["eaa1","蝓蝣蝪蠅螢螟螂螯蟋螽蟀蟐雖螫蟄螳蟇蟆螻蟯蟲蟠蠏蠍蟾蟶蟷蠎蟒蠑蠖蠕蠢蠡蠱蠶蠹蠧蠻衄衂衒衙衞衢衫袁衾袞衵衽袵衲袂袗袒袮袙袢袍袤袰袿袱裃裄裔裘裙裝裹褂裼裴裨裲褄褌褊褓襃褞褥褪褫襁襄褻褶褸襌褝襠襞"],["eba1","襦襤襭襪襯襴襷襾覃覈覊覓覘覡覩覦覬覯覲覺覽覿觀觚觜觝觧觴觸訃訖訐訌訛訝訥訶詁詛詒詆詈詼詭詬詢誅誂誄誨誡誑誥誦誚誣諄諍諂諚諫諳諧諤諱謔諠諢諷諞諛謌謇謚諡謖謐謗謠謳鞫謦謫謾謨譁譌譏譎證譖譛譚譫"],["eca1","譟譬譯譴譽讀讌讎讒讓讖讙讚谺豁谿豈豌豎豐豕豢豬豸豺貂貉貅貊貍貎貔豼貘戝貭貪貽貲貳貮貶賈賁賤賣賚賽賺賻贄贅贊贇贏贍贐齎贓賍贔贖赧赭赱赳趁趙跂趾趺跏跚跖跌跛跋跪跫跟跣跼踈踉跿踝踞踐踟蹂踵踰踴蹊"],["eda1","蹇蹉蹌蹐蹈蹙蹤蹠踪蹣蹕蹶蹲蹼躁躇躅躄躋躊躓躑躔躙躪躡躬躰軆躱躾軅軈軋軛軣軼軻軫軾輊輅輕輒輙輓輜輟輛輌輦輳輻輹轅轂輾轌轉轆轎轗轜轢轣轤辜辟辣辭辯辷迚迥迢迪迯邇迴逅迹迺逑逕逡逍逞逖逋逧逶逵逹迸"],["eea1","遏遐遑遒逎遉逾遖遘遞遨遯遶隨遲邂遽邁邀邊邉邏邨邯邱邵郢郤扈郛鄂鄒鄙鄲鄰酊酖酘酣酥酩酳酲醋醉醂醢醫醯醪醵醴醺釀釁釉釋釐釖釟釡釛釼釵釶鈞釿鈔鈬鈕鈑鉞鉗鉅鉉鉤鉈銕鈿鉋鉐銜銖銓銛鉚鋏銹銷鋩錏鋺鍄錮"],["efa1","錙錢錚錣錺錵錻鍜鍠鍼鍮鍖鎰鎬鎭鎔鎹鏖鏗鏨鏥鏘鏃鏝鏐鏈鏤鐚鐔鐓鐃鐇鐐鐶鐫鐵鐡鐺鑁鑒鑄鑛鑠鑢鑞鑪鈩鑰鑵鑷鑽鑚鑼鑾钁鑿閂閇閊閔閖閘閙閠閨閧閭閼閻閹閾闊濶闃闍闌闕闔闖關闡闥闢阡阨阮阯陂陌陏陋陷陜陞"],["f0a1","陝陟陦陲陬隍隘隕隗險隧隱隲隰隴隶隸隹雎雋雉雍襍雜霍雕雹霄霆霈霓霎霑霏霖霙霤霪霰霹霽霾靄靆靈靂靉靜靠靤靦靨勒靫靱靹鞅靼鞁靺鞆鞋鞏鞐鞜鞨鞦鞣鞳鞴韃韆韈韋韜韭齏韲竟韶韵頏頌頸頤頡頷頽顆顏顋顫顯顰"],["f1a1","顱顴顳颪颯颱颶飄飃飆飩飫餃餉餒餔餘餡餝餞餤餠餬餮餽餾饂饉饅饐饋饑饒饌饕馗馘馥馭馮馼駟駛駝駘駑駭駮駱駲駻駸騁騏騅駢騙騫騷驅驂驀驃騾驕驍驛驗驟驢驥驤驩驫驪骭骰骼髀髏髑髓體髞髟髢髣髦髯髫髮髴髱髷"],["f2a1","髻鬆鬘鬚鬟鬢鬣鬥鬧鬨鬩鬪鬮鬯鬲魄魃魏魍魎魑魘魴鮓鮃鮑鮖鮗鮟鮠鮨鮴鯀鯊鮹鯆鯏鯑鯒鯣鯢鯤鯔鯡鰺鯲鯱鯰鰕鰔鰉鰓鰌鰆鰈鰒鰊鰄鰮鰛鰥鰤鰡鰰鱇鰲鱆鰾鱚鱠鱧鱶鱸鳧鳬鳰鴉鴈鳫鴃鴆鴪鴦鶯鴣鴟鵄鴕鴒鵁鴿鴾鵆鵈"],["f3a1","鵝鵞鵤鵑鵐鵙鵲鶉鶇鶫鵯鵺鶚鶤鶩鶲鷄鷁鶻鶸鶺鷆鷏鷂鷙鷓鷸鷦鷭鷯鷽鸚鸛鸞鹵鹹鹽麁麈麋麌麒麕麑麝麥麩麸麪麭靡黌黎黏黐黔黜點黝黠黥黨黯黴黶黷黹黻黼黽鼇鼈皷鼕鼡鼬鼾齊齒齔齣齟齠齡齦齧齬齪齷齲齶龕龜龠"],["f4a1","堯槇遙瑤凜熙"],["f9a1","纊褜鍈銈蓜俉炻昱棈鋹曻彅丨仡仼伀伃伹佖侒侊侚侔俍偀倢俿倞偆偰偂傔僴僘兊兤冝冾凬刕劜劦勀勛匀匇匤卲厓厲叝﨎咜咊咩哿喆坙坥垬埈埇﨏塚增墲夋奓奛奝奣妤妺孖寀甯寘寬尞岦岺峵崧嵓﨑嵂嵭嶸嶹巐弡弴彧德"],["faa1","忞恝悅悊惞惕愠惲愑愷愰憘戓抦揵摠撝擎敎昀昕昻昉昮昞昤晥晗晙晴晳暙暠暲暿曺朎朗杦枻桒柀栁桄棏﨓楨﨔榘槢樰橫橆橳橾櫢櫤毖氿汜沆汯泚洄涇浯涖涬淏淸淲淼渹湜渧渼溿澈澵濵瀅瀇瀨炅炫焏焄煜煆煇凞燁燾犱"],["fba1","犾猤猪獷玽珉珖珣珒琇珵琦琪琩琮瑢璉璟甁畯皂皜皞皛皦益睆劯砡硎硤硺礰礼神祥禔福禛竑竧靖竫箞精絈絜綷綠緖繒罇羡羽茁荢荿菇菶葈蒴蕓蕙蕫﨟薰蘒﨡蠇裵訒訷詹誧誾諟諸諶譓譿賰賴贒赶﨣軏﨤逸遧郞都鄕鄧釚"],["fca1","釗釞釭釮釤釥鈆鈐鈊鈺鉀鈼鉎鉙鉑鈹鉧銧鉷鉸鋧鋗鋙鋐﨧鋕鋠鋓錥錡鋻﨨錞鋿錝錂鍰鍗鎤鏆鏞鏸鐱鑅鑈閒隆﨩隝隯霳霻靃靍靏靑靕顗顥飯飼餧館馞驎髙髜魵魲鮏鮱鮻鰀鵰鵫鶴鸙黑"],["fcf1","ⅰ",9,"¬¦'""],["8fa2af","˘ˇ¸˙˝¯˛˚~΄΅"],["8fa2c2","¡¦¿"],["8fa2eb","ºª©®™¤№"],["8fa6e1","ΆΈΉΊΪ"],["8fa6e7","Ό"],["8fa6e9","ΎΫ"],["8fa6ec","Ώ"],["8fa6f1","άέήίϊΐόςύϋΰώ"],["8fa7c2","Ђ",10,"ЎЏ"],["8fa7f2","ђ",10,"ўџ"],["8fa9a1","ÆĐ"],["8fa9a4","Ħ"],["8fa9a6","IJ"],["8fa9a8","ŁĿ"],["8fa9ab","ŊØŒ"],["8fa9af","ŦÞ"],["8fa9c1","æđðħıijĸłŀʼnŋøœßŧþ"],["8faaa1","ÁÀÄÂĂǍĀĄÅÃĆĈČÇĊĎÉÈËÊĚĖĒĘ"],["8faaba","ĜĞĢĠĤÍÌÏÎǏİĪĮĨĴĶĹĽĻŃŇŅÑÓÒÖÔǑŐŌÕŔŘŖŚŜŠŞŤŢÚÙÜÛŬǓŰŪŲŮŨǗǛǙǕŴÝŸŶŹŽŻ"],["8faba1","áàäâăǎāąåãćĉčçċďéèëêěėēęǵĝğ"],["8fabbd","ġĥíìïîǐ"],["8fabc5","īįĩĵķĺľļńňņñóòöôǒőōõŕřŗśŝšşťţúùüûŭǔűūųůũǘǜǚǖŵýÿŷźžż"],["8fb0a1","丂丄丅丌丒丟丣两丨丫丮丯丰丵乀乁乄乇乑乚乜乣乨乩乴乵乹乿亍亖亗亝亯亹仃仐仚仛仠仡仢仨仯仱仳仵份仾仿伀伂伃伈伋伌伒伕伖众伙伮伱你伳伵伷伹伻伾佀佂佈佉佋佌佒佔佖佘佟佣佪佬佮佱佷佸佹佺佽佾侁侂侄"],["8fb1a1","侅侉侊侌侎侐侒侓侔侗侙侚侞侟侲侷侹侻侼侽侾俀俁俅俆俈俉俋俌俍俏俒俜俠俢俰俲俼俽俿倀倁倄倇倊倌倎倐倓倗倘倛倜倝倞倢倧倮倰倲倳倵偀偁偂偅偆偊偌偎偑偒偓偗偙偟偠偢偣偦偧偪偭偰偱倻傁傃傄傆傊傎傏傐"],["8fb2a1","傒傓傔傖傛傜傞",4,"傪傯傰傹傺傽僀僃僄僇僌僎僐僓僔僘僜僝僟僢僤僦僨僩僯僱僶僺僾儃儆儇儈儋儌儍儎僲儐儗儙儛儜儝儞儣儧儨儬儭儯儱儳儴儵儸儹兂兊兏兓兕兗兘兟兤兦兾冃冄冋冎冘冝冡冣冭冸冺冼冾冿凂"],["8fb3a1","凈减凑凒凓凕凘凞凢凥凮凲凳凴凷刁刂刅划刓刕刖刘刢刨刱刲刵刼剅剉剕剗剘剚剜剟剠剡剦剮剷剸剹劀劂劅劊劌劓劕劖劗劘劚劜劤劥劦劧劯劰劶劷劸劺劻劽勀勄勆勈勌勏勑勔勖勛勜勡勥勨勩勪勬勰勱勴勶勷匀匃匊匋"],["8fb4a1","匌匑匓匘匛匜匞匟匥匧匨匩匫匬匭匰匲匵匼匽匾卂卌卋卙卛卡卣卥卬卭卲卹卾厃厇厈厎厓厔厙厝厡厤厪厫厯厲厴厵厷厸厺厽叀叅叏叒叓叕叚叝叞叠另叧叵吂吓吚吡吧吨吪启吱吴吵呃呄呇呍呏呞呢呤呦呧呩呫呭呮呴呿"],["8fb5a1","咁咃咅咈咉咍咑咕咖咜咟咡咦咧咩咪咭咮咱咷咹咺咻咿哆哊响哎哠哪哬哯哶哼哾哿唀唁唅唈唉唌唍唎唕唪唫唲唵唶唻唼唽啁啇啉啊啍啐啑啘啚啛啞啠啡啤啦啿喁喂喆喈喎喏喑喒喓喔喗喣喤喭喲喿嗁嗃嗆嗉嗋嗌嗎嗑嗒"],["8fb6a1","嗓嗗嗘嗛嗞嗢嗩嗶嗿嘅嘈嘊嘍",5,"嘙嘬嘰嘳嘵嘷嘹嘻嘼嘽嘿噀噁噃噄噆噉噋噍噏噔噞噠噡噢噣噦噩噭噯噱噲噵嚄嚅嚈嚋嚌嚕嚙嚚嚝嚞嚟嚦嚧嚨嚩嚫嚬嚭嚱嚳嚷嚾囅囉囊囋囏囐囌囍囙囜囝囟囡囤",4,"囱囫园"],["8fb7a1","囶囷圁圂圇圊圌圑圕圚圛圝圠圢圣圤圥圩圪圬圮圯圳圴圽圾圿坅坆坌坍坒坢坥坧坨坫坭",4,"坳坴坵坷坹坺坻坼坾垁垃垌垔垗垙垚垜垝垞垟垡垕垧垨垩垬垸垽埇埈埌埏埕埝埞埤埦埧埩埭埰埵埶埸埽埾埿堃堄堈堉埡"],["8fb8a1","堌堍堛堞堟堠堦堧堭堲堹堿塉塌塍塏塐塕塟塡塤塧塨塸塼塿墀墁墇墈墉墊墌墍墏墐墔墖墝墠墡墢墦墩墱墲壄墼壂壈壍壎壐壒壔壖壚壝壡壢壩壳夅夆夋夌夒夓夔虁夝夡夣夤夨夯夰夳夵夶夿奃奆奒奓奙奛奝奞奟奡奣奫奭"],["8fb9a1","奯奲奵奶她奻奼妋妌妎妒妕妗妟妤妧妭妮妯妰妳妷妺妼姁姃姄姈姊姍姒姝姞姟姣姤姧姮姯姱姲姴姷娀娄娌娍娎娒娓娞娣娤娧娨娪娭娰婄婅婇婈婌婐婕婞婣婥婧婭婷婺婻婾媋媐媓媖媙媜媞媟媠媢媧媬媱媲媳媵媸媺媻媿"],["8fbaa1","嫄嫆嫈嫏嫚嫜嫠嫥嫪嫮嫵嫶嫽嬀嬁嬈嬗嬴嬙嬛嬝嬡嬥嬭嬸孁孋孌孒孖孞孨孮孯孼孽孾孿宁宄宆宊宎宐宑宓宔宖宨宩宬宭宯宱宲宷宺宼寀寁寍寏寖",4,"寠寯寱寴寽尌尗尞尟尣尦尩尫尬尮尰尲尵尶屙屚屜屢屣屧屨屩"],["8fbba1","屭屰屴屵屺屻屼屽岇岈岊岏岒岝岟岠岢岣岦岪岲岴岵岺峉峋峒峝峗峮峱峲峴崁崆崍崒崫崣崤崦崧崱崴崹崽崿嵂嵃嵆嵈嵕嵑嵙嵊嵟嵠嵡嵢嵤嵪嵭嵰嵹嵺嵾嵿嶁嶃嶈嶊嶒嶓嶔嶕嶙嶛嶟嶠嶧嶫嶰嶴嶸嶹巃巇巋巐巎巘巙巠巤"],["8fbca1","巩巸巹帀帇帍帒帔帕帘帟帠帮帨帲帵帾幋幐幉幑幖幘幛幜幞幨幪",4,"幰庀庋庎庢庤庥庨庪庬庱庳庽庾庿廆廌廋廎廑廒廔廕廜廞廥廫异弆弇弈弎弙弜弝弡弢弣弤弨弫弬弮弰弴弶弻弽弿彀彄彅彇彍彐彔彘彛彠彣彤彧"],["8fbda1","彯彲彴彵彸彺彽彾徉徍徏徖徜徝徢徧徫徤徬徯徰徱徸忄忇忈忉忋忐",4,"忞忡忢忨忩忪忬忭忮忯忲忳忶忺忼怇怊怍怓怔怗怘怚怟怤怭怳怵恀恇恈恉恌恑恔恖恗恝恡恧恱恾恿悂悆悈悊悎悑悓悕悘悝悞悢悤悥您悰悱悷"],["8fbea1","悻悾惂惄惈惉惊惋惎惏惔惕惙惛惝惞惢惥惲惵惸惼惽愂愇愊愌愐",4,"愖愗愙愜愞愢愪愫愰愱愵愶愷愹慁慅慆慉慞慠慬慲慸慻慼慿憀憁憃憄憋憍憒憓憗憘憜憝憟憠憥憨憪憭憸憹憼懀懁懂懎懏懕懜懝懞懟懡懢懧懩懥"],["8fbfa1","懬懭懯戁戃戄戇戓戕戜戠戢戣戧戩戫戹戽扂扃扄扆扌扐扑扒扔扖扚扜扤扭扯扳扺扽抍抎抏抐抦抨抳抶抷抺抾抿拄拎拕拖拚拪拲拴拼拽挃挄挊挋挍挐挓挖挘挩挪挭挵挶挹挼捁捂捃捄捆捊捋捎捒捓捔捘捛捥捦捬捭捱捴捵"],["8fc0a1","捸捼捽捿掂掄掇掊掐掔掕掙掚掞掤掦掭掮掯掽揁揅揈揎揑揓揔揕揜揠揥揪揬揲揳揵揸揹搉搊搐搒搔搘搞搠搢搤搥搩搪搯搰搵搽搿摋摏摑摒摓摔摚摛摜摝摟摠摡摣摭摳摴摻摽撅撇撏撐撑撘撙撛撝撟撡撣撦撨撬撳撽撾撿"],["8fc1a1","擄擉擊擋擌擎擐擑擕擗擤擥擩擪擭擰擵擷擻擿攁攄攈攉攊攏攓攔攖攙攛攞攟攢攦攩攮攱攺攼攽敃敇敉敐敒敔敟敠敧敫敺敽斁斅斊斒斕斘斝斠斣斦斮斲斳斴斿旂旈旉旎旐旔旖旘旟旰旲旴旵旹旾旿昀昄昈昉昍昑昒昕昖昝"],["8fc2a1","昞昡昢昣昤昦昩昪昫昬昮昰昱昳昹昷晀晅晆晊晌晑晎晗晘晙晛晜晠晡曻晪晫晬晾晳晵晿晷晸晹晻暀晼暋暌暍暐暒暙暚暛暜暟暠暤暭暱暲暵暻暿曀曂曃曈曌曎曏曔曛曟曨曫曬曮曺朅朇朎朓朙朜朠朢朳朾杅杇杈杌杔杕杝"],["8fc3a1","杦杬杮杴杶杻极构枎枏枑枓枖枘枙枛枰枱枲枵枻枼枽柹柀柂柃柅柈柉柒柗柙柜柡柦柰柲柶柷桒栔栙栝栟栨栧栬栭栯栰栱栳栻栿桄桅桊桌桕桗桘桛桫桮",4,"桵桹桺桻桼梂梄梆梈梖梘梚梜梡梣梥梩梪梮梲梻棅棈棌棏"],["8fc4a1","棐棑棓棖棙棜棝棥棨棪棫棬棭棰棱棵棶棻棼棽椆椉椊椐椑椓椖椗椱椳椵椸椻楂楅楉楎楗楛楣楤楥楦楨楩楬楰楱楲楺楻楿榀榍榒榖榘榡榥榦榨榫榭榯榷榸榺榼槅槈槑槖槗槢槥槮槯槱槳槵槾樀樁樃樏樑樕樚樝樠樤樨樰樲"],["8fc5a1","樴樷樻樾樿橅橆橉橊橎橐橑橒橕橖橛橤橧橪橱橳橾檁檃檆檇檉檋檑檛檝檞檟檥檫檯檰檱檴檽檾檿櫆櫉櫈櫌櫐櫔櫕櫖櫜櫝櫤櫧櫬櫰櫱櫲櫼櫽欂欃欆欇欉欏欐欑欗欛欞欤欨欫欬欯欵欶欻欿歆歊歍歒歖歘歝歠歧歫歮歰歵歽"],["8fc6a1","歾殂殅殗殛殟殠殢殣殨殩殬殭殮殰殸殹殽殾毃毄毉毌毖毚毡毣毦毧毮毱毷毹毿氂氄氅氉氍氎氐氒氙氟氦氧氨氬氮氳氵氶氺氻氿汊汋汍汏汒汔汙汛汜汫汭汯汴汶汸汹汻沅沆沇沉沔沕沗沘沜沟沰沲沴泂泆泍泏泐泑泒泔泖"],["8fc7a1","泚泜泠泧泩泫泬泮泲泴洄洇洊洎洏洑洓洚洦洧洨汧洮洯洱洹洼洿浗浞浟浡浥浧浯浰浼涂涇涑涒涔涖涗涘涪涬涴涷涹涽涿淄淈淊淎淏淖淛淝淟淠淢淥淩淯淰淴淶淼渀渄渞渢渧渲渶渹渻渼湄湅湈湉湋湏湑湒湓湔湗湜湝湞"],["8fc8a1","湢湣湨湳湻湽溍溓溙溠溧溭溮溱溳溻溿滀滁滃滇滈滊滍滎滏滫滭滮滹滻滽漄漈漊漌漍漖漘漚漛漦漩漪漯漰漳漶漻漼漭潏潑潒潓潗潙潚潝潞潡潢潨潬潽潾澃澇澈澋澌澍澐澒澓澔澖澚澟澠澥澦澧澨澮澯澰澵澶澼濅濇濈濊"],["8fc9a1","濚濞濨濩濰濵濹濼濽瀀瀅瀆瀇瀍瀗瀠瀣瀯瀴瀷瀹瀼灃灄灈灉灊灋灔灕灝灞灎灤灥灬灮灵灶灾炁炅炆炔",4,"炛炤炫炰炱炴炷烊烑烓烔烕烖烘烜烤烺焃",4,"焋焌焏焞焠焫焭焯焰焱焸煁煅煆煇煊煋煐煒煗煚煜煞煠"],["8fcaa1","煨煹熀熅熇熌熒熚熛熠熢熯熰熲熳熺熿燀燁燄燋燌燓燖燙燚燜燸燾爀爇爈爉爓爗爚爝爟爤爫爯爴爸爹牁牂牃牅牎牏牐牓牕牖牚牜牞牠牣牨牫牮牯牱牷牸牻牼牿犄犉犍犎犓犛犨犭犮犱犴犾狁狇狉狌狕狖狘狟狥狳狴狺狻"],["8fcba1","狾猂猄猅猇猋猍猒猓猘猙猞猢猤猧猨猬猱猲猵猺猻猽獃獍獐獒獖獘獝獞獟獠獦獧獩獫獬獮獯獱獷獹獼玀玁玃玅玆玎玐玓玕玗玘玜玞玟玠玢玥玦玪玫玭玵玷玹玼玽玿珅珆珉珋珌珏珒珓珖珙珝珡珣珦珧珩珴珵珷珹珺珻珽"],["8fcca1","珿琀琁琄琇琊琑琚琛琤琦琨",9,"琹瑀瑃瑄瑆瑇瑋瑍瑑瑒瑗瑝瑢瑦瑧瑨瑫瑭瑮瑱瑲璀璁璅璆璇璉璏璐璑璒璘璙璚璜璟璠璡璣璦璨璩璪璫璮璯璱璲璵璹璻璿瓈瓉瓌瓐瓓瓘瓚瓛瓞瓟瓤瓨瓪瓫瓯瓴瓺瓻瓼瓿甆"],["8fcda1","甒甖甗甠甡甤甧甩甪甯甶甹甽甾甿畀畃畇畈畎畐畒畗畞畟畡畯畱畹",5,"疁疅疐疒疓疕疙疜疢疤疴疺疿痀痁痄痆痌痎痏痗痜痟痠痡痤痧痬痮痯痱痹瘀瘂瘃瘄瘇瘈瘊瘌瘏瘒瘓瘕瘖瘙瘛瘜瘝瘞瘣瘥瘦瘩瘭瘲瘳瘵瘸瘹"],["8fcea1","瘺瘼癊癀癁癃癄癅癉癋癕癙癟癤癥癭癮癯癱癴皁皅皌皍皕皛皜皝皟皠皢",6,"皪皭皽盁盅盉盋盌盎盔盙盠盦盨盬盰盱盶盹盼眀眆眊眎眒眔眕眗眙眚眜眢眨眭眮眯眴眵眶眹眽眾睂睅睆睊睍睎睏睒睖睗睜睞睟睠睢"],["8fcfa1","睤睧睪睬睰睲睳睴睺睽瞀瞄瞌瞍瞔瞕瞖瞚瞟瞢瞧瞪瞮瞯瞱瞵瞾矃矉矑矒矕矙矞矟矠矤矦矪矬矰矱矴矸矻砅砆砉砍砎砑砝砡砢砣砭砮砰砵砷硃硄硇硈硌硎硒硜硞硠硡硣硤硨硪确硺硾碊碏碔碘碡碝碞碟碤碨碬碭碰碱碲碳"],["8fd0a1","碻碽碿磇磈磉磌磎磒磓磕磖磤磛磟磠磡磦磪磲磳礀磶磷磺磻磿礆礌礐礚礜礞礟礠礥礧礩礭礱礴礵礻礽礿祄祅祆祊祋祏祑祔祘祛祜祧祩祫祲祹祻祼祾禋禌禑禓禔禕禖禘禛禜禡禨禩禫禯禱禴禸离秂秄秇秈秊秏秔秖秚秝秞"],["8fd1a1","秠秢秥秪秫秭秱秸秼稂稃稇稉稊稌稑稕稛稞稡稧稫稭稯稰稴稵稸稹稺穄穅穇穈穌穕穖穙穜穝穟穠穥穧穪穭穵穸穾窀窂窅窆窊窋窐窑窔窞窠窣窬窳窵窹窻窼竆竉竌竎竑竛竨竩竫竬竱竴竻竽竾笇笔笟笣笧笩笪笫笭笮笯笰"],["8fd2a1","笱笴笽笿筀筁筇筎筕筠筤筦筩筪筭筯筲筳筷箄箉箎箐箑箖箛箞箠箥箬箯箰箲箵箶箺箻箼箽篂篅篈篊篔篖篗篙篚篛篨篪篲篴篵篸篹篺篼篾簁簂簃簄簆簉簋簌簎簏簙簛簠簥簦簨簬簱簳簴簶簹簺籆籊籕籑籒籓籙",5],["8fd3a1","籡籣籧籩籭籮籰籲籹籼籽粆粇粏粔粞粠粦粰粶粷粺粻粼粿糄糇糈糉糍糏糓糔糕糗糙糚糝糦糩糫糵紃紇紈紉紏紑紒紓紖紝紞紣紦紪紭紱紼紽紾絀絁絇絈絍絑絓絗絙絚絜絝絥絧絪絰絸絺絻絿綁綂綃綅綆綈綋綌綍綑綖綗綝"],["8fd4a1","綞綦綧綪綳綶綷綹緂",4,"緌緍緎緗緙縀緢緥緦緪緫緭緱緵緶緹緺縈縐縑縕縗縜縝縠縧縨縬縭縯縳縶縿繄繅繇繎繐繒繘繟繡繢繥繫繮繯繳繸繾纁纆纇纊纍纑纕纘纚纝纞缼缻缽缾缿罃罄罇罏罒罓罛罜罝罡罣罤罥罦罭"],["8fd5a1","罱罽罾罿羀羋羍羏羐羑羖羗羜羡羢羦羪羭羴羼羿翀翃翈翎翏翛翟翣翥翨翬翮翯翲翺翽翾翿耇耈耊耍耎耏耑耓耔耖耝耞耟耠耤耦耬耮耰耴耵耷耹耺耼耾聀聄聠聤聦聭聱聵肁肈肎肜肞肦肧肫肸肹胈胍胏胒胔胕胗胘胠胭胮"],["8fd6a1","胰胲胳胶胹胺胾脃脋脖脗脘脜脞脠脤脧脬脰脵脺脼腅腇腊腌腒腗腠腡腧腨腩腭腯腷膁膐膄膅膆膋膎膖膘膛膞膢膮膲膴膻臋臃臅臊臎臏臕臗臛臝臞臡臤臫臬臰臱臲臵臶臸臹臽臿舀舃舏舓舔舙舚舝舡舢舨舲舴舺艃艄艅艆"],["8fd7a1","艋艎艏艑艖艜艠艣艧艭艴艻艽艿芀芁芃芄芇芉芊芎芑芔芖芘芚芛芠芡芣芤芧芨芩芪芮芰芲芴芷芺芼芾芿苆苐苕苚苠苢苤苨苪苭苯苶苷苽苾茀茁茇茈茊茋荔茛茝茞茟茡茢茬茭茮茰茳茷茺茼茽荂荃荄荇荍荎荑荕荖荗荰荸"],["8fd8a1","荽荿莀莂莄莆莍莒莔莕莘莙莛莜莝莦莧莩莬莾莿菀菇菉菏菐菑菔菝荓菨菪菶菸菹菼萁萆萊萏萑萕萙莭萯萹葅葇葈葊葍葏葑葒葖葘葙葚葜葠葤葥葧葪葰葳葴葶葸葼葽蒁蒅蒒蒓蒕蒞蒦蒨蒩蒪蒯蒱蒴蒺蒽蒾蓀蓂蓇蓈蓌蓏蓓"],["8fd9a1","蓜蓧蓪蓯蓰蓱蓲蓷蔲蓺蓻蓽蔂蔃蔇蔌蔎蔐蔜蔞蔢蔣蔤蔥蔧蔪蔫蔯蔳蔴蔶蔿蕆蕏",4,"蕖蕙蕜",6,"蕤蕫蕯蕹蕺蕻蕽蕿薁薅薆薉薋薌薏薓薘薝薟薠薢薥薧薴薶薷薸薼薽薾薿藂藇藊藋藎薭藘藚藟藠藦藨藭藳藶藼"],["8fdaa1","藿蘀蘄蘅蘍蘎蘐蘑蘒蘘蘙蘛蘞蘡蘧蘩蘶蘸蘺蘼蘽虀虂虆虒虓虖虗虘虙虝虠",4,"虩虬虯虵虶虷虺蚍蚑蚖蚘蚚蚜蚡蚦蚧蚨蚭蚱蚳蚴蚵蚷蚸蚹蚿蛀蛁蛃蛅蛑蛒蛕蛗蛚蛜蛠蛣蛥蛧蚈蛺蛼蛽蜄蜅蜇蜋蜎蜏蜐蜓蜔蜙蜞蜟蜡蜣"],["8fdba1","蜨蜮蜯蜱蜲蜹蜺蜼蜽蜾蝀蝃蝅蝍蝘蝝蝡蝤蝥蝯蝱蝲蝻螃",6,"螋螌螐螓螕螗螘螙螞螠螣螧螬螭螮螱螵螾螿蟁蟈蟉蟊蟎蟕蟖蟙蟚蟜蟟蟢蟣蟤蟪蟫蟭蟱蟳蟸蟺蟿蠁蠃蠆蠉蠊蠋蠐蠙蠒蠓蠔蠘蠚蠛蠜蠞蠟蠨蠭蠮蠰蠲蠵"],["8fdca1","蠺蠼衁衃衅衈衉衊衋衎衑衕衖衘衚衜衟衠衤衩衱衹衻袀袘袚袛袜袟袠袨袪袺袽袾裀裊",4,"裑裒裓裛裞裧裯裰裱裵裷褁褆褍褎褏褕褖褘褙褚褜褠褦褧褨褰褱褲褵褹褺褾襀襂襅襆襉襏襒襗襚襛襜襡襢襣襫襮襰襳襵襺"],["8fdda1","襻襼襽覉覍覐覔覕覛覜覟覠覥覰覴覵覶覷覼觔",4,"觥觩觫觭觱觳觶觹觽觿訄訅訇訏訑訒訔訕訞訠訢訤訦訫訬訯訵訷訽訾詀詃詅詇詉詍詎詓詖詗詘詜詝詡詥詧詵詶詷詹詺詻詾詿誀誃誆誋誏誐誒誖誗誙誟誧誩誮誯誳"],["8fdea1","誶誷誻誾諃諆諈諉諊諑諓諔諕諗諝諟諬諰諴諵諶諼諿謅謆謋謑謜謞謟謊謭謰謷謼譂",4,"譈譒譓譔譙譍譞譣譭譶譸譹譼譾讁讄讅讋讍讏讔讕讜讞讟谸谹谽谾豅豇豉豋豏豑豓豔豗豘豛豝豙豣豤豦豨豩豭豳豵豶豻豾貆"],["8fdfa1","貇貋貐貒貓貙貛貜貤貹貺賅賆賉賋賏賖賕賙賝賡賨賬賯賰賲賵賷賸賾賿贁贃贉贒贗贛赥赩赬赮赿趂趄趈趍趐趑趕趞趟趠趦趫趬趯趲趵趷趹趻跀跅跆跇跈跊跎跑跔跕跗跙跤跥跧跬跰趼跱跲跴跽踁踄踅踆踋踑踔踖踠踡踢"],["8fe0a1","踣踦踧踱踳踶踷踸踹踽蹀蹁蹋蹍蹎蹏蹔蹛蹜蹝蹞蹡蹢蹩蹬蹭蹯蹰蹱蹹蹺蹻躂躃躉躐躒躕躚躛躝躞躢躧躩躭躮躳躵躺躻軀軁軃軄軇軏軑軔軜軨軮軰軱軷軹軺軭輀輂輇輈輏輐輖輗輘輞輠輡輣輥輧輨輬輭輮輴輵輶輷輺轀轁"],["8fe1a1","轃轇轏轑",4,"轘轝轞轥辝辠辡辤辥辦辵辶辸达迀迁迆迊迋迍运迒迓迕迠迣迤迨迮迱迵迶迻迾适逄逈逌逘逛逨逩逯逪逬逭逳逴逷逿遃遄遌遛遝遢遦遧遬遰遴遹邅邈邋邌邎邐邕邗邘邙邛邠邡邢邥邰邲邳邴邶邽郌邾郃"],["8fe2a1","郄郅郇郈郕郗郘郙郜郝郟郥郒郶郫郯郰郴郾郿鄀鄄鄅鄆鄈鄍鄐鄔鄖鄗鄘鄚鄜鄞鄠鄥鄢鄣鄧鄩鄮鄯鄱鄴鄶鄷鄹鄺鄼鄽酃酇酈酏酓酗酙酚酛酡酤酧酭酴酹酺酻醁醃醅醆醊醎醑醓醔醕醘醞醡醦醨醬醭醮醰醱醲醳醶醻醼醽醿"],["8fe3a1","釂釃釅釓釔釗釙釚釞釤釥釩釪釬",5,"釷釹釻釽鈀鈁鈄鈅鈆鈇鈉鈊鈌鈐鈒鈓鈖鈘鈜鈝鈣鈤鈥鈦鈨鈮鈯鈰鈳鈵鈶鈸鈹鈺鈼鈾鉀鉂鉃鉆鉇鉊鉍鉎鉏鉑鉘鉙鉜鉝鉠鉡鉥鉧鉨鉩鉮鉯鉰鉵",4,"鉻鉼鉽鉿銈銉銊銍銎銒銗"],["8fe4a1","銙銟銠銤銥銧銨銫銯銲銶銸銺銻銼銽銿",4,"鋅鋆鋇鋈鋋鋌鋍鋎鋐鋓鋕鋗鋘鋙鋜鋝鋟鋠鋡鋣鋥鋧鋨鋬鋮鋰鋹鋻鋿錀錂錈錍錑錔錕錜錝錞錟錡錤錥錧錩錪錳錴錶錷鍇鍈鍉鍐鍑鍒鍕鍗鍘鍚鍞鍤鍥鍧鍩鍪鍭鍯鍰鍱鍳鍴鍶"],["8fe5a1","鍺鍽鍿鎀鎁鎂鎈鎊鎋鎍鎏鎒鎕鎘鎛鎞鎡鎣鎤鎦鎨鎫鎴鎵鎶鎺鎩鏁鏄鏅鏆鏇鏉",4,"鏓鏙鏜鏞鏟鏢鏦鏧鏹鏷鏸鏺鏻鏽鐁鐂鐄鐈鐉鐍鐎鐏鐕鐖鐗鐟鐮鐯鐱鐲鐳鐴鐻鐿鐽鑃鑅鑈鑊鑌鑕鑙鑜鑟鑡鑣鑨鑫鑭鑮鑯鑱鑲钄钃镸镹"],["8fe6a1","镾閄閈閌閍閎閝閞閟閡閦閩閫閬閴閶閺閽閿闆闈闉闋闐闑闒闓闙闚闝闞闟闠闤闦阝阞阢阤阥阦阬阱阳阷阸阹阺阼阽陁陒陔陖陗陘陡陮陴陻陼陾陿隁隂隃隄隉隑隖隚隝隟隤隥隦隩隮隯隳隺雊雒嶲雘雚雝雞雟雩雯雱雺霂"],["8fe7a1","霃霅霉霚霛霝霡霢霣霨霱霳靁靃靊靎靏靕靗靘靚靛靣靧靪靮靳靶靷靸靻靽靿鞀鞉鞕鞖鞗鞙鞚鞞鞟鞢鞬鞮鞱鞲鞵鞶鞸鞹鞺鞼鞾鞿韁韄韅韇韉韊韌韍韎韐韑韔韗韘韙韝韞韠韛韡韤韯韱韴韷韸韺頇頊頙頍頎頔頖頜頞頠頣頦"],["8fe8a1","頫頮頯頰頲頳頵頥頾顄顇顊顑顒顓顖顗顙顚顢顣顥顦顪顬颫颭颮颰颴颷颸颺颻颿飂飅飈飌飡飣飥飦飧飪飳飶餂餇餈餑餕餖餗餚餛餜餟餢餦餧餫餱",4,"餹餺餻餼饀饁饆饇饈饍饎饔饘饙饛饜饞饟饠馛馝馟馦馰馱馲馵"],["8fe9a1","馹馺馽馿駃駉駓駔駙駚駜駞駧駪駫駬駰駴駵駹駽駾騂騃騄騋騌騐騑騖騞騠騢騣騤騧騭騮騳騵騶騸驇驁驄驊驋驌驎驑驔驖驝骪骬骮骯骲骴骵骶骹骻骾骿髁髃髆髈髎髐髒髕髖髗髛髜髠髤髥髧髩髬髲髳髵髹髺髽髿",4],["8feaa1","鬄鬅鬈鬉鬋鬌鬍鬎鬐鬒鬖鬙鬛鬜鬠鬦鬫鬭鬳鬴鬵鬷鬹鬺鬽魈魋魌魕魖魗魛魞魡魣魥魦魨魪",4,"魳魵魷魸魹魿鮀鮄鮅鮆鮇鮉鮊鮋鮍鮏鮐鮔鮚鮝鮞鮦鮧鮩鮬鮰鮱鮲鮷鮸鮻鮼鮾鮿鯁鯇鯈鯎鯐鯗鯘鯝鯟鯥鯧鯪鯫鯯鯳鯷鯸"],["8feba1","鯹鯺鯽鯿鰀鰂鰋鰏鰑鰖鰘鰙鰚鰜鰞鰢鰣鰦",4,"鰱鰵鰶鰷鰽鱁鱃鱄鱅鱉鱊鱎鱏鱐鱓鱔鱖鱘鱛鱝鱞鱟鱣鱩鱪鱜鱫鱨鱮鱰鱲鱵鱷鱻鳦鳲鳷鳹鴋鴂鴑鴗鴘鴜鴝鴞鴯鴰鴲鴳鴴鴺鴼鵅鴽鵂鵃鵇鵊鵓鵔鵟鵣鵢鵥鵩鵪鵫鵰鵶鵷鵻"],["8feca1","鵼鵾鶃鶄鶆鶊鶍鶎鶒鶓鶕鶖鶗鶘鶡鶪鶬鶮鶱鶵鶹鶼鶿鷃鷇鷉鷊鷔鷕鷖鷗鷚鷞鷟鷠鷥鷧鷩鷫鷮鷰鷳鷴鷾鸊鸂鸇鸎鸐鸑鸒鸕鸖鸙鸜鸝鹺鹻鹼麀麂麃麄麅麇麎麏麖麘麛麞麤麨麬麮麯麰麳麴麵黆黈黋黕黟黤黧黬黭黮黰黱黲黵"],["8feda1","黸黿鼂鼃鼉鼏鼐鼑鼒鼔鼖鼗鼙鼚鼛鼟鼢鼦鼪鼫鼯鼱鼲鼴鼷鼹鼺鼼鼽鼿齁齃",4,"齓齕齖齗齘齚齝齞齨齩齭",4,"齳齵齺齽龏龐龑龒龔龖龗龞龡龢龣龥"]]'); + +/***/ }), + +/***/ 7302: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('{"uChars":[128,165,169,178,184,216,226,235,238,244,248,251,253,258,276,284,300,325,329,334,364,463,465,467,469,471,473,475,477,506,594,610,712,716,730,930,938,962,970,1026,1104,1106,8209,8215,8218,8222,8231,8241,8244,8246,8252,8365,8452,8454,8458,8471,8482,8556,8570,8596,8602,8713,8720,8722,8726,8731,8737,8740,8742,8748,8751,8760,8766,8777,8781,8787,8802,8808,8816,8854,8858,8870,8896,8979,9322,9372,9548,9588,9616,9622,9634,9652,9662,9672,9676,9680,9702,9735,9738,9793,9795,11906,11909,11913,11917,11928,11944,11947,11951,11956,11960,11964,11979,12284,12292,12312,12319,12330,12351,12436,12447,12535,12543,12586,12842,12850,12964,13200,13215,13218,13253,13263,13267,13270,13384,13428,13727,13839,13851,14617,14703,14801,14816,14964,15183,15471,15585,16471,16736,17208,17325,17330,17374,17623,17997,18018,18212,18218,18301,18318,18760,18811,18814,18820,18823,18844,18848,18872,19576,19620,19738,19887,40870,59244,59336,59367,59413,59417,59423,59431,59437,59443,59452,59460,59478,59493,63789,63866,63894,63976,63986,64016,64018,64021,64025,64034,64037,64042,65074,65093,65107,65112,65127,65132,65375,65510,65536],"gbChars":[0,36,38,45,50,81,89,95,96,100,103,104,105,109,126,133,148,172,175,179,208,306,307,308,309,310,311,312,313,341,428,443,544,545,558,741,742,749,750,805,819,820,7922,7924,7925,7927,7934,7943,7944,7945,7950,8062,8148,8149,8152,8164,8174,8236,8240,8262,8264,8374,8380,8381,8384,8388,8390,8392,8393,8394,8396,8401,8406,8416,8419,8424,8437,8439,8445,8482,8485,8496,8521,8603,8936,8946,9046,9050,9063,9066,9076,9092,9100,9108,9111,9113,9131,9162,9164,9218,9219,11329,11331,11334,11336,11346,11361,11363,11366,11370,11372,11375,11389,11682,11686,11687,11692,11694,11714,11716,11723,11725,11730,11736,11982,11989,12102,12336,12348,12350,12384,12393,12395,12397,12510,12553,12851,12962,12973,13738,13823,13919,13933,14080,14298,14585,14698,15583,15847,16318,16434,16438,16481,16729,17102,17122,17315,17320,17402,17418,17859,17909,17911,17915,17916,17936,17939,17961,18664,18703,18814,18962,19043,33469,33470,33471,33484,33485,33490,33497,33501,33505,33513,33520,33536,33550,37845,37921,37948,38029,38038,38064,38065,38066,38069,38075,38076,38078,39108,39109,39113,39114,39115,39116,39265,39394,189000]}'); + +/***/ }), + +/***/ 9603: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('[["a140","",62],["a180","",32],["a240","",62],["a280","",32],["a2ab","",5],["a2e3","€"],["a2ef",""],["a2fd",""],["a340","",62],["a380","",31," "],["a440","",62],["a480","",32],["a4f4","",10],["a540","",62],["a580","",32],["a5f7","",7],["a640","",62],["a680","",32],["a6b9","",7],["a6d9","",6],["a6ec",""],["a6f3",""],["a6f6","",8],["a740","",62],["a780","",32],["a7c2","",14],["a7f2","",12],["a896","",10],["a8bc","ḿ"],["a8bf","ǹ"],["a8c1",""],["a8ea","",20],["a958",""],["a95b",""],["a95d",""],["a989","〾⿰",11],["a997","",12],["a9f0","",14],["aaa1","",93],["aba1","",93],["aca1","",93],["ada1","",93],["aea1","",93],["afa1","",93],["d7fa","",4],["f8a1","",93],["f9a1","",93],["faa1","",93],["fba1","",93],["fca1","",93],["fda1","",93],["fe50","⺁⺄㑳㑇⺈⺋㖞㘚㘎⺌⺗㥮㤘㧏㧟㩳㧐㭎㱮㳠⺧⺪䁖䅟⺮䌷⺳⺶⺷䎱䎬⺻䏝䓖䙡䙌"],["fe80","䜣䜩䝼䞍⻊䥇䥺䥽䦂䦃䦅䦆䦟䦛䦷䦶䲣䲟䲠䲡䱷䲢䴓",6,"䶮",93],["8135f437",""]]'); + +/***/ }), + +/***/ 7572: +/***/ ((module) => { + +"use strict"; +module.exports = /*#__PURE__*/JSON.parse('[["0","\\u0000",128],["a1","。",62],["8140"," 、。,.・:;?!゛゜´`¨^ ̄_ヽヾゝゞ〃仝々〆〇ー―‐/\~∥|…‥‘’“”()〔〕[]{}〈",9,"+-±×"],["8180","÷=≠<>≦≧∞∴♂♀°′″℃¥$¢£%#&*@§☆★○●◎◇◆□■△▲▽▼※〒→←↑↓〓"],["81b8","∈∋⊆⊇⊂⊃∪∩"],["81c8","∧∨¬⇒⇔∀∃"],["81da","∠⊥⌒∂∇≡≒≪≫√∽∝∵∫∬"],["81f0","ʼn♯♭♪†‡¶"],["81fc","◯"],["824f","0",9],["8260","A",25],["8281","a",25],["829f","ぁ",82],["8340","ァ",62],["8380","ム",22],["839f","Α",16,"Σ",6],["83bf","α",16,"σ",6],["8440","А",5,"ЁЖ",25],["8470","а",5,"ёж",7],["8480","о",17],["849f","─│┌┐┘└├┬┤┴┼━┃┏┓┛┗┣┳┫┻╋┠┯┨┷┿┝┰┥┸╂"],["8740","①",19,"Ⅰ",9],["875f","㍉㌔㌢㍍㌘㌧㌃㌶㍑㍗㌍㌦㌣㌫㍊㌻㎜㎝㎞㎎㎏㏄㎡"],["877e","㍻"],["8780","〝〟№㏍℡㊤",4,"㈱㈲㈹㍾㍽㍼≒≡∫∮∑√⊥∠∟⊿∵∩∪"],["889f","亜唖娃阿哀愛挨姶逢葵茜穐悪握渥旭葦芦鯵梓圧斡扱宛姐虻飴絢綾鮎或粟袷安庵按暗案闇鞍杏以伊位依偉囲夷委威尉惟意慰易椅為畏異移維緯胃萎衣謂違遺医井亥域育郁磯一壱溢逸稲茨芋鰯允印咽員因姻引飲淫胤蔭"],["8940","院陰隠韻吋右宇烏羽迂雨卯鵜窺丑碓臼渦嘘唄欝蔚鰻姥厩浦瓜閏噂云運雲荏餌叡営嬰影映曳栄永泳洩瑛盈穎頴英衛詠鋭液疫益駅悦謁越閲榎厭円"],["8980","園堰奄宴延怨掩援沿演炎焔煙燕猿縁艶苑薗遠鉛鴛塩於汚甥凹央奥往応押旺横欧殴王翁襖鴬鴎黄岡沖荻億屋憶臆桶牡乙俺卸恩温穏音下化仮何伽価佳加可嘉夏嫁家寡科暇果架歌河火珂禍禾稼箇花苛茄荷華菓蝦課嘩貨迦過霞蚊俄峨我牙画臥芽蛾賀雅餓駕介会解回塊壊廻快怪悔恢懐戒拐改"],["8a40","魁晦械海灰界皆絵芥蟹開階貝凱劾外咳害崖慨概涯碍蓋街該鎧骸浬馨蛙垣柿蛎鈎劃嚇各廓拡撹格核殻獲確穫覚角赫較郭閣隔革学岳楽額顎掛笠樫"],["8a80","橿梶鰍潟割喝恰括活渇滑葛褐轄且鰹叶椛樺鞄株兜竃蒲釜鎌噛鴨栢茅萱粥刈苅瓦乾侃冠寒刊勘勧巻喚堪姦完官寛干幹患感慣憾換敢柑桓棺款歓汗漢澗潅環甘監看竿管簡緩缶翰肝艦莞観諌貫還鑑間閑関陥韓館舘丸含岸巌玩癌眼岩翫贋雁頑顔願企伎危喜器基奇嬉寄岐希幾忌揮机旗既期棋棄"],["8b40","機帰毅気汽畿祈季稀紀徽規記貴起軌輝飢騎鬼亀偽儀妓宜戯技擬欺犠疑祇義蟻誼議掬菊鞠吉吃喫桔橘詰砧杵黍却客脚虐逆丘久仇休及吸宮弓急救"],["8b80","朽求汲泣灸球究窮笈級糾給旧牛去居巨拒拠挙渠虚許距鋸漁禦魚亨享京供侠僑兇競共凶協匡卿叫喬境峡強彊怯恐恭挟教橋況狂狭矯胸脅興蕎郷鏡響饗驚仰凝尭暁業局曲極玉桐粁僅勤均巾錦斤欣欽琴禁禽筋緊芹菌衿襟謹近金吟銀九倶句区狗玖矩苦躯駆駈駒具愚虞喰空偶寓遇隅串櫛釧屑屈"],["8c40","掘窟沓靴轡窪熊隈粂栗繰桑鍬勲君薫訓群軍郡卦袈祁係傾刑兄啓圭珪型契形径恵慶慧憩掲携敬景桂渓畦稽系経継繋罫茎荊蛍計詣警軽頚鶏芸迎鯨"],["8c80","劇戟撃激隙桁傑欠決潔穴結血訣月件倹倦健兼券剣喧圏堅嫌建憲懸拳捲検権牽犬献研硯絹県肩見謙賢軒遣鍵険顕験鹸元原厳幻弦減源玄現絃舷言諺限乎個古呼固姑孤己庫弧戸故枯湖狐糊袴股胡菰虎誇跨鈷雇顧鼓五互伍午呉吾娯後御悟梧檎瑚碁語誤護醐乞鯉交佼侯候倖光公功効勾厚口向"],["8d40","后喉坑垢好孔孝宏工巧巷幸広庚康弘恒慌抗拘控攻昂晃更杭校梗構江洪浩港溝甲皇硬稿糠紅紘絞綱耕考肯肱腔膏航荒行衡講貢購郊酵鉱砿鋼閤降"],["8d80","項香高鴻剛劫号合壕拷濠豪轟麹克刻告国穀酷鵠黒獄漉腰甑忽惚骨狛込此頃今困坤墾婚恨懇昏昆根梱混痕紺艮魂些佐叉唆嵯左差査沙瑳砂詐鎖裟坐座挫債催再最哉塞妻宰彩才採栽歳済災采犀砕砦祭斎細菜裁載際剤在材罪財冴坂阪堺榊肴咲崎埼碕鷺作削咋搾昨朔柵窄策索錯桜鮭笹匙冊刷"],["8e40","察拶撮擦札殺薩雑皐鯖捌錆鮫皿晒三傘参山惨撒散桟燦珊産算纂蚕讃賛酸餐斬暫残仕仔伺使刺司史嗣四士始姉姿子屍市師志思指支孜斯施旨枝止"],["8e80","死氏獅祉私糸紙紫肢脂至視詞詩試誌諮資賜雌飼歯事似侍児字寺慈持時次滋治爾璽痔磁示而耳自蒔辞汐鹿式識鴫竺軸宍雫七叱執失嫉室悉湿漆疾質実蔀篠偲柴芝屡蕊縞舎写射捨赦斜煮社紗者謝車遮蛇邪借勺尺杓灼爵酌釈錫若寂弱惹主取守手朱殊狩珠種腫趣酒首儒受呪寿授樹綬需囚収周"],["8f40","宗就州修愁拾洲秀秋終繍習臭舟蒐衆襲讐蹴輯週酋酬集醜什住充十従戎柔汁渋獣縦重銃叔夙宿淑祝縮粛塾熟出術述俊峻春瞬竣舜駿准循旬楯殉淳"],["8f80","準潤盾純巡遵醇順処初所暑曙渚庶緒署書薯藷諸助叙女序徐恕鋤除傷償勝匠升召哨商唱嘗奨妾娼宵将小少尚庄床廠彰承抄招掌捷昇昌昭晶松梢樟樵沼消渉湘焼焦照症省硝礁祥称章笑粧紹肖菖蒋蕉衝裳訟証詔詳象賞醤鉦鍾鐘障鞘上丈丞乗冗剰城場壌嬢常情擾条杖浄状畳穣蒸譲醸錠嘱埴飾"],["9040","拭植殖燭織職色触食蝕辱尻伸信侵唇娠寝審心慎振新晋森榛浸深申疹真神秦紳臣芯薪親診身辛進針震人仁刃塵壬尋甚尽腎訊迅陣靭笥諏須酢図厨"],["9080","逗吹垂帥推水炊睡粋翠衰遂酔錐錘随瑞髄崇嵩数枢趨雛据杉椙菅頗雀裾澄摺寸世瀬畝是凄制勢姓征性成政整星晴棲栖正清牲生盛精聖声製西誠誓請逝醒青静斉税脆隻席惜戚斥昔析石積籍績脊責赤跡蹟碩切拙接摂折設窃節説雪絶舌蝉仙先千占宣専尖川戦扇撰栓栴泉浅洗染潜煎煽旋穿箭線"],["9140","繊羨腺舛船薦詮賎践選遷銭銑閃鮮前善漸然全禅繕膳糎噌塑岨措曾曽楚狙疏疎礎祖租粗素組蘇訴阻遡鼠僧創双叢倉喪壮奏爽宋層匝惣想捜掃挿掻"],["9180","操早曹巣槍槽漕燥争痩相窓糟総綜聡草荘葬蒼藻装走送遭鎗霜騒像増憎臓蔵贈造促側則即息捉束測足速俗属賊族続卒袖其揃存孫尊損村遜他多太汰詑唾堕妥惰打柁舵楕陀駄騨体堆対耐岱帯待怠態戴替泰滞胎腿苔袋貸退逮隊黛鯛代台大第醍題鷹滝瀧卓啄宅托択拓沢濯琢託鐸濁諾茸凧蛸只"],["9240","叩但達辰奪脱巽竪辿棚谷狸鱈樽誰丹単嘆坦担探旦歎淡湛炭短端箪綻耽胆蛋誕鍛団壇弾断暖檀段男談値知地弛恥智池痴稚置致蜘遅馳築畜竹筑蓄"],["9280","逐秩窒茶嫡着中仲宙忠抽昼柱注虫衷註酎鋳駐樗瀦猪苧著貯丁兆凋喋寵帖帳庁弔張彫徴懲挑暢朝潮牒町眺聴脹腸蝶調諜超跳銚長頂鳥勅捗直朕沈珍賃鎮陳津墜椎槌追鎚痛通塚栂掴槻佃漬柘辻蔦綴鍔椿潰坪壷嬬紬爪吊釣鶴亭低停偵剃貞呈堤定帝底庭廷弟悌抵挺提梯汀碇禎程締艇訂諦蹄逓"],["9340","邸鄭釘鼎泥摘擢敵滴的笛適鏑溺哲徹撤轍迭鉄典填天展店添纏甜貼転顛点伝殿澱田電兎吐堵塗妬屠徒斗杜渡登菟賭途都鍍砥砺努度土奴怒倒党冬"],["9380","凍刀唐塔塘套宕島嶋悼投搭東桃梼棟盗淘湯涛灯燈当痘祷等答筒糖統到董蕩藤討謄豆踏逃透鐙陶頭騰闘働動同堂導憧撞洞瞳童胴萄道銅峠鴇匿得徳涜特督禿篤毒独読栃橡凸突椴届鳶苫寅酉瀞噸屯惇敦沌豚遁頓呑曇鈍奈那内乍凪薙謎灘捺鍋楢馴縄畷南楠軟難汝二尼弐迩匂賑肉虹廿日乳入"],["9440","如尿韮任妊忍認濡禰祢寧葱猫熱年念捻撚燃粘乃廼之埜嚢悩濃納能脳膿農覗蚤巴把播覇杷波派琶破婆罵芭馬俳廃拝排敗杯盃牌背肺輩配倍培媒梅"],["9480","楳煤狽買売賠陪這蝿秤矧萩伯剥博拍柏泊白箔粕舶薄迫曝漠爆縛莫駁麦函箱硲箸肇筈櫨幡肌畑畠八鉢溌発醗髪伐罰抜筏閥鳩噺塙蛤隼伴判半反叛帆搬斑板氾汎版犯班畔繁般藩販範釆煩頒飯挽晩番盤磐蕃蛮匪卑否妃庇彼悲扉批披斐比泌疲皮碑秘緋罷肥被誹費避非飛樋簸備尾微枇毘琵眉美"],["9540","鼻柊稗匹疋髭彦膝菱肘弼必畢筆逼桧姫媛紐百謬俵彪標氷漂瓢票表評豹廟描病秒苗錨鋲蒜蛭鰭品彬斌浜瀕貧賓頻敏瓶不付埠夫婦富冨布府怖扶敷"],["9580","斧普浮父符腐膚芙譜負賦赴阜附侮撫武舞葡蕪部封楓風葺蕗伏副復幅服福腹複覆淵弗払沸仏物鮒分吻噴墳憤扮焚奮粉糞紛雰文聞丙併兵塀幣平弊柄並蔽閉陛米頁僻壁癖碧別瞥蔑箆偏変片篇編辺返遍便勉娩弁鞭保舗鋪圃捕歩甫補輔穂募墓慕戊暮母簿菩倣俸包呆報奉宝峰峯崩庖抱捧放方朋"],["9640","法泡烹砲縫胞芳萌蓬蜂褒訪豊邦鋒飽鳳鵬乏亡傍剖坊妨帽忘忙房暴望某棒冒紡肪膨謀貌貿鉾防吠頬北僕卜墨撲朴牧睦穆釦勃没殆堀幌奔本翻凡盆"],["9680","摩磨魔麻埋妹昧枚毎哩槙幕膜枕鮪柾鱒桝亦俣又抹末沫迄侭繭麿万慢満漫蔓味未魅巳箕岬密蜜湊蓑稔脈妙粍民眠務夢無牟矛霧鵡椋婿娘冥名命明盟迷銘鳴姪牝滅免棉綿緬面麺摸模茂妄孟毛猛盲網耗蒙儲木黙目杢勿餅尤戻籾貰問悶紋門匁也冶夜爺耶野弥矢厄役約薬訳躍靖柳薮鑓愉愈油癒"],["9740","諭輸唯佑優勇友宥幽悠憂揖有柚湧涌猶猷由祐裕誘遊邑郵雄融夕予余与誉輿預傭幼妖容庸揚揺擁曜楊様洋溶熔用窯羊耀葉蓉要謡踊遥陽養慾抑欲"],["9780","沃浴翌翼淀羅螺裸来莱頼雷洛絡落酪乱卵嵐欄濫藍蘭覧利吏履李梨理璃痢裏裡里離陸律率立葎掠略劉流溜琉留硫粒隆竜龍侶慮旅虜了亮僚両凌寮料梁涼猟療瞭稜糧良諒遼量陵領力緑倫厘林淋燐琳臨輪隣鱗麟瑠塁涙累類令伶例冷励嶺怜玲礼苓鈴隷零霊麗齢暦歴列劣烈裂廉恋憐漣煉簾練聯"],["9840","蓮連錬呂魯櫓炉賂路露労婁廊弄朗楼榔浪漏牢狼篭老聾蝋郎六麓禄肋録論倭和話歪賄脇惑枠鷲亙亘鰐詫藁蕨椀湾碗腕"],["989f","弌丐丕个丱丶丼丿乂乖乘亂亅豫亊舒弍于亞亟亠亢亰亳亶从仍仄仆仂仗仞仭仟价伉佚估佛佝佗佇佶侈侏侘佻佩佰侑佯來侖儘俔俟俎俘俛俑俚俐俤俥倚倨倔倪倥倅伜俶倡倩倬俾俯們倆偃假會偕偐偈做偖偬偸傀傚傅傴傲"],["9940","僉僊傳僂僖僞僥僭僣僮價僵儉儁儂儖儕儔儚儡儺儷儼儻儿兀兒兌兔兢竸兩兪兮冀冂囘册冉冏冑冓冕冖冤冦冢冩冪冫决冱冲冰况冽凅凉凛几處凩凭"],["9980","凰凵凾刄刋刔刎刧刪刮刳刹剏剄剋剌剞剔剪剴剩剳剿剽劍劔劒剱劈劑辨辧劬劭劼劵勁勍勗勞勣勦飭勠勳勵勸勹匆匈甸匍匐匏匕匚匣匯匱匳匸區卆卅丗卉卍凖卞卩卮夘卻卷厂厖厠厦厥厮厰厶參簒雙叟曼燮叮叨叭叺吁吽呀听吭吼吮吶吩吝呎咏呵咎呟呱呷呰咒呻咀呶咄咐咆哇咢咸咥咬哄哈咨"],["9a40","咫哂咤咾咼哘哥哦唏唔哽哮哭哺哢唹啀啣啌售啜啅啖啗唸唳啝喙喀咯喊喟啻啾喘喞單啼喃喩喇喨嗚嗅嗟嗄嗜嗤嗔嘔嗷嘖嗾嗽嘛嗹噎噐營嘴嘶嘲嘸"],["9a80","噫噤嘯噬噪嚆嚀嚊嚠嚔嚏嚥嚮嚶嚴囂嚼囁囃囀囈囎囑囓囗囮囹圀囿圄圉圈國圍圓團圖嗇圜圦圷圸坎圻址坏坩埀垈坡坿垉垓垠垳垤垪垰埃埆埔埒埓堊埖埣堋堙堝塲堡塢塋塰毀塒堽塹墅墹墟墫墺壞墻墸墮壅壓壑壗壙壘壥壜壤壟壯壺壹壻壼壽夂夊夐夛梦夥夬夭夲夸夾竒奕奐奎奚奘奢奠奧奬奩"],["9b40","奸妁妝佞侫妣妲姆姨姜妍姙姚娥娟娑娜娉娚婀婬婉娵娶婢婪媚媼媾嫋嫂媽嫣嫗嫦嫩嫖嫺嫻嬌嬋嬖嬲嫐嬪嬶嬾孃孅孀孑孕孚孛孥孩孰孳孵學斈孺宀"],["9b80","它宦宸寃寇寉寔寐寤實寢寞寥寫寰寶寳尅將專對尓尠尢尨尸尹屁屆屎屓屐屏孱屬屮乢屶屹岌岑岔妛岫岻岶岼岷峅岾峇峙峩峽峺峭嶌峪崋崕崗嵜崟崛崑崔崢崚崙崘嵌嵒嵎嵋嵬嵳嵶嶇嶄嶂嶢嶝嶬嶮嶽嶐嶷嶼巉巍巓巒巖巛巫已巵帋帚帙帑帛帶帷幄幃幀幎幗幔幟幢幤幇幵并幺麼广庠廁廂廈廐廏"],["9c40","廖廣廝廚廛廢廡廨廩廬廱廳廰廴廸廾弃弉彝彜弋弑弖弩弭弸彁彈彌彎弯彑彖彗彙彡彭彳彷徃徂彿徊很徑徇從徙徘徠徨徭徼忖忻忤忸忱忝悳忿怡恠"],["9c80","怙怐怩怎怱怛怕怫怦怏怺恚恁恪恷恟恊恆恍恣恃恤恂恬恫恙悁悍惧悃悚悄悛悖悗悒悧悋惡悸惠惓悴忰悽惆悵惘慍愕愆惶惷愀惴惺愃愡惻惱愍愎慇愾愨愧慊愿愼愬愴愽慂慄慳慷慘慙慚慫慴慯慥慱慟慝慓慵憙憖憇憬憔憚憊憑憫憮懌懊應懷懈懃懆憺懋罹懍懦懣懶懺懴懿懽懼懾戀戈戉戍戌戔戛"],["9d40","戞戡截戮戰戲戳扁扎扞扣扛扠扨扼抂抉找抒抓抖拔抃抔拗拑抻拏拿拆擔拈拜拌拊拂拇抛拉挌拮拱挧挂挈拯拵捐挾捍搜捏掖掎掀掫捶掣掏掉掟掵捫"],["9d80","捩掾揩揀揆揣揉插揶揄搖搴搆搓搦搶攝搗搨搏摧摯摶摎攪撕撓撥撩撈撼據擒擅擇撻擘擂擱擧舉擠擡抬擣擯攬擶擴擲擺攀擽攘攜攅攤攣攫攴攵攷收攸畋效敖敕敍敘敞敝敲數斂斃變斛斟斫斷旃旆旁旄旌旒旛旙无旡旱杲昊昃旻杳昵昶昴昜晏晄晉晁晞晝晤晧晨晟晢晰暃暈暎暉暄暘暝曁暹曉暾暼"],["9e40","曄暸曖曚曠昿曦曩曰曵曷朏朖朞朦朧霸朮朿朶杁朸朷杆杞杠杙杣杤枉杰枩杼杪枌枋枦枡枅枷柯枴柬枳柩枸柤柞柝柢柮枹柎柆柧檜栞框栩桀桍栲桎"],["9e80","梳栫桙档桷桿梟梏梭梔條梛梃檮梹桴梵梠梺椏梍桾椁棊椈棘椢椦棡椌棍棔棧棕椶椒椄棗棣椥棹棠棯椨椪椚椣椡棆楹楷楜楸楫楔楾楮椹楴椽楙椰楡楞楝榁楪榲榮槐榿槁槓榾槎寨槊槝榻槃榧樮榑榠榜榕榴槞槨樂樛槿權槹槲槧樅榱樞槭樔槫樊樒櫁樣樓橄樌橲樶橸橇橢橙橦橈樸樢檐檍檠檄檢檣"],["9f40","檗蘗檻櫃櫂檸檳檬櫞櫑櫟檪櫚櫪櫻欅蘖櫺欒欖鬱欟欸欷盜欹飮歇歃歉歐歙歔歛歟歡歸歹歿殀殄殃殍殘殕殞殤殪殫殯殲殱殳殷殼毆毋毓毟毬毫毳毯"],["9f80","麾氈氓气氛氤氣汞汕汢汪沂沍沚沁沛汾汨汳沒沐泄泱泓沽泗泅泝沮沱沾沺泛泯泙泪洟衍洶洫洽洸洙洵洳洒洌浣涓浤浚浹浙涎涕濤涅淹渕渊涵淇淦涸淆淬淞淌淨淒淅淺淙淤淕淪淮渭湮渮渙湲湟渾渣湫渫湶湍渟湃渺湎渤滿渝游溂溪溘滉溷滓溽溯滄溲滔滕溏溥滂溟潁漑灌滬滸滾漿滲漱滯漲滌"],["e040","漾漓滷澆潺潸澁澀潯潛濳潭澂潼潘澎澑濂潦澳澣澡澤澹濆澪濟濕濬濔濘濱濮濛瀉瀋濺瀑瀁瀏濾瀛瀚潴瀝瀘瀟瀰瀾瀲灑灣炙炒炯烱炬炸炳炮烟烋烝"],["e080","烙焉烽焜焙煥煕熈煦煢煌煖煬熏燻熄熕熨熬燗熹熾燒燉燔燎燠燬燧燵燼燹燿爍爐爛爨爭爬爰爲爻爼爿牀牆牋牘牴牾犂犁犇犒犖犢犧犹犲狃狆狄狎狒狢狠狡狹狷倏猗猊猜猖猝猴猯猩猥猾獎獏默獗獪獨獰獸獵獻獺珈玳珎玻珀珥珮珞璢琅瑯琥珸琲琺瑕琿瑟瑙瑁瑜瑩瑰瑣瑪瑶瑾璋璞璧瓊瓏瓔珱"],["e140","瓠瓣瓧瓩瓮瓲瓰瓱瓸瓷甄甃甅甌甎甍甕甓甞甦甬甼畄畍畊畉畛畆畚畩畤畧畫畭畸當疆疇畴疊疉疂疔疚疝疥疣痂疳痃疵疽疸疼疱痍痊痒痙痣痞痾痿"],["e180","痼瘁痰痺痲痳瘋瘍瘉瘟瘧瘠瘡瘢瘤瘴瘰瘻癇癈癆癜癘癡癢癨癩癪癧癬癰癲癶癸發皀皃皈皋皎皖皓皙皚皰皴皸皹皺盂盍盖盒盞盡盥盧盪蘯盻眈眇眄眩眤眞眥眦眛眷眸睇睚睨睫睛睥睿睾睹瞎瞋瞑瞠瞞瞰瞶瞹瞿瞼瞽瞻矇矍矗矚矜矣矮矼砌砒礦砠礪硅碎硴碆硼碚碌碣碵碪碯磑磆磋磔碾碼磅磊磬"],["e240","磧磚磽磴礇礒礑礙礬礫祀祠祗祟祚祕祓祺祿禊禝禧齋禪禮禳禹禺秉秕秧秬秡秣稈稍稘稙稠稟禀稱稻稾稷穃穗穉穡穢穩龝穰穹穽窈窗窕窘窖窩竈窰"],["e280","窶竅竄窿邃竇竊竍竏竕竓站竚竝竡竢竦竭竰笂笏笊笆笳笘笙笞笵笨笶筐筺笄筍笋筌筅筵筥筴筧筰筱筬筮箝箘箟箍箜箚箋箒箏筝箙篋篁篌篏箴篆篝篩簑簔篦篥籠簀簇簓篳篷簗簍篶簣簧簪簟簷簫簽籌籃籔籏籀籐籘籟籤籖籥籬籵粃粐粤粭粢粫粡粨粳粲粱粮粹粽糀糅糂糘糒糜糢鬻糯糲糴糶糺紆"],["e340","紂紜紕紊絅絋紮紲紿紵絆絳絖絎絲絨絮絏絣經綉絛綏絽綛綺綮綣綵緇綽綫總綢綯緜綸綟綰緘緝緤緞緻緲緡縅縊縣縡縒縱縟縉縋縢繆繦縻縵縹繃縷"],["e380","縲縺繧繝繖繞繙繚繹繪繩繼繻纃緕繽辮繿纈纉續纒纐纓纔纖纎纛纜缸缺罅罌罍罎罐网罕罔罘罟罠罨罩罧罸羂羆羃羈羇羌羔羞羝羚羣羯羲羹羮羶羸譱翅翆翊翕翔翡翦翩翳翹飜耆耄耋耒耘耙耜耡耨耿耻聊聆聒聘聚聟聢聨聳聲聰聶聹聽聿肄肆肅肛肓肚肭冐肬胛胥胙胝胄胚胖脉胯胱脛脩脣脯腋"],["e440","隋腆脾腓腑胼腱腮腥腦腴膃膈膊膀膂膠膕膤膣腟膓膩膰膵膾膸膽臀臂膺臉臍臑臙臘臈臚臟臠臧臺臻臾舁舂舅與舊舍舐舖舩舫舸舳艀艙艘艝艚艟艤"],["e480","艢艨艪艫舮艱艷艸艾芍芒芫芟芻芬苡苣苟苒苴苳苺莓范苻苹苞茆苜茉苙茵茴茖茲茱荀茹荐荅茯茫茗茘莅莚莪莟莢莖茣莎莇莊荼莵荳荵莠莉莨菴萓菫菎菽萃菘萋菁菷萇菠菲萍萢萠莽萸蔆菻葭萪萼蕚蒄葷葫蒭葮蒂葩葆萬葯葹萵蓊葢蒹蒿蒟蓙蓍蒻蓚蓐蓁蓆蓖蒡蔡蓿蓴蔗蔘蔬蔟蔕蔔蓼蕀蕣蕘蕈"],["e540","蕁蘂蕋蕕薀薤薈薑薊薨蕭薔薛藪薇薜蕷蕾薐藉薺藏薹藐藕藝藥藜藹蘊蘓蘋藾藺蘆蘢蘚蘰蘿虍乕虔號虧虱蚓蚣蚩蚪蚋蚌蚶蚯蛄蛆蚰蛉蠣蚫蛔蛞蛩蛬"],["e580","蛟蛛蛯蜒蜆蜈蜀蜃蛻蜑蜉蜍蛹蜊蜴蜿蜷蜻蜥蜩蜚蝠蝟蝸蝌蝎蝴蝗蝨蝮蝙蝓蝣蝪蠅螢螟螂螯蟋螽蟀蟐雖螫蟄螳蟇蟆螻蟯蟲蟠蠏蠍蟾蟶蟷蠎蟒蠑蠖蠕蠢蠡蠱蠶蠹蠧蠻衄衂衒衙衞衢衫袁衾袞衵衽袵衲袂袗袒袮袙袢袍袤袰袿袱裃裄裔裘裙裝裹褂裼裴裨裲褄褌褊褓襃褞褥褪褫襁襄褻褶褸襌褝襠襞"],["e640","襦襤襭襪襯襴襷襾覃覈覊覓覘覡覩覦覬覯覲覺覽覿觀觚觜觝觧觴觸訃訖訐訌訛訝訥訶詁詛詒詆詈詼詭詬詢誅誂誄誨誡誑誥誦誚誣諄諍諂諚諫諳諧"],["e680","諤諱謔諠諢諷諞諛謌謇謚諡謖謐謗謠謳鞫謦謫謾謨譁譌譏譎證譖譛譚譫譟譬譯譴譽讀讌讎讒讓讖讙讚谺豁谿豈豌豎豐豕豢豬豸豺貂貉貅貊貍貎貔豼貘戝貭貪貽貲貳貮貶賈賁賤賣賚賽賺賻贄贅贊贇贏贍贐齎贓賍贔贖赧赭赱赳趁趙跂趾趺跏跚跖跌跛跋跪跫跟跣跼踈踉跿踝踞踐踟蹂踵踰踴蹊"],["e740","蹇蹉蹌蹐蹈蹙蹤蹠踪蹣蹕蹶蹲蹼躁躇躅躄躋躊躓躑躔躙躪躡躬躰軆躱躾軅軈軋軛軣軼軻軫軾輊輅輕輒輙輓輜輟輛輌輦輳輻輹轅轂輾轌轉轆轎轗轜"],["e780","轢轣轤辜辟辣辭辯辷迚迥迢迪迯邇迴逅迹迺逑逕逡逍逞逖逋逧逶逵逹迸遏遐遑遒逎遉逾遖遘遞遨遯遶隨遲邂遽邁邀邊邉邏邨邯邱邵郢郤扈郛鄂鄒鄙鄲鄰酊酖酘酣酥酩酳酲醋醉醂醢醫醯醪醵醴醺釀釁釉釋釐釖釟釡釛釼釵釶鈞釿鈔鈬鈕鈑鉞鉗鉅鉉鉤鉈銕鈿鉋鉐銜銖銓銛鉚鋏銹銷鋩錏鋺鍄錮"],["e840","錙錢錚錣錺錵錻鍜鍠鍼鍮鍖鎰鎬鎭鎔鎹鏖鏗鏨鏥鏘鏃鏝鏐鏈鏤鐚鐔鐓鐃鐇鐐鐶鐫鐵鐡鐺鑁鑒鑄鑛鑠鑢鑞鑪鈩鑰鑵鑷鑽鑚鑼鑾钁鑿閂閇閊閔閖閘閙"],["e880","閠閨閧閭閼閻閹閾闊濶闃闍闌闕闔闖關闡闥闢阡阨阮阯陂陌陏陋陷陜陞陝陟陦陲陬隍隘隕隗險隧隱隲隰隴隶隸隹雎雋雉雍襍雜霍雕雹霄霆霈霓霎霑霏霖霙霤霪霰霹霽霾靄靆靈靂靉靜靠靤靦靨勒靫靱靹鞅靼鞁靺鞆鞋鞏鞐鞜鞨鞦鞣鞳鞴韃韆韈韋韜韭齏韲竟韶韵頏頌頸頤頡頷頽顆顏顋顫顯顰"],["e940","顱顴顳颪颯颱颶飄飃飆飩飫餃餉餒餔餘餡餝餞餤餠餬餮餽餾饂饉饅饐饋饑饒饌饕馗馘馥馭馮馼駟駛駝駘駑駭駮駱駲駻駸騁騏騅駢騙騫騷驅驂驀驃"],["e980","騾驕驍驛驗驟驢驥驤驩驫驪骭骰骼髀髏髑髓體髞髟髢髣髦髯髫髮髴髱髷髻鬆鬘鬚鬟鬢鬣鬥鬧鬨鬩鬪鬮鬯鬲魄魃魏魍魎魑魘魴鮓鮃鮑鮖鮗鮟鮠鮨鮴鯀鯊鮹鯆鯏鯑鯒鯣鯢鯤鯔鯡鰺鯲鯱鯰鰕鰔鰉鰓鰌鰆鰈鰒鰊鰄鰮鰛鰥鰤鰡鰰鱇鰲鱆鰾鱚鱠鱧鱶鱸鳧鳬鳰鴉鴈鳫鴃鴆鴪鴦鶯鴣鴟鵄鴕鴒鵁鴿鴾鵆鵈"],["ea40","鵝鵞鵤鵑鵐鵙鵲鶉鶇鶫鵯鵺鶚鶤鶩鶲鷄鷁鶻鶸鶺鷆鷏鷂鷙鷓鷸鷦鷭鷯鷽鸚鸛鸞鹵鹹鹽麁麈麋麌麒麕麑麝麥麩麸麪麭靡黌黎黏黐黔黜點黝黠黥黨黯"],["ea80","黴黶黷黹黻黼黽鼇鼈皷鼕鼡鼬鼾齊齒齔齣齟齠齡齦齧齬齪齷齲齶龕龜龠堯槇遙瑤凜熙"],["ed40","纊褜鍈銈蓜俉炻昱棈鋹曻彅丨仡仼伀伃伹佖侒侊侚侔俍偀倢俿倞偆偰偂傔僴僘兊兤冝冾凬刕劜劦勀勛匀匇匤卲厓厲叝﨎咜咊咩哿喆坙坥垬埈埇﨏"],["ed80","塚增墲夋奓奛奝奣妤妺孖寀甯寘寬尞岦岺峵崧嵓﨑嵂嵭嶸嶹巐弡弴彧德忞恝悅悊惞惕愠惲愑愷愰憘戓抦揵摠撝擎敎昀昕昻昉昮昞昤晥晗晙晴晳暙暠暲暿曺朎朗杦枻桒柀栁桄棏﨓楨﨔榘槢樰橫橆橳橾櫢櫤毖氿汜沆汯泚洄涇浯涖涬淏淸淲淼渹湜渧渼溿澈澵濵瀅瀇瀨炅炫焏焄煜煆煇凞燁燾犱"],["ee40","犾猤猪獷玽珉珖珣珒琇珵琦琪琩琮瑢璉璟甁畯皂皜皞皛皦益睆劯砡硎硤硺礰礼神祥禔福禛竑竧靖竫箞精絈絜綷綠緖繒罇羡羽茁荢荿菇菶葈蒴蕓蕙"],["ee80","蕫﨟薰蘒﨡蠇裵訒訷詹誧誾諟諸諶譓譿賰賴贒赶﨣軏﨤逸遧郞都鄕鄧釚釗釞釭釮釤釥鈆鈐鈊鈺鉀鈼鉎鉙鉑鈹鉧銧鉷鉸鋧鋗鋙鋐﨧鋕鋠鋓錥錡鋻﨨錞鋿錝錂鍰鍗鎤鏆鏞鏸鐱鑅鑈閒隆﨩隝隯霳霻靃靍靏靑靕顗顥飯飼餧館馞驎髙髜魵魲鮏鮱鮻鰀鵰鵫鶴鸙黑"],["eeef","ⅰ",9,"¬¦'""],["f040","",62],["f080","",124],["f140","",62],["f180","",124],["f240","",62],["f280","",124],["f340","",62],["f380","",124],["f440","",62],["f480","",124],["f540","",62],["f580","",124],["f640","",62],["f680","",124],["f740","",62],["f780","",124],["f840","",62],["f880","",124],["f940",""],["fa40","ⅰ",9,"Ⅰ",9,"¬¦'"㈱№℡∵纊褜鍈銈蓜俉炻昱棈鋹曻彅丨仡仼伀伃伹佖侒侊侚侔俍偀倢俿倞偆偰偂傔僴僘兊"],["fa80","兤冝冾凬刕劜劦勀勛匀匇匤卲厓厲叝﨎咜咊咩哿喆坙坥垬埈埇﨏塚增墲夋奓奛奝奣妤妺孖寀甯寘寬尞岦岺峵崧嵓﨑嵂嵭嶸嶹巐弡弴彧德忞恝悅悊惞惕愠惲愑愷愰憘戓抦揵摠撝擎敎昀昕昻昉昮昞昤晥晗晙晴晳暙暠暲暿曺朎朗杦枻桒柀栁桄棏﨓楨﨔榘槢樰橫橆橳橾櫢櫤毖氿汜沆汯泚洄涇浯"],["fb40","涖涬淏淸淲淼渹湜渧渼溿澈澵濵瀅瀇瀨炅炫焏焄煜煆煇凞燁燾犱犾猤猪獷玽珉珖珣珒琇珵琦琪琩琮瑢璉璟甁畯皂皜皞皛皦益睆劯砡硎硤硺礰礼神"],["fb80","祥禔福禛竑竧靖竫箞精絈絜綷綠緖繒罇羡羽茁荢荿菇菶葈蒴蕓蕙蕫﨟薰蘒﨡蠇裵訒訷詹誧誾諟諸諶譓譿賰賴贒赶﨣軏﨤逸遧郞都鄕鄧釚釗釞釭釮釤釥鈆鈐鈊鈺鉀鈼鉎鉙鉑鈹鉧銧鉷鉸鋧鋗鋙鋐﨧鋕鋠鋓錥錡鋻﨨錞鋿錝錂鍰鍗鎤鏆鏞鏸鐱鑅鑈閒隆﨩隝隯霳霻靃靍靏靑靕顗顥飯飼餧館馞驎髙"],["fc40","髜魵魲鮏鮱鮻鰀鵰鵫鶴鸙黑"]]'); + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __nccwpck_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ var threw = true; +/******/ try { +/******/ __webpack_modules__[moduleId].call(module.exports, module, module.exports, __nccwpck_require__); +/******/ threw = false; +/******/ } finally { +/******/ if(threw) delete __webpack_module_cache__[moduleId]; +/******/ } +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/compat */ +/******/ +/******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/"; +/******/ +/************************************************************************/ +var __webpack_exports__ = {}; +const core = __nccwpck_require__(7484) +const { analyzeMemoryTrendsAcrossVersions } = __nccwpck_require__(1548) + +async function run() { + try { + const mixpanelUser = core.getInput('mixpanel-user', { required: true }) + const mixpanelSecret = core.getInput('mixpanel-secret', { required: true }) + const mixpanelProjectId = core.getInput('mixpanel-project-id', { + required: true, + }) + const previousVersionCount = parseInt( + core.getInput('previous-version-count') || '2' + ) + + core.info('Beginning analysis...') + const memoryAnalysis = await analyzeMemoryTrendsAcrossVersions({ + previousVersionCount, + uname: mixpanelUser, + pwd: mixpanelSecret, + projectId: mixpanelProjectId, + }) + + console.log( + 'ODD Available Memory and Processes with Increasing Memory Trend or Selectively Observed by Version (Rolling 1 Month Analysis Window):' + ) + console.log(JSON.stringify(memoryAnalysis, null, 2)) + + const outputText = + 'ODD Available Memory and Processes with Increasing Memory Trend or Selectively Observed by Version (Rolling 1 Month Analysis Window):\n' + + Object.entries(memoryAnalysis) + .map( + ([version, analysis]) => + `\n${version}: ${JSON.stringify(analysis, null, 2)}` + ) + .join('\n') + + core.setOutput('analysis-results', JSON.stringify(memoryAnalysis)) + + await core.summary + .addHeading('ODD Memory Usage Results') + .addCodeBlock(outputText, 'json') + .write() + } catch (error) { + core.setFailed(error.message) + } +} + +run() + +module.exports = __webpack_exports__; +/******/ })() +; \ No newline at end of file diff --git a/.github/actions/odd-resource-analysis/package-lock.json b/.github/actions/odd-resource-analysis/package-lock.json new file mode 100644 index 00000000000..f7f21ad07d0 --- /dev/null +++ b/.github/actions/odd-resource-analysis/package-lock.json @@ -0,0 +1,331 @@ +{ + "name": "odd-memory-analysis", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "odd-memory-analysis", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@actions/core": "1.11.1", + "@actions/github": "6.0.0", + "calculate-correlation": "^1.2.3", + "node-fetch": "2.6.9" + }, + "devDependencies": { + "@vercel/ncc": "0.38.3", + "prettier": "2.7.1" + } + }, + "node_modules/@actions/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", + "dependencies": { + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" + } + }, + "node_modules/@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "dependencies": { + "@actions/io": "^1.0.1" + } + }, + "node_modules/@actions/github": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.0.tgz", + "integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==", + "dependencies": { + "@actions/http-client": "^2.2.0", + "@octokit/core": "^5.0.1", + "@octokit/plugin-paginate-rest": "^9.0.0", + "@octokit/plugin-rest-endpoint-methods": "^10.0.0" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==" + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", + "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.5.tgz", + "integrity": "sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.0.tgz", + "integrity": "sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==", + "dependencies": { + "@octokit/request": "^8.3.0", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.2.1.tgz", + "integrity": "sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.4.1.tgz", + "integrity": "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==", + "dependencies": { + "@octokit/types": "^12.6.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.0.tgz", + "integrity": "sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==", + "dependencies": { + "@octokit/endpoint": "^9.0.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.0.tgz", + "integrity": "sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "13.6.1", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.6.1.tgz", + "integrity": "sha512-PHZE9Z+kWXb23Ndik8MKPirBPziOc0D2/3KH1P+6jK5nGWe96kadZuE4jev2/Jq7FvIfTlT2Ltg8Fv2x1v0a5g==", + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } + }, + "node_modules/@vercel/ncc": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.38.3.tgz", + "integrity": "sha512-rnK6hJBS6mwc+Bkab+PGPs9OiS0i/3kdTO+CkI8V0/VrW3vmz7O2Pxjw/owOlmo6PKEIxRSeZKv/kuL9itnpYA==", + "dev": true, + "bin": { + "ncc": "dist/ncc/cli.js" + } + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==" + }, + "node_modules/calculate-correlation": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/calculate-correlation/-/calculate-correlation-1.2.3.tgz", + "integrity": "sha512-WLoM4ZXJcFcnqhiYtTCBoKaNoWM6O0daL/7jbctM+ZJU2tNdUHH+mvxeLSc11Kgd/jsNHtnVW6lGlxA0H+MmiQ==" + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + }, + "node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/undici": { + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + } + } +} diff --git a/.github/actions/odd-resource-analysis/package.json b/.github/actions/odd-resource-analysis/package.json new file mode 100644 index 00000000000..30a28ac529d --- /dev/null +++ b/.github/actions/odd-resource-analysis/package.json @@ -0,0 +1,22 @@ +{ + "name": "odd-memory-analysis", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build": "ncc build action/index.js -o dist" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@actions/core": "1.11.1", + "@actions/github": "6.0.0", + "calculate-correlation": "^1.2.3", + "node-fetch": "2.6.9" + }, + "devDependencies": { + "@vercel/ncc": "0.38.3", + "prettier": "2.7.1" + } +} diff --git a/.github/workflows/abr-testing-lint-test.yaml b/.github/workflows/abr-testing-lint-test.yaml index e103c61efdd..447597c2b89 100644 --- a/.github/workflows/abr-testing-lint-test.yaml +++ b/.github/workflows/abr-testing-lint-test.yaml @@ -36,11 +36,11 @@ jobs: runs-on: 'windows-latest' steps: - name: Checkout opentrons repo - uses: 'actions/checkout@v3' + uses: 'actions/checkout@v4' with: fetch-depth: 0 - name: Setup Node - uses: 'actions/setup-node@v3' + uses: 'actions/setup-node@v4' with: node-version: '12' - name: Setup Python @@ -52,8 +52,6 @@ jobs: with: project: 'abr-testing' - name: lint - run: - make -C abr-testing lint + run: make -C abr-testing lint - name: test - run: - make -C abr-testing test + run: make -C abr-testing test diff --git a/.github/workflows/analyses-snapshot-lint.yaml b/.github/workflows/analyses-snapshot-lint.yaml index 90724f3c7a2..7a51a5e976a 100644 --- a/.github/workflows/analyses-snapshot-lint.yaml +++ b/.github/workflows/analyses-snapshot-lint.yaml @@ -27,7 +27,7 @@ jobs: - name: Setup Python uses: 'actions/setup-python@v5' with: - python-version: '3.12' + python-version: '3.13.0' cache: 'pipenv' cache-dependency-path: analyses-snapshot-testing/Pipfile.lock - name: Setup diff --git a/.github/workflows/analyses-snapshot-test.yaml b/.github/workflows/analyses-snapshot-test.yaml index 26576899188..fffdd6b667d 100644 --- a/.github/workflows/analyses-snapshot-test.yaml +++ b/.github/workflows/analyses-snapshot-test.yaml @@ -45,12 +45,13 @@ jobs: timeout-minutes: 15 runs-on: ubuntu-latest env: + BASE_IMAGE_NAME: opentrons-python-base:3.10 ANALYSIS_REF: ${{ github.event.inputs.ANALYSIS_REF || github.head_ref || 'edge' }} SNAPSHOT_REF: ${{ github.event.inputs.SNAPSHOT_REF || github.head_ref || 'edge' }} # If we're running because of workflow_dispatch, use the user input to decide - # whether to open a PR on failure. Otherwise, there is no user input, so always - # open a PR on failure. - OPEN_PR_ON_FAILURE: ${{ (github.event_name == 'workflow_dispatch' && github.events.inputs.OPEN_PR_ON_FAILURE) || ((github.event_name != 'workflow_dispatch') && (contains(github.event.pull_request.labels.*.name, 'gen-analyses-snapshot-pr'))) }} + # whether to open a PR on failure. Otherwise, there is no user input, + # so we only open a PR if the PR has the label 'gen-analyses-snapshot-pr' + OPEN_PR_ON_FAILURE: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.OPEN_PR_ON_FAILURE) || ((github.event_name != 'workflow_dispatch') && (contains(github.event.pull_request.labels.*.name, 'gen-analyses-snapshot-pr'))) }} PR_TARGET_BRANCH: ${{ github.event.pull_request.base.ref || 'not a pr'}} steps: - name: Checkout Repository @@ -71,14 +72,29 @@ jobs: echo "Analyses snapshots match ${{ env.PR_TARGET_BRANCH }} snapshots." fi - - name: Docker Build + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build base image + id: build_base_image + uses: docker/build-push-action@v6 + with: + context: analyses-snapshot-testing/citools + file: analyses-snapshot-testing/citools/Dockerfile.base + push: false + load: true + tags: ${{ env.BASE_IMAGE_NAME }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Build analysis image working-directory: analyses-snapshot-testing - run: make build-opentrons-analysis + run: make build-opentrons-analysis BASE_IMAGE_NAME=${{ env.BASE_IMAGE_NAME }} ANALYSIS_REF=${{ env.ANALYSIS_REF }} CACHEBUST=${{ github.run_number }} - - name: Set up Python 3.12 + - name: Set up Python 3.13 uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13.0' cache: 'pipenv' cache-dependency-path: analyses-snapshot-testing/Pipfile.lock @@ -107,13 +123,13 @@ jobs: - name: Create Snapshot update Request id: create_pull_request if: always() && steps.handle_failure.outcome == 'success' && env.OPEN_PR_ON_FAILURE == 'true' && github.event_name == 'pull_request' - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: commit-message: 'fix(analyses-snapshot-testing): heal analyses snapshots' title: 'fix(analyses-snapshot-testing): heal ${{ env.ANALYSIS_REF }} snapshots' body: 'This PR was requested on the PR https://github.com/${{ github.repository }}/pull/${{ github.event.pull_request.number }}' - branch: 'analyses-snapshot-testing/${{ env.ANALYSIS_REF }}-from-${{ env.SNAPSHOT_REF}}' - base: ${{ env.SNAPSHOT_REF}} + branch: 'analyses-snapshot-testing/${{ env.ANALYSIS_REF }}-from-${{ env.SNAPSHOT_REF }}' + base: ${{ env.SNAPSHOT_REF }} - name: Comment on feature PR if: always() && steps.create_pull_request.outcome == 'success' && github.event_name == 'pull_request' @@ -130,10 +146,10 @@ jobs: - name: Create Snapshot update Request on edge overnight failure if: always() && steps.handle_failure.outcome == 'success' && github.event_name == 'schedule' - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: # scheduled run uses the default values for ANALYSIS_REF and SNAPSHOT_REF which are edge commit-message: 'fix(analyses-snapshot-testing): heal ${{ env.ANALYSIS_REF }} snapshots' title: 'fix(analyses-snapshot-testing): heal ${{ env.ANALYSIS_REF }} snapshots' body: 'The ${{ env.ANALYSIS_REF }} overnight analyses snapshot test is failing. This PR was opened to alert us to the failure.' - branch: 'analyses-snapshot-testing/${{ env.ANALYSIS_REF }}-from-${{ env.SNAPSHOT_REF}}' - base: ${{ env.SNAPSHOT_REF}} + branch: 'analyses-snapshot-testing/${{ env.ANALYSIS_REF }}-from-${{ env.SNAPSHOT_REF }}' + base: ${{ env.SNAPSHOT_REF }} diff --git a/.github/workflows/api-test-lint-deploy.yaml b/.github/workflows/api-test-lint-deploy.yaml index 5143c6e8021..6297cb7c747 100644 --- a/.github/workflows/api-test-lint-deploy.yaml +++ b/.github/workflows/api-test-lint-deploy.yaml @@ -51,12 +51,12 @@ jobs: timeout-minutes: 10 runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: 'actions/setup-python@v4' with: python-version: '3.10' @@ -84,7 +84,7 @@ jobs: with-ot-hardware: 'true' runs-on: '${{ matrix.os }}' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 # https://github.com/actions/checkout/issues/290 @@ -93,9 +93,9 @@ jobs: run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: 'actions/setup-python@v4' with: python-version: ${{ matrix.python }} @@ -134,7 +134,7 @@ jobs: runs-on: 'ubuntu-22.04' if: github.event_name == 'push' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 # https://github.com/actions/checkout/issues/290 @@ -143,9 +143,9 @@ jobs: run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: 'actions/setup-python@v4' with: python-version: '3.10' diff --git a/.github/workflows/app-test-build-deploy.yaml b/.github/workflows/app-test-build-deploy.yaml index 8c3bd21503d..da43d601115 100644 --- a/.github/workflows/app-test-build-deploy.yaml +++ b/.github/workflows/app-test-build-deploy.yaml @@ -56,10 +56,10 @@ jobs: name: 'opentrons app frontend unit tests' timeout-minutes: 60 steps: - - uses: 'actions/checkout@v3' - - uses: 'actions/setup-node@v3' + - uses: 'actions/checkout@v4' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install udev' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved @@ -97,11 +97,15 @@ jobs: strategy: matrix: os: ['windows-2022', 'ubuntu-22.04', 'macos-latest'] - name: 'opentrons app backend unit tests on ${{matrix.os}}' + shell: ['app-shell', 'app-shell-odd', 'discovery-client'] + exclude: + - os: 'windows-2022' + shell: 'app-shell-odd' + name: 'opentrons ${{matrix.shell}} unit tests on ${{matrix.os}}' timeout-minutes: 60 runs-on: ${{ matrix.os }} steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 # https://github.com/actions/checkout/issues/290 @@ -110,9 +114,9 @@ jobs: run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: actions/setup-python@v4 with: python-version: '3.10' @@ -144,7 +148,7 @@ jobs: yarn config set cache-folder ${{ github.workspace }}/.yarn-cache make setup-js - name: 'test native(er) packages' - run: make test-js-internal tests="app-shell/src app-shell-odd/src discovery-client/src" cov_opts="--coverage=true" + run: make test-js-internal tests="${{matrix.shell}}/src" cov_opts="--coverage=true" - name: 'Upload coverage report' uses: 'codecov/codecov-action@v3' with: @@ -257,7 +261,7 @@ jobs: echo "bucket=${{env._APP_DEPLOY_BUCKET_OT3}}" >> $GITHUB_OUTPUT echo "folder=${{env._APP_DEPLOY_FOLDER_OT3}}" >> $GITHUB_OUTPUT fi - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 # https://github.com/actions/checkout/issues/290 @@ -266,9 +270,9 @@ jobs: run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: actions/setup-python@v4 with: python-version: '3.10' @@ -304,7 +308,7 @@ jobs: if: startsWith(matrix.os, 'windows') && contains(needs.determine-build-type.outputs.type, 'release') shell: bash run: | - echo "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12 + echo "${{ secrets.SM_CLIENT_CERT_FILE_B64_V2 }}" | base64 --decode > /d/Certificate_pkcs12.p12 echo "${{ secrets.WINDOWS_CSC_B64}}" | base64 --decode > /d/opentrons_labworks_inc.crt echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH @@ -314,12 +318,12 @@ jobs: if: startsWith(matrix.os, 'windows') && contains(needs.determine-build-type.outputs.type, 'release') shell: cmd env: - SM_HOST: ${{ secrets.SM_HOST }} + SM_HOST: ${{ secrets.SM_HOST_V2 }} SM_CLIENT_CERT_FILE: "D:\\Certificate_pkcs12.p12" - SM_CLIENT_CERT_PASSWORD: ${{secrets.SM_CLIENT_CERT_PASSWORD}} - SM_API_KEY: ${{secrets.SM_API_KEY}} + SM_CLIENT_CERT_PASSWORD: ${{secrets.SM_CLIENT_CERT_PASSWORD_V2}} + SM_API_KEY: ${{secrets.SM_API_KEY_V2}} run: | - curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/Keylockertools-windows-x64.msi/download -H "x-api-key:${{secrets.SM_API_KEY}}" -o Keylockertools-windows-x64.msi + curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/Keylockertools-windows-x64.msi/download -H "x-api-key:${{secrets.SM_API_KEY_V2}}" -o Keylockertools-windows-x64.msi msiexec /i Keylockertools-windows-x64.msi /quiet /qn smksp_registrar.exe list smctl.exe keypair ls @@ -327,6 +331,15 @@ jobs: smksp_cert_sync.exe smctl.exe healthcheck --all + # Do the frontend dist bundle + - name: 'bundle ${{matrix.variant}} frontend' + env: + OT_APP_MIXPANEL_ID: ${{ secrets.OT_APP_MIXPANEL_ID }} + OT_APP_INTERCOM_ID: ${{ secrets.OT_APP_INTERCOM_ID }} + OPENTRONS_PROJECT: ${{ steps.project.outputs.project }} + run: | + make -C app dist + # build the desktop app and deploy it - name: 'build ${{matrix.variant}} app for ${{ matrix.os }}' if: matrix.target == 'desktop' @@ -335,18 +348,18 @@ jobs: OT_APP_MIXPANEL_ID: ${{ secrets.OT_APP_MIXPANEL_ID }} OT_APP_INTERCOM_ID: ${{ secrets.OT_APP_INTERCOM_ID }} WINDOWS_SIGN: ${{ format('{0}', contains(needs.determine-build-type.outputs.type, 'release')) }} - SM_HOST: ${{secrets.SM_HOST}} + SM_CODE_SIGNING_CERT_SHA1_HASH: ${{secrets.SM_CODE_SIGNING_CERT_SHA1_HASH_V2}} + SM_KEYPAIR_ALIAS: ${{secrets.SM_KEYPAIR_ALIAS_V2}} + SM_HOST: ${{ secrets.SM_HOST_V2 }} SM_CLIENT_CERT_FILE: "D:\\Certificate_pkcs12.p12" - SM_CLIENT_CERT_PASSWORD: ${{secrets.SM_CLIENT_CERT_PASSWORD}} - SM_API_KEY: ${{secrets.SM_API_KEY}} - SM_CODE_SIGNING_CERT_SHA1_HASH: ${{secrets.SM_CODE_SIGNING_CERT_SHA1_HASH}} - SM_KEYPAIR_ALIAS: ${{secrets.SM_KEYPAIR_ALIAS}} + SM_CLIENT_CERT_PASSWORD: ${{secrets.SM_CLIENT_CERT_PASSWORD_V2}} + SM_API_KEY: ${{secrets.SM_API_KEY_V2}} WINDOWS_CSC_FILEPATH: "D:\\opentrons_labworks_inc.crt" - CSC_LINK: ${{ secrets.OT_APP_CSC_MACOS }} - CSC_KEY_PASSWORD: ${{ secrets.OT_APP_CSC_KEY_MACOS }} - APPLE_ID: ${{ secrets.OT_APP_APPLE_ID }} - APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.OT_APP_APPLE_ID_PASSWORD }} - APPLE_TEAM_ID: ${{ secrets.OT_APP_APPLE_TEAM_ID }} + CSC_LINK: ${{ secrets.OT_APP_CSC_MACOS_V2 }} + CSC_KEY_PASSWORD: ${{ secrets.OT_APP_CSC_KEY_MACOS_V2 }} + APPLE_ID: ${{ secrets.OT_APP_APPLE_ID_V2 }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.OT_APP_APPLE_ID_PASSWORD_V2 }} + APPLE_TEAM_ID: ${{ secrets.OT_APP_APPLE_TEAM_ID_V2 }} HOST_PYTHON: python OPENTRONS_PROJECT: ${{ steps.project.outputs.project }} OT_APP_DEPLOY_BUCKET: ${{ steps.project.outputs.bucket }} @@ -475,7 +488,7 @@ jobs: _ACCESS_URL: https://${{env._APP_DEPLOY_BUCKET_ROBOTSTACK}}/${{env._APP_DEPLOY_FOLDER_ROBOTSTACK}} - name: 'pull repo for scripts' - uses: 'actions/checkout@v3' + uses: 'actions/checkout@v4' with: path: ./monorepo # https://github.com/actions/checkout/issues/290 @@ -485,9 +498,9 @@ jobs: cd ./monorepo git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install udev' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved diff --git a/.github/workflows/components-test-build-deploy.yaml b/.github/workflows/components-test-build-deploy.yaml index 01d4355e355..3eba5c64a4b 100644 --- a/.github/workflows/components-test-build-deploy.yaml +++ b/.github/workflows/components-test-build-deploy.yaml @@ -43,10 +43,10 @@ jobs: timeout-minutes: 30 runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' - - uses: 'actions/setup-node@v3' + - uses: 'actions/checkout@v4' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install udev for usb-detection' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved @@ -79,10 +79,10 @@ jobs: if: github.event_name != 'pull_request' needs: ['js-unit-test'] steps: - - uses: 'actions/checkout@v3' - - uses: 'actions/setup-node@v3' + - uses: 'actions/checkout@v4' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install udev for usb-detection' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved @@ -140,16 +140,16 @@ jobs: ['js-unit-test', 'build-components-storybook', 'determine-build-type'] if: needs.determine-build-type.outputs.type != 'none' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' # https://github.com/actions/checkout/issues/290 - name: 'Fix actions/checkout odd handling of tags' if: startsWith(github.ref, 'refs/tags') run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'set complex environment variables' id: 'set-vars' uses: actions/github-script@v6 @@ -180,16 +180,16 @@ jobs: needs: ['js-unit-test', 'determine-build-type'] if: needs.determine-build-type.outputs.type == 'publish' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' # https://github.com/actions/checkout/issues/290 - name: 'Fix actions/checkout odd handling of tags' if: startsWith(github.ref, 'refs/tags') run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' registry-url: 'https://registry.npmjs.org' - name: 'install udev for usb-detection' run: | @@ -203,9 +203,6 @@ jobs: make setup-js - name: 'build typescript types' run: make -C components build-ts - - name: 'build js bundle' - run: | - make -C components lib # replace package.json stub version number with version from tag - name: 'set version number' run: | @@ -213,12 +210,15 @@ jobs: VERSION_STRING=$(echo ${{ github.ref }} | sed 's/refs\/tags\/components@//') json -I -f ./components/package.json -e "this.version=\"$VERSION_STRING\"" json -I -f ./components/package.json -e "this.dependencies['@opentrons/shared-data']=\"$VERSION_STRING\"" - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' registry-url: 'https://registry.npmjs.org' - name: 'publish to npm registry' env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | - cd ./components && echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > ./.npmrc && npm publish --access public + cd ./components + echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" > ./.npmrc + ls -R # Debug: View contents of ./components + npm publish --access public diff --git a/.github/workflows/docs-build.yaml b/.github/workflows/docs-build.yaml index 08b1c2b76cf..6a4f49f0d20 100644 --- a/.github/workflows/docs-build.yaml +++ b/.github/workflows/docs-build.yaml @@ -40,7 +40,7 @@ jobs: name: opentrons documentation build runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 # https://github.com/actions/checkout/issues/290 @@ -49,9 +49,9 @@ jobs: run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: 'actions/setup-python@v3' with: python-version: '3.10' diff --git a/.github/workflows/g-code-confirm-tests.yaml b/.github/workflows/g-code-confirm-tests.yaml index 146fa96b9a2..9c43bfe16d8 100644 --- a/.github/workflows/g-code-confirm-tests.yaml +++ b/.github/workflows/g-code-confirm-tests.yaml @@ -1,4 +1,4 @@ -name: "G-Code-Confirm" +name: 'G-Code-Confirm' on: # Run on any change to the api directory @@ -31,20 +31,14 @@ jobs: confirm-g-code: strategy: matrix: - command: [ - '2-modules', - 'swift-smoke', - 'swift-turbo', - 'omega', - 'fast' - ] + command: ['2-modules', 'swift-smoke', 'swift-turbo', 'omega', 'fast'] name: 'Confirm G-Code (${{ matrix.command }})' runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: node-version: '12' - uses: 'actions/setup-python@v3' @@ -54,7 +48,7 @@ jobs: with: project: 'g-code-testing' - - name: "Verify no missing comparison files" + - name: 'Verify no missing comparison files' run: make -C g-code-testing check-for-missing-comparison-files - name: 'Run & Compare to comparison files' diff --git a/.github/workflows/g-code-testing-lint-test.yaml b/.github/workflows/g-code-testing-lint-test.yaml index e174bc7ac52..9da53b78182 100644 --- a/.github/workflows/g-code-testing-lint-test.yaml +++ b/.github/workflows/g-code-testing-lint-test.yaml @@ -42,7 +42,7 @@ jobs: name: 'g-code-testing package linting and tests' runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 - name: 'install udev' @@ -50,9 +50,9 @@ jobs: # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list sudo apt-get update && sudo apt-get install libudev-dev - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'set complex environment variables' id: 'set-vars' uses: actions/github-script@v6 diff --git a/.github/workflows/hardware-lint-test.yaml b/.github/workflows/hardware-lint-test.yaml index f5e701ea883..8714a40d250 100644 --- a/.github/workflows/hardware-lint-test.yaml +++ b/.github/workflows/hardware-lint-test.yaml @@ -44,11 +44,11 @@ jobs: runs-on: 'ubuntu-20.04' steps: - name: Checkout opentrons repo - uses: 'actions/checkout@v3' + uses: 'actions/checkout@v4' with: fetch-depth: 0 - name: Setup Node - uses: 'actions/setup-node@v3' + uses: 'actions/setup-node@v4' with: node-version: '12' diff --git a/.github/workflows/hardware-testing-protocols.yaml b/.github/workflows/hardware-testing-protocols.yaml index ee59d2dc25c..1573c69380a 100644 --- a/.github/workflows/hardware-testing-protocols.yaml +++ b/.github/workflows/hardware-testing-protocols.yaml @@ -38,12 +38,12 @@ jobs: runs-on: 'ubuntu-20.04' steps: - name: Checkout opentrons repo - uses: 'actions/checkout@v3' + uses: 'actions/checkout@v4' with: fetch-depth: 0 - name: Setup Node - uses: 'actions/setup-node@v3' + uses: 'actions/setup-node@v4' with: node-version: '12' diff --git a/.github/workflows/hardware-testing.yaml b/.github/workflows/hardware-testing.yaml index bb738b13e4b..116ca872c85 100644 --- a/.github/workflows/hardware-testing.yaml +++ b/.github/workflows/hardware-testing.yaml @@ -43,12 +43,12 @@ jobs: runs-on: 'ubuntu-20.04' steps: - name: Checkout opentrons repo - uses: 'actions/checkout@v3' + uses: 'actions/checkout@v4' with: fetch-depth: 0 - name: Setup Node - uses: 'actions/setup-node@v3' + uses: 'actions/setup-node@v4' with: node-version: '12' diff --git a/.github/workflows/http-docs-build.yaml b/.github/workflows/http-docs-build.yaml index 6294eeb2172..f2c21368d5e 100644 --- a/.github/workflows/http-docs-build.yaml +++ b/.github/workflows/http-docs-build.yaml @@ -40,7 +40,7 @@ jobs: name: HTTP API reference build runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 # https://github.com/actions/checkout/issues/290 @@ -52,9 +52,9 @@ jobs: - uses: 'actions/setup-python@v3' with: python-version: '3.10' - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: './.github/actions/python/setup' with: project: 'robot-server' diff --git a/.github/workflows/js-check.yaml b/.github/workflows/js-check.yaml index 139d3b618ad..807d4a2570c 100644 --- a/.github/workflows/js-check.yaml +++ b/.github/workflows/js-check.yaml @@ -42,10 +42,10 @@ jobs: runs-on: 'ubuntu-22.04' timeout-minutes: 20 steps: - - uses: 'actions/checkout@v3' - - uses: 'actions/setup-node@v3' + - uses: 'actions/checkout@v4' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'set complex environment variables' id: 'set-vars' uses: actions/github-script@v6 diff --git a/.github/workflows/ll-test-build-deploy.yaml b/.github/workflows/ll-test-build-deploy.yaml index 140537593e2..e00e9b9cab9 100644 --- a/.github/workflows/ll-test-build-deploy.yaml +++ b/.github/workflows/ll-test-build-deploy.yaml @@ -40,12 +40,12 @@ jobs: js-unit-test: name: 'labware library unit tests' timeout-minutes: 20 - runs-on: 'ubuntu-20.04' + runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' - - uses: 'actions/setup-node@v3' + - uses: 'actions/checkout@v4' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' # https://github.com/actions/checkout/issues/290 - name: 'Fix actions/checkout odd handling of tags' if: startsWith(github.ref, 'refs/tags') @@ -83,18 +83,18 @@ jobs: name: 'labware library e2e tests' needs: ['js-unit-test'] timeout-minutes: 30 - runs-on: 'ubuntu-20.04' + runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' # https://github.com/actions/checkout/issues/290 - name: 'Fix actions/checkout odd handling of tags' if: startsWith(github.ref, 'refs/tags') run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install libudev for usb-detection' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved @@ -123,10 +123,10 @@ jobs: name: 'build labware library artifact' needs: ['js-unit-test'] timeout-minutes: 30 - runs-on: 'ubuntu-20.04' + runs-on: 'ubuntu-22.04' if: github.event_name != 'pull_request' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 # https://github.com/actions/checkout/issues/290 @@ -135,9 +135,9 @@ jobs: run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install libudev for usb-detection' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved @@ -170,20 +170,20 @@ jobs: path: labware-library/dist deploy-ll: name: 'deploy LL artifact to S3' - runs-on: 'ubuntu-20.04' + runs-on: 'ubuntu-22.04' needs: ['js-unit-test', 'e2e-test', 'build-ll'] if: github.event_name != 'pull_request' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' # https://github.com/actions/checkout/issues/290 - name: 'Fix actions/checkout odd handling of tags' if: startsWith(github.ref, 'refs/tags') run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install udev for usb-detection' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved diff --git a/.github/workflows/odd-memory-usage-test.yaml b/.github/workflows/odd-memory-usage-test.yaml new file mode 100644 index 00000000000..ce79fb4dc32 --- /dev/null +++ b/.github/workflows/odd-memory-usage-test.yaml @@ -0,0 +1,24 @@ +name: 'ODD Memory Usage Test' + +on: + schedule: + - cron: '30 12 * * *' + workflow_dispatch: + +jobs: + analyze-memory: + name: 'ODD Memory Usage Test' + runs-on: 'ubuntu-latest' + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: 'actions/checkout@v4' + + - name: Run memory analysis + uses: ./.github/actions/odd-resource-analysis + with: + mixpanel-user: ${{ secrets.MIXPANEL_INGEST_USER }} + mixpanel-secret: ${{ secrets.MIXPANEL_INGEST_SECRET }} + mixpanel-project-id: 12345 + previous-version-count: '2' \ No newline at end of file diff --git a/.github/workflows/opentrons-ai-client-staging-continuous-deploy.yaml b/.github/workflows/opentrons-ai-client-staging-continuous-deploy.yaml index af767b36adc..88c7c70d3ec 100644 --- a/.github/workflows/opentrons-ai-client-staging-continuous-deploy.yaml +++ b/.github/workflows/opentrons-ai-client-staging-continuous-deploy.yaml @@ -23,10 +23,10 @@ jobs: name: 'OpentronsAI client edge continuous deployment to staging' timeout-minutes: 10 steps: - - uses: 'actions/checkout@v3' - - uses: 'actions/setup-node@v3' + - uses: 'actions/checkout@v4' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install udev' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved @@ -52,6 +52,9 @@ jobs: yarn config set cache-folder ${{ github.workspace }}/.yarn-cache make setup-js - name: 'build' + env: + # inject dev id since this is for staging + OT_AI_CLIENT_MIXPANEL_ID: ${{ secrets.OT_AI_CLIENT_MIXPANEL_DEV_ID }} run: | make -C opentrons-ai-client build-staging - name: Configure AWS Credentials diff --git a/.github/workflows/opentrons-ai-client-test-build-deploy.yaml b/.github/workflows/opentrons-ai-client-test-build-deploy.yaml deleted file mode 100644 index 2f569d9bf78..00000000000 --- a/.github/workflows/opentrons-ai-client-test-build-deploy.yaml +++ /dev/null @@ -1,81 +0,0 @@ -# Run tests, build the app, and deploy it cross platform - -name: 'OpentronsAI client test, build, and deploy' - -# ToDo (kk:04/16/2024) Add build and deploy task - -on: - push: - paths: - - 'Makefile' - - 'opentrons-ai-client/**/*' - - 'components/**/*' - - '*.js' - - '*.json' - - 'yarn.lock' - - '.github/workflows/app-test-build-deploy.yaml' - - '.github/workflows/utils.js' - branches: - - '**' - tags: - - 'v*' - - 'ot3@*' - pull_request: - paths: - - 'Makefile' - - 'opentrons-ai-client/**/*' - - 'components/**/*' - - '*.js' - - '*.json' - - 'yarn.lock' - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.ref_name != 'edge' || github.run_id}}-${{ github.ref_type != 'tag' || github.run_id }} - cancel-in-progress: true - -env: - CI: true - -jobs: - js-unit-test: - runs-on: 'ubuntu-22.04' - name: 'opentrons ai frontend unit tests' - timeout-minutes: 60 - steps: - - uses: 'actions/checkout@v3' - - uses: 'actions/setup-node@v3' - with: - node-version: '18.19.0' - - name: 'install udev' - run: | - # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved - sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list - sudo apt-get update && sudo apt-get install libudev-dev - - name: 'set complex environment variables' - id: 'set-vars' - uses: actions/github-script@v6 - with: - script: | - const { buildComplexEnvVars } = require(`${process.env.GITHUB_WORKSPACE}/.github/workflows/utils.js`) - buildComplexEnvVars(core, context) - - name: 'cache yarn cache' - uses: actions/cache@v3 - with: - path: | - ${{ github.workspace }}/.npm-cache/_prebuild - ${{ github.workspace }}/.yarn-cache - key: js-${{ secrets.GH_CACHE_VERSION }}-${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} - - name: 'setup-js' - run: | - npm config set cache ${{ github.workspace }}/.npm-cache - yarn config set cache-folder ${{ github.workspace }}/.yarn-cache - make setup-js - - name: 'test frontend packages' - run: | - make -C opentrons-ai-client test-cov - - name: 'Upload coverage report' - uses: codecov/codecov-action@v3 - with: - files: ./coverage/lcov.info - flags: opentrons-ai-client diff --git a/.github/workflows/opentrons-ai-client-test.yaml b/.github/workflows/opentrons-ai-client-test.yaml new file mode 100644 index 00000000000..0a78cf73da3 --- /dev/null +++ b/.github/workflows/opentrons-ai-client-test.yaml @@ -0,0 +1,77 @@ +# Run tests, build the app, and deploy it cross platform + +name: 'OpentronsAI client test, build, and deploy' + +# ToDo (kk:04/16/2024) Add build and deploy task + +on: + push: + paths: + - 'Makefile' + - 'opentrons-ai-client/**/*' + - 'components/**' + - 'shared-data/**' + - '.github/workflows/opentrons-ai-client-test.yml' + branches: + - '**' + tags: + - 'v*' + - 'ot3@*' + pull_request: + paths: + - 'Makefile' + - 'opentrons-ai-client/**/*' + - 'components/**' + - 'shared-data/**' + - '.github/workflows/opentrons-ai-client-test.yml' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-${{ github.ref_name != 'edge' || github.run_id}}-${{ github.ref_type != 'tag' || github.run_id }} + cancel-in-progress: true + +env: + CI: true + +jobs: + js-unit-test: + runs-on: 'ubuntu-22.04' + name: 'opentrons ai frontend unit tests' + timeout-minutes: 60 + steps: + - uses: 'actions/checkout@v4' + - uses: 'actions/setup-node@v4' + with: + node-version: '22.11.0' + - name: 'install udev' + run: | + # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo apt-get update && sudo apt-get install libudev-dev + - name: 'set complex environment variables' + id: 'set-vars' + uses: actions/github-script@v6 + with: + script: | + const { buildComplexEnvVars } = require(`${process.env.GITHUB_WORKSPACE}/.github/workflows/utils.js`) + buildComplexEnvVars(core, context) + - name: 'cache yarn cache' + uses: actions/cache@v3 + with: + path: | + ${{ github.workspace }}/.npm-cache/_prebuild + ${{ github.workspace }}/.yarn-cache + key: js-${{ secrets.GH_CACHE_VERSION }}-${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + - name: 'setup-js' + run: | + npm config set cache ${{ github.workspace }}/.npm-cache + yarn config set cache-folder ${{ github.workspace }}/.yarn-cache + make setup-js + - name: 'test frontend packages' + run: | + make -C opentrons-ai-client test-cov + - name: 'Upload coverage report' + uses: codecov/codecov-action@v3 + with: + files: ./coverage/lcov.info + flags: opentrons-ai-client diff --git a/.github/workflows/opentrons-ai-production-deploy.yaml b/.github/workflows/opentrons-ai-production-deploy.yaml index 825c3561f25..1850400bbd0 100644 --- a/.github/workflows/opentrons-ai-production-deploy.yaml +++ b/.github/workflows/opentrons-ai-production-deploy.yaml @@ -23,10 +23,10 @@ jobs: name: 'OpentronsAI client prod deploy' timeout-minutes: 10 steps: - - uses: 'actions/checkout@v3' - - uses: 'actions/setup-node@v3' + - uses: 'actions/checkout@v4' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install udev' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved @@ -52,6 +52,8 @@ jobs: yarn config set cache-folder ${{ github.workspace }}/.yarn-cache make setup-js - name: 'build' + env: + OT_AI_CLIENT_MIXPANEL_ID: ${{ secrets.OT_AI_CLIENT_MIXPANEL_ID }} run: | make -C opentrons-ai-client build-production - name: Configure AWS Credentials diff --git a/.github/workflows/opentrons-ai-server-lint-test.yaml b/.github/workflows/opentrons-ai-server-lint-test.yaml index d1a169b024d..8fe9bcb19ea 100644 --- a/.github/workflows/opentrons-ai-server-lint-test.yaml +++ b/.github/workflows/opentrons-ai-server-lint-test.yaml @@ -27,7 +27,7 @@ jobs: - name: Setup Python uses: 'actions/setup-python@v5' with: - python-version: '3.12.4' + python-version: '3.12' cache: 'pipenv' cache-dependency-path: opentrons-ai-server/Pipfile.lock - name: Setup diff --git a/.github/workflows/pd-test-build-deploy.yaml b/.github/workflows/pd-test-build-deploy.yaml index 9f23419da94..ce28d79e9a5 100644 --- a/.github/workflows/pd-test-build-deploy.yaml +++ b/.github/workflows/pd-test-build-deploy.yaml @@ -42,16 +42,16 @@ jobs: runs-on: 'ubuntu-22.04' timeout-minutes: 30 steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' # https://github.com/actions/checkout/issues/290 - name: 'Fix actions/checkout odd handling of tags' if: startsWith(github.ref, 'refs/tags') run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install udev for usb-detection' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved @@ -88,7 +88,7 @@ jobs: os: ['ubuntu-22.04'] runs-on: '${{ matrix.os }}' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 # https://github.com/actions/checkout/issues/290 @@ -97,9 +97,9 @@ jobs: run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install udev for usb-detection' if: startsWith(matrix.os, 'ubuntu') run: | @@ -128,7 +128,7 @@ jobs: runs-on: 'ubuntu-22.04' if: github.event_name != 'pull_request' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 # https://github.com/actions/checkout/issues/290 @@ -137,9 +137,9 @@ jobs: run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install udev for usb-detection' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved @@ -162,7 +162,7 @@ jobs: OT_PD_MIXPANEL_ID: ${{ secrets.OT_PD_MIXPANEL_ID }} OT_PD_MIXPANEL_DEV_ID: ${{ secrets.OT_PD_MIXPANEL_DEV_ID }} run: | - make -C protocol-designer + make -C protocol-designer NODE_ENV=development - name: 'upload github artifact' uses: actions/upload-artifact@v3 with: @@ -174,16 +174,16 @@ jobs: needs: ['js-unit-test', 'build-pd'] if: github.event_name != 'pull_request' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' # https://github.com/actions/checkout/issues/290 - name: 'Fix actions/checkout odd handling of tags' if: startsWith(github.ref, 'refs/tags') run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install udev for usb-detection' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved @@ -215,4 +215,7 @@ jobs: aws configure set role_arn ${{ secrets.OT_PD_DEPLOY_ROLE }} --profile deploy aws configure set source_profile identity --profile deploy aws s3 sync ./dist s3://sandbox.designer.opentrons.com/${{ env.OT_BRANCH }} --acl=public-read --profile=deploy + # invalidate both sandbox.opentrons.com and www.sandbox.opentrons.com cloudfront caches + aws cloudfront create-invalidation --distribution-id ${{ secrets.PD_CLOUDFRONT_SANDBOX_DISTRIBUTION_ID }} --paths "/*" --profile deploy + aws cloudfront create-invalidation --distribution-id ${{ secrets.PD_CLOUDFRONT_SANDBOX_WWW_DISTRIBUTION_ID }} --paths "/*" --profile deploy shell: bash diff --git a/.github/workflows/react-api-client-test.yaml b/.github/workflows/react-api-client-test.yaml index a8f5ed959b2..8a8759f12e1 100644 --- a/.github/workflows/react-api-client-test.yaml +++ b/.github/workflows/react-api-client-test.yaml @@ -32,14 +32,14 @@ env: jobs: js-unit-test: - name: 'react-api-client unit tests' + name: 'api-client and react-api-client unit tests' timeout-minutes: 30 runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' - - uses: 'actions/setup-node@v3' + - uses: 'actions/checkout@v4' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install libudev for usb-detection' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved @@ -59,8 +59,8 @@ jobs: npm config set cache ./.npm-cache yarn config set cache-folder ./.yarn-cache make setup-js - - name: 'run react-api-client unit tests' - run: make -C react-api-client test-cov + - name: 'run api-client and react-api-client unit tests' + run: make -C api-client test-cov - name: 'Upload coverage report' uses: codecov/codecov-action@v3 with: diff --git a/.github/workflows/robot-server-lint-test.yaml b/.github/workflows/robot-server-lint-test.yaml index 96d1969121b..6735ab36136 100644 --- a/.github/workflows/robot-server-lint-test.yaml +++ b/.github/workflows/robot-server-lint-test.yaml @@ -56,12 +56,12 @@ jobs: matrix: with-ot-hardware: ['true', 'false'] steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: 'actions/setup-python@v4' with: python-version: '3.10' diff --git a/.github/workflows/server-utils-lint-test.yaml b/.github/workflows/server-utils-lint-test.yaml index 240d9e0bd25..9c8f37b6c79 100644 --- a/.github/workflows/server-utils-lint-test.yaml +++ b/.github/workflows/server-utils-lint-test.yaml @@ -41,12 +41,12 @@ jobs: timeout-minutes: 10 runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: 'actions/setup-python@v4' with: python-version: '3.10' @@ -62,12 +62,12 @@ jobs: needs: [lint] runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: 'actions/setup-python@v4' with: python-version: '3.10' diff --git a/.github/workflows/shared-data-test-lint-deploy.yaml b/.github/workflows/shared-data-test-lint-deploy.yaml index 39cc4cd30e4..c5858d5da0e 100644 --- a/.github/workflows/shared-data-test-lint-deploy.yaml +++ b/.github/workflows/shared-data-test-lint-deploy.yaml @@ -46,12 +46,12 @@ jobs: timeout-minutes: 10 runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: 'actions/setup-python@v3' with: python-version: '3.10' @@ -75,7 +75,7 @@ jobs: runs-on: '${{ matrix.os }}' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 - name: 'install udev for usb-detection' @@ -86,7 +86,7 @@ jobs: sudo apt-get update && sudo apt-get install libudev-dev - uses: 'actions/setup-node@v1' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: 'actions/setup-python@v4' with: python-version: ${{ matrix.python }} @@ -115,10 +115,10 @@ jobs: runs-on: 'ubuntu-22.04' timeout-minutes: 30 steps: - - uses: 'actions/checkout@v3' - - uses: 'actions/setup-node@v3' + - uses: 'actions/checkout@v4' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install udev' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved @@ -152,7 +152,7 @@ jobs: runs-on: 'ubuntu-22.04' if: github.event_name == 'push' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 # https://github.com/actions/checkout/issues/290 @@ -161,9 +161,9 @@ jobs: run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install udev for usb-detection' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved @@ -223,16 +223,16 @@ jobs: needs: ['js-test', 'publish-switch'] if: needs.publish-switch.outputs.should_publish == 'true' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' # https://github.com/actions/checkout/issues/290 - name: 'Fix actions/checkout odd handling of tags' if: startsWith(github.ref, 'refs/tags') run: | git fetch -f origin ${{ github.ref }}:${{ github.ref }} git checkout ${{ github.ref }} - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' registry-url: 'https://registry.npmjs.org' - name: 'install udev for usb-detection' run: | @@ -252,7 +252,7 @@ jobs: run: | npm config set cache ./.npm-cache yarn config set cache-folder ./.yarn-cache - make setup-js + make setup-js - name: 'build typescript' run: make build-ts - name: 'build library' @@ -265,9 +265,9 @@ jobs: VERSION_STRING=$(echo ${{ github.ref }} | sed -E 's/refs\/tags\/(components|shared-data)@//') json -I -f ./shared-data/package.json -e "this.version=\"$VERSION_STRING\"" cd ./shared-data - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' registry-url: 'https://registry.npmjs.org' - name: 'publish to npm registry' env: diff --git a/.github/workflows/step-generation-test.yaml b/.github/workflows/step-generation-test.yaml index 7ac65f3997e..ac435cf999d 100644 --- a/.github/workflows/step-generation-test.yaml +++ b/.github/workflows/step-generation-test.yaml @@ -35,10 +35,10 @@ jobs: runs-on: 'ubuntu-22.04' timeout-minutes: 30 steps: - - uses: 'actions/checkout@v3' - - uses: 'actions/setup-node@v3' + - uses: 'actions/checkout@v4' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'install udev for usb-detection' run: | # WORKAROUND: Remove microsoft debian repo due to https://github.com/microsoft/linux-package-repositories/issues/130. Remove line below after it is resolved diff --git a/.github/workflows/system-server-lint-test.yaml b/.github/workflows/system-server-lint-test.yaml index 720ca905bd7..ffd526c6834 100644 --- a/.github/workflows/system-server-lint-test.yaml +++ b/.github/workflows/system-server-lint-test.yaml @@ -43,12 +43,12 @@ jobs: timeout-minutes: 10 runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: 'actions/setup-python@v4' with: python-version: '3.10' @@ -64,12 +64,12 @@ jobs: needs: [lint] runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: 'actions/setup-python@v4' with: python-version: '3.10' diff --git a/.github/workflows/tag-releases.yaml b/.github/workflows/tag-releases.yaml index 864f1e45b36..1f8c3ee153a 100644 --- a/.github/workflows/tag-releases.yaml +++ b/.github/workflows/tag-releases.yaml @@ -19,12 +19,12 @@ jobs: # git fetch origin ${{ github.ref_name }}:${{ github.ref_name }} # git checkout ${{ github.ref_name }} # This would pull history for only the tag in question. - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - name: 'cache yarn cache' uses: actions/cache@v3 with: diff --git a/.github/workflows/test-edge-with-hypothesis.yaml b/.github/workflows/test-edge-with-hypothesis.yaml index 15e452dc33a..596a5339d45 100644 --- a/.github/workflows/test-edge-with-hypothesis.yaml +++ b/.github/workflows/test-edge-with-hypothesis.yaml @@ -2,8 +2,8 @@ name: 'Testing Edge with Hypothesis' on: schedule: - - cron: '45 22 * * 1-5' - + - cron: '45 22 * * 1-5' + workflow_dispatch: concurrency: @@ -20,23 +20,23 @@ jobs: timeout-minutes: 120 runs-on: 'ubuntu-latest' steps: - - name: 'Checkout opentrons repo' - uses: 'actions/checkout@v3' - with: - ref: 'edge' - fetch-depth: 0 - - - name: 'Setup Python' - uses: 'actions/setup-python@v5' - with: - python-version: '3.10' - cache: 'pipenv' - cache-dependency-path: 'test-data-generation/Pipfile.lock' - - - name: 'Install Python deps' - uses: './.github/actions/python/setup' - with: - project: 'test-data-generation' - - - name: 'Run Hypothesis tests' - run: 'make -C test-data-generation test' + - name: 'Checkout opentrons repo' + uses: 'actions/checkout@v4' + with: + ref: 'edge' + fetch-depth: 0 + + - name: 'Setup Python' + uses: 'actions/setup-python@v5' + with: + python-version: '3.10' + cache: 'pipenv' + cache-dependency-path: 'test-data-generation/Pipfile.lock' + + - name: 'Install Python deps' + uses: './.github/actions/python/setup' + with: + project: 'test-data-generation' + + - name: 'Run Hypothesis tests' + run: 'make -C test-data-generation test' diff --git a/.github/workflows/update-server-lint-test.yaml b/.github/workflows/update-server-lint-test.yaml index b4d1435838f..1d3164a63cf 100644 --- a/.github/workflows/update-server-lint-test.yaml +++ b/.github/workflows/update-server-lint-test.yaml @@ -41,12 +41,12 @@ jobs: timeout-minutes: 10 runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: 'actions/setup-python@v4' with: python-version: '3.10' @@ -62,12 +62,12 @@ jobs: needs: [lint] runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: 'actions/setup-python@v4' with: python-version: '3.10' diff --git a/.github/workflows/usb-bridge-lint-test.yaml b/.github/workflows/usb-bridge-lint-test.yaml index 2888291871a..bfe11aed61b 100644 --- a/.github/workflows/usb-bridge-lint-test.yaml +++ b/.github/workflows/usb-bridge-lint-test.yaml @@ -41,12 +41,12 @@ jobs: timeout-minutes: 10 runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: 'actions/setup-python@v4' with: python-version: '3.10' @@ -62,12 +62,12 @@ jobs: needs: [lint] runs-on: 'ubuntu-22.04' steps: - - uses: 'actions/checkout@v3' + - uses: 'actions/checkout@v4' with: fetch-depth: 0 - - uses: 'actions/setup-node@v3' + - uses: 'actions/setup-node@v4' with: - node-version: '18.19.0' + node-version: '22.11.0' - uses: 'actions/setup-python@v4' with: python-version: '3.10' diff --git a/.nvmrc b/.nvmrc index 3c032078a4a..fdb2eaaff0c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18 +22.11.0 \ No newline at end of file diff --git a/.storybook/preview.jsx b/.storybook/preview.jsx index 4d97efe9c4d..bdae9a4f833 100644 --- a/.storybook/preview.jsx +++ b/.storybook/preview.jsx @@ -1,4 +1,3 @@ -import React from 'react' import { I18nextProvider } from 'react-i18next' import { GlobalStyle } from '../app/src/atoms/GlobalStyle' import { i18n } from '../app/src/i18n' diff --git a/DEV_SETUP.md b/DEV_SETUP.md index 238f2c7fda3..a334b047745 100644 --- a/DEV_SETUP.md +++ b/DEV_SETUP.md @@ -13,7 +13,7 @@ You will need the following tools installed to develop on the Opentrons platform - curl - ssh - Python v3.10 -- Node.js v18 +- Node.js v22.11.0 ### macOS @@ -85,7 +85,7 @@ nvs --version Now we can use `nvs` to install the currently required Node.js version set in `.nvmrc`. The `auto` command selects the correct version of Node.js any time we're in the `opentrons` project directory. Without `auto`, we would have to manually run `use` or `install` each time we work on the project. ```shell -nvs add 18 +nvs add 22 nvs auto on ``` @@ -202,7 +202,7 @@ Once you are inside the repository for the first time, you should do two things: 3. Run `python --version` to confirm your chosen version. If you get the incorrect version and you're using an Apple silicon Mac, try running `eval "$(pyenv init --path)"` and then `pyenv local 3.10.13`. Then check `python --version` again. ```shell -# confirm Node v18 +# confirm Node v22.11.0 or greater node --version # set Python version, and confirm diff --git a/Makefile b/Makefile index ffdbb8509c0..7a5bfc7a7e9 100755 --- a/Makefile +++ b/Makefile @@ -152,6 +152,10 @@ push: sleep 1 $(MAKE) -C $(UPDATE_SERVER_DIR) push +.PHONY: push-folder +PUSH_HELPER := abr-testing/abr_testing/tools/make_push.py +push-folder: + $(OT_PYTHON) $(PUSH_HELPER) .PHONY: push-ot3 push-ot3: @@ -228,7 +232,7 @@ lint-js-prettier: .PHONY: lint-json lint-json: - yarn eslint --max-warnings 0 --ext .json . + yarn eslint --ignore-pattern "abr-testing/protocols/" --max-warnings 0 --ext .json . .PHONY: lint-css lint-css: diff --git a/abr-testing/.flake8 b/abr-testing/.flake8 index cc618b04ba2..ec1d7b91184 100644 --- a/abr-testing/.flake8 +++ b/abr-testing/.flake8 @@ -21,4 +21,5 @@ docstring-convention = google noqa-require-code = true -# per-file-ignores = +per-file-ignores = + abr_testing/protocols/*: C901 diff --git a/abr-testing/Makefile b/abr-testing/Makefile index f711579ff57..5c5cc6d06df 100644 --- a/abr-testing/Makefile +++ b/abr-testing/Makefile @@ -88,3 +88,14 @@ push-no-restart-ot3: sdist Pipfile.lock .PHONY: push-ot3 push-ot3: push-no-restart-ot3 + +.PHONY: abr-setup +abr-setup: + $(python) abr_testing/tools/abr_setup.py + +.PHONY: simulate +PROTOCOL_DIR := abr_testing/protocols +SIMULATION_TOOL := abr_testing/protocol_simulation/abr_sim_check.py +EXTENSION := .py +simulate: + $(python) $(SIMULATION_TOOL) \ No newline at end of file diff --git a/abr-testing/Pipfile b/abr-testing/Pipfile index c046e523a69..613ca5203f7 100644 --- a/abr-testing/Pipfile +++ b/abr-testing/Pipfile @@ -18,7 +18,8 @@ slackclient = "*" slack-sdk = "*" pandas = "*" pandas-stubs = "*" - +paramiko = "*" +prettier = "*" [dev-packages] atomicwrites = "==1.4.1" diff --git a/abr-testing/Pipfile.lock b/abr-testing/Pipfile.lock index ff84e2bfab3..a1b677f52bb 100644 --- a/abr-testing/Pipfile.lock +++ b/abr-testing/Pipfile.lock @@ -1,8 +1,7 @@ { "_meta": { "hash": { - "sha256": "b82b82f6cc192a520ccaa5f2c94a2dda16993ef31ebd9e140f85f80b6a96cc6a" - + "sha256": "f773f4880fa452637eeaf5e1aebee4ca6a1dc34907f588e0c6f71f0f222dc725" }, "pipfile-spec": 6, "requires": { @@ -21,93 +20,117 @@ "editable": true, "path": "." }, + "aiohappyeyeballs": { + "hashes": [ + "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586", + "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572" + ], + "markers": "python_version >= '3.8'", + "version": "==2.4.3" + }, "aiohttp": { "hashes": [ - "sha256:0605cc2c0088fcaae79f01c913a38611ad09ba68ff482402d3410bf59039bfb8", - "sha256:0a158704edf0abcac8ac371fbb54044f3270bdbc93e254a82b6c82be1ef08f3c", - "sha256:0cbf56238f4bbf49dab8c2dc2e6b1b68502b1e88d335bea59b3f5b9f4c001475", - "sha256:1732102949ff6087589408d76cd6dea656b93c896b011ecafff418c9661dc4ed", - "sha256:18f634d540dd099c262e9f887c8bbacc959847cfe5da7a0e2e1cf3f14dbf2daf", - "sha256:239f975589a944eeb1bad26b8b140a59a3a320067fb3cd10b75c3092405a1372", - "sha256:2faa61a904b83142747fc6a6d7ad8fccff898c849123030f8e75d5d967fd4a81", - "sha256:320e8618eda64e19d11bdb3bd04ccc0a816c17eaecb7e4945d01deee2a22f95f", - "sha256:38d80498e2e169bc61418ff36170e0aad0cd268da8b38a17c4cf29d254a8b3f1", - "sha256:3916c8692dbd9d55c523374a3b8213e628424d19116ac4308e434dbf6d95bbdd", - "sha256:393c7aba2b55559ef7ab791c94b44f7482a07bf7640d17b341b79081f5e5cd1a", - "sha256:3b7b30258348082826d274504fbc7c849959f1989d86c29bc355107accec6cfb", - "sha256:3fcb4046d2904378e3aeea1df51f697b0467f2aac55d232c87ba162709478c46", - "sha256:4109adee842b90671f1b689901b948f347325045c15f46b39797ae1bf17019de", - "sha256:4558e5012ee03d2638c681e156461d37b7a113fe13970d438d95d10173d25f78", - "sha256:45731330e754f5811c314901cebdf19dd776a44b31927fa4b4dbecab9e457b0c", - "sha256:4715a9b778f4293b9f8ae7a0a7cef9829f02ff8d6277a39d7f40565c737d3771", - "sha256:471f0ef53ccedec9995287f02caf0c068732f026455f07db3f01a46e49d76bbb", - "sha256:4d3ebb9e1316ec74277d19c5f482f98cc65a73ccd5430540d6d11682cd857430", - "sha256:4ff550491f5492ab5ed3533e76b8567f4b37bd2995e780a1f46bca2024223233", - "sha256:52c27110f3862a1afbcb2af4281fc9fdc40327fa286c4625dfee247c3ba90156", - "sha256:55b39c8684a46e56ef8c8d24faf02de4a2b2ac60d26cee93bc595651ff545de9", - "sha256:5a7ee16aab26e76add4afc45e8f8206c95d1d75540f1039b84a03c3b3800dd59", - "sha256:5ca51eadbd67045396bc92a4345d1790b7301c14d1848feaac1d6a6c9289e888", - "sha256:5d6b3f1fabe465e819aed2c421a6743d8debbde79b6a8600739300630a01bf2c", - "sha256:60cdbd56f4cad9f69c35eaac0fbbdf1f77b0ff9456cebd4902f3dd1cf096464c", - "sha256:6380c039ec52866c06d69b5c7aad5478b24ed11696f0e72f6b807cfb261453da", - "sha256:639d0042b7670222f33b0028de6b4e2fad6451462ce7df2af8aee37dcac55424", - "sha256:66331d00fb28dc90aa606d9a54304af76b335ae204d1836f65797d6fe27f1ca2", - "sha256:67c3119f5ddc7261d47163ed86d760ddf0e625cd6246b4ed852e82159617b5fb", - "sha256:694d828b5c41255e54bc2dddb51a9f5150b4eefa9886e38b52605a05d96566e8", - "sha256:6ae79c1bc12c34082d92bf9422764f799aee4746fd7a392db46b7fd357d4a17a", - "sha256:702e2c7c187c1a498a4e2b03155d52658fdd6fda882d3d7fbb891a5cf108bb10", - "sha256:714d4e5231fed4ba2762ed489b4aec07b2b9953cf4ee31e9871caac895a839c0", - "sha256:7b179eea70833c8dee51ec42f3b4097bd6370892fa93f510f76762105568cf09", - "sha256:7f64cbd44443e80094309875d4f9c71d0401e966d191c3d469cde4642bc2e031", - "sha256:82a6a97d9771cb48ae16979c3a3a9a18b600a8505b1115cfe354dfb2054468b4", - "sha256:84dabd95154f43a2ea80deffec9cb44d2e301e38a0c9d331cc4aa0166fe28ae3", - "sha256:8676e8fd73141ded15ea586de0b7cda1542960a7b9ad89b2b06428e97125d4fa", - "sha256:88e311d98cc0bf45b62fc46c66753a83445f5ab20038bcc1b8a1cc05666f428a", - "sha256:8b4f72fbb66279624bfe83fd5eb6aea0022dad8eec62b71e7bf63ee1caadeafe", - "sha256:8c64a6dc3fe5db7b1b4d2b5cb84c4f677768bdc340611eca673afb7cf416ef5a", - "sha256:8cf142aa6c1a751fcb364158fd710b8a9be874b81889c2bd13aa8893197455e2", - "sha256:8d1964eb7617907c792ca00b341b5ec3e01ae8c280825deadbbd678447b127e1", - "sha256:93e22add827447d2e26d67c9ac0161756007f152fdc5210277d00a85f6c92323", - "sha256:9c69e77370cce2d6df5d12b4e12bdcca60c47ba13d1cbbc8645dd005a20b738b", - "sha256:9dbc053ac75ccc63dc3a3cc547b98c7258ec35a215a92bd9f983e0aac95d3d5b", - "sha256:9e3a1ae66e3d0c17cf65c08968a5ee3180c5a95920ec2731f53343fac9bad106", - "sha256:a6ea1a5b409a85477fd8e5ee6ad8f0e40bf2844c270955e09360418cfd09abac", - "sha256:a81b1143d42b66ffc40a441379387076243ef7b51019204fd3ec36b9f69e77d6", - "sha256:ad7f2919d7dac062f24d6f5fe95d401597fbb015a25771f85e692d043c9d7832", - "sha256:afc52b8d969eff14e069a710057d15ab9ac17cd4b6753042c407dcea0e40bf75", - "sha256:b3df71da99c98534be076196791adca8819761f0bf6e08e07fd7da25127150d6", - "sha256:c088c4d70d21f8ca5c0b8b5403fe84a7bc8e024161febdd4ef04575ef35d474d", - "sha256:c26959ca7b75ff768e2776d8055bf9582a6267e24556bb7f7bd29e677932be72", - "sha256:c413016880e03e69d166efb5a1a95d40f83d5a3a648d16486592c49ffb76d0db", - "sha256:c6021d296318cb6f9414b48e6a439a7f5d1f665464da507e8ff640848ee2a58a", - "sha256:c671dc117c2c21a1ca10c116cfcd6e3e44da7fcde37bf83b2be485ab377b25da", - "sha256:c7a4b7a6cf5b6eb11e109a9755fd4fda7d57395f8c575e166d363b9fc3ec4678", - "sha256:c8a02fbeca6f63cb1f0475c799679057fc9268b77075ab7cf3f1c600e81dd46b", - "sha256:cd2adf5c87ff6d8b277814a28a535b59e20bfea40a101db6b3bdca7e9926bc24", - "sha256:d1469f228cd9ffddd396d9948b8c9cd8022b6d1bf1e40c6f25b0fb90b4f893ed", - "sha256:d153f652a687a8e95ad367a86a61e8d53d528b0530ef382ec5aaf533140ed00f", - "sha256:d5ab8e1f6bee051a4bf6195e38a5c13e5e161cb7bad83d8854524798bd9fcd6e", - "sha256:da00da442a0e31f1c69d26d224e1efd3a1ca5bcbf210978a2ca7426dfcae9f58", - "sha256:da22dab31d7180f8c3ac7c7635f3bcd53808f374f6aa333fe0b0b9e14b01f91a", - "sha256:e0ae53e33ee7476dd3d1132f932eeb39bf6125083820049d06edcdca4381f342", - "sha256:e7a6a8354f1b62e15d48e04350f13e726fa08b62c3d7b8401c0a1314f02e3558", - "sha256:e9a3d838441bebcf5cf442700e3963f58b5c33f015341f9ea86dcd7d503c07e2", - "sha256:edea7d15772ceeb29db4aff55e482d4bcfb6ae160ce144f2682de02f6d693551", - "sha256:f22eb3a6c1080d862befa0a89c380b4dafce29dc6cd56083f630073d102eb595", - "sha256:f26383adb94da5e7fb388d441bf09c61e5e35f455a3217bfd790c6b6bc64b2ee", - "sha256:f3c2890ca8c59ee683fd09adf32321a40fe1cf164e3387799efb2acebf090c11", - "sha256:f64fd07515dad67f24b6ea4a66ae2876c01031de91c93075b8093f07c0a2d93d", - "sha256:fcde4c397f673fdec23e6b05ebf8d4751314fa7c24f93334bf1f1364c1c69ac7", - "sha256:ff84aeb864e0fac81f676be9f4685f0527b660f1efdc40dcede3c251ef1e867f" + "sha256:007ec22fbc573e5eb2fb7dec4198ef8f6bf2fe4ce20020798b2eb5d0abda6138", + "sha256:00819de9e45d42584bed046314c40ea7e9aea95411b38971082cad449392b08c", + "sha256:01948b1d570f83ee7bbf5a60ea2375a89dfb09fd419170e7f5af029510033d24", + "sha256:038f514fe39e235e9fef6717fbf944057bfa24f9b3db9ee551a7ecf584b5b480", + "sha256:03a42ac7895406220124c88911ebee31ba8b2d24c98507f4a8bf826b2937c7f2", + "sha256:05646ebe6b94cc93407b3bf34b9eb26c20722384d068eb7339de802154d61bc5", + "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a", + "sha256:0b00807e2605f16e1e198f33a53ce3c4523114059b0c09c337209ae55e3823a8", + "sha256:0e1b370d8007c4ae31ee6db7f9a2fe801a42b146cec80a86766e7ad5c4a259cf", + "sha256:15ecd889a709b0080f02721255b3f80bb261c2293d3c748151274dfea93ac871", + "sha256:1b66ccafef7336a1e1f0e389901f60c1d920102315a56df85e49552308fc0486", + "sha256:1bbb122c557a16fafc10354b9d99ebf2f2808a660d78202f10ba9d50786384b9", + "sha256:1eb89d3d29adaf533588f209768a9c02e44e4baf832b08118749c5fad191781d", + "sha256:258c5dd01afc10015866114e210fb7365f0d02d9d059c3c3415382ab633fcbcb", + "sha256:2609e9ab08474702cc67b7702dbb8a80e392c54613ebe80db7e8dbdb79837c68", + "sha256:274cfa632350225ce3fdeb318c23b4a10ec25c0e2c880eff951a3842cf358ac1", + "sha256:28529e08fde6f12eba8677f5a8608500ed33c086f974de68cc65ab218713a59d", + "sha256:2b606353da03edcc71130b52388d25f9a30a126e04caef1fd637e31683033abd", + "sha256:30ca7c3b94708a9d7ae76ff281b2f47d8eaf2579cd05971b5dc681db8caac6e1", + "sha256:333cf6cf8e65f6a1e06e9eb3e643a0c515bb850d470902274239fea02033e9a8", + "sha256:3455522392fb15ff549d92fbf4b73b559d5e43dc522588f7eb3e54c3f38beee7", + "sha256:362f641f9071e5f3ee6f8e7d37d5ed0d95aae656adf4ef578313ee585b585959", + "sha256:3bcd391d083f636c06a68715e69467963d1f9600f85ef556ea82e9ef25f043f7", + "sha256:3dffb610a30d643983aeb185ce134f97f290f8935f0abccdd32c77bed9388b42", + "sha256:3fe407bf93533a6fa82dece0e74dbcaaf5d684e5a51862887f9eaebe6372cd79", + "sha256:413251f6fcf552a33c981c4709a6bba37b12710982fec8e558ae944bfb2abd38", + "sha256:438cd072f75bb6612f2aca29f8bd7cdf6e35e8f160bc312e49fbecab77c99e3a", + "sha256:4470c73c12cd9109db8277287d11f9dd98f77fc54155fc71a7738a83ffcc8ea8", + "sha256:45c3b868724137f713a38376fef8120c166d1eadd50da1855c112fe97954aed8", + "sha256:486f7aabfa292719a2753c016cc3a8f8172965cabb3ea2e7f7436c7f5a22a151", + "sha256:4f05e9727ce409358baa615dbeb9b969db94324a79b5a5cea45d39bdb01d82e6", + "sha256:50aed5155f819873d23520919e16703fc8925e509abbb1a1491b0087d1cd969e", + "sha256:50edbcad60d8f0e3eccc68da67f37268b5144ecc34d59f27a02f9611c1d4eec7", + "sha256:54ca74df1be3c7ca1cf7f4c971c79c2daf48d9aa65dea1a662ae18926f5bc8ce", + "sha256:578a4b875af3e0daaf1ac6fa983d93e0bbfec3ead753b6d6f33d467100cdc67b", + "sha256:597a079284b7ee65ee102bc3a6ea226a37d2b96d0418cc9047490f231dc09fe8", + "sha256:59bb3c54aa420521dc4ce3cc2c3fe2ad82adf7b09403fa1f48ae45c0cbde6628", + "sha256:5c6a5b8c7926ba5d8545c7dd22961a107526562da31a7a32fa2456baf040939f", + "sha256:64f6c17757251e2b8d885d728b6433d9d970573586a78b78ba8929b0f41d045a", + "sha256:679abe5d3858b33c2cf74faec299fda60ea9de62916e8b67e625d65bf069a3b7", + "sha256:741a46d58677d8c733175d7e5aa618d277cd9d880301a380fd296975a9cdd7bc", + "sha256:7789050d9e5d0c309c706953e5e8876e38662d57d45f936902e176d19f1c58ab", + "sha256:77abf6665ae54000b98b3c742bc6ea1d1fb31c394bcabf8b5d2c1ac3ebfe7f3b", + "sha256:79019094f87c9fb44f8d769e41dbb664d6e8fcfd62f665ccce36762deaa0e911", + "sha256:7b06b7843929e41a94ea09eb1ce3927865387e3e23ebe108e0d0d09b08d25be9", + "sha256:7e338c0523d024fad378b376a79faff37fafb3c001872a618cde1d322400a572", + "sha256:7ea7ffc6d6d6f8a11e6f40091a1040995cdff02cfc9ba4c2f30a516cb2633554", + "sha256:8105fd8a890df77b76dd3054cddf01a879fc13e8af576805d667e0fa0224c35d", + "sha256:84afcdea18eda514c25bc68b9af2a2b1adea7c08899175a51fe7c4fb6d551257", + "sha256:9294bbb581f92770e6ed5c19559e1e99255e4ca604a22c5c6397b2f9dd3ee42c", + "sha256:93429602396f3383a797a2a70e5f1de5df8e35535d7806c9f91df06f297e109b", + "sha256:9627cc1a10c8c409b5822a92d57a77f383b554463d1884008e051c32ab1b3742", + "sha256:998f3bd3cfc95e9424a6acd7840cbdd39e45bc09ef87533c006f94ac47296090", + "sha256:9c72109213eb9d3874f7ac8c0c5fa90e072d678e117d9061c06e30c85b4cf0e6", + "sha256:9fc1500fd2a952c5c8e3b29aaf7e3cc6e27e9cfc0a8819b3bce48cc1b849e4cc", + "sha256:a3f00003de6eba42d6e94fabb4125600d6e484846dbf90ea8e48a800430cc142", + "sha256:a45d85cf20b5e0d0aa5a8dca27cce8eddef3292bc29d72dcad1641f4ed50aa16", + "sha256:a7d8d14fe962153fc681f6366bdec33d4356f98a3e3567782aac1b6e0e40109a", + "sha256:a8fa23fe62c436ccf23ff930149c047f060c7126eae3ccea005f0483f27b2e28", + "sha256:aa6658732517ddabe22c9036479eabce6036655ba87a0224c612e1ae6af2087e", + "sha256:aafc8ee9b742ce75044ae9a4d3e60e3d918d15a4c2e08a6c3c3e38fa59b92d94", + "sha256:ab5a5a0c7a7991d90446a198689c0535be89bbd6b410a1f9a66688f0880ec026", + "sha256:acd48d5b80ee80f9432a165c0ac8cbf9253eaddb6113269a5e18699b33958dbb", + "sha256:ad7593bb24b2ab09e65e8a1d385606f0f47c65b5a2ae6c551db67d6653e78c28", + "sha256:baa42524a82f75303f714108fea528ccacf0386af429b69fff141ffef1c534f9", + "sha256:bdfcf6443637c148c4e1a20c48c566aa694fa5e288d34b20fcdc58507882fed3", + "sha256:be7443669ae9c016b71f402e43208e13ddf00912f47f623ee5994e12fc7d4b3f", + "sha256:c02a30b904282777d872266b87b20ed8cc0d1501855e27f831320f471d54d983", + "sha256:c1277cd707c465cd09572a774559a3cc7c7a28802eb3a2a9472588f062097205", + "sha256:c30a0eafc89d28e7f959281b58198a9fa5e99405f716c0289b7892ca345fe45f", + "sha256:c5ce2ce7c997e1971b7184ee37deb6ea9922ef5163c6ee5aa3c274b05f9e12fa", + "sha256:c823bc3971c44ab93e611ab1a46b1eafeae474c0c844aff4b7474287b75fe49c", + "sha256:ce0cdc074d540265bfeb31336e678b4e37316849d13b308607efa527e981f5c2", + "sha256:d1720b4f14c78a3089562b8875b53e36b51c97c51adc53325a69b79b4b48ebcb", + "sha256:d183cf9c797a5291e8301790ed6d053480ed94070637bfaad914dd38b0981f67", + "sha256:d9010c31cd6fa59438da4e58a7f19e4753f7f264300cd152e7f90d4602449762", + "sha256:d9e5e4a85bdb56d224f412d9c98ae4cbd032cc4f3161818f692cd81766eee65a", + "sha256:da1dee8948d2137bb51fbb8a53cce6b1bcc86003c6b42565f008438b806cccd8", + "sha256:df9270660711670e68803107d55c2b5949c2e0f2e4896da176e1ecfc068b974a", + "sha256:e00e3505cd80440f6c98c6d69269dcc2a119f86ad0a9fd70bccc59504bebd68a", + "sha256:e48d5021a84d341bcaf95c8460b152cfbad770d28e5fe14a768988c461b821bc", + "sha256:e7f8b04d83483577fd9200461b057c9f14ced334dcb053090cea1da9c8321a91", + "sha256:edfe3341033a6b53a5c522c802deb2079eee5cbfbb0af032a55064bd65c73a23", + "sha256:ef9c33cc5cbca35808f6c74be11eb7f5f6b14d2311be84a15b594bd3e58b5527", + "sha256:f2d4324a98062be0525d16f768a03e0bbb3b9fe301ceee99611dc9a7953124e6", + "sha256:f3935f82f6f4a3820270842e90456ebad3af15810cf65932bd24da4463bc0a4c", + "sha256:f614ab0c76397661b90b6851a030004dac502e48260ea10f2441abd2207fbcc7", + "sha256:f7db54c7914cc99d901d93a34704833568d86c20925b2762f9fa779f9cd2e70f", + "sha256:fbc6264158392bad9df19537e872d476f7c57adf718944cc1e4495cbabf38e2a", + "sha256:fe2fb38c2ed905a2582948e2de560675e9dfbee94c6d5ccdb1301c6d0a5bf092", + "sha256:ffe595f10566f8276b76dc3a11ae4bb7eba1aac8ddd75811736a15b0d5311414" ], "markers": "python_version >= '3.8'", - "version": "==3.9.5" + "version": "==3.10.10" }, "aionotify": { "hashes": [ "sha256:25816a9eef030c774beaee22189a24e29bc43f81cebe574ef723851eaf89ddee", "sha256:9651e1373873c75786101330e302e114f85b6e8b5ad70b491497c8b3609a8449" ], + "markers": "python_version >= '3.8'", "version": "==0.3.1" }, "aiosignal": { @@ -136,123 +159,244 @@ }, "attrs": { "hashes": [ - "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", - "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" + "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", + "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" ], "markers": "python_version >= '3.7'", - "version": "==23.2.0" + "version": "==24.2.0" + }, + "bcrypt": { + "hashes": [ + "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb", + "sha256:0da52759f7f30e83f1e30a888d9163a81353ef224d82dc58eb5bb52efcabc399", + "sha256:1bb429fedbe0249465cdd85a58e8376f31bb315e484f16e68ca4c786dcc04291", + "sha256:1d84cf6d877918620b687b8fd1bf7781d11e8a0998f576c7aa939776b512b98d", + "sha256:1ee38e858bf5d0287c39b7a1fc59eec64bbf880c7d504d3a06a96c16e14058e7", + "sha256:1ff39b78a52cf03fdf902635e4c81e544714861ba3f0efc56558979dd4f09170", + "sha256:27fe0f57bb5573104b5a6de5e4153c60814c711b29364c10a75a54bb6d7ff48d", + "sha256:3413bd60460f76097ee2e0a493ccebe4a7601918219c02f503984f0a7ee0aebe", + "sha256:3698393a1b1f1fd5714524193849d0c6d524d33523acca37cd28f02899285060", + "sha256:373db9abe198e8e2c70d12b479464e0d5092cc122b20ec504097b5f2297ed184", + "sha256:39e1d30c7233cfc54f5c3f2c825156fe044efdd3e0b9d309512cc514a263ec2a", + "sha256:3bbbfb2734f0e4f37c5136130405332640a1e46e6b23e000eeff2ba8d005da68", + "sha256:3d3a6d28cb2305b43feac298774b997e372e56c7c7afd90a12b3dc49b189151c", + "sha256:5a1e8aa9b28ae28020a3ac4b053117fb51c57a010b9f969603ed885f23841458", + "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9", + "sha256:655ea221910bcac76ea08aaa76df427ef8625f92e55a8ee44fbf7753dbabb328", + "sha256:762a2c5fb35f89606a9fde5e51392dad0cd1ab7ae64149a8b935fe8d79dd5ed7", + "sha256:77800b7147c9dc905db1cba26abe31e504d8247ac73580b4aa179f98e6608f34", + "sha256:8ac68872c82f1add6a20bd489870c71b00ebacd2e9134a8aa3f98a0052ab4b0e", + "sha256:8d7bb9c42801035e61c109c345a28ed7e84426ae4865511eb82e913df18f58c2", + "sha256:8f6ede91359e5df88d1f5c1ef47428a4420136f3ce97763e31b86dd8280fbdf5", + "sha256:9c1c4ad86351339c5f320ca372dfba6cb6beb25e8efc659bedd918d921956bae", + "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00", + "sha256:c52aac18ea1f4a4f65963ea4f9530c306b56ccd0c6f8c8da0c06976e34a6e841", + "sha256:cb2a8ec2bc07d3553ccebf0746bbf3d19426d1c6d1adbd4fa48925f66af7b9e8", + "sha256:cf69eaf5185fd58f268f805b505ce31f9b9fc2d64b376642164e9244540c1221", + "sha256:f4f4acf526fcd1c34e7ce851147deedd4e26e6402369304220250598b26448db" + ], + "markers": "python_version >= '3.7'", + "version": "==4.2.0" }, "cachetools": { "hashes": [ - "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945", - "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105" + "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292", + "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a" ], "markers": "python_version >= '3.7'", - "version": "==5.3.3" + "version": "==5.5.0" }, "certifi": { "hashes": [ - "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516", - "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56" + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" ], "markers": "python_version >= '3.6'", - "version": "==2024.6.2" + "version": "==2024.8.30" + }, + "cffi": { + "hashes": [ + "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", + "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", + "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1", + "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", + "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", + "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", + "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", + "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", + "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", + "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", + "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc", + "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", + "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", + "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", + "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", + "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", + "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", + "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", + "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", + "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b", + "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", + "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", + "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c", + "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", + "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", + "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", + "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8", + "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1", + "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", + "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", + "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", + "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", + "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", + "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", + "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", + "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", + "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", + "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", + "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", + "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", + "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", + "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", + "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", + "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964", + "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", + "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", + "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", + "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", + "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", + "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", + "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", + "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", + "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", + "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", + "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", + "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", + "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", + "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9", + "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", + "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", + "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", + "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", + "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", + "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", + "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", + "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", + "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==1.17.1" }, "charset-normalizer": { "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621", + "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", + "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", + "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", + "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", + "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", + "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", + "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d", + "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", + "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", + "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", + "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", + "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab", + "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be", + "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", + "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", + "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0", + "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2", + "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62", + "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62", + "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", + "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", + "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", + "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", + "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455", + "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858", + "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", + "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", + "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", + "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", + "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", + "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea", + "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", + "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", + "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", + "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", + "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd", + "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", + "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242", + "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee", + "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", + "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", + "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51", + "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", + "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8", + "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", + "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613", + "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742", + "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", + "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", + "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", + "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", + "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", + "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", + "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", + "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", + "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417", + "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", + "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", + "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca", + "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa", + "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", + "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149", + "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41", + "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574", + "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0", + "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f", + "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", + "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654", + "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3", + "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19", + "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", + "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578", + "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", + "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", + "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51", + "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", + "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", + "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a", + "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", + "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade", + "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", + "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", + "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6", + "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", + "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", + "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6", + "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2", + "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12", + "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf", + "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", + "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7", + "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", + "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", + "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b", + "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", + "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", + "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4", + "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", + "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", + "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a", + "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748", + "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", + "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", + "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" + "version": "==3.4.0" }, "click": { "hashes": [ @@ -270,121 +414,169 @@ "markers": "platform_system == 'Windows'", "version": "==0.4.6" }, + "cryptography": { + "hashes": [ + "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", + "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", + "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa", + "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83", + "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff", + "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", + "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", + "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664", + "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08", + "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", + "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", + "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", + "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", + "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", + "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", + "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", + "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3", + "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", + "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", + "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", + "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c", + "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", + "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", + "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", + "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7", + "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", + "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7" + ], + "markers": "python_version >= '3.7'", + "version": "==43.0.3" + }, "exceptiongroup": { "hashes": [ - "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad", - "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16" + "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", + "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc" ], "markers": "python_version < '3.11'", - "version": "==1.2.1" + "version": "==1.2.2" }, "frozenlist": { "hashes": [ - "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", - "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98", - "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad", - "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5", - "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae", - "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e", - "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a", - "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701", - "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d", - "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6", - "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6", - "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106", - "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75", - "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868", - "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a", - "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0", - "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1", - "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826", - "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec", - "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6", - "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950", - "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19", - "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0", - "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8", - "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a", - "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09", - "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86", - "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c", - "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5", - "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b", - "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b", - "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d", - "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0", - "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea", - "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776", - "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a", - "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897", - "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7", - "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09", - "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9", - "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe", - "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd", - "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742", - "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09", - "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0", - "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932", - "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1", - "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a", - "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49", - "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d", - "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7", - "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480", - "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89", - "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e", - "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b", - "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82", - "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb", - "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068", - "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8", - "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b", - "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb", - "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2", - "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11", - "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b", - "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc", - "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0", - "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497", - "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17", - "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0", - "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2", - "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439", - "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5", - "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac", - "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825", - "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887", - "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced", - "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74" + "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", + "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf", + "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6", + "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a", + "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d", + "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f", + "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", + "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", + "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", + "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", + "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec", + "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2", + "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c", + "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336", + "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4", + "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d", + "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b", + "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c", + "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10", + "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08", + "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942", + "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", + "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f", + "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10", + "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5", + "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", + "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", + "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", + "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d", + "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923", + "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", + "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", + "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17", + "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0", + "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", + "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", + "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c", + "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a", + "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0", + "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", + "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab", + "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", + "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3", + "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", + "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", + "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604", + "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", + "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5", + "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", + "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", + "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", + "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", + "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d", + "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", + "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3", + "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", + "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", + "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9", + "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf", + "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76", + "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba", + "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171", + "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb", + "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", + "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", + "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972", + "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d", + "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869", + "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9", + "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411", + "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723", + "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2", + "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b", + "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99", + "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e", + "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", + "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3", + "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb", + "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", + "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", + "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca", + "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45", + "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", + "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f", + "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5", + "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307", + "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e", + "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2", + "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778", + "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a", + "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30", + "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a" ], "markers": "python_version >= '3.8'", - "version": "==1.4.1" + "version": "==1.5.0" }, "google-api-core": { "hashes": [ - "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125", - "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd" + "sha256:26f8d76b96477db42b55fd02a33aae4a42ec8b86b98b94969b7333a2c828bf35", + "sha256:a6652b6bd51303902494998626653671703c420f6f4c88cfd3f50ed723e9d021" ], "markers": "python_version >= '3.7'", - "version": "==2.19.1" + "version": "==2.22.0" }, "google-api-python-client": { "hashes": [ - "sha256:4a8f0bea651a212997cc83c0f271fc86f80ef93d1cee9d84de7dfaeef2a858b6", - "sha256:ba05d60f6239990b7994f6328f17bb154c602d31860fb553016dc9f8ce886945" + "sha256:4427b2f47cd88b0355d540c2c52215f68c337f3bc9d6aae1ceeae4525977504c", + "sha256:a9d26d630810ed4631aea21d1de3e42072f98240aaf184a8a1a874a371115034" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2.134.0" + "version": "==2.151.0" }, "google-auth": { "hashes": [ - "sha256:8df7da660f62757388b8a7f249df13549b3373f24388cb5d2f1dd91cc18180b5", - "sha256:ab630a1320f6720909ad76a7dbdb6841cdf5c66b328d690027e4867bdfb16688" + "sha256:51a15d47028b66fd36e5c64a82d2d57480075bccc7da37cde257fc94177a61fb", + "sha256:545e9618f2df0bcbb7dcbc45a546485b1212624716975a1ea5ae8149ce769ab1" ], "markers": "python_version >= '3.7'", - "version": "==2.30.0" + "version": "==2.36.0" }, "google-auth-httplib2": { "hashes": [ @@ -395,19 +587,19 @@ }, "google-auth-oauthlib": { "hashes": [ - "sha256:292d2d3783349f2b0734a0a0207b1e1e322ac193c2c09d8f7c613fb7cc501ea8", - "sha256:297c1ce4cb13a99b5834c74a1fe03252e1e499716718b190f56bcb9c4abc4faf" + "sha256:2d58a27262d55aa1b87678c3ba7142a080098cbc2024f903c62355deb235d91f", + "sha256:afd0cad092a2eaa53cd8e8298557d6de1034c6cb4a740500b5357b648af97263" ], "markers": "python_version >= '3.6'", - "version": "==1.2.0" + "version": "==1.2.1" }, "googleapis-common-protos": { "hashes": [ - "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945", - "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87" + "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63", + "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0" ], "markers": "python_version >= '3.7'", - "version": "==1.63.2" + "version": "==1.65.0" }, "gspread": { "hashes": [ @@ -433,19 +625,11 @@ }, "idna": { "hashes": [ - "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", - "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" ], - "markers": "python_version >= '3.5'", - "version": "==3.7" - }, - "joblib": { - "hashes": [ - "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6", - "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e" - ], - "markers": "python_version >= '3.8'", - "version": "==1.4.2" + "markers": "python_version >= '3.6'", + "version": "==3.10" }, "jsonschema": { "hashes": [ @@ -457,99 +641,101 @@ }, "multidict": { "hashes": [ - "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556", - "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c", - "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29", - "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b", - "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8", - "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7", - "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd", - "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40", - "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6", - "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3", - "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c", - "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9", - "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5", - "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae", - "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442", - "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9", - "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc", - "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c", - "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea", - "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5", - "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50", - "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182", - "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453", - "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e", - "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600", - "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733", - "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda", - "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241", - "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461", - "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e", - "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e", - "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b", - "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e", - "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7", - "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386", - "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd", - "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9", - "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf", - "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee", - "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5", - "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a", - "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271", - "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54", - "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4", - "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496", - "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb", - "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319", - "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3", - "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f", - "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527", - "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed", - "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604", - "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef", - "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8", - "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5", - "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5", - "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626", - "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c", - "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d", - "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c", - "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc", - "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc", - "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b", - "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38", - "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450", - "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1", - "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f", - "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3", - "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755", - "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226", - "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a", - "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046", - "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf", - "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479", - "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e", - "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1", - "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a", - "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83", - "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929", - "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93", - "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a", - "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c", - "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44", - "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89", - "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba", - "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e", - "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da", - "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24", - "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423", - "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef" + "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", + "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056", + "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", + "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", + "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b", + "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", + "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748", + "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", + "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f", + "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", + "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6", + "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada", + "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", + "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2", + "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d", + "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", + "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef", + "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", + "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb", + "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60", + "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6", + "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", + "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478", + "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", + "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7", + "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56", + "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", + "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6", + "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30", + "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb", + "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", + "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0", + "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", + "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c", + "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", + "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", + "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", + "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", + "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", + "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2", + "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", + "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3", + "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", + "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", + "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657", + "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581", + "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492", + "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43", + "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", + "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", + "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926", + "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057", + "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc", + "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80", + "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255", + "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1", + "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972", + "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53", + "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1", + "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423", + "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a", + "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160", + "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c", + "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd", + "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", + "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5", + "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", + "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", + "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", + "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", + "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4", + "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156", + "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753", + "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28", + "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d", + "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a", + "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304", + "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", + "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429", + "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72", + "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399", + "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3", + "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392", + "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167", + "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c", + "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", + "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351", + "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76", + "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875", + "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd", + "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", + "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db" ], - "markers": "python_version >= '3.7'", - "version": "==6.0.5" + "markers": "python_version >= '3.8'", + "version": "==6.1.0" }, "numpy": { "hashes": [ @@ -590,7 +776,7 @@ "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3", "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f" ], - "markers": "python_version < '3.12' and python_version >= '3.9'", + "markers": "python_version < '3.11'", "version": "==1.26.4" }, "oauth2client": { @@ -639,147 +825,304 @@ }, "pandas": { "hashes": [ - "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863", - "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2", - "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1", - "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad", - "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db", - "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76", - "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51", - "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32", - "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08", - "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b", - "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4", - "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921", - "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288", - "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee", - "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0", - "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24", - "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99", - "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151", - "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd", - "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce", - "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57", - "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef", - "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54", - "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a", - "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238", - "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23", - "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772", - "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce", - "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad" + "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", + "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", + "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5", + "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", + "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", + "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", + "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea", + "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", + "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f", + "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348", + "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", + "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", + "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", + "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e", + "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", + "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645", + "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", + "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30", + "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", + "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", + "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", + "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", + "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", + "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", + "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", + "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761", + "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", + "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57", + "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c", + "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c", + "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", + "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", + "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", + "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42", + "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", + "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39", + "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", + "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", + "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed", + "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", + "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", + "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==2.2.2" + "version": "==2.2.3" }, "pandas-stubs": { "hashes": [ - "sha256:2dcc86e8fa6ea41535a4561c1f08b3942ba5267b464eff2e99caeee66f9e4cd1", - "sha256:e08ce7f602a4da2bff5a67475ba881c39f2a4d4f7fccc1cba57c6f35a379c6c0" + "sha256:3a6f8f142105a42550be677ba741ba532621f4e0acad2155c0e7b2450f114cfa", + "sha256:d4ab618253f0acf78a5d0d2bfd6dffdd92d91a56a69bdc8144e5a5c6d25be3b5" ], "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==2.2.2.240603" + "markers": "python_version >= '3.10'", + "version": "==2.2.3.241009" + }, + "paramiko": { + "hashes": [ + "sha256:1fedf06b085359051cd7d0d270cebe19e755a8a921cc2ddbfa647fb0cd7d68f9", + "sha256:ad11e540da4f55cedda52931f1a3f812a8238a7af7f62a60de538cd80bb28124" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==3.5.0" + }, + "prettier": { + "hashes": [ + "sha256:20e76791de41cafe481328dd49552303f29ca192151cee1b120c26f66cae9bfc", + "sha256:6c34b8cd09fd9c8956c05d6395ea3f575e0122dce494ba57685c07065abed427" + ], + "index": "pypi", + "version": "==0.0.7" + }, + "propcache": { + "hashes": [ + "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9", + "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763", + "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325", + "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb", + "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b", + "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09", + "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957", + "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68", + "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f", + "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798", + "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418", + "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6", + "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162", + "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f", + "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036", + "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8", + "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2", + "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110", + "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23", + "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8", + "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638", + "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a", + "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44", + "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2", + "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2", + "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850", + "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136", + "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b", + "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887", + "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89", + "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87", + "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348", + "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4", + "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861", + "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e", + "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c", + "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b", + "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb", + "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1", + "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de", + "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354", + "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563", + "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5", + "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf", + "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9", + "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12", + "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4", + "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5", + "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71", + "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9", + "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed", + "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336", + "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90", + "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063", + "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad", + "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6", + "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8", + "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e", + "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2", + "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7", + "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d", + "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d", + "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df", + "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b", + "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178", + "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2", + "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630", + "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48", + "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61", + "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89", + "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb", + "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3", + "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6", + "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562", + "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b", + "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58", + "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db", + "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99", + "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37", + "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83", + "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a", + "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d", + "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04", + "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70", + "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544", + "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394", + "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea", + "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7", + "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1", + "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793", + "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577", + "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7", + "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57", + "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d", + "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032", + "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d", + "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016", + "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504" + ], + "markers": "python_version >= '3.8'", + "version": "==0.2.0" }, - "proto-plus": { "hashes": [ - "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445", - "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12" + "sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961", + "sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91" ], "markers": "python_version >= '3.7'", - "version": "==1.24.0" + "version": "==1.25.0" }, "protobuf": { "hashes": [ - "sha256:0e341109c609749d501986b835f667c6e1e24531096cff9d34ae411595e26505", - "sha256:176c12b1f1c880bf7a76d9f7c75822b6a2bc3db2d28baa4d300e8ce4cde7409b", - "sha256:354d84fac2b0d76062e9b3221f4abbbacdfd2a4d8af36bab0474f3a0bb30ab38", - "sha256:4fadd8d83e1992eed0248bc50a4a6361dc31bcccc84388c54c86e530b7f58863", - "sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470", - "sha256:610e700f02469c4a997e58e328cac6f305f649826853813177e6290416e846c6", - "sha256:7fc3add9e6003e026da5fc9e59b131b8f22b428b991ccd53e2af8071687b4fce", - "sha256:9e8f199bf7f97bd7ecebffcae45ebf9527603549b2b562df0fbc6d4d688f14ca", - "sha256:a109916aaac42bff84702fb5187f3edadbc7c97fc2c99c5ff81dd15dcce0d1e5", - "sha256:b848dbe1d57ed7c191dfc4ea64b8b004a3f9ece4bf4d0d80a367b76df20bf36e", - "sha256:f3ecdef226b9af856075f28227ff2c90ce3a594d092c39bee5513573f25e2714" + "sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24", + "sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535", + "sha256:27b246b3723692bf1068d5734ddaf2fccc2cdd6e0c9b47fe099244d80200593b", + "sha256:3e6101d095dfd119513cde7259aa703d16c6bbdfae2554dfe5cfdbe94e32d548", + "sha256:3fa2de6b8b29d12c61911505d893afe7320ce7ccba4df913e2971461fa36d584", + "sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b", + "sha256:70585a70fc2dd4818c51287ceef5bdba6387f88a578c86d47bb34669b5552c36", + "sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135", + "sha256:91fba8f445723fcf400fdbe9ca796b19d3b1242cd873907979b9ed71e4afe868", + "sha256:a3f6857551e53ce35e60b403b8a27b0295f7d6eb63d10484f12bc6879c715687", + "sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed" ], "markers": "python_version >= '3.8'", - "version": "==5.27.2" + "version": "==5.28.3" }, "pyasn1": { "hashes": [ - "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c", - "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473" + "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", + "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034" ], "markers": "python_version >= '3.8'", - "version": "==0.6.0" + "version": "==0.6.1" }, "pyasn1-modules": { "hashes": [ - "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6", - "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b" + "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd", + "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c" + ], + "markers": "python_version >= '3.8'", + "version": "==0.4.1" + }, + "pycparser": { + "hashes": [ + "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" ], "markers": "python_version >= '3.8'", - "version": "==0.4.0" + "version": "==2.22" }, "pydantic": { "hashes": [ - "sha256:098ad8de840c92ea586bf8efd9e2e90c6339d33ab5c1cfbb85be66e4ecf8213f", - "sha256:0e2495309b1266e81d259a570dd199916ff34f7f51f1b549a0d37a6d9b17b4dc", - "sha256:0fa51175313cc30097660b10eec8ca55ed08bfa07acbfe02f7a42f6c242e9a4b", - "sha256:11289fa895bcbc8f18704efa1d8020bb9a86314da435348f59745473eb042e6b", - "sha256:2a72d2a5ff86a3075ed81ca031eac86923d44bc5d42e719d585a8eb547bf0c9b", - "sha256:371dcf1831f87c9e217e2b6a0c66842879a14873114ebb9d0861ab22e3b5bb1e", - "sha256:409b2b36d7d7d19cd8310b97a4ce6b1755ef8bd45b9a2ec5ec2b124db0a0d8f3", - "sha256:4866a1579c0c3ca2c40575398a24d805d4db6cb353ee74df75ddeee3c657f9a7", - "sha256:48db882e48575ce4b39659558b2f9f37c25b8d348e37a2b4e32971dd5a7d6227", - "sha256:525bbef620dac93c430d5d6bdbc91bdb5521698d434adf4434a7ef6ffd5c4b7f", - "sha256:543da3c6914795b37785703ffc74ba4d660418620cc273490d42c53949eeeca6", - "sha256:62d96b8799ae3d782df7ec9615cb59fc32c32e1ed6afa1b231b0595f6516e8ab", - "sha256:6654028d1144df451e1da69a670083c27117d493f16cf83da81e1e50edce72ad", - "sha256:7017971ffa7fd7808146880aa41b266e06c1e6e12261768a28b8b41ba55c8076", - "sha256:7623b59876f49e61c2e283551cc3647616d2fbdc0b4d36d3d638aae8547ea681", - "sha256:7e17c0ee7192e54a10943f245dc79e36d9fe282418ea05b886e1c666063a7b54", - "sha256:820ae12a390c9cbb26bb44913c87fa2ff431a029a785642c1ff11fed0a095fcb", - "sha256:94833612d6fd18b57c359a127cbfd932d9150c1b72fea7c86ab58c2a77edd7c7", - "sha256:95ef534e3c22e5abbdbdd6f66b6ea9dac3ca3e34c5c632894f8625d13d084cbe", - "sha256:9c803a5113cfab7bbb912f75faa4fc1e4acff43e452c82560349fff64f852e1b", - "sha256:9e53fb834aae96e7b0dadd6e92c66e7dd9cdf08965340ed04c16813102a47fab", - "sha256:ab2f976336808fd5d539fdc26eb51f9aafc1f4b638e212ef6b6f05e753c8011d", - "sha256:ad1e33dc6b9787a6f0f3fd132859aa75626528b49cc1f9e429cdacb2608ad5f0", - "sha256:ae5184e99a060a5c80010a2d53c99aee76a3b0ad683d493e5f0620b5d86eeb75", - "sha256:aeb4e741782e236ee7dc1fb11ad94dc56aabaf02d21df0e79e0c21fe07c95741", - "sha256:b4ad32aed3bf5eea5ca5decc3d1bbc3d0ec5d4fbcd72a03cdad849458decbc63", - "sha256:b8ad363330557beac73159acfbeed220d5f1bfcd6b930302a987a375e02f74fd", - "sha256:bfbb18b616abc4df70591b8c1ff1b3eabd234ddcddb86b7cac82657ab9017e33", - "sha256:c1e51d1af306641b7d1574d6d3307eaa10a4991542ca324f0feb134fee259815", - "sha256:c31d281c7485223caf6474fc2b7cf21456289dbaa31401844069b77160cab9c7", - "sha256:c7e8988bb16988890c985bd2093df9dd731bfb9d5e0860db054c23034fab8f7a", - "sha256:c87cedb4680d1614f1d59d13fea353faf3afd41ba5c906a266f3f2e8c245d655", - "sha256:cafb9c938f61d1b182dfc7d44a7021326547b7b9cf695db5b68ec7b590214773", - "sha256:d2f89a719411cb234105735a520b7c077158a81e0fe1cb05a79c01fc5eb59d3c", - "sha256:d4b40c9e13a0b61583e5599e7950490c700297b4a375b55b2b592774332798b7", - "sha256:d4ecb515fa7cb0e46e163ecd9d52f9147ba57bc3633dca0e586cdb7a232db9e3", - "sha256:d8c209af63ccd7b22fba94b9024e8b7fd07feffee0001efae50dd99316b27768", - "sha256:db3b48d9283d80a314f7a682f7acae8422386de659fffaba454b77a083c3937d", - "sha256:e41b5b973e5c64f674b3b4720286ded184dcc26a691dd55f34391c62c6934688", - "sha256:e840e6b2026920fc3f250ea8ebfdedf6ea7a25b77bf04c6576178e681942ae0f", - "sha256:ebb249096d873593e014535ab07145498957091aa6ae92759a32d40cb9998e2e", - "sha256:f434160fb14b353caf634149baaf847206406471ba70e64657c1e8330277a991", - "sha256:fa43f362b46741df8f201bf3e7dff3569fa92069bcc7b4a740dea3602e27ab7a" + "sha256:0399094464ae7f28482de22383e667625e38e1516d6b213176df1acdd0c477ea", + "sha256:076c49e24b73d346c45f9282d00dbfc16eef7ae27c970583d499f11110d9e5b0", + "sha256:07d00ca5ef0de65dd274005433ce2bb623730271d495a7d190a91c19c5679d34", + "sha256:0890fbd7fec9e151c7512941243d830b2d6076d5df159a2030952d480ab80a4e", + "sha256:0bfb5b378b78229119d66ced6adac2e933c67a0aa1d0a7adffbe432f3ec14ce4", + "sha256:0d32227ea9a3bf537a2273fd2fdb6d64ab4d9b83acd9e4e09310a777baaabb98", + "sha256:11965f421f7eb026439d4eb7464e9182fe6d69c3d4d416e464a4485d1ba61ab6", + "sha256:1fc8cc264afaf47ae6a9bcbd36c018d0c6b89293835d7fb0e5e1a95898062d59", + "sha256:2206a1752d9fac011e95ca83926a269fb0ef5536f7e053966d058316e24d929f", + "sha256:22a1794e01591884741be56c6fba157c4e99dcc9244beb5a87bd4aa54b84ea8b", + "sha256:4739c206bfb6bb2bdc78dcd40bfcebb2361add4ceac6d170e741bb914e9eff0f", + "sha256:4a5d5b877c7d3d9e17399571a8ab042081d22fe6904416a8b20f8af5909e6c8f", + "sha256:566bebdbe6bc0ac593fa0f67d62febbad9f8be5433f686dc56401ba4aab034e3", + "sha256:570ad0aeaf98b5e33ff41af75aba2ef6604ee25ce0431ecd734a28e74a208555", + "sha256:573254d844f3e64093f72fcd922561d9c5696821ff0900a0db989d8c06ab0c25", + "sha256:5d4320510682d5a6c88766b2a286d03b87bd3562bf8d78c73d63bab04b21e7b4", + "sha256:6d8a38a44bb6a15810084316ed69c854a7c06e0c99c5429f1d664ad52cec353c", + "sha256:6eb56074b11a696e0b66c7181da682e88c00e5cebe6570af8013fcae5e63e186", + "sha256:7e66aa0fa7f8aa9d0a620361834f6eb60d01d3e9cea23ca1a92cda99e6f61dac", + "sha256:7ea24e8614f541d69ea72759ff635df0e612b7dc9d264d43f51364df310081a3", + "sha256:7f31742c95e3f9443b8c6fa07c119623e61d76603be9c0d390bcf7e888acabcb", + "sha256:83ee8c9916689f8e6e7d90161e6663ac876be2efd32f61fdcfa3a15e87d4e413", + "sha256:8b2cf5e26da84f2d2dee3f60a3f1782adedcee785567a19b68d0af7e1534bd1f", + "sha256:945407f4d08cd12485757a281fca0e5b41408606228612f421aa4ea1b63a095d", + "sha256:9c46f58ef2df958ed2ea7437a8be0897d5efe9ee480818405338c7da88186fb3", + "sha256:9d7d48fbc5289efd23982a0d68e973a1f37d49064ccd36d86de4543aff21e086", + "sha256:9f28a81978e936136c44e6a70c65bde7548d87f3807260f73aeffbf76fb94c2f", + "sha256:a415b9e95fa602b10808113967f72b2da8722061265d6af69268c111c254832d", + "sha256:a82746c6d6e91ca17e75f7f333ed41d70fce93af520a8437821dec3ee52dfb10", + "sha256:ad57004e5d73aee36f1e25e4e73a4bc853b473a1c30f652dc8d86b0a987ffce3", + "sha256:c6444368b651a14c2ce2fb22145e1496f7ab23cbdb978590d47c8d34a7bc0289", + "sha256:d216f8d0484d88ab72ab45d699ac669fe031275e3fa6553e3804e69485449fa0", + "sha256:d3449633c207ec3d2d672eedb3edbe753e29bd4e22d2e42a37a2c1406564c20f", + "sha256:d5b5b7c6bafaef90cbb7dafcb225b763edd71d9e22489647ee7df49d6d341890", + "sha256:d7a8a1dd68bac29f08f0a3147de1885f4dccec35d4ea926e6e637fac03cdb4b3", + "sha256:d8d72553d2f3f57ce547de4fa7dc8e3859927784ab2c88343f1fc1360ff17a08", + "sha256:dce355fe7ae53e3090f7f5fa242423c3a7b53260747aa398b4b3aaf8b25f41c3", + "sha256:e351df83d1c9cffa53d4e779009a093be70f1d5c6bb7068584086f6a19042526", + "sha256:ec5c44e6e9eac5128a9bfd21610df3b8c6b17343285cc185105686888dc81206", + "sha256:f5bb81fcfc6d5bff62cd786cbd87480a11d23f16d5376ad2e057c02b3b44df96", + "sha256:fd34012691fbd4e67bdf4accb1f0682342101015b78327eaae3543583fcd451e", + "sha256:fea36c2065b7a1d28c6819cc2e93387b43dd5d3cf5a1e82d8132ee23f36d1f10", + "sha256:ff09600cebe957ecbb4a27496fe34c1d449e7957ed20a202d5029a71a8af2e35" ], "markers": "python_version >= '3.7'", - "version": "==1.10.17" + "version": "==1.10.19" + }, + "pynacl": { + "hashes": [ + "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858", + "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d", + "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", + "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1", + "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92", + "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff", + "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba", + "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394", + "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b", + "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543" + ], + "markers": "python_version >= '3.6'", + "version": "==1.5.0" }, "pyparsing": { "hashes": [ - "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad", - "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742" + "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84", + "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c" ], "markers": "python_version >= '3.1'", - "version": "==3.1.2" + "version": "==3.2.0" }, "pyrsistent": { "hashes": [ @@ -844,30 +1187,42 @@ }, "pytz": { "hashes": [ - "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", - "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319" + "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", + "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725" ], - "version": "==2024.1" + "version": "==2024.2" + }, + "pyusb": { + "hashes": [ + "sha256:2b4c7cb86dbadf044dfb9d3a4ff69fd217013dbe78a792177a3feb172449ea36", + "sha256:a4cc7404a203144754164b8b40994e2849fde1cfff06b08492f12fff9d9de7b9" + ], + "markers": "python_full_version >= '3.6.0'", + "version": "==1.2.1" }, "pywin32": { "hashes": [ - "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d", - "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65", - "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e", - "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b", - "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4", - "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040", - "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a", - "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36", - "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8", - "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e", - "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802", - "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a", - "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407", - "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0" + "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", + "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6", + "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6", + "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed", + "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff", + "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de", + "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e", + "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b", + "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0", + "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", + "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a", + "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920", + "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341", + "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e", + "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", + "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c", + "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd", + "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4" ], "markers": "platform_system == 'Windows' and platform_python_implementation == 'CPython'", - "version": "==306" + "version": "==308" }, "requests": { "hashes": [ @@ -893,72 +1248,13 @@ "markers": "python_version >= '3.6' and python_version < '4'", "version": "==4.9" }, - "scikit-learn": { - "hashes": [ - "sha256:057b991ac64b3e75c9c04b5f9395eaf19a6179244c089afdebaad98264bff37c", - "sha256:118a8d229a41158c9f90093e46b3737120a165181a1b58c03461447aa4657415", - "sha256:12e40ac48555e6b551f0a0a5743cc94cc5a765c9513fe708e01f0aa001da2801", - "sha256:174beb56e3e881c90424e21f576fa69c4ffcf5174632a79ab4461c4c960315ac", - "sha256:1b94d6440603752b27842eda97f6395f570941857456c606eb1d638efdb38184", - "sha256:1f77547165c00625551e5c250cefa3f03f2fc92c5e18668abd90bfc4be2e0bff", - "sha256:261fe334ca48f09ed64b8fae13f9b46cc43ac5f580c4a605cbb0a517456c8f71", - "sha256:2a65af2d8a6cce4e163a7951a4cfbfa7fceb2d5c013a4b593686c7f16445cf9d", - "sha256:2c75ea812cd83b1385bbfa94ae971f0d80adb338a9523f6bbcb5e0b0381151d4", - "sha256:40fb7d4a9a2db07e6e0cae4dc7bdbb8fada17043bac24104d8165e10e4cff1a2", - "sha256:460806030c666addee1f074788b3978329a5bfdc9b7d63e7aad3f6d45c67a210", - "sha256:47132440050b1c5beb95f8ba0b2402bbd9057ce96ec0ba86f2f445dd4f34df67", - "sha256:4c0c56c3005f2ec1db3787aeaabefa96256580678cec783986836fc64f8ff622", - "sha256:789e3db01c750ed6d496fa2db7d50637857b451e57bcae863bff707c1247bef7", - "sha256:855fc5fa8ed9e4f08291203af3d3e5fbdc4737bd617a371559aaa2088166046e", - "sha256:a03b09f9f7f09ffe8c5efffe2e9de1196c696d811be6798ad5eddf323c6f4d40", - "sha256:a3a10e1d9e834e84d05e468ec501a356226338778769317ee0b84043c0d8fb06", - "sha256:a90c5da84829a0b9b4bf00daf62754b2be741e66b5946911f5bdfaa869fcedd6", - "sha256:d82c2e573f0f2f2f0be897e7a31fcf4e73869247738ab8c3ce7245549af58ab8", - "sha256:df8ccabbf583315f13160a4bb06037bde99ea7d8211a69787a6b7c5d4ebb6fc3", - "sha256:f405c4dae288f5f6553b10c4ac9ea7754d5180ec11e296464adb5d6ac68b6ef5" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==1.5.0" - }, - "scipy": { - "hashes": [ - "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d", - "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c", - "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca", - "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9", - "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54", - "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16", - "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2", - "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5", - "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59", - "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326", - "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b", - "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1", - "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d", - "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24", - "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627", - "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c", - "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa", - "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949", - "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989", - "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004", - "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f", - "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884", - "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299", - "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94", - "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f" - ], - "markers": "python_version >= '3.9'", - "version": "==1.13.1" - }, "setuptools": { "hashes": [ - "sha256:937a48c7cdb7a21eb53cd7f9b59e525503aa8abaf3584c730dc5f7a5bec3a650", - "sha256:a58a8fde0541dab0419750bcc521fbdf8585f6e5cb41909df3a472ef7b81ca95" + "sha256:f2504966861356aa38616760c0f66568e535562374995367b4e69c7143cf6bcd", + "sha256:fba5dd4d766e97be1b1681d98712680ae8f2f26d7881245f2ce9e40714f1a686" ], "markers": "python_version >= '3.8'", - "version": "==70.1.1" + "version": "==75.3.0" }, "six": { "hashes": [ @@ -970,12 +1266,12 @@ }, "slack-sdk": { "hashes": [ - "sha256:001a4013698d3f244645add49c80adf8addc3a6bf633193848f7cbae3d387e0b", - "sha256:42d1c95f7159887ddb4841d461fbe7ab0c48e4968f3cd44eaaa792cf109f4425" + "sha256:0515fb93cd03b18de61f876a8304c4c3cef4dd3c2a3bad62d7394d2eb5a3c8e6", + "sha256:4cc44c9ffe4bb28a01fbe3264c2f466c783b893a4eca62026ab845ec7c176ff1" ], "index": "pypi", "markers": "python_version >= '3.6'", - "version": "==3.30.0" + "version": "==3.33.3" }, "slackclient": { "hashes": [ @@ -1001,14 +1297,6 @@ ], "version": "==0.4.15" }, - "threadpoolctl": { - "hashes": [ - "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107", - "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467" - ], - "markers": "python_version >= '3.8'", - "version": "==3.5.0" - }, "types-httplib2": { "hashes": [ "sha256:1eda99fea18ec8a1dc1a725ead35b889d0836fec1b11ae6f1fe05440724c1d15", @@ -1020,11 +1308,11 @@ }, "types-pytz": { "hashes": [ - "sha256:6810c8a1f68f21fdf0f4f374a432487c77645a0ac0b31de4bf4690cf21ad3981", - "sha256:8335d443310e2db7b74e007414e74c4f53b67452c0cb0d228ca359ccfba59659" + "sha256:3e22df1336c0c6ad1d29163c8fda82736909eb977281cb823c57f8bae07118b7", + "sha256:575dc38f385a922a212bac00a7d6d2e16e141132a3c955078f4a4fd13ed6cb44" ], "markers": "python_version >= '3.8'", - "version": "==2024.1.0.20240417" + "version": "==2024.2.0.20241003" }, "typing-extensions": { "hashes": [ @@ -1036,11 +1324,11 @@ }, "tzdata": { "hashes": [ - "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd", - "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252" + "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", + "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd" ], "markers": "python_version >= '2'", - "version": "==2024.1" + "version": "==2024.2" }, "uritemplate": { "hashes": [ @@ -1052,11 +1340,11 @@ }, "urllib3": { "hashes": [ - "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", - "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168" + "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", + "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9" ], "markers": "python_version >= '3.8'", - "version": "==2.2.2" + "version": "==2.2.3" }, "wrapt": { "hashes": [ @@ -1136,99 +1424,91 @@ }, "yarl": { "hashes": [ - "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51", - "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce", - "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559", - "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0", - "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81", - "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc", - "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4", - "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c", - "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130", - "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136", - "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e", - "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec", - "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7", - "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1", - "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455", - "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099", - "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129", - "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10", - "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142", - "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98", - "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa", - "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7", - "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525", - "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c", - "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9", - "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c", - "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8", - "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b", - "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf", - "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23", - "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd", - "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27", - "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f", - "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece", - "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434", - "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec", - "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff", - "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78", - "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d", - "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863", - "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53", - "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31", - "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15", - "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5", - "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b", - "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57", - "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3", - "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1", - "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f", - "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad", - "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c", - "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7", - "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2", - "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b", - "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2", - "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b", - "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9", - "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be", - "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e", - "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984", - "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4", - "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074", - "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2", - "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392", - "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91", - "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541", - "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf", - "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572", - "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66", - "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575", - "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14", - "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5", - "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1", - "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e", - "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551", - "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17", - "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead", - "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0", - "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe", - "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234", - "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0", - "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7", - "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34", - "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42", - "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385", - "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78", - "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be", - "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958", - "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749", - "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec" + "sha256:06157fb3c58f2736a5e47c8fcbe1afc8b5de6fb28b14d25574af9e62150fcaac", + "sha256:067a63fcfda82da6b198fa73079b1ca40b7c9b7994995b6ee38acda728b64d47", + "sha256:0b1794853124e2f663f0ea54efb0340b457f08d40a1cef78edfa086576179c91", + "sha256:0bdff5e0995522706c53078f531fb586f56de9c4c81c243865dd5c66c132c3b5", + "sha256:117ed8b3732528a1e41af3aa6d4e08483c2f0f2e3d3d7dca7cf538b3516d93df", + "sha256:14bc88baa44e1f84164a392827b5defb4fa8e56b93fecac3d15315e7c8e5d8b3", + "sha256:1654ec814b18be1af2c857aa9000de7a601400bd4c9ca24629b18486c2e35463", + "sha256:16bca6678a83657dd48df84b51bd56a6c6bd401853aef6d09dc2506a78484c7b", + "sha256:1a3b91c44efa29e6c8ef8a9a2b583347998e2ba52c5d8280dbd5919c02dfc3b5", + "sha256:1a52a1ffdd824fb1835272e125385c32fd8b17fbdefeedcb4d543cc23b332d74", + "sha256:1ce36ded585f45b1e9bb36d0ae94765c6608b43bd2e7f5f88079f7a85c61a4d3", + "sha256:299f11b44d8d3a588234adbe01112126010bd96d9139c3ba7b3badd9829261c3", + "sha256:2b24ec55fad43e476905eceaf14f41f6478780b870eda5d08b4d6de9a60b65b4", + "sha256:2d374d70fdc36f5863b84e54775452f68639bc862918602d028f89310a034ab0", + "sha256:2d9f0606baaec5dd54cb99667fcf85183a7477f3766fbddbe3f385e7fc253299", + "sha256:2e7ba4c9377e48fb7b20dedbd473cbcbc13e72e1826917c185157a137dac9df2", + "sha256:2f0a6423295a0d282d00e8701fe763eeefba8037e984ad5de44aa349002562ac", + "sha256:327828786da2006085a4d1feb2594de6f6d26f8af48b81eb1ae950c788d97f61", + "sha256:380e6c38ef692b8fd5a0f6d1fa8774d81ebc08cfbd624b1bca62a4d4af2f9931", + "sha256:3b74ff4767d3ef47ffe0cd1d89379dc4d828d4873e5528976ced3b44fe5b0a21", + "sha256:3e844be8d536afa129366d9af76ed7cb8dfefec99f5f1c9e4f8ae542279a6dc3", + "sha256:459e81c2fb920b5f5df744262d1498ec2c8081acdcfe18181da44c50f51312f7", + "sha256:46ddf6e0b975cd680eb83318aa1d321cb2bf8d288d50f1754526230fcf59ba96", + "sha256:482c122b72e3c5ec98f11457aeb436ae4aecca75de19b3d1de7cf88bc40db82f", + "sha256:561c87fea99545ef7d692403c110b2f99dced6dff93056d6e04384ad3bc46243", + "sha256:578d00c9b7fccfa1745a44f4eddfdc99d723d157dad26764538fbdda37209857", + "sha256:58c8e9620eb82a189c6c40cb6b59b4e35b2ee68b1f2afa6597732a2b467d7e8f", + "sha256:5b29beab10211a746f9846baa39275e80034e065460d99eb51e45c9a9495bcca", + "sha256:5d1d42556b063d579cae59e37a38c61f4402b47d70c29f0ef15cee1acaa64488", + "sha256:5f236cb5999ccd23a0ab1bd219cfe0ee3e1c1b65aaf6dd3320e972f7ec3a39da", + "sha256:62a91aefff3d11bf60e5956d340eb507a983a7ec802b19072bb989ce120cd948", + "sha256:64cc6e97f14cf8a275d79c5002281f3040c12e2e4220623b5759ea7f9868d6a5", + "sha256:6f4c9156c4d1eb490fe374fb294deeb7bc7eaccda50e23775b2354b6a6739934", + "sha256:7294e38f9aa2e9f05f765b28ffdc5d81378508ce6dadbe93f6d464a8c9594473", + "sha256:7615058aabad54416ddac99ade09a5510cf77039a3b903e94e8922f25ed203d7", + "sha256:7e48cdb8226644e2fbd0bdb0a0f87906a3db07087f4de77a1b1b1ccfd9e93685", + "sha256:7f63d176a81555984e91f2c84c2a574a61cab7111cc907e176f0f01538e9ff6e", + "sha256:7f6595c852ca544aaeeb32d357e62c9c780eac69dcd34e40cae7b55bc4fb1147", + "sha256:7fac95714b09da9278a0b52e492466f773cfe37651cf467a83a1b659be24bf71", + "sha256:81713b70bea5c1386dc2f32a8f0dab4148a2928c7495c808c541ee0aae614d67", + "sha256:846dd2e1243407133d3195d2d7e4ceefcaa5f5bf7278f0a9bda00967e6326b04", + "sha256:84c063af19ef5130084db70ada40ce63a84f6c1ef4d3dbc34e5e8c4febb20822", + "sha256:881764d610e3269964fc4bb3c19bb6fce55422828e152b885609ec176b41cf11", + "sha256:8994b29c462de9a8fce2d591028b986dbbe1b32f3ad600b2d3e1c482c93abad6", + "sha256:8c79e9d7e3d8a32d4824250a9c6401194fb4c2ad9a0cec8f6a96e09a582c2cc0", + "sha256:8ee427208c675f1b6e344a1f89376a9613fc30b52646a04ac0c1f6587c7e46ec", + "sha256:949681f68e0e3c25377462be4b658500e85ca24323d9619fdc41f68d46a1ffda", + "sha256:9e275792097c9f7e80741c36de3b61917aebecc08a67ae62899b074566ff8556", + "sha256:9fb815155aac6bfa8d86184079652c9715c812d506b22cfa369196ef4e99d1b4", + "sha256:a2a64e62c7a0edd07c1c917b0586655f3362d2c2d37d474db1a509efb96fea1c", + "sha256:a7ac5b4984c468ce4f4a553df281450df0a34aefae02e58d77a0847be8d1e11f", + "sha256:aa46dce75078fceaf7cecac5817422febb4355fbdda440db55206e3bd288cfb8", + "sha256:ae3476e934b9d714aa8000d2e4c01eb2590eee10b9d8cd03e7983ad65dfbfcba", + "sha256:b0341e6d9a0c0e3cdc65857ef518bb05b410dbd70d749a0d33ac0f39e81a4258", + "sha256:b40d1bf6e6f74f7c0a567a9e5e778bbd4699d1d3d2c0fe46f4b717eef9e96b95", + "sha256:b5c4804e4039f487e942c13381e6c27b4b4e66066d94ef1fae3f6ba8b953f383", + "sha256:b5d6a6c9602fd4598fa07e0389e19fe199ae96449008d8304bf5d47cb745462e", + "sha256:b5f1ac7359e17efe0b6e5fec21de34145caef22b260e978336f325d5c84e6938", + "sha256:c0167540094838ee9093ef6cc2c69d0074bbf84a432b4995835e8e5a0d984374", + "sha256:c180ac742a083e109c1a18151f4dd8675f32679985a1c750d2ff806796165b55", + "sha256:c73df5b6e8fabe2ddb74876fb82d9dd44cbace0ca12e8861ce9155ad3c886139", + "sha256:c7e177c619342e407415d4f35dec63d2d134d951e24b5166afcdfd1362828e17", + "sha256:cbad927ea8ed814622305d842c93412cb47bd39a496ed0f96bfd42b922b4a217", + "sha256:cc353841428d56b683a123a813e6a686e07026d6b1c5757970a877195f880c2d", + "sha256:cc7c92c1baa629cb03ecb0c3d12564f172218fb1739f54bf5f3881844daadc6d", + "sha256:cc7d768260f4ba4ea01741c1b5fe3d3a6c70eb91c87f4c8761bbcce5181beafe", + "sha256:d0eea830b591dbc68e030c86a9569826145df485b2b4554874b07fea1275a199", + "sha256:d216e5d9b8749563c7f2c6f7a0831057ec844c68b4c11cb10fc62d4fd373c26d", + "sha256:d401f07261dc5aa36c2e4efc308548f6ae943bfff20fcadb0a07517a26b196d8", + "sha256:d6324274b4e0e2fa1b3eccb25997b1c9ed134ff61d296448ab8269f5ac068c4c", + "sha256:d8a8b74d843c2638f3864a17d97a4acda58e40d3e44b6303b8cc3d3c44ae2d29", + "sha256:d9b6b28a57feb51605d6ae5e61a9044a31742db557a3b851a74c13bc61de5172", + "sha256:de599af166970d6a61accde358ec9ded821234cbbc8c6413acfec06056b8e860", + "sha256:e594b22688d5747b06e957f1ef822060cb5cb35b493066e33ceac0cf882188b7", + "sha256:e5b078134f48552c4d9527db2f7da0b5359abd49393cdf9794017baec7506170", + "sha256:eb6dce402734575e1a8cc0bb1509afca508a400a57ce13d306ea2c663bad1138", + "sha256:f1790a4b1e8e8e028c391175433b9c8122c39b46e1663228158e61e6f915bf06", + "sha256:f5efe0661b9fcd6246f27957f6ae1c0eb29bc60552820f01e970b4996e016004", + "sha256:f9cbfbc5faca235fbdf531b93aa0f9f005ec7d267d9d738761a4d42b744ea159", + "sha256:fbea1751729afe607d84acfd01efd95e3b31db148a181a441984ce9b3d3469da", + "sha256:fca4b4307ebe9c3ec77a084da3a9d1999d164693d16492ca2b64594340999988", + "sha256:ff5c6771c7e3511a06555afa317879b7db8d640137ba55d6ab0d0c50425cab75" ], - "markers": "python_version >= '3.7'", - "version": "==1.9.4" + "markers": "python_version >= '3.9'", + "version": "==1.17.1" } }, "develop": { @@ -1241,11 +1521,11 @@ }, "attrs": { "hashes": [ - "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", - "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" + "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", + "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" ], "markers": "python_version >= '3.7'", - "version": "==23.2.0" + "version": "==24.2.0" }, "black": { "hashes": [ @@ -1279,115 +1559,130 @@ }, "cachetools": { "hashes": [ - "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945", - "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105" + "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292", + "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a" ], "markers": "python_version >= '3.7'", - "version": "==5.3.3" + "version": "==5.5.0" }, "certifi": { "hashes": [ - "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516", - "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56" + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" ], "markers": "python_version >= '3.6'", - "version": "==2024.6.2" + "version": "==2024.8.30" }, "charset-normalizer": { "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621", + "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", + "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", + "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", + "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", + "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", + "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", + "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d", + "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", + "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", + "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", + "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", + "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab", + "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be", + "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", + "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", + "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0", + "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2", + "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62", + "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62", + "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", + "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", + "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", + "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", + "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455", + "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858", + "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", + "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", + "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", + "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", + "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", + "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea", + "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", + "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", + "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", + "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", + "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd", + "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", + "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242", + "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee", + "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", + "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", + "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51", + "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", + "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8", + "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", + "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613", + "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742", + "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", + "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", + "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", + "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", + "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", + "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", + "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", + "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", + "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417", + "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", + "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", + "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca", + "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa", + "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", + "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149", + "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41", + "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574", + "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0", + "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f", + "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", + "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654", + "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3", + "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19", + "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", + "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578", + "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", + "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", + "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51", + "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", + "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", + "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a", + "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", + "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade", + "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", + "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", + "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6", + "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", + "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", + "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6", + "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2", + "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12", + "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf", + "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", + "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7", + "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", + "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", + "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b", + "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", + "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", + "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4", + "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", + "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", + "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a", + "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748", + "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", + "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", + "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" + "version": "==3.4.0" }, "click": { "hashes": [ @@ -1407,61 +1702,71 @@ }, "coverage": { "hashes": [ - "sha256:018a12985185038a5b2bcafab04ab833a9a0f2c59995b3cec07e10074c78635f", - "sha256:02ff6e898197cc1e9fa375581382b72498eb2e6d5fc0b53f03e496cfee3fac6d", - "sha256:042183de01f8b6d531e10c197f7f0315a61e8d805ab29c5f7b51a01d62782747", - "sha256:1014fbf665fef86cdfd6cb5b7371496ce35e4d2a00cda501cf9f5b9e6fced69f", - "sha256:1137f46adb28e3813dec8c01fefadcb8c614f33576f672962e323b5128d9a68d", - "sha256:16852febd96acd953b0d55fc842ce2dac1710f26729b31c80b940b9afcd9896f", - "sha256:2174e7c23e0a454ffe12267a10732c273243b4f2d50d07544a91198f05c48f47", - "sha256:2214ee920787d85db1b6a0bd9da5f8503ccc8fcd5814d90796c2f2493a2f4d2e", - "sha256:3257fdd8e574805f27bb5342b77bc65578e98cbc004a92232106344053f319ba", - "sha256:3684bc2ff328f935981847082ba4fdc950d58906a40eafa93510d1b54c08a66c", - "sha256:3a6612c99081d8d6134005b1354191e103ec9705d7ba2754e848211ac8cacc6b", - "sha256:3d7564cc09dd91b5a6001754a5b3c6ecc4aba6323baf33a12bd751036c998be4", - "sha256:44da56a2589b684813f86d07597fdf8a9c6ce77f58976727329272f5a01f99f7", - "sha256:5013ed890dc917cef2c9f765c4c6a8ae9df983cd60dbb635df8ed9f4ebc9f555", - "sha256:54317c2b806354cbb2dc7ac27e2b93f97096912cc16b18289c5d4e44fc663233", - "sha256:56b4eafa21c6c175b3ede004ca12c653a88b6f922494b023aeb1e836df953ace", - "sha256:581ea96f92bf71a5ec0974001f900db495488434a6928a2ca7f01eee20c23805", - "sha256:5cd64adedf3be66f8ccee418473c2916492d53cbafbfcff851cbec5a8454b136", - "sha256:5df54843b88901fdc2f598ac06737f03d71168fd1175728054c8f5a2739ac3e4", - "sha256:65e528e2e921ba8fd67d9055e6b9f9e34b21ebd6768ae1c1723f4ea6ace1234d", - "sha256:6aae5cce399a0f065da65c7bb1e8abd5c7a3043da9dceb429ebe1b289bc07806", - "sha256:6cfb5a4f556bb51aba274588200a46e4dd6b505fb1a5f8c5ae408222eb416f99", - "sha256:7076b4b3a5f6d2b5d7f1185fde25b1e54eb66e647a1dfef0e2c2bfaf9b4c88c8", - "sha256:73ca8fbc5bc622e54627314c1a6f1dfdd8db69788f3443e752c215f29fa87a0b", - "sha256:79b356f3dd5b26f3ad23b35c75dbdaf1f9e2450b6bcefc6d0825ea0aa3f86ca5", - "sha256:7a892be37ca35eb5019ec85402c3371b0f7cda5ab5056023a7f13da0961e60da", - "sha256:8192794d120167e2a64721d88dbd688584675e86e15d0569599257566dec9bf0", - "sha256:820bc841faa502e727a48311948e0461132a9c8baa42f6b2b84a29ced24cc078", - "sha256:8f894208794b164e6bd4bba61fc98bf6b06be4d390cf2daacfa6eca0a6d2bb4f", - "sha256:a04e990a2a41740b02d6182b498ee9796cf60eefe40cf859b016650147908029", - "sha256:a44963520b069e12789d0faea4e9fdb1e410cdc4aab89d94f7f55cbb7fef0353", - "sha256:a6bb74ed465d5fb204b2ec41d79bcd28afccf817de721e8a807d5141c3426638", - "sha256:ab73b35e8d109bffbda9a3e91c64e29fe26e03e49addf5b43d85fc426dde11f9", - "sha256:aea072a941b033813f5e4814541fc265a5c12ed9720daef11ca516aeacd3bd7f", - "sha256:b1ccf5e728ccf83acd313c89f07c22d70d6c375a9c6f339233dcf792094bcbf7", - "sha256:b385d49609f8e9efc885790a5a0e89f2e3ae042cdf12958b6034cc442de428d3", - "sha256:b3d45ff86efb129c599a3b287ae2e44c1e281ae0f9a9bad0edc202179bcc3a2e", - "sha256:b4a474f799456e0eb46d78ab07303286a84a3140e9700b9e154cfebc8f527016", - "sha256:b95c3a8cb0463ba9f77383d0fa8c9194cf91f64445a63fc26fb2327e1e1eb088", - "sha256:c5986ee7ea0795a4095ac4d113cbb3448601efca7f158ec7f7087a6c705304e4", - "sha256:cdd31315fc20868c194130de9ee6bfd99755cc9565edff98ecc12585b90be882", - "sha256:cef4649ec906ea7ea5e9e796e68b987f83fa9a718514fe147f538cfeda76d7a7", - "sha256:d05c16cf4b4c2fc880cb12ba4c9b526e9e5d5bb1d81313d4d732a5b9fe2b9d53", - "sha256:d2e344d6adc8ef81c5a233d3a57b3c7d5181f40e79e05e1c143da143ccb6377d", - "sha256:d45d3cbd94159c468b9b8c5a556e3f6b81a8d1af2a92b77320e887c3e7a5d080", - "sha256:db14f552ac38f10758ad14dd7b983dbab424e731588d300c7db25b6f89e335b5", - "sha256:dbc5958cb471e5a5af41b0ddaea96a37e74ed289535e8deca404811f6cb0bc3d", - "sha256:ddbd2f9713a79e8e7242d7c51f1929611e991d855f414ca9996c20e44a895f7c", - "sha256:e16f3d6b491c48c5ae726308e6ab1e18ee830b4cdd6913f2d7f77354b33f91c8", - "sha256:e2afe743289273209c992075a5a4913e8d007d569a406ffed0bd080ea02b0633", - "sha256:e564c2cf45d2f44a9da56f4e3a26b2236504a496eb4cb0ca7221cd4cc7a9aca9", - "sha256:ed550e7442f278af76d9d65af48069f1fb84c9f745ae249c1a183c1e9d1b025c" + "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376", + "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9", + "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111", + "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172", + "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491", + "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546", + "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2", + "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11", + "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08", + "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c", + "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2", + "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963", + "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613", + "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0", + "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db", + "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf", + "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73", + "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117", + "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1", + "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e", + "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522", + "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25", + "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc", + "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea", + "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52", + "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a", + "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07", + "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06", + "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa", + "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901", + "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b", + "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17", + "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0", + "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21", + "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19", + "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5", + "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51", + "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3", + "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3", + "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f", + "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076", + "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a", + "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718", + "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba", + "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e", + "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27", + "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e", + "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09", + "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e", + "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70", + "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f", + "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72", + "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a", + "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef", + "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b", + "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b", + "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f", + "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806", + "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b", + "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1", + "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c", + "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858" ], - "markers": "python_version >= '3.8'", - "version": "==7.5.4" + "markers": "python_version >= '3.9'", + "version": "==7.6.4" }, "flake8": { "hashes": [ @@ -1500,37 +1805,37 @@ }, "google-api-core": { "hashes": [ - "sha256:f12a9b8309b5e21d92483bbd47ce2c445861ec7d269ef6784ecc0ea8c1fa6125", - "sha256:f4695f1e3650b316a795108a76a1c416e6afb036199d1c1f1f110916df479ffd" + "sha256:26f8d76b96477db42b55fd02a33aae4a42ec8b86b98b94969b7333a2c828bf35", + "sha256:a6652b6bd51303902494998626653671703c420f6f4c88cfd3f50ed723e9d021" ], "markers": "python_version >= '3.7'", - "version": "==2.19.1" + "version": "==2.22.0" }, "google-api-python-client": { "hashes": [ - "sha256:4a8f0bea651a212997cc83c0f271fc86f80ef93d1cee9d84de7dfaeef2a858b6", - "sha256:ba05d60f6239990b7994f6328f17bb154c602d31860fb553016dc9f8ce886945" + "sha256:4427b2f47cd88b0355d540c2c52215f68c337f3bc9d6aae1ceeae4525977504c", + "sha256:a9d26d630810ed4631aea21d1de3e42072f98240aaf184a8a1a874a371115034" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2.134.0" + "version": "==2.151.0" }, "google-api-python-client-stubs": { "hashes": [ - "sha256:0614b0cef5beac43e6ab02418f07e64ee66dc99ae4e377d54a155ac261533987", - "sha256:f3b38b46f7b5cf4f6e7cc63ca554a2d23096d49c841f38b9ea553a5237074b56" + "sha256:7327c058fb5ba975309922f962f17931b9c82af51d95a5dc04061ed0c20b9f06", + "sha256:75b3dfe67b9d74ac3b58d78725326836769d0b2df1cbef354a5455a5cc57d68d" ], "index": "pypi", - "markers": "python_version >= '3.7' and python_version < '4.0'", - "version": "==1.26.0" + "markers": "python_version >= '3.7'", + "version": "==1.28.0" }, "google-auth": { "hashes": [ - "sha256:8df7da660f62757388b8a7f249df13549b3373f24388cb5d2f1dd91cc18180b5", - "sha256:ab630a1320f6720909ad76a7dbdb6841cdf5c66b328d690027e4867bdfb16688" + "sha256:51a15d47028b66fd36e5c64a82d2d57480075bccc7da37cde257fc94177a61fb", + "sha256:545e9618f2df0bcbb7dcbc45a546485b1212624716975a1ea5ae8149ce769ab1" ], "markers": "python_version >= '3.7'", - "version": "==2.30.0" + "version": "==2.36.0" }, "google-auth-httplib2": { "hashes": [ @@ -1541,11 +1846,11 @@ }, "googleapis-common-protos": { "hashes": [ - "sha256:27a2499c7e8aff199665b22741997e485eccc8645aa9176c7c988e6fae507945", - "sha256:27c5abdffc4911f28101e635de1533fb4cfd2c37fbaa9174587c799fac90aa87" + "sha256:2972e6c496f435b92590fd54045060867f3fe9be2c82ab148fc8885035479a63", + "sha256:334a29d07cddc3aa01dee4988f9afd9b2916ee2ff49d6b757155dc0d197852c0" ], "markers": "python_version >= '3.7'", - "version": "==1.63.2" + "version": "==1.65.0" }, "httplib2": { "hashes": [ @@ -1558,11 +1863,11 @@ }, "idna": { "hashes": [ - "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", - "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" ], - "markers": "python_version >= '3.5'", - "version": "==3.7" + "markers": "python_version >= '3.6'", + "version": "==3.10" }, "iniconfig": { "hashes": [ @@ -1639,11 +1944,11 @@ }, "platformdirs": { "hashes": [ - "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", - "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3" + "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", + "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb" ], "markers": "python_version >= '3.8'", - "version": "==4.2.2" + "version": "==4.3.6" }, "pluggy": { "hashes": [ @@ -1655,28 +1960,28 @@ }, "proto-plus": { "hashes": [ - "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445", - "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12" + "sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961", + "sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91" ], "markers": "python_version >= '3.7'", - "version": "==1.24.0" + "version": "==1.25.0" }, "protobuf": { "hashes": [ - "sha256:0e341109c609749d501986b835f667c6e1e24531096cff9d34ae411595e26505", - "sha256:176c12b1f1c880bf7a76d9f7c75822b6a2bc3db2d28baa4d300e8ce4cde7409b", - "sha256:354d84fac2b0d76062e9b3221f4abbbacdfd2a4d8af36bab0474f3a0bb30ab38", - "sha256:4fadd8d83e1992eed0248bc50a4a6361dc31bcccc84388c54c86e530b7f58863", - "sha256:54330f07e4949d09614707c48b06d1a22f8ffb5763c159efd5c0928326a91470", - "sha256:610e700f02469c4a997e58e328cac6f305f649826853813177e6290416e846c6", - "sha256:7fc3add9e6003e026da5fc9e59b131b8f22b428b991ccd53e2af8071687b4fce", - "sha256:9e8f199bf7f97bd7ecebffcae45ebf9527603549b2b562df0fbc6d4d688f14ca", - "sha256:a109916aaac42bff84702fb5187f3edadbc7c97fc2c99c5ff81dd15dcce0d1e5", - "sha256:b848dbe1d57ed7c191dfc4ea64b8b004a3f9ece4bf4d0d80a367b76df20bf36e", - "sha256:f3ecdef226b9af856075f28227ff2c90ce3a594d092c39bee5513573f25e2714" + "sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24", + "sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535", + "sha256:27b246b3723692bf1068d5734ddaf2fccc2cdd6e0c9b47fe099244d80200593b", + "sha256:3e6101d095dfd119513cde7259aa703d16c6bbdfae2554dfe5cfdbe94e32d548", + "sha256:3fa2de6b8b29d12c61911505d893afe7320ce7ccba4df913e2971461fa36d584", + "sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b", + "sha256:70585a70fc2dd4818c51287ceef5bdba6387f88a578c86d47bb34669b5552c36", + "sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135", + "sha256:91fba8f445723fcf400fdbe9ca796b19d3b1242cd873907979b9ed71e4afe868", + "sha256:a3f6857551e53ce35e60b403b8a27b0295f7d6eb63d10484f12bc6879c715687", + "sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed" ], "markers": "python_version >= '3.8'", - "version": "==5.27.2" + "version": "==5.28.3" }, "py": { "hashes": [ @@ -1688,19 +1993,19 @@ }, "pyasn1": { "hashes": [ - "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c", - "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473" + "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", + "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034" ], "markers": "python_version >= '3.8'", - "version": "==0.6.0" + "version": "==0.6.1" }, "pyasn1-modules": { "hashes": [ - "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6", - "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b" + "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd", + "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c" ], "markers": "python_version >= '3.8'", - "version": "==0.4.0" + "version": "==0.4.1" }, "pycodestyle": { "hashes": [ @@ -1728,11 +2033,11 @@ }, "pyparsing": { "hashes": [ - "sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad", - "sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742" + "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84", + "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c" ], "markers": "python_version >= '3.1'", - "version": "==3.1.2" + "version": "==3.2.0" }, "pytest": { "hashes": [ @@ -1777,11 +2082,11 @@ }, "tomli": { "hashes": [ - "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38", + "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed" ], "markers": "python_version < '3.11'", - "version": "==2.0.1" + "version": "==2.0.2" }, "types-httplib2": { "hashes": [ @@ -1818,11 +2123,11 @@ }, "urllib3": { "hashes": [ - "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", - "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168" + "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", + "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9" ], "markers": "python_version >= '3.8'", - "version": "==2.2.2" + "version": "==2.2.3" } } } diff --git a/abr-testing/abr_testing/automation/google_drive_tool.py b/abr-testing/abr_testing/automation/google_drive_tool.py index 45464d35c3f..d9ac35f76d1 100644 --- a/abr-testing/abr_testing/automation/google_drive_tool.py +++ b/abr-testing/abr_testing/automation/google_drive_tool.py @@ -56,6 +56,8 @@ def list_folder(self, delete: Any = False, folder: bool = False) -> Set[str]: else "" # type: ignore if self.parent_folder else None, + supportsAllDrives=True, + includeItemsFromAllDrives=True, pageSize=1000, fields="nextPageToken, files(id, name, mimeType)", pageToken=page_token, diff --git a/abr-testing/abr_testing/automation/google_sheets_tool.py b/abr-testing/abr_testing/automation/google_sheets_tool.py index b45684dcea0..d284a13a241 100644 --- a/abr-testing/abr_testing/automation/google_sheets_tool.py +++ b/abr-testing/abr_testing/automation/google_sheets_tool.py @@ -117,7 +117,7 @@ def batch_delete_rows(self, row_indices: List[int]) -> None: def batch_update_cells( self, data: List[List[Any]], - start_column: str, + start_column_index: Any, start_row: int, sheet_id: str, ) -> None: @@ -132,7 +132,8 @@ def column_letter_to_index(column_letter: str) -> int: requests = [] user_entered_value: Dict[str, Any] = {} - start_column_index = column_letter_to_index(start_column) - 1 + if type(start_column_index) == str: + start_column_index = column_letter_to_index(start_column_index) - 1 for col_offset, col_values in enumerate(data): column_index = start_column_index + col_offset @@ -166,12 +167,21 @@ def column_letter_to_index(column_letter: str) -> int: self.spread_sheet.batch_update(body=body) except gspread.exceptions.APIError as e: print(f"ERROR MESSAGE: {e}") + raise def update_cell( self, sheet_title: str, row: int, column: int, single_data: Any ) -> Tuple[int, int, Any]: """Update ONE individual cell according to a row and column.""" - self.spread_sheet.worksheet(sheet_title).update_cell(row, column, single_data) + try: + self.spread_sheet.worksheet(sheet_title).update_cell( + row, column, single_data + ) + except gspread.exceptions.APIError: + t.sleep(30) + self.spread_sheet.worksheet(sheet_title).update_cell( + row, column, single_data + ) return row, column, single_data def get_all_data( @@ -223,9 +233,9 @@ def get_sheet_by_name(self, title: str) -> None: ) def token_check(self) -> None: - """Check if still credentials are still logged in.""" + """Check if credentials are still valid and refresh if expired.""" if self.credentials.access_token_expired: - self.gc.login() + self.gc.login() # Refresh the credentials def get_row_index_with_value(self, some_string: str, col_num: int) -> Any: """Find row index of string by looking in specific column.""" diff --git a/abr-testing/abr_testing/automation/jira_tool.py b/abr-testing/abr_testing/automation/jira_tool.py index 6f81503ec42..a61c16c7f46 100644 --- a/abr-testing/abr_testing/automation/jira_tool.py +++ b/abr-testing/abr_testing/automation/jira_tool.py @@ -51,8 +51,12 @@ def issues_on_board(self, project_key: str) -> List[List[Any]]: def match_issues(self, issue_ids: List[List[str]], ticket_summary: str) -> List: """Matches related ticket ID's.""" to_link = [] - error = ticket_summary.split("_")[3] - robot = ticket_summary.split("_")[0] + try: + error = ticket_summary.split("_")[3] + robot = ticket_summary.split("_")[0] + except IndexError: + error = "" + robot = "" # for every issue see if both match, if yes then grab issue ID and add it to a list for issue in issue_ids: summary = issue[1] @@ -103,6 +107,12 @@ def open_issue(self, issue_key: str) -> str: webbrowser.open(url) return url + def get_labels(self) -> List[str]: + """Get list of available labels.""" + url = f"{self.url}/rest/api/3/label" + response = requests.request("GET", url, headers=self.headers, auth=self.auth) + return response.json() + def create_ticket( self, summary: str, @@ -114,10 +124,12 @@ def create_ticket( priority: str, components: list, affects_versions: str, - robot: str, + labels: list, + parent_name: str, ) -> Tuple[str, str]: """Create ticket.""" # Check if software version is a field on JIRA, if not replaces with existing version + # TODO: automate parent linking data = { "fields": { "project": {"id": "10273", "key": project_key}, @@ -125,7 +137,8 @@ def create_ticket( "summary": summary, "reporter": {"id": reporter_id}, "assignee": {"id": assignee_id}, - "parent": {"key": robot}, + # "parent": {"key": parent_name}, + "labels": labels, "priority": {"name": priority}, "components": [{"name": component} for component in components], "description": { @@ -190,6 +203,7 @@ def post_attachment_to_ticket(self, issue_id: str, attachment_path: str) -> None def get_project_issues(self, project_key: str) -> Dict[str, Any]: """Retrieve all issues for the given project key.""" + # TODO: add field for ticket type. headers = {"Accept": "application/json"} query = {"jql": f"project={project_key}"} response = requests.request( @@ -199,7 +213,6 @@ def get_project_issues(self, project_key: str) -> Dict[str, Any]: params=query, auth=self.auth, ) - response.raise_for_status() return response.json() def get_project_versions(self, project_key: str) -> List[str]: diff --git a/abr-testing/abr_testing/data_collection/abr_calibration_logs.py b/abr-testing/abr_testing/data_collection/abr_calibration_logs.py index 82d9d9c45bc..46cc409e53d 100644 --- a/abr-testing/abr_testing/data_collection/abr_calibration_logs.py +++ b/abr-testing/abr_testing/data_collection/abr_calibration_logs.py @@ -1,129 +1,333 @@ """Get Calibration logs from robots.""" -from typing import Dict, Any, List, Union +from typing import Dict, Any, List, Set import argparse import os import json import sys -import time as t +import traceback +import hashlib from abr_testing.data_collection import read_robot_logs from abr_testing.automation import google_drive_tool, google_sheets_tool -def check_for_duplicates( - sheet_location: str, - google_sheet: Any, - col_1: int, - col_2: int, - row: List[str], - headers: List[str], -) -> Union[List[str], None]: - """Check google sheet for duplicates.""" - t.sleep(5) - serials = google_sheet.get_column(col_1) - modify_dates = google_sheet.get_column(col_2) - # Check for calibration time stamp. - if row[-1] is not None: - if len(row[-1]) > 0: - for serial, modify_date in zip(serials, modify_dates): - if row[col_1 - 1] == serial and row[col_2 - 1] == modify_date: - print( - f"Skipped row for instrument {serial}. Already on Google Sheet." - ) - return None - read_robot_logs.write_to_sheets(sheet_location, google_sheet, row, headers) - print(f"Writing calibration for: {row[7]}") - return row - - -def upload_calibration_offsets( - calibration: Dict[str, Any], storage_directory: str -) -> None: - """Upload calibration data to google_sheet.""" - # Common Headers - headers_beg = list(calibration.keys())[:4] - headers_end = list(["X", "Y", "Z", "lastModified"]) +def instrument_helper( + headers_beg: List[str], + headers_end: List[str], + calibration_log: Dict[Any, Any], + google_sheet_name: str, + inst_sheet_serials: Set[str], + inst_sheet_modify_dates: Set[str], + storage_directory: str, +) -> List[Any]: + """Helper for parsing instrument calibration data.""" + # Populate Instruments # INSTRUMENT SHEET + instruments_upload_rows: List[Any] = [] instrument_headers = ( - headers_beg + list(calibration["Instruments"][0].keys())[:7] + headers_end + headers_beg + list(calibration_log["Instruments"][0].keys())[:7] + headers_end ) local_instrument_file = google_sheet_name + "-Instruments" - instrument_sheet_location = read_robot_logs.create_abr_data_sheet( + read_robot_logs.create_abr_data_sheet( storage_directory, local_instrument_file, instrument_headers ) # INSTRUMENTS DATA - instruments = calibration["Instruments"] + instruments = calibration_log["Instruments"] for instrument in range(len(instruments)): one_instrument = instruments[instrument] + inst_serial = one_instrument["serialNumber"] + modified = one_instrument["data"]["calibratedOffset"].get("last_modified", "") + if inst_serial in inst_sheet_serials and modified in inst_sheet_modify_dates: + continue x = one_instrument["data"]["calibratedOffset"]["offset"].get("x", "") y = one_instrument["data"]["calibratedOffset"]["offset"].get("y", "") z = one_instrument["data"]["calibratedOffset"]["offset"].get("z", "") - modified = one_instrument["data"]["calibratedOffset"].get("last_modified", "") instrument_row = ( - list(calibration.values())[:4] + list(calibration_log.values())[:4] + list(one_instrument.values())[:7] + list([x, y, z, modified]) ) - check_for_duplicates( - instrument_sheet_location, - google_sheet_instruments, - 8, - 15, - instrument_row, - instrument_headers, - ) + instruments_upload_rows.append(instrument_row) + return instruments_upload_rows + +def module_helper( + headers_beg: List[str], + headers_end: List[str], + calibration_log: Dict[Any, Any], + google_sheet_name: str, + module_sheet_serials: Set[str], + module_modify_dates: Set[str], + storage_directory: str, +) -> List[Any]: + """Helper for parsing module calibration data.""" + # Populate Modules # MODULE SHEET - if len(calibration.get("Modules", "")) > 0: + modules_upload_rows: List[Any] = [] + if len(calibration_log.get("Modules", "")) > 0: module_headers = ( - headers_beg + list(calibration["Modules"][0].keys())[:7] + headers_end + headers_beg + list(calibration_log["Modules"][0].keys())[:7] + headers_end ) local_modules_file = google_sheet_name + "-Modules" - modules_sheet_location = read_robot_logs.create_abr_data_sheet( + read_robot_logs.create_abr_data_sheet( storage_directory, local_modules_file, module_headers ) # MODULES DATA - modules = calibration["Modules"] + modules = calibration_log["Modules"] for module in range(len(modules)): one_module = modules[module] - x = one_module["moduleOffset"]["offset"].get("x", "") - y = one_module["moduleOffset"]["offset"].get("y", "") - z = one_module["moduleOffset"]["offset"].get("z", "") - modified = one_module["moduleOffset"].get("last_modified", "") + mod_serial = one_module["serialNumber"] + try: + modified = one_module["moduleOffset"].get("last_modified", "") + x = one_module["moduleOffset"]["offset"].get("x", "") + y = one_module["moduleOffset"]["offset"].get("y", "") + z = one_module["moduleOffset"]["offset"].get("z", "") + except KeyError: + continue + if mod_serial in module_sheet_serials and modified in module_modify_dates: + continue module_row = ( - list(calibration.values())[:4] + list(calibration_log.values())[:4] + list(one_module.values())[:7] + list([x, y, z, modified]) ) - check_for_duplicates( - modules_sheet_location, - google_sheet_modules, - 8, - 15, - module_row, - module_headers, - ) + modules_upload_rows.append(module_row) + return modules_upload_rows + + +def create_hash( + robot_name: str, deck_slot: str, pipette_calibrated_with: str, last_modified: str +) -> str: + """Create unique hash identifier for deck calibrations.""" + combined_string = robot_name + deck_slot + pipette_calibrated_with + last_modified + hashed_obj = hashlib.sha256(combined_string.encode()) + return hashed_obj.hexdigest() + + +def deck_helper( + headers_beg: List[str], + headers_end: List[str], + calibration_log: Dict[Any, Any], + google_sheet_name: str, + deck_sheet_hashes: Set[str], + storage_directory: str, +) -> List[Any]: + """Helper for parsing deck calibration data.""" + deck_upload_rows: List[Any] = [] + # Populate Deck # DECK SHEET local_deck_file = google_sheet_name + "-Deck" deck_headers = headers_beg + list(["pipetteCalibratedWith", "Slot"]) + headers_end - deck_sheet_location = read_robot_logs.create_abr_data_sheet( + read_robot_logs.create_abr_data_sheet( storage_directory, local_deck_file, deck_headers ) # DECK DATA - deck = calibration["Deck"] + deck = calibration_log["Deck"] + deck_modified = str(deck["data"].get("lastModified")) slots = ["D3", "D1", "A1"] - deck_modified = deck["data"].get("lastModified", "") - pipette_calibrated_with = deck["data"].get("pipetteCalibratedWith", "") + pipette_calibrated_with = str(deck["data"].get("pipetteCalibratedWith", "")) for i in range(len(deck["data"]["matrix"])): + robot = calibration_log["Robot"] + deck_slot = slots[i] + unique_hash = create_hash( + robot, deck_slot, pipette_calibrated_with, deck_modified + ) + if unique_hash in deck_sheet_hashes: + continue coords = deck["data"]["matrix"][i] x = coords[0] y = coords[1] z = coords[2] - deck_row = list(calibration.values())[:4] + list( + deck_row = list(calibration_log.values())[:4] + list( [pipette_calibrated_with, slots[i], x, y, z, deck_modified] ) - check_for_duplicates( - deck_sheet_location, google_sheet_deck, 6, 10, deck_row, deck_headers + deck_upload_rows.append(deck_row) + return deck_upload_rows + + +def send_batch_update( + instruments_upload_rows: List[str], + google_sheet_instruments: google_sheets_tool.google_sheet, + modules_upload_rows: List[str], + google_sheet_modules: google_sheets_tool.google_sheet, + deck_upload_rows: List[str], + google_sheet_deck: google_sheets_tool.google_sheet, +) -> None: + """Executes batch updates.""" + # Prepare data for batch update + try: + transposed_instruments_upload_rows = list( + map(list, zip(*instruments_upload_rows)) + ) + google_sheet_instruments.batch_update_cells( + transposed_instruments_upload_rows, + "A", + google_sheet_instruments.get_index_row() + 1, + "0", + ) + except Exception: + print("No new instrument data") + try: + transposed_module_upload_rows = list(map(list, zip(*modules_upload_rows))) + google_sheet_modules.batch_update_cells( + transposed_module_upload_rows, + "A", + google_sheet_modules.get_index_row() + 1, + "1020695883", + ) + except Exception: + print("No new module data") + try: + transposed_deck_upload_rows = list(map(list, zip(*deck_upload_rows))) + google_sheet_deck.batch_update_cells( + transposed_deck_upload_rows, + "A", + google_sheet_deck.get_index_row() + 1, + "1332568460", + ) + except Exception: + print("No new deck data") + + +def upload_calibration_offsets( + calibration_data: List[Dict[str, Any]], + storage_directory: str, + google_sheet_instruments: google_sheets_tool.google_sheet, + google_sheet_modules: google_sheets_tool.google_sheet, + google_sheet_deck: google_sheets_tool.google_sheet, + google_sheet_name: str, +) -> None: + """Upload calibration data to google_sheet.""" + # Common Headers + headers_beg = list(calibration_data[0].keys())[:4] + headers_end = list(["X", "Y", "Z", "lastModified"]) + sheets = [google_sheet_instruments, google_sheet_modules, google_sheet_deck] + instruments_upload_rows: List[Any] = [] + modules_upload_rows: List[Any] = [] + deck_upload_rows: List[Any] = [] + inst_sheet_serials: Set[str] = set() + inst_sheet_modify_dates: Set[str] = set() + module_sheet_serials: Set[str] = set() + deck_sheet_hashes: Set[str] = set() + # Get current serials, and modified info from google sheet + for i, sheet in enumerate(sheets): + if i == 0: + inst_sheet_serials = sheet.get_column(8) + inst_sheet_modify_dates = sheet.get_column(15) + if i == 1: + module_sheet_serials = sheet.get_column(6) + module_modify_dates = sheet.get_column(15) + elif i == 2: + deck_sheet_hashes = sheet.get_column(11) + + # Iterate through calibration logs and accumulate data + for calibration_log in calibration_data: + for sheet_ind, sheet in enumerate(sheets): + if sheet_ind == 0: + instruments_upload_rows += instrument_helper( + headers_beg, + headers_end, + calibration_log, + google_sheet_name, + inst_sheet_serials, + inst_sheet_modify_dates, + storage_directory, + ) + elif sheet_ind == 1: + modules_upload_rows += module_helper( + headers_beg, + headers_end, + calibration_log, + google_sheet_name, + module_sheet_serials, + module_modify_dates, + storage_directory, + ) + elif sheet_ind == 2: + deck_upload_rows += deck_helper( + headers_beg, + headers_end, + calibration_log, + google_sheet_name, + deck_sheet_hashes, + storage_directory, + ) + send_batch_update( + instruments_upload_rows, + google_sheet_instruments, + modules_upload_rows, + google_sheet_modules, + deck_upload_rows, + google_sheet_deck, + ) + + +def run( + storage_directory: str, folder_name: str, google_sheet_name_param: str, email: str +) -> None: + """Main control function.""" + # Connect to google drive. + google_sheet_name = google_sheet_name_param + try: + credentials_path = os.path.join(storage_directory, "credentials.json") + except FileNotFoundError: + print(f"Add credentials.json file to: {storage_directory}.") + sys.exit() + google_drive = google_drive_tool.google_drive(credentials_path, folder_name, email) + # Connect to google sheet + google_sheet_instruments = google_sheets_tool.google_sheet( + credentials_path, google_sheet_name, 0 + ) + google_sheet_modules = google_sheets_tool.google_sheet( + credentials_path, google_sheet_name, 1 + ) + google_sheet_deck = google_sheets_tool.google_sheet( + credentials_path, google_sheet_name, 2 + ) + ip_json_file = os.path.join(storage_directory, "IPs.json") + try: + ip_file = json.load(open(ip_json_file)) + robot_dict = ip_file.get("ip_address_list") + except FileNotFoundError: + print(f"Add .json file with robot IPs to: {storage_directory}.") + sys.exit() + ip_or_all = "" + while not ip_or_all: + ip_or_all = input("IP Address or ALL: ") + calibration_data = [] + if ip_or_all.upper() == "ALL": + ip_address_list = list(robot_dict.keys()) + for ip in ip_address_list: + saved_file_path, calibration = read_robot_logs.get_calibration_offsets( + ip, storage_directory + ) + calibration_data.append(calibration) + else: + try: + ( + saved_file_path, + calibration, + ) = read_robot_logs.get_calibration_offsets( + ip_or_all, storage_directory + ) + calibration_data.append(calibration) + except Exception: + print("Invalid IP try again") + ip_or_all = "" + try: + upload_calibration_offsets( + calibration_data, + storage_directory, + google_sheet_instruments, + google_sheet_modules, + google_sheet_deck, + google_sheet_name, ) + print("Successfully uploaded calibration data!") + except Exception: + print("No calibration data to upload: ") + traceback.print_exc() + sys.exit(1) + google_drive.upload_missing_files(storage_directory) if __name__ == "__main__": @@ -160,42 +364,4 @@ def upload_calibration_offsets( folder_name = args.folder_name[0] google_sheet_name = args.google_sheet_name[0] email = args.email[0] - # Connect to google drive. - try: - credentials_path = os.path.join(storage_directory, "credentials.json") - except FileNotFoundError: - print(f"Add credentials.json file to: {storage_directory}.") - sys.exit() - google_drive = google_drive_tool.google_drive(credentials_path, folder_name, email) - # Connect to google sheet - google_sheet_instruments = google_sheets_tool.google_sheet( - credentials_path, google_sheet_name, 0 - ) - google_sheet_modules = google_sheets_tool.google_sheet( - credentials_path, google_sheet_name, 1 - ) - google_sheet_deck = google_sheets_tool.google_sheet( - credentials_path, google_sheet_name, 2 - ) - ip_json_file = os.path.join(storage_directory, "IPs.json") - try: - ip_file = json.load(open(ip_json_file)) - except FileNotFoundError: - print(f"Add .json file with robot IPs to: {storage_directory}.") - sys.exit() - ip_or_all = input("IP Address or ALL: ") - - if ip_or_all == "ALL": - ip_address_list = ip_file["ip_address_list"] - for ip in ip_address_list: - saved_file_path, calibration = read_robot_logs.get_calibration_offsets( - ip, storage_directory - ) - upload_calibration_offsets(calibration, storage_directory) - else: - saved_file_path, calibration = read_robot_logs.get_calibration_offsets( - ip_or_all, storage_directory - ) - upload_calibration_offsets(calibration, storage_directory) - - google_drive.upload_missing_files(storage_directory) + run(storage_directory, folder_name, google_sheet_name, email) diff --git a/abr-testing/abr_testing/data_collection/abr_google_drive.py b/abr-testing/abr_testing/data_collection/abr_google_drive.py index 3bd03cf3e3d..8f82567a7d1 100644 --- a/abr-testing/abr_testing/data_collection/abr_google_drive.py +++ b/abr-testing/abr_testing/data_collection/abr_google_drive.py @@ -7,7 +7,7 @@ from abr_testing.data_collection import read_robot_logs from typing import Set, Dict, Any, Tuple, List, Union from abr_testing.automation import google_drive_tool, google_sheets_tool -from abr_testing.tools import sync_abr_sheet +from abr_testing.tools import sync_abr_sheet, plate_reader def get_modules(file_results: Dict[str, str]) -> Dict[str, Any]: @@ -17,6 +17,7 @@ def get_modules(file_results: Dict[str, str]) -> Dict[str, Any]: "temperatureModuleV2", "magneticBlockV1", "thermocyclerModuleV2", + "absorbanceReaderV1", ) all_modules = {key: "" for key in modList} for module in file_results.get("modules", []): @@ -35,17 +36,24 @@ def create_data_dictionary( issue_url: str, plate: str, accuracy: Any, -) -> Tuple[List[List[Any]], List[str], List[List[Any]], List[str]]: + hellma_plate_standards: List[Dict[str, Any]], +) -> Tuple[List[List[Any]], List[str], List[List[Any]], List[str], List[List[Any]]]: """Pull data from run files and format into a dictionary.""" runs_and_robots: List[Any] = [] runs_and_lpc: List[Dict[str, Any]] = [] headers: List[str] = [] headers_lpc: List[str] = [] + list_of_heights: List[List[Any]] = [[], [], [], [], [], [], [], []] + hellma_plate_orientation = False # default hellma plate is not rotated. for filename in os.listdir(storage_directory): file_path = os.path.join(storage_directory, filename) if file_path.endswith(".json"): with open(file_path) as file: - file_results = json.load(file) + try: + file_results = json.load(file) + except json.decoder.JSONDecodeError: + print(f"Skipped file {file_path} bc no data.") + continue else: continue if not isinstance(file_results, dict): @@ -58,20 +66,18 @@ def create_data_dictionary( print(f"Run {run_id} is incomplete. Skipping run.") continue if run_id in runs_to_save: - print("started reading run.") + print(f"started reading run {run_id}.") robot = file_results.get("robot_name") + parameters = file_results.get("runTimeParameters", "") + for parameter in parameters: + if parameter["displayName"] == "Hellma Plate Orientation": + hellma_plate_orientation = bool(parameter["value"]) protocol_name = file_results["protocol"]["metadata"].get("protocolName", "") software_version = file_results.get("API_Version", "") left_pipette = file_results.get("left", "") right_pipette = file_results.get("right", "") extension = file_results.get("extension", "") - ( - num_of_errors, - error_type, - error_code, - error_instrument, - error_level, - ) = read_robot_logs.get_error_info(file_results) + error_dict = read_robot_logs.get_error_info(file_results) all_modules = get_modules(file_results) @@ -99,7 +105,7 @@ def create_data_dictionary( pass # Handle datetime parsing errors if necessary if run_time_min > 0: - row = { + run_row = { "Robot": robot, "Run_ID": run_id, "Protocol_Name": protocol_name, @@ -108,19 +114,25 @@ def create_data_dictionary( "Start_Time": start_time_str, "End_Time": complete_time_str, "Run_Time (min)": run_time_min, - "Errors": num_of_errors, - "Error_Code": error_code, - "Error_Type": error_type, - "Error_Instrument": error_instrument, - "Error_Level": error_level, + } + instrument_row = { "Left Mount": left_pipette, "Right Mount": right_pipette, "Extension": extension, } + row = {**run_row, **error_dict, **instrument_row} tc_dict = read_robot_logs.thermocycler_commands(file_results) hs_dict = read_robot_logs.hs_commands(file_results) tm_dict = read_robot_logs.temperature_module_commands(file_results) - pipette_dict = read_robot_logs.instrument_commands(file_results) + pipette_dict = read_robot_logs.instrument_commands( + file_results, labware_name="opentrons_tough_pcr_auto_sealing_lid" + ) + plate_reader_dict = read_robot_logs.plate_reader_commands( + file_results, hellma_plate_standards, hellma_plate_orientation + ) + list_of_heights = read_robot_logs.liquid_height_commands( + file_results, list_of_heights + ) notes = {"Note1": "", "Jira Link": issue_url} plate_measure = { "Plate Measured": plate, @@ -128,7 +140,11 @@ def create_data_dictionary( "Average Temp (oC)": "", "Average RH(%)": "", } - row_for_lpc = {**row, **all_modules, **notes} + row_for_lpc = { + **row, + **all_modules, + **notes, + } row_2 = { **row, **all_modules, @@ -136,6 +152,7 @@ def create_data_dictionary( **hs_dict, **tm_dict, **tc_dict, + **plate_reader_dict, **pipette_dict, **plate_measure, } @@ -151,40 +168,19 @@ def create_data_dictionary( print(f"Number of runs read: {num_of_runs_read}") transposed_runs_and_robots = list(map(list, zip(*runs_and_robots))) transposed_runs_and_lpc = list(map(list, zip(*runs_and_lpc))) - return transposed_runs_and_robots, headers, transposed_runs_and_lpc, headers_lpc + return ( + transposed_runs_and_robots, + headers, + transposed_runs_and_lpc, + headers_lpc, + list_of_heights, + ) -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Read run logs on google drive.") - parser.add_argument( - "storage_directory", - metavar="STORAGE_DIRECTORY", - type=str, - nargs=1, - help="Path to long term storage directory for run logs.", - ) - parser.add_argument( - "folder_name", - metavar="FOLDER_NAME", - type=str, - nargs=1, - help="Google Drive folder name. Open desired folder and copy string after drive/folders/.", - ) - parser.add_argument( - "google_sheet_name", - metavar="GOOGLE_SHEET_NAME", - type=str, - nargs=1, - help="Google sheet name.", - ) - parser.add_argument( - "email", metavar="EMAIL", type=str, nargs=1, help="opentrons gmail." - ) - args = parser.parse_args() - folder_name = args.folder_name[0] - storage_directory = args.storage_directory[0] - google_sheet_name = args.google_sheet_name[0] - email = args.email[0] +def run( + storage_directory: str, folder_name: str, google_sheet_name: str, email: str +) -> None: + """Main control function.""" try: credentials_path = os.path.join(storage_directory, "credentials.json") except FileNotFoundError: @@ -196,9 +192,9 @@ def create_data_dictionary( credentials_path, google_sheet_name, 0 ) # Get run ids on google sheet - run_ids_on_gs = set(google_sheet.get_column(2)) + run_ids_on_gs: Set[str] = set(google_sheet.get_column(2)) + # Get robots on google sheet - robots = list(set(google_sheet.get_column(1))) # Uploads files that are not in google drive directory google_drive.upload_missing_files(storage_directory) @@ -207,23 +203,74 @@ def create_data_dictionary( missing_runs_from_gs = read_robot_logs.get_unseen_run_ids( run_ids_on_gd, run_ids_on_gs ) + # Read Hellma Files + file_values = plate_reader.read_hellma_plate_files(storage_directory, 101934) # Add missing runs to google sheet ( transposed_runs_and_robots, headers, transposed_runs_and_lpc, headers_lpc, - ) = create_data_dictionary(missing_runs_from_gs, storage_directory, "", "", "") + list_of_heights, + ) = create_data_dictionary( + missing_runs_from_gs, + storage_directory, + "", + "", + "", + hellma_plate_standards=file_values, + ) start_row = google_sheet.get_index_row() + 1 - print(start_row) google_sheet.batch_update_cells(transposed_runs_and_robots, "A", start_row, "0") - + # Record Liquid Heights Found + google_sheet_ldf = google_sheets_tool.google_sheet( + credentials_path, google_sheet_name, 2 + ) + google_sheet_ldf.get_row(1) + start_row_lhd = google_sheet_ldf.get_index_row() + 1 + google_sheet_ldf.batch_update_cells( + list_of_heights, "A", start_row_lhd, "2075262446" + ) # Add LPC to google sheet google_sheet_lpc = google_sheets_tool.google_sheet(credentials_path, "ABR-LPC", 0) start_row_lpc = google_sheet_lpc.get_index_row() + 1 google_sheet_lpc.batch_update_cells( transposed_runs_and_lpc, "A", start_row_lpc, "0" ) - robots = list(set(google_sheet.get_column(1))) # Calculate Robot Lifetimes sync_abr_sheet.determine_lifetime(google_sheet) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Read run logs on google drive.") + parser.add_argument( + "storage_directory", + metavar="STORAGE_DIRECTORY", + type=str, + nargs=1, + help="Path to long term storage directory for run logs.", + ) + parser.add_argument( + "folder_name", + metavar="FOLDER_NAME", + type=str, + nargs=1, + help="Google Drive folder name. Open desired folder and copy string after drive/folders/.", + ) + parser.add_argument( + "google_sheet_name", + metavar="GOOGLE_SHEET_NAME", + type=str, + nargs=1, + help="Google sheet name.", + ) + parser.add_argument( + "email", metavar="EMAIL", type=str, nargs=1, help="opentrons gmail." + ) + args = parser.parse_args() + folder_name = args.folder_name[0] + storage_directory = args.storage_directory[0] + google_sheet_name = args.google_sheet_name[0] + email = args.email[0] + + run(storage_directory, folder_name, google_sheet_name, email) diff --git a/abr-testing/abr_testing/data_collection/abr_robot_error.py b/abr-testing/abr_testing/data_collection/abr_robot_error.py index 76a3f09ec18..73cf12c6253 100644 --- a/abr-testing/abr_testing/data_collection/abr_robot_error.py +++ b/abr-testing/abr_testing/data_collection/abr_robot_error.py @@ -13,6 +13,7 @@ import re import pandas as pd from statistics import mean, StatisticsError +from abr_testing.tools import plate_reader def compare_current_trh_to_average( @@ -62,7 +63,9 @@ def compare_current_trh_to_average( df_all_run_data["Start_Time"] = pd.to_datetime( df_all_run_data["Start_Time"], format="mixed", utc=True ).dt.tz_localize(None) - df_all_run_data["Errors"] = pd.to_numeric(df_all_run_data["Errors"]) + df_all_run_data["Run Ending Error"] = pd.to_numeric( + df_all_run_data["Run Ending Error"] + ) df_all_run_data["Average Temp (oC)"] = pd.to_numeric( df_all_run_data["Average Temp (oC)"] ) @@ -70,7 +73,7 @@ def compare_current_trh_to_average( (df_all_run_data["Robot"] == robot) & (df_all_run_data["Start_Time"] >= weeks_ago_3) & (df_all_run_data["Start_Time"] <= start_time) - & (df_all_run_data["Errors"] < 1) + & (df_all_run_data["Run Ending Error"] < 1) & (df_all_run_data["Average Temp (oC)"] > 1) ) @@ -122,15 +125,20 @@ def compare_lpc_to_historical_data( & (df_lpc_data["Robot"] == robot) & (df_lpc_data["Module"] == labware_dict["Module"]) & (df_lpc_data["Adapter"] == labware_dict["Adapter"]) - & (df_lpc_data["Errors"] < 1) + & (df_lpc_data["Run Ending Error"]) + < 1 ] # Converts coordinates to floats and finds averages. - x_float = [float(value) for value in relevant_lpc["X"]] - y_float = [float(value) for value in relevant_lpc["Y"]] - z_float = [float(value) for value in relevant_lpc["Z"]] - current_x = round(labware_dict["X"], 2) - current_y = round(labware_dict["Y"], 2) - current_z = round(labware_dict["Z"], 2) + try: + x_float = [float(value) for value in relevant_lpc["X"]] + y_float = [float(value) for value in relevant_lpc["Y"]] + z_float = [float(value) for value in relevant_lpc["Z"]] + current_x = round(labware_dict["X"], 2) + current_y = round(labware_dict["Y"], 2) + current_z = round(labware_dict["Z"], 2) + except (ValueError): + x_float, y_float, z_float = [0.0], [0.0], [0.0] + current_x, current_y, current_z = 0.0, 0.0, 0.0 try: avg_x = round(mean(x_float), 2) avg_y = round(mean(y_float), 2) @@ -244,7 +252,7 @@ def get_error_runs_from_robot(ip: str) -> List[str]: f"http://{ip}:31950/runs", headers={"opentrons-version": "3"} ) run_data = response.json() - run_list = run_data["data"] + run_list = run_data.get("data", []) for run in run_list: run_id = run["id"] num_of_errors = len(run["errors"]) @@ -255,7 +263,7 @@ def get_error_runs_from_robot(ip: str) -> List[str]: def get_robot_state( ip: str, reported_string: str -) -> Tuple[Any, Any, Any, List[str], str]: +) -> Tuple[Any, Any, Any, List[str], List[str], str]: """Get robot status in case of non run error.""" description = dict() # Get instruments attached to robot @@ -271,10 +279,11 @@ def get_robot_state( f"http://{ip}:31950/health", headers={"opentrons-version": "3"} ) health_data = response.json() - parent = health_data.get("name", "") + print(f"health data {health_data}") + robot = health_data.get("name", "") # Create summary name - description["robot_name"] = parent - summary = parent + "_" + reported_string + description["robot_name"] = robot + summary = robot + "_" + reported_string affects_version = health_data.get("api_version", "") description["affects_version"] = affects_version # Instruments Attached @@ -294,6 +303,12 @@ def get_robot_state( description[module["moduleType"]] = module components = ["Flex-RABR"] components = match_error_to_component("RABR", reported_string, components) + if "alpha" in affects_version: + components.append("flex internal releases") + labels = [robot] + if "8.2" in affects_version: + labels.append("8_2_0") + parent = affects_version + " Bugs" print(components) end_time = datetime.now() print(end_time) @@ -314,13 +329,14 @@ def get_robot_state( parent, affects_version, components, + labels, whole_description_str, ) def get_run_error_info_from_robot( ip: str, one_run: str, storage_directory: str -) -> Tuple[str, str, str, List[str], str, str]: +) -> Tuple[str, str, str, List[str], List[str], str, str]: """Get error information from robot to fill out ticket.""" description = dict() # get run information @@ -330,23 +346,25 @@ def get_run_error_info_from_robot( ip, results, storage_directory ) # Error Printout - ( - num_of_errors, - error_type, - error_code, - error_instrument, - error_level, - ) = read_robot_logs.get_error_info(results) + error_dict = read_robot_logs.get_error_info(results) + error_level = error_dict["Error_Level"] + error_type = error_dict["Error_Type"] + error_code = error_dict["Error_Code"] + error_instrument = error_dict["Error_Instrument"] # JIRA Ticket Fields + robot = results.get("robot_name", "") failure_level = "Level " + str(error_level) + " Failure" components = [failure_level, "Flex-RABR"] - components = match_error_to_component("RABR", error_type, components) - print(components) + components = match_error_to_component("RABR", str(error_type), components) affects_version = results["API_Version"] - parent = results.get("robot_name", "") - print(parent) - summary = parent + "_" + str(one_run) + "_" + str(error_code) + "_" + error_type + if "alpha" in affects_version: + components.append("flex internal releases") + labels = [robot] + if "8.2" in affects_version: + labels.append("8_2_0") + parent = affects_version + " Bugs" + summary = robot + "_" + str(one_run) + "_" + str(error_code) + "_" + error_type # Description of error description["protocol_name"] = results["protocol"]["metadata"].get( "protocolName", "" @@ -428,6 +446,7 @@ def get_run_error_info_from_robot( parent, affects_version, components, + labels, whole_description_str, saved_file_path, ) @@ -501,18 +520,20 @@ def get_run_error_info_from_robot( one_run = error_runs[-1] # Most recent run with error. ( summary, - robot, + parent, affects_version, components, + labels, whole_description_str, run_log_file_path, ) = get_run_error_info_from_robot(ip, one_run, storage_directory) else: ( summary, - robot, + parent, affects_version, components, + labels, whole_description_str, ) = get_robot_state(ip, run_or_other) # Get Calibration Data @@ -523,13 +544,8 @@ def get_run_error_info_from_robot( print(f"Making ticket for {summary}.") # TODO: make argument or see if I can get rid of with using board_id. project_key = "RABR" - print(robot) - parent_key = project_key + "-" + robot.split("ABR")[1] - - # Grab all previous issues - all_issues = ticket.issues_on_board(project_key) - # TODO: read board to see if ticket for run id already exists. + all_issues = ticket.issues_on_board(project_key) # CREATE TICKET issue_key, raw_issue_url = ticket.create_ticket( summary, @@ -541,7 +557,8 @@ def get_run_error_info_from_robot( "Medium", components, affects_version, - parent_key, + labels, + parent, ) # Link Tickets to_link = ticket.match_issues(all_issues, summary) @@ -589,18 +606,36 @@ def get_run_error_info_from_robot( except FileNotFoundError: print("Run file not uploaded.") run_id = os.path.basename(error_run_log).split("_")[1].split(".")[0] + # Get hellma readings + file_values = plate_reader.read_hellma_plate_files(storage_directory, 101934) + ( runs_and_robots, headers, runs_and_lpc, headers_lpc, + list_of_heights, ) = abr_google_drive.create_data_dictionary( - run_id, error_folder_path, issue_url, "", "" + run_id, + error_folder_path, + issue_url, + "", + "", + hellma_plate_standards=file_values, ) start_row = google_sheet.get_index_row() + 1 google_sheet.batch_update_cells(runs_and_robots, "A", start_row, "0") print("Wrote run to ABR-run-data") + # Record Liquid Heights Found + google_sheet_ldf = google_sheets_tool.google_sheet( + credentials_path, google_sheet_name, 4 + ) + start_row_lhd = google_sheet_ldf.get_index_row() + 1 + google_sheet_ldf.batch_update_cells( + list_of_heights, "A", start_row_lhd, "1795535088" + ) + print("wrote liquid heights found.") # Add LPC to google sheet google_sheet_lpc = google_sheets_tool.google_sheet( credentials_path, "ABR-LPC", 0 diff --git a/abr-testing/abr_testing/data_collection/get_run_logs.py b/abr-testing/abr_testing/data_collection/get_run_logs.py index cc36acd5760..fe89f9f1543 100644 --- a/abr-testing/abr_testing/data_collection/get_run_logs.py +++ b/abr-testing/abr_testing/data_collection/get_run_logs.py @@ -17,7 +17,7 @@ def get_run_ids_from_robot(ip: str) -> Set[str]: f"http://{ip}:31950/runs", headers={"opentrons-version": "3"} ) run_data = response.json() - run_list = run_data["data"] + run_list = run_data.get("data", "") except requests.exceptions.RequestException: print(f"Could not connect to robot with IP {ip}") run_list = [] @@ -47,7 +47,7 @@ def get_run_data(one_run: Any, ip: str) -> Dict[str, Any]: params={"cursor": cursor, "pageLength": page_length}, ) command_data = response.json() - commands.extend(command_data["data"]) + commands.extend(command_data.get("data", "")) run["commands"] = commands response = requests.get( f"http://{ip}:31950/runs/{one_run}", headers={"opentrons-version": "3"} @@ -92,7 +92,9 @@ def save_runs(runs_to_save: Set[str], ip: str, storage_directory: str) -> Set[st return saved_file_paths -def get_all_run_logs(storage_directory: str) -> None: +def get_all_run_logs( + storage_directory: str, google_drive: google_drive_tool.google_drive +) -> None: """GET ALL RUN LOGS. Connect to each ABR robot to read run log data. @@ -102,10 +104,11 @@ def get_all_run_logs(storage_directory: str) -> None: ip_json_file = os.path.join(storage_directory, "IPs.json") try: ip_file = json.load(open(ip_json_file)) + robot_dict = ip_file.get("ip_address_list") except FileNotFoundError: print(f"Add .json file with robot IPs to: {storage_directory}.") sys.exit() - ip_address_list = ip_file["ip_address_list"] + ip_address_list = list(robot_dict.keys()) runs_from_storage = read_robot_logs.get_run_ids_from_google_drive(google_drive) for ip in ip_address_list: runs = get_run_ids_from_robot(ip) @@ -114,6 +117,17 @@ def get_all_run_logs(storage_directory: str) -> None: google_drive.upload_missing_files(storage_directory) +def run(storage_directory: str, folder_name: str, email: str) -> None: + """Main control function.""" + try: + credentials_path = os.path.join(storage_directory, "credentials.json") + except FileNotFoundError: + print(f"Add credentials.json file to: {storage_directory}.") + sys.exit() + google_drive = google_drive_tool.google_drive(credentials_path, folder_name, email) + get_all_run_logs(storage_directory, google_drive) + + if __name__ == "__main__": """Get run logs.""" parser = argparse.ArgumentParser(description="Pulls run logs from ABR robots.") @@ -138,10 +152,4 @@ def get_all_run_logs(storage_directory: str) -> None: storage_directory = args.storage_directory[0] folder_name = args.folder_name[0] email = args.email[0] - try: - credentials_path = os.path.join(storage_directory, "credentials.json") - except FileNotFoundError: - print(f"Add credentials.json file to: {storage_directory}.") - sys.exit() - google_drive = google_drive_tool.google_drive(credentials_path, folder_name, email) - get_all_run_logs(storage_directory) + run(storage_directory, folder_name, email) diff --git a/abr-testing/abr_testing/data_collection/read_robot_logs.py b/abr-testing/abr_testing/data_collection/read_robot_logs.py index 740adbf0cb6..7bc83e0a54b 100644 --- a/abr-testing/abr_testing/data_collection/read_robot_logs.py +++ b/abr-testing/abr_testing/data_collection/read_robot_logs.py @@ -9,11 +9,11 @@ from datetime import datetime import os from abr_testing.data_collection.error_levels import ERROR_LEVELS_PATH -from typing import List, Dict, Any, Tuple, Set +from typing import List, Dict, Any, Tuple, Set, Optional import time as t import json import requests -import sys +from abr_testing.tools import plate_reader def lpc_data( @@ -76,7 +76,75 @@ def command_time(command: Dict[str, str]) -> float: return start_to_complete -def instrument_commands(file_results: Dict[str, Any]) -> Dict[str, float]: +def count_command_in_run_data( + commands: List[Dict[str, Any]], command_of_interest: str, find_avg_time: bool +) -> Tuple[int, float]: + """Count number of times command occurs in a run.""" + total_command = 0 + total_time = 0.0 + for command in commands: + command_type = command["commandType"] + if command_type == command_of_interest: + total_command += 1 + if find_avg_time: + started_at = command.get("startedAt", "") + completed_at = command.get("completedAt", "") + + if started_at and completed_at: + try: + start_time = datetime.strptime( + started_at, "%Y-%m-%dT%H:%M:%S.%f%z" + ) + end_time = datetime.strptime( + completed_at, "%Y-%m-%dT%H:%M:%S.%f%z" + ) + total_time += (end_time - start_time).total_seconds() + except ValueError: + # Handle case where date parsing fails + pass + avg_time = total_time / total_command if total_command > 0 else 0.0 + return total_command, avg_time + + +def identify_labware_ids( + file_results: Dict[str, Any], labware_name: Optional[str] +) -> List[str]: + """Determine what type of labware is being picked up.""" + list_of_labware_ids: List[str] = [] + if labware_name: + labwares = file_results.get("labware", "") + list_of_labware_ids = [] + if len(labwares) > 1: + for labware in labwares: + load_name = labware["loadName"] + if load_name == labware_name: + labware_id = labware["id"] + list_of_labware_ids.append(labware_id) + return list_of_labware_ids + + +def match_pipette_to_action( + command_dict: Dict[str, Any], + commandTypes: List[str], + right_pipette: Optional[str], + left_pipette: Optional[str], +) -> Tuple[int, int]: + """Match pipette id to id in command.""" + right_pipette_add = 0 + left_pipette_add = 0 + for command in commandTypes: + command_type = command_dict["commandType"] + command_pipette = command_dict.get("pipetteId", "") + if command_type == command and command_pipette == right_pipette: + right_pipette_add = 1 + elif command_type == command and command_pipette == left_pipette: + left_pipette_add = 1 + return left_pipette_add, right_pipette_add + + +def instrument_commands( + file_results: Dict[str, Any], labware_name: Optional[str] +) -> Dict[str, float]: """Count number of pipette and gripper commands per run.""" pipettes = file_results.get("pipettes", "") commandData = file_results.get("commands", "") @@ -89,6 +157,9 @@ def instrument_commands(file_results: Dict[str, Any]) -> Dict[str, float]: right_pipette_id = "" left_pipette_id = "" gripper_pickups = 0.0 + gripper_labware_of_interest = 0.0 + avg_liquid_probe_time_sec = 0.0 + list_of_labware_ids = identify_labware_ids(file_results, labware_name) # Match pipette mount to id for pipette in pipettes: if pipette["mount"] == "right": @@ -96,30 +167,37 @@ def instrument_commands(file_results: Dict[str, Any]) -> Dict[str, float]: elif pipette["mount"] == "left": left_pipette_id = pipette["id"] for command in commandData: - commandType = command["commandType"] - # Count tip pick ups - if commandType == "pickUpTip": - if command["params"].get("pipetteId", "") == right_pipette_id: - right_tip_pick_up += 1 - elif command["params"].get("pipetteId", "") == left_pipette_id: - left_tip_pick_up += 1 + # Count pick ups + single_left_pickup, single_right_pickup = match_pipette_to_action( + command, ["pickUpTip"], right_pipette_id, left_pipette_id + ) + right_tip_pick_up += single_right_pickup + left_tip_pick_up += single_left_pickup # Count aspirates - elif commandType == "aspirate": - if command["params"].get("pipetteId", "") == right_pipette_id: - right_aspirate += 1 - elif command["params"].get("pipetteId", "") == left_pipette_id: - left_aspirate += 1 + single_left_aspirate, single_right_aspirate = match_pipette_to_action( + command, ["aspirate"], right_pipette_id, left_pipette_id + ) + right_aspirate += single_right_aspirate + left_aspirate += single_left_aspirate # count dispenses/blowouts - elif commandType == "dispense" or commandType == "blowout": - if command["params"].get("pipetteId", "") == right_pipette_id: - right_dispense += 1 - elif command["params"].get("pipetteId", "") == left_pipette_id: - left_dispense += 1 - elif ( + single_left_dispense, single_right_dispense = match_pipette_to_action( + command, ["blowOut", "dispense"], right_pipette_id, left_pipette_id + ) + right_dispense += single_right_dispense + left_dispense += single_left_dispense + # count gripper actions + commandType = command["commandType"] + if ( commandType == "moveLabware" and command["params"]["strategy"] == "usingGripper" ): gripper_pickups += 1 + labware_moving = command["params"]["labwareId"] + if labware_moving in list_of_labware_ids: + gripper_labware_of_interest += 1 + liquid_probes, avg_liquid_probe_time_sec = count_command_in_run_data( + commandData, "liquidProbe", True + ) pipette_dict = { "Left Pipette Total Tip Pick Up(s)": left_tip_pick_up, "Left Pipette Total Aspirates": left_aspirate, @@ -128,10 +206,132 @@ def instrument_commands(file_results: Dict[str, Any]) -> Dict[str, float]: "Right Pipette Total Aspirates": right_aspirate, "Right Pipette Total Dispenses": right_dispense, "Gripper Pick Ups": gripper_pickups, + f"Gripper Pick Ups of {labware_name}": gripper_labware_of_interest, + "Total Liquid Probes": liquid_probes, + "Average Liquid Probe Time (sec)": avg_liquid_probe_time_sec, } return pipette_dict +def liquid_height_commands( + file_results: Dict[str, Any], all_heights_list: List[List[Any]] +) -> List[List[Any]]: + """Record found liquid heights during a protocol.""" + commandData = file_results.get("commands", "") + robot = file_results.get("robot_name", "") + run_id = file_results.get("run_id", "") + for command in commandData: + commandType = command["commandType"] + if commandType == "comment": + result = command["params"].get("message", "") + try: + result_str = "'" + result.split("result: {")[1] + "'" + entries = result_str.split(", (") + comment_time = command["completedAt"] + for entry in entries: + height = float(entry.split(": ")[1].split("'")[0].split("}")[0]) + labware_type = str( + entry.split(",")[0].replace("'", "").replace("(", "") + ) + well_location = str(entry.split(", ")[1].split(" ")[0]) + slot_location = str(entry.split("slot ")[1].split(")")[0]) + labware_name = str(entry.split("of ")[1].split(" on")[0]) + all_heights_list[0].append(robot) + all_heights_list[1].append(run_id) + all_heights_list[2].append(comment_time) + all_heights_list[3].append(labware_type) + all_heights_list[4].append(labware_name) + all_heights_list[5].append(slot_location) + all_heights_list[6].append(well_location) + all_heights_list[7].append(height) + except (IndexError, ValueError): + continue + return all_heights_list + + +def plate_reader_commands( + file_results: Dict[str, Any], + hellma_plate_standards: List[Dict[str, Any]], + orientation: bool, +) -> Dict[str, object]: + """Plate Reader Command Counts.""" + commandData = file_results.get("commands", "") + move_lid_count: int = 0 + initialize_count: int = 0 + read = "no" + final_result = {} + read_num = 0 + # Count Number of Reads per measure mode + read_count, avg_read_time = count_command_in_run_data( + commandData, "absorbanceReader/read", True + ) + # Count Number of Initializations per measure mode + initialize_count, avg_initialize_time = count_command_in_run_data( + commandData, "absorbanceReader/initialize", True + ) + # Count Number of Lid Movements + for command in commandData: + commandType = command["commandType"] + if ( + commandType == "absorbanceReader/openLid" + or commandType == "absorbanceReader/closeLid" + ): + move_lid_count += 1 + elif commandType == "absorbanceReader/read": + read = "yes" + elif read == "yes" and commandType == "comment": + result = command["params"].get("message", "") + if "result:" in result: + plate_name = result.split("result:")[0] + formatted_result = result.split("result: ")[1] + print(formatted_result) + result_dict = eval(formatted_result) + result_dict_keys = list(result_dict.keys()) + if len(result_dict_keys) > 1: + read_type = "multi" + else: + read_type = "single" + if "hellma_plate" in plate_name: + for wavelength in result_dict_keys: + one_wavelength_dict = result_dict.get(wavelength) + result_ndarray = plate_reader.convert_read_dictionary_to_array( + one_wavelength_dict + ) + for item in hellma_plate_standards: + wavelength_of_interest = item["wavelength"] + if str(wavelength) == str(wavelength_of_interest): + error_cells = plate_reader.check_byonoy_data_accuracy( + result_ndarray, item, orientation + ) + if len(error_cells[0]) > 0: + percent = (96 - len(error_cells)) / 96 * 100 + for cell in error_cells: + print( + "FAIL: Cell " + + str(cell) + + " out of accuracy spec." + ) + else: + percent = 100 + print( + f"PASS: {wavelength_of_interest} meet accuracy spec." + ) + final_result[read_type, wavelength, read_num] = percent + read_num += 1 + else: + final_result = result_dict + read = "no" + plate_dict = { + "Plate Reader # of Reads": read_count, + "Plate Reader Avg Read Time (sec)": avg_read_time, + "Plate Reader # of Initializations": initialize_count, + "Plate Reader Avg Initialize Time (sec)": avg_initialize_time, + "Plate Reader # of Lid Movements": move_lid_count, + "Plate Reader Result": final_result, + } + return plate_dict + + def hs_commands(file_results: Dict[str, Any]) -> Dict[str, float]: """Gets total latch engagements, homes, rotations and total on time (sec) for heater shaker.""" # TODO: modify for cases that have more than 1 heater shaker. @@ -188,8 +388,9 @@ def hs_commands(file_results: Dict[str, Any]) -> Dict[str, float]: ) if temp_time is not None and deactivate_time is None: # If heater shaker module is not deactivated, protocol completedAt time stamp used. + default = commandData[len(commandData) - 1].get("completedAt") protocol_end = datetime.strptime( - file_results.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z" + file_results.get("completedAt", default), "%Y-%m-%dT%H:%M:%S.%f%z" ) temp_duration = (protocol_end - temp_time).total_seconds() hs_temps[hs_temp] = hs_temps.get(hs_temp, 0.0) + temp_duration @@ -236,8 +437,9 @@ def temperature_module_commands(file_results: Dict[str, Any]) -> Dict[str, Any]: tm_temps[tm_temp] = tm_temps.get(tm_temp, 0.0) + temp_duration if temp_time is not None and deactivate_time is None: # If temperature module is not deactivated, protocol completedAt time stamp used. + default = commandData[len(commandData) - 1].get("completedAt") protocol_end = datetime.strptime( - file_results.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z" + file_results.get("completedAt", default), "%Y-%m-%dT%H:%M:%S.%f%z" ) temp_duration = (protocol_end - temp_time).total_seconds() tm_temps[tm_temp] = tm_temps.get(tm_temp, 0.0) + temp_duration @@ -272,7 +474,10 @@ def thermocycler_commands(file_results: Dict[str, Any]) -> Dict[str, float]: or commandType == "thermocycler/closeLid" ): lid_engagements += 1 - if commandType == "thermocycler/setTargetBlockTemperature": + if ( + commandType == "thermocycler/setTargetBlockTemperature" + and command["status"] != "queued" + ): block_temp = command["params"]["celsius"] block_temp_changes += 1 block_on_time = datetime.strptime( @@ -317,15 +522,17 @@ def thermocycler_commands(file_results: Dict[str, Any]) -> Dict[str, float]: block_temps[block_temp] = block_temps.get(block_temp, 0.0) + block_time if block_on_time is not None and block_off_time is None: # If thermocycler block not deactivated protocol completedAt time stamp used + default = commandData[len(commandData) - 1].get("completedAt") protocol_end = datetime.strptime( - file_results.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z" + file_results.get("completedAt", default), "%Y-%m-%dT%H:%M:%S.%f%z" ) temp_duration = (protocol_end - block_on_time).total_seconds() - block_temps[block_temp] = block_temps.get(block_temp, 0.0) + temp_duration + if lid_on_time is not None and lid_off_time is None: # If thermocycler lid not deactivated protocol completedAt time stamp used + default = commandData[len(commandData) - 1].get("completedAt") protocol_end = datetime.strptime( - file_results.get("completedAt", ""), "%Y-%m-%dT%H:%M:%S.%f%z" + file_results.get("completedAt", default), "%Y-%m-%dT%H:%M:%S.%f%z" ) temp_duration = (protocol_end - lid_on_time).total_seconds() lid_temps[lid_temp] = block_temps.get(lid_temp, 0.0) + temp_duration @@ -362,50 +569,61 @@ def create_abr_data_sheet( return sheet_location -def get_error_info(file_results: Dict[str, Any]) -> Tuple[int, str, str, str, str]: +def get_error_info(file_results: Dict[str, Any]) -> Dict[str, Any]: """Determines if errors exist in run log and documents them.""" - error_levels = [] - error_level = "" # Read error levels file with open(ERROR_LEVELS_PATH, "r") as error_file: - error_levels = list(csv.reader(error_file)) - num_of_errors = len(file_results["errors"]) - if num_of_errors == 0: - error_type = "" - error_code = "" - error_instrument = "" - error_level = "" - return 0, error_type, error_code, error_instrument, error_level + error_levels = {row[1]: row[4] for row in csv.reader(error_file)} + # Initialize Variables + recoverable_errors: Dict[str, int] = dict() + total_recoverable_errors = 0 + end_run_errors = len(file_results["errors"]) commands_of_run: List[Dict[str, Any]] = file_results.get("commands", []) + error_recovery = file_results.get("hasEverEnteredErrorRecovery", False) + # Count recoverable errors + if error_recovery: + for command in commands_of_run: + error_info = command.get("error", {}) + if error_info.get("isDefined"): + total_recoverable_errors += 1 + error_type = error_info.get("errorType", "") + recoverable_errors[error_type] = ( + recoverable_errors.get(error_type, 0) + 1 + ) + # Get run-ending error info try: - run_command_error: Dict[str, Any] = commands_of_run[-1] - error_str: int = len(run_command_error.get("error", "")) - except IndexError: - error_str = 0 - if error_str > 1: - error_type = run_command_error["error"].get("errorType", "") + run_command_error = commands_of_run[-1]["error"] + error_type = run_command_error.get("errorType", "") if error_type == "PythonException": - # Reassign error_type to be more descriptive - error_type = run_command_error.get("detail", "").split(":")[0] - error_code = run_command_error["error"].get("errorCode", "") + error_type = commands_of_run[-1].get("detail", "").split(":")[0] + error_code = run_command_error.get("errorCode", "") + error_instrument = run_command_error.get("errorInfo", {}).get( + "node", run_command_error.get("errorInfo", {}).get("port", "") + ) + except (IndexError, KeyError): try: - # Instrument Error - error_instrument = run_command_error["error"]["errorInfo"]["node"] - except KeyError: - # Module - error_instrument = run_command_error["error"]["errorInfo"].get("port", "") + error_details = file_results.get("errors", [{}])[0] + except IndexError: + error_details = {} + error_type = error_details.get("errorType", "") + error_code = error_details.get("errorCode", "") + error_instrument = error_details.get("detail", "") + # Determine error level + if end_run_errors > 0: + error_level = error_levels.get(error_code, "4") else: - error_type = file_results["errors"][0]["errorType"] - error_code = file_results["errors"][0]["errorCode"] - error_instrument = file_results["errors"][0]["detail"] - for error in error_levels: - code_error = error[1] - if code_error == error_code: - error_level = error[4] - if len(error_level) < 1: - error_level = str(4) - - return num_of_errors, error_type, error_code, error_instrument, error_level + error_level = "" + # Create dictionary with all error descriptions + error_dict = { + "Total Recoverable Error(s)": total_recoverable_errors, + "Recoverable Error(s) Description": recoverable_errors, + "Run Ending Error": end_run_errors, + "Error_Code": error_code, + "Error_Type": error_type, + "Error_Instrument": error_instrument, + "Error_Level": error_level, + } + return error_dict def write_to_local_and_google_sheet( @@ -527,7 +745,7 @@ def get_calibration_offsets( print(f"Connected to {ip}") except Exception: print(f"ERROR: Failed to read IP address: {ip}") - sys.exit() + pass health_data = response.json() robot_name = health_data.get("name", "") api_version = health_data.get("api_version", "") @@ -570,10 +788,10 @@ def get_calibration_offsets( def get_logs(storage_directory: str, ip: str) -> List[str]: """Get Robot logs.""" log_types: List[Dict[str, Any]] = [ - {"log type": "api.log", "records": 1000}, + {"log type": "api.log", "records": 10000}, {"log type": "server.log", "records": 10000}, {"log type": "serial.log", "records": 10000}, - {"log type": "touchscreen.log", "records": 1000}, + {"log type": "touchscreen.log", "records": 10000}, ] all_paths = [] for log_type in log_types: diff --git a/abr-testing/abr_testing/data_collection/single_run_log_reader.py b/abr-testing/abr_testing/data_collection/single_run_log_reader.py index 5304842b550..a61670e6d12 100644 --- a/abr-testing/abr_testing/data_collection/single_run_log_reader.py +++ b/abr-testing/abr_testing/data_collection/single_run_log_reader.py @@ -5,6 +5,7 @@ import csv from abr_testing.data_collection import read_robot_logs from abr_testing.data_collection import abr_google_drive +from abr_testing.tools import plate_reader if __name__ == "__main__": parser = argparse.ArgumentParser(description="Read single run log locally saved.") @@ -25,14 +26,24 @@ sys.exit() # Get Runs from Storage and Read Logs run_ids_in_storage = read_robot_logs.get_run_ids_from_storage(run_log_file_path) + # Get hellma readins + file_values = plate_reader.read_hellma_plate_files(run_log_file_path, 101934) + ( runs_and_robots, header, runs_and_lpc, lpc_headers, + list_of_heights, ) = abr_google_drive.create_data_dictionary( - run_ids_in_storage, run_log_file_path, "", "", "" + run_ids_in_storage, + run_log_file_path, + "", + "", + "", + hellma_plate_standards=file_values, ) + print("list_of_heights not recorded.") transposed_list = list(zip(*runs_and_robots)) # Adds Run to local csv sheet_location = os.path.join(run_log_file_path, "saved_data.csv") diff --git a/abr-testing/abr_testing/protocol_simulation/__init__.py b/abr-testing/abr_testing/protocol_simulation/__init__.py new file mode 100644 index 00000000000..d3776c77fad --- /dev/null +++ b/abr-testing/abr_testing/protocol_simulation/__init__.py @@ -0,0 +1 @@ +"""The package holding code for simulating protocols.""" diff --git a/abr-testing/abr_testing/protocol_simulation/abr_sim_check.py b/abr-testing/abr_testing/protocol_simulation/abr_sim_check.py new file mode 100644 index 00000000000..76852f70b9c --- /dev/null +++ b/abr-testing/abr_testing/protocol_simulation/abr_sim_check.py @@ -0,0 +1,107 @@ +"""Check ABR Protocols Simulate Successfully.""" +from abr_testing.protocol_simulation import simulation_metrics +import os +from pathlib import Path +from typing import Dict, List, Tuple, Union +import traceback + + +def run( + file_dict: Dict[str, Dict[str, Union[str, Path]]], labware_defs: List[Path] +) -> None: + """Simulate protocol and raise errors.""" + for file in file_dict: + path = file_dict[file]["path"] + csv_params = "" + try: + csv_params = str(file_dict[file]["csv"]) + except KeyError: + pass + try: + print(f"Simulating {file}") + simulation_metrics.main( + protocol_file_path=Path(path), + save=False, + parameters=csv_params, + extra_files=labware_defs, + ) + except Exception as e: + traceback.print_exc() + print(str(e)) + print("\n") + + +def search(seq: str, dictionary: dict) -> str: + """Search for specific sequence in file.""" + for key in dictionary.keys(): + parts = key.split("_") + if parts[0] == seq: + return key + return "" + + +def get_files() -> Tuple[Dict[str, Dict[str, Union[str, Path]]], List[Path]]: + """Map protocols with corresponding csv files.""" + file_dict: Dict[str, Dict[str, Union[str, Path]]] = {} + labware_defs = [] + for root, directories, _ in os.walk(root_dir): + for directory in directories: + if directory not in exclude: + active_dir = os.path.join(root, directory) + for file in os.listdir( + active_dir + ): # Iterate over files in `active_protocols` + if file.endswith(".py") and file not in exclude: + file_dict[file] = {} + file_dict[file]["path"] = Path( + os.path.abspath( + os.path.join(root_dir, os.path.join(directory, file)) + ) + ) + if directory == "csv_parameters": + active_dir = os.path.join(root, directory) + for file in os.listdir( + active_dir + ): # Iterate over files in `active_protocols` + if file.endswith(".csv") and file not in exclude: + search_str = file.split("_")[0] + protocol = search(search_str, file_dict) + if protocol: + file_dict[protocol]["csv"] = str( + os.path.abspath( + os.path.join( + root_dir, os.path.join(directory, file) + ) + ) + ) + if directory == "custom_labware": + active_dir = os.path.join(root, directory) + for file in os.listdir( + active_dir + ): # Iterate over files in `active_protocols` + if file.endswith(".json") and file not in exclude: + labware_defs.append( + Path( + os.path.abspath( + os.path.join( + root_dir, os.path.join(directory, file) + ) + ) + ) + ) + return (file_dict, labware_defs) + + +if __name__ == "__main__": + # Directory to search + global root_dir + root_dir = "abr_testing/protocols" + global exclude + exclude = [ + "__init__.py", + "helpers.py", + ] + print("Simulating Protocols") + file_dict, labware_defs = get_files() + # print(file_dict) + run(file_dict, labware_defs) diff --git a/abr-testing/abr_testing/protocol_simulation/simulation_metrics.py b/abr-testing/abr_testing/protocol_simulation/simulation_metrics.py new file mode 100644 index 00000000000..10c7ea12782 --- /dev/null +++ b/abr-testing/abr_testing/protocol_simulation/simulation_metrics.py @@ -0,0 +1,565 @@ +"""Creates google sheet to display metrics of protocol.""" +import sys +import os +from pathlib import Path +from click import Context +from opentrons.cli import analyze +import json +import argparse +import traceback +from datetime import datetime +from abr_testing.automation import google_sheets_tool +from abr_testing.data_collection import read_robot_logs +from typing import Any, Tuple, List, Dict, Union, NoReturn +from abr_testing.tools import plate_reader + + +def build_parser() -> Any: + """Builds argument parser.""" + parser = argparse.ArgumentParser(description="Read run logs on google drive.") + parser.add_argument( + "storage_directory", + metavar="STORAGE_DIRECTORY", + type=str, + nargs=1, + help="Path to long term storage directory for run logs.", + ) + parser.add_argument( + "sheet_name", + metavar="SHEET_NAME", + type=str, + nargs=1, + help="Name of sheet to upload results to", + ) + parser.add_argument( + "protocol_file_path", + metavar="PROTOCOL_FILE_PATH", + type=str, + nargs="*", + help="Path to protocol file(s)", + ) + return parser + + +def set_api_level(protocol_file_path: str) -> None: + """Set API level for analysis.""" + with open(protocol_file_path, "r") as file: + file_contents = file.readlines() + # Look for current'apiLevel:' + for i, line in enumerate(file_contents): + if "apiLevel" in line: + print(f"The current API level of this protocol is: {line}") + change = ( + input("Would you like to simulate with a different API level? (Y/N) ") + .strip() + .upper() + ) + if change == "Y": + api_level = input("Protocol API Level to Simulate with: ") + # Update new API level + file_contents[i] = f"apiLevel: {api_level}\n" + break + with open(protocol_file_path, "w") as file: + file.writelines(file_contents) + print("File updated successfully.") + + +def look_for_air_gaps(protocol_file_path: str) -> int: + """Search Protocol for Air Gaps.""" + instances = 0 + try: + with open(protocol_file_path, "r") as open_file: + protocol_lines = open_file.readlines() + for line in protocol_lines: + if "air_gap" in line: + print(line) + instances += 1 + print(f"Found {instances} instance(s) of the air gap function") + open_file.close() + except Exception as error: + print("Error reading protocol:", error) + raise error.with_traceback(error.__traceback__) + return instances + + +# Mock sys.exit to avoid program termination +original_exit = sys.exit # Save the original sys.exit function + + +def mock_exit(code: Union[str, int, None] = None) -> NoReturn: + """Prevents program from exiting after analysis.""" + print(f"sys.exit() called with code: {code}") + raise SystemExit(code) # Raise the exception but catch it to prevent termination + + +def get_labware_name(id: str, object_dict: dict, json_data: dict) -> str: + """Recursively find the labware_name.""" + slot = "" + for obj in object_dict: + if obj["id"] == id: + try: + # Try to get the slotName from the location + slot = obj["location"]["slotName"] + return " SLOT: " + slot + except KeyError: + # Handle KeyError when location or slotName is missing + location = obj.get("location", {}) + + # Check if location contains 'moduleId' + if "moduleId" in location: + return get_labware_name( + location["moduleId"], json_data["modules"], json_data + ) + + # Check if location contains 'labwareId' + elif "labwareId" in location: + return get_labware_name( + location["labwareId"], json_data["labware"], json_data + ) + + return " Labware not found" + + +def determine_liquid_movement_volumes( + commands: List[Dict[str, Any]], json_data: Dict[str, Any] +) -> Dict[str, Any]: + """Determine where liquid is moved during protocol.""" + labware_well_dict: Dict[str, Any] = {} + for x, command in enumerate(commands): + if x != 0: + if command["commandType"] == "aspirate": + labware_id = command["params"]["labwareId"] + labware_name = "" + for labware in json_data.get("labware", {}): + if labware["id"] == labware_id: + labware_name = (labware["loadName"]) + get_labware_name( + labware["id"], json_data["labware"], json_data + ) + well_name = command["params"]["wellName"] + + if labware_id not in labware_well_dict: + labware_well_dict[labware_id] = {} + + if well_name not in labware_well_dict[labware_id]: + labware_well_dict[labware_id][well_name] = (labware_name, 0, 0, "") + + vol = int(command["params"]["volume"]) + + ( + labware_name, + added_volumes, + subtracted_volumes, + log, + ) = labware_well_dict[labware_id][well_name] + + subtracted_volumes += vol + log += f"aspirated {vol} " + labware_well_dict[labware_id][well_name] = ( + labware_name, + added_volumes, + subtracted_volumes, + log, + ) + + elif command["commandType"] == "dispense": + labware_id = command["params"]["labwareId"] + labware_name = "" + for labware in json_data.get("labware", {}): + if labware["id"] == labware_id: + labware_name = (labware["loadName"]) + get_labware_name( + labware["id"], json_data["labware"], json_data + ) + well_name = command["params"]["wellName"] + + if labware_id not in labware_well_dict: + labware_well_dict[labware_id] = {} + + if well_name not in labware_well_dict[labware_id]: + labware_well_dict[labware_id][well_name] = (labware_name, 0, 0, "") + + vol = int(command["params"]["volume"]) + + ( + labware_name, + added_volumes, + subtracted_volumes, + log, + ) = labware_well_dict[labware_id][well_name] + + added_volumes += vol + log += f"dispensed {vol} " + labware_well_dict[labware_id][well_name] = ( + labware_name, + added_volumes, + subtracted_volumes, + log, + ) + return labware_well_dict + + +def parse_results_volume( + json_data_file: str, + protocol_name: str, + file_date: datetime, + file_date_formatted: str, + hellma_plate_standards: List[Any], +) -> Tuple[ + List[str], + List[str], + List[str], + List[str], + List[str], + List[str], + List[str], + List[str], + List[str], + List[str], + List[str], +]: + """Parse run log and extract necessary information.""" + json_data = {} + with open(json_data_file, "r") as json_file: + json_data = json.load(json_file) + if isinstance(json_data, dict): + commands = json_data.get("commands", {}) + else: + print(f"Expected JSON object (dict) but got {type(json_data).__name__}.") + commands = {} + hellma_plate_orientation = False + parameters = json_data.get("runTimeParameters", "") + for parameter in parameters: + if parameter["displayName"] == "Hellma Plate Orientation": + hellma_plate_orientation = bool(parameter["value"]) + start_time = datetime.fromisoformat(commands[0]["createdAt"]) + end_time = datetime.fromisoformat(commands[len(commands) - 1]["completedAt"]) + header = ["", "Protocol Name", "Date", "Time"] + header_fill_row = ["", protocol_name, str(file_date.date()), str(file_date.time())] + labware_names_row = ["Labware Name"] + volume_dispensed_row = ["Total Volume Dispensed uL"] + volume_aspirated_row = ["Total Volume Aspirated uL"] + change_in_volume_row = ["Total Change in Volume uL"] + start_time_row = ["Start Time"] + end_time_row = ["End Time"] + total_time_row = ["Total Time of Execution"] + metrics_row = [ + "Metric", + "Heatershaker # of Latch Open/Close", + "Heatershaker # of Homes", + "Heatershaker # of Rotations", + "Heatershaker Temp On Time (sec)", + "Temp Module # of Temp Changes", + "Temp Module Temp On Time (sec)", + "Temp Mod Time to 4C (sec)", + "Thermocycler # of Lid Open/Close", + "Thermocycler Block # of Temp Changes", + "Thermocycler Block Temp On Time (sec)", + "Thermocycler Block Time to 4C (sec)", + "Thermocycler Lid # of Temp Changes", + "Thermocycler Lid Temp On Time (sec)", + "Thermocycler Lid Time to 105C (sec)", + "Plate Reader # of Reads", + "Plate Reader Avg Read Time (sec)", + "Plate Reader # of Initializations", + "Plate Reader Avg Initialize Time (sec)", + "Plate Reader # of Lid Movements", + "Plate Reader Result", + "Left Pipette Total Tip Pick Up(s)", + "Left Pipette Total Aspirates", + "Left Pipette Total Dispenses", + "Right Pipette Total Tip Pick Up(s)", + "Right Pipette Total Aspirates", + "Right Pipette Total Dispenses", + "Gripper Pick Ups", + "Gripper Pick Ups of opentrons_tough_pcr_auto_sealing_lid", + "Total Liquid Probes", + "Average Liquid Probe Time (sec)", + ] + values_row = ["Value"] + ( + hs_dict, + temp_module_dict, + thermo_cycler_dict, + plate_reader_dict, + instrument_dict, + ) = ({}, {}, {}, {}, {}) + try: + hs_dict = read_robot_logs.hs_commands(json_data) + temp_module_dict = read_robot_logs.temperature_module_commands(json_data) + thermo_cycler_dict = read_robot_logs.thermocycler_commands(json_data) + plate_reader_dict = read_robot_logs.plate_reader_commands( + json_data, hellma_plate_standards, hellma_plate_orientation + ) + instrument_dict = read_robot_logs.instrument_commands( + json_data, labware_name=None + ) + except KeyError: + pass + + metrics = [ + hs_dict, + temp_module_dict, + thermo_cycler_dict, + plate_reader_dict, + instrument_dict, + ] + # Determine liquid moved to and from labware + labware_well_dict = determine_liquid_movement_volumes(commands, json_data) + file_name_to_open = f"{protocol_name}_well_volumes_{file_date_formatted}.json" + with open( + f"{os.path.dirname(json_data_file)}\\{file_name_to_open}", + "w", + ) as output_file: + json.dump(labware_well_dict, output_file) + output_file.close() + + # populate row lists + for labware_id in labware_well_dict.keys(): + volume_added = 0 + volume_subtracted = 0 + labware_name = "" + for well in labware_well_dict[labware_id].keys(): + labware_name, added_volumes, subtracted_volumes, log = labware_well_dict[ + labware_id + ][well] + volume_added += added_volumes + volume_subtracted += subtracted_volumes + labware_names_row.append(labware_name) + volume_dispensed_row.append(str(volume_added)) + volume_aspirated_row.append(str(volume_subtracted)) + change_in_volume_row.append(str(volume_added - volume_subtracted)) + start_time_row.append(str(start_time.time())) + end_time_row.append(str(end_time.time())) + total_time_row.append(str(end_time - start_time)) + + for metric in metrics: + print(f"Dictionary: {metric}\n\n") + for cmd in metric.keys(): + values_row.append(str(metric[cmd])) + return ( + header, + header_fill_row, + labware_names_row, + volume_dispensed_row, + volume_aspirated_row, + change_in_volume_row, + start_time_row, + end_time_row, + total_time_row, + metrics_row, + values_row, + ) + + +def main( + protocol_file_path: Path, + save: bool, + storage_directory: str = os.curdir, + google_sheet_name: str = "", + parameters: str = "", + extra_files: List[Path] = [], +) -> None: + """Main module control.""" + sys.exit = mock_exit # Replace sys.exit with the mock function + # Simulation run date + file_date = datetime.now() + file_date_formatted = file_date.strftime("%Y-%m-%d_%H-%M-%S") + error_output = f"{storage_directory}\\test_debug" + protocol_name = protocol_file_path.stem + protocol_files = [protocol_file_path] + if extra_files != []: + protocol_files += extra_files + print("Simulating....") + try: + with Context(analyze) as ctx: + if save: + # Prepare output file + json_file_path = ( + f"{storage_directory}\\{protocol_name}_{file_date_formatted}.json" + ) + json_file_output = open(json_file_path, "wb+") + # log_output_file = f"{protocol_name}_log" + if parameters: + csv_params = {} + csv_params["parameters_csv"] = parameters + rtp_json = json.dumps(csv_params) + ctx.invoke( + analyze, + files=protocol_files, + rtp_files=rtp_json, + json_output=json_file_output, + human_json_output=None, + log_output=error_output, + log_level="ERROR", + check=False, + ) + + else: + ctx.invoke( + analyze, + files=protocol_files, + json_output=json_file_output, + human_json_output=None, + log_output=error_output, + log_level="ERROR", + check=False, + ) + json_file_output.close() + else: + if parameters: + csv_params = {} + csv_params["parameters_csv"] = parameters + rtp_json = json.dumps(csv_params) + ctx.invoke( + analyze, + files=protocol_files, + rtp_files=rtp_json, + json_output=None, + human_json_output=None, + log_output=error_output, + log_level="ERROR", + check=True, + ) + else: + ctx.invoke( + analyze, + files=protocol_files, + json_output=None, + human_json_output=None, + log_output=error_output, + log_level="ERROR", + check=True, + ) + print("done!") + except SystemExit as e: + print(f"SystemExit caught with code: {e}") + if e != 0: + traceback.print_exc + finally: + # Reset sys.exit to the original behavior + sys.exit = original_exit + with open(error_output, "r") as open_file: + try: + errors = open_file.readlines() + if not errors: + pass + else: + print(f"Error:\n{errors}") + raise + except FileNotFoundError: + print("error simulating ...") + raise + open_file.close + if save: + try: + credentials_path = os.path.join(storage_directory, "credentials.json") + print(credentials_path) + except FileNotFoundError: + print(f"Add credentials.json file to: {storage_directory}.") + sys.exit() + hellma_plate_standards = plate_reader.read_hellma_plate_files( + storage_directory, 101934 + ) + google_sheet = google_sheets_tool.google_sheet( + credentials_path, google_sheet_name, 0 + ) + google_sheet.write_to_row([]) + + for row in parse_results_volume( + json_file_path, + protocol_name, + file_date, + file_date_formatted, + hellma_plate_standards, + ): + print("Writing results to", google_sheet_name) + print(str(row)) + google_sheet.write_to_row(row) + + +def check_params(protocol_path: str) -> str: + """Check if protocol requires supporting files.""" + print("checking for parameters") + with open(protocol_path, "r") as f: + lines = f.readlines() + file_as_str = "".join(lines) + if ( + "parameters.add_csv_file" in file_as_str + or "helpers.create_csv_parameter" in file_as_str + ): + params = "" + while not params: + name = Path(protocol_file_path).stem + params = input( + f"Protocol {name} needs a CSV parameter file. Please enter the path: " + ) + if os.path.exists(params): + return params + else: + params = "" + print("Invalid file path") + return "" + + +def get_extra_files(protocol_file_path: str) -> tuple[str, List[Path]]: + """Get supporting files for protocol simulation if needed.""" + params = check_params(protocol_file_path) + needs_files = input("Does your protocol utilize custom labware? (Y/N): ") + labware_files = [] + if needs_files == "Y": + num_labware = input("How many custom labware?: ") + for labware_num in range(int(num_labware)): + path = input("Enter custom labware definition path: ") + labware_files.append(Path(path)) + return (params, labware_files) + + +if __name__ == "__main__": + CLEAN_PROTOCOL = True + args = build_parser().parse_args() + storage_directory = args.storage_directory[0] + sheet_name = args.sheet_name[0] + protocol_file_path: str = args.protocol_file_path[0] + parameters: List[str] = args.protocol_file_path[1:] + print(parameters) + SETUP = True + while SETUP: + print( + "Current version cannot handle air gap calls. Simulation results may be inaccurate." + ) + air_gaps = look_for_air_gaps(protocol_file_path) + if air_gaps > 0: + choice = "" + while not choice: + choice = input( + "Remove air_gap commands to ensure accurate results: (continue)? (Y/N): " + ) + if choice.upper() == "Y": + SETUP = False + CLEAN_PROTOCOL = True + elif choice.upper() == "N": + CLEAN_PROTOCOL = False + SETUP = False + print("Please remove air gaps then re-run") + else: + choice = "" + print("Please enter a valid response.") + SETUP = False + + # Change api level + if CLEAN_PROTOCOL: + set_api_level(protocol_file_path) + params, extra_files = get_extra_files(protocol_file_path) + try: + main( + protocol_file_path=Path(protocol_file_path), + save=True, + storage_directory=storage_directory, + google_sheet_name=sheet_name, + parameters=params, + extra_files=extra_files, + ) + except Exception as e: + traceback.print_exc() + sys.exit(str(e)) + else: + sys.exit(0) diff --git a/abr-testing/abr_testing/protocols/__init__.py b/abr-testing/abr_testing/protocols/__init__.py new file mode 100644 index 00000000000..2f0d01ea241 --- /dev/null +++ b/abr-testing/abr_testing/protocols/__init__.py @@ -0,0 +1 @@ +"""protocols.""" diff --git a/abr-testing/abr_testing/protocols/active_protocols/10_ZymoBIOMICS_Magbead_DNA_Cells_Flex.py b/abr-testing/abr_testing/protocols/active_protocols/10_ZymoBIOMICS_Magbead_DNA_Cells_Flex.py new file mode 100644 index 00000000000..9631b442694 --- /dev/null +++ b/abr-testing/abr_testing/protocols/active_protocols/10_ZymoBIOMICS_Magbead_DNA_Cells_Flex.py @@ -0,0 +1,536 @@ +"""Flex ZymoBIOMICS Magbead DNA Extraction: Cells.""" +import math +from opentrons import types +from typing import List, Dict +from opentrons import protocol_api +from opentrons.protocol_api import Well, InstrumentContext +import numpy as np +from opentrons.protocol_api.module_contexts import ( + HeaterShakerContext, + TemperatureModuleContext, + MagneticBlockContext, +) +from abr_testing.protocols import helpers + +metadata = { + "author": "Zach Galluzzo ", + "protocolName": "Flex ZymoBIOMICS Magbead DNA Extraction: Cells", +} + +requirements = {"robotType": "OT-3", "apiLevel": "2.21"} +""" +Slot A1: Tips 1000 +Slot A2: Tips 1000 +Slot A3: Temperature module (gen2) with 96 well PCR block and Armadillo 96 well PCR Plate +Slot B1: Tips 1000 +Slot B3: Nest 1 Well Reservoir +Slot C1: Magblock +Slot C2: Nest 12 well 15 ml Reservoir +Slot D1: H-S with Nest 96 Well Deepwell and DW Adapter +Slot D2: Nest 12 well 15 ml Reservoir +Slot D3: Trash + +Reservoir 1: +Well 1 - 12,320 ul +Wells 2-4 - 11,875 ul +Wells 5-6 - 13,500 ul +Wells 7-8 - 13,500 ul +Well 12 - 5,200 ul + +Reservoir 2: +Wells 1-12 - 9,000 ul + +""" +whichwash = 1 +sample_max = 48 +tip1k = 0 +tip200 = 0 +drop_count = 0 + + +def add_parameters(parameters: protocol_api.ParameterContext) -> None: + """Define parameters.""" + helpers.create_hs_speed_parameter(parameters) + helpers.create_single_pipette_mount_parameter(parameters) + helpers.create_dot_bottom_parameter(parameters) + + +def run(ctx: protocol_api.ProtocolContext) -> None: + """Protocol Set Up.""" + heater_shaker_speed = ctx.params.heater_shaker_speed # type: ignore[attr-defined] + mount = ctx.params.pipette_mount # type: ignore[attr-defined] + dot_bottom = ctx.params.dot_bottom # type: ignore[attr-defined] + dry_run = False + TIP_TRASH = ( + False # True = Used tips go in Trash, False = Used tips go back into rack + ) + res_type = "nest_12_reservoir_15ml" + + num_samples = 8 + wash1_vol = 500.0 + wash2_vol = wash3_vol = 900.0 + lysis_vol = 200.0 + sample_vol = 10.0 # Sample should be pelleted tissue/bacteria/cells + bind_vol = 600.0 + bind2_vol = 500.0 + elution_vol = 75.0 + + # Protocol Parameters + deepwell_type = "nest_96_wellplate_2ml_deep" + + if not dry_run: + settling_time = 2.0 + lysis_incubation = 30.0 + bind_time_1 = 10.0 + bind_time_2 = 1.0 + wash_time = 5.0 + drybeads = 9.0 + lysis_rep_1 = 3 + lysis_rep_2 = 5 + bead_reps_2 = 8 + else: + settling_time = 0.25 + lysis_incubation = 0.25 + bind_time_1 = bind_time_2 = wash_time = 0.25 + drybeads = 0.5 + lysis_rep_1 = lysis_rep_2 = bead_reps_2 = 1 + bead_vol = 25.0 + starting_vol = lysis_vol + sample_vol + binding_buffer_vol = bind_vol + bead_vol + ctx.load_trash_bin("A3") + h_s: HeaterShakerContext = ctx.load_module(helpers.hs_str, "D1") # type: ignore[assignment] + labware_name = "Samples" + sample_plate, h_s_adapter = helpers.load_hs_adapter_and_labware( + deepwell_type, h_s, labware_name + ) + h_s.close_labware_latch() + + temp: TemperatureModuleContext = ctx.load_module( + helpers.temp_str, "D3" + ) # type: ignore[assignment] + elutionplate, temp_adapter = helpers.load_temp_adapter_and_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", temp, "Elution Plate" + ) + magblock: MagneticBlockContext = ctx.load_module( + helpers.mag_str, "C1" + ) # type: ignore[assignment] + waste_reservoir = ctx.load_labware("nest_1_reservoir_195ml", "B3", "Liquid Waste") + waste = waste_reservoir.wells()[0].top() + res1 = ctx.load_labware(res_type, "D2", "reagent reservoir 1") + res2 = ctx.load_labware(res_type, "C2", "reagent reservoir 2") + num_cols = math.ceil(num_samples / 8) + + # Load tips and combine all similar boxes + tips1000 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", "Tips 1") + tips1001 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", "Tips 2") + tips1002 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B1", "Tips 3") + tips = [*tips1000.wells()[num_samples:96], *tips1001.wells(), *tips1002.wells()] + tips_sn = tips1000.wells()[:num_samples] + # load instruments + m1000 = ctx.load_instrument( + "flex_8channel_1000", mount, tip_racks=[tips1000, tips1001, tips1002] + ) + + """ + Here is where you can define the locations of your reagents. + """ + lysis_ = res1.wells()[0] + binding_buffer = res1.wells()[1:4] + bind2_res = res1.wells()[4:8] + wash1 = res1.wells()[6:8] + elution_solution = res1.wells()[-1] + wash2 = res2.wells()[:6] + wash3 = res2.wells()[6:] + + samples_m = sample_plate.rows()[0][:num_cols] + elution_samples_m = elutionplate.rows()[0][:num_cols] + # Redefine per well for liquid definitions + samps = sample_plate.wells()[: (8 * num_cols)] + + liquid_vols_and_wells: Dict[str, List[Dict[str, Well | List[Well] | float]]] = { + "Lysis and PK": [{"well": lysis_, "volume": 12320.0}], + "Beads and Binding": [{"well": binding_buffer, "volume": 11875.0}], + "Binding 2": [{"well": bind2_res, "volume": 13500.0}], + "Final Elution": [{"well": elution_solution, "volume": 52000}], + "Samples": [{"well": samps, "volume": 0.0}], + "Reagents": [{"well": res2.wells(), "volume": 9000.0}], + } + flattened_list_of_wells = helpers.find_liquid_height_of_loaded_liquids( + ctx, liquid_vols_and_wells, m1000 + ) + + m1000.flow_rate.aspirate = 300 + m1000.flow_rate.dispense = 300 + m1000.flow_rate.blow_out = 300 + + def tiptrack(tipbox: List[Well]) -> None: + """Track Tips.""" + global tip1k + global drop_count + if tipbox == tips: + m1000.pick_up_tip(tipbox[int(tip1k)]) + tip1k = tip1k + 8 + + drop_count = drop_count + 8 + if drop_count >= 150: + drop_count = 0 + ctx.pause("Please empty the waste bin of all the tips before continuing.") + + def remove_supernatant(vol: float) -> None: + """Remove supernatant.""" + ctx.comment("-----Removing Supernatant-----") + m1000.flow_rate.aspirate = 30 + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + + for i, m in enumerate(samples_m): + m1000.pick_up_tip(tips_sn[8 * i]) + loc = m.bottom(dot_bottom) + for _ in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, m.top()) + m1000.move_to(m.center()) + m1000.transfer(vol_per_trans, loc, waste, new_tip="never", air_gap=20) + m1000.blow_out(waste) + m1000.air_gap(20) + m1000.drop_tip(tips_sn[8 * i]) if TIP_TRASH else m1000.return_tip() + m1000.flow_rate.aspirate = 300 + + # Transfer from Magdeck plate to H-S + helpers.move_labware_to_hs(ctx, sample_plate, h_s, h_s_adapter) + + def bead_mixing( + well: Well, pip: InstrumentContext, mvol: float, reps: int = 8 + ) -> None: + """Mixing. + + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top().move(types.Point(x=0, y=0, z=5)) + aspbot = well.bottom().move(types.Point(x=0, y=2, z=1)) + asptop = well.bottom().move(types.Point(x=0, y=-2, z=2)) + disbot = well.bottom().move(types.Point(x=0, y=2, z=3)) + distop = well.top().move(types.Point(x=0, y=1, z=-5)) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, aspbot) + pip.dispense(vol, distop) + pip.aspirate(vol, asptop) + pip.dispense(vol, disbot) + if _ == reps - 1: + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 100 + pip.aspirate(vol, aspbot) + pip.dispense(vol, aspbot) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def mixing(well: Well, pip: InstrumentContext, mvol: float, reps: int = 8) -> None: + """Mixing. + + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top(5) + asp = well.bottom(1) + disp = well.top(-8) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + if _ == reps - 1: + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 100 + pip.aspirate(vol, asp) + pip.dispense(vol, asp) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def lysis(vol: float, source: Well) -> None: + """Lysis.""" + ctx.comment("-----Beginning Lysis Steps-----") + ctx.pause(msg="\n Hello \n - step 1 \n - step 2") + num_transfers = math.ceil(vol / 980) + tiptrack(tips) + for i in range(num_cols): + src = source + tvol = vol / num_transfers + # Mix Shield and PK before transferring first time + if i == 0: + for x in range(lysis_rep_1): + m1000.aspirate(vol, src.bottom(1)) + m1000.dispense(vol, src.bottom(8)) + # Transfer Shield and PK + for t in range(num_transfers): + m1000.require_liquid_presence(src) + m1000.aspirate(tvol, src.bottom(1)) + m1000.air_gap(10) + m1000.dispense(m1000.current_volume, samples_m[i].top()) + + # Mix shield and pk with samples + for i in range(num_cols): + if i != 0: + tiptrack(tips) + mixing(samples_m[i], m1000, tvol, reps=lysis_rep_2) + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + + helpers.set_hs_speed(ctx, h_s, heater_shaker_speed, lysis_incubation, True) + + def bind(vol1: float, vol2: float) -> None: + """Binding. + + `bind` will perform magnetic bead binding on each sample in the + deepwell plate. Each channel of binding beads will be mixed before + transfer, and the samples will be mixed with the binding beads after + the transfer. The magnetic deck activates after the addition to all + samples, and the supernatant is removed after bead bining. + :param vol (float): The amount of volume to aspirate from the elution + buffer source and dispense to each well containing + beads. + :param park (boolean): Whether to save sample-corresponding tips + between adding elution buffer and transferring + supernatant to the final clean elutions PCR + plate. + """ + ctx.comment("-----Beginning Binding Steps-----") + for i, well in enumerate(samples_m): + tiptrack(tips) + num_trans = math.ceil(vol1 / 980) + vol_per_trans = vol1 / num_trans + source = binding_buffer[i // 2] + if i == 0: + reps = 5 + else: + reps = 2 + bead_mixing(source, m1000, vol_per_trans, reps=reps if not dry_run else 1) + # Transfer beads and binding from source to H-S plate + for t in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, source.top()) + m1000.require_liquid_presence(source) + m1000.transfer( + vol_per_trans, source, well.top(), air_gap=20, new_tip="never" + ) + m1000.air_gap(20) + bead_mixing(well, m1000, vol_per_trans, reps=bead_reps_2) + m1000.blow_out() + m1000.air_gap(10) + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + + helpers.set_hs_speed(ctx, h_s, heater_shaker_speed * 0.9, bind_time_1, True) + + # Transfer from H-S plate to Magdeck plate + helpers.move_labware_from_hs_to_destination(ctx, sample_plate, h_s, magblock) + + for bindi in np.arange( + settling_time + 1, 0, -0.5 + ): # Settling time delay with countdown timer + ctx.delay( + minutes=0.5, + msg="There are " + str(bindi) + " minutes left in the incubation.", + ) + + # remove initial supernatant + remove_supernatant(vol1 + starting_vol) + + ctx.comment("-----Beginning Bind #2 Steps-----") + tiptrack(tips) + for i, well in enumerate(samples_m): + num_trans = math.ceil(vol2 / 980) + vol_per_trans = vol2 / num_trans + source = bind2_res[i // 3] + if i == 0 or i == 3: + height = 10 + else: + height = 1 + # Transfer beads and binding from source to H-S plate + for t in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, source.top()) + m1000.transfer( + vol_per_trans, + source.bottom(height), + well.top(), + air_gap=20, + new_tip="never", + ) + m1000.air_gap(20) + + for i in range(num_cols): + if i != 0: + tiptrack(tips) + bead_mixing( + samples_m[i], m1000, vol_per_trans, reps=3 if not dry_run else 1 + ) + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + helpers.set_hs_speed(ctx, h_s, heater_shaker_speed, bind_time_2, True) + + # Transfer from H-S plate to Magdeck plate + helpers.move_labware_from_hs_to_destination(ctx, sample_plate, h_s, magblock) + + for bindi in np.arange( + settling_time + 1, 0, -0.5 + ): # Settling time delay with countdown timer + ctx.delay( + minutes=0.5, + msg="There are " + str(bindi) + " minutes left in the incubation.", + ) + + # remove initial supernatant + remove_supernatant(vol2 + 25) + + def wash(vol: float, source: List[Well]) -> None: + """Wash Steps.""" + global whichwash # Defines which wash the protocol is on to log on the app + + if source == wash1: + whichwash = 1 + const = 6 // len(source) + if source == wash2: + whichwash = 2 + const = 6 // len(source) + height = 1 + if source == wash3: + whichwash = 3 + const = 6 // len(source) + height = 1 + + ctx.comment("-----Wash #" + str(whichwash) + " is starting now------") + + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + + tiptrack(tips) + for i, m in enumerate(samples_m): + if source == wash1: + if i == 0 or i == 3: + height = 10 + else: + height = 1 + src = source[i // const] + for n in range(num_trans): + if m1000.current_volume > 0: + m1000.dispense(m1000.current_volume, src.top()) + m1000.require_liquid_presence(src) + m1000.transfer( + vol_per_trans, + src.bottom(height), + m.top(), + air_gap=20, + new_tip="never", + ) + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + helpers.set_hs_speed(ctx, h_s, heater_shaker_speed * 0.9, wash_time, True) + + helpers.move_labware_from_hs_to_destination(ctx, sample_plate, h_s, magblock) + + for washi in np.arange( + settling_time, 0, -0.5 + ): # settling time timer for washes + ctx.delay( + minutes=0.5, + msg="There are " + + str(washi) + + " minutes left in wash " + + str(whichwash) + + " incubation.", + ) + + remove_supernatant(vol) + + def elute(vol: float) -> None: + tiptrack(tips) + for i, m in enumerate(samples_m): + m1000.require_liquid_presence(elution_solution) + m1000.aspirate(vol, elution_solution) + m1000.air_gap(20) + m1000.dispense(m1000.current_volume, m.top(-3)) + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + + helpers.set_hs_speed(ctx, h_s, heater_shaker_speed * 0.9, wash_time, True) + + # Transfer back to magnet + helpers.move_labware_from_hs_to_destination(ctx, sample_plate, h_s, magblock) + + for elutei in np.arange(settling_time, 0, -0.5): + ctx.delay( + minutes=0.5, + msg="Incubating on MagDeck for " + str(elutei) + " more minutes.", + ) + + for i, (m, e) in enumerate(zip(samples_m, elution_samples_m)): + tiptrack(tips) + m1000.flow_rate.dispense = 100 + m1000.flow_rate.aspirate = 25 + m1000.transfer( + vol, m.bottom(dot_bottom), e.bottom(5), air_gap=20, new_tip="never" + ) + m1000.blow_out(e.top(-2)) + m1000.air_gap(20) + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + + m1000.flow_rate.aspirate = 150 + + """ + Here is where you can call the methods defined above to fit your specific + protocol. The normal sequence is: + """ + lysis(lysis_vol, lysis_) + bind(binding_buffer_vol, bind2_vol) + wash(wash1_vol, wash1) + wash(wash2_vol, wash2) + wash(wash3_vol, wash3) + h_s.set_and_wait_for_temperature(55) + for beaddry in np.arange(drybeads, 0, -0.5): + ctx.delay( + minutes=0.5, + msg="There are " + str(beaddry) + " minutes left in the drying step.", + ) + elute(elution_vol) + h_s.deactivate_heater() + flattened_list_of_wells.extend([waste_reservoir["A1"], elutionplate["A1"]]) + helpers.find_liquid_height_of_all_wells(ctx, m1000, flattened_list_of_wells) diff --git a/abr-testing/abr_testing/protocols/active_protocols/11_Dynabeads_IP_Flex_96well_RIT.py b/abr-testing/abr_testing/protocols/active_protocols/11_Dynabeads_IP_Flex_96well_RIT.py new file mode 100644 index 00000000000..e885ec45a5e --- /dev/null +++ b/abr-testing/abr_testing/protocols/active_protocols/11_Dynabeads_IP_Flex_96well_RIT.py @@ -0,0 +1,273 @@ +"""Immunoprecipitation by Dynabeads.""" +from opentrons.protocol_api import ProtocolContext, ParameterContext, Well +from opentrons.protocol_api.module_contexts import ( + HeaterShakerContext, + TemperatureModuleContext, + MagneticBlockContext, +) +from abr_testing.protocols import helpers +from typing import List, Dict, Union + +metadata = { + "protocolName": "Immunoprecipitation by Dynabeads - (Reagents in 15 mL tubes)", + "author": "Boren Lin, Opentrons", + "description": "Isolates protein from liquid samples using protein A /G coupled magnetic beads", +} + +requirements = { + "robotType": "OT-3", + "apiLevel": "2.21", +} + + +def add_parameters(parameters: ParameterContext) -> None: + """Define parameters.""" + helpers.create_hs_speed_parameter(parameters) + helpers.create_two_pipette_mount_parameters(parameters) + helpers.create_dot_bottom_parameter(parameters) + + +NUM_COL = 12 + +MAG_DELAY_MIN = 1 + +BEADS_VOL = 50 +AB_VOL = 50 +SAMPLE_VOL = 200 +WASH_TIMES = 3 +WASH_VOL = 200 +ELUTION_VOL = 50 + +WASTE_VOL_MAX = 275000 + +READY_FOR_SDSPAGE = 0 + +waste_vol_chk = 0.0 +waste_vol = 0.0 + +TIP_TRASH = False + + +def run(ctx: ProtocolContext) -> None: + """Protocol.""" + # defining variables inside def run + heater_shaker_speed = ctx.params.heater_shaker_speed # type: ignore[attr-defined] + ASP_HEIGHT = ctx.params.dot_bottom # type: ignore[attr-defined] + single_channel_mount = ctx.params.pipette_mount_1 # type: ignore[attr-defined] + eight_channel_mount = ctx.params.pipette_mount_2 # type: ignore[attr-defined] + MIX_SPEED = heater_shaker_speed + MIX_SEC = 10 + + # if on deck: + INCUBATION_SPEEND = heater_shaker_speed * 0.5 + INCUBATION_MIN = 60 + # load labware + + sample_plate = ctx.load_labware("nest_96_wellplate_2ml_deep", "B2", "samples") + wash_res = ctx.load_labware("nest_12_reservoir_15ml", "B1", "wash") + reagent_res = ctx.load_labware( + "opentrons_15_tuberack_nest_15ml_conical", "C3", "reagents" + ) + waste_res = ctx.load_labware("nest_1_reservoir_290ml", "D2", "waste") + + tips = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B3") + tips_sample = ctx.load_labware( + "opentrons_flex_96_tiprack_1000ul", "A2", "sample tips" + ) + tips_sample_loc = tips_sample.wells()[:95] + if READY_FOR_SDSPAGE == 0: + tips_elu = ctx.load_labware( + "opentrons_flex_96_tiprack_1000ul", "A1", "elution tips" + ) + tips_elu_loc = tips_elu.wells()[:95] + tips_reused = ctx.load_labware( + "opentrons_flex_96_tiprack_1000ul", "C2", "reused tips" + ) + tips_reused_loc = tips_reused.wells()[:95] + p1000 = ctx.load_instrument( + "flex_8channel_1000", eight_channel_mount, tip_racks=[tips] + ) + p1000_single = ctx.load_instrument( + "flex_1channel_1000", single_channel_mount, tip_racks=[tips] + ) + h_s: HeaterShakerContext = ctx.load_module(helpers.hs_str, "D1") # type: ignore[assignment] + working_plate, h_s_adapter = helpers.load_hs_adapter_and_labware( + "nest_96_wellplate_2ml_deep", h_s, "Working Plate" + ) + + if READY_FOR_SDSPAGE == 0: + temp: TemperatureModuleContext = ctx.load_module( + helpers.temp_str, "D3" + ) # type: ignore[assignment] + final_plate, temp_adapter = helpers.load_temp_adapter_and_labware( + "nest_96_wellplate_2ml_deep", temp, "Final Plate" + ) + mag: MagneticBlockContext = ctx.load_module(helpers.mag_str, "C1") # type: ignore[assignment] + + # liquids + samples = sample_plate.rows()[0][:NUM_COL] # 1 + beads = reagent_res.wells()[0] # 2 + ab = reagent_res.wells()[1] # 3 + elu = reagent_res.wells()[2] # 4 + wash = wash_res.rows()[0][:NUM_COL] # 5 + waste = waste_res.wells()[0] + working_cols = working_plate.rows()[0][:NUM_COL] # 6 + working_wells = working_plate.wells()[: NUM_COL * 8] # 6 + if READY_FOR_SDSPAGE == 0: + final_cols = final_plate.rows()[0][:NUM_COL] + # Define Liquids + liquid_vols_and_wells: Dict[ + str, List[Dict[str, Union[Well, List[Well], float]]] + ] = { + "Beads": [{"well": beads, "volume": 4900.0}], + "AB": [{"well": ab, "volume": 4900.0}], + "Elution": [{"well": elu, "volume": 4900.0}], + "Wash": [{"well": wash, "volume": 750.0}], + "Samples": [{"well": samples, "volume": 250.0}], + } + helpers.find_liquid_height_of_loaded_liquids( + ctx, liquid_vols_and_wells, p1000_single + ) + + def transfer_plate_to_plate( + vol1: float, start: List[Well], end: List[Well], liquid: int + ) -> None: + """Transfer from plate to plate.""" + for i in range(NUM_COL): + if liquid == 1: + p1000.pick_up_tip(tips_sample_loc[i * 8]) + else: + p1000.pick_up_tip(tips_elu_loc[i * 8]) + start_loc = start[i] + end_loc = end[i] + p1000.aspirate(vol1, start_loc.bottom(z=ASP_HEIGHT), rate=2) + p1000.air_gap(10) + p1000.dispense(vol1 + 10, end_loc.bottom(z=15), rate=2) + p1000.blow_out() + p1000.touch_tip() + p1000.return_tip() if not TIP_TRASH else p1000.drop_tip() + + def transfer_well_to_plate( + vol2: float, + start: Union[List[Well], Well], + end: List[Well], + liquid: int, + drop_height: int = -20, + ) -> None: + """Transfer from well to plate.""" + if liquid == 5 and type(start) == List: + p1000.pick_up_tip() + for j in range(NUM_COL): + start_loc = start[j] + p1000.require_liquid_presence(start_loc) + end_loc = end[j] + p1000.aspirate(vol2, start_loc.bottom(z=ASP_HEIGHT), rate=2) + p1000.air_gap(10) + p1000.dispense(vol2 + 10, end_loc.top(z=drop_height), rate=2) + p1000.blow_out() + p1000.return_tip() if not TIP_TRASH else p1000.drop_tip() + + elif type(start) == Well: + p1000_single.pick_up_tip() + vol = vol2 * 8 + p1000_single.mix(5, vol * 0.75, start.bottom(z=ASP_HEIGHT * 5), rate=2) + p1000_single.mix(5, vol * 0.75, start.bottom(z=ASP_HEIGHT * 20), rate=2) + for j in range(NUM_COL): + end_loc_gap = end[j * 8] + if liquid == 2: + p1000_single.mix( + 2, vol * 0.75, start.bottom(z=ASP_HEIGHT * 5), rate=2 + ) + p1000_single.require_liquid_presence(start) + p1000_single.aspirate(vol, start.bottom(z=ASP_HEIGHT * 5), rate=2) + p1000_single.air_gap(10) + p1000_single.dispense(10, end_loc_gap.top(z=-5)) + for jj in range(8): + end_loc = end[j * 8 + jj] + p1000_single.dispense(vol2, end_loc.bottom(z=10), rate=0.75) + p1000_single.touch_tip() + p1000_single.blow_out() + p1000_single.return_tip() if not TIP_TRASH else p1000.drop_tip() + + def discard(vol3: float, start: List[Well]) -> None: + """Discard function.""" + global waste_vol + global waste_vol_chk + if waste_vol_chk >= WASTE_VOL_MAX: + ctx.pause("Empty Liquid Waste") + waste_vol_chk = 0 + waste_vol = 0.0 + for k in range(NUM_COL): + p1000.pick_up_tip(tips_reused_loc[k * 8]) + start_loc = start[k] + end_loc = waste + p1000.aspirate(vol3, start_loc.bottom(z=ASP_HEIGHT), rate=0.3) + p1000.air_gap(10) + p1000.dispense(vol3 + 10, end_loc.top(z=-5), rate=2) + p1000.blow_out() + p1000.return_tip() + waste_vol = vol3 * NUM_COL * 8.0 + waste_vol_chk = waste_vol_chk + waste_vol + + # protocol + + # Add beads, samples and antibody solution + h_s.close_labware_latch() + transfer_well_to_plate(BEADS_VOL, beads, working_wells, 2) + + helpers.move_labware_from_hs_to_destination(ctx, working_plate, h_s, mag) + + ctx.delay(minutes=MAG_DELAY_MIN) + discard(BEADS_VOL * 1.1, working_cols) + + helpers.move_labware_to_hs(ctx, working_plate, h_s, h_s_adapter) + + transfer_plate_to_plate(SAMPLE_VOL, samples, working_cols, 1) + transfer_well_to_plate(AB_VOL, ab, working_wells, 3) + + h_s.set_and_wait_for_shake_speed(rpm=MIX_SPEED) + ctx.delay(seconds=MIX_SEC) + + h_s.set_and_wait_for_shake_speed(rpm=INCUBATION_SPEEND) + ctx.delay(seconds=INCUBATION_MIN * 60) + h_s.deactivate_shaker() + + helpers.move_labware_from_hs_to_destination(ctx, working_plate, h_s, mag) + + ctx.delay(minutes=MAG_DELAY_MIN) + vol_total = SAMPLE_VOL + AB_VOL + discard(vol_total * 1.1, working_cols) + + # Wash + for _ in range(WASH_TIMES): + helpers.move_labware_to_hs(ctx, working_plate, h_s, h_s_adapter) + + transfer_well_to_plate(WASH_VOL, wash, working_cols, 5) + helpers.set_hs_speed(ctx, h_s, MIX_SPEED, MIX_SEC / 60, True) + helpers.move_labware_from_hs_to_destination(ctx, working_plate, h_s, mag) + ctx.delay(minutes=MAG_DELAY_MIN) + discard(WASH_VOL * 1.1, working_cols) + + # Elution + helpers.move_labware_to_hs(ctx, working_plate, h_s, h_s_adapter) + + transfer_well_to_plate(ELUTION_VOL, elu, working_wells, 4) + if READY_FOR_SDSPAGE == 1: + ctx.pause("Seal the Working Plate") + h_s.set_and_wait_for_temperature(70) + helpers.set_hs_speed(ctx, h_s, MIX_SPEED, (MIX_SEC / 60) + 10, True) + h_s.deactivate_heater() + h_s.open_labware_latch() + ctx.pause("Protocol Complete") + + elif READY_FOR_SDSPAGE == 0: + helpers.set_hs_speed(ctx, h_s, MIX_SPEED, (MIX_SEC / 60) + 2, True) + + temp.set_temperature(4) + helpers.move_labware_from_hs_to_destination(ctx, working_plate, h_s, mag) + ctx.delay(minutes=MAG_DELAY_MIN) + transfer_plate_to_plate(ELUTION_VOL * 1.1, working_cols, final_cols, 6) + temp.deactivate() + end_wells_to_probe = [reagent_res["A1"], reagent_res["B1"], reagent_res["C1"]] + end_wells_to_probe.extend(wash_res.wells()) + helpers.find_liquid_height_of_all_wells(ctx, p1000_single, end_wells_to_probe) diff --git a/abr-testing/abr_testing/protocols/active_protocols/12_KAPA HyperPlus Library Prep.py b/abr-testing/abr_testing/protocols/active_protocols/12_KAPA HyperPlus Library Prep.py new file mode 100644 index 00000000000..79414e13765 --- /dev/null +++ b/abr-testing/abr_testing/protocols/active_protocols/12_KAPA HyperPlus Library Prep.py @@ -0,0 +1,1219 @@ +"""KAPA HyperPlus Library Preparation.""" +from opentrons.protocol_api import ( + ProtocolContext, + ParameterContext, + Labware, + Well, + InstrumentContext, +) +from opentrons import types +import math +from abr_testing.protocols import helpers +from opentrons.protocol_api.module_contexts import ( + TemperatureModuleContext, + MagneticBlockContext, + ThermocyclerContext, +) +from typing import List, Tuple, Optional + +metadata = { + "protocolName": "KAPA HyperPlus Library Preparation", + "author": "Your Name ", +} + +requirements = {"robotType": "Flex", "apiLevel": "2.21"} + +tt_50 = 0 +tt_200 = 0 +p50_rack_count = 0 +p200_rack_count = 0 +tip50 = 50 +tip200 = 200 +p50_racks_ondeck = [] +p200_racks_ondeck = [] +p50_racks_offdeck = [] +p200_racks_offdeck = [] + + +def add_parameters(parameters: ParameterContext) -> None: + """Parameters.""" + parameters.add_bool( + variable_name="dry_run", + display_name="Dry Run", + description="Skip incubation delays and shorten mix steps.", + default=False, + ) + parameters.add_bool( + variable_name="trash_tips", + display_name="Trash tip", + description="tip trashes after every use", + default=False, + ) + helpers.create_disposable_lid_parameter(parameters) + helpers.create_tc_lid_deck_riser_parameter(parameters) + helpers.create_two_pipette_mount_parameters(parameters) + parameters.add_int( + variable_name="num_samples", + display_name="number of samples", + description="How many samples to be perform for library prep", + default=8, + minimum=8, + maximum=48, + ) + parameters.add_int( + variable_name="PCR_CYCLES", + display_name="number of PCR Cycles", + description="How many pcr cycles to be perform for library prep", + default=2, + minimum=2, + maximum=16, + ) + + parameters.add_int( + variable_name="Fragmentation_time", + display_name="time on thermocycler", + description="Fragmentation time in thermocycler", + default=10, + minimum=10, + maximum=30, + ) + + +def run(ctx: ProtocolContext) -> None: + """Protocol.""" + USE_GRIPPER = True + trash_tips = ctx.params.trash_tips # type: ignore[attr-defined] + dry_run = ctx.params.dry_run # type: ignore[attr-defined] + pipette_1000_mount = ctx.params.pipette_mount_1 # type: ignore[attr-defined] + pipette_50_mount = ctx.params.pipette_mount_2 # type: ignore[attr-defined] + deck_riser = ctx.params.deck_riser # type: ignore[attr-defined] + REUSE_ETOH_TIPS = True + REUSE_RSB_TIPS = ( + True # Reuse tips for RSB buffer (adding RSB, mixing, and transferring) + ) + REUSE_REMOVE_TIPS = True # Reuse tips for supernatant removal + num_samples = ctx.params.num_samples # type: ignore[attr-defined] + PCRCYCLES = ctx.params.PCR_CYCLES # type: ignore[attr-defined] + disposable_lid = ctx.params.disposable_lid # type: ignore[attr-defined] + Fragmentation_time = 10 + ligation_tc_time = 15 + used_lids: List[Labware] = [] + if dry_run: + trash_tips = False + + num_cols = math.ceil(num_samples / 8) + + # Pre-set parameters + sample_vol = 35.0 + frag_vol = 15.0 + end_repair_vol = 10.0 + adapter_vol = 5.0 + ligation_vol = 45.0 + amplification_vol = 30.0 + bead_vol_1 = 88.0 + bead_vol_2 = 50.0 + bead_vol = bead_vol_1 + bead_vol_2 + bead_inc = 2.0 + rsb_vol_1 = 25.0 + rsb_vol_2 = 20.0 + rsb_vol = rsb_vol_1 + rsb_vol_2 + elution_vol = 20.0 + elution_vol_2 = 17.0 + etoh_vol = 400.0 + + # Importing Labware, Modules and Instruments + magblock: MagneticBlockContext = ctx.load_module( + helpers.mag_str, "D2" + ) # type: ignore[assignment] + temp_mod: TemperatureModuleContext = ctx.load_module( + helpers.temp_str, "B3" + ) # type: ignore[assignment] + temp_plate, temp_adapter = helpers.load_temp_adapter_and_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", + temp_mod, + "Temp Module Reservoir Plate", + ) + + if not dry_run: + temp_mod.set_temperature(4) + tc_mod: ThermocyclerContext = ctx.load_module(helpers.tc_str) # type: ignore[assignment] + # Just in case + tc_mod.open_lid() + + FLP_plate = magblock.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", "FLP Plate" + ) + samples_flp = FLP_plate.rows()[0][:num_cols] + + sample_plate = ctx.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", "D1", "Sample Pate" + ) + + sample_plate_2 = ctx.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", "B2", "Sample Pate" + ) + samples_2 = sample_plate_2.rows()[0][:num_cols] + samples = sample_plate.rows()[0][:num_cols] + reservoir = ctx.load_labware("nest_96_wellplate_2ml_deep", "C2") + + trash = ctx.load_waste_chute() + unused_lids: List[Labware] = [] + # Load TC Lids + if disposable_lid: + unused_lids = helpers.load_disposable_lids(ctx, 5, ["C3"], deck_riser) + # Import Global Variables + + global tip50 + global tip200 + global p50_rack_count + global p200_rack_count + global tt_50 + global tt_200 + + p200 = ctx.load_instrument("flex_8channel_1000", pipette_1000_mount) + p50 = ctx.load_instrument("flex_8channel_50", pipette_50_mount) + + Available_on_deck_slots = ["A2", "A3", "B3"] + Available_off_deck_slots = ["A4", "B4"] + p50_racks_to_dump = [] + p200_racks_to_dump = [] + + if REUSE_RSB_TIPS: + Available_on_deck_slots.remove("A3") + tip50_reuse = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "A3") + RSB_tip = [] + p50_rack_count += 1 + tt_50 += 12 + p50.tip_racks.append(tip50_reuse) + ctx.comment(f"Adding 50 ul tip rack #{p50_rack_count}") + for x in range(num_cols): + RSB_tip.append(tip50_reuse.wells()[8 * x]) + tt_50 -= 1 + p50.starting_tip = tip50_reuse.wells()[(len(RSB_tip)) * 8] + + if REUSE_REMOVE_TIPS: + Available_on_deck_slots.remove("A2") + tip200_reuse = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "A2") + RemoveSup_tip = [] + p200_rack_count += 1 + tt_200 += 12 + p200.tip_racks.append(tip200_reuse) + ctx.comment(f"Adding 200 ul tip rack #{p200_rack_count}") + for x in range(num_cols): + RemoveSup_tip.append(tip200_reuse.wells()[8 * x]) + tt_200 -= 1 + p200.starting_tip = tip200_reuse.wells()[(len(RemoveSup_tip)) * 8] + + # Load Reagent Locations in Reservoirs + lib_amplification_wells: List[Well] = temp_plate.columns()[num_cols + 3] + amplification_res = lib_amplification_wells[0] + adapters = temp_plate.rows()[0][:num_cols] # used for filling liquids + end_repair_cols: List[Well] = temp_plate.columns()[ + num_cols + ] # used for filling liquids + er_res = end_repair_cols[0] + frag: List[Well] = temp_plate.columns()[num_cols + 1] + frag_res = frag[0] + ligation: List[Well] = temp_plate.columns()[num_cols + 2] + ligation_res = ligation[0] + # Room Temp Res (deepwell) + bead = reservoir.columns()[0] + bead_res = bead[0] + rsb = reservoir.columns()[3] + rsb_res = rsb[0] + etoh1 = reservoir.columns()[4] + etoh1_res = etoh1[0] + etoh2 = reservoir.columns()[5] + etoh2_res = etoh2[0] + + liquid_vols_and_wells = { + "Samples": [ + {"well": sample_plate.wells()[: 8 * num_cols], "volume": sample_vol} + ], + "Final Library": [ + {"well": sample_plate_2.wells()[: 8 * num_cols], "volume": elution_vol_2} + ], + "Adapters": [{"well": adapters, "volume": adapter_vol * 2.0}], + "End Repair Mix": [ + { + "well": end_repair_cols, + "volume": (end_repair_vol * num_cols) + (0.1 * end_repair_vol), + } + ], + "Fragmentation Mix": [ + {"well": frag, "volume": (frag_vol * num_cols) + (0.1 * frag_vol)} + ], + "Ligation Mix": [ + { + "well": ligation, + "volume": (ligation_vol * num_cols) + (0.1 * ligation_vol), + } + ], + "Amplification Mix": [ + { + "well": lib_amplification_wells, + "volume": (amplification_vol * num_cols) + (0.1 * amplification_vol), + } + ], + "Ampure Beads": [ + { + "well": bead, + "volume": (bead_vol * num_cols) + (0.1 * bead_vol * num_cols), + } + ], + "Resuspension Buffer": [ + {"well": rsb, "volume": (rsb_vol * num_cols) + (0.1 * rsb_vol * num_cols)} + ], + "Ethanol 80%": [ + { + "well": etoh1, + "volume": (etoh_vol * num_cols) + (0.1 * etoh_vol * num_cols), + }, + { + "well": etoh2, + "volume": (etoh_vol * num_cols) + (0.1 * etoh_vol * num_cols), + }, + ], + } + waste1 = reservoir.columns()[6] + waste1_res = waste1[0] + + waste2 = reservoir.columns()[7] + waste2_res = waste2[0] + + def tiptrack(rack: int, reuse_col: Optional[int], reuse: bool = False) -> None: + """Tip Track.""" + global tt_50 + global tt_200 + global p50_racks_ondeck + global p200_racks_ondeck + global p50_racks_offdeck + global p200_racks_offdeck + global p50_rack_count + global p200_rack_count + + if rack == tip50: + if ( + tt_50 == 0 and not reuse + ): # If this is the first column of tip box and these aren't reused tips + ctx.comment("Troubleshoot") + if len(Available_on_deck_slots) > 0: + avail_slot = Available_on_deck_slots[0] + p50_rack_count += 1 + tt_50 += 12 + addtiprack = ctx.load_labware( + "opentrons_flex_96_tiprack_50ul", + avail_slot, + f"50 ul Tip Rack #{p50_rack_count}", + ) + ctx.comment( + f"Add 50 ul tip rack #{p50_rack_count} to slot {avail_slot}." + ) + Available_on_deck_slots.pop(0) + p50_racks_ondeck.append(addtiprack) + p50_racks_to_dump.append(addtiprack) + p50.tip_racks.append(addtiprack) + elif ( + len(Available_on_deck_slots) == 0 + and len(Available_off_deck_slots) > 0 + ): + p50_rack_count += 1 + tt_50 += 12 + addtiprack = ctx.load_labware( + "opentrons_flex_96_tiprack_50ul", + Available_off_deck_slots[0], + f"50 ul Tip Rack #{p50_rack_count}", + ) + Available_off_deck_slots.pop( + 0 + ) # Load rack into staging area slot to be moved on deck + ctx.comment(f"Adding 50 ul tip rack #{p50_rack_count}") + p50_racks_offdeck.append( + addtiprack + ) # used in TipSwap then deleted once it is moved + p50.tip_racks.append( + addtiprack + ) # lets pipette know it can use this rack now + TipSwap( + 50 + ) # Throw first tip box out and replace with a box from staging area + elif ( + len(Available_on_deck_slots) == 0 + and len(Available_off_deck_slots) == 0 + ): # If there are no tip racks on deck or in staging area to use + ctx.pause("Please place a new 50ul Tip Rack in slot A4") + p50_rack_count += 1 + tt_50 += 12 + addtiprack = ctx.load_labware( + "opentrons_flex_96_tiprack_50ul", + "A4", + f"50 ul Tip Rack #{p50_rack_count}", + ) + ctx.comment(f"Adding 50 ul tip rack #{p50_rack_count}") + p50_racks_offdeck.append( + addtiprack + ) # used in TipSwap, then deleted once it is moved + p50.tip_racks.append( + addtiprack + ) # lets pipette know it can use this rack now + TipSwap( + 50 + ) # Throw first tip box out and replace with a box from staging area + # Call where tips will actually be picked up + if reuse and REUSE_RSB_TIPS and reuse_col: + p50.pick_up_tip(tip50_reuse.wells()[8 * reuse_col]) + else: + tt_50 -= 1 + ctx.comment("Column " + str(12 - tt_50)) + ctx.comment( + "Available On Deck Slots:" + str(len(Available_on_deck_slots)) + ) + ctx.comment( + "Available Off Deck Slots:" + str(len(Available_off_deck_slots)) + ) + p50.pick_up_tip() + + if rack == tip200: + if ( + tt_200 == 0 and not reuse + ): # If this is the first column of tip box and these aren't reused tips + if len(Available_on_deck_slots) > 0: + avail_slot = Available_on_deck_slots[0] + p200_rack_count += 1 + tt_200 += 12 + addtiprack = ctx.load_labware( + "opentrons_flex_96_tiprack_200ul", + avail_slot, + f"200 ul Tip Rack #{p200_rack_count}", + ) + ctx.comment( + f"Adding 200 ul tip rack #{p200_rack_count} to slot {avail_slot}" + ) + Available_on_deck_slots.pop(0) + p200_racks_ondeck.append(addtiprack) + p200_racks_to_dump.append(addtiprack) + p200.tip_racks.append(addtiprack) + elif ( + len(Available_on_deck_slots) == 0 + and len(Available_off_deck_slots) > 0 + ): + p200_rack_count += 1 + tt_200 += 12 + addtiprack = ctx.load_labware( + "opentrons_flex_96_tiprack_200ul", + Available_off_deck_slots[0], + f"200 ul Tip Rack #{p200_rack_count}", + ) + Available_off_deck_slots.pop( + 0 + ) # Load rack into staging area slot to be moved on deck + ctx.comment(f"Adding 200 ul tip rack #{p200_rack_count}") + p200_racks_offdeck.append( + addtiprack + ) # used in TipSwap then deleted once it is moved + p200.tip_racks.append( + addtiprack + ) # lets pipette know it can use this rack now + TipSwap( + 200 + ) # Throw first tip box out and replace with a box from staging area + elif ( + len(Available_on_deck_slots) == 0 + and len(Available_off_deck_slots) == 0 + ): # If there are no tip racks on deck or in staging area to use + ctx.pause("Please place a new 200ul Tip Rack in slot B4") + p200_rack_count += 1 + tt_200 += 12 + addtiprack = ctx.load_labware( + "opentrons_flex_96_tiprack_200ul", + "B4", + f"200 ul Tip Rack #{p200_rack_count}", + ) + ctx.comment(f"Adding 200 ul tip rack #{p200_rack_count}") + p200_racks_offdeck.append( + addtiprack + ) # used in TipSwap, then deleted once it is moved + p200.tip_racks.append( + addtiprack + ) # lets pipette know it can use this rack now + TipSwap( + 200 + ) # Throw first tip box out and replace with a box from staging area + # Call where tips will actually be picked up + if reuse and REUSE_REMOVE_TIPS and reuse_col: + p200.pick_up_tip(tip200_reuse.wells()[8 * reuse_col]) + else: + tt_200 -= 1 + ctx.comment("Column " + str(12 - tt_200)) + ctx.comment( + "Available On Deck Slots:" + str(len(Available_on_deck_slots)) + ) + ctx.comment( + "Available Off Deck Slots:" + str(len(Available_off_deck_slots)) + ) + p200.pick_up_tip() + + tiptrack(tip50, None, reuse=False) + p50.return_tip() + helpers.find_liquid_height_of_loaded_liquids(ctx, liquid_vols_and_wells, p50) + + def TipSwap(tipvol: int) -> None: + """Tip swap.""" + if tipvol == 50: + rack_to_dispose = p50_racks_to_dump[0] + rack_to_add = p50_racks_offdeck[0] + deck_slot = p50_racks_to_dump[0].parent + p50_racks_ondeck.append(rack_to_add) + p50_racks_to_dump.pop(0) + p50_racks_to_dump.append(rack_to_add) + p50_racks_ondeck.pop(0) + p50_racks_offdeck.pop(0) + + if tipvol == 200: + rack_to_dispose = p200_racks_to_dump[0] + rack_to_add = p200_racks_offdeck[0] + deck_slot = p200_racks_to_dump[0].parent + p200_racks_ondeck.append(rack_to_add) + p200_racks_to_dump.pop(0) + p200_racks_to_dump.append(rack_to_add) + p200_racks_ondeck.pop(0) + p200_racks_offdeck.pop(0) + + ctx.move_labware( + labware=rack_to_dispose, new_location=trash, use_gripper=USE_GRIPPER + ) + ctx.move_labware( + labware=rack_to_add, new_location=deck_slot, use_gripper=USE_GRIPPER + ) + ctx.comment( + f"Threw out: {rack_to_dispose} and placed {rack_to_add} to {deck_slot}" + ) + + def run_tag_profile( + unused_lids: List[Labware], used_lids: List[Labware] + ) -> Tuple[List[Labware], List[Labware]]: + """Run Tag Profile.""" + # Presetting Thermocycler Temps + ctx.comment( + "****Starting Fragmentation Profile (37C for 10 minutes with 100C lid)****" + ) + tc_mod.set_lid_temperature(100) + tc_mod.set_block_temperature(37) + + # Move Plate to TC + ctx.comment("****Moving Plate to Pre-Warmed TC Module Block****") + ctx.move_labware(sample_plate, tc_mod, use_gripper=USE_GRIPPER) + + if disposable_lid: + lid_on_plate, unused_lids, used_lids = helpers.use_disposable_lid_with_tc( + ctx, unused_lids, used_lids, sample_plate, tc_mod + ) + else: + tc_mod.close_lid() + tc_mod.set_block_temperature( + temperature=37, hold_time_minutes=Fragmentation_time, block_max_volume=50 + ) + tc_mod.open_lid() + + if disposable_lid: + if len(used_lids) <= 1: + ctx.move_labware(lid_on_plate, "C4", use_gripper=True) + else: + ctx.move_labware(lid_on_plate, used_lids[-2], use_gripper=True) + # #Move Plate to H-S + ctx.comment("****Moving Plate off of TC****") + + ctx.move_labware(sample_plate, "D1", use_gripper=USE_GRIPPER) + return unused_lids, used_lids + + def run_er_profile( + unused_lids: List[Labware], used_lids: List[Labware] + ) -> Tuple[List[Labware], List[Labware]]: + """End Repair Profile.""" + # Presetting Thermocycler Temps + ctx.comment( + "****Starting End Repair Profile (65C for 30 minutes with 100C lid)****" + ) + tc_mod.set_lid_temperature(100) + tc_mod.set_block_temperature(65) + + # Move Plate to TC + ctx.comment("****Moving Plate to Pre-Warmed TC Module Block****") + ctx.move_labware(sample_plate, tc_mod, use_gripper=USE_GRIPPER) + + if disposable_lid: + lid_on_plate, unused_lids, used_lids = helpers.use_disposable_lid_with_tc( + ctx, unused_lids, used_lids, sample_plate, tc_mod + ) + else: + tc_mod.close_lid() + tc_mod.set_block_temperature( + temperature=65, hold_time_minutes=30, block_max_volume=50 + ) + + tc_mod.deactivate_block() + tc_mod.open_lid() + + if disposable_lid: + # move lid + if len(used_lids) <= 1: + ctx.move_labware(lid_on_plate, "C4", use_gripper=True) + else: + ctx.move_labware(lid_on_plate, used_lids[-2], use_gripper=True) + # #Move Plate to H-S + ctx.comment("****Moving Plate off of TC****") + + ctx.move_labware(sample_plate, "D1", use_gripper=USE_GRIPPER) + return unused_lids, used_lids + + def run_ligation_profile( + unused_lids: List[Labware], used_lids: List[Labware] + ) -> Tuple[List[Labware], List[Labware]]: + """Run Ligation Profile.""" + # Presetting Thermocycler Temps + ctx.comment( + "****Starting Ligation Profile (20C for 15 minutes with 100C lid)****" + ) + tc_mod.set_lid_temperature(100) + tc_mod.set_block_temperature(20) + + # Move Plate to TC + ctx.comment("****Moving Plate to Pre-Warmed TC Module Block****") + + ctx.move_labware(sample_plate, tc_mod, use_gripper=USE_GRIPPER) + + if disposable_lid: + lid_on_plate, unused_lids, used_lids = helpers.use_disposable_lid_with_tc( + ctx, unused_lids, used_lids, sample_plate, tc_mod + ) + else: + tc_mod.close_lid() + tc_mod.set_block_temperature( + temperature=20, hold_time_minutes=ligation_tc_time, block_max_volume=50 + ) + + tc_mod.deactivate_block() + + tc_mod.open_lid() + # Move lid + tc_mod.open_lid() + if disposable_lid: + if len(used_lids) <= 1: + ctx.move_labware(lid_on_plate, "C4", use_gripper=True) + else: + ctx.move_labware(lid_on_plate, used_lids[-2], use_gripper=True) + + # #Move Plate to H-S + ctx.comment("****Moving Plate off of TC****") + + ctx.move_labware(sample_plate, "D1", use_gripper=USE_GRIPPER) + return unused_lids, used_lids + + def run_amplification_profile( + unused_lids: List[Labware], used_lids: List[Labware] + ) -> Tuple[List[Labware], List[Labware]]: + """Run Amplification Profile.""" + # Presetting Thermocycler Temps + ctx.comment( + "Amplification Profile (37C for 5 min, 50C for 5 min with 100C lid)" + ) + tc_mod.set_lid_temperature(100) + tc_mod.set_block_temperature(98) + + # Move Plate to TC + ctx.comment("****Moving Sample Plate onto TC****") + ctx.move_labware(sample_plate_2, tc_mod, use_gripper=USE_GRIPPER) + + if not dry_run: + tc_mod.set_lid_temperature(105) + if disposable_lid: + lid_on_plate, unused_lids, used_lids = helpers.use_disposable_lid_with_tc( + ctx, unused_lids, used_lids, sample_plate_2, tc_mod + ) + else: + tc_mod.close_lid() + if not dry_run: + helpers.perform_pcr( + ctx, + tc_mod, + initial_denature_time_sec=45, + denaturation_time_sec=15, + anneal_time_sec=30, + extension_time_sec=30, + cycle_repetitions=PCRCYCLES, + final_extension_time_min=1, + ) + tc_mod.set_block_temperature(4) + tc_mod.open_lid() + if disposable_lid: + if len(used_lids) <= 1: + ctx.move_labware(lid_on_plate, "C4", use_gripper=True) + else: + ctx.move_labware(lid_on_plate, used_lids[-2], use_gripper=True) + + # Move Sample Plate to H-S + ctx.comment("****Moving Sample Plate back to H-S****") + ctx.move_labware(sample_plate_2, "D1", use_gripper=USE_GRIPPER) + # get FLP plate out of the way + ctx.comment("****Moving FLP Plate back to TC****") + ctx.move_labware(FLP_plate, tc_mod, use_gripper=USE_GRIPPER) + return unused_lids, used_lids + + def mix_beads( + pip: InstrumentContext, res: Well, vol: float, reps: int, col: int + ) -> None: + """Mix beads function.""" + # Multiplier tells + mix_vol = (num_cols - col) * vol + if pip == p50: + if mix_vol > 50: + mix_vol = 50 + if pip == p200: + if mix_vol > 200: + mix_vol = 200 + + if res == bead_res: + width = res.width + else: + width = res.diameter + if width: + move = (width / 2) - 1 + + loc_center_a = res.bottom().move(types.Point(x=0, y=0, z=0.5)) + loc_center_d = res.bottom().move(types.Point(x=0, y=0, z=0.5)) + loc1 = res.bottom().move(types.Point(x=move, y=0, z=5)) + loc2 = res.bottom().move(types.Point(x=0, y=move, z=5)) + loc3 = res.bottom().move(types.Point(x=-move, y=0, z=5)) + loc4 = res.bottom().move(types.Point(x=0, y=-move, z=5)) + loc5 = res.bottom().move(types.Point(x=move / 2, y=move / 2, z=5)) + loc6 = res.bottom().move(types.Point(x=-move / 2, y=move / 2, z=5)) + loc7 = res.bottom().move(types.Point(x=-move / 2, y=-move / 2, z=5)) + loc8 = res.bottom().move(types.Point(x=move / 2, y=-move / 2, z=5)) + + loc = [loc_center_d, loc1, loc5, loc2, loc6, loc3, loc7, loc4, loc8] + + pip.aspirate( + mix_vol, res.bottom().move(types.Point(x=0, y=0, z=10)) + ) # Blow bubbles to start + pip.dispense(mix_vol, loc_center_d) + for x in range(reps): + pip.aspirate(mix_vol, loc_center_a) + pip.dispense(mix_vol, loc[x]) + pip.flow_rate.aspirate = 10 + pip.flow_rate.dispense = 10 + pip.aspirate(mix_vol, loc_center_a) + pip.dispense(mix_vol, loc_center_d) + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 150 + + def remove_supernatant(well: Well, vol: float, waste_: Well, column: int) -> None: + """Remove supernatant.""" + ctx.comment("-------Removing " + str(vol) + "ul of Supernatant-------") + p200.flow_rate.aspirate = 15 + num_trans = math.ceil(vol / 190) + vol_per_trans = vol / num_trans + for x in range(num_trans): + tiptrack(tip200, column, reuse=True if REUSE_REMOVE_TIPS else False) + p200.aspirate(vol_per_trans / 2, well.bottom(0.2)) + ctx.delay(seconds=1) + p200.aspirate(vol_per_trans / 2, well.bottom(0.2)) + p200.air_gap(10) + p200.dispense(p200.current_volume, waste_) + p200.air_gap(10) + if REUSE_REMOVE_TIPS: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + else: + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + p200.flow_rate.aspirate = 150 + + def Fragmentation( + unused_lids: List[Labware], used_lids: List[Labware] + ) -> Tuple[List[Labware], List[Labware]]: + """Fragmentation Function.""" + ctx.comment("-------Starting Fragmentation-------") + + for i in range(num_cols): + + ctx.comment("Mixing and Transfering beads to column " + str(i + 1)) + + tiptrack(tip50, None, reuse=False) + p50.flow_rate.dispense = 15 + p50.aspirate(frag_vol, frag_res) + p50.dispense(p50.current_volume, samples[i]) + p50.flow_rate.dispense = 150 + for x in range(10 if not dry_run else 1): + if x == 9: + p50.flow_rate.aspirate = 15 + p50.flow_rate.dispense = 15 + p50.aspirate(frag_vol, samples[i].bottom(1)) + p50.dispense(p50.current_volume, samples[i].bottom(5)) + p50.flow_rate.aspirate = 150 + p50.flow_rate.dispense = 150 + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + unused_lids, used_lids = run_tag_profile( + unused_lids, used_lids + ) # Heats TC --> moves plate to TC --> TAG Profile --> removes plate from TC + return unused_lids, used_lids + + def end_repair( + unused_lids: List[Labware], used_lids: List[Labware] + ) -> Tuple[List[Labware], List[Labware]]: + """End Repair Function.""" + ctx.comment("-------Starting end_repair-------") + + for i in range(num_cols): + + ctx.comment( + "**** Mixing and Transfering beads to column " + str(i + 1) + " ****" + ) + + tiptrack(tip50, None, reuse=False) + p50.flow_rate.dispense = 15 + p50.aspirate(end_repair_vol, er_res) + p50.dispense(p50.current_volume, samples[i]) + p50.flow_rate.dispense = 150 + for x in range(10 if not dry_run else 1): + if x == 9: + p50.flow_rate.aspirate = 15 + p50.flow_rate.dispense = 15 + p50.aspirate(end_repair_vol, samples[i].bottom(1)) + p50.dispense(p50.current_volume, samples[i].bottom(5)) + p50.flow_rate.aspirate = 150 + p50.flow_rate.dispense = 150 + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + unused_lids, used_lids = run_er_profile( + unused_lids, used_lids + ) # Heats TC --> moves plate to TC --> TAG Profile --> removes plate from TC + return unused_lids, used_lids + + # Index Ligation + + def index_ligation( + unused_lids: List[Labware], used_lids: List[Labware] + ) -> Tuple[List[Labware], List[Labware]]: + """Index Ligation.""" + ctx.comment("-------Ligating Indexes-------") + ctx.comment("-------Adding and Mixing ELM-------") + for i in samples: + tiptrack(tip50, None, reuse=False) + p50.aspirate(ligation_vol, ligation_res) + p50.dispense(p50.current_volume, i) + for x in range(10 if not dry_run else 1): + if x == 9: + p50.flow_rate.aspirate = 75 + p50.flow_rate.dispense = 75 + p50.aspirate(ligation_vol - 10, i) + p50.dispense(p50.current_volume, i.bottom(8)) + p50.flow_rate.aspirate = 150 + p50.flow_rate.dispense = 150 + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + # Add and mix adapters + ctx.comment("-------Adding and Mixing Adapters-------") + for i_well, x_well in zip(samples, adapters): + tiptrack(tip50, None, reuse=False) + p50.aspirate(adapter_vol, x_well) + p50.dispense(p50.current_volume, i_well) + for y in range(10 if not dry_run else 1): + if y == 9: + p50.flow_rate.aspirate = 75 + p50.flow_rate.dispense = 75 + p50.aspirate(40, i_well) + p50.dispense(40, i_well.bottom(8)) + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + p50.flow_rate.aspirate = 150 + p50.flow_rate.dispense = 150 + + unused_lids, used_lids = run_ligation_profile(unused_lids, used_lids) + return unused_lids, used_lids + + def lib_cleanup() -> None: + """Litigation Clean up.""" + ctx.comment("-------Starting Cleanup-------") + ctx.comment("-------Adding and Mixing Cleanup Beads-------") + + # Move FLP plate off magnetic module if it's there + if FLP_plate.parent == magblock: + ctx.comment("****Moving FLP Plate off Magnetic Module****") + ctx.move_labware(FLP_plate, tc_mod, use_gripper=USE_GRIPPER) + + for x, i in enumerate(samples): + tiptrack(tip200, None, reuse=False) + mix_beads(p200, bead_res, bead_vol_1, 7 if x == 0 else 2, x) + p200.aspirate(bead_vol_1, bead_res) + p200.dispense(bead_vol_1, i) + mix_beads(p200, i, bead_vol_1, 7 if not dry_run else 1, num_cols - 1) + for x in range(10 if not dry_run else 1): + if x == 9: + p200.flow_rate.aspirate = 75 + p200.flow_rate.dispense = 75 + p200.aspirate(bead_vol_1, i) + p200.dispense(bead_vol_1, i.bottom(8)) + p200.flow_rate.aspirate = 150 + p200.flow_rate.dispense = 150 + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + ctx.delay( + minutes=bead_inc, + msg="Please wait " + + str(bead_inc) + + " minutes while samples incubate at RT.", + ) + + ctx.comment("****Moving Labware to Magnet for Pelleting****") + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + + ctx.delay(minutes=4.5, msg="Time for Pelleting") + + for col, i in enumerate(samples): + remove_supernatant(i, 130, waste1_res, col) + samp_list = samples + + # Wash 2 x with 80% Ethanol + p200.flow_rate.aspirate = 75 + p200.flow_rate.dispense = 75 + for y in range(2 if not dry_run else 1): + ctx.comment(f"-------Wash # {y+1} with Ethanol-------") + if y == 0: # First wash + this_res = etoh1_res + this_waste_res = waste1_res + else: # Second Wash + this_res = etoh2_res + this_waste_res = waste2_res + if REUSE_ETOH_TIPS: + tiptrack(tip200, None, reuse=False) + for i in samp_list: + if not REUSE_ETOH_TIPS: + tiptrack(tip200, None, reuse=False) + p200.aspirate(150, this_res) + p200.air_gap(10) + p200.dispense(p200.current_volume, i.top()) + ctx.delay(seconds=1) + p200.air_gap(10) + if not REUSE_ETOH_TIPS: + p200.drop_tip() if trash_tips else p200.return_tip() + + ctx.delay(seconds=10) + # Remove the ethanol wash + for x, i in enumerate(samp_list): + if REUSE_ETOH_TIPS: + if x != 0: + tiptrack(tip200, None, reuse=False) + if not REUSE_ETOH_TIPS: + tiptrack(tip200, None, reuse=False) + p200.aspirate(155, i) + p200.air_gap(10) + p200.dispense(p200.current_volume, this_waste_res) + ctx.delay(seconds=1) + p200.air_gap(10) + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + p200.flow_rate.aspirate = 150 + p200.flow_rate.dispense = 150 + + # Wash complete, move on to drying steps. + ctx.delay(minutes=2, msg="Allow 3 minutes for residual ethanol to dry") + + # Return Plate to H-S from Magnet + + ctx.comment("****Moving sample plate off of Magnet****") + ctx.move_labware(sample_plate, "D1", use_gripper=USE_GRIPPER) + + # Adding RSB and Mixing + + for col, i in enumerate(samp_list): + ctx.comment(f"****Adding RSB to Columns: {col+1}****") + tiptrack(tip50, col, reuse=True if REUSE_RSB_TIPS else False) + p50.aspirate(rsb_vol_1, rsb_res) + p50.air_gap(5) + p50.dispense(p50.current_volume, i) + for x in range(10 if not dry_run else 1): + if x == 9: + p50.flow_rate.aspirate = 15 + p50.flow_rate.dispense = 15 + p50.aspirate(15, i.bottom(1)) + p50.dispense(15, i.bottom(4)) + p50.flow_rate.aspirate = 100 + p50.flow_rate.dispense = 100 + p50.air_gap(5) + if REUSE_RSB_TIPS: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + else: + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + ctx.delay( + minutes=3, msg="Allow 3 minutes for incubation and liquid aggregation." + ) + + ctx.comment("****Move Samples to Magnet for Pelleting****") + ctx.move_labware(sample_plate, magblock, use_gripper=USE_GRIPPER) + + ctx.delay(minutes=2, msg="Please allow 2 minutes for beads to pellet.") + + p200.flow_rate.aspirate = 10 + for i_int, (s, e) in enumerate(zip(samp_list, samples_2)): + tiptrack(tip50, i_int, reuse=True if REUSE_RSB_TIPS else False) + p50.aspirate(elution_vol, s) + p50.air_gap(5) + p50.dispense(p50.current_volume, e.bottom(1), push_out=3) + p50.air_gap(5) + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + # move new sample plate to D1 or heatershaker + ctx.comment("****Moving sample plate off of Magnet****") + ctx.move_labware(sample_plate_2, "D1", use_gripper=USE_GRIPPER) + + # Keep Sample PLate 1 to B2 + ctx.comment("****Moving Sample_plate_1 Plate off magnet to B2****") + ctx.move_labware(sample_plate, "B2", use_gripper=USE_GRIPPER) + + ctx.comment("****Moving FLP Plate off TC****") + ctx.move_labware(FLP_plate, magblock, use_gripper=USE_GRIPPER) + + def lib_amplification( + unused_lids: List[Labware], used_lids: List[Labware] + ) -> Tuple[List[Labware], List[Labware]]: + """Library Amplification.""" + ctx.comment("-------Starting lib_amplification-------") + + for i in range(num_cols): + + ctx.comment( + "**** Mixing and Transfering beads to column " + str(i + 1) + " ****" + ) + + tiptrack(tip50, None, reuse=False) + mix_beads( + p50, amplification_res, amplification_vol, 7 if i == 0 else 2, i + ) # 5 reps for first mix in reservoir + p50.flow_rate.dispense = 15 + p50.aspirate(amplification_vol, amplification_res) + p50.dispense(p50.current_volume, samples_2[i]) + p50.flow_rate.dispense = 150 + for x in range(10 if not dry_run else 1): + if x == 9: + p50.flow_rate.aspirate = 15 + p50.flow_rate.dispense = 15 + p50.aspirate(amplification_vol, samples_2[i].bottom(1)) + p50.dispense(p50.current_volume, samples_2[i].bottom(5)) + p50.flow_rate.aspirate = 150 + p50.flow_rate.dispense = 150 + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + unused_lids, used_lids = run_amplification_profile( + unused_lids, used_lids + ) # moves plate to TC --> TAG Profile --> removes plate from TC + return unused_lids, used_lids + + def lib_cleanup_2() -> None: + """Final Library Clean up.""" + ctx.comment("-------Starting Cleanup-------") + ctx.comment("-------Adding and Mixing Cleanup Beads-------") + for x, i in enumerate(samples_2): + tiptrack(tip200, None, reuse=False) + mix_beads(p200, bead_res, bead_vol_2, 7 if x == 0 else 2, x) + p200.aspirate(bead_vol_2, bead_res) + p200.dispense(bead_vol_2, i) + mix_beads(p200, i, bead_vol_2, 7 if not dry_run else 1, num_cols - 1) + for x in range(10 if not dry_run else 1): + if x == 9: + p200.flow_rate.aspirate = 75 + p200.flow_rate.dispense = 75 + p200.aspirate(bead_vol_2, i) + p200.dispense(bead_vol_2, i.bottom(8)) + p200.flow_rate.aspirate = 150 + p200.flow_rate.dispense = 150 + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + ctx.delay( + minutes=bead_inc, + msg="Please wait " + + str(bead_inc) + + " minutes while samples incubate at RT.", + ) + + ctx.comment("****Moving Labware to Magnet for Pelleting****") + ctx.move_labware(sample_plate_2, magblock, use_gripper=USE_GRIPPER) + + ctx.delay(minutes=4.5, msg="Time for Pelleting") + + for col, i in enumerate(samples_2): + remove_supernatant(i, 130, waste1_res, col) + samp_list_2 = samples_2 + # Wash 2 x with 80% Ethanol + + p200.flow_rate.aspirate = 75 + p200.flow_rate.dispense = 75 + for y in range(2 if not dry_run else 1): + ctx.comment(f"-------Wash # {y+1} with Ethanol-------") + if y == 0: # First wash + this_res = etoh1_res + this_waste_res = waste1_res + else: # Second Wash + this_res = etoh2_res + this_waste_res = waste2_res + if REUSE_ETOH_TIPS: + tiptrack(tip200, None, reuse=False) + for i in samp_list_2: + if not REUSE_ETOH_TIPS: + tiptrack(tip200, None, reuse=False) + p200.aspirate(150, this_res) + p200.air_gap(10) + p200.dispense(p200.current_volume, i.top()) + ctx.delay(seconds=1) + p200.air_gap(10) + if not REUSE_ETOH_TIPS: + p200.drop_tip() if trash_tips else p200.return_tip() + + ctx.delay(seconds=10) + # Remove the ethanol wash + for x, i in enumerate(samp_list_2): + if REUSE_ETOH_TIPS: + if x != 0: + tiptrack(tip200, None, reuse=False) + if not REUSE_ETOH_TIPS: + tiptrack(tip200, None, reuse=False) + p200.aspirate(155, i) + p200.air_gap(10) + p200.dispense(p200.current_volume, this_waste_res) + ctx.delay(seconds=1) + p200.air_gap(10) + if trash_tips: + p200.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p200.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + p200.flow_rate.aspirate = 150 + p200.flow_rate.dispense = 150 + + # Washes Complete, Move on to Drying Steps + + ctx.delay(minutes=3, msg="Allow 3 minutes for residual ethanol to dry") + + ctx.comment("****Moving sample plate off of Magnet****") + ctx.move_labware(sample_plate_2, "D1", use_gripper=USE_GRIPPER) + + # Adding RSB and Mixing + + for col, i in enumerate(samp_list_2): + ctx.comment(f"****Adding RSB to Columns: {col+1}****") + tiptrack(tip50, col, reuse=True if REUSE_RSB_TIPS else False) + p50.aspirate(rsb_vol_2, rsb_res) + p50.air_gap(5) + p50.dispense(p50.current_volume, i) + for x in range(10 if not dry_run else 1): + if x == 9: + p50.flow_rate.aspirate = 15 + p50.flow_rate.dispense = 15 + p50.aspirate(15, i.bottom(1)) + p50.dispense(15, i.bottom(4)) + p50.flow_rate.aspirate = 100 + p50.flow_rate.dispense = 100 + p50.air_gap(5) + if REUSE_RSB_TIPS: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + else: + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + ctx.delay( + minutes=3, msg="Allow 3 minutes for incubation and liquid aggregation." + ) + + ctx.comment("****Move Samples to Magnet for Pelleting****") + ctx.move_labware(sample_plate_2, magblock, use_gripper=USE_GRIPPER) + + ctx.delay(minutes=2, msg="Please allow 2 minutes for beads to pellet.") + + p200.flow_rate.aspirate = 10 + for i_int, (s, e) in enumerate(zip(samp_list_2, samples_flp)): + tiptrack(tip50, i_int, reuse=True if REUSE_RSB_TIPS else False) + p50.aspirate(elution_vol_2, s) + p50.air_gap(5) + p50.dispense(p50.current_volume, e.bottom(1), push_out=3) + p50.air_gap(5) + if trash_tips: + p50.drop_tip() + ctx.comment("****Dropping Tip in Waste shoot****") + else: + p50.return_tip() + ctx.comment("****Dropping Tip Back in Tip Box****") + + # Set Block Temp for Final Plate + tc_mod.set_block_temperature(4) + + unused_lids, used_lids = Fragmentation(unused_lids, used_lids) + unused_lids, used_lids = end_repair(unused_lids, used_lids) + unused_lids, used_lids = index_ligation(unused_lids, used_lids) + lib_cleanup() + unused_lids, used_lids = lib_amplification(unused_lids, used_lids) + lib_cleanup_2() + end_probed_wells = [waste1_res, waste2_res] + helpers.find_liquid_height_of_all_wells(ctx, p50, end_probed_wells) diff --git a/abr-testing/abr_testing/protocols/active_protocols/1_Simple Normalize Long Right.py b/abr-testing/abr_testing/protocols/active_protocols/1_Simple Normalize Long Right.py new file mode 100644 index 00000000000..525a82c3095 --- /dev/null +++ b/abr-testing/abr_testing/protocols/active_protocols/1_Simple Normalize Long Right.py @@ -0,0 +1,354 @@ +"""Simple Normalize Long with LPD and Single Tip.""" +from opentrons.protocol_api import ( + ProtocolContext, + ParameterContext, + Labware, + SINGLE, + InstrumentContext, + Well, +) +from abr_testing.protocols import helpers + +metadata = { + "protocolName": "Simple Normalize Long with LPD and Single Tip", + "author": "Opentrons ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.21", +} + + +def add_parameters(parameters: ParameterContext) -> None: + """Parameters.""" + helpers.create_single_pipette_mount_parameter(parameters) + helpers.create_tip_size_parameter(parameters) + + +def get_next_tip_by_row(tip_rack: Labware, pipette: InstrumentContext) -> Well | None: + """Get next tip by row. + + This function returns the well name of the next tip to pick up for a given + tiprack with row-bias. Returns None if the pipette is out of tips + """ + if tip_rack.is_tiprack: + if pipette.channels == 8: + for passes in range( + 0, int(len(tip_rack.columns()[0]) / pipette.active_channels) + ): + for column in tip_rack.columns(): + # When the pipette's starting channels is H1, consume tips starting at top row. + if pipette._core.get_nozzle_map().starting_nozzle == "H1": + active_column = column + else: + # We reverse our tiprack reference to consume tips starting at bottom. + active_column = column[::-1] + + if len(active_column) >= ( + ((pipette.active_channels * passes) + pipette.active_channels) + ) and all( + well.has_tip is True + for well in active_column[ + (pipette.active_channels * passes) : ( + ( + (pipette.active_channels * passes) + + pipette.active_channels + ) + ) + ] + ): + return active_column[ + ( + (pipette.active_channels * passes) + + (pipette.active_channels - 1) + ) + ] + # No valid tips were found for current pipette configuration in provided tip rack. + return None + else: + raise ValueError( + "Parameter 'pipette' of get_next_tip_by_row must be an 8 Channel Pipette." + ) + else: + raise ValueError( + "Parameter 'tip_rack' of get_next_tip_by_row must be a recognized Tip Rack labware." + ) + + +def run(protocol: ProtocolContext) -> None: + """Protocol.""" + tip_type = protocol.params.tip_size # type: ignore[attr-defined] + mount_pos = protocol.params.pipette_mount # type: ignore[attr-defined] + # DECK SETUP AND LABWARE + protocol.comment("THIS IS A NO MODULE RUN") + tiprack_x_1 = protocol.load_labware(tip_type, "D1") + tiprack_x_2 = protocol.load_labware(tip_type, "D2") + tiprack_x_3 = protocol.load_labware(tip_type, "B1") + sample_plate_1 = protocol.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", "D3" + ) + + reservoir = protocol.load_labware("nest_12_reservoir_15ml", "B3") + sample_plate_2 = protocol.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", "C2" + ) + sample_plate_3 = protocol.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", "B2" + ) + protocol.load_trash_bin("A3") + + # reagent + Dye_1 = reservoir["A1"] + Dye_2 = reservoir["A2"] + Dye_3 = reservoir["A3"] + Diluent_1 = reservoir["A4"] + Diluent_2 = reservoir["A5"] + + # pipette + p1000 = protocol.load_instrument( + "flex_8channel_1000", mount_pos, liquid_presence_detection=True + ) + # LOAD LIQUIDS + liquid_volumes = [675.0, 675.0, 675.0, 675.0, 675.0] + wells = [Dye_1, Dye_2, Dye_3, Diluent_1, Diluent_2] + helpers.load_wells_with_water(protocol, wells, liquid_volumes) + + current_rack = tiprack_x_1 + # CONFIGURE SINGLE LAYOUT + p1000.configure_nozzle_layout( + style=SINGLE, start="H1", tip_racks=[tiprack_x_1, tiprack_x_2, tiprack_x_3] + ) + helpers.find_liquid_height_of_all_wells(protocol, p1000, wells) + sample_quant_csv = """ + sample_plate_1, Sample_well,DYE,DILUENT + sample_plate_1,A1,0,100 + sample_plate_1,B1,5,95 + sample_plate_1,C1,10,90 + sample_plate_1,D1,20,80 + sample_plate_1,E1,40,60 + sample_plate_1,F1,15,40 + sample_plate_1,G1,40,20 + sample_plate_1,H1,40,0 + sample_plate_1,A2,35,65 + sample_plate_1,B2,38,42 + sample_plate_1,C2,42,58 + sample_plate_1,D2,32,8 + sample_plate_1,E2,38,12 + sample_plate_1,F2,26,74 + sample_plate_1,G2,31,69 + sample_plate_1,H2,46,4 + sample_plate_1,A3,47,13 + sample_plate_1,B3,42,18 + sample_plate_1,C3,46,64 + sample_plate_1,D3,48,22 + sample_plate_1,E3,26,74 + sample_plate_1,F3,34,66 + sample_plate_1,G3,43,37 + sample_plate_1,H3,20,80 + sample_plate_1,A4,44,16 + sample_plate_1,B4,49,41 + sample_plate_1,C4,48,42 + sample_plate_1,D4,44,16 + sample_plate_1,E4,47,53 + sample_plate_1,F4,47,33 + sample_plate_1,G4,42,48 + sample_plate_1,H4,39,21 + sample_plate_1,A5,30,20 + sample_plate_1,B5,36,14 + sample_plate_1,C5,31,59 + sample_plate_1,D5,38,52 + sample_plate_1,E5,36,4 + sample_plate_1,F5,32,28 + sample_plate_1,G5,35,55 + sample_plate_1,H5,39,1 + sample_plate_1,A6,31,59 + sample_plate_1,B6,20,80 + sample_plate_1,C6,38,2 + sample_plate_1,D6,34,46 + sample_plate_1,E6,30,70 + sample_plate_1,F6,32,58 + sample_plate_1,G6,21,79 + sample_plate_1,H6,38,52 + sample_plate_1,A7,33,27 + sample_plate_1,B7,34,16 + sample_plate_1,C7,40,60 + sample_plate_1,D7,34,26 + sample_plate_1,E7,30,20 + sample_plate_1,F7,44,56 + sample_plate_1,G7,26,74 + sample_plate_1,H7,45,55 + sample_plate_1,A8,39,1 + sample_plate_1,B8,38,2 + sample_plate_1,C8,34,66 + sample_plate_1,D8,39,11 + sample_plate_1,E8,46,54 + sample_plate_1,F8,37,63 + sample_plate_1,G8,38,42 + sample_plate_1,H8,34,66 + sample_plate_1,A9,44,56 + sample_plate_1,B9,39,11 + sample_plate_1,C9,30,70 + sample_plate_1,D9,37,33 + sample_plate_1,E9,46,54 + sample_plate_1,F9,39,21 + sample_plate_1,G9,29,41 + sample_plate_1,H9,23,77 + sample_plate_1,A10,26,74 + sample_plate_1,B10,39,1 + sample_plate_1,C10,31,49 + sample_plate_1,D10,38,62 + sample_plate_1,E10,29,1 + sample_plate_1,F10,21,79 + sample_plate_1,G10,29,41 + sample_plate_1,H10,28,42 + sample_plate_1,A11,15,55 + sample_plate_1,B11,28,72 + sample_plate_1,C11,11,49 + sample_plate_1,D11,34,66 + sample_plate_1,E11,27,73 + sample_plate_1,F11,30,40 + sample_plate_1,G11,33,67 + sample_plate_1,H11,31,39 + sample_plate_1,A12,39,31 + sample_plate_1,B12,47,53 + sample_plate_1,C12,46,54 + sample_plate_1,D12,13,7 + sample_plate_1,E12,34,46 + sample_plate_1,F12,45,35 + sample_plate_1,G12,28,42 + sample_plate_1,H12,37,63 + """ + + data = [r.split(",") for r in sample_quant_csv.strip().splitlines() if r][1:] + for X in range(1): + protocol.comment("==============================================") + protocol.comment("Adding Dye Sample Plate 1") + protocol.comment("==============================================") + + current = 0 + + well = get_next_tip_by_row(current_rack, p1000) + p1000.pick_up_tip(well) + while current < len(data): + CurrentWell = str(data[current][1]) + DyeVol = float(data[current][2]) + if DyeVol != 0 and DyeVol < 100: + p1000.liquid_presence_detection = False + p1000.transfer( + DyeVol, + Dye_1.bottom(z=2), + sample_plate_1.wells_by_name()[CurrentWell].top(z=1), + new_tip="never", + ) + if DyeVol > 20: + wells.append(sample_plate_1.wells_by_name()[CurrentWell]) + current += 1 + p1000.blow_out() + p1000.touch_tip() + p1000.drop_tip() + p1000.liquid_presence_detection = True + + protocol.comment("==============================================") + protocol.comment("Adding Diluent Sample Plate 1") + protocol.comment("==============================================") + + current = 0 + while current < len(data): + CurrentWell = str(data[current][1]) + DilutionVol = float(data[current][2]) + if DilutionVol != 0 and DilutionVol < 100: + well = get_next_tip_by_row(current_rack, p1000) + p1000.pick_up_tip(well) + p1000.aspirate(DilutionVol, Diluent_1.bottom(z=2)) + p1000.dispense( + DilutionVol, sample_plate_1.wells_by_name()[CurrentWell].top(z=0.2) + ) + if DilutionVol > 20: + wells.append(sample_plate_1.wells_by_name()[CurrentWell]) + p1000.blow_out() + p1000.touch_tip() + p1000.drop_tip() + current += 1 + + protocol.comment("==============================================") + protocol.comment("Adding Dye Sample Plate 2") + protocol.comment("==============================================") + + current = 0 + well = get_next_tip_by_row(tiprack_x_2, p1000) + p1000.pick_up_tip(well) + while current < len(data): + CurrentWell = str(data[current][1]) + DyeVol = float(data[current][2]) + if DyeVol != 0 and DyeVol < 100: + p1000.transfer( + DyeVol, + Dye_2.bottom(z=2), + sample_plate_2.wells_by_name()[CurrentWell].top(z=1), + new_tip="never", + ) + if DyeVol > 20: + wells.append(sample_plate_2.wells_by_name()[CurrentWell]) + current += 1 + p1000.blow_out() + p1000.touch_tip() + p1000.drop_tip() + + protocol.comment("==============================================") + protocol.comment("Adding Diluent Sample Plate 2") + protocol.comment("==============================================") + + current = 0 + while current < len(data): + CurrentWell = str(data[current][1]) + DilutionVol = float(data[current][2]) + if DilutionVol != 0 and DilutionVol < 100: + well = get_next_tip_by_row(tiprack_x_2, p1000) + p1000.pick_up_tip(well) + p1000.aspirate(DilutionVol, Diluent_2.bottom(z=2)) + p1000.dispense( + DilutionVol, sample_plate_2.wells_by_name()[CurrentWell].top(z=0.2) + ) + if DilutionVol > 20: + wells.append(sample_plate_2.wells_by_name()[CurrentWell]) + p1000.blow_out() + p1000.touch_tip() + p1000.drop_tip() + current += 1 + + protocol.comment("==============================================") + protocol.comment("Adding Dye Sample Plate 3") + protocol.comment("==============================================") + + current = 0 + well = get_next_tip_by_row(tiprack_x_3, p1000) + p1000.pick_up_tip(well) + while current < len(data): + CurrentWell = str(data[current][1]) + DyeVol = float(data[current][2]) + if DyeVol != 0 and DyeVol < 100: + p1000.liquid_presence_detection = False + p1000.transfer( + DyeVol, + Dye_3.bottom(z=2), + sample_plate_3.wells_by_name()[CurrentWell].top(z=1), + blow_out=True, + blowout_location="destination well", + new_tip="never", + ) + if DyeVol > 20: + wells.append(sample_plate_3.wells_by_name()[CurrentWell]) + current += 1 + p1000.liquid_presence_detection = True + p1000.blow_out() + p1000.touch_tip() + p1000.drop_tip() + protocol.comment("==============================================") + protocol.comment("Adding Diluent Sample Plate 3") + protocol.comment("==============================================") + + current = 0 + # Probe heights + helpers.find_liquid_height_of_all_wells(protocol, p1000, wells) diff --git a/abr-testing/abr_testing/protocols/active_protocols/2_BMS_PCR_Protocol.py b/abr-testing/abr_testing/protocols/active_protocols/2_BMS_PCR_Protocol.py new file mode 100644 index 00000000000..24e7358f6e1 --- /dev/null +++ b/abr-testing/abr_testing/protocols/active_protocols/2_BMS_PCR_Protocol.py @@ -0,0 +1,213 @@ +"""BMS PCR Protocol.""" + +from opentrons.protocol_api import ParameterContext, ProtocolContext, Labware +from opentrons.protocol_api.module_contexts import ( + ThermocyclerContext, + TemperatureModuleContext, +) +from opentrons.protocol_api import SINGLE, Well +from abr_testing.protocols import helpers +from typing import List, Dict + + +metadata = { + "protocolName": "PCR Protocol with TC Auto Sealing Lid", + "author": "Rami Farawi None: + """Parameters.""" + helpers.create_single_pipette_mount_parameter(parameters) + helpers.create_disposable_lid_parameter(parameters) + helpers.create_csv_parameter(parameters) + helpers.create_tc_lid_deck_riser_parameter(parameters) + + +def run(ctx: ProtocolContext) -> None: + """Protocol.""" + pipette_mount = ctx.params.pipette_mount # type: ignore[attr-defined] + disposable_lid = ctx.params.disposable_lid # type: ignore[attr-defined] + parsed_csv = ctx.params.parameters_csv.parse_as_csv() # type: ignore[attr-defined] + deck_riser = ctx.params.deck_riser # type: ignore[attr-defined] + + rxn_vol = 50 + real_mode = True + # DECK SETUP AND LABWARE + + tc_mod: ThermocyclerContext = ctx.load_module( + helpers.tc_str + ) # type: ignore[assignment] + + tc_mod.open_lid() + tc_mod.set_lid_temperature(105) + temp_mod: TemperatureModuleContext = ctx.load_module( + helpers.temp_str, location="D3" + ) # type: ignore[assignment] + reagent_rack = temp_mod.load_labware( + "opentrons_24_aluminumblock_nest_1.5ml_snapcap" + ) # check if 2mL + + dest_plate = tc_mod.load_labware( + "opentrons_96_wellplate_200ul_pcr_full_skirt" + ) # do I change this to tough plate if they run pcr? + + source_plate = ctx.load_labware( + "opentrons_96_wellplate_200ul_pcr_full_skirt", location="D1" + ) # do I change this to their plate? + + tiprack_50 = [ + ctx.load_labware("opentrons_flex_96_tiprack_50ul", slot) for slot in [8, 9] + ] + + # Opentrons tough pcr auto sealing lids + if disposable_lid: + unused_lids = helpers.load_disposable_lids(ctx, 3, ["C3"], deck_riser) + used_lids: List[Labware] = [] + + # LOAD PIPETTES + p50 = ctx.load_instrument( + "flex_8channel_50", + pipette_mount, + tip_racks=tiprack_50, + liquid_presence_detection=True, + ) + p50.configure_nozzle_layout(style=SINGLE, start="A1", tip_racks=tiprack_50) + ctx.load_trash_bin("A3") + + temp_mod.set_temperature(4) + + # LOAD LIQUIDS + water: Well = reagent_rack["B1"] + mmx_pic: List[Well] = reagent_rack.rows()[0] + dna_pic: List[Well] = source_plate.wells() + liquid_vols_and_wells: Dict[str, List[Dict[str, Well | List[Well] | float]]] = { + "Water": [{"well": water, "volume": 1500.0}], + "Mastermix": [{"well": mmx_pic, "volume": 1500.0}], + "DNA": [{"well": dna_pic, "volume": 50.0}], + } + helpers.load_wells_with_custom_liquids(ctx, liquid_vols_and_wells) + wells_to_probe = [[water], mmx_pic, dna_pic] + wells_to_probe_flattened = [ + well for list_of_wells in wells_to_probe for well in list_of_wells + ] + helpers.find_liquid_height_of_all_wells(ctx, p50, wells_to_probe_flattened) + # adding water + ctx.comment("\n\n----------ADDING WATER----------\n") + p50.pick_up_tip() + # p50.aspirate(40, water) # prewet + # p50.dispense(40, water) + parsed_csv = parsed_csv[1:] + num_of_rows = len(parsed_csv) + for row_index in range(num_of_rows): + row_values = parsed_csv[row_index] + water_vol = row_values[1] + if water_vol.lower() == "x": + continue + water_vol = int(water_vol) + dest_well = row_values[0] + if water_vol == 0: + break + + p50.configure_for_volume(water_vol) + p50.aspirate(water_vol, water) + p50.dispense(water_vol, dest_plate[dest_well], rate=0.5) + p50.configure_for_volume(50) + # p50.blow_out() + p50.drop_tip() + + # adding Mastermix + ctx.comment("\n\n----------ADDING MASTERMIX----------\n") + for i, row in enumerate(parsed_csv): + p50.pick_up_tip() + mmx_vol = row[3] + if mmx_vol.lower() == "x": + continue + + if i == 0: + mmx_tube = row[4] + mmx_tube_check = mmx_tube + mmx_tube = row[4] + if mmx_tube_check != mmx_tube: + + p50.drop_tip() + p50.pick_up_tip() + + if not p50.has_tip: + p50.pick_up_tip() + + mmx_vol = int(row[3]) + dest_well = row[0] + + if mmx_vol == 0: + break + p50.configure_for_volume(mmx_vol) + p50.aspirate(mmx_vol, reagent_rack[mmx_tube]) + p50.dispense(mmx_vol, dest_plate[dest_well].top()) + ctx.delay(seconds=2) + p50.blow_out() + p50.touch_tip() + p50.configure_for_volume(50) + p50.drop_tip() + if p50.has_tip: + p50.drop_tip() + + # adding DNA + ctx.comment("\n\n----------ADDING DNA----------\n") + for row in parsed_csv: + dna_vol = row[2] + if dna_vol.lower() == "x": + continue + + p50.pick_up_tip() + + dna_vol = int(row[2]) + dest_and_source_well = row[0] + + if dna_vol == 0: + break + p50.configure_for_volume(dna_vol) + p50.aspirate(dna_vol, source_plate[dest_and_source_well]) + p50.dispense(dna_vol, dest_plate[dest_and_source_well], rate=0.5) + + p50.mix( + 10, + 0.7 * rxn_vol if 0.7 * rxn_vol < 30 else 30, + dest_plate[dest_and_source_well], + ) + p50.drop_tip() + p50.configure_for_volume(50) + wells_to_probe_flattened.append(dest_plate[dest_well]) + + ctx.comment("\n\n-----------Running PCR------------\n") + + if real_mode: + if disposable_lid: + lid_on_plate, unused_lids, used_lids = helpers.use_disposable_lid_with_tc( + ctx, unused_lids, used_lids, dest_plate, tc_mod + ) + else: + tc_mod.close_lid() + helpers.perform_pcr( + ctx, + tc_mod, + initial_denature_time_sec=120, + denaturation_time_sec=10, + anneal_time_sec=10, + extension_time_sec=30, + cycle_repetitions=30, + final_extension_time_min=5, + ) + + tc_mod.set_block_temperature(4) + + tc_mod.open_lid() + if disposable_lid: + if len(used_lids) <= 1: + ctx.move_labware(lid_on_plate, "C2", use_gripper=True) + else: + ctx.move_labware(lid_on_plate, used_lids[-2], use_gripper=True) + p50.drop_tip() + p50.configure_nozzle_layout(style=SINGLE, start="A1", tip_racks=tiprack_50) + helpers.find_liquid_height_of_all_wells(ctx, p50, wells_to_probe_flattened) diff --git a/abr-testing/abr_testing/protocols/active_protocols/3_Tartrazine Protocol.py b/abr-testing/abr_testing/protocols/active_protocols/3_Tartrazine Protocol.py new file mode 100644 index 00000000000..66db85468f4 --- /dev/null +++ b/abr-testing/abr_testing/protocols/active_protocols/3_Tartrazine Protocol.py @@ -0,0 +1,155 @@ +"""Tartrazine Protocol.""" +from opentrons.protocol_api import ProtocolContext, ParameterContext, Well +from abr_testing.protocols import helpers +from opentrons.protocol_api.module_contexts import ( + AbsorbanceReaderContext, + HeaterShakerContext, +) +from datetime import datetime +from typing import Dict, List +import statistics + +metadata = { + "protocolName": "Tartrazine Protocol", + "author": "Opentrons ", + "source": "Protocol Library", +} + +requirements = {"robotType": "Flex", "apiLevel": "2.21"} + + +def add_parameters(parameters: ParameterContext) -> None: + """Add parameters.""" + parameters.add_int( + variable_name="number_of_plates", + display_name="Number of Plates", + default=4, + minimum=1, + maximum=4, + ) + + +def run(ctx: ProtocolContext) -> None: + """Protocol.""" + number_of_plates = ctx.params.number_of_plates # type: ignore [attr-defined] + # Plate Reader + plate_reader: AbsorbanceReaderContext = ctx.load_module( + helpers.abs_mod_str, "A3" + ) # type: ignore[assignment] + hs: HeaterShakerContext = ctx.load_module(helpers.hs_str, "A1") # type: ignore[assignment] + hs_adapter = hs.load_adapter("opentrons_universal_flat_adapter") + tube_rack = ctx.load_labware( + "opentrons_10_tuberack_nest_4x50ml_6x15ml_conical", "C2", "Reagent Tube" + ) + tartrazine_tube = tube_rack["A3"] + water_tube_1 = tube_rack["A4"] + water_tube_2 = tube_rack["B3"] + sample_plate_1 = ctx.load_labware( + "corning_96_wellplate_360ul_flat", "D1", "Sample Plate 1" + ) + sample_plate_2 = ctx.load_labware( + "corning_96_wellplate_360ul_flat", "D2", "Sample Plate 2" + ) + sample_plate_3 = ctx.load_labware( + "corning_96_wellplate_360ul_flat", "C1", "Sample Plate 3" + ) + sample_plate_4 = ctx.load_labware( + "corning_96_wellplate_360ul_flat", "B1", "Sample Plate 4" + ) + + sample_plate_list = [sample_plate_1, sample_plate_2, sample_plate_3, sample_plate_4] + tiprack_50_1 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "D3") + tiprack_50_2 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "C3") + tiprack_50_3 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "B3") + tiprack_1000_1 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2") + tip_racks = [tiprack_50_1, tiprack_50_2, tiprack_50_3] + + # Pipette + p50 = ctx.load_instrument("flex_1channel_50", "left", tip_racks=tip_racks) + p1000 = ctx.load_instrument( + "flex_1channel_1000", "right", tip_racks=[tiprack_1000_1] + ) + + # Probe wells + liquid_vols_and_wells: Dict[str, List[Dict[str, Well | List[Well] | float]]] = { + "Tartrazine": [{"well": tartrazine_tube, "volume": 45.0}], + "Water": [{"well": [water_tube_1, water_tube_2], "volume": 45.0}], + } + helpers.find_liquid_height_of_loaded_liquids(ctx, liquid_vols_and_wells, p50) + + i = 0 + all_percent_error_dict = {} + cv_dict = {} + vol = 0.0 + tip_count = 0 + for sample_plate in sample_plate_list[:number_of_plates]: + deck_locations = ["D1", "D2", "C1", "B1"] + p1000.pick_up_tip() + for well in sample_plate.wells(): + if vol < 45000: + tube_of_choice = water_tube_1 + else: + tube_of_choice = water_tube_2 + p50.pick_up_tip() + p1000.aspirate(190, tube_of_choice) + p1000.air_gap(5) + p1000.dispense(5, well.top()) + p1000.dispense(190, well) + vol += 190 + height = helpers.find_liquid_height(p50, tartrazine_tube) + p50.aspirate(10, tartrazine_tube.bottom(z=height)) + p50.air_gap(5) + p50.dispense(5, well.top()) + p50.dispense(10, well.bottom(z=0.5)) + p50.blow_out() + p50.return_tip() + tip_count += 1 + if tip_count >= (96 * 3): + p50.reset_tipracks() + p1000.return_tip() + helpers.move_labware_to_hs(ctx, sample_plate, hs, hs_adapter) + helpers.set_hs_speed(ctx, hs, 1500, 2.0, True) + hs.open_labware_latch() + plate_reader.close_lid() + plate_reader.initialize("single", [450]) + plate_reader.open_lid() + ctx.move_labware(sample_plate, plate_reader, use_gripper=True) + sample_plate_name = "sample plate_" + str(i + 1) + csv_string = sample_plate_name + "_" + str(datetime.now()) + plate_reader.close_lid() + result = plate_reader.read(csv_string) + for wavelength in result: + dict_of_wells = result[wavelength] + readings_and_wells = dict_of_wells.items() + readings = dict_of_wells.values() + avg = statistics.mean(readings) + # Check if every average is within +/- 5% of 2.85 + percent_error_dict = {} + percent_error_sum = 0.0 + for reading in readings_and_wells: + well_name = str(reading[0]) + measurement = reading[1] + percent_error = (measurement - 2.85) / 2.85 * 100 + percent_error_dict[well_name] = percent_error + percent_error_sum += percent_error + avg_percent_error = percent_error_sum / 96.0 + standard_deviation = statistics.stdev(readings) + try: + cv = standard_deviation / avg + except ZeroDivisionError: + cv = 0.0 + cv_percent = cv * 100 + cv_dict[sample_plate_name] = { + "CV": cv_percent, + "Mean": avg, + "SD": standard_deviation, + "Avg Percent Error": avg_percent_error, + } + all_percent_error_dict[sample_plate_name] = percent_error_dict + plate_reader.open_lid() + ctx.move_labware(sample_plate, deck_locations[i], use_gripper=True) + i += 1 + # Print percent error dictionary + ctx.comment("Percent Error: " + str(all_percent_error_dict)) + # Print cv dictionary + ctx.comment("Plate Reader result: " + str(cv_dict)) diff --git a/abr-testing/abr_testing/protocols/active_protocols/5_96ch complex protocol with single tip Pick Up.py b/abr-testing/abr_testing/protocols/active_protocols/5_96ch complex protocol with single tip Pick Up.py new file mode 100644 index 00000000000..dc40db7f177 --- /dev/null +++ b/abr-testing/abr_testing/protocols/active_protocols/5_96ch complex protocol with single tip Pick Up.py @@ -0,0 +1,412 @@ +"""96 ch Test Single Tip and Gripper Moves.""" +from opentrons.protocol_api import ( + ALL, + SINGLE, + ParameterContext, + ProtocolContext, + Labware, +) +from opentrons.protocol_api.module_contexts import ( + HeaterShakerContext, + MagneticBlockContext, + ThermocyclerContext, + TemperatureModuleContext, +) +from abr_testing.protocols import helpers +from typing import List + +metadata = { + "protocolName": "96ch protocol with modules gripper moves and SINGLE tip pickup", + "author": "Derek Maggio ", +} + +requirements = { + "robotType": "OT-3", + "apiLevel": "2.21", +} + + +# prefer to move off deck, instead of waste chute disposal, if possible +PREFER_MOVE_OFF_DECK = False + + +PCR_PLATE_96_NAME = "armadillo_96_wellplate_200ul_pcr_full_skirt" +RESERVOIR_NAME = "nest_96_wellplate_2ml_deep" +TIPRACK_96_ADAPTER_NAME = "opentrons_flex_96_tiprack_adapter" +PIPETTE_96_CHANNEL_NAME = "flex_96channel_1000" + +USING_GRIPPER = True +RESET_AFTER_EACH_MOVE = True + + +def add_parameters(parameters: ParameterContext) -> None: + """Parameters.""" + helpers.create_tip_size_parameter(parameters) + helpers.create_dot_bottom_parameter(parameters) + helpers.create_disposable_lid_parameter(parameters) + helpers.create_tc_lid_deck_riser_parameter(parameters) + + +def run(ctx: ProtocolContext) -> None: + """Protocol.""" + b = ctx.params.dot_bottom # type: ignore[attr-defined] + TIPRACK_96_NAME = ctx.params.tip_size # type: ignore[attr-defined] + disposable_lid = ctx.params.disposable_lid # type: ignore[attr-defined] + deck_riser = ctx.params.deck_riser # type: ignore[attr-defined] + + waste_chute = ctx.load_waste_chute() + + thermocycler: ThermocyclerContext = ctx.load_module(helpers.tc_str) # type: ignore[assignment] + mag: MagneticBlockContext = ctx.load_module(helpers.mag_str, "A3") # type: ignore[assignment] + h_s: HeaterShakerContext = ctx.load_module(helpers.hs_str, "D1") # type: ignore[assignment] + temperature_module: TemperatureModuleContext = ctx.load_module( + helpers.temp_str, "C1" + ) # type: ignore[assignment] + if disposable_lid: + unused_lids = helpers.load_disposable_lids(ctx, 3, ["A4"], deck_riser) + used_lids: List[Labware] = [] + thermocycler.open_lid() + h_s.open_labware_latch() + + temperature_module_adapter = temperature_module.load_adapter( + "opentrons_96_well_aluminum_block" + ) + h_s_adapter = h_s.load_adapter("opentrons_96_pcr_adapter") + + adapters = [temperature_module_adapter, h_s_adapter] + + source_reservoir = ctx.load_labware(RESERVOIR_NAME, "D2") + dest_pcr_plate = ctx.load_labware(PCR_PLATE_96_NAME, "C2") + + tip_rack_1 = ctx.load_labware( + TIPRACK_96_NAME, "A2", adapter=TIPRACK_96_ADAPTER_NAME + ) + tip_rack_adapter = tip_rack_1.parent + + tip_rack_2 = ctx.load_labware(TIPRACK_96_NAME, "C3") + tip_rack_3 = ctx.load_labware(TIPRACK_96_NAME, "C4") + + tip_racks = [ + tip_rack_1, + tip_rack_2, + tip_rack_3, + ] + + pipette_96_channel = ctx.load_instrument( + PIPETTE_96_CHANNEL_NAME, + mount="left", + tip_racks=tip_racks, + liquid_presence_detection=True, + ) + + water = ctx.define_liquid(name="water", description="H₂O", display_color="#42AB2D") + source_reservoir.wells_by_name()["A1"].load_liquid(liquid=water, volume=29000) + + def run_moves( + labware: Labware, move_sequences: List, reset_location: str, use_gripper: bool + ) -> None: + """Perform a series of moves for a given labware using specified move sequences. + + Will perform 2 versions of the moves: + 1.Moves to each location, resetting to the reset location after each move. + 2.Moves to each location, resetting to the reset location after all moves. + """ + + def move_to_locations( + labware_to_move: Labware, + move_locations: List, + reset_after_each_move: bool, + use_gripper: bool, + reset_location: str, + ) -> None: + """Move labware to specific destinations.""" + + def reset_labware() -> None: + """Reset the labware to the reset location.""" + ctx.move_labware( + labware_to_move, reset_location, use_gripper=use_gripper + ) + + if len(move_locations) == 0: + return + + for location in move_locations: + ctx.move_labware(labware_to_move, location, use_gripper=use_gripper) + + if reset_after_each_move: + reset_labware() + + if not reset_after_each_move: + reset_labware() + + for move_sequence in move_sequences: + move_to_locations( + labware, + move_sequence, + RESET_AFTER_EACH_MOVE, + use_gripper, + reset_location, + ) + move_to_locations( + labware, + move_sequence, + not RESET_AFTER_EACH_MOVE, + use_gripper, + reset_location, + ) + + def test_gripper_moves() -> None: + """Function to test the movement of the gripper in various locations.""" + + def deck_moves(labware: Labware, reset_location: str) -> None: + """Function to perform the movement of labware.""" + deck_move_sequence = [ + ["B2"], # Deck Moves + ["C3"], # Staging Area Slot 3 Moves + ["C4", "D4"], # Staging Area Slot 4 Moves + [ + thermocycler, + temperature_module_adapter, + h_s_adapter, + mag, + ], # Module Moves + ] + + run_moves(labware, deck_move_sequence, reset_location, USING_GRIPPER) + + def staging_area_slot_3_moves(labware: Labware, reset_location: str) -> None: + """Function to perform the movement of labware, starting w/ staging area slot 3.""" + staging_area_slot_3_move_sequence = [ + ["B2", "C2"], # Deck Moves + [], # Don't have Staging Area Slot 3 open + ["C4", "D4"], # Staging Area Slot 4 Moves + [ + thermocycler, + temperature_module_adapter, + h_s_adapter, + mag, + ], # Module Moves + ] + + run_moves( + labware, + staging_area_slot_3_move_sequence, + reset_location, + USING_GRIPPER, + ) + + def staging_area_slot_4_moves(labware: Labware, reset_location: str) -> None: + """Function to perform the movement of labware, starting with staging area slot 4.""" + staging_area_slot_4_move_sequence = [ + ["C2", "B2"], # Deck Moves + ["C3"], # Staging Area Slot 3 Moves + ["C4"], # Staging Area Slot 4 Moves + [ + thermocycler, + temperature_module_adapter, + h_s_adapter, + mag, + ], # Module Moves + ] + + run_moves( + labware, + staging_area_slot_4_move_sequence, + reset_location, + USING_GRIPPER, + ) + + def module_moves(labware: Labware, module_locations: List) -> None: + """Function to perform the movement of labware, starting on a module.""" + module_move_sequence = [ + ["C2", "B2"], # Deck Moves + ["C3"], # Staging Area Slot 3 Moves + ["C4", "D4"], # Staging Area Slot 4 Moves + ] + + for module_starting_location in module_locations: + labware_move_to_locations = module_locations.copy() + labware_move_to_locations.remove(module_starting_location) + all_sequences = module_move_sequence.copy() + all_sequences.append(labware_move_to_locations) + ctx.move_labware( + labware, module_starting_location, use_gripper=USING_GRIPPER + ) + run_moves( + labware, all_sequences, module_starting_location, USING_GRIPPER + ) + + DECK_MOVE_RESET_LOCATION = "C2" + STAGING_AREA_SLOT_3_RESET_LOCATION = "C3" + STAGING_AREA_SLOT_4_RESET_LOCATION = "D4" + + deck_moves(dest_pcr_plate, DECK_MOVE_RESET_LOCATION) + + ctx.move_labware( + dest_pcr_plate, + STAGING_AREA_SLOT_3_RESET_LOCATION, + use_gripper=USING_GRIPPER, + ) + staging_area_slot_3_moves(dest_pcr_plate, STAGING_AREA_SLOT_3_RESET_LOCATION) + + ctx.move_labware( + dest_pcr_plate, + STAGING_AREA_SLOT_4_RESET_LOCATION, + use_gripper=USING_GRIPPER, + ) + staging_area_slot_4_moves(dest_pcr_plate, STAGING_AREA_SLOT_4_RESET_LOCATION) + + module_locations = [thermocycler, mag] + adapters + module_moves(dest_pcr_plate, module_locations) + ctx.move_labware(dest_pcr_plate, thermocycler, use_gripper=USING_GRIPPER) + + def test_manual_moves() -> None: + """Test manual moves.""" + ctx.move_labware(source_reservoir, "D4", use_gripper=USING_GRIPPER) + + def test_pipetting() -> None: + """Test pipetting.""" + + def test_single_tip_pickup_usage() -> None: + """Test Single Tip Pick Up.""" + pipette_96_channel.configure_nozzle_layout(style=SINGLE, start="H12") + pipette_96_channel.liquid_presence_detection = True + tip_count = 0 # Tip counter to ensure proper tip usage + rows = ["A", "B", "C", "D", "E", "F", "G", "H"] # 8 rows + columns = range(1, 13) # 12 columns + for row in rows: + for col in columns: + well_position = f"{row}{col}" + pipette_96_channel.pick_up_tip(tip_rack_2) + + pipette_96_channel.aspirate(5, source_reservoir[well_position]) + pipette_96_channel.touch_tip() + + pipette_96_channel.dispense( + 5, dest_pcr_plate[well_position].bottom(b) + ) + pipette_96_channel.drop_tip() + tip_count += 1 + # leave this dropping in waste chute, do not use get_disposal_preference + # want to test partial drop + ctx.move_labware(tip_rack_2, waste_chute, use_gripper=USING_GRIPPER) + + def test_full_tip_rack_usage() -> None: + """Full Tip Pick Up.""" + pipette_96_channel.configure_nozzle_layout(style=ALL, start="A1") + pipette_96_channel.liquid_presence_detection = True + pipette_96_channel.pick_up_tip(tip_rack_1["A1"]) + + pipette_96_channel.aspirate(5, source_reservoir["A1"]) + pipette_96_channel.touch_tip() + + pipette_96_channel.liquid_presence_detection = False + pipette_96_channel.air_gap(height=30) + pipette_96_channel.blow_out(waste_chute) + + pipette_96_channel.aspirate(5, source_reservoir["A1"]) + pipette_96_channel.touch_tip() + + pipette_96_channel.air_gap(height=30) + pipette_96_channel.blow_out() + + pipette_96_channel.aspirate(10, source_reservoir["A1"]) + pipette_96_channel.touch_tip() + + pipette_96_channel.dispense(10, dest_pcr_plate["A1"].bottom(b)) + pipette_96_channel.mix(repetitions=5, volume=15) + pipette_96_channel.return_tip() + + ctx.move_labware(tip_rack_1, waste_chute, use_gripper=USING_GRIPPER) + ctx.move_labware(tip_rack_3, tip_rack_adapter, use_gripper=USING_GRIPPER) + + pipette_96_channel.pick_up_tip(tip_rack_3["A1"]) + pipette_96_channel.transfer( + volume=10, + source=source_reservoir["A1"], + dest=dest_pcr_plate["A1"], + new_tip="never", + touch_tip=True, + blow_out=True, + blowout_location="trash", + mix_before=(3, 5), + mix_after=(1, 5), + ) + pipette_96_channel.return_tip() + + ctx.move_labware(tip_rack_3, waste_chute, use_gripper=USING_GRIPPER) + + test_single_tip_pickup_usage() + test_full_tip_rack_usage() + + def test_module_usage(unused_lids: List[Labware], used_lids: List[Labware]) -> None: + """Test Module Use.""" + + def test_thermocycler( + unused_lids: List[Labware], used_lids: List[Labware] + ) -> None: + if disposable_lid: + ( + lid_on_plate, + unused_lids, + used_lids, + ) = helpers.use_disposable_lid_with_tc( + ctx, unused_lids, used_lids, dest_pcr_plate, thermocycler + ) + thermocycler.set_block_temperature(4) + thermocycler.set_lid_temperature(105) + # Close lid + thermocycler.close_lid() + helpers.perform_pcr( + ctx, + thermocycler, + initial_denature_time_sec=45, + denaturation_time_sec=30, + anneal_time_sec=30, + extension_time_sec=10, + cycle_repetitions=30, + final_extension_time_min=5, + ) + # Cool to 4° + thermocycler.set_block_temperature(4) + thermocycler.set_lid_temperature(105) + # Open lid + thermocycler.open_lid() + if disposable_lid: + if len(used_lids) <= 1: + ctx.move_labware(lid_on_plate, "B3", use_gripper=True) + else: + ctx.move_labware(lid_on_plate, used_lids[-2], use_gripper=True) + thermocycler.deactivate() + + def test_h_s() -> None: + """Tests heatershaker.""" + h_s.open_labware_latch() + h_s.close_labware_latch() + + h_s.set_target_temperature(75.0) + h_s.set_and_wait_for_shake_speed(1000) + h_s.wait_for_temperature() + + h_s.deactivate_heater() + h_s.deactivate_shaker() + + def test_temperature_module() -> None: + """Tests temperature module.""" + temperature_module.set_temperature(80) + temperature_module.set_temperature(10) + temperature_module.deactivate() + + def test_mag() -> None: + """Tests magnetic block.""" + pass + + test_thermocycler(unused_lids, used_lids) + test_h_s() + test_temperature_module() + test_mag() + + test_pipetting() + test_gripper_moves() + test_module_usage(unused_lids, used_lids) + test_manual_moves() diff --git a/abr-testing/abr_testing/protocols/active_protocols/7_HDQ_DNA_Bacteria_Flex.py b/abr-testing/abr_testing/protocols/active_protocols/7_HDQ_DNA_Bacteria_Flex.py new file mode 100644 index 00000000000..aa33079f553 --- /dev/null +++ b/abr-testing/abr_testing/protocols/active_protocols/7_HDQ_DNA_Bacteria_Flex.py @@ -0,0 +1,510 @@ +"""Omega HDQ DNA Extraction: Bacteria - Tissue Protocol.""" +from abr_testing.protocols import helpers +import math +from opentrons import types +from opentrons.protocol_api import ( + ProtocolContext, + Well, + ParameterContext, + InstrumentContext, +) +import numpy as np +from opentrons.protocol_api.module_contexts import ( + HeaterShakerContext, + TemperatureModuleContext, + MagneticBlockContext, +) +from typing import List, Dict + +metadata = { + "author": "Zach Galluzzo ", + "protocolName": "Omega HDQ DNA Extraction: Bacteria- Tissue Protocol", +} + +requirements = { + "robotType": "OT-3", + "apiLevel": "2.21", +} +""" +Slot A1: Tips 1000 +Slot A2: Tips 1000 +Slot A3: Temperature module (gen2) with 96 well PCR block and Armadillo 96 well PCR Plate +Slot B1: Tips 1000 +Slot B2: +Slot B3: Nest 1 Well Reservoir +Slot C1: Magblock +Slot C2: +Slot C3: +Slot D1: H-S with Nest 96 Well Deep well and DW Adapter +Slot D2: Nest 12 well 15 ml Reservoir +Slot D3: Trash + +Reservoir 1: +Wells 1-2 - 9,900 ul +Well 3 - 14,310 ul +Wells 4-12 - 11,400 ul +""" + +whichwash = 1 +sample_max = 48 +tip1k = 0 +drop_count = 0 +waste_vol = 0 + + +def add_parameters(parameters: ParameterContext) -> None: + """Define Parameters.""" + helpers.create_single_pipette_mount_parameter(parameters) + helpers.create_hs_speed_parameter(parameters) + helpers.create_dot_bottom_parameter(parameters) + + +def run(ctx: ProtocolContext) -> None: + """Protocol.""" + heater_shaker_speed = ctx.params.heater_shaker_speed # type: ignore[attr-defined] + mount = ctx.params.pipette_mount # type: ignore[attr-defined] + dot_bottom = ctx.params.dot_bottom # type: ignore[attr-defined] + dry_run = False + TIP_TRASH = False + res_type = "nest_12_reservoir_22ml" + + num_samples = 8 + wash1_vol = 600.0 + wash2_vol = 600.0 + wash3_vol = 600.0 + AL_vol = 230.0 + sample_vol = 180.0 + bind_vol = 320.0 + elution_vol = 100.0 + + # Protocol Parameters + deepwell_type = "nest_96_wellplate_2ml_deep" + res_type = "nest_12_reservoir_15ml" + if not dry_run: + settling_time = 2.0 + A_lysis_time_1 = 15.0 + A_lysis_time_2 = 10.0 + bind_time = 10.0 + elute_wash_time = 5.0 + else: + settling_time = ( + elute_wash_time + ) = A_lysis_time_1 = A_lysis_time_2 = bind_time = 0.25 + PK_vol = bead_vol = 20 + AL_total_vol = AL_vol + PK_vol + starting_vol = AL_vol + sample_vol + binding_buffer_vol = bind_vol + bead_vol + + ctx.load_trash_bin("A3") + h_s: HeaterShakerContext = ctx.load_module(helpers.hs_str, "D1") # type: ignore[assignment] + sample_plate, h_s_adapter = helpers.load_hs_adapter_and_labware( + deepwell_type, h_s, "Sample Plate" + ) + h_s.close_labware_latch() + temp: TemperatureModuleContext = ctx.load_module( + helpers.temp_str, "D3" + ) # type: ignore[assignment] + elutionplate, temp_adapter = helpers.load_temp_adapter_and_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", temp, "Elution Plate" + ) + magnetic_block: MagneticBlockContext = ctx.load_module( + helpers.mag_str, "C1" + ) # type: ignore[assignment] + waste_reservoir = ctx.load_labware("nest_1_reservoir_195ml", "B3", "Liquid Waste") + waste = waste_reservoir.wells()[0].top() + + res1 = ctx.load_labware(res_type, "D2", "Reagent Reservoir 1") + num_cols = math.ceil(num_samples / 8) + + # Load tips and combine all similar boxes + tips1000 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A1", "Tips 1") + tips1001 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "A2", "Tips 2") + tips1002 = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B1", "Tips 3") + tips = [*tips1000.wells()[num_samples:96], *tips1001.wells(), *tips1002.wells()] + tips_sn = tips1000.wells()[:num_samples] + + # load instruments + m1000 = ctx.load_instrument( + "flex_8channel_1000", mount, tip_racks=[tips1000, tips1001, tips1002] + ) + + """ + Here is where you can define the locations of your reagents. + """ + binding_buffer = res1.wells()[:2] + AL = res1.wells()[2] + wash1 = res1.wells()[3:6] + wash2 = res1.wells()[6:9] + wash3 = res1.wells()[9:] + + samples_m = sample_plate.rows()[0][:num_cols] + elution_samples_m = elutionplate.rows()[0][:num_cols] + + # Probe wells + liquid_vols_and_wells: Dict[str, List[Dict[str, Well | List[Well] | float]]] = { + "AL Lysis": [{"well": AL, "volume": AL_vol}], + "PK": [{"well": AL, "volume": PK_vol}], + "Beads": [{"well": binding_buffer, "volume": bead_vol}], + "Binding": [{"well": binding_buffer, "volume": bind_vol}], + "Wash 1": [{"well": wash1, "volume": wash1_vol}], + "Wash 2": [{"well": wash2, "volume": wash2_vol}], + "Wash 3": [{"well": wash3, "volume": wash3_vol}], + "Samples": [{"well": sample_plate.wells()[:num_samples], "volume": sample_vol}], + "Elution Buffer": [ + {"well": elutionplate.wells()[:num_samples], "volume": elution_vol} + ], + } + + m1000.flow_rate.aspirate = 300 + m1000.flow_rate.dispense = 300 + m1000.flow_rate.blow_out = 300 + helpers.find_liquid_height_of_loaded_liquids(ctx, liquid_vols_and_wells, m1000) + + def tiptrack(tipbox: List[Well]) -> None: + """Track Tips.""" + global tip1k + global drop_count + if tipbox == tips: + m1000.pick_up_tip(tipbox[int(tip1k)]) + tip1k = tip1k + 8 + drop_count = drop_count + 8 + if drop_count >= 150: + drop_count = 0 + ctx.pause("Empty Waste Bin.") + + def remove_supernatant(vol: float) -> None: + """Remove supernatants.""" + ctx.comment("-----Removing Supernatant-----") + m1000.flow_rate.aspirate = 150 + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + + for i, m in enumerate(samples_m): + m1000.pick_up_tip(tips_sn[8 * i]) + loc = m.bottom(dot_bottom) + for _ in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, m.top()) + m1000.move_to(m.center()) + m1000.transfer(vol_per_trans, loc, waste, new_tip="never", air_gap=20) + m1000.blow_out(waste) + m1000.air_gap(20) + m1000.drop_tip(tips_sn[8 * i]) if TIP_TRASH else m1000.return_tip() + m1000.flow_rate.aspirate = 300 + helpers.move_labware_to_hs(ctx, sample_plate, h_s, h_s_adapter) + + def bead_mixing( + well: Well, pip: InstrumentContext, mvol: float, reps: int = 8 + ) -> None: + """Bead Mixing. + + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top().move(types.Point(x=0, y=0, z=5)) + aspbot = well.bottom().move(types.Point(x=0, y=2, z=1)) + asptop = well.bottom().move(types.Point(x=0, y=-2, z=2.5)) + disbot = well.bottom().move(types.Point(x=0, y=1.5, z=3)) + distop = well.top().move(types.Point(x=0, y=1.5, z=0)) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, aspbot) + pip.dispense(vol, distop) + pip.aspirate(vol, asptop) + pip.dispense(vol, disbot) + if _ == reps - 1: + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 100 + pip.aspirate(vol, aspbot) + pip.dispense(vol, aspbot) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def mixing(well: Well, pip: InstrumentContext, mvol: float, reps: int = 8) -> None: + """Mixing. + + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top(5) + asp = well.bottom(1) + disp = well.top(-8) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + if _ == reps - 1: + pip.flow_rate.aspirate = 150 + pip.flow_rate.dispense = 100 + pip.aspirate(vol, asp) + pip.dispense(vol, asp) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def A_lysis(vol: float, source: Well) -> None: + """A Lysis.""" + ctx.comment("-----Mixing then transferring AL buffer-----") + num_transfers = math.ceil(vol / 980) + tiptrack(tips) + for i in range(num_cols): + if num_cols >= 5: + if i == 0: + height = 10 + else: + height = 1 + else: + height = 1 + src = source + tvol = vol / num_transfers + for t in range(num_transfers): + if i == 0 and t == 0: + for _ in range(3): + m1000.require_liquid_presence(src) + m1000.aspirate(tvol, src.bottom(1)) + m1000.dispense(tvol, src.bottom(4)) + m1000.require_liquid_presence(src) + m1000.aspirate(tvol, src.bottom(height)) + m1000.air_gap(10) + m1000.dispense(m1000.current_volume, samples_m[i].top()) + m1000.air_gap(20) + + for i in range(num_cols): + if i != 0: + tiptrack(tips) + mixing( + samples_m[i], m1000, tvol - 40, reps=10 if not dry_run else 1 + ) # vol is 250 AL + 180 sample + m1000.air_gap(20) + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + + ctx.comment("-----Mixing then Heating AL and Sample-----") + + helpers.set_hs_speed(ctx, h_s, heater_shaker_speed, A_lysis_time_1, False) + if not dry_run: + h_s.set_and_wait_for_temperature(55) + ctx.delay( + minutes=A_lysis_time_2, + msg="Incubating at 55C " + + str(heater_shaker_speed) + + " rpm for 10 minutes.", + ) + h_s.deactivate_shaker() + + def bind(vol: float) -> None: + """Bind. + + `bind` will perform magnetic bead binding on each sample in the + deepwell plate. Each channel of binding beads will be mixed before + transfer, and the samples will be mixed with the binding beads after + the transfer. The magnetic deck activates after the addition to all + samples, and the supernatant is removed after bead bining. + :param vol (float): The amount of volume to aspirate from the elution + buffer source and dispense to each well containing + beads. + :param park (boolean): Whether to save sample-corresponding tips + between adding elution buffer and transferring + supernatant to the final clean elutions PCR + plate. + """ + ctx.comment("-----Beginning Bind Steps-----") + tiptrack(tips) + for i, well in enumerate(samples_m): + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + source = binding_buffer[i // 3] + if i == 0: + reps = 6 if not dry_run else 1 + else: + reps = 1 + ctx.comment("-----Mixing Beads in Reservoir-----") + bead_mixing(source, m1000, vol_per_trans, reps=reps if not dry_run else 1) + # Transfer beads and binding from source to H-S plate + for t in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, source.top()) + m1000.transfer( + vol_per_trans, source, well.top(), air_gap=20, new_tip="never" + ) + if t < num_trans - 1: + m1000.air_gap(20) + + ctx.comment("-----Mixing Beads in Plate-----") + for i in range(num_cols): + if i != 0: + tiptrack(tips) + mixing( + samples_m[i], m1000, vol + starting_vol, reps=10 if not dry_run else 1 + ) + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + + ctx.comment("-----Incubating Beads and Bind on H-S-----") + + speed_val = heater_shaker_speed * 0.9 + helpers.set_hs_speed(ctx, h_s, speed_val, bind_time, True) + + # Transfer from H-S plate to Magdeck plate + helpers.move_labware_from_hs_to_destination( + ctx, sample_plate, h_s, magnetic_block + ) + for bindi in np.arange( + settling_time + 1, 0, -0.5 + ): # Settling time delay with countdown timer + ctx.delay( + minutes=0.5, + msg="There are " + str(bindi) + " minutes left in the incubation.", + ) + + # remove initial supernatant + remove_supernatant(vol + starting_vol) + + def wash(vol: float, source: List[Well]) -> None: + """Wash function.""" + global whichwash # Defines which wash the protocol is on to log on the app + + if source == wash1: + whichwash = 1 + if source == wash2: + whichwash = 2 + if source == wash3: + whichwash = 3 + + ctx.comment("-----Beginning Wash #" + str(whichwash) + "-----") + + num_trans = math.ceil(vol / 980) + vol_per_trans = vol / num_trans + tiptrack(tips) + for i, m in enumerate(samples_m): + src = source[i // 2] + for n in range(num_trans): + if m1000.current_volume > 0: + m1000.dispense(m1000.current_volume, src.top()) + m1000.transfer(vol_per_trans, src, m.top(), air_gap=20, new_tip="never") + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + + helpers.set_hs_speed(ctx, h_s, heater_shaker_speed, elute_wash_time, True) + + helpers.move_labware_from_hs_to_destination( + ctx, sample_plate, h_s, magnetic_block + ) + + for washi in np.arange( + settling_time, 0, -0.5 + ): # settling time timer for washes + ctx.delay( + minutes=0.5, + msg="There are " + + str(washi) + + " minutes left in wash " + + str(whichwash) + + " incubation.", + ) + + remove_supernatant(vol) + + def elute(vol: float) -> None: + """Elution Function.""" + ctx.comment("-----Beginning Elution Steps-----") + tiptrack(tips) + for i, (m, e) in enumerate(zip(samples_m, elution_samples_m)): + m1000.flow_rate.aspirate = 25 + m1000.aspirate(vol, e.bottom(dot_bottom)) + m1000.air_gap(20) + m1000.dispense(m1000.current_volume, m.top()) + m1000.flow_rate.aspirate = 150 + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + + h_s.set_and_wait_for_shake_speed(heater_shaker_speed * 1.1) + speed_val = heater_shaker_speed * 1.1 + helpers.set_hs_speed(ctx, h_s, speed_val, elute_wash_time, True) + + # Transfer back to magnet + helpers.move_labware_from_hs_to_destination( + ctx, sample_plate, h_s, magnetic_block + ) + + for elutei in np.arange(settling_time, 0, -0.5): + ctx.delay( + minutes=0.5, + msg="Incubating on MagDeck for " + str(elutei) + " more minutes.", + ) + + for i, (m, e) in enumerate(zip(samples_m, elution_samples_m)): + tiptrack(tips) + m1000.flow_rate.dispense = 100 + m1000.flow_rate.aspirate = 150 + m1000.transfer( + vol, m.bottom(dot_bottom), e.bottom(5), air_gap=20, new_tip="never" + ) + m1000.blow_out(e.top(-2)) + m1000.air_gap(20) + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + + """ + Here is where you can call the methods defined above to fit your specific + protocol. The normal sequence is: + """ + A_lysis(AL_total_vol, AL) + bind(binding_buffer_vol) + wash(wash1_vol, wash1) + wash(wash2_vol, wash2) + wash(wash3_vol, wash3) + if not dry_run: + drybeads = 10.0 # Number of minutes you want to dry for + else: + drybeads = 0.5 + for beaddry in np.arange(drybeads, 0, -0.5): + ctx.delay( + minutes=0.5, + msg="There are " + str(beaddry) + " minutes left in the drying step.", + ) + elute(elution_vol) + + # Probe wells + end_wells_with_liquid = [ + waste_reservoir.wells()[0], + res1.wells()[0], + elutionplate.wells()[0], + ] + helpers.find_liquid_height_of_all_wells(ctx, m1000, end_wells_with_liquid) diff --git a/abr-testing/abr_testing/protocols/active_protocols/8_Illumina and Plate Reader.py b/abr-testing/abr_testing/protocols/active_protocols/8_Illumina and Plate Reader.py new file mode 100644 index 00000000000..4894cae41d4 --- /dev/null +++ b/abr-testing/abr_testing/protocols/active_protocols/8_Illumina and Plate Reader.py @@ -0,0 +1,948 @@ +"""Illumina DNA Prep and Plate Reader Test.""" +from opentrons.protocol_api import ParameterContext, ProtocolContext, Labware +from abr_testing.protocols import helpers +from opentrons.protocol_api.module_contexts import ( + AbsorbanceReaderContext, + ThermocyclerContext, + HeaterShakerContext, + TemperatureModuleContext, + MagneticBlockContext, +) +from datetime import datetime +from opentrons.hardware_control.modules.types import ThermocyclerStep +from typing import List +from opentrons import types + +metadata = { + "protocolName": "Illumina DNA Prep and Plate Reader Test", + "author": "Platform Expansion", +} + + +requirements = {"robotType": "Flex", "apiLevel": "2.21"} + +HELLMA_PLATE_SLOT = "D4" +PLATE_READER_SLOT = "C3" + +# SCRIPT SETTINGS +DRYRUN = False # True = skip incubation times, shorten mix, for testing purposes +USE_GRIPPER = True # True = Uses Gripper, False = Manual Move +TIP_TRASH = False # True = Used tips go in Trash, False = Used tips go back into rack +HYBRID_PAUSE = True # True = sets a pause on the Hybridization + +# PROTOCOL SETTINGS +COLUMNS = 3 # 1-3 +HYBRIDDECK = True +HYBRIDTIME = 1.6 # Hours + +# PROTOCOL BLOCKS +STEP_VOLPOOL = 0 +STEP_HYB = 0 +STEP_CAPTURE = 1 +STEP_WASH = 1 +STEP_PCR = 1 +STEP_PCRDECK = 1 +STEP_CLEANUP = 1 + +p200_tips = 0 +p50_tips = 0 + + +RUN = 1 + + +def add_parameters(parameters: ParameterContext) -> None: + """Add Parameters.""" + helpers.create_hs_speed_parameter(parameters) + helpers.create_dot_bottom_parameter(parameters) + parameters.add_bool( + variable_name="plate_orientation", + display_name="Hellma Plate Orientation", + default=True, + description="If hellma plate is rotated, set to True.", + ) + + +def plate_reader_actions( + protocol: ProtocolContext, + plate_reader: AbsorbanceReaderContext, + hellma_plate: Labware, + hellma_plate_name: str, +) -> None: + """Plate reader single and multi wavelength readings.""" + wavelengths = [450, 650] + # Single Wavelength Readings + plate_reader.close_lid() + for wavelength in wavelengths: + plate_reader.initialize("single", [wavelength], reference_wavelength=wavelength) + plate_reader.open_lid() + protocol.move_labware(hellma_plate, plate_reader, use_gripper=True) + plate_reader.close_lid() + result = plate_reader.read(str(datetime.now())) + msg = f"{hellma_plate_name} result: {result}" + protocol.comment(msg=msg) + plate_reader.open_lid() + protocol.move_labware(hellma_plate, HELLMA_PLATE_SLOT, use_gripper=True) + plate_reader.close_lid() + # Multi Wavelength + plate_reader.initialize("multi", [450, 650]) + plate_reader.open_lid() + protocol.move_labware(hellma_plate, plate_reader, use_gripper=True) + plate_reader.close_lid() + result = plate_reader.read(str(datetime.now())) + msg = f"{hellma_plate_name} result: {result}" + protocol.comment(msg=msg) + plate_reader.open_lid() + protocol.move_labware(hellma_plate, HELLMA_PLATE_SLOT, use_gripper=True) + plate_reader.close_lid() + + +def run(protocol: ProtocolContext) -> None: + """Protocol.""" + # LOAD PARAMETERS + heater_shaker_speed = protocol.params.heater_shaker_speed # type: ignore[attr-defined] + dot_bottom = protocol.params.dot_bottom # type: ignore[attr-defined] + plate_orientation = protocol.params.plate_orientation # type: ignore[attr-defined] + plate_name_str = "hellma_plate_" + str(plate_orientation) + global p200_tips + global p50_tips + # WASTE BIN + protocol.load_waste_chute() + # TIP RACKS + tiprack_200_1 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "B2") + tiprack_200_2 = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "C2") + tiprack_50_1 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "A2") + tiprack_50_2 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "A3") + # MODULES + LABWARE + # Reservoir + reservoir = protocol.load_labware("nest_96_wellplate_2ml_deep", "D2") + # Heatershaker + heatershaker: HeaterShakerContext = protocol.load_module( + helpers.hs_str, "D1" + ) # type: ignore[assignment] + sample_plate_2 = heatershaker.load_labware( + "thermoscientificnunc_96_wellplate_1300ul" + ) + # Magnetic Block + mag_block: MagneticBlockContext = protocol.load_module( + helpers.mag_str, "C1" + ) # type: ignore[assignment] + thermocycler: ThermocyclerContext = protocol.load_module( + helpers.tc_str + ) # type: ignore[assignment] + sample_plate_1 = thermocycler.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt" + ) + # Temperature Module + temp_block: TemperatureModuleContext = protocol.load_module( + helpers.temp_str, "B3" + ) # type: ignore[assignment] + reagent_plate, temp_adapter = helpers.load_temp_adapter_and_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", temp_block, "Reagent Plate" + ) + # Plate Reader + plate_reader: AbsorbanceReaderContext = protocol.load_module( + helpers.abs_mod_str, PLATE_READER_SLOT + ) # type: ignore[assignment] + hellma_plate = protocol.load_labware("hellma_reference_plate", HELLMA_PLATE_SLOT) + # PIPETTES + p1000 = protocol.load_instrument( + "flex_8channel_1000", + "left", + tip_racks=[tiprack_200_1, tiprack_200_2], + ) + p50 = protocol.load_instrument( + "flex_8channel_50", "right", tip_racks=[tiprack_50_1, tiprack_50_2] + ) + # reagent + AMPure = reservoir["A1"] + SMB = reservoir["A2"] + + EtOH = reservoir["A4"] + RSB = reservoir["A5"] + + Liquid_trash_well_1 = reservoir["A9"] + Liquid_trash_well_2 = reservoir["A10"] + Liquid_trash_well_3 = reservoir["A11"] + Liquid_trash_well_4 = reservoir["A12"] + + # Will Be distributed during the protocol + EEW_1 = sample_plate_2.wells_by_name()["A10"] + EEW_2 = sample_plate_2.wells_by_name()["A11"] + EEW_3 = sample_plate_2.wells_by_name()["A12"] + + NHB2 = reagent_plate.wells_by_name()["A1"] + Panel = reagent_plate.wells_by_name()["A2"] + EHB2 = reagent_plate.wells_by_name()["A3"] + Elute = reagent_plate.wells_by_name()["A4"] + ET2 = reagent_plate.wells_by_name()["A5"] + PPC = reagent_plate.wells_by_name()["A6"] + EPM = reagent_plate.wells_by_name()["A7"] + # Load Liquids + plate_reader_actions(protocol, plate_reader, hellma_plate, plate_name_str) + + # tip and sample tracking + if COLUMNS == 1: + column_1_list = ["A1"] # Plate 1 + column_2_list = ["A1"] # Plate 2 + column_3_list = ["A4"] # Plate 2 + column_4_list = ["A4"] # Plate 1 + column_5_list = ["A7"] # Plate 2 + column_6_list = ["A7"] # Plate 1 + WASHES = [EEW_1] + if COLUMNS == 2: + column_1_list = ["A1", "A2"] # Plate 1 + column_2_list = ["A1", "A2"] # Plate 2 + column_3_list = ["A4", "A5"] # Plate 2 + column_4_list = ["A4", "A5"] # Plate 1 + column_5_list = ["A7", "A8"] # Plate 2 + column_6_list = ["A7", "A8"] # Plate 1 + WASHES = [EEW_1, EEW_2] + if COLUMNS == 3: + column_1_list = ["A1", "A2", "A3"] # Plate 1 + column_2_list = ["A1", "A2", "A3"] # Plate 2 + column_3_list = ["A4", "A5", "A6"] # Plate 2 + column_4_list = ["A4", "A5", "A6"] # Plate 1 + column_5_list = ["A7", "A8", "A9"] # Plate 2 + column_6_list = ["A7", "A8", "A9"] # Plate 1 + WASHES = [EEW_1, EEW_2, EEW_3] + + def tipcheck() -> None: + """Check tips.""" + if p200_tips >= 2 * 12: + p1000.reset_tipracks() + p200_tips == 0 + if p50_tips >= 2 * 12: + p50.reset_tipracks() + p50_tips == 0 + + # commands + for loop in range(RUN): + thermocycler.open_lid() + heatershaker.open_labware_latch() + if DRYRUN is False: + if STEP_HYB == 1: + protocol.comment("SETTING THERMO and TEMP BLOCK Temperature") + thermocycler.set_block_temperature(4) + thermocycler.set_lid_temperature(100) + temp_block.set_temperature(4) + else: + protocol.comment("SETTING THERMO and TEMP BLOCK Temperature") + thermocycler.set_block_temperature(58) + thermocycler.set_lid_temperature(58) + heatershaker.set_and_wait_for_temperature(58) + protocol.pause("Ready") + heatershaker.close_labware_latch() + Liquid_trash = Liquid_trash_well_1 + + # Sample Plate contains 30ul of DNA + + if STEP_VOLPOOL == 1: + protocol.comment("==============================================") + protocol.comment("--> Quick Vol Pool") + protocol.comment("==============================================") + + if STEP_HYB == 1: + protocol.comment("==============================================") + protocol.comment("--> HYB") + protocol.comment("==============================================") + + protocol.comment("--> Adding NHB2") + NHB2Vol = 50 + for loop, X in enumerate(column_1_list): + p50.pick_up_tip() + p50.aspirate(NHB2Vol, NHB2.bottom(z=dot_bottom)) # original = () + p50.dispense( + NHB2Vol, sample_plate_1[X].bottom(z=dot_bottom) + ) # original = () + p50.return_tip() if TIP_TRASH is False else p50.drop_tip() + p50_tips += 1 + tipcheck() + + protocol.comment("--> Adding Panel") + PanelVol = 10 + for loop, X in enumerate(column_1_list): + p50.pick_up_tip() + p50.aspirate(PanelVol, Panel.bottom(z=dot_bottom)) # original = () + p50.dispense( + PanelVol, sample_plate_1[X].bottom(z=dot_bottom) + ) # original = () + p50.return_tip() if TIP_TRASH is False else p50.drop_tip() + p50_tips += 1 + tipcheck() + + protocol.comment("--> Adding EHB2") + EHB2Vol = 10 + EHB2MixRep = 10 if DRYRUN is False else 1 + EHB2MixVol = 90 + for loop, X in enumerate(column_1_list): + p1000.pick_up_tip() + p1000.aspirate(EHB2Vol, EHB2.bottom(z=dot_bottom)) # original = () + p1000.dispense( + EHB2Vol, sample_plate_1[X].bottom(z=dot_bottom) + ) # original = () + p1000.move_to(sample_plate_1[X].bottom(z=dot_bottom)) # original = () + p1000.mix(EHB2MixRep, EHB2MixVol) + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p50_tips += 1 + tipcheck() + + if HYBRIDDECK: + protocol.comment("Hybridize on Deck") + thermocycler.close_lid() + if DRYRUN is False: + profile_TAGSTOP: List[ThermocyclerStep] = [ + {"temperature": 98, "hold_time_minutes": 5}, + {"temperature": 97, "hold_time_minutes": 1}, + {"temperature": 95, "hold_time_minutes": 1}, + {"temperature": 93, "hold_time_minutes": 1}, + {"temperature": 91, "hold_time_minutes": 1}, + {"temperature": 89, "hold_time_minutes": 1}, + {"temperature": 87, "hold_time_minutes": 1}, + {"temperature": 85, "hold_time_minutes": 1}, + {"temperature": 83, "hold_time_minutes": 1}, + {"temperature": 81, "hold_time_minutes": 1}, + {"temperature": 79, "hold_time_minutes": 1}, + {"temperature": 77, "hold_time_minutes": 1}, + {"temperature": 75, "hold_time_minutes": 1}, + {"temperature": 73, "hold_time_minutes": 1}, + {"temperature": 71, "hold_time_minutes": 1}, + {"temperature": 69, "hold_time_minutes": 1}, + {"temperature": 67, "hold_time_minutes": 1}, + {"temperature": 65, "hold_time_minutes": 1}, + {"temperature": 63, "hold_time_minutes": 1}, + {"temperature": 62, "hold_time_minutes": HYBRIDTIME * 60}, + ] + thermocycler.execute_profile( + steps=profile_TAGSTOP, repetitions=1, block_max_volume=100 + ) + thermocycler.set_block_temperature(62) + if HYBRID_PAUSE: + protocol.comment("HYBRIDIZATION PAUSED") + thermocycler.set_block_temperature(10) + thermocycler.open_lid() + else: + protocol.comment("Hybridize off Deck") + + if STEP_CAPTURE == 1: + protocol.comment("==============================================") + protocol.comment("--> Capture") + protocol.comment("==============================================") + # Standard Setup + + if DRYRUN is False: + protocol.comment("SETTING THERMO and TEMP BLOCK Temperature") + thermocycler.set_block_temperature(58) + thermocycler.set_lid_temperature(58) + + if DRYRUN is False: + heatershaker.set_and_wait_for_temperature(58) + + protocol.comment("--> Transfer Hybridization") + TransferSup = 100 + for loop, X in enumerate(column_1_list): + p1000.pick_up_tip() + p1000.move_to(sample_plate_1[X].bottom(z=0.5)) + p1000.aspirate(TransferSup + 1, rate=0.25) + p1000.dispense( + TransferSup + 1, sample_plate_2[column_2_list[loop]].bottom(z=1) + ) + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p200_tips += 1 + tipcheck() + + thermocycler.close_lid() + + protocol.comment("--> ADDING SMB") + SMBVol = 250 + SMBMixRPM = heater_shaker_speed + SMBMixRep = 5 * 60 if DRYRUN is False else 0.1 * 60 + SMBPremix = 3 if DRYRUN is False else 1 + # ============================== + for loop, X in enumerate(column_2_list): + p1000.pick_up_tip() + p1000.mix(SMBPremix, 200, SMB.bottom(z=1)) + p1000.aspirate(SMBVol / 2, SMB.bottom(z=1), rate=0.25) + p1000.dispense(SMBVol / 2, sample_plate_2[X].top(z=-7), rate=0.25) + p1000.aspirate(SMBVol / 2, SMB.bottom(z=1), rate=0.25) + p1000.dispense(SMBVol / 2, sample_plate_2[X].bottom(z=1), rate=0.25) + p1000.default_speed = 5 + p1000.move_to(sample_plate_2[X].bottom(z=5)) + for Mix in range(2): + p1000.aspirate(100, rate=0.5) + p1000.move_to(sample_plate_2[X].bottom(z=1)) + p1000.aspirate(80, rate=0.5) + p1000.dispense(80, rate=0.5) + p1000.move_to(sample_plate_2[X].bottom(z=5)) + p1000.dispense(100, rate=0.5) + Mix += 1 + p1000.blow_out(sample_plate_2[X].top(z=-7)) + p1000.default_speed = 400 + p1000.move_to(sample_plate_2[X].top(z=5)) + p1000.move_to(sample_plate_2[X].top(z=0)) + p1000.move_to(sample_plate_2[X].top(z=5)) + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p200_tips += 1 + tipcheck() + # ============================== + heatershaker.set_and_wait_for_shake_speed(rpm=SMBMixRPM) + protocol.delay(SMBMixRep) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE sample_plate_2 FROM heatershaker TO MAGPLATE + helpers.move_labware_from_hs_to_destination( + protocol, sample_plate_2, heatershaker, mag_block + ) + thermocycler.open_lid() + + if DRYRUN is False: + protocol.delay(minutes=2) + + protocol.comment("==============================================") + protocol.comment("--> WASH") + protocol.comment("==============================================") + # Setting Labware to Resume at Cleanup 1 + + protocol.comment("--> Remove SUPERNATANT") + for loop, X in enumerate(column_2_list): + p1000.pick_up_tip() + p1000.move_to(sample_plate_2[X].bottom(4)) + p1000.aspirate(200, rate=0.25) + p1000.dispense(200, Liquid_trash.top(z=-7)) + p1000.move_to(sample_plate_2[X].bottom(0.5)) + p1000.aspirate(200, rate=0.25) + p1000.dispense(200, Liquid_trash.top(z=-7)) + p1000.move_to(Liquid_trash.top(z=-7)) + protocol.delay(minutes=0.1) + p1000.blow_out(Liquid_trash.top(z=-7)) + p1000.aspirate(20) + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p200_tips += 1 + tipcheck() + + Liquid_trash = Liquid_trash_well_2 + + # ============================================================================================ + # GRIPPER MOVE sample_plate_2 FROM MAGPLATE TO heatershaker + helpers.move_labware_to_hs( + protocol, sample_plate_2, heatershaker, heatershaker + ) + # ============================================================================================ + + protocol.comment("--> Repeating 3 washes") + washreps = 3 + washcount = 0 + for wash in range(washreps): + + protocol.comment("--> Adding EEW") + EEWVol = 200 + for loop, X in enumerate(column_2_list): + p1000.pick_up_tip() + p1000.aspirate( + EEWVol, WASHES[loop].bottom(z=dot_bottom) + ) # original = () + p1000.dispense( + EEWVol, sample_plate_2[X].bottom(z=dot_bottom) + ) # original = () + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p200_tips += 1 + tipcheck() + heatershaker.close_labware_latch() + heatershaker.set_and_wait_for_shake_speed( + rpm=(heater_shaker_speed * 0.9) + ) + if DRYRUN is False: + protocol.delay(seconds=4 * 60) + heatershaker.deactivate_shaker() + heatershaker.open_labware_latch() + + if DRYRUN is False: + protocol.delay(seconds=5 * 60) + + # ============================================================================================ + # GRIPPER MOVE sample_plate_2 FROM heatershaker TO MAGPLATE + helpers.move_labware_from_hs_to_destination( + protocol, sample_plate_2, heatershaker, mag_block + ) + + if DRYRUN is False: + protocol.delay(seconds=1 * 60) + + if washcount > 2: + Liquid_trash = Liquid_trash_well_3 + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + for loop, X in enumerate(column_2_list): + p1000.pick_up_tip() + p1000.move_to(sample_plate_2[X].bottom(z=3.5)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(sample_plate_2[X].bottom(z=0.5)) + p1000.aspirate(100, rate=0.25) + p1000.move_to(sample_plate_2[X].top(z=0.5)) + p1000.dispense(200, Liquid_trash.top(z=-7)) + protocol.delay(minutes=0.1) + p1000.blow_out(Liquid_trash.top(z=-7)) + p1000.aspirate(20) + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p200_tips += 1 + tipcheck() + + # ============================================================================================ + # GRIPPER MOVE sample_plate_2 FROM MAGPLATE TO heatershaker + helpers.move_labware_to_hs( + protocol, sample_plate_2, heatershaker, heatershaker + ) + washcount += 1 + + protocol.comment("--> Adding EEW") + EEWVol = 200 + for loop, X in enumerate(column_2_list): + p1000.pick_up_tip() + p1000.aspirate( + EEWVol, WASHES[loop].bottom(z=dot_bottom) + ) # original = () + p1000.dispense( + EEWVol, sample_plate_2[X].bottom(z=dot_bottom) + ) # original = () + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p200_tips += 1 + tipcheck() + + heatershaker.set_and_wait_for_shake_speed(rpm=(heater_shaker_speed * 0.9)) + if DRYRUN is False: + protocol.delay(seconds=4 * 60) + heatershaker.deactivate_shaker() + + if DRYRUN is False: + protocol.delay(seconds=1 * 60) + + protocol.comment("--> Transfer Hybridization") + TransferSup = 200 + for loop, X in enumerate(column_2_list): + p1000.pick_up_tip() + p1000.move_to(sample_plate_2[X].bottom(z=0.5)) + p1000.aspirate(TransferSup, rate=0.25) + p1000.dispense( + TransferSup, sample_plate_2[column_3_list[loop]].bottom(z=1) + ) + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p200_tips += 1 + tipcheck() + + if DRYRUN is False: + protocol.delay(seconds=5 * 60) + + # ============================================================================================ + # GRIPPER MOVE sample_plate_2 FROM heatershaker TO MAGPLATE + helpers.move_labware_from_hs_to_destination( + protocol, sample_plate_2, heatershaker, mag_block + ) + + if DRYRUN is False: + protocol.delay(seconds=1 * 60) + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + for loop, X in enumerate(column_3_list): + p1000.pick_up_tip() + p1000.move_to(sample_plate_2[X].bottom(z=3.5)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(sample_plate_2[X].bottom(z=0.5)) + p1000.aspirate(100, rate=0.25) + p1000.move_to(sample_plate_2[X].top(z=0.5)) + p1000.dispense(200, Liquid_trash.top(z=-7)) + protocol.delay(minutes=0.1) + p1000.blow_out(Liquid_trash.top(z=-7)) + p1000.aspirate(20) + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p200_tips += 1 + tipcheck() + + protocol.comment("--> Removing Residual") + for loop, X in enumerate(column_3_list): + p50.pick_up_tip() + p50.move_to(sample_plate_2[X].bottom(z=dot_bottom)) # original = z=0 + p50.aspirate(50, rate=0.25) + p50.default_speed = 200 + p50.dispense(50, Liquid_trash.top(z=-7)) + protocol.delay(minutes=0.1) + p50.blow_out() + p50.default_speed = 400 + p50.move_to(Liquid_trash.top(z=-7)) + p50.move_to(Liquid_trash.top(z=0)) + p50.return_tip() if TIP_TRASH is False else p50.drop_tip() + p50_tips += 1 + tipcheck() + + protocol.comment("==============================================") + protocol.comment("--> ELUTE") + protocol.comment("==============================================") + + protocol.comment("--> Adding Elute") + EluteVol = 23 + for loop, X in enumerate(column_3_list): + p50.pick_up_tip() + p50.aspirate(EluteVol, Elute.bottom(z=dot_bottom)) # original = () + p50.dispense( + EluteVol, sample_plate_2[X].bottom(z=dot_bottom) + ) # original = () + p50.return_tip() if TIP_TRASH is False else p50.drop_tip() + p50_tips += 1 + tipcheck() + + # ============================================================================================ + # GRIPPER MOVE sample_plate_2 FROM MAGPLATE TO heatershaker + helpers.move_labware_to_hs( + protocol, sample_plate_2, heatershaker, heatershaker + ) + # ============================================================================================ + + heatershaker.close_labware_latch() + heatershaker.set_and_wait_for_shake_speed(rpm=(heater_shaker_speed * 0.9)) + if DRYRUN is False: + protocol.delay(seconds=2 * 60) + heatershaker.deactivate_shaker() + heatershaker.open_labware_latch() + + if DRYRUN is False: + protocol.delay(minutes=2) + + # ============================================================================================ + # GRIPPER MOVE sample_plate_2 FROM heatershaker TO MAGPLATE + helpers.move_labware_from_hs_to_destination( + protocol, sample_plate_2, heatershaker, mag_block + ) + protocol.comment("--> Transfer Elution") + TransferSup = 21 + for loop, X in enumerate(column_3_list): + p50.pick_up_tip() + p50.move_to(sample_plate_2[X].bottom(z=0.5)) + p50.aspirate(TransferSup + 1, rate=0.25) + p50.dispense( + TransferSup + 1, sample_plate_1[column_4_list[loop]].bottom(z=1) + ) + p50.return_tip() if TIP_TRASH is False else p50.drop_tip() + p50_tips += 1 + tipcheck() + + protocol.comment("--> Adding ET2") + ET2Vol = 4 + ET2MixRep = 10 if DRYRUN is False else 1 + ET2MixVol = 20 + for loop, X in enumerate(column_4_list): + p50.pick_up_tip() + p50.aspirate(ET2Vol, ET2.bottom(z=dot_bottom)) # original = () + p50.dispense( + ET2Vol, sample_plate_1[X].bottom(z=dot_bottom) + ) # original = () + p50.move_to(sample_plate_1[X].bottom(z=dot_bottom)) # original = () + p50.mix(ET2MixRep, ET2MixVol) + p50.return_tip() if TIP_TRASH is False else p50.drop_tip() + p50_tips += 1 + tipcheck() + + if STEP_PCR == 1: + protocol.comment("==============================================") + protocol.comment("--> AMPLIFICATION") + protocol.comment("==============================================") + + protocol.comment("--> Adding PPC") + PPCVol = 5 + for loop, X in enumerate(column_4_list): + p50.pick_up_tip() + p50.aspirate(PPCVol, PPC.bottom(z=dot_bottom)) # original = () + p50.dispense( + PPCVol, sample_plate_1[X].bottom(z=dot_bottom) + ) # original = () + p50.return_tip() if TIP_TRASH is False else p50.drop_tip() + p50_tips += 1 + tipcheck() + + protocol.comment("--> Adding EPM") + EPMVol = 20 + EPMMixRep = 10 if DRYRUN is False else 1 + EPMMixVol = 45 + for loop, X in enumerate(column_4_list): + p50.pick_up_tip() + p50.aspirate(EPMVol, EPM.bottom(z=dot_bottom)) # original = () + p50.dispense( + EPMVol, sample_plate_1[X].bottom(z=dot_bottom) + ) # original = () + p50.move_to(sample_plate_1[X].bottom(z=dot_bottom)) # original = () + p50.mix(EPMMixRep, EPMMixVol) + p50.return_tip() if TIP_TRASH is False else p50.drop_tip() + p50_tips += 1 + tipcheck() + + if DRYRUN is False: + heatershaker.deactivate_heater() + + if STEP_PCRDECK == 1: + if DRYRUN is False: + if DRYRUN is False: + thermocycler.close_lid() + helpers.perform_pcr( + protocol, + thermocycler, + initial_denature_time_sec=45, + denaturation_time_sec=30, + anneal_time_sec=30, + extension_time_sec=30, + cycle_repetitions=12, + final_extension_time_min=1, + ) + thermocycler.set_block_temperature(10) + + thermocycler.open_lid() + + if STEP_CLEANUP == 1: + protocol.comment("==============================================") + protocol.comment("--> Cleanup") + protocol.comment("==============================================") + + # GRIPPER MOVE sample_plate_2 FROM MAGPLATE TO heatershaker + helpers.move_labware_to_hs( + protocol, sample_plate_2, heatershaker, heatershaker + ) + + protocol.comment("--> Transfer Elution") + TransferSup = 45 + for loop, X in enumerate(column_4_list): + p50.pick_up_tip() + p50.move_to(sample_plate_1[X].bottom(z=0.5)) + p50.aspirate(TransferSup + 1, rate=0.25) + p50.dispense( + TransferSup + 1, sample_plate_2[column_5_list[loop]].bottom(z=1) + ) + p50.return_tip() if TIP_TRASH is False else p50.drop_tip() + p50_tips += 1 + tipcheck() + + Liquid_trash = Liquid_trash_well_4 + + protocol.comment("--> ADDING AMPure (0.8x)") + AMPureVol = 40.5 + AMPureMixRep = 5 * 60 if DRYRUN is False else 0.1 * 60 + AMPurePremix = 3 if DRYRUN is False else 1 + # ========NEW SINGLE TIP DISPENSE=========== + for loop, X in enumerate(column_5_list): + p1000.pick_up_tip() + p1000.mix(AMPurePremix, AMPureVol + 10, AMPure.bottom(z=1)) + p1000.aspirate(AMPureVol, AMPure.bottom(z=1), rate=0.25) + p1000.dispense(AMPureVol, sample_plate_2[X].bottom(z=1), rate=0.25) + p1000.default_speed = 5 + p1000.move_to(sample_plate_2[X].bottom(z=5)) + for Mix in range(2): + p1000.aspirate(60, rate=0.5) + p1000.move_to(sample_plate_2[X].bottom(z=1)) + p1000.aspirate(60, rate=0.5) + p1000.dispense(60, rate=0.5) + p1000.move_to(sample_plate_2[X].bottom(z=5)) + p1000.dispense(30, rate=0.5) + Mix += 1 + p1000.blow_out(sample_plate_2[X].top(z=2)) + p1000.default_speed = 400 + p1000.move_to(sample_plate_2[X].top(z=5)) + p1000.move_to(sample_plate_2[X].top(z=0)) + p1000.move_to(sample_plate_2[X].top(z=5)) + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p200_tips += 1 + tipcheck() + # ========NEW HS MIX========================= + heatershaker.set_and_wait_for_shake_speed(rpm=(heater_shaker_speed * 0.9)) + protocol.delay(AMPureMixRep) + heatershaker.deactivate_shaker() + + # GRIPPER MOVE PLATE FROM HEATER SHAKER TO MAG PLATE + helpers.move_labware_from_hs_to_destination( + protocol, sample_plate_2, heatershaker, mag_block + ) + + if DRYRUN is False: + protocol.delay(minutes=4) + + protocol.comment("--> Removing Supernatant") + RemoveSup = 200 + for loop, X in enumerate(column_5_list): + p1000.pick_up_tip() + p1000.move_to(sample_plate_2[X].bottom(z=3.5)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(sample_plate_2[X].bottom(z=0.5)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(sample_plate_2[X].top(z=2)) + p1000.default_speed = 200 + p1000.dispense(200, Liquid_trash.top(z=-7)) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.default_speed = 400 + p1000.move_to(Liquid_trash.top(z=-7)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p200_tips += 1 + tipcheck() + + for well_num in ["A1", "A2"]: + protocol.comment("--> ETOH Wash") + ETOHMaxVol = 150 + for loop, X in enumerate(column_5_list): + p1000.pick_up_tip() + p1000.aspirate(ETOHMaxVol, EtOH.bottom(z=1)) + p1000.move_to(EtOH.top(z=0)) + p1000.move_to(EtOH.top(z=-5)) + p1000.move_to(EtOH.top(z=0)) + p1000.move_to(sample_plate_2[well_num].top(z=-2)) + p1000.dispense(ETOHMaxVol, rate=1) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.move_to(sample_plate_2[well_num].top(z=5)) + p1000.move_to(sample_plate_2[well_num].top(z=0)) + p1000.move_to(sample_plate_2[well_num].top(z=5)) + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p200_tips += 1 + tipcheck() + + if DRYRUN is False: + protocol.delay(minutes=0.5) + + protocol.comment("--> Remove ETOH Wash") + for loop, X in enumerate(column_5_list): + p1000.pick_up_tip() + p1000.move_to(sample_plate_2[X].bottom(z=3.5)) + p1000.aspirate(RemoveSup - 100, rate=0.25) + protocol.delay(minutes=0.1) + p1000.move_to(sample_plate_2[X].bottom(z=0.5)) + p1000.aspirate(100, rate=0.25) + p1000.default_speed = 5 + p1000.move_to(sample_plate_2[X].top(z=2)) + p1000.default_speed = 200 + p1000.dispense(200, Liquid_trash.top(z=-7)) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.default_speed = 400 + p1000.move_to(Liquid_trash.top(z=-7)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p200_tips += 1 + tipcheck() + + if DRYRUN is False: + protocol.delay(minutes=2) + + protocol.comment("--> Removing Residual ETOH") + for loop, X in enumerate(column_5_list): + p1000.pick_up_tip() + p1000.move_to( + sample_plate_2[X].bottom(z=dot_bottom) + ) # original = (z=0) + p1000.aspirate(50, rate=0.25) + p1000.default_speed = 200 + p1000.dispense(50, Liquid_trash.top(z=-7)) + protocol.delay(minutes=0.1) + p1000.blow_out() + p1000.default_speed = 400 + p1000.move_to(Liquid_trash.top(z=-7)) + p1000.move_to(Liquid_trash.top(z=0)) + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p200_tips += 1 + tipcheck() + + if DRYRUN is False: + protocol.delay(minutes=1) + + # ============================================================================================ + # GRIPPER MOVE PLATE FROM MAG PLATE TO HEATER SHAKER + helpers.move_labware_to_hs( + protocol, sample_plate_2, heatershaker, heatershaker + ) + + protocol.comment("--> Adding RSB") + RSBVol = 32 + RSBMixRep = 1 * 60 if DRYRUN is False else 0.1 * 60 + for loop, X in enumerate(column_5_list): + p1000.pick_up_tip() + p1000.aspirate(RSBVol, RSB.bottom(z=1)) + + p1000.move_to( + ( + sample_plate_2.wells_by_name()[X] + .center() + .move(types.Point(x=1.3 * 0.8, y=0, z=-4)) + ) + ) + p1000.dispense(RSBVol, rate=1) + p1000.move_to(sample_plate_2.wells_by_name()[X].bottom(z=1)) + p1000.aspirate(RSBVol, rate=1) + p1000.move_to( + ( + sample_plate_2.wells_by_name()[X] + .center() + .move(types.Point(x=0, y=1.3 * 0.8, z=-4)) + ) + ) + p1000.dispense(RSBVol, rate=1) + p1000.move_to(sample_plate_2.wells_by_name()[X].bottom(z=1)) + p1000.aspirate(RSBVol, rate=1) + p1000.move_to( + ( + sample_plate_2.wells_by_name()[X] + .center() + .move(types.Point(x=1.3 * -0.8, y=0, z=-4)) + ) + ) + p1000.dispense(RSBVol, rate=1) + p1000.move_to(sample_plate_2.wells_by_name()[X].bottom(z=1)) + p1000.aspirate(RSBVol, rate=1) + p1000.move_to( + ( + sample_plate_2.wells_by_name()[X] + .center() + .move(types.Point(x=0, y=1.3 * -0.8, z=-4)) + ) + ) + p1000.dispense(RSBVol, rate=1) + p1000.move_to(sample_plate_2.wells_by_name()[X].bottom(z=1)) + p1000.aspirate(RSBVol, rate=1) + p1000.dispense(RSBVol, rate=1) + + p1000.blow_out(sample_plate_2.wells_by_name()[X].center()) + p1000.move_to(sample_plate_2.wells_by_name()[X].top(z=5)) + p1000.move_to(sample_plate_2.wells_by_name()[X].top(z=0)) + p1000.move_to(sample_plate_2.wells_by_name()[X].top(z=5)) + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p200_tips += 1 + tipcheck() + if DRYRUN is False: + heatershaker.set_and_wait_for_shake_speed( + rpm=(heater_shaker_speed * 0.8) + ) + protocol.delay(RSBMixRep) + heatershaker.deactivate_shaker() + + # ============================================================================================ + # GRIPPER MOVE PLATE FROM HEATER SHAKER TO MAG PLATE + helpers.move_labware_from_hs_to_destination( + protocol, sample_plate_2, heatershaker, mag_block + ) + + if DRYRUN is False: + protocol.delay(minutes=3) + + protocol.comment("--> Transferring Supernatant") + TransferSup = 30 + for loop, X in enumerate(column_5_list): + p1000.pick_up_tip() + p1000.move_to(sample_plate_2[X].bottom(z=0.5)) + p1000.aspirate(TransferSup + 1, rate=0.25) + p1000.dispense( + TransferSup + 1, sample_plate_1[column_6_list[loop]].bottom(z=1) + ) + p1000.return_tip() if TIP_TRASH is False else p1000.drop_tip() + p200_tips += 1 + tipcheck() + plate_reader_actions(protocol, plate_reader, hellma_plate, plate_name_str) diff --git a/abr-testing/abr_testing/protocols/active_protocols/9_Magmax_RNA_Cells_Flex.py b/abr-testing/abr_testing/protocols/active_protocols/9_Magmax_RNA_Cells_Flex.py new file mode 100644 index 00000000000..09201e58314 --- /dev/null +++ b/abr-testing/abr_testing/protocols/active_protocols/9_Magmax_RNA_Cells_Flex.py @@ -0,0 +1,559 @@ +"""Thermo MagMax RNA Extraction: Cells Multi-Channel.""" +import math +from opentrons import types +from opentrons.protocol_api import ( + ProtocolContext, + ParameterContext, + Well, + InstrumentContext, +) +from typing import List +from opentrons.protocol_api.module_contexts import ( + HeaterShakerContext, + MagneticBlockContext, + TemperatureModuleContext, +) + +import numpy as np +from abr_testing.protocols import helpers +from typing import Dict + +metadata = { + "author": "Zach Galluzzo ", + "protocolName": "Thermo MagMax RNA Extraction: Cells Multi-Channel", +} + +requirements = { + "robotType": "OT-3", + "apiLevel": "2.21", +} +""" +Slot A1: Tips 200 +Slot A2: Tips 200 +Slot A3: Temperature module (gen2) with 96 well PCR block and Armadillo 96 well PCR Plate +** Plate gets 55 ul per well in each well of the entire plate +Slot B1: Tips 200 +Slot B2: Tips 200 +Slot B3: Nest 1 Well Reservoir +Slot C1: Magblock +Slot C2: +Slot C3: +Slot D1: H-S with Nest 96 Well Deepwell and DW Adapter +Slot D2: Nest 12 well 15 ml Reservoir +Slot D3: Trash + +Reservoir 1: +Well 1 - 8120 ul +Well 2 - 6400 ul +Well 3-7 - 8550 ul +""" + +whichwash = 1 +sample_max = 48 +tip = 0 +drop_count = 0 +waste_vol = 0 + + +# Start protocol +def add_parameters(parameters: ParameterContext) -> None: + """Parameters.""" + helpers.create_dot_bottom_parameter(parameters) + helpers.create_single_pipette_mount_parameter(parameters) + helpers.create_hs_speed_parameter(parameters) + + +def run(ctx: ProtocolContext) -> None: + """Protocol.""" + dry_run = False + inc_lysis = True + res_type = "nest_12_reservoir_15ml" + TIP_TRASH = False + num_samples = 48 + wash_vol = 150.0 + lysis_vol = 140.0 + stop_vol = 100.0 + elution_vol = dnase_vol = 50.0 + heater_shaker_speed = ctx.params.heater_shaker_speed # type: ignore[attr-defined] + dot_bottom = ctx.params.dot_bottom # type: ignore[attr-defined] + pipette_mount = ctx.params.pipette_mount # type: ignore[attr-defined] + + # Protocol Parameters + deepwell_type = "nest_96_wellplate_2ml_deep" + if not dry_run: + settling_time = 2.0 + lysis_time = 1.0 + drybeads = 2.0 # Number of minutes you want to dry for + bind_time = wash_time = 5.0 + dnase_time = 10.0 + stop_time = elute_time = 3.0 + else: + settling_time = 0.25 + lysis_time = 0.25 + drybeads = elute_time = 0.25 + bind_time = wash_time = dnase_time = stop_time = 0.25 + bead_vol = 20.0 + ctx.load_trash_bin("A3") + h_s: HeaterShakerContext = ctx.load_module(helpers.hs_str, "D1") # type: ignore[assignment] + sample_plate, h_s_adapter = helpers.load_hs_adapter_and_labware( + deepwell_type, h_s, "Sample Plate" + ) + h_s.close_labware_latch() + temp: TemperatureModuleContext = ctx.load_module( + helpers.temp_str, "D3" + ) # type: ignore[assignment] + elutionplate, temp_adapter = helpers.load_temp_adapter_and_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", temp, "Elution Plate" + ) + temp.set_temperature(4) + magblock: MagneticBlockContext = ctx.load_module( + helpers.mag_str, "C1" + ) # type: ignore[assignment] + waste_reservoir = ctx.load_labware("nest_1_reservoir_195ml", "B3", "Liquid Waste") + waste = waste_reservoir.wells()[0].top() + res1 = ctx.load_labware(res_type, "D2", "reagent reservoir 1") + num_cols = math.ceil(num_samples / 8) + + # Load tips and combine all similar boxes + tips200 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "A1", "Tips 1") + tips201 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "A2", "Tips 2") + tips202 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "B1", "Tips 3") + tips203 = ctx.load_labware("opentrons_flex_96_tiprack_200ul", "B2", "Tips 4") + tips = [ + *tips200.wells()[num_samples:96], + *tips201.wells(), + *tips202.wells(), + *tips203.wells(), + ] + tips_sn = tips200.wells()[:num_samples] + + # load P1000M pipette + m1000 = ctx.load_instrument( + "flex_8channel_1000", + pipette_mount, + tip_racks=[tips200, tips201, tips202, tips203], + ) + + # Load Liquid Locations in Reservoir + elution_solution = elutionplate.rows()[0][:num_cols] + dnase1 = elutionplate.rows()[0][num_cols : 2 * num_cols] + lysis_ = res1.wells()[0] + stopreaction = res1.wells()[1] + wash1 = res1.wells()[2] + wash2 = res1.wells()[3] + wash3 = res1.wells()[4] + wash4 = res1.wells()[5] + wash5 = res1.wells()[6] + + """ + Here is where you can define the locations of your reagents. + """ + samples_m = sample_plate.rows()[0][:num_cols] # 20ul beads each well + cells_m = sample_plate.rows()[0][num_cols : 2 * num_cols] + elution_samples_m = elutionplate.rows()[0][:num_cols] + # Do the same for color mapping + beads_ = sample_plate.wells()[: (8 * num_cols)] + cells_ = sample_plate.wells()[(8 * num_cols) : (16 * num_cols)] + elution_samps = elutionplate.wells()[: (8 * num_cols)] + dnase1_ = elutionplate.wells()[(8 * num_cols) : (16 * num_cols)] + + # Add liquids to non-reservoir labware + liquid_vols_and_wells: Dict[str, List[Dict[str, Well | List[Well] | float]]] = { + "Beads": [{"well": beads_, "volume": bead_vol}], + "Sample": [{"well": cells_, "volume": 0.0}], + "DNAse": [{"well": dnase1_, "volume": dnase_vol}], + "Elution Buffer": [{"well": elution_samps, "volume": elution_vol}], + "Lysis": [{"well": lysis_, "volume": lysis_vol}], + "Wash 1": [{"well": wash1, "volume": wash_vol}], + "Wash 2": [{"well": wash2, "volume": wash_vol}], + "Wash 3": [{"well": wash3, "volume": wash_vol}], + "Wash 4": [{"well": wash4, "volume": wash_vol}], + "Wash 5": [{"well": wash5, "volume": wash_vol}], + "Stop": [{"well": stopreaction, "volume": stop_vol}], + } + + helpers.find_liquid_height_of_loaded_liquids(ctx, liquid_vols_and_wells, m1000) + + m1000.flow_rate.aspirate = 50 + m1000.flow_rate.dispense = 150 + m1000.flow_rate.blow_out = 300 + + def tiptrack(pip: InstrumentContext, tipbox: List[Well]) -> None: + """Tip Track.""" + global tip + global drop_count + pip.pick_up_tip(tipbox[int(tip)]) + tip = tip + 8 + drop_count = drop_count + 8 + if drop_count >= 250: + drop_count = 0 + if TIP_TRASH: + ctx.pause("Empty Trash bin.") + + def remove_supernatant(vol: float) -> None: + """Remove Supernatant.""" + ctx.comment("-----Removing Supernatant-----") + m1000.flow_rate.aspirate = 30 + num_trans = math.ceil(vol / 180) + vol_per_trans = vol / num_trans + + for i, m in enumerate(samples_m): + m1000.pick_up_tip(tips_sn[8 * i]) + loc = m.bottom(dot_bottom) + for _ in range(num_trans): + if m1000.current_volume > 0: + # void air gap if necessary + m1000.dispense(m1000.current_volume, m.top()) + m1000.move_to(m.center()) + m1000.transfer(vol_per_trans, loc, waste, new_tip="never", air_gap=20) + m1000.blow_out(waste) + m1000.air_gap(20) + m1000.drop_tip(tips_sn[8 * i]) if TIP_TRASH else m1000.return_tip() + m1000.flow_rate.aspirate = 300 + # Move Plate From Magnet to H-S + helpers.move_labware_to_hs(ctx, sample_plate, h_s, h_s_adapter) + + def bead_mixing( + well: Well, pip: InstrumentContext, mvol: float, reps: int = 8 + ) -> None: + """Bead Mixing. + + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top().move(types.Point(x=0, y=0, z=5)) + aspbot = well.bottom().move(types.Point(x=0, y=0, z=1)) + asptop = well.bottom().move(types.Point(x=2, y=-2, z=1)) + disbot = well.bottom().move(types.Point(x=-2, y=1.5, z=2)) + distop = well.bottom().move(types.Point(x=0, y=0, z=6)) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, aspbot) + pip.dispense(vol, distop) + pip.aspirate(vol, asptop) + pip.dispense(vol, disbot) + if _ == reps - 1: + pip.flow_rate.aspirate = 100 + pip.flow_rate.dispense = 75 + pip.aspirate(vol, aspbot) + pip.dispense(vol, aspbot) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def mixing(well: Well, pip: InstrumentContext, mvol: float, reps: int = 8) -> None: + """Mixing. + + 'mixing' will mix liquid that contains beads. This will be done by + aspirating from the bottom of the well and dispensing from the top as to + mix the beads with the other liquids as much as possible. Aspiration and + dispensing will also be reversed for a short to to ensure maximal mixing. + param well: The current well that the mixing will occur in. + param pip: The pipet that is currently attached/ being used. + param mvol: The volume that is transferred before the mixing steps. + param reps: The number of mix repetitions that should occur. Note~ + During each mix rep, there are 2 cycles of aspirating from bottom, + dispensing at the top and 2 cycles of aspirating from middle, + dispensing at the bottom + """ + center = well.top(5) + asp = well.bottom(dot_bottom) + disp = well.top(-8) + + if mvol > 1000: + mvol = 1000 + + vol = mvol * 0.9 + + pip.flow_rate.aspirate = 500 + pip.flow_rate.dispense = 500 + + pip.move_to(center) + for _ in range(reps): + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + pip.aspirate(vol, asp) + pip.dispense(vol, disp) + if _ == reps - 1: + pip.flow_rate.aspirate = 100 + pip.flow_rate.dispense = 75 + pip.aspirate(vol, asp) + pip.dispense(vol, asp) + + pip.flow_rate.aspirate = 300 + pip.flow_rate.dispense = 300 + + def lysis(vol: float, source: Well) -> None: + """Lysis Steps.""" + ctx.comment("-----Beginning lysis steps-----") + num_transfers = math.ceil(vol / 180) + tiptrack(m1000, tips) + for i in range(num_cols): + src = source + tvol = vol / num_transfers + for t in range(num_transfers): + m1000.require_liquid_presence(src) + m1000.aspirate(tvol, src.bottom(1)) + m1000.dispense(m1000.current_volume, cells_m[i].top(-3)) + + # mix after adding all reagent to wells with cells + for i in range(num_cols): + if i != 0: + tiptrack(m1000, tips) + for x in range(8 if not dry_run else 1): + m1000.aspirate(tvol * 0.75, cells_m[i].bottom(dot_bottom)) + m1000.dispense(tvol * 0.75, cells_m[i].bottom(8)) + if x == 3: + ctx.delay(minutes=0.0167) + m1000.blow_out(cells_m[i].bottom(1)) + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + + helpers.set_hs_speed(ctx, h_s, heater_shaker_speed, lysis_time, True) + + def bind() -> None: + """Bind. + + `bind` will perform magnetic bead binding on each sample in the + deepwell plate. Each channel of binding beads will be mixed before + transfer, and the samples will be mixed with the binding beads after + the transfer. The magnetic deck activates after the addition to all + samples, and the supernatant is removed after bead binding. + :param vol (float): The amount of volume to aspirate from the elution + buffer source and dispense to each well containing + beads. + :param park (boolean): Whether to save sample-corresponding tips + between adding elution buffer and transferring + supernatant to the final clean elutions PCR + plate. + """ + ctx.comment("-----Beginning bind steps-----") + for i, well in enumerate(samples_m): + # Transfer cells+lysis/bind to wells with beads + tiptrack(m1000, tips) + m1000.aspirate(185, cells_m[i].bottom(dot_bottom)) + m1000.air_gap(10) + m1000.dispense(m1000.current_volume, well.bottom(8)) + # Mix after transfer + bead_mixing(well, m1000, 130, reps=5 if not dry_run else 1) + m1000.air_gap(10) + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + helpers.set_hs_speed(ctx, h_s, heater_shaker_speed, bind_time, True) + + # Transfer from H-S plate to Magdeck plate + helpers.move_labware_from_hs_to_destination(ctx, sample_plate, h_s, magblock) + + for bindi in np.arange( + settling_time, 0, -0.5 + ): # Settling time delay with countdown timer + ctx.delay( + minutes=0.5, + msg="There are " + str(bindi) + " minutes left in the incubation.", + ) + + # remove initial supernatant + remove_supernatant(180) + + def wash(vol: float, source: Well) -> None: + """Wash Function.""" + global whichwash # Defines which wash the protocol is on to log on the app + + if source == wash1: + whichwash = 1 + if source == wash2: + whichwash = 2 + if source == wash3: + whichwash = 3 + if source == wash4: + whichwash = 4 + + ctx.comment("-----Now starting Wash #" + str(whichwash) + "-----") + + tiptrack(m1000, tips) + num_trans = math.ceil(vol / 180) + vol_per_trans = vol / num_trans + for i, m in enumerate(samples_m): + src = source + for n in range(num_trans): + m1000.aspirate(vol_per_trans, src) + m1000.air_gap(10) + m1000.dispense(m1000.current_volume, m.top(-2)) + ctx.delay(seconds=2) + m1000.blow_out(m.top(-2)) + m1000.air_gap(10) + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + + # Shake for 5 minutes to mix wash with beads + helpers.set_hs_speed(ctx, h_s, heater_shaker_speed, wash_time, True) + + # Transfer from H-S plate to Magdeck plate + helpers.move_labware_from_hs_to_destination(ctx, sample_plate, h_s, magblock) + + for washi in np.arange( + settling_time, 0, -0.5 + ): # settling time timer for washes + ctx.delay( + minutes=0.5, + msg="There are " + + str(washi) + + " minutes left in wash " + + str(whichwash) + + " incubation.", + ) + + remove_supernatant(vol) + + def dnase(vol: float, source: List[Well]) -> None: + """Steps for DNAseI.""" + ctx.comment("-----DNAseI Steps Beginning-----") + num_trans = math.ceil(vol / 180) + vol_per_trans = vol / num_trans + tiptrack(m1000, tips) + for i, m in enumerate(samples_m): + src = source[i] + m1000.flow_rate.aspirate = 10 + for n in range(num_trans): + if m1000.current_volume > 0: + m1000.dispense(m1000.current_volume, src.top()) + m1000.aspirate(vol_per_trans, src.bottom(dot_bottom)) + m1000.dispense(vol_per_trans, m.top(-3)) + m1000.blow_out(m.top(-3)) + m1000.air_gap(20) + + m1000.flow_rate.aspirate = 300 + + # Is this mixing needed? \/\/\/ + for i in range(num_cols): + if i != 0: + tiptrack(m1000, tips) + mixing(samples_m[i], m1000, 45, reps=5 if not dry_run else 1) + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + + # Shake for 10 minutes to mix DNAseI + helpers.set_hs_speed(ctx, h_s, heater_shaker_speed, dnase_time, True) + + def stop_reaction(vol: float, source: Well) -> None: + """Adding stop solution.""" + ctx.comment("-----Adding Stop Solution-----") + tiptrack(m1000, tips) + num_trans = math.ceil(vol / 180) + vol_per_trans = vol / num_trans + for i, m in enumerate(samples_m): + src = source + for n in range(num_trans): + if m1000.current_volume > 0: + m1000.dispense(m1000.current_volume, src.top()) + m1000.transfer(vol_per_trans, src, m.top(), air_gap=20, new_tip="never") + m1000.blow_out(m.top(-3)) + m1000.air_gap(20) + + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + + # Shake for 3 minutes to mix wash with beads + helpers.set_hs_speed(ctx, h_s, heater_shaker_speed, stop_time, True) + + # Transfer from H-S plate to Magdeck plate + helpers.move_labware_from_hs_to_destination(ctx, sample_plate, h_s, magblock) + + for stop in np.arange(settling_time, 0, -0.5): + ctx.delay( + minutes=0.5, + msg="There are " + str(stop) + " minutes left in this incubation.", + ) + + remove_supernatant(vol + 50) + + def elute(vol: float) -> None: + """Elution.""" + ctx.comment("-----Elution Beginning-----") + tiptrack(m1000, tips) + m1000.flow_rate.aspirate = 10 + for i, m in enumerate(samples_m): + loc = m.top(-2) + m1000.aspirate(vol, elution_solution[i]) + m1000.air_gap(10) + m1000.dispense(m1000.current_volume, loc) + m1000.blow_out(m.top(-3)) + m1000.air_gap(10) + + m1000.flow_rate.aspirate = 300 + + # Is this mixing needed? \/\/\/ + for i in range(num_cols): + if i != 0: + tiptrack(m1000, tips) + for mixes in range(10): + m1000.aspirate(elution_vol - 10, samples_m[i]) + m1000.dispense(elution_vol - 10, samples_m[i].bottom(10)) + if mixes == 9: + m1000.flow_rate.dispense = 20 + m1000.aspirate(elution_vol - 10, samples_m[i]) + m1000.dispense(elution_vol - 10, samples_m[i].bottom(10)) + m1000.flow_rate.dispense = 300 + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + + # Shake for 3 minutes to mix wash with beads + helpers.set_hs_speed(ctx, h_s, heater_shaker_speed, elute_time, True) + + # Transfer from H-S plate to Magdeck plate + helpers.move_labware_from_hs_to_destination(ctx, sample_plate, h_s, magblock) + + for elutei in np.arange(settling_time, 0, -0.5): + ctx.delay( + minutes=0.5, + msg="Incubating on MagDeck for " + str(elutei) + " more minutes.", + ) + + ctx.comment("-----Trasnferring Sample to Elution Plate-----") + for i, (m, e) in enumerate(zip(samples_m, elution_samples_m)): + tiptrack(m1000, tips) + loc = m.bottom(dot_bottom) + m1000.transfer(vol, loc, e.bottom(5), air_gap=20, new_tip="never") + m1000.blow_out(e.top(-2)) + m1000.air_gap(20) + m1000.drop_tip() if TIP_TRASH else m1000.return_tip() + + """ + Here is where you can call the methods defined above to fit your specific + protocol. The normal sequence is: + """ + if inc_lysis: + lysis(lysis_vol, lysis_) + bind() + wash(wash_vol, wash1) + wash(wash_vol, wash2) + # dnase1 treatment + dnase(dnase_vol, dnase1) + stop_reaction(stop_vol, stopreaction) + # Resume washes + wash(wash_vol, wash3) + wash(wash_vol, wash4) + wash(wash_vol, wash5) + + for beaddry in np.arange(drybeads, 0, -0.5): + ctx.delay( + minutes=0.5, + msg="There are " + str(beaddry) + " minutes left in the drying step.", + ) + elute(elution_vol) + + end_list_of_wells_to_probe = [waste_reservoir["A1"], res1["A1"]] + end_list_of_wells_to_probe.extend(elution_samples_m) + helpers.find_liquid_height_of_all_wells(ctx, m1000, end_list_of_wells_to_probe) diff --git a/abr-testing/abr_testing/protocols/csv_parameters/2_samplevols.csv b/abr-testing/abr_testing/protocols/csv_parameters/2_samplevols.csv new file mode 100644 index 00000000000..fa50562e68b --- /dev/null +++ b/abr-testing/abr_testing/protocols/csv_parameters/2_samplevols.csv @@ -0,0 +1,25 @@ +Destination Well,Water Transfer Volume (ul),DNA Transfer Volume (ul),Mastermix Volume (ul),Mastermix Source Tube +A1,3,7,40,A1 +B1,0,10,40,A1 +C1,10,0,40,A1 +D1,3,7,40,A1 +E1,2,8,40,A2 +F1,1,9,40,A2 +G1,5,5,40,A2 +H1,3,7,40,A3 +A2,3,7,40,A3 +B2,3,7,40,A3 +C2,3,7,40,A3 +D2,3,7,40,A3 +E2,3,7,40,A3 +F2,3,7,40,A3 +G2,3,7,40,A3 +H2,3,7,40,A3 +A3,3,7,40,A3 +B3,3,7,40,A3 +C3,3,7,45,A3 +D3,3,7,45,A3 +E3,3,5,45,A3 +F3,3,5,45,A3 +G3,3,5,45,A6 +H3,3,4,45,A5 \ No newline at end of file diff --git a/abr-testing/abr_testing/protocols/custom_labware/hellma_reference_plate.json b/abr-testing/abr_testing/protocols/custom_labware/hellma_reference_plate.json new file mode 100644 index 00000000000..0abef3ca15f --- /dev/null +++ b/abr-testing/abr_testing/protocols/custom_labware/hellma_reference_plate.json @@ -0,0 +1,1017 @@ +{ + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "brand": { + "brand": "Hellma", + "brandId": ["666-R013 Reference Plate"] + }, + "metadata": { + "displayName": "Hellma Reference Plate", + "displayCategory": "wellPlate", + "displayVolumeUnits": "µL", + "tags": [] + }, + "dimensions": { + "xDimension": 127, + "yDimension": 85.5, + "zDimension": 13 + }, + "wells": { + "A1": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 14.38, + "y": 74.26, + "z": 12 + }, + "B1": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 14.38, + "y": 65.26, + "z": 12 + }, + "C1": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 14.38, + "y": 56.26, + "z": 12 + }, + "D1": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 14.38, + "y": 47.26, + "z": 12 + }, + "E1": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 14.38, + "y": 38.26, + "z": 12 + }, + "F1": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 14.38, + "y": 29.26, + "z": 12 + }, + "G1": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 14.38, + "y": 20.26, + "z": 12 + }, + "H1": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 14.38, + "y": 11.26, + "z": 12 + }, + "A2": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 23.38, + "y": 74.26, + "z": 12 + }, + "B2": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 23.38, + "y": 65.26, + "z": 12 + }, + "C2": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 23.38, + "y": 56.26, + "z": 12 + }, + "D2": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 23.38, + "y": 47.26, + "z": 12 + }, + "E2": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 23.38, + "y": 38.26, + "z": 12 + }, + "F2": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 23.38, + "y": 29.26, + "z": 12 + }, + "G2": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 23.38, + "y": 20.26, + "z": 12 + }, + "H2": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 23.38, + "y": 11.26, + "z": 12 + }, + "A3": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 32.38, + "y": 74.26, + "z": 12 + }, + "B3": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 32.38, + "y": 65.26, + "z": 12 + }, + "C3": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 32.38, + "y": 56.26, + "z": 12 + }, + "D3": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 32.38, + "y": 47.26, + "z": 12 + }, + "E3": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 32.38, + "y": 38.26, + "z": 12 + }, + "F3": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 32.38, + "y": 29.26, + "z": 12 + }, + "G3": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 32.38, + "y": 20.26, + "z": 12 + }, + "H3": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 32.38, + "y": 11.26, + "z": 12 + }, + "A4": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 41.38, + "y": 74.26, + "z": 12 + }, + "B4": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 41.38, + "y": 65.26, + "z": 12 + }, + "C4": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 41.38, + "y": 56.26, + "z": 12 + }, + "D4": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 41.38, + "y": 47.26, + "z": 12 + }, + "E4": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 41.38, + "y": 38.26, + "z": 12 + }, + "F4": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 41.38, + "y": 29.26, + "z": 12 + }, + "G4": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 41.38, + "y": 20.26, + "z": 12 + }, + "H4": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 41.38, + "y": 11.26, + "z": 12 + }, + "A5": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 50.38, + "y": 74.26, + "z": 12 + }, + "B5": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 50.38, + "y": 65.26, + "z": 12 + }, + "C5": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 50.38, + "y": 56.26, + "z": 12 + }, + "D5": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 50.38, + "y": 47.26, + "z": 12 + }, + "E5": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 50.38, + "y": 38.26, + "z": 12 + }, + "F5": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 50.38, + "y": 29.26, + "z": 12 + }, + "G5": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 50.38, + "y": 20.26, + "z": 12 + }, + "H5": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 50.38, + "y": 11.26, + "z": 12 + }, + "A6": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 59.38, + "y": 74.26, + "z": 12 + }, + "B6": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 59.38, + "y": 65.26, + "z": 12 + }, + "C6": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 59.38, + "y": 56.26, + "z": 12 + }, + "D6": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 59.38, + "y": 47.26, + "z": 12 + }, + "E6": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 59.38, + "y": 38.26, + "z": 12 + }, + "F6": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 59.38, + "y": 29.26, + "z": 12 + }, + "G6": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 59.38, + "y": 20.26, + "z": 12 + }, + "H6": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 59.38, + "y": 11.26, + "z": 12 + }, + "A7": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 68.38, + "y": 74.26, + "z": 12 + }, + "B7": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 68.38, + "y": 65.26, + "z": 12 + }, + "C7": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 68.38, + "y": 56.26, + "z": 12 + }, + "D7": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 68.38, + "y": 47.26, + "z": 12 + }, + "E7": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 68.38, + "y": 38.26, + "z": 12 + }, + "F7": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 68.38, + "y": 29.26, + "z": 12 + }, + "G7": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 68.38, + "y": 20.26, + "z": 12 + }, + "H7": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 68.38, + "y": 11.26, + "z": 12 + }, + "A8": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 77.38, + "y": 74.26, + "z": 12 + }, + "B8": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 77.38, + "y": 65.26, + "z": 12 + }, + "C8": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 77.38, + "y": 56.26, + "z": 12 + }, + "D8": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 77.38, + "y": 47.26, + "z": 12 + }, + "E8": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 77.38, + "y": 38.26, + "z": 12 + }, + "F8": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 77.38, + "y": 29.26, + "z": 12 + }, + "G8": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 77.38, + "y": 20.26, + "z": 12 + }, + "H8": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 77.38, + "y": 11.26, + "z": 12 + }, + "A9": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 86.38, + "y": 74.26, + "z": 12 + }, + "B9": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 86.38, + "y": 65.26, + "z": 12 + }, + "C9": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 86.38, + "y": 56.26, + "z": 12 + }, + "D9": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 86.38, + "y": 47.26, + "z": 12 + }, + "E9": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 86.38, + "y": 38.26, + "z": 12 + }, + "F9": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 86.38, + "y": 29.26, + "z": 12 + }, + "G9": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 86.38, + "y": 20.26, + "z": 12 + }, + "H9": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 86.38, + "y": 11.26, + "z": 12 + }, + "A10": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 95.38, + "y": 74.26, + "z": 12 + }, + "B10": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 95.38, + "y": 65.26, + "z": 12 + }, + "C10": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 95.38, + "y": 56.26, + "z": 12 + }, + "D10": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 95.38, + "y": 47.26, + "z": 12 + }, + "E10": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 95.38, + "y": 38.26, + "z": 12 + }, + "F10": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 95.38, + "y": 29.26, + "z": 12 + }, + "G10": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 95.38, + "y": 20.26, + "z": 12 + }, + "H10": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 95.38, + "y": 11.26, + "z": 12 + }, + "A11": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 104.38, + "y": 74.26, + "z": 12 + }, + "B11": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 104.38, + "y": 65.26, + "z": 12 + }, + "C11": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 104.38, + "y": 56.26, + "z": 12 + }, + "D11": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 104.38, + "y": 47.26, + "z": 12 + }, + "E11": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 104.38, + "y": 38.26, + "z": 12 + }, + "F11": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 104.38, + "y": 29.26, + "z": 12 + }, + "G11": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 104.38, + "y": 20.26, + "z": 12 + }, + "H11": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 104.38, + "y": 11.26, + "z": 12 + }, + "A12": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 113.38, + "y": 74.26, + "z": 12 + }, + "B12": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 113.38, + "y": 65.26, + "z": 12 + }, + "C12": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 113.38, + "y": 56.26, + "z": 12 + }, + "D12": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 113.38, + "y": 47.26, + "z": 12 + }, + "E12": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 113.38, + "y": 38.26, + "z": 12 + }, + "F12": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 113.38, + "y": 29.26, + "z": 12 + }, + "G12": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 113.38, + "y": 20.26, + "z": 12 + }, + "H12": { + "depth": 1, + "totalLiquidVolume": 1, + "shape": "circular", + "diameter": 6.6, + "x": 113.38, + "y": 11.26, + "z": 12 + } + }, + "groups": [ + { + "metadata": { + "wellBottomShape": "flat" + }, + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ] + } + ], + "parameters": { + "format": "irregular", + "quirks": [], + "isTiprack": false, + "isMagneticModuleCompatible": false, + "loadName": "hellma_reference_plate" + }, + "namespace": "custom_beta", + "version": 1, + "schemaVersion": 2, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + } +} diff --git a/abr-testing/abr_testing/protocols/helpers.py b/abr-testing/abr_testing/protocols/helpers.py new file mode 100644 index 00000000000..12abbfa9b3f --- /dev/null +++ b/abr-testing/abr_testing/protocols/helpers.py @@ -0,0 +1,506 @@ +"""Helper functions commonly used in protocols.""" + +from opentrons.protocol_api import ( + ProtocolContext, + Labware, + InstrumentContext, + ParameterContext, + Well, +) +from typing import Tuple +from opentrons.protocol_api.module_contexts import ( + HeaterShakerContext, + MagneticBlockContext, + ThermocyclerContext, + TemperatureModuleContext, +) +from typing import List, Union, Dict +from opentrons.hardware_control.modules.types import ThermocyclerStep +from opentrons_shared_data.errors.exceptions import PipetteLiquidNotFoundError + +# FUNCTIONS FOR LOADING COMMON CONFIGURATIONS + + +def load_common_liquid_setup_labware_and_instruments( + protocol: ProtocolContext, +) -> Tuple[Labware, Labware, InstrumentContext]: + """Load Commonly used Labware and Instruments.""" + # Tip rack + tip_rack = protocol.load_labware("opentrons_flex_96_tiprack_1000ul", "D1") + # Pipette + p1000 = protocol.load_instrument( + instrument_name="flex_8channel_1000", mount="left", tip_racks=[tip_rack] + ) + # Source_reservoir + source_reservoir = protocol.load_labware("nest_1_reservoir_290ml", "C2") + protocol.load_trash_bin("A3") + return source_reservoir, tip_rack, p1000 + + +def load_disposable_lids( + protocol: ProtocolContext, + num_of_lids: int, + deck_slot: List[str], + deck_riser: bool = False, +) -> List[Labware]: + """Load Stack of Disposable lids.""" + if deck_riser: + deck_riser_adapter = protocol.load_adapter( + "opentrons_flex_deck_riser", deck_slot[0] + ) + unused_lids = [ + deck_riser_adapter.load_labware("opentrons_tough_pcr_auto_sealing_lid") + ] + else: + unused_lids = [ + protocol.load_labware("opentrons_tough_pcr_auto_sealing_lid", deck_slot[0]) + ] + + if len(deck_slot) == 1: + for i in range(num_of_lids - 1): + unused_lids.append( + unused_lids[-1].load_labware("opentrons_tough_pcr_auto_sealing_lid") + ) + else: + for i in range(len(deck_slot) - 1): + unused_lids.append( + protocol.load_labware( + "opentrons_tough_pcr_auto_sealing_lid", deck_slot[i] + ) + ) + unused_lids.reverse() + return unused_lids + + +def load_hs_adapter_and_labware( + labware_str: str, heatershaker: HeaterShakerContext, labware_name: str +) -> Tuple[Labware, Labware]: + """Load appropriate adapter on heatershaker based off labware type.""" + heatershaker_adapters = { + "nest_96_wellplate_2ml_deep": "opentrons_96_deep_well_adapter", + "armadillo_96_wellplate_200ul_pcr_full_skirt": "opentrons_96_pcr_adapter", + } + hs_adapter_type = heatershaker_adapters.get(labware_str, "") + if hs_adapter_type: + hs_adapter = heatershaker.load_adapter(hs_adapter_type) + labware_on_hs = hs_adapter.load_labware(labware_str, labware_name) + else: + heatershaker.load_labware(labware_str, labware_name) + return labware_on_hs, hs_adapter + + +def load_temp_adapter_and_labware( + labware_str: str, temp_mod: TemperatureModuleContext, labware_name: str +) -> Tuple[Labware, Labware]: + """Load appropriate adapter on temperature module based off labware type.""" + temp_mod_adapters = { + "nest_96_wellplate_2ml_deep": "opentrons_96_deep_well_temp_mod_adapter", + "armadillo_96_wellplate_200ul_pcr_full_skirt": "opentrons_96_well_aluminum_block", + } + temp_adapter_type = temp_mod_adapters.get(labware_str, "") + if temp_adapter_type: + temp_adapter = temp_mod.load_adapter(temp_adapter_type) + labware_on_temp_mod = temp_adapter.load_labware(labware_str, labware_name) + else: + labware_on_temp_mod = temp_mod.load_labware(labware_str, labware_name) + return labware_on_temp_mod, temp_adapter + + +# FUNCTIONS FOR LOADING COMMON PARAMETERS + + +def create_single_pipette_mount_parameter(parameters: ParameterContext) -> None: + """Create parameter to specify pipette mount.""" + parameters.add_str( + variable_name="pipette_mount", + display_name="Pipette Mount", + choices=[ + {"display_name": "Left", "value": "left"}, + {"display_name": "Right", "value": "right"}, + ], + default="left", + ) + + +def create_two_pipette_mount_parameters(parameters: ParameterContext) -> None: + """Create mount parameters for 2 pipettes.""" + parameters.add_str( + variable_name="pipette_mount_1", + display_name="Pipette Mount 1", + choices=[ + {"display_name": "Left", "value": "left"}, + {"display_name": "Right", "value": "right"}, + ], + default="left", + ) + parameters.add_str( + variable_name="pipette_mount_2", + display_name="Pipette Mount 2", + choices=[ + {"display_name": "Left", "value": "left"}, + {"display_name": "Right", "value": "right"}, + ], + default="right", + ) + + +def create_csv_parameter(parameters: ParameterContext) -> None: + """Create parameter for sample volume csvs.""" + parameters.add_csv_file( + variable_name="parameters_csv", + display_name="Sample CSV", + description="CSV File for Protocol.", + ) + + +def create_disposable_lid_parameter(parameters: ParameterContext) -> None: + """Create parameter to use/not use disposable lid.""" + parameters.add_bool( + variable_name="disposable_lid", + display_name="Disposable Lid", + description="True means use lid.", + default=True, + ) + + +def create_tc_lid_deck_riser_parameter(parameters: ParameterContext) -> None: + """Create parameter for tc lid deck riser.""" + parameters.add_bool( + variable_name="deck_riser", + display_name="Deck Riser", + description="True means use deck riser.", + default=False, + ) + + +def create_tip_size_parameter(parameters: ParameterContext) -> None: + """Create parameter for tip size.""" + parameters.add_str( + variable_name="tip_size", + display_name="Tip Size", + description="Set Tip Size", + choices=[ + {"display_name": "50 uL", "value": "opentrons_flex_96_tiprack_50ul"}, + {"display_name": "200 µL", "value": "opentrons_flex_96_tiprack_200ul"}, + {"display_name": "1000 µL", "value": "opentrons_flex_96_tiprack_1000ul"}, + ], + default="opentrons_flex_96_tiprack_1000ul", + ) + + +def create_dot_bottom_parameter(parameters: ParameterContext) -> None: + """Create parameter for dot bottom value.""" + parameters.add_float( + variable_name="dot_bottom", + display_name=".bottom", + description="Lowest value pipette will go to.", + default=0.3, + choices=[ + {"display_name": "0.0", "value": 0.0}, + {"display_name": "0.1", "value": 0.1}, + {"display_name": "0.2", "value": 0.2}, + {"display_name": "0.3", "value": 0.3}, + {"display_name": "0.4", "value": 0.4}, + {"display_name": "0.5", "value": 0.5}, + {"display_name": "0.6", "value": 0.6}, + {"display_name": "0.7", "value": 0.7}, + {"display_name": "0.8", "value": 0.8}, + {"display_name": "0.9", "value": 0.9}, + {"display_name": "1.0", "value": 1.0}, + ], + ) + + +def create_hs_speed_parameter(parameters: ParameterContext) -> None: + """Create parameter for max heatershaker speed.""" + parameters.add_int( + variable_name="heater_shaker_speed", + display_name="Heater Shaker Shake Speed", + description="Speed to set the heater shaker to", + default=2000, + minimum=200, + maximum=3000, + unit="rpm", + ) + + +def create_tc_compatible_labware_parameter(parameters: ParameterContext) -> None: + """Create parameter for labware type compatible with thermocycler.""" + parameters.add_str( + variable_name="labware_tc_compatible", + display_name="Labware Type for Thermocycler", + description="labware compatible with thermocycler.", + default="biorad_96_wellplate_200ul_pcr", + choices=[ + { + "display_name": "Armadillo_200ul", + "value": "armadillo_96_wellplate_200ul_pcr_full_skirt", + }, + {"display_name": "Bio-Rad_200ul", "value": "biorad_96_wellplate_200ul_pcr"}, + { + "display_name": "NEST_100ul", + "value": "nest_96_wellplate_100ul_pcr_full_skirt", + }, + { + "display_name": "Opentrons_200ul", + "value": "opentrons_96_wellplate_200ul_pcr_full_skirt", + }, + ], + ) + + +# FUNCTIONS FOR COMMON MODULE SEQUENCES + + +def move_labware_from_hs_to_destination( + protocol: ProtocolContext, + labware_to_move: Labware, + hs: HeaterShakerContext, + new_module: Union[MagneticBlockContext, ThermocyclerContext], +) -> None: + """Move labware from heatershaker to magnetic block.""" + hs.open_labware_latch() + protocol.move_labware(labware_to_move, new_module, use_gripper=True) + hs.close_labware_latch() + + +def move_labware_to_hs( + protocol: ProtocolContext, + labware_to_move: Labware, + hs: HeaterShakerContext, + hs_adapter: Union[Labware, HeaterShakerContext], +) -> None: + """Move labware to heatershaker.""" + hs.open_labware_latch() + protocol.move_labware(labware_to_move, hs_adapter, use_gripper=True) + hs.close_labware_latch() + + +def set_hs_speed( + protocol: ProtocolContext, + hs: HeaterShakerContext, + hs_speed: int, + time_min: float, + deactivate: bool, +) -> None: + """Set heatershaker for a speed and duration.""" + hs.close_labware_latch() + hs.set_and_wait_for_shake_speed(hs_speed) + protocol.delay( + minutes=time_min, + msg=f"Shake at {hs_speed} rpm for {time_min} minutes.", + ) + if deactivate: + hs.deactivate_shaker() + + +def use_disposable_lid_with_tc( + protocol: ProtocolContext, + unused_lids: List[Labware], + used_lids: List[Labware], + plate_in_thermocycler: Labware, + thermocycler: ThermocyclerContext, +) -> Tuple[Labware, List[Labware], List[Labware]]: + """Use disposable lid with thermocycler.""" + lid_on_plate = unused_lids[0] + protocol.move_labware(lid_on_plate, plate_in_thermocycler, use_gripper=True) + # Remove lid from the list + unused_lids.pop(0) + used_lids.append(lid_on_plate) + thermocycler.close_lid() + return lid_on_plate, unused_lids, used_lids + + +# FUNCTIONS FOR COMMON PIPETTE COMMAND SEQUENCES + + +def find_liquid_height(pipette: InstrumentContext, well_to_probe: Well) -> float: + """Find liquid height of well.""" + try: + liquid_height = ( + pipette.measure_liquid_height(well_to_probe) + - well_to_probe.bottom().point.z + ) + except PipetteLiquidNotFoundError: + liquid_height = 0 + return liquid_height + + +def load_wells_with_custom_liquids( + protocol: ProtocolContext, + liquid_vols_and_wells: Dict[str, List[Dict[str, Union[Well, List[Well], float]]]], +) -> None: + """Load custom liquids into wells.""" + liquid_colors = [ + "#008000", + "#A52A2A", + "#00FFFF", + "#0000FF", + "#800080", + "#ADD8E6", + "#FF0000", + "#FFFF00", + "#FF00FF", + "#00008B", + "#7FFFD4", + "#FFC0CB", + "#FFA500", + "#00FF00", + "#C0C0C0", + ] + i = 0 + volume = 0.0 + for liquid_name, wells_info in liquid_vols_and_wells.items(): + # Define the liquid with a color + liquid = protocol.define_liquid( + liquid_name, display_color=liquid_colors[i % len(liquid_colors)] + ) + i += 1 + # Load liquid into each specified well or list of wells + for well_info in wells_info: + if isinstance(well_info["well"], list): + wells = well_info["well"] + elif isinstance(well_info["well"], Well): + wells = [well_info["well"]] + else: + wells = [] + if isinstance(well_info["volume"], float): + volume = well_info["volume"] + + # Load liquid into each well + for well in wells: + well.load_liquid(liquid, volume) + + +def find_liquid_height_of_all_wells( + protocol: ProtocolContext, + pipette: InstrumentContext, + wells: List[Well], +) -> Dict: + """Find the liquid height of all wells in protocol.""" + dict_of_labware_heights = {} + pipette.pick_up_tip() + pip_channels = pipette.active_channels + for well in wells: + labware_name = well.parent.load_name + total_number_of_wells_in_plate = len(well.parent.wells()) + # if pip_channels is > 1 and total_wells > 12 - only probe 1st row. + if ( + pip_channels > 1 + and total_number_of_wells_in_plate > 12 + and well.well_name.startswith("A") + ): + liquid_height_of_well = find_liquid_height(pipette, well) + dict_of_labware_heights[labware_name, well] = liquid_height_of_well + elif total_number_of_wells_in_plate <= 12: + liquid_height_of_well = find_liquid_height(pipette, well) + dict_of_labware_heights[labware_name, well] = liquid_height_of_well + if pip_channels != pipette.channels: + pipette.drop_tip() + else: + pipette.return_tip() + pipette.reset_tipracks() + msg = f"result: {dict_of_labware_heights}" + protocol.comment(msg=msg) + return dict_of_labware_heights + + +def find_liquid_height_of_loaded_liquids( + ctx: ProtocolContext, + liquid_vols_and_wells: Dict[str, List[Dict[str, Union[Well, List[Well], float]]]], + pipette: InstrumentContext, +) -> List[Well]: + """Find Liquid height of loaded liquids.""" + load_wells_with_custom_liquids(ctx, liquid_vols_and_wells) + # Get flattened list of wells. + wells: list[Well] = [ + well + for items in liquid_vols_and_wells.values() + for entry in items + if isinstance(entry["well"], (Well, list)) and entry["volume"] != 0.0 + # Ensure "well" is Well or list of Well + for well in ( + entry["well"] if isinstance(entry["well"], list) else [entry["well"]] + ) + ] + find_liquid_height_of_all_wells(ctx, pipette, wells) + return wells + + +def load_wells_with_water( + protocol: ProtocolContext, wells: List[Well], volumes: List[float] +) -> None: + """Load liquids into wells.""" + water = protocol.define_liquid("Water", display_color="#0000FF") + for well, volume in zip(wells, volumes): + well.load_liquid(water, volume) + + +# CONSTANTS + +hs_str = "heaterShakerModuleV1" +mag_str = "magneticBlockV1" +temp_str = "temperature module gen2" +tc_str = "thermocycler module gen2" +abs_mod_str = "absorbanceReaderV1" +liquid_colors = [ + "#008000", + "#008000", + "#A52A2A", + "#A52A2A", + "#00FFFF", + "#0000FF", + "#800080", + "#ADD8E6", + "#FF0000", + "#FFFF00", + "#FF00FF", + "#00008B", + "#7FFFD4", + "#FFC0CB", + "#FFA500", + "#00FF00", + "#C0C0C0", +] + +# THERMOCYCLER PROFILES + + +def perform_pcr( + protocol: ProtocolContext, + thermocycler: ThermocyclerContext, + initial_denature_time_sec: int, + denaturation_time_sec: int, + anneal_time_sec: int, + extension_time_sec: int, + cycle_repetitions: int, + final_extension_time_min: int, +) -> None: + """Perform PCR.""" + # Define profiles. + initial_denaturation_profile: List[ThermocyclerStep] = [ + {"temperature": 98, "hold_time_seconds": initial_denature_time_sec} + ] + cycling_profile: List[ThermocyclerStep] = [ + {"temperature": 98, "hold_time_seconds": denaturation_time_sec}, + {"temperature": 60, "hold_time_seconds": anneal_time_sec}, + {"temperature": 72, "hold_time_seconds": extension_time_sec}, + ] + final_extension_profile: List[ThermocyclerStep] = [ + {"temperature": 72, "hold_time_minutes": final_extension_time_min} + ] + protocol.comment(f"Initial Denaturation for {initial_denature_time_sec} seconds.") + thermocycler.execute_profile( + steps=initial_denaturation_profile, repetitions=1, block_max_volume=50 + ) + protocol.comment(f"PCR for {cycle_repetitions} cycles.") + thermocycler.execute_profile( + steps=cycling_profile, repetitions=cycle_repetitions, block_max_volume=50 + ) + protocol.comment(f"Final Extension profile for {final_extension_time_min} minutes.") + thermocycler.execute_profile( + steps=final_extension_profile, repetitions=1, block_max_volume=50 + ) + + +# TODO: Create dictionary of labware, module, and adapter. diff --git a/abr-testing/abr_testing/protocols/liquid_setups/10_ZymoBIOMICS Magbead Liquid Setup.py b/abr-testing/abr_testing/protocols/liquid_setups/10_ZymoBIOMICS Magbead Liquid Setup.py new file mode 100644 index 00000000000..422102e4321 --- /dev/null +++ b/abr-testing/abr_testing/protocols/liquid_setups/10_ZymoBIOMICS Magbead Liquid Setup.py @@ -0,0 +1,75 @@ +"""Plate Filler Protocol for Zymobiomics DNA Extraction.""" +from opentrons import protocol_api +from abr_testing.protocols.helpers import ( + load_common_liquid_setup_labware_and_instruments, +) + + +metadata = { + "protocolName": "PVT1ABR10 Liquids: ZymoBIOMICS Magbead DNA Extraction", + "author": "Rhyann Clarke ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def run(protocol: protocol_api.ProtocolContext) -> None: + """Protocol.""" + # Initiate Labware + ( + source_reservoir, + tip_rack, + p1000, + ) = load_common_liquid_setup_labware_and_instruments(protocol) + + res1 = protocol.load_labware("nest_12_reservoir_15ml", "C3", "R1") + res2 = protocol.load_labware("nest_12_reservoir_15ml", "B3", "R2") + + lysis_and_pk = 12320 / 8 + beads_and_binding = 11875 / 8 + binding2 = 13500 / 8 + wash2 = 9000 / 8 + wash2_list = [wash2] * 12 + # Fill up Plates + # Res1 + p1000.transfer( + volume=[ + lysis_and_pk, + beads_and_binding, + beads_and_binding, + beads_and_binding, + binding2, + binding2, + binding2, + binding2, + binding2, + ], + source=source_reservoir["A1"].bottom(z=0.2), + dest=[ + res1["A1"].top(), + res1["A2"].top(), + res1["A3"].top(), + res1["A4"].top(), + res1["A5"].top(), + res1["A6"].top(), + res1["A7"].top(), + res1["A8"].top(), + res1["A12"].top(), + ], + blow_out=True, + blowout_location="source well", + trash=False, + ) + # Res2 + p1000.transfer( + volume=wash2_list, + source=source_reservoir["A1"], + dest=res2.wells(), + blow_out=True, + blowout_location="source well", + trash=False, + ) diff --git a/abr-testing/abr_testing/protocols/liquid_setups/11_Dynabeads RIT Liquid Setup.py b/abr-testing/abr_testing/protocols/liquid_setups/11_Dynabeads RIT Liquid Setup.py new file mode 100644 index 00000000000..112aec315b5 --- /dev/null +++ b/abr-testing/abr_testing/protocols/liquid_setups/11_Dynabeads RIT Liquid Setup.py @@ -0,0 +1,61 @@ +"""Plate Filler Protocol for Immunoprecipitation by Dynabeads.""" +from opentrons import protocol_api +from abr_testing.protocols.helpers import ( + load_common_liquid_setup_labware_and_instruments, +) + + +metadata = { + "protocolName": "PVT1ABR11 Liquids: Immunoprecipitation by Dynabeads - 96-well", + "author": "Rhyann Clarke ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def run(protocol: protocol_api.ProtocolContext) -> None: + """Protocol.""" + # Deck Setup + ( + source_reservoir, + tip_rack, + p1000, + ) = load_common_liquid_setup_labware_and_instruments(protocol) + + reservoir_wash = protocol.load_labware("nest_12_reservoir_15ml", "D2", "Reservoir") + sample_plate = protocol.load_labware( + "nest_96_wellplate_2ml_deep", "C3", "Sample Plate" + ) + + columns = [ + "A1", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "A10", + "A11", + "A12", + ] + # 1 column 6000 uL + p1000.pick_up_tip() + for i in columns: + p1000.aspirate(750, source_reservoir["A1"].bottom(z=0.5)) + p1000.dispense(750, reservoir_wash[i].top()) + p1000.blow_out(location=source_reservoir["A1"].top()) + p1000.return_tip() + # Nest 96 Deep Well Plate 2 mL: 250 uL per well + p1000.pick_up_tip() + for n in columns: + p1000.aspirate(250, source_reservoir["A1"].bottom(z=0.5)) + p1000.dispense(250, sample_plate[n].bottom(z=1)) + p1000.blow_out(location=source_reservoir["A1"].top()) + p1000.return_tip() diff --git a/abr-testing/abr_testing/protocols/liquid_setups/12_KAPA HyperPlus Library Prep Liquid Setup.py b/abr-testing/abr_testing/protocols/liquid_setups/12_KAPA HyperPlus Library Prep Liquid Setup.py new file mode 100644 index 00000000000..b4282397baf --- /dev/null +++ b/abr-testing/abr_testing/protocols/liquid_setups/12_KAPA HyperPlus Library Prep Liquid Setup.py @@ -0,0 +1,83 @@ +"""KAPA HyperPlus Library Preparation Liquid Setup.""" +from opentrons import protocol_api +from abr_testing.protocols.helpers import ( + load_common_liquid_setup_labware_and_instruments, +) + + +metadata = { + "protocolName": "PVT1ABR12: KAPA HyperPlus Library Preparation Liquid Setup", + "author": "Rhyann Clarke ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def run(protocol: protocol_api.ProtocolContext) -> None: + """Protocol.""" + ( + source_reservoir, + tip_rack, + p1000, + ) = load_common_liquid_setup_labware_and_instruments(protocol) + + reservoir = protocol.load_labware("nest_96_wellplate_2ml_deep", "D2") # Reservoir + temp_module_res = protocol.load_labware( + "opentrons_96_wellplate_200ul_pcr_full_skirt", "B3" + ) + sample_plate_1 = protocol.load_labware( + "opentrons_96_wellplate_200ul_pcr_full_skirt", "D3" + ) # Sample Plate + sample_plate_2 = protocol.load_labware( + "opentrons_96_wellplate_200ul_pcr_full_skirt", "C3" + ) # Sample Plate + + # Sample Plate 1 Prep: dispense 17 ul into column 1 total 136 ul + p1000.transfer( + volume=136, + source=source_reservoir["A1"].bottom(z=0.5), + dest=sample_plate_1["A1"], + blow_out=True, + blowout_location="source well", + trash=False, + ) + + # Sample Plate 2 Prep: dispense 17 ul into column 1 total 136 ul + p1000.transfer( + volume=136, + source=source_reservoir["A1"].bottom(z=0.5), + dest=sample_plate_2["A1"], + blow_out=True, + blowout_location="source well", + trash=False, + ) + + # Reservoir Plate Prep: + p1000.transfer( + volume=[1214.4, 396, 352, 352], + source=source_reservoir["A1"].bottom(z=0.5), + dest=[reservoir["A1"], reservoir["A4"], reservoir["A5"], reservoir["A6"]], + blow_out=True, + blowout_location="source well", + trash=False, + ) + + # Temp Module Res Prep: dispense 30 and 200 ul into columns 1 and 3 - total 1840 ul + p1000.transfer( + volume=[80, 88, 132, 200, 200], + source=source_reservoir["A1"].bottom(z=0.5), + dest=[ + temp_module_res["A1"], + temp_module_res["A2"], + temp_module_res["A3"], + temp_module_res["A4"], + temp_module_res["A5"], + ], + blow_out=True, + blowout_location="source well", + trash=False, + ) diff --git a/abr-testing/abr_testing/protocols/liquid_setups/1_Simple normalize long Liquid Setup.py b/abr-testing/abr_testing/protocols/liquid_setups/1_Simple normalize long Liquid Setup.py new file mode 100644 index 00000000000..2d995fede39 --- /dev/null +++ b/abr-testing/abr_testing/protocols/liquid_setups/1_Simple normalize long Liquid Setup.py @@ -0,0 +1,39 @@ +"""Plate Filler Protocol for Simple Normalize Long.""" +from opentrons import protocol_api +from abr_testing.protocols.helpers import ( + load_common_liquid_setup_labware_and_instruments, +) + +metadata = { + "protocolName": "DVT1ABR1 Liquids: Simple Normalize Long", + "author": "Rhyann clarke ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def run(protocol: protocol_api.ProtocolContext) -> None: + """Protocol.""" + # Initiate Labware + ( + source_reservoir, + tip_rack, + p1000, + ) = load_common_liquid_setup_labware_and_instruments(protocol) + reservoir = protocol.load_labware("nest_12_reservoir_15ml", "D2", "Reservoir") + # Transfer Liquid + vol = 5400 / 8 + columns = ["A1", "A2", "A3", "A4", "A5"] + for i in columns: + p1000.transfer( + vol, + source=source_reservoir["A1"].bottom(z=0.5), + dest=reservoir[i].top(), + blowout=True, + blowout_location="source well", + trash=False, + ) diff --git a/abr-testing/abr_testing/protocols/liquid_setups/2_BMS_PCR_protocol Liquid Setup.py b/abr-testing/abr_testing/protocols/liquid_setups/2_BMS_PCR_protocol Liquid Setup.py new file mode 100644 index 00000000000..a6c71b563d4 --- /dev/null +++ b/abr-testing/abr_testing/protocols/liquid_setups/2_BMS_PCR_protocol Liquid Setup.py @@ -0,0 +1,41 @@ +"""Plate Filler Protocol for Simple Normalize Long.""" +from opentrons import protocol_api +from abr_testing.protocols.helpers import ( + load_common_liquid_setup_labware_and_instruments, +) + + +metadata = { + "protocolName": "DVT1ABR2 Liquids: BMS PCR Protocol", + "author": "Rhyann clarke ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def run(protocol: protocol_api.ProtocolContext) -> None: + """Protocol.""" + # Initiate Labware + ( + source_reservoir, + tip_rack, + p1000, + ) = load_common_liquid_setup_labware_and_instruments(protocol) + pcr_plate_1 = protocol.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", "C3", "PCR Plate 1" + ) + snap_caps = protocol.load_labware( + "opentrons_24_aluminumblock_nest_1.5ml_snapcap", "B3", "Snap Caps" + ) + # Steps + # Dispense into plate 1 + p1000.transfer(50, source_reservoir["A1"], pcr_plate_1.wells(), trash=False) + + # Dispense + p1000.configure_nozzle_layout(protocol_api.SINGLE, start="H1", tip_racks=[tip_rack]) + p1000.transfer(1500, source_reservoir["A1"], snap_caps["B1"]) + p1000.transfer(1500, source_reservoir["A1"], snap_caps.rows()[0]) diff --git a/abr-testing/abr_testing/protocols/liquid_setups/3_Tartrazine Liquid Setup.py b/abr-testing/abr_testing/protocols/liquid_setups/3_Tartrazine Liquid Setup.py new file mode 100644 index 00000000000..9e0b29a03ed --- /dev/null +++ b/abr-testing/abr_testing/protocols/liquid_setups/3_Tartrazine Liquid Setup.py @@ -0,0 +1,47 @@ +"""Plate Filler Protocol for Tartrazine Protocol.""" +from opentrons import protocol_api +from abr_testing.protocols.helpers import ( + load_common_liquid_setup_labware_and_instruments, +) + +metadata = { + "protocolName": "DVT1ABR3 Liquids: Tartrazine Protocol", + "author": "Rhyann clarke ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def run(protocol: protocol_api.ProtocolContext) -> None: + """Protocol.""" + # Initiate Labware + ( + source_reservoir, + tip_rack, + p1000, + ) = load_common_liquid_setup_labware_and_instruments(protocol) + reagent_tube = protocol.load_labware( + "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", "D3", "Reagent Tube" + ) + p1000.configure_nozzle_layout( + style=protocol_api.SINGLE, start="H1", tip_racks=[tip_rack] + ) + # Transfer Liquid + p1000.transfer( + 45000, + source_reservoir["A1"], + reagent_tube["B3"].top(), + blowout=True, + blowout_location="source well", + ) + p1000.transfer( + 45000, + source_reservoir["A1"], + reagent_tube["A4"].top(), + blowout=True, + blowout_location="source well", + ) diff --git a/abr-testing/abr_testing/protocols/liquid_setups/4_Illumina DNA Enrichment Liquid Setup.py b/abr-testing/abr_testing/protocols/liquid_setups/4_Illumina DNA Enrichment Liquid Setup.py new file mode 100644 index 00000000000..18aee383ace --- /dev/null +++ b/abr-testing/abr_testing/protocols/liquid_setups/4_Illumina DNA Enrichment Liquid Setup.py @@ -0,0 +1,91 @@ +"""Illumina DNA Enrichment Liquid Set up.""" +from opentrons import protocol_api +from abr_testing.protocols.helpers import ( + load_common_liquid_setup_labware_and_instruments, +) + +metadata = { + "protocolName": "DVT1ABR4/8: Illumina DNA Enrichment Liquid Set Up", + "author": "Tony Ngumah ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def run(protocol: protocol_api.ProtocolContext) -> None: + """Protocol.""" + ( + source_reservoir, + tip_rack, + p1000, + ) = load_common_liquid_setup_labware_and_instruments(protocol) + + reservoir_1 = protocol.load_labware( + "nest_96_wellplate_2ml_deep", "D2", "Reservoir 1" + ) # Reservoir + reservoir_2 = protocol.load_labware( + "thermoscientificnunc_96_wellplate_1300ul", "D3", "Sample Plate 2" + ) # Reservoir + sample_plate_1 = protocol.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", "C3", "Sample Plate 1" + ) # Sample Plate + reagent_plate_1 = protocol.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt", "B3", "Reagent Plate" + ) # reagent Plate + + # Reagent Plate Prep: dispense liquid into columns 4 - 7 - total 156 ul + p1000.transfer( + volume=[75, 15, 20, 65], + source=source_reservoir["A1"].bottom(z=0.5), + dest=[ + reagent_plate_1["A4"], + reagent_plate_1["A5"], + reagent_plate_1["A6"], + reagent_plate_1["A7"], + ], + blow_out=True, + blowout_location="source well", + trash=False, + ) + + # Reservoir 1 Plate Prep: dispense liquid into columns 1, 2, 4, 5 total 1866 ul + p1000.transfer( + volume=[120, 750, 900, 96], + source=source_reservoir["A1"], + dest=[ + reservoir_1["A1"].top(), + reservoir_1["A2"].top(), + reservoir_1["A4"].top(), + reservoir_1["A5"].top(), + ], + blow_out=True, + blowout_location="source well", + trash=False, + ) + + # Reservoir 2 Plate Prep: dispense liquid into columns 1-9 total 3690 ul + reservoir_2_wells = reservoir_2.wells() + list_of_locations = [well_location.top() for well_location in reservoir_2_wells] + p1000.transfer( + volume=[50, 50, 50, 50, 50, 50, 330, 330, 330, 800, 800, 800], + source=source_reservoir["A1"], + dest=list_of_locations, + blow_out=True, + blowout_location="source well", + trash=False, + ) + + # Sample Plate Prep: total 303 + dest_list = [sample_plate_1["A1"], sample_plate_1["A2"], sample_plate_1["A3"]] + p1000.transfer( + volume=[101, 101, 101], + source=source_reservoir["A1"], + dest=dest_list, + blow_out=True, + blowout_location="source well", + trash=False, + ) diff --git a/abr-testing/abr_testing/protocols/liquid_setups/5_96ch Complex Protocol Liquid Setup.py b/abr-testing/abr_testing/protocols/liquid_setups/5_96ch Complex Protocol Liquid Setup.py new file mode 100644 index 00000000000..cd263318442 --- /dev/null +++ b/abr-testing/abr_testing/protocols/liquid_setups/5_96ch Complex Protocol Liquid Setup.py @@ -0,0 +1,53 @@ +"""Plate Filler Protocol for 96ch Complex Protocol.""" +from opentrons import protocol_api +from abr_testing.protocols.helpers import ( + load_common_liquid_setup_labware_and_instruments, +) + +metadata = { + "protocolName": "DVT2ABR5 and 6 Liquids: 96ch Complex Protocol", + "author": "Rhyann clarke ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + + +def run(protocol: protocol_api.ProtocolContext) -> None: + """Protocol.""" + # Initiate Labware + ( + source_reservoir, + tip_rack, + p1000, + ) = load_common_liquid_setup_labware_and_instruments(protocol) + + reservoir = protocol.load_labware( + "nest_96_wellplate_2ml_deep", "D2", "Reservoir" + ) # Reservoir + + vol = 500 + + column_list = [ + "A1", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "A10", + "A11", + "A12", + ] + for i in column_list: + p1000.pick_up_tip() + p1000.aspirate(vol, source_reservoir["A1"].bottom(z=0.5)) + p1000.dispense(vol, reservoir[i].top()) + p1000.blow_out(location=source_reservoir["A1"].top()) + p1000.return_tip() diff --git a/abr-testing/abr_testing/protocols/liquid_setups/7_HDQ DNA Bacteria Extraction Liquid Setup.py b/abr-testing/abr_testing/protocols/liquid_setups/7_HDQ DNA Bacteria Extraction Liquid Setup.py new file mode 100644 index 00000000000..4addbd5c7e8 --- /dev/null +++ b/abr-testing/abr_testing/protocols/liquid_setups/7_HDQ DNA Bacteria Extraction Liquid Setup.py @@ -0,0 +1,114 @@ +"""Plate Filler Protocol for HDQ DNA Bacteria Extraction.""" +from opentrons import protocol_api +from abr_testing.protocols.helpers import ( + load_common_liquid_setup_labware_and_instruments, +) + + +metadata = { + "protocolName": "PVT1ABR7 Liquids: HDQ DNA Bacteria Extraction", + "author": "Rhyann Clarke ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + + +def run(protocol: protocol_api.ProtocolContext) -> None: + """Protocol.""" + # Deck Setup + ( + source_reservoir, + tip_rack, + p1000, + ) = load_common_liquid_setup_labware_and_instruments(protocol) + + sample_plate = protocol.load_labware( + "nest_96_wellplate_2ml_deep", "C3", "Sample Plate" + ) + elution_plate = protocol.load_labware( + "opentrons_96_wellplate_200ul_pcr_full_skirt", "B3", "Elution Plate" + ) + res1 = protocol.load_labware("nest_12_reservoir_15ml", "D2", "reagent reservoir 1") + # Label Reservoirs + well1 = res1["A1"].top() + well2 = res1["A2"].top() + well3 = res1["A3"].top() + well4 = res1["A4"].top() + well5 = res1["A5"].top() + well6 = res1["A6"].top() + well7 = res1["A7"].top() + well8 = res1["A8"].top() + well9 = res1["A9"].top() + well10 = res1["A10"].top() + well11 = res1["A11"].top() + well12 = res1["A12"].top() + # Volumes + wash = 600 + binding = 320 + beads = 230 + pk = 230 + lysis = 230 + + # Sample Plate + p1000.transfer( + volume=180, + source=source_reservoir["A1"].bottom(z=0.5), + dest=sample_plate["A1"].top(), + blowout=True, + blowout_location="source well", + trash=False, + ) + # Elution Plate + p1000.transfer( + volume=100, + source=source_reservoir["A1"].bottom(z=0.5), + dest=elution_plate["A1"].top(), + blowout=True, + blowout_location="source well", + trash=False, + ) + # Res 1 + p1000.transfer( + volume=[ + binding, + beads, + binding, + beads, + lysis, + pk, + wash, + wash, + wash, + wash, + wash, + wash, + wash, + wash, + wash, + ], + source=source_reservoir["A1"].bottom(z=0.5), + dest=[ + well1, + well1, + well2, + well2, + well3, + well3, + well4, + well5, + well6, + well7, + well8, + well9, + well10, + well11, + well12, + ], + blowout=True, + blowout_location="source well", + trash=False, + ) diff --git a/abr-testing/abr_testing/protocols/liquid_setups/9_Thermo MagMax RNA Extraction Liquid Setup.py b/abr-testing/abr_testing/protocols/liquid_setups/9_Thermo MagMax RNA Extraction Liquid Setup.py new file mode 100644 index 00000000000..c6ded28719d --- /dev/null +++ b/abr-testing/abr_testing/protocols/liquid_setups/9_Thermo MagMax RNA Extraction Liquid Setup.py @@ -0,0 +1,110 @@ +"""Plate Filler Protocol for Thermo MagMax RNA Extraction.""" +from opentrons import protocol_api +from abr_testing.protocols.helpers import ( + load_common_liquid_setup_labware_and_instruments, +) + + +metadata = { + "protocolName": "PVT1ABR9 Liquids: Thermo MagMax RNA Extraction", + "author": "Rhyann Clarke ", + "source": "Protocol Library", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.16", +} + + +def run(protocol: protocol_api.ProtocolContext) -> None: + """Protocol.""" + # Initiate Labware + ( + source_reservoir, + tip_rack, + p1000, + ) = load_common_liquid_setup_labware_and_instruments(protocol) + res1 = protocol.load_labware("nest_12_reservoir_15ml", "D2", "Reservoir") + elution_plate = protocol.load_labware( + "opentrons_96_wellplate_200ul_pcr_full_skirt", "C3", "Elution Plate" + ) + sample_plate = protocol.load_labware( + "nest_96_wellplate_2ml_deep", "B3", "Sample Plate" + ) + + # Volumes + elution_vol = 55 + well1 = 8120 / 8 + well2 = 6400 / 8 + well3_7 = 8550 / 8 + sample_vol = 100 + + # Reservoir + p1000.transfer( + volume=[well1, well2, well3_7, well3_7, well3_7, well3_7, well3_7], + source=source_reservoir["A1"].bottom(z=0.2), + dest=[ + res1["A1"].top(), + res1["A2"].top(), + res1["A3"].top(), + res1["A4"].top(), + res1["A5"].top(), + res1["A6"].top(), + res1["A7"].top(), + ], + blow_out=True, + blowout_location="source well", + trash=False, + ) + # Elution Plate + p1000.transfer( + volume=[ + elution_vol, + elution_vol, + elution_vol, + elution_vol, + elution_vol, + elution_vol, + elution_vol, + elution_vol, + elution_vol, + elution_vol, + elution_vol, + elution_vol, + ], + source=source_reservoir["A1"].bottom(z=0.2), + dest=[ + elution_plate["A1"].bottom(z=0.3), + elution_plate["A2"].bottom(z=0.3), + elution_plate["A3"].bottom(z=0.3), + elution_plate["A4"].bottom(z=0.3), + elution_plate["A5"].bottom(z=0.3), + elution_plate["A6"].bottom(z=0.3), + elution_plate["A7"].bottom(z=0.3), + elution_plate["A8"].bottom(z=0.3), + elution_plate["A9"].bottom(z=0.3), + elution_plate["A10"].bottom(z=0.3), + elution_plate["A11"].bottom(z=0.3), + elution_plate["A12"].bottom(z=0.3), + ], + blow_out=True, + blowout_location="source well", + trash=False, + ) + # Sample Plate + p1000.transfer( + volume=[sample_vol, sample_vol, sample_vol, sample_vol, sample_vol, sample_vol], + source=source_reservoir["A1"].bottom(z=0.2), + dest=[ + sample_plate["A1"].top(), + sample_plate["A2"].top(), + sample_plate["A3"].top(), + sample_plate["A4"].top(), + sample_plate["A5"].top(), + sample_plate["A6"].top(), + ], + blow_out=True, + blowout_location="source well", + trash=False, + ) diff --git a/abr-testing/abr_testing/protocols/test_protocols/tc_biorad_evap_test.py b/abr-testing/abr_testing/protocols/test_protocols/tc_biorad_evap_test.py new file mode 100644 index 00000000000..4593c06f425 --- /dev/null +++ b/abr-testing/abr_testing/protocols/test_protocols/tc_biorad_evap_test.py @@ -0,0 +1,109 @@ +"""Test TC Disposable Lid with BioRad Plate.""" + +from opentrons.protocol_api import ( + ProtocolContext, + ParameterContext, + Well, + Labware, + InstrumentContext, +) +from typing import List +from abr_testing.protocols import helpers +from opentrons.protocol_api.module_contexts import ThermocyclerContext +from opentrons.hardware_control.modules.types import ThermocyclerStep + +metadata = {"protocolName": "Tough Auto Seal Lid Evaporation Test"} +requirements = {"robotType": "Flex", "apiLevel": "2.21"} + + +def add_parameters(parameters: ParameterContext) -> None: + """Add Parameters.""" + helpers.create_single_pipette_mount_parameter(parameters) + helpers.create_tc_lid_deck_riser_parameter(parameters) + helpers.create_tc_compatible_labware_parameter(parameters) + + +def _pcr_cycle(thermocycler: ThermocyclerContext) -> None: + """30x cycles of: 70° for 30s 72° for 30s 95° for 10s.""" + profile_TAG2: List[ThermocyclerStep] = [ + {"temperature": 70, "hold_time_seconds": 30}, + {"temperature": 72, "hold_time_seconds": 30}, + {"temperature": 95, "hold_time_seconds": 10}, + ] + thermocycler.execute_profile( + steps=profile_TAG2, repetitions=30, block_max_volume=50 + ) + + +def _fill_with_liquid_and_measure( + protocol: ProtocolContext, + pipette: InstrumentContext, + reservoir: Labware, + plate_in_cycler: Labware, +) -> None: + """Fill plate with 10 ul per well.""" + locations: List[Well] = [ + plate_in_cycler["A1"], + plate_in_cycler["A2"], + plate_in_cycler["A3"], + plate_in_cycler["A4"], + plate_in_cycler["A5"], + plate_in_cycler["A6"], + plate_in_cycler["A7"], + plate_in_cycler["A8"], + plate_in_cycler["A9"], + plate_in_cycler["A10"], + plate_in_cycler["A11"], + plate_in_cycler["A12"], + ] + volumes = [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10] + protocol.pause("Weight Armadillo Plate, place on thermocycler") + # pipette 10uL into Armadillo wells + source_well: Well = reservoir["A1"] + pipette.distribute( + volume=volumes, + source=source_well, + dest=locations, + return_tips=True, + blow_out=False, + ) + protocol.pause("Weight Armadillo Plate, place on thermocycler, put on lid") + + +def run(ctx: ProtocolContext) -> None: + """Evaporation Test.""" + pipette_mount = ctx.params.pipette_mount # type: ignore[attr-defined] + deck_riser = ctx.params.deck_riser # type: ignore[attr-defined] + labware_tc_compatible = ctx.params.labware_tc_compatible # type: ignore[attr-defined] + tiprack_50 = ctx.load_labware("opentrons_flex_96_tiprack_50ul", "B2") + ctx.load_trash_bin("A3") + tc_mod: ThermocyclerContext = ctx.load_module( + helpers.tc_str + ) # type: ignore[assignment] + plate_in_cycler = tc_mod.load_labware(labware_tc_compatible) + p50 = ctx.load_instrument("flex_8channel_50", pipette_mount, tip_racks=[tiprack_50]) + unused_lids = helpers.load_disposable_lids(ctx, 5, ["D2"], deck_riser) + top_lid = unused_lids[0] + reservoir = ctx.load_labware("nest_12_reservoir_15ml", "A2") + tc_mod.open_lid() + tc_mod.set_block_temperature(4) + tc_mod.set_lid_temperature(105) + + # hold at 95° for 3 minutes + profile_TAG: List[ThermocyclerStep] = [{"temperature": 95, "hold_time_minutes": 3}] + # hold at 72° for 5min + profile_TAG3: List[ThermocyclerStep] = [{"temperature": 72, "hold_time_minutes": 5}] + tc_mod.open_lid() + _fill_with_liquid_and_measure(ctx, p50, reservoir, plate_in_cycler) + ctx.move_labware(top_lid, plate_in_cycler, use_gripper=True) + tc_mod.close_lid() + tc_mod.execute_profile(steps=profile_TAG, repetitions=1, block_max_volume=50) + _pcr_cycle(tc_mod) + tc_mod.execute_profile(steps=profile_TAG3, repetitions=1, block_max_volume=50) + # # # Cool to 4° + tc_mod.set_block_temperature(4) + tc_mod.set_lid_temperature(105) + # Open lid + tc_mod.open_lid() + ctx.move_labware(top_lid, "C2", use_gripper=True) + ctx.move_labware(top_lid, unused_lids[1], use_gripper=True) diff --git a/abr-testing/abr_testing/protocols/test_protocols/tc_lid_x_offset_test.py b/abr-testing/abr_testing/protocols/test_protocols/tc_lid_x_offset_test.py new file mode 100644 index 00000000000..df85453ff28 --- /dev/null +++ b/abr-testing/abr_testing/protocols/test_protocols/tc_lid_x_offset_test.py @@ -0,0 +1,96 @@ +"""Protocol to Test the Stacking and Movement of Tough Auto Seal Lid.""" +from opentrons.protocol_api import ( + ParameterContext, + ProtocolContext, +) +from opentrons.protocol_api.module_contexts import ( + ThermocyclerContext, +) +from abr_testing.protocols import helpers + + +metadata = {"protocolName": "5 Stack Test"} +requirements = {"robotType": "Flex", "apiLevel": "2.20"} + + +def add_parameters(parameters: ParameterContext) -> None: + """Add parameters.""" + parameters.add_int( + variable_name="lids_in_a_stack", + display_name="Num of Lids in Stack", + minimum=1, + maximum=5, + default=5, + ) + parameters.add_float( + variable_name="x_offset", + display_name="X Offset", + choices=[ + {"display_name": "0.0", "value": 0.0}, + {"display_name": "0.1", "value": 0.1}, + {"display_name": "0.2", "value": 0.2}, + {"display_name": "0.3", "value": 0.3}, + {"display_name": "0.4", "value": 0.4}, + {"display_name": "0.5", "value": 0.5}, + {"display_name": "0.6", "value": 0.6}, + {"display_name": "0.7", "value": 0.7}, + {"display_name": "0.8", "value": 0.8}, + {"display_name": "0.9", "value": 0.9}, + {"display_name": "1.0", "value": 1.0}, + {"display_name": "1.1", "value": 1.1}, + {"display_name": "1.2", "value": 1.2}, + {"display_name": "1.3", "value": 1.3}, + {"display_name": "1.4", "value": 1.4}, + {"display_name": "1.5", "value": 1.5}, + {"display_name": "1.6", "value": 1.6}, + {"display_name": "1.7", "value": 1.7}, + {"display_name": "1.8", "value": 1.8}, + {"display_name": "1.9", "value": 1.9}, + {"display_name": "2", "value": 2}, + ], + default=2, + ) + parameters.add_bool( + variable_name="negative", + display_name="Negative", + description="Turn on to make offset negative.", + default=False, + ) + + +def run(protocol: ProtocolContext) -> None: + """Runs protocol that moves lids and stacks them.""" + # Load Parameters + lids_in_stack = protocol.params.lids_in_a_stack # type: ignore[attr-defined] + x_offset = protocol.params.x_offset # type: ignore[attr-defined] + negative = protocol.params.negative # type: ignore[attr-defined] + if negative: + x_offset = x_offset * -1 + # Thermocycler + thermocycler: ThermocyclerContext = protocol.load_module( + "thermocyclerModuleV2" + ) # type: ignore[assignment] + plate_in_cycler = thermocycler.load_labware( + "armadillo_96_wellplate_200ul_pcr_full_skirt" + ) + thermocycler.open_lid() + # Load Lids + lid_stack_1 = helpers.load_disposable_lids(protocol, lids_in_stack, ["D2"]) + lid_stack_2 = helpers.load_disposable_lids(protocol, lids_in_stack, ["C2"]) + lid_stack_3 = helpers.load_disposable_lids(protocol, lids_in_stack, ["B2"]) + lid_stack_4 = helpers.load_disposable_lids(protocol, lids_in_stack, ["C3"]) + lid_stack_5 = helpers.load_disposable_lids(protocol, lids_in_stack, ["B3"]) + + drop_offset = {"x": x_offset, "y": 0, "z": 0} + slot = 0 + lids = [lid_stack_1, lid_stack_2, lid_stack_3, lid_stack_4, lid_stack_5] + for lid_list in lids: + lid_to_move = lid_list[0] + + lid_to_move_back_to = lid_list[1] + protocol.comment(f"Offset {x_offset}, Lid # {slot+1}") + # move lid to plate in thermocycler + protocol.move_labware( + lid_to_move, plate_in_cycler, use_gripper=True, drop_offset=drop_offset + ) + protocol.move_labware(lid_to_move, lid_to_move_back_to, use_gripper=True) diff --git a/abr-testing/abr_testing/tools/abr_scale.py b/abr-testing/abr_testing/tools/abr_scale.py index d02bf0acfed..a35fee93fbf 100644 --- a/abr-testing/abr_testing/tools/abr_scale.py +++ b/abr-testing/abr_testing/tools/abr_scale.py @@ -10,6 +10,7 @@ from typing import Any, Tuple import sys import json +from abr_testing.tools import plate_reader def get_protocol_step_as_int( @@ -141,14 +142,21 @@ def get_most_recent_run_and_record( ) # Record run to google sheets. print(most_recent_run_id) - + # Read Hellma Files + hellma_file_values = plate_reader.read_hellma_plate_files(storage_directory, 101934) ( runs_and_robots, headers, runs_and_lpc, headers_lpc, + list_of_heights, ) = abr_google_drive.create_data_dictionary( - most_recent_run_id, storage_directory, "", labware, accuracy + most_recent_run_id, + storage_directory, + "", + labware, + accuracy, + hellma_plate_standards=hellma_file_values, ) google_sheet_abr_data = google_sheets_tool.google_sheet( credentials_path, "ABR-run-data", tab_number=0 @@ -156,6 +164,15 @@ def get_most_recent_run_and_record( start_row = google_sheet_abr_data.get_index_row() + 1 google_sheet_abr_data.batch_update_cells(runs_and_robots, "A", start_row, "0") print("Wrote run to ABR-run-data") + # Add liquid height detection to abr sheet + google_sheet_ldf = google_sheets_tool.google_sheet( + credentials_path, "ABR-run-data", 4 + ) + start_row_lhd = google_sheet_ldf.get_index_row() + 1 + google_sheet_ldf.batch_update_cells( + list_of_heights, "A", start_row_lhd, "1795535088" + ) + print("Wrote found liquid heights to ABR-run-data") # Add LPC to google sheet google_sheet_lpc = google_sheets_tool.google_sheet( credentials_path, "ABR-LPC", tab_number=0 diff --git a/abr-testing/abr_testing/tools/abr_setup.py b/abr-testing/abr_testing/tools/abr_setup.py new file mode 100644 index 00000000000..a0fca76b6e4 --- /dev/null +++ b/abr-testing/abr_testing/tools/abr_setup.py @@ -0,0 +1,148 @@ +"""Automate ABR data collection.""" +import os +import time +import configparser +import traceback +import sys +from hardware_testing.scripts import ABRAsairScript # type: ignore +from abr_testing.data_collection import ( + get_run_logs, + abr_google_drive, + abr_calibration_logs, +) +from abr_testing.tools import sync_abr_sheet + + +def run_sync_abr_sheet( + storage_directory: str, abr_data_sheet: str, room_conditions_sheet: str +) -> None: + """Sync ABR sheet with temp and lifetime percents.""" + sync_abr_sheet.run(storage_directory, abr_data_sheet, room_conditions_sheet) + + +def run_temp_sensor() -> None: + """Run temperature sensors on all robots.""" + processes = ABRAsairScript.run() + for process in processes: + process.start() + time.sleep(20) + for process in processes: + process.join() + + +def get_abr_logs(storage_directory: str, folder_name: str, email: str) -> None: + """Retrieve run logs on all robots and record missing run logs in google drive.""" + try: + get_run_logs.run(storage_directory, folder_name, email) + except Exception as e: + print("Cannot Get Run Logs", e) + traceback.print_exc + + +def record_abr_logs( + storage_directory: str, folder_name: str, google_sheet_name: str, email: str +) -> None: + """Write run logs to ABR run logs in sheets.""" + try: + abr_google_drive.run(storage_directory, folder_name, google_sheet_name, email) + except Exception as e: + print(e) + + +def get_calibration_data( + storage_directory: str, folder_name: str, google_sheet_name: str, email: str +) -> None: + """Download calibration logs and write to ABR-calibration-data in sheets.""" + try: + abr_calibration_logs.run( + storage_directory, folder_name, google_sheet_name, email + ) + except Exception as e: + print("Cannot get calibration data", e) + traceback.print_exc() + + +def main(configurations: configparser.ConfigParser) -> None: + """Main function.""" + storage_directory = None + email = None + drive_folder = None + sheet_name = None + ambient_conditions_sheet = None + sheet_url = None + + has_defaults = False + # If default is not specified get all values + default = configurations["DEFAULT"] + if len(default) > 0: + has_defaults = True + try: + if has_defaults: + storage_directory = default["Storage"] + email = default["Email"] + drive_folder = default["Drive_Folder"] + sheet_name = default["Sheet_Name"] + sheet_url = default["Sheet_Url"] + except KeyError as e: + print("Cannot read config file\n" + str(e)) + + # Run Temperature Sensors + if not has_defaults: + ambient_conditions_sheet = configurations["TEMP-SENSOR"]["Sheet_Url"] + print("Starting temp sensors...") + run_temp_sensor() + print("Temp Sensors Started") + # Get Run Logs and Record + if not has_defaults: + storage_directory = configurations["RUN-LOG"]["Storage"] + email = configurations["RUN-LOG"]["Email"] + drive_folder = configurations["RUN-LOG"]["Drive_Folder"] + sheet_name = configurations["RUN-LOG"]["Sheet_Name"] + sheet_url = configurations["RUN-LOG"]["Sheet_Url"] + print(sheet_name) + if storage_directory and drive_folder and sheet_name and email: + print("Retrieving robot run logs...") + get_abr_logs(storage_directory, drive_folder, email) + print("Recording robot run logs...") + record_abr_logs(storage_directory, drive_folder, sheet_name, email) + print("Run logs updated") + else: + print("Storage, Email, or Drive Folder is missing, please fix configs") + sys.exit(1) + # Update Google Sheet with missing temp/rh + if storage_directory and sheet_url and ambient_conditions_sheet: + run_sync_abr_sheet(storage_directory, sheet_url, ambient_conditions_sheet) + # Collect calibration data + if not has_defaults: + storage_directory = configurations["CALIBRATION"]["Storage"] + email = configurations["CALIBRATION"]["Email"] + drive_folder = configurations["CALIBRATION"]["Drive_Folder"] + sheet_name = configurations["CALIBRATION"]["Sheet_Name"] + if storage_directory and drive_folder and sheet_name and email: + print("Retrieving and recording robot calibration data...") + get_calibration_data(storage_directory, drive_folder, sheet_name, email) + print("Calibration logs updated") + else: + print( + "Storage, Email, Drive Folder, or Sheet name is missing, please fix configs" + ) + sys.exit(1) + + +if __name__ == "__main__": + configurations = None + configs_file = None + while not configs_file: + configs_file = input("Please enter path to config.ini: ") + if os.path.exists(configs_file): + break + else: + configs_file = None + print("Please enter a valid path") + try: + configurations = configparser.ConfigParser() + configurations.read(configs_file) + except configparser.ParsingError as e: + print("Cannot read configuration file\n" + str(e)) + if configurations: + main(configurations) diff --git a/abr-testing/abr_testing/tools/make_push.py b/abr-testing/abr_testing/tools/make_push.py new file mode 100644 index 00000000000..28a69b11103 --- /dev/null +++ b/abr-testing/abr_testing/tools/make_push.py @@ -0,0 +1,95 @@ +"""Push one or more folders to one or more robots.""" +import subprocess +import multiprocessing +import json + +global folders +# Opentrons folders that can be pushed to robot +folders = [ + "abr-testing", + "hardware-testing", + "abr-testing + hardware-testing", + "other", +] + + +def push_subroutine(cmd: str) -> None: + """Pushes specified folder to specified robot.""" + try: + subprocess.run(cmd) + except Exception: + print("failed to push folder") + raise + + +def main(folder_to_push: str, robot_to_push: str) -> int: + """Main process!""" + cmd = "make -C {folder} push-ot3 host={ip}" + robot_ip_path = "" + push_cmd = "" + folder_int = int(folder_to_push) + if folders[folder_int].lower() == "abr-testing + hardware-testing": + if robot_to_push.lower() == "all": + robot_ip_path = input("Path to robot ips: ") + with open(robot_ip_path, "r") as ip_file: + robot_json = json.load(ip_file) + robot_ips_dict = robot_json.get("ip_address_list") + robot_ips = list(robot_ips_dict.keys()) + ip_file.close() + else: + robot_ips = [robot_to_push] + for folder_name in folders[:-2]: + # Push abr-testing and hardware-testing folders to all robots + for robot in robot_ips: + print_proc = multiprocessing.Process( + target=print, args=(f"Pushing {folder_name} to {robot}!\n\n",) + ) + print_proc.start() + print_proc.join() + push_cmd = cmd.format(folder=folder_name, ip=robot) + process = multiprocessing.Process( + target=push_subroutine, args=(push_cmd,) + ) + process.start() + process.join() + print_proc = multiprocessing.Process(target=print, args=("Done!\n\n",)) + print_proc.start() + print_proc.join() + else: + + if folder_int == (len(folders) - 1): + folder_name = input("Which folder? ") + else: + folder_name = folders[folder_int] + if robot_to_push.lower() == "all": + robot_ip_path = input("Path to robot ips: ") + with open(robot_ip_path, "r") as ip_file: + robot_json = json.load(ip_file) + robot_ips = robot_json.get("ip_address_list") + ip_file.close() + else: + robot_ips = [robot_to_push] + + # Push folder to robots + for robot in robot_ips: + print_proc = multiprocessing.Process( + target=print, args=(f"Pushing {folder_name} to {robot}!\n\n",) + ) + print_proc.start() + print_proc.join() + push_cmd = cmd.format(folder=folder_name, ip=robot) + process = multiprocessing.Process(target=push_subroutine, args=(push_cmd,)) + process.start() + process.join() + print_proc = multiprocessing.Process(target=print, args=("Done!\n\n",)) + print_proc.start() + print_proc.join() + return 0 + + +if __name__ == "__main__": + for i, folder in enumerate(folders): + print(f"{i}) {folder}") + folder_to_push = input("Please Select a Folder to Push: ") + robot_to_push = input("Type in robots ip (type all for all): ") + print(main(folder_to_push, robot_to_push)) diff --git a/abr-testing/abr_testing/tools/plate_reader.py b/abr-testing/abr_testing/tools/plate_reader.py new file mode 100644 index 00000000000..3be15fa4268 --- /dev/null +++ b/abr-testing/abr_testing/tools/plate_reader.py @@ -0,0 +1,144 @@ +"""Plate Reader Functions.""" +import argparse +import os +import numpy as np +from typing import Dict, Any, List + + +def convert_read_dictionary_to_array(read_data: Dict[str, Any]) -> np.ndarray: + """Convert a dictionary of read results to an array. + + Converts a dictionary of OD values, as formatted by the Opentrons API's + plate reader read() function, to a 2D numpy.array of shape (8,12) for + further processing. + + read_data: dict + a dictonary of read values with celll numbers for keys, e.g. 'A1' + """ + data = np.empty((8, 12)) + for key, value in read_data.items(): + row_index = ord(key[0]) - ord("A") + column_index = int(key[1:]) - 1 + data[row_index][column_index] = value + + return data + + +def check_byonoy_data_accuracy( + output: Any, cal: Dict[str, np.ndarray], flipped: bool +) -> List[Any]: + """Check multiple OD measurements for accuracy. + + od_list: list of 2D numpy.array of shape (8,12) + a list of multiple plate readings as returned by read_byonoy_directory_to_list() + cal: namedtuple + 2D numpy.array of shape (8,12) of calibration values, and 1D + numpy.array of tolerances, as returned by read_byonoy_file_to_array + flipped: bool + True if reference plate was rotated 180 degrees for measurment + """ + print("entered analysis") + run_error_cells = [] + cal_data = cal["data"] + cal_tolerance = cal["tolerance"] + # Calculate absolute accuracy tolerances for each cell + # The last two columns have a higher tolerance per the Byonoy datasheet + # because OD>2.0 and wavelength>=450nm on the Hellma plate + output_array = np.asarray(output) + accuracy_tols = np.zeros((8, 12)) + accuracy_tols[:, :10] = cal_data[:, :10] * 0.01 + cal_tolerance[:10] + 0.01 + accuracy_tols[:, 10:] = cal_data[:, 10:] * 0.015 + cal_tolerance[10:] + 0.01 + if flipped: + within_tolerance = np.isclose( + output_array, + np.rot90(cal_data, 2), + atol=np.rot90(accuracy_tols, 2), + ) # type: ignore + else: + within_tolerance = np.isclose(output_array, cal_data, atol=accuracy_tols) # type: ignore + errors = np.where(within_tolerance is False) + error_cells = [ + (chr(ord("@") + errors[0][i] + 1) + str(errors[1][i] + 1)) + for i in range(0, len(errors[0])) + ] + run_error_cells.append(error_cells) + return run_error_cells + + +def read_byonoy_file_to_array(filename: str) -> Dict[str, Any]: + """Read a Byonoy endpoint CSV file into a numpy array. + + Returns a named tuple with a 2D numpy array of shape (8,12) of OD values + from a Byonoy endpoint CSV file and a 1D numpy array of tolerances (which + are present in the reference plate calibration data files). + + filename: str + absolute path and filename of the CSV file to be read + """ + wavelength = filename.split("nm.csv")[0].split("_")[-1] + with open(filename, "r") as f: + # print(filename) + + f.seek(0) + file_data = np.genfromtxt( + f, usecols=range(1, 13), skip_header=1, max_rows=8, delimiter="," + ) + # print(file_data.shape, file_data) + + f.seek(0) + file_tolerance = np.genfromtxt( + f, usecols=range(1, 13), skip_header=9, max_rows=1, delimiter="," + ) + # print(file_tolerance.shape, file_tolerance) + + File_Values = { + "wavelength": wavelength, + "data": file_data, + "tolerance": file_tolerance, + } + return File_Values + + +def read_hellma_plate_files( + storage_directory: str, hellma_plate_number: int +) -> List[Any]: + """Read hellma files for the following wavelengths.""" + wavelengths = [405, 450, 490, 650] + file_values = [] + for wave in wavelengths: + file_name = "_".join(["hellma", str(hellma_plate_number), str(wave)]) + file_name_csv = file_name + "nm.csv" + try: + file_path = os.path.join(storage_directory, file_name_csv) + File_Values = read_byonoy_file_to_array(file_path) + file_values.append(File_Values) + except FileNotFoundError: + print( + f"Hellma plate {hellma_plate_number} file at {wave} does not exist in this folder." + ) + continue + return file_values + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Read storage directory with hellma plate files." + ) + parser.add_argument( + "storage_directory", + metavar="STORAGE_DIRECTORY", + type=str, + nargs=1, + help="Path to storage directory for hellma plate files.", + ) + parser.add_argument( + "hellma_plate_number", + metavar="HELLMA_PLATE_NUMBER", + type=int, + nargs=1, + help="Hellma Plate Number.", + ) + args = parser.parse_args() + storage_directory = args.storage_directory[0] + hellma_plate_number = args.hellma_plate_number[0] + read_hellma_plate_files(storage_directory, hellma_plate_number) diff --git a/abr-testing/abr_testing/tools/sync_abr_sheet.py b/abr-testing/abr_testing/tools/sync_abr_sheet.py index 1bae79cd159..2ae0769dec1 100644 --- a/abr-testing/abr_testing/tools/sync_abr_sheet.py +++ b/abr-testing/abr_testing/tools/sync_abr_sheet.py @@ -7,6 +7,8 @@ import csv import sys import os +import time +import traceback from typing import Dict, Tuple, Any, List from statistics import mean, StatisticsError @@ -15,6 +17,7 @@ def determine_lifetime(abr_google_sheet: Any) -> None: """Record lifetime % of robot, pipettes, and gripper per run.""" # Get all data headers = abr_google_sheet.get_row(1) + lifetime_index = headers.index("Robot Lifetime (%)") all_google_data = abr_google_sheet.get_all_data(expected_headers=headers) # Convert dictionary to pandas dataframe df_sheet_data = pd.DataFrame.from_dict(all_google_data) @@ -26,74 +29,94 @@ def determine_lifetime(abr_google_sheet: Any) -> None: ) # Goes through dataframe per robot for index, run in df_sheet_data.iterrows(): - end_time = run["End_Time"] - robot = run["Robot"] - robot_lifetime = ( - float(run["Robot Lifetime (%)"]) if run["Robot Lifetime (%)"] != "" else 0 + max_retries = 5 + retries = 0 + while retries < max_retries: + try: + update_df(abr_google_sheet, lifetime_index, df_sheet_data, dict(run)) + break + except Exception as e: + if "Quota exceeded for quota metric" in str(e): + retries += 1 + print( + f"Read/write limit reached on attempt: {retries}, pausing then retrying..." + ) + time.sleep(65) + else: + print("unrecoverable error:", e) + traceback.print_exc() + sys.exit(1) + + +def update_df( + abr_google_sheet: Any, lifetime_index: int, df_sheet_data: Any, run: Dict[Any, Any] +) -> None: + """Update google sheets with new run log data.""" + end_time = run["End_Time"] + robot = run["Robot"] + robot_lifetime = ( + float(run["Robot Lifetime (%)"]) if run["Robot Lifetime (%)"] != "" else 0 + ) + if robot_lifetime < 1 and len(run["Run_ID"]) > 1: + # Get Robot % Lifetime + robot_runs_before = df_sheet_data[ + (df_sheet_data["End_Time"] <= end_time) & (df_sheet_data["Robot"] == robot) + ] + robot_percent_lifetime = ( + (robot_runs_before["Run_Time (min)"].sum() / 60) / 3750 * 100 ) - if robot_lifetime < 1 and len(run["Run_ID"]) > 1: - # Get Robot % Lifetime - robot_runs_before = df_sheet_data[ + # Get Left Pipette % Lifetime + left_pipette = run["Left Mount"] + if len(left_pipette) > 1: + left_pipette_runs_before = df_sheet_data[ (df_sheet_data["End_Time"] <= end_time) - & (df_sheet_data["Robot"] == robot) + & ( + (df_sheet_data["Left Mount"] == left_pipette) + | (df_sheet_data["Right Mount"] == left_pipette) + ) ] - robot_percent_lifetime = ( - (robot_runs_before["Run_Time (min)"].sum() / 60) / 3750 * 100 + left_pipette_percent_lifetime = ( + (left_pipette_runs_before["Run_Time (min)"].sum() / 60) / 1248 * 100 ) - # Get Left Pipette % Lifetime - left_pipette = run["Left Mount"] - if len(left_pipette) > 1: - left_pipette_runs_before = df_sheet_data[ - (df_sheet_data["End_Time"] <= end_time) - & ( - (df_sheet_data["Left Mount"] == left_pipette) - | (df_sheet_data["Right Mount"] == left_pipette) - ) - ] - left_pipette_percent_lifetime = ( - (left_pipette_runs_before["Run_Time (min)"].sum() / 60) / 1248 * 100 - ) - else: - left_pipette_percent_lifetime = "" - # Get Right Pipette % Lifetime - right_pipette = run["Right Mount"] - if len(right_pipette) > 1: - right_pipette_runs_before = df_sheet_data[ - (df_sheet_data["End_Time"] <= end_time) - & ( - (df_sheet_data["Left Mount"] == right_pipette) - | (df_sheet_data["Right Mount"] == right_pipette) - ) - ] - right_pipette_percent_lifetime = ( - (right_pipette_runs_before["Run_Time (min)"].sum() / 60) - / 1248 - * 100 - ) - else: - right_pipette_percent_lifetime = "" - # Get Gripper % Lifetime - gripper = run["Extension"] - if len(gripper) > 1: - gripper_runs_before = df_sheet_data[ - (df_sheet_data["End_Time"] <= end_time) - & (df_sheet_data["Extension"] == gripper) - ] - gripper_percent_lifetime = ( - (gripper_runs_before["Run_Time (min)"].sum() / 60) / 3750 * 100 + else: + left_pipette_percent_lifetime = "" + # Get Right Pipette % Lifetime + right_pipette = run["Right Mount"] + if len(right_pipette) > 1: + right_pipette_runs_before = df_sheet_data[ + (df_sheet_data["End_Time"] <= end_time) + & ( + (df_sheet_data["Left Mount"] == right_pipette) + | (df_sheet_data["Right Mount"] == right_pipette) ) - else: - gripper_percent_lifetime = "" - run_id = run["Run_ID"] - row_num = abr_google_sheet.get_row_index_with_value(run_id, 2) - update_list = [ - [robot_percent_lifetime], - [left_pipette_percent_lifetime], - [right_pipette_percent_lifetime], - [gripper_percent_lifetime], ] - abr_google_sheet.batch_update_cells(update_list, "AV", row_num, "0") - print(f"Updated row {row_num} for run: {run_id}") + right_pipette_percent_lifetime = ( + (right_pipette_runs_before["Run_Time (min)"].sum() / 60) / 1248 * 100 + ) + else: + right_pipette_percent_lifetime = "" + # Get Gripper % Lifetime + gripper = run["Extension"] + if len(gripper) > 1: + gripper_runs_before = df_sheet_data[ + (df_sheet_data["End_Time"] <= end_time) + & (df_sheet_data["Extension"] == gripper) + ] + gripper_percent_lifetime = ( + (gripper_runs_before["Run_Time (min)"].sum() / 60) / 3750 * 100 + ) + else: + gripper_percent_lifetime = "" + run_id = run["Run_ID"] + row_num = abr_google_sheet.get_row_index_with_value(run_id, 2) + update_list = [ + [robot_percent_lifetime], + [left_pipette_percent_lifetime], + [right_pipette_percent_lifetime], + [gripper_percent_lifetime], + ] + abr_google_sheet.batch_update_cells(update_list, lifetime_index, row_num, "0") + print(f"Updated row {row_num} for run: {run_id}") def compare_run_to_temp_data( @@ -101,6 +124,10 @@ def compare_run_to_temp_data( ) -> None: """Read ABR Data and compare robot and timestamp columns to temp data.""" row_update = 0 + # Get column number for average temp and rh + headers = google_sheet.get_row(1) + temp_index = headers.index("Average Temp (oC)") + 1 + rh_index = headers.index("Average RH(%)") + 1 for run in abr_data: run_id = run["Run_ID"] try: @@ -129,9 +156,9 @@ def compare_run_to_temp_data( avg_humidity = mean(rel_hums) row_num = google_sheet.get_row_index_with_value(run_id, 2) # Write average temperature - google_sheet.update_cell("Sheet1", row_num, 46, avg_temps) + google_sheet.update_cell("Sheet1", row_num, temp_index, avg_temps) # Write average humidity - google_sheet.update_cell("Sheet1", row_num, 47, avg_humidity) + google_sheet.update_cell("Sheet1", row_num, rh_index, avg_humidity) row_update += 1 # TODO: Write averages to google sheet print(f"Updated row {row_num}.") @@ -175,6 +202,30 @@ def connect_and_download( return file_paths, credentials_path +def run( + storage_directory: str, abr_data_sheet_url: str, abr_room_conditions_sheet: str +) -> None: + """Connect to storage and google sheets and update.""" + google_sheets_to_download = { + "ABR-run-data": abr_data_sheet_url, + "ABR Ambient Conditions": abr_room_conditions_sheet, + } + # Download google sheets. + + file_paths, credentials_path = connect_and_download( + google_sheets_to_download, storage_directory + ) + # Read csvs. + abr_data = read_csv_as_dict(file_paths[0]) + temp_data = read_csv_as_dict(file_paths[1]) + # Compare robot and timestamps. + abr_google_sheet = google_sheets_tool.google_sheet( + credentials_path, "ABR-run-data", 0 + ) + determine_lifetime(abr_google_sheet) + compare_run_to_temp_data(abr_data, temp_data, abr_google_sheet) + + if __name__ == "__main__": parser = argparse.ArgumentParser( description="Adds average robot ambient conditions to run sheet." @@ -198,21 +249,7 @@ def connect_and_download( help="Path to long term storage directory for run logs.", ) args = parser.parse_args() - google_sheets_to_download = { - "ABR-run-data": args.abr_data_sheet, - "ABR Ambient Conditions": args.room_conditions_sheet, - } - storage_directory = args.storage_directory - # Download google sheets. - file_paths, credentials_path = connect_and_download( - google_sheets_to_download, storage_directory - ) - # TODO: read csvs. - abr_data = read_csv_as_dict(file_paths[0]) - temp_data = read_csv_as_dict(file_paths[1]) - # TODO: compare robot and timestamps. - abr_google_sheet = google_sheets_tool.google_sheet( - credentials_path, "ABR-run-data", 0 - ) - determine_lifetime(abr_google_sheet) - compare_run_to_temp_data(abr_data, temp_data, abr_google_sheet) + storage_directory = args.storage_directory[0] + abr_data_sheet_url = args.abr_data_sheet[0] + room_conditions_sheet_url = args.room_conditions_sheet[0] + run(storage_directory, abr_data_sheet_url, room_conditions_sheet_url) diff --git a/analyses-snapshot-testing/citools/write_failed_analysis.py b/abr-testing/test_debug similarity index 100% rename from analyses-snapshot-testing/citools/write_failed_analysis.py rename to abr-testing/test_debug diff --git a/analyses-snapshot-testing/Makefile b/analyses-snapshot-testing/Makefile index e0aa23a8415..6918d17bf3e 100644 --- a/analyses-snapshot-testing/Makefile +++ b/analyses-snapshot-testing/Makefile @@ -1,38 +1,56 @@ +BASE_IMAGE_NAME ?= opentrons-python-base:3.10 +CACHEBUST ?= $(shell date +%s) +ANALYSIS_REF ?= edge +PROTOCOL_NAMES ?= all +OVERRIDE_PROTOCOL_NAMES ?= all +LOCAL_IMAGE_TAG ?= local +ANALYZER_IMAGE_NAME ?= opentrons-analysis + +export ANALYSIS_REF # tag, branch or commit for the opentrons repository. Used as the image tag for the analyzer image +export PROTOCOL_NAMES # tell the test which protocols to run +export OVERRIDE_PROTOCOL_NAMES # tell the test which override protocols to run + +ifeq ($(CI), true) + PYTHON=python +else + PYTHON=pyenv exec python +endif + .PHONY: black black: - python -m pipenv run python -m black . + $(PYTHON) -m pipenv run python -m black . .PHONY: black-check black-check: - python -m pipenv run python -m black . --check + $(PYTHON) -m pipenv run python -m black . --check .PHONY: ruff ruff: - python -m pipenv run python -m ruff check . --fix + $(PYTHON) -m pipenv run python -m ruff check . --fix .PHONY: ruff-check ruff-check: - python -m pipenv run python -m ruff check . + $(PYTHON) -m pipenv run python -m ruff check . .PHONY: mypy mypy: - python -m pipenv run python -m mypy automation tests citools + $(PYTHON) -m pipenv run python -m mypy automation tests citools .PHONY: lint lint: black-check ruff-check mypy .PHONY: format format: - @echo runnning black + @echo "Running black" $(MAKE) black - @echo running ruff + @echo "Running ruff" $(MAKE) ruff - @echo formatting the readme with yarn prettier + @echo "Formatting the readme with yarn prettier" $(MAKE) format-readme .PHONY: test-ci test-ci: - python -m pipenv run python -m pytest -m "emulated_alpha" + $(PYTHON) -m pipenv run python -m pytest -m "emulated_alpha" .PHONY: test-protocol-analysis test-protocol-analysis: @@ -40,52 +58,97 @@ test-protocol-analysis: .PHONY: setup setup: install-pipenv - python -m pipenv install + $(PYTHON) -m pipenv install .PHONY: teardown teardown: - python -m pipenv --rm + $(PYTHON) -m pipenv --rm .PHONY: format-readme format-readme: - yarn prettier --ignore-path .eslintignore --write analyses-snapshot-testing/**/*.md + yarn prettier --ignore-path .eslintignore --write analyses-snapshot-testing/**/*.md .github/workflows/analyses-snapshot-test.yaml .PHONY: install-pipenv install-pipenv: - python -m pip install -U pipenv - -ANALYSIS_REF ?= edge -PROTOCOL_NAMES ?= all -OVERRIDE_PROTOCOL_NAMES ?= all - -export ANALYSIS_REF -export PROTOCOL_NAMES -export OVERRIDE_PROTOCOL_NAMES + $(PYTHON) -m pip install -U pipenv .PHONY: snapshot-test snapshot-test: @echo "ANALYSIS_REF is $(ANALYSIS_REF)" @echo "PROTOCOL_NAMES is $(PROTOCOL_NAMES)" @echo "OVERRIDE_PROTOCOL_NAMES is $(OVERRIDE_PROTOCOL_NAMES)" - python -m pipenv run pytest -k analyses_snapshot_test -vv + $(PYTHON) -m pipenv run pytest -k analyses_snapshot_test -vv .PHONY: snapshot-test-update snapshot-test-update: @echo "ANALYSIS_REF is $(ANALYSIS_REF)" @echo "PROTOCOL_NAMES is $(PROTOCOL_NAMES)" @echo "OVERRIDE_PROTOCOL_NAMES is $(OVERRIDE_PROTOCOL_NAMES)" - python -m pipenv run pytest -k analyses_snapshot_test --snapshot-update + $(PYTHON) -m pipenv run pytest -k analyses_snapshot_test --snapshot-update -CACHEBUST := $(shell date +%s) +.PHONY: build-base-image +build-base-image: + @echo "Building the base image $(BASE_IMAGE_NAME)" + docker build --build-arg BASE_IMAGE_NAME=$(BASE_IMAGE_NAME) -f citools/Dockerfile.base -t $(BASE_IMAGE_NAME) citools/. .PHONY: build-opentrons-analysis build-opentrons-analysis: - @echo "Building docker image for $(ANALYSIS_REF)" - @echo "The image will be named opentrons-analysis:$(ANALYSIS_REF)" - @echo "If you want to build a different version, run 'make build-opentrons-analysis ANALYSIS_REF='" - @echo "Cache is always busted to ensure latest version of the code is used" - docker build --build-arg OPENTRONS_VERSION=$(ANALYSIS_REF) --build-arg CACHEBUST=$(CACHEBUST) -t opentrons-analysis:$(ANALYSIS_REF) citools/. + @echo "Building docker image for opentrons repository reference$(ANALYSIS_REF)" + @echo "The image will be named $(ANALYZER_IMAGE_NAME):$(ANALYSIS_REF)" + @echo "If you want to build a different version, run 'make build-opentrons-analysis ANALYSIS_REF='" + docker build --build-arg BASE_IMAGE_NAME=$(BASE_IMAGE_NAME) --build-arg ANALYSIS_REF=$(ANALYSIS_REF) --build-arg CACHEBUST=$(CACHEBUST) -t $(ANALYZER_IMAGE_NAME):$(ANALYSIS_REF) -f citools/Dockerfile.analyze citools/. + +.PHONY: build-local +build-local: + @echo "Building docker image for your local opentrons code" + @echo "This image will be named $(ANALYZER_IMAGE_NAME):$(LOCAL_IMAGE_TAG)" + docker build --build-arg BASE_IMAGE_NAME=$(BASE_IMAGE_NAME) -t $(ANALYZER_IMAGE_NAME):$(LOCAL_IMAGE_TAG) -f citools/Dockerfile.local .. + @echo "Build complete" + +.PHONY: snapshot-test-local +snapshot-test-local: ANALYSIS_REF=$(LOCAL_IMAGE_TAG) +snapshot-test-local: build-base-image build-local + @echo "This target is overriding the ANALYSIS_REF to the LOCAL_IMAGE_TAG: $(LOCAL_IMAGE_TAG)" + @echo "ANALYSIS_REF is $(ANALYSIS_REF). The the test maps this env variable to the image tag." + @echo "The image the test will use is $(ANALYZER_IMAGE_NAME):$(LOCAL_IMAGE_TAG)" + @echo "PROTOCOL_NAMES is $(PROTOCOL_NAMES)" + @echo "OVERRIDE_PROTOCOL_NAMES is $(OVERRIDE_PROTOCOL_NAMES)" + $(PYTHON) -m pipenv run pytest -k analyses_snapshot_test -vv + +.PHONY: snapshot-test-update-local +snapshot-test-update-local: ANALYSIS_REF=$(LOCAL_IMAGE_TAG) +snapshot-test-update-local: build-base-image build-local + @echo "This target is overriding the ANALYSIS_REF to the LOCAL_IMAGE_TAG: $(LOCAL_IMAGE_TAG)" + @echo "ANALYSIS_REF is $(ANALYSIS_REF). The the test maps this env variable to the image tag." + @echo "The image the test will use is $(ANALYZER_IMAGE_NAME):$(LOCAL_IMAGE_TAG)" + @echo "PROTOCOL_NAMES is $(PROTOCOL_NAMES)" + @echo "OVERRIDE_PROTOCOL_NAMES is $(OVERRIDE_PROTOCOL_NAMES)" + $(PYTHON) -m pipenv run pytest -k analyses_snapshot_test --snapshot-update .PHONY: generate-protocols generate-protocols: - python -m pipenv run python -m automation.data.protocol_registry + $(PYTHON) -m pipenv run python -m automation.data.protocol_registry + +# Tools for running the robot server in a container + +OPENTRONS_VERSION ?= edge +export OPENTRONS_VERSION # used for the robot server image as the tag, branch or commit for the opentrons repository + +.PHONY: build-rs +build-rs: + @echo "Building docker image for opentrons-robot-server:$(OPENTRONS_VERSION)" + @echo "Cache is always busted to ensure latest version of the code is used" + @echo "If you want to build a different version, run 'make build-rs OPENTRONS_VERSION=chore_release-8.0.0'" + docker build --build-arg BASE_IMAGE_NAME=$(BASE_IMAGE_NAME) --build-arg OPENTRONS_VERSION=$(OPENTRONS_VERSION) --build-arg CACHEBUST=$(CACHEBUST) -t opentrons-robot-server:$(OPENTRONS_VERSION) -f citools/Dockerfile.server . + +.PHONY: run-flex +run-flex: + @echo "Running opentrons-robot-server:$(OPENTRONS_VERSION)" + @echo "If you want to run a different version, run 'make run-flex OPENTRONS_VERSION=chore_release-8.0.0'" + docker run -p 31950:31950 --env-file ../robot-server/dev-flex.env opentrons-robot-server:$(OPENTRONS_VERSION) + +.PHONY: run-ot2 +run-ot2: + @echo "Running opentrons-robot-server:$(OPENTRONS_VERSION)" + @echo "If you want to run a different version, run 'make run-ot2 OPENTRONS_VERSION=chore_release-8.0.0'" + docker run -p 31950:31950 --env-file ../robot-server/dev.env opentrons-robot-server:$(OPENTRONS_VERSION) diff --git a/analyses-snapshot-testing/Pipfile b/analyses-snapshot-testing/Pipfile index 43bb4dd2475..85a1d29c337 100644 --- a/analyses-snapshot-testing/Pipfile +++ b/analyses-snapshot-testing/Pipfile @@ -4,20 +4,18 @@ url = "https://pypi.org/simple" verify_ssl = true [packages] -pytest = "==8.1.1" -black = "==24.3.0" -selenium = "==4.19.0" -importlib-metadata = "==7.1.0" -requests = "==2.31.0" +pytest = "==8.3.3" +black = "==24.10.0" +importlib-metadata = "==8.5.0" +httpx = "==0.27.2" python-dotenv = "==1.0.1" -mypy = "==1.9.0" -types-requests = "==2.31.0.20240311" -rich = "==13.7.1" -pydantic = "==2.6.4" -ruff = "==0.3.4" -docker = "==7.0.0" -syrupy = "==4.6.1" +mypy = "==1.11.2" +rich = "==13.9.2" +pydantic = "==2.9.2" +ruff = "==0.6.9" +docker = "==7.1.0" +syrupy = "==4.7.2" pytest-html = "==4.1.1" [requires] -python_version = "3.12" +python_version = "3.13" diff --git a/analyses-snapshot-testing/Pipfile.lock b/analyses-snapshot-testing/Pipfile.lock index 0672556f9cd..648d065049c 100644 --- a/analyses-snapshot-testing/Pipfile.lock +++ b/analyses-snapshot-testing/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "b7ac4510c6e3aa343c669e1bd838183e905abb6f1701c6efbfb1c22f20cfae44" + "sha256": "6657134003c472f3e25e7c91a6b1f46eb8231b4518c18c90c6353bcf2666923e" }, "pipfile-spec": 6, "requires": { - "python_version": "3.12" + "python_version": "3.13" }, "sources": [ { @@ -18,56 +18,56 @@ "default": { "annotated-types": { "hashes": [ - "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43", - "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d" + "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", + "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89" ], "markers": "python_version >= '3.8'", - "version": "==0.6.0" + "version": "==0.7.0" }, - "attrs": { + "anyio": { "hashes": [ - "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", - "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" + "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb", + "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a" ], - "markers": "python_version >= '3.7'", - "version": "==23.2.0" + "markers": "python_version >= '3.9'", + "version": "==4.6.0" }, "black": { "hashes": [ - "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f", - "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93", - "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11", - "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0", - "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9", - "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5", - "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213", - "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d", - "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7", - "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837", - "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f", - "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395", - "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995", - "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f", - "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597", - "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959", - "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5", - "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb", - "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4", - "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7", - "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd", - "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7" + "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f", + "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd", + "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea", + "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981", + "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b", + "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7", + "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8", + "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175", + "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d", + "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392", + "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad", + "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f", + "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f", + "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b", + "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875", + "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3", + "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800", + "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65", + "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2", + "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812", + "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50", + "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==24.3.0" + "markers": "python_version >= '3.9'", + "version": "==24.10.0" }, "certifi": { "hashes": [ - "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", - "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" + "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", + "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9" ], "markers": "python_version >= '3.6'", - "version": "==2024.2.2" + "version": "==2024.8.30" }, "charset-normalizer": { "hashes": [ @@ -175,12 +175,12 @@ }, "docker": { "hashes": [ - "sha256:12ba681f2777a0ad28ffbcc846a69c31b4dfd9752b47eb425a274ee269c5e14b", - "sha256:323736fb92cd9418fc5e7133bc953e11a9da04f4483f828b527db553f1e7e5a3" + "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c", + "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==7.0.0" + "version": "==7.1.0" }, "h11": { "hashes": [ @@ -190,22 +190,39 @@ "markers": "python_version >= '3.7'", "version": "==0.14.0" }, + "httpcore": { + "hashes": [ + "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f", + "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f" + ], + "markers": "python_version >= '3.8'", + "version": "==1.0.6" + }, + "httpx": { + "hashes": [ + "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", + "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.27.2" + }, "idna": { "hashes": [ - "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", - "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" ], - "markers": "python_version >= '3.5'", - "version": "==3.6" + "markers": "python_version >= '3.6'", + "version": "==3.10" }, "importlib-metadata": { "hashes": [ - "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", - "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2" + "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", + "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==7.1.0" + "version": "==8.5.0" }, "iniconfig": { "hashes": [ @@ -217,11 +234,11 @@ }, "jinja2": { "hashes": [ - "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa", - "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90" + "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", + "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d" ], "markers": "python_version >= '3.7'", - "version": "==3.1.3" + "version": "==3.1.4" }, "markdown-it-py": { "hashes": [ @@ -233,69 +250,70 @@ }, "markupsafe": { "hashes": [ - "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", - "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", - "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", - "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", - "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", - "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", - "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", - "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", - "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", - "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", - "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", - "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", - "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", - "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", - "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", - "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", - "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", - "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", - "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", - "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", - "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", - "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", - "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", - "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", - "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", - "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", - "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", - "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", - "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", - "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", - "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", - "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", - "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", - "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", - "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", - "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", - "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", - "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", - "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", - "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", - "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", - "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", - "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", - "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", - "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", - "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", - "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", - "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", - "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", - "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", - "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", - "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", - "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", - "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", - "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", - "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", - "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", - "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", - "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", - "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68" - ], - "markers": "python_version >= '3.7'", - "version": "==2.1.5" + "sha256:03ff62dea2fef3eadf2f1853bc6332bcb0458d9608b11dfb1cd5aeda1c178ea6", + "sha256:105ada43a61af22acb8774514c51900dc820c481cc5ba53f17c09d294d9c07ca", + "sha256:12ddac720b8965332d36196f6f83477c6351ba6a25d4aff91e30708c729350d7", + "sha256:1d151b9cf3307e259b749125a5a08c030ba15a8f1d567ca5bfb0e92f35e761f5", + "sha256:1ee9790be6f62121c4c58bbced387b0965ab7bffeecb4e17cc42ef290784e363", + "sha256:1fd02f47596e00a372f5b4af2b4c45f528bade65c66dfcbc6e1ea1bfda758e98", + "sha256:23efb2be7221105c8eb0e905433414d2439cb0a8c5d5ca081c1c72acef0f5613", + "sha256:25396abd52b16900932e05b7104bcdc640a4d96c914f39c3b984e5a17b01fba0", + "sha256:27d6a73682b99568916c54a4bfced40e7d871ba685b580ea04bbd2e405dfd4c5", + "sha256:380faf314c3c84c1682ca672e6280c6c59e92d0bc13dc71758ffa2de3cd4e252", + "sha256:3b231255770723f1e125d63c14269bcd8b8136ecfb620b9a18c0297e046d0736", + "sha256:3cd0bba31d484fe9b9d77698ddb67c978704603dc10cdc905512af308cfcca6b", + "sha256:3efde9a8c56c3b6e5f3fa4baea828f8184970c7c78480fedb620d804b1c31e5c", + "sha256:409535e0521c4630d5b5a1bf284e9d3c76d2fc2f153ebb12cf3827797798cc99", + "sha256:494a64efc535e147fcc713dba58eecfce3a79f1e93ebe81995b387f5cd9bc2e1", + "sha256:4ca04c60006867610a06575b46941ae616b19da0adc85b9f8f3d9cbd7a3da385", + "sha256:4deea1d9169578917d1f35cdb581bc7bab56a7e8c5be2633bd1b9549c3c22a01", + "sha256:509c424069dd037d078925b6815fc56b7271f3aaec471e55e6fa513b0a80d2aa", + "sha256:5509a8373fed30b978557890a226c3d30569746c565b9daba69df80c160365a5", + "sha256:59420b5a9a5d3fee483a32adb56d7369ae0d630798da056001be1e9f674f3aa6", + "sha256:5d207ff5cceef77796f8aacd44263266248cf1fbc601441524d7835613f8abec", + "sha256:5ddf5cb8e9c00d9bf8b0c75949fb3ff9ea2096ba531693e2e87336d197fdb908", + "sha256:63dae84964a9a3d2610808cee038f435d9a111620c37ccf872c2fcaeca6865b3", + "sha256:64a7c7856c3a409011139b17d137c2924df4318dab91ee0530800819617c4381", + "sha256:64f7d04410be600aa5ec0626d73d43e68a51c86500ce12917e10fd013e258df5", + "sha256:658fdf6022740896c403d45148bf0c36978c6b48c9ef8b1f8d0c7a11b6cdea86", + "sha256:678fbceb202382aae42c1f0cd9f56b776bc20a58ae5b553ee1fe6b802983a1d6", + "sha256:7835de4c56066e096407a1852e5561f6033786dd987fa90dc384e45b9bd21295", + "sha256:7c524203207f5b569df06c96dafdc337228921ee8c3cc5f6e891d024c6595352", + "sha256:7ed789d0f7f11fcf118cf0acb378743dfdd4215d7f7d18837c88171405c9a452", + "sha256:81be2c0084d8c69e97e3c5d73ce9e2a6e523556f2a19c4e195c09d499be2f808", + "sha256:81ee9c967956b9ea39b3a5270b7cb1740928d205b0dc72629164ce621b4debf9", + "sha256:8219e2207f6c188d15614ea043636c2b36d2d79bf853639c124a179412325a13", + "sha256:96e3ed550600185d34429477f1176cedea8293fa40e47fe37a05751bcb64c997", + "sha256:98fb3a2bf525ad66db96745707b93ba0f78928b7a1cb2f1cb4b143bc7e2ba3b3", + "sha256:9b36473a2d3e882d1873ea906ce54408b9588dc2c65989664e6e7f5a2de353d7", + "sha256:9f91c90f8f3bf436f81c12eeb4d79f9ddd263c71125e6ad71341906832a34386", + "sha256:a5fd5500d4e4f7cc88d8c0f2e45126c4307ed31e08f8ec521474f2fd99d35ac3", + "sha256:a7171d2b869e9be238ea318c196baf58fbf272704e9c1cd4be8c380eea963342", + "sha256:a80c6740e1bfbe50cea7cbf74f48823bb57bd59d914ee22ff8a81963b08e62d2", + "sha256:b2a7afd24d408b907672015555bc10be2382e6c5f62a488e2d452da670bbd389", + "sha256:b43ac1eb9f91e0c14aac1d2ef0f76bc7b9ceea51de47536f61268191adf52ad7", + "sha256:b6cc46a27d904c9be5732029769acf4b0af69345172ed1ef6d4db0c023ff603b", + "sha256:b94bec9eda10111ec7102ef909eca4f3c2df979643924bfe58375f560713a7d1", + "sha256:bd9b8e458e2bab52f9ad3ab5dc8b689a3c84b12b2a2f64cd9a0dfe209fb6b42f", + "sha256:c182d45600556917f811aa019d834a89fe4b6f6255da2fd0bdcf80e970f95918", + "sha256:c409691696bec2b5e5c9efd9593c99025bf2f317380bf0d993ee0213516d908a", + "sha256:c5243044a927e8a6bb28517838662a019cd7f73d7f106bbb37ab5e7fa8451a92", + "sha256:c8ab7efeff1884c5da8e18f743b667215300e09043820d11723718de0b7db934", + "sha256:cb244adf2499aa37d5dc43431990c7f0b632d841af66a51d22bd89c437b60264", + "sha256:d261ec38b8a99a39b62e0119ed47fe3b62f7691c500bc1e815265adc016438c1", + "sha256:d2c099be5274847d606574234e494f23a359e829ba337ea9037c3a72b0851942", + "sha256:d7e63d1977d3806ce0a1a3e0099b089f61abdede5238ca6a3f3bf8877b46d095", + "sha256:dba0f83119b9514bc37272ad012f0cc03f0805cc6a2bea7244e19250ac8ff29f", + "sha256:dcbee57fedc9b2182c54ffc1c5eed316c3da8bbfeda8009e1b5d7220199d15da", + "sha256:e042ccf8fe5bf8b6a4b38b3f7d618eb10ea20402b0c9f4add9293408de447974", + "sha256:e363440c8534bf2f2ef1b8fdc02037eb5fff8fce2a558519b22d6a3a38b3ec5e", + "sha256:e64b390a306f9e849ee809f92af6a52cda41741c914358e0e9f8499d03741526", + "sha256:f0411641d31aa6f7f0cc13f0f18b63b8dc08da5f3a7505972a42ab059f479ba3", + "sha256:f1c13c6c908811f867a8e9e66efb2d6c03d1cdd83e92788fe97f693c457dc44f", + "sha256:f846fd7c241e5bd4161e2a483663eb66e4d8e12130fcdc052f310f388f1d61c6" + ], + "markers": "python_version >= '3.9'", + "version": "==3.0.0" }, "mdurl": { "hashes": [ @@ -307,37 +325,37 @@ }, "mypy": { "hashes": [ - "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6", - "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913", - "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129", - "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc", - "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974", - "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374", - "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150", - "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03", - "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9", - "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02", - "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89", - "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2", - "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d", - "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3", - "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612", - "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e", - "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3", - "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e", - "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd", - "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04", - "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed", - "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185", - "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf", - "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b", - "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4", - "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f", - "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6" + "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36", + "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce", + "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6", + "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b", + "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca", + "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24", + "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383", + "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7", + "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86", + "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d", + "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4", + "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8", + "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987", + "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385", + "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79", + "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef", + "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6", + "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70", + "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca", + "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70", + "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12", + "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104", + "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a", + "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318", + "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1", + "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b", + "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.9.0" + "version": "==1.11.2" }, "mypy-extensions": { "hashes": [ @@ -347,21 +365,13 @@ "markers": "python_version >= '3.5'", "version": "==1.0.0" }, - "outcome": { - "hashes": [ - "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8", - "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b" - ], - "markers": "python_version >= '3.7'", - "version": "==1.3.0.post0" - }, "packaging": { "hashes": [ - "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", - "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9" + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" ], - "markers": "python_version >= '3.7'", - "version": "==24.0" + "markers": "python_version >= '3.8'", + "version": "==24.1" }, "pathspec": { "hashes": [ @@ -373,138 +383,140 @@ }, "platformdirs": { "hashes": [ - "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", - "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768" + "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", + "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb" ], "markers": "python_version >= '3.8'", - "version": "==4.2.0" + "version": "==4.3.6" }, "pluggy": { "hashes": [ - "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981", - "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be" + "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", + "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669" ], "markers": "python_version >= '3.8'", - "version": "==1.4.0" + "version": "==1.5.0" }, "pydantic": { "hashes": [ - "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6", - "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5" + "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", + "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==2.6.4" + "version": "==2.9.2" }, "pydantic-core": { "hashes": [ - "sha256:00ee1c97b5364b84cb0bd82e9bbf645d5e2871fb8c58059d158412fee2d33d8a", - "sha256:0d32576b1de5a30d9a97f300cc6a3f4694c428d956adbc7e6e2f9cad279e45ed", - "sha256:0df446663464884297c793874573549229f9eca73b59360878f382a0fc085979", - "sha256:0f56ae86b60ea987ae8bcd6654a887238fd53d1384f9b222ac457070b7ac4cff", - "sha256:13dcc4802961b5f843a9385fc821a0b0135e8c07fc3d9949fd49627c1a5e6ae5", - "sha256:162e498303d2b1c036b957a1278fa0899d02b2842f1ff901b6395104c5554a45", - "sha256:1b662180108c55dfbf1280d865b2d116633d436cfc0bba82323554873967b340", - "sha256:1cac689f80a3abab2d3c0048b29eea5751114054f032a941a32de4c852c59cad", - "sha256:21b888c973e4f26b7a96491c0965a8a312e13be108022ee510248fe379a5fa23", - "sha256:287073c66748f624be4cef893ef9174e3eb88fe0b8a78dc22e88eca4bc357ca6", - "sha256:2a1ef6a36fdbf71538142ed604ad19b82f67b05749512e47f247a6ddd06afdc7", - "sha256:2a72fb9963cba4cd5793854fd12f4cfee731e86df140f59ff52a49b3552db241", - "sha256:2acca2be4bb2f2147ada8cac612f8a98fc09f41c89f87add7256ad27332c2fda", - "sha256:2f583bd01bbfbff4eaee0868e6fc607efdfcc2b03c1c766b06a707abbc856187", - "sha256:33809aebac276089b78db106ee692bdc9044710e26f24a9a2eaa35a0f9fa70ba", - "sha256:36fa178aacbc277bc6b62a2c3da95226520da4f4e9e206fdf076484363895d2c", - "sha256:4204e773b4b408062960e65468d5346bdfe139247ee5f1ca2a378983e11388a2", - "sha256:4384a8f68ddb31a0b0c3deae88765f5868a1b9148939c3f4121233314ad5532c", - "sha256:456855f57b413f077dff513a5a28ed838dbbb15082ba00f80750377eed23d132", - "sha256:49d5d58abd4b83fb8ce763be7794d09b2f50f10aa65c0f0c1696c677edeb7cbf", - "sha256:4ac6b4ce1e7283d715c4b729d8f9dab9627586dafce81d9eaa009dd7f25dd972", - "sha256:4df8a199d9f6afc5ae9a65f8f95ee52cae389a8c6b20163762bde0426275b7db", - "sha256:500960cb3a0543a724a81ba859da816e8cf01b0e6aaeedf2c3775d12ee49cade", - "sha256:519ae0312616026bf4cedc0fe459e982734f3ca82ee8c7246c19b650b60a5ee4", - "sha256:578114bc803a4c1ff9946d977c221e4376620a46cf78da267d946397dc9514a8", - "sha256:5c5cbc703168d1b7a838668998308018a2718c2130595e8e190220238addc96f", - "sha256:6162f8d2dc27ba21027f261e4fa26f8bcb3cf9784b7f9499466a311ac284b5b9", - "sha256:704d35ecc7e9c31d48926150afada60401c55efa3b46cd1ded5a01bdffaf1d48", - "sha256:716b542728d4c742353448765aa7cdaa519a7b82f9564130e2b3f6766018c9ec", - "sha256:72282ad4892a9fb2da25defeac8c2e84352c108705c972db82ab121d15f14e6d", - "sha256:7233d65d9d651242a68801159763d09e9ec96e8a158dbf118dc090cd77a104c9", - "sha256:732da3243e1b8d3eab8c6ae23ae6a58548849d2e4a4e03a1924c8ddf71a387cb", - "sha256:75b81e678d1c1ede0785c7f46690621e4c6e63ccd9192af1f0bd9d504bbb6bf4", - "sha256:75f76ee558751746d6a38f89d60b6228fa174e5172d143886af0f85aa306fd89", - "sha256:7ee8d5f878dccb6d499ba4d30d757111847b6849ae07acdd1205fffa1fc1253c", - "sha256:7f752826b5b8361193df55afcdf8ca6a57d0232653494ba473630a83ba50d8c9", - "sha256:86b3d0033580bd6bbe07590152007275bd7af95f98eaa5bd36f3da219dcd93da", - "sha256:8d62da299c6ecb04df729e4b5c52dc0d53f4f8430b4492b93aa8de1f541c4aac", - "sha256:8e47755d8152c1ab5b55928ab422a76e2e7b22b5ed8e90a7d584268dd49e9c6b", - "sha256:9091632a25b8b87b9a605ec0e61f241c456e9248bfdcf7abdf344fdb169c81cf", - "sha256:936e5db01dd49476fa8f4383c259b8b1303d5dd5fb34c97de194560698cc2c5e", - "sha256:99b6add4c0b39a513d323d3b93bc173dac663c27b99860dd5bf491b240d26137", - "sha256:9c865a7ee6f93783bd5d781af5a4c43dadc37053a5b42f7d18dc019f8c9d2bd1", - "sha256:a425479ee40ff021f8216c9d07a6a3b54b31c8267c6e17aa88b70d7ebd0e5e5b", - "sha256:a4b2bf78342c40b3dc830880106f54328928ff03e357935ad26c7128bbd66ce8", - "sha256:a6b1bb0827f56654b4437955555dc3aeeebeddc47c2d7ed575477f082622c49e", - "sha256:aaf09e615a0bf98d406657e0008e4a8701b11481840be7d31755dc9f97c44053", - "sha256:b1f6f5938d63c6139860f044e2538baeee6f0b251a1816e7adb6cbce106a1f01", - "sha256:b29eeb887aa931c2fcef5aa515d9d176d25006794610c264ddc114c053bf96fe", - "sha256:b3992a322a5617ded0a9f23fd06dbc1e4bd7cf39bc4ccf344b10f80af58beacd", - "sha256:b5b6079cc452a7c53dd378c6f881ac528246b3ac9aae0f8eef98498a75657805", - "sha256:b60cc1a081f80a2105a59385b92d82278b15d80ebb3adb200542ae165cd7d183", - "sha256:b926dd38db1519ed3043a4de50214e0d600d404099c3392f098a7f9d75029ff8", - "sha256:bd87f48924f360e5d1c5f770d6155ce0e7d83f7b4e10c2f9ec001c73cf475c99", - "sha256:bda1ee3e08252b8d41fa5537413ffdddd58fa73107171a126d3b9ff001b9b820", - "sha256:be0ec334369316fa73448cc8c982c01e5d2a81c95969d58b8f6e272884df0074", - "sha256:c6119dc90483a5cb50a1306adb8d52c66e447da88ea44f323e0ae1a5fcb14256", - "sha256:c9803edf8e29bd825f43481f19c37f50d2b01899448273b3a7758441b512acf8", - "sha256:c9bd22a2a639e26171068f8ebb5400ce2c1bc7d17959f60a3b753ae13c632975", - "sha256:cbcc558401de90a746d02ef330c528f2e668c83350f045833543cd57ecead1ad", - "sha256:cf6204fe865da605285c34cf1172879d0314ff267b1c35ff59de7154f35fdc2e", - "sha256:d33dd21f572545649f90c38c227cc8631268ba25c460b5569abebdd0ec5974ca", - "sha256:d89ca19cdd0dd5f31606a9329e309d4fcbb3df860960acec32630297d61820df", - "sha256:d8f99b147ff3fcf6b3cc60cb0c39ea443884d5559a30b1481e92495f2310ff2b", - "sha256:d937653a696465677ed583124b94a4b2d79f5e30b2c46115a68e482c6a591c8a", - "sha256:dcca5d2bf65c6fb591fff92da03f94cd4f315972f97c21975398bd4bd046854a", - "sha256:ded1c35f15c9dea16ead9bffcde9bb5c7c031bff076355dc58dcb1cb436c4721", - "sha256:e3e70c94a0c3841e6aa831edab1619ad5c511199be94d0c11ba75fe06efe107a", - "sha256:e56f8186d6210ac7ece503193ec84104da7ceb98f68ce18c07282fcc2452e76f", - "sha256:e7774b570e61cb998490c5235740d475413a1f6de823169b4cf94e2fe9e9f6b2", - "sha256:e7c6ed0dc9d8e65f24f5824291550139fe6f37fac03788d4580da0d33bc00c97", - "sha256:ec08be75bb268473677edb83ba71e7e74b43c008e4a7b1907c6d57e940bf34b6", - "sha256:ecdf6bf5f578615f2e985a5e1f6572e23aa632c4bd1dc67f8f406d445ac115ed", - "sha256:ed25e1835c00a332cb10c683cd39da96a719ab1dfc08427d476bce41b92531fc", - "sha256:f4cb85f693044e0f71f394ff76c98ddc1bc0953e48c061725e540396d5c8a2e1", - "sha256:f53aace168a2a10582e570b7736cc5bef12cae9cf21775e3eafac597e8551fbe", - "sha256:f651dd19363c632f4abe3480a7c87a9773be27cfe1341aef06e8759599454120", - "sha256:fc4ad7f7ee1a13d9cb49d8198cd7d7e3aa93e425f371a68235f784e99741561f", - "sha256:fee427241c2d9fb7192b658190f9f5fd6dfe41e02f3c1489d2ec1e6a5ab1e04a" + "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36", + "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", + "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071", + "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", + "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c", + "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", + "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29", + "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744", + "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", + "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec", + "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", + "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", + "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577", + "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232", + "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", + "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", + "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368", + "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480", + "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", + "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2", + "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6", + "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", + "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", + "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2", + "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", + "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166", + "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271", + "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", + "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb", + "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13", + "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323", + "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556", + "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665", + "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef", + "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb", + "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119", + "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", + "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", + "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", + "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", + "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", + "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", + "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", + "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21", + "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f", + "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", + "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658", + "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", + "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3", + "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb", + "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59", + "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", + "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", + "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", + "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", + "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753", + "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55", + "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad", + "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a", + "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605", + "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e", + "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b", + "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433", + "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", + "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07", + "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728", + "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", + "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", + "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555", + "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", + "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6", + "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", + "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b", + "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df", + "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", + "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", + "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068", + "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3", + "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040", + "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12", + "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916", + "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", + "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f", + "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801", + "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", + "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5", + "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8", + "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", + "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607" ], "markers": "python_version >= '3.8'", - "version": "==2.16.3" + "version": "==2.23.4" }, "pygments": { "hashes": [ - "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", - "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367" + "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", + "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a" ], - "markers": "python_version >= '3.7'", - "version": "==2.17.2" - }, - "pysocks": { - "hashes": [ - "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299", - "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", - "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0" - ], - "version": "==1.7.1" + "markers": "python_version >= '3.8'", + "version": "==2.18.0" }, "pytest": { "hashes": [ - "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7", - "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044" + "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", + "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==8.1.1" + "version": "==8.3.3" }, "pytest-html": { "hashes": [ @@ -534,54 +546,45 @@ }, "requests": { "hashes": [ - "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==2.31.0" + "markers": "python_version >= '3.8'", + "version": "==2.32.3" }, "rich": { "hashes": [ - "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", - "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432" + "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c", + "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1" ], "index": "pypi", - "markers": "python_full_version >= '3.7.0'", - "version": "==13.7.1" + "markers": "python_full_version >= '3.8.0'", + "version": "==13.9.2" }, "ruff": { "hashes": [ - "sha256:3f3860057590e810c7ffea75669bdc6927bfd91e29b4baa9258fd48b540a4365", - "sha256:519cf6a0ebed244dce1dc8aecd3dc99add7a2ee15bb68cf19588bb5bf58e0488", - "sha256:60c870a7d46efcbc8385d27ec07fe534ac32f3b251e4fc44b3cbfd9e09609ef4", - "sha256:64abeed785dad51801b423fa51840b1764b35d6c461ea8caef9cf9e5e5ab34d9", - "sha256:6810563cc08ad0096b57c717bd78aeac888a1bfd38654d9113cb3dc4d3f74232", - "sha256:6fc14fa742e1d8f24910e1fff0bd5e26d395b0e0e04cc1b15c7c5e5fe5b4af91", - "sha256:986f2377f7cf12efac1f515fc1a5b753c000ed1e0a6de96747cdf2da20a1b369", - "sha256:98e98300056445ba2cc27d0b325fd044dc17fcc38e4e4d2c7711585bd0a958ed", - "sha256:af27ac187c0a331e8ef91d84bf1c3c6a5dea97e912a7560ac0cef25c526a4102", - "sha256:bb0acfb921030d00070539c038cd24bb1df73a2981e9f55942514af8b17be94e", - "sha256:c4fd98e85869603e65f554fdc5cddf0712e352fe6e61d29d5a6fe087ec82b76c", - "sha256:cf133dd744f2470b347f602452a88e70dadfbe0fcfb5fd46e093d55da65f82f7", - "sha256:cf187a7e7098233d0d0c71175375c5162f880126c4c716fa28a8ac418dcf3378", - "sha256:d3ee7880f653cc03749a3bfea720cf2a192e4f884925b0cf7eecce82f0ce5854", - "sha256:de0d5069b165e5a32b3c6ffbb81c350b1e3d3483347196ffdf86dc0ef9e37dd6", - "sha256:df52972138318bc7546d92348a1ee58449bc3f9eaf0db278906eb511889c4b50", - "sha256:f0f4484c6541a99862b693e13a151435a279b271cff20e37101116a21e2a1ad1" + "sha256:064df58d84ccc0ac0fcd63bc3090b251d90e2a372558c0f057c3f75ed73e1ccd", + "sha256:12edd2af0c60fa61ff31cefb90aef4288ac4d372b4962c2864aeea3a1a2460c0", + "sha256:140d4b5c9f5fc7a7b074908a78ab8d384dd7f6510402267bc76c37195c02a7ec", + "sha256:3c866b631f5fbce896a74a6e4383407ba7507b815ccc52bcedabb6810fdb3ef7", + "sha256:3ef0cc774b00fec123f635ce5c547dac263f6ee9fb9cc83437c5904183b55ceb", + "sha256:417b81aa1c9b60b2f8edc463c58363075412866ae4e2b9ab0f690dc1e87ac1b5", + "sha256:53fd8ca5e82bdee8da7f506d7b03a261f24cd43d090ea9db9a1dc59d9313914c", + "sha256:55bb01caeaf3a60b2b2bba07308a02fca6ab56233302406ed5245180a05c5625", + "sha256:645d7d8761f915e48a00d4ecc3686969761df69fb561dd914a773c1a8266e14e", + "sha256:785d31851c1ae91f45b3d8fe23b8ae4b5170089021fbb42402d811135f0b7117", + "sha256:7b118afbb3202f5911486ad52da86d1d52305b59e7ef2031cea3425142b97d6f", + "sha256:7d5ccc9e58112441de8ad4b29dcb7a86dc25c5f770e3c06a9d57e0e5eba48829", + "sha256:925d26471fa24b0ce5a6cdfab1bb526fb4159952385f386bdcc643813d472039", + "sha256:a67267654edc23c97335586774790cde402fb6bbdb3c2314f1fc087dee320bfa", + "sha256:a9641e31476d601f83cd602608739a0840e348bda93fec9f1ee816f8b6798b93", + "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2", + "sha256:eae02b700763e3847595b9d2891488989cac00214da7f845f4bcf2989007d577", + "sha256:eb61ec9bdb2506cffd492e05ac40e5bc6284873aceb605503d8494180d6fc84d" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==0.3.4" - }, - "selenium": { - "hashes": [ - "sha256:5b4f49240d61e687a73f7968ae2517d403882aae3550eae2a229c745e619f1d9", - "sha256:d9dfd6d0b021d71d0a48b865fe7746490ba82b81e9c87b212360006629eb1853" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==4.19.0" + "version": "==0.6.9" }, "sniffio": { "hashes": [ @@ -591,81 +594,41 @@ "markers": "python_version >= '3.7'", "version": "==1.3.1" }, - "sortedcontainers": { - "hashes": [ - "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", - "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" - ], - "version": "==2.4.0" - }, "syrupy": { "hashes": [ - "sha256:203e52f9cb9fa749cf683f29bd68f02c16c3bc7e7e5fe8f2fc59bdfe488ce133", - "sha256:37a835c9ce7857eeef86d62145885e10b3cb9615bc6abeb4ce404b3f18e1bb36" + "sha256:ea45e099f242de1bb53018c238f408a5bb6c82007bc687aefcbeaa0e1c2e935a", + "sha256:eae7ba6be5aed190237caa93be288e97ca1eec5ca58760e4818972a10c4acc64" ], "index": "pypi", - "markers": "python_version < '4' and python_full_version >= '3.8.1'", - "version": "==4.6.1" - }, - "trio": { - "hashes": [ - "sha256:9b41f5993ad2c0e5f62d0acca320ec657fdb6b2a2c22b8c7aed6caf154475c4e", - "sha256:e6458efe29cc543e557a91e614e2b51710eba2961669329ce9c862d50c6e8e81" - ], - "markers": "python_version >= '3.8'", - "version": "==0.25.0" - }, - "trio-websocket": { - "hashes": [ - "sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f", - "sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638" - ], - "markers": "python_version >= '3.7'", - "version": "==0.11.1" - }, - "types-requests": { - "hashes": [ - "sha256:47872893d65a38e282ee9f277a4ee50d1b28bd592040df7d1fdaffdf3779937d", - "sha256:b1c1b66abfb7fa79aae09097a811c4aa97130eb8831c60e47aee4ca344731ca5" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==2.31.0.20240311" + "markers": "python_full_version >= '3.8.1'", + "version": "==4.7.2" }, "typing-extensions": { "hashes": [ - "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475", - "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb" + "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", + "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" ], - "markers": "python_version >= '3.8'", - "version": "==4.10.0" + "markers": "python_version >= '3.13'", + "version": "==4.12.2" }, "urllib3": { "extras": [ "socks" ], "hashes": [ - "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", - "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" + "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", + "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9" ], "markers": "python_version >= '3.8'", - "version": "==2.2.1" - }, - "wsproto": { - "hashes": [ - "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065", - "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736" - ], - "markers": "python_full_version >= '3.7.0'", - "version": "==1.2.0" + "version": "==2.2.3" }, "zipp": { "hashes": [ - "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b", - "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715" + "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", + "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29" ], "markers": "python_version >= '3.8'", - "version": "==3.18.1" + "version": "==3.20.2" } }, "develop": {} diff --git a/analyses-snapshot-testing/README.md b/analyses-snapshot-testing/README.md index 1ed330efefe..03ce1d87518 100644 --- a/analyses-snapshot-testing/README.md +++ b/analyses-snapshot-testing/README.md @@ -4,7 +4,7 @@ 1. Follow the instructions in [DEV_SETUP.md](../DEV_SETUP.md) 1. `cd analyses-snapshot-testing` -1. use pyenv to install python 3.12 and set it as the local python version for this directory +1. use pyenv to install python 3.13 and set it as the local python version for this directory 1. `make setup` 1. Have docker installed and ready @@ -14,6 +14,15 @@ - In CI this is the `SNAPSHOT_REF`. This is the branch or tag of the test code/snapshots that analyses generated will be compared to. - The `ANALYSIS_REF` is the branch or tag that you want analyses generated from. +## Build the opentrons-analysis image + +> This ALWAYS gets the remote code pushed to Opentrons/opentrons for the specified ANALYSIS_REF + +- build the base image + - `make build-base-image` +- build the opentrons-analysis image + - `make build-opentrons-analysis ANALYSIS_REF=release` + ## Running the tests locally - Compare the current branch snapshots to analyses generated from the edge branch @@ -38,3 +47,42 @@ - `make snapshot-test PROTOCOL_NAMES=Flex_S_v2_19_Illumina_DNA_PCR_Free OVERRIDE_PROTOCOL_NAMES=none` - `make snapshot-test PROTOCOL_NAMES=none OVERRIDE_PROTOCOL_NAMES=Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP` - `make snapshot-test PROTOCOL_NAMES="Flex_S_v2_19_Illumina_DNA_PCR_Free,OT2_S_v2_18_P300M_P20S_HS_TC_TM_SmokeTestV3" OVERRIDE_PROTOCOL_NAMES=none` + +## Running a Flex just like `make -C robot-server dev-flex` + +> This ALWAYS gets the remote code pushed to Opentrons/opentrons for the specified OPENTRONS_VERSION + +```shell +cd analyses-snapshot-testing \ +&& make build-base-image \ +&& make build-rs OPENTRONS_VERSION=release \ +&& make run-rs OPENTRONS_VERSION=release` +``` + +### Default OPENTRONS_VERSION=edge in the Makefile so you can omit it if you want latest edge + +```shell +cd analyses-snapshot-testing \ +&& make build-base-image \ +&& make build-rs \ +&& make run-rs +``` + +## Running the Analyses Battery against your local code + +> This copies in your local code to the container and runs the analyses battery against it. + +`cd PYENV_ROOT && git pull` - make sure pyenv is up to date so you may install python 3.13.0 +`pyenv install 3.13.0` - install python 3.13.0 +`cd /analyses-snapshot-testing` - navigate to the analyses-snapshot-testing directory +`pyenv local 3.13.0` - set the local python version to 3.13.0 +`make setup` - install the requirements +`make snapshot-test-local` - this target builds the base image, builds the local code into the base image, then runs the analyses battery against the image you just created + +You have the option to specify one or many protocols to run the analyses on. This is also described above [Running the tests against specific protocols](#running-the-tests-against-specific-protocols) + +- `make snapshot-test-local PROTOCOL_NAMES=Flex_S_v2_19_Illumina_DNA_PCR_Free OVERRIDE_PROTOCOL_NAMES=none` + +### Updating the snapshots locally + +- `make snapshot-test-update-local` - this target builds the base image, builds the local code into the base image, then runs the analyses battery against the image you just created, updating the snapshots by passing the `--update-snapshots` flag to the test diff --git a/analyses-snapshot-testing/automation/data/protocols.py b/analyses-snapshot-testing/automation/data/protocols.py index 580c9183d9d..ada74a736e0 100644 --- a/analyses-snapshot-testing/automation/data/protocols.py +++ b/analyses-snapshot-testing/automation/data/protocols.py @@ -641,6 +641,74 @@ class Protocols: robot="Flex", ) + # analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_None_PARTIAL_COLUMN_HappyPath.py + Flex_S_v2_20_8_None_PARTIAL_COLUMN_HappyPath: Protocol = Protocol( + file_stem="Flex_S_v2_20_8_None_PARTIAL_COLUMN_HappyPath", + file_extension="py", + robot="Flex", + ) + # analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_None_SINGLE_HappyPath.py + Flex_S_v2_20_8_None_SINGLE_HappyPath: Protocol = Protocol( + file_stem="Flex_S_v2_20_8_None_SINGLE_HappyPath", + file_extension="py", + robot="Flex", + ) + # analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_AllCorners.py + Flex_S_v2_20_96_AllCorners: Protocol = Protocol( + file_stem="Flex_S_v2_20_96_AllCorners", + file_extension="py", + robot="Flex", + ) + # analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_COLUMN_HappyPath.py + Flex_S_v2_20_96_None_COLUMN_HappyPath: Protocol = Protocol( + file_stem="Flex_S_v2_20_96_None_COLUMN_HappyPath", + file_extension="py", + robot="Flex", + ) + # analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_Column3_SINGLE_.py + Flex_S_v2_20_96_None_Column3_SINGLE_: Protocol = Protocol( + file_stem="Flex_S_v2_20_96_None_Column3_SINGLE_", + file_extension="py", + robot="Flex", + ) + # analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_ROW_HappyPath.py + Flex_S_v2_20_96_None_ROW_HappyPath: Protocol = Protocol( + file_stem="Flex_S_v2_20_96_None_ROW_HappyPath", + file_extension="py", + robot="Flex", + ) + # analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_SINGLE_4Corners50ul.py + Flex_S_v2_20_96_None_SINGLE_4Corners50ul: Protocol = Protocol( + file_stem="Flex_S_v2_20_96_None_SINGLE_4Corners50ul", + file_extension="py", + robot="Flex", + ) + # analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_SINGLE_HappyPathNorthSide.py + Flex_S_v2_20_96_None_SINGLE_HappyPathNorthSide: Protocol = Protocol( + file_stem="Flex_S_v2_20_96_None_SINGLE_HappyPathNorthSide", + file_extension="py", + robot="Flex", + ) + # analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_SINGLE_HappyPathSouthSide.py + Flex_S_v2_20_96_None_SINGLE_HappyPathSouthSide: Protocol = Protocol( + file_stem="Flex_S_v2_20_96_None_SINGLE_HappyPathSouthSide", + file_extension="py", + robot="Flex", + ) + + # analyses-snapshot-testing/files/protocols/Flex_S_v2_20_P8X1000_P50_LLD.py + Flex_S_v2_20_P8X1000_P50_LLD: Protocol = Protocol( + file_stem="Flex_S_v2_20_P8X1000_P50_LLD", + file_extension="py", + robot="Flex", + ) + # analyses-snapshot-testing/files/protocols/Flex_S_v2_20_P50_LPD.py + Flex_S_v2_20_P50_LPD: Protocol = Protocol( + file_stem="Flex_S_v2_20_P50_LPD", + file_extension="py", + robot="Flex", + ) + OT2_X_v2_18_None_None_duplicateRTPVariableName: Protocol = Protocol( file_stem="OT2_X_v2_18_None_None_duplicateRTPVariableName", file_extension="py", @@ -677,6 +745,19 @@ class Protocols: robot="OT2", ) + # analyses-snapshot-testing/files/protocols/OT2_S_v2_20_8_None_PARTIAL_COLUMN_HappyPathMixedTipRacks.py + OT2_S_v2_20_8_None_PARTIAL_COLUMN_HappyPathMixedTipRacks: Protocol = Protocol( + file_stem="OT2_S_v2_20_8_None_PARTIAL_COLUMN_HappyPathMixedTipRacks", + file_extension="py", + robot="OT2", + ) + # analyses-snapshot-testing/files/protocols/OT2_S_v2_20_8_None_SINGLE_HappyPath.py + OT2_S_v2_20_8_None_SINGLE_HappyPath: Protocol = Protocol( + file_stem="OT2_S_v2_20_8_None_SINGLE_HappyPath", + file_extension="py", + robot="OT2", + ) + ########################################################################################################## # Begin Protocol Library Protocols ####################################################################### ########################################################################################################## diff --git a/analyses-snapshot-testing/automation/data/protocols_with_overrides.py b/analyses-snapshot-testing/automation/data/protocols_with_overrides.py index ff4a900e58d..5018a848c31 100644 --- a/analyses-snapshot-testing/automation/data/protocols_with_overrides.py +++ b/analyses-snapshot-testing/automation/data/protocols_with_overrides.py @@ -38,3 +38,72 @@ class ProtocolsWithOverrides: override_variable_name="type_to_test", overrides=["str_default_no_matching_choices", "float_default_no_matching_choices", "int_default_no_matching_choices"], ) + + # analyses-snapshot-testing/files/protocols/Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs.py + + Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs: ProtocolWithOverrides = ProtocolWithOverrides( + file_stem="Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs", + file_extension="py", + robot="Flex", + override_variable_name="key", + overrides=[ + "ninety_six_partial_column_1", + "ninety_six_partial_column_2", + "ninety_six_partial_column_3", + "eight_partial_column_bottom_left", + "eight_partial_column_bottom_right", + "eight_partial_column_no_end", + "return_tip_error", + "drop_tip_with_location", + ], + ) + + # analyses-snapshot-testing/files/protocols/Flex_X_v2_20_96_None_Overrides_TooTallLabware.py + + Flex_X_v2_20_96_None_Overrides_TooTallLabware: ProtocolWithOverrides = ProtocolWithOverrides( + file_stem="Flex_X_v2_20_96_None_Overrides_TooTallLabware", + file_extension="py", + robot="Flex", + override_variable_name="key", + overrides=[ + "transfer_source_collision", + "transfer_destination_collision", + "c3_right_edge", + "north", + "north_west", + "west", + "south_west", + "south", + "south_east", + "east", + "east_column", + "west_column", + "north_row", + "south_row", + "top_edge", + "bottom_left_edge", + "bottom_left_edge", + "bottom_right_edge", + "mix_collision", + "consolidate_source_collision", + "consolidate_destination_collision", + "distribute_source_collision", + "distribute_destination_collision", + ], + ) + + # analyses-snapshot-testing/files/protocols/OT2_X_v2_20_8_Overrides_InvalidConfigs.py + + OT2_X_v2_20_8_Overrides_InvalidConfigs: ProtocolWithOverrides = ProtocolWithOverrides( + file_stem="OT2_X_v2_20_8_Overrides_InvalidConfigs", + file_extension="py", + robot="Flex", + override_variable_name="key", + overrides=[ + "eight_partial_column_bottom_left", + "eight_partial_column_bottom_right", + "eight_partial_column_no_end", + "return_tip_error", + "drop_tip_with_location", + ], + ) diff --git a/analyses-snapshot-testing/citools/Dockerfile b/analyses-snapshot-testing/citools/Dockerfile deleted file mode 100644 index 123b7636652..00000000000 --- a/analyses-snapshot-testing/citools/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -# Use 3.10 just like the app does -FROM python:3.10-slim-bullseye - -# Update packages and install git -RUN apt-get update && \ - apt-get upgrade -y && \ - apt-get install -y git libsystemd-dev - -# Define build arguments -ARG ANALYSIS_REF=edge - -# Set the working directory in the container -WORKDIR /opentrons - -# Clone the Opentrons repository at the specified commit or tag -ARG CACHEBUST=1 -RUN git clone --branch $ANALYSIS_REF --depth 1 https://github.com/Opentrons/opentrons . - -# Install packages from local directories -RUN python -m pip install -U ./shared-data/python -RUN python -m pip install -U ./hardware[flex] -RUN python -m pip install -U ./api -RUN python -m pip install -U pandas==1.4.3 - -# The default command to run when starting the container -CMD ["tail", "-f", "/dev/null"] diff --git a/analyses-snapshot-testing/citools/Dockerfile.analyze b/analyses-snapshot-testing/citools/Dockerfile.analyze new file mode 100644 index 00000000000..1b85981cdaf --- /dev/null +++ b/analyses-snapshot-testing/citools/Dockerfile.analyze @@ -0,0 +1,22 @@ +ARG BASE_IMAGE_NAME=opentrons-python-base:3.10 + +FROM ${BASE_IMAGE_NAME} + +# Define build arguments +ARG ANALYSIS_REF=edge + +# Set the working directory in the container +WORKDIR /opentrons + +# Clone the Opentrons repository at the specified commit or tag +ARG CACHEBUST=1 +RUN git clone --branch $ANALYSIS_REF --depth 1 https://github.com/Opentrons/opentrons . + +# Install packages from local directories +RUN python -m pip install -U ./shared-data/python +RUN python -m pip install -U ./hardware[flex] +RUN python -m pip install -U ./api +RUN python -m pip install -U pandas==1.4.3 + +# The default command to run when starting the container +CMD ["tail", "-f", "/dev/null"] diff --git a/analyses-snapshot-testing/citools/Dockerfile.base b/analyses-snapshot-testing/citools/Dockerfile.base new file mode 100644 index 00000000000..086987e671b --- /dev/null +++ b/analyses-snapshot-testing/citools/Dockerfile.base @@ -0,0 +1,7 @@ +# Use Python 3.10 as the base image +FROM python:3.10-slim-bullseye + +# Update packages and install dependencies +RUN apt-get update && \ + apt-get upgrade -y && \ + apt-get install -y git libsystemd-dev build-essential pkg-config network-manager diff --git a/analyses-snapshot-testing/citools/Dockerfile.local b/analyses-snapshot-testing/citools/Dockerfile.local new file mode 100644 index 00000000000..2346b4680c2 --- /dev/null +++ b/analyses-snapshot-testing/citools/Dockerfile.local @@ -0,0 +1,19 @@ +ARG BASE_IMAGE_NAME=opentrons-python-base:3.10 + +FROM ${BASE_IMAGE_NAME} + +# Set the working directory in the container +WORKDIR /opentrons + +# Copy everything from the build context into the /opentrons directory +# root directory .dockerignore file is respected +COPY . /opentrons + +# Install required packages from the copied code +RUN python -m pip install -U ./shared-data/python +RUN python -m pip install -U ./hardware[flex] +RUN python -m pip install -U ./api +RUN python -m pip install -U pandas==1.4.3 + +# The default command to keep the container running +CMD ["tail", "-f", "/dev/null"] diff --git a/analyses-snapshot-testing/citools/Dockerfile.server b/analyses-snapshot-testing/citools/Dockerfile.server new file mode 100644 index 00000000000..0c44c1e04f0 --- /dev/null +++ b/analyses-snapshot-testing/citools/Dockerfile.server @@ -0,0 +1,27 @@ +ARG BASE_IMAGE_NAME=opentrons-python-base:3.10 + +FROM ${BASE_IMAGE_NAME} + +# Define build arguments +ARG OPENTRONS_VERSION=edge + +# Set the working directory +WORKDIR /opentrons + +# Clone the Opentrons repository +ARG CACHEBUST=1 +RUN git clone --branch $OPENTRONS_VERSION --depth 1 https://github.com/Opentrons/opentrons . + +# Install dependencies +RUN make setup-py -j + +WORKDIR /opentrons/robot-server + +# Set the port via environment variable +ENV PORT=31950 + +# Expose the port +EXPOSE ${PORT} + +# Default command +CMD ["sh", "-c", "python -m pipenv run uvicorn robot_server.app:app --host 0.0.0.0 --port ${PORT} --ws wsproto --lifespan on"] diff --git a/analyses-snapshot-testing/citools/generate_analyses.py b/analyses-snapshot-testing/citools/generate_analyses.py index f1335b102ae..7d550b47776 100644 --- a/analyses-snapshot-testing/citools/generate_analyses.py +++ b/analyses-snapshot-testing/citools/generate_analyses.py @@ -1,13 +1,13 @@ +import concurrent import json import os -import signal import time -from contextlib import contextmanager +from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass from datetime import datetime, timezone from enum import Enum, auto from pathlib import Path -from typing import Any, Dict, Generator, List, Optional +from typing import Any, Dict, List, Optional import docker # type: ignore from automation.data.protocol import Protocol @@ -15,31 +15,22 @@ from rich.traceback import install install(show_locals=True) -IMAGE = "opentrons-analysis" -CONTAINER_LABWARE = "/var/lib/ot" -HOST_LABWARE = Path(Path(__file__).parent.parent, "files", "labware") -HOST_PROTOCOLS_ROOT = Path(Path(__file__).parent.parent, "files", "protocols") -CONTAINER_PROTOCOLS_ROOT = "/var/lib/ot/protocols" -CONTAINER_RESULTS = "/var/lib/ot/analysis_results" -HOST_RESULTS = Path(Path(__file__).parent.parent, "analysis_results") -ANALYSIS_SUFFIX = "analysis.json" +IMAGE: str = "opentrons-analysis" +CONTAINER_LABWARE: str = "/var/lib/ot" +HOST_LABWARE: Path = Path(Path(__file__).parent.parent, "files", "labware") +HOST_PROTOCOLS_ROOT: Path = Path(Path(__file__).parent.parent, "files", "protocols") +CONTAINER_PROTOCOLS_ROOT: str = "/var/lib/ot/protocols" +CONTAINER_RESULTS: str = "/var/lib/ot/analysis_results" +HOST_RESULTS: Path = Path(Path(__file__).parent.parent, "analysis_results") +ANALYSIS_SUFFIX: str = "analysis.json" +ANALYSIS_TIMEOUT_SECONDS: int = 30 +MAX_ANALYSIS_CONTAINER_INSTANCES: int = 5 console = Console() -@contextmanager -def timeout(seconds: int) -> Generator[None, None, None]: - # Signal handler function - def raise_timeout(signum, frame) -> None: # type: ignore[no-untyped-def] - raise TimeoutError - - # Set the signal handler for the alarm signal - signal.signal(signal.SIGALRM, raise_timeout) - signal.alarm(seconds) # Set the alarm - try: - yield - finally: - signal.alarm(0) # Disable the alarm +def is_running_in_github_actions() -> bool: + return os.getenv("GITHUB_ACTIONS") == "true" class ProtocolType(Enum): @@ -48,7 +39,7 @@ class ProtocolType(Enum): @dataclass -class AnalyzedProtocol: +class TargetProtocol: host_protocol_file: Path container_protocol_file: Path host_analysis_file: Path @@ -111,7 +102,7 @@ def set_analysis_execution_time(self, analysis_execution_time: float) -> None: self.analysis_execution_time = analysis_execution_time -def stop_and_restart_container(image_name: str, timeout: int = 60) -> docker.models.containers.Container: +def start_containers(image_name: str, num_containers: int, timeout: int = 60) -> List[docker.models.containers.Container]: client = docker.from_env() volumes = { str(HOST_LABWARE): {"bind": CONTAINER_LABWARE, "mode": "rw"}, @@ -119,64 +110,55 @@ def stop_and_restart_container(image_name: str, timeout: int = 60) -> docker.mod str(HOST_PROTOCOLS_ROOT): {"bind": CONTAINER_PROTOCOLS_ROOT, "mode": "rw"}, } - # Find the running container using the specified image - containers = client.containers.list(filters={"ancestor": image_name, "status": "running"}) - + # Stop and remove existing containers + containers: List[docker.models.containers.Container] = client.containers.list(filters={"ancestor": image_name}) if containers: - console.print("Stopping the running container(s)...") + console.print("Stopping and removing existing container(s)...") for container in containers: container.stop(timeout=10) + container.remove() - # Start a new container with the specified volume - console.print("Starting a new container.") - container = client.containers.run(image_name, detach=True, volumes=volumes) + # Start new containers + console.print(f"Starting {num_containers} new container(s).") + containers = [] + for _ in range(num_containers): + container = client.containers.run(image_name, detach=True, volumes=volumes) + containers.append(container) - # Wait for the container to be ready if a readiness command is provided + # Wait for containers to be ready start_time = time.time() while time.time() - start_time < timeout: - exit_code, output = container.exec_run(f"ls -al {CONTAINER_LABWARE}") - if exit_code == 0: - console.print("Container is ready.") + all_ready = True + for container in containers: + exit_code, _ = container.exec_run(f"ls -al {CONTAINER_LABWARE}") + if exit_code != 0: + all_ready = False + break + if all_ready: + console.print("All containers are ready.") break else: - console.print("Waiting for container to be ready...") - time.sleep(5) + console.print("Waiting for containers to be ready...") + time.sleep(5) else: - console.print("Timeout waiting for container to be ready. Proceeding anyway.") - return container + console.print("Timeout waiting for containers to be ready. Proceeding anyway.") + return containers def stop_and_remove_containers(image_name: str) -> None: client = docker.from_env() - - # Find all containers created from the specified image containers = client.containers.list(all=True, filters={"ancestor": image_name}) - for container in containers: try: - # Stop the container if it's running if container.status == "running": console.print(f"Stopping container {container.short_id}...") container.stop(timeout=10) - - # Remove the container console.print(f"Removing container {container.short_id}...") container.remove() - except docker.errors.ContainerError as e: + except Exception as e: console.print(f"Error stopping/removing container {container.short_id}: {e}") -def has_designer_application(json_file_path: Path) -> bool: - try: - with open(json_file_path, "r", encoding="utf-8") as file: - data = json.load(file) - return "designerApplication" in data - except json.JSONDecodeError: - # Handle the exception if the file is not a valid JSON - console.print(f"Invalid JSON file: {json_file_path}") - return False - - def host_analysis_path(protocol_file: Path, tag: str) -> Path: return Path(HOST_RESULTS, f"{protocol_file.stem}_{tag}_{ANALYSIS_SUFFIX}") @@ -185,79 +167,6 @@ def container_analysis_path(protocol_file: Path, tag: str) -> Path: return Path(CONTAINER_RESULTS, f"{protocol_file.stem}_{tag}_{ANALYSIS_SUFFIX}") -def generate_protocols(tag: str) -> List[AnalyzedProtocol]: - - # Since we do not have the specification for which labware to use - # we will use all labware in the host labware directory - all_custom_labware_paths = [str(host_path.relative_to(CONTAINER_LABWARE)) for host_path in list(Path(HOST_LABWARE).rglob("*.json"))] - - def find_pd_protocols() -> List[AnalyzedProtocol]: - # Check if the provided path is a valid directory - if not HOST_PROTOCOLS_ROOT.is_dir(): - raise NotADirectoryError(f"The path {HOST_PROTOCOLS_ROOT} is not a valid directory.") - - nonlocal all_custom_labware_paths - - # Recursively find all .json files - json_files = list(HOST_PROTOCOLS_ROOT.rglob("*.json")) - filtered_json_files = [file for file in json_files if has_designer_application(file)] - pd_protocols: List[AnalyzedProtocol] = [] - for path in filtered_json_files: - relative_path = path.relative_to(HOST_PROTOCOLS_ROOT) - updated_path = Path(CONTAINER_PROTOCOLS_ROOT, relative_path) - pd_protocols.append( - AnalyzedProtocol( - host_protocol_file=path, - container_protocol_file=updated_path, - host_analysis_file=host_analysis_path(path, tag), - container_analysis_file=container_analysis_path(path, tag), - tag=tag, - custom_labware_paths=all_custom_labware_paths, - ) - ) - return pd_protocols - - def find_python_protocols() -> List[AnalyzedProtocol]: - # Check if the provided path is a valid directory - if not HOST_PROTOCOLS_ROOT.is_dir(): - raise NotADirectoryError(f"The path {HOST_PROTOCOLS_ROOT} is not a valid directory.") - - nonlocal all_custom_labware_paths - - # Recursively find all .py files - python_files = list(HOST_PROTOCOLS_ROOT.rglob("*.py")) - py_protocols: List[AnalyzedProtocol] = [] - - for path in python_files: - relative_path = path.relative_to(HOST_PROTOCOLS_ROOT) - container_path = Path(CONTAINER_PROTOCOLS_ROOT, relative_path) - py_protocols.append( - AnalyzedProtocol( - host_protocol_file=path, - container_protocol_file=container_path, - host_analysis_file=host_analysis_path(path, tag), - container_analysis_file=container_analysis_path(path, tag), - tag=tag, - custom_labware_paths=all_custom_labware_paths, - ) - ) - return py_protocols - - return find_pd_protocols() + find_python_protocols() - - -def remove_all_files_in_directory(directory: Path) -> None: - for filename in os.listdir(directory): - file_path = os.path.join(directory, filename) - try: - if os.path.isfile(file_path) or os.path.islink(file_path): - os.unlink(file_path) - elif os.path.isdir(file_path): - pass # Currently, subdirectories are not removed - except Exception as e: - console.print(f"Failed to delete {file_path}. Reason: {e}") - - def protocol_custom_labware_paths_in_container(protocol: Protocol) -> List[str]: if not HOST_LABWARE.is_dir() or protocol.custom_labware is None: return [] @@ -269,98 +178,95 @@ def protocol_custom_labware_paths_in_container(protocol: Protocol) -> List[str]: ] -def analyze(protocol: AnalyzedProtocol, container: docker.models.containers.Container) -> bool: - # Run the analyze command - command = f"python -I -m opentrons.cli analyze --json-output {protocol.container_analysis_file} {protocol.container_protocol_file} {' '.join(protocol.custom_labware_paths)}" # noqa: E501 +def analyze(protocol: TargetProtocol, container: docker.models.containers.Container) -> bool: + command = ( + f"python -I -m opentrons.cli analyze --json-output {protocol.container_analysis_file} " + f"{protocol.container_protocol_file} {' '.join(protocol.custom_labware_paths)}" + ) start_time = time.time() - timeout_duration = 30 # seconds + result = None + exit_code = None + console.print(f"Beginning analysis of {protocol.host_protocol_file.name}") try: - with timeout(timeout_duration): - command_result = container.exec_run(cmd=command) - exit_code = command_result.exit_code - result = command_result.output - protocol.command_output = result.decode("utf-8") - protocol.command_exit_code = exit_code - protocol.set_analysis() - protocol.set_analysis_execution_time(time.time() - start_time) - return True - except TimeoutError: - console.print(f"Command execution exceeded {timeout_duration} seconds and was aborted.") - logs = container.logs() - # Decode and print the logs - console.print(f"container logs{logs.decode('utf-8')}") - except KeyboardInterrupt: - console.print("Execution was interrupted by the user.") - raise + command_result = container.exec_run(cmd=command) + exit_code = command_result.exit_code + result = command_result.output + protocol.command_output = result.decode("utf-8") if result else "" + protocol.command_exit_code = exit_code + protocol.set_analysis() + return True except Exception as e: console.print(f"An unexpected error occurred: {e}") - protocol.command_output = result.decode("utf-8") - console.print(f"Command output: {protocol.command_output}") - protocol.command_exit_code = exit_code - console.print(f"Exit code: {protocol.command_exit_code}") + protocol.command_output = result.decode("utf-8") if result else str(e) + protocol.command_exit_code = exit_code if exit_code is not None else -1 protocol.set_analysis() return False - protocol.command_output = None - protocol.command_exit_code = None - protocol.analysis = None - protocol.set_analysis_execution_time(time.time() - start_time) - return False - + finally: + protocol.set_analysis_execution_time(time.time() - start_time) + console.print(f"Analysis of {protocol.host_protocol_file.name} completed in {protocol.analysis_execution_time:.2f} seconds.") + + +def analyze_many(protocol_files: List[TargetProtocol], containers: List[docker.models.containers.Container]) -> None: + num_containers = len(containers) + with ThreadPoolExecutor(max_workers=num_containers) as executor: + futures = [] + for i, protocol in enumerate(protocol_files): + container = containers[i % num_containers] + future = executor.submit(analyze, protocol, container) + futures.append((future, protocol)) + for future, protocol in futures: + try: + future.result(timeout=ANALYSIS_TIMEOUT_SECONDS) + except concurrent.futures.TimeoutError: + console.print(f"Analysis of {protocol.host_protocol_file} exceeded {ANALYSIS_TIMEOUT_SECONDS} seconds and was aborted.") + # Handle timeout (e.g., mark as failed) + except Exception as e: + console.print(f"An error occurred during analysis: {e}") -def analyze_many(protocol_files: List[AnalyzedProtocol], container: docker.models.containers.Container) -> None: - for file in protocol_files: - analyze(file, container) accumulated_time = sum(protocol.analysis_execution_time for protocol in protocol_files if protocol.analysis_execution_time is not None) console.print(f"{len(protocol_files)} protocols with total analysis time of {accumulated_time:.2f} seconds.\n") -def analyze_against_image(tag: str) -> List[AnalyzedProtocol]: +def analyze_against_image(tag: str, protocols: List[TargetProtocol], num_containers: int = 1) -> List[TargetProtocol]: image_name = f"{IMAGE}:{tag}" - protocols = generate_protocols(tag) - protocols_to_process = protocols - # protocols_to_process = protocols[:1] # For testing try: - console.print(f"Analyzing {len(protocols_to_process)} protocol(s) against {image_name}...") - container = stop_and_restart_container(image_name) - analyze_many(protocols_to_process, container) + console.print(f"\nAnalyzing {len(protocols)} protocol(s) against {image_name} using {num_containers} container(s)...") + containers = start_containers(image_name, num_containers) + analyze_many(protocols, containers) finally: - stop_and_remove_containers(image_name) - return protocols_to_process + if is_running_in_github_actions(): + pass # We don't need to stop and remove containers in CI + else: + stop_and_remove_containers(image_name) + return protocols + + +def get_container_instances(protocol_len: int) -> int: + # Scaling linearly with the number of protocols + instances = max(1, min(MAX_ANALYSIS_CONTAINER_INSTANCES, protocol_len // 10)) + return instances def generate_analyses_from_test(tag: str, protocols: List[Protocol]) -> None: """Generate analyses from the tests.""" - try: - image_name = f"{IMAGE}:{tag}" - protocols_to_process: List[AnalyzedProtocol] = [] - # convert the protocols to AnalyzedProtocol - for test_protocol in protocols: - host_protocol_file = Path(test_protocol.file_path) - container_protocol_file = Path(CONTAINER_PROTOCOLS_ROOT, host_protocol_file.relative_to(HOST_PROTOCOLS_ROOT)) - host_analysis_file = host_analysis_path(host_protocol_file, tag) - container_analysis_file = container_analysis_path(host_protocol_file, tag) - protocols_to_process.append( - AnalyzedProtocol( - host_protocol_file, - container_protocol_file, - host_analysis_file, - container_analysis_file, - tag, - protocol_custom_labware_paths_in_container(test_protocol), - ) + start_time = time.time() + protocols_to_process: List[TargetProtocol] = [] + for test_protocol in protocols: + host_protocol_file = Path(test_protocol.file_path) + container_protocol_file = Path(CONTAINER_PROTOCOLS_ROOT, host_protocol_file.relative_to(HOST_PROTOCOLS_ROOT)) + host_analysis_file = host_analysis_path(host_protocol_file, tag) + container_analysis_file = container_analysis_path(host_protocol_file, tag) + protocols_to_process.append( + TargetProtocol( + host_protocol_file, + container_protocol_file, + host_analysis_file, + container_analysis_file, + tag, + protocol_custom_labware_paths_in_container(test_protocol), ) - console.print(f"Analyzing {len(protocols_to_process)} protocol(s) against {tag}...") - container = stop_and_restart_container(image_name) - # Analyze the protocols - for protocol_to_analyze in protocols_to_process: - console.print(f"Analyzing {protocol_to_analyze.host_protocol_file}...") - analyzed = analyze(protocol_to_analyze, container) - if not analyzed: # Fail fast - console.print("Analysis failed. Exiting.") - stop_and_remove_containers(image_name) - accumulated_time = sum( - protocol.analysis_execution_time for protocol in protocols_to_process if protocol.analysis_execution_time is not None ) - console.print(f"{len(protocols_to_process)} protocols with total analysis time of {accumulated_time:.2f} seconds.\n") - finally: - stop_and_remove_containers(image_name) + instance_count = get_container_instances(len(protocols_to_process)) + analyze_against_image(tag, protocols_to_process, instance_count) + end_time = time.time() + console.print(f"Clock time to generate analyses: {end_time - start_time:.2f} seconds.") diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_1000M_Simple.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_1000M_Simple.py new file mode 100644 index 00000000000..36fe24def92 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_1000M_Simple.py @@ -0,0 +1,93 @@ +from opentrons.protocol_api import PARTIAL_COLUMN + +metadata = { + "protocolName": "flex_8channel_1000 Simple", + "description": "A protocol that demonstrates safe actions with flex_8channel_1000", + "author": "Josh McVey", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def comment_tip_rack_status(ctx, tip_rack): + """ + Print out the tip status for each row in a tip rack. + Each row (A-H) will print the well statuses for columns 1-12 in a single comment, + with a '🟢' for present tips and a '❌' for missing tips. + """ + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + range_1_to_12 = range(1, 13) + + ctx.comment(f"Tip rack in {tip_rack.parent}") + + for row in range_A_to_H: + status_line = f"{row}: " + for col in range_1_to_12: + well = f"{row}{col}" + has_tip = tip_rack.wells_by_name()[well].has_tip + status_emoji = "🟢" if has_tip else "❌" + status_line += f"{well} {status_emoji} " + + # Print the full status line for the row + ctx.comment(status_line) + + +def run(protocol): + + trash = protocol.load_trash_bin("A3") # must load trash bin + tip_rack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + label="Tip Rack", + location="A1", + ) + + pipette = protocol.load_instrument(instrument_name="flex_8channel_1000", mount="left", tip_racks=[tip_rack]) + + source_labware_C2 = protocol.load_labware( + load_name="nest_1_reservoir_290ml", + label="Source Reservoir", + location="C2", + ) + + destination_labware_D2 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="PCR Plate", + location="2", + ) + + volume = 100 + pipette.pick_up_tip() + comment_tip_rack_status(protocol, tip_rack) + pipette.aspirate(volume=volume, location=destination_labware_D2["H1"]) + pipette.dispense(volume=volume, location=destination_labware_D2["H2"]) + for i in range(1, 13): + protocol.comment(f"Touching tip to {destination_labware_D2[f'H{i}']}") + pipette.touch_tip(location=destination_labware_D2[f"H{i}"]) + + pipette.blow_out(location=destination_labware_D2["H1"]) + pipette.mix(repetitions=3, volume=volume, location=destination_labware_D2["H1"]) + pipette.drop_tip() + + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + column1 = [destination_labware_D2[f"{row}1"] for row in range_A_to_H] + column2 = [destination_labware_D2[f"{row}2"] for row in range_A_to_H] + column3 = [destination_labware_D2[f"{row}3"] for row in range_A_to_H] + column4 = [destination_labware_D2[f"{row}4"] for row in range_A_to_H] + column5 = [destination_labware_D2[f"{row}5"] for row in range_A_to_H] + column6 = [destination_labware_D2[f"{row}6"] for row in range_A_to_H] + column7 = [destination_labware_D2[f"{row}7"] for row in range_A_to_H] + + protocol.comment(f"Transferring {volume}uL from column 1 to column 2") + pipette.transfer(volume=volume, source=column1, dest=column2) + comment_tip_rack_status(protocol, tip_rack) + + volume = 50 + protocol.comment(f"Distribute {volume}uL from column 2 to column 3 and 4") + pipette.distribute(volume=volume, source=column2, dest=column3 + column4) + comment_tip_rack_status(protocol, tip_rack) + protocol.comment(f"Consolidate {volume}uL from column 5 and 6 to column 7") + pipette.consolidate(volume=volume, source=column5 + column6, dest=column7) + comment_tip_rack_status(protocol, tip_rack) diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_None_PARTIAL_COLUMN_HappyPath.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_None_PARTIAL_COLUMN_HappyPath.py new file mode 100644 index 00000000000..cde6baf358f --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_None_PARTIAL_COLUMN_HappyPath.py @@ -0,0 +1,109 @@ +from opentrons.protocol_api import PARTIAL_COLUMN + +metadata = { + "protocolName": "8 Channel PARTIAL_COLUMN Happy Path A1 or H1", + "description": "Tip Rack South Clearance for the 8 Channel pipette and a SINGLE partial tip configuration.", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def run(protocol): + + trash = protocol.load_trash_bin("A3") # must load trash bin + + thermocycler = protocol.load_module("thermocycler module gen2") + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_50ul", + label="Partial Tip Rack", + location="B3", + ) + + pipette = protocol.load_instrument(instrument_name="flex_8channel_50", mount="right") + # mount on the right and you will get an error. + + pipette.configure_nozzle_layout( + style=PARTIAL_COLUMN, + start="H1", + end="B1", # 2 Tips + tip_racks=[partial_tip_rack], + ) + + source_labware_B2 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="B2 Source Labware", + location="B2", + ) + + destination_labware_C2 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="C2 Destination Labware", + location="C2", + ) + + volume = 10 # Default volume for actions that require it + + # ############################# + # # Pipette do work + # # leading nozzle + # # index in based on eh number of tips in my current configuration + # pipette.transfer(volume, source_labware_B2["B6"], destination_labware_C2["A6"]) + + # pipette.pick_up_tip() + # pipette.touch_tip(source_labware_B2["B1"]) + # pipette.drop_tip() + # pipette.pick_up_tip() + # pipette.home() + # pipette.drop_tip() + + # pipette.pick_up_tip() + # well = source_labware_B2["D1"] + # # directly from docs http://sandbox.docs.opentrons.com/edge/v2/new_protocol_api.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate + # pipette.move_to(well.bottom(z=2)) + # pipette.mix(10, 10) + # pipette.move_to(well.top(z=5)) + # pipette.blow_out() + # pipette.prepare_to_aspirate() + # pipette.move_to(well.bottom(z=2)) + # pipette.aspirate(10, well.bottom(z=2)) + # pipette.dispense(10) + # pipette.drop_tip() + + # ############################ + # # Change the pipette configuration + + # pipette.configure_nozzle_layout( + # style=PARTIAL_COLUMN, + # start="H1", + # end="B1", # 7 Tips + # tip_racks=[partial_tip_rack], + # ) + + ############################# + # Pipette do work + + pipette.transfer(volume, source_labware_B2["A6"], destination_labware_C2["A6"]) + + pipette.pick_up_tip() + pipette.touch_tip(source_labware_B2["B1"]) + pipette.drop_tip() + pipette.pick_up_tip() + pipette.home() + pipette.drop_tip() + + pipette.pick_up_tip() + well = source_labware_B2["D1"] + # directly from docs http://sandbox.docs.opentrons.com/edge/v2/new_protocol_api.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate + pipette.move_to(well.bottom(z=2)) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.prepare_to_aspirate() + pipette.move_to(well.bottom(z=2)) + pipette.aspirate(10, well.bottom(z=2)) + pipette.dispense(10) + pipette.drop_tip() diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_None_SINGLE_HappyPath.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_None_SINGLE_HappyPath.py new file mode 100644 index 00000000000..95493eb0ab3 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_None_SINGLE_HappyPath.py @@ -0,0 +1,131 @@ +from opentrons.protocol_api import SINGLE + +metadata = { + "protocolName": "8 Channel SINGLE Happy Path A1 or H1", + "description": "Tip Rack South Clearance for the 8 Channel pipette and a SINGLE partial tip configuration.", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def run(protocol): + + trash = protocol.load_trash_bin("A3") # must load trash bin + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_50ul", + label="Partial Tip Rack", + location="B3", + ) + + thermocycler = protocol.load_module("thermocycler module gen2") + + pipette = protocol.load_instrument(instrument_name="flex_8channel_50", mount="right") + # mount on the right and you will get an error. + + # On the 8-channel SINGLE + # start="A1" Means the South nozzle will pickup from the NW corner of the tip rack + # start="H1" Means the North nozzle will pickup from the SW corner of the tip rack + pipette.configure_nozzle_layout( + style=SINGLE, + start="A1", # Which tip to start with + tip_racks=[partial_tip_rack], + ) + + source_labware_B2 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="B2 Source Labware", + location="B2", + ) + + destination_labware_C2 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="C2 Destination Labware", + location="C2", + ) + + volume = 10 # Default volume for actions that require it + + ############################# + # Pipette do work + pipette.consolidate( + [volume, volume], + [source_labware_B2["A3"], source_labware_B2["A4"]], + destination_labware_C2["A3"], + ) + + pipette.transfer(volume, source_labware_B2["A6"], destination_labware_C2["A6"]) + + pipette.distribute( + 5, + source_labware_B2["A7"], + [destination_labware_C2["A7"], destination_labware_C2["A8"]], + ) + + pipette.pick_up_tip() + pipette.touch_tip(source_labware_B2["B1"]) + pipette.drop_tip() + pipette.pick_up_tip() + pipette.home() + pipette.drop_tip() + + pipette.pick_up_tip() + well = source_labware_B2["D1"] + # directly from docs http://sandbox.docs.opentrons.com/edge/v2/new_protocol_api.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate + pipette.move_to(well.bottom(z=2)) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.prepare_to_aspirate() + pipette.move_to(well.bottom(z=2)) + pipette.aspirate(10, well.bottom(z=2)) + pipette.dispense(10) + pipette.drop_tip() + + ############################ + # Change the pipette configuration + + pipette.configure_nozzle_layout( + style=SINGLE, + start="H1", # Which tip to start with + tip_racks=[partial_tip_rack], + ) + + ############################# + # Pipette do work + pipette.consolidate( + [volume, volume], + [source_labware_B2["A3"], source_labware_B2["A4"]], + destination_labware_C2["A3"], + ) + + pipette.transfer(volume, source_labware_B2["A6"], destination_labware_C2["A6"]) + + pipette.distribute( + 5, + source_labware_B2["A7"], + [destination_labware_C2["A7"], destination_labware_C2["A8"]], + ) + + pipette.pick_up_tip() + pipette.touch_tip(source_labware_B2["B1"]) + pipette.drop_tip() + pipette.pick_up_tip() + pipette.home() + pipette.drop_tip() + + pipette.pick_up_tip() + well = source_labware_B2["D1"] + # directly from docs http://sandbox.docs.opentrons.com/edge/v2/new_protocol_api.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate + pipette.move_to(well.bottom(z=2)) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.prepare_to_aspirate() + pipette.move_to(well.bottom(z=2)) + pipette.aspirate(10, well.bottom(z=2)) + pipette.dispense(10) + pipette.drop_tip() diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_None_SINGLE_TubeRackCollision.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_None_SINGLE_TubeRackCollision.py new file mode 100644 index 00000000000..4f7a21d58bb --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_None_SINGLE_TubeRackCollision.py @@ -0,0 +1,65 @@ +from opentrons.protocol_api import SINGLE + +metadata = { + "protocolName": "8Channel SINGLE Pickup tuberack collision", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def comment_tip_rack_status(ctx, tip_rack): + """ + Print out the tip status for each row in a tip rack. + Each row (A-H) will print the well statuses for columns 1-12 in a single comment, + with a '🟢' for present tips and a '❌' for missing tips. + """ + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + range_1_to_12 = range(1, 13) + + ctx.comment(f"Tip rack in {tip_rack.parent}") + + for row in range_A_to_H: + status_line = f"{row}: " + for col in range_1_to_12: + well = f"{row}{col}" + has_tip = tip_rack.wells_by_name()[well].has_tip + status_emoji = "🟢" if has_tip else "❌" + status_line += f"{well} {status_emoji} " + + # Print the full status line for the row + ctx.comment(status_line) + + +def run(protocol): + + trash = protocol.load_trash_bin("A3") # must load trash bin + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_50ul", + label="Partial Tip Rack", + location="D2", + ) + + tube_rack = protocol.load_labware( + load_name="opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + label="Tube Rack", + location="B2", + ) + + pipette = protocol.load_instrument(instrument_name="flex_8channel_50", mount="left") + + pipette.configure_nozzle_layout( + style=SINGLE, + start="H1", + tip_racks=[partial_tip_rack], + ) + + pipette.pick_up_tip() + comment_tip_rack_status(protocol, partial_tip_rack) + # can the flex_8channel_50 with 50μl tips really reach the bottom of the tube? + pipette.aspirate(30, tube_rack["A1"].bottom()) + pipette.dispense(30, tube_rack["A2"].bottom()) + pipette.drop_tip() diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_PARTIAL_COLUMN_Overhang.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_PARTIAL_COLUMN_Overhang.py new file mode 100644 index 00000000000..b1bcf5ed77b --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_PARTIAL_COLUMN_Overhang.py @@ -0,0 +1,150 @@ +from opentrons.protocol_api import PARTIAL_COLUMN + +metadata = { + "protocolName": "8 Channel PARTIAL_COLUMN Overhang", + "description": "A protocol that demonstrates overhang into a slot with a partial column configuration.", + "author": "Josh McVey", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def comment_tip_rack_status(ctx, tip_rack): + """ + Print out the tip status for each row in a tip rack. + Each row (A-H) will print the well statuses for columns 1-12 in a single comment, + with a '🟢' for present tips and a '❌' for missing tips. + """ + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + range_1_to_12 = range(1, 13) + + ctx.comment(f"Tip rack in {tip_rack.parent}") + + for row in range_A_to_H: + status_line = f"{row}: " + for col in range_1_to_12: + well = f"{row}{col}" + has_tip = tip_rack.wells_by_name()[well].has_tip + status_emoji = "🟢" if has_tip else "❌" + status_line += f"{well} {status_emoji} " + + # Print the full status line for the row + ctx.comment(status_line) + + +def run(protocol): + + trash = protocol.load_trash_bin("A3") # must load trash bin + + thermocycler = protocol.load_module("thermocycler module gen2") + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_50ul", + label="Partial Tip Rack", + location="B3", + ) + + pipette = protocol.load_instrument(instrument_name="flex_8channel_50", mount="right") + + pipette.configure_nozzle_layout( + style=PARTIAL_COLUMN, + start="H1", + end="D1", # 5 Tips + tip_racks=[partial_tip_rack], + ) + + source_labware_B2 = protocol.load_labware( + load_name="nest_1_reservoir_290ml", + label="B2 Source Reservoir", + location="B2", + ) + + destination_labware_C2 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="C2 Destination Labware", + location="C2", + ) + + volume = 10 # Default volume for actions that require it + + # Known issue in 8.0.0 + # if you target A1 of the labware in C2 + # nozzle H1 - the front-most nozzle will go to A1 + # this means that tips will be out of the slot + # they will over hang into the B2 + # no error is raised + # but a collision will occur with the labware in slot B2 + # the overhanging tips will collide with the labware in B2 + + # If we ever do detect and error for this + # Take all these examples into a negative Overrides test + + volume = 20 + pipette.pick_up_tip() + comment_tip_rack_status(protocol, partial_tip_rack) + # bad - 4 tips will overhang into B2 + pipette.aspirate(volume=5, location=destination_labware_C2["A1"]) + # bad - 3 tips will overhang into B2 + pipette.aspirate(volume=5, location=destination_labware_C2["B1"]) + # bad - 2 tips will overhang into B2 + pipette.aspirate(volume=5, location=destination_labware_C2["C1"]) + # bad - 1 tip will overhang into B2 + pipette.aspirate(volume=5, location=destination_labware_C2["D1"]) + # this is safe + pipette.aspirate(volume=5, location=destination_labware_C2["E1"]) + + # this is safe - from a reservoir + # must aspirate before dispense + pipette.aspirate(volume=volume, location=source_labware_B2["A1"]) + # bad - bad - 4 tips will overhang into B2 + pipette.dispense(volume=volume, location=destination_labware_C2["A1"]) + # this is safe - from a reservoir + # must aspirate before dispense + pipette.aspirate(volume=volume, location=source_labware_B2["A1"]) + # bad - bad - 3 tips will overhang into B2 + pipette.dispense(volume=volume, location=destination_labware_C2["B2"]) + # this is safe - from a reservoir + # must aspirate before dispense + pipette.aspirate(volume=volume, location=source_labware_B2["A1"]) + # bad - bad - 2 tips will overhang into B2 + pipette.dispense(volume=volume, location=destination_labware_C2["C3"]) + # this is safe - from a reservoir + # must aspirate before dispense + pipette.aspirate(volume=volume, location=source_labware_B2["A1"]) + # bad - bad - 1 tip will overhang into B2 + pipette.dispense(volume=volume, location=destination_labware_C2["D4"]) + # this is safe - from a reservoir + # must aspirate before dispense + pipette.aspirate(volume=volume, location=source_labware_B2["A1"]) + # this is safe - 0 tips will overhang into B2 + pipette.dispense(volume=volume, location=destination_labware_C2["E5"]) + # bad - has overhang into B2 - 4 tips + pipette.touch_tip(location=destination_labware_C2["A1"]) + # bad - has overhang into B2 - 4 tips + pipette.blow_out(location=destination_labware_C2["A1"]) + # bad - has overhang into B2 - 4 tips + pipette.mix(repetitions=3, volume=volume, location=destination_labware_C2["A2"]) + # bad - aspirate and dispense have overhang into B2 + pipette.drop_tip() + pipette.transfer(volume=volume, source=destination_labware_C2["A1"], dest=destination_labware_C2["A2"]) + # bad - aspirate has 3 tip overhang but not dispense + pipette.transfer(volume=volume, source=destination_labware_C2["B1"], dest=destination_labware_C2["E1"]) + # bad - aspirate is safe but dispense has 2 tip overhang + pipette.transfer(volume=volume, source=destination_labware_C2["E1"], dest=destination_labware_C2["C3"]) + # bad - source and destinations have overhang + pipette.distribute(volume=20, source=destination_labware_C2["D2"], dest=[destination_labware_C2["D3"], destination_labware_C2["D4"]]) + # bad - source has overhang but destinations are safe + pipette.distribute(volume=20, source=destination_labware_C2["D2"], dest=[destination_labware_C2["E3"], destination_labware_C2["E4"]]) + # bad - source has no overhang but 1 destination does + pipette.distribute(volume=20, source=destination_labware_C2["E6"], dest=[destination_labware_C2["A7"], destination_labware_C2["E7"]]) + # bad - source has no overhang but 2 destinations do + pipette.distribute(volume=20, source=destination_labware_C2["E7"], dest=[destination_labware_C2["A8"], destination_labware_C2["A9"]]) + # bad - all sources and destination have overhang + pipette.consolidate(volume=5, source=[destination_labware_C2["A9"], destination_labware_C2["A10"]], dest=destination_labware_C2["A11"]) + # bad - 1 source has overhang but destination is safe + pipette.consolidate(volume=5, source=[destination_labware_C2["A9"], destination_labware_C2["E10"]], dest=destination_labware_C2["E11"]) + # bad - sources are safe but destination has overhang + pipette.consolidate(volume=5, source=[destination_labware_C2["E9"], destination_labware_C2["E10"]], dest=destination_labware_C2["A12"]) diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_TipsDontMatchWells.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_TipsDontMatchWells.py new file mode 100644 index 00000000000..a6ecaf02340 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_8_TipsDontMatchWells.py @@ -0,0 +1,87 @@ +from opentrons.protocol_api import SINGLE, ALL + +metadata = { + "protocolName": "8channel 50 into a 48 well plate", + "description": "the nozzles on the pipette do not match the target labware wells", + "author": "Josh McVey", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def comment_tip_rack_status(ctx, tip_rack): + """ + Print out the tip status for each row in a tip rack. + Each row (A-H) will print the well statuses for columns 1-12 in a single comment, + with a '🟢' for present tips and a '❌' for missing tips. + """ + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + range_1_to_12 = range(1, 13) + + ctx.comment(f"Tip rack in {tip_rack.parent}") + + for row in range_A_to_H: + status_line = f"{row}: " + for col in range_1_to_12: + well = f"{row}{col}" + has_tip = tip_rack.wells_by_name()[well].has_tip + status_emoji = "🟢" if has_tip else "❌" + status_line += f"{well} {status_emoji} " + + # Print the full status line for the row + ctx.comment(status_line) + + +def run(protocol): + + trash = protocol.load_trash_bin("A3") # must load trash bin + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_50ul", + label="Partial Tip Rack", + location="B3", + ) + + # corning_48_wellplate_1.6ml_flat + corning_48 = protocol.load_labware( + load_name="corning_48_wellplate_1.6ml_flat", + label="Corning 48 Wellplate", + location="D2", + ) + + pipette = protocol.load_instrument(instrument_name="flex_8channel_50", mount="right") + + pipette.configure_nozzle_layout( + style=ALL, + start="H1", + end="B1", # Test that end is ignored when style is ALL and on 8.0.0-alpha.6 it is ✅ + tip_racks=[partial_tip_rack], + ) + + pipette.pick_up_tip() + comment_tip_rack_status(protocol, partial_tip_rack) + volume = 20 + + ###### Purpose statement + ### This protocol is made to detect if analysis output ever changes + ### When we try and pipette to wells that do not match the geometry of the nozzles on the pipette + ###### + + # when using 8 channel location is the front-most well in the column of the target labware + pipette.aspirate(volume=volume, location=corning_48["F1"]) + pipette.dispense(volume=volume, location=corning_48["F1"]) + pipette.touch_tip(location=corning_48["F1"]) + pipette.blow_out(location=corning_48["F1"]) + pipette.mix(repetitions=3, volume=volume, location=corning_48["F1"]) + # next line has this error: Invalid source for multichannel transfer: [F1 of Corning 48 Wellplate on slot D2] + # pipette.transfer(volume=volume, source=corning_48["F1"], dest=corning_48["F2"]) + # next line has this error: Invalid source for multichannel transfer: [F2 of Corning 48 Wellplate on slot D2] + # pipette.distribute(volume=20, source=corning_48["F2"], dest=[corning_48["F3"], corning_48["F4"]]) + # next line has this error: + # Invalid source for multichannel transfer: [F6 of Corning 48 Wellplate on slot D2, F7 of Corning 48 Wellplate on slot D2] + # pipette.consolidate(volume=20, source=[corning_48["F6"], corning_48["F7"]], dest=corning_48["F8"]) + + pipette.drop_tip() # so trash bin shows up in the deck map diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_AllCorners.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_AllCorners.py new file mode 100644 index 00000000000..01fc0b45b9b --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_AllCorners.py @@ -0,0 +1,296 @@ +from opentrons import protocol_api +from opentrons.protocol_api import COLUMN, SINGLE, ROW + +requirements = {"robotType": "Flex", "apiLevel": "2.20"} + + +def run(protocol: protocol_api.ProtocolContext): + pipette = protocol.load_instrument(instrument_name="flex_96channel_1000") + trash = protocol.load_trash_bin("B3") + t1 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + label="A1 Corner Tiprack 1", + location="B1", + ) + t2 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + label="D1 Corner Tiprack", + location="C1", + ) + t3 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + label="A3 Corner Tiprack 1", + location="B2", + ) + t4 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + label="D3 Corner Tiprack", + location="C2", + ) + t5 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + label="A3 Corner Tiprack 2", + location="A2", + ) + t6 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + label="A1 Corner Tiprack 2", + location="D4", + ) + + ### SETUP TIPRACK FUNCTIONS + # These functions serve the purpose of removing tips from a tiprack before it is moved to a corner slot + # This is done to ensure the tiprack is in such a state that it will trigger zero deck extent conflicts + + # Setup T2 + def t2_setup() -> None: + pipette.configure_nozzle_layout( + style=ROW, + start="A1", + tip_racks=[t2], + ) + for i in range(3): + pipette.pick_up_tip() + pipette.drop_tip() + + # Setup T3 + def t3_setup() -> None: + pipette.configure_nozzle_layout(style=COLUMN, start="A1", tip_racks=[t3]) + for i in range(2): + pipette.pick_up_tip() + pipette.drop_tip() + + # Setup T4 + def t4_setup() -> None: + pipette.configure_nozzle_layout( + style=ROW, + start="A1", + tip_racks=[t4], + ) + for i in range(3): + pipette.pick_up_tip() + pipette.drop_tip() + + pipette.configure_nozzle_layout( + style=SINGLE, + start="A1", + tip_racks=[t4], + ) + for i in range(10): + pipette.pick_up_tip() + pipette.drop_tip() + + # Setup T5 + def t5_setup() -> None: + pipette.configure_nozzle_layout( + style=ROW, + start="H1", + tip_racks=[t5], + ) + for i in range(7): + pipette.pick_up_tip() + pipette.drop_tip() + + pipette.configure_nozzle_layout( + style=SINGLE, + start="A1", + tip_racks=[t5], + ) + for i in range(2): + pipette.pick_up_tip() + pipette.drop_tip() + + # Setup T6 + def t6_setup() -> None: + pipette.configure_nozzle_layout( + style=ROW, + start="H1", + tip_racks=[t6], + ) + for i in range(7): + pipette.pick_up_tip() + pipette.drop_tip() + + ### PICKUP TIP FUNCTIONS + # These functions perform pickup tip behavior for a given tiprack + + # Pickup T2 + def t1_pickup() -> None: + pipette.configure_nozzle_layout( + style=SINGLE, + start="A1", + tip_racks=[t1], + ) + for i in range(48): + pipette.pick_up_tip() + pipette.drop_tip() + pipette.configure_nozzle_layout( + style=SINGLE, + start="A12", + tip_racks=[t1], + ) + for i in range(48): + pipette.pick_up_tip() + pipette.drop_tip() + + # Pickup T2 + def t2_pickup() -> None: + pipette.configure_nozzle_layout( + style=SINGLE, + start="A1", + tip_racks=[t2], + ) + for i in range(15): + pipette.pick_up_tip() + pipette.drop_tip() + pipette.configure_nozzle_layout( + style=SINGLE, + start="A12", + tip_racks=[t2], + ) + for i in range(15): + pipette.pick_up_tip() + pipette.drop_tip() + pipette.configure_nozzle_layout( + style=SINGLE, + start="H1", + tip_racks=[t2], + ) + for i in range(15): + pipette.pick_up_tip() + pipette.drop_tip() + pipette.configure_nozzle_layout( + style=SINGLE, + start="H12", + tip_racks=[t2], + ) + for i in range(15): + pipette.pick_up_tip() + pipette.drop_tip() + + # Pickup T3 + def t3_pickup() -> None: + pipette.configure_nozzle_layout( + style=SINGLE, + start="A1", + tip_racks=[t3], + ) + for i in range(40): + pipette.pick_up_tip() + pipette.drop_tip() + pipette.configure_nozzle_layout( + style=SINGLE, + start="A12", + tip_racks=[t3], + ) + for i in range(40): + pipette.pick_up_tip() + pipette.drop_tip() + + # Pickup T4 + def t4_pickup() -> None: + pipette.configure_nozzle_layout( + style=SINGLE, + start="A1", + tip_racks=[t4], + ) + for i in range(10): + pipette.pick_up_tip() + pipette.drop_tip() + pipette.configure_nozzle_layout( + style=SINGLE, + start="A12", + tip_racks=[t4], + ) + for i in range(10): + pipette.pick_up_tip() + pipette.drop_tip() + pipette.configure_nozzle_layout( + style=SINGLE, + start="H1", + tip_racks=[t4], + ) + for i in range(15): + pipette.pick_up_tip() + pipette.drop_tip() + pipette.configure_nozzle_layout( + style=SINGLE, + start="H12", + tip_racks=[t4], + ) + for i in range(15): + pipette.pick_up_tip() + pipette.drop_tip() + + # Pickup T5 + def t5_pickup() -> None: + pipette.configure_nozzle_layout( + style=SINGLE, + start="H1", + tip_racks=[t5], + ) + for i in range(5): + pipette.pick_up_tip() + pipette.drop_tip() + pipette.configure_nozzle_layout( + style=SINGLE, + start="H12", + tip_racks=[t5], + ) + for i in range(5): + pipette.pick_up_tip() + pipette.drop_tip() + + # Pickup T6 + def t6_pickup() -> None: + pipette.configure_nozzle_layout( + style=SINGLE, + start="H1", + tip_racks=[t6], + ) + for i in range(6): + pipette.pick_up_tip() + pipette.drop_tip() + pipette.configure_nozzle_layout( + style=SINGLE, + start="H12", + tip_racks=[t6], + ) + for i in range(6): + pipette.pick_up_tip() + pipette.drop_tip() + + ### Protocol Actions + # Perform Setup of first set of tipracks + protocol.move_labware(t1, "A1", True) + t2_setup() + protocol.move_labware(t2, "D1", True) + t3_setup() + protocol.move_labware(t3, "A3", True) + t4_setup() + protocol.move_labware(t4, "D3", True) + + # Clear out T2 first to make room for T5 + t2_pickup() + protocol.move_labware(t5, "B2", True) + t5_setup() + protocol.move_labware(t5, "C1", True) + + # Clear out T1, T3, and T4 + t1_pickup() + t3_pickup() + protocol.move_labware(t6, "A4", True) + t4_pickup() + protocol.move_labware(t6, "D4", True) + + # Move T3, then move and handle T5 + protocol.move_labware(t3, "D2", True) + protocol.move_labware(t5, "A3", True) + t5_pickup() + + # Move T6 to On-Deck in B2 and setup, then move to A1 and handle + protocol.move_labware(t6, "B2", True) + t6_setup() + protocol.move_labware(t1, "C3", True) + protocol.move_labware(t6, "A1", True) + t6_pickup() diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_LLD.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_LLD.py new file mode 100644 index 00000000000..080fda8a759 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_LLD.py @@ -0,0 +1,93 @@ +from opentrons import protocol_api + +# metadata +metadata = { + "protocolName": "2.20 Error Recovery Testing Protocol - 96ch", + "author": "Sara Kowalski", + "description": "Simple Protocol that user can use to Phase 1 Error Recovery options for a 96 channel p1000.", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + +DRYRUN = "NO" + + +def run(protocol: protocol_api.ProtocolContext): + + # modules/fixtures + trashbin = protocol.load_trash_bin(location="A3") + + # labware + tip_adapter = protocol.load_adapter("opentrons_flex_96_tiprack_adapter", "A2") + tiprack1 = tip_adapter.load_labware("opentrons_flex_96_tiprack_1000ul") + sample_plate = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "C3") + reservoir = protocol.load_labware("nest_1_reservoir_290ml", "D3") + wet_sample = protocol.load_labware("nest_12_reservoir_15ml", "B3") + + # liquids + water = protocol.define_liquid( + name="Water", + description="water for ER testing", + display_color="#90e0ef", + ) + waterButMoreBlue = protocol.define_liquid( + name="Water but more blue", + description="Water for ER testing", + display_color="#0077b6", + ) + + wet_sample["A1"].load_liquid(liquid=water, volume=800) + wet_sample["A2"].load_liquid(liquid=waterButMoreBlue, volume=800) + + # instruments + p1000 = protocol.load_instrument("flex_96channel_1000", mount="left", tip_racks=[tiprack1]) + + volume = 900 + + ############################## + #####Tip Pick Up Failure###### + ############################## + + protocol.pause("This tests Tip Pick Up Failure (General Error for now)") + protocol.pause("Please remove tip rack from deck. When you're testing recovery, add tiprack back as necessary.") + p1000.pick_up_tip() + + ########################################## + #####Overpressure - While Aspirating###### + ########################################## + + protocol.pause("Overpressure - While Aspirating") + p1000.home() + protocol.pause("Swap with tip(s) that are hot glued shut (you will need to swap the tips for retry recovery options)") + + for i in range(10): + p1000.aspirate(volume, reservoir["A1"].top()) + p1000.dispense(volume, reservoir["A1"].top()) + + #################################### + #####Liquid Presence Detection###### + #################################### + protocol.pause("This tests Liquid Presence Detection - PLEASE MAKE SURE YOU'RE USING 1000UL TIPS OR YOU WILL BREAK THE PIPETTE") + protocol.pause("Make sure reservoir is empty, and have full reservoir on standby to switch out with for recovery") + + p1000.require_liquid_presence(wet_sample["A1"]) + + p1000.aspirate(volume, wet_sample["A1"]) + p1000.dispense(volume, wet_sample["A1"]) + p1000.home() + + ##################################################### + #####Overpressure - While Dispensing###### + ##################################################### + + protocol.pause("Overpressure - While Dispensing") + + p1000.aspirate(volume, reservoir["A1"].top(20)) + protocol.pause("Swap with tip(s) that are hot glued shut (you will need to cut the tips for retry recovery options)") + p1000.dispense(volume, reservoir["A1"].top(20)) + + p1000.return_tip() + p1000.reset_tipracks() diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_COLUMN_HappyPath.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_COLUMN_HappyPath.py new file mode 100644 index 00000000000..6fec53ee5b3 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_COLUMN_HappyPath.py @@ -0,0 +1,121 @@ +from opentrons.protocol_api import COLUMN + +metadata = { + "protocolName": "96Channel COLUMN Happy Path", + "description": "96 channel pipette and a COLUMN partial tip configuration.", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def run(protocol): + + trash = protocol.load_trash_bin("A3") # must load trash bin + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + label="Partial Tip Rack", + location="B2", + ) + + pipette = protocol.load_instrument(instrument_name="flex_96channel_1000") + + # Which nozzle to use on the pipette + # Think of the nozzles from above like you are looking down on a 96-well plate + # start="A1" Means the West most nozzle column will pickup from the East most column of the tip rack + # start="A12" Means the East most nozzle column will pickup from the West most column of the tip rack + pipette.configure_nozzle_layout( + style=COLUMN, + start="A1", + tip_racks=[partial_tip_rack], + ) + + source_labware = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="Liquid Transfer - Source Labware", + location="C2", + ) + + destination_labware = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="Liquid Transfer - Destination Labware", + location="C3", + ) + + volume = 10 # Default volume for actions that require it + + ############################# + # Pipette do work + + # consolidate does not work with the COLUMN configuration + # consolidate only has a single well as a destination + + # transfer the row + pipette.transfer(volume, source_labware["A1"], destination_labware["A1"]) + + # distribute does not work with the COLUMN configuration + # distribute only has a single well as a source + + pipette.pick_up_tip() + pipette.touch_tip(source_labware["A2"]) + pipette.drop_tip() + pipette.pick_up_tip() + pipette.home() + pipette.drop_tip() + + pipette.pick_up_tip() + well = source_labware["A3"] + # directly from docs http://sandbox.docs.opentrons.com/edge/v2/new_protocol_api.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate + pipette.move_to(well.bottom(z=2)) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.prepare_to_aspirate() + pipette.move_to(well.bottom(z=2)) + pipette.aspirate(10, well.bottom(z=2)) + pipette.dispense(10) + pipette.drop_tip() + + ############################# + # Change the pipette configuration + + pipette.configure_nozzle_layout( + style=COLUMN, + start="A12", # Which nozzle to start with + tip_racks=[partial_tip_rack], + ) + + ############################# + # Pipette do work + + # consolidate does not work with the COLUMN configuration + # consolidate only has a single well as a destination + + # transfer the row + pipette.transfer(volume, source_labware["A5"], destination_labware["A5"]) + + # distribute does not work with the COLUMN configuration + # distribute only has a single well as a source + + pipette.pick_up_tip() + pipette.touch_tip(source_labware["A6"]) + pipette.drop_tip() + pipette.pick_up_tip() + pipette.home() + pipette.drop_tip() + + pipette.pick_up_tip() + well = source_labware["A7"] + # directly from docs http://sandbox.docs.opentrons.com/edge/v2/new_protocol_api.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate + pipette.move_to(well.bottom(z=2)) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.prepare_to_aspirate() + pipette.move_to(well.bottom(z=2)) + pipette.aspirate(10, well.bottom(z=2)) + pipette.dispense(10) + pipette.drop_tip() diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_Column3_SINGLE_.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_Column3_SINGLE_.py new file mode 100644 index 00000000000..e5d1427a477 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_Column3_SINGLE_.py @@ -0,0 +1,118 @@ +from opentrons import protocol_api +from opentrons.protocol_api import COLUMN, ALL, SINGLE, ROW + +requirements = {"robotType": "Flex", "apiLevel": "2.20"} + + +def run(protocol: protocol_api.ProtocolContext): + pipette = protocol.load_instrument(instrument_name="flex_96channel_1000") + trash = protocol.load_trash_bin("A1") + t1 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + label="Partial Tip Rack", + location="A2", + ) + t2 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + label="Partial Tip Rack", + location="B2", + ) + t3 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + label="Partial Tip Rack", + location="C2", + ) + t4 = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + label="Partial Tip Rack", + location="D2", + ) + + ### Prep tipracks in B2 and D2 by removing 3 columns of tips + pipette.configure_nozzle_layout( + style=COLUMN, + start="A1", + tip_racks=[t2], + ) + for i in range(3): + pipette.pick_up_tip() + pipette.drop_tip() + + pipette.configure_nozzle_layout( + style=COLUMN, + start="A1", + tip_racks=[t4], + ) + for i in range(3): + pipette.pick_up_tip() + pipette.drop_tip() + + ### Relocate tipracks to A3 and C3 for single tip extraction at furthest extant + protocol.move_labware(t2, "A3", True) + protocol.move_labware(t4, "C3", True) + + pipette.configure_nozzle_layout( + style=SINGLE, + start="A1", + tip_racks=[t2, t4], + ) + + for i in range(72 * 2): + pipette.pick_up_tip() + pipette.drop_tip() + + ### Move tipracks out of the way to B1 and C1 + protocol.move_labware(t2, "B1", True) + protocol.move_labware(t4, "C1", True) + + ### Prepare tiprack in A2 by removing 3 columns of tips + pipette.configure_nozzle_layout( + style=COLUMN, + start="A1", + tip_racks=[t1], + ) + for i in range(3): + pipette.pick_up_tip() + pipette.drop_tip() + + ### Prepare tiprack in C2 by removing bottom 3 rows of tips, plus 15 tips (3 leftmost remaining columns) + ### This results in a tiprack of the following layout (matching the requirements for our bottom right extents): + # X X X X X X X X X - - - + # X X X X X X X X X - - - + # X X X X X X X X X - - - + # X X X X X X X X X - - - + # X X X X X X X X X - - - + # - - - - - - - - - - - - + # - - - - - - - - - - - - + # - - - - - - - - - - - - + pipette.configure_nozzle_layout( + style=ROW, + start="A1", + tip_racks=[t3], + ) + for i in range(3): + pipette.pick_up_tip() + pipette.drop_tip() + + pipette.configure_nozzle_layout( + style=SINGLE, + start="A1", + tip_racks=[t3], + ) + for i in range(15): + pipette.pick_up_tip() + pipette.drop_tip() + + ### Relocate tipracks to B3 and D3 on the deck + protocol.move_labware(t1, "B3", True) + protocol.move_labware(t3, "D3", True) + + pipette.configure_nozzle_layout( + style=SINGLE, + start="A1", + tip_racks=[t1, t3], + ) + + for i in range(72 + 45): + pipette.pick_up_tip() + pipette.drop_tip() diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_ROW_HappyPath.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_ROW_HappyPath.py new file mode 100644 index 00000000000..cf253f04bfe --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_ROW_HappyPath.py @@ -0,0 +1,121 @@ +from opentrons.protocol_api import ROW + +metadata = { + "protocolName": "96Channel ROW Happy Path", + "description": "96 channel pipette and a ROW partial tip configuration.", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def run(protocol): + + trash = protocol.load_trash_bin("A3") # must load trash bin + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + label="Partial Tip Rack", + location="B1", + ) + + pipette = protocol.load_instrument(instrument_name="flex_96channel_1000") + + # Which nozzle to use on the pipette + # Think of the nozzles from above like you are looking down on a 96-well plate + # start="A1" Means the North most nozzle row will pickup from the South most row of the tip rack + # start="H1" Means the South most nozzle row will pickup from the North most row of the tip rack + pipette.configure_nozzle_layout( + style=ROW, + start="A1", + tip_racks=[partial_tip_rack], + ) + + source_labware = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="Liquid Transfer - Source Labware", + location="C2", + ) + + destination_labware = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="Liquid Transfer - Destination Labware", + location="C3", + ) + + volume = 10 # Default volume for actions that require it + + ############################# + # Pipette do work + + # consolidate is does not work with the ROW configuration + # consolidate only has a single well as a destination + + # transfer the row + pipette.transfer(volume, source_labware["A1"], destination_labware["A1"]) + + # distribute does not work with the ROW configuration + # distribute only has a single well as a source + + pipette.pick_up_tip() + pipette.touch_tip(source_labware["B1"]) + pipette.drop_tip() + pipette.pick_up_tip() + pipette.home() + pipette.drop_tip() + + pipette.pick_up_tip() + well = source_labware["D1"] + # directly from docs http://sandbox.docs.opentrons.com/edge/v2/new_protocol_api.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate + pipette.move_to(well.bottom(z=2)) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.prepare_to_aspirate() + pipette.move_to(well.bottom(z=2)) + pipette.aspirate(10, well.bottom(z=2)) + pipette.dispense(10) + pipette.drop_tip() + + ############################# + # Change the pipette configuration + + pipette.configure_nozzle_layout( + style=ROW, + start="H1", # Which nozzle to start with + tip_racks=[partial_tip_rack], + ) + + ############################# + # Pipette do work + + # consolidate is does not work with the ROW configuration + # consolidate only has a single well as a destination + + # transfer the row + pipette.transfer(volume, source_labware["E1"], destination_labware["E1"]) + + # distribute does not work with the ROW configuration + # distribute only has a single well as a source + + pipette.pick_up_tip() + pipette.touch_tip(source_labware["F1"]) + pipette.drop_tip() + pipette.pick_up_tip() + pipette.home() + pipette.drop_tip() + + pipette.pick_up_tip() + well = source_labware["G1"] + # directly from docs http://sandbox.docs.opentrons.com/edge/v2/new_protocol_api.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate + pipette.move_to(well.bottom(z=2)) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.prepare_to_aspirate() + pipette.move_to(well.bottom(z=2)) + pipette.aspirate(10, well.bottom(z=2)) + pipette.dispense(10) + pipette.drop_tip() diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_SINGLE_4Corners50ul.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_SINGLE_4Corners50ul.py new file mode 100644 index 00000000000..1ac96b7a7ac --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_SINGLE_4Corners50ul.py @@ -0,0 +1,66 @@ +from opentrons.protocol_api import SINGLE + +metadata = { + "protocolName": "96Channel SINGLE Pickup on all 4 corners 2 pickups", + "description": "96 2 tips picked up on all 4 corners of the tip rack", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def comment_tip_rack_status(ctx, tip_rack): + """ + Print out the tip status for each row in a tip rack. + Each row (A-H) will print the well statuses for columns 1-12 in a single comment, + with a '🟢' for present tips and a '❌' for missing tips. + """ + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + range_1_to_12 = range(1, 13) + + ctx.comment(f"Tip rack in {tip_rack.parent}") + + for row in range_A_to_H: + status_line = f"{row}: " + for col in range_1_to_12: + well = f"{row}{col}" + has_tip = tip_rack.wells_by_name()[well].has_tip + status_emoji = "🟢" if has_tip else "❌" + status_line += f"{well} {status_emoji} " + + # Print the full status line for the row + ctx.comment(status_line) + + +def run(protocol): + + trash = protocol.load_trash_bin("A3") # must load trash bin + + four_corners = ["A1", "A12", "H1", "H12"] + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_50ul", + label="Partial Tip Rack", + location="C2", + ) + + pipette = protocol.load_instrument(instrument_name="flex_96channel_1000") + + for corner in four_corners: + + pipette.configure_nozzle_layout( + style=SINGLE, + start=corner, + tip_racks=[partial_tip_rack], + ) + + pipette.pick_up_tip() + comment_tip_rack_status(protocol, partial_tip_rack) + protocol.pause("How was the pickup of first tip?") + pipette.drop_tip() + pipette.pick_up_tip() + comment_tip_rack_status(protocol, partial_tip_rack) + protocol.pause("How was the pickup of second tip?") + pipette.drop_tip() diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_SINGLE_HappyPathNorthSide.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_SINGLE_HappyPathNorthSide.py new file mode 100644 index 00000000000..ba26d34802d --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_SINGLE_HappyPathNorthSide.py @@ -0,0 +1,143 @@ +from opentrons.protocol_api import SINGLE + +metadata = { + "protocolName": "96Channel SINGLE Happy Path H1 or H12", + "description": "Tip Rack North Clearance for the 96 channel pipette and a SINGLE partial tip configuration.", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def run(protocol): + + trash = protocol.load_trash_bin("A3") # must load trash bin + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + label="Partial Tip Rack", + location="D2", + ) + + pipette = protocol.load_instrument(instrument_name="flex_96channel_1000") + + # Which nozzle to use on the pipette + # Think of the nozzles from above like you are looking down on a 96-well plate + # start="A1" Means the NW nozzle will pickup from the SE corner of the tip rack + # start="A12" Means the SW nozzle will pickup from the NE corner of the tip rack + # start="H1" Means the NE nozzle will pickup from the SW corner of the tip rack + # start="H12" Means the SE nozzle will pickup from the NW corner of the tip rack + pipette.configure_nozzle_layout( + style=SINGLE, + start="H1", # Which nozzle to start with + tip_racks=[partial_tip_rack], + ) + + source_labware_C1 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="Liquid Transfer - Source Labware", + location="C1", + ) + + destination_labware_C2 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="Liquid Transfer - Destination Labware", + location="C2", + ) + + volume = 10 # Default volume for actions that require it + + ############################# + # Pipette do work + pipette.consolidate( + [volume, volume], + [source_labware_C1["A3"], source_labware_C1["A4"]], + destination_labware_C2["A3"], + ) + + pipette.transfer(volume, source_labware_C1["A6"], source_labware_C1["A6"]) + + pipette.distribute( + 5, + source_labware_C1["A7"], + [destination_labware_C2["A7"], destination_labware_C2["A8"]], + ) + + pipette.pick_up_tip() + pipette.touch_tip(source_labware_C1["B1"]) + pipette.drop_tip() + pipette.pick_up_tip() + pipette.home() + pipette.drop_tip() + + pipette.pick_up_tip() + well = source_labware_C1["D1"] + # directly from docs http://sandbox.docs.opentrons.com/edge/v2/new_protocol_api.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate + pipette.move_to(well.bottom(z=2)) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.prepare_to_aspirate() + pipette.move_to(well.bottom(z=2)) + pipette.aspirate(10, well.bottom(z=2)) + pipette.dispense(10) + pipette.drop_tip() + + ############################# + # Change the pipette configuration + + pipette.configure_nozzle_layout( + style=SINGLE, + start="H12", # Which nozzle to start with + tip_racks=[partial_tip_rack], + ) + + source_labware_C3 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="C3 Source Labware", + location="C3", + ) + + destination_labware_B2 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="B2 Destination Labware", + location="B2", + ) + + ############################# + # Pipette do work + pipette.consolidate( + [volume, volume], + [source_labware_C3["A3"], source_labware_C3["A4"]], + destination_labware_B2["A3"], + ) + + pipette.transfer(volume, source_labware_C3["A6"], destination_labware_B2["A6"]) + + pipette.distribute( + 5, + source_labware_C3["A7"], + [destination_labware_B2["A7"], destination_labware_B2["A8"]], + ) + + pipette.pick_up_tip() + pipette.touch_tip(source_labware_C3["B1"]) + pipette.drop_tip() + pipette.pick_up_tip() + pipette.home() + pipette.drop_tip() + + pipette.pick_up_tip() + well = source_labware_C3["D1"] + # directly from docs http://sandbox.docs.opentrons.com/edge/v2/new_protocol_api.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate + pipette.move_to(well.bottom(z=2)) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.prepare_to_aspirate() + pipette.move_to(well.bottom(z=2)) + pipette.aspirate(10, well.bottom(z=2)) + pipette.dispense(10) + pipette.drop_tip() diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_SINGLE_HappyPathSouthSide.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_SINGLE_HappyPathSouthSide.py new file mode 100644 index 00000000000..e050a0e4558 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_SINGLE_HappyPathSouthSide.py @@ -0,0 +1,143 @@ +from opentrons.protocol_api import SINGLE + +metadata = { + "protocolName": "96Channel SINGLE Happy Path A1 or A12", + "description": "Unsafe protocol ❗❗❗❗❗❗❗❗❗❗❗ will collide with tube.", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def run(protocol): + + trash = protocol.load_trash_bin("A3") # must load trash bin + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_1000ul", + label="Partial Tip Rack", + location="A2", + ) + + pipette = protocol.load_instrument(instrument_name="flex_96channel_1000") + + # Which nozzle to use on the pipette + # Think of the nozzles from above like you are looking down on a 96-well plate + # start="A1" Means the NW nozzle will pickup from the SE corner of the tip rack + # start="A12" Means the SW nozzle will pickup from the NE corner of the tip rack + # start="H1" Means the NE nozzle will pickup from the SW corner of the tip rack + # start="H12" Means the SE nozzle will pickup from the NW corner of the tip rack + pipette.configure_nozzle_layout( + style=SINGLE, + start="A1", # Which nozzle to start with + tip_racks=[partial_tip_rack], + ) + + source_labware_C1 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="C1 Source Labware", + location="C1", + ) + + destination_labware_C2 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="C2 Destination Labware", + location="C2", + ) + + volume = 10 # Default volume for actions that require it + + ############################# + # Pipette do work + pipette.consolidate( + [volume, volume], + [source_labware_C1["A3"], source_labware_C1["A4"]], + destination_labware_C2["A3"], + ) + + pipette.transfer(volume, source_labware_C1["A6"], destination_labware_C2["A6"]) + + pipette.distribute( + 5, + source_labware_C1["A7"], + [destination_labware_C2["A7"], destination_labware_C2["A8"]], + ) + + pipette.pick_up_tip() + pipette.touch_tip(source_labware_C1["B1"]) + pipette.drop_tip() + pipette.pick_up_tip() + pipette.home() + pipette.drop_tip() + + pipette.pick_up_tip() + well = source_labware_C1["D1"] + # directly from docs http://sandbox.docs.opentrons.com/edge/v2/new_protocol_api.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate + pipette.move_to(well.bottom(z=2)) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.prepare_to_aspirate() + pipette.move_to(well.bottom(z=2)) + pipette.aspirate(10, well.bottom(z=2)) + pipette.dispense(10) + pipette.drop_tip() + + ############################# + # Change the pipette configuration + + pipette.configure_nozzle_layout( + style=SINGLE, + start="A12", # Which nozzle to start with + tip_racks=[partial_tip_rack], + ) + + source_labware_B2 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="B2 Source Labware", + location="B2", + ) + + destination_labware_C3 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="C3 Destination Labware", + location="C3", + ) + + ############################# + # Pipette do work + pipette.consolidate( + [volume, volume], + [source_labware_B2["A3"], source_labware_B2["A4"]], + destination_labware_C3["A3"], + ) + + pipette.transfer(volume, source_labware_B2["A6"], destination_labware_C3["A6"]) + + pipette.distribute( + 5, + source_labware_B2["A7"], + [destination_labware_C3["A7"], destination_labware_C3["A8"]], + ) + + pipette.pick_up_tip() + pipette.touch_tip(source_labware_B2["B1"]) + pipette.drop_tip() + pipette.pick_up_tip() + pipette.home() + pipette.drop_tip() + + pipette.pick_up_tip() + well = source_labware_B2["D1"] + # directly from docs http://sandbox.docs.opentrons.com/edge/v2/new_protocol_api.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate + pipette.move_to(well.bottom(z=2)) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.prepare_to_aspirate() + pipette.move_to(well.bottom(z=2)) + pipette.aspirate(10, well.bottom(z=2)) + pipette.dispense(10) + pipette.drop_tip() diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_SINGLE_TubeRackCollision.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_SINGLE_TubeRackCollision.py new file mode 100644 index 00000000000..b499a0ef16d --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_None_SINGLE_TubeRackCollision.py @@ -0,0 +1,68 @@ +from opentrons.protocol_api import SINGLE + +metadata = { + "protocolName": "96Channel SINGLE Pickup tuberack collision", + "description": "Unsafe protocol ❗❗❗❗❗❗❗❗❗❗❗ will collide with tube.", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def comment_tip_rack_status(ctx, tip_rack): + """ + Print out the tip status for each row in a tip rack. + Each row (A-H) will print the well statuses for columns 1-12 in a single comment, + with a '🟢' for present tips and a '❌' for missing tips. + """ + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + range_1_to_12 = range(1, 13) + + ctx.comment(f"Tip rack in {tip_rack.parent}") + + for row in range_A_to_H: + status_line = f"{row}: " + for col in range_1_to_12: + well = f"{row}{col}" + has_tip = tip_rack.wells_by_name()[well].has_tip + status_emoji = "🟢" if has_tip else "❌" + status_line += f"{well} {status_emoji} " + + # Print the full status line for the row + ctx.comment(status_line) + + +def run(protocol): + + trash = protocol.load_trash_bin("A3") # must load trash bin + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_50ul", + label="Partial Tip Rack", + location="B2", + ) + + # tubes in column 1 and 2 are taller than the tubes in 3 and 4 + tube_rack = protocol.load_labware( + load_name="opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + label="Tube Rack", + location="D2", + ) + + pipette = protocol.load_instrument(instrument_name="flex_96channel_1000") + + pipette.configure_nozzle_layout( + style=SINGLE, + start="H12", + tip_racks=[partial_tip_rack], + ) + + pipette.pick_up_tip() + comment_tip_rack_status(protocol, partial_tip_rack) + # tubes in column 1 and 2 are taller than the tubes in 3 and 4 + # I would expect that with 50ul tips + # the pipette would collide with the tubes in column 1 and 2 + pipette.aspirate(30, tube_rack["B3"].bottom()) + pipette.drop_tip() diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_Reservoir.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_Reservoir.py new file mode 100644 index 00000000000..399c0008d10 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_96_Reservoir.py @@ -0,0 +1,91 @@ +from opentrons.protocol_api import SINGLE + +metadata = { + "protocolName": "96Channel Partial Tip to single well labware", + "description": "How does the 96 channel pipette behave with a partial tip rack and a single well labware?", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def comment_tip_rack_status(ctx, tip_rack): + """ + Print out the tip status for each row in a tip rack. + Each row (A-H) will print the well statuses for columns 1-12 in a single comment, + with a '🟢' for present tips and a '❌' for missing tips. + """ + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + range_1_to_12 = range(1, 13) + + ctx.comment(f"Tip rack in {tip_rack.parent}") + + for row in range_A_to_H: + status_line = f"{row}: " + for col in range_1_to_12: + well = f"{row}{col}" + has_tip = tip_rack.wells_by_name()[well].has_tip + status_emoji = "🟢" if has_tip else "❌" + status_line += f"{well} {status_emoji} " + + # Print the full status line for the row + ctx.comment(status_line) + + +def aspirate_to_reservoir_test(ctx, labware): + location = labware.wells()[0] + ctx.comment(f"Aspirating from {labware.parent} {location}") + ctx.aspirate(20, location) + ctx.pause("Where did I aspirate from?") + + +def run(protocol): + + trash = protocol.load_trash_bin("A3") # must load trash bin + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_flex_96_tiprack_200ul", + label="Partial Tip Rack", + location="B2", + ) + + # agilent_1_reservoir_290ml + agilent_290 = protocol.load_labware( + load_name="agilent_1_reservoir_290ml", + label="Agilent 290mL", + location="D1", + ) + + # axygen_1_reservoir_90ml + axygen_90 = protocol.load_labware( + load_name="axygen_1_reservoir_90ml", + label="Axygen 90mL", + location="D2", + ) + + # nest_1_reservoir_195ml + nest_195 = protocol.load_labware( + load_name="nest_1_reservoir_195ml", + label="Nest 195mL", + location="D3", + ) + + pipette = protocol.load_instrument(instrument_name="flex_96channel_1000") + + pipette.configure_nozzle_layout( + style=SINGLE, + start="H1", + tip_racks=[partial_tip_rack], + ) + + pipette.pick_up_tip() + comment_tip_rack_status(protocol, partial_tip_rack) + aspirate_to_reservoir_test(protocol, agilent_290) + + pipette.configure_nozzle_layout( + style=COLUMN, + start="A1", + tip_racks=[partial_tip_rack], + ) diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_P50_LPD.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_P50_LPD.py new file mode 100644 index 00000000000..c2038880a03 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_P50_LPD.py @@ -0,0 +1,122 @@ +import random +from typing import Set +from opentrons import protocol_api + +# metadata +metadata = { + "protocolName": "Wet test for LPD", + "author": "Josh McVey", + "description": "http://sandbox.docs.opentrons.com/edge/v2/pipettes/loading.html#liquid-presence-detection", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def load_liquid_in_all_wells(labware, liquid) -> None: + for well in labware.wells(): + well.load_liquid(liquid=liquid, volume=well.max_volume) + + +def run(protocol: protocol_api.ProtocolContext): + + # modules/fixtures + trashbin = protocol.load_trash_bin(location="A3") + + # labware + tiprack1 = protocol.load_labware("opentrons_flex_96_tiprack_1000ul", "A2") + tiprack2 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "B2") + sample_plate = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "C3") + reservoir = protocol.load_labware("nest_1_reservoir_290ml", "D3") + wet_sample = protocol.load_labware("nest_12_reservoir_15ml", "B3") + + # liquids + waterButMoreBlue = protocol.define_liquid( + name="H20", + description="Test this wet!!!", + display_color="#0077b6", + ) + + load_liquid_in_all_wells(wet_sample, waterButMoreBlue) + + # instruments + p50 = protocol.load_instrument("flex_1channel_50", mount="right", tip_racks=[tiprack2], liquid_presence_detection=True) + volume = 50 + + pipette = p50 + + # Wet A1 to sample plate A1 + # should be successful + well = "A1" + pipette.pick_up_tip() + pipette.aspirate(volume, wet_sample.well(well)) + pipette.dispense(volume, sample_plate.well(well)) + pipette.drop_tip() + + # reuse a tip with liquid_presence_detection=True + # we do NOT get an error if we reuse a tip + # but it is not recommended + well = "A2" + pipette.pick_up_tip() + pipette.aspirate(volume, wet_sample.well(well)) + pipette.dispense(volume, sample_plate.well(well)) + + well = "A3" + pipette.aspirate(volume, wet_sample.well(well)) + pipette.dispense(volume, sample_plate.well(well)) + pipette.drop_tip() + + # disable liquid presence detection on the pipette + pipette.liquid_presence_detection = False + # dry aspirate to prove it is off + protocol.comment(f"Reservoir in {reservoir.parent} is to have NO liquid") + pipette.pick_up_tip() + # dry aspirate to prove it is off + pipette.aspirate(volume, reservoir["A1"]) + pipette.blow_out(trashbin) # make sure tip is empty then use for next step + protocol.comment(f"Current volume in pipette: {pipette.current_volume}") # prints 0 + # detect liquid presence + # we expect this to move the pipette to well A1 of the reservoir + # and then return False during the run + # but will always return true in the simulation (analysis) + # ❗❗❗❗❗❗ + # currently the next line is throwing an error: + # Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): TipNotEmptyError: + # This operation requires a tip with no liquid in it. + # https://opentrons.atlassian.net/browse/RQA-3171 + pipette.prepare_to_aspirate() # This removes the error + is_liquid_in_reservoir = pipette.detect_liquid_presence(reservoir["A1"]) + protocol.comment(f"Is there liquid in the reservoir? {is_liquid_in_reservoir}") + if not protocol.is_simulating(): + if is_liquid_in_reservoir: + protocol.comment("🐛🐛🐛🐛🐛 False + for liquid") + pipette.drop_tip() + # ❗❗❗❗❗❗ + + # now we turn back on liquid presence detection + # with the property + pipette.liquid_presence_detection = True + pipette.pick_up_tip() + # the next line should throw an error and pause the protocol + # resolve and continue + protocol.comment(f"We expect an error on the next line") + pipette.aspirate(volume, reservoir["A1"]) + + if pipette.has_tip: + pipette.drop_tip() + + # pipette.liquid_presence_detection = True + # and we try to use + # pipette.detect_liquid_presence + # no error is thrown regardless of the presence of liquid + + pipette.pick_up_tip() + protocol.comment(f"Reservoir in {reservoir.parent} is to have NO liquid") + pipette.detect_liquid_presence(reservoir["A1"]) + is_liquid_in_reservoir = pipette.detect_liquid_presence(reservoir["A1"]) + protocol.comment(f"Is there liquid in the reservoir? {is_liquid_in_reservoir}") + if not protocol.is_simulating(): + if is_liquid_in_reservoir: + protocol.comment("🐛🐛🐛🐛🐛 False + for liquid") diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_P50_LPD_wet_tip_scenarios.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_P50_LPD_wet_tip_scenarios.py new file mode 100644 index 00000000000..059d4278001 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_P50_LPD_wet_tip_scenarios.py @@ -0,0 +1,169 @@ +import random +from typing import Set +from opentrons import protocol_api + +# metadata +metadata = { + "protocolName": "LPD with wet tip scenarios", + "author": "Josh McVey", + "description": "http://sandbox.docs.opentrons.com/edge/v2/pipettes/loading.html#liquid-presence-detection", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def load_liquid_in_all_wells(labware, liquid) -> None: + for well in labware.wells(): + well.load_liquid(liquid=liquid, volume=well.max_volume) + + +def run(protocol: protocol_api.ProtocolContext): + + # modules/fixtures + trashbin = protocol.load_trash_bin(location="A3") + + # labware + tiprack1 = protocol.load_labware("opentrons_flex_96_tiprack_1000ul", "A2") + tiprack2 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "B2") + sample_plate = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "C3") + reservoir = protocol.load_labware("nest_1_reservoir_290ml", "D3") + wet_sample = protocol.load_labware("nest_12_reservoir_15ml", "B3") + + # liquids + waterButMoreBlue = protocol.define_liquid( + name="H20", + description="Test this wet!!!", + display_color="#0077b6", + ) + + wet_sample.well("A1").load_liquid(liquid=waterButMoreBlue, volume=200) + wet_sample.well("A2").load_liquid(liquid=waterButMoreBlue, volume=200) + wet_sample.well("A3").load_liquid(liquid=waterButMoreBlue, volume=200) + + # instruments + p50 = protocol.load_instrument("flex_1channel_50", mount="right", tip_racks=[tiprack2], liquid_presence_detection=True) + volume = 50 + + pipette = p50 + total_volume = 30 + + pipette.pick_up_tip() + protocol.comment("touch_tip") + pipette.touch_tip(wet_sample.well("A1")) + protocol.comment("air_gap") + pipette.air_gap(volume=20) + protocol.comment("blow_out with no arguments") + pipette.blow_out() + pipette.drop_tip() + + protocol.comment(f"reuse=True") + pipette.transfer( + volume=10, + source=[wet_sample.well("A1"), wet_sample.well("A2"), wet_sample.well("A3")], + dest=sample_plate.well("A2"), + reuse=True, + ) + + # example_1 = "During mixing, all aspirates on repetitions > 1 LPD with wet tip" + # protocol.comment(f"{example_1} 🔽🔽🔽🔽🔽🔽🔽") + # pipette.pick_up_tip() + # pipette.mix(repetitions=3, volume=20, location=wet_sample.well("A1")) + # pipette.drop_tip() + # protocol.comment(f"{example_1} 🔼🔼🔼🔼🔼🔼🔼") + + # example_2 = "Consolidate default, reuse=true, new_tip once or never;second and subsequent aspirates LPD with wet tip" + # protocol.comment(f"{example_2} 🔽🔽🔽🔽🔽🔽🔽") + # protocol.comment(f"default tip use") + # pipette.consolidate( + # volume=total_volume, + # source=[wet_sample.well("A1"), wet_sample.well("A2"), wet_sample.well("A3")], + # dest=sample_plate.well("A1"), + # ) + # protocol.comment(f"reuse=True") + # pipette.consolidate( + # volume=total_volume, + # source=[wet_sample.well("A1"), wet_sample.well("A2"), wet_sample.well("A3")], + # dest=sample_plate.well("A2"), + # reuse=True, + # ) + # protocol.comment(f"new_tip='once'") + # pipette.consolidate( + # volume=total_volume, + # source=[wet_sample.well("A1"), wet_sample.well("A2"), wet_sample.well("A3")], + # dest=sample_plate.well("A3"), + # new_tip="once", + # ) + # protocol.comment(f"new_tip='never'") + # pipette.pick_up_tip() + # pipette.consolidate( + # volume=total_volume, + # source=[wet_sample.well("A1"), wet_sample.well("A2"), wet_sample.well("A3")], + # dest=sample_plate.well("A4"), + # new_tip="never", + # ) + # pipette.drop_tip() + # protocol.comment(f"{example_2} 🔼🔼🔼🔼🔼🔼🔼") + + # example_3 = "Consolidate mix_before or mix_after, during mixing, all aspirates on repetitions > 1 LPD with wet tip" + # protocol.comment(f"{example_3} 🔽🔽🔽🔽🔽🔽🔽") + # pipette.consolidate( + # volume=total_volume, + # source=[wet_sample.well("A1"), wet_sample.well("A2"), wet_sample.well("A3")], + # dest=sample_plate.well("A5"), + # mix_before=(3, 15), + # mix_after=(3, 20), + # ) + # protocol.comment(f"{example_3} 🔼🔼🔼🔼🔼🔼🔼") + + # example_4 = "Distribute default and reuse=true;second and subsequent aspirates LPD with wet tip" + # protocol.comment(f"{example_4} 🔽🔽🔽🔽🔽🔽🔽") + # pipette.distribute( + # volume=total_volume, + # source=sample_plate.well("A2"), + # dest=[sample_plate.well("A1"), sample_plate.well("A3")], + # reuse=True, + # ) + # protocol.comment(f"{example_4} 🔼🔼🔼🔼🔼🔼🔼") + + # example_5 = "Distribute mix=true, During mixing, all aspirates on repetitions > 1 LPD with wet tip" + # protocol.comment(f"{example_5} 🔽🔽🔽🔽🔽🔽🔽") + # pipette.distribute( + # volume=total_volume, + # source=sample_plate.well("A2"), + # dest=[sample_plate.well("A1"), sample_plate.well("A3")], + # mix=True, + + # # reuse the tip for a second transfer + # # no error + # # we must say in docs do not do this + # protocol.comment("LPD no error thrown on simple command tip reuse 🔽🔽🔽🔽🔽🔽🔽") + # well = "A2" + # pipette.pick_up_tip() + # pipette.aspirate(volume, wet_sample.well(well)) + # pipette.dispense(volume, sample_plate.well(well)) + + # well = "A3" + # pipette.aspirate(volume, wet_sample.well(well)) + # pipette.dispense(volume, sample_plate.well(well)) + # pipette.drop_tip() + # protocol.comment("LPD no error thrown on simple command tip reuse 🔼🔼🔼🔼🔼🔼🔼") + + # # Again no error is thrown + # # but we must say in docs do not do this + # protocol.comment("LPD no error thrown on blowout + prepare_to_aspirate 🔽🔽🔽🔽🔽🔽🔽") + # pipette.pick_up_tip() + # pipette.aspirate(volume=20, location=reservoir["A1"]) + # pipette.blow_out(trashbin) # make sure tip is empty then use for next step + # protocol.comment(f"Current volume in pipette: {pipette.current_volume}") # prints 0 + # # pipette.prepare_to_aspirate() removes the error from line 93 + # # but this should NOT be done!!! + # # Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): TipNotEmptyError: + # # This operation requires a tip with no liquid in it. + # pipette.prepare_to_aspirate() + # is_liquid_in_reservoir = pipette.detect_liquid_presence(reservoir["A1"]) + # protocol.comment(f"Is there liquid in the reservoir? {is_liquid_in_reservoir}") + # pipette.drop_tip() + # protocol.comment("LPD no error thrown on blowout + prepare_to_aspirate 🔼🔼🔼🔼🔼🔼🔼") diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_P50_touch_tip_directly.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_P50_touch_tip_directly.py new file mode 100644 index 00000000000..ac8a4420650 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_P50_touch_tip_directly.py @@ -0,0 +1,66 @@ +import random +from typing import Set +from opentrons import protocol_api + +# metadata +metadata = { + "protocolName": "Flex touch tip first", + "author": "Josh McVey", + "description": "touch tip first", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +def load_liquid_in_all_wells(labware, liquid) -> None: + for well in labware.wells(): + well.load_liquid(liquid=liquid, volume=well.max_volume) + + +def run(protocol: protocol_api.ProtocolContext): + + # modules/fixtures + trashbin = protocol.load_trash_bin(location="A3") + + # labware + tiprack1 = protocol.load_labware("opentrons_flex_96_tiprack_1000ul", "A2") + tiprack2 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "B2") + sample_plate = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "C3") + reservoir = protocol.load_labware("nest_1_reservoir_290ml", "D3") + wet_sample = protocol.load_labware("nest_12_reservoir_15ml", "B3") + + # liquids + waterButMoreBlue = protocol.define_liquid( + name="H20", + description="Test this wet!!!", + display_color="#0077b6", + ) + + wet_sample.well("A1").load_liquid(liquid=waterButMoreBlue, volume=200) + wet_sample.well("A2").load_liquid(liquid=waterButMoreBlue, volume=200) + wet_sample.well("A3").load_liquid(liquid=waterButMoreBlue, volume=200) + + # instruments + p50 = protocol.load_instrument("flex_1channel_50", mount="right", tip_racks=[tiprack2], liquid_presence_detection=True) + volume = 50 + + pipette = p50 + total_volume = 30 + + pipette.pick_up_tip() + # don't do an aspirate before the touch_tip + # pipette.aspirate(volume=total_volume, location=wet_sample.well("A1")) + protocol.comment("touch_tip") + # no matter if you aspirate before or not, + # the touch_tip is not shown in the app preview run + pipette.touch_tip(location=wet_sample.well("A1")) + protocol.comment("air_gap") + # if you uncomment the air_gap an error is thrown + # I should be at the wet_sample.well("A1") but it says I am at the tiprack + # pipette.air_gap(volume=20) + protocol.comment("blow_out with no arguments") + pipette.blow_out() + pipette.drop_tip() diff --git a/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_P8X1000_P50_LLD.py b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_P8X1000_P50_LLD.py new file mode 100644 index 00000000000..1550f3313d5 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_S_v2_20_P8X1000_P50_LLD.py @@ -0,0 +1,106 @@ +from opentrons import protocol_api + +# metadata +metadata = { + "protocolName": "2.20 Error Recovery Testing Protocol - Modified", + "author": "Sara Kowalski", + "description": "Simple Protocol that user can use to Phase 1 Error Recovery options for single and multi channel pipettes. NOTE: YOU WILL NEED A MODIFIED PIPETTE (NO SHROUD), and changed order of operations to reduce impact of dispense ER failure", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + +DRYRUN = "NO" + + +def run(protocol: protocol_api.ProtocolContext): + + # modules/fixtures + trashbin = protocol.load_trash_bin(location="A3") + + # labware + tiprack1 = protocol.load_labware("opentrons_flex_96_tiprack_1000ul", "A2") + tiprack2 = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "B2") + sample_plate = protocol.load_labware("armadillo_96_wellplate_200ul_pcr_full_skirt", "C3") + reservoir = protocol.load_labware("nest_1_reservoir_290ml", "D3") + wet_sample = protocol.load_labware("nest_12_reservoir_15ml", "B3") + + # liquids + water = protocol.define_liquid( + name="Water", + description="water for ER testing", + display_color="#90e0ef", + ) + waterButMoreBlue = protocol.define_liquid( + name="Water but more blue", + description="Water for ER testing", + display_color="#0077b6", + ) + + wet_sample["A1"].load_liquid(liquid=water, volume=800) + wet_sample["A2"].load_liquid(liquid=waterButMoreBlue, volume=800) + + # instruments + p1000 = protocol.load_instrument( + "flex_8channel_1000", mount="left", tip_racks=[tiprack1] + ) ## will need a modified pipette that doesn't have the ejector(?) + p50 = protocol.load_instrument( + "flex_1channel_50", mount="right", tip_racks=[tiprack2] + ) ## will need a modified pipette that doesn't have the ejector(?) + + pipette_list = [p1000, p50] + volume = 900 + + for pipette in pipette_list: + if pipette == p50: + volume = 50 + + ############################## + #####Tip Pick Up Failure###### + ############################## + + protocol.pause("This tests Tip Pick Up Failure (General Error for now)") + protocol.pause("Please remove tip rack from deck. When you're testing recovery, add tiprack back as necessary.") + pipette.pick_up_tip() + + ########################################## + #####Overpressure - While Aspirating###### + ########################################## + + protocol.pause("Overpressure - While Aspirating") + pipette.home() + protocol.pause( + "Use p50UL tips - Swap with tip(s) that are hot glued shut (you will need to swap the tips for retry recovery options)" + ) + + for i in range(10): + pipette.aspirate(volume, reservoir["A1"].top()) + pipette.dispense(volume, reservoir["A1"].top()) + + #################################### + #####Liquid Presence Detection###### + #################################### + protocol.pause("This tests Liquid Presence Detection - PLEASE MAKE SURE YOU'RE USING 1000UL TIPS OR YOU WILL BREAK THE PIPETTE") + protocol.pause("Make sure reservoir is empty, and have full reservoir on standby to switch out with for recovery") + + pipette.require_liquid_presence(wet_sample["A1"]) + + pipette.aspirate(volume, wet_sample["A1"]) + pipette.dispense(volume, wet_sample["A1"]) + pipette.home() + + ##################################################### + #####Overpressure - While Dispensing###### + ##################################################### + + protocol.pause("Overpressure - While Dispensing") + + pipette.aspirate(volume, reservoir["A1"].top(20)) + protocol.pause("Swap with tip(s) that are hot glued shut (you will need to cut the tips for retry recovery options)") + pipette.dispense(volume, reservoir["A1"].top(20)) + + pipette.move_to(trashbin) + protocol.pause("You will have to manually remove tips if you're using a modified pipette") + pipette.drop_tip(trashbin) diff --git a/analyses-snapshot-testing/files/protocols/Flex_X_v2_20_96_None_Overrides_TooTallLabware.py b/analyses-snapshot-testing/files/protocols/Flex_X_v2_20_96_None_Overrides_TooTallLabware.py new file mode 100644 index 00000000000..d717c497655 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_X_v2_20_96_None_Overrides_TooTallLabware.py @@ -0,0 +1,504 @@ +from dataclasses import dataclass +from typing import Optional +from opentrons.protocol_api import SINGLE, COLUMN, PARTIAL_COLUMN, ROW + +metadata = { + "protocolName": "Too tall labware on pickup tip", + "description": "oooo", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +@dataclass +class PartialTipConfig: + key: str + description: str + startingTip: str + startingNozzle: str + apiTipConfig: str + apiStart: str + apiEnd: Optional[str] + + +# flex_96channel_1000 SINGLE + +ninety_six_single_top_left = PartialTipConfig( + key="ninety_six_single_top_left", + description="96 single picking up top left of tiprack", + startingTip="A1", + startingNozzle="H12", + apiTipConfig=SINGLE, + apiStart="H12", + apiEnd=None, +) + +ninety_six_single_top_right = PartialTipConfig( + key="ninety_six_single_top_right", + description="96 single picking up top right of tiprack", + startingTip="A12", + startingNozzle="H1", + apiTipConfig=SINGLE, + apiStart="H1", + apiEnd=None, +) + +ninety_six_single_bottom_left = PartialTipConfig( + key="ninety_six_single_bottom_left", + description="96 single picking up bottom left of tiprack", + startingTip="H1", + startingNozzle="A12", + apiTipConfig=SINGLE, + apiStart="A12", + apiEnd=None, +) + +ninety_six_single_bottom_right = PartialTipConfig( + key="ninety_six_single_bottom_right", + description="96 single picking up bottom right of tiprack", + startingTip="H12", + startingNozzle="A1", + apiTipConfig=SINGLE, + apiStart="A1", + apiEnd=None, +) + +# flex_96channel_1000 COLUMN + +ninety_six_column_left = PartialTipConfig( + key="ninety_six_column_left", + description="96 column picking up left column of tiprack", + startingTip="Column 1", + startingNozzle="Column 12", + apiTipConfig=COLUMN, + apiStart="A12", + apiEnd=None, +) + + +ninety_six_column_right = PartialTipConfig( + key="ninety_six_column_right", + description="96 column picking up right column of tiprack", + startingTip="Row 12", + startingNozzle="Row 1", + apiTipConfig=COLUMN, + apiStart="A1", + apiEnd=None, +) + +# flex_96channel_1000 ROW + +ninety_six_row_top = PartialTipConfig( + key="ninety_six_row_top", + description="96 row picking up top row of tiprack", + startingTip="Row A", + startingNozzle="Row H", + apiTipConfig=ROW, + apiStart="H1", + apiEnd=None, +) + +ninety_six_row_bottom = PartialTipConfig( + key="ninety_six_row_bottom", + description="96 row picking up bottom row of tiprack", + startingTip="Row H", + startingNozzle="Row A", + apiTipConfig=ROW, + apiStart="A1", + apiEnd=None, +) + +# pipette = protocol.load_instrument(instrument_name="flex_8channel_50", mount="right") +# works for all 8 channel pipettes +eight_single_top = PartialTipConfig( + key="eight_single_top", + description="8 channel single picking up from the top of the tiprack", + startingTip="A1", + startingNozzle="H1", + apiTipConfig=SINGLE, + apiStart="H1", + apiEnd=None, +) + +eight_single_bottom = PartialTipConfig( + key="eight_single_bottom", + description="8 channel single picking up from the bottom of the tiprack", + startingTip="H1", + startingNozzle="A1", + apiTipConfig=SINGLE, + apiStart="A1", + apiEnd=None, +) + + +eight_partial_top_2_tips = PartialTipConfig( + key="eight_partial_top_2_tips", + description="8 partial bottom 2 tips", + startingTip="H1", + startingNozzle="B1", + apiTipConfig=PARTIAL_COLUMN, + apiStart="H1", + apiEnd="B1", +) + +eight_partial_top_3_tips = PartialTipConfig( + key="eight_partial_top_3_tips", + description="8 partial bottom 3 tips", + startingTip="H1", + startingNozzle="C1", + apiTipConfig=PARTIAL_COLUMN, + apiStart="H1", + apiEnd="C1", +) +eight_partial_top_4_tips = PartialTipConfig( + key="eight_partial_top_4", + description="8 partial bottom 4 tips", + startingTip="H1", + startingNozzle="D1", + apiTipConfig=PARTIAL_COLUMN, + apiStart="H1", + apiEnd="D1", +) +eight_partial_top_5_tips = PartialTipConfig( + key="eight_partial_top_5", + description="8 partial bottom 5 tips", + startingTip="H1", + startingNozzle="E1", + apiTipConfig=PARTIAL_COLUMN, + apiStart="H1", + apiEnd="E1", +) +eight_partial_top_6_tips = PartialTipConfig( + key="eight_partial_top_6", + description="8 partial bottom 6 tips", + startingTip="H1", + startingNozzle="F1", + apiTipConfig=PARTIAL_COLUMN, + apiStart="H1", + apiEnd="F1", +) + +eight_partial_top_7_tips = PartialTipConfig( + key="eight_partial_top_7", + description="8 partial bottom 7 tips", + startingTip="H1", + startingNozzle="G1", + apiTipConfig=PARTIAL_COLUMN, + apiStart="H1", + apiEnd="G1", +) + +all_partial_configs = [ + ninety_six_single_top_left, + ninety_six_single_top_right, + ninety_six_single_bottom_left, + ninety_six_single_bottom_right, + ninety_six_column_left, + ninety_six_column_right, + ninety_six_row_top, + ninety_six_row_bottom, + eight_single_top, + eight_single_bottom, + eight_partial_top_2_tips, + eight_partial_top_3_tips, + eight_partial_top_4_tips, + eight_partial_top_5_tips, + eight_partial_top_6_tips, + eight_partial_top_7_tips, +] + + +def find_partial_tip_config(key: str) -> Optional[PartialTipConfig]: + for config in all_partial_configs: + if config.key == key: + return config + raise ValueError(f"Could not find partial tip config with key {key}") + + +@dataclass +class TestCase: + key: str + description: str + pipette_config_key: str + collision_slot: Optional[str] = None + tip_rack_slot: Optional[str] = None + movement: Optional[str] = None + source_slot: Optional[str] = None + source_well: Optional[str] = None + destination_slot: Optional[str] = None + destination_well: Optional[str] = None + + +north = TestCase( + key="north", + description="North too tall labware on pickup tip", + pipette_config_key="ninety_six_single_top_left", + collision_slot="B2", +) + +north_west = TestCase( + key="north_west", + description="NW too tall labware on pickup tip", + pipette_config_key="ninety_six_single_top_left", + collision_slot="B1", +) + +west = TestCase( + key="west", + description="west too tall labware on pickup tip", + pipette_config_key="ninety_six_single_top_left", + collision_slot="C1", +) + +south_west = TestCase( + key="south_west", + description="west too tall labware on pickup tip", + pipette_config_key="ninety_six_single_bottom_left", + collision_slot="D1", +) + +south = TestCase( + key="south", + description="west too tall labware on pickup tip", + pipette_config_key="ninety_six_single_bottom_left", + collision_slot="D2", +) + +south_east = TestCase( + key="south_east", + description="west too tall labware on pickup tip", + pipette_config_key="ninety_six_single_bottom_right", + collision_slot="D3", +) +east = TestCase( + key="east", + description="west too tall labware on pickup tip", + pipette_config_key="ninety_six_single_bottom_right", + collision_slot="C3", +) + +east_column = TestCase( + key="east_column", + description="west too tall labware on pickup tip", + pipette_config_key="ninety_six_column_left", + collision_slot="C1", +) +west_column = TestCase( + key="west_column", + description="west too tall labware on pickup tip", + pipette_config_key="ninety_six_column_right", + collision_slot="C3", +) + +north_row = TestCase( + key="north_row", + description="north row too tall labware on pickup tip", + pipette_config_key="ninety_six_row_top", + collision_slot="B2", +) + +south_row = TestCase( + key="south_row", + description="south row too tall labware on pickup tip", + pipette_config_key="ninety_six_row_bottom", + collision_slot="D2", +) + +top_edge = TestCase(key="top_edge", description="top edge of robot", pipette_config_key="ninety_six_single_top_left", tip_rack_slot="A1") + +bottom_left_edge = TestCase( + key="bottom_left_edge", description="bottom left edge of robot", pipette_config_key="ninety_six_single_bottom_left", tip_rack_slot="D1" +) + +bottom_right_edge = TestCase( + key="bottom_right_edge", + description="bottom right edge of robot", + pipette_config_key="ninety_six_single_bottom_right", + tip_rack_slot="D3", +) + +c3_right_edge = TestCase( + key="c3_right_edge", description="right edge of c2", pipette_config_key="ninety_six_single_bottom_right", tip_rack_slot="C3" +) + +transfer_destination_collision = TestCase( + key="transfer_destination_collision", + description="transfer north", + pipette_config_key="ninety_six_single_top_left", + tip_rack_slot="B2", + source_slot="D2", + source_well="A1", + destination_slot="D3", + destination_well="A1", + movement="transfer", + collision_slot="C3", +) + +transfer_source_collision = TestCase( + key="transfer_source_collision", + description="transfer north", + pipette_config_key="ninety_six_single_top_left", + tip_rack_slot="B2", + source_slot="D2", + source_well="A1", + destination_slot="D3", + destination_well="A1", + movement="transfer", + collision_slot="C1", +) + +mix_collision = TestCase( + key="mix_collision", + description="mix north", + pipette_config_key="ninety_six_single_top_left", + tip_rack_slot="B2", + source_slot="D2", + source_well="A1", + movement="mix", + collision_slot="C1", +) + +consolidate_source_collision = TestCase( + key="consolidate_source_collision", + description="consolidate north", + pipette_config_key="ninety_six_single_top_left", + tip_rack_slot="B2", + source_slot="D2", + source_well="A1", + destination_slot="D3", + destination_well="A1", + movement="consolidate", + collision_slot="C1", +) + +consolidate_destination_collision = TestCase( + key="consolidate_destination_collision", + description="consolidate north", + pipette_config_key="ninety_six_single_top_left", + tip_rack_slot="B2", + source_slot="D2", + source_well="A1", + destination_slot="D3", + destination_well="A1", + movement="consolidate", + collision_slot="C3", +) + +distribute_source_collision = TestCase( + key="distribute_source_collision", + description="distribute north", + pipette_config_key="ninety_six_single_top_left", + tip_rack_slot="B2", + source_slot="D2", + source_well="A1", + destination_slot="D3", + destination_well="A1", + movement="distribute", + collision_slot="C1", +) + +distribute_destination_collision = TestCase( + key="distribute_destination_collision", + description="distribute north", + pipette_config_key="ninety_six_single_top_left", + tip_rack_slot="B2", + source_slot="D2", + source_well="A1", + destination_slot="D3", + destination_well="A1", + movement="distribute", + collision_slot="C3", +) + +# all have been tested manually and throw an error as expected JTM 20240814 +test_cases = [ + transfer_source_collision, + transfer_destination_collision, + c3_right_edge, + north, + north_west, + west, + south_west, + south, + south_east, + east, + east_column, + west_column, + north_row, + south_row, + top_edge, + bottom_left_edge, + bottom_left_edge, + bottom_right_edge, + mix_collision, + consolidate_source_collision, + consolidate_destination_collision, + distribute_source_collision, + distribute_destination_collision, +] + + +def get_test_case(key: str) -> Optional[TestCase]: + for test_case in test_cases: + if test_case.key == key: + return test_case + raise ValueError(f"Could not find test case with key {key}") + + +def run(ctx): + tall_labware_loadname = "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical" + test_case = get_test_case(key) + + if test_case.tip_rack_slot and test_case.tip_rack_slot != "C2": + tip_rack = ctx.load_labware("opentrons_96_tiprack_1000ul", test_case.tip_rack_slot) + else: + tip_rack = ctx.load_labware("opentrons_96_tiprack_1000ul", "C2") + + pipette_config = find_partial_tip_config(test_case.pipette_config_key) + + pipette = ctx.load_instrument("flex_96channel_1000") + + pipette.configure_nozzle_layout(pipette_config.apiTipConfig, pipette_config.apiStart, tip_racks=[tip_rack]) + + if test_case.collision_slot: + ctx.load_labware(tall_labware_loadname, test_case.collision_slot) + + target_labware_loadname = "nest_96_wellplate_100ul_pcr_full_skirt" + if test_case.source_slot: + source = ctx.load_labware(target_labware_loadname, test_case.source_slot) + + if test_case.destination_slot: + destination = ctx.load_labware(target_labware_loadname, test_case.destination_slot) + + if not test_case.movement: + None # No movement simply pickup tip from the tip rack + pipette.pick_up_tip() + elif test_case.movement == "transfer": + trash = ctx.load_trash_bin("A3") + pipette.transfer(10, source[test_case.source_well], destination[test_case.destination_well]) + elif test_case.movement == "mix": + trash = ctx.load_trash_bin("A3") + well = source[test_case.source_well] + pipette.pick_up_tip() + pipette.move_to(well.bottom(z=2)) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.drop_tip() + elif test_case.movement == "consolidate": + trash = ctx.load_trash_bin("A3") + pipette.consolidate( + [10, 10], + [source[test_case.source_well], source[test_case.source_well]], + destination[test_case.destination_well], + ) + elif test_case.movement == "distribute": + trash = ctx.load_trash_bin("A3") + pipette.distribute( + 20, + source[test_case.source_well], + [destination[test_case.destination_well], destination[test_case.destination_well]], + ) diff --git a/analyses-snapshot-testing/files/protocols/Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs.py b/analyses-snapshot-testing/files/protocols/Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs.py new file mode 100644 index 00000000000..9d275c2db84 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs.py @@ -0,0 +1,185 @@ +from dataclasses import dataclass +from typing import Optional +from opentrons.protocol_api import PARTIAL_COLUMN + +# inspired by https://opentrons.atlassian.net/browse/PLAT-457 + +metadata = { + "protocolName": "Invalid tip configs that should error", + "description": "oooo", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + + +@dataclass +class PartialTipConfig: + key: str + pipette_load_name: str + description: str + starting_tip: str + starting_nozzle: str + api_tip_config: str + api_start: str + api_end: Optional[str] = None + + +# Want to see +"Partial column configuration is only supported on 8-Channel pipettes" +ninety_six_partial_column_1 = PartialTipConfig( + key="ninety_six_partial_column_1", + pipette_load_name="flex_96channel_1000", + description="96 2 tip pick up top left of tiprack", + starting_tip="A1", + starting_nozzle="H12", + api_tip_config=PARTIAL_COLUMN, + api_start="H12", + api_end="G12", +) + +# https://opentrons.atlassian.net/browse/PLAT-457 +ninety_six_partial_column_2 = PartialTipConfig( + key="ninety_six_partial_column_2", + pipette_load_name="flex_96channel_1000", + description="Full row", + starting_tip="A1", + starting_nozzle="H12", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", + api_end="A1", +) + +ninety_six_partial_column_3 = PartialTipConfig( + key="ninety_six_partial_column_3", + pipette_load_name="flex_96channel_1000", + description="Full Row", + starting_tip="A1", + starting_nozzle="H12", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", + api_end="H12", +) + +# We do not allow PARTIAL_COLUMN to start on the bottom of the tip rack +# Want to see +# "IncompatibleNozzleConfiguration: Attempted Nozzle Configuration does not match any approved map layout for the current pipette." +eight_partial_column_bottom_left = PartialTipConfig( + key="eight_partial_column_bottom_left", + pipette_load_name="flex_8channel_1000", + description="8 channel 2 tip pick up bottom left of tiprack", + starting_tip="H1", + starting_nozzle="A1", + api_tip_config=PARTIAL_COLUMN, + api_start="A1", + api_end="B1", +) + + +# Want to see +# Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): IncompatibleNozzleConfiguration: No entry for front right nozzle 'G12' in pipette +eight_partial_column_bottom_right = PartialTipConfig( + key="eight_partial_column_bottom_right", + pipette_load_name="flex_8channel_1000", + description="8 channel 2 tip pick up bottom left of tiprack", + starting_tip="H12", + starting_nozzle="A1", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", # for partial column only H1 + api_end="G12", # the author thinks this is to specify the ending tip and wants to start at bottom right for 2 tips +) + + +# Partial column configurations require the 'end' parameter. +eight_partial_column_no_end = PartialTipConfig( + key="eight_partial_column_no_end", + pipette_load_name="flex_8channel_1000", + description="8 channel PARTIAL_COLUMN with no end", + starting_tip="H1", + starting_nozzle="A1", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", + # api_end="B1", sets the end to None +) + +# If you call return_tip() while using partial tip pickup, the API will raise an error. +# Error 4000 GENERAL_ERROR (UnexpectedProtocolError): Cannot return tip to a tiprack while the pipette is configured for partial tip. +return_tip_error = PartialTipConfig( + key="return_tip_error", + pipette_load_name="flex_8channel_1000", + description="8 channel 2 tip pick up top left of tiprack", + starting_tip="A1", + starting_nozzle="H1", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", + api_end="G1", # valid 2 tip +) + + +# pipette.drop_tip(tiprack["B1"]) # drops tip in rack location A1 +drop_tip_with_location = PartialTipConfig( + key="drop_tip_with_location", + pipette_load_name="flex_8channel_1000", + description="8 channel 2 tip pick up top left of tiprack", + starting_tip="A1", + starting_nozzle="H1", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", + api_end="G1", # valid 2 tip +) + +all_partial_configs = [ + ninety_six_partial_column_1, + ninety_six_partial_column_2, + ninety_six_partial_column_3, + eight_partial_column_bottom_left, + eight_partial_column_bottom_right, + eight_partial_column_no_end, + return_tip_error, + drop_tip_with_location, +] + + +def find_partial_tip_config(key: str) -> Optional[PartialTipConfig]: + for config in all_partial_configs: + if config.key == key: + return config + raise ValueError(f"Could not find partial tip config with key {key}") + + +def comment_column_has_tip(ctx, tip_rack, column): + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + wells = [f"{row}{column}" for row in range_A_to_H] + for well in wells: + ctx.comment(f"Tip rack in {tip_rack.parent}, well {well} has tip: {tip_rack.wells_by_name()[well].has_tip}") + + +def run(ctx): + tip_rack = ctx.load_labware("opentrons_flex_96_tiprack_1000ul", "B2") + + pipette_config = find_partial_tip_config(key) + + pipette = ctx.load_instrument(pipette_config.pipette_load_name, "left") + + pipette.configure_nozzle_layout( + style=pipette_config.api_tip_config, start=pipette_config.api_start, end=pipette_config.api_end, tip_racks=[tip_rack] + ) + + target_labware_loadname = "nest_96_wellplate_100ul_pcr_full_skirt" + source = ctx.load_labware(target_labware_loadname, "D2") + destination = ctx.load_labware(target_labware_loadname, "D3") + + trash = ctx.load_trash_bin("A3") + if key == "return_tip_error": + pipette.pick_up_tip() + # this test picks up 2 tips + comment_column_has_tip(ctx, tip_rack, 1) + pipette.return_tip() # this should raise an error + elif key == "drop_tip_with_location": + pipette.pick_up_tip() + comment_column_has_tip(ctx, tip_rack, 1) + pipette.drop_tip(tip_rack["B1"]) # this should raise an error + else: + pipette.transfer(10, source["A1"], destination["A1"]) diff --git a/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_8_None_PARTIAL_COLUMN_HappyPathMixedTipRacks.py b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_8_None_PARTIAL_COLUMN_HappyPathMixedTipRacks.py new file mode 100644 index 00000000000..784667691d4 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_8_None_PARTIAL_COLUMN_HappyPathMixedTipRacks.py @@ -0,0 +1,111 @@ +from opentrons import protocol_api +from opentrons.protocol_api import PARTIAL_COLUMN + +metadata = { + "protocolName": "Partial Tip with Partial Column and Single Smoke", + "description": "OT-2 protocol with 1ch and 8ch pipette partial/single tip configurations. Mixing tipracks and using separate tipracks. ", +} + +requirements = { + "robotType": "OT-2", + "apiLevel": "2.20", +} + + +def run(protocol: protocol_api.ProtocolContext): + + # DECK SETUP AND LABWARE + partial_tiprack_1 = protocol.load_labware("opentrons_96_tiprack_300ul", "7") + partial_tiprack_2 = protocol.load_labware("opentrons_96_tiprack_300ul", "8") + sample_plate = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "2") + reservoir_12 = protocol.load_labware("nest_12_reservoir_15ml", "1") + reservoir_1 = protocol.load_labware("nest_1_reservoir_290ml", "3") + + p300_multi = protocol.load_instrument("p300_multi_gen2", mount="left", tip_racks=[partial_tiprack_1]) + p300_single = protocol.load_instrument("p300_single_gen2", mount="right", tip_racks=[partial_tiprack_2]) + + p300_multi.configure_nozzle_layout(style=PARTIAL_COLUMN, start="H1", end="D1", tip_racks=[partial_tiprack_1]) + p300_multi.pick_up_tip() + p300_multi.mix(3, 75, sample_plate["E1"]) + p300_multi.mix(3, 200, reservoir_12["A1"]) + p300_multi.mix(3, 200, reservoir_1["A1"]) + p300_multi.drop_tip() + + p300_multi.transfer( + volume=150, + source=sample_plate["E1"], ##NOTE TO SELF THINK ABOUT THIS THE OFFSET SHOULD BE + dest=sample_plate["E12"], + new_tip="once", + ) + + p300_multi.configure_nozzle_layout(style=PARTIAL_COLUMN, start="H1", end="E1", tip_racks=[partial_tiprack_1]) + p300_multi.distribute( + volume=300, + source=reservoir_12["A12"], + dest=[reservoir_12["A11"], reservoir_12["A10"], reservoir_12["A9"], reservoir_12["A8"], reservoir_12["A7"], reservoir_12["A6"]], + ) + + p300_multi.configure_nozzle_layout(style=PARTIAL_COLUMN, start="H1", end="F1", tip_racks=[partial_tiprack_1]) + p300_multi.consolidate( + volume=25, + source=[ + reservoir_12["A1"], + reservoir_12["A2"], + reservoir_12["A3"], + reservoir_12["A4"], + reservoir_12["A5"], + reservoir_12["A6"], + ], + dest=reservoir_1["A1"], + ) + + p300_multi.pick_up_tip() + p300_multi.touch_tip(reservoir_12["A7"]) + p300_multi.drop_tip() + p300_multi.pick_up_tip() + p300_multi.home() + p300_multi.drop_tip() + + p300_multi.pick_up_tip() + well = reservoir_12["A7"] + # directly from docs http://sandbox.docs.opentrons.com/edge/v2/new_protocol_api.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate + p300_multi.move_to(well.bottom(z=2)) + p300_multi.mix(10, 10) + p300_multi.move_to(well.top(z=5)) + p300_multi.blow_out() + p300_multi.prepare_to_aspirate() + p300_multi.move_to(well.bottom(z=2)) + p300_multi.aspirate(10, well.bottom(z=2)) + p300_multi.dispense(10) + p300_multi.drop_tip() + + p300_single.transfer(volume=25, source=sample_plate["A1"], dest=sample_plate["A12"], new_tip="once") + p300_single.distribute( + volume=300, + source=reservoir_12["A1"], + dest=[reservoir_12["A2"], reservoir_12["A3"], reservoir_12["A4"], reservoir_12["A5"], reservoir_12["A6"], reservoir_12["A7"]], + ) + + p300_single.consolidate( + volume=25, + source=[ + reservoir_12["A6"], + reservoir_12["A7"], + reservoir_12["A8"], + reservoir_12["A9"], + reservoir_12["A10"], + reservoir_12["A11"], + reservoir_12["A12"], + ], + dest=reservoir_1["A1"], + ) + + try: + while partial_tiprack_1 != None: + for well in sample_plate.wells(): + p300_single.pick_up_tip(partial_tiprack_1) + p300_single.aspirate(10, well) + p300_single.dispense(10, well) + p300_single.drop_tip() + except: + p300_single.home() diff --git a/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_8_None_SINGLE_HappyPath.py b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_8_None_SINGLE_HappyPath.py new file mode 100644 index 00000000000..1adf454421c --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_8_None_SINGLE_HappyPath.py @@ -0,0 +1,135 @@ +from opentrons.protocol_api import SINGLE + +metadata = { + "protocolName": "OT2 8 Channel SINGLE Happy Path A1 and H1", + "description": "OT2 8 Channel pipette and a SINGLE partial tip configuration.", +} + +requirements = { + "robotType": "OT-2", + "apiLevel": "2.20", +} + + +def run(protocol): + + # trash = protocol.load_trash_bin("A3") # must load trash bin + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_96_tiprack_300ul", + label="Partial Tip Rack", + location="8", + ) + + pipette = protocol.load_instrument(instrument_name="p300_multi_gen2", mount="left") + # mount on the right and you will get an error. + + # On the 8-channel SINGLE + # start="A1" Means the North nozzle will pickup from the SW corner of the tip rack + # start="H1" Means the South nozzle will pickup from the NW corner of the tip rack + pipette.configure_nozzle_layout( + style=SINGLE, + start="A1", # Which nozzle + tip_racks=[partial_tip_rack], + ) + + source_labware_2 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="2 Source Labware", + location="2", + ) + + destination_labware_3 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="3 Destination Labware", + location="3", + ) + + volume = 10 # Default volume for actions that require it + + ############################# + # Pipette do work + pipette.consolidate( + [volume, volume], + [source_labware_2["A3"], source_labware_2["A4"]], + destination_labware_3["A3"], + ) + + pipette.transfer(volume, source_labware_2["A6"], destination_labware_3["A6"]) + + pipette.distribute( + 5, + source_labware_2["A7"], + [destination_labware_3["A7"], destination_labware_3["A8"]], + ) + + pipette.pick_up_tip() + pipette.touch_tip(source_labware_2["B1"]) + pipette.drop_tip() + pipette.pick_up_tip() + pipette.home() + pipette.drop_tip() + + pipette.pick_up_tip() + well = source_labware_2["D1"] + # directly from docs http://sandbox.docs.opentrons.com/edge/v2/new_protocol_api.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate + pipette.move_to(well.bottom(z=2)) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.prepare_to_aspirate() + pipette.move_to(well.bottom(z=2)) + pipette.aspirate(10, well.bottom(z=2)) + pipette.dispense(10) + pipette.drop_tip() + + ############################ + # Change the pipette configuration + + pipette.configure_nozzle_layout( + style=SINGLE, + start="H1", # Which nozzle + tip_racks=[partial_tip_rack], + ) + + source_labware_4 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="4 Source Labware", + location="4", + ) + + ############################# + # Pipette do work + pipette.consolidate( + [volume, volume], + [source_labware_4["A3"], source_labware_4["A4"]], + destination_labware_3["A3"], + ) + + pipette.transfer(volume, source_labware_4["A6"], source_labware_4["A6"]) + + pipette.distribute( + 5, + source_labware_4["A7"], + [destination_labware_3["A7"], destination_labware_3["A8"]], + ) + + pipette.pick_up_tip() + pipette.touch_tip(destination_labware_3["B1"]) + pipette.drop_tip() + pipette.pick_up_tip() + pipette.home() + pipette.drop_tip() + + pipette.pick_up_tip() + well = destination_labware_3["D1"] + # directly from docs http://sandbox.docs.opentrons.com/edge/v2/new_protocol_api.html#opentrons.protocol_api.InstrumentContext.prepare_to_aspirate + pipette.move_to(well.bottom(z=2)) + pipette.mix(10, 10) + pipette.move_to(well.top(z=5)) + pipette.blow_out() + pipette.prepare_to_aspirate() + pipette.move_to(well.bottom(z=2)) + pipette.aspirate(10, well.bottom(z=2)) + pipette.dispense(10) + pipette.drop_tip() diff --git a/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_8_None_SINGLE_TubeRackCollision.py b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_8_None_SINGLE_TubeRackCollision.py new file mode 100644 index 00000000000..2cf77913071 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_8_None_SINGLE_TubeRackCollision.py @@ -0,0 +1,64 @@ +from opentrons.protocol_api import SINGLE + +metadata = { + "protocolName": "8Channel SINGLE Pickup Tube Rack collision", + "description": "Unsafe protocol ❗❗❗❗❗❗❗❗❗❗❗ will collide with tube.", +} + +requirements = { + "robotType": "OT-2", + "apiLevel": "2.20", +} + + +def comment_tip_rack_status(ctx, tip_rack): + """ + Print out the tip status for each row in a tip rack. + Each row (A-H) will print the well statuses for columns 1-12 in a single comment, + with a '🟢' for present tips and a '❌' for missing tips. + """ + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + range_1_to_12 = range(1, 13) + + ctx.comment(f"Tip rack in {tip_rack.parent}") + + for row in range_A_to_H: + status_line = f"{row}: " + for col in range_1_to_12: + well = f"{row}{col}" + has_tip = tip_rack.wells_by_name()[well].has_tip + status_emoji = "🟢" if has_tip else "❌" + status_line += f"{well} {status_emoji} " + + # Print the full status line for the row + ctx.comment(status_line) + + +def run(protocol): + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_96_tiprack_300ul", + label="Partial Tip Rack", + location="3", + ) + + tube_rack = protocol.load_labware( + load_name="opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + label="Tube Rack", + location="2", + ) + + pipette = protocol.load_instrument(instrument_name="p300_multi_gen2", mount="left") + + pipette.configure_nozzle_layout( + style=SINGLE, + start="H1", + tip_racks=[partial_tip_rack], + ) + + pipette.pick_up_tip() + comment_tip_rack_status(protocol, partial_tip_rack) + # both commands are not physically possible but throw no error + pipette.aspirate(30, tube_rack["A3"].bottom()) + pipette.dispense(30, tube_rack["A4"].bottom()) + pipette.drop_tip() diff --git a/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_P20M_PARTIAL_COLUMN_Overhang.py b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_P20M_PARTIAL_COLUMN_Overhang.py new file mode 100644 index 00000000000..6e4a3b5b8e5 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_P20M_PARTIAL_COLUMN_Overhang.py @@ -0,0 +1,155 @@ +from opentrons.protocol_api import PARTIAL_COLUMN + +metadata = { + "protocolName": "p20_multi_gen2 PARTIAL_COLUMN Overhang", + "description": "A protocol that demonstrates overhang into a slot with a partial column configuration.", + "author": "Josh McVey", +} + +requirements = { + "robotType": "OT-2", + "apiLevel": "2.20", +} + + +def comment_tip_rack_status(ctx, tip_rack): + """ + Print out the tip status for each row in a tip rack. + Each row (A-H) will print the well statuses for columns 1-12 in a single comment, + with a '🟢' for present tips and a '❌' for missing tips. + """ + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + range_1_to_12 = range(1, 13) + + ctx.comment(f"Tip rack in {tip_rack.parent}") + + for row in range_A_to_H: + status_line = f"{row}: " + for col in range_1_to_12: + well = f"{row}{col}" + has_tip = tip_rack.wells_by_name()[well].has_tip + status_emoji = "🟢" if has_tip else "❌" + status_line += f"{well} {status_emoji} " + + # Print the full status line for the row + ctx.comment(status_line) + + +def run(protocol): + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_96_tiprack_20ul", + label="Partial Tip Rack", + location="10", + ) + + pipette = protocol.load_instrument(instrument_name="p20_multi_gen2", mount="left") + + pipette.configure_nozzle_layout( + style=PARTIAL_COLUMN, + start="H1", + end="D1", # 5 Tips + tip_racks=[partial_tip_rack], + ) + + source_labware_5 = protocol.load_labware( + load_name="nest_1_reservoir_290ml", + label="Source Reservoir", + location="5", + ) + + destination_labware_2 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="PCR Plate", + location="2", + ) + + # Known issue in 8.0.0 + # if you target A1 of the labware in 2 + # nozzle H1 - the front-most nozzle will go to A1 + # this means that tips will be out of the slot + # they will over hang into the 5 slot + # no error is raised + # the overhanging tips will collide with the labware in slot 5 + + # If we ever do detect and error for this + # Take all these examples into a negative Overrides test + + volume = 5 + pipette.pick_up_tip() + comment_tip_rack_status(protocol, partial_tip_rack) + # why with P20 do I get: + # MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to PCR Plate in slot 2 + # with H1 nozzle partial configuration will result in collision with items in deck slot 5. + # because the P20 would actually collide with the labware in slot 5 + # https://opentrons.atlassian.net/browse/RQA-3198 + # bad - 4 tips will overhang into slot 5 + # pipette.aspirate(volume=volume, location=destination_labware_2["A1"]) + # bad - 3 tips will overhang into slot 5 + # pipette.aspirate(volume=volume, location=destination_labware_2["B1"]) + # bad - 2 tips will overhang into slot 5 + # pipette.aspirate(volume=volume, location=destination_labware_2["C1"]) + # bad - 1 tip will overhang into slot 5 + # pipette.aspirate(volume=volume, location=destination_labware_2["D1"]) + # pipette.aspirate(volume=volume, location=destination_labware_2["E1"]) + # pipette.aspirate(volume=volume, location=destination_labware_2["F1"]) + # pipette.aspirate(volume=volume, location=destination_labware_2["G1"]) + # H is the only safe row + pipette.aspirate(volume=volume, location=destination_labware_2["H1"]) + + +# ignore the below for the time being +""" + # this is safe - from a reservoir + # must aspirate before dispense + pipette.aspirate(volume=volume, location=source_labware_5["A1"]) + # bad - bad - 4 tips will overhang into B2 + pipette.dispense(volume=volume, location=destination_labware_2["A1"]) + # this is safe - from a reservoir + # must aspirate before dispense + pipette.aspirate(volume=volume, location=source_labware_5["A1"]) + # bad - bad - 3 tips will overhang into B2 + pipette.dispense(volume=volume, location=destination_labware_2["B2"]) + # this is safe - from a reservoir + # must aspirate before dispense + pipette.aspirate(volume=volume, location=source_labware_5["A1"]) + # bad - bad - 2 tips will overhang into B2 + pipette.dispense(volume=volume, location=destination_labware_2["C3"]) + # this is safe - from a reservoir + # must aspirate before dispense + pipette.aspirate(volume=volume, location=source_labware_5["A1"]) + # bad - bad - 1 tip will overhang into B2 + pipette.dispense(volume=volume, location=destination_labware_2["D4"]) + # this is safe - from a reservoir + # must aspirate before dispense + pipette.aspirate(volume=volume, location=source_labware_5["A1"]) + # this is safe - 0 tips will overhang into B2 + pipette.dispense(volume=volume, location=destination_labware_2["E5"]) + # bad - has overhang into B2 - 4 tips + pipette.touch_tip(location=destination_labware_2["A1"]) + # bad - has overhang into B2 - 4 tips + pipette.blow_out(location=destination_labware_2["A1"]) + # bad - has overhang into B2 - 4 tips + pipette.mix(repetitions=3, volume=volume, location=destination_labware_2["A2"]) + # bad - aspirate and dispense have overhang into B2 + pipette.drop_tip() + pipette.transfer(volume=volume, source=destination_labware_2["A1"], dest=destination_labware_2["A2"]) + # bad - aspirate has 3 tip overhang but not dispense + pipette.transfer(volume=volume, source=destination_labware_2["B1"], dest=destination_labware_2["E1"]) + # bad - aspirate is safe but dispense has 2 tip overhang + pipette.transfer(volume=volume, source=destination_labware_2["E1"], dest=destination_labware_2["C3"]) + # bad - source and destinations have overhang + pipette.distribute(volume=20, source=destination_labware_2["D2"], dest=[destination_labware_2["D3"], destination_labware_2["D4"]]) + # bad - source has overhang but destinations are safe + pipette.distribute(volume=20, source=destination_labware_2["D2"], dest=[destination_labware_2["E3"], destination_labware_2["E4"]]) + # bad - source has no overhang but 1 destination does + pipette.distribute(volume=20, source=destination_labware_2["E6"], dest=[destination_labware_2["A7"], destination_labware_2["E7"]]) + # bad - source has no overhang but 2 destinations do + pipette.distribute(volume=20, source=destination_labware_2["E7"], dest=[destination_labware_2["A8"], destination_labware_2["A9"]]) + # bad - all sources and destination have overhang + pipette.consolidate(volume=volume, source=[destination_labware_2["A9"], destination_labware_2["A10"]], dest=destination_labware_2["A11"]) + # bad - 1 source has overhang but destination is safe + pipette.consolidate(volume=volume, source=[destination_labware_2["A9"], destination_labware_2["E10"]], dest=destination_labware_2["E11"]) + # bad - sources are safe but destination has overhang + pipette.consolidate(volume=volume, source=[destination_labware_2["E9"], destination_labware_2["E10"]], dest=destination_labware_2["A12"]) +""" diff --git a/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_P20M_Simple.py b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_P20M_Simple.py new file mode 100644 index 00000000000..6fcfad51541 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_P20M_Simple.py @@ -0,0 +1,90 @@ +from opentrons.protocol_api import PARTIAL_COLUMN + +metadata = { + "protocolName": "p20_multi_gen2 Simple", + "description": "A protocol that demonstrates safe actions with p20_multi_gen2", + "author": "Josh McVey", +} + +requirements = { + "robotType": "OT-2", + "apiLevel": "2.20", +} + + +def comment_tip_rack_status(ctx, tip_rack): + """ + Print out the tip status for each row in a tip rack. + Each row (A-H) will print the well statuses for columns 1-12 in a single comment, + with a '🟢' for present tips and a '❌' for missing tips. + """ + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + range_1_to_12 = range(1, 13) + + ctx.comment(f"Tip rack in {tip_rack.parent}") + + for row in range_A_to_H: + status_line = f"{row}: " + for col in range_1_to_12: + well = f"{row}{col}" + has_tip = tip_rack.wells_by_name()[well].has_tip + status_emoji = "🟢" if has_tip else "❌" + status_line += f"{well} {status_emoji} " + + # Print the full status line for the row + ctx.comment(status_line) + + +def run(protocol): + + tip_rack = protocol.load_labware( + load_name="opentrons_96_tiprack_20ul", + label="Partial Tip Rack", + location="10", + ) + + pipette = protocol.load_instrument(instrument_name="p20_multi_gen2", mount="left", tip_racks=[tip_rack]) + + source_labware_5 = protocol.load_labware( + load_name="nest_1_reservoir_290ml", + label="Source Reservoir", + location="5", + ) + + destination_labware_2 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="PCR Plate", + location="2", + ) + + volume = 10 + pipette.pick_up_tip() + comment_tip_rack_status(protocol, tip_rack) + pipette.aspirate(volume=volume, location=destination_labware_2["H1"]) + pipette.dispense(volume=volume, location=destination_labware_2["H2"]) + for i in range(1, 13): + protocol.comment(f"Touching tip to {destination_labware_2[f'H{i}']}") + pipette.touch_tip(location=destination_labware_2[f"H{i}"]) + + pipette.blow_out(location=destination_labware_2["H1"]) + pipette.mix(repetitions=3, volume=volume, location=destination_labware_2["H1"]) + pipette.drop_tip() + # Note that you cannot target Hn like you could on a partial tip + # Invalid source for multichannel transfer: [H3 of PCR Plate on slot 2] + # pipette.transfer(volume=volume, source=destination_labware_2["H3"], dest=destination_labware_2["H4"]) + # comment_tip_rack_status(protocol, tip_rack) + # Invalid source for multichannel transfer: [H4 of PCR Plate on slot 2] + # pipette.distribute(volume=volume, source=destination_labware_2["H4"], dest=[destination_labware_2["H5"], destination_labware_2["H6"]]) + # comment_tip_rack_status(protocol, tip_rack) + # Invalid source for multichannel transfer: [H7 of PCR Plate on slot 2, H8 of PCR Plate on slot 2] + # pipette.consolidate(volume=volume, source=[destination_labware_2["H7"], destination_labware_2["H8"]], dest=destination_labware_2["H9"]) + # comment_tip_rack_status(protocol, tip_rack) + + # but this works and I am inferring + # when using 8 channel pipette and you are specifying a column, use An + pipette.transfer(volume=volume, source=destination_labware_2["A3"], dest=destination_labware_2["A4"]) + comment_tip_rack_status(protocol, tip_rack) + pipette.distribute(volume=volume, source=destination_labware_2["A4"], dest=[destination_labware_2["A5"], destination_labware_2["A6"]]) + comment_tip_rack_status(protocol, tip_rack) + pipette.consolidate(volume=volume, source=[destination_labware_2["A7"], destination_labware_2["A8"]], dest=destination_labware_2["A9"]) + comment_tip_rack_status(protocol, tip_rack) diff --git a/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_P300M_PARTIAL_COLUMN_Overhang.py b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_P300M_PARTIAL_COLUMN_Overhang.py new file mode 100644 index 00000000000..489b72abfe5 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_P300M_PARTIAL_COLUMN_Overhang.py @@ -0,0 +1,151 @@ +from opentrons.protocol_api import PARTIAL_COLUMN + +metadata = { + "protocolName": "p300_multi_gen2 PARTIAL_COLUMN Overhang", + "description": "A protocol that demonstrates overhang into a slot with a partial column configuration.", + "author": "Josh McVey", +} + +requirements = { + "robotType": "OT-2", + "apiLevel": "2.20", +} + + +def comment_tip_rack_status(ctx, tip_rack): + """ + Print out the tip status for each row in a tip rack. + Each row (A-H) will print the well statuses for columns 1-12 in a single comment, + with a '🟢' for present tips and a '❌' for missing tips. + """ + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + range_1_to_12 = range(1, 13) + + ctx.comment(f"Tip rack in {tip_rack.parent}") + + for row in range_A_to_H: + status_line = f"{row}: " + for col in range_1_to_12: + well = f"{row}{col}" + has_tip = tip_rack.wells_by_name()[well].has_tip + status_emoji = "🟢" if has_tip else "❌" + status_line += f"{well} {status_emoji} " + + # Print the full status line for the row + ctx.comment(status_line) + + +def run(protocol): + + partial_tip_rack = protocol.load_labware( + load_name="opentrons_96_tiprack_300ul", + label="Partial Tip Rack", + location="10", + ) + + pipette = protocol.load_instrument(instrument_name="p300_multi_gen2", mount="left") + + pipette.configure_nozzle_layout( + style=PARTIAL_COLUMN, + start="H1", + end="D1", # 5 Tips + tip_racks=[partial_tip_rack], + ) + + source_labware_5 = protocol.load_labware( + load_name="nest_1_reservoir_290ml", + label="Source Reservoir", + location="5", + ) + + destination_labware_2 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="PCR Plate", + location="2", + ) + + # Known issue in 8.0.0 + # if you target A1 of the labware in 2 + # nozzle H1 - the front-most nozzle will go to A1 + # this means that tips will be out of the slot + # they will over hang into the 5 slot + # no error is raised + # the overhanging tips will collide with the labware in 5 + + # If we ever do detect and error for this + # Take all these examples into a negative Overrides test + + volume = 20 + pipette.pick_up_tip() + comment_tip_rack_status(protocol, partial_tip_rack) + # bad - 4 tips will overhang into slot 5 + pipette.aspirate(volume=volume, location=destination_labware_2["A1"]) + # bad - 3 tips will overhang into slot 5 + pipette.aspirate(volume=volume, location=destination_labware_2["B1"]) + # bad - 2 tips will overhang into slot 5 + pipette.aspirate(volume=volume, location=destination_labware_2["C1"]) + # bad - 1 tip will overhang into slot 5 + pipette.aspirate(volume=volume, location=destination_labware_2["D1"]) + # these should be safe??? + pipette.aspirate(volume=volume, location=destination_labware_2["E1"]) + pipette.aspirate(volume=volume, location=destination_labware_2["F1"]) + pipette.aspirate(volume=volume, location=destination_labware_2["G1"]) + # only one that is safe + pipette.aspirate(volume=volume, location=destination_labware_2["H1"]) + + +# ignore the below for the time being +""" + # this is safe - from a reservoir + # must aspirate before dispense + pipette.aspirate(volume=volume, location=source_labware_5["A1"]) + # bad - bad - 4 tips will overhang into B2 + pipette.dispense(volume=volume, location=destination_labware_2["A1"]) + # this is safe - from a reservoir + # must aspirate before dispense + pipette.aspirate(volume=volume, location=source_labware_5["A1"]) + # bad - bad - 3 tips will overhang into B2 + pipette.dispense(volume=volume, location=destination_labware_2["B2"]) + # this is safe - from a reservoir + # must aspirate before dispense + pipette.aspirate(volume=volume, location=source_labware_5["A1"]) + # bad - bad - 2 tips will overhang into B2 + pipette.dispense(volume=volume, location=destination_labware_2["C3"]) + # this is safe - from a reservoir + # must aspirate before dispense + pipette.aspirate(volume=volume, location=source_labware_5["A1"]) + # bad - bad - 1 tip will overhang into B2 + pipette.dispense(volume=volume, location=destination_labware_2["D4"]) + # this is safe - from a reservoir + # must aspirate before dispense + pipette.aspirate(volume=volume, location=source_labware_5["A1"]) + # this is safe - 0 tips will overhang into B2 + pipette.dispense(volume=volume, location=destination_labware_2["E5"]) + # bad - has overhang into B2 - 4 tips + pipette.touch_tip(location=destination_labware_2["A1"]) + # bad - has overhang into B2 - 4 tips + pipette.blow_out(location=destination_labware_2["A1"]) + # bad - has overhang into B2 - 4 tips + pipette.mix(repetitions=3, volume=volume, location=destination_labware_2["A2"]) + # bad - aspirate and dispense have overhang into B2 + pipette.drop_tip() + pipette.transfer(volume=volume, source=destination_labware_2["A1"], dest=destination_labware_2["A2"]) + # bad - aspirate has 3 tip overhang but not dispense + pipette.transfer(volume=volume, source=destination_labware_2["B1"], dest=destination_labware_2["E1"]) + # bad - aspirate is safe but dispense has 2 tip overhang + pipette.transfer(volume=volume, source=destination_labware_2["E1"], dest=destination_labware_2["C3"]) + # bad - source and destinations have overhang + pipette.distribute(volume=20, source=destination_labware_2["D2"], dest=[destination_labware_2["D3"], destination_labware_2["D4"]]) + # bad - source has overhang but destinations are safe + pipette.distribute(volume=20, source=destination_labware_2["D2"], dest=[destination_labware_2["E3"], destination_labware_2["E4"]]) + # bad - source has no overhang but 1 destination does + pipette.distribute(volume=20, source=destination_labware_2["E6"], dest=[destination_labware_2["A7"], destination_labware_2["E7"]]) + # bad - source has no overhang but 2 destinations do + pipette.distribute(volume=20, source=destination_labware_2["E7"], dest=[destination_labware_2["A8"], destination_labware_2["A9"]]) + # bad - all sources and destination have overhang + pipette.consolidate(volume=volume, source=[destination_labware_2["A9"], destination_labware_2["A10"]], dest=destination_labware_2["A11"]) + # bad - 1 source has overhang but destination is safe + pipette.consolidate(volume=volume, source=[destination_labware_2["A9"], destination_labware_2["E10"]], dest=destination_labware_2["E11"]) + # bad - sources are safe but destination has overhang + pipette.consolidate(volume=volume, source=[destination_labware_2["E9"], destination_labware_2["E10"]], dest=destination_labware_2["A12"]) +""" diff --git a/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_P300M_Simple.py b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_P300M_Simple.py new file mode 100644 index 00000000000..7728a94a077 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_P300M_Simple.py @@ -0,0 +1,94 @@ +from opentrons.protocol_api import PARTIAL_COLUMN, ALL + +metadata = { + "protocolName": "p20_multi_gen2 Simple", + "description": "A protocol that demonstrates safe actions with p20_multi_gen2", + "author": "Josh McVey", +} + +requirements = { + "robotType": "OT-2", + "apiLevel": "2.20", +} + + +def comment_tip_rack_status(ctx, tip_rack): + """ + Print out the tip status for each row in a tip rack. + Each row (A-H) will print the well statuses for columns 1-12 in a single comment, + with a '🟢' for present tips and a '❌' for missing tips. + """ + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + range_1_to_12 = range(1, 13) + + ctx.comment(f"Tip rack in {tip_rack.parent}") + + for row in range_A_to_H: + status_line = f"{row}: " + for col in range_1_to_12: + well = f"{row}{col}" + has_tip = tip_rack.wells_by_name()[well].has_tip + status_emoji = "🟢" if has_tip else "❌" + status_line += f"{well} {status_emoji} " + + # Print the full status line for the row + ctx.comment(status_line) + + +def run(protocol): + + tip_rack = protocol.load_labware( + load_name="opentrons_96_tiprack_300ul", + label="Partial Tip Rack", + location="10", + ) + + pipette = protocol.load_instrument(instrument_name="p300_multi_gen2", mount="left", tip_racks=[tip_rack]) + + source_labware_5 = protocol.load_labware( + load_name="nest_1_reservoir_290ml", + label="Source Reservoir", + location="5", + ) + + destination_labware_2 = protocol.load_labware( + load_name="nest_96_wellplate_100ul_pcr_full_skirt", + label="PCR Plate", + location="2", + ) + + volume = 40 + pipette.pick_up_tip() + comment_tip_rack_status(protocol, tip_rack) + pipette.aspirate(volume=volume, location=destination_labware_2["H1"]) + pipette.dispense(volume=volume, location=destination_labware_2["H2"]) + for i in range(1, 13): + protocol.comment(f"Touching tip to {destination_labware_2[f'H{i}']}") + pipette.touch_tip(location=destination_labware_2[f"H{i}"]) + + pipette.blow_out(location=destination_labware_2["H1"]) + pipette.mix(repetitions=3, volume=volume, location=destination_labware_2["H1"]) + pipette.drop_tip() + + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + column1 = [destination_labware_2[f"{row}1"] for row in range_A_to_H] + column2 = [destination_labware_2[f"{row}2"] for row in range_A_to_H] + protocol.comment(f"Transferring {volume}uL from column 1 to column 2") + pipette.transfer(volume=volume, source=column1, dest=column2) + comment_tip_rack_status(protocol, tip_rack) + + # Note that you cannot target Hn like you could on a partial tip + # when I try to use the well destination of Hn for + # transfer, distribute, or consolidate, I get an error like + # Invalid source for multichannel transfer: [H3 of PCR Plate on slot 2] + # pipette.transfer(volume=volume, source=destination_labware_2["H3"], dest=destination_labware_2["H4"]) + # comment_tip_rack_status(protocol, tip_rack) + + # but this works and I am inferring + # when using 8 channel pipette and you are specifying a column, use An + pipette.transfer(volume=volume, source=destination_labware_2["A3"], dest=destination_labware_2["A4"]) + comment_tip_rack_status(protocol, tip_rack) + pipette.distribute(volume=volume, source=destination_labware_2["A4"], dest=[destination_labware_2["A5"], destination_labware_2["A6"]]) + comment_tip_rack_status(protocol, tip_rack) + pipette.consolidate(volume=volume, source=[destination_labware_2["A7"], destination_labware_2["A8"]], dest=destination_labware_2["A9"]) + comment_tip_rack_status(protocol, tip_rack) diff --git a/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_P50_touch_tip.py b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_P50_touch_tip.py new file mode 100644 index 00000000000..50f04c1ead2 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/OT2_S_v2_20_P50_touch_tip.py @@ -0,0 +1,52 @@ +import random +from typing import Set +from opentrons import protocol_api + +# metadata +metadata = { + "protocolName": "touch_tip directly on OT2", + "author": "Josh McVey", + "description": "touch tip on OT2", +} + +requirements = { + "robotType": "OT-2", + "apiLevel": "2.20", +} + + +def load_liquid_in_all_wells(labware, liquid) -> None: + for well in labware.wells(): + well.load_liquid(liquid=liquid, volume=well.max_volume) + + +def run(protocol: protocol_api.ProtocolContext): + + # labware + tiprack2 = protocol.load_labware("opentrons_96_tiprack_20ul", "5") + wet_sample = protocol.load_labware("nest_12_reservoir_15ml", "2") + + # liquids + waterButMoreBlue = protocol.define_liquid( + name="H20", + description="Test this wet!!!", + display_color="#0077b6", + ) + + wet_sample.well("A1").load_liquid(liquid=waterButMoreBlue, volume=200) + wet_sample.well("A2").load_liquid(liquid=waterButMoreBlue, volume=200) + wet_sample.well("A3").load_liquid(liquid=waterButMoreBlue, volume=200) + + # instruments + p20 = protocol.load_instrument("p20_single_gen2", mount="right", tip_racks=[tiprack2]) + + pipette = p20 + + pipette.pick_up_tip() + protocol.comment("touch_tip") + pipette.touch_tip(wet_sample.well("A1")) + protocol.comment("air_gap") + pipette.air_gap(volume=10) + protocol.comment("blow_out with no arguments") + pipette.blow_out() + pipette.drop_tip() diff --git a/analyses-snapshot-testing/files/protocols/OT2_X_v2_20_8_Overrides_InvalidConfigs.py b/analyses-snapshot-testing/files/protocols/OT2_X_v2_20_8_Overrides_InvalidConfigs.py new file mode 100644 index 00000000000..e08cf13eda6 --- /dev/null +++ b/analyses-snapshot-testing/files/protocols/OT2_X_v2_20_8_Overrides_InvalidConfigs.py @@ -0,0 +1,151 @@ +# key = "drop_tip_with_location" +from dataclasses import dataclass +from typing import Optional +from opentrons.protocol_api import PARTIAL_COLUMN + +# inspired by https://opentrons.atlassian.net/browse/PLAT-457 + +metadata = { + "protocolName": "Invalid tip configs that should error", + "description": "oooo", +} + +requirements = { + "robotType": "OT-2", + "apiLevel": "2.20", +} + + +@dataclass +class PartialTipConfig: + key: str + pipette_load_name: str + description: str + starting_tip: str + starting_nozzle: str + api_tip_config: str + api_start: str + api_end: Optional[str] = None + + +# We do not allow PARTIAL_COLUMN to start on the bottom of the tip rack +# Want to see +# "IncompatibleNozzleConfiguration: Attempted Nozzle Configuration does not match any approved map layout for the current pipette." +eight_partial_column_bottom_left = PartialTipConfig( + key="eight_partial_column_bottom_left", + pipette_load_name="p300_multi_gen2", + description="8 channel 2 tip pick up bottom left of tiprack", + starting_tip="H1", + starting_nozzle="A1", + api_tip_config=PARTIAL_COLUMN, + api_start="A1", + api_end="B1", +) + + +# Want to see +# Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): IncompatibleNozzleConfiguration: No entry for front right nozzle 'G12' in pipette +eight_partial_column_bottom_right = PartialTipConfig( + key="eight_partial_column_bottom_right", + pipette_load_name="p20_multi_gen2", + description="8 channel 2 tip pick up bottom left of tiprack", + starting_tip="H12", + starting_nozzle="A1", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", # for partial column only H1 + api_end="G12", # the author thinks this is to specify the ending tip and wants to start at bottom right for 2 tips +) + + +# Partial column configurations require the 'end' parameter. +eight_partial_column_no_end = PartialTipConfig( + key="eight_partial_column_no_end", + pipette_load_name="p20_multi_gen2", + description="8 channel PARTIAL_COLUMN with no end", + starting_tip="H1", + starting_nozzle="A1", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", + # api_end="B1", sets the end to None +) + +# If you call return_tip() while using partial tip pickup, the API will raise an error. +# Error 4000 GENERAL_ERROR (UnexpectedProtocolError): Cannot return tip to a tiprack while the pipette is configured for partial tip. +return_tip_error = PartialTipConfig( + key="return_tip_error", + pipette_load_name="p20_multi_gen2", + description="8 channel 2 tip pick up top left of tiprack", + starting_tip="A1", + starting_nozzle="H1", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", + api_end="G1", # valid 2 tip +) + + +# pipette.drop_tip(tiprack["B1"]) # drops tip in rack location A1 +drop_tip_with_location = PartialTipConfig( + key="drop_tip_with_location", + pipette_load_name="p300_multi_gen2", + description="8 channel 2 tip pick up top left of tiprack", + starting_tip="A1", + starting_nozzle="H1", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", + api_end="G1", # valid 2 tip +) + +all_partial_configs = [ + eight_partial_column_bottom_left, + eight_partial_column_bottom_right, + eight_partial_column_no_end, + return_tip_error, + drop_tip_with_location, +] + + +def find_partial_tip_config(key: str) -> Optional[PartialTipConfig]: + for config in all_partial_configs: + if config.key == key: + return config + raise ValueError(f"Could not find partial tip config with key {key}") + + +def comment_column_has_tip(ctx, tip_rack, column): + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + wells = [f"{row}{column}" for row in range_A_to_H] + for well in wells: + ctx.comment(f"Tip rack in {tip_rack.parent}, well {well} has tip: {tip_rack.wells_by_name()[well].has_tip}") + + +def run(ctx): + + tip_rack_20 = ctx.load_labware("opentrons_96_tiprack_20ul", "1") + tip_rack_300 = ctx.load_labware("opentrons_96_tiprack_300ul", "2") + + pipette_config = find_partial_tip_config(key) + + pipette = ctx.load_instrument(pipette_config.pipette_load_name, "left") + + tip_rack = tip_rack_20 + if pipette_config.pipette_load_name.__contains__("300"): + tip_rack = tip_rack_300 + pipette.configure_nozzle_layout( + style=pipette_config.api_tip_config, start=pipette_config.api_start, end=pipette_config.api_end, tip_racks=[tip_rack] + ) + + target_labware_loadname = "nest_96_wellplate_100ul_pcr_full_skirt" + source = ctx.load_labware(target_labware_loadname, "4") + destination = ctx.load_labware(target_labware_loadname, "5") + + if key == "return_tip_error": + pipette.pick_up_tip() + # this test picks up 2 tips + comment_column_has_tip(ctx, tip_rack, 1) + pipette.return_tip() # this should raise an error + elif key == "drop_tip_with_location": + pipette.pick_up_tip() + comment_column_has_tip(ctx, tip_rack, 1) + pipette.drop_tip(tip_rack["A1"]) # this should raise an error + else: + pipette.transfer(10, source["A1"], destination["A1"]) diff --git a/analyses-snapshot-testing/files/templates/basic_flex_rtp_v1.1.py b/analyses-snapshot-testing/files/templates/basic_flex_rtp_v1.1.py new file mode 100644 index 00000000000..9a35d06e0c4 --- /dev/null +++ b/analyses-snapshot-testing/files/templates/basic_flex_rtp_v1.1.py @@ -0,0 +1,522 @@ +from dataclasses import dataclass +from typing import Any, Optional, Union +from opentrons.protocol_api import SINGLE, COLUMN, PARTIAL_COLUMN, ROW, ALL + +metadata = { + "protocolName": "Basic flex RTP template", +} + +requirements = { + "robotType": "Flex", + "apiLevel": "2.20", +} + +####### RTP DEFINITIONS ####### +# NozzleConfigurationType is from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType +# do not want to import that as that interface or location might change +# type is not in shared-data +# cannot do the below +# ApiTipConfigType = Union[SINGLE, COLUMN, PARTIAL_COLUMN, ROW] + + +@dataclass +class PartialTipConfig: + """Dataclass to hold a partial tip configuration descriptively.""" + + key: str + description: str + starting_tip: str + starting_nozzle: str + api_tip_config: Any + api_start: str + api_end: Optional[str] + + def __str__(self): + return ( + f"🔑 Key: {self.key} | 📝 Description: {self.description} | " + f"💉 Starting Tip: {self.starting_tip} | 🔧 Starting Nozzle: {self.starting_nozzle} | " + f"📜 API Tip Config: {self.api_tip_config} | 🚀 API Start: {self.api_start} | " + f"🛑 API End: {self.api_end if self.api_end else 'None'}" + ) + + +#### Define all viable partial tip configurations. + +# flex_96channel_1000 SINGLE +# names and descriptions describe where relative to the tiprack the pipette will pick up tips + +ninety_six_single_back_left = PartialTipConfig( + key="ninety_six_single_back_left", + description="96 single picking up back left of tiprack", + starting_tip="A1", + starting_nozzle="H12", + api_tip_config=SINGLE, + api_start="H12", + api_end=None, +) + +ninety_six_single_back_right = PartialTipConfig( + key="ninety_six_single_back_right", + description="96 single picking up back right of tiprack", + starting_tip="A12", + starting_nozzle="H1", + api_tip_config=SINGLE, + api_start="H1", + api_end=None, +) + +ninety_six_single_front_left = PartialTipConfig( + key="ninety_six_single_front_left", + description="96 single picking up front left of tiprack", + starting_tip="H1", + starting_nozzle="A12", + api_tip_config=SINGLE, + api_start="A12", + api_end=None, +) + +ninety_six_single_front_right = PartialTipConfig( + key="ninety_six_single_front_right", + description="96 single picking up front right of tiprack", + starting_tip="H12", + starting_nozzle="A1", + api_tip_config=SINGLE, + api_start="A1", + api_end=None, +) + +# flex_96channel_1000 COLUMN + +ninety_six_column_left = PartialTipConfig( + key="ninety_six_column_left", + description="96 column picking up left column of tiprack", + starting_tip="Column 1", + starting_nozzle="Column 12", + api_tip_config=COLUMN, + api_start="A12", + api_end=None, +) + + +ninety_six_column_right = PartialTipConfig( + key="ninety_six_column_right", + description="96 column picking up right column of tiprack", + starting_tip="Row 12", + starting_nozzle="Row 1", + api_tip_config=COLUMN, + api_start="A1", + api_end=None, +) + +# flex_96channel_1000 ROW + +ninety_six_row_back = PartialTipConfig( + key="ninety_six_row_back", + description="96 row picking up back row of tiprack", + starting_tip="Row A", + starting_nozzle="Row H", + api_tip_config=ROW, + api_start="H1", + api_end=None, +) + +ninety_six_row_front = PartialTipConfig( + key="ninety_six_row_front", + description="96 row picking up front row of tiprack", + starting_tip="Row H", + starting_nozzle="Row A", + api_tip_config=ROW, + api_start="A1", + api_end=None, +) + +# 8 channel SINGLE +eight_single = PartialTipConfig( + key="eight_single", + description="8 channel single picking up from the back left of the tiprack", + starting_tip="A1", + starting_nozzle="H1", + api_tip_config=SINGLE, + api_start="H1", + api_end=None, +) + +# PARTIAL_COLUMN +eight_partial_back_7_tips = PartialTipConfig( + key="eight_partial_back_7_tips", + description="8 channel picking up 7 tips", + starting_tip="H1", + starting_nozzle="B1", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", + api_end="B1", +) + +eight_partial_back_6_tips = PartialTipConfig( + key="eight_partial_back_6_tips", + description="8 channel picking up 6 tips", + starting_tip="H1", + starting_nozzle="C1", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", + api_end="C1", +) +eight_partial_back_5_tips = PartialTipConfig( + key="eight_partial_back_5_tips", + description="8 channel picking up 5 tips", + starting_tip="H1", + starting_nozzle="D1", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", + api_end="D1", +) +eight_partial_back_4_tips = PartialTipConfig( + key="eight_partial_back_4_tips", + description="8 channel picking up 4 tips", + starting_tip="H1", + starting_nozzle="E1", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", + api_end="E1", +) +eight_partial_back_3_tips = PartialTipConfig( + key="eight_partial_back_3_tips", + description="8 channel picking up 3 tips", + starting_tip="H1", + starting_nozzle="F1", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", + api_end="F1", +) + +eight_partial_back_2_tips = PartialTipConfig( + key="eight_partial_back_2_tips", + description="8 channel picking up 2 tips", + starting_tip="H1", + starting_nozzle="G1", + api_tip_config=PARTIAL_COLUMN, + api_start="H1", + api_end="G1", +) + +no_tip_config = PartialTipConfig( + key="no_tip_config", + description="Will discard and not set a partial tip config", + starting_tip="", + starting_nozzle="", + api_tip_config=ALL, + api_start="", + api_end="", +) + +# make a list of all the partial tip configurations + +all_partial_configs = [ + ninety_six_single_back_left, + ninety_six_single_back_right, + ninety_six_single_front_left, + ninety_six_single_front_right, + ninety_six_column_left, + ninety_six_column_right, + ninety_six_row_back, + ninety_six_row_front, + eight_single, + eight_partial_back_2_tips, + eight_partial_back_3_tips, + eight_partial_back_4_tips, + eight_partial_back_5_tips, + eight_partial_back_6_tips, + eight_partial_back_7_tips, + no_tip_config, +] + + +def find_partial_tip_config(key: str) -> Optional[PartialTipConfig]: + """Find a partial tip config by key.""" + for config in all_partial_configs: + if config.key == key: + return config + raise ValueError(f"Could not find partial tip config with key {key}") + + +reservoir_choices = [ + { + "display_name": "Agilent Reservoir 290 mL", + "value": "agilent_1_reservoir_290ml", + }, + { + "display_name": "Axygen Reservoir 90 mL", + "value": "axygen_1_reservoir_90ml", + }, + { + "display_name": "nest_12_reservoir_15ml", + "value": "nest_12_reservoir_15ml", + }, + { + "display_name": "Nest Reservoir 195 mL", + "value": "nest_1_reservoir_195ml", + }, + { + "display_name": "Nest Reservoir 290 mL", + "value": "nest_1_reservoir_290ml", + }, + { + "display_name": "usa..._12_reservoir_22ml", + "value": "usascientific_12_reservoir_22ml", + }, +] + +position_choices = [ + {"display_name": "A1", "value": "A1"}, + {"display_name": "A2", "value": "A2"}, + {"display_name": "A3", "value": "A3"}, + {"display_name": "B1", "value": "B1"}, + {"display_name": "B2", "value": "B2"}, + {"display_name": "B3", "value": "B3"}, + {"display_name": "C1", "value": "C1"}, + {"display_name": "C2", "value": "C2"}, + {"display_name": "C3", "value": "C3"}, + {"display_name": "D1", "value": "D1"}, + {"display_name": "D2", "value": "D2"}, + {"display_name": "D3", "value": "D3"}, +] + + +def add_parameters(parameters): + parameters.add_str( + display_name="Partial Tip Configuration", + variable_name="partial_tip_config_key", + default="ninety_six_single_back_left", + description="Partial tip configurations described by pickup nozzle and tip count", + choices=[ # value of each choice maps to the key of the partial tip config dataclass we defined + {"display_name": "96 SINGLE nozzle H12", "value": "ninety_six_single_back_left"}, + {"display_name": "96 SINGLE nozzle H1", "value": "ninety_six_single_back_right"}, + {"display_name": "96 SINGLE nozzle A12", "value": "ninety_six_single_front_left"}, + {"display_name": "96 SINGLE nozzle A1", "value": "ninety_six_single_front_right"}, + {"display_name": "96 COLUMN 1", "value": "ninety_six_column_left"}, + {"display_name": "96 COLUMN 12", "value": "ninety_six_column_right"}, + {"display_name": "96 ROW A", "value": "ninety_six_row_back"}, + {"display_name": "96 ROW H", "value": "ninety_six_row_front"}, + {"display_name": "8 SINGLE", "value": "eight_single"}, + {"display_name": "8 PARTIAL 2 tips", "value": "eight_partial_back_2_tips"}, + {"display_name": "8 PARTIAL 3 tips", "value": "eight_partial_back_3_tips"}, + {"display_name": "8 PARTIAL 4 tips", "value": "eight_partial_back_4_tips"}, + {"display_name": "8 PARTIAL 5 tips", "value": "eight_partial_back_5_tips"}, + {"display_name": "8 PARTIAL 6 tips", "value": "eight_partial_back_6_tips"}, + {"display_name": "8 PARTIAL 7 tips", "value": "eight_partial_back_7_tips"}, + {"display_name": "No Partial tip config", "value": "no_tip_config"}, + ], + ) + + parameters.add_str( + display_name="Pipette", + variable_name="pipette_load_name", + choices=[ + { + "display_name": "50µl single channel", + "value": "flex_1channel_50", + }, + { + "display_name": "1000µl single channel", + "value": "flex_1channel_1000", + }, + { + "display_name": "50µl 8 channel", + "value": "flex_8channel_50", + }, + { + "display_name": "1000µl 8 channel", + "value": "flex_8channel_1000", + }, + { + "display_name": "96-Channel Pipette", + "value": "flex_96channel_1000", + }, + ], + default="flex_96channel_1000", + description="Select the pipette type", + ) + + parameters.add_str( + display_name="Tip Rack", + variable_name="tiprack_load_name", + choices=[ + { + "display_name": "1000µl Filter Tip Rack", + "value": "opentrons_flex_96_filtertiprack_1000ul", + }, + { + "display_name": "1000µl Standard Tip Rack", + "value": "opentrons_flex_96_tiprack_1000ul", + }, + { + "display_name": "200µl Standard Tip Rack", + "value": "opentrons_flex_96_tiprack_200ul", + }, + { + "display_name": "200µl Filter Tip Rack", + "value": "opentrons_flex_96_filtertiprack_200ul", + }, + { + "display_name": "50µl Filter Tip Rack", + "value": "opentrons_flex_96_filtertiprack_50ul", + }, + { + "display_name": "50µl Standard Tip Rack", + "value": "opentrons_flex_96_tiprack_50ul", + }, + ], + default="opentrons_flex_96_tiprack_1000ul", + description="Select the tip rack type", + ) + + parameters.add_str( + display_name="Pipette Mount", + variable_name="pipette_mount", + choices=[ + {"display_name": "left", "value": "left"}, + {"display_name": "right", "value": "right"}, + ], + default="left", + description="Select the pipette mount.", + ) + + parameters.add_str( + display_name="Reservoir A", + variable_name="reservoir_a_load_name", + choices=reservoir_choices, + default="nest_1_reservoir_290ml", + description="Select the reservoir type", + ) + + parameters.add_str( + display_name="Reservoir B", + variable_name="reservoir_b_load_name", + choices=reservoir_choices, + default="nest_1_reservoir_290ml", + description="Select the reservoir type", + ) + + parameters.add_str( + display_name="Tiprack Position", + variable_name="tiprack_position", + default="B2", + description="Select the position of the tiprack", + choices=position_choices, + ) + + parameters.add_str( + display_name="Reservoir A Position", + variable_name="reservoir_a_position", + default="C1", + description="Select the position of reservoir A", + choices=position_choices, + ) + + parameters.add_str( + display_name="Reservoir B Position", + variable_name="reservoir_b_position", + default="D1", + description="Select the position of reservoir B", + choices=position_choices, + ) + + +####### END RTP DEFINITIONS ####### + + +def set_configure_nozzle_layout(ctx, pipette, tipracks, tip_config): + """Convenience function to set the nozzle layout of a pipette + with the given tip config we have mapped to a RTP.""" + ctx.comment(f"Setting nozzle layout for {pipette}") + ctx.comment(f"Tip config: {tip_config}") + if tip_config.api_end: + pipette.configure_nozzle_layout( + style=tip_config.api_tip_config, start=tip_config.api_start, end=tip_config.api_end, tip_racks=tipracks + ) + else: + pipette.configure_nozzle_layout(style=tip_config.api_tip_config, start=tip_config.api_start, tip_racks=tipracks) + + +def comment_tip_rack_status(ctx, tip_rack): + """ + Print out the tip status for each row in a tip rack. + Each row (A-H) will print the well statuses for columns 1-12 in a single comment, + with a '🟢' for present tips and a '❌' for missing tips. + """ + range_A_to_H = [chr(i) for i in range(ord("A"), ord("H") + 1)] + range_1_to_12 = range(1, 13) + + ctx.comment(f"Tip rack in {tip_rack.parent}") + + for row in range_A_to_H: + status_line = f"{row}: " + for col in range_1_to_12: + well = f"{row}{col}" + has_tip = tip_rack.wells_by_name()[well].has_tip + status_emoji = "🟢" if has_tip else "❌" + status_line += f"{well} {status_emoji} " + + # Print the full status line for the row + ctx.comment(status_line) + + +def run(ctx): + trash = ctx.load_trash_bin("A3") # must load trash bin + # get the key from the parameters + tip_config = find_partial_tip_config(ctx.params.partial_tip_config_key) + pipette_load_name = ctx.params.pipette_load_name + tiprack_load_name = ctx.params.tiprack_load_name + tip_rack_position = ctx.params.tiprack_position + pipette_mount = ctx.params.pipette_mount + reservoir_a_load_name = ctx.params.reservoir_a_load_name + reservoir_b_load_name = ctx.params.reservoir_b_load_name + reservoir_a_position = ctx.params.reservoir_a_position + reservoir_b_position = ctx.params.reservoir_b_position + # print out the tip config + ctx.comment(f"Running with {tip_config}") + ctx.comment(f"Using pipette {pipette_load_name}") + ctx.comment(f"Using tip rack {tiprack_load_name}") + ctx.comment(f"Using pipette mount {pipette_mount}") + ctx.comment(f"Using reservoir A {reservoir_a_load_name}") + ctx.comment(f"Using reservoir B {reservoir_b_load_name}") + ctx.comment(f"Using reservoir A position {reservoir_a_position}") + ctx.comment(f"Using reservoir B position {reservoir_b_position}") + # load the labware + reservoir_a = ctx.load_labware(reservoir_a_load_name, reservoir_a_position) + reservoir_b = ctx.load_labware(reservoir_b_load_name, reservoir_b_position) + # example code on Flex for a pipette + # comment shows we picked up the tips we expected + if tip_config.key == "no_tip_config" and pipette_load_name == "flex_96channel_1000": + tip_rack = ctx.load_labware(tiprack_load_name, tip_rack_position, adapter="opentrons_flex_96_tiprack_adapter") + else: + tip_rack = ctx.load_labware(tiprack_load_name, tip_rack_position) + pipette = ctx.load_instrument(pipette_load_name, pipette_mount) + # use this convenience function to set the nozzle layout + set_configure_nozzle_layout(ctx=ctx, pipette=pipette, tipracks=[tip_rack], tip_config=tip_config) + + def how_much_to_pipette(tiprack_load_name): + if "50" in tiprack_load_name: + return 20 + else: + return 100 + + volume = how_much_to_pipette(tiprack_load_name) + + pipette.pick_up_tip() + comment_tip_rack_status(ctx=ctx, tip_rack=tip_rack) + ctx.comment("aspirate from reservoir A") + pipette.aspirate(volume=volume, location=reservoir_a.wells()[0]) + ctx.comment("dispense to reservoir B") + pipette.dispense(volume=volume, location=reservoir_b.wells()[0]) + ctx.comment("mixing in reservoir B") + pipette.mix(repetitions=3, volume=volume / 2) + ctx.comment("Aspirate from reservoir A") + pipette.aspirate(volume=volume, location=reservoir_a.wells()[0]) + ctx.comment("Blow out in reservoir A") + pipette.blow_out() + ctx.comment("Aspirate from reservoir B") + pipette.aspirate(volume=volume, location=reservoir_b.wells()[0]) + ctx.comment("air_gap with no argument in reservoir B") + pipette.air_gap() + pipette.drop_tip() diff --git a/analyses-snapshot-testing/mypy.ini b/analyses-snapshot-testing/mypy.ini index cab126eb42d..d5e1e97f945 100644 --- a/analyses-snapshot-testing/mypy.ini +++ b/analyses-snapshot-testing/mypy.ini @@ -7,7 +7,7 @@ disallow_any_generics = true check_untyped_defs = true no_implicit_reexport = true exclude = "__init__.py" -python_version = 3.12 +python_version = 3.13 plugins = pydantic.mypy [pydantic-mypy] diff --git a/analyses-snapshot-testing/pyproject.toml b/analyses-snapshot-testing/pyproject.toml index 4d4c9cf166c..9ea01a884cb 100644 --- a/analyses-snapshot-testing/pyproject.toml +++ b/analyses-snapshot-testing/pyproject.toml @@ -1,13 +1,13 @@ [tool.black] line-length = 140 -target-version = ['py312'] +target-version = ['py313'] [tool.ruff] # Like Black line-length = 140 # Like Black indent-width = 4 -target-version = "py312" +target-version = "py313" exclude = ["files"] src = ["*.py", "automation", "tests", "citools"] diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[004ebb2b82][OT2_S_v2_11_P10S_P300M_MM_TC1_TM_Swift].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[004ebb2b82][OT2_S_v2_11_P10S_P300M_MM_TC1_TM_Swift].json index f84466f2f28..f59c9684e23 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[004ebb2b82][OT2_S_v2_11_P10S_P300M_MM_TC1_TM_Swift].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[004ebb2b82][OT2_S_v2_11_P10S_P300M_MM_TC1_TM_Swift].json @@ -6914,7 +6914,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6947,7 +6948,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -6979,7 +6981,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7011,7 +7014,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7044,7 +7048,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7077,7 +7082,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7110,7 +7116,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7172,7 +7179,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7268,7 +7276,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7301,7 +7310,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -7333,7 +7343,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -7365,7 +7376,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -7398,7 +7410,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -7431,7 +7444,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -7464,7 +7478,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -7526,7 +7541,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -7622,7 +7638,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7655,7 +7672,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -7687,7 +7705,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -7719,7 +7738,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -7752,7 +7772,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -7785,7 +7806,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -7818,7 +7840,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -7880,7 +7903,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -7976,7 +8000,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8009,7 +8034,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -8041,7 +8067,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -8073,7 +8100,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -8106,7 +8134,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -8139,7 +8168,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -8172,7 +8202,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -8234,7 +8265,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -8330,7 +8362,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8363,7 +8396,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -8395,7 +8429,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -8427,7 +8462,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -8460,7 +8496,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -8493,7 +8530,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -8526,7 +8564,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -8588,7 +8627,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -8684,7 +8724,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8717,7 +8758,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -8749,7 +8791,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -8781,7 +8824,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -8814,7 +8858,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -8847,7 +8892,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -8880,7 +8926,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -8942,7 +8989,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -9038,7 +9086,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9071,7 +9120,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -9103,7 +9153,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -9135,7 +9186,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -9168,7 +9220,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -9201,7 +9254,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -9234,7 +9288,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -9296,7 +9351,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -9392,7 +9448,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9425,7 +9482,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -9457,7 +9515,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -9489,7 +9548,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -9522,7 +9582,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -9555,7 +9616,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -9588,7 +9650,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -9650,7 +9713,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -9866,7 +9930,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9899,7 +9964,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9931,7 +9997,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10027,7 +10094,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10060,7 +10128,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10093,7 +10162,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10126,7 +10196,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10159,7 +10230,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10207,7 +10279,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10239,7 +10312,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10271,7 +10345,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10304,7 +10379,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10337,7 +10413,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10370,7 +10447,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10402,7 +10480,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10513,7 +10592,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10546,7 +10626,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10594,7 +10675,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10627,7 +10709,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -10724,7 +10807,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10866,7 +10950,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10899,7 +10984,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10931,7 +11017,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11042,7 +11129,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -11075,7 +11163,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11107,7 +11196,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11203,7 +11293,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -11236,7 +11327,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -11268,7 +11360,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -11364,7 +11457,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -11397,7 +11491,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -11429,7 +11524,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -11525,7 +11621,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -11558,7 +11655,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -11590,7 +11688,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -11686,7 +11785,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -11719,7 +11819,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -11751,7 +11852,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -11847,7 +11949,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -11880,7 +11983,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -11912,7 +12016,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -12008,7 +12113,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -12041,7 +12147,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -12073,7 +12180,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -12169,7 +12277,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -12202,7 +12311,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -12234,7 +12344,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -12330,7 +12441,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12363,7 +12475,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12396,7 +12509,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12429,7 +12543,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12526,7 +12641,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12559,7 +12675,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12591,7 +12708,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12687,7 +12805,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12720,7 +12839,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12753,7 +12873,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12786,7 +12907,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12819,7 +12941,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12852,7 +12975,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12884,7 +13008,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12980,7 +13105,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13013,7 +13139,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -13045,7 +13172,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -13141,7 +13269,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13174,7 +13303,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -13207,7 +13337,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -13240,7 +13371,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -13273,7 +13405,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -13306,7 +13439,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -13338,7 +13472,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -13434,7 +13569,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13467,7 +13603,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -13499,7 +13636,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -13595,7 +13733,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13628,7 +13767,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -13661,7 +13801,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -13694,7 +13835,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -13727,7 +13869,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -13760,7 +13903,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -13792,7 +13936,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -13888,7 +14033,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13921,7 +14067,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -13953,7 +14100,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -14049,7 +14197,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14082,7 +14231,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -14115,7 +14265,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -14148,7 +14299,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -14181,7 +14333,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -14214,7 +14367,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -14246,7 +14400,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -14342,7 +14497,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14375,7 +14531,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -14407,7 +14564,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -14503,7 +14661,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14536,7 +14695,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -14569,7 +14729,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -14602,7 +14763,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -14635,7 +14797,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -14668,7 +14831,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -14700,7 +14864,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -14796,7 +14961,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14829,7 +14995,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -14861,7 +15028,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -14957,7 +15125,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14990,7 +15159,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15023,7 +15193,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15056,7 +15227,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15089,7 +15261,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15122,7 +15295,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15154,7 +15328,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15250,7 +15425,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15283,7 +15459,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -15315,7 +15492,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -15411,7 +15589,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15444,7 +15623,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -15477,7 +15657,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -15510,7 +15691,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -15543,7 +15725,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -15576,7 +15759,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -15608,7 +15792,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -15704,7 +15889,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15737,7 +15923,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -15769,7 +15956,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -15865,7 +16053,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15898,7 +16087,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -15931,7 +16121,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -15964,7 +16155,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -15997,7 +16189,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -16030,7 +16223,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -16062,7 +16256,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -16248,7 +16443,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16281,7 +16477,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16313,7 +16510,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16470,6 +16668,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.11" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[00574c503f][pl_BacteriaInoculation_Flex_6plates].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[00574c503f][pl_BacteriaInoculation_Flex_6plates].json index 0369557eec4..05fa920a764 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[00574c503f][pl_BacteriaInoculation_Flex_6plates].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[00574c503f][pl_BacteriaInoculation_Flex_6plates].json @@ -4543,7 +4543,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -4575,7 +4576,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -4607,7 +4609,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -4640,7 +4643,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4673,7 +4677,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4706,7 +4711,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -4738,7 +4744,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -4770,7 +4777,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -4803,7 +4811,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4836,7 +4845,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -4869,7 +4879,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -4901,7 +4912,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -4933,7 +4945,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -4965,7 +4978,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -4997,7 +5011,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -5030,7 +5045,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5063,7 +5079,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -5096,7 +5113,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -5128,7 +5146,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -5160,7 +5179,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -5192,7 +5212,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -5224,7 +5245,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -5257,7 +5279,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5290,7 +5313,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -5323,7 +5347,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -5355,7 +5380,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -5387,7 +5413,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5421,7 +5448,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5454,7 +5482,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5488,7 +5517,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5521,7 +5551,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5555,7 +5586,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5588,7 +5620,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5622,7 +5655,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5655,7 +5689,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5688,7 +5723,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5721,7 +5757,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5755,7 +5792,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5788,7 +5826,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5822,7 +5861,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5855,7 +5895,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5889,7 +5930,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5922,7 +5964,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5956,7 +5999,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5989,7 +6033,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6022,7 +6067,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6054,7 +6100,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6086,7 +6133,8 @@ "y": 0.0, "z": -115.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6118,7 +6166,8 @@ "y": 0.0, "z": 5.000000000000014 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6150,7 +6199,8 @@ "y": 0.0, "z": 5.000000000000014 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6183,7 +6233,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -6215,7 +6266,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -6247,7 +6299,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -6280,7 +6333,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6313,7 +6367,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6347,7 +6402,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6380,7 +6436,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6414,7 +6471,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6447,7 +6505,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6481,7 +6540,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6514,7 +6574,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6548,7 +6609,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6581,7 +6643,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6614,7 +6677,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6646,7 +6710,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6678,7 +6743,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6711,7 +6777,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -6744,7 +6811,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -6778,7 +6846,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -6811,7 +6880,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -6845,7 +6915,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -6878,7 +6949,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -6912,7 +6984,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -6945,7 +7018,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -6979,7 +7053,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -7012,7 +7087,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -7045,7 +7121,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -7077,7 +7154,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -7109,7 +7187,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -7142,7 +7221,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7175,7 +7255,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7209,7 +7290,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7242,7 +7324,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7276,7 +7359,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7309,7 +7393,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7343,7 +7428,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7376,7 +7462,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7410,7 +7497,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7443,7 +7531,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7476,7 +7565,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7508,7 +7598,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7540,7 +7631,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7573,7 +7665,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -7606,7 +7699,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -7640,7 +7734,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -7673,7 +7768,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -7707,7 +7803,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -7740,7 +7837,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -7774,7 +7872,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -7807,7 +7906,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -7841,7 +7941,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -7874,7 +7975,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -7907,7 +8009,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -7939,7 +8042,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -7971,7 +8075,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -8004,7 +8109,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -8037,7 +8143,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -8071,7 +8178,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -8104,7 +8212,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -8138,7 +8247,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -8171,7 +8281,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -8205,7 +8316,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -8238,7 +8350,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -8272,7 +8385,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -8305,7 +8419,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -8338,7 +8453,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -8370,7 +8486,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -8401,7 +8518,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -8433,7 +8551,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -8542,7 +8661,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -8574,7 +8694,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -8606,7 +8727,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -8639,7 +8761,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8672,7 +8795,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8705,7 +8829,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -8737,7 +8862,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -8769,7 +8895,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -8802,7 +8929,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8835,7 +8963,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -8868,7 +8997,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -8900,7 +9030,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -8932,7 +9063,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -8964,7 +9096,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -8996,7 +9129,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -9029,7 +9163,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9062,7 +9197,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -9095,7 +9231,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -9127,7 +9264,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -9159,7 +9297,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -9191,7 +9330,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -9223,7 +9363,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -9256,7 +9397,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9289,7 +9431,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -9322,7 +9465,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -9354,7 +9498,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -9386,7 +9531,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9420,7 +9566,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9453,7 +9600,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9487,7 +9635,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9520,7 +9669,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9554,7 +9704,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9587,7 +9738,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9621,7 +9773,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9654,7 +9807,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9687,7 +9841,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9720,7 +9875,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9754,7 +9910,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9787,7 +9944,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9821,7 +9979,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9854,7 +10013,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9888,7 +10048,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9921,7 +10082,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9955,7 +10117,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9988,7 +10151,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -10021,7 +10185,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -10053,7 +10218,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -10085,7 +10251,8 @@ "y": 0.0, "z": -115.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -10117,7 +10284,8 @@ "y": 0.0, "z": 5.000000000000014 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -10149,7 +10317,8 @@ "y": 0.0, "z": 5.000000000000014 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -10182,7 +10351,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -10214,7 +10384,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -10246,7 +10417,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -10279,7 +10451,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -10312,7 +10485,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -10346,7 +10520,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -10379,7 +10554,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -10413,7 +10589,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -10446,7 +10623,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -10480,7 +10658,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -10513,7 +10692,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -10547,7 +10727,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -10580,7 +10761,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -10613,7 +10795,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -10645,7 +10828,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -10677,7 +10861,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -10710,7 +10895,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -10743,7 +10929,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -10777,7 +10964,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -10810,7 +10998,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -10844,7 +11033,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -10877,7 +11067,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -10911,7 +11102,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -10944,7 +11136,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -10978,7 +11171,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -11011,7 +11205,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -11044,7 +11239,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -11076,7 +11272,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -11108,7 +11305,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -11141,7 +11339,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -11174,7 +11373,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -11208,7 +11408,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -11241,7 +11442,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -11275,7 +11477,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -11308,7 +11511,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -11342,7 +11546,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -11375,7 +11580,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -11409,7 +11615,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -11442,7 +11649,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -11475,7 +11683,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -11507,7 +11716,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -11539,7 +11749,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -11572,7 +11783,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -11605,7 +11817,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -11639,7 +11852,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -11672,7 +11886,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -11706,7 +11921,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -11739,7 +11955,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -11773,7 +11990,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -11806,7 +12024,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -11840,7 +12059,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -11873,7 +12093,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -11906,7 +12127,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -11938,7 +12160,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -11970,7 +12193,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -12003,7 +12227,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -12036,7 +12261,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -12070,7 +12296,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -12103,7 +12330,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -12137,7 +12365,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -12170,7 +12399,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -12204,7 +12434,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -12237,7 +12468,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -12271,7 +12503,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -12304,7 +12537,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -12337,7 +12571,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -12369,7 +12604,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -12400,7 +12636,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -12432,7 +12669,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -12541,7 +12779,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -12573,7 +12812,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -12605,7 +12845,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -12638,7 +12879,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12671,7 +12913,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12704,7 +12947,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -12736,7 +12980,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -12768,7 +13013,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -12801,7 +13047,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12834,7 +13081,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -12867,7 +13115,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -12899,7 +13148,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -12931,7 +13181,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -12963,7 +13214,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -12995,7 +13247,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -13028,7 +13281,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13061,7 +13315,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -13094,7 +13349,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -13126,7 +13382,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -13158,7 +13415,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -13190,7 +13448,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -13222,7 +13481,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -13255,7 +13515,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13288,7 +13549,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -13321,7 +13583,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -13353,7 +13616,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -13385,7 +13649,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13419,7 +13684,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13452,7 +13718,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13486,7 +13753,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13519,7 +13787,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13553,7 +13822,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13586,7 +13856,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13620,7 +13891,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13653,7 +13925,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13686,7 +13959,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13719,7 +13993,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13753,7 +14028,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13786,7 +14062,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13820,7 +14097,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13853,7 +14131,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13887,7 +14166,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13920,7 +14200,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13954,7 +14235,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13987,7 +14269,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -14020,7 +14303,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -14052,7 +14336,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -14084,7 +14369,8 @@ "y": 0.0, "z": -115.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -14116,7 +14402,8 @@ "y": 0.0, "z": 5.000000000000014 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -14148,7 +14435,8 @@ "y": 0.0, "z": 5.000000000000014 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -14181,7 +14469,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -14213,7 +14502,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -14245,7 +14535,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -14278,7 +14569,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -14311,7 +14603,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -14345,7 +14638,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -14378,7 +14672,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -14412,7 +14707,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -14445,7 +14741,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -14479,7 +14776,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -14512,7 +14810,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -14546,7 +14845,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -14579,7 +14879,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -14612,7 +14913,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -14644,7 +14946,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -14676,7 +14979,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -14709,7 +15013,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -14742,7 +15047,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -14776,7 +15082,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -14809,7 +15116,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -14843,7 +15151,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -14876,7 +15185,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -14910,7 +15220,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -14943,7 +15254,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -14977,7 +15289,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -15010,7 +15323,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -15043,7 +15357,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -15075,7 +15390,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -15107,7 +15423,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -15140,7 +15457,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -15173,7 +15491,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -15207,7 +15526,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -15240,7 +15560,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -15274,7 +15595,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -15307,7 +15629,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -15341,7 +15664,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -15374,7 +15698,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -15408,7 +15733,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -15441,7 +15767,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -15474,7 +15801,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -15506,7 +15834,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -15538,7 +15867,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -15571,7 +15901,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15604,7 +15935,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15638,7 +15970,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15671,7 +16004,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15705,7 +16039,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15738,7 +16073,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15772,7 +16108,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15805,7 +16142,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15839,7 +16177,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15872,7 +16211,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15905,7 +16245,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15937,7 +16278,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -15969,7 +16311,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -16002,7 +16345,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -16035,7 +16379,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -16069,7 +16414,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -16102,7 +16448,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -16136,7 +16483,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -16169,7 +16517,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -16203,7 +16552,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -16236,7 +16586,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -16270,7 +16621,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -16303,7 +16655,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -16336,7 +16689,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -16368,7 +16722,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -16399,7 +16754,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -16431,7 +16787,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -16540,7 +16897,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -16572,7 +16930,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -16604,7 +16963,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -16637,7 +16997,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16670,7 +17031,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16703,7 +17065,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -16735,7 +17098,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -16767,7 +17131,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -16800,7 +17165,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16833,7 +17199,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -16866,7 +17233,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -16898,7 +17266,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -16930,7 +17299,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -16962,7 +17332,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -16994,7 +17365,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -17027,7 +17399,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17060,7 +17433,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -17093,7 +17467,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -17125,7 +17500,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -17157,7 +17533,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -17189,7 +17566,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -17221,7 +17599,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -17254,7 +17633,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17287,7 +17667,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -17320,7 +17701,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -17352,7 +17734,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -17384,7 +17767,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17418,7 +17802,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17451,7 +17836,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17485,7 +17871,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17518,7 +17905,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17552,7 +17940,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17585,7 +17974,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17619,7 +18009,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17652,7 +18043,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17685,7 +18077,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17718,7 +18111,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17752,7 +18146,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17785,7 +18180,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17819,7 +18215,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17852,7 +18249,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17886,7 +18284,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17919,7 +18318,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17953,7 +18353,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17986,7 +18387,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18019,7 +18421,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18051,7 +18454,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18083,7 +18487,8 @@ "y": 0.0, "z": -115.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18115,7 +18520,8 @@ "y": 0.0, "z": 5.000000000000014 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18147,7 +18553,8 @@ "y": 0.0, "z": 5.000000000000014 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18180,7 +18587,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -18212,7 +18620,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -18244,7 +18653,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -18277,7 +18687,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18310,7 +18721,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18344,7 +18756,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18377,7 +18790,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18411,7 +18825,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18444,7 +18859,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18478,7 +18894,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18511,7 +18928,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18545,7 +18963,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18578,7 +18997,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18611,7 +19031,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18643,7 +19064,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18675,7 +19097,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18708,7 +19131,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -18741,7 +19165,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -18775,7 +19200,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -18808,7 +19234,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -18842,7 +19269,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -18875,7 +19303,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -18909,7 +19338,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -18942,7 +19372,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -18976,7 +19407,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -19009,7 +19441,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -19042,7 +19475,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -19074,7 +19508,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -19106,7 +19541,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -19139,7 +19575,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19172,7 +19609,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19206,7 +19644,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19239,7 +19678,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19273,7 +19713,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19306,7 +19747,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19340,7 +19782,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19373,7 +19816,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19407,7 +19851,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19440,7 +19885,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19473,7 +19919,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19505,7 +19952,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19537,7 +19985,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19570,7 +20019,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -19603,7 +20053,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -19637,7 +20088,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -19670,7 +20122,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -19704,7 +20157,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -19737,7 +20191,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -19771,7 +20226,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -19804,7 +20260,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -19838,7 +20295,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -19871,7 +20329,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -19904,7 +20363,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -19936,7 +20396,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -19968,7 +20429,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -20001,7 +20463,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20034,7 +20497,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20068,7 +20532,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20101,7 +20566,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20135,7 +20601,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20168,7 +20635,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20202,7 +20670,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20235,7 +20704,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20269,7 +20739,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20302,7 +20773,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20335,7 +20807,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20367,7 +20840,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20398,7 +20872,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20430,7 +20905,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20539,7 +21015,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -20571,7 +21048,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -20603,7 +21081,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -20636,7 +21115,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20669,7 +21149,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20702,7 +21183,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -20734,7 +21216,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -20766,7 +21249,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -20799,7 +21283,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20832,7 +21317,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -20865,7 +21351,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -20897,7 +21384,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -20929,7 +21417,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -20961,7 +21450,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -20993,7 +21483,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -21026,7 +21517,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21059,7 +21551,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -21092,7 +21585,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -21124,7 +21618,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -21156,7 +21651,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -21188,7 +21684,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -21220,7 +21717,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -21253,7 +21751,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21286,7 +21785,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -21319,7 +21819,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -21351,7 +21852,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -21383,7 +21885,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21417,7 +21920,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21450,7 +21954,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21484,7 +21989,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21517,7 +22023,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21551,7 +22058,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21584,7 +22092,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21618,7 +22127,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21651,7 +22161,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21684,7 +22195,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21717,7 +22229,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21751,7 +22264,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21784,7 +22298,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21818,7 +22333,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21851,7 +22367,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21885,7 +22402,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21918,7 +22436,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21952,7 +22471,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21985,7 +22505,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -22018,7 +22539,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -22050,7 +22572,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -22082,7 +22605,8 @@ "y": 0.0, "z": -115.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -22114,7 +22638,8 @@ "y": 0.0, "z": 5.000000000000014 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -22146,7 +22671,8 @@ "y": 0.0, "z": 5.000000000000014 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -22179,7 +22705,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -22211,7 +22738,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -22243,7 +22771,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -22276,7 +22805,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22309,7 +22839,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22343,7 +22874,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22376,7 +22908,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22410,7 +22943,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22443,7 +22977,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22477,7 +23012,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22510,7 +23046,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22544,7 +23081,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22577,7 +23115,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22610,7 +23149,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22642,7 +23182,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22674,7 +23215,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22707,7 +23249,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -22740,7 +23283,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -22774,7 +23318,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -22807,7 +23352,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -22841,7 +23387,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -22874,7 +23421,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -22908,7 +23456,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -22941,7 +23490,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -22975,7 +23525,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -23008,7 +23559,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -23041,7 +23593,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -23073,7 +23626,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -23105,7 +23659,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -23138,7 +23693,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -23171,7 +23727,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -23205,7 +23762,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -23238,7 +23796,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -23272,7 +23831,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -23305,7 +23865,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -23339,7 +23900,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -23372,7 +23934,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -23406,7 +23969,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -23439,7 +24003,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -23472,7 +24037,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -23504,7 +24070,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -23536,7 +24103,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -23569,7 +24137,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -23602,7 +24171,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -23636,7 +24206,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -23669,7 +24240,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -23703,7 +24275,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -23736,7 +24309,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -23770,7 +24344,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -23803,7 +24378,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -23837,7 +24413,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -23870,7 +24447,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -23903,7 +24481,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -23935,7 +24514,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -23967,7 +24547,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -24000,7 +24581,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -24033,7 +24615,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -24067,7 +24650,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -24100,7 +24684,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -24134,7 +24719,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -24167,7 +24753,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -24201,7 +24788,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -24234,7 +24822,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -24268,7 +24857,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -24301,7 +24891,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -24334,7 +24925,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -24366,7 +24958,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -24397,7 +24990,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -24429,7 +25023,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -24538,7 +25133,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -24570,7 +25166,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -24602,7 +25199,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -24635,7 +25233,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24668,7 +25267,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24701,7 +25301,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -24733,7 +25334,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -24765,7 +25367,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -24798,7 +25401,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24831,7 +25435,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -24864,7 +25469,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -24896,7 +25502,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -24928,7 +25535,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -24960,7 +25568,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -24992,7 +25601,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -25025,7 +25635,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -25058,7 +25669,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -25091,7 +25703,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -25123,7 +25736,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -25155,7 +25769,8 @@ "y": 0.0, "z": -111.05 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -25187,7 +25802,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -25219,7 +25835,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -25252,7 +25869,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -25285,7 +25903,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -25318,7 +25937,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -25350,7 +25970,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -25382,7 +26003,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25416,7 +26038,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25449,7 +26072,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25483,7 +26107,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25516,7 +26141,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25550,7 +26176,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25583,7 +26210,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25617,7 +26245,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25650,7 +26279,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25683,7 +26313,8 @@ "y": 0.0, "z": -112.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25716,7 +26347,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25750,7 +26382,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25783,7 +26416,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25817,7 +26451,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25850,7 +26485,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25884,7 +26520,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25917,7 +26554,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25951,7 +26589,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -25984,7 +26623,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -26017,7 +26657,8 @@ "y": 0.0, "z": -107.79999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -26049,7 +26690,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -26081,7 +26723,8 @@ "y": 0.0, "z": -115.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -26113,7 +26756,8 @@ "y": 0.0, "z": 5.000000000000014 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -26145,7 +26789,8 @@ "y": 0.0, "z": 5.000000000000014 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -26178,7 +26823,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -26210,7 +26856,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -26242,7 +26889,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -26275,7 +26923,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -26308,7 +26957,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -26342,7 +26992,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -26375,7 +27026,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -26409,7 +27061,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -26442,7 +27095,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -26476,7 +27130,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -26509,7 +27164,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -26543,7 +27199,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -26576,7 +27233,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -26609,7 +27267,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -26641,7 +27300,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -26673,7 +27333,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -26706,7 +27367,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -26739,7 +27401,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -26773,7 +27436,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -26806,7 +27470,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -26840,7 +27505,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -26873,7 +27539,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -26907,7 +27574,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -26940,7 +27608,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -26974,7 +27643,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -27007,7 +27677,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -27040,7 +27711,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -27072,7 +27744,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -27104,7 +27777,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -27137,7 +27811,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -27170,7 +27845,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -27204,7 +27880,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -27237,7 +27914,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -27271,7 +27949,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -27304,7 +27983,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -27338,7 +28018,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -27371,7 +28052,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -27405,7 +28087,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -27438,7 +28121,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -27471,7 +28155,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -27503,7 +28188,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -27535,7 +28221,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -27568,7 +28255,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -27601,7 +28289,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -27635,7 +28324,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -27668,7 +28358,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -27702,7 +28393,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -27735,7 +28427,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -27769,7 +28462,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -27802,7 +28496,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -27836,7 +28531,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -27869,7 +28565,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -27902,7 +28599,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -27934,7 +28632,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -27966,7 +28665,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -27999,7 +28699,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -28032,7 +28733,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -28066,7 +28768,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -28099,7 +28802,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -28133,7 +28837,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -28166,7 +28871,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -28200,7 +28906,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -28233,7 +28940,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -28267,7 +28975,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -28300,7 +29009,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -28333,7 +29043,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -28365,7 +29076,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -28396,7 +29108,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -28428,7 +29141,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -29877,7 +30591,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29911,7 +30626,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29944,7 +30660,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29977,7 +30694,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30010,7 +30728,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30056,7 +30775,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30088,7 +30808,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30121,7 +30842,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30154,7 +30876,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30200,7 +30923,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30232,7 +30956,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -31901,7 +32626,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31935,7 +32661,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31968,7 +32695,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32001,7 +32729,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32034,7 +32763,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32080,7 +32810,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32112,7 +32843,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32145,7 +32877,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32178,7 +32911,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32224,7 +32958,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32256,7 +32991,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33925,7 +34661,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33959,7 +34696,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33992,7 +34730,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -34025,7 +34764,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -34058,7 +34798,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -34104,7 +34845,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -34136,7 +34878,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -34169,7 +34912,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34202,7 +34946,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34248,7 +34993,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34280,7 +35026,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35949,7 +36696,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -35983,7 +36731,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36016,7 +36765,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36049,7 +36799,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36082,7 +36833,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36128,7 +36880,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36160,7 +36913,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36193,7 +36947,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36226,7 +36981,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36272,7 +37028,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36304,7 +37061,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37973,7 +38731,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38007,7 +38766,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38040,7 +38800,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38073,7 +38834,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38106,7 +38868,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38152,7 +38915,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38184,7 +38948,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38217,7 +38982,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38250,7 +39016,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38296,7 +39063,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38328,7 +39096,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -39997,7 +40766,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40031,7 +40801,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40064,7 +40835,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40097,7 +40869,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40130,7 +40903,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40176,7 +40950,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40208,7 +40983,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40241,7 +41017,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40274,7 +41051,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40320,7 +41098,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40352,7 +41131,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40804,6 +41584,7 @@ "location": "offDeck" } ], + "liquidClasses": [], "liquids": [ { "description": "Bacterial culture medium (e.g., LB broth)", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[011481812b][OT2_S_v2_7_P20S_None_Walkthrough].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[011481812b][OT2_S_v2_7_P20S_None_Walkthrough].json index a40e1549015..c709366a42a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[011481812b][OT2_S_v2_7_P20S_None_Walkthrough].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[011481812b][OT2_S_v2_7_P20S_None_Walkthrough].json @@ -2401,7 +2401,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2434,7 +2435,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2467,7 +2469,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2500,7 +2503,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2533,7 +2537,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2566,7 +2571,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2599,7 +2605,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2632,7 +2639,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2665,7 +2673,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2698,7 +2707,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2731,7 +2741,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2764,7 +2775,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2797,7 +2809,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2830,7 +2843,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2863,7 +2877,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2896,7 +2911,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2929,7 +2945,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2962,7 +2979,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2995,7 +3013,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3043,7 +3062,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3076,7 +3096,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3124,7 +3145,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3157,7 +3179,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3190,7 +3213,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3222,7 +3246,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3254,7 +3279,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3287,7 +3313,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3319,7 +3346,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3351,7 +3379,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3384,7 +3413,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -3416,7 +3446,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3527,7 +3558,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3560,7 +3592,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3608,7 +3641,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3641,7 +3675,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3674,7 +3709,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3722,7 +3758,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3755,7 +3792,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3788,7 +3826,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3836,7 +3875,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3869,7 +3909,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3902,7 +3943,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3935,7 +3977,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3968,7 +4011,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4015,7 +4059,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4111,7 +4156,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4144,7 +4190,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -4177,7 +4224,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -4210,7 +4258,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -4243,7 +4292,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -4276,7 +4326,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -4309,7 +4360,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -4342,7 +4394,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -4375,7 +4428,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -4472,7 +4526,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -4505,7 +4560,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4538,7 +4594,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -4571,7 +4628,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -4604,7 +4662,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -4637,7 +4696,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -4670,7 +4730,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -4703,7 +4764,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -4736,7 +4798,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -4768,7 +4831,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4855,6 +4919,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.7", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[01255c3f3b][pl_Flex_Protein_Digestion_Protocol].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[01255c3f3b][pl_Flex_Protein_Digestion_Protocol].json index 42669f7106f..2da7a9c47bd 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[01255c3f3b][pl_Flex_Protein_Digestion_Protocol].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[01255c3f3b][pl_Flex_Protein_Digestion_Protocol].json @@ -6331,7 +6331,8 @@ "y": 0.0, "z": -34.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6364,7 +6365,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6397,7 +6399,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6431,7 +6434,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6464,7 +6468,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6498,7 +6503,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6531,7 +6537,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6564,7 +6571,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6596,7 +6604,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6628,7 +6637,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7984,7 +7994,8 @@ "y": 0.0, "z": -34.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8017,7 +8028,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8050,7 +8062,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8084,7 +8097,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8117,7 +8131,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8151,7 +8166,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8184,7 +8200,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8217,7 +8234,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8249,7 +8267,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8281,7 +8300,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9637,7 +9657,8 @@ "y": 0.0, "z": -34.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9670,7 +9691,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9703,7 +9725,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9737,7 +9760,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9770,7 +9794,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9804,7 +9829,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9837,7 +9863,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9870,7 +9897,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9902,7 +9930,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9934,7 +9963,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11290,7 +11320,8 @@ "y": 0.0, "z": -34.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11323,7 +11354,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11356,7 +11388,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11390,7 +11423,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11423,7 +11457,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11457,7 +11492,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11490,7 +11526,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11523,7 +11560,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11555,7 +11593,8 @@ "y": 0.0, "z": -34.36 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11587,7 +11626,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11784,6 +11824,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0190369ce5][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1NoFixtures].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0190369ce5][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1NoFixtures].json index 4e09c2731bd..4f1452dcdfc 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0190369ce5][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1NoFixtures].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0190369ce5][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1NoFixtures].json @@ -10725,7 +10725,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10758,7 +10759,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10855,7 +10857,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10888,7 +10891,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11053,7 +11057,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11086,7 +11091,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11183,7 +11189,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11216,7 +11223,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11444,6 +11452,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "High Quality H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0256665840][OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0256665840][OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume].json index f37a5b0fb5c..39491fae6aa 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0256665840][OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0256665840][OT2_S_v2_16_P300M_P20S_aspirateDispenseMix0Volume].json @@ -2686,7 +2686,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2719,7 +2720,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2752,7 +2754,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2785,7 +2788,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2818,7 +2822,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2851,7 +2856,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2911,6 +2917,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[041ad55e7b][OT2_S_v2_15_P300M_P20S_HS_TC_TM_dispense_changes].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[041ad55e7b][OT2_S_v2_15_P300M_P20S_HS_TC_TM_dispense_changes].json index b77ef4b9846..a561da0a387 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[041ad55e7b][OT2_S_v2_15_P300M_P20S_HS_TC_TM_dispense_changes].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[041ad55e7b][OT2_S_v2_15_P300M_P20S_HS_TC_TM_dispense_changes].json @@ -2969,7 +2969,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3002,7 +3003,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3035,7 +3037,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3110,6 +3113,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons Engineering ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[057de2035d][OT2_S_v2_19_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[057de2035d][OT2_S_v2_19_P300M_P20S_HS_TC_TM_SmokeTestV3].json index a73a19e4c88..fe3d81be11b 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[057de2035d][OT2_S_v2_19_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[057de2035d][OT2_S_v2_19_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -9383,7 +9383,14 @@ }, "id": "UUID", "key": "08e16a2cac011d4bef561f8b0854d19e", - "notes": [], + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], "params": { "displayName": "4 custom tubes", "loadName": "cpx_4_tuberack_100ul", @@ -9562,6 +9569,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons Engineering ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0903a95825][Flex_S_v2_19_QIASeq_FX_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0903a95825][Flex_S_v2_19_QIASeq_FX_48x].json index d043cde2da1..f85b03c5703 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0903a95825][Flex_S_v2_19_QIASeq_FX_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0903a95825][Flex_S_v2_19_QIASeq_FX_48x].json @@ -7651,7 +7651,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7685,7 +7686,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7718,7 +7720,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7751,7 +7754,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7784,7 +7788,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7817,7 +7822,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7850,7 +7856,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7883,7 +7890,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7917,7 +7925,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7950,7 +7959,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7984,7 +7994,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8017,7 +8028,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8050,7 +8062,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8082,7 +8095,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8127,7 +8141,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8234,7 +8249,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8268,7 +8284,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8301,7 +8318,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8334,7 +8352,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8367,7 +8386,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8400,7 +8420,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8433,7 +8454,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8466,7 +8488,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8500,7 +8523,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8533,7 +8557,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8567,7 +8592,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8600,7 +8626,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8633,7 +8660,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8665,7 +8693,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8710,7 +8739,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8817,7 +8847,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8851,7 +8882,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8884,7 +8916,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8917,7 +8950,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8950,7 +8984,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8983,7 +9018,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9016,7 +9052,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9049,7 +9086,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9083,7 +9121,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9116,7 +9155,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9150,7 +9190,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9183,7 +9224,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9216,7 +9258,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9248,7 +9291,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9293,7 +9337,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9541,7 +9586,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -9574,7 +9620,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -9607,7 +9654,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9640,7 +9688,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9674,7 +9723,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9707,7 +9757,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9741,7 +9792,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9774,7 +9826,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9807,7 +9860,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9839,7 +9893,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9884,7 +9939,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9991,7 +10047,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -10024,7 +10081,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -10057,7 +10115,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10090,7 +10149,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10124,7 +10184,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10157,7 +10218,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10191,7 +10253,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10224,7 +10287,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10257,7 +10321,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10289,7 +10354,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10334,7 +10400,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10441,7 +10508,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -10474,7 +10542,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -10507,7 +10576,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10540,7 +10610,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10574,7 +10645,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10607,7 +10679,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10641,7 +10714,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10674,7 +10748,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10707,7 +10782,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10739,7 +10815,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10784,7 +10861,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12092,7 +12170,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12126,7 +12205,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12159,7 +12239,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12192,7 +12273,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12225,7 +12307,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12258,7 +12341,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12290,7 +12374,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12336,7 +12421,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12368,7 +12454,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12400,7 +12487,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12434,7 +12522,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12467,7 +12556,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12501,7 +12591,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12534,7 +12625,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12568,7 +12660,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12601,7 +12694,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12635,7 +12729,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12668,7 +12763,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12702,7 +12798,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12735,7 +12832,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12769,7 +12867,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12802,7 +12901,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12836,7 +12936,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12869,7 +12970,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12903,7 +13005,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12936,7 +13039,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12970,7 +13074,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13003,7 +13108,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13036,7 +13142,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13068,7 +13175,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13113,7 +13221,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13220,7 +13329,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13254,7 +13364,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13287,7 +13398,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13320,7 +13432,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13353,7 +13466,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13386,7 +13500,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13418,7 +13533,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13464,7 +13580,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13496,7 +13613,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13528,7 +13646,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13562,7 +13681,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13595,7 +13715,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13629,7 +13750,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13662,7 +13784,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13696,7 +13819,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13729,7 +13853,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13763,7 +13888,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13796,7 +13922,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13830,7 +13957,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13863,7 +13991,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13897,7 +14026,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13930,7 +14060,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13964,7 +14095,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13997,7 +14129,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14031,7 +14164,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14064,7 +14198,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14098,7 +14233,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14131,7 +14267,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14164,7 +14301,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14196,7 +14334,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14241,7 +14380,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14348,7 +14488,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14382,7 +14523,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14415,7 +14557,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14448,7 +14591,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14481,7 +14625,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14514,7 +14659,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14546,7 +14692,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14592,7 +14739,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14624,7 +14772,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14656,7 +14805,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14690,7 +14840,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14723,7 +14874,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14757,7 +14909,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14790,7 +14943,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14824,7 +14978,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14857,7 +15012,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14891,7 +15047,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14924,7 +15081,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14958,7 +15116,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14991,7 +15150,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15025,7 +15185,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15058,7 +15219,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15092,7 +15254,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15125,7 +15288,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15159,7 +15323,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15192,7 +15357,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15226,7 +15392,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15259,7 +15426,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15292,7 +15460,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15324,7 +15493,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15369,7 +15539,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15613,7 +15784,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15647,7 +15819,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15680,7 +15853,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15714,7 +15888,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15747,7 +15922,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15780,7 +15956,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15813,7 +15990,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15846,7 +16024,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15878,7 +16057,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15909,7 +16089,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15940,7 +16121,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15972,7 +16154,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16018,7 +16201,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16050,7 +16234,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16096,7 +16281,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16128,7 +16314,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16161,7 +16348,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16193,7 +16381,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16225,7 +16414,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16257,7 +16447,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16289,7 +16480,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16322,7 +16514,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16354,7 +16547,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16386,7 +16580,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16419,7 +16614,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16451,7 +16647,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16483,7 +16680,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16516,7 +16714,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16548,7 +16747,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16580,7 +16780,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16612,7 +16813,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16657,7 +16859,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16689,7 +16892,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16720,7 +16924,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16751,7 +16956,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16782,7 +16988,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16889,7 +17096,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16923,7 +17131,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16956,7 +17165,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16990,7 +17200,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17023,7 +17234,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17056,7 +17268,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17089,7 +17302,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17122,7 +17336,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17154,7 +17369,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17185,7 +17401,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17216,7 +17433,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17248,7 +17466,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17294,7 +17513,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17326,7 +17546,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17372,7 +17593,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17404,7 +17626,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17437,7 +17660,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17469,7 +17693,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17501,7 +17726,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17533,7 +17759,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17565,7 +17792,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17598,7 +17826,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17630,7 +17859,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17662,7 +17892,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17695,7 +17926,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17727,7 +17959,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17759,7 +17992,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17792,7 +18026,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17824,7 +18059,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17856,7 +18092,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17888,7 +18125,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17933,7 +18171,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17965,7 +18204,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17996,7 +18236,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18027,7 +18268,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18058,7 +18300,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18165,7 +18408,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18199,7 +18443,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18232,7 +18477,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18266,7 +18512,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18299,7 +18546,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18332,7 +18580,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18365,7 +18614,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18398,7 +18648,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18430,7 +18681,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18461,7 +18713,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18492,7 +18745,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18524,7 +18778,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18570,7 +18825,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18602,7 +18858,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18648,7 +18905,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18680,7 +18938,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18713,7 +18972,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18745,7 +19005,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18777,7 +19038,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18809,7 +19071,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18841,7 +19104,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18874,7 +19138,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18906,7 +19171,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18938,7 +19204,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18971,7 +19238,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19003,7 +19271,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19035,7 +19304,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19068,7 +19338,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19100,7 +19371,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19132,7 +19404,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19164,7 +19437,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19209,7 +19483,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19241,7 +19516,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19272,7 +19548,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19303,7 +19580,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19334,7 +19612,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19571,7 +19850,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19603,7 +19883,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19649,7 +19930,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19681,7 +19963,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19713,7 +19996,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19759,7 +20043,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19805,7 +20090,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19836,7 +20122,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19867,7 +20154,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19962,7 +20250,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19994,7 +20283,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20040,7 +20330,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20072,7 +20363,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20104,7 +20396,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20150,7 +20443,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20196,7 +20490,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20227,7 +20522,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20258,7 +20554,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20353,7 +20650,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20385,7 +20683,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20431,7 +20730,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20463,7 +20763,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20495,7 +20796,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20541,7 +20843,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20587,7 +20890,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20618,7 +20922,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20649,7 +20954,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20773,7 +21079,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20805,7 +21112,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20836,7 +21144,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20867,7 +21176,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20898,7 +21208,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20930,7 +21241,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20976,7 +21288,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21007,7 +21320,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21038,7 +21352,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21069,7 +21384,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21190,7 +21506,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21222,7 +21539,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21253,7 +21571,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21284,7 +21603,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21315,7 +21635,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21347,7 +21668,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21393,7 +21715,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21424,7 +21747,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21455,7 +21779,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21486,7 +21811,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21607,7 +21933,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21639,7 +21966,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21670,7 +21998,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21701,7 +22030,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21732,7 +22062,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21764,7 +22095,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21810,7 +22142,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21841,7 +22174,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21872,7 +22206,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21903,7 +22238,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23224,7 +23560,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23256,7 +23593,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23302,7 +23640,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23334,7 +23673,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23366,7 +23706,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23398,7 +23739,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23444,7 +23786,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23490,7 +23833,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23521,7 +23865,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23552,7 +23897,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23583,7 +23929,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23614,7 +23961,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23709,7 +24057,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23741,7 +24090,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23787,7 +24137,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23819,7 +24170,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23851,7 +24203,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23883,7 +24236,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23929,7 +24283,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23975,7 +24330,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24006,7 +24362,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24037,7 +24394,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24068,7 +24426,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24099,7 +24458,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24194,7 +24554,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24226,7 +24587,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24272,7 +24634,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24304,7 +24667,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24336,7 +24700,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24368,7 +24733,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24414,7 +24780,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24460,7 +24827,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24491,7 +24859,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24522,7 +24891,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24553,7 +24923,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24584,7 +24955,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24708,7 +25080,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24740,7 +25113,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24771,7 +25145,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24802,7 +25177,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24833,7 +25209,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24865,7 +25242,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24911,7 +25289,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24942,7 +25321,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24973,7 +25353,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25004,7 +25385,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25125,7 +25507,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25157,7 +25540,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25188,7 +25572,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25219,7 +25604,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25250,7 +25636,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25282,7 +25669,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25328,7 +25716,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25359,7 +25748,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25390,7 +25780,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25421,7 +25812,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25542,7 +25934,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25574,7 +25967,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25605,7 +25999,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25636,7 +26031,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25667,7 +26063,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25699,7 +26096,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25745,7 +26143,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25776,7 +26175,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25807,7 +26207,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25838,7 +26239,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25972,7 +26374,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26004,7 +26407,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26050,7 +26454,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26082,7 +26487,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26114,7 +26520,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26146,7 +26553,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26192,7 +26600,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26238,7 +26647,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26269,7 +26679,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26300,7 +26711,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26331,7 +26743,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26362,7 +26775,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26457,7 +26871,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26489,7 +26904,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26535,7 +26951,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26567,7 +26984,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26599,7 +27017,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26631,7 +27050,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26677,7 +27097,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26723,7 +27144,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26754,7 +27176,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26785,7 +27208,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26816,7 +27240,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26847,7 +27272,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26942,7 +27368,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26974,7 +27401,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27020,7 +27448,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27052,7 +27481,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27084,7 +27514,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27116,7 +27547,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27162,7 +27594,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27208,7 +27641,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27239,7 +27673,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27270,7 +27705,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27301,7 +27737,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27332,7 +27769,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27455,7 +27893,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27487,7 +27926,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27583,7 +28023,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27615,7 +28056,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27711,7 +28153,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27743,7 +28186,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27926,7 +28370,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27958,7 +28403,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27990,7 +28436,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28022,7 +28469,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28118,7 +28566,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28150,7 +28599,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28182,7 +28632,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28214,7 +28665,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28310,7 +28762,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28342,7 +28795,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28374,7 +28828,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28406,7 +28861,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28632,7 +29088,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28664,7 +29121,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28710,7 +29168,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28742,7 +29201,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28775,7 +29235,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28871,7 +29332,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28903,7 +29365,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28949,7 +29412,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28981,7 +29445,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29014,7 +29479,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -29110,7 +29576,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29142,7 +29609,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29188,7 +29656,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29220,7 +29689,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29253,7 +29723,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30665,7 +31136,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30699,7 +31171,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30732,7 +31205,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30766,7 +31240,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30799,7 +31274,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30832,7 +31308,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30865,7 +31342,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30898,7 +31376,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30930,7 +31409,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30961,7 +31441,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30992,7 +31473,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31024,7 +31506,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31056,7 +31539,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31088,7 +31572,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31120,7 +31605,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31152,7 +31638,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31185,7 +31672,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31217,7 +31705,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31249,7 +31738,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31282,7 +31772,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31314,7 +31805,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31346,7 +31838,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31379,7 +31872,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31411,7 +31905,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31443,7 +31938,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31475,7 +31971,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31520,7 +32017,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31552,7 +32050,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31583,7 +32082,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31614,7 +32114,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31645,7 +32146,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31752,7 +32254,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31786,7 +32289,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31819,7 +32323,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31853,7 +32358,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31886,7 +32392,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31919,7 +32426,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31952,7 +32460,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31985,7 +32494,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32017,7 +32527,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32048,7 +32559,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32079,7 +32591,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32111,7 +32624,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32143,7 +32657,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32175,7 +32690,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32207,7 +32723,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32239,7 +32756,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32272,7 +32790,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32304,7 +32823,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32336,7 +32856,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32369,7 +32890,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32401,7 +32923,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32433,7 +32956,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32466,7 +32990,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32498,7 +33023,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32530,7 +33056,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32562,7 +33089,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32607,7 +33135,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32639,7 +33168,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32670,7 +33200,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32701,7 +33232,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32732,7 +33264,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32839,7 +33372,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32873,7 +33407,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32906,7 +33441,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32940,7 +33476,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32973,7 +33510,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33006,7 +33544,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33039,7 +33578,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33072,7 +33612,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33104,7 +33645,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33135,7 +33677,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33166,7 +33709,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33198,7 +33742,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33230,7 +33775,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33262,7 +33808,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33294,7 +33841,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33326,7 +33874,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33359,7 +33908,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33391,7 +33941,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33423,7 +33974,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33456,7 +34008,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33488,7 +34041,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33520,7 +34074,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33553,7 +34108,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33585,7 +34141,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33617,7 +34174,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33649,7 +34207,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33694,7 +34253,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33726,7 +34286,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33757,7 +34318,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33788,7 +34350,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33819,7 +34382,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34056,7 +34620,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34088,7 +34653,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34134,7 +34700,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34166,7 +34733,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34198,7 +34766,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34230,7 +34799,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34276,7 +34846,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34322,7 +34893,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34353,7 +34925,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34384,7 +34957,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34415,7 +34989,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34446,7 +35021,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34541,7 +35117,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34573,7 +35150,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34619,7 +35197,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34651,7 +35230,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34683,7 +35263,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34715,7 +35296,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34761,7 +35343,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34807,7 +35390,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34838,7 +35422,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34869,7 +35454,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34900,7 +35486,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34931,7 +35518,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35026,7 +35614,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35058,7 +35647,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35104,7 +35694,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35136,7 +35727,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35168,7 +35760,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35200,7 +35793,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35246,7 +35840,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35292,7 +35887,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35323,7 +35919,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35354,7 +35951,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35385,7 +35983,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35416,7 +36015,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35540,7 +36140,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35572,7 +36173,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35603,7 +36205,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35634,7 +36237,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35665,7 +36269,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35697,7 +36302,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35743,7 +36349,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35774,7 +36381,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35805,7 +36413,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35836,7 +36445,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35957,7 +36567,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35989,7 +36600,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36020,7 +36632,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36051,7 +36664,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36082,7 +36696,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36114,7 +36729,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36160,7 +36776,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36191,7 +36808,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36222,7 +36840,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36253,7 +36872,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36374,7 +36994,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36406,7 +37027,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36437,7 +37059,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36468,7 +37091,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36499,7 +37123,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36531,7 +37156,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36577,7 +37203,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36608,7 +37235,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36639,7 +37267,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36670,7 +37299,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36804,7 +37434,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36836,7 +37467,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36882,7 +37514,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36914,7 +37547,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36946,7 +37580,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36978,7 +37613,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37010,7 +37646,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37056,7 +37693,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37087,7 +37725,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37118,7 +37757,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37149,7 +37789,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37180,7 +37821,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37275,7 +37917,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37307,7 +37950,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37353,7 +37997,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37385,7 +38030,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37417,7 +38063,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37449,7 +38096,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37481,7 +38129,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37527,7 +38176,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37558,7 +38208,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37589,7 +38240,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37620,7 +38272,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37651,7 +38304,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37746,7 +38400,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37778,7 +38433,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37824,7 +38480,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37856,7 +38513,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37888,7 +38546,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37920,7 +38579,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37952,7 +38612,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37998,7 +38659,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -38029,7 +38691,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -38060,7 +38723,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -38091,7 +38755,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -38122,7 +38787,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39433,7 +40099,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39465,7 +40132,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39496,7 +40164,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39527,7 +40196,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39558,7 +40228,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39590,7 +40261,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39636,7 +40308,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39667,7 +40340,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39698,7 +40372,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39729,7 +40404,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39850,7 +40526,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39882,7 +40559,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39913,7 +40591,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39944,7 +40623,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39975,7 +40655,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -40007,7 +40688,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40053,7 +40735,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40084,7 +40767,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40115,7 +40799,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40146,7 +40831,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40267,7 +40953,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40299,7 +40986,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40330,7 +41018,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40361,7 +41050,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40392,7 +41082,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40424,7 +41115,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40470,7 +41162,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40501,7 +41194,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40532,7 +41226,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40563,7 +41258,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40697,7 +41393,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40729,7 +41426,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40775,7 +41473,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40807,7 +41506,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40839,7 +41539,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40871,7 +41572,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40903,7 +41605,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40949,7 +41652,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40980,7 +41684,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41011,7 +41716,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41042,7 +41748,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41073,7 +41780,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41168,7 +41876,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41200,7 +41909,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41246,7 +41956,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41278,7 +41989,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41310,7 +42022,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41342,7 +42055,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41374,7 +42088,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41420,7 +42135,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41451,7 +42167,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41482,7 +42199,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41513,7 +42231,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41544,7 +42263,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41639,7 +42359,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41671,7 +42392,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41717,7 +42439,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41749,7 +42472,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41781,7 +42505,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41813,7 +42538,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41845,7 +42571,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41891,7 +42618,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41922,7 +42650,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41953,7 +42682,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41984,7 +42714,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42015,7 +42746,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42138,7 +42870,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42170,7 +42903,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42266,7 +43000,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -42298,7 +43033,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -42394,7 +43130,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42426,7 +43163,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43832,7 +44570,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43865,7 +44604,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43897,7 +44637,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43929,7 +44670,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43961,7 +44703,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44057,7 +44800,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44090,7 +44834,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44122,7 +44867,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44154,7 +44900,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44186,7 +44933,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44282,7 +45030,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44315,7 +45064,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44347,7 +45097,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44379,7 +45130,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44411,7 +45163,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44680,7 +45433,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -44713,7 +45467,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44759,7 +45514,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44791,7 +45547,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44837,7 +45594,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44869,7 +45627,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44902,7 +45661,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44935,7 +45695,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44969,7 +45730,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45002,7 +45764,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45035,7 +45798,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45143,7 +45907,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -45176,7 +45941,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45222,7 +45988,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45254,7 +46021,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45300,7 +46068,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45332,7 +46101,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45365,7 +46135,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45398,7 +46169,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45432,7 +46204,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45465,7 +46238,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45498,7 +46272,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45606,7 +46381,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -45639,7 +46415,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -45685,7 +46462,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45717,7 +46495,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45763,7 +46542,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45795,7 +46575,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45828,7 +46609,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45861,7 +46643,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45895,7 +46678,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45928,7 +46712,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45961,7 +46746,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46083,7 +46869,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46117,7 +46904,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46150,7 +46938,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46183,7 +46972,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46216,7 +47006,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46249,7 +47040,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46282,7 +47074,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46316,7 +47109,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46349,7 +47143,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46383,7 +47178,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46416,7 +47212,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46450,7 +47247,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46483,7 +47281,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46517,7 +47316,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46550,7 +47350,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46584,7 +47385,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46617,7 +47419,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46651,7 +47454,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46684,7 +47488,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46718,7 +47523,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46751,7 +47557,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46785,7 +47592,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46818,7 +47626,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46852,7 +47661,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46885,7 +47695,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46918,7 +47729,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46950,7 +47762,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46995,7 +47808,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47102,7 +47916,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47136,7 +47951,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47169,7 +47985,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47202,7 +48019,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47235,7 +48053,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47268,7 +48087,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47301,7 +48121,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47335,7 +48156,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47368,7 +48190,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47402,7 +48225,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47435,7 +48259,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47469,7 +48294,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47502,7 +48328,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47536,7 +48363,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47569,7 +48397,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47603,7 +48432,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47636,7 +48466,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47670,7 +48501,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47703,7 +48535,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47737,7 +48570,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47770,7 +48604,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47804,7 +48639,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47837,7 +48673,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47871,7 +48708,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47904,7 +48742,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47937,7 +48776,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47969,7 +48809,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48014,7 +48855,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48121,7 +48963,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48155,7 +48998,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48188,7 +49032,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48221,7 +49066,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48254,7 +49100,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48287,7 +49134,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48320,7 +49168,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48354,7 +49203,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48387,7 +49237,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48421,7 +49272,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48454,7 +49306,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48488,7 +49341,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48521,7 +49375,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48555,7 +49410,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48588,7 +49444,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48622,7 +49479,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48655,7 +49513,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48689,7 +49548,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48722,7 +49582,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48756,7 +49617,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48789,7 +49651,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48823,7 +49686,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48856,7 +49720,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48890,7 +49755,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48923,7 +49789,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48956,7 +49823,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48988,7 +49856,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49033,7 +49902,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -50644,7 +51514,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50678,7 +51549,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50711,7 +51583,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50745,7 +51618,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50778,7 +51652,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50811,7 +51686,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50844,7 +51720,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50877,7 +51754,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50909,7 +51787,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50940,7 +51819,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50971,7 +51851,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51003,7 +51884,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51049,7 +51931,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51081,7 +51964,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51127,7 +52011,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51159,7 +52044,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51192,7 +52078,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51224,7 +52111,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51256,7 +52144,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51288,7 +52177,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51320,7 +52210,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51353,7 +52244,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51385,7 +52277,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51417,7 +52310,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51450,7 +52344,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51482,7 +52377,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51514,7 +52410,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51547,7 +52444,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51579,7 +52477,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51611,7 +52510,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51643,7 +52543,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51688,7 +52589,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51720,7 +52622,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51751,7 +52654,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51782,7 +52686,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51813,7 +52718,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51920,7 +52826,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51954,7 +52861,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51987,7 +52895,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52021,7 +52930,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52054,7 +52964,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52087,7 +52998,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52120,7 +53032,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52153,7 +53066,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52185,7 +53099,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52216,7 +53131,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52247,7 +53163,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52279,7 +53196,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52325,7 +53243,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52357,7 +53276,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52403,7 +53323,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52435,7 +53356,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52468,7 +53390,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52500,7 +53423,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52532,7 +53456,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52564,7 +53489,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52596,7 +53522,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52629,7 +53556,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52661,7 +53589,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52693,7 +53622,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52726,7 +53656,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52758,7 +53689,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52790,7 +53722,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52823,7 +53756,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52855,7 +53789,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52887,7 +53822,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52919,7 +53855,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52964,7 +53901,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52996,7 +53934,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53027,7 +53966,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53058,7 +53998,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53089,7 +54030,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53196,7 +54138,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53230,7 +54173,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53263,7 +54207,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53297,7 +54242,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53330,7 +54276,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53363,7 +54310,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53396,7 +54344,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53429,7 +54378,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53461,7 +54411,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53492,7 +54443,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53523,7 +54475,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53555,7 +54508,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53601,7 +54555,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53633,7 +54588,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53679,7 +54635,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53711,7 +54668,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53744,7 +54702,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53776,7 +54735,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53808,7 +54768,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53840,7 +54801,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53872,7 +54834,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53905,7 +54868,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53937,7 +54901,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53969,7 +54934,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54002,7 +54968,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54034,7 +55001,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54066,7 +55034,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54099,7 +55068,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54131,7 +55101,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54163,7 +55134,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54195,7 +55167,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54240,7 +55213,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54272,7 +55246,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54303,7 +55278,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54334,7 +55310,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54365,7 +55342,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54602,7 +55580,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54634,7 +55613,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54680,7 +55660,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54712,7 +55693,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54744,7 +55726,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54776,7 +55759,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54822,7 +55806,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54868,7 +55853,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54899,7 +55885,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54930,7 +55917,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54961,7 +55949,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54992,7 +55981,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55087,7 +56077,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -55119,7 +56110,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -55165,7 +56157,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -55197,7 +56190,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -55229,7 +56223,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -55261,7 +56256,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -55307,7 +56303,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55353,7 +56350,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55384,7 +56382,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55415,7 +56414,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55446,7 +56446,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55477,7 +56478,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55572,7 +56574,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55604,7 +56607,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55650,7 +56654,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55682,7 +56687,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55714,7 +56720,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55746,7 +56753,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55792,7 +56800,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55838,7 +56847,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55869,7 +56879,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55900,7 +56911,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55931,7 +56943,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55962,7 +56975,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56086,7 +57100,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56118,7 +57133,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56149,7 +57165,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56180,7 +57197,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56211,7 +57229,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56243,7 +57262,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56289,7 +57309,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56320,7 +57341,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56351,7 +57373,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56382,7 +57405,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56503,7 +57527,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56535,7 +57560,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56566,7 +57592,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56597,7 +57624,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56628,7 +57656,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56660,7 +57689,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56706,7 +57736,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56737,7 +57768,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56768,7 +57800,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56799,7 +57832,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56920,7 +57954,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56952,7 +57987,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56983,7 +58019,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -57014,7 +58051,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -57045,7 +58083,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -57077,7 +58116,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57123,7 +58163,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57154,7 +58195,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57185,7 +58227,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57216,7 +58259,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57350,7 +58394,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57382,7 +58427,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57428,7 +58474,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57460,7 +58507,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57492,7 +58540,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57524,7 +58573,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57570,7 +58620,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57616,7 +58667,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57647,7 +58699,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57678,7 +58731,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57709,7 +58763,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57740,7 +58795,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57835,7 +58891,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57867,7 +58924,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57913,7 +58971,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57945,7 +59004,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57977,7 +59037,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -58009,7 +59070,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -58055,7 +59117,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58101,7 +59164,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58132,7 +59196,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58163,7 +59228,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58194,7 +59260,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58225,7 +59292,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58320,7 +59388,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58352,7 +59421,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58398,7 +59468,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58430,7 +59501,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58462,7 +59534,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58494,7 +59567,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58540,7 +59614,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58586,7 +59661,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58617,7 +59693,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58648,7 +59725,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58679,7 +59757,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58710,7 +59789,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -60057,7 +61137,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60089,7 +61170,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60120,7 +61202,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60151,7 +61234,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60182,7 +61266,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60214,7 +61299,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -60260,7 +61346,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -60291,7 +61378,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -60322,7 +61410,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -60353,7 +61442,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -60474,7 +61564,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60506,7 +61597,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60537,7 +61629,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60568,7 +61661,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60599,7 +61693,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60631,7 +61726,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60677,7 +61773,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60708,7 +61805,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60739,7 +61837,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60770,7 +61869,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60891,7 +61991,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60923,7 +62024,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60954,7 +62056,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60985,7 +62088,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -61016,7 +62120,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -61048,7 +62153,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -61094,7 +62200,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -61125,7 +62232,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -61156,7 +62264,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -61187,7 +62296,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -61321,7 +62431,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61353,7 +62464,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61399,7 +62511,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61431,7 +62544,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61463,7 +62577,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61495,7 +62610,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61541,7 +62657,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61587,7 +62704,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61618,7 +62736,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61649,7 +62768,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61680,7 +62800,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61711,7 +62832,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61806,7 +62928,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61838,7 +62961,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61884,7 +63008,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61916,7 +63041,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61948,7 +63074,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61980,7 +63107,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62026,7 +63154,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62072,7 +63201,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62103,7 +63233,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62134,7 +63265,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62165,7 +63297,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62196,7 +63329,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62291,7 +63425,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62323,7 +63458,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62369,7 +63505,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62401,7 +63538,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62433,7 +63571,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62465,7 +63604,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62511,7 +63651,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62557,7 +63698,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62588,7 +63730,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62619,7 +63762,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62650,7 +63794,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62681,7 +63826,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62804,7 +63950,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62836,7 +63983,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62932,7 +64080,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62964,7 +64113,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63060,7 +64210,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63092,7 +64243,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63275,7 +64427,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63308,7 +64461,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63340,7 +64494,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -63372,7 +64527,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -63404,7 +64560,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -63500,7 +64657,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63533,7 +64691,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63565,7 +64724,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63597,7 +64757,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63629,7 +64790,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63725,7 +64887,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63758,7 +64921,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63790,7 +64954,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63822,7 +64987,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63854,7 +65020,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -64080,7 +65247,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64112,7 +65280,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64158,7 +65327,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64190,7 +65360,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64223,7 +65394,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64319,7 +65491,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64351,7 +65524,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64397,7 +65571,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64429,7 +65604,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64462,7 +65638,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64558,7 +65735,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -64590,7 +65768,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -64636,7 +65815,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -64668,7 +65848,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -64701,7 +65882,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -64974,6 +66156,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[09676b9f7e][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_west].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[09676b9f7e][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_west].json new file mode 100644 index 00000000000..0f7d7d308b5 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[09676b9f7e][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_west].json @@ -0,0 +1,1501 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-1000ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 97.47 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_1000ul", + "tipLength": 88, + "tipOverlap": 7.95 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.24, + "z": 9.47 + }, + "A10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.24, + "z": 9.47 + }, + "A11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.24, + "z": 9.47 + }, + "A12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.24, + "z": 9.47 + }, + "A2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.24, + "z": 9.47 + }, + "A3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.24, + "z": 9.47 + }, + "A4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.24, + "z": 9.47 + }, + "A5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.24, + "z": 9.47 + }, + "A6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.24, + "z": 9.47 + }, + "A7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.24, + "z": 9.47 + }, + "A8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.24, + "z": 9.47 + }, + "A9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.24, + "z": 9.47 + }, + "B1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.24, + "z": 9.47 + }, + "B10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.24, + "z": 9.47 + }, + "B11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.24, + "z": 9.47 + }, + "B12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.24, + "z": 9.47 + }, + "B2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.24, + "z": 9.47 + }, + "B3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.24, + "z": 9.47 + }, + "B4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.24, + "z": 9.47 + }, + "B5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.24, + "z": 9.47 + }, + "B6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.24, + "z": 9.47 + }, + "B7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.24, + "z": 9.47 + }, + "B8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.24, + "z": 9.47 + }, + "B9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.24, + "z": 9.47 + }, + "C1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.24, + "z": 9.47 + }, + "C10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.24, + "z": 9.47 + }, + "C11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.24, + "z": 9.47 + }, + "C12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.24, + "z": 9.47 + }, + "C2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.24, + "z": 9.47 + }, + "C3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.24, + "z": 9.47 + }, + "C4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.24, + "z": 9.47 + }, + "C5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.24, + "z": 9.47 + }, + "C6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.24, + "z": 9.47 + }, + "C7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.24, + "z": 9.47 + }, + "C8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.24, + "z": 9.47 + }, + "C9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.24, + "z": 9.47 + }, + "D1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.24, + "z": 9.47 + }, + "D10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.24, + "z": 9.47 + }, + "D11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.24, + "z": 9.47 + }, + "D12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.24, + "z": 9.47 + }, + "D2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.24, + "z": 9.47 + }, + "D3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.24, + "z": 9.47 + }, + "D4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.24, + "z": 9.47 + }, + "D5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.24, + "z": 9.47 + }, + "D6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.24, + "z": 9.47 + }, + "D7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.24, + "z": 9.47 + }, + "D8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.24, + "z": 9.47 + }, + "D9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.24, + "z": 9.47 + }, + "E1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.24, + "z": 9.47 + }, + "E10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.24, + "z": 9.47 + }, + "E11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.24, + "z": 9.47 + }, + "E12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.24, + "z": 9.47 + }, + "E2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.24, + "z": 9.47 + }, + "E3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.24, + "z": 9.47 + }, + "E4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.24, + "z": 9.47 + }, + "E5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.24, + "z": 9.47 + }, + "E6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.24, + "z": 9.47 + }, + "E7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.24, + "z": 9.47 + }, + "E8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.24, + "z": 9.47 + }, + "E9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.24, + "z": 9.47 + }, + "F1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.24, + "z": 9.47 + }, + "F10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.24, + "z": 9.47 + }, + "F11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.24, + "z": 9.47 + }, + "F12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.24, + "z": 9.47 + }, + "F2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.24, + "z": 9.47 + }, + "F3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.24, + "z": 9.47 + }, + "F4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.24, + "z": 9.47 + }, + "F5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.24, + "z": 9.47 + }, + "F6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.24, + "z": 9.47 + }, + "F7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.24, + "z": 9.47 + }, + "F8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.24, + "z": 9.47 + }, + "F9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.24, + "z": 9.47 + }, + "G1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.24, + "z": 9.47 + }, + "G10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.24, + "z": 9.47 + }, + "G11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.24, + "z": 9.47 + }, + "G12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.24, + "z": 9.47 + }, + "G2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.24, + "z": 9.47 + }, + "G3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.24, + "z": 9.47 + }, + "G4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.24, + "z": 9.47 + }, + "G5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.24, + "z": 9.47 + }, + "G6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.24, + "z": 9.47 + }, + "G7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.24, + "z": 9.47 + }, + "G8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.24, + "z": 9.47 + }, + "G9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.24, + "z": 9.47 + }, + "H1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.24, + "z": 9.47 + }, + "H10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.24, + "z": 9.47 + }, + "H11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.24, + "z": 9.47 + }, + "H12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.24, + "z": 9.47 + }, + "H2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.24, + "z": 9.47 + }, + "H3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.24, + "z": 9.47 + }, + "H4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.24, + "z": 9.47 + }, + "H5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.24, + "z": 9.47 + }, + "H6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.24, + "z": 9.47 + }, + "H7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.24, + "z": 9.47 + }, + "H8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.24, + "z": 9.47 + }, + "H9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.24, + "z": 9.47 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "D1" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/tube-rack-set-1" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 124.35 + }, + "gripperOffsets": {}, + "groups": [ + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352095", + "352096", + "352097", + "352099", + "352196" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 6x15 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A2", + "B1", + "B2", + "C1", + "C2" + ] + }, + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352070", + "352098" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 4x50 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A3", + "A4", + "B3", + "B4" + ] + } + ], + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Opentrons 10 Tube Rack with Falcon 4x50 mL, 6x15 mL Conical", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1" + ], + [ + "A2", + "B2", + "C2" + ], + [ + "A3", + "B3" + ], + [ + "A4", + "B4" + ] + ], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 67.75, + "z": 6.85 + }, + "A2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 67.75, + "z": 6.85 + }, + "A3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 60.25, + "z": 7.3 + }, + "A4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 60.25, + "z": 7.3 + }, + "B1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 42.75, + "z": 6.85 + }, + "B2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 42.75, + "z": 6.85 + }, + "B3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 25.25, + "z": 7.3 + }, + "B4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 25.25, + "z": 7.3 + }, + "C1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 17.75, + "z": 6.85 + }, + "C2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 17.75, + "z": 6.85 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "PartialTipMovementNotAllowedError [line 479]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with A12 nozzle partial configuration will result in collision with items in deck slot D1.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with A12 nozzle partial configuration will result in collision with items in deck slot D1.", + "errorCode": "2004", + "errorInfo": {}, + "errorType": "PartialTipMovementNotAllowedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_west.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + } + }, + { + "definitionUri": "opentrons/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical/1", + "id": "UUID", + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "D1" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Too tall labware on pickup tip" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[09ba51132a][OT2_S_v2_14_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[09ba51132a][OT2_S_v2_14_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json index 7a7269decb6..d9895fb2c9e 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[09ba51132a][OT2_S_v2_14_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[09ba51132a][OT2_S_v2_14_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json @@ -154,6 +154,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0a9ef592c8][Flex_S_v2_18_Illumina_DNA_Prep_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0a9ef592c8][Flex_S_v2_18_Illumina_DNA_Prep_48x].json index bf3cd597d68..f892fc456ce 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0a9ef592c8][Flex_S_v2_18_Illumina_DNA_Prep_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0a9ef592c8][Flex_S_v2_18_Illumina_DNA_Prep_48x].json @@ -7651,7 +7651,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7685,7 +7686,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7718,7 +7720,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7752,7 +7755,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7785,7 +7789,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7818,7 +7823,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7851,7 +7857,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7884,7 +7891,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7917,7 +7925,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7950,7 +7959,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7984,7 +7994,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8017,7 +8028,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8050,7 +8062,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8082,7 +8095,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8127,7 +8141,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8158,7 +8173,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8189,7 +8205,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8220,7 +8237,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8327,7 +8345,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8361,7 +8380,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8394,7 +8414,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8428,7 +8449,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8461,7 +8483,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8494,7 +8517,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8527,7 +8551,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8560,7 +8585,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8593,7 +8619,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8626,7 +8653,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8660,7 +8688,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8693,7 +8722,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8726,7 +8756,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8758,7 +8789,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8803,7 +8835,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8834,7 +8867,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8865,7 +8899,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8896,7 +8931,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9003,7 +9039,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9037,7 +9074,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9070,7 +9108,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9104,7 +9143,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9137,7 +9177,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9170,7 +9211,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9203,7 +9245,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9236,7 +9279,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9269,7 +9313,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9302,7 +9347,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9336,7 +9382,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9369,7 +9416,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9402,7 +9450,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9434,7 +9483,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9479,7 +9529,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9510,7 +9561,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9541,7 +9593,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9572,7 +9625,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11064,7 +11118,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11097,7 +11152,8 @@ "y": 0.0, "z": -26.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11130,7 +11186,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11162,7 +11219,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11194,7 +11252,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11228,7 +11287,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11261,7 +11321,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11295,7 +11356,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11328,7 +11390,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11362,7 +11425,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11395,7 +11459,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11429,7 +11494,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11462,7 +11528,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11496,7 +11563,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11529,7 +11597,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11563,7 +11632,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11596,7 +11666,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11630,7 +11701,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11663,7 +11735,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11697,7 +11770,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11730,7 +11804,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11764,7 +11839,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11797,7 +11873,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11830,7 +11907,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11862,7 +11940,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11969,7 +12048,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12002,7 +12082,8 @@ "y": 0.0, "z": -26.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12035,7 +12116,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12067,7 +12149,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12099,7 +12182,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12133,7 +12217,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12166,7 +12251,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12200,7 +12286,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12233,7 +12320,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12267,7 +12355,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12300,7 +12389,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12334,7 +12424,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12367,7 +12458,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12401,7 +12493,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12434,7 +12527,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12468,7 +12562,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12501,7 +12596,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12535,7 +12631,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12568,7 +12665,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12602,7 +12700,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12635,7 +12734,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12669,7 +12769,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12702,7 +12803,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12735,7 +12837,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12767,7 +12870,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12874,7 +12978,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12907,7 +13012,8 @@ "y": 0.0, "z": -26.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12940,7 +13046,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12972,7 +13079,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13004,7 +13112,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13038,7 +13147,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13071,7 +13181,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13105,7 +13216,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13138,7 +13250,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13172,7 +13285,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13205,7 +13319,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13239,7 +13354,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13272,7 +13388,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13306,7 +13423,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13339,7 +13457,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13373,7 +13492,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13406,7 +13526,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13440,7 +13561,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13473,7 +13595,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13507,7 +13630,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13540,7 +13664,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13574,7 +13699,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13607,7 +13733,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13640,7 +13767,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13672,7 +13800,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13874,7 +14003,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13907,7 +14037,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14004,7 +14135,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14037,7 +14169,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14134,7 +14267,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14167,7 +14301,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14410,7 +14545,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14442,7 +14578,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14488,7 +14625,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14520,7 +14658,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14552,7 +14691,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14598,7 +14738,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14644,7 +14785,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14675,7 +14817,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14706,7 +14849,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14737,7 +14881,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14832,7 +14977,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14864,7 +15010,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14910,7 +15057,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14942,7 +15090,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14974,7 +15123,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15020,7 +15170,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15066,7 +15217,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15097,7 +15249,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15128,7 +15281,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15159,7 +15313,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15254,7 +15409,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15286,7 +15442,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15332,7 +15489,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15364,7 +15522,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15396,7 +15555,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15442,7 +15602,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15488,7 +15649,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15519,7 +15681,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15550,7 +15713,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15581,7 +15745,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15749,7 +15914,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15782,7 +15948,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15814,7 +15981,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15845,7 +16013,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15876,7 +16045,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15908,7 +16078,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15941,7 +16112,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15975,7 +16147,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16008,7 +16181,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16041,7 +16215,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16073,7 +16248,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16118,7 +16294,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16150,7 +16327,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16258,7 +16436,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16291,7 +16470,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16323,7 +16503,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16354,7 +16535,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16385,7 +16567,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16417,7 +16600,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16450,7 +16634,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16484,7 +16669,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16517,7 +16703,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16550,7 +16737,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16582,7 +16770,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16627,7 +16816,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16659,7 +16849,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16767,7 +16958,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16800,7 +16992,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16832,7 +17025,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16863,7 +17057,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16894,7 +17089,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16926,7 +17122,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16959,7 +17156,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16993,7 +17191,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17026,7 +17225,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17059,7 +17259,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17091,7 +17292,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17136,7 +17338,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17168,7 +17371,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18548,7 +18752,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18580,7 +18785,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18612,7 +18818,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18658,7 +18865,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18705,7 +18913,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18737,7 +18946,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18782,7 +18992,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18813,7 +19024,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18844,7 +19056,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18875,7 +19088,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18970,7 +19184,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19002,7 +19217,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19034,7 +19250,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19080,7 +19297,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19127,7 +19345,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19159,7 +19378,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19204,7 +19424,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19235,7 +19456,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19266,7 +19488,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19297,7 +19520,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19392,7 +19616,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19424,7 +19649,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19456,7 +19682,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19502,7 +19729,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19549,7 +19777,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19581,7 +19810,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19626,7 +19856,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19657,7 +19888,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19688,7 +19920,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19719,7 +19952,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19887,7 +20121,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19920,7 +20155,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19952,7 +20188,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19983,7 +20220,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20014,7 +20252,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20046,7 +20285,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20079,7 +20319,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20113,7 +20354,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20146,7 +20388,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20179,7 +20422,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20211,7 +20455,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20256,7 +20501,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20288,7 +20534,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20396,7 +20643,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20429,7 +20677,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20461,7 +20710,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20492,7 +20742,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20523,7 +20774,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20555,7 +20807,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20588,7 +20841,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20622,7 +20876,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20655,7 +20910,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20688,7 +20944,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20720,7 +20977,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20765,7 +21023,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20797,7 +21056,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20905,7 +21165,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20938,7 +21199,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20970,7 +21232,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21001,7 +21264,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21032,7 +21296,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21064,7 +21329,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21097,7 +21363,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21131,7 +21398,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21164,7 +21432,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21197,7 +21466,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21229,7 +21499,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21274,7 +21545,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21306,7 +21578,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21499,7 +21772,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21531,7 +21805,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21563,7 +21838,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21609,7 +21885,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21656,7 +21933,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21688,7 +21966,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21733,7 +22012,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21764,7 +22044,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21795,7 +22076,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21826,7 +22108,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21921,7 +22204,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21953,7 +22237,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21985,7 +22270,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22031,7 +22317,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22078,7 +22365,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22110,7 +22398,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22155,7 +22444,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22186,7 +22476,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22217,7 +22508,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22248,7 +22540,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22343,7 +22636,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22375,7 +22669,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22407,7 +22702,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22453,7 +22749,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22500,7 +22797,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22532,7 +22830,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22577,7 +22876,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22608,7 +22908,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22639,7 +22940,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22670,7 +22972,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22838,7 +23141,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22871,7 +23175,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22903,7 +23208,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22934,7 +23240,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22965,7 +23272,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22997,7 +23305,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23030,7 +23339,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23064,7 +23374,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23097,7 +23408,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23130,7 +23442,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23162,7 +23475,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23207,7 +23521,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23239,7 +23554,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23347,7 +23663,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23380,7 +23697,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23412,7 +23730,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23443,7 +23762,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23474,7 +23794,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23506,7 +23827,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23539,7 +23861,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23573,7 +23896,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23606,7 +23930,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23639,7 +23964,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23671,7 +23997,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23716,7 +24043,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23748,7 +24076,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23856,7 +24185,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23889,7 +24219,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23921,7 +24252,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23952,7 +24284,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23983,7 +24316,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24015,7 +24349,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24048,7 +24383,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24082,7 +24418,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24115,7 +24452,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24148,7 +24486,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24180,7 +24519,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24225,7 +24565,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24257,7 +24598,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25637,7 +25979,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25669,7 +26012,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25701,7 +26045,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25747,7 +26092,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25794,7 +26140,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25826,7 +26173,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25871,7 +26219,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25902,7 +26251,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25933,7 +26283,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25964,7 +26315,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26059,7 +26411,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26091,7 +26444,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26123,7 +26477,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26169,7 +26524,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26216,7 +26572,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26248,7 +26605,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26293,7 +26651,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26324,7 +26683,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26355,7 +26715,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26386,7 +26747,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26481,7 +26843,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26513,7 +26876,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26545,7 +26909,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26591,7 +26956,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26638,7 +27004,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26670,7 +27037,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26715,7 +27083,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26746,7 +27115,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26777,7 +27147,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26808,7 +27179,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26931,7 +27303,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26963,7 +27336,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27010,7 +27384,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27042,7 +27417,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27087,7 +27463,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27118,7 +27495,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27149,7 +27527,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27180,7 +27559,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27275,7 +27655,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27307,7 +27688,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27354,7 +27736,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27386,7 +27769,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27431,7 +27815,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27462,7 +27847,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27493,7 +27879,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27524,7 +27911,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27619,7 +28007,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27651,7 +28040,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27698,7 +28088,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27730,7 +28121,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27775,7 +28167,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27806,7 +28199,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27837,7 +28231,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27868,7 +28263,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -28050,7 +28446,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28083,7 +28480,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28115,7 +28513,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28147,7 +28546,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28179,7 +28579,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28211,7 +28612,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28243,7 +28645,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28275,7 +28678,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28307,7 +28711,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28339,7 +28744,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28371,7 +28777,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28403,7 +28810,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28435,7 +28843,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28467,7 +28876,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28499,7 +28909,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28531,7 +28942,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28563,7 +28975,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28595,7 +29008,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28628,7 +29042,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28660,7 +29075,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28691,7 +29107,8 @@ "y": 0.0, "z": -37.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28722,7 +29139,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28753,7 +29171,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28784,7 +29203,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28891,7 +29311,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28924,7 +29345,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28956,7 +29378,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28988,7 +29411,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29020,7 +29444,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29052,7 +29477,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29084,7 +29510,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29116,7 +29543,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29148,7 +29576,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29180,7 +29609,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29212,7 +29642,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29244,7 +29675,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29276,7 +29708,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29308,7 +29741,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29340,7 +29774,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29372,7 +29807,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29404,7 +29840,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29436,7 +29873,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29469,7 +29907,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29501,7 +29940,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29532,7 +29972,8 @@ "y": 0.0, "z": -37.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29563,7 +30004,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29594,7 +30036,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29625,7 +30068,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29732,7 +30176,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29765,7 +30210,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29797,7 +30243,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29829,7 +30276,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29861,7 +30309,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29893,7 +30342,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29925,7 +30375,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29957,7 +30408,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29989,7 +30441,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30021,7 +30474,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30053,7 +30507,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30085,7 +30540,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30117,7 +30573,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30149,7 +30606,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30181,7 +30639,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30213,7 +30672,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30245,7 +30705,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30277,7 +30738,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30310,7 +30772,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30342,7 +30805,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30373,7 +30837,8 @@ "y": 0.0, "z": -37.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30404,7 +30869,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30435,7 +30901,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30466,7 +30933,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30660,7 +31128,8 @@ "y": 0.0, "z": -37.800000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30693,7 +31162,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30740,7 +31210,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30773,7 +31244,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30806,7 +31278,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30839,7 +31312,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30873,7 +31347,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30906,7 +31381,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30940,7 +31416,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30973,7 +31450,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31006,7 +31484,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31128,7 +31607,8 @@ "y": 0.0, "z": -37.800000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31161,7 +31641,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31208,7 +31689,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -31241,7 +31723,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -31274,7 +31757,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31307,7 +31791,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31341,7 +31826,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31374,7 +31860,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31408,7 +31895,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31441,7 +31929,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31474,7 +31963,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31596,7 +32086,8 @@ "y": 0.0, "z": -37.800000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31629,7 +32120,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31676,7 +32168,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -31709,7 +32202,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -31742,7 +32236,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31775,7 +32270,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31809,7 +32305,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31842,7 +32339,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31876,7 +32374,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31909,7 +32408,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31942,7 +32442,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32368,7 +32869,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32401,7 +32903,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32434,7 +32937,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32480,7 +32984,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32512,7 +33017,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32546,7 +33052,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32579,7 +33086,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32613,7 +33121,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32646,7 +33155,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32679,7 +33189,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32712,7 +33223,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32745,7 +33257,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32777,7 +33290,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32808,7 +33322,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32839,7 +33354,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32871,7 +33387,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32917,7 +33434,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32963,7 +33481,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32996,7 +33515,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33029,7 +33549,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33062,7 +33583,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33094,7 +33616,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33139,7 +33662,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33260,7 +33784,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33293,7 +33818,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33326,7 +33852,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33372,7 +33899,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33404,7 +33932,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33438,7 +33967,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33471,7 +34001,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33505,7 +34036,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33538,7 +34070,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33571,7 +34104,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33604,7 +34138,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33637,7 +34172,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33669,7 +34205,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33700,7 +34237,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33731,7 +34269,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33763,7 +34302,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33809,7 +34349,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33855,7 +34396,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33888,7 +34430,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33921,7 +34464,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33954,7 +34498,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33986,7 +34531,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -34031,7 +34577,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -34152,7 +34699,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34185,7 +34733,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34218,7 +34767,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34264,7 +34814,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34296,7 +34847,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34330,7 +34882,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34363,7 +34916,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34397,7 +34951,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34430,7 +34985,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34463,7 +35019,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34496,7 +35053,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34529,7 +35087,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34561,7 +35120,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34592,7 +35152,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34623,7 +35184,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34655,7 +35217,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34701,7 +35264,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34747,7 +35311,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34780,7 +35345,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34813,7 +35379,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34846,7 +35413,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34878,7 +35446,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34923,7 +35492,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -35237,7 +35807,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35269,7 +35840,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35315,7 +35887,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35347,7 +35920,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35379,7 +35953,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35425,7 +36000,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35471,7 +36047,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35502,7 +36079,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35533,7 +36111,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35628,7 +36207,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35660,7 +36240,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35706,7 +36287,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35738,7 +36320,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35770,7 +36353,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35816,7 +36400,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35862,7 +36447,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35893,7 +36479,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35924,7 +36511,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36019,7 +36607,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36051,7 +36640,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36097,7 +36687,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36129,7 +36720,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36161,7 +36753,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36207,7 +36800,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36253,7 +36847,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36284,7 +36879,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36315,7 +36911,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36439,7 +37036,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36471,7 +37069,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36502,7 +37101,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36533,7 +37133,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36564,7 +37165,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36595,7 +37197,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36627,7 +37230,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36673,7 +37277,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36704,7 +37309,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36735,7 +37341,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36766,7 +37373,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36887,7 +37495,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36919,7 +37528,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36950,7 +37560,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36981,7 +37592,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -37012,7 +37624,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -37043,7 +37656,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -37075,7 +37689,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -37121,7 +37736,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -37152,7 +37768,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -37183,7 +37800,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -37214,7 +37832,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -37335,7 +37954,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -37367,7 +37987,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -37398,7 +38019,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -37429,7 +38051,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -37460,7 +38083,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -37491,7 +38115,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -37523,7 +38148,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -37569,7 +38195,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -37600,7 +38227,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -37631,7 +38259,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -37662,7 +38291,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -38983,7 +39613,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39015,7 +39646,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39061,7 +39693,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39093,7 +39726,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39125,7 +39759,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39171,7 +39806,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39217,7 +39853,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39248,7 +39885,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39279,7 +39917,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39374,7 +40013,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39406,7 +40046,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39452,7 +40093,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39484,7 +40126,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39516,7 +40159,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39562,7 +40206,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39608,7 +40253,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39639,7 +40285,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39670,7 +40317,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39765,7 +40413,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39797,7 +40446,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39843,7 +40493,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39875,7 +40526,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39907,7 +40559,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39953,7 +40606,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39999,7 +40653,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40030,7 +40685,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40061,7 +40717,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40185,7 +40842,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40217,7 +40875,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40248,7 +40907,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40279,7 +40939,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40310,7 +40971,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40341,7 +41003,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40373,7 +41036,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40419,7 +41083,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40450,7 +41115,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40481,7 +41147,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40512,7 +41179,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40633,7 +41301,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40665,7 +41334,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40696,7 +41366,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40727,7 +41398,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40758,7 +41430,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40789,7 +41462,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40821,7 +41495,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40867,7 +41542,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40898,7 +41574,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40929,7 +41606,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40960,7 +41638,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -41081,7 +41760,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41113,7 +41793,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41144,7 +41825,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41175,7 +41857,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41206,7 +41889,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41237,7 +41921,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41269,7 +41954,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41315,7 +42001,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41346,7 +42033,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41377,7 +42065,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41408,7 +42097,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41542,7 +42232,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41574,7 +42265,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41620,7 +42312,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41652,7 +42345,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41684,7 +42378,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41730,7 +42425,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41776,7 +42472,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41807,7 +42504,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41838,7 +42536,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41933,7 +42632,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -41965,7 +42665,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42011,7 +42712,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42043,7 +42745,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42075,7 +42778,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42121,7 +42825,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42167,7 +42872,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42198,7 +42904,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42229,7 +42936,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42324,7 +43032,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -42356,7 +43065,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -42402,7 +43112,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -42434,7 +43145,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -42466,7 +43178,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -42512,7 +43225,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42558,7 +43272,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42589,7 +43304,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42620,7 +43336,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42743,7 +43460,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42775,7 +43493,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42822,7 +43541,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42854,7 +43574,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42899,7 +43620,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42930,7 +43652,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42961,7 +43684,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42992,7 +43716,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43087,7 +43812,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -43119,7 +43845,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -43166,7 +43893,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43198,7 +43926,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43243,7 +43972,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43274,7 +44004,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43305,7 +44036,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43336,7 +44068,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43431,7 +44164,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43463,7 +44197,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43510,7 +44245,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43542,7 +44278,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43587,7 +44324,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43618,7 +44356,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43649,7 +44388,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43680,7 +44420,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45085,7 +45826,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -45117,7 +45859,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45149,7 +45892,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45182,7 +45926,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45215,7 +45960,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45247,7 +45993,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45343,7 +46090,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -45375,7 +46123,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -45407,7 +46156,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -45440,7 +46190,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -45473,7 +46224,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -45505,7 +46257,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -45601,7 +46354,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -45633,7 +46387,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -45665,7 +46420,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -45698,7 +46454,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -45731,7 +46488,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -45763,7 +46521,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -45989,7 +46748,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46021,7 +46781,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46054,7 +46815,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46150,7 +46912,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -46182,7 +46945,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -46215,7 +46979,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46311,7 +47076,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46343,7 +47109,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46376,7 +47143,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -48939,6 +49707,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "CleanupBead Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0affe60373][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_maximum].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0affe60373][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_maximum].json index 64072eb8834..a877268d0bd 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0affe60373][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_maximum].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0affe60373][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_maximum].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0b42cfc151][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_row].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0b42cfc151][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_row].json new file mode 100644 index 00000000000..3a0f63a8f99 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0b42cfc151][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_row].json @@ -0,0 +1,1501 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-1000ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 97.47 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_1000ul", + "tipLength": 88, + "tipOverlap": 7.95 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.24, + "z": 9.47 + }, + "A10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.24, + "z": 9.47 + }, + "A11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.24, + "z": 9.47 + }, + "A12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.24, + "z": 9.47 + }, + "A2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.24, + "z": 9.47 + }, + "A3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.24, + "z": 9.47 + }, + "A4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.24, + "z": 9.47 + }, + "A5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.24, + "z": 9.47 + }, + "A6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.24, + "z": 9.47 + }, + "A7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.24, + "z": 9.47 + }, + "A8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.24, + "z": 9.47 + }, + "A9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.24, + "z": 9.47 + }, + "B1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.24, + "z": 9.47 + }, + "B10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.24, + "z": 9.47 + }, + "B11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.24, + "z": 9.47 + }, + "B12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.24, + "z": 9.47 + }, + "B2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.24, + "z": 9.47 + }, + "B3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.24, + "z": 9.47 + }, + "B4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.24, + "z": 9.47 + }, + "B5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.24, + "z": 9.47 + }, + "B6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.24, + "z": 9.47 + }, + "B7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.24, + "z": 9.47 + }, + "B8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.24, + "z": 9.47 + }, + "B9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.24, + "z": 9.47 + }, + "C1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.24, + "z": 9.47 + }, + "C10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.24, + "z": 9.47 + }, + "C11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.24, + "z": 9.47 + }, + "C12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.24, + "z": 9.47 + }, + "C2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.24, + "z": 9.47 + }, + "C3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.24, + "z": 9.47 + }, + "C4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.24, + "z": 9.47 + }, + "C5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.24, + "z": 9.47 + }, + "C6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.24, + "z": 9.47 + }, + "C7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.24, + "z": 9.47 + }, + "C8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.24, + "z": 9.47 + }, + "C9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.24, + "z": 9.47 + }, + "D1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.24, + "z": 9.47 + }, + "D10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.24, + "z": 9.47 + }, + "D11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.24, + "z": 9.47 + }, + "D12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.24, + "z": 9.47 + }, + "D2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.24, + "z": 9.47 + }, + "D3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.24, + "z": 9.47 + }, + "D4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.24, + "z": 9.47 + }, + "D5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.24, + "z": 9.47 + }, + "D6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.24, + "z": 9.47 + }, + "D7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.24, + "z": 9.47 + }, + "D8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.24, + "z": 9.47 + }, + "D9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.24, + "z": 9.47 + }, + "E1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.24, + "z": 9.47 + }, + "E10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.24, + "z": 9.47 + }, + "E11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.24, + "z": 9.47 + }, + "E12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.24, + "z": 9.47 + }, + "E2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.24, + "z": 9.47 + }, + "E3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.24, + "z": 9.47 + }, + "E4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.24, + "z": 9.47 + }, + "E5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.24, + "z": 9.47 + }, + "E6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.24, + "z": 9.47 + }, + "E7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.24, + "z": 9.47 + }, + "E8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.24, + "z": 9.47 + }, + "E9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.24, + "z": 9.47 + }, + "F1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.24, + "z": 9.47 + }, + "F10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.24, + "z": 9.47 + }, + "F11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.24, + "z": 9.47 + }, + "F12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.24, + "z": 9.47 + }, + "F2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.24, + "z": 9.47 + }, + "F3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.24, + "z": 9.47 + }, + "F4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.24, + "z": 9.47 + }, + "F5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.24, + "z": 9.47 + }, + "F6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.24, + "z": 9.47 + }, + "F7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.24, + "z": 9.47 + }, + "F8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.24, + "z": 9.47 + }, + "F9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.24, + "z": 9.47 + }, + "G1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.24, + "z": 9.47 + }, + "G10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.24, + "z": 9.47 + }, + "G11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.24, + "z": 9.47 + }, + "G12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.24, + "z": 9.47 + }, + "G2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.24, + "z": 9.47 + }, + "G3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.24, + "z": 9.47 + }, + "G4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.24, + "z": 9.47 + }, + "G5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.24, + "z": 9.47 + }, + "G6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.24, + "z": 9.47 + }, + "G7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.24, + "z": 9.47 + }, + "G8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.24, + "z": 9.47 + }, + "G9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.24, + "z": 9.47 + }, + "H1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.24, + "z": 9.47 + }, + "H10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.24, + "z": 9.47 + }, + "H11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.24, + "z": 9.47 + }, + "H12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.24, + "z": 9.47 + }, + "H2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.24, + "z": 9.47 + }, + "H3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.24, + "z": 9.47 + }, + "H4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.24, + "z": 9.47 + }, + "H5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.24, + "z": 9.47 + }, + "H6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.24, + "z": 9.47 + }, + "H7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.24, + "z": 9.47 + }, + "H8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.24, + "z": 9.47 + }, + "H9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.24, + "z": 9.47 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H1", + "style": "ROW" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/tube-rack-set-1" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 124.35 + }, + "gripperOffsets": {}, + "groups": [ + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352095", + "352096", + "352097", + "352099", + "352196" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 6x15 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A2", + "B1", + "B2", + "C1", + "C2" + ] + }, + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352070", + "352098" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 4x50 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A3", + "A4", + "B3", + "B4" + ] + } + ], + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Opentrons 10 Tube Rack with Falcon 4x50 mL, 6x15 mL Conical", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1" + ], + [ + "A2", + "B2", + "C2" + ], + [ + "A3", + "B3" + ], + [ + "A4", + "B4" + ] + ], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 67.75, + "z": 6.85 + }, + "A2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 67.75, + "z": 6.85 + }, + "A3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 60.25, + "z": 7.3 + }, + "A4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 60.25, + "z": 7.3 + }, + "B1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 42.75, + "z": 6.85 + }, + "B2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 42.75, + "z": 6.85 + }, + "B3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 25.25, + "z": 7.3 + }, + "B4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 25.25, + "z": 7.3 + }, + "C1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 17.75, + "z": 6.85 + }, + "C2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 17.75, + "z": 6.85 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "PartialTipMovementNotAllowedError [line 479]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with H1 nozzle partial configuration will result in collision with items in deck slot B2.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with H1 nozzle partial configuration will result in collision with items in deck slot B2.", + "errorCode": "2004", + "errorInfo": {}, + "errorType": "PartialTipMovementNotAllowedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_row.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + } + }, + { + "definitionUri": "opentrons/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical/1", + "id": "UUID", + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "B2" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Too tall labware on pickup tip" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0c4ae179bb][OT2_S_v2_15_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0c4ae179bb][OT2_S_v2_15_P300M_P20S_HS_TC_TM_SmokeTestV3].json index f35525aa18e..957e685c737 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0c4ae179bb][OT2_S_v2_15_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0c4ae179bb][OT2_S_v2_15_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -11045,7 +11045,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11108,7 +11109,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11171,7 +11173,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11234,7 +11237,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11297,7 +11301,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11343,7 +11348,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11376,7 +11382,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -11408,7 +11415,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11440,7 +11448,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11473,7 +11482,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -11505,7 +11515,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11537,7 +11548,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11570,7 +11582,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -11602,7 +11615,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11634,7 +11648,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11667,7 +11682,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -11699,7 +11715,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11731,7 +11748,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11764,7 +11782,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -11796,7 +11815,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11892,7 +11912,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11925,7 +11946,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11958,7 +11980,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11991,7 +12014,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12024,7 +12048,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12057,7 +12082,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12090,7 +12116,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12123,7 +12150,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12156,7 +12184,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12189,7 +12218,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12222,7 +12252,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12253,7 +12284,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12285,7 +12317,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12318,7 +12351,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12351,7 +12385,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12384,7 +12419,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12417,7 +12453,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12450,7 +12487,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12483,7 +12521,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12516,7 +12555,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12549,7 +12589,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12582,7 +12623,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12615,7 +12657,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12646,7 +12689,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12678,7 +12722,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12711,7 +12756,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12744,7 +12790,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12777,7 +12824,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12810,7 +12858,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12843,7 +12892,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12876,7 +12926,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12909,7 +12960,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12942,7 +12994,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12975,7 +13028,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -13008,7 +13062,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -13039,7 +13094,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -13071,7 +13127,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13104,7 +13161,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13137,7 +13195,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13170,7 +13229,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13203,7 +13263,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13236,7 +13297,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13269,7 +13331,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13302,7 +13365,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -13335,7 +13399,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -13368,7 +13433,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -13401,7 +13467,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -13432,7 +13499,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -13464,7 +13532,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13497,7 +13566,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13530,7 +13600,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13563,7 +13634,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13596,7 +13668,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13629,7 +13702,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13662,7 +13736,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13695,7 +13770,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -13728,7 +13804,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -13761,7 +13838,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -13794,7 +13872,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -13825,7 +13904,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -13857,7 +13937,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13890,7 +13971,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13923,7 +14005,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13956,7 +14039,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13989,7 +14073,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14022,7 +14107,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14055,7 +14141,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14088,7 +14175,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -14121,7 +14209,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -14154,7 +14243,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -14187,7 +14277,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -14218,7 +14309,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -14250,7 +14342,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14283,7 +14376,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14316,7 +14410,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14349,7 +14444,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14382,7 +14478,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14415,7 +14512,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14448,7 +14546,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14481,7 +14580,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -14514,7 +14614,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -14547,7 +14648,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -14580,7 +14682,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -14611,7 +14714,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -14643,7 +14747,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14676,7 +14781,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14709,7 +14815,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14742,7 +14849,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14775,7 +14883,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14808,7 +14917,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14841,7 +14951,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14874,7 +14985,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -14907,7 +15019,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -14940,7 +15053,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -14973,7 +15087,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15004,7 +15119,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15036,7 +15152,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15069,7 +15186,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15102,7 +15220,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15135,7 +15254,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15168,7 +15288,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15201,7 +15322,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15234,7 +15356,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15267,7 +15390,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15300,7 +15424,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15333,7 +15458,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15366,7 +15492,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15397,7 +15524,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15429,7 +15557,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -15462,7 +15591,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15494,7 +15624,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15526,7 +15657,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -15559,7 +15691,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15591,7 +15724,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15623,7 +15757,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15656,7 +15791,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15688,7 +15824,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15784,7 +15921,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -15816,7 +15954,8 @@ "y": 0.0, "z": 5.0000000000000036 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -15848,7 +15987,8 @@ "y": 0.0, "z": 5.0000000000000036 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -15895,7 +16035,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -15927,7 +16068,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -15958,7 +16100,8 @@ "y": 0.0, "z": -14.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -15989,7 +16132,8 @@ "y": 0.0, "z": -14.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -16021,7 +16165,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -16353,7 +16498,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16386,7 +16532,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16483,7 +16630,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16516,7 +16664,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16627,7 +16776,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16660,7 +16810,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -16724,7 +16875,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16757,7 +16909,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -16919,6 +17072,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0cbde10c37][OT2_S_v2_18_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0cbde10c37][OT2_S_v2_18_P300M_P20S_HS_TC_TM_SmokeTestV3].json index 56d23e7468e..35dc7ecc804 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0cbde10c37][OT2_S_v2_18_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0cbde10c37][OT2_S_v2_18_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -9383,7 +9383,14 @@ }, "id": "UUID", "key": "08e16a2cac011d4bef561f8b0854d19e", - "notes": [], + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], "params": { "displayName": "4 custom tubes", "loadName": "cpx_4_tuberack_100ul", @@ -9562,6 +9569,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons Engineering ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0dd21c0ee5][pl_EM_seq_48Samples_AllSteps_Edits_150].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0dd21c0ee5][pl_EM_seq_48Samples_AllSteps_Edits_150].json index 1769d90469a..db42ce35fdc 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0dd21c0ee5][pl_EM_seq_48Samples_AllSteps_Edits_150].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[0dd21c0ee5][pl_EM_seq_48Samples_AllSteps_Edits_150].json @@ -20290,7 +20290,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20323,7 +20324,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20356,7 +20358,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20390,7 +20393,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20423,7 +20427,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20457,7 +20462,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20490,7 +20496,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20524,7 +20531,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20557,7 +20565,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20591,7 +20600,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20624,7 +20634,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20658,7 +20669,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20691,7 +20703,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20725,7 +20738,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20758,7 +20772,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20792,7 +20807,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20825,7 +20841,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20859,7 +20876,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20892,7 +20910,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20926,7 +20945,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20959,7 +20979,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20992,7 +21013,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21024,7 +21046,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21069,7 +21092,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21232,7 +21256,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21265,7 +21290,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21298,7 +21324,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21332,7 +21359,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21365,7 +21393,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21399,7 +21428,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21432,7 +21462,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21466,7 +21497,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21499,7 +21531,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21533,7 +21566,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21566,7 +21600,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21600,7 +21635,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21633,7 +21669,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21667,7 +21704,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21700,7 +21738,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21734,7 +21773,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21767,7 +21807,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21801,7 +21842,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21834,7 +21876,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21868,7 +21911,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21901,7 +21945,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21935,7 +21980,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21968,7 +22014,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22002,7 +22049,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22035,7 +22083,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22069,7 +22118,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22102,7 +22152,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22136,7 +22187,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22169,7 +22221,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22203,7 +22256,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22236,7 +22290,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22269,7 +22324,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22301,7 +22357,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22346,7 +22403,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22509,7 +22567,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22542,7 +22601,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22588,7 +22648,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22709,7 +22770,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22742,7 +22804,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22776,7 +22839,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22809,7 +22873,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22843,7 +22908,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22876,7 +22942,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22910,7 +22977,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22943,7 +23011,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22977,7 +23046,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23010,7 +23080,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23044,7 +23115,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23077,7 +23149,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23111,7 +23184,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23144,7 +23218,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23178,7 +23253,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23211,7 +23287,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23245,7 +23322,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23278,7 +23356,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23312,7 +23391,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23345,7 +23425,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23378,7 +23459,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23411,7 +23493,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23445,7 +23528,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23478,7 +23562,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23511,7 +23596,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23543,7 +23629,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23589,7 +23676,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23621,7 +23709,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23653,7 +23742,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23685,7 +23775,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23717,7 +23808,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23750,7 +23842,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23784,7 +23877,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23817,7 +23911,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23851,7 +23946,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23884,7 +23980,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23918,7 +24015,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23951,7 +24049,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23985,7 +24084,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24018,7 +24118,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24052,7 +24153,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24085,7 +24187,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24119,7 +24222,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24152,7 +24256,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24186,7 +24291,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24219,7 +24325,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24253,7 +24360,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24286,7 +24394,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24320,7 +24429,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24353,7 +24463,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24387,7 +24498,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24420,7 +24532,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24454,7 +24567,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24487,7 +24601,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24521,7 +24636,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24554,7 +24670,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24588,7 +24705,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24621,7 +24739,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24655,7 +24774,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24688,7 +24808,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24721,7 +24842,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24753,7 +24875,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24798,7 +24921,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24951,7 +25075,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25030,7 +25155,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25198,7 +25324,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25230,7 +25357,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25262,7 +25390,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25308,7 +25437,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25429,7 +25559,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25476,7 +25607,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25644,7 +25776,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25676,7 +25809,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25708,7 +25842,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25754,7 +25889,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25875,7 +26011,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25922,7 +26059,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26122,7 +26260,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26155,7 +26294,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26189,7 +26329,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26222,7 +26363,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26256,7 +26398,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26289,7 +26432,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26323,7 +26467,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26356,7 +26501,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26390,7 +26536,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26423,7 +26570,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26457,7 +26605,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26490,7 +26639,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26524,7 +26674,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26557,7 +26708,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26591,7 +26743,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26624,7 +26777,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26658,7 +26812,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26691,7 +26846,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26725,7 +26881,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26758,7 +26915,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26792,7 +26950,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26825,7 +26984,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26859,7 +27019,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26892,7 +27053,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26926,7 +27088,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26959,7 +27122,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26993,7 +27157,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27026,7 +27191,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27060,7 +27226,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27093,7 +27260,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27126,7 +27294,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27159,7 +27328,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27205,7 +27375,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27237,7 +27408,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27404,7 +27576,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27451,7 +27624,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27484,7 +27658,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27517,7 +27692,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27549,7 +27725,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27594,7 +27771,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27626,7 +27804,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27849,7 +28028,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27882,7 +28062,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27915,7 +28096,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27949,7 +28131,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27982,7 +28165,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28016,7 +28200,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28049,7 +28234,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28083,7 +28269,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28116,7 +28303,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28150,7 +28338,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28183,7 +28372,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28217,7 +28407,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28250,7 +28441,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28284,7 +28476,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28317,7 +28510,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28351,7 +28545,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28384,7 +28579,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28418,7 +28614,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28451,7 +28648,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28485,7 +28683,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28518,7 +28717,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28551,7 +28751,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28583,7 +28784,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28628,7 +28830,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28749,7 +28952,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -28782,7 +28986,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28815,7 +29020,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28849,7 +29055,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28882,7 +29089,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28916,7 +29124,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28949,7 +29158,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28983,7 +29193,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29016,7 +29227,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29050,7 +29262,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29083,7 +29296,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29117,7 +29331,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29150,7 +29365,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29184,7 +29400,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29217,7 +29434,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29251,7 +29469,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29284,7 +29503,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29318,7 +29538,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29351,7 +29572,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29385,7 +29607,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29418,7 +29641,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29451,7 +29675,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29483,7 +29708,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29528,7 +29754,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29691,7 +29918,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29724,7 +29952,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29758,7 +29987,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29791,7 +30021,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29825,7 +30056,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29858,7 +30090,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29892,7 +30125,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29925,7 +30159,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29959,7 +30194,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29992,7 +30228,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30026,7 +30263,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30059,7 +30297,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30093,7 +30332,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30126,7 +30366,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30160,7 +30401,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30193,7 +30435,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30227,7 +30470,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30260,7 +30504,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30294,7 +30539,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30327,7 +30573,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30360,7 +30607,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30393,7 +30641,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30427,7 +30676,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30460,7 +30710,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30493,7 +30744,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30525,7 +30777,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30571,7 +30824,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30603,7 +30857,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30635,7 +30890,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30667,7 +30923,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30699,7 +30956,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30732,7 +30990,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30766,7 +31025,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30799,7 +31059,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30833,7 +31094,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30866,7 +31128,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30900,7 +31163,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30933,7 +31197,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30967,7 +31232,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31000,7 +31266,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31034,7 +31301,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31067,7 +31335,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31101,7 +31370,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31134,7 +31404,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31168,7 +31439,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31201,7 +31473,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31235,7 +31508,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31268,7 +31542,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31302,7 +31577,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31335,7 +31611,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31369,7 +31646,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31402,7 +31680,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31436,7 +31715,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31469,7 +31749,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31503,7 +31784,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31536,7 +31818,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31570,7 +31853,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31603,7 +31887,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31637,7 +31922,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31670,7 +31956,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31703,7 +31990,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31735,7 +32023,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31780,7 +32069,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31947,7 +32237,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32115,7 +32406,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32283,7 +32575,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32315,7 +32608,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32347,7 +32641,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32393,7 +32688,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32514,7 +32810,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32561,7 +32858,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32729,7 +33027,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32761,7 +33060,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32793,7 +33093,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32839,7 +33140,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32960,7 +33262,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33007,7 +33310,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33193,7 +33497,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33226,7 +33531,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33260,7 +33566,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33293,7 +33600,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33327,7 +33635,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33360,7 +33669,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33394,7 +33704,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33427,7 +33738,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33461,7 +33773,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33494,7 +33807,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33528,7 +33842,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33561,7 +33876,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33595,7 +33911,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33628,7 +33945,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33662,7 +33980,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33695,7 +34014,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33729,7 +34049,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33762,7 +34083,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33796,7 +34118,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33829,7 +34152,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33863,7 +34187,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33896,7 +34221,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33930,7 +34256,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33963,7 +34290,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33997,7 +34325,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34030,7 +34359,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34064,7 +34394,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34097,7 +34428,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34131,7 +34463,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34164,7 +34497,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34197,7 +34531,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34230,7 +34565,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34276,7 +34612,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34308,7 +34645,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34475,7 +34813,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34522,7 +34861,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34555,7 +34895,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34588,7 +34929,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34620,7 +34962,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34665,7 +35008,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34697,7 +35041,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34938,7 +35283,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34971,7 +35317,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35003,7 +35350,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35048,7 +35396,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35211,7 +35560,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35243,7 +35593,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35275,7 +35626,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35308,7 +35660,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35342,7 +35695,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35375,7 +35729,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35409,7 +35764,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35442,7 +35798,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35476,7 +35833,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35509,7 +35867,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35543,7 +35902,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35576,7 +35936,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35610,7 +35971,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35643,7 +36005,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35677,7 +36040,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35710,7 +36074,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35744,7 +36109,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35777,7 +36143,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35811,7 +36178,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35844,7 +36212,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35878,7 +36247,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35911,7 +36281,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35944,7 +36315,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35976,7 +36348,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36021,7 +36394,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36184,7 +36558,8 @@ "y": 0.0, "z": -14.350000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36231,7 +36606,8 @@ "y": 0.0, "z": -14.350000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36264,7 +36640,8 @@ "y": 0.0, "z": -32.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36297,7 +36674,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36329,7 +36707,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36374,7 +36753,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36406,7 +36786,8 @@ "y": 0.0, "z": -15.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36527,7 +36908,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36560,7 +36942,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36594,7 +36977,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36627,7 +37011,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36661,7 +37046,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36694,7 +37080,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36728,7 +37115,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36761,7 +37149,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36795,7 +37184,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36828,7 +37218,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36862,7 +37253,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36895,7 +37287,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36929,7 +37322,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36962,7 +37356,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36996,7 +37391,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37029,7 +37425,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37063,7 +37460,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37096,7 +37494,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37130,7 +37529,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37163,7 +37563,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37196,7 +37597,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37229,7 +37631,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37263,7 +37666,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37296,7 +37700,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37329,7 +37734,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37361,7 +37767,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37407,7 +37814,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37439,7 +37847,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37471,7 +37880,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37503,7 +37913,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37535,7 +37946,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37568,7 +37980,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37602,7 +38015,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37635,7 +38049,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37669,7 +38084,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37702,7 +38118,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37736,7 +38153,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37769,7 +38187,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37803,7 +38222,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37836,7 +38256,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37870,7 +38291,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37903,7 +38325,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37937,7 +38360,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37970,7 +38394,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38004,7 +38429,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38037,7 +38463,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38071,7 +38498,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38104,7 +38532,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38138,7 +38567,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38171,7 +38601,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38205,7 +38636,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38238,7 +38670,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38272,7 +38705,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38305,7 +38739,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38339,7 +38774,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38372,7 +38808,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38406,7 +38843,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38439,7 +38877,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38473,7 +38912,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38506,7 +38946,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38539,7 +38980,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38571,7 +39013,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38616,7 +39059,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38783,7 +39227,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38862,7 +39307,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39030,7 +39476,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39062,7 +39509,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39094,7 +39542,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39140,7 +39589,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39261,7 +39711,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39308,7 +39759,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39476,7 +39928,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39508,7 +39961,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39540,7 +39994,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39586,7 +40041,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39707,7 +40163,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39754,7 +40211,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39954,7 +40412,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39987,7 +40446,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40021,7 +40481,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40054,7 +40515,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40088,7 +40550,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40121,7 +40584,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40155,7 +40619,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40188,7 +40653,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40222,7 +40688,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40255,7 +40722,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40289,7 +40757,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40322,7 +40791,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40356,7 +40826,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40389,7 +40860,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40423,7 +40895,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40456,7 +40929,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40490,7 +40964,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40523,7 +40998,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40557,7 +41033,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40590,7 +41067,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40624,7 +41102,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40657,7 +41136,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40691,7 +41171,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40724,7 +41205,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40758,7 +41240,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40791,7 +41274,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40825,7 +41309,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40858,7 +41343,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40892,7 +41378,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40925,7 +41412,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40958,7 +41446,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40991,7 +41480,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41037,7 +41527,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41069,7 +41560,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41236,7 +41728,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41283,7 +41776,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41316,7 +41810,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41349,7 +41844,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41381,7 +41877,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41426,7 +41923,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41458,7 +41956,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41657,7 +42156,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41690,7 +42190,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41724,7 +42225,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41757,7 +42259,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41791,7 +42294,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41824,7 +42328,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41858,7 +42363,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41891,7 +42397,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41925,7 +42432,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41958,7 +42466,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41992,7 +42501,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42025,7 +42535,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42059,7 +42570,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42092,7 +42604,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42126,7 +42639,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42159,7 +42673,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42193,7 +42708,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42226,7 +42742,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42260,7 +42777,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42293,7 +42811,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42326,7 +42845,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42359,7 +42879,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42393,7 +42914,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42426,7 +42948,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42459,7 +42982,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42491,7 +43015,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42537,7 +43062,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42569,7 +43095,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42601,7 +43128,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42633,7 +43161,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42665,7 +43194,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42698,7 +43228,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42732,7 +43263,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42765,7 +43297,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42799,7 +43332,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42832,7 +43366,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42866,7 +43401,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42899,7 +43435,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42933,7 +43470,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42966,7 +43504,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43000,7 +43539,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43033,7 +43573,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43067,7 +43608,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43100,7 +43642,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43134,7 +43677,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43167,7 +43711,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43201,7 +43746,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43234,7 +43780,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43268,7 +43815,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43301,7 +43849,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43335,7 +43884,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43368,7 +43918,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43402,7 +43953,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43435,7 +43987,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43469,7 +44022,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43502,7 +44056,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43536,7 +44091,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43569,7 +44125,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43603,7 +44160,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43636,7 +44194,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43669,7 +44228,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43701,7 +44261,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43746,7 +44307,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43913,7 +44475,8 @@ "y": 0.0, "z": -14.249999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44153,7 +44716,8 @@ "y": 0.0, "z": -14.249999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44335,7 +44899,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44367,7 +44932,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44399,7 +44965,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44445,7 +45012,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44566,7 +45134,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44613,7 +45182,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44781,7 +45351,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44813,7 +45384,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44845,7 +45417,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44891,7 +45464,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45012,7 +45586,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45059,7 +45634,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45245,7 +45821,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45278,7 +45855,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45312,7 +45890,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45345,7 +45924,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45379,7 +45959,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45412,7 +45993,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45446,7 +46028,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45479,7 +46062,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45513,7 +46097,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45546,7 +46131,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45580,7 +46166,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45613,7 +46200,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45647,7 +46235,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45680,7 +46269,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45714,7 +46304,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45747,7 +46338,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45781,7 +46373,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45814,7 +46407,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45848,7 +46442,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45881,7 +46476,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45915,7 +46511,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45948,7 +46545,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45982,7 +46580,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46015,7 +46614,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46049,7 +46649,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46082,7 +46683,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46116,7 +46718,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46149,7 +46752,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46183,7 +46787,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46216,7 +46821,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46249,7 +46855,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46282,7 +46889,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46328,7 +46936,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46360,7 +46969,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46527,7 +47137,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46574,7 +47185,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46607,7 +47219,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46640,7 +47253,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46672,7 +47286,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46717,7 +47332,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46749,7 +47365,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48775,6 +49392,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[109b7ad1f0][Flex_S_v2_20_96_None_ROW_HappyPath].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[109b7ad1f0][Flex_S_v2_20_96_None_ROW_HappyPath].json new file mode 100644 index 00000000000..cf56c96470e --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[109b7ad1f0][Flex_S_v2_20_96_None_ROW_HappyPath].json @@ -0,0 +1,6283 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "displayName": "Partial Tip Rack", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B1" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "ROW" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "displayName": "Liquid Transfer - Source Labware", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c910be62291bb94458a59cb4185c5180", + "notes": [], + "params": { + "displayName": "Liquid Transfer - Destination Labware", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C3" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2f59e0552969594ba6ab06f03af324e1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 14.38, + "y": 225.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6b0053809cc5ea894449b20181bda1d6", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 178.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1c921a50baabd805b1ee8fa0e85c6682", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "75aa0b5116a9eba713b68a3850e26391", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "34f14498853a691a86c6e51955a923f5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "92f6d16db82fc47bbd830b87c04cf587", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 14.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "touchTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d4506d1b80c117247379fd1da0cdd540", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "radius": 1.0, + "speed": 60.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -1.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 178.38, + "y": 172.24, + "z": 14.7 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b6c0a6862c1e869c974d3bbf491b2f77", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "239bbe2e12ee55487024bc01a20c71b9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ccd85f84c2d5a9969bce2957cc8ea1f1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 14.38, + "y": 243.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c603a57fd9bba99db45ecb759ed91041", + "notes": [], + "params": { + "axes": [ + "leftPlunger", + "leftZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "615a33e51b3fb8d9a7c25fc4e3f6a04d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3fcbc107a2ddb8aef2be961108add200", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e9ce054762d4edc21b2b4c74b3a264cb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 14.38, + "y": 252.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a0f88d95e77f51e86818cb9e324a7006", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7ed7eaaa162b89d760f55d949352464b", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "23eb3478c9444fcf3001ecb326f15411", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "34c3afeaf56af250ec42af0751995669", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "45d2cde26215d9e311e7f9745228d93c", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f3e7a78d7da267bc877af3a8bc8d8de3", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b5b355ee01907f745c80f2350c8e3e31", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dc872d009459778cbbf93fd8edfa4ef6", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ac0822a34f4d47ffb4989a5f63bfab59", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4e4778958eeb399df7ed9910c6d52431", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3bd5afcf7db6fd3db5480254738264d9", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "331962de59fdeac6a9ecfc21dcda5cac", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "457dec90436799e13fcbb6ffdde3859f", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a835d08fcbabe71ad357dcdafa9632c4", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d6e6d0dcaa50989719d4265d3fb9aaaf", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d92475e5e072d6961fe38612564b2d31", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0129acc2b378f88a55ba77758eb3ed02", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0354a7d87886f9008260cf75347b2452", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "93e0f4ac6e51c80ff19bfe60dc97542b", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c3bedc4720be98d3ed4a3c1734854d6a", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "29bfc3397bb9f9430b9f2f59288a540c", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7d6c3bf51293b3fee38ca9a4e44f6cfd", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a7d6a6a0fddaf753a629565eb4d912a3", + "notes": [], + "params": { + "flowRate": 80.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "prepareToAspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1654457443c6f75e15e311f0543fc66f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9911036ea844c7a6ba90923698fc224e", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c3188c7feb06653501f6bf397a131dc8", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1943586caa4c85679a9ae04821c2d38a", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9579bd2d4199b9920413263739ef30ed", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4f2dfd5aa4da5e2a45a3f3b692227df5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e50910ece222b51bd908e743c6bf7804", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H1", + "style": "ROW" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5a00693acdb90f3631535748d93a8223", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b782827466e82f28c3969485a65f3fe2", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 178.38, + "y": 145.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "32989d99a33a00680804e9fc9fc2804b", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 342.38, + "y": 145.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ba2c5376c66ec84d18a9a8585e95d9d9", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "252f2c9c7e1a96f8a4c8cc7eeff8fbdf", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ec32821a6c616dea97d3d2b4f59e1cd4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 14.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "touchTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dc740b4cde605ccfc143bd6b2dc65f7b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "radius": 1.0, + "speed": 60.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -1.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 178.38, + "y": 136.24, + "z": 14.7 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d8894bf3d181e7ea041a675ae0eeb074", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3438f69dae7bf7c63c3caf25b1c0af6f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "36fce93206223fa6086fc3958b91de53", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 14.38, + "y": 270.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0f96ea2f581e7da13b2920758720217c", + "notes": [], + "params": { + "axes": [ + "leftPlunger", + "leftZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "052f6060ce6e871a6a65936ec86cd64e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ba2b5012fff1efcc43be440738b96812", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "be37ed68d97eb27cdaa9f28b458c84cb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 261.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c484812021d17a80f73bf5376416ff93", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "abeb19b3de05703b3e0ec8b69735094c", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cf5afad6cb0e55ff82bf41cbb61278f9", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "abc3039f932b9588a796a3d2844da6bc", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8231c70a38e5e9514386c43272b24b3b", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a9b22d17829464f4fc6f8bda7be4e82b", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d2d5e3037bdb3057f4ac220c39d9968e", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bbd96dd68d79179af153ef884b3430da", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8c5b08c533f9c792b534a3231d054fa3", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5f47db85c4a26fad793ff0bd71056e8f", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f8c9b70248e4087475baa0ea41b7a8f2", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a258c594b3064208d6b6a8c304dbe002", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9fc7b604becb670c1c2bcc718b191072", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "feaf07c0bf3861b566e4ab6bc54bc632", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4b61daf27e7e1a5d020fc247827adafe", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cd870b78d981e980bd5ca0c179487646", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bff9cf8246b58f1bdbbb16ca3dc12d80", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3d02ddc2c5fa9cfe85e9b0459595940e", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c8b8e1fb79a8c8c22ea041fe1d30fe06", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c960ea23d531567b5131f108b07f280d", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3a0eadb753166ea07bd95887ffca1ef1", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5fe73007151bae4924c6c6c55ef663b0", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "88544514bc37e481edb0e05e76e088bb", + "notes": [], + "params": { + "flowRate": 80.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "prepareToAspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2228ebb5354fb6841543953901ad0caf", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "25e7036b1ceb4c316002632bcb52134b", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a261b02a0248d089146924e44691411b", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "19a48a90466241d64935c5a55b23fbea", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "263dd8836ac5c6cff37fff2ef09bf09e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "33d098affbd40500bc8579d4995dad56", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [], + "files": [ + { + "name": "Flex_S_v2_20_96_None_ROW_HappyPath.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "displayName": "Partial Tip Rack", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B1" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "displayName": "Liquid Transfer - Source Labware", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "displayName": "Liquid Transfer - Destination Labware", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C3" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "96 channel pipette and a ROW partial tip configuration.", + "protocolName": "96Channel ROW Happy Path" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[10c2386b92][Flex_S_v2_20_96_AllCorners].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[10c2386b92][Flex_S_v2_20_96_AllCorners].json new file mode 100644 index 00000000000..e4de2f89a14 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[10c2386b92][Flex_S_v2_20_96_AllCorners].json @@ -0,0 +1,33714 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0c3bb3b5f63458c1ff149d26e64d98b5", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "80976ce3dffe6efe69a8b26c3c4a1f4b", + "notes": [], + "params": { + "displayName": "A1 Corner Tiprack 1", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B1" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d89b7e852badd592f2c16cd019f596b5", + "notes": [], + "params": { + "displayName": "D1 Corner Tiprack", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "C1" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "440fd5d4d25f78376014c66392a18e38", + "notes": [], + "params": { + "displayName": "A3 Corner Tiprack 1", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d18d39c6822ff1cd6ce2fd098338c7f0", + "notes": [], + "params": { + "displayName": "D3 Corner Tiprack", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "C2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d26e79600f75864088f39597ce155cb4", + "notes": [], + "params": { + "displayName": "A3 Corner Tiprack 2", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "A2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0ad11818ed6a58b4125b09f521ef7427", + "notes": [], + "params": { + "displayName": "A1 Corner Tiprack 2", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "addressableAreaName": "D4" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e59adc286bd3bc6c094ce600f1fc7df7", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "A1" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dc3333d4314b8f2e93c504d0d76cd023", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "ROW" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fd5e1fbbdbd5980754514c29ff56f612", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 14.38, + "y": 118.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "55c001604193d02deca6d26ee961e665", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2bc2e305fc1f083468154eb63543dc6b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "581858797e1c84d6ef6907ff6564704b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 14.38, + "y": 127.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2ad8d3cad2ceb13e535a911c14f2ca51", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "085700c231f9630847ca7be0a9712c10", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bfc154c2055eafd8de53ed7918a8b2bd", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 14.38, + "y": 136.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "26ac7fd5faef93302d3fbc49db7add06", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ea4eb91e895b420538a0f4a3de35215e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f4bafffc24563db38f66d512f9d52608", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "D1" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "43f132bd8c8be367bbea536d45800b00", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "COLUMN" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5234bb754a520a0c82d95a2cc0a11630", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 277.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "80792fac4307676440a65242d477c0ba", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8d8a833b3949c88948069284842708e2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "84535a57c980ec368b9273f4cae42077", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A11" + }, + "result": { + "position": { + "x": 268.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f5cc216ec1e6fbd4ce8a80f25ee81bff", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "66addfca5f355d51d9e1187f739b1933", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cae1bda03bf2cc927b8809f29e0461ba", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "A3" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cb826260b033670baaf76946fe18cb0c", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "ROW" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "477b2ca4b04f63f479976cea028ddc34", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 178.38, + "y": 118.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ef685bf857220d56d2ec09c953cdd54c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "caaa68a8872363bd55a03840a073f7a3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2f7ae7ea31cb49a846c2e85131933241", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3cc8b36c29f009e6ddd79ab7d51f9b9c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fd186ea036e5c43100366048a9bf96bf", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a7da3feeaf59ab9910abd390415ae0e3", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 178.38, + "y": 136.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3b86d87937b20d929bfce9dba650e3bc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2b83cf73f3499cd1e645e8b5d3a2268f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6feea658b6c942f5b277ac0865e4fe6f", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "241eca009cb04e7eafe03e547bfb9f0f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E12" + }, + "result": { + "position": { + "x": 277.38, + "y": 145.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0be2b0fb1538d3dc7e3b995b8db53e04", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "920b03027fda8f0380c157234d3ecd73", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f69826303f6a2752d1c912a9928093e2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D12" + }, + "result": { + "position": { + "x": 277.38, + "y": 154.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "44d8fecee455b87b7c71773c061b766d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "81c7f6220dcd4580909c4d889559392e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "40666d2d148b449215a32a9ea46cfd20", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C12" + }, + "result": { + "position": { + "x": 277.38, + "y": 163.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f30c895d42dc07604ca0a7a714dba8b3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f8cf5fe75119a980716eb872c8f54498", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3849966716a1d0aa6f54c8d62fe5129f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B12" + }, + "result": { + "position": { + "x": 277.38, + "y": 172.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "23c40755c72164e6cb4960e930f806fa", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c887c611c40293ea14ebb66d57c9e2b1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e6f1db550101e672a1b882bb41daf8f2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 277.38, + "y": 181.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cd7ff8e7a96ed93a96b3e174644df331", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f57b28a3534977384836f5835db58300", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "56d0e95682f0cb1e0879a3cfe6df4c90", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E11" + }, + "result": { + "position": { + "x": 268.38, + "y": 145.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "aa0097329cfb6974a656e4e48ad9045d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7f4aad30a64ea520545090b8ecbd6957", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "679a3d4e21ca968e77c91f0e6c715624", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D11" + }, + "result": { + "position": { + "x": 268.38, + "y": 154.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "60eae530a213eb258059469d3ec1cff3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9fcb72c68fcf7a6f6065c01bca605f5b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8533435cdfea2ee9f208027a557b3ba5", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C11" + }, + "result": { + "position": { + "x": 268.38, + "y": 163.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a9ec53b4f44b7d3d71157c1537843d15", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7a4d0c477562921f79fb938ab9e8156f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ac1e40d322083b6d79b93ba1c68af10c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B11" + }, + "result": { + "position": { + "x": 268.38, + "y": 172.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a49902680bb808047d1f43b764971554", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "796a15fd2ad469beece86be7dd7ebe65", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "93ca7655a522d7721e8c4694bbba68a9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A11" + }, + "result": { + "position": { + "x": 268.38, + "y": 181.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b8336ac6302191e710ed99e9e6adf41e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5b582194b41028577624f215159d76ae", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0ba74b1ae7de89595b2e8d6384dd6202", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "D3" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7a8c624e1508d4b12fcba0b3649387f2", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "65daad6f2fac10b8009cf754adbcc192", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E12" + }, + "result": { + "position": { + "x": 113.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8e8ec2d4f03ce33a370b155bb19002ad", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6eadef0c6f337062bd02835b9fab4b89", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "29ebfa5049489f12fb5b5e3d93b70431", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D12" + }, + "result": { + "position": { + "x": 113.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "24f33a0efeb2b8cf0e859474dae2b22d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c7bb920ec5ec7eab264a5919b94797cf", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d0433b56b7e9fdaad17fff3f46eaf0c8", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C12" + }, + "result": { + "position": { + "x": 113.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "900947aa38f6b74be850e578c4a072b4", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "74487159c087b2a18436bfafa54861db", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8f084b45586bc3555ed741af945d7070", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B12" + }, + "result": { + "position": { + "x": 113.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d8f80d27fcfc38ed3bd1faded465cdd5", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dd7219e1fc01741cd131415a22e3b87d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1193b4a08a9c759700cf568bf4a88b4a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f521f51e2b73ff52e0612040912f6924", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4a626e29cbb967070723a6d01213138e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fad0059b46c4edeb84095e9ba6e71eb3", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E11" + }, + "result": { + "position": { + "x": 104.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "89bb6a003749f5123c48bbf55cbd32a8", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f15b703741d9c2fcbaf5fd294c2396bd", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "068f1ffc1b770b921cb4dc939dc6ffd9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D11" + }, + "result": { + "position": { + "x": 104.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d7ac79ad61d16f25244c5f6a1672406d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "482c6e36e3c3e1a1287e9f754d5313cc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0ba822570668ca07719e98413b4944fd", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C11" + }, + "result": { + "position": { + "x": 104.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2b819cf6061f0fccc739fab480d3146b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "76de98b36b9fbad51fb179d53b2d563d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ca9af7cd6188408d7e49e6e6277fd5f6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B11" + }, + "result": { + "position": { + "x": 104.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a23044ec7a251b0ff8b9d439a29834ea", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c04c5f88f0d665a874d7056c2be4e0aa", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7c8c0b9c6ceeb88152e819bf34a639d7", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A11" + }, + "result": { + "position": { + "x": 104.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cb8e7aa358a0e09d593ca9d74b77511e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "56aa8a870b94fa26317f2fc8adac8432", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "666becbcb68227de89a8862b08a32bec", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E10" + }, + "result": { + "position": { + "x": 95.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b84dcd58d6435ca3c49a037f604e511e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6d1eebaf10c55301752f7dc037f8bdfb", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b526f30b873a29b6c438234db0ab7fbe", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D10" + }, + "result": { + "position": { + "x": 95.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e4b806a88ccf7da7778891f4540bbbd7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "542a65a5f9b9291e3f616b672246720f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fbf7b7d915ef8f511b67c20842fd529d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C10" + }, + "result": { + "position": { + "x": 95.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "88e017cfde3286e114d080785d91aff9", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "31f51a22279576d33752d3691b14763f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f49f12eb381d1979a74aba3a4acad6ec", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B10" + }, + "result": { + "position": { + "x": 95.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "18b48ebffe6d23a582b4dabff91ae5fc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "56c752b2a026271cd0df2518e5f0fc91", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "eabc12011277672d886be2f85fd40017", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A10" + }, + "result": { + "position": { + "x": 95.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3c747638500d7344551d3480fa2e31ad", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "13e8311ee634170e75112c31cc8e7d7a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3c3241059ddca1ab71d95401c385f7ed", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d46b10b6c1d5843dede0a5e53f849652", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 14.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "88779d29803d3fbba6a9294da7491200", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6bbc49890257568fba0e3e6c74996a73", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b8ea68c3fb8b89adb783329cf5695d2a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0293b1047a20be51a0d0e23288b99f27", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7454e3dba9a2ca6d2d2ae97a3d481e42", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "57be6958aef7d861809108d2cb6943cb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 14.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7abc41a52d8571499a87c37ca6e2ed7e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2ca6f7f8811cde0642d513ea1c9626cb", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "92919140073a46b9834a3b877c6b0164", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 14.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d0e7b1c082f1a03878fcdcfac3d38088", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "952ffb780eec38bce33166569c1a1fb8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c40ddf768c7c884c2b104e7eb5abb31d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a7cfc4e694b9c09147b3c730f8bf9863", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ce8cdbb13fc142652fcbef5389ad43d2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3e1f5e062c7516a0b0c2a6a2ded77eb1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E2" + }, + "result": { + "position": { + "x": 23.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c6fb7ed1ab303623d94354372cfd4396", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "297c57e5258012246c176c654e8d40ab", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3d1e0bf4201ae6014e0be849df34cddb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D2" + }, + "result": { + "position": { + "x": 23.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7da7dae43dfd05fec92878914f66a524", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d3b0863b4a751a4f468689d751820ebf", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "aa67a33a881a773ae15967cc5866afcd", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C2" + }, + "result": { + "position": { + "x": 23.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f56730ff29a71011babff188d0adac14", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cbd0ec4b4fd3804eec2db9cfed4065a5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "63ebd9796b2a84b80ae46b2a34179c82", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B2" + }, + "result": { + "position": { + "x": 23.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "06462e84cd9bba2206501755f8a9502c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "583fb4530f969d51b6f5c31fd0ea74ad", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "439436633ba8c82db083be9224d6ca03", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 23.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6f120296f38cb4136ea281d647c6c968", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "488e49a2ae65b958137e5b96f33b86c0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a6b9c91aebb57a2da747d3af33389881", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E3" + }, + "result": { + "position": { + "x": 32.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d14acbf657c39804cc60a3ebd74c4b93", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "448361a19a8a5d203444b758dce42d4a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cfd5c6a077b5fdb508c84815cc0c0419", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D3" + }, + "result": { + "position": { + "x": 32.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b691c808ac83cdaa847d6145a21a7e1f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d81df2d794fa8d86f9081044a8b4632d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "db4116a1fd2504c822d16176537a9744", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C3" + }, + "result": { + "position": { + "x": 32.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ed9fda97210f0ff38c246a3356f493af", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a9b6cd450ffa30637df72120515b27a0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4473f8a398d3ad940e5d2d9e3e2a8b99", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B3" + }, + "result": { + "position": { + "x": 32.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c1efab737f3ac1b0c32d57346a213103", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "424cb0fa05d3212b4a240524b7b72b1b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "edba3092ebf3b9e53fd499e5358231ba", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 32.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d58310c3a50ed08137e402ef22bd3134", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "446144a4e689564b2f5901198acfef9f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4b9e3c7a245107c3ef20445b1478ed31", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f3febe36dce0f9ea84638f460101787c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A9" + }, + "result": { + "position": { + "x": 86.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e07c12a45a58d15c84070ca7a7f0bebf", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2cc012e503f5eec627f28502dca84f24", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a4b5f6d122c7abe8c0ac8f6eeef15686", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B9" + }, + "result": { + "position": { + "x": 86.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bcfadc4d666de17d13f216a430745423", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "de2576c7c78db325ef24346cf5cf4159", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "324ba1c75e7b6c4f3213edbd572f3d7a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C9" + }, + "result": { + "position": { + "x": 86.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e9559850fa7942dc65780a762e309744", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "925d384a350a9ab95e4bed52d99d12fa", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "75ba53438adcb5db46651e69f11e84ad", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D9" + }, + "result": { + "position": { + "x": 86.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d321176bbd49d60fceb734e0223411ec", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "99b7ac2d67ad676e79f70a5eec1fb5f5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e09e2f94817979918bbd7c49b04ace84", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E9" + }, + "result": { + "position": { + "x": 86.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b4118f8deb8e82e0a5e9da6a16fe553f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "39200025551a33bf2e7278e1ced7bcd5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4dc6bc30062e1c6989903ce38a85d902", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 77.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "58caf4548070bbdac328defdeab0be98", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d8b34d9e6e6be788ac5bad8abc7b1ee2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "904685cbf1633bb78e2e8a628b13c91e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B8" + }, + "result": { + "position": { + "x": 77.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "597950b3b675fb432e6aefa4a8a09e54", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ef137e1d61725a85ab02d610f4c92be1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3a405a69123e1e0432da74f8441f6ad3", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C8" + }, + "result": { + "position": { + "x": 77.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7359802f4bc9967ac0643ece9b2284f1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3b7b674008a1622788dbed7d09edbaa4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "733cc2a55c013d2847ecb03f50e69a0d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D8" + }, + "result": { + "position": { + "x": 77.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2e1cc93473ddb6e6ea9e160b6578fce3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "09e3a4d04f46b8c4a554814f26b26a92", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6db3e055a3eb15ca1beb837075821cbf", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E8" + }, + "result": { + "position": { + "x": 77.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4de4faf1849c9203450e4fa59331492a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "48f45578f33a7e5b67fcd7ebb773262e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "13542871f6d055e73a08e14f361b8eac", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2998f6bba5e08651ad75a24f38f2bb1d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2340dc15447fc6272a693242744ff8ca", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f8043e4634696564497681eeb3a0305c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B7" + }, + "result": { + "position": { + "x": 68.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9c11805f77cc4df9f3574e67d171bd6f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3f89aa970306e6c12302ce7d4236555e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8abc075e569d91bbcd49dc7529524587", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C7" + }, + "result": { + "position": { + "x": 68.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "04c10ec59ae45389aa1db99df716fb84", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2d3f8c30e6e9665a223f97486bff50c3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "59c8327d1e9f711d6dd81142f60bbd20", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D7" + }, + "result": { + "position": { + "x": 68.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e1a0e8f24ec93f7151d6259d80001222", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8b03ac0181b84973e9b56c87a23b9f17", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cd7ea6ef414aeb10bfd996753c6ec262", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E7" + }, + "result": { + "position": { + "x": 68.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9bf3ad7c73330f611dee46a2c4305cac", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "65bd4ea7a7dc70ea852fee4b84032431", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "781c152f1a6e86df45864a79f418a10f", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d2af8e44f6eecbc5ed6da21135d319d1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 41.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0162e6eccab6ac26e3f9cdc516d23482", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "45c070d255ca6c6e0dc2215342f2326d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "75bf67cacb2631ffdac705704f9030a8", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B4" + }, + "result": { + "position": { + "x": 41.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d7f33cb3c10ec244227fa0411fb50f71", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a7d6a6f5b79ef0447a5eae98daf763d9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "34635985f44d6064c200ffdb81d9f227", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C4" + }, + "result": { + "position": { + "x": 41.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b38547bb27e653dc2714a02152ff7c36", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3e6f781ae687e0ab9ff3bdfd5480b39a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0f950f55ad850aafe8f2645abf1e62b5", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D4" + }, + "result": { + "position": { + "x": 41.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9a62e1e5475d1efb974e8f032a712593", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fc4d95a82e65f190ff38fca615465a02", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1ba428cb9bb8a25005b6594722be2d2f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E4" + }, + "result": { + "position": { + "x": 41.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cb4674cab1438a455e9a16b3060912fc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d7d7871c1d0958444ad3109c2404991a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7ec0679185952164b8993101b25476e0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 50.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "126a5cdc7b9be24f096047fed0d8c524", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4ae6c04aabce9bbe77767e02aaaca2a6", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f00f357dcff058e09cdf6db04576995f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B5" + }, + "result": { + "position": { + "x": 50.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "01e4cab0798dd41c1d4a75a3f290d5f7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5f8f50c3094059e37f0f0fb70dba1088", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "99ee1e37123eeed187024561fbcd59b4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C5" + }, + "result": { + "position": { + "x": 50.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b89ae5081ca3bf28159ae9cea0f36c72", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f15ce25a4e4a2583c40f9a204aa03737", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "19910e2422de93bebfb6f53c856a95c8", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D5" + }, + "result": { + "position": { + "x": 50.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "091ef9daf282e51a1db640688ab95897", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a51684dec2501528b4491a6f06e505d6", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "87545e0c14a275d8900d185c26394335", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E5" + }, + "result": { + "position": { + "x": 50.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c8d3fe477333f520415a3ba694ec0f8b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b78244639c1ba35817db350b5febd6fa", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "828d0ab1a8cfcc634ea10300c4d242bf", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 59.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5d2594b2fa3d1bc1a446e80a4e8ce1a4", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "798d2e708312ef90908f1fd936a35aaa", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "130dff34f9f1f14b1aea16e61c175cb4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B6" + }, + "result": { + "position": { + "x": 59.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "abf4b6e24cbc14a6bce761903873a54c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "23306966afbb5f5a48049ff738c00cd0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8b8cb2669c6eb7b27a7beb57758bead6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C6" + }, + "result": { + "position": { + "x": 59.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "29a5fc0430c7b9ce02d03ae1bc1509bf", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a3cfc43606fb9d8c879f88df03b7cd4c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "995e9d73203fe3fe9a0665f9cb020a26", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D6" + }, + "result": { + "position": { + "x": 59.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "854cb07c78c566afe1f67ac83a43eef5", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "55050f3328e73697b0e3bcd4515cbaee", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b50c54d6a19afcb3242e60b8d88baa79", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E6" + }, + "result": { + "position": { + "x": 59.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bdbdf677d0064ca4f32f960c51d6a445", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4a1fdbfccd14852406c034ea50ea0c10", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "532b1be9c7e4ff77746a800b746982fd", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "B2" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4407a4c50ab9bca786ff1eb3aded4c7c", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H1", + "style": "ROW" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a3d3a254f58dbf5eaf54d3e9d8b0430e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 178.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "31afd3a65d9fff5f3c7f1bdc94f39a02", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "852d90573712a29469c40462d58bf44b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "26153c16168a3fea625595ef12cac2cb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 178.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1a60728e4281690a85cc180186dcee14", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "90b5d2ae9184f8e902e89258479d2dc9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e896afe0dab6242cc4a0b2284854f4b2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 178.38, + "y": 270.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6eca8573e72eb22fd69a5b9258aedd6b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "46b6ab5a7cc1a2d384daca47ab685093", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "faf22c19a9b4ba20a2b5aab3b0bb60e1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a9b70d77ff319e5ac26b93755bbc7453", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8d7f1f400c4cf876993291a83f5b36c8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6b0398211d16aac590e0a745c5f69a20", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 178.38, + "y": 252.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4f10163c2ff2046a35e3ee824c7b63f3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1466ebd16eeeed80a76b8f940f215770", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0f6ae3480820082349e36eef2768afdb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 178.38, + "y": 243.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b1a3880a04bdd33d91ab43b79efc2ee1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f97c7eb31b0e2a1db652fb0d48beac09", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "29f42d9a0f06178291eb27779972f665", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d0694099b83e3ef4f205f8900244560f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b9026d22c3fed4cba4551c47d5514872", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "96a7e3ac47b91f0c2359c5004dd567fe", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f77f000873cc2acb3c2ef36416ffa6e9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H12" + }, + "result": { + "position": { + "x": 277.38, + "y": 225.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "93d72c623251300144711f8e1ffca2c7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3afd5fd12b773af9fd9ea6022e849b18", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c4d1ff84cca093ccbfc9389db6cb83f3", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H11" + }, + "result": { + "position": { + "x": 268.38, + "y": 225.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ec5561932dfeed3448d49b6ae19cafdb", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2681611e12ba3f43f60105ea40dc2cf8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "41c9116cf34d7fa591d2b8ce1867aca1", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "C1" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7c76efe563f7a20e0242eb5ead0cb9a9", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "01ea156ed112d6bc2bac8d35531b67aa", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H12" + }, + "result": { + "position": { + "x": 113.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "998ff1e207d7649c2b3705919dac54fc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "676c0b0fcbbc22b5b52d0f9911e73cce", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "69d6675492957b4d8ae660781a2b5e54", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G12" + }, + "result": { + "position": { + "x": 113.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fa385876a40816d97be595f871c55586", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "26b60271c1d32e7cacfe0332e7401da2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "80841e9dedbc7b94a74df97e0a323262", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F12" + }, + "result": { + "position": { + "x": 113.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "906912bd2261f8bfa9d22f1d32f4e6f1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0907a969265f7b85e9fa79a8aef88631", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ebf91c6459fc0404bb5a0b7069ff5e24", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E12" + }, + "result": { + "position": { + "x": 113.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5042def3dfef5de5aecf9614c815c684", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "df46106aacbc01643ccc8100ea78c296", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "625ea1255906c411ff4c40571d84c8ee", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D12" + }, + "result": { + "position": { + "x": 113.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c7ccc820777900863f148da03b44641a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1ff65f42435700eb138d3c6a0fd884b4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "11aa4b24d023c7084f3c2246bbd14fe9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C12" + }, + "result": { + "position": { + "x": 113.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8819690542a49ac8ea16485a42fb72ec", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b66d8fdbf993fa368481ab79ed0d47a3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f0ed36a24af5778cd633de4be25adbe2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B12" + }, + "result": { + "position": { + "x": 113.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bb84180808ea83fdd65d77b9b3781d14", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e10732b3cc753d22874d59e88429933f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "aa4f35b4133a88582c9c15d8100b3485", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ca43d5642bf49f0c5630fa15ca275e9d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b47af825db3ceea4d48e1ded3f9a9c94", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a367df43057b99792288c16758c4641d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H11" + }, + "result": { + "position": { + "x": 104.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3dd5124d6b9ff0ed07f215d50866df47", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3c3819db10059e841503ea96d7aa3ae7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b555a35a4712a960be7d7ba3078e2c2c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G11" + }, + "result": { + "position": { + "x": 104.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5f89638804ac3890a36ba24817faaf3c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "92a173fbffbc5364d91991c5d425454a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2a26229245fea599aadb0cdf191d3818", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F11" + }, + "result": { + "position": { + "x": 104.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "34cb5296321674959a04a032e41f9f78", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "789f783381552d6bf74d3637102385a5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d458854df8af17425adb8cb2ec1c7725", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E11" + }, + "result": { + "position": { + "x": 104.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ae48e657f88e7cc63cec6f10f1704938", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fe4fee5cb325c9ec86836089bb23ca07", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e77b160f34beab2403e8f7b6a49b837f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D11" + }, + "result": { + "position": { + "x": 104.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0119a54bbd9979cb77e523b106f379a7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d7485dada9d77f9e02766d02b7121a82", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d5cefe617cb82aa087792628b11e084a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C11" + }, + "result": { + "position": { + "x": 104.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "981e22013991709788e36779c805b1dd", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3eac44c354438e5ec47c7e56f31146f6", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c6fd655602415a4ffebb34a1dcb2f515", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B11" + }, + "result": { + "position": { + "x": 104.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b657a79e0b06a1f159022c02de18e24d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "67a1296d5954060c07c1d8745e59044b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8e86fe7085f1e216b50190094c5383d0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A11" + }, + "result": { + "position": { + "x": 104.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "07c63b8179da557cab3e9564faedad1e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "00ea041a4af6e576c7727b44bb06ef4c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "83a9c2b6d49e81e8c70b516e7d80341f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H10" + }, + "result": { + "position": { + "x": 95.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d0064b714da1df79c9dd56bcd56498f6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6ada1d237b794734e8922a270dd33bf8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d02287e9dc964cdca2db343497e7934f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G10" + }, + "result": { + "position": { + "x": 95.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c9de20f52b787eb9e43d87ea7b9a7e0e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "303da1da439decf38c0eacd30716cab0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fca59dc908629d84b8fbfdfd6a3899d6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F10" + }, + "result": { + "position": { + "x": 95.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "82967b8d51d01a718842b63b945105a0", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "904a64c977ea00fb0959f9b3e8905baf", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "68936c987cc71e5046397a7100e75757", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E10" + }, + "result": { + "position": { + "x": 95.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b3b4a292a947437a6256f5ced6b05237", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fb4e8e1d2999847749ae4b38cee72aac", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "de416e66819992158d8b013bf7f5ecc0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D10" + }, + "result": { + "position": { + "x": 95.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4cdd8a33e6408438271dce62fb8cee65", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e5e5cb7f5ee78f1de307f16169d8bb8f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bb104ec3111cb5323c5403911258f902", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C10" + }, + "result": { + "position": { + "x": 95.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c78cfbee03e4ac477b9a639aa4b2fe64", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0baa0123aba5752868b59fdf3fe1f280", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2d5db8b26229573755e3a816322a1435", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B10" + }, + "result": { + "position": { + "x": 95.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7a7fdfcf22b9d2d182d9a20916b31d49", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "586b7960d1f537d053135d49ee88c0c9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e3f26721ca7ed73a542d9d7d1244bdbf", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A10" + }, + "result": { + "position": { + "x": 95.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e2cfa06b27bd46c4c9df2a19e78354dc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1e1d402db78a2c54cd95e8d6543d37e0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cdbf43640a99ff91d0bb54a482475c7e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H9" + }, + "result": { + "position": { + "x": 86.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "07144ab339c518c13a39c1120c0fe931", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cc60a671f169933a769ee407648189f1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f99a16031c12e93dbb7a58d2812bd082", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G9" + }, + "result": { + "position": { + "x": 86.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "612b2ba97e09981314a5eb73bdbac57a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8fa81d919277daa89b848d44a73ed1ed", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d32580d57c1330866fd502ecb7f8be38", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F9" + }, + "result": { + "position": { + "x": 86.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "db54694455645a82b2c391b991145c35", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "62e8fa03c718363f2eae2cca56b6b990", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9b05c325539b90ff9ed534fa4e586b86", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E9" + }, + "result": { + "position": { + "x": 86.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ec0bed8a77b5241b314fdc986a46f8a2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6ff571c7f1af11e3b4b4d42f39322cfa", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "446b56c4e011230fb7f9c33b15c0885b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D9" + }, + "result": { + "position": { + "x": 86.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0e4c7462bf4a701f06e29a3bc9a63ca4", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f12526837bc03fc836b1713af96f4066", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "789ba62a54b493cf84b9e77165b9a584", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C9" + }, + "result": { + "position": { + "x": 86.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "64bdea2a8b73f074e90955b9ba54bf04", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "661646579d48c85eee1d9cb5845253b4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0eae169696e9429ed7ffc06cdcf880be", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B9" + }, + "result": { + "position": { + "x": 86.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "15bb137bbe607729e2f67acea6d655d7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "645aa67564678e9a12c75652c22159b0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b1cd85389280e6bda8d61a1bf0038665", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A9" + }, + "result": { + "position": { + "x": 86.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b4c14bf6af26e4934ac4934c0741fd64", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a5a4abc1d20830b1c013b380f5102858", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f4c8bfbb9d9ffa33aaf27bf3a2b606f2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H8" + }, + "result": { + "position": { + "x": 77.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d5a86a7fd6ba5ef4e38f2ac740f4033a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d201e9c1eb937634253755f706e7d869", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c20d7915462ba55b25dce9de65caefbf", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G8" + }, + "result": { + "position": { + "x": 77.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "174b9a2eeaded2155dd374c5f763397d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2b98b6a9374987b4c85aae4a90dde170", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e5810bcd05293d78d917cccd4877cbd5", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F8" + }, + "result": { + "position": { + "x": 77.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1350ce3f30fd4b74e94957da386304fd", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e8647f1d7b5fb517a48a4675bb0eaa0d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ee0629c3c2a8c38c6c9de32e38b07f78", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E8" + }, + "result": { + "position": { + "x": 77.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d65e06fe08381c131f633cc3acb95614", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f1892b30c8052b4076ab887b6d687e62", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b9cbc87c24c17ef53a2b3566a60d391c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D8" + }, + "result": { + "position": { + "x": 77.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "be8ef89d90cedf22a579e5d26a5fda28", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "17a0c4e8ae80077bb0af6ce76614087a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bdd34c8cf9e42cda89711ab3713a69ff", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C8" + }, + "result": { + "position": { + "x": 77.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0fcf8cbadf2c96070c49eb259b287453", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8d88be309e137990a6a81a468aa5e9b4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d21a12b8dec55360d53d8609d669943a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B8" + }, + "result": { + "position": { + "x": 77.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f7f6303988ef2920bb16724afa69f88a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3be22156da42f7d9ebfae7dcb98d447e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "87310a1532fb827a49fd79037ef6b843", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 77.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c92d10507dc4d7773edf9dd959928b2b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5011b3cecdc9edc29b103178ad4ba001", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1ae8b3ee1926c86102180ea764698c85", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H7" + }, + "result": { + "position": { + "x": 68.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d14949f54d015f2cff41b54934ad74bf", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "935a7c05e5b25d3bbf1c7a4e74d61f59", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "aeffa465b0a00beec26f112d6aa4bf09", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G7" + }, + "result": { + "position": { + "x": 68.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "648f1d9682fcfd3db1df65d27b4daf0d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "eb5c95a9762adbdb09285b7e40d5cf3b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a29f0c6ae1535d39e03c1385b0020c22", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F7" + }, + "result": { + "position": { + "x": 68.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "09a023cf2190bf1ab2f53fc12cac25cd", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "02b233df33d3bbfbc80400aef4a673de", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f8370c6311ab2be97ced4e48103d3f4d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E7" + }, + "result": { + "position": { + "x": 68.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "24ebbcb639525b4bbe5ca2d6c3726551", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "89aa309b56a5cab6056151c16328a879", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6b29ec3b4986d473476f501c59451568", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D7" + }, + "result": { + "position": { + "x": 68.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c3c69e177bb06851b1c7c29e93350888", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "820971d1aa87f0120ddeb56f15dc1200", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "118a76905b9e524f27d1fe08564ff664", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C7" + }, + "result": { + "position": { + "x": 68.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "65014974120b50fbfb10f80a9d48c95e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a1fa5be3e345864988076f45848e3eba", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a4c79d516791c3009ca1458dcf5a0b9a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B7" + }, + "result": { + "position": { + "x": 68.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f067a6248bad4b83f51301c575d8c869", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "20228e610dfea5a198ea02a883949bd4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "576455f3a5d3933c20da6af1d84d3d23", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "257911e91c5ff4745437fad2642a8b1d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "07fc4164b2721da8c0b61da637c97a7e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8da1ef248ab5c85de0d34f6bfcbd7de5", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e31e294fa0fa9366a6204cdfba890c83", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 14.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a41f2be500d29c898b9408317e3143a0", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "371b7fac69c59b585ef44b5b403eef07", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8c1379f153d12f446d44ca37c680779e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 14.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a8003b6371de7e8aeee4714f89675217", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d3447610f3da4bb3df4ee45b525c1015", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8bae4193146b1b3e6ecad781fda558d6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 14.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cc958bb95666998dd17e5454bf6b86f9", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fec1d6c8233f653961cd474b105bcd5a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bf5c1f5eaa9016114c35baa31f69447c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 14.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c26442bca4fd8421ca5fea3fa54b8405", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "32ad8bf50f5f748ef5dc4a778c351b78", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "593b6ab957edbd16ff2ccba10cc81413", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "37bf107670a631e9a92a9bd27327e2d9", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e1a2226b3aeb092e2f7b229cb2f2649d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "892fc5886a5a90ab19261623d01e1ce1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 14.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "04a8715aa9c072ead1ee5e48fd517bee", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "89b3e82488b5069b1a5d9781eed386db", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c3e289a173e39343e492f840a43409ce", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 14.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b0024cbf5a921b47920f766d5317cf7e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6d9b557958eb4041c946823177a6a525", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "97d0c0cd1e40c99fdda5ea54c337010f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "63c756d0e15e2f7c46e1a0e1f86aa7cd", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "68a597ff53c6865ecfd3be150b3c549c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c177c1e3ebd1b7a5e29eb5d0749b338a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H2" + }, + "result": { + "position": { + "x": 23.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "be6902b9abe645acb4a97a41270797c2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "85f6c9e13c8c3e7641ab56b280a69c18", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f2d4334434063b919ed116e7d1ce81b3", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G2" + }, + "result": { + "position": { + "x": 23.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "af5972095785b5fa251602a6dfde9041", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c49ce737c28b866965703d67e24206a0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "11203b5795c3e5767eb8cc901dfd315a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F2" + }, + "result": { + "position": { + "x": 23.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a2934a4b9b57fb267d6e13bb098247bb", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c9ec96152ffa6b441fd20700e06fa57e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dd4c91a1885df077a87b0f8243c3bb6a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E2" + }, + "result": { + "position": { + "x": 23.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3037c0a20adddf74ee7d4fd2a25b00c5", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5b62493686a068a87a958e8e19d213b1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1e1655c11fbd103872722c2c2a3e65fb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D2" + }, + "result": { + "position": { + "x": 23.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4a7bb648e4d350769315f6edcba980ac", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a96b735ef4b542daaa9536ce097527e2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2a0ed0e72b2f18b45f9ec248265fe016", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C2" + }, + "result": { + "position": { + "x": 23.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8fc130912c080523fa9ba79032911b1f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5efb88c17db8bbc70142fc16cc215849", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "65178a1567f99baf562cbeb9cadbad62", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B2" + }, + "result": { + "position": { + "x": 23.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd851bade975a3d12e210de566e7f700", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "880ed4bafd9dbcf09ce318eaba4b263c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "42106772f98b669e37544bab155cdcd7", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 23.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e1b8ed9f5c44726d00236c9b295516a0", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "23ca49d1754272e88fe099bd40d9faca", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "877ec65acfb065390e4bf7a8c074ee7f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H3" + }, + "result": { + "position": { + "x": 32.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0ea8791cbeb5e701cd01d69d967e27dc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "abe76eddb473705df349cb8340b876ed", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "af90a8dc3ca0d9f459c30bdc39c7c992", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G3" + }, + "result": { + "position": { + "x": 32.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "818a6fe4c7599c8a5096014bc6ced7b7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1f11643d98f7bd0c8cbb399a0209cd0f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c05d5f7d36844a586f6f9a85a057c2f0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F3" + }, + "result": { + "position": { + "x": 32.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2baca8c299a4fbff7f4bf298375d4de2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f506af465640277f3de0163b9cf8dc17", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bcaa9233840b62dd04a4e14ed836ee47", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E3" + }, + "result": { + "position": { + "x": 32.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8a5f8327a31b45a433dbfe0ab78e5f6f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cae6759d694ff670eb4d392fcc61c884", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "466bcc4e60019b0a4b5f3f050838fdf2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D3" + }, + "result": { + "position": { + "x": 32.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "72cebe7829c0a9b61f6941a748fc5a3c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e0b44a7965c6d4624b28350a1b2a6d38", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ccf5af1e159790b6d5bfe8d5c7910611", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C3" + }, + "result": { + "position": { + "x": 32.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "148275ecdd68c79e0aac66fabd3f788d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1855200817e768061064bbaa3162403d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "783697fbf5bd51a9e5bc200463a7efcb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B3" + }, + "result": { + "position": { + "x": 32.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "76f2dc0e1f07df7ddfa8dab116baa5e3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ee86912a612728dd716b40bf11146365", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a0a3622471dc28820343dd9badf5fc30", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 32.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "534352903657980cbdbd1e533fdb99b2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7e4a5282ba9a63766f590e20ef2fd9be", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0612c83cdced113debafd2ffb1f5738a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H4" + }, + "result": { + "position": { + "x": 41.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "20442d68f77b39013a96faf51235b3a2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4e5f5475ad6bed9f86ec38fb4b49b2aa", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ddd1abb6a2ea73ff2bbf07d483424e2d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G4" + }, + "result": { + "position": { + "x": 41.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ab6efce78849e1855ad3f3af9d90cf9b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1cb40e09400f7056ecfdc07ca3465fcb", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "372a86276e5755b4f74ba21c1c9ce55b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F4" + }, + "result": { + "position": { + "x": 41.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3685722ec6fb831432d56d2b9842a352", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d100dd3a15e63cf14cafb3d2637f4fb4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e1b535e3c9e207a40223b0945b67289b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E4" + }, + "result": { + "position": { + "x": 41.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "126e055f234cb4d2aa5f4b9e39dfdc2d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "480c54697bb7831d6b41194d004cdb49", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ca464193568b9af4c04fd6d0acdff9f9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D4" + }, + "result": { + "position": { + "x": 41.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cf89114d77ea21feb0476081687dd075", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d7bee3088b8f14c5c82a34482d907bef", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e996252f8e620a518c561f6c7cafa027", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C4" + }, + "result": { + "position": { + "x": 41.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "81cfd4a93a9958aa6d670bef0c190b04", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0ebbd2aed8406663086203730813171b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "96958a532d34f35c680ad217b218a360", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B4" + }, + "result": { + "position": { + "x": 41.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2920fd70277f48be0c4c61660279a038", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "02ad9354623f6a4096396d1b26a35ae3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "82744fab02f43397c58c3d7f01882b94", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 41.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ae61c6f3bc189dd71547d9d0597aab19", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "29b8b68fbc24cf23eab2ce551dcd139b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e8c42b39a5e157e930639a6011fdd393", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H5" + }, + "result": { + "position": { + "x": 50.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3c235fde533078643982e46248459e38", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "df9c114032b36f846730ed5fbc420f62", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "aa3884c750256c60467df6e8bdfec403", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G5" + }, + "result": { + "position": { + "x": 50.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1226a569fc64a585766ade98018f6470", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "43812b7d3eff4e1b1e5a5c7d00f42ed4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7fecb063320c0fdf66e4ef56080bae08", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F5" + }, + "result": { + "position": { + "x": 50.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "295ff8d6b69633ac9c10cca2fd7715ec", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9043e2a4adeacdf553dd6f4e8abb4cf6", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1d0eeb0458cb80d28ce2bb418d16d2aa", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E5" + }, + "result": { + "position": { + "x": 50.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a34acec5dc987ee4b8b148ed9eafc473", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3489ceac1aa5626b9bc354891702e81e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "28e3f3e46980221eeee61caf4d3f8b6c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D5" + }, + "result": { + "position": { + "x": 50.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d9682c82cd756f0e4ab7b44ab122f97e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "efd7fe01f5363818b972cc829a9d9b2b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c8db6b4c0fb5ed1517de2b549e46814f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C5" + }, + "result": { + "position": { + "x": 50.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "991d1ea970ea7c9a3388975f1e65d2c5", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "04e1c6e9308b7c8f9d8a72bb7eed986d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e028469328577914d6979c7ca54df9dd", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B5" + }, + "result": { + "position": { + "x": 50.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "29096d1e8bcea20cdaacb42f04f88c9a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4ff3a1e507f48ad655a393904bb87ec9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "263ffdf13dc7d4325789eabdd3880d92", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 50.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "82e90fca23d05f6b505ceb2ff3530ce8", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fcdd158004042b340611f877ad80e876", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1fb601233b3c65ed4f67174200d5d854", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H6" + }, + "result": { + "position": { + "x": 59.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "52a68e26f39e2c83e8c63129eab68548", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e7a55276ff8a13c1557caa98a8cbe1ae", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c579dcc422d614bb875bdf1da40a7cc6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G6" + }, + "result": { + "position": { + "x": 59.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b6075b8cbc583be3c6cf772e5044fcf8", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6abdb95a8ce7957fa25c9d17a771abd9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a9b11c6b0cc6122cff6881b9bb48fbe0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F6" + }, + "result": { + "position": { + "x": 59.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dbb5b121636123e25bf643562cb2f29d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f97947d8dd29c2610682ef7a5b4758fc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "099d5e23d1a810e21b274221dfd8ca63", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E6" + }, + "result": { + "position": { + "x": 59.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2d687727201d2d5bd885f0013d0b6bd6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b673e09d1e15cacca6496055d8ce1e09", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5b7f37166b7cc232af1ff2ba90bb52ae", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D6" + }, + "result": { + "position": { + "x": 59.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2b2876f3b126ac832921b92f891f02fb", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "615c930997aa3d7512a4c6a7f992db7c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2925bedd4a0f399b96ba1b791052d891", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C6" + }, + "result": { + "position": { + "x": 59.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ded827c0cfc1eae8370fe5a530030907", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "deb75cf16192c99b4436ed9eac91a751", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3d0616c70492909c417a501fcacac5b0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B6" + }, + "result": { + "position": { + "x": 59.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "013c3a9025583fa9b715fb9a30f97936", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3ebc3d7a4d2725d96d03c3d4c136e771", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0105ab201af86161f28a72dc6c1674e1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 59.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e0873c6907eca0e84a1c0a998d0ae8a3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "54ef7582dff334bf42fc179b1cf597d1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6e01967f5c5b810d7497bdcd1423f7e5", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7dbd3a0f1bd6f9f3340d5efb2ec353a7", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H10" + }, + "result": { + "position": { + "x": 423.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7b69a80483ad36b7bb62bb517303f137", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2755964b641b9a23802a7c3398db397a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cf6e6aaa146bc592acd3f0b1b640d25f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G10" + }, + "result": { + "position": { + "x": 423.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9b36669c9d0b9c9775431a58931d4a93", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "959b38b1589e99d2ba9fffe679cf79da", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4db5b14be34ff233843566a30e9eaaee", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F10" + }, + "result": { + "position": { + "x": 423.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c6890e42b3cbae9c98d53902b6fcc1c1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "90c9ec7b540a5860ed44e7a40a1ef8c5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "561afb0724a474bea02119dd0a80d887", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E10" + }, + "result": { + "position": { + "x": 423.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "79bc5bc21de8d32213cb7896c91faedc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8ef16139ea7d5488caecccd31909aa81", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f81f7e6b269822c9c2a5e3262e2324e1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D10" + }, + "result": { + "position": { + "x": 423.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "35abd555b9c91db0773a7a773a3ba821", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "074158002233c573cd4fe5d11ff8c503", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cb0042c149dba68c3c92d9e4ff5c0610", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C10" + }, + "result": { + "position": { + "x": 423.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0f21c96834f593aa1cc740b1dd77cdb8", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3ecc6454c6f3b9469f7ad3b4a072a31e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7db567566187e75f8b71f3452bbe9974", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B10" + }, + "result": { + "position": { + "x": 423.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "12dc3844186b29284e2df7f34a1d3808", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d5ce869d32df699540e6dc6072b5273f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3089e6a2190cc061fb01670b31edb938", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A10" + }, + "result": { + "position": { + "x": 423.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b3c5eab02d004666a7593249419ad052", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3bc1c65f78610974c641885299e2d8c6", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0d2f1d533572453a9c50d7e9cf9eea7d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H9" + }, + "result": { + "position": { + "x": 414.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b81175fca1af5b65e3ea24f1b6f24b72", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "702f89dd9bf1dab711a35c4ab3c40a75", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "acbc8d05a25092366f271273fb06c899", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G9" + }, + "result": { + "position": { + "x": 414.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "529a30b986d159a2fd52e7bf44bad759", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e6d4ecd4b31375919d82436705c68cae", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "074a35994b4981f50932940aa60114c0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F9" + }, + "result": { + "position": { + "x": 414.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "374dd0e8e493d3c56468d5c34ad3e80b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "71bee6d12397b150bdc3ce4721b03304", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ee49b5796a9a33185dcba1de6cb611a5", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E9" + }, + "result": { + "position": { + "x": 414.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f3db0e6fa845da099dba7cdb85d24daa", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "00800ff335719cedf79ec269318ae551", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "25cfc86a090f26d7414f0effa28e3de7", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D9" + }, + "result": { + "position": { + "x": 414.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "07f92974ce698d73ea7b8b9787f73192", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "da356919f097a5ad8e256e24c2132236", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5c71735d0d306348ea67eccde1201082", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C9" + }, + "result": { + "position": { + "x": 414.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b2a4b075218021405560f3e1795926ad", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d225319a077991ae40e075bf17136ba7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4164f12fb323c73275b6db3d87befa1d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B9" + }, + "result": { + "position": { + "x": 414.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1c154e60f01c49bceb3ef7b2c207599e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c63af70183bb9873621961297a41c369", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "150512268b54877a0bfa519cdb528756", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A9" + }, + "result": { + "position": { + "x": 414.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "64a8bed4d87c0f272423490631b523d1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "68a566da4eef0177dee39de8e16ae224", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7420018f9eafd5c9beff377f0aa88d9e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H8" + }, + "result": { + "position": { + "x": 405.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a3d61c332e1868a3ab6a1a26996fd4fc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "03eabcb52c6864d38e5fc7d25eb39b9e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8694b98222b4c14c108cfd4a42078bc9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G8" + }, + "result": { + "position": { + "x": 405.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cfb900a1a396223689a1072fa768363a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "37cc84cdf16ce2f9a503ea14d855d9dd", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fb271d9e24017d9a51f2bba5296b7673", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F8" + }, + "result": { + "position": { + "x": 405.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "645d96fa0f311b0763610442acf19c0c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "aa422cc71428ba4b5b3f42538303e29e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0f8f1cb8972b3131edac300e6960bc11", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E8" + }, + "result": { + "position": { + "x": 405.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e154132dfb6dee1156d2aae43721ba34", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "057500321e55c1407fa63327f3d52ae6", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a92430c44f81de8ef4b84791b4407657", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D8" + }, + "result": { + "position": { + "x": 405.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "418804e8aa37c95ff88fef5daf7376a8", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e7717649a000decbc244d2f4e74dad33", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "eca110999ae7fdc4ddea9979e1724175", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C8" + }, + "result": { + "position": { + "x": 405.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a70d9ba30bbe320a02682ad0528bbabe", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c4b22f8d39e4b8ac5a12631ad7497f35", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1737cd414181f4f7836c6db330381eaf", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B8" + }, + "result": { + "position": { + "x": 405.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6c358889668f79c49f6ba6ed7e87860f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "92fe9864c7e6a5cf60e7bb7d455ffa4c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0a77bd1d1dfa444d9430e44630f7bdef", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 405.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b5990df971afff4d737a85c5041fedca", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "942381538cd4e0006587abe44b2e0450", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9d646859c0068262d2df79a733378e0e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H7" + }, + "result": { + "position": { + "x": 396.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1c8274f3e398238341a67fe41fdb10d0", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c5e2bbbbd454a138fe7d42ae9e8839db", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0ea1904f1bf91e45d8d633d31843a194", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G7" + }, + "result": { + "position": { + "x": 396.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d39ab7e211f9d2b9c331550e2f253d14", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "55ae7743eea78cf18367814540a68f62", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a95c59a706d3a4315f14675e89d57994", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F7" + }, + "result": { + "position": { + "x": 396.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a5d4347389185aa9b15c773874b0290a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6101af4154d4b092b792d146ad1f1e8f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "560bc5ab0a8a4bb0446d981a9609a7c8", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E7" + }, + "result": { + "position": { + "x": 396.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ee3cb767f5d5310613e5d641696f8ee2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "17fd702202c9824a40e4a697d380a183", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f31d528f4032524ae0be891705b85f19", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D7" + }, + "result": { + "position": { + "x": 396.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e8757776e617fd15e6fd1260e98ed6f1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "34688f0879134d5990d66299beeb97a4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1562ce3d26b4cced31d414c5cb9dd1e7", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C7" + }, + "result": { + "position": { + "x": 396.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3e789171c5d344d9e05e769d0e86a7b7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "917bc650dad124fcbb9a2438bdff60a3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b892e4217ac02abc6c06b8a77387385b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B7" + }, + "result": { + "position": { + "x": 396.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "295f71cd158ac77904f756c4060e6793", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0f4756185b4a5e17519181ef5ab3d66d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "61b015a9d363bb33fabf5a51d536c0e0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 396.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "286f54cab33dadf13eadf628712f47f6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "547c934ed4e83bdde9490c6a700ad333", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ba3eaed7e661cfafbf45410e8329eecf", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H6" + }, + "result": { + "position": { + "x": 387.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b40c9301228ec24c33fbd11f4fca25c1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a6af618fbec807d6a91a446107333bc9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "524cadcb2745e374166f453122db34b1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G6" + }, + "result": { + "position": { + "x": 387.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ae912dbb73244b0fc7b0c5d4a87dd005", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8e53ac696cfc1678263b514d68e0f426", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6fbffb6371920fb462cc219082321f42", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F6" + }, + "result": { + "position": { + "x": 387.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "74dcb77a6f8b803535899163ecbb2454", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9507fe3156aa1878f60f0f900b65cec3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ef85ae3d1eae94f84df2bd421f42ab2c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E6" + }, + "result": { + "position": { + "x": 387.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b228de93f81fe5783c520f635d6162d0", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c21eee7bdc17e3d47338d3d42e5ba792", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f045691a392d7aa568afb79f5e5806f6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D6" + }, + "result": { + "position": { + "x": 387.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ef5dcf3662be2bfef68c908147c2440b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7863d112bfb165c1b8829aed536a0ab0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cd5de4016c0d17b1d2472c9e588f83bb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C6" + }, + "result": { + "position": { + "x": 387.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d5e945200930eaf799b9ecb549dcb305", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b89da5e63b789e6935fad4a917b6c439", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0394a1f67d7487c34546da9a0e1e68cb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B6" + }, + "result": { + "position": { + "x": 387.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0985201f92b9b04036e56710e36b99e9", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7a7f83555ce072ef82bc1c4f9919f3ba", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "16bd83cd235f3023c44b570a92be3bee", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 387.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4639e7c42217f3cf195c822d6f683862", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7001052cad1700875411cc50e226b8db", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e20911cbafb8511028363952a08d0e01", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b0937e3920af4bbe7619104e0d9ed7d8", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 342.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7ed9623020a12eab5d87ca4f677fccdc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ca8dcb166431f5a64e475e5ccbf02d3e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6f0fbbfc969b9d0a6d4940d51eb5f9fc", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 342.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a88b411fc3096956860d7910187e4623", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "35542ca9fcf3e26d9faa954457d8a281", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a6f27b91605214d9c0f91967f70246d9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 342.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "86a65b7983710e9adc4b8a1e3734b3c6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "981eafb5d50bb58889a96847b696de38", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "373c711bac00413b1d9c6e5884a190ba", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 342.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "01ce7f1730d961bee936397a9b39b65b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7d7998a5b09b5111a4f3d7e56a80e015", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "25a6fb51d2aeb1605c4a377bc50d3b54", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 342.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "341967128fa10a2e0680a1065701550f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a82e63ba42890436c622caf769a53103", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e43f75ab09e84d2638261f62a6921642", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 342.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "792a53502535810427d82e7d3423a48a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bba2a3713140042d16c03890ca6c3d8e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "899377fe4b4b5ab4528d0a7fd194de63", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 342.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c0bf003c89da88422ee9df786a85c06b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a7f6d088bb233ec54bc98038fc32026f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d0ab802ab5d39e9a713196791acc8200", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bbf9a01c731023679ad4484b83802cdc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7392a8c8df856d893da74f2621fbd591", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "926dbdd0e4c516cb64ebe64787edb2e0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H2" + }, + "result": { + "position": { + "x": 351.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0acaaf7343ab5bd96f0fb73b91516f65", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a3da180aff8b2c506c9548284ebf5fd8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "528f910e2594ad18fcbbb138f2d0e36a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G2" + }, + "result": { + "position": { + "x": 351.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "372f12bfdbeb9aa89636015435183c2e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4de43ece7caf62a37066e3a1cb0faed1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b2b75aa20bb25917a3e1944938a85356", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F2" + }, + "result": { + "position": { + "x": 351.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "340aaa73f03e647f31a449fdb570f47d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b429013d61d1a7b9e91996d9bd98f341", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5df928c66e7ebaff8eba953271ef0a5a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E2" + }, + "result": { + "position": { + "x": 351.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2283928e7b62ce3d8a1e371e56027121", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7e823c62d6e696c9f3e1c67a352b214e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "230f93a6870a7adfb95604c909bb0932", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D2" + }, + "result": { + "position": { + "x": 351.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cb7755c8237bab422004256f267b7b3a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8deccc90d10f738b937e3222a0e16a43", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dd4cc07a997c4d11ffadab8bbd199535", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C2" + }, + "result": { + "position": { + "x": 351.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "af3cc237a466f5e856df242607c8c0de", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "29dd7312ccb93a1141a057e247b7da54", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3981aeb8fdd92e72257f610b94205304", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B2" + }, + "result": { + "position": { + "x": 351.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ffc884b47bf3b1ceebec7a140f6b8c69", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bcafef636252e9d6c7a2cb8cac938b87", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "93de9992af90654531a8473481bf09cf", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 351.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f42524dd0650308e0a2c3314a450b284", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1912fb91471114ee063426cd9afb6e79", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "47bb707aabccb8c98ed07ed83c45778a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H3" + }, + "result": { + "position": { + "x": 360.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "071a72f5ec91378b82728bc51531f72c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "61149ed6ce5d2d567ae44f774c63e7b4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c191466b2a8ecc97c82559e954ec53b2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G3" + }, + "result": { + "position": { + "x": 360.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0f06420df2cda5288e7a0f8e1f1e537f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b8e26e43c0b8a01902ae020c821273a9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "76d7ffa2250469acb86f8471268a7357", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F3" + }, + "result": { + "position": { + "x": 360.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "82c22210037d35e8dd2526f7186005f0", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9a0c49941439a1470f6bd09f37d145e3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "677698fc7cf2f7f9c936cd7683698f3a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E3" + }, + "result": { + "position": { + "x": 360.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a821b799105c2cc7fc1267d65892be52", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "83cc563ef4916931fa00ceb7b0dffad6", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "26b835cb3014e34db5e9c3411bd35396", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D3" + }, + "result": { + "position": { + "x": 360.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6e3806ae0a6dadb2b276e3883b5a6ff2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bb7720c4baa6d9b0ef9ca774609248ba", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c42886adc207fc91d8c591b4861fe8d0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C3" + }, + "result": { + "position": { + "x": 360.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cd80cf4dd434caf0998a068b85e38ccb", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8d4ea09e7c2565e8d2b1461ab23fda15", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dcb7291ab93536dd17e34281a8c5373d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B3" + }, + "result": { + "position": { + "x": 360.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ae0423e2c46f49f1aea8c1d940046def", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "71c521c1005c550ae7acc2defe4985ba", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5670eb410c1e4f0400deb4c01efa89b6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 360.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d8bbc484d4535ca467927e32eb6ff30d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "164e85a77bce24e4ade7f0776daf5814", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f9cb893e2a1bbfa97b3856e6ef8e5eed", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H4" + }, + "result": { + "position": { + "x": 369.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5e9d82f8b7a0548c6e4f876bf43d45e4", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e094aad575fca03e911a3a00e15dbae9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2e1954c062b457db0f2feac2e14fc8b4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G4" + }, + "result": { + "position": { + "x": 369.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "286dca3d33a64fe089ed692b4bac3ddf", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "37b0a36b51c73851ccdf33201ca6fe24", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a032208841855326a8178ef84105ea91", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F4" + }, + "result": { + "position": { + "x": 369.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b5d0ce3403cff5e5fb04cd26ba24466c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "be466db458e74fc12846754b50216d6b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8d58d9a76c6c2c111bc8e2d3367d2d01", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E4" + }, + "result": { + "position": { + "x": 369.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "402584fcd38b2b99b478bfd4702be149", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2ca8673ad6d5fbf0a707c71d695baa7f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "88410674a560b5842c290a2a05c5b399", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D4" + }, + "result": { + "position": { + "x": 369.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fc8b3555b984e41076e3b172f54ff6cc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5718559d1f28f26a9bfde7fc112f74a9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "85ed8801921352be92c1092ff285959f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C4" + }, + "result": { + "position": { + "x": 369.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ac60738ae4f26eaf80a0a40c90ca64f6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d43087f090be4f00976373051317d2e0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "555a8dcc048868221b1bc3cc6df926ac", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B4" + }, + "result": { + "position": { + "x": 369.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fab04c4f54bf1a4251f034fe03d4cb85", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fdf82612ff980e9907246502e7f5058b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e777ec605fb0042886a1cee2e99c5ef8", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 369.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "98d348eaced3cf3d267b235a7956f38d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e1e605aa4bd6a4cedf87ff2db6914f4f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e40e38c2e49a9bfc6349402ab66c15d7", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H5" + }, + "result": { + "position": { + "x": 378.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8fd5140f861d5cb928817b70c52c7af6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6f99ff52649e3f849ca00f0e78914a60", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f47ad841ccfbae8970bb44614796edc6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G5" + }, + "result": { + "position": { + "x": 378.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e8b4a76a6c496a1925e9922248787b7d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "81be73e7d8d67a3b3d523d5f430ffb61", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0107324b1f779679a5022b667326774a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F5" + }, + "result": { + "position": { + "x": 378.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b7a794eda36026dde09ec4ea4e952a6c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2ab38a1cb45b1cf896584da4a536749a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "be0d9d914e2b70acfd905a89c9692aab", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E5" + }, + "result": { + "position": { + "x": 378.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "225f6c04d32e603573288067936d4a6a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d6eccdd7a873e6db96625351eb618dfd", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "46592708e01f0f923dd36ad018162c52", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D5" + }, + "result": { + "position": { + "x": 378.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "89de33f9164ab6a17d7675f6b5ddd7b2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cee6812da90b0732507b0d5eff0b8d0a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7423c8f6ca701c6499e528427c905cc6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C5" + }, + "result": { + "position": { + "x": 378.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "66247b61a54d7f6b398d4a11d42200e2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d6568d009d285a68d4907679900fc439", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1befdd9aaf3dd1e0550da86f1959ed87", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B5" + }, + "result": { + "position": { + "x": 378.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "16dc620122d17b59e770cb3f2ca6665c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f14aded4ebf3e32b07f790ccbdd25d4c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "11359c9722e9d55a9df1052083ac9b96", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 378.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "80db9f1422fca1c504fe022940cd4b39", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "529fe3907e0cee635b313fb86a5750a2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d2dda93bb1966de0a916ab53657f51be", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "addressableAreaName": "A4" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2d5b6ba42f927352b1bf1c9c3247cb7c", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73cbdf81b2329618b9a02facd27ab8cb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E10" + }, + "result": { + "position": { + "x": 423.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d8e0048b86b386c3cf7fbed77f103a8d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "771493b4c76a2ce6576a7ca183ece284", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ea323964f5dad9ccffff97c41975d3d9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D10" + }, + "result": { + "position": { + "x": 423.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "af920212f2bd0ade99d74f01932fc6f1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ae1a8ea6a629d587300b722d169c4886", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b02aae1e2d0bf39f311efc3bdbcac1e5", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C10" + }, + "result": { + "position": { + "x": 423.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "754e6104ead4f39190ce8dae92e0a2d7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f663a6c825a8b57af22b498b667c7a37", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9bb224ad9f17af4c5026037ed8fd520f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B10" + }, + "result": { + "position": { + "x": 423.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f962ead2152756e477b9a7ddaf0431e8", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4613cd8a14e55b2b70167aebeff2cec3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fb9f85008cefb60987e677920cb50f05", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A10" + }, + "result": { + "position": { + "x": 423.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8d4bff2efc7487527dc56de6558cda28", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d6642b30ceaa29201f485acfcba8874b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c4a5997a0853547e216396f09557fbbc", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E9" + }, + "result": { + "position": { + "x": 414.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "06f1994fcf44a2d7b7ce53b3fbc6fb88", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "78dee0c6eb213a83b04fe7b8a3ee60e3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e4d95a0424c909ae87bc23d35d597d5f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D9" + }, + "result": { + "position": { + "x": 414.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3efc677302f2729098a59a54946cdfb1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d4f02aaa88bc124dce24e059309d6531", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e230e4367265f02486a9aed4a65336cc", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C9" + }, + "result": { + "position": { + "x": 414.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3a025c8a11259fbe7857dfc0b49baba0", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "adbc478d45b41628e94b5395245b2957", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "540a5793f120c3f67d1f684596965cf0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B9" + }, + "result": { + "position": { + "x": 414.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a72a835031c98d97ae11c522179490fd", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8cf48279ef652f9528624573cef06a2b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7b5f4bc3bfbf1cba3a3a6bbf3c6681d1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A9" + }, + "result": { + "position": { + "x": 414.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e71eb989d35a51f7ec8a6daf34617efa", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d68d1c9ea9f3e4a0ec0daf7b0f038330", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e27675102957b4cb63f98ab91d9c7ed5", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3868ab1c535a7c62d346cb81e233d0aa", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 342.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1d80638cf0da1534f235aa0c193d7dfe", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ea7a8c70ef4f39161d991ec2161ce473", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a0cf930e28e72166bc2800b02649dfd7", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 342.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6085215f281f9121a8eaebdb7bc77d36", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5f3c2b62a92b4568ef62b5a1970dfcd1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "89f7dca9b8fb6340c94e74d0d861be1f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 342.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "47d4ada22b8ab83448ed24ff94f304a6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8666db9e335cfe8f3ffa02052d7e6790", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "63c6ac8b16588d07bef6bef5579b9297", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 342.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "303fd0cb0235b01895b4c54dd7ddc7f5", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b8c1f2b8845918db0e97bd6a656f24ae", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d66c80770fedfdba9d8461edaddb88ce", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9684fffe64a093ce8585cd42be67a07e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6a8d26b000134cc223f34009fcc7f115", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "aec438e91cfb0d9748b0b76ea2239df5", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E2" + }, + "result": { + "position": { + "x": 351.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "994aeb1e4c1823a00b7c63a3391efe12", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e9eb706de6c253ea0818b189807839b8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1ebab681646674b37a51b79d90a16e7a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D2" + }, + "result": { + "position": { + "x": 351.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "09a0da30dd760da3ad9752775399e569", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b3acfa0e8398e96e0754bb0ea98da2b8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e9eb56fb3ea086734c70754c04d321ca", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C2" + }, + "result": { + "position": { + "x": 351.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "13b5bf16f74cd266abae0e4f17613d5d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8487ed07b348c791d374152b446b2fbd", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ca8e42e57e7ca116a8dfe5bf0a4dd645", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B2" + }, + "result": { + "position": { + "x": 351.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8c475800a139f9326642270e06acbae2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "48028d9157d16f180cfec1f00a1f8dc9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "12058ef8ec196af9747306718d200ecc", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 351.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cf5cab6b7af81f0bcb5be88e0c6e5d79", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c22631db547ad09ac676e1788d5bff90", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a14d019655f85c97310c6524ec9ffde3", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d4e7cb68291e27a1f24a3455ba0c9b05", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 405.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "901af170b27283fef67e3573e4dd5bd5", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "eac45c472880dd2d22db92bdd18e661d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6323b627a98e5335780ca564b659939a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B8" + }, + "result": { + "position": { + "x": 405.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "85c87dcd75c43a491334684345399396", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3d7eac2e3cd50600dfbb68a591a68fbc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "432788e777d39b7ca9deefdb0014bcfa", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C8" + }, + "result": { + "position": { + "x": 405.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "173aa11041d13035eaedef7bfd94a857", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "78d8d762bb8d4393b512fa87c4829bff", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "74fe4f9f9ff7eb8b42f2be01ad5f19d7", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D8" + }, + "result": { + "position": { + "x": 405.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2e434f98998e9825faed5810b6072343", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f36dfb0274e4d45a6bc887d3cd3a2e3a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4d49deec216abc515b0b54a9feab4ef5", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E8" + }, + "result": { + "position": { + "x": 405.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "22535ef2f08f40f3552c2eabdaeff011", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "feb7608f156ecefd90b1eae3fa4e2545", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9001c5354e234fc7a8f6e6c4ed661e45", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 396.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "218dd92475206713aa3f0766713519a5", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0fa7d6c73ae8bb4e89dac2f86599eec0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "346b1fcb831aec9e2ba1d98d9f245341", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B7" + }, + "result": { + "position": { + "x": 396.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fff5a79b19d082c22f4be41759129d4c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2eec9d36f870a016a055cc32583cff34", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "943cc0579d5b7a06412296d4cb83287c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C7" + }, + "result": { + "position": { + "x": 396.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6e95646589b15267cdbc219b3de4fd07", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ebf8e73441f89b5ec73ebc663538df61", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "209e2151129b775391851f103e411e7a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D7" + }, + "result": { + "position": { + "x": 396.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dc853f9695d7810f92db085039dc6d04", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "00ad929bdb54040745d143ccd87bae7d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e532da39d332f39a9ce711cd8b268e46", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E7" + }, + "result": { + "position": { + "x": 396.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e8e2f1a9d8544ed151a82b588dba3dfe", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "edd344364f59050b273c4ebec1dadb17", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b9534f992947ce499bda0eff712950cd", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 387.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "45fe4b983b3c1de706bec152a214b4f2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0a76fa44f105392882cabbc07f7f073a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0249478fc14c18568c5144924e45d1b6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B6" + }, + "result": { + "position": { + "x": 387.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0cf220923a384dcf747c53d6952425ab", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cdd6446cfbbbd7b5e01b91e2220208e3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2dfa9a1b49bc515402daccab67cd4f01", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C6" + }, + "result": { + "position": { + "x": 387.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0ba82ca5f133111b0349b52445ae895c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dda485025195b674fc284a254a7f4c1f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2775221a31aa813108e994fe79e02829", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D6" + }, + "result": { + "position": { + "x": 387.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fb894933f6aefd7646f2b80d84c983f0", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d6da63260436dfefc42c45bee23fa512", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "99733a8b5afec002991fcaa786b3fd59", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E6" + }, + "result": { + "position": { + "x": 387.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b05df60be2caf97bc0822af4b8cfa75f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9ab5ff51b45bc34be4a9f64831956bb9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7c55b0195c4185722601329cbda61249", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "61e71d8116fd5362e76f5c38402eeb89", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 360.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "db7cebeb6f7f7cd452ccce25a3f090c0", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "93d63b4242ff008d4729bc8452d42afb", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3f83cab4f076e9d3810483ab035b28cf", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B3" + }, + "result": { + "position": { + "x": 360.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7ef2af786a74feb1a5b4a0ebdfd4238b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3a02c83f62d1ac7037956019dd111f05", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b8367f70f7958e19225082886a2bd981", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C3" + }, + "result": { + "position": { + "x": 360.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cf67f06e478e0f633829e90567360d69", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "25b5c8019503537d4091370c23ebd8d0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8e86cd67f5f1c50deb291876676c6b5e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D3" + }, + "result": { + "position": { + "x": 360.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bed3e1b915e2351366f8949d1b398782", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1ded024e4ef208dd947ee8688198d1fd", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6ee2ca5e157f4924da97a17e46915c9b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E3" + }, + "result": { + "position": { + "x": 360.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "03a8b3b5655fa79205722323d21545a0", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e813722f8f6270290110d6bb9859e587", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5e773ddb3f4e25e53f36621dfacbf08e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 369.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c1448312b1773ba15764230aabf8cc61", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7b99737eb7fa57cfa299a19c5cbf7927", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "858276422b785499c894bc1722aa1997", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B4" + }, + "result": { + "position": { + "x": 369.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b577575564bad863511a32b7144e4e0d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fc015272c344b3ca5db148219e4c6b93", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7fca504e083734021326b888b4bc6610", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C4" + }, + "result": { + "position": { + "x": 369.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "42ee6c141ab105d1fcc2073512e17dbf", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d6e777e1706e97b1b5882b1156e0bb66", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7e59fbac3ac205268e836a90edffd385", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D4" + }, + "result": { + "position": { + "x": 369.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3a23e407340d4539285ba4351f593e6e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "959569ad59d9c3ae34b614164f40d94b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "45f1bbc3107770a38d70e1d7f3ead45c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E4" + }, + "result": { + "position": { + "x": 369.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bf306abead081d95e4a192269b140230", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d5b0cd34a88a4a5d2f267aa476357fc0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c5d821692ad59d0fa1e46e864ab5cc97", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 378.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "302a6825e48d93d7a0e57be230d7f8d6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e32ba4e7b814c5fd9f4e093ef39b222e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "36e4b0a094c465342bfd68ef7555bbb2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B5" + }, + "result": { + "position": { + "x": 378.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "736a482704adb52db4dd700f29a3bfb2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bb05deac414a8ed549c043f290ceea11", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a986c49bfb22abe8e6989b328d949056", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C5" + }, + "result": { + "position": { + "x": 378.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "72108834e48b339bf07e4b6bd6c4d95f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "95c15c9ecfb267f98a0b6cd0bad65a3f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "22bf19af2ea3749c773fc9dfc147e542", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D5" + }, + "result": { + "position": { + "x": 378.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ba16adf0e01f2fefa0807994f4d83894", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4bb5907e2b9d8004130bbc681dc1beeb", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ca5931fb5f63397d65d9d6b976842e68", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E5" + }, + "result": { + "position": { + "x": 378.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1d1562e9860df353baffa1a1861dcb2c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bc969b48325a9173b608f5ab86ce7eb5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d2fe55dc114566815da41c8ba0d7ed01", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "addressableAreaName": "D4" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cc2f605bb3295a00501d383928a8e364", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "D2" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "633e183d9012553ad1809bfc06999d02", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "A3" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b9bfa86a5a8244e63655ffd7a450963f", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4700e418bd8c2ffea896fad82c64295d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H10" + }, + "result": { + "position": { + "x": 423.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2dcd7dc479fec3fd63f3b26e9e45dc05", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b0c3d37aac4f7a0b8172d487f1fc6f5b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a994d1fc8db2ebc39f8f98a62c81affd", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H9" + }, + "result": { + "position": { + "x": 414.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "25a825954529a800a5928ce1e33484a2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "69ce71aadb2f05176236bdf5fb550566", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cd9e65b037015c9fc0f84e49bee9f9f1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H8" + }, + "result": { + "position": { + "x": 405.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7db1b8c7eb24c173de8cd2735028fd63", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5d82c56715e0e1c7b6aedabd9f2663ac", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e9d1d18e882409beb58e37ce89f2d792", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H7" + }, + "result": { + "position": { + "x": 396.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "47b5d87c0b15e2075b2623657c95d9cc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9eebc15a777c2e97e54f3e5a8ce0c104", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6cd18667bfc31771d8722a01f0bff1e6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H6" + }, + "result": { + "position": { + "x": 387.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a80bccad5e81fd5898a0ce085d4f4a3c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d63a9d48529b5073845c250bffb9ecfa", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ad47b4391b17dfeeeed630cd2ea5fc8b", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d7a2ee27aa7647caee8c2ce4bf424acd", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 342.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1ff853b12567a852f5771a91133fa3d4", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c40b60afea39e27c42fecef375da9295", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bb22d4b0945d05ebedf42ac9cf72de89", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H2" + }, + "result": { + "position": { + "x": 351.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ca49d073071619bbcd042230f3ebf4c5", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3495edb8ce7ece223d069f5430194aae", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ef035a90a6b88674f6501ca27bcae33c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H3" + }, + "result": { + "position": { + "x": 360.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d6143666860a09f5f270ffff2ac08487", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cd3ca017f71cbff64f6842e3875ac4a8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "96a1201608ccb5e913321b49b4d934ad", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H4" + }, + "result": { + "position": { + "x": 369.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d63d69e7bb71b538495154522e124fdc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f84bf3252b2798569437c702563f6e2e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "835b162c41e1ac0d075cf2cbc687bed6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H5" + }, + "result": { + "position": { + "x": 378.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c59e1e3d168ab2eeb85fc63fd4e8fb8", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "06ad657f3dda981932c591055f13f326", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a1a15e7f8b141c887ff70c444aae7ed3", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "B2" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fe757ed1fa44666d54979161797ec39d", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H1", + "style": "ROW" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2d3961a6cbf87adbf3a35c65ae78b5de", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 178.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cc978ccc9e5d7a64c00dc43a6c45da96", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3fa3699fd7e32e51208e9252253a9429", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e4a0bf4a1aeb2911f441bb657d95da1a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 178.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "77b3c8fa88495bc30cbc3c38e45d633d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5f0f652e1b63d96f68c48806f36dc499", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "91f8ae768aaa6504e68513b56792d3e4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 178.38, + "y": 270.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f501e8b4e0a2a34ec63b349605c21f4a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cb390aa7298262f6d68a7566df4b8300", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3be5a2eaca6153b0dd6c1497cada434d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "45a6f379ac67d6bd897c451233cf0415", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ffb6a397ba17f325e856412e8a0d9645", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3eeabcf1a927ed5e3cc89b311ccbd001", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 178.38, + "y": 252.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ff3dcaf653544e8a99afc3ba1560258b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5432505fcb798867655ee2b04dd5152e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2cf6a56ac40778276229458d25964bce", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 178.38, + "y": 243.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "92212e8dae667dfe61e8d360da7e39f7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9b61c4b6f907735084e434621e6724a1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "917a84f401daf656bb9a192269e270e3", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "052f464e0a10c3c0f823369ee761d120", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9b55d869054cedc0921a9040eb53a594", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "834a19a05914f4b7cdab77548b69642d", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "C3" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f418041b1043d09e3f78480832e8eb37", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "A1" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4c61b023023208d28a31697120f5025c", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "22a8ebc62b5faa76a138cafc451c5306", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H12" + }, + "result": { + "position": { + "x": 113.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6615eacf6dc74dbd805e6d7b1002edd7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "23db3dda8d5e9dce4b0393cec67c0938", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ff94a12288debec3748142f51509f6f2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H11" + }, + "result": { + "position": { + "x": 104.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1c4251a2b539ab053ad0519af4621672", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "72cabeaa6de17e253774ef9126a24bc5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "25727b16cca23eb448e157ce1c3704bd", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H10" + }, + "result": { + "position": { + "x": 95.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4d6716fd265e148c4fa2e47ac0b259ed", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "38693fcc9dcaa6910654c14ded4e9f84", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4630bf7e11af5cde23ca3cadeac80e23", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H9" + }, + "result": { + "position": { + "x": 86.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d034ec1445544bc84ed7cc71830ca2af", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a877dc5069115b586b611ecbc73db1be", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6e86f95e7c85bbaa107edd2a1e282eab", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H8" + }, + "result": { + "position": { + "x": 77.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "480b681945214388512545afb589d2e4", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5568d8f9de34ab32ecb792657eeaad5a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "55772ace7bb27890823195c1eb9bc6cd", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H7" + }, + "result": { + "position": { + "x": 68.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c3db6379991de2c177b4eeb5c68a6924", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d1df57569ed3b8f26912aef93b738294", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c884bd0a43bdfb9f2d8d6e82f015d3b6", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "480903e9ad10b20440c766bd3c1e40e5", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 14.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3d51375fb89843b06e38477ace5fb3a0", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "896fd02263673b40d86ff01a5de8a169", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "422ed8642bac64a84f425649d70a885e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H2" + }, + "result": { + "position": { + "x": 23.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c7b65b463fff9cdc9768c71e6869ca0f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5bc183d00f2791ff60254bb7d2cdf98b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "92b95c1912905a3b360a918b4de886ed", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H3" + }, + "result": { + "position": { + "x": 32.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "40d21c65925f17ed3fb761f2bdfbd0e6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6e6d9cd182d5890bcb21d77eb2409038", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5dc4b08c3d4bd692055f0f0b1a70a6fc", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H4" + }, + "result": { + "position": { + "x": 41.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fb04a83e5d255f980694218baa92d76f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "be8d296ef3d8e0a86f1d466e3fff8336", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7482b15057987979ab99b25cba533ba2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H5" + }, + "result": { + "position": { + "x": 50.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4a6c319ff0ffc9f3491fe02fc06ec8fe", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "20054d01b064fe8e0f42f0730d66d1a1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cc69f9f62eae9d852d23131aee055ce3", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H6" + }, + "result": { + "position": { + "x": 59.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "381535c145f0445202baa2766d46ed52", + "notes": [], + "params": { + "addressableAreaName": "movableTrashB3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 257.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b94c4ce70f53fb36fe77c397e17247eb", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [], + "files": [ + { + "name": "Flex_S_v2_20_96_AllCorners.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "displayName": "A1 Corner Tiprack 1", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "C3" + } + }, + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "displayName": "D1 Corner Tiprack", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "D1" + } + }, + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "displayName": "A3 Corner Tiprack 1", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "D2" + } + }, + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "displayName": "D3 Corner Tiprack", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "D3" + } + }, + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "displayName": "A3 Corner Tiprack 2", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "A3" + } + }, + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "displayName": "A1 Corner Tiprack 2", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "A1" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": {}, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[11020a4e17][pl_Bradford_proteinassay].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[11020a4e17][pl_Bradford_proteinassay].json index f0092eb9069..7ce2978d56a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[11020a4e17][pl_Bradford_proteinassay].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[11020a4e17][pl_Bradford_proteinassay].json @@ -7313,7 +7313,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7359,7 +7360,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7391,7 +7393,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7424,7 +7427,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7457,7 +7461,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7504,7 +7509,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7551,7 +7557,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -7598,7 +7605,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7644,7 +7652,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7676,7 +7685,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7709,7 +7719,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -7742,7 +7753,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -7789,7 +7801,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -7836,7 +7849,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -7883,7 +7897,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7929,7 +7944,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7961,7 +7977,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7994,7 +8011,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -8027,7 +8045,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -8074,7 +8093,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -8121,7 +8141,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -8168,7 +8189,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8214,7 +8236,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8246,7 +8269,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8279,7 +8303,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -8312,7 +8337,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -8359,7 +8385,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -8406,7 +8433,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -8529,7 +8557,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8576,7 +8605,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8623,7 +8653,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8746,7 +8777,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8793,7 +8825,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8840,7 +8873,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -8963,7 +8997,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -9010,7 +9045,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -9057,7 +9093,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -9180,7 +9217,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -9227,7 +9265,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -9274,7 +9313,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -9397,7 +9437,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9444,7 +9485,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9491,7 +9533,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -9614,7 +9657,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -9661,7 +9705,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -9708,7 +9753,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -9831,7 +9877,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -9878,7 +9925,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -9925,7 +9973,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -10048,7 +10097,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -10095,7 +10145,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -10142,7 +10193,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -10265,7 +10317,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10312,7 +10365,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10359,7 +10413,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -10482,7 +10537,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -10529,7 +10585,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -10576,7 +10633,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -10699,7 +10757,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -10746,7 +10805,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -10793,7 +10853,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -10916,7 +10977,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -10963,7 +11025,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -11010,7 +11073,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -11133,7 +11197,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -11180,7 +11245,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -11227,7 +11293,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -11350,7 +11417,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -11397,7 +11465,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -11444,7 +11513,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -11567,7 +11637,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -11614,7 +11685,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -11661,7 +11733,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -11784,7 +11857,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -11831,7 +11905,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -11878,7 +11953,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -12001,7 +12077,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12048,7 +12125,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12095,7 +12173,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -12218,7 +12297,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -12265,7 +12345,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -12312,7 +12393,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -12435,7 +12517,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -12482,7 +12565,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -12529,7 +12613,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -12652,7 +12737,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -12699,7 +12785,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -12746,7 +12833,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -12869,7 +12957,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -12916,7 +13005,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -12963,7 +13053,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -13086,7 +13177,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -13133,7 +13225,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -13180,7 +13273,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -13303,7 +13397,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -13350,7 +13445,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -13397,7 +13493,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -13520,7 +13617,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -13567,7 +13665,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -13614,7 +13713,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -13737,7 +13837,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13784,7 +13885,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -13831,7 +13933,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -13954,7 +14057,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -14001,7 +14105,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -14048,7 +14153,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -14171,7 +14277,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -14218,7 +14325,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -14265,7 +14373,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -14388,7 +14497,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -14435,7 +14545,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -14482,7 +14593,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -14605,7 +14717,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14652,7 +14765,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -14699,7 +14813,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -14822,7 +14937,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -14869,7 +14985,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -14916,7 +15033,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -15039,7 +15157,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -15086,7 +15205,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -15133,7 +15253,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -15256,7 +15377,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -15303,7 +15425,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -15350,7 +15473,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -15473,7 +15597,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15520,7 +15645,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15567,7 +15693,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -15690,7 +15817,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -15737,7 +15865,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -15784,7 +15913,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -15907,7 +16037,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -15954,7 +16085,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -16001,7 +16133,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -16124,7 +16257,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -16171,7 +16305,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -16218,7 +16353,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -16341,7 +16477,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16388,7 +16525,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -16435,7 +16573,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -16558,7 +16697,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -16605,7 +16745,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -16652,7 +16793,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -16775,7 +16917,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -16822,7 +16965,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -16869,7 +17013,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -16992,7 +17137,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -17039,7 +17185,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -17086,7 +17233,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -17209,7 +17357,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -17256,7 +17405,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -17303,7 +17453,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -17426,7 +17577,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -17473,7 +17625,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -17520,7 +17673,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -17643,7 +17797,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -17690,7 +17845,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -17737,7 +17893,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -17860,7 +18017,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -17907,7 +18065,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -17954,7 +18113,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -18077,7 +18237,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -18124,7 +18285,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18171,7 +18333,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -18294,7 +18457,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -18341,7 +18505,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -18388,7 +18553,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -18511,7 +18677,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -18558,7 +18725,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -18605,7 +18773,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -18728,7 +18897,8 @@ "y": 0.0, "z": -36.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -18775,7 +18945,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -18822,7 +18993,8 @@ "y": 0.0, "z": -8.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -19180,6 +19352,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Coomassie Brilliant Blue G-250 solution ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[134037b2aa][OT2_X_v6_P300M_P20S_HS_MM_TM_TC_AllMods].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[134037b2aa][OT2_X_v6_P300M_P20S_HS_MM_TM_TC_AllMods].json index 3503fd4fa30..d33b6cf51cb 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[134037b2aa][OT2_X_v6_P300M_P20S_HS_MM_TM_TC_AllMods].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[134037b2aa][OT2_X_v6_P300M_P20S_HS_MM_TM_TC_AllMods].json @@ -6150,7 +6150,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6183,7 +6184,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6280,7 +6282,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6313,7 +6316,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -6410,7 +6414,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6443,7 +6448,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6540,7 +6546,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6573,7 +6580,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -6670,7 +6678,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6703,7 +6712,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -6800,7 +6810,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6833,7 +6844,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -6930,7 +6942,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6963,7 +6976,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -7060,7 +7074,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7093,7 +7108,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -7190,7 +7206,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7223,7 +7240,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -7320,7 +7338,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7353,7 +7372,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -7450,7 +7470,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7483,7 +7504,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -7580,7 +7602,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7613,7 +7636,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -7871,7 +7895,14 @@ }, "id": "UUID", "key": "675eb8be-6c85-4204-9c87-d7fdd522f580", - "notes": [], + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], "params": { "moduleId": "UUID" }, @@ -7966,6 +7997,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[13e5a9c68b][Flex_S_v2_20_P8X1000_P50_LLD].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[13e5a9c68b][Flex_S_v2_20_P8X1000_P50_LLD].json new file mode 100644 index 00000000000..8fece97c06c --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[13e5a9c68b][Flex_S_v2_20_P8X1000_P50_LLD].json @@ -0,0 +1,6242 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "A2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50de88d471ad3910c29207fb6df4502e", + "notes": [], + "params": { + "loadName": "opentrons_flex_96_tiprack_50ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 50 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_50ul", + "quirks": [], + "tipLength": 57.9, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a83392f86baf8cd5b4f0157c89d31dbd", + "notes": [], + "params": { + "loadName": "armadillo_96_wellplate_200ul_pcr_full_skirt", + "location": { + "slotName": "C3" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Thermo Scientific", + "brandId": [ + "AB2396" + ], + "links": [ + "https://www.fishersci.com/shop/products/armadillo-96-well-pcr-plate-1/AB2396" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 16.0 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.0, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "Armadillo 96 Well Plate 200 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "armadillo_96_wellplate_200ul_pcr_full_skirt" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.95 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 11.91 + } + }, + "stackingOffsetWithModule": { + "magneticBlockV1": { + "x": 0, + "y": 0, + "z": 3.54 + }, + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.7 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 74.24, + "z": 1.05 + }, + "A10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 74.24, + "z": 1.05 + }, + "A11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 74.24, + "z": 1.05 + }, + "A12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 74.24, + "z": 1.05 + }, + "A2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 74.24, + "z": 1.05 + }, + "A3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 74.24, + "z": 1.05 + }, + "A4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 74.24, + "z": 1.05 + }, + "A5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 74.24, + "z": 1.05 + }, + "A6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 74.24, + "z": 1.05 + }, + "A7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 74.24, + "z": 1.05 + }, + "A8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 74.24, + "z": 1.05 + }, + "A9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 74.24, + "z": 1.05 + }, + "B1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 65.24, + "z": 1.05 + }, + "B10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 65.24, + "z": 1.05 + }, + "B11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 65.24, + "z": 1.05 + }, + "B12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 65.24, + "z": 1.05 + }, + "B2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 65.24, + "z": 1.05 + }, + "B3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 65.24, + "z": 1.05 + }, + "B4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 65.24, + "z": 1.05 + }, + "B5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 65.24, + "z": 1.05 + }, + "B6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 65.24, + "z": 1.05 + }, + "B7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 65.24, + "z": 1.05 + }, + "B8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 65.24, + "z": 1.05 + }, + "B9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 65.24, + "z": 1.05 + }, + "C1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 56.24, + "z": 1.05 + }, + "C10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 56.24, + "z": 1.05 + }, + "C11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 56.24, + "z": 1.05 + }, + "C12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 56.24, + "z": 1.05 + }, + "C2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 56.24, + "z": 1.05 + }, + "C3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 56.24, + "z": 1.05 + }, + "C4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 56.24, + "z": 1.05 + }, + "C5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 56.24, + "z": 1.05 + }, + "C6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 56.24, + "z": 1.05 + }, + "C7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 56.24, + "z": 1.05 + }, + "C8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 56.24, + "z": 1.05 + }, + "C9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 56.24, + "z": 1.05 + }, + "D1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 47.24, + "z": 1.05 + }, + "D10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 47.24, + "z": 1.05 + }, + "D11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 47.24, + "z": 1.05 + }, + "D12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 47.24, + "z": 1.05 + }, + "D2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 47.24, + "z": 1.05 + }, + "D3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 47.24, + "z": 1.05 + }, + "D4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 47.24, + "z": 1.05 + }, + "D5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 47.24, + "z": 1.05 + }, + "D6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 47.24, + "z": 1.05 + }, + "D7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 47.24, + "z": 1.05 + }, + "D8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 47.24, + "z": 1.05 + }, + "D9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 47.24, + "z": 1.05 + }, + "E1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 38.24, + "z": 1.05 + }, + "E10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 38.24, + "z": 1.05 + }, + "E11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 38.24, + "z": 1.05 + }, + "E12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 38.24, + "z": 1.05 + }, + "E2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 38.24, + "z": 1.05 + }, + "E3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 38.24, + "z": 1.05 + }, + "E4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 38.24, + "z": 1.05 + }, + "E5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 38.24, + "z": 1.05 + }, + "E6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 38.24, + "z": 1.05 + }, + "E7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 38.24, + "z": 1.05 + }, + "E8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 38.24, + "z": 1.05 + }, + "E9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 38.24, + "z": 1.05 + }, + "F1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 29.24, + "z": 1.05 + }, + "F10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 29.24, + "z": 1.05 + }, + "F11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 29.24, + "z": 1.05 + }, + "F12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 29.24, + "z": 1.05 + }, + "F2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 29.24, + "z": 1.05 + }, + "F3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 29.24, + "z": 1.05 + }, + "F4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 29.24, + "z": 1.05 + }, + "F5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 29.24, + "z": 1.05 + }, + "F6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 29.24, + "z": 1.05 + }, + "F7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 29.24, + "z": 1.05 + }, + "F8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 29.24, + "z": 1.05 + }, + "F9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 29.24, + "z": 1.05 + }, + "G1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 20.24, + "z": 1.05 + }, + "G10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 20.24, + "z": 1.05 + }, + "G11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 20.24, + "z": 1.05 + }, + "G12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 20.24, + "z": 1.05 + }, + "G2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 20.24, + "z": 1.05 + }, + "G3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 20.24, + "z": 1.05 + }, + "G4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 20.24, + "z": 1.05 + }, + "G5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 20.24, + "z": 1.05 + }, + "G6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 20.24, + "z": 1.05 + }, + "G7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 20.24, + "z": 1.05 + }, + "G8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 20.24, + "z": 1.05 + }, + "G9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 20.24, + "z": 1.05 + }, + "H1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 11.24, + "z": 1.05 + }, + "H10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 11.24, + "z": 1.05 + }, + "H11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 11.24, + "z": 1.05 + }, + "H12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 11.24, + "z": 1.05 + }, + "H2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 11.24, + "z": 1.05 + }, + "H3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 11.24, + "z": 1.05 + }, + "H4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 11.24, + "z": 1.05 + }, + "H5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 11.24, + "z": 1.05 + }, + "H6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 11.24, + "z": 1.05 + }, + "H7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 11.24, + "z": 1.05 + }, + "H8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 11.24, + "z": 1.05 + }, + "H9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 11.24, + "z": 1.05 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "596b975cce05f1835b73fd3e2e9c04b0", + "notes": [], + "params": { + "loadName": "nest_1_reservoir_290ml", + "location": { + "slotName": "D3" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "360206", + "360266" + ], + "links": [ + "https://www.nest-biotech.com/reagent-reserviors" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.47, + "zDimension": 44.4 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1" + ] + } + ], + "metadata": { + "displayCategory": "reservoir", + "displayName": "NEST 1 Well Reservoir 290 mL", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1" + ] + ], + "parameters": { + "format": "trough", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "nest_1_reservoir_290ml", + "quirks": [ + "centerMultichannelOnWells", + "touchTipDisabled" + ] + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 39.55, + "shape": "rectangular", + "totalLiquidVolume": 290000, + "x": 63.88, + "xDimension": 106.8, + "y": 42.74, + "yDimension": 71.2, + "z": 4.85 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "43c0dcf1828d77562caa82b5e8a8c91e", + "notes": [], + "params": { + "loadName": "nest_12_reservoir_15ml", + "location": { + "slotName": "B3" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "360102" + ], + "links": [ + "https://www.nest-biotech.com/reagent-reserviors/59178414.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 31.4 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9" + ] + } + ], + "metadata": { + "displayCategory": "reservoir", + "displayName": "NEST 12 Well Reservoir 15 mL", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1" + ], + [ + "A10" + ], + [ + "A11" + ], + [ + "A12" + ], + [ + "A2" + ], + [ + "A3" + ], + [ + "A4" + ], + [ + "A5" + ], + [ + "A6" + ], + [ + "A7" + ], + [ + "A8" + ], + [ + "A9" + ] + ], + "parameters": { + "format": "trough", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "nest_12_reservoir_15ml", + "quirks": [ + "centerMultichannelOnWells", + "touchTipDisabled" + ] + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 14.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A10": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 95.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A11": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 104.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A12": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 113.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A2": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 23.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A3": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 32.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A4": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 41.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A5": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 50.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A6": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 59.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A7": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 68.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A8": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 77.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A9": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 86.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLiquid", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5d3ae21e7b20c8d594290fcda36e0906", + "notes": [], + "params": { + "labwareId": "UUID", + "liquidId": "UUID", + "volumeByWell": { + "A1": 800.0 + } + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLiquid", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "660a0039e8fc361930633bfde72e7048", + "notes": [], + "params": { + "labwareId": "UUID", + "liquidId": "UUID", + "volumeByWell": { + "A2": 800.0 + } + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "688fe76a3082d775c9982d7bbaac7474", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_multi_flex", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d2d8580716928e6cedf10922be675c01", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "right", + "pipetteName": "p50_single_flex", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "305bf2316d3fc7964ebdf17e5cb150fc", + "notes": [], + "params": { + "message": "This tests Tip Pick Up Failure (General Error for now)" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6627c460f2c1d181b2d6bce906457ae0", + "notes": [], + "params": { + "message": "Please remove tip rack from deck. When you're testing recovery, add tiprack back as necessary." + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "748924f7bc1bc2293181fde240dd2497", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 178.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.92999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b9df600eccab4fa173cf714a9e113bac", + "notes": [], + "params": { + "message": "Overpressure - While Aspirating" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7058414b08572e4a67114c649fbc4baf", + "notes": [], + "params": { + "axes": [ + "leftPlunger", + "leftZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f8432cddacf8e16c07eba0897f1eb9e4", + "notes": [], + "params": { + "message": "Use p50UL tips - Swap with tip(s) that are hot glued shut (you will need to swap the tips for retry recovery options)" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "64ac4c89015a5b101b05c9f241d2b01a", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dfbcaf24956ff601c54c8eca232341b8", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dc3fc81e61d1b082aff31b267536c7f0", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d5a40428312b4232069455942e7fc4fb", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "89153d8b32e4685dbc442e6591d02947", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a5b8fd811d0ef18cc2dfd021bd49f205", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6fb040ec1b86b7ccd93e803275eeaedb", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "489b07c38ce0381f99b57179d4e21479", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bedb30145351546caecd4fde02041d3e", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "14184571a28f9a7cbced653653a5e4c8", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fa062920af0726db7ad4887c27b36880", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0b302adfd08da748fe70219d2b2aec25", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2eb6e6298939a64ca99771710a0f796c", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "09e6b07abc76b0f47eca5bde4a073fc8", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f77abbc6fccb00d97c44105e4d0dc8c5", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cf010ab2b5036733a0536650b665ef0d", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7201cbd6da4b3a1f9f9a46b87e7f0406", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2ede2f094fbeec343eed731eef8194e7", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9d8af9e55efc49019e13e9deb41f8931", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "99f5375c033353b7601cae5b9aa65ac0", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8c2e09cbe90d48fa924fb8cc51ab6f2e", + "notes": [], + "params": { + "message": "This tests Liquid Presence Detection - PLEASE MAKE SURE YOU'RE USING 1000UL TIPS OR YOU WILL BREAK THE PIPETTE" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "113ace5134dc05770bbbc9f25616e4d2", + "notes": [], + "params": { + "message": "Make sure reservoir is empty, and have full reservoir on standby to switch out with for recovery" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "liquidProbe", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1c5720bacb264e3da2b314e18b3591c5", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 2.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 256.78, + "z": 33.4 + }, + "z_position": 26.85 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e0bbd11eb919a3804d1d23771f645a8d", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 256.78, + "z": 5.55 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b66c39dd7f7222e62b5c25ef63dc4eaa", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 256.78, + "z": 5.55 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1f47f37d8e4b3aea8bbea66e7014ab27", + "notes": [], + "params": { + "axes": [ + "leftPlunger", + "leftZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0f904e31fcd8dcc7ac6778e30cb61bb8", + "notes": [], + "params": { + "message": "Overpressure - While Dispensing" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f04ecdcd9f9b5219d1cf73c37e540d9f", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 19.999999999999993 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 64.39999999999999 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "690df57327aeb3c16ca4ad85203d1ec5", + "notes": [], + "params": { + "message": "Swap with tip(s) that are hot glued shut (you will need to cut the tips for retry recovery options)" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "631e9e0a8230489d6abb8665f0f13463", + "notes": [], + "params": { + "flowRate": 716.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 900.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 19.999999999999993 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 64.39999999999999 + }, + "volume": 900.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0e801e389ac14f125f5ef5db2eae3d08", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 434.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "20b6b0a1dd75709308db2b292ec03992", + "notes": [], + "params": { + "message": "You will have to manually remove tips if you're using a modified pipette" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d7fecbae31209f6f04a2ee350e584c46", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 434.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c4e9dc65f4db718d38cf627ad786ac83", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "65bf172b33169e051174a2d2c6ff08f9", + "notes": [], + "params": { + "message": "This tests Tip Pick Up Failure (General Error for now)" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "eddba592c53196f06dd465abd040ab57", + "notes": [], + "params": { + "message": "Please remove tip rack from deck. When you're testing recovery, add tiprack back as necessary." + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d3fb46eb1e9183a7897d150cc1ef5898", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 178.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 47.81, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5019b8a5d55914c4a86174b01c40d297", + "notes": [], + "params": { + "message": "Overpressure - While Aspirating" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7aafe07a40a3c427dee717cc84c6e303", + "notes": [], + "params": { + "axes": [ + "rightPlunger", + "rightZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2630be2e9b808711cf5ff3c5d69815df", + "notes": [], + "params": { + "message": "Use p50UL tips - Swap with tip(s) that are hot glued shut (you will need to swap the tips for retry recovery options)" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2492d664f9c55efed5652b1cbcf9fdde", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b2d6eb28bc88f476044b1e7e4d6b4107", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "737fbfd381fa1576044bb4666a4c9483", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e45f68ed478fc9f9825f00ff31fcde46", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9dead7e7447f7a0dbdb9b2145d28c692", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f154f5a1679179fabd7a55e8e26d0992", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e20d2093cb882e562bdf5bc427646443", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b0a14d30a958196db673fa5ac8eae550", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dcba87cf3be1f8600a2dea25b0a83274", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c9c41ee06959a54f1aab7a8c5119912b", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e4c179da374f4f5ba047d341a2aa629e", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd6698a069e856a66de54a83992d695f", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6d4f1b88b6955d6574e48e31084aeac4", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "147d5fc4adfbe0a46301abaa465df262", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b660287ab347393357863827d926402d", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "afaecd2d3f12f463dc0c1596fbec73d8", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "18a206fcf40b85ef77d65a95eb69b977", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a66f9c581041537d7c9a0b5b83b2a3bb", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ff3ce00c290b4f3a025b51cc08e87e7f", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0c2b7f9b5117e811de2ca8c708719a4f", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "16b3eb79760f3264be5d1d29a9405940", + "notes": [], + "params": { + "message": "This tests Liquid Presence Detection - PLEASE MAKE SURE YOU'RE USING 1000UL TIPS OR YOU WILL BREAK THE PIPETTE" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "448d8d14a4ba598c39cedae64f1a7d1d", + "notes": [], + "params": { + "message": "Make sure reservoir is empty, and have full reservoir on standby to switch out with for recovery" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "liquidProbe", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "de2087c49b77fe03a83649111113c157", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 2.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 256.78, + "z": 33.4 + }, + "z_position": 26.85 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bcb050e4952ef2d7f7c42dd6838c3c24", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 256.78, + "z": 5.55 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "48dbf180376bece74dd403b1407f9db6", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 256.78, + "z": 5.55 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "431a83945a647ae1699424f1b19555d4", + "notes": [], + "params": { + "axes": [ + "rightPlunger", + "rightZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d291fec87c615fe4446dd14989ec6503", + "notes": [], + "params": { + "message": "Overpressure - While Dispensing" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "29849adcc3a3cc1c478077d9336f1037", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 19.999999999999993 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 64.39999999999999 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5a2ff23afb6c9e0d8d201e674d3ce9f3", + "notes": [], + "params": { + "message": "Swap with tip(s) that are hot glued shut (you will need to cut the tips for retry recovery options)" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "25dff8ae10364bc6e427439df62fd75e", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 19.999999999999993 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 64.39999999999999 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a3cd8b7881d952254804421be5e8229e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 434.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "waitForResume", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "96228aa179a3137a865a4889db38242d", + "notes": [], + "params": { + "message": "You will have to manually remove tips if you're using a modified pipette" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c5ad2c9360fc17ec095d7bdc27b726d4", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 434.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f32667674c9e53f66e65233250b4e939", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [], + "files": [ + { + "name": "Flex_S_v2_20_P8X1000_P50_LLD.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "A2" + } + }, + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_50ul/1", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_50ul", + "location": { + "slotName": "B2" + } + }, + { + "definitionUri": "opentrons/armadillo_96_wellplate_200ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "armadillo_96_wellplate_200ul_pcr_full_skirt", + "location": { + "slotName": "C3" + } + }, + { + "definitionUri": "opentrons/nest_1_reservoir_290ml/1", + "id": "UUID", + "loadName": "nest_1_reservoir_290ml", + "location": { + "slotName": "D3" + } + }, + { + "definitionUri": "opentrons/nest_12_reservoir_15ml/1", + "id": "UUID", + "loadName": "nest_12_reservoir_15ml", + "location": { + "slotName": "B3" + } + } + ], + "liquidClasses": [], + "liquids": [ + { + "description": "water for ER testing", + "displayColor": "#90e0ef", + "displayName": "Water", + "id": "UUID" + }, + { + "description": "Water for ER testing", + "displayColor": "#0077b6", + "displayName": "Water but more blue", + "id": "UUID" + } + ], + "metadata": { + "author": "Sara Kowalski", + "description": "Simple Protocol that user can use to Phase 1 Error Recovery options for single and multi channel pipettes. NOTE: YOU WILL NEED A MODIFIED PIPETTE (NO SHROUD), and changed order of operations to reduce impact of dispense ER failure", + "protocolName": "2.20 Error Recovery Testing Protocol - Modified" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_multi_flex" + }, + { + "id": "UUID", + "mount": "right", + "pipetteName": "p50_single_flex" + } + ], + "result": "ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[13ec753045][Flex_S_v2_18_Illumina_Stranded_total_RNA_Ribo_Zero].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[13ec753045][Flex_S_v2_18_Illumina_Stranded_total_RNA_Ribo_Zero].json index 99501a91cd3..c30b18aa93e 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[13ec753045][Flex_S_v2_18_Illumina_Stranded_total_RNA_Ribo_Zero].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[13ec753045][Flex_S_v2_18_Illumina_Stranded_total_RNA_Ribo_Zero].json @@ -4341,7 +4341,14 @@ }, "id": "UUID", "key": "bccdb28e967f574dfbe472004101d7f9", - "notes": [], + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], "params": { "displayName": "Index Anchors", "loadName": "eppendorf_96_wellplate_150ul", @@ -4485,6 +4492,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Dandra Howell ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[15a60fccf4][pl_microBioID_beads_touchtip].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[15a60fccf4][pl_microBioID_beads_touchtip].json index 0942e72c890..3f500210e5a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[15a60fccf4][pl_microBioID_beads_touchtip].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[15a60fccf4][pl_microBioID_beads_touchtip].json @@ -18899,7 +18899,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18932,7 +18933,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18965,7 +18967,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18998,7 +19001,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19031,7 +19035,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19065,7 +19070,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19098,7 +19104,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19132,7 +19139,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19165,7 +19173,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19199,7 +19208,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19232,7 +19242,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19266,7 +19277,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19299,7 +19311,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19333,7 +19346,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19366,7 +19380,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19400,7 +19415,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19433,7 +19449,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19467,7 +19484,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19500,7 +19518,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19534,7 +19553,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19567,7 +19587,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19601,7 +19622,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19634,7 +19656,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19667,7 +19690,8 @@ "y": 0.0, "z": -9.669999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19700,7 +19724,8 @@ "y": 0.0, "z": -10.169999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19733,7 +19758,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19766,7 +19792,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19901,7 +19928,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19934,7 +19962,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19967,7 +19996,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20001,7 +20031,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20034,7 +20065,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20068,7 +20100,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20101,7 +20134,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20134,7 +20168,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20166,7 +20201,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20198,7 +20234,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20364,7 +20401,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20398,7 +20436,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20431,7 +20470,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20465,7 +20505,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20498,7 +20539,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20532,7 +20574,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20565,7 +20608,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20599,7 +20643,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20632,7 +20677,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20666,7 +20712,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20699,7 +20746,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20733,7 +20781,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20766,7 +20815,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20800,7 +20850,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20833,7 +20884,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20867,7 +20919,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20900,7 +20953,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20934,7 +20988,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20967,7 +21022,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21001,7 +21057,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21034,7 +21091,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21068,7 +21126,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21101,7 +21160,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21135,7 +21195,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21168,7 +21229,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21202,7 +21264,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21235,7 +21298,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21269,7 +21333,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21302,7 +21367,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21336,7 +21402,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21369,7 +21436,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21403,7 +21471,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21436,7 +21505,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21470,7 +21540,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21503,7 +21574,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21537,7 +21609,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21570,7 +21643,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21604,7 +21678,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21637,7 +21712,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21670,7 +21746,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21703,7 +21780,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21736,7 +21814,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21769,7 +21848,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21803,7 +21883,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21836,7 +21917,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21870,7 +21952,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21903,7 +21986,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21936,7 +22020,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21968,7 +22053,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22000,7 +22086,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22214,7 +22301,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22247,7 +22335,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22279,7 +22368,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22311,7 +22401,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22344,7 +22435,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22376,7 +22468,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22529,7 +22622,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22562,7 +22656,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22595,7 +22690,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22629,7 +22725,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22662,7 +22759,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22696,7 +22794,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22729,7 +22828,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22763,7 +22863,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22796,7 +22897,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22830,7 +22932,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22863,7 +22966,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22897,7 +23001,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22930,7 +23035,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22964,7 +23070,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22997,7 +23104,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23031,7 +23139,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23064,7 +23173,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23098,7 +23208,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23131,7 +23242,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23165,7 +23277,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23198,7 +23311,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23232,7 +23346,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23265,7 +23380,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23299,7 +23415,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23332,7 +23449,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23366,7 +23484,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23399,7 +23518,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23433,7 +23553,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23466,7 +23587,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23500,7 +23622,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23533,7 +23656,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23566,7 +23690,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23599,7 +23724,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23768,7 +23894,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23801,7 +23928,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23834,7 +23962,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -23866,7 +23995,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -24019,7 +24149,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24052,7 +24183,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24085,7 +24217,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24119,7 +24252,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24152,7 +24286,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24186,7 +24321,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24219,7 +24355,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24253,7 +24390,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24286,7 +24424,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24320,7 +24459,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24353,7 +24493,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24387,7 +24528,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24420,7 +24562,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24454,7 +24597,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24487,7 +24631,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24521,7 +24666,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24554,7 +24700,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24588,7 +24735,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24621,7 +24769,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24655,7 +24804,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24688,7 +24838,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24722,7 +24873,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24755,7 +24907,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24789,7 +24942,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24822,7 +24976,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24856,7 +25011,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24889,7 +25045,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24923,7 +25080,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24956,7 +25114,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24990,7 +25149,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25023,7 +25183,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25056,7 +25217,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25089,7 +25251,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25258,7 +25421,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25291,7 +25455,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25324,7 +25489,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25356,7 +25522,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25509,7 +25676,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25542,7 +25710,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25575,7 +25744,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25609,7 +25779,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25642,7 +25813,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25676,7 +25848,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25709,7 +25882,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25743,7 +25917,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25776,7 +25951,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25810,7 +25986,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25843,7 +26020,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25877,7 +26055,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25910,7 +26089,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25944,7 +26124,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25977,7 +26158,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26011,7 +26193,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26044,7 +26227,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26078,7 +26262,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26111,7 +26296,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26145,7 +26331,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26178,7 +26365,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26212,7 +26400,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26245,7 +26434,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26279,7 +26469,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26312,7 +26503,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26346,7 +26538,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26379,7 +26572,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26413,7 +26607,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26446,7 +26641,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26480,7 +26676,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26513,7 +26710,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26546,7 +26744,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26579,7 +26778,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26748,7 +26948,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26781,7 +26982,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26814,7 +27016,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -26846,7 +27049,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -26999,7 +27203,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27032,7 +27237,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27065,7 +27271,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27099,7 +27306,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27132,7 +27340,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27166,7 +27375,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27199,7 +27409,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27233,7 +27444,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27266,7 +27478,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27300,7 +27513,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27333,7 +27547,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27367,7 +27582,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27400,7 +27616,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27434,7 +27651,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27467,7 +27685,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27501,7 +27720,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27534,7 +27754,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27568,7 +27789,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27601,7 +27823,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27635,7 +27858,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27668,7 +27892,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27702,7 +27927,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27735,7 +27961,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27769,7 +27996,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27802,7 +28030,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27836,7 +28065,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27869,7 +28099,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27903,7 +28134,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27936,7 +28168,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27970,7 +28203,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28003,7 +28237,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28036,7 +28271,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28069,7 +28305,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28238,7 +28475,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28271,7 +28509,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -28303,7 +28542,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -28335,7 +28575,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28368,7 +28609,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -28400,7 +28642,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -28535,7 +28778,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28568,7 +28812,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28601,7 +28846,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28635,7 +28881,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28668,7 +28915,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28702,7 +28950,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28735,7 +28984,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28768,7 +29018,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28815,7 +29066,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28848,7 +29100,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -28880,7 +29133,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -28912,7 +29166,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -29019,7 +29274,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29052,7 +29308,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29085,7 +29342,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29119,7 +29377,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29152,7 +29411,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29186,7 +29446,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29219,7 +29480,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29252,7 +29514,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29299,7 +29562,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29332,7 +29596,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -29364,7 +29629,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -29396,7 +29662,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -29503,7 +29770,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29536,7 +29804,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29569,7 +29838,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29603,7 +29873,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29636,7 +29907,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29670,7 +29942,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29703,7 +29976,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29736,7 +30010,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29783,7 +30058,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29816,7 +30092,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -29848,7 +30125,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -29880,7 +30158,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -30047,7 +30326,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30081,7 +30361,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30114,7 +30395,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30148,7 +30430,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30181,7 +30464,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30215,7 +30499,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30248,7 +30533,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30282,7 +30568,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30315,7 +30602,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30349,7 +30637,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30382,7 +30671,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30416,7 +30706,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30449,7 +30740,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30483,7 +30775,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30516,7 +30809,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30550,7 +30844,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30583,7 +30878,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30617,7 +30913,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30650,7 +30947,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30683,7 +30981,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30716,7 +31015,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30750,7 +31050,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30783,7 +31084,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30816,7 +31118,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30849,7 +31152,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30882,7 +31186,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30914,7 +31219,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30947,7 +31253,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30979,7 +31286,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31013,7 +31321,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31046,7 +31355,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31079,7 +31389,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31112,7 +31423,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31145,7 +31457,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31177,7 +31490,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -31210,7 +31524,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -31242,7 +31557,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31276,7 +31592,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31309,7 +31626,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31342,7 +31660,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31375,7 +31694,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31408,7 +31728,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31440,7 +31761,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -31473,7 +31795,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -31505,7 +31828,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31539,7 +31863,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31572,7 +31897,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31605,7 +31931,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31638,7 +31965,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31671,7 +31999,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31703,7 +32032,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -31736,7 +32066,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -31768,7 +32099,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31802,7 +32134,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31835,7 +32168,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31868,7 +32202,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31901,7 +32236,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31934,7 +32270,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31966,7 +32303,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -31999,7 +32337,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -32031,7 +32370,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32065,7 +32405,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32098,7 +32439,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32131,7 +32473,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32164,7 +32507,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32197,7 +32541,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32229,7 +32574,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -32262,7 +32608,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -32294,7 +32641,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32328,7 +32676,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32361,7 +32710,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32394,7 +32744,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32427,7 +32778,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32460,7 +32812,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32492,7 +32845,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -32525,7 +32879,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -32557,7 +32912,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32591,7 +32947,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32624,7 +32981,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32657,7 +33015,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32690,7 +33049,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32723,7 +33083,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32755,7 +33116,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -32788,7 +33150,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -32895,7 +33258,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32929,7 +33293,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32962,7 +33327,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32996,7 +33362,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33029,7 +33396,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33062,7 +33430,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33095,7 +33464,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33128,7 +33498,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33174,7 +33545,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33206,7 +33578,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33463,7 +33836,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33496,7 +33870,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33529,7 +33904,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33563,7 +33939,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33596,7 +33973,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33629,7 +34007,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33661,7 +34040,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33693,7 +34073,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33848,7 +34229,8 @@ "y": 0.0, "z": -14.149999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33881,7 +34263,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33914,7 +34297,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33947,7 +34331,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33979,7 +34364,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34011,7 +34397,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34203,6 +34590,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[194e3c49bb][pl_Normalization_with_PCR].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[194e3c49bb][pl_Normalization_with_PCR].json index 85b1868ada6..059f375baec 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[194e3c49bb][pl_Normalization_with_PCR].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[194e3c49bb][pl_Normalization_with_PCR].json @@ -5741,7 +5741,8 @@ "y": 0.0, "z": -36.89999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -5774,7 +5775,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5839,7 +5841,8 @@ "y": 0.0, "z": -36.89999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -5872,7 +5875,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -5937,7 +5941,8 @@ "y": 0.0, "z": -36.89999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -5970,7 +5975,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6113,7 +6119,8 @@ "y": 0.0, "z": -36.89999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6146,7 +6153,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6192,7 +6200,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6224,7 +6233,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6288,7 +6298,8 @@ "y": 0.0, "z": -36.89999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6321,7 +6332,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -6367,7 +6379,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -6399,7 +6412,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -6463,7 +6477,8 @@ "y": 0.0, "z": -36.89999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6496,7 +6511,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6542,7 +6558,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6574,7 +6591,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6716,7 +6734,8 @@ "y": 0.0, "z": -19.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6749,7 +6768,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6782,7 +6802,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6815,7 +6836,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6848,7 +6870,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6881,7 +6904,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6914,7 +6938,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6947,7 +6972,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6980,7 +7006,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7013,7 +7040,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7046,7 +7074,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7079,7 +7108,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7112,7 +7142,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7145,7 +7176,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7178,7 +7210,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7211,7 +7244,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7244,7 +7278,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7277,7 +7312,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7310,7 +7346,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7343,7 +7380,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7376,7 +7414,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7409,7 +7448,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7538,7 +7578,8 @@ "y": 0.0, "z": -19.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7571,7 +7612,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7604,7 +7646,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7637,7 +7680,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7670,7 +7714,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7703,7 +7748,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7736,7 +7782,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7769,7 +7816,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7802,7 +7850,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7835,7 +7884,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7868,7 +7918,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7901,7 +7952,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7934,7 +7986,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7967,7 +8020,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8000,7 +8054,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8033,7 +8088,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8066,7 +8122,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8099,7 +8156,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8132,7 +8190,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8165,7 +8224,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8198,7 +8258,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8231,7 +8292,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8360,7 +8422,8 @@ "y": 0.0, "z": -19.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8393,7 +8456,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8426,7 +8490,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8459,7 +8524,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8492,7 +8558,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8525,7 +8592,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8558,7 +8626,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8591,7 +8660,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8624,7 +8694,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8657,7 +8728,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8690,7 +8762,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8723,7 +8796,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8756,7 +8830,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8789,7 +8864,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8822,7 +8898,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8855,7 +8932,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8888,7 +8966,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8921,7 +9000,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8954,7 +9034,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8987,7 +9068,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -9020,7 +9102,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -9053,7 +9136,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -9213,6 +9297,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Rami Farawi ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4b9e0f93d9][OT2_S_v2_20_8_None_PARTIAL_COLUMN_HappyPathMixedTipRacks].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4b9e0f93d9][OT2_S_v2_20_8_None_PARTIAL_COLUMN_HappyPathMixedTipRacks].json new file mode 100644 index 00000000000..fd7b30ca845 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4b9e0f93d9][OT2_S_v2_20_8_None_PARTIAL_COLUMN_HappyPathMixedTipRacks].json @@ -0,0 +1,19739 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "7" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-300ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.49 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 300 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_300ul", + "tipLength": 59.3, + "tipOverlap": 7.47 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 74.24, + "z": 5.39 + }, + "A10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 74.24, + "z": 5.39 + }, + "A11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 74.24, + "z": 5.39 + }, + "A12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 74.24, + "z": 5.39 + }, + "A2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 74.24, + "z": 5.39 + }, + "A3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 74.24, + "z": 5.39 + }, + "A4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 74.24, + "z": 5.39 + }, + "A5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 74.24, + "z": 5.39 + }, + "A6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 74.24, + "z": 5.39 + }, + "A7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 74.24, + "z": 5.39 + }, + "A8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 74.24, + "z": 5.39 + }, + "A9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 74.24, + "z": 5.39 + }, + "B1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 65.24, + "z": 5.39 + }, + "B10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 65.24, + "z": 5.39 + }, + "B11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 65.24, + "z": 5.39 + }, + "B12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 65.24, + "z": 5.39 + }, + "B2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 65.24, + "z": 5.39 + }, + "B3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 65.24, + "z": 5.39 + }, + "B4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 65.24, + "z": 5.39 + }, + "B5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 65.24, + "z": 5.39 + }, + "B6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 65.24, + "z": 5.39 + }, + "B7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 65.24, + "z": 5.39 + }, + "B8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 65.24, + "z": 5.39 + }, + "B9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 65.24, + "z": 5.39 + }, + "C1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 56.24, + "z": 5.39 + }, + "C10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 56.24, + "z": 5.39 + }, + "C11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 56.24, + "z": 5.39 + }, + "C12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 56.24, + "z": 5.39 + }, + "C2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 56.24, + "z": 5.39 + }, + "C3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 56.24, + "z": 5.39 + }, + "C4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 56.24, + "z": 5.39 + }, + "C5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 56.24, + "z": 5.39 + }, + "C6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 56.24, + "z": 5.39 + }, + "C7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 56.24, + "z": 5.39 + }, + "C8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 56.24, + "z": 5.39 + }, + "C9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 56.24, + "z": 5.39 + }, + "D1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 47.24, + "z": 5.39 + }, + "D10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 47.24, + "z": 5.39 + }, + "D11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 47.24, + "z": 5.39 + }, + "D12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 47.24, + "z": 5.39 + }, + "D2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 47.24, + "z": 5.39 + }, + "D3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 47.24, + "z": 5.39 + }, + "D4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 47.24, + "z": 5.39 + }, + "D5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 47.24, + "z": 5.39 + }, + "D6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 47.24, + "z": 5.39 + }, + "D7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 47.24, + "z": 5.39 + }, + "D8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 47.24, + "z": 5.39 + }, + "D9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 47.24, + "z": 5.39 + }, + "E1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 38.24, + "z": 5.39 + }, + "E10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 38.24, + "z": 5.39 + }, + "E11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 38.24, + "z": 5.39 + }, + "E12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 38.24, + "z": 5.39 + }, + "E2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 38.24, + "z": 5.39 + }, + "E3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 38.24, + "z": 5.39 + }, + "E4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 38.24, + "z": 5.39 + }, + "E5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 38.24, + "z": 5.39 + }, + "E6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 38.24, + "z": 5.39 + }, + "E7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 38.24, + "z": 5.39 + }, + "E8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 38.24, + "z": 5.39 + }, + "E9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 38.24, + "z": 5.39 + }, + "F1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 29.24, + "z": 5.39 + }, + "F10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 29.24, + "z": 5.39 + }, + "F11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 29.24, + "z": 5.39 + }, + "F12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 29.24, + "z": 5.39 + }, + "F2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 29.24, + "z": 5.39 + }, + "F3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 29.24, + "z": 5.39 + }, + "F4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 29.24, + "z": 5.39 + }, + "F5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 29.24, + "z": 5.39 + }, + "F6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 29.24, + "z": 5.39 + }, + "F7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 29.24, + "z": 5.39 + }, + "F8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 29.24, + "z": 5.39 + }, + "F9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 29.24, + "z": 5.39 + }, + "G1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 20.24, + "z": 5.39 + }, + "G10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 20.24, + "z": 5.39 + }, + "G11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 20.24, + "z": 5.39 + }, + "G12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 20.24, + "z": 5.39 + }, + "G2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 20.24, + "z": 5.39 + }, + "G3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 20.24, + "z": 5.39 + }, + "G4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 20.24, + "z": 5.39 + }, + "G5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 20.24, + "z": 5.39 + }, + "G6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 20.24, + "z": 5.39 + }, + "G7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 20.24, + "z": 5.39 + }, + "G8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 20.24, + "z": 5.39 + }, + "G9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 20.24, + "z": 5.39 + }, + "H1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 11.24, + "z": 5.39 + }, + "H10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 11.24, + "z": 5.39 + }, + "H11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 11.24, + "z": 5.39 + }, + "H12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 11.24, + "z": 5.39 + }, + "H2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 11.24, + "z": 5.39 + }, + "H3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 11.24, + "z": 5.39 + }, + "H4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 11.24, + "z": 5.39 + }, + "H5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 11.24, + "z": 5.39 + }, + "H6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 11.24, + "z": 5.39 + }, + "H7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 11.24, + "z": 5.39 + }, + "H8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 11.24, + "z": 5.39 + }, + "H9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 11.24, + "z": 5.39 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50de88d471ad3910c29207fb6df4502e", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "8" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-300ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.49 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 300 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_300ul", + "tipLength": 59.3, + "tipOverlap": 7.47 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 74.24, + "z": 5.39 + }, + "A10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 74.24, + "z": 5.39 + }, + "A11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 74.24, + "z": 5.39 + }, + "A12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 74.24, + "z": 5.39 + }, + "A2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 74.24, + "z": 5.39 + }, + "A3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 74.24, + "z": 5.39 + }, + "A4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 74.24, + "z": 5.39 + }, + "A5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 74.24, + "z": 5.39 + }, + "A6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 74.24, + "z": 5.39 + }, + "A7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 74.24, + "z": 5.39 + }, + "A8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 74.24, + "z": 5.39 + }, + "A9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 74.24, + "z": 5.39 + }, + "B1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 65.24, + "z": 5.39 + }, + "B10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 65.24, + "z": 5.39 + }, + "B11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 65.24, + "z": 5.39 + }, + "B12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 65.24, + "z": 5.39 + }, + "B2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 65.24, + "z": 5.39 + }, + "B3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 65.24, + "z": 5.39 + }, + "B4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 65.24, + "z": 5.39 + }, + "B5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 65.24, + "z": 5.39 + }, + "B6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 65.24, + "z": 5.39 + }, + "B7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 65.24, + "z": 5.39 + }, + "B8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 65.24, + "z": 5.39 + }, + "B9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 65.24, + "z": 5.39 + }, + "C1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 56.24, + "z": 5.39 + }, + "C10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 56.24, + "z": 5.39 + }, + "C11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 56.24, + "z": 5.39 + }, + "C12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 56.24, + "z": 5.39 + }, + "C2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 56.24, + "z": 5.39 + }, + "C3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 56.24, + "z": 5.39 + }, + "C4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 56.24, + "z": 5.39 + }, + "C5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 56.24, + "z": 5.39 + }, + "C6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 56.24, + "z": 5.39 + }, + "C7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 56.24, + "z": 5.39 + }, + "C8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 56.24, + "z": 5.39 + }, + "C9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 56.24, + "z": 5.39 + }, + "D1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 47.24, + "z": 5.39 + }, + "D10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 47.24, + "z": 5.39 + }, + "D11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 47.24, + "z": 5.39 + }, + "D12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 47.24, + "z": 5.39 + }, + "D2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 47.24, + "z": 5.39 + }, + "D3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 47.24, + "z": 5.39 + }, + "D4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 47.24, + "z": 5.39 + }, + "D5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 47.24, + "z": 5.39 + }, + "D6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 47.24, + "z": 5.39 + }, + "D7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 47.24, + "z": 5.39 + }, + "D8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 47.24, + "z": 5.39 + }, + "D9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 47.24, + "z": 5.39 + }, + "E1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 38.24, + "z": 5.39 + }, + "E10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 38.24, + "z": 5.39 + }, + "E11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 38.24, + "z": 5.39 + }, + "E12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 38.24, + "z": 5.39 + }, + "E2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 38.24, + "z": 5.39 + }, + "E3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 38.24, + "z": 5.39 + }, + "E4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 38.24, + "z": 5.39 + }, + "E5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 38.24, + "z": 5.39 + }, + "E6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 38.24, + "z": 5.39 + }, + "E7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 38.24, + "z": 5.39 + }, + "E8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 38.24, + "z": 5.39 + }, + "E9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 38.24, + "z": 5.39 + }, + "F1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 29.24, + "z": 5.39 + }, + "F10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 29.24, + "z": 5.39 + }, + "F11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 29.24, + "z": 5.39 + }, + "F12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 29.24, + "z": 5.39 + }, + "F2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 29.24, + "z": 5.39 + }, + "F3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 29.24, + "z": 5.39 + }, + "F4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 29.24, + "z": 5.39 + }, + "F5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 29.24, + "z": 5.39 + }, + "F6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 29.24, + "z": 5.39 + }, + "F7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 29.24, + "z": 5.39 + }, + "F8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 29.24, + "z": 5.39 + }, + "F9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 29.24, + "z": 5.39 + }, + "G1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 20.24, + "z": 5.39 + }, + "G10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 20.24, + "z": 5.39 + }, + "G11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 20.24, + "z": 5.39 + }, + "G12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 20.24, + "z": 5.39 + }, + "G2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 20.24, + "z": 5.39 + }, + "G3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 20.24, + "z": 5.39 + }, + "G4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 20.24, + "z": 5.39 + }, + "G5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 20.24, + "z": 5.39 + }, + "G6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 20.24, + "z": 5.39 + }, + "G7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 20.24, + "z": 5.39 + }, + "G8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 20.24, + "z": 5.39 + }, + "G9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 20.24, + "z": 5.39 + }, + "H1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 11.24, + "z": 5.39 + }, + "H10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 11.24, + "z": 5.39 + }, + "H11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 11.24, + "z": 5.39 + }, + "H12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 11.24, + "z": 5.39 + }, + "H2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 11.24, + "z": 5.39 + }, + "H3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 11.24, + "z": 5.39 + }, + "H4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 11.24, + "z": 5.39 + }, + "H5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 11.24, + "z": 5.39 + }, + "H6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 11.24, + "z": 5.39 + }, + "H7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 11.24, + "z": 5.39 + }, + "H8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 11.24, + "z": 5.39 + }, + "H9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 11.24, + "z": 5.39 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a83392f86baf8cd5b4f0157c89d31dbd", + "notes": [], + "params": { + "loadName": "opentrons_96_wellplate_200ul_pcr_full_skirt", + "location": { + "slotName": "2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [ + "991-00076" + ], + "links": [ + "https://shop.opentrons.com/tough-0.2-ml-96-well-pcr-plate-full-skirt/" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 16.0 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.0, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "Opentrons Tough 96 Well Plate 200 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "opentrons_96_wellplate_200ul_pcr_full_skirt" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.95 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 11.91 + } + }, + "stackingOffsetWithModule": { + "magneticBlockV1": { + "x": 0, + "y": 0, + "z": 3.54 + }, + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.7 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 74.24, + "z": 1.05 + }, + "A10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 74.24, + "z": 1.05 + }, + "A11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 74.24, + "z": 1.05 + }, + "A12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 74.24, + "z": 1.05 + }, + "A2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 74.24, + "z": 1.05 + }, + "A3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 74.24, + "z": 1.05 + }, + "A4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 74.24, + "z": 1.05 + }, + "A5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 74.24, + "z": 1.05 + }, + "A6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 74.24, + "z": 1.05 + }, + "A7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 74.24, + "z": 1.05 + }, + "A8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 74.24, + "z": 1.05 + }, + "A9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 74.24, + "z": 1.05 + }, + "B1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 65.24, + "z": 1.05 + }, + "B10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 65.24, + "z": 1.05 + }, + "B11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 65.24, + "z": 1.05 + }, + "B12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 65.24, + "z": 1.05 + }, + "B2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 65.24, + "z": 1.05 + }, + "B3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 65.24, + "z": 1.05 + }, + "B4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 65.24, + "z": 1.05 + }, + "B5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 65.24, + "z": 1.05 + }, + "B6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 65.24, + "z": 1.05 + }, + "B7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 65.24, + "z": 1.05 + }, + "B8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 65.24, + "z": 1.05 + }, + "B9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 65.24, + "z": 1.05 + }, + "C1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 56.24, + "z": 1.05 + }, + "C10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 56.24, + "z": 1.05 + }, + "C11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 56.24, + "z": 1.05 + }, + "C12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 56.24, + "z": 1.05 + }, + "C2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 56.24, + "z": 1.05 + }, + "C3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 56.24, + "z": 1.05 + }, + "C4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 56.24, + "z": 1.05 + }, + "C5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 56.24, + "z": 1.05 + }, + "C6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 56.24, + "z": 1.05 + }, + "C7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 56.24, + "z": 1.05 + }, + "C8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 56.24, + "z": 1.05 + }, + "C9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 56.24, + "z": 1.05 + }, + "D1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 47.24, + "z": 1.05 + }, + "D10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 47.24, + "z": 1.05 + }, + "D11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 47.24, + "z": 1.05 + }, + "D12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 47.24, + "z": 1.05 + }, + "D2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 47.24, + "z": 1.05 + }, + "D3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 47.24, + "z": 1.05 + }, + "D4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 47.24, + "z": 1.05 + }, + "D5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 47.24, + "z": 1.05 + }, + "D6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 47.24, + "z": 1.05 + }, + "D7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 47.24, + "z": 1.05 + }, + "D8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 47.24, + "z": 1.05 + }, + "D9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 47.24, + "z": 1.05 + }, + "E1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 38.24, + "z": 1.05 + }, + "E10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 38.24, + "z": 1.05 + }, + "E11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 38.24, + "z": 1.05 + }, + "E12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 38.24, + "z": 1.05 + }, + "E2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 38.24, + "z": 1.05 + }, + "E3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 38.24, + "z": 1.05 + }, + "E4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 38.24, + "z": 1.05 + }, + "E5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 38.24, + "z": 1.05 + }, + "E6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 38.24, + "z": 1.05 + }, + "E7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 38.24, + "z": 1.05 + }, + "E8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 38.24, + "z": 1.05 + }, + "E9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 38.24, + "z": 1.05 + }, + "F1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 29.24, + "z": 1.05 + }, + "F10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 29.24, + "z": 1.05 + }, + "F11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 29.24, + "z": 1.05 + }, + "F12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 29.24, + "z": 1.05 + }, + "F2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 29.24, + "z": 1.05 + }, + "F3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 29.24, + "z": 1.05 + }, + "F4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 29.24, + "z": 1.05 + }, + "F5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 29.24, + "z": 1.05 + }, + "F6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 29.24, + "z": 1.05 + }, + "F7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 29.24, + "z": 1.05 + }, + "F8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 29.24, + "z": 1.05 + }, + "F9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 29.24, + "z": 1.05 + }, + "G1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 20.24, + "z": 1.05 + }, + "G10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 20.24, + "z": 1.05 + }, + "G11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 20.24, + "z": 1.05 + }, + "G12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 20.24, + "z": 1.05 + }, + "G2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 20.24, + "z": 1.05 + }, + "G3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 20.24, + "z": 1.05 + }, + "G4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 20.24, + "z": 1.05 + }, + "G5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 20.24, + "z": 1.05 + }, + "G6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 20.24, + "z": 1.05 + }, + "G7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 20.24, + "z": 1.05 + }, + "G8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 20.24, + "z": 1.05 + }, + "G9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 20.24, + "z": 1.05 + }, + "H1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 11.24, + "z": 1.05 + }, + "H10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 11.24, + "z": 1.05 + }, + "H11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 11.24, + "z": 1.05 + }, + "H12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 11.24, + "z": 1.05 + }, + "H2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 11.24, + "z": 1.05 + }, + "H3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 11.24, + "z": 1.05 + }, + "H4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 11.24, + "z": 1.05 + }, + "H5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 11.24, + "z": 1.05 + }, + "H6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 11.24, + "z": 1.05 + }, + "H7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 11.24, + "z": 1.05 + }, + "H8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 11.24, + "z": 1.05 + }, + "H9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 11.24, + "z": 1.05 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "596b975cce05f1835b73fd3e2e9c04b0", + "notes": [], + "params": { + "loadName": "nest_12_reservoir_15ml", + "location": { + "slotName": "1" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "360102" + ], + "links": [ + "https://www.nest-biotech.com/reagent-reserviors/59178414.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 31.4 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9" + ] + } + ], + "metadata": { + "displayCategory": "reservoir", + "displayName": "NEST 12 Well Reservoir 15 mL", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1" + ], + [ + "A10" + ], + [ + "A11" + ], + [ + "A12" + ], + [ + "A2" + ], + [ + "A3" + ], + [ + "A4" + ], + [ + "A5" + ], + [ + "A6" + ], + [ + "A7" + ], + [ + "A8" + ], + [ + "A9" + ] + ], + "parameters": { + "format": "trough", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "nest_12_reservoir_15ml", + "quirks": [ + "centerMultichannelOnWells", + "touchTipDisabled" + ] + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 14.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A10": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 95.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A11": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 104.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A12": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 113.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A2": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 23.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A3": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 32.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A4": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 41.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A5": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 50.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A6": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 59.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A7": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 68.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A8": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 77.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A9": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 86.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "43c0dcf1828d77562caa82b5e8a8c91e", + "notes": [], + "params": { + "loadName": "nest_1_reservoir_290ml", + "location": { + "slotName": "3" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "360206", + "360266" + ], + "links": [ + "https://www.nest-biotech.com/reagent-reserviors" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.47, + "zDimension": 44.4 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1" + ] + } + ], + "metadata": { + "displayCategory": "reservoir", + "displayName": "NEST 1 Well Reservoir 290 mL", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1" + ] + ], + "parameters": { + "format": "trough", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "nest_1_reservoir_290ml", + "quirks": [ + "centerMultichannelOnWells", + "touchTipDisabled" + ] + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 39.55, + "shape": "rectangular", + "totalLiquidVolume": 290000, + "x": 63.88, + "xDimension": 106.8, + "y": 42.74, + "yDimension": 71.2, + "z": 4.85 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d9a687a9d473851aee5fac3b50b03e2e", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p300_multi_gen2", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9ae9b45b175fa09926415c9d7230b2e0", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "right", + "pipetteName": "p300_single_gen2", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2b195e6d68e009f85baa60afdbc418ea", + "notes": [], + "params": { + "configurationParams": { + "backLeftNozzle": "D1", + "frontRightNozzle": "H1", + "primaryNozzle": "H1", + "style": "QUADRANT" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b4e86544c2cab9a0f2c471eb91806985", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 14.38, + "y": 219.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "be62f97ba6fcad3f3cc33d23f8b1bf7b", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 75.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 146.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 75.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f894fe6b4f28774dc813d4855574d848", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 75.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 146.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 75.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a75573002c04007cdb36510bf254c7fc", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 75.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 146.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 75.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bcea200f78463aaca135898d28e85696", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 75.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 146.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 75.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "953242d49e74810f9e3a43e473d1f796", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 75.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 146.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 75.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2f0f7a41410e74ca014d56532a363e18", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 75.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 146.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 75.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ebde3acc0ee301b2c6bc607cf6be1616", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 200.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 200.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ca42ee43cff534275009e0f5767235f6", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 200.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 200.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1c6e5435d7b4281f6a0bb586698ba833", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 200.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 200.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b7b8cdb13fa839890abed88aaa9c5e8d", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 200.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 200.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "26b8a507e7a464c2fa86e3245a624283", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 200.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 200.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1760ed35842f0aa393677d771f50eaeb", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 200.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 200.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "388ecd2e412cee6a4dbf1d2629fd87a6", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 200.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -38.55 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 328.88, + "y": 42.74, + "z": 5.85 + }, + "volume": 200.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f563bd46dc381d6dd8dc79a428aa51c2", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 200.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -38.55 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 328.88, + "y": 42.74, + "z": 5.85 + }, + "volume": 200.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b03ff7f85185b8a18e1e035439b5c7ad", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 200.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -38.55 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 328.88, + "y": 42.74, + "z": 5.85 + }, + "volume": 200.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c8d15c07d0c70a218e4d7ccd28aa311a", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 200.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -38.55 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 328.88, + "y": 42.74, + "z": 5.85 + }, + "volume": 200.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8152746351e6e9fa2dc57a75b05feab2", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 200.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -38.55 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 328.88, + "y": 42.74, + "z": 5.85 + }, + "volume": 200.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e02d107d393459ad0fccbcf6b01ac453", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 200.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -38.55 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 328.88, + "y": 42.74, + "z": 5.85 + }, + "volume": 200.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7dab8b9c412991f023852d0befdc2b6e", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d06356c1b5e5ca94ed43562a63f0007f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5a1d2cb3fd6580bdb9cf2fb168904934", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E2" + }, + "result": { + "position": { + "x": 23.38, + "y": 219.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b6347fe0756fa94418cd7704311b9808", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 146.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d2b44c1803554c41efb9642c87c4a4a6", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E12" + }, + "result": { + "position": { + "x": 245.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "da6fbc1f7088efe1e12fb900fa8f3c1b", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9e81fbc22f962b4c85df452b37b6c728", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "135c48f1aa8e9529ee42f54e14ac427a", + "notes": [], + "params": { + "configurationParams": { + "backLeftNozzle": "E1", + "frontRightNozzle": "H1", + "primaryNozzle": "H1", + "style": "QUADRANT" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6c6743c6dfad685d95926475654a0aa9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D3" + }, + "result": { + "position": { + "x": 32.38, + "y": 228.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "58772178d187cbca5aad1f7f4679d785", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "33b1396fa8c42666452738704960a486", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A11" + }, + "result": { + "position": { + "x": 104.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "19a8fdc55ad00c9b7ca4d0272c9dcc28", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e3a244bf269a9ae5a867010c2c363fd1", + "notes": [], + "params": { + "flowRate": 94.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d208bf48e3890febcd78e5e0e61f2ec7", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f13e4df8ad03a8deeec315e606ceac1e", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A11" + }, + "result": { + "position": { + "x": 104.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9aa922599e627b37fe09737f63a8987c", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5fabd636c0b162a265811d3e149f498f", + "notes": [], + "params": { + "flowRate": 94.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "102e15a19abb6d22fdc650263155b341", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3916e4d32b0496e23491e41ed5a4e926", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A10" + }, + "result": { + "position": { + "x": 95.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d97b0ce95e6df38113b3cd26168dff3d", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9aa5cabc76e8c083fb1f43a82dc14d34", + "notes": [], + "params": { + "flowRate": 94.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "28d5b25358d19abb36eea296030308df", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "61803ae78a7bf64f1cb816d6f872b69f", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A10" + }, + "result": { + "position": { + "x": 95.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "af1d76729b63e4d55e9b94fbafa98fad", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a8edae33870dc0ac5d17a431ef81f2cf", + "notes": [], + "params": { + "flowRate": 94.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1ae50a790eea08310509a29a31410402", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "075127c691ffae2657115b4d51878c62", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A9" + }, + "result": { + "position": { + "x": 86.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "56e07b010b997a75fea718014c885999", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fd109acb1a66d0178013ea50dd13462e", + "notes": [], + "params": { + "flowRate": 94.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "54a1d1e63c850d8f0425ca1d4b15dacd", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "27bfc1a5dd3effd6f8f29e27220fbb06", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A9" + }, + "result": { + "position": { + "x": 86.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "12f30369d38fb26f81d76c1639065f5e", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c1fc304514bf11e6bd05e4eea586aabb", + "notes": [], + "params": { + "flowRate": 94.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cb245020b31ea2969938401bec3ae715", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "018ed8392645a185927343d212ee668e", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 77.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "632e431a53a65c618ea9b012a59206b8", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a710ba1f24a52969d21a6a8a56194169", + "notes": [], + "params": { + "flowRate": 94.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d712dddbae269e0d0f2573b526551250", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "661f8ada9071bc83acd81b7930202d5c", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 77.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2e5adf80c80a57ec93a4de4381d31408", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6cb63d4aa257c9c204d7878aca8612aa", + "notes": [], + "params": { + "flowRate": 94.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a18bf0b251be17ddd6ba57c3a0d94677", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a6dd6f764dfb49e73d9860f1943cac15", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5b269d82a7ff688610c91be3aa3d0b3b", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e1e83f38a67446e232be929676a65638", + "notes": [], + "params": { + "flowRate": 94.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9326e0397319a1ab0a97d06c43c162d1", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3b51058e2a4e0e632f99226a6f11c91d", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0629615e4f48c9b6cbcff1dce8c68f72", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4a3c31f07e620ffb0963aa41491a0191", + "notes": [], + "params": { + "flowRate": 94.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5336ca8b10ec85ac7b212fab66364130", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d52cd0ed6e925fcb1025746925cc7b22", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 59.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6441ecc6d372590495359859d4c1113a", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a2812327ad3dc701b06522033960f8d9", + "notes": [], + "params": { + "flowRate": 94.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a38bf772b0232b8c989b924f8c05fb46", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d7c430dedf0e498ef88406466a0e18f1", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 59.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "abc9f1189f74df82e76862f4bac2b792", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a6b4c6946ab2b022b600a3066f5759a8", + "notes": [], + "params": { + "flowRate": 94.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "94310e3ca4e4c96828994277f7cc3561", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "df49f22d83da74838de3324a5ca3e78a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fc41029b61861f556c9ca510628ff7dc", + "notes": [], + "params": { + "configurationParams": { + "backLeftNozzle": "F1", + "frontRightNozzle": "H1", + "primaryNozzle": "H1", + "style": "QUADRANT" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "152cff6dd6e1fb534fe712b339a4748f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 14.38, + "y": 192.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5a6a3110f9971eee39748d6f842d1498", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 25.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 25.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "522b4ee6be332a6a0b7363963cc2f5d6", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 25.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 23.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 25.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "93375a78b433f8535c4c890dd1e47d5f", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 25.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 32.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 25.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "19b6370936b337de8b77cb7a9d334cae", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 25.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 41.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 25.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2652fa09e30689a8e309e9f2bb838b24", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 25.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 50.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 25.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f1eb5a3980e0ad113e9e8e4d9f618e50", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 25.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 59.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 25.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6da3375894027a04222aaf2aa81dd32c", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -38.55 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 328.88, + "y": 42.74, + "z": 5.85 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8ec8209083ee6f80c790f47da28cf40d", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "78d9a0219687a3a442fda9924ba46a98", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9f4016c9478ee19ed973f92b8ea03762", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H2" + }, + "result": { + "position": { + "x": 23.38, + "y": 192.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "605af3c5e4caee00e5d3534921d86862", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ad451a470f3156a09eb8d0844eb34c9e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9ea8729fc2245c565c726e8ea5dd4e70", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G3" + }, + "result": { + "position": { + "x": 32.38, + "y": 201.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "83992dfbf5a02dc6346ac308c5123f0c", + "notes": [], + "params": { + "axes": [ + "leftPlunger", + "leftZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4449f3502fbe760b0d5b9eb9d9b7f97d", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "366b2038db497aa9b511ac169259507e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7c045850504687390e733bf2184ac236", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C4" + }, + "result": { + "position": { + "x": 41.38, + "y": 237.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7eab01ac8fef01e7c0245a196905ffde", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2326e018304c8a2f236cd77001f2c046", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "15cb21bb391a925f6839dc8a9668e886", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b6f219a0c84c9eaf481b7283a96a9842", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7001d9217e822e47a02d328a0ff75c43", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "89570f8d0cab2abaf2a8d06a1b9370af", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "21e4f8336f34523e9c0a6905f17966ac", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3fe10d165d6fb1a2e459d3bf7f65d0ba", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "afed7ca3146b89b1c4f1524feeefb54f", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ac1ba0be5fcdd3e2fcabead0c1326099", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "364674bcb6da27d9fd87de0092479d02", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ef9e1305ec261ded62d2d99cff31077e", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cbe60207cd9291ad271afd34f3ad14b4", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "82216e10f28ac0bd102e30cb04248626", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "71546b5885a9332a2869b840ef832c19", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f7797ea8d7d4ceaa0af2cd3bb05abbde", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "174cdd838aa2bd7070dd5fb21e394106", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "770e4156eaa63dc0d18e83fdc82d83c6", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "763574cc074c9b075866b00331a2ff14", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "74398706543d6e3ab776836f0be7c827", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "51db0a032476d5d56ba26217d6a0ff5d", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "402161510f2c125bb58f50e6d58f9c21", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 4.9999999999999964 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 36.4 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6968b346f47723e0bfa20d32dbe18a6a", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 4.9999999999999964 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 36.4 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "prepareToAspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e1f15165a05555f26e2f73510f7ea1ab", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "39e624991f0b9dfe4ee4799b8c619736", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a349f8b6f68c6860c36c51c51a0acd9b", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c71443127ae322a1f577e9dfe892cda8", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -24.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 6.55 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1527dce7a7053c38dd04c65d6f6e28bd", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "32555a392b1698a6746ec1a8039f3d90", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "10733b93463e7348b72a1120e4a006f1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 146.88, + "y": 255.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bc1bbc67fc48a8e1f4a9d1983ea6df8d", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 25.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 146.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 25.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "19ac6118093011e8ad409dc0573ba677", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 25.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 245.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 25.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "222ef2c3585d7d89896ca03000365e60", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "18cbbca7496587ba8d79cd0766fdeb6c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cde3b666ccdf93283909895c5fe20446", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 146.88, + "y": 246.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "362d17afb1a98e76d2b7fcfaa57b3511", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dbdb70f55a54fc45914489a042e34268", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 23.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a9a980872b31047a731a843697442186", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "21512038e8dda579028dba1618f2378d", + "notes": [], + "params": { + "flowRate": 92.86, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "78feacf7c9ec742a8c2f6d7a1b6dc0c2", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "43acf269afef496a3c53fdf87b16b02a", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 23.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e10b219b7b3831326926715df857020c", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "984b8bc88393de41f66bf9fe5bba5899", + "notes": [], + "params": { + "flowRate": 92.86, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "89a130f04d737df6a7a527504ee26a16", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "20f91d0aef5f0563821506266234c4b7", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 32.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5aa1698157210be40ea0b796a7717c31", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2b79d553bbec25001738f5fa36879298", + "notes": [], + "params": { + "flowRate": 92.86, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "781283e9e0351598120735f9e994b190", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6b7500a04dbfb9de7058e4ff1c5b0ca7", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 32.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2e2e2bd80735464507e610efa3492160", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "77410e6e603a344eb7077cafba22905c", + "notes": [], + "params": { + "flowRate": 92.86, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c0b3df7862b967ece7270a5d6bfde097", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e549d41897227e9ca7f796f062e6da6d", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 41.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "78b71cbcdd28b71f94823e2ac5a97e15", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "52b013f1a6da6df62a3c4fdf59fa61c9", + "notes": [], + "params": { + "flowRate": 92.86, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7a44bfc36a53c95aecaf59ce1a81166e", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "514a41687cc2a0bb34dc4d3f95db7d44", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 41.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9ae4b4d8dc5df1084a5f7faf33c1f20a", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "38ca6b948c01bf029c8741a48549bed4", + "notes": [], + "params": { + "flowRate": 92.86, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6784743115590dcbc40af418951923e1", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "de1881b1f7c4e26c8c03bce2ba0a58d8", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 50.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6a301b41e80501cee5a8f47be4943b2b", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "170e99e8e6e8f011160bdde7e887aa5b", + "notes": [], + "params": { + "flowRate": 92.86, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "028dcfd6461dc742dd7b327e49903849", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f30c255690b0c4a9ff3f84a6d72c2320", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 50.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4c3c60a9c2ab15edc381c527e93bf6d0", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9759dda3637b5278c0b563bce0487a0c", + "notes": [], + "params": { + "flowRate": 92.86, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c404644b1e5c4ce90f7b44fc0e88a81a", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2fb6e0a2b6b13ec0bc07447ddf645dcb", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 59.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "636f883908ceb2aebb11d9459ce6e368", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bdb38cf5875a07896b9a140cf64ff512", + "notes": [], + "params": { + "flowRate": 92.86, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9ac9b55c61ff27e8c5fbe39e99c512d2", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1c89c554822f6043d64ae7eb50f59651", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 59.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f590bbc9fae4401c3c0431a6c1d9ce50", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f859b4ccad4459562b9acd962c4918d7", + "notes": [], + "params": { + "flowRate": 92.86, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e5882c371ae09159b4642cf0e176b772", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e781f18fc8542af8fa577c3db4266904", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "56965c4f549bce7854ffd4b4f1a0e2d7", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "756b77b14f61686ae8481390e10f800d", + "notes": [], + "params": { + "flowRate": 92.86, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8b454b6b3038609680e9bfb9033405cb", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 170.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 14.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 170.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "14653ebc88fe702a8a122950d5ac9b1c", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 150.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 150.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "611b9cd86a3bc68e2a0367466643df59", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f0cf8f8fafbbdd5d88d3faf4845ecd27", + "notes": [], + "params": { + "flowRate": 92.86, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b87a96d851ce0c0f2a362b26a351114a", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4605241b5ca2a9fb6b62395a67b60cbc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c86352a3e497650ac0f63fe5b526fd25", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 146.88, + "y": 237.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0d42e232f72b0742eea8ea8ef018f04b", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 25.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 59.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 25.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4873218fc95625b728af694b0364fe6b", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 25.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 25.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c06cd01ff8949d0d29a6bc730c96fd2f", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 25.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 77.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 25.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e9f7063f507b68bd69657ab17e8a9abb", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 25.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A9" + }, + "result": { + "position": { + "x": 86.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 25.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d591e2a8290c4dafc2e8f2026bf496ce", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 25.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A10" + }, + "result": { + "position": { + "x": 95.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 25.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b6ba6931d13de965183210ccfc5a9de8", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 25.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A11" + }, + "result": { + "position": { + "x": 104.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 25.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "140005afcb3d539c56799bd2e1d1f3b2", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 25.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 42.78, + "z": 5.55 + }, + "volume": 25.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9dab6da6ad95f179af59ae635de83b90", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 175.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -38.55 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 328.88, + "y": 42.74, + "z": 5.85 + }, + "volume": 175.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0cc0c61d4c14d5749a093d733da39e47", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1e8ebb9491440e5c9e516d2c2000a684", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "99601303847e1136854292a1e9f39d97", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H3" + }, + "result": { + "position": { + "x": 32.38, + "y": 192.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e592602159e378d87db29bd4b54946a3", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 146.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7b81b70512d9629d9d88dbab1446a57e", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 146.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5d8216651b7613ad8b1e744b45c50e02", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7aa3ae7ca401d664b924e573e154f601", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0f5c008b2b4b497614a09d0100798d48", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D4" + }, + "result": { + "position": { + "x": 41.38, + "y": 228.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "314e0aedd3e2171e4b74e4a5eaadad2d", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 146.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "af1c7291eeb1874e174f62b4670b258f", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 146.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1d9e9c40943ae39feef4d20a15756985", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "25a9e3e1af3d34cde82377e38fcbff35", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a3d37a8fa6d10fea316506816f19a0d3", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E4" + }, + "result": { + "position": { + "x": 41.38, + "y": 219.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ba7d80924676e03644782e34ae821b43", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 146.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b7f2a68cf81e06fa47c68aa24dbc1419", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 146.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a08f5c5ad45283ee1fe3583a64542cd4", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2258522ee0436de3a68c7469fbca44be", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bf7b3c17f1a257aa511965f44b1bd4fd", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F4" + }, + "result": { + "position": { + "x": 41.38, + "y": 210.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b3649b2d7e9fe0a9695a7ca9a6b0a329", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b7d4ad7b47d7fa3548d289800a5e18a3", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f60b409de5d18772ed2ca0649bc27476", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f8464fdb711870dfee5d693b3d2104c8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ea1e2bef11fc087efb7a10cb10b53857", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G4" + }, + "result": { + "position": { + "x": 41.38, + "y": 201.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "aec57479efabdff8957c3bf6f25b7258", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 146.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "31dc7c5b41f407c1fe3536846ae6d678", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 146.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f95ae015dc1de71b050ff83bbe299626", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7bd0984704f19b2b34163285cafe042e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e8dcb53d139843fb4f52b4a2ba31f908", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H4" + }, + "result": { + "position": { + "x": 41.38, + "y": 192.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bbfcb7141c3f1a8b2883a95188adb33f", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 146.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f0a790c9246b8dbcdd4e51d4cd108971", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 146.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9fbe54908d39ede8ec52c277be0fd3c4", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0741bc342297f066f8ed17a5cc85b361", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5cfd2d19fa37ae428c9dce06c966043a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 50.38, + "y": 255.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "65913a2a6e242c8bfbdfbcba41bc7fb7", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 146.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3542501659ea3a3c9e912ea2ff6a6727", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 146.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "570dd0890122754d9411cd1c5d72b3f8", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dae168dfe966f200c582ff4175834672", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6b91d7abab00074fb0e7127131e92822", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B5" + }, + "result": { + "position": { + "x": 50.38, + "y": 246.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "247976438e4f4aff9c82749de264520a", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 146.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "20764e18a3baaec477aeb13bfb86e6cf", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 146.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "caeaf8374c3525ab1a338cbd52dd1dc4", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bb42568391064beb22adbfd5996a8a9b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f4546e372c8b84cffe1e2b3ae0fa3137", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C5" + }, + "result": { + "position": { + "x": 50.38, + "y": 237.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "51b361c15855392c5229c90b05ee6308", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 155.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "84120f69104caf0cca5a0211dcdfe44b", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 155.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "046a4735ecb624dc6fcf37b346cb1935", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3763628f312a6729e1b88d5045976dd4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "57b75c2a6e091f81b763b0e743795ec6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D5" + }, + "result": { + "position": { + "x": 50.38, + "y": 228.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ba90cf278ac31e464e7095c5c906e10b", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B2" + }, + "result": { + "position": { + "x": 155.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ca567a69c8ca4b50ae7e6fba893ce681", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B2" + }, + "result": { + "position": { + "x": 155.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f1601b5fb5c1295de33735f9ab084796", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f080850cc6a90a7f9c0b4f231db3b1f8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6d66b0573f483b646312c5c557119591", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E5" + }, + "result": { + "position": { + "x": 50.38, + "y": 219.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1f9913a9d8a4be1de0fe70f72b5b2963", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C2" + }, + "result": { + "position": { + "x": 155.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "45c9f7671d47b338c21c427df444df14", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C2" + }, + "result": { + "position": { + "x": 155.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4a29614606113a6249946fb008f0d400", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3ed754fae6a978e4e0da26794a955bd4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bb703612756e04b1917f4d9b2ed3c48c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F5" + }, + "result": { + "position": { + "x": 50.38, + "y": 210.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2e29d1b20d2bfb7c1cb1b449d4bda986", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D2" + }, + "result": { + "position": { + "x": 155.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e27f9ed41aeb16fa547eb3cb90dfc426", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D2" + }, + "result": { + "position": { + "x": 155.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3a84d9d91409f7cdc006c8c70dd20cf5", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4d333f6b38a6aeb550f5817bc45b4aec", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "02302bb1cfbfce48b664d378410b76d1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G5" + }, + "result": { + "position": { + "x": 50.38, + "y": 201.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "48c83f6e3372acceda091ca5701fbd92", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E2" + }, + "result": { + "position": { + "x": 155.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "44b5f3d50cb7c1cba67cb6bee44356cb", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E2" + }, + "result": { + "position": { + "x": 155.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fea1efd852b457f4786e752b1d600c97", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1dfc706b582aafb5d75b2e5d22e87c6f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0c196309168697f57bb08ca48915f858", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H5" + }, + "result": { + "position": { + "x": 50.38, + "y": 192.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "67a5aaec54157b3c925cf78d844a22a5", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F2" + }, + "result": { + "position": { + "x": 155.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fb07fdbe2907cec56e92d82aa45273cc", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F2" + }, + "result": { + "position": { + "x": 155.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3ecddc2a953075d260a7ce8fdeec5dac", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9620527b7be9ed5f886a67f13b4d0d2b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a182c96560e520c285a305ce1bbbca54", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 59.38, + "y": 255.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9bf46732cee7875cf0cc08a140b93cad", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G2" + }, + "result": { + "position": { + "x": 155.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "195bf388415a32342eaf7ca5de9eb0db", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G2" + }, + "result": { + "position": { + "x": 155.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "65dc99433a610e6b01e8160fa7b3c9cf", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a4d0c42aad6dd2be5a925a5065624f4a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "837f1e4c8a93e452a88670e06d66b97d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B6" + }, + "result": { + "position": { + "x": 59.38, + "y": 246.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "027eb71a3fdec7ddd7b5b4973f20c748", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H2" + }, + "result": { + "position": { + "x": 155.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "082bdab9e2cff2116b2e07ce1cabd115", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H2" + }, + "result": { + "position": { + "x": 155.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0fa7c31ce90deb452c2bb4dda6404809", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1e4cf8df72fdb9e53ee7d2da93dd0499", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "66f1c1a515df2a2fa5a8c94e390669fa", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C6" + }, + "result": { + "position": { + "x": 59.38, + "y": 237.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "985d6b2e42587482de706f8224b6b2f9", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 164.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0ba825d8db5827a5c223955318cf9e02", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 164.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cc9136e05ba56b73a9edde8c0ec8a7f0", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "38cae5761f5b19f428f07fa44c7d270a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "acbf4d5a5b613d97e0694414a04626ed", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D6" + }, + "result": { + "position": { + "x": 59.38, + "y": 228.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8bae8fd8c1d9187e495456403513d285", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B3" + }, + "result": { + "position": { + "x": 164.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "48cad3a14eec60b2e807cdb488a5cb13", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B3" + }, + "result": { + "position": { + "x": 164.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f6ccd935a261c98a0da22aabe57e5035", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6e7fa469d9a6bd19e5ed2a28c8cb1c24", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c1b33267b61eb0641439aa7c7deff668", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E6" + }, + "result": { + "position": { + "x": 59.38, + "y": 219.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "725e1aaafd81fb540fce22550c991d75", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C3" + }, + "result": { + "position": { + "x": 164.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "657547726373e39e73c1479496becd9b", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C3" + }, + "result": { + "position": { + "x": 164.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e022abe96abba9269b8d70c2e3d4bcf8", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "79d190f8b71f109cdf7f57beec2b5576", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f783ddb62d701c1dea318140cb2ab2d0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F6" + }, + "result": { + "position": { + "x": 59.38, + "y": 210.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6a9f6f611ebed5ad042bdb9d8ce8cb5c", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D3" + }, + "result": { + "position": { + "x": 164.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "600712e5ad4b4836b8f5e6112b850f58", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D3" + }, + "result": { + "position": { + "x": 164.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3ef1f6e68815e8200fece97f14b2b526", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "12721cbfb05477deb06dd2dfc710a2e2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "93790c69e94002d8535cdbb86fcc9fad", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G6" + }, + "result": { + "position": { + "x": 59.38, + "y": 201.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7553443b729dce980a1c2664be3ce166", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E3" + }, + "result": { + "position": { + "x": 164.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "80b1cbffa8f32f20cff6812da08e8414", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E3" + }, + "result": { + "position": { + "x": 164.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7edb74f8bbc0611064b6028626325231", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "00913c1b24aa068a54d6179db78202c0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "20ab5b6079ae6c27d1689ac5be828441", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H6" + }, + "result": { + "position": { + "x": 59.38, + "y": 192.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d7ad7528c21a6d1be398780167d38a7c", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F3" + }, + "result": { + "position": { + "x": 164.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1de24b15e67f30b194a1b0106b57dd92", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F3" + }, + "result": { + "position": { + "x": 164.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "886e4b5c9d9b55d1f2c5f71a9d5b2046", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1d3dcb8ca6a4f61b1820552a39a500f5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b753190564ba8e9df6152a56ee9324db", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 255.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a3fc626e539f5cc11325103eb58a1b50", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G3" + }, + "result": { + "position": { + "x": 164.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4be84624f82ab3044df87b948452a99d", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G3" + }, + "result": { + "position": { + "x": 164.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "67b74e12ba7012a8b394788a5cdff1a2", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3ceda73f3f8766ff9efeb02a87111454", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b5d2346bab7cb2d9d2744bb13f53e19a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B7" + }, + "result": { + "position": { + "x": 68.38, + "y": 246.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c71e2df61f5f9238d224623169abb1b3", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H3" + }, + "result": { + "position": { + "x": 164.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8658687e032a356c83e4429e31ca9cfd", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H3" + }, + "result": { + "position": { + "x": 164.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "15d6a8d217212d3e740de4d934cebe52", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1bab4438401030a8b09878c2b677e61b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "021f61f3bb7401a3749f522298f9f734", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C7" + }, + "result": { + "position": { + "x": 68.38, + "y": 237.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c07fddae19028e0128ddc6b1aa8e3a4a", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 173.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "40f44e2e284aef59dd10966976a9e210", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 173.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1560430a0e74946b7993e47ae11cd183", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cace4563d6d5a17dee8001dca229029b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b893c070fed862e6e3b4c7c7d9315e76", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D7" + }, + "result": { + "position": { + "x": 68.38, + "y": 228.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2917268e983fe5b4460b5215be834cb8", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B4" + }, + "result": { + "position": { + "x": 173.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5c0128223fa3d3e6b9d0a08ddc64e97d", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B4" + }, + "result": { + "position": { + "x": 173.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "55e95abc24637f5e80e8daa4839c47e2", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a41ca7ff90dad9ae11a60ea807a80330", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6ab834b6c158e2dfeaf3b2e78e58f5fd", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E7" + }, + "result": { + "position": { + "x": 68.38, + "y": 219.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d826c99016ce74abaf0ecf27e6016c99", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C4" + }, + "result": { + "position": { + "x": 173.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1fd697aabbe02bed334d8dd1bcf5bf4b", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C4" + }, + "result": { + "position": { + "x": 173.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2de38953d6a3219a6a29fec1bdc7a313", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f858fb0fd988e0d5169d941536076d91", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "af3d5f5d9ebbd04792c1ecf3daf28631", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F7" + }, + "result": { + "position": { + "x": 68.38, + "y": 210.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3b062fd2c1df0f4515793318b7119035", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D4" + }, + "result": { + "position": { + "x": 173.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "01ecfee7fb3122cacd5488d503473abd", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D4" + }, + "result": { + "position": { + "x": 173.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d80eb8b9527c675a82d2de9eb1955ab9", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a1f3cf4dd1f8f3fd8015d22d45ff1ed0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "86b4ceada7e2713661c73726fdd040ce", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G7" + }, + "result": { + "position": { + "x": 68.38, + "y": 201.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "929114e66c66e7b14466e8d1b0a66bd9", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E4" + }, + "result": { + "position": { + "x": 173.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1efd0788945f3600716be938d448ca4d", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E4" + }, + "result": { + "position": { + "x": 173.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "402456aa65e6594c6b4178cb6426642b", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8bc2b907719b56da1c5744ef9fd3eab5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cbf6fda6ddcb71f06cbf5805ecbe53e0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H7" + }, + "result": { + "position": { + "x": 68.38, + "y": 192.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e55ad8e257dab828759538e661c97256", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F4" + }, + "result": { + "position": { + "x": 173.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3852482c04dd42e83d3ba08fc7a0a957", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F4" + }, + "result": { + "position": { + "x": 173.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd6ffa187f5bae0a3ae2becedc529139", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f57904306ee090a7b2b6495dfa198aab", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7f8c2bd8f074d9ab3394364c2db4d3b3", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 77.38, + "y": 255.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4f257652b6cea715adcd984f34d5e788", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G4" + }, + "result": { + "position": { + "x": 173.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c673440cdc9b1fc95f86275065de406c", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G4" + }, + "result": { + "position": { + "x": 173.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "65cc2bf126dc72e1570e4444b63246dc", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4c3d195d35df5191b574c3899e80fb7a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8341e79abd64bb273d3d6ad7f95c86dc", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B8" + }, + "result": { + "position": { + "x": 77.38, + "y": 246.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "75e85ce021a4e0eb9ec5c25f8b5aaa99", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H4" + }, + "result": { + "position": { + "x": 173.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b8b67c721b4eaaa9ec414ca5f2c0c428", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H4" + }, + "result": { + "position": { + "x": 173.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "070439fa10543865a4654f1c2b26cba1", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "456db48b7a91c8b2af2ea11da73068a5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f37db9bced0f87308d56d962e75e8a42", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C8" + }, + "result": { + "position": { + "x": 77.38, + "y": 237.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bf7d78d5eb95632c57232174fcb52d72", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 182.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6f93c73b62a4ad0a928d01232bc7528b", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 182.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a595966dea62045803867c6f46ac4696", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "22d3431e27a6e6e1afda3c553d47b2f4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "22f1585717475277a03d0c8a2b982c7d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D8" + }, + "result": { + "position": { + "x": 77.38, + "y": 228.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b896c050069134ff0659931d49ee1782", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B5" + }, + "result": { + "position": { + "x": 182.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "efacce4b60dde4603b015e74f267bd18", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B5" + }, + "result": { + "position": { + "x": 182.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "36f200386274a7a69aa30443aeae3d40", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "887ec5579b15ceec42246b1ff61a4b27", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c76ca74dbf62887ac9251af8fa287a57", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E8" + }, + "result": { + "position": { + "x": 77.38, + "y": 219.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2eb1ac81f71c9805e8b8337297ce6f0b", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C5" + }, + "result": { + "position": { + "x": 182.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "28f10d3033dce99464666618ed5eb51b", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C5" + }, + "result": { + "position": { + "x": 182.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "09d17c00e04e8c4cff295a37fc42062c", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "48a0a0223599d6b6c631f43f5ffdf882", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "43cdfb54f2815eb061ad020d74c92810", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F8" + }, + "result": { + "position": { + "x": 77.38, + "y": 210.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "848bd213fbc3409175a288aa652ad090", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D5" + }, + "result": { + "position": { + "x": 182.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f08c7a6773edeab39722145211063da3", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D5" + }, + "result": { + "position": { + "x": 182.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0c126c7e5eba869cdcbe284f9a4f4751", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dc492570131e0a8a99607b3f6e351669", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "66385238818fee75fbd20ff9662b0476", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G8" + }, + "result": { + "position": { + "x": 77.38, + "y": 201.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1fcdaf259cb1572f0d5788f7def58c2e", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E5" + }, + "result": { + "position": { + "x": 182.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "abde7370718d6721d3be7b2fcefe91f4", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E5" + }, + "result": { + "position": { + "x": 182.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "36d4f7888fc7e4aced19eb063bf71757", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e02e1ed9de1133f5a576978667f77380", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f7eb8840ff5b668b48f2ae35da5f6572", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H8" + }, + "result": { + "position": { + "x": 77.38, + "y": 192.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5353282f66c23218cb5c55f3fa86149c", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F5" + }, + "result": { + "position": { + "x": 182.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "aa3eaa0f27ea60942304c1fef17c6b59", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F5" + }, + "result": { + "position": { + "x": 182.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3de1ad9f978ca07f52e93c4cd4ffc789", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5102ce3d98ddb4855a418871df206d3e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9e31f5660481cd33ebc500d372aa0990", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A9" + }, + "result": { + "position": { + "x": 86.38, + "y": 255.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "262fd94c20f8e671eb932d98f023d392", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G5" + }, + "result": { + "position": { + "x": 182.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d374b763fb6b83e23df1d956e8b1880b", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G5" + }, + "result": { + "position": { + "x": 182.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "20b052c930903fee7aeae374beb844ae", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b9d8ebb0109a4b4c3ee374e65f64030f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c481c7678081da33b4de8273a00c468f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B9" + }, + "result": { + "position": { + "x": 86.38, + "y": 246.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5d17fba111dd3e9960517fffd9d2339c", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H5" + }, + "result": { + "position": { + "x": 182.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "853401905bf1e2101d863092f80ef0e5", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H5" + }, + "result": { + "position": { + "x": 182.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6330a4fef79899596448b4979070eafc", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "815d34b6ce83aa5da8b9eed908b109ad", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5189693e8400254efecf38a19024e743", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C9" + }, + "result": { + "position": { + "x": 86.38, + "y": 237.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e6cb6d54f8ade3ec577bdf950c45594f", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 191.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "db32b19841f300ed90efab7942ac5ccc", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 191.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d9d4c094a5f75134169783535c9bcf18", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d30e8e55c71f36775c8b9d3c44899dd1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "032c357ad25912e7c8a10569bf6ffa51", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D9" + }, + "result": { + "position": { + "x": 86.38, + "y": 228.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "61942ade3bb8b8c8161e67baba4a5776", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B6" + }, + "result": { + "position": { + "x": 191.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bab515ad8f0740d088735af455db0914", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B6" + }, + "result": { + "position": { + "x": 191.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cc0c2a25b20ba02f69a10f2d682cdf4c", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d96f58703c7293fa5673fc7dcca8e7c7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3a031dc7ca90bed255cc7e5b7b239db3", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E9" + }, + "result": { + "position": { + "x": 86.38, + "y": 219.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6945fd4764128e2a9abfb0220083c4fe", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C6" + }, + "result": { + "position": { + "x": 191.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f9b65dd7423927eeb369eaaeb6d07bbd", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C6" + }, + "result": { + "position": { + "x": 191.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3417af4132da947509668cded9483a17", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2a43fd79ca78394251384cd372adaf70", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "13b3f5151a73b597d0021a1e85878b6d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F9" + }, + "result": { + "position": { + "x": 86.38, + "y": 210.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d50e7b9141e9e7494c0f5768dc0e793d", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D6" + }, + "result": { + "position": { + "x": 191.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1cbf10eff4e42bc783c347f1bdd42eb0", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D6" + }, + "result": { + "position": { + "x": 191.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9e13012a095f7c2927f4994ee287abb5", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "620cc908a9ef2f77324e08471a466d91", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8f068e1fdc00ae4248d537436f425e86", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G9" + }, + "result": { + "position": { + "x": 86.38, + "y": 201.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "32a95203a326b9efe91cd3cf38623fb4", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E6" + }, + "result": { + "position": { + "x": 191.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8333c607fd0ace5937bfeebe9cc8698d", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E6" + }, + "result": { + "position": { + "x": 191.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "846a3ed24431b1e68c0bb47b2c400b61", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e214c7d6fa8e20d913d330f248a861e4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9c74231d0af699359e45732769227039", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H9" + }, + "result": { + "position": { + "x": 86.38, + "y": 192.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a002dc83f92d42e07ff7884440c56396", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F6" + }, + "result": { + "position": { + "x": 191.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9bc63de367832769b26f01b301450f71", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F6" + }, + "result": { + "position": { + "x": 191.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "250bfc96f3be172edddd87e78677ada6", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c51b2e0c7a55ba0a6dc05827996421ad", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ff26f20fa76ff73b6ebac0d5a6939b90", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A10" + }, + "result": { + "position": { + "x": 95.38, + "y": 255.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "602f54bba25c3e7f4aee51ccff2f7c12", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G6" + }, + "result": { + "position": { + "x": 191.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f31ac10ba6e4d3c4193ec970251da226", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G6" + }, + "result": { + "position": { + "x": 191.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f82098db43bf7a922f6e7111e9690c30", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6fcb7f6d4a636847e95e4be4f4ef0dd1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0e608336f7387de4b4d47cb10a61bada", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B10" + }, + "result": { + "position": { + "x": 95.38, + "y": 246.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0063988e1bff9360b9ad8da42b7abd8b", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H6" + }, + "result": { + "position": { + "x": 191.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cea3e7ca665538ca94cd5e55b77ad5ff", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H6" + }, + "result": { + "position": { + "x": 191.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6204c2e4d101b85fea59a3e82041ad66", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1cba31902d09d6473f16f0819060f6b8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5ff7d97ad682fc8772214b82f93483e3", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C10" + }, + "result": { + "position": { + "x": 95.38, + "y": 237.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a7b5a204c884c6703fad03ba311120d0", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 200.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "432a988d2867b4d4ff0ee97e5e8013e4", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 200.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c50feda06cb503ef96c3531078576d4d", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "192c0fd9528981f983dd04fce06a0341", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd191e6493b5610d10dfa86febe78275", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D10" + }, + "result": { + "position": { + "x": 95.38, + "y": 228.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5e0e1bda9cdde872ae39f454742cc956", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B7" + }, + "result": { + "position": { + "x": 200.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e78e4cf7e862862006fb30325e470659", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B7" + }, + "result": { + "position": { + "x": 200.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "118d6fc970885521d574a1831e3cb2f3", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "815048d1958cfbc56bcad6b3174d277e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4e4b714a710019a646bf585e814f717e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E10" + }, + "result": { + "position": { + "x": 95.38, + "y": 219.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "db33e6b30632def50aa2e9b208ae7e9a", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C7" + }, + "result": { + "position": { + "x": 200.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "59099452d79e2ed96fcaa7101709c3e2", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C7" + }, + "result": { + "position": { + "x": 200.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "de374a894eac0a69644d75ecc5c79cea", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7e2ffd02abde9f729df27411822ffb54", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3c4c511c2cbe1d35c73141d623118f64", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F10" + }, + "result": { + "position": { + "x": 95.38, + "y": 210.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cb4fe5ebeb90ca46a5a890ce5a1722e3", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D7" + }, + "result": { + "position": { + "x": 200.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d26a8609a0f45f8f459ee929fa0d40d4", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D7" + }, + "result": { + "position": { + "x": 200.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a60437d239fbcc28a7688af3c245122c", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1dad1b9bedb823addf796474b19ffdbc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "359079e50d4f728cbf9bfd9ff725b5e8", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G10" + }, + "result": { + "position": { + "x": 95.38, + "y": 201.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "29a7e4b1ddc53b3466a37013143b293b", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E7" + }, + "result": { + "position": { + "x": 200.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cb7c3ee67774609162196888cb77b78d", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E7" + }, + "result": { + "position": { + "x": 200.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a54ecf29cc2ab4ac15f198a763ee846b", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1c2a4853e9737d81fddaa3df0964579a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c792bab8ddb3912a4052db6a894bc1a6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H10" + }, + "result": { + "position": { + "x": 95.38, + "y": 192.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "99f0e923e9c48929aa1a56f0edf88fe6", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F7" + }, + "result": { + "position": { + "x": 200.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "729ced1ea326b283fd7889211f816e7b", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F7" + }, + "result": { + "position": { + "x": 200.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3d0cbe7bf3f8f272d5d5e6df9cb5e065", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d365665f7edce965722d410621b4e9b4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "622d64ce82c63ea8bbff9dc7f03ee513", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A11" + }, + "result": { + "position": { + "x": 104.38, + "y": 255.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "831d0955189ebf35550ae78f7aa00620", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G7" + }, + "result": { + "position": { + "x": 200.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3f033d6da3b15066039c93ece1dd5c22", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G7" + }, + "result": { + "position": { + "x": 200.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "70f8fe3c1f575feaf5f10f83f2968309", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9ea9109bcde076589dff89c89612e410", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "60ef4f76888755d2d69dd63080c538c4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B11" + }, + "result": { + "position": { + "x": 104.38, + "y": 246.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8fcfdb9ec1401c56a64bf0fe46d81d77", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H7" + }, + "result": { + "position": { + "x": 200.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "db1955994e6bcf4c828e959b9bff1e5a", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H7" + }, + "result": { + "position": { + "x": 200.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "42183e52642676caeb170eb6282094ea", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "541a35bf0a0c58fb0b111dc1fe82f0bf", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f89efd0bd88939d0146d50738bbddce4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C11" + }, + "result": { + "position": { + "x": 104.38, + "y": 237.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f2f848c7c111c1bb8a7cb96214e6eca0", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 209.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a63e8166e63e69bf6603a642542b3ab6", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 209.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a5bb2df172b56b9017973128242bfec0", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6e5d94ba06598cda8813af593e937a60", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "66496baff414dc5c1ca8319dfebd3c6e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D11" + }, + "result": { + "position": { + "x": 104.38, + "y": 228.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f730b3ccbf45ecbb6faa31ded6bd7a35", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B8" + }, + "result": { + "position": { + "x": 209.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4f3ba6646dcb38cc3fd667e879805381", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B8" + }, + "result": { + "position": { + "x": 209.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5ad5b7dfd60908290440bf20ad007891", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "79b590a78f7d145b350e713138340c93", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "adc776e4ca18160d443a75a26411ecb5", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E11" + }, + "result": { + "position": { + "x": 104.38, + "y": 219.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e8c396072201933abc21e955ca2798c9", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C8" + }, + "result": { + "position": { + "x": 209.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6141f74d1b98fc592e6710189018c29a", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C8" + }, + "result": { + "position": { + "x": 209.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "12138b6e4839283b44a357c036957f43", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9a98e894aa30ef7263fac5a08d19a894", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "20ecd3b1eb74abbdca83503f1b0b25cf", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F11" + }, + "result": { + "position": { + "x": 104.38, + "y": 210.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cb534010951ad74f8df2c430ef336c2c", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D8" + }, + "result": { + "position": { + "x": 209.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "153a878b6251c7768273a714c52f736d", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D8" + }, + "result": { + "position": { + "x": 209.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c21c18eb3275c974c105043eed626045", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "70f34550518a66c1bf3fcb9082c27ae9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9ff70a81196029afc75599a59077af60", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G11" + }, + "result": { + "position": { + "x": 104.38, + "y": 201.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "853a54d07e439206bab73befdd9badf1", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E8" + }, + "result": { + "position": { + "x": 209.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9d830768d0448d46f3de04b57ea2ac77", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E8" + }, + "result": { + "position": { + "x": 209.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a323c948278230c6d8fb187c219f6057", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd0e604dfbcd1340f7e76831229152f5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "122cd381c7da316bd318452ba16dbeda", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H11" + }, + "result": { + "position": { + "x": 104.38, + "y": 192.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "320b936ab3bf8f01b792a68c300ef742", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F8" + }, + "result": { + "position": { + "x": 209.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bbcd5c14b33c447a2e058cfc134f61fd", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F8" + }, + "result": { + "position": { + "x": 209.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cc37d16e54a723eee881269b1c9d9b5f", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f0ef81a1ea70c2aae7771b1546bdb06c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8713b7b0efec7630a796e811182860c8", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 113.38, + "y": 255.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e6b417a2fd26410dbc7dc407da128a03", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G8" + }, + "result": { + "position": { + "x": 209.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5faf5caf4ed7665a7d3a5ea39dd0a26a", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "G8" + }, + "result": { + "position": { + "x": 209.88, + "y": 20.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f87efd2a6da4d9a5e126d3131d5ad9d1", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f2714bac9df513aa8e56647bd025295f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "78eaaa49c6659875f766420320f58bf2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B12" + }, + "result": { + "position": { + "x": 113.38, + "y": 246.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e8aac38642f651eb3ba948f89825902e", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H8" + }, + "result": { + "position": { + "x": 209.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "147bb0a8bdbd1ffb1e2220e245157151", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "H8" + }, + "result": { + "position": { + "x": 209.88, + "y": 11.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bad51cfdc472f069192df8be2f5d6dd4", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1640ff3934f251b5f4c4e0dd2780d5db", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d3372d7368820786fece94a81c79488b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C12" + }, + "result": { + "position": { + "x": 113.38, + "y": 237.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "94234d53562dc2a5e3fed8c292dcf2e7", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A9" + }, + "result": { + "position": { + "x": 218.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4bdc1e6817064ec58a52ca4075571a29", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A9" + }, + "result": { + "position": { + "x": 218.88, + "y": 74.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4b937f4828b58db3d508d5235bf80182", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "20c520a886a146e1dca261a83e362cc9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b8c2c7452e6bbd8b0ad9200d9b067a80", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D12" + }, + "result": { + "position": { + "x": 113.38, + "y": 228.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "696f628ccdb1184c3b8f1ef3b95a4819", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B9" + }, + "result": { + "position": { + "x": 218.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4af9e66e5dc599fcd866d0015cb4d206", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B9" + }, + "result": { + "position": { + "x": 218.88, + "y": 65.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8cfe77bb0a42b365569e4e0a97fcc892", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "86a5a1c548a4c65d477d520c4cbb8893", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9c068587c38eef82d453232764a3b501", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E12" + }, + "result": { + "position": { + "x": 113.38, + "y": 219.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6448a1ddde5c758da02b399df66f6b9a", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C9" + }, + "result": { + "position": { + "x": 218.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8880ffd03efb07de14b5423ee0c90e39", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "C9" + }, + "result": { + "position": { + "x": 218.88, + "y": 56.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "89c66dc65e5d2bd5212295bff15139c1", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7879875b3fea0f901946c59a2fabdc0d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "13a4c14a1cce964d531d4a86eb749455", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F12" + }, + "result": { + "position": { + "x": 113.38, + "y": 210.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bf1c743c51a543b91314cd13f66f9951", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D9" + }, + "result": { + "position": { + "x": 218.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3a720b3611927fd2089178d0a6a6b1d3", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D9" + }, + "result": { + "position": { + "x": 218.88, + "y": 47.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ee9de82481ccad19b4ccaa8e3709a810", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "81f9e5aa9a5f482f233f02db748fa0e9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e0b649c6b81ff8a11279143a87133f36", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G12" + }, + "result": { + "position": { + "x": 113.38, + "y": 201.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4fcf96796450750f3d7334a2bc7ad110", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E9" + }, + "result": { + "position": { + "x": 218.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c81e09ae9cd25a7fd5a12e7ae8c45ee6", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "E9" + }, + "result": { + "position": { + "x": 218.88, + "y": 38.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "eeadec0c25424cb023fc3a805629007a", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7be5483be45ddf7827dd2e3bb837e725", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "06a926609178be57fc72d659db43a530", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H12" + }, + "result": { + "position": { + "x": 113.38, + "y": 192.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8effc6c32aefbe9a0f61ed7f9debea25", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F9" + }, + "result": { + "position": { + "x": 218.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "83aa7e155dc14aca3201f15af45c9d4a", + "notes": [], + "params": { + "flowRate": 92.86, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "F9" + }, + "result": { + "position": { + "x": 218.88, + "y": 29.24, + "z": 2.05 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "165a29e8e1fb302c355928dc20d9bfdb", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bc841b656fa5f0b5dae163f888261c9e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c4a5865101b2bd88186351e6aeb813cd", + "notes": [], + "params": { + "axes": [ + "rightPlunger", + "rightZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [], + "files": [ + { + "name": "OT2_S_v2_20_8_None_PARTIAL_COLUMN_HappyPathMixedTipRacks.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_300ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "7" + } + }, + { + "definitionUri": "opentrons/opentrons_96_tiprack_300ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "8" + } + }, + { + "definitionUri": "opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "opentrons_96_wellplate_200ul_pcr_full_skirt", + "location": { + "slotName": "2" + } + }, + { + "definitionUri": "opentrons/nest_12_reservoir_15ml/1", + "id": "UUID", + "loadName": "nest_12_reservoir_15ml", + "location": { + "slotName": "1" + } + }, + { + "definitionUri": "opentrons/nest_1_reservoir_290ml/1", + "id": "UUID", + "loadName": "nest_1_reservoir_290ml", + "location": { + "slotName": "3" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "OT-2 protocol with 1ch and 8ch pipette partial/single tip configurations. Mixing tipracks and using separate tipracks. ", + "protocolName": "Partial Tip with Partial Column and Single Smoke" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p300_multi_gen2" + }, + { + "id": "UUID", + "mount": "right", + "pipetteName": "p300_single_gen2" + } + ], + "result": "ok", + "robotType": "OT-2 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4cb705bdbf][Flex_X_v2_16_NO_PIPETTES_MM_MagneticModuleInFlexProtocol].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4cb705bdbf][Flex_X_v2_16_NO_PIPETTES_MM_MagneticModuleInFlexProtocol].json index 60a3d0150e4..b63443781ac 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4cb705bdbf][Flex_X_v2_16_NO_PIPETTES_MM_MagneticModuleInFlexProtocol].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4cb705bdbf][Flex_X_v2_16_NO_PIPETTES_MM_MagneticModuleInFlexProtocol].json @@ -32,7 +32,14 @@ }, "id": "UUID", "key": "8511b05ba5565bf0e6dcccd800e2ee23", - "notes": [], + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], "params": { "location": { "slotName": "C1" @@ -96,6 +103,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4ea9d66206][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4ea9d66206][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end].json new file mode 100644 index 00000000000..a126374479b --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4ea9d66206][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end].json @@ -0,0 +1,1261 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_multi_flex", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "ValueError [line 167]: Partial column configurations require the 'end' parameter.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "ValueError: Partial column configurations require the 'end' parameter.", + "errorCode": "4000", + "errorInfo": { + "args": "(\"Partial column configurations require the 'end' parameter.\",)", + "class": "ValueError", + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line N, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line N, in \n\n File \"Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end.py\", line N, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line N, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/instrument_context.py\", line N, in configure_nozzle_layout\n validated_end = _check_valid_end_nozzle(validated_start, end)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/instrument_context.py\", line N, in _check_valid_end_nozzle\n raise ValueError(\"Partial column configurations require the 'end' parameter.\")\n" + }, + "errorType": "PythonException", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Invalid tip configs that should error" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_multi_flex" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4f50c02c81][Flex_S_v2_19_AMPure_XP_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4f50c02c81][Flex_S_v2_19_AMPure_XP_48x].json index e96f09c54ae..f102cab8bc5 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4f50c02c81][Flex_S_v2_19_AMPure_XP_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4f50c02c81][Flex_S_v2_19_AMPure_XP_48x].json @@ -6431,7 +6431,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6464,7 +6465,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6572,7 +6574,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -6605,7 +6608,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -6713,7 +6717,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6746,7 +6751,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -8055,7 +8061,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8088,7 +8095,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8121,7 +8129,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8154,7 +8163,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8186,7 +8196,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8217,7 +8228,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8248,7 +8260,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8280,7 +8293,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8312,7 +8326,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8343,7 +8358,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8375,7 +8391,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8407,7 +8424,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8439,7 +8457,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8472,7 +8491,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8504,7 +8524,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8536,7 +8557,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8569,7 +8591,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8601,7 +8624,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8633,7 +8657,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8666,7 +8691,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8698,7 +8724,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8730,7 +8757,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8762,7 +8790,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8793,7 +8822,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8838,7 +8868,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8870,7 +8901,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8901,7 +8933,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8932,7 +8965,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8963,7 +8997,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9070,7 +9105,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9103,7 +9139,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9136,7 +9173,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9169,7 +9207,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9201,7 +9240,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9232,7 +9272,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9263,7 +9304,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9295,7 +9337,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9327,7 +9370,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9358,7 +9402,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9390,7 +9435,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9422,7 +9468,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9454,7 +9501,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9487,7 +9535,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9519,7 +9568,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9551,7 +9601,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9584,7 +9635,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9616,7 +9668,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9648,7 +9701,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9681,7 +9735,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9713,7 +9768,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9745,7 +9801,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9777,7 +9834,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9808,7 +9866,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9853,7 +9912,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9885,7 +9945,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9916,7 +9977,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9947,7 +10009,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9978,7 +10041,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10085,7 +10149,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10118,7 +10183,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10151,7 +10217,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10184,7 +10251,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10216,7 +10284,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10247,7 +10316,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10278,7 +10348,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10310,7 +10381,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10342,7 +10414,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10373,7 +10446,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10405,7 +10479,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10437,7 +10512,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10469,7 +10545,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10502,7 +10579,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10534,7 +10612,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10566,7 +10645,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10599,7 +10679,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10631,7 +10712,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10663,7 +10745,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10696,7 +10779,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10728,7 +10812,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10760,7 +10845,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10792,7 +10878,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10823,7 +10910,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10868,7 +10956,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10900,7 +10989,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10931,7 +11021,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10962,7 +11053,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10993,7 +11085,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11231,7 +11324,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11264,7 +11358,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11297,7 +11392,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11330,7 +11426,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11363,7 +11460,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11396,7 +11494,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11504,7 +11603,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11537,7 +11637,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11570,7 +11671,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11603,7 +11705,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11636,7 +11739,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11669,7 +11773,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11777,7 +11882,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11810,7 +11916,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11843,7 +11950,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11876,7 +11984,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11909,7 +12018,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11942,7 +12052,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12122,7 +12233,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12155,7 +12267,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12188,7 +12301,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12221,7 +12335,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12253,7 +12368,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12284,7 +12400,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12315,7 +12432,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12347,7 +12465,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12379,7 +12498,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12410,7 +12530,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12442,7 +12563,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12474,7 +12596,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12506,7 +12629,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12539,7 +12663,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12571,7 +12696,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12603,7 +12729,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12636,7 +12763,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12668,7 +12796,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12700,7 +12829,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12733,7 +12863,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12765,7 +12896,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12797,7 +12929,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12829,7 +12962,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12860,7 +12994,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12905,7 +13040,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12937,7 +13073,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12968,7 +13105,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12999,7 +13137,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13030,7 +13169,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13137,7 +13277,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13170,7 +13311,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13203,7 +13345,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13236,7 +13379,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13268,7 +13412,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13299,7 +13444,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13330,7 +13476,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13362,7 +13509,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13394,7 +13542,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13425,7 +13574,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13457,7 +13607,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13489,7 +13640,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13521,7 +13673,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13554,7 +13707,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13586,7 +13740,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13618,7 +13773,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13651,7 +13807,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13683,7 +13840,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13715,7 +13873,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13748,7 +13907,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13780,7 +13940,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13812,7 +13973,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13844,7 +14006,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13875,7 +14038,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13920,7 +14084,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13952,7 +14117,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13983,7 +14149,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14014,7 +14181,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14045,7 +14213,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14152,7 +14321,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14185,7 +14355,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14218,7 +14389,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14251,7 +14423,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14283,7 +14456,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14314,7 +14488,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14345,7 +14520,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14377,7 +14553,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14409,7 +14586,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14440,7 +14618,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14472,7 +14651,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14504,7 +14684,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14536,7 +14717,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14569,7 +14751,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14601,7 +14784,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14633,7 +14817,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14666,7 +14851,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14698,7 +14884,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14730,7 +14917,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14763,7 +14951,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14795,7 +14984,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14827,7 +15017,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14859,7 +15050,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14890,7 +15082,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14935,7 +15128,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14967,7 +15161,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14998,7 +15193,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15029,7 +15225,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15060,7 +15257,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15283,7 +15481,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15315,7 +15514,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15361,7 +15561,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15393,7 +15594,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15425,7 +15627,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15471,7 +15674,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15517,7 +15721,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15548,7 +15753,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15579,7 +15785,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15674,7 +15881,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15706,7 +15914,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15752,7 +15961,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15784,7 +15994,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15816,7 +16027,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15862,7 +16074,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15908,7 +16121,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15939,7 +16153,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15970,7 +16185,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -16065,7 +16281,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16097,7 +16314,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16143,7 +16361,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16175,7 +16394,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16207,7 +16427,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16253,7 +16474,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16299,7 +16521,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16330,7 +16553,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16361,7 +16585,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16485,7 +16710,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16517,7 +16743,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16548,7 +16775,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16579,7 +16807,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16610,7 +16839,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16641,7 +16871,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16672,7 +16903,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16704,7 +16936,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16750,7 +16983,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16781,7 +17015,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16812,7 +17047,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16843,7 +17079,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16964,7 +17201,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16996,7 +17234,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17027,7 +17266,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17058,7 +17298,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17089,7 +17330,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17120,7 +17362,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17151,7 +17394,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17183,7 +17427,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17229,7 +17474,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17260,7 +17506,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17291,7 +17538,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17322,7 +17570,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17443,7 +17692,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17475,7 +17725,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17506,7 +17757,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17537,7 +17789,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17568,7 +17821,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17599,7 +17853,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17630,7 +17885,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17662,7 +17918,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17708,7 +17965,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17739,7 +17997,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17770,7 +18029,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17801,7 +18061,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19122,7 +19383,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19154,7 +19416,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19200,7 +19463,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19232,7 +19496,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19264,7 +19529,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19310,7 +19576,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19356,7 +19623,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19387,7 +19655,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19418,7 +19687,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19513,7 +19783,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19545,7 +19816,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19591,7 +19863,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19623,7 +19896,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19655,7 +19929,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19701,7 +19976,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19747,7 +20023,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19778,7 +20055,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19809,7 +20087,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19904,7 +20183,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19936,7 +20216,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19982,7 +20263,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20014,7 +20296,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20046,7 +20329,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20092,7 +20376,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20138,7 +20423,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20169,7 +20455,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20200,7 +20487,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20324,7 +20612,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20356,7 +20645,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20387,7 +20677,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20418,7 +20709,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20449,7 +20741,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20480,7 +20773,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20511,7 +20805,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20543,7 +20838,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20589,7 +20885,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20620,7 +20917,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20651,7 +20949,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20682,7 +20981,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20803,7 +21103,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20835,7 +21136,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20866,7 +21168,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20897,7 +21200,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20928,7 +21232,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20959,7 +21264,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20990,7 +21296,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21022,7 +21329,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21068,7 +21376,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21099,7 +21408,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21130,7 +21440,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21161,7 +21472,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21282,7 +21594,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21314,7 +21627,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21345,7 +21659,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21376,7 +21691,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21407,7 +21723,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21438,7 +21755,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21469,7 +21787,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21501,7 +21820,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21547,7 +21867,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21578,7 +21899,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21609,7 +21931,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21640,7 +21963,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21774,7 +22098,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21806,7 +22131,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21852,7 +22178,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21884,7 +22211,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21916,7 +22244,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21962,7 +22291,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22008,7 +22338,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22039,7 +22370,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22070,7 +22402,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22165,7 +22498,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22197,7 +22531,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22243,7 +22578,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22275,7 +22611,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22307,7 +22644,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22353,7 +22691,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22399,7 +22738,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22430,7 +22770,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22461,7 +22802,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22556,7 +22898,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22588,7 +22931,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22634,7 +22978,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22666,7 +23011,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22698,7 +23044,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22744,7 +23091,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22790,7 +23138,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22821,7 +23170,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22852,7 +23202,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22975,7 +23326,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23007,7 +23359,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23114,7 +23467,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23146,7 +23500,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23253,7 +23608,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23285,7 +23641,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23479,7 +23836,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23511,7 +23869,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23543,7 +23902,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23575,7 +23935,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23607,7 +23968,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23640,7 +24002,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23673,7 +24036,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23706,7 +24070,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23738,7 +24103,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23834,7 +24200,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23866,7 +24233,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23898,7 +24266,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23930,7 +24299,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23962,7 +24332,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23995,7 +24366,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24028,7 +24400,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24061,7 +24434,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24093,7 +24467,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24189,7 +24564,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -24221,7 +24597,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24253,7 +24630,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24285,7 +24663,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24317,7 +24696,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24350,7 +24730,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24383,7 +24764,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24416,7 +24798,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24448,7 +24831,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25861,7 +26245,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25893,7 +26278,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25926,7 +26312,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25958,7 +26345,8 @@ "y": 0.0, "z": -12.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26053,7 +26441,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26085,7 +26474,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26118,7 +26508,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26150,7 +26541,8 @@ "y": 0.0, "z": -12.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26245,7 +26637,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26277,7 +26670,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26310,7 +26704,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26342,7 +26737,8 @@ "y": 0.0, "z": -12.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27817,6 +28213,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "AMPure Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4fadc166c0][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_variable_name].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4fadc166c0][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_variable_name].json index 843078fa552..484c6600849 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4fadc166c0][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_variable_name].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[4fadc166c0][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_variable_name].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[50d7be4518][pl_Zymo_Quick_RNA_Cells_Flex_multi].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[50d7be4518][pl_Zymo_Quick_RNA_Cells_Flex_multi].json index 9dfa6a8d688..3a839b9cdbd 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[50d7be4518][pl_Zymo_Quick_RNA_Cells_Flex_multi].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[50d7be4518][pl_Zymo_Quick_RNA_Cells_Flex_multi].json @@ -10766,7 +10766,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10799,7 +10800,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10831,7 +10833,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10863,7 +10866,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10896,7 +10900,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10929,7 +10934,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10962,7 +10968,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10995,7 +11002,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11028,7 +11036,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11061,7 +11070,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11094,7 +11104,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11127,7 +11138,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11160,7 +11172,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11193,7 +11206,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11226,7 +11240,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11259,7 +11274,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11292,7 +11308,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11325,7 +11342,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11358,7 +11376,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11391,7 +11410,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11424,7 +11444,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11457,7 +11478,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11490,7 +11512,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11523,7 +11546,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11556,7 +11580,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11589,7 +11614,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11622,7 +11648,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11655,7 +11682,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11688,7 +11716,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11721,7 +11750,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11754,7 +11784,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11787,7 +11818,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11820,7 +11852,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11853,7 +11886,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11886,7 +11920,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11919,7 +11954,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11952,7 +11988,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12120,7 +12157,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12152,7 +12190,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12185,7 +12224,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12218,7 +12258,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12251,7 +12292,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12284,7 +12326,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12317,7 +12360,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12350,7 +12394,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12383,7 +12428,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12416,7 +12462,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12449,7 +12496,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12482,7 +12530,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12515,7 +12564,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12548,7 +12598,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12581,7 +12632,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12614,7 +12666,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12647,7 +12700,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12680,7 +12734,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12713,7 +12768,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12746,7 +12802,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12779,7 +12836,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12812,7 +12870,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12845,7 +12904,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12878,7 +12938,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12911,7 +12972,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12944,7 +13006,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12977,7 +13040,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13010,7 +13074,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13042,7 +13107,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13074,7 +13140,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13107,7 +13174,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13139,7 +13207,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13171,7 +13240,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13203,7 +13273,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13235,7 +13306,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13268,7 +13340,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13301,7 +13374,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13334,7 +13408,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13367,7 +13442,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13400,7 +13476,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13433,7 +13510,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13466,7 +13544,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13499,7 +13578,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13532,7 +13612,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13565,7 +13646,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13598,7 +13680,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13631,7 +13714,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13664,7 +13748,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13697,7 +13782,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13730,7 +13816,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13763,7 +13850,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13796,7 +13884,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13828,7 +13917,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13859,7 +13949,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13891,7 +13982,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14211,7 +14303,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14243,7 +14336,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14275,7 +14369,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14307,7 +14402,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14340,7 +14436,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14372,7 +14469,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14403,7 +14501,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14435,7 +14534,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14468,7 +14568,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14500,7 +14601,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14532,7 +14634,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14564,7 +14667,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14596,7 +14700,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14629,7 +14734,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14661,7 +14767,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14692,7 +14799,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14724,7 +14832,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14883,7 +14992,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14915,7 +15025,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14947,7 +15058,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14980,7 +15092,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15012,7 +15125,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15043,7 +15157,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15075,7 +15190,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15351,7 +15467,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15383,7 +15500,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15415,7 +15533,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15447,7 +15566,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15480,7 +15600,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15512,7 +15633,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15543,7 +15665,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15575,7 +15698,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15734,7 +15858,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15766,7 +15891,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15798,7 +15924,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15831,7 +15958,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15863,7 +15991,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15894,7 +16023,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15926,7 +16056,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16202,7 +16333,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16234,7 +16366,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16266,7 +16399,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16298,7 +16432,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16331,7 +16466,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16363,7 +16499,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16394,7 +16531,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16426,7 +16564,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16585,7 +16724,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16617,7 +16757,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16649,7 +16790,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16682,7 +16824,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16714,7 +16857,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16745,7 +16889,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16777,7 +16922,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17053,7 +17199,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17085,7 +17232,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17117,7 +17265,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17149,7 +17298,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17182,7 +17332,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17214,7 +17365,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17245,7 +17397,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17277,7 +17430,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17436,7 +17590,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17469,7 +17624,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17501,7 +17657,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17532,7 +17689,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17564,7 +17722,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17597,7 +17756,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17630,7 +17790,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17663,7 +17824,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17696,7 +17858,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17729,7 +17892,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17762,7 +17926,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17795,7 +17960,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17828,7 +17994,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17861,7 +18028,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17894,7 +18062,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17927,7 +18096,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17960,7 +18130,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17993,7 +18164,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18026,7 +18198,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18059,7 +18232,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18092,7 +18266,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18125,7 +18300,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18158,7 +18334,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18191,7 +18368,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18224,7 +18402,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18257,7 +18436,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18290,7 +18470,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18323,7 +18504,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18356,7 +18538,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18389,7 +18572,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18422,7 +18606,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18455,7 +18640,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18488,7 +18674,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18521,7 +18708,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18554,7 +18742,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18587,7 +18776,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18620,7 +18810,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18653,7 +18844,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18851,7 +19043,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18883,7 +19076,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18915,7 +19109,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18948,7 +19143,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19224,7 +19420,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19256,7 +19453,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19288,7 +19486,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19320,7 +19519,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19353,7 +19553,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19385,7 +19586,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19416,7 +19618,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19448,7 +19651,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19607,7 +19811,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19639,7 +19844,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19671,7 +19877,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19704,7 +19911,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19736,7 +19944,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19767,7 +19976,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19799,7 +20009,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20075,7 +20286,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20107,7 +20319,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20139,7 +20352,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20171,7 +20385,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20204,7 +20419,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20236,7 +20452,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20267,7 +20484,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20299,7 +20517,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20458,7 +20677,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20490,7 +20710,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20522,7 +20743,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20555,7 +20777,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20587,7 +20810,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20618,7 +20842,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20650,7 +20875,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20926,7 +21152,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20958,7 +21185,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20990,7 +21218,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21022,7 +21251,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21055,7 +21285,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21087,7 +21318,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21118,7 +21350,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21150,7 +21383,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21309,7 +21543,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -21341,7 +21576,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -21373,7 +21609,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -21406,7 +21643,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21438,7 +21676,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21469,7 +21708,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21501,7 +21741,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21777,7 +22018,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21809,7 +22051,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21841,7 +22084,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21873,7 +22117,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21906,7 +22151,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21938,7 +22184,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21969,7 +22216,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22001,7 +22249,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22460,7 +22709,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22493,7 +22743,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22525,7 +22776,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22787,7 +23039,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22819,7 +23072,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22851,7 +23105,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22884,7 +23139,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22916,7 +23172,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22947,7 +23204,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22979,7 +23237,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23161,6 +23420,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Cells in DNA/ RNA Shield", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51a761307d][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultOutOfRangeRTP_Override_default_greater_than_maximum].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51a761307d][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultOutOfRangeRTP_Override_default_greater_than_maximum].json index d2955132ff2..72f8481bc29 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51a761307d][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultOutOfRangeRTP_Override_default_greater_than_maximum].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51a761307d][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultOutOfRangeRTP_Override_default_greater_than_maximum].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Default not in range" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51fc977577][OT2_S_v6_P300M_P20S_MixTransferManyLiquids].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51fc977577][OT2_S_v6_P300M_P20S_MixTransferManyLiquids].json index 0341ff5d5c0..4c45089bc7c 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51fc977577][OT2_S_v6_P300M_P20S_MixTransferManyLiquids].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[51fc977577][OT2_S_v6_P300M_P20S_MixTransferManyLiquids].json @@ -3428,7 +3428,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3461,7 +3462,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3558,7 +3560,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3591,7 +3594,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3688,7 +3692,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3721,7 +3726,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3818,7 +3824,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3851,7 +3858,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3948,7 +3956,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -3981,7 +3990,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4078,7 +4088,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -4111,7 +4122,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4208,7 +4220,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -4241,7 +4254,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4338,7 +4352,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -4371,7 +4386,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4468,7 +4484,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4501,7 +4518,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4534,7 +4552,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4567,7 +4586,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4600,7 +4620,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4633,7 +4654,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4666,7 +4688,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4699,7 +4722,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4732,7 +4756,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4765,7 +4790,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4798,7 +4824,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4831,7 +4858,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4864,7 +4892,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4897,7 +4926,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4930,7 +4960,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4963,7 +4994,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4996,7 +5028,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5029,7 +5062,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5062,7 +5096,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5095,7 +5130,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5192,7 +5228,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5225,7 +5262,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5322,7 +5360,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5355,7 +5394,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5452,7 +5492,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5485,7 +5526,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -5582,7 +5624,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5615,7 +5658,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -5712,7 +5756,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5745,7 +5790,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -5842,7 +5888,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5875,7 +5922,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -6016,6 +6064,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[53db9bf516][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[53db9bf516][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left].json new file mode 100644 index 00000000000..58b6e3ffb42 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[53db9bf516][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left].json @@ -0,0 +1,1304 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_multi_flex", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "error": { + "createdAt": "TIMESTAMP", + "detail": "Attempted Nozzle Configuration does not match any approved map layout for the current pipette.", + "errorCode": "4007", + "errorInfo": {}, + "errorType": "IncompatibleNozzleConfiguration", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + }, + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], + "params": { + "configurationParams": { + "backLeftNozzle": "A1", + "frontRightNozzle": "B1", + "primaryNozzle": "A1", + "style": "QUADRANT" + }, + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "failed" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "ProtocolCommandFailedError [line 167]: Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): IncompatibleNozzleConfiguration: Attempted Nozzle Configuration does not match any approved map layout for the current pipette.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "IncompatibleNozzleConfiguration: Attempted Nozzle Configuration does not match any approved map layout for the current pipette.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ProtocolCommandFailedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Attempted Nozzle Configuration does not match any approved map layout for the current pipette.", + "errorCode": "4007", + "errorInfo": {}, + "errorType": "IncompatibleNozzleConfiguration", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Invalid tip configs that should error" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_multi_flex" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[549cc904ac][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_c3_right_edge].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[549cc904ac][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_c3_right_edge].json new file mode 100644 index 00000000000..dde453f20ab --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[549cc904ac][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_c3_right_edge].json @@ -0,0 +1,1269 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C3" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-1000ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 97.47 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_1000ul", + "tipLength": 88, + "tipOverlap": 7.95 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.24, + "z": 9.47 + }, + "A10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.24, + "z": 9.47 + }, + "A11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.24, + "z": 9.47 + }, + "A12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.24, + "z": 9.47 + }, + "A2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.24, + "z": 9.47 + }, + "A3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.24, + "z": 9.47 + }, + "A4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.24, + "z": 9.47 + }, + "A5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.24, + "z": 9.47 + }, + "A6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.24, + "z": 9.47 + }, + "A7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.24, + "z": 9.47 + }, + "A8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.24, + "z": 9.47 + }, + "A9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.24, + "z": 9.47 + }, + "B1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.24, + "z": 9.47 + }, + "B10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.24, + "z": 9.47 + }, + "B11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.24, + "z": 9.47 + }, + "B12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.24, + "z": 9.47 + }, + "B2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.24, + "z": 9.47 + }, + "B3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.24, + "z": 9.47 + }, + "B4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.24, + "z": 9.47 + }, + "B5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.24, + "z": 9.47 + }, + "B6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.24, + "z": 9.47 + }, + "B7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.24, + "z": 9.47 + }, + "B8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.24, + "z": 9.47 + }, + "B9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.24, + "z": 9.47 + }, + "C1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.24, + "z": 9.47 + }, + "C10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.24, + "z": 9.47 + }, + "C11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.24, + "z": 9.47 + }, + "C12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.24, + "z": 9.47 + }, + "C2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.24, + "z": 9.47 + }, + "C3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.24, + "z": 9.47 + }, + "C4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.24, + "z": 9.47 + }, + "C5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.24, + "z": 9.47 + }, + "C6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.24, + "z": 9.47 + }, + "C7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.24, + "z": 9.47 + }, + "C8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.24, + "z": 9.47 + }, + "C9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.24, + "z": 9.47 + }, + "D1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.24, + "z": 9.47 + }, + "D10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.24, + "z": 9.47 + }, + "D11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.24, + "z": 9.47 + }, + "D12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.24, + "z": 9.47 + }, + "D2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.24, + "z": 9.47 + }, + "D3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.24, + "z": 9.47 + }, + "D4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.24, + "z": 9.47 + }, + "D5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.24, + "z": 9.47 + }, + "D6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.24, + "z": 9.47 + }, + "D7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.24, + "z": 9.47 + }, + "D8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.24, + "z": 9.47 + }, + "D9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.24, + "z": 9.47 + }, + "E1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.24, + "z": 9.47 + }, + "E10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.24, + "z": 9.47 + }, + "E11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.24, + "z": 9.47 + }, + "E12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.24, + "z": 9.47 + }, + "E2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.24, + "z": 9.47 + }, + "E3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.24, + "z": 9.47 + }, + "E4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.24, + "z": 9.47 + }, + "E5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.24, + "z": 9.47 + }, + "E6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.24, + "z": 9.47 + }, + "E7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.24, + "z": 9.47 + }, + "E8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.24, + "z": 9.47 + }, + "E9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.24, + "z": 9.47 + }, + "F1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.24, + "z": 9.47 + }, + "F10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.24, + "z": 9.47 + }, + "F11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.24, + "z": 9.47 + }, + "F12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.24, + "z": 9.47 + }, + "F2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.24, + "z": 9.47 + }, + "F3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.24, + "z": 9.47 + }, + "F4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.24, + "z": 9.47 + }, + "F5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.24, + "z": 9.47 + }, + "F6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.24, + "z": 9.47 + }, + "F7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.24, + "z": 9.47 + }, + "F8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.24, + "z": 9.47 + }, + "F9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.24, + "z": 9.47 + }, + "G1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.24, + "z": 9.47 + }, + "G10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.24, + "z": 9.47 + }, + "G11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.24, + "z": 9.47 + }, + "G12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.24, + "z": 9.47 + }, + "G2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.24, + "z": 9.47 + }, + "G3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.24, + "z": 9.47 + }, + "G4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.24, + "z": 9.47 + }, + "G5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.24, + "z": 9.47 + }, + "G6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.24, + "z": 9.47 + }, + "G7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.24, + "z": 9.47 + }, + "G8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.24, + "z": 9.47 + }, + "G9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.24, + "z": 9.47 + }, + "H1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.24, + "z": 9.47 + }, + "H10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.24, + "z": 9.47 + }, + "H11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.24, + "z": 9.47 + }, + "H12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.24, + "z": 9.47 + }, + "H2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.24, + "z": 9.47 + }, + "H3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.24, + "z": 9.47 + }, + "H4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.24, + "z": 9.47 + }, + "H5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.24, + "z": 9.47 + }, + "H6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.24, + "z": 9.47 + }, + "H7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.24, + "z": 9.47 + }, + "H8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.24, + "z": 9.47 + }, + "H9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.24, + "z": 9.47 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "PartialTipMovementNotAllowedError [line 479]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Requested motion with the A1 nozzle partial configuration is outside of robot bounds for the pipette.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Requested motion with the A1 nozzle partial configuration is outside of robot bounds for the pipette.", + "errorCode": "2004", + "errorInfo": {}, + "errorType": "PartialTipMovementNotAllowedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_c3_right_edge.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C3" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Too tall labware on pickup tip" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54b0b509cd][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54b0b509cd][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end].json new file mode 100644 index 00000000000..8cb5125c17c --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54b0b509cd][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end].json @@ -0,0 +1,2415 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_20ul", + "location": { + "slotName": "1" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-10ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.69 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 20 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_20ul", + "tipLength": 39.2, + "tipOverlap": 8.25 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 74.24, + "z": 25.49 + }, + "A10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 74.24, + "z": 25.49 + }, + "A11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 74.24, + "z": 25.49 + }, + "A12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 74.24, + "z": 25.49 + }, + "A2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 74.24, + "z": 25.49 + }, + "A3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 74.24, + "z": 25.49 + }, + "A4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 74.24, + "z": 25.49 + }, + "A5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 74.24, + "z": 25.49 + }, + "A6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 74.24, + "z": 25.49 + }, + "A7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 74.24, + "z": 25.49 + }, + "A8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 74.24, + "z": 25.49 + }, + "A9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 74.24, + "z": 25.49 + }, + "B1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 65.24, + "z": 25.49 + }, + "B10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 65.24, + "z": 25.49 + }, + "B11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 65.24, + "z": 25.49 + }, + "B12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 65.24, + "z": 25.49 + }, + "B2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 65.24, + "z": 25.49 + }, + "B3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 65.24, + "z": 25.49 + }, + "B4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 65.24, + "z": 25.49 + }, + "B5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 65.24, + "z": 25.49 + }, + "B6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 65.24, + "z": 25.49 + }, + "B7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 65.24, + "z": 25.49 + }, + "B8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 65.24, + "z": 25.49 + }, + "B9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 65.24, + "z": 25.49 + }, + "C1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 56.24, + "z": 25.49 + }, + "C10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 56.24, + "z": 25.49 + }, + "C11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 56.24, + "z": 25.49 + }, + "C12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 56.24, + "z": 25.49 + }, + "C2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 56.24, + "z": 25.49 + }, + "C3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 56.24, + "z": 25.49 + }, + "C4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 56.24, + "z": 25.49 + }, + "C5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 56.24, + "z": 25.49 + }, + "C6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 56.24, + "z": 25.49 + }, + "C7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 56.24, + "z": 25.49 + }, + "C8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 56.24, + "z": 25.49 + }, + "C9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 56.24, + "z": 25.49 + }, + "D1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 47.24, + "z": 25.49 + }, + "D10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 47.24, + "z": 25.49 + }, + "D11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 47.24, + "z": 25.49 + }, + "D12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 47.24, + "z": 25.49 + }, + "D2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 47.24, + "z": 25.49 + }, + "D3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 47.24, + "z": 25.49 + }, + "D4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 47.24, + "z": 25.49 + }, + "D5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 47.24, + "z": 25.49 + }, + "D6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 47.24, + "z": 25.49 + }, + "D7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 47.24, + "z": 25.49 + }, + "D8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 47.24, + "z": 25.49 + }, + "D9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 47.24, + "z": 25.49 + }, + "E1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 38.24, + "z": 25.49 + }, + "E10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 38.24, + "z": 25.49 + }, + "E11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 38.24, + "z": 25.49 + }, + "E12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 38.24, + "z": 25.49 + }, + "E2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 38.24, + "z": 25.49 + }, + "E3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 38.24, + "z": 25.49 + }, + "E4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 38.24, + "z": 25.49 + }, + "E5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 38.24, + "z": 25.49 + }, + "E6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 38.24, + "z": 25.49 + }, + "E7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 38.24, + "z": 25.49 + }, + "E8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 38.24, + "z": 25.49 + }, + "E9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 38.24, + "z": 25.49 + }, + "F1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 29.24, + "z": 25.49 + }, + "F10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 29.24, + "z": 25.49 + }, + "F11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 29.24, + "z": 25.49 + }, + "F12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 29.24, + "z": 25.49 + }, + "F2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 29.24, + "z": 25.49 + }, + "F3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 29.24, + "z": 25.49 + }, + "F4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 29.24, + "z": 25.49 + }, + "F5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 29.24, + "z": 25.49 + }, + "F6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 29.24, + "z": 25.49 + }, + "F7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 29.24, + "z": 25.49 + }, + "F8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 29.24, + "z": 25.49 + }, + "F9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 29.24, + "z": 25.49 + }, + "G1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 20.24, + "z": 25.49 + }, + "G10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 20.24, + "z": 25.49 + }, + "G11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 20.24, + "z": 25.49 + }, + "G12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 20.24, + "z": 25.49 + }, + "G2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 20.24, + "z": 25.49 + }, + "G3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 20.24, + "z": 25.49 + }, + "G4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 20.24, + "z": 25.49 + }, + "G5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 20.24, + "z": 25.49 + }, + "G6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 20.24, + "z": 25.49 + }, + "G7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 20.24, + "z": 25.49 + }, + "G8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 20.24, + "z": 25.49 + }, + "G9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 20.24, + "z": 25.49 + }, + "H1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 11.24, + "z": 25.49 + }, + "H10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 11.24, + "z": 25.49 + }, + "H11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 11.24, + "z": 25.49 + }, + "H12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 11.24, + "z": 25.49 + }, + "H2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 11.24, + "z": 25.49 + }, + "H3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 11.24, + "z": 25.49 + }, + "H4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 11.24, + "z": 25.49 + }, + "H5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 11.24, + "z": 25.49 + }, + "H6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 11.24, + "z": 25.49 + }, + "H7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 11.24, + "z": 25.49 + }, + "H8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 11.24, + "z": 25.49 + }, + "H9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 11.24, + "z": 25.49 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50de88d471ad3910c29207fb6df4502e", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-300ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.49 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 300 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_300ul", + "tipLength": 59.3, + "tipOverlap": 7.47 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 74.24, + "z": 5.39 + }, + "A10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 74.24, + "z": 5.39 + }, + "A11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 74.24, + "z": 5.39 + }, + "A12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 74.24, + "z": 5.39 + }, + "A2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 74.24, + "z": 5.39 + }, + "A3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 74.24, + "z": 5.39 + }, + "A4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 74.24, + "z": 5.39 + }, + "A5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 74.24, + "z": 5.39 + }, + "A6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 74.24, + "z": 5.39 + }, + "A7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 74.24, + "z": 5.39 + }, + "A8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 74.24, + "z": 5.39 + }, + "A9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 74.24, + "z": 5.39 + }, + "B1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 65.24, + "z": 5.39 + }, + "B10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 65.24, + "z": 5.39 + }, + "B11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 65.24, + "z": 5.39 + }, + "B12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 65.24, + "z": 5.39 + }, + "B2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 65.24, + "z": 5.39 + }, + "B3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 65.24, + "z": 5.39 + }, + "B4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 65.24, + "z": 5.39 + }, + "B5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 65.24, + "z": 5.39 + }, + "B6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 65.24, + "z": 5.39 + }, + "B7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 65.24, + "z": 5.39 + }, + "B8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 65.24, + "z": 5.39 + }, + "B9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 65.24, + "z": 5.39 + }, + "C1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 56.24, + "z": 5.39 + }, + "C10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 56.24, + "z": 5.39 + }, + "C11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 56.24, + "z": 5.39 + }, + "C12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 56.24, + "z": 5.39 + }, + "C2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 56.24, + "z": 5.39 + }, + "C3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 56.24, + "z": 5.39 + }, + "C4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 56.24, + "z": 5.39 + }, + "C5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 56.24, + "z": 5.39 + }, + "C6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 56.24, + "z": 5.39 + }, + "C7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 56.24, + "z": 5.39 + }, + "C8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 56.24, + "z": 5.39 + }, + "C9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 56.24, + "z": 5.39 + }, + "D1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 47.24, + "z": 5.39 + }, + "D10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 47.24, + "z": 5.39 + }, + "D11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 47.24, + "z": 5.39 + }, + "D12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 47.24, + "z": 5.39 + }, + "D2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 47.24, + "z": 5.39 + }, + "D3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 47.24, + "z": 5.39 + }, + "D4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 47.24, + "z": 5.39 + }, + "D5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 47.24, + "z": 5.39 + }, + "D6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 47.24, + "z": 5.39 + }, + "D7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 47.24, + "z": 5.39 + }, + "D8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 47.24, + "z": 5.39 + }, + "D9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 47.24, + "z": 5.39 + }, + "E1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 38.24, + "z": 5.39 + }, + "E10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 38.24, + "z": 5.39 + }, + "E11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 38.24, + "z": 5.39 + }, + "E12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 38.24, + "z": 5.39 + }, + "E2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 38.24, + "z": 5.39 + }, + "E3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 38.24, + "z": 5.39 + }, + "E4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 38.24, + "z": 5.39 + }, + "E5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 38.24, + "z": 5.39 + }, + "E6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 38.24, + "z": 5.39 + }, + "E7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 38.24, + "z": 5.39 + }, + "E8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 38.24, + "z": 5.39 + }, + "E9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 38.24, + "z": 5.39 + }, + "F1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 29.24, + "z": 5.39 + }, + "F10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 29.24, + "z": 5.39 + }, + "F11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 29.24, + "z": 5.39 + }, + "F12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 29.24, + "z": 5.39 + }, + "F2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 29.24, + "z": 5.39 + }, + "F3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 29.24, + "z": 5.39 + }, + "F4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 29.24, + "z": 5.39 + }, + "F5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 29.24, + "z": 5.39 + }, + "F6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 29.24, + "z": 5.39 + }, + "F7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 29.24, + "z": 5.39 + }, + "F8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 29.24, + "z": 5.39 + }, + "F9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 29.24, + "z": 5.39 + }, + "G1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 20.24, + "z": 5.39 + }, + "G10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 20.24, + "z": 5.39 + }, + "G11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 20.24, + "z": 5.39 + }, + "G12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 20.24, + "z": 5.39 + }, + "G2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 20.24, + "z": 5.39 + }, + "G3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 20.24, + "z": 5.39 + }, + "G4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 20.24, + "z": 5.39 + }, + "G5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 20.24, + "z": 5.39 + }, + "G6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 20.24, + "z": 5.39 + }, + "G7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 20.24, + "z": 5.39 + }, + "G8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 20.24, + "z": 5.39 + }, + "G9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 20.24, + "z": 5.39 + }, + "H1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 11.24, + "z": 5.39 + }, + "H10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 11.24, + "z": 5.39 + }, + "H11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 11.24, + "z": 5.39 + }, + "H12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 11.24, + "z": 5.39 + }, + "H2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 11.24, + "z": 5.39 + }, + "H3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 11.24, + "z": 5.39 + }, + "H4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 11.24, + "z": 5.39 + }, + "H5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 11.24, + "z": 5.39 + }, + "H6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 11.24, + "z": 5.39 + }, + "H7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 11.24, + "z": 5.39 + }, + "H8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 11.24, + "z": 5.39 + }, + "H9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 11.24, + "z": 5.39 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "54fddab1eb921f6e1d3aa7e96d2ac307", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p20_multi_gen2", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "ValueError [line 134]: Partial column configurations require the 'end' parameter.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "ValueError: Partial column configurations require the 'end' parameter.", + "errorCode": "4000", + "errorInfo": { + "args": "(\"Partial column configurations require the 'end' parameter.\",)", + "class": "ValueError", + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line N, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line N, in \n\n File \"OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end.py\", line N, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line N, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/instrument_context.py\", line N, in configure_nozzle_layout\n validated_end = _check_valid_end_nozzle(validated_start, end)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/instrument_context.py\", line N, in _check_valid_end_nozzle\n raise ValueError(\"Partial column configurations require the 'end' parameter.\")\n" + }, + "errorType": "PythonException", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_no_end.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_20ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_20ul", + "location": { + "slotName": "1" + } + }, + { + "definitionUri": "opentrons/opentrons_96_tiprack_300ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "2" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Invalid tip configs that should error" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p20_multi_gen2" + } + ], + "result": "not-ok", + "robotType": "OT-2 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54f717cfd1][OT2_S_v2_16_P300S_None_verifyNoFloatingPointErrorInPipetting].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54f717cfd1][OT2_S_v2_16_P300S_None_verifyNoFloatingPointErrorInPipetting].json index 913c5293682..27656b80cca 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54f717cfd1][OT2_S_v2_16_P300S_None_verifyNoFloatingPointErrorInPipetting].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[54f717cfd1][OT2_S_v2_16_P300S_None_verifyNoFloatingPointErrorInPipetting].json @@ -1601,7 +1601,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -1633,7 +1634,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -1665,7 +1667,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -1698,7 +1701,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -1730,7 +1734,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -1762,7 +1767,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -1795,7 +1801,8 @@ "y": 0.0, "z": -36.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -1887,6 +1894,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [], diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[555b2fff00][Flex_S_v2_19_Illumina_DNA_Prep_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[555b2fff00][Flex_S_v2_19_Illumina_DNA_Prep_48x].json index 89a791c0b97..84bff8651d2 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[555b2fff00][Flex_S_v2_19_Illumina_DNA_Prep_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[555b2fff00][Flex_S_v2_19_Illumina_DNA_Prep_48x].json @@ -7651,7 +7651,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7685,7 +7686,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7718,7 +7720,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7752,7 +7755,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7785,7 +7789,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7818,7 +7823,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7851,7 +7857,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7884,7 +7891,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7917,7 +7925,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7950,7 +7959,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7984,7 +7994,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8017,7 +8028,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8050,7 +8062,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8082,7 +8095,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8127,7 +8141,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8158,7 +8173,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8189,7 +8205,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8220,7 +8237,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8327,7 +8345,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8361,7 +8380,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8394,7 +8414,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8428,7 +8449,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8461,7 +8483,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8494,7 +8517,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8527,7 +8551,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8560,7 +8585,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8593,7 +8619,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8626,7 +8653,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8660,7 +8688,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8693,7 +8722,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8726,7 +8756,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8758,7 +8789,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8803,7 +8835,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8834,7 +8867,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8865,7 +8899,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8896,7 +8931,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9003,7 +9039,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9037,7 +9074,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9070,7 +9108,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9104,7 +9143,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9137,7 +9177,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9170,7 +9211,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9203,7 +9245,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9236,7 +9279,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9269,7 +9313,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9302,7 +9347,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9336,7 +9382,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9369,7 +9416,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9402,7 +9450,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9434,7 +9483,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9479,7 +9529,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9510,7 +9561,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9541,7 +9593,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9572,7 +9625,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11064,7 +11118,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11097,7 +11152,8 @@ "y": 0.0, "z": -26.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11130,7 +11186,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11162,7 +11219,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11194,7 +11252,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11228,7 +11287,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11261,7 +11321,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11295,7 +11356,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11328,7 +11390,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11362,7 +11425,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11395,7 +11459,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11429,7 +11494,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11462,7 +11528,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11496,7 +11563,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11529,7 +11597,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11563,7 +11632,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11596,7 +11666,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11630,7 +11701,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11663,7 +11735,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11697,7 +11770,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11730,7 +11804,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11764,7 +11839,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11797,7 +11873,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11830,7 +11907,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11862,7 +11940,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11969,7 +12048,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12002,7 +12082,8 @@ "y": 0.0, "z": -26.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12035,7 +12116,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12067,7 +12149,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12099,7 +12182,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12133,7 +12217,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12166,7 +12251,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12200,7 +12286,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12233,7 +12320,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12267,7 +12355,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12300,7 +12389,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12334,7 +12424,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12367,7 +12458,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12401,7 +12493,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12434,7 +12527,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12468,7 +12562,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12501,7 +12596,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12535,7 +12631,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12568,7 +12665,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12602,7 +12700,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12635,7 +12734,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12669,7 +12769,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12702,7 +12803,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12735,7 +12837,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12767,7 +12870,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12874,7 +12978,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12907,7 +13012,8 @@ "y": 0.0, "z": -26.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12940,7 +13046,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12972,7 +13079,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13004,7 +13112,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13038,7 +13147,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13071,7 +13181,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13105,7 +13216,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13138,7 +13250,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13172,7 +13285,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13205,7 +13319,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13239,7 +13354,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13272,7 +13388,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13306,7 +13423,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13339,7 +13457,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13373,7 +13492,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13406,7 +13526,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13440,7 +13561,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13473,7 +13595,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13507,7 +13630,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13540,7 +13664,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13574,7 +13699,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13607,7 +13733,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13640,7 +13767,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13672,7 +13800,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13874,7 +14003,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13907,7 +14037,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14004,7 +14135,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14037,7 +14169,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14134,7 +14267,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14167,7 +14301,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14410,7 +14545,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14442,7 +14578,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14488,7 +14625,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14520,7 +14658,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14552,7 +14691,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14598,7 +14738,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14644,7 +14785,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14675,7 +14817,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14706,7 +14849,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14737,7 +14881,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14832,7 +14977,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14864,7 +15010,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14910,7 +15057,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14942,7 +15090,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14974,7 +15123,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15020,7 +15170,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15066,7 +15217,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15097,7 +15249,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15128,7 +15281,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15159,7 +15313,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15254,7 +15409,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15286,7 +15442,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15332,7 +15489,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15364,7 +15522,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15396,7 +15555,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15442,7 +15602,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15488,7 +15649,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15519,7 +15681,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15550,7 +15713,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15581,7 +15745,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15749,7 +15914,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15782,7 +15948,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15814,7 +15981,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15845,7 +16013,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15876,7 +16045,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15908,7 +16078,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15941,7 +16112,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15975,7 +16147,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16008,7 +16181,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16041,7 +16215,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16073,7 +16248,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16118,7 +16294,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16150,7 +16327,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16258,7 +16436,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16291,7 +16470,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16323,7 +16503,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16354,7 +16535,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16385,7 +16567,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16417,7 +16600,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16450,7 +16634,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16484,7 +16669,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16517,7 +16703,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16550,7 +16737,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16582,7 +16770,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16627,7 +16816,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16659,7 +16849,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16767,7 +16958,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16800,7 +16992,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16832,7 +17025,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16863,7 +17057,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16894,7 +17089,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16926,7 +17122,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16959,7 +17156,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16993,7 +17191,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17026,7 +17225,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17059,7 +17259,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17091,7 +17292,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17136,7 +17338,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17168,7 +17371,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18548,7 +18752,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18580,7 +18785,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18612,7 +18818,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18658,7 +18865,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18705,7 +18913,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18737,7 +18946,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18782,7 +18992,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18813,7 +19024,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18844,7 +19056,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18875,7 +19088,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18970,7 +19184,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19002,7 +19217,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19034,7 +19250,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19080,7 +19297,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19127,7 +19345,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19159,7 +19378,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19204,7 +19424,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19235,7 +19456,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19266,7 +19488,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19297,7 +19520,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19392,7 +19616,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19424,7 +19649,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19456,7 +19682,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19502,7 +19729,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19549,7 +19777,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19581,7 +19810,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19626,7 +19856,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19657,7 +19888,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19688,7 +19920,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19719,7 +19952,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19887,7 +20121,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19920,7 +20155,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19952,7 +20188,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19983,7 +20220,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20014,7 +20252,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20046,7 +20285,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20079,7 +20319,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20113,7 +20354,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20146,7 +20388,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20179,7 +20422,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20211,7 +20455,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20256,7 +20501,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20288,7 +20534,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20396,7 +20643,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20429,7 +20677,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20461,7 +20710,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20492,7 +20742,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20523,7 +20774,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20555,7 +20807,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20588,7 +20841,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20622,7 +20876,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20655,7 +20910,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20688,7 +20944,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20720,7 +20977,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20765,7 +21023,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20797,7 +21056,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20905,7 +21165,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20938,7 +21199,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20970,7 +21232,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21001,7 +21264,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21032,7 +21296,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21064,7 +21329,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21097,7 +21363,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21131,7 +21398,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21164,7 +21432,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21197,7 +21466,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21229,7 +21499,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21274,7 +21545,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21306,7 +21578,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21499,7 +21772,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21531,7 +21805,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21563,7 +21838,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21609,7 +21885,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21656,7 +21933,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21688,7 +21966,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21733,7 +22012,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21764,7 +22044,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21795,7 +22076,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21826,7 +22108,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21921,7 +22204,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21953,7 +22237,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21985,7 +22270,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22031,7 +22317,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22078,7 +22365,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22110,7 +22398,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22155,7 +22444,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22186,7 +22476,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22217,7 +22508,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22248,7 +22540,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22343,7 +22636,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22375,7 +22669,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22407,7 +22702,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22453,7 +22749,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22500,7 +22797,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22532,7 +22830,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22577,7 +22876,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22608,7 +22908,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22639,7 +22940,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22670,7 +22972,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22838,7 +23141,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22871,7 +23175,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22903,7 +23208,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22934,7 +23240,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22965,7 +23272,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22997,7 +23305,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23030,7 +23339,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23064,7 +23374,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23097,7 +23408,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23130,7 +23442,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23162,7 +23475,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23207,7 +23521,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23239,7 +23554,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23347,7 +23663,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23380,7 +23697,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23412,7 +23730,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23443,7 +23762,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23474,7 +23794,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23506,7 +23827,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23539,7 +23861,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23573,7 +23896,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23606,7 +23930,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23639,7 +23964,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23671,7 +23997,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23716,7 +24043,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23748,7 +24076,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23856,7 +24185,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23889,7 +24219,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23921,7 +24252,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23952,7 +24284,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23983,7 +24316,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24015,7 +24349,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24048,7 +24383,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24082,7 +24418,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24115,7 +24452,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24148,7 +24486,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24180,7 +24519,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24225,7 +24565,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24257,7 +24598,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25637,7 +25979,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25669,7 +26012,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25701,7 +26045,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25747,7 +26092,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25794,7 +26140,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25826,7 +26173,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25871,7 +26219,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25902,7 +26251,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25933,7 +26283,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25964,7 +26315,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26059,7 +26411,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26091,7 +26444,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26123,7 +26477,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26169,7 +26524,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26216,7 +26572,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26248,7 +26605,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26293,7 +26651,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26324,7 +26683,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26355,7 +26715,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26386,7 +26747,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26481,7 +26843,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26513,7 +26876,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26545,7 +26909,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26591,7 +26956,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26638,7 +27004,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26670,7 +27037,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26715,7 +27083,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26746,7 +27115,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26777,7 +27147,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26808,7 +27179,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26931,7 +27303,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26963,7 +27336,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27010,7 +27384,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27042,7 +27417,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27087,7 +27463,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27118,7 +27495,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27149,7 +27527,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27180,7 +27559,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27275,7 +27655,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27307,7 +27688,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27354,7 +27736,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27386,7 +27769,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27431,7 +27815,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27462,7 +27847,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27493,7 +27879,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27524,7 +27911,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27619,7 +28007,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27651,7 +28040,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27698,7 +28088,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27730,7 +28121,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27775,7 +28167,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27806,7 +28199,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27837,7 +28231,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27868,7 +28263,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -28050,7 +28446,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28083,7 +28480,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28115,7 +28513,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28147,7 +28546,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28179,7 +28579,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28211,7 +28612,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28243,7 +28645,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28275,7 +28678,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28307,7 +28711,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28339,7 +28744,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28371,7 +28777,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28403,7 +28810,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28435,7 +28843,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28467,7 +28876,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28499,7 +28909,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28531,7 +28942,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28563,7 +28975,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28595,7 +29008,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28628,7 +29042,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28660,7 +29075,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28691,7 +29107,8 @@ "y": 0.0, "z": -37.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28722,7 +29139,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28753,7 +29171,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28784,7 +29203,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28891,7 +29311,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28924,7 +29345,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28956,7 +29378,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28988,7 +29411,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29020,7 +29444,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29052,7 +29477,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29084,7 +29510,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29116,7 +29543,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29148,7 +29576,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29180,7 +29609,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29212,7 +29642,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29244,7 +29675,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29276,7 +29708,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29308,7 +29741,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29340,7 +29774,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29372,7 +29807,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29404,7 +29840,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29436,7 +29873,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29469,7 +29907,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29501,7 +29940,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29532,7 +29972,8 @@ "y": 0.0, "z": -37.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29563,7 +30004,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29594,7 +30036,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29625,7 +30068,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29732,7 +30176,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29765,7 +30210,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29797,7 +30243,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29829,7 +30276,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29861,7 +30309,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29893,7 +30342,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29925,7 +30375,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29957,7 +30408,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29989,7 +30441,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30021,7 +30474,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30053,7 +30507,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30085,7 +30540,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30117,7 +30573,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30149,7 +30606,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30181,7 +30639,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30213,7 +30672,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30245,7 +30705,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30277,7 +30738,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30310,7 +30772,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30342,7 +30805,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30373,7 +30837,8 @@ "y": 0.0, "z": -37.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30404,7 +30869,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30435,7 +30901,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30466,7 +30933,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30660,7 +31128,8 @@ "y": 0.0, "z": -37.800000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30693,7 +31162,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30740,7 +31210,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30773,7 +31244,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30806,7 +31278,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30839,7 +31312,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30873,7 +31347,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30906,7 +31381,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30940,7 +31416,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30973,7 +31450,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31006,7 +31484,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31128,7 +31607,8 @@ "y": 0.0, "z": -37.800000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31161,7 +31641,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31208,7 +31689,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -31241,7 +31723,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -31274,7 +31757,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31307,7 +31791,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31341,7 +31826,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31374,7 +31860,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31408,7 +31895,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31441,7 +31929,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31474,7 +31963,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31596,7 +32086,8 @@ "y": 0.0, "z": -37.800000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31629,7 +32120,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31676,7 +32168,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -31709,7 +32202,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -31742,7 +32236,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31775,7 +32270,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31809,7 +32305,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31842,7 +32339,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31876,7 +32374,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31909,7 +32408,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31942,7 +32442,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32368,7 +32869,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32401,7 +32903,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32434,7 +32937,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32480,7 +32984,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32512,7 +33017,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32546,7 +33052,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32579,7 +33086,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32613,7 +33121,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32646,7 +33155,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32679,7 +33189,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32712,7 +33223,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32745,7 +33257,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32777,7 +33290,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32808,7 +33322,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32839,7 +33354,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32871,7 +33387,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32917,7 +33434,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32963,7 +33481,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32996,7 +33515,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33029,7 +33549,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33062,7 +33583,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33094,7 +33616,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33139,7 +33662,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33260,7 +33784,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33293,7 +33818,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33326,7 +33852,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33372,7 +33899,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33404,7 +33932,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33438,7 +33967,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33471,7 +34001,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33505,7 +34036,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33538,7 +34070,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33571,7 +34104,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33604,7 +34138,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33637,7 +34172,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33669,7 +34205,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33700,7 +34237,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33731,7 +34269,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33763,7 +34302,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33809,7 +34349,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33855,7 +34396,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33888,7 +34430,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33921,7 +34464,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33954,7 +34498,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33986,7 +34531,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -34031,7 +34577,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -34152,7 +34699,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34185,7 +34733,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34218,7 +34767,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34264,7 +34814,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34296,7 +34847,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34330,7 +34882,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34363,7 +34916,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34397,7 +34951,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34430,7 +34985,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34463,7 +35019,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34496,7 +35053,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34529,7 +35087,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34561,7 +35120,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34592,7 +35152,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34623,7 +35184,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34655,7 +35217,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34701,7 +35264,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34747,7 +35311,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34780,7 +35345,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34813,7 +35379,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34846,7 +35413,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34878,7 +35446,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34923,7 +35492,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -35237,7 +35807,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35269,7 +35840,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35315,7 +35887,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35347,7 +35920,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35379,7 +35953,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35425,7 +36000,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35471,7 +36047,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35502,7 +36079,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35533,7 +36111,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35628,7 +36207,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35660,7 +36240,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35706,7 +36287,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35738,7 +36320,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35770,7 +36353,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35816,7 +36400,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35862,7 +36447,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35893,7 +36479,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35924,7 +36511,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36019,7 +36607,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36051,7 +36640,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36097,7 +36687,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36129,7 +36720,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36161,7 +36753,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36207,7 +36800,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36253,7 +36847,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36284,7 +36879,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36315,7 +36911,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36439,7 +37036,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36471,7 +37069,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36502,7 +37101,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36533,7 +37133,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36564,7 +37165,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36595,7 +37197,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36627,7 +37230,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36673,7 +37277,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36704,7 +37309,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36735,7 +37341,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36766,7 +37373,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36887,7 +37495,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36919,7 +37528,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36950,7 +37560,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36981,7 +37592,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -37012,7 +37624,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -37043,7 +37656,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -37075,7 +37689,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -37121,7 +37736,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -37152,7 +37768,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -37183,7 +37800,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -37214,7 +37832,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -37335,7 +37954,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -37367,7 +37987,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -37398,7 +38019,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -37429,7 +38051,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -37460,7 +38083,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -37491,7 +38115,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -37523,7 +38148,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -37569,7 +38195,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -37600,7 +38227,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -37631,7 +38259,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -37662,7 +38291,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -38983,7 +39613,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39015,7 +39646,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39061,7 +39693,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39093,7 +39726,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39125,7 +39759,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39171,7 +39806,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39217,7 +39853,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39248,7 +39885,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39279,7 +39917,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39374,7 +40013,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39406,7 +40046,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39452,7 +40093,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39484,7 +40126,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39516,7 +40159,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39562,7 +40206,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39608,7 +40253,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39639,7 +40285,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39670,7 +40317,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39765,7 +40413,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39797,7 +40446,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39843,7 +40493,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39875,7 +40526,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39907,7 +40559,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39953,7 +40606,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39999,7 +40653,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40030,7 +40685,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40061,7 +40717,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40185,7 +40842,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40217,7 +40875,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40248,7 +40907,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40279,7 +40939,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40310,7 +40971,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40341,7 +41003,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40373,7 +41036,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40419,7 +41083,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40450,7 +41115,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40481,7 +41147,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40512,7 +41179,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40633,7 +41301,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40665,7 +41334,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40696,7 +41366,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40727,7 +41398,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40758,7 +41430,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40789,7 +41462,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40821,7 +41495,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40867,7 +41542,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40898,7 +41574,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40929,7 +41606,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40960,7 +41638,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -41081,7 +41760,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41113,7 +41793,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41144,7 +41825,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41175,7 +41857,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41206,7 +41889,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41237,7 +41921,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41269,7 +41954,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41315,7 +42001,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41346,7 +42033,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41377,7 +42065,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41408,7 +42097,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41542,7 +42232,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41574,7 +42265,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41620,7 +42312,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41652,7 +42345,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41684,7 +42378,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41730,7 +42425,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41776,7 +42472,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41807,7 +42504,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41838,7 +42536,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41933,7 +42632,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -41965,7 +42665,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42011,7 +42712,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42043,7 +42745,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42075,7 +42778,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42121,7 +42825,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42167,7 +42872,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42198,7 +42904,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42229,7 +42936,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42324,7 +43032,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -42356,7 +43065,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -42402,7 +43112,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -42434,7 +43145,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -42466,7 +43178,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -42512,7 +43225,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42558,7 +43272,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42589,7 +43304,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42620,7 +43336,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42743,7 +43460,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42775,7 +43493,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42822,7 +43541,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42854,7 +43574,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42899,7 +43620,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42930,7 +43652,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42961,7 +43684,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42992,7 +43716,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43087,7 +43812,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -43119,7 +43845,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -43166,7 +43893,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43198,7 +43926,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43243,7 +43972,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43274,7 +44004,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43305,7 +44036,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43336,7 +44068,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43431,7 +44164,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43463,7 +44197,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43510,7 +44245,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43542,7 +44278,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43587,7 +44324,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43618,7 +44356,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43649,7 +44388,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43680,7 +44420,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45085,7 +45826,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -45117,7 +45859,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45149,7 +45892,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45182,7 +45926,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45215,7 +45960,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45247,7 +45993,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45343,7 +46090,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -45375,7 +46123,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -45407,7 +46156,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -45440,7 +46190,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -45473,7 +46224,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -45505,7 +46257,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -45601,7 +46354,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -45633,7 +46387,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -45665,7 +46420,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -45698,7 +46454,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -45731,7 +46488,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -45763,7 +46521,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -45989,7 +46748,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46021,7 +46781,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46054,7 +46815,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46150,7 +46912,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -46182,7 +46945,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -46215,7 +46979,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46311,7 +47076,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46343,7 +47109,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46376,7 +47143,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -48939,6 +49707,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "CleanupBead Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[58750bf5fb][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[58750bf5fb][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4].json index 7c04e4274de..63ed50d9c04 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[58750bf5fb][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[58750bf5fb][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol4].json @@ -55,6 +55,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[59b00713a7][Flex_S_v2_18_ligseq].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[59b00713a7][Flex_S_v2_18_ligseq].json index 3aaaf1b9915..4744b1f1992 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[59b00713a7][Flex_S_v2_18_ligseq].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[59b00713a7][Flex_S_v2_18_ligseq].json @@ -12445,7 +12445,8 @@ "y": 0.0, "z": -14.680000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12478,7 +12479,8 @@ "y": 0.0, "z": -14.68 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12510,7 +12512,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12542,7 +12545,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12576,7 +12580,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12609,7 +12614,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12643,7 +12649,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12676,7 +12683,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12710,7 +12718,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12743,7 +12752,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12777,7 +12787,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12810,7 +12821,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12844,7 +12856,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12877,7 +12890,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12911,7 +12925,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12944,7 +12959,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12978,7 +12994,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13011,7 +13028,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13045,7 +13063,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13078,7 +13097,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13112,7 +13132,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13145,7 +13166,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13178,7 +13200,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13210,7 +13233,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13535,7 +13559,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13569,7 +13594,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13602,7 +13628,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13636,7 +13663,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13669,7 +13697,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13702,7 +13731,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13735,7 +13765,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13768,7 +13799,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13800,7 +13832,8 @@ "y": 0.0, "z": -11.279999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13832,7 +13865,8 @@ "y": 0.0, "z": -11.279999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13864,7 +13898,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13896,7 +13931,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13929,7 +13965,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13961,7 +13998,8 @@ "y": 0.0, "z": -11.279999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13993,7 +14031,8 @@ "y": 0.0, "z": -11.279999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14026,7 +14065,8 @@ "y": 0.0, "z": -11.279999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14058,7 +14098,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14090,7 +14131,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14123,7 +14165,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14155,7 +14198,8 @@ "y": 0.0, "z": -11.279999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14187,7 +14231,8 @@ "y": 0.0, "z": -11.279999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14219,7 +14264,8 @@ "y": 0.0, "z": 2.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14250,7 +14296,8 @@ "y": 0.0, "z": 5.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14281,7 +14328,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14312,7 +14360,8 @@ "y": 0.0, "z": 5.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14540,7 +14589,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14572,7 +14622,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14618,7 +14669,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14650,7 +14702,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14682,7 +14735,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14714,7 +14768,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14760,7 +14815,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14791,7 +14847,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14822,7 +14879,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14944,7 +15002,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -14976,7 +15035,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15007,7 +15067,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15038,7 +15099,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15069,7 +15131,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15101,7 +15164,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15147,7 +15211,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15178,7 +15243,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15209,7 +15275,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15240,7 +15307,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15375,7 +15443,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15407,7 +15476,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15453,7 +15523,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15485,7 +15556,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15517,7 +15589,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15549,7 +15622,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15595,7 +15669,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15626,7 +15701,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15657,7 +15733,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15779,7 +15856,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15811,7 +15889,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15842,7 +15921,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15873,7 +15953,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15904,7 +15985,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15936,7 +16018,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15982,7 +16065,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16013,7 +16097,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16044,7 +16129,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16075,7 +16161,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16210,7 +16297,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16242,7 +16330,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16288,7 +16377,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16320,7 +16410,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16352,7 +16443,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16384,7 +16476,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -16430,7 +16523,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -16461,7 +16555,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -16492,7 +16587,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -16627,7 +16723,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16659,7 +16756,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16844,7 +16942,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -16876,7 +16975,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16908,7 +17008,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16940,7 +17041,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17168,7 +17270,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17200,7 +17303,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17233,7 +17337,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17446,7 +17551,8 @@ "y": 0.0, "z": -14.530000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17480,7 +17586,8 @@ "y": 0.0, "z": -14.530000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17513,7 +17620,8 @@ "y": 0.0, "z": -14.530000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17547,7 +17655,8 @@ "y": 0.0, "z": -14.530000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17580,7 +17689,8 @@ "y": 0.0, "z": -14.530000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17613,7 +17723,8 @@ "y": 0.0, "z": -14.530000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17646,7 +17757,8 @@ "y": 0.0, "z": -14.680000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17678,7 +17790,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17724,7 +17837,8 @@ "y": 0.0, "z": -14.679999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17756,7 +17870,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17788,7 +17903,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17822,7 +17938,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17855,7 +17972,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17889,7 +18007,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17922,7 +18041,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17956,7 +18076,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17989,7 +18110,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18023,7 +18145,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18056,7 +18179,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18090,7 +18214,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18123,7 +18248,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18157,7 +18283,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18190,7 +18317,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18224,7 +18352,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18257,7 +18386,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18291,7 +18421,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18324,7 +18455,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18358,7 +18490,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18391,7 +18524,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18425,7 +18559,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18458,7 +18593,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18492,7 +18628,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18525,7 +18662,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18559,7 +18697,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18592,7 +18731,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18626,7 +18766,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18659,7 +18800,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18693,7 +18835,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18726,7 +18869,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18760,7 +18904,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18793,7 +18938,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18827,7 +18973,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18860,7 +19007,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18894,7 +19042,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18927,7 +19076,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18961,7 +19111,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18994,7 +19145,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19028,7 +19180,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19061,7 +19214,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19094,7 +19248,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19126,7 +19281,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19332,7 +19488,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19365,7 +19522,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19411,7 +19569,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19639,7 +19798,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19671,7 +19831,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19717,7 +19878,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19749,7 +19911,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19781,7 +19944,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19813,7 +19977,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19859,7 +20024,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19890,7 +20056,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19921,7 +20088,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20091,7 +20259,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -20123,7 +20292,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20155,7 +20325,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20187,7 +20358,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20429,7 +20601,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20461,7 +20634,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20507,7 +20681,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20539,7 +20714,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20571,7 +20747,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20603,7 +20780,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20649,7 +20827,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20680,7 +20859,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20711,7 +20891,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20881,7 +21062,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -20913,7 +21095,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20945,7 +21128,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20977,7 +21161,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21219,7 +21404,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21251,7 +21437,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21297,7 +21484,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21329,7 +21517,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21361,7 +21550,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21393,7 +21583,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21439,7 +21630,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21470,7 +21662,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21501,7 +21694,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21636,7 +21830,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21668,7 +21863,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21853,7 +22049,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -21885,7 +22082,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21917,7 +22115,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21949,7 +22148,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22335,7 +22535,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22367,7 +22568,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22400,7 +22602,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -22641,6 +22844,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "AMPure Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5c57add326][pl_Omega_HDQ_DNA_Bacteria_Flex_96_channel].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5c57add326][pl_Omega_HDQ_DNA_Bacteria_Flex_96_channel].json index 0da5924a86a..353a1b46f45 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5c57add326][pl_Omega_HDQ_DNA_Bacteria_Flex_96_channel].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5c57add326][pl_Omega_HDQ_DNA_Bacteria_Flex_96_channel].json @@ -28729,7 +28729,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28762,7 +28763,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28795,7 +28797,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28828,7 +28831,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28861,7 +28865,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28894,7 +28899,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28941,7 +28947,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28973,7 +28980,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29005,7 +29013,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29038,7 +29047,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29100,7 +29110,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29133,7 +29144,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29166,7 +29178,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29199,7 +29212,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29232,7 +29246,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29265,7 +29280,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29298,7 +29314,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29331,7 +29348,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29364,7 +29382,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29397,7 +29416,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29430,7 +29450,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29463,7 +29484,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29496,7 +29518,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29529,7 +29552,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29562,7 +29586,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29595,7 +29620,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29628,7 +29654,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29661,7 +29688,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29694,7 +29722,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29727,7 +29756,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29760,7 +29790,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29793,7 +29824,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29826,7 +29858,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29859,7 +29892,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29892,7 +29926,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29925,7 +29960,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29958,7 +29994,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29991,7 +30028,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30024,7 +30062,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30057,7 +30096,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30090,7 +30130,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30123,7 +30164,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30156,7 +30198,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30189,7 +30232,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30222,7 +30266,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30255,7 +30300,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30288,7 +30334,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30321,7 +30368,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30354,7 +30402,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30387,7 +30436,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30420,7 +30470,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30453,7 +30504,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30486,7 +30538,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30519,7 +30572,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30552,7 +30606,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30585,7 +30640,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30618,7 +30674,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30651,7 +30708,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30684,7 +30742,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30717,7 +30776,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30750,7 +30810,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30783,7 +30844,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30816,7 +30878,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30849,7 +30912,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30882,7 +30946,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30915,7 +30980,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30948,7 +31014,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30981,7 +31048,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31014,7 +31082,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31047,7 +31116,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31080,7 +31150,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31113,7 +31184,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31146,7 +31218,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31179,7 +31252,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31212,7 +31286,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31245,7 +31320,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31278,7 +31354,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31311,7 +31388,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31344,7 +31422,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31377,7 +31456,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31410,7 +31490,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31443,7 +31524,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31476,7 +31558,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31509,7 +31592,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31542,7 +31626,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31575,7 +31660,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31608,7 +31694,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31641,7 +31728,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31674,7 +31762,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31707,7 +31796,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31740,7 +31830,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31773,7 +31864,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31806,7 +31898,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31839,7 +31932,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31872,7 +31966,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31905,7 +32000,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31938,7 +32034,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31971,7 +32068,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32004,7 +32102,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32037,7 +32136,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32070,7 +32170,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32103,7 +32204,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32136,7 +32238,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32169,7 +32272,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32202,7 +32306,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32235,7 +32340,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32268,7 +32374,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32301,7 +32408,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32334,7 +32442,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32367,7 +32476,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32400,7 +32510,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32433,7 +32544,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32466,7 +32578,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32499,7 +32612,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32532,7 +32646,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32565,7 +32680,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32598,7 +32714,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32631,7 +32748,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32664,7 +32782,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32697,7 +32816,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32730,7 +32850,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32763,7 +32884,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32796,7 +32918,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32829,7 +32952,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32862,7 +32986,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32895,7 +33020,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32928,7 +33054,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32961,7 +33088,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32994,7 +33122,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33027,7 +33156,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33060,7 +33190,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33093,7 +33224,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33126,7 +33258,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33159,7 +33292,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33192,7 +33326,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33225,7 +33360,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33258,7 +33394,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33291,7 +33428,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33324,7 +33462,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33357,7 +33496,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33390,7 +33530,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33423,7 +33564,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33456,7 +33598,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33489,7 +33632,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33522,7 +33666,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33555,7 +33700,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33588,7 +33734,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33621,7 +33768,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33654,7 +33802,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33687,7 +33836,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33720,7 +33870,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33753,7 +33904,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33786,7 +33938,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33819,7 +33972,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33852,7 +34006,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33885,7 +34040,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34056,7 +34212,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34088,7 +34245,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34120,7 +34278,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34153,7 +34312,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34185,7 +34345,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34393,7 +34554,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34425,7 +34587,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34457,7 +34620,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34490,7 +34654,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34523,7 +34688,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34556,7 +34722,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34589,7 +34756,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34622,7 +34790,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34655,7 +34824,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34688,7 +34858,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34721,7 +34892,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34754,7 +34926,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34787,7 +34960,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34820,7 +34994,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34853,7 +35028,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34886,7 +35062,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34919,7 +35096,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34952,7 +35130,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34985,7 +35164,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35018,7 +35198,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35051,7 +35232,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35084,7 +35266,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35117,7 +35300,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35150,7 +35334,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35183,7 +35368,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35216,7 +35402,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35249,7 +35436,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35282,7 +35470,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35315,7 +35504,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35348,7 +35538,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35381,7 +35572,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35414,7 +35606,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35447,7 +35640,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35480,7 +35674,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35513,7 +35708,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35546,7 +35742,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35579,7 +35776,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35612,7 +35810,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35645,7 +35844,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35678,7 +35878,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35711,7 +35912,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35744,7 +35946,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35777,7 +35980,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35810,7 +36014,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35843,7 +36048,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35876,7 +36082,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35909,7 +36116,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35942,7 +36150,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35975,7 +36184,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36008,7 +36218,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36041,7 +36252,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36074,7 +36286,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36107,7 +36320,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36140,7 +36354,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36173,7 +36388,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36206,7 +36422,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36239,7 +36456,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36272,7 +36490,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36305,7 +36524,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36338,7 +36558,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36371,7 +36592,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36404,7 +36626,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36437,7 +36660,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36470,7 +36694,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36503,7 +36728,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36536,7 +36762,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36569,7 +36796,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36602,7 +36830,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36635,7 +36864,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36668,7 +36898,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36701,7 +36932,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36734,7 +36966,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36767,7 +37000,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36800,7 +37034,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36833,7 +37068,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36866,7 +37102,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36899,7 +37136,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36932,7 +37170,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36965,7 +37204,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36998,7 +37238,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37031,7 +37272,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37064,7 +37306,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37097,7 +37340,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37130,7 +37374,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37163,7 +37408,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37196,7 +37442,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37229,7 +37476,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37262,7 +37510,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37295,7 +37544,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37328,7 +37578,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37361,7 +37612,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37394,7 +37646,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37427,7 +37680,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37460,7 +37714,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37493,7 +37748,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37526,7 +37782,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37559,7 +37816,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37592,7 +37850,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37625,7 +37884,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37658,7 +37918,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37691,7 +37952,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37724,7 +37986,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37757,7 +38020,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37790,7 +38054,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37823,7 +38088,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37856,7 +38122,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37889,7 +38156,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37922,7 +38190,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37955,7 +38224,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37988,7 +38258,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38021,7 +38292,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38054,7 +38326,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38087,7 +38360,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38120,7 +38394,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38153,7 +38428,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38186,7 +38462,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38219,7 +38496,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38252,7 +38530,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38285,7 +38564,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38318,7 +38598,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38351,7 +38632,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38384,7 +38666,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38417,7 +38700,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38450,7 +38734,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38483,7 +38768,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38516,7 +38802,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38549,7 +38836,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38582,7 +38870,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38615,7 +38904,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38648,7 +38938,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38681,7 +38972,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38714,7 +39006,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38747,7 +39040,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38780,7 +39074,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38813,7 +39108,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38846,7 +39142,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38879,7 +39176,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38912,7 +39210,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38945,7 +39244,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38978,7 +39278,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39011,7 +39312,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39044,7 +39346,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39077,7 +39380,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39110,7 +39414,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39143,7 +39448,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39176,7 +39482,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39209,7 +39516,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39242,7 +39550,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39275,7 +39584,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39308,7 +39618,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39465,7 +39776,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39498,7 +39810,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39531,7 +39844,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39564,7 +39878,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39597,7 +39912,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39630,7 +39946,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39663,7 +39980,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39696,7 +40014,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39729,7 +40048,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39762,7 +40082,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39795,7 +40116,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39828,7 +40150,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39861,7 +40184,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39894,7 +40218,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39927,7 +40252,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39960,7 +40286,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39993,7 +40320,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40026,7 +40354,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40059,7 +40388,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40092,7 +40422,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40125,7 +40456,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40158,7 +40490,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40191,7 +40524,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40224,7 +40558,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40257,7 +40592,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40290,7 +40626,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40323,7 +40660,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40356,7 +40694,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40389,7 +40728,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40422,7 +40762,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40455,7 +40796,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40488,7 +40830,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40521,7 +40864,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40554,7 +40898,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40587,7 +40932,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40620,7 +40966,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40653,7 +41000,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40686,7 +41034,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40719,7 +41068,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40752,7 +41102,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40785,7 +41136,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40818,7 +41170,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40851,7 +41204,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40884,7 +41238,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40917,7 +41272,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40950,7 +41306,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40983,7 +41340,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41016,7 +41374,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41049,7 +41408,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41082,7 +41442,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41115,7 +41476,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41148,7 +41510,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41181,7 +41544,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41214,7 +41578,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41451,7 +41816,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41484,7 +41850,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41643,7 +42010,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41676,7 +42044,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41708,7 +42077,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41739,7 +42109,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41771,7 +42142,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42007,7 +42379,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42040,7 +42413,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42199,7 +42573,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42232,7 +42607,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42264,7 +42640,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42295,7 +42672,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42327,7 +42705,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42563,7 +42942,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42596,7 +42976,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42755,7 +43136,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42788,7 +43170,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42820,7 +43203,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42851,7 +43235,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42883,7 +43268,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43119,7 +43505,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43152,7 +43539,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43611,7 +43999,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43644,7 +44033,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43677,7 +44067,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43710,7 +44101,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43743,7 +44135,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43776,7 +44169,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43809,7 +44203,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43842,7 +44237,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43875,7 +44271,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43908,7 +44305,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43941,7 +44339,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43974,7 +44373,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44007,7 +44407,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44040,7 +44441,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44073,7 +44475,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44106,7 +44509,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44139,7 +44543,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44172,7 +44577,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44205,7 +44611,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44238,7 +44645,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44271,7 +44679,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44304,7 +44713,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44337,7 +44747,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44370,7 +44781,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44403,7 +44815,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44436,7 +44849,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44469,7 +44883,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44502,7 +44917,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44535,7 +44951,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44568,7 +44985,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44601,7 +45019,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44634,7 +45053,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44667,7 +45087,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44700,7 +45121,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44733,7 +45155,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44766,7 +45189,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44799,7 +45223,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44832,7 +45257,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44865,7 +45291,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44898,7 +45325,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44931,7 +45359,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44964,7 +45393,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44997,7 +45427,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45030,7 +45461,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45063,7 +45495,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45096,7 +45529,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45129,7 +45563,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45162,7 +45597,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45195,7 +45631,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45228,7 +45665,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45261,7 +45699,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45294,7 +45733,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45517,7 +45957,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45550,7 +45991,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45751,6 +46193,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Sample Resuspended in PBS", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5e958b7c98][Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5e958b7c98][Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol].json index 75ea09b454d..059e7fc2b84 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5e958b7c98][Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5e958b7c98][Flex_X_v2_16_P300MGen2_None_OT2PipetteInFlexProtocol].json @@ -1181,7 +1181,14 @@ }, "id": "UUID", "key": "bd403a1c851a75b4b68ce34796d713fa", - "notes": [], + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], "params": { "liquidPresenceDetection": false, "mount": "left", @@ -1251,6 +1258,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5edb9b4de3][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_2].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5edb9b4de3][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_2].json new file mode 100644 index 00000000000..47c65a0dfc5 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[5edb9b4de3][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_2].json @@ -0,0 +1,1261 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "ValueError [line 167]: Partial column configuration is only supported on 8-Channel pipettes.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "ValueError: Partial column configuration is only supported on 8-Channel pipettes.", + "errorCode": "4000", + "errorInfo": { + "args": "('Partial column configuration is only supported on 8-Channel pipettes.',)", + "class": "ValueError", + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line N, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line N, in \n\n File \"Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_2.py\", line N, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line N, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/instrument_context.py\", line N, in configure_nozzle_layout\n self._raise_if_configuration_not_supported_by_pipette(style)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/instrument_context.py\", line N, in _raise_if_configuration_not_supported_by_pipette\n raise ValueError(\n" + }, + "errorType": "PythonException", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_2.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Invalid tip configs that should error" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60015c6e65][OT2_X_v2_18_None_None_duplicateRTPVariableName].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60015c6e65][OT2_X_v2_18_None_None_duplicateRTPVariableName].json index 86d3274f412..3c69dda38e7 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60015c6e65][OT2_X_v2_18_None_None_duplicateRTPVariableName].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60015c6e65][OT2_X_v2_18_None_None_duplicateRTPVariableName].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Multiple RTP Variables with Same Name" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[604023f7f1][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[604023f7f1][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol3].json index 24e88e5454e..fde783d94b8 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[604023f7f1][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[604023f7f1][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol3].json @@ -121,7 +121,14 @@ }, "id": "UUID", "key": "a3a7eed460d8d94a91747f23820a180d", - "notes": [], + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], "params": { "location": { "slotName": "C3" @@ -190,6 +197,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60c1d39463][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_int_default_no_matching_choices].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60c1d39463][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_int_default_no_matching_choices].json index 726906c04d4..b8ef1cbc5f2 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60c1d39463][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_int_default_no_matching_choices].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[60c1d39463][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_int_default_no_matching_choices].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "default choice does not match a choice" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6122c72996][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_1].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6122c72996][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_1].json new file mode 100644 index 00000000000..180178d1d44 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6122c72996][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_1].json @@ -0,0 +1,1261 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "ValueError [line 167]: Partial column configuration is only supported on 8-Channel pipettes.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "ValueError: Partial column configuration is only supported on 8-Channel pipettes.", + "errorCode": "4000", + "errorInfo": { + "args": "('Partial column configuration is only supported on 8-Channel pipettes.',)", + "class": "ValueError", + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line N, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line N, in \n\n File \"Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_1.py\", line N, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line N, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/instrument_context.py\", line N, in configure_nozzle_layout\n self._raise_if_configuration_not_supported_by_pipette(style)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/instrument_context.py\", line N, in _raise_if_configuration_not_supported_by_pipette\n raise ValueError(\n" + }, + "errorType": "PythonException", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_1.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Invalid tip configs that should error" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6126498df7][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6126498df7][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4].json index d8409d8db46..8623a021746 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6126498df7][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6126498df7][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInStagingAreaCol4].json @@ -55,6 +55,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[61619d5498][Flex_S_v2_18_NO_PIPETTES_GoldenRTP].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[61619d5498][Flex_S_v2_18_NO_PIPETTES_GoldenRTP].json index afa5bb0b4d2..8b06eca9390 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[61619d5498][Flex_S_v2_18_NO_PIPETTES_GoldenRTP].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[61619d5498][Flex_S_v2_18_NO_PIPETTES_GoldenRTP].json @@ -295,6 +295,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Golden RTP Examples" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[618f29898f][pl_Flex_customizable_serial_dilution_upload].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[618f29898f][pl_Flex_customizable_serial_dilution_upload].json index 6ddeb4aca16..b1528f23cbf 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[618f29898f][pl_Flex_customizable_serial_dilution_upload].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[618f29898f][pl_Flex_customizable_serial_dilution_upload].json @@ -3973,7 +3973,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4005,7 +4006,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4037,7 +4039,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4070,7 +4073,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -4103,7 +4107,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4135,7 +4140,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4167,7 +4173,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4200,7 +4207,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -4233,7 +4241,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4265,7 +4274,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4297,7 +4307,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4330,7 +4341,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -4363,7 +4375,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4395,7 +4408,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4427,7 +4441,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4460,7 +4475,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -4493,7 +4509,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4525,7 +4542,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4557,7 +4575,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4590,7 +4609,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -4623,7 +4643,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4655,7 +4676,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4687,7 +4709,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4720,7 +4743,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -4753,7 +4777,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4785,7 +4810,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4817,7 +4843,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4850,7 +4877,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -4883,7 +4911,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4915,7 +4944,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4947,7 +4977,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4980,7 +5011,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -5013,7 +5045,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5045,7 +5078,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5077,7 +5111,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5110,7 +5145,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -5143,7 +5179,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5175,7 +5212,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5207,7 +5245,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5240,7 +5279,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -5337,7 +5377,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5369,7 +5410,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5401,7 +5443,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5434,7 +5477,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5467,7 +5511,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5500,7 +5545,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5533,7 +5579,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5566,7 +5613,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5599,7 +5647,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5632,7 +5681,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5665,7 +5715,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5698,7 +5749,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5731,7 +5783,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5764,7 +5817,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5797,7 +5851,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5829,7 +5884,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5861,7 +5917,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5894,7 +5951,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -5927,7 +5985,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -5960,7 +6019,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -5993,7 +6053,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6026,7 +6087,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6059,7 +6121,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6092,7 +6155,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6125,7 +6189,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6158,7 +6223,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6191,7 +6257,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6224,7 +6291,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6257,7 +6325,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6289,7 +6358,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6321,7 +6391,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6354,7 +6425,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6387,7 +6459,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6420,7 +6493,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6453,7 +6527,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6486,7 +6561,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6519,7 +6595,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6552,7 +6629,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6585,7 +6663,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6618,7 +6697,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6651,7 +6731,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6684,7 +6765,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6717,7 +6799,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6749,7 +6832,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6781,7 +6865,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6814,7 +6899,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -6847,7 +6933,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -6880,7 +6967,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -6913,7 +7001,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -6946,7 +7035,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -6979,7 +7069,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -7012,7 +7103,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -7045,7 +7137,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -7078,7 +7171,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -7111,7 +7205,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -7144,7 +7239,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -7177,7 +7273,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -7209,7 +7306,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -7241,7 +7339,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -7274,7 +7373,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -7307,7 +7407,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -7340,7 +7441,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -7373,7 +7475,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -7406,7 +7509,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -7439,7 +7543,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -7472,7 +7577,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -7505,7 +7611,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -7538,7 +7645,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -7571,7 +7679,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -7604,7 +7713,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -7637,7 +7747,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -7669,7 +7780,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -7701,7 +7813,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -7734,7 +7847,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -7767,7 +7881,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -7800,7 +7915,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -7833,7 +7949,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -7866,7 +7983,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -7899,7 +8017,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -7932,7 +8051,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -7965,7 +8085,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -7998,7 +8119,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -8031,7 +8153,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -8064,7 +8187,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -8097,7 +8221,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -8129,7 +8254,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -8161,7 +8287,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -8194,7 +8321,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -8227,7 +8355,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -8260,7 +8389,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -8293,7 +8423,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -8326,7 +8457,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -8359,7 +8491,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -8392,7 +8525,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -8425,7 +8559,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -8458,7 +8593,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -8491,7 +8627,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -8524,7 +8661,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -8557,7 +8695,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -8589,7 +8728,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -8621,7 +8761,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -8654,7 +8795,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -8687,7 +8829,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -8720,7 +8863,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -8753,7 +8897,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -8786,7 +8931,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -8819,7 +8965,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -8852,7 +8999,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -8885,7 +9033,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -8918,7 +9067,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -8951,7 +9101,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -8984,7 +9135,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -9017,7 +9169,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -9049,7 +9202,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -9081,7 +9235,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -9114,7 +9269,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9147,7 +9303,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9180,7 +9337,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9213,7 +9371,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9246,7 +9405,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9279,7 +9439,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9312,7 +9473,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9345,7 +9507,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9378,7 +9541,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9411,7 +9575,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9444,7 +9609,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9477,7 +9643,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9509,7 +9676,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9541,7 +9709,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9574,7 +9743,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9607,7 +9777,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9640,7 +9811,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9673,7 +9845,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9706,7 +9879,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9739,7 +9913,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9772,7 +9947,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9805,7 +9981,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9838,7 +10015,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9871,7 +10049,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9904,7 +10083,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -10001,7 +10181,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10033,7 +10214,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10065,7 +10247,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10098,7 +10281,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -10202,6 +10386,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Diluent liquid is filled in the reservoir", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[63ea171b47][pl_M_N_Nucleomag_DNA_Flex_multi].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[63ea171b47][pl_M_N_Nucleomag_DNA_Flex_multi].json index bb4e8521786..1441d3d1cac 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[63ea171b47][pl_M_N_Nucleomag_DNA_Flex_multi].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[63ea171b47][pl_M_N_Nucleomag_DNA_Flex_multi].json @@ -9135,7 +9135,8 @@ "y": 0.0, "z": -26.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9168,7 +9169,8 @@ "y": 0.0, "z": -6.850000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9201,7 +9203,8 @@ "y": 0.0, "z": -26.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9234,7 +9237,8 @@ "y": 0.0, "z": -6.850000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9267,7 +9271,8 @@ "y": 0.0, "z": -26.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9300,7 +9305,8 @@ "y": 0.0, "z": -6.850000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9333,7 +9339,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9365,7 +9372,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9397,7 +9405,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9430,7 +9439,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9463,7 +9473,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9496,7 +9507,8 @@ "y": 0.0, "z": -13.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9529,7 +9541,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9562,7 +9575,8 @@ "y": 0.0, "z": -13.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9595,7 +9609,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9628,7 +9643,8 @@ "y": 0.0, "z": -13.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9661,7 +9677,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9694,7 +9711,8 @@ "y": 0.0, "z": -13.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9727,7 +9745,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9760,7 +9779,8 @@ "y": 0.0, "z": -13.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9793,7 +9813,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9826,7 +9847,8 @@ "y": 0.0, "z": -13.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9859,7 +9881,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9892,7 +9915,8 @@ "y": 0.0, "z": -13.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9925,7 +9949,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9958,7 +9983,8 @@ "y": 0.0, "z": -13.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9991,7 +10017,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10024,7 +10051,8 @@ "y": 0.0, "z": -13.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10057,7 +10085,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10090,7 +10119,8 @@ "y": 0.0, "z": -13.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10123,7 +10153,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10156,7 +10187,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10367,7 +10399,8 @@ "y": 0.0, "z": 5.0000000000000036 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10399,7 +10432,8 @@ "y": 1.75, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10432,7 +10466,8 @@ "y": 1.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10465,7 +10500,8 @@ "y": -1.75, "z": -21.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10498,7 +10534,8 @@ "y": 1.75, "z": -23.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10531,7 +10568,8 @@ "y": 1.75, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10564,7 +10602,8 @@ "y": 1.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10597,7 +10636,8 @@ "y": -1.75, "z": -21.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10630,7 +10670,8 @@ "y": 1.75, "z": -23.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10663,7 +10704,8 @@ "y": 1.75, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10696,7 +10738,8 @@ "y": 1.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10729,7 +10772,8 @@ "y": -1.75, "z": -21.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10762,7 +10806,8 @@ "y": 1.75, "z": -23.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10795,7 +10840,8 @@ "y": 1.75, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10828,7 +10874,8 @@ "y": 1.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10861,7 +10908,8 @@ "y": -1.75, "z": -21.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10894,7 +10942,8 @@ "y": 1.75, "z": -23.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10927,7 +10976,8 @@ "y": 1.75, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10960,7 +11010,8 @@ "y": 1.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10993,7 +11044,8 @@ "y": -1.75, "z": -21.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11026,7 +11078,8 @@ "y": 1.75, "z": -23.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11059,7 +11112,8 @@ "y": 1.75, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11092,7 +11146,8 @@ "y": 1.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11125,7 +11180,8 @@ "y": -1.75, "z": -21.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11158,7 +11214,8 @@ "y": 1.75, "z": -23.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11191,7 +11248,8 @@ "y": 1.75, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11224,7 +11282,8 @@ "y": 1.75, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11270,7 +11329,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11302,7 +11362,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11334,7 +11395,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11366,7 +11428,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11399,7 +11462,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11431,7 +11495,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11463,7 +11528,8 @@ "y": 1.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11496,7 +11562,8 @@ "y": 1.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11529,7 +11596,8 @@ "y": -1.75, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11562,7 +11630,8 @@ "y": 1.75, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11595,7 +11664,8 @@ "y": 1.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11628,7 +11698,8 @@ "y": 1.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11661,7 +11732,8 @@ "y": -1.75, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11694,7 +11766,8 @@ "y": 1.75, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11727,7 +11800,8 @@ "y": 1.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11760,7 +11834,8 @@ "y": 1.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11793,7 +11868,8 @@ "y": -1.75, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11826,7 +11902,8 @@ "y": 1.75, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11859,7 +11936,8 @@ "y": 1.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11892,7 +11970,8 @@ "y": 1.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11925,7 +12004,8 @@ "y": -1.75, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11958,7 +12038,8 @@ "y": 1.75, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11991,7 +12072,8 @@ "y": 1.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12024,7 +12106,8 @@ "y": 1.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12057,7 +12140,8 @@ "y": -1.75, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12090,7 +12174,8 @@ "y": 1.75, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12123,7 +12208,8 @@ "y": 1.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12156,7 +12242,8 @@ "y": 1.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12189,7 +12276,8 @@ "y": -1.75, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12222,7 +12310,8 @@ "y": 1.75, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12255,7 +12344,8 @@ "y": 1.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12288,7 +12378,8 @@ "y": 1.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12653,7 +12744,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12699,7 +12791,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12731,7 +12824,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12763,7 +12857,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12796,7 +12891,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12828,7 +12924,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12860,7 +12957,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13065,7 +13163,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -13097,7 +13196,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -13129,7 +13229,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -13162,7 +13263,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13483,7 +13585,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13529,7 +13632,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13561,7 +13665,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13593,7 +13698,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13626,7 +13732,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13658,7 +13765,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13690,7 +13798,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13895,7 +14004,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -13927,7 +14037,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -13959,7 +14070,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -13992,7 +14104,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14313,7 +14426,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14359,7 +14473,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14391,7 +14506,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14423,7 +14539,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14456,7 +14573,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14488,7 +14606,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14520,7 +14639,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14725,7 +14845,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -14757,7 +14878,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -14789,7 +14911,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -14822,7 +14945,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15144,7 +15268,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15177,7 +15302,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15274,7 +15400,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15307,7 +15434,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15354,7 +15482,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15387,7 +15516,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15496,7 +15626,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15529,7 +15660,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15887,7 +16019,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15919,7 +16052,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15951,7 +16085,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15984,7 +16119,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16016,7 +16152,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16047,7 +16184,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16079,7 +16217,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16247,6 +16386,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Cell Pellet", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[675d2c2562][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_east].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[675d2c2562][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_east].json new file mode 100644 index 00000000000..d27c90a866c --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[675d2c2562][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_east].json @@ -0,0 +1,1501 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-1000ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 97.47 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_1000ul", + "tipLength": 88, + "tipOverlap": 7.95 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.24, + "z": 9.47 + }, + "A10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.24, + "z": 9.47 + }, + "A11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.24, + "z": 9.47 + }, + "A12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.24, + "z": 9.47 + }, + "A2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.24, + "z": 9.47 + }, + "A3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.24, + "z": 9.47 + }, + "A4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.24, + "z": 9.47 + }, + "A5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.24, + "z": 9.47 + }, + "A6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.24, + "z": 9.47 + }, + "A7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.24, + "z": 9.47 + }, + "A8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.24, + "z": 9.47 + }, + "A9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.24, + "z": 9.47 + }, + "B1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.24, + "z": 9.47 + }, + "B10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.24, + "z": 9.47 + }, + "B11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.24, + "z": 9.47 + }, + "B12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.24, + "z": 9.47 + }, + "B2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.24, + "z": 9.47 + }, + "B3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.24, + "z": 9.47 + }, + "B4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.24, + "z": 9.47 + }, + "B5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.24, + "z": 9.47 + }, + "B6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.24, + "z": 9.47 + }, + "B7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.24, + "z": 9.47 + }, + "B8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.24, + "z": 9.47 + }, + "B9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.24, + "z": 9.47 + }, + "C1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.24, + "z": 9.47 + }, + "C10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.24, + "z": 9.47 + }, + "C11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.24, + "z": 9.47 + }, + "C12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.24, + "z": 9.47 + }, + "C2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.24, + "z": 9.47 + }, + "C3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.24, + "z": 9.47 + }, + "C4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.24, + "z": 9.47 + }, + "C5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.24, + "z": 9.47 + }, + "C6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.24, + "z": 9.47 + }, + "C7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.24, + "z": 9.47 + }, + "C8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.24, + "z": 9.47 + }, + "C9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.24, + "z": 9.47 + }, + "D1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.24, + "z": 9.47 + }, + "D10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.24, + "z": 9.47 + }, + "D11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.24, + "z": 9.47 + }, + "D12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.24, + "z": 9.47 + }, + "D2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.24, + "z": 9.47 + }, + "D3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.24, + "z": 9.47 + }, + "D4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.24, + "z": 9.47 + }, + "D5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.24, + "z": 9.47 + }, + "D6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.24, + "z": 9.47 + }, + "D7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.24, + "z": 9.47 + }, + "D8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.24, + "z": 9.47 + }, + "D9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.24, + "z": 9.47 + }, + "E1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.24, + "z": 9.47 + }, + "E10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.24, + "z": 9.47 + }, + "E11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.24, + "z": 9.47 + }, + "E12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.24, + "z": 9.47 + }, + "E2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.24, + "z": 9.47 + }, + "E3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.24, + "z": 9.47 + }, + "E4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.24, + "z": 9.47 + }, + "E5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.24, + "z": 9.47 + }, + "E6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.24, + "z": 9.47 + }, + "E7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.24, + "z": 9.47 + }, + "E8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.24, + "z": 9.47 + }, + "E9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.24, + "z": 9.47 + }, + "F1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.24, + "z": 9.47 + }, + "F10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.24, + "z": 9.47 + }, + "F11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.24, + "z": 9.47 + }, + "F12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.24, + "z": 9.47 + }, + "F2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.24, + "z": 9.47 + }, + "F3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.24, + "z": 9.47 + }, + "F4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.24, + "z": 9.47 + }, + "F5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.24, + "z": 9.47 + }, + "F6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.24, + "z": 9.47 + }, + "F7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.24, + "z": 9.47 + }, + "F8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.24, + "z": 9.47 + }, + "F9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.24, + "z": 9.47 + }, + "G1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.24, + "z": 9.47 + }, + "G10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.24, + "z": 9.47 + }, + "G11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.24, + "z": 9.47 + }, + "G12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.24, + "z": 9.47 + }, + "G2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.24, + "z": 9.47 + }, + "G3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.24, + "z": 9.47 + }, + "G4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.24, + "z": 9.47 + }, + "G5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.24, + "z": 9.47 + }, + "G6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.24, + "z": 9.47 + }, + "G7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.24, + "z": 9.47 + }, + "G8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.24, + "z": 9.47 + }, + "G9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.24, + "z": 9.47 + }, + "H1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.24, + "z": 9.47 + }, + "H10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.24, + "z": 9.47 + }, + "H11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.24, + "z": 9.47 + }, + "H12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.24, + "z": 9.47 + }, + "H2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.24, + "z": 9.47 + }, + "H3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.24, + "z": 9.47 + }, + "H4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.24, + "z": 9.47 + }, + "H5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.24, + "z": 9.47 + }, + "H6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.24, + "z": 9.47 + }, + "H7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.24, + "z": 9.47 + }, + "H8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.24, + "z": 9.47 + }, + "H9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.24, + "z": 9.47 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "D3" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/tube-rack-set-1" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 124.35 + }, + "gripperOffsets": {}, + "groups": [ + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352095", + "352096", + "352097", + "352099", + "352196" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 6x15 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A2", + "B1", + "B2", + "C1", + "C2" + ] + }, + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352070", + "352098" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 4x50 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A3", + "A4", + "B3", + "B4" + ] + } + ], + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Opentrons 10 Tube Rack with Falcon 4x50 mL, 6x15 mL Conical", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1" + ], + [ + "A2", + "B2", + "C2" + ], + [ + "A3", + "B3" + ], + [ + "A4", + "B4" + ] + ], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 67.75, + "z": 6.85 + }, + "A2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 67.75, + "z": 6.85 + }, + "A3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 60.25, + "z": 7.3 + }, + "A4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 60.25, + "z": 7.3 + }, + "B1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 42.75, + "z": 6.85 + }, + "B2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 42.75, + "z": 6.85 + }, + "B3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 25.25, + "z": 7.3 + }, + "B4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 25.25, + "z": 7.3 + }, + "C1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 17.75, + "z": 6.85 + }, + "C2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 17.75, + "z": 6.85 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "PartialTipMovementNotAllowedError [line 479]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with A1 nozzle partial configuration will result in collision with items in deck slot D3.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with A1 nozzle partial configuration will result in collision with items in deck slot D3.", + "errorCode": "2004", + "errorInfo": {}, + "errorType": "PartialTipMovementNotAllowedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south_east.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + } + }, + { + "definitionUri": "opentrons/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical/1", + "id": "UUID", + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "D3" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Too tall labware on pickup tip" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[68614da0b3][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_east].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[68614da0b3][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_east].json new file mode 100644 index 00000000000..7209e028a2b --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[68614da0b3][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_east].json @@ -0,0 +1,1501 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-1000ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 97.47 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_1000ul", + "tipLength": 88, + "tipOverlap": 7.95 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.24, + "z": 9.47 + }, + "A10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.24, + "z": 9.47 + }, + "A11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.24, + "z": 9.47 + }, + "A12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.24, + "z": 9.47 + }, + "A2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.24, + "z": 9.47 + }, + "A3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.24, + "z": 9.47 + }, + "A4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.24, + "z": 9.47 + }, + "A5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.24, + "z": 9.47 + }, + "A6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.24, + "z": 9.47 + }, + "A7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.24, + "z": 9.47 + }, + "A8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.24, + "z": 9.47 + }, + "A9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.24, + "z": 9.47 + }, + "B1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.24, + "z": 9.47 + }, + "B10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.24, + "z": 9.47 + }, + "B11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.24, + "z": 9.47 + }, + "B12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.24, + "z": 9.47 + }, + "B2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.24, + "z": 9.47 + }, + "B3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.24, + "z": 9.47 + }, + "B4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.24, + "z": 9.47 + }, + "B5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.24, + "z": 9.47 + }, + "B6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.24, + "z": 9.47 + }, + "B7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.24, + "z": 9.47 + }, + "B8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.24, + "z": 9.47 + }, + "B9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.24, + "z": 9.47 + }, + "C1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.24, + "z": 9.47 + }, + "C10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.24, + "z": 9.47 + }, + "C11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.24, + "z": 9.47 + }, + "C12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.24, + "z": 9.47 + }, + "C2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.24, + "z": 9.47 + }, + "C3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.24, + "z": 9.47 + }, + "C4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.24, + "z": 9.47 + }, + "C5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.24, + "z": 9.47 + }, + "C6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.24, + "z": 9.47 + }, + "C7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.24, + "z": 9.47 + }, + "C8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.24, + "z": 9.47 + }, + "C9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.24, + "z": 9.47 + }, + "D1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.24, + "z": 9.47 + }, + "D10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.24, + "z": 9.47 + }, + "D11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.24, + "z": 9.47 + }, + "D12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.24, + "z": 9.47 + }, + "D2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.24, + "z": 9.47 + }, + "D3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.24, + "z": 9.47 + }, + "D4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.24, + "z": 9.47 + }, + "D5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.24, + "z": 9.47 + }, + "D6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.24, + "z": 9.47 + }, + "D7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.24, + "z": 9.47 + }, + "D8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.24, + "z": 9.47 + }, + "D9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.24, + "z": 9.47 + }, + "E1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.24, + "z": 9.47 + }, + "E10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.24, + "z": 9.47 + }, + "E11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.24, + "z": 9.47 + }, + "E12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.24, + "z": 9.47 + }, + "E2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.24, + "z": 9.47 + }, + "E3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.24, + "z": 9.47 + }, + "E4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.24, + "z": 9.47 + }, + "E5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.24, + "z": 9.47 + }, + "E6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.24, + "z": 9.47 + }, + "E7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.24, + "z": 9.47 + }, + "E8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.24, + "z": 9.47 + }, + "E9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.24, + "z": 9.47 + }, + "F1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.24, + "z": 9.47 + }, + "F10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.24, + "z": 9.47 + }, + "F11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.24, + "z": 9.47 + }, + "F12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.24, + "z": 9.47 + }, + "F2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.24, + "z": 9.47 + }, + "F3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.24, + "z": 9.47 + }, + "F4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.24, + "z": 9.47 + }, + "F5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.24, + "z": 9.47 + }, + "F6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.24, + "z": 9.47 + }, + "F7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.24, + "z": 9.47 + }, + "F8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.24, + "z": 9.47 + }, + "F9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.24, + "z": 9.47 + }, + "G1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.24, + "z": 9.47 + }, + "G10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.24, + "z": 9.47 + }, + "G11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.24, + "z": 9.47 + }, + "G12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.24, + "z": 9.47 + }, + "G2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.24, + "z": 9.47 + }, + "G3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.24, + "z": 9.47 + }, + "G4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.24, + "z": 9.47 + }, + "G5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.24, + "z": 9.47 + }, + "G6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.24, + "z": 9.47 + }, + "G7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.24, + "z": 9.47 + }, + "G8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.24, + "z": 9.47 + }, + "G9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.24, + "z": 9.47 + }, + "H1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.24, + "z": 9.47 + }, + "H10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.24, + "z": 9.47 + }, + "H11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.24, + "z": 9.47 + }, + "H12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.24, + "z": 9.47 + }, + "H2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.24, + "z": 9.47 + }, + "H3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.24, + "z": 9.47 + }, + "H4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.24, + "z": 9.47 + }, + "H5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.24, + "z": 9.47 + }, + "H6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.24, + "z": 9.47 + }, + "H7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.24, + "z": 9.47 + }, + "H8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.24, + "z": 9.47 + }, + "H9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.24, + "z": 9.47 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "C3" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/tube-rack-set-1" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 124.35 + }, + "gripperOffsets": {}, + "groups": [ + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352095", + "352096", + "352097", + "352099", + "352196" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 6x15 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A2", + "B1", + "B2", + "C1", + "C2" + ] + }, + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352070", + "352098" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 4x50 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A3", + "A4", + "B3", + "B4" + ] + } + ], + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Opentrons 10 Tube Rack with Falcon 4x50 mL, 6x15 mL Conical", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1" + ], + [ + "A2", + "B2", + "C2" + ], + [ + "A3", + "B3" + ], + [ + "A4", + "B4" + ] + ], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 67.75, + "z": 6.85 + }, + "A2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 67.75, + "z": 6.85 + }, + "A3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 60.25, + "z": 7.3 + }, + "A4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 60.25, + "z": 7.3 + }, + "B1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 42.75, + "z": 6.85 + }, + "B2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 42.75, + "z": 6.85 + }, + "B3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 25.25, + "z": 7.3 + }, + "B4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 25.25, + "z": 7.3 + }, + "C1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 17.75, + "z": 6.85 + }, + "C2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 17.75, + "z": 6.85 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "PartialTipMovementNotAllowedError [line 479]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with A1 nozzle partial configuration will result in collision with items in deck slot C3.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with A1 nozzle partial configuration will result in collision with items in deck slot C3.", + "errorCode": "2004", + "errorInfo": {}, + "errorType": "PartialTipMovementNotAllowedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_east.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + } + }, + { + "definitionUri": "opentrons/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical/1", + "id": "UUID", + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "C3" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Too tall labware on pickup tip" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6ad5590adf][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_unit].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6ad5590adf][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_unit].json index e545da56bd4..e30b5bee0d8 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6ad5590adf][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_unit].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6ad5590adf][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_unit].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6c20d6c570][Flex_S_v2_20_96_None_COLUMN_HappyPath].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6c20d6c570][Flex_S_v2_20_96_None_COLUMN_HappyPath].json new file mode 100644 index 00000000000..3ac36a59ee5 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6c20d6c570][Flex_S_v2_20_96_None_COLUMN_HappyPath].json @@ -0,0 +1,6283 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "displayName": "Partial Tip Rack", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "COLUMN" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "displayName": "Liquid Transfer - Source Labware", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c910be62291bb94458a59cb4185c5180", + "notes": [], + "params": { + "displayName": "Liquid Transfer - Destination Labware", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C3" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2f59e0552969594ba6ab06f03af324e1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 277.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6b0053809cc5ea894449b20181bda1d6", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 178.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1c921a50baabd805b1ee8fa0e85c6682", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "75aa0b5116a9eba713b68a3850e26391", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "34f14498853a691a86c6e51955a923f5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "92f6d16db82fc47bbd830b87c04cf587", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A11" + }, + "result": { + "position": { + "x": 268.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "touchTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d4506d1b80c117247379fd1da0cdd540", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "radius": 1.0, + "speed": 60.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -1.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 187.38, + "y": 181.24, + "z": 14.7 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b6c0a6862c1e869c974d3bbf491b2f77", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "239bbe2e12ee55487024bc01a20c71b9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ccd85f84c2d5a9969bce2957cc8ea1f1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A10" + }, + "result": { + "position": { + "x": 259.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c603a57fd9bba99db45ecb759ed91041", + "notes": [], + "params": { + "axes": [ + "leftPlunger", + "leftZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "615a33e51b3fb8d9a7c25fc4e3f6a04d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3fcbc107a2ddb8aef2be961108add200", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e9ce054762d4edc21b2b4c74b3a264cb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A9" + }, + "result": { + "position": { + "x": 250.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a0f88d95e77f51e86818cb9e324a7006", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7ed7eaaa162b89d760f55d949352464b", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "23eb3478c9444fcf3001ecb326f15411", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "34c3afeaf56af250ec42af0751995669", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "45d2cde26215d9e311e7f9745228d93c", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f3e7a78d7da267bc877af3a8bc8d8de3", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b5b355ee01907f745c80f2350c8e3e31", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dc872d009459778cbbf93fd8edfa4ef6", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ac0822a34f4d47ffb4989a5f63bfab59", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4e4778958eeb399df7ed9910c6d52431", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3bd5afcf7db6fd3db5480254738264d9", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "331962de59fdeac6a9ecfc21dcda5cac", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "457dec90436799e13fcbb6ffdde3859f", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a835d08fcbabe71ad357dcdafa9632c4", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d6e6d0dcaa50989719d4265d3fb9aaaf", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d92475e5e072d6961fe38612564b2d31", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0129acc2b378f88a55ba77758eb3ed02", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0354a7d87886f9008260cf75347b2452", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "93e0f4ac6e51c80ff19bfe60dc97542b", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c3bedc4720be98d3ed4a3c1734854d6a", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "29bfc3397bb9f9430b9f2f59288a540c", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7d6c3bf51293b3fee38ca9a4e44f6cfd", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a7d6a6a0fddaf753a629565eb4d912a3", + "notes": [], + "params": { + "flowRate": 80.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "prepareToAspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1654457443c6f75e15e311f0543fc66f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9911036ea844c7a6ba90923698fc224e", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c3188c7feb06653501f6bf397a131dc8", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1943586caa4c85679a9ae04821c2d38a", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9579bd2d4199b9920413263739ef30ed", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4f2dfd5aa4da5e2a45a3f3b692227df5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e50910ece222b51bd908e743c6bf7804", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A12", + "style": "COLUMN" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5a00693acdb90f3631535748d93a8223", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 178.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b782827466e82f28c3969485a65f3fe2", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 214.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "32989d99a33a00680804e9fc9fc2804b", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 378.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ba2c5376c66ec84d18a9a8585e95d9d9", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "252f2c9c7e1a96f8a4c8cc7eeff8fbdf", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ec32821a6c616dea97d3d2b4f59e1cd4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 187.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "touchTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dc740b4cde605ccfc143bd6b2dc65f7b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "radius": 1.0, + "speed": 60.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -1.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 223.38, + "y": 181.24, + "z": 14.7 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d8894bf3d181e7ea041a675ae0eeb074", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3438f69dae7bf7c63c3caf25b1c0af6f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "36fce93206223fa6086fc3958b91de53", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0f96ea2f581e7da13b2920758720217c", + "notes": [], + "params": { + "axes": [ + "leftPlunger", + "leftZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "052f6060ce6e871a6a65936ec86cd64e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ba2b5012fff1efcc43be440738b96812", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "be37ed68d97eb27cdaa9f28b458c84cb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 205.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c484812021d17a80f73bf5376416ff93", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "abeb19b3de05703b3e0ec8b69735094c", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cf5afad6cb0e55ff82bf41cbb61278f9", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "abc3039f932b9588a796a3d2844da6bc", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8231c70a38e5e9514386c43272b24b3b", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a9b22d17829464f4fc6f8bda7be4e82b", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d2d5e3037bdb3057f4ac220c39d9968e", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bbd96dd68d79179af153ef884b3430da", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8c5b08c533f9c792b534a3231d054fa3", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5f47db85c4a26fad793ff0bd71056e8f", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f8c9b70248e4087475baa0ea41b7a8f2", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a258c594b3064208d6b6a8c304dbe002", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9fc7b604becb670c1c2bcc718b191072", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "feaf07c0bf3861b566e4ab6bc54bc632", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4b61daf27e7e1a5d020fc247827adafe", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cd870b78d981e980bd5ca0c179487646", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bff9cf8246b58f1bdbbb16ca3dc12d80", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3d02ddc2c5fa9cfe85e9b0459595940e", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c8b8e1fb79a8c8c22ea041fe1d30fe06", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c960ea23d531567b5131f108b07f280d", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3a0eadb753166ea07bd95887ffca1ef1", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5fe73007151bae4924c6c6c55ef663b0", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "88544514bc37e481edb0e05e76e088bb", + "notes": [], + "params": { + "flowRate": 80.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "prepareToAspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2228ebb5354fb6841543953901ad0caf", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "25e7036b1ceb4c316002632bcb52134b", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a261b02a0248d089146924e44691411b", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "19a48a90466241d64935c5a55b23fbea", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "263dd8836ac5c6cff37fff2ef09bf09e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "33d098affbd40500bc8579d4995dad56", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [], + "files": [ + { + "name": "Flex_S_v2_20_96_None_COLUMN_HappyPath.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "displayName": "Partial Tip Rack", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "displayName": "Liquid Transfer - Source Labware", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "displayName": "Liquid Transfer - Destination Labware", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C3" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "96 channel pipette and a COLUMN partial tip configuration.", + "protocolName": "96Channel COLUMN Happy Path" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6cee20a957][OT2_S_v2_12_NO_PIPETTES_Python310SyntaxRobotAnalysisOnlyError].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6cee20a957][OT2_S_v2_12_NO_PIPETTES_Python310SyntaxRobotAnalysisOnlyError].json index 8f88134625a..da1993d6e56 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6cee20a957][OT2_S_v2_12_NO_PIPETTES_Python310SyntaxRobotAnalysisOnlyError].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6cee20a957][OT2_S_v2_12_NO_PIPETTES_Python310SyntaxRobotAnalysisOnlyError].json @@ -94,6 +94,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.12", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e2f6f10c5][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_destination_collision].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e2f6f10c5][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_destination_collision].json new file mode 100644 index 00000000000..0e079e7daa2 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e2f6f10c5][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_destination_collision].json @@ -0,0 +1,3966 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-1000ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 97.47 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_1000ul", + "tipLength": 88, + "tipOverlap": 7.95 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.24, + "z": 9.47 + }, + "A10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.24, + "z": 9.47 + }, + "A11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.24, + "z": 9.47 + }, + "A12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.24, + "z": 9.47 + }, + "A2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.24, + "z": 9.47 + }, + "A3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.24, + "z": 9.47 + }, + "A4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.24, + "z": 9.47 + }, + "A5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.24, + "z": 9.47 + }, + "A6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.24, + "z": 9.47 + }, + "A7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.24, + "z": 9.47 + }, + "A8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.24, + "z": 9.47 + }, + "A9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.24, + "z": 9.47 + }, + "B1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.24, + "z": 9.47 + }, + "B10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.24, + "z": 9.47 + }, + "B11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.24, + "z": 9.47 + }, + "B12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.24, + "z": 9.47 + }, + "B2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.24, + "z": 9.47 + }, + "B3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.24, + "z": 9.47 + }, + "B4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.24, + "z": 9.47 + }, + "B5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.24, + "z": 9.47 + }, + "B6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.24, + "z": 9.47 + }, + "B7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.24, + "z": 9.47 + }, + "B8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.24, + "z": 9.47 + }, + "B9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.24, + "z": 9.47 + }, + "C1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.24, + "z": 9.47 + }, + "C10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.24, + "z": 9.47 + }, + "C11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.24, + "z": 9.47 + }, + "C12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.24, + "z": 9.47 + }, + "C2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.24, + "z": 9.47 + }, + "C3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.24, + "z": 9.47 + }, + "C4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.24, + "z": 9.47 + }, + "C5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.24, + "z": 9.47 + }, + "C6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.24, + "z": 9.47 + }, + "C7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.24, + "z": 9.47 + }, + "C8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.24, + "z": 9.47 + }, + "C9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.24, + "z": 9.47 + }, + "D1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.24, + "z": 9.47 + }, + "D10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.24, + "z": 9.47 + }, + "D11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.24, + "z": 9.47 + }, + "D12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.24, + "z": 9.47 + }, + "D2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.24, + "z": 9.47 + }, + "D3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.24, + "z": 9.47 + }, + "D4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.24, + "z": 9.47 + }, + "D5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.24, + "z": 9.47 + }, + "D6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.24, + "z": 9.47 + }, + "D7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.24, + "z": 9.47 + }, + "D8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.24, + "z": 9.47 + }, + "D9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.24, + "z": 9.47 + }, + "E1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.24, + "z": 9.47 + }, + "E10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.24, + "z": 9.47 + }, + "E11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.24, + "z": 9.47 + }, + "E12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.24, + "z": 9.47 + }, + "E2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.24, + "z": 9.47 + }, + "E3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.24, + "z": 9.47 + }, + "E4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.24, + "z": 9.47 + }, + "E5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.24, + "z": 9.47 + }, + "E6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.24, + "z": 9.47 + }, + "E7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.24, + "z": 9.47 + }, + "E8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.24, + "z": 9.47 + }, + "E9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.24, + "z": 9.47 + }, + "F1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.24, + "z": 9.47 + }, + "F10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.24, + "z": 9.47 + }, + "F11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.24, + "z": 9.47 + }, + "F12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.24, + "z": 9.47 + }, + "F2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.24, + "z": 9.47 + }, + "F3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.24, + "z": 9.47 + }, + "F4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.24, + "z": 9.47 + }, + "F5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.24, + "z": 9.47 + }, + "F6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.24, + "z": 9.47 + }, + "F7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.24, + "z": 9.47 + }, + "F8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.24, + "z": 9.47 + }, + "F9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.24, + "z": 9.47 + }, + "G1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.24, + "z": 9.47 + }, + "G10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.24, + "z": 9.47 + }, + "G11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.24, + "z": 9.47 + }, + "G12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.24, + "z": 9.47 + }, + "G2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.24, + "z": 9.47 + }, + "G3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.24, + "z": 9.47 + }, + "G4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.24, + "z": 9.47 + }, + "G5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.24, + "z": 9.47 + }, + "G6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.24, + "z": 9.47 + }, + "G7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.24, + "z": 9.47 + }, + "G8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.24, + "z": 9.47 + }, + "G9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.24, + "z": 9.47 + }, + "H1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.24, + "z": 9.47 + }, + "H10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.24, + "z": 9.47 + }, + "H11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.24, + "z": 9.47 + }, + "H12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.24, + "z": 9.47 + }, + "H2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.24, + "z": 9.47 + }, + "H3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.24, + "z": 9.47 + }, + "H4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.24, + "z": 9.47 + }, + "H5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.24, + "z": 9.47 + }, + "H6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.24, + "z": 9.47 + }, + "H7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.24, + "z": 9.47 + }, + "H8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.24, + "z": 9.47 + }, + "H9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.24, + "z": 9.47 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "C3" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/tube-rack-set-1" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 124.35 + }, + "gripperOffsets": {}, + "groups": [ + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352095", + "352096", + "352097", + "352099", + "352196" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 6x15 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A2", + "B1", + "B2", + "C1", + "C2" + ] + }, + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352070", + "352098" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 4x50 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A3", + "A4", + "B3", + "B4" + ] + } + ], + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Opentrons 10 Tube Rack with Falcon 4x50 mL, 6x15 mL Conical", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1" + ], + [ + "A2", + "B2", + "C2" + ], + [ + "A3", + "B3" + ], + [ + "A4", + "B4" + ] + ], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 67.75, + "z": 6.85 + }, + "A2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 67.75, + "z": 6.85 + }, + "A3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 60.25, + "z": 7.3 + }, + "A4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 60.25, + "z": 7.3 + }, + "B1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 42.75, + "z": 6.85 + }, + "B2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 42.75, + "z": 6.85 + }, + "B3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 25.25, + "z": 7.3 + }, + "B4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 25.25, + "z": 7.3 + }, + "C1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 17.75, + "z": 6.85 + }, + "C2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 17.75, + "z": 6.85 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c910be62291bb94458a59cb4185c5180", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d75e38152238e9ad0c18e291fe97e483", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D3" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8de5f9595204f05bd1016cce23cd32b9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 178.38, + "y": 288.24, + "z": 97.47 + }, + "tipDiameter": 7.23, + "tipLength": 77.5, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d770b3dd007307016fd235b6e50d1e5d", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 178.38, + "y": 74.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1b1e9e653601e5ff154ade4fc2221e84", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 178.38, + "y": 74.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "PartialTipMovementNotAllowedError [line 494]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to NEST 96 Well Plate 100 µL PCR Full Skirt in slot D3 with H12 nozzle partial configuration will result in collision with items in deck slot C3.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Moving to NEST 96 Well Plate 100 µL PCR Full Skirt in slot D3 with H12 nozzle partial configuration will result in collision with items in deck slot C3.", + "errorCode": "2004", + "errorInfo": {}, + "errorType": "PartialTipMovementNotAllowedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_destination_collision.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "B2" + } + }, + { + "definitionUri": "opentrons/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical/1", + "id": "UUID", + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "C3" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D3" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Too tall labware on pickup tip" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e34343cfc][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e34343cfc][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction].json index 38e39b9a965..eba57a84196 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e34343cfc][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e34343cfc][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TM_MagMaxRNAExtraction].json @@ -13439,7 +13439,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13472,7 +13473,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13505,7 +13507,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13538,7 +13541,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13571,7 +13575,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13604,7 +13609,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13637,7 +13643,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13670,7 +13677,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13703,7 +13711,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13736,7 +13745,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13769,7 +13779,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13802,7 +13813,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13835,7 +13847,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13868,7 +13881,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13901,7 +13915,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13934,7 +13949,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13967,7 +13983,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14000,7 +14017,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14033,7 +14051,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14066,7 +14085,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14099,7 +14119,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14132,7 +14153,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14165,7 +14187,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14198,7 +14221,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14231,7 +14255,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14264,7 +14289,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14297,7 +14323,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14330,7 +14357,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14363,7 +14391,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14396,7 +14425,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14429,7 +14459,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14462,7 +14493,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14495,7 +14527,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14528,7 +14561,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14561,7 +14595,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14594,7 +14629,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14627,7 +14663,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14660,7 +14697,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14693,7 +14731,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14726,7 +14765,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14759,7 +14799,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14792,7 +14833,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14825,7 +14867,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14858,7 +14901,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14891,7 +14935,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14924,7 +14969,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14957,7 +15003,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14990,7 +15037,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15023,7 +15071,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15056,7 +15105,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15089,7 +15139,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15122,7 +15173,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15155,7 +15207,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15188,7 +15241,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15221,7 +15275,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15254,7 +15309,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15287,7 +15343,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15320,7 +15377,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15353,7 +15411,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15386,7 +15445,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15419,7 +15479,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15452,7 +15513,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15485,7 +15547,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15518,7 +15581,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15551,7 +15615,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15584,7 +15649,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15617,7 +15683,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15650,7 +15717,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15683,7 +15751,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15716,7 +15785,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15749,7 +15819,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15782,7 +15853,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15815,7 +15887,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15848,7 +15921,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15881,7 +15955,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15914,7 +15989,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15947,7 +16023,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15980,7 +16057,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16013,7 +16091,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16046,7 +16125,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16079,7 +16159,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16112,7 +16193,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16145,7 +16227,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16178,7 +16261,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16321,7 +16405,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16354,7 +16439,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16387,7 +16473,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16420,7 +16507,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16453,7 +16541,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16486,7 +16575,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16519,7 +16609,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16551,7 +16642,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16583,7 +16675,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16616,7 +16709,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16649,7 +16743,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16682,7 +16777,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16715,7 +16811,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16748,7 +16845,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16781,7 +16879,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16814,7 +16913,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16847,7 +16947,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16880,7 +16981,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16913,7 +17015,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16946,7 +17049,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16979,7 +17083,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17012,7 +17117,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17045,7 +17151,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17078,7 +17185,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17111,7 +17219,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17144,7 +17253,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17177,7 +17287,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17210,7 +17321,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17243,7 +17355,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17276,7 +17389,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17309,7 +17423,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17342,7 +17457,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17375,7 +17491,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17408,7 +17525,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17441,7 +17559,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17474,7 +17593,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17507,7 +17627,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17540,7 +17661,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17573,7 +17695,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17606,7 +17729,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17639,7 +17763,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17672,7 +17797,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17705,7 +17831,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17738,7 +17865,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17771,7 +17899,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17804,7 +17933,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17837,7 +17967,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17870,7 +18001,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17903,7 +18035,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17936,7 +18069,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17969,7 +18103,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18002,7 +18137,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18034,7 +18170,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18065,7 +18202,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18097,7 +18235,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18472,7 +18611,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18505,7 +18645,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18660,7 +18801,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18693,7 +18835,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18725,7 +18868,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18756,7 +18900,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18788,7 +18933,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19049,7 +19195,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19082,7 +19229,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19237,7 +19385,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19270,7 +19419,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19302,7 +19452,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19333,7 +19484,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19365,7 +19517,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19626,7 +19779,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19659,7 +19813,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19814,7 +19969,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19847,7 +20003,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19880,7 +20037,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19913,7 +20071,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19946,7 +20105,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19979,7 +20139,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20012,7 +20173,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20045,7 +20207,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20078,7 +20241,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20111,7 +20275,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20144,7 +20309,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20177,7 +20343,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20210,7 +20377,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20243,7 +20411,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20276,7 +20445,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20309,7 +20479,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20342,7 +20513,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20375,7 +20547,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20408,7 +20581,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20441,7 +20615,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20474,7 +20649,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20507,7 +20683,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20540,7 +20717,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20573,7 +20751,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20606,7 +20785,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20639,7 +20819,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20672,7 +20853,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20705,7 +20887,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20738,7 +20921,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20771,7 +20955,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20804,7 +20989,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20837,7 +21023,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20870,7 +21057,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20903,7 +21091,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20936,7 +21125,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20969,7 +21159,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21002,7 +21193,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21035,7 +21227,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21068,7 +21261,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21101,7 +21295,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21134,7 +21329,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21167,7 +21363,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21200,7 +21397,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21233,7 +21431,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21266,7 +21465,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21299,7 +21499,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21332,7 +21533,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21365,7 +21567,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21398,7 +21601,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21431,7 +21635,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21464,7 +21669,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21497,7 +21703,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21530,7 +21737,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21563,7 +21771,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21596,7 +21805,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21629,7 +21839,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21662,7 +21873,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21695,7 +21907,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21728,7 +21941,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21761,7 +21975,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21794,7 +22009,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21827,7 +22043,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21860,7 +22077,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21893,7 +22111,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21926,7 +22145,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21959,7 +22179,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21992,7 +22213,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22025,7 +22247,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22168,7 +22391,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22201,7 +22425,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22234,7 +22459,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22267,7 +22493,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22300,7 +22527,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22333,7 +22561,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22366,7 +22595,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22399,7 +22629,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22432,7 +22663,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22465,7 +22697,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22498,7 +22731,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22531,7 +22765,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22564,7 +22799,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22597,7 +22833,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22630,7 +22867,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22663,7 +22901,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22696,7 +22935,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22729,7 +22969,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22762,7 +23003,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22795,7 +23037,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22828,7 +23071,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22861,7 +23105,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22894,7 +23139,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22927,7 +23173,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22960,7 +23207,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22993,7 +23241,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23026,7 +23275,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23059,7 +23309,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23092,7 +23343,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23125,7 +23377,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23158,7 +23411,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23191,7 +23445,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23224,7 +23479,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23257,7 +23513,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23290,7 +23547,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23323,7 +23581,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23355,7 +23614,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23386,7 +23646,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23418,7 +23679,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23679,7 +23941,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23712,7 +23975,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23867,7 +24131,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23900,7 +24165,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23932,7 +24198,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23963,7 +24230,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23995,7 +24263,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24256,7 +24525,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24289,7 +24559,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24444,7 +24715,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24477,7 +24749,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24509,7 +24782,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24540,7 +24814,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24572,7 +24847,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24833,7 +25109,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24866,7 +25143,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25082,7 +25360,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25115,7 +25394,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25148,7 +25428,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25181,7 +25462,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25214,7 +25496,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25247,7 +25530,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25280,7 +25564,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25313,7 +25598,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25346,7 +25632,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25379,7 +25666,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25412,7 +25700,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25445,7 +25734,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25478,7 +25768,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25511,7 +25802,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25544,7 +25836,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25577,7 +25870,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25610,7 +25904,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25643,7 +25938,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25676,7 +25972,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25709,7 +26006,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25742,7 +26040,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25775,7 +26074,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25808,7 +26108,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25841,7 +26142,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25874,7 +26176,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25907,7 +26210,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25940,7 +26244,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25973,7 +26278,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26006,7 +26312,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26039,7 +26346,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26072,7 +26380,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26105,7 +26414,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26138,7 +26448,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26171,7 +26482,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26204,7 +26516,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26237,7 +26550,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26514,7 +26828,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26547,7 +26862,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26730,7 +27046,8 @@ "y": 0.0, "z": -20.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26763,7 +27080,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26795,7 +27113,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26827,7 +27146,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26860,7 +27180,8 @@ "y": 0.0, "z": -25.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26893,7 +27214,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26925,7 +27247,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26957,7 +27280,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26990,7 +27314,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27023,7 +27348,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27055,7 +27381,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27087,7 +27414,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27120,7 +27448,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27153,7 +27482,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27185,7 +27515,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27217,7 +27548,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27250,7 +27582,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27283,7 +27616,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27315,7 +27649,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27347,7 +27682,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27380,7 +27716,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27413,7 +27750,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27445,7 +27783,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27477,7 +27816,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27574,7 +27914,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27607,7 +27948,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27640,7 +27982,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27673,7 +28016,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27706,7 +28050,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27739,7 +28084,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27772,7 +28118,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27805,7 +28152,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27838,7 +28186,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27871,7 +28220,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27904,7 +28254,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27937,7 +28288,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27970,7 +28322,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28003,7 +28356,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28036,7 +28390,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28069,7 +28424,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28102,7 +28458,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28135,7 +28492,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28168,7 +28526,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28201,7 +28560,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28234,7 +28594,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28267,7 +28628,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28300,7 +28662,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28333,7 +28696,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28366,7 +28730,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28399,7 +28764,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28432,7 +28798,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28465,7 +28832,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28498,7 +28866,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28531,7 +28900,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28564,7 +28934,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28597,7 +28968,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28630,7 +29002,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28663,7 +29036,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28696,7 +29070,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28729,7 +29104,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28762,7 +29138,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28795,7 +29172,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28828,7 +29206,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28861,7 +29240,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28894,7 +29274,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28927,7 +29308,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28960,7 +29342,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28993,7 +29376,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29026,7 +29410,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29059,7 +29444,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29092,7 +29478,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29125,7 +29512,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29158,7 +29546,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29191,7 +29580,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29224,7 +29614,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29257,7 +29648,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29290,7 +29682,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29323,7 +29716,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29356,7 +29750,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29389,7 +29784,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29422,7 +29818,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29455,7 +29852,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29488,7 +29886,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29521,7 +29920,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29554,7 +29954,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29587,7 +29988,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29620,7 +30022,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29653,7 +30056,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29686,7 +30090,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29719,7 +30124,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29752,7 +30158,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29785,7 +30192,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29818,7 +30226,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29851,7 +30260,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29884,7 +30294,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29917,7 +30328,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29950,7 +30362,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29983,7 +30396,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30016,7 +30430,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30049,7 +30464,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30082,7 +30498,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30115,7 +30532,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30148,7 +30566,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30181,7 +30600,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30214,7 +30634,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30247,7 +30668,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30280,7 +30702,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30313,7 +30736,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30456,7 +30880,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30489,7 +30914,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30522,7 +30948,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30555,7 +30982,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30588,7 +31016,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30621,7 +31050,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30654,7 +31084,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30686,7 +31117,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30718,7 +31150,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30751,7 +31184,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30784,7 +31218,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30817,7 +31252,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30850,7 +31286,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30883,7 +31320,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30916,7 +31354,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30949,7 +31388,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30982,7 +31422,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31015,7 +31456,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31048,7 +31490,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31081,7 +31524,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31114,7 +31558,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31147,7 +31592,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31180,7 +31626,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31213,7 +31660,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31246,7 +31694,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31279,7 +31728,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31312,7 +31762,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31345,7 +31796,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31378,7 +31830,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31411,7 +31864,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31444,7 +31898,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31477,7 +31932,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31510,7 +31966,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31543,7 +32000,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31576,7 +32034,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31609,7 +32068,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31642,7 +32102,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31675,7 +32136,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31708,7 +32170,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31741,7 +32204,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31774,7 +32238,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31807,7 +32272,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31840,7 +32306,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31873,7 +32340,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31906,7 +32374,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31939,7 +32408,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31972,7 +32442,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32005,7 +32476,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32038,7 +32510,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32071,7 +32544,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32104,7 +32578,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32137,7 +32612,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32169,7 +32645,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32200,7 +32677,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32232,7 +32710,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32607,7 +33086,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32640,7 +33120,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32795,7 +33276,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32828,7 +33310,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32860,7 +33343,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32891,7 +33375,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32923,7 +33408,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33184,7 +33670,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33217,7 +33704,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33372,7 +33860,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33405,7 +33894,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33437,7 +33927,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33468,7 +33959,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33500,7 +33992,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33761,7 +34254,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33794,7 +34288,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33949,7 +34444,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33982,7 +34478,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34015,7 +34512,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34048,7 +34546,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34081,7 +34580,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34114,7 +34614,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34147,7 +34648,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34180,7 +34682,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34213,7 +34716,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34246,7 +34750,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34279,7 +34784,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34312,7 +34818,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34345,7 +34852,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34378,7 +34886,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34411,7 +34920,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34444,7 +34954,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34477,7 +34988,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34510,7 +35022,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34543,7 +35056,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34576,7 +35090,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34609,7 +35124,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34642,7 +35158,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34675,7 +35192,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34708,7 +35226,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34741,7 +35260,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34774,7 +35294,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34807,7 +35328,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34840,7 +35362,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34873,7 +35396,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34906,7 +35430,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34939,7 +35464,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34972,7 +35498,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35005,7 +35532,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35038,7 +35566,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35071,7 +35600,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35104,7 +35634,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35137,7 +35668,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35170,7 +35702,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35203,7 +35736,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35236,7 +35770,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35269,7 +35804,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35302,7 +35838,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35335,7 +35872,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35368,7 +35906,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35401,7 +35940,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35434,7 +35974,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35467,7 +36008,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35500,7 +36042,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35533,7 +36076,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35566,7 +36110,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35599,7 +36144,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35632,7 +36178,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35665,7 +36212,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35698,7 +36246,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35731,7 +36280,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35764,7 +36314,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35797,7 +36348,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35830,7 +36382,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35863,7 +36416,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35896,7 +36450,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35929,7 +36484,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35962,7 +36518,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35995,7 +36552,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36028,7 +36586,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36061,7 +36620,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36094,7 +36654,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36127,7 +36688,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36160,7 +36722,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36303,7 +36866,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36336,7 +36900,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36369,7 +36934,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36402,7 +36968,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36435,7 +37002,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36468,7 +37036,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36501,7 +37070,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36534,7 +37104,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36567,7 +37138,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36600,7 +37172,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36633,7 +37206,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36666,7 +37240,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36699,7 +37274,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36732,7 +37308,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36765,7 +37342,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36798,7 +37376,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36831,7 +37410,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36864,7 +37444,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36897,7 +37478,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36930,7 +37512,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36963,7 +37546,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36996,7 +37580,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37029,7 +37614,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37062,7 +37648,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37095,7 +37682,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37128,7 +37716,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37161,7 +37750,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37194,7 +37784,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37227,7 +37818,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37260,7 +37852,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37293,7 +37886,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37326,7 +37920,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37359,7 +37954,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37392,7 +37988,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37425,7 +38022,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37458,7 +38056,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37490,7 +38089,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37521,7 +38121,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37553,7 +38154,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37814,7 +38416,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37847,7 +38450,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38002,7 +38606,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38035,7 +38640,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38067,7 +38673,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38098,7 +38705,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38130,7 +38738,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38391,7 +39000,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38424,7 +39034,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38579,7 +39190,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38612,7 +39224,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38644,7 +39257,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38675,7 +39289,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38707,7 +39322,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38968,7 +39584,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39001,7 +39618,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39217,7 +39835,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39250,7 +39869,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39283,7 +39903,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39316,7 +39937,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39349,7 +39971,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39382,7 +40005,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39415,7 +40039,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39448,7 +40073,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39481,7 +40107,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39514,7 +40141,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39547,7 +40175,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39580,7 +40209,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39613,7 +40243,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39646,7 +40277,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39679,7 +40311,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39712,7 +40345,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39745,7 +40379,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39778,7 +40413,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39811,7 +40447,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39844,7 +40481,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39877,7 +40515,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39910,7 +40549,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39943,7 +40583,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39976,7 +40617,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40009,7 +40651,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40042,7 +40685,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40075,7 +40719,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40108,7 +40753,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40141,7 +40787,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40174,7 +40821,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40207,7 +40855,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40240,7 +40889,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40273,7 +40923,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40306,7 +40957,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40339,7 +40991,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40372,7 +41025,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40649,7 +41303,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40682,7 +41337,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40865,7 +41521,8 @@ "y": 0.0, "z": -20.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40898,7 +41555,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40930,7 +41588,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40962,7 +41621,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40995,7 +41655,8 @@ "y": 0.0, "z": -25.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41028,7 +41689,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41060,7 +41722,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41092,7 +41755,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41125,7 +41789,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41158,7 +41823,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41190,7 +41856,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41222,7 +41889,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41255,7 +41923,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41288,7 +41957,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41320,7 +41990,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41352,7 +42023,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41385,7 +42057,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41418,7 +42091,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41450,7 +42124,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41482,7 +42157,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41515,7 +42191,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41548,7 +42225,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41580,7 +42258,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41612,7 +42291,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41709,7 +42389,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41742,7 +42423,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41775,7 +42457,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41808,7 +42491,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41841,7 +42525,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41874,7 +42559,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41907,7 +42593,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41940,7 +42627,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41973,7 +42661,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42006,7 +42695,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42039,7 +42729,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42072,7 +42763,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42105,7 +42797,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42138,7 +42831,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42171,7 +42865,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42204,7 +42899,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42237,7 +42933,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42270,7 +42967,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42303,7 +43001,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42336,7 +43035,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42369,7 +43069,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42402,7 +43103,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42435,7 +43137,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42468,7 +43171,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42501,7 +43205,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42534,7 +43239,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42567,7 +43273,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42600,7 +43307,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42633,7 +43341,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42666,7 +43375,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42699,7 +43409,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42732,7 +43443,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42765,7 +43477,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42798,7 +43511,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42831,7 +43545,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42864,7 +43579,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42897,7 +43613,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42930,7 +43647,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42963,7 +43681,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42996,7 +43715,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43029,7 +43749,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43062,7 +43783,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43095,7 +43817,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43128,7 +43851,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43161,7 +43885,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43194,7 +43919,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43227,7 +43953,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43260,7 +43987,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43293,7 +44021,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43326,7 +44055,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43359,7 +44089,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43392,7 +44123,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43425,7 +44157,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43458,7 +44191,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43491,7 +44225,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43524,7 +44259,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43557,7 +44293,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43590,7 +44327,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43623,7 +44361,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43656,7 +44395,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43689,7 +44429,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43722,7 +44463,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43755,7 +44497,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43788,7 +44531,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43821,7 +44565,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43854,7 +44599,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43887,7 +44633,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43920,7 +44667,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43953,7 +44701,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43986,7 +44735,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44019,7 +44769,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44052,7 +44803,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44085,7 +44837,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44118,7 +44871,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44151,7 +44905,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44184,7 +44939,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44217,7 +44973,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44250,7 +45007,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44283,7 +45041,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44316,7 +45075,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44349,7 +45109,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44382,7 +45143,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44415,7 +45177,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44448,7 +45211,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44591,7 +45355,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44624,7 +45389,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44657,7 +45423,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44690,7 +45457,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44723,7 +45491,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44756,7 +45525,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44789,7 +45559,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44821,7 +45592,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44853,7 +45625,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44886,7 +45659,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44919,7 +45693,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44952,7 +45727,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44985,7 +45761,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45018,7 +45795,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45051,7 +45829,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45084,7 +45863,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45117,7 +45897,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45150,7 +45931,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45183,7 +45965,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45216,7 +45999,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45249,7 +46033,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45282,7 +46067,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45315,7 +46101,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45348,7 +46135,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45381,7 +46169,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45414,7 +46203,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45447,7 +46237,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45480,7 +46271,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45513,7 +46305,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45546,7 +46339,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45579,7 +46373,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45612,7 +46407,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45645,7 +46441,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45678,7 +46475,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45711,7 +46509,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45744,7 +46543,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45777,7 +46577,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45810,7 +46611,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45843,7 +46645,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45876,7 +46679,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45909,7 +46713,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45942,7 +46747,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45975,7 +46781,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46008,7 +46815,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46041,7 +46849,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46074,7 +46883,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46107,7 +46917,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46140,7 +46951,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46173,7 +46985,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46206,7 +47019,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46239,7 +47053,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46272,7 +47087,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46304,7 +47120,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46335,7 +47152,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46367,7 +47185,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46742,7 +47561,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46775,7 +47595,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46930,7 +47751,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46963,7 +47785,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46995,7 +47818,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47026,7 +47850,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47058,7 +47883,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47319,7 +48145,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47352,7 +48179,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47507,7 +48335,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47540,7 +48369,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47572,7 +48402,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47603,7 +48434,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47635,7 +48467,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47896,7 +48729,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47929,7 +48763,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48084,7 +48919,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48117,7 +48953,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48150,7 +48987,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48183,7 +49021,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48216,7 +49055,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48249,7 +49089,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48282,7 +49123,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48315,7 +49157,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48348,7 +49191,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48381,7 +49225,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48414,7 +49259,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48447,7 +49293,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48480,7 +49327,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48513,7 +49361,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48546,7 +49395,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48579,7 +49429,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48612,7 +49463,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48645,7 +49497,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48678,7 +49531,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48711,7 +49565,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48744,7 +49599,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48777,7 +49633,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48810,7 +49667,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48843,7 +49701,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48876,7 +49735,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48909,7 +49769,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48942,7 +49803,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48975,7 +49837,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49008,7 +49871,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49041,7 +49905,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49074,7 +49939,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49107,7 +49973,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49140,7 +50007,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49173,7 +50041,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49206,7 +50075,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49239,7 +50109,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49272,7 +50143,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49305,7 +50177,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49338,7 +50211,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49371,7 +50245,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49404,7 +50279,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49437,7 +50313,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49470,7 +50347,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49503,7 +50381,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49536,7 +50415,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49569,7 +50449,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49602,7 +50483,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49635,7 +50517,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49668,7 +50551,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49701,7 +50585,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49734,7 +50619,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49767,7 +50653,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49800,7 +50687,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49833,7 +50721,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49866,7 +50755,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49899,7 +50789,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49932,7 +50823,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49965,7 +50857,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49998,7 +50891,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50031,7 +50925,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50064,7 +50959,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50097,7 +50993,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50130,7 +51027,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50163,7 +51061,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50196,7 +51095,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50229,7 +51129,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50262,7 +51163,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50295,7 +51197,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50438,7 +51341,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50471,7 +51375,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50504,7 +51409,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50537,7 +51443,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50570,7 +51477,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50603,7 +51511,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50636,7 +51545,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50669,7 +51579,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50702,7 +51613,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50735,7 +51647,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50768,7 +51681,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50801,7 +51715,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50834,7 +51749,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50867,7 +51783,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50900,7 +51817,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50933,7 +51851,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50966,7 +51885,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50999,7 +51919,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51032,7 +51953,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51065,7 +51987,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51098,7 +52021,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51131,7 +52055,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51164,7 +52089,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51197,7 +52123,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51230,7 +52157,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51263,7 +52191,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51296,7 +52225,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51329,7 +52259,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51362,7 +52293,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51395,7 +52327,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51428,7 +52361,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51461,7 +52395,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51494,7 +52429,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51527,7 +52463,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51560,7 +52497,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51593,7 +52531,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51625,7 +52564,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51656,7 +52596,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51688,7 +52629,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51949,7 +52891,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51982,7 +52925,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52137,7 +53081,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52170,7 +53115,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52202,7 +53148,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52233,7 +53180,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52265,7 +53213,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52526,7 +53475,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52559,7 +53509,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52714,7 +53665,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52747,7 +53699,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52779,7 +53732,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52810,7 +53764,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52842,7 +53797,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53103,7 +54059,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53136,7 +54093,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53352,7 +54310,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53385,7 +54344,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53418,7 +54378,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53451,7 +54412,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53484,7 +54446,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53517,7 +54480,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53550,7 +54514,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53583,7 +54548,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53616,7 +54582,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53649,7 +54616,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53682,7 +54650,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53715,7 +54684,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53748,7 +54718,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53781,7 +54752,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53814,7 +54786,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53847,7 +54820,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53880,7 +54854,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53913,7 +54888,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53946,7 +54922,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53979,7 +54956,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54012,7 +54990,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54045,7 +55024,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54078,7 +55058,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54111,7 +55092,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54144,7 +55126,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54177,7 +55160,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54210,7 +55194,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54243,7 +55228,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54276,7 +55262,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54309,7 +55296,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54342,7 +55330,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54375,7 +55364,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54408,7 +55398,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54441,7 +55432,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54474,7 +55466,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54507,7 +55500,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54784,7 +55778,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54817,7 +55812,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55000,7 +55996,8 @@ "y": 0.0, "z": -20.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55033,7 +56030,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55065,7 +56063,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55097,7 +56096,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55130,7 +56130,8 @@ "y": 0.0, "z": -25.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55163,7 +56164,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55195,7 +56197,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55227,7 +56230,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55260,7 +56264,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55293,7 +56298,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55325,7 +56331,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55357,7 +56364,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55390,7 +56398,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55423,7 +56432,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55455,7 +56465,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55487,7 +56498,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55520,7 +56532,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55553,7 +56566,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55585,7 +56599,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55617,7 +56632,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55650,7 +56666,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55683,7 +56700,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55715,7 +56733,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55747,7 +56766,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55915,6 +56935,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons Engineering ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e5128f107][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e5128f107][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1].json index 63567ca7c96..f052823d867 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e5128f107][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e5128f107][OT2_X_v2_16_None_None_HS_HeaterShakerConflictWithTrashBin1].json @@ -512,6 +512,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Heater-shaker conflict OT-2" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e744cbb48][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_str_default_no_matching_choices].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e744cbb48][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_str_default_no_matching_choices].json index cae3345ff13..2b5614762ba 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e744cbb48][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_str_default_no_matching_choices].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6e744cbb48][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_str_default_no_matching_choices].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "default choice does not match a choice" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f246e1cd8][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAEnrichmentV4].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f246e1cd8][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAEnrichmentV4].json index 503beb744bb..fd1c3550795 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f246e1cd8][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAEnrichmentV4].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f246e1cd8][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAEnrichmentV4].json @@ -12120,7 +12120,8 @@ "y": 0.0, "z": -14.28 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12152,7 +12153,8 @@ "y": 0.0, "z": -14.28 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12185,7 +12187,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12281,7 +12284,8 @@ "y": 0.0, "z": -14.28 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12313,7 +12317,8 @@ "y": 0.0, "z": -14.28 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12346,7 +12351,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12442,7 +12448,8 @@ "y": 0.0, "z": -14.28 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12474,7 +12481,8 @@ "y": 0.0, "z": -14.28 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12507,7 +12515,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12632,7 +12641,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12666,7 +12676,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12699,7 +12710,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12733,7 +12745,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12766,7 +12779,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12799,7 +12813,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12832,7 +12847,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12865,7 +12881,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12898,7 +12915,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12931,7 +12949,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12963,7 +12982,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12995,7 +13015,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13027,7 +13048,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13059,7 +13081,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13092,7 +13115,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13124,7 +13148,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13156,7 +13181,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13189,7 +13215,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13221,7 +13248,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13253,7 +13281,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13286,7 +13315,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13318,7 +13348,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13350,7 +13381,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13382,7 +13414,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13413,7 +13446,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13444,7 +13478,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13475,7 +13510,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13571,7 +13607,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13605,7 +13642,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13638,7 +13676,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13672,7 +13711,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13705,7 +13745,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13738,7 +13779,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13771,7 +13813,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13804,7 +13847,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13837,7 +13881,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13870,7 +13915,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13902,7 +13948,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13934,7 +13981,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13966,7 +14014,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13998,7 +14047,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14031,7 +14081,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14063,7 +14114,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14095,7 +14147,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14128,7 +14181,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14160,7 +14214,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14192,7 +14247,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14225,7 +14281,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14257,7 +14314,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14289,7 +14347,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14321,7 +14380,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14352,7 +14412,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14383,7 +14444,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14414,7 +14476,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14510,7 +14573,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14544,7 +14608,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14577,7 +14642,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14611,7 +14677,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14644,7 +14711,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14677,7 +14745,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14710,7 +14779,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14743,7 +14813,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14776,7 +14847,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14809,7 +14881,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14841,7 +14914,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14873,7 +14947,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14905,7 +14980,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14937,7 +15013,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14970,7 +15047,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15002,7 +15080,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15034,7 +15113,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15067,7 +15147,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15099,7 +15180,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15131,7 +15213,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15164,7 +15247,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15196,7 +15280,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15228,7 +15313,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15260,7 +15346,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15291,7 +15378,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15322,7 +15410,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15353,7 +15442,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15625,7 +15715,8 @@ "y": 0.0, "z": -25.099999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15657,7 +15748,8 @@ "y": 0.0, "z": -25.099999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15690,7 +15782,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15722,7 +15815,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15754,7 +15848,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15787,7 +15882,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15819,7 +15915,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15864,7 +15961,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15896,7 +15994,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15992,7 +16091,8 @@ "y": 0.0, "z": -25.099999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16024,7 +16124,8 @@ "y": 0.0, "z": -25.099999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16057,7 +16158,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -16089,7 +16191,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16121,7 +16224,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16154,7 +16258,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -16186,7 +16291,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -16231,7 +16337,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -16263,7 +16370,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -16359,7 +16467,8 @@ "y": 0.0, "z": -25.099999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16391,7 +16500,8 @@ "y": 0.0, "z": -25.099999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16424,7 +16534,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -16456,7 +16567,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16488,7 +16600,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16521,7 +16634,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -16553,7 +16667,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -16598,7 +16713,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -16630,7 +16746,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -16803,7 +16920,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -16836,7 +16954,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16933,7 +17052,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16966,7 +17086,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17063,7 +17184,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -17096,7 +17218,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17357,7 +17480,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17389,7 +17513,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17435,7 +17560,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17467,7 +17593,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17499,7 +17626,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17531,7 +17659,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -17577,7 +17706,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -17609,7 +17739,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -17705,7 +17836,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17737,7 +17869,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17783,7 +17916,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17815,7 +17949,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17847,7 +17982,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17879,7 +18015,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -17925,7 +18062,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -17957,7 +18095,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18053,7 +18192,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18085,7 +18225,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18131,7 +18272,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18163,7 +18305,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18195,7 +18338,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18227,7 +18371,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18273,7 +18418,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18305,7 +18451,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18464,7 +18611,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18497,7 +18645,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18594,7 +18743,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -18627,7 +18777,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18724,7 +18875,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18757,7 +18909,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19018,7 +19171,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19050,7 +19204,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19096,7 +19251,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19128,7 +19284,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19160,7 +19317,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19192,7 +19350,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -19238,7 +19397,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -19270,7 +19430,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -19366,7 +19527,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19398,7 +19560,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19444,7 +19607,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19476,7 +19640,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19508,7 +19673,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19540,7 +19706,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -19586,7 +19753,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -19618,7 +19786,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -19714,7 +19883,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19746,7 +19916,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19792,7 +19963,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19824,7 +19996,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19856,7 +20029,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19888,7 +20062,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -19934,7 +20109,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -19966,7 +20142,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -20125,7 +20302,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -20158,7 +20336,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20255,7 +20434,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20288,7 +20468,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20385,7 +20566,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20418,7 +20600,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20679,7 +20862,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20711,7 +20895,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20757,7 +20942,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20789,7 +20975,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20821,7 +21008,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20853,7 +21041,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -20899,7 +21088,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -20931,7 +21121,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -21027,7 +21218,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21059,7 +21251,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21105,7 +21298,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21137,7 +21331,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21169,7 +21364,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21201,7 +21397,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -21247,7 +21444,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -21279,7 +21477,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -21375,7 +21574,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21407,7 +21607,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21453,7 +21654,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21485,7 +21687,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21517,7 +21720,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21549,7 +21753,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -21595,7 +21800,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -21627,7 +21833,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -21786,7 +21993,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -21819,7 +22027,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21916,7 +22125,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -21949,7 +22159,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22046,7 +22257,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22079,7 +22291,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22248,7 +22461,8 @@ "y": 0.0, "z": -28.599999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22280,7 +22494,8 @@ "y": 0.0, "z": -28.599999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22313,7 +22528,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22409,7 +22625,8 @@ "y": 0.0, "z": -28.599999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22441,7 +22658,8 @@ "y": 0.0, "z": -28.599999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22474,7 +22692,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22570,7 +22789,8 @@ "y": 0.0, "z": -28.599999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22602,7 +22822,8 @@ "y": 0.0, "z": -28.599999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22635,7 +22856,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -22821,7 +23043,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22853,7 +23076,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22899,7 +23123,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22931,7 +23156,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22963,7 +23189,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22995,7 +23222,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -23041,7 +23269,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -23073,7 +23302,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -23169,7 +23399,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -23201,7 +23432,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -23247,7 +23479,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -23279,7 +23512,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -23311,7 +23545,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -23343,7 +23578,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -23389,7 +23625,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -23421,7 +23658,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -23517,7 +23755,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -23549,7 +23788,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -23595,7 +23835,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -23627,7 +23868,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -23659,7 +23901,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -23691,7 +23934,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -23737,7 +23981,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -23769,7 +24014,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -23879,7 +24125,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23911,7 +24158,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23944,7 +24192,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -23990,7 +24239,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -24021,7 +24271,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -24052,7 +24303,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -24147,7 +24399,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24179,7 +24432,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24212,7 +24466,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -24258,7 +24513,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -24289,7 +24545,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -24320,7 +24577,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -24415,7 +24673,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24447,7 +24706,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24480,7 +24740,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -24526,7 +24787,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -24557,7 +24819,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -24588,7 +24851,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -24740,7 +25004,8 @@ "y": 0.0, "z": -14.480000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24773,7 +25038,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24870,7 +25136,8 @@ "y": 0.0, "z": -14.480000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24903,7 +25170,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25000,7 +25268,8 @@ "y": 0.0, "z": -14.480000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25033,7 +25302,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -25328,7 +25598,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25360,7 +25631,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25393,7 +25665,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25489,7 +25762,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25521,7 +25795,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25554,7 +25829,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25650,7 +25926,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -25682,7 +25959,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -25715,7 +25993,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -25826,7 +26105,8 @@ "y": 0.0, "z": -14.480000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25859,7 +26139,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25891,7 +26172,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25923,7 +26205,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25957,7 +26240,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25990,7 +26274,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26024,7 +26309,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26057,7 +26343,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26091,7 +26378,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26124,7 +26412,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26158,7 +26447,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26191,7 +26481,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26225,7 +26516,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26258,7 +26550,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26292,7 +26585,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26325,7 +26619,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26359,7 +26654,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26392,7 +26688,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26426,7 +26723,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26459,7 +26757,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26493,7 +26792,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26526,7 +26826,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26559,7 +26860,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26656,7 +26958,8 @@ "y": 0.0, "z": -14.480000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26689,7 +26992,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26721,7 +27025,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26753,7 +27058,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26787,7 +27093,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26820,7 +27127,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26854,7 +27162,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26887,7 +27196,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26921,7 +27231,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26954,7 +27265,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26988,7 +27300,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27021,7 +27334,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27055,7 +27369,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27088,7 +27403,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27122,7 +27438,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27155,7 +27472,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27189,7 +27507,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27222,7 +27541,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27256,7 +27576,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27289,7 +27610,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27323,7 +27645,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27356,7 +27679,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27389,7 +27713,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27486,7 +27811,8 @@ "y": 0.0, "z": -14.480000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27519,7 +27845,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27551,7 +27878,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27583,7 +27911,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27617,7 +27946,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27650,7 +27980,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27684,7 +28015,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27717,7 +28049,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27751,7 +28084,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27784,7 +28118,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27818,7 +28153,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27851,7 +28187,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27885,7 +28222,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27918,7 +28256,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27952,7 +28291,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27985,7 +28325,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -28019,7 +28360,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -28052,7 +28394,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -28086,7 +28429,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -28119,7 +28463,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -28153,7 +28498,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -28186,7 +28532,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -28219,7 +28566,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -28372,7 +28720,8 @@ "y": 0.0, "z": -14.480000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -28405,7 +28754,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28502,7 +28852,8 @@ "y": 0.0, "z": -14.480000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -28535,7 +28886,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28632,7 +28984,8 @@ "y": 0.0, "z": -14.480000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -28665,7 +29018,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -28776,7 +29130,8 @@ "y": 0.0, "z": -14.480000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28809,7 +29164,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28841,7 +29197,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28873,7 +29230,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28907,7 +29265,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28940,7 +29299,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28974,7 +29334,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29007,7 +29368,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29041,7 +29403,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29074,7 +29437,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29108,7 +29472,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29141,7 +29506,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29175,7 +29541,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29208,7 +29575,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29242,7 +29610,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29275,7 +29644,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29309,7 +29679,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29342,7 +29713,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29376,7 +29748,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29409,7 +29782,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29443,7 +29817,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29476,7 +29851,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29509,7 +29885,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29606,7 +29983,8 @@ "y": 0.0, "z": -14.480000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29639,7 +30017,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29671,7 +30050,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29703,7 +30083,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29737,7 +30118,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29770,7 +30152,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29804,7 +30187,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29837,7 +30221,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29871,7 +30256,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29904,7 +30290,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29938,7 +30325,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29971,7 +30359,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30005,7 +30394,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30038,7 +30428,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30072,7 +30463,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30105,7 +30497,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30139,7 +30532,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30172,7 +30566,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30206,7 +30601,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30239,7 +30635,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30273,7 +30670,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30306,7 +30704,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30339,7 +30738,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30436,7 +30836,8 @@ "y": 0.0, "z": -14.480000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30469,7 +30870,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30501,7 +30903,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30533,7 +30936,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30567,7 +30971,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30600,7 +31005,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30634,7 +31040,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30667,7 +31074,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30701,7 +31109,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30734,7 +31143,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30768,7 +31178,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30801,7 +31212,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30835,7 +31247,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30868,7 +31281,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30902,7 +31316,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30935,7 +31350,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30969,7 +31385,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31002,7 +31419,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31036,7 +31454,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31069,7 +31488,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31103,7 +31523,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31136,7 +31557,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31169,7 +31591,8 @@ "y": 0.0, "z": -14.479999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31646,7 +32069,8 @@ "y": 0.0, "z": -14.28 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31678,7 +32102,8 @@ "y": 0.0, "z": -14.28 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31711,7 +32136,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31807,7 +32233,8 @@ "y": 0.0, "z": -14.28 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31839,7 +32266,8 @@ "y": 0.0, "z": -14.28 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31872,7 +32300,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -31968,7 +32397,8 @@ "y": 0.0, "z": -14.28 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32000,7 +32430,8 @@ "y": 0.0, "z": -14.28 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32033,7 +32464,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -32144,7 +32576,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32178,7 +32611,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32211,7 +32645,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32245,7 +32680,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32278,7 +32714,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32311,7 +32748,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32344,7 +32782,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32377,7 +32816,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32409,7 +32849,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32441,7 +32882,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32473,7 +32915,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32505,7 +32948,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32538,7 +32982,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32570,7 +33015,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32602,7 +33048,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32635,7 +33082,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32667,7 +33115,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32699,7 +33148,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32732,7 +33182,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32764,7 +33215,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32796,7 +33248,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32828,7 +33281,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32859,7 +33313,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32890,7 +33345,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32921,7 +33377,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33017,7 +33474,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33051,7 +33509,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33084,7 +33543,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33118,7 +33578,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33151,7 +33612,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33184,7 +33646,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33217,7 +33680,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33250,7 +33714,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33282,7 +33747,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33314,7 +33780,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33346,7 +33813,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33378,7 +33846,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33411,7 +33880,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33443,7 +33913,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33475,7 +33946,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33508,7 +33980,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33540,7 +34013,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33572,7 +34046,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33605,7 +34080,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33637,7 +34113,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33669,7 +34146,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33701,7 +34179,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33732,7 +34211,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33763,7 +34243,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33794,7 +34275,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33890,7 +34372,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33924,7 +34407,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33957,7 +34441,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33991,7 +34476,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34024,7 +34510,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34057,7 +34544,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34090,7 +34578,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34123,7 +34612,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34155,7 +34645,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34187,7 +34678,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34219,7 +34711,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34251,7 +34744,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34284,7 +34778,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34316,7 +34811,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34348,7 +34844,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34381,7 +34878,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34413,7 +34911,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34445,7 +34944,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34478,7 +34978,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34510,7 +35011,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34542,7 +35044,8 @@ "y": 0.0, "z": -24.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34574,7 +35077,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34605,7 +35109,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34636,7 +35141,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34667,7 +35173,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34883,7 +35390,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34915,7 +35423,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34961,7 +35470,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34993,7 +35503,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35025,7 +35536,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35057,7 +35569,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35103,7 +35616,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35134,7 +35648,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35165,7 +35680,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35260,7 +35776,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35292,7 +35809,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35338,7 +35856,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35370,7 +35889,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35402,7 +35922,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35434,7 +35955,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35480,7 +36002,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35511,7 +36034,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35542,7 +36066,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35637,7 +36162,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -35669,7 +36195,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -35715,7 +36242,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -35747,7 +36275,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -35779,7 +36308,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -35811,7 +36341,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35857,7 +36388,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35888,7 +36420,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35919,7 +36452,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36029,7 +36563,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36061,7 +36596,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36092,7 +36628,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36123,7 +36660,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36154,7 +36692,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36186,7 +36725,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36232,7 +36772,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36263,7 +36804,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36294,7 +36836,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36325,7 +36868,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36421,7 +36965,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36453,7 +36998,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36484,7 +37030,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36515,7 +37062,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36546,7 +37094,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36578,7 +37127,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36624,7 +37174,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36655,7 +37206,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36686,7 +37238,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36717,7 +37270,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36813,7 +37367,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36845,7 +37400,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36876,7 +37432,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36907,7 +37464,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36938,7 +37496,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36970,7 +37529,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37016,7 +37576,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37047,7 +37608,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37078,7 +37640,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37109,7 +37672,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37232,7 +37796,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37264,7 +37829,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37310,7 +37876,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37342,7 +37909,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37374,7 +37942,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37406,7 +37975,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37452,7 +38022,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37483,7 +38054,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37514,7 +38086,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37609,7 +38182,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -37641,7 +38215,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -37687,7 +38262,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -37719,7 +38295,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -37751,7 +38328,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -37783,7 +38361,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37829,7 +38408,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37860,7 +38440,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37891,7 +38472,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37986,7 +38568,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38018,7 +38601,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38064,7 +38648,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38096,7 +38681,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38128,7 +38714,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38160,7 +38747,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38206,7 +38794,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38237,7 +38826,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38268,7 +38858,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38378,7 +38969,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38410,7 +39002,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38441,7 +39034,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38472,7 +39066,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38503,7 +39098,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38535,7 +39131,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38581,7 +39178,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38612,7 +39210,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38643,7 +39242,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38674,7 +39274,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38770,7 +39371,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38802,7 +39404,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38833,7 +39436,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38864,7 +39468,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38895,7 +39500,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -38927,7 +39533,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -38973,7 +39580,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39004,7 +39612,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39035,7 +39644,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39066,7 +39676,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39162,7 +39773,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39194,7 +39806,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39225,7 +39838,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39256,7 +39870,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39287,7 +39902,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39319,7 +39935,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39365,7 +39982,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39396,7 +40014,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39427,7 +40046,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39458,7 +40078,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39581,7 +40202,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39613,7 +40235,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39659,7 +40282,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39691,7 +40315,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39723,7 +40348,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39755,7 +40381,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -39801,7 +40428,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -39832,7 +40460,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -39863,7 +40492,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -39958,7 +40588,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39990,7 +40621,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -40036,7 +40668,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -40068,7 +40701,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -40100,7 +40734,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -40132,7 +40767,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40178,7 +40814,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40209,7 +40846,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40240,7 +40878,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40335,7 +40974,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -40367,7 +41007,8 @@ "y": 0.0, "z": -25.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -40413,7 +41054,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -40445,7 +41087,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -40477,7 +41120,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -40509,7 +41153,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40555,7 +41200,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40586,7 +41232,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40617,7 +41264,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40740,7 +41388,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40772,7 +41421,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40805,7 +41455,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40851,7 +41502,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40882,7 +41534,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40913,7 +41566,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -41008,7 +41662,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -41040,7 +41695,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -41073,7 +41729,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -41119,7 +41776,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -41150,7 +41808,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -41181,7 +41840,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -41276,7 +41936,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -41308,7 +41969,8 @@ "y": 0.0, "z": -28.799999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -41341,7 +42003,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -41387,7 +42050,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -41418,7 +42082,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -41449,7 +42114,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -41621,7 +42287,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41653,7 +42320,8 @@ "y": 0.0, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41685,7 +42353,8 @@ "y": 0.0, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41717,7 +42386,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41749,7 +42419,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41781,7 +42452,8 @@ "y": 1.0400000000000063, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41813,7 +42485,8 @@ "y": 1.0400000000000063, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41845,7 +42518,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41877,7 +42551,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41909,7 +42584,8 @@ "y": 0.0, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41941,7 +42617,8 @@ "y": 0.0, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41973,7 +42650,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42005,7 +42683,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42037,7 +42716,8 @@ "y": -1.0400000000000063, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42069,7 +42749,8 @@ "y": -1.0400000000000063, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42101,7 +42782,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42133,7 +42815,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42166,7 +42849,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42198,7 +42882,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42229,7 +42914,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42260,7 +42946,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42291,7 +42978,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42432,7 +43120,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42464,7 +43153,8 @@ "y": 0.0, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42496,7 +43186,8 @@ "y": 0.0, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42528,7 +43219,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42560,7 +43252,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42592,7 +43285,8 @@ "y": 1.0400000000000063, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42624,7 +43318,8 @@ "y": 1.0400000000000063, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42656,7 +43351,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42688,7 +43384,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42720,7 +43417,8 @@ "y": 0.0, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42752,7 +43450,8 @@ "y": 0.0, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42784,7 +43483,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42816,7 +43516,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42848,7 +43549,8 @@ "y": -1.0400000000000063, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42880,7 +43582,8 @@ "y": -1.0400000000000063, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42912,7 +43615,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42944,7 +43648,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -42977,7 +43682,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -43009,7 +43715,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -43040,7 +43747,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -43071,7 +43779,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -43102,7 +43811,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -43243,7 +43953,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43275,7 +43986,8 @@ "y": 0.0, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43307,7 +44019,8 @@ "y": 0.0, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43339,7 +44052,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43371,7 +44085,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43403,7 +44118,8 @@ "y": 1.0400000000000063, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43435,7 +44151,8 @@ "y": 1.0400000000000063, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43467,7 +44184,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43499,7 +44217,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43531,7 +44250,8 @@ "y": 0.0, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43563,7 +44283,8 @@ "y": 0.0, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43595,7 +44316,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43627,7 +44349,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43659,7 +44382,8 @@ "y": -1.0400000000000063, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43691,7 +44415,8 @@ "y": -1.0400000000000063, "z": -18.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43723,7 +44448,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43755,7 +44481,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43788,7 +44515,8 @@ "y": 0.0, "z": -28.099999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43820,7 +44548,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43851,7 +44580,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43882,7 +44612,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43913,7 +44644,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44129,7 +44861,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44161,7 +44894,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44194,7 +44928,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44290,7 +45025,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44322,7 +45058,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44355,7 +45092,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44451,7 +45189,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44483,7 +45222,8 @@ "y": 0.0, "z": -28.599999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44516,7 +45256,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44652,6 +45393,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f3e297a11][OT2_S_v2_3_P300S_None_MM1_MM2_TM_Mix].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f3e297a11][OT2_S_v2_3_P300S_None_MM1_MM2_TM_Mix].json index c54d8e33c11..0028c36df1b 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f3e297a11][OT2_S_v2_3_P300S_None_MM1_MM2_TM_Mix].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f3e297a11][OT2_S_v2_3_P300S_None_MM1_MM2_TM_Mix].json @@ -3139,7 +3139,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3172,7 +3173,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3205,7 +3207,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3238,7 +3241,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3271,7 +3275,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3304,7 +3309,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3337,7 +3343,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3370,7 +3377,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3427,6 +3435,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.3" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f84e60cb0][OT2_S_v2_16_P300M_P20S_HS_TC_TM_aspirateDispenseMix0Volume].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f84e60cb0][OT2_S_v2_16_P300M_P20S_HS_TC_TM_aspirateDispenseMix0Volume].json index 1b541c34406..86023eb8c12 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f84e60cb0][OT2_S_v2_16_P300M_P20S_HS_TC_TM_aspirateDispenseMix0Volume].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[6f84e60cb0][OT2_S_v2_16_P300M_P20S_HS_TC_TM_aspirateDispenseMix0Volume].json @@ -2686,7 +2686,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2719,7 +2720,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2752,7 +2754,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2785,7 +2788,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2845,6 +2849,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[70b873c24b][pl_SamplePrep_MS_Digest_Flex_upto96].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[70b873c24b][pl_SamplePrep_MS_Digest_Flex_upto96].json index e5b09a7a490..b79aec33a1b 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[70b873c24b][pl_SamplePrep_MS_Digest_Flex_upto96].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[70b873c24b][pl_SamplePrep_MS_Digest_Flex_upto96].json @@ -5958,7 +5958,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6004,7 +6005,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6036,7 +6038,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6069,7 +6072,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6102,7 +6106,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6135,7 +6140,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6249,7 +6255,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -6295,7 +6302,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -6327,7 +6335,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -6360,7 +6369,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -6393,7 +6403,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -6426,7 +6437,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -6540,7 +6552,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6586,7 +6599,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6618,7 +6632,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6651,7 +6666,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6684,7 +6700,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6717,7 +6734,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6831,7 +6849,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -6877,7 +6896,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -6909,7 +6929,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -6942,7 +6963,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -6975,7 +6997,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -7008,7 +7031,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -7122,7 +7146,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7168,7 +7193,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7200,7 +7226,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7233,7 +7260,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7266,7 +7294,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7299,7 +7328,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -7413,7 +7443,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -7459,7 +7490,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -7491,7 +7523,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -7524,7 +7557,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -7557,7 +7591,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -7590,7 +7625,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -7704,7 +7740,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -7750,7 +7787,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -7782,7 +7820,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -7815,7 +7854,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -7848,7 +7888,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -7881,7 +7922,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -7995,7 +8037,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -8041,7 +8084,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -8073,7 +8117,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -8106,7 +8151,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -8139,7 +8185,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -8172,7 +8219,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -8286,7 +8334,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -8332,7 +8381,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -8364,7 +8414,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -8397,7 +8448,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8430,7 +8482,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8463,7 +8516,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8577,7 +8631,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -8623,7 +8678,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -8655,7 +8711,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -8688,7 +8745,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -8721,7 +8779,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -8754,7 +8813,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -8868,7 +8928,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -8914,7 +8975,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -8946,7 +9008,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -8979,7 +9042,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -9012,7 +9076,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -9045,7 +9110,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -9159,7 +9225,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -9205,7 +9272,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -9237,7 +9305,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -9270,7 +9339,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -9303,7 +9373,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -9336,7 +9407,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -9450,7 +9522,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -9496,7 +9569,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -9528,7 +9602,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -9561,7 +9636,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -9594,7 +9670,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -9627,7 +9704,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -9741,7 +9819,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -9787,7 +9866,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -9819,7 +9899,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -9852,7 +9933,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -9885,7 +9967,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -9918,7 +10001,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -10032,7 +10116,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -10078,7 +10163,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -10110,7 +10196,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -10143,7 +10230,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -10176,7 +10264,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -10209,7 +10298,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -10323,7 +10413,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -10369,7 +10460,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -10401,7 +10493,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -10434,7 +10527,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -10467,7 +10561,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -10500,7 +10595,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -10614,7 +10710,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -10660,7 +10757,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -10692,7 +10790,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -10725,7 +10824,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10758,7 +10858,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10791,7 +10892,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10905,7 +11007,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -10951,7 +11054,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -10983,7 +11087,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -11016,7 +11121,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -11049,7 +11155,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -11082,7 +11189,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -11196,7 +11304,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -11242,7 +11351,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -11274,7 +11384,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -11307,7 +11418,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -11340,7 +11452,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -11373,7 +11486,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -11487,7 +11601,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -11533,7 +11648,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -11565,7 +11681,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -11598,7 +11715,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -11631,7 +11749,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -11664,7 +11783,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -11778,7 +11898,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -11824,7 +11945,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -11856,7 +11978,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -11889,7 +12012,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -11922,7 +12046,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -11955,7 +12080,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -12069,7 +12195,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -12115,7 +12242,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -12147,7 +12275,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -12180,7 +12309,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -12213,7 +12343,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -12246,7 +12377,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -12360,7 +12492,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -12406,7 +12539,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -12438,7 +12572,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -12471,7 +12606,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -12504,7 +12640,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -12537,7 +12674,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -12651,7 +12789,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -12697,7 +12836,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -12729,7 +12869,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -12762,7 +12903,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -12795,7 +12937,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -12828,7 +12971,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -13303,7 +13447,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13349,7 +13494,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13381,7 +13527,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13414,7 +13561,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -13447,7 +13595,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -13480,7 +13629,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -13594,7 +13744,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -13640,7 +13791,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -13672,7 +13824,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -13705,7 +13858,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -13738,7 +13892,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -13771,7 +13926,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -13885,7 +14041,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13931,7 +14088,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13963,7 +14121,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -13996,7 +14155,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -14029,7 +14189,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -14062,7 +14223,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -14176,7 +14338,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -14222,7 +14385,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -14254,7 +14418,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -14287,7 +14452,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -14320,7 +14486,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -14353,7 +14520,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -14467,7 +14635,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14513,7 +14682,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14545,7 +14715,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14578,7 +14749,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -14611,7 +14783,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -14644,7 +14817,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -14758,7 +14932,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -14804,7 +14979,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -14836,7 +15012,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -14869,7 +15046,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -14902,7 +15080,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -14935,7 +15114,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -15049,7 +15229,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -15095,7 +15276,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -15127,7 +15309,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -15160,7 +15343,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -15193,7 +15377,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -15226,7 +15411,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -15340,7 +15526,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -15386,7 +15573,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -15418,7 +15606,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -15451,7 +15640,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -15484,7 +15674,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -15517,7 +15708,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -15631,7 +15823,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15677,7 +15870,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15709,7 +15903,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15742,7 +15937,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15775,7 +15971,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15808,7 +16005,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15922,7 +16120,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -15968,7 +16167,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -16000,7 +16200,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -16033,7 +16234,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -16066,7 +16268,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -16099,7 +16302,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -16213,7 +16417,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -16259,7 +16464,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -16291,7 +16497,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -16324,7 +16531,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -16357,7 +16565,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -16390,7 +16599,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -16504,7 +16714,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -16550,7 +16761,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -16582,7 +16794,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -16615,7 +16828,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -16648,7 +16862,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -16681,7 +16896,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -16795,7 +17011,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16841,7 +17058,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16873,7 +17091,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16906,7 +17125,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -16939,7 +17159,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -16972,7 +17193,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -17086,7 +17308,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -17132,7 +17355,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -17164,7 +17388,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -17197,7 +17422,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -17230,7 +17456,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -17263,7 +17490,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -17377,7 +17605,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -17423,7 +17652,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -17455,7 +17685,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -17488,7 +17719,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -17521,7 +17753,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -17554,7 +17787,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -17668,7 +17902,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -17714,7 +17949,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -17746,7 +17982,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -17779,7 +18016,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -17812,7 +18050,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -17845,7 +18084,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -17959,7 +18199,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18005,7 +18246,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18037,7 +18279,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18070,7 +18313,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -18103,7 +18347,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -18136,7 +18381,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -18250,7 +18496,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -18296,7 +18543,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -18328,7 +18576,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -18361,7 +18610,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -18394,7 +18644,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -18427,7 +18678,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -18541,7 +18793,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -18587,7 +18840,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -18619,7 +18873,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -18652,7 +18907,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -18685,7 +18941,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -18718,7 +18975,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -18832,7 +19090,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -18878,7 +19137,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -18910,7 +19170,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -18943,7 +19204,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -18976,7 +19238,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -19009,7 +19272,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -19123,7 +19387,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -19169,7 +19434,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -19201,7 +19467,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -19234,7 +19501,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -19267,7 +19535,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -19300,7 +19569,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -19414,7 +19684,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -19460,7 +19731,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -19492,7 +19764,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -19525,7 +19798,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -19558,7 +19832,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -19591,7 +19866,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -19705,7 +19981,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -19751,7 +20028,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -19783,7 +20061,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -19816,7 +20095,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -19849,7 +20129,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -19882,7 +20163,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -19996,7 +20278,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -20042,7 +20325,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -20074,7 +20358,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -20107,7 +20392,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -20140,7 +20426,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -20173,7 +20460,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -20648,7 +20936,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20694,7 +20983,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20726,7 +21016,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20759,7 +21050,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -20792,7 +21084,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -20825,7 +21118,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -20939,7 +21233,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -20985,7 +21280,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -21017,7 +21313,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -21050,7 +21347,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -21083,7 +21381,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -21116,7 +21415,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -21230,7 +21530,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -21276,7 +21577,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -21308,7 +21610,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -21341,7 +21644,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -21374,7 +21678,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -21407,7 +21712,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -21521,7 +21827,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -21567,7 +21874,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -21599,7 +21907,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -21632,7 +21941,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -21665,7 +21975,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -21698,7 +22009,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -21812,7 +22124,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21858,7 +22171,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21890,7 +22204,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21923,7 +22238,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -21956,7 +22272,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -21989,7 +22306,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -22103,7 +22421,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -22149,7 +22468,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -22181,7 +22501,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -22214,7 +22535,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -22247,7 +22569,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -22280,7 +22603,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -22394,7 +22718,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -22440,7 +22765,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -22472,7 +22798,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -22505,7 +22832,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -22538,7 +22866,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -22571,7 +22900,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -22685,7 +23015,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -22731,7 +23062,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -22763,7 +23095,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -22796,7 +23129,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -22829,7 +23163,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -22862,7 +23197,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -22976,7 +23312,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23022,7 +23359,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23054,7 +23392,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23087,7 +23426,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -23120,7 +23460,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -23153,7 +23494,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -23267,7 +23609,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -23313,7 +23656,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -23345,7 +23689,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -23378,7 +23723,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -23411,7 +23757,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -23444,7 +23791,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -23558,7 +23906,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -23604,7 +23953,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -23636,7 +23986,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -23669,7 +24020,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -23702,7 +24054,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -23735,7 +24088,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -23849,7 +24203,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -23895,7 +24250,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -23927,7 +24283,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -23960,7 +24317,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -23993,7 +24351,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -24026,7 +24385,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -24140,7 +24500,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24186,7 +24547,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24218,7 +24580,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24251,7 +24614,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -24284,7 +24648,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -24317,7 +24682,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -24431,7 +24797,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -24477,7 +24844,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -24509,7 +24877,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -24542,7 +24911,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -24575,7 +24945,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -24608,7 +24979,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -24722,7 +25094,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -24768,7 +25141,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -24800,7 +25174,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -24833,7 +25208,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -24866,7 +25242,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -24899,7 +25276,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -25013,7 +25391,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -25059,7 +25438,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -25091,7 +25471,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -25124,7 +25505,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -25157,7 +25539,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -25190,7 +25573,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -25304,7 +25688,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25350,7 +25735,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25382,7 +25768,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25415,7 +25802,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -25448,7 +25836,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -25481,7 +25870,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -25595,7 +25985,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -25641,7 +26032,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -25673,7 +26065,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -25706,7 +26099,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -25739,7 +26133,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -25772,7 +26167,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -25886,7 +26282,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -25932,7 +26329,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -25964,7 +26362,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -25997,7 +26396,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -26030,7 +26430,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -26063,7 +26464,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -26177,7 +26579,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -26223,7 +26626,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -26255,7 +26659,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -26288,7 +26693,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -26321,7 +26727,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -26354,7 +26761,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -26468,7 +26876,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26514,7 +26923,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26546,7 +26956,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26579,7 +26990,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -26612,7 +27024,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -26645,7 +27058,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -26759,7 +27173,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -26805,7 +27220,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -26837,7 +27253,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -26870,7 +27287,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -26903,7 +27321,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -26936,7 +27355,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -27050,7 +27470,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -27096,7 +27517,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -27128,7 +27550,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -27161,7 +27584,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -27194,7 +27618,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -27227,7 +27652,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -27341,7 +27767,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -27387,7 +27814,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -27419,7 +27847,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -27452,7 +27881,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -27485,7 +27915,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -27518,7 +27949,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -27993,7 +28425,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28039,7 +28472,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28071,7 +28505,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28104,7 +28539,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -28137,7 +28573,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -28170,7 +28607,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -28284,7 +28722,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -28330,7 +28769,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -28362,7 +28802,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -28395,7 +28836,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -28428,7 +28870,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -28461,7 +28904,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -28575,7 +29019,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -28621,7 +29066,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -28653,7 +29099,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -28686,7 +29133,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -28719,7 +29167,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -28752,7 +29201,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -28866,7 +29316,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -28912,7 +29363,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -28944,7 +29396,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -28977,7 +29430,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -29010,7 +29464,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -29043,7 +29498,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -29157,7 +29613,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29203,7 +29660,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29235,7 +29693,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29268,7 +29727,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -29301,7 +29761,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -29334,7 +29795,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -29448,7 +29910,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -29494,7 +29957,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -29526,7 +29990,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -29559,7 +30024,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -29592,7 +30058,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -29625,7 +30092,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -29739,7 +30207,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -29785,7 +30254,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -29817,7 +30287,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -29850,7 +30321,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -29883,7 +30355,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -29916,7 +30389,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -30030,7 +30504,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -30076,7 +30551,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -30108,7 +30584,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -30141,7 +30618,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -30174,7 +30652,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -30207,7 +30686,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -30321,7 +30801,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30367,7 +30848,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30399,7 +30881,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30432,7 +30915,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -30465,7 +30949,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -30498,7 +30983,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -30612,7 +31098,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -30658,7 +31145,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -30690,7 +31178,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -30723,7 +31212,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -30756,7 +31246,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -30789,7 +31280,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -30903,7 +31395,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -30949,7 +31442,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -30981,7 +31475,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -31014,7 +31509,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -31047,7 +31543,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -31080,7 +31577,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -31194,7 +31692,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -31240,7 +31739,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -31272,7 +31772,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -31305,7 +31806,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -31338,7 +31840,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -31371,7 +31874,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -31485,7 +31989,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31531,7 +32036,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31563,7 +32069,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31596,7 +32103,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -31629,7 +32137,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -31662,7 +32171,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -31776,7 +32286,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -31822,7 +32333,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -31854,7 +32366,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -31887,7 +32400,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -31920,7 +32434,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -31953,7 +32468,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -32067,7 +32583,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -32113,7 +32630,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -32145,7 +32663,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -32178,7 +32697,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -32211,7 +32731,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -32244,7 +32765,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -32358,7 +32880,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -32404,7 +32927,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -32436,7 +32960,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -32469,7 +32994,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -32502,7 +33028,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -32535,7 +33062,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -32649,7 +33177,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32695,7 +33224,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32727,7 +33257,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32760,7 +33291,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32793,7 +33325,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32826,7 +33359,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32940,7 +33474,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -32986,7 +33521,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -33018,7 +33554,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -33051,7 +33588,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -33084,7 +33622,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -33117,7 +33656,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -33231,7 +33771,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -33277,7 +33818,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -33309,7 +33851,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -33342,7 +33885,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -33375,7 +33919,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -33408,7 +33953,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -33522,7 +34068,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -33568,7 +34115,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -33600,7 +34148,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -33633,7 +34182,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -33666,7 +34216,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -33699,7 +34250,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -33813,7 +34365,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33859,7 +34412,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33891,7 +34445,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33924,7 +34479,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -33957,7 +34513,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -33990,7 +34547,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -34104,7 +34662,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -34150,7 +34709,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -34182,7 +34742,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -34215,7 +34776,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -34248,7 +34810,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -34281,7 +34844,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -34395,7 +34959,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -34441,7 +35006,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -34473,7 +35039,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -34506,7 +35073,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -34539,7 +35107,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -34572,7 +35141,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -34686,7 +35256,8 @@ "y": 0.0, "z": -37.699999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -34732,7 +35303,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -34764,7 +35336,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -34797,7 +35370,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -34830,7 +35404,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -34863,7 +35438,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -34977,7 +35553,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35010,7 +35587,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35056,7 +35634,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35088,7 +35667,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35121,7 +35701,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35154,7 +35735,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35186,7 +35768,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35218,7 +35801,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35251,7 +35835,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35297,7 +35882,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35329,7 +35915,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35362,7 +35949,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35395,7 +35983,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35427,7 +36016,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35459,7 +36049,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35492,7 +36083,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35538,7 +36130,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35570,7 +36163,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35603,7 +36197,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35636,7 +36231,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35668,7 +36264,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35700,7 +36297,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35733,7 +36331,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35779,7 +36378,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35811,7 +36411,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35844,7 +36445,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -35877,7 +36479,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -35909,7 +36512,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -35941,7 +36545,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35974,7 +36579,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36020,7 +36626,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36052,7 +36659,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36085,7 +36693,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36118,7 +36727,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36150,7 +36760,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36182,7 +36793,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36215,7 +36827,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36261,7 +36874,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36293,7 +36907,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36326,7 +36941,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36359,7 +36975,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36391,7 +37008,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36423,7 +37041,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36456,7 +37075,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36502,7 +37122,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36534,7 +37155,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36567,7 +37189,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36600,7 +37223,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36632,7 +37256,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36664,7 +37289,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36697,7 +37323,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36743,7 +37370,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36775,7 +37403,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36808,7 +37437,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36841,7 +37471,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36873,7 +37504,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36905,7 +37537,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36938,7 +37571,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36984,7 +37618,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37016,7 +37651,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37049,7 +37685,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37082,7 +37719,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37114,7 +37752,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37146,7 +37785,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37179,7 +37819,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37225,7 +37866,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37257,7 +37899,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37290,7 +37933,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -37323,7 +37967,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -37355,7 +38000,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -37387,7 +38033,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37420,7 +38067,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37466,7 +38114,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37498,7 +38147,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37531,7 +38181,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37564,7 +38215,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37596,7 +38248,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37628,7 +38281,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37661,7 +38315,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37707,7 +38362,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37739,7 +38395,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37772,7 +38429,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37805,7 +38463,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37837,7 +38496,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37933,7 +38593,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -37966,7 +38627,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38012,7 +38674,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38044,7 +38707,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38077,7 +38741,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38110,7 +38775,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38142,7 +38808,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38174,7 +38841,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38207,7 +38875,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38253,7 +38922,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38285,7 +38955,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38318,7 +38989,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38351,7 +39023,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38383,7 +39056,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38415,7 +39089,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38448,7 +39123,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38494,7 +39170,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38526,7 +39203,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38559,7 +39237,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -38592,7 +39271,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -38624,7 +39304,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -38656,7 +39337,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38689,7 +39371,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38735,7 +39418,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38767,7 +39451,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38800,7 +39485,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38833,7 +39519,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38865,7 +39552,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38897,7 +39585,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38930,7 +39619,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -38976,7 +39666,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39008,7 +39699,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39041,7 +39733,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39074,7 +39767,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39106,7 +39800,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39138,7 +39833,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39171,7 +39867,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39217,7 +39914,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39249,7 +39947,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39282,7 +39981,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -39315,7 +40015,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -39347,7 +40048,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -39379,7 +40081,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39412,7 +40115,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39458,7 +40162,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39490,7 +40195,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39523,7 +40229,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39556,7 +40263,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39588,7 +40296,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39620,7 +40329,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39653,7 +40363,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39699,7 +40410,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39731,7 +40443,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39764,7 +40477,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39797,7 +40511,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39829,7 +40544,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -39861,7 +40577,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39894,7 +40611,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39940,7 +40658,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39972,7 +40691,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40005,7 +40725,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -40038,7 +40759,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -40070,7 +40792,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -40102,7 +40825,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40135,7 +40859,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40181,7 +40906,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40213,7 +40939,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40246,7 +40973,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -40279,7 +41007,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -40311,7 +41040,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -40343,7 +41073,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40376,7 +41107,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40422,7 +41154,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40454,7 +41187,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40487,7 +41221,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40520,7 +41255,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40552,7 +41288,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40584,7 +41321,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40617,7 +41355,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40663,7 +41402,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40695,7 +41435,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -40728,7 +41469,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40761,7 +41503,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40793,7 +41536,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -40889,7 +41633,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40923,7 +41668,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40956,7 +41702,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40990,7 +41737,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41023,7 +41771,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41057,7 +41806,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41090,7 +41840,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41124,7 +41875,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41157,7 +41909,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41190,7 +41943,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41222,7 +41976,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41254,7 +42009,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41350,7 +42106,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -41384,7 +42141,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -41417,7 +42175,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -41451,7 +42210,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -41484,7 +42244,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -41518,7 +42279,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -41551,7 +42313,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -41585,7 +42348,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -41618,7 +42382,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -41651,7 +42416,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -41683,7 +42449,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -41715,7 +42482,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -41811,7 +42579,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41845,7 +42614,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41878,7 +42648,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41912,7 +42683,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41945,7 +42717,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41979,7 +42752,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -42012,7 +42786,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -42046,7 +42821,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -42079,7 +42855,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -42112,7 +42889,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -42144,7 +42922,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -42176,7 +42955,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -42272,7 +43052,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42306,7 +43087,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42339,7 +43121,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42373,7 +43156,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42406,7 +43190,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42440,7 +43225,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42473,7 +43259,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42507,7 +43294,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42540,7 +43328,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42573,7 +43362,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42605,7 +43395,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42637,7 +43428,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42733,7 +43525,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42767,7 +43560,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42800,7 +43594,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42834,7 +43629,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42867,7 +43663,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42901,7 +43698,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42934,7 +43732,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42968,7 +43767,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43001,7 +43801,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43034,7 +43835,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43066,7 +43868,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43098,7 +43901,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43194,7 +43998,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -43228,7 +44033,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -43261,7 +44067,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -43295,7 +44102,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -43328,7 +44136,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -43362,7 +44171,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -43395,7 +44205,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -43429,7 +44240,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -43462,7 +44274,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -43495,7 +44308,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -43527,7 +44341,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -43559,7 +44374,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -43655,7 +44471,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43689,7 +44506,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43722,7 +44540,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43756,7 +44575,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43789,7 +44609,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43823,7 +44644,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43856,7 +44678,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43890,7 +44713,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43923,7 +44747,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43956,7 +44781,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43988,7 +44814,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44020,7 +44847,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44116,7 +44944,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44150,7 +44979,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44183,7 +45013,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44217,7 +45048,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44250,7 +45082,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44284,7 +45117,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44317,7 +45151,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44351,7 +45186,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44384,7 +45220,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44417,7 +45254,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44449,7 +45287,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44481,7 +45320,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44577,7 +45417,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44611,7 +45452,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44644,7 +45486,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44678,7 +45521,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44711,7 +45555,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44745,7 +45590,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44778,7 +45624,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44812,7 +45659,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44845,7 +45693,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44878,7 +45727,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44910,7 +45760,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44942,7 +45793,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -45038,7 +45890,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -45072,7 +45925,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -45105,7 +45959,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -45139,7 +45994,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -45172,7 +46028,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -45206,7 +46063,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -45239,7 +46097,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -45273,7 +46132,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -45306,7 +46166,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -45339,7 +46200,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -45371,7 +46233,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -45403,7 +46266,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -45499,7 +46363,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45533,7 +46398,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45566,7 +46432,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45600,7 +46467,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45633,7 +46501,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45667,7 +46536,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45700,7 +46570,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45734,7 +46605,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45767,7 +46639,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45800,7 +46673,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45832,7 +46706,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45864,7 +46739,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45960,7 +46836,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -45994,7 +46871,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46027,7 +46905,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46061,7 +46940,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46094,7 +46974,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46128,7 +47009,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46161,7 +47043,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46195,7 +47078,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46228,7 +47112,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46261,7 +47146,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46293,7 +47179,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46325,7 +47212,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46574,7 +47462,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -46607,7 +47496,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -46653,7 +47543,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -46685,7 +47576,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -46718,7 +47610,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46751,7 +47644,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46783,7 +47677,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46815,7 +47710,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -46848,7 +47744,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -46894,7 +47791,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -46926,7 +47824,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -46959,7 +47858,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -46992,7 +47892,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -47024,7 +47925,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -47056,7 +47958,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47089,7 +47992,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47135,7 +48039,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47167,7 +48072,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47200,7 +48106,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47233,7 +48140,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47265,7 +48173,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47297,7 +48206,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47330,7 +48240,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47376,7 +48287,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47408,7 +48320,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47441,7 +48354,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47474,7 +48388,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47506,7 +48421,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47538,7 +48454,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47571,7 +48488,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47617,7 +48535,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47649,7 +48568,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47682,7 +48602,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47715,7 +48636,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47747,7 +48669,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47779,7 +48702,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47812,7 +48736,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47858,7 +48783,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47890,7 +48816,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47923,7 +48850,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47956,7 +48884,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47988,7 +48917,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48020,7 +48950,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48053,7 +48984,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48099,7 +49031,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48131,7 +49064,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48164,7 +49098,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48197,7 +49132,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48229,7 +49165,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48261,7 +49198,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48294,7 +49232,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48340,7 +49279,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48372,7 +49312,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48405,7 +49346,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -48438,7 +49380,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -48470,7 +49413,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -48502,7 +49446,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48535,7 +49480,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48581,7 +49527,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48613,7 +49560,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48646,7 +49594,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48679,7 +49628,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48711,7 +49661,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48743,7 +49694,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48776,7 +49728,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48822,7 +49775,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48854,7 +49808,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48887,7 +49842,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48920,7 +49876,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48952,7 +49909,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48984,7 +49942,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49017,7 +49976,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49063,7 +50023,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49095,7 +50056,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49128,7 +50090,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -49161,7 +50124,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -49193,7 +50157,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -49225,7 +50190,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49258,7 +50224,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49304,7 +50271,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49336,7 +50304,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49369,7 +50338,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -49402,7 +50372,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -49434,7 +50405,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -49530,7 +50502,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49564,7 +50537,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49597,7 +50571,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49631,7 +50606,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49664,7 +50640,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49698,7 +50675,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49731,7 +50709,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49765,7 +50744,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49798,7 +50778,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49831,7 +50812,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49863,7 +50845,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49895,7 +50878,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -49991,7 +50975,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -50025,7 +51010,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -50058,7 +51044,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -50092,7 +51079,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -50125,7 +51113,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -50159,7 +51148,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -50192,7 +51182,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -50226,7 +51217,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -50259,7 +51251,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -50292,7 +51285,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -50324,7 +51318,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -50356,7 +51351,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -50452,7 +51448,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50486,7 +51483,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50519,7 +51517,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50553,7 +51552,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50586,7 +51586,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50620,7 +51621,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50653,7 +51655,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50687,7 +51690,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50720,7 +51724,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50753,7 +51758,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50785,7 +51791,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50817,7 +51824,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50913,7 +51921,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -50947,7 +51956,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -50980,7 +51990,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -51014,7 +52025,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -51047,7 +52059,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -51081,7 +52094,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -51114,7 +52128,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -51148,7 +52163,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -51181,7 +52197,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -51214,7 +52231,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -51246,7 +52264,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -51278,7 +52297,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -51374,7 +52394,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51408,7 +52429,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51441,7 +52463,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51475,7 +52498,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51508,7 +52532,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51542,7 +52567,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51575,7 +52601,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51609,7 +52636,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51642,7 +52670,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51675,7 +52704,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51707,7 +52737,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51739,7 +52770,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51835,7 +52867,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51869,7 +52902,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51902,7 +52936,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51936,7 +52971,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51969,7 +53005,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52003,7 +53040,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52036,7 +53074,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52070,7 +53109,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52103,7 +53143,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52136,7 +53177,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52168,7 +53210,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52200,7 +53243,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52296,7 +53340,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52330,7 +53375,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52363,7 +53409,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52397,7 +53444,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52430,7 +53478,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52464,7 +53513,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52497,7 +53547,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52531,7 +53582,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52564,7 +53616,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52597,7 +53650,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52629,7 +53683,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52661,7 +53716,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52757,7 +53813,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -52791,7 +53848,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -52824,7 +53882,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -52858,7 +53917,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -52891,7 +53951,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -52925,7 +53986,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -52958,7 +54020,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -52992,7 +54055,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -53025,7 +54089,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -53058,7 +54123,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -53090,7 +54156,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -53122,7 +54189,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -53218,7 +54286,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53252,7 +54321,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53285,7 +54355,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53319,7 +54390,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53352,7 +54424,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53386,7 +54459,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53419,7 +54493,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53453,7 +54528,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53486,7 +54562,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53519,7 +54596,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53551,7 +54629,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53583,7 +54662,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53679,7 +54759,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53713,7 +54794,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53746,7 +54828,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53780,7 +54863,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53813,7 +54897,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53847,7 +54932,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53880,7 +54966,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53914,7 +55001,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53947,7 +55035,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53980,7 +55069,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -54012,7 +55102,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -54044,7 +55135,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -54140,7 +55232,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54174,7 +55267,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54207,7 +55301,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54241,7 +55336,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54274,7 +55370,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54308,7 +55405,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54341,7 +55439,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54375,7 +55474,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54408,7 +55508,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54441,7 +55542,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54473,7 +55575,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54505,7 +55608,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54601,7 +55705,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -54635,7 +55740,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -54668,7 +55774,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -54702,7 +55809,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -54735,7 +55843,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -54769,7 +55878,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -54802,7 +55912,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -54836,7 +55947,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -54869,7 +55981,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -54902,7 +56015,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -54934,7 +56048,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -54966,7 +56081,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -55076,7 +56192,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55109,7 +56226,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55155,7 +56273,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55187,7 +56306,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55220,7 +56340,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55253,7 +56374,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55285,7 +56407,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55317,7 +56440,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55350,7 +56474,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55396,7 +56521,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55428,7 +56554,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55461,7 +56588,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -55494,7 +56622,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -55526,7 +56655,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -55558,7 +56688,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55591,7 +56722,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55637,7 +56769,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55669,7 +56802,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55702,7 +56836,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -55735,7 +56870,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -55767,7 +56903,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -55799,7 +56936,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55832,7 +56970,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55878,7 +57017,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55910,7 +57050,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55943,7 +57084,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -55976,7 +57118,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56008,7 +57151,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56040,7 +57184,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56073,7 +57218,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56119,7 +57265,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56151,7 +57298,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56184,7 +57332,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56217,7 +57366,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56249,7 +57399,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56281,7 +57432,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56314,7 +57466,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56360,7 +57513,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56392,7 +57546,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56425,7 +57580,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -56458,7 +57614,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -56490,7 +57647,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -56522,7 +57680,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56555,7 +57714,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56601,7 +57761,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56633,7 +57794,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56666,7 +57828,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -56699,7 +57862,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -56731,7 +57895,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -56763,7 +57928,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56796,7 +57962,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56842,7 +58009,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56874,7 +58042,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -56907,7 +58076,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -56940,7 +58110,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -56972,7 +58143,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -57004,7 +58176,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57037,7 +58210,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57083,7 +58257,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57115,7 +58290,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57148,7 +58324,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57181,7 +58358,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57213,7 +58391,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57245,7 +58424,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57278,7 +58458,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57324,7 +58505,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57356,7 +58538,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57389,7 +58572,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57422,7 +58606,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57454,7 +58639,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57486,7 +58672,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57519,7 +58706,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57565,7 +58753,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57597,7 +58786,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57630,7 +58820,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57663,7 +58854,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57695,7 +58887,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57727,7 +58920,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57760,7 +58954,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57806,7 +59001,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57838,7 +59034,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57871,7 +59068,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -57904,7 +59102,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -57936,7 +59135,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -58032,7 +59232,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -58066,7 +59267,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -58099,7 +59301,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -58133,7 +59336,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -58166,7 +59370,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -58200,7 +59405,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -58233,7 +59439,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -58267,7 +59474,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -58300,7 +59508,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -58333,7 +59542,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -58365,7 +59575,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -58397,7 +59608,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -58493,7 +59705,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -58527,7 +59740,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -58560,7 +59774,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -58594,7 +59809,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -58627,7 +59843,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -58661,7 +59878,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -58694,7 +59912,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -58728,7 +59947,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -58761,7 +59981,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -58794,7 +60015,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -58826,7 +60048,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -58858,7 +60081,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -58954,7 +60178,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -58988,7 +60213,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -59021,7 +60247,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -59055,7 +60282,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -59088,7 +60316,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -59122,7 +60351,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -59155,7 +60385,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -59189,7 +60420,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -59222,7 +60454,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -59255,7 +60488,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -59287,7 +60521,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -59319,7 +60554,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -59415,7 +60651,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -59449,7 +60686,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -59482,7 +60720,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -59516,7 +60755,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -59549,7 +60789,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -59583,7 +60824,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -59616,7 +60858,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -59650,7 +60893,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -59683,7 +60927,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -59716,7 +60961,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -59748,7 +60994,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -59780,7 +61027,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -59876,7 +61124,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -59910,7 +61159,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -59943,7 +61193,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -59977,7 +61228,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60010,7 +61262,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60044,7 +61297,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60077,7 +61331,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60111,7 +61366,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60144,7 +61400,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60177,7 +61434,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60209,7 +61467,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60241,7 +61500,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60337,7 +61597,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60371,7 +61632,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60404,7 +61666,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60438,7 +61701,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60471,7 +61735,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60505,7 +61770,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60538,7 +61804,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60572,7 +61839,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60605,7 +61873,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60638,7 +61907,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60670,7 +61940,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60702,7 +61973,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60798,7 +62070,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -60832,7 +62105,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -60865,7 +62139,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -60899,7 +62174,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -60932,7 +62208,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -60966,7 +62243,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -60999,7 +62277,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -61033,7 +62312,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -61066,7 +62346,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -61099,7 +62380,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -61131,7 +62413,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -61163,7 +62446,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -61259,7 +62543,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -61293,7 +62578,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -61326,7 +62612,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -61360,7 +62647,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -61393,7 +62681,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -61427,7 +62716,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -61460,7 +62750,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -61494,7 +62785,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -61527,7 +62819,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -61560,7 +62853,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -61592,7 +62886,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -61624,7 +62919,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -61720,7 +63016,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61754,7 +63051,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61787,7 +63085,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61821,7 +63120,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61854,7 +63154,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61888,7 +63189,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61921,7 +63223,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61955,7 +63258,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61988,7 +63292,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62021,7 +63326,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62053,7 +63359,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62085,7 +63392,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62181,7 +63489,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62215,7 +63524,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62248,7 +63558,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62282,7 +63593,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62315,7 +63627,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62349,7 +63662,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62382,7 +63696,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62416,7 +63731,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62449,7 +63765,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62482,7 +63799,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62514,7 +63832,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62546,7 +63865,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62642,7 +63962,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62676,7 +63997,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62709,7 +64031,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62743,7 +64066,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62776,7 +64100,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62810,7 +64135,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62843,7 +64169,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62877,7 +64204,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62910,7 +64238,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62943,7 +64272,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62975,7 +64305,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63007,7 +64338,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63103,7 +64435,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -63137,7 +64470,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -63170,7 +64504,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -63204,7 +64539,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -63237,7 +64573,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -63271,7 +64608,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -63304,7 +64642,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -63338,7 +64677,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -63371,7 +64711,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -63404,7 +64745,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -63436,7 +64778,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -63468,7 +64811,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -63720,6 +65064,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "100 mM ABC in MS grade water, volume per well", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[733c9cdf62][Flex_S_v2_20_8_None_PARTIAL_COLUMN_HappyPath].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[733c9cdf62][Flex_S_v2_20_8_None_PARTIAL_COLUMN_HappyPath].json new file mode 100644 index 00000000000..a79c72e6781 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[733c9cdf62][Flex_S_v2_20_8_None_PARTIAL_COLUMN_HappyPath].json @@ -0,0 +1,5071 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadModule", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8511b05ba5565bf0e6dcccd800e2ee23", + "notes": [], + "params": { + "location": { + "slotName": "B1" + }, + "model": "thermocyclerModuleV2" + }, + "result": { + "definition": { + "calibrationPoint": { + "x": 14.4, + "y": 64.93, + "z": 97.8 + }, + "compatibleWith": [], + "dimensions": { + "bareOverallHeight": 108.96, + "lidHeight": 61.7, + "overLabwareHeight": 0.0 + }, + "displayName": "Thermocycler Module GEN2", + "gripperOffsets": { + "default": { + "dropOffset": { + "x": 0.0, + "y": 0.0, + "z": 5.6 + }, + "pickUpOffset": { + "x": 0.0, + "y": 0.0, + "z": 4.6 + } + } + }, + "labwareOffset": { + "x": 0.0, + "y": 68.8, + "z": 108.96 + }, + "model": "thermocyclerModuleV2", + "moduleType": "thermocyclerModuleType", + "otSharedSchema": "module/schemas/2", + "quirks": [], + "slotTransforms": { + "ot3_standard": { + "B1": { + "cornerOffsetFromSlot": [ + [ + -98, + 0, + 0, + 1 + ], + [ + -20.005, + 0, + 0, + 1 + ], + [ + -0.84, + 0, + 0, + 1 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + "labwareOffset": [ + [ + -98, + 0, + 0, + 1 + ], + [ + -20.005, + 0, + 0, + 1 + ], + [ + -0.84, + 0, + 0, + 1 + ], + [ + 0, + 0, + 0, + 1 + ] + ] + } + } + } + }, + "model": "thermocyclerModuleV2", + "moduleId": "UUID", + "serialNumber": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b9563ff54d4bfe61c469a7da06e79f42", + "notes": [], + "params": { + "displayName": "Partial Tip Rack", + "loadName": "opentrons_flex_96_tiprack_50ul", + "location": { + "slotName": "B3" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 50 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_50ul", + "quirks": [], + "tipLength": 57.9, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2763353e564ce1df96aeccf789058132", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "right", + "pipetteName": "p50_multi_flex", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2a57aa0e5f1e286b479c11d95e2cce35", + "notes": [], + "params": { + "configurationParams": { + "backLeftNozzle": "B1", + "frontRightNozzle": "H1", + "primaryNozzle": "H1", + "style": "QUADRANT" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "56a999b1f9d1f98d65c52b72ad11551a", + "notes": [], + "params": { + "displayName": "B2 Source Labware", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0351ff93d36efb26af2690af0d9c18da", + "notes": [], + "params": { + "displayName": "C2 Destination Labware", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0f43da3e882dcc90adbc877804eaffe6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 342.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cc5f9f6546e3f07395b5d6c85dc1aafc", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 223.38, + "y": 288.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3d49cc97271f412432aa850ffebabbef", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 223.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d30ce65d2285b1859dc18e1b6fe76faf", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 509.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d1ec4afd293e8644a5ad7882a4d69184", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d52c4ef3d0cd067951e7173ed2637ece", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G2" + }, + "result": { + "position": { + "x": 351.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "touchTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "58c371d1cc2f6dc4560763aa55a1e5e9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "radius": 1.0, + "speed": 60.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -1.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 178.38, + "y": 279.24, + "z": 14.7 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ac8bc689f5418cf36ea82bbf47844eef", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 359.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b8d14201243d718803419b98d5440021", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "18dda871b2b4261ef86950c47f111601", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G3" + }, + "result": { + "position": { + "x": 360.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "67d798e0e456b00d20dbef680a99f6eb", + "notes": [], + "params": { + "axes": [ + "rightPlunger", + "rightZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "881b99e5f41141cc07951c770058ea05", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 509.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "db7943deaf147c1de6aa8117fb8d63a1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "239037161f9a2de7216a7acba49bcff0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G4" + }, + "result": { + "position": { + "x": 369.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "230db894fdbb892ae3f39783462461c8", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c0feef97ca84c32f45401db87d05abbb", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4d99a8ac641ea0a85e7b01ba99a1ec5f", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3e1e5a70e87c15f46dc5a603f36f2f69", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0b08807727836dedb8213e2578018760", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cf6d84ea92c521749b113f12d2de23b2", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "802cf26e8622de7e0e56831e1e1aa9e6", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e8c57d0e8cd7e873e445e4d2719402c7", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "40534f69ecb65ebde84f9c02f8a89091", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a743f98a6f337d93f3dd45655c72cdf3", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dd3a4d714afac8767f22a6090c33da72", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f1b2f79bd838c6eb807902a1df314b3e", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a4b55b4d8a3a90086c6065cccaca1e2f", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a78a1b3b71268b80b8f88f2aabb5cd4e", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "86339a29798c93069dd73bbf7385869e", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "43090a02ca924a2114b5f5fdef66352a", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "181398c9b8867f97df69a15db2c2a739", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "518814c4283a01e0eba14fcadbff229f", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "592f0519806b1da6fd13314b9723adcc", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f2187dba5ee11e9414dfcd9d20f4b4fb", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "658e0f8e124681aa7fb0541ee186f1cd", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7d034866f22501788bd95a0d2c1e48da", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6a85ffbd375379e2dae09152dc75adb9", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "prepareToAspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "999ad712dcc9e8b37111110b86162146", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c3e83b0e29a4363ad235a866192b5f50", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e5c948a6a91f21f8c9589cc15e9e07a0", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e9f585bb4513553294ed54d2f517989b", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a64b1b0d0856efc0d1dc8cad94c4c113", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 359.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cab6fcfb11256dc0fc9f44ec3a6a6e48", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [], + "files": [ + { + "name": "Flex_S_v2_20_8_None_PARTIAL_COLUMN_HappyPath.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_50ul/1", + "displayName": "Partial Tip Rack", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_50ul", + "location": { + "slotName": "B3" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "displayName": "B2 Source Labware", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "B2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "displayName": "C2 Destination Labware", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C2" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "Tip Rack South Clearance for the 8 Channel pipette and a SINGLE partial tip configuration.", + "protocolName": "8 Channel PARTIAL_COLUMN Happy Path A1 or H1" + }, + "modules": [ + { + "id": "UUID", + "location": { + "slotName": "B1" + }, + "model": "thermocyclerModuleV2", + "serialNumber": "UUID" + } + ], + "pipettes": [ + { + "id": "UUID", + "mount": "right", + "pipetteName": "p50_multi_flex" + } + ], + "result": "ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7583faec7c][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_return_tip_error].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7583faec7c][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_return_tip_error].json new file mode 100644 index 00000000000..3a2911f043d --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7583faec7c][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_return_tip_error].json @@ -0,0 +1,4940 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_20ul", + "location": { + "slotName": "1" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-10ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.69 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 20 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_20ul", + "tipLength": 39.2, + "tipOverlap": 8.25 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 74.24, + "z": 25.49 + }, + "A10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 74.24, + "z": 25.49 + }, + "A11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 74.24, + "z": 25.49 + }, + "A12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 74.24, + "z": 25.49 + }, + "A2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 74.24, + "z": 25.49 + }, + "A3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 74.24, + "z": 25.49 + }, + "A4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 74.24, + "z": 25.49 + }, + "A5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 74.24, + "z": 25.49 + }, + "A6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 74.24, + "z": 25.49 + }, + "A7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 74.24, + "z": 25.49 + }, + "A8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 74.24, + "z": 25.49 + }, + "A9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 74.24, + "z": 25.49 + }, + "B1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 65.24, + "z": 25.49 + }, + "B10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 65.24, + "z": 25.49 + }, + "B11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 65.24, + "z": 25.49 + }, + "B12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 65.24, + "z": 25.49 + }, + "B2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 65.24, + "z": 25.49 + }, + "B3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 65.24, + "z": 25.49 + }, + "B4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 65.24, + "z": 25.49 + }, + "B5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 65.24, + "z": 25.49 + }, + "B6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 65.24, + "z": 25.49 + }, + "B7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 65.24, + "z": 25.49 + }, + "B8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 65.24, + "z": 25.49 + }, + "B9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 65.24, + "z": 25.49 + }, + "C1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 56.24, + "z": 25.49 + }, + "C10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 56.24, + "z": 25.49 + }, + "C11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 56.24, + "z": 25.49 + }, + "C12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 56.24, + "z": 25.49 + }, + "C2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 56.24, + "z": 25.49 + }, + "C3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 56.24, + "z": 25.49 + }, + "C4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 56.24, + "z": 25.49 + }, + "C5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 56.24, + "z": 25.49 + }, + "C6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 56.24, + "z": 25.49 + }, + "C7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 56.24, + "z": 25.49 + }, + "C8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 56.24, + "z": 25.49 + }, + "C9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 56.24, + "z": 25.49 + }, + "D1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 47.24, + "z": 25.49 + }, + "D10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 47.24, + "z": 25.49 + }, + "D11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 47.24, + "z": 25.49 + }, + "D12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 47.24, + "z": 25.49 + }, + "D2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 47.24, + "z": 25.49 + }, + "D3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 47.24, + "z": 25.49 + }, + "D4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 47.24, + "z": 25.49 + }, + "D5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 47.24, + "z": 25.49 + }, + "D6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 47.24, + "z": 25.49 + }, + "D7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 47.24, + "z": 25.49 + }, + "D8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 47.24, + "z": 25.49 + }, + "D9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 47.24, + "z": 25.49 + }, + "E1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 38.24, + "z": 25.49 + }, + "E10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 38.24, + "z": 25.49 + }, + "E11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 38.24, + "z": 25.49 + }, + "E12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 38.24, + "z": 25.49 + }, + "E2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 38.24, + "z": 25.49 + }, + "E3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 38.24, + "z": 25.49 + }, + "E4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 38.24, + "z": 25.49 + }, + "E5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 38.24, + "z": 25.49 + }, + "E6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 38.24, + "z": 25.49 + }, + "E7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 38.24, + "z": 25.49 + }, + "E8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 38.24, + "z": 25.49 + }, + "E9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 38.24, + "z": 25.49 + }, + "F1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 29.24, + "z": 25.49 + }, + "F10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 29.24, + "z": 25.49 + }, + "F11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 29.24, + "z": 25.49 + }, + "F12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 29.24, + "z": 25.49 + }, + "F2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 29.24, + "z": 25.49 + }, + "F3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 29.24, + "z": 25.49 + }, + "F4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 29.24, + "z": 25.49 + }, + "F5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 29.24, + "z": 25.49 + }, + "F6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 29.24, + "z": 25.49 + }, + "F7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 29.24, + "z": 25.49 + }, + "F8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 29.24, + "z": 25.49 + }, + "F9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 29.24, + "z": 25.49 + }, + "G1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 20.24, + "z": 25.49 + }, + "G10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 20.24, + "z": 25.49 + }, + "G11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 20.24, + "z": 25.49 + }, + "G12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 20.24, + "z": 25.49 + }, + "G2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 20.24, + "z": 25.49 + }, + "G3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 20.24, + "z": 25.49 + }, + "G4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 20.24, + "z": 25.49 + }, + "G5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 20.24, + "z": 25.49 + }, + "G6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 20.24, + "z": 25.49 + }, + "G7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 20.24, + "z": 25.49 + }, + "G8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 20.24, + "z": 25.49 + }, + "G9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 20.24, + "z": 25.49 + }, + "H1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 11.24, + "z": 25.49 + }, + "H10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 11.24, + "z": 25.49 + }, + "H11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 11.24, + "z": 25.49 + }, + "H12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 11.24, + "z": 25.49 + }, + "H2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 11.24, + "z": 25.49 + }, + "H3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 11.24, + "z": 25.49 + }, + "H4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 11.24, + "z": 25.49 + }, + "H5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 11.24, + "z": 25.49 + }, + "H6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 11.24, + "z": 25.49 + }, + "H7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 11.24, + "z": 25.49 + }, + "H8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 11.24, + "z": 25.49 + }, + "H9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 11.24, + "z": 25.49 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50de88d471ad3910c29207fb6df4502e", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-300ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.49 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 300 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_300ul", + "tipLength": 59.3, + "tipOverlap": 7.47 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 74.24, + "z": 5.39 + }, + "A10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 74.24, + "z": 5.39 + }, + "A11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 74.24, + "z": 5.39 + }, + "A12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 74.24, + "z": 5.39 + }, + "A2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 74.24, + "z": 5.39 + }, + "A3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 74.24, + "z": 5.39 + }, + "A4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 74.24, + "z": 5.39 + }, + "A5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 74.24, + "z": 5.39 + }, + "A6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 74.24, + "z": 5.39 + }, + "A7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 74.24, + "z": 5.39 + }, + "A8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 74.24, + "z": 5.39 + }, + "A9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 74.24, + "z": 5.39 + }, + "B1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 65.24, + "z": 5.39 + }, + "B10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 65.24, + "z": 5.39 + }, + "B11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 65.24, + "z": 5.39 + }, + "B12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 65.24, + "z": 5.39 + }, + "B2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 65.24, + "z": 5.39 + }, + "B3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 65.24, + "z": 5.39 + }, + "B4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 65.24, + "z": 5.39 + }, + "B5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 65.24, + "z": 5.39 + }, + "B6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 65.24, + "z": 5.39 + }, + "B7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 65.24, + "z": 5.39 + }, + "B8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 65.24, + "z": 5.39 + }, + "B9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 65.24, + "z": 5.39 + }, + "C1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 56.24, + "z": 5.39 + }, + "C10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 56.24, + "z": 5.39 + }, + "C11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 56.24, + "z": 5.39 + }, + "C12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 56.24, + "z": 5.39 + }, + "C2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 56.24, + "z": 5.39 + }, + "C3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 56.24, + "z": 5.39 + }, + "C4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 56.24, + "z": 5.39 + }, + "C5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 56.24, + "z": 5.39 + }, + "C6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 56.24, + "z": 5.39 + }, + "C7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 56.24, + "z": 5.39 + }, + "C8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 56.24, + "z": 5.39 + }, + "C9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 56.24, + "z": 5.39 + }, + "D1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 47.24, + "z": 5.39 + }, + "D10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 47.24, + "z": 5.39 + }, + "D11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 47.24, + "z": 5.39 + }, + "D12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 47.24, + "z": 5.39 + }, + "D2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 47.24, + "z": 5.39 + }, + "D3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 47.24, + "z": 5.39 + }, + "D4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 47.24, + "z": 5.39 + }, + "D5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 47.24, + "z": 5.39 + }, + "D6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 47.24, + "z": 5.39 + }, + "D7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 47.24, + "z": 5.39 + }, + "D8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 47.24, + "z": 5.39 + }, + "D9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 47.24, + "z": 5.39 + }, + "E1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 38.24, + "z": 5.39 + }, + "E10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 38.24, + "z": 5.39 + }, + "E11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 38.24, + "z": 5.39 + }, + "E12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 38.24, + "z": 5.39 + }, + "E2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 38.24, + "z": 5.39 + }, + "E3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 38.24, + "z": 5.39 + }, + "E4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 38.24, + "z": 5.39 + }, + "E5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 38.24, + "z": 5.39 + }, + "E6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 38.24, + "z": 5.39 + }, + "E7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 38.24, + "z": 5.39 + }, + "E8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 38.24, + "z": 5.39 + }, + "E9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 38.24, + "z": 5.39 + }, + "F1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 29.24, + "z": 5.39 + }, + "F10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 29.24, + "z": 5.39 + }, + "F11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 29.24, + "z": 5.39 + }, + "F12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 29.24, + "z": 5.39 + }, + "F2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 29.24, + "z": 5.39 + }, + "F3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 29.24, + "z": 5.39 + }, + "F4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 29.24, + "z": 5.39 + }, + "F5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 29.24, + "z": 5.39 + }, + "F6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 29.24, + "z": 5.39 + }, + "F7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 29.24, + "z": 5.39 + }, + "F8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 29.24, + "z": 5.39 + }, + "F9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 29.24, + "z": 5.39 + }, + "G1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 20.24, + "z": 5.39 + }, + "G10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 20.24, + "z": 5.39 + }, + "G11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 20.24, + "z": 5.39 + }, + "G12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 20.24, + "z": 5.39 + }, + "G2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 20.24, + "z": 5.39 + }, + "G3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 20.24, + "z": 5.39 + }, + "G4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 20.24, + "z": 5.39 + }, + "G5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 20.24, + "z": 5.39 + }, + "G6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 20.24, + "z": 5.39 + }, + "G7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 20.24, + "z": 5.39 + }, + "G8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 20.24, + "z": 5.39 + }, + "G9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 20.24, + "z": 5.39 + }, + "H1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 11.24, + "z": 5.39 + }, + "H10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 11.24, + "z": 5.39 + }, + "H11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 11.24, + "z": 5.39 + }, + "H12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 11.24, + "z": 5.39 + }, + "H2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 11.24, + "z": 5.39 + }, + "H3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 11.24, + "z": 5.39 + }, + "H4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 11.24, + "z": 5.39 + }, + "H5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 11.24, + "z": 5.39 + }, + "H6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 11.24, + "z": 5.39 + }, + "H7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 11.24, + "z": 5.39 + }, + "H8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 11.24, + "z": 5.39 + }, + "H9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 11.24, + "z": 5.39 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "54fddab1eb921f6e1d3aa7e96d2ac307", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p20_multi_gen2", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4b1d27a6f17f312dd76668f0c48ed406", + "notes": [], + "params": { + "configurationParams": { + "backLeftNozzle": "G1", + "frontRightNozzle": "H1", + "primaryNozzle": "H1", + "style": "QUADRANT" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8f1105742fe19ad1e0b7c436509543f3", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "4" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "51917f4fe2c45a5895dd30e853f4b689", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "5" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bb4e42ce3a347009d85098cb49c54446", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 14.38, + "y": 65.24, + "z": 64.69 + }, + "tipDiameter": 3.27, + "tipLength": 30.950000000000003, + "tipVolume": 20.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3e2d6ccf28ac1864869f3fba1e6c918c", + "notes": [], + "params": { + "message": "Tip rack in 1, well A1 has tip: False" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "adb87a628b0270e6b7ea03956c308caf", + "notes": [], + "params": { + "message": "Tip rack in 1, well B1 has tip: False" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d52723fce284549fc5363df14c8af77b", + "notes": [], + "params": { + "message": "Tip rack in 1, well C1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1eb8c634a559f734e5bf0152943ccfc5", + "notes": [], + "params": { + "message": "Tip rack in 1, well D1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3e38d3b5e3cfe9ddb63397a97e876ed4", + "notes": [], + "params": { + "message": "Tip rack in 1, well E1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8c0f1de98d6cbf48fd9e76be1389b807", + "notes": [], + "params": { + "message": "Tip rack in 1, well F1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "560a90fcbfc35c9f895b03c1d49abd29", + "notes": [], + "params": { + "message": "Tip rack in 1, well G1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "eb9fed787fef1871ea9fbc3f506af0e6", + "notes": [], + "params": { + "message": "Tip rack in 1, well H1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "UnexpectedProtocolError [line 146]: Error 4000 GENERAL_ERROR (UnexpectedProtocolError): Cannot return tip to a tiprack while the pipette is configured for partial tip.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Cannot return tip to a tiprack while the pipette is configured for partial tip.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "UnexpectedProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_return_tip_error.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_20ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_20ul", + "location": { + "slotName": "1" + } + }, + { + "definitionUri": "opentrons/opentrons_96_tiprack_300ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "4" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "5" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Invalid tip configs that should error" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p20_multi_gen2" + } + ], + "result": "not-ok", + "robotType": "OT-2 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[770ebdcd29][pl_KAPA_Library_Quant_48_v8].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[770ebdcd29][pl_KAPA_Library_Quant_48_v8].json index cce5f727939..c577f539508 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[770ebdcd29][pl_KAPA_Library_Quant_48_v8].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[770ebdcd29][pl_KAPA_Library_Quant_48_v8].json @@ -12420,7 +12420,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12453,7 +12454,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12486,7 +12488,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -12518,7 +12521,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12549,7 +12553,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12581,7 +12586,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12614,7 +12620,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12647,7 +12654,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -12679,7 +12687,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12710,7 +12719,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12742,7 +12752,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12775,7 +12786,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12808,7 +12820,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -12840,7 +12853,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12871,7 +12885,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14168,7 +14183,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14201,7 +14217,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14234,7 +14251,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14267,7 +14285,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14300,7 +14319,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14333,7 +14353,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14367,7 +14388,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14400,7 +14422,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14434,7 +14457,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14467,7 +14491,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14501,7 +14526,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14534,7 +14560,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14568,7 +14595,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14601,7 +14629,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14635,7 +14664,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14668,7 +14698,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14702,7 +14733,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14735,7 +14767,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14769,7 +14802,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14802,7 +14836,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14836,7 +14871,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14869,7 +14905,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14903,7 +14940,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14936,7 +14974,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14969,7 +15008,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15001,7 +15041,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15097,7 +15138,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15130,7 +15172,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15163,7 +15206,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15196,7 +15240,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15229,7 +15274,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15262,7 +15308,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15296,7 +15343,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15329,7 +15377,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15363,7 +15412,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15396,7 +15446,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15430,7 +15481,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15463,7 +15515,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15497,7 +15550,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15530,7 +15584,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15564,7 +15619,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15597,7 +15653,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15631,7 +15688,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15664,7 +15722,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15698,7 +15757,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15731,7 +15791,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15765,7 +15826,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15798,7 +15860,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15832,7 +15895,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15865,7 +15929,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15898,7 +15963,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15930,7 +15996,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16026,7 +16093,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16059,7 +16127,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16092,7 +16161,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16125,7 +16195,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16158,7 +16229,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16191,7 +16263,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16225,7 +16298,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16258,7 +16332,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16292,7 +16367,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16325,7 +16401,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16359,7 +16436,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16392,7 +16470,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16426,7 +16505,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16459,7 +16539,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16493,7 +16574,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16526,7 +16608,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16560,7 +16643,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16593,7 +16677,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16627,7 +16712,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16660,7 +16746,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16694,7 +16781,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16727,7 +16815,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16761,7 +16850,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16794,7 +16884,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16827,7 +16918,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16859,7 +16951,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16969,7 +17062,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17002,7 +17096,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17035,7 +17130,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17068,7 +17164,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17101,7 +17198,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17134,7 +17232,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17168,7 +17267,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17201,7 +17301,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17235,7 +17336,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17268,7 +17370,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17302,7 +17405,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17335,7 +17439,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17369,7 +17474,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17402,7 +17508,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17436,7 +17543,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17469,7 +17577,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17503,7 +17612,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17536,7 +17646,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17570,7 +17681,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17603,7 +17715,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17637,7 +17750,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17670,7 +17784,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17704,7 +17819,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17737,7 +17853,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17770,7 +17887,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17802,7 +17920,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17898,7 +18017,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17931,7 +18051,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17964,7 +18085,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17997,7 +18119,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18030,7 +18153,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18063,7 +18187,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18097,7 +18222,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18130,7 +18256,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18164,7 +18291,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18197,7 +18325,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18231,7 +18360,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18264,7 +18394,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18298,7 +18429,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18331,7 +18463,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18365,7 +18498,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18398,7 +18532,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18432,7 +18567,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18465,7 +18601,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18499,7 +18636,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18532,7 +18670,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18566,7 +18705,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18599,7 +18739,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18633,7 +18774,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18666,7 +18808,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18699,7 +18842,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18731,7 +18875,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18827,7 +18972,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18860,7 +19006,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18893,7 +19040,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18926,7 +19074,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18959,7 +19108,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -18992,7 +19142,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19026,7 +19177,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19059,7 +19211,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19093,7 +19246,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19126,7 +19280,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19160,7 +19315,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19193,7 +19349,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19227,7 +19384,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19260,7 +19418,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19294,7 +19453,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19327,7 +19487,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19361,7 +19522,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19394,7 +19556,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19428,7 +19591,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19461,7 +19625,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19495,7 +19660,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19528,7 +19694,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19562,7 +19729,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19595,7 +19763,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19628,7 +19797,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19660,7 +19830,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -19798,7 +19969,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19831,7 +20003,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19863,7 +20036,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19908,7 +20082,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19940,7 +20115,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19973,7 +20149,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20005,7 +20182,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20050,7 +20228,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20082,7 +20261,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20115,7 +20295,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20147,7 +20328,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20192,7 +20374,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20224,7 +20407,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20257,7 +20441,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20289,7 +20474,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20334,7 +20520,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20472,7 +20659,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20505,7 +20693,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20538,7 +20727,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20572,7 +20762,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20605,7 +20796,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20639,7 +20831,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20672,7 +20865,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20706,7 +20900,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20739,7 +20934,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20773,7 +20969,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20806,7 +21003,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20839,7 +21037,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20871,7 +21070,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20916,7 +21116,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21054,7 +21255,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -21087,7 +21289,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21120,7 +21323,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21154,7 +21358,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21187,7 +21392,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21221,7 +21427,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21254,7 +21461,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21288,7 +21496,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21321,7 +21530,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21355,7 +21565,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21388,7 +21599,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21421,7 +21633,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21453,7 +21666,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21498,7 +21712,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21636,7 +21851,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21670,7 +21886,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21703,7 +21920,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21737,7 +21955,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21770,7 +21989,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21804,7 +22024,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21837,7 +22058,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21871,7 +22093,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21904,7 +22127,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21937,7 +22161,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21970,7 +22195,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22003,7 +22229,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22049,7 +22276,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22094,7 +22322,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22126,7 +22355,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22172,7 +22402,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22217,7 +22448,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22249,7 +22481,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22295,7 +22528,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -22340,7 +22574,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -22372,7 +22607,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -22418,7 +22654,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -22463,7 +22700,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -22495,7 +22733,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -22606,7 +22845,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22640,7 +22880,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22673,7 +22914,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22707,7 +22949,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22740,7 +22983,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22774,7 +23018,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22807,7 +23052,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22841,7 +23087,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22874,7 +23121,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22907,7 +23155,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22940,7 +23189,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22973,7 +23223,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23019,7 +23270,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23064,7 +23316,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23096,7 +23349,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23142,7 +23396,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23187,7 +23442,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23219,7 +23475,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23265,7 +23522,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -23310,7 +23568,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -23342,7 +23601,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -23388,7 +23648,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -23433,7 +23694,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -23465,7 +23727,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -23576,7 +23839,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23610,7 +23874,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23643,7 +23908,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23677,7 +23943,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23710,7 +23977,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23744,7 +24012,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23777,7 +24046,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23811,7 +24081,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23844,7 +24115,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23877,7 +24149,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23910,7 +24183,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23943,7 +24217,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23989,7 +24264,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24034,7 +24310,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24066,7 +24343,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24112,7 +24390,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24157,7 +24436,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24189,7 +24469,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24235,7 +24516,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -24280,7 +24562,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -24312,7 +24595,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -24358,7 +24642,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -24403,7 +24688,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -24435,7 +24721,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -25733,7 +26020,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25767,7 +26055,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25800,7 +26089,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25834,7 +26124,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25867,7 +26158,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25901,7 +26193,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25934,7 +26227,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25968,7 +26262,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26001,7 +26296,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26034,7 +26330,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26067,7 +26364,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26100,7 +26398,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26146,7 +26445,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -26191,7 +26491,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -26223,7 +26524,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -26269,7 +26571,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -26314,7 +26617,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -26346,7 +26650,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -26392,7 +26697,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -26437,7 +26743,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -26469,7 +26776,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -26515,7 +26823,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -26560,7 +26869,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -26592,7 +26902,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -31618,6 +31929,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Dilution Buffer", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[79e61426a2][Flex_S_v2_18_AMPure_XP_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[79e61426a2][Flex_S_v2_18_AMPure_XP_48x].json index dfde9933d98..00efc4b9178 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[79e61426a2][Flex_S_v2_18_AMPure_XP_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[79e61426a2][Flex_S_v2_18_AMPure_XP_48x].json @@ -6431,7 +6431,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6464,7 +6465,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6572,7 +6574,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -6605,7 +6608,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -6713,7 +6717,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6746,7 +6751,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -8055,7 +8061,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8088,7 +8095,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8121,7 +8129,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8154,7 +8163,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8186,7 +8196,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8217,7 +8228,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8248,7 +8260,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8280,7 +8293,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8312,7 +8326,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8343,7 +8358,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8375,7 +8391,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8407,7 +8424,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8439,7 +8457,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8472,7 +8491,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8504,7 +8524,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8536,7 +8557,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8569,7 +8591,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8601,7 +8624,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8633,7 +8657,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8666,7 +8691,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8698,7 +8724,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8730,7 +8757,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8762,7 +8790,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8793,7 +8822,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8838,7 +8868,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8870,7 +8901,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8901,7 +8933,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8932,7 +8965,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8963,7 +8997,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9070,7 +9105,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9103,7 +9139,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9136,7 +9173,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9169,7 +9207,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9201,7 +9240,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9232,7 +9272,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9263,7 +9304,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9295,7 +9337,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9327,7 +9370,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9358,7 +9402,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9390,7 +9435,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9422,7 +9468,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9454,7 +9501,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9487,7 +9535,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9519,7 +9568,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9551,7 +9601,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9584,7 +9635,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9616,7 +9668,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9648,7 +9701,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9681,7 +9735,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9713,7 +9768,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9745,7 +9801,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9777,7 +9834,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9808,7 +9866,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9853,7 +9912,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9885,7 +9945,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9916,7 +9977,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9947,7 +10009,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9978,7 +10041,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10085,7 +10149,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10118,7 +10183,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10151,7 +10217,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10184,7 +10251,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10216,7 +10284,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10247,7 +10316,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10278,7 +10348,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10310,7 +10381,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10342,7 +10414,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10373,7 +10446,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10405,7 +10479,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10437,7 +10512,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10469,7 +10545,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10502,7 +10579,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10534,7 +10612,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10566,7 +10645,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10599,7 +10679,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10631,7 +10712,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10663,7 +10745,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10696,7 +10779,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10728,7 +10812,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10760,7 +10845,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10792,7 +10878,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10823,7 +10910,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10868,7 +10956,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10900,7 +10989,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10931,7 +11021,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10962,7 +11053,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10993,7 +11085,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11231,7 +11324,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11264,7 +11358,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11297,7 +11392,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11330,7 +11426,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11363,7 +11460,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11396,7 +11494,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11504,7 +11603,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11537,7 +11637,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11570,7 +11671,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11603,7 +11705,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11636,7 +11739,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11669,7 +11773,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11777,7 +11882,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11810,7 +11916,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11843,7 +11950,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11876,7 +11984,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11909,7 +12018,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11942,7 +12052,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12122,7 +12233,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12155,7 +12267,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12188,7 +12301,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12221,7 +12335,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12253,7 +12368,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12284,7 +12400,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12315,7 +12432,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12347,7 +12465,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12379,7 +12498,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12410,7 +12530,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12442,7 +12563,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12474,7 +12596,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12506,7 +12629,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12539,7 +12663,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12571,7 +12696,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12603,7 +12729,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12636,7 +12763,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12668,7 +12796,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12700,7 +12829,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12733,7 +12863,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12765,7 +12896,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12797,7 +12929,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12829,7 +12962,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12860,7 +12994,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12905,7 +13040,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12937,7 +13073,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12968,7 +13105,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12999,7 +13137,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13030,7 +13169,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13137,7 +13277,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13170,7 +13311,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13203,7 +13345,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13236,7 +13379,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13268,7 +13412,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13299,7 +13444,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13330,7 +13476,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13362,7 +13509,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13394,7 +13542,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13425,7 +13574,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13457,7 +13607,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13489,7 +13640,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13521,7 +13673,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13554,7 +13707,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13586,7 +13740,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13618,7 +13773,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13651,7 +13807,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13683,7 +13840,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13715,7 +13873,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13748,7 +13907,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13780,7 +13940,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13812,7 +13973,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13844,7 +14006,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13875,7 +14038,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13920,7 +14084,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13952,7 +14117,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13983,7 +14149,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14014,7 +14181,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14045,7 +14213,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14152,7 +14321,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14185,7 +14355,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14218,7 +14389,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14251,7 +14423,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14283,7 +14456,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14314,7 +14488,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14345,7 +14520,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14377,7 +14553,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14409,7 +14586,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14440,7 +14618,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14472,7 +14651,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14504,7 +14684,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14536,7 +14717,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14569,7 +14751,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14601,7 +14784,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14633,7 +14817,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14666,7 +14851,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14698,7 +14884,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14730,7 +14917,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14763,7 +14951,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14795,7 +14984,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14827,7 +15017,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14859,7 +15050,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14890,7 +15082,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14935,7 +15128,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14967,7 +15161,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14998,7 +15193,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15029,7 +15225,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15060,7 +15257,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15283,7 +15481,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15315,7 +15514,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15361,7 +15561,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15393,7 +15594,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15425,7 +15627,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15471,7 +15674,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15517,7 +15721,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15548,7 +15753,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15579,7 +15785,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15674,7 +15881,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15706,7 +15914,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15752,7 +15961,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15784,7 +15994,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15816,7 +16027,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15862,7 +16074,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15908,7 +16121,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15939,7 +16153,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15970,7 +16185,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -16065,7 +16281,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16097,7 +16314,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16143,7 +16361,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16175,7 +16394,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16207,7 +16427,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16253,7 +16474,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16299,7 +16521,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16330,7 +16553,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16361,7 +16585,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16485,7 +16710,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16517,7 +16743,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16548,7 +16775,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16579,7 +16807,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16610,7 +16839,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16641,7 +16871,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16672,7 +16903,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16704,7 +16936,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16750,7 +16983,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16781,7 +17015,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16812,7 +17047,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16843,7 +17079,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16964,7 +17201,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16996,7 +17234,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17027,7 +17266,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17058,7 +17298,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17089,7 +17330,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17120,7 +17362,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17151,7 +17394,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17183,7 +17427,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17229,7 +17474,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17260,7 +17506,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17291,7 +17538,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17322,7 +17570,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17443,7 +17692,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17475,7 +17725,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17506,7 +17757,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17537,7 +17789,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17568,7 +17821,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17599,7 +17853,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17630,7 +17885,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17662,7 +17918,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17708,7 +17965,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17739,7 +17997,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17770,7 +18029,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17801,7 +18061,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19122,7 +19383,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19154,7 +19416,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19200,7 +19463,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19232,7 +19496,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19264,7 +19529,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19310,7 +19576,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19356,7 +19623,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19387,7 +19655,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19418,7 +19687,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19513,7 +19783,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19545,7 +19816,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19591,7 +19863,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19623,7 +19896,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19655,7 +19929,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19701,7 +19976,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19747,7 +20023,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19778,7 +20055,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19809,7 +20087,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19904,7 +20183,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19936,7 +20216,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19982,7 +20263,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20014,7 +20296,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20046,7 +20329,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20092,7 +20376,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20138,7 +20423,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20169,7 +20455,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20200,7 +20487,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20324,7 +20612,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20356,7 +20645,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20387,7 +20677,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20418,7 +20709,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20449,7 +20741,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20480,7 +20773,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20511,7 +20805,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20543,7 +20838,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20589,7 +20885,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20620,7 +20917,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20651,7 +20949,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20682,7 +20981,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20803,7 +21103,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20835,7 +21136,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20866,7 +21168,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20897,7 +21200,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20928,7 +21232,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20959,7 +21264,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20990,7 +21296,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21022,7 +21329,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21068,7 +21376,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21099,7 +21408,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21130,7 +21440,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21161,7 +21472,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21282,7 +21594,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21314,7 +21627,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21345,7 +21659,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21376,7 +21691,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21407,7 +21723,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21438,7 +21755,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21469,7 +21787,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21501,7 +21820,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21547,7 +21867,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21578,7 +21899,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21609,7 +21931,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21640,7 +21963,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21774,7 +22098,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21806,7 +22131,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21852,7 +22178,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21884,7 +22211,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21916,7 +22244,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21962,7 +22291,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22008,7 +22338,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22039,7 +22370,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22070,7 +22402,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22165,7 +22498,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22197,7 +22531,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22243,7 +22578,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22275,7 +22611,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22307,7 +22644,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22353,7 +22691,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22399,7 +22738,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22430,7 +22770,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22461,7 +22802,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22556,7 +22898,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22588,7 +22931,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22634,7 +22978,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22666,7 +23011,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22698,7 +23044,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22744,7 +23091,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22790,7 +23138,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22821,7 +23170,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22852,7 +23202,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22975,7 +23326,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23007,7 +23359,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23114,7 +23467,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23146,7 +23500,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23253,7 +23608,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23285,7 +23641,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23479,7 +23836,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23511,7 +23869,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23543,7 +23902,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23575,7 +23935,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23607,7 +23968,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23640,7 +24002,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23673,7 +24036,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23706,7 +24070,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23738,7 +24103,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23834,7 +24200,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23866,7 +24233,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23898,7 +24266,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23930,7 +24299,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23962,7 +24332,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23995,7 +24366,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24028,7 +24400,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24061,7 +24434,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24093,7 +24467,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24189,7 +24564,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -24221,7 +24597,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24253,7 +24630,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24285,7 +24663,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24317,7 +24696,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24350,7 +24730,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24383,7 +24764,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24416,7 +24798,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24448,7 +24831,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25861,7 +26245,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25893,7 +26278,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25926,7 +26312,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25958,7 +26345,8 @@ "y": 0.0, "z": -12.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26053,7 +26441,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26085,7 +26474,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26118,7 +26508,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26150,7 +26541,8 @@ "y": 0.0, "z": -12.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26245,7 +26637,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26277,7 +26670,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26310,7 +26704,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26342,7 +26737,8 @@ "y": 0.0, "z": -12.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27817,6 +28213,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "AMPure Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7d06568bfe][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_display_name].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7d06568bfe][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_display_name].json index bd4f009a701..ae9e8d99862 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7d06568bfe][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_display_name].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7d06568bfe][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_display_name].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7e7a90041b][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_west_column].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7e7a90041b][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_west_column].json new file mode 100644 index 00000000000..48077d59118 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7e7a90041b][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_west_column].json @@ -0,0 +1,1501 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-1000ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 97.47 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_1000ul", + "tipLength": 88, + "tipOverlap": 7.95 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.24, + "z": 9.47 + }, + "A10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.24, + "z": 9.47 + }, + "A11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.24, + "z": 9.47 + }, + "A12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.24, + "z": 9.47 + }, + "A2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.24, + "z": 9.47 + }, + "A3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.24, + "z": 9.47 + }, + "A4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.24, + "z": 9.47 + }, + "A5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.24, + "z": 9.47 + }, + "A6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.24, + "z": 9.47 + }, + "A7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.24, + "z": 9.47 + }, + "A8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.24, + "z": 9.47 + }, + "A9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.24, + "z": 9.47 + }, + "B1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.24, + "z": 9.47 + }, + "B10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.24, + "z": 9.47 + }, + "B11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.24, + "z": 9.47 + }, + "B12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.24, + "z": 9.47 + }, + "B2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.24, + "z": 9.47 + }, + "B3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.24, + "z": 9.47 + }, + "B4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.24, + "z": 9.47 + }, + "B5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.24, + "z": 9.47 + }, + "B6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.24, + "z": 9.47 + }, + "B7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.24, + "z": 9.47 + }, + "B8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.24, + "z": 9.47 + }, + "B9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.24, + "z": 9.47 + }, + "C1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.24, + "z": 9.47 + }, + "C10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.24, + "z": 9.47 + }, + "C11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.24, + "z": 9.47 + }, + "C12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.24, + "z": 9.47 + }, + "C2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.24, + "z": 9.47 + }, + "C3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.24, + "z": 9.47 + }, + "C4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.24, + "z": 9.47 + }, + "C5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.24, + "z": 9.47 + }, + "C6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.24, + "z": 9.47 + }, + "C7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.24, + "z": 9.47 + }, + "C8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.24, + "z": 9.47 + }, + "C9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.24, + "z": 9.47 + }, + "D1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.24, + "z": 9.47 + }, + "D10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.24, + "z": 9.47 + }, + "D11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.24, + "z": 9.47 + }, + "D12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.24, + "z": 9.47 + }, + "D2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.24, + "z": 9.47 + }, + "D3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.24, + "z": 9.47 + }, + "D4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.24, + "z": 9.47 + }, + "D5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.24, + "z": 9.47 + }, + "D6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.24, + "z": 9.47 + }, + "D7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.24, + "z": 9.47 + }, + "D8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.24, + "z": 9.47 + }, + "D9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.24, + "z": 9.47 + }, + "E1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.24, + "z": 9.47 + }, + "E10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.24, + "z": 9.47 + }, + "E11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.24, + "z": 9.47 + }, + "E12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.24, + "z": 9.47 + }, + "E2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.24, + "z": 9.47 + }, + "E3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.24, + "z": 9.47 + }, + "E4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.24, + "z": 9.47 + }, + "E5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.24, + "z": 9.47 + }, + "E6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.24, + "z": 9.47 + }, + "E7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.24, + "z": 9.47 + }, + "E8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.24, + "z": 9.47 + }, + "E9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.24, + "z": 9.47 + }, + "F1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.24, + "z": 9.47 + }, + "F10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.24, + "z": 9.47 + }, + "F11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.24, + "z": 9.47 + }, + "F12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.24, + "z": 9.47 + }, + "F2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.24, + "z": 9.47 + }, + "F3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.24, + "z": 9.47 + }, + "F4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.24, + "z": 9.47 + }, + "F5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.24, + "z": 9.47 + }, + "F6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.24, + "z": 9.47 + }, + "F7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.24, + "z": 9.47 + }, + "F8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.24, + "z": 9.47 + }, + "F9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.24, + "z": 9.47 + }, + "G1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.24, + "z": 9.47 + }, + "G10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.24, + "z": 9.47 + }, + "G11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.24, + "z": 9.47 + }, + "G12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.24, + "z": 9.47 + }, + "G2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.24, + "z": 9.47 + }, + "G3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.24, + "z": 9.47 + }, + "G4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.24, + "z": 9.47 + }, + "G5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.24, + "z": 9.47 + }, + "G6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.24, + "z": 9.47 + }, + "G7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.24, + "z": 9.47 + }, + "G8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.24, + "z": 9.47 + }, + "G9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.24, + "z": 9.47 + }, + "H1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.24, + "z": 9.47 + }, + "H10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.24, + "z": 9.47 + }, + "H11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.24, + "z": 9.47 + }, + "H12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.24, + "z": 9.47 + }, + "H2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.24, + "z": 9.47 + }, + "H3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.24, + "z": 9.47 + }, + "H4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.24, + "z": 9.47 + }, + "H5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.24, + "z": 9.47 + }, + "H6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.24, + "z": 9.47 + }, + "H7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.24, + "z": 9.47 + }, + "H8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.24, + "z": 9.47 + }, + "H9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.24, + "z": 9.47 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "COLUMN" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "C3" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/tube-rack-set-1" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 124.35 + }, + "gripperOffsets": {}, + "groups": [ + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352095", + "352096", + "352097", + "352099", + "352196" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 6x15 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A2", + "B1", + "B2", + "C1", + "C2" + ] + }, + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352070", + "352098" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 4x50 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A3", + "A4", + "B3", + "B4" + ] + } + ], + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Opentrons 10 Tube Rack with Falcon 4x50 mL, 6x15 mL Conical", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1" + ], + [ + "A2", + "B2", + "C2" + ], + [ + "A3", + "B3" + ], + [ + "A4", + "B4" + ] + ], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 67.75, + "z": 6.85 + }, + "A2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 67.75, + "z": 6.85 + }, + "A3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 60.25, + "z": 7.3 + }, + "A4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 60.25, + "z": 7.3 + }, + "B1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 42.75, + "z": 6.85 + }, + "B2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 42.75, + "z": 6.85 + }, + "B3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 25.25, + "z": 7.3 + }, + "B4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 25.25, + "z": 7.3 + }, + "C1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 17.75, + "z": 6.85 + }, + "C2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 17.75, + "z": 6.85 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "PartialTipMovementNotAllowedError [line 479]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with A1 nozzle partial configuration will result in collision with items in deck slot C3.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with A1 nozzle partial configuration will result in collision with items in deck slot C3.", + "errorCode": "2004", + "errorInfo": {}, + "errorType": "PartialTipMovementNotAllowedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_west_column.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + } + }, + { + "definitionUri": "opentrons/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical/1", + "id": "UUID", + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "C3" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Too tall labware on pickup tip" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7ebefe4580][pl_QIAseq_FX_24x_Normalizer_Workflow_B].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7ebefe4580][pl_QIAseq_FX_24x_Normalizer_Workflow_B].json index 65235010c43..ac4311409dc 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7ebefe4580][pl_QIAseq_FX_24x_Normalizer_Workflow_B].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7ebefe4580][pl_QIAseq_FX_24x_Normalizer_Workflow_B].json @@ -15986,7 +15986,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16020,7 +16021,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16053,7 +16055,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16086,7 +16089,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16119,7 +16123,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16152,7 +16157,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16185,7 +16191,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16218,7 +16225,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16252,7 +16260,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16285,7 +16294,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16319,7 +16329,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16352,7 +16363,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16385,7 +16397,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16417,7 +16430,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16462,7 +16476,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16570,7 +16585,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16604,7 +16620,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16637,7 +16654,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16670,7 +16688,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16703,7 +16722,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16736,7 +16756,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16769,7 +16790,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16802,7 +16824,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16836,7 +16859,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16869,7 +16893,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16903,7 +16928,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16936,7 +16962,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16969,7 +16996,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17001,7 +17029,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17046,7 +17075,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17154,7 +17184,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17188,7 +17219,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17221,7 +17253,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17254,7 +17287,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17287,7 +17321,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17320,7 +17355,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17353,7 +17389,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17386,7 +17423,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17420,7 +17458,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17453,7 +17492,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17487,7 +17527,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17520,7 +17561,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17553,7 +17595,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17585,7 +17628,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17630,7 +17674,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17879,7 +17924,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17912,7 +17958,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17945,7 +17992,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17978,7 +18026,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18012,7 +18061,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18045,7 +18095,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18079,7 +18130,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18112,7 +18164,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18145,7 +18198,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18177,7 +18231,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18222,7 +18277,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18330,7 +18386,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18363,7 +18420,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -18396,7 +18454,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18429,7 +18488,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18463,7 +18523,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18496,7 +18557,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18530,7 +18592,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18563,7 +18626,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18596,7 +18660,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18628,7 +18693,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18673,7 +18739,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18781,7 +18848,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -18814,7 +18882,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -18847,7 +18916,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18880,7 +18950,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18914,7 +18985,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18947,7 +19019,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18981,7 +19054,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19014,7 +19088,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19047,7 +19122,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19079,7 +19155,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19124,7 +19201,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19246,7 +19324,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19280,7 +19359,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19313,7 +19393,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19346,7 +19427,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19379,7 +19461,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19412,7 +19495,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19444,7 +19528,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19490,7 +19575,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19522,7 +19608,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19554,7 +19641,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19588,7 +19676,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19621,7 +19710,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19655,7 +19745,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19688,7 +19779,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19722,7 +19814,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19755,7 +19848,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19789,7 +19883,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19822,7 +19917,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19856,7 +19952,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19889,7 +19986,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19923,7 +20021,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19956,7 +20055,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19990,7 +20090,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20023,7 +20124,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20057,7 +20159,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20090,7 +20193,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20124,7 +20228,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20157,7 +20262,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20190,7 +20296,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20222,7 +20329,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20267,7 +20375,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20375,7 +20484,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20409,7 +20519,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20442,7 +20553,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20475,7 +20587,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20508,7 +20621,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20541,7 +20655,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20573,7 +20688,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20619,7 +20735,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20651,7 +20768,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20683,7 +20801,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20717,7 +20836,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20750,7 +20870,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20784,7 +20905,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20817,7 +20939,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20851,7 +20974,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20884,7 +21008,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20918,7 +21043,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20951,7 +21077,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20985,7 +21112,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21018,7 +21146,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21052,7 +21181,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21085,7 +21215,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21119,7 +21250,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21152,7 +21284,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21186,7 +21319,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21219,7 +21353,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21253,7 +21388,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21286,7 +21422,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21319,7 +21456,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21351,7 +21489,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21396,7 +21535,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21504,7 +21644,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21538,7 +21679,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21571,7 +21713,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21604,7 +21747,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21637,7 +21781,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21670,7 +21815,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21702,7 +21848,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21748,7 +21895,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21780,7 +21928,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21812,7 +21961,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21846,7 +21996,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21879,7 +22030,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21913,7 +22065,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21946,7 +22099,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21980,7 +22134,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22013,7 +22168,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22047,7 +22203,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22080,7 +22237,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22114,7 +22272,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22147,7 +22306,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22181,7 +22341,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22214,7 +22375,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22248,7 +22410,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22281,7 +22444,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22315,7 +22479,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22348,7 +22513,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22382,7 +22548,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22415,7 +22582,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22448,7 +22616,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22480,7 +22649,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22525,7 +22695,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22770,7 +22941,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22804,7 +22976,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22837,7 +23010,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22871,7 +23045,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22904,7 +23079,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22937,7 +23113,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22970,7 +23147,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23003,7 +23181,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23035,7 +23214,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23066,7 +23246,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23097,7 +23278,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23129,7 +23311,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23175,7 +23358,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23207,7 +23391,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23253,7 +23438,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23285,7 +23471,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23318,7 +23505,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23350,7 +23538,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23382,7 +23571,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23414,7 +23604,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23446,7 +23637,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23479,7 +23671,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23511,7 +23704,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23543,7 +23737,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23576,7 +23771,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23608,7 +23804,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23640,7 +23837,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23673,7 +23871,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23705,7 +23904,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23737,7 +23937,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23769,7 +23970,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23814,7 +24016,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23846,7 +24049,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23877,7 +24081,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23908,7 +24113,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23939,7 +24145,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24047,7 +24254,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24081,7 +24289,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24114,7 +24323,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24148,7 +24358,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24181,7 +24392,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24214,7 +24426,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24247,7 +24460,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24280,7 +24494,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24312,7 +24527,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24343,7 +24559,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24374,7 +24591,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24406,7 +24624,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24452,7 +24671,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24484,7 +24704,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24530,7 +24751,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24562,7 +24784,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24595,7 +24818,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24627,7 +24851,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24659,7 +24884,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24691,7 +24917,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24723,7 +24950,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24756,7 +24984,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24788,7 +25017,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24820,7 +25050,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24853,7 +25084,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24885,7 +25117,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24917,7 +25150,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24950,7 +25184,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24982,7 +25217,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25014,7 +25250,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25046,7 +25283,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25091,7 +25329,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25123,7 +25362,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25154,7 +25394,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25185,7 +25426,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25216,7 +25458,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25324,7 +25567,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25358,7 +25602,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25391,7 +25636,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25425,7 +25671,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25458,7 +25705,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25491,7 +25739,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25524,7 +25773,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25557,7 +25807,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25589,7 +25840,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25620,7 +25872,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25651,7 +25904,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25683,7 +25937,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25729,7 +25984,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25761,7 +26017,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25807,7 +26064,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25839,7 +26097,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25872,7 +26131,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25904,7 +26164,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25936,7 +26197,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25968,7 +26230,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26000,7 +26263,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26033,7 +26297,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26065,7 +26330,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26097,7 +26363,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26130,7 +26397,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26162,7 +26430,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26194,7 +26463,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26227,7 +26497,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26259,7 +26530,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26291,7 +26563,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26323,7 +26596,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26368,7 +26642,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26400,7 +26675,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26431,7 +26707,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26462,7 +26739,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26493,7 +26771,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26731,7 +27010,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26763,7 +27043,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26809,7 +27090,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26841,7 +27123,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26873,7 +27156,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26905,7 +27189,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26937,7 +27222,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26983,7 +27269,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27014,7 +27301,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27045,7 +27333,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27076,7 +27365,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27107,7 +27397,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27214,7 +27505,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27246,7 +27538,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27292,7 +27585,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27324,7 +27618,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27356,7 +27651,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27388,7 +27684,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27420,7 +27717,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27466,7 +27764,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27497,7 +27796,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27528,7 +27828,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27559,7 +27860,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27590,7 +27892,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27697,7 +28000,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27729,7 +28033,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27775,7 +28080,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27807,7 +28113,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27839,7 +28146,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27871,7 +28179,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27903,7 +28212,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27949,7 +28259,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27980,7 +28291,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -28011,7 +28323,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -28042,7 +28355,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -28073,7 +28387,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -28195,7 +28510,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28227,7 +28543,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28258,7 +28575,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28289,7 +28607,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28320,7 +28639,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28352,7 +28672,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28398,7 +28719,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28429,7 +28751,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28460,7 +28783,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28491,7 +28815,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28599,7 +28924,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28631,7 +28957,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28662,7 +28989,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28693,7 +29021,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28724,7 +29053,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28756,7 +29086,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28802,7 +29133,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28833,7 +29165,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28864,7 +29197,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28895,7 +29229,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29003,7 +29338,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29035,7 +29371,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29066,7 +29403,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29097,7 +29435,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29128,7 +29467,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29160,7 +29500,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29206,7 +29547,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29237,7 +29579,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29268,7 +29611,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29299,7 +29643,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29434,7 +29779,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29466,7 +29812,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29512,7 +29859,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29544,7 +29892,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29576,7 +29925,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29608,7 +29958,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29640,7 +29991,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -29686,7 +30038,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -29717,7 +30070,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -29748,7 +30102,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -29779,7 +30134,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -29810,7 +30166,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -29917,7 +30274,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29949,7 +30307,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29995,7 +30354,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30027,7 +30387,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30059,7 +30420,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30091,7 +30453,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30123,7 +30486,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30169,7 +30533,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30200,7 +30565,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30231,7 +30597,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30262,7 +30629,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30293,7 +30661,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30400,7 +30769,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30432,7 +30802,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30478,7 +30849,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30510,7 +30882,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30542,7 +30915,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30574,7 +30948,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30606,7 +30981,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30652,7 +31028,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30683,7 +31060,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30714,7 +31092,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30745,7 +31124,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30776,7 +31156,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30898,7 +31279,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30930,7 +31312,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30961,7 +31344,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30992,7 +31376,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31023,7 +31408,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31055,7 +31441,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31101,7 +31488,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31132,7 +31520,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31163,7 +31552,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31194,7 +31584,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31302,7 +31693,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31334,7 +31726,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31365,7 +31758,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31396,7 +31790,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31427,7 +31822,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31459,7 +31855,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31505,7 +31902,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31536,7 +31934,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31567,7 +31966,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31598,7 +31998,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31706,7 +32107,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31738,7 +32140,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31769,7 +32172,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31800,7 +32204,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31831,7 +32236,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31863,7 +32269,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31909,7 +32316,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31940,7 +32348,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31971,7 +32380,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32002,7 +32412,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32137,7 +32548,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32169,7 +32581,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32215,7 +32628,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32247,7 +32661,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32279,7 +32694,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32311,7 +32727,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32343,7 +32760,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32389,7 +32807,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32420,7 +32839,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32451,7 +32871,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32482,7 +32903,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32513,7 +32935,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32620,7 +33043,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32652,7 +33076,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32698,7 +33123,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32730,7 +33156,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32762,7 +33189,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32794,7 +33222,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32826,7 +33255,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32872,7 +33302,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32903,7 +33334,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32934,7 +33366,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32965,7 +33398,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32996,7 +33430,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33103,7 +33538,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33135,7 +33571,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33181,7 +33618,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33213,7 +33651,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33245,7 +33684,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33277,7 +33717,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33309,7 +33750,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33355,7 +33797,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33386,7 +33829,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33417,7 +33861,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33448,7 +33893,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33479,7 +33925,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33614,7 +34061,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33646,7 +34094,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33754,7 +34203,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33786,7 +34236,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33894,7 +34345,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33926,7 +34378,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -34121,7 +34574,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34153,7 +34607,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34185,7 +34640,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34217,7 +34673,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34325,7 +34782,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34357,7 +34815,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34389,7 +34848,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34421,7 +34881,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34529,7 +34990,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34561,7 +35023,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -34593,7 +35056,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -34625,7 +35089,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -34863,7 +35328,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34895,7 +35361,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34941,7 +35408,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34973,7 +35441,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35006,7 +35475,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -35114,7 +35584,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35146,7 +35617,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35192,7 +35664,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35224,7 +35697,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35257,7 +35731,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35365,7 +35840,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35397,7 +35873,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35443,7 +35920,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35475,7 +35953,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35508,7 +35987,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -35745,7 +36225,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35779,7 +36260,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35812,7 +36294,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35846,7 +36329,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35879,7 +36363,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35912,7 +36397,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35945,7 +36431,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35978,7 +36465,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36010,7 +36498,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36041,7 +36530,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36072,7 +36562,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36104,7 +36595,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36136,7 +36628,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36168,7 +36661,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36200,7 +36694,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36232,7 +36727,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36265,7 +36761,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36297,7 +36794,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36329,7 +36827,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36362,7 +36861,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36394,7 +36894,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36426,7 +36927,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36459,7 +36961,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36491,7 +36994,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36523,7 +37027,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36555,7 +37060,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36600,7 +37106,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36632,7 +37139,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36663,7 +37171,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36694,7 +37203,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36725,7 +37235,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36833,7 +37344,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36867,7 +37379,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36900,7 +37413,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36934,7 +37448,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36967,7 +37482,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37000,7 +37516,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37033,7 +37550,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37066,7 +37584,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37098,7 +37617,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37129,7 +37649,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37160,7 +37681,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37192,7 +37714,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37224,7 +37747,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37256,7 +37780,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37288,7 +37813,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37320,7 +37846,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37353,7 +37880,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37385,7 +37913,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37417,7 +37946,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37450,7 +37980,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37482,7 +38013,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37514,7 +38046,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37547,7 +38080,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37579,7 +38113,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37611,7 +38146,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37643,7 +38179,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37688,7 +38225,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37720,7 +38258,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37751,7 +38290,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37782,7 +38322,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37813,7 +38354,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37921,7 +38463,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37955,7 +38498,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37988,7 +38532,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38022,7 +38567,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38055,7 +38601,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38088,7 +38635,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38121,7 +38669,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38154,7 +38703,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38186,7 +38736,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38217,7 +38768,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38248,7 +38800,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38280,7 +38833,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38312,7 +38866,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38344,7 +38899,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38376,7 +38932,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38408,7 +38965,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38441,7 +38999,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38473,7 +39032,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38505,7 +39065,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38538,7 +39099,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38570,7 +39132,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38602,7 +39165,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38635,7 +39199,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38667,7 +39232,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38699,7 +39265,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38731,7 +39298,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38776,7 +39344,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38808,7 +39377,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38839,7 +39409,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38870,7 +39441,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38901,7 +39473,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -39139,7 +39712,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39171,7 +39745,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39217,7 +39792,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39249,7 +39825,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39281,7 +39858,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39313,7 +39891,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39345,7 +39924,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39391,7 +39971,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39422,7 +40003,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39453,7 +40035,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39484,7 +40067,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39515,7 +40099,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39622,7 +40207,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39654,7 +40240,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39700,7 +40287,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39732,7 +40320,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39764,7 +40353,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39796,7 +40386,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39828,7 +40419,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39874,7 +40466,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39905,7 +40498,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39936,7 +40530,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39967,7 +40562,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39998,7 +40594,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40105,7 +40702,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40137,7 +40735,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40183,7 +40782,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40215,7 +40815,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40247,7 +40848,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40279,7 +40881,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40311,7 +40914,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40357,7 +40961,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40388,7 +40993,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40419,7 +41025,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40450,7 +41057,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40481,7 +41089,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40603,7 +41212,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40635,7 +41245,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40666,7 +41277,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40697,7 +41309,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40728,7 +41341,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40760,7 +41374,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40806,7 +41421,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40837,7 +41453,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40868,7 +41485,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40899,7 +41517,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41007,7 +41626,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41039,7 +41659,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41070,7 +41691,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41101,7 +41723,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41132,7 +41755,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41164,7 +41788,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41210,7 +41835,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41241,7 +41867,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41272,7 +41899,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41303,7 +41931,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41411,7 +42040,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41443,7 +42073,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41474,7 +42105,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41505,7 +42137,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41536,7 +42169,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41568,7 +42202,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41614,7 +42249,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41645,7 +42281,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41676,7 +42313,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41707,7 +42345,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41842,7 +42481,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41874,7 +42514,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41920,7 +42561,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41952,7 +42594,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41984,7 +42627,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42016,7 +42660,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42048,7 +42693,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42094,7 +42740,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42125,7 +42772,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42156,7 +42804,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42187,7 +42836,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42218,7 +42868,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42325,7 +42976,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42357,7 +43009,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42403,7 +43056,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42435,7 +43089,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42467,7 +43122,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42499,7 +43155,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42531,7 +43188,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42577,7 +43235,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42608,7 +43267,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42639,7 +43299,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42670,7 +43331,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42701,7 +43363,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42808,7 +43471,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -42840,7 +43504,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -42886,7 +43551,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -42918,7 +43584,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -42950,7 +43617,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -42982,7 +43650,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -43014,7 +43683,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43060,7 +43730,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43091,7 +43762,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43122,7 +43794,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43153,7 +43826,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43184,7 +43858,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -43320,7 +43995,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43352,7 +44028,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43383,7 +44060,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43414,7 +44092,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43445,7 +44124,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43477,7 +44157,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43523,7 +44204,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43554,7 +44236,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43585,7 +44268,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43616,7 +44300,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43724,7 +44409,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43756,7 +44442,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43787,7 +44474,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43818,7 +44506,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43849,7 +44538,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43881,7 +44571,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43927,7 +44618,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43958,7 +44650,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43989,7 +44682,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44020,7 +44714,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44128,7 +44823,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -44160,7 +44856,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -44191,7 +44888,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -44222,7 +44920,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -44253,7 +44952,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -44285,7 +44985,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44331,7 +45032,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44362,7 +45064,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44393,7 +45096,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44424,7 +45128,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44559,7 +45264,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -44591,7 +45297,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -44637,7 +45344,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -44669,7 +45377,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -44701,7 +45410,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -44733,7 +45443,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -44765,7 +45476,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44811,7 +45523,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44842,7 +45555,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44873,7 +45587,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44904,7 +45619,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44935,7 +45651,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45042,7 +45759,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45074,7 +45792,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45120,7 +45839,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45152,7 +45872,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45184,7 +45905,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45216,7 +45938,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45248,7 +45971,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45294,7 +46018,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45325,7 +46050,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45356,7 +46082,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45387,7 +46114,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45418,7 +46146,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45525,7 +46254,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45557,7 +46287,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45603,7 +46334,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45635,7 +46367,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45667,7 +46400,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45699,7 +46433,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45731,7 +46466,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45777,7 +46513,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45808,7 +46545,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45839,7 +46577,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45870,7 +46609,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45901,7 +46641,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -46036,7 +46777,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -46068,7 +46810,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -46176,7 +46919,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46208,7 +46952,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46316,7 +47061,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -46348,7 +47094,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -46543,7 +47290,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46575,7 +47323,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -46607,7 +47356,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -46639,7 +47389,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -46747,7 +47498,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46779,7 +47531,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46811,7 +47564,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46843,7 +47597,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46951,7 +47706,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46983,7 +47739,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47015,7 +47772,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47047,7 +47805,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47328,7 +48087,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47361,7 +48121,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47407,7 +48168,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47439,7 +48201,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47485,7 +48248,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47517,7 +48281,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47550,7 +48315,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47583,7 +48349,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47617,7 +48384,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47650,7 +48418,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47683,7 +48452,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47792,7 +48562,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47825,7 +48596,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47871,7 +48643,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47903,7 +48676,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47949,7 +48723,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47981,7 +48756,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48014,7 +48790,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48047,7 +48824,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48081,7 +48859,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48114,7 +48893,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48147,7 +48927,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48256,7 +49037,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48289,7 +49071,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48335,7 +49118,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48367,7 +49151,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48413,7 +49198,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48445,7 +49231,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48478,7 +49265,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48511,7 +49299,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48545,7 +49334,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48578,7 +49368,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48611,7 +49402,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48734,7 +49526,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -48768,7 +49561,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -48801,7 +49595,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -48834,7 +49629,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -48867,7 +49663,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -48900,7 +49697,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -48933,7 +49731,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -48967,7 +49766,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49000,7 +49800,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49034,7 +49835,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49067,7 +49869,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49101,7 +49904,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49134,7 +49938,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49168,7 +49973,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49201,7 +50007,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49235,7 +50042,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49268,7 +50076,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49302,7 +50111,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49335,7 +50145,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49369,7 +50180,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49402,7 +50214,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49436,7 +50249,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49469,7 +50283,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49503,7 +50318,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49536,7 +50352,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49569,7 +50386,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49601,7 +50419,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49646,7 +50465,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49754,7 +50574,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49788,7 +50609,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49821,7 +50643,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49854,7 +50677,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49887,7 +50711,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49920,7 +50745,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -49953,7 +50779,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -49987,7 +50814,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50020,7 +50848,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50054,7 +50883,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50087,7 +50917,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50121,7 +50952,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50154,7 +50986,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50188,7 +51021,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50221,7 +51055,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50255,7 +51090,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50288,7 +51124,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50322,7 +51159,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50355,7 +51193,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50389,7 +51228,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50422,7 +51262,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50456,7 +51297,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50489,7 +51331,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50523,7 +51366,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50556,7 +51400,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50589,7 +51434,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50621,7 +51467,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50666,7 +51513,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50774,7 +51622,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -50808,7 +51657,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -50841,7 +51691,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -50874,7 +51725,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -50907,7 +51759,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -50940,7 +51793,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -50973,7 +51827,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51007,7 +51862,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51040,7 +51896,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51074,7 +51931,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51107,7 +51965,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51141,7 +52000,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51174,7 +52034,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51208,7 +52069,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51241,7 +52103,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51275,7 +52138,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51308,7 +52172,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51342,7 +52207,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51375,7 +52241,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51409,7 +52276,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51442,7 +52310,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51476,7 +52345,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51509,7 +52379,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51543,7 +52414,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51576,7 +52448,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51609,7 +52482,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51641,7 +52515,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51686,7 +52561,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52099,7 +52975,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52133,7 +53010,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52166,7 +53044,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52200,7 +53079,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52233,7 +53113,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52266,7 +53147,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52299,7 +53181,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52332,7 +53215,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52364,7 +53248,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52395,7 +53280,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52426,7 +53312,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52458,7 +53345,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52504,7 +53392,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -52536,7 +53425,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -52582,7 +53472,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -52614,7 +53505,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -52647,7 +53539,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52679,7 +53572,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52711,7 +53605,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52743,7 +53638,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52775,7 +53671,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52808,7 +53705,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52840,7 +53738,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52872,7 +53771,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52905,7 +53805,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52937,7 +53838,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -52969,7 +53871,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53002,7 +53905,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53034,7 +53938,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53066,7 +53971,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53098,7 +54004,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53143,7 +54050,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53175,7 +54083,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53206,7 +54115,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53237,7 +54147,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53268,7 +54179,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53376,7 +54288,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53410,7 +54323,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53443,7 +54357,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53477,7 +54392,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53510,7 +54426,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53543,7 +54460,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53576,7 +54494,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53609,7 +54528,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53641,7 +54561,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53672,7 +54593,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53703,7 +54625,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53735,7 +54658,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -53781,7 +54705,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -53813,7 +54738,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -53859,7 +54785,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -53891,7 +54818,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -53924,7 +54852,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -53956,7 +54885,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -53988,7 +54918,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54020,7 +54951,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54052,7 +54984,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54085,7 +55018,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54117,7 +55051,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54149,7 +55084,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54182,7 +55118,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54214,7 +55151,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54246,7 +55184,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54279,7 +55218,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54311,7 +55251,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54343,7 +55284,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54375,7 +55317,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54420,7 +55363,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54452,7 +55396,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54483,7 +55428,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54514,7 +55460,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54545,7 +55492,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -54653,7 +55601,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54687,7 +55636,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54720,7 +55670,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54754,7 +55705,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54787,7 +55739,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54820,7 +55773,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54853,7 +55807,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54886,7 +55841,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54918,7 +55874,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54949,7 +55906,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54980,7 +55938,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -55012,7 +55971,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55058,7 +56018,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -55090,7 +56051,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -55136,7 +56098,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -55168,7 +56131,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -55201,7 +56165,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55233,7 +56198,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55265,7 +56231,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55297,7 +56264,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55329,7 +56297,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55362,7 +56331,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55394,7 +56364,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55426,7 +56397,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55459,7 +56431,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55491,7 +56464,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55523,7 +56497,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55556,7 +56531,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55588,7 +56564,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55620,7 +56597,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55652,7 +56630,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55697,7 +56676,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55729,7 +56709,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55760,7 +56741,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55791,7 +56773,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55822,7 +56805,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56060,7 +57044,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -56092,7 +57077,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -56138,7 +57124,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -56170,7 +57157,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -56202,7 +57190,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -56234,7 +57223,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -56266,7 +57256,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56312,7 +57303,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56343,7 +57335,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56374,7 +57367,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56405,7 +57399,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56436,7 +57431,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56543,7 +57539,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -56575,7 +57572,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -56621,7 +57619,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -56653,7 +57652,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -56685,7 +57685,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -56717,7 +57718,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -56749,7 +57751,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56795,7 +57798,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56826,7 +57830,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56857,7 +57862,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56888,7 +57894,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56919,7 +57926,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57026,7 +58034,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57058,7 +58067,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57104,7 +58114,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57136,7 +58147,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57168,7 +58180,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57200,7 +58213,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57232,7 +58246,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57278,7 +58293,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57309,7 +58325,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57340,7 +58357,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57371,7 +58389,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57402,7 +58421,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57524,7 +58544,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -57556,7 +58577,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -57587,7 +58609,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -57618,7 +58641,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -57649,7 +58673,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -57681,7 +58706,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -57727,7 +58753,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -57758,7 +58785,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -57789,7 +58817,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -57820,7 +58849,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -57928,7 +58958,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -57960,7 +58991,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -57991,7 +59023,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -58022,7 +59055,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -58053,7 +59087,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -58085,7 +59120,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -58131,7 +59167,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -58162,7 +59199,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -58193,7 +59231,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -58224,7 +59263,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -58332,7 +59372,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -58364,7 +59405,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -58395,7 +59437,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -58426,7 +59469,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -58457,7 +59501,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -58489,7 +59534,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -58535,7 +59581,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -58566,7 +59613,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -58597,7 +59645,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -58628,7 +59677,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -58763,7 +59813,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -58795,7 +59846,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -58841,7 +59893,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -58873,7 +59926,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -58905,7 +59959,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -58937,7 +59992,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -58969,7 +60025,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59015,7 +60072,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59046,7 +60104,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59077,7 +60136,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59108,7 +60168,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59139,7 +60200,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59246,7 +60308,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -59278,7 +60341,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -59324,7 +60388,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -59356,7 +60421,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -59388,7 +60454,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -59420,7 +60487,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -59452,7 +60520,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59498,7 +60567,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59529,7 +60599,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59560,7 +60631,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59591,7 +60663,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59622,7 +60695,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59729,7 +60803,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -59761,7 +60836,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -59807,7 +60883,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -59839,7 +60916,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -59871,7 +60949,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -59903,7 +60982,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -59935,7 +61015,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59981,7 +61062,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60012,7 +61094,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60043,7 +61126,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60074,7 +61158,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60105,7 +61190,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60227,7 +61313,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60259,7 +61346,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60290,7 +61378,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60321,7 +61410,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60352,7 +61442,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60384,7 +61475,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -60430,7 +61522,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -60461,7 +61554,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -60492,7 +61586,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -60523,7 +61618,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -60631,7 +61727,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60663,7 +61760,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60694,7 +61792,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60725,7 +61824,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60756,7 +61856,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60788,7 +61889,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -60834,7 +61936,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -60865,7 +61968,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -60896,7 +62000,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -60927,7 +62032,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -61035,7 +62141,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -61067,7 +62174,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -61098,7 +62206,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -61129,7 +62238,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -61160,7 +62270,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -61192,7 +62303,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61238,7 +62350,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61269,7 +62382,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61300,7 +62414,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61331,7 +62446,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61466,7 +62582,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -61498,7 +62615,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -61544,7 +62662,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -61576,7 +62695,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -61608,7 +62728,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -61640,7 +62761,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -61672,7 +62794,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61718,7 +62841,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61749,7 +62873,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61780,7 +62905,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61811,7 +62937,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61842,7 +62969,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61949,7 +63077,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -61981,7 +63110,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -62027,7 +63157,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -62059,7 +63190,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -62091,7 +63223,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -62123,7 +63256,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -62155,7 +63289,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62201,7 +63336,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62232,7 +63368,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62263,7 +63400,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62294,7 +63432,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62325,7 +63464,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62432,7 +63572,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62464,7 +63605,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62510,7 +63652,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62542,7 +63685,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62574,7 +63718,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62606,7 +63751,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62638,7 +63784,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62684,7 +63831,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62715,7 +63863,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62746,7 +63895,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62777,7 +63927,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62808,7 +63959,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62943,7 +64095,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -62975,7 +64128,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63083,7 +64237,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -63115,7 +64270,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -63223,7 +64379,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -63255,7 +64412,8 @@ "y": 0.0, "z": -37.9 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -63450,7 +64608,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63482,7 +64641,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63514,7 +64674,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63546,7 +64707,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63654,7 +64816,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63686,7 +64849,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -63718,7 +64882,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -63750,7 +64915,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -63858,7 +65024,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63890,7 +65057,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -63922,7 +65090,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -63954,7 +65123,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64267,7 +65437,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -64300,7 +65471,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -64333,7 +65505,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -64442,7 +65615,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -64475,7 +65649,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -64508,7 +65683,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -64617,7 +65793,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -64650,7 +65827,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64683,7 +65861,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64806,7 +65985,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -64840,7 +66020,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -64873,7 +66054,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -64907,7 +66089,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -64940,7 +66123,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -64974,7 +66158,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65007,7 +66192,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65041,7 +66227,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65074,7 +66261,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65108,7 +66296,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65141,7 +66330,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65175,7 +66365,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65208,7 +66399,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65242,7 +66434,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65275,7 +66468,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65309,7 +66503,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65342,7 +66537,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65376,7 +66572,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65409,7 +66606,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65442,7 +66640,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65475,7 +66674,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -65508,7 +66708,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -65541,7 +66742,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -65574,7 +66776,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -65696,7 +66899,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -65728,7 +66932,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -65761,7 +66966,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -65793,7 +66999,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -65825,7 +67032,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -65858,7 +67066,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -65966,7 +67175,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -65998,7 +67208,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -66031,7 +67242,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -66063,7 +67275,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -66095,7 +67308,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -66128,7 +67342,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -66250,7 +67465,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -66282,7 +67498,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -66315,7 +67532,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -66347,7 +67565,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -66379,7 +67598,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -66412,7 +67632,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -66606,7 +67827,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -66638,7 +67860,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -66684,7 +67907,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -66716,7 +67940,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -66749,7 +67974,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -66857,7 +68083,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -66889,7 +68116,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -66935,7 +68163,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -66967,7 +68196,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -67000,7 +68230,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -67108,7 +68339,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -67140,7 +68372,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -67186,7 +68419,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -67218,7 +68452,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -67251,7 +68486,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -67374,7 +68610,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -67407,7 +68644,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -67440,7 +68678,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -67549,7 +68788,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -67582,7 +68822,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -67615,7 +68856,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -67724,7 +68966,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -67757,7 +69000,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -67790,7 +69034,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -67984,7 +69229,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68016,7 +69262,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68062,7 +69309,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68094,7 +69342,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68126,7 +69375,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68158,7 +69408,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68190,7 +69441,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68236,7 +69488,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68267,7 +69520,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68298,7 +69552,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68329,7 +69584,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68360,7 +69616,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68467,7 +69724,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -68499,7 +69757,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -68545,7 +69804,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -68577,7 +69837,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -68609,7 +69870,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -68641,7 +69903,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -68673,7 +69936,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68719,7 +69983,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68750,7 +70015,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68781,7 +70047,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68812,7 +70079,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68843,7 +70111,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -68950,7 +70219,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -68982,7 +70252,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -69028,7 +70299,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -69060,7 +70332,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -69092,7 +70365,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -69124,7 +70398,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -69156,7 +70431,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -69202,7 +70478,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -69233,7 +70510,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -69264,7 +70542,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -69295,7 +70574,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -69326,7 +70606,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -69506,7 +70787,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -69539,7 +70821,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -69572,7 +70855,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -69681,7 +70965,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -69714,7 +70999,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -69747,7 +71033,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -69856,7 +71143,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -69889,7 +71177,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -69922,7 +71211,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -70102,7 +71392,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70134,7 +71425,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70180,7 +71472,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70212,7 +71505,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70244,7 +71538,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70276,7 +71571,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70308,7 +71604,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70354,7 +71651,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70385,7 +71683,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70416,7 +71715,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70447,7 +71747,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70478,7 +71779,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70585,7 +71887,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -70617,7 +71920,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -70663,7 +71967,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -70695,7 +72000,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -70727,7 +72033,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -70759,7 +72066,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -70791,7 +72099,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70837,7 +72146,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70868,7 +72178,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70899,7 +72210,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70930,7 +72242,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -70961,7 +72274,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71068,7 +72382,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -71100,7 +72415,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -71146,7 +72462,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -71178,7 +72495,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -71210,7 +72528,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -71242,7 +72561,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -71274,7 +72594,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71320,7 +72641,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71351,7 +72673,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71382,7 +72705,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71413,7 +72737,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71444,7 +72769,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71565,7 +72891,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71597,7 +72924,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71643,7 +72971,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71675,7 +73004,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71707,7 +73037,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71739,7 +73070,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71771,7 +73103,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71817,7 +73150,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71848,7 +73182,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71879,7 +73214,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71910,7 +73246,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71941,7 +73278,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -72048,7 +73386,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -72080,7 +73419,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -72126,7 +73466,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -72158,7 +73499,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -72190,7 +73532,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -72222,7 +73565,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -72254,7 +73598,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -72300,7 +73645,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -72331,7 +73677,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -72362,7 +73709,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -72393,7 +73741,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -72424,7 +73773,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -72531,7 +73881,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -72563,7 +73914,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -72609,7 +73961,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -72641,7 +73994,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -72673,7 +74027,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -72705,7 +74060,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -72737,7 +74093,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -72783,7 +74140,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -72814,7 +74172,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -72845,7 +74204,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -72876,7 +74236,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -72907,7 +74268,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -73101,7 +74463,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -73133,7 +74496,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -73165,7 +74529,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -73197,7 +74562,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -73305,7 +74671,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -73337,7 +74704,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -73369,7 +74737,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -73401,7 +74770,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -73509,7 +74879,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -73541,7 +74912,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -73573,7 +74945,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -73605,7 +74978,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -73843,7 +75217,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -73875,7 +75250,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -73908,7 +75284,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -74016,7 +75393,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -74048,7 +75426,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -74081,7 +75460,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -74189,7 +75569,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -74221,7 +75602,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -74254,7 +75636,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -74522,6 +75905,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "AMPure Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7f2ef0eaff][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_float_default_no_matching_choices].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7f2ef0eaff][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_float_default_no_matching_choices].json index 0c559ae74b3..235d5eb9fe3 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7f2ef0eaff][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_float_default_no_matching_choices].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[7f2ef0eaff][Flex_X_v2_18_NO_PIPETTES_Overrides_DefaultChoiceNoMatchChoice_Override_float_default_no_matching_choices].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "default choice does not match a choice" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[82e9853b34][Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[82e9853b34][Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2].json index 44584111a12..e0fd663c213 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[82e9853b34][Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[82e9853b34][Flex_X_v2_16_NO_PIPETTES_TrashBinInCol2].json @@ -55,6 +55,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8455adcea9][OT2_S_v2_12_P300M_P20S_FailOnRun].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8455adcea9][OT2_S_v2_12_P300M_P20S_FailOnRun].json index 63aed19f5f3..d4cf07c0f99 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8455adcea9][OT2_S_v2_12_P300M_P20S_FailOnRun].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8455adcea9][OT2_S_v2_12_P300M_P20S_FailOnRun].json @@ -2666,6 +2666,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.12", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[84f684cbc4][Flex_S_v2_18_IDT_xGen_EZ_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[84f684cbc4][Flex_S_v2_18_IDT_xGen_EZ_48x].json index 66e8da7e7f0..2312c3a011e 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[84f684cbc4][Flex_S_v2_18_IDT_xGen_EZ_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[84f684cbc4][Flex_S_v2_18_IDT_xGen_EZ_48x].json @@ -7651,7 +7651,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7685,7 +7686,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7718,7 +7720,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7751,7 +7754,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7784,7 +7788,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7817,7 +7822,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7850,7 +7856,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7883,7 +7890,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7917,7 +7925,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7950,7 +7959,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7984,7 +7994,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8017,7 +8028,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8051,7 +8063,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8084,7 +8097,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8118,7 +8132,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8151,7 +8166,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8185,7 +8201,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8218,7 +8235,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8252,7 +8270,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8285,7 +8304,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8319,7 +8339,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8352,7 +8373,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8386,7 +8408,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8419,7 +8442,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8453,7 +8477,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8486,7 +8511,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8520,7 +8546,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8553,7 +8580,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8587,7 +8615,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8620,7 +8649,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8654,7 +8684,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8687,7 +8718,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8721,7 +8753,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8754,7 +8787,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8788,7 +8822,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8821,7 +8856,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8855,7 +8891,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8888,7 +8925,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8922,7 +8960,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8955,7 +8994,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8989,7 +9029,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9022,7 +9063,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9056,7 +9098,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9089,7 +9132,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9123,7 +9167,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9156,7 +9201,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9190,7 +9236,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9223,7 +9270,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9257,7 +9305,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9290,7 +9339,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9324,7 +9374,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9357,7 +9408,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9391,7 +9443,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9424,7 +9477,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9458,7 +9512,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9491,7 +9546,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9524,7 +9580,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9556,7 +9613,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9601,7 +9659,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9708,7 +9767,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9742,7 +9802,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9775,7 +9836,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9808,7 +9870,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9841,7 +9904,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9874,7 +9938,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9907,7 +9972,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9940,7 +10006,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9974,7 +10041,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10007,7 +10075,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10041,7 +10110,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10074,7 +10144,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10108,7 +10179,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10141,7 +10213,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10175,7 +10248,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10208,7 +10282,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10242,7 +10317,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10275,7 +10351,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10309,7 +10386,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10342,7 +10420,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10376,7 +10455,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10409,7 +10489,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10443,7 +10524,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10476,7 +10558,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10510,7 +10593,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10543,7 +10627,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10577,7 +10662,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10610,7 +10696,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10644,7 +10731,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10677,7 +10765,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10711,7 +10800,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10744,7 +10834,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10778,7 +10869,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10811,7 +10903,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10845,7 +10938,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10878,7 +10972,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10912,7 +11007,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10945,7 +11041,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10979,7 +11076,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11012,7 +11110,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11046,7 +11145,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11079,7 +11179,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11113,7 +11214,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11146,7 +11248,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11180,7 +11283,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11213,7 +11317,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11247,7 +11352,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11280,7 +11386,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11314,7 +11421,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11347,7 +11455,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11381,7 +11490,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11414,7 +11524,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11448,7 +11559,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11481,7 +11593,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11515,7 +11628,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11548,7 +11662,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11581,7 +11696,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11613,7 +11729,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11658,7 +11775,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11765,7 +11883,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11799,7 +11918,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11832,7 +11952,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11865,7 +11986,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11898,7 +12020,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11931,7 +12054,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11964,7 +12088,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11997,7 +12122,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12031,7 +12157,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12064,7 +12191,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12098,7 +12226,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12131,7 +12260,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12165,7 +12295,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12198,7 +12329,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12232,7 +12364,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12265,7 +12398,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12299,7 +12433,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12332,7 +12467,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12366,7 +12502,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12399,7 +12536,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12433,7 +12571,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12466,7 +12605,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12500,7 +12640,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12533,7 +12674,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12567,7 +12709,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12600,7 +12743,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12634,7 +12778,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12667,7 +12812,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12701,7 +12847,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12734,7 +12881,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12768,7 +12916,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12801,7 +12950,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12835,7 +12985,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12868,7 +13019,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12902,7 +13054,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12935,7 +13088,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12969,7 +13123,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13002,7 +13157,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13036,7 +13192,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13069,7 +13226,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13103,7 +13261,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13136,7 +13295,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13170,7 +13330,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13203,7 +13364,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13237,7 +13399,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13270,7 +13433,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13304,7 +13468,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13337,7 +13502,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13371,7 +13537,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13404,7 +13571,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13438,7 +13606,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13471,7 +13640,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13505,7 +13675,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13538,7 +13709,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13572,7 +13744,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13605,7 +13778,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13638,7 +13812,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13670,7 +13845,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13715,7 +13891,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15150,7 +15327,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15184,7 +15362,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15217,7 +15396,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15250,7 +15430,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15283,7 +15464,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15316,7 +15498,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15348,7 +15531,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15394,7 +15578,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15426,7 +15611,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15458,7 +15644,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15492,7 +15679,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15525,7 +15713,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15559,7 +15748,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15592,7 +15782,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15626,7 +15817,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15659,7 +15851,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15693,7 +15886,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15726,7 +15920,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15760,7 +15955,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15793,7 +15989,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15827,7 +16024,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15860,7 +16058,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15894,7 +16093,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15927,7 +16127,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15961,7 +16162,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15994,7 +16196,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16028,7 +16231,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16061,7 +16265,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16095,7 +16300,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16128,7 +16334,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16162,7 +16369,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16195,7 +16403,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16229,7 +16438,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16262,7 +16472,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16296,7 +16507,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16329,7 +16541,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16363,7 +16576,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16396,7 +16610,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16430,7 +16645,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16463,7 +16679,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16497,7 +16714,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16530,7 +16748,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16564,7 +16783,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16597,7 +16817,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16631,7 +16852,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16664,7 +16886,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16698,7 +16921,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16731,7 +16955,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16765,7 +16990,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16798,7 +17024,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16832,7 +17059,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16865,7 +17093,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16899,7 +17128,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16932,7 +17162,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16966,7 +17197,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16999,7 +17231,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17033,7 +17266,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17066,7 +17300,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17100,7 +17335,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17133,7 +17369,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17167,7 +17404,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17200,7 +17438,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17234,7 +17473,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17267,7 +17507,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17301,7 +17542,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17334,7 +17576,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17368,7 +17611,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17401,7 +17645,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17434,7 +17679,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17466,7 +17712,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17511,7 +17758,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17618,7 +17866,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17652,7 +17901,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17685,7 +17935,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17718,7 +17969,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17751,7 +18003,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17784,7 +18037,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17816,7 +18070,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17862,7 +18117,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17894,7 +18150,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17926,7 +18183,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17960,7 +18218,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17993,7 +18252,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18027,7 +18287,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18060,7 +18321,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18094,7 +18356,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18127,7 +18390,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18161,7 +18425,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18194,7 +18459,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18228,7 +18494,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18261,7 +18528,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18295,7 +18563,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18328,7 +18597,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18362,7 +18632,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18395,7 +18666,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18429,7 +18701,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18462,7 +18735,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18496,7 +18770,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18529,7 +18804,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18563,7 +18839,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18596,7 +18873,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18630,7 +18908,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18663,7 +18942,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18697,7 +18977,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18730,7 +19011,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18764,7 +19046,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18797,7 +19080,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18831,7 +19115,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18864,7 +19149,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18898,7 +19184,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18931,7 +19218,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18965,7 +19253,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18998,7 +19287,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19032,7 +19322,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19065,7 +19356,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19099,7 +19391,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19132,7 +19425,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19166,7 +19460,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19199,7 +19494,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19233,7 +19529,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19266,7 +19563,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19300,7 +19598,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19333,7 +19632,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19367,7 +19667,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19400,7 +19701,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19434,7 +19736,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19467,7 +19770,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19501,7 +19805,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19534,7 +19839,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19568,7 +19874,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19601,7 +19908,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19635,7 +19943,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19668,7 +19977,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19702,7 +20012,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19735,7 +20046,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19769,7 +20081,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19802,7 +20115,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19836,7 +20150,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19869,7 +20184,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19902,7 +20218,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19934,7 +20251,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19979,7 +20297,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20086,7 +20405,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20120,7 +20440,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20153,7 +20474,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20186,7 +20508,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20219,7 +20542,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20252,7 +20576,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20284,7 +20609,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20330,7 +20656,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20362,7 +20689,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20394,7 +20722,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20428,7 +20757,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20461,7 +20791,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20495,7 +20826,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20528,7 +20860,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20562,7 +20895,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20595,7 +20929,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20629,7 +20964,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20662,7 +20998,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20696,7 +21033,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20729,7 +21067,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20763,7 +21102,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20796,7 +21136,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20830,7 +21171,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20863,7 +21205,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20897,7 +21240,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20930,7 +21274,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20964,7 +21309,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20997,7 +21343,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21031,7 +21378,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21064,7 +21412,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21098,7 +21447,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21131,7 +21481,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21165,7 +21516,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21198,7 +21550,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21232,7 +21585,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21265,7 +21619,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21299,7 +21654,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21332,7 +21688,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21366,7 +21723,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21399,7 +21757,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21433,7 +21792,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21466,7 +21826,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21500,7 +21861,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21533,7 +21895,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21567,7 +21930,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21600,7 +21964,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21634,7 +21999,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21667,7 +22033,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21701,7 +22068,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21734,7 +22102,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21768,7 +22137,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21801,7 +22171,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21835,7 +22206,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21868,7 +22240,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21902,7 +22275,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21935,7 +22309,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21969,7 +22344,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22002,7 +22378,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22036,7 +22413,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22069,7 +22447,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22103,7 +22482,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22136,7 +22516,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22170,7 +22551,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22203,7 +22585,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22237,7 +22620,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22270,7 +22654,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22304,7 +22689,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22337,7 +22723,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22370,7 +22757,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22402,7 +22790,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22447,7 +22836,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22691,7 +23081,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22725,7 +23116,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22758,7 +23150,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22792,7 +23185,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22825,7 +23219,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22858,7 +23253,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22891,7 +23287,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22924,7 +23321,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22956,7 +23354,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22987,7 +23386,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23018,7 +23418,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23050,7 +23451,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23096,7 +23498,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23128,7 +23531,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23174,7 +23578,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23206,7 +23611,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23239,7 +23645,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23271,7 +23678,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23303,7 +23711,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23335,7 +23744,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23367,7 +23777,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23400,7 +23811,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23432,7 +23844,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23464,7 +23877,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23496,7 +23910,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23541,7 +23956,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23573,7 +23989,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23604,7 +24021,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23635,7 +24053,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23666,7 +24085,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23773,7 +24193,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23807,7 +24228,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23840,7 +24262,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23874,7 +24297,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23907,7 +24331,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23940,7 +24365,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23973,7 +24399,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24006,7 +24433,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24038,7 +24466,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24069,7 +24498,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24100,7 +24530,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24132,7 +24563,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24178,7 +24610,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24210,7 +24643,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24256,7 +24690,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24288,7 +24723,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24321,7 +24757,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24353,7 +24790,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24385,7 +24823,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24417,7 +24856,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24449,7 +24889,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24482,7 +24923,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24514,7 +24956,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24546,7 +24989,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24578,7 +25022,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24623,7 +25068,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24655,7 +25101,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24686,7 +25133,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24717,7 +25165,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24748,7 +25197,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24855,7 +25305,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24889,7 +25340,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24922,7 +25374,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24956,7 +25409,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24989,7 +25443,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25022,7 +25477,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25055,7 +25511,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25088,7 +25545,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25120,7 +25578,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25151,7 +25610,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25182,7 +25642,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25214,7 +25675,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25260,7 +25722,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25292,7 +25755,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25338,7 +25802,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25370,7 +25835,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25403,7 +25869,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25435,7 +25902,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25467,7 +25935,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25499,7 +25968,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25531,7 +26001,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25564,7 +26035,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25596,7 +26068,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25628,7 +26101,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25660,7 +26134,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25705,7 +26180,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25737,7 +26213,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25768,7 +26245,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25799,7 +26277,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25830,7 +26309,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26067,7 +26547,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26099,7 +26580,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26145,7 +26627,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26177,7 +26660,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26209,7 +26693,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26255,7 +26740,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26301,7 +26787,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26332,7 +26819,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26363,7 +26851,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26458,7 +26947,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26490,7 +26980,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26536,7 +27027,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26568,7 +27060,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26600,7 +27093,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26646,7 +27140,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26692,7 +27187,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26723,7 +27219,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26754,7 +27251,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26849,7 +27347,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26881,7 +27380,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26927,7 +27427,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26959,7 +27460,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26991,7 +27493,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27037,7 +27540,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27083,7 +27587,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27114,7 +27619,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27145,7 +27651,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27269,7 +27776,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27301,7 +27809,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27332,7 +27841,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27363,7 +27873,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27394,7 +27905,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27425,7 +27937,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27457,7 +27970,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27503,7 +28017,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27534,7 +28049,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27565,7 +28081,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27596,7 +28113,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27717,7 +28235,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27749,7 +28268,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27780,7 +28300,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27811,7 +28332,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27842,7 +28364,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27873,7 +28396,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27905,7 +28429,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27951,7 +28476,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27982,7 +28508,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28013,7 +28540,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28044,7 +28572,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28165,7 +28694,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28197,7 +28727,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28228,7 +28759,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28259,7 +28791,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28290,7 +28823,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28321,7 +28855,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28353,7 +28888,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28399,7 +28935,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28430,7 +28967,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28461,7 +28999,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28492,7 +29031,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29813,7 +30353,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29845,7 +30386,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29891,7 +30433,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29923,7 +30466,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29955,7 +30499,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29987,7 +30532,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30033,7 +30579,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30079,7 +30626,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30110,7 +30658,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30141,7 +30690,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30172,7 +30722,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30203,7 +30754,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30298,7 +30850,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30330,7 +30883,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30376,7 +30930,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30408,7 +30963,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30440,7 +30996,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30472,7 +31029,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30518,7 +31076,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30564,7 +31123,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30595,7 +31155,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30626,7 +31187,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30657,7 +31219,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30688,7 +31251,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30783,7 +31347,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30815,7 +31380,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30861,7 +31427,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30893,7 +31460,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30925,7 +31493,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30957,7 +31526,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31003,7 +31573,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -31049,7 +31620,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -31080,7 +31652,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -31111,7 +31684,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -31142,7 +31716,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -31173,7 +31748,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -31297,7 +31873,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31329,7 +31906,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31360,7 +31938,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31391,7 +31970,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31422,7 +32002,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31453,7 +32034,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31485,7 +32067,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31531,7 +32114,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31562,7 +32146,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31593,7 +32178,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31624,7 +32210,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31745,7 +32332,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31777,7 +32365,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31808,7 +32397,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31839,7 +32429,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31870,7 +32461,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31901,7 +32493,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31933,7 +32526,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31979,7 +32573,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32010,7 +32605,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32041,7 +32637,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32072,7 +32669,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32193,7 +32791,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -32225,7 +32824,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -32256,7 +32856,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -32287,7 +32888,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -32318,7 +32920,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -32349,7 +32952,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32381,7 +32985,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32427,7 +33032,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32458,7 +33064,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32489,7 +33096,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32520,7 +33128,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32654,7 +33263,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32686,7 +33296,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32732,7 +33343,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32764,7 +33376,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32796,7 +33409,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32828,7 +33442,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32874,7 +33489,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32920,7 +33536,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32951,7 +33568,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32982,7 +33600,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33013,7 +33632,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33044,7 +33664,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33139,7 +33760,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33171,7 +33793,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33217,7 +33840,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33249,7 +33873,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33281,7 +33906,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33313,7 +33939,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33359,7 +33986,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33405,7 +34033,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33436,7 +34065,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33467,7 +34097,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33498,7 +34129,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33529,7 +34161,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33624,7 +34257,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33656,7 +34290,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33702,7 +34337,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33734,7 +34370,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33766,7 +34403,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33798,7 +34436,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33844,7 +34483,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33890,7 +34530,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33921,7 +34562,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33952,7 +34594,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33983,7 +34626,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34014,7 +34658,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34137,7 +34782,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34169,7 +34815,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34216,7 +34863,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34248,7 +34896,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34293,7 +34942,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34324,7 +34974,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34355,7 +35006,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34386,7 +35038,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34481,7 +35134,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34513,7 +35167,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34560,7 +35215,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34592,7 +35248,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34637,7 +35294,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34668,7 +35326,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34699,7 +35358,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34730,7 +35390,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34825,7 +35486,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -34857,7 +35519,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -34904,7 +35567,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34936,7 +35600,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34981,7 +35646,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35012,7 +35678,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35043,7 +35710,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35074,7 +35742,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35256,7 +35925,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35288,7 +35958,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35320,7 +35991,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35353,7 +36025,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35386,7 +36059,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35418,7 +36092,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35514,7 +36189,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35546,7 +36222,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35578,7 +36255,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35611,7 +36289,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35644,7 +36323,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35676,7 +36356,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35772,7 +36453,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35804,7 +36486,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35836,7 +36519,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35869,7 +36553,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35902,7 +36587,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35934,7 +36620,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36160,7 +36847,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36192,7 +36880,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36238,7 +36927,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36270,7 +36960,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36303,7 +36994,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36399,7 +37091,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -36431,7 +37124,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -36477,7 +37171,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -36509,7 +37204,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -36542,7 +37238,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36638,7 +37335,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36670,7 +37368,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36716,7 +37415,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36748,7 +37448,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36781,7 +37482,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36934,7 +37636,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36967,7 +37670,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37000,7 +37704,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37033,7 +37738,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37067,7 +37773,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37100,7 +37807,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37134,7 +37842,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37167,7 +37876,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37200,7 +37910,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37232,7 +37943,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37277,7 +37989,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37384,7 +38097,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -37417,7 +38131,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -37450,7 +38165,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37483,7 +38199,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37517,7 +38234,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37550,7 +38268,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37584,7 +38303,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37617,7 +38337,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37650,7 +38371,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37682,7 +38404,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37727,7 +38450,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37834,7 +38558,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37867,7 +38592,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37900,7 +38626,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37933,7 +38660,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37967,7 +38695,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38000,7 +38729,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38034,7 +38764,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38067,7 +38798,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38100,7 +38832,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38132,7 +38865,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38177,7 +38911,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39485,7 +40220,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39519,7 +40255,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39552,7 +40289,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39585,7 +40323,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39618,7 +40357,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39651,7 +40391,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39684,7 +40425,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39718,7 +40460,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39751,7 +40494,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39785,7 +40529,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39818,7 +40563,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39852,7 +40598,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39885,7 +40632,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39919,7 +40667,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39952,7 +40701,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39986,7 +40736,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40019,7 +40770,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40053,7 +40805,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40086,7 +40839,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40120,7 +40874,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40153,7 +40908,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40187,7 +40943,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40220,7 +40977,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40254,7 +41012,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40287,7 +41046,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40320,7 +41080,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40352,7 +41113,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40397,7 +41159,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40504,7 +41267,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40538,7 +41302,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40571,7 +41336,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40604,7 +41370,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40637,7 +41404,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40670,7 +41438,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40703,7 +41472,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40737,7 +41507,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40770,7 +41541,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40804,7 +41576,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40837,7 +41610,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40871,7 +41645,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40904,7 +41679,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40938,7 +41714,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40971,7 +41748,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41005,7 +41783,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41038,7 +41817,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41072,7 +41852,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41105,7 +41886,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41139,7 +41921,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41172,7 +41955,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41206,7 +41990,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41239,7 +42024,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41273,7 +42059,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41306,7 +42093,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41339,7 +42127,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41371,7 +42160,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41416,7 +42206,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41523,7 +42314,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41557,7 +42349,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41590,7 +42383,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41623,7 +42417,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41656,7 +42451,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41689,7 +42485,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41722,7 +42519,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41756,7 +42554,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41789,7 +42588,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41823,7 +42623,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41856,7 +42657,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41890,7 +42692,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41923,7 +42726,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41957,7 +42761,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41990,7 +42795,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42024,7 +42830,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42057,7 +42864,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42091,7 +42899,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42124,7 +42933,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42158,7 +42968,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42191,7 +43002,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42225,7 +43037,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42258,7 +43071,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42292,7 +43106,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42325,7 +43140,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42358,7 +43174,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42390,7 +43207,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42435,7 +43253,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42823,7 +43642,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42857,7 +43677,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42890,7 +43711,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42924,7 +43746,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42957,7 +43780,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42990,7 +43814,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43023,7 +43848,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43056,7 +43882,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43088,7 +43915,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43119,7 +43947,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43150,7 +43979,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43182,7 +44012,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43228,7 +44059,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43260,7 +44092,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43306,7 +44139,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43338,7 +44172,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43371,7 +44206,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43403,7 +44239,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43435,7 +44272,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43467,7 +44305,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43499,7 +44338,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43532,7 +44372,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43564,7 +44405,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43596,7 +44438,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43628,7 +44471,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43673,7 +44517,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43705,7 +44550,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43736,7 +44582,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43767,7 +44614,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43798,7 +44646,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43905,7 +44754,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43939,7 +44789,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43972,7 +44823,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44006,7 +44858,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44039,7 +44892,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44072,7 +44926,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44105,7 +44960,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44138,7 +44994,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44170,7 +45027,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44201,7 +45059,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44232,7 +45091,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44264,7 +45124,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44310,7 +45171,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44342,7 +45204,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44388,7 +45251,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44420,7 +45284,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44453,7 +45318,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44485,7 +45351,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44517,7 +45384,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44549,7 +45417,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44581,7 +45450,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44614,7 +45484,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44646,7 +45517,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44678,7 +45550,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44710,7 +45583,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44755,7 +45629,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44787,7 +45662,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44818,7 +45694,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44849,7 +45726,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44880,7 +45758,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44987,7 +45866,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45021,7 +45901,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45054,7 +45935,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45088,7 +45970,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45121,7 +46004,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45154,7 +46038,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45187,7 +46072,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45220,7 +46106,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45252,7 +46139,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45283,7 +46171,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45314,7 +46203,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45346,7 +46236,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45392,7 +46283,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45424,7 +46316,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45470,7 +46363,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45502,7 +46396,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45535,7 +46430,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45567,7 +46463,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45599,7 +46496,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45631,7 +46529,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45663,7 +46562,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45696,7 +46596,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45728,7 +46629,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45760,7 +46662,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45792,7 +46695,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45837,7 +46741,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45869,7 +46774,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45900,7 +46806,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45931,7 +46838,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45962,7 +46870,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46199,7 +47108,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46231,7 +47141,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46277,7 +47188,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46309,7 +47221,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46341,7 +47254,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46373,7 +47287,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46419,7 +47334,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46465,7 +47381,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46496,7 +47413,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46527,7 +47445,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46558,7 +47477,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46589,7 +47509,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46684,7 +47605,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46716,7 +47638,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46762,7 +47685,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46794,7 +47718,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46826,7 +47751,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46858,7 +47784,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46904,7 +47831,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46950,7 +47878,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46981,7 +47910,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47012,7 +47942,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47043,7 +47974,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47074,7 +48006,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47169,7 +48102,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -47201,7 +48135,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -47247,7 +48182,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -47279,7 +48215,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -47311,7 +48248,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -47343,7 +48281,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -47389,7 +48328,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47435,7 +48375,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47466,7 +48407,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47497,7 +48439,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47528,7 +48471,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47559,7 +48503,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47683,7 +48628,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47715,7 +48661,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47746,7 +48693,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47777,7 +48725,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47808,7 +48757,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47840,7 +48790,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -47886,7 +48837,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -47917,7 +48869,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -47948,7 +48901,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -47979,7 +48933,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48100,7 +49055,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48132,7 +49088,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48163,7 +49120,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48194,7 +49152,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48225,7 +49184,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48257,7 +49217,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48303,7 +49264,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48334,7 +49296,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48365,7 +49328,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48396,7 +49360,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48517,7 +49482,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48549,7 +49515,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48580,7 +49547,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48611,7 +49579,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48642,7 +49611,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48674,7 +49644,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -48720,7 +49691,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -48751,7 +49723,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -48782,7 +49755,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -48813,7 +49787,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -50134,7 +51109,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -50166,7 +51142,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -50212,7 +51189,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -50244,7 +51222,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -50276,7 +51255,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -50308,7 +51288,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -50354,7 +51335,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50400,7 +51382,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50431,7 +51414,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50462,7 +51446,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50493,7 +51478,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50524,7 +51510,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50619,7 +51606,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -50651,7 +51639,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -50697,7 +51686,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -50729,7 +51719,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -50761,7 +51752,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -50793,7 +51785,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -50839,7 +51832,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50885,7 +51879,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50916,7 +51911,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50947,7 +51943,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50978,7 +51975,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -51009,7 +52007,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -51104,7 +52103,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51136,7 +52136,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51182,7 +52183,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51214,7 +52216,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51246,7 +52249,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51278,7 +52282,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51324,7 +52329,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51370,7 +52376,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51401,7 +52408,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51432,7 +52440,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51463,7 +52472,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51494,7 +52504,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51618,7 +52629,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51650,7 +52662,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51681,7 +52694,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51712,7 +52726,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51743,7 +52758,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51775,7 +52791,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51821,7 +52838,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51852,7 +52870,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51883,7 +52902,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51914,7 +52934,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -52035,7 +53056,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52067,7 +53089,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52098,7 +53121,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52129,7 +53153,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52160,7 +53185,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52192,7 +53218,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52238,7 +53265,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52269,7 +53297,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52300,7 +53329,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52331,7 +53361,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52452,7 +53483,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52484,7 +53516,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52515,7 +53548,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52546,7 +53580,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52577,7 +53612,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52609,7 +53645,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -52655,7 +53692,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -52686,7 +53724,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -52717,7 +53756,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -52748,7 +53788,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -52882,7 +53923,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -52914,7 +53956,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -52960,7 +54003,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -52992,7 +54036,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53024,7 +54069,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53056,7 +54102,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53102,7 +54149,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53148,7 +54196,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53179,7 +54228,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53210,7 +54260,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53241,7 +54292,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53272,7 +54324,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53367,7 +54420,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53399,7 +54453,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53445,7 +54500,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53477,7 +54533,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53509,7 +54566,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53541,7 +54599,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53587,7 +54646,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53633,7 +54693,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53664,7 +54725,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53695,7 +54757,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53726,7 +54789,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53757,7 +54821,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53852,7 +54917,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53884,7 +54950,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53930,7 +54997,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53962,7 +55030,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53994,7 +55063,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54026,7 +55096,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54072,7 +55143,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54118,7 +55190,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54149,7 +55222,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54180,7 +55254,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54211,7 +55286,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54242,7 +55318,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54365,7 +55442,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54397,7 +55475,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54444,7 +55523,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54476,7 +55556,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54521,7 +55602,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54552,7 +55634,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54583,7 +55666,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54614,7 +55698,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54709,7 +55794,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -54741,7 +55827,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -54788,7 +55875,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54820,7 +55908,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54865,7 +55954,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54896,7 +55986,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54927,7 +56018,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54958,7 +56050,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55053,7 +56146,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55085,7 +56179,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55132,7 +56227,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55164,7 +56260,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55209,7 +56306,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55240,7 +56338,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55271,7 +56370,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55302,7 +56402,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56707,7 +57808,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -56739,7 +57841,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56771,7 +57874,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56804,7 +57908,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56837,7 +57942,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56869,7 +57975,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56965,7 +58072,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -56997,7 +58105,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57029,7 +58138,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57062,7 +58172,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57095,7 +58206,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57127,7 +58239,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57223,7 +58336,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -57255,7 +58369,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57287,7 +58402,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57320,7 +58436,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57353,7 +58470,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57385,7 +58503,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57611,7 +58730,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57643,7 +58763,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57689,7 +58810,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57721,7 +58843,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57754,7 +58877,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57850,7 +58974,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57882,7 +59007,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57928,7 +59054,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57960,7 +59087,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57993,7 +59121,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -58089,7 +59218,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58121,7 +59251,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58167,7 +59298,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58199,7 +59331,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58232,7 +59365,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58785,6 +59919,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8860ee702c][OT2_S_v2_14_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8860ee702c][OT2_S_v2_14_P300M_P20S_HS_TC_TM_SmokeTestV3].json index 2c8423641b1..ac524674f7e 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8860ee702c][OT2_S_v2_14_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8860ee702c][OT2_S_v2_14_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -8664,7 +8664,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8697,7 +8698,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -8729,7 +8731,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8761,7 +8764,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8794,7 +8798,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -8826,7 +8831,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8858,7 +8864,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8891,7 +8898,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -8923,7 +8931,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8955,7 +8964,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8988,7 +8998,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -9020,7 +9031,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9052,7 +9064,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9085,7 +9098,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -9117,7 +9131,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9213,7 +9228,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9246,7 +9262,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9279,7 +9296,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9312,7 +9330,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9345,7 +9364,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9378,7 +9398,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9411,7 +9432,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9444,7 +9466,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9477,7 +9500,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9510,7 +9534,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9543,7 +9568,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9574,7 +9600,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9606,7 +9633,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9639,7 +9667,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9672,7 +9701,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9705,7 +9735,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9738,7 +9769,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9771,7 +9803,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9804,7 +9837,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9837,7 +9871,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9870,7 +9905,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9903,7 +9939,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9936,7 +9973,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9967,7 +10005,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9999,7 +10038,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10032,7 +10072,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10065,7 +10106,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10098,7 +10140,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10131,7 +10174,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10164,7 +10208,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10197,7 +10242,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10230,7 +10276,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -10263,7 +10310,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -10296,7 +10344,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -10329,7 +10378,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -10360,7 +10410,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -10392,7 +10443,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10425,7 +10477,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10458,7 +10511,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10491,7 +10545,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10524,7 +10579,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10557,7 +10613,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10590,7 +10647,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10623,7 +10681,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -10656,7 +10715,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -10689,7 +10749,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -10722,7 +10783,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -10753,7 +10815,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -10785,7 +10848,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10818,7 +10882,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10851,7 +10916,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10884,7 +10950,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10917,7 +10984,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10950,7 +11018,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10983,7 +11052,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11016,7 +11086,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -11049,7 +11120,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -11082,7 +11154,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -11115,7 +11188,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -11146,7 +11220,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -11178,7 +11253,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11211,7 +11287,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11244,7 +11321,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11277,7 +11355,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11310,7 +11389,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11343,7 +11423,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11376,7 +11457,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11409,7 +11491,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -11442,7 +11525,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -11475,7 +11559,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -11508,7 +11593,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -11539,7 +11625,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -11571,7 +11658,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11604,7 +11692,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11637,7 +11726,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11670,7 +11760,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11703,7 +11794,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11736,7 +11828,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11769,7 +11862,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11802,7 +11896,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -11835,7 +11930,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -11868,7 +11964,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -11901,7 +11998,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -11932,7 +12030,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -11964,7 +12063,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11997,7 +12097,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12030,7 +12131,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12063,7 +12165,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12096,7 +12199,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12129,7 +12233,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12162,7 +12267,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12195,7 +12301,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12228,7 +12335,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12261,7 +12369,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12294,7 +12403,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12325,7 +12435,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12357,7 +12468,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12390,7 +12502,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12423,7 +12536,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12456,7 +12570,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12489,7 +12604,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12522,7 +12638,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12555,7 +12672,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12588,7 +12706,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12621,7 +12740,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12654,7 +12774,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12687,7 +12808,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12718,7 +12840,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12750,7 +12873,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12783,7 +12907,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12815,7 +12940,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12847,7 +12973,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -12880,7 +13007,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12912,7 +13040,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12944,7 +13073,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12977,7 +13107,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -13009,7 +13140,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -13105,7 +13237,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -13137,7 +13270,8 @@ "y": 0.0, "z": 5.0000000000000036 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -13169,7 +13303,8 @@ "y": 0.0, "z": 5.0000000000000036 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -13216,7 +13351,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -13248,7 +13384,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -13279,7 +13416,8 @@ "y": 0.0, "z": -14.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -13310,7 +13448,8 @@ "y": 0.0, "z": -14.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -13342,7 +13481,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -13674,7 +13814,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13707,7 +13848,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13804,7 +13946,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13837,7 +13980,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13948,7 +14092,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13981,7 +14126,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -14045,7 +14191,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14078,7 +14225,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -14224,6 +14372,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88abcfdbca][pl_Zymo_Quick_RNA_Cells_Flex_96_Channel].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88abcfdbca][pl_Zymo_Quick_RNA_Cells_Flex_96_Channel].json index e95ddbc7178..7fb0dceab92 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88abcfdbca][pl_Zymo_Quick_RNA_Cells_Flex_96_Channel].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88abcfdbca][pl_Zymo_Quick_RNA_Cells_Flex_96_Channel].json @@ -33350,7 +33350,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33383,7 +33384,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33416,7 +33418,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33449,7 +33452,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33482,7 +33486,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33515,7 +33520,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33548,7 +33554,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33581,7 +33588,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33614,7 +33622,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33647,7 +33656,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33680,7 +33690,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33713,7 +33724,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33746,7 +33758,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33779,7 +33792,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33812,7 +33826,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33845,7 +33860,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33878,7 +33894,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33911,7 +33928,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33944,7 +33962,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33977,7 +33996,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34010,7 +34030,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34043,7 +34064,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34076,7 +34098,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34109,7 +34132,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34142,7 +34166,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34175,7 +34200,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34208,7 +34234,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34241,7 +34268,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34274,7 +34302,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34307,7 +34336,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34340,7 +34370,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34373,7 +34404,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34406,7 +34438,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34439,7 +34472,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34472,7 +34506,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34505,7 +34540,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34538,7 +34574,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34571,7 +34608,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34604,7 +34642,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34637,7 +34676,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34670,7 +34710,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34703,7 +34744,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34736,7 +34778,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34769,7 +34812,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34802,7 +34846,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34835,7 +34880,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34868,7 +34914,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34901,7 +34948,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34934,7 +34982,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34967,7 +35016,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35000,7 +35050,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35033,7 +35084,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35066,7 +35118,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35099,7 +35152,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35132,7 +35186,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35165,7 +35220,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35198,7 +35254,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35231,7 +35288,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35264,7 +35322,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35297,7 +35356,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35330,7 +35390,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35363,7 +35424,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35396,7 +35458,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35429,7 +35492,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35462,7 +35526,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35495,7 +35560,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35528,7 +35594,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35561,7 +35628,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35740,7 +35808,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35773,7 +35842,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35806,7 +35876,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35839,7 +35910,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35872,7 +35944,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35905,7 +35978,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35938,7 +36012,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35971,7 +36046,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36004,7 +36080,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36037,7 +36114,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36070,7 +36148,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36103,7 +36182,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36136,7 +36216,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36169,7 +36250,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36202,7 +36284,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36235,7 +36318,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36268,7 +36352,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36301,7 +36386,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36334,7 +36420,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36367,7 +36454,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36400,7 +36488,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36433,7 +36522,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36466,7 +36556,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36499,7 +36590,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36532,7 +36624,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36565,7 +36658,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36598,7 +36692,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36631,7 +36726,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36664,7 +36760,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36697,7 +36794,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36730,7 +36828,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36763,7 +36862,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36796,7 +36896,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36829,7 +36930,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36862,7 +36964,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36895,7 +36998,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36928,7 +37032,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36961,7 +37066,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36994,7 +37100,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37027,7 +37134,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37060,7 +37168,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37093,7 +37202,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37126,7 +37236,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37159,7 +37270,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37192,7 +37304,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37225,7 +37338,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37258,7 +37372,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37291,7 +37406,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37324,7 +37440,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37357,7 +37474,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37390,7 +37508,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37423,7 +37542,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37456,7 +37576,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37489,7 +37610,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37522,7 +37644,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37555,7 +37678,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37588,7 +37712,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37621,7 +37746,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37654,7 +37780,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37687,7 +37814,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37720,7 +37848,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37753,7 +37882,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38094,7 +38224,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38127,7 +38258,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38272,7 +38404,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38305,7 +38438,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38586,7 +38720,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38619,7 +38754,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38764,7 +38900,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38797,7 +38934,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39078,7 +39216,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39111,7 +39250,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39256,7 +39396,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39289,7 +39430,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39570,7 +39712,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39603,7 +39746,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39748,7 +39892,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39795,7 +39940,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39843,7 +39989,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39876,7 +40023,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39909,7 +40057,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39942,7 +40091,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39975,7 +40125,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40008,7 +40159,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40041,7 +40193,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40074,7 +40227,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40107,7 +40261,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40140,7 +40295,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40173,7 +40329,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40206,7 +40363,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40239,7 +40397,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40272,7 +40431,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40305,7 +40465,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40338,7 +40499,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40371,7 +40533,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40404,7 +40567,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40437,7 +40601,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40470,7 +40635,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40503,7 +40669,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40536,7 +40703,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40569,7 +40737,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40602,7 +40771,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40635,7 +40805,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40668,7 +40839,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40701,7 +40873,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40734,7 +40907,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40767,7 +40941,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40800,7 +40975,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40833,7 +41009,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40866,7 +41043,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40899,7 +41077,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40932,7 +41111,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40965,7 +41145,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40998,7 +41179,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41031,7 +41213,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41064,7 +41247,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41097,7 +41281,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41130,7 +41315,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41163,7 +41349,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41196,7 +41383,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41229,7 +41417,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41262,7 +41451,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41295,7 +41485,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41328,7 +41519,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41361,7 +41553,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41394,7 +41587,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41427,7 +41621,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41460,7 +41655,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41493,7 +41689,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41526,7 +41723,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41559,7 +41757,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41592,7 +41791,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41625,7 +41825,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41658,7 +41859,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41691,7 +41893,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41724,7 +41927,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41757,7 +41961,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41790,7 +41995,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41823,7 +42029,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41856,7 +42063,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41889,7 +42097,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41922,7 +42131,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41955,7 +42165,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41988,7 +42199,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42021,7 +42233,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42054,7 +42267,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42087,7 +42301,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42120,7 +42335,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42153,7 +42369,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42186,7 +42403,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42219,7 +42437,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42252,7 +42471,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42285,7 +42505,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42318,7 +42539,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42351,7 +42573,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42384,7 +42607,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42417,7 +42641,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42450,7 +42675,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42483,7 +42709,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42516,7 +42743,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42549,7 +42777,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42582,7 +42811,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42615,7 +42845,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42648,7 +42879,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42681,7 +42913,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42714,7 +42947,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42747,7 +42981,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42780,7 +43015,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42813,7 +43049,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42846,7 +43083,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42879,7 +43117,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42912,7 +43151,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42945,7 +43185,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42978,7 +43219,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43011,7 +43253,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43044,7 +43287,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43077,7 +43321,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43110,7 +43355,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43143,7 +43389,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43176,7 +43423,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43209,7 +43457,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43242,7 +43491,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43275,7 +43525,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43308,7 +43559,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43341,7 +43593,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43374,7 +43627,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43407,7 +43661,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43440,7 +43695,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43473,7 +43729,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43506,7 +43763,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43539,7 +43797,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43572,7 +43831,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43605,7 +43865,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43638,7 +43899,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43671,7 +43933,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43704,7 +43967,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43737,7 +44001,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43770,7 +44035,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43803,7 +44069,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43836,7 +44103,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43869,7 +44137,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43902,7 +44171,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43935,7 +44205,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43968,7 +44239,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44001,7 +44273,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44034,7 +44307,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44067,7 +44341,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44100,7 +44375,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44243,7 +44519,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44276,7 +44553,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44587,7 +44865,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44620,7 +44899,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44765,7 +45045,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44798,7 +45079,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45079,7 +45361,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45112,7 +45395,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45257,7 +45541,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45290,7 +45575,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45571,7 +45857,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45604,7 +45891,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45749,7 +46037,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45782,7 +46071,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46063,7 +46353,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46096,7 +46387,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46542,7 +46834,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46575,7 +46868,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46608,7 +46902,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46641,7 +46936,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46674,7 +46970,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46707,7 +47004,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46740,7 +47038,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46773,7 +47072,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46806,7 +47106,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46839,7 +47140,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46872,7 +47174,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46905,7 +47208,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46938,7 +47242,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46971,7 +47276,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47004,7 +47310,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47037,7 +47344,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47070,7 +47378,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47103,7 +47412,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47136,7 +47446,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47169,7 +47480,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47202,7 +47514,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47235,7 +47548,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47268,7 +47582,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47301,7 +47616,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47334,7 +47650,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47367,7 +47684,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47400,7 +47718,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47433,7 +47752,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47466,7 +47786,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47499,7 +47820,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47532,7 +47854,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47565,7 +47888,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47598,7 +47922,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47631,7 +47956,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47664,7 +47990,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47697,7 +48024,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47730,7 +48058,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47763,7 +48092,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47796,7 +48126,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47829,7 +48160,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47862,7 +48194,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47895,7 +48228,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47928,7 +48262,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47961,7 +48296,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47994,7 +48330,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48027,7 +48364,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48060,7 +48398,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48093,7 +48432,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48126,7 +48466,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48159,7 +48500,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48192,7 +48534,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48225,7 +48568,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48597,7 +48941,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48630,7 +48975,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -48823,6 +49169,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Sample Volume in Shield", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88de2fb78f][OT2_X_v6_P20S_P300M_HS_HSCollision].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88de2fb78f][OT2_X_v6_P20S_P300M_HS_HSCollision].json index 5d219d91f72..7eedccb2cf8 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88de2fb78f][OT2_X_v6_P20S_P300M_HS_HSCollision].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[88de2fb78f][OT2_X_v6_P20S_P300M_HS_HSCollision].json @@ -5373,6 +5373,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a255db0b][OT2_X_v2_18_None_None_StrRTPwith_unit].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a255db0b][OT2_X_v2_18_None_None_StrRTPwith_unit].json index 2b9cd2584d3..70bb212b45b 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a255db0b][OT2_X_v2_18_None_None_StrRTPwith_unit].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a255db0b][OT2_X_v2_18_None_None_StrRTPwith_unit].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Str RTP with unit" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a8226c4e][Flex_X_v2_16_P1000_96_TC_PartialTipPickupThermocyclerLidConflict].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a8226c4e][Flex_X_v2_16_P1000_96_TC_PartialTipPickupThermocyclerLidConflict].json index cf0293eee21..ea3c1cc76b0 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a8226c4e][Flex_X_v2_16_P1000_96_TC_PartialTipPickupThermocyclerLidConflict].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[89a8226c4e][Flex_X_v2_16_P1000_96_TC_PartialTipPickupThermocyclerLidConflict].json @@ -4875,7 +4875,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -4889,6 +4890,40 @@ }, "startedAt": "TIMESTAMP", "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "517162d1e8d73c035348a1870a8abc8a", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 20.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -9.8 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 23.28, + "y": 181.18, + "z": 4.5 + }, + "volume": 20.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" } ], "config": { @@ -4902,7 +4937,7 @@ "errors": [ { "createdAt": "TIMESTAMP", - "detail": "PartialTipMovementNotAllowedError [line 24]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Requested motion with the A12 nozzle partial configuration is outside of robot bounds for the pipette.", + "detail": "PartialTipMovementNotAllowedError [line 26]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to NEST 96 Well Plate 200 µL Flat in slot A2 with A12 nozzle partial configuration will result in collision with thermocycler lid in deck slot A1.", "errorCode": "4000", "errorInfo": {}, "errorType": "ExceptionInProtocolError", @@ -4911,7 +4946,7 @@ "wrappedErrors": [ { "createdAt": "TIMESTAMP", - "detail": "Requested motion with the A12 nozzle partial configuration is outside of robot bounds for the pipette.", + "detail": "Moving to NEST 96 Well Plate 200 µL Flat in slot A2 with A12 nozzle partial configuration will result in collision with thermocycler lid in deck slot A1.", "errorCode": "2004", "errorInfo": {}, "errorType": "PartialTipMovementNotAllowedError", @@ -4962,6 +4997,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8a663305c4][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8a663305c4][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south].json new file mode 100644 index 00000000000..cadf197c142 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8a663305c4][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south].json @@ -0,0 +1,1501 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-1000ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 97.47 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_1000ul", + "tipLength": 88, + "tipOverlap": 7.95 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.24, + "z": 9.47 + }, + "A10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.24, + "z": 9.47 + }, + "A11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.24, + "z": 9.47 + }, + "A12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.24, + "z": 9.47 + }, + "A2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.24, + "z": 9.47 + }, + "A3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.24, + "z": 9.47 + }, + "A4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.24, + "z": 9.47 + }, + "A5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.24, + "z": 9.47 + }, + "A6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.24, + "z": 9.47 + }, + "A7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.24, + "z": 9.47 + }, + "A8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.24, + "z": 9.47 + }, + "A9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.24, + "z": 9.47 + }, + "B1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.24, + "z": 9.47 + }, + "B10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.24, + "z": 9.47 + }, + "B11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.24, + "z": 9.47 + }, + "B12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.24, + "z": 9.47 + }, + "B2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.24, + "z": 9.47 + }, + "B3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.24, + "z": 9.47 + }, + "B4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.24, + "z": 9.47 + }, + "B5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.24, + "z": 9.47 + }, + "B6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.24, + "z": 9.47 + }, + "B7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.24, + "z": 9.47 + }, + "B8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.24, + "z": 9.47 + }, + "B9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.24, + "z": 9.47 + }, + "C1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.24, + "z": 9.47 + }, + "C10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.24, + "z": 9.47 + }, + "C11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.24, + "z": 9.47 + }, + "C12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.24, + "z": 9.47 + }, + "C2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.24, + "z": 9.47 + }, + "C3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.24, + "z": 9.47 + }, + "C4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.24, + "z": 9.47 + }, + "C5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.24, + "z": 9.47 + }, + "C6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.24, + "z": 9.47 + }, + "C7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.24, + "z": 9.47 + }, + "C8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.24, + "z": 9.47 + }, + "C9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.24, + "z": 9.47 + }, + "D1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.24, + "z": 9.47 + }, + "D10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.24, + "z": 9.47 + }, + "D11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.24, + "z": 9.47 + }, + "D12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.24, + "z": 9.47 + }, + "D2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.24, + "z": 9.47 + }, + "D3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.24, + "z": 9.47 + }, + "D4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.24, + "z": 9.47 + }, + "D5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.24, + "z": 9.47 + }, + "D6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.24, + "z": 9.47 + }, + "D7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.24, + "z": 9.47 + }, + "D8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.24, + "z": 9.47 + }, + "D9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.24, + "z": 9.47 + }, + "E1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.24, + "z": 9.47 + }, + "E10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.24, + "z": 9.47 + }, + "E11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.24, + "z": 9.47 + }, + "E12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.24, + "z": 9.47 + }, + "E2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.24, + "z": 9.47 + }, + "E3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.24, + "z": 9.47 + }, + "E4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.24, + "z": 9.47 + }, + "E5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.24, + "z": 9.47 + }, + "E6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.24, + "z": 9.47 + }, + "E7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.24, + "z": 9.47 + }, + "E8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.24, + "z": 9.47 + }, + "E9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.24, + "z": 9.47 + }, + "F1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.24, + "z": 9.47 + }, + "F10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.24, + "z": 9.47 + }, + "F11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.24, + "z": 9.47 + }, + "F12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.24, + "z": 9.47 + }, + "F2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.24, + "z": 9.47 + }, + "F3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.24, + "z": 9.47 + }, + "F4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.24, + "z": 9.47 + }, + "F5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.24, + "z": 9.47 + }, + "F6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.24, + "z": 9.47 + }, + "F7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.24, + "z": 9.47 + }, + "F8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.24, + "z": 9.47 + }, + "F9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.24, + "z": 9.47 + }, + "G1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.24, + "z": 9.47 + }, + "G10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.24, + "z": 9.47 + }, + "G11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.24, + "z": 9.47 + }, + "G12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.24, + "z": 9.47 + }, + "G2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.24, + "z": 9.47 + }, + "G3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.24, + "z": 9.47 + }, + "G4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.24, + "z": 9.47 + }, + "G5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.24, + "z": 9.47 + }, + "G6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.24, + "z": 9.47 + }, + "G7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.24, + "z": 9.47 + }, + "G8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.24, + "z": 9.47 + }, + "G9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.24, + "z": 9.47 + }, + "H1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.24, + "z": 9.47 + }, + "H10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.24, + "z": 9.47 + }, + "H11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.24, + "z": 9.47 + }, + "H12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.24, + "z": 9.47 + }, + "H2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.24, + "z": 9.47 + }, + "H3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.24, + "z": 9.47 + }, + "H4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.24, + "z": 9.47 + }, + "H5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.24, + "z": 9.47 + }, + "H6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.24, + "z": 9.47 + }, + "H7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.24, + "z": 9.47 + }, + "H8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.24, + "z": 9.47 + }, + "H9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.24, + "z": 9.47 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "D2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/tube-rack-set-1" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 124.35 + }, + "gripperOffsets": {}, + "groups": [ + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352095", + "352096", + "352097", + "352099", + "352196" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 6x15 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A2", + "B1", + "B2", + "C1", + "C2" + ] + }, + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352070", + "352098" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 4x50 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A3", + "A4", + "B3", + "B4" + ] + } + ], + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Opentrons 10 Tube Rack with Falcon 4x50 mL, 6x15 mL Conical", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1" + ], + [ + "A2", + "B2", + "C2" + ], + [ + "A3", + "B3" + ], + [ + "A4", + "B4" + ] + ], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 67.75, + "z": 6.85 + }, + "A2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 67.75, + "z": 6.85 + }, + "A3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 60.25, + "z": 7.3 + }, + "A4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 60.25, + "z": 7.3 + }, + "B1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 42.75, + "z": 6.85 + }, + "B2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 42.75, + "z": 6.85 + }, + "B3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 25.25, + "z": 7.3 + }, + "B4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 25.25, + "z": 7.3 + }, + "C1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 17.75, + "z": 6.85 + }, + "C2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 17.75, + "z": 6.85 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "PartialTipMovementNotAllowedError [line 479]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with A12 nozzle partial configuration will result in collision with items in deck slot D2.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with A12 nozzle partial configuration will result in collision with items in deck slot D2.", + "errorCode": "2004", + "errorInfo": {}, + "errorType": "PartialTipMovementNotAllowedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_south.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + } + }, + { + "definitionUri": "opentrons/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical/1", + "id": "UUID", + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "D2" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Too tall labware on pickup tip" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8b07e799f6][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_drop_tip_with_location].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8b07e799f6][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_drop_tip_with_location].json new file mode 100644 index 00000000000..e433acf53ff --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8b07e799f6][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_drop_tip_with_location].json @@ -0,0 +1,3786 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_multi_flex", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "backLeftNozzle": "G1", + "frontRightNozzle": "H1", + "primaryNozzle": "H1", + "style": "QUADRANT" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c910be62291bb94458a59cb4185c5180", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D3" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2f59e0552969594ba6ab06f03af324e1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 178.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.92999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7ba58db1295d2a1b66a5e6a40d618831", + "notes": [], + "params": { + "message": "Tip rack in B2, well A1 has tip: False" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f6a04baea84331f9d79f8645ae5f367f", + "notes": [], + "params": { + "message": "Tip rack in B2, well B1 has tip: False" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3a257d45e68ca9f2c7279f9a7c31ad21", + "notes": [], + "params": { + "message": "Tip rack in B2, well C1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4b1c42c87368694a75338a5577e79371", + "notes": [], + "params": { + "message": "Tip rack in B2, well D1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "80cdcab7661e6faccf065771049bb6d4", + "notes": [], + "params": { + "message": "Tip rack in B2, well E1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b4253d217967a7cfbc022f5e143ac580", + "notes": [], + "params": { + "message": "Tip rack in B2, well F1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "60f927b4473b1fe3a70f61aabfb79781", + "notes": [], + "params": { + "message": "Tip rack in B2, well G1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6f45d808ddc38796bd92b2e64bee48a4", + "notes": [], + "params": { + "message": "Tip rack in B2, well H1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "UnexpectedProtocolError [line 184]: Error 4000 GENERAL_ERROR (UnexpectedProtocolError): Cannot return tip to a tiprack while the pipette is configured for partial tip.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Cannot return tip to a tiprack while the pipette is configured for partial tip.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "UnexpectedProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_drop_tip_with_location.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D3" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Invalid tip configs that should error" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_multi_flex" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8e1f35ed6c][pl_NiNTA_Flex_96well_PlatePrep_final].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8e1f35ed6c][pl_NiNTA_Flex_96well_PlatePrep_final].json index 561bd970524..c21c19205cf 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8e1f35ed6c][pl_NiNTA_Flex_96well_PlatePrep_final].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8e1f35ed6c][pl_NiNTA_Flex_96well_PlatePrep_final].json @@ -8002,7 +8002,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8035,7 +8036,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8068,7 +8070,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8101,7 +8104,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8134,7 +8138,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8167,7 +8172,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8200,7 +8206,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8233,7 +8240,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8266,7 +8274,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8299,7 +8308,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8332,7 +8342,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8365,7 +8376,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8398,7 +8410,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8431,7 +8444,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8464,7 +8478,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8497,7 +8512,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8530,7 +8546,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8563,7 +8580,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8596,7 +8614,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8629,7 +8648,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8662,7 +8682,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8694,7 +8715,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8726,7 +8748,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8759,7 +8782,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8792,7 +8816,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8825,7 +8850,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8858,7 +8884,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -8891,7 +8918,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -8924,7 +8952,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -8957,7 +8986,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -8989,7 +9019,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -9021,7 +9052,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9054,7 +9086,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9087,7 +9120,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9120,7 +9154,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9153,7 +9188,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9186,7 +9222,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9219,7 +9256,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9252,7 +9290,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9285,7 +9324,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9318,7 +9358,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9351,7 +9392,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9384,7 +9426,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9417,7 +9460,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9450,7 +9494,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9483,7 +9528,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9516,7 +9562,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9549,7 +9596,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9582,7 +9630,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9615,7 +9664,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9648,7 +9698,8 @@ "y": 0.0, "z": -26.450000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9681,7 +9732,8 @@ "y": 0.0, "z": -26.650000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9713,7 +9765,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9745,7 +9798,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9778,7 +9832,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -9811,7 +9866,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -9844,7 +9900,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -9877,7 +9934,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -9910,7 +9968,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9943,7 +10002,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9976,7 +10036,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -10008,7 +10069,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -10120,7 +10182,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10152,7 +10215,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10184,7 +10248,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10217,7 +10282,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10250,7 +10316,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10282,7 +10349,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10314,7 +10382,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10347,7 +10416,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10380,7 +10450,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10412,7 +10483,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10444,7 +10516,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10477,7 +10550,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10510,7 +10584,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10542,7 +10617,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10574,7 +10650,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10607,7 +10684,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10640,7 +10718,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10672,7 +10751,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10704,7 +10784,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10737,7 +10818,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -10770,7 +10852,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10802,7 +10885,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10834,7 +10918,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10867,7 +10952,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -10900,7 +10986,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10932,7 +11019,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10964,7 +11052,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10997,7 +11086,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -11030,7 +11120,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11062,7 +11153,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11094,7 +11186,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11127,7 +11220,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -11160,7 +11254,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11192,7 +11287,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11224,7 +11320,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11257,7 +11354,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -11290,7 +11388,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11322,7 +11421,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11354,7 +11454,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11387,7 +11488,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -11420,7 +11522,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11452,7 +11555,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11484,7 +11588,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11517,7 +11622,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -11550,7 +11656,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11582,7 +11689,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11614,7 +11722,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11647,7 +11756,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -11680,7 +11790,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11712,7 +11823,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11744,7 +11856,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11777,7 +11890,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11810,7 +11924,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11842,7 +11957,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11874,7 +11990,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11907,7 +12024,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11940,7 +12058,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11972,7 +12091,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12004,7 +12124,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12037,7 +12158,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12070,7 +12192,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12102,7 +12225,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12134,7 +12258,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12167,7 +12292,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -12200,7 +12326,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12232,7 +12359,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12264,7 +12392,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12297,7 +12426,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12330,7 +12460,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12362,7 +12493,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12394,7 +12526,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12427,7 +12560,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -12460,7 +12594,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12492,7 +12627,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12524,7 +12660,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12557,7 +12694,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -12590,7 +12728,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12622,7 +12761,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12654,7 +12794,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12687,7 +12828,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -12720,7 +12862,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12752,7 +12895,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12784,7 +12928,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12817,7 +12962,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -12850,7 +12996,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12882,7 +13029,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12914,7 +13062,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12947,7 +13096,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -12980,7 +13130,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13012,7 +13163,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13044,7 +13196,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13077,7 +13230,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -13110,7 +13264,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13142,7 +13297,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13174,7 +13330,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13207,7 +13364,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -13304,7 +13462,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13336,7 +13495,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13368,7 +13528,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13401,7 +13562,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13434,7 +13596,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13466,7 +13629,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13498,7 +13662,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13531,7 +13696,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13564,7 +13730,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13596,7 +13763,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13628,7 +13796,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13661,7 +13830,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13694,7 +13864,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13726,7 +13897,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13758,7 +13930,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13791,7 +13964,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -13824,7 +13998,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13856,7 +14031,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13888,7 +14064,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13921,7 +14098,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -13954,7 +14132,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13986,7 +14165,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14018,7 +14198,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14051,7 +14232,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -14084,7 +14266,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14116,7 +14299,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14148,7 +14332,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14181,7 +14366,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -14214,7 +14400,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14246,7 +14433,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14278,7 +14466,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14311,7 +14500,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -14344,7 +14534,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14376,7 +14567,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14408,7 +14600,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14441,7 +14634,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -14474,7 +14668,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14506,7 +14701,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14538,7 +14734,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14571,7 +14768,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -14604,7 +14802,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14636,7 +14835,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14668,7 +14868,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14701,7 +14902,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -14734,7 +14936,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14766,7 +14969,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14798,7 +15002,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14831,7 +15036,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14864,7 +15070,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14896,7 +15103,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14928,7 +15136,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14961,7 +15170,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14994,7 +15204,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15026,7 +15237,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15058,7 +15270,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15091,7 +15304,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15124,7 +15338,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15156,7 +15371,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15188,7 +15404,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15221,7 +15438,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15254,7 +15472,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15286,7 +15505,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15318,7 +15538,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15351,7 +15572,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15384,7 +15606,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15416,7 +15639,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15448,7 +15672,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15481,7 +15706,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15514,7 +15740,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15546,7 +15773,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15578,7 +15806,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15611,7 +15840,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -15644,7 +15874,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15676,7 +15907,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15708,7 +15940,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15741,7 +15974,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -15774,7 +16008,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15806,7 +16041,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15838,7 +16074,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15871,7 +16108,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -15904,7 +16142,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15936,7 +16175,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15968,7 +16208,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16001,7 +16242,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -16034,7 +16276,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16066,7 +16309,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16098,7 +16342,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16131,7 +16376,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -16164,7 +16410,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16196,7 +16443,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16228,7 +16476,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16261,7 +16510,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16294,7 +16544,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16326,7 +16577,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16358,7 +16610,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16391,7 +16644,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -16488,7 +16742,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16520,7 +16775,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16552,7 +16808,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16585,7 +16842,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16618,7 +16876,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16650,7 +16909,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16682,7 +16942,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16715,7 +16976,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16748,7 +17010,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16780,7 +17043,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16812,7 +17076,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16845,7 +17110,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16878,7 +17144,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16910,7 +17177,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16942,7 +17210,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16975,7 +17244,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17008,7 +17278,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17040,7 +17311,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17072,7 +17344,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17105,7 +17378,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -17138,7 +17412,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17170,7 +17445,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17202,7 +17478,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17235,7 +17512,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -17268,7 +17546,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17300,7 +17579,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17332,7 +17612,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17365,7 +17646,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17398,7 +17680,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17430,7 +17713,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17462,7 +17746,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17495,7 +17780,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -17528,7 +17814,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17560,7 +17847,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17592,7 +17880,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17625,7 +17914,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -17658,7 +17948,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17690,7 +17981,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17722,7 +18014,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17755,7 +18048,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -17788,7 +18082,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17820,7 +18115,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17852,7 +18148,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17885,7 +18182,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -17918,7 +18216,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17950,7 +18249,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17982,7 +18282,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18015,7 +18316,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18175,6 +18477,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Equilibration Buffer", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8fcfd2ced0][Flex_S_v2_16_P1000_96_TC_PartialTipPickupColumn].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8fcfd2ced0][Flex_S_v2_16_P1000_96_TC_PartialTipPickupColumn].json index 02df13c1a33..933aa66cf7d 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8fcfd2ced0][Flex_S_v2_16_P1000_96_TC_PartialTipPickupColumn].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[8fcfd2ced0][Flex_S_v2_16_P1000_96_TC_PartialTipPickupColumn].json @@ -3592,7 +3592,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -3606,6 +3607,83 @@ }, "startedAt": "TIMESTAMP", "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ddccee6754fe0092b9c66898d66b79a7", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 20.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -9.8 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 23.28, + "y": 181.18, + "z": 4.5 + }, + "volume": 20.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5287b77e909d217f4b05e5006cf9ff25", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b81364c35c04784c34f571446e64484c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" } ], "config": { @@ -3616,29 +3694,7 @@ "protocolType": "python" }, "createdAt": "TIMESTAMP", - "errors": [ - { - "createdAt": "TIMESTAMP", - "detail": "PartialTipMovementNotAllowedError [line 20]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Requested motion with the A12 nozzle partial configuration is outside of robot bounds for the pipette.", - "errorCode": "4000", - "errorInfo": {}, - "errorType": "ExceptionInProtocolError", - "id": "UUID", - "isDefined": false, - "wrappedErrors": [ - { - "createdAt": "TIMESTAMP", - "detail": "Requested motion with the A12 nozzle partial configuration is outside of robot bounds for the pipette.", - "errorCode": "2004", - "errorInfo": {}, - "errorType": "PartialTipMovementNotAllowedError", - "id": "UUID", - "isDefined": false, - "wrappedErrors": [] - } - ] - } - ], + "errors": [], "files": [ { "name": "Flex_S_v2_16_P1000_96_TC_PartialTipPickupColumn.py", @@ -3671,6 +3727,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [], @@ -3681,7 +3738,7 @@ "pipetteName": "p1000_96" } ], - "result": "not-ok", + "result": "ok", "robotType": "OT-3 Standard", "runTimeParameters": [] } diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[918747b2f9][pl_Illumina_DNA_Prep_48x_v8].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[918747b2f9][pl_Illumina_DNA_Prep_48x_v8].json index e79b8070f02..794499f75ce 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[918747b2f9][pl_Illumina_DNA_Prep_48x_v8].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[918747b2f9][pl_Illumina_DNA_Prep_48x_v8].json @@ -7651,7 +7651,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7685,7 +7686,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7718,7 +7720,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7752,7 +7755,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7785,7 +7789,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7818,7 +7823,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7851,7 +7857,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7884,7 +7891,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7917,7 +7925,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7950,7 +7959,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7984,7 +7994,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8017,7 +8028,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8050,7 +8062,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8082,7 +8095,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8127,7 +8141,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8158,7 +8173,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8189,7 +8205,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8220,7 +8237,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8327,7 +8345,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8361,7 +8380,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8394,7 +8414,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8428,7 +8449,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8461,7 +8483,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8494,7 +8517,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8527,7 +8551,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8560,7 +8585,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8593,7 +8619,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8626,7 +8653,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8660,7 +8688,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8693,7 +8722,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8726,7 +8756,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8758,7 +8789,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8803,7 +8835,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8834,7 +8867,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8865,7 +8899,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8896,7 +8931,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9003,7 +9039,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9037,7 +9074,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9070,7 +9108,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9104,7 +9143,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9137,7 +9177,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9170,7 +9211,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9203,7 +9245,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9236,7 +9279,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9269,7 +9313,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9302,7 +9347,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9336,7 +9382,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9369,7 +9416,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9402,7 +9450,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9434,7 +9483,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9479,7 +9529,8 @@ "y": 0.0, "z": -2.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9510,7 +9561,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9541,7 +9593,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9572,7 +9625,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11064,7 +11118,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -11097,7 +11152,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -11130,7 +11186,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11162,7 +11219,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11194,7 +11252,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11228,7 +11287,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11261,7 +11321,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11295,7 +11356,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11328,7 +11390,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11362,7 +11425,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11395,7 +11459,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11429,7 +11494,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11462,7 +11528,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11496,7 +11563,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11529,7 +11597,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11563,7 +11632,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11596,7 +11666,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11630,7 +11701,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11663,7 +11735,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11697,7 +11770,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11730,7 +11804,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11764,7 +11839,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11797,7 +11873,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11830,7 +11907,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11862,7 +11940,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11969,7 +12048,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12002,7 +12082,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12035,7 +12116,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12067,7 +12149,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12099,7 +12182,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12133,7 +12217,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12166,7 +12251,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12200,7 +12286,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12233,7 +12320,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12267,7 +12355,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12300,7 +12389,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12334,7 +12424,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12367,7 +12458,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12401,7 +12493,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12434,7 +12527,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12468,7 +12562,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12501,7 +12596,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12535,7 +12631,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12568,7 +12665,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12602,7 +12700,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12635,7 +12734,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12669,7 +12769,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12702,7 +12803,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12735,7 +12837,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12767,7 +12870,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12874,7 +12978,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12907,7 +13012,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12940,7 +13046,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12972,7 +13079,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13004,7 +13112,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13038,7 +13147,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13071,7 +13181,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13105,7 +13216,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13138,7 +13250,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13172,7 +13285,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13205,7 +13319,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13239,7 +13354,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13272,7 +13388,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13306,7 +13423,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13339,7 +13457,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13373,7 +13492,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13406,7 +13526,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13440,7 +13561,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13473,7 +13595,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13507,7 +13630,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13540,7 +13664,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13574,7 +13699,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13607,7 +13733,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13640,7 +13767,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13672,7 +13800,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13874,7 +14003,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13907,7 +14037,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14004,7 +14135,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14037,7 +14169,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14134,7 +14267,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14167,7 +14301,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14410,7 +14545,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14442,7 +14578,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14488,7 +14625,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14520,7 +14658,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14552,7 +14691,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14598,7 +14738,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14644,7 +14785,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14675,7 +14817,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14706,7 +14849,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14737,7 +14881,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14832,7 +14977,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14864,7 +15010,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14910,7 +15057,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14942,7 +15090,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14974,7 +15123,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15020,7 +15170,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15066,7 +15217,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15097,7 +15249,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15128,7 +15281,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15159,7 +15313,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15254,7 +15409,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15286,7 +15442,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15332,7 +15489,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15364,7 +15522,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15396,7 +15555,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15442,7 +15602,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15488,7 +15649,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15519,7 +15681,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15550,7 +15713,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15581,7 +15745,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15749,7 +15914,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -15782,7 +15948,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -15814,7 +15981,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -15845,7 +16013,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -15876,7 +16045,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15908,7 +16078,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15941,7 +16112,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15975,7 +16147,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16008,7 +16181,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16041,7 +16215,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16073,7 +16248,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16118,7 +16294,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16150,7 +16327,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16258,7 +16436,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16291,7 +16470,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16323,7 +16503,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16354,7 +16535,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16385,7 +16567,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16417,7 +16600,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16450,7 +16634,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16484,7 +16669,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16517,7 +16703,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16550,7 +16737,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16582,7 +16770,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16627,7 +16816,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16659,7 +16849,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16767,7 +16958,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16800,7 +16992,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16832,7 +17025,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16863,7 +17057,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16894,7 +17089,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16926,7 +17122,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16959,7 +17156,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16993,7 +17191,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17026,7 +17225,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17059,7 +17259,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17091,7 +17292,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17136,7 +17338,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17168,7 +17371,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18548,7 +18752,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18580,7 +18785,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18612,7 +18818,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18658,7 +18865,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18705,7 +18913,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18737,7 +18946,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18782,7 +18992,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18813,7 +19024,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18844,7 +19056,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18875,7 +19088,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18970,7 +19184,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19002,7 +19217,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19034,7 +19250,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19080,7 +19297,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19127,7 +19345,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19159,7 +19378,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19204,7 +19424,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19235,7 +19456,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19266,7 +19488,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19297,7 +19520,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19392,7 +19616,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19424,7 +19649,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19456,7 +19682,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19502,7 +19729,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19549,7 +19777,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19581,7 +19810,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19626,7 +19856,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19657,7 +19888,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19688,7 +19920,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19719,7 +19952,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19887,7 +20121,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -19920,7 +20155,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -19952,7 +20188,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -19983,7 +20220,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -20014,7 +20252,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20046,7 +20285,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20079,7 +20319,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20113,7 +20354,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20146,7 +20388,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20179,7 +20422,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20211,7 +20455,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20256,7 +20501,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20288,7 +20534,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20396,7 +20643,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -20429,7 +20677,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -20461,7 +20710,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -20492,7 +20742,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -20523,7 +20774,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20555,7 +20807,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20588,7 +20841,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20622,7 +20876,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20655,7 +20910,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20688,7 +20944,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20720,7 +20977,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20765,7 +21023,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20797,7 +21056,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20905,7 +21165,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -20938,7 +21199,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -20970,7 +21232,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -21001,7 +21264,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -21032,7 +21296,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21064,7 +21329,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21097,7 +21363,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21131,7 +21398,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21164,7 +21432,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21197,7 +21466,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21229,7 +21499,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21274,7 +21545,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21306,7 +21578,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21499,7 +21772,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21531,7 +21805,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21563,7 +21838,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21609,7 +21885,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21656,7 +21933,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21688,7 +21966,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21733,7 +22012,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21764,7 +22044,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21795,7 +22076,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21826,7 +22108,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21921,7 +22204,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21953,7 +22237,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21985,7 +22270,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22031,7 +22317,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22078,7 +22365,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22110,7 +22398,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22155,7 +22444,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22186,7 +22476,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22217,7 +22508,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22248,7 +22540,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22343,7 +22636,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22375,7 +22669,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22407,7 +22702,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22453,7 +22749,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22500,7 +22797,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22532,7 +22830,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22577,7 +22876,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22608,7 +22908,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22639,7 +22940,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22670,7 +22972,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -22838,7 +23141,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -22871,7 +23175,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -22903,7 +23208,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -22934,7 +23240,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -22965,7 +23272,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22997,7 +23305,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23030,7 +23339,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23064,7 +23374,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23097,7 +23408,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23130,7 +23442,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23162,7 +23475,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23207,7 +23521,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23239,7 +23554,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23347,7 +23663,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23380,7 +23697,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23412,7 +23730,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23443,7 +23762,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23474,7 +23794,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23506,7 +23827,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23539,7 +23861,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23573,7 +23896,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23606,7 +23930,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23639,7 +23964,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23671,7 +23997,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23716,7 +24043,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23748,7 +24076,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23856,7 +24185,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23889,7 +24219,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23921,7 +24252,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23952,7 +24284,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23983,7 +24316,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24015,7 +24349,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24048,7 +24383,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24082,7 +24418,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24115,7 +24452,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24148,7 +24486,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24180,7 +24519,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24225,7 +24565,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24257,7 +24598,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25637,7 +25979,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25669,7 +26012,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25701,7 +26045,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25747,7 +26092,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25794,7 +26140,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25826,7 +26173,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25871,7 +26219,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25902,7 +26251,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25933,7 +26283,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25964,7 +26315,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26059,7 +26411,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26091,7 +26444,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26123,7 +26477,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26169,7 +26524,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26216,7 +26572,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26248,7 +26605,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26293,7 +26651,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26324,7 +26683,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26355,7 +26715,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26386,7 +26747,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26481,7 +26843,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26513,7 +26876,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26545,7 +26909,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26591,7 +26956,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26638,7 +27004,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26670,7 +27037,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26715,7 +27083,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26746,7 +27115,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26777,7 +27147,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26808,7 +27179,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26931,7 +27303,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26963,7 +27336,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27010,7 +27384,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27042,7 +27417,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27087,7 +27463,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27118,7 +27495,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27149,7 +27527,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27180,7 +27559,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27275,7 +27655,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27307,7 +27688,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27354,7 +27736,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27386,7 +27769,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27431,7 +27815,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27462,7 +27847,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27493,7 +27879,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27524,7 +27911,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27619,7 +28007,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27651,7 +28040,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27698,7 +28088,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27730,7 +28121,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27775,7 +28167,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27806,7 +28199,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27837,7 +28231,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27868,7 +28263,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -28050,7 +28446,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28083,7 +28480,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28115,7 +28513,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28147,7 +28546,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28179,7 +28579,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28211,7 +28612,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28243,7 +28645,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28275,7 +28678,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28307,7 +28711,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28339,7 +28744,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28371,7 +28777,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28403,7 +28810,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28435,7 +28843,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28467,7 +28876,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28499,7 +28909,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28531,7 +28942,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28563,7 +28975,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28595,7 +29008,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28628,7 +29042,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28660,7 +29075,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28692,7 +29108,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28724,7 +29141,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28756,7 +29174,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28788,7 +29207,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28820,7 +29240,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28852,7 +29273,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28884,7 +29306,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28916,7 +29339,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28948,7 +29372,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28980,7 +29405,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29012,7 +29438,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29044,7 +29471,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29076,7 +29504,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29109,7 +29538,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29141,7 +29571,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29172,7 +29603,8 @@ "y": 0.0, "z": -37.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29203,7 +29635,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29234,7 +29667,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29265,7 +29699,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29372,7 +29807,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29405,7 +29841,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29437,7 +29874,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29469,7 +29907,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29501,7 +29940,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29533,7 +29973,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29565,7 +30006,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29597,7 +30039,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29629,7 +30072,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29661,7 +30105,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29693,7 +30138,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29725,7 +30171,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29757,7 +30204,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29789,7 +30237,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29821,7 +30270,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29853,7 +30303,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29885,7 +30336,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29917,7 +30369,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29950,7 +30403,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29982,7 +30436,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30014,7 +30469,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30046,7 +30502,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30078,7 +30535,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30110,7 +30568,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30142,7 +30601,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30174,7 +30634,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30206,7 +30667,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30238,7 +30700,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30270,7 +30733,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30302,7 +30766,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30334,7 +30799,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30366,7 +30832,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30398,7 +30865,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30431,7 +30899,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30463,7 +30932,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30494,7 +30964,8 @@ "y": 0.0, "z": -37.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30525,7 +30996,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30556,7 +31028,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30587,7 +31060,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30694,7 +31168,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30727,7 +31202,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30759,7 +31235,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30791,7 +31268,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30823,7 +31301,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30855,7 +31334,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30887,7 +31367,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30919,7 +31400,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30951,7 +31433,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30983,7 +31466,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31015,7 +31499,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31047,7 +31532,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31079,7 +31565,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31111,7 +31598,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31143,7 +31631,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31175,7 +31664,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31207,7 +31697,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31239,7 +31730,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31272,7 +31764,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31304,7 +31797,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31336,7 +31830,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31368,7 +31863,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31400,7 +31896,8 @@ "y": 1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31432,7 +31929,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31464,7 +31962,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31496,7 +31995,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31528,7 +32028,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31560,7 +32061,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31592,7 +32094,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31624,7 +32127,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31656,7 +32160,8 @@ "y": -1.0400000000000063, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31688,7 +32193,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31720,7 +32226,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31753,7 +32260,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31785,7 +32293,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31816,7 +32325,8 @@ "y": 0.0, "z": -37.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31847,7 +32357,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31878,7 +32389,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31909,7 +32421,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32103,7 +32616,8 @@ "y": 0.0, "z": -37.800000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32136,7 +32650,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32183,7 +32698,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32216,7 +32732,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32249,7 +32766,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32282,7 +32800,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32316,7 +32835,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32349,7 +32869,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32383,7 +32904,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32416,7 +32938,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32449,7 +32972,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32571,7 +33095,8 @@ "y": 0.0, "z": -37.800000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32604,7 +33129,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32651,7 +33177,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -32684,7 +33211,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -32717,7 +33245,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32750,7 +33279,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32784,7 +33314,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32817,7 +33348,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32851,7 +33383,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32884,7 +33417,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32917,7 +33451,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33039,7 +33574,8 @@ "y": 0.0, "z": -37.800000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33072,7 +33608,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33119,7 +33656,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -33152,7 +33690,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -33185,7 +33724,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33218,7 +33758,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33252,7 +33793,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33285,7 +33827,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33319,7 +33862,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33352,7 +33896,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33385,7 +33930,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33811,7 +34357,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33844,7 +34391,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33877,7 +34425,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33923,7 +34472,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33955,7 +34505,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33989,7 +34540,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34022,7 +34574,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34056,7 +34609,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34089,7 +34643,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34122,7 +34677,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34155,7 +34711,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34188,7 +34745,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34220,7 +34778,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34251,7 +34810,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34282,7 +34842,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34314,7 +34875,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34360,7 +34922,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34406,7 +34969,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34439,7 +35003,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34472,7 +35037,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34504,7 +35070,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34536,7 +35103,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34569,7 +35137,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34601,7 +35170,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34633,7 +35203,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34666,7 +35237,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34698,7 +35270,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34730,7 +35303,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34763,7 +35337,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34795,7 +35370,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34827,7 +35403,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34859,7 +35436,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34904,7 +35482,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35025,7 +35604,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35058,7 +35638,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35091,7 +35672,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35137,7 +35719,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35169,7 +35752,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35203,7 +35787,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35236,7 +35821,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35270,7 +35856,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35303,7 +35890,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35336,7 +35924,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35369,7 +35958,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35402,7 +35992,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35434,7 +36025,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35465,7 +36057,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35496,7 +36089,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35528,7 +36122,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35574,7 +36169,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35620,7 +36216,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -35653,7 +36250,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35686,7 +36284,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35718,7 +36317,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35750,7 +36350,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35783,7 +36384,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35815,7 +36417,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35847,7 +36450,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35880,7 +36484,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35912,7 +36517,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35944,7 +36550,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -35977,7 +36584,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36009,7 +36617,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36041,7 +36650,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36073,7 +36683,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36118,7 +36729,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36239,7 +36851,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -36272,7 +36885,8 @@ "y": 0.0, "z": -14.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -36305,7 +36919,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36351,7 +36966,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36383,7 +36999,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36417,7 +37034,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36450,7 +37068,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36484,7 +37103,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36517,7 +37137,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36550,7 +37171,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36583,7 +37205,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36616,7 +37239,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36648,7 +37272,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36679,7 +37304,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36710,7 +37336,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36742,7 +37369,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36788,7 +37416,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36834,7 +37463,8 @@ "y": 0.0, "z": -14.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36867,7 +37497,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36900,7 +37531,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36932,7 +37564,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36964,7 +37597,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36997,7 +37631,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37029,7 +37664,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37061,7 +37697,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37094,7 +37731,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37126,7 +37764,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37158,7 +37797,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37191,7 +37831,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37223,7 +37864,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37255,7 +37897,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37287,7 +37930,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37332,7 +37976,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37646,7 +38291,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37678,7 +38324,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37724,7 +38371,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37756,7 +38404,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37788,7 +38437,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37834,7 +38484,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37880,7 +38531,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37911,7 +38563,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37942,7 +38595,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38037,7 +38691,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -38069,7 +38724,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -38115,7 +38771,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -38147,7 +38804,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -38179,7 +38837,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -38225,7 +38884,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38271,7 +38931,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38302,7 +38963,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38333,7 +38995,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38428,7 +39091,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38460,7 +39124,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38506,7 +39171,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38538,7 +39204,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38570,7 +39237,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38616,7 +39284,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38662,7 +39331,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38693,7 +39363,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38724,7 +39395,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38834,7 +39506,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38866,7 +39539,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38897,7 +39571,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38928,7 +39603,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38959,7 +39635,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -38990,7 +39667,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39022,7 +39700,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39068,7 +39747,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39099,7 +39779,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39130,7 +39811,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39161,7 +39843,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39268,7 +39951,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39300,7 +39984,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39331,7 +40016,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39362,7 +40048,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39393,7 +40080,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39424,7 +40112,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39456,7 +40145,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39502,7 +40192,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39533,7 +40224,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39564,7 +40256,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39595,7 +40288,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39702,7 +40396,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39734,7 +40429,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39765,7 +40461,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39796,7 +40493,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39827,7 +40525,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39858,7 +40557,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39890,7 +40590,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39936,7 +40637,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39967,7 +40669,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39998,7 +40701,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -40029,7 +40733,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -41350,7 +42055,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41382,7 +42088,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41428,7 +42135,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41460,7 +42168,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41492,7 +42201,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41538,7 +42248,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41584,7 +42295,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41615,7 +42327,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41646,7 +42359,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41741,7 +42455,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -41773,7 +42488,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -41819,7 +42535,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -41851,7 +42568,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -41883,7 +42601,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -41929,7 +42648,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41975,7 +42695,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42006,7 +42727,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42037,7 +42759,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42132,7 +42855,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -42164,7 +42888,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -42210,7 +42935,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -42242,7 +42968,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -42274,7 +43001,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -42320,7 +43048,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42366,7 +43095,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42397,7 +43127,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42428,7 +43159,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42538,7 +43270,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42570,7 +43303,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42601,7 +43335,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42632,7 +43367,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42663,7 +43399,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42694,7 +43431,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42726,7 +43464,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42772,7 +43511,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42803,7 +43543,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42834,7 +43575,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42865,7 +43607,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42972,7 +43715,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43004,7 +43748,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43035,7 +43780,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43066,7 +43812,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43097,7 +43844,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43128,7 +43876,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -43160,7 +43909,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -43206,7 +43956,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -43237,7 +43988,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -43268,7 +44020,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -43299,7 +44052,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -43406,7 +44160,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43438,7 +44193,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43469,7 +44225,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43500,7 +44257,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43531,7 +44289,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -43562,7 +44321,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -43594,7 +44354,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -43640,7 +44401,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -43671,7 +44433,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -43702,7 +44465,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -43733,7 +44497,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -43867,7 +44632,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43899,7 +44665,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43945,7 +44712,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43977,7 +44745,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44009,7 +44778,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44055,7 +44825,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44101,7 +44872,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44132,7 +44904,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44163,7 +44936,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44258,7 +45032,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44290,7 +45065,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44336,7 +45112,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44368,7 +45145,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44400,7 +45178,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -44446,7 +45225,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44492,7 +45272,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44523,7 +45304,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44554,7 +45336,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44649,7 +45432,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44681,7 +45465,8 @@ "y": 0.0, "z": -34.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44727,7 +45512,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44759,7 +45545,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44791,7 +45578,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44837,7 +45625,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44883,7 +45672,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44914,7 +45704,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44945,7 +45736,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45068,7 +45860,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45100,7 +45893,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45147,7 +45941,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45179,7 +45974,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45224,7 +46020,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45255,7 +46052,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45286,7 +46084,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45317,7 +46116,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45412,7 +46212,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -45444,7 +46245,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -45491,7 +46293,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45523,7 +46326,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45568,7 +46372,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45599,7 +46404,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45630,7 +46436,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45661,7 +46468,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45756,7 +46564,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -45788,7 +46597,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -45835,7 +46645,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45867,7 +46678,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45912,7 +46724,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45943,7 +46756,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -45974,7 +46788,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -46005,7 +46820,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -47410,7 +48226,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -47442,7 +48259,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -47474,7 +48292,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -47507,7 +48326,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -47540,7 +48360,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -47573,7 +48394,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -47606,7 +48428,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -47638,7 +48461,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -47734,7 +48558,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -47766,7 +48591,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -47798,7 +48624,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -47831,7 +48658,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -47864,7 +48692,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -47897,7 +48726,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -47930,7 +48760,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -47962,7 +48793,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -48058,7 +48890,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -48090,7 +48923,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48122,7 +48956,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48155,7 +48990,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48188,7 +49024,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48221,7 +49058,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48254,7 +49092,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48286,7 +49125,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48512,7 +49352,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48544,7 +49385,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48577,7 +49419,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48673,7 +49516,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -48705,7 +49549,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -48738,7 +49583,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48834,7 +49680,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48866,7 +49713,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48899,7 +49747,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51318,6 +52167,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "CleanupBead Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[93b724671e][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[93b724671e][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right].json new file mode 100644 index 00000000000..d9f59af3587 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[93b724671e][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right].json @@ -0,0 +1,2488 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_20ul", + "location": { + "slotName": "1" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-10ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.69 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 20 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_20ul", + "tipLength": 39.2, + "tipOverlap": 8.25 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 74.24, + "z": 25.49 + }, + "A10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 74.24, + "z": 25.49 + }, + "A11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 74.24, + "z": 25.49 + }, + "A12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 74.24, + "z": 25.49 + }, + "A2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 74.24, + "z": 25.49 + }, + "A3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 74.24, + "z": 25.49 + }, + "A4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 74.24, + "z": 25.49 + }, + "A5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 74.24, + "z": 25.49 + }, + "A6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 74.24, + "z": 25.49 + }, + "A7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 74.24, + "z": 25.49 + }, + "A8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 74.24, + "z": 25.49 + }, + "A9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 74.24, + "z": 25.49 + }, + "B1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 65.24, + "z": 25.49 + }, + "B10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 65.24, + "z": 25.49 + }, + "B11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 65.24, + "z": 25.49 + }, + "B12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 65.24, + "z": 25.49 + }, + "B2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 65.24, + "z": 25.49 + }, + "B3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 65.24, + "z": 25.49 + }, + "B4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 65.24, + "z": 25.49 + }, + "B5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 65.24, + "z": 25.49 + }, + "B6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 65.24, + "z": 25.49 + }, + "B7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 65.24, + "z": 25.49 + }, + "B8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 65.24, + "z": 25.49 + }, + "B9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 65.24, + "z": 25.49 + }, + "C1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 56.24, + "z": 25.49 + }, + "C10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 56.24, + "z": 25.49 + }, + "C11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 56.24, + "z": 25.49 + }, + "C12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 56.24, + "z": 25.49 + }, + "C2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 56.24, + "z": 25.49 + }, + "C3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 56.24, + "z": 25.49 + }, + "C4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 56.24, + "z": 25.49 + }, + "C5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 56.24, + "z": 25.49 + }, + "C6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 56.24, + "z": 25.49 + }, + "C7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 56.24, + "z": 25.49 + }, + "C8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 56.24, + "z": 25.49 + }, + "C9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 56.24, + "z": 25.49 + }, + "D1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 47.24, + "z": 25.49 + }, + "D10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 47.24, + "z": 25.49 + }, + "D11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 47.24, + "z": 25.49 + }, + "D12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 47.24, + "z": 25.49 + }, + "D2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 47.24, + "z": 25.49 + }, + "D3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 47.24, + "z": 25.49 + }, + "D4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 47.24, + "z": 25.49 + }, + "D5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 47.24, + "z": 25.49 + }, + "D6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 47.24, + "z": 25.49 + }, + "D7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 47.24, + "z": 25.49 + }, + "D8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 47.24, + "z": 25.49 + }, + "D9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 47.24, + "z": 25.49 + }, + "E1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 38.24, + "z": 25.49 + }, + "E10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 38.24, + "z": 25.49 + }, + "E11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 38.24, + "z": 25.49 + }, + "E12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 38.24, + "z": 25.49 + }, + "E2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 38.24, + "z": 25.49 + }, + "E3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 38.24, + "z": 25.49 + }, + "E4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 38.24, + "z": 25.49 + }, + "E5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 38.24, + "z": 25.49 + }, + "E6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 38.24, + "z": 25.49 + }, + "E7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 38.24, + "z": 25.49 + }, + "E8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 38.24, + "z": 25.49 + }, + "E9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 38.24, + "z": 25.49 + }, + "F1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 29.24, + "z": 25.49 + }, + "F10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 29.24, + "z": 25.49 + }, + "F11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 29.24, + "z": 25.49 + }, + "F12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 29.24, + "z": 25.49 + }, + "F2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 29.24, + "z": 25.49 + }, + "F3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 29.24, + "z": 25.49 + }, + "F4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 29.24, + "z": 25.49 + }, + "F5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 29.24, + "z": 25.49 + }, + "F6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 29.24, + "z": 25.49 + }, + "F7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 29.24, + "z": 25.49 + }, + "F8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 29.24, + "z": 25.49 + }, + "F9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 29.24, + "z": 25.49 + }, + "G1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 20.24, + "z": 25.49 + }, + "G10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 20.24, + "z": 25.49 + }, + "G11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 20.24, + "z": 25.49 + }, + "G12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 20.24, + "z": 25.49 + }, + "G2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 20.24, + "z": 25.49 + }, + "G3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 20.24, + "z": 25.49 + }, + "G4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 20.24, + "z": 25.49 + }, + "G5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 20.24, + "z": 25.49 + }, + "G6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 20.24, + "z": 25.49 + }, + "G7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 20.24, + "z": 25.49 + }, + "G8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 20.24, + "z": 25.49 + }, + "G9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 20.24, + "z": 25.49 + }, + "H1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 11.24, + "z": 25.49 + }, + "H10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 11.24, + "z": 25.49 + }, + "H11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 11.24, + "z": 25.49 + }, + "H12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 11.24, + "z": 25.49 + }, + "H2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 11.24, + "z": 25.49 + }, + "H3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 11.24, + "z": 25.49 + }, + "H4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 11.24, + "z": 25.49 + }, + "H5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 11.24, + "z": 25.49 + }, + "H6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 11.24, + "z": 25.49 + }, + "H7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 11.24, + "z": 25.49 + }, + "H8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 11.24, + "z": 25.49 + }, + "H9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 11.24, + "z": 25.49 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50de88d471ad3910c29207fb6df4502e", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-300ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.49 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 300 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_300ul", + "tipLength": 59.3, + "tipOverlap": 7.47 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 74.24, + "z": 5.39 + }, + "A10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 74.24, + "z": 5.39 + }, + "A11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 74.24, + "z": 5.39 + }, + "A12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 74.24, + "z": 5.39 + }, + "A2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 74.24, + "z": 5.39 + }, + "A3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 74.24, + "z": 5.39 + }, + "A4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 74.24, + "z": 5.39 + }, + "A5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 74.24, + "z": 5.39 + }, + "A6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 74.24, + "z": 5.39 + }, + "A7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 74.24, + "z": 5.39 + }, + "A8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 74.24, + "z": 5.39 + }, + "A9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 74.24, + "z": 5.39 + }, + "B1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 65.24, + "z": 5.39 + }, + "B10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 65.24, + "z": 5.39 + }, + "B11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 65.24, + "z": 5.39 + }, + "B12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 65.24, + "z": 5.39 + }, + "B2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 65.24, + "z": 5.39 + }, + "B3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 65.24, + "z": 5.39 + }, + "B4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 65.24, + "z": 5.39 + }, + "B5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 65.24, + "z": 5.39 + }, + "B6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 65.24, + "z": 5.39 + }, + "B7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 65.24, + "z": 5.39 + }, + "B8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 65.24, + "z": 5.39 + }, + "B9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 65.24, + "z": 5.39 + }, + "C1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 56.24, + "z": 5.39 + }, + "C10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 56.24, + "z": 5.39 + }, + "C11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 56.24, + "z": 5.39 + }, + "C12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 56.24, + "z": 5.39 + }, + "C2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 56.24, + "z": 5.39 + }, + "C3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 56.24, + "z": 5.39 + }, + "C4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 56.24, + "z": 5.39 + }, + "C5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 56.24, + "z": 5.39 + }, + "C6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 56.24, + "z": 5.39 + }, + "C7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 56.24, + "z": 5.39 + }, + "C8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 56.24, + "z": 5.39 + }, + "C9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 56.24, + "z": 5.39 + }, + "D1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 47.24, + "z": 5.39 + }, + "D10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 47.24, + "z": 5.39 + }, + "D11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 47.24, + "z": 5.39 + }, + "D12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 47.24, + "z": 5.39 + }, + "D2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 47.24, + "z": 5.39 + }, + "D3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 47.24, + "z": 5.39 + }, + "D4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 47.24, + "z": 5.39 + }, + "D5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 47.24, + "z": 5.39 + }, + "D6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 47.24, + "z": 5.39 + }, + "D7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 47.24, + "z": 5.39 + }, + "D8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 47.24, + "z": 5.39 + }, + "D9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 47.24, + "z": 5.39 + }, + "E1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 38.24, + "z": 5.39 + }, + "E10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 38.24, + "z": 5.39 + }, + "E11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 38.24, + "z": 5.39 + }, + "E12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 38.24, + "z": 5.39 + }, + "E2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 38.24, + "z": 5.39 + }, + "E3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 38.24, + "z": 5.39 + }, + "E4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 38.24, + "z": 5.39 + }, + "E5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 38.24, + "z": 5.39 + }, + "E6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 38.24, + "z": 5.39 + }, + "E7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 38.24, + "z": 5.39 + }, + "E8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 38.24, + "z": 5.39 + }, + "E9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 38.24, + "z": 5.39 + }, + "F1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 29.24, + "z": 5.39 + }, + "F10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 29.24, + "z": 5.39 + }, + "F11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 29.24, + "z": 5.39 + }, + "F12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 29.24, + "z": 5.39 + }, + "F2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 29.24, + "z": 5.39 + }, + "F3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 29.24, + "z": 5.39 + }, + "F4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 29.24, + "z": 5.39 + }, + "F5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 29.24, + "z": 5.39 + }, + "F6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 29.24, + "z": 5.39 + }, + "F7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 29.24, + "z": 5.39 + }, + "F8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 29.24, + "z": 5.39 + }, + "F9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 29.24, + "z": 5.39 + }, + "G1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 20.24, + "z": 5.39 + }, + "G10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 20.24, + "z": 5.39 + }, + "G11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 20.24, + "z": 5.39 + }, + "G12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 20.24, + "z": 5.39 + }, + "G2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 20.24, + "z": 5.39 + }, + "G3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 20.24, + "z": 5.39 + }, + "G4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 20.24, + "z": 5.39 + }, + "G5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 20.24, + "z": 5.39 + }, + "G6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 20.24, + "z": 5.39 + }, + "G7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 20.24, + "z": 5.39 + }, + "G8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 20.24, + "z": 5.39 + }, + "G9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 20.24, + "z": 5.39 + }, + "H1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 11.24, + "z": 5.39 + }, + "H10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 11.24, + "z": 5.39 + }, + "H11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 11.24, + "z": 5.39 + }, + "H12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 11.24, + "z": 5.39 + }, + "H2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 11.24, + "z": 5.39 + }, + "H3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 11.24, + "z": 5.39 + }, + "H4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 11.24, + "z": 5.39 + }, + "H5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 11.24, + "z": 5.39 + }, + "H6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 11.24, + "z": 5.39 + }, + "H7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 11.24, + "z": 5.39 + }, + "H8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 11.24, + "z": 5.39 + }, + "H9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 11.24, + "z": 5.39 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "54fddab1eb921f6e1d3aa7e96d2ac307", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p20_multi_gen2", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "error": { + "createdAt": "TIMESTAMP", + "detail": "No entry for back left nozzle 'G12' in pipette", + "errorCode": "4007", + "errorInfo": {}, + "errorType": "IncompatibleNozzleConfiguration", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "KeyError: 'G12'", + "errorCode": "4000", + "errorInfo": { + "args": "('G12',)", + "class": "KeyError", + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/hardware_control/nozzle_manager.py\", line N, in build\n back_left_row_index, back_left_column_index = _row_col_indices_for_nozzle(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/hardware_control/nozzle_manager.py\", line N, in _row_col_indices_for_nozzle\n return _row_or_col_index_for_nozzle(rows, nozzle), _row_or_col_index_for_nozzle(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/hardware_control/nozzle_manager.py\", line N, in _row_or_col_index_for_nozzle\n raise KeyError(nozzle)\n" + }, + "errorType": "PythonException", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + }, + "id": "UUID", + "key": "4b1d27a6f17f312dd76668f0c48ed406", + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], + "params": { + "configurationParams": { + "backLeftNozzle": "G12", + "frontRightNozzle": "H1", + "primaryNozzle": "H1", + "style": "QUADRANT" + }, + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "failed" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "ProtocolCommandFailedError [line 134]: Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): IncompatibleNozzleConfiguration: No entry for back left nozzle 'G12' in pipette", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "IncompatibleNozzleConfiguration: No entry for back left nozzle 'G12' in pipette", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ProtocolCommandFailedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "No entry for back left nozzle 'G12' in pipette", + "errorCode": "4007", + "errorInfo": {}, + "errorType": "IncompatibleNozzleConfiguration", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "KeyError: 'G12'", + "errorCode": "4000", + "errorInfo": { + "args": "('G12',)", + "class": "KeyError", + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/hardware_control/nozzle_manager.py\", line N, in build\n back_left_row_index, back_left_column_index = _row_col_indices_for_nozzle(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/hardware_control/nozzle_manager.py\", line N, in _row_col_indices_for_nozzle\n return _row_or_col_index_for_nozzle(rows, nozzle), _row_or_col_index_for_nozzle(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/hardware_control/nozzle_manager.py\", line N, in _row_or_col_index_for_nozzle\n raise KeyError(nozzle)\n" + }, + "errorType": "PythonException", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ] + } + ] + } + ], + "files": [ + { + "name": "OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_20ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_20ul", + "location": { + "slotName": "1" + } + }, + { + "definitionUri": "opentrons/opentrons_96_tiprack_300ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "2" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Invalid tip configs that should error" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p20_multi_gen2" + } + ], + "result": "not-ok", + "robotType": "OT-2 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[94913d2988][OT2_S_v3_P300SGen1_None_Gen1PipetteSimple].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[94913d2988][OT2_S_v3_P300SGen1_None_Gen1PipetteSimple].json index bf268eb5abb..405df785df9 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[94913d2988][OT2_S_v3_P300SGen1_None_Gen1PipetteSimple].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[94913d2988][OT2_S_v3_P300SGen1_None_Gen1PipetteSimple].json @@ -3817,7 +3817,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3850,7 +3851,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3947,7 +3949,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3980,7 +3983,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -4077,7 +4081,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4110,7 +4115,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -4207,7 +4213,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4240,7 +4247,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -4337,7 +4345,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4370,7 +4379,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -4467,7 +4477,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4500,7 +4511,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -4597,7 +4609,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4630,7 +4643,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -4727,7 +4741,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4760,7 +4775,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -4857,7 +4873,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4890,7 +4907,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -4987,7 +5005,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5020,7 +5039,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -5117,7 +5137,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5150,7 +5171,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -5247,7 +5269,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5280,7 +5303,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -5377,7 +5401,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5410,7 +5435,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -5507,7 +5533,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5540,7 +5567,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -5637,7 +5665,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5670,7 +5699,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -5767,7 +5797,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5800,7 +5831,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -5897,7 +5929,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5930,7 +5963,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -6027,7 +6061,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6060,7 +6095,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -6157,7 +6193,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6190,7 +6227,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -6287,7 +6325,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6320,7 +6359,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -6434,6 +6474,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[95da6fbef2][Flex_S_2_15_P1000M_GRIP_HS_TM_MB_OmegaHDQDNAExtractionBacteria].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[95da6fbef2][Flex_S_2_15_P1000M_GRIP_HS_TM_MB_OmegaHDQDNAExtractionBacteria].json index 98d5736d5f8..004f5251126 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[95da6fbef2][Flex_S_2_15_P1000M_GRIP_HS_TM_MB_OmegaHDQDNAExtractionBacteria].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[95da6fbef2][Flex_S_2_15_P1000M_GRIP_HS_TM_MB_OmegaHDQDNAExtractionBacteria].json @@ -14905,7 +14905,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14938,7 +14939,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14971,7 +14973,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15004,7 +15007,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15037,7 +15041,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15070,7 +15075,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15117,7 +15123,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15149,7 +15156,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15181,7 +15189,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15214,7 +15223,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15276,7 +15286,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15309,7 +15320,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15342,7 +15354,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15375,7 +15388,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15408,7 +15422,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15441,7 +15456,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15474,7 +15490,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15507,7 +15524,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15540,7 +15558,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15573,7 +15592,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15606,7 +15626,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15639,7 +15660,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15672,7 +15694,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15705,7 +15728,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15738,7 +15762,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15771,7 +15796,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15804,7 +15830,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15837,7 +15864,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15870,7 +15898,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15903,7 +15932,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15936,7 +15966,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15969,7 +16000,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16002,7 +16034,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16035,7 +16068,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16068,7 +16102,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16101,7 +16136,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16134,7 +16170,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16167,7 +16204,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16200,7 +16238,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16233,7 +16272,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16266,7 +16306,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16299,7 +16340,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16332,7 +16374,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16365,7 +16408,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16398,7 +16442,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16431,7 +16476,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16464,7 +16510,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16497,7 +16544,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16530,7 +16578,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16563,7 +16612,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16596,7 +16646,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16629,7 +16680,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16662,7 +16714,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16695,7 +16748,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16728,7 +16782,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16761,7 +16816,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16794,7 +16850,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16827,7 +16884,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16860,7 +16918,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16893,7 +16952,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16926,7 +16986,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16959,7 +17020,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16992,7 +17054,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17025,7 +17088,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17058,7 +17122,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17091,7 +17156,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17124,7 +17190,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17157,7 +17224,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17190,7 +17258,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17223,7 +17292,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17256,7 +17326,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17289,7 +17360,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17322,7 +17394,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17355,7 +17428,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17388,7 +17462,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17421,7 +17496,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17454,7 +17530,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17487,7 +17564,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17520,7 +17598,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17553,7 +17632,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17586,7 +17666,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17619,7 +17700,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17652,7 +17734,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17685,7 +17768,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17718,7 +17802,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17751,7 +17836,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17784,7 +17870,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17817,7 +17904,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17850,7 +17938,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17883,7 +17972,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17916,7 +18006,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17949,7 +18040,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17982,7 +18074,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18015,7 +18108,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18048,7 +18142,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18081,7 +18176,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18114,7 +18210,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18147,7 +18244,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18180,7 +18278,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18213,7 +18312,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18246,7 +18346,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18279,7 +18380,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18312,7 +18414,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18345,7 +18448,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18378,7 +18482,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18411,7 +18516,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18444,7 +18550,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18477,7 +18584,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18510,7 +18618,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18543,7 +18652,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18576,7 +18686,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18609,7 +18720,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18642,7 +18754,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18675,7 +18788,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18708,7 +18822,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18741,7 +18856,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18774,7 +18890,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18807,7 +18924,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18840,7 +18958,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18873,7 +18992,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18906,7 +19026,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18939,7 +19060,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18972,7 +19094,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19005,7 +19128,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19038,7 +19162,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19071,7 +19196,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19104,7 +19230,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19137,7 +19264,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19170,7 +19298,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19203,7 +19332,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19236,7 +19366,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19269,7 +19400,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19302,7 +19434,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19335,7 +19468,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19368,7 +19502,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19401,7 +19536,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19434,7 +19570,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19467,7 +19604,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19500,7 +19638,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19533,7 +19672,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19566,7 +19706,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19599,7 +19740,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19632,7 +19774,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19665,7 +19808,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19698,7 +19842,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19731,7 +19876,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19764,7 +19910,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19797,7 +19944,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19830,7 +19978,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19863,7 +20012,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19896,7 +20046,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19929,7 +20080,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19962,7 +20114,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19995,7 +20148,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20028,7 +20182,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20061,7 +20216,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20232,7 +20388,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20264,7 +20421,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20296,7 +20454,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20329,7 +20488,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20361,7 +20521,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20569,7 +20730,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20601,7 +20763,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20633,7 +20796,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20666,7 +20830,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20699,7 +20864,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20732,7 +20898,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20765,7 +20932,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20798,7 +20966,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20831,7 +21000,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20864,7 +21034,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20897,7 +21068,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20930,7 +21102,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20963,7 +21136,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20996,7 +21170,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21029,7 +21204,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21062,7 +21238,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21095,7 +21272,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21128,7 +21306,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21161,7 +21340,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21194,7 +21374,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21227,7 +21408,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21260,7 +21442,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21293,7 +21476,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21326,7 +21510,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21359,7 +21544,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21392,7 +21578,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21425,7 +21612,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21458,7 +21646,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21491,7 +21680,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21524,7 +21714,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21557,7 +21748,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21590,7 +21782,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21623,7 +21816,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21656,7 +21850,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21689,7 +21884,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21722,7 +21918,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21755,7 +21952,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21788,7 +21986,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21821,7 +22020,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21854,7 +22054,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21887,7 +22088,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21920,7 +22122,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21953,7 +22156,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21986,7 +22190,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22019,7 +22224,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22052,7 +22258,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22085,7 +22292,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22118,7 +22326,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22151,7 +22360,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22184,7 +22394,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22217,7 +22428,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22250,7 +22462,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22283,7 +22496,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22316,7 +22530,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22349,7 +22564,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22382,7 +22598,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22415,7 +22632,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22448,7 +22666,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22481,7 +22700,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22514,7 +22734,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22547,7 +22768,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22580,7 +22802,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22613,7 +22836,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22646,7 +22870,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22679,7 +22904,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22712,7 +22938,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22745,7 +22972,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22778,7 +23006,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22811,7 +23040,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22844,7 +23074,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22877,7 +23108,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22910,7 +23142,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22943,7 +23176,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22976,7 +23210,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23009,7 +23244,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23042,7 +23278,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23075,7 +23312,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23108,7 +23346,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23141,7 +23380,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23174,7 +23414,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23207,7 +23448,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23240,7 +23482,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23273,7 +23516,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23306,7 +23550,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23339,7 +23584,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23372,7 +23618,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23405,7 +23652,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23438,7 +23686,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23471,7 +23720,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23504,7 +23754,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23537,7 +23788,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23570,7 +23822,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23603,7 +23856,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23636,7 +23890,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23669,7 +23924,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23702,7 +23958,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23735,7 +23992,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23768,7 +24026,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23801,7 +24060,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23834,7 +24094,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23867,7 +24128,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23900,7 +24162,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23933,7 +24196,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23966,7 +24230,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23999,7 +24264,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24032,7 +24298,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24065,7 +24332,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24098,7 +24366,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24131,7 +24400,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24164,7 +24434,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24197,7 +24468,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24230,7 +24502,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24263,7 +24536,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24296,7 +24570,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24329,7 +24604,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24362,7 +24638,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24395,7 +24672,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24428,7 +24706,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24461,7 +24740,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24494,7 +24774,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24527,7 +24808,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24560,7 +24842,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24593,7 +24876,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24626,7 +24910,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24659,7 +24944,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24692,7 +24978,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24725,7 +25012,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24758,7 +25046,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24791,7 +25080,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24824,7 +25114,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24857,7 +25148,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24890,7 +25182,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24923,7 +25216,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24956,7 +25250,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24989,7 +25284,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25022,7 +25318,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25055,7 +25352,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25088,7 +25386,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25121,7 +25420,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25154,7 +25454,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25187,7 +25488,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25220,7 +25522,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25253,7 +25556,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25286,7 +25590,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25319,7 +25624,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25352,7 +25658,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25385,7 +25692,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25418,7 +25726,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25451,7 +25760,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25484,7 +25794,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25641,7 +25952,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25674,7 +25986,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25707,7 +26020,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25740,7 +26054,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25773,7 +26088,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25806,7 +26122,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25839,7 +26156,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25872,7 +26190,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25905,7 +26224,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25938,7 +26258,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25971,7 +26292,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26004,7 +26326,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26037,7 +26360,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26070,7 +26394,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26103,7 +26428,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26136,7 +26462,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26169,7 +26496,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26202,7 +26530,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26235,7 +26564,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26268,7 +26598,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26301,7 +26632,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26334,7 +26666,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26367,7 +26700,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26400,7 +26734,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26433,7 +26768,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26466,7 +26802,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26499,7 +26836,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26532,7 +26870,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26565,7 +26904,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26598,7 +26938,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26631,7 +26972,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26664,7 +27006,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26697,7 +27040,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26730,7 +27074,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26763,7 +27108,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26796,7 +27142,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26829,7 +27176,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26862,7 +27210,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26895,7 +27244,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26928,7 +27278,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26961,7 +27312,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26994,7 +27346,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27027,7 +27380,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27060,7 +27414,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27093,7 +27448,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27126,7 +27482,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27159,7 +27516,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27192,7 +27550,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27225,7 +27584,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27258,7 +27618,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27291,7 +27652,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27324,7 +27686,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27357,7 +27720,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27390,7 +27754,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27627,7 +27992,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27660,7 +28026,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27819,7 +28186,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27852,7 +28220,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27884,7 +28253,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27915,7 +28285,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27947,7 +28318,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28169,7 +28541,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28202,7 +28575,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28345,7 +28719,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28378,7 +28753,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28410,7 +28786,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28441,7 +28818,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28473,7 +28851,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28695,7 +29074,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28728,7 +29108,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28871,7 +29252,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28904,7 +29286,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28936,7 +29319,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28967,7 +29351,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28999,7 +29384,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29221,7 +29607,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29254,7 +29641,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29697,7 +30085,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29730,7 +30119,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29763,7 +30153,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29796,7 +30187,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29829,7 +30221,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29862,7 +30255,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29895,7 +30289,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29928,7 +30323,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29961,7 +30357,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29994,7 +30391,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30027,7 +30425,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30060,7 +30459,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30093,7 +30493,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30126,7 +30527,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30159,7 +30561,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30192,7 +30595,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30225,7 +30629,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30258,7 +30663,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30291,7 +30697,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30324,7 +30731,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30357,7 +30765,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30390,7 +30799,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30423,7 +30833,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30456,7 +30867,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30489,7 +30901,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30522,7 +30935,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30555,7 +30969,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30588,7 +31003,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30621,7 +31037,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30654,7 +31071,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30687,7 +31105,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30720,7 +31139,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30753,7 +31173,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30786,7 +31207,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30819,7 +31241,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30852,7 +31275,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30885,7 +31309,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30918,7 +31343,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30951,7 +31377,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30984,7 +31411,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31017,7 +31445,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31050,7 +31479,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31083,7 +31513,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31116,7 +31547,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31149,7 +31581,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31182,7 +31615,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31215,7 +31649,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31248,7 +31683,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31281,7 +31717,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31314,7 +31751,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31347,7 +31785,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31380,7 +31819,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31589,7 +32029,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31622,7 +32063,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31831,6 +32273,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Zach Galluzzo ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9618a6623c][OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9618a6623c][OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError].json index cbad73a3a2d..8a871949e46 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9618a6623c][OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9618a6623c][OT2_X_v2_11_P300S_TC1_TC2_ThermocyclerMoamError].json @@ -2775,6 +2775,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.11" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[973fa979e6][Flex_S_v2_16_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[973fa979e6][Flex_S_v2_16_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json index cbf301b89e7..5538166da59 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[973fa979e6][Flex_S_v2_16_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[973fa979e6][Flex_S_v2_16_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json @@ -171,6 +171,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9bcb0a3f13][pl_normalization_with_csv].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9bcb0a3f13][pl_normalization_with_csv].json index b0f0b8ac0bd..23fd7f389a0 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9bcb0a3f13][pl_normalization_with_csv].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9bcb0a3f13][pl_normalization_with_csv].json @@ -6024,6 +6024,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Krishna Soma ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9e56ee92f6][Flex_X_v2_16_P1000_96_GRIP_DropLabwareIntoTrashBin].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9e56ee92f6][Flex_X_v2_16_P1000_96_GRIP_DropLabwareIntoTrashBin].json index eedcd721687..aba00388845 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9e56ee92f6][Flex_X_v2_16_P1000_96_GRIP_DropLabwareIntoTrashBin].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[9e56ee92f6][Flex_X_v2_16_P1000_96_GRIP_DropLabwareIntoTrashBin].json @@ -1350,7 +1350,14 @@ }, "id": "UUID", "key": "4cca9753dc59d176eee1522349363a75", - "notes": [], + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], "params": { "labwareId": "UUID", "newLocation": { @@ -1428,6 +1435,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a01a35c14a][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a01a35c14a][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol3].json index 5a508d84d58..7cb88cd0308 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a01a35c14a][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a01a35c14a][Flex_X_v2_16_NO_PIPETTES_TrashBinInStagingAreaCol3].json @@ -153,6 +153,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a06502b2dc][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_description].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a06502b2dc][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_description].json index 7808bbc2d03..e2a5dced311 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a06502b2dc][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_description].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a06502b2dc][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_description].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a08c261369][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModulesNoFixtures].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a08c261369][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModulesNoFixtures].json index 24db4fa06ff..5bc309d3cac 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a08c261369][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModulesNoFixtures].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a08c261369][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModulesNoFixtures].json @@ -8924,7 +8924,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8957,7 +8958,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9054,7 +9056,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9087,7 +9090,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9252,7 +9256,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9285,7 +9290,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9382,7 +9388,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9415,7 +9422,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9567,6 +9575,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "High Quality H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a0b755a1a1][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_west].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a0b755a1a1][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_west].json new file mode 100644 index 00000000000..68185db5dbd --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a0b755a1a1][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_west].json @@ -0,0 +1,1501 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-1000ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 97.47 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_1000ul", + "tipLength": 88, + "tipOverlap": 7.95 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.24, + "z": 9.47 + }, + "A10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.24, + "z": 9.47 + }, + "A11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.24, + "z": 9.47 + }, + "A12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.24, + "z": 9.47 + }, + "A2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.24, + "z": 9.47 + }, + "A3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.24, + "z": 9.47 + }, + "A4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.24, + "z": 9.47 + }, + "A5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.24, + "z": 9.47 + }, + "A6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.24, + "z": 9.47 + }, + "A7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.24, + "z": 9.47 + }, + "A8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.24, + "z": 9.47 + }, + "A9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.24, + "z": 9.47 + }, + "B1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.24, + "z": 9.47 + }, + "B10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.24, + "z": 9.47 + }, + "B11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.24, + "z": 9.47 + }, + "B12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.24, + "z": 9.47 + }, + "B2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.24, + "z": 9.47 + }, + "B3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.24, + "z": 9.47 + }, + "B4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.24, + "z": 9.47 + }, + "B5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.24, + "z": 9.47 + }, + "B6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.24, + "z": 9.47 + }, + "B7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.24, + "z": 9.47 + }, + "B8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.24, + "z": 9.47 + }, + "B9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.24, + "z": 9.47 + }, + "C1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.24, + "z": 9.47 + }, + "C10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.24, + "z": 9.47 + }, + "C11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.24, + "z": 9.47 + }, + "C12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.24, + "z": 9.47 + }, + "C2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.24, + "z": 9.47 + }, + "C3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.24, + "z": 9.47 + }, + "C4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.24, + "z": 9.47 + }, + "C5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.24, + "z": 9.47 + }, + "C6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.24, + "z": 9.47 + }, + "C7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.24, + "z": 9.47 + }, + "C8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.24, + "z": 9.47 + }, + "C9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.24, + "z": 9.47 + }, + "D1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.24, + "z": 9.47 + }, + "D10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.24, + "z": 9.47 + }, + "D11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.24, + "z": 9.47 + }, + "D12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.24, + "z": 9.47 + }, + "D2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.24, + "z": 9.47 + }, + "D3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.24, + "z": 9.47 + }, + "D4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.24, + "z": 9.47 + }, + "D5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.24, + "z": 9.47 + }, + "D6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.24, + "z": 9.47 + }, + "D7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.24, + "z": 9.47 + }, + "D8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.24, + "z": 9.47 + }, + "D9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.24, + "z": 9.47 + }, + "E1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.24, + "z": 9.47 + }, + "E10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.24, + "z": 9.47 + }, + "E11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.24, + "z": 9.47 + }, + "E12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.24, + "z": 9.47 + }, + "E2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.24, + "z": 9.47 + }, + "E3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.24, + "z": 9.47 + }, + "E4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.24, + "z": 9.47 + }, + "E5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.24, + "z": 9.47 + }, + "E6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.24, + "z": 9.47 + }, + "E7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.24, + "z": 9.47 + }, + "E8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.24, + "z": 9.47 + }, + "E9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.24, + "z": 9.47 + }, + "F1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.24, + "z": 9.47 + }, + "F10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.24, + "z": 9.47 + }, + "F11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.24, + "z": 9.47 + }, + "F12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.24, + "z": 9.47 + }, + "F2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.24, + "z": 9.47 + }, + "F3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.24, + "z": 9.47 + }, + "F4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.24, + "z": 9.47 + }, + "F5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.24, + "z": 9.47 + }, + "F6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.24, + "z": 9.47 + }, + "F7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.24, + "z": 9.47 + }, + "F8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.24, + "z": 9.47 + }, + "F9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.24, + "z": 9.47 + }, + "G1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.24, + "z": 9.47 + }, + "G10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.24, + "z": 9.47 + }, + "G11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.24, + "z": 9.47 + }, + "G12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.24, + "z": 9.47 + }, + "G2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.24, + "z": 9.47 + }, + "G3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.24, + "z": 9.47 + }, + "G4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.24, + "z": 9.47 + }, + "G5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.24, + "z": 9.47 + }, + "G6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.24, + "z": 9.47 + }, + "G7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.24, + "z": 9.47 + }, + "G8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.24, + "z": 9.47 + }, + "G9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.24, + "z": 9.47 + }, + "H1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.24, + "z": 9.47 + }, + "H10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.24, + "z": 9.47 + }, + "H11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.24, + "z": 9.47 + }, + "H12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.24, + "z": 9.47 + }, + "H2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.24, + "z": 9.47 + }, + "H3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.24, + "z": 9.47 + }, + "H4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.24, + "z": 9.47 + }, + "H5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.24, + "z": 9.47 + }, + "H6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.24, + "z": 9.47 + }, + "H7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.24, + "z": 9.47 + }, + "H8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.24, + "z": 9.47 + }, + "H9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.24, + "z": 9.47 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "B1" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/tube-rack-set-1" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 124.35 + }, + "gripperOffsets": {}, + "groups": [ + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352095", + "352096", + "352097", + "352099", + "352196" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 6x15 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A2", + "B1", + "B2", + "C1", + "C2" + ] + }, + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352070", + "352098" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 4x50 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A3", + "A4", + "B3", + "B4" + ] + } + ], + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Opentrons 10 Tube Rack with Falcon 4x50 mL, 6x15 mL Conical", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1" + ], + [ + "A2", + "B2", + "C2" + ], + [ + "A3", + "B3" + ], + [ + "A4", + "B4" + ] + ], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 67.75, + "z": 6.85 + }, + "A2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 67.75, + "z": 6.85 + }, + "A3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 60.25, + "z": 7.3 + }, + "A4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 60.25, + "z": 7.3 + }, + "B1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 42.75, + "z": 6.85 + }, + "B2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 42.75, + "z": 6.85 + }, + "B3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 25.25, + "z": 7.3 + }, + "B4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 25.25, + "z": 7.3 + }, + "C1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 17.75, + "z": 6.85 + }, + "C2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 17.75, + "z": 6.85 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "PartialTipMovementNotAllowedError [line 479]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with H12 nozzle partial configuration will result in collision with items in deck slot B1.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with H12 nozzle partial configuration will result in collision with items in deck slot B1.", + "errorCode": "2004", + "errorInfo": {}, + "errorType": "PartialTipMovementNotAllowedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north_west.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + } + }, + { + "definitionUri": "opentrons/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical/1", + "id": "UUID", + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "B1" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Too tall labware on pickup tip" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a0dad2eb8e][pl_SamplePrep_MS_Cleanup_Flex_upto96].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a0dad2eb8e][pl_SamplePrep_MS_Cleanup_Flex_upto96].json index 02de93ebc9a..7aecea25f6a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a0dad2eb8e][pl_SamplePrep_MS_Cleanup_Flex_upto96].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a0dad2eb8e][pl_SamplePrep_MS_Cleanup_Flex_upto96].json @@ -13802,7 +13802,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13836,7 +13837,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13869,7 +13871,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13903,7 +13906,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13936,7 +13940,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13969,7 +13974,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14002,7 +14008,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14048,7 +14055,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14080,7 +14088,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14113,7 +14122,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14146,7 +14156,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14192,7 +14203,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14288,7 +14300,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14322,7 +14335,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14355,7 +14369,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14389,7 +14404,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14422,7 +14438,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14455,7 +14472,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14488,7 +14506,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14534,7 +14553,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14566,7 +14586,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14599,7 +14620,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14632,7 +14654,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14678,7 +14701,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14774,7 +14798,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14808,7 +14833,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14841,7 +14867,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14875,7 +14902,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14908,7 +14936,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14941,7 +14970,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14974,7 +15004,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15020,7 +15051,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15052,7 +15084,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15085,7 +15118,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15118,7 +15152,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15164,7 +15199,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15260,7 +15296,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15294,7 +15331,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15327,7 +15365,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15361,7 +15400,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15394,7 +15434,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15427,7 +15468,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15460,7 +15502,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15506,7 +15549,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15538,7 +15582,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15571,7 +15616,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15604,7 +15650,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15650,7 +15697,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15746,7 +15794,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15780,7 +15829,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15813,7 +15863,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15847,7 +15898,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15880,7 +15932,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15913,7 +15966,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15946,7 +16000,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15992,7 +16047,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -16024,7 +16080,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -16057,7 +16114,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -16090,7 +16148,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -16136,7 +16195,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -16232,7 +16292,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -16266,7 +16327,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -16299,7 +16361,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -16333,7 +16396,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -16366,7 +16430,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -16399,7 +16464,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -16432,7 +16498,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -16478,7 +16545,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -16510,7 +16578,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -16543,7 +16612,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -16576,7 +16646,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -16622,7 +16693,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -16718,7 +16790,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16752,7 +16825,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16785,7 +16859,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16819,7 +16894,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16852,7 +16928,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16885,7 +16962,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16918,7 +16996,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16964,7 +17043,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16996,7 +17076,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17029,7 +17110,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17062,7 +17144,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17108,7 +17191,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17204,7 +17288,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -17238,7 +17323,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -17271,7 +17357,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -17305,7 +17392,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -17338,7 +17426,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -17371,7 +17460,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -17404,7 +17494,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -17450,7 +17541,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -17482,7 +17574,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -17515,7 +17608,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -17548,7 +17642,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -17594,7 +17689,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -17690,7 +17786,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -17724,7 +17821,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -17757,7 +17855,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -17791,7 +17890,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -17824,7 +17924,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -17857,7 +17958,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -17890,7 +17992,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -17936,7 +18039,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -17968,7 +18072,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -18001,7 +18106,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -18034,7 +18140,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -18080,7 +18187,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -18176,7 +18284,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18210,7 +18319,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18243,7 +18353,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18277,7 +18388,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18310,7 +18422,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18343,7 +18456,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18376,7 +18490,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18422,7 +18537,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18454,7 +18570,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18487,7 +18604,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18520,7 +18638,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18566,7 +18685,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18662,7 +18782,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -18696,7 +18817,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -18729,7 +18851,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -18763,7 +18886,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -18796,7 +18920,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -18829,7 +18954,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -18862,7 +18988,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -18908,7 +19035,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -18940,7 +19068,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -18973,7 +19102,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19006,7 +19136,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19052,7 +19183,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19148,7 +19280,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19182,7 +19315,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19215,7 +19349,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19249,7 +19384,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19282,7 +19418,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19315,7 +19452,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19348,7 +19486,8 @@ "y": 0.0, "z": -14.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19394,7 +19533,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19426,7 +19566,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19459,7 +19600,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19492,7 +19634,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19538,7 +19681,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19670,7 +19814,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19704,7 +19849,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19737,7 +19883,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19771,7 +19918,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19804,7 +19952,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19838,7 +19987,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19871,7 +20021,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19905,7 +20056,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19938,7 +20090,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19971,7 +20124,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20004,7 +20158,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20050,7 +20205,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20082,7 +20238,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20115,7 +20272,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20148,7 +20306,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20180,7 +20339,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20212,7 +20372,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20246,7 +20407,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20279,7 +20441,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20313,7 +20476,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20346,7 +20510,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20380,7 +20545,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20413,7 +20579,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20447,7 +20614,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20480,7 +20648,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20513,7 +20682,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20546,7 +20716,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20592,7 +20763,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20624,7 +20796,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20657,7 +20830,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20690,7 +20864,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20722,7 +20897,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20754,7 +20930,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20788,7 +20965,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20821,7 +20999,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20855,7 +21034,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20888,7 +21068,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20922,7 +21103,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20955,7 +21137,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20989,7 +21172,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21022,7 +21206,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21055,7 +21240,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21088,7 +21274,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21134,7 +21321,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21166,7 +21354,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21199,7 +21388,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21232,7 +21422,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21264,7 +21455,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21296,7 +21488,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21330,7 +21523,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21363,7 +21557,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21397,7 +21592,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21430,7 +21626,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21464,7 +21661,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21497,7 +21695,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21531,7 +21730,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21564,7 +21764,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21597,7 +21798,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21630,7 +21832,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21676,7 +21879,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21708,7 +21912,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21741,7 +21946,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21774,7 +21980,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21806,7 +22013,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21838,7 +22046,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21872,7 +22081,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21905,7 +22115,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21939,7 +22150,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21972,7 +22184,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22006,7 +22219,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22039,7 +22253,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22073,7 +22288,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22106,7 +22322,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22139,7 +22356,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22172,7 +22390,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22218,7 +22437,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22250,7 +22470,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22283,7 +22504,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22316,7 +22538,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22348,7 +22571,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22380,7 +22604,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22414,7 +22639,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22447,7 +22673,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22481,7 +22708,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22514,7 +22742,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22548,7 +22777,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22581,7 +22811,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22615,7 +22846,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22648,7 +22880,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22681,7 +22914,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22714,7 +22948,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22760,7 +22995,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22792,7 +23028,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22825,7 +23062,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -22858,7 +23096,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -22890,7 +23129,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -22922,7 +23162,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22956,7 +23197,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22989,7 +23231,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23023,7 +23266,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23056,7 +23300,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23090,7 +23335,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23123,7 +23369,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23157,7 +23404,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23190,7 +23438,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23223,7 +23472,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23256,7 +23506,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23302,7 +23553,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23334,7 +23586,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23367,7 +23620,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23400,7 +23654,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23432,7 +23687,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23464,7 +23720,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23498,7 +23755,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23531,7 +23789,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23565,7 +23824,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23598,7 +23858,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23632,7 +23893,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23665,7 +23927,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23699,7 +23962,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23732,7 +23996,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23765,7 +24030,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23798,7 +24064,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23844,7 +24111,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23876,7 +24144,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23909,7 +24178,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -23942,7 +24212,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -23974,7 +24245,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -24006,7 +24278,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24040,7 +24313,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24073,7 +24347,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24107,7 +24382,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24140,7 +24416,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24174,7 +24451,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24207,7 +24485,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24241,7 +24520,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24274,7 +24554,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24307,7 +24588,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24340,7 +24622,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24386,7 +24669,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24418,7 +24702,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24451,7 +24736,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -24484,7 +24770,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -24516,7 +24803,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -24548,7 +24836,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24582,7 +24871,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24615,7 +24905,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24649,7 +24940,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24682,7 +24974,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24716,7 +25009,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24749,7 +25043,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24783,7 +25078,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24816,7 +25112,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24849,7 +25146,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24882,7 +25180,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24928,7 +25227,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24960,7 +25260,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24993,7 +25294,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -25026,7 +25328,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -25058,7 +25361,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -25090,7 +25394,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25124,7 +25429,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25157,7 +25463,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25191,7 +25498,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25224,7 +25532,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25258,7 +25567,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25291,7 +25601,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25325,7 +25636,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25358,7 +25670,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25391,7 +25704,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25424,7 +25738,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25470,7 +25785,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25502,7 +25818,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25535,7 +25852,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25568,7 +25886,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25600,7 +25919,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25632,7 +25952,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25666,7 +25987,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25699,7 +26021,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25733,7 +26056,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25766,7 +26090,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25800,7 +26125,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25833,7 +26159,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25867,7 +26194,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25900,7 +26228,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25933,7 +26262,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25966,7 +26296,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26012,7 +26343,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26044,7 +26376,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26077,7 +26410,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26110,7 +26444,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26142,7 +26477,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26238,7 +26574,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26270,7 +26607,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26302,7 +26640,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26335,7 +26674,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26367,7 +26707,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26399,7 +26740,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26431,7 +26773,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26463,7 +26806,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26496,7 +26840,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26528,7 +26873,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26560,7 +26906,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26592,7 +26939,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26624,7 +26972,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26657,7 +27006,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26689,7 +27039,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26721,7 +27072,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26753,7 +27105,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26785,7 +27138,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26818,7 +27172,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26850,7 +27205,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26882,7 +27238,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26914,7 +27271,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26946,7 +27304,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26979,7 +27338,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27011,7 +27371,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27043,7 +27404,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27075,7 +27437,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27107,7 +27470,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27140,7 +27504,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27172,7 +27537,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27204,7 +27570,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27236,7 +27603,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27268,7 +27636,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27301,7 +27670,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27333,7 +27703,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27365,7 +27736,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27397,7 +27769,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27429,7 +27802,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27462,7 +27836,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -27494,7 +27869,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -27526,7 +27902,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27558,7 +27935,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27590,7 +27968,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27623,7 +28002,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -27655,7 +28035,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -27687,7 +28068,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27719,7 +28101,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27751,7 +28134,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27784,7 +28168,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -27816,7 +28201,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -27848,7 +28234,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27880,7 +28267,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27912,7 +28300,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27945,7 +28334,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -27977,7 +28367,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -28009,7 +28400,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28041,7 +28433,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28073,7 +28466,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28106,7 +28500,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -28138,7 +28533,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -28170,7 +28566,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28202,7 +28599,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28234,7 +28632,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28267,7 +28666,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28300,7 +28700,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28332,7 +28733,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28364,7 +28766,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28397,7 +28800,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28430,7 +28834,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28462,7 +28867,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28494,7 +28900,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28527,7 +28934,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28560,7 +28968,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28592,7 +29001,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28624,7 +29034,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28657,7 +29068,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28690,7 +29102,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28722,7 +29135,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28754,7 +29168,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28787,7 +29202,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28820,7 +29236,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28852,7 +29269,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28884,7 +29302,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28917,7 +29336,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -28950,7 +29370,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28982,7 +29403,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29014,7 +29436,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29047,7 +29470,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29080,7 +29504,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29112,7 +29537,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29144,7 +29570,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29177,7 +29604,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -29210,7 +29638,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29242,7 +29671,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29274,7 +29704,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29307,7 +29738,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -29340,7 +29772,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29372,7 +29805,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29404,7 +29838,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29437,7 +29872,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -29470,7 +29906,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29502,7 +29939,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29534,7 +29972,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29567,7 +30006,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -29600,7 +30040,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29632,7 +30073,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29664,7 +30106,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29697,7 +30140,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -29949,7 +30393,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29981,7 +30426,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30013,7 +30459,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30046,7 +30493,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30078,7 +30526,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30174,7 +30623,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30206,7 +30656,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30238,7 +30689,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30271,7 +30723,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30303,7 +30756,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30399,7 +30853,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30431,7 +30886,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30463,7 +30919,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30496,7 +30953,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30528,7 +30986,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30624,7 +31083,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -30656,7 +31116,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -30688,7 +31149,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -30721,7 +31183,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30753,7 +31216,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30849,7 +31313,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30881,7 +31346,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30913,7 +31379,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30946,7 +31413,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30978,7 +31446,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31074,7 +31543,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31106,7 +31576,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31138,7 +31609,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31171,7 +31643,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31203,7 +31676,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31299,7 +31773,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31331,7 +31806,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31363,7 +31839,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31396,7 +31873,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31428,7 +31906,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31524,7 +32003,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -31556,7 +32036,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -31588,7 +32069,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -31621,7 +32103,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31653,7 +32136,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31749,7 +32233,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -31781,7 +32266,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -31813,7 +32299,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -31846,7 +32333,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31878,7 +32366,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31974,7 +32463,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -32006,7 +32496,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -32038,7 +32529,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -32071,7 +32563,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32103,7 +32596,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32199,7 +32693,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -32231,7 +32726,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -32263,7 +32759,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -32296,7 +32793,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32328,7 +32826,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32424,7 +32923,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32456,7 +32956,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32488,7 +32989,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32521,7 +33023,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32553,7 +33056,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32649,7 +33153,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32681,7 +33186,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32713,7 +33219,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32746,7 +33253,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32778,7 +33286,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32874,7 +33383,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32906,7 +33416,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32938,7 +33449,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32971,7 +33483,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33003,7 +33516,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33099,7 +33613,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33131,7 +33646,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33163,7 +33679,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33196,7 +33713,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33228,7 +33746,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33324,7 +33843,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -33356,7 +33876,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -33388,7 +33909,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -33421,7 +33943,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33453,7 +33976,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33549,7 +34073,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33581,7 +34106,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33613,7 +34139,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33646,7 +34173,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33678,7 +34206,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33774,7 +34303,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33806,7 +34336,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33838,7 +34369,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33871,7 +34403,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33903,7 +34436,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33999,7 +34533,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34031,7 +34566,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34063,7 +34599,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34096,7 +34633,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34128,7 +34666,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34224,7 +34763,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -34256,7 +34796,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -34288,7 +34829,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -34321,7 +34863,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34353,7 +34896,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34449,7 +34993,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34481,7 +35026,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34513,7 +35059,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -34546,7 +35093,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34578,7 +35126,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34674,7 +35223,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -34706,7 +35256,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -34738,7 +35289,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -34771,7 +35323,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34803,7 +35356,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34899,7 +35453,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34931,7 +35486,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34963,7 +35519,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34996,7 +35553,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35028,7 +35586,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35124,7 +35683,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35156,7 +35716,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35188,7 +35749,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35221,7 +35783,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35253,7 +35816,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35381,7 +35945,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35413,7 +35978,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35445,7 +36011,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35478,7 +36045,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35510,7 +36078,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35542,7 +36111,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35574,7 +36144,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35606,7 +36177,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35639,7 +36211,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35671,7 +36244,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35703,7 +36277,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35735,7 +36310,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35767,7 +36343,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35800,7 +36377,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35832,7 +36410,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35864,7 +36443,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35896,7 +36476,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35928,7 +36509,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35961,7 +36543,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -35993,7 +36576,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -36025,7 +36609,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36057,7 +36642,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36089,7 +36675,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36122,7 +36709,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36154,7 +36742,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36186,7 +36775,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36218,7 +36808,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36250,7 +36841,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36283,7 +36875,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36315,7 +36908,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36347,7 +36941,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36379,7 +36974,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36411,7 +37007,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36444,7 +37041,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36476,7 +37074,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36508,7 +37107,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36540,7 +37140,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36572,7 +37173,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36605,7 +37207,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36637,7 +37240,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -36669,7 +37273,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36701,7 +37306,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36733,7 +37339,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36766,7 +37373,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36798,7 +37406,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -36830,7 +37439,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36862,7 +37472,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36894,7 +37505,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36927,7 +37539,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -36959,7 +37572,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -36991,7 +37605,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37023,7 +37638,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37055,7 +37671,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37088,7 +37705,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37120,7 +37738,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37152,7 +37771,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37184,7 +37804,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37216,7 +37837,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37249,7 +37871,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37281,7 +37904,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37313,7 +37937,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37345,7 +37970,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37377,7 +38003,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37410,7 +38037,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37443,7 +38071,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37475,7 +38104,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37507,7 +38137,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37540,7 +38171,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -37573,7 +38205,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37605,7 +38238,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37637,7 +38271,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37670,7 +38305,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -37703,7 +38339,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37735,7 +38372,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37767,7 +38405,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37800,7 +38439,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -37833,7 +38473,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37865,7 +38506,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37897,7 +38539,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37930,7 +38573,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37963,7 +38607,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37995,7 +38640,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38027,7 +38673,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38060,7 +38707,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38093,7 +38741,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38125,7 +38774,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38157,7 +38807,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38190,7 +38841,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38223,7 +38875,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38255,7 +38908,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38287,7 +38941,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38320,7 +38975,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -38353,7 +39009,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38385,7 +39042,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38417,7 +39075,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38450,7 +39109,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38483,7 +39143,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38515,7 +39176,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38547,7 +39209,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38580,7 +39243,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -38613,7 +39277,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38645,7 +39310,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38677,7 +39343,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38710,7 +39377,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -38743,7 +39411,8 @@ "y": 0.0, "z": -39.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38775,7 +39444,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38807,7 +39477,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38840,7 +39511,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -39092,7 +39764,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39124,7 +39797,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39156,7 +39830,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39189,7 +39864,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39221,7 +39897,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39317,7 +39994,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39349,7 +40027,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39381,7 +40060,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -39414,7 +40094,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39446,7 +40127,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39542,7 +40224,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39574,7 +40257,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39606,7 +40290,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39639,7 +40324,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39671,7 +40357,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39767,7 +40454,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39799,7 +40487,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39831,7 +40520,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39864,7 +40554,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39896,7 +40587,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39992,7 +40684,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40024,7 +40717,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40056,7 +40750,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40089,7 +40784,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40121,7 +40817,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40217,7 +40914,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40249,7 +40947,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40281,7 +40980,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40314,7 +41014,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40346,7 +41047,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40442,7 +41144,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40474,7 +41177,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40506,7 +41210,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40539,7 +41244,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40571,7 +41277,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40667,7 +41374,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -40699,7 +41407,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -40731,7 +41440,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -40764,7 +41474,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40796,7 +41507,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40892,7 +41604,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -40924,7 +41637,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -40956,7 +41670,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -40989,7 +41704,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41021,7 +41737,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41117,7 +41834,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -41149,7 +41867,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -41181,7 +41900,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -41214,7 +41934,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41246,7 +41967,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41342,7 +42064,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41374,7 +42097,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41406,7 +42130,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41439,7 +42164,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41471,7 +42197,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41567,7 +42294,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -41599,7 +42327,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -41631,7 +42360,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -41664,7 +42394,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41696,7 +42427,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41792,7 +42524,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41824,7 +42557,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41856,7 +42590,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41889,7 +42624,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41921,7 +42657,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42017,7 +42754,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -42049,7 +42787,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -42081,7 +42820,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -42114,7 +42854,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42146,7 +42887,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42242,7 +42984,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -42274,7 +43017,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -42306,7 +43050,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -42339,7 +43084,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42371,7 +43117,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42467,7 +43214,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42499,7 +43247,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42531,7 +43280,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -42564,7 +43314,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42596,7 +43347,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42692,7 +43444,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42724,7 +43477,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42756,7 +43510,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42789,7 +43544,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42821,7 +43577,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42917,7 +43674,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -42949,7 +43707,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -42981,7 +43740,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -43014,7 +43774,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43046,7 +43807,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43142,7 +43904,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43174,7 +43937,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43206,7 +43970,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43239,7 +44004,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43271,7 +44037,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43367,7 +44134,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -43399,7 +44167,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -43431,7 +44200,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -43464,7 +44234,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43496,7 +44267,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43592,7 +44364,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43624,7 +44397,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43656,7 +44430,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -43689,7 +44464,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43721,7 +44497,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43817,7 +44594,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -43849,7 +44627,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -43881,7 +44660,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -43914,7 +44694,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43946,7 +44727,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44042,7 +44824,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44074,7 +44857,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44106,7 +44890,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -44139,7 +44924,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44171,7 +44957,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44267,7 +45054,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -44299,7 +45087,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -44331,7 +45120,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -44364,7 +45154,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44396,7 +45187,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44538,7 +45330,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -44584,7 +45377,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -44616,7 +45410,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -44649,7 +45444,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44681,7 +45477,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44713,7 +45510,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -44759,7 +45557,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -44791,7 +45590,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -44824,7 +45624,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -44856,7 +45657,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -44888,7 +45690,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -44934,7 +45737,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -44966,7 +45770,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -44999,7 +45804,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -45031,7 +45837,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -45063,7 +45870,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45109,7 +45917,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45141,7 +45950,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45174,7 +45984,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -45206,7 +46017,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -45238,7 +46050,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45284,7 +46097,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45316,7 +46130,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45349,7 +46164,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45381,7 +46197,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45413,7 +46230,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45459,7 +46277,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45491,7 +46310,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45524,7 +46344,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45556,7 +46377,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45588,7 +46410,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45634,7 +46457,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45666,7 +46490,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45699,7 +46524,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45731,7 +46557,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45763,7 +46590,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45809,7 +46637,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45841,7 +46670,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45874,7 +46704,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -45906,7 +46737,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -45938,7 +46770,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45984,7 +46817,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -46016,7 +46850,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -46049,7 +46884,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46081,7 +46917,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46113,7 +46950,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -46159,7 +46997,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -46191,7 +47030,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -46224,7 +47064,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46256,7 +47097,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46288,7 +47130,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -46334,7 +47177,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -46366,7 +47210,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -46399,7 +47244,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -46431,7 +47277,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -46463,7 +47310,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -46509,7 +47357,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -46541,7 +47390,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -46574,7 +47424,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46606,7 +47457,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46795,7 +47647,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46841,7 +47694,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46873,7 +47727,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -46906,7 +47761,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -47003,7 +47859,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -47049,7 +47906,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -47081,7 +47939,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -47114,7 +47973,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -47211,7 +48071,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47257,7 +48118,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47289,7 +48151,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47322,7 +48185,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -47419,7 +48283,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47465,7 +48330,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47497,7 +48363,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47530,7 +48397,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -47627,7 +48495,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47673,7 +48542,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47705,7 +48575,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47738,7 +48609,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47835,7 +48707,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47881,7 +48754,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47913,7 +48787,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47946,7 +48821,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48043,7 +48919,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48089,7 +48966,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48121,7 +48999,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48154,7 +49033,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48251,7 +49131,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -48297,7 +49178,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -48329,7 +49211,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -48362,7 +49245,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -48459,7 +49343,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48505,7 +49390,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48537,7 +49423,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48570,7 +49457,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48667,7 +49555,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48713,7 +49602,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48745,7 +49635,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48778,7 +49669,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48875,7 +49767,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -48921,7 +49814,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -48953,7 +49847,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -48986,7 +49881,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -49083,7 +49979,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -49129,7 +50026,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -49161,7 +50059,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -49194,7 +50093,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -49365,6 +50265,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Digested Protein samples, volume per well", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a3dfca7f0c][Flex_S_v2_19_Illumina_DNA_PCR_Free].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a3dfca7f0c][Flex_S_v2_19_Illumina_DNA_PCR_Free].json index 60b739776d1..36400ae7de7 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a3dfca7f0c][Flex_S_v2_19_Illumina_DNA_PCR_Free].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a3dfca7f0c][Flex_S_v2_19_Illumina_DNA_PCR_Free].json @@ -10218,7 +10218,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10251,7 +10252,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10284,7 +10286,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10317,7 +10320,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10350,7 +10354,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10383,7 +10388,8 @@ "y": 0.625, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10416,7 +10422,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10449,7 +10456,8 @@ "y": 1.25, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10482,7 +10490,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10515,7 +10524,8 @@ "y": 0.625, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10548,7 +10558,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10581,7 +10592,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10614,7 +10626,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10647,7 +10660,8 @@ "y": -0.625, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10680,7 +10694,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10713,7 +10728,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10746,7 +10762,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10779,7 +10796,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10812,7 +10830,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10845,7 +10864,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10878,7 +10898,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10911,7 +10932,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10944,7 +10966,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10977,7 +11000,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11010,7 +11034,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11043,7 +11068,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11076,7 +11102,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11109,7 +11136,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11142,7 +11170,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11175,7 +11204,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11208,7 +11238,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11241,7 +11272,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11274,7 +11306,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11307,7 +11340,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11835,7 +11869,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -11868,7 +11903,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11901,7 +11937,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11934,7 +11971,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11967,7 +12005,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12000,7 +12039,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12033,7 +12073,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12066,7 +12107,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12099,7 +12141,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12132,7 +12175,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12165,7 +12209,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12198,7 +12243,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12231,7 +12277,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12264,7 +12311,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12297,7 +12345,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12330,7 +12379,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12363,7 +12413,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12396,7 +12447,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12428,7 +12480,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12460,7 +12513,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14011,7 +14065,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14058,7 +14113,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14090,7 +14146,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14122,7 +14179,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14155,7 +14213,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -14187,7 +14246,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -14219,7 +14279,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -14445,7 +14506,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14477,7 +14539,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14509,7 +14572,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14542,7 +14606,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14588,7 +14653,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14620,7 +14686,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14952,7 +15019,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14999,7 +15067,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15031,7 +15100,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15063,7 +15133,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15096,7 +15167,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15128,7 +15200,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15160,7 +15233,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15386,7 +15460,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15420,7 +15495,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15452,7 +15528,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15484,7 +15561,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15662,7 +15740,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15695,7 +15774,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15728,7 +15808,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15761,7 +15842,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15794,7 +15876,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15827,7 +15910,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15860,7 +15944,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15893,7 +15978,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15926,7 +16012,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15959,7 +16046,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15992,7 +16080,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16025,7 +16114,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16058,7 +16148,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16091,7 +16182,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16124,7 +16216,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16157,7 +16250,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16190,7 +16284,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16223,7 +16318,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16256,7 +16352,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16959,7 +17056,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17006,7 +17104,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17038,7 +17137,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17070,7 +17170,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17103,7 +17204,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -17135,7 +17237,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -17167,7 +17270,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -17407,7 +17511,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17440,7 +17545,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17472,7 +17578,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17504,7 +17611,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17537,7 +17645,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17583,7 +17692,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17615,7 +17725,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17961,7 +18072,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18008,7 +18120,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18040,7 +18153,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18072,7 +18186,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18105,7 +18220,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18137,7 +18253,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18169,7 +18286,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18424,7 +18542,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18457,7 +18576,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18490,7 +18610,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18523,7 +18644,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18556,7 +18678,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18589,7 +18712,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18622,7 +18746,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18655,7 +18780,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18688,7 +18814,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18721,7 +18848,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18754,7 +18882,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18787,7 +18916,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18820,7 +18950,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18853,7 +18984,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18886,7 +19018,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18919,7 +19052,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18952,7 +19086,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18985,7 +19120,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19237,7 +19373,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19270,7 +19407,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19303,7 +19441,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19336,7 +19475,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19369,7 +19509,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19402,7 +19543,8 @@ "y": 1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19435,7 +19577,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19468,7 +19611,8 @@ "y": 2.5999999999999943, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19501,7 +19645,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19534,7 +19679,8 @@ "y": 1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19567,7 +19713,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19600,7 +19747,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19633,7 +19781,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19666,7 +19815,8 @@ "y": -1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19699,7 +19849,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19732,7 +19883,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19765,7 +19917,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19799,7 +19952,8 @@ "y": 0.0, "z": -12.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19846,7 +20000,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19879,7 +20034,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19912,7 +20068,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19945,7 +20102,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19978,7 +20136,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20011,7 +20170,8 @@ "y": 0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20044,7 +20204,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20077,7 +20238,8 @@ "y": 1.25, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20110,7 +20272,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20143,7 +20306,8 @@ "y": 0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20176,7 +20340,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20209,7 +20374,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20242,7 +20408,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20275,7 +20442,8 @@ "y": -0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20308,7 +20476,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20341,7 +20510,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20656,7 +20826,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20689,7 +20860,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20722,7 +20894,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20755,7 +20928,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20788,7 +20962,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20821,7 +20996,8 @@ "y": 1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20854,7 +21030,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20887,7 +21064,8 @@ "y": 2.5999999999999943, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20920,7 +21098,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20953,7 +21132,8 @@ "y": 1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20986,7 +21166,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21019,7 +21200,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21052,7 +21234,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21085,7 +21268,8 @@ "y": -1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21118,7 +21302,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21151,7 +21336,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21184,7 +21370,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21218,7 +21405,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21264,7 +21452,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21295,7 +21484,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21327,7 +21517,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21491,7 +21682,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21523,7 +21715,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21555,7 +21748,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21588,7 +21782,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21620,7 +21815,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21652,7 +21848,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21892,7 +22089,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21925,7 +22123,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21958,7 +22157,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21991,7 +22191,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22024,7 +22225,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22057,7 +22259,8 @@ "y": 0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22090,7 +22293,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22123,7 +22327,8 @@ "y": 1.25, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22156,7 +22361,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22189,7 +22395,8 @@ "y": 0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22222,7 +22429,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22255,7 +22463,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22288,7 +22497,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22321,7 +22531,8 @@ "y": -0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22354,7 +22565,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22387,7 +22599,8 @@ "y": -1.25, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22420,7 +22633,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22453,7 +22667,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22739,7 +22954,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22786,7 +23002,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22818,7 +23035,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22850,7 +23068,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22883,7 +23102,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -22915,7 +23135,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -22947,7 +23168,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -24298,7 +24520,8 @@ "y": 0.0, "z": 4.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24331,7 +24554,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24363,7 +24587,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24395,7 +24620,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24428,7 +24654,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24578,7 +24805,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24625,7 +24853,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24658,7 +24887,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -24704,7 +24934,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -24881,7 +25112,8 @@ "y": 0.0, "z": 4.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24914,7 +25146,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24946,7 +25179,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24978,7 +25212,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -25011,7 +25246,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25161,7 +25397,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25208,7 +25445,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25241,7 +25479,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25287,7 +25526,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25465,7 +25705,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25498,7 +25739,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25738,7 +25980,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25771,7 +26014,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25818,7 +26062,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25851,7 +26096,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25884,7 +26130,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25917,7 +26164,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25950,7 +26198,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25983,7 +26232,8 @@ "y": 0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26016,7 +26266,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26049,7 +26300,8 @@ "y": 1.25, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26082,7 +26334,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26115,7 +26368,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26417,7 +26671,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26449,7 +26704,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26481,7 +26737,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26515,7 +26772,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26547,7 +26805,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26579,7 +26838,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26770,6 +27030,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "DNA sample of known quantity", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a437534569][Flex_S_v2_19_kapahyperplus].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a437534569][Flex_S_v2_19_kapahyperplus].json index 87322829ea9..fe8184c0608 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a437534569][Flex_S_v2_19_kapahyperplus].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a437534569][Flex_S_v2_19_kapahyperplus].json @@ -10782,7 +10782,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10815,7 +10816,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10848,7 +10850,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10881,7 +10884,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10914,7 +10918,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10947,7 +10952,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10980,7 +10986,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11013,7 +11020,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11046,7 +11054,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11079,7 +11088,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11112,7 +11122,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11145,7 +11156,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11178,7 +11190,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11211,7 +11224,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11244,7 +11258,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11277,7 +11292,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11310,7 +11326,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11343,7 +11360,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11376,7 +11394,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11409,7 +11428,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11442,7 +11462,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11475,7 +11496,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11869,7 +11891,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11902,7 +11925,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11935,7 +11959,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11968,7 +11993,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12001,7 +12027,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12034,7 +12061,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12067,7 +12095,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12100,7 +12129,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12133,7 +12163,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12166,7 +12197,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12199,7 +12231,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12232,7 +12265,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12265,7 +12299,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12298,7 +12333,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12331,7 +12367,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12364,7 +12401,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12397,7 +12435,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12430,7 +12469,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12463,7 +12503,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12496,7 +12537,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12529,7 +12571,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12562,7 +12605,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12970,7 +13014,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -13003,7 +13048,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13036,7 +13082,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13069,7 +13116,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13102,7 +13150,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13135,7 +13184,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13168,7 +13218,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13201,7 +13252,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13234,7 +13286,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13267,7 +13320,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13300,7 +13354,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13333,7 +13388,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13366,7 +13422,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13399,7 +13456,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13432,7 +13490,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13465,7 +13524,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13498,7 +13558,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13531,7 +13592,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13564,7 +13626,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13597,7 +13660,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13630,7 +13694,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13663,7 +13728,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13841,7 +13907,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13874,7 +13941,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13907,7 +13975,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13940,7 +14009,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13973,7 +14043,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14006,7 +14077,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14039,7 +14111,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14072,7 +14145,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14105,7 +14179,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14138,7 +14213,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14171,7 +14247,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14204,7 +14281,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14237,7 +14315,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14270,7 +14349,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14303,7 +14383,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14336,7 +14417,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14369,7 +14451,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14402,7 +14485,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14435,7 +14519,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14468,7 +14553,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14501,7 +14587,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14534,7 +14621,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16161,7 +16249,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16194,7 +16283,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16227,7 +16317,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16260,7 +16351,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16293,7 +16385,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16326,7 +16419,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16359,7 +16453,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16392,7 +16487,8 @@ "y": 1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16425,7 +16521,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16458,7 +16555,8 @@ "y": 3.0999999999999943, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16491,7 +16589,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16524,7 +16623,8 @@ "y": 1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16557,7 +16657,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16590,7 +16691,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16623,7 +16725,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16656,7 +16759,8 @@ "y": -1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16689,7 +16793,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16722,7 +16827,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16755,7 +16861,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16788,7 +16895,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16821,7 +16929,8 @@ "y": 0.0, "z": -4.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16854,7 +16963,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16887,7 +16997,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16920,7 +17031,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16953,7 +17065,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16986,7 +17099,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17019,7 +17133,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17052,7 +17167,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17085,7 +17201,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17118,7 +17235,8 @@ "y": 1.75, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17151,7 +17269,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17184,7 +17303,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17217,7 +17337,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17250,7 +17371,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17283,7 +17405,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17316,7 +17439,8 @@ "y": -0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17349,7 +17473,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17382,7 +17507,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17415,7 +17541,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17448,7 +17575,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17481,7 +17609,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17514,7 +17643,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17547,7 +17677,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17580,7 +17711,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17613,7 +17745,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17646,7 +17779,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17679,7 +17813,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17712,7 +17847,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17745,7 +17881,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17778,7 +17915,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17811,7 +17949,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17844,7 +17983,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17877,7 +18017,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17910,7 +18051,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17943,7 +18085,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17976,7 +18119,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18009,7 +18153,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18042,7 +18187,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18282,7 +18428,8 @@ "y": 0.0, "z": -14.749999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18329,7 +18476,8 @@ "y": 0.0, "z": -14.749999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18361,7 +18509,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18393,7 +18542,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18426,7 +18576,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -18458,7 +18609,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -18490,7 +18642,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -18668,7 +18821,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18700,7 +18854,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18732,7 +18887,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18765,7 +18921,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18811,7 +18968,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18843,7 +19001,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19007,7 +19166,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19039,7 +19199,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19071,7 +19232,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19104,7 +19266,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -19150,7 +19313,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -19182,7 +19346,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -19360,7 +19525,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -19392,7 +19558,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -19424,7 +19591,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -19457,7 +19625,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19503,7 +19672,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19535,7 +19705,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19699,7 +19870,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19731,7 +19903,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19763,7 +19936,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19796,7 +19970,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -19842,7 +20017,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -19874,7 +20050,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -20099,7 +20276,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20131,7 +20309,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20163,7 +20342,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20196,7 +20376,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20229,7 +20410,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20262,7 +20444,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20295,7 +20478,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20328,7 +20512,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20361,7 +20546,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20394,7 +20580,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20427,7 +20614,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20460,7 +20648,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20493,7 +20682,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20526,7 +20716,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20559,7 +20750,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20592,7 +20784,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20625,7 +20818,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20658,7 +20852,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20691,7 +20886,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20724,7 +20920,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20757,7 +20954,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20790,7 +20988,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20823,7 +21022,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20856,7 +21056,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20888,7 +21089,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20920,7 +21122,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21146,7 +21349,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21178,7 +21382,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21210,7 +21415,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21244,7 +21450,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21276,7 +21483,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21308,7 +21516,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21596,7 +21805,8 @@ "y": 0.0, "z": -4.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21629,7 +21839,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21662,7 +21873,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21695,7 +21907,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21728,7 +21941,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21761,7 +21975,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21794,7 +22009,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21827,7 +22043,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21860,7 +22077,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21893,7 +22111,8 @@ "y": 1.75, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21926,7 +22145,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21959,7 +22179,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21992,7 +22213,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22025,7 +22247,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22058,7 +22281,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22091,7 +22315,8 @@ "y": -0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22124,7 +22349,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22157,7 +22383,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22190,7 +22417,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22223,7 +22451,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22256,7 +22485,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22289,7 +22519,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22322,7 +22553,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22355,7 +22587,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22388,7 +22621,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22421,7 +22655,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22454,7 +22689,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22487,7 +22723,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22520,7 +22757,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22553,7 +22791,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22586,7 +22825,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22619,7 +22859,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22652,7 +22893,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22685,7 +22927,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22718,7 +22961,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22751,7 +22995,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22784,7 +23029,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22817,7 +23063,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22850,7 +23097,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22883,7 +23131,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23436,7 +23685,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23469,7 +23719,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23502,7 +23753,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23535,7 +23787,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23568,7 +23821,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23601,7 +23855,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23634,7 +23889,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23667,7 +23923,8 @@ "y": 1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23700,7 +23957,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23733,7 +23991,8 @@ "y": 3.0999999999999943, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23766,7 +24025,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23799,7 +24059,8 @@ "y": 1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23832,7 +24093,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23865,7 +24127,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23898,7 +24161,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23931,7 +24195,8 @@ "y": -1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23964,7 +24229,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23997,7 +24263,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24030,7 +24297,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24063,7 +24331,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24096,7 +24365,8 @@ "y": 0.0, "z": -4.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24129,7 +24399,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24162,7 +24433,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24195,7 +24467,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24228,7 +24501,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24261,7 +24535,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24294,7 +24569,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24327,7 +24603,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24360,7 +24637,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24393,7 +24671,8 @@ "y": 1.75, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24426,7 +24705,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24459,7 +24739,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24492,7 +24773,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24525,7 +24807,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24558,7 +24841,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24591,7 +24875,8 @@ "y": -0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24624,7 +24909,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24657,7 +24943,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24690,7 +24977,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24723,7 +25011,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24756,7 +25045,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24789,7 +25079,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24822,7 +25113,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24855,7 +25147,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24888,7 +25181,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24921,7 +25215,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24954,7 +25249,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24987,7 +25283,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25020,7 +25317,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25053,7 +25351,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25086,7 +25385,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25119,7 +25419,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25152,7 +25453,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25185,7 +25487,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25218,7 +25521,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25251,7 +25555,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25284,7 +25589,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25317,7 +25623,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25557,7 +25864,8 @@ "y": 0.0, "z": -14.749999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25604,7 +25912,8 @@ "y": 0.0, "z": -14.749999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25636,7 +25945,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25668,7 +25978,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25701,7 +26012,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -25733,7 +26045,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -25765,7 +26078,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -25943,7 +26257,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25975,7 +26290,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26007,7 +26323,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26040,7 +26357,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26086,7 +26404,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26118,7 +26437,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26282,7 +26602,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26314,7 +26635,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26346,7 +26668,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26379,7 +26702,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -26425,7 +26749,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -26457,7 +26782,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -26635,7 +26961,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26667,7 +26994,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26699,7 +27027,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26732,7 +27061,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26778,7 +27108,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26810,7 +27141,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26974,7 +27306,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27006,7 +27339,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27038,7 +27372,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27071,7 +27406,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -27117,7 +27453,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -27149,7 +27486,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -27374,7 +27712,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27406,7 +27745,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27438,7 +27778,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27471,7 +27812,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27504,7 +27846,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27537,7 +27880,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27570,7 +27914,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27603,7 +27948,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27636,7 +27982,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27669,7 +28016,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27702,7 +28050,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27735,7 +28084,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27768,7 +28118,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27801,7 +28152,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27834,7 +28186,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27867,7 +28220,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27900,7 +28254,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27933,7 +28288,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27966,7 +28322,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27999,7 +28356,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28032,7 +28390,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28065,7 +28424,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28098,7 +28458,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28131,7 +28492,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28163,7 +28525,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28195,7 +28558,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28421,7 +28785,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28453,7 +28818,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28485,7 +28851,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28519,7 +28886,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28551,7 +28919,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28583,7 +28952,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28774,6 +29144,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "DNA sample of known quantity", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a46928c103][pl_Nanopore_Genomic_Ligation_v5_Final].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a46928c103][pl_Nanopore_Genomic_Ligation_v5_Final].json index 3caab573ed5..e623aec42f7 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a46928c103][pl_Nanopore_Genomic_Ligation_v5_Final].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a46928c103][pl_Nanopore_Genomic_Ligation_v5_Final].json @@ -12445,7 +12445,8 @@ "y": 0.0, "z": -14.680000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12478,7 +12479,8 @@ "y": 0.0, "z": -14.68 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12510,7 +12512,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12542,7 +12545,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12576,7 +12580,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12609,7 +12614,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12643,7 +12649,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12676,7 +12683,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12710,7 +12718,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12743,7 +12752,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12777,7 +12787,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12810,7 +12821,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12844,7 +12856,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12877,7 +12890,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12911,7 +12925,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12944,7 +12959,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12978,7 +12994,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13011,7 +13028,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13045,7 +13063,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13078,7 +13097,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13112,7 +13132,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13145,7 +13166,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13178,7 +13200,8 @@ "y": 0.0, "z": -14.53 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13210,7 +13233,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13535,7 +13559,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13569,7 +13594,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13602,7 +13628,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13636,7 +13663,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13669,7 +13697,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13702,7 +13731,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13735,7 +13765,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13768,7 +13799,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13800,7 +13832,8 @@ "y": 0.0, "z": -11.279999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13832,7 +13865,8 @@ "y": 0.0, "z": -11.279999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13864,7 +13898,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13896,7 +13931,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13929,7 +13965,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13961,7 +13998,8 @@ "y": 0.0, "z": -11.279999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13993,7 +14031,8 @@ "y": 0.0, "z": -11.279999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14026,7 +14065,8 @@ "y": 0.0, "z": -11.279999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14058,7 +14098,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14090,7 +14131,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14123,7 +14165,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14155,7 +14198,8 @@ "y": 0.0, "z": -11.279999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14187,7 +14231,8 @@ "y": 0.0, "z": -11.279999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14219,7 +14264,8 @@ "y": 0.0, "z": 2.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14250,7 +14296,8 @@ "y": 0.0, "z": 5.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14281,7 +14328,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14312,7 +14360,8 @@ "y": 0.0, "z": 5.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14540,7 +14589,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14572,7 +14622,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14618,7 +14669,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14650,7 +14702,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14682,7 +14735,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14714,7 +14768,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14760,7 +14815,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14791,7 +14847,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14822,7 +14879,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -14944,7 +15002,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -14976,7 +15035,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15007,7 +15067,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15038,7 +15099,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15069,7 +15131,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15101,7 +15164,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15147,7 +15211,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15178,7 +15243,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15209,7 +15275,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15240,7 +15307,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15375,7 +15443,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15407,7 +15476,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15453,7 +15523,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15485,7 +15556,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15517,7 +15589,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15549,7 +15622,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15595,7 +15669,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15626,7 +15701,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15657,7 +15733,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15779,7 +15856,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15811,7 +15889,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15842,7 +15921,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15873,7 +15953,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -15904,7 +15985,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15936,7 +16018,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15982,7 +16065,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16013,7 +16097,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16044,7 +16129,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16075,7 +16161,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16210,7 +16297,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16242,7 +16330,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16288,7 +16377,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16320,7 +16410,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16352,7 +16443,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16384,7 +16476,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -16430,7 +16523,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -16461,7 +16555,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -16492,7 +16587,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -16627,7 +16723,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16659,7 +16756,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16844,7 +16942,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -16876,7 +16975,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16908,7 +17008,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16940,7 +17041,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17168,7 +17270,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17200,7 +17303,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17233,7 +17337,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17446,7 +17551,8 @@ "y": 0.0, "z": -14.530000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17480,7 +17586,8 @@ "y": 0.0, "z": -14.530000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17513,7 +17620,8 @@ "y": 0.0, "z": -14.530000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17547,7 +17655,8 @@ "y": 0.0, "z": -14.530000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17580,7 +17689,8 @@ "y": 0.0, "z": -14.530000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17613,7 +17723,8 @@ "y": 0.0, "z": -14.530000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17646,7 +17757,8 @@ "y": 0.0, "z": -14.680000000000003 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17678,7 +17790,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17724,7 +17837,8 @@ "y": 0.0, "z": -14.679999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17756,7 +17870,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17788,7 +17903,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17822,7 +17938,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17855,7 +17972,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17889,7 +18007,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17922,7 +18041,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17956,7 +18076,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17989,7 +18110,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18023,7 +18145,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18056,7 +18179,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18090,7 +18214,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18123,7 +18248,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18157,7 +18283,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18190,7 +18317,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18224,7 +18352,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18257,7 +18386,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18291,7 +18421,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18324,7 +18455,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18358,7 +18490,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18391,7 +18524,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18425,7 +18559,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18458,7 +18593,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18492,7 +18628,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18525,7 +18662,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18559,7 +18697,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18592,7 +18731,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18626,7 +18766,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18659,7 +18800,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18693,7 +18835,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18726,7 +18869,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18760,7 +18904,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18793,7 +18938,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18827,7 +18973,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18860,7 +19007,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18894,7 +19042,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18927,7 +19076,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18961,7 +19111,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18994,7 +19145,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19028,7 +19180,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19061,7 +19214,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19094,7 +19248,8 @@ "y": 0.0, "z": -14.529999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19126,7 +19281,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19332,7 +19488,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19365,7 +19522,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19411,7 +19569,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19639,7 +19798,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19671,7 +19831,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19717,7 +19878,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19749,7 +19911,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19781,7 +19944,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19813,7 +19977,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19859,7 +20024,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19890,7 +20056,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19921,7 +20088,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20091,7 +20259,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -20123,7 +20292,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20155,7 +20325,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20187,7 +20358,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20429,7 +20601,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20461,7 +20634,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20507,7 +20681,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20539,7 +20714,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20571,7 +20747,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20603,7 +20780,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20649,7 +20827,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20680,7 +20859,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20711,7 +20891,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20881,7 +21062,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -20913,7 +21095,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20945,7 +21128,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20977,7 +21161,8 @@ "y": 0.0, "z": 3.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21219,7 +21404,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21251,7 +21437,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21297,7 +21484,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21329,7 +21517,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21361,7 +21550,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21393,7 +21583,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21439,7 +21630,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21470,7 +21662,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21501,7 +21694,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -21636,7 +21830,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21668,7 +21863,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21853,7 +22049,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -21885,7 +22082,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21917,7 +22115,8 @@ "y": 0.0, "z": 0.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21949,7 +22148,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22335,7 +22535,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22367,7 +22568,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22400,7 +22602,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -22641,6 +22844,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "AMPure Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a4d3b3a2d3][pl_96_ch_demo_rtp_with_hs].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a4d3b3a2d3][pl_96_ch_demo_rtp_with_hs].json index 7887dcd2406..0aacb0b3e73 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a4d3b3a2d3][pl_96_ch_demo_rtp_with_hs].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a4d3b3a2d3][pl_96_ch_demo_rtp_with_hs].json @@ -9729,7 +9729,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9762,7 +9763,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9795,7 +9797,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9828,7 +9831,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9861,7 +9865,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9894,7 +9899,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9941,7 +9947,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9974,7 +9981,8 @@ "y": 0.0, "z": -4.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10007,7 +10015,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10040,7 +10049,8 @@ "y": 0.0, "z": -4.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10334,7 +10344,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10679,7 +10690,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10855,7 +10867,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10888,7 +10901,8 @@ "y": 0.0, "z": -4.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10921,7 +10935,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10954,7 +10969,8 @@ "y": 0.0, "z": -4.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11248,7 +11264,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11593,7 +11610,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11887,7 +11905,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11920,7 +11939,8 @@ "y": 0.0, "z": -4.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11953,7 +11973,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11986,7 +12007,8 @@ "y": 0.0, "z": -4.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12280,7 +12302,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12313,7 +12336,8 @@ "y": 0.0, "z": -12.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12611,7 +12635,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12644,7 +12669,8 @@ "y": 0.0, "z": -12.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16567,6 +16593,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "generic", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a66d700ed6][OT2_S_v2_13_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a66d700ed6][OT2_S_v2_13_P300M_P20S_HS_TC_TM_SmokeTestV3].json index 5df00cb3ea3..e6cb5eace9a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a66d700ed6][OT2_S_v2_13_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a66d700ed6][OT2_S_v2_13_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -8522,7 +8522,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8555,7 +8556,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -8587,7 +8589,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8619,7 +8622,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8652,7 +8656,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -8684,7 +8689,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8716,7 +8722,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8749,7 +8756,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -8781,7 +8789,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8813,7 +8822,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8846,7 +8856,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -8878,7 +8889,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8910,7 +8922,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8943,7 +8956,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -8975,7 +8989,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9071,7 +9086,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9104,7 +9120,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9137,7 +9154,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9170,7 +9188,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9203,7 +9222,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9236,7 +9256,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9269,7 +9290,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9317,7 +9339,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9350,7 +9373,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9383,7 +9407,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9430,7 +9455,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9462,7 +9488,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9495,7 +9522,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9528,7 +9556,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9561,7 +9590,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9594,7 +9624,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9627,7 +9658,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9660,7 +9692,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9708,7 +9741,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9741,7 +9775,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9774,7 +9809,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9821,7 +9857,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9853,7 +9890,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9886,7 +9924,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9919,7 +9958,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9952,7 +9992,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9985,7 +10026,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10018,7 +10060,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10051,7 +10094,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10099,7 +10143,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -10132,7 +10177,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -10165,7 +10211,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -10212,7 +10259,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -10244,7 +10292,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10277,7 +10326,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10310,7 +10360,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10343,7 +10394,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10376,7 +10428,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10409,7 +10462,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10442,7 +10496,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10490,7 +10545,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -10523,7 +10579,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -10556,7 +10613,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -10603,7 +10661,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -10635,7 +10694,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10668,7 +10728,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10701,7 +10762,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10734,7 +10796,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10767,7 +10830,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10800,7 +10864,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10833,7 +10898,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10881,7 +10947,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -10914,7 +10981,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -10947,7 +11015,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -10994,7 +11063,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -11026,7 +11096,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11059,7 +11130,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11092,7 +11164,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11125,7 +11198,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11158,7 +11232,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11191,7 +11266,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11224,7 +11300,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11272,7 +11349,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -11305,7 +11383,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -11338,7 +11417,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -11385,7 +11465,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -11417,7 +11498,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11450,7 +11532,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11483,7 +11566,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11516,7 +11600,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11549,7 +11634,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11582,7 +11668,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11615,7 +11702,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11663,7 +11751,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -11696,7 +11785,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -11729,7 +11819,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -11776,7 +11867,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -11808,7 +11900,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11841,7 +11934,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11874,7 +11968,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11907,7 +12002,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11940,7 +12036,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11973,7 +12070,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12006,7 +12104,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12054,7 +12153,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12087,7 +12187,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12120,7 +12221,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12167,7 +12269,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12199,7 +12302,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12232,7 +12336,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12265,7 +12370,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12298,7 +12404,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12331,7 +12438,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12364,7 +12472,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12397,7 +12506,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12445,7 +12555,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12478,7 +12589,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12511,7 +12623,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12558,7 +12671,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12590,7 +12704,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12623,7 +12738,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12655,7 +12771,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12687,7 +12804,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -12720,7 +12838,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12752,7 +12871,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12784,7 +12904,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -12817,7 +12938,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12849,7 +12971,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12945,7 +13068,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12978,7 +13102,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -13026,7 +13151,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -13088,7 +13214,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -13407,7 +13534,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13440,7 +13568,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13537,7 +13666,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13570,7 +13700,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13681,7 +13812,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13714,7 +13846,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -13778,7 +13911,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13811,7 +13945,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -13957,6 +14092,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons Engineering ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a8528e52b4][Flex_S_v2_20_96_None_SINGLE_HappyPathSouthSide].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a8528e52b4][Flex_S_v2_20_96_None_SINGLE_HappyPathSouthSide].json new file mode 100644 index 00000000000..b0eb2e93f00 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a8528e52b4][Flex_S_v2_20_96_None_SINGLE_HappyPathSouthSide].json @@ -0,0 +1,9451 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "displayName": "Partial Tip Rack", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "A2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "displayName": "C1 Source Labware", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C1" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c910be62291bb94458a59cb4185c5180", + "notes": [], + "params": { + "displayName": "C2 Destination Labware", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2f59e0552969594ba6ab06f03af324e1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H12" + }, + "result": { + "position": { + "x": 277.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6b0053809cc5ea894449b20181bda1d6", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 32.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9d2ba7d37a294b62afe4d91153783e8d", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 41.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e8ce83939641089264883e922565169e", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 20.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 20.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bc7a3401208e1ebbf2ef653b39f7ec05", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "71264e0d8f121750fadffa4de419d934", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "957785aca5143d6556af5c2cb3c59297", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G12" + }, + "result": { + "position": { + "x": 277.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4d87d61c6915c64c29bf9601dce504d6", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 59.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a6b3ab33bb429b17ddfcfa80e4795882", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 223.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ad078a7983560bdf409c3fafb76a20df", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dc80dca10e7a8e4eb0148afda35d9f7f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "47e2a6e4e082a3edcd417887da1391ea", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F12" + }, + "result": { + "position": { + "x": 277.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8fcfb95fb44994648ec969a5fcc049df", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 15.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 15.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d5419055b04d30ef91ae9ad466be6814", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 5.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 5.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "beb80898b59a1324e0cab2e514684044", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 5.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 241.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 5.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b189af8f175fc84c04677212be3713cd", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 434.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "70fbf1f310a94ff9a76abbf78aed0f5d", + "notes": [], + "params": { + "flowRate": 80.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "98e8fdad613e2394706e4ed69bcacd5a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "02ddfbbad3a85f2dea9649b185df31c0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d4b54bd1cc3ea0b0f00c771f6d069552", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E12" + }, + "result": { + "position": { + "x": 277.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "touchTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8d44ea65bcbbc368be1bc486199c5c36", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "radius": 1.0, + "speed": 60.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -1.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 14.38, + "y": 172.24, + "z": 14.7 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ff637cfa2197a46f886f9a8f430cba99", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "05e32557ff805e84d62ca868c9d3ffb9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "09a9af20ba5025d9b73f1e8abb0ae921", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D12" + }, + "result": { + "position": { + "x": 277.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "886acb0a83206c4beffe85c574403c8c", + "notes": [], + "params": { + "axes": [ + "leftPlunger", + "leftZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0be564f428e87599bd9ad2c95132690f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0d0b65bd3564dcbd484a27851516a8d8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bc1207d443da2a4be8c29c638ed95832", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C12" + }, + "result": { + "position": { + "x": 277.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "800a566578fc2c77b3ba63a8740bc11d", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7cdd48fe814ff91d04f2541b5bda803f", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b73df6c1dc10a15fccad64146a9253e3", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "303e819bc213e27cd87898f84a0849d8", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "05d169be70745d1a65829f1a968b95f6", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "76cef1507e46661cad77313a0ed3727d", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "77bfe511646e7418f20da0817e3b96e9", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6c13cf87265353a92dc843600b9d9203", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ade2d2d2074263c9dd505940d90c2dfb", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cccc3a958942894e8046f7b40e5ca74f", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e9c371bfd5e628a170bfbecb5db3a9fd", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7de6a6935a0a885c8bbcaf24d799c23a", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "663261a619fb3978db881c0b49ee5757", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "23b170d0f6b4310456e22bff31768726", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ca000faa3e0a7c16d820dba7f034ea17", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "358e3d9564fef6580430c29ec1abc8be", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5ae5e07479909c02ab7c3380f3d6831c", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "aba2f3c1667e265516ba1b2f7a70bd9b", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e4401c648a31e63d991b887e1e967edf", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8653a8d80a26e9ae5e402cbbf005314c", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "79f12967defc4069a8c9957178510267", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d42541fb8e9dad7fd6df764276d97e85", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a5fe690c7ee2e46760c78e76c1799e32", + "notes": [], + "params": { + "flowRate": 80.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "prepareToAspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "147f19190dec5bb7e8082142106cdba8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b0f86fda12faff8a53661cffc418a681", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "27b170cfd7791ee5264cd8360381fc14", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c580bea6ba31532addf5ce76c8b0aa63", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 14.38, + "y": 154.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3a50114af65ad0ce2b5e517279c3a5b6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d370233cdd09b4925594d9f78744c355", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "492f10d86634ba52387d3434a2c96163", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b485fe600db6f864ef8a02a863d2f2df", + "notes": [], + "params": { + "displayName": "B2 Source Labware", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5c2612f6a457dd5a9cd1fcbef90d7202", + "notes": [], + "params": { + "displayName": "C3 Destination Labware", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C3" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2581590044c8560d58362d124bc9bdb6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 178.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3650650eea34d6d4e66c10e0fa1ebdb5", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 288.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3ef9c7f672bd43cde576c6d73613126d", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 205.38, + "y": 288.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6fb55db7ac1b49dbe9156eaeba8194bd", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 20.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 360.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 20.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2339acb2db069cef24c9c100f428e0db", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f6a019bfa57d0a606735ea72303528d9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a174b97232ef172d0e27137664a19397", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3158782be2a54e06cdb5d21858c543c4", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 223.38, + "y": 288.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fcae01b8b4e0c415dbc27208278875f8", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 387.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d42746a0d3d7fb860d731f37e437c15c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f6c3f602d9098ce4dc436d13f9dcb273", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2e3de388c00c39ef2dee24cae75e6c1d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 178.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7884a44cb06e8548433d28f491cceea5", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 15.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 288.24, + "z": 1.92 + }, + "volume": 15.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bce465ed11bd648962c1f17925d1cbdf", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 5.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 396.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 5.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "91ffbc1925155dae952866f06b74a980", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 5.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 405.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 5.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "91dc16ab2f2dce621da90df5bd45e3e0", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 434.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3161ab53aabd77d0bcadd85d2da313bf", + "notes": [], + "params": { + "flowRate": 80.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bb1be49b3dd7eae3db1b99859cf9b5c2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "42068e979dd4b9cb2b97462903a1f0f5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ec3e5a07d61f225fcde1a1a0c823380a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 178.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "touchTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "58eee9576ecb10dfe0a4498f62a9bc16", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "radius": 1.0, + "speed": 60.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -1.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 178.38, + "y": 279.24, + "z": 14.7 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b7d3af3327dcf5a4b2b9aeb4d25c4268", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dccfc99f4dbd996fd6f1dbfff503f5b8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "846e5dece4834bfca01905e3bea4601c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "77e5462d5ef2d7d0f9d5f2ffa0f8fdb2", + "notes": [], + "params": { + "axes": [ + "leftPlunger", + "leftZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ea7c3fda775d7684b13ce5580091718a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 466.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "aebb1c492347ac603b682596f730d963", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "91e93ec228e98c0289636c2864eb6c46", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 178.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "eeb7edd79823036cfb229eb4e791f0f6", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "05fcbdb33a96b07db3ddad355c7f72c0", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "97c9e4f009511a98fce5dc76c4fe7fea", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2029dc5b30bcc4739da6fa852631a0d1", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4b708488472fbd0408b853c5346d264a", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9b2f886d5e9b192599b03520c766e1b0", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0475379c541e2ba86ffd71ad80bf0d95", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b991098c1256775c13375f2224cb3542", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7cef453d8ca1bce5e791d827488663fe", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a45727f65ed4eabbe3798d52c8426276", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c4dc466e0287dbe35ebc9c262c574b33", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e87a0a206eb43db4615755f179a768eb", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e11f0e411cde7118fc4b744156bfd609", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c1293f9799131c8772b1bf34ec155389", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7f65fbc1e61f69ec9f742e92aad0eaae", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "20ffbf6cb800a7b2d1ee50f32d7ee374", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8cc590a6565142be07c064e9df1f31c9", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a0de49c2ca6e1dca96256af101f616be", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "09dd576331ee4d9e7756516b38fb093d", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d7dfe39e2bfef7a3d3a6a4e846896112", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dfd1754741a559fbd431586a396c01cf", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1dc658dff0124e0091b1ab4391609568", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f42637d6cb96d258b40ee87c94f2a662", + "notes": [], + "params": { + "flowRate": 80.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "prepareToAspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4fadad19c8f235c7a5fc76f6e1049aa9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a8dd5c8b8f316ef5c65697d84512317d", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0019e1328404f6f7bc5108407dbe942b", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ec30fc82f7f081a7abe12d2da1be7259", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f83d0dd289ab00c4ff75f6e4b95f827b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 402.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "234fc0121d41506c465aad9fe3fac238", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [], + "files": [ + { + "name": "Flex_S_v2_20_96_None_SINGLE_HappyPathSouthSide.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "displayName": "Partial Tip Rack", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "A2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "displayName": "C1 Source Labware", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C1" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "displayName": "C2 Destination Labware", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "displayName": "B2 Source Labware", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "B2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "displayName": "C3 Destination Labware", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C3" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "Unsafe protocol ❗❗❗❗❗❗❗❗❗❗❗ will collide with tube.", + "protocolName": "96Channel SINGLE Happy Path A1 or A12" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a8a5ad823d][pl_cherrypicking_flex_v3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a8a5ad823d][pl_cherrypicking_flex_v3].json index 7fa196a5094..86a33113a16 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a8a5ad823d][pl_cherrypicking_flex_v3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a8a5ad823d][pl_cherrypicking_flex_v3].json @@ -9914,7 +9914,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9947,7 +9948,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -10044,7 +10046,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10077,7 +10080,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -10174,7 +10178,8 @@ "y": 0.0, "z": -24.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10207,7 +10212,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -10367,6 +10373,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Krishna Soma ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a94f22054f][Flex_S_v2_18_kapahyperplus].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a94f22054f][Flex_S_v2_18_kapahyperplus].json index 261f501e33e..52120bd6dc3 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a94f22054f][Flex_S_v2_18_kapahyperplus].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a94f22054f][Flex_S_v2_18_kapahyperplus].json @@ -10782,7 +10782,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10815,7 +10816,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10848,7 +10850,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10881,7 +10884,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10914,7 +10918,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10947,7 +10952,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10980,7 +10986,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11013,7 +11020,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11046,7 +11054,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11079,7 +11088,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11112,7 +11122,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11145,7 +11156,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11178,7 +11190,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11211,7 +11224,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11244,7 +11258,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11277,7 +11292,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11310,7 +11326,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11343,7 +11360,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11376,7 +11394,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11409,7 +11428,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11442,7 +11462,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11475,7 +11496,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11869,7 +11891,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11902,7 +11925,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11935,7 +11959,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11968,7 +11993,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12001,7 +12027,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12034,7 +12061,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12067,7 +12095,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12100,7 +12129,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12133,7 +12163,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12166,7 +12197,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12199,7 +12231,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12232,7 +12265,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12265,7 +12299,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12298,7 +12333,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12331,7 +12367,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12364,7 +12401,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12397,7 +12435,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12430,7 +12469,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12463,7 +12503,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12496,7 +12537,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12529,7 +12571,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12562,7 +12605,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12970,7 +13014,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -13003,7 +13048,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13036,7 +13082,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13069,7 +13116,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13102,7 +13150,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13135,7 +13184,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13168,7 +13218,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13201,7 +13252,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13234,7 +13286,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13267,7 +13320,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13300,7 +13354,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13333,7 +13388,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13366,7 +13422,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13399,7 +13456,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13432,7 +13490,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13465,7 +13524,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13498,7 +13558,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13531,7 +13592,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13564,7 +13626,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13597,7 +13660,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13630,7 +13694,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13663,7 +13728,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13841,7 +13907,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13874,7 +13941,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13907,7 +13975,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13940,7 +14009,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13973,7 +14043,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14006,7 +14077,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14039,7 +14111,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14072,7 +14145,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14105,7 +14179,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14138,7 +14213,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14171,7 +14247,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14204,7 +14281,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14237,7 +14315,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14270,7 +14349,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14303,7 +14383,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14336,7 +14417,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14369,7 +14451,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14402,7 +14485,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14435,7 +14519,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14468,7 +14553,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14501,7 +14587,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14534,7 +14621,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16161,7 +16249,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16194,7 +16283,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16227,7 +16317,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16260,7 +16351,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16293,7 +16385,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16326,7 +16419,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16359,7 +16453,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16392,7 +16487,8 @@ "y": 1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16425,7 +16521,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16458,7 +16555,8 @@ "y": 3.0999999999999943, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16491,7 +16589,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16524,7 +16623,8 @@ "y": 1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16557,7 +16657,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16590,7 +16691,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16623,7 +16725,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16656,7 +16759,8 @@ "y": -1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16689,7 +16793,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16722,7 +16827,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16755,7 +16861,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16788,7 +16895,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16821,7 +16929,8 @@ "y": 0.0, "z": -4.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16854,7 +16963,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16887,7 +16997,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16920,7 +17031,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16953,7 +17065,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16986,7 +17099,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17019,7 +17133,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17052,7 +17167,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17085,7 +17201,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17118,7 +17235,8 @@ "y": 1.75, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17151,7 +17269,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17184,7 +17303,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17217,7 +17337,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17250,7 +17371,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17283,7 +17405,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17316,7 +17439,8 @@ "y": -0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17349,7 +17473,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17382,7 +17507,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17415,7 +17541,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17448,7 +17575,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17481,7 +17609,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17514,7 +17643,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17547,7 +17677,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17580,7 +17711,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17613,7 +17745,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17646,7 +17779,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17679,7 +17813,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17712,7 +17847,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17745,7 +17881,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17778,7 +17915,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17811,7 +17949,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17844,7 +17983,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17877,7 +18017,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17910,7 +18051,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17943,7 +18085,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17976,7 +18119,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18009,7 +18153,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18042,7 +18187,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18282,7 +18428,8 @@ "y": 0.0, "z": -14.749999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18329,7 +18476,8 @@ "y": 0.0, "z": -14.749999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18361,7 +18509,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18393,7 +18542,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18426,7 +18576,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -18458,7 +18609,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -18490,7 +18642,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -18668,7 +18821,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18700,7 +18854,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18732,7 +18887,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18765,7 +18921,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18811,7 +18968,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18843,7 +19001,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19007,7 +19166,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19039,7 +19199,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19071,7 +19232,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19104,7 +19266,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -19150,7 +19313,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -19182,7 +19346,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -19360,7 +19525,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -19392,7 +19558,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -19424,7 +19591,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -19457,7 +19625,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19503,7 +19672,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19535,7 +19705,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19699,7 +19870,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19731,7 +19903,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19763,7 +19936,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19796,7 +19970,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -19842,7 +20017,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -19874,7 +20050,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -20099,7 +20276,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20131,7 +20309,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20163,7 +20342,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20196,7 +20376,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20229,7 +20410,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20262,7 +20444,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20295,7 +20478,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20328,7 +20512,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20361,7 +20546,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20394,7 +20580,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20427,7 +20614,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20460,7 +20648,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20493,7 +20682,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20526,7 +20716,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20559,7 +20750,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20592,7 +20784,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20625,7 +20818,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20658,7 +20852,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20691,7 +20886,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20724,7 +20920,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20757,7 +20954,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20790,7 +20988,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20823,7 +21022,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20856,7 +21056,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20888,7 +21089,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20920,7 +21122,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21146,7 +21349,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21178,7 +21382,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21210,7 +21415,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21244,7 +21450,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21276,7 +21483,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21308,7 +21516,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21596,7 +21805,8 @@ "y": 0.0, "z": -4.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21629,7 +21839,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21662,7 +21873,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21695,7 +21907,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21728,7 +21941,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21761,7 +21975,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21794,7 +22009,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21827,7 +22043,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21860,7 +22077,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21893,7 +22111,8 @@ "y": 1.75, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21926,7 +22145,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21959,7 +22179,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21992,7 +22213,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22025,7 +22247,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22058,7 +22281,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22091,7 +22315,8 @@ "y": -0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22124,7 +22349,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22157,7 +22383,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22190,7 +22417,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22223,7 +22451,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22256,7 +22485,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22289,7 +22519,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22322,7 +22553,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22355,7 +22587,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22388,7 +22621,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22421,7 +22655,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22454,7 +22689,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22487,7 +22723,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22520,7 +22757,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22553,7 +22791,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22586,7 +22825,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22619,7 +22859,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22652,7 +22893,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22685,7 +22927,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22718,7 +22961,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22751,7 +22995,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22784,7 +23029,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22817,7 +23063,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22850,7 +23097,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22883,7 +23131,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23436,7 +23685,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23469,7 +23719,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23502,7 +23753,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23535,7 +23787,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23568,7 +23821,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23601,7 +23855,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23634,7 +23889,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23667,7 +23923,8 @@ "y": 1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23700,7 +23957,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23733,7 +23991,8 @@ "y": 3.0999999999999943, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23766,7 +24025,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23799,7 +24059,8 @@ "y": 1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23832,7 +24093,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23865,7 +24127,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23898,7 +24161,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23931,7 +24195,8 @@ "y": -1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23964,7 +24229,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23997,7 +24263,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24030,7 +24297,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24063,7 +24331,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24096,7 +24365,8 @@ "y": 0.0, "z": -4.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24129,7 +24399,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24162,7 +24433,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24195,7 +24467,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24228,7 +24501,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24261,7 +24535,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24294,7 +24569,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24327,7 +24603,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24360,7 +24637,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24393,7 +24671,8 @@ "y": 1.75, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24426,7 +24705,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24459,7 +24739,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24492,7 +24773,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24525,7 +24807,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24558,7 +24841,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24591,7 +24875,8 @@ "y": -0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24624,7 +24909,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24657,7 +24943,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24690,7 +24977,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24723,7 +25011,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24756,7 +25045,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24789,7 +25079,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24822,7 +25113,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24855,7 +25147,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24888,7 +25181,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24921,7 +25215,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24954,7 +25249,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24987,7 +25283,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25020,7 +25317,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25053,7 +25351,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25086,7 +25385,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25119,7 +25419,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25152,7 +25453,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25185,7 +25487,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25218,7 +25521,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25251,7 +25555,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25284,7 +25589,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25317,7 +25623,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25557,7 +25864,8 @@ "y": 0.0, "z": -14.749999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25604,7 +25912,8 @@ "y": 0.0, "z": -14.749999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25636,7 +25945,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25668,7 +25978,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25701,7 +26012,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -25733,7 +26045,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -25765,7 +26078,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -25943,7 +26257,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25975,7 +26290,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26007,7 +26323,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26040,7 +26357,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26086,7 +26404,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26118,7 +26437,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26282,7 +26602,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26314,7 +26635,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26346,7 +26668,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26379,7 +26702,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -26425,7 +26749,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -26457,7 +26782,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -26635,7 +26961,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26667,7 +26994,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26699,7 +27027,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26732,7 +27061,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26778,7 +27108,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26810,7 +27141,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26974,7 +27306,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27006,7 +27339,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27038,7 +27372,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27071,7 +27406,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -27117,7 +27453,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -27149,7 +27486,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -27374,7 +27712,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27406,7 +27745,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27438,7 +27778,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27471,7 +27812,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27504,7 +27846,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27537,7 +27880,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27570,7 +27914,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27603,7 +27948,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27636,7 +27982,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27669,7 +28016,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27702,7 +28050,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27735,7 +28084,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27768,7 +28118,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27801,7 +28152,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27834,7 +28186,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27867,7 +28220,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27900,7 +28254,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27933,7 +28288,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27966,7 +28322,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27999,7 +28356,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28032,7 +28390,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28065,7 +28424,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28098,7 +28458,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28131,7 +28492,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28163,7 +28525,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28195,7 +28558,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28421,7 +28785,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28453,7 +28818,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28485,7 +28851,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28519,7 +28886,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28551,7 +28919,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28583,7 +28952,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28774,6 +29144,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "DNA sample of known quantity", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json index 60a0f1c77a3..1d043a44952 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[a9557d762c][Flex_X_v2_16_NO_PIPETTES_AccessToFixedTrashProp].json @@ -55,6 +55,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[aa61eee0bf][pl_sigdx_part2].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[aa61eee0bf][pl_sigdx_part2].json index b5159c7e3ce..93eff2447db 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[aa61eee0bf][pl_sigdx_part2].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[aa61eee0bf][pl_sigdx_part2].json @@ -17901,7 +17901,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17934,7 +17935,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17968,7 +17970,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18001,7 +18004,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18035,7 +18039,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18068,7 +18073,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18102,7 +18108,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18135,7 +18142,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18169,7 +18177,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18202,7 +18211,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18236,7 +18246,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18269,7 +18280,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18303,7 +18315,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18336,7 +18349,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18370,7 +18384,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18403,7 +18418,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18437,7 +18453,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18470,7 +18487,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18504,7 +18522,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18537,7 +18556,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18571,7 +18591,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18604,7 +18625,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18638,7 +18660,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18671,7 +18694,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18705,7 +18729,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18738,7 +18763,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18772,7 +18798,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18805,7 +18832,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18839,7 +18867,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18872,7 +18901,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18906,7 +18936,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18939,7 +18970,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18973,7 +19005,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19006,7 +19039,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19040,7 +19074,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19073,7 +19108,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19107,7 +19143,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19140,7 +19177,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19174,7 +19212,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19207,7 +19246,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19240,7 +19280,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19272,7 +19313,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19304,7 +19346,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19336,7 +19379,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19368,7 +19412,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19400,7 +19445,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19432,7 +19478,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19464,7 +19511,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19496,7 +19544,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19541,7 +19590,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19573,7 +19623,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19694,7 +19745,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19727,7 +19779,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19760,7 +19813,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19794,7 +19848,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19827,7 +19882,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19861,7 +19917,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19894,7 +19951,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19928,7 +19986,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19961,7 +20020,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19995,7 +20055,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20028,7 +20089,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20062,7 +20124,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20095,7 +20158,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20129,7 +20193,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20162,7 +20227,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20196,7 +20262,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20229,7 +20296,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20263,7 +20331,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20296,7 +20365,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20330,7 +20400,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20363,7 +20434,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20397,7 +20469,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20430,7 +20503,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20464,7 +20538,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20497,7 +20572,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20531,7 +20607,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20564,7 +20641,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20598,7 +20676,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20631,7 +20710,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20665,7 +20745,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20698,7 +20779,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20731,7 +20813,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20763,7 +20846,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20808,7 +20892,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20966,7 +21051,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21045,7 +21131,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21245,7 +21332,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21278,7 +21366,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21311,7 +21400,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21345,7 +21435,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21378,7 +21469,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21412,7 +21504,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21445,7 +21538,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21479,7 +21573,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21512,7 +21607,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21546,7 +21642,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21579,7 +21676,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21613,7 +21711,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21646,7 +21745,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21680,7 +21780,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21713,7 +21814,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21747,7 +21849,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21780,7 +21883,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21814,7 +21918,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21847,7 +21952,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21881,7 +21987,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21914,7 +22021,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21948,7 +22056,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21981,7 +22090,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22015,7 +22125,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22048,7 +22159,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22082,7 +22194,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22115,7 +22228,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22149,7 +22263,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22182,7 +22297,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22216,7 +22332,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22249,7 +22366,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22282,7 +22400,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22314,7 +22433,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22359,7 +22479,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22517,7 +22638,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22564,7 +22686,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22750,7 +22873,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22783,7 +22907,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22816,7 +22941,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22850,7 +22976,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22883,7 +23010,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22917,7 +23045,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22950,7 +23079,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22984,7 +23114,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23017,7 +23148,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23051,7 +23183,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23084,7 +23217,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23118,7 +23252,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23151,7 +23286,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23185,7 +23321,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23218,7 +23355,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23252,7 +23390,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23285,7 +23424,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23319,7 +23459,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23352,7 +23493,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23386,7 +23528,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23419,7 +23562,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23453,7 +23597,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23486,7 +23631,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23520,7 +23666,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23553,7 +23700,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23587,7 +23735,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23620,7 +23769,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23654,7 +23804,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23687,7 +23838,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23721,7 +23873,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23754,7 +23907,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23787,7 +23941,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23819,7 +23974,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23864,7 +24020,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24055,7 +24212,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24102,7 +24260,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24223,7 +24382,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24256,7 +24416,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24289,7 +24450,8 @@ "y": 0.0, "z": -37.6 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24321,7 +24483,8 @@ "y": 0.0, "z": -37.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24506,7 +24669,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24539,7 +24703,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24572,7 +24737,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24606,7 +24772,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24639,7 +24806,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24673,7 +24841,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24706,7 +24875,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24740,7 +24910,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24773,7 +24944,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24807,7 +24979,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24840,7 +25013,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24874,7 +25048,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24907,7 +25082,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24941,7 +25117,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24974,7 +25151,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25008,7 +25186,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25041,7 +25220,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25075,7 +25255,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25108,7 +25289,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25142,7 +25324,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25175,7 +25358,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25209,7 +25393,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25242,7 +25427,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25276,7 +25462,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25309,7 +25496,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25343,7 +25531,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25376,7 +25565,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25410,7 +25600,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25443,7 +25634,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25477,7 +25669,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25510,7 +25703,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25543,7 +25737,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25575,7 +25770,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25620,7 +25816,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25820,7 +26017,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25899,7 +26097,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26099,7 +26298,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26132,7 +26332,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26165,7 +26366,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26199,7 +26401,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26232,7 +26435,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26266,7 +26470,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26299,7 +26504,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26333,7 +26539,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26366,7 +26573,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26400,7 +26608,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26433,7 +26642,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26467,7 +26677,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26500,7 +26711,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26534,7 +26746,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26567,7 +26780,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26601,7 +26815,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26634,7 +26849,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26668,7 +26884,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26701,7 +26918,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26735,7 +26953,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26768,7 +26987,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26802,7 +27022,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26835,7 +27056,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26869,7 +27091,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26902,7 +27125,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26936,7 +27160,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26969,7 +27194,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27003,7 +27229,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27036,7 +27263,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27070,7 +27298,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27103,7 +27332,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27136,7 +27366,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27168,7 +27399,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27213,7 +27445,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27385,7 +27618,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27432,7 +27666,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27618,7 +27853,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27651,7 +27887,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27684,7 +27921,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27718,7 +27956,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27751,7 +27990,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27785,7 +28025,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27818,7 +28059,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27852,7 +28094,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27885,7 +28128,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27919,7 +28163,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27952,7 +28197,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27986,7 +28232,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28019,7 +28266,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28053,7 +28301,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28086,7 +28335,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28120,7 +28370,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28153,7 +28404,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28187,7 +28439,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28220,7 +28473,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28254,7 +28508,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28287,7 +28542,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28321,7 +28577,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28354,7 +28611,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28388,7 +28646,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28421,7 +28680,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28455,7 +28715,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28488,7 +28749,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28522,7 +28784,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28555,7 +28818,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28589,7 +28853,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28622,7 +28887,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28655,7 +28921,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28687,7 +28954,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28732,7 +29000,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28937,7 +29206,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28984,7 +29254,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29105,7 +29376,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29138,7 +29410,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29171,7 +29444,8 @@ "y": 0.0, "z": -37.6 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29203,7 +29477,8 @@ "y": 0.0, "z": -37.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29468,7 +29743,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29501,7 +29777,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29534,7 +29811,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29568,7 +29846,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29601,7 +29880,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29635,7 +29915,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29668,7 +29949,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29702,7 +29984,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29735,7 +30018,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29769,7 +30053,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29802,7 +30087,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29836,7 +30122,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29869,7 +30156,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29903,7 +30191,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29936,7 +30225,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29970,7 +30260,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30003,7 +30294,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30037,7 +30329,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30070,7 +30363,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30104,7 +30398,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30137,7 +30432,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30171,7 +30467,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30204,7 +30501,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30238,7 +30536,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30271,7 +30570,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30305,7 +30605,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30338,7 +30639,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30372,7 +30674,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30405,7 +30708,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30439,7 +30743,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30472,7 +30777,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30505,7 +30811,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30537,7 +30844,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30582,7 +30890,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30754,7 +31063,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30801,7 +31111,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30987,7 +31298,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31020,7 +31332,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31053,7 +31366,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31087,7 +31401,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31120,7 +31435,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31154,7 +31470,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31187,7 +31504,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31221,7 +31539,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31254,7 +31573,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31288,7 +31608,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31321,7 +31642,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31355,7 +31677,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31388,7 +31711,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31422,7 +31746,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31455,7 +31780,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31489,7 +31815,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31522,7 +31849,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31556,7 +31884,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31589,7 +31918,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31623,7 +31953,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31656,7 +31987,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31690,7 +32022,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31723,7 +32056,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31757,7 +32091,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31790,7 +32125,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31824,7 +32160,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31857,7 +32194,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31891,7 +32229,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31924,7 +32263,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31958,7 +32298,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31991,7 +32332,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32024,7 +32366,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32056,7 +32399,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32101,7 +32445,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32273,7 +32618,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32320,7 +32666,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32506,7 +32853,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32539,7 +32887,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32572,7 +32921,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32606,7 +32956,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32639,7 +32990,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32673,7 +33025,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32706,7 +33059,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32740,7 +33094,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32773,7 +33128,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32807,7 +33163,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32840,7 +33197,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32874,7 +33232,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32907,7 +33266,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32941,7 +33301,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32974,7 +33335,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33008,7 +33370,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33041,7 +33404,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33075,7 +33439,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33108,7 +33473,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33142,7 +33508,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33175,7 +33542,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33209,7 +33577,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33242,7 +33611,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33276,7 +33646,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33309,7 +33680,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33343,7 +33715,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33376,7 +33749,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33410,7 +33784,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33443,7 +33818,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33477,7 +33853,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33510,7 +33887,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33543,7 +33921,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33575,7 +33954,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33620,7 +34000,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33825,7 +34206,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33872,7 +34254,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33993,7 +34376,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34026,7 +34410,8 @@ "y": 0.0, "z": -37.4 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34059,7 +34444,8 @@ "y": 0.0, "z": -37.6 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34091,7 +34477,8 @@ "y": 0.0, "z": -37.2 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34276,7 +34663,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34309,7 +34697,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34342,7 +34731,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34376,7 +34766,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34409,7 +34800,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34443,7 +34835,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34476,7 +34869,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34510,7 +34904,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34543,7 +34938,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34577,7 +34973,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34610,7 +35007,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34644,7 +35042,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34677,7 +35076,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34711,7 +35111,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34744,7 +35145,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34778,7 +35180,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34811,7 +35214,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34845,7 +35249,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34878,7 +35283,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34912,7 +35318,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34945,7 +35352,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34979,7 +35387,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35012,7 +35421,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35046,7 +35456,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35079,7 +35490,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35113,7 +35525,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35146,7 +35559,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35180,7 +35594,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35213,7 +35628,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35247,7 +35663,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35280,7 +35697,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35313,7 +35731,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35345,7 +35764,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35390,7 +35810,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35567,7 +35988,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35600,7 +36022,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35633,7 +36056,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35667,7 +36091,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35700,7 +36125,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35734,7 +36160,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35767,7 +36194,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35801,7 +36229,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35834,7 +36263,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35868,7 +36298,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35901,7 +36332,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35935,7 +36367,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35968,7 +36401,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36002,7 +36436,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36035,7 +36470,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36069,7 +36505,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36102,7 +36539,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36136,7 +36574,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36169,7 +36608,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36203,7 +36643,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36236,7 +36677,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36269,7 +36711,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36301,7 +36744,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36346,7 +36790,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36467,7 +36912,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -36500,7 +36946,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36533,7 +36980,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36567,7 +37015,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36600,7 +37049,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36634,7 +37084,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36667,7 +37118,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36701,7 +37153,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36734,7 +37187,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36768,7 +37222,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36801,7 +37256,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36835,7 +37291,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36868,7 +37325,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36902,7 +37360,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36935,7 +37394,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36969,7 +37429,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37002,7 +37463,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37036,7 +37498,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37069,7 +37532,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37103,7 +37567,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37136,7 +37601,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37169,7 +37635,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37201,7 +37668,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37246,7 +37714,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37409,7 +37878,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37442,7 +37912,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37476,7 +37947,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37509,7 +37981,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37543,7 +38016,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37576,7 +38050,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37610,7 +38085,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37643,7 +38119,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37677,7 +38154,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37710,7 +38188,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37744,7 +38223,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37777,7 +38257,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37811,7 +38292,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37844,7 +38326,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37878,7 +38361,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37911,7 +38395,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37945,7 +38430,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37978,7 +38464,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38012,7 +38499,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38045,7 +38533,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38079,7 +38568,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38112,7 +38602,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38146,7 +38637,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38179,7 +38671,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38213,7 +38706,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38246,7 +38740,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38280,7 +38775,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38313,7 +38809,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38347,7 +38844,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38380,7 +38878,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38414,7 +38913,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38447,7 +38947,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38481,7 +38982,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38514,7 +39016,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38548,7 +39051,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38581,7 +39085,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38615,7 +39120,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38648,7 +39154,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38682,7 +39189,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38715,7 +39223,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38748,7 +39257,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38780,7 +39290,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38812,7 +39323,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38844,7 +39356,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38876,7 +39389,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38908,7 +39422,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38940,7 +39455,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38972,7 +39488,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39005,7 +39522,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39039,7 +39557,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39072,7 +39591,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39106,7 +39626,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39139,7 +39660,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39173,7 +39695,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39206,7 +39729,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39240,7 +39764,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39273,7 +39798,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39307,7 +39833,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39340,7 +39867,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39374,7 +39902,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39407,7 +39936,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39441,7 +39971,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39474,7 +40005,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39508,7 +40040,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39541,7 +40074,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39575,7 +40109,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39608,7 +40143,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39642,7 +40178,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39675,7 +40212,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39709,7 +40247,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39742,7 +40281,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39776,7 +40316,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39809,7 +40350,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39843,7 +40385,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39876,7 +40419,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39910,7 +40454,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39943,7 +40488,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39976,7 +40522,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40008,7 +40555,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40053,7 +40601,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40085,7 +40634,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40252,7 +40802,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40299,7 +40850,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40467,7 +41019,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -40499,7 +41052,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -40531,7 +41085,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40577,7 +41132,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40712,7 +41268,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40759,7 +41316,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40927,7 +41485,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -40959,7 +41518,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -40991,7 +41551,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41037,7 +41598,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41277,7 +41839,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41324,7 +41887,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41445,7 +42009,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41478,7 +42043,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41511,7 +42077,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41543,7 +42110,8 @@ "y": 0.0, "z": -14.149999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41742,7 +42310,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41775,7 +42344,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41808,7 +42378,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41841,7 +42412,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41875,7 +42447,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41908,7 +42481,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41942,7 +42516,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -41975,7 +42550,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42009,7 +42585,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42042,7 +42619,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42076,7 +42654,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42109,7 +42688,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42143,7 +42723,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42176,7 +42757,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42210,7 +42792,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42243,7 +42826,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42277,7 +42861,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42310,7 +42895,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42344,7 +42930,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42377,7 +42964,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42411,7 +42999,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42444,7 +43033,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42478,7 +43068,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42511,7 +43102,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42545,7 +43137,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42578,7 +43171,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42612,7 +43206,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42645,7 +43240,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42679,7 +43275,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42712,7 +43309,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42746,7 +43344,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42779,7 +43378,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42812,7 +43412,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42845,7 +43446,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42891,7 +43493,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43058,7 +43661,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43105,7 +43709,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43138,7 +43743,8 @@ "y": 0.0, "z": -8.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43171,7 +43777,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43203,7 +43810,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43248,7 +43856,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45776,6 +46385,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac5a46e74b][pl_langone_pt2_ribo].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac5a46e74b][pl_langone_pt2_ribo].json index a8a4d400843..ec2e77260d2 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac5a46e74b][pl_langone_pt2_ribo].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac5a46e74b][pl_langone_pt2_ribo].json @@ -17417,7 +17417,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17450,7 +17451,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17483,7 +17485,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17516,7 +17519,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17549,7 +17553,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17582,7 +17587,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17615,7 +17621,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17648,7 +17655,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17681,7 +17689,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17714,7 +17723,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17747,7 +17757,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17780,7 +17791,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17812,7 +17824,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17857,7 +17870,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17889,7 +17903,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18084,7 +18099,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18117,7 +18133,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18150,7 +18167,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18183,7 +18201,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18216,7 +18235,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18249,7 +18269,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18282,7 +18303,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18315,7 +18337,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18348,7 +18371,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18381,7 +18405,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18414,7 +18439,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18447,7 +18473,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18479,7 +18506,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18524,7 +18552,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18556,7 +18585,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18747,7 +18777,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18780,7 +18811,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18813,7 +18845,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18846,7 +18879,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18879,7 +18913,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18912,7 +18947,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18945,7 +18981,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18978,7 +19015,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19011,7 +19049,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19044,7 +19083,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19077,7 +19117,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19110,7 +19151,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19142,7 +19184,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19187,7 +19230,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19219,7 +19263,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19361,7 +19406,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19394,7 +19440,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19427,7 +19474,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19460,7 +19508,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19493,7 +19542,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19526,7 +19576,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19559,7 +19610,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19592,7 +19644,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19625,7 +19678,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19658,7 +19712,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19691,7 +19746,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19724,7 +19780,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19757,7 +19814,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19790,7 +19848,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19823,7 +19882,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19856,7 +19916,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19889,7 +19950,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19922,7 +19984,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19955,7 +20018,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19988,7 +20052,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20021,7 +20086,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20054,7 +20120,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20087,7 +20154,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20120,7 +20188,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20153,7 +20222,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20186,7 +20256,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20219,7 +20290,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20252,7 +20324,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20285,7 +20358,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20318,7 +20392,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20351,7 +20426,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20384,7 +20460,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20417,7 +20494,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20450,7 +20528,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20483,7 +20562,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20516,7 +20596,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20549,7 +20630,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20582,7 +20664,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20615,7 +20698,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20648,7 +20732,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20681,7 +20766,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20713,7 +20799,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20745,7 +20832,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20778,7 +20866,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20811,7 +20900,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20844,7 +20934,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20877,7 +20968,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20910,7 +21002,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20943,7 +21036,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20976,7 +21070,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21009,7 +21104,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21042,7 +21138,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21075,7 +21172,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21108,7 +21206,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21141,7 +21240,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21174,7 +21274,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21207,7 +21308,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21240,7 +21342,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21273,7 +21376,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21306,7 +21410,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21339,7 +21444,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21372,7 +21478,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21405,7 +21512,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21437,7 +21545,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21482,7 +21591,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21638,7 +21748,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21671,7 +21782,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21703,7 +21815,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21735,7 +21848,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21781,7 +21895,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21891,7 +22006,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21923,7 +22039,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21955,7 +22072,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21987,7 +22105,8 @@ "y": 0.0, "z": 20.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22033,7 +22152,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22066,7 +22186,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22098,7 +22219,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22130,7 +22252,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22176,7 +22299,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22272,7 +22396,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22304,7 +22429,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22336,7 +22462,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22368,7 +22495,8 @@ "y": 0.0, "z": 20.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22414,7 +22542,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22447,7 +22576,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22479,7 +22609,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22511,7 +22642,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22557,7 +22689,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22699,7 +22832,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22732,7 +22866,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22765,7 +22900,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22798,7 +22934,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22831,7 +22968,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22864,7 +23002,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22897,7 +23036,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22930,7 +23070,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22963,7 +23104,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22996,7 +23138,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23029,7 +23172,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23062,7 +23206,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23095,7 +23240,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23128,7 +23274,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23161,7 +23308,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23194,7 +23342,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23227,7 +23376,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23260,7 +23410,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23293,7 +23444,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23326,7 +23478,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23359,7 +23512,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23392,7 +23546,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23425,7 +23580,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23458,7 +23614,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23491,7 +23648,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23524,7 +23682,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23556,7 +23715,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23601,7 +23761,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23633,7 +23794,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23793,7 +23955,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23826,7 +23989,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23858,7 +24022,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23889,7 +24054,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24015,7 +24181,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24048,7 +24215,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24080,7 +24248,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24111,7 +24280,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24221,7 +24391,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24254,7 +24425,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24287,7 +24459,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24320,7 +24493,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24353,7 +24527,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24386,7 +24561,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24419,7 +24595,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24452,7 +24629,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24485,7 +24663,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24518,7 +24697,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24551,7 +24731,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24584,7 +24765,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24616,7 +24798,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24661,7 +24844,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24693,7 +24877,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25148,7 +25333,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25181,7 +25367,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25214,7 +25401,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25247,7 +25435,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25280,7 +25469,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25313,7 +25503,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25346,7 +25537,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25379,7 +25571,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25412,7 +25605,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25445,7 +25639,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25478,7 +25673,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25511,7 +25707,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25544,7 +25741,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25577,7 +25775,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25610,7 +25809,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25643,7 +25843,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25676,7 +25877,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25709,7 +25911,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25742,7 +25945,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25775,7 +25979,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25808,7 +26013,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25841,7 +26047,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25874,7 +26081,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25907,7 +26115,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25940,7 +26149,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25973,7 +26183,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26006,7 +26217,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26039,7 +26251,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26072,7 +26285,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26105,7 +26319,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26138,7 +26353,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26171,7 +26387,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26204,7 +26421,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26237,7 +26455,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26270,7 +26489,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26303,7 +26523,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26336,7 +26557,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26369,7 +26591,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26402,7 +26625,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26435,7 +26659,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26468,7 +26693,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26500,7 +26726,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26532,7 +26759,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26565,7 +26793,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26598,7 +26827,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26631,7 +26861,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26664,7 +26895,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26697,7 +26929,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26730,7 +26963,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26763,7 +26997,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26796,7 +27031,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26829,7 +27065,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26862,7 +27099,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26895,7 +27133,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26928,7 +27167,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26961,7 +27201,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26994,7 +27235,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27027,7 +27269,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27060,7 +27303,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27093,7 +27337,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27126,7 +27371,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27159,7 +27405,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27192,7 +27439,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27224,7 +27472,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27269,7 +27518,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27425,7 +27675,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27458,7 +27709,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27490,7 +27742,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27522,7 +27775,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27568,7 +27822,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27678,7 +27933,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27710,7 +27966,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27742,7 +27999,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27774,7 +28032,8 @@ "y": 0.0, "z": 20.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27820,7 +28079,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27853,7 +28113,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27885,7 +28146,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27917,7 +28179,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28028,7 +28291,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28060,7 +28324,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28092,7 +28357,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28124,7 +28390,8 @@ "y": 0.0, "z": 20.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28170,7 +28437,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28203,7 +28471,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28235,7 +28504,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28267,7 +28537,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28424,7 +28695,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28457,7 +28729,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28490,7 +28763,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28523,7 +28797,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28556,7 +28831,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28589,7 +28865,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28622,7 +28899,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28655,7 +28933,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28688,7 +28967,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28721,7 +29001,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28754,7 +29035,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28787,7 +29069,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28820,7 +29103,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28853,7 +29137,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28886,7 +29171,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28919,7 +29205,8 @@ "y": 0.0, "z": -8.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28952,7 +29239,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28985,7 +29273,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29018,7 +29307,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29051,7 +29341,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29084,7 +29375,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29117,7 +29409,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29150,7 +29443,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29183,7 +29477,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29216,7 +29511,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29249,7 +29545,8 @@ "y": 0.0, "z": -14.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29281,7 +29578,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29326,7 +29624,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29358,7 +29657,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29518,7 +29818,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29551,7 +29852,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29583,7 +29885,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29614,7 +29917,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29779,6 +30083,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "ATL4", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac886d7768][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac886d7768][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3].json index 5f98592f617..30f2c70b0ea 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac886d7768][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ac886d7768][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3].json @@ -11329,7 +11329,8 @@ "y": 0.0, "z": -14.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11362,7 +11363,8 @@ "y": 0.0, "z": -14.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11394,7 +11396,8 @@ "y": 0.0, "z": -14.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11426,7 +11429,8 @@ "y": 0.0, "z": -14.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11459,7 +11463,8 @@ "y": 0.0, "z": -14.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11612,7 +11617,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11645,7 +11651,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11678,7 +11685,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11711,7 +11719,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11744,7 +11753,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11777,7 +11787,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11810,7 +11821,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11842,7 +11854,8 @@ "y": 0.0, "z": 5.0000000000000036 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11888,7 +11901,8 @@ "y": 0.0, "z": -14.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11920,7 +11934,8 @@ "y": 0.0, "z": -14.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11952,7 +11967,8 @@ "y": 0.0, "z": -14.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11985,7 +12001,8 @@ "y": 0.0, "z": -14.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12017,7 +12034,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12253,7 +12271,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12286,7 +12305,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12319,7 +12339,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12352,7 +12373,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12384,7 +12406,8 @@ "y": 0.0, "z": -9.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12416,7 +12439,8 @@ "y": 0.0, "z": -9.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12448,7 +12472,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12480,7 +12505,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12513,7 +12539,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12545,7 +12572,8 @@ "y": 0.0, "z": -9.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12577,7 +12605,8 @@ "y": 0.0, "z": -9.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12610,7 +12639,8 @@ "y": 0.0, "z": -9.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12642,7 +12672,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12674,7 +12705,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12707,7 +12739,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12739,7 +12772,8 @@ "y": 0.0, "z": -9.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12771,7 +12805,8 @@ "y": 0.0, "z": -9.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12803,7 +12838,8 @@ "y": 0.0, "z": 2.0000000000000036 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12834,7 +12870,8 @@ "y": 0.0, "z": 5.0000000000000036 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12865,7 +12902,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12896,7 +12934,8 @@ "y": 0.0, "z": 5.0000000000000036 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13033,7 +13072,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13065,7 +13105,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13111,7 +13152,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13143,7 +13185,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13175,7 +13218,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13207,7 +13251,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13253,7 +13298,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13284,7 +13330,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13315,7 +13362,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13425,7 +13473,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13457,7 +13506,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13488,7 +13538,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13519,7 +13570,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13550,7 +13602,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13582,7 +13635,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13628,7 +13682,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13659,7 +13714,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13690,7 +13746,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13721,7 +13778,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13830,7 +13888,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13862,7 +13921,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13908,7 +13968,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13940,7 +14001,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13972,7 +14034,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14004,7 +14067,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14050,7 +14114,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14081,7 +14146,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14112,7 +14178,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14222,7 +14289,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14254,7 +14322,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14285,7 +14354,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14316,7 +14386,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14347,7 +14418,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14379,7 +14451,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14425,7 +14498,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14456,7 +14530,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14487,7 +14562,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14518,7 +14594,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14627,7 +14704,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14659,7 +14737,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14705,7 +14784,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14737,7 +14817,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14769,7 +14850,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14801,7 +14883,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14847,7 +14930,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14878,7 +14962,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14909,7 +14994,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15005,9 +15091,26 @@ "commandType": "moveToWell", "completedAt": "TIMESTAMP", "createdAt": "TIMESTAMP", + "error": { + "createdAt": "TIMESTAMP", + "detail": "Specifying top with an offset of x=0.0 y=0.0 z=-14.780000000000001 and a volume offset of 0.0 results in an operation location below the bottom of the well", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "OperationLocationNotInWellError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + }, "id": "UUID", "key": "c3eacf39e9a35058cac9f69100549344", - "notes": [], + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], "params": { "forceDirect": false, "labwareId": "UUID", @@ -15018,6505 +15121,106 @@ "y": 0.0, "z": -14.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 38.92 - } - }, "startedAt": "TIMESTAMP", - "status": "succeeded" + "status": "failed" + } + ], + "config": { + "apiVersion": [ + 2, + 15 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "ProtocolCommandFailedError [line 439]: Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): OperationLocationNotInWellError: Specifying top with an offset of x=0.0 y=0.0 z=-14.780000000000001 and a volume offset of 0.0 results in an operation location below the bottom of the well", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "OperationLocationNotInWellError: Specifying top with an offset of x=0.0 y=0.0 z=-14.780000000000001 and a volume offset of 0.0 results in an operation location below the bottom of the well", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ProtocolCommandFailedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Specifying top with an offset of x=0.0 y=0.0 z=-14.780000000000001 and a volume offset of 0.0 results in an operation location below the bottom of the well", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "OperationLocationNotInWellError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ] + } + ], + "files": [ + { + "name": "Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_1_trash_3200ml_fixed/1", + "id": "UUID", + "loadName": "opentrons_1_trash_3200ml_fixed", + "location": { + "slotName": "A3" + } }, { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", "id": "UUID", - "key": "3c7a0a79113266092510ea87cb6a83b2", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.780000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 38.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "B1" + } }, { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", + "definitionUri": "opentrons/nest_96_wellplate_2ml_deep/2", "id": "UUID", - "key": "e99740b20e776657e9697cc51098f33a", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" + "loadName": "nest_96_wellplate_2ml_deep", + "location": { + "slotName": "D2" + } }, { - "commandType": "waitForDuration", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", "id": "UUID", - "key": "4698310d9e228d01a3d261dbbdd21e88", - "notes": [], - "params": { - "seconds": 6.0 - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D3" + } }, { - "commandType": "blowout", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", + "definitionUri": "opentrons/nest_96_wellplate_2ml_deep/2", "id": "UUID", - "key": "c13ae172bc1cc196859086c49abaa26f", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" + "loadName": "nest_96_wellplate_2ml_deep", + "location": { + "slotName": "C2" + } }, { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "c6540955e4e6815525ff5e4ce7928f0a", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -5.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 36.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "7cdd9ae2115946cfd5b44d08be0b7bd2", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "2006e83a6de7158e3dbcb2289a2f12e5", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 288.38, - "z": 98.33 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveLabware", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "28792f546a7e9989005767c8994934f1", - "notes": [], - "params": { - "dropOffset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "labwareId": "UUID", - "newLocation": { - "slotName": "D1" - }, - "pickUpOffset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "strategy": "usingGripper" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "12917eac98792cd0ed9e3ec926d9e8e1", - "notes": [], - "params": { - "message": "--> Adding RSB" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "pickUpTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "4b055ac8ff754bd6b34a2b2b2d6d9d31", - "notes": [], - "params": { - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 181.38, - "z": 110.0 - }, - "tipDiameter": 5.58, - "tipLength": 47.4, - "tipVolume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "6ca6627ea6cf56f94179c2ef3845797a", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -37.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 288.15, - "z": 4.0 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "9e537e1b4454ed59813ac433b3fcb911", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 1.040000000000001, - "y": 0.0, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 15.420000000000002, - "y": 74.24, - "z": 4.3100000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "948c46d630f4a0deda144f2b7c5f3ce4", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 1.040000000000001, - "y": 0.0, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 15.420000000000002, - "y": 74.24, - "z": 4.3100000000000005 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "844f55e5ff15f8d03eebf1a3a83a1f43", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "595a224cf2373432182329f6b1c48018", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "f238fe36cc13a1263d121ea0d1489b22", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 1.0400000000000063, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 75.28, - "z": 4.3100000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "a2d0eafc6b03ea987fae21d714dde177", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 1.0400000000000063, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 75.28, - "z": 4.3100000000000005 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "caf07e3f032310c11a312a9c0031f20c", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "078d97a2a8e87560eedb8dba95d4e5f2", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "6938ea8f11c9ac7cf56bc3e3312d2f92", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": -1.040000000000001, - "y": 0.0, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 13.34, - "y": 74.24, - "z": 4.3100000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "7c37bd248b47c93febe4cd103c919792", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": -1.040000000000001, - "y": 0.0, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 13.34, - "y": 74.24, - "z": 4.3100000000000005 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "ffb3912aa5407fc5d379d97c7dec497e", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "315ba895eb18de446e9153a4b487b8ba", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "f114d45e760f7cad0ece10838d81aefb", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": -1.0400000000000063, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 73.19999999999999, - "z": 4.3100000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "0730976655f30fa8c433432d2bc8c0f3", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": -1.0400000000000063, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 73.19999999999999, - "z": 4.3100000000000005 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "4fea8a95c81e7ca43450846b071b4967", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "28b92e81fc837cadc9faf150a354fa82", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "485f7ac7340b20f608c8d97ff47dd038", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "blowout", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "4e45adcae9155df459793fc8063f4779", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -7.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 8.31 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "c26d4202f43bc918f3bc8c496742e2c4", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 5.0000000000000036 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 20.700000000000003 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "0e66fb7985bd5db53daac251dbd51730", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 15.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "8a99ae5aa1d2027b1a24c6a50ab1edbe", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 5.0000000000000036 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 20.700000000000003 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "9d4e2a8aa6d32e53a65cd50799879912", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 181.38, - "z": 98.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveLabware", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "dc491c2608a9e9641d72038e2ca1a585", - "notes": [], - "params": { - "dropOffset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "labwareId": "UUID", - "newLocation": { - "moduleId": "UUID" - }, - "pickUpOffset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "strategy": "usingGripper" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "6d4cb4aa0d06ab340815b27f8c84ef8f", - "notes": [], - "params": { - "message": "--> Transferring Supernatant" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "pickUpTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "99c50d4eacb943bfc2eba6568fd64450", - "notes": [], - "params": { - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 181.38, - "z": 110.0 - }, - "tipDiameter": 5.58, - "tipLength": 47.4, - "tipVolume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "470d66c2dca2e21489b90c0248d23529", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.530000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 39.17 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "a7d1316fccfadaf97de0772fd60acdb1", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 21.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.530000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 39.17 - }, - "volume": 21.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "a93f5814cce5d49d7f3b17732f17db18", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 21.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 21.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "b587bec7d48af7e373b0566ec95ac809", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 181.38, - "z": 98.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "16f41862ae4a135d79cd1fd7f8f73afc", - "notes": [], - "params": { - "message": "==============================================" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "4d140cd47250f51fb771c7c133d1f2ed", - "notes": [], - "params": { - "message": "--> Amplification" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "f9d9072d7cb59c045d0fc4a723a07c8d", - "notes": [], - "params": { - "message": "==============================================" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "3e3cc7fe4c0c2401488b0a66661f72ac", - "notes": [], - "params": { - "message": "--> Adding PCR" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "pickUpTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "87f9ba65bca7f3623b81eeeef6dda4e8", - "notes": [], - "params": { - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 181.38, - "z": 110.0 - }, - "tipDiameter": 5.58, - "tipLength": 47.4, - "tipVolume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "fe22d58a76aafd4bf8e7743d6f37819f", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 25.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 25.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "7f47020d5743fcc1d8d344c36c3b52ac", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 25.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 25.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "4cca78524ba9c12b87482109e96a2587", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 25.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 25.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "192dbd241a802ab7daf7b18cf2b06638", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 25.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 25.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "5a745a3ad520eecec674a1e7b72dfe73", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 25.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 25.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "c2c4e5eeb25745acd22c94dffc25b87c", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 25.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 25.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "64bb321bf71487242dd75d3228570b69", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "69a8b0f690209c60334dfdca977726f6", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "8aee918f244d843f844d4b061760f107", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "c4a2ebdfc0aabaea49c7056f4d24ab3d", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "0786b5a9e0564a69d6043c5f23b7529a", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "ed9f6aa3794edff681a5e23fd7a09a0c", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "67c829f7b3d31c3ddb8c18b029486043", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "55fd6c2600519ccb6c24b112814bc197", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "6a9bbe7dcf5ddf32f9a93eef661cda77", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "896877cf8cbd1cbc89d25fe27480e55f", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "2d386840848260e87ef10b3e00d763d6", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "1d2a64f4c6c2fe39426d92a4c168131f", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "2d7aaf87a674a46dcc55947b4e3ab1cb", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "016a2bc5a825eb411dd50224ea20d147", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "edbcdcd49af788cc2e1cff9bd3353634", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "3401f77193f082b5efb21f16ed03de61", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "0660902c7aff4303a571f96175a57b54", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "6da6f9a0d9b4933670acfe69154fd36a", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "bacbde7bc66f60e3f7e06ca8af30501f", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "c33d0ac278dc159b4629e17d1bd9ba0d", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "4f69d781483869de2e013fcb06bedd67", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 1.92 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "waitForDuration", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "feabdcff3e042735acfb39bfb7ce841a", - "notes": [], - "params": { - "seconds": 6.0 - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "blowout", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "724651173298757e4906053c2f70b719", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -5.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 288.24, - "z": 10.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "cd8d59cc0c7fb723867bc8ba40090074", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 181.38, - "z": 98.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveLabware", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "6a9df44abcf0c9d1addb4a96ebad72dd", - "notes": [], - "params": { - "dropOffset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "labwareId": "UUID", - "newLocation": { - "slotName": "D1" - }, - "pickUpOffset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "strategy": "usingGripper" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveLabware", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "814a9450e0a67d0a6c4fa3e53c394680", - "notes": [], - "params": { - "dropOffset": { - "x": 0.0, - "y": 0.0, - "z": 2.5 - }, - "labwareId": "UUID", - "newLocation": { - "slotName": "B1" - }, - "pickUpOffset": { - "x": 0.0, - "y": 0.0, - "z": 2.5 - }, - "strategy": "usingGripper" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "aa1951fa38879e0899843bb390a7b8b1", - "notes": [], - "params": { - "message": "==============================================" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "e7f1198af861c701008b85f998146760", - "notes": [], - "params": { - "message": "--> Cleanup" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "d6e8bf045e71cf7a82fb180e3a66354a", - "notes": [], - "params": { - "message": "==============================================" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "26cf5debaf21ed1a03d300556cee5449", - "notes": [], - "params": { - "message": "--> ADDING AMPure (0.8x)" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "pickUpTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "b3633aff03787b1665ab46efd6525833", - "notes": [], - "params": { - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 288.38, - "z": 110.0 - }, - "tipDiameter": 5.59, - "tipLength": 47.85, - "tipVolume": 200.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "8806d6428a043d2a761034ae59187448", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 42.5, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -37.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 181.15, - "z": 4.0 - }, - "volume": 42.5 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "638475cd54ea33aa1b99781c216f97b4", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 42.5, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -37.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 181.15, - "z": 4.0 - }, - "volume": 42.5 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "658f670682d4ce440c195bb02ca09ee1", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 32.5, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -37.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 181.15, - "z": 4.0 - }, - "volume": 32.5 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "d90229144f31d052d4ed66adbcc08218", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 32.5, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 32.5 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "8e96b9e54bf2838f4ef3615b136c7c42", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -11.28 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 4.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "23a45acaf1c7b7f151ffd2014ea99639", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -11.28 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 4.42 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "5dcd1aaa97dd090ec7cb80b5ec484e67", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "faed2d7eda59600df9f1e650c8703334", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 30.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 30.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "23b2d4d82448260c5f5defef07f8a083", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 30.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 30.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "37c076c17ea25bae0e82a538c254b29b", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -11.28 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 4.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "13129a71ae9a69275b584352767031a2", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -11.28 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 4.42 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "10347f507f8e4a4e2f5fbb5d8e27f412", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -11.28 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 4.42 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "d73ccdcfece0a338871a1d28ce911a28", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "9be4065382b68a82f31fb146b4682621", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 30.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 30.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "d6a40bd86b9705adb90a340e20ba445d", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 30.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 30.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "f3979b6571a8a531b46a37f23fa66632", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -11.28 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 4.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "f22d6d4732f754a595dc15f6907d6ec8", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -11.28 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 4.42 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "blowout", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "dfcb1a69d25e6b5e274b51eb3d0e234d", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 2.0000000000000036 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 17.700000000000003 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "7c7be0f0d8203b5eb7170e35a56c513d", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 5.0000000000000036 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 20.700000000000003 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "6b9cc232b1f625f92a5f517450e3ce7c", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 15.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "c7a1113697abebf2abde06496a063088", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 5.0000000000000036 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 20.700000000000003 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "75be28e1d33906646a467639ca3b6e21", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 288.38, - "z": 98.33 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveLabware", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "24f18250fffd7db34b1db3325e04cc08", - "notes": [], - "params": { - "dropOffset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "labwareId": "UUID", - "newLocation": { - "moduleId": "UUID" - }, - "pickUpOffset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "strategy": "usingGripper" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "2991ead2557f526e9dc472b7cec14239", - "notes": [], - "params": { - "message": "--> Removing Supernatant" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "pickUpTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "a06bcf153dde96a6e1ea714c1ce8395c", - "notes": [], - "params": { - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 288.38, - "z": 110.0 - }, - "tipDiameter": 5.59, - "tipLength": 47.85, - "tipVolume": 200.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "23b57bc03e22992e00b9ad05c6a18698", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -11.280000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 42.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "6e55232a48d6f47be556cccdbdfb4209", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 100.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -11.280000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 42.42 - }, - "volume": 100.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "waitForDuration", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "c49a6eafa13ef28c520c1b10dda15d4c", - "notes": [], - "params": { - "seconds": 6.0 - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "5a4c804a6e76bb110d9ef7f67bdcfbf1", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.280000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 39.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "fcb27fca6044f0a1575a0da7580321df", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 100.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.280000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 39.42 - }, - "volume": 100.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "550eb2759b834a2654bb4b48009a108e", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 2.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 55.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "987a91ca640f51b039922303be75425d", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 200.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - }, - "volume": 200.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "waitForDuration", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "2b9848b57508a0d08fd2f8bf7d24d272", - "notes": [], - "params": { - "seconds": 6.0 - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "blowout", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "72a83938eef920866c15e77222df96b9", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "efc86cce59ff824c0289f9244a23fe24", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -5.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 36.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "99f82809524010e4454b5226d4af6294", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "0cabbe54412b1386d8816bc9463a3248", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 288.38, - "z": 98.33 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "80c45a492a00607e41f3dd721319c05e", - "notes": [], - "params": { - "message": "--> ETOH Wash" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "pickUpTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "a548c677b40f961f70558d86a32f6de8", - "notes": [], - "params": { - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 288.38, - "z": 110.0 - }, - "tipDiameter": 5.59, - "tipLength": 47.85, - "tipVolume": 200.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "f1f585f1ed768404ba10ff9ab596afa5", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 150.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -37.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 74.15, - "z": 4.0 - }, - "volume": 150.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "78d951601c7fdf0628b225ac402474a3", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 74.15, - "z": 41.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "db3d78f976a5699833b32c70d2e8cda5", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -5.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 74.15, - "z": 36.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "11a9bb504b1a6837c74e17efd38ea09d", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 74.15, - "z": 41.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "036869fb57776335e8bc4bcd6d7d5890", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -2.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 51.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "68b6329faf0a4ad454773e5a375eabd0", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 150.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -2.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 51.7 - }, - "volume": 150.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "waitForDuration", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "5d2ef40b95dc73df441a3d7e8347dd2a", - "notes": [], - "params": { - "seconds": 6.0 - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "blowout", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "c4b5b06052d6eab3802c41e7b5c65bf9", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -2.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 51.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "de1bd027f8632cfd2145b7817f401215", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 5.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 58.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "f49797bc76040eb1421779a227e28f4a", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 53.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "7bd38545581447d9c25c9d1ffea0fb18", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 5.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 58.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "d1a6f67cd3c84ee462f0309044789a8a", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 288.38, - "z": 98.33 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "1aff87d9a5a7d39128ea5bdf9c17ea2d", - "notes": [], - "params": { - "message": "--> Remove ETOH Wash" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "pickUpTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "092f24dd2620273f3346195cbcf177b4", - "notes": [], - "params": { - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 288.38, - "z": 110.0 - }, - "tipDiameter": 5.59, - "tipLength": 47.85, - "tipVolume": 200.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "dc390204b83f5c541268e9a5a6970ebb", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -11.280000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 42.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "dd564b385a84a979d4f8ecee999451d8", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 100.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -11.280000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 42.42 - }, - "volume": 100.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "waitForDuration", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "0a6c35dccb117b7541a92a31d21cfe3f", - "notes": [], - "params": { - "seconds": 6.0 - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "2f60fa4dd0db2c727be606db997fc2f2", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.280000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 39.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "851ca78acdfc7623d4686d6db5ff1b44", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 100.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.280000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 39.42 - }, - "volume": 100.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "8365d88178616cf1f6f04c8743ce32c3", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 2.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 55.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "9bdd6137a799a31388c28b8ad0a7790b", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 200.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - }, - "volume": 200.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "waitForDuration", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "a14fe58e21b274c406530b250008f731", - "notes": [], - "params": { - "seconds": 6.0 - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "blowout", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "06c0a16a5af261d4af767d035d903aca", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "b76f341ce59fcd44c12f17fdeba78423", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -5.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 36.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "47a24c435aab42c035ebeb55994ef88c", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "715286898ae4c10e0883256f7e248524", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 288.38, - "z": 98.33 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "8bf111bb0f49d9ca300548b3bca02006", - "notes": [], - "params": { - "message": "--> ETOH Wash" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "pickUpTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "fa33e5a6a018480144a977f89ece9416", - "notes": [], - "params": { - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 288.38, - "z": 110.0 - }, - "tipDiameter": 5.59, - "tipLength": 47.85, - "tipVolume": 200.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "d33d528fbbc6705921a30d1fe4986d09", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 150.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -37.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 74.15, - "z": 4.0 - }, - "volume": 150.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "521205f58c4e8692a05d6e91ea533e74", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 74.15, - "z": 41.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "e49dcd39b2271da3746505498ce59c7a", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -5.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 74.15, - "z": 36.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "b9b6f66e8317346eac627e38ed4531de", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 74.15, - "z": 41.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "f8f249cf923bd72d9e145912ff4a9a2b", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -2.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 51.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "74f472222f4145720ec50104ea3af95f", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 150.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -2.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 51.7 - }, - "volume": 150.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "waitForDuration", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "f85e035e5229a2ce61f1a56d1960d6ee", - "notes": [], - "params": { - "seconds": 6.0 - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "blowout", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "1f928ee18b19c8b7fab68cb912d7b01a", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -2.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 51.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "97ac745a92ca64ad96b407fdf4e0b582", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 5.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 58.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "c5ecb091d69c504ee431a753e5458517", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 53.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "173760a7eab7740161821148b17342c0", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 5.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 58.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "796946193ecd5af7386d9b492cb78c8d", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 288.38, - "z": 98.33 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "3c2b104144107947fb0b51af4ec0b87e", - "notes": [], - "params": { - "message": "--> Remove ETOH Wash" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "pickUpTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "b7db2748b9c8b24ec9ef933846abed33", - "notes": [], - "params": { - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 288.38, - "z": 110.0 - }, - "tipDiameter": 5.59, - "tipLength": 47.85, - "tipVolume": 200.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "74201a7a47d5c0ac5930cf4e3938aa08", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -11.280000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 42.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "1ea3c1ac54fe5d2f9a0c6fab3ca0528b", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 100.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -11.280000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 42.42 - }, - "volume": 100.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "waitForDuration", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "6b005cb2d8f73ec6fe122a0f8a382379", - "notes": [], - "params": { - "seconds": 6.0 - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "63773680977884882194109e6faea5fe", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.280000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 39.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "790b29b7ba75de59551ed63da1548699", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 100.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.280000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 39.42 - }, - "volume": 100.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "f60c3212a41e944d73dfa5eb17ddbd01", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 2.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 55.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "276da36e6dfe24d7369b40ace4533a87", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 200.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - }, - "volume": 200.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "waitForDuration", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "175df187a15317f3ef2bb391837fb451", - "notes": [], - "params": { - "seconds": 6.0 - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "blowout", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "13602b2822688d3c9894a38ae11a99c5", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "4f7dd03104c116d85008d19081200b29", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -5.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 36.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "708ee69cbd175af9b43de27a5c4f138a", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "a0ea6776b8974d5a8f85c262636982f4", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 288.38, - "z": 98.33 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "ae1d7119bde7ae85a23d500f5d1f99c9", - "notes": [], - "params": { - "message": "--> Removing Residual ETOH" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "pickUpTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "c1a2163776122112108770bb88ea57ef", - "notes": [], - "params": { - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 288.38, - "z": 110.0 - }, - "tipDiameter": 5.59, - "tipLength": 47.85, - "tipVolume": 200.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "cb6e8a0557433dca2c57ad1da0203718", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.780000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 38.92 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "bb327e06fc1d9c67c992aae88ac64384", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.780000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 38.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "7c6a09550524ec5899a760ebff2784f0", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "waitForDuration", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "7860bf504c13d44280b45a9dce57a7aa", - "notes": [], - "params": { - "seconds": 6.0 - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "blowout", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "5c44e272238b1c7d1243f10df3c6b110", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "1ece3394e39f1cb774994781ac78e5a7", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -5.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 36.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "361335e7849a375cd78399d1c94cf733", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "898cff86a6ce1fed6e12cb69e0dbc92a", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 288.38, - "z": 98.33 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveLabware", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "0b79defdbd12e8f5a164895d54065b6f", - "notes": [], - "params": { - "dropOffset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "labwareId": "UUID", - "newLocation": { - "slotName": "D1" - }, - "pickUpOffset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "strategy": "usingGripper" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "4e3e3eb9640855f1b2175b38216f0427", - "notes": [], - "params": { - "message": "--> Adding RSB" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "pickUpTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "213ef05065ae1662fe3083f534375516", - "notes": [], - "params": { - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 181.38, - "z": 110.0 - }, - "tipDiameter": 5.58, - "tipLength": 47.4, - "tipVolume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "12320a7700e2126445d01da92db2097b", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -37.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 288.15, - "z": 4.0 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "0a683ac90cbca9dbc44d5c4adeee11a4", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 1.040000000000001, - "y": 0.0, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 15.420000000000002, - "y": 74.24, - "z": 4.3100000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "68da6986915d2f49b3c00f5833a865d4", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 1.040000000000001, - "y": 0.0, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 15.420000000000002, - "y": 74.24, - "z": 4.3100000000000005 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "a82541298ae8c311b708f7e127d755e3", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "7f86a3024d5a3c0fe793ec8c24470a6c", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "af08a90d8a1a9308019e04608ea77520", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 1.0400000000000063, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 75.28, - "z": 4.3100000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "0b184f5716f59f720521b0602d31ad0e", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 1.0400000000000063, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 75.28, - "z": 4.3100000000000005 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "1aa84d04a0d70f609f3fcf4e69fde1db", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "97c4958ed9c1955bf8b04b791086d8e2", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "14110cd02d5f69c634e6d24b0d0f5e19", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": -1.040000000000001, - "y": 0.0, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 13.34, - "y": 74.24, - "z": 4.3100000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "199fd1d9627cb787adf10e3cbf34fcb6", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": -1.040000000000001, - "y": 0.0, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 13.34, - "y": 74.24, - "z": 4.3100000000000005 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "883cb4361276e0f7007affa7d6802c80", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "a5ca868b9314a2f50462fd27bd4d1ad2", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "6e238e5bad4a4af39d62e97c8e4fada2", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": -1.0400000000000063, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 73.19999999999999, - "z": 4.3100000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "2f077c3a958b6008049bca10442ff5c1", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": -1.0400000000000063, - "z": -11.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 73.19999999999999, - "z": 4.3100000000000005 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "51db8d2bf889d97ec69876621e29a6e7", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "2215af66f3d9e78ec88c384788476b19", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "c70dab8c1db85c9c618a6a0f8e404d0e", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 22.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 22.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "blowout", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "7de2ac3793c0ea6aefa4aeb31093732f", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -7.389999999999999 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 8.31 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "6fe269487c3348c6bcc42c152f032494", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 5.0000000000000036 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 20.700000000000003 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "f082ad610caba9794608d5e81044f22f", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 15.7 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "3a9dbf8a8ea6c8fb262312a3b29ddc01", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 5.0000000000000036 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 20.700000000000003 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "970073739d6b996a85e6e54cc05fa4df", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 181.38, - "z": 98.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveLabware", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "2918bddc5aa115fe842c5318906125fa", - "notes": [], - "params": { - "dropOffset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "labwareId": "UUID", - "newLocation": { - "moduleId": "UUID" - }, - "pickUpOffset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "strategy": "usingGripper" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "98708bb529ab479dbc65bb3cfe67bd49", - "notes": [], - "params": { - "message": "--> Transferring Supernatant" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "pickUpTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "7a2a51a0fde432bcb08fa34f0d11e425", - "notes": [], - "params": { - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 181.38, - "z": 110.0 - }, - "tipDiameter": 5.58, - "tipLength": 47.4, - "tipVolume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "a9c83d38f3d5d60a85d875d06706f2cd", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.530000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 39.17 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "a70fa90b66284a788838df086b784325", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 21.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.530000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 39.17 - }, - "volume": 21.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "3737ec9b811f9c9f7fe9dbeda1ec5c26", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 21.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.78 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 74.24, - "z": 1.92 - }, - "volume": 21.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "dd3f847c0248fdabaa11ff52c6396edc", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 181.38, - "z": 98.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - } - ], - "config": { - "apiVersion": [ - 2, - 15 - ], - "protocolType": "python" - }, - "createdAt": "TIMESTAMP", - "errors": [], - "files": [ - { - "name": "Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IDTXgen96Part1to3.py", - "role": "main" - } - ], - "labware": [ - { - "definitionUri": "opentrons/opentrons_1_trash_3200ml_fixed/1", - "id": "UUID", - "loadName": "opentrons_1_trash_3200ml_fixed", - "location": { - "slotName": "A3" - } - }, - { - "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", - "id": "UUID", - "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", - "location": { - "moduleId": "UUID" - } - }, - { - "definitionUri": "opentrons/nest_96_wellplate_2ml_deep/2", - "id": "UUID", - "loadName": "nest_96_wellplate_2ml_deep", - "location": { - "slotName": "D2" - } - }, - { - "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", - "id": "UUID", - "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", - "location": { - "slotName": "D3" - } - }, - { - "definitionUri": "opentrons/nest_96_wellplate_2ml_deep/2", - "id": "UUID", - "loadName": "nest_96_wellplate_2ml_deep", - "location": { - "slotName": "C2" - } - }, - { - "definitionUri": "opentrons/opentrons_flex_96_tiprack_adapter/1", + "definitionUri": "opentrons/opentrons_flex_96_tiprack_adapter/1", "id": "UUID", "loadName": "opentrons_flex_96_tiprack_adapter", "location": { @@ -21536,7 +15240,7 @@ "id": "UUID", "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", "location": { - "slotName": "B1" + "moduleId": "UUID" } }, { @@ -21572,6 +15276,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", @@ -21594,7 +15299,7 @@ "pipetteName": "p1000_96" } ], - "result": "ok", + "result": "not-ok", "robotType": "OT-3 Standard", "runTimeParameters": [] } diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[acefe91275][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[acefe91275][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left].json new file mode 100644 index 00000000000..1f453e29cf8 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[acefe91275][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left].json @@ -0,0 +1,2458 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_20ul", + "location": { + "slotName": "1" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-10ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.69 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 20 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_20ul", + "tipLength": 39.2, + "tipOverlap": 8.25 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 74.24, + "z": 25.49 + }, + "A10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 74.24, + "z": 25.49 + }, + "A11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 74.24, + "z": 25.49 + }, + "A12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 74.24, + "z": 25.49 + }, + "A2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 74.24, + "z": 25.49 + }, + "A3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 74.24, + "z": 25.49 + }, + "A4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 74.24, + "z": 25.49 + }, + "A5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 74.24, + "z": 25.49 + }, + "A6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 74.24, + "z": 25.49 + }, + "A7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 74.24, + "z": 25.49 + }, + "A8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 74.24, + "z": 25.49 + }, + "A9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 74.24, + "z": 25.49 + }, + "B1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 65.24, + "z": 25.49 + }, + "B10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 65.24, + "z": 25.49 + }, + "B11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 65.24, + "z": 25.49 + }, + "B12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 65.24, + "z": 25.49 + }, + "B2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 65.24, + "z": 25.49 + }, + "B3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 65.24, + "z": 25.49 + }, + "B4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 65.24, + "z": 25.49 + }, + "B5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 65.24, + "z": 25.49 + }, + "B6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 65.24, + "z": 25.49 + }, + "B7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 65.24, + "z": 25.49 + }, + "B8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 65.24, + "z": 25.49 + }, + "B9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 65.24, + "z": 25.49 + }, + "C1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 56.24, + "z": 25.49 + }, + "C10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 56.24, + "z": 25.49 + }, + "C11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 56.24, + "z": 25.49 + }, + "C12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 56.24, + "z": 25.49 + }, + "C2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 56.24, + "z": 25.49 + }, + "C3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 56.24, + "z": 25.49 + }, + "C4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 56.24, + "z": 25.49 + }, + "C5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 56.24, + "z": 25.49 + }, + "C6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 56.24, + "z": 25.49 + }, + "C7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 56.24, + "z": 25.49 + }, + "C8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 56.24, + "z": 25.49 + }, + "C9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 56.24, + "z": 25.49 + }, + "D1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 47.24, + "z": 25.49 + }, + "D10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 47.24, + "z": 25.49 + }, + "D11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 47.24, + "z": 25.49 + }, + "D12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 47.24, + "z": 25.49 + }, + "D2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 47.24, + "z": 25.49 + }, + "D3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 47.24, + "z": 25.49 + }, + "D4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 47.24, + "z": 25.49 + }, + "D5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 47.24, + "z": 25.49 + }, + "D6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 47.24, + "z": 25.49 + }, + "D7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 47.24, + "z": 25.49 + }, + "D8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 47.24, + "z": 25.49 + }, + "D9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 47.24, + "z": 25.49 + }, + "E1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 38.24, + "z": 25.49 + }, + "E10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 38.24, + "z": 25.49 + }, + "E11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 38.24, + "z": 25.49 + }, + "E12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 38.24, + "z": 25.49 + }, + "E2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 38.24, + "z": 25.49 + }, + "E3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 38.24, + "z": 25.49 + }, + "E4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 38.24, + "z": 25.49 + }, + "E5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 38.24, + "z": 25.49 + }, + "E6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 38.24, + "z": 25.49 + }, + "E7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 38.24, + "z": 25.49 + }, + "E8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 38.24, + "z": 25.49 + }, + "E9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 38.24, + "z": 25.49 + }, + "F1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 29.24, + "z": 25.49 + }, + "F10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 29.24, + "z": 25.49 + }, + "F11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 29.24, + "z": 25.49 + }, + "F12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 29.24, + "z": 25.49 + }, + "F2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 29.24, + "z": 25.49 + }, + "F3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 29.24, + "z": 25.49 + }, + "F4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 29.24, + "z": 25.49 + }, + "F5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 29.24, + "z": 25.49 + }, + "F6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 29.24, + "z": 25.49 + }, + "F7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 29.24, + "z": 25.49 + }, + "F8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 29.24, + "z": 25.49 + }, + "F9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 29.24, + "z": 25.49 + }, + "G1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 20.24, + "z": 25.49 + }, + "G10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 20.24, + "z": 25.49 + }, + "G11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 20.24, + "z": 25.49 + }, + "G12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 20.24, + "z": 25.49 + }, + "G2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 20.24, + "z": 25.49 + }, + "G3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 20.24, + "z": 25.49 + }, + "G4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 20.24, + "z": 25.49 + }, + "G5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 20.24, + "z": 25.49 + }, + "G6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 20.24, + "z": 25.49 + }, + "G7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 20.24, + "z": 25.49 + }, + "G8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 20.24, + "z": 25.49 + }, + "G9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 20.24, + "z": 25.49 + }, + "H1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 11.24, + "z": 25.49 + }, + "H10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 11.24, + "z": 25.49 + }, + "H11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 11.24, + "z": 25.49 + }, + "H12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 11.24, + "z": 25.49 + }, + "H2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 11.24, + "z": 25.49 + }, + "H3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 11.24, + "z": 25.49 + }, + "H4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 11.24, + "z": 25.49 + }, + "H5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 11.24, + "z": 25.49 + }, + "H6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 11.24, + "z": 25.49 + }, + "H7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 11.24, + "z": 25.49 + }, + "H8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 11.24, + "z": 25.49 + }, + "H9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 11.24, + "z": 25.49 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50de88d471ad3910c29207fb6df4502e", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-300ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.49 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 300 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_300ul", + "tipLength": 59.3, + "tipOverlap": 7.47 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 74.24, + "z": 5.39 + }, + "A10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 74.24, + "z": 5.39 + }, + "A11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 74.24, + "z": 5.39 + }, + "A12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 74.24, + "z": 5.39 + }, + "A2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 74.24, + "z": 5.39 + }, + "A3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 74.24, + "z": 5.39 + }, + "A4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 74.24, + "z": 5.39 + }, + "A5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 74.24, + "z": 5.39 + }, + "A6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 74.24, + "z": 5.39 + }, + "A7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 74.24, + "z": 5.39 + }, + "A8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 74.24, + "z": 5.39 + }, + "A9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 74.24, + "z": 5.39 + }, + "B1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 65.24, + "z": 5.39 + }, + "B10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 65.24, + "z": 5.39 + }, + "B11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 65.24, + "z": 5.39 + }, + "B12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 65.24, + "z": 5.39 + }, + "B2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 65.24, + "z": 5.39 + }, + "B3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 65.24, + "z": 5.39 + }, + "B4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 65.24, + "z": 5.39 + }, + "B5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 65.24, + "z": 5.39 + }, + "B6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 65.24, + "z": 5.39 + }, + "B7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 65.24, + "z": 5.39 + }, + "B8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 65.24, + "z": 5.39 + }, + "B9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 65.24, + "z": 5.39 + }, + "C1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 56.24, + "z": 5.39 + }, + "C10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 56.24, + "z": 5.39 + }, + "C11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 56.24, + "z": 5.39 + }, + "C12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 56.24, + "z": 5.39 + }, + "C2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 56.24, + "z": 5.39 + }, + "C3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 56.24, + "z": 5.39 + }, + "C4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 56.24, + "z": 5.39 + }, + "C5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 56.24, + "z": 5.39 + }, + "C6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 56.24, + "z": 5.39 + }, + "C7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 56.24, + "z": 5.39 + }, + "C8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 56.24, + "z": 5.39 + }, + "C9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 56.24, + "z": 5.39 + }, + "D1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 47.24, + "z": 5.39 + }, + "D10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 47.24, + "z": 5.39 + }, + "D11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 47.24, + "z": 5.39 + }, + "D12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 47.24, + "z": 5.39 + }, + "D2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 47.24, + "z": 5.39 + }, + "D3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 47.24, + "z": 5.39 + }, + "D4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 47.24, + "z": 5.39 + }, + "D5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 47.24, + "z": 5.39 + }, + "D6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 47.24, + "z": 5.39 + }, + "D7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 47.24, + "z": 5.39 + }, + "D8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 47.24, + "z": 5.39 + }, + "D9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 47.24, + "z": 5.39 + }, + "E1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 38.24, + "z": 5.39 + }, + "E10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 38.24, + "z": 5.39 + }, + "E11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 38.24, + "z": 5.39 + }, + "E12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 38.24, + "z": 5.39 + }, + "E2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 38.24, + "z": 5.39 + }, + "E3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 38.24, + "z": 5.39 + }, + "E4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 38.24, + "z": 5.39 + }, + "E5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 38.24, + "z": 5.39 + }, + "E6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 38.24, + "z": 5.39 + }, + "E7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 38.24, + "z": 5.39 + }, + "E8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 38.24, + "z": 5.39 + }, + "E9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 38.24, + "z": 5.39 + }, + "F1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 29.24, + "z": 5.39 + }, + "F10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 29.24, + "z": 5.39 + }, + "F11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 29.24, + "z": 5.39 + }, + "F12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 29.24, + "z": 5.39 + }, + "F2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 29.24, + "z": 5.39 + }, + "F3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 29.24, + "z": 5.39 + }, + "F4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 29.24, + "z": 5.39 + }, + "F5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 29.24, + "z": 5.39 + }, + "F6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 29.24, + "z": 5.39 + }, + "F7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 29.24, + "z": 5.39 + }, + "F8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 29.24, + "z": 5.39 + }, + "F9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 29.24, + "z": 5.39 + }, + "G1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 20.24, + "z": 5.39 + }, + "G10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 20.24, + "z": 5.39 + }, + "G11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 20.24, + "z": 5.39 + }, + "G12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 20.24, + "z": 5.39 + }, + "G2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 20.24, + "z": 5.39 + }, + "G3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 20.24, + "z": 5.39 + }, + "G4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 20.24, + "z": 5.39 + }, + "G5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 20.24, + "z": 5.39 + }, + "G6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 20.24, + "z": 5.39 + }, + "G7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 20.24, + "z": 5.39 + }, + "G8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 20.24, + "z": 5.39 + }, + "G9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 20.24, + "z": 5.39 + }, + "H1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 11.24, + "z": 5.39 + }, + "H10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 11.24, + "z": 5.39 + }, + "H11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 11.24, + "z": 5.39 + }, + "H12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 11.24, + "z": 5.39 + }, + "H2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 11.24, + "z": 5.39 + }, + "H3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 11.24, + "z": 5.39 + }, + "H4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 11.24, + "z": 5.39 + }, + "H5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 11.24, + "z": 5.39 + }, + "H6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 11.24, + "z": 5.39 + }, + "H7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 11.24, + "z": 5.39 + }, + "H8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 11.24, + "z": 5.39 + }, + "H9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 11.24, + "z": 5.39 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "54fddab1eb921f6e1d3aa7e96d2ac307", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p300_multi_gen2", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "error": { + "createdAt": "TIMESTAMP", + "detail": "Attempted Nozzle Configuration does not match any approved map layout for the current pipette.", + "errorCode": "4007", + "errorInfo": {}, + "errorType": "IncompatibleNozzleConfiguration", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + }, + "id": "UUID", + "key": "4b1d27a6f17f312dd76668f0c48ed406", + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], + "params": { + "configurationParams": { + "backLeftNozzle": "A1", + "frontRightNozzle": "B1", + "primaryNozzle": "A1", + "style": "QUADRANT" + }, + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "failed" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "ProtocolCommandFailedError [line 134]: Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): IncompatibleNozzleConfiguration: Attempted Nozzle Configuration does not match any approved map layout for the current pipette.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "IncompatibleNozzleConfiguration: Attempted Nozzle Configuration does not match any approved map layout for the current pipette.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ProtocolCommandFailedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Attempted Nozzle Configuration does not match any approved map layout for the current pipette.", + "errorCode": "4007", + "errorInfo": {}, + "errorType": "IncompatibleNozzleConfiguration", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ] + } + ], + "files": [ + { + "name": "OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_left.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_20ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_20ul", + "location": { + "slotName": "1" + } + }, + { + "definitionUri": "opentrons/opentrons_96_tiprack_300ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "2" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Invalid tip configs that should error" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p300_multi_gen2" + } + ], + "result": "not-ok", + "robotType": "OT-2 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad627dcedf][OT2_S_v6_P300M_P20S_HS_Smoke620release].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad627dcedf][OT2_S_v6_P300M_P20S_HS_Smoke620release].json index 4c8cb3d5d6a..4af69fce36b 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad627dcedf][OT2_S_v6_P300M_P20S_HS_Smoke620release].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad627dcedf][OT2_S_v6_P300M_P20S_HS_Smoke620release].json @@ -5930,7 +5930,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5963,7 +5964,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6060,7 +6062,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6093,7 +6096,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -6190,7 +6194,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -6223,7 +6228,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -6320,7 +6326,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -6353,7 +6360,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -6450,7 +6458,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -6483,7 +6492,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -6696,7 +6706,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6729,7 +6740,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6826,7 +6838,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -6859,7 +6872,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6956,7 +6970,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -6989,7 +7004,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7086,7 +7102,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -7119,7 +7136,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7216,7 +7234,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -7249,7 +7268,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7346,7 +7366,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7379,7 +7400,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7412,7 +7434,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7445,7 +7468,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7478,7 +7502,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7511,7 +7536,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7544,7 +7570,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7577,7 +7604,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7610,7 +7638,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7643,7 +7672,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7740,7 +7770,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7773,7 +7804,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7806,7 +7838,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7839,7 +7872,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7872,7 +7906,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7905,7 +7940,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7938,7 +7974,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7971,7 +8008,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -8004,7 +8042,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8037,7 +8076,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -8070,7 +8110,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8103,7 +8144,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -8136,7 +8178,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8169,7 +8212,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -8202,7 +8246,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8235,7 +8280,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -8268,7 +8314,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8301,7 +8348,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -8334,7 +8382,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8367,7 +8416,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -8400,7 +8450,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8433,7 +8484,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -8466,7 +8518,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8499,7 +8552,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -8532,7 +8586,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8565,7 +8620,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -8598,7 +8654,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8631,7 +8688,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -8664,7 +8722,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8697,7 +8756,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -8730,7 +8790,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8763,7 +8824,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -8796,7 +8858,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8829,7 +8892,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -8862,7 +8926,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8895,7 +8960,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -8928,7 +8994,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8961,7 +9028,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -8994,7 +9062,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9027,7 +9096,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -9060,7 +9130,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9093,7 +9164,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -9126,7 +9198,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9159,7 +9232,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -9192,7 +9266,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9225,7 +9300,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -9258,7 +9334,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9291,7 +9368,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -9414,6 +9492,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad9184067d][Flex_X_v2_18_NO_PIPETTES_ReservedWord].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad9184067d][Flex_X_v2_18_NO_PIPETTES_ReservedWord].json index d3338855040..ed08b660a33 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad9184067d][Flex_X_v2_18_NO_PIPETTES_ReservedWord].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ad9184067d][Flex_X_v2_18_NO_PIPETTES_ReservedWord].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Default not in range" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[adc0621263][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLid].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[adc0621263][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLid].json index a3cf2d44d05..a18485392e9 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[adc0621263][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLid].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[adc0621263][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLid].json @@ -6102,7 +6102,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -6116,6 +6117,40 @@ }, "startedAt": "TIMESTAMP", "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e1b16944e3d0ff8ae0a964f7e638c1b3", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 20.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -9.8 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 23.28, + "y": 181.18, + "z": 4.5 + }, + "volume": 20.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" } ], "config": { @@ -6129,7 +6164,7 @@ "errors": [ { "createdAt": "TIMESTAMP", - "detail": "PartialTipMovementNotAllowedError [line 25]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Requested motion with the A12 nozzle partial configuration is outside of robot bounds for the pipette.", + "detail": "PartialTipMovementNotAllowedError [line 28]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to NEST 96 Well Plate 200 µL Flat in slot A2 with A12 nozzle partial configuration will result in collision with thermocycler lid in deck slot A1.", "errorCode": "4000", "errorInfo": {}, "errorType": "ExceptionInProtocolError", @@ -6138,7 +6173,7 @@ "wrappedErrors": [ { "createdAt": "TIMESTAMP", - "detail": "Requested motion with the A12 nozzle partial configuration is outside of robot bounds for the pipette.", + "detail": "Moving to NEST 96 Well Plate 200 µL Flat in slot A2 with A12 nozzle partial configuration will result in collision with thermocycler lid in deck slot A1.", "errorCode": "2004", "errorInfo": {}, "errorType": "PartialTipMovementNotAllowedError", @@ -6205,6 +6240,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[afd5d372a9][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_return_tip_error].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[afd5d372a9][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_return_tip_error].json new file mode 100644 index 00000000000..9f12179d1e2 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[afd5d372a9][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_return_tip_error].json @@ -0,0 +1,3786 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_multi_flex", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "backLeftNozzle": "G1", + "frontRightNozzle": "H1", + "primaryNozzle": "H1", + "style": "QUADRANT" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c910be62291bb94458a59cb4185c5180", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D3" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2f59e0552969594ba6ab06f03af324e1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 178.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.92999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7ba58db1295d2a1b66a5e6a40d618831", + "notes": [], + "params": { + "message": "Tip rack in B2, well A1 has tip: False" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f6a04baea84331f9d79f8645ae5f367f", + "notes": [], + "params": { + "message": "Tip rack in B2, well B1 has tip: False" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3a257d45e68ca9f2c7279f9a7c31ad21", + "notes": [], + "params": { + "message": "Tip rack in B2, well C1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4b1c42c87368694a75338a5577e79371", + "notes": [], + "params": { + "message": "Tip rack in B2, well D1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "80cdcab7661e6faccf065771049bb6d4", + "notes": [], + "params": { + "message": "Tip rack in B2, well E1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b4253d217967a7cfbc022f5e143ac580", + "notes": [], + "params": { + "message": "Tip rack in B2, well F1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "60f927b4473b1fe3a70f61aabfb79781", + "notes": [], + "params": { + "message": "Tip rack in B2, well G1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6f45d808ddc38796bd92b2e64bee48a4", + "notes": [], + "params": { + "message": "Tip rack in B2, well H1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "UnexpectedProtocolError [line 180]: Error 4000 GENERAL_ERROR (UnexpectedProtocolError): Cannot return tip to a tiprack while the pipette is configured for partial tip.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Cannot return tip to a tiprack while the pipette is configured for partial tip.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "UnexpectedProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_return_tip_error.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D3" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Invalid tip configs that should error" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_multi_flex" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b0ce7dde5d][Flex_X_v2_16_P1000_96_TC_PartialTipPickupTryToReturnTip].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b0ce7dde5d][Flex_X_v2_16_P1000_96_TC_PartialTipPickupTryToReturnTip].json index 32e9e2f9294..b41b7117e24 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b0ce7dde5d][Flex_X_v2_16_P1000_96_TC_PartialTipPickupTryToReturnTip].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b0ce7dde5d][Flex_X_v2_16_P1000_96_TC_PartialTipPickupTryToReturnTip].json @@ -3592,7 +3592,8 @@ "y": 0.0, "z": -9.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -3606,6 +3607,40 @@ }, "startedAt": "TIMESTAMP", "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ddccee6754fe0092b9c66898d66b79a7", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 20.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -9.8 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 23.28, + "y": 181.18, + "z": 4.5 + }, + "volume": 20.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" } ], "config": { @@ -3619,7 +3654,7 @@ "errors": [ { "createdAt": "TIMESTAMP", - "detail": "PartialTipMovementNotAllowedError [line 21]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Requested motion with the A12 nozzle partial configuration is outside of robot bounds for the pipette.", + "detail": "UnexpectedProtocolError [line 22]: Error 4000 GENERAL_ERROR (UnexpectedProtocolError): Cannot return tip to a tiprack while the pipette is configured for partial tip.", "errorCode": "4000", "errorInfo": {}, "errorType": "ExceptionInProtocolError", @@ -3628,10 +3663,10 @@ "wrappedErrors": [ { "createdAt": "TIMESTAMP", - "detail": "Requested motion with the A12 nozzle partial configuration is outside of robot bounds for the pipette.", - "errorCode": "2004", + "detail": "Cannot return tip to a tiprack while the pipette is configured for partial tip.", + "errorCode": "4000", "errorInfo": {}, - "errorType": "PartialTipMovementNotAllowedError", + "errorType": "UnexpectedProtocolError", "id": "UUID", "isDefined": false, "wrappedErrors": [] @@ -3671,6 +3706,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [], diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b48407ff98][pl_cherrypicking_csv_airgap].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b48407ff98][pl_cherrypicking_csv_airgap].json index 4bfdeb4e850..6dffb02e16c 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b48407ff98][pl_cherrypicking_csv_airgap].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b48407ff98][pl_cherrypicking_csv_airgap].json @@ -7686,7 +7686,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7718,7 +7719,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7750,7 +7752,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7783,7 +7786,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -7816,7 +7820,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7849,7 +7854,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7883,7 +7889,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7916,7 +7923,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7950,7 +7958,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -7983,7 +7992,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8017,7 +8027,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8050,7 +8061,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8083,7 +8095,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8115,7 +8128,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -8223,7 +8237,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8255,7 +8270,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8287,7 +8303,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8320,7 +8337,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -8353,7 +8371,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8386,7 +8405,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8420,7 +8440,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8453,7 +8474,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8487,7 +8509,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8520,7 +8543,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8554,7 +8578,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8587,7 +8612,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8620,7 +8646,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8652,7 +8679,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -8760,7 +8788,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8792,7 +8821,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8824,7 +8854,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8857,7 +8888,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -8890,7 +8922,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -8923,7 +8956,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -8957,7 +8991,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -8990,7 +9025,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -9024,7 +9060,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -9057,7 +9094,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -9091,7 +9129,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -9124,7 +9163,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -9157,7 +9197,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -9189,7 +9230,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -9297,7 +9339,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9329,7 +9372,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9361,7 +9405,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9394,7 +9439,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -9427,7 +9473,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -9460,7 +9507,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -9494,7 +9542,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -9527,7 +9576,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -9561,7 +9611,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -9594,7 +9645,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -9628,7 +9680,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -9661,7 +9714,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -9694,7 +9748,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -9726,7 +9781,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -9834,7 +9890,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9866,7 +9923,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9898,7 +9956,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9931,7 +9990,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -9964,7 +10024,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -9997,7 +10058,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -10031,7 +10093,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -10064,7 +10127,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -10098,7 +10162,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -10131,7 +10196,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -10165,7 +10231,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -10198,7 +10265,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -10231,7 +10299,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -10263,7 +10332,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -10371,7 +10441,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10403,7 +10474,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10435,7 +10507,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10468,7 +10541,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -10501,7 +10575,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -10534,7 +10609,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -10568,7 +10644,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -10601,7 +10678,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -10635,7 +10713,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -10668,7 +10747,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -10702,7 +10782,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -10735,7 +10816,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -10768,7 +10850,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -10800,7 +10883,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -10908,7 +10992,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10940,7 +11025,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10972,7 +11058,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11005,7 +11092,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -11038,7 +11126,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11071,7 +11160,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11105,7 +11195,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11138,7 +11229,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11172,7 +11264,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11205,7 +11298,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11239,7 +11333,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11272,7 +11367,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11305,7 +11401,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11337,7 +11434,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11445,7 +11543,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11477,7 +11576,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11509,7 +11609,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11542,7 +11643,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -11575,7 +11677,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -11608,7 +11711,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -11642,7 +11746,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -11675,7 +11780,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -11709,7 +11815,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -11742,7 +11849,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -11776,7 +11884,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -11809,7 +11918,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -11842,7 +11952,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -11874,7 +11985,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -11982,7 +12094,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12014,7 +12127,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12046,7 +12160,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12079,7 +12194,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -12112,7 +12228,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -12145,7 +12262,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -12179,7 +12297,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -12212,7 +12331,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -12246,7 +12366,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -12279,7 +12400,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -12313,7 +12435,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -12346,7 +12469,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -12379,7 +12503,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -12411,7 +12536,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -12519,7 +12645,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12551,7 +12678,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12583,7 +12711,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12616,7 +12745,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -12649,7 +12779,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -12682,7 +12813,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -12716,7 +12848,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -12749,7 +12882,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -12783,7 +12917,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -12816,7 +12951,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -12850,7 +12986,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -12883,7 +13020,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -12916,7 +13054,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -12948,7 +13087,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -13056,7 +13196,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13088,7 +13229,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13120,7 +13262,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13153,7 +13296,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -13186,7 +13330,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -13219,7 +13364,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -13253,7 +13399,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -13286,7 +13433,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -13320,7 +13468,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -13353,7 +13502,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -13387,7 +13537,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -13420,7 +13571,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -13453,7 +13605,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -13485,7 +13638,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -13593,7 +13747,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13625,7 +13780,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13657,7 +13813,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13690,7 +13847,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -13723,7 +13881,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -13756,7 +13915,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -13790,7 +13950,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -13823,7 +13984,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -13857,7 +14019,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -13890,7 +14053,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -13924,7 +14088,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -13957,7 +14122,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -13990,7 +14156,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -14022,7 +14189,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -14130,7 +14298,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14162,7 +14331,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14194,7 +14364,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14227,7 +14398,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -14260,7 +14432,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -14293,7 +14466,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -14327,7 +14501,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -14360,7 +14535,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -14394,7 +14570,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -14427,7 +14604,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -14461,7 +14639,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -14494,7 +14673,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -14527,7 +14707,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -14559,7 +14740,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -14667,7 +14849,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14699,7 +14882,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14731,7 +14915,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14764,7 +14949,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -14797,7 +14983,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -14830,7 +15017,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -14864,7 +15052,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -14897,7 +15086,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -14931,7 +15121,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -14964,7 +15155,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -14998,7 +15190,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -15031,7 +15224,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -15064,7 +15258,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -15096,7 +15291,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -15204,7 +15400,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15236,7 +15433,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15268,7 +15466,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15301,7 +15500,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -15334,7 +15534,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15367,7 +15568,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15401,7 +15603,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15434,7 +15637,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15468,7 +15672,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15501,7 +15706,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15535,7 +15741,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15568,7 +15775,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15601,7 +15809,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15633,7 +15842,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15741,7 +15951,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15773,7 +15984,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15805,7 +16017,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15838,7 +16051,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -15871,7 +16085,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -15904,7 +16119,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -15938,7 +16154,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -15971,7 +16188,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -16005,7 +16223,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -16038,7 +16257,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -16072,7 +16292,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -16105,7 +16326,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -16138,7 +16360,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -16170,7 +16393,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -16278,7 +16502,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16310,7 +16535,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16342,7 +16568,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16375,7 +16602,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -16408,7 +16636,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -16441,7 +16670,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -16475,7 +16705,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -16508,7 +16739,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -16542,7 +16774,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -16575,7 +16808,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -16609,7 +16843,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -16642,7 +16877,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -16675,7 +16911,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -16707,7 +16944,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -16815,7 +17053,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16847,7 +17086,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16879,7 +17119,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16912,7 +17153,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -16945,7 +17187,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -16978,7 +17221,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -17012,7 +17256,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -17045,7 +17290,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -17079,7 +17325,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -17112,7 +17359,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -17146,7 +17394,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -17179,7 +17428,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -17212,7 +17462,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -17244,7 +17495,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -17352,7 +17604,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17384,7 +17637,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17416,7 +17670,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17449,7 +17704,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -17482,7 +17738,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -17515,7 +17772,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -17549,7 +17807,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -17582,7 +17841,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -17616,7 +17876,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -17649,7 +17910,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -17683,7 +17945,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -17716,7 +17979,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -17749,7 +18013,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -17781,7 +18046,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -17889,7 +18155,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17921,7 +18188,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17953,7 +18221,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17986,7 +18255,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -18019,7 +18289,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -18052,7 +18323,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -18086,7 +18358,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -18119,7 +18392,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -18153,7 +18427,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -18186,7 +18461,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -18220,7 +18496,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -18253,7 +18530,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -18286,7 +18564,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -18318,7 +18597,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -18426,7 +18706,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18458,7 +18739,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18490,7 +18772,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18523,7 +18806,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -18556,7 +18840,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18589,7 +18874,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18623,7 +18909,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18656,7 +18943,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18690,7 +18978,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18723,7 +19012,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18757,7 +19047,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18790,7 +19081,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18823,7 +19115,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18855,7 +19148,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -18963,7 +19257,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18995,7 +19290,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19027,7 +19323,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19060,7 +19357,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -19093,7 +19391,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -19126,7 +19425,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -19160,7 +19460,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -19193,7 +19494,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -19227,7 +19529,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -19260,7 +19563,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -19294,7 +19598,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -19327,7 +19632,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -19360,7 +19666,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -19392,7 +19699,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -19500,7 +19808,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19532,7 +19841,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19564,7 +19874,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19597,7 +19908,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -19630,7 +19942,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19663,7 +19976,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19697,7 +20011,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19730,7 +20045,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19764,7 +20080,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19797,7 +20114,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19831,7 +20149,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19864,7 +20183,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19897,7 +20217,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -19929,7 +20250,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -20037,7 +20359,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20069,7 +20392,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20101,7 +20425,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20134,7 +20459,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -20167,7 +20493,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -20200,7 +20527,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -20234,7 +20562,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -20267,7 +20596,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -20301,7 +20631,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -20334,7 +20665,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -20368,7 +20700,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -20401,7 +20734,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -20434,7 +20768,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -20466,7 +20801,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -20574,7 +20910,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20606,7 +20943,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20638,7 +20976,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20671,7 +21010,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -20704,7 +21044,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20737,7 +21078,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20771,7 +21113,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20804,7 +21147,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20838,7 +21182,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20871,7 +21216,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20905,7 +21251,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20938,7 +21285,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -20971,7 +21319,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -21003,7 +21352,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -21111,7 +21461,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21143,7 +21494,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21175,7 +21527,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21208,7 +21561,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21241,7 +21595,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -21274,7 +21629,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -21308,7 +21664,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -21341,7 +21698,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -21375,7 +21733,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -21408,7 +21767,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -21442,7 +21802,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -21475,7 +21836,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -21508,7 +21870,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -21540,7 +21903,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -21648,7 +22012,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21680,7 +22045,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21712,7 +22078,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21745,7 +22112,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -21778,7 +22146,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21811,7 +22180,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21845,7 +22215,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21878,7 +22249,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21912,7 +22284,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21945,7 +22318,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21979,7 +22353,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22012,7 +22387,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22045,7 +22421,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22077,7 +22454,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22185,7 +22563,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22217,7 +22596,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22249,7 +22629,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22282,7 +22663,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -22315,7 +22697,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -22348,7 +22731,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -22382,7 +22766,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -22415,7 +22800,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -22449,7 +22835,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -22482,7 +22869,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -22516,7 +22904,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -22549,7 +22938,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -22582,7 +22972,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -22614,7 +23005,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -22722,7 +23114,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22754,7 +23147,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22786,7 +23180,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22819,7 +23214,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -22852,7 +23248,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22885,7 +23282,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22919,7 +23317,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22952,7 +23351,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -22986,7 +23386,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -23019,7 +23420,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -23053,7 +23455,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -23086,7 +23489,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -23119,7 +23523,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -23151,7 +23556,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -23259,7 +23665,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23291,7 +23698,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23323,7 +23731,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23356,7 +23765,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -23389,7 +23799,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -23422,7 +23833,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -23456,7 +23868,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -23489,7 +23902,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -23523,7 +23937,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -23556,7 +23971,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -23590,7 +24006,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -23623,7 +24040,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -23656,7 +24074,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -23688,7 +24107,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -23796,7 +24216,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23828,7 +24249,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23860,7 +24282,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23893,7 +24316,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -23926,7 +24350,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -23959,7 +24384,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -23993,7 +24419,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -24026,7 +24453,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -24060,7 +24488,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -24093,7 +24522,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -24127,7 +24557,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -24160,7 +24591,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -24193,7 +24625,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -24225,7 +24658,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -24333,7 +24767,8 @@ "y": 0.0, "z": -38.22 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24365,7 +24800,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24397,7 +24833,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24430,7 +24867,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -24463,7 +24901,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -24496,7 +24935,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -24530,7 +24970,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -24563,7 +25004,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -24597,7 +25039,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -24630,7 +25073,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -24664,7 +25108,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -24697,7 +25142,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -24730,7 +25176,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -24762,7 +25209,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -28367,6 +28815,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Samples", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b777168ac1][Flex_S_v2_19_Illumina_Stranded_total_RNA_Ribo_Zero].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b777168ac1][Flex_S_v2_19_Illumina_Stranded_total_RNA_Ribo_Zero].json index e542e8191b2..43f62a32282 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b777168ac1][Flex_S_v2_19_Illumina_Stranded_total_RNA_Ribo_Zero].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b777168ac1][Flex_S_v2_19_Illumina_Stranded_total_RNA_Ribo_Zero].json @@ -4341,7 +4341,14 @@ }, "id": "UUID", "key": "bccdb28e967f574dfbe472004101d7f9", - "notes": [], + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], "params": { "displayName": "Index Anchors", "loadName": "eppendorf_96_wellplate_150ul", @@ -4485,6 +4492,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Dandra Howell ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b79134ab8a][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_drop_tip_with_location].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b79134ab8a][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_drop_tip_with_location].json new file mode 100644 index 00000000000..7956e369c52 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b79134ab8a][OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_drop_tip_with_location].json @@ -0,0 +1,4940 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_20ul", + "location": { + "slotName": "1" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-10ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.69 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 20 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_20ul", + "tipLength": 39.2, + "tipOverlap": 8.25 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 74.24, + "z": 25.49 + }, + "A10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 74.24, + "z": 25.49 + }, + "A11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 74.24, + "z": 25.49 + }, + "A12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 74.24, + "z": 25.49 + }, + "A2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 74.24, + "z": 25.49 + }, + "A3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 74.24, + "z": 25.49 + }, + "A4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 74.24, + "z": 25.49 + }, + "A5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 74.24, + "z": 25.49 + }, + "A6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 74.24, + "z": 25.49 + }, + "A7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 74.24, + "z": 25.49 + }, + "A8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 74.24, + "z": 25.49 + }, + "A9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 74.24, + "z": 25.49 + }, + "B1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 65.24, + "z": 25.49 + }, + "B10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 65.24, + "z": 25.49 + }, + "B11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 65.24, + "z": 25.49 + }, + "B12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 65.24, + "z": 25.49 + }, + "B2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 65.24, + "z": 25.49 + }, + "B3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 65.24, + "z": 25.49 + }, + "B4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 65.24, + "z": 25.49 + }, + "B5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 65.24, + "z": 25.49 + }, + "B6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 65.24, + "z": 25.49 + }, + "B7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 65.24, + "z": 25.49 + }, + "B8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 65.24, + "z": 25.49 + }, + "B9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 65.24, + "z": 25.49 + }, + "C1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 56.24, + "z": 25.49 + }, + "C10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 56.24, + "z": 25.49 + }, + "C11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 56.24, + "z": 25.49 + }, + "C12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 56.24, + "z": 25.49 + }, + "C2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 56.24, + "z": 25.49 + }, + "C3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 56.24, + "z": 25.49 + }, + "C4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 56.24, + "z": 25.49 + }, + "C5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 56.24, + "z": 25.49 + }, + "C6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 56.24, + "z": 25.49 + }, + "C7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 56.24, + "z": 25.49 + }, + "C8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 56.24, + "z": 25.49 + }, + "C9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 56.24, + "z": 25.49 + }, + "D1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 47.24, + "z": 25.49 + }, + "D10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 47.24, + "z": 25.49 + }, + "D11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 47.24, + "z": 25.49 + }, + "D12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 47.24, + "z": 25.49 + }, + "D2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 47.24, + "z": 25.49 + }, + "D3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 47.24, + "z": 25.49 + }, + "D4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 47.24, + "z": 25.49 + }, + "D5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 47.24, + "z": 25.49 + }, + "D6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 47.24, + "z": 25.49 + }, + "D7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 47.24, + "z": 25.49 + }, + "D8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 47.24, + "z": 25.49 + }, + "D9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 47.24, + "z": 25.49 + }, + "E1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 38.24, + "z": 25.49 + }, + "E10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 38.24, + "z": 25.49 + }, + "E11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 38.24, + "z": 25.49 + }, + "E12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 38.24, + "z": 25.49 + }, + "E2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 38.24, + "z": 25.49 + }, + "E3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 38.24, + "z": 25.49 + }, + "E4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 38.24, + "z": 25.49 + }, + "E5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 38.24, + "z": 25.49 + }, + "E6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 38.24, + "z": 25.49 + }, + "E7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 38.24, + "z": 25.49 + }, + "E8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 38.24, + "z": 25.49 + }, + "E9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 38.24, + "z": 25.49 + }, + "F1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 29.24, + "z": 25.49 + }, + "F10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 29.24, + "z": 25.49 + }, + "F11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 29.24, + "z": 25.49 + }, + "F12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 29.24, + "z": 25.49 + }, + "F2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 29.24, + "z": 25.49 + }, + "F3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 29.24, + "z": 25.49 + }, + "F4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 29.24, + "z": 25.49 + }, + "F5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 29.24, + "z": 25.49 + }, + "F6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 29.24, + "z": 25.49 + }, + "F7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 29.24, + "z": 25.49 + }, + "F8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 29.24, + "z": 25.49 + }, + "F9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 29.24, + "z": 25.49 + }, + "G1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 20.24, + "z": 25.49 + }, + "G10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 20.24, + "z": 25.49 + }, + "G11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 20.24, + "z": 25.49 + }, + "G12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 20.24, + "z": 25.49 + }, + "G2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 20.24, + "z": 25.49 + }, + "G3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 20.24, + "z": 25.49 + }, + "G4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 20.24, + "z": 25.49 + }, + "G5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 20.24, + "z": 25.49 + }, + "G6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 20.24, + "z": 25.49 + }, + "G7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 20.24, + "z": 25.49 + }, + "G8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 20.24, + "z": 25.49 + }, + "G9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 20.24, + "z": 25.49 + }, + "H1": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 14.38, + "y": 11.24, + "z": 25.49 + }, + "H10": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 95.38, + "y": 11.24, + "z": 25.49 + }, + "H11": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 104.38, + "y": 11.24, + "z": 25.49 + }, + "H12": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 113.38, + "y": 11.24, + "z": 25.49 + }, + "H2": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 23.38, + "y": 11.24, + "z": 25.49 + }, + "H3": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 32.38, + "y": 11.24, + "z": 25.49 + }, + "H4": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 41.38, + "y": 11.24, + "z": 25.49 + }, + "H5": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 50.38, + "y": 11.24, + "z": 25.49 + }, + "H6": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 59.38, + "y": 11.24, + "z": 25.49 + }, + "H7": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 68.38, + "y": 11.24, + "z": 25.49 + }, + "H8": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 77.38, + "y": 11.24, + "z": 25.49 + }, + "H9": { + "depth": 39.2, + "diameter": 3.27, + "shape": "circular", + "totalLiquidVolume": 20, + "x": 86.38, + "y": 11.24, + "z": 25.49 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50de88d471ad3910c29207fb6df4502e", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-300ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.49 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 300 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_300ul", + "tipLength": 59.3, + "tipOverlap": 7.47 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 74.24, + "z": 5.39 + }, + "A10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 74.24, + "z": 5.39 + }, + "A11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 74.24, + "z": 5.39 + }, + "A12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 74.24, + "z": 5.39 + }, + "A2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 74.24, + "z": 5.39 + }, + "A3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 74.24, + "z": 5.39 + }, + "A4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 74.24, + "z": 5.39 + }, + "A5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 74.24, + "z": 5.39 + }, + "A6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 74.24, + "z": 5.39 + }, + "A7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 74.24, + "z": 5.39 + }, + "A8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 74.24, + "z": 5.39 + }, + "A9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 74.24, + "z": 5.39 + }, + "B1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 65.24, + "z": 5.39 + }, + "B10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 65.24, + "z": 5.39 + }, + "B11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 65.24, + "z": 5.39 + }, + "B12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 65.24, + "z": 5.39 + }, + "B2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 65.24, + "z": 5.39 + }, + "B3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 65.24, + "z": 5.39 + }, + "B4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 65.24, + "z": 5.39 + }, + "B5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 65.24, + "z": 5.39 + }, + "B6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 65.24, + "z": 5.39 + }, + "B7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 65.24, + "z": 5.39 + }, + "B8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 65.24, + "z": 5.39 + }, + "B9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 65.24, + "z": 5.39 + }, + "C1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 56.24, + "z": 5.39 + }, + "C10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 56.24, + "z": 5.39 + }, + "C11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 56.24, + "z": 5.39 + }, + "C12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 56.24, + "z": 5.39 + }, + "C2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 56.24, + "z": 5.39 + }, + "C3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 56.24, + "z": 5.39 + }, + "C4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 56.24, + "z": 5.39 + }, + "C5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 56.24, + "z": 5.39 + }, + "C6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 56.24, + "z": 5.39 + }, + "C7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 56.24, + "z": 5.39 + }, + "C8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 56.24, + "z": 5.39 + }, + "C9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 56.24, + "z": 5.39 + }, + "D1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 47.24, + "z": 5.39 + }, + "D10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 47.24, + "z": 5.39 + }, + "D11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 47.24, + "z": 5.39 + }, + "D12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 47.24, + "z": 5.39 + }, + "D2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 47.24, + "z": 5.39 + }, + "D3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 47.24, + "z": 5.39 + }, + "D4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 47.24, + "z": 5.39 + }, + "D5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 47.24, + "z": 5.39 + }, + "D6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 47.24, + "z": 5.39 + }, + "D7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 47.24, + "z": 5.39 + }, + "D8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 47.24, + "z": 5.39 + }, + "D9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 47.24, + "z": 5.39 + }, + "E1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 38.24, + "z": 5.39 + }, + "E10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 38.24, + "z": 5.39 + }, + "E11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 38.24, + "z": 5.39 + }, + "E12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 38.24, + "z": 5.39 + }, + "E2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 38.24, + "z": 5.39 + }, + "E3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 38.24, + "z": 5.39 + }, + "E4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 38.24, + "z": 5.39 + }, + "E5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 38.24, + "z": 5.39 + }, + "E6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 38.24, + "z": 5.39 + }, + "E7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 38.24, + "z": 5.39 + }, + "E8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 38.24, + "z": 5.39 + }, + "E9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 38.24, + "z": 5.39 + }, + "F1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 29.24, + "z": 5.39 + }, + "F10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 29.24, + "z": 5.39 + }, + "F11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 29.24, + "z": 5.39 + }, + "F12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 29.24, + "z": 5.39 + }, + "F2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 29.24, + "z": 5.39 + }, + "F3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 29.24, + "z": 5.39 + }, + "F4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 29.24, + "z": 5.39 + }, + "F5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 29.24, + "z": 5.39 + }, + "F6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 29.24, + "z": 5.39 + }, + "F7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 29.24, + "z": 5.39 + }, + "F8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 29.24, + "z": 5.39 + }, + "F9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 29.24, + "z": 5.39 + }, + "G1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 20.24, + "z": 5.39 + }, + "G10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 20.24, + "z": 5.39 + }, + "G11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 20.24, + "z": 5.39 + }, + "G12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 20.24, + "z": 5.39 + }, + "G2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 20.24, + "z": 5.39 + }, + "G3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 20.24, + "z": 5.39 + }, + "G4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 20.24, + "z": 5.39 + }, + "G5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 20.24, + "z": 5.39 + }, + "G6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 20.24, + "z": 5.39 + }, + "G7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 20.24, + "z": 5.39 + }, + "G8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 20.24, + "z": 5.39 + }, + "G9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 20.24, + "z": 5.39 + }, + "H1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 11.24, + "z": 5.39 + }, + "H10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 11.24, + "z": 5.39 + }, + "H11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 11.24, + "z": 5.39 + }, + "H12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 11.24, + "z": 5.39 + }, + "H2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 11.24, + "z": 5.39 + }, + "H3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 11.24, + "z": 5.39 + }, + "H4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 11.24, + "z": 5.39 + }, + "H5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 11.24, + "z": 5.39 + }, + "H6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 11.24, + "z": 5.39 + }, + "H7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 11.24, + "z": 5.39 + }, + "H8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 11.24, + "z": 5.39 + }, + "H9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 11.24, + "z": 5.39 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "54fddab1eb921f6e1d3aa7e96d2ac307", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p300_multi_gen2", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4b1d27a6f17f312dd76668f0c48ed406", + "notes": [], + "params": { + "configurationParams": { + "backLeftNozzle": "G1", + "frontRightNozzle": "H1", + "primaryNozzle": "H1", + "style": "QUADRANT" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8f1105742fe19ad1e0b7c436509543f3", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "4" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "51917f4fe2c45a5895dd30e853f4b689", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "5" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bb4e42ce3a347009d85098cb49c54446", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 146.88, + "y": 65.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3e2d6ccf28ac1864869f3fba1e6c918c", + "notes": [], + "params": { + "message": "Tip rack in 2, well A1 has tip: False" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "adb87a628b0270e6b7ea03956c308caf", + "notes": [], + "params": { + "message": "Tip rack in 2, well B1 has tip: False" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d52723fce284549fc5363df14c8af77b", + "notes": [], + "params": { + "message": "Tip rack in 2, well C1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1eb8c634a559f734e5bf0152943ccfc5", + "notes": [], + "params": { + "message": "Tip rack in 2, well D1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3e38d3b5e3cfe9ddb63397a97e876ed4", + "notes": [], + "params": { + "message": "Tip rack in 2, well E1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8c0f1de98d6cbf48fd9e76be1389b807", + "notes": [], + "params": { + "message": "Tip rack in 2, well F1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "560a90fcbfc35c9f895b03c1d49abd29", + "notes": [], + "params": { + "message": "Tip rack in 2, well G1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "eb9fed787fef1871ea9fbc3f506af0e6", + "notes": [], + "params": { + "message": "Tip rack in 2, well H1 has tip: True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "UnexpectedProtocolError [line 150]: Error 4000 GENERAL_ERROR (UnexpectedProtocolError): Cannot return tip to a tiprack while the pipette is configured for partial tip.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Cannot return tip to a tiprack while the pipette is configured for partial tip.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "UnexpectedProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "OT2_X_v2_20_8_Overrides_InvalidConfigs_Override_drop_tip_with_location.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_20ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_20ul", + "location": { + "slotName": "1" + } + }, + { + "definitionUri": "opentrons/opentrons_96_tiprack_300ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "4" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "5" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Invalid tip configs that should error" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p300_multi_gen2" + } + ], + "result": "not-ok", + "robotType": "OT-2 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b806f07be9][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_value].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b806f07be9][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_value].json index 6b2391f6118..cf8ec946db5 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b806f07be9][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_value].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b806f07be9][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_choice_value].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b91d31eaa2][pl_MagMax_RNA_Cells_Flex_96_Channel].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b91d31eaa2][pl_MagMax_RNA_Cells_Flex_96_Channel].json index c314c607ebd..db24530e196 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b91d31eaa2][pl_MagMax_RNA_Cells_Flex_96_Channel].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[b91d31eaa2][pl_MagMax_RNA_Cells_Flex_96_Channel].json @@ -29839,7 +29839,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29872,7 +29873,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29904,7 +29906,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29936,7 +29939,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29969,7 +29973,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30002,7 +30007,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30035,7 +30041,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30068,7 +30075,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30101,7 +30109,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30134,7 +30143,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30167,7 +30177,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30200,7 +30211,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30233,7 +30245,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30266,7 +30279,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30299,7 +30313,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30332,7 +30347,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30365,7 +30381,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30398,7 +30415,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30431,7 +30449,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30464,7 +30483,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30497,7 +30517,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30530,7 +30551,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30563,7 +30585,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30596,7 +30619,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30629,7 +30653,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30662,7 +30687,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30695,7 +30721,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30728,7 +30755,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30761,7 +30789,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30794,7 +30823,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30827,7 +30857,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30860,7 +30891,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30893,7 +30925,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30926,7 +30959,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30959,7 +30993,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30992,7 +31027,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31025,7 +31061,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31058,7 +31095,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31091,7 +31129,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31124,7 +31163,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31157,7 +31197,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31190,7 +31231,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31223,7 +31265,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31256,7 +31299,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31289,7 +31333,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31322,7 +31367,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31355,7 +31401,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31388,7 +31435,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31421,7 +31469,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31454,7 +31503,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31487,7 +31537,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31520,7 +31571,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31553,7 +31605,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31600,7 +31653,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31632,7 +31686,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31664,7 +31719,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31697,7 +31753,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31730,7 +31787,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31763,7 +31821,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31796,7 +31855,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31829,7 +31889,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31862,7 +31923,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31895,7 +31957,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31928,7 +31991,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31961,7 +32025,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31994,7 +32059,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32027,7 +32093,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32060,7 +32127,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32093,7 +32161,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32126,7 +32195,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32159,7 +32229,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32192,7 +32263,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32225,7 +32297,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32258,7 +32331,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32291,7 +32365,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32324,7 +32399,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32357,7 +32433,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32390,7 +32467,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32423,7 +32501,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32456,7 +32535,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32489,7 +32569,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32522,7 +32603,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32555,7 +32637,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32588,7 +32671,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32621,7 +32705,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32654,7 +32739,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32687,7 +32773,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32720,7 +32807,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32753,7 +32841,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32786,7 +32875,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32819,7 +32909,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32852,7 +32943,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32885,7 +32977,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32918,7 +33011,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32951,7 +33045,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32984,7 +33079,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33017,7 +33113,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33050,7 +33147,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33083,7 +33181,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33115,7 +33214,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33147,7 +33247,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33476,7 +33577,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33509,7 +33611,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33668,7 +33771,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33701,7 +33805,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33747,7 +33852,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33997,7 +34103,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34030,7 +34137,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34189,7 +34297,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34222,7 +34331,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34268,7 +34378,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34518,7 +34629,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34551,7 +34663,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34710,7 +34823,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34743,7 +34857,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34776,7 +34891,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34809,7 +34925,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34842,7 +34959,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34875,7 +34993,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34908,7 +35027,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34941,7 +35061,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34974,7 +35095,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35007,7 +35129,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35040,7 +35163,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35073,7 +35197,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35106,7 +35231,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35139,7 +35265,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35172,7 +35299,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35205,7 +35333,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35238,7 +35367,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35271,7 +35401,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35304,7 +35435,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35337,7 +35469,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35370,7 +35503,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35403,7 +35537,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35436,7 +35571,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35469,7 +35605,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35502,7 +35639,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35535,7 +35673,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35568,7 +35707,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35601,7 +35741,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35634,7 +35775,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35667,7 +35809,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35700,7 +35843,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35733,7 +35877,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35766,7 +35911,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35799,7 +35945,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35832,7 +35979,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35865,7 +36013,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35898,7 +36047,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35931,7 +36081,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35964,7 +36115,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35997,7 +36149,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36030,7 +36183,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36063,7 +36217,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36096,7 +36251,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36129,7 +36285,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36162,7 +36319,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36195,7 +36353,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36228,7 +36387,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36261,7 +36421,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36294,7 +36455,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36327,7 +36489,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36360,7 +36523,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36393,7 +36557,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36426,7 +36591,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36459,7 +36625,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36492,7 +36659,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36525,7 +36693,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36558,7 +36727,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36591,7 +36761,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36624,7 +36795,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36657,7 +36829,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36690,7 +36863,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36723,7 +36897,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36756,7 +36931,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36789,7 +36965,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36822,7 +36999,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36855,7 +37033,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36888,7 +37067,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36921,7 +37101,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37078,7 +37259,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37111,7 +37293,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37143,7 +37326,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37175,7 +37359,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37208,7 +37393,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37241,7 +37427,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37274,7 +37461,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37307,7 +37495,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37340,7 +37529,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37373,7 +37563,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37406,7 +37597,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37439,7 +37631,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37472,7 +37665,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37504,7 +37698,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37535,7 +37730,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37567,7 +37763,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37878,7 +38075,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37911,7 +38109,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38070,7 +38269,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38103,7 +38303,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38149,7 +38350,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38399,7 +38601,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38432,7 +38635,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38591,7 +38795,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38624,7 +38829,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38670,7 +38876,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38920,7 +39127,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38953,7 +39161,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39112,7 +39321,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39145,7 +39355,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39191,7 +39402,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39441,7 +39653,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39474,7 +39687,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39693,7 +39907,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39726,7 +39941,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39759,7 +39975,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39792,7 +40009,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39825,7 +40043,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39858,7 +40077,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39891,7 +40111,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39924,7 +40145,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39957,7 +40179,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39990,7 +40213,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40023,7 +40247,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40056,7 +40281,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40089,7 +40315,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40122,7 +40349,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40155,7 +40383,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40188,7 +40417,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40221,7 +40451,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40254,7 +40485,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40287,7 +40519,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40320,7 +40553,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40353,7 +40587,8 @@ "y": 0.0, "z": -36.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40386,7 +40621,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40419,7 +40655,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40452,7 +40689,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40763,7 +41001,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40796,7 +41035,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40989,6 +41229,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Magnetic Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[baf79d9b4a][Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[baf79d9b4a][Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight].json index 4741bdbc7fc..851fd7e1fbb 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[baf79d9b4a][Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[baf79d9b4a][Flex_S_v2_15_P1000S_None_SimpleNormalizeLongRight].json @@ -10868,7 +10868,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10901,7 +10902,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -10934,7 +10936,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10967,7 +10970,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -11000,7 +11004,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11033,7 +11038,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -11066,7 +11072,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11099,7 +11106,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -11132,7 +11140,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11165,7 +11174,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -11198,7 +11208,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11231,7 +11242,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -11264,7 +11276,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11297,7 +11310,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -11330,7 +11344,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11363,7 +11378,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11396,7 +11412,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11429,7 +11446,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -11462,7 +11480,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11495,7 +11514,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -11528,7 +11548,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11561,7 +11582,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -11594,7 +11616,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11627,7 +11650,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -11660,7 +11684,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11693,7 +11718,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -11726,7 +11752,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11759,7 +11786,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -11792,7 +11820,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11825,7 +11854,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -11858,7 +11888,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11891,7 +11922,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11924,7 +11956,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11957,7 +11990,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -11990,7 +12024,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12023,7 +12058,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -12056,7 +12092,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12089,7 +12126,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -12122,7 +12160,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12155,7 +12194,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -12188,7 +12228,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12221,7 +12262,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -12254,7 +12296,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12287,7 +12330,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -12320,7 +12364,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12353,7 +12398,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -12386,7 +12432,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12419,7 +12466,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -12452,7 +12500,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12485,7 +12534,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -12518,7 +12568,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12551,7 +12602,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -12584,7 +12636,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12617,7 +12670,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -12650,7 +12704,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12683,7 +12738,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -12716,7 +12772,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12749,7 +12806,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -12782,7 +12840,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12815,7 +12874,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -12848,7 +12908,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12881,7 +12942,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -12914,7 +12976,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12947,7 +13010,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -12980,7 +13044,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13013,7 +13078,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -13046,7 +13112,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13079,7 +13146,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -13112,7 +13180,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13145,7 +13214,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -13178,7 +13248,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13211,7 +13282,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -13244,7 +13316,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13277,7 +13350,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -13310,7 +13384,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13343,7 +13418,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -13376,7 +13452,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13409,7 +13486,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -13442,7 +13520,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13475,7 +13554,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -13508,7 +13588,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13541,7 +13622,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -13574,7 +13656,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13607,7 +13690,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -13640,7 +13724,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13673,7 +13758,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -13706,7 +13792,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13739,7 +13826,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -13772,7 +13860,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13805,7 +13894,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -13838,7 +13928,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13871,7 +13962,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -13904,7 +13996,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13937,7 +14030,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -13970,7 +14064,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14003,7 +14098,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -14036,7 +14132,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14069,7 +14166,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -14102,7 +14200,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14135,7 +14234,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -14168,7 +14268,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14201,7 +14302,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -14234,7 +14336,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14267,7 +14370,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -14300,7 +14404,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14333,7 +14438,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -14366,7 +14472,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14399,7 +14506,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -14432,7 +14540,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14465,7 +14574,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -14498,7 +14608,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14531,7 +14642,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -14564,7 +14676,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14597,7 +14710,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -14630,7 +14744,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14663,7 +14778,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -14696,7 +14812,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14729,7 +14846,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -14762,7 +14880,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14795,7 +14914,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -14828,7 +14948,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14861,7 +14982,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -14894,7 +15016,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14927,7 +15050,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -14960,7 +15084,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14993,7 +15118,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -15026,7 +15152,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15059,7 +15186,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15092,7 +15220,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15125,7 +15254,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -15158,7 +15288,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15191,7 +15322,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -15224,7 +15356,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15257,7 +15390,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -15290,7 +15424,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15323,7 +15458,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -15356,7 +15492,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15389,7 +15526,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -15422,7 +15560,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15455,7 +15594,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -15488,7 +15628,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15521,7 +15662,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -15554,7 +15696,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15587,7 +15730,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -15620,7 +15764,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15653,7 +15798,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -15686,7 +15832,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15719,7 +15866,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -15752,7 +15900,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15785,7 +15934,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -15818,7 +15968,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15851,7 +16002,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -15884,7 +16036,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15917,7 +16070,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -15950,7 +16104,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15983,7 +16138,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -16016,7 +16172,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16049,7 +16206,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -16082,7 +16240,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16115,7 +16274,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16148,7 +16308,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16181,7 +16342,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -16214,7 +16376,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16247,7 +16410,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -16280,7 +16444,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16313,7 +16478,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -16346,7 +16512,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16379,7 +16546,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -16412,7 +16580,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16445,7 +16614,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -16478,7 +16648,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16511,7 +16682,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -16544,7 +16716,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16577,7 +16750,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -16610,7 +16784,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16643,7 +16818,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -16676,7 +16852,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16709,7 +16886,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -16742,7 +16920,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16775,7 +16954,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -16808,7 +16988,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16841,7 +17022,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -16874,7 +17056,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16907,7 +17090,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -16940,7 +17124,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16973,7 +17158,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -17006,7 +17192,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17039,7 +17226,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -17072,7 +17260,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17105,7 +17294,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -17244,7 +17434,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17277,7 +17468,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -17374,7 +17566,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17407,7 +17600,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -17504,7 +17698,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17537,7 +17732,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -17634,7 +17830,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17667,7 +17864,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -17764,7 +17962,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17797,7 +17996,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -17894,7 +18094,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17927,7 +18128,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -18024,7 +18226,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18057,7 +18260,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -18154,7 +18358,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18187,7 +18392,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18284,7 +18490,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18317,7 +18524,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -18414,7 +18622,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18447,7 +18656,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -18544,7 +18754,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18577,7 +18788,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -18674,7 +18886,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18707,7 +18920,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -18804,7 +19018,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18837,7 +19052,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -18934,7 +19150,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18967,7 +19184,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -19064,7 +19282,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19097,7 +19316,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -19194,7 +19414,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19227,7 +19448,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19324,7 +19546,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19357,7 +19580,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -19454,7 +19678,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19487,7 +19712,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -19584,7 +19810,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19617,7 +19844,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -19714,7 +19942,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19747,7 +19976,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -19844,7 +20074,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19877,7 +20108,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -19974,7 +20206,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20007,7 +20240,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -20104,7 +20338,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20137,7 +20372,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -20234,7 +20470,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20267,7 +20504,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20364,7 +20602,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20397,7 +20636,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -20494,7 +20734,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20527,7 +20768,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -20624,7 +20866,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20657,7 +20900,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -20754,7 +20998,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20787,7 +21032,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -20884,7 +21130,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20917,7 +21164,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -21014,7 +21262,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21047,7 +21296,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -21144,7 +21394,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21177,7 +21428,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -21274,7 +21526,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21307,7 +21560,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21404,7 +21658,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21437,7 +21692,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -21534,7 +21790,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21567,7 +21824,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -21664,7 +21922,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21697,7 +21956,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -21794,7 +22054,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21827,7 +22088,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -21924,7 +22186,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21957,7 +22220,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -22054,7 +22318,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22087,7 +22352,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -22184,7 +22450,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22217,7 +22484,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -22314,7 +22582,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22347,7 +22616,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -22444,7 +22714,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22477,7 +22748,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -22574,7 +22846,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22607,7 +22880,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -22704,7 +22978,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22737,7 +23012,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -22834,7 +23110,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22867,7 +23144,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -22964,7 +23242,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22997,7 +23276,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -23094,7 +23374,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23127,7 +23408,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -23224,7 +23506,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23257,7 +23540,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -23354,7 +23638,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23387,7 +23672,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23484,7 +23770,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23517,7 +23804,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -23614,7 +23902,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23647,7 +23936,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -23744,7 +24034,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23777,7 +24068,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -23874,7 +24166,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23907,7 +24200,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -24004,7 +24298,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24037,7 +24332,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -24134,7 +24430,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24167,7 +24464,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -24264,7 +24562,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24297,7 +24596,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -24394,7 +24694,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24427,7 +24728,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -24524,7 +24826,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24557,7 +24860,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -24654,7 +24958,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24687,7 +24992,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -24784,7 +25090,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24817,7 +25124,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -24914,7 +25222,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24947,7 +25256,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -25044,7 +25354,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25077,7 +25388,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -25174,7 +25486,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25207,7 +25520,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -25304,7 +25618,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25337,7 +25652,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -25434,7 +25750,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25467,7 +25784,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -25564,7 +25882,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25597,7 +25916,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -25694,7 +26014,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25727,7 +26048,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -25824,7 +26146,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25857,7 +26180,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -25954,7 +26278,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25987,7 +26312,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -26084,7 +26410,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26117,7 +26444,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -26214,7 +26542,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26247,7 +26576,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -26344,7 +26674,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26377,7 +26708,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -26474,7 +26806,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26507,7 +26840,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -26604,7 +26938,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26637,7 +26972,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -26734,7 +27070,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26767,7 +27104,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -26864,7 +27202,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26897,7 +27236,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -26994,7 +27334,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27027,7 +27368,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -27124,7 +27466,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27157,7 +27500,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -27254,7 +27598,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27287,7 +27632,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -27384,7 +27730,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27417,7 +27764,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -27514,7 +27862,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27547,7 +27896,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -27644,7 +27994,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27677,7 +28028,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -27774,7 +28126,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27807,7 +28160,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -27904,7 +28258,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27937,7 +28292,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -28034,7 +28390,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28067,7 +28424,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -28164,7 +28522,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28197,7 +28556,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -28294,7 +28654,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28327,7 +28688,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -28424,7 +28786,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28457,7 +28820,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -28554,7 +28918,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28587,7 +28952,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -28684,7 +29050,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28717,7 +29084,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -28814,7 +29182,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28847,7 +29216,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -28944,7 +29314,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28977,7 +29348,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -29074,7 +29446,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29107,7 +29480,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -29204,7 +29578,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29237,7 +29612,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -29334,7 +29710,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29367,7 +29744,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -29464,7 +29842,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29497,7 +29876,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -29636,7 +30016,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29669,7 +30050,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -29702,7 +30084,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29735,7 +30118,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -29768,7 +30152,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29801,7 +30186,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -29834,7 +30220,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29867,7 +30254,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -29900,7 +30288,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29933,7 +30322,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -29966,7 +30356,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29999,7 +30390,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -30032,7 +30424,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30065,7 +30458,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -30098,7 +30492,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30131,7 +30526,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30164,7 +30560,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30197,7 +30594,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -30230,7 +30628,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30263,7 +30662,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -30296,7 +30696,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30329,7 +30730,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -30362,7 +30764,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30395,7 +30798,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -30428,7 +30832,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30461,7 +30866,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -30494,7 +30900,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30527,7 +30934,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -30560,7 +30968,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30593,7 +31002,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -30626,7 +31036,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30659,7 +31070,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30692,7 +31104,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30725,7 +31138,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -30758,7 +31172,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30791,7 +31206,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -30824,7 +31240,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30857,7 +31274,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -30890,7 +31308,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30923,7 +31342,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -30956,7 +31376,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30989,7 +31410,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -31022,7 +31444,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31055,7 +31478,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -31088,7 +31512,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31121,7 +31546,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -31154,7 +31580,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31187,7 +31614,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31220,7 +31648,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31253,7 +31682,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -31286,7 +31716,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31319,7 +31750,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -31352,7 +31784,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31385,7 +31818,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -31418,7 +31852,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31451,7 +31886,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -31484,7 +31920,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31517,7 +31954,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -31550,7 +31988,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31583,7 +32022,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -31616,7 +32056,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31649,7 +32090,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -31682,7 +32124,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31715,7 +32158,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31748,7 +32192,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31781,7 +32226,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -31814,7 +32260,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31847,7 +32294,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -31880,7 +32328,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31913,7 +32362,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -31946,7 +32396,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31979,7 +32430,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -32012,7 +32464,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32045,7 +32498,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -32078,7 +32532,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32111,7 +32566,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -32144,7 +32600,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32177,7 +32634,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -32210,7 +32668,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32243,7 +32702,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32276,7 +32736,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32309,7 +32770,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -32342,7 +32804,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32375,7 +32838,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -32408,7 +32872,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32441,7 +32906,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -32474,7 +32940,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32507,7 +32974,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -32540,7 +33008,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32573,7 +33042,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -32606,7 +33076,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32639,7 +33110,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -32672,7 +33144,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32705,7 +33178,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -32738,7 +33212,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32771,7 +33246,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32804,7 +33280,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32837,7 +33314,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -32870,7 +33348,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32903,7 +33382,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -32936,7 +33416,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32969,7 +33450,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -33002,7 +33484,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33035,7 +33518,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -33068,7 +33552,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33101,7 +33586,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -33134,7 +33620,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33167,7 +33654,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -33200,7 +33688,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33233,7 +33722,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -33266,7 +33756,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33299,7 +33790,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -33332,7 +33824,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33365,7 +33858,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -33398,7 +33892,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33431,7 +33926,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -33464,7 +33960,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33497,7 +33994,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -33530,7 +34028,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33563,7 +34062,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -33596,7 +34096,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33629,7 +34130,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -33662,7 +34164,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33695,7 +34198,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -33728,7 +34232,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33761,7 +34266,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -33794,7 +34300,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33827,7 +34334,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -33860,7 +34368,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33893,7 +34402,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -33926,7 +34436,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33959,7 +34470,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -33992,7 +34504,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34025,7 +34538,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -34058,7 +34572,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34091,7 +34606,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -34124,7 +34640,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34157,7 +34674,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -34190,7 +34708,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34223,7 +34742,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -34256,7 +34776,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34289,7 +34810,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -34322,7 +34844,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34355,7 +34878,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -34388,7 +34912,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34421,7 +34946,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -34454,7 +34980,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34487,7 +35014,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -34520,7 +35048,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34553,7 +35082,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -34586,7 +35116,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34619,7 +35150,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -34652,7 +35184,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34685,7 +35218,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -34718,7 +35252,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34751,7 +35286,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -34784,7 +35320,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34817,7 +35354,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -34850,7 +35388,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34883,7 +35422,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34916,7 +35456,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34949,7 +35490,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -34982,7 +35524,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35015,7 +35558,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -35048,7 +35592,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35081,7 +35626,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -35114,7 +35660,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35147,7 +35694,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -35180,7 +35728,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35213,7 +35762,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -35246,7 +35796,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35279,7 +35830,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -35312,7 +35864,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35345,7 +35898,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -35378,7 +35932,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35411,7 +35966,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35444,7 +36000,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35477,7 +36034,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -35510,7 +36068,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35543,7 +36102,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -35576,7 +36136,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35609,7 +36170,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -35642,7 +36204,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35675,7 +36238,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -35708,7 +36272,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35741,7 +36306,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -35774,7 +36340,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35807,7 +36374,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -35840,7 +36408,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35873,7 +36442,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -36012,7 +36582,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36045,7 +36616,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -36142,7 +36714,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36175,7 +36748,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -36272,7 +36846,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36305,7 +36880,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -36402,7 +36978,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36435,7 +37012,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -36532,7 +37110,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36565,7 +37144,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -36662,7 +37242,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36695,7 +37276,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -36792,7 +37374,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36825,7 +37408,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -36922,7 +37506,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36955,7 +37540,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -37052,7 +37638,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37085,7 +37672,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -37182,7 +37770,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37215,7 +37804,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -37312,7 +37902,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37345,7 +37936,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -37442,7 +38034,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37475,7 +38068,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -37572,7 +38166,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37605,7 +38200,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -37702,7 +38298,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37735,7 +38332,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -37832,7 +38430,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37865,7 +38464,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -37962,7 +38562,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37995,7 +38596,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -38092,7 +38694,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38125,7 +38728,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -38222,7 +38826,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38255,7 +38860,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -38352,7 +38958,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38385,7 +38992,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -38482,7 +39090,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38515,7 +39124,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -38612,7 +39222,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38645,7 +39256,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -38742,7 +39354,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38775,7 +39388,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -38872,7 +39486,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38905,7 +39520,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -39002,7 +39618,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39035,7 +39652,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39132,7 +39750,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39165,7 +39784,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -39262,7 +39882,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39295,7 +39916,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -39392,7 +40014,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39425,7 +40048,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -39522,7 +40146,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39555,7 +40180,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -39652,7 +40278,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39685,7 +40312,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -39782,7 +40410,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39815,7 +40444,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -39912,7 +40542,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39945,7 +40576,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -40042,7 +40674,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40075,7 +40708,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40172,7 +40806,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40205,7 +40840,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -40302,7 +40938,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40335,7 +40972,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -40432,7 +41070,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40465,7 +41104,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -40562,7 +41202,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40595,7 +41236,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -40692,7 +41334,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40725,7 +41368,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -40822,7 +41466,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40855,7 +41500,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -40952,7 +41598,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40985,7 +41632,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -41082,7 +41730,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41115,7 +41764,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41212,7 +41862,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41245,7 +41896,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -41342,7 +41994,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41375,7 +42028,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -41472,7 +42126,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41505,7 +42160,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -41602,7 +42258,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41635,7 +42292,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -41732,7 +42390,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41765,7 +42424,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -41862,7 +42522,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41895,7 +42556,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -41992,7 +42654,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42025,7 +42688,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -42122,7 +42786,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42155,7 +42820,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42252,7 +42918,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42285,7 +42952,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -42382,7 +43050,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42415,7 +43084,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -42512,7 +43182,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42545,7 +43216,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -42642,7 +43314,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42675,7 +43348,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -42772,7 +43446,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42805,7 +43480,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -42902,7 +43578,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42935,7 +43612,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -43032,7 +43710,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43065,7 +43744,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -43162,7 +43842,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43195,7 +43876,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -43292,7 +43974,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43325,7 +44008,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -43422,7 +44106,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43455,7 +44140,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -43552,7 +44238,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43585,7 +44272,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -43682,7 +44370,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43715,7 +44404,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -43812,7 +44502,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43845,7 +44536,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -43942,7 +44634,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43975,7 +44668,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -44072,7 +44766,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44105,7 +44800,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -44202,7 +44898,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44235,7 +44932,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -44332,7 +45030,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44365,7 +45064,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -44462,7 +45162,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44495,7 +45196,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -44592,7 +45294,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44625,7 +45328,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -44722,7 +45426,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44755,7 +45460,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -44852,7 +45558,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44885,7 +45592,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -44982,7 +45690,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45015,7 +45724,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -45112,7 +45822,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45145,7 +45856,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -45242,7 +45954,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45275,7 +45988,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -45372,7 +46086,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45405,7 +46120,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -45502,7 +46218,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45535,7 +46252,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -45632,7 +46350,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45665,7 +46384,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -45762,7 +46482,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45795,7 +46516,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -45892,7 +46614,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45925,7 +46648,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -46022,7 +46746,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46055,7 +46780,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -46152,7 +46878,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46185,7 +46912,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -46282,7 +47010,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46315,7 +47044,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -46412,7 +47142,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46445,7 +47176,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -46542,7 +47274,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46575,7 +47308,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -46672,7 +47406,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46705,7 +47440,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -46802,7 +47538,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46835,7 +47572,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -46932,7 +47670,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46965,7 +47704,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -47062,7 +47802,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47095,7 +47836,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -47192,7 +47934,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47225,7 +47968,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -47322,7 +48066,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47355,7 +48100,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47452,7 +48198,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47485,7 +48232,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -47582,7 +48330,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47615,7 +48364,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -47712,7 +48462,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47745,7 +48496,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -47842,7 +48594,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47875,7 +48628,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -47972,7 +48726,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48005,7 +48760,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -48102,7 +48858,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48135,7 +48892,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -48232,7 +48990,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48265,7 +49024,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -48404,7 +49164,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48437,7 +49198,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -48470,7 +49232,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48503,7 +49266,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -48536,7 +49300,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48569,7 +49334,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -48602,7 +49368,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48635,7 +49402,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -48668,7 +49436,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48701,7 +49470,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -48734,7 +49504,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48767,7 +49538,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -48800,7 +49572,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48833,7 +49606,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -48866,7 +49640,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48899,7 +49674,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -48932,7 +49708,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -48965,7 +49742,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -48998,7 +49776,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49031,7 +49810,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -49064,7 +49844,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49097,7 +49878,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -49130,7 +49912,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49163,7 +49946,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -49196,7 +49980,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49229,7 +50014,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -49262,7 +50048,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49295,7 +50082,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -49328,7 +50116,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49361,7 +50150,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -49394,7 +50184,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49427,7 +50218,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49460,7 +50252,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49493,7 +50286,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -49526,7 +50320,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49559,7 +50354,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -49592,7 +50388,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49625,7 +50422,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -49658,7 +50456,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49691,7 +50490,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -49724,7 +50524,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49757,7 +50558,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -49790,7 +50592,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49823,7 +50626,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -49856,7 +50660,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49889,7 +50694,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -49922,7 +50728,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -49955,7 +50762,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -49988,7 +50796,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50021,7 +50830,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -50054,7 +50864,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50087,7 +50898,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -50120,7 +50932,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50153,7 +50966,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -50186,7 +51000,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50219,7 +51034,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -50252,7 +51068,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50285,7 +51102,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -50318,7 +51136,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50351,7 +51170,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -50384,7 +51204,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50417,7 +51238,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -50450,7 +51272,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50483,7 +51306,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50516,7 +51340,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50549,7 +51374,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -50582,7 +51408,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50615,7 +51442,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -50648,7 +51476,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50681,7 +51510,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -50714,7 +51544,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50747,7 +51578,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -50780,7 +51612,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50813,7 +51646,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -50846,7 +51680,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50879,7 +51714,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -50912,7 +51748,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -50945,7 +51782,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -50978,7 +51816,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -51011,7 +51850,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51044,7 +51884,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -51077,7 +51918,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -51110,7 +51952,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -51143,7 +51986,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -51176,7 +52020,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -51209,7 +52054,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -51242,7 +52088,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -51275,7 +52122,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -51308,7 +52156,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -51341,7 +52190,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -51374,7 +52224,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -51407,7 +52258,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -51440,7 +52292,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -51473,7 +52326,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -51506,7 +52360,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -51539,7 +52394,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -51572,7 +52428,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -51605,7 +52462,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -51638,7 +52496,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -51671,7 +52530,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -51704,7 +52564,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -51737,7 +52598,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -51770,7 +52632,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -51803,7 +52666,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -51836,7 +52700,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -51869,7 +52734,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -51902,7 +52768,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -51935,7 +52802,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -51968,7 +52836,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52001,7 +52870,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -52034,7 +52904,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52067,7 +52938,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -52100,7 +52972,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52133,7 +53006,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -52166,7 +53040,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52199,7 +53074,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -52232,7 +53108,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52265,7 +53142,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -52298,7 +53176,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52331,7 +53210,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -52364,7 +53244,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52397,7 +53278,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -52430,7 +53312,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52463,7 +53346,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -52496,7 +53380,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52529,7 +53414,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -52562,7 +53448,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52595,7 +53482,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -52628,7 +53516,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52661,7 +53550,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -52694,7 +53584,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52727,7 +53618,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -52760,7 +53652,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52793,7 +53686,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -52826,7 +53720,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52859,7 +53754,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -52892,7 +53788,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52925,7 +53822,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -52958,7 +53856,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -52991,7 +53890,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -53024,7 +53924,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -53057,7 +53958,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -53090,7 +53992,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -53123,7 +54026,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53156,7 +54060,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -53189,7 +54094,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -53222,7 +54128,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -53255,7 +54162,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -53288,7 +54196,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -53321,7 +54230,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -53354,7 +54264,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -53387,7 +54298,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -53420,7 +54332,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -53453,7 +54366,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -53486,7 +54400,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -53519,7 +54434,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -53552,7 +54468,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -53585,7 +54502,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -53618,7 +54536,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -53651,7 +54570,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53684,7 +54604,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -53717,7 +54638,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -53750,7 +54672,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -53783,7 +54706,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -53816,7 +54740,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -53849,7 +54774,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -53882,7 +54808,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -53915,7 +54842,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -53948,7 +54876,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -53981,7 +54910,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -54014,7 +54944,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -54047,7 +54978,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -54080,7 +55012,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -54113,7 +55046,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -54146,7 +55080,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -54179,7 +55114,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -54212,7 +55148,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -54245,7 +55182,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -54278,7 +55216,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -54311,7 +55250,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -54344,7 +55284,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -54377,7 +55318,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -54410,7 +55352,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -54443,7 +55386,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -54476,7 +55420,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -54509,7 +55454,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -54542,7 +55488,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -54575,7 +55522,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -54608,7 +55556,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -54641,7 +55590,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -54780,7 +55730,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -54813,7 +55764,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -54910,7 +55862,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -54943,7 +55896,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -55040,7 +55994,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -55073,7 +56028,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -55170,7 +56126,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -55203,7 +56160,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -55300,7 +56258,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -55333,7 +56292,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -55430,7 +56390,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -55463,7 +56424,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -55560,7 +56522,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -55593,7 +56556,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -55690,7 +56654,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -55723,7 +56688,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -55820,7 +56786,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -55853,7 +56820,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -55950,7 +56918,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -55983,7 +56952,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -56080,7 +57050,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -56113,7 +57084,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -56210,7 +57182,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -56243,7 +57216,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -56340,7 +57314,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -56373,7 +57348,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -56470,7 +57446,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -56503,7 +57480,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -56600,7 +57578,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -56633,7 +57612,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -56730,7 +57710,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -56763,7 +57744,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -56860,7 +57842,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -56893,7 +57876,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -56990,7 +57974,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57023,7 +58008,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -57120,7 +58106,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57153,7 +58140,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -57250,7 +58238,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57283,7 +58272,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -57380,7 +58370,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57413,7 +58404,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -57510,7 +58502,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57543,7 +58536,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -57640,7 +58634,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57673,7 +58668,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -57770,7 +58766,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57803,7 +58800,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -57900,7 +58898,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57933,7 +58932,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -58030,7 +59030,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -58063,7 +59064,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -58160,7 +59162,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -58193,7 +59196,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -58290,7 +59294,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -58323,7 +59328,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -58420,7 +59426,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -58453,7 +59460,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -58550,7 +59558,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -58583,7 +59592,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -58680,7 +59690,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -58713,7 +59724,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -58810,7 +59822,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -58843,7 +59856,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -58940,7 +59954,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -58973,7 +59988,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -59070,7 +60086,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -59103,7 +60120,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -59200,7 +60218,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -59233,7 +60252,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -59330,7 +60350,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -59363,7 +60384,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -59460,7 +60482,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -59493,7 +60516,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -59590,7 +60614,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -59623,7 +60648,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -59720,7 +60746,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -59753,7 +60780,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -59850,7 +60878,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -59883,7 +60912,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -59980,7 +61010,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60013,7 +61044,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -60110,7 +61142,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60143,7 +61176,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -60240,7 +61274,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60273,7 +61308,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -60370,7 +61406,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60403,7 +61440,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -60500,7 +61538,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60533,7 +61572,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -60630,7 +61670,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60663,7 +61704,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -60760,7 +61802,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60793,7 +61836,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -60890,7 +61934,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60923,7 +61968,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -61020,7 +62066,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -61053,7 +62100,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -61150,7 +62198,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -61183,7 +62232,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -61280,7 +62330,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -61313,7 +62364,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -61410,7 +62462,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -61443,7 +62496,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -61540,7 +62594,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -61573,7 +62628,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -61670,7 +62726,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -61703,7 +62760,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -61800,7 +62858,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -61833,7 +62892,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -61930,7 +62990,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -61963,7 +63024,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -62060,7 +63122,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -62093,7 +63156,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -62190,7 +63254,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -62223,7 +63288,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -62320,7 +63386,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -62353,7 +63420,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -62450,7 +63518,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -62483,7 +63552,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -62580,7 +63650,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -62613,7 +63684,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -62710,7 +63782,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -62743,7 +63816,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -62840,7 +63914,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -62873,7 +63948,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -62970,7 +64046,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -63003,7 +64080,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -63100,7 +64178,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -63133,7 +64212,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -63230,7 +64310,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -63263,7 +64344,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -63360,7 +64442,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -63393,7 +64476,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -63490,7 +64574,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -63523,7 +64608,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -63620,7 +64706,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -63653,7 +64740,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -63750,7 +64838,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -63783,7 +64872,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -63880,7 +64970,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -63913,7 +65004,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -64010,7 +65102,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -64043,7 +65136,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64140,7 +65234,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -64173,7 +65268,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -64270,7 +65366,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -64303,7 +65400,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -64400,7 +65498,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -64433,7 +65532,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -64530,7 +65630,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -64563,7 +65664,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -64660,7 +65762,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -64693,7 +65796,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -64790,7 +65894,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -64823,7 +65928,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -64920,7 +66026,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -64953,7 +66060,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -65050,7 +66158,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -65083,7 +66192,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -65180,7 +66290,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -65213,7 +66324,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -65310,7 +66422,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -65343,7 +66456,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -65440,7 +66554,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -65473,7 +66588,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -65570,7 +66686,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -65603,7 +66720,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -65700,7 +66818,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -65733,7 +66852,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -65830,7 +66950,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -65863,7 +66984,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -65960,7 +67082,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -65993,7 +67116,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -66090,7 +67214,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -66123,7 +67248,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -66220,7 +67346,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -66253,7 +67380,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -66350,7 +67478,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -66383,7 +67512,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -66480,7 +67610,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -66513,7 +67644,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -66610,7 +67742,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -66643,7 +67776,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -66740,7 +67874,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -66773,7 +67908,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -66870,7 +68006,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -66903,7 +68040,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -67000,7 +68138,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -67033,7 +68172,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -67172,7 +68312,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -67205,7 +68346,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -67238,7 +68380,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -67271,7 +68414,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -67304,7 +68448,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -67337,7 +68482,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -67370,7 +68516,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -67403,7 +68550,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -67436,7 +68584,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -67469,7 +68618,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -67502,7 +68652,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -67535,7 +68686,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -67568,7 +68720,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -67601,7 +68754,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -67634,7 +68788,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -67667,7 +68822,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -67700,7 +68856,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -67733,7 +68890,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -67766,7 +68924,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -67799,7 +68958,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -67832,7 +68992,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -67865,7 +69026,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -67898,7 +69060,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -67931,7 +69094,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -67964,7 +69128,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -67997,7 +69162,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -68030,7 +69196,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -68063,7 +69230,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -68096,7 +69264,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -68129,7 +69298,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -68162,7 +69332,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -68195,7 +69366,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -68228,7 +69400,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -68261,7 +69434,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -68294,7 +69468,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -68327,7 +69502,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -68360,7 +69536,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -68393,7 +69570,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -68426,7 +69604,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -68459,7 +69638,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -68492,7 +69672,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -68525,7 +69706,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -68558,7 +69740,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -68591,7 +69774,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -68624,7 +69808,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -68657,7 +69842,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -68690,7 +69876,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -68723,7 +69910,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -68756,7 +69944,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -68789,7 +69978,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -68822,7 +70012,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -68855,7 +70046,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -68888,7 +70080,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -68921,7 +70114,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -68954,7 +70148,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -68987,7 +70182,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -69020,7 +70216,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -69053,7 +70250,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -69086,7 +70284,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -69119,7 +70318,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -69152,7 +70352,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -69185,7 +70386,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -69218,7 +70420,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -69251,7 +70454,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -69284,7 +70488,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -69317,7 +70522,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -69350,7 +70556,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -69383,7 +70590,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -69416,7 +70624,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -69449,7 +70658,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -69482,7 +70692,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -69515,7 +70726,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -69548,7 +70760,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -69581,7 +70794,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -69614,7 +70828,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -69647,7 +70862,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -69680,7 +70896,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -69713,7 +70930,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -69746,7 +70964,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -69779,7 +70998,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -69812,7 +71032,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -69845,7 +71066,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -69878,7 +71100,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -69911,7 +71134,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -69944,7 +71168,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -69977,7 +71202,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -70010,7 +71236,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -70043,7 +71270,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -70076,7 +71304,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -70109,7 +71338,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -70142,7 +71372,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -70175,7 +71406,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -70208,7 +71440,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -70241,7 +71474,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -70274,7 +71508,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -70307,7 +71542,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -70340,7 +71576,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -70373,7 +71610,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -70406,7 +71644,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -70439,7 +71678,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -70472,7 +71712,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -70505,7 +71746,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -70538,7 +71780,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -70571,7 +71814,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -70604,7 +71848,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -70637,7 +71882,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -70670,7 +71916,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -70703,7 +71950,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -70736,7 +71984,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -70769,7 +72018,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -70802,7 +72052,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -70835,7 +72086,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -70868,7 +72120,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -70901,7 +72154,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -70934,7 +72188,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -70967,7 +72222,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -71000,7 +72256,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -71033,7 +72290,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -71066,7 +72324,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -71099,7 +72358,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -71132,7 +72392,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -71165,7 +72426,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -71198,7 +72460,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -71231,7 +72494,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -71264,7 +72528,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -71297,7 +72562,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -71330,7 +72596,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -71363,7 +72630,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -71396,7 +72664,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -71429,7 +72698,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -71462,7 +72732,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -71495,7 +72766,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -71528,7 +72800,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -71561,7 +72834,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -71594,7 +72868,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -71627,7 +72902,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -71660,7 +72936,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -71693,7 +72970,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -71726,7 +73004,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -71759,7 +73038,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -71792,7 +73072,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -71825,7 +73106,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -71858,7 +73140,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -71891,7 +73174,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -71924,7 +73208,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -71957,7 +73242,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -71990,7 +73276,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -72023,7 +73310,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -72056,7 +73344,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -72089,7 +73378,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -72122,7 +73412,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -72155,7 +73446,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -72188,7 +73480,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -72221,7 +73514,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -72254,7 +73548,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -72287,7 +73582,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -72320,7 +73616,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -72353,7 +73650,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -72386,7 +73684,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -72419,7 +73718,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -72452,7 +73752,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -72485,7 +73786,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -72518,7 +73820,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -72551,7 +73854,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -72584,7 +73888,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -72617,7 +73922,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -72650,7 +73956,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -72683,7 +73990,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -72716,7 +74024,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -72749,7 +74058,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -72782,7 +74092,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -72815,7 +74126,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -72848,7 +74160,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -72881,7 +74194,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -72914,7 +74228,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -72947,7 +74262,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -72980,7 +74296,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -73013,7 +74330,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -73046,7 +74364,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -73079,7 +74398,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -73112,7 +74432,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -73145,7 +74466,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -73178,7 +74500,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -73211,7 +74534,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -73244,7 +74568,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -73277,7 +74602,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -73310,7 +74636,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -73343,7 +74670,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -73376,7 +74704,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -73409,7 +74738,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -73548,7 +74878,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -73581,7 +74912,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -73678,7 +75010,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -73711,7 +75044,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -73808,7 +75142,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -73841,7 +75176,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -73938,7 +75274,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -73971,7 +75308,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -74068,7 +75406,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -74101,7 +75440,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -74198,7 +75538,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -74231,7 +75572,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -74328,7 +75670,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -74361,7 +75704,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -74458,7 +75802,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -74491,7 +75836,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -74588,7 +75934,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -74621,7 +75968,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -74718,7 +76066,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -74751,7 +76100,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -74848,7 +76198,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -74881,7 +76232,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -74978,7 +76330,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -75011,7 +76364,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -75108,7 +76462,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -75141,7 +76496,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -75238,7 +76594,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -75271,7 +76628,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -75368,7 +76726,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -75401,7 +76760,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -75498,7 +76858,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -75531,7 +76892,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -75628,7 +76990,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -75661,7 +77024,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -75758,7 +77122,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -75791,7 +77156,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -75888,7 +77254,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -75921,7 +77288,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -76018,7 +77386,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -76051,7 +77420,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -76148,7 +77518,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -76181,7 +77552,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -76278,7 +77650,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -76311,7 +77684,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -76408,7 +77782,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -76441,7 +77816,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -76538,7 +77914,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -76571,7 +77948,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -76668,7 +78046,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -76701,7 +78080,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -76798,7 +78178,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -76831,7 +78212,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -76928,7 +78310,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -76961,7 +78344,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -77058,7 +78442,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -77091,7 +78476,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -77188,7 +78574,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -77221,7 +78608,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -77318,7 +78706,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -77351,7 +78740,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -77448,7 +78838,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -77481,7 +78872,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -77578,7 +78970,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -77611,7 +79004,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -77708,7 +79102,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -77741,7 +79136,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -77838,7 +79234,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -77871,7 +79268,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -77968,7 +79366,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -78001,7 +79400,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -78098,7 +79498,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -78131,7 +79532,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -78228,7 +79630,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -78261,7 +79664,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -78358,7 +79762,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -78391,7 +79796,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -78488,7 +79894,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -78521,7 +79928,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -78618,7 +80026,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -78651,7 +80060,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -78748,7 +80158,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -78781,7 +80192,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -78878,7 +80290,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -78911,7 +80324,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -79008,7 +80422,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -79041,7 +80456,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -79138,7 +80554,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -79171,7 +80588,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -79268,7 +80686,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -79301,7 +80720,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -79398,7 +80818,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -79431,7 +80852,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -79528,7 +80950,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -79561,7 +80984,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -79658,7 +81082,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -79691,7 +81116,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -79788,7 +81214,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -79821,7 +81248,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -79918,7 +81346,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -79951,7 +81380,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -80048,7 +81478,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -80081,7 +81512,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -80178,7 +81610,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -80211,7 +81644,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -80308,7 +81742,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -80341,7 +81776,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -80438,7 +81874,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -80471,7 +81908,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -80568,7 +82006,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -80601,7 +82040,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -80698,7 +82138,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -80731,7 +82172,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -80828,7 +82270,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -80861,7 +82304,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -80958,7 +82402,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -80991,7 +82436,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -81088,7 +82534,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -81121,7 +82568,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -81218,7 +82666,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -81251,7 +82700,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -81348,7 +82798,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -81381,7 +82832,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -81478,7 +82930,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -81511,7 +82964,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -81608,7 +83062,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -81641,7 +83096,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -81738,7 +83194,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -81771,7 +83228,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -81868,7 +83326,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -81901,7 +83360,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -81998,7 +83458,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -82031,7 +83492,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -82128,7 +83590,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -82161,7 +83624,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -82258,7 +83722,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -82291,7 +83756,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -82388,7 +83854,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -82421,7 +83888,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -82518,7 +83986,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -82551,7 +84020,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -82648,7 +84118,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -82681,7 +84152,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -82778,7 +84250,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -82811,7 +84284,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -82908,7 +84382,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -82941,7 +84416,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -83038,7 +84514,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -83071,7 +84548,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -83168,7 +84646,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -83201,7 +84680,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -83298,7 +84778,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -83331,7 +84812,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -83428,7 +84910,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -83461,7 +84944,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -83558,7 +85042,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -83591,7 +85076,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -83688,7 +85174,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -83721,7 +85208,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -83818,7 +85306,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -83851,7 +85340,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -83948,7 +85438,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -83981,7 +85472,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -84078,7 +85570,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -84111,7 +85604,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -84208,7 +85702,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -84241,7 +85736,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -84338,7 +85834,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -84371,7 +85868,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -84468,7 +85966,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -84501,7 +86000,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -84598,7 +86098,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -84631,7 +86132,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -84728,7 +86230,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -84761,7 +86264,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -84858,7 +86362,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -84891,7 +86396,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -84988,7 +86494,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -85021,7 +86528,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -85118,7 +86626,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -85151,7 +86660,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -85248,7 +86758,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -85281,7 +86792,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -85378,7 +86890,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -85411,7 +86924,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -85508,7 +87022,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -85541,7 +87056,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -85638,7 +87154,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -85671,7 +87188,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -85768,7 +87286,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -85801,7 +87320,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -85940,7 +87460,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -85973,7 +87494,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -86006,7 +87528,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86039,7 +87562,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -86072,7 +87596,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86105,7 +87630,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -86138,7 +87664,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86171,7 +87698,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -86204,7 +87732,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86237,7 +87766,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -86270,7 +87800,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86303,7 +87834,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -86336,7 +87868,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86369,7 +87902,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -86402,7 +87936,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86435,7 +87970,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86468,7 +88004,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86501,7 +88038,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -86534,7 +88072,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86567,7 +88106,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -86600,7 +88140,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86633,7 +88174,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -86666,7 +88208,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86699,7 +88242,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -86732,7 +88276,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86765,7 +88310,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -86798,7 +88344,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86831,7 +88378,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -86864,7 +88412,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86897,7 +88446,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -86930,7 +88480,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -86963,7 +88514,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -86996,7 +88548,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -87029,7 +88582,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -87062,7 +88616,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -87095,7 +88650,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -87128,7 +88684,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -87161,7 +88718,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -87194,7 +88752,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -87227,7 +88786,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -87260,7 +88820,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -87293,7 +88854,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -87326,7 +88888,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -87359,7 +88922,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -87392,7 +88956,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -87425,7 +88990,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -87458,7 +89024,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -87491,7 +89058,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -87524,7 +89092,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -87557,7 +89126,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -87590,7 +89160,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -87623,7 +89194,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -87656,7 +89228,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -87689,7 +89262,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -87722,7 +89296,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -87755,7 +89330,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -87788,7 +89364,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -87821,7 +89398,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -87854,7 +89432,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -87887,7 +89466,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -87920,7 +89500,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -87953,7 +89534,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -87986,7 +89568,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -88019,7 +89602,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -88052,7 +89636,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -88085,7 +89670,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -88118,7 +89704,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -88151,7 +89738,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -88184,7 +89772,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -88217,7 +89806,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -88250,7 +89840,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -88283,7 +89874,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -88316,7 +89908,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -88349,7 +89942,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -88382,7 +89976,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -88415,7 +90010,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -88448,7 +90044,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -88481,7 +90078,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -88514,7 +90112,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -88547,7 +90146,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -88580,7 +90180,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -88613,7 +90214,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -88646,7 +90248,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -88679,7 +90282,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -88712,7 +90316,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -88745,7 +90350,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -88778,7 +90384,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -88811,7 +90418,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -88844,7 +90452,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -88877,7 +90486,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -88910,7 +90520,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -88943,7 +90554,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -88976,7 +90588,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89009,7 +90622,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -89042,7 +90656,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89075,7 +90690,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -89108,7 +90724,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89141,7 +90758,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -89174,7 +90792,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89207,7 +90826,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -89240,7 +90860,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89273,7 +90894,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -89306,7 +90928,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89339,7 +90962,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -89372,7 +90996,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89405,7 +91030,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -89438,7 +91064,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89471,7 +91098,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -89504,7 +91132,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89537,7 +91166,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -89570,7 +91200,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89603,7 +91234,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -89636,7 +91268,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89669,7 +91302,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -89702,7 +91336,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89735,7 +91370,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -89768,7 +91404,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89801,7 +91438,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -89834,7 +91472,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89867,7 +91506,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -89900,7 +91540,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89933,7 +91574,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -89966,7 +91608,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -89999,7 +91642,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -90032,7 +91676,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -90065,7 +91710,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -90098,7 +91744,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -90131,7 +91778,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -90164,7 +91812,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -90197,7 +91846,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -90230,7 +91880,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -90263,7 +91914,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -90296,7 +91948,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -90329,7 +91982,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -90362,7 +92016,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -90395,7 +92050,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -90428,7 +92084,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -90461,7 +92118,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -90494,7 +92152,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -90527,7 +92186,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -90560,7 +92220,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -90593,7 +92254,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -90626,7 +92288,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -90659,7 +92322,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -90692,7 +92356,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -90725,7 +92390,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -90758,7 +92424,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -90791,7 +92458,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -90824,7 +92492,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -90857,7 +92526,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -90890,7 +92560,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -90923,7 +92594,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -90956,7 +92628,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -90989,7 +92662,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -91022,7 +92696,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -91055,7 +92730,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -91088,7 +92764,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -91121,7 +92798,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -91154,7 +92832,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -91187,7 +92866,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -91220,7 +92900,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -91253,7 +92934,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -91286,7 +92968,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -91319,7 +93002,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -91352,7 +93036,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -91385,7 +93070,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -91418,7 +93104,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -91451,7 +93138,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -91484,7 +93172,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -91517,7 +93206,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -91550,7 +93240,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -91583,7 +93274,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -91616,7 +93308,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -91649,7 +93342,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -91682,7 +93376,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -91715,7 +93410,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -91748,7 +93444,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -91781,7 +93478,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -91814,7 +93512,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -91847,7 +93546,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -91880,7 +93580,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -91913,7 +93614,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -91946,7 +93648,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -91979,7 +93682,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -92012,7 +93716,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -92045,7 +93750,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -92078,7 +93784,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -92111,7 +93818,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -92144,7 +93852,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -92177,7 +93886,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -92316,7 +94026,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -92349,7 +94060,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -92446,7 +94158,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -92479,7 +94192,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -92576,7 +94290,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -92609,7 +94324,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -92706,7 +94422,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -92739,7 +94456,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -92836,7 +94554,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -92869,7 +94588,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -92966,7 +94686,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -92999,7 +94720,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -93096,7 +94818,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -93129,7 +94852,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -93226,7 +94950,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -93259,7 +94984,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -93356,7 +95082,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -93389,7 +95116,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -93486,7 +95214,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -93519,7 +95248,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -93616,7 +95346,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -93649,7 +95380,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -93746,7 +95478,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -93779,7 +95512,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -93876,7 +95610,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -93909,7 +95644,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -94006,7 +95742,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -94039,7 +95776,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -94136,7 +95874,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -94169,7 +95908,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -94266,7 +96006,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -94299,7 +96040,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -94396,7 +96138,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -94429,7 +96172,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -94526,7 +96270,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -94559,7 +96304,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -94656,7 +96402,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -94689,7 +96436,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -94786,7 +96534,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -94819,7 +96568,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -94916,7 +96666,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -94949,7 +96700,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -95046,7 +96798,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -95079,7 +96832,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -95176,7 +96930,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -95209,7 +96964,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -95306,7 +97062,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -95339,7 +97096,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -95436,7 +97194,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -95469,7 +97228,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -95566,7 +97326,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -95599,7 +97360,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -95696,7 +97458,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -95729,7 +97492,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -95826,7 +97590,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -95859,7 +97624,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -95956,7 +97722,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -95989,7 +97756,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -96086,7 +97854,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -96119,7 +97888,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -96216,7 +97986,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -96249,7 +98020,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -96346,7 +98118,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -96379,7 +98152,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -96476,7 +98250,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -96509,7 +98284,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -96606,7 +98382,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -96639,7 +98416,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -96736,7 +98514,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -96769,7 +98548,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -96866,7 +98646,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -96899,7 +98680,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -96996,7 +98778,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -97029,7 +98812,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -97126,7 +98910,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -97159,7 +98944,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -97256,7 +99042,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -97289,7 +99076,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -97386,7 +99174,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -97419,7 +99208,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -97516,7 +99306,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -97549,7 +99340,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -97646,7 +99438,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -97679,7 +99472,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -97776,7 +99570,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -97809,7 +99604,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -97906,7 +99702,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -97939,7 +99736,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -98036,7 +99834,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -98069,7 +99868,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -98166,7 +99966,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -98199,7 +100000,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -98296,7 +100098,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -98329,7 +100132,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -98426,7 +100230,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -98459,7 +100264,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -98556,7 +100362,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -98589,7 +100396,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -98686,7 +100494,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -98719,7 +100528,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -98816,7 +100626,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -98849,7 +100660,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -98946,7 +100758,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -98979,7 +100792,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -99076,7 +100890,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -99109,7 +100924,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -99206,7 +101022,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -99239,7 +101056,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -99336,7 +101154,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -99369,7 +101188,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -99466,7 +101286,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -99499,7 +101320,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -99596,7 +101418,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -99629,7 +101452,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -99726,7 +101550,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -99759,7 +101584,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -99856,7 +101682,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -99889,7 +101716,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -99986,7 +101814,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -100019,7 +101848,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -100116,7 +101946,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -100149,7 +101980,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -100246,7 +102078,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -100279,7 +102112,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -100376,7 +102210,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -100409,7 +102244,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -100506,7 +102342,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -100539,7 +102376,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -100636,7 +102474,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -100669,7 +102508,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -100766,7 +102606,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -100799,7 +102640,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -100896,7 +102738,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -100929,7 +102772,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -101026,7 +102870,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -101059,7 +102904,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -101156,7 +103002,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -101189,7 +103036,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -101286,7 +103134,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -101319,7 +103168,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -101416,7 +103266,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -101449,7 +103300,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -101546,7 +103398,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -101579,7 +103432,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -101676,7 +103530,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -101709,7 +103564,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -101806,7 +103662,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -101839,7 +103696,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -101936,7 +103794,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -101969,7 +103828,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -102066,7 +103926,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -102099,7 +103960,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -102196,7 +104058,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -102229,7 +104092,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -102326,7 +104190,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -102359,7 +104224,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -102456,7 +104322,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -102489,7 +104356,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -102586,7 +104454,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -102619,7 +104488,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -102716,7 +104586,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -102749,7 +104620,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -102846,7 +104718,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -102879,7 +104752,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -102976,7 +104850,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -103009,7 +104884,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -103106,7 +104982,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -103139,7 +105016,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -103236,7 +105114,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -103269,7 +105148,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -103366,7 +105246,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -103399,7 +105280,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -103496,7 +105378,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -103529,7 +105412,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -103626,7 +105510,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -103659,7 +105544,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -103756,7 +105642,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -103789,7 +105676,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -103886,7 +105774,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -103919,7 +105808,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -104016,7 +105906,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -104049,7 +105940,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -104146,7 +106038,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -104179,7 +106072,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -104276,7 +106170,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -104309,7 +106204,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -104406,7 +106302,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -104439,7 +106336,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -104536,7 +106434,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -104569,7 +106468,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -104708,7 +106608,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -104741,7 +106642,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -104774,7 +106676,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -104807,7 +106710,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -104840,7 +106744,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -104873,7 +106778,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -104906,7 +106812,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -104939,7 +106846,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -104972,7 +106880,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105005,7 +106914,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -105038,7 +106948,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105071,7 +106982,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -105104,7 +107016,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105137,7 +107050,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -105170,7 +107084,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105203,7 +107118,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -105236,7 +107152,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105269,7 +107186,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -105302,7 +107220,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105335,7 +107254,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -105368,7 +107288,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105401,7 +107322,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -105434,7 +107356,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105467,7 +107390,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -105500,7 +107424,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105533,7 +107458,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -105566,7 +107492,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105599,7 +107526,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -105632,7 +107560,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105665,7 +107594,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -105698,7 +107628,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105731,7 +107662,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105764,7 +107696,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105797,7 +107730,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -105830,7 +107764,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105863,7 +107798,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -105896,7 +107832,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105929,7 +107866,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -105962,7 +107900,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -105995,7 +107934,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -106028,7 +107968,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -106061,7 +108002,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -106094,7 +108036,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -106127,7 +108070,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -106160,7 +108104,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -106193,7 +108138,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -106226,7 +108172,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -106259,7 +108206,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -106292,7 +108240,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -106325,7 +108274,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -106358,7 +108308,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -106391,7 +108342,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -106424,7 +108376,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -106457,7 +108410,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -106490,7 +108444,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -106523,7 +108478,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -106556,7 +108512,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -106589,7 +108546,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -106622,7 +108580,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -106655,7 +108614,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -106688,7 +108648,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -106721,7 +108682,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -106754,7 +108716,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -106787,7 +108750,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -106820,7 +108784,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -106853,7 +108818,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -106886,7 +108852,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -106919,7 +108886,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -106952,7 +108920,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -106985,7 +108954,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -107018,7 +108988,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -107051,7 +109022,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -107084,7 +109056,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -107117,7 +109090,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -107150,7 +109124,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -107183,7 +109158,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -107216,7 +109192,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -107249,7 +109226,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -107282,7 +109260,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -107315,7 +109294,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -107348,7 +109328,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -107381,7 +109362,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -107414,7 +109396,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -107447,7 +109430,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -107480,7 +109464,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -107513,7 +109498,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -107546,7 +109532,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -107579,7 +109566,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -107612,7 +109600,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -107645,7 +109634,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -107678,7 +109668,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -107711,7 +109702,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -107744,7 +109736,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -107777,7 +109770,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -107810,7 +109804,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -107843,7 +109838,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -107876,7 +109872,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -107909,7 +109906,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -107942,7 +109940,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -107975,7 +109974,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -108008,7 +110008,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -108041,7 +110042,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -108074,7 +110076,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -108107,7 +110110,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -108140,7 +110144,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -108173,7 +110178,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -108206,7 +110212,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -108239,7 +110246,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -108272,7 +110280,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -108305,7 +110314,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -108338,7 +110348,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -108371,7 +110382,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -108404,7 +110416,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -108437,7 +110450,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -108470,7 +110484,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -108503,7 +110518,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -108536,7 +110552,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -108569,7 +110586,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -108602,7 +110620,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -108635,7 +110654,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -108668,7 +110688,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -108701,7 +110722,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -108734,7 +110756,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -108767,7 +110790,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -108800,7 +110824,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -108833,7 +110858,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -108866,7 +110892,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -108899,7 +110926,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -108932,7 +110960,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -108965,7 +110994,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -108998,7 +111028,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -109031,7 +111062,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -109064,7 +111096,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -109097,7 +111130,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -109130,7 +111164,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -109163,7 +111198,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -109196,7 +111232,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -109229,7 +111266,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -109262,7 +111300,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -109295,7 +111334,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -109328,7 +111368,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -109361,7 +111402,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -109394,7 +111436,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -109427,7 +111470,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -109460,7 +111504,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -109493,7 +111538,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -109526,7 +111572,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -109559,7 +111606,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -109592,7 +111640,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -109625,7 +111674,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -109658,7 +111708,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -109691,7 +111742,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -109724,7 +111776,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -109757,7 +111810,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -109790,7 +111844,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -109823,7 +111878,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -109856,7 +111912,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -109889,7 +111946,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -109922,7 +111980,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -109955,7 +112014,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -109988,7 +112048,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -110021,7 +112082,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -110054,7 +112116,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -110087,7 +112150,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -110120,7 +112184,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -110153,7 +112218,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -110186,7 +112252,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -110219,7 +112286,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -110252,7 +112320,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -110285,7 +112354,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -110318,7 +112388,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -110351,7 +112422,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -110384,7 +112456,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -110417,7 +112490,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -110450,7 +112524,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -110483,7 +112558,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -110516,7 +112592,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -110549,7 +112626,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -110582,7 +112660,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -110615,7 +112694,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -110648,7 +112728,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -110681,7 +112762,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -110714,7 +112796,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -110747,7 +112830,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -110780,7 +112864,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -110813,7 +112898,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -110846,7 +112932,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -110879,7 +112966,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -110912,7 +113000,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -110945,7 +113034,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -111084,7 +113174,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -111117,7 +113208,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -111214,7 +113306,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -111247,7 +113340,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -111344,7 +113438,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -111377,7 +113472,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -111474,7 +113570,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -111507,7 +113604,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -111604,7 +113702,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -111637,7 +113736,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -111734,7 +113834,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -111767,7 +113868,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -111864,7 +113966,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -111897,7 +114000,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -111994,7 +114098,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -112027,7 +114132,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -112124,7 +114230,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -112157,7 +114264,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -112254,7 +114362,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -112287,7 +114396,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -112384,7 +114494,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -112417,7 +114528,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -112514,7 +114626,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -112547,7 +114660,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -112644,7 +114758,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -112677,7 +114792,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -112774,7 +114890,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -112807,7 +114924,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -112904,7 +115022,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -112937,7 +115056,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -113034,7 +115154,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -113067,7 +115188,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -113164,7 +115286,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -113197,7 +115320,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -113294,7 +115418,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -113327,7 +115452,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -113424,7 +115550,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -113457,7 +115584,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -113554,7 +115682,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -113587,7 +115716,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -113684,7 +115814,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -113717,7 +115848,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -113814,7 +115946,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -113847,7 +115980,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -113944,7 +116078,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -113977,7 +116112,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -114074,7 +116210,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -114107,7 +116244,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -114204,7 +116342,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -114237,7 +116376,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -114334,7 +116474,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -114367,7 +116508,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -114464,7 +116606,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -114497,7 +116640,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -114594,7 +116738,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -114627,7 +116772,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -114724,7 +116870,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -114757,7 +116904,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -114854,7 +117002,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -114887,7 +117036,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -114984,7 +117134,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -115017,7 +117168,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -115114,7 +117266,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -115147,7 +117300,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -115244,7 +117398,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -115277,7 +117432,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -115374,7 +117530,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -115407,7 +117564,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -115504,7 +117662,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -115537,7 +117696,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -115634,7 +117794,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -115667,7 +117828,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -115764,7 +117926,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -115797,7 +117960,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -115894,7 +118058,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -115927,7 +118092,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -116024,7 +118190,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -116057,7 +118224,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -116154,7 +118322,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -116187,7 +118356,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -116284,7 +118454,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -116317,7 +118488,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -116414,7 +118586,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -116447,7 +118620,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -116544,7 +118718,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -116577,7 +118752,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -116674,7 +118850,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -116707,7 +118884,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -116804,7 +118982,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -116837,7 +119016,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -116934,7 +119114,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -116967,7 +119148,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -117064,7 +119246,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -117097,7 +119280,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -117194,7 +119378,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -117227,7 +119412,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -117324,7 +119510,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -117357,7 +119544,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -117454,7 +119642,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -117487,7 +119676,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -117584,7 +119774,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -117617,7 +119808,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -117714,7 +119906,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -117747,7 +119940,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -117844,7 +120038,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -117877,7 +120072,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -117974,7 +120170,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -118007,7 +120204,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -118104,7 +120302,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -118137,7 +120336,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -118234,7 +120434,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -118267,7 +120468,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -118364,7 +120566,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -118397,7 +120600,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -118494,7 +120698,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -118527,7 +120732,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C8" }, @@ -118624,7 +120830,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -118657,7 +120864,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -118754,7 +120962,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -118787,7 +120996,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -118884,7 +121094,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -118917,7 +121128,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -119014,7 +121226,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -119047,7 +121260,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -119144,7 +121358,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -119177,7 +121392,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H8" }, @@ -119274,7 +121490,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -119307,7 +121524,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -119404,7 +121622,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -119437,7 +121656,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -119534,7 +121754,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -119567,7 +121788,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C9" }, @@ -119664,7 +121886,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -119697,7 +121920,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D9" }, @@ -119794,7 +122018,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -119827,7 +122052,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -119924,7 +122150,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -119957,7 +122184,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -120054,7 +122282,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -120087,7 +122316,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G9" }, @@ -120184,7 +122414,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -120217,7 +122448,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H9" }, @@ -120314,7 +122546,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -120347,7 +122580,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -120444,7 +122678,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -120477,7 +122712,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -120574,7 +122810,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -120607,7 +122844,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C10" }, @@ -120704,7 +122942,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -120737,7 +122976,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D10" }, @@ -120834,7 +123074,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -120867,7 +123108,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E10" }, @@ -120964,7 +123206,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -120997,7 +123240,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F10" }, @@ -121094,7 +123338,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -121127,7 +123372,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G10" }, @@ -121224,7 +123470,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -121257,7 +123504,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H10" }, @@ -121354,7 +123602,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -121387,7 +123636,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -121484,7 +123734,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -121517,7 +123768,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -121614,7 +123866,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -121647,7 +123900,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -121744,7 +123998,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -121777,7 +124032,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D11" }, @@ -121874,7 +124130,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -121907,7 +124164,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -122004,7 +124262,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -122037,7 +124296,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F11" }, @@ -122134,7 +124394,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -122167,7 +124428,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G11" }, @@ -122264,7 +124526,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -122297,7 +124560,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -122394,7 +124658,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -122427,7 +124692,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -122524,7 +124790,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -122557,7 +124824,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -122654,7 +124922,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -122687,7 +124956,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -122784,7 +125054,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -122817,7 +125088,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -122914,7 +125186,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -122947,7 +125220,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -123044,7 +125318,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -123077,7 +125352,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -123174,7 +125450,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -123207,7 +125484,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -123304,7 +125582,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -123337,7 +125616,8 @@ "y": 0.0, "z": 0.1999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -123489,6 +125769,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons Engineering ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[bc049301c1][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_transfer_destination_collision].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[bc049301c1][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_transfer_destination_collision].json new file mode 100644 index 00000000000..8c741ed84ba --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[bc049301c1][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_transfer_destination_collision].json @@ -0,0 +1,3932 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-1000ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 97.47 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_1000ul", + "tipLength": 88, + "tipOverlap": 7.95 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.24, + "z": 9.47 + }, + "A10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.24, + "z": 9.47 + }, + "A11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.24, + "z": 9.47 + }, + "A12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.24, + "z": 9.47 + }, + "A2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.24, + "z": 9.47 + }, + "A3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.24, + "z": 9.47 + }, + "A4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.24, + "z": 9.47 + }, + "A5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.24, + "z": 9.47 + }, + "A6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.24, + "z": 9.47 + }, + "A7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.24, + "z": 9.47 + }, + "A8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.24, + "z": 9.47 + }, + "A9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.24, + "z": 9.47 + }, + "B1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.24, + "z": 9.47 + }, + "B10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.24, + "z": 9.47 + }, + "B11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.24, + "z": 9.47 + }, + "B12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.24, + "z": 9.47 + }, + "B2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.24, + "z": 9.47 + }, + "B3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.24, + "z": 9.47 + }, + "B4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.24, + "z": 9.47 + }, + "B5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.24, + "z": 9.47 + }, + "B6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.24, + "z": 9.47 + }, + "B7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.24, + "z": 9.47 + }, + "B8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.24, + "z": 9.47 + }, + "B9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.24, + "z": 9.47 + }, + "C1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.24, + "z": 9.47 + }, + "C10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.24, + "z": 9.47 + }, + "C11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.24, + "z": 9.47 + }, + "C12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.24, + "z": 9.47 + }, + "C2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.24, + "z": 9.47 + }, + "C3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.24, + "z": 9.47 + }, + "C4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.24, + "z": 9.47 + }, + "C5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.24, + "z": 9.47 + }, + "C6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.24, + "z": 9.47 + }, + "C7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.24, + "z": 9.47 + }, + "C8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.24, + "z": 9.47 + }, + "C9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.24, + "z": 9.47 + }, + "D1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.24, + "z": 9.47 + }, + "D10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.24, + "z": 9.47 + }, + "D11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.24, + "z": 9.47 + }, + "D12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.24, + "z": 9.47 + }, + "D2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.24, + "z": 9.47 + }, + "D3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.24, + "z": 9.47 + }, + "D4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.24, + "z": 9.47 + }, + "D5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.24, + "z": 9.47 + }, + "D6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.24, + "z": 9.47 + }, + "D7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.24, + "z": 9.47 + }, + "D8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.24, + "z": 9.47 + }, + "D9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.24, + "z": 9.47 + }, + "E1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.24, + "z": 9.47 + }, + "E10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.24, + "z": 9.47 + }, + "E11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.24, + "z": 9.47 + }, + "E12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.24, + "z": 9.47 + }, + "E2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.24, + "z": 9.47 + }, + "E3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.24, + "z": 9.47 + }, + "E4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.24, + "z": 9.47 + }, + "E5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.24, + "z": 9.47 + }, + "E6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.24, + "z": 9.47 + }, + "E7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.24, + "z": 9.47 + }, + "E8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.24, + "z": 9.47 + }, + "E9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.24, + "z": 9.47 + }, + "F1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.24, + "z": 9.47 + }, + "F10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.24, + "z": 9.47 + }, + "F11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.24, + "z": 9.47 + }, + "F12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.24, + "z": 9.47 + }, + "F2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.24, + "z": 9.47 + }, + "F3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.24, + "z": 9.47 + }, + "F4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.24, + "z": 9.47 + }, + "F5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.24, + "z": 9.47 + }, + "F6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.24, + "z": 9.47 + }, + "F7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.24, + "z": 9.47 + }, + "F8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.24, + "z": 9.47 + }, + "F9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.24, + "z": 9.47 + }, + "G1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.24, + "z": 9.47 + }, + "G10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.24, + "z": 9.47 + }, + "G11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.24, + "z": 9.47 + }, + "G12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.24, + "z": 9.47 + }, + "G2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.24, + "z": 9.47 + }, + "G3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.24, + "z": 9.47 + }, + "G4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.24, + "z": 9.47 + }, + "G5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.24, + "z": 9.47 + }, + "G6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.24, + "z": 9.47 + }, + "G7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.24, + "z": 9.47 + }, + "G8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.24, + "z": 9.47 + }, + "G9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.24, + "z": 9.47 + }, + "H1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.24, + "z": 9.47 + }, + "H10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.24, + "z": 9.47 + }, + "H11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.24, + "z": 9.47 + }, + "H12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.24, + "z": 9.47 + }, + "H2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.24, + "z": 9.47 + }, + "H3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.24, + "z": 9.47 + }, + "H4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.24, + "z": 9.47 + }, + "H5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.24, + "z": 9.47 + }, + "H6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.24, + "z": 9.47 + }, + "H7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.24, + "z": 9.47 + }, + "H8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.24, + "z": 9.47 + }, + "H9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.24, + "z": 9.47 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "C3" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/tube-rack-set-1" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 124.35 + }, + "gripperOffsets": {}, + "groups": [ + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352095", + "352096", + "352097", + "352099", + "352196" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 6x15 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A2", + "B1", + "B2", + "C1", + "C2" + ] + }, + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352070", + "352098" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 4x50 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A3", + "A4", + "B3", + "B4" + ] + } + ], + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Opentrons 10 Tube Rack with Falcon 4x50 mL, 6x15 mL Conical", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1" + ], + [ + "A2", + "B2", + "C2" + ], + [ + "A3", + "B3" + ], + [ + "A4", + "B4" + ] + ], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 67.75, + "z": 6.85 + }, + "A2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 67.75, + "z": 6.85 + }, + "A3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 60.25, + "z": 7.3 + }, + "A4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 60.25, + "z": 7.3 + }, + "B1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 42.75, + "z": 6.85 + }, + "B2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 42.75, + "z": 6.85 + }, + "B3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 25.25, + "z": 7.3 + }, + "B4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 25.25, + "z": 7.3 + }, + "C1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 17.75, + "z": 6.85 + }, + "C2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 17.75, + "z": 6.85 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c910be62291bb94458a59cb4185c5180", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d75e38152238e9ad0c18e291fe97e483", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D3" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8de5f9595204f05bd1016cce23cd32b9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 178.38, + "y": 288.24, + "z": 97.47 + }, + "tipDiameter": 7.23, + "tipLength": 77.5, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d770b3dd007307016fd235b6e50d1e5d", + "notes": [], + "params": { + "flowRate": 160.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 178.38, + "y": 74.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "PartialTipMovementNotAllowedError [line 482]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to NEST 96 Well Plate 100 µL PCR Full Skirt in slot D3 with H12 nozzle partial configuration will result in collision with items in deck slot C3.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Moving to NEST 96 Well Plate 100 µL PCR Full Skirt in slot D3 with H12 nozzle partial configuration will result in collision with items in deck slot C3.", + "errorCode": "2004", + "errorInfo": {}, + "errorType": "PartialTipMovementNotAllowedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_transfer_destination_collision.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "B2" + } + }, + { + "definitionUri": "opentrons/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical/1", + "id": "UUID", + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "C3" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D3" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Too tall labware on pickup tip" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c0435f08da][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c0435f08da][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north].json new file mode 100644 index 00000000000..8c38d6c5f57 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c0435f08da][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north].json @@ -0,0 +1,1501 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-1000ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 97.47 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_1000ul", + "tipLength": 88, + "tipOverlap": 7.95 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.24, + "z": 9.47 + }, + "A10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.24, + "z": 9.47 + }, + "A11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.24, + "z": 9.47 + }, + "A12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.24, + "z": 9.47 + }, + "A2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.24, + "z": 9.47 + }, + "A3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.24, + "z": 9.47 + }, + "A4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.24, + "z": 9.47 + }, + "A5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.24, + "z": 9.47 + }, + "A6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.24, + "z": 9.47 + }, + "A7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.24, + "z": 9.47 + }, + "A8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.24, + "z": 9.47 + }, + "A9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.24, + "z": 9.47 + }, + "B1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.24, + "z": 9.47 + }, + "B10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.24, + "z": 9.47 + }, + "B11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.24, + "z": 9.47 + }, + "B12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.24, + "z": 9.47 + }, + "B2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.24, + "z": 9.47 + }, + "B3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.24, + "z": 9.47 + }, + "B4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.24, + "z": 9.47 + }, + "B5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.24, + "z": 9.47 + }, + "B6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.24, + "z": 9.47 + }, + "B7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.24, + "z": 9.47 + }, + "B8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.24, + "z": 9.47 + }, + "B9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.24, + "z": 9.47 + }, + "C1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.24, + "z": 9.47 + }, + "C10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.24, + "z": 9.47 + }, + "C11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.24, + "z": 9.47 + }, + "C12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.24, + "z": 9.47 + }, + "C2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.24, + "z": 9.47 + }, + "C3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.24, + "z": 9.47 + }, + "C4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.24, + "z": 9.47 + }, + "C5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.24, + "z": 9.47 + }, + "C6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.24, + "z": 9.47 + }, + "C7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.24, + "z": 9.47 + }, + "C8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.24, + "z": 9.47 + }, + "C9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.24, + "z": 9.47 + }, + "D1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.24, + "z": 9.47 + }, + "D10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.24, + "z": 9.47 + }, + "D11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.24, + "z": 9.47 + }, + "D12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.24, + "z": 9.47 + }, + "D2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.24, + "z": 9.47 + }, + "D3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.24, + "z": 9.47 + }, + "D4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.24, + "z": 9.47 + }, + "D5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.24, + "z": 9.47 + }, + "D6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.24, + "z": 9.47 + }, + "D7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.24, + "z": 9.47 + }, + "D8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.24, + "z": 9.47 + }, + "D9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.24, + "z": 9.47 + }, + "E1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.24, + "z": 9.47 + }, + "E10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.24, + "z": 9.47 + }, + "E11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.24, + "z": 9.47 + }, + "E12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.24, + "z": 9.47 + }, + "E2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.24, + "z": 9.47 + }, + "E3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.24, + "z": 9.47 + }, + "E4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.24, + "z": 9.47 + }, + "E5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.24, + "z": 9.47 + }, + "E6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.24, + "z": 9.47 + }, + "E7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.24, + "z": 9.47 + }, + "E8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.24, + "z": 9.47 + }, + "E9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.24, + "z": 9.47 + }, + "F1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.24, + "z": 9.47 + }, + "F10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.24, + "z": 9.47 + }, + "F11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.24, + "z": 9.47 + }, + "F12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.24, + "z": 9.47 + }, + "F2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.24, + "z": 9.47 + }, + "F3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.24, + "z": 9.47 + }, + "F4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.24, + "z": 9.47 + }, + "F5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.24, + "z": 9.47 + }, + "F6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.24, + "z": 9.47 + }, + "F7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.24, + "z": 9.47 + }, + "F8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.24, + "z": 9.47 + }, + "F9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.24, + "z": 9.47 + }, + "G1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.24, + "z": 9.47 + }, + "G10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.24, + "z": 9.47 + }, + "G11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.24, + "z": 9.47 + }, + "G12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.24, + "z": 9.47 + }, + "G2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.24, + "z": 9.47 + }, + "G3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.24, + "z": 9.47 + }, + "G4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.24, + "z": 9.47 + }, + "G5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.24, + "z": 9.47 + }, + "G6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.24, + "z": 9.47 + }, + "G7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.24, + "z": 9.47 + }, + "G8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.24, + "z": 9.47 + }, + "G9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.24, + "z": 9.47 + }, + "H1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.24, + "z": 9.47 + }, + "H10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.24, + "z": 9.47 + }, + "H11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.24, + "z": 9.47 + }, + "H12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.24, + "z": 9.47 + }, + "H2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.24, + "z": 9.47 + }, + "H3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.24, + "z": 9.47 + }, + "H4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.24, + "z": 9.47 + }, + "H5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.24, + "z": 9.47 + }, + "H6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.24, + "z": 9.47 + }, + "H7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.24, + "z": 9.47 + }, + "H8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.24, + "z": 9.47 + }, + "H9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.24, + "z": 9.47 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/tube-rack-set-1" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 124.35 + }, + "gripperOffsets": {}, + "groups": [ + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352095", + "352096", + "352097", + "352099", + "352196" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 6x15 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A2", + "B1", + "B2", + "C1", + "C2" + ] + }, + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352070", + "352098" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 4x50 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A3", + "A4", + "B3", + "B4" + ] + } + ], + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Opentrons 10 Tube Rack with Falcon 4x50 mL, 6x15 mL Conical", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1" + ], + [ + "A2", + "B2", + "C2" + ], + [ + "A3", + "B3" + ], + [ + "A4", + "B4" + ] + ], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 67.75, + "z": 6.85 + }, + "A2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 67.75, + "z": 6.85 + }, + "A3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 60.25, + "z": 7.3 + }, + "A4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 60.25, + "z": 7.3 + }, + "B1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 42.75, + "z": 6.85 + }, + "B2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 42.75, + "z": 6.85 + }, + "B3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 25.25, + "z": 7.3 + }, + "B4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 25.25, + "z": 7.3 + }, + "C1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 17.75, + "z": 6.85 + }, + "C2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 17.75, + "z": 6.85 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "PartialTipMovementNotAllowedError [line 479]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with H12 nozzle partial configuration will result in collision with items in deck slot B2.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Moving to Opentrons OT-2 96 Tip Rack 1000 µL in slot C2 with H12 nozzle partial configuration will result in collision with items in deck slot B2.", + "errorCode": "2004", + "errorInfo": {}, + "errorType": "PartialTipMovementNotAllowedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_north.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "C2" + } + }, + { + "definitionUri": "opentrons/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical/1", + "id": "UUID", + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "B2" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Too tall labware on pickup tip" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c064d0de2c][OT2_S_v6_P1000S_None_SimpleTransfer].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c064d0de2c][OT2_S_v6_P1000S_None_SimpleTransfer].json index 9987d051bb0..4d2cd13d215 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c064d0de2c][OT2_S_v6_P1000S_None_SimpleTransfer].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c064d0de2c][OT2_S_v6_P1000S_None_SimpleTransfer].json @@ -1769,7 +1769,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -1802,7 +1803,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -1835,7 +1837,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -1868,7 +1871,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -1973,6 +1977,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c195291f84][OT2_S_v2_17_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c195291f84][OT2_S_v2_17_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json index d6eb8a28124..c2e5c309dcf 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c195291f84][OT2_S_v2_17_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c195291f84][OT2_S_v2_17_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json @@ -145,6 +145,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c1c04baffd][Flex_S_v2_17_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c1c04baffd][Flex_S_v2_17_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json index f59969368ab..7c817b2b869 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c1c04baffd][Flex_S_v2_17_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c1c04baffd][Flex_S_v2_17_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json @@ -171,6 +171,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c3098303ad][OT2_S_v2_15_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c3098303ad][OT2_S_v2_15_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json index 4c6c38162b3..f69446ee9cb 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c3098303ad][OT2_S_v2_15_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c3098303ad][OT2_S_v2_15_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json @@ -154,6 +154,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c343dfb5a0][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_bottom_right_edge].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c343dfb5a0][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_bottom_right_edge].json new file mode 100644 index 00000000000..fe3f96abc67 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c343dfb5a0][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_bottom_right_edge].json @@ -0,0 +1,1269 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "D3" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-1000ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 97.47 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_1000ul", + "tipLength": 88, + "tipOverlap": 7.95 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.24, + "z": 9.47 + }, + "A10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.24, + "z": 9.47 + }, + "A11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.24, + "z": 9.47 + }, + "A12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.24, + "z": 9.47 + }, + "A2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.24, + "z": 9.47 + }, + "A3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.24, + "z": 9.47 + }, + "A4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.24, + "z": 9.47 + }, + "A5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.24, + "z": 9.47 + }, + "A6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.24, + "z": 9.47 + }, + "A7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.24, + "z": 9.47 + }, + "A8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.24, + "z": 9.47 + }, + "A9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.24, + "z": 9.47 + }, + "B1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.24, + "z": 9.47 + }, + "B10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.24, + "z": 9.47 + }, + "B11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.24, + "z": 9.47 + }, + "B12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.24, + "z": 9.47 + }, + "B2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.24, + "z": 9.47 + }, + "B3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.24, + "z": 9.47 + }, + "B4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.24, + "z": 9.47 + }, + "B5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.24, + "z": 9.47 + }, + "B6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.24, + "z": 9.47 + }, + "B7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.24, + "z": 9.47 + }, + "B8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.24, + "z": 9.47 + }, + "B9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.24, + "z": 9.47 + }, + "C1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.24, + "z": 9.47 + }, + "C10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.24, + "z": 9.47 + }, + "C11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.24, + "z": 9.47 + }, + "C12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.24, + "z": 9.47 + }, + "C2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.24, + "z": 9.47 + }, + "C3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.24, + "z": 9.47 + }, + "C4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.24, + "z": 9.47 + }, + "C5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.24, + "z": 9.47 + }, + "C6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.24, + "z": 9.47 + }, + "C7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.24, + "z": 9.47 + }, + "C8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.24, + "z": 9.47 + }, + "C9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.24, + "z": 9.47 + }, + "D1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.24, + "z": 9.47 + }, + "D10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.24, + "z": 9.47 + }, + "D11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.24, + "z": 9.47 + }, + "D12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.24, + "z": 9.47 + }, + "D2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.24, + "z": 9.47 + }, + "D3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.24, + "z": 9.47 + }, + "D4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.24, + "z": 9.47 + }, + "D5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.24, + "z": 9.47 + }, + "D6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.24, + "z": 9.47 + }, + "D7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.24, + "z": 9.47 + }, + "D8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.24, + "z": 9.47 + }, + "D9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.24, + "z": 9.47 + }, + "E1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.24, + "z": 9.47 + }, + "E10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.24, + "z": 9.47 + }, + "E11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.24, + "z": 9.47 + }, + "E12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.24, + "z": 9.47 + }, + "E2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.24, + "z": 9.47 + }, + "E3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.24, + "z": 9.47 + }, + "E4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.24, + "z": 9.47 + }, + "E5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.24, + "z": 9.47 + }, + "E6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.24, + "z": 9.47 + }, + "E7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.24, + "z": 9.47 + }, + "E8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.24, + "z": 9.47 + }, + "E9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.24, + "z": 9.47 + }, + "F1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.24, + "z": 9.47 + }, + "F10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.24, + "z": 9.47 + }, + "F11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.24, + "z": 9.47 + }, + "F12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.24, + "z": 9.47 + }, + "F2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.24, + "z": 9.47 + }, + "F3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.24, + "z": 9.47 + }, + "F4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.24, + "z": 9.47 + }, + "F5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.24, + "z": 9.47 + }, + "F6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.24, + "z": 9.47 + }, + "F7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.24, + "z": 9.47 + }, + "F8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.24, + "z": 9.47 + }, + "F9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.24, + "z": 9.47 + }, + "G1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.24, + "z": 9.47 + }, + "G10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.24, + "z": 9.47 + }, + "G11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.24, + "z": 9.47 + }, + "G12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.24, + "z": 9.47 + }, + "G2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.24, + "z": 9.47 + }, + "G3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.24, + "z": 9.47 + }, + "G4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.24, + "z": 9.47 + }, + "G5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.24, + "z": 9.47 + }, + "G6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.24, + "z": 9.47 + }, + "G7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.24, + "z": 9.47 + }, + "G8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.24, + "z": 9.47 + }, + "G9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.24, + "z": 9.47 + }, + "H1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.24, + "z": 9.47 + }, + "H10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.24, + "z": 9.47 + }, + "H11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.24, + "z": 9.47 + }, + "H12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.24, + "z": 9.47 + }, + "H2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.24, + "z": 9.47 + }, + "H3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.24, + "z": 9.47 + }, + "H4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.24, + "z": 9.47 + }, + "H5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.24, + "z": 9.47 + }, + "H6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.24, + "z": 9.47 + }, + "H7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.24, + "z": 9.47 + }, + "H8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.24, + "z": 9.47 + }, + "H9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.24, + "z": 9.47 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "PartialTipMovementNotAllowedError [line 479]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Requested motion with the A1 nozzle partial configuration is outside of robot bounds for the pipette.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Requested motion with the A1 nozzle partial configuration is outside of robot bounds for the pipette.", + "errorCode": "2004", + "errorInfo": {}, + "errorType": "PartialTipMovementNotAllowedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_bottom_right_edge.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "D3" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Too tall labware on pickup tip" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c745e5824a][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModules].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c745e5824a][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModules].json index 11f0ed0c789..1a48b84ae0a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c745e5824a][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModules].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c745e5824a][Flex_S_v2_16_P1000_96_GRIP_DeckConfiguration1NoModules].json @@ -11240,7 +11240,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11273,7 +11274,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11381,7 +11383,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11414,7 +11417,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11595,7 +11599,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11628,7 +11633,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11736,7 +11742,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11769,7 +11776,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11950,7 +11958,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11983,7 +11992,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12091,7 +12101,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12124,7 +12135,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12303,6 +12315,7 @@ "location": "offDeck" } ], + "liquidClasses": [], "liquids": [ { "description": "High Quality H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c821e64fad][OT2_S_v2_13_P300M_P20S_MM_TC_TM_Smoke620Release].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c821e64fad][OT2_S_v2_13_P300M_P20S_MM_TC_TM_Smoke620Release].json index 99e1e178a9e..253a2bcff9d 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c821e64fad][OT2_S_v2_13_P300M_P20S_MM_TC_TM_Smoke620Release].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c821e64fad][OT2_S_v2_13_P300M_P20S_MM_TC_TM_Smoke620Release].json @@ -7791,7 +7791,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7824,7 +7825,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -7856,7 +7858,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7888,7 +7891,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -7921,7 +7925,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -7953,7 +7958,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7985,7 +7991,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8018,7 +8025,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -8050,7 +8058,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8082,7 +8091,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8115,7 +8125,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -8147,7 +8158,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8179,7 +8191,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8212,7 +8225,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -8244,7 +8258,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8276,7 +8291,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8309,7 +8325,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -8341,7 +8358,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8373,7 +8391,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8406,7 +8425,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -8438,7 +8458,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8470,7 +8491,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8503,7 +8525,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E8" }, @@ -8535,7 +8558,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8567,7 +8591,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8600,7 +8625,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E9" }, @@ -8632,7 +8658,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8664,7 +8691,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8697,7 +8725,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -8729,7 +8758,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8761,7 +8791,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8794,7 +8825,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -8826,7 +8858,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8858,7 +8891,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8891,7 +8925,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -8923,7 +8958,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8955,7 +8991,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8988,7 +9025,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F8" }, @@ -9020,7 +9058,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9052,7 +9091,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9085,7 +9125,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F9" }, @@ -9117,7 +9158,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9149,7 +9191,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9182,7 +9225,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -9214,7 +9258,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9246,7 +9291,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9279,7 +9325,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -9311,7 +9358,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9343,7 +9391,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9376,7 +9425,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G8" }, @@ -9408,7 +9458,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10149,7 +10200,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10182,7 +10234,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10279,7 +10332,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10312,7 +10366,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10409,7 +10464,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10442,7 +10498,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -10539,7 +10596,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10572,7 +10630,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -10718,6 +10777,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.13", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c8d2ca0089][Flex_S_v2_18_QIASeq_FX_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c8d2ca0089][Flex_S_v2_18_QIASeq_FX_48x].json index 347a9b1e5d2..06153dd11b4 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c8d2ca0089][Flex_S_v2_18_QIASeq_FX_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c8d2ca0089][Flex_S_v2_18_QIASeq_FX_48x].json @@ -7651,7 +7651,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7685,7 +7686,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7718,7 +7720,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7751,7 +7754,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7784,7 +7788,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7817,7 +7822,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7850,7 +7856,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7883,7 +7890,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7917,7 +7925,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7950,7 +7959,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7984,7 +7994,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8017,7 +8028,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8050,7 +8062,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8082,7 +8095,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8127,7 +8141,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8234,7 +8249,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8268,7 +8284,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8301,7 +8318,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8334,7 +8352,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8367,7 +8386,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8400,7 +8420,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8433,7 +8454,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8466,7 +8488,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8500,7 +8523,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8533,7 +8557,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8567,7 +8592,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8600,7 +8626,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8633,7 +8660,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8665,7 +8693,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8710,7 +8739,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8817,7 +8847,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8851,7 +8882,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8884,7 +8916,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8917,7 +8950,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8950,7 +8984,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8983,7 +9018,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9016,7 +9052,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9049,7 +9086,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9083,7 +9121,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9116,7 +9155,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9150,7 +9190,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9183,7 +9224,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9216,7 +9258,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9248,7 +9291,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9293,7 +9337,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9541,7 +9586,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -9574,7 +9620,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -9607,7 +9654,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9640,7 +9688,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9674,7 +9723,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9707,7 +9757,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9741,7 +9792,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9774,7 +9826,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9807,7 +9860,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9839,7 +9893,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9884,7 +9939,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9991,7 +10047,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -10024,7 +10081,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -10057,7 +10115,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10090,7 +10149,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10124,7 +10184,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10157,7 +10218,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10191,7 +10253,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10224,7 +10287,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10257,7 +10321,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10289,7 +10354,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10334,7 +10400,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10441,7 +10508,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -10474,7 +10542,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -10507,7 +10576,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10540,7 +10610,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10574,7 +10645,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10607,7 +10679,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10641,7 +10714,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10674,7 +10748,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10707,7 +10782,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10739,7 +10815,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10784,7 +10861,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12092,7 +12170,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12126,7 +12205,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12159,7 +12239,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12192,7 +12273,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12225,7 +12307,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12258,7 +12341,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12290,7 +12374,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12336,7 +12421,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12368,7 +12454,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12400,7 +12487,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12434,7 +12522,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12467,7 +12556,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12501,7 +12591,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12534,7 +12625,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12568,7 +12660,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12601,7 +12694,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12635,7 +12729,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12668,7 +12763,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12702,7 +12798,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12735,7 +12832,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12769,7 +12867,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12802,7 +12901,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12836,7 +12936,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12869,7 +12970,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12903,7 +13005,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12936,7 +13039,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12970,7 +13074,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13003,7 +13108,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13036,7 +13142,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13068,7 +13175,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13113,7 +13221,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13220,7 +13329,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13254,7 +13364,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13287,7 +13398,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13320,7 +13432,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13353,7 +13466,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13386,7 +13500,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13418,7 +13533,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13464,7 +13580,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13496,7 +13613,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13528,7 +13646,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13562,7 +13681,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13595,7 +13715,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13629,7 +13750,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13662,7 +13784,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13696,7 +13819,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13729,7 +13853,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13763,7 +13888,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13796,7 +13922,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13830,7 +13957,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13863,7 +13991,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13897,7 +14026,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13930,7 +14060,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13964,7 +14095,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13997,7 +14129,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14031,7 +14164,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14064,7 +14198,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14098,7 +14233,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14131,7 +14267,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14164,7 +14301,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14196,7 +14334,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14241,7 +14380,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14348,7 +14488,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14382,7 +14523,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14415,7 +14557,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14448,7 +14591,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14481,7 +14625,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14514,7 +14659,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14546,7 +14692,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14592,7 +14739,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14624,7 +14772,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14656,7 +14805,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14690,7 +14840,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14723,7 +14874,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14757,7 +14909,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14790,7 +14943,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14824,7 +14978,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14857,7 +15012,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14891,7 +15047,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14924,7 +15081,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14958,7 +15116,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14991,7 +15150,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15025,7 +15185,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15058,7 +15219,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15092,7 +15254,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15125,7 +15288,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15159,7 +15323,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15192,7 +15357,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15226,7 +15392,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15259,7 +15426,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15292,7 +15460,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15324,7 +15493,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15369,7 +15539,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15613,7 +15784,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15647,7 +15819,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15680,7 +15853,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15714,7 +15888,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15747,7 +15922,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15780,7 +15956,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15813,7 +15990,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15846,7 +16024,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15878,7 +16057,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15909,7 +16089,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15940,7 +16121,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15972,7 +16154,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16018,7 +16201,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16050,7 +16234,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16096,7 +16281,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16128,7 +16314,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16161,7 +16348,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16193,7 +16381,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16225,7 +16414,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16257,7 +16447,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16289,7 +16480,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16322,7 +16514,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16354,7 +16547,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16386,7 +16580,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16419,7 +16614,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16451,7 +16647,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16483,7 +16680,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16516,7 +16714,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16548,7 +16747,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16580,7 +16780,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16612,7 +16813,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16657,7 +16859,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16689,7 +16892,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16720,7 +16924,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16751,7 +16956,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16782,7 +16988,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16889,7 +17096,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16923,7 +17131,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16956,7 +17165,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16990,7 +17200,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17023,7 +17234,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17056,7 +17268,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17089,7 +17302,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17122,7 +17336,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17154,7 +17369,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17185,7 +17401,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17216,7 +17433,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17248,7 +17466,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17294,7 +17513,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17326,7 +17546,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17372,7 +17593,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17404,7 +17626,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17437,7 +17660,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17469,7 +17693,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17501,7 +17726,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17533,7 +17759,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17565,7 +17792,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17598,7 +17826,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17630,7 +17859,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17662,7 +17892,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17695,7 +17926,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17727,7 +17959,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17759,7 +17992,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17792,7 +18026,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17824,7 +18059,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17856,7 +18092,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17888,7 +18125,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17933,7 +18171,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17965,7 +18204,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17996,7 +18236,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18027,7 +18268,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18058,7 +18300,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18165,7 +18408,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18199,7 +18443,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18232,7 +18477,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18266,7 +18512,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18299,7 +18546,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18332,7 +18580,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18365,7 +18614,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18398,7 +18648,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18430,7 +18681,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18461,7 +18713,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18492,7 +18745,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18524,7 +18778,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18570,7 +18825,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18602,7 +18858,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18648,7 +18905,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18680,7 +18938,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18713,7 +18972,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18745,7 +19005,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18777,7 +19038,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18809,7 +19071,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18841,7 +19104,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18874,7 +19138,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18906,7 +19171,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18938,7 +19204,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18971,7 +19238,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19003,7 +19271,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19035,7 +19304,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19068,7 +19338,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19100,7 +19371,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19132,7 +19404,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19164,7 +19437,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19209,7 +19483,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19241,7 +19516,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19272,7 +19548,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19303,7 +19580,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19334,7 +19612,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19571,7 +19850,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19603,7 +19883,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19649,7 +19930,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19681,7 +19963,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19713,7 +19996,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19759,7 +20043,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19805,7 +20090,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19836,7 +20122,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19867,7 +20154,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19962,7 +20250,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19994,7 +20283,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20040,7 +20330,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20072,7 +20363,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20104,7 +20396,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20150,7 +20443,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20196,7 +20490,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20227,7 +20522,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20258,7 +20554,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20353,7 +20650,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20385,7 +20683,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20431,7 +20730,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20463,7 +20763,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20495,7 +20796,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20541,7 +20843,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20587,7 +20890,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20618,7 +20922,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20649,7 +20954,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20773,7 +21079,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20805,7 +21112,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20836,7 +21144,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20867,7 +21176,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20898,7 +21208,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20930,7 +21241,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20976,7 +21288,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21007,7 +21320,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21038,7 +21352,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21069,7 +21384,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21190,7 +21506,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21222,7 +21539,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21253,7 +21571,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21284,7 +21603,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21315,7 +21635,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21347,7 +21668,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21393,7 +21715,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21424,7 +21747,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21455,7 +21779,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21486,7 +21811,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21607,7 +21933,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21639,7 +21966,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21670,7 +21998,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21701,7 +22030,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21732,7 +22062,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21764,7 +22095,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21810,7 +22142,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21841,7 +22174,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21872,7 +22206,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21903,7 +22238,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23224,7 +23560,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23256,7 +23593,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23302,7 +23640,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23334,7 +23673,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23366,7 +23706,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23398,7 +23739,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23444,7 +23786,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23490,7 +23833,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23521,7 +23865,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23552,7 +23897,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23583,7 +23929,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23614,7 +23961,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23709,7 +24057,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23741,7 +24090,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23787,7 +24137,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23819,7 +24170,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23851,7 +24203,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23883,7 +24236,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23929,7 +24283,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23975,7 +24330,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24006,7 +24362,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24037,7 +24394,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24068,7 +24426,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24099,7 +24458,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24194,7 +24554,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24226,7 +24587,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24272,7 +24634,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24304,7 +24667,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24336,7 +24700,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24368,7 +24733,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24414,7 +24780,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24460,7 +24827,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24491,7 +24859,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24522,7 +24891,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24553,7 +24923,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24584,7 +24955,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24708,7 +25080,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24740,7 +25113,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24771,7 +25145,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24802,7 +25177,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24833,7 +25209,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24865,7 +25242,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24911,7 +25289,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24942,7 +25321,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24973,7 +25353,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25004,7 +25385,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25125,7 +25507,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25157,7 +25540,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25188,7 +25572,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25219,7 +25604,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25250,7 +25636,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25282,7 +25669,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25328,7 +25716,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25359,7 +25748,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25390,7 +25780,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25421,7 +25812,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25542,7 +25934,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25574,7 +25967,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25605,7 +25999,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25636,7 +26031,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25667,7 +26063,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25699,7 +26096,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25745,7 +26143,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25776,7 +26175,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25807,7 +26207,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25838,7 +26239,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25972,7 +26374,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26004,7 +26407,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26050,7 +26454,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26082,7 +26487,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26114,7 +26520,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26146,7 +26553,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26192,7 +26600,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26238,7 +26647,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26269,7 +26679,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26300,7 +26711,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26331,7 +26743,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26362,7 +26775,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26457,7 +26871,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26489,7 +26904,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26535,7 +26951,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26567,7 +26984,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26599,7 +27017,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26631,7 +27050,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26677,7 +27097,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26723,7 +27144,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26754,7 +27176,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26785,7 +27208,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26816,7 +27240,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26847,7 +27272,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26942,7 +27368,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26974,7 +27401,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27020,7 +27448,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27052,7 +27481,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27084,7 +27514,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27116,7 +27547,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27162,7 +27594,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27208,7 +27641,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27239,7 +27673,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27270,7 +27705,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27301,7 +27737,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27332,7 +27769,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27455,7 +27893,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27487,7 +27926,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27583,7 +28023,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27615,7 +28056,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27711,7 +28153,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27743,7 +28186,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27926,7 +28370,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27958,7 +28403,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27990,7 +28436,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28022,7 +28469,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28118,7 +28566,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28150,7 +28599,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28182,7 +28632,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28214,7 +28665,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28310,7 +28762,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28342,7 +28795,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28374,7 +28828,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28406,7 +28861,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28632,7 +29088,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28664,7 +29121,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28710,7 +29168,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28742,7 +29201,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28775,7 +29235,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28871,7 +29332,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28903,7 +29365,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28949,7 +29412,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28981,7 +29445,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29014,7 +29479,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -29110,7 +29576,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29142,7 +29609,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29188,7 +29656,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29220,7 +29689,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29253,7 +29723,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30665,7 +31136,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30699,7 +31171,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30732,7 +31205,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30766,7 +31240,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30799,7 +31274,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30832,7 +31308,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30865,7 +31342,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30898,7 +31376,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30930,7 +31409,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30961,7 +31441,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30992,7 +31473,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31024,7 +31506,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31056,7 +31539,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31088,7 +31572,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31120,7 +31605,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31152,7 +31638,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31185,7 +31672,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31217,7 +31705,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31249,7 +31738,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31282,7 +31772,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31314,7 +31805,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31346,7 +31838,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31379,7 +31872,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31411,7 +31905,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31443,7 +31938,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31475,7 +31971,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31520,7 +32017,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31552,7 +32050,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31583,7 +32082,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31614,7 +32114,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31645,7 +32146,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31752,7 +32254,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31786,7 +32289,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31819,7 +32323,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31853,7 +32358,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31886,7 +32392,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31919,7 +32426,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31952,7 +32460,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31985,7 +32494,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32017,7 +32527,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32048,7 +32559,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32079,7 +32591,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32111,7 +32624,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32143,7 +32657,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32175,7 +32690,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32207,7 +32723,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32239,7 +32756,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32272,7 +32790,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32304,7 +32823,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32336,7 +32856,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32369,7 +32890,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32401,7 +32923,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32433,7 +32956,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32466,7 +32990,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32498,7 +33023,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32530,7 +33056,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32562,7 +33089,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32607,7 +33135,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32639,7 +33168,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32670,7 +33200,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32701,7 +33232,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32732,7 +33264,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32839,7 +33372,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32873,7 +33407,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32906,7 +33441,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32940,7 +33476,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32973,7 +33510,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33006,7 +33544,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33039,7 +33578,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33072,7 +33612,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33104,7 +33645,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33135,7 +33677,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33166,7 +33709,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33198,7 +33742,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33230,7 +33775,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33262,7 +33808,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33294,7 +33841,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33326,7 +33874,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33359,7 +33908,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33391,7 +33941,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33423,7 +33974,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33456,7 +34008,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33488,7 +34041,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33520,7 +34074,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33553,7 +34108,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33585,7 +34141,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33617,7 +34174,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33649,7 +34207,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33694,7 +34253,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33726,7 +34286,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33757,7 +34318,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33788,7 +34350,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33819,7 +34382,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34056,7 +34620,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34088,7 +34653,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34134,7 +34700,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34166,7 +34733,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34198,7 +34766,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34230,7 +34799,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34276,7 +34846,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34322,7 +34893,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34353,7 +34925,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34384,7 +34957,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34415,7 +34989,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34446,7 +35021,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34541,7 +35117,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34573,7 +35150,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34619,7 +35197,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34651,7 +35230,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34683,7 +35263,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34715,7 +35296,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34761,7 +35343,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34807,7 +35390,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34838,7 +35422,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34869,7 +35454,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34900,7 +35486,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34931,7 +35518,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35026,7 +35614,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35058,7 +35647,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35104,7 +35694,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35136,7 +35727,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35168,7 +35760,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35200,7 +35793,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35246,7 +35840,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35292,7 +35887,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35323,7 +35919,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35354,7 +35951,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35385,7 +35983,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35416,7 +36015,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35540,7 +36140,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35572,7 +36173,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35603,7 +36205,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35634,7 +36237,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35665,7 +36269,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35697,7 +36302,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35743,7 +36349,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35774,7 +36381,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35805,7 +36413,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35836,7 +36445,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35957,7 +36567,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35989,7 +36600,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36020,7 +36632,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36051,7 +36664,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36082,7 +36696,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36114,7 +36729,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36160,7 +36776,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36191,7 +36808,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36222,7 +36840,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36253,7 +36872,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36374,7 +36994,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36406,7 +37027,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36437,7 +37059,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36468,7 +37091,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36499,7 +37123,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36531,7 +37156,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36577,7 +37203,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36608,7 +37235,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36639,7 +37267,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36670,7 +37299,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36804,7 +37434,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36836,7 +37467,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36882,7 +37514,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36914,7 +37547,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36946,7 +37580,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36978,7 +37613,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37010,7 +37646,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37056,7 +37693,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37087,7 +37725,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37118,7 +37757,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37149,7 +37789,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37180,7 +37821,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37275,7 +37917,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37307,7 +37950,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37353,7 +37997,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37385,7 +38030,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37417,7 +38063,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37449,7 +38096,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37481,7 +38129,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37527,7 +38176,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37558,7 +38208,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37589,7 +38240,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37620,7 +38272,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37651,7 +38304,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37746,7 +38400,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37778,7 +38433,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37824,7 +38480,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37856,7 +38513,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37888,7 +38546,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37920,7 +38579,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37952,7 +38612,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37998,7 +38659,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -38029,7 +38691,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -38060,7 +38723,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -38091,7 +38755,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -38122,7 +38787,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39433,7 +40099,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39465,7 +40132,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39496,7 +40164,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39527,7 +40196,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39558,7 +40228,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39590,7 +40261,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39636,7 +40308,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39667,7 +40340,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39698,7 +40372,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39729,7 +40404,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39850,7 +40526,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39882,7 +40559,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39913,7 +40591,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39944,7 +40623,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -39975,7 +40655,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -40007,7 +40688,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40053,7 +40735,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40084,7 +40767,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40115,7 +40799,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40146,7 +40831,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40267,7 +40953,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40299,7 +40986,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40330,7 +41018,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40361,7 +41050,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40392,7 +41082,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40424,7 +41115,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40470,7 +41162,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40501,7 +41194,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40532,7 +41226,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40563,7 +41258,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -40697,7 +41393,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40729,7 +41426,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40775,7 +41473,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40807,7 +41506,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40839,7 +41539,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40871,7 +41572,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40903,7 +41605,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40949,7 +41652,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40980,7 +41684,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41011,7 +41716,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41042,7 +41748,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41073,7 +41780,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41168,7 +41876,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41200,7 +41909,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41246,7 +41956,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41278,7 +41989,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41310,7 +42022,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41342,7 +42055,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41374,7 +42088,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41420,7 +42135,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41451,7 +42167,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41482,7 +42199,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41513,7 +42231,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41544,7 +42263,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41639,7 +42359,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41671,7 +42392,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41717,7 +42439,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41749,7 +42472,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41781,7 +42505,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41813,7 +42538,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41845,7 +42571,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41891,7 +42618,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41922,7 +42650,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41953,7 +42682,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41984,7 +42714,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42015,7 +42746,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42138,7 +42870,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42170,7 +42903,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42266,7 +43000,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -42298,7 +43033,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -42394,7 +43130,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42426,7 +43163,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43832,7 +44570,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43865,7 +44604,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43897,7 +44637,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43929,7 +44670,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43961,7 +44703,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44057,7 +44800,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44090,7 +44834,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44122,7 +44867,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44154,7 +44900,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44186,7 +44933,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44282,7 +45030,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44315,7 +45064,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44347,7 +45097,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44379,7 +45130,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44411,7 +45163,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44680,7 +45433,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -44713,7 +45467,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44759,7 +45514,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44791,7 +45547,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44837,7 +45594,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44869,7 +45627,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44902,7 +45661,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44935,7 +45695,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44969,7 +45730,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45002,7 +45764,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45035,7 +45798,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45143,7 +45907,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -45176,7 +45941,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45222,7 +45988,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45254,7 +46021,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45300,7 +46068,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45332,7 +46101,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45365,7 +46135,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45398,7 +46169,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45432,7 +46204,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45465,7 +46238,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45498,7 +46272,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45606,7 +46381,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -45639,7 +46415,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -45685,7 +46462,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45717,7 +46495,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45763,7 +46542,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45795,7 +46575,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45828,7 +46609,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45861,7 +46643,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45895,7 +46678,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45928,7 +46712,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45961,7 +46746,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46083,7 +46869,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46117,7 +46904,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46150,7 +46938,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46183,7 +46972,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46216,7 +47006,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46249,7 +47040,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46282,7 +47074,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46316,7 +47109,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46349,7 +47143,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46383,7 +47178,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46416,7 +47212,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46450,7 +47247,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46483,7 +47281,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46517,7 +47316,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46550,7 +47350,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46584,7 +47385,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46617,7 +47419,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46651,7 +47454,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46684,7 +47488,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46718,7 +47523,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46751,7 +47557,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46785,7 +47592,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46818,7 +47626,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46852,7 +47661,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46885,7 +47695,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46918,7 +47729,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46950,7 +47762,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46995,7 +47808,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47102,7 +47916,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47136,7 +47951,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47169,7 +47985,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47202,7 +48019,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47235,7 +48053,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47268,7 +48087,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47301,7 +48121,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47335,7 +48156,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47368,7 +48190,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47402,7 +48225,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47435,7 +48259,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47469,7 +48294,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47502,7 +48328,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47536,7 +48363,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47569,7 +48397,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47603,7 +48432,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47636,7 +48466,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47670,7 +48501,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47703,7 +48535,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47737,7 +48570,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47770,7 +48604,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47804,7 +48639,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47837,7 +48673,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47871,7 +48708,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47904,7 +48742,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47937,7 +48776,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47969,7 +48809,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48014,7 +48855,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48121,7 +48963,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48155,7 +48998,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48188,7 +49032,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48221,7 +49066,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48254,7 +49100,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48287,7 +49134,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48320,7 +49168,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48354,7 +49203,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48387,7 +49237,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48421,7 +49272,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48454,7 +49306,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48488,7 +49341,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48521,7 +49375,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48555,7 +49410,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48588,7 +49444,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48622,7 +49479,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48655,7 +49513,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48689,7 +49548,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48722,7 +49582,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48756,7 +49617,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48789,7 +49651,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48823,7 +49686,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48856,7 +49720,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48890,7 +49755,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48923,7 +49789,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48956,7 +49823,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -48988,7 +49856,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49033,7 +49902,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -50644,7 +51514,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50678,7 +51549,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50711,7 +51583,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50745,7 +51618,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50778,7 +51652,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50811,7 +51686,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50844,7 +51720,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50877,7 +51754,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50909,7 +51787,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50940,7 +51819,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50971,7 +51851,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51003,7 +51884,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51049,7 +51931,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51081,7 +51964,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51127,7 +52011,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51159,7 +52044,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51192,7 +52078,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51224,7 +52111,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51256,7 +52144,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51288,7 +52177,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51320,7 +52210,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51353,7 +52244,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51385,7 +52277,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51417,7 +52310,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51450,7 +52344,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51482,7 +52377,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51514,7 +52410,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51547,7 +52444,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51579,7 +52477,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51611,7 +52510,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51643,7 +52543,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51688,7 +52589,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51720,7 +52622,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51751,7 +52654,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51782,7 +52686,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51813,7 +52718,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51920,7 +52826,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51954,7 +52861,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51987,7 +52895,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52021,7 +52930,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52054,7 +52964,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52087,7 +52998,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52120,7 +53032,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52153,7 +53066,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52185,7 +53099,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52216,7 +53131,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52247,7 +53163,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52279,7 +53196,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52325,7 +53243,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52357,7 +53276,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52403,7 +53323,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52435,7 +53356,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52468,7 +53390,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52500,7 +53423,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52532,7 +53456,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52564,7 +53489,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52596,7 +53522,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52629,7 +53556,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52661,7 +53589,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52693,7 +53622,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52726,7 +53656,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52758,7 +53689,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52790,7 +53722,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52823,7 +53756,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52855,7 +53789,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52887,7 +53822,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52919,7 +53855,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52964,7 +53901,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52996,7 +53934,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53027,7 +53966,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53058,7 +53998,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53089,7 +54030,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53196,7 +54138,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53230,7 +54173,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53263,7 +54207,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53297,7 +54242,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53330,7 +54276,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53363,7 +54310,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53396,7 +54344,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53429,7 +54378,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53461,7 +54411,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53492,7 +54443,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53523,7 +54475,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -53555,7 +54508,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53601,7 +54555,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53633,7 +54588,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53679,7 +54635,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53711,7 +54668,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -53744,7 +54702,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53776,7 +54735,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53808,7 +54768,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53840,7 +54801,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53872,7 +54834,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53905,7 +54868,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53937,7 +54901,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53969,7 +54934,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54002,7 +54968,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54034,7 +55001,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54066,7 +55034,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54099,7 +55068,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54131,7 +55101,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54163,7 +55134,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54195,7 +55167,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54240,7 +55213,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54272,7 +55246,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54303,7 +55278,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54334,7 +55310,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54365,7 +55342,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54602,7 +55580,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54634,7 +55613,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54680,7 +55660,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54712,7 +55693,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54744,7 +55726,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54776,7 +55759,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54822,7 +55806,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54868,7 +55853,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54899,7 +55885,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54930,7 +55917,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54961,7 +55949,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54992,7 +55981,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55087,7 +56077,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -55119,7 +56110,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -55165,7 +56157,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -55197,7 +56190,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -55229,7 +56223,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -55261,7 +56256,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -55307,7 +56303,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55353,7 +56350,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55384,7 +56382,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55415,7 +56414,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55446,7 +56446,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55477,7 +56478,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55572,7 +56574,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55604,7 +56607,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55650,7 +56654,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55682,7 +56687,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55714,7 +56720,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55746,7 +56753,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55792,7 +56800,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55838,7 +56847,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55869,7 +56879,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55900,7 +56911,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55931,7 +56943,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55962,7 +56975,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56086,7 +57100,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56118,7 +57133,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56149,7 +57165,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56180,7 +57197,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56211,7 +57229,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56243,7 +57262,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56289,7 +57309,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56320,7 +57341,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56351,7 +57373,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56382,7 +57405,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56503,7 +57527,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56535,7 +57560,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56566,7 +57592,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56597,7 +57624,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56628,7 +57656,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56660,7 +57689,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56706,7 +57736,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56737,7 +57768,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56768,7 +57800,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56799,7 +57832,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56920,7 +57954,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56952,7 +57987,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -56983,7 +58019,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -57014,7 +58051,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -57045,7 +58083,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -57077,7 +58116,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57123,7 +58163,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57154,7 +58195,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57185,7 +58227,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57216,7 +58259,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57350,7 +58394,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57382,7 +58427,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57428,7 +58474,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57460,7 +58507,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57492,7 +58540,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57524,7 +58573,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57570,7 +58620,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57616,7 +58667,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57647,7 +58699,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57678,7 +58731,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57709,7 +58763,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57740,7 +58795,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57835,7 +58891,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57867,7 +58924,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57913,7 +58971,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57945,7 +59004,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57977,7 +59037,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -58009,7 +59070,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -58055,7 +59117,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58101,7 +59164,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58132,7 +59196,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58163,7 +59228,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58194,7 +59260,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58225,7 +59292,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58320,7 +59388,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58352,7 +59421,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58398,7 +59468,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58430,7 +59501,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58462,7 +59534,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58494,7 +59567,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58540,7 +59614,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58586,7 +59661,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58617,7 +59693,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58648,7 +59725,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58679,7 +59757,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58710,7 +59789,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -60057,7 +61137,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60089,7 +61170,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60120,7 +61202,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60151,7 +61234,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60182,7 +61266,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60214,7 +61299,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -60260,7 +61346,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -60291,7 +61378,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -60322,7 +61410,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -60353,7 +61442,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -60474,7 +61564,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60506,7 +61597,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60537,7 +61629,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60568,7 +61661,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60599,7 +61693,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60631,7 +61726,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60677,7 +61773,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60708,7 +61805,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60739,7 +61837,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60770,7 +61869,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60891,7 +61991,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60923,7 +62024,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60954,7 +62056,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -60985,7 +62088,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -61016,7 +62120,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -61048,7 +62153,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -61094,7 +62200,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -61125,7 +62232,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -61156,7 +62264,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -61187,7 +62296,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -61321,7 +62431,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61353,7 +62464,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61399,7 +62511,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61431,7 +62544,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61463,7 +62577,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61495,7 +62610,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -61541,7 +62657,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61587,7 +62704,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61618,7 +62736,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61649,7 +62768,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61680,7 +62800,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61711,7 +62832,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61806,7 +62928,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61838,7 +62961,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61884,7 +63008,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61916,7 +63041,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61948,7 +63074,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61980,7 +63107,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62026,7 +63154,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62072,7 +63201,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62103,7 +63233,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62134,7 +63265,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62165,7 +63297,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62196,7 +63329,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62291,7 +63425,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62323,7 +63458,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62369,7 +63505,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62401,7 +63538,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62433,7 +63571,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62465,7 +63604,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62511,7 +63651,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62557,7 +63698,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62588,7 +63730,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62619,7 +63762,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62650,7 +63794,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62681,7 +63826,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62804,7 +63950,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62836,7 +63983,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62932,7 +64080,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62964,7 +64113,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63060,7 +64210,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63092,7 +64243,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63275,7 +64427,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63308,7 +64461,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63340,7 +64494,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -63372,7 +64527,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -63404,7 +64560,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -63500,7 +64657,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63533,7 +64691,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63565,7 +64724,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63597,7 +64757,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63629,7 +64790,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63725,7 +64887,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63758,7 +64921,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -63790,7 +64954,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63822,7 +64987,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63854,7 +65020,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -64080,7 +65247,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64112,7 +65280,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64158,7 +65327,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64190,7 +65360,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64223,7 +65394,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64319,7 +65491,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64351,7 +65524,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64397,7 +65571,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64429,7 +65604,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64462,7 +65638,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64558,7 +65735,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -64590,7 +65768,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -64636,7 +65815,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -64668,7 +65848,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -64701,7 +65882,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -64974,6 +66156,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c9e6e3d59d][OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c9e6e3d59d][OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests].json index 27c588074b3..cf748ae6fa0 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c9e6e3d59d][OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[c9e6e3d59d][OT2_X_v4_P300M_P20S_MM_TC1_TM_e2eTests].json @@ -7055,7 +7055,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7183,6 +7184,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "NN MM", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cb5adc3d23][OT2_S_v6_P20S_P300M_TransferReTransferLiquid].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cb5adc3d23][OT2_S_v6_P20S_P300M_TransferReTransferLiquid].json index 7f3534fa0f5..ac74dbfc2a4 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cb5adc3d23][OT2_S_v6_P20S_P300M_TransferReTransferLiquid].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cb5adc3d23][OT2_S_v6_P20S_P300M_TransferReTransferLiquid].json @@ -4199,7 +4199,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4232,7 +4233,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4329,7 +4331,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4362,7 +4365,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -4459,7 +4463,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4492,7 +4497,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -4589,7 +4595,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4622,7 +4629,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -4719,7 +4727,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4752,7 +4761,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -4849,7 +4859,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4882,7 +4893,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -4979,7 +4991,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5012,7 +5025,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -5109,7 +5123,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5142,7 +5157,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -5239,7 +5255,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5272,7 +5289,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5369,7 +5387,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5402,7 +5421,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -5499,7 +5519,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5532,7 +5553,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -5629,7 +5651,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5662,7 +5685,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -5759,7 +5783,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5792,7 +5817,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -5889,7 +5915,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -5922,7 +5949,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -6019,7 +6047,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -6052,7 +6081,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -6149,7 +6179,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -6182,7 +6213,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -6279,7 +6311,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6312,7 +6345,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6409,7 +6443,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6442,7 +6477,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -6539,7 +6575,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6572,7 +6609,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -6669,7 +6707,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6702,7 +6741,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -6799,7 +6839,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6832,7 +6873,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -6929,7 +6971,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6962,7 +7005,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -7059,7 +7103,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -7092,7 +7137,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -7189,7 +7235,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -7222,7 +7269,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -7319,7 +7367,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -7352,7 +7401,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -7449,7 +7499,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -7482,7 +7533,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -7579,7 +7631,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -7612,7 +7665,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C4" }, @@ -7709,7 +7763,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -7742,7 +7797,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D4" }, @@ -7839,7 +7895,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -7872,7 +7929,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "E4" }, @@ -7969,7 +8027,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -8002,7 +8061,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "F4" }, @@ -8099,7 +8159,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -8132,7 +8193,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "G4" }, @@ -8229,7 +8291,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -8262,7 +8325,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "H4" }, @@ -8359,7 +8423,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -8392,7 +8457,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -8489,7 +8555,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -8522,7 +8589,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -8619,7 +8687,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -8652,7 +8721,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -8749,7 +8819,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -8782,7 +8853,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -8879,7 +8951,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -8912,7 +8985,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -9009,7 +9083,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -9042,7 +9117,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -9139,7 +9215,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -9172,7 +9249,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -9269,7 +9347,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -9302,7 +9381,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -9399,7 +9479,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9432,7 +9513,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -9529,7 +9611,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9562,7 +9645,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -9659,7 +9743,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9692,7 +9777,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -9789,7 +9875,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9822,7 +9909,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -9919,7 +10007,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -9952,7 +10041,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "E6" }, @@ -10049,7 +10139,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -10082,7 +10173,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "F6" }, @@ -10179,7 +10271,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -10212,7 +10305,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "G6" }, @@ -10309,7 +10403,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -10342,7 +10437,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "H6" }, @@ -10439,7 +10535,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -10472,7 +10569,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -10569,7 +10667,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -10602,7 +10701,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -10699,7 +10799,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -10732,7 +10833,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -10829,7 +10931,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -10862,7 +10965,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -10959,7 +11063,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -10992,7 +11097,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -11089,7 +11195,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -11122,7 +11229,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -11219,7 +11327,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -11252,7 +11361,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -11349,7 +11459,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -11382,7 +11493,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -11479,7 +11591,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11512,7 +11625,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11609,7 +11723,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -11642,7 +11757,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11739,7 +11855,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -11772,7 +11889,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11869,7 +11987,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -11902,7 +12021,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11999,7 +12119,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -12032,7 +12153,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12129,7 +12251,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12162,7 +12285,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12259,7 +12383,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -12292,7 +12417,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12389,7 +12515,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -12422,7 +12549,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12519,7 +12647,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -12552,7 +12681,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12666,6 +12796,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cc26c104b4][Flex_S_v2_20_8_None_SINGLE_HappyPath].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cc26c104b4][Flex_S_v2_20_8_None_SINGLE_HappyPath].json new file mode 100644 index 00000000000..c9afc886f56 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cc26c104b4][Flex_S_v2_20_8_None_SINGLE_HappyPath].json @@ -0,0 +1,7208 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "displayName": "Partial Tip Rack", + "loadName": "opentrons_flex_96_tiprack_50ul", + "location": { + "slotName": "B3" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 50 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_50ul", + "quirks": [], + "tipLength": 57.9, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadModule", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a3a7eed460d8d94a91747f23820a180d", + "notes": [], + "params": { + "location": { + "slotName": "B1" + }, + "model": "thermocyclerModuleV2" + }, + "result": { + "definition": { + "calibrationPoint": { + "x": 14.4, + "y": 64.93, + "z": 97.8 + }, + "compatibleWith": [], + "dimensions": { + "bareOverallHeight": 108.96, + "lidHeight": 61.7, + "overLabwareHeight": 0.0 + }, + "displayName": "Thermocycler Module GEN2", + "gripperOffsets": { + "default": { + "dropOffset": { + "x": 0.0, + "y": 0.0, + "z": 5.6 + }, + "pickUpOffset": { + "x": 0.0, + "y": 0.0, + "z": 4.6 + } + } + }, + "labwareOffset": { + "x": 0.0, + "y": 68.8, + "z": 108.96 + }, + "model": "thermocyclerModuleV2", + "moduleType": "thermocyclerModuleType", + "otSharedSchema": "module/schemas/2", + "quirks": [], + "slotTransforms": { + "ot3_standard": { + "B1": { + "cornerOffsetFromSlot": [ + [ + -98, + 0, + 0, + 1 + ], + [ + -20.005, + 0, + 0, + 1 + ], + [ + -0.84, + 0, + 0, + 1 + ], + [ + 0, + 0, + 0, + 1 + ] + ], + "labwareOffset": [ + [ + -98, + 0, + 0, + 1 + ], + [ + -20.005, + 0, + 0, + 1 + ], + [ + -0.84, + 0, + 0, + 1 + ], + [ + 0, + 0, + 0, + 1 + ] + ] + } + } + } + }, + "model": "thermocyclerModuleV2", + "moduleId": "UUID", + "serialNumber": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8ea59a68b923c7225add6547ba691d34", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "right", + "pipetteName": "p50_multi_flex", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c849833be6f44b56042aca98984638af", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e427a8e363e7924e22360de365a9ca3b", + "notes": [], + "params": { + "displayName": "B2 Source Labware", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "404adaf9f8cf5b42b31bf9f81dfe8746", + "notes": [], + "params": { + "displayName": "C2 Destination Labware", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "58fef1af51dc7228051c34fbcd75a2ff", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 342.38, + "y": 225.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e3ebcb4a881efb6bf7400eed606f2366", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 288.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fce539d97b7a3daa7a6d25257bce6f35", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 205.38, + "y": 288.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bce9d3bf890420236c3763d53a3805b3", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 20.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 20.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f0558c62484b029a0cbc2cb0f4249116", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 509.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2492fd04b2eb14d18bf0d7247de1df35", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a9b969b596943b43f87567d8ef58cf7c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 342.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1e5fd4aec2ffe66cc8554c40cefe4f41", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 223.38, + "y": 288.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "681e921d03210756c142949d294b2bd8", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 223.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6ff28a05904431af4a9c45926cb27321", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 359.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f2d120dec0962e6d72b1a329ff609a73", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6ea07ad2b65da3dbae05915e57dfb588", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 342.38, + "y": 243.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "64b038168ab3597a946133a748d2d5ec", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 15.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 288.24, + "z": 1.92 + }, + "volume": 15.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "660882c6c1b31a6e0d6712a369f435fb", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 5.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 5.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d9be788c4a914e3511492dcebb8fee55", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 5.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 241.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 5.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3c38e87d3897b4ccb92da8c0ba2aa41f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 434.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2431973ade142063fce9392c2facd38d", + "notes": [], + "params": { + "flowRate": 57.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "21e3f5172069f1420441da46936708df", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 509.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f4cb7fe6500787b8b275b4af99cae20a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a336a2fdf18991ff8b5e942731c6a3eb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 342.38, + "y": 252.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "touchTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b3932aef649a54f6dff065787b29d837", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "radius": 1.0, + "speed": 60.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -1.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 178.38, + "y": 279.24, + "z": 14.7 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "46e774a80f21bb16f781dc54a2aefd04", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 359.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d72aefe7ebc858059cd87ea6fc40bce5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ba7e242940add23f6ba76c467860a0cf", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 342.38, + "y": 261.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e4cd728baac276ab40a909864c6b7385", + "notes": [], + "params": { + "axes": [ + "rightPlunger", + "rightZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8aa3598a7e6baf6e15d00d9cf6e0e3a2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 509.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a9609b7c5335265267ac6bd1e223676b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e979c23f20333291739978a067171ea6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 342.38, + "y": 270.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "166fccb4a0375074923bd82de44f5bbf", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "277df9b98cea0310cacb9d0aa8e762d4", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e66935905062a60ac0e21803624f6152", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5f8e04268c990c0b6d19533b2acfe6ba", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "395de29f563bdb0c5dbf6c99a2e1c592", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "337c0d0731a240af2ec03e30c1bfe6ea", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "54003221f4dc68e69d9ad279efe90fcd", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "edf7e1a217e733e34c1106243ccc52ab", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5f2b86bb262a8981e3c8688f8be2e098", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f07ca1665d9f532e3e2bb1ed78896ed7", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "48901ec2b8fd08e20f676527032b760d", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "278dc462230e546d9cff4d68184c0ee8", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c57ecaf3c06dad7de6d21089c8471dd3", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1c40558e2060144208a1d6ebdc915eb6", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ce3f13e5bef1af4b923f316c1919847b", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3bcf1b2f20a083421f967da66a0d7899", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6fd4cb9b2bb17f89720633d458eecdba", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dd0bffd334b8f32aa4f082f145bd79c6", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3638cb0e7ed5dabcb954607390536db5", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "81e57845e15e2f1e087bbb13a2c9e96a", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7534004fa7b77da76840085c6d93efc2", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e61635e5d9ccc963e5c97e5210ef3b43", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "46c2a34323000fb4208b0ada66a7b54c", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "prepareToAspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b5ac508ea268740a7c96b385cbbdc1f1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5d30f15f4da6445dcff27681726f4562", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3cab660778769fc443fa7b9c948cd90f", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "86a0571c1d17be65c4f069e6de2b0da8", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0d7f59e88cc75d805484e710016c5fcb", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 359.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7bd22e430622757c2ea7395fea0abf68", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "edae468719a1aeee4f0695f0fb94d999", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b125c37b00c9a6463e85ef31f4420970", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "071b1a4cec94d9884920b0ae300164c6", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 288.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "088aa2286b82ad9c6dceb964bc8cce9b", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 205.38, + "y": 288.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d2d504a6a52593062645e6043155c2a5", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 20.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 196.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 20.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "19885230f7ff209e00b955b529301431", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 509.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "30c6447822967532d34d3124ff02d8e6", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3f9fee68cf5a8bea26f6398d96f90287", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 342.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4eb274e055ece3e6fb7bde1d3ca3552d", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 223.38, + "y": 288.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b537b35b01dc7d99e0ccf43c0a5321e1", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 223.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "04460c954e84932b5d47955e312dea21", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 359.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ca054494dbf04a512764c721074a4d0d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a6fbf79dd23b7a077bc6b6200b67b1c1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 351.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7ea836949df13fe70f57be392c6b08ef", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 15.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 288.24, + "z": 1.92 + }, + "volume": 15.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bac51a5fe8d75761820897e3cce18da2", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 5.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 232.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 5.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7ead5f2f2abd9c503078cac4da336e0b", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 5.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 241.38, + "y": 181.24, + "z": 1.92 + }, + "volume": 5.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a60adc4fe71bc85c1379bcb00492211c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 434.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d275595eacc17f85ec9cfde66be45122", + "notes": [], + "params": { + "flowRate": 57.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "df53aeedb76a5476c510252fb2f5a5f7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 509.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3d470209a5c7399dfc41b69da3f94e9b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c866a9e418db87af1a1da159586d53cc", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B2" + }, + "result": { + "position": { + "x": 351.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "touchTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "730da8f01d4338f51cef8ddb645b1c92", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "radius": 1.0, + "speed": 60.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -1.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 178.38, + "y": 279.24, + "z": 14.7 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fb39bb5dab7645090df3f770f6ff5eaa", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 359.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6de02322b063f53fc17822a5e3a165dd", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c2afe8249d76bfd3f365ce607cb7da55", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C2" + }, + "result": { + "position": { + "x": 351.38, + "y": 270.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1db58030512ddeb5528ab92338a1987a", + "notes": [], + "params": { + "axes": [ + "rightPlunger", + "rightZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f67aee2a0e4af89e3b516feba1aed132", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 509.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3d15c69f475c6a8bd5b3aa7ca4ddefe3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "370bea73c760ab4217fe1bc8fa5a3d79", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D2" + }, + "result": { + "position": { + "x": 351.38, + "y": 261.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 48.62, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a82953f0bc3646436c60e8b171aefa74", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0a9b50542395f231fbfa815117b361b8", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e65b3f1d497c2bb139199aec737df17e", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2b98d9ff67ef30514a2533adf6ff6879", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b89d395e96cd4eeee44be438cecd43d1", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "38858e6b33348a2f85612110c8e4f579", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0267b163687393bfef244066ce3907f1", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "171e60766b79993909e9ac8fbbcee7ce", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a39565525131dcf374765728a5d26afa", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "debb970a9560e6d440c0da22b0ecc64e", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3660aaa9cba97f02464697b91b66a366", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "59b9a2a3807c22f2ab27c0be35e584bc", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1d8b25b3f0c0f46e38121049d5e56bae", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9cbb25d683b57c7182f77bc36f7a4f52", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "77f621eac8fdc458aeb004ad98ebdfe7", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0bd40686919fc4a3de7e92e94a7786f2", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0cb76f19d99236435e46da44245b4d5c", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "37b2b4c92a8236f7b61f651c3f58cc0b", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "de4268df4b51eb0cb5a8e08cef5bbe39", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bbdbcfd6b38415f6170cfcd5e4038a93", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e34ac3384b12530622bc4e94daf3e978", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "52e16b89b1ab16d76b38e3e296da0a94", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a9d3f9c2811db1fc586589e3c1c6d1cb", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "prepareToAspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "aff74009e6502ca0ccceb9fd79a0dd12", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c157a882862ad34268f9db642ae7d2f6", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3de9510942adb48de0d7e022a7ce0ff0", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d44591710a9846ae9b7fe0973e1421f4", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b9183eeb2c4cd08719aad433be253e77", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 359.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ba7e34a7ba3b6bd01d4d95a0a10d3c38", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [], + "files": [ + { + "name": "Flex_S_v2_20_8_None_SINGLE_HappyPath.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_50ul/1", + "displayName": "Partial Tip Rack", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_50ul", + "location": { + "slotName": "B3" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "displayName": "B2 Source Labware", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "B2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "displayName": "C2 Destination Labware", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "C2" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "Tip Rack South Clearance for the 8 Channel pipette and a SINGLE partial tip configuration.", + "protocolName": "8 Channel SINGLE Happy Path A1 or H1" + }, + "modules": [ + { + "id": "UUID", + "location": { + "slotName": "B1" + }, + "model": "thermocyclerModuleV2", + "serialNumber": "UUID" + } + ], + "pipettes": [ + { + "id": "UUID", + "mount": "right", + "pipetteName": "p50_multi_flex" + } + ], + "result": "ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cecd51c8ee][pl_ExpressPlex_96_final].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cecd51c8ee][pl_ExpressPlex_96_final].json index ace83923e02..b591039cbbb 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cecd51c8ee][pl_ExpressPlex_96_final].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[cecd51c8ee][pl_ExpressPlex_96_final].json @@ -12834,7 +12834,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12881,7 +12882,8 @@ "y": 0.0, "z": -13.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12992,7 +12994,8 @@ "y": 0.0, "z": -13.810000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13039,7 +13042,8 @@ "y": 0.0, "z": -13.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13342,6 +13346,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Index Plate color", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d0154b1493][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d0154b1493][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right].json new file mode 100644 index 00000000000..f449eff0d94 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d0154b1493][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right].json @@ -0,0 +1,1334 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_multi_flex", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "error": { + "createdAt": "TIMESTAMP", + "detail": "No entry for back left nozzle 'G12' in pipette", + "errorCode": "4007", + "errorInfo": {}, + "errorType": "IncompatibleNozzleConfiguration", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "KeyError: 'G12'", + "errorCode": "4000", + "errorInfo": { + "args": "('G12',)", + "class": "KeyError", + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/hardware_control/nozzle_manager.py\", line N, in build\n back_left_row_index, back_left_column_index = _row_col_indices_for_nozzle(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/hardware_control/nozzle_manager.py\", line N, in _row_col_indices_for_nozzle\n return _row_or_col_index_for_nozzle(rows, nozzle), _row_or_col_index_for_nozzle(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/hardware_control/nozzle_manager.py\", line N, in _row_or_col_index_for_nozzle\n raise KeyError(nozzle)\n" + }, + "errorType": "PythonException", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + }, + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], + "params": { + "configurationParams": { + "backLeftNozzle": "G12", + "frontRightNozzle": "H1", + "primaryNozzle": "H1", + "style": "QUADRANT" + }, + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "failed" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "ProtocolCommandFailedError [line 167]: Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): IncompatibleNozzleConfiguration: No entry for back left nozzle 'G12' in pipette", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "IncompatibleNozzleConfiguration: No entry for back left nozzle 'G12' in pipette", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ProtocolCommandFailedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "No entry for back left nozzle 'G12' in pipette", + "errorCode": "4007", + "errorInfo": {}, + "errorType": "IncompatibleNozzleConfiguration", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "KeyError: 'G12'", + "errorCode": "4000", + "errorInfo": { + "args": "('G12',)", + "class": "KeyError", + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/hardware_control/nozzle_manager.py\", line N, in build\n back_left_row_index, back_left_column_index = _row_col_indices_for_nozzle(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/hardware_control/nozzle_manager.py\", line N, in _row_col_indices_for_nozzle\n return _row_or_col_index_for_nozzle(rows, nozzle), _row_or_col_index_for_nozzle(\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/hardware_control/nozzle_manager.py\", line N, in _row_or_col_index_for_nozzle\n raise KeyError(nozzle)\n" + }, + "errorType": "PythonException", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_eight_partial_column_bottom_right.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Invalid tip configs that should error" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_multi_flex" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d14738bdfe][pl_Zymo_Magbead_DNA_Cells_Flex_multi].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d14738bdfe][pl_Zymo_Magbead_DNA_Cells_Flex_multi].json index b897743dc90..a4d46be3d94 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d14738bdfe][pl_Zymo_Magbead_DNA_Cells_Flex_multi].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d14738bdfe][pl_Zymo_Magbead_DNA_Cells_Flex_multi].json @@ -9149,7 +9149,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9182,7 +9183,8 @@ "y": 0.0, "z": -18.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9215,7 +9217,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9248,7 +9251,8 @@ "y": 0.0, "z": -18.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9281,7 +9285,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9314,7 +9319,8 @@ "y": 0.0, "z": -18.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9347,7 +9353,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9379,7 +9386,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9411,7 +9419,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9444,7 +9453,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9476,7 +9486,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9508,7 +9519,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9541,7 +9553,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9574,7 +9587,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9607,7 +9621,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9640,7 +9655,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9673,7 +9689,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9706,7 +9723,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9739,7 +9757,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9772,7 +9791,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9805,7 +9825,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9838,7 +9859,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9871,7 +9893,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9904,7 +9927,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9937,7 +9961,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9970,7 +9995,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10003,7 +10029,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10036,7 +10063,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10069,7 +10097,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10102,7 +10131,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10135,7 +10165,8 @@ "y": 0.0, "z": -8.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10168,7 +10199,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10201,7 +10233,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10369,7 +10402,8 @@ "y": 0.0, "z": 5.0000000000000036 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10401,7 +10435,8 @@ "y": 2.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10434,7 +10469,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10467,7 +10503,8 @@ "y": -2.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10500,7 +10537,8 @@ "y": 2.0, "z": -23.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10533,7 +10571,8 @@ "y": 2.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10566,7 +10605,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10599,7 +10639,8 @@ "y": -2.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10632,7 +10673,8 @@ "y": 2.0, "z": -23.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10665,7 +10707,8 @@ "y": 2.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10698,7 +10741,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10731,7 +10775,8 @@ "y": -2.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10764,7 +10809,8 @@ "y": 2.0, "z": -23.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10797,7 +10843,8 @@ "y": 2.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10830,7 +10877,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10863,7 +10911,8 @@ "y": -2.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10896,7 +10945,8 @@ "y": 2.0, "z": -23.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10929,7 +10979,8 @@ "y": 2.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10962,7 +11013,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10995,7 +11047,8 @@ "y": -2.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11028,7 +11081,8 @@ "y": 2.0, "z": -23.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11061,7 +11115,8 @@ "y": 2.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11094,7 +11149,8 @@ "y": 2.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11127,7 +11183,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11159,7 +11216,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11191,7 +11249,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11224,7 +11283,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11256,7 +11316,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11288,7 +11349,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11320,7 +11382,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11352,7 +11415,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11385,7 +11449,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11418,7 +11483,8 @@ "y": -2.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11451,7 +11517,8 @@ "y": 2.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11484,7 +11551,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11517,7 +11585,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11550,7 +11619,8 @@ "y": -2.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11583,7 +11653,8 @@ "y": 2.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11616,7 +11687,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11649,7 +11721,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11682,7 +11755,8 @@ "y": -2.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11715,7 +11789,8 @@ "y": 2.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11748,7 +11823,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11781,7 +11857,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11814,7 +11891,8 @@ "y": -2.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11847,7 +11925,8 @@ "y": 2.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11880,7 +11959,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11913,7 +11993,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11946,7 +12027,8 @@ "y": -2.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11979,7 +12061,8 @@ "y": 2.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12012,7 +12095,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12045,7 +12129,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12078,7 +12163,8 @@ "y": -2.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12111,7 +12197,8 @@ "y": 2.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12144,7 +12231,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12177,7 +12265,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12210,7 +12299,8 @@ "y": -2.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12243,7 +12333,8 @@ "y": 2.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12276,7 +12367,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12309,7 +12401,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12342,7 +12435,8 @@ "y": -2.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12375,7 +12469,8 @@ "y": 2.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12408,7 +12503,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12441,7 +12537,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12473,7 +12570,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12504,7 +12602,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12536,7 +12635,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12842,7 +12942,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12874,7 +12975,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12906,7 +13008,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12938,7 +13041,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12971,7 +13075,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13003,7 +13108,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13034,7 +13140,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13066,7 +13173,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13225,7 +13333,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -13257,7 +13366,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -13289,7 +13399,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -13322,7 +13433,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13354,7 +13466,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13386,7 +13499,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13418,7 +13532,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13450,7 +13565,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13483,7 +13599,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13516,7 +13633,8 @@ "y": -2.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13549,7 +13667,8 @@ "y": 2.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13582,7 +13701,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13615,7 +13735,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13648,7 +13769,8 @@ "y": -2.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13681,7 +13803,8 @@ "y": 2.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13714,7 +13837,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13747,7 +13871,8 @@ "y": 1.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13780,7 +13905,8 @@ "y": -2.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13813,7 +13939,8 @@ "y": 2.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13846,7 +13973,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13879,7 +14007,8 @@ "y": 2.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14185,7 +14314,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14217,7 +14347,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14249,7 +14380,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14281,7 +14413,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14314,7 +14447,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14346,7 +14480,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14377,7 +14512,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14409,7 +14545,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14568,7 +14705,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -14600,7 +14738,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -14632,7 +14771,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -14665,7 +14805,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14940,7 +15081,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14972,7 +15114,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15004,7 +15147,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15036,7 +15180,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15069,7 +15214,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15101,7 +15247,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15132,7 +15279,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15164,7 +15312,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15323,7 +15472,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15355,7 +15505,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15387,7 +15538,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15420,7 +15572,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15695,7 +15848,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15727,7 +15881,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15759,7 +15914,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15791,7 +15947,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15824,7 +15981,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15856,7 +16014,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15887,7 +16046,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15919,7 +16079,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16078,7 +16239,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16110,7 +16272,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16142,7 +16305,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -16175,7 +16339,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16450,7 +16615,8 @@ "y": 0.0, "z": -19.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16482,7 +16648,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16514,7 +16681,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16546,7 +16714,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16579,7 +16748,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16611,7 +16781,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16642,7 +16813,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16674,7 +16846,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17118,7 +17291,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -17150,7 +17324,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -17182,7 +17357,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -17215,7 +17391,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17478,7 +17655,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17510,7 +17688,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17542,7 +17721,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17575,7 +17755,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17607,7 +17788,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17638,7 +17820,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17670,7 +17853,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17848,6 +18032,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Samples", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d29d74d7fb][pl_QIASeq_FX_48x_v8].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d29d74d7fb][pl_QIASeq_FX_48x_v8].json index 9951bcf11da..2f0c52a853f 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d29d74d7fb][pl_QIASeq_FX_48x_v8].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d29d74d7fb][pl_QIASeq_FX_48x_v8].json @@ -7651,7 +7651,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7685,7 +7686,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7718,7 +7720,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7751,7 +7754,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7784,7 +7788,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7817,7 +7822,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7850,7 +7856,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7883,7 +7890,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7917,7 +7925,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7950,7 +7959,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7984,7 +7994,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8017,7 +8028,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8050,7 +8062,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8082,7 +8095,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8127,7 +8141,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8234,7 +8249,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8268,7 +8284,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8301,7 +8318,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8334,7 +8352,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8367,7 +8386,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8400,7 +8420,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8433,7 +8454,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8466,7 +8488,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8500,7 +8523,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8533,7 +8557,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8567,7 +8592,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8600,7 +8626,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8633,7 +8660,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8665,7 +8693,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8710,7 +8739,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8817,7 +8847,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8851,7 +8882,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8884,7 +8916,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8917,7 +8950,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8950,7 +8984,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8983,7 +9018,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9016,7 +9052,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9049,7 +9086,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9083,7 +9121,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9116,7 +9155,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9150,7 +9190,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9183,7 +9224,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9216,7 +9258,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9248,7 +9291,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9293,7 +9337,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -9541,7 +9586,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -9574,7 +9620,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -9607,7 +9654,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9640,7 +9688,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9674,7 +9723,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9707,7 +9757,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9741,7 +9792,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9774,7 +9826,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9807,7 +9860,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9839,7 +9893,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9884,7 +9939,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9991,7 +10047,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -10024,7 +10081,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -10057,7 +10115,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10090,7 +10149,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10124,7 +10184,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10157,7 +10218,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10191,7 +10253,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10224,7 +10287,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10257,7 +10321,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10289,7 +10354,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10334,7 +10400,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10441,7 +10508,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -10474,7 +10542,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -10507,7 +10576,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10540,7 +10610,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10574,7 +10645,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10607,7 +10679,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10641,7 +10714,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10674,7 +10748,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10707,7 +10782,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10739,7 +10815,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10784,7 +10861,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12092,7 +12170,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12126,7 +12205,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12159,7 +12239,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12192,7 +12273,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12225,7 +12307,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12258,7 +12341,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12290,7 +12374,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12336,7 +12421,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12368,7 +12454,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12400,7 +12487,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12434,7 +12522,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12467,7 +12556,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12501,7 +12591,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12534,7 +12625,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12568,7 +12660,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12601,7 +12694,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12635,7 +12729,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12668,7 +12763,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12702,7 +12798,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12735,7 +12832,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12769,7 +12867,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12802,7 +12901,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12836,7 +12936,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12869,7 +12970,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12903,7 +13005,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12936,7 +13039,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12970,7 +13074,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13003,7 +13108,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13036,7 +13142,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13068,7 +13175,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13113,7 +13221,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13220,7 +13329,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13254,7 +13364,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13287,7 +13398,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13320,7 +13432,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13353,7 +13466,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13386,7 +13500,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13418,7 +13533,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13464,7 +13580,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13496,7 +13613,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13528,7 +13646,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13562,7 +13681,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13595,7 +13715,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13629,7 +13750,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13662,7 +13784,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13696,7 +13819,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13729,7 +13853,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13763,7 +13888,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13796,7 +13922,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13830,7 +13957,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13863,7 +13991,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13897,7 +14026,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13930,7 +14060,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13964,7 +14095,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13997,7 +14129,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14031,7 +14164,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14064,7 +14198,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14098,7 +14233,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14131,7 +14267,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14164,7 +14301,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14196,7 +14334,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14241,7 +14380,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14348,7 +14488,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14382,7 +14523,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14415,7 +14557,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14448,7 +14591,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14481,7 +14625,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14514,7 +14659,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14546,7 +14692,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14592,7 +14739,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14624,7 +14772,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14656,7 +14805,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14690,7 +14840,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14723,7 +14874,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14757,7 +14909,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14790,7 +14943,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14824,7 +14978,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14857,7 +15012,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14891,7 +15047,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14924,7 +15081,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14958,7 +15116,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14991,7 +15150,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15025,7 +15185,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15058,7 +15219,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15092,7 +15254,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15125,7 +15288,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15159,7 +15323,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15192,7 +15357,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15226,7 +15392,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15259,7 +15426,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15292,7 +15460,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15324,7 +15493,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15369,7 +15539,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15613,7 +15784,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15647,7 +15819,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15680,7 +15853,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15714,7 +15888,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15747,7 +15922,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15780,7 +15956,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15813,7 +15990,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15846,7 +16024,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15878,7 +16057,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15909,7 +16089,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15940,7 +16121,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15972,7 +16154,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16018,7 +16201,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16050,7 +16234,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16096,7 +16281,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16128,7 +16314,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16161,7 +16348,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16193,7 +16381,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16225,7 +16414,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16257,7 +16447,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16289,7 +16480,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16322,7 +16514,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16354,7 +16547,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16386,7 +16580,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16419,7 +16614,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16451,7 +16647,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16483,7 +16680,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16516,7 +16714,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16548,7 +16747,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16580,7 +16780,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16612,7 +16813,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16657,7 +16859,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16689,7 +16892,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16720,7 +16924,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16751,7 +16956,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16782,7 +16988,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16878,7 +17085,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16912,7 +17120,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16945,7 +17154,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16979,7 +17189,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17012,7 +17223,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17045,7 +17257,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17078,7 +17291,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17111,7 +17325,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17143,7 +17358,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17174,7 +17390,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17205,7 +17422,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17237,7 +17455,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17283,7 +17502,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17315,7 +17535,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17361,7 +17582,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17393,7 +17615,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17426,7 +17649,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17458,7 +17682,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17490,7 +17715,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17522,7 +17748,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17554,7 +17781,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17587,7 +17815,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17619,7 +17848,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17651,7 +17881,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17684,7 +17915,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17716,7 +17948,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17748,7 +17981,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17781,7 +18015,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17813,7 +18048,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17845,7 +18081,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17877,7 +18114,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17922,7 +18160,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17954,7 +18193,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17985,7 +18225,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18016,7 +18257,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18047,7 +18289,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18143,7 +18386,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18177,7 +18421,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18210,7 +18455,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18244,7 +18490,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18277,7 +18524,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18310,7 +18558,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18343,7 +18592,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18376,7 +18626,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18408,7 +18659,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18439,7 +18691,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18470,7 +18723,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18502,7 +18756,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18548,7 +18803,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18580,7 +18836,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18626,7 +18883,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18658,7 +18916,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18691,7 +18950,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18723,7 +18983,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18755,7 +19016,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18787,7 +19049,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18819,7 +19082,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18852,7 +19116,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18884,7 +19149,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18916,7 +19182,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18949,7 +19216,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18981,7 +19249,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19013,7 +19282,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19046,7 +19316,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19078,7 +19349,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19110,7 +19382,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19142,7 +19415,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19187,7 +19461,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19219,7 +19494,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19250,7 +19526,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19281,7 +19558,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19312,7 +19590,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19538,7 +19817,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19570,7 +19850,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19616,7 +19897,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19648,7 +19930,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19680,7 +19963,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19726,7 +20010,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19772,7 +20057,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19803,7 +20089,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19834,7 +20121,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -19929,7 +20217,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19961,7 +20250,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20007,7 +20297,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20039,7 +20330,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20071,7 +20363,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20117,7 +20410,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20163,7 +20457,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20194,7 +20489,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20225,7 +20521,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20320,7 +20617,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20352,7 +20650,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20398,7 +20697,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20430,7 +20730,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20462,7 +20763,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20508,7 +20810,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20554,7 +20857,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20585,7 +20889,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20616,7 +20921,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -20726,7 +21032,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20758,7 +21065,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20789,7 +21097,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20820,7 +21129,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20851,7 +21161,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20883,7 +21194,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20929,7 +21241,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20960,7 +21273,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20991,7 +21305,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21022,7 +21337,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21129,7 +21445,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21161,7 +21478,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21192,7 +21510,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21223,7 +21542,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21254,7 +21574,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21286,7 +21607,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21332,7 +21654,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21363,7 +21686,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21394,7 +21718,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21425,7 +21750,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21532,7 +21858,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21564,7 +21891,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21595,7 +21923,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21626,7 +21955,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21657,7 +21987,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21689,7 +22020,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21735,7 +22067,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21766,7 +22099,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21797,7 +22131,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21828,7 +22163,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23149,7 +23485,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23181,7 +23518,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23227,7 +23565,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23259,7 +23598,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23291,7 +23631,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23323,7 +23664,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23369,7 +23711,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23415,7 +23758,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23446,7 +23790,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23477,7 +23822,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23508,7 +23854,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23539,7 +23886,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23634,7 +23982,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23666,7 +24015,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23712,7 +24062,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23744,7 +24095,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23776,7 +24128,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23808,7 +24161,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23854,7 +24208,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23900,7 +24255,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23931,7 +24287,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23962,7 +24319,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23993,7 +24351,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24024,7 +24383,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24119,7 +24479,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24151,7 +24512,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24197,7 +24559,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24229,7 +24592,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24261,7 +24625,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24293,7 +24658,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24339,7 +24705,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24385,7 +24752,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24416,7 +24784,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24447,7 +24816,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24478,7 +24848,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24509,7 +24880,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -24619,7 +24991,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24651,7 +25024,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24682,7 +25056,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24713,7 +25088,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24744,7 +25120,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24776,7 +25153,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24822,7 +25200,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24853,7 +25232,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24884,7 +25264,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24915,7 +25296,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25022,7 +25404,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25054,7 +25437,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25085,7 +25469,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25116,7 +25501,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25147,7 +25533,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25179,7 +25566,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25225,7 +25613,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25256,7 +25645,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25287,7 +25677,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25318,7 +25709,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25425,7 +25817,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25457,7 +25850,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25488,7 +25882,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25519,7 +25914,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25550,7 +25946,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25582,7 +25979,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25628,7 +26026,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25659,7 +26058,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25690,7 +26090,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25721,7 +26122,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25855,7 +26257,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25887,7 +26290,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25933,7 +26337,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25965,7 +26370,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25997,7 +26403,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26029,7 +26436,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26075,7 +26483,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26121,7 +26530,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26152,7 +26562,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26183,7 +26594,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26214,7 +26626,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26245,7 +26658,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26340,7 +26754,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26372,7 +26787,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26418,7 +26834,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26450,7 +26867,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26482,7 +26900,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26514,7 +26933,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26560,7 +26980,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26606,7 +27027,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26637,7 +27059,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26668,7 +27091,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26699,7 +27123,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26730,7 +27155,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26825,7 +27251,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26857,7 +27284,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26903,7 +27331,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26935,7 +27364,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26967,7 +27397,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26999,7 +27430,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27045,7 +27477,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27091,7 +27524,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27122,7 +27556,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27153,7 +27588,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27184,7 +27620,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27215,7 +27652,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27338,7 +27776,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27370,7 +27809,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27466,7 +27906,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27498,7 +27939,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27594,7 +28036,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27626,7 +28069,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27809,7 +28253,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27841,7 +28286,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27873,7 +28319,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27906,7 +28353,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27939,7 +28387,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27972,7 +28421,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28005,7 +28455,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28037,7 +28488,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28133,7 +28585,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28165,7 +28618,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28197,7 +28651,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28230,7 +28685,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28263,7 +28719,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28296,7 +28753,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28329,7 +28787,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28361,7 +28820,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28457,7 +28917,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28489,7 +28950,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28521,7 +28983,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28554,7 +29017,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28587,7 +29051,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28620,7 +29085,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28653,7 +29119,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28685,7 +29152,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28911,7 +29379,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28943,7 +29412,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28989,7 +29459,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29021,7 +29492,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29054,7 +29526,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29150,7 +29623,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29182,7 +29656,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29228,7 +29703,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29260,7 +29736,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29293,7 +29770,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -29389,7 +29867,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29421,7 +29900,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29467,7 +29947,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29499,7 +29980,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29532,7 +30014,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30944,7 +31427,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30978,7 +31462,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31011,7 +31496,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31045,7 +31531,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31078,7 +31565,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31111,7 +31599,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31144,7 +31633,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31177,7 +31667,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31209,7 +31700,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31240,7 +31732,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31271,7 +31764,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31303,7 +31797,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31335,7 +31830,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31367,7 +31863,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31399,7 +31896,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31431,7 +31929,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31464,7 +31963,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31496,7 +31996,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31528,7 +32029,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31561,7 +32063,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31593,7 +32096,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31625,7 +32129,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31658,7 +32163,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31690,7 +32196,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31722,7 +32229,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31754,7 +32262,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31799,7 +32308,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31831,7 +32341,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31862,7 +32373,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31893,7 +32405,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31924,7 +32437,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32031,7 +32545,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32065,7 +32580,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32098,7 +32614,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32132,7 +32649,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32165,7 +32683,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32198,7 +32717,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32231,7 +32751,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32264,7 +32785,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32296,7 +32818,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32327,7 +32850,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32358,7 +32882,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32390,7 +32915,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32422,7 +32948,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32454,7 +32981,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32486,7 +33014,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32518,7 +33047,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32551,7 +33081,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32583,7 +33114,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32615,7 +33147,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32648,7 +33181,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32680,7 +33214,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32712,7 +33247,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32745,7 +33281,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32777,7 +33314,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32809,7 +33347,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32841,7 +33380,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32886,7 +33426,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32918,7 +33459,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32949,7 +33491,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -32980,7 +33523,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33011,7 +33555,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33118,7 +33663,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33152,7 +33698,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33185,7 +33732,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33219,7 +33767,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33252,7 +33801,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33285,7 +33835,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33318,7 +33869,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33351,7 +33903,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33383,7 +33936,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33414,7 +33968,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33445,7 +34000,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33477,7 +34033,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33509,7 +34066,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33541,7 +34099,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33573,7 +34132,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33605,7 +34165,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33638,7 +34199,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33670,7 +34232,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33702,7 +34265,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33735,7 +34299,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33767,7 +34332,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33799,7 +34365,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33832,7 +34399,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33864,7 +34432,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33896,7 +34465,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33928,7 +34498,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33973,7 +34544,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34005,7 +34577,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34036,7 +34609,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34067,7 +34641,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34098,7 +34673,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34335,7 +34911,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34367,7 +34944,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34413,7 +34991,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34445,7 +35024,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34477,7 +35057,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34509,7 +35090,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34555,7 +35137,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34601,7 +35184,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34632,7 +35216,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34663,7 +35248,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34694,7 +35280,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34725,7 +35312,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -34820,7 +35408,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34852,7 +35441,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34898,7 +35488,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34930,7 +35521,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34962,7 +35554,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34994,7 +35587,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35040,7 +35634,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35086,7 +35681,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35117,7 +35713,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35148,7 +35745,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35179,7 +35777,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35210,7 +35809,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35305,7 +35905,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35337,7 +35938,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35383,7 +35985,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35415,7 +36018,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35447,7 +36051,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35479,7 +36084,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35525,7 +36131,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35571,7 +36178,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35602,7 +36210,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35633,7 +36242,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35664,7 +36274,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35695,7 +36306,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -35790,7 +36402,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35822,7 +36435,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35868,7 +36482,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35900,7 +36515,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35932,7 +36548,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35964,7 +36581,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36010,7 +36628,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -36056,7 +36675,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -36087,7 +36707,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -36118,7 +36739,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -36149,7 +36771,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -36180,7 +36803,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -36290,7 +36914,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36322,7 +36947,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36353,7 +36979,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36384,7 +37011,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36415,7 +37043,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36447,7 +37076,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36493,7 +37123,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36524,7 +37155,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36555,7 +37187,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36586,7 +37219,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36693,7 +37327,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36725,7 +37360,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36756,7 +37392,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36787,7 +37424,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36818,7 +37456,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36850,7 +37489,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36896,7 +37536,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36927,7 +37568,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36958,7 +37600,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36989,7 +37632,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37096,7 +37740,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37128,7 +37773,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37159,7 +37805,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37190,7 +37837,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37221,7 +37869,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37253,7 +37902,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37299,7 +37949,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37330,7 +37981,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37361,7 +38013,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37392,7 +38045,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37526,7 +38180,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37558,7 +38213,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37604,7 +38260,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37636,7 +38293,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37668,7 +38326,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37700,7 +38359,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37732,7 +38392,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37778,7 +38439,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37809,7 +38471,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37840,7 +38503,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37871,7 +38535,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37902,7 +38567,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -37997,7 +38663,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38029,7 +38696,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38075,7 +38743,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38107,7 +38776,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38139,7 +38809,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38171,7 +38842,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38203,7 +38875,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -38249,7 +38922,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -38280,7 +38954,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -38311,7 +38986,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -38342,7 +39018,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -38373,7 +39050,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39655,7 +40333,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39687,7 +40366,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39733,7 +40413,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39765,7 +40446,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39797,7 +40479,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39829,7 +40512,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39861,7 +40545,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39907,7 +40592,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39938,7 +40624,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -39969,7 +40656,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40000,7 +40688,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40031,7 +40720,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40141,7 +40831,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40173,7 +40864,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40204,7 +40896,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40235,7 +40928,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40266,7 +40960,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40298,7 +40993,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40344,7 +41040,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40375,7 +41072,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40406,7 +41104,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40437,7 +41136,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40544,7 +41244,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40576,7 +41277,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40607,7 +41309,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40638,7 +41341,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40669,7 +41373,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40701,7 +41406,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40747,7 +41453,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40778,7 +41485,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40809,7 +41517,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40840,7 +41549,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40947,7 +41657,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40979,7 +41690,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41010,7 +41722,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41041,7 +41754,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41072,7 +41786,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41104,7 +41819,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41150,7 +41866,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41181,7 +41898,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41212,7 +41930,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41243,7 +41962,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41377,7 +42097,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41409,7 +42130,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41455,7 +42177,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41487,7 +42210,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41519,7 +42243,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41551,7 +42276,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -41583,7 +42309,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41629,7 +42356,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41660,7 +42388,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41691,7 +42420,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41722,7 +42452,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41753,7 +42484,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -41848,7 +42580,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41880,7 +42613,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41926,7 +42660,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41958,7 +42693,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41990,7 +42726,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -42022,7 +42759,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -42054,7 +42792,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42100,7 +42839,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42131,7 +42871,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42162,7 +42903,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42193,7 +42935,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42224,7 +42967,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42319,7 +43063,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42351,7 +43096,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42397,7 +43143,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42429,7 +43176,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42461,7 +43209,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42493,7 +43242,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42525,7 +43275,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42571,7 +43322,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42602,7 +43354,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42633,7 +43386,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42664,7 +43418,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42695,7 +43450,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -42818,7 +43574,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42850,7 +43607,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -42946,7 +43704,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -42978,7 +43737,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -43074,7 +43834,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -43106,7 +43867,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -44512,7 +45274,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -44545,7 +45308,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -44577,7 +45341,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44609,7 +45374,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44642,7 +45408,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44675,7 +45442,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44708,7 +45476,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44741,7 +45510,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44773,7 +45543,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -44869,7 +45640,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -44902,7 +45674,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -44934,7 +45707,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44966,7 +45740,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44999,7 +45774,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45032,7 +45808,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45065,7 +45842,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45098,7 +45876,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45130,7 +45909,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -45226,7 +46006,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45259,7 +46040,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -45291,7 +46073,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45323,7 +46106,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45356,7 +46140,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45389,7 +46174,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45422,7 +46208,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45455,7 +46242,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45487,7 +46275,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45756,7 +46545,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -45789,7 +46579,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45835,7 +46626,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45867,7 +46659,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45913,7 +46706,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45945,7 +46739,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -45978,7 +46773,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46011,7 +46807,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46045,7 +46842,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46078,7 +46876,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46111,7 +46910,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -46219,7 +47019,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -46252,7 +47053,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -46298,7 +47100,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -46330,7 +47133,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -46376,7 +47180,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -46408,7 +47213,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -46441,7 +47247,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -46474,7 +47281,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -46508,7 +47316,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -46541,7 +47350,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -46574,7 +47384,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -46682,7 +47493,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -46715,7 +47527,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -46761,7 +47574,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46793,7 +47607,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46839,7 +47654,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46871,7 +47687,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46904,7 +47721,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46937,7 +47755,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46971,7 +47790,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -47004,7 +47824,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -47037,7 +47858,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -47159,7 +47981,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47193,7 +48016,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47226,7 +48050,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47259,7 +48084,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47292,7 +48118,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47325,7 +48152,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47358,7 +48186,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47392,7 +48221,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47425,7 +48255,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47459,7 +48290,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47492,7 +48324,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47526,7 +48359,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47559,7 +48393,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47593,7 +48428,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47626,7 +48462,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47660,7 +48497,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47693,7 +48531,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47727,7 +48566,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47760,7 +48600,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47794,7 +48635,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47827,7 +48669,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47861,7 +48704,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47894,7 +48738,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47928,7 +48773,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47961,7 +48807,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -47994,7 +48841,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48026,7 +48874,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48071,7 +48920,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48178,7 +49028,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48212,7 +49063,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48245,7 +49097,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48278,7 +49131,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48311,7 +49165,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -48344,7 +49199,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48377,7 +49233,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48411,7 +49268,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48444,7 +49302,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48478,7 +49337,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48511,7 +49371,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48545,7 +49406,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48578,7 +49440,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48612,7 +49475,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48645,7 +49509,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48679,7 +49544,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48712,7 +49578,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48746,7 +49613,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48779,7 +49647,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48813,7 +49682,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48846,7 +49716,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48880,7 +49751,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48913,7 +49785,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48947,7 +49820,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48980,7 +49854,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -49013,7 +49888,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -49045,7 +49921,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -49090,7 +49967,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -49197,7 +50075,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -49231,7 +50110,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -49264,7 +50144,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -49297,7 +50178,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -49330,7 +50212,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -49363,7 +50246,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49396,7 +50280,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49430,7 +50315,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49463,7 +50349,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49497,7 +50384,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49530,7 +50418,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49564,7 +50453,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49597,7 +50487,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49631,7 +50522,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49664,7 +50556,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49698,7 +50591,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49731,7 +50625,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49765,7 +50660,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49798,7 +50694,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49832,7 +50729,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49865,7 +50763,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49899,7 +50798,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49932,7 +50832,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49966,7 +50867,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -49999,7 +50901,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -50032,7 +50935,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -50064,7 +50968,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -50109,7 +51014,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -50497,7 +51403,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50531,7 +51438,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50564,7 +51472,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50598,7 +51507,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50631,7 +51541,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50664,7 +51575,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50697,7 +51609,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50730,7 +51643,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50762,7 +51676,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50793,7 +51708,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50824,7 +51740,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -50856,7 +51773,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -50902,7 +51820,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50934,7 +51853,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -50980,7 +51900,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51012,7 +51933,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -51045,7 +51967,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51077,7 +52000,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51109,7 +52033,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51141,7 +52066,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51173,7 +52099,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51206,7 +52133,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51238,7 +52166,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51270,7 +52199,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51303,7 +52233,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51335,7 +52266,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51367,7 +52299,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51400,7 +52333,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51432,7 +52366,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51464,7 +52399,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51496,7 +52432,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51541,7 +52478,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51573,7 +52511,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51604,7 +52543,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51635,7 +52575,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51666,7 +52607,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51762,7 +52704,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51796,7 +52739,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51829,7 +52773,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51863,7 +52808,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51896,7 +52842,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51929,7 +52876,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51962,7 +52910,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -51995,7 +52944,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52027,7 +52977,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52058,7 +53009,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52089,7 +53041,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -52121,7 +53074,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52167,7 +53121,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52199,7 +53154,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52245,7 +53201,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52277,7 +53234,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52310,7 +53268,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52342,7 +53301,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52374,7 +53334,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52406,7 +53367,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52438,7 +53400,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52471,7 +53434,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52503,7 +53467,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52535,7 +53500,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52568,7 +53534,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52600,7 +53567,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52632,7 +53600,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52665,7 +53634,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52697,7 +53667,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52729,7 +53700,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52761,7 +53733,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52806,7 +53779,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52838,7 +53812,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52869,7 +53844,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52900,7 +53876,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52931,7 +53908,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -54250,7 +55228,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54284,7 +55263,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54317,7 +55297,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54351,7 +55332,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54384,7 +55366,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54417,7 +55400,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54450,7 +55434,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54483,7 +55468,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54515,7 +55501,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54546,7 +55533,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54577,7 +55565,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -54609,7 +55598,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54655,7 +55645,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -54687,7 +55678,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -54733,7 +55725,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -54765,7 +55758,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -54798,7 +55792,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54830,7 +55825,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54862,7 +55858,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54894,7 +55891,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54926,7 +55924,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54959,7 +55958,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54991,7 +55991,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55023,7 +56024,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55056,7 +56058,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55088,7 +56091,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55120,7 +56124,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55153,7 +56158,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55185,7 +56191,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55217,7 +56224,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55249,7 +56257,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55294,7 +56303,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55326,7 +56336,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55357,7 +56368,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55388,7 +56400,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55419,7 +56432,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55645,7 +56659,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55677,7 +56692,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55723,7 +56739,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55755,7 +56772,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55787,7 +56805,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55819,7 +56838,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -55865,7 +56885,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55911,7 +56932,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55942,7 +56964,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55973,7 +56996,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56004,7 +57028,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56035,7 +57060,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56130,7 +57156,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56162,7 +57189,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56208,7 +57236,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56240,7 +57269,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56272,7 +57302,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56304,7 +57335,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -56350,7 +57382,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56396,7 +57429,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56427,7 +57461,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56458,7 +57493,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56489,7 +57525,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56520,7 +57557,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56615,7 +57653,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56647,7 +57686,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56693,7 +57733,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56725,7 +57766,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56757,7 +57799,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56789,7 +57832,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56835,7 +57879,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56881,7 +57926,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56912,7 +57958,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56943,7 +57990,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56974,7 +58022,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57005,7 +58054,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57115,7 +58165,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57147,7 +58198,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57178,7 +58230,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57209,7 +58262,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57240,7 +58294,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57272,7 +58327,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57318,7 +58374,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57349,7 +58406,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57380,7 +58438,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57411,7 +58470,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57518,7 +58578,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57550,7 +58611,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57581,7 +58643,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57612,7 +58675,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57643,7 +58707,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57675,7 +58740,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57721,7 +58787,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57752,7 +58819,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57783,7 +58851,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57814,7 +58883,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57921,7 +58991,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57953,7 +59024,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -57984,7 +59056,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -58015,7 +59088,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -58046,7 +59120,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -58078,7 +59153,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58124,7 +59200,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58155,7 +59232,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58186,7 +59264,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58217,7 +59296,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58351,7 +59431,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -58383,7 +59464,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -58429,7 +59511,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -58461,7 +59544,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -58493,7 +59577,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -58525,7 +59610,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -58571,7 +59657,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58617,7 +59704,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58648,7 +59736,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58679,7 +59768,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58710,7 +59800,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58741,7 +59832,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58836,7 +59928,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -58868,7 +59961,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -58914,7 +60008,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -58946,7 +60041,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -58978,7 +60074,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59010,7 +60107,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59056,7 +60154,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -59102,7 +60201,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -59133,7 +60233,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -59164,7 +60265,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -59195,7 +60297,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -59226,7 +60329,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -59321,7 +60425,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -59353,7 +60458,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -59399,7 +60505,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -59431,7 +60538,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -59463,7 +60571,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -59495,7 +60604,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -59541,7 +60651,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59587,7 +60698,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59618,7 +60730,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59649,7 +60762,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59680,7 +60794,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59711,7 +60826,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -59821,7 +60937,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -59853,7 +60970,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -59884,7 +61002,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -59915,7 +61034,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -59946,7 +61066,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -59978,7 +61099,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -60024,7 +61146,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -60055,7 +61178,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -60086,7 +61210,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -60117,7 +61242,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -60224,7 +61350,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60256,7 +61383,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60287,7 +61415,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60318,7 +61447,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60349,7 +61479,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -60381,7 +61512,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60427,7 +61559,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60458,7 +61591,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60489,7 +61623,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -60520,7 +61655,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -61850,7 +62986,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -61882,7 +63019,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -61913,7 +63051,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -61944,7 +63083,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -61975,7 +63115,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -62007,7 +63148,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62053,7 +63195,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62084,7 +63227,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62115,7 +63259,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62146,7 +63291,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -62280,7 +63426,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62312,7 +63459,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62358,7 +63506,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62390,7 +63539,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62422,7 +63572,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62454,7 +63605,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -62500,7 +63652,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62546,7 +63699,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62577,7 +63731,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62608,7 +63763,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62639,7 +63795,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62670,7 +63827,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62765,7 +63923,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62797,7 +63956,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62843,7 +64003,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62875,7 +64036,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62907,7 +64069,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62939,7 +64102,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -62985,7 +64149,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63031,7 +64196,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63062,7 +64228,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63093,7 +64260,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63124,7 +64292,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63155,7 +64324,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63250,7 +64420,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63282,7 +64453,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63328,7 +64500,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63360,7 +64533,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63392,7 +64566,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63424,7 +64599,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -63470,7 +64646,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63516,7 +64693,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63547,7 +64725,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63578,7 +64757,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63609,7 +64789,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63640,7 +64821,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63763,7 +64945,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -63795,7 +64978,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -63891,7 +65075,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -63923,7 +65108,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64019,7 +65205,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -64051,7 +65238,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -64234,7 +65422,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -64267,7 +65456,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -64299,7 +65489,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64331,7 +65522,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64364,7 +65556,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64397,7 +65590,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64430,7 +65624,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64463,7 +65658,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64495,7 +65691,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -64591,7 +65788,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -64624,7 +65822,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -64656,7 +65855,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64688,7 +65888,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64721,7 +65922,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64754,7 +65956,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64787,7 +65990,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64820,7 +66024,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64852,7 +66057,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -64948,7 +66154,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -64981,7 +66188,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -65013,7 +66221,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -65045,7 +66254,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -65078,7 +66288,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -65111,7 +66322,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -65144,7 +66356,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -65177,7 +66390,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -65209,7 +66423,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -66658,7 +67873,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -66690,7 +67906,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -66736,7 +67953,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -66768,7 +67986,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -66801,7 +68020,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -66897,7 +68117,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -66929,7 +68150,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -66975,7 +68197,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -67007,7 +68230,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -67040,7 +68264,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -67136,7 +68361,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -67168,7 +68394,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -67214,7 +68441,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -67246,7 +68474,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -67279,7 +68508,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -69719,6 +70949,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "CleanupBead Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d2c818bf00][Flex_S_v2_20_P50_LPD].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d2c818bf00][Flex_S_v2_20_P50_LPD].json new file mode 100644 index 00000000000..1b664b4e963 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d2c818bf00][Flex_S_v2_20_P50_LPD].json @@ -0,0 +1,5141 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "A2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50de88d471ad3910c29207fb6df4502e", + "notes": [], + "params": { + "loadName": "opentrons_flex_96_tiprack_50ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 50 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_50ul", + "quirks": [], + "tipLength": 57.9, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.58, + "shape": "circular", + "totalLiquidVolume": 50, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a83392f86baf8cd5b4f0157c89d31dbd", + "notes": [], + "params": { + "loadName": "armadillo_96_wellplate_200ul_pcr_full_skirt", + "location": { + "slotName": "C3" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Thermo Scientific", + "brandId": [ + "AB2396" + ], + "links": [ + "https://www.fishersci.com/shop/products/armadillo-96-well-pcr-plate-1/AB2396" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 16.0 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.0, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "Armadillo 96 Well Plate 200 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "armadillo_96_wellplate_200ul_pcr_full_skirt" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.95 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 11.91 + } + }, + "stackingOffsetWithModule": { + "magneticBlockV1": { + "x": 0, + "y": 0, + "z": 3.54 + }, + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.7 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 74.24, + "z": 1.05 + }, + "A10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 74.24, + "z": 1.05 + }, + "A11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 74.24, + "z": 1.05 + }, + "A12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 74.24, + "z": 1.05 + }, + "A2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 74.24, + "z": 1.05 + }, + "A3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 74.24, + "z": 1.05 + }, + "A4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 74.24, + "z": 1.05 + }, + "A5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 74.24, + "z": 1.05 + }, + "A6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 74.24, + "z": 1.05 + }, + "A7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 74.24, + "z": 1.05 + }, + "A8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 74.24, + "z": 1.05 + }, + "A9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 74.24, + "z": 1.05 + }, + "B1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 65.24, + "z": 1.05 + }, + "B10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 65.24, + "z": 1.05 + }, + "B11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 65.24, + "z": 1.05 + }, + "B12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 65.24, + "z": 1.05 + }, + "B2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 65.24, + "z": 1.05 + }, + "B3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 65.24, + "z": 1.05 + }, + "B4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 65.24, + "z": 1.05 + }, + "B5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 65.24, + "z": 1.05 + }, + "B6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 65.24, + "z": 1.05 + }, + "B7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 65.24, + "z": 1.05 + }, + "B8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 65.24, + "z": 1.05 + }, + "B9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 65.24, + "z": 1.05 + }, + "C1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 56.24, + "z": 1.05 + }, + "C10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 56.24, + "z": 1.05 + }, + "C11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 56.24, + "z": 1.05 + }, + "C12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 56.24, + "z": 1.05 + }, + "C2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 56.24, + "z": 1.05 + }, + "C3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 56.24, + "z": 1.05 + }, + "C4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 56.24, + "z": 1.05 + }, + "C5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 56.24, + "z": 1.05 + }, + "C6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 56.24, + "z": 1.05 + }, + "C7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 56.24, + "z": 1.05 + }, + "C8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 56.24, + "z": 1.05 + }, + "C9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 56.24, + "z": 1.05 + }, + "D1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 47.24, + "z": 1.05 + }, + "D10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 47.24, + "z": 1.05 + }, + "D11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 47.24, + "z": 1.05 + }, + "D12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 47.24, + "z": 1.05 + }, + "D2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 47.24, + "z": 1.05 + }, + "D3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 47.24, + "z": 1.05 + }, + "D4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 47.24, + "z": 1.05 + }, + "D5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 47.24, + "z": 1.05 + }, + "D6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 47.24, + "z": 1.05 + }, + "D7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 47.24, + "z": 1.05 + }, + "D8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 47.24, + "z": 1.05 + }, + "D9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 47.24, + "z": 1.05 + }, + "E1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 38.24, + "z": 1.05 + }, + "E10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 38.24, + "z": 1.05 + }, + "E11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 38.24, + "z": 1.05 + }, + "E12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 38.24, + "z": 1.05 + }, + "E2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 38.24, + "z": 1.05 + }, + "E3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 38.24, + "z": 1.05 + }, + "E4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 38.24, + "z": 1.05 + }, + "E5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 38.24, + "z": 1.05 + }, + "E6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 38.24, + "z": 1.05 + }, + "E7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 38.24, + "z": 1.05 + }, + "E8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 38.24, + "z": 1.05 + }, + "E9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 38.24, + "z": 1.05 + }, + "F1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 29.24, + "z": 1.05 + }, + "F10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 29.24, + "z": 1.05 + }, + "F11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 29.24, + "z": 1.05 + }, + "F12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 29.24, + "z": 1.05 + }, + "F2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 29.24, + "z": 1.05 + }, + "F3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 29.24, + "z": 1.05 + }, + "F4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 29.24, + "z": 1.05 + }, + "F5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 29.24, + "z": 1.05 + }, + "F6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 29.24, + "z": 1.05 + }, + "F7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 29.24, + "z": 1.05 + }, + "F8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 29.24, + "z": 1.05 + }, + "F9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 29.24, + "z": 1.05 + }, + "G1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 20.24, + "z": 1.05 + }, + "G10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 20.24, + "z": 1.05 + }, + "G11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 20.24, + "z": 1.05 + }, + "G12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 20.24, + "z": 1.05 + }, + "G2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 20.24, + "z": 1.05 + }, + "G3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 20.24, + "z": 1.05 + }, + "G4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 20.24, + "z": 1.05 + }, + "G5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 20.24, + "z": 1.05 + }, + "G6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 20.24, + "z": 1.05 + }, + "G7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 20.24, + "z": 1.05 + }, + "G8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 20.24, + "z": 1.05 + }, + "G9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 20.24, + "z": 1.05 + }, + "H1": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 14.38, + "y": 11.24, + "z": 1.05 + }, + "H10": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 95.38, + "y": 11.24, + "z": 1.05 + }, + "H11": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 104.38, + "y": 11.24, + "z": 1.05 + }, + "H12": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 113.38, + "y": 11.24, + "z": 1.05 + }, + "H2": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 23.38, + "y": 11.24, + "z": 1.05 + }, + "H3": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 32.38, + "y": 11.24, + "z": 1.05 + }, + "H4": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 41.38, + "y": 11.24, + "z": 1.05 + }, + "H5": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 50.38, + "y": 11.24, + "z": 1.05 + }, + "H6": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 59.38, + "y": 11.24, + "z": 1.05 + }, + "H7": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 68.38, + "y": 11.24, + "z": 1.05 + }, + "H8": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 77.38, + "y": 11.24, + "z": 1.05 + }, + "H9": { + "depth": 14.95, + "diameter": 5.5, + "shape": "circular", + "totalLiquidVolume": 200, + "x": 86.38, + "y": 11.24, + "z": 1.05 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "596b975cce05f1835b73fd3e2e9c04b0", + "notes": [], + "params": { + "loadName": "nest_1_reservoir_290ml", + "location": { + "slotName": "D3" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "360206", + "360266" + ], + "links": [ + "https://www.nest-biotech.com/reagent-reserviors" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.47, + "zDimension": 44.4 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1" + ] + } + ], + "metadata": { + "displayCategory": "reservoir", + "displayName": "NEST 1 Well Reservoir 290 mL", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1" + ] + ], + "parameters": { + "format": "trough", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "nest_1_reservoir_290ml", + "quirks": [ + "centerMultichannelOnWells", + "touchTipDisabled" + ] + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 39.55, + "shape": "rectangular", + "totalLiquidVolume": 290000, + "x": 63.88, + "xDimension": 106.8, + "y": 42.74, + "yDimension": 71.2, + "z": 4.85 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "43c0dcf1828d77562caa82b5e8a8c91e", + "notes": [], + "params": { + "loadName": "nest_12_reservoir_15ml", + "location": { + "slotName": "B3" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "360102" + ], + "links": [ + "https://www.nest-biotech.com/reagent-reserviors/59178414.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 31.4 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9" + ] + } + ], + "metadata": { + "displayCategory": "reservoir", + "displayName": "NEST 12 Well Reservoir 15 mL", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1" + ], + [ + "A10" + ], + [ + "A11" + ], + [ + "A12" + ], + [ + "A2" + ], + [ + "A3" + ], + [ + "A4" + ], + [ + "A5" + ], + [ + "A6" + ], + [ + "A7" + ], + [ + "A8" + ], + [ + "A9" + ] + ], + "parameters": { + "format": "trough", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "nest_12_reservoir_15ml", + "quirks": [ + "centerMultichannelOnWells", + "touchTipDisabled" + ] + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 14.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A10": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 95.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A11": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 104.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A12": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 113.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A2": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 23.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A3": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 32.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A4": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 41.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A5": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 50.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A6": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 59.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A7": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 68.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A8": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 77.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + }, + "A9": { + "depth": 26.85, + "shape": "rectangular", + "totalLiquidVolume": 15000, + "x": 86.38, + "xDimension": 8.2, + "y": 42.78, + "yDimension": 71.2, + "z": 4.55 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLiquid", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5d3ae21e7b20c8d594290fcda36e0906", + "notes": [], + "params": { + "labwareId": "UUID", + "liquidId": "UUID", + "volumeByWell": { + "A1": 15000.0 + } + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLiquid", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "660a0039e8fc361930633bfde72e7048", + "notes": [], + "params": { + "labwareId": "UUID", + "liquidId": "UUID", + "volumeByWell": { + "A2": 15000.0 + } + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLiquid", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a2f86d0e4b58ed0924647422f6c09a19", + "notes": [], + "params": { + "labwareId": "UUID", + "liquidId": "UUID", + "volumeByWell": { + "A3": 15000.0 + } + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLiquid", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a060ac035737079606f40e32760f0433", + "notes": [], + "params": { + "labwareId": "UUID", + "liquidId": "UUID", + "volumeByWell": { + "A4": 15000.0 + } + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLiquid", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8f2290bcbb0d7f518924bfed155dc229", + "notes": [], + "params": { + "labwareId": "UUID", + "liquidId": "UUID", + "volumeByWell": { + "A5": 15000.0 + } + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLiquid", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd4f8f7f1a936fe3448b5eee40843272", + "notes": [], + "params": { + "labwareId": "UUID", + "liquidId": "UUID", + "volumeByWell": { + "A6": 15000.0 + } + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLiquid", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "701101b75d324afe841abfa3582e43d3", + "notes": [], + "params": { + "labwareId": "UUID", + "liquidId": "UUID", + "volumeByWell": { + "A7": 15000.0 + } + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLiquid", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d2adfadb3040917b299226cda9e05360", + "notes": [], + "params": { + "labwareId": "UUID", + "liquidId": "UUID", + "volumeByWell": { + "A8": 15000.0 + } + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLiquid", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "78cf87d9fcf2945246fe2082eb1345e1", + "notes": [], + "params": { + "labwareId": "UUID", + "liquidId": "UUID", + "volumeByWell": { + "A9": 15000.0 + } + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLiquid", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b568e2c5aa432ef1a7721ec51166eb88", + "notes": [], + "params": { + "labwareId": "UUID", + "liquidId": "UUID", + "volumeByWell": { + "A10": 15000.0 + } + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLiquid", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "97a948c60b7d36455d148bdf36d5eb38", + "notes": [], + "params": { + "labwareId": "UUID", + "liquidId": "UUID", + "volumeByWell": { + "A11": 15000.0 + } + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLiquid", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "86d4bfc615a089999c84d7cd5705e698", + "notes": [], + "params": { + "labwareId": "UUID", + "liquidId": "UUID", + "volumeByWell": { + "A12": 15000.0 + } + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fe99a456f4af16d7fc512769957a6be0", + "notes": [], + "params": { + "liquidPresenceDetection": true, + "mount": "right", + "pipetteName": "p50_single_flex", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "284d3678bb2e1cf23a240af047b6a825", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 178.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 47.81, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "liquidProbe", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "09178fc46f5d9d43c93ba8e69b385101", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 2.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 256.78, + "z": 33.4 + }, + "z_position": 26.85 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a144c088e27010f598d36cd00669b2b1", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 256.78, + "z": 5.55 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1d3ef34219675c06b73ee7756ae312a0", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 181.24, + "z": 2.05 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c7b3377a5521a396130f3fe9115525d6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 509.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c22ff0689e4c0f9b11fcdb54e102152b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "31c1579fe54b0a711b77b111c0c8ba1d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 178.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 47.81, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "liquidProbe", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c5bb1e6ec1d2ac5a4c350faf3672245a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 2.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 351.38, + "y": 256.78, + "z": 33.4 + }, + "z_position": 26.85 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "722a3e6de657c77d721895d8badab9b4", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 351.38, + "y": 256.78, + "z": 5.55 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5ac05e6cf96a720c066e94aca5045d24", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 351.38, + "y": 181.24, + "z": 2.05 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "liquidProbe", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8a6371348d39f444fc65e7304dde52ce", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 2.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 360.38, + "y": 256.78, + "z": 33.4 + }, + "z_position": 26.85 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b22e2f859d148b4dfdb91b3e43ddd7c0", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -25.85 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 360.38, + "y": 256.78, + "z": 5.55 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d7b826433bfc2c5dd7650b7f64de4387", + "notes": [], + "params": { + "flowRate": 57.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.95 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 360.38, + "y": 181.24, + "z": 2.05 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "45c70122bf33abff61217f0c752c06a4", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 359.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "440594c1295a83478b3bf55db1efc12d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dbeb059b62f722adf17e49167171024f", + "notes": [], + "params": { + "message": "Reservoir in D3 is to have NO liquid" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "04ea53a488348ffb1688af708b65f7e4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 178.38, + "y": 270.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 47.81, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e94242544868a35ba08cec280d753bc6", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -38.55 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 5.85 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ef6736559fde071db453f5eab377a125", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 434.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "57ecb3ed1ba19f712a64f34d0098c991", + "notes": [], + "params": { + "flowRate": 57.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ff5d401da9025c5e29c5687c101a5e3d", + "notes": [], + "params": { + "message": "Current volume in pipette: 0" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "prepareToAspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d4d7653c4ef95313ee313641835096ad", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "tryLiquidProbe", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "572506e8a901de4c31b05824435605e3", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "z_position": 39.55 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "99ff896a8dd377c6b88d73e413dd83fb", + "notes": [], + "params": { + "message": "Is there liquid in the reservoir? True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f3ad90410318c85bdafc440398099b1c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 509.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5f601c702191a47b8e0e0d99ae514a34", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3d627373932d2d82fdee646567e0aa8e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 178.38, + "y": 261.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 47.81, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "286f5eaff085f0888fa20b1f7e6ea0d8", + "notes": [], + "params": { + "message": "We expect an error on the next line" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "liquidProbe", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f0ce6617868dd419408be36fa2c9b687", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 2.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 46.4 + }, + "z_position": 39.55 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d184edc87ae8067624e33f43a08fe0c3", + "notes": [], + "params": { + "flowRate": 35.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 50.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -38.55 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 5.85 + }, + "volume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7e96f475215267b1f53e6431009b68f9", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA3", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 359.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "36e0b46690493b64dea41f3d6c27e266", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c58bd55bfad83d78f2859e636ee2723", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 178.38, + "y": 252.38, + "z": 99.0 + }, + "tipDiameter": 5.58, + "tipLength": 47.81, + "tipVolume": 50.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c713a0160a4176161ba486b32b07d0dd", + "notes": [], + "params": { + "message": "Reservoir in D3 is to have NO liquid" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "tryLiquidProbe", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ecd85454102ce7575176fc1430b9854f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "z_position": 39.55 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "tryLiquidProbe", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1bae2c05e983ffa93cb1e081d438f8ef", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 391.88, + "y": 42.74, + "z": 44.4 + }, + "z_position": 39.55 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "comment", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d249c3b29b22c37a6412d69a4c83b5c7", + "notes": [], + "params": { + "message": "Is there liquid in the reservoir? True" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [], + "files": [ + { + "name": "Flex_S_v2_20_P50_LPD.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "A2" + } + }, + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_50ul/1", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_50ul", + "location": { + "slotName": "B2" + } + }, + { + "definitionUri": "opentrons/armadillo_96_wellplate_200ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "armadillo_96_wellplate_200ul_pcr_full_skirt", + "location": { + "slotName": "C3" + } + }, + { + "definitionUri": "opentrons/nest_1_reservoir_290ml/1", + "id": "UUID", + "loadName": "nest_1_reservoir_290ml", + "location": { + "slotName": "D3" + } + }, + { + "definitionUri": "opentrons/nest_12_reservoir_15ml/1", + "id": "UUID", + "loadName": "nest_12_reservoir_15ml", + "location": { + "slotName": "B3" + } + } + ], + "liquidClasses": [], + "liquids": [ + { + "description": "Test this wet!!!", + "displayColor": "#0077b6", + "displayName": "H20", + "id": "UUID" + } + ], + "metadata": { + "author": "Josh McVey", + "description": "http://sandbox.docs.opentrons.com/edge/v2/pipettes/loading.html#liquid-presence-detection", + "protocolName": "Wet test for LPD" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "right", + "pipetteName": "p50_single_flex" + } + ], + "result": "ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d391213095][Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtractionCellsOrBacteria].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d391213095][Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtractionCellsOrBacteria].json index ece327c12e5..7fd14d2f851 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d391213095][Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtractionCellsOrBacteria].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d391213095][Flex_S_v2_15_P1000_96_GRIP_HS_TM_QuickZymoMagbeadRNAExtractionCellsOrBacteria].json @@ -14905,7 +14905,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14937,7 +14938,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14969,7 +14971,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15002,7 +15005,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15035,7 +15039,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15068,7 +15073,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15101,7 +15107,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15134,7 +15141,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15167,7 +15175,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15200,7 +15209,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15233,7 +15243,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15266,7 +15277,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15299,7 +15311,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15332,7 +15345,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15365,7 +15379,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15398,7 +15413,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15431,7 +15447,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15464,7 +15481,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15497,7 +15515,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15530,7 +15549,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15687,7 +15707,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15720,7 +15741,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15753,7 +15775,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15786,7 +15809,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15819,7 +15843,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15852,7 +15877,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15885,7 +15911,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15918,7 +15945,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15951,7 +15979,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15984,7 +16013,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16017,7 +16047,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16050,7 +16081,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16083,7 +16115,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16116,7 +16149,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16149,7 +16183,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16182,7 +16217,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16215,7 +16251,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16248,7 +16285,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16281,7 +16319,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16314,7 +16353,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16347,7 +16387,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16380,7 +16421,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16413,7 +16455,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16446,7 +16489,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16479,7 +16523,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16512,7 +16557,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16545,7 +16591,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16578,7 +16625,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16611,7 +16659,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16644,7 +16693,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16677,7 +16727,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16710,7 +16761,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16743,7 +16795,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16776,7 +16829,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16809,7 +16863,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16842,7 +16897,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16875,7 +16931,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16908,7 +16965,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16941,7 +16999,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16974,7 +17033,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17007,7 +17067,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17040,7 +17101,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17073,7 +17135,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17106,7 +17169,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17139,7 +17203,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17172,7 +17237,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17205,7 +17271,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17238,7 +17305,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17271,7 +17339,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17304,7 +17373,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17337,7 +17407,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17370,7 +17441,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17403,7 +17475,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17436,7 +17509,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17469,7 +17543,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17502,7 +17577,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17535,7 +17611,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17568,7 +17645,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17601,7 +17679,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17634,7 +17713,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17667,7 +17747,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17700,7 +17781,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17733,7 +17815,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17766,7 +17849,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17799,7 +17883,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17832,7 +17917,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17865,7 +17951,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17898,7 +17985,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17931,7 +18019,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17964,7 +18053,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17997,7 +18087,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18030,7 +18121,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18063,7 +18155,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18096,7 +18189,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18129,7 +18223,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18162,7 +18257,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18195,7 +18291,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18228,7 +18325,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18261,7 +18359,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18294,7 +18393,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18327,7 +18427,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18360,7 +18461,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18393,7 +18495,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18426,7 +18529,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18459,7 +18563,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18492,7 +18597,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18525,7 +18631,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18558,7 +18665,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18591,7 +18699,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18624,7 +18733,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18657,7 +18767,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18690,7 +18801,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18723,7 +18835,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18756,7 +18869,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18789,7 +18903,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18822,7 +18937,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18855,7 +18971,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18888,7 +19005,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18921,7 +19039,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18954,7 +19073,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18987,7 +19107,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19020,7 +19141,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19254,7 +19376,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19287,7 +19410,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19446,7 +19570,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19478,7 +19603,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19510,7 +19636,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19543,7 +19670,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19576,7 +19704,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19609,7 +19738,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19642,7 +19772,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19675,7 +19806,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19708,7 +19840,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19741,7 +19874,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19774,7 +19908,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19807,7 +19942,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19840,7 +19976,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19873,7 +20010,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19906,7 +20044,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19939,7 +20078,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19972,7 +20112,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20005,7 +20146,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20038,7 +20180,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20071,7 +20214,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20104,7 +20248,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20137,7 +20282,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20170,7 +20316,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20203,7 +20350,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20236,7 +20384,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20269,7 +20418,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20302,7 +20452,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20335,7 +20486,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20368,7 +20520,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20401,7 +20554,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20434,7 +20588,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20467,7 +20622,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20500,7 +20656,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20533,7 +20690,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20566,7 +20724,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20599,7 +20758,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20632,7 +20792,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20665,7 +20826,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20698,7 +20860,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20731,7 +20894,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20764,7 +20928,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20797,7 +20962,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20830,7 +20996,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20863,7 +21030,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20896,7 +21064,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20929,7 +21098,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20962,7 +21132,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20995,7 +21166,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21028,7 +21200,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21061,7 +21234,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21094,7 +21268,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21127,7 +21302,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21160,7 +21336,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21193,7 +21370,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21226,7 +21404,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21259,7 +21438,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21292,7 +21472,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21325,7 +21506,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21358,7 +21540,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21391,7 +21574,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21424,7 +21608,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21457,7 +21642,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21490,7 +21676,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21523,7 +21710,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21556,7 +21744,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21589,7 +21778,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21622,7 +21812,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21655,7 +21846,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21688,7 +21880,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21721,7 +21914,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21753,7 +21947,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22000,7 +22195,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22033,7 +22229,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22192,7 +22389,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22225,7 +22423,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22257,7 +22456,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22289,7 +22489,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22511,7 +22712,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22544,7 +22746,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22687,7 +22890,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22720,7 +22924,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22752,7 +22957,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22784,7 +22990,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23006,7 +23213,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23039,7 +23247,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23182,7 +23391,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23215,7 +23425,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23247,7 +23458,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23279,7 +23491,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23501,7 +23714,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23534,7 +23748,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23677,7 +23892,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23710,7 +23926,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23742,7 +23959,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23774,7 +23992,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23996,7 +24215,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24029,7 +24249,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24485,7 +24706,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24518,7 +24740,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24741,7 +24964,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24774,7 +24998,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24983,6 +25208,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Zach Galluzzo ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d3b28ea1d7][pl_Zymo_Magbead_DNA_Cells_Flex_96_channel].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d3b28ea1d7][pl_Zymo_Magbead_DNA_Cells_Flex_96_channel].json index 685595d593c..7916f424286 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d3b28ea1d7][pl_Zymo_Magbead_DNA_Cells_Flex_96_channel].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d3b28ea1d7][pl_Zymo_Magbead_DNA_Cells_Flex_96_channel].json @@ -30457,7 +30457,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30489,7 +30490,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30521,7 +30523,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30554,7 +30557,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30587,7 +30591,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30620,7 +30625,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30653,7 +30659,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30686,7 +30693,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30719,7 +30727,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30752,7 +30761,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30785,7 +30795,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30818,7 +30829,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30851,7 +30863,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30884,7 +30897,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30917,7 +30931,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30950,7 +30965,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30983,7 +30999,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31016,7 +31033,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31049,7 +31067,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31082,7 +31101,8 @@ "y": 0.0, "z": -18.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31239,7 +31259,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31272,7 +31293,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31305,7 +31327,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31338,7 +31361,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31371,7 +31395,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31404,7 +31429,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31437,7 +31463,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31470,7 +31497,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31503,7 +31531,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31536,7 +31565,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31569,7 +31599,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31602,7 +31633,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31635,7 +31667,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31668,7 +31701,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31701,7 +31735,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31734,7 +31769,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31767,7 +31803,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31800,7 +31837,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31833,7 +31871,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31866,7 +31905,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31899,7 +31939,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31932,7 +31973,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31965,7 +32007,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31998,7 +32041,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32031,7 +32075,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32064,7 +32109,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32097,7 +32143,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32130,7 +32177,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32163,7 +32211,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32196,7 +32245,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32229,7 +32279,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32262,7 +32313,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32295,7 +32347,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32328,7 +32381,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32361,7 +32415,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32394,7 +32449,8 @@ "y": 0.0, "z": -30.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32427,7 +32483,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32460,7 +32517,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32493,7 +32551,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32526,7 +32585,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32559,7 +32619,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32592,7 +32653,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32625,7 +32687,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32658,7 +32721,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32691,7 +32755,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32724,7 +32789,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32757,7 +32823,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32790,7 +32857,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32823,7 +32891,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32856,7 +32925,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32889,7 +32959,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32922,7 +32993,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32955,7 +33027,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32988,7 +33061,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33021,7 +33095,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33054,7 +33129,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33087,7 +33163,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33120,7 +33197,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33153,7 +33231,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33186,7 +33265,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33219,7 +33299,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33252,7 +33333,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33285,7 +33367,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33318,7 +33401,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33351,7 +33435,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33384,7 +33469,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33417,7 +33503,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33450,7 +33537,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33483,7 +33571,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33516,7 +33605,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33549,7 +33639,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33582,7 +33673,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33615,7 +33707,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33648,7 +33741,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33681,7 +33775,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33714,7 +33809,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33747,7 +33843,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33780,7 +33877,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33813,7 +33911,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33846,7 +33945,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33879,7 +33979,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33912,7 +34013,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33945,7 +34047,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -33978,7 +34081,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34011,7 +34115,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34044,7 +34149,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34077,7 +34183,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34110,7 +34217,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34143,7 +34251,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34176,7 +34285,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34209,7 +34319,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34242,7 +34353,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34275,7 +34387,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34308,7 +34421,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34341,7 +34455,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34374,7 +34489,8 @@ "y": 0.0, "z": -30.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34407,7 +34523,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34440,7 +34557,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34473,7 +34591,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34506,7 +34625,8 @@ "y": 0.0, "z": -14.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34539,7 +34659,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34572,7 +34693,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34806,7 +34928,8 @@ "y": 0.0, "z": -37.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34839,7 +34962,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34998,7 +35122,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35030,7 +35155,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35062,7 +35188,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35095,7 +35222,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35128,7 +35256,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35161,7 +35290,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35194,7 +35324,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35227,7 +35358,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35260,7 +35392,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35293,7 +35426,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35326,7 +35460,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35359,7 +35494,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35392,7 +35528,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35425,7 +35562,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35458,7 +35596,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35491,7 +35630,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35524,7 +35664,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35557,7 +35698,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35590,7 +35732,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35623,7 +35766,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35656,7 +35800,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35689,7 +35834,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35722,7 +35868,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35755,7 +35902,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35788,7 +35936,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35821,7 +35970,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35854,7 +36004,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35887,7 +36038,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35920,7 +36072,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35953,7 +36106,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35986,7 +36140,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36019,7 +36174,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36052,7 +36208,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36085,7 +36242,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36118,7 +36276,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36151,7 +36310,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36184,7 +36344,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36217,7 +36378,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36250,7 +36412,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36283,7 +36446,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36316,7 +36480,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36349,7 +36514,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36382,7 +36548,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36415,7 +36582,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36448,7 +36616,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36481,7 +36650,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36514,7 +36684,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36547,7 +36718,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36580,7 +36752,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36613,7 +36786,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36646,7 +36820,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36679,7 +36854,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36712,7 +36888,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36745,7 +36922,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36778,7 +36956,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36811,7 +36990,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36844,7 +37024,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36877,7 +37058,8 @@ "y": 1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36910,7 +37092,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36943,7 +37126,8 @@ "y": 0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36976,7 +37160,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37009,7 +37194,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37042,7 +37228,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37075,7 +37262,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37108,7 +37296,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37141,7 +37330,8 @@ "y": -1.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37174,7 +37364,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37207,7 +37398,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37240,7 +37432,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37273,7 +37466,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37305,7 +37499,8 @@ "y": -0.75, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37552,7 +37747,8 @@ "y": 0.0, "z": -37.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37585,7 +37781,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37744,7 +37941,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37777,7 +37975,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37809,7 +38008,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -37841,7 +38041,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38077,7 +38278,8 @@ "y": 0.0, "z": -37.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38110,7 +38312,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38269,7 +38472,8 @@ "y": 0.0, "z": -23.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38302,7 +38506,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38334,7 +38539,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38366,7 +38572,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38602,7 +38809,8 @@ "y": 0.0, "z": -37.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38635,7 +38843,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38794,7 +39003,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38827,7 +39037,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38859,7 +39070,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -38891,7 +39103,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39127,7 +39340,8 @@ "y": 0.0, "z": -37.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39160,7 +39374,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39319,7 +39534,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39352,7 +39568,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39384,7 +39601,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39416,7 +39634,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39652,7 +39871,8 @@ "y": 0.0, "z": -37.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -39685,7 +39905,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40157,7 +40378,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40190,7 +40412,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40427,7 +40650,8 @@ "y": 0.0, "z": -37.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40460,7 +40684,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -40661,6 +40886,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Lysis Buffer", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d48bc4f0c9][OT2_S_v2_17_P300M_P20S_HS_TC_TM_SmokeTestV3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d48bc4f0c9][OT2_S_v2_17_P300M_P20S_HS_TC_TM_SmokeTestV3].json index b14a472b30f..718e0a0df13 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d48bc4f0c9][OT2_S_v2_17_P300M_P20S_HS_TC_TM_SmokeTestV3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d48bc4f0c9][OT2_S_v2_17_P300M_P20S_HS_TC_TM_SmokeTestV3].json @@ -11045,7 +11045,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11108,7 +11109,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11171,7 +11173,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11234,7 +11237,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11297,7 +11301,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11356,7 +11361,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11402,7 +11408,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11449,7 +11456,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11580,7 +11588,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11613,7 +11622,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -11690,7 +11700,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11723,7 +11734,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -11800,7 +11812,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11833,7 +11846,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -11910,7 +11924,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11943,7 +11958,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D8" }, @@ -12020,7 +12036,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12053,7 +12070,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -12206,7 +12224,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12240,7 +12259,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12273,7 +12293,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12307,7 +12328,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12340,7 +12362,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12373,7 +12396,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12406,7 +12430,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12439,7 +12464,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12472,7 +12498,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12505,7 +12532,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12538,7 +12566,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12569,7 +12598,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12601,7 +12631,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12635,7 +12666,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12668,7 +12700,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12702,7 +12735,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12735,7 +12769,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12768,7 +12803,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12801,7 +12837,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -12834,7 +12871,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12867,7 +12905,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12900,7 +12939,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12933,7 +12973,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12964,7 +13005,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -12996,7 +13038,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13030,7 +13073,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13063,7 +13107,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13097,7 +13142,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13130,7 +13176,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13163,7 +13210,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13196,7 +13244,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13229,7 +13278,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -13262,7 +13312,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -13295,7 +13346,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -13328,7 +13380,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -13359,7 +13412,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -13391,7 +13445,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13425,7 +13480,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13458,7 +13514,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13492,7 +13549,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13525,7 +13583,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13558,7 +13617,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13591,7 +13651,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13624,7 +13685,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -13657,7 +13719,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -13690,7 +13753,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -13723,7 +13787,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -13754,7 +13819,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -13786,7 +13852,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13820,7 +13887,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13853,7 +13921,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13887,7 +13956,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13920,7 +13990,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13953,7 +14024,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13986,7 +14058,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14019,7 +14092,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -14052,7 +14126,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -14085,7 +14160,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -14118,7 +14194,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -14149,7 +14226,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -14181,7 +14259,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14215,7 +14294,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14248,7 +14328,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14282,7 +14363,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14315,7 +14397,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14348,7 +14431,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14381,7 +14465,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14414,7 +14499,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -14447,7 +14533,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -14480,7 +14567,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -14513,7 +14601,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -14544,7 +14633,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -14576,7 +14666,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14610,7 +14701,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14643,7 +14735,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14677,7 +14770,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14710,7 +14804,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14743,7 +14838,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14776,7 +14872,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14809,7 +14906,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -14842,7 +14940,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -14875,7 +14974,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -14908,7 +15008,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -14939,7 +15040,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -14971,7 +15073,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15005,7 +15108,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15038,7 +15142,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15072,7 +15177,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15105,7 +15211,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15138,7 +15245,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15171,7 +15279,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15204,7 +15313,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15237,7 +15347,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15270,7 +15381,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15303,7 +15415,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15334,7 +15447,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15366,7 +15480,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15400,7 +15515,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15433,7 +15549,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15467,7 +15584,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15500,7 +15618,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15533,7 +15652,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15566,7 +15686,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15599,7 +15720,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15632,7 +15754,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15665,7 +15788,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15698,7 +15822,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15729,7 +15854,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15761,7 +15887,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -15794,7 +15921,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15826,7 +15954,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15858,7 +15987,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -15891,7 +16021,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15923,7 +16054,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -15955,7 +16087,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C11" }, @@ -15988,7 +16121,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -16020,7 +16154,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -16116,7 +16251,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16148,7 +16284,8 @@ "y": 0.0, "z": 5.0000000000000036 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16180,7 +16317,8 @@ "y": 0.0, "z": 5.0000000000000036 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16227,7 +16365,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H11" }, @@ -16259,7 +16398,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -16290,7 +16430,8 @@ "y": 0.0, "z": -14.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -16321,7 +16462,8 @@ "y": 0.0, "z": -14.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E11" }, @@ -16353,7 +16495,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -16685,7 +16828,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16718,7 +16862,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16827,7 +16972,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16860,7 +17006,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16971,7 +17118,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17004,7 +17152,8 @@ "y": 0.0, "z": -22.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -17080,7 +17229,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17113,7 +17263,8 @@ "y": 0.0, "z": -13.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -17279,6 +17430,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6026e11c5][OT2_X_v2_7_P300S_TwinningError].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6026e11c5][OT2_X_v2_7_P300S_TwinningError].json index 8547ba9d9c5..5b6c3c3c690 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6026e11c5][OT2_X_v2_7_P300S_TwinningError].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6026e11c5][OT2_X_v2_7_P300S_TwinningError].json @@ -2677,7 +2677,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2710,7 +2711,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2834,6 +2836,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.7", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d61739e6a3][Flex_S_v2_19_IDT_xGen_EZ_48x].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d61739e6a3][Flex_S_v2_19_IDT_xGen_EZ_48x].json index b1c5e57a510..170de395195 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d61739e6a3][Flex_S_v2_19_IDT_xGen_EZ_48x].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d61739e6a3][Flex_S_v2_19_IDT_xGen_EZ_48x].json @@ -7651,7 +7651,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7685,7 +7686,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7718,7 +7720,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7751,7 +7754,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7784,7 +7788,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7817,7 +7822,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7850,7 +7856,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7883,7 +7890,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7917,7 +7925,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7950,7 +7959,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7984,7 +7994,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8017,7 +8028,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8051,7 +8063,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8084,7 +8097,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8118,7 +8132,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8151,7 +8166,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8185,7 +8201,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8218,7 +8235,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8252,7 +8270,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8285,7 +8304,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8319,7 +8339,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8352,7 +8373,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8386,7 +8408,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8419,7 +8442,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8453,7 +8477,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8486,7 +8511,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8520,7 +8546,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8553,7 +8580,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8587,7 +8615,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8620,7 +8649,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8654,7 +8684,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8687,7 +8718,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8721,7 +8753,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8754,7 +8787,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8788,7 +8822,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8821,7 +8856,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8855,7 +8891,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8888,7 +8925,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8922,7 +8960,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8955,7 +8994,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8989,7 +9029,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9022,7 +9063,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9056,7 +9098,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9089,7 +9132,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9123,7 +9167,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9156,7 +9201,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9190,7 +9236,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9223,7 +9270,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9257,7 +9305,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9290,7 +9339,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9324,7 +9374,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9357,7 +9408,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9391,7 +9443,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9424,7 +9477,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9458,7 +9512,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9491,7 +9546,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9524,7 +9580,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9556,7 +9613,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9601,7 +9659,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9708,7 +9767,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9742,7 +9802,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9775,7 +9836,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9808,7 +9870,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9841,7 +9904,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9874,7 +9938,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9907,7 +9972,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9940,7 +10006,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9974,7 +10041,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10007,7 +10075,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10041,7 +10110,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10074,7 +10144,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10108,7 +10179,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10141,7 +10213,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10175,7 +10248,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10208,7 +10282,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10242,7 +10317,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10275,7 +10351,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10309,7 +10386,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10342,7 +10420,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10376,7 +10455,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10409,7 +10489,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10443,7 +10524,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10476,7 +10558,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10510,7 +10593,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10543,7 +10627,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10577,7 +10662,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10610,7 +10696,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10644,7 +10731,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10677,7 +10765,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10711,7 +10800,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10744,7 +10834,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10778,7 +10869,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10811,7 +10903,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10845,7 +10938,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10878,7 +10972,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10912,7 +11007,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10945,7 +11041,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10979,7 +11076,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11012,7 +11110,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11046,7 +11145,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11079,7 +11179,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11113,7 +11214,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11146,7 +11248,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11180,7 +11283,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11213,7 +11317,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11247,7 +11352,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11280,7 +11386,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11314,7 +11421,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11347,7 +11455,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11381,7 +11490,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11414,7 +11524,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11448,7 +11559,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11481,7 +11593,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11515,7 +11628,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11548,7 +11662,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11581,7 +11696,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11613,7 +11729,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11658,7 +11775,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11765,7 +11883,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11799,7 +11918,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11832,7 +11952,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11865,7 +11986,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11898,7 +12020,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11931,7 +12054,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11964,7 +12088,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11997,7 +12122,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12031,7 +12157,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12064,7 +12191,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12098,7 +12226,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12131,7 +12260,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12165,7 +12295,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12198,7 +12329,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12232,7 +12364,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12265,7 +12398,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12299,7 +12433,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12332,7 +12467,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12366,7 +12502,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12399,7 +12536,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12433,7 +12571,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12466,7 +12605,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12500,7 +12640,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12533,7 +12674,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12567,7 +12709,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12600,7 +12743,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12634,7 +12778,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12667,7 +12812,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12701,7 +12847,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12734,7 +12881,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12768,7 +12916,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12801,7 +12950,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12835,7 +12985,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12868,7 +13019,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12902,7 +13054,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12935,7 +13088,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12969,7 +13123,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13002,7 +13157,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13036,7 +13192,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13069,7 +13226,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13103,7 +13261,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13136,7 +13295,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13170,7 +13330,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13203,7 +13364,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13237,7 +13399,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13270,7 +13433,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13304,7 +13468,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13337,7 +13502,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13371,7 +13537,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13404,7 +13571,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13438,7 +13606,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13471,7 +13640,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13505,7 +13675,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13538,7 +13709,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13572,7 +13744,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13605,7 +13778,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13638,7 +13812,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13670,7 +13845,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13715,7 +13891,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15150,7 +15327,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15184,7 +15362,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15217,7 +15396,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15250,7 +15430,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15283,7 +15464,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15316,7 +15498,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15348,7 +15531,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15394,7 +15578,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15426,7 +15611,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15458,7 +15644,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15492,7 +15679,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15525,7 +15713,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15559,7 +15748,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15592,7 +15782,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15626,7 +15817,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15659,7 +15851,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15693,7 +15886,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15726,7 +15920,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15760,7 +15955,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15793,7 +15989,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15827,7 +16024,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15860,7 +16058,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15894,7 +16093,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15927,7 +16127,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15961,7 +16162,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15994,7 +16196,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16028,7 +16231,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16061,7 +16265,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16095,7 +16300,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16128,7 +16334,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16162,7 +16369,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16195,7 +16403,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16229,7 +16438,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16262,7 +16472,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16296,7 +16507,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16329,7 +16541,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16363,7 +16576,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16396,7 +16610,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16430,7 +16645,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16463,7 +16679,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16497,7 +16714,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16530,7 +16748,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16564,7 +16783,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16597,7 +16817,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16631,7 +16852,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16664,7 +16886,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16698,7 +16921,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16731,7 +16955,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16765,7 +16990,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16798,7 +17024,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16832,7 +17059,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16865,7 +17093,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16899,7 +17128,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16932,7 +17162,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16966,7 +17197,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16999,7 +17231,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17033,7 +17266,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17066,7 +17300,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17100,7 +17335,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17133,7 +17369,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17167,7 +17404,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17200,7 +17438,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17234,7 +17473,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17267,7 +17507,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17301,7 +17542,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17334,7 +17576,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17368,7 +17611,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17401,7 +17645,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17434,7 +17679,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17466,7 +17712,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17511,7 +17758,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17618,7 +17866,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17652,7 +17901,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17685,7 +17935,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17718,7 +17969,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17751,7 +18003,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17784,7 +18037,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17816,7 +18070,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17862,7 +18117,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17894,7 +18150,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17926,7 +18183,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17960,7 +18218,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17993,7 +18252,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18027,7 +18287,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18060,7 +18321,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18094,7 +18356,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18127,7 +18390,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18161,7 +18425,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18194,7 +18459,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18228,7 +18494,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18261,7 +18528,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18295,7 +18563,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18328,7 +18597,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18362,7 +18632,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18395,7 +18666,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18429,7 +18701,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18462,7 +18735,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18496,7 +18770,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18529,7 +18804,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18563,7 +18839,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18596,7 +18873,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18630,7 +18908,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18663,7 +18942,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18697,7 +18977,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18730,7 +19011,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18764,7 +19046,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18797,7 +19080,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18831,7 +19115,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18864,7 +19149,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18898,7 +19184,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18931,7 +19218,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18965,7 +19253,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18998,7 +19287,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19032,7 +19322,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19065,7 +19356,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19099,7 +19391,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19132,7 +19425,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19166,7 +19460,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19199,7 +19494,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19233,7 +19529,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19266,7 +19563,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19300,7 +19598,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19333,7 +19632,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19367,7 +19667,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19400,7 +19701,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19434,7 +19736,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19467,7 +19770,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19501,7 +19805,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19534,7 +19839,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19568,7 +19874,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19601,7 +19908,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19635,7 +19943,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19668,7 +19977,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19702,7 +20012,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19735,7 +20046,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19769,7 +20081,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19802,7 +20115,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19836,7 +20150,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19869,7 +20184,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19902,7 +20218,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19934,7 +20251,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19979,7 +20297,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20086,7 +20405,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20120,7 +20440,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20153,7 +20474,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20186,7 +20508,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20219,7 +20542,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20252,7 +20576,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20284,7 +20609,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20330,7 +20656,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20362,7 +20689,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20394,7 +20722,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20428,7 +20757,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20461,7 +20791,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20495,7 +20826,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20528,7 +20860,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20562,7 +20895,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20595,7 +20929,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20629,7 +20964,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20662,7 +20998,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20696,7 +21033,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20729,7 +21067,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20763,7 +21102,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20796,7 +21136,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20830,7 +21171,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20863,7 +21205,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20897,7 +21240,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20930,7 +21274,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20964,7 +21309,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20997,7 +21343,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21031,7 +21378,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21064,7 +21412,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21098,7 +21447,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21131,7 +21481,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21165,7 +21516,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21198,7 +21550,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21232,7 +21585,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21265,7 +21619,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21299,7 +21654,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21332,7 +21688,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21366,7 +21723,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21399,7 +21757,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21433,7 +21792,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21466,7 +21826,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21500,7 +21861,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21533,7 +21895,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21567,7 +21930,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21600,7 +21964,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21634,7 +21999,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21667,7 +22033,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21701,7 +22068,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21734,7 +22102,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21768,7 +22137,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21801,7 +22171,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21835,7 +22206,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21868,7 +22240,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21902,7 +22275,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21935,7 +22309,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21969,7 +22344,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22002,7 +22378,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22036,7 +22413,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22069,7 +22447,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22103,7 +22482,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22136,7 +22516,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22170,7 +22551,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22203,7 +22585,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22237,7 +22620,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22270,7 +22654,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22304,7 +22689,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22337,7 +22723,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22370,7 +22757,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22402,7 +22790,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22447,7 +22836,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22691,7 +23081,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22725,7 +23116,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22758,7 +23150,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22792,7 +23185,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22825,7 +23219,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22858,7 +23253,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22891,7 +23287,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22924,7 +23321,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22956,7 +23354,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22987,7 +23386,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23018,7 +23418,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23050,7 +23451,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23096,7 +23498,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23128,7 +23531,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23174,7 +23578,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23206,7 +23611,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23239,7 +23645,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23271,7 +23678,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23303,7 +23711,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23335,7 +23744,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23367,7 +23777,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23400,7 +23811,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23432,7 +23844,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23464,7 +23877,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23496,7 +23910,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23541,7 +23956,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23573,7 +23989,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23604,7 +24021,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23635,7 +24053,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23666,7 +24085,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23773,7 +24193,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23807,7 +24228,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23840,7 +24262,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23874,7 +24297,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23907,7 +24331,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23940,7 +24365,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23973,7 +24399,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24006,7 +24433,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24038,7 +24466,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24069,7 +24498,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24100,7 +24530,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24132,7 +24563,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24178,7 +24610,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24210,7 +24643,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24256,7 +24690,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24288,7 +24723,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24321,7 +24757,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24353,7 +24790,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24385,7 +24823,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24417,7 +24856,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24449,7 +24889,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24482,7 +24923,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24514,7 +24956,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24546,7 +24989,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24578,7 +25022,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24623,7 +25068,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24655,7 +25101,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24686,7 +25133,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24717,7 +25165,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24748,7 +25197,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24855,7 +25305,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24889,7 +25340,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24922,7 +25374,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24956,7 +25409,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24989,7 +25443,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25022,7 +25477,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25055,7 +25511,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25088,7 +25545,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25120,7 +25578,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25151,7 +25610,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25182,7 +25642,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25214,7 +25675,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25260,7 +25722,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25292,7 +25755,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25338,7 +25802,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25370,7 +25835,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25403,7 +25869,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25435,7 +25902,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25467,7 +25935,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25499,7 +25968,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25531,7 +26001,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25564,7 +26035,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25596,7 +26068,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25628,7 +26101,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25660,7 +26134,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25705,7 +26180,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25737,7 +26213,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25768,7 +26245,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25799,7 +26277,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25830,7 +26309,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26067,7 +26547,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26099,7 +26580,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26145,7 +26627,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26177,7 +26660,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26209,7 +26693,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26255,7 +26740,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26301,7 +26787,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26332,7 +26819,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26363,7 +26851,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26458,7 +26947,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26490,7 +26980,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26536,7 +27027,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26568,7 +27060,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26600,7 +27093,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26646,7 +27140,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26692,7 +27187,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26723,7 +27219,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26754,7 +27251,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -26849,7 +27347,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26881,7 +27380,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26927,7 +27427,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26959,7 +27460,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26991,7 +27493,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -27037,7 +27540,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27083,7 +27587,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27114,7 +27619,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27145,7 +27651,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27269,7 +27776,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27301,7 +27809,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27332,7 +27841,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27363,7 +27873,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27394,7 +27905,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27425,7 +27937,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27457,7 +27970,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27503,7 +28017,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27534,7 +28049,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27565,7 +28081,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27596,7 +28113,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27717,7 +28235,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27749,7 +28268,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27780,7 +28300,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27811,7 +28332,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27842,7 +28364,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27873,7 +28396,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27905,7 +28429,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27951,7 +28476,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27982,7 +28508,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28013,7 +28540,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28044,7 +28572,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28165,7 +28694,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28197,7 +28727,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28228,7 +28759,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28259,7 +28791,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28290,7 +28823,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28321,7 +28855,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28353,7 +28888,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28399,7 +28935,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28430,7 +28967,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28461,7 +28999,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28492,7 +29031,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29813,7 +30353,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29845,7 +30386,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29891,7 +30433,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29923,7 +30466,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29955,7 +30499,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29987,7 +30532,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30033,7 +30579,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30079,7 +30626,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30110,7 +30658,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30141,7 +30690,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30172,7 +30722,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30203,7 +30754,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30298,7 +30850,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30330,7 +30883,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30376,7 +30930,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30408,7 +30963,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30440,7 +30996,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30472,7 +31029,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30518,7 +31076,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30564,7 +31123,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30595,7 +31155,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30626,7 +31187,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30657,7 +31219,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30688,7 +31251,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30783,7 +31347,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30815,7 +31380,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30861,7 +31427,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30893,7 +31460,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30925,7 +31493,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30957,7 +31526,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31003,7 +31573,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -31049,7 +31620,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -31080,7 +31652,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -31111,7 +31684,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -31142,7 +31716,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -31173,7 +31748,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -31297,7 +31873,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31329,7 +31906,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31360,7 +31938,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31391,7 +31970,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31422,7 +32002,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31453,7 +32034,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31485,7 +32067,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31531,7 +32114,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31562,7 +32146,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31593,7 +32178,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31624,7 +32210,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31745,7 +32332,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31777,7 +32365,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31808,7 +32397,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31839,7 +32429,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31870,7 +32461,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31901,7 +32493,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31933,7 +32526,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31979,7 +32573,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32010,7 +32605,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32041,7 +32637,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32072,7 +32669,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32193,7 +32791,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -32225,7 +32824,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -32256,7 +32856,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -32287,7 +32888,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -32318,7 +32920,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -32349,7 +32952,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32381,7 +32985,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32427,7 +33032,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32458,7 +33064,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32489,7 +33096,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32520,7 +33128,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32654,7 +33263,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32686,7 +33296,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32732,7 +33343,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32764,7 +33376,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32796,7 +33409,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32828,7 +33442,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32874,7 +33489,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32920,7 +33536,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32951,7 +33568,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -32982,7 +33600,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33013,7 +33632,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33044,7 +33664,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33139,7 +33760,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33171,7 +33793,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33217,7 +33840,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33249,7 +33873,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33281,7 +33906,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33313,7 +33939,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33359,7 +33986,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33405,7 +34033,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33436,7 +34065,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33467,7 +34097,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33498,7 +34129,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33529,7 +34161,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33624,7 +34257,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33656,7 +34290,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33702,7 +34337,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33734,7 +34370,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33766,7 +34403,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33798,7 +34436,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33844,7 +34483,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33890,7 +34530,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33921,7 +34562,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33952,7 +34594,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33983,7 +34626,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34014,7 +34658,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34137,7 +34782,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34169,7 +34815,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -34216,7 +34863,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34248,7 +34896,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34293,7 +34942,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34324,7 +34974,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34355,7 +35006,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34386,7 +35038,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34481,7 +35134,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34513,7 +35167,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -34560,7 +35215,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34592,7 +35248,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34637,7 +35294,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34668,7 +35326,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34699,7 +35358,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34730,7 +35390,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34825,7 +35486,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -34857,7 +35519,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -34904,7 +35567,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34936,7 +35600,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34981,7 +35646,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35012,7 +35678,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35043,7 +35710,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35074,7 +35742,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35256,7 +35925,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35288,7 +35958,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35320,7 +35991,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35353,7 +36025,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35386,7 +36059,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35418,7 +36092,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -35514,7 +36189,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35546,7 +36222,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35578,7 +36255,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35611,7 +36289,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35644,7 +36323,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35676,7 +36356,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35772,7 +36453,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -35804,7 +36486,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35836,7 +36519,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35869,7 +36553,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35902,7 +36587,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35934,7 +36620,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36160,7 +36847,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36192,7 +36880,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36238,7 +36927,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36270,7 +36960,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -36303,7 +36994,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36399,7 +37091,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -36431,7 +37124,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -36477,7 +37171,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -36509,7 +37204,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -36542,7 +37238,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36638,7 +37335,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36670,7 +37368,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36716,7 +37415,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36748,7 +37448,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -36781,7 +37482,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36934,7 +37636,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36967,7 +37670,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37000,7 +37704,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37033,7 +37738,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37067,7 +37773,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37100,7 +37807,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37134,7 +37842,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37167,7 +37876,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37200,7 +37910,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37232,7 +37943,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37277,7 +37989,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37384,7 +38097,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -37417,7 +38131,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -37450,7 +38165,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37483,7 +38199,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37517,7 +38234,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37550,7 +38268,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37584,7 +38303,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37617,7 +38337,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37650,7 +38371,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37682,7 +38404,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37727,7 +38450,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37834,7 +38558,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37867,7 +38592,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37900,7 +38626,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37933,7 +38660,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37967,7 +38695,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38000,7 +38729,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38034,7 +38764,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38067,7 +38798,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38100,7 +38832,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38132,7 +38865,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38177,7 +38911,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39485,7 +40220,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39519,7 +40255,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39552,7 +40289,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39585,7 +40323,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39618,7 +40357,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39651,7 +40391,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39684,7 +40425,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39718,7 +40460,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39751,7 +40494,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39785,7 +40529,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39818,7 +40563,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39852,7 +40598,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39885,7 +40632,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39919,7 +40667,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39952,7 +40701,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39986,7 +40736,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40019,7 +40770,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40053,7 +40805,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40086,7 +40839,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40120,7 +40874,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40153,7 +40908,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40187,7 +40943,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40220,7 +40977,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40254,7 +41012,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40287,7 +41046,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40320,7 +41080,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40352,7 +41113,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40397,7 +41159,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -40504,7 +41267,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40538,7 +41302,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40571,7 +41336,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40604,7 +41370,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40637,7 +41404,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -40670,7 +41438,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40703,7 +41472,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40737,7 +41507,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40770,7 +41541,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40804,7 +41576,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40837,7 +41610,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40871,7 +41645,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40904,7 +41679,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40938,7 +41714,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -40971,7 +41748,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41005,7 +41783,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41038,7 +41817,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41072,7 +41852,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41105,7 +41886,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41139,7 +41921,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41172,7 +41955,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41206,7 +41990,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41239,7 +42024,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41273,7 +42059,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41306,7 +42093,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41339,7 +42127,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41371,7 +42160,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41416,7 +42206,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -41523,7 +42314,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41557,7 +42349,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41590,7 +42383,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41623,7 +42417,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41656,7 +42451,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -41689,7 +42485,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41722,7 +42519,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41756,7 +42554,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41789,7 +42588,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41823,7 +42623,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41856,7 +42657,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41890,7 +42692,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41923,7 +42726,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41957,7 +42761,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -41990,7 +42795,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42024,7 +42830,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42057,7 +42864,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42091,7 +42899,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42124,7 +42933,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42158,7 +42968,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42191,7 +43002,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42225,7 +43037,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42258,7 +43071,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42292,7 +43106,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42325,7 +43140,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42358,7 +43174,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42390,7 +43207,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42435,7 +43253,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -42823,7 +43642,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42857,7 +43677,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42890,7 +43711,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42924,7 +43746,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42957,7 +43780,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -42990,7 +43814,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43023,7 +43848,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43056,7 +43882,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43088,7 +43915,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43119,7 +43947,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43150,7 +43979,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43182,7 +44012,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43228,7 +44059,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43260,7 +44092,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43306,7 +44139,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43338,7 +44172,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43371,7 +44206,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43403,7 +44239,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43435,7 +44272,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43467,7 +44305,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43499,7 +44338,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43532,7 +44372,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43564,7 +44405,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43596,7 +44438,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43628,7 +44471,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43673,7 +44517,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43705,7 +44550,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43736,7 +44582,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43767,7 +44614,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43798,7 +44646,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -43905,7 +44754,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43939,7 +44789,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -43972,7 +44823,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44006,7 +44858,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44039,7 +44892,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44072,7 +44926,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44105,7 +44960,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44138,7 +44994,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44170,7 +45027,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44201,7 +45059,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44232,7 +45091,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -44264,7 +45124,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44310,7 +45171,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44342,7 +45204,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44388,7 +45251,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44420,7 +45284,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44453,7 +45318,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44485,7 +45351,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44517,7 +45384,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44549,7 +45417,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44581,7 +45450,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44614,7 +45484,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44646,7 +45517,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44678,7 +45550,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44710,7 +45583,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44755,7 +45629,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44787,7 +45662,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44818,7 +45694,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44849,7 +45726,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44880,7 +45758,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -44987,7 +45866,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45021,7 +45901,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45054,7 +45935,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45088,7 +45970,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45121,7 +46004,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45154,7 +46038,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45187,7 +46072,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45220,7 +46106,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45252,7 +46139,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45283,7 +46171,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45314,7 +46203,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -45346,7 +46236,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45392,7 +46283,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45424,7 +46316,8 @@ "y": 0.0, "z": -14.65 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45470,7 +46363,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45502,7 +46396,8 @@ "y": 0.0, "z": -14.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45535,7 +46430,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45567,7 +46463,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45599,7 +46496,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45631,7 +46529,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45663,7 +46562,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45696,7 +46596,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45728,7 +46629,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45760,7 +46662,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45792,7 +46695,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45837,7 +46741,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45869,7 +46774,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45900,7 +46806,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45931,7 +46838,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -45962,7 +46870,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -46199,7 +47108,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46231,7 +47141,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46277,7 +47188,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46309,7 +47221,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46341,7 +47254,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46373,7 +47287,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -46419,7 +47334,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46465,7 +47381,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46496,7 +47413,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46527,7 +47445,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46558,7 +47477,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46589,7 +47509,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46684,7 +47605,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46716,7 +47638,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46762,7 +47685,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46794,7 +47718,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46826,7 +47751,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46858,7 +47784,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -46904,7 +47831,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46950,7 +47878,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -46981,7 +47910,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47012,7 +47942,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47043,7 +47974,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47074,7 +48006,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47169,7 +48102,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -47201,7 +48135,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -47247,7 +48182,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -47279,7 +48215,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -47311,7 +48248,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -47343,7 +48281,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -47389,7 +48328,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47435,7 +48375,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47466,7 +48407,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47497,7 +48439,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47528,7 +48471,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47559,7 +48503,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -47683,7 +48628,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47715,7 +48661,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47746,7 +48693,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47777,7 +48725,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47808,7 +48757,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -47840,7 +48790,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -47886,7 +48837,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -47917,7 +48869,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -47948,7 +48901,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -47979,7 +48933,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -48100,7 +49055,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48132,7 +49088,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48163,7 +49120,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48194,7 +49152,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48225,7 +49184,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48257,7 +49217,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48303,7 +49264,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48334,7 +49296,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48365,7 +49328,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48396,7 +49360,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -48517,7 +49482,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48549,7 +49515,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48580,7 +49547,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48611,7 +49579,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48642,7 +49611,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -48674,7 +49644,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -48720,7 +49691,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -48751,7 +49723,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -48782,7 +49755,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -48813,7 +49787,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -50134,7 +51109,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -50166,7 +51142,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -50212,7 +51189,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -50244,7 +51222,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -50276,7 +51255,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -50308,7 +51288,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -50354,7 +51335,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50400,7 +51382,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50431,7 +51414,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50462,7 +51446,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50493,7 +51478,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50524,7 +51510,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50619,7 +51606,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -50651,7 +51639,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -50697,7 +51686,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -50729,7 +51719,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -50761,7 +51752,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -50793,7 +51785,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -50839,7 +51832,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50885,7 +51879,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50916,7 +51911,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50947,7 +51943,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -50978,7 +51975,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -51009,7 +52007,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -51104,7 +52103,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51136,7 +52136,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51182,7 +52183,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51214,7 +52216,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51246,7 +52249,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51278,7 +52282,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51324,7 +52329,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51370,7 +52376,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51401,7 +52408,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51432,7 +52440,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51463,7 +52472,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51494,7 +52504,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -51618,7 +52629,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51650,7 +52662,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51681,7 +52694,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51712,7 +52726,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51743,7 +52758,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -51775,7 +52791,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51821,7 +52838,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51852,7 +52870,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51883,7 +52902,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -51914,7 +52934,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -52035,7 +53056,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52067,7 +53089,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52098,7 +53121,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52129,7 +53153,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52160,7 +53185,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52192,7 +53218,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52238,7 +53265,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52269,7 +53297,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52300,7 +53329,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52331,7 +53361,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -52452,7 +53483,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52484,7 +53516,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52515,7 +53548,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52546,7 +53580,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52577,7 +53612,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -52609,7 +53645,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -52655,7 +53692,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -52686,7 +53724,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -52717,7 +53756,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -52748,7 +53788,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -52882,7 +53923,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -52914,7 +53956,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -52960,7 +54003,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -52992,7 +54036,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53024,7 +54069,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53056,7 +54102,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -53102,7 +54149,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53148,7 +54196,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53179,7 +54228,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53210,7 +54260,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53241,7 +54292,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53272,7 +54324,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53367,7 +54420,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53399,7 +54453,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53445,7 +54500,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53477,7 +54533,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53509,7 +54566,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53541,7 +54599,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -53587,7 +54646,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53633,7 +54693,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53664,7 +54725,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53695,7 +54757,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53726,7 +54789,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53757,7 +54821,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53852,7 +54917,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53884,7 +54950,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53930,7 +54997,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53962,7 +55030,8 @@ "y": 0.0, "z": -37.25 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -53994,7 +55063,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54026,7 +55096,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54072,7 +55143,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54118,7 +55190,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54149,7 +55222,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54180,7 +55254,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54211,7 +55286,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54242,7 +55318,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54365,7 +55442,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54397,7 +55475,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -54444,7 +55523,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54476,7 +55556,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54521,7 +55602,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54552,7 +55634,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54583,7 +55666,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54614,7 +55698,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54709,7 +55794,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -54741,7 +55827,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -54788,7 +55875,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54820,7 +55908,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54865,7 +55954,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54896,7 +55986,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54927,7 +56018,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -54958,7 +56050,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55053,7 +56146,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55085,7 +56179,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55132,7 +56227,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55164,7 +56260,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55209,7 +56306,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55240,7 +56338,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55271,7 +56370,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -55302,7 +56402,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -56707,7 +57808,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -56739,7 +57841,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56771,7 +57874,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56804,7 +57908,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56837,7 +57942,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56869,7 +57975,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -56965,7 +58072,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -56997,7 +58105,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57029,7 +58138,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57062,7 +58172,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57095,7 +58206,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57127,7 +58239,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57223,7 +58336,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -57255,7 +58369,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57287,7 +58402,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57320,7 +58436,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57353,7 +58470,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57385,7 +58503,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -57611,7 +58730,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57643,7 +58763,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57689,7 +58810,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57721,7 +58843,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57754,7 +58877,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -57850,7 +58974,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57882,7 +59007,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57928,7 +59054,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57960,7 +59087,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -57993,7 +59121,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -58089,7 +59218,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58121,7 +59251,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58167,7 +59298,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58199,7 +59331,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58232,7 +59365,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -58785,6 +59919,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6389183c0][pl_AMPure_XP_48x_v8].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6389183c0][pl_AMPure_XP_48x_v8].json index 715a5f929e4..1ad848a9ef8 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6389183c0][pl_AMPure_XP_48x_v8].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d6389183c0][pl_AMPure_XP_48x_v8].json @@ -6431,7 +6431,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6464,7 +6465,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6572,7 +6574,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -6605,7 +6608,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -6713,7 +6717,8 @@ "y": 0.0, "z": -14.7 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -6746,7 +6751,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -8055,7 +8061,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8088,7 +8095,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8121,7 +8129,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8154,7 +8163,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8186,7 +8196,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8217,7 +8228,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8248,7 +8260,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8280,7 +8293,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8312,7 +8326,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8343,7 +8358,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8375,7 +8391,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8407,7 +8424,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8439,7 +8457,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8472,7 +8491,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8504,7 +8524,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8536,7 +8557,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8569,7 +8591,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8601,7 +8624,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8633,7 +8657,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8666,7 +8691,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8698,7 +8724,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8730,7 +8757,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8762,7 +8790,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8793,7 +8822,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8838,7 +8868,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8870,7 +8901,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8901,7 +8933,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8932,7 +8965,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8963,7 +8997,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9070,7 +9105,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9103,7 +9139,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9136,7 +9173,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9169,7 +9207,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9201,7 +9240,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9232,7 +9272,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9263,7 +9304,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9295,7 +9337,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9327,7 +9370,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9358,7 +9402,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9390,7 +9435,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9422,7 +9468,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9454,7 +9501,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9487,7 +9535,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9519,7 +9568,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9551,7 +9601,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9584,7 +9635,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9616,7 +9668,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9648,7 +9701,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9681,7 +9735,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9713,7 +9768,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9745,7 +9801,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9777,7 +9834,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9808,7 +9866,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9853,7 +9912,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9885,7 +9945,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9916,7 +9977,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9947,7 +10009,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -9978,7 +10041,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -10085,7 +10149,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10118,7 +10183,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10151,7 +10217,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10184,7 +10251,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10216,7 +10284,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10247,7 +10316,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10278,7 +10348,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10310,7 +10381,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10342,7 +10414,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10373,7 +10446,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10405,7 +10479,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10437,7 +10512,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10469,7 +10545,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10502,7 +10579,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10534,7 +10612,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10566,7 +10645,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10599,7 +10679,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10631,7 +10712,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10663,7 +10745,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10696,7 +10779,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10728,7 +10812,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10760,7 +10845,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10792,7 +10878,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10823,7 +10910,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10868,7 +10956,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10900,7 +10989,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10931,7 +11021,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10962,7 +11053,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10993,7 +11085,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11231,7 +11324,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11264,7 +11358,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11297,7 +11392,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11330,7 +11426,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11363,7 +11460,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11396,7 +11494,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11504,7 +11603,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11537,7 +11637,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11570,7 +11671,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11603,7 +11705,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11636,7 +11739,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11669,7 +11773,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11777,7 +11882,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11810,7 +11916,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11843,7 +11950,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11876,7 +11984,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11909,7 +12018,8 @@ "y": 0.0, "z": -37.8 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -11942,7 +12052,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -12122,7 +12233,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12155,7 +12267,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12188,7 +12301,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12221,7 +12335,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12253,7 +12368,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12284,7 +12400,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12315,7 +12432,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12347,7 +12465,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12379,7 +12498,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12410,7 +12530,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12442,7 +12563,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12474,7 +12596,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12506,7 +12629,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12539,7 +12663,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12571,7 +12696,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12603,7 +12729,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12636,7 +12763,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12668,7 +12796,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12700,7 +12829,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12733,7 +12863,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12765,7 +12896,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12797,7 +12929,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12829,7 +12962,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12860,7 +12994,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12905,7 +13040,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12937,7 +13073,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12968,7 +13105,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12999,7 +13137,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13030,7 +13169,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13137,7 +13277,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13170,7 +13311,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13203,7 +13345,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13236,7 +13379,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13268,7 +13412,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13299,7 +13444,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13330,7 +13476,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13362,7 +13509,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13394,7 +13542,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13425,7 +13574,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13457,7 +13607,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13489,7 +13640,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13521,7 +13673,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13554,7 +13707,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13586,7 +13740,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13618,7 +13773,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13651,7 +13807,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13683,7 +13840,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13715,7 +13873,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13748,7 +13907,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13780,7 +13940,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13812,7 +13973,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13844,7 +14006,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13875,7 +14038,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13920,7 +14084,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13952,7 +14117,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13983,7 +14149,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14014,7 +14181,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14045,7 +14213,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14152,7 +14321,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14185,7 +14355,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14218,7 +14389,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14251,7 +14423,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14283,7 +14456,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14314,7 +14488,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14345,7 +14520,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14377,7 +14553,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14409,7 +14586,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14440,7 +14618,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14472,7 +14651,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14504,7 +14684,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14536,7 +14717,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14569,7 +14751,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14601,7 +14784,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14633,7 +14817,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14666,7 +14851,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14698,7 +14884,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14730,7 +14917,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14763,7 +14951,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14795,7 +14984,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14827,7 +15017,8 @@ "y": 0.0, "z": -35.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14859,7 +15050,8 @@ "y": 0.0, "z": -31.000000000000004 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14890,7 +15082,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14935,7 +15128,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14967,7 +15161,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14998,7 +15193,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15029,7 +15225,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15060,7 +15257,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -15283,7 +15481,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15315,7 +15514,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15361,7 +15561,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15393,7 +15594,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15425,7 +15627,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15471,7 +15674,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15517,7 +15721,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15548,7 +15753,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15579,7 +15785,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15674,7 +15881,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15706,7 +15914,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15752,7 +15961,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15784,7 +15994,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15816,7 +16027,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15862,7 +16074,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15908,7 +16121,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15939,7 +16153,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -15970,7 +16185,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -16065,7 +16281,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16097,7 +16314,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16143,7 +16361,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16175,7 +16394,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16207,7 +16427,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16253,7 +16474,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16299,7 +16521,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16330,7 +16553,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16361,7 +16585,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -16485,7 +16710,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16517,7 +16743,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16548,7 +16775,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16579,7 +16807,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16610,7 +16839,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16641,7 +16871,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16672,7 +16903,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16704,7 +16936,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16750,7 +16983,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16781,7 +17015,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16812,7 +17047,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16843,7 +17079,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16964,7 +17201,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -16996,7 +17234,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17027,7 +17266,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17058,7 +17298,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17089,7 +17330,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17120,7 +17362,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17151,7 +17394,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17183,7 +17427,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17229,7 +17474,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17260,7 +17506,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17291,7 +17538,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17322,7 +17570,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17443,7 +17692,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17475,7 +17725,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17506,7 +17757,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17537,7 +17789,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17568,7 +17821,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17599,7 +17853,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -17630,7 +17885,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17662,7 +17918,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17708,7 +17965,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17739,7 +17997,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17770,7 +18029,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17801,7 +18061,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19122,7 +19383,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19154,7 +19416,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19200,7 +19463,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19232,7 +19496,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19264,7 +19529,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19310,7 +19576,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19356,7 +19623,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19387,7 +19655,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19418,7 +19687,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19513,7 +19783,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19545,7 +19816,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19591,7 +19863,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19623,7 +19896,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19655,7 +19929,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19701,7 +19976,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19747,7 +20023,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19778,7 +20055,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19809,7 +20087,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19904,7 +20183,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19936,7 +20216,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19982,7 +20263,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20014,7 +20296,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20046,7 +20329,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20092,7 +20376,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20138,7 +20423,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20169,7 +20455,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20200,7 +20487,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20324,7 +20612,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20356,7 +20645,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20387,7 +20677,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20418,7 +20709,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20449,7 +20741,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20480,7 +20773,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20511,7 +20805,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20543,7 +20838,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20589,7 +20885,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20620,7 +20917,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20651,7 +20949,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20682,7 +20981,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20803,7 +21103,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20835,7 +21136,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20866,7 +21168,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20897,7 +21200,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20928,7 +21232,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20959,7 +21264,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20990,7 +21296,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21022,7 +21329,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21068,7 +21376,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21099,7 +21408,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21130,7 +21440,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21161,7 +21472,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21282,7 +21594,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21314,7 +21627,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21345,7 +21659,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21376,7 +21691,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21407,7 +21723,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21438,7 +21755,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21469,7 +21787,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21501,7 +21820,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21547,7 +21867,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21578,7 +21899,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21609,7 +21931,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21640,7 +21963,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21774,7 +22098,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21806,7 +22131,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21852,7 +22178,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21884,7 +22211,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21916,7 +22244,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21962,7 +22291,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22008,7 +22338,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22039,7 +22370,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22070,7 +22402,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22165,7 +22498,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22197,7 +22531,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22243,7 +22578,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22275,7 +22611,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22307,7 +22644,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22353,7 +22691,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22399,7 +22738,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22430,7 +22770,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22461,7 +22802,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22556,7 +22898,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22588,7 +22931,8 @@ "y": 0.0, "z": -36.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22634,7 +22978,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22666,7 +23011,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22698,7 +23044,8 @@ "y": 0.0, "z": -31.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22744,7 +23091,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22790,7 +23138,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22821,7 +23170,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22852,7 +23202,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22975,7 +23326,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23007,7 +23359,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23114,7 +23467,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23146,7 +23500,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23253,7 +23608,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23285,7 +23641,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23479,7 +23836,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23511,7 +23869,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23543,7 +23902,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23575,7 +23935,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23607,7 +23968,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23640,7 +24002,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23673,7 +24036,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23706,7 +24070,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23738,7 +24103,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23834,7 +24200,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -23866,7 +24233,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23898,7 +24266,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23930,7 +24299,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23962,7 +24332,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23995,7 +24366,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24028,7 +24400,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24061,7 +24434,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24093,7 +24467,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24189,7 +24564,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -24221,7 +24597,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24253,7 +24630,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24285,7 +24663,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24317,7 +24696,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24350,7 +24730,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24383,7 +24764,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24416,7 +24798,8 @@ "y": 0.0, "z": -37.75 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24448,7 +24831,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25861,7 +26245,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25893,7 +26278,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25926,7 +26312,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25958,7 +26345,8 @@ "y": 0.0, "z": -12.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26053,7 +26441,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26085,7 +26474,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26118,7 +26508,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26150,7 +26541,8 @@ "y": 0.0, "z": -12.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26245,7 +26637,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26277,7 +26670,8 @@ "y": 0.0, "z": -38.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26310,7 +26704,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26342,7 +26737,8 @@ "y": 0.0, "z": -12.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27817,6 +28213,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "AMPure Beads", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d7e862d601][OT2_S_v2_18_None_None_duplicateChoiceValue].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d7e862d601][OT2_S_v2_18_None_None_duplicateChoiceValue].json index 7ea850030fd..87b61a0454d 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d7e862d601][OT2_S_v2_18_None_None_duplicateChoiceValue].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d7e862d601][OT2_S_v2_18_None_None_duplicateChoiceValue].json @@ -43,6 +43,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Duplicate choice value" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json index 1e9b318abf5..aba7dd56957 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d8cb88b3b2][Flex_S_v2_16_P1000_96_TC_PartialTipPickupSingle].json @@ -3591,6 +3591,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [], diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d979799443][OT2_S_v2_20_8_None_SINGLE_HappyPath].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d979799443][OT2_S_v2_20_8_None_SINGLE_HappyPath].json new file mode 100644 index 00000000000..bd408636813 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[d979799443][OT2_S_v2_20_8_None_SINGLE_HappyPath].json @@ -0,0 +1,8261 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "displayName": "Partial Tip Rack", + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "8" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-300ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 64.49 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 300 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_300ul", + "tipLength": 59.3, + "tipOverlap": 7.47 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 74.24, + "z": 5.39 + }, + "A10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 74.24, + "z": 5.39 + }, + "A11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 74.24, + "z": 5.39 + }, + "A12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 74.24, + "z": 5.39 + }, + "A2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 74.24, + "z": 5.39 + }, + "A3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 74.24, + "z": 5.39 + }, + "A4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 74.24, + "z": 5.39 + }, + "A5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 74.24, + "z": 5.39 + }, + "A6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 74.24, + "z": 5.39 + }, + "A7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 74.24, + "z": 5.39 + }, + "A8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 74.24, + "z": 5.39 + }, + "A9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 74.24, + "z": 5.39 + }, + "B1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 65.24, + "z": 5.39 + }, + "B10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 65.24, + "z": 5.39 + }, + "B11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 65.24, + "z": 5.39 + }, + "B12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 65.24, + "z": 5.39 + }, + "B2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 65.24, + "z": 5.39 + }, + "B3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 65.24, + "z": 5.39 + }, + "B4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 65.24, + "z": 5.39 + }, + "B5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 65.24, + "z": 5.39 + }, + "B6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 65.24, + "z": 5.39 + }, + "B7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 65.24, + "z": 5.39 + }, + "B8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 65.24, + "z": 5.39 + }, + "B9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 65.24, + "z": 5.39 + }, + "C1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 56.24, + "z": 5.39 + }, + "C10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 56.24, + "z": 5.39 + }, + "C11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 56.24, + "z": 5.39 + }, + "C12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 56.24, + "z": 5.39 + }, + "C2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 56.24, + "z": 5.39 + }, + "C3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 56.24, + "z": 5.39 + }, + "C4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 56.24, + "z": 5.39 + }, + "C5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 56.24, + "z": 5.39 + }, + "C6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 56.24, + "z": 5.39 + }, + "C7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 56.24, + "z": 5.39 + }, + "C8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 56.24, + "z": 5.39 + }, + "C9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 56.24, + "z": 5.39 + }, + "D1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 47.24, + "z": 5.39 + }, + "D10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 47.24, + "z": 5.39 + }, + "D11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 47.24, + "z": 5.39 + }, + "D12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 47.24, + "z": 5.39 + }, + "D2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 47.24, + "z": 5.39 + }, + "D3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 47.24, + "z": 5.39 + }, + "D4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 47.24, + "z": 5.39 + }, + "D5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 47.24, + "z": 5.39 + }, + "D6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 47.24, + "z": 5.39 + }, + "D7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 47.24, + "z": 5.39 + }, + "D8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 47.24, + "z": 5.39 + }, + "D9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 47.24, + "z": 5.39 + }, + "E1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 38.24, + "z": 5.39 + }, + "E10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 38.24, + "z": 5.39 + }, + "E11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 38.24, + "z": 5.39 + }, + "E12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 38.24, + "z": 5.39 + }, + "E2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 38.24, + "z": 5.39 + }, + "E3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 38.24, + "z": 5.39 + }, + "E4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 38.24, + "z": 5.39 + }, + "E5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 38.24, + "z": 5.39 + }, + "E6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 38.24, + "z": 5.39 + }, + "E7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 38.24, + "z": 5.39 + }, + "E8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 38.24, + "z": 5.39 + }, + "E9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 38.24, + "z": 5.39 + }, + "F1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 29.24, + "z": 5.39 + }, + "F10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 29.24, + "z": 5.39 + }, + "F11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 29.24, + "z": 5.39 + }, + "F12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 29.24, + "z": 5.39 + }, + "F2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 29.24, + "z": 5.39 + }, + "F3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 29.24, + "z": 5.39 + }, + "F4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 29.24, + "z": 5.39 + }, + "F5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 29.24, + "z": 5.39 + }, + "F6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 29.24, + "z": 5.39 + }, + "F7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 29.24, + "z": 5.39 + }, + "F8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 29.24, + "z": 5.39 + }, + "F9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 29.24, + "z": 5.39 + }, + "G1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 20.24, + "z": 5.39 + }, + "G10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 20.24, + "z": 5.39 + }, + "G11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 20.24, + "z": 5.39 + }, + "G12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 20.24, + "z": 5.39 + }, + "G2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 20.24, + "z": 5.39 + }, + "G3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 20.24, + "z": 5.39 + }, + "G4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 20.24, + "z": 5.39 + }, + "G5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 20.24, + "z": 5.39 + }, + "G6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 20.24, + "z": 5.39 + }, + "G7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 20.24, + "z": 5.39 + }, + "G8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 20.24, + "z": 5.39 + }, + "G9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 20.24, + "z": 5.39 + }, + "H1": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 14.38, + "y": 11.24, + "z": 5.39 + }, + "H10": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 95.38, + "y": 11.24, + "z": 5.39 + }, + "H11": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 104.38, + "y": 11.24, + "z": 5.39 + }, + "H12": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 113.38, + "y": 11.24, + "z": 5.39 + }, + "H2": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 23.38, + "y": 11.24, + "z": 5.39 + }, + "H3": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 32.38, + "y": 11.24, + "z": 5.39 + }, + "H4": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 41.38, + "y": 11.24, + "z": 5.39 + }, + "H5": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 50.38, + "y": 11.24, + "z": 5.39 + }, + "H6": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 59.38, + "y": 11.24, + "z": 5.39 + }, + "H7": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 68.38, + "y": 11.24, + "z": 5.39 + }, + "H8": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 77.38, + "y": 11.24, + "z": 5.39 + }, + "H9": { + "depth": 59.3, + "diameter": 5.23, + "shape": "circular", + "totalLiquidVolume": 300, + "x": 86.38, + "y": 11.24, + "z": 5.39 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p300_multi_gen2", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "displayName": "2 Source Labware", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c910be62291bb94458a59cb4185c5180", + "notes": [], + "params": { + "displayName": "3 Destination Labware", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "3" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2f59e0552969594ba6ab06f03af324e1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 146.88, + "y": 192.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6b0053809cc5ea894449b20181bda1d6", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 164.88, + "y": 74.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9d2ba7d37a294b62afe4d91153783e8d", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 173.88, + "y": 74.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e8ce83939641089264883e922565169e", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 20.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 297.38, + "y": 74.24, + "z": 1.92 + }, + "volume": 20.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bc7a3401208e1ebbf2ef653b39f7ec05", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "71264e0d8f121750fadffa4de419d934", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "957785aca5143d6556af5c2cb3c59297", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 146.88, + "y": 201.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4d87d61c6915c64c29bf9601dce504d6", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 191.88, + "y": 74.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a6b3ab33bb429b17ddfcfa80e4795882", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 324.38, + "y": 74.24, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ad078a7983560bdf409c3fafb76a20df", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dc80dca10e7a8e4eb0148afda35d9f7f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "47e2a6e4e082a3edcd417887da1391ea", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 146.88, + "y": 210.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8fcfb95fb44994648ec969a5fcc049df", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 30.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 200.88, + "y": 74.24, + "z": 1.92 + }, + "volume": 30.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d5419055b04d30ef91ae9ad466be6814", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 5.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 333.38, + "y": 74.24, + "z": 1.92 + }, + "volume": 5.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "beb80898b59a1324e0cab2e514684044", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 5.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 342.38, + "y": 74.24, + "z": 1.92 + }, + "volume": 5.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b189af8f175fc84c04677212be3713cd", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "70fbf1f310a94ff9a76abbf78aed0f5d", + "notes": [], + "params": { + "flowRate": 94.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "98e8fdad613e2394706e4ed69bcacd5a", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "02ddfbbad3a85f2dea9649b185df31c0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d4b54bd1cc3ea0b0f00c771f6d069552", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 146.88, + "y": 219.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "touchTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8d44ea65bcbbc368be1bc486199c5c36", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "radius": 1.0, + "speed": 60.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -1.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 146.88, + "y": 65.24, + "z": 14.7 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ff637cfa2197a46f886f9a8f430cba99", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "05e32557ff805e84d62ca868c9d3ffb9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "09a9af20ba5025d9b73f1e8abb0ae921", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 228.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "886acb0a83206c4beffe85c574403c8c", + "notes": [], + "params": { + "axes": [ + "leftPlunger", + "leftZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0be564f428e87599bd9ad2c95132690f", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0d0b65bd3564dcbd484a27851516a8d8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bc1207d443da2a4be8c29c638ed95832", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 146.88, + "y": 237.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "800a566578fc2c77b3ba63a8740bc11d", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7cdd48fe814ff91d04f2541b5bda803f", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b73df6c1dc10a15fccad64146a9253e3", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "303e819bc213e27cd87898f84a0849d8", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "05d169be70745d1a65829f1a968b95f6", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "76cef1507e46661cad77313a0ed3727d", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "77bfe511646e7418f20da0817e3b96e9", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6c13cf87265353a92dc843600b9d9203", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ade2d2d2074263c9dd505940d90c2dfb", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cccc3a958942894e8046f7b40e5ca74f", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e9c371bfd5e628a170bfbecb5db3a9fd", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7de6a6935a0a885c8bbcaf24d799c23a", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "663261a619fb3978db881c0b49ee5757", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "23b170d0f6b4310456e22bff31768726", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ca000faa3e0a7c16d820dba7f034ea17", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "358e3d9564fef6580430c29ec1abc8be", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5ae5e07479909c02ab7c3380f3d6831c", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "aba2f3c1667e265516ba1b2f7a70bd9b", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e4401c648a31e63d991b887e1e967edf", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8653a8d80a26e9ae5e402cbbf005314c", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "79f12967defc4069a8c9957178510267", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d42541fb8e9dad7fd6df764276d97e85", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a5fe690c7ee2e46760c78e76c1799e32", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "prepareToAspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "147f19190dec5bb7e8082142106cdba8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b0f86fda12faff8a53661cffc418a681", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "27b170cfd7791ee5264cd8360381fc14", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c580bea6ba31532addf5ce76c8b0aa63", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 146.88, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3a50114af65ad0ce2b5e517279c3a5b6", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d370233cdd09b4925594d9f78744c355", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "492f10d86634ba52387d3434a2c96163", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b485fe600db6f864ef8a02a863d2f2df", + "notes": [], + "params": { + "displayName": "4 Source Labware", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "4" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d342a7a2f2d899e4ba66723854c7ac1c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 146.88, + "y": 255.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "02260787a80fe7fe99e23ac1fca8da38", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 32.38, + "y": 164.74, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd2bb421a55653e63dc6e6b099db9ed2", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 41.38, + "y": 164.74, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1d82060c5d578b28bb8742629681ee06", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 20.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 297.38, + "y": 74.24, + "z": 1.92 + }, + "volume": 20.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a5ac2cddaae59eceac79585766284669", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "99295ecf85a0cf8d016bd17b601324fe", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a369fddbe8c5d53e319616b9b2bab53a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 146.88, + "y": 246.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5e88ca16149d637e8176d3a1400ab9c9", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 59.38, + "y": 164.74, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "885e2d386d79566126acfb1b1f67fcf1", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 59.38, + "y": 164.74, + "z": 1.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "45f1aca48183f0e7195311f34d496160", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3c8459ed43bae008bb7ab9f21ff42e7b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d8e8437d353c7e28e01b69748e6922ce", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 155.88, + "y": 255.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d1ca4f52dc8c202b1b6e6f0e3daf5701", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 30.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 68.38, + "y": 164.74, + "z": 1.92 + }, + "volume": 30.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c8923d71e8c46050219eb612600dea5a", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 5.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 333.38, + "y": 74.24, + "z": 1.92 + }, + "volume": 5.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3e3f3ed4d9a0428d5a32f1636f928e91", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 5.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -13.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 342.38, + "y": 74.24, + "z": 1.92 + }, + "volume": 5.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "023f69c7021c531af4d4fda2f9387b50", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": false, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 347.84000000000003, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowOutInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bc69177b8a2a770ff7605145761fff5d", + "notes": [], + "params": { + "flowRate": 94.0, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "51975e367416e2f6c1d2a522150b3500", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d3bbbb8214d81f471444571dfb2dc3a2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9dabbd470c235976667f28d0444dd3dc", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B2" + }, + "result": { + "position": { + "x": 155.88, + "y": 246.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "touchTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b26a3c132f8ad3706405bfd0c2f7e551", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "radius": 1.0, + "speed": 60.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -1.0 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 279.38, + "y": 65.24, + "z": 14.7 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cd63b658ce3fcddaa1ad64e6bc0ae46d", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "17ca68e5c158de12859b20cb7544b5db", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "240dd94e8911c623b8ac0c62a5dbf42d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C2" + }, + "result": { + "position": { + "x": 155.88, + "y": 237.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "10048f466a9c345ca8123a5b66e4d1a4", + "notes": [], + "params": { + "axes": [ + "leftPlunger", + "leftZ" + ] + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e6ba244c71314bb7c6728c4c9040b773", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 363.89500000000004, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c29530b269acd8ca7ae19bd3827db60d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7c3557cb39beec1a448812ae0e9d8caf", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D2" + }, + "result": { + "position": { + "x": 155.88, + "y": 228.24, + "z": 64.69 + }, + "tipDiameter": 5.23, + "tipLength": 51.099999999999994, + "tipVolume": 300.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "23f4e567a15a351a336a65387550c6fb", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f2758b7979d09db42c526e37ad996ce7", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8ebe6d7fb82b01a1a40863975097487b", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cdb131745372c3f23f795b8b6c8e3a27", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ffa7e9319827cf7160a227f55a5044e1", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5090e2ec6f1af16b3ad782af703fab3b", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f3c375ef10c41708becdf325ad99c66e", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3c99b73256ef0546bf970558fea377d9", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "14b31f7e50d50cc2a1fa62ffff4dea73", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "48495414937db819949301728ed14335", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b4c832b00b05cd24adb5de47460414e7", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8911314efdc1b5b2ad34e31851319ed8", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c648d8fbb5c317dee166662efb023ed8", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9e2b3d6d808e30892b38343c98a3188d", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7ec0e675702e7c0de055e628d30f98ad", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "16e8c93ac034471d25dfdd5f4daf5f2c", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "52e6d8801aa49ddc3e9ab9bb5bacf501", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b5b598a56f39cc2b5276b4dddc00b284", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e2d6c04a26fb8bd4cf5d757ef1c4712f", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "pushOut": 0.0, + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "622b545e9dd3846a153993d91f7b5c05", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "51100f9df2c951cf0ce7ded1c52509e2", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2d8c06eb56aad35557edf58cd6014e81", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "blowout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "142753a1e2a3a2f44806a9fca2cde945", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 5.0000000000000036 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 20.700000000000003 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "prepareToAspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8b59b2d67c8199cc77856f982a528565", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToWell", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "44d4da57eafecbbfd7d9862645284458", + "notes": [], + "params": { + "forceDirect": false, + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "aspirate", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0a8b0de159b4f9afd550efc670b6fde4", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dispense", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5d769cef47874e6bfa53fe86a15f64d9", + "notes": [], + "params": { + "flowRate": 94.0, + "labwareId": "UUID", + "pipetteId": "UUID", + "volume": 10.0, + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": -12.78 + }, + "origin": "top", + "volumeOffset": 0.0 + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 279.38, + "y": 47.24, + "z": 2.92 + }, + "volume": 10.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4d6c3e50d408e00ae799218a23732825", + "notes": [], + "params": { + "addressableAreaName": "fixedTrash", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 331.785, + "y": 351.5, + "z": 82.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "943db37c01519ce052292fdf7485a6e1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [], + "files": [ + { + "name": "OT2_S_v2_20_8_None_SINGLE_HappyPath.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_300ul/1", + "displayName": "Partial Tip Rack", + "id": "UUID", + "loadName": "opentrons_96_tiprack_300ul", + "location": { + "slotName": "8" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "displayName": "2 Source Labware", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "displayName": "3 Destination Labware", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "3" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "displayName": "4 Source Labware", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "4" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "OT2 8 Channel pipette and a SINGLE partial tip configuration.", + "protocolName": "OT2 8 Channel SINGLE Happy Path A1 and H1" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p300_multi_gen2" + } + ], + "result": "ok", + "robotType": "OT-2 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[da326082e1][pl_Hyperplus_tiptracking_V4_final].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[da326082e1][pl_Hyperplus_tiptracking_V4_final].json index d8ec32acad8..2651a003e75 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[da326082e1][pl_Hyperplus_tiptracking_V4_final].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[da326082e1][pl_Hyperplus_tiptracking_V4_final].json @@ -10782,7 +10782,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -10815,7 +10816,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10848,7 +10850,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10881,7 +10884,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10914,7 +10918,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10947,7 +10952,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10980,7 +10986,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11013,7 +11020,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11046,7 +11054,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11079,7 +11088,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11112,7 +11122,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11145,7 +11156,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11178,7 +11190,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11211,7 +11224,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11244,7 +11258,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11277,7 +11292,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11310,7 +11326,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11343,7 +11360,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11376,7 +11394,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11409,7 +11428,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11442,7 +11462,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11475,7 +11496,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11869,7 +11891,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -11902,7 +11925,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11935,7 +11959,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11968,7 +11993,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12001,7 +12027,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12034,7 +12061,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12067,7 +12095,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12100,7 +12129,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12133,7 +12163,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12166,7 +12197,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12199,7 +12231,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12232,7 +12265,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12265,7 +12299,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12298,7 +12333,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12331,7 +12367,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12364,7 +12401,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12397,7 +12435,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12430,7 +12469,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12463,7 +12503,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12496,7 +12537,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12529,7 +12571,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12562,7 +12605,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12970,7 +13014,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -13003,7 +13048,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13036,7 +13082,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13069,7 +13116,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13102,7 +13150,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13135,7 +13184,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13168,7 +13218,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13201,7 +13252,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13234,7 +13286,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13267,7 +13320,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13300,7 +13354,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13333,7 +13388,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13366,7 +13422,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13399,7 +13456,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13432,7 +13490,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13465,7 +13524,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13498,7 +13558,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13531,7 +13592,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13564,7 +13626,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13597,7 +13660,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13630,7 +13694,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13663,7 +13728,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13841,7 +13907,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13874,7 +13941,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13907,7 +13975,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13940,7 +14009,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13973,7 +14043,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14006,7 +14077,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14039,7 +14111,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14072,7 +14145,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14105,7 +14179,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14138,7 +14213,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14171,7 +14247,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14204,7 +14281,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14237,7 +14315,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14270,7 +14349,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14303,7 +14383,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14336,7 +14417,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14369,7 +14451,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14402,7 +14485,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14435,7 +14519,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14468,7 +14553,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14501,7 +14587,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14534,7 +14621,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16161,7 +16249,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16194,7 +16283,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16227,7 +16317,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16260,7 +16351,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16293,7 +16385,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16326,7 +16419,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16359,7 +16453,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16392,7 +16487,8 @@ "y": 1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16425,7 +16521,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16458,7 +16555,8 @@ "y": 3.0999999999999943, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16491,7 +16589,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16524,7 +16623,8 @@ "y": 1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16557,7 +16657,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16590,7 +16691,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16623,7 +16725,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16656,7 +16759,8 @@ "y": -1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16689,7 +16793,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16722,7 +16827,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16755,7 +16861,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16788,7 +16895,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16821,7 +16929,8 @@ "y": 0.0, "z": -4.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16854,7 +16963,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16887,7 +16997,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16920,7 +17031,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16953,7 +17065,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16986,7 +17099,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17019,7 +17133,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17052,7 +17167,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17085,7 +17201,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17118,7 +17235,8 @@ "y": 1.75, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17151,7 +17269,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17184,7 +17303,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17217,7 +17337,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17250,7 +17371,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17283,7 +17405,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17316,7 +17439,8 @@ "y": -0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17349,7 +17473,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17382,7 +17507,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17415,7 +17541,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17448,7 +17575,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17481,7 +17609,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17514,7 +17643,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17547,7 +17677,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17580,7 +17711,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17613,7 +17745,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17646,7 +17779,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17679,7 +17813,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17712,7 +17847,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17745,7 +17881,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17778,7 +17915,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17811,7 +17949,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17844,7 +17983,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17877,7 +18017,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17910,7 +18051,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17943,7 +18085,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17976,7 +18119,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18009,7 +18153,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18042,7 +18187,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18282,7 +18428,8 @@ "y": 0.0, "z": -14.749999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18329,7 +18476,8 @@ "y": 0.0, "z": -14.749999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18361,7 +18509,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18393,7 +18542,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18426,7 +18576,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -18458,7 +18609,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -18490,7 +18642,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -18668,7 +18821,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18700,7 +18854,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18732,7 +18887,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18765,7 +18921,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18811,7 +18968,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18843,7 +19001,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19007,7 +19166,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19039,7 +19199,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19071,7 +19232,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19104,7 +19266,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -19150,7 +19313,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -19182,7 +19346,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -19360,7 +19525,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -19392,7 +19558,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -19424,7 +19591,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -19457,7 +19625,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19503,7 +19672,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19535,7 +19705,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19699,7 +19870,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19731,7 +19903,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19763,7 +19936,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19796,7 +19970,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -19842,7 +20017,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -19874,7 +20050,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -20099,7 +20276,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20131,7 +20309,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20163,7 +20342,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20196,7 +20376,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20229,7 +20410,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20262,7 +20444,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20295,7 +20478,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20328,7 +20512,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20361,7 +20546,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20394,7 +20580,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20427,7 +20614,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20460,7 +20648,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20493,7 +20682,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20526,7 +20716,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20559,7 +20750,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20592,7 +20784,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20625,7 +20818,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20658,7 +20852,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20691,7 +20886,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20724,7 +20920,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20757,7 +20954,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20790,7 +20988,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20823,7 +21022,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20856,7 +21056,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20888,7 +21089,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20920,7 +21122,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21146,7 +21349,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21178,7 +21382,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21210,7 +21415,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21244,7 +21450,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21276,7 +21483,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21308,7 +21516,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21596,7 +21805,8 @@ "y": 0.0, "z": -4.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21629,7 +21839,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21662,7 +21873,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21695,7 +21907,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21728,7 +21941,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21761,7 +21975,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21794,7 +22009,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21827,7 +22043,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21860,7 +22077,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21893,7 +22111,8 @@ "y": 1.75, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21926,7 +22145,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21959,7 +22179,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -21992,7 +22213,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22025,7 +22247,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22058,7 +22281,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22091,7 +22315,8 @@ "y": -0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22124,7 +22349,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22157,7 +22383,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22190,7 +22417,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22223,7 +22451,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22256,7 +22485,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22289,7 +22519,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22322,7 +22553,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22355,7 +22587,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22388,7 +22621,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22421,7 +22655,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22454,7 +22689,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22487,7 +22723,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22520,7 +22757,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22553,7 +22791,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22586,7 +22825,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22619,7 +22859,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22652,7 +22893,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22685,7 +22927,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22718,7 +22961,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22751,7 +22995,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22784,7 +23029,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22817,7 +23063,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22850,7 +23097,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22883,7 +23131,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23436,7 +23685,8 @@ "y": 0.0, "z": -28.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23469,7 +23719,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23502,7 +23753,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23535,7 +23787,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23568,7 +23821,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23601,7 +23855,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23634,7 +23889,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23667,7 +23923,8 @@ "y": 1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23700,7 +23957,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23733,7 +23991,8 @@ "y": 3.0999999999999943, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23766,7 +24025,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23799,7 +24059,8 @@ "y": 1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23832,7 +24093,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23865,7 +24127,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23898,7 +24161,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23931,7 +24195,8 @@ "y": -1.5500000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23964,7 +24229,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23997,7 +24263,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24030,7 +24297,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24063,7 +24331,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24096,7 +24365,8 @@ "y": 0.0, "z": -4.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24129,7 +24399,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24162,7 +24433,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24195,7 +24467,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24228,7 +24501,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24261,7 +24535,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24294,7 +24569,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24327,7 +24603,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24360,7 +24637,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24393,7 +24671,8 @@ "y": 1.75, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24426,7 +24705,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24459,7 +24739,8 @@ "y": 0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24492,7 +24773,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24525,7 +24807,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24558,7 +24841,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24591,7 +24875,8 @@ "y": -0.875, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24624,7 +24909,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24657,7 +24943,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24690,7 +24977,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24723,7 +25011,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24756,7 +25045,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24789,7 +25079,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24822,7 +25113,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24855,7 +25147,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24888,7 +25181,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24921,7 +25215,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24954,7 +25249,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24987,7 +25283,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25020,7 +25317,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25053,7 +25351,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25086,7 +25385,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25119,7 +25419,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25152,7 +25453,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25185,7 +25487,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25218,7 +25521,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25251,7 +25555,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25284,7 +25589,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25317,7 +25623,8 @@ "y": 0.0, "z": -6.949999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25557,7 +25864,8 @@ "y": 0.0, "z": -14.749999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25604,7 +25912,8 @@ "y": 0.0, "z": -14.749999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25636,7 +25945,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25668,7 +25978,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25701,7 +26012,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -25733,7 +26045,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -25765,7 +26078,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -25943,7 +26257,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25975,7 +26290,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26007,7 +26323,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26040,7 +26357,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26086,7 +26404,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26118,7 +26437,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26282,7 +26602,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26314,7 +26635,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26346,7 +26668,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26379,7 +26702,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -26425,7 +26749,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -26457,7 +26782,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -26635,7 +26961,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26667,7 +26994,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26699,7 +27027,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26732,7 +27061,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26778,7 +27108,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26810,7 +27141,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26974,7 +27306,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27006,7 +27339,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27038,7 +27372,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27071,7 +27406,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -27117,7 +27453,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -27149,7 +27486,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -27374,7 +27712,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27406,7 +27745,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27438,7 +27778,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -27471,7 +27812,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27504,7 +27846,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27537,7 +27880,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27570,7 +27914,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27603,7 +27948,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27636,7 +27982,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27669,7 +28016,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27702,7 +28050,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27735,7 +28084,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27768,7 +28118,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27801,7 +28152,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27834,7 +28186,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27867,7 +28220,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27900,7 +28254,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27933,7 +28288,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27966,7 +28322,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27999,7 +28356,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28032,7 +28390,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28065,7 +28424,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28098,7 +28458,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28131,7 +28492,8 @@ "y": 0.0, "z": -10.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28163,7 +28525,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28195,7 +28558,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28421,7 +28785,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28453,7 +28818,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28485,7 +28851,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28519,7 +28886,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28551,7 +28919,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28583,7 +28952,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28774,6 +29144,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "DNA sample of known quantity", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dabb7872d8][Flex_S_v2_19_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dabb7872d8][Flex_S_v2_19_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json index 0e252fdc93b..c52ed516ba1 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dabb7872d8][Flex_S_v2_19_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dabb7872d8][Flex_S_v2_19_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json @@ -8381,7 +8381,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8414,7 +8415,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8536,7 +8538,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8569,7 +8572,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8692,7 +8696,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8725,7 +8730,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -8834,7 +8840,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8867,7 +8874,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -8976,7 +8984,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9009,7 +9018,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -9118,7 +9128,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9151,7 +9162,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -9260,7 +9272,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9293,7 +9306,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -9402,7 +9416,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9435,7 +9450,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -9544,7 +9560,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9577,7 +9594,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -9686,7 +9704,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9719,7 +9738,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9828,7 +9848,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9861,7 +9882,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9970,7 +9992,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10003,7 +10026,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -10145,7 +10169,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10177,7 +10202,8 @@ "y": 0.0, "z": 29.999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10209,7 +10235,8 @@ "y": 0.0, "z": 29.999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10242,7 +10269,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10416,7 +10444,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10450,7 +10479,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10483,7 +10513,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10517,7 +10548,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10550,7 +10582,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10583,7 +10616,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10616,7 +10650,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10649,7 +10684,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10682,7 +10718,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10716,7 +10753,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10749,7 +10787,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10783,7 +10822,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10816,7 +10856,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10850,7 +10891,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10883,7 +10925,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10917,7 +10960,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10950,7 +10994,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10983,7 +11028,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11016,7 +11062,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12613,7 +12660,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12675,7 +12723,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12738,7 +12787,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12800,7 +12850,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12865,7 +12916,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12928,7 +12980,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13111,6 +13164,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "High Quality H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[db1fae41ec][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[db1fae41ec][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_3].json new file mode 100644 index 00000000000..62ea1e316b2 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[db1fae41ec][Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_3].json @@ -0,0 +1,1261 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "ValueError [line 167]: Partial column configuration is only supported on 8-Channel pipettes.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "ValueError: Partial column configuration is only supported on 8-Channel pipettes.", + "errorCode": "4000", + "errorInfo": { + "args": "('Partial column configuration is only supported on 8-Channel pipettes.',)", + "class": "ValueError", + "traceback": " File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/execution/execute_python.py\", line N, in exec_run\n exec(\"run(__context)\", new_globs)\n\n File \"\", line N, in \n\n File \"Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_3.py\", line N, in run\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocols/api_support/util.py\", line N, in _check_version_wrapper\n return decorated_obj(*args, **kwargs)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/instrument_context.py\", line N, in configure_nozzle_layout\n self._raise_if_configuration_not_supported_by_pipette(style)\n\n File \"/usr/local/lib/python3.10/site-packages/opentrons/protocol_api/instrument_context.py\", line N, in _raise_if_configuration_not_supported_by_pipette\n raise ValueError(\n" + }, + "errorType": "PythonException", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_and_8_Overrides_InvalidConfigs_Override_ninety_six_partial_column_3.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Invalid tip configs that should error" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dbba7a71a8][OT2_S_v2_16_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dbba7a71a8][OT2_S_v2_16_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json index 2288dccf926..9e3cf07280a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dbba7a71a8][OT2_S_v2_16_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[dbba7a71a8][OT2_S_v2_16_NO_PIPETTES_TC_VerifyThermocyclerLoadedSlots].json @@ -145,6 +145,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de4249ddfb][Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de4249ddfb][Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict].json index 0353b26aed1..2eb5308529f 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de4249ddfb][Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[de4249ddfb][Flex_X_v2_16_NO_PIPETTES_TC_TrashBinAndThermocyclerConflict].json @@ -171,6 +171,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Thermocycler conflict 1" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e0b0133ffb][pl_Illumina_Stranded_total_RNA_Ribo_Zero_protocol].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e0b0133ffb][pl_Illumina_Stranded_total_RNA_Ribo_Zero_protocol].json index bc22292941d..1bb680c2c4f 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e0b0133ffb][pl_Illumina_Stranded_total_RNA_Ribo_Zero_protocol].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e0b0133ffb][pl_Illumina_Stranded_total_RNA_Ribo_Zero_protocol].json @@ -16094,7 +16094,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16128,7 +16129,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16160,7 +16162,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16208,7 +16211,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16242,7 +16246,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16275,7 +16280,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16309,7 +16315,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16342,7 +16349,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16376,7 +16384,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16409,7 +16418,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16443,7 +16453,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16476,7 +16487,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16510,7 +16522,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16543,7 +16556,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16577,7 +16591,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16610,7 +16625,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16644,7 +16660,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16677,7 +16694,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16711,7 +16729,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16744,7 +16763,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16778,7 +16798,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16811,7 +16832,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16845,7 +16867,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16891,7 +16914,8 @@ "y": 0.0, "z": -11.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17957,7 +17981,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17991,7 +18016,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18023,7 +18049,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18071,7 +18098,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18105,7 +18133,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18138,7 +18167,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18172,7 +18202,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18205,7 +18236,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18239,7 +18271,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18272,7 +18305,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18306,7 +18340,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18339,7 +18374,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18373,7 +18409,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18406,7 +18443,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18440,7 +18478,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18473,7 +18512,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18507,7 +18547,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18540,7 +18581,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18574,7 +18616,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18607,7 +18650,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18641,7 +18685,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18674,7 +18719,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18708,7 +18754,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18754,7 +18801,8 @@ "y": 0.0, "z": -11.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18972,7 +19020,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19006,7 +19055,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19039,7 +19089,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19073,7 +19124,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19106,7 +19158,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19140,7 +19193,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19173,7 +19227,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19207,7 +19262,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19240,7 +19296,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19274,7 +19331,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19307,7 +19365,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19341,7 +19400,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19374,7 +19434,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19408,7 +19469,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19441,7 +19503,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19475,7 +19538,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19508,7 +19572,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19542,7 +19607,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19575,7 +19641,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19609,7 +19676,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19655,7 +19723,8 @@ "y": 0.0, "z": -11.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19687,7 +19756,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19721,7 +19791,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19767,7 +19838,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19815,7 +19887,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19849,7 +19922,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19882,7 +19956,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19916,7 +19991,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19949,7 +20025,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19983,7 +20060,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20016,7 +20094,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20050,7 +20129,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20083,7 +20163,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20117,7 +20198,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20150,7 +20232,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20184,7 +20267,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20217,7 +20301,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20251,7 +20336,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20284,7 +20370,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20318,7 +20405,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20364,7 +20452,8 @@ "y": 0.0, "z": -11.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20588,7 +20677,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20622,7 +20712,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20655,7 +20746,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20689,7 +20781,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20722,7 +20815,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20756,7 +20850,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20789,7 +20884,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20823,7 +20919,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20856,7 +20953,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20890,7 +20988,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20923,7 +21022,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20957,7 +21057,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20990,7 +21091,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21024,7 +21126,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21057,7 +21160,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21091,7 +21195,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21124,7 +21229,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21158,7 +21264,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21191,7 +21298,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21225,7 +21333,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21258,7 +21367,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21292,7 +21402,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21325,7 +21436,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21359,7 +21471,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21392,7 +21505,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21426,7 +21540,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21459,7 +21574,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21493,7 +21609,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21526,7 +21643,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21560,7 +21678,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21593,7 +21712,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21627,7 +21747,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21660,7 +21781,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21694,7 +21816,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21727,7 +21850,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21761,7 +21885,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21794,7 +21919,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21828,7 +21954,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21861,7 +21988,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21894,7 +22022,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21940,7 +22069,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22047,7 +22177,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22080,7 +22211,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22113,7 +22245,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22147,7 +22280,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22180,7 +22314,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22214,7 +22349,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22247,7 +22383,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22281,7 +22418,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22314,7 +22452,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22348,7 +22487,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22381,7 +22521,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22415,7 +22556,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22448,7 +22590,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22482,7 +22625,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22515,7 +22659,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22549,7 +22694,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22582,7 +22728,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22616,7 +22763,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22649,7 +22797,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22683,7 +22832,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22716,7 +22866,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22750,7 +22901,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22783,7 +22935,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22817,7 +22970,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22850,7 +23004,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22884,7 +23039,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22917,7 +23073,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22951,7 +23108,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22984,7 +23142,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23018,7 +23177,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23051,7 +23211,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23085,7 +23246,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23131,7 +23293,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23284,7 +23447,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23438,7 +23602,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23471,7 +23636,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23593,7 +23759,8 @@ "y": 0.0, "z": -8.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23626,7 +23793,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23780,7 +23948,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23813,7 +23982,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23935,7 +24105,8 @@ "y": 0.0, "z": -8.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23968,7 +24139,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24122,7 +24294,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24308,7 +24481,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24342,7 +24516,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24388,7 +24563,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24436,7 +24612,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24470,7 +24647,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24503,7 +24681,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24537,7 +24716,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24570,7 +24750,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24604,7 +24785,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24637,7 +24819,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24671,7 +24854,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24704,7 +24888,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24738,7 +24923,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24771,7 +24957,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24805,7 +24992,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24838,7 +25026,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24872,7 +25061,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24905,7 +25095,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24939,7 +25130,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24972,7 +25164,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25006,7 +25199,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25039,7 +25233,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25073,7 +25268,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25106,7 +25302,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25140,7 +25337,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25173,7 +25371,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25207,7 +25406,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25240,7 +25440,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25274,7 +25475,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25307,7 +25509,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25341,7 +25544,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25374,7 +25578,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25408,7 +25613,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25441,7 +25647,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25475,7 +25682,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25508,7 +25716,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25542,7 +25751,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25575,7 +25785,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25609,7 +25820,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25642,7 +25854,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25676,7 +25889,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25709,7 +25923,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25743,7 +25958,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25789,7 +26005,8 @@ "y": 0.0, "z": -11.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25942,7 +26159,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25976,7 +26194,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26022,7 +26241,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26238,7 +26458,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26272,7 +26493,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26318,7 +26540,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26366,7 +26589,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26400,7 +26624,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26433,7 +26658,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26467,7 +26693,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26500,7 +26727,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26534,7 +26762,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26567,7 +26796,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26601,7 +26831,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26634,7 +26865,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26668,7 +26900,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26701,7 +26934,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26735,7 +26969,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26768,7 +27003,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26802,7 +27038,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26835,7 +27072,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26869,7 +27107,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26902,7 +27141,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26936,7 +27176,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26969,7 +27210,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27003,7 +27245,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27049,7 +27292,8 @@ "y": 0.0, "z": -11.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27285,7 +27529,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27319,7 +27564,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27352,7 +27598,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27386,7 +27633,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27419,7 +27667,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27453,7 +27702,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27486,7 +27736,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27520,7 +27771,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27553,7 +27805,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27587,7 +27840,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27620,7 +27874,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27654,7 +27909,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27687,7 +27943,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27721,7 +27978,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27754,7 +28012,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27788,7 +28047,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27821,7 +28081,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27855,7 +28116,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27888,7 +28150,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27922,7 +28185,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27968,7 +28232,8 @@ "y": 0.0, "z": -11.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -28000,7 +28265,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -28033,7 +28299,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28082,7 +28349,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28116,7 +28384,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28149,7 +28418,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28183,7 +28453,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28216,7 +28487,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28250,7 +28522,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28283,7 +28556,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28317,7 +28591,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28350,7 +28625,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28384,7 +28660,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28417,7 +28694,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28451,7 +28729,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28484,7 +28763,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28518,7 +28798,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28551,7 +28832,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28585,7 +28867,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28618,7 +28901,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28652,7 +28936,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28685,7 +28970,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28719,7 +29005,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28765,7 +29052,8 @@ "y": 0.0, "z": -11.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29006,7 +29294,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29039,7 +29328,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29088,7 +29378,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29122,7 +29413,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29155,7 +29447,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29189,7 +29482,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29222,7 +29516,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29256,7 +29551,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29289,7 +29585,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29323,7 +29620,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29356,7 +29654,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29390,7 +29689,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29423,7 +29723,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29457,7 +29758,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29490,7 +29792,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29524,7 +29827,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29557,7 +29861,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29591,7 +29896,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29624,7 +29930,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29658,7 +29965,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29691,7 +29999,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29725,7 +30034,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29771,7 +30081,8 @@ "y": 0.0, "z": -11.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -29991,7 +30302,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30025,7 +30337,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30058,7 +30371,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30092,7 +30406,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30125,7 +30440,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30159,7 +30475,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30192,7 +30509,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30226,7 +30544,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30259,7 +30578,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30293,7 +30613,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30326,7 +30647,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30360,7 +30682,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30393,7 +30716,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30427,7 +30751,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30460,7 +30785,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30494,7 +30820,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30527,7 +30854,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30561,7 +30889,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30594,7 +30923,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30628,7 +30958,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30661,7 +30992,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30695,7 +31027,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30728,7 +31061,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30762,7 +31096,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30795,7 +31130,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30829,7 +31165,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30862,7 +31199,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30896,7 +31234,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30929,7 +31268,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30963,7 +31303,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -30996,7 +31337,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31030,7 +31372,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31063,7 +31406,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31097,7 +31441,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31130,7 +31475,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31164,7 +31510,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31197,7 +31544,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31231,7 +31579,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31264,7 +31613,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31298,7 +31648,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31331,7 +31682,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31365,7 +31717,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31398,7 +31751,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31432,7 +31786,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31465,7 +31820,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31499,7 +31855,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31532,7 +31889,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31566,7 +31924,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31599,7 +31958,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31633,7 +31993,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31666,7 +32027,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31700,7 +32062,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31733,7 +32096,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31767,7 +32131,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31800,7 +32165,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31834,7 +32200,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31867,7 +32234,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31901,7 +32269,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31934,7 +32303,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31968,7 +32338,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32001,7 +32372,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32035,7 +32407,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32068,7 +32441,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32102,7 +32476,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32135,7 +32510,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32169,7 +32545,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32202,7 +32579,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32236,7 +32614,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32269,7 +32648,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32303,7 +32683,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32336,7 +32717,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32370,7 +32752,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32403,7 +32786,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32437,7 +32821,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32470,7 +32855,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32504,7 +32890,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32537,7 +32924,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32571,7 +32959,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32604,7 +32993,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32638,7 +33028,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32671,7 +33062,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32705,7 +33097,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32738,7 +33131,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32772,7 +33166,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32805,7 +33200,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32839,7 +33235,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32872,7 +33269,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32906,7 +33304,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32939,7 +33338,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -32973,7 +33373,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33006,7 +33407,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33040,7 +33442,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33073,7 +33476,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33107,7 +33511,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33140,7 +33545,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33174,7 +33580,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33207,7 +33614,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33241,7 +33649,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33274,7 +33683,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33307,7 +33717,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33353,7 +33764,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33460,7 +33872,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -33493,7 +33906,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33526,7 +33940,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33560,7 +33975,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33593,7 +34009,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33627,7 +34044,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33660,7 +34078,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33694,7 +34113,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33727,7 +34147,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33761,7 +34182,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33794,7 +34216,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33828,7 +34251,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33861,7 +34285,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33895,7 +34320,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33928,7 +34354,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33962,7 +34389,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33995,7 +34423,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34029,7 +34458,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34062,7 +34492,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34096,7 +34527,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34129,7 +34561,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34163,7 +34596,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34209,7 +34643,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34362,7 +34797,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34395,7 +34831,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34549,7 +34986,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -34582,7 +35020,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34704,7 +35143,8 @@ "y": 0.0, "z": -8.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34737,7 +35177,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34891,7 +35332,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -34924,7 +35366,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35046,7 +35489,8 @@ "y": 0.0, "z": -8.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35079,7 +35523,8 @@ "y": 0.0, "z": -14.649999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35233,7 +35678,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35419,7 +35865,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -35453,7 +35900,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35499,7 +35947,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35547,7 +35996,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35581,7 +36031,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35614,7 +36065,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35648,7 +36100,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35681,7 +36134,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35715,7 +36169,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35748,7 +36203,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35782,7 +36238,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35815,7 +36272,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35849,7 +36307,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35882,7 +36341,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35916,7 +36376,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35949,7 +36410,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35983,7 +36445,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36016,7 +36479,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36050,7 +36514,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36083,7 +36548,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36117,7 +36583,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36150,7 +36617,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36184,7 +36652,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36217,7 +36686,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36251,7 +36721,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36284,7 +36755,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36318,7 +36790,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36351,7 +36824,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36385,7 +36859,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36418,7 +36893,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36452,7 +36928,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36485,7 +36962,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36519,7 +36997,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36552,7 +37031,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36586,7 +37066,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36619,7 +37100,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36653,7 +37135,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36686,7 +37169,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36720,7 +37204,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36753,7 +37238,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36787,7 +37273,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36820,7 +37307,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36854,7 +37342,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36887,7 +37376,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36921,7 +37411,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36954,7 +37445,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36988,7 +37480,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37021,7 +37514,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37055,7 +37549,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37088,7 +37583,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37122,7 +37618,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37155,7 +37652,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37189,7 +37687,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37235,7 +37734,8 @@ "y": 0.0, "z": -11.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37388,7 +37888,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37422,7 +37923,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37468,7 +37970,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37684,7 +38187,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -37717,7 +38221,8 @@ "y": 0.0, "z": -10.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37766,7 +38271,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37800,7 +38306,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37833,7 +38340,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37867,7 +38375,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37900,7 +38409,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37934,7 +38444,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -37967,7 +38478,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38001,7 +38513,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38034,7 +38547,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38068,7 +38582,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38101,7 +38616,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38135,7 +38651,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38168,7 +38685,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38202,7 +38720,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38235,7 +38754,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38269,7 +38789,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38302,7 +38823,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38336,7 +38858,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38369,7 +38892,8 @@ "y": 0.0, "z": -14.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38403,7 +38927,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38449,7 +38974,8 @@ "y": 0.0, "z": -11.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -38521,7 +39047,14 @@ }, "id": "UUID", "key": "f524340032354f66bf69110d530e98ad", - "notes": [], + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], "params": { "labwareId": "UUID", "newLocation": { @@ -38701,6 +39234,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Dandra Howell ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e18bdd6f5d][Flex_S_2_15_P1000M_P50M_GRIP_HS_TM_MB_TC_KAPALibraryQuantv4_8].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e18bdd6f5d][Flex_S_2_15_P1000M_P50M_GRIP_HS_TM_MB_TC_KAPALibraryQuantv4_8].json index 7dabd545852..6c6c30ace61 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e18bdd6f5d][Flex_S_2_15_P1000M_P50M_GRIP_HS_TM_MB_TC_KAPALibraryQuantv4_8].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e18bdd6f5d][Flex_S_2_15_P1000M_P50M_GRIP_HS_TM_MB_TC_KAPALibraryQuantv4_8].json @@ -17682,7 +17682,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -17715,7 +17716,8 @@ "y": 0.0, "z": -14.449999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17748,7 +17750,8 @@ "y": 0.0, "z": -14.449999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -17780,7 +17783,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -17811,7 +17815,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -17843,7 +17848,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -17876,7 +17882,8 @@ "y": 0.0, "z": -14.449999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17909,7 +17916,8 @@ "y": 0.0, "z": -14.449999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -17941,7 +17949,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -17972,7 +17981,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18004,7 +18014,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18037,7 +18048,8 @@ "y": 0.0, "z": -14.449999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18070,7 +18082,8 @@ "y": 0.0, "z": -14.449999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -18102,7 +18115,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18133,7 +18147,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18165,7 +18180,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18198,7 +18214,8 @@ "y": 0.0, "z": -14.449999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18231,7 +18248,8 @@ "y": 0.0, "z": -14.449999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18263,7 +18281,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18294,7 +18313,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18326,7 +18346,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18359,7 +18380,8 @@ "y": 0.0, "z": -14.449999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18392,7 +18414,8 @@ "y": 0.0, "z": -14.449999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -18424,7 +18447,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18455,7 +18479,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18487,7 +18512,8 @@ "y": 0.0, "z": -24.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18520,7 +18546,8 @@ "y": 0.0, "z": -14.449999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -18553,7 +18580,8 @@ "y": 0.0, "z": -14.449999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -18585,7 +18613,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18616,7 +18645,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -18726,7 +18756,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18759,7 +18790,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18792,7 +18824,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18825,7 +18858,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18858,7 +18892,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18891,7 +18926,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18924,7 +18960,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18957,7 +18994,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18990,7 +19028,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19022,7 +19061,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19118,7 +19158,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19151,7 +19192,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19184,7 +19226,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19217,7 +19260,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19250,7 +19294,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19283,7 +19328,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19316,7 +19362,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19349,7 +19396,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19382,7 +19430,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19414,7 +19463,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19510,7 +19560,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19543,7 +19594,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19576,7 +19628,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19609,7 +19662,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19642,7 +19696,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19675,7 +19730,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19708,7 +19764,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19741,7 +19798,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19774,7 +19832,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19806,7 +19865,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19902,7 +19962,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19935,7 +19996,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19968,7 +20030,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20001,7 +20064,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20034,7 +20098,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20067,7 +20132,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20100,7 +20166,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20133,7 +20200,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20166,7 +20234,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20198,7 +20267,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20294,7 +20364,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20327,7 +20398,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20360,7 +20432,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20393,7 +20466,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20426,7 +20500,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20459,7 +20534,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20492,7 +20568,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20525,7 +20602,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20558,7 +20636,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20590,7 +20669,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -20686,7 +20766,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -20719,7 +20800,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -20752,7 +20834,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -20785,7 +20868,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -20818,7 +20902,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -20851,7 +20936,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -20884,7 +20970,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -20917,7 +21004,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -20950,7 +21038,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -20982,7 +21071,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -21137,7 +21227,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21170,7 +21261,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21203,7 +21295,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21236,7 +21329,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21269,7 +21363,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -21302,7 +21397,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -21335,7 +21431,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -21368,7 +21465,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -21401,7 +21499,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -21433,7 +21532,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -21529,7 +21629,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21562,7 +21663,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21595,7 +21697,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21628,7 +21731,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21661,7 +21765,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -21694,7 +21799,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -21727,7 +21833,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -21760,7 +21867,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -21793,7 +21901,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -21825,7 +21934,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -21921,7 +22031,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21954,7 +22065,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21987,7 +22099,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22020,7 +22133,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22053,7 +22167,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -22086,7 +22201,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -22119,7 +22235,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -22152,7 +22269,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -22185,7 +22303,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -22217,7 +22336,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -22313,7 +22433,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22346,7 +22467,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22379,7 +22501,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22412,7 +22535,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -22445,7 +22569,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -22478,7 +22603,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -22511,7 +22637,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -22544,7 +22671,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -22577,7 +22705,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -22609,7 +22738,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -22705,7 +22835,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22738,7 +22869,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22771,7 +22903,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22804,7 +22937,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22837,7 +22971,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22870,7 +23005,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22903,7 +23039,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22936,7 +23073,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22969,7 +23107,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -23001,7 +23140,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -23097,7 +23237,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -23130,7 +23271,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -23163,7 +23305,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -23196,7 +23339,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -23229,7 +23373,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23262,7 +23407,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23295,7 +23441,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23328,7 +23475,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23361,7 +23509,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23393,7 +23542,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23576,7 +23726,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23609,7 +23760,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23641,7 +23793,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23686,7 +23839,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23718,7 +23872,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23751,7 +23906,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23783,7 +23939,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23828,7 +23985,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23860,7 +24018,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23893,7 +24052,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23925,7 +24085,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23970,7 +24131,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24002,7 +24164,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24035,7 +24198,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24067,7 +24231,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24112,7 +24277,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24144,7 +24310,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24177,7 +24344,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24209,7 +24377,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24254,7 +24423,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24286,7 +24456,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24319,7 +24490,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24351,7 +24523,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24396,7 +24569,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24534,7 +24708,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24567,7 +24742,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24600,7 +24776,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24633,7 +24810,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24666,7 +24844,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24699,7 +24878,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24732,7 +24912,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24765,7 +24946,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24798,7 +24980,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24831,7 +25014,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24864,7 +25048,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24897,7 +25082,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24929,7 +25115,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24974,7 +25161,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25112,7 +25300,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -25145,7 +25334,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25178,7 +25368,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25211,7 +25402,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25244,7 +25436,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25277,7 +25470,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25310,7 +25504,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25343,7 +25538,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25376,7 +25572,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25409,7 +25606,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25442,7 +25640,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25475,7 +25674,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25507,7 +25707,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25552,7 +25753,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25648,7 +25850,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -25681,7 +25884,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25714,7 +25918,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25747,7 +25952,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25780,7 +25986,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25813,7 +26020,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25846,7 +26054,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25879,7 +26088,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25912,7 +26122,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25945,7 +26156,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25978,7 +26190,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26011,7 +26224,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26043,7 +26257,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26088,7 +26303,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26184,7 +26400,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -26217,7 +26434,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26250,7 +26468,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26283,7 +26502,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26316,7 +26536,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26349,7 +26570,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26382,7 +26604,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26415,7 +26638,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26448,7 +26672,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26481,7 +26706,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26514,7 +26740,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26547,7 +26774,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26579,7 +26807,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26624,7 +26853,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26720,7 +26950,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -26753,7 +26984,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26786,7 +27018,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26819,7 +27052,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26852,7 +27086,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26885,7 +27120,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26918,7 +27154,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26951,7 +27188,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26984,7 +27222,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27017,7 +27256,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27050,7 +27290,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27083,7 +27324,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27115,7 +27357,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27160,7 +27403,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27256,7 +27500,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -27289,7 +27534,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27322,7 +27568,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27355,7 +27602,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27388,7 +27636,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27421,7 +27670,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27454,7 +27704,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27487,7 +27738,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27520,7 +27772,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27553,7 +27806,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27586,7 +27840,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27619,7 +27874,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27651,7 +27907,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27696,7 +27953,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -27792,7 +28050,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -27825,7 +28084,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27858,7 +28118,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27891,7 +28152,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27924,7 +28186,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27957,7 +28220,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27990,7 +28254,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28023,7 +28288,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28056,7 +28322,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28089,7 +28356,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28122,7 +28390,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28155,7 +28424,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28187,7 +28457,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28232,7 +28503,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28370,7 +28642,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28403,7 +28676,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28436,7 +28710,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28469,7 +28744,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28502,7 +28778,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28535,7 +28812,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28568,7 +28846,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28601,7 +28880,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28634,7 +28914,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28667,7 +28948,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28700,7 +28982,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28733,7 +29016,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28779,7 +29063,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28824,7 +29109,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28856,7 +29142,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28902,7 +29189,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28947,7 +29235,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28979,7 +29268,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29025,7 +29315,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -29070,7 +29361,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -29102,7 +29394,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -29148,7 +29441,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -29193,7 +29487,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -29225,7 +29520,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -29336,7 +29632,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29369,7 +29666,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29402,7 +29700,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29435,7 +29734,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29468,7 +29768,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29501,7 +29802,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29534,7 +29836,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29567,7 +29870,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29600,7 +29904,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29633,7 +29938,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29666,7 +29972,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29699,7 +30006,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29745,7 +30053,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29790,7 +30099,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29822,7 +30132,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29868,7 +30179,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29913,7 +30225,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29945,7 +30258,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -29991,7 +30305,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -30036,7 +30351,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -30068,7 +30384,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -30114,7 +30431,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -30159,7 +30477,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -30191,7 +30510,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B4" }, @@ -30302,7 +30622,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30335,7 +30656,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30368,7 +30690,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30401,7 +30724,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30434,7 +30758,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30467,7 +30792,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30500,7 +30826,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30533,7 +30860,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30566,7 +30894,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30599,7 +30928,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30632,7 +30962,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30665,7 +30996,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30711,7 +31043,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30756,7 +31089,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30788,7 +31122,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30834,7 +31169,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30879,7 +31215,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30911,7 +31248,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -30957,7 +31295,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -31002,7 +31341,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -31034,7 +31374,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -31080,7 +31421,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -31125,7 +31467,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -31157,7 +31500,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -31268,7 +31612,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31301,7 +31646,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31334,7 +31680,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31367,7 +31714,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31400,7 +31748,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31433,7 +31782,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31466,7 +31816,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31499,7 +31850,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31532,7 +31884,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31565,7 +31918,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31598,7 +31952,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31631,7 +31986,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -31677,7 +32033,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31722,7 +32079,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31754,7 +32112,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -31800,7 +32159,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -31845,7 +32205,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -31877,7 +32238,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -31923,7 +32285,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -31968,7 +32331,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -32000,7 +32364,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -32046,7 +32411,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -32091,7 +32457,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -32123,7 +32490,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B8" }, @@ -32234,7 +32602,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32267,7 +32636,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32300,7 +32670,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32333,7 +32704,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32366,7 +32738,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32399,7 +32772,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32432,7 +32806,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32465,7 +32840,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32498,7 +32874,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32531,7 +32908,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32564,7 +32942,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32597,7 +32976,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32643,7 +33023,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -32688,7 +33069,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -32720,7 +33102,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -32766,7 +33149,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -32811,7 +33195,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -32843,7 +33228,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -32889,7 +33275,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -32934,7 +33321,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -32966,7 +33354,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B9" }, @@ -33012,7 +33401,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -33057,7 +33447,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -33089,7 +33480,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B10" }, @@ -33200,7 +33592,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33233,7 +33626,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33266,7 +33660,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33299,7 +33694,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33332,7 +33728,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33365,7 +33762,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33398,7 +33796,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33431,7 +33830,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33464,7 +33864,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33497,7 +33898,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33530,7 +33932,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33563,7 +33966,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33609,7 +34013,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -33654,7 +34059,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -33686,7 +34092,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -33732,7 +34139,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33777,7 +34185,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33809,7 +34218,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33855,7 +34265,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -33900,7 +34311,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -33932,7 +34344,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B11" }, @@ -33978,7 +34391,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -34023,7 +34437,8 @@ "y": 0.0, "z": -4.544999999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -34055,7 +34470,8 @@ "y": 0.0, "z": -8.09 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -34251,6 +34667,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e42e36e3ca][OT2_X_v2_13_None_None_PythonSyntaxError].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e42e36e3ca][OT2_X_v2_13_None_None_PythonSyntaxError].json index bd05f58334f..0dd0410636f 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e42e36e3ca][OT2_X_v2_13_None_None_PythonSyntaxError].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e42e36e3ca][OT2_X_v2_13_None_None_PythonSyntaxError].json @@ -45,6 +45,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "apiLevel": "2.13", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4660ca6df][OT2_S_v4_P300S_None_MM_TM_TM_MOAMTemps].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4660ca6df][OT2_S_v4_P300S_None_MM_TM_TM_MOAMTemps].json index 2225d4e28a5..1e4573b1d8e 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4660ca6df][OT2_S_v4_P300S_None_MM_TM_TM_MOAMTemps].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4660ca6df][OT2_S_v4_P300S_None_MM_TM_TM_MOAMTemps].json @@ -2458,7 +2458,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2491,7 +2492,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -2578,6 +2580,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "AA BB", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e496fec176][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_default].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e496fec176][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_default].json index 013da0c0d7d..f6c1ad84067 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e496fec176][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_default].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e496fec176][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_default].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4b5b30b2e][Flex_S_v2_18_Illumina_DNA_PCR_Free].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4b5b30b2e][Flex_S_v2_18_Illumina_DNA_PCR_Free].json index 66b92f40fa1..aca7454ff36 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4b5b30b2e][Flex_S_v2_18_Illumina_DNA_PCR_Free].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e4b5b30b2e][Flex_S_v2_18_Illumina_DNA_PCR_Free].json @@ -10218,7 +10218,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10251,7 +10252,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10284,7 +10286,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10317,7 +10320,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10350,7 +10354,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10383,7 +10388,8 @@ "y": 0.625, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10416,7 +10422,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10449,7 +10456,8 @@ "y": 1.25, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10482,7 +10490,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10515,7 +10524,8 @@ "y": 0.625, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10548,7 +10558,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10581,7 +10592,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10614,7 +10626,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10647,7 +10660,8 @@ "y": -0.625, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10680,7 +10694,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10713,7 +10728,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10746,7 +10762,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10779,7 +10796,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10812,7 +10830,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10845,7 +10864,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10878,7 +10898,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10911,7 +10932,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10944,7 +10966,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10977,7 +11000,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11010,7 +11034,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11043,7 +11068,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11076,7 +11102,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11109,7 +11136,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11142,7 +11170,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11175,7 +11204,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11208,7 +11238,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11241,7 +11272,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11274,7 +11306,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11307,7 +11340,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11835,7 +11869,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -11868,7 +11903,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11901,7 +11937,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11934,7 +11971,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11967,7 +12005,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12000,7 +12039,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12033,7 +12073,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12066,7 +12107,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12099,7 +12141,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12132,7 +12175,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12165,7 +12209,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12198,7 +12243,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12231,7 +12277,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12264,7 +12311,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12297,7 +12345,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12330,7 +12379,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12363,7 +12413,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12396,7 +12447,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12428,7 +12480,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12460,7 +12513,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14011,7 +14065,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14058,7 +14113,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14090,7 +14146,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14122,7 +14179,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14155,7 +14213,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -14187,7 +14246,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -14219,7 +14279,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -14445,7 +14506,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14477,7 +14539,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14509,7 +14572,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14542,7 +14606,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14588,7 +14653,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14620,7 +14686,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14952,7 +15019,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14999,7 +15067,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15031,7 +15100,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15063,7 +15133,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15096,7 +15167,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15128,7 +15200,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15160,7 +15233,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15386,7 +15460,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15420,7 +15495,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15452,7 +15528,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15484,7 +15561,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15662,7 +15740,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15695,7 +15774,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15728,7 +15808,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15761,7 +15842,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15794,7 +15876,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15827,7 +15910,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15860,7 +15944,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15893,7 +15978,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15926,7 +16012,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15959,7 +16046,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15992,7 +16080,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16025,7 +16114,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16058,7 +16148,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16091,7 +16182,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16124,7 +16216,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16157,7 +16250,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16190,7 +16284,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16223,7 +16318,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16256,7 +16352,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16959,7 +17056,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17006,7 +17104,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17038,7 +17137,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17070,7 +17170,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17103,7 +17204,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -17135,7 +17237,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -17167,7 +17270,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -17407,7 +17511,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17440,7 +17545,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17472,7 +17578,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17504,7 +17611,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17537,7 +17645,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17583,7 +17692,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17615,7 +17725,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17961,7 +18072,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18008,7 +18120,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18040,7 +18153,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18072,7 +18186,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18105,7 +18220,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18137,7 +18253,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18169,7 +18286,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18424,7 +18542,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18457,7 +18576,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18490,7 +18610,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18523,7 +18644,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18556,7 +18678,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18589,7 +18712,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18622,7 +18746,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18655,7 +18780,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18688,7 +18814,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18721,7 +18848,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18754,7 +18882,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18787,7 +18916,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18820,7 +18950,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18853,7 +18984,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18886,7 +19018,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18919,7 +19052,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18952,7 +19086,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18985,7 +19120,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19237,7 +19373,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19270,7 +19407,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19303,7 +19441,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19336,7 +19475,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19369,7 +19509,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19402,7 +19543,8 @@ "y": 1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19435,7 +19577,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19468,7 +19611,8 @@ "y": 2.5999999999999943, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19501,7 +19645,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19534,7 +19679,8 @@ "y": 1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19567,7 +19713,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19600,7 +19747,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19633,7 +19781,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19666,7 +19815,8 @@ "y": -1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19699,7 +19849,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19732,7 +19883,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19765,7 +19917,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19799,7 +19952,8 @@ "y": 0.0, "z": -12.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19846,7 +20000,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19879,7 +20034,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19912,7 +20068,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19945,7 +20102,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19978,7 +20136,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20011,7 +20170,8 @@ "y": 0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20044,7 +20204,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20077,7 +20238,8 @@ "y": 1.25, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20110,7 +20272,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20143,7 +20306,8 @@ "y": 0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20176,7 +20340,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20209,7 +20374,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20242,7 +20408,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20275,7 +20442,8 @@ "y": -0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20308,7 +20476,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20341,7 +20510,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20656,7 +20826,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20689,7 +20860,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20722,7 +20894,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20755,7 +20928,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20788,7 +20962,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20821,7 +20996,8 @@ "y": 1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20854,7 +21030,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20887,7 +21064,8 @@ "y": 2.5999999999999943, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20920,7 +21098,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20953,7 +21132,8 @@ "y": 1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20986,7 +21166,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21019,7 +21200,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21052,7 +21234,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21085,7 +21268,8 @@ "y": -1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21118,7 +21302,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21151,7 +21336,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21184,7 +21370,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21218,7 +21405,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21264,7 +21452,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21295,7 +21484,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21327,7 +21517,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21491,7 +21682,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21523,7 +21715,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21555,7 +21748,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21588,7 +21782,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21620,7 +21815,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21652,7 +21848,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21892,7 +22089,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21925,7 +22123,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21958,7 +22157,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21991,7 +22191,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22024,7 +22225,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22057,7 +22259,8 @@ "y": 0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22090,7 +22293,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22123,7 +22327,8 @@ "y": 1.25, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22156,7 +22361,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22189,7 +22395,8 @@ "y": 0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22222,7 +22429,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22255,7 +22463,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22288,7 +22497,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22321,7 +22531,8 @@ "y": -0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22354,7 +22565,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22387,7 +22599,8 @@ "y": -1.25, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22420,7 +22633,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22453,7 +22667,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22739,7 +22954,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22786,7 +23002,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22818,7 +23035,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22850,7 +23068,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22883,7 +23102,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -22915,7 +23135,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -22947,7 +23168,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -24298,7 +24520,8 @@ "y": 0.0, "z": 4.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24331,7 +24554,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24363,7 +24587,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24395,7 +24620,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24428,7 +24654,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24578,7 +24805,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24625,7 +24853,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24658,7 +24887,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -24704,7 +24934,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -24881,7 +25112,8 @@ "y": 0.0, "z": 4.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24914,7 +25146,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24946,7 +25179,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24978,7 +25212,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -25011,7 +25246,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25161,7 +25397,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25208,7 +25445,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25241,7 +25479,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25287,7 +25526,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25465,7 +25705,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25498,7 +25739,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25738,7 +25980,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25771,7 +26014,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25818,7 +26062,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25851,7 +26096,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25884,7 +26130,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25917,7 +26164,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25950,7 +26198,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25983,7 +26232,8 @@ "y": 0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26016,7 +26266,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26049,7 +26300,8 @@ "y": 1.25, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26082,7 +26334,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26115,7 +26368,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26417,7 +26671,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26449,7 +26704,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26481,7 +26737,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26515,7 +26772,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26547,7 +26805,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26579,7 +26838,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26770,6 +27030,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "DNA sample of known quantity", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e71b031f47][pl_Illumina_DNA_PCR_Free].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e71b031f47][pl_Illumina_DNA_PCR_Free].json index 28f185165e8..803f4133451 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e71b031f47][pl_Illumina_DNA_PCR_Free].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e71b031f47][pl_Illumina_DNA_PCR_Free].json @@ -10218,7 +10218,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10251,7 +10252,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10284,7 +10286,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10317,7 +10320,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10350,7 +10354,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10383,7 +10388,8 @@ "y": 0.625, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10416,7 +10422,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10449,7 +10456,8 @@ "y": 1.25, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10482,7 +10490,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10515,7 +10524,8 @@ "y": 0.625, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10548,7 +10558,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10581,7 +10592,8 @@ "y": 0.0, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10614,7 +10626,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10647,7 +10660,8 @@ "y": -0.625, "z": -9.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10680,7 +10694,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10713,7 +10728,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10746,7 +10762,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -10779,7 +10796,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10812,7 +10830,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10845,7 +10864,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10878,7 +10898,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10911,7 +10932,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10944,7 +10966,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10977,7 +11000,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11010,7 +11034,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11043,7 +11068,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11076,7 +11102,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11109,7 +11136,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11142,7 +11170,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11175,7 +11204,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11208,7 +11238,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11241,7 +11272,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11274,7 +11306,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11307,7 +11340,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11835,7 +11869,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -11868,7 +11903,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11901,7 +11937,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11934,7 +11971,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11967,7 +12005,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12000,7 +12039,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12033,7 +12073,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12066,7 +12107,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12099,7 +12141,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12132,7 +12175,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12165,7 +12209,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12198,7 +12243,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12231,7 +12277,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12264,7 +12311,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12297,7 +12345,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12330,7 +12379,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12363,7 +12413,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12396,7 +12447,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12428,7 +12480,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12460,7 +12513,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14011,7 +14065,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14058,7 +14113,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14090,7 +14146,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14122,7 +14179,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14155,7 +14213,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -14187,7 +14246,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -14219,7 +14279,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -14445,7 +14506,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14477,7 +14539,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14509,7 +14572,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14542,7 +14606,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14588,7 +14653,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14620,7 +14686,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14952,7 +15019,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14999,7 +15067,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15031,7 +15100,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15063,7 +15133,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15096,7 +15167,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15128,7 +15200,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15160,7 +15233,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -15386,7 +15460,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15420,7 +15495,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15452,7 +15528,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15484,7 +15561,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15662,7 +15740,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15695,7 +15774,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15728,7 +15808,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15761,7 +15842,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15794,7 +15876,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15827,7 +15910,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15860,7 +15944,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15893,7 +15978,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15926,7 +16012,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15959,7 +16046,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15992,7 +16080,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16025,7 +16114,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16058,7 +16148,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16091,7 +16182,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16124,7 +16216,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16157,7 +16250,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16190,7 +16284,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16223,7 +16318,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16256,7 +16352,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16959,7 +17056,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17006,7 +17104,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17038,7 +17137,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17070,7 +17170,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17103,7 +17204,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -17135,7 +17237,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -17167,7 +17270,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -17407,7 +17511,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17440,7 +17545,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17472,7 +17578,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17504,7 +17611,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17537,7 +17645,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17583,7 +17692,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17615,7 +17725,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17961,7 +18072,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18008,7 +18120,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18040,7 +18153,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18072,7 +18186,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18105,7 +18220,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18137,7 +18253,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18169,7 +18286,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -18424,7 +18542,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18457,7 +18576,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18490,7 +18610,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18523,7 +18644,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18556,7 +18678,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18589,7 +18712,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18622,7 +18746,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18655,7 +18780,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18688,7 +18814,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18721,7 +18848,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18754,7 +18882,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18787,7 +18916,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18820,7 +18950,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18853,7 +18984,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18886,7 +19018,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18919,7 +19052,8 @@ "y": 0.0, "z": -6.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18952,7 +19086,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18985,7 +19120,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19237,7 +19373,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19270,7 +19407,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19303,7 +19441,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19336,7 +19475,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19369,7 +19509,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19402,7 +19543,8 @@ "y": 1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19435,7 +19577,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19468,7 +19611,8 @@ "y": 2.5999999999999943, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19501,7 +19645,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19534,7 +19679,8 @@ "y": 1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19567,7 +19713,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19600,7 +19747,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19633,7 +19781,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19666,7 +19815,8 @@ "y": -1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19699,7 +19849,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19732,7 +19883,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19765,7 +19917,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19799,7 +19952,8 @@ "y": 0.0, "z": -12.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19846,7 +20000,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19879,7 +20034,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19912,7 +20068,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19945,7 +20102,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19978,7 +20136,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20011,7 +20170,8 @@ "y": 0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20044,7 +20204,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20077,7 +20238,8 @@ "y": 1.25, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20110,7 +20272,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20143,7 +20306,8 @@ "y": 0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20176,7 +20340,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20209,7 +20374,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20242,7 +20408,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20275,7 +20442,8 @@ "y": -0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20308,7 +20476,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20341,7 +20510,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20656,7 +20826,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20689,7 +20860,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20722,7 +20894,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20755,7 +20928,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20788,7 +20962,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20821,7 +20996,8 @@ "y": 1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20854,7 +21030,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20887,7 +21064,8 @@ "y": 2.5999999999999943, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20920,7 +21098,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20953,7 +21132,8 @@ "y": 1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20986,7 +21166,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21019,7 +21200,8 @@ "y": 0.0, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21052,7 +21234,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21085,7 +21268,8 @@ "y": -1.3000000000000114, "z": -33.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21118,7 +21302,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21151,7 +21336,8 @@ "y": 0.0, "z": -37.5 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21184,7 +21370,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21218,7 +21405,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21264,7 +21452,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21295,7 +21484,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21327,7 +21517,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21491,7 +21682,8 @@ "y": 0.0, "z": -14.349999999999994 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21523,7 +21715,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21555,7 +21748,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21588,7 +21782,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21620,7 +21815,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21652,7 +21848,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21892,7 +22089,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21925,7 +22123,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21958,7 +22157,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21991,7 +22191,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22024,7 +22225,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22057,7 +22259,8 @@ "y": 0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22090,7 +22293,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22123,7 +22327,8 @@ "y": 1.25, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22156,7 +22361,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22189,7 +22395,8 @@ "y": 0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22222,7 +22429,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22255,7 +22463,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22288,7 +22497,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22321,7 +22531,8 @@ "y": -0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22354,7 +22565,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22387,7 +22599,8 @@ "y": -1.25, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22420,7 +22633,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22453,7 +22667,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22739,7 +22954,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22786,7 +23002,8 @@ "y": 0.0, "z": -14.549999999999997 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22818,7 +23035,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22850,7 +23068,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22883,7 +23102,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -22915,7 +23135,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -22947,7 +23168,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -24298,7 +24520,8 @@ "y": 0.0, "z": 4.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24331,7 +24554,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24363,7 +24587,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24395,7 +24620,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24428,7 +24654,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24578,7 +24805,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24625,7 +24853,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24658,7 +24887,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -24704,7 +24934,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -24881,7 +25112,8 @@ "y": 0.0, "z": 4.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24914,7 +25146,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24946,7 +25179,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24978,7 +25212,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -25011,7 +25246,8 @@ "y": 0.0, "z": 3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25161,7 +25397,8 @@ "y": 0.0, "z": -11.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25208,7 +25445,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25241,7 +25479,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25287,7 +25526,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25465,7 +25705,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25498,7 +25739,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25738,7 +25980,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -25771,7 +26014,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25818,7 +26062,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25851,7 +26096,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25884,7 +26130,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25917,7 +26164,8 @@ "y": 0.0, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25950,7 +26198,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25983,7 +26232,8 @@ "y": 0.625, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26016,7 +26266,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26049,7 +26300,8 @@ "y": 1.25, "z": -9.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26082,7 +26334,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26115,7 +26368,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26417,7 +26671,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26449,7 +26704,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26481,7 +26737,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26515,7 +26772,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26547,7 +26805,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26579,7 +26838,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26770,6 +27030,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "DNA sample of known quantity", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e84e23a4ea][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_top_edge].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e84e23a4ea][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_top_edge].json new file mode 100644 index 00000000000..ea9fbf3efb7 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e84e23a4ea][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_top_edge].json @@ -0,0 +1,1269 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "A1" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-1000ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 97.47 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_1000ul", + "tipLength": 88, + "tipOverlap": 7.95 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.24, + "z": 9.47 + }, + "A10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.24, + "z": 9.47 + }, + "A11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.24, + "z": 9.47 + }, + "A12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.24, + "z": 9.47 + }, + "A2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.24, + "z": 9.47 + }, + "A3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.24, + "z": 9.47 + }, + "A4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.24, + "z": 9.47 + }, + "A5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.24, + "z": 9.47 + }, + "A6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.24, + "z": 9.47 + }, + "A7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.24, + "z": 9.47 + }, + "A8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.24, + "z": 9.47 + }, + "A9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.24, + "z": 9.47 + }, + "B1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.24, + "z": 9.47 + }, + "B10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.24, + "z": 9.47 + }, + "B11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.24, + "z": 9.47 + }, + "B12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.24, + "z": 9.47 + }, + "B2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.24, + "z": 9.47 + }, + "B3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.24, + "z": 9.47 + }, + "B4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.24, + "z": 9.47 + }, + "B5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.24, + "z": 9.47 + }, + "B6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.24, + "z": 9.47 + }, + "B7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.24, + "z": 9.47 + }, + "B8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.24, + "z": 9.47 + }, + "B9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.24, + "z": 9.47 + }, + "C1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.24, + "z": 9.47 + }, + "C10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.24, + "z": 9.47 + }, + "C11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.24, + "z": 9.47 + }, + "C12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.24, + "z": 9.47 + }, + "C2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.24, + "z": 9.47 + }, + "C3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.24, + "z": 9.47 + }, + "C4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.24, + "z": 9.47 + }, + "C5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.24, + "z": 9.47 + }, + "C6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.24, + "z": 9.47 + }, + "C7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.24, + "z": 9.47 + }, + "C8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.24, + "z": 9.47 + }, + "C9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.24, + "z": 9.47 + }, + "D1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.24, + "z": 9.47 + }, + "D10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.24, + "z": 9.47 + }, + "D11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.24, + "z": 9.47 + }, + "D12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.24, + "z": 9.47 + }, + "D2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.24, + "z": 9.47 + }, + "D3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.24, + "z": 9.47 + }, + "D4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.24, + "z": 9.47 + }, + "D5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.24, + "z": 9.47 + }, + "D6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.24, + "z": 9.47 + }, + "D7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.24, + "z": 9.47 + }, + "D8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.24, + "z": 9.47 + }, + "D9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.24, + "z": 9.47 + }, + "E1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.24, + "z": 9.47 + }, + "E10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.24, + "z": 9.47 + }, + "E11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.24, + "z": 9.47 + }, + "E12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.24, + "z": 9.47 + }, + "E2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.24, + "z": 9.47 + }, + "E3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.24, + "z": 9.47 + }, + "E4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.24, + "z": 9.47 + }, + "E5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.24, + "z": 9.47 + }, + "E6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.24, + "z": 9.47 + }, + "E7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.24, + "z": 9.47 + }, + "E8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.24, + "z": 9.47 + }, + "E9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.24, + "z": 9.47 + }, + "F1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.24, + "z": 9.47 + }, + "F10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.24, + "z": 9.47 + }, + "F11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.24, + "z": 9.47 + }, + "F12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.24, + "z": 9.47 + }, + "F2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.24, + "z": 9.47 + }, + "F3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.24, + "z": 9.47 + }, + "F4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.24, + "z": 9.47 + }, + "F5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.24, + "z": 9.47 + }, + "F6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.24, + "z": 9.47 + }, + "F7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.24, + "z": 9.47 + }, + "F8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.24, + "z": 9.47 + }, + "F9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.24, + "z": 9.47 + }, + "G1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.24, + "z": 9.47 + }, + "G10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.24, + "z": 9.47 + }, + "G11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.24, + "z": 9.47 + }, + "G12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.24, + "z": 9.47 + }, + "G2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.24, + "z": 9.47 + }, + "G3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.24, + "z": 9.47 + }, + "G4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.24, + "z": 9.47 + }, + "G5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.24, + "z": 9.47 + }, + "G6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.24, + "z": 9.47 + }, + "G7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.24, + "z": 9.47 + }, + "G8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.24, + "z": 9.47 + }, + "G9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.24, + "z": 9.47 + }, + "H1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.24, + "z": 9.47 + }, + "H10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.24, + "z": 9.47 + }, + "H11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.24, + "z": 9.47 + }, + "H12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.24, + "z": 9.47 + }, + "H2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.24, + "z": 9.47 + }, + "H3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.24, + "z": 9.47 + }, + "H4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.24, + "z": 9.47 + }, + "H5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.24, + "z": 9.47 + }, + "H6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.24, + "z": 9.47 + }, + "H7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.24, + "z": 9.47 + }, + "H8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.24, + "z": 9.47 + }, + "H9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.24, + "z": 9.47 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "PartialTipMovementNotAllowedError [line 479]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Requested motion with the H12 nozzle partial configuration is outside of robot bounds for the pipette.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Requested motion with the H12 nozzle partial configuration is outside of robot bounds for the pipette.", + "errorCode": "2004", + "errorInfo": {}, + "errorType": "PartialTipMovementNotAllowedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_top_edge.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "A1" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Too tall labware on pickup tip" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e8f451df45][Flex_S_v2_20_96_None_Column3_SINGLE_].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e8f451df45][Flex_S_v2_20_96_None_Column3_SINGLE_].json new file mode 100644 index 00000000000..81ebf160345 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[e8f451df45][Flex_S_v2_20_96_None_Column3_SINGLE_].json @@ -0,0 +1,26860 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0c3bb3b5f63458c1ff149d26e64d98b5", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "80976ce3dffe6efe69a8b26c3c4a1f4b", + "notes": [], + "params": { + "displayName": "Partial Tip Rack", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "A2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d89b7e852badd592f2c16cd019f596b5", + "notes": [], + "params": { + "displayName": "Partial Tip Rack", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "440fd5d4d25f78376014c66392a18e38", + "notes": [], + "params": { + "displayName": "Partial Tip Rack", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "C2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d18d39c6822ff1cd6ce2fd098338c7f0", + "notes": [], + "params": { + "displayName": "Partial Tip Rack", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "D2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.75, + "zDimension": 99 + }, + "gripForce": 16.0, + "gripHeightFromLabwareBottom": 23.9, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons Flex 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "quirks": [], + "tipLength": 95.6, + "tipOverlap": 10.5 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { + "x": 0, + "y": 0, + "z": 121 + } + }, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.38, + "z": 1.5 + }, + "A10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.38, + "z": 1.5 + }, + "A11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.38, + "z": 1.5 + }, + "A12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.38, + "z": 1.5 + }, + "A2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.38, + "z": 1.5 + }, + "A3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.38, + "z": 1.5 + }, + "A4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.38, + "z": 1.5 + }, + "A5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.38, + "z": 1.5 + }, + "A6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.38, + "z": 1.5 + }, + "A7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.38, + "z": 1.5 + }, + "A8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.38, + "z": 1.5 + }, + "A9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.38, + "z": 1.5 + }, + "B1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.38, + "z": 1.5 + }, + "B10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.38, + "z": 1.5 + }, + "B11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.38, + "z": 1.5 + }, + "B12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.38, + "z": 1.5 + }, + "B2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.38, + "z": 1.5 + }, + "B3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.38, + "z": 1.5 + }, + "B4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.38, + "z": 1.5 + }, + "B5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.38, + "z": 1.5 + }, + "B6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.38, + "z": 1.5 + }, + "B7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.38, + "z": 1.5 + }, + "B8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.38, + "z": 1.5 + }, + "B9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.38, + "z": 1.5 + }, + "C1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.38, + "z": 1.5 + }, + "C10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.38, + "z": 1.5 + }, + "C11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.38, + "z": 1.5 + }, + "C12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.38, + "z": 1.5 + }, + "C2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.38, + "z": 1.5 + }, + "C3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.38, + "z": 1.5 + }, + "C4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.38, + "z": 1.5 + }, + "C5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.38, + "z": 1.5 + }, + "C6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.38, + "z": 1.5 + }, + "C7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.38, + "z": 1.5 + }, + "C8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.38, + "z": 1.5 + }, + "C9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.38, + "z": 1.5 + }, + "D1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.38, + "z": 1.5 + }, + "D10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.38, + "z": 1.5 + }, + "D11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.38, + "z": 1.5 + }, + "D12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.38, + "z": 1.5 + }, + "D2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.38, + "z": 1.5 + }, + "D3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.38, + "z": 1.5 + }, + "D4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.38, + "z": 1.5 + }, + "D5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.38, + "z": 1.5 + }, + "D6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.38, + "z": 1.5 + }, + "D7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.38, + "z": 1.5 + }, + "D8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.38, + "z": 1.5 + }, + "D9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.38, + "z": 1.5 + }, + "E1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.38, + "z": 1.5 + }, + "E10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.38, + "z": 1.5 + }, + "E11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.38, + "z": 1.5 + }, + "E12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.38, + "z": 1.5 + }, + "E2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.38, + "z": 1.5 + }, + "E3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.38, + "z": 1.5 + }, + "E4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.38, + "z": 1.5 + }, + "E5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.38, + "z": 1.5 + }, + "E6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.38, + "z": 1.5 + }, + "E7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.38, + "z": 1.5 + }, + "E8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.38, + "z": 1.5 + }, + "E9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.38, + "z": 1.5 + }, + "F1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.38, + "z": 1.5 + }, + "F10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.38, + "z": 1.5 + }, + "F11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.38, + "z": 1.5 + }, + "F12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.38, + "z": 1.5 + }, + "F2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.38, + "z": 1.5 + }, + "F3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.38, + "z": 1.5 + }, + "F4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.38, + "z": 1.5 + }, + "F5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.38, + "z": 1.5 + }, + "F6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.38, + "z": 1.5 + }, + "F7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.38, + "z": 1.5 + }, + "F8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.38, + "z": 1.5 + }, + "F9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.38, + "z": 1.5 + }, + "G1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.38, + "z": 1.5 + }, + "G10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.38, + "z": 1.5 + }, + "G11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.38, + "z": 1.5 + }, + "G12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.38, + "z": 1.5 + }, + "G2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.38, + "z": 1.5 + }, + "G3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.38, + "z": 1.5 + }, + "G4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.38, + "z": 1.5 + }, + "G5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.38, + "z": 1.5 + }, + "G6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.38, + "z": 1.5 + }, + "G7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.38, + "z": 1.5 + }, + "G8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.38, + "z": 1.5 + }, + "G9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.38, + "z": 1.5 + }, + "H1": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.38, + "z": 1.5 + }, + "H10": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.38, + "z": 1.5 + }, + "H11": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.38, + "z": 1.5 + }, + "H12": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.38, + "z": 1.5 + }, + "H2": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.38, + "z": 1.5 + }, + "H3": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.38, + "z": 1.5 + }, + "H4": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.38, + "z": 1.5 + }, + "H5": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.38, + "z": 1.5 + }, + "H6": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.38, + "z": 1.5 + }, + "H7": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.38, + "z": 1.5 + }, + "H8": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.38, + "z": 1.5 + }, + "H9": { + "depth": 97.5, + "diameter": 5.47, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.38, + "z": 1.5 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b6efdcae1de4652876acdceb946f6692", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "COLUMN" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5bc9abd9f14d07e28f9cecca475da8a3", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 277.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a4f444f152515360572079abf7863e95", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "502a9d204218937bcf43d1e7b5475293", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a60f35d02f683a4e2472c915344791c4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A11" + }, + "result": { + "position": { + "x": 268.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cc8abf10cd822f1d06db718ec939adf2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d14725befda2dd788a93b7a995d6dacc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "91b745adb911d1f673c03955516fd87d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A10" + }, + "result": { + "position": { + "x": 259.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "40ded0ae6a5486523468e4bd6a91ded3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dd4fbe929710a88be37d111653ca573b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "91d94a0dcabc958f668fb083e4fd7c6e", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "COLUMN" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9ca947f79f13e6e7a9fbec11d114373a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 277.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "692930ab666db5afd67c1ec3af4d94fc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "784a63247d6f46dc9ad7d660d93b8a01", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "75edff5509a595e978075cb8dab532b6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A11" + }, + "result": { + "position": { + "x": 268.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c3b8dfc4cfc1a5df0bdd42f092fb5f76", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c38d2097124174a70b6b54eea4bfa2a5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "40336a475e1102b09006284cd9d62668", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A10" + }, + "result": { + "position": { + "x": 259.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b8dc8bb60876663445da4ec4088f7c0d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a8c2320724f48c318fcd9f1f8a886de3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "714e1a2765cc1935fca513103751606a", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "A3" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "22d0afb4175f4ed79ef197eadeed41ca", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "C3" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2297d27cdbccb5c764c02f18b5292151", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "54e9d9197241ef086a4b6e48f8927361", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H9" + }, + "result": { + "position": { + "x": 414.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4b88a02e961691f8ebff189ddc039573", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3e434153309994faaf71006688cfc4b8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "932cf057ad044c18b1039faf8635dbf8", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G9" + }, + "result": { + "position": { + "x": 414.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "07ffaf02f6ec33f425106087ddc304d2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c98d0505c74c6de696661cde5211dd7c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "731e217391cad22580d4df5b4a02b0c7", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F9" + }, + "result": { + "position": { + "x": 414.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cd172dbe1f919ab09c3a587b49072b7c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ea31f8a9982f9280b21106ad37dc5a8f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "69685add4ae50a9f9c78d4ceb5f993a1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E9" + }, + "result": { + "position": { + "x": 414.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "debd684a32918e206fe26ac6f437aae5", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a988c6623f277e374ba0f163497fb4b4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f93396445090258205cc446a89272c8d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D9" + }, + "result": { + "position": { + "x": 414.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "048ff299b38ccdaba087f315468cd73f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "135a6f5e359e285d0b1391f68e60fe09", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f6b6b831cb57b0fbcc238aeb3e457960", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C9" + }, + "result": { + "position": { + "x": 414.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ec08811892c83571923e73cac5e80c3d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "52fbfeb9de7a4b6b6a50e4efc7213e89", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8b43cdd4022399ec6fd853add66a8735", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B9" + }, + "result": { + "position": { + "x": 414.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "46915ea4baa9076288f180ece20f4283", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ec8e8fc2edddb95c2ff7fd6cecc91233", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d8c73ce30e25cd05c24fdd7ae4ebc60c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A9" + }, + "result": { + "position": { + "x": 414.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5c41321b175b4557c44ebd44970fedf2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "655ad4f48ee770487a4723def4e579d2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1f716948e6c5d7b0a8deb1bd9c595862", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H8" + }, + "result": { + "position": { + "x": 405.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4321b6f6dcb03761cbdad4c0adfd1571", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "05ffe6f06eae3a0dd6940496c38c9617", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "21b83917c3f887b120a79c5c321de5ac", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G8" + }, + "result": { + "position": { + "x": 405.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8fdb540e97465443b10d50176382ca04", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1b6b917582f9b135fb419a740b14aed3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1679db6b9a444fe0fb55cb0eaa615e49", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F8" + }, + "result": { + "position": { + "x": 405.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6d415f1240152c022b2550e4cef4c4de", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ea8b799af31510bed37f7adb3f789935", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "02f9a0dac1c9f39c9d96ec42c17c39c6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E8" + }, + "result": { + "position": { + "x": 405.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fea08f05b4d8ad8c37a6d9e574df4c8a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2f700bb0ba56c7c6ccb9b1154ce9501c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5f427af3404c8b7ae4f49187d3492c6d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D8" + }, + "result": { + "position": { + "x": 405.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "80955bbae54de714887e70c81c736b9c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ca88268ddd6ccdb514b0837f9868dc46", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9f28758630d49a4e113a85ca517e48d6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C8" + }, + "result": { + "position": { + "x": 405.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "637e5599fd395282f0fffc80a0bfb45b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e4019a9180787c9b998ba12594bbcb74", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "64443a6d5d239210874b4ac77c844f5f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B8" + }, + "result": { + "position": { + "x": 405.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "70fdf379a1bf76674e185db549b0d510", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a51dc03d193c172bfe95c785ee41d4d4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0dd3b3bb991dc43cde2f6b820ca83479", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 405.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f2a16c707278f7568971c5e9177e0a6a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "90468394954561b552ca1f47e1fb4d2a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cf70be9645a01bbe03ea99af916dff4a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H7" + }, + "result": { + "position": { + "x": 396.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b10114bf44059f800af792ff5144f80a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "103c4ffc5a8fe951d30de0baa4f2fb64", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a39308bd7ae558eee5cd9ca849ea2681", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G7" + }, + "result": { + "position": { + "x": 396.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8058b8f5f268e2ccdb018d159745c2ac", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "71fb6613a355791cbbb2773eea81a980", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9e3e0061571b92c7b48560cb9a30c6e6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F7" + }, + "result": { + "position": { + "x": 396.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c55272acb65e4f2214c2d868bb52641b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a8cc34ee79320f265db60371d52a24c1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c7f34eb1492e60cf56a8313dc3bac656", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E7" + }, + "result": { + "position": { + "x": 396.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b336b9b0c3bbed2b80f5868bf6cbda0e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4bd5666c35bbf102c5db48597462297a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "41d4f91976463b7ab5c71bded0b2aa87", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D7" + }, + "result": { + "position": { + "x": 396.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bcec596ce9c46300d46c8508419d681b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ed9ef033a042335305ef2a3c8f9a3a1f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f67dfd76f99da0133251783c24d64bff", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C7" + }, + "result": { + "position": { + "x": 396.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dd44faac7d16d6385a1ded842a91b31a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2935fe8d8666add784998fb1a61702d7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0486f7d05d5e8b9cd0b290cd5f3da409", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B7" + }, + "result": { + "position": { + "x": 396.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a7329c37bf7cb732dceb839898d775dd", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f17434ff7c94786b699b40f237420302", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c413754090a2b5820becf7a84841912e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 396.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9f2a0e3e101161c96ebb312be4c25717", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b30444d7d2682f94be6822d89f800cd3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "65bb51277863d6e209c415c40dd061eb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H6" + }, + "result": { + "position": { + "x": 387.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a403552e46f0a141577cb88738a8d2d8", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f86aeaa05438e2394dd7c3218456ddc6", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0670d817c658a4724d4177ba6df01b96", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G6" + }, + "result": { + "position": { + "x": 387.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c62a9e1d13cb991c23e57a5b520b8f7e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3856ed457394dcfb7697ab3ac5e0e47c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e261dd3c58a6c8531aca1c09898a5529", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F6" + }, + "result": { + "position": { + "x": 387.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0967876f96d6d05787351d511f1e3664", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2ce1f70488e339bd395a227bd88307d4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "29207b4620ee28a2e56041936db48a53", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E6" + }, + "result": { + "position": { + "x": 387.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c5268cb02ac85326aa386c8fd97ef0fa", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9f0ae1ac6998842c5689166586fceeb7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "212b1845afbbd3bc73802cc24f3db754", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D6" + }, + "result": { + "position": { + "x": 387.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8d0702bc30bc9958fe988d4a6e3d66f8", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e015d1976bd3633d5d3c2f8335552e3a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0146a6dafae43740586edd70504b0995", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C6" + }, + "result": { + "position": { + "x": 387.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50d857587fd67821672ba2509e89f6f1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5150dbec5cda034ca738568bcac66816", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dd3803b071c0f0918da50e4bdfbb9b4c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B6" + }, + "result": { + "position": { + "x": 387.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a4031b1343cb0397b32c658e631b2859", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b159d8622264704ebcaa38b6410474c0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e9aba6d831dc1570b5b2f3e9f7b11784", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 387.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "75e3483b3ce72c5d954dcbe3c3f1f7eb", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b8fea1a8c84441eee58172decb633947", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "02a3562695ec7b8977d2588f2e1f709e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H5" + }, + "result": { + "position": { + "x": 378.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b1f82f5af4d2dda52bac5695608e9e75", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "899d2be6b32460492421eefd96bc158e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6b53b643004c65114d607e7ace7ef089", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G5" + }, + "result": { + "position": { + "x": 378.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ab9d983841f2df4a74bd4a36aa5a868d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "acb70a2259f9ae99d481042913f8208e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "04691d958f66f1c3745b753a2bd1dd2d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F5" + }, + "result": { + "position": { + "x": 378.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "54a31a0461bd75adeab3fc30aea547ea", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "996c6aba327206752b57d90403d26e96", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5781695f9827a7ad22230f5a1a68483a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E5" + }, + "result": { + "position": { + "x": 378.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "83e40205fc9e522f4d4d127d1a76f7d6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2dd454233d3db5c8602dab04b04b7a0c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "89d66ffe8fa00ccbf2060c53e711a7c9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D5" + }, + "result": { + "position": { + "x": 378.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0d758bd2da0c911bc1ec65cc9d4f0227", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ec75015cc3b75fed5acdcf592eb79467", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f930eb611ce56feec900208ce4b927d0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C5" + }, + "result": { + "position": { + "x": 378.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f4a4983b1803d340ea4d509155507e71", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9528842017410222266a091086b1eee3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5e939aa95aca7d57f603f25c230c9825", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B5" + }, + "result": { + "position": { + "x": 378.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ff32175d2554f457203a865ba2d65f58", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e63e7caedd108aaad225d530fd1bcde3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7dcb389cd91f670f446233f7206713a3", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 378.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1597c02abecbcd1bcc02cf11adc23746", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "48992b109939025fd4f4e4ad27e66db2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ff62f78826955ea38b9e9a4d84c3b5ac", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H4" + }, + "result": { + "position": { + "x": 369.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "172f72f1bf86d3f2df64061bfa436917", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cfc01f516890d08249e8d9a84fdeb080", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "57c74e8d128f95d31efc056bf218a507", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G4" + }, + "result": { + "position": { + "x": 369.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "69f875d719841f347b6260a707c807f1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e840e303eab1902848c4722031650506", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6a4763419b122bd2484a9a2d3a230b6a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F4" + }, + "result": { + "position": { + "x": 369.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "77c89e063598014e3c07f47c03aae352", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9504dd36bd2cc6417e2bdd4e30870441", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8f652ed82e2856c15cd0714df3b0e837", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E4" + }, + "result": { + "position": { + "x": 369.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4a2170fb933646cb62ef442c91f5c0f1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "26a27bdbc94b940e9d4db504aa9953b2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b33d2a68668d20256f0c2f85c6be32e4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D4" + }, + "result": { + "position": { + "x": 369.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4754f6db1a9e2eae6f37e78d5a154d8c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "640fef2d2f88b4eee21f34201fffce39", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3b470c474177d94c7bad23c62bcea81a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C4" + }, + "result": { + "position": { + "x": 369.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73a6fb8ab15b70264be77c0f900377db", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73f5d8ca41803cf21a4ae96c0c155864", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5894159875a85d431945e63c214f72e8", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B4" + }, + "result": { + "position": { + "x": 369.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0cd1dcad2b16164d9f7e3bfdbf07bc02", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bc3d3b50f617fe1ac48a1fdeab47f2d3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f0c3d2a0efbee739b1fd1b17c71ccdb4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 369.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b036b8b53c734da7292f23104ed86697", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2dc25da724e5dfbb84803323d3fd2d17", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "35d46e5cce6ba950e38259b09ddcd06e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H3" + }, + "result": { + "position": { + "x": 360.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4c3cd74c0c570ee5247e47d873d248e4", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dd119db5687443db2a6d387054d4af50", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "eac306c0a48389a481ff6dc0eecbdb4a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G3" + }, + "result": { + "position": { + "x": 360.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a8b894b69f5fee6b117ae030d310693f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2bf113e6ca043ed1b9acb66bb92bf556", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6f277ce22ccd8b53397a18da730524e6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F3" + }, + "result": { + "position": { + "x": 360.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "865b8ab47b0e9be74f5e93fba6280dcb", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ab8384ef86b8a15ae1e0c33e5f077024", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0be59ad6be0f40eb5574aa28990fe6c0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E3" + }, + "result": { + "position": { + "x": 360.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "af80df10e86848bda315d49ec6018991", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "02a55c07d7d4c90fc33cf01785879bdc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f3733e30ae59f2435b8ed706a96116d5", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D3" + }, + "result": { + "position": { + "x": 360.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cd9422115b6d027a5de83f788ad62d81", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "56a74429c7b8ff69c6d8c9e4958134de", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2ce3addd57a8fcf4f4b86f86da7e5ef6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C3" + }, + "result": { + "position": { + "x": 360.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4f6c54f71687d9d686e435447e1310fc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0774996f7d026d69fac4c2ad530a8cec", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d3455961f93fbda95ba1ec8726c460a9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B3" + }, + "result": { + "position": { + "x": 360.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7e17d1ce94564ba05c40335eefa4c83b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "86a7680162019acbc7cc537ad3dfa12d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d1acab75e2b9c0f369833513185487b6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 360.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a33c6db563ee1b1e54dc909f85ebef13", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6fa9584686660e52a37c474ffb54e882", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ec76652e2ee6bc9909b915ad40e35412", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H2" + }, + "result": { + "position": { + "x": 351.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e06988620f6a8846f5134e079b9fa24c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6fc9dc6ad4421e92bce2c7b49d47f8d9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7abe0dfb79191439797bf23da3eb9284", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G2" + }, + "result": { + "position": { + "x": 351.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "580fc2882a125768c0775b6c82029ccc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d6219ae9af04c69c685fe430b2910910", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1f0dc78454b12ffab5b254a9d7c74bf6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F2" + }, + "result": { + "position": { + "x": 351.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7f7c4235712fd6d84f39d86f435f5c7f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b74801b3060c6b24b4a89e9c4d7856fc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "634d707d4ff99412ce47d411d010e29a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E2" + }, + "result": { + "position": { + "x": 351.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "64dda0d53089ab7d5ecac55928dc8b37", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c955dfed4a808f2580de3248fee6794c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ae9197920e709cf0ec2bfedd29924357", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D2" + }, + "result": { + "position": { + "x": 351.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c1623a292ff5f30fda1ccb3c656f45ab", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e85ce349c25fd477670f128b02194605", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ff2266e5abcf32882191e5ea0cb51b61", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C2" + }, + "result": { + "position": { + "x": 351.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4a1afbd1518758bb3feb24e692a670d8", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b5920a799c5523edfc722d4d9932c8da", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0c94ac66df5e01097559557f4cf0f7f6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B2" + }, + "result": { + "position": { + "x": 351.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "24658bc8b74b3dc607864ded58da3247", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "54cccfa43cca6caeb66eee2ffccab9ae", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4d17c39dd29ddbd2b65bcc70e71a8a95", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 351.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bc4595c5b3ff90e8de204aac92b5a4ef", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "60f5c44d93cbe59a5af14cd603b22e9a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6861d19493e4bfb3ebaadd3f6f49cf66", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 342.38, + "y": 332.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dffea8ca35c31930985989ce59d7f0bf", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1e0c4e7eb3bab5b4851e6715ec472967", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3520d1b665841acc620fe8646c7ff258", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 342.38, + "y": 341.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9ce42f22b6f407824d0ee6d425792863", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "78d179cf2e830e3f465103a68fd4a2af", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f27b4979c2e141855355a1cb8ee9f500", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 342.38, + "y": 350.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ff7b9de2e4dfd98750ac26288bf4df7f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c1c61d5c5d9caaf4e5710fe523f8ca69", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "14af71723bc10434cff4fb170a79fa69", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 342.38, + "y": 359.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3a76842ad36bc38e887f0898c5d26530", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "16d40ef0935ffdf7c69e620cf08179dd", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "509c39aef8d5f103da774ef5ce81f0fb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 342.38, + "y": 368.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d871b8d32a51b5f4d677ec05a64b7960", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b0789c110d346969d20c2c16a80f68b7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0fd2751960af1ebc42ef585d9f9c1f69", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 342.38, + "y": 377.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fa497367f5924dc829aa5b94305bc8a3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b07406d1494f5b71455327c4c195cddc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e70be18f0d51e2263443c0ba9bf4f00d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 342.38, + "y": 386.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd97b6f4079d89a07965116e97bc2076", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f12fbedd8f6eb04d516d552c01c67de2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f231ce1e3d7447dab9fc3d5fed6e852d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "959ff2ab7b19c2dbd54442090a7956c2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "02fc30dc5cfa9efc1c567cac62f02b60", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6c98a1d6e7967d7bcae5f8d2260e5249", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H9" + }, + "result": { + "position": { + "x": 414.38, + "y": 118.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "09f645ff68e674325097b2ad2d864f1e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "82c91cc026c6401f16859a25f0c900ae", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cc0e5358e19fd4286df339e45a1ec337", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G9" + }, + "result": { + "position": { + "x": 414.38, + "y": 127.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f254122eaf7ddec05a7fe64bac48d611", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "25f1c6ba4633dd38b7e27150634c6aa1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "335f332a1d584af9d7ff63d84bd72004", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F9" + }, + "result": { + "position": { + "x": 414.38, + "y": 136.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "00a4634463725ec22a3d35f0aeabe422", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "11dba9d6074db2db7d2581e0752921d4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9a5bbbb50eba769906b7e277b3fdf904", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E9" + }, + "result": { + "position": { + "x": 414.38, + "y": 145.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "19d21b6938613bb755a97aa19f1b2b4f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6c5bb501b3c8254d8e880dc623f73c95", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8ce33e1c1cedb7c888709e90924a7f31", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D9" + }, + "result": { + "position": { + "x": 414.38, + "y": 154.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "18e87404610ed62327f42539da78f49d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e8cc48bdd26a20c396aaf0ce08876269", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d17054cb1f763b256518e46cc3e5f55a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C9" + }, + "result": { + "position": { + "x": 414.38, + "y": 163.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ed0ffece76923615a45db3a90dcc3ef3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ade8173fa8cba9bdbb65309c7b8ee76a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "23e86986f74ae06eb3b168ca48e520ee", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B9" + }, + "result": { + "position": { + "x": 414.38, + "y": 172.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "06850f83b85809f1117db1a4bc9a9265", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e2fe836ce6d699289e082f776992c4a2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5ec029bdbe2f68323f72b02fd1dfbc25", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A9" + }, + "result": { + "position": { + "x": 414.38, + "y": 181.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b0b5dd0b54a48a22e3a330dcfa045767", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f564503077efd83b676efa4365380f00", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d71d29324d9c98753f3cc1ff34a30e73", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H8" + }, + "result": { + "position": { + "x": 405.38, + "y": 118.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "676857dd62a900b58d90eaac4088fe00", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "570c61ca4949a6345db1538d056922de", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7d297ac4bc4022dedbd71462a3099a4c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G8" + }, + "result": { + "position": { + "x": 405.38, + "y": 127.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "355908b7bb9756d5cb8917d3fd40c784", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2a4f9909eacf82fb6dc11588462e4bba", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cdfb04be600138f33591fef5a90a9879", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F8" + }, + "result": { + "position": { + "x": 405.38, + "y": 136.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "96f46034dbc7d5b7e42c3c74b180d28a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9097e77871fc8af0069ce2a04c4fb277", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5fad1eef655f99e9a248238b93f31242", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E8" + }, + "result": { + "position": { + "x": 405.38, + "y": 145.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "efc053f2dd11d1330c834a0fbd2ce22f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c5deff98ff717b98eacd4a400945e384", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3468fd9a9f43203d993a3e4a347df37e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D8" + }, + "result": { + "position": { + "x": 405.38, + "y": 154.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "77bed7e561fbbe5b5f08ba8efa9556fb", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5e69a708143e0af947f27ac6027a8ae7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dca5e5dd4e5521f9df8e769010689406", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C8" + }, + "result": { + "position": { + "x": 405.38, + "y": 163.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cd4902f9fc36021dc6ca882500604a8e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e40afb8bb621a9cc225f274b18c0ba78", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "498cbf85c9df2fc506ddfaff3d8cfd09", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B8" + }, + "result": { + "position": { + "x": 405.38, + "y": 172.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cfd88885fbb8a31ecc8196de335c205c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9c7e52c4c9f9014b2f56a469d5de24fe", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4dc92a08039495e9266ecd64e314df36", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 405.38, + "y": 181.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fb89bed3c3ce1bbf474a7c8abcf982fe", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f73786d5b387e335b929fae1fe28e96a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1d1598fbc751d810123abc88a626d521", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H7" + }, + "result": { + "position": { + "x": 396.38, + "y": 118.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1fda8961d1288fcb2af36d1c08ef29ea", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5eb4bbcddbd15fea8d651268ea1b687f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4fcebbe6cac7d3610c182b975f8f2f9e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G7" + }, + "result": { + "position": { + "x": 396.38, + "y": 127.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6a69a5d00a72ddd6eb9c05327860b634", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ca37a3b2be20b02f6d073bd25db150cc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0cad081378fdbf0030288b61286945e8", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F7" + }, + "result": { + "position": { + "x": 396.38, + "y": 136.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9ea1e8d86fffe904b209b95fd0458ab9", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a736ce0b42b3c6cab1515e0212d53e14", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c907e81c6947ae84998c6787509db6e2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E7" + }, + "result": { + "position": { + "x": 396.38, + "y": 145.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "39554fcfa898892afecf01d7528e7c8d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a7f160155658d99cf1f13a563fa4b14c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a931888071e277021bbecc9acdbd06f1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D7" + }, + "result": { + "position": { + "x": 396.38, + "y": 154.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ba0498b9386d5bd2da4e758b902fa7dc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ab0d9bdeb50618244237b25e09f82c8c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "aeeda1fc0978ee6772726b61834d421e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C7" + }, + "result": { + "position": { + "x": 396.38, + "y": 163.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ac38d5bdf7db82af4921a1e7580cc2b3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5325dcc7cffb14762b94a60971a7f9d6", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3c23824a3b357e22afc486539ecfa207", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B7" + }, + "result": { + "position": { + "x": 396.38, + "y": 172.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f7e2c7be5182d6d125ff2dfe25348fb3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7ccb2b30c968dc3888d54fe1c0d3c563", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7020c43bfc297519cdac6e558de1e09c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 396.38, + "y": 181.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "29cfd4288362bd3888b5962700aedd2f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "905ba1dcb9676c4eff70ec6a196695cc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "02d3dfe0ffb695947501a9fcabd53237", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H6" + }, + "result": { + "position": { + "x": 387.38, + "y": 118.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "746c3fd8903bff3e9797faed017b1652", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "643f0e2a01164c97b8de8d691db0b220", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9a09ba2aee2b02de3dee38d84db717e0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G6" + }, + "result": { + "position": { + "x": 387.38, + "y": 127.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a8fa70b1eee43676ad5b1f19c41c7903", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0e0a2be6c326ba4465842f7b0832c553", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6b23786edb28411966a15d6ee9f22e1d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F6" + }, + "result": { + "position": { + "x": 387.38, + "y": 136.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "abb0e930a2f9ea8c3923c8b5826d6cca", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "407274aac420d5a3e892f23ca0ffeb3f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bed2002c80ddc9bdebbf7f55fdb44c19", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E6" + }, + "result": { + "position": { + "x": 387.38, + "y": 145.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c5ca27488bbea76317f52784a988cc0b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2fc5d940edb053ac41dcb8f50c4005b7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3a3ba1da97ca9f7c39ebda6fb18d8ca2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D6" + }, + "result": { + "position": { + "x": 387.38, + "y": 154.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "30a4243456444fd6c30f452a80598d72", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ff3e4811195f72124c5ccd0fd0dd3587", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2adb73ae3dd4cb397c9f98f153997084", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C6" + }, + "result": { + "position": { + "x": 387.38, + "y": 163.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "821caf8c3d04a6cdd47ae576cc89f614", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "de0f1d37ac9fb26ec6b68b5354a96673", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "68efdce02f71577c7a8300a5d63574bb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B6" + }, + "result": { + "position": { + "x": 387.38, + "y": 172.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4b6698b689540d85a3aa7eda4ffac3cd", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f6c16d560748f925da4f8dbabf94d690", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "78b604f7fb1306c758ef08983f1f8e0a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 387.38, + "y": 181.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cf852a713466efe9621ba1e48f86a70c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8050d68611b2c6e21cc22b10ca8fe9d9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5c3d85979ee1762c38329edf3006753c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H5" + }, + "result": { + "position": { + "x": 378.38, + "y": 118.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "beeb1384a066084e7f8d0afc2c5d5809", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "168ae8caa1b39815ddbe2d059f9b06ea", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b4635132ab044bda6c9370637a366049", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G5" + }, + "result": { + "position": { + "x": 378.38, + "y": 127.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "79886be6c3f2bac1fe3813e05d83e053", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fd94fbd3b14b1a7da747addd7cca01ac", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5b25917bae9f3a6a771876ceb4188096", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F5" + }, + "result": { + "position": { + "x": 378.38, + "y": 136.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8ffdfc416e246392de03dbb83f7a1b61", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a87ed74a14ae673f98aba760c3153c1e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9a45915902b015f83b6428af0afa5619", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E5" + }, + "result": { + "position": { + "x": 378.38, + "y": 145.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a07cf43bac38971a798482b663908d73", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d33893f4e532a71cd247ab513cd6cfff", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1031facd8fc260a451d5fd6af068b4c9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D5" + }, + "result": { + "position": { + "x": 378.38, + "y": 154.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "75ee7c815ab39ca12fdbfa9c66422397", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "442a54f51a4faae797053baa6bebb9bc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "34609dcfb227977ed156d05cfc521a7c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C5" + }, + "result": { + "position": { + "x": 378.38, + "y": 163.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d6b8ec19c0714ba00490210c44dc3a82", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e328e5ed8db7a5704594902a23041e27", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0fffff47107ea2229ecba02b2aa1ec7c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B5" + }, + "result": { + "position": { + "x": 378.38, + "y": 172.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0b614755e624cc6f354921a166742eb2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "69201ca1502c01012de33829a93aa047", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "41bd0a0167d4e4b607ac2966b231adb1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 378.38, + "y": 181.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7762bf3be43234e5a7762ab79fb14c73", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "97c680d0ea7f63df30127ae0e85c3ab1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5c4e6c353fc06b57750906c985e25b19", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H4" + }, + "result": { + "position": { + "x": 369.38, + "y": 118.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7941f12b0343df2846267e00883ade64", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "47d7474b790a61303755ab709a8008c8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7fcf036df36c202a2cc66bedca4f471d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G4" + }, + "result": { + "position": { + "x": 369.38, + "y": 127.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "77ced35ad47fdfbddbcb81f1ad688144", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "431f8559c4c166db86bda7676415d326", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "06ca64ddc6ba06f4c2efd7a87c4b5b58", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F4" + }, + "result": { + "position": { + "x": 369.38, + "y": 136.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2bb6862202b11778a3a3c19cd3c5d986", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "541dc0ea7260fdec2fe62e3cf38fd57d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a8cc1e16e7885c4e17c38561c59605db", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E4" + }, + "result": { + "position": { + "x": 369.38, + "y": 145.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "35af7032e0a7b89a7de257d7015761da", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c7e207def8167c19ae97a7fadbe980cd", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c5e2db95d6927dba5c4e03b8e609b256", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D4" + }, + "result": { + "position": { + "x": 369.38, + "y": 154.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4024c7b641cfcc06e8fa13ee8cac38e5", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1cd753b3389001ed2ac45780af12afb6", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0e0bb90eed5ed73b08c14ea449cfa01a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C4" + }, + "result": { + "position": { + "x": 369.38, + "y": 163.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4ca453b5eececc4220fcdd908d3b6ed2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "23ff017728314cb963031f0864094a4f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3c161c9f1b7dbc07b512e77cd8e7cb6d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B4" + }, + "result": { + "position": { + "x": 369.38, + "y": 172.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "98c54b166a3cbb09c07ceb5b76fa25ab", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "20ee90f7bb3742af2f9c3647b7592018", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "74a16b23ff85426b52d0082824b5d082", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 369.38, + "y": 181.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d30221c34a97db4ba75ea750fac1d6ce", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4dd6f55723192a4f488d9d129ba9d1ef", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b7c8de9979cdd07b62322dfeea53f5ff", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H3" + }, + "result": { + "position": { + "x": 360.38, + "y": 118.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dc8fce620b0725de3c81d12b66527479", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "501ccdac51728a548574a96a69e0a452", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2571ecf58421e8ab8679b4844e83b09e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G3" + }, + "result": { + "position": { + "x": 360.38, + "y": 127.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "480558d29646e919e32bdb7ff39de631", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e1d52bfc78a375249d68b19fbb58052e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fcdb3e8c2fb5918abeb9d34bb45e2b0b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F3" + }, + "result": { + "position": { + "x": 360.38, + "y": 136.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c37e1795e6b97f076e7dcc8c28b934d7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f270a51b193fbd3094472bde165506d7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "da91ae5f6ba893c3dca9cfea73aaabe8", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E3" + }, + "result": { + "position": { + "x": 360.38, + "y": 145.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7c00d73797be284f9f8f77b1d17b5c7b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2299498f450189cddaed485bd520bd5b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c7818c4bf1d2975e4520f78667d5543b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D3" + }, + "result": { + "position": { + "x": 360.38, + "y": 154.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d9a30429521cdcaabb333d7d19c81523", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f3db8596b9884e076afb47df0d8372f4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "992c0d65ff87798d0559162a850b7b57", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C3" + }, + "result": { + "position": { + "x": 360.38, + "y": 163.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f4d999f1d5b1107c5397e62ab2d9a2a5", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c956748a71423728e0a80c96128c438e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "eb0e9b252b08f64e9f0a170d0a24b64c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B3" + }, + "result": { + "position": { + "x": 360.38, + "y": 172.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cf7454d0ae428e7d3a02d9c92c518a95", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fcf76cb038b095383da71da5ba06cc42", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3fcf43e70dca72ffc4afee3833c9c825", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 360.38, + "y": 181.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ac2cb8c4935751b730210a6e349fcfd1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "32ceb873d6e7fe9de361f5bb48bc8c2d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5c7f3561d1a45d6a7b5fdf674aff45fd", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H2" + }, + "result": { + "position": { + "x": 351.38, + "y": 118.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "335471ecf828accc5727a98d5169c6ee", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e089f97ef2d56227a5d7887251bdc120", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "93fbd4a5057c4634623eb3018079a624", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G2" + }, + "result": { + "position": { + "x": 351.38, + "y": 127.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7449e33c0b18277dbc269606e7367759", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c8385b24d207acaf78090194a655a056", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4bbb4c1c05362d8689a289e3477051da", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F2" + }, + "result": { + "position": { + "x": 351.38, + "y": 136.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2492c47db2a6817bc245a65410a9a8d1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1eed7575c2a1e6dcf69c20b74c9148ae", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ff765e97d0974910356d10c9ac5488d5", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E2" + }, + "result": { + "position": { + "x": 351.38, + "y": 145.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d6b39e7882b8df4f1d445bf674c9b8a2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "64918e6d5def3b22221fa73854699656", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "54c9783a656fd4d05710c40840b9c5ca", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D2" + }, + "result": { + "position": { + "x": 351.38, + "y": 154.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e69e5f4e1c16b0f0e937676d4b932721", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ce2863e400b84005e1cca56457ee0cb4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3ec31b33c3cafc005249bc76ce26bdda", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C2" + }, + "result": { + "position": { + "x": 351.38, + "y": 163.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5730a79a988f6fd961e5bfabf8f0c92c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "16ced8823344c2b63a1e68b5dfe2c48d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0ae8f2bc5ef69eb82fbb1750e701b635", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B2" + }, + "result": { + "position": { + "x": 351.38, + "y": 172.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "042875beaadb18b2285a6165e56f4ace", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ea705d8ace56b0317fd5bc09d03bd122", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2cf579dc80da3cb27384eee00fece36b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 351.38, + "y": 181.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a2c0b6aa148bcf361735b6165e806bc3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4ff95a3fbdfc0a320bba052969b274af", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "227a00080a91a214dbe6cb1476accc46", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 342.38, + "y": 118.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1db11b28ed2d95edb6ced5e75c5f46c1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8e3d12985e8d157172b011f4f7b36abd", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "942d7f51006d0ff73587d2e26a0342d2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 342.38, + "y": 127.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cca0d17eec7ca096e9d536aa0c10aef6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6f705bd949422c03678166bc9cafe80d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b5bcf6d36bc9a7353ecf84ec1d48c85e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 342.38, + "y": 136.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4b359b8f590bd2108b05d40da842d8c3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fdd4f84ff4f642246bb1d7293d1a13cc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3ec3e5c6ed45b3dee4ccb38c87a0541c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 342.38, + "y": 145.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a82cf1f31b6359b412edd7eb77a71b4b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3d5d8a1eb5c131921e23a036902aefec", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6d0ae84e38bd36e47c45d9aebb42bd34", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 342.38, + "y": 154.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6fd82760cc5f4cff74563783528f4f2e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f6e666aec71fb42d9118c667b483251b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "04f88f175c52ad9eb320213b75b81bef", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 342.38, + "y": 163.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "848629a3f7dfabe679fd6f832eda5068", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c737fe96a53e27f05f1100d64c94a335", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "53dbaabe4be5b7035ca8374df15d05ae", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 342.38, + "y": 172.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ef5b3084ef0634a762ba2ea4e33c3137", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f9a4c9d29ea44028738524fe410fed6e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "834dce2ee0bdac67367dcb4d0fb3f469", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 181.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b154d08b53d6d408d88b2938efcfe0f2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cbe28ae92197c8ce79c6176e9fa2ddd1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c8050e6e014d1528b910b2fa3412da5b", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "B1" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8ef075c0d7b5a0ee837a6d300f3db722", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "C1" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f3c242d47b13d58d37ec2ffcc7892a0a", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "COLUMN" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "126c5c77025f14b164c897a8d3cd1d1b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 277.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1cca8237ff6a261408e967fda5dd5c17", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c62caae2a676440f8881437366351bd7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "86e27513b187da071e0eec40eb3e6949", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A11" + }, + "result": { + "position": { + "x": 268.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0da3291a80d60c41d99f6e9f47344338", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8016025dba135726e1471f9f770d4b14", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "16b057662b0b7f5b21cbc5c9c5e0e6cc", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A10" + }, + "result": { + "position": { + "x": 259.38, + "y": 395.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cfe8b09978915a4887c5a1917dd8db97", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0a5ecad8ee8c33416b236c65f2f17042", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "64516540fc10a1d9d92f6e6a0b5e84b6", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "ROW" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f25bb4d2a464c59d45b5b7c3cb34d1f9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 178.38, + "y": 118.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5db97b51f60160f07eba9a2c4a57b823", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "16f961f2a3fb6779f13c0ca43e4b9ade", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "deafd1d60fa9347c6bb4bf05a782bb9a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 178.38, + "y": 127.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d97e5626fdf4c63c8263f09493397843", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "942bef03eae31a15abfa4aa661fc00dd", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "790d2c6fd15c3362e1811b9fefa589b2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 178.38, + "y": 136.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c6eebd19475379255ffec4ff71d4c4a7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "997b18db28586b495863dc16506c9829", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "94cfd354e3863d250d0d7bce98197ef6", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f383727d6a330af69f07de0b5a8dee1b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E12" + }, + "result": { + "position": { + "x": 277.38, + "y": 145.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5475a2cf244c58885c7a56b3e2e1ab9b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "961f006f1da77317d99cdb3ce659a900", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e9769b09cd53926aaf180d2dbfe9d1a8", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D12" + }, + "result": { + "position": { + "x": 277.38, + "y": 154.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c9b43ec76ba2d126b154d70548eb9bc1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9e1c3afd65a0b99fc5a501adcb191556", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9096935e37aa64ebd142eb660a5cd1a3", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C12" + }, + "result": { + "position": { + "x": 277.38, + "y": 163.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d84766085181aa64f590562a745e9698", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fa8bd25b76407d54077d838f3c77e0c0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3838582180587de8e0f594799bc64117", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B12" + }, + "result": { + "position": { + "x": 277.38, + "y": 172.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b896e33c3975a7aa9ffb7e50eec4b562", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8bbfd10e8030b6b2f56abb6d940dfb46", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0a35f748316bf8e1f025824f8ee0c4ec", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A12" + }, + "result": { + "position": { + "x": 277.38, + "y": 181.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6d31e4d08999e9ceaf274ac45424fba3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0ce572910f7fc90c514be2f459638b54", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f1312d64708528eef7dc70a6c8814f54", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E11" + }, + "result": { + "position": { + "x": 268.38, + "y": 145.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6c711a4a29c6f0e015e8b3fbe4718e97", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0b43e237a1bdd96dcafba94bca20abfc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8e2e0508c0dcf0af56479dab096d4fb9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D11" + }, + "result": { + "position": { + "x": 268.38, + "y": 154.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f71a38cdfe270907939c7b4625e461d8", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f5f9671b93d74fa165c2add8b8e28be2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "53268795e8690fa7c8654b313647182b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C11" + }, + "result": { + "position": { + "x": 268.38, + "y": 163.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50763b5797b117f3c03a25f4932f8c99", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "76834fe3475bbe7789e6bae6d0b5cb2e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "815926809dfc56315e8af360f55fef1f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B11" + }, + "result": { + "position": { + "x": 268.38, + "y": 172.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "444214c72d37b42b8e261f6a4709cfad", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4cabcacf6a35d37fd181e30d85b4862f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6173532284cc66b313606b9b4c3d55fb", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A11" + }, + "result": { + "position": { + "x": 268.38, + "y": 181.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "08bc6986ad89f72b3c535ee23db466ee", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9a7215b248b42a16b8e9478daa41de05", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "732be9df306f12262c509fa3fe8eab38", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E10" + }, + "result": { + "position": { + "x": 259.38, + "y": 145.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a17c63bf09919c4052a49e8b4c546705", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "672b89c13906557e44672646e3188590", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6c2e926df6c753a6f2fc1c6b0b7fd264", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D10" + }, + "result": { + "position": { + "x": 259.38, + "y": 154.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d30848c94367c3d36a1b6b5c15c3ff0a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ac6160b1905f4ee03d2ce4b1b721f556", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c758bcfefedc75514cb88bfa2918411e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C10" + }, + "result": { + "position": { + "x": 259.38, + "y": 163.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "37de9390ca5d10fcacfca813cc53d175", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a4ead647e57132b8394a1595d747fca0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5e8e85da7a1d51b86d1e2f71fe41072e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B10" + }, + "result": { + "position": { + "x": 259.38, + "y": 172.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a3a85f5a079baff590de4577a7252148", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "049ac70d696ecf3e9e60c4abdb35ac48", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f0aa9ed85730349b9017176ae5ca978d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A10" + }, + "result": { + "position": { + "x": 259.38, + "y": 181.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ebd2fc46d4570a3b7ad7350adbd5989d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a573b5dce92a03636747fe0720ba6346", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7ef0dc5f256980a9b9dc112b392dec84", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "B3" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b24e78f004c33f92cf559c38cf9fcb2a", + "notes": [], + "params": { + "labwareId": "UUID", + "newLocation": { + "slotName": "D3" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c9ceff7610d98d598d594df5ff2b30e4", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "A1", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "00ce43a8c222e0ddeaf66fb6b40bb3d6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H9" + }, + "result": { + "position": { + "x": 414.38, + "y": 225.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7a46535206fd461ecab8d2520a6a6bad", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b01f452bef5c3f65ee4e35d647f73750", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f155fe5e7afb1a437d78a9ea9b02d360", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G9" + }, + "result": { + "position": { + "x": 414.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d0399ddef3389e93d74a4c8680ec4943", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ef8fd436a299183c0185bb4e15ee820c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2fdf26592751a74a80a6a59ec136c6e5", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F9" + }, + "result": { + "position": { + "x": 414.38, + "y": 243.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f2b06dc43712483cf2fc804214a0e611", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6fef2b96c488323ae34072e12a6fe3f9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3229040f7ba23c32b2f5419ab4c4f4dc", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E9" + }, + "result": { + "position": { + "x": 414.38, + "y": 252.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d0033300b23c5aa1d98777343fc88508", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3feac66c12532663b3bdd49060100eb2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4ed9a38f7ca85f069ef0f241ea24dbc2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D9" + }, + "result": { + "position": { + "x": 414.38, + "y": 261.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "aebffbf093ca75d563aceef14bbadb31", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "139435c9c6c4871a308f374e3d46c4c9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "142a211304cbcb7365b964f6dd96f736", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C9" + }, + "result": { + "position": { + "x": 414.38, + "y": 270.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b217b94b6fd9e5976f286e1eb560b034", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4c55cd40148d2eee041fe96eda3ed333", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c16e6cd523e8885f7fafbadd7f880142", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B9" + }, + "result": { + "position": { + "x": 414.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "642f9ecc83773d8747fac347bac22c09", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ef3f82dbcf7f197c8e05ac8784b9be90", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "81b82466bfb10831d918e467b31107d6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A9" + }, + "result": { + "position": { + "x": 414.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9974e84e2ee65e046dc3cdb9e44c76f0", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cbf38f73dfbde406c8edd4189fb1c2cf", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5af923c00401375762e241149d535832", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H8" + }, + "result": { + "position": { + "x": 405.38, + "y": 225.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b64ad8409727b8578df89e24378ecb42", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fb5a3c0150d39f74d7af40de65cf4478", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "830de6e99d25638ea76f426136ecd600", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G8" + }, + "result": { + "position": { + "x": 405.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "35ba19e1e4081d5c4a4287b14c8cf82b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "49f9a5477a244e22da3027c8c7a01e49", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "069a3d08788512c6b7a66203698afec9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F8" + }, + "result": { + "position": { + "x": 405.38, + "y": 243.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9d4139fee06db3d5b838db402d18dea7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "04a4253189340c72eb040539f4f2b421", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8f21a3880ad53a4d35907a148e4c43be", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E8" + }, + "result": { + "position": { + "x": 405.38, + "y": 252.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d50d3447f02f5f46db58c047e01500c3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7b115af7c19e48bdd5a03d2a9e7368e7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5b7a71965ff7c0e0aa28ed521a650313", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D8" + }, + "result": { + "position": { + "x": 405.38, + "y": 261.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5913c148c11b473a08f1a23a27b20c4b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e5a057e248cf6e6879245a5080501af7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "04114466188a269fb0dd2cd90be72157", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C8" + }, + "result": { + "position": { + "x": 405.38, + "y": 270.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7c1d7d7f5141218181b8bb789053fc98", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e31730d77efbcfe3fe41bb2fa8ba11b6", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "56b0f5c77a1f023c11c341d644467ed9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B8" + }, + "result": { + "position": { + "x": 405.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "79dfd9f6ab448d3e63315536fa17c0e3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "83f03b862ab3804d1e10c5cb2754eb74", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c635f48d32a9170dca0b5c3184f2fe70", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 405.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "05816eae47c25a492f463fd9c10ee288", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "95c6e883abd5f5bfbdd0659a03bb992f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c5f936d6fffc96a0aebdba9bacdd307", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H7" + }, + "result": { + "position": { + "x": 396.38, + "y": 225.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ef8cb98912f96274a11152ebb6b0333a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "553eefe8a60bc5f9af0b1e2895462065", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6fa5f840439742e70d716d9b4be03015", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G7" + }, + "result": { + "position": { + "x": 396.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6b70c6287dd345eae40c36a940c55eec", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dcce39d6b4c3c0e9a88d4a5da46e1d7c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "af29dbfa0ecd9bd263bdeed0e8013f15", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F7" + }, + "result": { + "position": { + "x": 396.38, + "y": 243.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f3793ad5199dc95933c9bf8c35ae5fd3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e785ac169acb121c581eb5267b625f86", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6642b9e93eb5bd4d6834fc9eab3d66c2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E7" + }, + "result": { + "position": { + "x": 396.38, + "y": 252.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dec7a9f06954ab80cb8e2389143f1d26", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "978c4ee2182ccd06fe966a74cb245e78", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ba5c1d8216409140c97f26d0d6d9b2d4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D7" + }, + "result": { + "position": { + "x": 396.38, + "y": 261.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4e68d0b404ac4b8a139ba03bd05695d1", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "58aa6d6b2892e8789baee893bbc6ef9e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ddc42dcdbb3c9f89ee77e58050561b3b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C7" + }, + "result": { + "position": { + "x": 396.38, + "y": 270.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "582c9faca922ea733608317ea28e85b3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fed8c44bcbc26ddccaff671fd5277faf", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b1329e0308454bec24a4e657c24d661d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B7" + }, + "result": { + "position": { + "x": 396.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f0c05150b60a0246fcfacf070d162b6b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0ad2956b1b39a5015bc883fef8c7ae2d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "edaece6d61f0ddb00cb7abb2c34a2069", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 396.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "41242e7281ff7c0eff57a889064dcae0", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "92c1785e5ea8b2bd3e48f749e5258bd7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e6e7a014e9b92c6dfc17b585f04e69b1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H6" + }, + "result": { + "position": { + "x": 387.38, + "y": 225.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "02011cc5d5ba5f57a934d1ab9cbb625b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "694ad83f5e92b31c7c6ab5396e443dbc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6fde74ef7b06133c8280671bb36bdd96", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G6" + }, + "result": { + "position": { + "x": 387.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "376a9033bb01f5d412e5374b2947f191", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "035e4972e93daa21ba735a393c1987fd", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c3d78fb7a3c90b94fabab444fd581212", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F6" + }, + "result": { + "position": { + "x": 387.38, + "y": 243.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cb1018930eccadc55f33adc0565e5e9b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0610fd844d1591974789b1c192fca782", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "24900d56d46581b06813dd6765ef0dd6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E6" + }, + "result": { + "position": { + "x": 387.38, + "y": 252.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "997f1c3d4dedf9e422d09903f69ca2fe", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "14c872ce2ab5d3ce77d6a0f6a2d611b1", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cdc0382e10fa980379a1c70d22ad3d2a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D6" + }, + "result": { + "position": { + "x": 387.38, + "y": 261.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "47d2490341012acba42f439bb3283d87", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2fa8f90bf851328bb5b5e2fb26e29e82", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f7e33196b53e5b29c518f343182f3b72", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C6" + }, + "result": { + "position": { + "x": 387.38, + "y": 270.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f965d4fde16b31f30fc78873e3bede45", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a777637b41a459d960354bc99355aa92", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "adcc865a8528038cfaaf90919d9b63b2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B6" + }, + "result": { + "position": { + "x": 387.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "91916df40447271d92c30e3ab0e3a5a2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0569a1b997368e12c29e9a571ccc21b7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a2ef4a40b91b1bdee500734e6337ce01", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 387.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "00e64a7813af6f95a1220487f1808271", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "573280f99e175fe129e4506ec42e75a4", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6eb3ec369f9e257340f2a5d46f6b9869", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H5" + }, + "result": { + "position": { + "x": 378.38, + "y": 225.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c8a33bdb02320b4c5d892e8765f6e8f3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bdd39f9d63fd24386206590e685194b8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b8819a6e5567ab8b252e8182dcbe7f56", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G5" + }, + "result": { + "position": { + "x": 378.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2982e3a3f6a13b996bde37c8a86e82df", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8f3377848cc73fc26c1ab17a44f8359a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d6821c7091e4d7b4289ed0fb6c31b8bd", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F5" + }, + "result": { + "position": { + "x": 378.38, + "y": 243.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b7c1d0d9e4505bfadcadeb48db7b1be6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "227819e5e7519f329450ae4ba462b561", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e31e01856d0cc406c9d6ae898f6deaf1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E5" + }, + "result": { + "position": { + "x": 378.38, + "y": 252.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "57a2c3ff50ac26cb96a25a4c0d0194aa", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a499d669b51e93b91ff2503310c935ec", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2056f57cad76c9797326e899fa7cfcee", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D5" + }, + "result": { + "position": { + "x": 378.38, + "y": 261.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2dc66cacf68b2e44d1e60eb3bd3c2460", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bff745764482e0ccf7ec20b0e64ce3db", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2271a16d806f6ebcc3d35fc99a00dd2d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C5" + }, + "result": { + "position": { + "x": 378.38, + "y": 270.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5465260cb5796b30c2c1629350fa38d7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c04ec1d0914b87b712f3f448faf5f567", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ecb42d618687f14e841e2e2d990a1680", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B5" + }, + "result": { + "position": { + "x": 378.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "167f9c0c6f34e09045b0d4d67f1a6ab2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1ace772a545a37a1a88be9e38436d14f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6f25c1b5ed82e1c543aead95540d458f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 378.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "775e04ff0108b9ed2a0c0244b2fb9eed", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "33a8899f32e989ce00fc88de77fd93b7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7acb65844cd97625fd17b0ec613e9734", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H4" + }, + "result": { + "position": { + "x": 369.38, + "y": 225.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50fd8b5d3f5557dd862436870c97feec", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c05c3e3222f8fdc9fdbc38e290749c8f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d1605603b1ed6dbe9f3004c8802ddfc4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G4" + }, + "result": { + "position": { + "x": 369.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5e45fdabb709a97bc846559309ddf7d2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bbc2dc69dfaa97a3dcc52a08b55a9058", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9e2f0be3c21aa5b4196548373e58bf79", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F4" + }, + "result": { + "position": { + "x": 369.38, + "y": 243.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bf5c633e0c1e805b5d2813f14d59676a", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c68522da954ec9c5c969585ad07264f9", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "46688effff308932f830619fde3632e2", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E4" + }, + "result": { + "position": { + "x": 369.38, + "y": 252.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6b01c75efdaa11ee298158fdf62fd562", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "490008051cc4109b69ebb76988686a02", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fc52340619402cc47bd6971ef58751fc", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D4" + }, + "result": { + "position": { + "x": 369.38, + "y": 261.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e2a323013a1606b88a4f6f757b5a5723", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8f0848d682d879172df0710085f34a5f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6ce25c10a61ce05411943ade30ded765", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C4" + }, + "result": { + "position": { + "x": 369.38, + "y": 270.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8f47648d8ea52c274d8b00d030a48a36", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "93acb9bad023faf025679c8b16ccfb60", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c877974627cb210b1415b070dd82cb3c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B4" + }, + "result": { + "position": { + "x": 369.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5c321d95cd262414cc574380fdbc34f6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "57f809caf59565c8b2331ac5b7fae631", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "042fab071ad98bb453515fd6ad4dc1be", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 369.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "278bab3264080ce0a4dedadfffb0ed70", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3d9f93fd5956b3d4355a11e113c7bc0f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1b89778e18e1422de4937f4fd9fc8a62", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H3" + }, + "result": { + "position": { + "x": 360.38, + "y": 225.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c512eae80aaef17abce12bdb5836d984", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "46926f1231846fd3a83cc84aae1f16ca", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a04ea72eb123cdd900fbbdd0aaff09df", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G3" + }, + "result": { + "position": { + "x": 360.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "39c233c13b60a60dd5ea25a4f7895d46", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9ab2259101883b8631895d45dc3b9629", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bca1592fc885739eee6960af238767f4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F3" + }, + "result": { + "position": { + "x": 360.38, + "y": 243.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a5d3d21d7ce0fc8148d7f8944f463f08", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "acbe5d508718973aa69240cf3df378dd", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dc21aba9838ef842e6b1edf31586756b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E3" + }, + "result": { + "position": { + "x": 360.38, + "y": 252.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "436492ca6ffa5de1a924610a369db4bd", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "21157f9d79c30301400a48fff73a4664", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f346f1644569c8d8b1eed0d2c9367a7b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D3" + }, + "result": { + "position": { + "x": 360.38, + "y": 261.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "726a43623941ee40b683376ae48e41bb", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5fcefcc1491fdb5fa5ffb71df06dc13e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "64518e2b19524f45d539a0a538c3b976", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C3" + }, + "result": { + "position": { + "x": 360.38, + "y": 270.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cf527a16d506365e2750776a2233bbd3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c09dd58bbd0817f22e9db036eda3b848", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e62d9339999ab2bb3329d6b48e315a21", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B3" + }, + "result": { + "position": { + "x": 360.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8f81ba4e7407c1c2856f6b39156f6a47", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d05ddaea8947b236ba411d4868882670", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9a8c17dba80f717e91dcb5d499ea5d5b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 360.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1564b0951962dda5979f915fdccbb747", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6a16e002ff187f39866862f93a19ea25", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7d4d96afe72f305657a3b04b3dfe231f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H2" + }, + "result": { + "position": { + "x": 351.38, + "y": 225.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ec2f775ae713a87aba1cb9270215a464", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1547938206c413f7d78f2d03776de375", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7ad103a2c8fc0011b7a85c9a9e1bf49c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G2" + }, + "result": { + "position": { + "x": 351.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f094887e9f6970766fcd7a1200207b02", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f45cb7e563625e2e2c63466a374e4571", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dd1127f3d9d034306ae2e9ffdd3a9a92", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F2" + }, + "result": { + "position": { + "x": 351.38, + "y": 243.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f4415d8320996daac4a4ee96ec0bebe6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7b088a2861f70828fce956738cc046b3", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ad600566117ddc501671df33eb9978e6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E2" + }, + "result": { + "position": { + "x": 351.38, + "y": 252.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "31408719f70d5db499a7bbf072878fe8", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6f5a1c50a84b1f98ea1c6ca02800ab58", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "15cf5f4f042ab38b8cf6b444dd01d44d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D2" + }, + "result": { + "position": { + "x": 351.38, + "y": 261.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "47e9cba9a29b012c537e7dd22232227c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2f4b75e6266e942ba2f7d94bf3e18586", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2f8a201aaa0715b83922298aa1f432ec", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C2" + }, + "result": { + "position": { + "x": 351.38, + "y": 270.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9db5738c1bab7713658c4ede609a36db", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ef936de543090ba4338f968317244400", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f652d62b1dc78a1917b97c6affbea49d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B2" + }, + "result": { + "position": { + "x": 351.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e21083bcc8d61ae0c46030430f248ea8", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0c5ba5b1694fe5500d79be0c8543c2b2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6de5ab7a537b291bcfed37927f661d28", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 351.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "88999c4e2f775d2805d49eb2ea1baf8d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "32318bf6485242269348a0ae991e158c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "cf34e6584bb97bd9c312a0d26a0aad75", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "H1" + }, + "result": { + "position": { + "x": 342.38, + "y": 225.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f125c2af37329aeb4e85edb01cd8336b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6b5c064c4eb4410f5594934e71b98a79", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "93caee3e53135e5ecfa3c907d777b9f1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "G1" + }, + "result": { + "position": { + "x": 342.38, + "y": 234.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c6c923f1909de4b970893823da404e3d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fd7ae750631717e17be16d7c4411d80e", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fbb0f643bd20399aa11b93dfaf74cee5", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "F1" + }, + "result": { + "position": { + "x": 342.38, + "y": 243.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9781d9ca31a65a561942b172553c1f90", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "294fc156f8bcef3a36886e2c11ad9ad8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e8f925cc21744a31e907efa9af2f24cd", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 342.38, + "y": 252.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "67a0ada0ca14eb69a257eb2493fa0f09", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "907d3d9eaef0f68d6367822da1670637", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e9ffd2c8a4265ca3a83d984778d73d4a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 342.38, + "y": 261.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5662a0c5626afb96cdca5b543b9c5816", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "89d703ca1aa8fe353310b65793c579b6", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3ef272f51419e2b2db849aad46478a1f", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 342.38, + "y": 270.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3cd9ba9920f26683d6c62db34b4eea1b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "741d0430b8ac05059522890fe9562dfd", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1950e443719a1337b9ce6c353fe077dc", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 342.38, + "y": 279.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "56bd89917f2d269e8efdf2f7efc7a37b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e60550c8eae4918291a77f3ebf248f94", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "89149cb7224aa523675da1968431ad13", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 288.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ff6c0211638b7bcf610a6f69b4b93c67", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4f353aad474a330a78d4aa1f83ad964c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "55dec48470e1371ee6433d757efd030d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E9" + }, + "result": { + "position": { + "x": 414.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1f5bf76302891e285857d54b7b204432", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "250d19ad8a6a57ca81c586d32555b93a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d70102fc84cb7297a3a6256650b1c858", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D9" + }, + "result": { + "position": { + "x": 414.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c8e2ccedd4677c57d2edae178a768989", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "daa343a2bf6f17e77417c9cf852bf42d", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d92cf0f7b3bedf00fa3db15df128be8e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C9" + }, + "result": { + "position": { + "x": 414.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "35a828d2454ea143315f279031fa15a6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "968ff6fd6767f7ebd1df3b64cbb3cedf", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9ac7c04598bff0d25e961700e49cc06d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B9" + }, + "result": { + "position": { + "x": 414.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4335989ce4720c4ae983d2d53a76c3f9", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5d5cb0c60a5f1190cb611c56b88ebe34", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8885bf8a13dd95b2b7c420186cc33abd", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A9" + }, + "result": { + "position": { + "x": 414.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ec79792ffc863f948cd8506f00ec5bbb", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "599739c7d84ec28ab4463ea52e40a8e7", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4bc9aee612387f0623a2c6fbfd5940c4", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E8" + }, + "result": { + "position": { + "x": 405.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "43e7e551b3c284f3594192752895b0da", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e5065ac13956169ce3e68cc59d56d450", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3566ecc8d4b8b48271f3925c28dd8c37", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D8" + }, + "result": { + "position": { + "x": 405.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6862e7a144badc6f7f1565b478b77e0d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f859fe0f60324ddc65851ff37bcc50ef", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4fcf1992ea40b1326973b9ececafddc0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C8" + }, + "result": { + "position": { + "x": 405.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1f96535ddca507554eb84c702739c0bc", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6dfd1d178ea113d42922f6659344014f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c84e9673120c89a4dfa0b40851c1fb24", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B8" + }, + "result": { + "position": { + "x": 405.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0a2fb61cf21d6c8cd805723b88f8b6d2", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c721af21df521dcf12cb8038c4526ebc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8e6ee99477298b89fc141545bda2e46c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A8" + }, + "result": { + "position": { + "x": 405.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "31cd465454bff519346898323ee286eb", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1568e7ab86e3ff4df786254509457a7c", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3f957c9385fb7ceb823b0e81fee9c20b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E7" + }, + "result": { + "position": { + "x": 396.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9f2da5c7c116069516da83eed6b009c4", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "20e89a60f5401a3a6855836e78451b58", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d2b1553f68dacd513229b91f43fa91ba", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D7" + }, + "result": { + "position": { + "x": 396.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bc3a7f2c174ffc69db31a41d4e874eaf", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4fa3b1d6bc43dd2914f0dd2b87e20f3f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b6053bb4d079087ece95de378cf5b727", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C7" + }, + "result": { + "position": { + "x": 396.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0900f052b6cf757e7d3ecf9e07cb994f", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4bcc85ca7778bb4883df06ecd4512b9f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2908129f8099d5b72f14c0a72b8304e6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B7" + }, + "result": { + "position": { + "x": 396.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9a73ed948cae476b2ed8c2a1bed285e5", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "17e6381d39a292a6138319e5317fe7a5", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9524cdfbd41425d33939f7eacbd81f42", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A7" + }, + "result": { + "position": { + "x": 396.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ae52dc8256c332278b26fbd36ea0b3ab", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "993da4ad4b51e359e7289337c7673d58", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6fef63838f4dc6c6a097ddd8de4d4c9b", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E6" + }, + "result": { + "position": { + "x": 387.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9d73d34d68c147da843e6ea78063a4c6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a7c03bea6e9e710cb267dac20380f555", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "55863a01b067d60f64d14caba6b60de1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D6" + }, + "result": { + "position": { + "x": 387.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "59eb386c0bac850ac05780da14bdea21", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "606fdb157eac467ecd719b67fe1a5303", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "3d5c78d5f8d5a38c315e63d6200afcd0", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C6" + }, + "result": { + "position": { + "x": 387.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f65271774f81b4b588028201714986ad", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "99b5dd74f2acb6a912be3d18cc609de6", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1ed90f0fbb3d039a696556fa64e9b629", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B6" + }, + "result": { + "position": { + "x": 387.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "45238aa5663dcf38d95edfaf06f734fa", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "30385d4a1a0da1b2c058c65b2ee9c2a0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d287258a9692d664f009c7fb235fb510", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A6" + }, + "result": { + "position": { + "x": 387.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d9fdcbf55c890bef010e4fecaf7d61a0", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "46ce64494de84c0c68cdc693665cf48b", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f13cec355874ada7c03a73131da1f861", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E5" + }, + "result": { + "position": { + "x": 378.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "162ff16402f680de1bf7821c79e84eb3", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fe27bc34a179bccc12876f35063ee29f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "66c3603f0b58daea903d2c9d5f85ccf6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D5" + }, + "result": { + "position": { + "x": 378.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "87f2aac854f0571cc278dfa4306a3660", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6e4a1a691cb4365e9096243f3eb47a98", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fd604dd452ab5cd3ba829825aa04e5d8", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C5" + }, + "result": { + "position": { + "x": 378.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a6a90c34689d7c3b16c7293e14a0718c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "98136337fa51333dd86c51af85715bcc", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "17c38c86c5b05362df4463b15f038ae6", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B5" + }, + "result": { + "position": { + "x": 378.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "63d57a8a7a33293d62796e5ff3721c87", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c78dcddf6c8c4e7868d0bffe09d68f50", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "5d343ef467a82920d91b089db97d3753", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A5" + }, + "result": { + "position": { + "x": 378.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fdd3df5ff4b4f864e6cb88e129be3271", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0eb7464513802109fba8ed1a4853fdda", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d2c47846f92511d2b6efd0472948da6a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E4" + }, + "result": { + "position": { + "x": 369.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "92e0d0002802dacad114efd2106ff4c5", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "40b4ccbf9e5e1472e203f1be025a7676", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d16270e8d6ce98ba4ebb6a663544b93a", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D4" + }, + "result": { + "position": { + "x": 369.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "f56dafb1fbbe5edd562ac256d7ca3242", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "46f6e272f70b4603c44f160ab7e98af2", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7cd50e07263bfd18a1d73a34570f4aaf", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C4" + }, + "result": { + "position": { + "x": 369.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2f20a212cd8304935925372a04b22c7e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "167a82136e3635b910ce9f425c48584a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "dd00c2f6c2f080718ccff9871dd17358", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B4" + }, + "result": { + "position": { + "x": 369.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fb2c632066dc638014131a671f2aa346", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6c1de267e419fe5e9bce7fdd6fcad8b0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fde356b98736b7c025c8fd5457b094d1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A4" + }, + "result": { + "position": { + "x": 369.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8003d489e65197887659d9dfc6f69b1b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "74740fd766ede8bf42ee5d1884983ed0", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c8f21d48c354b434f5bf23ea8c71d43", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E3" + }, + "result": { + "position": { + "x": 360.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c68b9921019b4085ff49303759c19285", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2cb79f8131ed6388cb93667791e5a5b6", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1c707af9e766107654ee62234b914189", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D3" + }, + "result": { + "position": { + "x": 360.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4b6ef777054d4f0195bd63e7c2f82cb8", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "68d49f391755644380c64e6f20081e89", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8efad399cbac9030e2130921991ac2b5", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C3" + }, + "result": { + "position": { + "x": 360.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e586dfe750c5c88f6031749fa760ab42", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2aec47f945bcc4e908f1ec7e07c37285", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2f58ddf2f563889f7704ae052f699bbe", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B3" + }, + "result": { + "position": { + "x": 360.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "fe13a5842b1d93e60341f2eda4cd9029", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "785aa47ebdd350ddf692798f187e6bca", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "af7d26a9cbc3e8ec511be657284c4954", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A3" + }, + "result": { + "position": { + "x": 360.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0157cf22b548c55b434e128826f4bdfe", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "85ed33fe317038211c4d1d149968f46a", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bf12e7726001a495b6b36c9439bc70ee", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E2" + }, + "result": { + "position": { + "x": 351.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ec28af958f146bfbfb7f755efe5716ef", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "796730616c86fb5c0c185e506246d634", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b3c2ae5aaddf14104f40a4c58b278f8d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D2" + }, + "result": { + "position": { + "x": 351.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0d1598de6b5846e6511cd24f38057cad", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e99deb933a517de05f73bf900b585136", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "4f5393c0dc68ce06a462b4df918d81ce", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C2" + }, + "result": { + "position": { + "x": 351.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b56d86e8a4dae675b1e8cbfe3d90ad34", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "e94efe742feae0a455eccf3911d37374", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "417002b1a16b37f5cbd7cc0215714fe1", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B2" + }, + "result": { + "position": { + "x": 351.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "7eb10162ab9a2159d36a3a1f08dfa1f6", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "438f59f8c0a8e076551c13002a6e90da", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "538aa4317bd4a323ec2bdbd32783388c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A2" + }, + "result": { + "position": { + "x": 351.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "63904b45be75e9f4234133b161664dd7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1551d33d151bef898ed2f4bb4a70d531", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "b0a7bf6697e9d87377be6e342d28f53d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "E1" + }, + "result": { + "position": { + "x": 342.38, + "y": 38.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "1ebd8d7b0f1d280779b4ed51e486919b", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "9f70f5cce4a416f11a829ddba6b6c433", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "23916032e4a9b984d8fc108042b08f0d", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "D1" + }, + "result": { + "position": { + "x": 342.38, + "y": 47.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "6fad36620b8b17a757b325833519415d", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "84feac122e64a74f7148a0152f5803a8", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2068d898446e4b881918e8e73554c62c", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "C1" + }, + "result": { + "position": { + "x": 342.38, + "y": 56.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "ce174720155e2bede20044e49a7f7f8e", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "053e7f1a254d774cdffec83971061e96", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "60bd785ca5aa815cb75bdbf05ee0b42e", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "B1" + }, + "result": { + "position": { + "x": 342.38, + "y": 65.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "303172a538e453cb1e05cf3388e39d5c", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": 54.25, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c60dc0f5dfee6efa24f4c75bf5185e0f", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a4e56bfdf549ab141bb6d3c03e6a47f9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 342.38, + "y": 74.38, + "z": 99.0 + }, + "tipDiameter": 5.47, + "tipLength": 85.38999999999999, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "moveToAddressableAreaForDropTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "0b516a1ae89ff38776e2bef00239a2f7", + "notes": [], + "params": { + "addressableAreaName": "movableTrashA1", + "alternateDropLocation": true, + "forceDirect": false, + "ignoreTipConfiguration": true, + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "pipetteId": "UUID" + }, + "result": { + "position": { + "x": -9.75, + "y": 364.0, + "z": 40.0 + } + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "dropTipInPlace", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "a82abfb4983bcc1ddcab203dec854921", + "notes": [], + "params": { + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [], + "files": [ + { + "name": "Flex_S_v2_20_96_None_Column3_SINGLE_.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "displayName": "Partial Tip Rack", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B3" + } + }, + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "displayName": "Partial Tip Rack", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "B1" + } + }, + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "displayName": "Partial Tip Rack", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "D3" + } + }, + { + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "displayName": "Partial Tip Rack", + "id": "UUID", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "location": { + "slotName": "C1" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": {}, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed1e64c539][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInCol2].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed1e64c539][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInCol2].json index 5c1d9a41364..709b448717c 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed1e64c539][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInCol2].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed1e64c539][Flex_X_v2_16_NO_PIPETTES_TM_ModuleInCol2].json @@ -32,7 +32,14 @@ }, "id": "UUID", "key": "8511b05ba5565bf0e6dcccd800e2ee23", - "notes": [], + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], "params": { "location": { "slotName": "C2" @@ -96,6 +103,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed26635ff7][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_source_collision].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed26635ff7][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_source_collision].json new file mode 100644 index 00000000000..ed5d5a67171 --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed26635ff7][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_source_collision].json @@ -0,0 +1,3898 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-1000ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 97.47 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_1000ul", + "tipLength": 88, + "tipOverlap": 7.95 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.24, + "z": 9.47 + }, + "A10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.24, + "z": 9.47 + }, + "A11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.24, + "z": 9.47 + }, + "A12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.24, + "z": 9.47 + }, + "A2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.24, + "z": 9.47 + }, + "A3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.24, + "z": 9.47 + }, + "A4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.24, + "z": 9.47 + }, + "A5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.24, + "z": 9.47 + }, + "A6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.24, + "z": 9.47 + }, + "A7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.24, + "z": 9.47 + }, + "A8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.24, + "z": 9.47 + }, + "A9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.24, + "z": 9.47 + }, + "B1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.24, + "z": 9.47 + }, + "B10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.24, + "z": 9.47 + }, + "B11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.24, + "z": 9.47 + }, + "B12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.24, + "z": 9.47 + }, + "B2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.24, + "z": 9.47 + }, + "B3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.24, + "z": 9.47 + }, + "B4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.24, + "z": 9.47 + }, + "B5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.24, + "z": 9.47 + }, + "B6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.24, + "z": 9.47 + }, + "B7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.24, + "z": 9.47 + }, + "B8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.24, + "z": 9.47 + }, + "B9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.24, + "z": 9.47 + }, + "C1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.24, + "z": 9.47 + }, + "C10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.24, + "z": 9.47 + }, + "C11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.24, + "z": 9.47 + }, + "C12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.24, + "z": 9.47 + }, + "C2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.24, + "z": 9.47 + }, + "C3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.24, + "z": 9.47 + }, + "C4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.24, + "z": 9.47 + }, + "C5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.24, + "z": 9.47 + }, + "C6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.24, + "z": 9.47 + }, + "C7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.24, + "z": 9.47 + }, + "C8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.24, + "z": 9.47 + }, + "C9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.24, + "z": 9.47 + }, + "D1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.24, + "z": 9.47 + }, + "D10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.24, + "z": 9.47 + }, + "D11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.24, + "z": 9.47 + }, + "D12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.24, + "z": 9.47 + }, + "D2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.24, + "z": 9.47 + }, + "D3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.24, + "z": 9.47 + }, + "D4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.24, + "z": 9.47 + }, + "D5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.24, + "z": 9.47 + }, + "D6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.24, + "z": 9.47 + }, + "D7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.24, + "z": 9.47 + }, + "D8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.24, + "z": 9.47 + }, + "D9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.24, + "z": 9.47 + }, + "E1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.24, + "z": 9.47 + }, + "E10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.24, + "z": 9.47 + }, + "E11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.24, + "z": 9.47 + }, + "E12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.24, + "z": 9.47 + }, + "E2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.24, + "z": 9.47 + }, + "E3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.24, + "z": 9.47 + }, + "E4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.24, + "z": 9.47 + }, + "E5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.24, + "z": 9.47 + }, + "E6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.24, + "z": 9.47 + }, + "E7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.24, + "z": 9.47 + }, + "E8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.24, + "z": 9.47 + }, + "E9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.24, + "z": 9.47 + }, + "F1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.24, + "z": 9.47 + }, + "F10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.24, + "z": 9.47 + }, + "F11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.24, + "z": 9.47 + }, + "F12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.24, + "z": 9.47 + }, + "F2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.24, + "z": 9.47 + }, + "F3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.24, + "z": 9.47 + }, + "F4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.24, + "z": 9.47 + }, + "F5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.24, + "z": 9.47 + }, + "F6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.24, + "z": 9.47 + }, + "F7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.24, + "z": 9.47 + }, + "F8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.24, + "z": 9.47 + }, + "F9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.24, + "z": 9.47 + }, + "G1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.24, + "z": 9.47 + }, + "G10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.24, + "z": 9.47 + }, + "G11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.24, + "z": 9.47 + }, + "G12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.24, + "z": 9.47 + }, + "G2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.24, + "z": 9.47 + }, + "G3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.24, + "z": 9.47 + }, + "G4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.24, + "z": 9.47 + }, + "G5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.24, + "z": 9.47 + }, + "G6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.24, + "z": 9.47 + }, + "G7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.24, + "z": 9.47 + }, + "G8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.24, + "z": 9.47 + }, + "G9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.24, + "z": 9.47 + }, + "H1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.24, + "z": 9.47 + }, + "H10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.24, + "z": 9.47 + }, + "H11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.24, + "z": 9.47 + }, + "H12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.24, + "z": 9.47 + }, + "H2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.24, + "z": 9.47 + }, + "H3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.24, + "z": 9.47 + }, + "H4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.24, + "z": 9.47 + }, + "H5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.24, + "z": 9.47 + }, + "H6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.24, + "z": 9.47 + }, + "H7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.24, + "z": 9.47 + }, + "H8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.24, + "z": 9.47 + }, + "H9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.24, + "z": 9.47 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "C1" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/tube-rack-set-1" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 124.35 + }, + "gripperOffsets": {}, + "groups": [ + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352095", + "352096", + "352097", + "352099", + "352196" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 6x15 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A2", + "B1", + "B2", + "C1", + "C2" + ] + }, + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352070", + "352098" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 4x50 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A3", + "A4", + "B3", + "B4" + ] + } + ], + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Opentrons 10 Tube Rack with Falcon 4x50 mL, 6x15 mL Conical", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1" + ], + [ + "A2", + "B2", + "C2" + ], + [ + "A3", + "B3" + ], + [ + "A4", + "B4" + ] + ], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 67.75, + "z": 6.85 + }, + "A2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 67.75, + "z": 6.85 + }, + "A3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 60.25, + "z": 7.3 + }, + "A4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 60.25, + "z": 7.3 + }, + "B1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 42.75, + "z": 6.85 + }, + "B2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 42.75, + "z": 6.85 + }, + "B3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 25.25, + "z": 7.3 + }, + "B4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 25.25, + "z": 7.3 + }, + "C1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 17.75, + "z": 6.85 + }, + "C2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 17.75, + "z": 6.85 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c910be62291bb94458a59cb4185c5180", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d75e38152238e9ad0c18e291fe97e483", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D3" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8de5f9595204f05bd1016cce23cd32b9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 178.38, + "y": 288.24, + "z": 97.47 + }, + "tipDiameter": 7.23, + "tipLength": 77.5, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "PartialTipMovementNotAllowedError [line 494]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to NEST 96 Well Plate 100 µL PCR Full Skirt in slot D2 with H12 nozzle partial configuration will result in collision with items in deck slot C1.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Moving to NEST 96 Well Plate 100 µL PCR Full Skirt in slot D2 with H12 nozzle partial configuration will result in collision with items in deck slot C1.", + "errorCode": "2004", + "errorInfo": {}, + "errorType": "PartialTipMovementNotAllowedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_consolidate_source_collision.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "B2" + } + }, + { + "definitionUri": "opentrons/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical/1", + "id": "UUID", + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "C1" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D3" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Too tall labware on pickup tip" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed2f3800b6][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAPrep24xV4_7].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed2f3800b6][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAPrep24xV4_7].json index 4bafddac2cf..d946aae6d9d 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed2f3800b6][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAPrep24xV4_7].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[ed2f3800b6][Flex_S_2_18_P1000M_P50M_GRIP_HS_TM_MB_TC_IlluminaDNAPrep24xV4_7].json @@ -12152,7 +12152,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12186,7 +12187,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12219,7 +12221,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12253,7 +12256,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12286,7 +12290,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12319,7 +12324,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12352,7 +12358,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12385,7 +12392,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12418,7 +12426,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12451,7 +12460,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12485,7 +12495,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12518,7 +12529,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12551,7 +12563,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12583,7 +12596,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12628,7 +12642,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12659,7 +12674,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12690,7 +12706,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12721,7 +12738,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12817,7 +12835,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12851,7 +12870,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12884,7 +12904,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12918,7 +12939,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12951,7 +12973,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12984,7 +13007,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13017,7 +13041,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13050,7 +13075,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13083,7 +13109,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13116,7 +13143,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13150,7 +13178,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13183,7 +13212,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13216,7 +13246,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13248,7 +13279,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13293,7 +13325,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13324,7 +13357,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13355,7 +13389,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13386,7 +13421,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -13482,7 +13518,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13516,7 +13553,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13549,7 +13587,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13583,7 +13622,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13616,7 +13656,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13649,7 +13690,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13682,7 +13724,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13715,7 +13758,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13748,7 +13792,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13781,7 +13826,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13815,7 +13861,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13848,7 +13895,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13881,7 +13929,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13913,7 +13962,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13958,7 +14008,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -13989,7 +14040,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14020,7 +14072,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14051,7 +14104,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -14335,7 +14389,8 @@ "y": 0.0, "z": -26.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14368,7 +14423,8 @@ "y": 0.0, "z": -26.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -14401,7 +14457,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14433,7 +14490,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14465,7 +14523,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14499,7 +14558,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14532,7 +14592,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14566,7 +14627,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14599,7 +14661,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14633,7 +14696,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14666,7 +14730,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14700,7 +14765,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14733,7 +14799,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14767,7 +14834,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14800,7 +14868,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14834,7 +14903,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14867,7 +14937,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14901,7 +14972,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14934,7 +15006,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14968,7 +15041,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15001,7 +15075,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15035,7 +15110,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15068,7 +15144,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15101,7 +15178,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15133,7 +15211,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15229,7 +15308,8 @@ "y": 0.0, "z": -26.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15262,7 +15342,8 @@ "y": 0.0, "z": -26.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15295,7 +15376,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15327,7 +15409,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15359,7 +15442,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15393,7 +15477,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15426,7 +15511,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15460,7 +15546,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15493,7 +15580,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15527,7 +15615,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15560,7 +15649,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15594,7 +15684,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15627,7 +15718,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15661,7 +15753,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15694,7 +15787,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15728,7 +15822,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15761,7 +15856,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15795,7 +15891,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15828,7 +15925,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15862,7 +15960,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15895,7 +15994,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15929,7 +16029,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15962,7 +16063,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -15995,7 +16097,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16027,7 +16130,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16123,7 +16227,8 @@ "y": 0.0, "z": -26.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16156,7 +16261,8 @@ "y": 0.0, "z": -26.35 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -16189,7 +16295,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16221,7 +16328,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16253,7 +16361,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16287,7 +16396,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16320,7 +16430,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16354,7 +16465,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16387,7 +16499,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16421,7 +16534,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16454,7 +16568,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16488,7 +16603,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16521,7 +16637,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16555,7 +16672,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16588,7 +16706,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16622,7 +16741,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16655,7 +16775,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16689,7 +16810,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16722,7 +16844,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16756,7 +16879,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16789,7 +16913,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16823,7 +16948,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16856,7 +16982,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16889,7 +17016,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -16921,7 +17049,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -17292,7 +17421,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17324,7 +17454,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17370,7 +17501,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17402,7 +17534,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17434,7 +17567,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17466,7 +17600,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -17512,7 +17647,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -17544,7 +17680,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -17640,7 +17777,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17672,7 +17810,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17718,7 +17857,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17750,7 +17890,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17782,7 +17923,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -17814,7 +17956,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -17860,7 +18003,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -17892,7 +18036,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -17988,7 +18133,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18020,7 +18166,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18066,7 +18213,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18098,7 +18246,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18130,7 +18279,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -18162,7 +18312,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -18208,7 +18359,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -18240,7 +18392,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -18399,7 +18552,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18432,7 +18586,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18464,7 +18619,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18496,7 +18652,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18529,7 +18686,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18563,7 +18721,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18596,7 +18755,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18629,7 +18789,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18661,7 +18822,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18706,7 +18868,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18738,7 +18901,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -18835,7 +18999,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18868,7 +19033,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -18900,7 +19066,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18932,7 +19099,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18965,7 +19133,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -18999,7 +19168,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19032,7 +19202,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19065,7 +19236,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19097,7 +19269,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19142,7 +19315,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19174,7 +19348,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -19271,7 +19446,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19304,7 +19480,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -19336,7 +19513,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19368,7 +19546,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19401,7 +19580,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19435,7 +19615,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19468,7 +19649,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19501,7 +19683,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19533,7 +19716,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19578,7 +19762,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19610,7 +19795,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -19782,7 +19968,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19814,7 +20001,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19846,7 +20034,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19892,7 +20081,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -19925,7 +20115,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -19957,7 +20148,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20002,7 +20194,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20034,7 +20227,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20130,7 +20324,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20162,7 +20357,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20194,7 +20390,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20240,7 +20437,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -20273,7 +20471,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20305,7 +20504,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20350,7 +20550,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20382,7 +20583,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20478,7 +20680,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20510,7 +20713,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20542,7 +20746,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20588,7 +20793,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -20621,7 +20827,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20653,7 +20860,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20698,7 +20906,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20730,7 +20939,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -20889,7 +21099,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20922,7 +21133,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -20954,7 +21166,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -20986,7 +21199,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21019,7 +21233,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21053,7 +21268,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21086,7 +21302,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21119,7 +21336,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21151,7 +21369,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21196,7 +21415,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21228,7 +21448,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21325,7 +21546,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21358,7 +21580,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21390,7 +21613,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21422,7 +21646,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21455,7 +21680,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21489,7 +21715,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21522,7 +21749,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21555,7 +21783,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21587,7 +21816,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21632,7 +21862,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21664,7 +21895,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21761,7 +21993,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21794,7 +22027,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21826,7 +22060,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21858,7 +22093,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21891,7 +22127,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21925,7 +22162,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21958,7 +22196,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21991,7 +22230,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22023,7 +22263,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22068,7 +22309,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22100,7 +22342,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -22272,7 +22515,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22304,7 +22548,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22336,7 +22581,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22382,7 +22628,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22415,7 +22662,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22447,7 +22695,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22492,7 +22741,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22524,7 +22774,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22620,7 +22871,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22652,7 +22904,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22684,7 +22937,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22730,7 +22984,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -22763,7 +23018,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22795,7 +23051,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22840,7 +23097,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22872,7 +23130,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -22968,7 +23227,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23000,7 +23260,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23032,7 +23293,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23078,7 +23340,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23111,7 +23374,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -23143,7 +23407,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -23188,7 +23453,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -23220,7 +23486,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -23379,7 +23646,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23412,7 +23680,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23444,7 +23713,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23476,7 +23746,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23509,7 +23780,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23543,7 +23815,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23576,7 +23849,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23609,7 +23883,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23641,7 +23916,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23686,7 +23962,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23718,7 +23995,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23815,7 +24093,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23848,7 +24127,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -23880,7 +24160,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23912,7 +24193,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23945,7 +24227,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23979,7 +24262,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24012,7 +24296,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24045,7 +24330,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24077,7 +24363,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24122,7 +24409,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24154,7 +24442,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -24251,7 +24540,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24284,7 +24574,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24316,7 +24607,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24348,7 +24640,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24381,7 +24674,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24415,7 +24709,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24448,7 +24743,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24481,7 +24777,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24513,7 +24810,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24558,7 +24856,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24590,7 +24889,8 @@ "y": 0.0, "z": 1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24762,7 +25062,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24794,7 +25095,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24826,7 +25128,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24872,7 +25175,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -24905,7 +25209,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -24937,7 +25242,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -24982,7 +25288,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25014,7 +25321,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25110,7 +25418,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25142,7 +25451,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25174,7 +25484,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25220,7 +25531,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25253,7 +25565,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25285,7 +25598,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25330,7 +25644,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25362,7 +25677,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25458,7 +25774,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25490,7 +25807,8 @@ "y": 0.0, "z": -10.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25522,7 +25840,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25568,7 +25887,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25601,7 +25921,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25633,7 +25954,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25678,7 +26000,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25710,7 +26033,8 @@ "y": 0.0, "z": 4.9999999999999964 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25834,7 +26158,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25866,7 +26191,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25962,7 +26288,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -25994,7 +26321,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26090,7 +26418,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26122,7 +26451,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26295,7 +26625,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26328,7 +26659,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26360,7 +26692,8 @@ "y": 0.0, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26392,7 +26725,8 @@ "y": 0.0, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26424,7 +26758,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26456,7 +26791,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26488,7 +26824,8 @@ "y": 1.0400000000000063, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26520,7 +26857,8 @@ "y": 1.0400000000000063, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26552,7 +26890,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26584,7 +26923,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26616,7 +26956,8 @@ "y": 0.0, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26648,7 +26989,8 @@ "y": 0.0, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26680,7 +27022,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26712,7 +27055,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26744,7 +27088,8 @@ "y": -1.0400000000000063, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26776,7 +27121,8 @@ "y": -1.0400000000000063, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26808,7 +27154,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26840,7 +27187,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26873,7 +27221,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26905,7 +27254,8 @@ "y": 0.0, "z": -7.474999999999991 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26936,7 +27286,8 @@ "y": 0.0, "z": -14.449999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26967,7 +27318,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -26998,7 +27350,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27029,7 +27382,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -27125,7 +27479,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27158,7 +27513,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27190,7 +27546,8 @@ "y": 0.0, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27222,7 +27579,8 @@ "y": 0.0, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27254,7 +27612,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27286,7 +27645,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27318,7 +27678,8 @@ "y": 1.0400000000000063, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27350,7 +27711,8 @@ "y": 1.0400000000000063, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27382,7 +27744,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27414,7 +27777,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27446,7 +27810,8 @@ "y": 0.0, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27478,7 +27843,8 @@ "y": 0.0, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27510,7 +27876,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27542,7 +27909,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27574,7 +27942,8 @@ "y": -1.0400000000000063, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27606,7 +27975,8 @@ "y": -1.0400000000000063, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27638,7 +28008,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27670,7 +28041,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27703,7 +28075,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27735,7 +28108,8 @@ "y": 0.0, "z": -7.474999999999991 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27766,7 +28140,8 @@ "y": 0.0, "z": -14.449999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27797,7 +28172,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27828,7 +28204,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27859,7 +28236,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27955,7 +28333,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -27988,7 +28367,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28020,7 +28400,8 @@ "y": 0.0, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28052,7 +28433,8 @@ "y": 0.0, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28084,7 +28466,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28116,7 +28499,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28148,7 +28532,8 @@ "y": 1.0400000000000063, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28180,7 +28565,8 @@ "y": 1.0400000000000063, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28212,7 +28598,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28244,7 +28631,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28276,7 +28664,8 @@ "y": 0.0, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28308,7 +28697,8 @@ "y": 0.0, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28340,7 +28730,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28372,7 +28763,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28404,7 +28796,8 @@ "y": -1.0400000000000063, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28436,7 +28829,8 @@ "y": -1.0400000000000063, "z": -11.47499999999999 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28468,7 +28862,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28500,7 +28895,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28533,7 +28929,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28565,7 +28962,8 @@ "y": 0.0, "z": -7.474999999999991 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28596,7 +28994,8 @@ "y": 0.0, "z": -14.449999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28627,7 +29026,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28658,7 +29058,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28689,7 +29090,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28922,7 +29324,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28955,7 +29358,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28988,7 +29392,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29021,7 +29426,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29055,7 +29461,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29088,7 +29495,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29122,7 +29530,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29155,7 +29564,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29188,7 +29598,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -29285,7 +29696,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -29318,7 +29730,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -29351,7 +29764,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29384,7 +29798,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29418,7 +29833,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29451,7 +29867,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29485,7 +29902,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29518,7 +29936,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29551,7 +29970,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -29648,7 +30068,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -29681,7 +30102,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -29714,7 +30136,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29747,7 +30170,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29781,7 +30205,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29814,7 +30239,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29848,7 +30274,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29881,7 +30308,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -29914,7 +30342,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30413,7 +30842,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30446,7 +30876,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -30479,7 +30910,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30525,7 +30957,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30557,7 +30990,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30591,7 +31025,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30624,7 +31059,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30658,7 +31094,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30691,7 +31128,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30724,7 +31162,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30757,7 +31196,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30790,7 +31230,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30823,7 +31264,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30869,7 +31311,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30915,7 +31358,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30948,7 +31392,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -30981,7 +31426,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31014,7 +31460,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31046,7 +31493,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31091,7 +31539,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -31201,7 +31650,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31234,7 +31684,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -31267,7 +31718,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31313,7 +31765,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31345,7 +31798,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31379,7 +31833,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31412,7 +31867,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31446,7 +31902,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31479,7 +31936,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31512,7 +31970,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31545,7 +32004,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31578,7 +32038,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31611,7 +32072,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31657,7 +32119,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31703,7 +32166,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -31736,7 +32200,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31769,7 +32234,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31802,7 +32268,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31834,7 +32301,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31879,7 +32347,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -31989,7 +32458,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32022,7 +32492,8 @@ "y": 0.0, "z": -14.45 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32055,7 +32526,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32101,7 +32573,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32133,7 +32606,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32167,7 +32641,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32200,7 +32675,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32234,7 +32710,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32267,7 +32744,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32300,7 +32778,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32333,7 +32812,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32366,7 +32846,8 @@ "y": 0.0, "z": -26.1 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32399,7 +32880,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32445,7 +32927,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32491,7 +32974,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32524,7 +33008,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32557,7 +33042,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32590,7 +33076,8 @@ "y": 0.0, "z": -14.199999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32622,7 +33109,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32667,7 +33155,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -32960,7 +33449,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -32992,7 +33482,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33038,7 +33529,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33070,7 +33562,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33102,7 +33595,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33134,7 +33628,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -33180,7 +33675,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -33211,7 +33707,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -33242,7 +33739,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -33337,7 +33835,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33369,7 +33868,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33415,7 +33915,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33447,7 +33948,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33479,7 +33981,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -33511,7 +34014,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33557,7 +34061,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33588,7 +34093,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33619,7 +34125,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33714,7 +34221,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33746,7 +34254,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33792,7 +34301,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33824,7 +34334,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33856,7 +34367,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -33888,7 +34400,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33934,7 +34447,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33965,7 +34479,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -33996,7 +34511,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -34106,7 +34622,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34138,7 +34655,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34169,7 +34687,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34200,7 +34719,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34231,7 +34751,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34263,7 +34784,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34309,7 +34831,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34340,7 +34863,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34371,7 +34895,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34402,7 +34927,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34434,7 +34960,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34466,7 +34993,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34497,7 +35025,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34528,7 +35057,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34559,7 +35089,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34591,7 +35122,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34637,7 +35169,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34668,7 +35201,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34699,7 +35233,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34730,7 +35265,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34762,7 +35298,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34794,7 +35331,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34825,7 +35363,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34856,7 +35395,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -34887,7 +35427,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34919,7 +35460,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34965,7 +35507,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -34996,7 +35539,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35027,7 +35571,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35058,7 +35603,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35181,7 +35727,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35213,7 +35760,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35259,7 +35807,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35291,7 +35840,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35323,7 +35873,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35355,7 +35906,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35401,7 +35953,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35432,7 +35985,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35463,7 +36017,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35558,7 +36113,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -35590,7 +36146,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -35636,7 +36193,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -35668,7 +36226,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -35700,7 +36259,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -35732,7 +36292,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35778,7 +36339,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35809,7 +36371,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35840,7 +36403,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -35935,7 +36499,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35967,7 +36532,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36013,7 +36579,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36045,7 +36612,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36077,7 +36645,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -36109,7 +36678,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36155,7 +36725,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36186,7 +36757,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36217,7 +36789,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -36327,7 +36900,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36359,7 +36933,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36390,7 +36965,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36421,7 +36997,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36452,7 +37029,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36484,7 +37062,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36530,7 +37109,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36561,7 +37141,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36592,7 +37173,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36623,7 +37205,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -36655,7 +37238,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36687,7 +37271,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36718,7 +37303,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36749,7 +37335,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36780,7 +37367,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36812,7 +37400,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36858,7 +37447,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36889,7 +37479,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36920,7 +37511,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36951,7 +37543,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -36983,7 +37576,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37015,7 +37609,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37046,7 +37641,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37077,7 +37673,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37108,7 +37705,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37140,7 +37738,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37186,7 +37785,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37217,7 +37817,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37248,7 +37849,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37279,7 +37881,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -37402,7 +38005,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37434,7 +38038,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37480,7 +38085,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37512,7 +38118,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37544,7 +38151,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -37576,7 +38184,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37622,7 +38231,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37653,7 +38263,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37684,7 +38295,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37779,7 +38391,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37811,7 +38424,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37857,7 +38471,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37889,7 +38504,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37921,7 +38537,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -37953,7 +38570,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -37999,7 +38617,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38030,7 +38649,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38061,7 +38681,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38156,7 +38777,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38188,7 +38810,8 @@ "y": 0.0, "z": -11.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38234,7 +38857,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38266,7 +38890,8 @@ "y": 0.0, "z": -14.199999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38298,7 +38923,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38330,7 +38956,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38376,7 +39003,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38407,7 +39035,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38438,7 +39067,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -38561,7 +39191,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38593,7 +39224,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -38689,7 +39321,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38721,7 +39354,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -38817,7 +39451,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -38849,7 +39484,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39022,7 +39658,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39054,7 +39691,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39086,7 +39724,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39118,7 +39757,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39150,7 +39790,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39182,7 +39823,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -39214,7 +39856,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -39246,7 +39889,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -39278,7 +39922,8 @@ "y": 0.0, "z": -13.95 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -39310,7 +39955,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39342,7 +39988,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39374,7 +40021,8 @@ "y": 0.0, "z": -3.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39590,7 +40238,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39622,7 +40271,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -39655,7 +40305,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -39751,7 +40402,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -39783,7 +40435,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -39816,7 +40469,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -39912,7 +40566,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39944,7 +40599,8 @@ "y": 0.0, "z": -14.449999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -39977,7 +40633,8 @@ "y": 0.0, "z": -13.949999999999996 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -40121,6 +40778,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f0efddcd7d][Flex_X_v2_16_P1000_96_DropTipsWithNoTrash].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f0efddcd7d][Flex_X_v2_16_P1000_96_DropTipsWithNoTrash].json index 4bcefec1199..d1a7a88d075 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f0efddcd7d][Flex_X_v2_16_P1000_96_DropTipsWithNoTrash].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f0efddcd7d][Flex_X_v2_16_P1000_96_DropTipsWithNoTrash].json @@ -1448,6 +1448,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f24bb0b4d9][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f24bb0b4d9][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3].json index fbcba187d2f..d454695d871 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f24bb0b4d9][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f24bb0b4d9][Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3].json @@ -12391,7 +12391,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12424,7 +12425,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12535,7 +12537,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12568,7 +12571,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12601,7 +12605,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12634,7 +12639,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12666,7 +12672,8 @@ "y": 0.0, "z": -9.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12698,7 +12705,8 @@ "y": 0.0, "z": -9.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12730,7 +12738,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12762,7 +12771,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12795,7 +12805,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12827,7 +12838,8 @@ "y": 0.0, "z": -9.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12859,7 +12871,8 @@ "y": 0.0, "z": -9.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12892,7 +12905,8 @@ "y": 0.0, "z": -9.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12924,7 +12938,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12956,7 +12971,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -12989,7 +13005,8 @@ "y": 0.0, "z": -13.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13021,7 +13038,8 @@ "y": 0.0, "z": -9.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13053,7 +13071,8 @@ "y": 0.0, "z": -9.779999999999998 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13085,7 +13104,8 @@ "y": 0.0, "z": 2.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13116,7 +13136,8 @@ "y": 0.0, "z": 5.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13147,7 +13168,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13178,7 +13200,8 @@ "y": 0.0, "z": 5.000000000000007 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13390,7 +13413,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13422,7 +13446,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13468,7 +13493,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13500,7 +13526,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13532,7 +13559,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13564,7 +13592,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13610,7 +13639,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13641,7 +13671,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13672,7 +13703,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16162,7 +16194,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16194,7 +16227,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16225,7 +16259,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16256,7 +16291,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16287,7 +16323,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16319,7 +16356,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16365,7 +16403,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16396,7 +16435,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16427,7 +16467,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16458,7 +16499,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16567,7 +16609,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16599,7 +16642,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16645,7 +16689,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16677,7 +16722,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16709,7 +16755,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16741,7 +16788,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16787,7 +16835,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16818,7 +16867,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16849,7 +16899,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16959,7 +17010,8 @@ "y": 0.0, "z": -37.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -16991,7 +17043,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17022,7 +17075,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17053,7 +17107,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17084,7 +17139,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17116,7 +17172,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17162,7 +17219,8 @@ "y": 0.0, "z": -2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17193,7 +17251,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17224,7 +17283,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17255,7 +17315,8 @@ "y": 0.0, "z": 5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17364,7 +17425,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17396,7 +17458,8 @@ "y": 0.0, "z": -11.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17442,7 +17505,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17474,7 +17538,8 @@ "y": 0.0, "z": -14.280000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17506,7 +17571,8 @@ "y": 0.0, "z": 2.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17538,7 +17604,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17584,7 +17651,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17615,7 +17683,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17646,7 +17715,8 @@ "y": 0.0, "z": 0.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -17742,151 +17812,26 @@ "commandType": "moveToWell", "completedAt": "TIMESTAMP", "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "7e96139ed2163fa7f870805d0b3d14b6", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.780000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 38.92 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "2fc4e3b5c5496c9069bfa428594d5b83", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.780000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 38.92 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "293b75ea0ecbacefa153ed06957d3826", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 50.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 - }, - "volume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "waitForDuration", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "4af4d6dfa210405e5cda86ca70a2b4cb", - "notes": [], - "params": { - "seconds": 6.0 + "error": { + "createdAt": "TIMESTAMP", + "detail": "Specifying top with an offset of x=0.0 y=0.0 z=-14.780000000000001 and a volume offset of 0.0 results in an operation location below the bottom of the well", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "OperationLocationNotInWellError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "blowout", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", "id": "UUID", - "key": "2cc6facd963f1224ab3a1c427ebad360", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 + "key": "7e96139ed2163fa7f870805d0b3d14b6", + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "ae58a93b888702ea3d5552a7132259df", - "notes": [], + ], "params": { "forceDirect": false, "labwareId": "UUID", @@ -17895,1210 +17840,59 @@ "offset": { "x": 0.0, "y": 0.0, - "z": -5.0 + "z": -14.780000000000001 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 36.0 - } - }, "startedAt": "TIMESTAMP", - "status": "succeeded" - }, + "status": "failed" + } + ], + "config": { + "apiVersion": [ + 2, + 15 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", "createdAt": "TIMESTAMP", + "detail": "ProtocolCommandFailedError [line 280]: Error 4000 GENERAL_ERROR (ProtocolCommandFailedError): OperationLocationNotInWellError: Specifying top with an offset of x=0.0 y=0.0 z=-14.780000000000001 and a volume offset of 0.0 results in an operation location below the bottom of the well", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", "id": "UUID", - "key": "e8c17bd83a28f7fb554fceccc7739e56", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.3, - "y": 395.15, - "z": 41.0 + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "OperationLocationNotInWellError: Specifying top with an offset of x=0.0 y=0.0 z=-14.780000000000001 and a volume offset of 0.0 results in an operation location below the bottom of the well", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ProtocolCommandFailedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Specifying top with an offset of x=0.0 y=0.0 z=-14.780000000000001 and a volume offset of 0.0 results in an operation location below the bottom of the well", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "OperationLocationNotInWellError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "ef9b94f267046d3d18f9ceaaf8b588de", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.38, - "y": 74.38, - "z": 98.33 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "heaterShaker/openLabwareLatch", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "ac51f98a51b8090329bb6c97661ef3f6", - "notes": [], - "params": { - "moduleId": "UUID" - }, - "result": { - "pipetteRetracted": true - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveLabware", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "1eeb10d9c927d233fbdcd7031c0047c8", - "notes": [], - "params": { - "dropOffset": { - "x": 0.0, - "y": 0.0, - "z": 2.5 - }, - "labwareId": "UUID", - "newLocation": { - "moduleId": "UUID" - }, - "pickUpOffset": { - "x": 0.0, - "y": 0.0, - "z": 29.5 - }, - "strategy": "usingGripper" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "heaterShaker/closeLabwareLatch", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "cdcab4e78991715e0bd9f7f9f9787e78", - "notes": [], - "params": { - "moduleId": "UUID" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "9e3315bbaf72df202626719681f8ce51", - "notes": [], - "params": { - "message": "--> Adding RSB" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "pickUpTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "8c78a5a5e700de468ff2bfdbf04a1805", - "notes": [], - "params": { - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.38, - "y": 288.38, - "z": 110.0 - }, - "tipDiameter": 5.58, - "tipLength": 47.4, - "tipVolume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "2c72aeeb2fbdbd435fe7cf4375bdf270", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 32.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -37.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.3, - "y": 288.15, - "z": 4.0 - }, - "volume": 32.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "529f6bdecc10296be849fa04c80cab54", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 1.040000000000001, - "y": 0.0, - "z": -11.389999999999997 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 15.420000000000002, - "y": 74.24, - "z": 23.260000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "808f476d33c89da7e686062ef89e0144", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 32.0, - "wellLocation": { - "offset": { - "x": 1.040000000000001, - "y": 0.0, - "z": -11.389999999999997 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 15.420000000000002, - "y": 74.24, - "z": 23.260000000000005 - }, - "volume": 32.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "b190d73bc896ade45ea37cec649f3b01", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.779999999999998 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 20.870000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "10aebaf6c91650e96e0109fad6356664", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 32.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.779999999999998 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 20.870000000000005 - }, - "volume": 32.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "d271d42ba20c1265c1f9ae67c271b2bc", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 1.0400000000000063, - "z": -11.389999999999997 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 75.28, - "z": 23.260000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "967ae7e19729a3be3c1088c3bd828eaa", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 32.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 1.0400000000000063, - "z": -11.389999999999997 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 75.28, - "z": 23.260000000000005 - }, - "volume": 32.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "e8d89c46d2923c53a08d30fbfd2a1aeb", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.779999999999998 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 20.870000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "59c1c1d6ab2c03db5e259df571ce465c", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 32.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.779999999999998 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 20.870000000000005 - }, - "volume": 32.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "bc0efc7c278528f930177c36ad06dc85", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": -1.040000000000001, - "y": 0.0, - "z": -11.389999999999997 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 13.34, - "y": 74.24, - "z": 23.260000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "18f440bd39035ecb965dcfea353c0fc7", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 32.0, - "wellLocation": { - "offset": { - "x": -1.040000000000001, - "y": 0.0, - "z": -11.389999999999997 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 13.34, - "y": 74.24, - "z": 23.260000000000005 - }, - "volume": 32.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "441a8c45231b4d0bd3b74b19cf0b6f2e", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.779999999999998 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 20.870000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "5b91b3bbb5b42019d65d2928092c97fd", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 32.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.779999999999998 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 20.870000000000005 - }, - "volume": 32.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "affaefea06a32b323d65abe188b7189f", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": -1.0400000000000063, - "z": -11.389999999999997 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 73.19999999999999, - "z": 23.260000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "4c8bd3ab016b893f1c874f352d5b1096", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 32.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": -1.0400000000000063, - "z": -11.389999999999997 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 73.19999999999999, - "z": 23.260000000000005 - }, - "volume": 32.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "f7f03696df895d9193cf13c923514c84", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.779999999999998 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 20.870000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "b2fa28a02c50105c9eb3561d3e0884e4", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 32.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.779999999999998 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 20.870000000000005 - }, - "volume": 32.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "055e47e1903d32ccfe1d698520e46cbe", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 32.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.779999999999998 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 20.870000000000005 - }, - "volume": 32.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "blowout", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "a717aa36dd5fd15117af9e58ec89dc0e", - "notes": [], - "params": { - "flowRate": 80.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -7.389999999999997 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 27.260000000000005 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "3dafa44bfa9eee7df3c3d6badec818d0", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 5.000000000000007 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 39.650000000000006 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "336db1f7801ff0793594c821c00fc6ed", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 34.65 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "05603da7b787a935d2795191fa5939cd", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 5.000000000000007 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 74.24, - "z": 39.650000000000006 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "6285fe5cc3b2dbf857d179a0fe4980c3", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.38, - "y": 288.38, - "z": 98.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "heaterShaker/setAndWaitForShakeSpeed", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "f20ecefeef9d0ff676b3a42bad54f7a7", - "notes": [], - "params": { - "moduleId": "UUID", - "rpm": 1600.0 - }, - "result": { - "pipetteRetracted": false - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "waitForDuration", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "7fc5156557fc1da3a55ffcd9a30d932c", - "notes": [], - "params": { - "seconds": 6.0 - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "heaterShaker/deactivateShaker", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "072bba5a89de6aafc71b6d4ee9211a6b", - "notes": [], - "params": { - "moduleId": "UUID" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "heaterShaker/openLabwareLatch", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "270c2246839a255fcd68d65312799287", - "notes": [], - "params": { - "moduleId": "UUID" - }, - "result": { - "pipetteRetracted": false - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveLabware", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "f2a883d8cba1af0fc1023bd432929649", - "notes": [], - "params": { - "dropOffset": { - "x": 0.0, - "y": 0.0, - "z": 30.0 - }, - "labwareId": "UUID", - "newLocation": { - "moduleId": "UUID" - }, - "pickUpOffset": { - "x": 0.0, - "y": 0.0, - "z": 3.5 - }, - "strategy": "usingGripper" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "heaterShaker/closeLabwareLatch", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "24a4cb6ddce3a043fe2d24e05503b0c0", - "notes": [], - "params": { - "moduleId": "UUID" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "comment", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "2a436f2cf99cf4571f513096b79cd097", - "notes": [], - "params": { - "message": "--> Transferring Supernatant" - }, - "result": {}, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "pickUpTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "d95fa6c98b0f1bb8a56ec15ccb20aad1", - "notes": [], - "params": { - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": 0.0 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.38, - "y": 288.38, - "z": 110.0 - }, - "tipDiameter": 5.58, - "tipLength": 47.4, - "tipVolume": 50.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "moveToWell", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "69aec81355d6906a53d6332d03d13b57", - "notes": [], - "params": { - "forceDirect": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.530000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 39.17 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "aspirate", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "9c4c29f2e23c3e6b80bb890941d5e8e6", - "notes": [], - "params": { - "flowRate": 40.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 31.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -14.530000000000001 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 14.38, - "y": 181.24, - "z": 39.17 - }, - "volume": 31.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dispense", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "d381169b8dfd5c89cb788046bbb2b0df", - "notes": [], - "params": { - "flowRate": 160.0, - "labwareId": "UUID", - "pipetteId": "UUID", - "volume": 31.0, - "wellLocation": { - "offset": { - "x": 0.0, - "y": 0.0, - "z": -13.780000000000003 - }, - "origin": "top" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 342.38, - "y": 74.24, - "z": 10.919999999999996 - }, - "volume": 31.0 - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" - }, - { - "commandType": "dropTip", - "completedAt": "TIMESTAMP", - "createdAt": "TIMESTAMP", - "id": "UUID", - "key": "65dbf2a58a2011f0749d994fcc632997", - "notes": [], - "params": { - "alternateDropLocation": false, - "labwareId": "UUID", - "pipetteId": "UUID", - "wellLocation": { - "offset": { - "x": 0, - "y": 0, - "z": 0 - }, - "origin": "default" - }, - "wellName": "A1" - }, - "result": { - "position": { - "x": 178.38, - "y": 288.38, - "z": 98.42 - } - }, - "startedAt": "TIMESTAMP", - "status": "succeeded" + ] } ], - "config": { - "apiVersion": [ - 2, - 15 - ], - "protocolType": "python" - }, - "createdAt": "TIMESTAMP", - "errors": [], "files": [ { "name": "Flex_S_v2_15_P1000_96_GRIP_HS_MB_TC_TM_IlluminaDNAPrep96PART3.py", @@ -19215,6 +18009,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons ", @@ -19261,7 +18056,7 @@ "pipetteName": "p1000_96" } ], - "result": "ok", + "result": "not-ok", "robotType": "OT-3 Standard", "runTimeParameters": [] } diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f301704f56][OT2_S_v6_P300M_P300S_HS_HS_NormalUseWithTransfer].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f301704f56][OT2_S_v6_P300M_P300S_HS_HS_NormalUseWithTransfer].json index 6f1fae9067d..3d7f6d10b51 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f301704f56][OT2_S_v6_P300M_P300S_HS_HS_NormalUseWithTransfer].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f301704f56][OT2_S_v6_P300M_P300S_HS_HS_NormalUseWithTransfer].json @@ -4776,7 +4776,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4809,7 +4810,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4906,7 +4908,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -4939,7 +4942,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -5036,7 +5040,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -5069,7 +5074,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -5166,7 +5172,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -5199,7 +5206,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -5296,7 +5304,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -5329,7 +5338,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -5542,7 +5552,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5575,7 +5586,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5672,7 +5684,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -5705,7 +5718,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5802,7 +5816,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B6" }, @@ -5835,7 +5850,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -5932,7 +5948,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C6" }, @@ -5965,7 +5982,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6062,7 +6080,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D6" }, @@ -6095,7 +6114,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6192,7 +6212,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6225,7 +6246,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6258,7 +6280,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6291,7 +6314,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6324,7 +6348,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6357,7 +6382,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6390,7 +6416,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6423,7 +6450,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6456,7 +6484,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6489,7 +6518,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6586,7 +6616,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6619,7 +6650,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -6652,7 +6684,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "B2" }, @@ -6685,7 +6718,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "C2" }, @@ -6718,7 +6752,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "D2" }, @@ -6751,7 +6786,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "E2" }, @@ -6783,7 +6819,8 @@ "y": 0, "z": 0.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6815,7 +6852,8 @@ "y": 0, "z": 1.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -6848,7 +6886,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "F2" }, @@ -6881,7 +6920,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "G2" }, @@ -6914,7 +6954,8 @@ "y": 0, "z": 0.5 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "H2" }, @@ -6946,7 +6987,8 @@ "y": 0, "z": 0.0 }, - "origin": "bottom" + "origin": "bottom", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7059,6 +7101,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f345e8e33a][OT2_S_v4_P300M_P20S_MM_TM_TC1_PD40].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f345e8e33a][OT2_S_v4_P300M_P20S_MM_TM_TC1_PD40].json index 8f8997fac9d..d86eae54045 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f345e8e33a][OT2_S_v4_P300M_P20S_MM_TM_TC1_PD40].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f345e8e33a][OT2_S_v4_P300M_P20S_MM_TM_TC1_PD40].json @@ -7030,7 +7030,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -7063,7 +7064,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -7205,7 +7207,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -7238,7 +7241,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -7271,7 +7275,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -7304,7 +7309,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -7337,7 +7343,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -7370,7 +7377,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -7403,7 +7411,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -7436,7 +7445,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -7469,7 +7479,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -7502,7 +7513,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -7599,7 +7611,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -7632,7 +7645,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -7665,7 +7679,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -7698,7 +7713,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -7731,7 +7747,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -7764,7 +7781,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -7797,7 +7815,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -7830,7 +7849,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -7863,7 +7883,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -7896,7 +7917,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B12" }, @@ -7993,7 +8015,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -8026,7 +8049,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -8059,7 +8083,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -8092,7 +8117,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -8125,7 +8151,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -8158,7 +8185,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -8191,7 +8219,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -8224,7 +8253,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -8257,7 +8287,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -8290,7 +8321,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C12" }, @@ -8387,7 +8419,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -8420,7 +8453,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -8453,7 +8487,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -8486,7 +8521,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -8519,7 +8555,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -8552,7 +8589,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -8585,7 +8623,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -8618,7 +8657,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -8651,7 +8691,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -8684,7 +8725,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D12" }, @@ -8781,7 +8823,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -8814,7 +8857,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -8847,7 +8891,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -8880,7 +8925,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -8913,7 +8959,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -8946,7 +8993,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -8979,7 +9027,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -9012,7 +9061,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -9045,7 +9095,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -9078,7 +9129,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E12" }, @@ -9175,7 +9227,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -9208,7 +9261,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -9241,7 +9295,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -9274,7 +9329,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -9307,7 +9363,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -9340,7 +9397,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -9373,7 +9431,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -9406,7 +9465,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -9439,7 +9499,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -9472,7 +9533,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F12" }, @@ -9569,7 +9631,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -9602,7 +9665,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -9635,7 +9699,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -9668,7 +9733,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -9701,7 +9767,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -9734,7 +9801,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -9767,7 +9835,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -9800,7 +9869,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -9833,7 +9903,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -9866,7 +9937,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G12" }, @@ -9963,7 +10035,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -9996,7 +10069,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -10029,7 +10103,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -10062,7 +10137,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -10095,7 +10171,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -10128,7 +10205,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -10161,7 +10239,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -10194,7 +10273,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -10227,7 +10307,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -10260,7 +10341,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H12" }, @@ -10357,7 +10439,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -10390,7 +10473,8 @@ "y": 0, "z": 0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -10531,6 +10615,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "NN MM", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f37bb0ec36][OT2_S_v2_16_NO_PIPETTES_verifyDoesNotDeadlock].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f37bb0ec36][OT2_S_v2_16_NO_PIPETTES_verifyDoesNotDeadlock].json index b12618b009e..e0c21a82e55 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f37bb0ec36][OT2_S_v2_16_NO_PIPETTES_verifyDoesNotDeadlock].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f37bb0ec36][OT2_S_v2_16_NO_PIPETTES_verifyDoesNotDeadlock].json @@ -29,6 +29,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [], diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f51172f73b][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f51172f73b][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json index 936a2cb2c8c..50ab65351e1 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f51172f73b][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f51172f73b][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_Smoke].json @@ -8381,7 +8381,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8414,7 +8415,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8523,7 +8525,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8556,7 +8559,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -8665,7 +8669,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8698,7 +8703,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -8807,7 +8813,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8840,7 +8847,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -8949,7 +8957,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -8982,7 +8991,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -9091,7 +9101,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9124,7 +9135,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -9233,7 +9245,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9266,7 +9279,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -9375,7 +9389,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9408,7 +9423,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -9517,7 +9533,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9550,7 +9567,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -9659,7 +9677,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9692,7 +9711,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -9801,7 +9821,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9834,7 +9855,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -9943,7 +9965,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -9976,7 +9999,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -10120,7 +10144,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10152,7 +10177,8 @@ "y": 0.0, "z": 29.999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10184,7 +10210,8 @@ "y": 0.0, "z": 29.999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10260,7 +10287,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10292,7 +10320,8 @@ "y": 0.0, "z": 29.999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10324,7 +10353,8 @@ "y": 0.0, "z": 29.999999999999993 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10401,7 +10431,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10434,7 +10465,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10467,7 +10499,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10501,7 +10534,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10534,7 +10568,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10568,7 +10603,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10601,7 +10637,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10635,7 +10672,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10668,7 +10706,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10702,7 +10741,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10735,7 +10775,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10768,7 +10809,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10899,7 +10941,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10933,7 +10976,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -10966,7 +11010,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11000,7 +11045,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11033,7 +11079,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11066,7 +11113,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11099,7 +11147,8 @@ "y": 0.0, "z": -38.55 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11132,7 +11181,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11165,7 +11215,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11198,7 +11249,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -11231,7 +11283,8 @@ "y": 0.0, "z": -1.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -15333,6 +15386,7 @@ "location": "offDeck" } ], + "liquidClasses": [], "liquids": [ { "description": "High Quality H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f5f3b9c5bb][Flex_X_v2_16_P1000_96_TM_ModuleAndWasteChuteConflict].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f5f3b9c5bb][Flex_X_v2_16_P1000_96_TM_ModuleAndWasteChuteConflict].json index 9fbcd62f394..950c5ee4395 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f5f3b9c5bb][Flex_X_v2_16_P1000_96_TM_ModuleAndWasteChuteConflict].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f5f3b9c5bb][Flex_X_v2_16_P1000_96_TM_ModuleAndWasteChuteConflict].json @@ -1255,7 +1255,14 @@ }, "id": "UUID", "key": "c55807b45b6b1d4ea04e12b0ee553f78", - "notes": [], + "notes": [ + { + "longMessage": "Handling this command failure with FAIL_RUN.", + "noteKind": "debugErrorRecovery", + "shortMessage": "Handling this command failure with FAIL_RUN.", + "source": "execution" + } + ], "params": { "location": { "slotName": "D3" @@ -1332,6 +1339,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Derek Maggio ", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f639acc89d][Flex_S_v2_15_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f639acc89d][Flex_S_v2_15_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json index 2c598934321..e4fed39c549 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f639acc89d][Flex_S_v2_15_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f639acc89d][Flex_S_v2_15_NO_PIPETTES_TC_verifyThermocyclerLoadedSlots].json @@ -180,6 +180,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f6c1ddbb32][pl_ExpressPlex_Pooling_Final].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f6c1ddbb32][pl_ExpressPlex_Pooling_Final].json index 3c951cd5f49..920a648041a 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f6c1ddbb32][pl_ExpressPlex_Pooling_Final].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f6c1ddbb32][pl_ExpressPlex_Pooling_Final].json @@ -21367,7 +21367,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21414,7 +21415,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21460,7 +21462,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21556,7 +21559,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -21603,7 +21607,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21649,7 +21654,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21745,7 +21751,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -21792,7 +21799,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21838,7 +21846,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -21934,7 +21943,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -21981,7 +21991,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22027,7 +22038,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22123,7 +22135,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -22170,7 +22183,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22216,7 +22230,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22312,7 +22327,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -22359,7 +22375,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22405,7 +22422,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22501,7 +22519,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -22548,7 +22567,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22594,7 +22614,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22690,7 +22711,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -22737,7 +22759,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22783,7 +22806,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22879,7 +22903,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -22926,7 +22951,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -22972,7 +22998,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23068,7 +23095,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -23115,7 +23143,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23161,7 +23190,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23257,7 +23287,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -23304,7 +23335,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23350,7 +23382,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23446,7 +23479,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -23493,7 +23527,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23539,7 +23574,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23635,7 +23671,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -23682,7 +23719,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23728,7 +23766,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23824,7 +23863,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -23871,7 +23911,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -23917,7 +23958,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24013,7 +24055,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24060,7 +24103,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24106,7 +24150,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24202,7 +24247,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -24249,7 +24295,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24295,7 +24342,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24391,7 +24439,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -24438,7 +24487,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24484,7 +24534,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24580,7 +24631,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -24627,7 +24679,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24673,7 +24726,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24769,7 +24823,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -24816,7 +24871,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24862,7 +24918,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -24958,7 +25015,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -25005,7 +25063,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25051,7 +25110,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25147,7 +25207,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -25194,7 +25255,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25240,7 +25302,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25336,7 +25399,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -25383,7 +25447,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25429,7 +25494,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25525,7 +25591,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -25572,7 +25639,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25618,7 +25686,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25714,7 +25783,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -25761,7 +25831,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25807,7 +25878,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -25903,7 +25975,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -25950,7 +26023,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -25996,7 +26070,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26092,7 +26167,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -26139,7 +26215,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26185,7 +26262,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26281,7 +26359,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -26328,7 +26407,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26374,7 +26454,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26470,7 +26551,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -26517,7 +26599,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26563,7 +26646,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26659,7 +26743,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26706,7 +26791,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26752,7 +26838,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26848,7 +26935,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -26895,7 +26983,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -26941,7 +27030,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27037,7 +27127,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -27084,7 +27175,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27130,7 +27222,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27226,7 +27319,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -27273,7 +27367,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27319,7 +27414,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27415,7 +27511,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -27462,7 +27559,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27508,7 +27606,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27604,7 +27703,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -27651,7 +27751,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27697,7 +27798,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27793,7 +27895,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -27840,7 +27943,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27886,7 +27990,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -27982,7 +28087,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -28029,7 +28135,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28075,7 +28182,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28171,7 +28279,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -28218,7 +28327,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28264,7 +28374,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28360,7 +28471,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A2" }, @@ -28407,7 +28519,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28453,7 +28566,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28549,7 +28663,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -28596,7 +28711,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28642,7 +28758,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28738,7 +28855,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A4" }, @@ -28785,7 +28903,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28831,7 +28950,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -28927,7 +29047,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -28974,7 +29095,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29020,7 +29142,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29116,7 +29239,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A6" }, @@ -29163,7 +29287,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29209,7 +29334,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29305,7 +29431,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29352,7 +29479,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29398,7 +29526,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29494,7 +29623,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A8" }, @@ -29541,7 +29671,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29587,7 +29718,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29683,7 +29815,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A9" }, @@ -29730,7 +29863,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29776,7 +29910,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29872,7 +30007,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A10" }, @@ -29919,7 +30055,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -29965,7 +30102,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30061,7 +30199,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A11" }, @@ -30108,7 +30247,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30154,7 +30294,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30250,7 +30391,8 @@ "y": 0.0, "z": -14.310000000000002 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A12" }, @@ -30297,7 +30439,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30343,7 +30486,8 @@ "y": 0.0, "z": -7.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -30556,7 +30700,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30603,7 +30748,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30649,7 +30795,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30745,7 +30892,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B1" }, @@ -30792,7 +30940,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30838,7 +30987,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -30934,7 +31084,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -30981,7 +31132,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31027,7 +31179,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31123,7 +31276,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D1" }, @@ -31170,7 +31324,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31216,7 +31371,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31312,7 +31468,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E1" }, @@ -31359,7 +31516,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31405,7 +31563,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31501,7 +31660,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F1" }, @@ -31548,7 +31708,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31594,7 +31755,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31690,7 +31852,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G1" }, @@ -31737,7 +31900,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31783,7 +31947,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31879,7 +32044,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H1" }, @@ -31926,7 +32092,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -31972,7 +32139,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -32068,7 +32236,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32115,7 +32284,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32161,7 +32331,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32257,7 +32428,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B3" }, @@ -32304,7 +32476,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32350,7 +32523,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32446,7 +32620,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C3" }, @@ -32493,7 +32668,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32539,7 +32715,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32635,7 +32812,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D3" }, @@ -32682,7 +32860,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32728,7 +32907,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32824,7 +33004,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E3" }, @@ -32871,7 +33052,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -32917,7 +33099,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33013,7 +33196,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F3" }, @@ -33060,7 +33244,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33106,7 +33291,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33202,7 +33388,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G3" }, @@ -33249,7 +33436,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33295,7 +33483,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33391,7 +33580,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H3" }, @@ -33438,7 +33628,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33484,7 +33675,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A3" }, @@ -33580,7 +33772,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33627,7 +33820,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33673,7 +33867,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33769,7 +33964,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B5" }, @@ -33816,7 +34012,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33862,7 +34059,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -33958,7 +34156,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C5" }, @@ -34005,7 +34204,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34051,7 +34251,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34147,7 +34348,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D5" }, @@ -34194,7 +34396,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34240,7 +34443,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34336,7 +34540,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E5" }, @@ -34383,7 +34588,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34429,7 +34635,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34525,7 +34732,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F5" }, @@ -34572,7 +34780,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34618,7 +34827,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34714,7 +34924,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G5" }, @@ -34761,7 +34972,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34807,7 +35019,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34903,7 +35116,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H5" }, @@ -34950,7 +35164,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -34996,7 +35211,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A5" }, @@ -35092,7 +35308,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A7" }, @@ -35139,7 +35356,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -35185,7 +35403,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -35281,7 +35500,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "B7" }, @@ -35328,7 +35548,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -35374,7 +35595,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -35470,7 +35692,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C7" }, @@ -35517,7 +35740,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -35563,7 +35787,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -35659,7 +35884,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "D7" }, @@ -35706,7 +35932,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -35752,7 +35979,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -35848,7 +36076,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "E7" }, @@ -35895,7 +36124,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -35941,7 +36171,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -36037,7 +36268,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "F7" }, @@ -36084,7 +36316,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -36130,7 +36363,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -36226,7 +36460,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "G7" }, @@ -36273,7 +36508,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -36319,7 +36555,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -36415,7 +36652,8 @@ "y": 0.0, "z": -13.949999999999992 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "H7" }, @@ -36462,7 +36700,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -36508,7 +36747,8 @@ "y": 0.0, "z": -5.0 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "C1" }, @@ -36709,6 +36949,7 @@ } } ], + "liquidClasses": [], "liquids": [ { "description": "Amplified Libraries_1", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f7085d7134][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLidClips].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f7085d7134][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLidClips].json index 5157314f76f..7ad30e9d04e 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f7085d7134][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLidClips].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f7085d7134][Flex_X_v2_16_P1000_96_TC_pipetteCollisionWithThermocyclerLidClips].json @@ -1349,7 +1349,7 @@ "errors": [ { "createdAt": "TIMESTAMP", - "detail": "PartialTipMovementNotAllowedError [line 20]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to Opentrons Flex 96 Tip Rack 200 µL in slot A2 with A12 nozzle partial configuration will result in collision with thermocycler lid in deck slot A1.", + "detail": "PartialTipMovementNotAllowedError [line 20]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Requested motion with the A12 nozzle partial configuration is outside of robot bounds for the pipette.", "errorCode": "4000", "errorInfo": {}, "errorType": "ExceptionInProtocolError", @@ -1358,7 +1358,7 @@ "wrappedErrors": [ { "createdAt": "TIMESTAMP", - "detail": "Moving to Opentrons Flex 96 Tip Rack 200 µL in slot A2 with A12 nozzle partial configuration will result in collision with thermocycler lid in deck slot A1.", + "detail": "Requested motion with the A12 nozzle partial configuration is outside of robot bounds for the pipette.", "errorCode": "2004", "errorInfo": {}, "errorType": "PartialTipMovementNotAllowedError", @@ -1385,6 +1385,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": {}, "modules": [ diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f834b97da1][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f834b97da1][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1].json index 195134dfb1c..acf7455e286 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f834b97da1][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f834b97da1][Flex_S_v2_16_P1000_96_GRIP_HS_MB_TC_TM_DeckConfiguration1].json @@ -13041,7 +13041,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13074,7 +13075,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13182,7 +13184,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13215,7 +13218,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13396,7 +13400,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13429,7 +13434,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13537,7 +13543,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13570,7 +13577,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13751,7 +13759,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13784,7 +13793,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13892,7 +13902,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -13925,7 +13936,8 @@ "y": 0.0, "z": -13.78 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -14180,6 +14192,7 @@ "location": "offDeck" } ], + "liquidClasses": [], "liquids": [ { "description": "High Quality H₂O", diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f86713b4d4][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_distribute_source_collision].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f86713b4d4][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_distribute_source_collision].json new file mode 100644 index 00000000000..1fdb58d69ab --- /dev/null +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f86713b4d4][Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_distribute_source_collision].json @@ -0,0 +1,3898 @@ +{ + "commands": [ + { + "commandType": "home", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "notes": [], + "params": {}, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "73d9d4d55ae8466f3a793ceb70545fa5", + "notes": [], + "params": { + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "B2" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/opentrons-1000ul-tips" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 97.47 + }, + "gripperOffsets": {}, + "groups": [ + { + "metadata": {}, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "tipRack", + "displayName": "Opentrons OT-2 96 Tip Rack 1000 µL", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": false, + "isTiprack": true, + "loadName": "opentrons_96_tiprack_1000ul", + "tipLength": 88, + "tipOverlap": 7.95 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 74.24, + "z": 9.47 + }, + "A10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 74.24, + "z": 9.47 + }, + "A11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 74.24, + "z": 9.47 + }, + "A12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 74.24, + "z": 9.47 + }, + "A2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 74.24, + "z": 9.47 + }, + "A3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 74.24, + "z": 9.47 + }, + "A4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 74.24, + "z": 9.47 + }, + "A5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 74.24, + "z": 9.47 + }, + "A6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 74.24, + "z": 9.47 + }, + "A7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 74.24, + "z": 9.47 + }, + "A8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 74.24, + "z": 9.47 + }, + "A9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 74.24, + "z": 9.47 + }, + "B1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 65.24, + "z": 9.47 + }, + "B10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 65.24, + "z": 9.47 + }, + "B11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 65.24, + "z": 9.47 + }, + "B12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 65.24, + "z": 9.47 + }, + "B2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 65.24, + "z": 9.47 + }, + "B3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 65.24, + "z": 9.47 + }, + "B4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 65.24, + "z": 9.47 + }, + "B5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 65.24, + "z": 9.47 + }, + "B6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 65.24, + "z": 9.47 + }, + "B7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 65.24, + "z": 9.47 + }, + "B8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 65.24, + "z": 9.47 + }, + "B9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 65.24, + "z": 9.47 + }, + "C1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 56.24, + "z": 9.47 + }, + "C10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 56.24, + "z": 9.47 + }, + "C11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 56.24, + "z": 9.47 + }, + "C12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 56.24, + "z": 9.47 + }, + "C2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 56.24, + "z": 9.47 + }, + "C3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 56.24, + "z": 9.47 + }, + "C4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 56.24, + "z": 9.47 + }, + "C5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 56.24, + "z": 9.47 + }, + "C6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 56.24, + "z": 9.47 + }, + "C7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 56.24, + "z": 9.47 + }, + "C8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 56.24, + "z": 9.47 + }, + "C9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 56.24, + "z": 9.47 + }, + "D1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 47.24, + "z": 9.47 + }, + "D10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 47.24, + "z": 9.47 + }, + "D11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 47.24, + "z": 9.47 + }, + "D12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 47.24, + "z": 9.47 + }, + "D2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 47.24, + "z": 9.47 + }, + "D3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 47.24, + "z": 9.47 + }, + "D4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 47.24, + "z": 9.47 + }, + "D5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 47.24, + "z": 9.47 + }, + "D6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 47.24, + "z": 9.47 + }, + "D7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 47.24, + "z": 9.47 + }, + "D8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 47.24, + "z": 9.47 + }, + "D9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 47.24, + "z": 9.47 + }, + "E1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 38.24, + "z": 9.47 + }, + "E10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 38.24, + "z": 9.47 + }, + "E11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 38.24, + "z": 9.47 + }, + "E12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 38.24, + "z": 9.47 + }, + "E2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 38.24, + "z": 9.47 + }, + "E3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 38.24, + "z": 9.47 + }, + "E4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 38.24, + "z": 9.47 + }, + "E5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 38.24, + "z": 9.47 + }, + "E6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 38.24, + "z": 9.47 + }, + "E7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 38.24, + "z": 9.47 + }, + "E8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 38.24, + "z": 9.47 + }, + "E9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 38.24, + "z": 9.47 + }, + "F1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 29.24, + "z": 9.47 + }, + "F10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 29.24, + "z": 9.47 + }, + "F11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 29.24, + "z": 9.47 + }, + "F12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 29.24, + "z": 9.47 + }, + "F2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 29.24, + "z": 9.47 + }, + "F3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 29.24, + "z": 9.47 + }, + "F4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 29.24, + "z": 9.47 + }, + "F5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 29.24, + "z": 9.47 + }, + "F6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 29.24, + "z": 9.47 + }, + "F7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 29.24, + "z": 9.47 + }, + "F8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 29.24, + "z": 9.47 + }, + "F9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 29.24, + "z": 9.47 + }, + "G1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 20.24, + "z": 9.47 + }, + "G10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 20.24, + "z": 9.47 + }, + "G11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 20.24, + "z": 9.47 + }, + "G12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 20.24, + "z": 9.47 + }, + "G2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 20.24, + "z": 9.47 + }, + "G3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 20.24, + "z": 9.47 + }, + "G4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 20.24, + "z": 9.47 + }, + "G5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 20.24, + "z": 9.47 + }, + "G6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 20.24, + "z": 9.47 + }, + "G7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 20.24, + "z": 9.47 + }, + "G8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 20.24, + "z": 9.47 + }, + "G9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 20.24, + "z": 9.47 + }, + "H1": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 14.38, + "y": 11.24, + "z": 9.47 + }, + "H10": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 95.38, + "y": 11.24, + "z": 9.47 + }, + "H11": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 104.38, + "y": 11.24, + "z": 9.47 + }, + "H12": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 113.38, + "y": 11.24, + "z": 9.47 + }, + "H2": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 23.38, + "y": 11.24, + "z": 9.47 + }, + "H3": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 32.38, + "y": 11.24, + "z": 9.47 + }, + "H4": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 41.38, + "y": 11.24, + "z": 9.47 + }, + "H5": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 50.38, + "y": 11.24, + "z": 9.47 + }, + "H6": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 59.38, + "y": 11.24, + "z": 9.47 + }, + "H7": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 68.38, + "y": 11.24, + "z": 9.47 + }, + "H8": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 77.38, + "y": 11.24, + "z": 9.47 + }, + "H9": { + "depth": 88, + "diameter": 7.23, + "shape": "circular", + "totalLiquidVolume": 1000, + "x": 86.38, + "y": 11.24, + "z": 9.47 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadPipette", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "bd403a1c851a75b4b68ce34796d713fa", + "notes": [], + "params": { + "liquidPresenceDetection": false, + "mount": "left", + "pipetteName": "p1000_96", + "tipOverlapNotAfterVersion": "v3" + }, + "result": { + "pipetteId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "configureNozzleLayout", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "2c37ad797da7df791b57a7843a203e88", + "notes": [], + "params": { + "configurationParams": { + "primaryNozzle": "H12", + "style": "SINGLE" + }, + "pipetteId": "UUID" + }, + "result": {}, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "691afd54dfa7982fb89e5f77c763bfd4", + "notes": [], + "params": { + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "C1" + }, + "namespace": "opentrons", + "version": 1 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "Opentrons", + "brandId": [], + "links": [ + "https://shop.opentrons.com/collections/opentrons-tips/products/tube-rack-set-1" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.75, + "yDimension": 85.5, + "zDimension": 124.35 + }, + "gripperOffsets": {}, + "groups": [ + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352095", + "352096", + "352097", + "352099", + "352196" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 6x15 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A2", + "B1", + "B2", + "C1", + "C2" + ] + }, + { + "brand": { + "brand": "Falcon", + "brandId": [ + "352070", + "352098" + ], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Liquid-Handling/Tubes,-Liquid-Handling/Centrifuge-Tubes/Falcon%C2%AE-Conical-Centrifuge-Tubes/p/falconConicalTubes" + ] + }, + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Falcon 4x50 mL Conical", + "wellBottomShape": "v" + }, + "wells": [ + "A3", + "A4", + "B3", + "B4" + ] + } + ], + "metadata": { + "displayCategory": "tubeRack", + "displayName": "Opentrons 10 Tube Rack with Falcon 4x50 mL, 6x15 mL Conical", + "displayVolumeUnits": "mL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1" + ], + [ + "A2", + "B2", + "C2" + ], + [ + "A3", + "B3" + ], + [ + "A4", + "B4" + ] + ], + "parameters": { + "format": "irregular", + "isMagneticModuleCompatible": false, + "isTiprack": false, + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical" + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "version": 1, + "wells": { + "A1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 67.75, + "z": 6.85 + }, + "A2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 67.75, + "z": 6.85 + }, + "A3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 60.25, + "z": 7.3 + }, + "A4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 60.25, + "z": 7.3 + }, + "B1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 42.75, + "z": 6.85 + }, + "B2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 42.75, + "z": 6.85 + }, + "B3": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 71.38, + "y": 25.25, + "z": 7.3 + }, + "B4": { + "depth": 113, + "diameter": 27.81, + "shape": "circular", + "totalLiquidVolume": 50000, + "x": 106.38, + "y": 25.25, + "z": 7.3 + }, + "C1": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 13.88, + "y": 17.75, + "z": 6.85 + }, + "C2": { + "depth": 117.5, + "diameter": 14.9, + "shape": "circular", + "totalLiquidVolume": 15000, + "x": 38.88, + "y": 17.75, + "z": 6.85 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "c910be62291bb94458a59cb4185c5180", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D2" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "loadLabware", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "d75e38152238e9ad0c18e291fe97e483", + "notes": [], + "params": { + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D3" + }, + "namespace": "opentrons", + "version": 2 + }, + "result": { + "definition": { + "allowedRoles": [], + "brand": { + "brand": "NEST", + "brandId": [ + "402501" + ], + "links": [ + "https://www.nest-biotech.com/pcr-plates/58773587.html" + ] + }, + "cornerOffsetFromSlot": { + "x": 0, + "y": 0, + "z": 0 + }, + "dimensions": { + "xDimension": 127.76, + "yDimension": 85.48, + "zDimension": 15.7 + }, + "gripForce": 15.0, + "gripHeightFromLabwareBottom": 10.65, + "gripperOffsets": {}, + "groups": [ + { + "metadata": { + "wellBottomShape": "v" + }, + "wells": [ + "A1", + "A10", + "A11", + "A12", + "A2", + "A3", + "A4", + "A5", + "A6", + "A7", + "A8", + "A9", + "B1", + "B10", + "B11", + "B12", + "B2", + "B3", + "B4", + "B5", + "B6", + "B7", + "B8", + "B9", + "C1", + "C10", + "C11", + "C12", + "C2", + "C3", + "C4", + "C5", + "C6", + "C7", + "C8", + "C9", + "D1", + "D10", + "D11", + "D12", + "D2", + "D3", + "D4", + "D5", + "D6", + "D7", + "D8", + "D9", + "E1", + "E10", + "E11", + "E12", + "E2", + "E3", + "E4", + "E5", + "E6", + "E7", + "E8", + "E9", + "F1", + "F10", + "F11", + "F12", + "F2", + "F3", + "F4", + "F5", + "F6", + "F7", + "F8", + "F9", + "G1", + "G10", + "G11", + "G12", + "G2", + "G3", + "G4", + "G5", + "G6", + "G7", + "G8", + "G9", + "H1", + "H10", + "H11", + "H12", + "H2", + "H3", + "H4", + "H5", + "H6", + "H7", + "H8", + "H9" + ] + } + ], + "metadata": { + "displayCategory": "wellPlate", + "displayName": "NEST 96 Well Plate 100 µL PCR Full Skirt", + "displayVolumeUnits": "µL", + "tags": [] + }, + "namespace": "opentrons", + "ordering": [ + [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1" + ], + [ + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10" + ], + [ + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11" + ], + [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + [ + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2" + ], + [ + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3" + ], + [ + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4" + ], + [ + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5" + ], + [ + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6" + ], + [ + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7" + ], + [ + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8" + ], + [ + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9" + ] + ], + "parameters": { + "format": "96Standard", + "isMagneticModuleCompatible": true, + "isTiprack": false, + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "magneticModuleEngageHeight": 20 + }, + "schemaVersion": 2, + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { + "x": 0, + "y": 0, + "z": 10.2 + }, + "opentrons_96_well_aluminum_block": { + "x": 0, + "y": 0, + "z": 12.66 + } + }, + "stackingOffsetWithModule": { + "thermocyclerModuleV2": { + "x": 0, + "y": 0, + "z": 10.8 + } + }, + "version": 2, + "wells": { + "A1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 74.24, + "z": 0.92 + }, + "A10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 74.24, + "z": 0.92 + }, + "A11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 74.24, + "z": 0.92 + }, + "A12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 74.24, + "z": 0.92 + }, + "A2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 74.24, + "z": 0.92 + }, + "A3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 74.24, + "z": 0.92 + }, + "A4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 74.24, + "z": 0.92 + }, + "A5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 74.24, + "z": 0.92 + }, + "A6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 74.24, + "z": 0.92 + }, + "A7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 74.24, + "z": 0.92 + }, + "A8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 74.24, + "z": 0.92 + }, + "A9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 74.24, + "z": 0.92 + }, + "B1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 65.24, + "z": 0.92 + }, + "B10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 65.24, + "z": 0.92 + }, + "B11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 65.24, + "z": 0.92 + }, + "B12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 65.24, + "z": 0.92 + }, + "B2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 65.24, + "z": 0.92 + }, + "B3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 65.24, + "z": 0.92 + }, + "B4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 65.24, + "z": 0.92 + }, + "B5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 65.24, + "z": 0.92 + }, + "B6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 65.24, + "z": 0.92 + }, + "B7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 65.24, + "z": 0.92 + }, + "B8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 65.24, + "z": 0.92 + }, + "B9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 65.24, + "z": 0.92 + }, + "C1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 56.24, + "z": 0.92 + }, + "C10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 56.24, + "z": 0.92 + }, + "C11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 56.24, + "z": 0.92 + }, + "C12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 56.24, + "z": 0.92 + }, + "C2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 56.24, + "z": 0.92 + }, + "C3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 56.24, + "z": 0.92 + }, + "C4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 56.24, + "z": 0.92 + }, + "C5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 56.24, + "z": 0.92 + }, + "C6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 56.24, + "z": 0.92 + }, + "C7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 56.24, + "z": 0.92 + }, + "C8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 56.24, + "z": 0.92 + }, + "C9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 56.24, + "z": 0.92 + }, + "D1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 47.24, + "z": 0.92 + }, + "D10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 47.24, + "z": 0.92 + }, + "D11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 47.24, + "z": 0.92 + }, + "D12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 47.24, + "z": 0.92 + }, + "D2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 47.24, + "z": 0.92 + }, + "D3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 47.24, + "z": 0.92 + }, + "D4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 47.24, + "z": 0.92 + }, + "D5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 47.24, + "z": 0.92 + }, + "D6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 47.24, + "z": 0.92 + }, + "D7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 47.24, + "z": 0.92 + }, + "D8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 47.24, + "z": 0.92 + }, + "D9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 47.24, + "z": 0.92 + }, + "E1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 38.24, + "z": 0.92 + }, + "E10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 38.24, + "z": 0.92 + }, + "E11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 38.24, + "z": 0.92 + }, + "E12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 38.24, + "z": 0.92 + }, + "E2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 38.24, + "z": 0.92 + }, + "E3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 38.24, + "z": 0.92 + }, + "E4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 38.24, + "z": 0.92 + }, + "E5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 38.24, + "z": 0.92 + }, + "E6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 38.24, + "z": 0.92 + }, + "E7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 38.24, + "z": 0.92 + }, + "E8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 38.24, + "z": 0.92 + }, + "E9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 38.24, + "z": 0.92 + }, + "F1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 29.24, + "z": 0.92 + }, + "F10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 29.24, + "z": 0.92 + }, + "F11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 29.24, + "z": 0.92 + }, + "F12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 29.24, + "z": 0.92 + }, + "F2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 29.24, + "z": 0.92 + }, + "F3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 29.24, + "z": 0.92 + }, + "F4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 29.24, + "z": 0.92 + }, + "F5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 29.24, + "z": 0.92 + }, + "F6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 29.24, + "z": 0.92 + }, + "F7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 29.24, + "z": 0.92 + }, + "F8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 29.24, + "z": 0.92 + }, + "F9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 29.24, + "z": 0.92 + }, + "G1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 20.24, + "z": 0.92 + }, + "G10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 20.24, + "z": 0.92 + }, + "G11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 20.24, + "z": 0.92 + }, + "G12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 20.24, + "z": 0.92 + }, + "G2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 20.24, + "z": 0.92 + }, + "G3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 20.24, + "z": 0.92 + }, + "G4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 20.24, + "z": 0.92 + }, + "G5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 20.24, + "z": 0.92 + }, + "G6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 20.24, + "z": 0.92 + }, + "G7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 20.24, + "z": 0.92 + }, + "G8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 20.24, + "z": 0.92 + }, + "G9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 20.24, + "z": 0.92 + }, + "H1": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 14.38, + "y": 11.24, + "z": 0.92 + }, + "H10": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 95.38, + "y": 11.24, + "z": 0.92 + }, + "H11": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 104.38, + "y": 11.24, + "z": 0.92 + }, + "H12": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 113.38, + "y": 11.24, + "z": 0.92 + }, + "H2": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 23.38, + "y": 11.24, + "z": 0.92 + }, + "H3": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 32.38, + "y": 11.24, + "z": 0.92 + }, + "H4": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 41.38, + "y": 11.24, + "z": 0.92 + }, + "H5": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 50.38, + "y": 11.24, + "z": 0.92 + }, + "H6": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 59.38, + "y": 11.24, + "z": 0.92 + }, + "H7": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 68.38, + "y": 11.24, + "z": 0.92 + }, + "H8": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 77.38, + "y": 11.24, + "z": 0.92 + }, + "H9": { + "depth": 14.78, + "diameter": 5.34, + "shape": "circular", + "totalLiquidVolume": 100, + "x": 86.38, + "y": 11.24, + "z": 0.92 + } + } + }, + "labwareId": "UUID" + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + }, + { + "commandType": "pickUpTip", + "completedAt": "TIMESTAMP", + "createdAt": "TIMESTAMP", + "id": "UUID", + "key": "8de5f9595204f05bd1016cce23cd32b9", + "notes": [], + "params": { + "labwareId": "UUID", + "pipetteId": "UUID", + "wellLocation": { + "offset": { + "x": 0.0, + "y": 0.0, + "z": 0.0 + }, + "origin": "top" + }, + "wellName": "A1" + }, + "result": { + "position": { + "x": 178.38, + "y": 288.24, + "z": 97.47 + }, + "tipDiameter": 7.23, + "tipLength": 77.5, + "tipVolume": 1000.0 + }, + "startedAt": "TIMESTAMP", + "status": "succeeded" + } + ], + "config": { + "apiVersion": [ + 2, + 20 + ], + "protocolType": "python" + }, + "createdAt": "TIMESTAMP", + "errors": [ + { + "createdAt": "TIMESTAMP", + "detail": "PartialTipMovementNotAllowedError [line 501]: Error 2004 MOTION_PLANNING_FAILURE (PartialTipMovementNotAllowedError): Moving to NEST 96 Well Plate 100 µL PCR Full Skirt in slot D2 with H12 nozzle partial configuration will result in collision with items in deck slot C1.", + "errorCode": "4000", + "errorInfo": {}, + "errorType": "ExceptionInProtocolError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [ + { + "createdAt": "TIMESTAMP", + "detail": "Moving to NEST 96 Well Plate 100 µL PCR Full Skirt in slot D2 with H12 nozzle partial configuration will result in collision with items in deck slot C1.", + "errorCode": "2004", + "errorInfo": {}, + "errorType": "PartialTipMovementNotAllowedError", + "id": "UUID", + "isDefined": false, + "wrappedErrors": [] + } + ] + } + ], + "files": [ + { + "name": "Flex_X_v2_20_96_None_Overrides_TooTallLabware_Override_distribute_source_collision.py", + "role": "main" + } + ], + "labware": [ + { + "definitionUri": "opentrons/opentrons_96_tiprack_1000ul/1", + "id": "UUID", + "loadName": "opentrons_96_tiprack_1000ul", + "location": { + "slotName": "B2" + } + }, + { + "definitionUri": "opentrons/opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical/1", + "id": "UUID", + "loadName": "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", + "location": { + "slotName": "C1" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D2" + } + }, + { + "definitionUri": "opentrons/nest_96_wellplate_100ul_pcr_full_skirt/2", + "id": "UUID", + "loadName": "nest_96_wellplate_100ul_pcr_full_skirt", + "location": { + "slotName": "D3" + } + } + ], + "liquidClasses": [], + "liquids": [], + "metadata": { + "description": "oooo", + "protocolName": "Too tall labware on pickup tip" + }, + "modules": [], + "pipettes": [ + { + "id": "UUID", + "mount": "left", + "pipetteName": "p1000_96" + } + ], + "result": "not-ok", + "robotType": "OT-3 Standard", + "runTimeParameters": [] +} diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f88b7d6e30][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_display_name].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f88b7d6e30][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_display_name].json index 1652972327b..fe2e22fae05 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f88b7d6e30][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_display_name].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[f88b7d6e30][Flex_X_v2_18_NO_PIPETTES_Overrides_BadTypesInRTP_Override_wrong_type_in_display_name].json @@ -42,6 +42,7 @@ } ], "labware": [], + "liquidClasses": [], "liquids": [], "metadata": { "protocolName": "Description Too Long 2.18" diff --git a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[fc60ef9cbd][OT2_S_v2_16_P300M_P20S_HS_TC_TM_dispense_changes].json b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[fc60ef9cbd][OT2_S_v2_16_P300M_P20S_HS_TC_TM_dispense_changes].json index 6ff22cf6281..cd25845d931 100644 --- a/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[fc60ef9cbd][OT2_S_v2_16_P300M_P20S_HS_TC_TM_dispense_changes].json +++ b/analyses-snapshot-testing/tests/__snapshots__/analyses_snapshot_test/test_analysis_snapshot[fc60ef9cbd][OT2_S_v2_16_P300M_P20S_HS_TC_TM_dispense_changes].json @@ -2969,7 +2969,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3002,7 +3003,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3035,7 +3037,8 @@ "y": 0.0, "z": -25.85 }, - "origin": "top" + "origin": "top", + "volumeOffset": 0.0 }, "wellName": "A1" }, @@ -3102,6 +3105,7 @@ } } ], + "liquidClasses": [], "liquids": [], "metadata": { "author": "Opentrons Engineering ", diff --git a/api-client/Makefile b/api-client/Makefile index 1ac1ecbc08d..3eb4fd7f05e 100644 --- a/api-client/Makefile +++ b/api-client/Makefile @@ -4,6 +4,12 @@ # TODO(mc, 2021-02-12): this may be unnecessary by using `yarn run` instead SHELL := bash +# These variables can be overriden when make is invoked to customize the +# behavior of jest +tests ?= +cov_opts ?= --coverage=true +test_opts ?= + # standard targets ##################################################################### @@ -20,4 +26,8 @@ build: .PHONY: test test: - $(MAKE) -C .. test-js-api-client + $(MAKE) -C .. test-js-api-client tests="$(tests)" test_opts="$(test_opts)" + +.PHONY: test-cov +test-cov: + make -C .. test-js-api-client tests=$(tests) test_opts="$(test_opts)" cov_opts="$(cov_opts)" diff --git a/api-client/src/errorRecovery/index.ts b/api-client/src/errorRecovery/index.ts new file mode 100644 index 00000000000..eca32dd0aef --- /dev/null +++ b/api-client/src/errorRecovery/index.ts @@ -0,0 +1 @@ +export * from './settings' diff --git a/api-client/src/errorRecovery/settings/getErrorRecoverySettings.ts b/api-client/src/errorRecovery/settings/getErrorRecoverySettings.ts new file mode 100644 index 00000000000..30a3b9f6aa1 --- /dev/null +++ b/api-client/src/errorRecovery/settings/getErrorRecoverySettings.ts @@ -0,0 +1,16 @@ +import { GET, request } from '../../request' + +import type { ResponsePromise } from '../../request' +import type { HostConfig } from '../../types' +import type { ErrorRecoverySettingsResponse } from './types' + +export function getErrorRecoverySettings( + config: HostConfig +): ResponsePromise { + return request( + GET, + '/errorRecovery/settings', + null, + config + ) +} diff --git a/api-client/src/errorRecovery/settings/index.ts b/api-client/src/errorRecovery/settings/index.ts new file mode 100644 index 00000000000..7f33c4c0069 --- /dev/null +++ b/api-client/src/errorRecovery/settings/index.ts @@ -0,0 +1,3 @@ +export { getErrorRecoverySettings } from './getErrorRecoverySettings' +export { updateErrorRecoverySettings } from './updateErrorRecoverySettings' +export * from './types' diff --git a/api-client/src/errorRecovery/settings/types.ts b/api-client/src/errorRecovery/settings/types.ts new file mode 100644 index 00000000000..d427ab88714 --- /dev/null +++ b/api-client/src/errorRecovery/settings/types.ts @@ -0,0 +1,9 @@ +export interface ErrorRecoverySettingsResponse { + data: { + enabled: boolean + } +} + +export interface ErrorRecoverySettingsRequest { + data: Partial +} diff --git a/api-client/src/errorRecovery/settings/updateErrorRecoverySettings.ts b/api-client/src/errorRecovery/settings/updateErrorRecoverySettings.ts new file mode 100644 index 00000000000..e5d1acf29aa --- /dev/null +++ b/api-client/src/errorRecovery/settings/updateErrorRecoverySettings.ts @@ -0,0 +1,20 @@ +import { PATCH, request } from '../../request' + +import type { ResponsePromise } from '../../request' +import type { HostConfig } from '../../types' +import type { + ErrorRecoverySettingsRequest, + ErrorRecoverySettingsResponse, +} from './types' + +export function updateErrorRecoverySettings( + config: HostConfig, + settings: ErrorRecoverySettingsRequest +): ResponsePromise { + return request( + PATCH, + '/errorRecovery/settings', + settings, + config + ) +} diff --git a/api-client/src/index.ts b/api-client/src/index.ts index 858772034ab..ade46aeee7f 100644 --- a/api-client/src/index.ts +++ b/api-client/src/index.ts @@ -3,6 +3,7 @@ export * from './calibration' export * from './client_data' export * from './dataFiles' export * from './deck_configuration' +export * from './errorRecovery' export * from './health' export * from './instruments' export * from './maintenance_runs' diff --git a/api-client/src/modules/api-types.ts b/api-client/src/modules/api-types.ts index c8560ae3980..298b5189ac4 100644 --- a/api-client/src/modules/api-types.ts +++ b/api-client/src/modules/api-types.ts @@ -81,7 +81,7 @@ export interface HeaterShakerData { status: HeaterShakerStatus } export interface AbsorbanceReaderData { - lidStatus: 'open' | 'closed' | 'unknown' + lidStatus: 'on' | 'off' | 'unknown' platePresence: 'present' | 'absent' | 'unknown' sampleWavelength: number | null status: AbsorbanceReaderStatus diff --git a/api-client/src/runs/commands/getCommands.ts b/api-client/src/runs/commands/getCommands.ts index 4833b94e5a8..95283c81b64 100644 --- a/api-client/src/runs/commands/getCommands.ts +++ b/api-client/src/runs/commands/getCommands.ts @@ -3,12 +3,12 @@ import { GET, request } from '../../request' import type { ResponsePromise } from '../../request' import type { HostConfig } from '../../types' import type { CommandsData } from '..' -import type { GetCommandsParams } from './types' +import type { GetRunCommandsParamsRequest } from './types' export function getCommands( config: HostConfig, runId: string, - params: GetCommandsParams + params: GetRunCommandsParamsRequest ): ResponsePromise { return request( GET, diff --git a/api-client/src/runs/commands/getCommandsAsPreSerializedList.ts b/api-client/src/runs/commands/getCommandsAsPreSerializedList.ts index 420f984b280..1d96f3d2209 100644 --- a/api-client/src/runs/commands/getCommandsAsPreSerializedList.ts +++ b/api-client/src/runs/commands/getCommandsAsPreSerializedList.ts @@ -4,13 +4,13 @@ import type { ResponsePromise } from '../../request' import type { HostConfig } from '../../types' import type { CommandsAsPreSerializedListData, - GetCommandsParams, + GetRunCommandsParamsRequest, } from './types' export function getCommandsAsPreSerializedList( config: HostConfig, runId: string, - params: GetCommandsParams + params: GetRunCommandsParamsRequest ): ResponsePromise { return request( GET, diff --git a/api-client/src/runs/commands/types.ts b/api-client/src/runs/commands/types.ts index cd18924201c..215756ede66 100644 --- a/api-client/src/runs/commands/types.ts +++ b/api-client/src/runs/commands/types.ts @@ -1,8 +1,16 @@ import type { RunTimeCommand, RunCommandError } from '@opentrons/shared-data' export interface GetCommandsParams { - cursor: number | null // the index of the command at the center of the window pageLength: number // the number of items to include + cursor?: number +} + +export interface GetRunCommandsParams extends GetCommandsParams { + includeFixitCommands?: boolean +} + +export interface GetRunCommandsParamsRequest extends GetCommandsParams { + includeFixitCommands?: boolean } export interface RunCommandErrors { diff --git a/api-client/src/runs/getErrorRecoveryPolicy.ts b/api-client/src/runs/getErrorRecoveryPolicy.ts new file mode 100644 index 00000000000..67a3d0bd93c --- /dev/null +++ b/api-client/src/runs/getErrorRecoveryPolicy.ts @@ -0,0 +1,17 @@ +import { GET, request } from '../request' + +import type { HostConfig } from '../types' +import type { ResponsePromise } from '../request' +import type { ErrorRecoveryPolicyResponse } from './types' + +export function getErrorRecoveryPolicy( + config: HostConfig, + runId: string +): ResponsePromise { + return request( + GET, + `/runs/${runId}/errorRecoveryPolicy`, + null, + config + ) +} diff --git a/api-client/src/runs/getRunCurrentState.ts b/api-client/src/runs/getRunCurrentState.ts new file mode 100644 index 00000000000..6a64d9b9e0f --- /dev/null +++ b/api-client/src/runs/getRunCurrentState.ts @@ -0,0 +1,17 @@ +import { GET, request } from '../request' + +import type { ResponsePromise } from '../request' +import type { HostConfig } from '../types' +import type { RunCurrentState } from './types' + +export function getRunCurrentState( + config: HostConfig, + runId: string +): ResponsePromise { + return request( + GET, + `/runs/${runId}/currentState`, + null, + config + ) +} diff --git a/api-client/src/runs/index.ts b/api-client/src/runs/index.ts index 9f314f4b025..fff1f303543 100644 --- a/api-client/src/runs/index.ts +++ b/api-client/src/runs/index.ts @@ -10,10 +10,12 @@ export { getCommands } from './commands/getCommands' export { getCommandsAsPreSerializedList } from './commands/getCommandsAsPreSerializedList' export { createRunAction } from './createRunAction' export { getRunCommandErrors } from './commands/getRunCommandErrors' +export { getRunCurrentState } from './getRunCurrentState' export * from './createLabwareOffset' export * from './createLabwareDefinition' export * from './constants' export * from './updateErrorRecoveryPolicy' +export * from './getErrorRecoveryPolicy' export * from './types' export type { CreateRunData } from './createRun' diff --git a/api-client/src/runs/types.ts b/api-client/src/runs/types.ts index 5998259ae50..e41626b6448 100644 --- a/api-client/src/runs/types.ts +++ b/api-client/src/runs/types.ts @@ -7,6 +7,8 @@ import type { RunCommandError, RunTimeCommand, RunTimeParameter, + NozzleLayoutConfig, + OnDeckLabwareLocation, } from '@opentrons/shared-data' import type { ResourceLink, ErrorDetails } from '../types' export * from './commands/types' @@ -59,6 +61,7 @@ export interface LegacyGoodRunData { export interface KnownGoodRunData extends LegacyGoodRunData { ok: true runTimeParameters: RunTimeParameter[] + outputFileIds: string[] } export interface KnownInvalidRunData extends LegacyGoodRunData { @@ -87,30 +90,54 @@ export interface Run { data: RunData } +export interface RunCurrentState { + data: RunCurrentStateData + links: RunCommandLink +} + export interface RunsLinks { current?: ResourceLink } +export interface RunCommandLink { + lastCompleted: CommandLinkNoMeta +} + +export interface CommandLinkNoMeta { + id: string + href: string +} + export interface GetRunsParams { pageLength?: number // the number of items to include } export interface Runs { - data: RunData[] + data: readonly RunData[] links: RunsLinks } +export interface RunCurrentStateData { + estopEngaged: boolean + activeNozzleLayouts: Record // keyed by pipetteId + tipStates: Record // keyed by pipetteId + placeLabwareState?: PlaceLabwareState +} + export const RUN_ACTION_TYPE_PLAY: 'play' = 'play' export const RUN_ACTION_TYPE_PAUSE: 'pause' = 'pause' export const RUN_ACTION_TYPE_STOP: 'stop' = 'stop' export const RUN_ACTION_TYPE_RESUME_FROM_RECOVERY: 'resume-from-recovery' = 'resume-from-recovery' +export const RUN_ACTION_TYPE_RESUME_FROM_RECOVERY_ASSUMING_FALSE_POSITIVE: 'resume-from-recovery-assuming-false-positive' = + 'resume-from-recovery-assuming-false-positive' export type RunActionType = | typeof RUN_ACTION_TYPE_PLAY | typeof RUN_ACTION_TYPE_PAUSE | typeof RUN_ACTION_TYPE_STOP | typeof RUN_ACTION_TYPE_RESUME_FROM_RECOVERY + | typeof RUN_ACTION_TYPE_RESUME_FROM_RECOVERY_ASSUMING_FALSE_POSITIVE export interface RunAction { id: string @@ -152,7 +179,11 @@ export type RunError = RunCommandError * Error Policy */ -export type IfMatchType = 'ignoreAndContinue' | 'failRun' | 'waitForRecovery' +export type IfMatchType = + | 'assumeFalsePositiveAndContinue' + | 'ignoreAndContinue' + | 'failRun' + | 'waitForRecovery' export interface ErrorRecoveryPolicy { policyRules: Array<{ @@ -173,3 +204,23 @@ export interface UpdateErrorRecoveryPolicyRequest { } export type UpdateErrorRecoveryPolicyResponse = Record +export type ErrorRecoveryPolicyResponse = UpdateErrorRecoveryPolicyRequest + +/** + * Current Run State Data + */ +export interface NozzleLayoutValues { + startingNozzle: string + activeNozzles: string[] + config: NozzleLayoutConfig +} + +export interface PlaceLabwareState { + labwareURI: string + location: OnDeckLabwareLocation + shouldPlaceDown: boolean +} + +export interface TipStates { + hasTip: boolean +} diff --git a/api/docs/img/partial-pickup-deck-extents.png b/api/docs/img/partial-pickup-deck-extents.png new file mode 100644 index 00000000000..9977357139a Binary files /dev/null and b/api/docs/img/partial-pickup-deck-extents.png differ diff --git a/api/docs/static/override_sphinx.css b/api/docs/static/override_sphinx.css index 20e923f16c4..8af01be9c04 100644 --- a/api/docs/static/override_sphinx.css +++ b/api/docs/static/override_sphinx.css @@ -1,25 +1,80 @@ -@import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700'); +@import url('https://fonts.googleapis.com/css?family=Public+Sans:300,400,400i,600,700'); +@import url('https://fonts.googleapis.com/css2?family=Reddit+Mono:wght@200..900&display=swap'); -/* OT NAV */ +/* OT TOP NAV */ body { padding: 0; margin: 0; - font-family: "Open Sans", "sans-serif"; } -.highlight-none, .mi, .literal { - color: #048f85; +/* restate button styling */ +.text-brand-blue:hover { + --tw-text-opacity: 1; + color: rgb(0 108 250 / var(--tw-text-opacity)); +} + +.text-white:hover { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +/* hack out search button */ + +div.flex.gap-4 div.text-white.flex.items-center.h-full > button { + display: none; +} + +/* use Akko from docs site instead of from main site */ +header a.button-sm { + font-family: "AkkoPro-Regular", "Akko Pro", "Open Sans", sans-serif !important; +} + +/* OT FOOTER */ + +footer { + padding: 0; +} + +.footer-app-button:hover { + color: #E8E9EA; +} + +/* restate newsletter form styling */ + +footer input.hs-input { + margin: 0; + max-width: 100%; + width: 100%; + height: 42px; + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.5rem; + padding-bottom: 0.5rem; + font-size: 1rem; + --tw-text-opacity: 1; + color: rgb(112 112 117 / var(--tw-text-opacity)); +} + +footer .hs_submit input { + height: 40px; + min-width: 100% !important; + width: 100% !important; } /* DOCS */ +div.document .highlight-none, .mi, .literal { + color: #048f85; +} + div.document { - padding-top: 150px; + padding-top: 96px; margin-top: 0; + font-family: "Public Sans", "sans-serif"; } div.document [id] { - scroll-margin-top: 150px; + scroll-margin-top: 110px; } @media only screen and (min-device-width: 768px) and (min-width: 768px) and (max-width: 1023px) { @@ -33,20 +88,62 @@ div.document [id] { } } -div.body p { +div.document p { + line-height: 20pt; +} + +div.document section > p, +div.document dl p { + margin: 1em 0; +} + +div.document pre, +div.document tt, +div.document code { + font-size: 0.9em; + font-family: "Reddit Mono", "Consolas", "Lucida Console", monospace; +} + +div.document dl { + margin-bottom: 15px; +} + +/* API Reference */ +div.document .sig, +div.document .sig-name, +div.document code.descname, +div.document .sig-prename, +div.document .optional, +div.document .sig-paren { + font-size: 1em; + font-family: "Reddit Mono", "Consolas", "Lucida Console", monospace; +} + +div.document dt.field-even, +div.document dt.field-odd { + margin-top: 1em; line-height: 20pt; - font-family: "Open Sans", "sans-serif"; } -div.body h1 { +div.document dd ul { + margin-top: 1em; +} + +span.colon { + display: none; +} + +/* all article pages */ + +div.document h1 { margin-top: 0; margin-bottom: 0; padding-top: 0; - font-size: 2.4em; + font-size: 2em; min-height: 78px; } -div.body h1 + p { +div.document h1 + p { padding-top: 24px; } @@ -58,6 +155,19 @@ div.documentwrapper { float: none; } +div.body { + padding-top: 1rem; +} + +a:hover { + color: inherit; + text-decoration: inherit; +} + +div.body a { + color: #006FFF; +} + /* Don't allow inline items try to clear themselves below the sidebar */ pre, div[class*="highlight-"], blockquote, blockquote::after, div.admonition::after { clear: none; @@ -66,14 +176,14 @@ pre, div[class*="highlight-"], blockquote, blockquote::after, div.admonition::af /* Sticky, scrolling sidebar. Height calc leaves room for header. */ div.sphinxsidebar { position: sticky; - top: 150px; + top: 96px; align-self: flex-start; - max-height: calc(100vh - 150px); + max-height: calc(100vh - 96px); overflow-y: auto; } div.sphinxsidebarwrapper { - padding-top: 6px; + padding-top: 1rem; } /* Hide the redundant 'description' tagline */ @@ -86,11 +196,28 @@ div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar h5, div.sphinxsidebar h6, -div.body h2, -div.body h3, -div.body h4, -div.body h5 { - font-family: "Open Sans", "sans-serif"; +div.document h2, +div.document h3, +div.document h4, +div.document h5 { + font-family: "Public Sans", "sans-serif" !important; + text-transform: none !important; + letter-spacing: 0 !important; +} + +/* hide the word "Navigation" because people know what links are */ +div.sphinxsidebarwrapper > h3 { + display: none; +} + +/* restate header sizes to avoid conflict from inline style in footer */ +div.document h3 { font-size: 150% !important; } +div.document h4 { font-size: 130% !important; } + +/* Links need an extra two pixels of padding to compensate between body font height +being 1em and code font height being 0.9em */ +a.reference { + padding-bottom: 2px; } /* Suppressing the display of the toctrees rendered in the doc body means we @@ -191,15 +318,23 @@ div.body p.caption { font-size: 22px; } -ul { - /* margin-left: 0; */ - font-family: "Open Sans", "sans-serif"; +div.body ul, div.body ol { + margin: 10px 0 10px 30px; + font-family: "Public Sans", "sans-serif"; } -ul ul { +div.body ul { + list-style-type: disc; +} + +div.body ul p, div.body ol p { + margin-bottom: 0; +} + +div.body ul ul { list-style-type: circle; margin-left: 30px; - font-family: "Open Sans", "sans-serif"; + font-family: "Public Sans", "sans-serif"; } @media screen and (min-device-width: 320px)and (max-device-width: 640px) { @@ -262,3 +397,9 @@ div.warning { background-color: #f7e0e0; border: none; } + +/* Tabs also in Public Sans */ + +.sphinx-tabs-tab { + font-family: "Public Sans", "sans-serif" !important; +} \ No newline at end of file diff --git a/api/docs/templates/v2/layout.html b/api/docs/templates/v2/layout.html index fbc6e860eaf..fd795d94c38 100644 --- a/api/docs/templates/v2/layout.html +++ b/api/docs/templates/v2/layout.html @@ -31,1015 +31,7 @@ {%- if theme_fixed_sidebar|lower == 'true' %} @@ -1061,116 +53,8 @@ {%- else %} {{ super() }} {%- endif %} {%- endblock %} {%- block footer %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/api/docs/templates/v2/remote-nav.html b/api/docs/templates/v2/remote-nav.html new file mode 100644 index 00000000000..7dc44e55804 --- /dev/null +++ b/api/docs/templates/v2/remote-nav.html @@ -0,0 +1,965 @@ + + + + + + + + + + + +Remote Nav - Opentrons + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + diff --git a/api/docs/v2/basic_commands/liquids.rst b/api/docs/v2/basic_commands/liquids.rst index 9e2e4ff8782..59ccf337892 100644 --- a/api/docs/v2/basic_commands/liquids.rst +++ b/api/docs/v2/basic_commands/liquids.rst @@ -263,14 +263,15 @@ This example aspirates enough air to fill the remaining volume in a pipette:: Detect Liquids ============== -The :py:meth:`.InstrumentContext.detect_liquid_presence` method tells a Flex pipette to check for liquid in a well. It returns ``True`` if the pressure sensors in the pipette detect a liquid and ``False`` if the sensors do not. +The :py:meth:`.InstrumentContext.detect_liquid_presence` method tells a Flex pipette to check for liquid in a well. It returns ``True`` if the pressure sensors in the pipette detect a liquid and ``False`` if the sensors do not. When ``detect_liquid_presence()`` finds an empty well it won't raise an error or stop your protocol. -Aspiration isn't required to use ``detect_liquid_presence()``. This is a standalone method that can be called when you want the robot to record the presence or absence of a liquid. When ``detect_liquid_presence()`` finds an empty well it won't raise an error or stop your protocol. +``detect_liquid_presence()`` is a standalone method to record the presence or absence of a liquid. You don't have to aspirate after detecting liquid presence. However, you should always pick up a tip immediately prior to checking for liquid, and either aspirate or drop the tip immediately after. This ensures that the pipette uses a clean, dry tip to check for liquid, and prevents cross-contamination. A potential use of liquid detection is to try aspirating from another well if the first well is found to contain no liquid. .. code-block:: python + pipette.pick_up_tip() if pipette.detect_liquid_presence(reservoir["A1"]): pipette.aspirate(100, reservoir["A1"]) else: @@ -283,13 +284,16 @@ A potential use of liquid detection is to try aspirating from another well if th Require Liquids =============== -The :py:meth:`.InstrumentContext.require_liquid_presence` method tells a Flex pipette to check for `and require` liquid in a well. +The :py:meth:`.InstrumentContext.require_liquid_presence` method tells a Flex pipette to check for `and require` liquid in a well. When ``require_liquid_presence()`` finds an empty well, it raises an error and pauses the protocol to let you resolve the problem. -Aspiration isn't required to use ``require_liquid_presence()``. This is a standalone method that can be called when you want the robot to react to a missing liquid or empty well. When ``require_liquid_presence()`` finds an empty well, it raises an error and pauses the protocol to let you resolve the problem. See also :ref:`lpd`. +``require_liquid_presence()`` is a standalone method to react to a missing liquid or empty well. You don't have to aspirate after requiring liquid presence. However, you should always pick up a tip immediately prior to checking for liquid, and either aspirate or drop the tip immediately after. This ensures that the pipette uses a clean, dry tip to check for liquid, and prevents cross-contamination. .. code-block:: python + pipette.pick_up_tip() pipette.require_liquid_presence(reservoir["A1"]) pipette.aspirate(100, reservoir["A1"]) # only occurs if liquid found +You can also require liquid presence for all aspirations performed with a given pipette. See :ref:`lpd`. + .. versionadded:: 2.20 diff --git a/api/docs/v2/conf.py b/api/docs/v2/conf.py index 5ab0fdaad76..c8d1e8e7f97 100644 --- a/api/docs/v2/conf.py +++ b/api/docs/v2/conf.py @@ -99,7 +99,7 @@ # use rst_prolog to hold the subsitution # update the apiLevel value whenever a new minor version is released rst_prolog = f""" -.. |apiLevel| replace:: 2.20 +.. |apiLevel| replace:: 2.21 .. |release| replace:: {release} """ @@ -445,7 +445,6 @@ ("py:class", r".*protocol_api\.config.*"), ("py:class", r".*opentrons_shared_data.*"), ("py:class", r".*protocol_api._parameters.Parameters.*"), - ("py:class", r".*AbsorbanceReaderContext"), ("py:class", r".*RobotContext"), # shh it's a secret (for now) ("py:class", r'.*AbstractLabware|APIVersion|LabwareLike|LoadedCoreMap|ModuleTypes|NoneType|OffDeckType|ProtocolCore|WellCore'), # laundry list of not fully qualified things ] diff --git a/api/docs/v2/modules/absorbance_plate_reader.rst b/api/docs/v2/modules/absorbance_plate_reader.rst new file mode 100644 index 00000000000..9f96d5e90d3 --- /dev/null +++ b/api/docs/v2/modules/absorbance_plate_reader.rst @@ -0,0 +1,147 @@ +:og:description: How to use the Absorbance Plate Reader Module in a Python protocol. + +.. _absorbance-plate-reader-module: + +****************************** +Absorbance Plate Reader Module +****************************** + +The Absorbance Plate Reader Module is an on-deck microplate spectrophotometer that works with the Flex robot only. The module uses light absorbance to determine sample concentrations in 96-well plates. + +The Absorbance Plate Reader is represented in code by an :py:class:`.AbsorbanceReaderContext` object, which has methods for moving the module lid with the Flex Gripper, initializing the module to read at a single wavelength or multiple wavelengths, and reading a plate. With the Python Protocol API, you can process plate reader data immediately in your protocol or export it to a CSV for post-run use. + +This page explains the actions necessary for using the Absorbance Plate Reader. These combine to form the typical reader workflow: + + 1. Close the lid with no plate inside + 2. Initialize the reader + 3. Open the lid + 4. Move a plate onto the module + 5. Close the lid + 6. Read the plate + + +Loading and Deck Slots +====================== + +The Absorbance Plate Reader can only be loaded in slots A3–D3. If you try to load it in any other slot, the API will raise an error. The module's caddy is designed such that the detection unit is in deck column 3 and the special staging area for the lid/illumination unit is in deck column 4. You can't load or move other labware on the Absorbance Plate Reader caddy in deck column 4, even while the lid is in the closed position (on top of the detection unit in deck column 3). + +The examples in this section will use an Absorbance Plate Reader Module loaded as follows:: + + pr_mod = protocol.load_module( + module_name="absorbanceReaderV1", + location="D3" + ) + +.. versionadded:: 2.21 + +Lid Control +=========== + +Flex uses the gripper to move the lid between its two positions. + + - :py:meth:`~.AbsorbanceReaderContext.open_lid()` moves the lid to the righthand side of the caddy, in deck column 4. + - :py:meth:`~.AbsorbanceReaderContext.close_lid()` moves the lid onto the detection unit, in deck column 3. + +If you call ``open_lid()`` or ``close_lid()`` and the lid is already in the corresponding position, the method will succeed immediately. You can also check the position of the lid with :py:meth:`~.AbsorbanceReaderContext.is_lid_on()`. + +You need to call ``close_lid()`` before initializing the reader, even if the reader was in the closed position at the start of the protocol. + +.. warning:: + Do not move the lid manually, during or outside of a protocol. The API does not allow manual lid movement because there is a risk of damaging the module. + +.. _absorbance-initialization: + +Initialization +============== + +Initializing the reader prepares it to read a plate later in your protocol. The :py:meth:`.AbsorbanceReaderContext.initialize` method accepts parameters for the number of readings you want to take, the wavelengths to read, and whether you want to compare the reading to a reference wavelength. In the default hardware configuration, the supported wavelengths are 450 nm (blue), 562 nm (green), 600 nm (orange), and 650 nm (red). + +The module uses these parameters immediately to perform the physical initialization. Additionally, the API preserves these values and uses them when you read the plate later in your protocol. + +Let's take a look at examples of how to combine these parameters to prepare different types of readings. The simplest reading measures one wavelength, with no reference wavelength:: + + pr_mod.initialize(mode="single", wavelengths=[450]) + +.. versionadded:: 2.21 + +Now the reader is prepared to read at 450 nm. Note that the ``wavelengths`` parameter always takes a list of integer wavelengths, even when only reading a single wavelength. + +This example can be extended by adding a reference wavelength:: + + pr_mod.initialize( + mode="single", wavelengths=[450], reference_wavelength=[562] + ) + +When configured this way, the module will read twice. In the :ref:`output data `, the values read for ``reference_wavelength`` will be subtracted from the values read for the single member of ``wavelengths``. This is useful for normalization, or to correct for background interference in wavelength measurements. + +The reader can also be initialized to take multiple measurements. When ``mode="multi"``, the ``wavelengths`` list can have up to six elements. This example will initialize the reader to read at three wavelengths:: + + pr_mod.initialize(mode="multi", wavelengths=[450, 562, 600]) + +You can't use a reference wavelength when performing multiple measurements. + + +Reading a Plate +=============== + +Use :py:meth:`.AbsorbanceReaderContext.read` to have the module read the plate, using the parameters that you specified during initialization:: + + pr_data = pr_mod.read() + +.. versionadded:: 2.21 + +The ``read()`` method returns the results in a dictionary, which the above example saves to the variable ``pr_data``. + +If you need to access this data after the conclusion of your protocol, add the ``export_filename`` parameter to instruct the API to output a CSV file, which is available in the Opentrons App by going to your Flex and viewing Recent Protocol Runs:: + + pr_data = pr_mod.read(export_filename="plate_data") + +In the above example, the API both saves the data to a variable and outputs a CSV file. If you only need the data post-run, you can omit the variable assignment. + +.. _plate-reader-data: + +Using Plate Reader Data +======================= + +There are two ways to use output data from the Absorbance Plate Reader: + +- Within your protocol as a nested dictionary object. +- Outside of your protocol, as a tabular CSV file. + +The two formats are structured differently, even though they contain the same measurement data. + +Dictionary Data +--------------- + +The dictionary object returned by ``read()`` has two nested levels. The keys at the top level are the wavelengths you provided to ``initialize()``. The keys at the second level are string names of each of the 96 wells, ``"A1"`` through ``"H12"``. The values at the second level are the measured values for each wells. These values are floating point numbers, representing the optical density (OD) of the samples in each well. OD ranges from 0.0 (low sample concentration) to 4.0 (high sample concentration). + +The nested dictionary structure allows you to access results by index later in your protocol. This example initializes a multiple read and then accesses different portions of the results:: + + # initializing and reading + pr_mod.initialize(mode="multi", wavelengths=[450, 600]) + pr_mod.open_lid() + protocol.move_labware(plate, pr_mod, use_gripper=True) + pr_mod.close_lid() + pr_data = pr_mod.read() + + # accessing results + pr_data[450]["A1"] # value for well A1 at 450 nm + pr_data[600]["H12"] # value for well H12 at 600 nm + pr_data[450] # dict of all wells at 450 nm + +You can write additional code to transform this data in any way that you need. For example, you could use a list comprehension to create a list of only the 450 nm values for column 1, ordered by well from A1 to H1:: + + [pr_data[450][w.well_name] for w in plate.columns()[0]] + +.. _absorbance-csv: + +CSV data +-------- + +The CSV exported when specifying ``export_filename`` consists of tabular data followed by additional information. Each measurement produces 9 rows in the CSV file, representing the layout of the well plate that has been read. These rows form a table with numeric labels in the first row and alphabetic labels in the first column, as you would see on physical labware. Each "cell" of the table contains the measured OD value for the well (0.0–4.0) in the corresponding position on the plate. + +Additional information, starting with one blank labware grid, is output at the end of the file. The last few lines of the file list the sample wavelengths, serial number of the module, and timestamps for when measurement started and finished. + +Each output file for your protocol is available in the Opentrons App by going to your Flex and viewing Recent Protocol Runs. After downloading the file from your Flex, you can read it with any software that reads CSV files, and you can write additional code to parse and act upon its contents. + +You can also select the output CSV as the value of a CSV runtime parameter in a subsequent protocol. When you :ref:`parse the CSV data `, make sure to set ``detect_dialect=False``, or the API will raise an error. \ No newline at end of file diff --git a/api/docs/v2/modules/setup.rst b/api/docs/v2/modules/setup.rst index c6badd82954..a0cbe18bf0e 100644 --- a/api/docs/v2/modules/setup.rst +++ b/api/docs/v2/modules/setup.rst @@ -66,7 +66,7 @@ Available Modules The first parameter of :py:meth:`.ProtocolContext.load_module` is the module's *API load name*. The load name tells your robot which module you're going to use in a protocol. The table below lists the API load names for the currently available modules. .. table:: - :widths: 4 5 2 + :widths: 4 4 2 +--------------------+-------------------------------+---------------------------+ | Module | API Load Name | Introduced in API Version | @@ -95,6 +95,9 @@ The first parameter of :py:meth:`.ProtocolContext.load_module` is the module's | Magnetic Block | ``magneticBlockV1`` | 2.15 | | GEN1 | | | +--------------------+-------------------------------+---------------------------+ + | Absorbance Plate | ``absorbanceReaderV1`` | 2.21 | + | Reader Module | | | + +--------------------+-------------------------------+---------------------------+ Some modules were added to our Python API later than others, and others span multiple hardware generations. When writing a protocol that requires a module, make sure your ``requirements`` or ``metadata`` code block specifies an :ref:`API version ` high enough to support all the module generations you want to use. @@ -124,7 +127,7 @@ Any :ref:`custom labware ` added to your Opentrons App is als Module and Labware Compatibility -------------------------------- -It's your responsibility to ensure the labware and module combinations you load together work together. The Protocol API won't raise a warning or error if you load an unusual combination, like placing a tube rack on a Thermocycler. See `What labware can I use with my modules? `_ for more information about labware/module combinations. +It's your responsibility to ensure the labware and module combinations you load together work together. The API generally won't raise a warning or error if you load an unusual combination, like placing a tube rack on a Thermocycler. The API will raise an error if you try to load a labware on an unsupported adapter. When working with custom labware and module adapters, be sure to add stacking offsets for the adapter to your custom labware definition. Additional Labware Parameters diff --git a/api/docs/v2/modules/thermocycler.rst b/api/docs/v2/modules/thermocycler.rst index 9322e0a96f0..17d57e84292 100644 --- a/api/docs/v2/modules/thermocycler.rst +++ b/api/docs/v2/modules/thermocycler.rst @@ -15,7 +15,7 @@ The examples in this section will use a Thermocycler Module GEN2 loaded as follo .. code-block:: python tc_mod = protocol.load_module(module_name="thermocyclerModuleV2") - plate = tc_mod.load_labware(name="nest_96_wellplate_100ul_pcr_full_skirt") + plate = tc_mod.load_labware(name="opentrons_96_wellplate_200ul_pcr_full_skirt") .. versionadded:: 2.13 @@ -139,6 +139,70 @@ However, this code would generate 60 lines in the protocol's run log, while exec .. versionadded:: 2.0 +Auto-sealing Lids +================= + +Starting in robot software version 8.2.0, you can use the Opentrons Tough PCR Auto-sealing Lid to reduce evaporation on the Thermocycler. The auto-sealing lids are designed for automated use with the Flex Gripper, although you can move them manually if needed. They also work with the Opentrons Flex Deck Riser adapter, which keeps lids away from the unsterilized deck and provides better access for the gripper. + +Use the following API load names for the auto-sealing lid and deck riser: + +.. list-table:: + :header-rows: 1 + + * - Labware + - API load name + * - Opentrons Tough PCR Auto-sealing Lid + - ``opentrons_tough_pcr_auto_sealing_lid`` + * - Opentrons Flex Deck Riser + - ``opentrons_flex_deck_riser`` + +Load the riser directly onto the deck with :py:meth:`.ProtocolContext.load_adapter`. Load the auto-sealing lid onto a compatible location (the deck, the riser, or another lid) with the appropriate ``load_labware()`` method. You can create a stack of up to five auto-sealing lids. If you try to stack more than five lids, the API will raise an error. + +Setting up the riser and preparing a lid to use on the Thermocycler generally consists of the following steps: + + 1. Load the riser on the deck. + 2. Load the lids onto the adapter. + 3. Load or move a PCR plate onto the Thermocycler. + 4. Move a lid onto the PCR plate. + 5. Close the Thermocycler. + +The following code sample shows how to perform these steps, using the riser and three auto-sealing lids. In a full protocol, you would likely have additional steps, such as pipetting to or from the PCR plate. + +.. code-block:: python + + # load riser + riser = protocol.load_adapter( + load_name="opentrons_flex_deck_riser", location="A2" + ) + + # load three lids + lid_1 = riser.load_labware("opentrons_tough_pcr_auto_sealing_lid") + lid_2 = lid_1.load_labware("opentrons_tough_pcr_auto_sealing_lid") + lid_3 = lid_2.load_labware("opentrons_tough_pcr_auto_sealing_lid") + + # load plate on Thermocycler + plate = protocol.load_labware( + load_name="opentrons_96_wellplate_200ul_pcr_full_skirt", location=tc_mod + ) + + # move lid to PCR plate + protocol.move_labware(labware=lid_3, new_location=plate, use_gripper=True) + + # close Thermocycler + tc_mod.close_lid() + +.. warning:: + When using the auto-sealing lids, `do not` affix a rubber automation seal to the inside of the Thermocycler lid. The Thermocycler will not close properly. + +When you're finished with a lid, use the gripper to dispose of it in either the waste chute or a trash bin:: + + tc_mod.open_lid() + protocol.move_labware(labware=lid_3, new_location=trash, use_gripper=True) + +.. versionadded:: 2.16 + :py:class:`.TrashBin` and :py:class:`.WasteChute` objects can accept lids. + +You can then move the PCR plate off of the Thermocycler. The Flex Gripper can't move a plate that has a lid on top of it. Always move the lid first, then the plate. Changes with the GEN2 Thermocycler Module ========================================= diff --git a/api/docs/v2/new_examples.rst b/api/docs/v2/new_examples.rst index 28490e03135..42dbf92fd8d 100644 --- a/api/docs/v2/new_examples.rst +++ b/api/docs/v2/new_examples.rst @@ -284,7 +284,7 @@ When used in a protocol, loops automate repetitive steps such as aspirating and # etc... # range() starts at 0 and stops before 8, creating a range of 0-7 for i in range(8): - pipette.distribute(200, reservoir.wells()[i], plate.rows()[i]) + pipette.distribute(20, reservoir.wells()[i], plate.rows()[i]) .. tab:: OT-2 @@ -315,7 +315,7 @@ When used in a protocol, loops automate repetitive steps such as aspirating and # etc... # range() starts at 0 and stops before 8, creating a range of 0-7 for i in range(8): - p300.distribute(200, reservoir.wells()[i], plate.rows()[i]) + p300.distribute(20, reservoir.wells()[i], plate.rows()[i]) Notice here how Python's :py:class:`range` class (e.g., ``range(8)``) determines how many times the code loops. Also, in Python, a range of numbers is *exclusive* of the end value and counting starts at 0, not 1. For the Corning 96-well plate used here, this means well A1=0, B1=1, C1=2, and so on to the last well in the row, which is H1=7. @@ -383,7 +383,7 @@ Opentrons electronic pipettes can do some things that a human cannot do with a p location=3) p300 = protocol.load_instrument( instrument_name="p300_single", - mount="right", + mount="left", tip_racks=[tiprack_1]) p300.pick_up_tip() @@ -442,13 +442,13 @@ This protocol dispenses diluent to all wells of a Corning 96-well plate. Next, i source = reservoir.wells()[i] row = plate.rows()[i] - # transfer 30 µL of source to first well in column - pipette.transfer(30, source, row[0], mix_after=(3, 25)) + # transfer 30 µL of source to first well in column + pipette.transfer(30, source, row[0], mix_after=(3, 25)) - # dilute the sample down the column - pipette.transfer( - 30, row[:11], row[1:], - mix_after=(3, 25)) + # dilute the sample down the column + pipette.transfer( + 30, row[:11], row[1:], + mix_after=(3, 25)) .. tab:: OT-2 @@ -474,7 +474,7 @@ This protocol dispenses diluent to all wells of a Corning 96-well plate. Next, i location=4) p300 = protocol.load_instrument( instrument_name="p300_single", - mount="right", + mount="left", tip_racks=[tiprack_1, tiprack_2]) # Dispense diluent p300.distribute(50, reservoir["A12"], plate.wells()) @@ -483,16 +483,15 @@ This protocol dispenses diluent to all wells of a Corning 96-well plate. Next, i for i in range(8): # save the source well and destination column to variables source = reservoir.wells()[i] - source = reservoir.wells()[i] row = plate.rows()[i] - # transfer 30 µL of source to first well in column - p300.transfer(30, source, row[0], mix_after=(3, 25)) + # transfer 30 µL of source to first well in column + p300.transfer(30, source, row[0], mix_after=(3, 25)) - # dilute the sample down the column - p300.transfer( - 30, row[:11], row[1:], - mix_after=(3, 25)) + # dilute the sample down the column + p300.transfer( + 30, row[:11], row[1:], + mix_after=(3, 25)) Notice here how the code sample loops through the rows and uses slicing to distribute the diluent. For information about these features, see the Loops and Air Gaps examples above. See also, the :ref:`tutorial-commands` section of the Tutorial. diff --git a/api/docs/v2/new_modules.rst b/api/docs/v2/new_modules.rst index 956a2bc7989..594ceca3867 100644 --- a/api/docs/v2/new_modules.rst +++ b/api/docs/v2/new_modules.rst @@ -8,6 +8,7 @@ Hardware Modules .. toctree:: modules/setup + modules/absorbance_plate_reader modules/heater_shaker modules/magnetic_block modules/magnetic_module @@ -17,13 +18,14 @@ Hardware Modules Hardware modules are powered and unpowered deck-mounted peripherals. The Flex and OT-2 are aware of deck-mounted powered modules when they're attached via a USB connection and used in an uploaded protocol. The robots do not know about unpowered modules until you use one in a protocol and upload it to the Opentrons App. -Powered modules include the Heater-Shaker Module, Magnetic Module, Temperature Module, and Thermocycler Module. The 96-well Magnetic Block is an unpowered module. +Powered modules include the Absorbance Plate Reader Module, Heater-Shaker Module, Magnetic Module, Temperature Module, and Thermocycler Module. The 96-well Magnetic Block is an unpowered module. Pages in this section of the documentation cover: - :ref:`Setting up modules and their labware `. - Working with the module contexts for each type of module. + - :ref:`Absorbance Plate Reader Module ` - :ref:`Heater-Shaker Module ` - :ref:`Magnetic Block ` - :ref:`Magnetic Module ` diff --git a/api/docs/v2/new_protocol_api.rst b/api/docs/v2/new_protocol_api.rst index a71ad5cf4a2..d7428799fe0 100644 --- a/api/docs/v2/new_protocol_api.rst +++ b/api/docs/v2/new_protocol_api.rst @@ -28,7 +28,7 @@ Labware ======= .. autoclass:: opentrons.protocol_api.Labware :members: - :exclude-members: next_tip, use_tips, previous_tip, return_tips + :exclude-members: next_tip, use_tips, previous_tip, return_tips, load_empty, load_liquid, load_liquid_by_well .. The trailing ()s at the end of TrashBin and WasteChute here hide the __init__() @@ -53,29 +53,53 @@ Wells and Liquids Modules ======= +Absorbance Plate Reader +----------------------- + +.. autoclass:: opentrons.protocol_api.AbsorbanceReaderContext + :members: + :exclude-members: broker, geometry, load_labware_object, load_adapter, load_adapter_from_definition + :inherited-members: + + +Heater-Shaker +------------- + .. autoclass:: opentrons.protocol_api.HeaterShakerContext :members: :exclude-members: broker, geometry, load_labware_object :inherited-members: +Magnetic Block +-------------- + .. autoclass:: opentrons.protocol_api.MagneticBlockContext :members: :exclude-members: broker, geometry, load_labware_object :inherited-members: +Magnetic Module +--------------- + .. autoclass:: opentrons.protocol_api.MagneticModuleContext :members: :exclude-members: calibrate, broker, geometry, load_labware_object :inherited-members: +Temperature Module +------------------ + .. autoclass:: opentrons.protocol_api.TemperatureModuleContext :members: :exclude-members: start_set_temperature, await_temperature, broker, geometry, load_labware_object :inherited-members: +Thermocycler +------------ + .. autoclass:: opentrons.protocol_api.ThermocyclerContext :members: - :exclude-members: total_step_count, current_cycle_index, total_cycle_count, hold_time, ramp_rate, current_step_index, broker, geometry, load_labware_object + :exclude-members: total_step_count, current_cycle_index, total_cycle_count, hold_time, ramp_rate, current_step_index, broker, geometry, load_labware_object, load_adapter, load_adapter_from_definition :inherited-members: diff --git a/api/docs/v2/parameters/use_case_cherrypicking.rst b/api/docs/v2/parameters/use_case_cherrypicking.rst index 75d2345edde..e721aa75816 100644 --- a/api/docs/v2/parameters/use_case_cherrypicking.rst +++ b/api/docs/v2/parameters/use_case_cherrypicking.rst @@ -123,9 +123,9 @@ The entire start of the ``run()`` function, including a pipette and fixed labwar instrument_name="flex_1channel_1000", mount="left", tip_racks=[tiprack] + ) # load trash bin trash = protocol.load_trash_bin("A3") - ) # load destination plate in deck slot C2 dest_plate = protocol.load_labware( load_name="opentrons_96_wellplate_200ul_pcr_full_skirt", @@ -174,4 +174,4 @@ The last piece of information needed is the destination well. We take the index With all the information gathered and stored in variables, all that's left is to pass that information as the arguments of ``transfer()``. With our example file, this will execute three transfers. By using a different CSV at run time, this code could complete up to 96 transfers (at which point it would run out of both tips and destination wells). -For more complex transfer behavior — such as adjusting location within the well — you could extend the CSV format and the associated code to work with additional data. And check out the `verified cherrypicking protocol `_ in the Opentrons Protocol Library for further automation based on CSV data, including loading different types of plates, automatically loading tip racks, and more. \ No newline at end of file +For more complex transfer behavior — such as adjusting location within the well — you could extend the CSV format and the associated code to work with additional data. And check out the `verified cherrypicking protocol `_ in the Opentrons Protocol Library for further automation based on CSV data, including loading different types of plates, automatically loading tip racks, and more. diff --git a/api/docs/v2/parameters/use_case_sample_count.rst b/api/docs/v2/parameters/use_case_sample_count.rst index 15933752592..d7ce6529e48 100644 --- a/api/docs/v2/parameters/use_case_sample_count.rst +++ b/api/docs/v2/parameters/use_case_sample_count.rst @@ -166,7 +166,7 @@ Now we'll bring sample count into consideration as we :ref:`load the liquids ` instead. Automatic liquid presence detection is disabled by default. Pipette Compatibility --------------------- Liquid presence detection works with Flex 1-, 8-, and 96-channel pipettes only. 1-channel pipettes have one pressure sensor. The 8-channel pipette pressure sensors are on channels 1 and 8 (positions A1 and H1). The 96-channel pipette pressure sensors are on channels 1 and 96 (positions A1 and H12). Other channels on multi-channel pipettes do not have sensors and cannot detect liquid. -.. add text with link to revised pipette sensor section in manual? - Enabling Globally ----------------- @@ -245,9 +247,11 @@ To automatically use liquid presence detection, add the optional Boolean argumen ) .. note:: - Accurate liquid detection requires fresh, dry pipette tips. Protocols using this feature must discard used tips after an aspirate/dispense cycle and pick up new tips before the next cycle. The API will raise an error if liquid detection is active and your protocol attempts to reuse a pipette tip or if the robot thinks the tip is wet. + Accurate liquid detection requires fresh, dry pipette tips. Protocols using this feature must discard used tips after an aspirate/dispense cycle and pick up new tips before the next cycle. :ref:`Complex commands ` may include aspirate steps after a tip is already wet. When global liquid detection is enabled, use :ref:`building block commands ` to ensure that your protocol picks up a tip immediately before aspiration. + + The API will not raise an error during liquid detection if a tip is empty but wet. It will raise an error if liquid detection is active and your protocol attempts to aspirate with liquid in the tip. -Let's take a look at how all this works. First, tell the robot to pick up a clean tip, aspirate 100 µL from a reservoir, and dispense that volume into a well plate. +Let's take a look at how all this works. With automatic liquid detection enabled, tell the robot to pick up a clean tip, aspirate 100 µL from a reservoir, and dispense that volume into a well plate: .. code-block:: python diff --git a/api/docs/v2/pipettes/partial_tip_pickup.rst b/api/docs/v2/pipettes/partial_tip_pickup.rst index e799d25554f..719262760b9 100644 --- a/api/docs/v2/pipettes/partial_tip_pickup.rst +++ b/api/docs/v2/pipettes/partial_tip_pickup.rst @@ -183,10 +183,10 @@ The ``start`` parameter sets the first and only nozzle used in the configuration - | Back to front, left to right | (A1 through H1, A2 through H2, …) -Since they follow the same pickup order as a single-channel pipette, Opentrons recommends using the following configurations: +.. warning:: + In certain conditions, tips in adjacent columns may cling to empty nozzles during single-tip pickup. You can avoid this by overriding automatic tip tracking to pick up tips row by row, rather than column by column. The code sample below demonstrates how to pick up tips this way. -- For 8-channel pipettes, ``start="H1"``. -- For 96-channel pipettes, ``start="H12"``. + However, as with all partial tip layouts, be careful that you don't place the pipette in a position where it overlaps more tips than intended. Here is the start of a protocol that imports the ``SINGLE`` and ``ALL`` layout constants, loads an 8-channel pipette, and sets it to pick up a single tip. @@ -210,21 +210,22 @@ Here is the start of a protocol that imports the ``SINGLE`` and ``ALL`` layout c ) pipette.configure_nozzle_layout( style=SINGLE, - start="H12", - tip_racks=[partial_rack] + start="H1" ) .. versionadded:: 2.20 -Since this configuration uses ``start="H12"``, it will pick up tips in the usual order:: +To pick up tips row by row, first construct a list of all wells in the tip rack ordered from A1, A2 … H11, H12. One way to do this is to use :py:func:`sum` to flatten the list of lists returned by :py:meth:`.Labware.rows`:: - pipette.pick_up_tip() # picks up A1 from tip rack - pipette.drop_tip() - pipette.pick_up_tip() # picks up B1 from tip rack + tips_by_row = sum(partial_rack.rows(), []) -.. note:: +Then ``pop`` items from the front of the list (index 0) and pass them as the ``location`` of :py:meth:`.pick_up_tip`:: - You can pick up tips row by row, rather than column by column, by specifying a location for :py:meth:`.pick_up_tip` each time you use it in ``SINGLE`` configuration. However, as with all partial tip layouts, be careful that you don't place the pipette in a position where it overlaps more tips than intended. + # pick up A1 from tip rack + pipette.pick_up_tip(location=tips_by_row.pop(0)) + pipette.drop_tip() + # pick up A2 from tip rack + pipette.pick_up_tip(location=tips_by_row.pop(0)) Partial Column Layout @@ -232,7 +233,7 @@ Partial Column Layout Partial column pickup is available on 8-channel pipettes only. Partial columns contain 2 to 7 consecutive tips in a single column. The pipette always picks up partial columns with its frontmost nozzles (``start="H1"``). -To specify the number of tips to pick up, add the ``end`` parameter when calling :py:meth:`.configure_nozzle_layout`. Use the chart below to determine the end row (G through B) for your desired number of tips. The end column should be the same as your start column (1 or 12). +To specify the number of tips to pick up, add the ``end`` parameter when calling :py:meth:`.configure_nozzle_layout`. Use the chart below to determine the ending nozzle (G1 through B1) for your desired number of tips. .. list-table:: :stub-columns: 1 @@ -244,16 +245,21 @@ To specify the number of tips to pick up, add the ``end`` parameter when calling - 5 - 6 - 7 - * - ``end`` row - - G - - F - - E - - D - - C - - B + * - ``end`` nozzle + - G1 + - F1 + - E1 + - D1 + - C1 + - B1 When picking up 3, 5, 6, or 7 tips, extra tips will be left at the front of each column. You can use these tips with a different nozzle configuration, or you can manually re-rack them at the end of your protocol for future use. +.. warning:: + In certain conditions, tips in adjacent columns may cling to empty nozzles during partial-column pickup. You can avoid this by overriding automatic tip tracking to pick up tips row by row, rather than column by column. The code sample below demonstrates how to pick up tips this way. + + However, as with all partial tip layouts, be careful that you don't place the pipette in a position where it overlaps more tips than intended. + Here is the start of a protocol that imports the ``PARTIAL_COLUMN`` and ``ALL`` layout constants, loads an 8-channel pipette, and sets it to pick up four tips: .. code-block:: python @@ -274,21 +280,24 @@ Here is the start of a protocol that imports the ``PARTIAL_COLUMN`` and ``ALL`` pipette.configure_nozzle_layout( style=PARTIAL_COLUMN, start="H1", - end="E1", - tip_racks=[partial_rack] + end="E1" ) .. versionadded:: 2.20 -This configuration will pick up tips from the back half of column 1, then the front half of column 1, then the back half of column 2, and so on:: +When pipetting in partial column configuration, remember that *the frontmost channel of the pipette is its primary channel*. To pick up tips across the back half of the rack, then across the front half of the rack, construct a list of that includes all and only the wells in row D and row H:: - pipette.pick_up_tip() # picks up A1-D1 from tip rack - pipette.drop_tip() - pipette.pick_up_tip() # picks up E1-H1 from tip rack + tips_by_row = partial_rack.rows_by_name()["D"] + partial_rack.rows_by_name()["H"] + +Then ``pop`` items from the front of the list (index 0) and pass them as the ``location`` of :py:meth:`.pick_up_tip`:: + + # pick up A1-D1 from tip rack + pipette.pick_up_tip(location=tips_by_row.pop(0)) pipette.drop_tip() - pipette.pick_up_tip() # picks up A2-D2 from tip rack + # pick up A2-D2 from tip rack + pipette.pick_up_tip(location=tips_by_row.pop(0)) -When handling liquids in partial column configuration, remember that *the frontmost channel of the pipette is its primary channel*. For example, to use the same configuration as above to transfer liquid from wells A1–D1 to wells A2–D2 on a plate, you must use the wells in row D as the source and destination targets:: +To use the same configuration as above to transfer liquid from wells A1–D1 to wells A2–D2 on a plate, you must use the wells in row D as the source and destination targets:: # pipette in 4-nozzle partial column layout pipette.transfer( @@ -361,14 +370,58 @@ This keeps tip tracking consistent across each type of pickup. And it reduces th Tip Pickup and Conflicts ======================== -During partial tip pickup, pipettes move into spaces above adjacent slots. To avoid crashes, the API prevents you from performing partial tip pickup when there is tall labware in these spaces. The current nozzle layout determines which labware can safely occupy adjacent slots. +During partial tip pickup, the pipette moves into spaces above adjacent slots. To avoid crashes, the API prevents you from performing partial tip pickup in locations where the pipette could collide with the outer edges of the robot or labware in the working area. The current nozzle layout, pickup or pipetting location, and adjacent labware determine whether a particular pipetting action is safe to perform. -The API will raise errors for potential labware crashes when using a partial nozzle configuration. Nevertheless, it's a good idea to do the following when working with partial tip pickup: +The API will raise errors for potential crashes when using a partial nozzle configuration. Nevertheless, it's a good idea to do the following when working with partial tip pickup: - Plan your deck layout carefully. Make a diagram and visualize everywhere the pipette will travel. - - Simulate your protocol and compare the run preview to your expectations of where the pipette will travel. + - Simulate your protocol and compare the output to your expectations of where the pipette will travel. - Perform a dry run with only tip racks on the deck. Have the Emergency Stop Pendant handy in case you see an impending crash. +Deck Extents +------------ + +When using partial nozzle configurations around the back, right, and front edges of the deck, there are limitations on how far the pipette can move beyond the outer edge of the deck slot. The API will raise an error if you try to pipette beyond these outer `extents` of the working area. + +.. tip:: + There are no extents-related limitations on slots B1, B2, C1, and C2. When performing partial pickup and pipetting in these slots, you only have to consider :ref:`possible labware conflicts `. + +One way to think of deck extents is in terms of where you can pick up tips or pipette to a 96-well plate loaded in a given slot. These limitations only apply when using a layout that places the pipette further towards the windows of the robot than an ``ALL`` layout would. For example, using a ``ROW`` layout with the frontmost nozzles of the 96-channel pipette, it will never move farther forward than the H row of a labware in slots D1–D3. But using a ``ROW`` layout with the backmost nozzles would bring it farther forward — it could collide with the front window, except that the API prevents it. + +The following table summarizes the limitations in place along each side of the deck. + +.. list-table:: + :header-rows: 1 + + * - Deck slots + - Nozzle configuration + - Inaccessible wells + * - A1–D1 (left edge) + - Rightmost column + - None (all wells accessible) + * - A1–A3 (back edge) + - Frontmost row + - Rows A–G + * - A3–D3 (right edge) + - Leftmost column + - Columns 11–12 + * - D1–D3 (front edge) + - Backmost row + - Rows F–H + +To visualize these limitations, the below deck map shades all wells that have a single limitation in light blue, and all wells that have two limitations in dark blue. + +.. image:: ../../img/partial-pickup-deck-extents.png + +Multiple limitations occur when you use a ``SINGLE`` configuration that uses the innermost corner nozzle, with respect to the pipette's position on the deck. For example, using nozzle A1 on the 96-channel pipette has multiple limitations in slot D3. + +Additionally, column A of plates loaded on a Thermocycler Module is inaccessible by the rightmost nozzles of the 96-channel pipette. Although the API treats such plates as being in slot A1, the physical location of a plate on the Thermocycler is slightly further left than a plate loaded directly on the slot. + +.. _partial-labware-conflicts: + +Arranging Labware +----------------- + For column pickup, Opentrons recommends using the nozzles in column 12 of the pipette:: pipette.configure_nozzle_layout( diff --git a/api/docs/v2/versioning.rst b/api/docs/v2/versioning.rst index 8a4eae39718..935011f61dd 100644 --- a/api/docs/v2/versioning.rst +++ b/api/docs/v2/versioning.rst @@ -68,7 +68,7 @@ The maximum supported API version for your robot is listed in the Opentrons App If you upload a protocol that specifies a higher API level than the maximum supported, your robot won't be able to analyze or run your protocol. You can increase the maximum supported version by updating your robot software and Opentrons App. -Opentrons robots running the latest software (8.0.0) support the following version ranges: +Opentrons robots running the latest software (8.2.0) support the following version ranges: * **Flex:** version 2.15–|apiLevel|. * **OT-2:** versions 2.0–|apiLevel|. @@ -84,6 +84,8 @@ This table lists the correspondence between Protocol API versions and robot soft +-------------+------------------------------+ | API Version | Introduced in Robot Software | +=============+==============================+ +| 2.21 | 8.2.0 | ++-------------+------------------------------+ | 2.20 | 8.0.0 | +-------------+------------------------------+ | 2.19 | 7.3.1 | @@ -134,9 +136,17 @@ This table lists the correspondence between Protocol API versions and robot soft Changes in API Versions ======================= +Version 2.21 +------------ +- Adds :py:class:`.AbsorbanceReaderContext` to support the :ref:`Absorbance Plate Reader Module `. Use the load name ``absorbanceReaderV1`` with :py:meth:`.ProtocolContext.load_module` to add an Absorbance Plate Reader to a protocol. +- :ref:`Liquid presence detection ` now only checks on the first aspiration of the :py:meth:`.mix` cycle. +- Improved the run log output of :py:meth:`.ThermocyclerContext.execute_profile`. + Version 2.20 ------------ +- Detect liquid presence within a well. The :py:meth:`.InstrumentContext.detect_liquid_presence()` and :py:meth:`.InstrumentContext.require_liquid_presence()` building block commands check for liquid any point in your protocol. You can also :ref:`enable liquid presence detection ` for all aspirations when loading a pipette, although this will add significant time to your protocol. +- Define CSV runtime parameters and use their contents in a protocol with new :ref:`data manipulation methods `. See the :ref:`cherrypicking use case ` for a full example. - :py:meth:`.configure_nozzle_layout` now accepts row, single, and partial column layout constants. See :ref:`partial-tip-pickup`. - You can now call :py:obj:`.ProtocolContext.define_liquid()` without supplying a ``description`` or ``display_color``. diff --git a/api/pytest.ini b/api/pytest.ini index a8e3bbb1933..61288b3f3c1 100644 --- a/api/pytest.ini +++ b/api/pytest.ini @@ -5,3 +5,9 @@ markers = ot3_only: Test only functions using the OT3 hardware addopts = --color=yes --strict-markers asyncio_mode = auto + +# TODO this should be looked into being removed upon updating the Decoy library. The purpose of this warning is to +# catch missing attributes, but it raises for any property referenced in a test which accounts for about ~250 warnings +# which aren't serving any useful purpose and obscure other warnings. +filterwarnings = + ignore::decoy.warnings.MissingSpecAttributeWarning diff --git a/api/release-notes-internal.md b/api/release-notes-internal.md index 6ddd85c885b..761f1f604f3 100644 --- a/api/release-notes-internal.md +++ b/api/release-notes-internal.md @@ -2,6 +2,24 @@ For more details about this release, please see the full [technical change log][ [technical change log]: https://github.com/Opentrons/opentrons/releases +## Internal Release 2.3.0-alpha.0 + +This internal release, pulled from the `edge` branch, contains features being developed for evo tip functionality. It's for internal testing only. + +## Internal Release 2.2.0-alpha.1 + +This internal release, pulled from the `edge` branch, contains features being developed for 8.2.0. It's for internal testing only. + +## Internal Release 2.2.0-alpha.0 + +This internal release, pulled from the `edge` branch, contains features being developed for 8.2.0. It's for internal testing only. + +## Internal Release 2.1.0-alpha.0 + +This internal release contains features being developed for 8.1.0. It's for internal testing only. + +- Added support for Verdin IMX8MM Rev E and above which changes the CAN base clock from 20Mhz to 40Mhz. + ## Internal Release 2.0.0-alpha.4 This internal release, pulled from the `edge` branch, contains features being developed for 8.0.0. It's for internal testing only. There are no changes to `buildroot`, `ot3-firmware`, or `oe-core` since the last internal release. diff --git a/api/release-notes.md b/api/release-notes.md index 8664eac3f8d..1fdd9a033d9 100644 --- a/api/release-notes.md +++ b/api/release-notes.md @@ -8,6 +8,38 @@ By installing and using Opentrons software, you agree to the Opentrons End-User --- +## Opentrons Robot Software Changes in 8.2.0 + +Welcome to the v8.2.0 release of the Opentrons robot software! This release adds support for the Opentrons Absorbance Plate Reader Module. + +### New Features + +- Create and run Python protocols that use the Opentrons Absorbance Plate Reader. + +### Improved Features + +- Liquid presence detection no longer checks for liquid before every aspiration in a `mix()` command. + +### Bug Fixes + +- Error recovery no longer causes an `AssertionError` when a Python protocol changes the pipette speed. + +### Known Issues + +- You can't downgrade the robot software with an Absorbance Plate Reader attached. Disconnect the module first if you need to downgrade. + +--- + +## Opentrons Robot Software Changes in 8.1.0 + +Welcome to the v8.1.0 release of the Opentrons robot software! + +### Hardware Support + +- Latest production version of Flex robots + +--- + ## Opentrons Robot Software Changes in 8.0.0 Welcome to the v8.0.0 release of the Opentrons robot software! @@ -24,6 +56,12 @@ Welcome to the v8.0.0 release of the Opentrons robot software! - Provides more partial tip pickup configurations. All multi-channel pipettes now support single and partial column pickup, and the Flex 96-channel pipette now supports row pickup. - Improves homing behavior when a Flex protocol completes or is canceled with liquid-filled tips attached to the pipette. +### Known Issues + +- During single-tip or partial-column pickup with a multi-channel pipette, tips in adjacent columns may cling to empty nozzles. Pick up tips row by row, rather than column by column, to avoid this. +- Protocol analysis and `opentrons_simulate` do not raise an error when a protocol tries to detect liquid with a pipette nozzle configuration that doesn't contain a pressure sensor (single-tip pickup with A12 or H1). Avoid using the A12 and H1 nozzles for single-tip pickup if you need to detect liquid presence within wells. +- `opentrons_simulate` describes motion to wells only with respect to the primary channel, regardless of the current pipette nozzle configuration. + --- ## Opentrons Robot Software Changes in 7.5.0 diff --git a/api/src/opentrons/cli/analyze.py b/api/src/opentrons/cli/analyze.py index a2430b282b2..f995266d140 100644 --- a/api/src/opentrons/cli/analyze.py +++ b/api/src/opentrons/cli/analyze.py @@ -23,8 +23,14 @@ ) import logging import sys +import json -from opentrons.protocol_engine.types import RunTimeParameter, EngineStatus +from opentrons.protocol_engine.types import ( + RunTimeParameter, + CSVRuntimeParamPaths, + PrimitiveRunTimeParamValuesType, + EngineStatus, +) from opentrons.protocols.api_support.types import APIVersion from opentrons.protocol_reader import ( ProtocolReader, @@ -47,6 +53,7 @@ LoadedPipette, LoadedModule, Liquid, + LiquidClassRecordWithId, StateSummary, ) from opentrons.protocol_engine.protocol_engine import code_in_error_tree @@ -104,8 +111,22 @@ class _Output: type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR"], case_sensitive=False), default="WARNING", ) +@click.option( + "--rtp-values", + help="Serialized JSON of runtime parameter variable names to values.", + default="{}", + type=str, +) +@click.option( + "--rtp-files", + help="Serialized JSON of runtime parameter variable names to file paths.", + default="{}", + type=str, +) def analyze( files: Sequence[Path], + rtp_values: str, + rtp_files: str, json_output: Optional[IO[bytes]], human_json_output: Optional[IO[bytes]], log_output: str, @@ -125,7 +146,7 @@ def analyze( try: with _capture_logs(log_output, log_level): - sys.exit(run(_analyze, files, outputs, check)) + sys.exit(run(_analyze, files, rtp_values, rtp_files, outputs, check)) except click.ClickException: raise except Exception as e: @@ -194,6 +215,37 @@ def _get_input_files(files_and_dirs: Sequence[Path]) -> List[Path]: return results +def _get_runtime_parameter_values( + serialized_rtp_values: str, +) -> PrimitiveRunTimeParamValuesType: + rtp_values = {} + try: + for variable_name, value in json.loads(serialized_rtp_values).items(): + if not isinstance(value, (bool, int, float, str)): + raise click.BadParameter( + f"Runtime parameter '{value}' is not of allowed type boolean, integer, float or string", + param_hint="--rtp-values", + ) + rtp_values[variable_name] = value + except json.JSONDecodeError as error: + raise click.BadParameter( + f"JSON decode error: {error}", param_hint="--rtp-values" + ) + return rtp_values + + +def _get_runtime_parameter_paths(serialized_rtp_files: str) -> CSVRuntimeParamPaths: + try: + return { + variable_name: Path(path_string) + for variable_name, path_string in json.loads(serialized_rtp_files).items() + } + except json.JSONDecodeError as error: + raise click.BadParameter( + f"JSON decode error: {error}", param_hint="--rtp-files" + ) + + R = TypeVar("R") @@ -238,7 +290,11 @@ def _convert_exc() -> Iterator[EnumeratedError]: ) -async def _do_analyze(protocol_source: ProtocolSource) -> RunResult: +async def _do_analyze( + protocol_source: ProtocolSource, + rtp_values: PrimitiveRunTimeParamValuesType, + rtp_paths: CSVRuntimeParamPaths, +) -> RunResult: orchestrator = await create_simulating_orchestrator( robot_type=protocol_source.robot_type, protocol_config=protocol_source.config @@ -247,8 +303,8 @@ async def _do_analyze(protocol_source: ProtocolSource) -> RunResult: await orchestrator.load( protocol_source=protocol_source, parse_mode=ParseMode.NORMAL, - run_time_param_values=None, - run_time_param_paths=None, + run_time_param_values=rtp_values, + run_time_param_paths=rtp_paths, ) except Exception as error: err_id = "analysis-setup-error" @@ -275,7 +331,10 @@ async def _do_analyze(protocol_source: ProtocolSource) -> RunResult: modules=[], labwareOffsets=[], liquids=[], + wells=[], hasEverEnteredErrorRecovery=False, + files=[], + liquidClasses=[], ), parameters=[], command_annotations=[], @@ -285,9 +344,16 @@ async def _do_analyze(protocol_source: ProtocolSource) -> RunResult: async def _analyze( - files_and_dirs: Sequence[Path], outputs: Sequence[_Output], check: bool + files_and_dirs: Sequence[Path], + rtp_values: str, + rtp_files: str, + outputs: Sequence[_Output], + check: bool, ) -> int: input_files = _get_input_files(files_and_dirs) + parsed_rtp_values = _get_runtime_parameter_values(rtp_values) + rtp_paths = _get_runtime_parameter_paths(rtp_files) + try: protocol_source = await ProtocolReader().read_saved( files=input_files, @@ -296,7 +362,7 @@ async def _analyze( except ProtocolFilesInvalidError as error: raise click.ClickException(str(error)) - analysis = await _do_analyze(protocol_source) + analysis = await _do_analyze(protocol_source, parsed_rtp_values, rtp_paths) return_code = _get_return_code(analysis) if not outputs: @@ -337,6 +403,7 @@ async def _analyze( modules=analysis.state_summary.modules, liquids=analysis.state_summary.liquids, commandAnnotations=analysis.command_annotations, + liquidClasses=analysis.state_summary.liquidClasses, ) _call_for_output_of_kind( @@ -424,5 +491,6 @@ class AnalyzeResults(BaseModel): pipettes: List[LoadedPipette] modules: List[LoadedModule] liquids: List[Liquid] + liquidClasses: List[LiquidClassRecordWithId] errors: List[ErrorOccurrence] commandAnnotations: List[Any] diff --git a/api/src/opentrons/config/__init__.py b/api/src/opentrons/config/__init__.py index a4571521211..71ba78d39b0 100644 --- a/api/src/opentrons/config/__init__.py +++ b/api/src/opentrons/config/__init__.py @@ -202,6 +202,15 @@ class ConfigElement(NamedTuple): " absolute path, it will be used directly. If it is a " "relative path it will be relative to log_dir", ), + ConfigElement( + "sensor_log_file", + "Sensor Log File", + Path("logs") / "sensor.log", + ConfigElementType.FILE, + "The location of the file to save sensor logs to. If this is an" + " absolute path, it will be used directly. If it is a " + "relative path it will be relative to log_dir", + ), ConfigElement( "serial_log_file", "Serial Log File", diff --git a/api/src/opentrons/config/advanced_settings.py b/api/src/opentrons/config/advanced_settings.py index 812ca73a661..44cf5c0fcc4 100644 --- a/api/src/opentrons/config/advanced_settings.py +++ b/api/src/opentrons/config/advanced_settings.py @@ -222,6 +222,17 @@ class Setting(NamedTuple): robot_type=[RobotTypeEnum.OT2, RobotTypeEnum.FLEX], internal_only=True, ), + SettingDefinition( + _id="allowLiquidClasses", + title="Allow the use of liquid classes", + description=( + "Do not enable." + " This is an Opentrons internal setting to allow using in-development" + " liquid classes." + ), + robot_type=[RobotTypeEnum.OT2, RobotTypeEnum.FLEX], + internal_only=True, + ), ] @@ -715,6 +726,16 @@ def _migrate34to35(previous: SettingsMap) -> SettingsMap: return newmap +def _migrate35to36(previous: SettingsMap) -> SettingsMap: + """Migrate to version 36 of the feature flags file. + + - Adds the allowLiquidClasses config element. + """ + newmap = {k: v for k, v in previous.items()} + newmap["allowLiquidClasses"] = None + return newmap + + _MIGRATIONS = [ _migrate0to1, _migrate1to2, @@ -751,6 +772,7 @@ def _migrate34to35(previous: SettingsMap) -> SettingsMap: _migrate32to33, _migrate33to34, _migrate34to35, + _migrate35to36, ] """ List of all migrations to apply, indexed by (version - 1). See _migrate below diff --git a/api/src/opentrons/config/defaults_ot3.py b/api/src/opentrons/config/defaults_ot3.py index 08b86f16c95..53fab18392c 100644 --- a/api/src/opentrons/config/defaults_ot3.py +++ b/api/src/opentrons/config/defaults_ot3.py @@ -15,7 +15,6 @@ LiquidProbeSettings, ZSenseSettings, EdgeSenseSettings, - OutputOptions, ) @@ -27,13 +26,11 @@ plunger_speed=15, plunger_impulse_time=0.2, sensor_threshold_pascals=15, - output_option=OutputOptions.sync_buffer_to_csv, aspirate_while_sensing=False, z_overlap_between_passes_mm=0.1, plunger_reset_offset=2.0, samples_for_baselining=20, sample_time_sec=0.004, - data_files={InstrumentProbeType.PRIMARY: "/data/pressure_sensor_data.csv"}, ) DEFAULT_CALIBRATION_SETTINGS: Final[OT3CalibrationSettings] = OT3CalibrationSettings( @@ -43,7 +40,6 @@ max_overrun_distance_mm=5.0, speed_mm_per_s=1.0, sensor_threshold_pf=3.0, - output_option=OutputOptions.sync_only, ), ), edge_sense=EdgeSenseSettings( @@ -54,7 +50,6 @@ max_overrun_distance_mm=0.5, speed_mm_per_s=1, sensor_threshold_pf=3.0, - output_option=OutputOptions.sync_only, ), search_initial_tolerance_mm=12.0, search_iteration_limit=8, @@ -80,6 +75,7 @@ DEFAULT_GRIPPER_MOUNT_OFFSET: Final[Offset] = (84.55, -12.75, 93.85) DEFAULT_SAFE_HOME_DISTANCE: Final = 5 DEFAULT_CALIBRATION_AXIS_MAX_SPEED: Final = 30 +DEFAULT_EMULSIFYING_PIPETTE_AXIS_MAX_SPEED: Final = 90 DEFAULT_MAX_SPEEDS: Final[ByGantryLoad[Dict[OT3AxisKind, float]]] = ByGantryLoad( high_throughput={ @@ -195,23 +191,6 @@ ) -def _build_output_option_with_default( - from_conf: Any, default: OutputOptions -) -> OutputOptions: - if from_conf is None: - return default - else: - if isinstance(from_conf, OutputOptions): - return from_conf - else: - try: - enumval = OutputOptions[from_conf] - except KeyError: # not an enum entry - return default - else: - return enumval - - def _build_log_files_with_default( from_conf: Any, default: Optional[Dict[InstrumentProbeType, str]], @@ -316,24 +295,12 @@ def _build_default_cap_pass( sensor_threshold_pf=from_conf.get( "sensor_threshold_pf", default.sensor_threshold_pf ), - output_option=from_conf.get("output_option", default.output_option), ) def _build_default_liquid_probe( from_conf: Any, default: LiquidProbeSettings ) -> LiquidProbeSettings: - output_option = _build_output_option_with_default( - from_conf.get("output_option", None), default.output_option - ) - data_files: Optional[Dict[InstrumentProbeType, str]] = None - if ( - output_option is OutputOptions.sync_buffer_to_csv - or output_option is OutputOptions.stream_to_csv - ): - data_files = _build_log_files_with_default( - from_conf.get("data_files", None), default.data_files - ) return LiquidProbeSettings( mount_speed=from_conf.get("mount_speed", default.mount_speed), plunger_speed=from_conf.get("plunger_speed", default.plunger_speed), @@ -343,7 +310,6 @@ def _build_default_liquid_probe( sensor_threshold_pascals=from_conf.get( "sensor_threshold_pascals", default.sensor_threshold_pascals ), - output_option=from_conf.get("output_option", default.output_option), aspirate_while_sensing=from_conf.get( "aspirate_while_sensing", default.aspirate_while_sensing ), @@ -357,7 +323,6 @@ def _build_default_liquid_probe( "samples_for_baselining", default.samples_for_baselining ), sample_time_sec=from_conf.get("sample_time_sec", default.sample_time_sec), - data_files=data_files, ) diff --git a/api/src/opentrons/config/feature_flags.py b/api/src/opentrons/config/feature_flags.py index 7eb40721511..2164e66f90a 100644 --- a/api/src/opentrons/config/feature_flags.py +++ b/api/src/opentrons/config/feature_flags.py @@ -78,3 +78,7 @@ def enable_performance_metrics(robot_type: RobotTypeEnum) -> bool: def oem_mode_enabled() -> bool: return advs.get_setting_with_env_overload("enableOEMMode", RobotTypeEnum.FLEX) + + +def allow_liquid_classes(robot_type: RobotTypeEnum) -> bool: + return advs.get_setting_with_env_overload("allowLiquidClasses", robot_type) diff --git a/api/src/opentrons/config/types.py b/api/src/opentrons/config/types.py index 5a6c67725d0..d35b58578ca 100644 --- a/api/src/opentrons/config/types.py +++ b/api/src/opentrons/config/types.py @@ -1,8 +1,8 @@ from enum import Enum from dataclasses import dataclass, asdict, fields -from typing import Dict, Tuple, TypeVar, Generic, List, cast, Optional +from typing import Dict, Tuple, TypeVar, Generic, List, cast from typing_extensions import TypedDict, Literal -from opentrons.hardware_control.types import OT3AxisKind, InstrumentProbeType +from opentrons.hardware_control.types import OT3AxisKind class AxisDict(TypedDict): @@ -103,25 +103,12 @@ def by_gantry_load( ) -class OutputOptions(int, Enum): - """Specifies where we should report sensor data to during a sensor pass.""" - - stream_to_csv = 0x1 # compile sensor data stream into a csv file, in addition to can_bus_only behavior - sync_buffer_to_csv = 0x2 # collect sensor data on pipette mcu, then stream to robot server and compile into a csv file, in addition to can_bus_only behavior - can_bus_only = ( - 0x4 # stream sensor data over CAN bus, in addition to sync_only behavior - ) - sync_only = 0x8 # trigger pipette sync line upon sensor's detection of something - - @dataclass(frozen=True) class CapacitivePassSettings: prep_distance_mm: float max_overrun_distance_mm: float speed_mm_per_s: float sensor_threshold_pf: float - output_option: OutputOptions - data_files: Optional[Dict[InstrumentProbeType, str]] = None @dataclass(frozen=True) @@ -135,13 +122,11 @@ class LiquidProbeSettings: plunger_speed: float plunger_impulse_time: float sensor_threshold_pascals: float - output_option: OutputOptions aspirate_while_sensing: bool z_overlap_between_passes_mm: float plunger_reset_offset: float samples_for_baselining: int sample_time_sec: float - data_files: Optional[Dict[InstrumentProbeType, str]] @dataclass(frozen=True) diff --git a/api/src/opentrons/drivers/absorbance_reader/abstract.py b/api/src/opentrons/drivers/absorbance_reader/abstract.py index 6dc403fdff9..600497cbd6d 100644 --- a/api/src/opentrons/drivers/absorbance_reader/abstract.py +++ b/api/src/opentrons/drivers/absorbance_reader/abstract.py @@ -1,6 +1,7 @@ from abc import ABC, abstractmethod -from typing import Dict, List, Tuple +from typing import Dict, List, Optional, Tuple from opentrons.drivers.types import ( + ABSMeasurementMode, AbsorbanceReaderLidStatus, AbsorbanceReaderDeviceState, AbsorbanceReaderPlatePresence, @@ -32,11 +33,18 @@ async def get_available_wavelengths(self) -> List[int]: ... @abstractmethod - async def get_single_measurement(self, wavelength: int) -> List[float]: + async def initialize_measurement( + self, + wavelengths: List[int], + mode: ABSMeasurementMode = ABSMeasurementMode.SINGLE, + reference_wavelength: Optional[int] = None, + ) -> None: + """Initialize measurement for the device in single or multi mode for the given wavelengths""" ... @abstractmethod - async def initialize_measurement(self, wavelength: int) -> None: + async def get_measurement(self) -> List[List[float]]: + """Gets one or more measurements based on the current configuration.""" ... @abstractmethod diff --git a/api/src/opentrons/drivers/absorbance_reader/async_byonoy.py b/api/src/opentrons/drivers/absorbance_reader/async_byonoy.py index 6f3ab10afcd..6f405c9af32 100644 --- a/api/src/opentrons/drivers/absorbance_reader/async_byonoy.py +++ b/api/src/opentrons/drivers/absorbance_reader/async_byonoy.py @@ -3,26 +3,29 @@ import re from concurrent.futures.thread import ThreadPoolExecutor from functools import partial -from typing import Optional, List, Dict, Tuple +from typing import Any, Optional, List, Dict, Tuple from .hid_protocol import ( AbsorbanceHidInterface as AbsProtocol, ErrorCodeNames, DeviceStateNames, SlotStateNames, + MeasurementConfig, ) from opentrons.drivers.types import ( AbsorbanceReaderLidStatus, AbsorbanceReaderPlatePresence, AbsorbanceReaderDeviceState, + ABSMeasurementMode, ) from opentrons.drivers.rpi_drivers.types import USBPort from opentrons.hardware_control.modules.errors import AbsorbanceReaderDisconnectedError SN_PARSER = re.compile(r'ATTRS{serial}=="(?P.+?)"') -VERSION_PARSER = re.compile(r"Absorbance (?PV\d+\.\d+\.\d+)") -SERIAL_PARSER = re.compile(r"SN: (?PBYO[A-Z]{3}[0-9]{5})") +# match semver V0.0.0 (old format) or one integer (latest format) +VERSION_PARSER = re.compile(r"(?P(V\d+\.\d+\.\d+|^\d+$))") +SERIAL_PARSER = re.compile(r"(?P(OPT|BYO)[A-Z]{3}[0-9]+)") class AsyncByonoy: @@ -72,13 +75,13 @@ async def create( loop = loop or asyncio.get_running_loop() executor = ThreadPoolExecutor(max_workers=1) - import pybyonoy_device_library as byonoy # type: ignore[import-not-found] + import byonoy_devices as byonoy # type: ignore[import-not-found] interface: AbsProtocol = byonoy device_sn = cls.serial_number_from_port(usb_port.name) found: List[AbsProtocol.Device] = await loop.run_in_executor( - executor=executor, func=byonoy.byonoy_available_devices + executor=executor, func=byonoy.available_devices ) device = cls.match_device_with_sn(device_sn, found) @@ -110,7 +113,7 @@ def __init__( self._loop = loop self._supported_wavelengths: Optional[list[int]] = None self._device_handle: Optional[int] = None - self._current_config: Optional[AbsProtocol.MeasurementConfig] = None + self._current_config: Optional[MeasurementConfig] = None async def open(self) -> bool: """ @@ -121,7 +124,7 @@ async def open(self) -> bool: err, device_handle = await self._loop.run_in_executor( executor=self._executor, - func=partial(self._interface.byonoy_open_device, self._device), + func=partial(self._interface.open_device, self._device), ) self._raise_if_error(err.name, f"Error opening device: {err}") self._device_handle = device_handle @@ -132,7 +135,7 @@ async def close(self) -> None: handle = self._verify_device_handle() await self._loop.run_in_executor( executor=self._executor, - func=partial(self._interface.byonoy_free_device, handle), + func=partial(self._interface.free_device, handle), ) self._device_handle = None @@ -143,7 +146,7 @@ async def is_open(self) -> bool: handle = self._verify_device_handle() return await self._loop.run_in_executor( executor=self._executor, - func=partial(self._interface.byonoy_device_open, handle), + func=partial(self._interface.device_open, handle), ) async def get_device_information(self) -> Dict[str, str]: @@ -151,13 +154,13 @@ async def get_device_information(self) -> Dict[str, str]: handle = self._verify_device_handle() err, device_info = await self._loop.run_in_executor( executor=self._executor, - func=partial(self._interface.byonoy_get_device_information, handle), + func=partial(self._interface.get_device_information, handle), ) self._raise_if_error(err.name, f"Error getting device information: {err}") serial_match = SERIAL_PARSER.match(device_info.sn) - version_match = VERSION_PARSER.match(device_info.version) - serial = serial_match["serial"] if serial_match else "BYOMAA00000" - version = version_match["version"].lower() if version_match else "v0.0.0" + version_match = VERSION_PARSER.search(device_info.version) + serial = serial_match["serial"].strip() if serial_match else "OPTMAA00000" + version = version_match["version"].lower() if version_match else "v0" info = { "serial": serial, "version": version, @@ -170,7 +173,7 @@ async def get_device_status(self) -> AbsorbanceReaderDeviceState: handle = self._verify_device_handle() err, status = await self._loop.run_in_executor( executor=self._executor, - func=partial(self._interface.byonoy_get_device_status, handle), + func=partial(self._interface.get_device_status, handle), ) self._raise_if_error(err.name, f"Error getting device status: {err}") return self.convert_device_state(status.name) @@ -182,11 +185,9 @@ async def update_firmware(self, firmware_file_path: str) -> Tuple[bool, str]: return False, f"Firmware file not found: {firmware_file_path}" err = await self._loop.run_in_executor( executor=self._executor, - func=partial( - self._interface.byonoy_update_device, handle, firmware_file_path - ), + func=partial(self._interface.update_device, handle, firmware_file_path), ) - if err.name != "BYONOY_ERROR_NO_ERROR": + if err.name != "NO_ERROR": return False, f"Byonoy update failed with error: {err}" return True, "" @@ -195,7 +196,7 @@ async def get_device_uptime(self) -> int: handle = self._verify_device_handle() err, uptime = await self._loop.run_in_executor( executor=self._executor, - func=partial(self._interface.byonoy_get_device_uptime, handle), + func=partial(self._interface.get_device_uptime, handle), ) self._raise_if_error(err.name, "Error getting device uptime: ") return uptime @@ -205,7 +206,7 @@ async def get_lid_status(self) -> AbsorbanceReaderLidStatus: handle = self._verify_device_handle() err, lid_info = await self._loop.run_in_executor( executor=self._executor, - func=partial(self._interface.byonoy_get_device_parts_aligned, handle), + func=partial(self._interface.get_device_parts_aligned, handle), ) self._raise_if_error(err.name, f"Error getting lid status: {err}") return ( @@ -217,38 +218,38 @@ async def get_supported_wavelengths(self) -> list[int]: handle = self._verify_device_handle() err, wavelengths = await self._loop.run_in_executor( executor=self._executor, - func=partial( - self._interface.byonoy_abs96_get_available_wavelengths, handle - ), + func=partial(self._interface.abs96_get_available_wavelengths, handle), ) self._raise_if_error(err.name, "Error getting available wavelengths: ") self._supported_wavelengths = wavelengths return wavelengths - async def get_single_measurement(self, wavelength: int) -> List[float]: - """Get a single measurement based on the current configuration.""" + async def get_measurement(self) -> List[List[float]]: + """Gets one or more measurements based on the current configuration.""" handle = self._verify_device_handle() assert ( - self._current_config - and self._current_config.sample_wavelength == wavelength - ) + self._current_config is not None + ), "Cannot get measurement without initializing." + measure_func: Any = self._interface.abs96_single_measure + if isinstance(self._current_config, AbsProtocol.MultiMeasurementConfig): + measure_func = self._interface.abs96_multiple_measure err, measurements = await self._loop.run_in_executor( executor=self._executor, func=partial( - self._interface.byonoy_abs96_single_measure, + measure_func, handle, self._current_config, ), ) - self._raise_if_error(err.name, f"Error getting single measurement: {err}") - return measurements + self._raise_if_error(err.name, f"Error getting measurement: {err}") + return measurements if isinstance(measurements[0], List) else [measurements] # type: ignore async def get_plate_presence(self) -> AbsorbanceReaderPlatePresence: """Get the state of the plate for the reader.""" handle = self._verify_device_handle() err, presence = await self._loop.run_in_executor( executor=self._executor, - func=partial(self._interface.byonoy_get_device_slot_status, handle), + func=partial(self._interface.get_device_slot_status, handle), ) self._raise_if_error(err.name, f"Error getting slot status: {err}") return self.convert_plate_presence(presence.name) @@ -256,40 +257,54 @@ async def get_plate_presence(self) -> AbsorbanceReaderPlatePresence: def _get_supported_wavelengths(self) -> List[int]: handle = self._verify_device_handle() wavelengths: List[int] - err, wavelengths = self._interface.byonoy_abs96_get_available_wavelengths( - handle - ) + err, wavelengths = self._interface.abs96_get_available_wavelengths(handle) self._raise_if_error(err.name, f"Error getting available wavelengths: {err}") self._supported_wavelengths = wavelengths return wavelengths - def _initialize_measurement(self, conf: AbsProtocol.MeasurementConfig) -> None: + def _initialize_measurement(self, conf: MeasurementConfig) -> None: handle = self._verify_device_handle() - err = self._interface.byonoy_abs96_initialize_single_measurement(handle, conf) + if isinstance(conf, AbsProtocol.SingleMeasurementConfig): + err = self._interface.abs96_initialize_single_measurement(handle, conf) + else: + err = self._interface.abs96_initialize_multiple_measurement(handle, conf) self._raise_if_error(err.name, f"Error initializing measurement: {err}") self._current_config = conf - def _set_sample_wavelength(self, wavelength: int) -> AbsProtocol.MeasurementConfig: + def _initialize( + self, + mode: ABSMeasurementMode, + wavelengths: List[int], + reference_wavelength: Optional[int] = None, + ) -> None: if not self._supported_wavelengths: self._get_supported_wavelengths() assert self._supported_wavelengths - if wavelength in self._supported_wavelengths: - conf = self._interface.ByonoyAbs96SingleMeasurementConfig() - conf.sample_wavelength = wavelength - return conf + conf: MeasurementConfig + if set(wavelengths).issubset(self._supported_wavelengths): + if mode == ABSMeasurementMode.SINGLE: + conf = self._interface.Abs96SingleMeasurementConfig() + conf.sample_wavelength = wavelengths[0] or 0 + conf.reference_wavelength = reference_wavelength or 0 + else: + conf = self._interface.Abs96MultipleMeasurementConfig() + conf.sample_wavelengths = wavelengths else: raise ValueError( - f"Unsupported wavelength: {wavelength}, expected: {self._supported_wavelengths}" + f"Unsupported wavelength: {wavelengths}, expected: {self._supported_wavelengths}" ) - - def _initialize(self, wavelength: int) -> None: - conf = self._set_sample_wavelength(wavelength) self._initialize_measurement(conf) - async def initialize(self, wavelength: int) -> None: - """Initialize the device so we can start reading samples from it.""" + async def initialize( + self, + mode: ABSMeasurementMode, + wavelengths: List[int], + reference_wavelength: Optional[int] = None, + ) -> None: + """initialize the device so we can start reading samples from it.""" await self._loop.run_in_executor( - executor=self._executor, func=partial(self._initialize, wavelength) + executor=self._executor, + func=partial(self._initialize, mode, wavelengths, reference_wavelength), ) def _verify_device_handle(self) -> int: @@ -304,12 +319,12 @@ def _raise_if_error( msg: str = "Error occurred: ", ) -> None: if err_name in [ - "BYONOY_ERROR_DEVICE_CLOSED", - "BYONOY_ERROR_DEVICE_COMMUNICATION_FAILURE", - "BYONOY_ERROR_UNSUPPORTED_OPERATION", + "DEVICE_CLOSED", + "DEVICE_COMMUNICATION_FAILURE", + "UNSUPPORTED_OPERATION", ]: raise AbsorbanceReaderDisconnectedError(self._device.sn) - if err_name != "BYONOY_ERROR_NO_ERROR": + if err_name != "NO_ERROR": raise RuntimeError(msg, err_name) @staticmethod diff --git a/api/src/opentrons/drivers/absorbance_reader/driver.py b/api/src/opentrons/drivers/absorbance_reader/driver.py index be6fdaa5c15..5899fef89d0 100644 --- a/api/src/opentrons/drivers/absorbance_reader/driver.py +++ b/api/src/opentrons/drivers/absorbance_reader/driver.py @@ -4,11 +4,14 @@ from typing import Dict, Optional, List, Tuple, TYPE_CHECKING from opentrons.drivers.types import ( + ABSMeasurementMode, AbsorbanceReaderLidStatus, AbsorbanceReaderDeviceState, AbsorbanceReaderPlatePresence, ) -from opentrons.drivers.absorbance_reader.abstract import AbstractAbsorbanceReaderDriver +from opentrons.drivers.absorbance_reader.abstract import ( + AbstractAbsorbanceReaderDriver, +) from opentrons.drivers.rpi_drivers.types import USBPort if TYPE_CHECKING: @@ -60,12 +63,16 @@ async def get_lid_status(self) -> AbsorbanceReaderLidStatus: async def get_available_wavelengths(self) -> List[int]: return await self._connection.get_supported_wavelengths() - async def get_single_measurement(self, wavelength: int) -> List[float]: - # TODO (cb, 08-02-2024): The list of measurements for 96 wells is rotated 180 degrees (well A1 is where well H12 should be) this must be corrected - return await self._connection.get_single_measurement(wavelength) + async def initialize_measurement( + self, + wavelengths: List[int], + mode: ABSMeasurementMode = ABSMeasurementMode.SINGLE, + reference_wavelength: Optional[int] = None, + ) -> None: + await self._connection.initialize(mode, wavelengths, reference_wavelength) - async def initialize_measurement(self, wavelength: int) -> None: - await self._connection.initialize(wavelength) + async def get_measurement(self) -> List[List[float]]: + return await self._connection.get_measurement() async def get_status(self) -> AbsorbanceReaderDeviceState: return await self._connection.get_device_status() diff --git a/api/src/opentrons/drivers/absorbance_reader/hid_protocol.py b/api/src/opentrons/drivers/absorbance_reader/hid_protocol.py index 8816b2e2903..c418a652130 100644 --- a/api/src/opentrons/drivers/absorbance_reader/hid_protocol.py +++ b/api/src/opentrons/drivers/absorbance_reader/hid_protocol.py @@ -1,9 +1,11 @@ from typing import ( Dict, + Optional, Protocol, List, Literal, Tuple, + Union, runtime_checkable, TypeVar, ) @@ -11,28 +13,28 @@ Response = TypeVar("Response") ErrorCodeNames = Literal[ - "BYONOY_ERROR_NO_ERROR", - "BYONOY_ERROR_UNKNOWN_ERROR", - "BYONOY_ERROR_DEVICE_CLOSED", - "BYONOY_ERROR_INVALID_ARGUMENT", - "BYONOY_ERROR_NO_MEMORY", - "BYONOY_ERROR_UNSUPPORTED_OPERATION", - "BYONOY_ERROR_DEVICE_COMMUNICATION_FAILURE", - "BYONOY_ERROR_DEVICE_OPERATION_FAILED", - "BYONOY_ERROR_DEVICE_OPEN_PREFIX", - "BYONOY_ERROR_DEVICE_NOT_FOUND", - "BYONOY_ERROR_DEVICE_TOO_NEW", - "BYONOY_ERROR_DEVICE_ALREADY_OPEN", - "BYONOY_ERROR_FIRMWARE_UPDATE_ERROR_PREFIX", - "BYONOY_ERROR_FIRMWARE_UPDATE_FILE_NOT_FOUND", - "BYONOY_ERROR_FIRMWARE_UPDATE_FILE_NOT_VALID", - "BYONOY_ERROR_FIRMWARE_UPDATE_FAILED", - "BYONOY_ERROR_FILE_ERROR_PREFIX", - "BYONOY_ERROR_FILE_WRITE_ERROR", - "BYONOY_ERROR_MEASUTEMNT_ERROR_PREFIX", - "BYONOY_ERROR_MEASUTEMNT_SLOT_NOT_EMPTY", - "BYONOY_ERROR_NOT_INITIALIZED", - "BYONOY_ERROR_INTERNAL", + "NO_ERROR", + "UNKNOWN_ERROR", + "DEVICE_CLOSED", + "INVALID_ARGUMENT", + "NO_MEMORY", + "UNSUPPORTED_OPERATION", + "DEVICE_COMMUNICATION_FAILURE", + "DEVICE_OPERATION_FAILED", + "DEVICE_OPEN_PREFIX", + "DEVICE_NOT_FOUND", + "DEVICE_TOO_NEW", + "DEVICE_ALREADY_OPEN", + "FIRMWARE_UPDATE_ERROR_PREFIX", + "FIRMWARE_UPDATE_FILE_NOT_FOUND", + "FIRMWARE_UPDATE_FILE_NOT_VALID", + "FIRMWARE_UPDATE_FAILED", + "FILE_ERROR_PREFIX", + "FILE_WRITE_ERROR", + "MEASUTEMNT_ERROR_PREFIX", + "MEASUTEMNT_SLOT_NOT_EMPTY", + "NOT_INITIALIZED", + "INTERNAL", ] SlotStateNames = Literal[ @@ -69,8 +71,13 @@ class SlotState(Protocol): value: int @runtime_checkable - class MeasurementConfig(Protocol): + class SingleMeasurementConfig(Protocol): sample_wavelength: int + reference_wavelength: Optional[int] + + @runtime_checkable + class MultiMeasurementConfig(Protocol): + sample_wavelengths: List[int] @runtime_checkable class DeviceInfo(Protocol): @@ -84,60 +91,71 @@ class DeviceState(Protocol): name: DeviceStateNames value: int - def ByonoyAbs96SingleMeasurementConfig(self) -> MeasurementConfig: + def Abs96SingleMeasurementConfig(self) -> SingleMeasurementConfig: + ... + + def Abs96MultipleMeasurementConfig(self) -> MultiMeasurementConfig: ... - def byonoy_open_device(self, device: Device) -> Tuple[ErrorCode, int]: + def open_device(self, device: Device) -> Tuple[ErrorCode, int]: ... - def byonoy_free_device(self, device_handle: int) -> Tuple[ErrorCode, bool]: + def free_device(self, device_handle: int) -> Tuple[ErrorCode, bool]: ... - def byonoy_device_open(self, device_handle: int) -> bool: + def device_open(self, device_handle: int) -> bool: ... - def byonoy_get_device_information( + def get_device_information( self, device_handle: int ) -> Tuple[ErrorCode, DeviceInfo]: ... - def byonoy_update_device( - self, device_handle: int, firmware_file_path: str - ) -> ErrorCode: + def update_device(self, device_handle: int, firmware_file_path: str) -> ErrorCode: ... - def byonoy_get_device_status( - self, device_handle: int - ) -> Tuple[ErrorCode, DeviceState]: + def get_device_status(self, device_handle: int) -> Tuple[ErrorCode, DeviceState]: ... - def byonoy_get_device_uptime(self, device_handle: int) -> Tuple[ErrorCode, int]: + def get_device_uptime(self, device_handle: int) -> Tuple[ErrorCode, int]: ... - def byonoy_get_device_slot_status( - self, device_handle: int - ) -> Tuple[ErrorCode, SlotState]: + def get_device_slot_status(self, device_handle: int) -> Tuple[ErrorCode, SlotState]: ... - def byonoy_get_device_parts_aligned( - self, device_handle: int - ) -> Tuple[ErrorCode, bool]: + def get_device_parts_aligned(self, device_handle: int) -> Tuple[ErrorCode, bool]: ... - def byonoy_abs96_get_available_wavelengths( + def abs96_get_available_wavelengths( self, device_handle: int ) -> Tuple[ErrorCode, List[int]]: ... - def byonoy_abs96_initialize_single_measurement( - self, device_handle: int, conf: MeasurementConfig + def abs96_initialize_single_measurement( + self, device_handle: int, conf: SingleMeasurementConfig ) -> ErrorCode: ... - def byonoy_abs96_single_measure( - self, device_handle: int, conf: MeasurementConfig + def abs96_initialize_multiple_measurement( + self, device_handle: int, conf: MultiMeasurementConfig + ) -> ErrorCode: + ... + + def abs96_single_measure( + self, device_handle: int, conf: SingleMeasurementConfig ) -> Tuple[ErrorCode, List[float]]: ... - def byonoy_available_devices(self) -> List[Device]: + def abs96_multiple_measure( + self, device_handle: int, conf: MultiMeasurementConfig + ) -> Tuple[ErrorCode, List[List[float]]]: + ... + + def available_devices(self) -> List[Device]: ... + + +MeasurementConfig = Union[ + AbsorbanceHidInterface.SingleMeasurementConfig, + AbsorbanceHidInterface.MultiMeasurementConfig, +] diff --git a/api/src/opentrons/drivers/absorbance_reader/simulator.py b/api/src/opentrons/drivers/absorbance_reader/simulator.py index 6a2a0aa6228..c4d7af2c1eb 100644 --- a/api/src/opentrons/drivers/absorbance_reader/simulator.py +++ b/api/src/opentrons/drivers/absorbance_reader/simulator.py @@ -2,6 +2,7 @@ from opentrons.util.async_helpers import ensure_yield from opentrons.drivers.types import ( + ABSMeasurementMode, AbsorbanceReaderLidStatus, AbsorbanceReaderDeviceState, AbsorbanceReaderPlatePresence, @@ -54,11 +55,16 @@ async def get_available_wavelengths(self) -> List[int]: return [450, 570, 600, 650] @ensure_yield - async def get_single_measurement(self, wavelength: int) -> List[float]: - return [0.0] + async def get_measurement(self) -> List[List[float]]: + return [[0.0]] @ensure_yield - async def initialize_measurement(self, wavelength: int) -> None: + async def initialize_measurement( + self, + wavelengths: List[int], + mode: ABSMeasurementMode = ABSMeasurementMode.SINGLE, + reference_wavelength: Optional[int] = None, + ) -> None: pass @ensure_yield diff --git a/api/src/opentrons/drivers/types.py b/api/src/opentrons/drivers/types.py index a4047ca64da..f01001bdef8 100644 --- a/api/src/opentrons/drivers/types.py +++ b/api/src/opentrons/drivers/types.py @@ -1,6 +1,6 @@ """ Type definitions for modules in this tree """ from dataclasses import dataclass -from typing import Dict, NamedTuple, Optional +from typing import Any, Dict, List, NamedTuple, Optional from enum import Enum @@ -83,3 +83,25 @@ class AbsorbanceReaderDeviceState(str, Enum): OK = "ok" BROKEN_FW = "broken_fw" ERROR = "error" + + +class ABSMeasurementMode(Enum): + """The current mode configured for reading the Absorbance Reader.""" + + SINGLE = "single" + MULTI = "multi" + + +@dataclass +class ABSMeasurementConfig: + measure_mode: ABSMeasurementMode + sample_wavelengths: List[int] + reference_wavelength: Optional[int] + + @property + def data(self) -> Dict[str, Any]: + return { + "measureMode": self.measure_mode.value, + "sampleWavelengths": self.sample_wavelengths, + "referenceWavelength": self.reference_wavelength, + } diff --git a/api/src/opentrons/execute.py b/api/src/opentrons/execute.py index 1e8d3771f59..a9b3562d82b 100644 --- a/api/src/opentrons/execute.py +++ b/api/src/opentrons/execute.py @@ -546,6 +546,7 @@ def _create_live_context_pe( hardware_api=hardware_api_wrapped, config=_get_protocol_engine_config(), deck_configuration=entrypoint_util.get_deck_configuration(), + file_provider=None, error_recovery_policy=error_recovery_policy.never_recover, drop_tips_after_run=False, post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE, @@ -609,8 +610,6 @@ def _run_file_non_pe( context.home() try: - # TODO (spp, 2024-03-18): use true run-time param overrides once enabled - # for cli protocol simulation/ execution execute_apiv2.run_protocol( protocol, context, run_time_parameters_with_overrides=None ) @@ -626,6 +625,7 @@ def _run_file_pe( """Run a protocol file with Protocol Engine.""" async def run(protocol_source: ProtocolSource) -> None: + # TODO (spp, 2024-03-18): use run-time param overrides once enabled for cli protocol execution hardware_api_wrapped = hardware_api.wrapped() protocol_engine = await create_protocol_engine( hardware_api=hardware_api_wrapped, diff --git a/api/src/opentrons/hardware_control/api.py b/api/src/opentrons/hardware_control/api.py index ea4c44265c3..c52fae64131 100644 --- a/api/src/opentrons/hardware_control/api.py +++ b/api/src/opentrons/hardware_control/api.py @@ -169,6 +169,16 @@ def _update_door_state(self, door_state: DoorState) -> None: def _reset_last_mount(self) -> None: self._last_moved_mount = None + def get_deck_from_machine( + self, machine_pos: Dict[Axis, float] + ) -> Dict[Axis, float]: + return deck_from_machine( + machine_pos=machine_pos, + attitude=self._robot_calibration.deck_calibration.attitude, + offset=top_types.Point(0, 0, 0), + robot_type=cast(RobotType, "OT-2 Standard"), + ) + @classmethod async def build_hardware_controller( # noqa: C901 cls, @@ -657,11 +667,8 @@ async def home(self, axes: Optional[List[Axis]] = None) -> None: async with self._motion_lock: if smoothie_gantry: smoothie_pos.update(await self._backend.home(smoothie_gantry)) - self._current_position = deck_from_machine( - machine_pos=self._axis_map_from_string_map(smoothie_pos), - attitude=self._robot_calibration.deck_calibration.attitude, - offset=top_types.Point(0, 0, 0), - robot_type=cast(RobotType, "OT-2 Standard"), + self._current_position = self.get_deck_from_machine( + self._axis_map_from_string_map(smoothie_pos) ) for plunger in plungers: await self._do_plunger_home(axis=plunger, acquire_lock=False) @@ -703,11 +710,8 @@ async def current_position( async with self._motion_lock: if refresh: smoothie_pos = await self._backend.update_position() - self._current_position = deck_from_machine( - machine_pos=self._axis_map_from_string_map(smoothie_pos), - attitude=self._robot_calibration.deck_calibration.attitude, - offset=top_types.Point(0, 0, 0), - robot_type=cast(RobotType, "OT-2 Standard"), + self._current_position = self.get_deck_from_machine( + self._axis_map_from_string_map(smoothie_pos) ) if mount == top_types.Mount.RIGHT: offset = top_types.Point(0, 0, 0) @@ -917,6 +921,16 @@ def engaged_axes(self) -> Dict[Axis, bool]: async def disengage_axes(self, which: List[Axis]) -> None: await self._backend.disengage_axes([ot2_axis_to_string(ax) for ax in which]) + def axis_is_present(self, axis: Axis) -> bool: + is_ot2 = axis in Axis.ot2_axes() + if not is_ot2: + return False + if axis in Axis.pipette_axes(): + mount = Axis.to_ot2_mount(axis) + if self.attached_pipettes.get(mount) is None: + return False + return True + @ExecutionManagerProvider.wait_for_running async def _fast_home(self, axes: Sequence[str], margin: float) -> Dict[str, float]: converted_axes = "".join(axes) @@ -938,11 +952,8 @@ async def retract_axis(self, axis: Axis, margin: float = 10) -> None: async with self._motion_lock: smoothie_pos = await self._fast_home(smoothie_ax, margin) - self._current_position = deck_from_machine( - machine_pos=self._axis_map_from_string_map(smoothie_pos), - attitude=self._robot_calibration.deck_calibration.attitude, - offset=top_types.Point(0, 0, 0), - robot_type=cast(RobotType, "OT-2 Standard"), + self._current_position = self.get_deck_from_machine( + self._axis_map_from_string_map(smoothie_pos) ) # Gantry/frame (i.e. not pipette) config API @@ -1189,11 +1200,6 @@ async def tip_pickup_moves( await self.retract(mount, spec.retract_target) - def cache_tip(self, mount: top_types.Mount, tip_length: float) -> None: - instrument = self.get_pipette(mount) - instrument.add_tip(tip_length=tip_length) - instrument.set_current_volume(0) - async def pick_up_tip( self, mount: top_types.Mount, @@ -1241,10 +1247,10 @@ async def pick_up_tip( if prep_after: await self.prepare_for_aspirate(mount) - async def drop_tip(self, mount: top_types.Mount, home_after: bool = True) -> None: - """Drop tip at the current location.""" - - spec, _remove = self.plan_check_drop_tip(mount, home_after) + async def tip_drop_moves( + self, mount: top_types.Mount, home_after: bool = True + ) -> None: + spec, _ = self.plan_check_drop_tip(mount, home_after) for move in spec.drop_moves: self._backend.set_active_current(move.current) @@ -1261,18 +1267,28 @@ async def drop_tip(self, mount: top_types.Mount, home_after: bool = True) -> Non axes=[ot2_axis_to_string(ax) for ax in move.home_axes], margin=move.home_after_safety_margin, ) - self._current_position = deck_from_machine( - machine_pos=self._axis_map_from_string_map(smoothie_pos), - attitude=self._robot_calibration.deck_calibration.attitude, - offset=top_types.Point(0, 0, 0), - robot_type=cast(RobotType, "OT-2 Standard"), + self._current_position = self.get_deck_from_machine( + self._axis_map_from_string_map(smoothie_pos) ) for shake in spec.shake_moves: await self.move_rel(mount, shake[0], speed=shake[1]) self._backend.set_active_current(spec.ending_current) - _remove() + + async def drop_tip(self, mount: top_types.Mount, home_after: bool = True) -> None: + """Drop tip at the current location.""" + await self.tip_drop_moves(mount, home_after) + + # todo(mm, 2024-10-17): Ideally, callers would be able to replicate the behavior + # of this method via self.drop_tip_moves() plus other public methods. This + # currently prevents that: there is no public equivalent for + # instrument.set_current_volume(). + instrument = self.get_pipette(mount) + instrument.set_current_volume(0) + + self.set_current_tiprack_diameter(mount, 0.0) + self.remove_tip(mount) async def create_simulating_module( self, diff --git a/api/src/opentrons/hardware_control/backends/flex_protocol.py b/api/src/opentrons/hardware_control/backends/flex_protocol.py index 71ce9833251..3862981b20c 100644 --- a/api/src/opentrons/hardware_control/backends/flex_protocol.py +++ b/api/src/opentrons/hardware_control/backends/flex_protocol.py @@ -15,7 +15,7 @@ from opentrons_shared_data.pipette.types import ( PipetteName, ) -from opentrons.config.types import GantryLoad, OutputOptions +from opentrons.config.types import GantryLoad from opentrons.hardware_control.types import ( BoardRevision, Axis, @@ -38,6 +38,8 @@ StatusBarState, ) from opentrons.hardware_control.module_control import AttachedModulesControl +from opentrons_hardware.firmware_bindings.constants import SensorId +from opentrons_hardware.sensors.types import SensorDataType from ..dev_types import OT3AttachedInstruments from .types import HWStopCondition @@ -54,6 +56,18 @@ async def get_serial_number(self) -> Optional[str]: def restore_system_constraints(self) -> AsyncIterator[None]: ... + @asynccontextmanager + def grab_pressure(self, channels: int, mount: OT3Mount) -> AsyncIterator[None]: + ... + + def set_pressure_sensor_available( + self, pipette_axis: Axis, available: bool + ) -> None: + ... + + def get_pressure_sensor_available(self, pipette_axis: Axis) -> bool: + ... + def update_constraints_for_gantry_load(self, gantry_load: GantryLoad) -> None: ... @@ -64,7 +78,11 @@ def update_constraints_for_calibration_with_gantry_load( ... def update_constraints_for_plunger_acceleration( - self, mount: OT3Mount, acceleration: float, gantry_load: GantryLoad + self, + mount: OT3Mount, + acceleration: float, + gantry_load: GantryLoad, + high_speed_pipette: bool = False, ) -> None: ... @@ -147,10 +165,12 @@ async def liquid_probe( plunger_speed: float, threshold_pascals: float, plunger_impulse_time: float, - output_format: OutputOptions = OutputOptions.can_bus_only, - data_files: Optional[Dict[InstrumentProbeType, str]] = None, + num_baseline_reads: int, probe: InstrumentProbeType = InstrumentProbeType.PRIMARY, force_both_sensors: bool = False, + response_queue: Optional[ + asyncio.Queue[Dict[SensorId, List[SensorDataType]]] + ] = None, ) -> float: ... @@ -213,7 +233,7 @@ async def get_jaw_state(self) -> GripperJawState: ... async def tip_action( - self, origin: Dict[Axis, float], targets: List[Tuple[Dict[Axis, float], float]] + self, origin: float, targets: List[Tuple[float, float]] ) -> None: ... @@ -366,8 +386,6 @@ async def capacitive_probe( speed_mm_per_s: float, sensor_threshold_pf: float, probe: InstrumentProbeType = InstrumentProbeType.PRIMARY, - output_format: OutputOptions = OutputOptions.sync_only, - data_files: Optional[Dict[InstrumentProbeType, str]] = None, ) -> bool: ... diff --git a/api/src/opentrons/hardware_control/backends/ot3controller.py b/api/src/opentrons/hardware_control/backends/ot3controller.py index 386e6a36159..0ff3c033f44 100644 --- a/api/src/opentrons/hardware_control/backends/ot3controller.py +++ b/api/src/opentrons/hardware_control/backends/ot3controller.py @@ -25,7 +25,7 @@ Union, Mapping, ) -from opentrons.config.types import OT3Config, GantryLoad, OutputOptions +from opentrons.config.types import OT3Config, GantryLoad from opentrons.config import gripper_config from .ot3utils import ( axis_convert, @@ -102,7 +102,9 @@ NodeId, PipetteName as FirmwarePipetteName, ErrorCode, + SensorId, ) +from opentrons_hardware.sensors.types import SensorDataType from opentrons_hardware.firmware_bindings.messages.message_definitions import ( StopRequest, ) @@ -161,6 +163,7 @@ capacitive_pass, liquid_probe, check_overpressure, + grab_pressure, ) from opentrons_hardware.hardware_control.rear_panel_settings import ( get_door_state, @@ -194,6 +197,7 @@ PipetteLiquidNotFoundError, CommunicationError, PythonException, + UnsupportedHardwareCommand, ) from .subsystem_manager import SubsystemManager @@ -359,6 +363,7 @@ def __init__( self._configuration.motion_settings, GantryLoad.LOW_THROUGHPUT ) ) + self._pressure_sensor_available: Dict[NodeId, bool] = {} @asynccontextmanager async def restore_system_constraints(self) -> AsyncIterator[None]: @@ -369,6 +374,24 @@ async def restore_system_constraints(self) -> AsyncIterator[None]: self._move_manager.update_constraints(old_system_constraints) log.debug(f"Restore previous system constraints: {old_system_constraints}") + @asynccontextmanager + async def grab_pressure( + self, channels: int, mount: OT3Mount + ) -> AsyncIterator[None]: + tool = axis_to_node(Axis.of_main_tool_actuator(mount)) + async with grab_pressure(channels, tool, self._messenger): + yield + + def set_pressure_sensor_available( + self, pipette_axis: Axis, available: bool + ) -> None: + pip_node = axis_to_node(pipette_axis) + self._pressure_sensor_available[pip_node] = available + + def get_pressure_sensor_available(self, pipette_axis: Axis) -> bool: + pip_node = axis_to_node(pipette_axis) + return self._pressure_sensor_available[pip_node] + def update_constraints_for_calibration_with_gantry_load( self, gantry_load: GantryLoad, @@ -388,10 +411,18 @@ def update_constraints_for_gantry_load(self, gantry_load: GantryLoad) -> None: ) def update_constraints_for_plunger_acceleration( - self, mount: OT3Mount, acceleration: float, gantry_load: GantryLoad + self, + mount: OT3Mount, + acceleration: float, + gantry_load: GantryLoad, + high_speed_pipette: bool = False, ) -> None: new_constraints = get_system_constraints_for_plunger_acceleration( - self._configuration.motion_settings, gantry_load, mount, acceleration + self._configuration.motion_settings, + gantry_load, + mount, + acceleration, + high_speed_pipette, ) self._move_manager.update_constraints(new_constraints) @@ -586,30 +617,104 @@ async def update_encoder_position(self) -> OT3AxisMap[float]: return axis_convert(self._encoder_position, 0.0) def _handle_motor_status_response( - self, - response: NodeMap[MotorPositionStatus], + self, response: NodeMap[MotorPositionStatus], handle_gear_move: bool = False ) -> None: for axis, pos in response.items(): - self._position.update({axis: pos.motor_position}) - self._encoder_position.update({axis: pos.encoder_position}) - # TODO (FPS 6-01-2023): Remove this once the Feature Flag to ignore stall detection is removed. - # This check will latch the motor status for an axis at "true" if it was ever set to true. - # To account for the case where a motor axis has its power reset, we also depend on the - # "encoder_ok" flag staying set (it will only be False if the motor axis has not been - # homed since a power cycle) - motor_ok_latch = ( - (not self._feature_flags.stall_detection_enabled) - and ((axis in self._motor_status) and self._motor_status[axis].motor_ok) - and self._motor_status[axis].encoder_ok - ) - self._motor_status.update( - { - axis: MotorStatus( - motor_ok=(pos.motor_ok or motor_ok_latch), - encoder_ok=pos.encoder_ok, + if handle_gear_move and axis == NodeId.pipette_left: + self._gear_motor_position = {axis: pos.motor_position} + else: + self._position.update({axis: pos.motor_position}) + self._encoder_position.update({axis: pos.encoder_position}) + # TODO (FPS 6-01-2023): Remove this once the Feature Flag to ignore stall detection is removed. + # This check will latch the motor status for an axis at "true" if it was ever set to true. + # To account for the case where a motor axis has its power reset, we also depend on the + # "encoder_ok" flag staying set (it will only be False if the motor axis has not been + # homed since a power cycle) + motor_ok_latch = ( + (not self._feature_flags.stall_detection_enabled) + and ( + (axis in self._motor_status) + and self._motor_status[axis].motor_ok ) - } + and self._motor_status[axis].encoder_ok + ) + self._motor_status.update( + { + axis: MotorStatus( + motor_ok=(pos.motor_ok or motor_ok_latch), + encoder_ok=pos.encoder_ok, + ) + } + ) + + def _build_move_node_axis_runner( + self, + origin: Dict[Axis, float], + target: Dict[Axis, float], + speed: float, + stop_condition: HWStopCondition, + nodes_in_moves_only: bool, + ) -> Tuple[Optional[MoveGroupRunner], bool]: + if not target: + return None, False + move_target = MoveTarget.build(position=target, max_speed=speed) + try: + _, movelist = self._move_manager.plan_motion( + origin=origin, target_list=[move_target] ) + except ZeroLengthMoveError as zme: + log.debug(f"Not moving because move was zero length {str(zme)}") + return None, False + moves = movelist[0] + log.debug( + f"move: machine coordinates {target} from origin: machine coordinates {origin} at speed: {speed} requires {moves}" + ) + + ordered_nodes = self._motor_nodes() + if nodes_in_moves_only: + moving_axes = { + axis_to_node(ax) for move in moves for ax in move.unit_vector.keys() + } + ordered_nodes = ordered_nodes.intersection(moving_axes) + + move_group, _ = create_move_group( + origin, moves, ordered_nodes, MoveStopCondition[stop_condition.name] + ) + return ( + MoveGroupRunner( + move_groups=[move_group], + ignore_stalls=True + if not self._feature_flags.stall_detection_enabled + else False, + ), + False, + ) + + def _build_move_gear_axis_runner( + self, + possible_q_axis_origin: Optional[float], + possible_q_axis_target: Optional[float], + speed: float, + nodes_in_moves_only: bool, + ) -> Tuple[Optional[MoveGroupRunner], bool]: + if possible_q_axis_origin is None or possible_q_axis_target is None: + return None, True + tip_motor_move_group = self._build_tip_action_group( + possible_q_axis_origin, [(possible_q_axis_target, speed)] + ) + if nodes_in_moves_only: + ordered_nodes = self._motor_nodes() + + ordered_nodes.intersection({axis_to_node(Axis.Q)}) + return ( + MoveGroupRunner( + move_groups=[tip_motor_move_group], + ignore_stalls=True + if not self._feature_flags.stall_detection_enabled + else False, + ), + True, + ) @requires_update @requires_estop @@ -637,40 +742,52 @@ async def move( Returns: None """ - move_target = MoveTarget.build(position=target, max_speed=speed) - try: - _, movelist = self._move_manager.plan_motion( - origin=origin, target_list=[move_target] - ) - except ZeroLengthMoveError as zme: - log.debug(f"Not moving because move was zero length {str(zme)}") - return - moves = movelist[0] - log.info(f"move: machine {target} from {origin} requires {moves}") + possible_q_axis_origin = origin.pop(Axis.Q, None) + possible_q_axis_target = target.pop(Axis.Q, None) - ordered_nodes = self._motor_nodes() - if nodes_in_moves_only: - moving_axes = { - axis_to_node(ax) for move in moves for ax in move.unit_vector.keys() - } - ordered_nodes = ordered_nodes.intersection(moving_axes) - - group = create_move_group( - origin, moves, ordered_nodes, MoveStopCondition[stop_condition.name] + maybe_runners = ( + self._build_move_node_axis_runner( + origin, target, speed, stop_condition, nodes_in_moves_only + ), + self._build_move_gear_axis_runner( + possible_q_axis_origin, + possible_q_axis_target, + speed, + nodes_in_moves_only, + ), ) - move_group, _ = group - runner = MoveGroupRunner( - move_groups=[move_group], - ignore_stalls=True - if not self._feature_flags.stall_detection_enabled - else False, + log.debug(f"The move groups are {maybe_runners}.") + + gather_moving_nodes = set() + all_moving_nodes = set() + for runner, _ in maybe_runners: + if runner: + for n in runner.all_nodes(): + gather_moving_nodes.add(n) + for n in runner.all_moving_nodes(): + all_moving_nodes.add(n) + + pipettes_moving = moving_pipettes_in_move_group( + gather_moving_nodes, all_moving_nodes ) - pipettes_moving = moving_pipettes_in_move_group(move_group) - - async with self._monitor_overpressure(pipettes_moving): + async def _runner_coroutine( + runner: MoveGroupRunner, is_gear_move: bool + ) -> Tuple[Dict[NodeId, MotorPositionStatus], bool]: positions = await runner.run(can_messenger=self._messenger) - self._handle_motor_status_response(positions) + return positions, is_gear_move + + coros = [ + _runner_coroutine(runner, is_gear_move) + for runner, is_gear_move in maybe_runners + if runner + ] + checked_moving_pipettes = self._pipettes_to_monitor_pressure(pipettes_moving) + async with self._monitor_overpressure(checked_moving_pipettes): + all_positions = await asyncio.gather(*coros) + + for positions, handle_gear_move in all_positions: + self._handle_motor_status_response(positions, handle_gear_move) def _get_axis_home_distance(self, axis: Axis) -> float: if self.check_motor_status([axis]): @@ -775,7 +892,8 @@ async def home( moving_pipettes = [ axis_to_node(ax) for ax in checked_axes if ax in Axis.pipette_axes() ] - async with self._monitor_overpressure(moving_pipettes): + checked_moving_pipettes = self._pipettes_to_monitor_pressure(moving_pipettes) + async with self._monitor_overpressure(checked_moving_pipettes): positions = await asyncio.gather(*coros) # TODO(CM): default gear motor homing routine to have some acceleration if Axis.Q in checked_axes: @@ -785,10 +903,14 @@ async def home( Axis.to_kind(Axis.Q) ], ) + for position in positions: self._handle_motor_status_response(position) return axis_convert(self._position, 0.0) + def _pipettes_to_monitor_pressure(self, pipettes: List[NodeId]) -> List[NodeId]: + return [pip for pip in pipettes if self._pressure_sensor_available[pip]] + def _filter_move_group(self, move_group: MoveGroup) -> MoveGroup: new_group: MoveGroup = [] for step in move_group: @@ -829,17 +951,24 @@ async def home_tip_motors( self._gear_motor_position = {} raise e - async def tip_action( - self, origin: Dict[Axis, float], targets: List[Tuple[Dict[Axis, float], float]] - ) -> None: + def _build_tip_action_group( + self, origin: float, targets: List[Tuple[float, float]] + ) -> MoveGroup: move_targets = [ - MoveTarget.build(target_pos, speed) for target_pos, speed in targets + MoveTarget.build({Axis.Q: target_pos}, speed) + for target_pos, speed in targets ] _, moves = self._move_manager.plan_motion( - origin=origin, target_list=move_targets + origin={Axis.Q: origin}, target_list=move_targets ) - move_group = create_tip_action_group(moves[0], [NodeId.pipette_left], "clamp") + return create_tip_action_group(moves[0], [NodeId.pipette_left], "clamp") + + async def tip_action( + self, origin: float, targets: List[Tuple[float, float]] + ) -> None: + + move_group = self._build_tip_action_group(origin, targets) runner = MoveGroupRunner( move_groups=[move_group], ignore_stalls=True @@ -904,10 +1033,12 @@ def _lookup_serial_key(pipette_name: FirmwarePipetteName) -> str: lookup_name = { FirmwarePipetteName.p1000_single: "P1KS", FirmwarePipetteName.p1000_multi: "P1KM", + FirmwarePipetteName.p1000_multi_em: "P1KP", FirmwarePipetteName.p50_single: "P50S", FirmwarePipetteName.p50_multi: "P50M", FirmwarePipetteName.p1000_96: "P1KH", FirmwarePipetteName.p50_96: "P50H", + FirmwarePipetteName.p200_96: "P2HH", } return lookup_name[pipette_name] @@ -1358,28 +1489,20 @@ async def liquid_probe( plunger_speed: float, threshold_pascals: float, plunger_impulse_time: float, - output_option: OutputOptions = OutputOptions.can_bus_only, - data_files: Optional[Dict[InstrumentProbeType, str]] = None, + num_baseline_reads: int, probe: InstrumentProbeType = InstrumentProbeType.PRIMARY, force_both_sensors: bool = False, + response_queue: Optional[ + asyncio.Queue[Dict[SensorId, List[SensorDataType]]] + ] = None, ) -> float: head_node = axis_to_node(Axis.by_mount(mount)) tool = sensor_node_for_pipette(OT3Mount(mount.value)) - csv_output = bool(output_option.value & OutputOptions.stream_to_csv.value) - sync_buffer_output = bool( - output_option.value & OutputOptions.sync_buffer_to_csv.value - ) - can_bus_only_output = bool( - output_option.value & OutputOptions.can_bus_only.value - ) - data_files_transposed = ( - None - if data_files is None - else { - sensor_id_for_instrument(probe): data_files[probe] - for probe in data_files.keys() - } - ) + if tool not in self._pipettes_to_monitor_pressure([tool]): + raise UnsupportedHardwareCommand( + "Liquid Presence Detection not available on this pipette." + ) + positions = await liquid_probe( messenger=self._messenger, tool=tool, @@ -1389,12 +1512,10 @@ async def liquid_probe( mount_speed=mount_speed, threshold_pascals=threshold_pascals, plunger_impulse_time=plunger_impulse_time, - csv_output=csv_output, - sync_buffer_output=sync_buffer_output, - can_bus_only_output=can_bus_only_output, - data_files=data_files_transposed, + num_baseline_reads=num_baseline_reads, sensor_id=sensor_id_for_instrument(probe), force_both_sensors=force_both_sensors, + response_queue=response_queue, ) for node, point in positions.items(): self._position.update({node: point.motor_position}) @@ -1421,41 +1542,13 @@ async def capacitive_probe( speed_mm_per_s: float, sensor_threshold_pf: float, probe: InstrumentProbeType = InstrumentProbeType.PRIMARY, - output_option: OutputOptions = OutputOptions.sync_only, - data_files: Optional[Dict[InstrumentProbeType, str]] = None, ) -> bool: - if output_option == OutputOptions.sync_buffer_to_csv: - assert ( - self._subsystem_manager.device_info[ - SubSystem.of_mount(mount) - ].revision.tertiary - == "1" - ) - csv_output = bool(output_option.value & OutputOptions.stream_to_csv.value) - sync_buffer_output = bool( - output_option.value & OutputOptions.sync_buffer_to_csv.value - ) - can_bus_only_output = bool( - output_option.value & OutputOptions.can_bus_only.value - ) - data_files_transposed = ( - None - if data_files is None - else { - sensor_id_for_instrument(probe): data_files[probe] - for probe in data_files.keys() - } - ) status = await capacitive_probe( messenger=self._messenger, tool=sensor_node_for_mount(mount), mover=axis_to_node(moving), distance=distance_mm, mount_speed=speed_mm_per_s, - csv_output=csv_output, - sync_buffer_output=sync_buffer_output, - can_bus_only_output=can_bus_only_output, - data_files=data_files_transposed, sensor_id=sensor_id_for_instrument(probe), relative_threshold_pf=sensor_threshold_pf, ) diff --git a/api/src/opentrons/hardware_control/backends/ot3simulator.py b/api/src/opentrons/hardware_control/backends/ot3simulator.py index 97d3661e32e..377397ba597 100644 --- a/api/src/opentrons/hardware_control/backends/ot3simulator.py +++ b/api/src/opentrons/hardware_control/backends/ot3simulator.py @@ -17,7 +17,7 @@ Mapping, ) -from opentrons.config.types import OT3Config, GantryLoad, OutputOptions +from opentrons.config.types import OT3Config, GantryLoad from opentrons.config import gripper_config from opentrons.hardware_control.module_control import AttachedModulesControl @@ -63,7 +63,8 @@ from opentrons.util.async_helpers import ensure_yield from .types import HWStopCondition from .flex_protocol import FlexBackend - +from opentrons_hardware.firmware_bindings.constants import SensorId +from opentrons_hardware.sensors.types import SensorDataType log = logging.getLogger(__name__) @@ -234,7 +235,11 @@ def update_constraints_for_calibration_with_gantry_load( self._sim_gantry_load = gantry_load def update_constraints_for_plunger_acceleration( - self, mount: OT3Mount, acceleration: float, gantry_load: GantryLoad + self, + mount: OT3Mount, + acceleration: float, + gantry_load: GantryLoad, + high_speed_pipette: bool = False, ) -> None: self._sim_gantry_load = gantry_load @@ -346,10 +351,12 @@ async def liquid_probe( plunger_speed: float, threshold_pascals: float, plunger_impulse_time: float, - output_format: OutputOptions = OutputOptions.can_bus_only, - data_files: Optional[Dict[InstrumentProbeType, str]] = None, + num_baseline_reads: int, probe: InstrumentProbeType = InstrumentProbeType.PRIMARY, force_both_sensors: bool = False, + response_queue: Optional[ + asyncio.Queue[Dict[SensorId, List[SensorDataType]]] + ] = None, ) -> float: z_axis = Axis.by_mount(mount) pos = self._position @@ -436,10 +443,12 @@ async def get_jaw_state(self) -> GripperJawState: return self._sim_jaw_state async def tip_action( - self, origin: Dict[Axis, float], targets: List[Tuple[Dict[Axis, float], float]] + self, origin: float, targets: List[Tuple[float, float]] ) -> None: self._gear_motor_position.update( - coalesce_move_segments(origin, [target[0] for target in targets]) + coalesce_move_segments( + {Axis.Q: origin}, [{Axis.Q: target[0]} for target in targets] + ) ) await asyncio.sleep(0) @@ -749,8 +758,6 @@ async def capacitive_probe( speed_mm_per_s: float, sensor_threshold_pf: float, probe: InstrumentProbeType = InstrumentProbeType.PRIMARY, - output_format: OutputOptions = OutputOptions.sync_only, - data_files: Optional[Dict[InstrumentProbeType, str]] = None, ) -> bool: self._position[moving] += distance_mm return True diff --git a/api/src/opentrons/hardware_control/backends/ot3utils.py b/api/src/opentrons/hardware_control/backends/ot3utils.py index 6d62ffc4581..cb8f5e95e71 100644 --- a/api/src/opentrons/hardware_control/backends/ot3utils.py +++ b/api/src/opentrons/hardware_control/backends/ot3utils.py @@ -2,7 +2,10 @@ from typing import Dict, Iterable, List, Set, Tuple, TypeVar, cast, Sequence, Optional from typing_extensions import Literal from logging import getLogger -from opentrons.config.defaults_ot3 import DEFAULT_CALIBRATION_AXIS_MAX_SPEED +from opentrons.config.defaults_ot3 import ( + DEFAULT_CALIBRATION_AXIS_MAX_SPEED, + DEFAULT_EMULSIFYING_PIPETTE_AXIS_MAX_SPEED, +) from opentrons.config.types import OT3MotionSettings, OT3CurrentSettings, GantryLoad from opentrons.hardware_control.types import ( Axis, @@ -281,12 +284,22 @@ def get_system_constraints_for_plunger_acceleration( gantry_load: GantryLoad, mount: OT3Mount, acceleration: float, + high_speed_pipette: bool = False, ) -> "SystemConstraints[Axis]": old_constraints = config.by_gantry_load(gantry_load) new_constraints = {} axis_kinds = set([k for _, v in old_constraints.items() for k in v.keys()]) + + def _get_axis_max_speed(ax: Axis) -> float: + if ax == Axis.of_main_tool_actuator(mount) and high_speed_pipette: + _max_speed = float(DEFAULT_EMULSIFYING_PIPETTE_AXIS_MAX_SPEED) + else: + _max_speed = old_constraints["default_max_speed"][axis_kind] + return _max_speed + for axis_kind in axis_kinds: for axis in Axis.of_kind(axis_kind): + _default_max_speed = _get_axis_max_speed(axis) if axis == Axis.of_main_tool_actuator(mount): _accel = acceleration else: @@ -295,7 +308,32 @@ def get_system_constraints_for_plunger_acceleration( _accel, old_constraints["max_speed_discontinuity"][axis_kind], old_constraints["direction_change_speed_discontinuity"][axis_kind], - old_constraints["default_max_speed"][axis_kind], + _default_max_speed, + ) + return new_constraints + + +def get_system_constraints_for_emulsifying_pipette( + config: OT3MotionSettings, + gantry_load: GantryLoad, + mount: OT3Mount, +) -> "SystemConstraints[Axis]": + old_constraints = config.by_gantry_load(gantry_load) + new_constraints = {} + axis_kinds = set([k for _, v in old_constraints.items() for k in v.keys()]) + for axis_kind in axis_kinds: + for axis in Axis.of_kind(axis_kind): + if axis == Axis.of_main_tool_actuator(mount): + _max_speed = float(DEFAULT_EMULSIFYING_PIPETTE_AXIS_MAX_SPEED) + else: + _max_speed = old_constraints["default_max_speed"][axis_kind] + new_constraints[axis] = AxisConstraints.build( + max_acceleration=old_constraints["acceleration"][axis_kind], + max_speed_discont=old_constraints["max_speed_discontinuity"][axis_kind], + max_direction_change_speed_discont=old_constraints[ + "direction_change_speed_discontinuity" + ][axis_kind], + max_speed=_max_speed, ) return new_constraints @@ -498,10 +536,10 @@ def create_gripper_jaw_hold_group(encoder_position_um: int) -> MoveGroup: return move_group -def moving_pipettes_in_move_group(group: MoveGroup) -> List[NodeId]: +def moving_pipettes_in_move_group( + all_nodes: Set[NodeId], moving_nodes: Set[NodeId] +) -> List[NodeId]: """Utility function to get which pipette nodes are moving either in z or their plunger.""" - all_nodes = [node for step in group for node, _ in step.items()] - moving_nodes = moving_axes_in_move_group(group) pipettes_moving: List[NodeId] = [ k for k in moving_nodes if k in [NodeId.pipette_left, NodeId.pipette_right] ] @@ -512,16 +550,6 @@ def moving_pipettes_in_move_group(group: MoveGroup) -> List[NodeId]: return pipettes_moving -def moving_axes_in_move_group(group: MoveGroup) -> Set[NodeId]: - """Utility function to get only the moving nodes in a move group.""" - ret: Set[NodeId] = set() - for step in group: - for node, node_step in step.items(): - if node_step.is_moving_step(): - ret.add(node) - return ret - - AxisMapPayload = TypeVar("AxisMapPayload") @@ -652,6 +680,7 @@ def update( FirmwareGripperjawState.force_controlling_home: GripperJawState.HOMED_READY, FirmwareGripperjawState.force_controlling: GripperJawState.GRIPPING, FirmwareGripperjawState.position_controlling: GripperJawState.HOLDING, + FirmwareGripperjawState.stopped: GripperJawState.STOPPED, } diff --git a/api/src/opentrons/hardware_control/dev_types.py b/api/src/opentrons/hardware_control/dev_types.py index a6773cb9184..981e95e114e 100644 --- a/api/src/opentrons/hardware_control/dev_types.py +++ b/api/src/opentrons/hardware_control/dev_types.py @@ -20,6 +20,7 @@ PipetteConfigurations, SupportedTipsDefinition, PipetteBoundingBoxOffsetDefinition, + AvailableSensorDefinition, ) from opentrons_shared_data.gripper import ( GripperModel, @@ -100,6 +101,9 @@ class PipetteDict(InstrumentDict): pipette_bounding_box_offsets: PipetteBoundingBoxOffsetDefinition current_nozzle_map: NozzleMap lld_settings: Optional[Dict[str, Dict[str, float]]] + plunger_positions: Dict[str, float] + shaft_ul_per_mm: float + available_sensors: AvailableSensorDefinition class PipetteStateDict(TypedDict): diff --git a/api/src/opentrons/hardware_control/instruments/ot2/pipette.py b/api/src/opentrons/hardware_control/instruments/ot2/pipette.py index 7fc15c4c2d3..2d63342cf19 100644 --- a/api/src/opentrons/hardware_control/instruments/ot2/pipette.py +++ b/api/src/opentrons/hardware_control/instruments/ot2/pipette.py @@ -28,7 +28,7 @@ CommandPreconditionViolated, ) from opentrons_shared_data.pipette.ul_per_mm import ( - piecewise_volume_conversion, + calculate_ul_per_mm, PIPETTING_FUNCTION_FALLBACK_VERSION, PIPETTING_FUNCTION_LATEST_VERSION, ) @@ -584,21 +584,9 @@ def get_nominal_tip_overlap_dictionary_by_configuration( # want this to unbounded. @functools.lru_cache(maxsize=100) def ul_per_mm(self, ul: float, action: UlPerMmAction) -> float: - if action == "aspirate": - fallback = self._active_tip_settings.aspirate.default[ - PIPETTING_FUNCTION_FALLBACK_VERSION - ] - sequence = self._active_tip_settings.aspirate.default.get( - self._pipetting_function_version, fallback - ) - else: - fallback = self._active_tip_settings.dispense.default[ - PIPETTING_FUNCTION_FALLBACK_VERSION - ] - sequence = self._active_tip_settings.dispense.default.get( - self._pipetting_function_version, fallback - ) - return piecewise_volume_conversion(ul, sequence) + return calculate_ul_per_mm( + ul, action, self._active_tip_settings, self._pipetting_function_version + ) def __str__(self) -> str: return "{} current volume {}ul critical point: {} at {}".format( diff --git a/api/src/opentrons/hardware_control/instruments/ot2/pipette_handler.py b/api/src/opentrons/hardware_control/instruments/ot2/pipette_handler.py index 99a7a49d41a..7bd41e02e74 100644 --- a/api/src/opentrons/hardware_control/instruments/ot2/pipette_handler.py +++ b/api/src/opentrons/hardware_control/instruments/ot2/pipette_handler.py @@ -13,7 +13,6 @@ Sequence, Iterator, TypeVar, - overload, ) import numpy @@ -261,6 +260,13 @@ def get_attached_instrument(self, mount: MountType) -> PipetteDict: "pipette_bounding_box_offsets" ] = instr.config.pipette_bounding_box_offsets result["lld_settings"] = instr.config.lld_settings + result["plunger_positions"] = { + "top": instr.plunger_positions.top, + "bottom": instr.plunger_positions.bottom, + "blow_out": instr.plunger_positions.blow_out, + "drop_tip": instr.plunger_positions.drop_tip, + } + result["shaft_ul_per_mm"] = instr.config.shaft_ul_per_mm return cast(PipetteDict, result) @property @@ -415,7 +421,7 @@ async def reset_nozzle_configuration(self, mount: MountType) -> None: if instr: instr.reset_nozzle_configuration() - async def add_tip(self, mount: MountType, tip_length: float) -> None: + def add_tip(self, mount: MountType, tip_length: float) -> None: instr = self._attached_instruments[mount] attached = self.attached_instruments instr_dict = attached[mount] @@ -430,7 +436,15 @@ async def add_tip(self, mount: MountType, tip_length: float) -> None: f"attach tip called while tip already attached to {instr}" ) - async def remove_tip(self, mount: MountType) -> None: + def cache_tip(self, mount: MountType, tip_length: float) -> None: + instrument = self.get_pipette(mount) + if instrument.has_tip: + # instrument.add_tip() would raise an AssertionError if we tried to overwrite an existing tip. + instrument.remove_tip() + instrument.add_tip(tip_length=tip_length) + instrument.set_current_volume(0) + + def remove_tip(self, mount: MountType) -> None: instr = self._attached_instruments[mount] attached = self.attached_instruments instr_dict = attached[mount] @@ -495,25 +509,12 @@ def plunger_flowrate( ul_per_s = mm_per_s * instr.ul_per_mm(instr.liquid_class.max_volume, action) return round(ul_per_s, 6) - @overload def plan_check_aspirate( - self, mount: top_types.Mount, volume: Optional[float], rate: float - ) -> Optional[LiquidActionSpec]: - ... - - @overload - def plan_check_aspirate( - self, mount: OT3Mount, volume: Optional[float], rate: float - ) -> Optional[LiquidActionSpec]: - ... - - # note on this type ignore: see motion_utilities - def plan_check_aspirate( # type: ignore[no-untyped-def] self, - mount, - volume, - rate, - ): + mount: MountType, + volume: Optional[float], + rate: float, + ) -> Optional[LiquidActionSpec]: """Check preconditions for aspirate, parse args, and calculate positions. While the mechanics of issuing an aspirate move itself are left to child @@ -572,28 +573,12 @@ def plan_check_aspirate( # type: ignore[no-untyped-def] current=instrument.plunger_motor_current.run, ) - @overload def plan_check_dispense( self, - mount: top_types.Mount, - volume: Optional[float], - rate: float, - push_out: Optional[float], - ) -> Optional[LiquidActionSpec]: - ... - - @overload - def plan_check_dispense( - self, - mount: OT3Mount, + mount: MountType, volume: Optional[float], rate: float, push_out: Optional[float], - ) -> Optional[LiquidActionSpec]: - ... - - def plan_check_dispense( # type: ignore[no-untyped-def] - self, mount, volume, rate, push_out ) -> Optional[LiquidActionSpec]: """Check preconditions for dispense, parse args, and calculate positions. @@ -687,15 +672,7 @@ def plan_check_dispense( # type: ignore[no-untyped-def] current=instrument.plunger_motor_current.run, ) - @overload - def plan_check_blow_out(self, mount: top_types.Mount) -> LiquidActionSpec: - ... - - @overload - def plan_check_blow_out(self, mount: OT3Mount) -> LiquidActionSpec: - ... - - def plan_check_blow_out(self, mount): # type: ignore[no-untyped-def] + def plan_check_blow_out(self, mount: MountType) -> LiquidActionSpec: """Check preconditions and calculate values for blowout.""" instrument = self.get_pipette(mount) speed = self.plunger_speed( @@ -743,33 +720,13 @@ def build_one_shake() -> List[Tuple[top_types.Point, Optional[float]]]: else: return [] - @overload - def plan_check_pick_up_tip( - self, - mount: top_types.Mount, - presses: Optional[int], - increment: Optional[float], - tip_length: float = 0, - ) -> Tuple[PickUpTipSpec, Callable[[], None]]: - ... - - @overload def plan_check_pick_up_tip( self, - mount: OT3Mount, + mount: MountType, presses: Optional[int], increment: Optional[float], tip_length: float = 0, ) -> Tuple[PickUpTipSpec, Callable[[], None]]: - ... - - def plan_check_pick_up_tip( # type: ignore[no-untyped-def] - self, - mount, - presses, - increment, - tip_length=0, - ): # Prechecks: ready for pickup tip and press/increment are valid instrument = self.get_pipette(mount) if instrument.has_tip: @@ -917,23 +874,13 @@ def build() -> List[DropTipMove]: return build - @overload - def plan_check_drop_tip( - self, mount: top_types.Mount, home_after: bool - ) -> Tuple[DropTipSpec, Callable[[], None]]: - ... - - @overload + # todo(mm, 2024-10-17): The returned _remove_tips() callable is not used by anything + # anymore. Delete it. def plan_check_drop_tip( - self, mount: OT3Mount, home_after: bool - ) -> Tuple[DropTipSpec, Callable[[], None]]: - ... - - def plan_check_drop_tip( # type: ignore[no-untyped-def] self, - mount, - home_after, - ): + mount: MountType, + home_after: bool, + ) -> Tuple[DropTipSpec, Callable[[], None]]: instrument = self.get_pipette(mount) if not instrument.drop_configurations.plunger_eject: diff --git a/api/src/opentrons/hardware_control/instruments/ot3/pipette.py b/api/src/opentrons/hardware_control/instruments/ot3/pipette.py index 109747ea1b9..b9355874906 100644 --- a/api/src/opentrons/hardware_control/instruments/ot3/pipette.py +++ b/api/src/opentrons/hardware_control/instruments/ot3/pipette.py @@ -27,7 +27,7 @@ InvalidInstrumentData, ) from opentrons_shared_data.pipette.ul_per_mm import ( - piecewise_volume_conversion, + calculate_ul_per_mm, PIPETTING_FUNCTION_FALLBACK_VERSION, PIPETTING_FUNCTION_LATEST_VERSION, ) @@ -41,6 +41,7 @@ UlPerMmAction, PipetteName, PipetteModel, + Quirks, ) from opentrons_shared_data.pipette import ( load_data as load_pipette_data, @@ -225,6 +226,9 @@ def active_tip_settings(self) -> SupportedTipsDefinition: def push_out_volume(self) -> float: return self._active_tip_settings.default_push_out_volume + def is_high_speed_pipette(self) -> bool: + return Quirks.highSpeed in self._config.quirks + def act_as(self, name: PipetteName) -> None: """Reconfigure to act as ``name``. ``name`` must be either the actual name of the pipette, or a name in its back-compatibility @@ -529,23 +533,13 @@ def tip_presence_responses(self) -> int: # want this to unbounded. @functools.lru_cache(maxsize=100) def ul_per_mm(self, ul: float, action: UlPerMmAction) -> float: - if action == "aspirate": - fallback = self._active_tip_settings.aspirate.default[ - PIPETTING_FUNCTION_FALLBACK_VERSION - ] - sequence = self._active_tip_settings.aspirate.default.get( - self._pipetting_function_version, fallback - ) - elif action == "blowout": - return self._config.shaft_ul_per_mm - else: - fallback = self._active_tip_settings.dispense.default[ - PIPETTING_FUNCTION_FALLBACK_VERSION - ] - sequence = self._active_tip_settings.dispense.default.get( - self._pipetting_function_version, fallback - ) - return piecewise_volume_conversion(ul, sequence) + return calculate_ul_per_mm( + ul, + action, + self._active_tip_settings, + self._pipetting_function_version, + self._config.shaft_ul_per_mm, + ) def __str__(self) -> str: return "{} current volume {}ul critical point: {} at {}".format( @@ -585,6 +579,7 @@ def as_dict(self) -> "Pipette.DictType": "versioned_tip_overlap": self.tip_overlap, "back_compat_names": self._config.pipette_backcompat_names, "supported_tips": self.liquid_class.supported_tips, + "shaft_ul_per_mm": self._config.shaft_ul_per_mm, } ) return self._config_as_dict diff --git a/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py b/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py index 4f24b19c51b..dda5031a8a3 100644 --- a/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py +++ b/api/src/opentrons/hardware_control/instruments/ot3/pipette_handler.py @@ -282,6 +282,13 @@ def get_attached_instrument(self, mount: OT3Mount) -> PipetteDict: "pipette_bounding_box_offsets" ] = instr.config.pipette_bounding_box_offsets result["lld_settings"] = instr.config.lld_settings + result["plunger_positions"] = { + "top": instr.plunger_positions.top, + "bottom": instr.plunger_positions.bottom, + "blow_out": instr.plunger_positions.blow_out, + "drop_tip": instr.plunger_positions.drop_tip, + } + result["shaft_ul_per_mm"] = instr.config.shaft_ul_per_mm return cast(PipetteDict, result) @property @@ -425,7 +432,7 @@ async def reset_nozzle_configuration(self, mount: OT3Mount) -> None: if instr: instr.reset_nozzle_configuration() - async def add_tip(self, mount: OT3Mount, tip_length: float) -> None: + def add_tip(self, mount: OT3Mount, tip_length: float) -> None: instr = self._attached_instruments[mount] attached = self.attached_instruments instr_dict = attached[mount] @@ -440,7 +447,15 @@ async def add_tip(self, mount: OT3Mount, tip_length: float) -> None: "attach tip called while tip already attached to {instr}" ) - async def remove_tip(self, mount: OT3Mount) -> None: + def cache_tip(self, mount: OT3Mount, tip_length: float) -> None: + instrument = self.get_pipette(mount) + if instrument.has_tip: + # instrument.add_tip() would raise an AssertionError if we tried to overwrite an existing tip. + instrument.remove_tip() + instrument.add_tip(tip_length=tip_length) + instrument.set_current_volume(0) + + def remove_tip(self, mount: OT3Mount) -> None: instr = self._attached_instruments[mount] attached = self.attached_instruments instr_dict = attached[mount] diff --git a/api/src/opentrons/hardware_control/modules/absorbance_reader.py b/api/src/opentrons/hardware_control/modules/absorbance_reader.py index e68b535c4bc..ec4a80b7f60 100644 --- a/api/src/opentrons/hardware_control/modules/absorbance_reader.py +++ b/api/src/opentrons/hardware_control/modules/absorbance_reader.py @@ -12,6 +12,8 @@ AbsorbanceReaderLidStatus, AbsorbanceReaderPlatePresence, AbsorbanceReaderDeviceState, + ABSMeasurementMode, + ABSMeasurementConfig, ) from opentrons.hardware_control.execution_manager import ExecutionManager @@ -194,13 +196,21 @@ def __init__( self._device_info = device_info self._reader = reader self._poller = poller + self._measurement_config: Optional[ABSMeasurementConfig] = None + self._device_status = AbsorbanceReaderStatus.IDLE self._error: Optional[str] = None self._reader.register_error_handler(self._enter_error_state) @property def status(self) -> AbsorbanceReaderStatus: - """Return some string describing status.""" - return AbsorbanceReaderStatus.IDLE + """Return some string describing the device status.""" + state = self._reader.device_state + if state not in [ + AbsorbanceReaderDeviceState.UNKNOWN, + AbsorbanceReaderDeviceState.OK, + ]: + return AbsorbanceReaderStatus.ERROR + return self._device_status @property def lid_status(self) -> AbsorbanceReaderLidStatus: @@ -210,6 +220,20 @@ def lid_status(self) -> AbsorbanceReaderLidStatus: def plate_presence(self) -> AbsorbanceReaderPlatePresence: return self._reader.plate_presence + @property + def uptime(self) -> int: + """Time in ms this device has been running for.""" + return self._reader.uptime + + @property + def supported_wavelengths(self) -> List[int]: + """The wavelengths in nm this plate reader supports.""" + return self._reader.supported_wavelengths + + @property + def measurement_config(self) -> Optional[ABSMeasurementConfig]: + return self._measurement_config + @property def device_info(self) -> Mapping[str, str]: """Return a dict of the module's static information (serial, etc)""" @@ -218,12 +242,17 @@ def device_info(self) -> Mapping[str, str]: @property def live_data(self) -> LiveData: """Return a dict of the module's dynamic information""" + conf = self._measurement_config.data if self._measurement_config else dict() return { "status": self.status.value, "data": { + "uptime": self.uptime, + "deviceStatus": self.status.value, "lidStatus": self.lid_status.value, "platePresence": self.plate_presence.value, - "sampleWavelength": 400, + "measureMode": conf.get("measureMode", ""), + "sampleWavelengths": conf.get("sampleWavelengths", []), + "referenceWavelength": conf.get("referenceWavelength", 0), }, } @@ -243,12 +272,8 @@ def usb_port(self) -> USBPort: return self._usb_port async def deactivate(self, must_be_running: bool = True) -> None: - """Deactivate the module. - - Contains an override to the `wait_for_is_running` step in cases where the - module must be deactivated regardless of context.""" - await self._poller.stop() - await self._driver.disconnect() + """Deactivate the module.""" + pass async def wait_for_is_running(self) -> None: if not self.is_simulated: @@ -287,6 +312,8 @@ async def update_device(self, firmware_file_path: str) -> Tuple[bool, str]: log.debug(f"Updating {self.name}: {self.port} with {firmware_file_path}") self._updating = True success, res = await self._driver.update_firmware(firmware_file_path) + # it takes time for the plate reader to re-init after an update. + await asyncio.sleep(10) self._device_info = await self._driver.get_device_info() await self._poller.start() self._updating = False @@ -307,19 +334,35 @@ async def cleanup(self) -> None: Clean up, i.e. stop pollers, disconnect serial, etc in preparation for object destruction. """ - await self.deactivate() - - async def set_sample_wavelength(self, wavelength: int) -> None: - """Set the Absorbance Reader's active wavelength.""" - await self._driver.initialize_measurement(wavelength) + await self._poller.stop() + await self._driver.disconnect() - async def start_measure(self, wavelength: int) -> List[float]: - """Initiate a single measurement.""" - return await self._driver.get_single_measurement(wavelength) + async def set_sample_wavelength( + self, + mode: ABSMeasurementMode, + wavelengths: List[int], + reference_wavelength: Optional[int] = None, + ) -> None: + """Set the Absorbance Reader's measurement mode and active wavelength.""" + if mode == ABSMeasurementMode.SINGLE: + assert ( + len(wavelengths) == 1 + ), "Cannot initialize single read mode with more than 1 wavelength." + + await self._driver.initialize_measurement(wavelengths, mode) + self._measurement_config = ABSMeasurementConfig( + measure_mode=mode, + sample_wavelengths=wavelengths, + reference_wavelength=reference_wavelength, + ) - async def get_current_wavelength(self) -> None: - """Get the Absorbance Reader's current active wavelength.""" - pass # TODO: implement + async def start_measure(self) -> List[List[float]]: + """Initiate a measurement depending on the measurement mode.""" + try: + self._device_status = AbsorbanceReaderStatus.MEASURING + return await self._driver.get_measurement() + finally: + self._device_status = AbsorbanceReaderStatus.IDLE async def get_current_lid_status(self) -> AbsorbanceReaderLidStatus: """Get the Absorbance Reader's current lid status.""" diff --git a/api/src/opentrons/hardware_control/modules/heater_shaker.py b/api/src/opentrons/hardware_control/modules/heater_shaker.py index e52de0bf49c..cc592d3c514 100644 --- a/api/src/opentrons/hardware_control/modules/heater_shaker.py +++ b/api/src/opentrons/hardware_control/modules/heater_shaker.py @@ -201,7 +201,6 @@ def device_info(self) -> Mapping[str, str]: @property def live_data(self) -> LiveData: return { - # TODO (spp, 2022-2-22): Revise what status includes "status": self.status.value, "data": { "temperatureStatus": self.temperature_status.value, diff --git a/api/src/opentrons/hardware_control/modules/thermocycler.py b/api/src/opentrons/hardware_control/modules/thermocycler.py index bcaac8650d9..4a1b2fe038b 100644 --- a/api/src/opentrons/hardware_control/modules/thermocycler.py +++ b/api/src/opentrons/hardware_control/modules/thermocycler.py @@ -2,7 +2,7 @@ import asyncio import logging -from typing import Callable, Optional, List, Dict, Mapping +from typing import Callable, Optional, List, Dict, Mapping, Union, cast from opentrons.drivers.rpi_drivers.types import USBPort from opentrons.drivers.types import ThermocyclerLidStatus, Temperature, PlateTemperature from opentrons.hardware_control.modules.lid_temp_status import LidTemperatureStatus @@ -363,6 +363,39 @@ async def cycle_temperatures( self.make_cancellable(task) await task + async def execute_profile( + self, + profile: List[Union[types.ThermocyclerCycle, types.ThermocyclerStep]], + volume: Optional[float] = None, + ) -> None: + """Begin a set temperature profile, with both repeating and non-repeating steps. + + Args: + profile: The temperature profile to follow. + volume: Optional volume + + Returns: None + """ + await self.wait_for_is_running() + self._total_cycle_count = 0 + self._total_step_count = 0 + self._current_cycle_index = 0 + self._current_step_index = 0 + for step_or_cycle in profile: + if "steps" in step_or_cycle: + # basically https://github.com/python/mypy/issues/14766 + this_cycle = cast(types.ThermocyclerCycle, step_or_cycle) + self._total_cycle_count += this_cycle["repetitions"] + self._total_step_count += ( + len(this_cycle["steps"]) * this_cycle["repetitions"] + ) + else: + self._total_step_count += 1 + self._total_cycle_count += 1 + task = self._loop.create_task(self._execute_profile(profile, volume)) + self.make_cancellable(task) + await task + async def set_lid_temperature(self, temperature: float) -> None: """Set the lid temperature in degrees Celsius""" await self.wait_for_is_running() @@ -574,7 +607,7 @@ async def _execute_cycles( self, steps: List[types.ThermocyclerStep], repetitions: int, - volume: Optional[float] = None, + volume: Optional[float], ) -> None: """ Execute cycles. @@ -592,6 +625,30 @@ async def _execute_cycles( self._current_step_index = step_idx + 1 # science starts at 1 await self._execute_cycle_step(step, volume) + async def _execute_profile( + self, + profile: List[Union[types.ThermocyclerCycle, types.ThermocyclerStep]], + volume: Optional[float], + ) -> None: + """ + Execute profiles. + + Profiles command a thermocycler pattern that can contain multiple cycles and out-of-cycle steps. + """ + self._current_cycle_index = 0 + self._current_step_index = 0 + for step_or_cycle in profile: + self._current_cycle_index += 1 + if "repetitions" in step_or_cycle: + # basically https://github.com/python/mypy/issues/14766 + this_cycle = cast(types.ThermocyclerCycle, step_or_cycle) + for rep in range(this_cycle["repetitions"]): + for step in this_cycle["steps"]: + self._current_step_index += 1 + await self._execute_cycle_step(step, volume) + else: + await self._execute_cycle_step(step_or_cycle, volume) + # TODO(mc, 2022-10-13): why does this exist? # Do the driver and poller really need to be disconnected? # Could we accomplish the same thing by latching the error state diff --git a/api/src/opentrons/hardware_control/modules/types.py b/api/src/opentrons/hardware_control/modules/types.py index c7b3c920c79..9b7c33058d4 100644 --- a/api/src/opentrons/hardware_control/modules/types.py +++ b/api/src/opentrons/hardware_control/modules/types.py @@ -3,6 +3,7 @@ from dataclasses import dataclass from typing import ( Dict, + List, NamedTuple, Callable, Any, @@ -38,6 +39,11 @@ class ThermocyclerStep(ThermocyclerStepBase, total=False): hold_time_minutes: float +class ThermocyclerCycle(TypedDict): + steps: List[ThermocyclerStep] + repetitions: int + + UploadFunction = Callable[[str, str, Dict[str, Any]], Awaitable[Tuple[bool, str]]] @@ -46,7 +52,7 @@ class ThermocyclerStep(ThermocyclerStepBase, total=False): class LiveData(TypedDict): status: str - data: Dict[str, Union[float, str, bool, None]] + data: Dict[str, Union[float, str, bool, List[int], None]] class ModuleType(str, Enum): diff --git a/api/src/opentrons/hardware_control/motion_utilities.py b/api/src/opentrons/hardware_control/motion_utilities.py index 15604dfd360..dd59437f7dc 100644 --- a/api/src/opentrons/hardware_control/motion_utilities.py +++ b/api/src/opentrons/hardware_control/motion_utilities.py @@ -1,4 +1,6 @@ """Utilities for calculating motion correctly.""" +from logging import getLogger + from functools import lru_cache from typing import Callable, Dict, Union, Optional, cast from collections import OrderedDict @@ -11,6 +13,7 @@ from .types import Axis, OT3Mount +log = getLogger(__name__) # TODO: The offset_for_mount function should be defined with an overload # set, as with other functions in this module. Unfortunately, mypy < 0.920 @@ -36,6 +39,19 @@ # ) -> Point: # ... +EMPTY_ORDERED_DICT = OrderedDict( + ( + (Axis.X, 0.0), + (Axis.Y, 0.0), + (Axis.Z_L, 0.0), + (Axis.Z_R, 0.0), + (Axis.Z_G, 0.0), + (Axis.P_L, 0.0), + (Axis.P_R, 0.0), + (Axis.Q, 0.0), + ) +) + @lru_cache(4) def offset_for_mount( @@ -68,6 +84,7 @@ def target_position_from_absolute( ) primary_cp = get_critical_point(mount) primary_z = Axis.by_mount(mount) + target_position = OrderedDict( ( (Axis.X, abs_position.x - offset.x - primary_cp.x), @@ -97,6 +114,57 @@ def target_position_from_relative( return target_position +def target_axis_map_from_absolute( + primary_mount: Union[OT3Mount, Mount], + axis_map: Dict[Axis, float], + get_critical_point: Callable[[Union[Mount, OT3Mount]], Point], + left_mount_offset: Point, + right_mount_offset: Point, + gripper_mount_offset: Optional[Point] = None, +) -> "OrderedDict[Axis, float]": + """Create an absolute target position for all specified machine axes.""" + keys_for_target_position = list(axis_map.keys()) + + offset = offset_for_mount( + primary_mount, left_mount_offset, right_mount_offset, gripper_mount_offset + ) + primary_cp = get_critical_point(primary_mount) + primary_z = Axis.by_mount(primary_mount) + target_position = OrderedDict() + + if Axis.X in keys_for_target_position: + target_position[Axis.X] = axis_map[Axis.X] - offset.x - primary_cp.x + if Axis.Y in keys_for_target_position: + target_position[Axis.Y] = axis_map[Axis.Y] - offset.y - primary_cp.y + if primary_z in keys_for_target_position: + # Since this function is intended to be used in conjunction with `API.move_axes` + # we must leave out the carriage offset subtraction from the target position as + # `move_axes` already does this calculation. + target_position[primary_z] = axis_map[primary_z] - primary_cp.z + + target_position.update( + {ax: val for ax, val in axis_map.items() if ax not in Axis.gantry_axes()} + ) + return target_position + + +def target_axis_map_from_relative( + axis_map: Dict[Axis, float], + current_position: Dict[Axis, float], +) -> "OrderedDict[Axis, float]": + """Create a target position for all specified machine axes.""" + target_position = OrderedDict( + ( + (ax, current_position[ax] + axis_map[ax]) + for ax in EMPTY_ORDERED_DICT.keys() + if ax in axis_map.keys() + ) + ) + log.info(f"Current position {current_position} and axis map delta {axis_map}") + log.info(f"Relative move target {target_position}") + return target_position + + def target_position_from_plunger( mount: Union[Mount, OT3Mount], delta: float, diff --git a/api/src/opentrons/hardware_control/nozzle_manager.py b/api/src/opentrons/hardware_control/nozzle_manager.py index bf42476f7ee..a761b9bcbe4 100644 --- a/api/src/opentrons/hardware_control/nozzle_manager.py +++ b/api/src/opentrons/hardware_control/nozzle_manager.py @@ -1,11 +1,13 @@ from typing import Dict, List, Optional, Any, Sequence, Iterator, Tuple, cast from dataclasses import dataclass from collections import OrderedDict -from enum import Enum from itertools import chain from opentrons.hardware_control.types import CriticalPoint -from opentrons.types import Point +from opentrons.types import ( + Point, + NozzleConfigurationType, +) from opentrons_shared_data.pipette.pipette_definition import ( PipetteGeometryDefinition, PipetteRowDefinition, @@ -41,43 +43,6 @@ def _row_col_indices_for_nozzle( ) -class NozzleConfigurationType(Enum): - """ - Nozzle Configuration Type. - - Represents the current nozzle - configuration stored in NozzleMap - """ - - COLUMN = "COLUMN" - ROW = "ROW" - SINGLE = "SINGLE" - FULL = "FULL" - SUBRECT = "SUBRECT" - - @classmethod - def determine_nozzle_configuration( - cls, - physical_rows: "OrderedDict[str, List[str]]", - current_rows: "OrderedDict[str, List[str]]", - physical_cols: "OrderedDict[str, List[str]]", - current_cols: "OrderedDict[str, List[str]]", - ) -> "NozzleConfigurationType": - """ - Determine the nozzle configuration based on the starting and - ending nozzle. - """ - if physical_rows == current_rows and physical_cols == current_cols: - return NozzleConfigurationType.FULL - if len(current_rows) == 1 and len(current_cols) == 1: - return NozzleConfigurationType.SINGLE - if len(current_rows) == 1: - return NozzleConfigurationType.ROW - if len(current_cols) == 1: - return NozzleConfigurationType.COLUMN - return NozzleConfigurationType.SUBRECT - - @dataclass class NozzleMap: """ @@ -113,6 +78,28 @@ class NozzleMap: full_instrument_rows: Dict[str, List[str]] #: A map of all the rows of an instrument + @classmethod + def determine_nozzle_configuration( + cls, + physical_rows: "OrderedDict[str, List[str]]", + current_rows: "OrderedDict[str, List[str]]", + physical_cols: "OrderedDict[str, List[str]]", + current_cols: "OrderedDict[str, List[str]]", + ) -> "NozzleConfigurationType": + """ + Determine the nozzle configuration based on the starting and + ending nozzle. + """ + if physical_rows == current_rows and physical_cols == current_cols: + return NozzleConfigurationType.FULL + if len(current_rows) == 1 and len(current_cols) == 1: + return NozzleConfigurationType.SINGLE + if len(current_rows) == 1: + return NozzleConfigurationType.ROW + if len(current_cols) == 1: + return NozzleConfigurationType.COLUMN + return NozzleConfigurationType.SUBRECT + def __str__(self) -> str: return f"back_left_nozzle: {self.back_left} front_right_nozzle: {self.front_right} configuration: {self.configuration}" @@ -216,6 +203,16 @@ def tip_count(self) -> int: """The total number of active nozzles in the configuration, and thus the number of tips that will be picked up.""" return len(self.map_store) + @property + def physical_nozzle_count(self) -> int: + """The number of physical nozzles, regardless of configuration.""" + return len(self.full_instrument_map_store) + + @property + def active_nozzles(self) -> list[str]: + """An unstructured list of all nozzles active in the configuration.""" + return list(self.map_store.keys()) + @classmethod def build( # noqa: C901 cls, @@ -274,7 +271,7 @@ def build( # noqa: C901 ) if ( - NozzleConfigurationType.determine_nozzle_configuration( + cls.determine_nozzle_configuration( physical_rows, rows, physical_columns, columns ) != NozzleConfigurationType.FULL @@ -289,6 +286,7 @@ def build( # noqa: C901 if valid_nozzle_maps.maps[map_key] == list(map_store.keys()): validated_map_key = map_key break + if validated_map_key is None: raise IncompatibleNozzleConfiguration( "Attempted Nozzle Configuration does not match any approved map layout for the current pipette." @@ -302,7 +300,7 @@ def build( # noqa: C901 full_instrument_map_store=physical_nozzles, full_instrument_rows=physical_rows, columns=columns, - configuration=NozzleConfigurationType.determine_nozzle_configuration( + configuration=cls.determine_nozzle_configuration( physical_rows, rows, physical_columns, columns ), ) diff --git a/api/src/opentrons/hardware_control/ot3_calibration.py b/api/src/opentrons/hardware_control/ot3_calibration.py index e49b4de171f..b0ebcd027ce 100644 --- a/api/src/opentrons/hardware_control/ot3_calibration.py +++ b/api/src/opentrons/hardware_control/ot3_calibration.py @@ -819,13 +819,13 @@ async def find_pipette_offset( try: if reset_instrument_offset: await hcapi.reset_instrument_offset(mount) - await hcapi.add_tip(mount, hcapi.config.calibration.probe_length) + hcapi.add_tip(mount, hcapi.config.calibration.probe_length) offset = await _calibrate_mount( hcapi, mount, slot, method, raise_verify_error, probe=probe ) return offset finally: - await hcapi.remove_tip(mount) + hcapi.remove_tip(mount) async def calibrate_pipette( @@ -877,7 +877,7 @@ async def calibrate_module( if mount == OT3Mount.GRIPPER: hcapi.add_gripper_probe(GripperProbe.FRONT) else: - await hcapi.add_tip(mount, hcapi.config.calibration.probe_length) + hcapi.add_tip(mount, hcapi.config.calibration.probe_length) LOG.info( f"Starting module calibration for {module_id} at {nominal_position} using {mount}" @@ -903,7 +903,7 @@ async def calibrate_module( hcapi.remove_gripper_probe() await hcapi.ungrip() else: - await hcapi.remove_tip(mount) + hcapi.remove_tip(mount) async def calibrate_belts( @@ -927,7 +927,7 @@ async def calibrate_belts( raise RuntimeError("Must use pipette mount, not gripper") try: hcapi.reset_deck_calibration() - await hcapi.add_tip(mount, hcapi.config.calibration.probe_length) + hcapi.add_tip(mount, hcapi.config.calibration.probe_length) belt_attitude, alignment_details = await _determine_transform_matrix( hcapi, mount ) @@ -935,7 +935,7 @@ async def calibrate_belts( return belt_attitude, alignment_details finally: hcapi.load_deck_calibration() - await hcapi.remove_tip(mount) + hcapi.remove_tip(mount) def apply_machine_transform( diff --git a/api/src/opentrons/hardware_control/ot3api.py b/api/src/opentrons/hardware_control/ot3api.py index 6fa74132aec..6c4b4f291bc 100644 --- a/api/src/opentrons/hardware_control/ot3api.py +++ b/api/src/opentrons/hardware_control/ot3api.py @@ -32,6 +32,7 @@ ) from opentrons_shared_data.pipette import ( pipette_load_name_conversions as pipette_load_name, + pipette_definition, ) from opentrons_shared_data.robot.types import RobotType @@ -45,7 +46,6 @@ LiquidProbeSettings, ) from opentrons.drivers.rpi_drivers.types import USBPort, PortGroup -from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType from opentrons_shared_data.errors.exceptions import ( EnumeratedError, PythonException, @@ -143,7 +143,8 @@ from .backends.flex_protocol import FlexBackend from .backends.ot3simulator import OT3Simulator from .backends.errors import SubsystemUpdating - +from opentrons_hardware.firmware_bindings.constants import SensorId +from opentrons_hardware.sensors.types import SensorDataType mod_log = logging.getLogger(__name__) @@ -164,6 +165,7 @@ def _adjust_high_throughput_z_current(func: Wrapped) -> Wrapped: A decorator that temproarily and conditionally changes the active current (based on the axis input) before a function is executed and the cleans up afterwards """ + # only home and retract should be wrappeed by this decorator @wraps(func) async def wrapper(self: Any, axis: Axis, *args: Any, **kwargs: Any) -> Any: @@ -297,8 +299,11 @@ async def set_system_constraints_for_calibration(self) -> None: async def set_system_constraints_for_plunger_acceleration( self, mount: OT3Mount, acceleration: float ) -> None: + high_speed_pipette = self._pipette_handler.get_pipette( + mount + ).is_high_speed_pipette() self._backend.update_constraints_for_plunger_acceleration( - mount, acceleration, self._gantry_load + mount, acceleration, self._gantry_load, high_speed_pipette ) @contextlib.asynccontextmanager @@ -306,6 +311,12 @@ async def restore_system_constrants(self) -> AsyncIterator[None]: async with self._backend.restore_system_constraints(): yield + @contextlib.asynccontextmanager + async def grab_pressure(self, mount: OT3Mount) -> AsyncIterator[None]: + instrument = self._pipette_handler.get_pipette(mount) + async with self._backend.grab_pressure(instrument.channels, mount): + yield + def _update_door_state(self, door_state: DoorState) -> None: mod_log.info(f"Updating the window switch status: {door_state}") self.door_state = door_state @@ -345,7 +356,9 @@ def _update_estop_state(self, event: HardwareEvent) -> "List[Future[None]]": def _reset_last_mount(self) -> None: self._last_moved_mount = None - def _deck_from_machine(self, machine_pos: Dict[Axis, float]) -> Dict[Axis, float]: + def get_deck_from_machine( + self, machine_pos: Dict[Axis, float] + ) -> Dict[Axis, float]: return deck_from_machine( machine_pos=machine_pos, attitude=self._robot_calibration.deck_calibration.attitude, @@ -625,10 +638,31 @@ async def cache_pipette( self._feature_flags.use_old_aspiration_functions, ) self._pipette_handler.hardware_instruments[mount] = p + + if config is not None: + self._set_pressure_sensor_available(mount, instrument_config=config) + # TODO (lc 12-5-2022) Properly support backwards compatibility # when applicable return skipped + def get_pressure_sensor_available(self, mount: OT3Mount) -> bool: + pip_axis = Axis.of_main_tool_actuator(mount) + return self._backend.get_pressure_sensor_available(pip_axis) + + def _set_pressure_sensor_available( + self, + mount: OT3Mount, + instrument_config: pipette_definition.PipetteConfigurations, + ) -> None: + pressure_sensor_available = ( + "pressure" in instrument_config.available_sensors.sensors + ) + pip_axis = Axis.of_main_tool_actuator(mount) + self._backend.set_pressure_sensor_available( + pipette_axis=pip_axis, available=pressure_sensor_available + ) + async def cache_gripper(self, instrument_data: AttachedGripper) -> bool: """Set up gripper based on scanned information.""" grip_cal = load_gripper_calibration_offset(instrument_data.get("id")) @@ -767,12 +801,14 @@ async def _update_position_estimation( """ Function to update motor estimation for a set of axes """ + await self._backend.update_motor_status() - if axes: - checked_axes = [ax for ax in axes if ax in Axis] - else: - checked_axes = [ax for ax in Axis] - await self._backend.update_motor_estimation(checked_axes) + if axes is None: + axes = [ax for ax in Axis] + + axes = [ax for ax in axes if self._backend.axis_is_present(ax)] + + await self._backend.update_motor_estimation(axes) # Global actions API def pause(self, pause_type: PauseType) -> None: @@ -933,11 +969,10 @@ async def home_gear_motors(self) -> None: current_pos_float > self._config.safe_home_distance and current_pos_float < max_distance ): - # move toward home until a safe distance await self._backend.tip_action( - origin={Axis.Q: current_pos_float}, - targets=[({Axis.Q: self._config.safe_home_distance}, 400)], + origin=current_pos_float, + targets=[(self._config.safe_home_distance, 400)], ) # update current position @@ -1014,14 +1049,14 @@ async def _refresh_jaw_state(self) -> None: async def _cache_current_position(self) -> Dict[Axis, float]: """Cache current position from backend and return in absolute deck coords.""" - self._current_position = self._deck_from_machine( + self._current_position = self.get_deck_from_machine( await self._backend.update_position() ) return self._current_position async def _cache_encoder_position(self) -> Dict[Axis, float]: """Cache encoder position from backend and return in absolute deck coords.""" - self._encoder_position = self._deck_from_machine( + self._encoder_position = self.get_deck_from_machine( await self._backend.update_encoder_position() ) if self.has_gripper(): @@ -1221,7 +1256,9 @@ async def move_axes( # noqa: C901 message=f"{axis} is not present", detail={"axis": str(axis)} ) + self._log.info(f"Attempting to move {position} with speed {speed}.") if not self._backend.check_encoder_status(list(position.keys())): + self._log.info("Calling home in move_axes") await self.home() self._assert_motor_ok(list(position.keys())) @@ -1432,6 +1469,10 @@ async def _move( check_motion_bounds(to_check, target_position, bounds, check_bounds) self._log.info(f"Move: deck {target_position} becomes machine {machine_pos}") origin = await self._backend.update_position() + + if self._gantry_load == GantryLoad.HIGH_THROUGHPUT: + origin[Axis.Q] = self._backend.gear_motor_position or 0.0 + async with contextlib.AsyncExitStack() as stack: if acquire_lock: await stack.enter_async_context(self._motion_lock) @@ -1633,7 +1674,12 @@ async def disengage_axes(self, which: List[Axis]) -> None: await self._backend.disengage_axes(which) async def engage_axes(self, which: List[Axis]) -> None: - await self._backend.engage_axes(which) + await self._backend.engage_axes( + [axis for axis in which if self._backend.axis_is_present(axis)] + ) + + def axis_is_present(self, axis: Axis) -> bool: + return self._backend.axis_is_present(axis) async def get_limit_switches(self) -> Dict[Axis, bool]: res = await self._backend.get_limit_switches() @@ -1811,14 +1857,15 @@ async def tip_pickup_moves( increment: Optional[float] = None, ) -> None: """This is a slightly more barebones variation of pick_up_tip. This is only the motor routine - directly involved in tip pickup, and leaves any state updates and plunger moves to the caller.""" + directly involved in tip pickup, and leaves any state updates and plunger moves to the caller. + """ realmount = OT3Mount.from_mount(mount) instrument = self._pipette_handler.get_pipette(realmount) if ( self.gantry_load == GantryLoad.HIGH_THROUGHPUT and instrument.nozzle_manager.current_configuration.configuration - == NozzleConfigurationType.FULL + == top_types.NozzleConfigurationType.FULL ): spec = self._pipette_handler.plan_ht_pick_up_tip( instrument.nozzle_manager.current_configuration.tip_count @@ -2148,8 +2195,8 @@ async def _high_throughput_check_tip(self) -> AsyncIterator[None]: # only move tip motors if they are not already below the sensor if tip_motor_pos_float < tip_presence_check_target: await self._backend.tip_action( - origin={Axis.Q: tip_motor_pos_float}, - targets=[({Axis.Q: tip_presence_check_target}, 400)], + origin=tip_motor_pos_float, + targets=[(tip_presence_check_target, 400)], ) try: yield @@ -2220,23 +2267,14 @@ async def _tip_motor_action( gear_origin_float = self._backend.gear_motor_position or 0.0 move_targets = [ - ({Axis.Q: move_segment.distance}, move_segment.speed or 400) + (move_segment.distance, move_segment.speed or 400) for move_segment in pipette_spec ] await self._backend.tip_action( - origin={Axis.Q: gear_origin_float}, targets=move_targets + origin=gear_origin_float, targets=move_targets ) await self.home_gear_motors() - def cache_tip( - self, mount: Union[top_types.Mount, OT3Mount], tip_length: float - ) -> None: - realmount = OT3Mount.from_mount(mount) - instrument = self._pipette_handler.get_pipette(realmount) - - instrument.add_tip(tip_length=tip_length) - instrument.set_current_volume(0) - async def pick_up_tip( self, mount: Union[top_types.Mount, OT3Mount], @@ -2282,17 +2320,10 @@ def set_working_volume( ) instrument.working_volume = tip_volume - async def drop_tip( + async def tip_drop_moves( self, mount: Union[top_types.Mount, OT3Mount], home_after: bool = False ) -> None: - """Drop tip at the current location.""" realmount = OT3Mount.from_mount(mount) - instrument = self._pipette_handler.get_pipette(realmount) - - def _remove_tips() -> None: - instrument.set_current_volume(0) - instrument.current_tiprack_diameter = 0.0 - instrument.remove_tip() await self._move_to_plunger_bottom(realmount, rate=1.0, check_current_vol=False) @@ -2319,11 +2350,27 @@ def _remove_tips() -> None: if home_after: await self._home([Axis.by_mount(mount)]) - _remove_tips() - # call this in case we're simulating + # call this in case we're simulating: if isinstance(self._backend, OT3Simulator): self._backend._update_tip_state(realmount, False) + async def drop_tip( + self, mount: Union[top_types.Mount, OT3Mount], home_after: bool = False + ) -> None: + """Drop tip at the current location.""" + await self.tip_drop_moves(mount=mount, home_after=home_after) + + # todo(mm, 2024-10-17): Ideally, callers would be able to replicate the behavior + # of this method via self.drop_tip_moves() plus other public methods. This + # currently prevents that: there is no public equivalent for + # instrument.set_current_volume(). + realmount = OT3Mount.from_mount(mount) + instrument = self._pipette_handler.get_pipette(realmount) + instrument.set_current_volume(0) + + self.set_current_tiprack_diameter(mount, 0.0) + self.remove_tip(mount) + async def clean_up(self) -> None: """Get the API ready to stop cleanly.""" await self._backend.clean_up() @@ -2549,7 +2596,7 @@ def get_instrument_max_height( mount: Union[top_types.Mount, OT3Mount], critical_point: Optional[CriticalPoint] = None, ) -> float: - carriage_pos = self._deck_from_machine(self._backend.home_position()) + carriage_pos = self.get_deck_from_machine(self._backend.home_position()) pos_at_home = self._effector_pos_from_carriage_pos( OT3Mount.from_mount(mount), carriage_pos, critical_point ) @@ -2591,13 +2638,18 @@ async def update_nozzle_configuration_for_mount( starting_nozzle, ) - async def add_tip( + def add_tip( self, mount: Union[top_types.Mount, OT3Mount], tip_length: float ) -> None: - await self._pipette_handler.add_tip(OT3Mount.from_mount(mount), tip_length) + self._pipette_handler.add_tip(OT3Mount.from_mount(mount), tip_length) - async def remove_tip(self, mount: Union[top_types.Mount, OT3Mount]) -> None: - await self._pipette_handler.remove_tip(OT3Mount.from_mount(mount)) + def cache_tip( + self, mount: Union[top_types.Mount, OT3Mount], tip_length: float + ) -> None: + self._pipette_handler.cache_tip(OT3Mount.from_mount(mount), tip_length) + + def remove_tip(self, mount: Union[top_types.Mount, OT3Mount]) -> None: + self._pipette_handler.remove_tip(OT3Mount.from_mount(mount)) def add_gripper_probe(self, probe: GripperProbe) -> None: self._gripper_handler.add_probe(probe) @@ -2627,6 +2679,9 @@ async def _liquid_probe_pass( probe: InstrumentProbeType, p_travel: float, force_both_sensors: bool = False, + response_queue: Optional[ + asyncio.Queue[Dict[SensorId, List[SensorDataType]]] + ] = None, ) -> float: plunger_direction = -1 if probe_settings.aspirate_while_sensing else 1 end_z = await self._backend.liquid_probe( @@ -2636,14 +2691,14 @@ async def _liquid_probe_pass( (probe_settings.plunger_speed * plunger_direction), probe_settings.sensor_threshold_pascals, probe_settings.plunger_impulse_time, - probe_settings.output_option, - probe_settings.data_files, + probe_settings.samples_for_baselining, probe=probe, force_both_sensors=force_both_sensors, + response_queue=response_queue, ) machine_pos = await self._backend.update_position() machine_pos[Axis.by_mount(mount)] = end_z - deck_end_z = self._deck_from_machine(machine_pos)[Axis.by_mount(mount)] + deck_end_z = self.get_deck_from_machine(machine_pos)[Axis.by_mount(mount)] offset = offset_for_mount( mount, top_types.Point(*self._config.left_mount_offset), @@ -2653,13 +2708,16 @@ async def _liquid_probe_pass( cp = self.critical_point_for(mount, None) return deck_end_z + offset.z + cp.z - async def liquid_probe( + async def liquid_probe( # noqa: C901 self, mount: Union[top_types.Mount, OT3Mount], max_z_dist: float, probe_settings: Optional[LiquidProbeSettings] = None, probe: Optional[InstrumentProbeType] = None, force_both_sensors: bool = False, + response_queue: Optional[ + asyncio.Queue[Dict[SensorId, List[SensorDataType]]] + ] = None, ) -> float: """Search for and return liquid level height. @@ -2682,6 +2740,16 @@ async def liquid_probe( self._pipette_handler.ready_for_tip_action( instrument, HardwareAction.LIQUID_PROBE, checked_mount ) + # default to using all available sensors + if probe: + checked_probe = probe + else: + checked_probe = ( + InstrumentProbeType.BOTH + if instrument.channels > 1 + else InstrumentProbeType.PRIMARY + ) + if not probe_settings: probe_settings = deepcopy(self.config.liquid_sense) @@ -2773,8 +2841,10 @@ async def prep_plunger_for_probe_move( height = await self._liquid_probe_pass( checked_mount, probe_settings, - probe if probe else InstrumentProbeType.PRIMARY, + checked_probe, plunger_travel_mm + sensor_baseline_plunger_move_mm, + force_both_sensors, + response_queue, ) # if we made it here without an error we found the liquid error = None @@ -2843,8 +2913,6 @@ async def capacitive_probe( pass_settings.speed_mm_per_s, pass_settings.sensor_threshold_pf, probe, - pass_settings.output_option, - pass_settings.data_files, ) end_pos = await self.gantry_position(mount, refresh=True) if retract_after: diff --git a/api/src/opentrons/hardware_control/protocols/__init__.py b/api/src/opentrons/hardware_control/protocols/__init__.py index cff17ff1d9a..13266ac731c 100644 --- a/api/src/opentrons/hardware_control/protocols/__init__.py +++ b/api/src/opentrons/hardware_control/protocols/__init__.py @@ -58,9 +58,6 @@ class HardwareControlInterface( def get_robot_type(self) -> Type[OT2RobotType]: return OT2RobotType - def cache_tip(self, mount: MountArgType, tip_length: float) -> None: - ... - class FlexHardwareControlInterface( PositionEstimator, @@ -87,12 +84,9 @@ class FlexHardwareControlInterface( def get_robot_type(self) -> Type[FlexRobotType]: return FlexRobotType - def cache_tip(self, mount: MountArgType, tip_length: float) -> None: - ... - __all__ = [ - "HardwareControlAPI", + "HardwareControlInterface", "FlexHardwareControlInterface", "Simulatable", "Stoppable", diff --git a/api/src/opentrons/hardware_control/protocols/gripper_controller.py b/api/src/opentrons/hardware_control/protocols/gripper_controller.py index fc81325193c..1b81f4ab460 100644 --- a/api/src/opentrons/hardware_control/protocols/gripper_controller.py +++ b/api/src/opentrons/hardware_control/protocols/gripper_controller.py @@ -14,6 +14,9 @@ async def grip( ) -> None: ... + async def home_gripper_jaw(self) -> None: + ... + async def ungrip(self, force_newtons: Optional[float] = None) -> None: """Release gripped object. diff --git a/api/src/opentrons/hardware_control/protocols/hardware_manager.py b/api/src/opentrons/hardware_control/protocols/hardware_manager.py index ee0228ae3b8..d2bfd94a06b 100644 --- a/api/src/opentrons/hardware_control/protocols/hardware_manager.py +++ b/api/src/opentrons/hardware_control/protocols/hardware_manager.py @@ -1,7 +1,7 @@ from typing import Dict, Optional from typing_extensions import Protocol -from ..types import SubSystem, SubSystemState +from ..types import SubSystem, SubSystemState, Axis class HardwareManager(Protocol): @@ -45,3 +45,7 @@ def attached_subsystems(self) -> Dict[SubSystem, SubSystemState]: async def get_serial_number(self) -> Optional[str]: """Get the robot serial number, if provisioned. If not provisioned, will be None.""" ... + + def axis_is_present(self, axis: Axis) -> bool: + """Get whether a motor axis is present on the machine.""" + ... diff --git a/api/src/opentrons/hardware_control/protocols/instrument_configurer.py b/api/src/opentrons/hardware_control/protocols/instrument_configurer.py index 11e718a9aff..5cd85716e36 100644 --- a/api/src/opentrons/hardware_control/protocols/instrument_configurer.py +++ b/api/src/opentrons/hardware_control/protocols/instrument_configurer.py @@ -142,15 +142,27 @@ def get_instrument_max_height( """ ... - async def add_tip(self, mount: MountArgType, tip_length: float) -> None: + # todo(mm, 2024-10-17): Consider deleting this in favor of cache_tip() + # if we can do so without breaking anything. + def add_tip(self, mount: MountArgType, tip_length: float) -> None: """Inform the hardware that a tip is now attached to a pipette. + If a tip is already attached, this no-ops. + This changes the critical point of the pipette to make sure that the end of the tip is what moves around, and allows liquid handling. """ ... - async def remove_tip(self, mount: MountArgType) -> None: + def cache_tip(self, mount: MountArgType, tip_length: float) -> None: + """Inform the hardware that a tip is now attached to a pipette. + + This is like `add_tip()`, except that if a tip is already attached, + this replaces it instead of no-opping. + """ + ... + + def remove_tip(self, mount: MountArgType) -> None: """Inform the hardware that a tip is no longer attached to a pipette. This changes the critical point of the system to the end of the diff --git a/api/src/opentrons/hardware_control/protocols/liquid_handler.py b/api/src/opentrons/hardware_control/protocols/liquid_handler.py index 8baa786dc9f..2aea15bd55b 100644 --- a/api/src/opentrons/hardware_control/protocols/liquid_handler.py +++ b/api/src/opentrons/hardware_control/protocols/liquid_handler.py @@ -1,6 +1,8 @@ from typing import Optional from typing_extensions import Protocol +from opentrons.types import Point +from opentrons.hardware_control.types import CriticalPoint from .types import MountArgType, CalibrationType, ConfigType from .instrument_configurer import InstrumentConfigurer @@ -16,6 +18,22 @@ class LiquidHandler( Calibratable[CalibrationType], Protocol[CalibrationType, MountArgType, ConfigType], ): + def critical_point_for( + self, + mount: MountArgType, + cp_override: Optional[CriticalPoint] = None, + ) -> Point: + """ + Determine the current critical point for the specified mount. + + :param mount: A robot mount that the instrument is on. + :param cp_override: The critical point override to use. + + If no critical point override is specified, the robot defaults to nozzle location `A1` or the mount critical point. + :return: Point. + """ + ... + async def update_nozzle_configuration_for_mount( self, mount: MountArgType, @@ -164,6 +182,11 @@ async def pick_up_tip( """ ... + async def tip_drop_moves( + self, mount: MountArgType, home_after: bool = True + ) -> None: + ... + async def drop_tip( self, mount: MountArgType, diff --git a/api/src/opentrons/hardware_control/protocols/motion_controller.py b/api/src/opentrons/hardware_control/protocols/motion_controller.py index 8387e4a907c..e95a9d2e24f 100644 --- a/api/src/opentrons/hardware_control/protocols/motion_controller.py +++ b/api/src/opentrons/hardware_control/protocols/motion_controller.py @@ -9,6 +9,12 @@ class MotionController(Protocol[MountArgType]): """Protocol specifying fundamental motion controls.""" + def get_deck_from_machine( + self, machine_pos: Dict[Axis, float] + ) -> Dict[Axis, float]: + """Convert machine coordinates to deck coordinates.""" + ... + async def halt(self, disengage_before_stopping: bool = False) -> None: """Immediately stop motion. @@ -204,6 +210,10 @@ async def disengage_axes(self, which: List[Axis]) -> None: """Disengage some axes.""" ... + async def engage_axes(self, which: List[Axis]) -> None: + """Engage some axes.""" + ... + async def retract(self, mount: MountArgType, margin: float = 10) -> None: """Pull the specified mount up to its home position. diff --git a/api/src/opentrons/hardware_control/protocols/position_estimator.py b/api/src/opentrons/hardware_control/protocols/position_estimator.py index 04d551020c3..fc4e1521a89 100644 --- a/api/src/opentrons/hardware_control/protocols/position_estimator.py +++ b/api/src/opentrons/hardware_control/protocols/position_estimator.py @@ -10,7 +10,7 @@ async def update_axis_position_estimations(self, axes: Sequence[Axis]) -> None: """Update the specified axes' position estimators from their encoders. This will allow these axes to make a non-home move even if they do not currently have - a position estimation (unless there is no tracked poition from the encoders, as would be + a position estimation (unless there is no tracked position from the encoders, as would be true immediately after boot). Axis encoders have less precision than their position estimators. Calling this function will @@ -19,6 +19,8 @@ async def update_axis_position_estimations(self, axes: Sequence[Axis]) -> None: This function updates only the requested axes. If other axes have bad position estimation, moves that require those axes or attempts to get the position of those axes will still fail. + Axes that are not currently available (like a plunger for a pipette that is not connected) + will be ignored. """ ... diff --git a/api/src/opentrons/hardware_control/types.py b/api/src/opentrons/hardware_control/types.py index 62265afffcc..bc32431d2a5 100644 --- a/api/src/opentrons/hardware_control/types.py +++ b/api/src/opentrons/hardware_control/types.py @@ -625,6 +625,8 @@ class GripperJawState(enum.Enum): #: the gripper is actively force-control gripping something HOLDING = enum.auto() #: the gripper is in position-control mode + STOPPED = enum.auto() + #: the gripper has been homed before but is stopped now class InstrumentProbeType(enum.Enum): diff --git a/api/src/opentrons/legacy_commands/helpers.py b/api/src/opentrons/legacy_commands/helpers.py index b3de03de4bc..5b08bb1e436 100644 --- a/api/src/opentrons/legacy_commands/helpers.py +++ b/api/src/opentrons/legacy_commands/helpers.py @@ -49,7 +49,9 @@ def stringify_disposal_location(location: Union[TrashBin, WasteChute]) -> str: def _stringify_labware_movement_location( - location: Union[DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute] + location: Union[ + DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute, TrashBin + ] ) -> str: if isinstance(location, (int, str)): return f"slot {location}" @@ -61,11 +63,15 @@ def _stringify_labware_movement_location( return str(location) elif isinstance(location, WasteChute): return "Waste Chute" + elif isinstance(location, TrashBin): + return "Trash Bin " + location.location.name def stringify_labware_movement_command( source_labware: Labware, - destination: Union[DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute], + destination: Union[ + DeckLocation, OffDeckType, Labware, ModuleContext, WasteChute, TrashBin + ], use_gripper: bool, ) -> str: source_labware_text = _stringify_labware_movement_location(source_labware) diff --git a/api/src/opentrons/motion_planning/__init__.py b/api/src/opentrons/motion_planning/__init__.py index 570d4250ebe..2b304ecb74d 100644 --- a/api/src/opentrons/motion_planning/__init__.py +++ b/api/src/opentrons/motion_planning/__init__.py @@ -6,6 +6,7 @@ MINIMUM_Z_MARGIN, get_waypoints, get_gripper_labware_movement_waypoints, + get_gripper_labware_placement_waypoints, ) from .types import Waypoint, MoveType @@ -27,4 +28,5 @@ "ArcOutOfBoundsError", "get_waypoints", "get_gripper_labware_movement_waypoints", + "get_gripper_labware_placement_waypoints", ] diff --git a/api/src/opentrons/motion_planning/waypoints.py b/api/src/opentrons/motion_planning/waypoints.py index b9c62114215..bcc56ad7eda 100644 --- a/api/src/opentrons/motion_planning/waypoints.py +++ b/api/src/opentrons/motion_planning/waypoints.py @@ -181,3 +181,35 @@ def get_gripper_labware_movement_waypoints( ) ) return waypoints_with_jaw_status + + +def get_gripper_labware_placement_waypoints( + to_labware_center: Point, + gripper_home_z: float, + drop_offset: Optional[Point], +) -> List[GripperMovementWaypointsWithJawStatus]: + """Get waypoints for placing labware using a gripper.""" + drop_offset = drop_offset or Point() + + drop_location = to_labware_center + Point( + drop_offset.x, drop_offset.y, drop_offset.z + ) + + post_drop_home_pos = Point(drop_location.x, drop_location.y, gripper_home_z) + + return [ + GripperMovementWaypointsWithJawStatus( + position=Point(drop_location.x, drop_location.y, gripper_home_z), + jaw_open=False, + dropping=False, + ), + GripperMovementWaypointsWithJawStatus( + position=drop_location, jaw_open=False, dropping=False + ), + # Gripper ungrips here + GripperMovementWaypointsWithJawStatus( + position=post_drop_home_pos, + jaw_open=True, + dropping=True, + ), + ] diff --git a/api/src/opentrons/protocol_api/__init__.py b/api/src/opentrons/protocol_api/__init__.py index 8cc4bd1154e..41a061f5a94 100644 --- a/api/src/opentrons/protocol_api/__init__.py +++ b/api/src/opentrons/protocol_api/__init__.py @@ -29,8 +29,17 @@ AbsorbanceReaderContext, ) from .disposal_locations import TrashBin, WasteChute -from ._liquid import Liquid -from ._types import OFF_DECK +from ._liquid import Liquid, LiquidClass +from ._types import ( + OFF_DECK, + PLUNGER_BLOWOUT, + PLUNGER_TOP, + PLUNGER_BOTTOM, + PLUNGER_DROPTIP, + ASPIRATE_ACTION, + DISPENSE_ACTION, + BLOWOUT_ACTION, +) from ._nozzle_layout import ( COLUMN, PARTIAL_COLUMN, @@ -67,13 +76,24 @@ "WasteChute", "Well", "Liquid", + "LiquidClass", "Parameters", + # Partial Tip types "COLUMN", "PARTIAL_COLUMN", "SINGLE", "ROW", "ALL", + # Deck location types "OFF_DECK", + # Pipette plunger types + "PLUNGER_BLOWOUT", + "PLUNGER_TOP", + "PLUNGER_BOTTOM", + "PLUNGER_DROPTIP", + "ASPIRATE_ACTION", + "DISPENSE_ACTION", + "BLOWOUT_ACTION", "RuntimeParameterRequiredError", "CSVParameter", # For internal Opentrons use only: diff --git a/api/src/opentrons/protocol_api/_liquid.py b/api/src/opentrons/protocol_api/_liquid.py index b43a9c08495..12c9a140ce3 100644 --- a/api/src/opentrons/protocol_api/_liquid.py +++ b/api/src/opentrons/protocol_api/_liquid.py @@ -1,5 +1,16 @@ +from __future__ import annotations + from dataclasses import dataclass -from typing import Optional +from typing import Optional, Dict + +from opentrons_shared_data.liquid_classes.liquid_class_definition import ( + LiquidClassSchemaV1, +) + +from ._liquid_properties import ( + TransferProperties, + build_transfer_properties, +) @dataclass(frozen=True) @@ -18,3 +29,53 @@ class Liquid: name: str description: Optional[str] display_color: Optional[str] + + +@dataclass +class LiquidClass: + """A data class that contains properties of a specific class of liquids.""" + + _name: str + _display_name: str + _by_pipette_setting: Dict[str, Dict[str, TransferProperties]] + + @classmethod + def create(cls, liquid_class_definition: LiquidClassSchemaV1) -> "LiquidClass": + """Liquid class factory method.""" + + by_pipette_settings: Dict[str, Dict[str, TransferProperties]] = {} + for by_pipette in liquid_class_definition.byPipette: + tip_settings: Dict[str, TransferProperties] = {} + for tip_type in by_pipette.byTipType: + tip_settings[tip_type.tiprack] = build_transfer_properties(tip_type) + by_pipette_settings[by_pipette.pipetteModel] = tip_settings + + return cls( + _name=liquid_class_definition.liquidClassName, + _display_name=liquid_class_definition.displayName, + _by_pipette_setting=by_pipette_settings, + ) + + @property + def name(self) -> str: + return self._name + + @property + def display_name(self) -> str: + return self._display_name + + def get_for(self, pipette: str, tiprack: str) -> TransferProperties: + """Get liquid class transfer properties for the specified pipette and tip.""" + try: + settings_for_pipette = self._by_pipette_setting[pipette] + except KeyError: + raise ValueError( + f"No properties found for {pipette} in {self._name} liquid class" + ) + try: + transfer_properties = settings_for_pipette[tiprack] + except KeyError: + raise ValueError( + f"No properties found for {tiprack} in {self._name} liquid class" + ) + return transfer_properties diff --git a/api/src/opentrons/protocol_api/_liquid_properties.py b/api/src/opentrons/protocol_api/_liquid_properties.py new file mode 100644 index 00000000000..dc848cfb7e2 --- /dev/null +++ b/api/src/opentrons/protocol_api/_liquid_properties.py @@ -0,0 +1,754 @@ +from dataclasses import dataclass +from numpy import interp +from typing import Optional, Dict, Sequence, Tuple, List + +from opentrons_shared_data.liquid_classes.liquid_class_definition import ( + AspirateProperties as SharedDataAspirateProperties, + SingleDispenseProperties as SharedDataSingleDispenseProperties, + MultiDispenseProperties as SharedDataMultiDispenseProperties, + DelayProperties as SharedDataDelayProperties, + DelayParams as SharedDataDelayParams, + TouchTipProperties as SharedDataTouchTipProperties, + LiquidClassTouchTipParams as SharedDataTouchTipParams, + MixProperties as SharedDataMixProperties, + MixParams as SharedDataMixParams, + BlowoutProperties as SharedDataBlowoutProperties, + BlowoutParams as SharedDataBlowoutParams, + ByTipTypeSetting as SharedByTipTypeSetting, + Submerge as SharedDataSubmerge, + RetractAspirate as SharedDataRetractAspirate, + RetractDispense as SharedDataRetractDispense, + BlowoutLocation, + PositionReference, + Coordinate, +) + +from . import validation + + +class LiquidHandlingPropertyByVolume: + def __init__(self, by_volume_property: Sequence[Tuple[float, float]]) -> None: + self._properties_by_volume: Dict[float, float] = { + float(volume): value for volume, value in by_volume_property + } + # Volumes need to be sorted for proper interpolation of non-defined volumes, and the + # corresponding values need to be in the same order for them to be interpolated correctly + self._sorted_volumes: Tuple[float, ...] = () + self._sorted_values: Tuple[float, ...] = () + self._sort_volume_and_values() + + def as_dict(self) -> Dict[float, float]: + """Get a dictionary representation of all set volumes and values along with the default.""" + return self._properties_by_volume + + def as_list_of_tuples(self) -> List[Tuple[float, float]]: + """Get as list of tuples.""" + return list(self._properties_by_volume.items()) + + def get_for_volume(self, volume: float) -> float: + """Get a value by volume for this property. Volumes not defined will be interpolated between set volumes.""" + validated_volume = validation.ensure_positive_float(volume) + if len(self._properties_by_volume) == 0: + raise ValueError( + "No properties found for any volumes. Cannot interpolate for the given volume." + ) + try: + return self._properties_by_volume[validated_volume] + except KeyError: + # If volume is not defined in dictionary, do a piecewise interpolation with existing sorted values + return float( + interp(validated_volume, self._sorted_volumes, self._sorted_values) + ) + + def set_for_volume(self, volume: float, value: float) -> None: + """Add a new volume and value for the property for the interpolation curve.""" + validated_volume = validation.ensure_positive_float(volume) + self._properties_by_volume[validated_volume] = value + self._sort_volume_and_values() + + def delete_for_volume(self, volume: float) -> None: + """Remove an existing volume and value from the property.""" + try: + del self._properties_by_volume[volume] + except KeyError: + raise KeyError(f"No value set for volume {volume} uL") + self._sort_volume_and_values() + + def _sort_volume_and_values(self) -> None: + """Sort volume in increasing order along with corresponding values in matching order.""" + self._sorted_volumes, self._sorted_values = ( + zip(*sorted(self._properties_by_volume.items())) + if len(self._properties_by_volume) > 0 + else [(), ()] + ) + + +@dataclass +class DelayProperties: + + _enabled: bool + _duration: Optional[float] + + @property + def enabled(self) -> bool: + return self._enabled + + @enabled.setter + def enabled(self, enable: bool) -> None: + validated_enable = validation.ensure_boolean(enable) + if validated_enable and self._duration is None: + raise ValueError("duration must be set before enabling delay.") + self._enabled = validated_enable + + @property + def duration(self) -> Optional[float]: + return self._duration + + @duration.setter + def duration(self, new_duration: float) -> None: + validated_duration = validation.ensure_positive_float(new_duration) + self._duration = validated_duration + + def as_shared_data_model(self) -> SharedDataDelayProperties: + return SharedDataDelayProperties( + enable=self._enabled, + params=SharedDataDelayParams(duration=self.duration) + if self.duration is not None + else None, + ) + + +@dataclass +class TouchTipProperties: + + _enabled: bool + _z_offset: Optional[float] + _mm_to_edge: Optional[float] + _speed: Optional[float] + + @property + def enabled(self) -> bool: + return self._enabled + + @enabled.setter + def enabled(self, enable: bool) -> None: + validated_enable = validation.ensure_boolean(enable) + if validated_enable and ( + self._z_offset is None or self._mm_to_edge is None or self._speed is None + ): + raise ValueError( + "z_offset, mm_to_edge and speed must be set before enabling touch tip." + ) + self._enabled = validated_enable + + @property + def z_offset(self) -> Optional[float]: + return self._z_offset + + @z_offset.setter + def z_offset(self, new_offset: float) -> None: + validated_offset = validation.ensure_float(new_offset) + self._z_offset = validated_offset + + @property + def mm_to_edge(self) -> Optional[float]: + return self._mm_to_edge + + @mm_to_edge.setter + def mm_to_edge(self, new_mm: float) -> None: + validated_mm = validation.ensure_float(new_mm) + self._z_offset = validated_mm + + @property + def speed(self) -> Optional[float]: + return self._speed + + @speed.setter + def speed(self, new_speed: float) -> None: + validated_speed = validation.ensure_positive_float(new_speed) + self._speed = validated_speed + + def _get_shared_data_params(self) -> Optional[SharedDataTouchTipParams]: + """Get the touch tip params in schema v1 shape.""" + if ( + self._z_offset is not None + and self._mm_to_edge is not None + and self._speed is not None + ): + return SharedDataTouchTipParams( + zOffset=self._z_offset, + mmToEdge=self._mm_to_edge, + speed=self._speed, + ) + else: + return None + + def as_shared_data_model(self) -> SharedDataTouchTipProperties: + return SharedDataTouchTipProperties( + enable=self._enabled, + params=self._get_shared_data_params(), + ) + + +@dataclass +class MixProperties: + + _enabled: bool + _repetitions: Optional[int] + _volume: Optional[float] + + @property + def enabled(self) -> bool: + return self._enabled + + @enabled.setter + def enabled(self, enable: bool) -> None: + validated_enable = validation.ensure_boolean(enable) + if validated_enable and (self._repetitions is None or self._volume is None): + raise ValueError("repetitions and volume must be set before enabling mix.") + self._enabled = validated_enable + + @property + def repetitions(self) -> Optional[int]: + return self._repetitions + + @repetitions.setter + def repetitions(self, new_repetitions: int) -> None: + validated_repetitions = validation.ensure_positive_int(new_repetitions) + self._repetitions = validated_repetitions + + @property + def volume(self) -> Optional[float]: + return self._volume + + @volume.setter + def volume(self, new_volume: float) -> None: + validated_volume = validation.ensure_positive_float(new_volume) + self._volume = validated_volume + + def _get_shared_data_params(self) -> Optional[SharedDataMixParams]: + """Get the mix params in schema v1 shape.""" + if self._repetitions is not None and self._volume is not None: + return SharedDataMixParams( + repetitions=self._repetitions, + volume=self._volume, + ) + else: + return None + + def as_shared_data_model(self) -> SharedDataMixProperties: + return SharedDataMixProperties( + enable=self._enabled, + params=self._get_shared_data_params(), + ) + + +@dataclass +class BlowoutProperties: + + _enabled: bool + _location: Optional[BlowoutLocation] + _flow_rate: Optional[float] + + @property + def enabled(self) -> bool: + return self._enabled + + @enabled.setter + def enabled(self, enable: bool) -> None: + validated_enable = validation.ensure_boolean(enable) + if validated_enable and (self._location is None or self._flow_rate is None): + raise ValueError( + "location and flow_rate must be set before enabling blowout." + ) + self._enabled = validated_enable + + @property + def location(self) -> Optional[BlowoutLocation]: + return self._location + + @location.setter + def location(self, new_location: str) -> None: + self._location = BlowoutLocation(new_location) + + @property + def flow_rate(self) -> Optional[float]: + return self._flow_rate + + @flow_rate.setter + def flow_rate(self, new_flow_rate: float) -> None: + validated_flow_rate = validation.ensure_positive_float(new_flow_rate) + self._flow_rate = validated_flow_rate + + def _get_shared_data_params(self) -> Optional[SharedDataBlowoutParams]: + """Get the mix params in schema v1 shape.""" + if self._location is not None and self._flow_rate is not None: + return SharedDataBlowoutParams( + location=self._location, + flowRate=self._flow_rate, + ) + else: + return None + + def as_shared_data_model(self) -> SharedDataBlowoutProperties: + return SharedDataBlowoutProperties( + enable=self._enabled, + params=self._get_shared_data_params(), + ) + + +@dataclass +class SubmergeRetractCommon: + + _position_reference: PositionReference + _offset: Coordinate + _speed: float + _delay: DelayProperties + + @property + def position_reference(self) -> PositionReference: + return self._position_reference + + @position_reference.setter + def position_reference(self, new_position: str) -> None: + self._position_reference = PositionReference(new_position) + + @property + def offset(self) -> Coordinate: + return self._offset + + @offset.setter + def offset(self, new_offset: Sequence[float]) -> None: + x, y, z = validation.validate_coordinates(new_offset) + self._offset = Coordinate(x=x, y=y, z=z) + + @property + def speed(self) -> float: + return self._speed + + @speed.setter + def speed(self, new_speed: float) -> None: + validated_speed = validation.ensure_positive_float(new_speed) + self._speed = validated_speed + + @property + def delay(self) -> DelayProperties: + return self._delay + + +@dataclass +class Submerge(SubmergeRetractCommon): + ... + + def as_shared_data_model(self) -> SharedDataSubmerge: + return SharedDataSubmerge( + positionReference=self._position_reference, + offset=self._offset, + speed=self._speed, + delay=self._delay.as_shared_data_model(), + ) + + +@dataclass +class RetractAspirate(SubmergeRetractCommon): + + _air_gap_by_volume: LiquidHandlingPropertyByVolume + _touch_tip: TouchTipProperties + + @property + def air_gap_by_volume(self) -> LiquidHandlingPropertyByVolume: + return self._air_gap_by_volume + + @property + def touch_tip(self) -> TouchTipProperties: + return self._touch_tip + + def as_shared_data_model(self) -> SharedDataRetractAspirate: + return SharedDataRetractAspirate( + positionReference=self._position_reference, + offset=self._offset, + speed=self._speed, + airGapByVolume=self._air_gap_by_volume.as_list_of_tuples(), + touchTip=self._touch_tip.as_shared_data_model(), + delay=self._delay.as_shared_data_model(), + ) + + +@dataclass +class RetractDispense(SubmergeRetractCommon): + + _air_gap_by_volume: LiquidHandlingPropertyByVolume + _touch_tip: TouchTipProperties + _blowout: BlowoutProperties + + @property + def air_gap_by_volume(self) -> LiquidHandlingPropertyByVolume: + return self._air_gap_by_volume + + @property + def touch_tip(self) -> TouchTipProperties: + return self._touch_tip + + @property + def blowout(self) -> BlowoutProperties: + return self._blowout + + def as_shared_data_model(self) -> SharedDataRetractDispense: + return SharedDataRetractDispense( + positionReference=self._position_reference, + offset=self._offset, + speed=self._speed, + airGapByVolume=self._air_gap_by_volume.as_list_of_tuples(), + blowout=self._blowout.as_shared_data_model(), + touchTip=self._touch_tip.as_shared_data_model(), + delay=self._delay.as_shared_data_model(), + ) + + +@dataclass +class BaseLiquidHandlingProperties: + + _submerge: Submerge + _position_reference: PositionReference + _offset: Coordinate + _flow_rate_by_volume: LiquidHandlingPropertyByVolume + _correction_by_volume: LiquidHandlingPropertyByVolume + _delay: DelayProperties + + @property + def submerge(self) -> Submerge: + return self._submerge + + @property + def position_reference(self) -> PositionReference: + return self._position_reference + + @position_reference.setter + def position_reference(self, new_position: str) -> None: + self._position_reference = PositionReference(new_position) + + @property + def offset(self) -> Coordinate: + return self._offset + + @offset.setter + def offset(self, new_offset: Sequence[float]) -> None: + x, y, z = validation.validate_coordinates(new_offset) + self._offset = Coordinate(x=x, y=y, z=z) + + @property + def flow_rate_by_volume(self) -> LiquidHandlingPropertyByVolume: + return self._flow_rate_by_volume + + @property + def correction_by_volume(self) -> LiquidHandlingPropertyByVolume: + return self._correction_by_volume + + @property + def delay(self) -> DelayProperties: + return self._delay + + +@dataclass +class AspirateProperties(BaseLiquidHandlingProperties): + + _retract: RetractAspirate + _pre_wet: bool + _mix: MixProperties + + @property + def pre_wet(self) -> bool: + return self._pre_wet + + @pre_wet.setter + def pre_wet(self, new_setting: bool) -> None: + validated_setting = validation.ensure_boolean(new_setting) + self._pre_wet = validated_setting + + @property + def retract(self) -> RetractAspirate: + return self._retract + + @property + def mix(self) -> MixProperties: + return self._mix + + def as_shared_data_model(self) -> SharedDataAspirateProperties: + return SharedDataAspirateProperties( + submerge=self._submerge.as_shared_data_model(), + retract=self._retract.as_shared_data_model(), + positionReference=self._position_reference, + offset=self._offset, + flowRateByVolume=self._flow_rate_by_volume.as_list_of_tuples(), + preWet=self._pre_wet, + mix=self._mix.as_shared_data_model(), + delay=self._delay.as_shared_data_model(), + correctionByVolume=self._correction_by_volume.as_list_of_tuples(), + ) + + +@dataclass +class SingleDispenseProperties(BaseLiquidHandlingProperties): + + _retract: RetractDispense + _push_out_by_volume: LiquidHandlingPropertyByVolume + _mix: MixProperties + + @property + def push_out_by_volume(self) -> LiquidHandlingPropertyByVolume: + return self._push_out_by_volume + + @property + def retract(self) -> RetractDispense: + return self._retract + + @property + def mix(self) -> MixProperties: + return self._mix + + def as_shared_data_model(self) -> SharedDataSingleDispenseProperties: + return SharedDataSingleDispenseProperties( + submerge=self._submerge.as_shared_data_model(), + retract=self._retract.as_shared_data_model(), + positionReference=self._position_reference, + offset=self._offset, + flowRateByVolume=self._flow_rate_by_volume.as_list_of_tuples(), + mix=self._mix.as_shared_data_model(), + pushOutByVolume=self._push_out_by_volume.as_list_of_tuples(), + delay=self._delay.as_shared_data_model(), + correctionByVolume=self._correction_by_volume.as_list_of_tuples(), + ) + + +@dataclass +class MultiDispenseProperties(BaseLiquidHandlingProperties): + + _retract: RetractDispense + _conditioning_by_volume: LiquidHandlingPropertyByVolume + _disposal_by_volume: LiquidHandlingPropertyByVolume + + @property + def retract(self) -> RetractDispense: + return self._retract + + @property + def conditioning_by_volume(self) -> LiquidHandlingPropertyByVolume: + return self._conditioning_by_volume + + @property + def disposal_by_volume(self) -> LiquidHandlingPropertyByVolume: + return self._disposal_by_volume + + def as_shared_data_model(self) -> SharedDataMultiDispenseProperties: + return SharedDataMultiDispenseProperties( + submerge=self._submerge.as_shared_data_model(), + retract=self._retract.as_shared_data_model(), + positionReference=self._position_reference, + offset=self._offset, + flowRateByVolume=self._flow_rate_by_volume.as_list_of_tuples(), + conditioningByVolume=self._conditioning_by_volume.as_list_of_tuples(), + disposalByVolume=self._disposal_by_volume.as_list_of_tuples(), + delay=self._delay.as_shared_data_model(), + correctionByVolume=self._correction_by_volume.as_list_of_tuples(), + ) + + +@dataclass +class TransferProperties: + _aspirate: AspirateProperties + _dispense: SingleDispenseProperties + _multi_dispense: Optional[MultiDispenseProperties] + + @property + def aspirate(self) -> AspirateProperties: + """Aspirate properties.""" + return self._aspirate + + @property + def dispense(self) -> SingleDispenseProperties: + """Single dispense properties.""" + return self._dispense + + @property + def multi_dispense(self) -> Optional[MultiDispenseProperties]: + """Multi dispense properties.""" + return self._multi_dispense + + +def _build_delay_properties( + delay_properties: SharedDataDelayProperties, +) -> DelayProperties: + if delay_properties.params is not None: + duration = delay_properties.params.duration + else: + duration = None + return DelayProperties(_enabled=delay_properties.enable, _duration=duration) + + +def _build_touch_tip_properties( + touch_tip_properties: SharedDataTouchTipProperties, +) -> TouchTipProperties: + if touch_tip_properties.params is not None: + z_offset = touch_tip_properties.params.zOffset + mm_to_edge = touch_tip_properties.params.mmToEdge + speed = touch_tip_properties.params.speed + else: + z_offset = None + mm_to_edge = None + speed = None + return TouchTipProperties( + _enabled=touch_tip_properties.enable, + _z_offset=z_offset, + _mm_to_edge=mm_to_edge, + _speed=speed, + ) + + +def _build_mix_properties( + mix_properties: SharedDataMixProperties, +) -> MixProperties: + if mix_properties.params is not None: + repetitions = mix_properties.params.repetitions + volume = mix_properties.params.volume + else: + repetitions = None + volume = None + return MixProperties( + _enabled=mix_properties.enable, _repetitions=repetitions, _volume=volume + ) + + +def _build_blowout_properties( + blowout_properties: SharedDataBlowoutProperties, +) -> BlowoutProperties: + if blowout_properties.params is not None: + location = blowout_properties.params.location + flow_rate = blowout_properties.params.flowRate + else: + location = None + flow_rate = None + return BlowoutProperties( + _enabled=blowout_properties.enable, _location=location, _flow_rate=flow_rate + ) + + +def _build_submerge( + submerge_properties: SharedDataSubmerge, +) -> Submerge: + return Submerge( + _position_reference=submerge_properties.positionReference, + _offset=submerge_properties.offset, + _speed=submerge_properties.speed, + _delay=_build_delay_properties(submerge_properties.delay), + ) + + +def _build_retract_aspirate( + retract_aspirate: SharedDataRetractAspirate, +) -> RetractAspirate: + return RetractAspirate( + _position_reference=retract_aspirate.positionReference, + _offset=retract_aspirate.offset, + _speed=retract_aspirate.speed, + _air_gap_by_volume=LiquidHandlingPropertyByVolume( + retract_aspirate.airGapByVolume + ), + _touch_tip=_build_touch_tip_properties(retract_aspirate.touchTip), + _delay=_build_delay_properties(retract_aspirate.delay), + ) + + +def _build_retract_dispense( + retract_dispense: SharedDataRetractDispense, +) -> RetractDispense: + return RetractDispense( + _position_reference=retract_dispense.positionReference, + _offset=retract_dispense.offset, + _speed=retract_dispense.speed, + _air_gap_by_volume=LiquidHandlingPropertyByVolume( + retract_dispense.airGapByVolume + ), + _blowout=_build_blowout_properties(retract_dispense.blowout), + _touch_tip=_build_touch_tip_properties(retract_dispense.touchTip), + _delay=_build_delay_properties(retract_dispense.delay), + ) + + +def build_aspirate_properties( + aspirate_properties: SharedDataAspirateProperties, +) -> AspirateProperties: + return AspirateProperties( + _submerge=_build_submerge(aspirate_properties.submerge), + _retract=_build_retract_aspirate(aspirate_properties.retract), + _position_reference=aspirate_properties.positionReference, + _offset=aspirate_properties.offset, + _flow_rate_by_volume=LiquidHandlingPropertyByVolume( + aspirate_properties.flowRateByVolume + ), + _correction_by_volume=LiquidHandlingPropertyByVolume( + aspirate_properties.correctionByVolume + ), + _pre_wet=aspirate_properties.preWet, + _mix=_build_mix_properties(aspirate_properties.mix), + _delay=_build_delay_properties(aspirate_properties.delay), + ) + + +def build_single_dispense_properties( + single_dispense_properties: SharedDataSingleDispenseProperties, +) -> SingleDispenseProperties: + return SingleDispenseProperties( + _submerge=_build_submerge(single_dispense_properties.submerge), + _retract=_build_retract_dispense(single_dispense_properties.retract), + _position_reference=single_dispense_properties.positionReference, + _offset=single_dispense_properties.offset, + _flow_rate_by_volume=LiquidHandlingPropertyByVolume( + single_dispense_properties.flowRateByVolume + ), + _correction_by_volume=LiquidHandlingPropertyByVolume( + single_dispense_properties.correctionByVolume + ), + _mix=_build_mix_properties(single_dispense_properties.mix), + _push_out_by_volume=LiquidHandlingPropertyByVolume( + single_dispense_properties.pushOutByVolume + ), + _delay=_build_delay_properties(single_dispense_properties.delay), + ) + + +def build_multi_dispense_properties( + multi_dispense_properties: Optional[SharedDataMultiDispenseProperties], +) -> Optional[MultiDispenseProperties]: + if multi_dispense_properties is None: + return None + return MultiDispenseProperties( + _submerge=_build_submerge(multi_dispense_properties.submerge), + _retract=_build_retract_dispense(multi_dispense_properties.retract), + _position_reference=multi_dispense_properties.positionReference, + _offset=multi_dispense_properties.offset, + _flow_rate_by_volume=LiquidHandlingPropertyByVolume( + multi_dispense_properties.flowRateByVolume + ), + _correction_by_volume=LiquidHandlingPropertyByVolume( + multi_dispense_properties.correctionByVolume + ), + _conditioning_by_volume=LiquidHandlingPropertyByVolume( + multi_dispense_properties.conditioningByVolume + ), + _disposal_by_volume=LiquidHandlingPropertyByVolume( + multi_dispense_properties.disposalByVolume + ), + _delay=_build_delay_properties(multi_dispense_properties.delay), + ) + + +def build_transfer_properties( + by_tip_type_setting: SharedByTipTypeSetting, +) -> TransferProperties: + return TransferProperties( + _aspirate=build_aspirate_properties(by_tip_type_setting.aspirate), + _dispense=build_single_dispense_properties(by_tip_type_setting.singleDispense), + _multi_dispense=build_multi_dispense_properties( + by_tip_type_setting.multiDispense + ), + ) diff --git a/api/src/opentrons/protocol_api/_parameter_context.py b/api/src/opentrons/protocol_api/_parameter_context.py index 2e0e0096f44..a52aee7819b 100644 --- a/api/src/opentrons/protocol_api/_parameter_context.py +++ b/api/src/opentrons/protocol_api/_parameter_context.py @@ -1,4 +1,5 @@ """Parameter context for python protocols.""" +import uuid from typing import List, Optional, Union, Dict from opentrons.protocols.api_support.types import APIVersion @@ -251,8 +252,16 @@ def initialize_csv_files( f" but '{variable_name}' is not a CSV parameter." ) - # The parent folder in the path will be the file ID, so we can use that to resolve that here + # TODO(jbl 2024-09-30) Refactor this so file ID is passed as its own argument and not assumed from the path + # If this is running on a robot, the parent folder in the path will be the file ID + # If it is running locally, most likely the parent folder will not be a UUID, so instead we will change + # this to be an empty string file_id = file_path.parent.name + try: + uuid.UUID(file_id, version=4) + except ValueError: + file_id = "" + file_name = file_path.name with file_path.open("rb") as fh: diff --git a/api/src/opentrons/protocol_api/_types.py b/api/src/opentrons/protocol_api/_types.py index 9890e29c2bc..0e73405b3b7 100644 --- a/api/src/opentrons/protocol_api/_types.py +++ b/api/src/opentrons/protocol_api/_types.py @@ -17,3 +17,27 @@ class OffDeckType(enum.Enum): See :ref:`off-deck-location` for details on using ``OFF_DECK`` with :py:obj:`ProtocolContext.move_labware()`. """ + + +class PlungerPositionTypes(enum.Enum): + PLUNGER_TOP = "top" + PLUNGER_BOTTOM = "bottom" + PLUNGER_BLOWOUT = "blow_out" + PLUNGER_DROPTIP = "drop_tip" + + +PLUNGER_TOP: Final = PlungerPositionTypes.PLUNGER_TOP +PLUNGER_BOTTOM: Final = PlungerPositionTypes.PLUNGER_BOTTOM +PLUNGER_BLOWOUT: Final = PlungerPositionTypes.PLUNGER_BLOWOUT +PLUNGER_DROPTIP: Final = PlungerPositionTypes.PLUNGER_DROPTIP + + +class PipetteActionTypes(enum.Enum): + ASPIRATE_ACTION = "aspirate" + DISPENSE_ACTION = "dispense" + BLOWOUT_ACTION = "blowout" + + +ASPIRATE_ACTION: Final = PipetteActionTypes.ASPIRATE_ACTION +DISPENSE_ACTION: Final = PipetteActionTypes.DISPENSE_ACTION +BLOWOUT_ACTION: Final = PipetteActionTypes.BLOWOUT_ACTION diff --git a/api/src/opentrons/protocol_api/core/common.py b/api/src/opentrons/protocol_api/core/common.py index 5a63abb46b3..3aff2523a1f 100644 --- a/api/src/opentrons/protocol_api/core/common.py +++ b/api/src/opentrons/protocol_api/core/common.py @@ -14,6 +14,7 @@ ) from .protocol import AbstractProtocol from .well import AbstractWellCore +from .robot import AbstractRobot WellCore = AbstractWellCore @@ -26,4 +27,5 @@ HeaterShakerCore = AbstractHeaterShakerCore MagneticBlockCore = AbstractMagneticBlockCore AbsorbanceReaderCore = AbstractAbsorbanceReaderCore +RobotCore = AbstractRobot ProtocolCore = AbstractProtocol[InstrumentCore, LabwareCore, ModuleCore] diff --git a/api/src/opentrons/protocol_api/core/engine/deck_conflict.py b/api/src/opentrons/protocol_api/core/engine/deck_conflict.py index 405aa2256a7..ee724ea5ca3 100644 --- a/api/src/opentrons/protocol_api/core/engine/deck_conflict.py +++ b/api/src/opentrons/protocol_api/core/engine/deck_conflict.py @@ -10,7 +10,6 @@ overload, Union, TYPE_CHECKING, - List, ) from opentrons_shared_data.errors.exceptions import MotionPlanningFailureError @@ -18,7 +17,6 @@ from opentrons.hardware_control.modules.types import ModuleType from opentrons.motion_planning import deck_conflict as wrapped_deck_conflict -from opentrons.motion_planning import adjacent_slots_getters from opentrons.protocol_engine import ( StateView, @@ -27,16 +25,10 @@ OnLabwareLocation, AddressableAreaLocation, OFF_DECK_LOCATION, - WellLocation, - DropTipWellLocation, ) from opentrons.protocol_engine.errors.exceptions import LabwareNotLoadedOnModuleError -from opentrons.protocol_engine.types import ( - StagingSlotLocation, -) from opentrons.types import DeckSlotName, StagingSlotName, Point from ...disposal_locations import TrashBin, WasteChute -from . import point_calculations if TYPE_CHECKING: from ...labware import Labware @@ -192,250 +184,6 @@ def check( ) -# TODO (spp, 2023-02-16): move pipette movement safety checks to its own separate file. -def check_safe_for_pipette_movement( - engine_state: StateView, - pipette_id: str, - labware_id: str, - well_name: str, - well_location: Union[WellLocation, DropTipWellLocation], -) -> None: - """Check if the labware is safe to move to with a pipette in partial tip configuration. - - Args: - engine_state: engine state view - pipette_id: ID of the pipette to be moved - labware_id: ID of the labware we are moving to - well_name: Name of the well to move to - well_location: exact location within the well to move to - """ - # TODO (spp, 2023-02-06): remove this check after thorough testing. - # This function is capable of checking for movement conflict regardless of - # nozzle configuration. - if not engine_state.pipettes.get_is_partially_configured(pipette_id): - return - - if isinstance(well_location, DropTipWellLocation): - # convert to WellLocation - well_location = engine_state.geometry.get_checked_tip_drop_location( - pipette_id=pipette_id, - labware_id=labware_id, - well_location=well_location, - partially_configured=True, - ) - well_location_point = engine_state.geometry.get_well_position( - labware_id=labware_id, well_name=well_name, well_location=well_location - ) - primary_nozzle = engine_state.pipettes.get_primary_nozzle(pipette_id) - - pipette_bounds_at_well_location = ( - engine_state.pipettes.get_pipette_bounds_at_specified_move_to_position( - pipette_id=pipette_id, destination_position=well_location_point - ) - ) - if not _is_within_pipette_extents( - engine_state=engine_state, - pipette_id=pipette_id, - pipette_bounding_box_at_loc=pipette_bounds_at_well_location, - ): - raise PartialTipMovementNotAllowedError( - f"Requested motion with the {primary_nozzle} nozzle partial configuration" - f" is outside of robot bounds for the pipette." - ) - - labware_slot = engine_state.geometry.get_ancestor_slot_name(labware_id) - - surrounding_slots = adjacent_slots_getters.get_surrounding_slots( - slot=labware_slot.as_int(), robot_type=engine_state.config.robot_type - ) - - if _will_collide_with_thermocycler_lid( - engine_state=engine_state, - pipette_bounds=pipette_bounds_at_well_location, - surrounding_regular_slots=surrounding_slots.regular_slots, - ): - raise PartialTipMovementNotAllowedError( - f"Moving to {engine_state.labware.get_display_name(labware_id)} in slot" - f" {labware_slot} with {primary_nozzle} nozzle partial configuration" - f" will result in collision with thermocycler lid in deck slot A1." - ) - - for regular_slot in surrounding_slots.regular_slots: - if _slot_has_potential_colliding_object( - engine_state=engine_state, - pipette_bounds=pipette_bounds_at_well_location, - surrounding_slot=regular_slot, - ): - raise PartialTipMovementNotAllowedError( - f"Moving to {engine_state.labware.get_display_name(labware_id)} in slot" - f" {labware_slot} with {primary_nozzle} nozzle partial configuration" - f" will result in collision with items in deck slot {regular_slot}." - ) - for staging_slot in surrounding_slots.staging_slots: - if _slot_has_potential_colliding_object( - engine_state=engine_state, - pipette_bounds=pipette_bounds_at_well_location, - surrounding_slot=staging_slot, - ): - raise PartialTipMovementNotAllowedError( - f"Moving to {engine_state.labware.get_display_name(labware_id)} in slot" - f" {labware_slot} with {primary_nozzle} nozzle partial configuration" - f" will result in collision with items in staging slot {staging_slot}." - ) - - -def _slot_has_potential_colliding_object( - engine_state: StateView, - pipette_bounds: Tuple[Point, Point, Point, Point], - surrounding_slot: Union[DeckSlotName, StagingSlotName], -) -> bool: - """Return the slot, if any, that has an item that the pipette might collide into.""" - # Check if slot overlaps with pipette position - slot_pos = engine_state.addressable_areas.get_addressable_area_position( - addressable_area_name=surrounding_slot.id, - do_compatibility_check=False, - ) - slot_bounds = engine_state.addressable_areas.get_addressable_area_bounding_box( - addressable_area_name=surrounding_slot.id, - do_compatibility_check=False, - ) - slot_back_left_coords = Point(slot_pos.x, slot_pos.y + slot_bounds.y, slot_pos.z) - slot_front_right_coords = Point(slot_pos.x + slot_bounds.x, slot_pos.y, slot_pos.z) - - # If slot overlaps with pipette bounds - if point_calculations.are_overlapping_rectangles( - rectangle1=(pipette_bounds[0], pipette_bounds[1]), - rectangle2=(slot_back_left_coords, slot_front_right_coords), - ): - # Check z-height of items in overlapping slot - if isinstance(surrounding_slot, DeckSlotName): - slot_highest_z = engine_state.geometry.get_highest_z_in_slot( - DeckSlotLocation(slotName=surrounding_slot) - ) - else: - slot_highest_z = engine_state.geometry.get_highest_z_in_slot( - StagingSlotLocation(slotName=surrounding_slot) - ) - return slot_highest_z >= pipette_bounds[0].z - return False - - -def _will_collide_with_thermocycler_lid( - engine_state: StateView, - pipette_bounds: Tuple[Point, Point, Point, Point], - surrounding_regular_slots: List[DeckSlotName], -) -> bool: - """Return whether the pipette might collide with thermocycler's lid/clips on a Flex. - - If any of the pipette's bounding vertices lie inside the no-go zone of the thermocycler- - which is the area that's to the left, back and below the thermocycler's lid's - protruding clips, then we will mark the movement for possible collision. - - This could cause false raises for the case where an 8-channel is accessing the - thermocycler labware in a location such that the pipette is in the area between - the clips but not touching either clips. But that's a tradeoff we'll need to make - between a complicated check involving accurate positions of all entities involved - and a crude check that disallows all partial tip movements around the thermocycler. - """ - # TODO (spp, 2024-02-27): Improvements: - # - make the check dynamic according to lid state: - # - if lid is open, check if pipette is in no-go zone - # - if lid is closed, use the closed lid height to check for conflict - if ( - DeckSlotName.SLOT_A1 in surrounding_regular_slots - and engine_state.modules.is_flex_deck_with_thermocycler() - ): - return ( - point_calculations.are_overlapping_rectangles( - rectangle1=(_FLEX_TC_LID_BACK_LEFT_PT, _FLEX_TC_LID_FRONT_RIGHT_PT), - rectangle2=(pipette_bounds[0], pipette_bounds[1]), - ) - and pipette_bounds[0].z <= _FLEX_TC_LID_BACK_LEFT_PT.z - ) - - return False - - -def check_safe_for_tip_pickup_and_return( - engine_state: StateView, - pipette_id: str, - labware_id: str, -) -> None: - """Check if the presence or absence of a tiprack adapter might cause any pipette movement issues. - - A 96 channel pipette will pick up tips using cam action when it's configured - to use ALL nozzles. For this, the tiprack needs to be on the Flex 96 channel tiprack adapter - or similar or the tips will not be picked up. - - On the other hand, if the pipette is configured with partial nozzle configuration, - it uses the usual pipette presses to pick the tips up, in which case, having the tiprack - on the Flex 96 channel tiprack adapter (or similar) will cause the pipette to - crash against the adapter posts. - - In order to check if the 96-channel can move and pickup/drop tips safely, this method - checks for the height attribute of the tiprack adapter rather than checking for the - specific official adapter since users might create custom labware &/or definitions - compatible with the official adapter. - """ - if not engine_state.pipettes.get_channels(pipette_id) == 96: - # Adapters only matter to 96 ch. - return - - is_partial_config = engine_state.pipettes.get_is_partially_configured(pipette_id) - tiprack_name = engine_state.labware.get_display_name(labware_id) - tiprack_parent = engine_state.labware.get_location(labware_id) - if isinstance(tiprack_parent, OnLabwareLocation): # tiprack is on an adapter - is_96_ch_tiprack_adapter = engine_state.labware.get_has_quirk( - labware_id=tiprack_parent.labwareId, quirk="tiprackAdapterFor96Channel" - ) - tiprack_height = engine_state.labware.get_dimensions(labware_id).z - adapter_height = engine_state.labware.get_dimensions(tiprack_parent.labwareId).z - if is_partial_config and tiprack_height < adapter_height: - raise PartialTipMovementNotAllowedError( - f"{tiprack_name} cannot be on an adapter taller than the tip rack" - f" when picking up fewer than 96 tips." - ) - elif not is_partial_config and not is_96_ch_tiprack_adapter: - raise UnsuitableTiprackForPipetteMotion( - f"{tiprack_name} must be on an Opentrons Flex 96 Tip Rack Adapter" - f" in order to pick up or return all 96 tips simultaneously." - ) - - elif ( - not is_partial_config - ): # tiprack is not on adapter and pipette is in full config - raise UnsuitableTiprackForPipetteMotion( - f"{tiprack_name} must be on an Opentrons Flex 96 Tip Rack Adapter" - f" in order to pick up or return all 96 tips simultaneously." - ) - - -def _is_within_pipette_extents( - engine_state: StateView, - pipette_id: str, - pipette_bounding_box_at_loc: Tuple[Point, Point, Point, Point], -) -> bool: - """Whether a given point is within the extents of a configured pipette on the specified robot.""" - mount = engine_state.pipettes.get_mount(pipette_id) - robot_extent_per_mount = engine_state.geometry.absolute_deck_extents - pip_back_left_bound, pip_front_right_bound, _, _ = pipette_bounding_box_at_loc - pipette_bounds_offsets = engine_state.pipettes.get_pipette_bounding_box(pipette_id) - from_back_right = ( - robot_extent_per_mount.back_right[mount] - + pipette_bounds_offsets.back_right_corner - ) - from_front_left = ( - robot_extent_per_mount.front_left[mount] - + pipette_bounds_offsets.front_left_corner - ) - return ( - from_back_right.x >= pip_back_left_bound.x >= from_front_left.x - and from_back_right.y >= pip_back_left_bound.y >= from_front_left.y - and from_back_right.x >= pip_front_right_bound.x >= from_front_left.x - and from_back_right.y >= pip_front_right_bound.y >= from_front_left.y - ) - - def _map_labware( engine_state: StateView, labware_id: str, diff --git a/api/src/opentrons/protocol_api/core/engine/instrument.py b/api/src/opentrons/protocol_api/core/engine/instrument.py index 55519e7899c..010f3110fdb 100644 --- a/api/src/opentrons/protocol_api/core/engine/instrument.py +++ b/api/src/opentrons/protocol_api/core/engine/instrument.py @@ -1,13 +1,14 @@ """ProtocolEngine-based InstrumentContext core implementation.""" -from __future__ import annotations -from typing import Optional, TYPE_CHECKING, cast, Union -from opentrons.protocols.api_support.types import APIVersion +from __future__ import annotations -from opentrons.types import Location, Mount +from typing import Optional, TYPE_CHECKING, cast, Union, List +from opentrons.types import Location, Mount, NozzleConfigurationType, NozzleMapInterface from opentrons.hardware_control import SyncHardwareAPI from opentrons.hardware_control.dev_types import PipetteDict from opentrons.protocols.api_support.util import FlowRates, find_value_for_api_version +from opentrons.protocols.api_support.types import APIVersion +from opentrons.protocols.advanced_control.transfers.common import TransferTipPolicyV2 from opentrons.protocol_engine import commands as cmd from opentrons.protocol_engine import ( DeckPoint, @@ -26,24 +27,25 @@ PRIMARY_NOZZLE_LITERAL, NozzleLayoutConfigurationType, AddressableOffsetVector, + LiquidClassRecord, ) from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError from opentrons.protocol_engine.clients import SyncClient as EngineClient from opentrons.protocols.api_support.definitions import MAX_SUPPORTED_VERSION from opentrons_shared_data.pipette.types import PipetteNameType +from opentrons_shared_data.errors.exceptions import ( + UnsupportedHardwareCommand, +) from opentrons.protocol_api._nozzle_layout import NozzleLayout -from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType -from opentrons.hardware_control.nozzle_manager import NozzleMap -from . import deck_conflict, overlap_versions +from . import overlap_versions, pipette_movement_conflict -from ..instrument import AbstractInstrument from .well import WellCore - +from ..instrument import AbstractInstrument from ...disposal_locations import TrashBin, WasteChute if TYPE_CHECKING: from .protocol import ProtocolCore - + from opentrons.protocol_api._liquid import LiquidClass _DISPENSE_VOLUME_VALIDATION_ADDED_IN = APIVersion(2, 17) @@ -86,6 +88,13 @@ def __init__( self._liquid_presence_detection = bool( self._engine_client.state.pipettes.get_liquid_presence_detection(pipette_id) ) + if ( + self._liquid_presence_detection + and not self._pressure_supported_by_pipette() + ): + raise UnsupportedHardwareCommand( + "Pressure sensor not available for this pipette" + ) @property def pipette_id(self) -> str: @@ -104,6 +113,19 @@ def set_default_speed(self, speed: float) -> None: pipette_id=self._pipette_id, speed=speed ) + def air_gap_in_place(self, volume: float, flow_rate: float) -> None: + """Aspirate a given volume of air from the current location of the pipette. + + Args: + volume: The volume of air to aspirate, in microliters. + folw_rate: The flow rate of air into the pipette, in microliters/s + """ + self._engine_client.execute_command( + cmd.AirGapInPlaceParams( + pipetteId=self._pipette_id, volume=volume, flowRate=flow_rate + ) + ) + def aspirate( self, location: Location, @@ -112,6 +134,7 @@ def aspirate( rate: float, flow_rate: float, in_place: bool, + is_meniscus: Optional[bool] = None, ) -> None: """Aspirate a given volume of liquid from the specified location. Args: @@ -146,14 +169,15 @@ def aspirate( well_name = well_core.get_name() labware_id = well_core.labware_id - well_location = ( - self._engine_client.state.geometry.get_relative_well_location( - labware_id=labware_id, - well_name=well_name, - absolute_point=location.point, - ) + well_location = self._engine_client.state.geometry.get_relative_liquid_handling_well_location( + labware_id=labware_id, + well_name=well_name, + absolute_point=location.point, + is_meniscus=is_meniscus, ) - deck_conflict.check_safe_for_pipette_movement( + if well_location.origin == WellOrigin.MENISCUS: + well_location.volumeOffset = "operationVolume" + pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=self._engine_client.state, pipette_id=self._pipette_id, labware_id=labware_id, @@ -182,6 +206,7 @@ def dispense( flow_rate: float, in_place: bool, push_out: Optional[float], + is_meniscus: Optional[bool] = None, ) -> None: """Dispense a given volume of liquid into the specified location. Args: @@ -237,14 +262,13 @@ def dispense( well_name = well_core.get_name() labware_id = well_core.labware_id - well_location = ( - self._engine_client.state.geometry.get_relative_well_location( - labware_id=labware_id, - well_name=well_name, - absolute_point=location.point, - ) + well_location = self._engine_client.state.geometry.get_relative_liquid_handling_well_location( + labware_id=labware_id, + well_name=well_name, + absolute_point=location.point, + is_meniscus=is_meniscus, ) - deck_conflict.check_safe_for_pipette_movement( + pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=self._engine_client.state, pipette_id=self._pipette_id, labware_id=labware_id, @@ -321,7 +345,7 @@ def blow_out( absolute_point=location.point, ) ) - deck_conflict.check_safe_for_pipette_movement( + pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=self._engine_client.state, pipette_id=self._pipette_id, labware_id=labware_id, @@ -371,7 +395,7 @@ def touch_tip( well_location = WellLocation( origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=z_offset) ) - deck_conflict.check_safe_for_pipette_movement( + pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=self._engine_client.state, pipette_id=self._pipette_id, labware_id=labware_id, @@ -416,17 +440,19 @@ def pick_up_tip( well_name = well_core.get_name() labware_id = well_core.labware_id - well_location = self._engine_client.state.geometry.get_relative_well_location( - labware_id=labware_id, - well_name=well_name, - absolute_point=location.point, + well_location = ( + self._engine_client.state.geometry.get_relative_pick_up_tip_well_location( + labware_id=labware_id, + well_name=well_name, + absolute_point=location.point, + ) ) - deck_conflict.check_safe_for_tip_pickup_and_return( + pipette_movement_conflict.check_safe_for_tip_pickup_and_return( engine_state=self._engine_client.state, pipette_id=self._pipette_id, labware_id=labware_id, ) - deck_conflict.check_safe_for_pipette_movement( + pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=self._engine_client.state, pipette_id=self._pipette_id, labware_id=labware_id, @@ -486,12 +512,12 @@ def drop_tip( well_location = DropTipWellLocation() if self._engine_client.state.labware.is_tiprack(labware_id): - deck_conflict.check_safe_for_tip_pickup_and_return( + pipette_movement_conflict.check_safe_for_tip_pickup_and_return( engine_state=self._engine_client.state, pipette_id=self._pipette_id, labware_id=labware_id, ) - deck_conflict.check_safe_for_pipette_movement( + pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=self._engine_client.state, pipette_id=self._pipette_id, labware_id=labware_id, @@ -720,7 +746,7 @@ def get_active_channels(self) -> int: self._pipette_id ) - def get_nozzle_map(self) -> NozzleMap: + def get_nozzle_map(self) -> NozzleMapInterface: return self._engine_client.state.tips.get_pipette_nozzle_map(self._pipette_id) def has_tip(self) -> bool: @@ -838,11 +864,55 @@ def configure_nozzle_layout( ) ) + def load_liquid_class( + self, + liquid_class: LiquidClass, + pipette_load_name: str, + tiprack_uri: str, + ) -> str: + """Load a liquid class into the engine and return its ID.""" + transfer_props = liquid_class.get_for( + pipette=pipette_load_name, tiprack=tiprack_uri + ) + + liquid_class_record = LiquidClassRecord( + liquidClassName=liquid_class.name, + pipetteModel=self.get_model(), # TODO: verify this is the correct 'model' to use + tiprack=tiprack_uri, + aspirate=transfer_props.aspirate.as_shared_data_model(), + singleDispense=transfer_props.dispense.as_shared_data_model(), + multiDispense=transfer_props.multi_dispense.as_shared_data_model() + if transfer_props.multi_dispense + else None, + ) + result = self._engine_client.execute_command_without_recovery( + cmd.LoadLiquidClassParams( + liquidClassRecord=liquid_class_record, + ) + ) + return result.liquidClassId + + def transfer_liquid( + self, + liquid_class_id: str, + volume: float, + source: List[WellCore], + dest: List[WellCore], + new_tip: TransferTipPolicyV2, + trash_location: Union[WellCore, Location, TrashBin, WasteChute], + ) -> None: + """Execute transfer using liquid class properties.""" + def retract(self) -> None: """Retract this instrument to the top of the gantry.""" z_axis = self._engine_client.state.pipettes.get_z_axis(self._pipette_id) self._engine_client.execute_command(cmd.HomeParams(axes=[z_axis])) + def _pressure_supported_by_pipette(self) -> bool: + return self._engine_client.state.pipettes.get_pipette_supports_pressure( + self.pipette_id + ) + def detect_liquid_presence(self, well_core: WellCore, loc: Location) -> bool: labware_id = well_core.labware_id well_name = well_core.get_name() @@ -918,3 +988,9 @@ def liquid_probe_without_recovery( self._protocol_core.set_last_location(location=loc, mount=self.get_mount()) return result.z_position + + def nozzle_configuration_valid_for_lld(self) -> bool: + """Check if the nozzle configuration currently supports LLD.""" + return self._engine_client.state.pipettes.get_nozzle_configuration_supports_lld( + self.pipette_id + ) diff --git a/api/src/opentrons/protocol_api/core/engine/labware.py b/api/src/opentrons/protocol_api/core/engine/labware.py index f09a51ef181..4d868bd30ac 100644 --- a/api/src/opentrons/protocol_api/core/engine/labware.py +++ b/api/src/opentrons/protocol_api/core/engine/labware.py @@ -1,5 +1,6 @@ """ProtocolEngine-based Labware core implementations.""" -from typing import List, Optional, cast + +from typing import List, Optional, cast, Dict from opentrons_shared_data.labware.types import ( LabwareParameters as LabwareParametersDict, @@ -19,11 +20,12 @@ LabwareOffsetCreate, LabwareOffsetVector, ) -from opentrons.types import DeckSlotName, Point -from opentrons.hardware_control.nozzle_manager import NozzleMap +from opentrons.types import DeckSlotName, NozzleMapInterface, Point, StagingSlotName +from ..._liquid import Liquid from ..labware import AbstractLabware, LabwareLoadParams + from .well import WellCore @@ -139,6 +141,10 @@ def is_adapter(self) -> bool: """Whether the labware is an adapter.""" return LabwareRole.adapter in self._definition.allowedRoles + def is_lid(self) -> bool: + """Whether the labware is a lid.""" + return LabwareRole.lid in self._definition.allowedRoles + def is_fixed_trash(self) -> bool: """Whether the labware is a fixed trash.""" return self._engine_client.state.labware.is_fixed_trash( @@ -158,7 +164,7 @@ def get_next_tip( self, num_tips: int, starting_tip: Optional[WellCore], - nozzle_map: Optional[NozzleMap], + nozzle_map: Optional[NozzleMapInterface], ) -> Optional[str]: return self._engine_client.state.tips.get_next_tip( labware_id=self._labware_id, @@ -186,12 +192,34 @@ def get_well_core(self, well_name: str) -> WellCore: def get_deck_slot(self) -> Optional[DeckSlotName]: """Get the deck slot the labware is in, if on deck.""" try: - return self._engine_client.state.geometry.get_ancestor_slot_name( + ancestor = self._engine_client.state.geometry.get_ancestor_slot_name( self.labware_id ) + if isinstance(ancestor, StagingSlotName): + # The only use case for get_deck_slot is with a legacy OT-2 function which resolves to a numerical deck slot, so we can ignore staging area slots for now + return None + return ancestor except ( LabwareNotOnDeckError, ModuleNotOnDeckError, LocationIsStagingSlotError, ): return None + + def load_liquid(self, volumes: Dict[str, float], liquid: Liquid) -> None: + """Load liquid into wells of the labware.""" + self._engine_client.execute_command( + cmd.LoadLiquidParams( + labwareId=self._labware_id, liquidId=liquid._id, volumeByWell=volumes + ) + ) + + def load_empty(self, wells: List[str]) -> None: + """Mark wells of the labware as empty.""" + self._engine_client.execute_command( + cmd.LoadLiquidParams( + labwareId=self._labware_id, + liquidId="EMPTY", + volumeByWell={well: 0.0 for well in wells}, + ) + ) diff --git a/api/src/opentrons/protocol_api/core/engine/module_core.py b/api/src/opentrons/protocol_api/core/engine/module_core.py index 643111122ce..d3cf8dca725 100644 --- a/api/src/opentrons/protocol_api/core/engine/module_core.py +++ b/api/src/opentrons/protocol_api/core/engine/module_core.py @@ -1,14 +1,13 @@ """Protocol API module implementation logic.""" from __future__ import annotations -from typing import Optional, List, Dict +from typing import Optional, List, Dict, Union from opentrons.hardware_control import SynchronousAdapter, modules as hw_modules from opentrons.hardware_control.modules.types import ( ModuleModel, TemperatureStatus, MagneticStatus, - ThermocyclerStep, SpeedStatus, module_model_from_string, ) @@ -18,6 +17,7 @@ ) from opentrons.protocol_engine import commands as cmd +from opentrons.protocol_engine.types import ABSMeasureMode from opentrons.types import DeckSlotName from opentrons.protocol_engine.clients import SyncClient as ProtocolEngineClient from opentrons.protocol_engine.errors.exceptions import ( @@ -26,7 +26,7 @@ CannotPerformModuleAction, ) -from opentrons.protocols.api_support.types import APIVersion +from opentrons.protocols.api_support.types import APIVersion, ThermocyclerStep from ... import validation from ..module import ( @@ -41,6 +41,11 @@ from .exceptions import InvalidMagnetEngageHeightError +# Valid wavelength range for absorbance reader +ABS_WAVELENGTH_MIN = 350 +ABS_WAVELENGTH_MAX = 1000 + + class ModuleCore(AbstractModuleCore): """Module core logic implementation for Python protocols. Args: @@ -326,15 +331,13 @@ def wait_for_lid_temperature(self) -> None: cmd.thermocycler.WaitForLidTemperatureParams(moduleId=self.module_id) ) - def execute_profile( + def _execute_profile_pre_221( self, steps: List[ThermocyclerStep], repetitions: int, - block_max_volume: Optional[float] = None, + block_max_volume: Optional[float], ) -> None: - """Execute a Thermocycler Profile.""" - self._repetitions = repetitions - self._step_count = len(steps) + """Execute a thermocycler profile using thermocycler/runProfile and flattened steps.""" engine_steps = [ cmd.thermocycler.RunProfileStepParams( celsius=step["temperature"], @@ -351,6 +354,49 @@ def execute_profile( ) ) + def _execute_profile_post_221( + self, + steps: List[ThermocyclerStep], + repetitions: int, + block_max_volume: Optional[float], + ) -> None: + """Execute a thermocycler profile using thermocycler/runExtendedProfile.""" + engine_steps: List[ + Union[cmd.thermocycler.ProfileCycle, cmd.thermocycler.ProfileStep] + ] = [ + cmd.thermocycler.ProfileCycle( + repetitions=repetitions, + steps=[ + cmd.thermocycler.ProfileStep( + celsius=step["temperature"], + holdSeconds=step["hold_time_seconds"], + ) + for step in steps + ], + ) + ] + self._engine_client.execute_command( + cmd.thermocycler.RunExtendedProfileParams( + moduleId=self.module_id, + profileElements=engine_steps, + blockMaxVolumeUl=block_max_volume, + ) + ) + + def execute_profile( + self, + steps: List[ThermocyclerStep], + repetitions: int, + block_max_volume: Optional[float] = None, + ) -> None: + """Execute a Thermocycler Profile.""" + self._repetitions = repetitions + self._step_count = len(steps) + if self.api_version >= APIVersion(2, 21): + return self._execute_profile_post_221(steps, repetitions, block_max_volume) + else: + return self._execute_profile_pre_221(steps, repetitions, block_max_volume) + def deactivate_lid(self) -> None: """Turn off the heated lid.""" self._engine_client.execute_command( @@ -525,24 +571,77 @@ class AbsorbanceReaderCore(ModuleCore, AbstractAbsorbanceReaderCore): """Absorbance Reader core logic implementation for Python protocols.""" _sync_module_hardware: SynchronousAdapter[hw_modules.AbsorbanceReader] - _initialized_value: Optional[int] = None + _initialized_value: Optional[List[int]] = None + _ready_to_initialize: bool = False - def initialize(self, wavelength: int) -> None: + def initialize( + self, + mode: ABSMeasureMode, + wavelengths: List[int], + reference_wavelength: Optional[int] = None, + ) -> None: """Initialize the Absorbance Reader by taking zero reading.""" + if not self._ready_to_initialize: + raise CannotPerformModuleAction( + "Cannot perform Initialize action on Absorbance Reader without calling `.close_lid()` first." + ) + + wavelength_len = len(wavelengths) + if mode == "single" and wavelength_len != 1: + raise ValueError( + f"Single mode can only be initialized with 1 wavelength" + f" {wavelength_len} wavelengths provided instead." + ) + + if mode == "multi" and (wavelength_len < 1 or wavelength_len > 6): + raise ValueError( + f"Multi mode can only be initialized with 1 - 6 wavelengths." + f" {wavelength_len} wavelengths provided instead." + ) + + if reference_wavelength is not None and ( + reference_wavelength < ABS_WAVELENGTH_MIN + or reference_wavelength > ABS_WAVELENGTH_MAX + ): + raise ValueError( + f"Unsupported reference wavelength: ({reference_wavelength}) needs" + f" to between {ABS_WAVELENGTH_MIN} and {ABS_WAVELENGTH_MAX} nm." + ) + + for wavelength in wavelengths: + if ( + not isinstance(wavelength, int) + or wavelength < ABS_WAVELENGTH_MIN + or wavelength > ABS_WAVELENGTH_MAX + ): + raise ValueError( + f"Unsupported sample wavelength: ({wavelength}) needs" + f" to between {ABS_WAVELENGTH_MIN} and {ABS_WAVELENGTH_MAX} nm." + ) + self._engine_client.execute_command( cmd.absorbance_reader.InitializeParams( moduleId=self.module_id, - sampleWavelength=wavelength, + measureMode=mode, + sampleWavelengths=wavelengths, + referenceWavelength=reference_wavelength, ), ) - self._initialized_value = wavelength + self._initialized_value = wavelengths - def read(self) -> Optional[Dict[str, float]]: - """Initiate a read on the Absorbance Reader, and return the results. During Analysis, this will return None.""" + def read(self, filename: Optional[str] = None) -> Dict[int, Dict[str, float]]: + """Initiate a read on the Absorbance Reader, and return the results. During Analysis, this will return a measurement of zero for all wells.""" + wavelengths = self._engine_client.state.modules.get_absorbance_reader_substate( + self.module_id + ).configured_wavelengths + if wavelengths is None: + raise CannotPerformModuleAction( + "Cannot perform Read action on Absorbance Reader without calling `.initialize(...)` first." + ) if self._initialized_value: self._engine_client.execute_command( cmd.absorbance_reader.ReadAbsorbanceParams( - moduleId=self.module_id, sampleWavelength=self._initialized_value + moduleId=self.module_id, fileName=filename ) ) if not self._engine_client.state.config.use_virtual_modules: @@ -556,7 +655,17 @@ def read(self) -> Optional[Dict[str, float]]: raise CannotPerformModuleAction( "Absorbance Reader failed to return expected read result." ) - return None + + # When using virtual modules, return all zeroes + virtual_asbsorbance_result: Dict[int, Dict[str, float]] = {} + for wavelength in wavelengths: + converted_values = ( + self._engine_client.state.modules.convert_absorbance_reader_data_points( + data=[0] * 96 + ) + ) + virtual_asbsorbance_result[wavelength] = converted_values + return virtual_asbsorbance_result def close_lid( self, @@ -567,6 +676,7 @@ def close_lid( moduleId=self.module_id, ) ) + self._ready_to_initialize = True def open_lid(self) -> None: """Close the Absorbance Reader's lid.""" diff --git a/api/src/opentrons/protocol_api/core/engine/pipette_movement_conflict.py b/api/src/opentrons/protocol_api/core/engine/pipette_movement_conflict.py new file mode 100644 index 00000000000..6433c638190 --- /dev/null +++ b/api/src/opentrons/protocol_api/core/engine/pipette_movement_conflict.py @@ -0,0 +1,362 @@ +"""A Protocol-Engine-friendly wrapper for opentrons.motion_planning.deck_conflict.""" +from __future__ import annotations +import logging +from typing import ( + Optional, + Tuple, + Union, + List, +) + +from opentrons_shared_data.errors.exceptions import MotionPlanningFailureError +from opentrons.protocol_engine.errors import LocationIsStagingSlotError +from opentrons_shared_data.module import FLEX_TC_LID_COLLISION_ZONE + +from opentrons.hardware_control import CriticalPoint +from opentrons.motion_planning import adjacent_slots_getters + +from opentrons.protocol_engine import ( + StateView, + DeckSlotLocation, + OnLabwareLocation, + WellLocation, + LiquidHandlingWellLocation, + PickUpTipWellLocation, + DropTipWellLocation, +) +from opentrons.protocol_engine.types import ( + StagingSlotLocation, +) +from opentrons.types import DeckSlotName, StagingSlotName, Point +from . import point_calculations + + +class PartialTipMovementNotAllowedError(MotionPlanningFailureError): + """Error raised when trying to perform a partial tip movement to an illegal location.""" + + def __init__(self, message: str) -> None: + super().__init__( + message=message, + ) + + +class UnsuitableTiprackForPipetteMotion(MotionPlanningFailureError): + """Error raised when trying to perform a pipette movement to a tip rack, based on adapter status.""" + + def __init__(self, message: str) -> None: + super().__init__( + message=message, + ) + + +_log = logging.getLogger(__name__) + +_FLEX_TC_LID_BACK_LEFT_PT = Point( + x=FLEX_TC_LID_COLLISION_ZONE["back_left"]["x"], + y=FLEX_TC_LID_COLLISION_ZONE["back_left"]["y"], + z=FLEX_TC_LID_COLLISION_ZONE["back_left"]["z"], +) + +_FLEX_TC_LID_FRONT_RIGHT_PT = Point( + x=FLEX_TC_LID_COLLISION_ZONE["front_right"]["x"], + y=FLEX_TC_LID_COLLISION_ZONE["front_right"]["y"], + z=FLEX_TC_LID_COLLISION_ZONE["front_right"]["z"], +) + + +def check_safe_for_pipette_movement( # noqa: C901 + engine_state: StateView, + pipette_id: str, + labware_id: str, + well_name: str, + well_location: Union[ + WellLocation, + LiquidHandlingWellLocation, + PickUpTipWellLocation, + DropTipWellLocation, + ], +) -> None: + """Check if the labware is safe to move to with a pipette in partial tip configuration. + + Args: + engine_state: engine state view + pipette_id: ID of the pipette to be moved + labware_id: ID of the labware we are moving to + well_name: Name of the well to move to + well_location: exact location within the well to move to + """ + # TODO (spp, 2023-02-06): remove this check after thorough testing. + # This function is capable of checking for movement conflict regardless of + # nozzle configuration. + if not engine_state.pipettes.get_is_partially_configured(pipette_id): + return + + if isinstance(well_location, DropTipWellLocation): + # convert to WellLocation + well_location = engine_state.geometry.get_checked_tip_drop_location( + pipette_id=pipette_id, + labware_id=labware_id, + well_location=well_location, + partially_configured=True, + ) + well_location_point = engine_state.geometry.get_well_position( + labware_id=labware_id, well_name=well_name, well_location=well_location + ) + primary_nozzle = engine_state.pipettes.get_primary_nozzle(pipette_id) + + destination_cp = _get_critical_point_to_use(engine_state, labware_id) + + pipette_bounds_at_well_location = ( + engine_state.pipettes.get_pipette_bounds_at_specified_move_to_position( + pipette_id=pipette_id, + destination_position=well_location_point, + critical_point=destination_cp, + ) + ) + if not _is_within_pipette_extents( + engine_state=engine_state, + pipette_id=pipette_id, + pipette_bounding_box_at_loc=pipette_bounds_at_well_location, + ): + raise PartialTipMovementNotAllowedError( + f"Requested motion with the {primary_nozzle} nozzle partial configuration" + f" is outside of robot bounds for the pipette." + ) + ancestor = engine_state.geometry.get_ancestor_slot_name(labware_id) + if isinstance(ancestor, StagingSlotName): + raise LocationIsStagingSlotError( + "Cannot perform pipette actions on labware in Staging Area Slot." + ) + labware_slot = ancestor + + surrounding_slots = adjacent_slots_getters.get_surrounding_slots( + slot=labware_slot.as_int(), robot_type=engine_state.config.robot_type + ) + + if _will_collide_with_thermocycler_lid( + engine_state=engine_state, + pipette_bounds=pipette_bounds_at_well_location, + surrounding_regular_slots=surrounding_slots.regular_slots, + ): + raise PartialTipMovementNotAllowedError( + f"Moving to {engine_state.labware.get_display_name(labware_id)} in slot" + f" {labware_slot} with {primary_nozzle} nozzle partial configuration" + f" will result in collision with thermocycler lid in deck slot A1." + ) + + for regular_slot in surrounding_slots.regular_slots: + if _slot_has_potential_colliding_object( + engine_state=engine_state, + pipette_bounds=pipette_bounds_at_well_location, + surrounding_slot=regular_slot, + ): + raise PartialTipMovementNotAllowedError( + f"Moving to {engine_state.labware.get_display_name(labware_id)} in slot" + f" {labware_slot} with {primary_nozzle} nozzle partial configuration" + f" will result in collision with items in deck slot {regular_slot}." + ) + for staging_slot in surrounding_slots.staging_slots: + if _slot_has_potential_colliding_object( + engine_state=engine_state, + pipette_bounds=pipette_bounds_at_well_location, + surrounding_slot=staging_slot, + ): + raise PartialTipMovementNotAllowedError( + f"Moving to {engine_state.labware.get_display_name(labware_id)} in slot" + f" {labware_slot} with {primary_nozzle} nozzle partial configuration" + f" will result in collision with items in staging slot {staging_slot}." + ) + + +def _get_critical_point_to_use( + engine_state: StateView, labware_id: str +) -> Optional[CriticalPoint]: + """Return the critical point to use when accessing the given labware.""" + # TODO (spp, 2024-09-17): looks like Y_CENTER of column is the same as its XY_CENTER. + # I'm using this if-else ladder to be consistent with what we do in + # `MotionPlanning.get_movement_waypoints_to_well()`. + # We should probably use only XY_CENTER in both places. + if engine_state.labware.get_should_center_column_on_target_well(labware_id): + return CriticalPoint.Y_CENTER + elif engine_state.labware.get_should_center_pipette_on_target_well(labware_id): + return CriticalPoint.XY_CENTER + return None + + +def _slot_has_potential_colliding_object( + engine_state: StateView, + pipette_bounds: Tuple[Point, Point, Point, Point], + surrounding_slot: Union[DeckSlotName, StagingSlotName], +) -> bool: + """Return the slot, if any, that has an item that the pipette might collide into.""" + # Check if slot overlaps with pipette position + slot_pos = engine_state.addressable_areas.get_addressable_area_position( + addressable_area_name=surrounding_slot.id, + do_compatibility_check=False, + ) + slot_bounds = engine_state.addressable_areas.get_addressable_area_bounding_box( + addressable_area_name=surrounding_slot.id, + do_compatibility_check=False, + ) + slot_back_left_coords = Point(slot_pos.x, slot_pos.y + slot_bounds.y, slot_pos.z) + slot_front_right_coords = Point(slot_pos.x + slot_bounds.x, slot_pos.y, slot_pos.z) + + # If slot overlaps with pipette bounds + if point_calculations.are_overlapping_rectangles( + rectangle1=(pipette_bounds[0], pipette_bounds[1]), + rectangle2=(slot_back_left_coords, slot_front_right_coords), + ): + # Check z-height of items in overlapping slot + if isinstance(surrounding_slot, DeckSlotName): + slot_highest_z = engine_state.geometry.get_highest_z_in_slot( + DeckSlotLocation(slotName=surrounding_slot) + ) + else: + slot_highest_z = engine_state.geometry.get_highest_z_in_slot( + StagingSlotLocation(slotName=surrounding_slot) + ) + return slot_highest_z >= pipette_bounds[0].z + return False + + +def _will_collide_with_thermocycler_lid( + engine_state: StateView, + pipette_bounds: Tuple[Point, Point, Point, Point], + surrounding_regular_slots: List[DeckSlotName], +) -> bool: + """Return whether the pipette might collide with thermocycler's lid/clips on a Flex. + + If any of the pipette's bounding vertices lie inside the no-go zone of the thermocycler- + which is the area that's to the left, back and below the thermocycler's lid's + protruding clips, then we will mark the movement for possible collision. + + This could cause false raises for the case where an 8-channel is accessing the + thermocycler labware in a location such that the pipette is in the area between + the clips but not touching either clips. But that's a tradeoff we'll need to make + between a complicated check involving accurate positions of all entities involved + and a crude check that disallows all partial tip movements around the thermocycler. + """ + # TODO (spp, 2024-02-27): Improvements: + # - make the check dynamic according to lid state: + # - if lid is open, check if pipette is in no-go zone + # - if lid is closed, use the closed lid height to check for conflict + if ( + DeckSlotName.SLOT_A1 in surrounding_regular_slots + and engine_state.modules.is_flex_deck_with_thermocycler() + ): + return ( + point_calculations.are_overlapping_rectangles( + rectangle1=(_FLEX_TC_LID_BACK_LEFT_PT, _FLEX_TC_LID_FRONT_RIGHT_PT), + rectangle2=(pipette_bounds[0], pipette_bounds[1]), + ) + and pipette_bounds[0].z <= _FLEX_TC_LID_BACK_LEFT_PT.z + ) + + return False + + +def check_safe_for_tip_pickup_and_return( + engine_state: StateView, + pipette_id: str, + labware_id: str, +) -> None: + """Check if the presence or absence of a tiprack adapter might cause any pipette movement issues. + + A 96 channel pipette will pick up tips using cam action when it's configured + to use ALL nozzles. For this, the tiprack needs to be on the Flex 96 channel tiprack adapter + or similar or the tips will not be picked up. + + On the other hand, if the pipette is configured with partial nozzle configuration, + it uses the usual pipette presses to pick the tips up, in which case, having the tiprack + on the Flex 96 channel tiprack adapter (or similar) will cause the pipette to + crash against the adapter posts. + + In order to check if the 96-channel can move and pickup/drop tips safely, this method + checks for the height attribute of the tiprack adapter rather than checking for the + specific official adapter since users might create custom labware &/or definitions + compatible with the official adapter. + """ + if not engine_state.pipettes.get_channels(pipette_id) == 96: + # Adapters only matter to 96 ch. + return + + is_partial_config = engine_state.pipettes.get_is_partially_configured(pipette_id) + tiprack_name = engine_state.labware.get_display_name(labware_id) + tiprack_parent = engine_state.labware.get_location(labware_id) + if isinstance(tiprack_parent, OnLabwareLocation): # tiprack is on an adapter + is_96_ch_tiprack_adapter = engine_state.labware.get_has_quirk( + labware_id=tiprack_parent.labwareId, quirk="tiprackAdapterFor96Channel" + ) + tiprack_height = engine_state.labware.get_dimensions(labware_id=labware_id).z + adapter_height = engine_state.labware.get_dimensions( + labware_id=tiprack_parent.labwareId + ).z + if is_partial_config and tiprack_height < adapter_height: + raise PartialTipMovementNotAllowedError( + f"{tiprack_name} cannot be on an adapter taller than the tip rack" + f" when picking up fewer than 96 tips." + ) + elif not is_partial_config and not is_96_ch_tiprack_adapter: + raise UnsuitableTiprackForPipetteMotion( + f"{tiprack_name} must be on an Opentrons Flex 96 Tip Rack Adapter" + f" in order to pick up or return all 96 tips simultaneously." + ) + + elif ( + not is_partial_config + ): # tiprack is not on adapter and pipette is in full config + raise UnsuitableTiprackForPipetteMotion( + f"{tiprack_name} must be on an Opentrons Flex 96 Tip Rack Adapter" + f" in order to pick up or return all 96 tips simultaneously." + ) + + +def _is_within_pipette_extents( + engine_state: StateView, + pipette_id: str, + pipette_bounding_box_at_loc: Tuple[Point, Point, Point, Point], +) -> bool: + """Whether a given point is within the extents of a configured pipette on the specified robot.""" + channels = engine_state.pipettes.get_channels(pipette_id) + robot_extents = engine_state.geometry.absolute_deck_extents + ( + pip_back_left_bound, + pip_front_right_bound, + pip_back_right_bound, + pip_front_left_bound, + ) = pipette_bounding_box_at_loc + + # Given the padding values accounted for against the deck extents, + # a pipette is within extents when all of the following are true: + + # Each corner slot full pickup case: + # A1: Front right nozzle is within the rear and left-side padding limits + # D1: Back right nozzle is within the front and left-side padding limits + # A3 Front left nozzle is within the rear and right-side padding limits + # D3: Back left nozzle is within the front and right-side padding limits + # Thermocycler Column A2: Front right nozzle is within padding limits + + if channels == 96: + return ( + pip_front_right_bound.y + <= robot_extents.deck_extents.y + robot_extents.padding_rear + and pip_front_right_bound.x >= robot_extents.padding_left_side + and pip_back_right_bound.y >= robot_extents.padding_front + and pip_back_right_bound.x >= robot_extents.padding_left_side + and pip_front_left_bound.y + <= robot_extents.deck_extents.y + robot_extents.padding_rear + and pip_front_left_bound.x + <= robot_extents.deck_extents.x + robot_extents.padding_right_side + and pip_back_left_bound.y >= robot_extents.padding_front + and pip_back_left_bound.x + <= robot_extents.deck_extents.x + robot_extents.padding_right_side + ) + # For 8ch pipettes we only check the rear and front extents + return ( + pip_front_right_bound.y + <= robot_extents.deck_extents.y + robot_extents.padding_rear + and pip_back_right_bound.y >= robot_extents.padding_front + and pip_front_left_bound.y + <= robot_extents.deck_extents.y + robot_extents.padding_rear + and pip_back_left_bound.y >= robot_extents.padding_front + ) diff --git a/api/src/opentrons/protocol_api/core/engine/protocol.py b/api/src/opentrons/protocol_api/core/engine/protocol.py index 04ddaf55a48..d43fc9a2058 100644 --- a/api/src/opentrons/protocol_api/core/engine/protocol.py +++ b/api/src/opentrons/protocol_api/core/engine/protocol.py @@ -2,11 +2,17 @@ from __future__ import annotations from typing import Dict, Optional, Type, Union, List, Tuple, TYPE_CHECKING +from opentrons_shared_data.liquid_classes import LiquidClassDefinitionDoesNotExist + from opentrons.protocol_engine import commands as cmd from opentrons.protocol_engine.commands import LoadModuleResult from opentrons_shared_data.deck.types import DeckDefinitionV5, SlotDefV3 from opentrons_shared_data.labware.labware_definition import LabwareDefinition from opentrons_shared_data.labware.types import LabwareDefinition as LabwareDefDict +from opentrons_shared_data import liquid_classes +from opentrons_shared_data.liquid_classes.liquid_class_definition import ( + LiquidClassSchemaV1, +) from opentrons_shared_data.pipette.types import PipetteNameType from opentrons_shared_data.robot.types import RobotType @@ -51,12 +57,13 @@ from ... import validation from ..._types import OffDeckType -from ..._liquid import Liquid +from ..._liquid import Liquid, LiquidClass from ...disposal_locations import TrashBin, WasteChute from ..protocol import AbstractProtocol from ..labware import LabwareLoadParams from .labware import LabwareCore from .instrument import InstrumentCore +from .robot import RobotCore from .module_core import ( ModuleCore, TemperatureModuleCore, @@ -76,7 +83,9 @@ class ProtocolCore( AbstractProtocol[ - InstrumentCore, LabwareCore, Union[ModuleCore, NonConnectedModuleCore] + InstrumentCore, + LabwareCore, + Union[ModuleCore, NonConnectedModuleCore], ] ): """Protocol API core using a ProtocolEngine. @@ -103,6 +112,7 @@ def __init__( str, Union[ModuleCore, NonConnectedModuleCore] ] = {} self._disposal_locations: List[Union[Labware, TrashBin, WasteChute]] = [] + self._defined_liquid_class_defs_by_name: Dict[str, LiquidClassSchemaV1] = {} self._load_fixed_trash() @property @@ -311,7 +321,6 @@ def load_adapter( return labware_core - # TODO (spp, 2022-12-14): https://opentrons.atlassian.net/browse/RLAB-237 def move_labware( self, labware_core: LabwareCore, @@ -323,6 +332,7 @@ def move_labware( NonConnectedModuleCore, OffDeckType, WasteChute, + TrashBin, ], use_gripper: bool, pause_for_manual_move: bool, @@ -415,6 +425,8 @@ def load_module( raise InvalidModuleLocationError(deck_slot, model.name) robot_type = self._engine_client.state.config.robot_type + # todo(mm, 2024-12-03): This might be possible to remove: + # Protocol Engine will normalize the deck slot itself. normalized_deck_slot = deck_slot.to_equivalent_for_robot_type(robot_type) result = self._engine_client.execute_command_without_recovery( @@ -441,35 +453,10 @@ def load_module( existing_module_ids=list(self._module_cores_by_id.keys()), ) - # When the protocol engine is created, we add Module Lids as part of the deck fixed labware - # If a valid module exists in the deck config. For analysis, we add the labware here since - # deck fixed labware is not created under the same conditions. - if self._engine_client.state.config.use_virtual_modules: - self._load_virtual_module_lid(module_core) - self._module_cores_by_id[module_core.module_id] = module_core return module_core - def _load_virtual_module_lid( - self, module_core: Union[ModuleCore, NonConnectedModuleCore] - ) -> None: - if isinstance(module_core, AbsorbanceReaderCore): - lid = self._engine_client.execute_command_without_recovery( - cmd.LoadLabwareParams( - loadName="opentrons_flex_lid_absorbance_plate_reader_module", - location=ModuleLocation(moduleId=module_core.module_id), - namespace="opentrons", - version=1, - displayName="Absorbance Reader Lid", - ) - ) - - self._engine_client.add_absorbance_reader_lid( - module_id=module_core.module_id, - lid_id=lid.labwareId, - ) - def _create_non_connected_module_core( self, load_module_result: LoadModuleResult ) -> NonConnectedModuleCore: @@ -520,6 +507,12 @@ def _get_module_core( load_module_result=load_module_result, model=model ) + def load_robot(self) -> RobotCore: + """Load a robot core into the RobotContext.""" + return RobotCore( + engine_client=self._engine_client, sync_hardware_api=self._sync_hardware + ) + def load_instrument( self, instrument_name: PipetteNameType, @@ -748,6 +741,23 @@ def define_liquid( ), ) + def define_liquid_class(self, name: str) -> LiquidClass: + """Define a liquid class for use in transfer functions.""" + try: + # Check if we have already loaded this liquid class' definition + liquid_class_def = self._defined_liquid_class_defs_by_name[name] + except KeyError: + try: + # Fetching the liquid class data from file and parsing it + # is an expensive operation and should be avoided. + # Calling this often will degrade protocol execution performance. + liquid_class_def = liquid_classes.load_definition(name) + self._defined_liquid_class_defs_by_name[name] = liquid_class_def + except LiquidClassDefinitionDoesNotExist: + raise ValueError(f"Liquid class definition not found for '{name}'.") + + return LiquidClass.create(liquid_class_def) + def get_labware_location( self, labware_core: LabwareCore ) -> Union[str, LabwareCore, ModuleCore, NonConnectedModuleCore, OffDeckType]: @@ -779,6 +789,7 @@ def _convert_labware_location( NonConnectedModuleCore, OffDeckType, WasteChute, + TrashBin, ], ) -> LabwareLocation: if isinstance(location, LabwareCore): @@ -795,6 +806,7 @@ def _get_non_stacked_location( NonConnectedModuleCore, OffDeckType, WasteChute, + TrashBin, ] ) -> NonStackedLocation: if isinstance(location, (ModuleCore, NonConnectedModuleCore)): @@ -808,3 +820,5 @@ def _get_non_stacked_location( elif isinstance(location, WasteChute): # TODO(mm, 2023-12-06) This will need to determine the appropriate Waste Chute to return, but only move_labware uses this for now return AddressableAreaLocation(addressableAreaName="gripperWasteChute") + elif isinstance(location, TrashBin): + return AddressableAreaLocation(addressableAreaName=location.area_name) diff --git a/api/src/opentrons/protocol_api/core/engine/robot.py b/api/src/opentrons/protocol_api/core/engine/robot.py new file mode 100644 index 00000000000..0418afcbb95 --- /dev/null +++ b/api/src/opentrons/protocol_api/core/engine/robot.py @@ -0,0 +1,139 @@ +from typing import Optional, Dict, Union +from opentrons.hardware_control import SyncHardwareAPI + +from opentrons.types import Mount, MountType, Point, AxisType, AxisMapType +from opentrons_shared_data.pipette import types as pip_types +from opentrons.protocol_api._types import PipetteActionTypes, PlungerPositionTypes +from opentrons.protocol_engine import commands as cmd +from opentrons.protocol_engine.clients import SyncClient as EngineClient +from opentrons.protocol_engine.types import DeckPoint, MotorAxis + +from opentrons.protocol_api.core.robot import AbstractRobot + + +_AXIS_TYPE_TO_MOTOR_AXIS = { + AxisType.X: MotorAxis.X, + AxisType.Y: MotorAxis.Y, + AxisType.P_L: MotorAxis.LEFT_PLUNGER, + AxisType.P_R: MotorAxis.RIGHT_PLUNGER, + AxisType.Z_L: MotorAxis.LEFT_Z, + AxisType.Z_R: MotorAxis.RIGHT_Z, + AxisType.Z_G: MotorAxis.EXTENSION_Z, + AxisType.G: MotorAxis.EXTENSION_JAW, + AxisType.Q: MotorAxis.AXIS_96_CHANNEL_CAM, +} + + +class RobotCore(AbstractRobot): + """Robot API core using a ProtocolEngine. + + Args: + engine_client: A client to the ProtocolEngine that is executing the protocol. + api_version: The Python Protocol API versionat which this core is operating. + sync_hardware: A SynchronousAdapter-wrapped Hardware Control API. + """ + + def __init__( + self, engine_client: EngineClient, sync_hardware_api: SyncHardwareAPI + ) -> None: + self._engine_client = engine_client + self._sync_hardware_api = sync_hardware_api + + def _convert_to_engine_mount(self, axis_map: AxisMapType) -> Dict[MotorAxis, float]: + return {_AXIS_TYPE_TO_MOTOR_AXIS[ax]: dist for ax, dist in axis_map.items()} + + def get_pipette_type_from_engine( + self, mount: Union[Mount, str] + ) -> Optional[pip_types.PipetteNameType]: + """Get the pipette attached to the given mount.""" + if isinstance(mount, Mount): + engine_mount = MountType[mount.name] + else: + if mount.lower() == "right": + engine_mount = MountType.RIGHT + else: + engine_mount = MountType.LEFT + maybe_pipette = self._engine_client.state.pipettes.get_by_mount(engine_mount) + return maybe_pipette.pipetteName if maybe_pipette else None + + def get_plunger_position_from_name( + self, mount: Mount, position_name: PlungerPositionTypes + ) -> float: + engine_mount = MountType[mount.name] + maybe_pipette = self._engine_client.state.pipettes.get_by_mount(engine_mount) + if not maybe_pipette: + return 0.0 + return self._engine_client.state.pipettes.lookup_plunger_position_name( + maybe_pipette.id, position_name.value + ) + + def get_plunger_position_from_volume( + self, mount: Mount, volume: float, action: PipetteActionTypes, robot_type: str + ) -> float: + engine_mount = MountType[mount.name] + maybe_pipette = self._engine_client.state.pipettes.get_by_mount(engine_mount) + if not maybe_pipette: + raise RuntimeError( + f"Cannot load plunger position as no pipette is attached to {mount}" + ) + convert_volume = ( + self._engine_client.state.pipettes.lookup_volume_to_mm_conversion( + maybe_pipette.id, volume, action.value + ) + ) + plunger_bottom = ( + self._engine_client.state.pipettes.lookup_plunger_position_name( + maybe_pipette.id, "bottom" + ) + ) + mm = volume / convert_volume + if robot_type == "OT-2 Standard": + position = plunger_bottom + mm + else: + position = plunger_bottom - mm + return round(position, 6) + + def move_to(self, mount: Mount, destination: Point, speed: Optional[float]) -> None: + engine_mount = MountType[mount.name] + engine_destination = DeckPoint( + x=destination.x, y=destination.y, z=destination.z + ) + self._engine_client.execute_command( + cmd.robot.MoveToParams( + mount=engine_mount, destination=engine_destination, speed=speed + ) + ) + + def move_axes_to( + self, + axis_map: AxisMapType, + critical_point: Optional[AxisMapType], + speed: Optional[float], + ) -> None: + axis_engine_map = self._convert_to_engine_mount(axis_map) + if critical_point: + critical_point_engine = self._convert_to_engine_mount(critical_point) + else: + critical_point_engine = None + + self._engine_client.execute_command( + cmd.robot.MoveAxesToParams( + axis_map=axis_engine_map, + critical_point=critical_point_engine, + speed=speed, + ) + ) + + def move_axes_relative(self, axis_map: AxisMapType, speed: Optional[float]) -> None: + axis_engine_map = self._convert_to_engine_mount(axis_map) + self._engine_client.execute_command( + cmd.robot.MoveAxesRelativeParams(axis_map=axis_engine_map, speed=speed) + ) + + def release_grip(self) -> None: + self._engine_client.execute_command(cmd.robot.openGripperJawParams()) + + def close_gripper(self, force: Optional[float] = None) -> None: + self._engine_client.execute_command( + cmd.robot.closeGripperJawParams(force=force) + ) diff --git a/api/src/opentrons/protocol_api/core/engine/well.py b/api/src/opentrons/protocol_api/core/engine/well.py index 6743a8a39c5..34616d9eb55 100644 --- a/api/src/opentrons/protocol_api/core/engine/well.py +++ b/api/src/opentrons/protocol_api/core/engine/well.py @@ -130,7 +130,10 @@ def load_liquid( liquid: Liquid, volume: float, ) -> None: - """Load liquid into a well.""" + """Load liquid into a well. + + If the well is known to be empty, use ``load_empty()`` instead of calling this with a 0.0 volume. + """ self._engine_client.execute_command( cmd.LoadLiquidParams( labwareId=self._labware_id, diff --git a/api/src/opentrons/protocol_api/core/instrument.py b/api/src/opentrons/protocol_api/core/instrument.py index 1695f96e5db..bc1ec3669df 100644 --- a/api/src/opentrons/protocol_api/core/instrument.py +++ b/api/src/opentrons/protocol_api/core/instrument.py @@ -3,14 +3,14 @@ from __future__ import annotations from abc import abstractmethod, ABC -from typing import Any, Generic, Optional, TypeVar, Union +from typing import Any, Generic, Optional, TypeVar, Union, List from opentrons import types from opentrons.hardware_control.dev_types import PipetteDict from opentrons.protocols.api_support.util import FlowRates +from opentrons.protocols.advanced_control.transfers.common import TransferTipPolicyV2 from opentrons.protocol_api._nozzle_layout import NozzleLayout -from opentrons.hardware_control.nozzle_manager import NozzleMap - +from opentrons.protocol_api._liquid import LiquidClass from ..disposal_locations import TrashBin, WasteChute from .well import WellCoreType @@ -24,6 +24,14 @@ def get_default_speed(self) -> float: def set_default_speed(self, speed: float) -> None: ... + @abstractmethod + def air_gap_in_place(self, volume: float, flow_rate: float) -> None: + """Aspirate a given volume of air from the current location of the pipette. + Args: + volume: The volume of air to aspirate, in microliters. + flow_rate: The flow rate of air into the pipette, in microliters. + """ + @abstractmethod def aspirate( self, @@ -33,6 +41,7 @@ def aspirate( rate: float, flow_rate: float, in_place: bool, + is_meniscus: Optional[bool] = None, ) -> None: """Aspirate a given volume of liquid from the specified location. Args: @@ -55,6 +64,7 @@ def dispense( flow_rate: float, in_place: bool, push_out: Optional[float], + is_meniscus: Optional[bool] = None, ) -> None: """Dispense a given volume of liquid into the specified location. Args: @@ -220,7 +230,7 @@ def get_active_channels(self) -> int: ... @abstractmethod - def get_nozzle_map(self) -> NozzleMap: + def get_nozzle_map(self) -> types.NozzleMapInterface: ... @abstractmethod @@ -251,6 +261,10 @@ def get_blow_out_flow_rate(self, rate: float = 1.0) -> float: def get_liquid_presence_detection(self) -> bool: ... + @abstractmethod + def _pressure_supported_by_pipette(self) -> bool: + ... + @abstractmethod def set_liquid_presence_detection(self, enable: bool) -> None: ... @@ -296,6 +310,32 @@ def configure_nozzle_layout( """ ... + @abstractmethod + def load_liquid_class( + self, + liquid_class: LiquidClass, + pipette_load_name: str, + tiprack_uri: str, + ) -> str: + """Load the liquid class properties of given pipette and tiprack into the engine. + + Returns: ID of the liquid class record + """ + ... + + @abstractmethod + def transfer_liquid( + self, + liquid_class_id: str, + volume: float, + source: List[WellCoreType], + dest: List[WellCoreType], + new_tip: TransferTipPolicyV2, + trash_location: Union[WellCoreType, types.Location, TrashBin, WasteChute], + ) -> None: + """Transfer a liquid from source to dest according to liquid class properties.""" + ... + @abstractmethod def is_tip_tracking_available(self) -> bool: """Return whether auto tip tracking is available for the pipette's current nozzle configuration.""" @@ -325,5 +365,9 @@ def liquid_probe_without_recovery( """Do a liquid probe to find the level of the liquid in the well.""" ... + @abstractmethod + def nozzle_configuration_valid_for_lld(self) -> bool: + """Check if the nozzle configuration currently supports LLD.""" + InstrumentCoreType = TypeVar("InstrumentCoreType", bound=AbstractInstrument[Any]) diff --git a/api/src/opentrons/protocol_api/core/labware.py b/api/src/opentrons/protocol_api/core/labware.py index 67b452cca6d..8bb5c66eb90 100644 --- a/api/src/opentrons/protocol_api/core/labware.py +++ b/api/src/opentrons/protocol_api/core/labware.py @@ -1,8 +1,9 @@ """The interface that implements InstrumentContext.""" + from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any, Generic, List, NamedTuple, Optional, TypeVar +from typing import Any, Generic, List, NamedTuple, Optional, TypeVar, Dict from opentrons_shared_data.labware.types import ( LabwareUri, @@ -10,8 +11,8 @@ LabwareDefinition as LabwareDefinitionDict, ) -from opentrons.types import DeckSlotName, Point -from opentrons.hardware_control.nozzle_manager import NozzleMap +from opentrons.types import DeckSlotName, Point, NozzleMapInterface +from .._liquid import Liquid from .well import WellCoreType @@ -97,6 +98,10 @@ def is_tip_rack(self) -> bool: def is_adapter(self) -> bool: """Whether the labware is an adapter.""" + @abstractmethod + def is_lid(self) -> bool: + """Whether the labware is a lid.""" + @abstractmethod def is_fixed_trash(self) -> bool: """Whether the labware is a fixed trash.""" @@ -114,7 +119,7 @@ def get_next_tip( self, num_tips: int, starting_tip: Optional[WellCoreType], - nozzle_map: Optional[NozzleMap], + nozzle_map: Optional[NozzleMapInterface], ) -> Optional[str]: """Get the name of the next available tip(s) in the rack, if available.""" @@ -130,5 +135,13 @@ def get_well_core(self, well_name: str) -> WellCoreType: def get_deck_slot(self) -> Optional[DeckSlotName]: """Get the deck slot the labware or its parent is in, if any.""" + @abstractmethod + def load_liquid(self, volumes: Dict[str, float], liquid: Liquid) -> None: + """Load liquid into wells of the labware.""" + + @abstractmethod + def load_empty(self, wells: List[str]) -> None: + """Mark wells of the labware as empty.""" + LabwareCoreType = TypeVar("LabwareCoreType", bound=AbstractLabware[Any]) diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py index a831a9113f2..d2d25051d49 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_instrument_core.py @@ -1,12 +1,13 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING, Optional, Union, List from opentrons import types from opentrons.hardware_control import CriticalPoint from opentrons.hardware_control.dev_types import PipetteDict from opentrons.protocol_api.core.common import WellCore +from opentrons.protocols.advanced_control.transfers.common import TransferTipPolicyV2 from opentrons.protocols.api_support import instrument as instrument_support from opentrons.protocols.api_support.definitions import MAX_SUPPORTED_VERSION from opentrons.protocols.api_support.labware_like import LabwareLike @@ -19,7 +20,7 @@ ) from opentrons.protocols.geometry import planning from opentrons.protocol_api._nozzle_layout import NozzleLayout -from opentrons.hardware_control.nozzle_manager import NozzleMap +from opentrons.protocol_api._liquid import LiquidClass from ...disposal_locations import TrashBin, WasteChute from ..instrument import AbstractInstrument @@ -72,6 +73,9 @@ def set_default_speed(self, speed: float) -> None: """Sets the speed at which the robot's gantry moves.""" self._default_speed = speed + def air_gap_in_place(self, volume: float, flow_rate: float) -> None: + assert False, "Air gap tracking only available in API version 2.22 and later" + def aspirate( self, location: types.Location, @@ -80,6 +84,7 @@ def aspirate( rate: float, flow_rate: float, in_place: bool, + is_meniscus: Optional[bool] = None, ) -> None: """Aspirate a given volume of liquid from the specified location. Args: @@ -122,6 +127,7 @@ def dispense( flow_rate: float, in_place: bool, push_out: Optional[float], + is_meniscus: Optional[bool] = None, ) -> None: """Dispense a given volume of liquid into the specified location. Args: @@ -550,11 +556,34 @@ def configure_nozzle_layout( """This will never be called because it was added in API 2.16.""" pass + def load_liquid_class( + self, + liquid_class: LiquidClass, + pipette_load_name: str, + tiprack_uri: str, + ) -> str: + """This will never be called because it was added in ..""" + # TODO(spp, 2024-11-20): update the docstring and error to include API version + assert False, "load_liquid_class is not supported in legacy context" + + def transfer_liquid( + self, + liquid_class_id: str, + volume: float, + source: List[LegacyWellCore], + dest: List[LegacyWellCore], + new_tip: TransferTipPolicyV2, + trash_location: Union[LegacyWellCore, types.Location, TrashBin, WasteChute], + ) -> None: + """This will never be called because it was added in ..""" + # TODO(spp, 2024-11-20): update the docstring and error to include API version + assert False, "transfer_liquid is not supported in legacy context" + def get_active_channels(self) -> int: """This will never be called because it was added in API 2.16.""" assert False, "get_active_channels only supported in API 2.16 & later" - def get_nozzle_map(self) -> NozzleMap: + def get_nozzle_map(self) -> types.NozzleMapInterface: """This will never be called because it was added in API 2.18.""" assert False, "get_nozzle_map only supported in API 2.18 & later" @@ -581,3 +610,10 @@ def liquid_probe_without_recovery( ) -> float: """This will never be called because it was added in API 2.20.""" assert False, "liquid_probe_without_recovery only supported in API 2.20 & later" + + def _pressure_supported_by_pipette(self) -> bool: + return False + + def nozzle_configuration_valid_for_lld(self) -> bool: + """Check if the nozzle configuration currently supports LLD.""" + return False diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_labware_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_labware_core.py index 575fd7a8cc6..3957edb106c 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_labware_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_labware_core.py @@ -1,13 +1,14 @@ -from typing import List, Optional +from typing import List, Optional, Dict from opentrons.calibration_storage import helpers from opentrons.protocols.geometry.labware_geometry import LabwareGeometry from opentrons.protocols.api_support.tip_tracker import TipTracker -from opentrons.types import DeckSlotName, Location, Point -from opentrons.hardware_control.nozzle_manager import NozzleMap +from opentrons.types import DeckSlotName, Location, Point, NozzleMapInterface + from opentrons_shared_data.labware.types import LabwareParameters, LabwareDefinition +from ..._liquid import Liquid from ..labware import AbstractLabware, LabwareLoadParams from .legacy_well_core import LegacyWellCore from .well_geometry import WellGeometry @@ -138,6 +139,11 @@ def is_tip_rack(self) -> bool: def is_adapter(self) -> bool: return False # Adapters were introduced in v2.15 and not supported in legacy protocols + def is_lid(self) -> bool: + return ( + False # Lids were introduced in v2.21 and not supported in legacy protocols + ) + def is_fixed_trash(self) -> bool: """Whether the labware is fixed trash.""" return "fixedTrash" in self.get_quirks() @@ -157,7 +163,7 @@ def get_next_tip( self, num_tips: int, starting_tip: Optional[LegacyWellCore], - nozzle_map: Optional[NozzleMap], + nozzle_map: Optional[NozzleMapInterface], ) -> Optional[str]: if nozzle_map is not None: raise ValueError( @@ -215,3 +221,11 @@ def get_deck_slot(self) -> Optional[DeckSlotName]: """Get the deck slot the labware is in, if in a deck slot.""" slot = self._geometry.parent.labware.first_parent() return DeckSlotName.from_primitive(slot) if slot is not None else None + + def load_liquid(self, volumes: Dict[str, float], liquid: Liquid) -> None: + """Load liquid into wells of the labware.""" + assert False, "load_liquid only supported in API version 2.22 & later" + + def load_empty(self, wells: List[str]) -> None: + """Mark wells of the labware as empty.""" + assert False, "load_empty only supported in API version 2.22 & later" diff --git a/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py b/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py index d698604ac30..d0b95ed82ca 100644 --- a/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py +++ b/api/src/opentrons/protocol_api/core/legacy/legacy_protocol_core.py @@ -17,7 +17,7 @@ from ...labware import Labware from ...disposal_locations import TrashBin, WasteChute -from ..._liquid import Liquid +from ..._liquid import Liquid, LiquidClass from ..._types import OffDeckType from ..protocol import AbstractProtocol from ..labware import LabwareLoadParams @@ -267,7 +267,10 @@ def load_adapter( """Load an adapter using its identifying parameters""" raise APIVersionError(api_element="Loading adapter") - # TODO (spp, 2022-12-14): https://opentrons.atlassian.net/browse/RLAB-237 + def load_robot(self) -> None: # type: ignore + """Load an adapter using its identifying parameters""" + raise APIVersionError(api_element="Loading robot") + def move_labware( self, labware_core: LegacyLabwareCore, @@ -278,6 +281,7 @@ def move_labware( legacy_module_core.LegacyModuleCore, OffDeckType, WasteChute, + TrashBin, ], use_gripper: bool, pause_for_manual_move: bool, @@ -532,6 +536,10 @@ def define_liquid( """Define a liquid to load into a well.""" assert False, "define_liquid only supported on engine core" + def define_liquid_class(self, name: str) -> LiquidClass: + """Define a liquid class.""" + assert False, "define_liquid_class is only supported on engine core" + def get_labware_location( self, labware_core: LegacyLabwareCore ) -> Union[ diff --git a/app/src/atoms/Tooltip/index.tsx b/api/src/opentrons/protocol_api/core/legacy/legacy_robot_core.py similarity index 100% rename from app/src/atoms/Tooltip/index.tsx rename to api/src/opentrons/protocol_api/core/legacy/legacy_robot_core.py diff --git a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py index 1471af79fe8..ec194874528 100644 --- a/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py +++ b/api/src/opentrons/protocol_api/core/legacy_simulator/legacy_instrument_core.py @@ -1,12 +1,13 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING, Optional, Union, List from opentrons import types from opentrons.hardware_control.dev_types import PipetteDict from opentrons.hardware_control.types import HardwareAction from opentrons.protocol_api.core.common import WellCore +from opentrons.protocols.advanced_control.transfers.common import TransferTipPolicyV2 from opentrons.protocols.api_support import instrument as instrument_support from opentrons.protocols.api_support.labware_like import LabwareLike from opentrons.protocols.api_support.types import APIVersion @@ -24,7 +25,7 @@ from ...disposal_locations import TrashBin, WasteChute from opentrons.protocol_api._nozzle_layout import NozzleLayout -from opentrons.hardware_control.nozzle_manager import NozzleMap +from opentrons.protocol_api._liquid import LiquidClass from ..instrument import AbstractInstrument @@ -83,6 +84,9 @@ def get_default_speed(self) -> float: def set_default_speed(self, speed: float) -> None: self._default_speed = speed + def air_gap_in_place(self, volume: float, flow_rate: float) -> None: + assert False, "Air gap tracking only available in API version 2.22 and later" + def aspirate( self, location: types.Location, @@ -91,6 +95,7 @@ def aspirate( rate: float, flow_rate: float, in_place: bool, + is_meniscus: Optional[bool] = None, ) -> None: if self.get_current_volume() == 0: # Make sure we're at the top of the labware and clear of any @@ -132,6 +137,7 @@ def dispense( flow_rate: float, in_place: bool, push_out: Optional[float], + is_meniscus: Optional[bool] = None, ) -> None: if isinstance(location, (TrashBin, WasteChute)): raise APIVersionError( @@ -468,11 +474,34 @@ def configure_nozzle_layout( """This will never be called because it was added in API 2.15.""" pass + def load_liquid_class( + self, + liquid_class: LiquidClass, + pipette_load_name: str, + tiprack_uri: str, + ) -> str: + """This will never be called because it was added in ..""" + # TODO(spp, 2024-11-20): update the docstring and error to include API version + assert False, "load_liquid_class is not supported in legacy context" + + def transfer_liquid( + self, + liquid_class_id: str, + volume: float, + source: List[LegacyWellCore], + dest: List[LegacyWellCore], + new_tip: TransferTipPolicyV2, + trash_location: Union[LegacyWellCore, types.Location, TrashBin, WasteChute], + ) -> None: + """Transfer a liquid from source to dest according to liquid class properties.""" + # TODO(spp, 2024-11-20): update the docstring and error to include API version + assert False, "transfer_liquid is not supported in legacy context" + def get_active_channels(self) -> int: """This will never be called because it was added in API 2.16.""" assert False, "get_active_channels only supported in API 2.16 & later" - def get_nozzle_map(self) -> NozzleMap: + def get_nozzle_map(self) -> types.NozzleMapInterface: """This will never be called because it was added in API 2.18.""" assert False, "get_nozzle_map only supported in API 2.18 & later" @@ -499,3 +528,10 @@ def liquid_probe_without_recovery( ) -> float: """This will never be called because it was added in API 2.20.""" assert False, "liquid_probe_without_recovery only supported in API 2.20 & later" + + def _pressure_supported_by_pipette(self) -> bool: + return False + + def nozzle_configuration_valid_for_lld(self) -> bool: + """Check if the nozzle configuration currently supports LLD.""" + return False diff --git a/api/src/opentrons/protocol_api/core/module.py b/api/src/opentrons/protocol_api/core/module.py index e6968de91d6..e24fbbc54b0 100644 --- a/api/src/opentrons/protocol_api/core/module.py +++ b/api/src/opentrons/protocol_api/core/module.py @@ -16,6 +16,7 @@ MagneticStatus, SpeedStatus, ) +from opentrons.protocol_engine.types import ABSMeasureMode from opentrons.types import DeckSlotName @@ -355,11 +356,16 @@ def get_serial_number(self) -> str: """Get the module's unique hardware serial number.""" @abstractmethod - def initialize(self, wavelength: int) -> None: + def initialize( + self, + mode: ABSMeasureMode, + wavelengths: List[int], + reference_wavelength: Optional[int] = None, + ) -> None: """Initialize the Absorbance Reader by taking zero reading.""" @abstractmethod - def read(self) -> Optional[Dict[str, float]]: + def read(self, filename: Optional[str] = None) -> Dict[int, Dict[str, float]]: """Get an absorbance reading from the Absorbance Reader.""" @abstractmethod diff --git a/api/src/opentrons/protocol_api/core/protocol.py b/api/src/opentrons/protocol_api/core/protocol.py index a8403cc40da..ba9f9a7d14a 100644 --- a/api/src/opentrons/protocol_api/core/protocol.py +++ b/api/src/opentrons/protocol_api/core/protocol.py @@ -18,7 +18,8 @@ from .instrument import InstrumentCoreType from .labware import LabwareCoreType, LabwareLoadParams from .module import ModuleCoreType -from .._liquid import Liquid +from .._liquid import Liquid, LiquidClass +from .robot import AbstractRobot from .._types import OffDeckType from ..disposal_locations import TrashBin, WasteChute @@ -93,7 +94,6 @@ def load_adapter( """Load an adapter using its identifying parameters""" ... - # TODO (spp, 2022-12-14): https://opentrons.atlassian.net/browse/RLAB-237 @abstractmethod def move_labware( self, @@ -105,6 +105,7 @@ def move_labware( ModuleCoreType, OffDeckType, WasteChute, + TrashBin, ], use_gripper: bool, pause_for_manual_move: bool, @@ -248,8 +249,16 @@ def define_liquid( ) -> Liquid: """Define a liquid to load into a well.""" + @abstractmethod + def define_liquid_class(self, name: str) -> LiquidClass: + """Define a liquid class for use in transfer functions.""" + @abstractmethod def get_labware_location( self, labware_core: LabwareCoreType ) -> Union[str, LabwareCoreType, ModuleCoreType, OffDeckType]: """Get labware parent location.""" + + @abstractmethod + def load_robot(self) -> AbstractRobot: + """Load a Robot Core context into a protocol""" diff --git a/api/src/opentrons/protocol_api/core/robot.py b/api/src/opentrons/protocol_api/core/robot.py new file mode 100644 index 00000000000..f65ddbbd7bb --- /dev/null +++ b/api/src/opentrons/protocol_api/core/robot.py @@ -0,0 +1,51 @@ +from abc import abstractmethod, ABC +from typing import Optional, Union + +from opentrons.types import AxisMapType, Mount, Point +from opentrons_shared_data.pipette.types import PipetteNameType +from opentrons.protocol_api._types import PlungerPositionTypes, PipetteActionTypes + + +class AbstractRobot(ABC): + @abstractmethod + def get_pipette_type_from_engine( + self, mount: Union[Mount, str] + ) -> Optional[PipetteNameType]: + ... + + @abstractmethod + def get_plunger_position_from_volume( + self, mount: Mount, volume: float, action: PipetteActionTypes, robot_type: str + ) -> float: + ... + + @abstractmethod + def get_plunger_position_from_name( + self, mount: Mount, position_name: PlungerPositionTypes + ) -> float: + ... + + @abstractmethod + def move_to(self, mount: Mount, destination: Point, speed: Optional[float]) -> None: + ... + + @abstractmethod + def move_axes_to( + self, + axis_map: AxisMapType, + critical_point: Optional[AxisMapType], + speed: Optional[float], + ) -> None: + ... + + @abstractmethod + def move_axes_relative(self, axis_map: AxisMapType, speed: Optional[float]) -> None: + ... + + @abstractmethod + def release_grip(self) -> None: + ... + + @abstractmethod + def close_gripper(self, force: Optional[float] = None) -> None: + ... diff --git a/api/src/opentrons/protocol_api/instrument_context.py b/api/src/opentrons/protocol_api/instrument_context.py index 56dff7f425e..9c6338270c7 100644 --- a/api/src/opentrons/protocol_api/instrument_context.py +++ b/api/src/opentrons/protocol_api/instrument_context.py @@ -2,12 +2,14 @@ import logging from contextlib import ExitStack from typing import Any, List, Optional, Sequence, Union, cast, Dict -from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError from opentrons_shared_data.errors.exceptions import ( CommandPreconditionViolated, CommandParameterLimitViolated, UnexpectedTipRemovalError, + UnsupportedHardwareCommand, ) +from opentrons_shared_data.robot.types import RobotTypeEnum + from opentrons.legacy_broker import LegacyBroker from opentrons.hardware_control.dev_types import PipetteDict from opentrons import types @@ -15,8 +17,7 @@ from opentrons.legacy_commands import publisher from opentrons.protocols.advanced_control.mix import mix_from_kwargs -from opentrons.protocols.advanced_control import transfers - +from opentrons.protocols.advanced_control.transfers import transfer as v1_transfer from opentrons.protocols.api_support.deck_type import NoTrashDefinedError from opentrons.protocols.api_support.types import APIVersion from opentrons.protocols.api_support import instrument @@ -28,7 +29,6 @@ APIVersionError, UnsupportedAPIError, ) -from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType from .core.common import InstrumentCore, ProtocolCore from .core.engine import ENGINE_CORE_API_VERSION @@ -36,10 +36,13 @@ from .config import Clearances from .disposal_locations import TrashBin, WasteChute from ._nozzle_layout import NozzleLayout +from ._liquid import LiquidClass from . import labware, validation - - -AdvancedLiquidHandling = transfers.AdvancedLiquidHandling +from ..config import feature_flags +from ..protocols.advanced_control.transfers.common import ( + TransferTipPolicyV2, + TransferTipPolicyV2Type, +) _DEFAULT_ASPIRATE_CLEARANCE = 1.0 _DEFAULT_DISPENSE_CLEARANCE = 1.0 @@ -60,6 +63,10 @@ """The version after which offsets for deck configured trash containers and changes to alternating tip drop behavior were introduced.""" _PARTIAL_NOZZLE_CONFIGURATION_SINGLE_ROW_PARTIAL_COLUMN_ADDED_IN = APIVersion(2, 20) """The version after which partial nozzle configurations of single, row, and partial column layouts became available.""" +_AIR_GAP_TRACKING_ADDED_IN = APIVersion(2, 22) +"""The version after which air gaps should be implemented with a separate call instead of an aspirate for better liquid volume tracking.""" + +AdvancedLiquidHandling = v1_transfer.AdvancedLiquidHandling class InstrumentContext(publisher.CommandPublisher): @@ -217,8 +224,9 @@ def aspirate( ) ) - well: Optional[labware.Well] = None move_to_location: types.Location + well: Optional[labware.Well] = None + is_meniscus: Optional[bool] = None last_location = self._get_last_location_by_api_version() try: target = validation.validate_location( @@ -232,17 +240,13 @@ def aspirate( "knows where it is." ) from e - if isinstance(target, validation.WellTarget): - move_to_location = target.location or target.well.bottom( - z=self._well_bottom_clearances.aspirate - ) - well = target.well - if isinstance(target, validation.PointTarget): - move_to_location = target.location if isinstance(target, (TrashBin, WasteChute)): raise ValueError( "Trash Bin and Waste Chute are not acceptable location parameters for Aspirate commands." ) + move_to_location, well, is_meniscus = self._handle_aspirate_target( + target=target + ) if self.api_version >= APIVersion(2, 11): instrument.validate_takes_liquid( location=move_to_location, @@ -260,10 +264,11 @@ def aspirate( self.api_version >= APIVersion(2, 20) and well is not None and self.liquid_presence_detection - and self._96_tip_config_valid() + and self._core.nozzle_configuration_valid_for_lld() + and self._core.get_current_volume() == 0 ): + self._raise_if_pressure_not_supported_by_pipette() self.require_liquid_presence(well=well) - self.prepare_to_aspirate() with publisher.publish_context( broker=self.broker, @@ -282,6 +287,7 @@ def aspirate( rate=rate, flow_rate=flow_rate, in_place=target.in_place, + is_meniscus=is_meniscus, ) return self @@ -384,6 +390,7 @@ def dispense( # noqa: C901 ) ) well: Optional[labware.Well] = None + is_meniscus: Optional[bool] = None last_location = self._get_last_location_by_api_version() try: @@ -402,6 +409,7 @@ def dispense( # noqa: C901 well = target.well if target.location: move_to_location = target.location + is_meniscus = target.location.is_meniscus elif well.parent._core.is_fixed_trash(): move_to_location = target.well.top() else: @@ -467,6 +475,7 @@ def dispense( # noqa: C901 flow_rate=flow_rate, in_place=target.in_place, push_out=push_out, + is_meniscus=is_meniscus, ) return self @@ -512,6 +521,8 @@ def mix( ``pipette.mix(1, location=wellplate['A1'])`` is a valid call, but ``pipette.mix(1, wellplate['A1'])`` is not. + .. versionchanged:: 2.21 + Does not repeatedly check for liquid presence. """ _log.debug( "mixing {}uL with {} repetitions in {} at rate={}".format( @@ -540,12 +551,12 @@ def mix( ), ): self.aspirate(volume, location, rate) - while repetitions - 1 > 0: - self.dispense(volume, rate=rate, **dispense_kwargs) - self.aspirate(volume, rate=rate) - repetitions -= 1 - self.dispense(volume, rate=rate) - + with AutoProbeDisable(self): + while repetitions - 1 > 0: + self.dispense(volume, rate=rate, **dispense_kwargs) + self.aspirate(volume, rate=rate) + repetitions -= 1 + self.dispense(volume, rate=rate) return self @requires_version(2, 0) @@ -752,7 +763,11 @@ def air_gap( ``pipette.air_gap(height=2)``. If you call ``air_gap`` with a single, unnamed argument, it will always be interpreted as a volume. - + .. TODO: restore this as a note block for 2.22 docs + Before API version 2.22, this function was implemented as an aspirate, and + dispensing into a well would add the air gap volume to the liquid tracked in + the well. At or above API version 2.22, air gap volume is not counted as liquid + when dispensing into a well. """ if not self._core.has_tip(): raise UnexpectedTipRemovalError("air_gap", self.name, self.mount) @@ -764,7 +779,12 @@ def air_gap( raise RuntimeError("No previous Well cached to perform air gap") target = loc.labware.as_well().top(height) self.move_to(target, publish=False) - self.aspirate(volume) + if self.api_version >= _AIR_GAP_TRACKING_ADDED_IN: + c_vol = self._core.get_available_volume() if volume is None else volume + flow_rate = self._core.get_aspirate_flow_rate() + self._core.air_gap_in_place(c_vol, flow_rate) + else: + self.aspirate(volume) return self @publisher.publish(command=cmds.return_tip) @@ -933,7 +953,7 @@ def pick_up_tip( # noqa: C901 if location is None: if ( nozzle_map is not None - and nozzle_map.configuration != NozzleConfigurationType.FULL + and nozzle_map.configuration != types.NozzleConfigurationType.FULL and self.starting_tip is not None ): # Disallowing this avoids concerning the system with the direction @@ -1205,7 +1225,6 @@ def home_plunger(self) -> InstrumentContext: self._core.home_plunger() return self - # TODO (spp, 2024-03-08): verify if ok to & change source & dest types to AdvancedLiquidHandling @publisher.publish(command=cmds.distribute) @requires_version(2, 0) def distribute( @@ -1245,7 +1264,6 @@ def distribute( return self.transfer(volume, source, dest, **kwargs) - # TODO (spp, 2024-03-08): verify if ok to & change source & dest types to AdvancedLiquidHandling @publisher.publish(command=cmds.consolidate) @requires_version(2, 0) def consolidate( @@ -1395,9 +1413,9 @@ def transfer( # noqa: C901 mix_strategy, mix_opts = mix_from_kwargs(kwargs) if trash: - drop_tip = transfers.DropTipStrategy.TRASH + drop_tip = v1_transfer.DropTipStrategy.TRASH else: - drop_tip = transfers.DropTipStrategy.RETURN + drop_tip = v1_transfer.DropTipStrategy.RETURN new_tip = kwargs.get("new_tip") if isinstance(new_tip, str): @@ -1419,19 +1437,19 @@ def transfer( # noqa: C901 if blow_out and not blowout_location: if self.current_volume: - blow_out_strategy = transfers.BlowOutStrategy.SOURCE + blow_out_strategy = v1_transfer.BlowOutStrategy.SOURCE else: - blow_out_strategy = transfers.BlowOutStrategy.TRASH + blow_out_strategy = v1_transfer.BlowOutStrategy.TRASH elif blow_out and blowout_location: if blowout_location == "source well": - blow_out_strategy = transfers.BlowOutStrategy.SOURCE + blow_out_strategy = v1_transfer.BlowOutStrategy.SOURCE elif blowout_location == "destination well": - blow_out_strategy = transfers.BlowOutStrategy.DEST + blow_out_strategy = v1_transfer.BlowOutStrategy.DEST elif blowout_location == "trash": - blow_out_strategy = transfers.BlowOutStrategy.TRASH + blow_out_strategy = v1_transfer.BlowOutStrategy.TRASH if new_tip != types.TransferTipPolicy.NEVER: - tr, next_tip = labware.next_available_tip( + _, next_tip = labware.next_available_tip( self.starting_tip, self.tip_racks, active_channels, @@ -1443,9 +1461,9 @@ def transfer( # noqa: C901 touch_tip = None if kwargs.get("touch_tip"): - touch_tip = transfers.TouchTipStrategy.ALWAYS + touch_tip = v1_transfer.TouchTipStrategy.ALWAYS - default_args = transfers.Transfer() + default_args = v1_transfer.Transfer() disposal = kwargs.get("disposal_volume") if disposal is None: @@ -1458,7 +1476,7 @@ def transfer( # noqa: C901 f"working volume, {max_volume}uL" ) - transfer_args = transfers.Transfer( + transfer_args = v1_transfer.Transfer( new_tip=new_tip or default_args.new_tip, air_gap=air_gap, carryover=kwargs.get("carryover") or default_args.carryover, @@ -1471,10 +1489,10 @@ def transfer( # noqa: C901 blow_out_strategy=blow_out_strategy or default_args.blow_out_strategy, touch_tip_strategy=(touch_tip or default_args.touch_tip_strategy), ) - transfer_options = transfers.TransferOptions( + transfer_options = v1_transfer.TransferOptions( transfer=transfer_args, mix=mix_opts ) - plan = transfers.TransferPlan( + plan = v1_transfer.TransferPlan( volume, source, dest, @@ -1487,10 +1505,113 @@ def transfer( # noqa: C901 self._execute_transfer(plan) return self - def _execute_transfer(self, plan: transfers.TransferPlan) -> None: + def _execute_transfer(self, plan: v1_transfer.TransferPlan) -> None: for cmd in plan: getattr(self, cmd["method"])(*cmd["args"], **cmd["kwargs"]) + def transfer_liquid( + self, + liquid_class: LiquidClass, + volume: float, + source: Union[ + labware.Well, Sequence[labware.Well], Sequence[Sequence[labware.Well]] + ], + dest: Union[ + labware.Well, Sequence[labware.Well], Sequence[Sequence[labware.Well]] + ], + new_tip: TransferTipPolicyV2Type = "once", + tip_drop_location: Optional[ + Union[types.Location, labware.Well, TrashBin, WasteChute] + ] = None, # Maybe call this 'tip_drop_location' which is similar to PD + ) -> InstrumentContext: + """Transfer liquid from source to dest using the specified liquid class properties. + + TODO: Add args description. + """ + if not feature_flags.allow_liquid_classes( + robot_type=RobotTypeEnum.robot_literal_to_enum( + self._protocol_core.robot_type + ) + ): + raise NotImplementedError("This method is not implemented.") + + flat_sources_list = validation.ensure_valid_flat_wells_list_for_transfer_v2( + source + ) + flat_dests_list = validation.ensure_valid_flat_wells_list_for_transfer_v2(dest) + for well in flat_sources_list + flat_dests_list: + instrument.validate_takes_liquid( + location=well.top(), + reject_module=True, + reject_adapter=True, + ) + if len(flat_sources_list) != len(flat_dests_list): + raise ValueError( + "Sources and destinations should be of the same length in order to perform a transfer." + " To transfer liquid from one source to many destinations, use 'distribute_liquid'," + " to transfer liquid onto one destinations from many sources, use 'consolidate_liquid'." + ) + + valid_new_tip = validation.ensure_new_tip_policy(new_tip) + if valid_new_tip == TransferTipPolicyV2.NEVER: + if self._last_tip_picked_up_from is None: + raise RuntimeError( + "Pipette has no tip attached to perform transfer." + " Either do a pick_up_tip beforehand or specify a new_tip parameter" + " of 'once' or 'always'." + ) + else: + tiprack = self._last_tip_picked_up_from.parent + else: + tiprack, well = labware.next_available_tip( + starting_tip=self.starting_tip, + tip_racks=self.tip_racks, + channels=self.active_channels, + nozzle_map=self._core.get_nozzle_map(), + ) + if self.current_volume != 0: + raise RuntimeError( + "A transfer on a liquid class cannot start with liquid already in the tip." + " Ensure that all previously aspirated liquid is dispensed before starting" + " a new transfer." + ) + + _trash_location: Union[types.Location, labware.Well, TrashBin, WasteChute] + if tip_drop_location is None: + saved_trash = self.trash_container + if isinstance(saved_trash, labware.Labware): + _trash_location = saved_trash.wells()[0] + else: + _trash_location = saved_trash + else: + _trash_location = tip_drop_location + + checked_trash_location = ( + validation.ensure_valid_tip_drop_location_for_transfer_v2( + tip_drop_location=_trash_location + ) + ) + liquid_class_id = self._core.load_liquid_class( + liquid_class=liquid_class, + pipette_load_name=self.name, + tiprack_uri=tiprack.uri, + ) + + self._core.transfer_liquid( + liquid_class_id=liquid_class_id, + volume=volume, + source=[well._core for well in flat_sources_list], + dest=[well._core for well in flat_dests_list], + new_tip=valid_new_tip, + trash_location=( + checked_trash_location._core + if isinstance(checked_trash_location, labware.Well) + else checked_trash_location + ), + ) + + return self + @requires_version(2, 0) def delay(self, *args: Any, **kwargs: Any) -> None: """ @@ -1693,6 +1814,8 @@ def liquid_presence_detection(self) -> bool: @liquid_presence_detection.setter @requires_version(2, 20) def liquid_presence_detection(self, enable: bool) -> None: + if enable: + self._raise_if_pressure_not_supported_by_pipette() self._core.set_liquid_presence_detection(enable) @property @@ -1869,19 +1992,6 @@ def _get_last_location_by_api_version(self) -> Optional[types.Location]: else: return self._protocol_core.get_last_location() - def _96_tip_config_valid(self) -> bool: - n_map = self._core.get_nozzle_map() - channels = self._core.get_active_channels() - if channels == 96: - if ( - n_map.back_left != n_map.full_instrument_back_left - and n_map.front_right != n_map.full_instrument_front_right - ): - raise TipNotAttachedError( - "Either the front right or the back left nozzle must have a tip attached to do LLD." - ) - return True - def __repr__(self) -> str: return "<{}: {} in {}>".format( self.__class__.__name__, @@ -2142,8 +2252,8 @@ def detect_liquid_presence(self, well: labware.Well) -> bool: .. note:: The pressure sensors for the Flex 8-channel pipette are on channels 1 and 8 (positions A1 and H1). For the Flex 96-channel pipette, the pressure sensors are on channels 1 and 96 (positions A1 and H12). Other channels on multi-channel pipettes do not have sensors and cannot detect liquid. """ + self._raise_if_pressure_not_supported_by_pipette() loc = well.top() - self._96_tip_config_valid() return self._core.detect_liquid_presence(well._core, loc) @requires_version(2, 20) @@ -2155,8 +2265,8 @@ def require_liquid_presence(self, well: labware.Well) -> None: .. note:: The pressure sensors for the Flex 8-channel pipette are on channels 1 and 8 (positions A1 and H1). For the Flex 96-channel pipette, the pressure sensors are on channels 1 and 96 (positions A1 and H12). Other channels on multi-channel pipettes do not have sensors and cannot detect liquid. """ + self._raise_if_pressure_not_supported_by_pipette() loc = well.top() - self._96_tip_config_valid() self._core.liquid_probe_with_recovery(well._core, loc) @requires_version(2, 20) @@ -2169,9 +2279,8 @@ def measure_liquid_height(self, well: labware.Well) -> float: This is intended for Opentrons internal use only and is not a guaranteed API. """ - + self._raise_if_pressure_not_supported_by_pipette() loc = well.top() - self._96_tip_config_valid() height = self._core.liquid_probe_without_recovery(well._core, loc) return height @@ -2191,6 +2300,48 @@ def _raise_if_configuration_not_supported_by_pipette( ) # SINGLE, QUADRANT and ALL are supported by all pipettes + def _raise_if_pressure_not_supported_by_pipette(self) -> None: + if not self._core._pressure_supported_by_pipette(): + raise UnsupportedHardwareCommand( + "Pressure sensor not available for this pipette" + ) + + def _handle_aspirate_target( + self, target: validation.ValidTarget + ) -> tuple[types.Location, Optional[labware.Well], Optional[bool]]: + move_to_location: types.Location + well: Optional[labware.Well] = None + is_meniscus: Optional[bool] = None + if isinstance(target, validation.WellTarget): + well = target.well + if target.location: + move_to_location = target.location + is_meniscus = target.location.is_meniscus + + else: + move_to_location = target.well.bottom( + z=self._well_bottom_clearances.aspirate + ) + if isinstance(target, validation.PointTarget): + move_to_location = target.location + return (move_to_location, well, is_meniscus) + + +class AutoProbeDisable: + """Use this class to temporarily disable automatic liquid presence detection.""" + + def __init__(self, instrument: InstrumentContext): + self.instrument = instrument + + def __enter__(self) -> None: + if self.instrument.api_version >= APIVersion(2, 21): + self.auto_presence = self.instrument.liquid_presence_detection + self.instrument.liquid_presence_detection = False + + def __exit__(self, *args: Any, **kwargs: Any) -> None: + if self.instrument.api_version >= APIVersion(2, 21): + self.instrument.liquid_presence_detection = self.auto_presence + def _raise_if_has_end_or_front_right_or_back_left( style: NozzleLayout, diff --git a/api/src/opentrons/protocol_api/labware.py b/api/src/opentrons/protocol_api/labware.py index 1e26342b61e..5e919a44f86 100644 --- a/api/src/opentrons/protocol_api/labware.py +++ b/api/src/opentrons/protocol_api/labware.py @@ -1,4 +1,4 @@ -""" opentrons.protocol_api.labware: classes and functions for labware handling +"""opentrons.protocol_api.labware: classes and functions for labware handling This module provides things like :py:class:`Labware`, and :py:class:`Well` to encapsulate labware instances used in protocols @@ -13,24 +13,34 @@ import logging from itertools import dropwhile -from typing import TYPE_CHECKING, Any, List, Dict, Optional, Union, Tuple, cast +from typing import ( + TYPE_CHECKING, + Any, + List, + Dict, + Optional, + Union, + Tuple, + cast, + Sequence, + Mapping, +) from opentrons_shared_data.labware.types import LabwareDefinition, LabwareParameters -from opentrons.types import Location, Point +from opentrons.types import Location, Point, NozzleMapInterface from opentrons.protocols.api_support.types import APIVersion from opentrons.protocols.api_support.util import ( requires_version, APIVersionError, UnsupportedAPIError, ) -from opentrons.hardware_control.nozzle_manager import NozzleMap + # TODO(mc, 2022-09-02): re-exports provided for backwards compatibility # remove when their usage is no longer needed from opentrons.protocols.labware import ( # noqa: F401 get_labware_definition as get_labware_definition, - get_all_labware_definitions as get_all_labware_definitions, verify_definition as verify_definition, save_definition as save_definition, ) @@ -222,6 +232,19 @@ def center(self) -> Location: """ return Location(self._core.get_center(), self) + @requires_version(2, 21) + def meniscus(self, z: float = 0.0) -> Location: + """ + :param z: An offset on the z-axis, in mm. Positive offsets are higher and + negative offsets are lower. + :return: A :py:class:`~opentrons.types.Location` that indicates location is meniscus and that holds the ``z`` offset in its point.z field. + + :meta private: + """ + return Location( + point=Point(x=0, y=0, z=z), labware=self, _ot_internal_is_meniscus=True + ) + @requires_version(2, 8) def from_center_cartesian(self, x: float, y: float, z: float) -> Point: """ @@ -268,6 +291,10 @@ def load_liquid(self, liquid: Liquid, volume: float) -> None: :param Liquid liquid: The liquid to load into the well. :param float volume: The volume of liquid to load, in µL. + + .. TODO: flag as deprecated in 2.22 docs + In API version 2.22 and later, use :py:meth:`~Labware.load_liquid`, :py:meth:`~Labware.load_liquid_by_well`, + or :py:meth:`~Labware.load_empty` to load liquid into a well. """ self._core.load_liquid( liquid=liquid, @@ -920,7 +947,7 @@ def next_tip( num_tips: int = 1, starting_tip: Optional[Well] = None, *, - nozzle_map: Optional[NozzleMap] = None, + nozzle_map: Optional[NozzleMapInterface] = None, ) -> Optional[Well]: """ Find the next valid well for pick-up. @@ -1093,6 +1120,141 @@ def reset(self) -> None: """ self._core.reset_tips() + @requires_version(2, 22) + def load_liquid( + self, wells: Sequence[Union[str, Well]], volume: float, liquid: Liquid + ) -> None: + """Mark several wells as containing the same amount of liquid. + + This method should be called at the beginning of a protocol, soon after loading the labware and before + liquid handling operations begin. It is a base of information for liquid tracking functionality. If a well in a labware + has not been named in a call to :py:meth:`~Labware.load_empty`, :py:meth:`~Labware.load_liquid`, or + :py:meth:`~Labware.load_liquid_by_well`, the volume it contains is unknown and the well's liquid will not be tracked. + + For example, to load 10µL of a liquid named ``water`` (defined with :py:meth:`~ProtocolContext.define_liquid`) + into all the wells of a labware, you could call ``labware.load_liquid(labware.wells(), 10, water)``. + + If you want to load different volumes of liquid into different wells, use :py:meth:`~Labware.load_liquid_by_well`. + + If you want to mark the well as containing no liquid, use :py:meth:`~Labware.load_empty`. + + :param wells: The wells to load the liquid into. + :type wells: List of well names or list of Well objects, for instance from :py:meth:`~Labware.wells`. + + :param volume: The volume of liquid to load into each well, in 10µL. + :type volume: float + + :param liquid: The liquid to load into each well, previously defined by :py:meth:`~ProtocolContext.define_liquid` + :type liquid: Liquid + """ + well_names: List[str] = [] + for well in wells: + if isinstance(well, str): + if well not in self.wells_by_name(): + raise KeyError( + f"{well} is not a well in labware {self.name}. The elements of wells should name wells in this labware." + ) + well_names.append(well) + elif isinstance(well, Well): + if well.parent is not self: + raise KeyError( + f"{well.well_name} is not a well in labware {self.name}. The elements of wells should be wells of this labware." + ) + well_names.append(well.well_name) + else: + raise TypeError( + f"Unexpected type for element {repr(well)}. The elements of wells should be Well instances or well names." + ) + if not isinstance(volume, (float, int)): + raise TypeError( + f"Unexpected type for volume {repr(volume)}. Volume should be a number in microliters." + ) + self._core.load_liquid({well_name: volume for well_name in well_names}, liquid) + + @requires_version(2, 22) + def load_liquid_by_well( + self, volumes: Mapping[Union[str, Well], float], liquid: Liquid + ) -> None: + """Mark several wells as containing unique volumes of liquid. + + This method should be called at the beginning of a protocol, soon after loading the labware and before + liquid handling operations begin. It is a base of information for liquid tracking functionality. If a well in a labware + has not been named in a call to :py:meth:`~Labware.load_empty`, :py:meth:`~Labware.load_liquid`, or + :py:meth:`~Labware.load_liquid_by_well`, the volume it contains is unknown and the well's liquid will not be tracked. + + For example, to load a decreasing amount of of a liquid named ``water`` (defined with :py:meth:`~ProtocolContext.define_liquid`) + into each successive well of a row, you could call + ``labware.load_liquid_by_well({'A1': 1000, 'A2': 950, 'A3': 900, ..., 'A12': 600}, water)`` + + If you want to load the same volume of a liquid into multiple wells, it is often easier to use :py:meth:`~Labware.load_liquid`. + + If you want to mark the well as containing no liquid, use :py:meth:`~Labware.load_empty`. + + :param volumes: A dictionary of well names (or :py:class:`Well` objects, for instance from ``labware['A1']``) + :type wells: Dict[Union[str, Well], float] + + :param liquid: The liquid to load into each well, previously defined by :py:meth:`~ProtocolContext.define_liquid` + :type liquid: Liquid + """ + verified_volumes: Dict[str, float] = {} + for well, volume in volumes.items(): + if isinstance(well, str): + if well not in self.wells_by_name(): + raise KeyError( + f"{well} is not a well in {self.name}. The keys of volumes should name wells in this labware" + ) + verified_volumes[well] = volume + elif isinstance(well, Well): + if well.parent is not self: + raise KeyError( + f"{well.well_name} is not a well in {self.name}. The keys of volumes should be wells of this labware" + ) + verified_volumes[well.well_name] = volume + else: + raise TypeError( + f"Unexpected type for well name {repr(well)}. The keys of volumes should be Well instances or well names." + ) + if not isinstance(volume, (float, int)): + raise TypeError( + f"Unexpected type for volume {repr(volume)}. The values of volumes should be numbers in microliters." + ) + self._core.load_liquid(verified_volumes, liquid) + + @requires_version(2, 22) + def load_empty(self, wells: Sequence[Union[Well, str]]) -> None: + """Mark several wells as empty. + + This method should be called at the beginning of a protocol, soon after loading the labware and before liquid handling + operations begin. It is a base of information for liquid tracking functionality. If a well in a labware has not been named + in a call to :py:meth:`Labware.load_empty`, :py:meth:`Labware.load_liquid`, or :py:meth:`Labware.load_liquid_by_well`, the + volume it contains is unknown and the well's liquid will not be tracked. + + For instance, to mark all wells in the labware as empty, you can call ``labware.load_empty(labware.wells())``. + + :param wells: The list of wells to mark empty. To mark all wells as empty, pass ``labware.wells()``. You can also specify + wells by their names (for instance, ``labware.load_empty(['A1', 'A2'])``). + :type wells: Union[List[Well], List[str]] + """ + well_names: List[str] = [] + for well in wells: + if isinstance(well, str): + if well not in self.wells_by_name(): + raise KeyError( + f"{well} is not a well in {self.name}. The elements of wells should name wells in this labware." + ) + well_names.append(well) + elif isinstance(well, Well): + if well.parent is not self: + raise KeyError( + f"{well.well_name} is not a well in {self.name}. The elements of wells should be wells of this labware." + ) + well_names.append(well.well_name) + else: + raise TypeError( + f"Unexpected type for well name {repr(well)}. The elements of wells should be Well instances or well names." + ) + self._core.load_empty(well_names) + # TODO(mc, 2022-11-09): implementation detail, move to core def split_tipracks(tip_racks: List[Labware]) -> Tuple[Labware, List[Labware]]: @@ -1109,7 +1271,7 @@ def select_tiprack_from_list( num_channels: int, starting_point: Optional[Well] = None, *, - nozzle_map: Optional[NozzleMap] = None, + nozzle_map: Optional[NozzleMapInterface] = None, ) -> Tuple[Labware, Well]: try: first, rest = split_tipracks(tip_racks) @@ -1147,7 +1309,7 @@ def next_available_tip( tip_racks: List[Labware], channels: int, *, - nozzle_map: Optional[NozzleMap] = None, + nozzle_map: Optional[NozzleMapInterface] = None, ) -> Tuple[Labware, Well]: start = starting_tip if start is None: diff --git a/api/src/opentrons/protocol_api/module_contexts.py b/api/src/opentrons/protocol_api/module_contexts.py index b2abeca24e4..96950b927ef 100644 --- a/api/src/opentrons/protocol_api/module_contexts.py +++ b/api/src/opentrons/protocol_api/module_contexts.py @@ -3,14 +3,16 @@ import logging from typing import List, Dict, Optional, Union, cast +from opentrons_shared_data.errors.exceptions import CommandPreconditionViolated + +from opentrons.protocol_engine.types import ABSMeasureMode from opentrons_shared_data.labware.types import LabwareDefinition from opentrons_shared_data.module.types import ModuleModel, ModuleType from opentrons.legacy_broker import LegacyBroker -from opentrons.hardware_control.modules import ThermocyclerStep from opentrons.legacy_commands import module_commands as cmds from opentrons.legacy_commands.publisher import CommandPublisher, publish -from opentrons.protocols.api_support.types import APIVersion +from opentrons.protocols.api_support.types import APIVersion, ThermocyclerStep from opentrons.protocols.api_support.util import ( APIVersionError, requires_version, @@ -159,7 +161,18 @@ def load_labware( load_location = loaded_adapter._core else: load_location = self._core + name = validation.ensure_lowercase_name(name) + + # todo(mm, 2024-11-08): This check belongs in opentrons.protocol_api.core.engine.deck_conflict. + # We're currently doing it here, at the ModuleContext level, for consistency with what + # ProtocolContext.load_labware() does. (It should also be moved to the deck_conflict module.) + if isinstance(self._core, AbsorbanceReaderCore): + if self._core.is_lid_on(): + raise CommandPreconditionViolated( + f"Cannot load {name} onto the Absorbance Reader Module when its lid is closed." + ) + labware_core = self._protocol_core.load_labware( load_name=name, label=label, @@ -568,7 +581,7 @@ def set_block_temperature( individual well of the loaded labware, in µL. If not specified, the default is 25 µL. - .. note: + .. note:: If ``hold_time_minutes`` and ``hold_time_seconds`` are not specified, the Thermocycler will proceed to the next command @@ -592,10 +605,10 @@ def set_lid_temperature(self, temperature: float) -> None: :param temperature: A value between 37 and 110, representing the target temperature in °C. - .. note: + .. note:: The Thermocycler will proceed to the next command immediately after - ``temperature`` has been reached. + ``temperature`` is reached. """ self._core.set_target_lid_temperature(celsius=temperature) @@ -612,21 +625,18 @@ def execute_profile( """Execute a Thermocycler profile, defined as a cycle of ``steps``, for a given number of ``repetitions``. - :param steps: List of unique steps that make up a single cycle. - Each list item should be a dictionary that maps to - the parameters of the :py:meth:`set_block_temperature` - method with a ``temperature`` key, and either or both of + :param steps: List of steps that make up a single cycle. + Each list item should be a dictionary that maps to the parameters + of the :py:meth:`set_block_temperature` method. The dictionary's + keys must be ``temperature`` and one or both of ``hold_time_seconds`` and ``hold_time_minutes``. :param repetitions: The number of times to repeat the cycled steps. :param block_max_volume: The greatest volume of liquid contained in any individual well of the loaded labware, in µL. If not specified, the default is 25 µL. - .. note: - - Unlike with :py:meth:`set_block_temperature`, either or both of - ``hold_time_minutes`` and ``hold_time_seconds`` must be defined - and for each step. + .. versionchanged:: 2.21 + Fixed run log listing number of steps instead of number of repetitions. """ repetitions = validation.ensure_thermocycler_repetition_count(repetitions) @@ -971,7 +981,7 @@ class MagneticBlockContext(ModuleContext): class AbsorbanceReaderContext(ModuleContext): - """An object representing a connected Absorbance Reader Module. + """An object representing a connected Absorbance Plate Reader Module. It should not be instantiated directly; instead, it should be created through :py:meth:`.ProtocolContext.load_module`. @@ -989,25 +999,88 @@ def serial_number(self) -> str: @requires_version(2, 21) def close_lid(self) -> None: - """Close the lid of the Absorbance Reader.""" + """Use the Flex Gripper to close the lid of the Absorbance Plate Reader. + + You must call this method before initializing the reader, even if the reader was + in the closed position at the start of the protocol. + """ self._core.close_lid() @requires_version(2, 21) def open_lid(self) -> None: - """Open the lid of the Absorbance Reader.""" + """Use the Flex Gripper to open the lid of the Absorbance Plate Reader.""" self._core.open_lid() @requires_version(2, 21) def is_lid_on(self) -> bool: - """Return ``True`` if the Absorbance Reader's lid is currently closed.""" + """Return ``True`` if the Absorbance Plate Reader's lid is currently closed.""" return self._core.is_lid_on() @requires_version(2, 21) - def initialize(self, wavelength: int) -> None: - """Initialize the Absorbance Reader by taking zero reading.""" - self._core.initialize(wavelength) + def initialize( + self, + mode: ABSMeasureMode, + wavelengths: List[int], + reference_wavelength: Optional[int] = None, + ) -> None: + """Prepare the Absorbance Plate Reader to read a plate. + + See :ref:`absorbance-initialization` for examples. + + :param mode: Either ``"single"`` or ``"multi"``. + + - In single measurement mode, :py:meth:`.AbsorbanceReaderContext.read` uses + one sample wavelength and an optional reference wavelength. + - In multiple measurement mode, :py:meth:`.AbsorbanceReaderContext.read` uses + a list of up to six sample wavelengths. + :param wavelengths: A list of wavelengths, in nm, to measure. + + - In the default hardware configuration, each wavelength must be one of + ``450`` (blue), ``562`` (green), ``600`` (orange), or ``650`` (red). In + custom hardware configurations, the module may accept other integers + between 350 and 1000. + - The list must contain only one item when initializing a single measurement. + - The list can contain one to six items when initializing a multiple measurement. + :param reference_wavelength: An optional reference wavelength, in nm. If provided, + :py:meth:`.AbsorbanceReaderContext.read` will read at the reference + wavelength and then subtract the reference wavelength values from the + measurement wavelength values. Can only be used with single measurements. + """ + self._core.initialize( + mode, wavelengths, reference_wavelength=reference_wavelength + ) @requires_version(2, 21) - def read(self) -> Optional[Dict[str, float]]: - """Initiate read on the Absorbance Reader. Returns a dictionary of values ordered by well name.""" - return self._core.read() + def read( + self, export_filename: Optional[str] = None + ) -> Dict[int, Dict[str, float]]: + """Read a plate on the Absorbance Plate Reader. + + This method always returns a dictionary of measurement data. It optionally will + save a CSV file of the results to the Flex filesystem, which you can access from + the Recent Protocol Runs screen in the Opentrons App. These files are `only` saved + if you specify ``export_filename``. + + In simulation, the values for each well key in the dictionary are set to zero, and + no files are written. + + .. note:: + + Avoid divide-by-zero errors when simulating and using the results of this + method later in the protocol. If you divide by any of the measurement + values, use :py:meth:`.ProtocolContext.is_simulating` to use alternate dummy + data or skip the division step. + + :param export_filename: An optional file basename. If provided, this method + will write a CSV file for each measurement in the read operation. File + names will use the value of this parameter, the measurement wavelength + supplied in :py:meth:`~.AbsorbanceReaderContext.initialize`, and a + ``.csv`` extension. For example, when reading at wavelengths 450 and 562 + with ``export_filename="my_data"``, there will be two output files: + ``my_data_450.csv`` and ``my_data_562.csv``. + + See :ref:`absorbance-csv` for information on working with these CSV files. + + :returns: A dictionary of wavelengths to dictionary of values ordered by well name. + """ + return self._core.read(filename=export_filename) diff --git a/api/src/opentrons/protocol_api/protocol_context.py b/api/src/opentrons/protocol_api/protocol_context.py index 687c2277c0a..182019674a5 100644 --- a/api/src/opentrons/protocol_api/protocol_context.py +++ b/api/src/opentrons/protocol_api/protocol_context.py @@ -14,8 +14,10 @@ from opentrons_shared_data.labware.types import LabwareDefinition from opentrons_shared_data.pipette.types import PipetteNameType +from opentrons_shared_data.robot.types import RobotTypeEnum from opentrons.types import Mount, Location, DeckLocation, DeckSlotName, StagingSlotName +from opentrons.config import feature_flags from opentrons.legacy_broker import LegacyBroker from opentrons.hardware_control.modules.types import ( MagneticBlockModel, @@ -43,6 +45,7 @@ UnsupportedAPIError, ) from opentrons_shared_data.errors.exceptions import CommandPreconditionViolated +from opentrons.protocol_engine.errors import LabwareMovementNotAllowedError from ._types import OffDeckType from .core.common import ModuleCore, LabwareCore, ProtocolCore @@ -61,7 +64,7 @@ from .core.legacy.legacy_protocol_core import LegacyProtocolCore from . import validation -from ._liquid import Liquid +from ._liquid import Liquid, LiquidClass from .disposal_locations import TrashBin, WasteChute from .deck import Deck from .instrument_context import InstrumentContext @@ -183,7 +186,14 @@ def __init__( self._commands: List[str] = [] self._params: Parameters = Parameters() self._unsubscribe_commands: Optional[Callable[[], None]] = None - self._robot = RobotContext(self._core) + try: + self._robot: Optional[RobotContext] = RobotContext( + core=self._core.load_robot(), + protocol_core=self._core, + api_version=self._api_version, + ) + except APIVersionError: + self._robot = None self.clear_commands() @property @@ -209,8 +219,14 @@ def api_version(self) -> APIVersion: return self._api_version @property - @requires_version(2, 20) + @requires_version(2, 22) def robot(self) -> RobotContext: + """The :py:class:`.RobotContext` for the protocol. + + :meta private: + """ + if self._core.robot_type != "OT-3 Standard" or not self._robot: + raise RobotTypeError("The RobotContext is only available on Flex robots.") return self._robot @property @@ -222,7 +238,9 @@ def _hw_manager(self) -> HardwareManager: "This function will be deprecated in later versions." "Please use with caution." ) - return self._robot.hardware + if self._robot: + return self._robot.hardware + return HardwareManager(hardware=self._core.get_hardware()) @property @requires_version(2, 0) @@ -657,13 +675,12 @@ def loaded_labwares(self) -> Dict[int, Labware]: if slot is not None } - # TODO (spp, 2022-12-14): https://opentrons.atlassian.net/browse/RLAB-237 @requires_version(2, 15) def move_labware( self, labware: Labware, new_location: Union[ - DeckLocation, Labware, ModuleTypes, OffDeckType, WasteChute + DeckLocation, Labware, ModuleTypes, OffDeckType, WasteChute, TrashBin ], use_gripper: bool = False, pick_up_offset: Optional[Mapping[str, float]] = None, @@ -708,7 +725,8 @@ def move_labware( f"Expected labware of type 'Labware' but got {type(labware)}." ) - # Ensure that when moving to an absorbance reader than the lid is open + # Ensure that when moving to an absorbance reader that the lid is open + # todo(mm, 2024-11-08): Unify this with opentrons.protocol_api.core.engine.deck_conflict. if isinstance(new_location, AbsorbanceReaderContext): if new_location.is_lid_on(): raise CommandPreconditionViolated( @@ -722,11 +740,19 @@ def move_labware( OffDeckType, DeckSlotName, StagingSlotName, + TrashBin, ] if isinstance(new_location, (Labware, ModuleContext)): location = new_location._core elif isinstance(new_location, (OffDeckType, WasteChute)): location = new_location + elif isinstance(new_location, TrashBin): + if labware._core.is_lid(): + location = new_location + else: + raise LabwareMovementNotAllowedError( + "Can only dispose of tips and Lid-type labware in a Trash Bin. Did you mean to use a Waste Chute?" + ) else: location = validation.ensure_and_convert_deck_slot( new_location, self._api_version, self._core.robot_type @@ -932,6 +958,7 @@ def load_instrument( from the Opentrons App or touchscreen. :param bool liquid_presence_detection: If ``True``, enable automatic :ref:`liquid presence detection ` for Flex 1-, 8-, or 96-channel pipettes. + .. versionadded:: 2.20 """ instrument_name = validation.ensure_lowercase_name(instrument_name) @@ -940,7 +967,10 @@ def load_instrument( mount, checked_instrument_name ) - is_96_channel = checked_instrument_name == PipetteNameType.P1000_96 + is_96_channel = checked_instrument_name in [ + PipetteNameType.P1000_96, + PipetteNameType.P200_96, + ] tip_racks = tip_racks or [] @@ -1280,6 +1310,18 @@ def define_liquid( display_color=display_color, ) + def define_liquid_class( + self, + name: str, + ) -> LiquidClass: + """Define a liquid class for use in the protocol.""" + if feature_flags.allow_liquid_classes( + robot_type=RobotTypeEnum.robot_literal_to_enum(self._core.robot_type) + ): + return self._core.define_liquid_class(name=name) + else: + raise NotImplementedError("This method is not implemented.") + @property @requires_version(2, 5) def rail_lights_on(self) -> bool: diff --git a/api/src/opentrons/protocol_api/robot_context.py b/api/src/opentrons/protocol_api/robot_context.py index 01a443cd743..df14b8bb7c5 100644 --- a/api/src/opentrons/protocol_api/robot_context.py +++ b/api/src/opentrons/protocol_api/robot_context.py @@ -1,11 +1,25 @@ -from typing import NamedTuple, Union, Dict, Optional +from typing import NamedTuple, Union, Optional -from opentrons.types import Mount, DeckLocation, Point +from opentrons.types import ( + Mount, + DeckLocation, + Location, + Point, + AxisMapType, + AxisType, + StringAxisMap, +) from opentrons.legacy_commands import publisher -from opentrons.hardware_control import SyncHardwareAPI, types as hw_types +from opentrons.hardware_control import SyncHardwareAPI +from opentrons.protocols.api_support.util import requires_version +from opentrons.protocols.api_support.types import APIVersion +from opentrons_shared_data.pipette.types import PipetteNameType -from ._types import OffDeckType -from .core.common import ProtocolCore +from . import validation +from .core.common import ProtocolCore, RobotCore +from .module_contexts import ModuleContext +from .labware import Labware +from ._types import PipetteActionTypes, PlungerPositionTypes class HardwareManager(NamedTuple): @@ -34,56 +48,214 @@ class RobotContext(publisher.CommandPublisher): """ - def __init__(self, core: ProtocolCore) -> None: - self._hardware = HardwareManager(hardware=core.get_hardware()) + def __init__( + self, core: RobotCore, protocol_core: ProtocolCore, api_version: APIVersion + ) -> None: + self._hardware = HardwareManager(hardware=protocol_core.get_hardware()) + self._core = core + self._protocol_core = protocol_core + self._api_version = api_version + + @property + @requires_version(2, 22) + def api_version(self) -> APIVersion: + return self._api_version @property def hardware(self) -> HardwareManager: + # TODO this hardware attribute should be deprecated + # in version 3.0+ as we will only support exposed robot + # context commands. return self._hardware + @requires_version(2, 22) def move_to( self, mount: Union[Mount, str], - destination: Point, - velocity: float, + destination: Location, + speed: Optional[float] = None, ) -> None: - raise NotImplementedError() + """ + Move a specified mount to a destination location on the deck. + + :param mount: The mount of the instrument you wish to move. + This can either be an instance of :py:class:`.types.Mount` or one + of the strings ``"left"``, ``"right"``, ``"extension"``, ``"gripper"``. Note + that the gripper mount can be referred to either as ``"extension"`` or ``"gripper"``. + :type mount: types.Mount or str + :param Location destination: + :param speed: + """ + mount = validation.ensure_instrument_mount(mount) + self._core.move_to(mount, destination.point, speed) + @requires_version(2, 22) def move_axes_to( self, - abs_axis_map: Dict[hw_types.Axis, hw_types.AxisMapValue], - velocity: float, - critical_point: Optional[hw_types.CriticalPoint], + axis_map: Union[AxisMapType, StringAxisMap], + critical_point: Optional[Union[AxisMapType, StringAxisMap]] = None, + speed: Optional[float] = None, ) -> None: - raise NotImplementedError() + """ + Move a set of axes to an absolute position on the deck. + :param axis_map: A dictionary mapping axes to an absolute position on the deck in mm. + :param critical_point: The critical point to move the axes with. It should only + specify the gantry axes (i.e. `x`, `y`, `z`). + :param float speed: The maximum speed with which you want to move all the axes + in the axis map. + """ + instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT) + is_96_channel = instrument_on_left == PipetteNameType.P1000_96 + axis_map = validation.ensure_axis_map_type( + axis_map, self._protocol_core.robot_type, is_96_channel + ) + if critical_point: + critical_point = validation.ensure_axis_map_type( + critical_point, self._protocol_core.robot_type, is_96_channel + ) + validation.ensure_only_gantry_axis_map_type( + critical_point, self._protocol_core.robot_type + ) + else: + critical_point = None + self._core.move_axes_to(axis_map, critical_point, speed) + + @requires_version(2, 22) def move_axes_relative( - self, rel_axis_map: Dict[hw_types.Axis, hw_types.AxisMapValue], velocity: float + self, + axis_map: Union[AxisMapType, StringAxisMap], + speed: Optional[float] = None, ) -> None: - raise NotImplementedError() + """ + Move a set of axes to a relative position on the deck. + + :param axis_map: A dictionary mapping axes to relative movements in mm. + :type mount: types.Mount or str - def close_gripper_jaw(self, force: float) -> None: - raise NotImplementedError() + :param float speed: The maximum speed with which you want to move all the axes + in the axis map. + """ + instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT) + is_96_channel = instrument_on_left == PipetteNameType.P1000_96 + + axis_map = validation.ensure_axis_map_type( + axis_map, self._protocol_core.robot_type, is_96_channel + ) + self._core.move_axes_relative(axis_map, speed) + + def close_gripper_jaw(self, force: Optional[float] = None) -> None: + """Command the gripper closed with some force.""" + self._core.close_gripper(force) def open_gripper_jaw(self) -> None: - raise NotImplementedError() + """Command the gripper open.""" + self._core.release_grip() def axis_coordinates_for( - self, mount: Union[Mount, str], location: Union[DeckLocation, OffDeckType] - ) -> None: - raise NotImplementedError() + self, + mount: Union[Mount, str], + location: Union[Location, ModuleContext, DeckLocation], + ) -> AxisMapType: + """ + Build a :py:class:`.types.AxisMapType` from a location to be compatible with + either :py:meth:`.RobotContext.move_axes_to` or :py:meth:`.RobotContext.move_axes_relative`. + You must provide only one of `location`, `slot`, or `module` to build + the axis map. + + :param mount: The mount of the instrument you wish create an axis map for. + This can either be an instance of :py:class:`.types.Mount` or one + of the strings ``"left"``, ``"right"``, ``"extension"``, ``"gripper"``. Note + that the gripper mount can be referred to either as ``"extension"`` or ``"gripper"``. + :type mount: types.Mount or str + :param location: The location to format an axis map for. + :type location: `Well`, `ModuleContext`, `DeckLocation` or `OffDeckType` + """ + mount = validation.ensure_instrument_mount(mount) + + mount_axis = AxisType.axis_for_mount(mount) + if location: + loc: Union[Point, Labware, None] + if isinstance(location, ModuleContext): + loc = location.labware + if not loc: + raise ValueError(f"There must be a labware on {location}") + top_of_labware = loc.wells()[0].top() + loc = top_of_labware.point + return {mount_axis: loc.z, AxisType.X: loc.x, AxisType.Y: loc.y} + elif location is DeckLocation and not isinstance(location, Location): + slot_name = validation.ensure_and_convert_deck_slot( + location, + api_version=self._api_version, + robot_type=self._protocol_core.robot_type, + ) + loc = self._protocol_core.get_slot_center(slot_name) + return {mount_axis: loc.z, AxisType.X: loc.x, AxisType.Y: loc.y} + elif isinstance(location, Location): + assert isinstance(location, Location) + loc = location.point + return {mount_axis: loc.z, AxisType.X: loc.x, AxisType.Y: loc.y} + else: + raise ValueError( + "Location parameter must be a Module, Deck Location, or Location type." + ) + else: + raise TypeError("You must specify a location to move to.") def plunger_coordinates_for_volume( - self, mount: Union[Mount, str], volume: float - ) -> None: - raise NotImplementedError() + self, mount: Union[Mount, str], volume: float, action: PipetteActionTypes + ) -> AxisMapType: + """ + Build a :py:class:`.types.AxisMapType` for a pipette plunger motor from volume. + + """ + pipette_name = self._core.get_pipette_type_from_engine(mount) + if not pipette_name: + raise ValueError( + f"Expected a pipette to be attached to provided mount {mount}" + ) + mount = validation.ensure_mount_for_pipette(mount, pipette_name) + pipette_axis = AxisType.plunger_axis_for_mount(mount) + + pipette_position = self._core.get_plunger_position_from_volume( + mount, volume, action, self._protocol_core.robot_type + ) + return {pipette_axis: pipette_position} def plunger_coordinates_for_named_position( - self, mount: Union[Mount, str], position_name: str - ) -> None: - raise NotImplementedError() + self, mount: Union[Mount, str], position_name: PlungerPositionTypes + ) -> AxisMapType: + """ + Build a :py:class:`.types.AxisMapType` for a pipette plunger motor from position_name. - def build_axis_map( - self, axis_map: Dict[hw_types.Axis, hw_types.AxisMapValue] - ) -> None: - raise NotImplementedError() + """ + pipette_name = self._core.get_pipette_type_from_engine(mount) + if not pipette_name: + raise ValueError( + f"Expected a pipette to be attached to provided mount {mount}" + ) + mount = validation.ensure_mount_for_pipette(mount, pipette_name) + pipette_axis = AxisType.plunger_axis_for_mount(mount) + pipette_position = self._core.get_plunger_position_from_name( + mount, position_name + ) + return {pipette_axis: pipette_position} + + def build_axis_map(self, axis_map: StringAxisMap) -> AxisMapType: + """Take in a :py:class:`.types.StringAxisMap` and output a :py:class:`.types.AxisMapType`. + A :py:class:`.types.StringAxisMap` is allowed to contain any of the following strings: + ``"x"``, ``"y"``, "``z_l"``, "``z_r"``, "``z_g"``, ``"q"``. + + An example of a valid axis map could be: + + {"x": 1, "y": 2} or {"Z_L": 100} + + Note that capitalization does not matter. + + """ + instrument_on_left = self._core.get_pipette_type_from_engine(Mount.LEFT) + is_96_channel = instrument_on_left == PipetteNameType.P1000_96 + + return validation.ensure_axis_map_type( + axis_map, self._protocol_core.robot_type, is_96_channel + ) diff --git a/api/src/opentrons/protocol_api/validation.py b/api/src/opentrons/protocol_api/validation.py index 08e56fdef8f..f0db8a71e5e 100644 --- a/api/src/opentrons/protocol_api/validation.py +++ b/api/src/opentrons/protocol_api/validation.py @@ -11,17 +11,26 @@ NamedTuple, TYPE_CHECKING, ) - +from math import isinf, isnan from typing_extensions import TypeGuard from opentrons_shared_data.labware.labware_definition import LabwareRole from opentrons_shared_data.pipette.types import PipetteNameType from opentrons_shared_data.robot.types import RobotType -from opentrons.protocols.api_support.types import APIVersion +from opentrons.protocols.api_support.types import APIVersion, ThermocyclerStep from opentrons.protocols.api_support.util import APIVersionError from opentrons.protocols.models import LabwareDefinition -from opentrons.types import Mount, DeckSlotName, StagingSlotName, Location +from opentrons.protocols.advanced_control.transfers.common import TransferTipPolicyV2 +from opentrons.types import ( + Mount, + DeckSlotName, + StagingSlotName, + Location, + AxisType, + AxisMapType, + StringAxisMap, +) from opentrons.hardware_control.modules.types import ( ModuleModel, MagneticModuleModel, @@ -30,7 +39,6 @@ HeaterShakerModuleModel, MagneticBlockModel, AbsorbanceReaderModel, - ThermocyclerStep, ) from .disposal_locations import TrashBin, WasteChute @@ -65,6 +73,7 @@ "flex_1channel_1000": PipetteNameType.P1000_SINGLE_FLEX, "flex_8channel_1000": PipetteNameType.P1000_MULTI_FLEX, "flex_96channel_1000": PipetteNameType.P1000_96, + "flex_96channel_200": PipetteNameType.P200_96, } @@ -76,6 +85,14 @@ class PipetteMountTypeError(TypeError): """An error raised when an invalid mount type is used for loading pipettes.""" +class InstrumentMountTypeError(TypeError): + """An error raised when an invalid mount type is used for any available instruments.""" + + +class IncorrectAxisError(TypeError): + """An error raised when an invalid axis key is provided in an axis map.""" + + class LabwareDefinitionIsNotAdapterError(ValueError): """An error raised when an adapter is attempted to be loaded as a labware.""" @@ -96,7 +113,7 @@ def ensure_mount_for_pipette( mount: Union[str, Mount, None], pipette: PipetteNameType ) -> Mount: """Ensure that an input value represents a valid mount, and is valid for the given pipette.""" - if pipette == PipetteNameType.P1000_96: + if pipette in [PipetteNameType.P1000_96, PipetteNameType.P200_96]: # Always validate the raw mount input, even if the pipette is a 96-channel and we're not going # to use the mount value. if mount is not None: @@ -147,6 +164,25 @@ def _ensure_mount(mount: Union[str, Mount]) -> Mount: ) +def ensure_instrument_mount(mount: Union[str, Mount]) -> Mount: + """Ensure that an input value represents a valid Mount for all instruments.""" + if isinstance(mount, Mount): + return mount + + if isinstance(mount, str): + if mount == "gripper": + # TODO (lc 08-02-2024) We should decide on the user facing name for + # the gripper mount axis. + mount = "extension" + try: + return Mount[mount.upper()] + except KeyError as e: + raise InstrumentMountTypeError( + "If mount is specified as a string, it must be 'left', 'right', 'gripper', or 'extension';" + f" instead, {mount} was given." + ) from e + + def ensure_pipette_name(pipette_name: str) -> PipetteNameType: """Ensure that an input value represents a valid pipette name.""" pipette_name = ensure_lowercase_name(pipette_name) @@ -159,6 +195,79 @@ def ensure_pipette_name(pipette_name: str) -> PipetteNameType: ) from None +def _check_ot2_axis_type( + robot_type: RobotType, axis_map_keys: Union[List[str], List[AxisType]] +) -> None: + if robot_type == "OT-2 Standard" and isinstance(axis_map_keys[0], AxisType): + if any(k not in AxisType.ot2_axes() for k in axis_map_keys): + raise IncorrectAxisError( + f"An OT-2 Robot only accepts the following axes {AxisType.ot2_axes()}" + ) + if robot_type == "OT-2 Standard" and isinstance(axis_map_keys[0], str): + if any(k.upper() not in [axis.value for axis in AxisType.ot2_axes()] for k in axis_map_keys): # type: ignore [union-attr] + raise IncorrectAxisError( + f"An OT-2 Robot only accepts the following axes {AxisType.ot2_axes()}" + ) + + +def _check_96_channel_axis_type( + is_96_channel: bool, axis_map_keys: Union[List[str], List[AxisType]] +) -> None: + if is_96_channel and any( + key_variation in axis_map_keys for key_variation in ["Z_R", "z_r", AxisType.Z_R] + ): + raise IncorrectAxisError( + "A 96 channel is attached. You cannot move the `Z_R` mount." + ) + if not is_96_channel and any( + key_variation in axis_map_keys for key_variation in ["Q", "q", AxisType.Q] + ): + raise IncorrectAxisError( + "A 96 channel is not attached. The clamp `Q` motor does not exist." + ) + + +def ensure_axis_map_type( + axis_map: Union[AxisMapType, StringAxisMap], + robot_type: RobotType, + is_96_channel: bool = False, +) -> AxisMapType: + """Ensure that the axis map provided is in the correct shape and contains the correct keys.""" + axis_map_keys: Union[List[str], List[AxisType]] = list(axis_map.keys()) # type: ignore + key_type = set(type(k) for k in axis_map_keys) + + if len(key_type) > 1: + raise IncorrectAxisError( + "Please provide an `axis_map` with only string or only AxisType keys." + ) + _check_ot2_axis_type(robot_type, axis_map_keys) + _check_96_channel_axis_type(is_96_channel, axis_map_keys) + + if all(isinstance(k, AxisType) for k in axis_map_keys): + return_map: AxisMapType = axis_map # type: ignore + return return_map + try: + return {AxisType[k.upper()]: v for k, v in axis_map.items()} # type: ignore [union-attr] + except KeyError as e: + raise IncorrectAxisError(f"{e} is not a supported `AxisMapType`") + + +def ensure_only_gantry_axis_map_type( + axis_map: AxisMapType, robot_type: RobotType +) -> None: + """Ensure that the axis map provided is in the correct shape and matches the gantry axes for the robot.""" + if robot_type == "OT-2 Standard": + if any(k not in AxisType.ot2_gantry_axes() for k in axis_map.keys()): + raise IncorrectAxisError( + f"A critical point only accepts OT-2 gantry axes which are {AxisType.ot2_gantry_axes()}" + ) + else: + if any(k not in AxisType.flex_gantry_axes() for k in axis_map.keys()): + raise IncorrectAxisError( + f"A critical point only accepts Flex gantry axes which are {AxisType.flex_gantry_axes()}" + ) + + # TODO(jbl 11-17-2023) this function's original purpose was ensure a valid deck slot for a given robot type # With deck configuration, the shape of this should change to better represent it checking if a deck slot # (and maybe any addressable area) being valid for that deck configuration @@ -436,10 +545,13 @@ class LocationTypeError(TypeError): """Error representing that the location supplied is of different expected type.""" +ValidTarget = Union[WellTarget, PointTarget, TrashBin, WasteChute] + + def validate_location( location: Union[Location, Well, TrashBin, WasteChute, None], last_location: Optional[Location], -) -> Union[WellTarget, PointTarget, TrashBin, WasteChute]: +) -> ValidTarget: """Validate a given location for a liquid handling command. Args: @@ -481,3 +593,127 @@ def validate_location( if well is not None else PointTarget(location=target_location, in_place=in_place) ) + + +def ensure_boolean(value: bool) -> bool: + """Ensure value is a boolean.""" + if not isinstance(value, bool): + raise ValueError("Value must be a boolean.") + return value + + +def ensure_float(value: Union[int, float]) -> float: + """Ensure value is a float (or an integer) and return it as a float.""" + if not isinstance(value, (int, float)): + raise ValueError("Value must be a floating point number.") + return float(value) + + +def ensure_positive_float(value: Union[int, float]) -> float: + """Ensure value is a positive and real float value.""" + float_value = ensure_float(value) + if isnan(float_value) or isinf(float_value): + raise ValueError("Value must be a defined, non-infinite number.") + if float_value < 0: + raise ValueError("Value must be a positive float.") + return float_value + + +def ensure_positive_int(value: int) -> int: + """Ensure value is a positive integer.""" + if not isinstance(value, int): + raise ValueError("Value must be an integer.") + if value < 0: + raise ValueError("Value must be a positive integer.") + return value + + +def validate_coordinates(value: Sequence[float]) -> Tuple[float, float, float]: + """Ensure value is a valid sequence of 3 floats and return a tuple of 3 floats.""" + if len(value) != 3: + raise ValueError("Coordinates must be a sequence of exactly three numbers") + if not all(isinstance(v, (float, int)) for v in value): + raise ValueError("All values in coordinates must be floats.") + return float(value[0]), float(value[1]), float(value[2]) + + +def ensure_new_tip_policy(value: str) -> TransferTipPolicyV2: + """Ensure that new_tip value is a valid TransferTipPolicy value.""" + try: + return TransferTipPolicyV2(value.lower()) + except ValueError: + raise ValueError( + f"'{value}' is invalid value for 'new_tip'." + f" Acceptable value is either 'never', 'once', 'always' or 'per source'." + ) + + +def _verify_each_list_element_is_valid_location(locations: Sequence[Well]) -> None: + from .labware import Well + + for loc in locations: + if not isinstance(loc, Well): + raise ValueError( + f"'{loc}' is not a valid location for transfer." + f" Location should be a well instance." + ) + + +def ensure_valid_flat_wells_list_for_transfer_v2( + target: Union[Well, Sequence[Well], Sequence[Sequence[Well]]], +) -> List[Well]: + """Ensure that the given target(s) for a liquid transfer are valid and in a flat list.""" + from .labware import Well + + if isinstance(target, Well): + return [target] + + if isinstance(target, (list, tuple)): + if len(target) == 0: + raise ValueError("No target well(s) specified for transfer.") + if isinstance(target[0], (list, tuple)): + for sub_sequence in target: + _verify_each_list_element_is_valid_location(sub_sequence) + return [loc for sub_sequence in target for loc in sub_sequence] + else: + _verify_each_list_element_is_valid_location(target) + return list(target) + else: + raise ValueError( + f"'{target}' is not a valid location for transfer." + f" Location should be a well instance, or a 1-dimensional or" + f" 2-dimensional sequence of well instances." + ) + + +def ensure_valid_tip_drop_location_for_transfer_v2( + tip_drop_location: Union[Location, Well, TrashBin, WasteChute] +) -> Union[Location, Well, TrashBin, WasteChute]: + """Ensure that the tip drop location is valid for v2 transfer.""" + from .labware import Well + + if ( + isinstance(tip_drop_location, Well) + or isinstance(tip_drop_location, TrashBin) + or isinstance(tip_drop_location, WasteChute) + ): + return tip_drop_location + elif isinstance(tip_drop_location, Location): + _, maybe_well = tip_drop_location.labware.get_parent_labware_and_well() + + if maybe_well is None: + raise TypeError( + "If a location is specified as a `types.Location`" + " (for instance, as the result of a call to `Well.top()`)," + " it must be a location relative to a well," + " since that is where a tip is dropped." + " However, the given location doesn't refer to any well." + ) + return tip_drop_location + else: + raise TypeError( + f"If specified, location should be an instance of" + f" `types.Location` (e.g. the return value from `Well.top()`)" + f" or `Well` (e.g. `reservoir.wells()[0]`) or an instance of `TrashBin` or `WasteChute`." + f" However, it is '{tip_drop_location}'." + ) diff --git a/api/src/opentrons/protocol_engine/__init__.py b/api/src/opentrons/protocol_engine/__init__.py index 2538b67daf5..7efaef7199d 100644 --- a/api/src/opentrons/protocol_engine/__init__.py +++ b/api/src/opentrons/protocol_engine/__init__.py @@ -18,15 +18,10 @@ CommandType, CommandIntent, ) -from .state import ( - State, - StateView, - StateSummary, - CommandSlice, - CommandPointer, - Config, - CommandErrorSlice, -) +from .state.state import State, StateView +from .state.state_summary import StateSummary +from .state.commands import CommandSlice, CommandErrorSlice, CommandPointer +from .state.config import Config from .plugins import AbstractPlugin from .types import ( @@ -52,13 +47,18 @@ LoadedPipette, MotorAxis, WellLocation, + LiquidHandlingWellLocation, + PickUpTipWellLocation, DropTipWellLocation, WellOrigin, DropTipWellOrigin, + PickUpTipWellOrigin, WellOffset, ModuleModel, ModuleDefinition, Liquid, + LiquidClassRecord, + LiquidClassRecordWithId, AllNozzleLayoutConfiguration, SingleNozzleLayoutConfiguration, RowNozzleLayoutConfiguration, @@ -114,13 +114,18 @@ "LoadedPipette", "MotorAxis", "WellLocation", + "LiquidHandlingWellLocation", + "PickUpTipWellLocation", "DropTipWellLocation", "WellOrigin", "DropTipWellOrigin", + "PickUpTipWellOrigin", "WellOffset", "ModuleModel", "ModuleDefinition", "Liquid", + "LiquidClassRecord", + "LiquidClassRecordWithId", "AllNozzleLayoutConfiguration", "SingleNozzleLayoutConfiguration", "RowNozzleLayoutConfiguration", diff --git a/api/src/opentrons/protocol_engine/actions/__init__.py b/api/src/opentrons/protocol_engine/actions/__init__.py index ff59548971d..6d7125cc83e 100644 --- a/api/src/opentrons/protocol_engine/actions/__init__.py +++ b/api/src/opentrons/protocol_engine/actions/__init__.py @@ -28,8 +28,8 @@ DoorChangeAction, ResetTipsAction, SetPipetteMovementSpeedAction, - AddAbsorbanceReaderLidAction, ) +from .get_state_update import get_state_updates __all__ = [ # action pipeline interface @@ -57,8 +57,9 @@ "DoorChangeAction", "ResetTipsAction", "SetPipetteMovementSpeedAction", - "AddAbsorbanceReaderLidAction", # action payload values "PauseSource", "FinishErrorDetails", + # helper functions + "get_state_updates", ] diff --git a/api/src/opentrons/protocol_engine/actions/actions.py b/api/src/opentrons/protocol_engine/actions/actions.py index 38cbbe18bb3..15b04048699 100644 --- a/api/src/opentrons/protocol_engine/actions/actions.py +++ b/api/src/opentrons/protocol_engine/actions/actions.py @@ -3,7 +3,7 @@ Actions can be passed to the ActionDispatcher, where they will trigger reactions in objects that subscribe to the pipeline, like the StateStore. """ -from dataclasses import dataclass +import dataclasses from datetime import datetime from enum import Enum from typing import List, Optional, Union @@ -18,10 +18,10 @@ Command, CommandCreate, CommandDefinedErrorData, - CommandPrivateResult, ) from ..error_recovery_policy import ErrorRecoveryPolicy, ErrorRecoveryType from ..notes.notes import CommandNote +from ..state.update_types import StateUpdate from ..types import ( LabwareOffsetCreate, ModuleDefinition, @@ -31,7 +31,7 @@ ) -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class PlayAction: """Start or resume processing commands in the engine.""" @@ -50,28 +50,28 @@ class PauseSource(str, Enum): PROTOCOL = "protocol" -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class PauseAction: """Pause processing commands in the engine.""" source: PauseSource -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class StopAction: """Request engine execution to stop soon.""" from_estop: bool = False -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class ResumeFromRecoveryAction: """See `ProtocolEngine.resume_from_recovery()`.""" - pass + state_update: StateUpdate -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class FinishErrorDetails: """Error details for the payload of a FinishAction or HardwareStoppedAction.""" @@ -80,7 +80,7 @@ class FinishErrorDetails: created_at: datetime -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class FinishAction: """Gracefully stop processing commands in the engine.""" @@ -95,7 +95,7 @@ class FinishAction: """The fatal error that caused the run to fail.""" -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class HardwareStoppedAction: """An action dispatched after hardware has been stopped for good, for this engine instance.""" @@ -105,14 +105,14 @@ class HardwareStoppedAction: """The error that happened while doing post-run finish steps (homing and dropping tips).""" -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class DoorChangeAction: """Handle events coming in from hardware control.""" door_state: DoorState -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class QueueCommandAction: """Add a command request to the queue.""" @@ -123,7 +123,7 @@ class QueueCommandAction: failed_command_id: Optional[str] = None -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class RunCommandAction: """Mark a given command as running. @@ -135,7 +135,7 @@ class RunCommandAction: started_at: datetime -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class SucceedCommandAction: """Mark a given command as succeeded. @@ -145,10 +145,15 @@ class SucceedCommandAction: command: Command """The command in its new succeeded state.""" - private_result: CommandPrivateResult + state_update: StateUpdate = dataclasses.field( + # todo(mm, 2024-08-26): This has a default only to make it easier to transition + # old tests while https://opentrons.atlassian.net/browse/EXEC-639 is in + # progress. Make this mandatory when that's completed. + default_factory=StateUpdate + ) -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class FailCommandAction: """Mark a given command as failed. @@ -196,7 +201,7 @@ class FailCommandAction: """The command to fail, in its prior `running` state.""" -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class AddLabwareOffsetAction: """Add a labware offset, to apply to subsequent `LoadLabwareCommand`s.""" @@ -205,28 +210,28 @@ class AddLabwareOffsetAction: request: LabwareOffsetCreate -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class AddLabwareDefinitionAction: """Add a labware definition, to apply to subsequent `LoadLabwareCommand`s.""" definition: LabwareDefinition -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class AddLiquidAction: """Add a liquid, to apply to subsequent `LoadLiquid`s.""" liquid: Liquid -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class SetDeckConfigurationAction: """See `ProtocolEngine.set_deck_configuration()`.""" deck_configuration: Optional[DeckConfigurationType] -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class AddAddressableAreaAction: """Add a single addressable area to state. @@ -238,7 +243,7 @@ class AddAddressableAreaAction: addressable_area: AddressableAreaLocation -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class AddModuleAction: """Add an attached module directly to state without a location.""" @@ -248,14 +253,14 @@ class AddModuleAction: module_live_data: LiveData -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class ResetTipsAction: """Reset the tip tracking state of a given tip rack.""" labware_id: str -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class SetPipetteMovementSpeedAction: """Set the speed of a pipette's X/Y/Z movements. Does not affect plunger speed. @@ -266,18 +271,7 @@ class SetPipetteMovementSpeedAction: speed: Optional[float] -@dataclass(frozen=True) -class AddAbsorbanceReaderLidAction: - """Add the absorbance reader lid id to the absorbance reader module substate. - - This action is dispatched the absorbance reader module is first loaded. - """ - - module_id: str - lid_id: str - - -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class SetErrorRecoveryPolicyAction: """See `ProtocolEngine.set_error_recovery_policy()`.""" @@ -304,6 +298,5 @@ class SetErrorRecoveryPolicyAction: AddLiquidAction, ResetTipsAction, SetPipetteMovementSpeedAction, - AddAbsorbanceReaderLidAction, SetErrorRecoveryPolicyAction, ] diff --git a/api/src/opentrons/protocol_engine/actions/get_state_update.py b/api/src/opentrons/protocol_engine/actions/get_state_update.py new file mode 100644 index 00000000000..ec29a6e38ef --- /dev/null +++ b/api/src/opentrons/protocol_engine/actions/get_state_update.py @@ -0,0 +1,38 @@ +# noqa: D100 +from __future__ import annotations +from typing import TYPE_CHECKING + +from .actions import ( + Action, + ResumeFromRecoveryAction, + SucceedCommandAction, + FailCommandAction, +) +from ..commands.command import DefinedErrorData +from ..error_recovery_policy import ErrorRecoveryType + +if TYPE_CHECKING: + from ..state.update_types import StateUpdate + + +def get_state_updates(action: Action) -> list[StateUpdate]: + """Extract all the StateUpdates that the StateStores should apply when they apply an action.""" + if isinstance(action, SucceedCommandAction): + return [action.state_update] + + elif isinstance(action, FailCommandAction) and isinstance( + action.error, DefinedErrorData + ): + if action.type == ErrorRecoveryType.ASSUME_FALSE_POSITIVE_AND_CONTINUE: + return [ + action.error.state_update, + action.error.state_update_if_false_positive, + ] + else: + return [action.error.state_update] + + elif isinstance(action, ResumeFromRecoveryAction): + return [action.state_update] + + else: + return [] diff --git a/api/src/opentrons/protocol_engine/clients/sync_client.py b/api/src/opentrons/protocol_engine/clients/sync_client.py index 26356a76a15..71837a7a2ca 100644 --- a/api/src/opentrons/protocol_engine/clients/sync_client.py +++ b/api/src/opentrons/protocol_engine/clients/sync_client.py @@ -7,7 +7,7 @@ from .. import commands from ..commands.command_unions import CREATE_TYPES_BY_PARAMS_TYPE -from ..state import StateView +from ..state.state import StateView from ..types import ( Liquid, LabwareOffsetCreate, @@ -89,6 +89,12 @@ def execute_command_without_recovery( ) -> commands.TryLiquidProbeResult: pass + @overload + def execute_command_without_recovery( + self, params: commands.LoadLiquidClassParams + ) -> commands.LoadLiquidClassResult: + pass + def execute_command_without_recovery( self, params: commands.CommandParams ) -> commands.CommandResult: @@ -119,12 +125,6 @@ def add_addressable_area(self, addressable_area_name: str) -> None: "add_addressable_area", addressable_area_name=addressable_area_name ) - def add_absorbance_reader_lid(self, module_id: str, lid_id: str) -> None: - """Add an absorbance reader lid to the module state.""" - self._transport.call_method( - "add_absorbance_reader_lid", module_id=module_id, lid_id=lid_id - ) - def add_liquid( self, name: str, color: Optional[str], description: Optional[str] ) -> Liquid: diff --git a/api/src/opentrons/protocol_engine/clients/transports.py b/api/src/opentrons/protocol_engine/clients/transports.py index 348bbc286c2..5d678026fb2 100644 --- a/api/src/opentrons/protocol_engine/clients/transports.py +++ b/api/src/opentrons/protocol_engine/clients/transports.py @@ -10,7 +10,7 @@ from ..protocol_engine import ProtocolEngine from ..errors import ProtocolCommandFailedError from ..error_recovery_policy import ErrorRecoveryType -from ..state import StateView +from ..state.state import StateView from ..commands import Command, CommandCreate, CommandResult, CommandStatus diff --git a/api/src/opentrons/protocol_engine/commands/__init__.py b/api/src/opentrons/protocol_engine/commands/__init__.py index d0550fce8c5..f25293f85fb 100644 --- a/api/src/opentrons/protocol_engine/commands/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/__init__.py @@ -20,6 +20,7 @@ from . import thermocycler from . import calibration from . import unsafe +from . import robot from .hash_command_params import hash_protocol_command_params from .generate_command_schema import generate_command_schema @@ -38,10 +39,17 @@ CommandCreate, CommandResult, CommandType, - CommandPrivateResult, CommandDefinedErrorData, ) +from .air_gap_in_place import ( + AirGapInPlace, + AirGapInPlaceParams, + AirGapInPlaceCreate, + AirGapInPlaceResult, + AirGapInPlaceCommandType, +) + from .aspirate import ( Aspirate, AspirateParams, @@ -139,6 +147,15 @@ LoadLiquidImplementation, ) +from .load_liquid_class import ( + LoadLiquidClass, + LoadLiquidClassParams, + LoadLiquidClassCreate, + LoadLiquidClassResult, + LoadLiquidClassCommandType, + LoadLiquidClassImplementation, +) + from .load_module import ( LoadModule, LoadModuleParams, @@ -153,7 +170,6 @@ LoadPipetteCreate, LoadPipetteResult, LoadPipetteCommandType, - LoadPipettePrivateResult, ) from .move_labware import ( @@ -306,7 +322,6 @@ ConfigureNozzleLayoutCreate, ConfigureNozzleLayoutParams, ConfigureNozzleLayoutResult, - ConfigureNozzleLayoutPrivateResult, ConfigureNozzleLayoutCommandType, ) @@ -358,6 +373,12 @@ "hash_protocol_command_params", # command schema generation "generate_command_schema", + # air gap command models + "AirGapInPlace", + "AirGapInPlaceCreate", + "AirGapInPlaceParams", + "AirGapInPlaceResult", + "AirGapInPlaceCommandType", # aspirate command models "Aspirate", "AspirateCreate", @@ -541,6 +562,14 @@ "LoadLiquidParams", "LoadLiquidResult", "LoadLiquidCommandType", + # load liquid class command models + "LoadLiquidClass", + "LoadLiquidClassParams", + "LoadLiquidClassCreate", + "LoadLiquidClassResult", + "LoadLiquidClassImplementation", + "LoadLiquidClassCommandType", + # hardware control command models # hardware module command bundles "absorbance_reader", "heater_shaker", @@ -551,6 +580,7 @@ "calibration", # unsafe command bundle "unsafe", + "robot", # configure pipette volume command bundle "ConfigureForVolume", "ConfigureForVolumeCreate", @@ -569,7 +599,6 @@ "ConfigureNozzleLayoutParams", "ConfigureNozzleLayoutResult", "ConfigureNozzleLayoutCommandType", - "ConfigureNozzleLayoutPrivateResult", # get pipette tip presence bundle "GetTipPresence", "GetTipPresenceCreate", diff --git a/api/src/opentrons/protocol_engine/commands/absorbance_reader/__init__.py b/api/src/opentrons/protocol_engine/commands/absorbance_reader/__init__.py index 09307e85230..2ed24ae23c3 100644 --- a/api/src/opentrons/protocol_engine/commands/absorbance_reader/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/absorbance_reader/__init__.py @@ -1,5 +1,4 @@ """Command models for Absorbance Reader commands.""" -from .types import MoveLidResult from .close_lid import ( CloseLidCommandType, CloseLidParams, diff --git a/api/src/opentrons/protocol_engine/commands/absorbance_reader/close_lid.py b/api/src/opentrons/protocol_engine/commands/absorbance_reader/close_lid.py index 31c51676e7d..069c2803b22 100644 --- a/api/src/opentrons/protocol_engine/commands/absorbance_reader/close_lid.py +++ b/api/src/opentrons/protocol_engine/commands/absorbance_reader/close_lid.py @@ -10,13 +10,13 @@ from ...errors import CannotPerformModuleAction from opentrons.protocol_engine.types import AddressableAreaLocation -from opentrons.protocol_engine.resources import labware_validation -from .types import MoveLidResult +from ...state.update_types import StateUpdate + from opentrons.drivers.types import AbsorbanceReaderLidStatus if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import ( EquipmentHandler, LabwareMovementHandler, @@ -32,13 +32,11 @@ class CloseLidParams(BaseModel): moduleId: str = Field(..., description="Unique ID of the absorbance reader.") -class CloseLidResult(MoveLidResult): +class CloseLidResult(BaseModel): """Result data from closing the lid on an aborbance reading.""" -class CloseLidImpl( - AbstractCommandImpl[CloseLidParams, SuccessData[CloseLidResult, None]] -): +class CloseLidImpl(AbstractCommandImpl[CloseLidParams, SuccessData[CloseLidResult]]): """Execution implementation of closing the lid on an Absorbance Reader.""" def __init__( @@ -52,45 +50,37 @@ def __init__( self._equipment = equipment self._labware_movement = labware_movement - async def execute( - self, params: CloseLidParams - ) -> SuccessData[CloseLidResult, None]: + async def execute(self, params: CloseLidParams) -> SuccessData[CloseLidResult]: """Execute the close lid command.""" + state_update = StateUpdate() mod_substate = self._state_view.modules.get_absorbance_reader_substate( module_id=params.moduleId ) - # lid should currently be on the module - assert mod_substate.lid_id is not None - loaded_lid = self._state_view.labware.get(mod_substate.lid_id) - assert labware_validation.is_absorbance_reader_lid(loaded_lid.loadName) - hardware_lid_status = AbsorbanceReaderLidStatus.OFF - # If the lid is closed, if the lid is open No-op out if not self._state_view.config.use_virtual_modules: abs_reader = self._equipment.get_module_hardware_api(mod_substate.module_id) if abs_reader is not None: - result = await abs_reader.get_current_lid_status() - hardware_lid_status = result + hardware_lid_status = await abs_reader.get_current_lid_status() else: raise CannotPerformModuleAction( "Could not reach the Hardware API for Opentrons Plate Reader Module." ) - # If the lid is already ON, no-op losing lid if hardware_lid_status is AbsorbanceReaderLidStatus.ON: - # The lid is already On, so we can no-op and return the lids current location data - assert isinstance(loaded_lid.location, AddressableAreaLocation) - new_location = loaded_lid.location - new_offset_id = self._equipment.find_applicable_labware_offset_id( - labware_definition_uri=loaded_lid.definitionUri, - labware_location=loaded_lid.location, + # The lid is already physically ON, so we can no-op physically closing it + state_update.set_absorbance_reader_lid( + module_id=mod_substate.module_id, is_lid_on=True ) else: # Allow propagation of ModuleNotAttachedError. _ = self._equipment.get_module_hardware_api(mod_substate.module_id) + lid_definition = ( + self._state_view.labware.get_absorbance_reader_lid_definition() + ) + current_location = self._state_view.modules.absorbance_reader_dock_location( params.moduleId ) @@ -110,33 +100,32 @@ async def execute( ) ) - lid_gripper_offsets = self._state_view.labware.get_labware_gripper_offsets( - loaded_lid.id, None + # The lid's labware definition stores gripper offsets for itself in the + # space normally meant for offsets for labware stacked atop it. + lid_gripper_offsets = self._state_view.labware.get_child_gripper_offsets( + labware_definition=lid_definition, + slot_name=None, ) if lid_gripper_offsets is None: raise ValueError( "Gripper Offset values for Absorbance Reader Lid labware must not be None." ) - # Skips gripper moves when using virtual gripper await self._labware_movement.move_labware_with_gripper( - labware_id=loaded_lid.id, + labware_definition=lid_definition, current_location=current_location, new_location=new_location, user_offset_data=lid_gripper_offsets, post_drop_slide_offset=None, ) - - new_offset_id = self._equipment.find_applicable_labware_offset_id( - labware_definition_uri=loaded_lid.definitionUri, - labware_location=new_location, + state_update.set_absorbance_reader_lid( + module_id=mod_substate.module_id, + is_lid_on=True, ) return SuccessData( - public=CloseLidResult( - lidId=loaded_lid.id, newLocation=new_location, offsetId=new_offset_id - ), - private=None, + public=CloseLidResult(), + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/commands/absorbance_reader/initialize.py b/api/src/opentrons/protocol_engine/commands/absorbance_reader/initialize.py index 469732ee9fd..458225ad1bb 100644 --- a/api/src/opentrons/protocol_engine/commands/absorbance_reader/initialize.py +++ b/api/src/opentrons/protocol_engine/commands/absorbance_reader/initialize.py @@ -1,15 +1,19 @@ """Command models to initialize an Absorbance Reader.""" from __future__ import annotations -from typing import Optional, Literal, TYPE_CHECKING +from typing import List, Optional, Literal, TYPE_CHECKING from typing_extensions import Type from pydantic import BaseModel, Field +from opentrons.drivers.types import ABSMeasurementMode +from opentrons.protocol_engine.types import ABSMeasureMode + from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ...errors.error_occurrence import ErrorOccurrence +from ...errors import InvalidWavelengthError if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler @@ -20,7 +24,13 @@ class InitializeParams(BaseModel): """Input parameters to initialize an absorbance reading.""" moduleId: str = Field(..., description="Unique ID of the absorbance reader.") - sampleWavelength: int = Field(..., description="Sample wavelength in nm.") + measureMode: ABSMeasureMode = Field( + ..., description="Initialize single or multi measurement mode." + ) + sampleWavelengths: List[int] = Field(..., description="Sample wavelengths in nm.") + referenceWavelength: Optional[int] = Field( + None, description="Optional reference wavelength in nm." + ) class InitializeResult(BaseModel): @@ -28,7 +38,7 @@ class InitializeResult(BaseModel): class InitializeImpl( - AbstractCommandImpl[InitializeParams, SuccessData[InitializeResult, None]] + AbstractCommandImpl[InitializeParams, SuccessData[InitializeResult]] ): """Execution implementation of initializing an Absorbance Reader.""" @@ -41,9 +51,7 @@ def __init__( self._state_view = state_view self._equipment = equipment - async def execute( - self, params: InitializeParams - ) -> SuccessData[InitializeResult, None]: + async def execute(self, params: InitializeParams) -> SuccessData[InitializeResult]: """Initiate a single absorbance measurement.""" abs_reader_substate = self._state_view.modules.get_absorbance_reader_substate( module_id=params.moduleId @@ -54,11 +62,59 @@ async def execute( ) if abs_reader is not None: - await abs_reader.set_sample_wavelength(wavelength=params.sampleWavelength) + # Validate the parameters before initializing. + sample_wavelengths = set(params.sampleWavelengths) + sample_wavelengths_len = len(params.sampleWavelengths) + reference_wavelength = params.referenceWavelength + supported_wavelengths = set(abs_reader.supported_wavelengths) + unsupported_wavelengths = sample_wavelengths.difference( + supported_wavelengths + ) + sample_wl_str = ", ".join([str(w) + "nm" for w in sample_wavelengths]) + supported_wl_str = ", ".join([str(w) + "nm" for w in supported_wavelengths]) + unsupported_wl_str = ", ".join( + [str(w) + "nm" for w in unsupported_wavelengths] + ) + if unsupported_wavelengths: + raise InvalidWavelengthError( + f"Unsupported wavelengths: {unsupported_wl_str}. " + f" Use one of {supported_wl_str} instead." + ) + + if params.measureMode == "single": + if sample_wavelengths_len != 1: + raise ValueError( + f"Measure mode `single` requires one sample wavelength," + f" {sample_wl_str} provided instead." + ) + if ( + reference_wavelength is not None + and reference_wavelength not in supported_wavelengths + ): + raise InvalidWavelengthError( + f"Reference wavelength {reference_wavelength}nm is not supported." + f" Use one of {supported_wl_str} instead." + ) + + if params.measureMode == "multi": + if sample_wavelengths_len < 1 or sample_wavelengths_len > 6: + raise ValueError( + f"Measure mode `multi` requires 1-6 sample wavelengths," + f" {sample_wl_str} provided instead." + ) + if reference_wavelength is not None: + raise ValueError( + "Reference wavelength cannot be used with Measure mode `multi`." + ) + + await abs_reader.set_sample_wavelength( + ABSMeasurementMode(params.measureMode), + params.sampleWavelengths, + reference_wavelength=params.referenceWavelength, + ) return SuccessData( public=InitializeResult(), - private=None, ) diff --git a/api/src/opentrons/protocol_engine/commands/absorbance_reader/open_lid.py b/api/src/opentrons/protocol_engine/commands/absorbance_reader/open_lid.py index f12a612f649..1ad56413f9a 100644 --- a/api/src/opentrons/protocol_engine/commands/absorbance_reader/open_lid.py +++ b/api/src/opentrons/protocol_engine/commands/absorbance_reader/open_lid.py @@ -9,14 +9,15 @@ from ...errors.error_occurrence import ErrorOccurrence from ...errors import CannotPerformModuleAction -from .types import MoveLidResult -from opentrons.protocol_engine.resources import labware_validation from opentrons.protocol_engine.types import AddressableAreaLocation from opentrons.drivers.types import AbsorbanceReaderLidStatus +from ...state.update_types import StateUpdate + + if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import ( EquipmentHandler, LabwareMovementHandler, @@ -32,11 +33,11 @@ class OpenLidParams(BaseModel): moduleId: str = Field(..., description="Unique ID of the absorbance reader.") -class OpenLidResult(MoveLidResult): +class OpenLidResult(BaseModel): """Result data from opening the lid on an aborbance reading.""" -class OpenLidImpl(AbstractCommandImpl[OpenLidParams, SuccessData[OpenLidResult, None]]): +class OpenLidImpl(AbstractCommandImpl[OpenLidParams, SuccessData[OpenLidResult]]): """Execution implementation of opening the lid on an Absorbance Reader.""" def __init__( @@ -50,41 +51,37 @@ def __init__( self._equipment = equipment self._labware_movement = labware_movement - async def execute(self, params: OpenLidParams) -> SuccessData[OpenLidResult, None]: + async def execute(self, params: OpenLidParams) -> SuccessData[OpenLidResult]: """Move the absorbance reader lid from the module to the lid dock.""" + state_update = StateUpdate() mod_substate = self._state_view.modules.get_absorbance_reader_substate( module_id=params.moduleId ) - # lid should currently be on the module - assert mod_substate.lid_id is not None - loaded_lid = self._state_view.labware.get(mod_substate.lid_id) - assert labware_validation.is_absorbance_reader_lid(loaded_lid.loadName) hardware_lid_status = AbsorbanceReaderLidStatus.ON - # If the lid is closed, if the lid is open No-op out if not self._state_view.config.use_virtual_modules: abs_reader = self._equipment.get_module_hardware_api(mod_substate.module_id) if abs_reader is not None: - result = await abs_reader.get_current_lid_status() - hardware_lid_status = result + hardware_lid_status = await abs_reader.get_current_lid_status() else: raise CannotPerformModuleAction( "Could not reach the Hardware API for Opentrons Plate Reader Module." ) - # If the lid is already OFF, no-op the lid removal if hardware_lid_status is AbsorbanceReaderLidStatus.OFF: - assert isinstance(loaded_lid.location, AddressableAreaLocation) - new_location = loaded_lid.location - new_offset_id = self._equipment.find_applicable_labware_offset_id( - labware_definition_uri=loaded_lid.definitionUri, - labware_location=loaded_lid.location, + # The lid is already physically OFF, so we can no-op physically closing it + state_update.set_absorbance_reader_lid( + module_id=mod_substate.module_id, is_lid_on=False ) else: # Allow propagation of ModuleNotAttachedError. _ = self._equipment.get_module_hardware_api(mod_substate.module_id) + lid_definition = ( + self._state_view.labware.get_absorbance_reader_lid_definition() + ) + absorbance_model = self._state_view.modules.get_requested_model( params.moduleId ) @@ -104,34 +101,31 @@ async def execute(self, params: OpenLidParams) -> SuccessData[OpenLidResult, Non mod_substate.module_id ) - lid_gripper_offsets = self._state_view.labware.get_labware_gripper_offsets( - loaded_lid.id, None + # The lid's labware definition stores gripper offsets for itself in the + # space normally meant for offsets for labware stacked atop it. + lid_gripper_offsets = self._state_view.labware.get_child_gripper_offsets( + labware_definition=lid_definition, + slot_name=None, ) if lid_gripper_offsets is None: raise ValueError( "Gripper Offset values for Absorbance Reader Lid labware must not be None." ) - # Skips gripper moves when using virtual gripper await self._labware_movement.move_labware_with_gripper( - labware_id=loaded_lid.id, + labware_definition=lid_definition, current_location=current_location, new_location=new_location, user_offset_data=lid_gripper_offsets, post_drop_slide_offset=None, ) - new_offset_id = self._equipment.find_applicable_labware_offset_id( - labware_definition_uri=loaded_lid.definitionUri, - labware_location=new_location, + state_update.set_absorbance_reader_lid( + module_id=mod_substate.module_id, is_lid_on=False ) return SuccessData( - public=OpenLidResult( - lidId=loaded_lid.id, - newLocation=new_location, - offsetId=new_offset_id, - ), - private=None, + public=OpenLidResult(), + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/commands/absorbance_reader/read.py b/api/src/opentrons/protocol_engine/commands/absorbance_reader/read.py index a3f11e8d886..556db619cb5 100644 --- a/api/src/opentrons/protocol_engine/commands/absorbance_reader/read.py +++ b/api/src/opentrons/protocol_engine/commands/absorbance_reader/read.py @@ -1,16 +1,25 @@ """Command models to read absorbance.""" from __future__ import annotations -from typing import Optional, Dict, TYPE_CHECKING +from datetime import datetime +from typing import Optional, Dict, TYPE_CHECKING, List from typing_extensions import Literal, Type from pydantic import BaseModel, Field from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData -from ...errors import CannotPerformModuleAction +from ...errors import CannotPerformModuleAction, StorageLimitReachedError from ...errors.error_occurrence import ErrorOccurrence +from ...resources.file_provider import ( + PlateReaderData, + ReadData, + MAXIMUM_CSV_FILE_LIMIT, +) +from ...resources import FileProvider +from ...state import update_types + if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler @@ -18,20 +27,29 @@ class ReadAbsorbanceParams(BaseModel): - """Input parameters for a single absorbance reading.""" + """Input parameters for an absorbance reading.""" moduleId: str = Field(..., description="Unique ID of the Absorbance Reader.") - sampleWavelength: int = Field(..., description="Sample wavelength in nm.") + fileName: Optional[str] = Field( + None, + description="Optional file name to use when storing the results of a measurement.", + ) class ReadAbsorbanceResult(BaseModel): - """Result data from running an aborbance reading, returned as a dictionary map of values by well name (eg. ("A1": 0.0, ...)).""" + """Result data from running an aborbance reading, returned as a dictionary map of wavelengths containing a map of values by well name (eg. {450: {"A1": 0.0, ...}}).""" - data: Optional[Dict[str, float]] = Field(..., description="Absorbance data points.") + data: Optional[Dict[int, Dict[str, float]]] = Field( + ..., description="Absorbance data points per wavelength." + ) + fileIds: Optional[List[str]] = Field( + ..., + description="List of file IDs for files output as a result of a Read action.", + ) class ReadAbsorbanceImpl( - AbstractCommandImpl[ReadAbsorbanceParams, SuccessData[ReadAbsorbanceResult, None]] + AbstractCommandImpl[ReadAbsorbanceParams, SuccessData[ReadAbsorbanceResult]] ): """Execution implementation of an Absorbance Reader measurement.""" @@ -39,18 +57,21 @@ def __init__( self, state_view: StateView, equipment: EquipmentHandler, + file_provider: FileProvider, **unused_dependencies: object, ) -> None: self._state_view = state_view self._equipment = equipment + self._file_provider = file_provider - async def execute( + async def execute( # noqa: C901 self, params: ReadAbsorbanceParams - ) -> SuccessData[ReadAbsorbanceResult, None]: - """Initiate a single absorbance measurement.""" + ) -> SuccessData[ReadAbsorbanceResult]: + """Initiate an absorbance measurement.""" abs_reader_substate = self._state_view.modules.get_absorbance_reader_substate( module_id=params.moduleId ) + # Allow propagation of ModuleNotAttachedError. abs_reader = self._equipment.get_module_hardware_api( abs_reader_substate.module_id @@ -60,22 +81,108 @@ async def execute( raise CannotPerformModuleAction( "Cannot perform Read action on Absorbance Reader without calling `.initialize(...)` first." ) + if abs_reader_substate.is_lid_on is False: + raise CannotPerformModuleAction( + "Absorbance Plate Reader can't read a plate with the lid open. Call `close_lid()` first." + ) + + # TODO: we need to return a file ID and increase the file count even when a moduel is not attached + if ( + params.fileName is not None + and abs_reader_substate.configured_wavelengths is not None + ): + # Validate that the amount of files we are about to generate does not put us higher than the limit + if ( + self._state_view.files.get_filecount() + + len(abs_reader_substate.configured_wavelengths) + > MAXIMUM_CSV_FILE_LIMIT + ): + raise StorageLimitReachedError( + message=f"Attempt to write file {params.fileName} exceeds file creation limit of {MAXIMUM_CSV_FILE_LIMIT} files." + ) + asbsorbance_result: Dict[int, Dict[str, float]] = {} + transform_results = [] + # Handle the measurement and begin building data for return if abs_reader is not None: - result = await abs_reader.start_measure(wavelength=params.sampleWavelength) - converted_values = ( - self._state_view.modules.convert_absorbance_reader_data_points( - data=result + start_time = datetime.now() + results = await abs_reader.start_measure() + finish_time = datetime.now() + if abs_reader._measurement_config is not None: + sample_wavelengths = abs_reader._measurement_config.sample_wavelengths + for wavelength, result in zip(sample_wavelengths, results): + converted_values = ( + self._state_view.modules.convert_absorbance_reader_data_points( + data=result + ) + ) + asbsorbance_result[wavelength] = converted_values + transform_results.append( + ReadData.construct(wavelength=wavelength, data=converted_values) + ) + # Handle the virtual module case for data creation (all zeroes) + elif self._state_view.config.use_virtual_modules: + start_time = finish_time = datetime.now() + if abs_reader_substate.configured_wavelengths is not None: + for wavelength in abs_reader_substate.configured_wavelengths: + converted_values = ( + self._state_view.modules.convert_absorbance_reader_data_points( + data=[0] * 96 + ) + ) + asbsorbance_result[wavelength] = converted_values + transform_results.append( + ReadData.construct(wavelength=wavelength, data=converted_values) + ) + else: + raise CannotPerformModuleAction( + "Plate Reader data cannot be requested with a module that has not been initialized." ) + + # TODO (cb, 10-17-2024): FILE PROVIDER - Some day we may want to break the file provider behavior into a seperate API function. + # When this happens, we probably will to have the change the command results handler we utilize to track file IDs in engine. + # Today, the action handler for the FileStore looks for a ReadAbsorbanceResult command action, this will need to be delinked. + + # Begin interfacing with the file provider if the user provided a filename + file_ids: list[str] = [] + if params.fileName is not None: + # Create the Plate Reader Transform + plate_read_result = PlateReaderData.construct( + read_results=transform_results, + reference_wavelength=abs_reader_substate.reference_wavelength, + start_time=start_time, + finish_time=finish_time, + serial_number=abs_reader.serial_number + if (abs_reader is not None and abs_reader.serial_number is not None) + else "VIRTUAL_SERIAL", ) - return SuccessData( - public=ReadAbsorbanceResult(data=converted_values), - private=None, - ) + + if isinstance(plate_read_result, PlateReaderData): + # Write a CSV file for each of the measurements taken + for measurement in plate_read_result.read_results: + file_id = await self._file_provider.write_csv( + write_data=plate_read_result.build_generic_csv( + filename=params.fileName, + measurement=measurement, + ) + ) + file_ids.append(file_id) + + # Return success data to api + return SuccessData( + public=ReadAbsorbanceResult( + data=asbsorbance_result, fileIds=file_ids + ), + ) return SuccessData( - public=ReadAbsorbanceResult(data=None), - private=None, + public=ReadAbsorbanceResult( + data=asbsorbance_result, + fileIds=file_ids, + ), + state_update=update_types.StateUpdate( + files_added=update_types.FilesAddedUpdate(file_ids=file_ids) + ), ) diff --git a/api/src/opentrons/protocol_engine/commands/absorbance_reader/types.py b/api/src/opentrons/protocol_engine/commands/absorbance_reader/types.py deleted file mode 100644 index 5595502d6a1..00000000000 --- a/api/src/opentrons/protocol_engine/commands/absorbance_reader/types.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Move Lid Result model.""" - -from typing import Optional -from pydantic import BaseModel, Field -from opentrons.protocol_engine.types import LabwareLocation - - -class MoveLidResult(BaseModel): - """Input parameters to open the lid on an absorbance reading.""" - - lidId: str = Field(..., description="Unique ID of the absorbance reader lid.") - newLocation: LabwareLocation = Field(..., description="New location of the lid") - offsetId: Optional[str] = Field( - # Default `None` instead of `...` so this field shows up as non-required in - # OpenAPI. The server is allowed to omit it or make it null. - None, - description=( - "An ID referencing the labware offset that will apply to this labware" - " now that it's in the new location." - " This offset will be in effect until the labware is moved" - " with another `moveLabware` command." - " Null or undefined means no offset applies," - " so the default of (0, 0, 0) will be used." - ), - ) diff --git a/api/src/opentrons/protocol_engine/commands/air_gap_in_place.py b/api/src/opentrons/protocol_engine/commands/air_gap_in_place.py new file mode 100644 index 00000000000..461a446f3e4 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/air_gap_in_place.py @@ -0,0 +1,160 @@ +"""AirGap in place command request, result, and implementation models.""" + +from __future__ import annotations +from typing import TYPE_CHECKING, Optional, Type, Union +from typing_extensions import Literal + +from opentrons_shared_data.errors.exceptions import PipetteOverpressureError + +from opentrons.hardware_control import HardwareControlAPI + +from .pipetting_common import ( + PipetteIdMixin, + AspirateVolumeMixin, + FlowRateMixin, + BaseLiquidHandlingResult, + OverpressureError, +) +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, + DefinedErrorData, +) +from ..errors.error_occurrence import ErrorOccurrence +from ..errors.exceptions import PipetteNotReadyToAspirateError +from ..state.update_types import StateUpdate +from ..types import AspiratedFluid, FluidKind + +if TYPE_CHECKING: + from ..execution import PipettingHandler, GantryMover + from ..resources import ModelUtils + from ..state.state import StateView + from ..notes import CommandNoteAdder + +AirGapInPlaceCommandType = Literal["airGapInPlace"] + + +class AirGapInPlaceParams(PipetteIdMixin, AspirateVolumeMixin, FlowRateMixin): + """Payload required to air gap in place.""" + + pass + + +class AirGapInPlaceResult(BaseLiquidHandlingResult): + """Result data from the execution of a AirGapInPlace command.""" + + pass + + +_ExecuteReturn = Union[ + SuccessData[AirGapInPlaceResult], + DefinedErrorData[OverpressureError], +] + + +class AirGapInPlaceImplementation( + AbstractCommandImpl[AirGapInPlaceParams, _ExecuteReturn] +): + """AirGapInPlace command implementation.""" + + def __init__( + self, + pipetting: PipettingHandler, + hardware_api: HardwareControlAPI, + state_view: StateView, + command_note_adder: CommandNoteAdder, + model_utils: ModelUtils, + gantry_mover: GantryMover, + **kwargs: object, + ) -> None: + self._pipetting = pipetting + self._state_view = state_view + self._hardware_api = hardware_api + self._command_note_adder = command_note_adder + self._model_utils = model_utils + self._gantry_mover = gantry_mover + + async def execute(self, params: AirGapInPlaceParams) -> _ExecuteReturn: + """Air gap without moving the pipette. + + Raises: + TipNotAttachedError: if no tip is attached to the pipette. + PipetteNotReadyToAirGapError: pipette plunger is not ready. + """ + ready_to_aspirate = self._pipetting.get_is_ready_to_aspirate( + pipette_id=params.pipetteId, + ) + + if not ready_to_aspirate: + raise PipetteNotReadyToAspirateError( + "Pipette cannot air gap in place because of a previous blow out." + " The first aspirate following a blow-out must be from a specific well" + " so the plunger can be reset in a known safe position." + ) + + state_update = StateUpdate() + + try: + current_position = await self._gantry_mover.get_position(params.pipetteId) + volume = await self._pipetting.aspirate_in_place( + pipette_id=params.pipetteId, + volume=params.volume, + flow_rate=params.flowRate, + command_note_adder=self._command_note_adder, + ) + except PipetteOverpressureError as e: + return DefinedErrorData( + public=OverpressureError( + id=self._model_utils.generate_id(), + createdAt=self._model_utils.get_timestamp(), + wrappedErrors=[ + ErrorOccurrence.from_failed( + id=self._model_utils.generate_id(), + createdAt=self._model_utils.get_timestamp(), + error=e, + ) + ], + errorInfo=( + { + "retryLocation": ( + current_position.x, + current_position.y, + current_position.z, + ) + } + ), + ), + state_update=state_update, + ) + else: + state_update.set_fluid_aspirated( + pipette_id=params.pipetteId, + fluid=AspiratedFluid(kind=FluidKind.AIR, volume=volume), + ) + return SuccessData( + public=AirGapInPlaceResult(volume=volume), + state_update=state_update, + ) + + +class AirGapInPlace( + BaseCommand[AirGapInPlaceParams, AirGapInPlaceResult, OverpressureError] +): + """AirGapInPlace command model.""" + + commandType: AirGapInPlaceCommandType = "airGapInPlace" + params: AirGapInPlaceParams + result: Optional[AirGapInPlaceResult] + + _ImplementationCls: Type[AirGapInPlaceImplementation] = AirGapInPlaceImplementation + + +class AirGapInPlaceCreate(BaseCommandCreate[AirGapInPlaceParams]): + """AirGapInPlace command request model.""" + + commandType: AirGapInPlaceCommandType = "airGapInPlace" + params: AirGapInPlaceParams + + _CommandCls: Type[AirGapInPlace] = AirGapInPlace diff --git a/api/src/opentrons/protocol_engine/commands/aspirate.py b/api/src/opentrons/protocol_engine/commands/aspirate.py index 29daea563bb..fa84afbde8c 100644 --- a/api/src/opentrons/protocol_engine/commands/aspirate.py +++ b/api/src/opentrons/protocol_engine/commands/aspirate.py @@ -1,18 +1,23 @@ """Aspirate command request, result, and implementation models.""" + from __future__ import annotations from typing import TYPE_CHECKING, Optional, Type, Union -from opentrons_shared_data.errors.exceptions import PipetteOverpressureError from typing_extensions import Literal from .pipetting_common import ( OverpressureError, - OverpressureErrorInternalData, PipetteIdMixin, AspirateVolumeMixin, FlowRateMixin, - WellLocationMixin, BaseLiquidHandlingResult, + aspirate_in_place, + prepare_for_aspirate, +) +from .movement_common import ( + LiquidHandlingWellLocationMixin, DestinationPositionResult, + StallOrCollisionError, + move_to_well, ) from .command import ( AbstractCommandImpl, @@ -21,16 +26,20 @@ DefinedErrorData, SuccessData, ) -from ..errors.error_occurrence import ErrorOccurrence from opentrons.hardware_control import HardwareControlAPI -from ..types import WellLocation, WellOrigin, CurrentWell, DeckPoint +from ..state.update_types import StateUpdate, CLEAR +from ..types import ( + WellLocation, + WellOrigin, + CurrentWell, +) if TYPE_CHECKING: from ..execution import MovementHandler, PipettingHandler from ..resources import ModelUtils - from ..state import StateView + from ..state.state import StateView from ..notes import CommandNoteAdder @@ -38,7 +47,7 @@ class AspirateParams( - PipetteIdMixin, AspirateVolumeMixin, FlowRateMixin, WellLocationMixin + PipetteIdMixin, AspirateVolumeMixin, FlowRateMixin, LiquidHandlingWellLocationMixin ): """Parameters required to aspirate from a specific well.""" @@ -52,8 +61,8 @@ class AspirateResult(BaseLiquidHandlingResult, DestinationPositionResult): _ExecuteReturn = Union[ - SuccessData[AspirateResult, None], - DefinedErrorData[OverpressureError, OverpressureErrorInternalData], + SuccessData[AspirateResult], + DefinedErrorData[OverpressureError] | DefinedErrorData[StallOrCollisionError], ] @@ -86,6 +95,17 @@ async def execute(self, params: AspirateParams) -> _ExecuteReturn: pipette_id = params.pipetteId labware_id = params.labwareId well_name = params.wellName + well_location = params.wellLocation + + state_update = StateUpdate() + + final_location = self._state_view.geometry.get_well_position( + labware_id=labware_id, + well_name=well_name, + well_location=well_location, + operation_volume=-params.volume, + pipette_id=pipette_id, + ) ready_to_aspirate = self._pipetting.get_is_ready_to_aspirate( pipette_id=pipette_id @@ -94,14 +114,32 @@ async def execute(self, params: AspirateParams) -> _ExecuteReturn: current_well = None if not ready_to_aspirate: - await self._movement.move_to_well( + move_result = await move_to_well( + movement=self._movement, + model_utils=self._model_utils, pipette_id=pipette_id, labware_id=labware_id, well_name=well_name, well_location=WellLocation(origin=WellOrigin.TOP), ) + state_update.append(move_result.state_update) + if isinstance(move_result, DefinedErrorData): + return DefinedErrorData(move_result.public, state_update=state_update) - await self._pipetting.prepare_for_aspirate(pipette_id=pipette_id) + prepare_result = await prepare_for_aspirate( + pipette_id=pipette_id, + pipetting=self._pipetting, + model_utils=self._model_utils, + # Note that the retryLocation is the final location, inside the liquid, + # because that's where we'd want the client to try re-aspirating if this + # command fails and the run enters error recovery. + location_if_error={"retryLocation": final_location}, + ) + state_update.append(prepare_result.state_update) + if isinstance(prepare_result, DefinedErrorData): + return DefinedErrorData( + public=prepare_result.public, state_update=state_update + ) # set our current deck location to the well now that we've made # an intermediate move for the "prepare for aspirate" step @@ -111,54 +149,79 @@ async def execute(self, params: AspirateParams) -> _ExecuteReturn: well_name=well_name, ) - position = await self._movement.move_to_well( + move_result = await move_to_well( + movement=self._movement, + model_utils=self._model_utils, pipette_id=pipette_id, labware_id=labware_id, well_name=well_name, - well_location=params.wellLocation, + well_location=well_location, current_well=current_well, + operation_volume=-params.volume, ) - - try: - volume_aspirated = await self._pipetting.aspirate_in_place( - pipette_id=pipette_id, - volume=params.volume, - flow_rate=params.flowRate, - command_note_adder=self._command_note_adder, - ) - except PipetteOverpressureError as e: + state_update.append(move_result.state_update) + if isinstance(move_result, DefinedErrorData): return DefinedErrorData( - public=OverpressureError( - id=self._model_utils.generate_id(), - createdAt=self._model_utils.get_timestamp(), - wrappedErrors=[ - ErrorOccurrence.from_failed( - id=self._model_utils.generate_id(), - createdAt=self._model_utils.get_timestamp(), - error=e, - ) - ], - errorInfo={"retryLocation": (position.x, position.y, position.z)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint.construct( - x=position.x, y=position.y, z=position.z - ) - ), + public=move_result.public, state_update=state_update ) - else: - return SuccessData( - public=AspirateResult( - volume=volume_aspirated, - position=DeckPoint.construct( - x=position.x, y=position.y, z=position.z - ), + + aspirate_result = await aspirate_in_place( + pipette_id=pipette_id, + volume=params.volume, + flow_rate=params.flowRate, + location_if_error={ + "retryLocation": ( + move_result.public.position.x, + move_result.public.position.y, + move_result.public.position.z, + ) + }, + command_note_adder=self._command_note_adder, + pipetting=self._pipetting, + model_utils=self._model_utils, + ) + state_update.append(aspirate_result.state_update) + if isinstance(aspirate_result, DefinedErrorData): + state_update.set_liquid_operated( + labware_id=labware_id, + well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well( + labware_id, + well_name, + params.pipetteId, ), - private=None, + volume_added=CLEAR, + ) + return DefinedErrorData( + public=aspirate_result.public, state_update=state_update ) + state_update.set_liquid_operated( + labware_id=labware_id, + well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well( + labware_id, well_name, pipette_id + ), + volume_added=-aspirate_result.public.volume + * self._state_view.geometry.get_nozzles_per_well( + labware_id, + well_name, + params.pipetteId, + ), + ) -class Aspirate(BaseCommand[AspirateParams, AspirateResult, ErrorOccurrence]): + return SuccessData( + public=AspirateResult( + volume=aspirate_result.public.volume, + position=move_result.public.position, + ), + state_update=state_update, + ) + + +class Aspirate( + BaseCommand[ + AspirateParams, AspirateResult, OverpressureError | StallOrCollisionError + ] +): """Aspirate command model.""" commandType: AspirateCommandType = "aspirate" diff --git a/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py index 23b11598573..1f89c9c5d74 100644 --- a/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/aspirate_in_place.py @@ -4,8 +4,6 @@ from typing import TYPE_CHECKING, Optional, Type, Union from typing_extensions import Literal -from opentrons_shared_data.errors.exceptions import PipetteOverpressureError - from opentrons.hardware_control import HardwareControlAPI from .pipetting_common import ( @@ -14,7 +12,7 @@ FlowRateMixin, BaseLiquidHandlingResult, OverpressureError, - OverpressureErrorInternalData, + aspirate_in_place, ) from .command import ( AbstractCommandImpl, @@ -23,14 +21,14 @@ SuccessData, DefinedErrorData, ) -from ..errors.error_occurrence import ErrorOccurrence from ..errors.exceptions import PipetteNotReadyToAspirateError -from ..types import DeckPoint +from ..state.update_types import CLEAR +from ..types import CurrentWell if TYPE_CHECKING: from ..execution import PipettingHandler, GantryMover from ..resources import ModelUtils - from ..state import StateView + from ..state.state import StateView from ..notes import CommandNoteAdder AspirateInPlaceCommandType = Literal["aspirateInPlace"] @@ -49,8 +47,8 @@ class AspirateInPlaceResult(BaseLiquidHandlingResult): _ExecuteReturn = Union[ - SuccessData[AspirateInPlaceResult, None], - DefinedErrorData[OverpressureError, OverpressureErrorInternalData], + SuccessData[AspirateInPlaceResult], + DefinedErrorData[OverpressureError], ] @@ -86,59 +84,82 @@ async def execute(self, params: AspirateInPlaceParams) -> _ExecuteReturn: ready_to_aspirate = self._pipetting.get_is_ready_to_aspirate( pipette_id=params.pipetteId, ) - if not ready_to_aspirate: raise PipetteNotReadyToAspirateError( "Pipette cannot aspirate in place because of a previous blow out." " The first aspirate following a blow-out must be from a specific well" " so the plunger can be reset in a known safe position." ) - try: - volume = await self._pipetting.aspirate_in_place( - pipette_id=params.pipetteId, - volume=params.volume, - flow_rate=params.flowRate, - command_note_adder=self._command_note_adder, - ) - except PipetteOverpressureError as e: - current_position = await self._gantry_mover.get_position(params.pipetteId) - return DefinedErrorData( - public=OverpressureError( - id=self._model_utils.generate_id(), - createdAt=self._model_utils.get_timestamp(), - wrappedErrors=[ - ErrorOccurrence.from_failed( - id=self._model_utils.generate_id(), - createdAt=self._model_utils.get_timestamp(), - error=e, - ) - ], - errorInfo=( - { - "retryLocation": ( - current_position.x, - current_position.y, - current_position.z, - ) - } - ), - ), - private=OverpressureErrorInternalData( - position=DeckPoint( - x=current_position.x, - y=current_position.y, - z=current_position.z, + + current_position = await self._gantry_mover.get_position(params.pipetteId) + current_location = self._state_view.pipettes.get_current_location() + + result = await aspirate_in_place( + pipette_id=params.pipetteId, + volume=params.volume, + flow_rate=params.flowRate, + location_if_error={ + "retryLocation": ( + current_position.x, + current_position.y, + current_position.z, + ) + }, + command_note_adder=self._command_note_adder, + pipetting=self._pipetting, + model_utils=self._model_utils, + ) + if isinstance(result, DefinedErrorData): + if ( + isinstance(current_location, CurrentWell) + and current_location.pipette_id == params.pipetteId + ): + return DefinedErrorData( + public=result.public, + state_update=result.state_update.set_liquid_operated( + labware_id=current_location.labware_id, + well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well( + current_location.labware_id, + current_location.well_name, + params.pipetteId, + ), + volume_added=CLEAR, ), - ), - ) + state_update_if_false_positive=result.state_update_if_false_positive, + ) + else: + return result else: - return SuccessData( - public=AspirateInPlaceResult(volume=volume), private=None - ) + if ( + isinstance(current_location, CurrentWell) + and current_location.pipette_id == params.pipetteId + ): + return SuccessData( + public=AspirateInPlaceResult(volume=result.public.volume), + state_update=result.state_update.set_liquid_operated( + labware_id=current_location.labware_id, + well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well( + current_location.labware_id, + current_location.well_name, + params.pipetteId, + ), + volume_added=-result.public.volume + * self._state_view.geometry.get_nozzles_per_well( + current_location.labware_id, + current_location.well_name, + params.pipetteId, + ), + ), + ) + else: + return SuccessData( + public=AspirateInPlaceResult(volume=result.public.volume), + state_update=result.state_update, + ) class AspirateInPlace( - BaseCommand[AspirateInPlaceParams, AspirateInPlaceResult, ErrorOccurrence] + BaseCommand[AspirateInPlaceParams, AspirateInPlaceResult, OverpressureError] ): """AspirateInPlace command model.""" diff --git a/api/src/opentrons/protocol_engine/commands/blow_out.py b/api/src/opentrons/protocol_engine/commands/blow_out.py index f17b4b44ebc..b2e8765b4a1 100644 --- a/api/src/opentrons/protocol_engine/commands/blow_out.py +++ b/api/src/opentrons/protocol_engine/commands/blow_out.py @@ -1,24 +1,39 @@ """Blow-out command request, result, and implementation models.""" + from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Type +from typing import TYPE_CHECKING, Optional, Type, Union from typing_extensions import Literal -from ..types import DeckPoint + from .pipetting_common import ( + OverpressureError, PipetteIdMixin, FlowRateMixin, + blow_out_in_place, +) +from .movement_common import ( WellLocationMixin, DestinationPositionResult, + move_to_well, + StallOrCollisionError, +) +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + DefinedErrorData, + SuccessData, ) -from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData -from ..errors.error_occurrence import ErrorOccurrence +from ..state.update_types import StateUpdate from opentrons.hardware_control import HardwareControlAPI if TYPE_CHECKING: from ..execution import MovementHandler, PipettingHandler - from ..state import StateView + from ..state.state import StateView + from ..resources import ModelUtils + BlowOutCommandType = Literal["blowout"] @@ -35,9 +50,13 @@ class BlowOutResult(DestinationPositionResult): pass -class BlowOutImplementation( - AbstractCommandImpl[BlowOutParams, SuccessData[BlowOutResult, None]] -): +_ExecuteReturn = Union[ + SuccessData[BlowOutResult], + DefinedErrorData[OverpressureError] | DefinedErrorData[StallOrCollisionError], +] + + +class BlowOutImplementation(AbstractCommandImpl[BlowOutParams, _ExecuteReturn]): """BlowOut command implementation.""" def __init__( @@ -46,32 +65,67 @@ def __init__( pipetting: PipettingHandler, state_view: StateView, hardware_api: HardwareControlAPI, + model_utils: ModelUtils, **kwargs: object, ) -> None: self._movement = movement self._pipetting = pipetting self._state_view = state_view self._hardware_api = hardware_api + self._model_utils = model_utils - async def execute(self, params: BlowOutParams) -> SuccessData[BlowOutResult, None]: + async def execute(self, params: BlowOutParams) -> _ExecuteReturn: """Move to and blow-out the requested well.""" - x, y, z = await self._movement.move_to_well( + move_result = await move_to_well( + movement=self._movement, + model_utils=self._model_utils, pipette_id=params.pipetteId, labware_id=params.labwareId, well_name=params.wellName, well_location=params.wellLocation, ) - - await self._pipetting.blow_out_in_place( - pipette_id=params.pipetteId, flow_rate=params.flowRate - ) - - return SuccessData( - public=BlowOutResult(position=DeckPoint(x=x, y=y, z=z)), private=None + if isinstance(move_result, DefinedErrorData): + return move_result + blow_out_result = await blow_out_in_place( + pipette_id=params.pipetteId, + flow_rate=params.flowRate, + location_if_error={ + "retryLocation": ( + move_result.public.position.x, + move_result.public.position.y, + move_result.public.position.z, + ) + }, + pipetting=self._pipetting, + model_utils=self._model_utils, ) - - -class BlowOut(BaseCommand[BlowOutParams, BlowOutResult, ErrorOccurrence]): + if isinstance(blow_out_result, DefinedErrorData): + return DefinedErrorData( + public=blow_out_result.public, + state_update=StateUpdate.reduce( + move_result.state_update, blow_out_result.state_update + ), + state_update_if_false_positive=StateUpdate.reduce( + move_result.state_update, + blow_out_result.state_update_if_false_positive, + ), + ) + else: + return SuccessData( + public=BlowOutResult(position=move_result.public.position), + state_update=StateUpdate.reduce( + move_result.state_update, blow_out_result.state_update + ), + ) + + +class BlowOut( + BaseCommand[ + BlowOutParams, + BlowOutResult, + OverpressureError | StallOrCollisionError, + ] +): """Blow-out command model.""" commandType: BlowOutCommandType = "blowout" diff --git a/api/src/opentrons/protocol_engine/commands/blow_out_in_place.py b/api/src/opentrons/protocol_engine/commands/blow_out_in_place.py index d1527457c9c..f5f648bcec8 100644 --- a/api/src/opentrons/protocol_engine/commands/blow_out_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/blow_out_in_place.py @@ -1,23 +1,32 @@ """Blow-out in place command request, result, and implementation models.""" from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Type +from typing import TYPE_CHECKING, Optional, Type, Union from typing_extensions import Literal from pydantic import BaseModel from .pipetting_common import ( + OverpressureError, PipetteIdMixin, FlowRateMixin, + blow_out_in_place, +) +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + DefinedErrorData, + SuccessData, ) -from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ..errors.error_occurrence import ErrorOccurrence from opentrons.hardware_control import HardwareControlAPI if TYPE_CHECKING: - from ..execution import PipettingHandler - from ..state import StateView + from ..execution import PipettingHandler, GantryMover + from ..state.state import StateView + from ..resources import ModelUtils BlowOutInPlaceCommandType = Literal["blowOutInPlace"] @@ -35,8 +44,14 @@ class BlowOutInPlaceResult(BaseModel): pass +_ExecuteReturn = Union[ + SuccessData[BlowOutInPlaceResult], + DefinedErrorData[OverpressureError], +] + + class BlowOutInPlaceImplementation( - AbstractCommandImpl[BlowOutInPlaceParams, SuccessData[BlowOutInPlaceResult, None]] + AbstractCommandImpl[BlowOutInPlaceParams, _ExecuteReturn] ): """BlowOutInPlace command implementation.""" @@ -45,21 +60,37 @@ def __init__( pipetting: PipettingHandler, state_view: StateView, hardware_api: HardwareControlAPI, + model_utils: ModelUtils, + gantry_mover: GantryMover, **kwargs: object, ) -> None: self._pipetting = pipetting self._state_view = state_view self._hardware_api = hardware_api + self._model_utils = model_utils + self._gantry_mover = gantry_mover - async def execute( - self, params: BlowOutInPlaceParams - ) -> SuccessData[BlowOutInPlaceResult, None]: + async def execute(self, params: BlowOutInPlaceParams) -> _ExecuteReturn: """Blow-out without moving the pipette.""" - await self._pipetting.blow_out_in_place( - pipette_id=params.pipetteId, flow_rate=params.flowRate + current_position = await self._gantry_mover.get_position(params.pipetteId) + result = await blow_out_in_place( + pipette_id=params.pipetteId, + flow_rate=params.flowRate, + location_if_error={ + "retryLocation": ( + current_position.x, + current_position.y, + current_position.z, + ) + }, + pipetting=self._pipetting, + model_utils=self._model_utils, + ) + if isinstance(result, DefinedErrorData): + return result + return SuccessData( + public=BlowOutInPlaceResult(), state_update=result.state_update ) - - return SuccessData(public=BlowOutInPlaceResult(), private=None) class BlowOutInPlace( diff --git a/api/src/opentrons/protocol_engine/commands/calibration/calibrate_gripper.py b/api/src/opentrons/protocol_engine/commands/calibration/calibrate_gripper.py index b400e2dd33a..0333a171077 100644 --- a/api/src/opentrons/protocol_engine/commands/calibration/calibrate_gripper.py +++ b/api/src/opentrons/protocol_engine/commands/calibration/calibrate_gripper.py @@ -71,9 +71,7 @@ class CalibrateGripperResult(BaseModel): class CalibrateGripperImplementation( - AbstractCommandImpl[ - CalibrateGripperParams, SuccessData[CalibrateGripperResult, None] - ] + AbstractCommandImpl[CalibrateGripperParams, SuccessData[CalibrateGripperResult]] ): """The implementation of a `calibrateGripper` command.""" @@ -87,7 +85,7 @@ def __init__( async def execute( self, params: CalibrateGripperParams - ) -> SuccessData[CalibrateGripperResult, None]: + ) -> SuccessData[CalibrateGripperResult]: """Execute a `calibrateGripper` command. 1. Move from the current location to the calibration area on the deck. @@ -126,7 +124,6 @@ async def execute( ), savedCalibration=calibration_data, ), - private=None, ) @staticmethod diff --git a/api/src/opentrons/protocol_engine/commands/calibration/calibrate_module.py b/api/src/opentrons/protocol_engine/commands/calibration/calibrate_module.py index 08f5f45330f..f488e8eab97 100644 --- a/api/src/opentrons/protocol_engine/commands/calibration/calibrate_module.py +++ b/api/src/opentrons/protocol_engine/commands/calibration/calibrate_module.py @@ -12,7 +12,7 @@ # Work around type-only circular dependencies. if TYPE_CHECKING: - from ...state import StateView + from ...state.state import StateView from ...types import ModuleOffsetVector, DeckSlotLocation @@ -49,7 +49,7 @@ class CalibrateModuleResult(BaseModel): class CalibrateModuleImplementation( - AbstractCommandImpl[CalibrateModuleParams, SuccessData[CalibrateModuleResult, None]] + AbstractCommandImpl[CalibrateModuleParams, SuccessData[CalibrateModuleResult]] ): """CalibrateModule command implementation.""" @@ -64,7 +64,7 @@ def __init__( async def execute( self, params: CalibrateModuleParams - ) -> SuccessData[CalibrateModuleResult, None]: + ) -> SuccessData[CalibrateModuleResult]: """Execute calibrate-module command.""" ot3_api = ensure_ot3_hardware( self._hardware_api, @@ -91,7 +91,6 @@ async def execute( ), location=slot, ), - private=None, ) diff --git a/api/src/opentrons/protocol_engine/commands/calibration/calibrate_pipette.py b/api/src/opentrons/protocol_engine/commands/calibration/calibrate_pipette.py index 4369f88a9c5..fbe754f6389 100644 --- a/api/src/opentrons/protocol_engine/commands/calibration/calibrate_pipette.py +++ b/api/src/opentrons/protocol_engine/commands/calibration/calibrate_pipette.py @@ -34,9 +34,7 @@ class CalibratePipetteResult(BaseModel): class CalibratePipetteImplementation( - AbstractCommandImpl[ - CalibratePipetteParams, SuccessData[CalibratePipetteResult, None] - ] + AbstractCommandImpl[CalibratePipetteParams, SuccessData[CalibratePipetteResult]] ): """CalibratePipette command implementation.""" @@ -49,7 +47,7 @@ def __init__( async def execute( self, params: CalibratePipetteParams - ) -> SuccessData[CalibratePipetteResult, None]: + ) -> SuccessData[CalibratePipetteResult]: """Execute calibrate-pipette command.""" # TODO (tz, 20-9-22): Add a better solution to determine if a command can be executed on an OT-3/OT-2 ot3_api = ensure_ot3_hardware( @@ -72,7 +70,6 @@ async def execute( x=pipette_offset.x, y=pipette_offset.y, z=pipette_offset.z ) ), - private=None, ) diff --git a/api/src/opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py b/api/src/opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py index 81d9e30d1cc..afb178cae99 100644 --- a/api/src/opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py +++ b/api/src/opentrons/protocol_engine/commands/calibration/move_to_maintenance_position.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: from opentrons.hardware_control import HardwareControlAPI - from ...state import StateView + from ...state.state import StateView # These offsets supplied from HW _ATTACH_POINT = Point(x=0, y=110) @@ -57,7 +57,7 @@ class MoveToMaintenancePositionResult(BaseModel): class MoveToMaintenancePositionImplementation( AbstractCommandImpl[ MoveToMaintenancePositionParams, - SuccessData[MoveToMaintenancePositionResult, None], + SuccessData[MoveToMaintenancePositionResult], ] ): """Calibration set up position command implementation.""" @@ -73,7 +73,7 @@ def __init__( async def execute( self, params: MoveToMaintenancePositionParams - ) -> SuccessData[MoveToMaintenancePositionResult, None]: + ) -> SuccessData[MoveToMaintenancePositionResult]: """Move the requested mount to a maintenance deck slot.""" ot3_api = ensure_ot3_hardware( self._hardware_api, @@ -108,12 +108,19 @@ async def execute( await ot3_api.move_axes( { Axis.Z_L: max_motion_range + _LEFT_MOUNT_Z_MARGIN, + } + ) + await ot3_api.disengage_axes([Axis.Z_L]) + await ot3_api.move_axes( + { Axis.Z_R: max_motion_range + _RIGHT_MOUNT_Z_MARGIN, } ) - await ot3_api.disengage_axes([Axis.Z_L, Axis.Z_R]) + await ot3_api.disengage_axes([Axis.Z_R]) - return SuccessData(public=MoveToMaintenancePositionResult(), private=None) + return SuccessData( + public=MoveToMaintenancePositionResult(), + ) class MoveToMaintenancePosition( diff --git a/api/src/opentrons/protocol_engine/commands/command.py b/api/src/opentrons/protocol_engine/commands/command.py index 04846b54fc0..c009f314afb 100644 --- a/api/src/opentrons/protocol_engine/commands/command.py +++ b/api/src/opentrons/protocol_engine/commands/command.py @@ -1,12 +1,11 @@ """Base command data model and type definitions.""" - from __future__ import annotations +import dataclasses from abc import ABC, abstractmethod -from dataclasses import dataclass from datetime import datetime -from enum import Enum +import enum from typing import ( TYPE_CHECKING, Generic, @@ -21,6 +20,7 @@ from pydantic.generics import GenericModel from opentrons.hardware_control import HardwareControlAPI +from opentrons.protocol_engine.state.update_types import StateUpdate from ..resources import ModelUtils from ..errors import ErrorOccurrence @@ -29,7 +29,7 @@ # Work around type-only circular dependencies. if TYPE_CHECKING: from .. import execution - from ..state import StateView + from ..state.state import StateView _ParamsT = TypeVar("_ParamsT", bound=BaseModel) @@ -38,10 +38,9 @@ _ResultT_co = TypeVar("_ResultT_co", bound=BaseModel, covariant=True) _ErrorT = TypeVar("_ErrorT", bound=ErrorOccurrence) _ErrorT_co = TypeVar("_ErrorT_co", bound=ErrorOccurrence, covariant=True) -_PrivateResultT_co = TypeVar("_PrivateResultT_co", covariant=True) -class CommandStatus(str, Enum): +class CommandStatus(str, enum.Enum): """Command execution status.""" QUEUED = "queued" @@ -50,7 +49,7 @@ class CommandStatus(str, Enum): FAILED = "failed" -class CommandIntent(str, Enum): +class CommandIntent(str, enum.Enum): """Run intent for a given command. Props: @@ -106,19 +105,23 @@ class BaseCommandCreate( ) -@dataclass(frozen=True) -class SuccessData(Generic[_ResultT_co, _PrivateResultT_co]): +@dataclasses.dataclass(frozen=True) +class SuccessData(Generic[_ResultT_co]): """Data from the successful completion of a command.""" public: _ResultT_co """Public result data. Exposed over HTTP and stored in databases.""" - private: _PrivateResultT_co - """Additional result data, only given to `opentrons.protocol_engine` internals.""" + state_update: StateUpdate = dataclasses.field( + # todo(mm, 2024-08-22): Remove the default once all command implementations + # use this, to make it harder to forget in new command implementations. + default_factory=StateUpdate + ) + """How the engine state should be updated to reflect this command success.""" -@dataclass(frozen=True) -class DefinedErrorData(Generic[_ErrorT_co, _PrivateResultT_co]): +@dataclasses.dataclass(frozen=True) +class DefinedErrorData(Generic[_ErrorT_co]): """Data from a command that failed with a defined error. This should only be used for "defined" errors, not any error. @@ -128,8 +131,16 @@ class DefinedErrorData(Generic[_ErrorT_co, _PrivateResultT_co]): public: _ErrorT_co """Public error data. Exposed over HTTP and stored in databases.""" - private: _PrivateResultT_co - """Additional error data, only given to `opentrons.protocol_engine` internals.""" + state_update: StateUpdate = dataclasses.field( + # todo(mm, 2024-08-22): Remove the default once all command implementations + # use this, to make it harder to forget in new command implementations. + default_factory=StateUpdate + ) + """How the engine state should be updated to reflect this command failure.""" + + state_update_if_false_positive: StateUpdate = dataclasses.field( + default_factory=StateUpdate + ) class BaseCommand( @@ -173,7 +184,9 @@ class BaseCommand( ) error: Union[ _ErrorT, - # ErrorOccurrence here is for undefined errors not captured by _ErrorT. + # ErrorOccurrence here is a catch-all for undefined errors not captured by + # _ErrorT, or defined errors that don't parse into _ErrorT because, for example, + # they are from an older software version that was missing some fields. ErrorOccurrence, None, ] = Field( @@ -219,13 +232,11 @@ class BaseCommand( # Our _ImplementationCls must return public result data that can fit # in our `result` field: _ResultT, - # But we don't care (here) what kind of private result data it returns: - object, ], DefinedErrorData[ - # Likewise, for our `error` field: + # Our _ImplementationCls must return public error data that can fit + # in our `error` field: _ErrorT, - object, ], ], ] @@ -235,8 +246,8 @@ class BaseCommand( _ExecuteReturnT_co = TypeVar( "_ExecuteReturnT_co", bound=Union[ - SuccessData[BaseModel, object], - DefinedErrorData[ErrorOccurrence, object], + SuccessData[BaseModel], + DefinedErrorData[ErrorOccurrence], ], covariant=True, ) @@ -258,6 +269,7 @@ def __init__( state_view: StateView, hardware_api: HardwareControlAPI, equipment: execution.EquipmentHandler, + file_provider: execution.FileProvider, movement: execution.MovementHandler, gantry_mover: execution.GantryMover, labware_movement: execution.LabwareMovementHandler, diff --git a/api/src/opentrons/protocol_engine/commands/command_unions.py b/api/src/opentrons/protocol_engine/commands/command_unions.py index bd45a48e7d8..9c548fa8045 100644 --- a/api/src/opentrons/protocol_engine/commands/command_unions.py +++ b/api/src/opentrons/protocol_engine/commands/command_unions.py @@ -10,10 +10,10 @@ from .command import DefinedErrorData from .pipetting_common import ( OverpressureError, - OverpressureErrorInternalData, LiquidNotFoundError, - LiquidNotFoundErrorInternalData, + TipPhysicallyAttachedError, ) +from .movement_common import StallOrCollisionError from . import absorbance_reader from . import heater_shaker @@ -23,6 +23,7 @@ from . import calibration from . import unsafe +from . import robot from .set_rail_lights import ( SetRailLights, @@ -32,6 +33,14 @@ SetRailLightsResult, ) +from .air_gap_in_place import ( + AirGapInPlace, + AirGapInPlaceParams, + AirGapInPlaceCreate, + AirGapInPlaceResult, + AirGapInPlaceCommandType, +) + from .aspirate import ( Aspirate, AspirateParams, @@ -128,6 +137,14 @@ LoadLiquidCommandType, ) +from .load_liquid_class import ( + LoadLiquidClass, + LoadLiquidClassParams, + LoadLiquidClassCreate, + LoadLiquidClassResult, + LoadLiquidClassCommandType, +) + from .load_module import ( LoadModule, LoadModuleParams, @@ -142,10 +159,10 @@ LoadPipetteCreate, LoadPipetteResult, LoadPipetteCommandType, - LoadPipettePrivateResult, ) from .move_labware import ( + GripperMovementError, MoveLabware, MoveLabwareParams, MoveLabwareCreate, @@ -216,7 +233,6 @@ PickUpTipResult, PickUpTipCommandType, TipPhysicallyMissingError, - TipPhysicallyMissingErrorInternalData, ) from .touch_tip import ( @@ -273,7 +289,6 @@ ConfigureForVolumeCreate, ConfigureForVolumeResult, ConfigureForVolumeCommandType, - ConfigureForVolumePrivateResult, ) from .prepare_to_aspirate import ( @@ -290,7 +305,6 @@ ConfigureNozzleLayoutParams, ConfigureNozzleLayoutResult, ConfigureNozzleLayoutCommandType, - ConfigureNozzleLayoutPrivateResult, ) from .verify_tip_presence import ( @@ -324,6 +338,7 @@ Command = Annotated[ Union[ + AirGapInPlace, Aspirate, AspirateInPlace, Comment, @@ -341,6 +356,7 @@ LoadLabware, ReloadLabware, LoadLiquid, + LoadLiquidClass, LoadModule, LoadPipette, MoveLabware, @@ -382,6 +398,7 @@ thermocycler.OpenLid, thermocycler.CloseLid, thermocycler.RunProfile, + thermocycler.RunExtendedProfile, absorbance_reader.CloseLid, absorbance_reader.OpenLid, absorbance_reader.Initialize, @@ -393,11 +410,20 @@ unsafe.UnsafeBlowOutInPlace, unsafe.UnsafeDropTipInPlace, unsafe.UpdatePositionEstimators, + unsafe.UnsafeEngageAxes, + unsafe.UnsafeUngripLabware, + unsafe.UnsafePlaceLabware, + robot.MoveTo, + robot.MoveAxesRelative, + robot.MoveAxesTo, + robot.openGripperJaw, + robot.closeGripperJaw, ], Field(discriminator="commandType"), ] CommandParams = Union[ + AirGapInPlaceParams, AspirateParams, AspirateInPlaceParams, CommentParams, @@ -415,6 +441,7 @@ LoadLabwareParams, ReloadLabwareParams, LoadLiquidParams, + LoadLiquidClassParams, LoadModuleParams, LoadPipetteParams, MoveLabwareParams, @@ -456,6 +483,7 @@ thermocycler.OpenLidParams, thermocycler.CloseLidParams, thermocycler.RunProfileParams, + thermocycler.RunExtendedProfileParams, absorbance_reader.CloseLidParams, absorbance_reader.OpenLidParams, absorbance_reader.InitializeParams, @@ -467,9 +495,18 @@ unsafe.UnsafeBlowOutInPlaceParams, unsafe.UnsafeDropTipInPlaceParams, unsafe.UpdatePositionEstimatorsParams, + unsafe.UnsafeEngageAxesParams, + unsafe.UnsafeUngripLabwareParams, + unsafe.UnsafePlaceLabwareParams, + robot.MoveAxesRelativeParams, + robot.MoveAxesToParams, + robot.MoveToParams, + robot.openGripperJawParams, + robot.closeGripperJawParams, ] CommandType = Union[ + AirGapInPlaceCommandType, AspirateCommandType, AspirateInPlaceCommandType, CommentCommandType, @@ -487,6 +524,7 @@ LoadLabwareCommandType, ReloadLabwareCommandType, LoadLiquidCommandType, + LoadLiquidClassCommandType, LoadModuleCommandType, LoadPipetteCommandType, MoveLabwareCommandType, @@ -528,6 +566,7 @@ thermocycler.OpenLidCommandType, thermocycler.CloseLidCommandType, thermocycler.RunProfileCommandType, + thermocycler.RunExtendedProfileCommandType, absorbance_reader.CloseLidCommandType, absorbance_reader.OpenLidCommandType, absorbance_reader.InitializeCommandType, @@ -539,10 +578,19 @@ unsafe.UnsafeBlowOutInPlaceCommandType, unsafe.UnsafeDropTipInPlaceCommandType, unsafe.UpdatePositionEstimatorsCommandType, + unsafe.UnsafeEngageAxesCommandType, + unsafe.UnsafeUngripLabwareCommandType, + unsafe.UnsafePlaceLabwareCommandType, + robot.MoveAxesRelativeCommandType, + robot.MoveAxesToCommandType, + robot.MoveToCommandType, + robot.openGripperJawCommandType, + robot.closeGripperJawCommandType, ] CommandCreate = Annotated[ Union[ + AirGapInPlaceCreate, AspirateCreate, AspirateInPlaceCreate, CommentCreate, @@ -560,6 +608,7 @@ LoadLabwareCreate, ReloadLabwareCreate, LoadLiquidCreate, + LoadLiquidClassCreate, LoadModuleCreate, LoadPipetteCreate, MoveLabwareCreate, @@ -601,6 +650,7 @@ thermocycler.OpenLidCreate, thermocycler.CloseLidCreate, thermocycler.RunProfileCreate, + thermocycler.RunExtendedProfileCreate, absorbance_reader.CloseLidCreate, absorbance_reader.OpenLidCreate, absorbance_reader.InitializeCreate, @@ -612,11 +662,20 @@ unsafe.UnsafeBlowOutInPlaceCreate, unsafe.UnsafeDropTipInPlaceCreate, unsafe.UpdatePositionEstimatorsCreate, + unsafe.UnsafeEngageAxesCreate, + unsafe.UnsafeUngripLabwareCreate, + unsafe.UnsafePlaceLabwareCreate, + robot.MoveAxesRelativeCreate, + robot.MoveAxesToCreate, + robot.MoveToCreate, + robot.openGripperJawCreate, + robot.closeGripperJawCreate, ], Field(discriminator="commandType"), ] CommandResult = Union[ + AirGapInPlaceResult, AspirateResult, AspirateInPlaceResult, CommentResult, @@ -634,6 +693,7 @@ LoadLabwareResult, ReloadLabwareResult, LoadLiquidResult, + LoadLiquidClassResult, LoadModuleResult, LoadPipetteResult, MoveLabwareResult, @@ -675,6 +735,7 @@ thermocycler.OpenLidResult, thermocycler.CloseLidResult, thermocycler.RunProfileResult, + thermocycler.RunExtendedProfileResult, absorbance_reader.CloseLidResult, absorbance_reader.OpenLidResult, absorbance_reader.InitializeResult, @@ -686,24 +747,25 @@ unsafe.UnsafeBlowOutInPlaceResult, unsafe.UnsafeDropTipInPlaceResult, unsafe.UpdatePositionEstimatorsResult, + unsafe.UnsafeEngageAxesResult, + unsafe.UnsafeUngripLabwareResult, + unsafe.UnsafePlaceLabwareResult, + robot.MoveAxesRelativeResult, + robot.MoveAxesToResult, + robot.MoveToResult, + robot.openGripperJawResult, + robot.closeGripperJawResult, ] -# todo(mm, 2024-06-12): Ideally, command return types would have specific -# CommandPrivateResults paired with specific CommandResults. For example, -# a TouchTipResult can never be paired with a LoadPipettePrivateResult in practice, -# and ideally our types would reflect that. -CommandPrivateResult = Union[ - None, - LoadPipettePrivateResult, - ConfigureForVolumePrivateResult, - ConfigureNozzleLayoutPrivateResult, -] # All `DefinedErrorData`s that implementations will actually return in practice. CommandDefinedErrorData = Union[ - DefinedErrorData[TipPhysicallyMissingError, TipPhysicallyMissingErrorInternalData], - DefinedErrorData[OverpressureError, OverpressureErrorInternalData], - DefinedErrorData[LiquidNotFoundError, LiquidNotFoundErrorInternalData], + DefinedErrorData[TipPhysicallyMissingError], + DefinedErrorData[TipPhysicallyAttachedError], + DefinedErrorData[OverpressureError], + DefinedErrorData[LiquidNotFoundError], + DefinedErrorData[GripperMovementError], + DefinedErrorData[StallOrCollisionError], ] diff --git a/api/src/opentrons/protocol_engine/commands/comment.py b/api/src/opentrons/protocol_engine/commands/comment.py index d411b6b4047..5cd0b0c3113 100644 --- a/api/src/opentrons/protocol_engine/commands/comment.py +++ b/api/src/opentrons/protocol_engine/commands/comment.py @@ -24,16 +24,18 @@ class CommentResult(BaseModel): class CommentImplementation( - AbstractCommandImpl[CommentParams, SuccessData[CommentResult, None]] + AbstractCommandImpl[CommentParams, SuccessData[CommentResult]] ): """Comment command implementation.""" def __init__(self, **kwargs: object) -> None: pass - async def execute(self, params: CommentParams) -> SuccessData[CommentResult, None]: + async def execute(self, params: CommentParams) -> SuccessData[CommentResult]: """No operation taken other than capturing message in command.""" - return SuccessData(public=CommentResult(), private=None) + return SuccessData( + public=CommentResult(), + ) class Comment(BaseCommand[CommentParams, CommentResult, ErrorOccurrence]): diff --git a/api/src/opentrons/protocol_engine/commands/configure_for_volume.py b/api/src/opentrons/protocol_engine/commands/configure_for_volume.py index 8415c401fe7..1c8aa21f491 100644 --- a/api/src/opentrons/protocol_engine/commands/configure_for_volume.py +++ b/api/src/opentrons/protocol_engine/commands/configure_for_volume.py @@ -7,7 +7,7 @@ from .pipetting_common import PipetteIdMixin from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ..errors.error_occurrence import ErrorOccurrence -from .configuring_common import PipetteConfigUpdateResultMixin +from ..state.update_types import StateUpdate if TYPE_CHECKING: from ..execution import EquipmentHandler @@ -34,12 +34,6 @@ class ConfigureForVolumeParams(PipetteIdMixin): ) -class ConfigureForVolumePrivateResult(PipetteConfigUpdateResultMixin): - """Result sent to the store but not serialized.""" - - pass - - class ConfigureForVolumeResult(BaseModel): """Result data from execution of an ConfigureForVolume command.""" @@ -49,7 +43,7 @@ class ConfigureForVolumeResult(BaseModel): class ConfigureForVolumeImplementation( AbstractCommandImpl[ ConfigureForVolumeParams, - SuccessData[ConfigureForVolumeResult, ConfigureForVolumePrivateResult], + SuccessData[ConfigureForVolumeResult], ] ): """Configure for volume command implementation.""" @@ -59,7 +53,7 @@ def __init__(self, equipment: EquipmentHandler, **kwargs: object) -> None: async def execute( self, params: ConfigureForVolumeParams - ) -> SuccessData[ConfigureForVolumeResult, ConfigureForVolumePrivateResult]: + ) -> SuccessData[ConfigureForVolumeResult]: """Check that requested pipette can be configured for the given volume.""" pipette_result = await self._equipment.configure_for_volume( pipette_id=params.pipetteId, @@ -67,13 +61,16 @@ async def execute( tip_overlap_version=params.tipOverlapNotAfterVersion, ) + state_update = StateUpdate() + state_update.update_pipette_config( + pipette_id=pipette_result.pipette_id, + config=pipette_result.static_config, + serial_number=pipette_result.serial_number, + ) + return SuccessData( public=ConfigureForVolumeResult(), - private=ConfigureForVolumePrivateResult( - pipette_id=pipette_result.pipette_id, - serial_number=pipette_result.serial_number, - config=pipette_result.static_config, - ), + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/commands/configure_nozzle_layout.py b/api/src/opentrons/protocol_engine/commands/configure_nozzle_layout.py index 74681098ab9..f78839773ec 100644 --- a/api/src/opentrons/protocol_engine/commands/configure_nozzle_layout.py +++ b/api/src/opentrons/protocol_engine/commands/configure_nozzle_layout.py @@ -1,5 +1,6 @@ """Configure nozzle layout command request, result, and implementation models.""" from __future__ import annotations +from opentrons.protocol_engine.state.update_types import StateUpdate from pydantic import BaseModel from typing import TYPE_CHECKING, Optional, Type, Union from typing_extensions import Literal @@ -9,9 +10,6 @@ ) from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ..errors.error_occurrence import ErrorOccurrence -from .configuring_common import ( - PipetteNozzleLayoutResultMixin, -) from ..types import ( AllNozzleLayoutConfiguration, SingleNozzleLayoutConfiguration, @@ -39,12 +37,6 @@ class ConfigureNozzleLayoutParams(PipetteIdMixin): ] -class ConfigureNozzleLayoutPrivateResult(PipetteNozzleLayoutResultMixin): - """Result sent to the store but not serialized.""" - - pass - - class ConfigureNozzleLayoutResult(BaseModel): """Result data from execution of an configureNozzleLayout command.""" @@ -54,7 +46,7 @@ class ConfigureNozzleLayoutResult(BaseModel): class ConfigureNozzleLayoutImplementation( AbstractCommandImpl[ ConfigureNozzleLayoutParams, - SuccessData[ConfigureNozzleLayoutResult, ConfigureNozzleLayoutPrivateResult], + SuccessData[ConfigureNozzleLayoutResult], ] ): """Configure nozzle layout command implementation.""" @@ -67,7 +59,7 @@ def __init__( async def execute( self, params: ConfigureNozzleLayoutParams - ) -> SuccessData[ConfigureNozzleLayoutResult, ConfigureNozzleLayoutPrivateResult]: + ) -> SuccessData[ConfigureNozzleLayoutResult]: """Check that requested pipette can support the requested nozzle layout.""" primary_nozzle = params.configurationParams.dict().get("primaryNozzle") front_right_nozzle = params.configurationParams.dict().get("frontRightNozzle") @@ -85,12 +77,14 @@ async def execute( **nozzle_params, ) + update_state = StateUpdate() + update_state.update_pipette_nozzle( + pipette_id=params.pipetteId, nozzle_map=nozzle_map + ) + return SuccessData( public=ConfigureNozzleLayoutResult(), - private=ConfigureNozzleLayoutPrivateResult( - pipette_id=params.pipetteId, - nozzle_map=nozzle_map, - ), + state_update=update_state, ) diff --git a/api/src/opentrons/protocol_engine/commands/configuring_common.py b/api/src/opentrons/protocol_engine/commands/configuring_common.py deleted file mode 100644 index 6998bcbac7b..00000000000 --- a/api/src/opentrons/protocol_engine/commands/configuring_common.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Common configuration command base models.""" - -from dataclasses import dataclass -from opentrons.hardware_control.nozzle_manager import ( - NozzleMap, -) -from ..resources import pipette_data_provider - - -@dataclass -class PipetteConfigUpdateResultMixin: - """A mixin-suitable model for adding pipette config to private results.""" - - pipette_id: str - serial_number: str - config: pipette_data_provider.LoadedStaticPipetteData - - -@dataclass -class PipetteNozzleLayoutResultMixin: - """A nozzle layout result for updating the pipette state.""" - - pipette_id: str - - nozzle_map: NozzleMap - """A dataclass object holding information about the current nozzle configuration.""" diff --git a/api/src/opentrons/protocol_engine/commands/custom.py b/api/src/opentrons/protocol_engine/commands/custom.py index 2ceebda764c..1bdf07084be 100644 --- a/api/src/opentrons/protocol_engine/commands/custom.py +++ b/api/src/opentrons/protocol_engine/commands/custom.py @@ -40,16 +40,18 @@ class Config: class CustomImplementation( - AbstractCommandImpl[CustomParams, SuccessData[CustomResult, None]] + AbstractCommandImpl[CustomParams, SuccessData[CustomResult]] ): """Custom command implementation.""" # TODO(mm, 2022-11-09): figure out how a plugin can specify a custom command # implementation. For now, always no-op, so we can use custom commands as containers # for legacy RPC (pre-ProtocolEngine) payloads. - async def execute(self, params: CustomParams) -> SuccessData[CustomResult, None]: + async def execute(self, params: CustomParams) -> SuccessData[CustomResult]: """A custom command does nothing when executed directly.""" - return SuccessData(public=CustomResult.construct(), private=None) + return SuccessData( + public=CustomResult.construct(), + ) class Custom(BaseCommand[CustomParams, CustomResult, ErrorOccurrence]): diff --git a/api/src/opentrons/protocol_engine/commands/dispense.py b/api/src/opentrons/protocol_engine/commands/dispense.py index b346fb5845a..18f157934d4 100644 --- a/api/src/opentrons/protocol_engine/commands/dispense.py +++ b/api/src/opentrons/protocol_engine/commands/dispense.py @@ -1,22 +1,26 @@ """Dispense command request, result, and implementation models.""" + from __future__ import annotations from typing import TYPE_CHECKING, Optional, Type, Union from typing_extensions import Literal -from opentrons_shared_data.errors.exceptions import PipetteOverpressureError from pydantic import Field -from ..types import DeckPoint +from ..state.update_types import StateUpdate, CLEAR from .pipetting_common import ( PipetteIdMixin, DispenseVolumeMixin, FlowRateMixin, - WellLocationMixin, BaseLiquidHandlingResult, - DestinationPositionResult, OverpressureError, - OverpressureErrorInternalData, + dispense_in_place, +) +from .movement_common import ( + LiquidHandlingWellLocationMixin, + DestinationPositionResult, + StallOrCollisionError, + move_to_well, ) from .command import ( AbstractCommandImpl, @@ -25,18 +29,18 @@ DefinedErrorData, SuccessData, ) -from ..errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: from ..execution import MovementHandler, PipettingHandler from ..resources import ModelUtils + from ..state.state import StateView DispenseCommandType = Literal["dispense"] class DispenseParams( - PipetteIdMixin, DispenseVolumeMixin, FlowRateMixin, WellLocationMixin + PipetteIdMixin, DispenseVolumeMixin, FlowRateMixin, LiquidHandlingWellLocationMixin ): """Payload required to dispense to a specific well.""" @@ -53,8 +57,8 @@ class DispenseResult(BaseLiquidHandlingResult, DestinationPositionResult): _ExecuteReturn = Union[ - SuccessData[DispenseResult, None], - DefinedErrorData[OverpressureError, OverpressureErrorInternalData], + SuccessData[DispenseResult], + DefinedErrorData[OverpressureError] | DefinedErrorData[StallOrCollisionError], ] @@ -63,61 +67,107 @@ class DispenseImplementation(AbstractCommandImpl[DispenseParams, _ExecuteReturn] def __init__( self, + state_view: StateView, movement: MovementHandler, pipetting: PipettingHandler, model_utils: ModelUtils, **kwargs: object, ) -> None: + self._state_view = state_view self._movement = movement self._pipetting = pipetting self._model_utils = model_utils async def execute(self, params: DispenseParams) -> _ExecuteReturn: """Move to and dispense to the requested well.""" - position = await self._movement.move_to_well( + well_location = params.wellLocation + labware_id = params.labwareId + well_name = params.wellName + volume = params.volume + + # TODO(pbm, 10-15-24): call self._state_view.geometry.validate_dispense_volume_into_well() + + move_result = await move_to_well( + movement=self._movement, + model_utils=self._model_utils, pipette_id=params.pipetteId, - labware_id=params.labwareId, - well_name=params.wellName, - well_location=params.wellLocation, + labware_id=labware_id, + well_name=well_name, + well_location=well_location, ) - try: - volume = await self._pipetting.dispense_in_place( - pipette_id=params.pipetteId, - volume=params.volume, - flow_rate=params.flowRate, - push_out=params.pushOut, - ) - except PipetteOverpressureError as e: + if isinstance(move_result, DefinedErrorData): + return move_result + dispense_result = await dispense_in_place( + pipette_id=params.pipetteId, + volume=volume, + flow_rate=params.flowRate, + push_out=params.pushOut, + location_if_error={ + "retryLocation": ( + move_result.public.position.x, + move_result.public.position.y, + move_result.public.position.z, + ) + }, + pipetting=self._pipetting, + model_utils=self._model_utils, + ) + + if isinstance(dispense_result, DefinedErrorData): return DefinedErrorData( - public=OverpressureError( - id=self._model_utils.generate_id(), - createdAt=self._model_utils.get_timestamp(), - wrappedErrors=[ - ErrorOccurrence.from_failed( - id=self._model_utils.generate_id(), - createdAt=self._model_utils.get_timestamp(), - error=e, - ) - ], - errorInfo={"retryLocation": (position.x, position.y, position.z)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint.construct( - x=position.x, y=position.y, z=position.z + public=dispense_result.public, + state_update=( + StateUpdate.reduce( + move_result.state_update, dispense_result.state_update + ).set_liquid_operated( + labware_id=labware_id, + well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well( + labware_id, well_name, params.pipetteId + ), + volume_added=CLEAR, ) ), + state_update_if_false_positive=StateUpdate.reduce( + move_result.state_update, + dispense_result.state_update_if_false_positive, + ), ) else: + volume_added = ( + self._state_view.pipettes.get_liquid_dispensed_by_ejecting_volume( + pipette_id=params.pipetteId, volume=dispense_result.public.volume + ) + ) + if volume_added is not None: + volume_added *= self._state_view.geometry.get_nozzles_per_well( + labware_id, well_name, params.pipetteId + ) return SuccessData( public=DispenseResult( - volume=volume, - position=DeckPoint(x=position.x, y=position.y, z=position.z), + volume=dispense_result.public.volume, + position=move_result.public.position, + ), + state_update=( + StateUpdate.reduce( + move_result.state_update, dispense_result.state_update + ).set_liquid_operated( + labware_id=labware_id, + well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well( + labware_id, well_name, params.pipetteId + ), + volume_added=volume_added + if volume_added is not None + else CLEAR, + ) ), - private=None, ) -class Dispense(BaseCommand[DispenseParams, DispenseResult, ErrorOccurrence]): +class Dispense( + BaseCommand[ + DispenseParams, DispenseResult, OverpressureError | StallOrCollisionError + ] +): """Dispense command model.""" commandType: DispenseCommandType = "dispense" diff --git a/api/src/opentrons/protocol_engine/commands/dispense_in_place.py b/api/src/opentrons/protocol_engine/commands/dispense_in_place.py index d71f191d1df..fc1f9e19610 100644 --- a/api/src/opentrons/protocol_engine/commands/dispense_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/dispense_in_place.py @@ -1,18 +1,17 @@ """Dispense-in-place command request, result, and implementation models.""" + from __future__ import annotations from typing import TYPE_CHECKING, Optional, Type, Union from typing_extensions import Literal from pydantic import Field -from opentrons_shared_data.errors.exceptions import PipetteOverpressureError - from .pipetting_common import ( PipetteIdMixin, DispenseVolumeMixin, FlowRateMixin, BaseLiquidHandlingResult, OverpressureError, - OverpressureErrorInternalData, + dispense_in_place, ) from .command import ( AbstractCommandImpl, @@ -21,12 +20,13 @@ SuccessData, DefinedErrorData, ) -from ..errors.error_occurrence import ErrorOccurrence -from ..types import DeckPoint +from ..state.update_types import CLEAR +from ..types import CurrentWell if TYPE_CHECKING: from ..execution import PipettingHandler, GantryMover from ..resources import ModelUtils + from ..state.state import StateView DispenseInPlaceCommandType = Literal["dispenseInPlace"] @@ -48,8 +48,8 @@ class DispenseInPlaceResult(BaseLiquidHandlingResult): _ExecuteReturn = Union[ - SuccessData[DispenseInPlaceResult, None], - DefinedErrorData[OverpressureError, OverpressureErrorInternalData], + SuccessData[DispenseInPlaceResult], + DefinedErrorData[OverpressureError], ] @@ -61,62 +61,94 @@ class DispenseInPlaceImplementation( def __init__( self, pipetting: PipettingHandler, + state_view: StateView, gantry_mover: GantryMover, model_utils: ModelUtils, **kwargs: object, ) -> None: self._pipetting = pipetting + self._state_view = state_view self._gantry_mover = gantry_mover self._model_utils = model_utils async def execute(self, params: DispenseInPlaceParams) -> _ExecuteReturn: """Dispense without moving the pipette.""" - try: - volume = await self._pipetting.dispense_in_place( - pipette_id=params.pipetteId, - volume=params.volume, - flow_rate=params.flowRate, - push_out=params.pushOut, - ) - except PipetteOverpressureError as e: - current_position = await self._gantry_mover.get_position(params.pipetteId) - return DefinedErrorData( - public=OverpressureError( - id=self._model_utils.generate_id(), - createdAt=self._model_utils.get_timestamp(), - wrappedErrors=[ - ErrorOccurrence.from_failed( - id=self._model_utils.generate_id(), - createdAt=self._model_utils.get_timestamp(), - error=e, - ) - ], - errorInfo=( - { - "retryLocation": ( - current_position.x, - current_position.y, - current_position.z, - ) - } + current_location = self._state_view.pipettes.get_current_location() + current_position = await self._gantry_mover.get_position(params.pipetteId) + result = await dispense_in_place( + pipette_id=params.pipetteId, + volume=params.volume, + flow_rate=params.flowRate, + push_out=params.pushOut, + location_if_error={ + "retryLocation": ( + current_position.x, + current_position.y, + current_position.z, + ) + }, + pipetting=self._pipetting, + model_utils=self._model_utils, + ) + if isinstance(result, DefinedErrorData): + if ( + isinstance(current_location, CurrentWell) + and current_location.pipette_id == params.pipetteId + ): + return DefinedErrorData( + public=result.public, + state_update=result.state_update.set_liquid_operated( + labware_id=current_location.labware_id, + well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well( + current_location.labware_id, + current_location.well_name, + params.pipetteId, + ), + volume_added=CLEAR, ), - ), - private=OverpressureErrorInternalData( - position=DeckPoint( - x=current_position.x, - y=current_position.y, - z=current_position.z, - ), - ), - ) + state_update_if_false_positive=result.state_update_if_false_positive, + ) + else: + return result else: - return SuccessData( - public=DispenseInPlaceResult(volume=volume), private=None - ) + if ( + isinstance(current_location, CurrentWell) + and current_location.pipette_id == params.pipetteId + ): + volume_added = ( + self._state_view.pipettes.get_liquid_dispensed_by_ejecting_volume( + pipette_id=params.pipetteId, volume=result.public.volume + ) + ) + if volume_added is not None: + volume_added *= self._state_view.geometry.get_nozzles_per_well( + current_location.labware_id, + current_location.well_name, + params.pipetteId, + ) + return SuccessData( + public=DispenseInPlaceResult(volume=result.public.volume), + state_update=result.state_update.set_liquid_operated( + labware_id=current_location.labware_id, + well_names=self._state_view.geometry.get_wells_covered_by_pipette_with_active_well( + current_location.labware_id, + current_location.well_name, + params.pipetteId, + ), + volume_added=volume_added + if volume_added is not None + else CLEAR, + ), + ) + else: + return SuccessData( + public=DispenseInPlaceResult(volume=result.public.volume), + state_update=result.state_update, + ) class DispenseInPlace( - BaseCommand[DispenseInPlaceParams, DispenseInPlaceResult, ErrorOccurrence] + BaseCommand[DispenseInPlaceParams, DispenseInPlaceResult, OverpressureError] ): """DispenseInPlace command model.""" diff --git a/api/src/opentrons/protocol_engine/commands/drop_tip.py b/api/src/opentrons/protocol_engine/commands/drop_tip.py index ddb3c56cf7e..4faee3d5e2f 100644 --- a/api/src/opentrons/protocol_engine/commands/drop_tip.py +++ b/api/src/opentrons/protocol_engine/commands/drop_tip.py @@ -1,17 +1,36 @@ """Drop tip command request, result, and implementation models.""" + from __future__ import annotations from pydantic import Field from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal -from ..types import DropTipWellLocation, DeckPoint -from .pipetting_common import PipetteIdMixin, DestinationPositionResult -from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData +from opentrons.protocol_engine.errors.exceptions import TipAttachedError +from opentrons.protocol_engine.resources.model_utils import ModelUtils + +from ..state.update_types import StateUpdate +from ..types import DropTipWellLocation +from .pipetting_common import ( + PipetteIdMixin, + TipPhysicallyAttachedError, +) +from .movement_common import ( + DestinationPositionResult, + move_to_well, + StallOrCollisionError, +) +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + DefinedErrorData, + SuccessData, +) from ..errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from ..state import StateView + from ..state.state import StateView from ..execution import MovementHandler, TipHandler @@ -53,9 +72,14 @@ class DropTipResult(DestinationPositionResult): pass -class DropTipImplementation( - AbstractCommandImpl[DropTipParams, SuccessData[DropTipResult, None]] -): +_ExecuteReturn = ( + SuccessData[DropTipResult] + | DefinedErrorData[TipPhysicallyAttachedError] + | DefinedErrorData[StallOrCollisionError] +) + + +class DropTipImplementation(AbstractCommandImpl[DropTipParams, _ExecuteReturn]): """Drop tip command implementation.""" def __init__( @@ -63,13 +87,15 @@ def __init__( state_view: StateView, tip_handler: TipHandler, movement: MovementHandler, + model_utils: ModelUtils, **kwargs: object, ) -> None: self._state_view = state_view self._tip_handler = tip_handler self._movement_handler = movement + self._model_utils = model_utils - async def execute(self, params: DropTipParams) -> SuccessData[DropTipResult, None]: + async def execute(self, params: DropTipParams) -> _ExecuteReturn: """Move to and drop a tip using the requested pipette.""" pipette_id = params.pipetteId labware_id = params.labwareId @@ -95,24 +121,65 @@ async def execute(self, params: DropTipParams) -> SuccessData[DropTipResult, Non partially_configured=is_partially_configured, ) - position = await self._movement_handler.move_to_well( + move_result = await move_to_well( + movement=self._movement_handler, + model_utils=self._model_utils, pipette_id=pipette_id, labware_id=labware_id, well_name=well_name, well_location=tip_drop_location, ) + if isinstance(move_result, DefinedErrorData): + return move_result - await self._tip_handler.drop_tip(pipette_id=pipette_id, home_after=home_after) - - return SuccessData( - public=DropTipResult( - position=DeckPoint(x=position.x, y=position.y, z=position.z) - ), - private=None, - ) + try: + await self._tip_handler.drop_tip( + pipette_id=pipette_id, home_after=home_after + ) + except TipAttachedError as exception: + error = TipPhysicallyAttachedError( + id=self._model_utils.generate_id(), + createdAt=self._model_utils.get_timestamp(), + wrappedErrors=[ + ErrorOccurrence.from_failed( + id=self._model_utils.generate_id(), + createdAt=self._model_utils.get_timestamp(), + error=exception, + ) + ], + errorInfo={ + "retryLocation": ( + move_result.public.position.x, + move_result.public.position.y, + move_result.public.position.z, + ) + }, + ) + return DefinedErrorData( + public=error, + state_update=StateUpdate.reduce( + StateUpdate(), move_result.state_update + ).set_fluid_unknown(pipette_id=pipette_id), + state_update_if_false_positive=move_result.state_update.update_pipette_tip_state( + pipette_id=params.pipetteId, tip_geometry=None + ), + ) + else: + return SuccessData( + public=DropTipResult(position=move_result.public.position), + state_update=move_result.state_update.set_fluid_unknown( + pipette_id=pipette_id + ).update_pipette_tip_state( + pipette_id=params.pipetteId, tip_geometry=None + ), + ) -class DropTip(BaseCommand[DropTipParams, DropTipResult, ErrorOccurrence]): +class DropTip( + BaseCommand[ + DropTipParams, DropTipResult, TipPhysicallyAttachedError | StallOrCollisionError + ] +): """Drop tip command model.""" commandType: DropTipCommandType = "dropTip" diff --git a/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py b/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py index cf27732a6a5..8687382b53f 100644 --- a/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/drop_tip_in_place.py @@ -4,12 +4,21 @@ from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal -from .pipetting_common import PipetteIdMixin -from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + DefinedErrorData, + SuccessData, +) +from .pipetting_common import PipetteIdMixin, TipPhysicallyAttachedError +from ..errors.exceptions import TipAttachedError from ..errors.error_occurrence import ErrorOccurrence +from ..resources.model_utils import ModelUtils +from ..state import update_types if TYPE_CHECKING: - from ..execution import TipHandler + from ..execution import TipHandler, GantryMover DropTipInPlaceCommandType = Literal["dropTipInPlace"] @@ -34,31 +43,70 @@ class DropTipInPlaceResult(BaseModel): pass +_ExecuteReturn = ( + SuccessData[DropTipInPlaceResult] | DefinedErrorData[TipPhysicallyAttachedError] +) + + class DropTipInPlaceImplementation( - AbstractCommandImpl[DropTipInPlaceParams, SuccessData[DropTipInPlaceResult, None]] + AbstractCommandImpl[DropTipInPlaceParams, _ExecuteReturn] ): """Drop tip in place command implementation.""" def __init__( self, tip_handler: TipHandler, + model_utils: ModelUtils, + gantry_mover: GantryMover, **kwargs: object, ) -> None: self._tip_handler = tip_handler + self._model_utils = model_utils + self._gantry_mover = gantry_mover - async def execute( - self, params: DropTipInPlaceParams - ) -> SuccessData[DropTipInPlaceResult, None]: + async def execute(self, params: DropTipInPlaceParams) -> _ExecuteReturn: """Drop a tip using the requested pipette.""" - await self._tip_handler.drop_tip( - pipette_id=params.pipetteId, home_after=params.homeAfter - ) - - return SuccessData(public=DropTipInPlaceResult(), private=None) + state_update = update_types.StateUpdate() + + retry_location = await self._gantry_mover.get_position(params.pipetteId) + + try: + await self._tip_handler.drop_tip( + pipette_id=params.pipetteId, home_after=params.homeAfter + ) + except TipAttachedError as exception: + state_update_if_false_positive = update_types.StateUpdate() + state_update_if_false_positive.update_pipette_tip_state( + pipette_id=params.pipetteId, tip_geometry=None + ) + state_update.set_fluid_unknown(pipette_id=params.pipetteId) + error = TipPhysicallyAttachedError( + id=self._model_utils.generate_id(), + createdAt=self._model_utils.get_timestamp(), + wrappedErrors=[ + ErrorOccurrence.from_failed( + id=self._model_utils.generate_id(), + createdAt=self._model_utils.get_timestamp(), + error=exception, + ) + ], + errorInfo={"retryLocation": retry_location}, + ) + return DefinedErrorData( + public=error, + state_update=state_update, + state_update_if_false_positive=state_update_if_false_positive, + ) + else: + state_update.set_fluid_unknown(pipette_id=params.pipetteId) + state_update.update_pipette_tip_state( + pipette_id=params.pipetteId, tip_geometry=None + ) + return SuccessData(public=DropTipInPlaceResult(), state_update=state_update) class DropTipInPlace( - BaseCommand[DropTipInPlaceParams, DropTipInPlaceResult, ErrorOccurrence] + BaseCommand[DropTipInPlaceParams, DropTipInPlaceResult, TipPhysicallyAttachedError] ): """Drop tip in place command model.""" diff --git a/api/src/opentrons/protocol_engine/commands/get_tip_presence.py b/api/src/opentrons/protocol_engine/commands/get_tip_presence.py index 6c4eea93a84..6bbe5fa2fe3 100644 --- a/api/src/opentrons/protocol_engine/commands/get_tip_presence.py +++ b/api/src/opentrons/protocol_engine/commands/get_tip_presence.py @@ -38,7 +38,7 @@ class GetTipPresenceResult(BaseModel): class GetTipPresenceImplementation( - AbstractCommandImpl[GetTipPresenceParams, SuccessData[GetTipPresenceResult, None]] + AbstractCommandImpl[GetTipPresenceParams, SuccessData[GetTipPresenceResult]] ): """GetTipPresence command implementation.""" @@ -51,7 +51,7 @@ def __init__( async def execute( self, params: GetTipPresenceParams - ) -> SuccessData[GetTipPresenceResult, None]: + ) -> SuccessData[GetTipPresenceResult]: """Verify if tip presence is as expected for the requested pipette.""" pipette_id = params.pipetteId @@ -59,7 +59,9 @@ async def execute( pipette_id=pipette_id, ) - return SuccessData(public=GetTipPresenceResult(status=result), private=None) + return SuccessData( + public=GetTipPresenceResult(status=result), + ) class GetTipPresence( diff --git a/api/src/opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py b/api/src/opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py index b86bbc0e2ab..2151fb05877 100644 --- a/api/src/opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py +++ b/api/src/opentrons/protocol_engine/commands/heater_shaker/close_labware_latch.py @@ -9,7 +9,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler @@ -27,9 +27,7 @@ class CloseLabwareLatchResult(BaseModel): class CloseLabwareLatchImpl( - AbstractCommandImpl[ - CloseLabwareLatchParams, SuccessData[CloseLabwareLatchResult, None] - ] + AbstractCommandImpl[CloseLabwareLatchParams, SuccessData[CloseLabwareLatchResult]] ): """Execution implementation of a Heater-Shaker's close labware latch command.""" @@ -44,7 +42,7 @@ def __init__( async def execute( self, params: CloseLabwareLatchParams - ) -> SuccessData[CloseLabwareLatchResult, None]: + ) -> SuccessData[CloseLabwareLatchResult]: """Close a Heater-Shaker's labware latch.""" # Allow propagation of ModuleNotLoadedError and WrongModuleTypeError. hs_module_substate = self._state_view.modules.get_heater_shaker_module_substate( @@ -59,7 +57,9 @@ async def execute( if hs_hardware_module is not None: await hs_hardware_module.close_labware_latch() - return SuccessData(public=CloseLabwareLatchResult(), private=None) + return SuccessData( + public=CloseLabwareLatchResult(), + ) class CloseLabwareLatch( diff --git a/api/src/opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py b/api/src/opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py index 3392ddc5a9d..3932f1d6490 100644 --- a/api/src/opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py +++ b/api/src/opentrons/protocol_engine/commands/heater_shaker/deactivate_heater.py @@ -9,7 +9,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler @@ -27,9 +27,7 @@ class DeactivateHeaterResult(BaseModel): class DeactivateHeaterImpl( - AbstractCommandImpl[ - DeactivateHeaterParams, SuccessData[DeactivateHeaterResult, None] - ] + AbstractCommandImpl[DeactivateHeaterParams, SuccessData[DeactivateHeaterResult]] ): """Execution implementation of a Heater-Shaker's deactivate heater command.""" @@ -44,7 +42,7 @@ def __init__( async def execute( self, params: DeactivateHeaterParams - ) -> SuccessData[DeactivateHeaterResult, None]: + ) -> SuccessData[DeactivateHeaterResult]: """Unset a Heater-Shaker's target temperature.""" hs_module_substate = self._state_view.modules.get_heater_shaker_module_substate( module_id=params.moduleId @@ -58,7 +56,9 @@ async def execute( if hs_hardware_module is not None: await hs_hardware_module.deactivate_heater() - return SuccessData(public=DeactivateHeaterResult(), private=None) + return SuccessData( + public=DeactivateHeaterResult(), + ) class DeactivateHeater( diff --git a/api/src/opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py b/api/src/opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py index 8c77c064282..b259745ea3d 100644 --- a/api/src/opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py +++ b/api/src/opentrons/protocol_engine/commands/heater_shaker/deactivate_shaker.py @@ -9,7 +9,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler DeactivateShakerCommandType = Literal["heaterShaker/deactivateShaker"] @@ -26,9 +26,7 @@ class DeactivateShakerResult(BaseModel): class DeactivateShakerImpl( - AbstractCommandImpl[ - DeactivateShakerParams, SuccessData[DeactivateShakerResult, None] - ] + AbstractCommandImpl[DeactivateShakerParams, SuccessData[DeactivateShakerResult]] ): """Execution implementation of a Heater-Shaker's deactivate shaker command.""" @@ -43,7 +41,7 @@ def __init__( async def execute( self, params: DeactivateShakerParams - ) -> SuccessData[DeactivateShakerResult, None]: + ) -> SuccessData[DeactivateShakerResult]: """Deactivate shaker for a Heater-Shaker.""" # Allow propagation of ModuleNotLoadedError and WrongModuleTypeError. hs_module_substate = self._state_view.modules.get_heater_shaker_module_substate( @@ -60,7 +58,9 @@ async def execute( if hs_hardware_module is not None: await hs_hardware_module.deactivate_shaker() - return SuccessData(public=DeactivateShakerResult(), private=None) + return SuccessData( + public=DeactivateShakerResult(), + ) class DeactivateShaker( diff --git a/api/src/opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py b/api/src/opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py index a823f59149a..9c3a9d8ae7d 100644 --- a/api/src/opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py +++ b/api/src/opentrons/protocol_engine/commands/heater_shaker/open_labware_latch.py @@ -6,9 +6,10 @@ from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ...errors.error_occurrence import ErrorOccurrence +from ...state import update_types if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler, MovementHandler OpenLabwareLatchCommandType = Literal["heaterShaker/openLabwareLatch"] @@ -33,9 +34,7 @@ class OpenLabwareLatchResult(BaseModel): class OpenLabwareLatchImpl( - AbstractCommandImpl[ - OpenLabwareLatchParams, SuccessData[OpenLabwareLatchResult, None] - ] + AbstractCommandImpl[OpenLabwareLatchParams, SuccessData[OpenLabwareLatchResult]] ): """Execution implementation of a Heater-Shaker's open latch labware command.""" @@ -52,8 +51,10 @@ def __init__( async def execute( self, params: OpenLabwareLatchParams - ) -> SuccessData[OpenLabwareLatchResult, None]: + ) -> SuccessData[OpenLabwareLatchResult]: """Open a Heater-Shaker's labware latch.""" + state_update = update_types.StateUpdate() + # Allow propagation of ModuleNotLoadedError and WrongModuleTypeError. hs_module_substate = self._state_view.modules.get_heater_shaker_module_substate( module_id=params.moduleId @@ -72,6 +73,7 @@ async def execute( await self._movement.home( axes=self._state_view.motion.get_robot_mount_axes() ) + state_update.clear_all_pipette_locations() # Allow propagation of ModuleNotAttachedError. hs_hardware_module = self._equipment.get_module_hardware_api( @@ -83,7 +85,7 @@ async def execute( return SuccessData( public=OpenLabwareLatchResult(pipetteRetracted=pipette_should_retract), - private=None, + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py b/api/src/opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py index ca89166adae..8828195c658 100644 --- a/api/src/opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py +++ b/api/src/opentrons/protocol_engine/commands/heater_shaker/set_and_wait_for_shake_speed.py @@ -6,9 +6,10 @@ from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ...errors.error_occurrence import ErrorOccurrence +from ...state import update_types if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler, MovementHandler SetAndWaitForShakeSpeedCommandType = Literal["heaterShaker/setAndWaitForShakeSpeed"] @@ -35,7 +36,7 @@ class SetAndWaitForShakeSpeedResult(BaseModel): class SetAndWaitForShakeSpeedImpl( AbstractCommandImpl[ - SetAndWaitForShakeSpeedParams, SuccessData[SetAndWaitForShakeSpeedResult, None] + SetAndWaitForShakeSpeedParams, SuccessData[SetAndWaitForShakeSpeedResult] ] ): """Execution implementation of Heater-Shaker's set and wait shake speed command.""" @@ -54,8 +55,10 @@ def __init__( async def execute( self, params: SetAndWaitForShakeSpeedParams, - ) -> SuccessData[SetAndWaitForShakeSpeedResult, None]: + ) -> SuccessData[SetAndWaitForShakeSpeedResult]: """Set and wait for a Heater-Shaker's target shake speed.""" + state_update = update_types.StateUpdate() + # Allow propagation of ModuleNotLoadedError and WrongModuleTypeError. hs_module_substate = self._state_view.modules.get_heater_shaker_module_substate( module_id=params.moduleId @@ -77,6 +80,7 @@ async def execute( await self._movement.home( axes=self._state_view.motion.get_robot_mount_axes() ) + state_update.clear_all_pipette_locations() # Allow propagation of ModuleNotAttachedError. hs_hardware_module = self._equipment.get_module_hardware_api( @@ -90,7 +94,7 @@ async def execute( public=SetAndWaitForShakeSpeedResult( pipetteRetracted=pipette_should_retract ), - private=None, + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py b/api/src/opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py index 9e7cfba0f33..fa29390b910 100644 --- a/api/src/opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py +++ b/api/src/opentrons/protocol_engine/commands/heater_shaker/set_target_temperature.py @@ -9,7 +9,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler @@ -29,7 +29,7 @@ class SetTargetTemperatureResult(BaseModel): class SetTargetTemperatureImpl( AbstractCommandImpl[ - SetTargetTemperatureParams, SuccessData[SetTargetTemperatureResult, None] + SetTargetTemperatureParams, SuccessData[SetTargetTemperatureResult] ] ): """Execution implementation of a Heater-Shaker's set temperature command.""" @@ -46,7 +46,7 @@ def __init__( async def execute( self, params: SetTargetTemperatureParams, - ) -> SuccessData[SetTargetTemperatureResult, None]: + ) -> SuccessData[SetTargetTemperatureResult]: """Set a Heater-Shaker's target temperature.""" # Allow propagation of ModuleNotLoadedError and WrongModuleTypeError. hs_module_substate = self._state_view.modules.get_heater_shaker_module_substate( @@ -64,7 +64,9 @@ async def execute( if hs_hardware_module is not None: await hs_hardware_module.start_set_temperature(validated_temp) - return SuccessData(public=SetTargetTemperatureResult(), private=None) + return SuccessData( + public=SetTargetTemperatureResult(), + ) class SetTargetTemperature( diff --git a/api/src/opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py b/api/src/opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py index 981053cc459..bb440a2674c 100644 --- a/api/src/opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py +++ b/api/src/opentrons/protocol_engine/commands/heater_shaker/wait_for_temperature.py @@ -9,7 +9,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler @@ -36,9 +36,7 @@ class WaitForTemperatureResult(BaseModel): class WaitForTemperatureImpl( - AbstractCommandImpl[ - WaitForTemperatureParams, SuccessData[WaitForTemperatureResult, None] - ] + AbstractCommandImpl[WaitForTemperatureParams, SuccessData[WaitForTemperatureResult]] ): """Execution implementation of a Heater-Shaker's wait for temperature command.""" @@ -53,7 +51,7 @@ def __init__( async def execute( self, params: WaitForTemperatureParams - ) -> SuccessData[WaitForTemperatureResult, None]: + ) -> SuccessData[WaitForTemperatureResult]: """Wait for a Heater-Shaker's target temperature to be reached.""" hs_module_substate = self._state_view.modules.get_heater_shaker_module_substate( module_id=params.moduleId @@ -72,7 +70,9 @@ async def execute( if hs_hardware_module is not None: await hs_hardware_module.await_temperature(awaiting_temperature=target_temp) - return SuccessData(public=WaitForTemperatureResult(), private=None) + return SuccessData( + public=WaitForTemperatureResult(), + ) class WaitForTemperature( diff --git a/api/src/opentrons/protocol_engine/commands/home.py b/api/src/opentrons/protocol_engine/commands/home.py index 9455470602a..7b82f90e1fd 100644 --- a/api/src/opentrons/protocol_engine/commands/home.py +++ b/api/src/opentrons/protocol_engine/commands/home.py @@ -5,6 +5,7 @@ from typing_extensions import Literal from opentrons.types import MountType +from ..state import update_types from ..types import MotorAxis from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ..errors.error_occurrence import ErrorOccurrence @@ -41,16 +42,16 @@ class HomeResult(BaseModel): """Result data from the execution of a Home command.""" -class HomeImplementation( - AbstractCommandImpl[HomeParams, SuccessData[HomeResult, None]] -): +class HomeImplementation(AbstractCommandImpl[HomeParams, SuccessData[HomeResult]]): """Home command implementation.""" def __init__(self, movement: MovementHandler, **kwargs: object) -> None: self._movement = movement - async def execute(self, params: HomeParams) -> SuccessData[HomeResult, None]: + async def execute(self, params: HomeParams) -> SuccessData[HomeResult]: """Home some or all motors to establish positional accuracy.""" + state_update = update_types.StateUpdate() + if ( params.skipIfMountPositionOk is None or not await self._movement.check_for_valid_position( @@ -58,7 +59,12 @@ async def execute(self, params: HomeParams) -> SuccessData[HomeResult, None]: ) ): await self._movement.home(axes=params.axes) - return SuccessData(public=HomeResult(), private=None) + + # todo(mm, 2024-09-17): Clearing all pipette locations *unconditionally* is to + # preserve prior behavior, but we might only want to do this if we actually home. + state_update.clear_all_pipette_locations() + + return SuccessData(public=HomeResult(), state_update=state_update) class Home(BaseCommand[HomeParams, HomeResult, ErrorOccurrence]): diff --git a/api/src/opentrons/protocol_engine/commands/liquid_probe.py b/api/src/opentrons/protocol_engine/commands/liquid_probe.py index ecf932a3470..1bf58e8be26 100644 --- a/api/src/opentrons/protocol_engine/commands/liquid_probe.py +++ b/api/src/opentrons/protocol_engine/commands/liquid_probe.py @@ -1,23 +1,35 @@ """The liquidProbe and tryLiquidProbe commands.""" from __future__ import annotations -from typing import TYPE_CHECKING, Optional, Type, Union -from opentrons.protocol_engine.errors.exceptions import MustHomeError, TipNotEmptyError +from typing import TYPE_CHECKING, NamedTuple, Optional, Type, Union +from typing_extensions import Literal + +from pydantic import Field + +from opentrons.protocol_engine.state import update_types +from opentrons.protocol_engine.errors.exceptions import ( + MustHomeError, + PipetteNotReadyToAspirateError, + TipNotEmptyError, + IncompleteLabwareDefinitionError, + TipNotAttachedError, +) from opentrons.types import MountType from opentrons_shared_data.errors.exceptions import ( PipetteLiquidNotFoundError, + UnsupportedHardwareCommand, ) -from typing_extensions import Literal - -from pydantic import Field from ..types import DeckPoint from .pipetting_common import ( LiquidNotFoundError, - LiquidNotFoundErrorInternalData, PipetteIdMixin, +) +from .movement_common import ( WellLocationMixin, DestinationPositionResult, + StallOrCollisionError, + move_to_well, ) from .command import ( AbstractCommandImpl, @@ -32,6 +44,7 @@ if TYPE_CHECKING: from ..execution import MovementHandler, PipettingHandler from ..resources import ModelUtils + from ..state.state import StateView LiquidProbeCommandType = Literal["liquidProbe"] @@ -79,10 +92,99 @@ class TryLiquidProbeResult(DestinationPositionResult): _LiquidProbeExecuteReturn = Union[ - SuccessData[LiquidProbeResult, None], - DefinedErrorData[LiquidNotFoundError, LiquidNotFoundErrorInternalData], + SuccessData[LiquidProbeResult], + DefinedErrorData[LiquidNotFoundError] | DefinedErrorData[StallOrCollisionError], ] -_TryLiquidProbeExecuteReturn = SuccessData[TryLiquidProbeResult, None] +_TryLiquidProbeExecuteReturn = ( + SuccessData[TryLiquidProbeResult] | DefinedErrorData[StallOrCollisionError] +) + + +class _ExecuteCommonResult(NamedTuple): + # If the probe succeeded, the z_pos that it returned. + # Or, if the probe found no liquid, the error representing that, + # so calling code can propagate those details up. + z_pos_or_error: float | PipetteLiquidNotFoundError + + state_update: update_types.StateUpdate + deck_point: DeckPoint + + +async def _execute_common( + state_view: StateView, + movement: MovementHandler, + pipetting: PipettingHandler, + model_utils: ModelUtils, + params: _CommonParams, +) -> _ExecuteCommonResult | DefinedErrorData[StallOrCollisionError]: + pipette_id = params.pipetteId + labware_id = params.labwareId + well_name = params.wellName + if ( + "pressure" + not in state_view.pipettes.get_config(pipette_id).available_sensors.sensors + ): + raise UnsupportedHardwareCommand( + "Pressure sensor not available for this pipette" + ) + + if not state_view.pipettes.get_nozzle_configuration_supports_lld(pipette_id): + raise TipNotAttachedError( + "Either the front right or back left nozzle must have a tip attached to probe liquid height." + ) + + # May raise TipNotAttachedError. + aspirated_volume = state_view.pipettes.get_aspirated_volume(pipette_id) + + if aspirated_volume is None: + # Theoretically, we could avoid raising an error by automatically preparing + # to aspirate above the well like AspirateImplementation does. However, the + # only way for this to happen is if someone tries to do a liquid probe with + # a tip that's previously held liquid, which they should avoid anyway. + raise PipetteNotReadyToAspirateError( + "The pipette cannot probe liquid because of a previous blow out." + " The plunger must be reset while the tip is somewhere away from liquid." + ) + elif aspirated_volume != 0: + raise TipNotEmptyError( + message="The pipette cannot probe for liquid when the tip has liquid in it." + ) + + if await movement.check_for_valid_position(mount=MountType.LEFT) is False: + raise MustHomeError( + message="Current position of pipette is invalid. Please home." + ) + + # liquid_probe process start position + move_result = await move_to_well( + movement=movement, + model_utils=model_utils, + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=params.wellLocation, + ) + if isinstance(move_result, DefinedErrorData): + return move_result + try: + z_pos = await pipetting.liquid_probe_in_place( + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=params.wellLocation, + ) + except PipetteLiquidNotFoundError as exception: + return _ExecuteCommonResult( + z_pos_or_error=exception, + state_update=move_result.state_update, + deck_point=move_result.public.position, + ) + else: + return _ExecuteCommonResult( + z_pos_or_error=z_pos, + state_update=move_result.state_update, + deck_point=move_result.public.position, + ) class LiquidProbeImplementation( @@ -92,11 +194,13 @@ class LiquidProbeImplementation( def __init__( self, + state_view: StateView, movement: MovementHandler, pipetting: PipettingHandler, model_utils: ModelUtils, **kwargs: object, ) -> None: + self._state_view = state_view self._movement = movement self._pipetting = pipetting self._model_utils = model_utils @@ -112,43 +216,29 @@ async def execute(self, params: _CommonParams) -> _LiquidProbeExecuteReturn: the pipette. TipNotEmptyError: as an undefined error, if the tip starts with liquid in it. + PipetteNotReadyToAspirateError: as an undefined error, if the plunger is not + in a safe position to do the liquid probe. MustHomeError: as an undefined error, if the plunger is not in a valid position. """ - pipette_id = params.pipetteId - labware_id = params.labwareId - well_name = params.wellName - - # _validate_tip_attached in pipetting.py is a private method so we're using - # get_is_ready_to_aspirate as an indirect way to throw a TipNotAttachedError if appropriate - self._pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id) - - if self._pipetting.get_is_empty(pipette_id=pipette_id) is False: - raise TipNotEmptyError( - message="This operation requires a tip with no liquid in it." - ) - - if await self._movement.check_for_valid_position(mount=MountType.LEFT) is False: - raise MustHomeError( - message="Current position of pipette is invalid. Please home." - ) - - # liquid_probe process start position - position = await self._movement.move_to_well( - pipette_id=pipette_id, - labware_id=labware_id, - well_name=well_name, - well_location=params.wellLocation, + result = await _execute_common( + state_view=self._state_view, + movement=self._movement, + pipetting=self._pipetting, + model_utils=self._model_utils, + params=params, ) - - try: - z_pos = await self._pipetting.liquid_probe_in_place( - pipette_id=pipette_id, - labware_id=labware_id, - well_name=well_name, - well_location=params.wellLocation, + if isinstance(result, DefinedErrorData): + return result + z_pos_or_error, state_update, deck_point = result + if isinstance(z_pos_or_error, PipetteLiquidNotFoundError): + state_update.set_liquid_probed( + labware_id=params.labwareId, + well_name=params.wellName, + height=update_types.CLEAR, + volume=update_types.CLEAR, + last_probed=self._model_utils.get_timestamp(), ) - except PipetteLiquidNotFoundError as e: return DefinedErrorData( public=LiquidNotFoundError( id=self._model_utils.generate_id(), @@ -157,21 +247,35 @@ async def execute(self, params: _CommonParams) -> _LiquidProbeExecuteReturn: ErrorOccurrence.from_failed( id=self._model_utils.generate_id(), createdAt=self._model_utils.get_timestamp(), - error=e, + error=z_pos_or_error, ) ], ), - private=LiquidNotFoundErrorInternalData( - position=DeckPoint(x=position.x, y=position.y, z=position.z) - ), + state_update=state_update, ) else: + try: + well_volume: float | update_types.ClearType = ( + self._state_view.geometry.get_well_volume_at_height( + labware_id=params.labwareId, + well_name=params.wellName, + height=z_pos_or_error, + ) + ) + except IncompleteLabwareDefinitionError: + well_volume = update_types.CLEAR + state_update.set_liquid_probed( + labware_id=params.labwareId, + well_name=params.wellName, + height=z_pos_or_error, + volume=well_volume, + last_probed=self._model_utils.get_timestamp(), + ) return SuccessData( public=LiquidProbeResult( - z_position=z_pos, - position=DeckPoint(x=position.x, y=position.y, z=position.z), + z_position=z_pos_or_error, position=deck_point ), - private=None, + state_update=state_update, ) @@ -182,11 +286,13 @@ class TryLiquidProbeImplementation( def __init__( self, + state_view: StateView, movement: MovementHandler, pipetting: PipettingHandler, model_utils: ModelUtils, **kwargs: object, ) -> None: + self._state_view = state_view self._movement = movement self._pipetting = pipetting self._model_utils = model_utils @@ -198,43 +304,52 @@ async def execute(self, params: _CommonParams) -> _TryLiquidProbeExecuteReturn: found, `tryLiquidProbe` returns a success result with `z_position=null` instead of a defined error. """ - # We defer to the `liquidProbe` implementation. If it returns a defined - # `liquidNotFound` error, we remap that to a success result. - # Otherwise, we return the result or propagate the exception unchanged. - - original_impl = LiquidProbeImplementation( + result = await _execute_common( + state_view=self._state_view, movement=self._movement, pipetting=self._pipetting, model_utils=self._model_utils, + params=params, ) - original_result = await original_impl.execute(params) - - match original_result: - case DefinedErrorData( - public=LiquidNotFoundError(), - private=LiquidNotFoundErrorInternalData() as original_private, - ): - return SuccessData( - public=TryLiquidProbeResult( - z_position=None, - position=original_private.position, - ), - private=None, - ) - case SuccessData( - public=LiquidProbeResult() as original_public, private=None - ): - return SuccessData( - public=TryLiquidProbeResult( - position=original_public.position, - z_position=original_public.z_position, - ), - private=None, + if isinstance(result, DefinedErrorData): + return result + z_pos_or_error, state_update, deck_point = result + + if isinstance(z_pos_or_error, PipetteLiquidNotFoundError): + z_pos = None + well_volume: float | update_types.ClearType = update_types.CLEAR + else: + z_pos = z_pos_or_error + try: + well_volume = self._state_view.geometry.get_well_volume_at_height( + labware_id=params.labwareId, well_name=params.wellName, height=z_pos ) + except IncompleteLabwareDefinitionError: + well_volume = update_types.CLEAR + + state_update.set_liquid_probed( + labware_id=params.labwareId, + well_name=params.wellName, + height=z_pos if z_pos is not None else update_types.CLEAR, + volume=well_volume, + last_probed=self._model_utils.get_timestamp(), + ) + + return SuccessData( + public=TryLiquidProbeResult( + z_position=z_pos, + position=deck_point, + ), + state_update=state_update, + ) class LiquidProbe( - BaseCommand[LiquidProbeParams, LiquidProbeResult, LiquidNotFoundError] + BaseCommand[ + LiquidProbeParams, + LiquidProbeResult, + LiquidNotFoundError | StallOrCollisionError, + ] ): """The model for a full `liquidProbe` command.""" @@ -246,7 +361,7 @@ class LiquidProbe( class TryLiquidProbe( - BaseCommand[TryLiquidProbeParams, TryLiquidProbeResult, ErrorOccurrence] + BaseCommand[TryLiquidProbeParams, TryLiquidProbeResult, StallOrCollisionError] ): """The model for a full `tryLiquidProbe` command.""" diff --git a/api/src/opentrons/protocol_engine/commands/load_labware.py b/api/src/opentrons/protocol_engine/commands/load_labware.py index 6a4b53f4180..fb97f5d2c87 100644 --- a/api/src/opentrons/protocol_engine/commands/load_labware.py +++ b/api/src/opentrons/protocol_engine/commands/load_labware.py @@ -10,6 +10,8 @@ from ..resources import labware_validation, fixture_validation from ..types import ( LabwareLocation, + ModuleLocation, + ModuleModel, OnLabwareLocation, DeckSlotLocation, AddressableAreaLocation, @@ -17,9 +19,10 @@ from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ..errors.error_occurrence import ErrorOccurrence +from ..state.update_types import StateUpdate if TYPE_CHECKING: - from ..state import StateView + from ..state.state import StateView from ..execution import EquipmentHandler @@ -87,7 +90,7 @@ class LoadLabwareResult(BaseModel): class LoadLabwareImplementation( - AbstractCommandImpl[LoadLabwareParams, SuccessData[LoadLabwareResult, None]] + AbstractCommandImpl[LoadLabwareParams, SuccessData[LoadLabwareResult]] ): """Load labware command implementation.""" @@ -99,7 +102,7 @@ def __init__( async def execute( self, params: LoadLabwareParams - ) -> SuccessData[LoadLabwareResult, None]: + ) -> SuccessData[LoadLabwareResult]: """Load definition and calibration data necessary for a labware.""" # TODO (tz, 8-15-2023): extend column validation to column 1 when working # on https://opentrons.atlassian.net/browse/RSS-258 and completing @@ -141,6 +144,16 @@ async def execute( labware_id=params.labwareId, ) + state_update = StateUpdate() + + state_update.set_loaded_labware( + labware_id=loaded_labware.labware_id, + offset_id=loaded_labware.offsetId, + definition=loaded_labware.definition, + location=verified_location, + display_name=params.displayName, + ) + # TODO(jbl 2023-06-23) these validation checks happen after the labware is loaded, because they rely on # on the definition. In practice this will not cause any issues since they will raise protocol ending # exception, but for correctness should be refactored to do this check beforehand. @@ -149,6 +162,13 @@ async def execute( top_labware_definition=loaded_labware.definition, bottom_labware_id=verified_location.labwareId, ) + # Validate labware for the absorbance reader + elif isinstance(params.location, ModuleLocation): + module = self._state_view.modules.get(params.location.moduleId) + if module is not None and module.model == ModuleModel.ABSORBANCE_READER_V1: + self._state_view.labware.raise_if_labware_incompatible_with_plate_reader( + loaded_labware.definition + ) return SuccessData( public=LoadLabwareResult( @@ -156,7 +176,7 @@ async def execute( definition=loaded_labware.definition, offsetId=loaded_labware.offsetId, ), - private=None, + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/commands/load_liquid.py b/api/src/opentrons/protocol_engine/commands/load_liquid.py index 02585640b0e..f6aa037fa01 100644 --- a/api/src/opentrons/protocol_engine/commands/load_liquid.py +++ b/api/src/opentrons/protocol_engine/commands/load_liquid.py @@ -4,11 +4,16 @@ from typing import Optional, Type, Dict, TYPE_CHECKING from typing_extensions import Literal +from opentrons.protocol_engine.state.update_types import StateUpdate +from opentrons.protocol_engine.types import LiquidId +from opentrons.protocol_engine.errors import InvalidLiquidError + from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ..errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from ..state import StateView + from ..state.state import StateView + from ..resources import ModelUtils LoadLiquidCommandType = Literal["loadLiquid"] @@ -16,9 +21,9 @@ class LoadLiquidParams(BaseModel): """Payload required to load a liquid into a well.""" - liquidId: str = Field( + liquidId: LiquidId = Field( ..., - description="Unique identifier of the liquid to load.", + description="Unique identifier of the liquid to load. If this is the sentinel value EMPTY, all values of volumeByWell must be 0.", ) labwareId: str = Field( ..., @@ -26,7 +31,7 @@ class LoadLiquidParams(BaseModel): ) volumeByWell: Dict[str, float] = Field( ..., - description="Volume of liquid, in µL, loaded into each well by name, in this labware.", + description="Volume of liquid, in µL, loaded into each well by name, in this labware. If the liquid id is the sentinel value EMPTY, all volumes must be 0.", ) @@ -37,24 +42,38 @@ class LoadLiquidResult(BaseModel): class LoadLiquidImplementation( - AbstractCommandImpl[LoadLiquidParams, SuccessData[LoadLiquidResult, None]] + AbstractCommandImpl[LoadLiquidParams, SuccessData[LoadLiquidResult]] ): """Load liquid command implementation.""" - def __init__(self, state_view: StateView, **kwargs: object) -> None: + def __init__( + self, state_view: StateView, model_utils: ModelUtils, **kwargs: object + ) -> None: self._state_view = state_view + self._model_utils = model_utils - async def execute( - self, params: LoadLiquidParams - ) -> SuccessData[LoadLiquidResult, None]: + async def execute(self, params: LoadLiquidParams) -> SuccessData[LoadLiquidResult]: """Load data necessary for a liquid.""" self._state_view.liquid.validate_liquid_id(params.liquidId) self._state_view.labware.validate_liquid_allowed_in_labware( labware_id=params.labwareId, wells=params.volumeByWell ) + if params.liquidId == "EMPTY": + for well_name, volume in params.volumeByWell.items(): + if volume != 0.0: + raise InvalidLiquidError( + 'loadLiquid commands that specify the special liquid "EMPTY" must set volume to be 0.0, but the volume for {well_name} is {volume}' + ) + + state_update = StateUpdate() + state_update.set_liquid_loaded( + labware_id=params.labwareId, + volumes=params.volumeByWell, + last_loaded=self._model_utils.get_timestamp(), + ) - return SuccessData(public=LoadLiquidResult(), private=None) + return SuccessData(public=LoadLiquidResult(), state_update=state_update) class LoadLiquid(BaseCommand[LoadLiquidParams, LoadLiquidResult, ErrorOccurrence]): diff --git a/api/src/opentrons/protocol_engine/commands/load_liquid_class.py b/api/src/opentrons/protocol_engine/commands/load_liquid_class.py new file mode 100644 index 00000000000..bd267abe567 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/load_liquid_class.py @@ -0,0 +1,137 @@ +"""LoadLiquidClass stores the liquid class settings used for a transfer into the Protocol Engine.""" +from __future__ import annotations + +from typing import Optional, Type, TYPE_CHECKING +from typing_extensions import Literal +from pydantic import BaseModel, Field + +from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData +from ..errors import LiquidClassDoesNotExistError +from ..errors.error_occurrence import ErrorOccurrence +from ..errors.exceptions import LiquidClassRedefinitionError +from ..state.update_types import LiquidClassLoadedUpdate, StateUpdate +from ..types import LiquidClassRecord + +if TYPE_CHECKING: + from ..state.state import StateView + from ..resources import ModelUtils + +LoadLiquidClassCommandType = Literal["loadLiquidClass"] + + +class LoadLiquidClassParams(BaseModel): + """The liquid class transfer properties to store.""" + + liquidClassId: Optional[str] = Field( + None, + description="Unique identifier for the liquid class to store. " + "If you do not supply a liquidClassId, we will generate one.", + ) + liquidClassRecord: LiquidClassRecord = Field( + ..., + description="The liquid class to store.", + ) + + +class LoadLiquidClassResult(BaseModel): + """Result from execution of LoadLiquidClass command.""" + + liquidClassId: str = Field( + ..., + description="The ID for the liquid class that was loaded, either the one you " + "supplied or the one we generated.", + ) + + +class LoadLiquidClassImplementation( + AbstractCommandImpl[LoadLiquidClassParams, SuccessData[LoadLiquidClassResult]] +): + """Load Liquid Class command implementation.""" + + def __init__( + self, state_view: StateView, model_utils: ModelUtils, **kwargs: object + ) -> None: + self._state_view = state_view + self._model_utils = model_utils + + async def execute( + self, params: LoadLiquidClassParams + ) -> SuccessData[LoadLiquidClassResult]: + """Store the liquid class in the Protocol Engine.""" + liquid_class_id: Optional[str] + already_loaded = False + + if params.liquidClassId: + liquid_class_id = params.liquidClassId + if self._liquid_class_id_already_loaded( + liquid_class_id, params.liquidClassRecord + ): + already_loaded = True + else: + liquid_class_id = ( + self._state_view.liquid_classes.get_id_for_liquid_class_record( + params.liquidClassRecord + ) # if liquidClassRecord was already loaded, reuse the existing ID + ) + if liquid_class_id: + already_loaded = True + else: + liquid_class_id = self._model_utils.generate_id() + + if already_loaded: + state_update = StateUpdate() # liquid class already loaded, do nothing + else: + state_update = StateUpdate( + liquid_class_loaded=LiquidClassLoadedUpdate( + liquid_class_id=liquid_class_id, + liquid_class_record=params.liquidClassRecord, + ) + ) + + return SuccessData( + public=LoadLiquidClassResult(liquidClassId=liquid_class_id), + state_update=state_update, + ) + + def _liquid_class_id_already_loaded( + self, liquid_class_id: str, liquid_class_record: LiquidClassRecord + ) -> bool: + """Check if the liquid_class_id has already been loaded. + + If it has, make sure that liquid_class_record matches the previously loaded definition. + """ + try: + existing_liquid_class_record = self._state_view.liquid_classes.get( + liquid_class_id + ) + except LiquidClassDoesNotExistError: + return False + + if liquid_class_record != existing_liquid_class_record: + raise LiquidClassRedefinitionError( + f"Liquid class {liquid_class_id} conflicts with previously loaded definition." + ) + return True + + +class LoadLiquidClass( + BaseCommand[LoadLiquidClassParams, LoadLiquidClassResult, ErrorOccurrence] +): + """Load Liquid Class command resource model.""" + + commandType: LoadLiquidClassCommandType = "loadLiquidClass" + params: LoadLiquidClassParams + result: Optional[LoadLiquidClassResult] + + _ImplementationCls: Type[ + LoadLiquidClassImplementation + ] = LoadLiquidClassImplementation + + +class LoadLiquidClassCreate(BaseCommandCreate[LoadLiquidClassParams]): + """Load Liquid Class command creation request.""" + + commandType: LoadLiquidClassCommandType = "loadLiquidClass" + params: LoadLiquidClassParams + + _CommandCls: Type[LoadLiquidClass] = LoadLiquidClass diff --git a/api/src/opentrons/protocol_engine/commands/load_module.py b/api/src/opentrons/protocol_engine/commands/load_module.py index d4cd1efba60..79e67182666 100644 --- a/api/src/opentrons/protocol_engine/commands/load_module.py +++ b/api/src/opentrons/protocol_engine/commands/load_module.py @@ -16,10 +16,9 @@ from opentrons.protocol_engine.resources import deck_configuration_provider -from opentrons.drivers.types import AbsorbanceReaderLidStatus if TYPE_CHECKING: - from ..state import StateView + from ..state.state import StateView from ..execution import EquipmentHandler @@ -105,7 +104,7 @@ class LoadModuleResult(BaseModel): class LoadModuleImplementation( - AbstractCommandImpl[LoadModuleParams, SuccessData[LoadModuleResult, None]] + AbstractCommandImpl[LoadModuleParams, SuccessData[LoadModuleResult]] ): """The implementation of the load module command.""" @@ -115,9 +114,7 @@ def __init__( self._equipment = equipment self._state_view = state_view - async def execute( - self, params: LoadModuleParams - ) -> SuccessData[LoadModuleResult, None]: + async def execute(self, params: LoadModuleParams) -> SuccessData[LoadModuleResult]: """Check that the requested module is attached and assign its identifier.""" module_type = params.model.as_type() self._ensure_module_location(params.location.slotName, module_type) @@ -153,40 +150,6 @@ async def execute( module_id=params.moduleId, ) - # Handle lid position update for loaded Plate Reader module on deck - if ( - not self._state_view.config.use_virtual_modules - and params.model == ModuleModel.ABSORBANCE_READER_V1 - and params.moduleId is not None - ): - abs_reader = self._equipment.get_module_hardware_api( - self._state_view.modules.get_absorbance_reader_substate( - params.moduleId - ).module_id - ) - - if abs_reader is not None: - result = await abs_reader.get_current_lid_status() - if ( - isinstance(result, AbsorbanceReaderLidStatus) - and result is not AbsorbanceReaderLidStatus.ON - ): - reader_area = self._state_view.modules.ensure_and_convert_module_fixture_location( - params.location.slotName, - self._state_view.config.deck_type, - params.model, - ) - lid_labware = self._state_view.labware.get_by_addressable_area( - reader_area - ) - - if lid_labware is not None: - self._state_view.labware._state.labware_by_id[ - lid_labware.id - ].location = self._state_view.modules.absorbance_reader_dock_location( - params.moduleId - ) - return SuccessData( public=LoadModuleResult( moduleId=loaded_module.module_id, @@ -194,7 +157,6 @@ async def execute( model=loaded_module.definition.model, definition=loaded_module.definition, ), - private=None, ) def _ensure_module_location( @@ -211,7 +173,7 @@ def _ensure_module_location( cutout_fixture_id = ModuleType.to_module_fixture_id(module_type) module_fixture = deck_configuration_provider.get_cutout_fixture( cutout_fixture_id, - self._state_view.addressable_areas.state.deck_definition, + self._state_view.labware.get_deck_definition(), ) cutout_id = ( self._state_view.addressable_areas.get_cutout_id_by_deck_slot_name(slot) diff --git a/api/src/opentrons/protocol_engine/commands/load_pipette.py b/api/src/opentrons/protocol_engine/commands/load_pipette.py index ff000a30f0f..6d8d74b4fa2 100644 --- a/api/src/opentrons/protocol_engine/commands/load_pipette.py +++ b/api/src/opentrons/protocol_engine/commands/load_pipette.py @@ -1,6 +1,7 @@ """Load pipette command request, result, and implementation models.""" from __future__ import annotations +from opentrons.protocol_engine.state.update_types import StateUpdate from opentrons_shared_data.pipette.pipette_load_name_conversions import ( convert_to_pipette_name_type, ) @@ -16,23 +17,16 @@ from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ..errors.error_occurrence import ErrorOccurrence -from .configuring_common import PipetteConfigUpdateResultMixin from ..errors import InvalidSpecificationForRobotTypeError, InvalidLoadPipetteSpecsError if TYPE_CHECKING: from ..execution import EquipmentHandler - from ..state import StateView + from ..state.state import StateView LoadPipetteCommandType = Literal["loadPipette"] -class LoadPipettePrivateResult(PipetteConfigUpdateResultMixin): - """The not-to-be-exposed results of a load pipette call.""" - - ... - - class LoadPipetteParams(BaseModel): """Payload needed to load a pipette on to a mount.""" @@ -72,9 +66,7 @@ class LoadPipetteResult(BaseModel): class LoadPipetteImplementation( - AbstractCommandImpl[ - LoadPipetteParams, SuccessData[LoadPipetteResult, LoadPipettePrivateResult] - ] + AbstractCommandImpl[LoadPipetteParams, SuccessData[LoadPipetteResult]] ): """Load pipette command implementation.""" @@ -86,7 +78,7 @@ def __init__( async def execute( self, params: LoadPipetteParams - ) -> SuccessData[LoadPipetteResult, LoadPipettePrivateResult]: + ) -> SuccessData[LoadPipetteResult]: """Check that requested pipette is attached and assign its identifier.""" pipette_generation = convert_to_pipette_name_type( params.pipetteName.value @@ -123,13 +115,23 @@ async def execute( tip_overlap_version=params.tipOverlapNotAfterVersion, ) + state_update = StateUpdate() + state_update.set_load_pipette( + pipette_id=loaded_pipette.pipette_id, + pipette_name=params.pipetteName, + mount=params.mount, + liquid_presence_detection=params.liquidPresenceDetection, + ) + state_update.update_pipette_config( + pipette_id=loaded_pipette.pipette_id, + serial_number=loaded_pipette.serial_number, + config=loaded_pipette.static_config, + ) + state_update.set_fluid_unknown(pipette_id=loaded_pipette.pipette_id) + return SuccessData( public=LoadPipetteResult(pipetteId=loaded_pipette.pipette_id), - private=LoadPipettePrivateResult( - pipette_id=loaded_pipette.pipette_id, - serial_number=loaded_pipette.serial_number, - config=loaded_pipette.static_config, - ), + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/commands/magnetic_module/disengage.py b/api/src/opentrons/protocol_engine/commands/magnetic_module/disengage.py index 47a087059d5..c20b18e481d 100644 --- a/api/src/opentrons/protocol_engine/commands/magnetic_module/disengage.py +++ b/api/src/opentrons/protocol_engine/commands/magnetic_module/disengage.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: from opentrons.protocol_engine.execution import EquipmentHandler - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView DisengageCommandType = Literal["magneticModule/disengage"] @@ -38,7 +38,7 @@ class DisengageResult(BaseModel): class DisengageImplementation( - AbstractCommandImpl[DisengageParams, SuccessData[DisengageResult, None]] + AbstractCommandImpl[DisengageParams, SuccessData[DisengageResult]] ): """The implementation of a Magnetic Module disengage command.""" @@ -51,9 +51,7 @@ def __init__( self._state_view = state_view self._equipment = equipment - async def execute( - self, params: DisengageParams - ) -> SuccessData[DisengageResult, None]: + async def execute(self, params: DisengageParams) -> SuccessData[DisengageResult]: """Execute a Magnetic Module disengage command. Raises: @@ -75,7 +73,9 @@ async def execute( if hardware_module is not None: # Not virtualizing modules. await hardware_module.deactivate() - return SuccessData(public=DisengageResult(), private=None) + return SuccessData( + public=DisengageResult(), + ) class Disengage(BaseCommand[DisengageParams, DisengageResult, ErrorOccurrence]): diff --git a/api/src/opentrons/protocol_engine/commands/magnetic_module/engage.py b/api/src/opentrons/protocol_engine/commands/magnetic_module/engage.py index fcedd750bc3..62f4e24eef4 100644 --- a/api/src/opentrons/protocol_engine/commands/magnetic_module/engage.py +++ b/api/src/opentrons/protocol_engine/commands/magnetic_module/engage.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from opentrons.protocol_engine.execution import EquipmentHandler - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView EngageCommandType = Literal["magneticModule/engage"] @@ -54,7 +54,7 @@ class EngageResult(BaseModel): class EngageImplementation( - AbstractCommandImpl[EngageParams, SuccessData[EngageResult, None]] + AbstractCommandImpl[EngageParams, SuccessData[EngageResult]] ): """The implementation of a Magnetic Module engage command.""" @@ -67,7 +67,7 @@ def __init__( self._state_view = state_view self._equipment = equipment - async def execute(self, params: EngageParams) -> SuccessData[EngageResult, None]: + async def execute(self, params: EngageParams) -> SuccessData[EngageResult]: """Execute a Magnetic Module engage command. Raises: @@ -95,7 +95,9 @@ async def execute(self, params: EngageParams) -> SuccessData[EngageResult, None] if hardware_module is not None: # Not virtualizing modules. await hardware_module.engage(height=hardware_height) - return SuccessData(public=EngageResult(), private=None) + return SuccessData( + public=EngageResult(), + ) class Engage(BaseCommand[EngageParams, EngageResult, ErrorOccurrence]): diff --git a/api/src/opentrons/protocol_engine/commands/move_labware.py b/api/src/opentrons/protocol_engine/commands/move_labware.py index 42728c05272..09cdc08561c 100644 --- a/api/src/opentrons/protocol_engine/commands/move_labware.py +++ b/api/src/opentrons/protocol_engine/commands/move_labware.py @@ -1,29 +1,49 @@ """Models and implementation for the ``moveLabware`` command.""" from __future__ import annotations +from opentrons_shared_data.errors.exceptions import ( + FailedGripperPickupError, + LabwareDroppedError, + StallOrCollisionDetectedError, +) from pydantic import BaseModel, Field from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal +from opentrons.protocol_engine.resources.model_utils import ModelUtils from opentrons.types import Point from ..types import ( + ModuleModel, + CurrentWell, LabwareLocation, DeckSlotLocation, + ModuleLocation, OnLabwareLocation, AddressableAreaLocation, LabwareMovementStrategy, LabwareOffsetVector, LabwareMovementOffsetData, ) -from ..errors import LabwareMovementNotAllowedError, NotSupportedOnRobotType +from ..errors import ( + LabwareMovementNotAllowedError, + NotSupportedOnRobotType, + LabwareOffsetDoesNotExistError, +) from ..resources import labware_validation, fixture_validation -from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + DefinedErrorData, + SuccessData, +) from ..errors.error_occurrence import ErrorOccurrence +from ..state.update_types import StateUpdate from opentrons_shared_data.gripper.constants import GRIPPER_PADDLE_WIDTH if TYPE_CHECKING: from ..execution import EquipmentHandler, RunControlHandler, LabwareMovementHandler - from ..state import StateView + from ..state.state import StateView MoveLabwareCommandType = Literal["moveLabware"] @@ -33,7 +53,6 @@ _TRASH_CHUTE_DROP_BUFFER_MM = 8 -# TODO (spp, 2022-12-14): https://opentrons.atlassian.net/browse/RLAB-237 class MoveLabwareParams(BaseModel): """Input parameters for a ``moveLabware`` command.""" @@ -74,28 +93,42 @@ class MoveLabwareResult(BaseModel): ) -class MoveLabwareImplementation( - AbstractCommandImpl[MoveLabwareParams, SuccessData[MoveLabwareResult, None]] -): +class GripperMovementError(ErrorOccurrence): + """Returned when something physically goes wrong when the gripper moves labware. + + When this error happens, the engine will leave the labware in its original place. + """ + + isDefined: bool = True + + errorType: Literal["gripperMovement"] = "gripperMovement" + + +_ExecuteReturn = SuccessData[MoveLabwareResult] | DefinedErrorData[GripperMovementError] + + +class MoveLabwareImplementation(AbstractCommandImpl[MoveLabwareParams, _ExecuteReturn]): """The execution implementation for ``moveLabware`` commands.""" def __init__( self, + model_utils: ModelUtils, state_view: StateView, equipment: EquipmentHandler, labware_movement: LabwareMovementHandler, run_control: RunControlHandler, **kwargs: object, ) -> None: + self._model_utils = model_utils self._state_view = state_view self._equipment = equipment self._labware_movement = labware_movement self._run_control = run_control - async def execute( # noqa: C901 - self, params: MoveLabwareParams - ) -> SuccessData[MoveLabwareResult, None]: + async def execute(self, params: MoveLabwareParams) -> _ExecuteReturn: # noqa: C901 """Move a loaded labware to a new location.""" + state_update = StateUpdate() + # Allow propagation of LabwareNotLoadedError. current_labware = self._state_view.labware.get(labware_id=params.labwareId) current_labware_definition = self._state_view.labware.get_definition( @@ -103,6 +136,7 @@ async def execute( # noqa: C901 ) definition_uri = current_labware.definitionUri post_drop_slide_offset: Optional[Point] = None + trash_lid_drop_offset: Optional[LabwareOffsetVector] = None if self._state_view.labware.is_fixed_trash(params.labwareId): raise LabwareMovementNotAllowedError( @@ -111,9 +145,11 @@ async def execute( # noqa: C901 if isinstance(params.newLocation, AddressableAreaLocation): area_name = params.newLocation.addressableAreaName - if not fixture_validation.is_gripper_waste_chute( - area_name - ) and not fixture_validation.is_deck_slot(area_name): + if ( + not fixture_validation.is_gripper_waste_chute(area_name) + and not fixture_validation.is_deck_slot(area_name) + and not fixture_validation.is_trash(area_name) + ): raise LabwareMovementNotAllowedError( f"Cannot move {current_labware.loadName} to addressable area {area_name}" ) @@ -135,6 +171,32 @@ async def execute( # noqa: C901 y=0, z=0, ) + elif fixture_validation.is_trash(area_name): + # When dropping labware in the trash bins we want to ensure they are lids + # and enforce a y-axis drop offset to ensure they fall within the trash bin + if labware_validation.validate_definition_is_lid( + self._state_view.labware.get_definition(params.labwareId) + ): + lid_disposable_offfets = ( + current_labware_definition.gripperOffsets.get( + "lidDisposalOffsets" + ) + ) + if lid_disposable_offfets is not None: + trash_lid_drop_offset = LabwareOffsetVector( + x=lid_disposable_offfets.dropOffset.x, + y=lid_disposable_offfets.dropOffset.y, + z=lid_disposable_offfets.dropOffset.z, + ) + else: + raise LabwareOffsetDoesNotExistError( + f"Labware Definition {current_labware.loadName} does not contain required field 'lidDisposalOffsets' of 'gripperOffsets'." + ) + else: + raise LabwareMovementNotAllowedError( + "Can only move labware with allowed role 'Lid' to a Trash Bin." + ) + elif isinstance(params.newLocation, DeckSlotLocation): self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration( params.newLocation.slotName.id @@ -157,6 +219,17 @@ async def execute( # noqa: C901 top_labware_definition=current_labware_definition, bottom_labware_id=available_new_location.labwareId, ) + if params.labwareId == available_new_location.labwareId: + raise LabwareMovementNotAllowedError( + "Cannot move a labware onto itself." + ) + # Validate labware for the absorbance reader + elif isinstance(available_new_location, ModuleLocation): + module = self._state_view.modules.get(available_new_location.moduleId) + if module is not None and module.model == ModuleModel.ABSORBANCE_READER_V1: + self._state_view.labware.raise_if_labware_incompatible_with_plate_reader( + current_labware_definition + ) # Allow propagation of ModuleNotLoadedError. new_offset_id = self._equipment.find_applicable_labware_offset_id( @@ -201,24 +274,83 @@ async def execute( # noqa: C901 dropOffset=params.dropOffset or LabwareOffsetVector(x=0, y=0, z=0), ) - # Skips gripper moves when using virtual gripper - await self._labware_movement.move_labware_with_gripper( - labware_id=params.labwareId, - current_location=validated_current_loc, - new_location=validated_new_loc, - user_offset_data=user_offset_data, - post_drop_slide_offset=post_drop_slide_offset, - ) + if trash_lid_drop_offset: + user_offset_data.dropOffset += trash_lid_drop_offset + + try: + # Skips gripper moves when using virtual gripper + await self._labware_movement.move_labware_with_gripper( + labware_id=params.labwareId, + current_location=validated_current_loc, + new_location=validated_new_loc, + user_offset_data=user_offset_data, + post_drop_slide_offset=post_drop_slide_offset, + ) + except ( + FailedGripperPickupError, + LabwareDroppedError, + StallOrCollisionDetectedError, + # todo(mm, 2024-09-26): Catch LabwareNotPickedUpError when that exists and + # move_labware_with_gripper() raises it. + ) as exception: + gripper_movement_error: GripperMovementError | None = ( + GripperMovementError( + id=self._model_utils.generate_id(), + createdAt=self._model_utils.get_timestamp(), + errorCode=exception.code.value.code, + detail=exception.code.value.detail, + wrappedErrors=[ + ErrorOccurrence.from_failed( + id=self._model_utils.generate_id(), + createdAt=self._model_utils.get_timestamp(), + error=exception, + ) + ], + ) + ) + else: + gripper_movement_error = None + + # All mounts will have been retracted as part of the gripper move. + state_update.clear_all_pipette_locations() + + if gripper_movement_error: + return DefinedErrorData( + public=gripper_movement_error, + state_update=state_update, + ) + elif params.strategy == LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE: # Pause to allow for manual labware movement await self._run_control.wait_for_resume() + # We may have just moved the labware that contains the current well out from + # under the pipette. Clear the current location to reflect the fact that the + # pipette is no longer over any labware. This is necessary for safe path + # planning in case the next movement goes to the same labware (now in a new + # place). + pipette_location = self._state_view.pipettes.get_current_location() + if ( + isinstance(pipette_location, CurrentWell) + and pipette_location.labware_id == params.labwareId + ): + state_update.clear_all_pipette_locations() + + state_update.set_labware_location( + labware_id=params.labwareId, + new_location=available_new_location, + new_offset_id=new_offset_id, + ) + return SuccessData( - public=MoveLabwareResult(offsetId=new_offset_id), private=None + public=MoveLabwareResult(offsetId=new_offset_id), + state_update=state_update, ) -class MoveLabware(BaseCommand[MoveLabwareParams, MoveLabwareResult, ErrorOccurrence]): +class MoveLabware( + BaseCommand[MoveLabwareParams, MoveLabwareResult, GripperMovementError] +): """A ``moveLabware`` command.""" commandType: MoveLabwareCommandType = "moveLabware" diff --git a/api/src/opentrons/protocol_engine/commands/move_relative.py b/api/src/opentrons/protocol_engine/commands/move_relative.py index 38ac0806217..54c877a3693 100644 --- a/api/src/opentrons/protocol_engine/commands/move_relative.py +++ b/api/src/opentrons/protocol_engine/commands/move_relative.py @@ -4,13 +4,24 @@ from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal -from ..types import MovementAxis, DeckPoint -from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData -from ..errors.error_occurrence import ErrorOccurrence -from .pipetting_common import DestinationPositionResult + +from ..types import MovementAxis +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, + DefinedErrorData, +) +from .movement_common import ( + DestinationPositionResult, + move_relative, + StallOrCollisionError, +) if TYPE_CHECKING: from ..execution import MovementHandler + from ..resources.model_utils import ModelUtils MoveRelativeCommandType = Literal["moveRelative"] @@ -37,30 +48,41 @@ class MoveRelativeResult(DestinationPositionResult): class MoveRelativeImplementation( - AbstractCommandImpl[MoveRelativeParams, SuccessData[MoveRelativeResult, None]] + AbstractCommandImpl[ + MoveRelativeParams, + SuccessData[MoveRelativeResult] | DefinedErrorData[StallOrCollisionError], + ] ): """Move relative command implementation.""" - def __init__(self, movement: MovementHandler, **kwargs: object) -> None: + def __init__( + self, movement: MovementHandler, model_utils: ModelUtils, **kwargs: object + ) -> None: self._movement = movement + self._model_utils = model_utils async def execute( self, params: MoveRelativeParams - ) -> SuccessData[MoveRelativeResult, None]: + ) -> SuccessData[MoveRelativeResult] | DefinedErrorData[StallOrCollisionError]: """Move (jog) a given pipette a relative distance.""" - x, y, z = await self._movement.move_relative( + result = await move_relative( + movement=self._movement, + model_utils=self._model_utils, pipette_id=params.pipetteId, axis=params.axis, distance=params.distance, ) - - return SuccessData( - public=MoveRelativeResult(position=DeckPoint(x=x, y=y, z=z)), private=None - ) + if isinstance(result, DefinedErrorData): + return result + else: + return SuccessData( + public=MoveRelativeResult(position=result.public.position), + state_update=result.state_update, + ) class MoveRelative( - BaseCommand[MoveRelativeParams, MoveRelativeResult, ErrorOccurrence] + BaseCommand[MoveRelativeParams, MoveRelativeResult, StallOrCollisionError] ): """Command to move (jog) a given pipette a relative distance.""" diff --git a/api/src/opentrons/protocol_engine/commands/move_to_addressable_area.py b/api/src/opentrons/protocol_engine/commands/move_to_addressable_area.py index 5d959538ca2..7380a01951a 100644 --- a/api/src/opentrons/protocol_engine/commands/move_to_addressable_area.py +++ b/api/src/opentrons/protocol_engine/commands/move_to_addressable_area.py @@ -4,20 +4,32 @@ from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal +from opentrons_shared_data.pipette.types import PipetteNameType + from ..errors import LocationNotAccessibleByPipetteError -from ..types import DeckPoint, AddressableOffsetVector +from ..types import AddressableOffsetVector from ..resources import fixture_validation from .pipetting_common import ( PipetteIdMixin, +) +from .movement_common import ( MovementMixin, DestinationPositionResult, + move_to_addressable_area, + StallOrCollisionError, +) +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, + DefinedErrorData, ) -from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData -from ..errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: from ..execution import MovementHandler - from ..state import StateView + from ..state.state import StateView + from ..resources.model_utils import ModelUtils MoveToAddressableAreaCommandType = Literal["moveToAddressableArea"] @@ -71,33 +83,54 @@ class MoveToAddressableAreaResult(DestinationPositionResult): pass +_ExecuteReturn = ( + SuccessData[MoveToAddressableAreaResult] | DefinedErrorData[StallOrCollisionError] +) + + class MoveToAddressableAreaImplementation( - AbstractCommandImpl[ - MoveToAddressableAreaParams, SuccessData[MoveToAddressableAreaResult, None] - ] + AbstractCommandImpl[MoveToAddressableAreaParams, _ExecuteReturn] ): """Move to addressable area command implementation.""" def __init__( - self, movement: MovementHandler, state_view: StateView, **kwargs: object + self, + movement: MovementHandler, + state_view: StateView, + model_utils: ModelUtils, + **kwargs: object, ) -> None: self._movement = movement self._state_view = state_view + self._model_utils = model_utils - async def execute( - self, params: MoveToAddressableAreaParams - ) -> SuccessData[MoveToAddressableAreaResult, None]: + async def execute(self, params: MoveToAddressableAreaParams) -> _ExecuteReturn: """Move the requested pipette to the requested addressable area.""" self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration( params.addressableAreaName ) + loaded_pipette = self._state_view.pipettes.get(params.pipetteId) + if loaded_pipette.pipetteName in ( + PipetteNameType.P10_SINGLE, + PipetteNameType.P10_MULTI, + PipetteNameType.P50_MULTI, + PipetteNameType.P50_SINGLE, + PipetteNameType.P300_SINGLE, + PipetteNameType.P300_MULTI, + PipetteNameType.P1000_SINGLE, + ): + extra_z_offset: Optional[float] = 5.0 + else: + extra_z_offset = None if fixture_validation.is_staging_slot(params.addressableAreaName): raise LocationNotAccessibleByPipetteError( f"Cannot move pipette to staging slot {params.addressableAreaName}" ) - x, y, z = await self._movement.move_to_addressable_area( + result = await move_to_addressable_area( + movement=self._movement, + model_utils=self._model_utils, pipette_id=params.pipetteId, addressable_area_name=params.addressableAreaName, offset=params.offset, @@ -105,17 +138,22 @@ async def execute( minimum_z_height=params.minimumZHeight, speed=params.speed, stay_at_highest_possible_z=params.stayAtHighestPossibleZ, + highest_possible_z_extra_offset=extra_z_offset, ) - - return SuccessData( - public=MoveToAddressableAreaResult(position=DeckPoint(x=x, y=y, z=z)), - private=None, - ) + if isinstance(result, DefinedErrorData): + return result + else: + return SuccessData( + public=MoveToAddressableAreaResult(position=result.public.position), + state_update=result.state_update, + ) class MoveToAddressableArea( BaseCommand[ - MoveToAddressableAreaParams, MoveToAddressableAreaResult, ErrorOccurrence + MoveToAddressableAreaParams, + MoveToAddressableAreaResult, + StallOrCollisionError, ] ): """Move to addressable area command model.""" diff --git a/api/src/opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py b/api/src/opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py index d38d7ceb758..679e769cc2e 100644 --- a/api/src/opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py +++ b/api/src/opentrons/protocol_engine/commands/move_to_addressable_area_for_drop_tip.py @@ -5,19 +5,29 @@ from typing_extensions import Literal from ..errors import LocationNotAccessibleByPipetteError -from ..types import DeckPoint, AddressableOffsetVector +from ..types import AddressableOffsetVector from ..resources import fixture_validation from .pipetting_common import ( PipetteIdMixin, +) +from .movement_common import ( MovementMixin, DestinationPositionResult, + move_to_addressable_area, + StallOrCollisionError, +) +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, + DefinedErrorData, ) -from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData -from ..errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: from ..execution import MovementHandler - from ..state import StateView + from ..state.state import StateView + from ..resources.model_utils import ModelUtils MoveToAddressableAreaForDropTipCommandType = Literal["moveToAddressableAreaForDropTip"] @@ -82,23 +92,31 @@ class MoveToAddressableAreaForDropTipResult(DestinationPositionResult): pass +_ExecuteReturn = ( + SuccessData[MoveToAddressableAreaForDropTipResult] + | DefinedErrorData[StallOrCollisionError] +) + + class MoveToAddressableAreaForDropTipImplementation( - AbstractCommandImpl[ - MoveToAddressableAreaForDropTipParams, - SuccessData[MoveToAddressableAreaForDropTipResult, None], - ] + AbstractCommandImpl[MoveToAddressableAreaForDropTipParams, _ExecuteReturn] ): """Move to addressable area for drop tip command implementation.""" def __init__( - self, movement: MovementHandler, state_view: StateView, **kwargs: object + self, + movement: MovementHandler, + state_view: StateView, + model_utils: ModelUtils, + **kwargs: object, ) -> None: self._movement = movement self._state_view = state_view + self._model_utils = model_utils async def execute( self, params: MoveToAddressableAreaForDropTipParams - ) -> SuccessData[MoveToAddressableAreaForDropTipResult, None]: + ) -> _ExecuteReturn: """Move the requested pipette to the requested addressable area in preperation of a drop tip.""" self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration( params.addressableAreaName @@ -117,7 +135,9 @@ async def execute( else: offset = params.offset - x, y, z = await self._movement.move_to_addressable_area( + result = await move_to_addressable_area( + movement=self._movement, + model_utils=self._model_utils, pipette_id=params.pipetteId, addressable_area_name=params.addressableAreaName, offset=offset, @@ -126,20 +146,22 @@ async def execute( speed=params.speed, ignore_tip_configuration=params.ignoreTipConfiguration, ) - - return SuccessData( - public=MoveToAddressableAreaForDropTipResult( - position=DeckPoint(x=x, y=y, z=z) - ), - private=None, - ) + if isinstance(result, DefinedErrorData): + return result + else: + return SuccessData( + public=MoveToAddressableAreaForDropTipResult( + position=result.public.position, + ), + state_update=result.state_update, + ) class MoveToAddressableAreaForDropTip( BaseCommand[ MoveToAddressableAreaForDropTipParams, MoveToAddressableAreaForDropTipResult, - ErrorOccurrence, + StallOrCollisionError, ] ): """Move to addressable area for drop tip command model.""" diff --git a/api/src/opentrons/protocol_engine/commands/move_to_coordinates.py b/api/src/opentrons/protocol_engine/commands/move_to_coordinates.py index 71e45b05e60..36b7ff64ed0 100644 --- a/api/src/opentrons/protocol_engine/commands/move_to_coordinates.py +++ b/api/src/opentrons/protocol_engine/commands/move_to_coordinates.py @@ -5,13 +5,26 @@ from typing import Optional, Type, TYPE_CHECKING from typing_extensions import Literal + from ..types import DeckPoint -from .pipetting_common import PipetteIdMixin, MovementMixin, DestinationPositionResult -from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData -from ..errors.error_occurrence import ErrorOccurrence +from .pipetting_common import PipetteIdMixin +from .movement_common import ( + MovementMixin, + DestinationPositionResult, + move_to_coordinates, + StallOrCollisionError, +) +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, + DefinedErrorData, +) if TYPE_CHECKING: from ..execution import MovementHandler + from ..resources.model_utils import ModelUtils MoveToCoordinatesCommandType = Literal["moveToCoordinates"] @@ -32,40 +45,47 @@ class MoveToCoordinatesResult(DestinationPositionResult): pass +_ExecuteReturn = ( + SuccessData[MoveToCoordinatesResult] | DefinedErrorData[StallOrCollisionError] +) + + class MoveToCoordinatesImplementation( - AbstractCommandImpl[ - MoveToCoordinatesParams, SuccessData[MoveToCoordinatesResult, None] - ] + AbstractCommandImpl[MoveToCoordinatesParams, _ExecuteReturn] ): """Move to coordinates command implementation.""" def __init__( self, movement: MovementHandler, + model_utils: ModelUtils, **kwargs: object, ) -> None: self._movement = movement + self._model_utils = model_utils - async def execute( - self, params: MoveToCoordinatesParams - ) -> SuccessData[MoveToCoordinatesResult, None]: + async def execute(self, params: MoveToCoordinatesParams) -> _ExecuteReturn: """Move the requested pipette to the requested coordinates.""" - x, y, z = await self._movement.move_to_coordinates( + result = await move_to_coordinates( + movement=self._movement, + model_utils=self._model_utils, pipette_id=params.pipetteId, deck_coordinates=params.coordinates, direct=params.forceDirect, additional_min_travel_z=params.minimumZHeight, speed=params.speed, ) - - return SuccessData( - public=MoveToCoordinatesResult(position=DeckPoint(x=x, y=y, z=z)), - private=None, - ) + if isinstance(result, DefinedErrorData): + return result + else: + return SuccessData( + public=MoveToCoordinatesResult(position=result.public.position), + state_update=result.state_update, + ) class MoveToCoordinates( - BaseCommand[MoveToCoordinatesParams, MoveToCoordinatesResult, ErrorOccurrence] + BaseCommand[MoveToCoordinatesParams, MoveToCoordinatesResult, StallOrCollisionError] ): """Move to well command model.""" diff --git a/api/src/opentrons/protocol_engine/commands/move_to_well.py b/api/src/opentrons/protocol_engine/commands/move_to_well.py index 2ed10757b69..6aaf398650f 100644 --- a/api/src/opentrons/protocol_engine/commands/move_to_well.py +++ b/api/src/opentrons/protocol_engine/commands/move_to_well.py @@ -1,20 +1,32 @@ """Move to well command request, result, and implementation models.""" + from __future__ import annotations from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal -from ..types import DeckPoint from .pipetting_common import ( PipetteIdMixin, +) +from .movement_common import ( WellLocationMixin, MovementMixin, DestinationPositionResult, + StallOrCollisionError, + move_to_well, +) +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, + DefinedErrorData, ) -from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData -from ..errors.error_occurrence import ErrorOccurrence +from ..errors import LabwareIsTipRackError if TYPE_CHECKING: from ..execution import MovementHandler + from ..state.state import StateView + from ..resources.model_utils import ModelUtils MoveToWellCommandType = Literal["moveToWell"] @@ -32,33 +44,64 @@ class MoveToWellResult(DestinationPositionResult): class MoveToWellImplementation( - AbstractCommandImpl[MoveToWellParams, SuccessData[MoveToWellResult, None]] + AbstractCommandImpl[ + MoveToWellParams, + SuccessData[MoveToWellResult] | DefinedErrorData[StallOrCollisionError], + ] ): """Move to well command implementation.""" - def __init__(self, movement: MovementHandler, **kwargs: object) -> None: + def __init__( + self, + state_view: StateView, + movement: MovementHandler, + model_utils: ModelUtils, + **kwargs: object, + ) -> None: + self._state_view = state_view self._movement = movement + self._model_utils = model_utils async def execute( self, params: MoveToWellParams - ) -> SuccessData[MoveToWellResult, None]: + ) -> SuccessData[MoveToWellResult] | DefinedErrorData[StallOrCollisionError]: """Move the requested pipette to the requested well.""" - x, y, z = await self._movement.move_to_well( - pipette_id=params.pipetteId, - labware_id=params.labwareId, - well_name=params.wellName, - well_location=params.wellLocation, + pipette_id = params.pipetteId + labware_id = params.labwareId + well_name = params.wellName + well_location = params.wellLocation + + if ( + self._state_view.labware.is_tiprack(labware_id) + and well_location.volumeOffset + ): + raise LabwareIsTipRackError( + "Cannot specify a WellLocation with a volumeOffset with movement to a tip rack" + ) + + move_result = await move_to_well( + model_utils=self._model_utils, + movement=self._movement, + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=well_location, force_direct=params.forceDirect, minimum_z_height=params.minimumZHeight, speed=params.speed, ) - - return SuccessData( - public=MoveToWellResult(position=DeckPoint(x=x, y=y, z=z)), private=None - ) + if isinstance(move_result, DefinedErrorData): + return move_result + else: + return SuccessData( + public=MoveToWellResult(position=move_result.public.position), + state_update=move_result.state_update, + ) -class MoveToWell(BaseCommand[MoveToWellParams, MoveToWellResult, ErrorOccurrence]): +class MoveToWell( + BaseCommand[MoveToWellParams, MoveToWellResult, StallOrCollisionError] +): """Move to well command model.""" commandType: MoveToWellCommandType = "moveToWell" diff --git a/api/src/opentrons/protocol_engine/commands/movement_common.py b/api/src/opentrons/protocol_engine/commands/movement_common.py new file mode 100644 index 00000000000..7917daa8613 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/movement_common.py @@ -0,0 +1,327 @@ +"""Common movement base models.""" + +from __future__ import annotations + +from typing import Optional, Union, TYPE_CHECKING, Literal + +from pydantic import BaseModel, Field + +from opentrons_shared_data.errors import ErrorCodes +from opentrons_shared_data.errors.exceptions import StallOrCollisionDetectedError +from ..errors import ErrorOccurrence +from ..types import ( + WellLocation, + LiquidHandlingWellLocation, + DeckPoint, + CurrentWell, + MovementAxis, + AddressableOffsetVector, +) +from ..state.update_types import StateUpdate, PipetteLocationUpdate +from .command import SuccessData, DefinedErrorData + + +if TYPE_CHECKING: + from ..execution.movement import MovementHandler + from ..resources.model_utils import ModelUtils + + +class WellLocationMixin(BaseModel): + """Mixin for command requests that take a location that's somewhere in a well.""" + + labwareId: str = Field( + ..., + description="Identifier of labware to use.", + ) + wellName: str = Field( + ..., + description="Name of well to use in labware.", + ) + wellLocation: WellLocation = Field( + default_factory=WellLocation, + description="Relative well location at which to perform the operation", + ) + + +class LiquidHandlingWellLocationMixin(BaseModel): + """Mixin for command requests that take a location that's somewhere in a well.""" + + labwareId: str = Field( + ..., + description="Identifier of labware to use.", + ) + wellName: str = Field( + ..., + description="Name of well to use in labware.", + ) + wellLocation: LiquidHandlingWellLocation = Field( + default_factory=LiquidHandlingWellLocation, + description="Relative well location at which to perform the operation", + ) + + +class MovementMixin(BaseModel): + """Mixin for command requests that move a pipette.""" + + minimumZHeight: Optional[float] = Field( + None, + description=( + "Optional minimal Z margin in mm." + " If this is larger than the API's default safe Z margin," + " it will make the arc higher. If it's smaller, it will have no effect." + ), + ) + + forceDirect: bool = Field( + False, + description=( + "If true, moving from one labware/well to another" + " will not arc to the default safe z," + " but instead will move directly to the specified location." + " This will also force the `minimumZHeight` param to be ignored." + " A 'direct' movement is in X/Y/Z simultaneously." + ), + ) + + speed: Optional[float] = Field( + None, + description=( + "Override the travel speed in mm/s." + " This controls the straight linear speed of motion." + ), + ) + + +class StallOrCollisionError(ErrorOccurrence): + """Returned when the machine detects that axis encoders are reading a different position than expected. + + All axes are stopped at the point where the error was encountered. + + The next thing to move the machine must account for the robot not having a valid estimate + of its position. It should be a `home` or `unsafe/updatePositionEstimators`. + """ + + isDefined: bool = True + errorType: Literal["stallOrCollision"] = "stallOrCollision" + + errorCode: str = ErrorCodes.STALL_OR_COLLISION_DETECTED.value.code + detail: str = ErrorCodes.STALL_OR_COLLISION_DETECTED.value.detail + + +class DestinationPositionResult(BaseModel): + """Mixin for command results that move a pipette.""" + + # todo(mm, 2024-08-02): Consider deprecating or redefining this. + # + # This is here because opentrons.protocol_engine needed it for internal bookkeeping + # and, at the time, we didn't have a way to do that without adding this to the + # public command results. Its usefulness to callers outside + # opentrons.protocol_engine is questionable because they would need to know which + # critical point is in play, and I think that can change depending on obscure + # things like labware quirks. + position: DeckPoint = Field( + DeckPoint(x=0, y=0, z=0), + description=( + "The (x,y,z) coordinates of the pipette's critical point in deck space" + " after the move was completed." + ), + ) + + +MoveToWellOperationReturn = ( + SuccessData[DestinationPositionResult] | DefinedErrorData[StallOrCollisionError] +) + + +async def move_to_well( + movement: MovementHandler, + model_utils: ModelUtils, + pipette_id: str, + labware_id: str, + well_name: str, + well_location: Optional[Union[WellLocation, LiquidHandlingWellLocation]] = None, + current_well: Optional[CurrentWell] = None, + force_direct: bool = False, + minimum_z_height: Optional[float] = None, + speed: Optional[float] = None, + operation_volume: Optional[float] = None, +) -> MoveToWellOperationReturn: + """Execute a move to well microoperation.""" + try: + position = await movement.move_to_well( + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=well_location, + current_well=current_well, + force_direct=force_direct, + minimum_z_height=minimum_z_height, + speed=speed, + operation_volume=operation_volume, + ) + except StallOrCollisionDetectedError as e: + return DefinedErrorData( + public=StallOrCollisionError( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + wrappedErrors=[ + ErrorOccurrence.from_failed( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + error=e, + ) + ], + ), + state_update=StateUpdate().clear_all_pipette_locations(), + ) + else: + deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z) + return SuccessData( + public=DestinationPositionResult( + position=deck_point, + ), + state_update=StateUpdate().set_pipette_location( + pipette_id=pipette_id, + new_labware_id=labware_id, + new_well_name=well_name, + new_deck_point=deck_point, + ), + ) + + +async def move_relative( + movement: MovementHandler, + model_utils: ModelUtils, + pipette_id: str, + axis: MovementAxis, + distance: float, +) -> SuccessData[DestinationPositionResult] | DefinedErrorData[StallOrCollisionError]: + """Move by a fixed displacement from the current position.""" + try: + position = await movement.move_relative(pipette_id, axis, distance) + except StallOrCollisionDetectedError as e: + return DefinedErrorData( + public=StallOrCollisionError( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + wrappedErrors=[ + ErrorOccurrence.from_failed( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + error=e, + ) + ], + ), + state_update=StateUpdate().clear_all_pipette_locations(), + ) + else: + deck_point = DeckPoint.construct(x=position.x, y=position.y, z=position.z) + return SuccessData( + public=DestinationPositionResult( + position=deck_point, + ), + state_update=StateUpdate().set_pipette_location( + pipette_id=pipette_id, new_deck_point=deck_point + ), + ) + + +async def move_to_addressable_area( + movement: MovementHandler, + model_utils: ModelUtils, + pipette_id: str, + addressable_area_name: str, + offset: AddressableOffsetVector, + force_direct: bool = False, + minimum_z_height: float | None = None, + speed: float | None = None, + stay_at_highest_possible_z: bool = False, + ignore_tip_configuration: bool | None = True, + highest_possible_z_extra_offset: float | None = None, +) -> SuccessData[DestinationPositionResult] | DefinedErrorData[StallOrCollisionError]: + """Move to an addressable area identified by name.""" + try: + x, y, z = await movement.move_to_addressable_area( + pipette_id=pipette_id, + addressable_area_name=addressable_area_name, + offset=offset, + force_direct=force_direct, + minimum_z_height=minimum_z_height, + speed=speed, + stay_at_highest_possible_z=stay_at_highest_possible_z, + ignore_tip_configuration=ignore_tip_configuration, + highest_possible_z_extra_offset=highest_possible_z_extra_offset, + ) + except StallOrCollisionDetectedError as e: + return DefinedErrorData( + public=StallOrCollisionError( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + wrappedErrors=[ + ErrorOccurrence.from_failed( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + error=e, + ) + ], + ), + state_update=StateUpdate().clear_all_pipette_locations(), + ) + else: + deck_point = DeckPoint.construct(x=x, y=y, z=z) + return SuccessData( + public=DestinationPositionResult(position=deck_point), + state_update=StateUpdate().set_pipette_location( + pipette_id=pipette_id, + new_addressable_area_name=addressable_area_name, + new_deck_point=deck_point, + ), + ) + + +async def move_to_coordinates( + movement: MovementHandler, + model_utils: ModelUtils, + pipette_id: str, + deck_coordinates: DeckPoint, + direct: bool, + additional_min_travel_z: float | None, + speed: float | None = None, +) -> SuccessData[DestinationPositionResult] | DefinedErrorData[StallOrCollisionError]: + """Move to a set of coordinates.""" + try: + x, y, z = await movement.move_to_coordinates( + pipette_id=pipette_id, + deck_coordinates=deck_coordinates, + direct=direct, + additional_min_travel_z=additional_min_travel_z, + speed=speed, + ) + except StallOrCollisionDetectedError as e: + return DefinedErrorData( + public=StallOrCollisionError( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + wrappedErrors=[ + ErrorOccurrence.from_failed( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + error=e, + ) + ], + ), + state_update=StateUpdate().clear_all_pipette_locations(), + ) + else: + deck_point = DeckPoint.construct(x=x, y=y, z=z) + + return SuccessData( + public=DestinationPositionResult(position=DeckPoint(x=x, y=y, z=z)), + state_update=StateUpdate( + pipette_location=PipetteLocationUpdate( + pipette_id=pipette_id, + new_location=None, + new_deck_point=deck_point, + ) + ), + ) diff --git a/api/src/opentrons/protocol_engine/commands/pick_up_tip.py b/api/src/opentrons/protocol_engine/commands/pick_up_tip.py index 0022c517eeb..af8723a5bba 100644 --- a/api/src/opentrons/protocol_engine/commands/pick_up_tip.py +++ b/api/src/opentrons/protocol_engine/commands/pick_up_tip.py @@ -1,20 +1,23 @@ """Pick up tip command request, result, and implementation models.""" + from __future__ import annotations -from dataclasses import dataclass from opentrons_shared_data.errors import ErrorCodes from pydantic import Field from typing import TYPE_CHECKING, Optional, Type, Union from typing_extensions import Literal -from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError -from ..errors import ErrorOccurrence +from ..errors import ErrorOccurrence, PickUpTipTipNotAttachedError from ..resources import ModelUtils -from ..types import DeckPoint +from ..state import update_types +from ..types import PickUpTipWellLocation from .pipetting_common import ( PipetteIdMixin, - WellLocationMixin, +) +from .movement_common import ( DestinationPositionResult, + StallOrCollisionError, + move_to_well, ) from .command import ( AbstractCommandImpl, @@ -25,17 +28,22 @@ ) if TYPE_CHECKING: - from ..state import StateView + from ..state.state import StateView from ..execution import MovementHandler, TipHandler PickUpTipCommandType = Literal["pickUpTip"] -class PickUpTipParams(PipetteIdMixin, WellLocationMixin): +class PickUpTipParams(PipetteIdMixin): """Payload needed to move a pipette to a specific well.""" - pass + labwareId: str = Field(..., description="Identifier of labware to use.") + wellName: str = Field(..., description="Name of well to use in labware.") + wellLocation: PickUpTipWellLocation = Field( + default_factory=PickUpTipWellLocation, + description="Relative well location at which to pick up the tip.", + ) class PickUpTipResult(DestinationPositionResult): @@ -72,24 +80,20 @@ class TipPhysicallyMissingError(ErrorOccurrence): of the pipette. """ + # The thing above about marking the tips as used makes it so that + # when the protocol is resumed and the Python Protocol API calls + # `get_next_tip()`, we'll move on to other tips as expected. + isDefined: bool = True errorType: Literal["tipPhysicallyMissing"] = "tipPhysicallyMissing" errorCode: str = ErrorCodes.TIP_PICKUP_FAILED.value.code - detail: str = "No tip detected." - - -@dataclass(frozen=True) -class TipPhysicallyMissingErrorInternalData: - """Internal-to-ProtocolEngine data about a TipPhysicallyMissingError.""" - - pipette_id: str - labware_id: str - well_name: str + detail: str = "No Tip Detected" _ExecuteReturn = Union[ - SuccessData[PickUpTipResult, None], - DefinedErrorData[TipPhysicallyMissingError, TipPhysicallyMissingErrorInternalData], + SuccessData[PickUpTipResult], + DefinedErrorData[TipPhysicallyMissingError] + | DefinedErrorData[StallOrCollisionError], ] @@ -111,19 +115,25 @@ def __init__( async def execute( self, params: PickUpTipParams - ) -> Union[SuccessData[PickUpTipResult, None], _ExecuteReturn]: + ) -> Union[SuccessData[PickUpTipResult], _ExecuteReturn]: """Move to and pick up a tip using the requested pipette.""" pipette_id = params.pipetteId labware_id = params.labwareId well_name = params.wellName - well_location = params.wellLocation - position = await self._movement.move_to_well( + well_location = self._state_view.geometry.convert_pick_up_tip_well_location( + well_location=params.wellLocation + ) + move_result = await move_to_well( + movement=self._movement, + model_utils=self._model_utils, pipette_id=pipette_id, labware_id=labware_id, well_name=well_name, well_location=well_location, ) + if isinstance(move_result, DefinedErrorData): + return move_result try: tip_geometry = await self._tip_handler.pick_up_tip( @@ -131,7 +141,29 @@ async def execute( labware_id=labware_id, well_name=well_name, ) - except TipNotAttachedError as e: + except PickUpTipTipNotAttachedError as e: + state_update_if_false_positive = ( + update_types.StateUpdate.reduce( + update_types.StateUpdate(), move_result.state_update + ) + .update_pipette_tip_state( + pipette_id=pipette_id, + tip_geometry=e.tip_geometry, + ) + .set_fluid_empty(pipette_id=pipette_id) + .mark_tips_as_used( + pipette_id=pipette_id, labware_id=labware_id, well_name=well_name + ) + ) + state_update = ( + update_types.StateUpdate.reduce( + update_types.StateUpdate(), move_result.state_update + ) + .mark_tips_as_used( + pipette_id=pipette_id, labware_id=labware_id, well_name=well_name + ) + .set_fluid_unknown(pipette_id=pipette_id) + ) return DefinedErrorData( public=TipPhysicallyMissingError( id=self._model_utils.generate_id(), @@ -144,26 +176,37 @@ async def execute( ) ], ), - private=TipPhysicallyMissingErrorInternalData( - pipette_id=pipette_id, - labware_id=labware_id, - well_name=well_name, - ), + state_update=state_update, + state_update_if_false_positive=state_update_if_false_positive, ) else: + state_update = ( + move_result.state_update.update_pipette_tip_state( + pipette_id=pipette_id, + tip_geometry=tip_geometry, + ) + .mark_tips_as_used( + pipette_id=pipette_id, labware_id=labware_id, well_name=well_name + ) + .set_fluid_empty(pipette_id=pipette_id) + ) return SuccessData( public=PickUpTipResult( tipVolume=tip_geometry.volume, tipLength=tip_geometry.length, tipDiameter=tip_geometry.diameter, - position=DeckPoint(x=position.x, y=position.y, z=position.z), + position=move_result.public.position, ), - private=None, + state_update=state_update, ) class PickUpTip( - BaseCommand[PickUpTipParams, PickUpTipResult, TipPhysicallyMissingError] + BaseCommand[ + PickUpTipParams, + PickUpTipResult, + TipPhysicallyMissingError | StallOrCollisionError, + ] ): """Pick up tip command model.""" diff --git a/api/src/opentrons/protocol_engine/commands/pipetting_common.py b/api/src/opentrons/protocol_engine/commands/pipetting_common.py index 2be1e6f2d54..0292b51eee1 100644 --- a/api/src/opentrons/protocol_engine/commands/pipetting_common.py +++ b/api/src/opentrons/protocol_engine/commands/pipetting_common.py @@ -1,12 +1,21 @@ """Common pipetting command base models.""" -from dataclasses import dataclass + +from __future__ import annotations from opentrons_shared_data.errors import ErrorCodes from pydantic import BaseModel, Field -from typing import Literal, Optional, Tuple, TypedDict +from typing import Literal, Tuple, TypedDict, TYPE_CHECKING from opentrons.protocol_engine.errors.error_occurrence import ErrorOccurrence +from opentrons.protocol_engine.types import AspiratedFluid, FluidKind +from opentrons_shared_data.errors.exceptions import PipetteOverpressureError +from .command import DefinedErrorData, SuccessData +from opentrons.protocol_engine.state.update_types import StateUpdate + -from ..types import WellLocation, DeckPoint +if TYPE_CHECKING: + from ..execution.pipetting import PipettingHandler + from ..resources import ModelUtils + from ..notes import CommandNoteAdder class PipetteIdMixin(BaseModel): @@ -52,55 +61,6 @@ class FlowRateMixin(BaseModel): ) -class WellLocationMixin(BaseModel): - """Mixin for command requests that take a location that's somewhere in a well.""" - - labwareId: str = Field( - ..., - description="Identifier of labware to use.", - ) - wellName: str = Field( - ..., - description="Name of well to use in labware.", - ) - wellLocation: WellLocation = Field( - default_factory=WellLocation, - description="Relative well location at which to perform the operation", - ) - - -class MovementMixin(BaseModel): - """Mixin for command requests that move a pipette.""" - - minimumZHeight: Optional[float] = Field( - None, - description=( - "Optional minimal Z margin in mm." - " If this is larger than the API's default safe Z margin," - " it will make the arc higher. If it's smaller, it will have no effect." - ), - ) - - forceDirect: bool = Field( - False, - description=( - "If true, moving from one labware/well to another" - " will not arc to the default safe z," - " but instead will move directly to the specified location." - " This will also force the `minimumZHeight` param to be ignored." - " A 'direct' movement is in X/Y/Z simultaneously." - ), - ) - - speed: Optional[float] = Field( - None, - description=( - "Override the travel speed in mm/s." - " This controls the straight linear speed of motion." - ), - ) - - class BaseLiquidHandlingResult(BaseModel): """Base properties of a liquid handling result.""" @@ -111,20 +71,13 @@ class BaseLiquidHandlingResult(BaseModel): ) -class DestinationPositionResult(BaseModel): - """Mixin for command results that move a pipette.""" - - position: DeckPoint = Field( - DeckPoint(x=0, y=0, z=0), - description=( - "The (x,y,z) coordinates of the pipette's critical point in deck space" - " after the move was completed." - ), - ) - - class ErrorLocationInfo(TypedDict): - """Holds a retry location for in-place error recovery.""" + """Holds a retry location for in-place error recovery. + + This is appropriate to pass to a `moveToCoordinates` command, + assuming the pipette has not been configured with a different nozzle layout + in the meantime. + """ retryLocation: Tuple[float, float, float] @@ -149,14 +102,6 @@ class OverpressureError(ErrorOccurrence): errorInfo: ErrorLocationInfo -@dataclass(frozen=True) -class OverpressureErrorInternalData: - """Internal-to-ProtocolEngine data about an OverpressureError.""" - - position: DeckPoint - """Same meaning as DestinationPositionResult.position.""" - - class LiquidNotFoundError(ErrorOccurrence): """Returned when no liquid is detected during the liquid probe process/move. @@ -171,9 +116,171 @@ class LiquidNotFoundError(ErrorOccurrence): detail: str = ErrorCodes.PIPETTE_LIQUID_NOT_FOUND.value.detail -@dataclass(frozen=True) -class LiquidNotFoundErrorInternalData: - """Internal-to-ProtocolEngine data about a LiquidNotFoundError.""" +class TipPhysicallyAttachedError(ErrorOccurrence): + """Returned when sensors determine that a tip remains on the pipette after a drop attempt. + + The pipette will act as if the tip was not dropped. So, you won't be able to pick + up a new tip without dropping the current one, and movement commands will assume + there is a tip hanging off the bottom of the pipette. + """ + + isDefined: bool = True + + errorType: Literal["tipPhysicallyAttached"] = "tipPhysicallyAttached" + + errorCode: str = ErrorCodes.TIP_DROP_FAILED.value.code + detail: str = ErrorCodes.TIP_DROP_FAILED.value.detail + + errorInfo: ErrorLocationInfo + - position: DeckPoint - """Same meaning as DestinationPositionResult.position.""" +async def prepare_for_aspirate( + pipette_id: str, + pipetting: PipettingHandler, + model_utils: ModelUtils, + location_if_error: ErrorLocationInfo, +) -> SuccessData[BaseModel] | DefinedErrorData[OverpressureError]: + """Execute pipetting.prepare_for_aspirate, handle errors, and marshal success.""" + try: + await pipetting.prepare_for_aspirate(pipette_id) + except PipetteOverpressureError as e: + return DefinedErrorData( + public=OverpressureError( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + wrappedErrors=[ + ErrorOccurrence.from_failed( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + error=e, + ) + ], + errorInfo=location_if_error, + ), + state_update=StateUpdate().set_fluid_unknown(pipette_id=pipette_id), + ) + else: + return SuccessData( + public=BaseModel(), + state_update=StateUpdate().set_fluid_empty(pipette_id=pipette_id), + ) + + +async def aspirate_in_place( + pipette_id: str, + volume: float, + flow_rate: float, + location_if_error: ErrorLocationInfo, + command_note_adder: CommandNoteAdder, + pipetting: PipettingHandler, + model_utils: ModelUtils, +) -> SuccessData[BaseLiquidHandlingResult] | DefinedErrorData[OverpressureError]: + """Execute an aspirate in place microoperation.""" + try: + volume_aspirated = await pipetting.aspirate_in_place( + pipette_id=pipette_id, + volume=volume, + flow_rate=flow_rate, + command_note_adder=command_note_adder, + ) + except PipetteOverpressureError as e: + return DefinedErrorData( + public=OverpressureError( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + wrappedErrors=[ + ErrorOccurrence.from_failed( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + error=e, + ) + ], + errorInfo=location_if_error, + ), + state_update=StateUpdate().set_fluid_unknown(pipette_id=pipette_id), + ) + else: + return SuccessData( + public=BaseLiquidHandlingResult( + volume=volume_aspirated, + ), + state_update=StateUpdate().set_fluid_aspirated( + pipette_id=pipette_id, + fluid=AspiratedFluid(kind=FluidKind.LIQUID, volume=volume_aspirated), + ), + ) + + +async def dispense_in_place( + pipette_id: str, + volume: float, + flow_rate: float, + push_out: float | None, + location_if_error: ErrorLocationInfo, + pipetting: PipettingHandler, + model_utils: ModelUtils, +) -> SuccessData[BaseLiquidHandlingResult] | DefinedErrorData[OverpressureError]: + """Dispense-in-place as a microoperation.""" + try: + volume = await pipetting.dispense_in_place( + pipette_id=pipette_id, + volume=volume, + flow_rate=flow_rate, + push_out=push_out, + ) + except PipetteOverpressureError as e: + return DefinedErrorData( + public=OverpressureError( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + wrappedErrors=[ + ErrorOccurrence.from_failed( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + error=e, + ) + ], + errorInfo=location_if_error, + ), + state_update=StateUpdate().set_fluid_unknown(pipette_id=pipette_id), + ) + else: + return SuccessData( + public=BaseLiquidHandlingResult(volume=volume), + state_update=StateUpdate().set_fluid_ejected( + pipette_id=pipette_id, volume=volume + ), + ) + + +async def blow_out_in_place( + pipette_id: str, + flow_rate: float, + location_if_error: ErrorLocationInfo, + pipetting: PipettingHandler, + model_utils: ModelUtils, +) -> SuccessData[BaseModel] | DefinedErrorData[OverpressureError]: + """Execute a blow-out-in-place micro-operation.""" + try: + await pipetting.blow_out_in_place(pipette_id=pipette_id, flow_rate=flow_rate) + except PipetteOverpressureError as e: + return DefinedErrorData( + public=OverpressureError( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + wrappedErrors=[ + ErrorOccurrence.from_failed( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + error=e, + ) + ], + errorInfo=location_if_error, + ), + state_update=StateUpdate().set_fluid_unknown(pipette_id=pipette_id), + ) + else: + return SuccessData( + public=BaseModel(), + state_update=StateUpdate().set_fluid_empty(pipette_id=pipette_id), + ) diff --git a/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py b/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py index d427b38dc1e..cabcb2039eb 100644 --- a/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py +++ b/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py @@ -2,17 +2,23 @@ from __future__ import annotations from pydantic import BaseModel -from typing import TYPE_CHECKING, Optional, Type +from typing import TYPE_CHECKING, Optional, Type, Union from typing_extensions import Literal -from .pipetting_common import ( - PipetteIdMixin, +from .pipetting_common import OverpressureError, PipetteIdMixin, prepare_for_aspirate +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + DefinedErrorData, + SuccessData, ) -from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ..errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from ..execution.pipetting import PipettingHandler + from ..execution import PipettingHandler, GantryMover + from ..resources import ModelUtils + PrepareToAspirateCommandType = Literal["prepareToAspirate"] @@ -29,25 +35,57 @@ class PrepareToAspirateResult(BaseModel): pass +_ExecuteReturn = Union[ + SuccessData[PrepareToAspirateResult], + DefinedErrorData[OverpressureError], +] + + class PrepareToAspirateImplementation( - AbstractCommandImpl[ - PrepareToAspirateParams, SuccessData[PrepareToAspirateResult, None] - ] + AbstractCommandImpl[PrepareToAspirateParams, _ExecuteReturn] ): """Prepare for aspirate command implementation.""" - def __init__(self, pipetting: PipettingHandler, **kwargs: object) -> None: + def __init__( + self, + pipetting: PipettingHandler, + model_utils: ModelUtils, + gantry_mover: GantryMover, + **kwargs: object, + ) -> None: self._pipetting_handler = pipetting + self._model_utils = model_utils + self._gantry_mover = gantry_mover + + def _transform_result( + self, result: SuccessData[BaseModel] + ) -> SuccessData[PrepareToAspirateResult]: + return SuccessData( + public=PrepareToAspirateResult(), state_update=result.state_update + ) - async def execute( - self, params: PrepareToAspirateParams - ) -> SuccessData[PrepareToAspirateResult, None]: + async def execute(self, params: PrepareToAspirateParams) -> _ExecuteReturn: """Prepare the pipette to aspirate.""" - await self._pipetting_handler.prepare_for_aspirate( + current_position = await self._gantry_mover.get_position(params.pipetteId) + prepare_result = await prepare_for_aspirate( pipette_id=params.pipetteId, + pipetting=self._pipetting_handler, + model_utils=self._model_utils, + location_if_error={ + "retryLocation": ( + current_position.x, + current_position.y, + current_position.z, + ) + }, ) - - return SuccessData(public=PrepareToAspirateResult(), private=None) + if isinstance(prepare_result, DefinedErrorData): + return prepare_result + else: + return SuccessData( + public=PrepareToAspirateResult(), + state_update=prepare_result.state_update, + ) class PrepareToAspirate( diff --git a/api/src/opentrons/protocol_engine/commands/reload_labware.py b/api/src/opentrons/protocol_engine/commands/reload_labware.py index 884b8324d21..60230a1c6dd 100644 --- a/api/src/opentrons/protocol_engine/commands/reload_labware.py +++ b/api/src/opentrons/protocol_engine/commands/reload_labware.py @@ -6,9 +6,10 @@ from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ..errors.error_occurrence import ErrorOccurrence +from ..state.update_types import StateUpdate if TYPE_CHECKING: - from ..state import StateView + from ..state.state import StateView from ..execution import EquipmentHandler @@ -46,7 +47,7 @@ class ReloadLabwareResult(BaseModel): class ReloadLabwareImplementation( - AbstractCommandImpl[ReloadLabwareParams, SuccessData[ReloadLabwareResult, None]] + AbstractCommandImpl[ReloadLabwareParams, SuccessData[ReloadLabwareResult]] ): """Reload labware command implementation.""" @@ -58,18 +59,26 @@ def __init__( async def execute( self, params: ReloadLabwareParams - ) -> SuccessData[ReloadLabwareResult, None]: + ) -> SuccessData[ReloadLabwareResult]: """Reload the definition and calibration data for a specific labware.""" reloaded_labware = await self._equipment.reload_labware( labware_id=params.labwareId, ) + state_update = StateUpdate() + + state_update.set_labware_location( + labware_id=params.labwareId, + new_location=reloaded_labware.location, + new_offset_id=reloaded_labware.offsetId, + ) + return SuccessData( public=ReloadLabwareResult( labwareId=params.labwareId, offsetId=reloaded_labware.offsetId, ), - private=None, + state_update=state_update, ) diff --git a/api/src/opentrons/protocol_engine/commands/retract_axis.py b/api/src/opentrons/protocol_engine/commands/retract_axis.py index d989f1fd793..49020eb517e 100644 --- a/api/src/opentrons/protocol_engine/commands/retract_axis.py +++ b/api/src/opentrons/protocol_engine/commands/retract_axis.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal +from ..state import update_types from ..types import MotorAxis from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ..errors.error_occurrence import ErrorOccurrence @@ -38,7 +39,7 @@ class RetractAxisResult(BaseModel): class RetractAxisImplementation( - AbstractCommandImpl[RetractAxisParams, SuccessData[RetractAxisResult, None]] + AbstractCommandImpl[RetractAxisParams, SuccessData[RetractAxisResult]] ): """Retract Axis command implementation.""" @@ -47,10 +48,12 @@ def __init__(self, movement: MovementHandler, **kwargs: object) -> None: async def execute( self, params: RetractAxisParams - ) -> SuccessData[RetractAxisResult, None]: + ) -> SuccessData[RetractAxisResult]: """Retract the specified axis.""" + state_update = update_types.StateUpdate() await self._movement.retract_axis(axis=params.axis) - return SuccessData(public=RetractAxisResult(), private=None) + state_update.clear_all_pipette_locations() + return SuccessData(public=RetractAxisResult(), state_update=state_update) class RetractAxis(BaseCommand[RetractAxisParams, RetractAxisResult, ErrorOccurrence]): diff --git a/api/src/opentrons/protocol_engine/commands/robot/__init__.py b/api/src/opentrons/protocol_engine/commands/robot/__init__.py index ee78c1d4044..048fecd09fe 100644 --- a/api/src/opentrons/protocol_engine/commands/robot/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/robot/__init__.py @@ -1 +1,70 @@ """Robot movement commands.""" + +from .move_to import ( + MoveTo, + MoveToCreate, + MoveToParams, + MoveToResult, + MoveToCommandType, +) +from .move_axes_to import ( + MoveAxesTo, + MoveAxesToCreate, + MoveAxesToParams, + MoveAxesToResult, + MoveAxesToCommandType, +) +from .move_axes_relative import ( + MoveAxesRelative, + MoveAxesRelativeCreate, + MoveAxesRelativeParams, + MoveAxesRelativeResult, + MoveAxesRelativeCommandType, +) +from .open_gripper_jaw import ( + openGripperJaw, + openGripperJawCreate, + openGripperJawParams, + openGripperJawResult, + openGripperJawCommandType, +) +from .close_gripper_jaw import ( + closeGripperJaw, + closeGripperJawCreate, + closeGripperJawParams, + closeGripperJawResult, + closeGripperJawCommandType, +) + +__all__ = [ + # robot/moveTo + "MoveTo", + "MoveToCreate", + "MoveToParams", + "MoveToResult", + "MoveToCommandType", + # robot/moveAxesTo + "MoveAxesTo", + "MoveAxesToCreate", + "MoveAxesToParams", + "MoveAxesToResult", + "MoveAxesToCommandType", + # robot/moveAxesRelative + "MoveAxesRelative", + "MoveAxesRelativeCreate", + "MoveAxesRelativeParams", + "MoveAxesRelativeResult", + "MoveAxesRelativeCommandType", + # robot/openGripperJaw + "openGripperJaw", + "openGripperJawCreate", + "openGripperJawParams", + "openGripperJawResult", + "openGripperJawCommandType", + # robot/closeGripperJaw + "closeGripperJaw", + "closeGripperJawCreate", + "closeGripperJawParams", + "closeGripperJawResult", + "closeGripperJawCommandType", +] diff --git a/api/src/opentrons/protocol_engine/commands/robot/close_gripper_jaw.py b/api/src/opentrons/protocol_engine/commands/robot/close_gripper_jaw.py new file mode 100644 index 00000000000..965c6d2ec72 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/robot/close_gripper_jaw.py @@ -0,0 +1,79 @@ +"""Command models for opening a gripper jaw.""" +from __future__ import annotations +from typing import Literal, Type, Optional +from opentrons.hardware_control import HardwareControlAPI +from opentrons.protocol_engine.resources import ensure_ot3_hardware + +from pydantic import BaseModel, Field + +from ..command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, +) +from opentrons.protocol_engine.errors.error_occurrence import ErrorOccurrence + + +closeGripperJawCommandType = Literal["robot/closeGripperJaw"] + + +class closeGripperJawParams(BaseModel): + """Payload required to close a gripper.""" + + force: Optional[float] = Field( + default=None, + description="The force the gripper should use to hold the jaws, falls to default if none is provided.", + ) + + +class closeGripperJawResult(BaseModel): + """Result data from the execution of a closeGripperJaw command.""" + + pass + + +class closeGripperJawImplementation( + AbstractCommandImpl[closeGripperJawParams, SuccessData[closeGripperJawResult]] +): + """closeGripperJaw command implementation.""" + + def __init__( + self, + hardware_api: HardwareControlAPI, + **kwargs: object, + ) -> None: + self._hardware_api = hardware_api + + async def execute( + self, params: closeGripperJawParams + ) -> SuccessData[closeGripperJawResult]: + """Release the gripper.""" + ot3_hardware_api = ensure_ot3_hardware(self._hardware_api) + await ot3_hardware_api.grip(force_newtons=params.force) + return SuccessData( + public=closeGripperJawResult(), + ) + + +class closeGripperJaw( + BaseCommand[closeGripperJawParams, closeGripperJawResult, ErrorOccurrence] +): + """closeGripperJaw command model.""" + + commandType: closeGripperJawCommandType = "robot/closeGripperJaw" + params: closeGripperJawParams + result: Optional[closeGripperJawResult] + + _ImplementationCls: Type[ + closeGripperJawImplementation + ] = closeGripperJawImplementation + + +class closeGripperJawCreate(BaseCommandCreate[closeGripperJawParams]): + """closeGripperJaw command request model.""" + + commandType: closeGripperJawCommandType = "robot/closeGripperJaw" + params: closeGripperJawParams + + _CommandCls: Type[closeGripperJaw] = closeGripperJaw diff --git a/api/src/opentrons/protocol_engine/commands/robot/common.py b/api/src/opentrons/protocol_engine/commands/robot/common.py new file mode 100644 index 00000000000..1cd0b0d17b3 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/robot/common.py @@ -0,0 +1,18 @@ +"""Shared result types for robot API commands.""" +from pydantic import BaseModel, Field + +from typing import Dict +from opentrons.protocol_engine.types import MotorAxis + + +MotorAxisMapType = Dict[MotorAxis, float] +default_position = {ax: 0.0 for ax in MotorAxis} + + +class DestinationRobotPositionResult(BaseModel): + """The result dictionary of `MotorAxis` type.""" + + position: MotorAxisMapType = Field( + default=default_position, + description="The position of all axes on the robot. If no mount was provided, the last moved mount is used to determine the location.", + ) diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py b/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py new file mode 100644 index 00000000000..238229ebce6 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/robot/move_axes_relative.py @@ -0,0 +1,94 @@ +"""Command models for moving any robot axis relative.""" +from __future__ import annotations +from typing import Literal, Type, Optional, TYPE_CHECKING + +from pydantic import BaseModel, Field +from opentrons.hardware_control import HardwareControlAPI +from opentrons.protocol_engine.resources import ensure_ot3_hardware + +from .common import MotorAxisMapType, DestinationRobotPositionResult + +from ..command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, +) +from ...errors.error_occurrence import ErrorOccurrence + +if TYPE_CHECKING: + from opentrons.protocol_engine.execution import GantryMover + + +MoveAxesRelativeCommandType = Literal["robot/moveAxesRelative"] + + +class MoveAxesRelativeParams(BaseModel): + """Payload required to move axes relative to position.""" + + axis_map: MotorAxisMapType = Field( + ..., description="A dictionary mapping axes to relative movements in mm." + ) + speed: Optional[float] = Field( + default=None, + description="The max velocity to move the axes at. Will fall to hardware defaults if none provided.", + ) + + +class MoveAxesRelativeResult(DestinationRobotPositionResult): + """Result data from the execution of a MoveAxesRelative command.""" + + pass + + +class MoveAxesRelativeImplementation( + AbstractCommandImpl[MoveAxesRelativeParams, SuccessData[MoveAxesRelativeResult]] +): + """MoveAxesRelative command implementation.""" + + def __init__( + self, + gantry_mover: GantryMover, + hardware_api: HardwareControlAPI, + **kwargs: object, + ) -> None: + self._gantry_mover = gantry_mover + self._hardware_api = hardware_api + + async def execute( + self, params: MoveAxesRelativeParams + ) -> SuccessData[MoveAxesRelativeResult]: + """Move the axes on a flex a relative distance.""" + # TODO (lc 08-16-2024) implement `move_axes` for OT 2 hardware controller + # and then we can remove this validation. + ensure_ot3_hardware(self._hardware_api) + + current_position = await self._gantry_mover.move_axes( + axis_map=params.axis_map, speed=params.speed, relative_move=True + ) + return SuccessData( + public=MoveAxesRelativeResult(position=current_position), + ) + + +class MoveAxesRelative( + BaseCommand[MoveAxesRelativeParams, MoveAxesRelativeResult, ErrorOccurrence] +): + """MoveAxesRelative command model.""" + + commandType: MoveAxesRelativeCommandType = "robot/moveAxesRelative" + params: MoveAxesRelativeParams + result: Optional[MoveAxesRelativeResult] + + _ImplementationCls: Type[ + MoveAxesRelativeImplementation + ] = MoveAxesRelativeImplementation + + +class MoveAxesRelativeCreate(BaseCommandCreate[MoveAxesRelativeParams]): + """MoveAxesRelative command request model.""" + + commandType: MoveAxesRelativeCommandType = "robot/moveAxesRelative" + params: MoveAxesRelativeParams + + _CommandCls: Type[MoveAxesRelative] = MoveAxesRelative diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py new file mode 100644 index 00000000000..0d17d5f171f --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/robot/move_axes_to.py @@ -0,0 +1,91 @@ +"""Command models for moving any robot axis to an absolute position.""" +from __future__ import annotations +from typing import Literal, Optional, Type, TYPE_CHECKING +from pydantic import Field, BaseModel + +from opentrons.hardware_control import HardwareControlAPI +from opentrons.protocol_engine.resources import ensure_ot3_hardware + +from .common import MotorAxisMapType, DestinationRobotPositionResult +from ..command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, +) +from ...errors.error_occurrence import ErrorOccurrence + +if TYPE_CHECKING: + from opentrons.protocol_engine.execution import GantryMover + + +MoveAxesToCommandType = Literal["robot/moveAxesTo"] + + +class MoveAxesToParams(BaseModel): + """Payload required to move axes to absolute position.""" + + axis_map: MotorAxisMapType = Field( + ..., description="The specified axes to move to an absolute deck position with." + ) + critical_point: Optional[MotorAxisMapType] = Field( + default=None, description="The critical point to move the mount with." + ) + speed: Optional[float] = Field( + default=None, + description="The max velocity to move the axes at. Will fall to hardware defaults if none provided.", + ) + + +class MoveAxesToResult(DestinationRobotPositionResult): + """Result data from the execution of a MoveAxesTo command.""" + + pass + + +class MoveAxesToImplementation( + AbstractCommandImpl[MoveAxesToParams, SuccessData[MoveAxesToResult]] +): + """MoveAxesTo command implementation.""" + + def __init__( + self, + gantry_mover: GantryMover, + hardware_api: HardwareControlAPI, + **kwargs: object, + ) -> None: + self._gantry_mover = gantry_mover + self._hardware_api = hardware_api + + async def execute(self, params: MoveAxesToParams) -> SuccessData[MoveAxesToResult]: + """Move the axes on a flex an absolute distance.""" + # TODO (lc 08-16-2024) implement `move_axes` for OT 2 hardware controller + # and then we can remove this validation. + ensure_ot3_hardware(self._hardware_api) + current_position = await self._gantry_mover.move_axes( + axis_map=params.axis_map, + speed=params.speed, + critical_point=params.critical_point, + ) + return SuccessData( + public=MoveAxesToResult(position=current_position), + ) + + +class MoveAxesTo(BaseCommand[MoveAxesToParams, MoveAxesToResult, ErrorOccurrence]): + """MoveAxesTo command model.""" + + commandType: MoveAxesToCommandType = "robot/moveAxesTo" + params: MoveAxesToParams + result: Optional[MoveAxesToResult] + + _ImplementationCls: Type[MoveAxesToImplementation] = MoveAxesToImplementation + + +class MoveAxesToCreate(BaseCommandCreate[MoveAxesToParams]): + """MoveAxesTo command request model.""" + + commandType: MoveAxesToCommandType = "robot/moveAxesTo" + params: MoveAxesToParams + + _CommandCls: Type[MoveAxesTo] = MoveAxesTo diff --git a/api/src/opentrons/protocol_engine/commands/robot/move_to.py b/api/src/opentrons/protocol_engine/commands/robot/move_to.py new file mode 100644 index 00000000000..199d5be5079 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/robot/move_to.py @@ -0,0 +1,87 @@ +"""Command models for moving any robot mount to a destination point.""" +from __future__ import annotations +from typing import Literal, Type, Optional, TYPE_CHECKING + +from pydantic import BaseModel, Field +from opentrons.types import MountType + +from ..movement_common import DestinationPositionResult +from ..command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, +) +from opentrons.protocol_engine.types import DeckPoint +from opentrons.protocol_engine.errors.error_occurrence import ErrorOccurrence + + +if TYPE_CHECKING: + from opentrons.protocol_engine.execution import MovementHandler + + +MoveToCommandType = Literal["robot/moveTo"] + + +class MoveToParams(BaseModel): + """Payload required to move to a destination position.""" + + mount: MountType = Field( + ..., + description="The mount to move to the destination point.", + ) + destination: DeckPoint = Field( + ..., + description="X, Y and Z coordinates in mm from deck's origin location (left-front-bottom corner of work space)", + ) + speed: Optional[float] = Field( + default=None, + description="The max velocity to move the axes at. Will fall to hardware defaults if none provided.", + ) + + +class MoveToResult(DestinationPositionResult): + """Result data from the execution of a MoveTo command.""" + + pass + + +class MoveToImplementation( + AbstractCommandImpl[MoveToParams, SuccessData[MoveToResult]] +): + """MoveTo command implementation.""" + + def __init__( + self, + movement: MovementHandler, + **kwargs: object, + ) -> None: + self._movement = movement + + async def execute(self, params: MoveToParams) -> SuccessData[MoveToResult]: + """Move to a given destination on a flex.""" + x, y, z = await self._movement.move_mount_to( + mount=params.mount, destination=params.destination, speed=params.speed + ) + return SuccessData( + public=MoveToResult(position=DeckPoint(x=x, y=y, z=z)), + ) + + +class MoveTo(BaseCommand[MoveToParams, MoveToResult, ErrorOccurrence]): + """MoveTo command model.""" + + commandType: MoveToCommandType = "robot/moveTo" + params: MoveToParams + result: Optional[MoveToResult] + + _ImplementationCls: Type[MoveToImplementation] = MoveToImplementation + + +class MoveToCreate(BaseCommandCreate[MoveToParams]): + """MoveTo command request model.""" + + commandType: MoveToCommandType = "robot/moveTo" + params: MoveToParams + + _CommandCls: Type[MoveTo] = MoveTo diff --git a/api/src/opentrons/protocol_engine/commands/robot/open_gripper_jaw.py b/api/src/opentrons/protocol_engine/commands/robot/open_gripper_jaw.py new file mode 100644 index 00000000000..22aa1debd42 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/robot/open_gripper_jaw.py @@ -0,0 +1,77 @@ +"""Command models for opening a gripper jaw.""" +from __future__ import annotations +from typing import Literal, Type, Optional +from opentrons.hardware_control import HardwareControlAPI +from opentrons.protocol_engine.resources import ensure_ot3_hardware + +from pydantic import BaseModel + +from ..command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, +) +from opentrons.protocol_engine.errors.error_occurrence import ErrorOccurrence + + +openGripperJawCommandType = Literal["robot/openGripperJaw"] + + +class openGripperJawParams(BaseModel): + """Payload required to release a gripper.""" + + pass + + +class openGripperJawResult(BaseModel): + """Result data from the execution of a openGripperJaw command.""" + + pass + + +class openGripperJawImplementation( + AbstractCommandImpl[openGripperJawParams, SuccessData[openGripperJawResult]] +): + """openGripperJaw command implementation.""" + + def __init__( + self, + hardware_api: HardwareControlAPI, + **kwargs: object, + ) -> None: + self._hardware_api = hardware_api + + async def execute( + self, params: openGripperJawParams + ) -> SuccessData[openGripperJawResult]: + """Release the gripper.""" + ot3_hardware_api = ensure_ot3_hardware(self._hardware_api) + + await ot3_hardware_api.home_gripper_jaw() + return SuccessData( + public=openGripperJawResult(), + ) + + +class openGripperJaw( + BaseCommand[openGripperJawParams, openGripperJawResult, ErrorOccurrence] +): + """openGripperJaw command model.""" + + commandType: openGripperJawCommandType = "robot/openGripperJaw" + params: openGripperJawParams + result: Optional[openGripperJawResult] + + _ImplementationCls: Type[ + openGripperJawImplementation + ] = openGripperJawImplementation + + +class openGripperJawCreate(BaseCommandCreate[openGripperJawParams]): + """openGripperJaw command request model.""" + + commandType: openGripperJawCommandType = "robot/openGripperJaw" + params: openGripperJawParams + + _CommandCls: Type[openGripperJaw] = openGripperJaw diff --git a/api/src/opentrons/protocol_engine/commands/save_position.py b/api/src/opentrons/protocol_engine/commands/save_position.py index 988e4b762a7..4bc208d1421 100644 --- a/api/src/opentrons/protocol_engine/commands/save_position.py +++ b/api/src/opentrons/protocol_engine/commands/save_position.py @@ -46,7 +46,7 @@ class SavePositionResult(BaseModel): class SavePositionImplementation( - AbstractCommandImpl[SavePositionParams, SuccessData[SavePositionResult, None]] + AbstractCommandImpl[SavePositionParams, SuccessData[SavePositionResult]] ): """Save position command implementation.""" @@ -61,7 +61,7 @@ def __init__( async def execute( self, params: SavePositionParams - ) -> SuccessData[SavePositionResult, None]: + ) -> SuccessData[SavePositionResult]: """Check the requested pipette's current position.""" position_id = self._model_utils.ensure_id(params.positionId) fail_on_not_homed = ( @@ -76,7 +76,6 @@ async def execute( positionId=position_id, position=DeckPoint(x=x, y=y, z=z), ), - private=None, ) diff --git a/api/src/opentrons/protocol_engine/commands/set_rail_lights.py b/api/src/opentrons/protocol_engine/commands/set_rail_lights.py index 6235e0d9bb6..09254dbe966 100644 --- a/api/src/opentrons/protocol_engine/commands/set_rail_lights.py +++ b/api/src/opentrons/protocol_engine/commands/set_rail_lights.py @@ -29,7 +29,7 @@ class SetRailLightsResult(BaseModel): class SetRailLightsImplementation( - AbstractCommandImpl[SetRailLightsParams, SuccessData[SetRailLightsResult, None]] + AbstractCommandImpl[SetRailLightsParams, SuccessData[SetRailLightsResult]] ): """setRailLights command implementation.""" @@ -38,10 +38,12 @@ def __init__(self, rail_lights: RailLightsHandler, **kwargs: object) -> None: async def execute( self, params: SetRailLightsParams - ) -> SuccessData[SetRailLightsResult, None]: + ) -> SuccessData[SetRailLightsResult]: """Dispatch a set lights command setting the state of the rail lights.""" await self._rail_lights.set_rail_lights(params.on) - return SuccessData(public=SetRailLightsResult(), private=None) + return SuccessData( + public=SetRailLightsResult(), + ) class SetRailLights( diff --git a/api/src/opentrons/protocol_engine/commands/set_status_bar.py b/api/src/opentrons/protocol_engine/commands/set_status_bar.py index cb83aa56ce2..2e1483f6d93 100644 --- a/api/src/opentrons/protocol_engine/commands/set_status_bar.py +++ b/api/src/opentrons/protocol_engine/commands/set_status_bar.py @@ -49,7 +49,7 @@ class SetStatusBarResult(BaseModel): class SetStatusBarImplementation( - AbstractCommandImpl[SetStatusBarParams, SuccessData[SetStatusBarResult, None]] + AbstractCommandImpl[SetStatusBarParams, SuccessData[SetStatusBarResult]] ): """setStatusBar command implementation.""" @@ -58,12 +58,14 @@ def __init__(self, status_bar: StatusBarHandler, **kwargs: object) -> None: async def execute( self, params: SetStatusBarParams - ) -> SuccessData[SetStatusBarResult, None]: + ) -> SuccessData[SetStatusBarResult]: """Execute the setStatusBar command.""" if not self._status_bar.status_bar_should_not_be_changed(): state = _animation_to_status_bar_state(params.animation) await self._status_bar.set_status_bar(state) - return SuccessData(public=SetStatusBarResult(), private=None) + return SuccessData( + public=SetStatusBarResult(), + ) class SetStatusBar( diff --git a/api/src/opentrons/protocol_engine/commands/temperature_module/deactivate.py b/api/src/opentrons/protocol_engine/commands/temperature_module/deactivate.py index 979195933b2..e56c98e6e30 100644 --- a/api/src/opentrons/protocol_engine/commands/temperature_module/deactivate.py +++ b/api/src/opentrons/protocol_engine/commands/temperature_module/deactivate.py @@ -9,7 +9,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler DeactivateTemperatureCommandType = Literal["temperatureModule/deactivate"] @@ -27,7 +27,7 @@ class DeactivateTemperatureResult(BaseModel): class DeactivateTemperatureImpl( AbstractCommandImpl[ - DeactivateTemperatureParams, SuccessData[DeactivateTemperatureResult, None] + DeactivateTemperatureParams, SuccessData[DeactivateTemperatureResult] ] ): """Execution implementation of a Temperature Module's deactivate command.""" @@ -43,7 +43,7 @@ def __init__( async def execute( self, params: DeactivateTemperatureParams - ) -> SuccessData[DeactivateTemperatureResult, None]: + ) -> SuccessData[DeactivateTemperatureResult]: """Deactivate a Temperature Module.""" # Allow propagation of ModuleNotLoadedError and WrongModuleTypeError. module_substate = self._state_view.modules.get_temperature_module_substate( @@ -57,7 +57,9 @@ async def execute( if temp_hardware_module is not None: await temp_hardware_module.deactivate() - return SuccessData(public=DeactivateTemperatureResult(), private=None) + return SuccessData( + public=DeactivateTemperatureResult(), + ) class DeactivateTemperature( diff --git a/api/src/opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py b/api/src/opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py index 4302773722b..6d65bf47bb0 100644 --- a/api/src/opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py +++ b/api/src/opentrons/protocol_engine/commands/temperature_module/set_target_temperature.py @@ -9,7 +9,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler SetTargetTemperatureCommandType = Literal["temperatureModule/setTargetTemperature"] @@ -34,7 +34,7 @@ class SetTargetTemperatureResult(BaseModel): class SetTargetTemperatureImpl( AbstractCommandImpl[ - SetTargetTemperatureParams, SuccessData[SetTargetTemperatureResult, None] + SetTargetTemperatureParams, SuccessData[SetTargetTemperatureResult] ] ): """Execution implementation of a Temperature Module's set temperature command.""" @@ -50,7 +50,7 @@ def __init__( async def execute( self, params: SetTargetTemperatureParams - ) -> SuccessData[SetTargetTemperatureResult, None]: + ) -> SuccessData[SetTargetTemperatureResult]: """Set a Temperature Module's target temperature.""" # Allow propagation of ModuleNotLoadedError and WrongModuleTypeError. module_substate = self._state_view.modules.get_temperature_module_substate( @@ -69,7 +69,6 @@ async def execute( await temp_hardware_module.start_set_temperature(celsius=validated_temp) return SuccessData( public=SetTargetTemperatureResult(targetTemperature=validated_temp), - private=None, ) diff --git a/api/src/opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py b/api/src/opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py index 9abd6d13179..fa7784de141 100644 --- a/api/src/opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py +++ b/api/src/opentrons/protocol_engine/commands/temperature_module/wait_for_temperature.py @@ -9,7 +9,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler WaitForTemperatureCommandType = Literal["temperatureModule/waitForTemperature"] @@ -35,9 +35,7 @@ class WaitForTemperatureResult(BaseModel): class WaitForTemperatureImpl( - AbstractCommandImpl[ - WaitForTemperatureParams, SuccessData[WaitForTemperatureResult, None] - ] + AbstractCommandImpl[WaitForTemperatureParams, SuccessData[WaitForTemperatureResult]] ): """Execution implementation of Temperature Module's wait for temperature command.""" @@ -52,7 +50,7 @@ def __init__( async def execute( self, params: WaitForTemperatureParams - ) -> SuccessData[WaitForTemperatureResult, None]: + ) -> SuccessData[WaitForTemperatureResult]: """Wait for a Temperature Module's target temperature.""" # Allow propagation of ModuleNotLoadedError and WrongModuleTypeError. module_substate = self._state_view.modules.get_temperature_module_substate( @@ -74,7 +72,9 @@ async def execute( await temp_hardware_module.await_temperature( awaiting_temperature=target_temp ) - return SuccessData(public=WaitForTemperatureResult(), private=None) + return SuccessData( + public=WaitForTemperatureResult(), + ) class WaitForTemperature( diff --git a/api/src/opentrons/protocol_engine/commands/thermocycler/__init__.py b/api/src/opentrons/protocol_engine/commands/thermocycler/__init__.py index b0ffdd53ce9..60e5c62591c 100644 --- a/api/src/opentrons/protocol_engine/commands/thermocycler/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/thermocycler/__init__.py @@ -73,6 +73,16 @@ RunProfileCreate, ) +from .run_extended_profile import ( + RunExtendedProfileCommandType, + RunExtendedProfileParams, + RunExtendedProfileResult, + RunExtendedProfile, + RunExtendedProfileCreate, + ProfileCycle, + ProfileStep, +) + __all__ = [ # Set target block temperature command models @@ -130,4 +140,13 @@ "RunProfileResult", "RunProfile", "RunProfileCreate", + # Run extended profile command models. + "RunExtendedProfileCommandType", + "RunExtendedProfileParams", + "RunExtendedProfileStepParams", + "RunExtendedProfileResult", + "RunExtendedProfile", + "RunExtendedProfileCreate", + "ProfileCycle", + "ProfileStep", ] diff --git a/api/src/opentrons/protocol_engine/commands/thermocycler/close_lid.py b/api/src/opentrons/protocol_engine/commands/thermocycler/close_lid.py index de7768c4c7a..578a5d6b7a7 100644 --- a/api/src/opentrons/protocol_engine/commands/thermocycler/close_lid.py +++ b/api/src/opentrons/protocol_engine/commands/thermocycler/close_lid.py @@ -7,10 +7,11 @@ from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData from ...errors.error_occurrence import ErrorOccurrence +from ...state import update_types from opentrons.protocol_engine.types import MotorAxis if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler, MovementHandler @@ -27,9 +28,7 @@ class CloseLidResult(BaseModel): """Result data from closing a Thermocycler's lid.""" -class CloseLidImpl( - AbstractCommandImpl[CloseLidParams, SuccessData[CloseLidResult, None]] -): +class CloseLidImpl(AbstractCommandImpl[CloseLidParams, SuccessData[CloseLidResult]]): """Execution implementation of a Thermocycler's close lid command.""" def __init__( @@ -43,10 +42,10 @@ def __init__( self._equipment = equipment self._movement = movement - async def execute( - self, params: CloseLidParams - ) -> SuccessData[CloseLidResult, None]: + async def execute(self, params: CloseLidParams) -> SuccessData[CloseLidResult]: """Close a Thermocycler's lid.""" + state_update = update_types.StateUpdate() + thermocycler_state = self._state_view.modules.get_thermocycler_module_substate( params.moduleId ) @@ -61,11 +60,12 @@ async def execute( MotorAxis.Y, ] + self._state_view.motion.get_robot_mount_axes() await self._movement.home(axes=axes_to_home) + state_update.clear_all_pipette_locations() if thermocycler_hardware is not None: await thermocycler_hardware.close() - return SuccessData(public=CloseLidResult(), private=None) + return SuccessData(public=CloseLidResult(), state_update=state_update) class CloseLid(BaseCommand[CloseLidParams, CloseLidResult, ErrorOccurrence]): diff --git a/api/src/opentrons/protocol_engine/commands/thermocycler/deactivate_block.py b/api/src/opentrons/protocol_engine/commands/thermocycler/deactivate_block.py index a24706a54c3..67199577d53 100644 --- a/api/src/opentrons/protocol_engine/commands/thermocycler/deactivate_block.py +++ b/api/src/opentrons/protocol_engine/commands/thermocycler/deactivate_block.py @@ -9,7 +9,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler @@ -27,7 +27,7 @@ class DeactivateBlockResult(BaseModel): class DeactivateBlockImpl( - AbstractCommandImpl[DeactivateBlockParams, SuccessData[DeactivateBlockResult, None]] + AbstractCommandImpl[DeactivateBlockParams, SuccessData[DeactivateBlockResult]] ): """Execution implementation of a Thermocycler's deactivate block command.""" @@ -42,7 +42,7 @@ def __init__( async def execute( self, params: DeactivateBlockParams - ) -> SuccessData[DeactivateBlockResult, None]: + ) -> SuccessData[DeactivateBlockResult]: """Unset a Thermocycler's target block temperature.""" thermocycler_state = self._state_view.modules.get_thermocycler_module_substate( params.moduleId @@ -54,7 +54,9 @@ async def execute( if thermocycler_hardware is not None: await thermocycler_hardware.deactivate_block() - return SuccessData(public=DeactivateBlockResult(), private=None) + return SuccessData( + public=DeactivateBlockResult(), + ) class DeactivateBlock( diff --git a/api/src/opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py b/api/src/opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py index 4f76d2c3d3e..9c3541efb12 100644 --- a/api/src/opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py +++ b/api/src/opentrons/protocol_engine/commands/thermocycler/deactivate_lid.py @@ -9,7 +9,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler @@ -27,7 +27,7 @@ class DeactivateLidResult(BaseModel): class DeactivateLidImpl( - AbstractCommandImpl[DeactivateLidParams, SuccessData[DeactivateLidResult, None]] + AbstractCommandImpl[DeactivateLidParams, SuccessData[DeactivateLidResult]] ): """Execution implementation of a Thermocycler's deactivate lid command.""" @@ -42,7 +42,7 @@ def __init__( async def execute( self, params: DeactivateLidParams - ) -> SuccessData[DeactivateLidResult, None]: + ) -> SuccessData[DeactivateLidResult]: """Unset a Thermocycler's target lid temperature.""" thermocycler_state = self._state_view.modules.get_thermocycler_module_substate( params.moduleId @@ -54,7 +54,9 @@ async def execute( if thermocycler_hardware is not None: await thermocycler_hardware.deactivate_lid() - return SuccessData(public=DeactivateLidResult(), private=None) + return SuccessData( + public=DeactivateLidResult(), + ) class DeactivateLid( diff --git a/api/src/opentrons/protocol_engine/commands/thermocycler/open_lid.py b/api/src/opentrons/protocol_engine/commands/thermocycler/open_lid.py index 0facf0d4ec3..3df32d1ec99 100644 --- a/api/src/opentrons/protocol_engine/commands/thermocycler/open_lid.py +++ b/api/src/opentrons/protocol_engine/commands/thermocycler/open_lid.py @@ -6,11 +6,12 @@ from pydantic import BaseModel, Field from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData +from ...state import update_types from ...errors.error_occurrence import ErrorOccurrence from opentrons.protocol_engine.types import MotorAxis if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler, MovementHandler @@ -27,7 +28,7 @@ class OpenLidResult(BaseModel): """Result data from opening a Thermocycler's lid.""" -class OpenLidImpl(AbstractCommandImpl[OpenLidParams, SuccessData[OpenLidResult, None]]): +class OpenLidImpl(AbstractCommandImpl[OpenLidParams, SuccessData[OpenLidResult]]): """Execution implementation of a Thermocycler's open lid command.""" def __init__( @@ -41,8 +42,10 @@ def __init__( self._equipment = equipment self._movement = movement - async def execute(self, params: OpenLidParams) -> SuccessData[OpenLidResult, None]: + async def execute(self, params: OpenLidParams) -> SuccessData[OpenLidResult]: """Open a Thermocycler's lid.""" + state_update = update_types.StateUpdate() + thermocycler_state = self._state_view.modules.get_thermocycler_module_substate( params.moduleId ) @@ -57,11 +60,12 @@ async def execute(self, params: OpenLidParams) -> SuccessData[OpenLidResult, Non MotorAxis.Y, ] + self._state_view.motion.get_robot_mount_axes() await self._movement.home(axes=axes_to_home) + state_update.clear_all_pipette_locations() if thermocycler_hardware is not None: await thermocycler_hardware.open() - return SuccessData(public=OpenLidResult(), private=None) + return SuccessData(public=OpenLidResult(), state_update=state_update) class OpenLid(BaseCommand[OpenLidParams, OpenLidResult, ErrorOccurrence]): diff --git a/api/src/opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py b/api/src/opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py new file mode 100644 index 00000000000..6f63aed8fe3 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/thermocycler/run_extended_profile.py @@ -0,0 +1,165 @@ +"""Command models to execute a Thermocycler profile.""" +from __future__ import annotations +from typing import List, Optional, TYPE_CHECKING, overload, Union +from typing_extensions import Literal, Type + +from pydantic import BaseModel, Field + +from opentrons.hardware_control.modules.types import ThermocyclerStep, ThermocyclerCycle + +from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData +from ...errors.error_occurrence import ErrorOccurrence + +if TYPE_CHECKING: + from opentrons.protocol_engine.state.state import StateView + from opentrons.protocol_engine.execution import EquipmentHandler + from opentrons.protocol_engine.state.module_substates.thermocycler_module_substate import ( + ThermocyclerModuleSubState, + ) + + +RunExtendedProfileCommandType = Literal["thermocycler/runExtendedProfile"] + + +class ProfileStep(BaseModel): + """An individual step in a Thermocycler extended profile.""" + + celsius: float = Field(..., description="Target temperature in °C.") + holdSeconds: float = Field( + ..., description="Time to hold target temperature in seconds." + ) + + +class ProfileCycle(BaseModel): + """An individual cycle in a Thermocycler extended profile.""" + + steps: List[ProfileStep] = Field(..., description="Steps to repeat.") + repetitions: int = Field(..., description="Number of times to repeat the steps.") + + +class RunExtendedProfileParams(BaseModel): + """Input parameters for an individual Thermocycler profile step.""" + + moduleId: str = Field(..., description="Unique ID of the Thermocycler.") + profileElements: List[Union[ProfileStep, ProfileCycle]] = Field( + ..., + description="Elements of the profile. Each can be either a step or a cycle.", + ) + blockMaxVolumeUl: Optional[float] = Field( + None, + description="Amount of liquid in uL of the most-full well" + " in labware loaded onto the thermocycler.", + ) + + +class RunExtendedProfileResult(BaseModel): + """Result data from running a Thermocycler profile.""" + + +def _transform_profile_step( + step: ProfileStep, thermocycler_state: ThermocyclerModuleSubState +) -> ThermocyclerStep: + return ThermocyclerStep( + temperature=thermocycler_state.validate_target_block_temperature(step.celsius), + hold_time_seconds=step.holdSeconds, + ) + + +@overload +def _transform_profile_element( + element: ProfileStep, thermocycler_state: ThermocyclerModuleSubState +) -> ThermocyclerStep: + ... + + +@overload +def _transform_profile_element( + element: ProfileCycle, thermocycler_state: ThermocyclerModuleSubState +) -> ThermocyclerCycle: + ... + + +def _transform_profile_element( + element: Union[ProfileStep, ProfileCycle], + thermocycler_state: ThermocyclerModuleSubState, +) -> Union[ThermocyclerStep, ThermocyclerCycle]: + if isinstance(element, ProfileStep): + return _transform_profile_step(element, thermocycler_state) + else: + return ThermocyclerCycle( + steps=[ + _transform_profile_step(step, thermocycler_state) + for step in element.steps + ], + repetitions=element.repetitions, + ) + + +class RunExtendedProfileImpl( + AbstractCommandImpl[RunExtendedProfileParams, SuccessData[RunExtendedProfileResult]] +): + """Execution implementation of a Thermocycler's run profile command.""" + + def __init__( + self, + state_view: StateView, + equipment: EquipmentHandler, + **unused_dependencies: object, + ) -> None: + self._state_view = state_view + self._equipment = equipment + + async def execute( + self, params: RunExtendedProfileParams + ) -> SuccessData[RunExtendedProfileResult]: + """Run a Thermocycler profile.""" + thermocycler_state = self._state_view.modules.get_thermocycler_module_substate( + params.moduleId + ) + thermocycler_hardware = self._equipment.get_module_hardware_api( + thermocycler_state.module_id + ) + + profile = [ + _transform_profile_element(element, thermocycler_state) + for element in params.profileElements + ] + target_volume: Optional[float] + if params.blockMaxVolumeUl is not None: + target_volume = thermocycler_state.validate_max_block_volume( + params.blockMaxVolumeUl + ) + else: + target_volume = None + + if thermocycler_hardware is not None: + # TODO(jbl 2022-06-27) hardcoded constant 1 for `repetitions` should be + # moved from HardwareControlAPI to the Python ProtocolContext + await thermocycler_hardware.execute_profile( + profile=profile, volume=target_volume + ) + + return SuccessData( + public=RunExtendedProfileResult(), + ) + + +class RunExtendedProfile( + BaseCommand[RunExtendedProfileParams, RunExtendedProfileResult, ErrorOccurrence] +): + """A command to execute a Thermocycler profile run.""" + + commandType: RunExtendedProfileCommandType = "thermocycler/runExtendedProfile" + params: RunExtendedProfileParams + result: Optional[RunExtendedProfileResult] + + _ImplementationCls: Type[RunExtendedProfileImpl] = RunExtendedProfileImpl + + +class RunExtendedProfileCreate(BaseCommandCreate[RunExtendedProfileParams]): + """A request to execute a Thermocycler profile run.""" + + commandType: RunExtendedProfileCommandType = "thermocycler/runExtendedProfile" + params: RunExtendedProfileParams + + _CommandCls: Type[RunExtendedProfile] = RunExtendedProfile diff --git a/api/src/opentrons/protocol_engine/commands/thermocycler/run_profile.py b/api/src/opentrons/protocol_engine/commands/thermocycler/run_profile.py index af387e3324e..02aa7ad93e2 100644 --- a/api/src/opentrons/protocol_engine/commands/thermocycler/run_profile.py +++ b/api/src/opentrons/protocol_engine/commands/thermocycler/run_profile.py @@ -11,7 +11,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler @@ -47,7 +47,7 @@ class RunProfileResult(BaseModel): class RunProfileImpl( - AbstractCommandImpl[RunProfileParams, SuccessData[RunProfileResult, None]] + AbstractCommandImpl[RunProfileParams, SuccessData[RunProfileResult]] ): """Execution implementation of a Thermocycler's run profile command.""" @@ -60,9 +60,7 @@ def __init__( self._state_view = state_view self._equipment = equipment - async def execute( - self, params: RunProfileParams - ) -> SuccessData[RunProfileResult, None]: + async def execute(self, params: RunProfileParams) -> SuccessData[RunProfileResult]: """Run a Thermocycler profile.""" thermocycler_state = self._state_view.modules.get_thermocycler_module_substate( params.moduleId @@ -96,7 +94,9 @@ async def execute( steps=steps, repetitions=1, volume=target_volume ) - return SuccessData(public=RunProfileResult(), private=None) + return SuccessData( + public=RunProfileResult(), + ) class RunProfile(BaseCommand[RunProfileParams, RunProfileResult, ErrorOccurrence]): diff --git a/api/src/opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py b/api/src/opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py index 796fb15c024..b69bb15ea90 100644 --- a/api/src/opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py +++ b/api/src/opentrons/protocol_engine/commands/thermocycler/set_target_block_temperature.py @@ -9,7 +9,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler @@ -46,7 +46,7 @@ class SetTargetBlockTemperatureResult(BaseModel): class SetTargetBlockTemperatureImpl( AbstractCommandImpl[ SetTargetBlockTemperatureParams, - SuccessData[SetTargetBlockTemperatureResult, None], + SuccessData[SetTargetBlockTemperatureResult], ] ): """Execution implementation of a Thermocycler's set block temperature command.""" @@ -63,7 +63,7 @@ def __init__( async def execute( self, params: SetTargetBlockTemperatureParams, - ) -> SuccessData[SetTargetBlockTemperatureResult, None]: + ) -> SuccessData[SetTargetBlockTemperatureResult]: """Set a Thermocycler's target block temperature.""" thermocycler_state = self._state_view.modules.get_thermocycler_module_substate( params.moduleId @@ -97,7 +97,6 @@ async def execute( public=SetTargetBlockTemperatureResult( targetBlockTemperature=target_temperature ), - private=None, ) diff --git a/api/src/opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py b/api/src/opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py index a819d6a3759..37217e047ae 100644 --- a/api/src/opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py +++ b/api/src/opentrons/protocol_engine/commands/thermocycler/set_target_lid_temperature.py @@ -9,7 +9,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler @@ -34,7 +34,7 @@ class SetTargetLidTemperatureResult(BaseModel): class SetTargetLidTemperatureImpl( AbstractCommandImpl[ - SetTargetLidTemperatureParams, SuccessData[SetTargetLidTemperatureResult, None] + SetTargetLidTemperatureParams, SuccessData[SetTargetLidTemperatureResult] ] ): """Execution implementation of a Thermocycler's set lid temperature command.""" @@ -51,7 +51,7 @@ def __init__( async def execute( self, params: SetTargetLidTemperatureParams, - ) -> SuccessData[SetTargetLidTemperatureResult, None]: + ) -> SuccessData[SetTargetLidTemperatureResult]: """Set a Thermocycler's target lid temperature.""" thermocycler_state = self._state_view.modules.get_thermocycler_module_substate( params.moduleId @@ -70,7 +70,6 @@ async def execute( public=SetTargetLidTemperatureResult( targetLidTemperature=target_temperature ), - private=None, ) diff --git a/api/src/opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py b/api/src/opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py index 40a8241adaa..8e8c9b1a4ec 100644 --- a/api/src/opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py +++ b/api/src/opentrons/protocol_engine/commands/thermocycler/wait_for_block_temperature.py @@ -9,7 +9,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler @@ -28,7 +28,7 @@ class WaitForBlockTemperatureResult(BaseModel): class WaitForBlockTemperatureImpl( AbstractCommandImpl[ - WaitForBlockTemperatureParams, SuccessData[WaitForBlockTemperatureResult, None] + WaitForBlockTemperatureParams, SuccessData[WaitForBlockTemperatureResult] ] ): """Execution implementation of Thermocycler's wait for block temperature command.""" @@ -45,7 +45,7 @@ def __init__( async def execute( self, params: WaitForBlockTemperatureParams, - ) -> SuccessData[WaitForBlockTemperatureResult, None]: + ) -> SuccessData[WaitForBlockTemperatureResult]: """Wait for a Thermocycler's target block temperature.""" thermocycler_state = self._state_view.modules.get_thermocycler_module_substate( params.moduleId @@ -61,7 +61,9 @@ async def execute( if thermocycler_hardware is not None: await thermocycler_hardware.wait_for_block_target() - return SuccessData(public=WaitForBlockTemperatureResult(), private=None) + return SuccessData( + public=WaitForBlockTemperatureResult(), + ) class WaitForBlockTemperature( diff --git a/api/src/opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py b/api/src/opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py index 026aed14ad6..95e5fbc4f0a 100644 --- a/api/src/opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py +++ b/api/src/opentrons/protocol_engine/commands/thermocycler/wait_for_lid_temperature.py @@ -9,7 +9,7 @@ from ...errors.error_occurrence import ErrorOccurrence if TYPE_CHECKING: - from opentrons.protocol_engine.state import StateView + from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import EquipmentHandler @@ -28,7 +28,7 @@ class WaitForLidTemperatureResult(BaseModel): class WaitForLidTemperatureImpl( AbstractCommandImpl[ - WaitForLidTemperatureParams, SuccessData[WaitForLidTemperatureResult, None] + WaitForLidTemperatureParams, SuccessData[WaitForLidTemperatureResult] ] ): """Execution implementation of Thermocycler's wait for lid temperature command.""" @@ -45,7 +45,7 @@ def __init__( async def execute( self, params: WaitForLidTemperatureParams, - ) -> SuccessData[WaitForLidTemperatureResult, None]: + ) -> SuccessData[WaitForLidTemperatureResult]: """Wait for a Thermocycler's lid temperature.""" thermocycler_state = self._state_view.modules.get_thermocycler_module_substate( params.moduleId @@ -61,7 +61,9 @@ async def execute( if thermocycler_hardware is not None: await thermocycler_hardware.wait_for_lid_target() - return SuccessData(public=WaitForLidTemperatureResult(), private=None) + return SuccessData( + public=WaitForLidTemperatureResult(), + ) class WaitForLidTemperature( diff --git a/api/src/opentrons/protocol_engine/commands/touch_tip.py b/api/src/opentrons/protocol_engine/commands/touch_tip.py index 858be81842c..2d7c507d321 100644 --- a/api/src/opentrons/protocol_engine/commands/touch_tip.py +++ b/api/src/opentrons/protocol_engine/commands/touch_tip.py @@ -1,22 +1,35 @@ """Touch tip command request, result, and implementation models.""" + from __future__ import annotations from pydantic import Field from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal +from opentrons.types import Point + from ..errors import TouchTipDisabledError, LabwareIsTipRackError from ..types import DeckPoint -from .command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData -from ..errors.error_occurrence import ErrorOccurrence +from .command import ( + AbstractCommandImpl, + BaseCommand, + BaseCommandCreate, + SuccessData, + DefinedErrorData, +) from .pipetting_common import ( PipetteIdMixin, +) +from .movement_common import ( WellLocationMixin, DestinationPositionResult, + StallOrCollisionError, + move_to_well, ) if TYPE_CHECKING: from ..execution import MovementHandler, GantryMover - from ..state import StateView + from ..state.state import StateView + from ..resources.model_utils import ModelUtils TouchTipCommandType = Literal["touchTip"] @@ -48,7 +61,10 @@ class TouchTipResult(DestinationPositionResult): class TouchTipImplementation( - AbstractCommandImpl[TouchTipParams, SuccessData[TouchTipResult, None]] + AbstractCommandImpl[ + TouchTipParams, + SuccessData[TouchTipResult] | DefinedErrorData[StallOrCollisionError], + ] ): """Touch tip command implementation.""" @@ -57,15 +73,17 @@ def __init__( state_view: StateView, movement: MovementHandler, gantry_mover: GantryMover, + model_utils: ModelUtils, **kwargs: object, ) -> None: self._state_view = state_view self._movement = movement self._gantry_mover = gantry_mover + self._model_utils = model_utils async def execute( self, params: TouchTipParams - ) -> SuccessData[TouchTipResult, None]: + ) -> SuccessData[TouchTipResult] | DefinedErrorData[StallOrCollisionError]: """Touch tip to sides of a well using the requested pipette.""" pipette_id = params.pipetteId labware_id = params.labwareId @@ -79,12 +97,16 @@ async def execute( if self._state_view.labware.is_tiprack(labware_id): raise LabwareIsTipRackError("Cannot touch tip on tip rack") - center_point = await self._movement.move_to_well( + center_result = await move_to_well( + movement=self._movement, + model_utils=self._model_utils, pipette_id=pipette_id, labware_id=labware_id, well_name=well_name, well_location=params.wellLocation, ) + if isinstance(center_result, DefinedErrorData): + return center_result touch_speed = self._state_view.pipettes.get_movement_speed( pipette_id, params.speed @@ -95,21 +117,35 @@ async def execute( labware_id=labware_id, well_name=well_name, radius=params.radius, - center_point=center_point, + center_point=Point( + center_result.public.position.x, + center_result.public.position.y, + center_result.public.position.z, + ), ) - x, y, z = await self._gantry_mover.move_to( + final_point = await self._gantry_mover.move_to( pipette_id=pipette_id, waypoints=touch_waypoints, speed=touch_speed, ) + final_deck_point = DeckPoint.construct( + x=final_point.x, y=final_point.y, z=final_point.z + ) + state_update = center_result.state_update.set_pipette_location( + pipette_id=pipette_id, + new_labware_id=labware_id, + new_well_name=well_name, + new_deck_point=final_deck_point, + ) return SuccessData( - public=TouchTipResult(position=DeckPoint(x=x, y=y, z=z)), private=None + public=TouchTipResult(position=final_deck_point), + state_update=state_update, ) -class TouchTip(BaseCommand[TouchTipParams, TouchTipResult, ErrorOccurrence]): +class TouchTip(BaseCommand[TouchTipParams, TouchTipResult, StallOrCollisionError]): """Touch up tip command model.""" commandType: TouchTipCommandType = "touchTip" diff --git a/api/src/opentrons/protocol_engine/commands/unsafe/__init__.py b/api/src/opentrons/protocol_engine/commands/unsafe/__init__.py index 2875d38cb8e..eb138d89914 100644 --- a/api/src/opentrons/protocol_engine/commands/unsafe/__init__.py +++ b/api/src/opentrons/protocol_engine/commands/unsafe/__init__.py @@ -23,6 +23,32 @@ UpdatePositionEstimatorsCreate, ) +from .unsafe_engage_axes import ( + UnsafeEngageAxesCommandType, + UnsafeEngageAxesParams, + UnsafeEngageAxesResult, + UnsafeEngageAxes, + UnsafeEngageAxesCreate, +) + +from .unsafe_ungrip_labware import ( + UnsafeUngripLabwareCommandType, + UnsafeUngripLabwareParams, + UnsafeUngripLabwareResult, + UnsafeUngripLabware, + UnsafeUngripLabwareCreate, +) + + +from .unsafe_place_labware import ( + UnsafePlaceLabwareCommandType, + UnsafePlaceLabwareParams, + UnsafePlaceLabwareResult, + UnsafePlaceLabware, + UnsafePlaceLabwareCreate, +) + + __all__ = [ # Unsafe blow-out-in-place command models "UnsafeBlowOutInPlaceCommandType", @@ -42,4 +68,22 @@ "UpdatePositionEstimatorsResult", "UpdatePositionEstimators", "UpdatePositionEstimatorsCreate", + # Unsafe engage axes + "UnsafeEngageAxesCommandType", + "UnsafeEngageAxesParams", + "UnsafeEngageAxesResult", + "UnsafeEngageAxes", + "UnsafeEngageAxesCreate", + # Unsafe ungrip labware + "UnsafeUngripLabwareCommandType", + "UnsafeUngripLabwareParams", + "UnsafeUngripLabwareResult", + "UnsafeUngripLabware", + "UnsafeUngripLabwareCreate", + # Unsafe place labware + "UnsafePlaceLabwareCommandType", + "UnsafePlaceLabwareParams", + "UnsafePlaceLabwareResult", + "UnsafePlaceLabware", + "UnsafePlaceLabwareCreate", ] diff --git a/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py index cbf17ff1026..4c767625782 100644 --- a/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_blow_out_in_place.py @@ -10,13 +10,14 @@ from ..pipetting_common import PipetteIdMixin, FlowRateMixin from ...resources import ensure_ot3_hardware from ...errors.error_occurrence import ErrorOccurrence +from ...state import update_types from opentrons.hardware_control import HardwareControlAPI from opentrons.hardware_control.types import Axis if TYPE_CHECKING: from ...execution import PipettingHandler - from ...state import StateView + from ...state.state import StateView UnsafeBlowOutInPlaceCommandType = Literal["unsafe/blowOutInPlace"] @@ -36,7 +37,7 @@ class UnsafeBlowOutInPlaceResult(BaseModel): class UnsafeBlowOutInPlaceImplementation( AbstractCommandImpl[ - UnsafeBlowOutInPlaceParams, SuccessData[UnsafeBlowOutInPlaceResult, None] + UnsafeBlowOutInPlaceParams, SuccessData[UnsafeBlowOutInPlaceResult] ] ): """UnsafeBlowOutInPlace command implementation.""" @@ -54,7 +55,7 @@ def __init__( async def execute( self, params: UnsafeBlowOutInPlaceParams - ) -> SuccessData[UnsafeBlowOutInPlaceResult, None]: + ) -> SuccessData[UnsafeBlowOutInPlaceResult]: """Blow-out without moving the pipette even when position is unknown.""" ot3_hardware_api = ensure_ot3_hardware(self._hardware_api) pipette_location = self._state_view.motion.get_pipette_location( @@ -66,8 +67,12 @@ async def execute( await self._pipetting.blow_out_in_place( pipette_id=params.pipetteId, flow_rate=params.flowRate ) + state_update = update_types.StateUpdate() + state_update.set_fluid_empty(pipette_id=params.pipetteId) - return SuccessData(public=UnsafeBlowOutInPlaceResult(), private=None) + return SuccessData( + public=UnsafeBlowOutInPlaceResult(), state_update=state_update + ) class UnsafeBlowOutInPlace( diff --git a/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py index 2cb3fa78dd8..5aa4e292f63 100644 --- a/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py +++ b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_drop_tip_in_place.py @@ -1,5 +1,6 @@ """Command models to drop tip in place while plunger positions are unknown.""" from __future__ import annotations +from opentrons.protocol_engine.state.update_types import StateUpdate from pydantic import Field, BaseModel from typing import TYPE_CHECKING, Optional, Type from typing_extensions import Literal @@ -14,7 +15,7 @@ if TYPE_CHECKING: from ...execution import TipHandler - from ...state import StateView + from ...state.state import StateView UnsafeDropTipInPlaceCommandType = Literal["unsafe/dropTipInPlace"] @@ -41,7 +42,7 @@ class UnsafeDropTipInPlaceResult(BaseModel): class UnsafeDropTipInPlaceImplementation( AbstractCommandImpl[ - UnsafeDropTipInPlaceParams, SuccessData[UnsafeDropTipInPlaceResult, None] + UnsafeDropTipInPlaceParams, SuccessData[UnsafeDropTipInPlaceResult] ] ): """Unsafe drop tip in place command implementation.""" @@ -59,7 +60,7 @@ def __init__( async def execute( self, params: UnsafeDropTipInPlaceParams - ) -> SuccessData[UnsafeDropTipInPlaceResult, None]: + ) -> SuccessData[UnsafeDropTipInPlaceResult]: """Drop a tip using the requested pipette, even if the plunger position is not known.""" ot3_hardware_api = ensure_ot3_hardware(self._hardware_api) pipette_location = self._state_view.motion.get_pipette_location( @@ -72,7 +73,15 @@ async def execute( pipette_id=params.pipetteId, home_after=params.homeAfter ) - return SuccessData(public=UnsafeDropTipInPlaceResult(), private=None) + state_update = StateUpdate() + state_update.update_pipette_tip_state( + pipette_id=params.pipetteId, tip_geometry=None + ) + state_update.set_fluid_unknown(pipette_id=params.pipetteId) + + return SuccessData( + public=UnsafeDropTipInPlaceResult(), state_update=state_update + ) class UnsafeDropTipInPlace( diff --git a/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py new file mode 100644 index 00000000000..4f80db24f42 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_engage_axes.py @@ -0,0 +1,82 @@ +"""Update position estimators payload, result, and implementaiton.""" + +from __future__ import annotations +from pydantic import BaseModel, Field +from typing import TYPE_CHECKING, Optional, List, Type +from typing_extensions import Literal + +from ...types import MotorAxis +from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData +from ...errors.error_occurrence import ErrorOccurrence +from ...resources import ensure_ot3_hardware + +from opentrons.hardware_control import HardwareControlAPI + +if TYPE_CHECKING: + from ...execution import GantryMover + + +UnsafeEngageAxesCommandType = Literal["unsafe/engageAxes"] + + +class UnsafeEngageAxesParams(BaseModel): + """Payload required for an UnsafeEngageAxes command.""" + + axes: List[MotorAxis] = Field(..., description="The axes for which to enable.") + + +class UnsafeEngageAxesResult(BaseModel): + """Result data from the execution of an UnsafeEngageAxes command.""" + + +class UnsafeEngageAxesImplementation( + AbstractCommandImpl[ + UnsafeEngageAxesParams, + SuccessData[UnsafeEngageAxesResult], + ] +): + """Enable axes command implementation.""" + + def __init__( + self, + hardware_api: HardwareControlAPI, + gantry_mover: GantryMover, + **kwargs: object, + ) -> None: + self._hardware_api = hardware_api + self._gantry_mover = gantry_mover + + async def execute( + self, params: UnsafeEngageAxesParams + ) -> SuccessData[UnsafeEngageAxesResult]: + """Enable exes.""" + ot3_hardware_api = ensure_ot3_hardware(self._hardware_api) + await ot3_hardware_api.engage_axes( + self._gantry_mover.motor_axes_to_present_hardware_axes(params.axes) + ) + return SuccessData( + public=UnsafeEngageAxesResult(), + ) + + +class UnsafeEngageAxes( + BaseCommand[UnsafeEngageAxesParams, UnsafeEngageAxesResult, ErrorOccurrence] +): + """UnsafeEngageAxes command model.""" + + commandType: UnsafeEngageAxesCommandType = "unsafe/engageAxes" + params: UnsafeEngageAxesParams + result: Optional[UnsafeEngageAxesResult] + + _ImplementationCls: Type[ + UnsafeEngageAxesImplementation + ] = UnsafeEngageAxesImplementation + + +class UnsafeEngageAxesCreate(BaseCommandCreate[UnsafeEngageAxesParams]): + """UnsafeEngageAxes command request model.""" + + commandType: UnsafeEngageAxesCommandType = "unsafe/engageAxes" + params: UnsafeEngageAxesParams + + _CommandCls: Type[UnsafeEngageAxes] = UnsafeEngageAxes diff --git a/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py new file mode 100644 index 00000000000..c69cea29243 --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_place_labware.py @@ -0,0 +1,208 @@ +"""Place labware payload, result, and implementaiton.""" + +from __future__ import annotations +from typing import TYPE_CHECKING, Optional, Type +from typing_extensions import Literal + +from opentrons_shared_data.labware.types import LabwareUri +from opentrons_shared_data.labware.labware_definition import LabwareDefinition +from pydantic import BaseModel, Field + +from opentrons.hardware_control.types import Axis, OT3Mount +from opentrons.motion_planning.waypoints import get_gripper_labware_placement_waypoints +from opentrons.protocol_engine.errors.exceptions import ( + CannotPerformGripperAction, + GripperNotAttachedError, +) +from opentrons.types import Point + +from ...types import ( + DeckSlotLocation, + ModuleModel, + OnDeckLabwareLocation, +) +from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData +from ...errors.error_occurrence import ErrorOccurrence +from ...resources import ensure_ot3_hardware + +from opentrons.hardware_control import HardwareControlAPI, OT3HardwareControlAPI + +if TYPE_CHECKING: + from ...state.state import StateView + from ...execution.equipment import EquipmentHandler + + +UnsafePlaceLabwareCommandType = Literal["unsafe/placeLabware"] + + +class UnsafePlaceLabwareParams(BaseModel): + """Payload required for an UnsafePlaceLabware command.""" + + labwareURI: str = Field(..., description="Labware URI for labware.") + location: OnDeckLabwareLocation = Field( + ..., description="Where to place the labware." + ) + + +class UnsafePlaceLabwareResult(BaseModel): + """Result data from the execution of an UnsafePlaceLabware command.""" + + +class UnsafePlaceLabwareImplementation( + AbstractCommandImpl[ + UnsafePlaceLabwareParams, + SuccessData[UnsafePlaceLabwareResult], + ] +): + """The UnsafePlaceLabware command implementation.""" + + def __init__( + self, + hardware_api: HardwareControlAPI, + state_view: StateView, + equipment: EquipmentHandler, + **kwargs: object, + ) -> None: + self._hardware_api = hardware_api + self._state_view = state_view + self._equipment = equipment + + async def execute( + self, params: UnsafePlaceLabwareParams + ) -> SuccessData[UnsafePlaceLabwareResult]: + """Place Labware. + + This command is used only when the gripper is in the middle of moving + labware but is interrupted before completing the move. (i.e., the e-stop + is pressed, get into error recovery, etc). + + Unlike the `moveLabware` command, where you pick a source and destination + location, this command takes the labwareURI of the labware to be moved + and location to move it to. + + """ + ot3api = ensure_ot3_hardware(self._hardware_api) + if not ot3api.has_gripper(): + raise GripperNotAttachedError("No gripper found to perform labware place.") + + if ot3api.gripper_jaw_can_home(): + raise CannotPerformGripperAction( + "Cannot place labware when gripper is not gripping." + ) + + location = self._state_view.geometry.ensure_valid_gripper_location( + params.location, + ) + + definition = self._state_view.labware.get_definition_by_uri( + # todo(mm, 2024-11-07): This is an unsafe cast from untrusted input. + # We need a str -> LabwareUri parse/validate function. + LabwareUri(params.labwareURI) + ) + + # todo(mm, 2024-11-06): This is only correct in the special case of an + # absorbance reader lid. Its definition currently puts the offsets for *itself* + # in the property that's normally meant for offsets for its *children.* + final_offsets = self._state_view.labware.get_child_gripper_offsets( + labware_definition=definition, slot_name=None + ) + drop_offset = ( + Point( + final_offsets.dropOffset.x, + final_offsets.dropOffset.y, + final_offsets.dropOffset.z, + ) + if final_offsets + else None + ) + + if isinstance(params.location, DeckSlotLocation): + self._state_view.addressable_areas.raise_if_area_not_in_deck_configuration( + params.location.slotName.id + ) + + # This is an absorbance reader, move the lid to its dock (staging area). + if isinstance(location, DeckSlotLocation): + module = self._state_view.modules.get_by_slot(location.slotName) + if module and module.model == ModuleModel.ABSORBANCE_READER_V1: + location = self._state_view.modules.absorbance_reader_dock_location( + module.id + ) + + # NOTE: When the estop is pressed, the gantry loses position, lets use + # the encoders to sync position. + # Ideally, we'd do a full home, but this command is used when + # the gripper is holding the plate reader, and a full home would + # bang it into the right window. + await ot3api.home(axes=[Axis.Z_L, Axis.Z_R, Axis.Z_G]) + await ot3api.engage_axes([Axis.X, Axis.Y]) + await ot3api.update_axis_position_estimations([Axis.X, Axis.Y]) + + # Place the labware down + await self._start_movement(ot3api, definition, location, drop_offset) + + return SuccessData(public=UnsafePlaceLabwareResult()) + + async def _start_movement( + self, + ot3api: OT3HardwareControlAPI, + labware_definition: LabwareDefinition, + location: OnDeckLabwareLocation, + drop_offset: Optional[Point], + ) -> None: + gripper_homed_position = await ot3api.gantry_position( + mount=OT3Mount.GRIPPER, + refresh=True, + ) + + to_labware_center = self._state_view.geometry.get_labware_grip_point( + labware_definition=labware_definition, location=location + ) + + movement_waypoints = get_gripper_labware_placement_waypoints( + to_labware_center=to_labware_center, + gripper_home_z=gripper_homed_position.z, + drop_offset=drop_offset, + ) + + # start movement + for waypoint_data in movement_waypoints: + if waypoint_data.jaw_open: + if waypoint_data.dropping: + # This `disengage_axes` step is important in order to engage + # the electronic brake on the Z axis of the gripper. The brake + # has a stronger holding force on the axis than the hold current, + # and prevents the axis from spuriously dropping when e.g. the notch + # on the side of a falling tiprack catches the jaw. + await ot3api.disengage_axes([Axis.Z_G]) + await ot3api.ungrip() + if waypoint_data.dropping: + # We lost the position estimation after disengaging the axis, so + # it is necessary to home it next + await ot3api.home_z(OT3Mount.GRIPPER) + await ot3api.move_to( + mount=OT3Mount.GRIPPER, abs_position=waypoint_data.position + ) + + +class UnsafePlaceLabware( + BaseCommand[UnsafePlaceLabwareParams, UnsafePlaceLabwareResult, ErrorOccurrence] +): + """UnsafePlaceLabware command model.""" + + commandType: UnsafePlaceLabwareCommandType = "unsafe/placeLabware" + params: UnsafePlaceLabwareParams + result: Optional[UnsafePlaceLabwareResult] + + _ImplementationCls: Type[ + UnsafePlaceLabwareImplementation + ] = UnsafePlaceLabwareImplementation + + +class UnsafePlaceLabwareCreate(BaseCommandCreate[UnsafePlaceLabwareParams]): + """UnsafePlaceLabware command request model.""" + + commandType: UnsafePlaceLabwareCommandType = "unsafe/placeLabware" + params: UnsafePlaceLabwareParams + + _CommandCls: Type[UnsafePlaceLabware] = UnsafePlaceLabware diff --git a/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py new file mode 100644 index 00000000000..6f8f5b71fce --- /dev/null +++ b/api/src/opentrons/protocol_engine/commands/unsafe/unsafe_ungrip_labware.py @@ -0,0 +1,77 @@ +"""Ungrip labware payload, result, and implementaiton.""" + +from __future__ import annotations + +from opentrons.hardware_control.types import Axis +from opentrons.protocol_engine.errors.exceptions import GripperNotAttachedError +from pydantic import BaseModel +from typing import Optional, Type +from typing_extensions import Literal + +from ..command import AbstractCommandImpl, BaseCommand, BaseCommandCreate, SuccessData +from ...errors.error_occurrence import ErrorOccurrence +from ...resources import ensure_ot3_hardware + +from opentrons.hardware_control import HardwareControlAPI + + +UnsafeUngripLabwareCommandType = Literal["unsafe/ungripLabware"] + + +class UnsafeUngripLabwareParams(BaseModel): + """Payload required for an UngripLabware command.""" + + +class UnsafeUngripLabwareResult(BaseModel): + """Result data from the execution of an UngripLabware command.""" + + +class UnsafeUngripLabwareImplementation( + AbstractCommandImpl[ + UnsafeUngripLabwareParams, + SuccessData[UnsafeUngripLabwareResult], + ] +): + """Ungrip labware command implementation.""" + + def __init__( + self, + hardware_api: HardwareControlAPI, + **kwargs: object, + ) -> None: + self._hardware_api = hardware_api + + async def execute( + self, params: UnsafeUngripLabwareParams + ) -> SuccessData[UnsafeUngripLabwareResult]: + """Ungrip Labware.""" + ot3_hardware_api = ensure_ot3_hardware(self._hardware_api) + if not ot3_hardware_api.has_gripper(): + raise GripperNotAttachedError("No gripper found to perform ungrip.") + await ot3_hardware_api.home([Axis.G]) + return SuccessData( + public=UnsafeUngripLabwareResult(), + ) + + +class UnsafeUngripLabware( + BaseCommand[UnsafeUngripLabwareParams, UnsafeUngripLabwareResult, ErrorOccurrence] +): + """UnsafeUngripLabware command model.""" + + commandType: UnsafeUngripLabwareCommandType = "unsafe/ungripLabware" + params: UnsafeUngripLabwareParams + result: Optional[UnsafeUngripLabwareResult] + + _ImplementationCls: Type[ + UnsafeUngripLabwareImplementation + ] = UnsafeUngripLabwareImplementation + + +class UnsafeUngripLabwareCreate(BaseCommandCreate[UnsafeUngripLabwareParams]): + """UnsafeEngageAxes command request model.""" + + commandType: UnsafeUngripLabwareCommandType = "unsafe/ungripLabware" + params: UnsafeUngripLabwareParams + + _CommandCls: Type[UnsafeUngripLabware] = UnsafeUngripLabware diff --git a/api/src/opentrons/protocol_engine/commands/unsafe/update_position_estimators.py b/api/src/opentrons/protocol_engine/commands/unsafe/update_position_estimators.py index 96be2eb8551..6b050d6472f 100644 --- a/api/src/opentrons/protocol_engine/commands/unsafe/update_position_estimators.py +++ b/api/src/opentrons/protocol_engine/commands/unsafe/update_position_estimators.py @@ -23,7 +23,11 @@ class UpdatePositionEstimatorsParams(BaseModel): """Payload required for an UpdatePositionEstimators command.""" axes: List[MotorAxis] = Field( - ..., description="The axes for which to update the position estimators." + ..., + description=( + "The axes for which to update the position estimators." + " Any axes that are not physically present will be ignored." + ), ) @@ -34,7 +38,7 @@ class UpdatePositionEstimatorsResult(BaseModel): class UpdatePositionEstimatorsImplementation( AbstractCommandImpl[ UpdatePositionEstimatorsParams, - SuccessData[UpdatePositionEstimatorsResult, None], + SuccessData[UpdatePositionEstimatorsResult], ] ): """Update position estimators command implementation.""" @@ -50,16 +54,15 @@ def __init__( async def execute( self, params: UpdatePositionEstimatorsParams - ) -> SuccessData[UpdatePositionEstimatorsResult, None]: + ) -> SuccessData[UpdatePositionEstimatorsResult]: """Update axis position estimators from their encoders.""" ot3_hardware_api = ensure_ot3_hardware(self._hardware_api) await ot3_hardware_api.update_axis_position_estimations( - [ - self._gantry_mover.motor_axis_to_hardware_axis(axis) - for axis in params.axes - ] + self._gantry_mover.motor_axes_to_present_hardware_axes(params.axes) + ) + return SuccessData( + public=UpdatePositionEstimatorsResult(), ) - return SuccessData(public=UpdatePositionEstimatorsResult(), private=None) class UpdatePositionEstimators( diff --git a/api/src/opentrons/protocol_engine/commands/verify_tip_presence.py b/api/src/opentrons/protocol_engine/commands/verify_tip_presence.py index 9816e03cf33..e0412022e85 100644 --- a/api/src/opentrons/protocol_engine/commands/verify_tip_presence.py +++ b/api/src/opentrons/protocol_engine/commands/verify_tip_presence.py @@ -36,9 +36,7 @@ class VerifyTipPresenceResult(BaseModel): class VerifyTipPresenceImplementation( - AbstractCommandImpl[ - VerifyTipPresenceParams, SuccessData[VerifyTipPresenceResult, None] - ] + AbstractCommandImpl[VerifyTipPresenceParams, SuccessData[VerifyTipPresenceResult]] ): """VerifyTipPresence command implementation.""" @@ -51,7 +49,7 @@ def __init__( async def execute( self, params: VerifyTipPresenceParams - ) -> SuccessData[VerifyTipPresenceResult, None]: + ) -> SuccessData[VerifyTipPresenceResult]: """Verify if tip presence is as expected for the requested pipette.""" pipette_id = params.pipetteId expected_state = params.expectedState @@ -67,7 +65,9 @@ async def execute( follow_singular_sensor=follow_singular_sensor, ) - return SuccessData(public=VerifyTipPresenceResult(), private=None) + return SuccessData( + public=VerifyTipPresenceResult(), + ) class VerifyTipPresence( diff --git a/api/src/opentrons/protocol_engine/commands/wait_for_duration.py b/api/src/opentrons/protocol_engine/commands/wait_for_duration.py index df1eae28aa4..04f8693386e 100644 --- a/api/src/opentrons/protocol_engine/commands/wait_for_duration.py +++ b/api/src/opentrons/protocol_engine/commands/wait_for_duration.py @@ -29,7 +29,7 @@ class WaitForDurationResult(BaseModel): class WaitForDurationImplementation( - AbstractCommandImpl[WaitForDurationParams, SuccessData[WaitForDurationResult, None]] + AbstractCommandImpl[WaitForDurationParams, SuccessData[WaitForDurationResult]] ): """Wait for duration command implementation.""" @@ -38,10 +38,12 @@ def __init__(self, run_control: RunControlHandler, **kwargs: object) -> None: async def execute( self, params: WaitForDurationParams - ) -> SuccessData[WaitForDurationResult, None]: + ) -> SuccessData[WaitForDurationResult]: """Wait for a duration of time.""" await self._run_control.wait_for_duration(params.seconds) - return SuccessData(public=WaitForDurationResult(), private=None) + return SuccessData( + public=WaitForDurationResult(), + ) class WaitForDuration( diff --git a/api/src/opentrons/protocol_engine/commands/wait_for_resume.py b/api/src/opentrons/protocol_engine/commands/wait_for_resume.py index c6036f852e2..f5066d52521 100644 --- a/api/src/opentrons/protocol_engine/commands/wait_for_resume.py +++ b/api/src/opentrons/protocol_engine/commands/wait_for_resume.py @@ -30,7 +30,7 @@ class WaitForResumeResult(BaseModel): class WaitForResumeImplementation( - AbstractCommandImpl[WaitForResumeParams, SuccessData[WaitForResumeResult, None]] + AbstractCommandImpl[WaitForResumeParams, SuccessData[WaitForResumeResult]] ): """Wait for resume command implementation.""" @@ -39,10 +39,12 @@ def __init__(self, run_control: RunControlHandler, **kwargs: object) -> None: async def execute( self, params: WaitForResumeParams - ) -> SuccessData[WaitForResumeResult, None]: + ) -> SuccessData[WaitForResumeResult]: """Dispatch a PauseAction to the store to pause the protocol.""" await self._run_control.wait_for_resume() - return SuccessData(public=WaitForResumeResult(), private=None) + return SuccessData( + public=WaitForResumeResult(), + ) class WaitForResume( diff --git a/api/src/opentrons/protocol_engine/create_protocol_engine.py b/api/src/opentrons/protocol_engine/create_protocol_engine.py index bf2157c83a5..5c21c70efef 100644 --- a/api/src/opentrons/protocol_engine/create_protocol_engine.py +++ b/api/src/opentrons/protocol_engine/create_protocol_engine.py @@ -5,13 +5,25 @@ from opentrons.hardware_control import HardwareControlAPI from opentrons.hardware_control.types import DoorState -from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryPolicy +from opentrons.protocol_engine.execution.error_recovery_hardware_state_synchronizer import ( + ErrorRecoveryHardwareStateSynchronizer, +) +from opentrons.protocol_engine.resources.labware_data_provider import ( + LabwareDataProvider, +) from opentrons.util.async_helpers import async_context_manager_in_thread + from opentrons_shared_data.robot import load as load_robot +from .actions.action_dispatcher import ActionDispatcher +from .error_recovery_policy import ErrorRecoveryPolicy +from .execution.door_watcher import DoorWatcher +from .execution.hardware_stopper import HardwareStopper +from .plugins import PluginStarter from .protocol_engine import ProtocolEngine -from .resources import DeckDataProvider, ModuleDataProvider -from .state import Config, StateStore +from .resources import DeckDataProvider, ModuleDataProvider, FileProvider, ModelUtils +from .state.config import Config +from .state.state import StateStore from .types import PostRunHardwareState, DeckConfigurationType from .engine_support import create_run_orchestrator @@ -25,6 +37,7 @@ async def create_protocol_engine( error_recovery_policy: ErrorRecoveryPolicy, load_fixed_trash: bool = False, deck_configuration: typing.Optional[DeckConfigurationType] = None, + file_provider: typing.Optional[FileProvider] = None, notify_publishers: typing.Optional[typing.Callable[[], None]] = None, ) -> ProtocolEngine: """Create a ProtocolEngine instance. @@ -36,6 +49,7 @@ async def create_protocol_engine( See documentation on `ErrorRecoveryPolicy`. load_fixed_trash: Automatically load fixed trash labware in engine. deck_configuration: The initial deck configuration the engine will be instantiated with. + file_provider: Provides access to robot server file writing procedures for protocol output. notify_publishers: Notifies robot server publishers of internal state change. """ deck_data = DeckDataProvider(config.deck_type) @@ -46,6 +60,7 @@ async def create_protocol_engine( module_calibration_offsets = ModuleDataProvider.load_module_calibrations() robot_definition = load_robot(config.robot_type) + state_store = StateStore( config=config, deck_definition=deck_definition, @@ -57,18 +72,51 @@ async def create_protocol_engine( deck_configuration=deck_configuration, notify_publishers=notify_publishers, ) - - return ProtocolEngine( - state_store=state_store, + hardware_state_synchronizer = ErrorRecoveryHardwareStateSynchronizer( + hardware_api, state_store + ) + action_dispatcher = ActionDispatcher(state_store) + action_dispatcher.add_handler(hardware_state_synchronizer) + plugin_starter = PluginStarter(state_store, action_dispatcher) + model_utils = ModelUtils() + hardware_stopper = HardwareStopper(hardware_api, state_store) + door_watcher = DoorWatcher(state_store, hardware_api, action_dispatcher) + module_data_provider = ModuleDataProvider() + file_provider = file_provider or FileProvider() + + pe = ProtocolEngine( hardware_api=hardware_api, + state_store=state_store, + action_dispatcher=action_dispatcher, + plugin_starter=plugin_starter, + model_utils=model_utils, + hardware_stopper=hardware_stopper, + door_watcher=door_watcher, + module_data_provider=module_data_provider, + file_provider=file_provider, ) + # todo(mm, 2024-11-08): This is a quick hack to support the absorbance reader, which + # expects the engine to have this special labware definition available. It would be + # cleaner for the `loadModule` command to do this I/O and insert the definition + # into state. That gets easier after https://opentrons.atlassian.net/browse/EXEC-756. + # + # NOTE: This needs to stay in sync with LabwareView.get_absorbance_reader_lid_definition(). + pe.add_labware_definition( + await LabwareDataProvider().get_labware_definition( + "opentrons_flex_lid_absorbance_plate_reader_module", "opentrons", 1 + ) + ) + + return pe + @contextlib.contextmanager def create_protocol_engine_in_thread( hardware_api: HardwareControlAPI, config: Config, deck_configuration: typing.Optional[DeckConfigurationType], + file_provider: typing.Optional[FileProvider], error_recovery_policy: ErrorRecoveryPolicy, drop_tips_after_run: bool, post_run_hardware_state: PostRunHardwareState, @@ -96,6 +144,7 @@ def create_protocol_engine_in_thread( with async_context_manager_in_thread( _protocol_engine( hardware_api, + file_provider, config, deck_configuration, error_recovery_policy, @@ -113,6 +162,7 @@ def create_protocol_engine_in_thread( @contextlib.asynccontextmanager async def _protocol_engine( hardware_api: HardwareControlAPI, + file_provider: typing.Optional[FileProvider], config: Config, deck_configuration: typing.Optional[DeckConfigurationType], error_recovery_policy: ErrorRecoveryPolicy, @@ -122,6 +172,7 @@ async def _protocol_engine( ) -> typing.AsyncGenerator[ProtocolEngine, None]: protocol_engine = await create_protocol_engine( hardware_api=hardware_api, + file_provider=file_provider, config=config, error_recovery_policy=error_recovery_policy, load_fixed_trash=load_fixed_trash, diff --git a/api/src/opentrons/protocol_engine/engine_support.py b/api/src/opentrons/protocol_engine/engine_support.py index 9d6bdcbdd69..b822b97914d 100644 --- a/api/src/opentrons/protocol_engine/engine_support.py +++ b/api/src/opentrons/protocol_engine/engine_support.py @@ -6,7 +6,8 @@ def create_run_orchestrator( - hardware_api: HardwareControlAPI, protocol_engine: ProtocolEngine + hardware_api: HardwareControlAPI, + protocol_engine: ProtocolEngine, ) -> RunOrchestrator: """Create a RunOrchestrator instance.""" return RunOrchestrator( diff --git a/api/src/opentrons/protocol_engine/error_recovery_policy.py b/api/src/opentrons/protocol_engine/error_recovery_policy.py index f9f39d99f4d..fcc8a2ffef5 100644 --- a/api/src/opentrons/protocol_engine/error_recovery_policy.py +++ b/api/src/opentrons/protocol_engine/error_recovery_policy.py @@ -26,10 +26,20 @@ class ErrorRecoveryType(enum.Enum): """ WAIT_FOR_RECOVERY = enum.auto() - """Stop and wait for the error to be recovered from manually.""" + """Enter interactive error recovery mode.""" - IGNORE_AND_CONTINUE = enum.auto() - """Continue with the run, as if the command never failed.""" + CONTINUE_WITH_ERROR = enum.auto() + """Continue without interruption, carrying on from whatever error state the failed + command left the engine in. + + This is like `ProtocolEngine.resume_from_recovery(reconcile_false_positive=False)`. + """ + + ASSUME_FALSE_POSITIVE_AND_CONTINUE = enum.auto() + """Continue without interruption, acting as if the underlying error was a false positive. + + This is like `ProtocolEngine.resume_from_recovery(reconcile_false_positive=True)`. + """ class ErrorRecoveryPolicy(Protocol): @@ -40,6 +50,7 @@ class ErrorRecoveryPolicy(Protocol): and return an appropriate `ErrorRecoveryType`. Args: + config: The config of the calling `ProtocolEngine`. failed_command: The command that failed, in its final `status=="failed"` state. defined_error_data: If the command failed with a defined error, details about that error. If the command failed with an undefined error, `None`. diff --git a/api/src/opentrons/protocol_engine/errors/__init__.py b/api/src/opentrons/protocol_engine/errors/__init__.py index 639648e820f..8148ce132e6 100644 --- a/api/src/opentrons/protocol_engine/errors/__init__.py +++ b/api/src/opentrons/protocol_engine/errors/__init__.py @@ -8,6 +8,7 @@ InvalidSpecificationForRobotTypeError, InvalidLoadPipetteSpecsError, TipNotAttachedError, + PickUpTipTipNotAttachedError, TipAttachedError, CommandDoesNotExistError, LabwareNotLoadedError, @@ -54,6 +55,7 @@ InvalidTargetTemperatureError, InvalidBlockVolumeError, InvalidHoldTimeError, + InvalidWavelengthError, CannotPerformModuleAction, PauseNotAllowedError, ResumeFromRecoveryNotAllowedError, @@ -69,6 +71,16 @@ InvalidAxisForRobotType, NotSupportedOnRobotType, CommandNotAllowedError, + InvalidLiquidHeightFound, + LiquidHeightUnknownError, + IncompleteLabwareDefinitionError, + IncompleteWellDefinitionError, + OperationLocationNotInWellError, + InvalidDispenseVolumeError, + StorageLimitReachedError, + InvalidLiquidError, + LiquidClassDoesNotExistError, + LiquidClassRedefinitionError, ) from .error_occurrence import ErrorOccurrence, ProtocolCommandFailedError @@ -82,6 +94,7 @@ "InvalidSpecificationForRobotTypeError", "InvalidLoadPipetteSpecsError", "TipNotAttachedError", + "PickUpTipTipNotAttachedError", "TipAttachedError", "CommandDoesNotExistError", "LabwareNotLoadedError", @@ -128,6 +141,8 @@ "InvalidTargetSpeedError", "InvalidBlockVolumeError", "InvalidHoldTimeError", + "InvalidLiquidError", + "InvalidWavelengthError", "CannotPerformModuleAction", "ResumeFromRecoveryNotAllowedError", "PauseNotAllowedError", @@ -146,4 +161,13 @@ # error occurrence models "ErrorOccurrence", "CommandNotAllowedError", + "InvalidLiquidHeightFound", + "LiquidHeightUnknownError", + "IncompleteLabwareDefinitionError", + "IncompleteWellDefinitionError", + "OperationLocationNotInWellError", + "InvalidDispenseVolumeError", + "StorageLimitReachedError", + "LiquidClassDoesNotExistError", + "LiquidClassRedefinitionError", ] diff --git a/api/src/opentrons/protocol_engine/errors/error_occurrence.py b/api/src/opentrons/protocol_engine/errors/error_occurrence.py index 02bcfb38b62..4141befe9b8 100644 --- a/api/src/opentrons/protocol_engine/errors/error_occurrence.py +++ b/api/src/opentrons/protocol_engine/errors/error_occurrence.py @@ -12,8 +12,6 @@ log = getLogger(__name__) -# TODO(mc, 2021-11-12): flesh this model out with structured error data -# for each error type so client may produce better error messages class ErrorOccurrence(BaseModel): """An occurrence of a specific error during protocol execution.""" @@ -44,8 +42,15 @@ def from_failed( id: str = Field(..., description="Unique identifier of this error occurrence.") createdAt: datetime = Field(..., description="When the error occurred.") + # Our Python should probably always set this to False--if we want it to be True, + # we should probably be using a more specific subclass of ErrorOccurrence anyway. + # However, we can't make this Literal[False], because we want this class to be able + # to act as a catch-all for parsing defined errors that might be missing some + # `errorInfo` fields because they were serialized by older software. isDefined: bool = Field( - default=False, # default=False for database backwards compatibility. + # default=False for database backwards compatibility, so we can parse objects + # serialized before isDefined existed. + default=False, description=dedent( """\ Whether this error is *defined.* diff --git a/api/src/opentrons/protocol_engine/errors/exceptions.py b/api/src/opentrons/protocol_engine/errors/exceptions.py index 8d8ed34fb9f..563a1fb816d 100644 --- a/api/src/opentrons/protocol_engine/errors/exceptions.py +++ b/api/src/opentrons/protocol_engine/errors/exceptions.py @@ -1,11 +1,17 @@ """Protocol engine exceptions.""" +from __future__ import annotations + from logging import getLogger -from typing import Any, Dict, Optional, Union, Iterator, Sequence +from typing import Any, Dict, Final, Optional, Union, Iterator, Sequence, TYPE_CHECKING from opentrons_shared_data.errors import ErrorCodes from opentrons_shared_data.errors.exceptions import EnumeratedError, PythonException +if TYPE_CHECKING: + from opentrons.protocol_engine.types import TipGeometry + + log = getLogger(__name__) @@ -132,6 +138,21 @@ def __init__( super().__init__(ErrorCodes.UNEXPECTED_TIP_REMOVAL, message, details, wrapping) +class PickUpTipTipNotAttachedError(TipNotAttachedError): + """Raised from TipHandler.pick_up_tip(). + + This is like TipNotAttachedError except that it carries some extra information + about the attempted operation. + """ + + tip_geometry: Final[TipGeometry] + """The tip geometry that would have been on the pipette, had the operation succeeded.""" + + def __init__(self, tip_geometry: TipGeometry) -> None: + super().__init__() + self.tip_geometry = tip_geometry + + class TipAttachedError(ProtocolEngineError): """Raised when a tip shouldn't be attached, but is.""" @@ -223,6 +244,19 @@ def __init__( super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) +class InvalidLiquidError(ProtocolEngineError): + """Raised when attempting to add a liquid with an invalid property.""" + + def __init__( + self, + message: Optional[str] = None, + details: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Build an InvalidLiquidError.""" + super().__init__(ErrorCodes.INVALID_PROTOCOL_DATA, message, details, wrapping) + + class LabwareDefinitionDoesNotExistError(ProtocolEngineError): """Raised when referencing a labware definition that does not exist.""" @@ -752,6 +786,19 @@ def __init__( super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) +class InvalidWavelengthError(ProtocolEngineError): + """Raised when attempting to set an invalid absorbance wavelength.""" + + def __init__( + self, + message: Optional[str] = None, + details: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Build a InvalidWavelengthError.""" + super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) + + class InvalidHoldTimeError(ProtocolEngineError): """An error raised when attempting to set an invalid temperature hold time.""" @@ -952,7 +999,7 @@ def __init__( """Build a InvalidPipettingVolumeError.""" message = ( f"Cannot aspirate {attempted_aspirate_volume} µL when only" - f" {available_volume} is available." + f" {available_volume} is available in the tip." ) details = { "attempted_aspirate_volume": attempted_aspirate_volume, @@ -1002,6 +1049,32 @@ def __init__( super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) +class InvalidLiquidHeightFound(ProtocolEngineError): + """Raised when attempting to estimate liquid height based on volume fails.""" + + def __init__( + self, + message: Optional[str] = None, + details: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Build an InvalidLiquidHeightFound error.""" + super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) + + +class LiquidHeightUnknownError(ProtocolEngineError): + """Raised when attempting to specify WellOrigin.MENISCUS before liquid probing has been done.""" + + def __init__( + self, + message: Optional[str] = None, + details: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Build a LiquidHeightUnknownError.""" + super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) + + class EStopActivatedError(ProtocolEngineError): """Represents an E-stop event.""" @@ -1043,3 +1116,79 @@ def __init__( ) -> None: """Build a TipNotEmptyError.""" super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) + + +class IncompleteLabwareDefinitionError(ProtocolEngineError): + """Raised when a labware definition lacks innerLabwareGeometry in general or for a specific well_id.""" + + def __init__( + self, + message: Optional[str] = None, + details: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Build an IncompleteLabwareDefinitionError.""" + super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) + + +class IncompleteWellDefinitionError(ProtocolEngineError): + """Raised when a well definition lacks a geometryDefinitionId.""" + + def __init__( + self, + message: Optional[str] = None, + details: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Build an IncompleteWellDefinitionError.""" + super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) + + +class OperationLocationNotInWellError(ProtocolEngineError): + """Raised when a calculated operation location is not within a well.""" + + def __init__( + self, + message: Optional[str] = None, + details: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Build an OperationLocationNotInWellError.""" + super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) + + +class StorageLimitReachedError(ProtocolEngineError): + """Raised to indicate that a file cannot be created due to storage limitations.""" + + def __init__( + self, + message: Optional[str] = None, + detail: Optional[Dict[str, str]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + """Build an StorageLimitReached.""" + super().__init__(ErrorCodes.GENERAL_ERROR, message, detail, wrapping) + + +class LiquidClassDoesNotExistError(ProtocolEngineError): + """Raised when referencing a liquid class that has not been loaded.""" + + def __init__( + self, + message: Optional[str] = None, + details: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) + + +class LiquidClassRedefinitionError(ProtocolEngineError): + """Raised when attempting to load a liquid class that conflicts with a liquid class already loaded.""" + + def __init__( + self, + message: Optional[str] = None, + details: Optional[Dict[str, Any]] = None, + wrapping: Optional[Sequence[EnumeratedError]] = None, + ) -> None: + super().__init__(ErrorCodes.GENERAL_ERROR, message, details, wrapping) diff --git a/api/src/opentrons/protocol_engine/execution/__init__.py b/api/src/opentrons/protocol_engine/execution/__init__.py index 80f2dfd0d99..482a16d787f 100644 --- a/api/src/opentrons/protocol_engine/execution/__init__.py +++ b/api/src/opentrons/protocol_engine/execution/__init__.py @@ -21,6 +21,7 @@ from .hardware_stopper import HardwareStopper from .door_watcher import DoorWatcher from .status_bar import StatusBarHandler +from ..resources.file_provider import FileProvider # .thermocycler_movement_flagger omitted from package's public interface. @@ -45,4 +46,5 @@ "DoorWatcher", "RailLightsHandler", "StatusBarHandler", + "FileProvider", ] diff --git a/api/src/opentrons/protocol_engine/execution/command_executor.py b/api/src/opentrons/protocol_engine/execution/command_executor.py index e427b945e0d..b6c686e0b11 100644 --- a/api/src/opentrons/protocol_engine/execution/command_executor.py +++ b/api/src/opentrons/protocol_engine/execution/command_executor.py @@ -12,9 +12,10 @@ ) from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.notes import make_error_recovery_debug_note -from ..state import StateStore -from ..resources import ModelUtils +from ..state.state import StateStore +from ..resources import ModelUtils, FileProvider from ..commands import CommandStatus from ..actions import ( ActionDispatcher, @@ -72,6 +73,7 @@ class CommandExecutor: def __init__( self, hardware_api: HardwareControlAPI, + file_provider: FileProvider, state_store: StateStore, action_dispatcher: ActionDispatcher, equipment: EquipmentHandler, @@ -88,6 +90,7 @@ def __init__( ) -> None: """Initialize the CommandExecutor with access to its dependencies.""" self._hardware_api = hardware_api + self._file_provider = file_provider self._state_store = state_store self._action_dispatcher = action_dispatcher self._equipment = equipment @@ -116,6 +119,7 @@ async def execute(self, command_id: str) -> None: command_impl = queued_command._ImplementationCls( state_view=self._state_store, hardware_api=self._hardware_api, + file_provider=self._file_provider, equipment=self._equipment, movement=self._movement, gantry_mover=self._gantry_mover, @@ -158,6 +162,12 @@ async def execute(self, command_id: str) -> None: elif not isinstance(error, EnumeratedError): error = PythonException(error) + error_recovery_type = error_recovery_policy( + self._state_store.config, + running_command, + None, + ) + note_tracker(make_error_recovery_debug_note(error_recovery_type)) self._action_dispatcher.dispatch( FailCommandAction( error=error, @@ -166,11 +176,7 @@ async def execute(self, command_id: str) -> None: error_id=self._model_utils.generate_id(), failed_at=self._model_utils.get_timestamp(), notes=note_tracker.get_notes(), - type=error_recovery_policy( - self._state_store.config, - running_command, - None, - ), + type=error_recovery_type, ) ) @@ -185,11 +191,18 @@ async def execute(self, command_id: str) -> None: succeeded_command = running_command.copy(update=update) self._action_dispatcher.dispatch( SucceedCommandAction( - command=succeeded_command, private_result=result.private + command=succeeded_command, + state_update=result.state_update, ), ) else: # The command encountered a defined error. + error_recovery_type = error_recovery_policy( + self._state_store.config, + running_command, + result, + ) + note_tracker(make_error_recovery_debug_note(error_recovery_type)) self._action_dispatcher.dispatch( FailCommandAction( error=result, @@ -198,10 +211,6 @@ async def execute(self, command_id: str) -> None: error_id=result.public.id, failed_at=result.public.createdAt, notes=note_tracker.get_notes(), - type=error_recovery_policy( - self._state_store.config, - running_command, - result, - ), + type=error_recovery_type, ) ) diff --git a/api/src/opentrons/protocol_engine/execution/create_queue_worker.py b/api/src/opentrons/protocol_engine/execution/create_queue_worker.py index 3596ce6d96e..e37a2c0716b 100644 --- a/api/src/opentrons/protocol_engine/execution/create_queue_worker.py +++ b/api/src/opentrons/protocol_engine/execution/create_queue_worker.py @@ -4,8 +4,9 @@ from opentrons.hardware_control import HardwareControlAPI from opentrons.protocol_engine.execution.rail_lights import RailLightsHandler -from ..state import StateStore +from ..state.state import StateStore from ..actions import ActionDispatcher +from ..resources import FileProvider from .equipment import EquipmentHandler from .movement import MovementHandler from .gantry_mover import create_gantry_mover @@ -20,6 +21,7 @@ def create_queue_worker( hardware_api: HardwareControlAPI, + file_provider: FileProvider, state_store: StateStore, action_dispatcher: ActionDispatcher, command_generator: Callable[[], AsyncGenerator[str, None]], @@ -28,6 +30,7 @@ def create_queue_worker( Arguments: hardware_api: Hardware control API to pass down to dependencies. + file_provider: Provides access to robot server file writing procedures for protocol output. state_store: StateStore to pass down to dependencies. action_dispatcher: ActionDispatcher to pass down to dependencies. error_recovery_policy: ErrorRecoveryPolicy to pass down to dependencies. @@ -78,6 +81,7 @@ def create_queue_worker( command_executor = CommandExecutor( hardware_api=hardware_api, + file_provider=file_provider, state_store=state_store, action_dispatcher=action_dispatcher, equipment=equipment_handler, diff --git a/api/src/opentrons/protocol_engine/execution/door_watcher.py b/api/src/opentrons/protocol_engine/execution/door_watcher.py index b35e73bdab9..a14712d4837 100644 --- a/api/src/opentrons/protocol_engine/execution/door_watcher.py +++ b/api/src/opentrons/protocol_engine/execution/door_watcher.py @@ -15,7 +15,7 @@ from opentrons.protocol_engine.actions import ActionDispatcher, DoorChangeAction -from ..state import StateStore +from ..state.state import StateStore _UnsubscribeCallback = Callable[[], None] diff --git a/api/src/opentrons/protocol_engine/execution/equipment.py b/api/src/opentrons/protocol_engine/execution/equipment.py index 4093c93489c..792bd583b88 100644 --- a/api/src/opentrons/protocol_engine/execution/equipment.py +++ b/api/src/opentrons/protocol_engine/execution/equipment.py @@ -35,7 +35,8 @@ ModelUtils, pipette_data_provider, ) -from ..state import StateStore, HardwareModule +from ..state.state import StateStore +from ..state.modules import HardwareModule from ..types import ( LabwareLocation, DeckSlotLocation, diff --git a/api/src/opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py b/api/src/opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py new file mode 100644 index 00000000000..67d75cfb181 --- /dev/null +++ b/api/src/opentrons/protocol_engine/execution/error_recovery_hardware_state_synchronizer.py @@ -0,0 +1,101 @@ +# noqa: D100 + + +from opentrons.hardware_control import HardwareControlAPI +from opentrons.protocol_engine.actions.action_handler import ActionHandler +from opentrons.protocol_engine.actions.actions import ( + Action, + FailCommandAction, + ResumeFromRecoveryAction, +) +from opentrons.protocol_engine.commands.command import DefinedErrorData +from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryType +from opentrons.protocol_engine.execution.tip_handler import HardwareTipHandler +from opentrons.protocol_engine.state import update_types +from opentrons.protocol_engine.state.state import StateView + + +class ErrorRecoveryHardwareStateSynchronizer(ActionHandler): + """A hack to keep the hardware API's state correct through certain error recovery flows. + + BACKGROUND: + + Certain parts of robot state are duplicated between `opentrons.protocol_engine` and + `opentrons.hardware_control`. Stuff like "is there a tip attached." + + Normally, Protocol Engine command implementations (`opentrons.protocol_engine.commands`) + mutate hardware API state when they execute; and then when they finish executing, + the Protocol Engine state stores (`opentrons.protocol_engine.state`) update Protocol + Engine state accordingly. So both halves are accounted for. This generally works fine. + + However, we need to go out of our way to support + `ProtocolEngine.resume_from_recovery(reconcile_false_positive=True)`. + It wants to apply a second set of state updates to "fix things up" with the + new knowledge that some error was a false positive. The Protocol Engine half of that + is easy for us to apply the normal way, through the state stores; but the + hardware API half of that cannot be applied the normal way, from the command + implementation, because the command in question is no longer running. + + THE HACK: + + This listens for the same error recovery state updates that the state stores do, + figures out what hardware API state mutations ought to go along with them, + and then does those mutations. + + The problem is that hardware API state is now mutated from two different places + (sometimes the command implementations, and sometimes here), which are bound + to grow accidental differences. + + TO FIX: + + Make Protocol Engine's use of the hardware API less stateful. e.g. supply + tip geometry every time we call a hardware API movement method, instead of + just once when we pick up a tip. Use Protocol Engine state as the single source + of truth. + """ + + def __init__(self, hardware_api: HardwareControlAPI, state_view: StateView) -> None: + self._hardware_api = hardware_api + self._state_view = state_view + + def handle_action(self, action: Action) -> None: + """Modify hardware API state in reaction to a Protocol Engine action.""" + state_update = _get_state_update(action) + if state_update: + self._synchronize(state_update) + + def _synchronize(self, state_update: update_types.StateUpdate) -> None: + tip_handler = HardwareTipHandler(self._state_view, self._hardware_api) + + if state_update.pipette_tip_state != update_types.NO_CHANGE: + pipette_id = state_update.pipette_tip_state.pipette_id + tip_geometry = state_update.pipette_tip_state.tip_geometry + if tip_geometry is None: + tip_handler.remove_tip(pipette_id) + else: + tip_handler.cache_tip(pipette_id=pipette_id, tip=tip_geometry) + + +def _get_state_update(action: Action) -> update_types.StateUpdate | None: + """Get the mutations that we need to do on the hardware API to stay in sync with an engine action. + + The mutations are returned in Protocol Engine terms, as a StateUpdate. + They then need to be converted to hardware API terms. + """ + match action: + case ResumeFromRecoveryAction(state_update=state_update): + return state_update + + case FailCommandAction( + error=DefinedErrorData( + state_update_if_false_positive=state_update_if_false_positive + ) + ): + return ( + state_update_if_false_positive + if action.type == ErrorRecoveryType.ASSUME_FALSE_POSITIVE_AND_CONTINUE + else None + ) + + case _: + return None diff --git a/api/src/opentrons/protocol_engine/execution/gantry_mover.py b/api/src/opentrons/protocol_engine/execution/gantry_mover.py index 26ab20f69de..5413de8741c 100644 --- a/api/src/opentrons/protocol_engine/execution/gantry_mover.py +++ b/api/src/opentrons/protocol_engine/execution/gantry_mover.py @@ -1,19 +1,28 @@ """Gantry movement wrapper for hardware and simulation based movement.""" -from typing import Optional, List, Dict +from logging import getLogger +from opentrons.config.types import OT3Config +from functools import partial +from typing import Optional, List, Dict, Tuple from typing_extensions import Protocol as TypingProtocol -from opentrons.types import Point, Mount +from opentrons.types import Point, Mount, MountType from opentrons.hardware_control import HardwareControlAPI -from opentrons.hardware_control.types import Axis as HardwareAxis +from opentrons.hardware_control.types import Axis as HardwareAxis, CriticalPoint +from opentrons.hardware_control.motion_utilities import ( + target_axis_map_from_relative, + target_axis_map_from_absolute, +) from opentrons_shared_data.errors.exceptions import PositionUnknownError from opentrons.motion_planning import Waypoint -from ..state import StateView +from ..state.state import StateView from ..types import MotorAxis, CurrentWell from ..errors import MustHomeError, InvalidAxisForRobotType +log = getLogger(__name__) + _MOTOR_AXIS_TO_HARDWARE_AXIS: Dict[MotorAxis, HardwareAxis] = { MotorAxis.X: HardwareAxis.X, @@ -24,8 +33,38 @@ MotorAxis.RIGHT_PLUNGER: HardwareAxis.C, MotorAxis.EXTENSION_Z: HardwareAxis.Z_G, MotorAxis.EXTENSION_JAW: HardwareAxis.G, + MotorAxis.AXIS_96_CHANNEL_CAM: HardwareAxis.Q, +} + +_MOTOR_AXIS_TO_HARDWARE_MOUNT: Dict[MotorAxis, Mount] = { + MotorAxis.LEFT_Z: Mount.LEFT, + MotorAxis.RIGHT_Z: Mount.RIGHT, + MotorAxis.EXTENSION_Z: Mount.EXTENSION, +} + +_HARDWARE_MOUNT_MOTOR_AXIS_TO: Dict[Mount, MotorAxis] = { + Mount.LEFT: MotorAxis.LEFT_Z, + Mount.RIGHT: MotorAxis.RIGHT_Z, + Mount.EXTENSION: MotorAxis.EXTENSION_Z, +} + +_HARDWARE_AXIS_TO_MOTOR_AXIS: Dict[HardwareAxis, MotorAxis] = { + HardwareAxis.X: MotorAxis.X, + HardwareAxis.Y: MotorAxis.Y, + HardwareAxis.Z: MotorAxis.LEFT_Z, + HardwareAxis.A: MotorAxis.RIGHT_Z, + HardwareAxis.B: MotorAxis.LEFT_PLUNGER, + HardwareAxis.C: MotorAxis.RIGHT_PLUNGER, + HardwareAxis.P_L: MotorAxis.LEFT_PLUNGER, + HardwareAxis.P_R: MotorAxis.RIGHT_PLUNGER, + HardwareAxis.Z_L: MotorAxis.LEFT_Z, + HardwareAxis.Z_R: MotorAxis.RIGHT_Z, + HardwareAxis.Z_G: MotorAxis.EXTENSION_Z, + HardwareAxis.G: MotorAxis.EXTENSION_JAW, + HardwareAxis.Q: MotorAxis.AXIS_96_CHANNEL_CAM, } + # The height of the bottom of the pipette nozzle at home position without any tips. # We rely on this being the same for every OT-3 pipette. # @@ -36,6 +75,8 @@ # That OT3Simulator return value is what Protocol Engine uses for simulation when Protocol Engine # is configured to not virtualize pipettes, so this number should match it. VIRTUAL_MAX_OT3_HEIGHT = 248.0 +# This number was found by using the longest pipette's P1000V2 default configuration values. +VIRTUAL_MAX_OT2_HEIGHT = 268.14 class GantryMover(TypingProtocol): @@ -50,16 +91,45 @@ async def get_position( """Get the current position of the gantry.""" ... + async def get_position_from_mount( + self, + mount: Mount, + critical_point: Optional[CriticalPoint] = None, + fail_on_not_homed: bool = False, + ) -> Point: + """Get the current position of the gantry based on the given mount.""" + ... + def get_max_travel_z(self, pipette_id: str) -> float: """Get the maximum allowed z-height for pipette movement.""" ... + def get_max_travel_z_from_mount(self, mount: MountType) -> float: + """Get the maximum allowed z-height for mount movement.""" + ... + + async def move_axes( + self, + axis_map: Dict[MotorAxis, float], + critical_point: Optional[Dict[MotorAxis, float]] = None, + speed: Optional[float] = None, + relative_move: bool = False, + ) -> Dict[MotorAxis, float]: + """Move a set of axes a given distance.""" + ... + async def move_to( self, pipette_id: str, waypoints: List[Waypoint], speed: Optional[float] ) -> Point: """Move the hardware gantry to a waypoint.""" ... + async def move_mount_to( + self, mount: Mount, waypoints: List[Waypoint], speed: Optional[float] + ) -> Point: + """Move the provided hardware mount to a waypoint.""" + ... + async def move_relative( self, pipette_id: str, @@ -85,6 +155,16 @@ def motor_axis_to_hardware_axis(self, motor_axis: MotorAxis) -> HardwareAxis: """Transform an engine motor axis into a hardware axis.""" ... + def pick_mount_from_axis_map(self, axis_map: Dict[MotorAxis, float]) -> Mount: + """Find a mount axis in the axis_map if it exists otherwise default to left mount.""" + ... + + def motor_axes_to_present_hardware_axes( + self, motor_axes: List[MotorAxis] + ) -> List[HardwareAxis]: + """Transform a list of engine axes into a list of hardware axes, filtering out non-present axes.""" + ... + class HardwareGantryMover(GantryMover): """Hardware API based gantry movement handler.""" @@ -93,10 +173,70 @@ def __init__(self, hardware_api: HardwareControlAPI, state_view: StateView) -> N self._hardware_api = hardware_api self._state_view = state_view + def motor_axes_to_present_hardware_axes( + self, motor_axes: List[MotorAxis] + ) -> List[HardwareAxis]: + """Get hardware axes from engine axes while filtering out non-present axes.""" + return [ + self.motor_axis_to_hardware_axis(motor_axis) + for motor_axis in motor_axes + if self._hardware_api.axis_is_present( + self.motor_axis_to_hardware_axis(motor_axis) + ) + ] + def motor_axis_to_hardware_axis(self, motor_axis: MotorAxis) -> HardwareAxis: """Transform an engine motor axis into a hardware axis.""" return _MOTOR_AXIS_TO_HARDWARE_AXIS[motor_axis] + def _hardware_axis_to_motor_axis(self, motor_axis: HardwareAxis) -> MotorAxis: + """Transform an hardware axis into a engine motor axis.""" + return _HARDWARE_AXIS_TO_MOTOR_AXIS[motor_axis] + + def _convert_axis_map_for_hw( + self, axis_map: Dict[MotorAxis, float] + ) -> Dict[HardwareAxis, float]: + """Transform an engine motor axis map to a hardware axis map.""" + return {_MOTOR_AXIS_TO_HARDWARE_AXIS[ax]: dist for ax, dist in axis_map.items()} + + def _critical_point_for( + self, mount: Mount, cp_override: Optional[Dict[MotorAxis, float]] = None + ) -> Point: + if cp_override: + return Point( + x=cp_override[MotorAxis.X], + y=cp_override[MotorAxis.Y], + z=cp_override[_HARDWARE_MOUNT_MOTOR_AXIS_TO[mount]], + ) + else: + return self._hardware_api.critical_point_for(mount) + + def _get_gantry_offsets_for_robot_type( + self, + ) -> Tuple[Point, Point, Optional[Point]]: + if isinstance(self._hardware_api.config, OT3Config): + return ( + Point(*self._hardware_api.config.left_mount_offset), + Point(*self._hardware_api.config.right_mount_offset), + Point(*self._hardware_api.config.gripper_mount_offset), + ) + else: + return ( + Point(*self._hardware_api.config.left_mount_offset), + Point(0, 0, 0), + None, + ) + + def pick_mount_from_axis_map(self, axis_map: Dict[MotorAxis, float]) -> Mount: + """Find a mount axis in the axis_map if it exists otherwise default to left mount.""" + found_mount = Mount.LEFT + mounts = list(_MOTOR_AXIS_TO_HARDWARE_MOUNT.keys()) + for k in axis_map.keys(): + if k in mounts: + found_mount = _MOTOR_AXIS_TO_HARDWARE_MOUNT[k] + break + return found_mount + async def get_position( self, pipette_id: str, @@ -114,12 +254,33 @@ async def get_position( pipette_id=pipette_id, current_location=current_well, ) + point = await self.get_position_from_mount( + mount=pipette_location.mount.to_hw_mount(), + critical_point=pipette_location.critical_point, + fail_on_not_homed=fail_on_not_homed, + ) + return point + + async def get_position_from_mount( + self, + mount: Mount, + critical_point: Optional[CriticalPoint] = None, + fail_on_not_homed: bool = False, + ) -> Point: + """Get the current position of the gantry based on the mount. + + Args: + mount: The mount to get the position for. + critical_point: Optional parameter for getting instrument location data, effects critical point. + fail_on_not_homed: Raise PositionUnknownError if gantry position is not known. + """ try: - return await self._hardware_api.gantry_position( - mount=pipette_location.mount.to_hw_mount(), - critical_point=pipette_location.critical_point, + point = await self._hardware_api.gantry_position( + mount=mount, + critical_point=critical_point, fail_on_not_homed=fail_on_not_homed, ) + return point except PositionUnknownError as e: raise MustHomeError(message=str(e), wrapping=[e]) @@ -129,8 +290,16 @@ def get_max_travel_z(self, pipette_id: str) -> float: Args: pipette_id: Pipette ID to get max travel z-height for. """ - hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount() - return self._hardware_api.get_instrument_max_height(mount=hw_mount) + mount = self._state_view.pipettes.get_mount(pipette_id) + return self.get_max_travel_z_from_mount(mount=mount) + + def get_max_travel_z_from_mount(self, mount: MountType) -> float: + """Get the maximum allowed z-height for any mount movement. + + Args: + mount: Mount to get max travel z-height for. + """ + return self._hardware_api.get_instrument_max_height(mount=mount.to_hw_mount()) async def move_to( self, pipette_id: str, waypoints: List[Waypoint], speed: Optional[float] @@ -150,6 +319,88 @@ async def move_to( return waypoints[-1].position + async def move_mount_to( + self, mount: Mount, waypoints: List[Waypoint], speed: Optional[float] + ) -> Point: + """Move the given hardware mount to a waypoint.""" + assert len(waypoints) > 0, "Must have at least one waypoint" + for waypoint in waypoints: + log.info(f"The current waypoint moving is {waypoint}") + await self._hardware_api.move_to( + mount=mount, + abs_position=waypoint.position, + critical_point=waypoint.critical_point, + speed=speed, + ) + + return waypoints[-1].position + + async def move_axes( + self, + axis_map: Dict[MotorAxis, float], + critical_point: Optional[Dict[MotorAxis, float]] = None, + speed: Optional[float] = None, + relative_move: bool = False, + ) -> Dict[MotorAxis, float]: + """Move a set of axes a given distance. + + Args: + axis_map: The mapping of axes to command. + critical_point: A critical point override for axes + speed: Optional speed parameter for the move. + relative_move: Whether the axis map needs to be converted from a relative to absolute move. + """ + try: + pos_hw = self._convert_axis_map_for_hw(axis_map) + mount = self.pick_mount_from_axis_map(axis_map) + if relative_move: + current_position = await self._hardware_api.current_position( + mount, refresh=True + ) + log.info(f"The current position of the robot is: {current_position}.") + converted_current_position_deck = ( + self._hardware_api.get_deck_from_machine(current_position) + ) + log.info(f"The current position of the robot is: {current_position}.") + + pos_hw = target_axis_map_from_relative(pos_hw, current_position) + log.info( + f"The absolute position is: {pos_hw} and hw pos map is {pos_hw}." + ) + log.info(f"The calculated move {pos_hw} and {mount}") + ( + left_offset, + right_offset, + gripper_offset, + ) = self._get_gantry_offsets_for_robot_type() + absolute_pos = target_axis_map_from_absolute( + mount, + pos_hw, + partial(self._critical_point_for, cp_override=critical_point), + left_mount_offset=left_offset, + right_mount_offset=right_offset, + gripper_mount_offset=gripper_offset, + ) + log.info(f"The prepped abs {absolute_pos}") + await self._hardware_api.move_axes( + position=absolute_pos, + speed=speed, + ) + + except PositionUnknownError as e: + raise MustHomeError(message=str(e), wrapping=[e]) + + current_position = await self._hardware_api.current_position( + mount, refresh=True + ) + converted_current_position_deck = self._hardware_api.get_deck_from_machine( + current_position + ) + return { + self._hardware_axis_to_motor_axis(ax): pos + for ax, pos in converted_current_position_deck.items() + } + async def move_relative( self, pipette_id: str, @@ -239,6 +490,16 @@ def motor_axis_to_hardware_axis(self, motor_axis: MotorAxis) -> HardwareAxis: """Transform an engine motor axis into a hardware axis.""" return _MOTOR_AXIS_TO_HARDWARE_AXIS[motor_axis] + def pick_mount_from_axis_map(self, axis_map: Dict[MotorAxis, float]) -> Mount: + """Find a mount axis in the axis_map if it exists otherwise default to left mount.""" + found_mount = Mount.LEFT + mounts = list(_MOTOR_AXIS_TO_HARDWARE_MOUNT.keys()) + for k in axis_map.keys(): + if k in mounts: + found_mount = _MOTOR_AXIS_TO_HARDWARE_MOUNT[k] + break + return found_mount + async def get_position( self, pipette_id: str, @@ -261,6 +522,31 @@ async def get_position( origin = Point(x=0, y=0, z=0) return origin + async def get_position_from_mount( + self, + mount: Mount, + critical_point: Optional[CriticalPoint] = None, + fail_on_not_homed: bool = False, + ) -> Point: + """Get the current position of the gantry based on the mount. + + Args: + mount: The mount to get the position for. + critical_point: Optional parameter for getting instrument location data, effects critical point. + fail_on_not_homed: Raise PositionUnknownError if gantry position is not known. + """ + pipette = self._state_view.pipettes.get_by_mount(MountType[mount.name]) + origin_deck_point = ( + self._state_view.pipettes.get_deck_point(pipette.id) if pipette else None + ) + if origin_deck_point is not None: + origin = Point( + x=origin_deck_point.x, y=origin_deck_point.y, z=origin_deck_point.z + ) + else: + origin = Point(x=0, y=0, z=0) + return origin + def get_max_travel_z(self, pipette_id: str) -> float: """Get the maximum allowed z-height for pipette movement. @@ -273,9 +559,73 @@ def get_max_travel_z(self, pipette_id: str) -> float: ) else: instrument_height = VIRTUAL_MAX_OT3_HEIGHT - tip_length = self._state_view.tips.get_tip_length(pipette_id) + + tip = self._state_view.pipettes.get_attached_tip(pipette_id=pipette_id) + tip_length = tip.length if tip is not None else 0 return instrument_height - tip_length + def get_max_travel_z_from_mount(self, mount: MountType) -> float: + """Get the maximum allowed z-height for mount.""" + pipette = self._state_view.pipettes.get_by_mount(mount) + if self._state_view.config.robot_type == "OT-2 Standard": + instrument_height = ( + self._state_view.pipettes.get_instrument_max_height_ot2(pipette.id) + if pipette + else VIRTUAL_MAX_OT2_HEIGHT + ) + else: + instrument_height = VIRTUAL_MAX_OT3_HEIGHT + if pipette: + tip = self._state_view.pipettes.get_attached_tip(pipette_id=pipette.id) + tip_length = tip.length if tip is not None else 0.0 + else: + tip_length = 0.0 + return instrument_height - tip_length + + async def move_axes( + self, + axis_map: Dict[MotorAxis, float], + critical_point: Optional[Dict[MotorAxis, float]] = None, + speed: Optional[float] = None, + relative_move: bool = False, + ) -> Dict[MotorAxis, float]: + """Move the give axes map. No-op in virtual implementation.""" + mount = self.pick_mount_from_axis_map(axis_map) + current_position = await self.get_position_from_mount(mount) + updated_position = {} + if relative_move: + updated_position[MotorAxis.X] = ( + axis_map.get(MotorAxis.X, 0.0) + current_position[0] + ) + updated_position[MotorAxis.Y] = ( + axis_map.get(MotorAxis.Y, 0.0) + current_position[1] + ) + if mount == Mount.RIGHT: + updated_position[MotorAxis.RIGHT_Z] = ( + axis_map.get(MotorAxis.RIGHT_Z, 0.0) + current_position[2] + ) + elif mount == Mount.EXTENSION: + updated_position[MotorAxis.EXTENSION_Z] = ( + axis_map.get(MotorAxis.EXTENSION_Z, 0.0) + current_position[2] + ) + else: + updated_position[MotorAxis.LEFT_Z] = ( + axis_map.get(MotorAxis.LEFT_Z, 0.0) + current_position[2] + ) + else: + critical_point = critical_point or {} + updated_position = { + ax: pos - critical_point.get(ax, 0.0) for ax, pos in axis_map.items() + } + return updated_position + + async def move_mount_to( + self, mount: Mount, waypoints: List[Waypoint], speed: Optional[float] + ) -> Point: + """Move the hardware mount to a waypoint. No-op in virtual implementation.""" + assert len(waypoints) > 0, "Must have at least one waypoint" + return waypoints[-1].position + async def move_to( self, pipette_id: str, waypoints: List[Waypoint], speed: Optional[float] ) -> Point: @@ -311,6 +661,14 @@ async def prepare_for_mount_movement(self, mount: Mount) -> None: """Retract the 'idle' mount if necessary.""" pass + def motor_axes_to_present_hardware_axes( + self, motor_axes: List[MotorAxis] + ) -> List[HardwareAxis]: + """Get present hardware axes from a list of engine axes. In simulation, all axes are present.""" + return [ + self.motor_axis_to_hardware_axis(motor_axis) for motor_axis in motor_axes + ] + def create_gantry_mover( state_view: StateView, hardware_api: HardwareControlAPI diff --git a/api/src/opentrons/protocol_engine/execution/hardware_stopper.py b/api/src/opentrons/protocol_engine/execution/hardware_stopper.py index 28eacd7525b..81d4f10d94d 100644 --- a/api/src/opentrons/protocol_engine/execution/hardware_stopper.py +++ b/api/src/opentrons/protocol_engine/execution/hardware_stopper.py @@ -6,7 +6,7 @@ from opentrons.types import PipetteNotAttachedError as HwPipetteNotAttachedError from ..resources.ot3_validation import ensure_ot3_hardware -from ..state import StateStore +from ..state.state import StateStore from ..types import MotorAxis, PostRunHardwareState from ..errors import HardwareNotSupportedError @@ -78,7 +78,7 @@ async def _drop_tip(self) -> None: try: if self._state_store.labware.get_fixed_trash_id() == FIXED_TRASH_ID: # OT-2 and Flex 2.15 protocols will default to the Fixed Trash Labware - await self._tip_handler.add_tip(pipette_id=pipette_id, tip=tip) + self._tip_handler.cache_tip(pipette_id=pipette_id, tip=tip) await self._movement_handler.move_to_well( pipette_id=pipette_id, labware_id=FIXED_TRASH_ID, @@ -90,7 +90,7 @@ async def _drop_tip(self) -> None: ) elif self._state_store.config.robot_type == "OT-2 Standard": # API 2.16 and above OT2 protocols use addressable areas - await self._tip_handler.add_tip(pipette_id=pipette_id, tip=tip) + self._tip_handler.cache_tip(pipette_id=pipette_id, tip=tip) await self._movement_handler.move_to_addressable_area( pipette_id=pipette_id, addressable_area_name="fixedTrash", diff --git a/api/src/opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py b/api/src/opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py index d5ddbf81554..efe8190f04a 100644 --- a/api/src/opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py +++ b/api/src/opentrons/protocol_engine/execution/heater_shaker_movement_flagger.py @@ -13,7 +13,7 @@ HeaterShakerLabwareLatchStatusUnknown, WrongModuleTypeError, ) -from ..state import StateStore +from ..state.state import StateStore from ..state.module_substates import HeaterShakerModuleSubState from ..types import ( HeaterShakerMovementRestrictors, @@ -61,9 +61,6 @@ async def raise_if_labware_latched_on_heater_shaker( return # Labware on a module, but not a Heater-Shaker. if hs_substate.labware_latch_status == HeaterShakerLatchStatus.CLOSED: - # TODO (spp, 2022-10-27): This only raises if latch status is 'idle_closed'. - # We need to update the flagger to raise if latch status is anything other - # than 'idle_open' raise HeaterShakerLabwareLatchNotOpenError( "Heater-Shaker labware latch must be open when moving labware to/from it." ) diff --git a/api/src/opentrons/protocol_engine/execution/labware_movement.py b/api/src/opentrons/protocol_engine/execution/labware_movement.py index 3cdd78b8808..77de449c058 100644 --- a/api/src/opentrons/protocol_engine/execution/labware_movement.py +++ b/api/src/opentrons/protocol_engine/execution/labware_movement.py @@ -1,7 +1,9 @@ """Labware movement command handling.""" from __future__ import annotations -from typing import Optional, TYPE_CHECKING +from typing import Optional, TYPE_CHECKING, overload + +from opentrons_shared_data.labware.labware_definition import LabwareDefinition from opentrons.types import Point @@ -9,7 +11,7 @@ from opentrons.hardware_control.types import OT3Mount, Axis from opentrons.motion_planning import get_gripper_labware_movement_waypoints -from opentrons.protocol_engine.state import StateStore +from opentrons.protocol_engine.state.state import StateStore from opentrons.protocol_engine.resources.ot3_validation import ensure_ot3_hardware from .thermocycler_movement_flagger import ThermocyclerMovementFlagger @@ -37,8 +39,6 @@ _GRIPPER_HOMED_POSITION_Z = 166.125 # Height of the center of the gripper critical point from the deck when homed -# TODO (spp, 2022-10-20): name this GripperMovementHandler if it doesn't handle -# any non-gripper implementations class LabwareMovementHandler: """Implementation logic for labware movement.""" @@ -81,24 +81,64 @@ def __init__( ) ) + @overload async def move_labware_with_gripper( self, + *, labware_id: str, current_location: OnDeckLabwareLocation, new_location: OnDeckLabwareLocation, user_offset_data: LabwareMovementOffsetData, post_drop_slide_offset: Optional[Point], ) -> None: - """Move a loaded labware from one location to another using gripper.""" + ... + + @overload + async def move_labware_with_gripper( + self, + *, + labware_definition: LabwareDefinition, + current_location: OnDeckLabwareLocation, + new_location: OnDeckLabwareLocation, + user_offset_data: LabwareMovementOffsetData, + post_drop_slide_offset: Optional[Point], + ) -> None: + ... + + async def move_labware_with_gripper( # noqa: C901 + self, + *, + labware_id: str | None = None, + labware_definition: LabwareDefinition | None = None, + current_location: OnDeckLabwareLocation, + new_location: OnDeckLabwareLocation, + user_offset_data: LabwareMovementOffsetData, + post_drop_slide_offset: Optional[Point], + ) -> None: + """Physically move a labware from one location to another using the gripper. + + Generally, provide the `labware_id` of a loaded labware, and this method will + automatically look up its labware definition. If you're physically moving + something that has not been loaded as a labware (this is not common), + provide the `labware_definition` yourself instead. + """ use_virtual_gripper = self._state_store.config.use_virtual_gripper + if labware_definition is None: + assert labware_id is not None # From this method's @typing.overloads. + labware_definition = self._state_store.labware.get_definition(labware_id) + if use_virtual_gripper: - # During Analysis we will pass in hard coded estimates for certain positions only accessible during execution - self._state_store.geometry.check_gripper_labware_tip_collision( - gripper_homed_position_z=_GRIPPER_HOMED_POSITION_Z, - labware_id=labware_id, - current_location=current_location, - ) + # todo(mm, 2024-11-07): We should do this collision checking even when we + # only have a `labware_definition`, not a `labware_id`. Resolve when + # `check_gripper_labware_tip_collision()` can be made independent of `labware_id`. + if labware_id is not None: + self._state_store.geometry.check_gripper_labware_tip_collision( + # During Analysis we will pass in hard coded estimates for certain positions only accessible during execution + gripper_homed_position_z=_GRIPPER_HOMED_POSITION_Z, + labware_id=labware_id, + current_location=current_location, + ) return ot3api = ensure_ot3_hardware( @@ -121,12 +161,15 @@ async def move_labware_with_gripper( await ot3api.home(axes=[Axis.Z_L, Axis.Z_R, Axis.Z_G]) gripper_homed_position = await ot3api.gantry_position(mount=gripper_mount) - # Verify that no tip collisions will occur during the move - self._state_store.geometry.check_gripper_labware_tip_collision( - gripper_homed_position_z=gripper_homed_position.z, - labware_id=labware_id, - current_location=current_location, - ) + # todo(mm, 2024-11-07): We should do this collision checking even when we + # only have a `labware_definition`, not a `labware_id`. Resolve when + # `check_gripper_labware_tip_collision()` can be made independent of `labware_id`. + if labware_id is not None: + self._state_store.geometry.check_gripper_labware_tip_collision( + gripper_homed_position_z=gripper_homed_position.z, + labware_id=labware_id, + current_location=current_location, + ) async with self._thermocycler_plate_lifter.lift_plate_for_labware_movement( labware_location=current_location @@ -136,13 +179,14 @@ async def move_labware_with_gripper( from_location=current_location, to_location=new_location, additional_offset_vector=user_offset_data, + current_labware=labware_definition, ) ) from_labware_center = self._state_store.geometry.get_labware_grip_point( - labware_id=labware_id, location=current_location + labware_definition=labware_definition, location=current_location ) to_labware_center = self._state_store.geometry.get_labware_grip_point( - labware_id=labware_id, location=new_location + labware_definition=labware_definition, location=new_location ) movement_waypoints = get_gripper_labware_movement_waypoints( from_labware_center=from_labware_center, @@ -151,7 +195,9 @@ async def move_labware_with_gripper( offset_data=final_offsets, post_drop_slide_offset=post_drop_slide_offset, ) - labware_grip_force = self._state_store.labware.get_grip_force(labware_id) + labware_grip_force = self._state_store.labware.get_grip_force( + labware_definition + ) holding_labware = False for waypoint_data in movement_waypoints: if waypoint_data.jaw_open: @@ -174,9 +220,14 @@ async def move_labware_with_gripper( # should be holding labware if holding_labware: labware_bbox = self._state_store.labware.get_dimensions( - labware_id + labware_definition=labware_definition + ) + well_bbox = self._state_store.labware.get_well_bbox( + labware_definition=labware_definition ) - well_bbox = self._state_store.labware.get_well_bbox(labware_id) + # todo(mm, 2024-09-26): This currently raises a lower-level 2015 FailedGripperPickupError. + # Convert this to a higher-level 3001 LabwareDroppedError or 3002 LabwareNotPickedUpError, + # depending on what waypoint we're at, to propagate a more specific error code to users. ot3api.raise_error_if_gripper_pickup_failed( expected_grip_width=labware_bbox.y, grip_width_uncertainty_wider=abs( diff --git a/api/src/opentrons/protocol_engine/execution/movement.py b/api/src/opentrons/protocol_engine/execution/movement.py index 451f482ad0d..be8bbbb8de2 100644 --- a/api/src/opentrons/protocol_engine/execution/movement.py +++ b/api/src/opentrons/protocol_engine/execution/movement.py @@ -2,21 +2,23 @@ from __future__ import annotations import logging -from typing import Optional, List +from typing import Optional, List, Union -from opentrons.types import Point, MountType +from opentrons.types import Point, MountType, StagingSlotName from opentrons.hardware_control import HardwareControlAPI from opentrons_shared_data.errors.exceptions import PositionUnknownError +from opentrons.protocol_engine.errors import LocationIsStagingSlotError from ..types import ( WellLocation, + LiquidHandlingWellLocation, DeckPoint, MovementAxis, MotorAxis, CurrentWell, AddressableOffsetVector, ) -from ..state import StateStore +from ..state.state import StateStore from ..resources import ModelUtils from .thermocycler_movement_flagger import ThermocyclerMovementFlagger from .heater_shaker_movement_flagger import HeaterShakerMovementFlagger @@ -66,11 +68,12 @@ async def move_to_well( pipette_id: str, labware_id: str, well_name: str, - well_location: Optional[WellLocation] = None, + well_location: Optional[Union[WellLocation, LiquidHandlingWellLocation]] = None, current_well: Optional[CurrentWell] = None, force_direct: bool = False, minimum_z_height: Optional[float] = None, speed: Optional[float] = None, + operation_volume: Optional[float] = None, ) -> Point: """Move to a specific well.""" self._state_store.labware.raise_if_labware_inaccessible_by_pipette( @@ -91,9 +94,13 @@ async def move_to_well( self._state_store.modules.get_heater_shaker_movement_restrictors() ) - dest_slot_int = self._state_store.geometry.get_ancestor_slot_name( - labware_id - ).as_int() + ancestor = self._state_store.geometry.get_ancestor_slot_name(labware_id) + if isinstance(ancestor, StagingSlotName): + raise LocationIsStagingSlotError( + "Cannot move to well on labware in Staging Area Slot." + ) + + dest_slot_int = ancestor.as_int() self._hs_movement_flagger.raise_if_movement_restricted( hs_movement_restrictors=hs_movement_restrictors, @@ -129,6 +136,7 @@ async def move_to_well( current_well=current_well, force_direct=force_direct, minimum_z_height=minimum_z_height, + operation_volume=operation_volume, ) speed = self._state_store.pipettes.get_movement_speed( @@ -141,6 +149,33 @@ async def move_to_well( return final_point + async def move_mount_to( + self, mount: MountType, destination: DeckPoint, speed: Optional[float] = None + ) -> Point: + """Move mount to a specific location on the deck.""" + hw_mount = mount.to_hw_mount() + await self._gantry_mover.prepare_for_mount_movement(hw_mount) + origin = await self._gantry_mover.get_position_from_mount(mount=hw_mount) + max_travel_z = self._gantry_mover.get_max_travel_z_from_mount(mount=mount) + + # calculate the movement's waypoints + waypoints = self._state_store.motion.get_movement_waypoints_to_coords( + origin=origin, + dest=Point(x=destination.x, y=destination.y, z=destination.z), + max_travel_z=max_travel_z, + direct=False, + additional_min_travel_z=None, + ) + + # move through the waypoints + final_point = await self._gantry_mover.move_mount_to( + mount=hw_mount, + waypoints=waypoints, + speed=speed, + ) + + return final_point + async def move_to_addressable_area( self, pipette_id: str, @@ -151,6 +186,7 @@ async def move_to_addressable_area( speed: Optional[float] = None, stay_at_highest_possible_z: bool = False, ignore_tip_configuration: Optional[bool] = True, + highest_possible_z_extra_offset: Optional[float] = None, ) -> Point: """Move to a specific addressable area.""" # Check for presence of heater shakers on deck, and if planned @@ -201,6 +237,7 @@ async def move_to_addressable_area( minimum_z_height=minimum_z_height, stay_at_max_travel_z=stay_at_highest_possible_z, ignore_tip_configuration=ignore_tip_configuration, + max_travel_z_extra_margin=highest_possible_z_extra_offset, ) speed = self._state_store.pipettes.get_movement_speed( diff --git a/api/src/opentrons/protocol_engine/execution/pipetting.py b/api/src/opentrons/protocol_engine/execution/pipetting.py index c3e606849ff..10d613e4dcf 100644 --- a/api/src/opentrons/protocol_engine/execution/pipetting.py +++ b/api/src/opentrons/protocol_engine/execution/pipetting.py @@ -5,7 +5,8 @@ from opentrons.hardware_control import HardwareControlAPI -from ..state import StateView, HardwarePipette +from ..state.state import StateView +from ..state.pipettes import HardwarePipette from ..notes import CommandNoteAdder, CommandNote from ..errors.exceptions import ( TipNotAttachedError, @@ -29,9 +30,6 @@ class PipettingHandler(TypingProtocol): """Liquid handling commands.""" - def get_is_empty(self, pipette_id: str) -> bool: - """Get whether a pipette has an aspirated volume equal to 0.""" - def get_is_ready_to_aspirate(self, pipette_id: str) -> bool: """Get whether a pipette is ready to aspirate.""" @@ -81,10 +79,6 @@ def __init__(self, state_view: StateView, hardware_api: HardwareControlAPI) -> N self._state_view = state_view self._hardware_api = hardware_api - def get_is_empty(self, pipette_id: str) -> bool: - """Get whether a pipette has an aspirated volume equal to 0.""" - return self._state_view.pipettes.get_aspirated_volume(pipette_id) == 0 - def get_is_ready_to_aspirate(self, pipette_id: str) -> bool: """Get whether a pipette is ready to aspirate.""" hw_pipette = self._state_view.pipettes.get_hardware_pipette( @@ -97,7 +91,11 @@ def get_is_ready_to_aspirate(self, pipette_id: str) -> bool: ) async def prepare_for_aspirate(self, pipette_id: str) -> None: - """Prepare for pipette aspiration.""" + """Prepare for pipette aspiration. + + Raises: + PipetteOverpressureError, propagated as-is from the hardware controller. + """ hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount() await self._hardware_api.prepare_for_aspirate(mount=hw_mount) @@ -193,7 +191,9 @@ async def liquid_probe_in_place( mount=hw_pipette.mount, max_z_dist=well_depth - lld_min_height + well_location.offset.z, ) - return float(z_pos) + labware_pos = self._state_view.geometry.get_labware_position(labware_id) + relative_height = z_pos - labware_pos.z - well_def.z + return float(relative_height) @contextmanager def _set_flow_rate( @@ -236,10 +236,6 @@ def __init__( """Initialize a PipettingHandler instance.""" self._state_view = state_view - def get_is_empty(self, pipette_id: str) -> bool: - """Get whether a pipette has an aspirated volume equal to 0.""" - return self._state_view.pipettes.get_aspirated_volume(pipette_id) == 0 - def get_is_ready_to_aspirate(self, pipette_id: str) -> bool: """Get whether a pipette is ready to aspirate.""" return self._state_view.pipettes.get_aspirated_volume(pipette_id) is not None @@ -296,8 +292,8 @@ async def liquid_probe_in_place( well_location: WellLocation, ) -> float: """Detect liquid level.""" - # TODO (pm, 6-18-24): return a value of worth if needed - return 0.0 + well_def = self._state_view.labware.get_well_definition(labware_id, well_name) + return well_def.depth def _validate_tip_attached(self, pipette_id: str, command_name: str) -> None: """Validate if there is a tip attached.""" diff --git a/api/src/opentrons/protocol_engine/execution/queue_worker.py b/api/src/opentrons/protocol_engine/execution/queue_worker.py index fc2eceebc96..015adf085c9 100644 --- a/api/src/opentrons/protocol_engine/execution/queue_worker.py +++ b/api/src/opentrons/protocol_engine/execution/queue_worker.py @@ -3,7 +3,7 @@ from logging import getLogger from typing import Optional, AsyncGenerator, Callable -from ..state import StateStore +from ..state.state import StateStore from .command_executor import CommandExecutor log = getLogger(__name__) @@ -69,7 +69,11 @@ async def join(self) -> None: async def _run_commands(self) -> None: async for command_id in self._command_generator(): - await self._command_executor.execute(command_id=command_id) + try: + await self._command_executor.execute(command_id=command_id) + except BaseException: + log.exception("Unhandled failure in command executor") + raise # Yield to the event loop in case we're executing a long sequence of commands # that never yields internally. For example, a long sequence of comment commands. await asyncio.sleep(0) diff --git a/api/src/opentrons/protocol_engine/execution/run_control.py b/api/src/opentrons/protocol_engine/execution/run_control.py index 23220742405..1525353ac60 100644 --- a/api/src/opentrons/protocol_engine/execution/run_control.py +++ b/api/src/opentrons/protocol_engine/execution/run_control.py @@ -1,7 +1,7 @@ """Run control command side-effect logic.""" import asyncio -from ..state import StateStore +from ..state.state import StateStore from ..actions import ActionDispatcher, PauseAction, PauseSource diff --git a/api/src/opentrons/protocol_engine/execution/thermocycler_movement_flagger.py b/api/src/opentrons/protocol_engine/execution/thermocycler_movement_flagger.py index 463a896c1bc..742bc6b4278 100644 --- a/api/src/opentrons/protocol_engine/execution/thermocycler_movement_flagger.py +++ b/api/src/opentrons/protocol_engine/execution/thermocycler_movement_flagger.py @@ -7,7 +7,7 @@ from opentrons.hardware_control.modules import Thermocycler as HardwareThermocycler from ..types import ModuleLocation, LabwareLocation -from ..state import StateStore +from ..state.state import StateStore from ..errors import ThermocyclerNotOpenError, WrongModuleTypeError diff --git a/api/src/opentrons/protocol_engine/execution/thermocycler_plate_lifter.py b/api/src/opentrons/protocol_engine/execution/thermocycler_plate_lifter.py index 5691312bba8..1118dcc91bd 100644 --- a/api/src/opentrons/protocol_engine/execution/thermocycler_plate_lifter.py +++ b/api/src/opentrons/protocol_engine/execution/thermocycler_plate_lifter.py @@ -5,7 +5,8 @@ from typing import TYPE_CHECKING, AsyncGenerator, Optional from opentrons.hardware_control.modules.thermocycler import Thermocycler from opentrons.protocol_engine.types import LabwareLocation, ModuleLocation, ModuleModel -from opentrons.protocol_engine.state import StateStore, ThermocyclerModuleId +from opentrons.protocol_engine.state.state import StateStore +from opentrons.protocol_engine.state.module_substates import ThermocyclerModuleId from contextlib import asynccontextmanager if TYPE_CHECKING: diff --git a/api/src/opentrons/protocol_engine/execution/tip_handler.py b/api/src/opentrons/protocol_engine/execution/tip_handler.py index 7acfae1e3ef..8a58536c10b 100644 --- a/api/src/opentrons/protocol_engine/execution/tip_handler.py +++ b/api/src/opentrons/protocol_engine/execution/tip_handler.py @@ -1,9 +1,13 @@ """Tip pickup and drop procedures.""" + from typing import Optional, Dict from typing_extensions import Protocol as TypingProtocol from opentrons.hardware_control import HardwareControlAPI from opentrons.hardware_control.types import FailedTipStateCheck, InstrumentProbeType +from opentrons.protocol_engine.errors.exceptions import PickUpTipTipNotAttachedError +from opentrons.types import Mount, NozzleConfigurationType + from opentrons_shared_data.errors.exceptions import ( CommandPreconditionViolated, CommandParameterLimitViolated, @@ -11,7 +15,7 @@ ) from ..resources import LabwareDataProvider, ensure_ot3_hardware -from ..state import StateView +from ..state.state import StateView from ..types import TipGeometry, TipPresenceStatus from ..errors import ( HardwareNotSupportedError, @@ -20,7 +24,6 @@ ProtocolEngineError, ) - PRIMARY_NOZZLE_TO_ENDING_NOZZLE_MAP = { "A1": {"COLUMN": "H1", "ROW": "A12"}, "H1": {"COLUMN": "A1", "ROW": "H12"}, @@ -66,18 +69,27 @@ async def pick_up_tip( Returns: Tip geometry of the picked up tip. + + Raises: + PickUpTipTipNotAttachedError """ ... async def drop_tip(self, pipette_id: str, home_after: Optional[bool]) -> None: - """Drop the attached tip into the named location. + """Drop the attached tip into the current location. Pipette should be in place over the destination prior to calling this method. + + Raises: + TipAttachedError """ - async def add_tip(self, pipette_id: str, tip: TipGeometry) -> None: + def cache_tip(self, pipette_id: str, tip: TipGeometry) -> None: """Tell the Hardware API that a tip is attached.""" + def remove_tip(self, pipette_id: str) -> None: + """Tell the hardware API that no tip is attached.""" + async def get_tip_presence(self, pipette_id: str) -> TipPresenceStatus: """Get tip presence status on the pipette.""" @@ -87,7 +99,12 @@ async def verify_tip_presence( expected: TipPresenceStatus, follow_singular_sensor: Optional[InstrumentProbeType] = None, ) -> None: - """Verify the expected tip presence status.""" + """Use sensors to verify that a tip is or is not physically attached. + + Raises: + TipNotAttachedError or TipAttachedError, as appropriate, if the physical + status doesn't match what was expected. + """ async def _available_for_nozzle_layout( # noqa: C901 @@ -185,6 +202,11 @@ def __init__( self._labware_data_provider = labware_data_provider or LabwareDataProvider() self._state_view = state_view + # WARNING: ErrorRecoveryHardwareStateSynchronizer can currently construct several + # instances of this class per run, in addition to the main instance used + # for command execution. We're therefore depending on this class being + # stateless, so consider that before adding additional attributes here. + async def available_for_nozzle_layout( self, pipette_id: str, @@ -193,7 +215,7 @@ async def available_for_nozzle_layout( front_right_nozzle: Optional[str] = None, back_left_nozzle: Optional[str] = None, ) -> Dict[str, str]: - """Returns configuration for nozzle layout to pass to configure_nozzle_layout.""" + """See documentation on abstract base class.""" if self._state_view.pipettes.get_attached_tip(pipette_id): raise CommandPreconditionViolated( message=f"Cannot configure nozzle layout of {str(self)} while it has tips attached." @@ -209,8 +231,8 @@ async def pick_up_tip( labware_id: str, well_name: str, ) -> TipGeometry: - """Pick up a tip at the current location using the Hardware API.""" - hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount() + """See documentation on abstract base class.""" + hw_mount = self._get_hw_mount(pipette_id) nominal_tip_geometry = self._state_view.geometry.get_nominal_tip_geometry( pipette_id=pipette_id, labware_id=labware_id, well_name=well_name @@ -222,33 +244,29 @@ async def pick_up_tip( nominal_fallback=nominal_tip_geometry.length, ) + tip_geometry = TipGeometry( + length=actual_tip_length, + diameter=nominal_tip_geometry.diameter, + volume=nominal_tip_geometry.volume, + ) + await self._hardware_api.tip_pickup_moves( mount=hw_mount, presses=None, increment=None ) - await self.verify_tip_presence(pipette_id, TipPresenceStatus.PRESENT) - - self._hardware_api.cache_tip(hw_mount, actual_tip_length) - await self._hardware_api.prepare_for_aspirate(hw_mount) + try: + await self.verify_tip_presence(pipette_id, TipPresenceStatus.PRESENT) + except TipNotAttachedError as e: + raise PickUpTipTipNotAttachedError(tip_geometry=tip_geometry) from e - self._hardware_api.set_current_tiprack_diameter( - mount=hw_mount, - tiprack_diameter=nominal_tip_geometry.diameter, - ) + self.cache_tip(pipette_id, tip_geometry) - self._hardware_api.set_working_volume( - mount=hw_mount, - tip_volume=nominal_tip_geometry.volume, - ) + await self._hardware_api.prepare_for_aspirate(hw_mount) - return TipGeometry( - length=actual_tip_length, - diameter=nominal_tip_geometry.diameter, - volume=nominal_tip_geometry.volume, - ) + return tip_geometry async def drop_tip(self, pipette_id: str, home_after: Optional[bool]) -> None: - """Drop a tip at the current location using the Hardware API.""" - hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount() + """See documentation on abstract base class.""" + hw_mount = self._get_hw_mount(pipette_id) # Let the hardware controller handle defaulting home_after since its behavior # differs between machines @@ -257,14 +275,18 @@ async def drop_tip(self, pipette_id: str, home_after: Optional[bool]) -> None: else: kwargs = {} - await self._hardware_api.drop_tip(mount=hw_mount, **kwargs) + await self._hardware_api.tip_drop_moves(mount=hw_mount, **kwargs) + + # Allow TipNotAttachedError to propagate. await self.verify_tip_presence(pipette_id, TipPresenceStatus.ABSENT) - async def add_tip(self, pipette_id: str, tip: TipGeometry) -> None: - """Tell the Hardware API that a tip is attached.""" - hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount() + self.remove_tip(pipette_id) - await self._hardware_api.add_tip(mount=hw_mount, tip_length=tip.length) + def cache_tip(self, pipette_id: str, tip: TipGeometry) -> None: + """See documentation on abstract base class.""" + hw_mount = self._get_hw_mount(pipette_id) + + self._hardware_api.cache_tip(mount=hw_mount, tip_length=tip.length) self._hardware_api.set_current_tiprack_diameter( mount=hw_mount, @@ -276,12 +298,18 @@ async def add_tip(self, pipette_id: str, tip: TipGeometry) -> None: tip_volume=tip.volume, ) + def remove_tip(self, pipette_id: str) -> None: + """See documentation on abstract base class.""" + hw_mount = self._get_hw_mount(pipette_id) + self._hardware_api.remove_tip(hw_mount) + self._hardware_api.set_current_tiprack_diameter(hw_mount, 0) + async def get_tip_presence(self, pipette_id: str) -> TipPresenceStatus: - """Get the tip presence status of the pipette.""" + """See documentation on abstract base class.""" try: ot3api = ensure_ot3_hardware(hardware_api=self._hardware_api) - hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount() + hw_mount = self._get_hw_mount(pipette_id) status = await ot3api.get_tip_presence_status(hw_mount) return TipPresenceStatus.from_hw_state(status) @@ -295,14 +323,34 @@ async def verify_tip_presence( expected: TipPresenceStatus, follow_singular_sensor: Optional[InstrumentProbeType] = None, ) -> None: - """Verify the expecterd tip presence status of the pipette. + """See documentation on abstract base class.""" + nozzle_configuration = self._state_view.pipettes.get_nozzle_configuration( + pipette_id=pipette_id + ) - This function will raise an exception if the specified tip presence status - isn't matched. - """ + # Configuration metrics by which tip presence checking is ignored + unsupported_pipette_types = [8, 96] + unsupported_layout_types = [ + NozzleConfigurationType.SINGLE, + NozzleConfigurationType.COLUMN, + ] + # NOTE: (09-20-2024) Current on multi-channel pipettes, utilizing less than 4 nozzles risks false positives on the tip presence sensor + supported_partial_nozzle_minimum = 4 + + if ( + nozzle_configuration is not None + and self._state_view.pipettes.get_channels(pipette_id) + in unsupported_pipette_types + and nozzle_configuration.configuration in unsupported_layout_types + and len(nozzle_configuration.map_store) < supported_partial_nozzle_minimum + ): + # Tip presence sensing is not supported for single tip pick up on the 96ch Flex Pipette, nor with single and some partial layous of the 8ch Flex Pipette. + # This is due in part to a press distance tolerance which creates a risk case for false positives. In the case of single tip, the mechanical tolerance + # for presses with 100% success is below the minimum average achieved press distance for a given multi channel pipette in that configuration. + return try: ot3api = ensure_ot3_hardware(hardware_api=self._hardware_api) - hw_mount = self._state_view.pipettes.get_mount(pipette_id).to_hw_mount() + hw_mount = self._get_hw_mount(pipette_id) await ot3api.verify_tip_presence( hw_mount, expected.to_hw_state(), follow_singular_sensor ) @@ -320,6 +368,9 @@ async def verify_tip_presence( wrapping=[PythonException(e)], ) + def _get_hw_mount(self, pipette_id: str) -> Mount: + return self._state_view.pipettes.get_mount(pipette_id).to_hw_mount() + class VirtualTipHandler(TipHandler): """Pick up and drop tips, using a virtual pipette.""" @@ -359,7 +410,7 @@ async def available_for_nozzle_layout( front_right_nozzle: Optional[str] = None, back_left_nozzle: Optional[str] = None, ) -> Dict[str, str]: - """Returns configuration for nozzle layout to pass to configure_nozzle_layout.""" + """See documentation on abstract base class.""" if self._state_view.pipettes.get_attached_tip(pipette_id): raise CommandPreconditionViolated( message=f"Cannot configure nozzle layout of {str(self)} while it has tips attached." @@ -383,12 +434,19 @@ async def drop_tip( expected_has_tip=True, ) - async def add_tip(self, pipette_id: str, tip: TipGeometry) -> None: - """Add a tip using a virtual pipette. + def cache_tip(self, pipette_id: str, tip: TipGeometry) -> None: + """See documentation on abstract base class. + + This should not be called when using virtual pipettes. + """ + assert False, "TipHandler.cache_tip should not be used with virtual pipettes" + + def remove_tip(self, pipette_id: str) -> None: + """See documentation on abstract base class. This should not be called when using virtual pipettes. """ - assert False, "TipHandler.add_tip should not be used with virtual pipettes" + assert False, "TipHandler.remove_tip should not be used with virtual pipettes" async def verify_tip_presence( self, diff --git a/api/src/opentrons/protocol_engine/notes/__init__.py b/api/src/opentrons/protocol_engine/notes/__init__.py index f5b1d8c1a2a..606d75665a4 100644 --- a/api/src/opentrons/protocol_engine/notes/__init__.py +++ b/api/src/opentrons/protocol_engine/notes/__init__.py @@ -1,5 +1,17 @@ """Protocol engine notes module.""" -from .notes import NoteKind, CommandNote, CommandNoteAdder, CommandNoteTracker +from .notes import ( + NoteKind, + CommandNote, + CommandNoteAdder, + CommandNoteTracker, + make_error_recovery_debug_note, +) -__all__ = ["NoteKind", "CommandNote", "CommandNoteAdder", "CommandNoteTracker"] +__all__ = [ + "NoteKind", + "CommandNote", + "CommandNoteAdder", + "CommandNoteTracker", + "make_error_recovery_debug_note", +] diff --git a/api/src/opentrons/protocol_engine/notes/notes.py b/api/src/opentrons/protocol_engine/notes/notes.py index cf381aa4a68..8c349d167cd 100644 --- a/api/src/opentrons/protocol_engine/notes/notes.py +++ b/api/src/opentrons/protocol_engine/notes/notes.py @@ -1,7 +1,10 @@ """Definitions of data and interface shapes for notes.""" -from typing import Union, Literal, Protocol, List +from typing import Union, Literal, Protocol, List, TYPE_CHECKING from pydantic import BaseModel, Field +if TYPE_CHECKING: + from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryType + NoteKind = Union[Literal["warning", "information"], str] @@ -26,6 +29,20 @@ class CommandNote(BaseModel): ) +def make_error_recovery_debug_note(type: "ErrorRecoveryType") -> CommandNote: + """Return a note for debugging error recovery. + + This is intended to be read by developers and support people, not computers. + """ + message = f"Handling this command failure with {type.name}." + return CommandNote.construct( + noteKind="debugErrorRecovery", + shortMessage=message, + longMessage=message, + source="execution", + ) + + class CommandNoteAdder(Protocol): """The shape of a function that something can use to add a command note.""" diff --git a/api/src/opentrons/protocol_engine/plugins.py b/api/src/opentrons/protocol_engine/plugins.py index d729302aea7..da900bb9929 100644 --- a/api/src/opentrons/protocol_engine/plugins.py +++ b/api/src/opentrons/protocol_engine/plugins.py @@ -5,7 +5,7 @@ from typing_extensions import final from .actions import Action, ActionDispatcher, ActionHandler -from .state import StateView +from .state.state import StateView class AbstractPlugin(ActionHandler, ABC): diff --git a/api/src/opentrons/protocol_engine/protocol_engine.py b/api/src/opentrons/protocol_engine/protocol_engine.py index ffb251166cd..3479e0a295b 100644 --- a/api/src/opentrons/protocol_engine/protocol_engine.py +++ b/api/src/opentrons/protocol_engine/protocol_engine.py @@ -6,7 +6,6 @@ ResumeFromRecoveryAction, SetErrorRecoveryPolicyAction, ) -from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryPolicy from opentrons.protocols.models import LabwareDefinition from opentrons.hardware_control import HardwareControlAPI @@ -19,8 +18,9 @@ from .errors import ProtocolCommandFailedError, ErrorOccurrence, CommandNotAllowedError from .errors.exceptions import EStopActivatedError +from .error_recovery_policy import ErrorRecoveryPolicy from . import commands, slot_standardization -from .resources import ModelUtils, ModuleDataProvider +from .resources import ModelUtils, ModuleDataProvider, FileProvider from .types import ( LabwareOffset, LabwareOffsetCreate, @@ -38,7 +38,8 @@ DoorWatcher, HardwareStopper, ) -from .state import StateStore, StateView +from .state.state import StateStore, StateView +from .state.update_types import StateUpdate from .plugins import AbstractPlugin, PluginStarter from .actions import ( ActionDispatcher, @@ -58,7 +59,6 @@ HardwareStoppedAction, ResetTipsAction, SetPipetteMovementSpeedAction, - AddAbsorbanceReaderLidAction, ) @@ -88,41 +88,31 @@ def __init__( self, hardware_api: HardwareControlAPI, state_store: StateStore, - action_dispatcher: Optional[ActionDispatcher] = None, - plugin_starter: Optional[PluginStarter] = None, + action_dispatcher: ActionDispatcher, + plugin_starter: PluginStarter, + model_utils: ModelUtils, + hardware_stopper: HardwareStopper, + door_watcher: DoorWatcher, + module_data_provider: ModuleDataProvider, + file_provider: FileProvider, queue_worker: Optional[QueueWorker] = None, - model_utils: Optional[ModelUtils] = None, - hardware_stopper: Optional[HardwareStopper] = None, - door_watcher: Optional[DoorWatcher] = None, - module_data_provider: Optional[ModuleDataProvider] = None, ) -> None: """Initialize a ProtocolEngine instance. Must be called while an event loop is active. - This constructor does not inject provider implementations. + This constructor is only for `ProtocolEngine` unit tests. Prefer the `create_protocol_engine()` factory function. """ self._hardware_api = hardware_api + self._file_provider = file_provider self._state_store = state_store - self._model_utils = model_utils or ModelUtils() - self._action_dispatcher = action_dispatcher or ActionDispatcher( - sink=self._state_store - ) - self._plugin_starter = plugin_starter or PluginStarter( - state=self._state_store, - action_dispatcher=self._action_dispatcher, - ) - self._hardware_stopper = hardware_stopper or HardwareStopper( - hardware_api=hardware_api, - state_store=state_store, - ) - self._door_watcher = door_watcher or DoorWatcher( - state_store=state_store, - hardware_api=hardware_api, - action_dispatcher=self._action_dispatcher, - ) - self._module_data_provider = module_data_provider or ModuleDataProvider() + self._model_utils = model_utils + self._action_dispatcher = action_dispatcher + self._plugin_starter = plugin_starter + self._hardware_stopper = hardware_stopper + self._door_watcher = door_watcher + self._module_data_provider = module_data_provider self._queue_worker = queue_worker if self._queue_worker: self._queue_worker.start() @@ -184,11 +174,35 @@ def request_pause(self) -> None: self._action_dispatcher.dispatch(action) self._hardware_api.pause(HardwarePauseType.PAUSE) - def resume_from_recovery(self) -> None: - """Resume normal protocol execution after the engine was `AWAITING_RECOVERY`.""" + def resume_from_recovery(self, reconcile_false_positive: bool) -> None: + """Resume normal protocol execution after the engine was `AWAITING_RECOVERY`. + + If `reconcile_false_positive` is `False`, the engine will continue naively from + whatever state the error left it in. (Each defined error individually documents + exactly how it affects state.) This is appropriate for client-driven error + recovery, where the client wants predictable behavior from the engine. + + If `reconcile_false_positive` is `True`, the engine may apply additional fixups + to its state to try to get the rest of the run to just work, assuming the error + was a false-positive. + + For example, a `tipPhysicallyMissing` error from a `pickUpTip` would normally + leave the engine state without a tip on the pipette. If `reconcile_false_positive=True`, + the engine will set the pipette to have that missing tip before continuing, so + subsequent path planning, aspirates, dispenses, etc. will work as if nothing + went wrong. + """ + if reconcile_false_positive: + state_update = ( + self._state_store.commands.get_state_update_for_false_positive() + ) + else: + state_update = StateUpdate() # Empty/no-op. + action = self._state_store.commands.validate_action_allowed( - ResumeFromRecoveryAction() + ResumeFromRecoveryAction(state_update) ) + self._action_dispatcher.dispatch(action) def add_command( @@ -413,7 +427,7 @@ async def finish( post_run_hardware_state: The state in which to leave the gantry and motors in after the run is over. """ - if self._state_store.commands.state.stopped_by_estop: + if self._state_store.commands.get_is_stopped_by_estop(): # This handles the case where the E-stop was pressed while we were *not* in the middle # of some hardware interaction that would raise it as an exception. For example, imagine # we were paused between two commands, or imagine we were executing a waitForDuration. @@ -551,9 +565,12 @@ def add_liquid( description=(description or ""), displayColor=color, ) + validated_liquid = self._state_store.liquid.validate_liquid_allowed( + liquid=liquid + ) - self._action_dispatcher.dispatch(AddLiquidAction(liquid=liquid)) - return liquid + self._action_dispatcher.dispatch(AddLiquidAction(liquid=validated_liquid)) + return validated_liquid def add_addressable_area(self, addressable_area_name: str) -> None: """Add an addressable area to state.""" @@ -562,12 +579,6 @@ def add_addressable_area(self, addressable_area_name: str) -> None: AddAddressableAreaAction(addressable_area=area) ) - def add_absorbance_reader_lid(self, module_id: str, lid_id: str) -> None: - """Add an absorbance reader lid to the module state.""" - self._action_dispatcher.dispatch( - AddAbsorbanceReaderLidAction(module_id=module_id, lid_id=lid_id) - ) - def reset_tips(self, labware_id: str) -> None: """Reset the tip state of a given labware.""" # TODO(mm, 2023-03-10): Safely raise an error if the given labware isn't a @@ -616,6 +627,7 @@ def set_and_start_queue_worker( assert self._queue_worker is None self._queue_worker = create_queue_worker( hardware_api=self._hardware_api, + file_provider=self._file_provider, state_store=self._state_store, action_dispatcher=self._action_dispatcher, command_generator=command_generator, diff --git a/api/src/opentrons/protocol_engine/resources/__init__.py b/api/src/opentrons/protocol_engine/resources/__init__.py index 94b71831589..a77075c95bb 100644 --- a/api/src/opentrons/protocol_engine/resources/__init__.py +++ b/api/src/opentrons/protocol_engine/resources/__init__.py @@ -9,6 +9,7 @@ from .deck_data_provider import DeckDataProvider, DeckFixedLabware from .labware_data_provider import LabwareDataProvider from .module_data_provider import ModuleDataProvider +from .file_provider import FileProvider from .ot3_validation import ensure_ot3_hardware @@ -18,6 +19,7 @@ "DeckDataProvider", "DeckFixedLabware", "ModuleDataProvider", + "FileProvider", "ensure_ot3_hardware", "pipette_data_provider", "labware_validation", diff --git a/api/src/opentrons/protocol_engine/resources/deck_data_provider.py b/api/src/opentrons/protocol_engine/resources/deck_data_provider.py index c67260a8001..72117c23075 100644 --- a/api/src/opentrons/protocol_engine/resources/deck_data_provider.py +++ b/api/src/opentrons/protocol_engine/resources/deck_data_provider.py @@ -17,11 +17,9 @@ DeckSlotLocation, DeckType, LabwareLocation, - AddressableAreaLocation, DeckConfigurationType, ) from .labware_data_provider import LabwareDataProvider -from ..resources import deck_configuration_provider @final @@ -71,43 +69,6 @@ async def get_deck_fixed_labware( slot = cast(Optional[str], fixture.get("slot")) if ( - deck_configuration is not None - and load_name is not None - and slot is not None - and slot not in DeckSlotName._value2member_map_ - ): - # The provided slot is likely to be an addressable area for Module-required labware Eg: Plate Reader Lid - for ( - cutout_id, - cutout_fixture_id, - opentrons_module_serial_number, - ) in deck_configuration: - provided_addressable_areas = ( - deck_configuration_provider.get_provided_addressable_area_names( - cutout_fixture_id=cutout_fixture_id, - cutout_id=cutout_id, - deck_definition=deck_definition, - ) - ) - if slot in provided_addressable_areas: - addressable_area_location = AddressableAreaLocation( - addressableAreaName=slot - ) - definition = await self._labware_data.get_labware_definition( - load_name=load_name, - namespace="opentrons", - version=1, - ) - - labware.append( - DeckFixedLabware( - labware_id=labware_id, - definition=definition, - location=addressable_area_location, - ) - ) - - elif ( load_fixed_trash and load_name is not None and slot is not None diff --git a/api/src/opentrons/protocol_engine/resources/file_provider.py b/api/src/opentrons/protocol_engine/resources/file_provider.py new file mode 100644 index 00000000000..a224e15a1b7 --- /dev/null +++ b/api/src/opentrons/protocol_engine/resources/file_provider.py @@ -0,0 +1,161 @@ +"""File interaction resource provider.""" +from datetime import datetime +from typing import List, Optional, Callable, Awaitable, Dict +from pydantic import BaseModel +from ..errors import StorageLimitReachedError + + +MAXIMUM_CSV_FILE_LIMIT = 400 + + +class GenericCsvTransform: + """Generic CSV File Type data for rows of data to be seperated by a delimeter.""" + + filename: str + rows: List[List[str]] + delimiter: str = "," + + @staticmethod + def build( + filename: str, rows: List[List[str]], delimiter: str = "," + ) -> "GenericCsvTransform": + """Build a Generic CSV datatype class.""" + if "." in filename and not filename.endswith(".csv"): + raise ValueError( + f"Provided filename {filename} invalid. Only CSV file format is accepted." + ) + elif "." not in filename: + filename = f"{filename}.csv" + csv = GenericCsvTransform() + csv.filename = filename + csv.rows = rows + csv.delimiter = delimiter + return csv + + +class ReadData(BaseModel): + """Read Data type containing the wavelength for a Plate Reader read alongside the Measurement Data of that read.""" + + wavelength: int + data: Dict[str, float] + + +class PlateReaderData(BaseModel): + """Data from a Opentrons Plate Reader Read. Can be converted to CSV template format.""" + + read_results: List[ReadData] + reference_wavelength: Optional[int] = None + start_time: datetime + finish_time: datetime + serial_number: str + + def build_generic_csv( # noqa: C901 + self, filename: str, measurement: ReadData + ) -> GenericCsvTransform: + """Builds a CSV compatible object containing Plate Reader Measurements. + + This will also automatically reformat the provided filename to include the wavelength of those measurements. + """ + plate_alpharows = ["A", "B", "C", "D", "E", "F", "G", "H"] + rows = [] + + rows.append(["", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]) + for i in range(8): + row = [plate_alpharows[i]] + for j in range(12): + row.append(str(measurement.data[f"{plate_alpharows[i]}{j+1}"])) + rows.append(row) + for i in range(3): + rows.append([]) + rows.append(["", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]) + for i in range(8): + row = [plate_alpharows[i]] + for j in range(12): + row.append("") + rows.append(row) + for i in range(3): + rows.append([]) + rows.append( + [ + "", + "ID", + "Well", + "Absorbance (OD)", + "Mean Absorbance (OD)", + "Absorbance %CV", + ] + ) + for i in range(3): + rows.append([]) + rows.append( + [ + "", + "ID", + "Well", + "Absorbance (OD)", + "Mean Absorbance (OD)", + "Dilution Factor", + "Absorbance %CV", + ] + ) + rows.append(["1", "Sample 1", "", "", "", "1", "", "", "", "", "", ""]) + for i in range(3): + rows.append([]) + + # end of file metadata + rows.append(["Protocol"]) + rows.append(["Assay"]) + rows.append(["Sample Wavelength (nm)", str(measurement.wavelength)]) + if self.reference_wavelength is not None: + rows.append(["Reference Wavelength (nm)", str(self.reference_wavelength)]) + rows.append(["Serial No.", self.serial_number]) + rows.append( + ["Measurement started at", self.start_time.strftime("%m %d %H:%M:%S %Y")] + ) + rows.append( + ["Measurement finished at", self.finish_time.strftime("%m %d %H:%M:%S %Y")] + ) + + # Ensure the filename adheres to ruleset contains the wavelength for a given measurement + if filename.endswith(".csv"): + filename = filename[:-4] + filename = filename + str(measurement.wavelength) + "nm.csv" + + return GenericCsvTransform.build( + filename=filename, + rows=rows, + delimiter=",", + ) + + +class FileProvider: + """Provider class to wrap file read write interactions to the data files directory in the engine.""" + + def __init__( + self, + data_files_write_csv_callback: Optional[ + Callable[[GenericCsvTransform], Awaitable[str]] + ] = None, + data_files_filecount: Optional[Callable[[], Awaitable[int]]] = None, + ) -> None: + """Initialize the interface callbacks of the File Provider for data file handling within the Protocol Engine. + + Params: + data_files_write_csv_callback: Callback to write a CSV file to the data files directory and add it to the database. + data_files_filecount: Callback to check the amount of data files already present in the data files directory. + """ + self._data_files_write_csv_callback = data_files_write_csv_callback + self._data_files_filecount = data_files_filecount + + async def write_csv(self, write_data: GenericCsvTransform) -> str: + """Writes the provided CSV object to a file in the Data Files directory. Returns the File ID of the file created.""" + if self._data_files_filecount is not None: + file_count = await self._data_files_filecount() + if file_count >= MAXIMUM_CSV_FILE_LIMIT: + raise StorageLimitReachedError( + f"Not enough space to store file {write_data.filename}." + ) + if self._data_files_write_csv_callback is not None: + return await self._data_files_write_csv_callback(write_data) + # If we are in an analysis or simulation state, return an empty file ID + return "" diff --git a/api/src/opentrons/protocol_engine/resources/fixture_validation.py b/api/src/opentrons/protocol_engine/resources/fixture_validation.py index 745df22d712..a17bf147f85 100644 --- a/api/src/opentrons/protocol_engine/resources/fixture_validation.py +++ b/api/src/opentrons/protocol_engine/resources/fixture_validation.py @@ -29,7 +29,12 @@ def is_drop_tip_waste_chute(addressable_area_name: str) -> bool: def is_trash(addressable_area_name: str) -> bool: """Check if an addressable area is a trash bin.""" - return addressable_area_name in {"movableTrash", "fixedTrash", "shortFixedTrash"} + return any( + [ + s in addressable_area_name + for s in {"movableTrash", "fixedTrash", "shortFixedTrash"} + ] + ) def is_staging_slot(addressable_area_name: str) -> bool: diff --git a/api/src/opentrons/protocol_engine/resources/labware_validation.py b/api/src/opentrons/protocol_engine/resources/labware_validation.py index 3b4ed14166c..090723ffb7e 100644 --- a/api/src/opentrons/protocol_engine/resources/labware_validation.py +++ b/api/src/opentrons/protocol_engine/resources/labware_validation.py @@ -27,6 +27,11 @@ def validate_definition_is_adapter(definition: LabwareDefinition) -> bool: return LabwareRole.adapter in definition.allowedRoles +def validate_definition_is_lid(definition: LabwareDefinition) -> bool: + """Validate that one of the definition's allowed roles is `lid`.""" + return LabwareRole.lid in definition.allowedRoles + + def validate_labware_can_be_stacked( top_labware_definition: LabwareDefinition, below_labware_load_name: str ) -> bool: diff --git a/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py b/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py index 43b3be16f38..4df6b0d4d77 100644 --- a/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py +++ b/api/src/opentrons/protocol_engine/resources/pipette_data_provider.py @@ -67,6 +67,9 @@ class LoadedStaticPipetteData: back_left_corner_offset: Point front_right_corner_offset: Point pipette_lld_settings: Optional[Dict[str, Dict[str, float]]] + plunger_positions: Dict[str, float] + shaft_ul_per_mm: float + available_sensors: pipette_definition.AvailableSensorDefinition class VirtualPipetteDataProvider: @@ -193,9 +196,12 @@ def _get_virtual_pipette_static_config_by_model( # noqa: C901 pipette_model.pipette_channels, pipette_model.pipette_version, ) - nozzle_manager = NozzleConfigurationManager.build_from_config( - config, valid_nozzle_maps - ) + if pipette_id not in self._nozzle_manager_layout_by_id: + nozzle_manager = NozzleConfigurationManager.build_from_config( + config, valid_nozzle_maps + ) + else: + nozzle_manager = self._nozzle_manager_layout_by_id[pipette_id] tip_overlap_dict_for_tip_type = None for configuration in ( @@ -249,6 +255,7 @@ def _get_virtual_pipette_static_config_by_model( # noqa: C901 pip_back_left = config.pipette_bounding_box_offsets.back_left_corner pip_front_right = config.pipette_bounding_box_offsets.front_right_corner + plunger_positions = config.plunger_positions_configurations[liquid_class] return LoadedStaticPipetteData( model=str(pipette_model), display_name=config.display_name, @@ -277,6 +284,15 @@ def _get_virtual_pipette_static_config_by_model( # noqa: C901 pip_front_right[0], pip_front_right[1], pip_front_right[2] ), pipette_lld_settings=config.lld_settings, + plunger_positions={ + "top": plunger_positions.top, + "bottom": plunger_positions.bottom, + "blow_out": plunger_positions.blow_out, + "drop_tip": plunger_positions.drop_tip, + }, + shaft_ul_per_mm=config.shaft_ul_per_mm, + available_sensors=config.available_sensors + or pipette_definition.AvailableSensorDefinition(sensors=[]), ) def get_virtual_pipette_static_config( @@ -295,6 +311,11 @@ def get_pipette_static_config( """Get the config for a pipette, given the state/config object from the HW API.""" back_left_offset = pipette_dict["pipette_bounding_box_offsets"].back_left_corner front_right_offset = pipette_dict["pipette_bounding_box_offsets"].front_right_corner + available_sensors = ( + pipette_dict["available_sensors"] + if "available_sensors" in pipette_dict.keys() + else pipette_definition.AvailableSensorDefinition(sensors=[]) + ) return LoadedStaticPipetteData( model=pipette_dict["model"], display_name=pipette_dict["display_name"], @@ -324,6 +345,9 @@ def get_pipette_static_config( front_right_offset[0], front_right_offset[1], front_right_offset[2] ), pipette_lld_settings=pipette_dict["lld_settings"], + plunger_positions=pipette_dict["plunger_positions"], + shaft_ul_per_mm=pipette_dict["shaft_ul_per_mm"], + available_sensors=available_sensors, ) diff --git a/api/src/opentrons/protocol_engine/state/__init__.py b/api/src/opentrons/protocol_engine/state/__init__.py index f9705905967..00043706a6c 100644 --- a/api/src/opentrons/protocol_engine/state/__init__.py +++ b/api/src/opentrons/protocol_engine/state/__init__.py @@ -1,71 +1 @@ """Protocol engine state module.""" - -from .state import State, StateStore, StateView -from .state_summary import StateSummary -from .config import Config -from .commands import ( - CommandState, - CommandView, - CommandSlice, - CommandErrorSlice, - CommandPointer, -) -from .command_history import CommandEntry -from .labware import LabwareState, LabwareView -from .pipettes import PipetteState, PipetteView, HardwarePipette -from .modules import ModuleState, ModuleView, HardwareModule -from .module_substates import ( - MagneticModuleId, - MagneticModuleSubState, - HeaterShakerModuleId, - HeaterShakerModuleSubState, - TemperatureModuleId, - TemperatureModuleSubState, - ThermocyclerModuleId, - ThermocyclerModuleSubState, - ModuleSubStateType, -) -from .geometry import GeometryView -from .motion import MotionView, PipetteLocationData - -__all__ = [ - # top level state value and interfaces - "State", - "StateStore", - "StateView", - "StateSummary", - # static engine configuration - "Config", - # command state and values - "CommandState", - "CommandView", - "CommandSlice", - "CommandErrorSlice", - "CommandPointer", - "CommandEntry", - # labware state and values - "LabwareState", - "LabwareView", - # pipette state and values - "PipetteState", - "PipetteView", - "HardwarePipette", - # module state and values - "ModuleState", - "ModuleView", - "HardwareModule", - "MagneticModuleId", - "MagneticModuleSubState", - "HeaterShakerModuleId", - "HeaterShakerModuleSubState", - "TemperatureModuleId", - "TemperatureModuleSubState", - "ThermocyclerModuleId", - "ThermocyclerModuleSubState", - "ModuleSubStateType", - # computed geometry state - "GeometryView", - # computed motion state - "MotionView", - "PipetteLocationData", -] diff --git a/api/src/opentrons/protocol_engine/state/abstract_store.py b/api/src/opentrons/protocol_engine/state/_abstract_store.py similarity index 100% rename from api/src/opentrons/protocol_engine/state/abstract_store.py rename to api/src/opentrons/protocol_engine/state/_abstract_store.py diff --git a/api/src/opentrons/protocol_engine/state/move_types.py b/api/src/opentrons/protocol_engine/state/_move_types.py similarity index 100% rename from api/src/opentrons/protocol_engine/state/move_types.py rename to api/src/opentrons/protocol_engine/state/_move_types.py diff --git a/api/src/opentrons/protocol_engine/state/_well_math.py b/api/src/opentrons/protocol_engine/state/_well_math.py new file mode 100644 index 00000000000..2d0998580f5 --- /dev/null +++ b/api/src/opentrons/protocol_engine/state/_well_math.py @@ -0,0 +1,193 @@ +"""Utilities for doing coverage math on wells.""" + +from typing import Iterator +from opentrons_shared_data.errors.exceptions import ( + InvalidStoredData, + InvalidProtocolData, +) + +from opentrons.hardware_control.nozzle_manager import NozzleMap + + +def wells_covered_by_pipette_configuration( + nozzle_map: NozzleMap, + target_well: str, + labware_wells_by_column: list[list[str]], +) -> Iterator[str]: + """Compute the wells covered by a pipette nozzle configuration.""" + if len(labware_wells_by_column) >= 12 and len(labware_wells_by_column[0]) >= 8: + yield from wells_covered_dense( + nozzle_map, + target_well, + labware_wells_by_column, + ) + elif len(labware_wells_by_column) < 12 and len(labware_wells_by_column[0]) < 8: + yield from wells_covered_sparse( + nozzle_map, target_well, labware_wells_by_column + ) + else: + raise InvalidStoredData( + "Labware of non-SBS and non-reservoir format cannot be handled" + ) + + +def row_col_ordinals_from_column_major_map( + target_well: str, column_major_wells: list[list[str]] +) -> tuple[int, int]: + """Turn a well name into the index of its row and column (in that order) within the labware.""" + for column_index, column in enumerate(column_major_wells): + if target_well in column: + return column.index(target_well), column_index + raise InvalidStoredData(f"Well name {target_well} is not present in labware") + + +def wells_covered_dense( # noqa: C901 + nozzle_map: NozzleMap, target_well: str, target_wells_by_column: list[list[str]] +) -> Iterator[str]: + """Get the list of wells covered by a nozzle map on an SBS format labware with a specified multiplier of 96 into the number of wells. + + This will handle the offsetting of the nozzle map into higher-density well plates. For instance, a full column config target at A1 of a + 96 plate would cover wells A1, B1, C1, D1, E1, F1, G1, H1, and use downsample_factor 1.0 (96*1 = 96). A full column config target on a + 384 plate would cover wells A1, C1, E1, G1, I1, K1, M1, O1 and use downsample_factor 4.0 (96*4 = 384), while a full column config + targeting B1 would cover wells B1, D1, F1, H1, J1, L1, N1, P1 - still using downsample_factor 4.0, with the offset gathered from the + target well. + + The function may also handle sub-96 regular labware with fractional downsample factors, but that's physically improbable and it's not + tested. If you have a regular labware with fewer than 96 wells that is still regularly-spaced and has little enough space between well + walls that it's reasonable to use with multiple channels, you probably want wells_covered_trough. + """ + target_row_index, target_column_index = row_col_ordinals_from_column_major_map( + target_well, target_wells_by_column + ) + column_downsample = len(target_wells_by_column) // 12 + row_downsample = len(target_wells_by_column[0]) // 8 + if column_downsample < 1 or row_downsample < 1: + raise InvalidStoredData( + "This labware cannot be used wells_covered_dense because it is less dense than an SBS 96 standard" + ) + + for nozzle_column in range(len(nozzle_map.columns)): + target_column_offset = nozzle_column * column_downsample + for nozzle_row in range(len(nozzle_map.rows)): + target_row_offset = nozzle_row * row_downsample + if nozzle_map.starting_nozzle == "A1": + if ( + target_column_index + target_column_offset + < len(target_wells_by_column) + ) and ( + target_row_index + target_row_offset + < len(target_wells_by_column[target_column_index]) + ): + yield target_wells_by_column[ + target_column_index + target_column_offset + ][target_row_index + target_row_offset] + elif nozzle_map.starting_nozzle == "A12": + if (target_column_index - target_column_offset >= 0) and ( + target_row_index + target_row_offset + < len(target_wells_by_column[target_column_index]) + ): + yield target_wells_by_column[ + target_column_index - target_column_offset + ][target_row_index + target_row_offset] + elif nozzle_map.starting_nozzle == "H1": + if ( + target_column_index + target_column_offset + < len(target_wells_by_column) + ) and (target_row_index - target_row_offset >= 0): + yield target_wells_by_column[ + target_column_index + target_column_offset + ][target_row_index - target_row_offset] + elif nozzle_map.starting_nozzle == "H12": + if (target_column_index - target_column_offset >= 0) and ( + target_row_index - target_row_offset >= 0 + ): + yield target_wells_by_column[ + target_column_index - target_column_offset + ][target_row_index - target_row_offset] + else: + raise InvalidProtocolData( + f"A pipette nozzle configuration may not having a starting nozzle of {nozzle_map.starting_nozzle}" + ) + + +def wells_covered_sparse( # noqa: C901 + nozzle_map: NozzleMap, target_well: str, target_wells_by_column: list[list[str]] +) -> Iterator[str]: + """Get the list of wells covered by a nozzle map on a column-oriented reservoir. + + This function handles reservoirs whose wells span multiple rows and columns - the most common case is something like a + 12-well reservoir, whose wells are the height of an SBS column and the width of an SBS row, or a 1-well reservoir whose well + is the size of an SBS active area. + """ + target_row_index, target_column_index = row_col_ordinals_from_column_major_map( + target_well, target_wells_by_column + ) + column_upsample = 12 // len(target_wells_by_column) + row_upsample = 8 // len(target_wells_by_column[0]) + if column_upsample < 1 or row_upsample < 1: + raise InvalidStoredData( + "This labware cannot be used with wells_covered_sparse because it is more dense than an SBS 96 standard." + ) + for nozzle_column in range(max(1, len(nozzle_map.columns) // column_upsample)): + for nozzle_row in range(max(1, len(nozzle_map.rows) // row_upsample)): + if nozzle_map.starting_nozzle == "A1": + if ( + target_column_index + nozzle_column < len(target_wells_by_column) + ) and ( + target_row_index + nozzle_row + < len(target_wells_by_column[target_column_index]) + ): + yield target_wells_by_column[target_column_index + nozzle_column][ + target_row_index + nozzle_row + ] + elif nozzle_map.starting_nozzle == "A12": + if (target_column_index - nozzle_column >= 0) and ( + target_row_index + nozzle_row + < len(target_wells_by_column[target_column_index]) + ): + yield target_wells_by_column[target_column_index - nozzle_column][ + target_row_index + nozzle_row + ] + elif nozzle_map.starting_nozzle == "H1": + if ( + target_column_index + nozzle_column + < len(target_wells_by_column[target_column_index]) + ) and (target_row_index - nozzle_row >= 0): + yield target_wells_by_column[target_column_index + nozzle_column][ + target_row_index - nozzle_row + ] + elif nozzle_map.starting_nozzle == "H12": + if (target_column_index - nozzle_column >= 0) and ( + target_row_index - nozzle_row >= 0 + ): + yield target_wells_by_column[target_column_index - nozzle_column][ + target_row_index - nozzle_row + ] + else: + raise InvalidProtocolData( + f"A pipette nozzle configuration may not having a starting nozzle of {nozzle_map.starting_nozzle}" + ) + + +def nozzles_per_well( + nozzle_map: NozzleMap, target_well: str, target_wells_by_column: list[list[str]] +) -> int: + """Get the number of nozzles that will interact with each well in the labware. + + For instance, if this is an SBS 96 or more dense, there is always 1 nozzle per well + that is interacted with (and some wells may not be interacted with at all). If this is + a 12-column reservoir, then all active nozzles in each column of the configuration will + interact with each well; so an 8-channel full config would have 8 nozzles per well, + and a 96 channel with a rectangle config from A1 to D12 would have 4 nozzles per well. + """ + _, target_column_index = row_col_ordinals_from_column_major_map( + target_well, target_wells_by_column + ) + # labware as or more dense than a 96 plate will only ever have 1 nozzle per well (and some wells won't be touched) + if len(target_wells_by_column) >= len(nozzle_map.columns) and len( + target_wells_by_column[target_column_index] + ) >= len(nozzle_map.rows): + return 1 + return max(1, len(nozzle_map.columns) // len(target_wells_by_column)) * max( + 1, len(nozzle_map.rows) // len(target_wells_by_column[target_column_index]) + ) diff --git a/api/src/opentrons/protocol_engine/state/addressable_areas.py b/api/src/opentrons/protocol_engine/state/addressable_areas.py index ab9c3d8462d..bd7d8de0188 100644 --- a/api/src/opentrons/protocol_engine/state/addressable_areas.py +++ b/api/src/opentrons/protocol_engine/state/addressable_areas.py @@ -43,7 +43,7 @@ AddAddressableAreaAction, ) from .config import Config -from .abstract_store import HasState, HandlesActions +from ._abstract_store import HasState, HandlesActions @dataclass @@ -323,7 +323,7 @@ def _validate_addressable_area_for_simulation( return cutout_id -class AddressableAreaView(HasState[AddressableAreaState]): +class AddressableAreaView: """Read-only addressable area state view.""" _state: AddressableAreaState @@ -345,13 +345,27 @@ def deck_extents(self) -> Point: @cached_property def mount_offsets(self) -> Dict[str, Point]: """The left and right mount offsets of the robot.""" - left_offset = self.state.robot_definition["mountOffsets"]["left"] - right_offset = self.state.robot_definition["mountOffsets"]["right"] + left_offset = self._state.robot_definition["mountOffsets"]["left"] + right_offset = self._state.robot_definition["mountOffsets"]["right"] return { "left": Point(x=left_offset[0], y=left_offset[1], z=left_offset[2]), "right": Point(x=right_offset[0], y=right_offset[1], z=right_offset[2]), } + @cached_property + def padding_offsets(self) -> Dict[str, float]: + """The padding offsets to be applied to the deck extents of the robot.""" + rear_offset = self._state.robot_definition["paddingOffsets"]["rear"] + front_offset = self._state.robot_definition["paddingOffsets"]["front"] + left_side_offset = self._state.robot_definition["paddingOffsets"]["leftSide"] + right_side_offset = self._state.robot_definition["paddingOffsets"]["rightSide"] + return { + "rear": rear_offset, + "front": front_offset, + "left_side": left_side_offset, + "right_side": right_side_offset, + } + def get_addressable_area(self, addressable_area_name: str) -> AddressableArea: """Get addressable area.""" if not self._state.use_simulated_deck_config: @@ -406,12 +420,12 @@ def _check_if_area_is_compatible_with_potential_fixtures( _get_conflicting_addressable_areas_error_string( self._state.potential_cutout_fixtures_by_cutout_id[cutout_id], self._state.loaded_addressable_areas_by_name, - self.state.deck_definition, + self._state.deck_definition, ) ) area_display_name = ( deck_configuration_provider.get_addressable_area_display_name( - area_name, self.state.deck_definition + area_name, self._state.deck_definition ) ) raise IncompatibleAddressableAreaError( @@ -490,7 +504,7 @@ def get_addressable_area_offsets_from_cutout( addressable_area_name: str, ) -> Point: """Get the offset form cutout fixture of an addressable area.""" - for addressable_area in self.state.deck_definition["locations"][ + for addressable_area in self._state.deck_definition["locations"][ "addressableAreas" ]: if addressable_area["id"] == addressable_area_name: @@ -554,7 +568,7 @@ def get_fixture_by_deck_slot_name( self, slot_name: DeckSlotName ) -> Optional[CutoutFixture]: """Get the Cutout Fixture currently loaded where a specific Deck Slot would be.""" - deck_config = self.state.deck_configuration + deck_config = self._state.deck_configuration if deck_config: slot_cutout_id = DECK_SLOT_TO_CUTOUT_MAP[slot_name] slot_cutout_fixture = None @@ -567,7 +581,7 @@ def get_fixture_by_deck_slot_name( if cutout_id == slot_cutout_id: slot_cutout_fixture = ( deck_configuration_provider.get_cutout_fixture( - cutout_fixture_id, self.state.deck_definition + cutout_fixture_id, self._state.deck_definition ) ) return slot_cutout_fixture @@ -591,7 +605,7 @@ def get_fixture_serial_from_deck_configuration_by_deck_slot( self, slot_name: DeckSlotName ) -> Optional[str]: """Get the serial number provided by the deck configuration for a Fixture at a given location.""" - deck_config = self.state.deck_configuration + deck_config = self._state.deck_configuration if deck_config: slot_cutout_id = DECK_SLOT_TO_CUTOUT_MAP[slot_name] # This will only ever be one under current assumptions diff --git a/api/src/opentrons/protocol_engine/state/command_history.py b/api/src/opentrons/protocol_engine/state/command_history.py index adebadd64bc..0879a7cd130 100644 --- a/api/src/opentrons/protocol_engine/state/command_history.py +++ b/api/src/opentrons/protocol_engine/state/command_history.py @@ -24,6 +24,12 @@ class CommandHistory: _all_command_ids: List[str] """All command IDs, in insertion order.""" + _all_failed_command_ids: List[str] + """All failed command IDs, in insertion order.""" + + _all_command_ids_but_fixit_command_ids: List[str] + """All command IDs besides fixit command intents, in insertion order.""" + _commands_by_id: Dict[str, CommandEntry] """All command resources, in insertion order, mapped by their unique IDs.""" @@ -44,6 +50,8 @@ class CommandHistory: def __init__(self) -> None: self._all_command_ids = [] + self._all_failed_command_ids = [] + self._all_command_ids_but_fixit_command_ids = [] self._queued_command_ids = OrderedSet() self._queued_setup_command_ids = OrderedSet() self._queued_fixit_command_ids = OrderedSet() @@ -97,13 +105,33 @@ def get_all_commands(self) -> List[Command]: for command_id in self._all_command_ids ] + def get_all_failed_commands(self) -> List[Command]: + """Get all failed commands.""" + return [ + self._commands_by_id[command_id].command + for command_id in self._all_failed_command_ids + ] + + def get_filtered_command_ids(self, include_fixit_commands: bool) -> List[str]: + """Get all fixit command IDs.""" + if include_fixit_commands: + return self._all_command_ids + else: + return self._all_command_ids_but_fixit_command_ids + def get_all_ids(self) -> List[str]: """Get all command IDs.""" return self._all_command_ids - def get_slice(self, start: int, stop: int) -> List[Command]: - """Get a list of commands between start and stop.""" + def get_slice( + self, start: int, stop: int, command_ids: Optional[list[str]] = None + ) -> List[Command]: + """Get a list of commands between start and stop.""" """Get a list of commands between start and stop.""" commands = self._all_command_ids[start:stop] + selected_command_ids = ( + command_ids if command_ids is not None else self._all_command_ids + ) + commands = selected_command_ids[start:stop] return [self._commands_by_id[command].command for command in commands] def get_tail_command(self) -> Optional[CommandEntry]: @@ -225,11 +253,14 @@ def set_command_failed(self, command: Command) -> None: self._remove_queue_id(command.id) self._remove_setup_queue_id(command.id) self._set_most_recently_completed_command_id(command.id) + self._all_failed_command_ids.append(command.id) def _add(self, command_id: str, command_entry: CommandEntry) -> None: """Create or update a command entry.""" if command_id not in self._commands_by_id: self._all_command_ids.append(command_id) + if command_entry.command.intent != CommandIntent.FIXIT: + self._all_command_ids_but_fixit_command_ids.append(command_id) self._commands_by_id[command_id] = command_entry def _add_to_queue(self, command_id: str) -> None: diff --git a/api/src/opentrons/protocol_engine/state/commands.py b/api/src/opentrons/protocol_engine/state/commands.py index c725c561ac3..8aa71c9c1f8 100644 --- a/api/src/opentrons/protocol_engine/state/commands.py +++ b/api/src/opentrons/protocol_engine/state/commands.py @@ -17,11 +17,15 @@ RunCommandAction, SetErrorRecoveryPolicyAction, ) +from opentrons.protocol_engine.commands.unsafe.unsafe_ungrip_labware import ( + UnsafeUngripLabwareCommandType, +) from opentrons.protocol_engine.error_recovery_policy import ( ErrorRecoveryPolicy, ErrorRecoveryType, ) from opentrons.protocol_engine.notes.notes import CommandNote +from opentrons.protocol_engine.state import update_types from ..actions import ( Action, @@ -36,7 +40,7 @@ DoorChangeAction, ) -from ..commands import Command, CommandStatus, CommandIntent +from ..commands import Command, CommandStatus, CommandIntent, CommandCreate from ..errors import ( RunStoppedError, ErrorOccurrence, @@ -49,7 +53,7 @@ ProtocolCommandFailedError, ) from ..types import EngineStatus -from .abstract_store import HasState, HandlesActions +from ._abstract_store import HasState, HandlesActions from .command_history import ( CommandEntry, CommandHistory, @@ -95,7 +99,9 @@ class QueueStatus(enum.Enum): AWAITING_RECOVERY_PAUSED = enum.auto() """Execution of fixit commands has been paused. - New protocol and fixit commands may be enqueued, but will wait to execute. + New protocol and fixit commands may be enqueued, but will usually wait to execute. + There are certain exceptions where fixit commands will still run. + New setup commands may not be enqueued. """ @@ -136,6 +142,16 @@ class CommandPointer: index: int +@dataclass(frozen=True) +class _RecoveryTargetInfo: + """Info about the failed command that we're currently recovering from.""" + + command_id: str + + state_update_if_false_positive: update_types.StateUpdate + """See `CommandView.get_state_update_if_continued()`.""" + + @dataclass class CommandState: """State of all protocol engine command resources.""" @@ -200,8 +216,8 @@ class CommandState: stable. Eventually, we might want this info to be stored directly on each command. """ - recovery_target_command_id: Optional[str] - """If we're currently recovering from a command failure, which command it was.""" + recovery_target: Optional[_RecoveryTargetInfo] + """If we're currently recovering from a command failure, info about that command.""" finish_error: Optional[ErrorOccurrence] """The error that happened during the post-run finish steps (homing & dropping tips), if any.""" @@ -212,9 +228,6 @@ class CommandState: This value can be used to generate future hashes. """ - failed_command_errors: List[ErrorOccurrence] - """List of command errors that occurred during run execution.""" - has_entered_error_recovery: bool """Whether the run has entered error recovery.""" @@ -248,12 +261,11 @@ def __init__( finish_error=None, failed_command=None, command_error_recovery_types={}, - recovery_target_command_id=None, + recovery_target=None, run_completed_at=None, run_started_at=None, latest_protocol_command_hash=None, stopped_by_estop=False, - failed_command_errors=[], error_recovery_policy=error_recovery_policy, has_entered_error_recovery=False, ) @@ -330,14 +342,17 @@ def _handle_succeed_command_action(self, action: SucceedCommandAction) -> None: def _handle_fail_command_action(self, action: FailCommandAction) -> None: prev_entry = self.state.command_history.get(action.command_id) - if isinstance(action.error, EnumeratedError): + if isinstance(action.error, EnumeratedError): # The error was undefined. public_error_occurrence = ErrorOccurrence.from_failed( id=action.error_id, createdAt=action.failed_at, error=action.error, ) - else: + # An empty state update, to no-op. + state_update_if_false_positive = update_types.StateUpdate() + else: # The error was defined. public_error_occurrence = action.error.public + state_update_if_false_positive = action.error.state_update_if_false_positive self._update_to_failed( command_id=action.command_id, @@ -347,8 +362,20 @@ def _handle_fail_command_action(self, action: FailCommandAction) -> None: notes=action.notes, ) self._state.failed_command = self._state.command_history.get(action.command_id) - self._state.failed_command_errors.append(public_error_occurrence) + if ( + prev_entry.command.intent in (CommandIntent.PROTOCOL, None) + and action.type == ErrorRecoveryType.WAIT_FOR_RECOVERY + ): + self._state.queue_status = QueueStatus.AWAITING_RECOVERY + self._state.recovery_target = _RecoveryTargetInfo( + command_id=action.command_id, + state_update_if_false_positive=state_update_if_false_positive, + ) + self._state.has_entered_error_recovery = True + + # When one command fails, we generally also cancel the commands that + # would have been queued after it. other_command_ids_to_fail: List[str] if prev_entry.command.intent == CommandIntent.SETUP: other_command_ids_to_fail = list( @@ -368,7 +395,8 @@ def _handle_fail_command_action(self, action: FailCommandAction) -> None: ) elif ( action.type == ErrorRecoveryType.WAIT_FOR_RECOVERY - or action.type == ErrorRecoveryType.IGNORE_AND_CONTINUE + or action.type == ErrorRecoveryType.CONTINUE_WITH_ERROR + or action.type == ErrorRecoveryType.ASSUME_FALSE_POSITIVE_AND_CONTINUE ): other_command_ids_to_fail = [] else: @@ -385,14 +413,6 @@ def _handle_fail_command_action(self, action: FailCommandAction) -> None: notes=None, ) - if ( - prev_entry.command.intent in (CommandIntent.PROTOCOL, None) - and action.type == ErrorRecoveryType.WAIT_FOR_RECOVERY - ): - self._state.queue_status = QueueStatus.AWAITING_RECOVERY - self._state.recovery_target_command_id = action.command_id - self._state.has_entered_error_recovery = True - def _handle_play_action(self, action: PlayAction) -> None: if not self._state.run_result: self._state.run_started_at = ( @@ -420,13 +440,13 @@ def _handle_resume_from_recovery_action( self, action: ResumeFromRecoveryAction ) -> None: self._state.queue_status = QueueStatus.RUNNING - self._state.recovery_target_command_id = None + self._state.recovery_target = None def _handle_stop_action(self, action: StopAction) -> None: if not self._state.run_result: - self._state.recovery_target_command_id = None - + self._state.recovery_target = None self._state.queue_status = QueueStatus.PAUSED + if action.from_estop: self._state.stopped_by_estop = True self._state.run_result = RunResult.FAILED @@ -435,7 +455,9 @@ def _handle_stop_action(self, action: StopAction) -> None: def _handle_finish_action(self, action: FinishAction) -> None: if not self._state.run_result: + self._state.recovery_target = None self._state.queue_status = QueueStatus.PAUSED + if action.set_run_status: self._state.run_result = ( RunResult.SUCCEEDED @@ -557,7 +579,7 @@ def _map_finish_exception_to_error_occurrence( ) -class CommandView(HasState[CommandState]): +class CommandView: """Read-only command state view.""" _state: CommandState @@ -580,18 +602,19 @@ def get_all(self) -> List[Command]: return self._state.command_history.get_all_commands() def get_slice( - self, - cursor: Optional[int], - length: int, + self, cursor: Optional[int], length: int, include_fixit_commands: bool ) -> CommandSlice: """Get a subset of commands around a given cursor. If the cursor is omitted, a cursor will be selected automatically based on the currently running or most recently executed command. """ + command_ids = self._state.command_history.get_filtered_command_ids( + include_fixit_commands=include_fixit_commands + ) running_command = self._state.command_history.get_running_command() queued_command_ids = self._state.command_history.get_queue_ids() - total_length = self._state.command_history.length() + total_length = len(command_ids) # TODO(mm, 2024-05-17): This looks like it's attempting to do the same thing # as self.get_current(), but in a different way. Can we unify them? @@ -620,7 +643,9 @@ def get_slice( # start is inclusive, stop is exclusive actual_cursor = max(0, min(cursor, total_length - 1)) stop = min(total_length, actual_cursor + length) - commands = self._state.command_history.get_slice(start=actual_cursor, stop=stop) + commands = self._state.command_history.get_slice( + start=actual_cursor, stop=stop, command_ids=command_ids + ) return CommandSlice( commands=commands, @@ -676,7 +701,12 @@ def get_error(self) -> Optional[ErrorOccurrence]: def get_all_errors(self) -> List[ErrorOccurrence]: """Get the run's full error list, if there was none, returns an empty list.""" - return self._state.failed_command_errors + failed_commands = self._state.command_history.get_all_failed_commands() + return [ + command_error.error + for command_error in failed_commands + if command_error.error is not None + ] def get_has_entered_recovery_mode(self) -> bool: """Get whether the run has entered recovery mode.""" @@ -737,6 +767,12 @@ def get_next_to_execute(self) -> Optional[str]: next_fixit_cmd = self._state.command_history.get_fixit_queue_ids().head(None) if next_fixit_cmd and self._state.queue_status == QueueStatus.AWAITING_RECOVERY: return next_fixit_cmd + if ( + next_fixit_cmd + and self._state.queue_status == QueueStatus.AWAITING_RECOVERY_PAUSED + and self._may_run_with_door_open(fixit_command=self.get(next_fixit_cmd)) + ): + return next_fixit_cmd # if there is a setup command queued, prioritize it next_setup_cmd = self._state.command_history.get_setup_queue_ids().head(None) @@ -852,11 +888,11 @@ def get_all_commands_final(self) -> bool: def get_recovery_target(self) -> Optional[CommandPointer]: """Return the command currently undergoing error recovery, if any.""" - recovery_target_command_id = self._state.recovery_target_command_id - if recovery_target_command_id is None: + recovery_target = self._state.recovery_target + if recovery_target is None: return None else: - entry = self._state.command_history.get(recovery_target_command_id) + entry = self._state.command_history.get(recovery_target.command_id) return CommandPointer( command_id=entry.command.id, command_key=entry.command.key, @@ -880,7 +916,7 @@ def raise_fatal_command_error(self) -> None: fatal error of the overall run coming from anywhere in the Python script, including in between commands. """ - failed_command = self.state.failed_command + failed_command = self._state.failed_command if ( failed_command and failed_command.command.error @@ -896,12 +932,16 @@ def get_error_recovery_type(self, command_id: str) -> ErrorRecoveryType: The command ID is assumed to point to a failed command. """ - return self.state.command_error_recovery_types[command_id] + return self._state.command_error_recovery_types[command_id] def get_is_stopped(self) -> bool: """Get whether an engine stop has completed.""" return self._state.run_completed_at is not None + def get_is_stopped_by_estop(self) -> bool: + """Return whether the engine was stopped specifically by an E-stop.""" + return self._state.stopped_by_estop + def has_been_played(self) -> bool: """Get whether engine has started.""" return self._state.run_started_at is not None @@ -967,12 +1007,23 @@ def validate_action_allowed( # noqa: C901 "Setup commands are not allowed after run has started." ) elif action.request.intent == CommandIntent.FIXIT: - if self._state.queue_status != QueueStatus.AWAITING_RECOVERY: + if self.get_status() == EngineStatus.AWAITING_RECOVERY: + return action + elif self.get_status() in ( + EngineStatus.AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, + EngineStatus.AWAITING_RECOVERY_PAUSED, + ): + if self._may_run_with_door_open(fixit_command=action.request): + return action + else: + raise FixitCommandNotAllowedError( + f"{action.request.commandType} fixit command may not run" + " until the door is closed and the run is played again." + ) + else: raise FixitCommandNotAllowedError( "Fixit commands are not allowed when the run is not in a recoverable state." ) - else: - return action else: return action @@ -1057,3 +1108,35 @@ def get_error_recovery_policy(self) -> ErrorRecoveryPolicy: higher-level code. """ return self._state.error_recovery_policy + + def get_state_update_for_false_positive(self) -> update_types.StateUpdate: + """Return the state update for if the current recovery target was a false positive. + + If we're currently in error recovery mode, and you have decided that the + underlying command error was a false positive, this returns a state update + that will undo the error's effects on engine state. + See `ProtocolEngine.resume_from_recovery(reconcile_false_positive=True)`. + """ + if self._state.recovery_target is None: + return update_types.StateUpdate() # Empty/no-op. + else: + return self._state.recovery_target.state_update_if_false_positive + + def _may_run_with_door_open( + self, *, fixit_command: Command | CommandCreate + ) -> bool: + """Return whether the given fixit command is exempt from the usual open-door auto pause. + + This is required for certain error recovery flows, where we want the robot to + do stuff while the door is open. + """ + # CommandIntent.PROTOCOL and CommandIntent.SETUP have their own rules for whether + # they run while the door is open. Passing one of those commands to this function + # is probably a mistake in the caller's logic. + assert fixit_command.intent == CommandIntent.FIXIT + + # This type annotation is to make sure the string constant stays in sync and isn't typo'd. + required_command_type: UnsafeUngripLabwareCommandType = "unsafe/ungripLabware" + # todo(mm, 2024-10-04): Instead of allowlisting command types, maybe we should + # add a `mayRunWithDoorOpen: bool` field to command requests. + return fixit_command.commandType == required_command_type diff --git a/api/src/opentrons/protocol_engine/state/files.py b/api/src/opentrons/protocol_engine/state/files.py new file mode 100644 index 00000000000..bd54d58a4f8 --- /dev/null +++ b/api/src/opentrons/protocol_engine/state/files.py @@ -0,0 +1,57 @@ +"""Basic protocol engine create file data state and store.""" +from dataclasses import dataclass +from typing import List + +from opentrons.protocol_engine.actions.get_state_update import get_state_updates +from opentrons.protocol_engine.state import update_types + +from ._abstract_store import HasState, HandlesActions +from ..actions import Action + + +@dataclass +class FileState: + """State of Engine created files.""" + + file_ids: List[str] + + +class FileStore(HasState[FileState], HandlesActions): + """File state container.""" + + _state: FileState + + def __init__(self) -> None: + """Initialize a File store and its state.""" + self._state = FileState(file_ids=[]) + + def handle_action(self, action: Action) -> None: + """Modify state in reaction to an action.""" + for state_update in get_state_updates(action): + self._handle_state_update(state_update) + + def _handle_state_update(self, state_update: update_types.StateUpdate) -> None: + if state_update.files_added != update_types.NO_CHANGE: + self._state.file_ids.extend(state_update.files_added.file_ids) + + +class FileView: + """Read-only engine created file state view.""" + + _state: FileState + + def __init__(self, state: FileState) -> None: + """Initialize the view of file state. + + Arguments: + state: File state dataclass used for tracking file creation status. + """ + self._state = state + + def get_filecount(self) -> int: + """Get the number of files currently created by the protocol.""" + return len(self._state.file_ids) + + def get_file_id_list(self) -> List[str]: + """Get the list of files by file ID created by the protocol.""" + return self._state.file_ids diff --git a/api/src/opentrons/protocol_engine/state/fluid_stack.py b/api/src/opentrons/protocol_engine/state/fluid_stack.py new file mode 100644 index 00000000000..95465e531b2 --- /dev/null +++ b/api/src/opentrons/protocol_engine/state/fluid_stack.py @@ -0,0 +1,138 @@ +"""Implements fluid stack tracking for pipettes. + +Inside a pipette's tip, there can be a mix of kinds of fluids - here, "fluid" means "liquid" (i.e. a protocol-relevant +working liquid that is aspirated or dispensed from wells) or "air" (i.e. because there was an air gap). Since sometimes +you want air gaps in different places - physically-below liquid to prevent dripping, physically-above liquid to provide +extra room to push the plunger - we need to support some notion of at least phsyical ordinal position of air and liquid, +and we do so as a logical stack because that's physically relevant. +""" +from logging import getLogger +from numpy import isclose +from ..types import AspiratedFluid, FluidKind + +_LOG = getLogger(__name__) + + +class FluidStack: + """A FluidStack data structure is a list of AspiratedFluids, with stack-style (last-in-first-out) ordering. + + The front of the list is the physical-top of the liquid stack (logical-bottom of the stack data structure) + and the back of the list is the physical-bottom of the liquid stack (logical-top of the stack data structure). + The state is internal and the interaction surface is the methods. This is a mutating API. + """ + + _FluidStack = list[AspiratedFluid] + + _fluid_stack: _FluidStack + + def __init__(self, _fluid_stack: _FluidStack | None = None) -> None: + """Build a FluidStack. + + The argument is provided for testing and shouldn't be generally used. + """ + self._fluid_stack = _fluid_stack or [] + + def add_fluid(self, new: AspiratedFluid) -> None: + """Add fluid to a stack. + + If the new fluid is of a different kind than what's on the physical-bottom of the stack, add a new record. + If the new fluid is of the same kind as what's on the physical-bottom of the stack, add the new volume to + the same record. + """ + if len(self._fluid_stack) == 0 or self._fluid_stack[-1].kind != new.kind: + # this is a new kind of fluid, append the record + self._fluid_stack.append(new) + else: + # this is more of the same kind of fluid, add the volumes + old_fluid = self._fluid_stack.pop(-1) + self._fluid_stack.append( + AspiratedFluid(kind=new.kind, volume=old_fluid.volume + new.volume) + ) + + def _alter_fluid_records( + self, remove: int, new_last: AspiratedFluid | None + ) -> None: + if remove >= len(self._fluid_stack) or len(self._fluid_stack) == 0: + self._fluid_stack = [] + return + if remove != 0: + removed = self._fluid_stack[:-remove] + else: + removed = self._fluid_stack + if new_last: + removed[-1] = new_last + self._fluid_stack = removed + + def remove_fluid(self, volume: float) -> None: + """Remove a specific amount of fluid from the physical-bottom of the stack. + + This will consume records that are wholly included in the provided volume and alter the remaining + final records (if any) to decrement the amount of volume removed from it. + + This function is designed to be used inside pipette store action handlers, which are generally not + exception-safe, and therefore swallows and logs errors. + """ + self._fluid_stack_iterator = reversed(self._fluid_stack) + removed_elements: list[AspiratedFluid] = [] + while volume > 0: + try: + last_stack_element = next(self._fluid_stack_iterator) + except StopIteration: + _LOG.error( + f"Attempting to remove more fluid than present, {volume}uL left over" + ) + self._alter_fluid_records(len(removed_elements), None) + return + if last_stack_element.volume < volume: + removed_elements.append(last_stack_element) + volume -= last_stack_element.volume + elif isclose(last_stack_element.volume, volume): + self._alter_fluid_records(len(removed_elements) + 1, None) + return + else: + self._alter_fluid_records( + len(removed_elements), + AspiratedFluid( + kind=last_stack_element.kind, + volume=last_stack_element.volume - volume, + ), + ) + return + + _LOG.error(f"Failed to handle removing {volume}uL from {self._fluid_stack}") + + def aspirated_volume(self, kind: FluidKind | None = None) -> float: + """Measure the total amount of fluid (optionally filtered by kind) in the stack.""" + volume = 0.0 + for el in self._fluid_stack: + if kind is not None and el.kind != kind: + continue + volume += el.volume + return volume + + def liquid_part_of_dispense_volume(self, volume: float) -> float: + """Get the amount of liquid in the specified volume starting at the physical-bottom of the stack.""" + liquid_volume = 0.0 + for el in reversed(self._fluid_stack): + if el.kind == FluidKind.LIQUID: + liquid_volume += min(volume, el.volume) + volume -= min(el.volume, volume) + if isclose(volume, 0.0): + return liquid_volume + return liquid_volume + + def __eq__(self, other: object) -> bool: + """Equality.""" + if isinstance(other, type(self)): + return other._fluid_stack == self._fluid_stack + return False + + def __repr__(self) -> str: + """String representation of a fluid stack.""" + if self._fluid_stack: + stringified_stack = ( + f'(top) {", ".join([str(item) for item in self._fluid_stack])} (bottom)' + ) + else: + stringified_stack = "empty" + return f"<{self.__class__.__name__}: {stringified_stack}>" diff --git a/api/src/opentrons/protocol_engine/state/frustum_helpers.py b/api/src/opentrons/protocol_engine/state/frustum_helpers.py new file mode 100644 index 00000000000..83499fb2510 --- /dev/null +++ b/api/src/opentrons/protocol_engine/state/frustum_helpers.py @@ -0,0 +1,465 @@ +"""Helper functions for liquid-level related calculations inside a given frustum.""" +from typing import List, Tuple +from numpy import pi, iscomplex, roots, real +from math import isclose + +from ..errors.exceptions import InvalidLiquidHeightFound + +from opentrons_shared_data.labware.labware_definition import ( + InnerWellGeometry, + WellSegment, + SphericalSegment, + ConicalFrustum, + CuboidalFrustum, + SquaredConeSegment, +) + + +def _reject_unacceptable_heights( + potential_heights: List[float], max_height: float +) -> float: + """Reject any solutions to a polynomial equation that cannot be the height of a frustum.""" + valid_heights: List[float] = [] + for root in potential_heights: + # reject any heights that are negative or greater than the max height + if not iscomplex(root): + # take only the real component of the root and round to 4 decimal places + rounded_root = round(real(root), 4) + if (rounded_root <= max_height) and (rounded_root >= 0): + if not any([isclose(rounded_root, height) for height in valid_heights]): + valid_heights.append(rounded_root) + if len(valid_heights) != 1: + raise InvalidLiquidHeightFound( + message="Unable to estimate valid liquid height from volume." + ) + return valid_heights[0] + + +def _cross_section_area_circular(diameter: float) -> float: + """Get the area of a circular cross-section.""" + radius = diameter / 2 + return pi * (radius**2) + + +def _cross_section_area_rectangular(x_dimension: float, y_dimension: float) -> float: + """Get the area of a rectangular cross-section.""" + return x_dimension * y_dimension + + +def _rectangular_frustum_polynomial_roots( + bottom_length: float, + bottom_width: float, + top_length: float, + top_width: float, + total_frustum_height: float, +) -> Tuple[float, float, float]: + """Polynomial representation of the volume of a rectangular frustum.""" + # roots of the polynomial with shape ax^3 + bx^2 + cx + a = ( + (top_length - bottom_length) + * (top_width - bottom_width) + / (3 * total_frustum_height**2) + ) + b = ( + (bottom_length * (top_width - bottom_width)) + + (bottom_width * (top_length - bottom_length)) + ) / (2 * total_frustum_height) + c = bottom_length * bottom_width + return a, b, c + + +def _circular_frustum_polynomial_roots( + bottom_radius: float, + top_radius: float, + total_frustum_height: float, +) -> Tuple[float, float, float]: + """Polynomial representation of the volume of a circular frustum.""" + # roots of the polynomial with shape ax^3 + bx^2 + cx + a = pi * ((top_radius - bottom_radius) ** 2) / (3 * total_frustum_height**2) + b = pi * bottom_radius * (top_radius - bottom_radius) / total_frustum_height + c = pi * bottom_radius**2 + return a, b, c + + +def _volume_from_height_circular( + target_height: float, + total_frustum_height: float, + bottom_radius: float, + top_radius: float, +) -> float: + """Find the volume given a height within a circular frustum.""" + a, b, c = _circular_frustum_polynomial_roots( + bottom_radius=bottom_radius, + top_radius=top_radius, + total_frustum_height=total_frustum_height, + ) + volume = a * (target_height**3) + b * (target_height**2) + c * target_height + return volume + + +def _volume_from_height_rectangular( + target_height: float, + total_frustum_height: float, + bottom_length: float, + bottom_width: float, + top_length: float, + top_width: float, +) -> float: + """Find the volume given a height within a rectangular frustum.""" + a, b, c = _rectangular_frustum_polynomial_roots( + bottom_length=bottom_length, + bottom_width=bottom_width, + top_length=top_length, + top_width=top_width, + total_frustum_height=total_frustum_height, + ) + volume = a * (target_height**3) + b * (target_height**2) + c * target_height + return volume + + +def _volume_from_height_spherical( + target_height: float, + radius_of_curvature: float, +) -> float: + """Find the volume given a height within a spherical frustum.""" + volume = ( + (1 / 3) * pi * (target_height**2) * (3 * radius_of_curvature - target_height) + ) + return volume + + +def _volume_from_height_squared_cone( + target_height: float, segment: SquaredConeSegment +) -> float: + """Find the volume given a height within a squared cone segment.""" + heights = segment.height_to_volume_table.keys() + best_fit_height = min(heights, key=lambda x: abs(x - target_height)) + return segment.height_to_volume_table[best_fit_height] + + +def _height_from_volume_circular( + volume: float, + total_frustum_height: float, + bottom_radius: float, + top_radius: float, +) -> float: + """Find the height given a volume within a circular frustum.""" + a, b, c = _circular_frustum_polynomial_roots( + bottom_radius=bottom_radius, + top_radius=top_radius, + total_frustum_height=total_frustum_height, + ) + d = volume * -1 + x_intercept_roots = (a, b, c, d) + + height_from_volume_roots = roots(x_intercept_roots) + height = _reject_unacceptable_heights( + potential_heights=list(height_from_volume_roots), + max_height=total_frustum_height, + ) + return height + + +def _height_from_volume_rectangular( + volume: float, + total_frustum_height: float, + bottom_length: float, + bottom_width: float, + top_length: float, + top_width: float, +) -> float: + """Find the height given a volume within a rectangular frustum.""" + a, b, c = _rectangular_frustum_polynomial_roots( + bottom_length=bottom_length, + bottom_width=bottom_width, + top_length=top_length, + top_width=top_width, + total_frustum_height=total_frustum_height, + ) + d = volume * -1 + x_intercept_roots = (a, b, c, d) + + height_from_volume_roots = roots(x_intercept_roots) + height = _reject_unacceptable_heights( + potential_heights=list(height_from_volume_roots), + max_height=total_frustum_height, + ) + return height + + +def _height_from_volume_spherical( + volume: float, + radius_of_curvature: float, + total_frustum_height: float, +) -> float: + """Find the height given a volume within a spherical frustum.""" + a = -1 * pi / 3 + b = pi * radius_of_curvature + c = 0.0 + d = volume * -1 + x_intercept_roots = (a, b, c, d) + + height_from_volume_roots = roots(x_intercept_roots) + height = _reject_unacceptable_heights( + potential_heights=list(height_from_volume_roots), + max_height=total_frustum_height, + ) + return height + + +def _height_from_volume_squared_cone( + target_volume: float, segment: SquaredConeSegment +) -> float: + """Find the height given a volume within a squared cone segment.""" + volumes = segment.volume_to_height_table.keys() + best_fit_volume = min(volumes, key=lambda x: abs(x - target_volume)) + return segment.volume_to_height_table[best_fit_volume] + + +def _get_segment_capacity(segment: WellSegment) -> float: + section_height = segment.topHeight - segment.bottomHeight + match segment: + case SphericalSegment(): + return ( + _volume_from_height_spherical( + target_height=segment.topHeight, + radius_of_curvature=segment.radiusOfCurvature, + ) + * segment.count + ) + case CuboidalFrustum(): + return ( + _volume_from_height_rectangular( + target_height=section_height, + bottom_length=segment.bottomYDimension, + bottom_width=segment.bottomXDimension, + top_length=segment.topYDimension, + top_width=segment.topXDimension, + total_frustum_height=section_height, + ) + * segment.count + ) + case ConicalFrustum(): + return ( + _volume_from_height_circular( + target_height=section_height, + total_frustum_height=section_height, + bottom_radius=(segment.bottomDiameter / 2), + top_radius=(segment.topDiameter / 2), + ) + * segment.count + ) + case SquaredConeSegment(): + return ( + _volume_from_height_squared_cone(section_height, segment) + * segment.count + ) + case _: + # TODO: implement volume calculations for truncated circular and rounded rectangular segments + raise NotImplementedError( + f"volume calculation for shape: {segment.shape} not yet implemented." + ) + + +def get_well_volumetric_capacity( + well_geometry: InnerWellGeometry, +) -> List[Tuple[float, float]]: + """Return the total volumetric capacity of a well as a map of height borders to volume.""" + # dictionary map of heights to volumetric capacities within their respective segment + # {top_height_0: volume_0, top_height_1: volume_1, top_height_2: volume_2} + well_volume = [] + + # get the well segments sorted in ascending order + sorted_well = sorted(well_geometry.sections, key=lambda section: section.topHeight) + + for segment in sorted_well: + section_volume = _get_segment_capacity(segment) + well_volume.append((segment.topHeight, section_volume)) + return well_volume + + +def height_at_volume_within_section( + section: WellSegment, + target_volume_relative: float, + section_height: float, +) -> float: + """Calculate a height within a bounded section according to geometry.""" + target_volume_relative = target_volume_relative / section.count + match section: + case SphericalSegment(): + return _height_from_volume_spherical( + volume=target_volume_relative, + total_frustum_height=section_height, + radius_of_curvature=section.radiusOfCurvature, + ) + case ConicalFrustum(): + return _height_from_volume_circular( + volume=target_volume_relative, + top_radius=(section.bottomDiameter / 2), + bottom_radius=(section.topDiameter / 2), + total_frustum_height=section_height, + ) + case CuboidalFrustum(): + return _height_from_volume_rectangular( + volume=target_volume_relative, + total_frustum_height=section_height, + bottom_width=section.bottomXDimension, + bottom_length=section.bottomYDimension, + top_width=section.topXDimension, + top_length=section.topYDimension, + ) + case SquaredConeSegment(): + return _height_from_volume_squared_cone(target_volume_relative, section) + case _: + raise NotImplementedError( + "Height from volume calculation not yet implemented for this well shape." + ) + + +def volume_at_height_within_section( + section: WellSegment, + target_height_relative: float, + section_height: float, +) -> float: + """Calculate a volume within a bounded section according to geometry.""" + match section: + case SphericalSegment(): + return ( + _volume_from_height_spherical( + target_height=target_height_relative, + radius_of_curvature=section.radiusOfCurvature, + ) + * section.count + ) + case ConicalFrustum(): + return ( + _volume_from_height_circular( + target_height=target_height_relative, + total_frustum_height=section_height, + bottom_radius=(section.bottomDiameter / 2), + top_radius=(section.topDiameter / 2), + ) + * section.count + ) + case CuboidalFrustum(): + return ( + _volume_from_height_rectangular( + target_height=target_height_relative, + total_frustum_height=section_height, + bottom_width=section.bottomXDimension, + bottom_length=section.bottomYDimension, + top_width=section.topXDimension, + top_length=section.topYDimension, + ) + * section.count + ) + case SquaredConeSegment(): + return ( + _volume_from_height_squared_cone(target_height_relative, section) + * section.count + ) + case _: + # TODO(cm): this would be the NEST-96 2uL wells referenced in EXEC-712 + # we need to input the math attached to that issue + raise NotImplementedError( + "Height from volume calculation not yet implemented for this well shape." + ) + + +def _find_volume_in_partial_frustum( + sorted_well: List[WellSegment], + target_height: float, +) -> float: + """Look through a sorted list of frusta for a target height, and find the volume at that height.""" + for segment in sorted_well: + if segment.bottomHeight < target_height < segment.topHeight: + relative_target_height = target_height - segment.bottomHeight + section_height = segment.topHeight - segment.bottomHeight + return volume_at_height_within_section( + section=segment, + target_height_relative=relative_target_height, + section_height=section_height, + ) + # if we've looked through all sections and can't find the target volume, raise an error + raise InvalidLiquidHeightFound( + f"Unable to find volume at given well-height {target_height}." + ) + + +def find_volume_at_well_height( + target_height: float, well_geometry: InnerWellGeometry +) -> float: + """Find the volume within a well, at a known height.""" + volumetric_capacity = get_well_volumetric_capacity(well_geometry) + max_height = volumetric_capacity[-1][0] + if target_height < 0 or target_height > max_height: + raise InvalidLiquidHeightFound("Invalid target height.") + # volumes in volumetric_capacity are relative to each frustum, + # so we have to find the volume of all the full sections enclosed + # beneath the target height + closed_section_volume = 0.0 + for boundary_height, section_volume in volumetric_capacity: + if boundary_height > target_height: + break + closed_section_volume += section_volume + # if target height is a boundary cross-section, we already know the volume + if target_height == boundary_height: + return closed_section_volume + # find the section the target height is in and compute the volume + + sorted_well = sorted(well_geometry.sections, key=lambda section: section.topHeight) + partial_volume = _find_volume_in_partial_frustum( + sorted_well=sorted_well, + target_height=target_height, + ) + return partial_volume + closed_section_volume + + +def _find_height_in_partial_frustum( + sorted_well: List[WellSegment], + volumetric_capacity: List[Tuple[float, float]], + target_volume: float, +) -> float: + """Look through a sorted list of frusta for a target volume, and find the height at that volume.""" + bottom_section_volume = 0.0 + for section, capacity in zip(sorted_well, volumetric_capacity): + section_top_height, section_volume = capacity + if ( + bottom_section_volume + < target_volume + < (bottom_section_volume + section_volume) + ): + relative_target_volume = target_volume - bottom_section_volume + section_height = section.topHeight - section.bottomHeight + partial_height = height_at_volume_within_section( + section=section, + target_volume_relative=relative_target_volume, + section_height=section_height, + ) + return partial_height + section.bottomHeight + # bottom section volume should always be the volume enclosed in the previously + # viewed section + bottom_section_volume += section_volume + + # if we've looked through all sections and can't find the target volume, raise an error + raise InvalidLiquidHeightFound( + f"Unable to find height at given volume {target_volume}." + ) + + +def find_height_at_well_volume( + target_volume: float, well_geometry: InnerWellGeometry +) -> float: + """Find the height within a well, at a known volume.""" + volumetric_capacity = get_well_volumetric_capacity(well_geometry) + max_volume = sum(row[1] for row in volumetric_capacity) + if target_volume < 0 or target_volume > max_volume: + raise InvalidLiquidHeightFound("Invalid target volume.") + + sorted_well = sorted(well_geometry.sections, key=lambda section: section.topHeight) + # find the section the target volume is in and compute the height + return _find_height_in_partial_frustum( + sorted_well=sorted_well, + volumetric_capacity=volumetric_capacity, + target_volume=target_volume, + ) diff --git a/api/src/opentrons/protocol_engine/state/geometry.py b/api/src/opentrons/protocol_engine/state/geometry.py index 9be6f7e5952..ed915530b90 100644 --- a/api/src/opentrons/protocol_engine/state/geometry.py +++ b/api/src/opentrons/protocol_engine/state/geometry.py @@ -1,4 +1,5 @@ """Geometry state getters.""" + import enum from numpy import array, dot, double as npdouble from numpy.typing import NDArray @@ -8,24 +9,29 @@ from opentrons.types import Point, DeckSlotName, StagingSlotName, MountType +from opentrons_shared_data.errors.exceptions import InvalidStoredData from opentrons_shared_data.labware.constants import WELL_NAME_PATTERN from opentrons_shared_data.deck.types import CutoutFixture from opentrons_shared_data.pipette import PIPETTE_X_SPAN from opentrons_shared_data.pipette.types import ChannelCount +from opentrons.protocols.models import LabwareDefinition from .. import errors from ..errors import ( LabwareNotLoadedOnLabwareError, LabwareNotLoadedOnModuleError, LabwareMovementNotAllowedError, + OperationLocationNotInWellError, ) -from ..resources import fixture_validation +from ..resources import fixture_validation, labware_validation from ..types import ( OFF_DECK_LOCATION, LoadedLabware, LoadedModule, WellLocation, + LiquidHandlingWellLocation, DropTipWellLocation, + PickUpTipWellLocation, WellOrigin, DropTipWellOrigin, WellOffset, @@ -45,12 +51,19 @@ AddressableOffsetVector, StagingSlotLocation, LabwareOffsetLocation, + ModuleModel, ) from .config import Config from .labware import LabwareView +from .wells import WellView from .modules import ModuleView from .pipettes import PipetteView from .addressable_areas import AddressableAreaView +from .frustum_helpers import ( + find_volume_at_well_height, + find_height_at_well_volume, +) +from ._well_math import wells_covered_by_pipette_configuration, nozzles_per_well SLOT_WIDTH = 128 @@ -77,6 +90,11 @@ class _GripperMoveType(enum.Enum): class _AbsoluteRobotExtents: front_left: Dict[MountType, Point] back_right: Dict[MountType, Point] + deck_extents: Point + padding_rear: float + padding_front: float + padding_left_side: float + padding_right_side: float _LabwareLocation = TypeVar("_LabwareLocation", bound=LabwareLocation) @@ -91,6 +109,7 @@ def __init__( self, config: Config, labware_view: LabwareView, + well_view: WellView, module_view: ModuleView, pipette_view: PipetteView, addressable_area_view: AddressableAreaView, @@ -98,6 +117,7 @@ def __init__( """Initialize a GeometryView instance.""" self._config = config self._labware = labware_view + self._wells = well_view self._modules = module_view self._pipettes = pipette_view self._addressable_areas = addressable_area_view @@ -118,7 +138,13 @@ def absolute_deck_extents(self) -> _AbsoluteRobotExtents: MountType.RIGHT: self._addressable_areas.deck_extents + right_offset, } return _AbsoluteRobotExtents( - front_left=front_left_abs, back_right=back_right_abs + front_left=front_left_abs, + back_right=back_right_abs, + deck_extents=self._addressable_areas.deck_extents, + padding_rear=self._addressable_areas.padding_offsets["rear"], + padding_front=self._addressable_areas.padding_offsets["front"], + padding_left_side=self._addressable_areas.padding_offsets["left_side"], + padding_right_side=self._addressable_areas.padding_offsets["right_side"], ) def get_labware_highest_z(self, labware_id: str) -> float: @@ -239,32 +265,33 @@ def get_min_travel_z( return min_travel_z def get_labware_parent_nominal_position(self, labware_id: str) -> Point: - """Get the position of the labware's uncalibrated parent slot (deck, module, or another labware).""" + """Get the position of the labware's uncalibrated parent (deck slot, module, or another labware).""" try: addressable_area_name = self.get_ancestor_slot_name(labware_id).id except errors.LocationIsStagingSlotError: addressable_area_name = self._get_staging_slot_name(labware_id) except errors.LocationIsLidDockSlotError: addressable_area_name = self._get_lid_dock_slot_name(labware_id) - slot_pos = self._addressable_areas.get_addressable_area_position( + parent_pos = self._addressable_areas.get_addressable_area_position( addressable_area_name ) - labware_data = self._labware.get(labware_id) - offset = self._get_labware_position_offset(labware_id, labware_data.location) + offset_from_parent = self._get_offset_from_parent( + child_definition=self._labware.get_definition(labware_id), + parent=self._labware.get(labware_id).location, + ) return Point( - slot_pos.x + offset.x, - slot_pos.y + offset.y, - slot_pos.z + offset.z, + parent_pos.x + offset_from_parent.x, + parent_pos.y + offset_from_parent.y, + parent_pos.z + offset_from_parent.z, ) - def _get_labware_position_offset( - self, labware_id: str, labware_location: LabwareLocation + def _get_offset_from_parent( + self, child_definition: LabwareDefinition, parent: LabwareLocation ) -> LabwareOffsetVector: - """Gets the offset vector of a labware on the given location. + """Gets the offset vector of a labware placed on the given location. - NOTE: Not to be confused with LPC offset. - For labware on Deck Slot: returns an offset of (0, 0, 0) - For labware on a Module: returns the nominal offset for the labware's position when placed on the specified module (using slot-transformed labwareOffset @@ -275,40 +302,42 @@ def _get_labware_position_offset( on modules as well as stacking overlaps. Does not include module calibration offset or LPC offset. """ - if isinstance(labware_location, (AddressableAreaLocation, DeckSlotLocation)): + if isinstance(parent, (AddressableAreaLocation, DeckSlotLocation)): return LabwareOffsetVector(x=0, y=0, z=0) - elif isinstance(labware_location, ModuleLocation): - module_id = labware_location.moduleId - module_offset = self._modules.get_nominal_module_offset( + elif isinstance(parent, ModuleLocation): + module_id = parent.moduleId + module_to_child = self._modules.get_nominal_offset_to_child( module_id=module_id, addressable_areas=self._addressable_areas ) module_model = self._modules.get_connected_model(module_id) stacking_overlap = self._labware.get_module_overlap_offsets( - labware_id, module_model + child_definition, module_model ) return LabwareOffsetVector( - x=module_offset.x - stacking_overlap.x, - y=module_offset.y - stacking_overlap.y, - z=module_offset.z - stacking_overlap.z, + x=module_to_child.x - stacking_overlap.x, + y=module_to_child.y - stacking_overlap.y, + z=module_to_child.z - stacking_overlap.z, + ) + elif isinstance(parent, OnLabwareLocation): + on_labware = self._labware.get(parent.labwareId) + on_labware_dimensions = self._labware.get_dimensions( + labware_id=on_labware.id ) - elif isinstance(labware_location, OnLabwareLocation): - on_labware = self._labware.get(labware_location.labwareId) - on_labware_dimensions = self._labware.get_dimensions(on_labware.id) stacking_overlap = self._labware.get_labware_overlap_offsets( - labware_id=labware_id, below_labware_name=on_labware.loadName + definition=child_definition, below_labware_name=on_labware.loadName ) labware_offset = LabwareOffsetVector( x=stacking_overlap.x, y=stacking_overlap.y, z=on_labware_dimensions.z - stacking_overlap.z, ) - return labware_offset + self._get_labware_position_offset( - on_labware.id, on_labware.location + return labware_offset + self._get_offset_from_parent( + self._labware.get_definition(on_labware.id), on_labware.location ) else: raise errors.LabwareNotOnDeckError( - f"Cannot access labware {labware_id} since it is not on the deck. " - f"Either it has been loaded off-deck or its been moved off-deck." + "Cannot access labware since it is not on the deck. " + "Either it has been loaded off-deck or its been moved off-deck." ) def _normalize_module_calibration_offset( @@ -399,11 +428,51 @@ def get_labware_position(self, labware_id: str) -> Point: z=origin_pos.z + cal_offset.z, ) + WellLocations = Union[ + WellLocation, LiquidHandlingWellLocation, PickUpTipWellLocation + ] + + def validate_well_position( + self, + well_location: WellLocations, + z_offset: float, + pipette_id: Optional[str] = None, + ) -> None: + """Raise exception if operation location is not within well. + + Primarily this checks if there is not enough liquid in a well to do meniscus-relative static aspiration. + """ + if well_location.origin == WellOrigin.MENISCUS: + assert pipette_id is not None + lld_min_height = self._pipettes.get_current_tip_lld_settings( + pipette_id=pipette_id + ) + if z_offset < lld_min_height: + if isinstance(well_location, PickUpTipWellLocation): + raise OperationLocationNotInWellError( + f"Specifying {well_location.origin} with an offset of {well_location.offset} results in an operation location that could be below the bottom of the well" + ) + else: + raise OperationLocationNotInWellError( + f"Specifying {well_location.origin} with an offset of {well_location.offset} and a volume offset of {well_location.volumeOffset} results in an operation location that could be below the bottom of the well" + ) + elif z_offset < 0: + if isinstance(well_location, PickUpTipWellLocation): + raise OperationLocationNotInWellError( + f"Specifying {well_location.origin} with an offset of {well_location.offset} results in an operation location below the bottom of the well" + ) + else: + raise OperationLocationNotInWellError( + f"Specifying {well_location.origin} with an offset of {well_location.offset} and a volume offset of {well_location.volumeOffset} results in an operation location below the bottom of the well" + ) + def get_well_position( self, labware_id: str, well_name: str, - well_location: Optional[WellLocation] = None, + well_location: Optional[WellLocations] = None, + operation_volume: Optional[float] = None, + pipette_id: Optional[str] = None, ) -> Point: """Given relative well location in a labware, get absolute position.""" labware_pos = self.get_labware_position(labware_id) @@ -413,10 +482,17 @@ def get_well_position( offset = WellOffset(x=0, y=0, z=well_depth) if well_location is not None: offset = well_location.offset - if well_location.origin == WellOrigin.TOP: - offset = offset.copy(update={"z": offset.z + well_depth}) - elif well_location.origin == WellOrigin.CENTER: - offset = offset.copy(update={"z": offset.z + well_depth / 2.0}) + offset_adjustment = self.get_well_offset_adjustment( + labware_id=labware_id, + well_name=well_name, + well_location=well_location, + well_depth=well_depth, + operation_volume=operation_volume, + ) + offset = offset.copy(update={"z": offset.z + offset_adjustment}) + self.validate_well_position( + well_location=well_location, z_offset=offset.z, pipette_id=pipette_id + ) return Point( x=labware_pos.x + offset.x + well_def.x, @@ -451,6 +527,41 @@ def get_relative_well_location( return WellLocation(offset=WellOffset(x=delta.x, y=delta.y, z=delta.z)) + def get_relative_liquid_handling_well_location( + self, + labware_id: str, + well_name: str, + absolute_point: Point, + is_meniscus: Optional[bool] = None, + ) -> LiquidHandlingWellLocation: + """Given absolute position, get relative location of a well in a labware. + + If is_meniscus is True, absolute_point will hold the z-offset in its z field. + """ + if is_meniscus: + return LiquidHandlingWellLocation( + origin=WellOrigin.MENISCUS, + offset=WellOffset(x=0, y=0, z=absolute_point.z), + ) + else: + well_absolute_point = self.get_well_position(labware_id, well_name) + delta = absolute_point - well_absolute_point + return LiquidHandlingWellLocation( + offset=WellOffset(x=delta.x, y=delta.y, z=delta.z) + ) + + def get_relative_pick_up_tip_well_location( + self, + labware_id: str, + well_name: str, + absolute_point: Point, + ) -> PickUpTipWellLocation: + """Given absolute position, get relative location of a well in a labware.""" + well_absolute_point = self.get_well_position(labware_id, well_name) + delta = absolute_point - well_absolute_point + + return PickUpTipWellLocation(offset=WellOffset(x=delta.x, y=delta.y, z=delta.z)) + def get_well_height( self, labware_id: str, @@ -574,6 +685,14 @@ def get_checked_tip_drop_location( ), ) + def convert_pick_up_tip_well_location( + self, well_location: PickUpTipWellLocation + ) -> WellLocation: + """Convert PickUpTipWellLocation to WellLocation.""" + return WellLocation( + origin=WellOrigin(well_location.origin.value), offset=well_location.offset + ) + # TODO(jbl 11-30-2023) fold this function into get_ancestor_slot_name see RSS-411 def _get_staging_slot_name(self, labware_id: str) -> str: """Get the staging slot name that the labware is on.""" @@ -596,10 +715,12 @@ def _get_lid_dock_slot_name(self, labware_id: str) -> str: assert isinstance(labware_location, AddressableAreaLocation) return labware_location.addressableAreaName - def get_ancestor_slot_name(self, labware_id: str) -> DeckSlotName: + def get_ancestor_slot_name( + self, labware_id: str + ) -> Union[DeckSlotName, StagingSlotName]: """Get the slot name of the labware or the module that the labware is on.""" labware = self._labware.get(labware_id) - slot_name: DeckSlotName + slot_name: Union[DeckSlotName, StagingSlotName] if isinstance(labware.location, DeckSlotLocation): slot_name = labware.location.slotName @@ -611,18 +732,14 @@ def get_ancestor_slot_name(self, labware_id: str) -> DeckSlotName: slot_name = self.get_ancestor_slot_name(below_labware_id) elif isinstance(labware.location, AddressableAreaLocation): area_name = labware.location.addressableAreaName - # TODO we might want to eventually return some sort of staging slot name when we're ready to work through - # the linting nightmare it will create if self._labware.is_absorbance_reader_lid(labware_id): raise errors.LocationIsLidDockSlotError( "Cannot get ancestor slot name for labware on lid dock slot." ) - if fixture_validation.is_staging_slot(area_name): - raise errors.LocationIsStagingSlotError( - "Cannot get ancestor slot name for labware on staging slot." - ) - raise errors.LocationIs - slot_name = DeckSlotName.from_primitive(area_name) + elif fixture_validation.is_staging_slot(area_name): + slot_name = StagingSlotName.from_primitive(area_name) + else: + slot_name = DeckSlotName.from_primitive(area_name) elif labware.location == OFF_DECK_LOCATION: raise errors.LabwareNotOnDeckError( f"Labware {labware_id} does not have a slot associated with it" @@ -655,7 +772,7 @@ def ensure_location_not_occupied( def get_labware_grip_point( self, - labware_id: str, + labware_definition: LabwareDefinition, location: Union[ DeckSlotLocation, ModuleLocation, OnLabwareLocation, AddressableAreaLocation ], @@ -671,7 +788,7 @@ def get_labware_grip_point( z-position of labware bottom + grip height from labware bottom. """ grip_height_from_labware_bottom = ( - self._labware.get_grip_height_from_labware_bottom(labware_id) + self._labware.get_grip_height_from_labware_bottom(labware_definition) ) location_name: str @@ -697,7 +814,9 @@ def get_labware_grip_point( ).slotName.id else: # OnLabwareLocation location_name = self.get_ancestor_slot_name(location.labwareId).id - labware_offset = self._get_labware_position_offset(labware_id, location) + labware_offset = self._get_offset_from_parent( + child_definition=labware_definition, parent=location + ) # Get the calibrated offset if the on labware location is on top of a module, otherwise return empty one cal_offset = self._get_calibrated_module_offset(location) offset = LabwareOffsetVector( @@ -716,7 +835,9 @@ def get_labware_grip_point( ) def get_extra_waypoints( - self, location: Optional[CurrentPipetteLocation], to_slot: DeckSlotName + self, + location: Optional[CurrentPipetteLocation], + to_slot: Union[DeckSlotName, StagingSlotName], ) -> List[Tuple[float, float]]: """Get extra waypoints for movement if thermocycler needs to be dodged.""" if location is not None: @@ -775,8 +896,10 @@ def get_slot_item( return maybe_labware or maybe_module or maybe_fixture or None @staticmethod - def get_slot_column(slot_name: DeckSlotName) -> int: + def get_slot_column(slot_name: Union[DeckSlotName, StagingSlotName]) -> int: """Get the column number for the specified slot.""" + if isinstance(slot_name, StagingSlotName): + return 4 row_col_name = slot_name.to_ot3_equivalent() slot_name_match = WELL_NAME_PATTERN.match(row_col_name.value) assert ( @@ -967,17 +1090,22 @@ def get_final_labware_movement_offset_vectors( from_location: OnDeckLabwareLocation, to_location: OnDeckLabwareLocation, additional_offset_vector: LabwareMovementOffsetData, + current_labware: LabwareDefinition, ) -> LabwareMovementOffsetData: """Calculate the final labware offset vector to use in labware movement.""" pick_up_offset = ( self.get_total_nominal_gripper_offset_for_move_type( - location=from_location, move_type=_GripperMoveType.PICK_UP_LABWARE + location=from_location, + move_type=_GripperMoveType.PICK_UP_LABWARE, + current_labware=current_labware, ) + additional_offset_vector.pickUpOffset ) drop_offset = ( self.get_total_nominal_gripper_offset_for_move_type( - location=to_location, move_type=_GripperMoveType.DROP_LABWARE + location=to_location, + move_type=_GripperMoveType.DROP_LABWARE, + current_labware=current_labware, ) + additional_offset_vector.dropOffset ) @@ -1008,7 +1136,10 @@ def ensure_valid_gripper_location( return location def get_total_nominal_gripper_offset_for_move_type( - self, location: OnDeckLabwareLocation, move_type: _GripperMoveType + self, + location: OnDeckLabwareLocation, + move_type: _GripperMoveType, + current_labware: LabwareDefinition, ) -> LabwareOffsetVector: """Get the total of the offsets to be used to pick up labware in its current location.""" if move_type == _GripperMoveType.PICK_UP_LABWARE: @@ -1024,14 +1155,45 @@ def get_total_nominal_gripper_offset_for_move_type( location ) ancestor = self._labware.get_parent_location(location.labwareId) + extra_offset = LabwareOffsetVector(x=0, y=0, z=0) + if ( + isinstance(ancestor, ModuleLocation) + and self._modules._state.requested_model_by_id[ancestor.moduleId] + == ModuleModel.THERMOCYCLER_MODULE_V2 + and labware_validation.validate_definition_is_lid(current_labware) + ): + if "lidOffsets" in current_labware.gripperOffsets.keys(): + extra_offset = LabwareOffsetVector( + x=current_labware.gripperOffsets[ + "lidOffsets" + ].pickUpOffset.x, + y=current_labware.gripperOffsets[ + "lidOffsets" + ].pickUpOffset.y, + z=current_labware.gripperOffsets[ + "lidOffsets" + ].pickUpOffset.z, + ) + else: + raise errors.LabwareOffsetDoesNotExistError( + f"Labware Definition {current_labware.parameters.loadName} does not contain required field 'lidOffsets' of 'gripperOffsets'." + ) + assert isinstance( - ancestor, (DeckSlotLocation, ModuleLocation) + ancestor, + ( + DeckSlotLocation, + ModuleLocation, + OnLabwareLocation, + AddressableAreaLocation, + ), ), "No gripper offsets for off-deck labware" return ( direct_parent_offset.pickUpOffset + self._nominal_gripper_offsets_for_location( location=ancestor ).pickUpOffset + + extra_offset ) else: if isinstance( @@ -1046,16 +1208,65 @@ def get_total_nominal_gripper_offset_for_move_type( location ) ancestor = self._labware.get_parent_location(location.labwareId) + extra_offset = LabwareOffsetVector(x=0, y=0, z=0) + if ( + isinstance(ancestor, ModuleLocation) + # todo(mm, 2024-11-06): Do not access private module state; only use public ModuleView methods. + and self._modules._state.requested_model_by_id[ancestor.moduleId] + == ModuleModel.THERMOCYCLER_MODULE_V2 + and labware_validation.validate_definition_is_lid(current_labware) + ): + if "lidOffsets" in current_labware.gripperOffsets.keys(): + extra_offset = LabwareOffsetVector( + x=current_labware.gripperOffsets[ + "lidOffsets" + ].pickUpOffset.x, + y=current_labware.gripperOffsets[ + "lidOffsets" + ].pickUpOffset.y, + z=current_labware.gripperOffsets[ + "lidOffsets" + ].pickUpOffset.z, + ) + else: + raise errors.LabwareOffsetDoesNotExistError( + f"Labware Definition {current_labware.parameters.loadName} does not contain required field 'lidOffsets' of 'gripperOffsets'." + ) + assert isinstance( - ancestor, (DeckSlotLocation, ModuleLocation) + ancestor, + ( + DeckSlotLocation, + ModuleLocation, + OnLabwareLocation, + AddressableAreaLocation, + ), ), "No gripper offsets for off-deck labware" return ( direct_parent_offset.dropOffset + self._nominal_gripper_offsets_for_location( location=ancestor ).dropOffset + + extra_offset ) + # todo(mm, 2024-11-05): This may be incorrect because it does not take the following + # offsets into account, which *are* taken into account for the actual gripper movement: + # + # * The pickup offset in the definition of the parent of the gripped labware. + # * The "additional offset" or "user offset", e.g. the `pickUpOffset` and `dropOffset` + # params in the `moveLabware` command. + # + # And this *does* take these extra offsets into account: + # + # * The labware's Labware Position Check offset + # + # For robustness, we should combine this with `get_gripper_labware_movement_waypoints()`. + # + # We should also be more explicit about which offsets act to move the gripper paddles + # relative to the gripped labware, and which offsets act to change how the gripped + # labware sits atop its parent. Those have different effects on how far the gripped + # labware juts beyond the paddles while it's in transit. def check_gripper_labware_tip_collision( self, gripper_homed_position_z: float, @@ -1063,18 +1274,22 @@ def check_gripper_labware_tip_collision( current_location: OnDeckLabwareLocation, ) -> None: """Check for potential collision of tips against labware to be lifted.""" - # TODO(cb, 2024-01-22): Remove the 1 and 8 channel special case once we are doing X axis validation + labware_definition = self._labware.get_definition(labware_id) pipettes = self._pipettes.get_all() for pipette in pipettes: + # TODO(cb, 2024-01-22): Remove the 1 and 8 channel special case once we are doing X axis validation if self._pipettes.get_channels(pipette.id) in [1, 8]: return tip = self._pipettes.get_attached_tip(pipette.id) if tip: + # NOTE: This call to get_labware_highest_z() uses the labware's LPC offset, + # which is an inconsistency between this and the actual gripper movement. + # See the todo comment above this function. labware_top_z_when_gripped = gripper_homed_position_z + ( self.get_labware_highest_z(labware_id=labware_id) - self.get_labware_grip_point( - labware_id=labware_id, location=current_location + labware_definition=labware_definition, location=current_location ).z ) # TODO(cb, 2024-01-18): Utilizing the nozzle map and labware X coordinates verify if collisions will occur on the X axis (analysis will use hard coded data to measure from the gripper critical point to the pipette mount) @@ -1082,7 +1297,7 @@ def check_gripper_labware_tip_collision( _PIPETTE_HOMED_POSITION_Z - tip.length ) < labware_top_z_when_gripped: raise LabwareMovementNotAllowedError( - f"Cannot move labware '{self._labware.get(labware_id).loadName}' when {int(tip.volume)} µL tips are attached." + f"Cannot move labware '{labware_definition.parameters.loadName}' when {int(tip.volume)} µL tips are attached." ) return @@ -1117,20 +1332,30 @@ def _labware_gripper_offsets( """ parent_location = self._labware.get_parent_location(labware_id) assert isinstance( - parent_location, (DeckSlotLocation, ModuleLocation) + parent_location, + ( + DeckSlotLocation, + ModuleLocation, + AddressableAreaLocation, + OnLabwareLocation, + ), ), "No gripper offsets for off-deck labware" if isinstance(parent_location, DeckSlotLocation): slot_name = parent_location.slotName + elif isinstance(parent_location, AddressableAreaLocation): + slot_name = self._addressable_areas.get_addressable_area_base_slot( + parent_location.addressableAreaName + ) else: module_loc = self._modules.get_location(parent_location.moduleId) slot_name = module_loc.slotName - slot_based_offset = self._labware.get_labware_gripper_offsets( + slot_based_offset = self._labware.get_child_gripper_offsets( labware_id=labware_id, slot_name=slot_name.to_ot3_equivalent() ) - return slot_based_offset or self._labware.get_labware_gripper_offsets( + return slot_based_offset or self._labware.get_child_gripper_offsets( labware_id=labware_id, slot_name=None ) @@ -1178,3 +1403,205 @@ def get_offset_location(self, labware_id: str) -> Optional[LabwareOffsetLocation ) return None + + def get_well_offset_adjustment( + self, + labware_id: str, + well_name: str, + well_location: WellLocations, + well_depth: float, + operation_volume: Optional[float] = None, + ) -> float: + """Return a z-axis distance that accounts for well handling height and operation volume. + + Distance is with reference to the well bottom. + """ + # TODO(pbm, 10-23-24): refactor to smartly reduce height/volume conversions + initial_handling_height = self.get_well_handling_height( + labware_id=labware_id, + well_name=well_name, + well_location=well_location, + well_depth=well_depth, + ) + if isinstance(well_location, PickUpTipWellLocation): + volume = 0.0 + elif isinstance(well_location.volumeOffset, float): + volume = well_location.volumeOffset + elif well_location.volumeOffset == "operationVolume": + volume = operation_volume or 0.0 + + if volume: + return self.get_well_height_after_volume( + labware_id=labware_id, + well_name=well_name, + initial_height=initial_handling_height, + volume=volume, + ) + else: + return initial_handling_height + + def get_meniscus_height( + self, + labware_id: str, + well_name: str, + ) -> float: + """Returns stored meniscus height in specified well.""" + well_liquid = self._wells.get_well_liquid_info( + labware_id=labware_id, well_name=well_name + ) + if ( + well_liquid.probed_height is not None + and well_liquid.probed_height.height is not None + ): + return well_liquid.probed_height.height + elif ( + well_liquid.loaded_volume is not None + and well_liquid.loaded_volume.volume is not None + ): + return self.get_well_height_at_volume( + labware_id=labware_id, + well_name=well_name, + volume=well_liquid.loaded_volume.volume, + ) + elif ( + well_liquid.probed_volume is not None + and well_liquid.probed_volume.volume is not None + ): + return self.get_well_height_at_volume( + labware_id=labware_id, + well_name=well_name, + volume=well_liquid.probed_volume.volume, + ) + else: + raise errors.LiquidHeightUnknownError( + "Must LiquidProbe or LoadLiquid before specifying WellOrigin.MENISCUS." + ) + + def get_well_handling_height( + self, + labware_id: str, + well_name: str, + well_location: WellLocations, + well_depth: float, + ) -> float: + """Return the handling height for a labware well (with reference to the well bottom).""" + handling_height = 0.0 + if well_location.origin == WellOrigin.TOP: + handling_height = well_depth + elif well_location.origin == WellOrigin.CENTER: + handling_height = well_depth / 2.0 + elif well_location.origin == WellOrigin.MENISCUS: + handling_height = self.get_meniscus_height( + labware_id=labware_id, well_name=well_name + ) + return float(handling_height) + + def get_well_height_after_volume( + self, labware_id: str, well_name: str, initial_height: float, volume: float + ) -> float: + """Return the height of liquid in a labware well after a given volume has been handled. + + This is given an initial handling height, with reference to the well bottom. + """ + well_geometry = self._labware.get_well_geometry( + labware_id=labware_id, well_name=well_name + ) + initial_volume = find_volume_at_well_height( + target_height=initial_height, well_geometry=well_geometry + ) + final_volume = initial_volume + volume + return find_height_at_well_volume( + target_volume=final_volume, well_geometry=well_geometry + ) + + def get_well_height_at_volume( + self, labware_id: str, well_name: str, volume: float + ) -> float: + """Convert well volume to height.""" + well_geometry = self._labware.get_well_geometry(labware_id, well_name) + return find_height_at_well_volume( + target_volume=volume, well_geometry=well_geometry + ) + + def get_well_volume_at_height( + self, labware_id: str, well_name: str, height: float + ) -> float: + """Convert well height to volume.""" + well_geometry = self._labware.get_well_geometry(labware_id, well_name) + return find_volume_at_well_height( + target_height=height, well_geometry=well_geometry + ) + + def validate_dispense_volume_into_well( + self, + labware_id: str, + well_name: str, + well_location: WellLocations, + volume: float, + ) -> None: + """Raise InvalidDispenseVolumeError if planned dispense volume will overflow well.""" + well_def = self._labware.get_well_definition(labware_id, well_name) + well_volumetric_capacity = well_def.totalLiquidVolume + if well_location.origin == WellOrigin.MENISCUS: + # TODO(pbm, 10-23-24): refactor to smartly reduce height/volume conversions + well_geometry = self._labware.get_well_geometry(labware_id, well_name) + meniscus_height = self.get_meniscus_height( + labware_id=labware_id, well_name=well_name + ) + meniscus_volume = find_volume_at_well_height( + target_height=meniscus_height, well_geometry=well_geometry + ) + remaining_volume = well_volumetric_capacity - meniscus_volume + if volume > remaining_volume: + raise errors.InvalidDispenseVolumeError( + f"Attempting to dispense {volume}µL of liquid into a well that can currently only hold {remaining_volume}µL (well {well_name} in labware_id: {labware_id})" + ) + else: + # TODO(pbm, 10-08-24): factor in well (LabwareStore) state volume + if volume > well_volumetric_capacity: + raise errors.InvalidDispenseVolumeError( + f"Attempting to dispense {volume}µL of liquid into a well that can only hold {well_volumetric_capacity}µL (well {well_name} in labware_id: {labware_id})" + ) + + def get_wells_covered_by_pipette_with_active_well( + self, labware_id: str, target_well_name: str, pipette_id: str + ) -> list[str]: + """Get a flat list of wells that are covered by a pipette when moved to a specified well. + + When you move a pipette in a multichannel configuration to a specific well - the target well - + the pipette will operate on other wells as well. + + For instance, a pipette with a COLUMN configuration with well A1 of an SBS standard labware target + will also "cover", under this definition, wells B1-H1. That same pipette, when C5 is the target well, will "cover" + wells C5-H5. + + This math only works, and may only be applied, if one of the following is true: + - The pipette is in a SINGLE configuration + - The pipette is in a non-SINGLE configuration, and the labware is an SBS-format 96 or 384 well plate (and is so + marked in its definition's parameters.format key, as 96Standard or 384Standard) + + If all of the following do not apply, regardless of the nozzle configuration of the pipette this function will + return only the labware covered by the primary well. + """ + pipette_nozzle_map = self._pipettes.get_nozzle_configuration(pipette_id) + labware_columns = [ + column for column in self._labware.get_definition(labware_id).ordering + ] + try: + return list( + wells_covered_by_pipette_configuration( + pipette_nozzle_map, target_well_name, labware_columns + ) + ) + except InvalidStoredData: + return [target_well_name] + + def get_nozzles_per_well( + self, labware_id: str, target_well_name: str, pipette_id: str + ) -> int: + """Get the number of nozzles that will interact with each well.""" + return nozzles_per_well( + self._pipettes.get_nozzle_configuration(pipette_id), + target_well_name, + self._labware.get_definition(labware_id).ordering, + ) diff --git a/api/src/opentrons/protocol_engine/state/labware.py b/api/src/opentrons/protocol_engine/state/labware.py index 16291062d66..95b2baa1974 100644 --- a/api/src/opentrons/protocol_engine/state/labware.py +++ b/api/src/opentrons/protocol_engine/state/labware.py @@ -13,11 +13,16 @@ NamedTuple, cast, Union, + overload, ) +from opentrons.protocol_engine.state import update_types from opentrons_shared_data.deck.types import DeckDefinitionV5 from opentrons_shared_data.gripper.constants import LABWARE_GRIP_FORCE -from opentrons_shared_data.labware.labware_definition import LabwareRole +from opentrons_shared_data.labware.labware_definition import ( + LabwareRole, + InnerWellGeometry, +) from opentrons_shared_data.pipette.types import LabwareUri from opentrons.types import DeckSlotName, StagingSlotName, MountType @@ -27,13 +32,6 @@ from .. import errors from ..resources import DeckFixedLabware, labware_validation, fixture_validation -from ..commands import ( - Command, - absorbance_reader, - LoadLabwareResult, - MoveLabwareResult, - ReloadLabwareResult, -) from ..types import ( DeckSlotLocation, OnLabwareLocation, @@ -54,12 +52,12 @@ ) from ..actions import ( Action, - SucceedCommandAction, AddLabwareOffsetAction, AddLabwareDefinitionAction, + get_state_updates, ) -from .abstract_store import HasState, HandlesActions -from .move_types import EdgePathType +from ._abstract_store import HasState, HandlesActions +from ._move_types import EdgePathType # URIs of labware whose definitions accidentally specify an engage height @@ -70,8 +68,6 @@ "opentrons/usascientific_96_wellplate_2.4ml_deep/1", } -_OT3_INSTRUMENT_ATTACH_SLOT = DeckSlotName.SLOT_D1 - _RIGHT_SIDE_SLOTS = { # OT-2: DeckSlotName.FIXED_TRASH, @@ -86,6 +82,10 @@ } +# The max height of the labware that can fit in a plate reader +_PLATE_READER_MAX_LABWARE_Z_MM = 16 + + class LabwareLoadParams(NamedTuple): """Parameters required to load a labware in Protocol Engine.""" @@ -154,10 +154,11 @@ def __init__( def handle_action(self, action: Action) -> None: """Modify state in reaction to an action.""" - if isinstance(action, SucceedCommandAction): - self._handle_command(action.command) + for state_update in get_state_updates(action): + self._add_loaded_labware(state_update) + self._set_labware_location(state_update) - elif isinstance(action, AddLabwareOffsetAction): + if isinstance(action, AddLabwareOffsetAction): labware_offset = LabwareOffset.construct( id=action.labware_offset_id, createdAt=action.created_at, @@ -175,77 +176,75 @@ def handle_action(self, action: Action) -> None: ) self._state.definitions_by_uri[uri] = action.definition - def _handle_command(self, command: Command) -> None: - """Modify state in reaction to a command.""" - if isinstance(command.result, LoadLabwareResult): + def _add_labware_offset(self, labware_offset: LabwareOffset) -> None: + """Add a new labware offset to state. + + `labware_offset.id` must not match any existing labware offset ID. + `LoadLabwareCommand`s retain references to their corresponding labware offsets + and expect them to be immutable. + """ + assert labware_offset.id not in self._state.labware_offsets_by_id + + self._state.labware_offsets_by_id[labware_offset.id] = labware_offset + + def _add_loaded_labware(self, state_update: update_types.StateUpdate) -> None: + loaded_labware_update = state_update.loaded_labware + if loaded_labware_update != update_types.NO_CHANGE: # If the labware load refers to an offset, that offset must actually exist. - if command.result.offsetId is not None: - assert command.result.offsetId in self._state.labware_offsets_by_id + if loaded_labware_update.offset_id is not None: + assert ( + loaded_labware_update.offset_id in self._state.labware_offsets_by_id + ) definition_uri = uri_from_details( - namespace=command.result.definition.namespace, - load_name=command.result.definition.parameters.loadName, - version=command.result.definition.version, + namespace=loaded_labware_update.definition.namespace, + load_name=loaded_labware_update.definition.parameters.loadName, + version=loaded_labware_update.definition.version, ) - self._state.definitions_by_uri[definition_uri] = command.result.definition - if isinstance(command.result, LoadLabwareResult): - location = command.params.location - else: - location = self._state.labware_by_id[command.result.labwareId].location + self._state.definitions_by_uri[ + definition_uri + ] = loaded_labware_update.definition + + location = loaded_labware_update.new_location + + display_name = loaded_labware_update.display_name self._state.labware_by_id[ - command.result.labwareId + loaded_labware_update.labware_id ] = LoadedLabware.construct( - id=command.result.labwareId, + id=loaded_labware_update.labware_id, location=location, - loadName=command.result.definition.parameters.loadName, + loadName=loaded_labware_update.definition.parameters.loadName, definitionUri=definition_uri, - offsetId=command.result.offsetId, - displayName=command.params.displayName, + offsetId=loaded_labware_update.offset_id, + displayName=display_name, ) - elif isinstance(command.result, ReloadLabwareResult): - labware_id = command.params.labwareId - new_offset_id = command.result.offsetId - self._state.labware_by_id[labware_id].offsetId = new_offset_id - - elif isinstance(command.result, MoveLabwareResult): - labware_id = command.params.labwareId - new_location = command.params.newLocation - new_offset_id = command.result.offsetId + def _set_labware_location(self, state_update: update_types.StateUpdate) -> None: + labware_location_update = state_update.labware_location + if labware_location_update != update_types.NO_CHANGE: + labware_id = labware_location_update.labware_id + new_offset_id = labware_location_update.offset_id self._state.labware_by_id[labware_id].offsetId = new_offset_id - if isinstance( - new_location, AddressableAreaLocation - ) and fixture_validation.is_gripper_waste_chute( - new_location.addressableAreaName - ): - # If a labware has been moved into a waste chute it's been chuted away and is now technically off deck - new_location = OFF_DECK_LOCATION - self._state.labware_by_id[labware_id].location = new_location - elif isinstance(command.result, absorbance_reader.MoveLidResult): - lid_id = command.result.lidId - new_location = command.result.newLocation - new_offset_id = command.result.offsetId + if labware_location_update.new_location: + new_location = labware_location_update.new_location - self._state.labware_by_id[lid_id].offsetId = new_offset_id - self._state.labware_by_id[lid_id].location = new_location + if isinstance(new_location, AddressableAreaLocation) and ( + fixture_validation.is_gripper_waste_chute( + new_location.addressableAreaName + ) + or fixture_validation.is_trash(new_location.addressableAreaName) + ): + # If a labware has been moved into a waste chute it's been chuted away and is now technically off deck + new_location = OFF_DECK_LOCATION - def _add_labware_offset(self, labware_offset: LabwareOffset) -> None: - """Add a new labware offset to state. + self._state.labware_by_id[labware_id].location = new_location - `labware_offset.id` must not match any existing labware offset ID. - `LoadLabwareCommand`s retain references to their corresponding labware offsets - and expect them to be immutable. - """ - assert labware_offset.id not in self._state.labware_offsets_by_id - - self._state.labware_offsets_by_id[labware_offset.id] = labware_offset - -class LabwareView(HasState[LabwareState]): +class LabwareView: """Read-only labware state view.""" _state: LabwareState @@ -269,7 +268,7 @@ def get(self, labware_id: str) -> LoadedLabware: def get_id_by_module(self, module_id: str) -> str: """Return the ID of the labware loaded on the given module.""" - for labware_id, labware in self.state.labware_by_id.items(): + for labware_id, labware in self._state.labware_by_id.items(): if ( isinstance(labware.location, ModuleLocation) and labware.location.moduleId == module_id @@ -282,7 +281,7 @@ def get_id_by_module(self, module_id: str) -> str: def get_id_by_labware(self, labware_id: str) -> str: """Return the ID of the labware loaded on the given labware.""" - for labware in self.state.labware_by_id.values(): + for labware in self._state.labware_by_id.values(): if ( isinstance(labware.location, OnLabwareLocation) and labware.location.labwareId == labware_id @@ -403,6 +402,16 @@ def get_parent_location(self, labware_id: str) -> NonStackedLocation: return self.get_parent_location(parent.labwareId) return parent + def get_labware_stack( + self, labware_stack: List[LoadedLabware] + ) -> List[LoadedLabware]: + """Get the a stack of labware starting from a given labware or existing stack.""" + parent = self.get_location(labware_stack[-1].id) + if isinstance(parent, OnLabwareLocation): + labware_stack.append(self.get(parent.labwareId)) + return self.get_labware_stack(labware_stack) + return labware_stack + def get_all(self) -> List[LoadedLabware]: """Get a list of all labware entries in state.""" return list(self._state.labware_by_id.values()) @@ -427,6 +436,27 @@ def get_should_center_column_on_target_well(self, labware_id: str) -> bool: and len(self.get_definition(labware_id).wells) < 96 ) + def get_labware_stacking_maximum(self, labware: LabwareDefinition) -> int: + """Returns the maximum number of labware allowed in a stack for a given labware definition. + + If not defined within a labware, defaults to one. + """ + stacking_quirks = { + "stackingMaxFive": 5, + "stackingMaxFour": 4, + "stackingMaxThree": 3, + "stackingMaxTwo": 2, + "stackingMaxOne": 1, + "stackingMaxZero": 0, + } + for quirk in stacking_quirks.keys(): + if ( + labware.parameters.quirks is not None + and quirk in labware.parameters.quirks + ): + return stacking_quirks[quirk] + return 1 + def get_should_center_pipette_on_target_well(self, labware_id: str) -> bool: """True if a pipette moving to a well of this labware should center its body on the target. @@ -460,6 +490,29 @@ def get_well_definition( f"{well_name} does not exist in {labware_id}." ) from e + def get_well_geometry( + self, labware_id: str, well_name: Optional[str] = None + ) -> InnerWellGeometry: + """Get a well's inner geometry by labware and well name.""" + labware_def = self.get_definition(labware_id) + if labware_def.innerLabwareGeometry is None: + raise errors.IncompleteLabwareDefinitionError( + message=f"No innerLabwareGeometry found in labware definition for labware_id: {labware_id}." + ) + well_def = self.get_well_definition(labware_id, well_name) + well_id = well_def.geometryDefinitionId + if well_id is None: + raise errors.IncompleteWellDefinitionError( + message=f"No geometryDefinitionId found in well definition for well: {well_name} in labware_id: {labware_id}" + ) + else: + well_geometry = labware_def.innerLabwareGeometry.get(well_id) + if well_geometry is None: + raise errors.IncompleteLabwareDefinitionError( + message=f"No innerLabwareGeometry found in labware definition for well_id: {well_id} in labware_id: {labware_id}" + ) + return well_geometry + def get_well_size( self, labware_id: str, well_name: str ) -> Tuple[float, float, float]: @@ -578,10 +631,26 @@ def get_load_name(self, labware_id: str) -> str: definition = self.get_definition(labware_id) return definition.parameters.loadName - def get_dimensions(self, labware_id: str) -> Dimensions: + @overload + def get_dimensions(self, *, labware_definition: LabwareDefinition) -> Dimensions: + pass + + @overload + def get_dimensions(self, *, labware_id: str) -> Dimensions: + pass + + def get_dimensions( + self, + *, + labware_definition: LabwareDefinition | None = None, + labware_id: str | None = None, + ) -> Dimensions: """Get the labware's dimensions.""" - definition = self.get_definition(labware_id) - dims = definition.dimensions + if labware_definition is None: + assert labware_id is not None # From our @overloads. + labware_definition = self.get_definition(labware_id) + + dims = labware_definition.dimensions return Dimensions( x=dims.xDimension, @@ -590,22 +659,25 @@ def get_dimensions(self, labware_id: str) -> Dimensions: ) def get_labware_overlap_offsets( - self, labware_id: str, below_labware_name: str + self, definition: LabwareDefinition, below_labware_name: str ) -> OverlapOffset: """Get the labware's overlap with requested labware's load name.""" - definition = self.get_definition(labware_id) - stacking_overlap = definition.stackingOffsetWithLabware.get( - below_labware_name, OverlapOffset(x=0, y=0, z=0) - ) + if below_labware_name in definition.stackingOffsetWithLabware.keys(): + stacking_overlap = definition.stackingOffsetWithLabware.get( + below_labware_name, OverlapOffset(x=0, y=0, z=0) + ) + else: + stacking_overlap = definition.stackingOffsetWithLabware.get( + "default", OverlapOffset(x=0, y=0, z=0) + ) return OverlapOffset( x=stacking_overlap.x, y=stacking_overlap.y, z=stacking_overlap.z ) def get_module_overlap_offsets( - self, labware_id: str, module_model: ModuleModel + self, definition: LabwareDefinition, module_model: ModuleModel ) -> OverlapOffset: """Get the labware's overlap with requested module model.""" - definition = self.get_definition(labware_id) stacking_overlap = definition.stackingOffsetWithModule.get( str(module_model.value) ) @@ -765,7 +837,25 @@ def raise_if_labware_in_location( f"Labware {labware.loadName} is already present at {location}." ) - def raise_if_labware_cannot_be_stacked( + def raise_if_labware_incompatible_with_plate_reader( + self, + labware_definition: LabwareDefinition, + ) -> None: + """Raise an error if the labware is not compatible with the plate reader.""" + load_name = labware_definition.parameters.loadName + number_of_wells = len(labware_definition.wells) + if number_of_wells != 96: + raise errors.LabwareMovementNotAllowedError( + f"Cannot move '{load_name}' into plate reader because the" + f" labware contains {number_of_wells} wells where 96 wells is expected." + ) + elif labware_definition.dimensions.zDimension > _PLATE_READER_MAX_LABWARE_Z_MM: + raise errors.LabwareMovementNotAllowedError( + f"Cannot move '{load_name}' into plate reader because the" + f" maximum allowed labware height is {_PLATE_READER_MAX_LABWARE_Z_MM}mm." + ) + + def raise_if_labware_cannot_be_stacked( # noqa: C901 self, top_labware_definition: LabwareDefinition, bottom_labware_id: str ) -> None: """Raise if the specified labware definition cannot be placed on top of the bottom labware.""" @@ -784,17 +874,37 @@ def raise_if_labware_cannot_be_stacked( ) elif isinstance(below_labware.location, ModuleLocation): below_definition = self.get_definition(labware_id=below_labware.id) - if not labware_validation.validate_definition_is_adapter(below_definition): + if not labware_validation.validate_definition_is_adapter( + below_definition + ) and not labware_validation.validate_definition_is_lid( + top_labware_definition + ): raise errors.LabwareCannotBeStackedError( f"Labware {top_labware_definition.parameters.loadName} cannot be loaded" f" onto a labware on top of a module" ) elif isinstance(below_labware.location, OnLabwareLocation): + labware_stack = self.get_labware_stack([below_labware]) + stack_without_adapters = [] + for lw in labware_stack: + if not labware_validation.validate_definition_is_adapter( + self.get_definition(lw.id) + ): + stack_without_adapters.append(lw) + if len(stack_without_adapters) >= self.get_labware_stacking_maximum( + top_labware_definition + ): + raise errors.LabwareCannotBeStackedError( + f"Labware {top_labware_definition.parameters.loadName} cannot be loaded to stack of more than {self.get_labware_stacking_maximum(top_labware_definition)} labware." + ) + further_below_definition = self.get_definition( labware_id=below_labware.location.labwareId ) if labware_validation.validate_definition_is_adapter( further_below_definition + ) and not labware_validation.validate_definition_is_lid( + top_labware_definition ): raise errors.LabwareCannotBeStackedError( f"Labware {top_labware_definition.parameters.loadName} cannot be loaded" @@ -828,22 +938,60 @@ def get_deck_default_gripper_offsets(self) -> Optional[LabwareMovementOffsetData else None ) - def get_labware_gripper_offsets( + def get_absorbance_reader_lid_definition(self) -> LabwareDefinition: + """Return the special labware definition for the plate reader lid. + + See todo comments in `create_protocol_engine(). + """ + # NOTE: This needs to stay in sync with create_protocol_engine(). + return self._state.definitions_by_uri[ + "opentrons/opentrons_flex_lid_absorbance_plate_reader_module/1" + ] + + @overload + def get_child_gripper_offsets( self, - labware_id: str, + *, + labware_definition: LabwareDefinition, slot_name: Optional[DeckSlotName], ) -> Optional[LabwareMovementOffsetData]: - """Get the labware's gripper offsets of the specified type. + pass + + @overload + def get_child_gripper_offsets( + self, *, labware_id: str, slot_name: Optional[DeckSlotName] + ) -> Optional[LabwareMovementOffsetData]: + pass + + def get_child_gripper_offsets( + self, + *, + labware_definition: Optional[LabwareDefinition] = None, + labware_id: Optional[str] = None, + slot_name: Optional[DeckSlotName], + ) -> Optional[LabwareMovementOffsetData]: + """Get the grip offsets that a labware says should be applied to children stacked atop it. + + Params: + labware_id: The ID of a parent labware (atop which another labware, the child, will be stacked). + slot_name: The ancestor slot that the parent labware is ultimately loaded into, + perhaps after going through a module in the middle. Returns: - If `slot_name` is provided, returns the gripper offsets that the labware definition + If `slot_name` is provided, returns the gripper offsets that the parent labware definition specifies just for that slot, or `None` if the labware definition doesn't have an exact match. - If `slot_name` is `None`, returns the gripper offsets that the labware + If `slot_name` is `None`, returns the gripper offsets that the parent labware definition designates as "default," or `None` if it doesn't designate any as such. """ - parsed_offsets = self.get_definition(labware_id).gripperOffsets + if labware_id is not None: + labware_definition = self.get_definition(labware_id) + else: + # Should be ensured by our @overloads. + assert labware_definition is not None + + parsed_offsets = labware_definition.gripperOffsets offset_key = slot_name.id if slot_name else "default" if parsed_offsets is None or offset_key not in parsed_offsets: @@ -858,20 +1006,22 @@ def get_labware_gripper_offsets( ), ) - def get_grip_force(self, labware_id: str) -> float: + def get_grip_force(self, labware_definition: LabwareDefinition) -> float: """Get the recommended grip force for gripping labware using gripper.""" - recommended_force = self.get_definition(labware_id).gripForce + recommended_force = labware_definition.gripForce return ( recommended_force if recommended_force is not None else LABWARE_GRIP_FORCE ) - def get_grip_height_from_labware_bottom(self, labware_id: str) -> float: + def get_grip_height_from_labware_bottom( + self, labware_definition: LabwareDefinition + ) -> float: """Get the recommended grip height from labware bottom, if present.""" - recommended_height = self.get_definition(labware_id).gripHeightFromLabwareBottom + recommended_height = labware_definition.gripHeightFromLabwareBottom return ( recommended_height if recommended_height is not None - else self.get_dimensions(labware_id).z / 2 + else self.get_dimensions(labware_definition=labware_definition).z / 2 ) @staticmethod @@ -914,7 +1064,7 @@ def _min_y_of_well(well_defn: WellDefinition) -> float: def _max_z_of_well(well_defn: WellDefinition) -> float: return well_defn.z + well_defn.depth - def get_well_bbox(self, labware_id: str) -> Dimensions: + def get_well_bbox(self, labware_definition: LabwareDefinition) -> Dimensions: """Get the bounding box implied by the wells. The bounding box of the labware that is implied by the wells is that required @@ -925,14 +1075,13 @@ def get_well_bbox(self, labware_id: str) -> Dimensions: This is used for the specific purpose of finding the reasonable uncertainty bounds of where and how a gripper will interact with a labware. """ - defn = self.get_definition(labware_id) max_x: Optional[float] = None min_x: Optional[float] = None max_y: Optional[float] = None min_y: Optional[float] = None max_z: Optional[float] = None - for well in defn.wells.values(): + for well in labware_definition.wells.values(): well_max_x = self._max_x_of_well(well) well_min_x = self._min_x_of_well(well) well_max_y = self._max_y_of_well(well) diff --git a/api/src/opentrons/protocol_engine/state/liquid_classes.py b/api/src/opentrons/protocol_engine/state/liquid_classes.py new file mode 100644 index 00000000000..4010c7be821 --- /dev/null +++ b/api/src/opentrons/protocol_engine/state/liquid_classes.py @@ -0,0 +1,82 @@ +"""A data store of liquid classes.""" + +from __future__ import annotations + +import dataclasses +from typing import Dict +from typing_extensions import Optional + +from .. import errors +from ..actions import Action, get_state_updates +from ..types import LiquidClassRecord +from . import update_types +from ._abstract_store import HasState, HandlesActions + + +@dataclasses.dataclass +class LiquidClassState: + """Our state is a bidirectional mapping between IDs <-> LiquidClassRecords.""" + + # We use the bidirectional map to see if we've already assigned an ID to a liquid class when the + # engine is asked to store a new liquid class. + liquid_class_record_by_id: Dict[str, LiquidClassRecord] + liquid_class_record_to_id: Dict[LiquidClassRecord, str] + + +class LiquidClassStore(HasState[LiquidClassState], HandlesActions): + """Container for LiquidClassState.""" + + _state: LiquidClassState + + def __init__(self) -> None: + self._state = LiquidClassState( + liquid_class_record_by_id={}, + liquid_class_record_to_id={}, + ) + + def handle_action(self, action: Action) -> None: + """Update the state in response to the action.""" + for state_update in get_state_updates(action): + if state_update.liquid_class_loaded != update_types.NO_CHANGE: + self._handle_liquid_class_loaded_update( + state_update.liquid_class_loaded + ) + + def _handle_liquid_class_loaded_update( + self, state_update: update_types.LiquidClassLoadedUpdate + ) -> None: + # We're just a data store. All the validation and ID generation happens in the command implementation. + self._state.liquid_class_record_by_id[ + state_update.liquid_class_id + ] = state_update.liquid_class_record + self._state.liquid_class_record_to_id[ + state_update.liquid_class_record + ] = state_update.liquid_class_id + + +class LiquidClassView: + """Read-only view of the LiquidClassState.""" + + _state: LiquidClassState + + def __init__(self, state: LiquidClassState) -> None: + self._state = state + + def get(self, liquid_class_id: str) -> LiquidClassRecord: + """Get the LiquidClassRecord with the given identifier.""" + try: + return self._state.liquid_class_record_by_id[liquid_class_id] + except KeyError as e: + raise errors.LiquidClassDoesNotExistError( + f"Liquid class ID {liquid_class_id} not found." + ) from e + + def get_id_for_liquid_class_record( + self, liquid_class_record: LiquidClassRecord + ) -> Optional[str]: + """See if the given LiquidClassRecord if already in the store, and if so, return its identifier.""" + return self._state.liquid_class_record_to_id.get(liquid_class_record) + + def get_all(self) -> Dict[str, LiquidClassRecord]: + """Get all the LiquidClassRecords in the store.""" + return self._state.liquid_class_record_by_id.copy() diff --git a/api/src/opentrons/protocol_engine/state/liquids.py b/api/src/opentrons/protocol_engine/state/liquids.py index c19d2fc3b87..034e0c4030b 100644 --- a/api/src/opentrons/protocol_engine/state/liquids.py +++ b/api/src/opentrons/protocol_engine/state/liquids.py @@ -1,11 +1,11 @@ """Basic liquid data state and store.""" from dataclasses import dataclass from typing import Dict, List -from opentrons.protocol_engine.types import Liquid +from opentrons.protocol_engine.types import Liquid, LiquidId -from .abstract_store import HasState, HandlesActions +from ._abstract_store import HasState, HandlesActions from ..actions import Action, AddLiquidAction -from ..errors import LiquidDoesNotExistError +from ..errors import LiquidDoesNotExistError, InvalidLiquidError @dataclass @@ -34,7 +34,7 @@ def _add_liquid(self, action: AddLiquidAction) -> None: self._state.liquids_by_id[action.liquid.id] = action.liquid -class LiquidView(HasState[LiquidState]): +class LiquidView: """Read-only liquid state view.""" _state: LiquidState @@ -51,11 +51,23 @@ def get_all(self) -> List[Liquid]: """Get all protocol liquids.""" return list(self._state.liquids_by_id.values()) - def validate_liquid_id(self, liquid_id: str) -> str: + def validate_liquid_id(self, liquid_id: LiquidId) -> LiquidId: """Check if liquid_id exists in liquids.""" + is_empty = liquid_id == "EMPTY" + if is_empty: + return liquid_id has_liquid = liquid_id in self._state.liquids_by_id if not has_liquid: raise LiquidDoesNotExistError( f"Supplied liquidId: {liquid_id} does not exist in the loaded liquids." ) return liquid_id + + def validate_liquid_allowed(self, liquid: Liquid) -> Liquid: + """Validate that a liquid is legal to load.""" + is_empty = liquid.id == "EMPTY" + if is_empty: + raise InvalidLiquidError( + message='Protocols may not define a liquid with the special id "EMPTY".' + ) + return liquid diff --git a/api/src/opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py b/api/src/opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py index 1b92948fc90..79bdbc50b60 100644 --- a/api/src/opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py +++ b/api/src/opentrons/protocol_engine/state/module_substates/absorbance_reader_substate.py @@ -1,13 +1,17 @@ """Heater-Shaker Module sub-state.""" from dataclasses import dataclass -from typing import NewType, Optional, Dict +from typing import List, NewType, Optional, Dict from opentrons.protocol_engine.errors import CannotPerformModuleAction AbsorbanceReaderId = NewType("AbsorbanceReaderId", str) AbsorbanceReaderLidId = NewType("AbsorbanceReaderLidId", str) +AbsorbanceReaderMeasureMode = NewType("AbsorbanceReaderMeasureMode", str) +# todo(mm, 2024-11-08): frozen=True is getting pretty painful because ModuleStore has +# no type-safe way to modify just a single attribute. Consider unfreezing this +# (taking care to ensure that consumers of ModuleView still only get a read-only view). @dataclass(frozen=True) class AbsorbanceReaderSubState: """Absorbance-Plate-Reader-specific state.""" @@ -16,9 +20,10 @@ class AbsorbanceReaderSubState: configured: bool measured: bool is_lid_on: bool - data: Optional[Dict[str, float]] - configured_wavelength: Optional[int] - lid_id: Optional[str] + data: Optional[Dict[int, Dict[str, float]]] + configured_wavelengths: Optional[List[int]] + measure_mode: Optional[AbsorbanceReaderMeasureMode] + reference_wavelength: Optional[int] def raise_if_lid_status_not_expected(self, lid_on_expected: bool) -> None: """Raise if the lid status is not correct.""" diff --git a/api/src/opentrons/protocol_engine/state/modules.py b/api/src/opentrons/protocol_engine/state/modules.py index 2036032a947..0292329b8ea 100644 --- a/api/src/opentrons/protocol_engine/state/modules.py +++ b/api/src/opentrons/protocol_engine/state/modules.py @@ -26,10 +26,15 @@ get_west_slot, get_adjacent_staging_slot, ) +from opentrons.protocol_engine.actions.get_state_update import get_state_updates from opentrons.protocol_engine.commands.calibration.calibrate_module import ( CalibrateModuleResult, ) -from opentrons.types import DeckSlotName, MountType +from opentrons.protocol_engine.state import update_types +from opentrons.protocol_engine.state.module_substates.absorbance_reader_substate import ( + AbsorbanceReaderMeasureMode, +) +from opentrons.types import DeckSlotName, MountType, StagingSlotName from ..errors import ModuleNotConnectedError from ..types import ( @@ -64,9 +69,8 @@ Action, SucceedCommandAction, AddModuleAction, - AddAbsorbanceReaderLidAction, ) -from .abstract_store import HasState, HandlesActions +from ._abstract_store import HasState, HandlesActions from .module_substates import ( MagneticModuleSubState, HeaterShakerModuleSubState, @@ -231,13 +235,14 @@ def handle_action(self, action: Action) -> None: requested_model=None, module_live_data=action.module_live_data, ) - elif isinstance(action, AddAbsorbanceReaderLidAction): - self._update_absorbance_reader_lid_id( - module_id=action.module_id, - lid_id=action.lid_id, - ) + + for state_update in get_state_updates(action): + self._handle_state_update(state_update) def _handle_command(self, command: Command) -> None: + # todo(mm, 2024-11-04): Delete this function. Port these isinstance() + # checks to the update_types.StateUpdate mechanism. + if isinstance(command.result, LoadModuleResult): slot_name = command.params.location.slotName self._add_module_substate( @@ -294,36 +299,40 @@ def _handle_command(self, command: Command) -> None: if isinstance( command.result, ( - absorbance_reader.CloseLidResult, - absorbance_reader.OpenLidResult, absorbance_reader.InitializeResult, absorbance_reader.ReadAbsorbanceResult, ), ): self._handle_absorbance_reader_commands(command) - def _update_absorbance_reader_lid_id( - self, - module_id: str, - lid_id: str, - ) -> None: - abs_substate = self._state.substate_by_module_id.get(module_id) - assert isinstance( - abs_substate, AbsorbanceReaderSubState - ), f"{module_id} is not an absorbance plate reader." + def _handle_state_update(self, state_update: update_types.StateUpdate) -> None: + if state_update.absorbance_reader_lid != update_types.NO_CHANGE: + module_id = state_update.absorbance_reader_lid.module_id + is_lid_on = state_update.absorbance_reader_lid.is_lid_on + + # Get current values: + absorbance_reader_substate = self._state.substate_by_module_id[module_id] + assert isinstance( + absorbance_reader_substate, AbsorbanceReaderSubState + ), f"{module_id} is not an absorbance plate reader." + configured = absorbance_reader_substate.configured + measure_mode = absorbance_reader_substate.measure_mode + configured_wavelengths = absorbance_reader_substate.configured_wavelengths + reference_wavelength = absorbance_reader_substate.reference_wavelength + data = absorbance_reader_substate.data - prev_state: AbsorbanceReaderSubState = abs_substate - self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState( - module_id=AbsorbanceReaderId(module_id), - configured=prev_state.configured, - measured=prev_state.measured, - is_lid_on=prev_state.is_lid_on, - data=prev_state.data, - configured_wavelength=prev_state.configured_wavelength, - lid_id=lid_id, - ) + self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState( + module_id=AbsorbanceReaderId(module_id), + configured=configured, + measured=True, + is_lid_on=is_lid_on, + measure_mode=measure_mode, + configured_wavelengths=configured_wavelengths, + reference_wavelength=reference_wavelength, + data=data, + ) - def _add_module_substate( # noqa: C901 + def _add_module_substate( self, module_id: str, serial_number: Optional[str], @@ -382,29 +391,16 @@ def _add_module_substate( # noqa: C901 module_id=MagneticBlockId(module_id) ) elif ModuleModel.is_absorbance_reader(actual_model): - slot = self._state.slot_by_module_id[module_id] - if slot is not None: - reader_addressable_area = f"absorbanceReaderV1{slot.value}" - lid_labware_id = None - for labware in self._state.deck_fixed_labware: - if labware.location == AddressableAreaLocation( - addressableAreaName=reader_addressable_area - ): - lid_labware_id = labware.labware_id - break - self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState( - module_id=AbsorbanceReaderId(module_id), - configured=False, - measured=False, - is_lid_on=True, - data=None, - configured_wavelength=None, - lid_id=lid_labware_id, - ) - else: - raise errors.ModuleNotOnDeckError( - "Opentrons Plate Reader location did not return a valid Deck Slot." - ) + self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState( + module_id=AbsorbanceReaderId(module_id), + configured=False, + measured=False, + is_lid_on=True, + data=None, + measure_mode=None, + configured_wavelengths=None, + reference_wavelength=None, + ) def _update_additional_slots_occupied_by_thermocycler( self, @@ -577,7 +573,6 @@ def _handle_thermocycler_module_commands( target_block_temperature=block_temperature, target_lid_temperature=None, ) - # TODO (spp, 2022-08-01): set is_lid_open to False upon lid commands' failure elif isinstance(command.result, thermocycler.OpenLidResult): self._state.substate_by_module_id[module_id] = ThermocyclerModuleSubState( module_id=ThermocyclerModuleId(module_id), @@ -598,8 +593,6 @@ def _handle_absorbance_reader_commands( command: Union[ absorbance_reader.Initialize, absorbance_reader.ReadAbsorbance, - absorbance_reader.CloseLid, - absorbance_reader.OpenLid, ], ) -> None: module_id = command.params.moduleId @@ -610,10 +603,10 @@ def _handle_absorbance_reader_commands( # Get current values configured = absorbance_reader_substate.configured - configured_wavelength = absorbance_reader_substate.configured_wavelength + measure_mode = absorbance_reader_substate.measure_mode + configured_wavelengths = absorbance_reader_substate.configured_wavelengths + reference_wavelength = absorbance_reader_substate.reference_wavelength is_lid_on = absorbance_reader_substate.is_lid_on - lid_id = absorbance_reader_substate.lid_id - data = absorbance_reader_substate.data if isinstance(command.result, absorbance_reader.InitializeResult): self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState( @@ -621,45 +614,25 @@ def _handle_absorbance_reader_commands( configured=True, measured=False, is_lid_on=is_lid_on, + measure_mode=AbsorbanceReaderMeasureMode(command.params.measureMode), + configured_wavelengths=command.params.sampleWavelengths, + reference_wavelength=command.params.referenceWavelength, data=None, - configured_wavelength=command.params.sampleWavelength, - lid_id=lid_id, ) elif isinstance(command.result, absorbance_reader.ReadAbsorbanceResult): self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState( module_id=AbsorbanceReaderId(module_id), configured=configured, - configured_wavelength=configured_wavelength, - is_lid_on=is_lid_on, measured=True, + is_lid_on=is_lid_on, + measure_mode=measure_mode, + configured_wavelengths=configured_wavelengths, + reference_wavelength=reference_wavelength, data=command.result.data, - lid_id=lid_id, ) - elif isinstance(command.result, absorbance_reader.OpenLidResult): - self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState( - module_id=AbsorbanceReaderId(module_id), - configured=configured, - configured_wavelength=configured_wavelength, - is_lid_on=False, - measured=True, - data=data, - lid_id=lid_id, - ) - elif isinstance(command.result, absorbance_reader.CloseLidResult): - self._state.substate_by_module_id[module_id] = AbsorbanceReaderSubState( - module_id=AbsorbanceReaderId(module_id), - configured=configured, - configured_wavelength=configured_wavelength, - is_lid_on=True, - measured=True, - data=data, - lid_id=lid_id, - ) - - -class ModuleView(HasState[ModuleState]): +class ModuleView: """Read-only view of computed module state.""" _state: ModuleState @@ -871,15 +844,24 @@ def get_dimensions(self, module_id: str) -> ModuleDimensions: """Get the specified module's dimensions.""" return self.get_definition(module_id).dimensions - def get_nominal_module_offset( + def get_nominal_offset_to_child( self, module_id: str, + # todo(mm, 2024-11-07): A method of one view taking a sibling view as an argument + # is unusual, and may be bug-prone if the order in which the views are updated + # matters. If we need to compute something that depends on module info and + # addressable area info, can we do that computation in GeometryView instead of + # here? addressable_areas: AddressableAreaView, ) -> LabwareOffsetVector: - """Get the module's nominal offset vector computed with slot transform.""" + """Get the nominal offset from a module's location to its child labware's location. + + Includes the slot-specific transform. Does not include the child's + Labware Position Check offset. + """ if ( - self.state.deck_type == DeckType.OT2_STANDARD - or self.state.deck_type == DeckType.OT2_SHORT_TRASH + self._state.deck_type == DeckType.OT2_STANDARD + or self._state.deck_type == DeckType.OT2_SHORT_TRASH ): definition = self.get_definition(module_id) slot = self.get_location(module_id).slotName.id @@ -926,7 +908,7 @@ def get_nominal_module_offset( "Module location invalid for nominal module offset calculation." ) module_addressable_area = self.ensure_and_convert_module_fixture_location( - location, self.state.deck_type, module.model + location, self._state.deck_type, module.model ) module_addressable_area_position = ( addressable_areas.get_addressable_area_offsets_from_cutout( @@ -984,7 +966,7 @@ def get_module_highest_z( default_lw_offset_point = self.get_definition(module_id).labwareOffset.z z_difference = module_height - default_lw_offset_point - nominal_transformed_lw_offset_z = self.get_nominal_module_offset( + nominal_transformed_lw_offset_z = self.get_nominal_offset_to_child( module_id=module_id, addressable_areas=addressable_areas ).z calibration_offset = self.get_module_calibration_offset(module_id) @@ -1112,8 +1094,8 @@ def calculate_magnet_height( def should_dodge_thermocycler( self, - from_slot: DeckSlotName, - to_slot: DeckSlotName, + from_slot: Union[DeckSlotName, StagingSlotName], + to_slot: Union[DeckSlotName, StagingSlotName], ) -> bool: """Decide if the requested path would cross the thermocycler, if installed. @@ -1286,7 +1268,10 @@ def convert_absorbance_reader_data_points( row = chr(ord("A") + i // 12) # Convert index to row (A-H) col = (i % 12) + 1 # Convert index to column (1-12) well_key = f"{row}{col}" - well_map[well_key] = value + truncated_value = float( + "{:.5}".format(str(value)) + ) # Truncate the returned value to the third decimal place + well_map[well_key] = truncated_value return well_map else: raise ValueError( diff --git a/api/src/opentrons/protocol_engine/state/motion.py b/api/src/opentrons/protocol_engine/state/motion.py index e8eff73447b..0863c42a0c1 100644 --- a/api/src/opentrons/protocol_engine/state/motion.py +++ b/api/src/opentrons/protocol_engine/state/motion.py @@ -1,8 +1,8 @@ """Motion state store and getters.""" from dataclasses import dataclass -from typing import List, Optional +from typing import List, Optional, Union -from opentrons.types import MountType, Point +from opentrons.types import MountType, Point, StagingSlotName from opentrons.hardware_control.types import CriticalPoint from opentrons.motion_planning.adjacent_slots_getters import ( get_east_west_slots, @@ -10,11 +10,12 @@ ) from opentrons import motion_planning -from . import move_types +from . import _move_types from .. import errors from ..types import ( MotorAxis, WellLocation, + LiquidHandlingWellLocation, CurrentWell, CurrentPipetteLocation, AddressableOffsetVector, @@ -89,13 +90,14 @@ def get_movement_waypoints_to_well( pipette_id: str, labware_id: str, well_name: str, - well_location: Optional[WellLocation], + well_location: Optional[Union[WellLocation, LiquidHandlingWellLocation]], origin: Point, origin_cp: Optional[CriticalPoint], max_travel_z: float, current_well: Optional[CurrentWell] = None, force_direct: bool = False, minimum_z_height: Optional[float] = None, + operation_volume: Optional[float] = None, ) -> List[motion_planning.Waypoint]: """Calculate waypoints to a destination that's specified as a well.""" location = current_well or self._pipettes.get_current_location() @@ -107,12 +109,14 @@ def get_movement_waypoints_to_well( destination_cp = CriticalPoint.XY_CENTER destination = self._geometry.get_well_position( - labware_id, - well_name, - well_location, + labware_id=labware_id, + well_name=well_name, + well_location=well_location, + operation_volume=operation_volume, + pipette_id=pipette_id, ) - move_type = move_types.get_move_type_to_well( + move_type = _move_types.get_move_type_to_well( pipette_id, labware_id, well_name, location, force_direct ) min_travel_z = self._geometry.get_min_travel_z( @@ -151,6 +155,7 @@ def get_movement_waypoints_to_addressable_area( minimum_z_height: Optional[float] = None, stay_at_max_travel_z: bool = False, ignore_tip_configuration: Optional[bool] = True, + max_travel_z_extra_margin: Optional[float] = None, ) -> List[motion_planning.Waypoint]: """Calculate waypoints to a destination that's specified as an addressable area.""" location = self._pipettes.get_current_location() @@ -169,7 +174,9 @@ def get_movement_waypoints_to_addressable_area( # beneath max_travel_z. Investigate why motion_planning.get_waypoints() does not # let us travel at max_travel_z, and whether it's safe to make it do that. # Possibly related: https://github.com/Opentrons/opentrons/pull/6882#discussion_r514248062 - max_travel_z - motion_planning.waypoints.MINIMUM_Z_MARGIN, + max_travel_z + - motion_planning.waypoints.MINIMUM_Z_MARGIN + - (max_travel_z_extra_margin or 0.0), ) destination = base_destination_at_max_z + Point( offset.x, offset.y, offset.z @@ -270,9 +277,13 @@ def check_pipette_blocking_hs_latch( current_location = self._pipettes.get_current_location() if current_location is not None: if isinstance(current_location, CurrentWell): - pipette_deck_slot = self._geometry.get_ancestor_slot_name( + ancestor = self._geometry.get_ancestor_slot_name( current_location.labware_id - ).as_int() + ) + if isinstance(ancestor, StagingSlotName): + # Staging Area Slots cannot intersect with the h/s + return False + pipette_deck_slot = ancestor.as_int() else: pipette_deck_slot = ( self._addressable_areas.get_addressable_area_base_slot( @@ -292,9 +303,13 @@ def check_pipette_blocking_hs_shaker( current_location = self._pipettes.get_current_location() if current_location is not None: if isinstance(current_location, CurrentWell): - pipette_deck_slot = self._geometry.get_ancestor_slot_name( + ancestor = self._geometry.get_ancestor_slot_name( current_location.labware_id - ).as_int() + ) + if isinstance(ancestor, StagingSlotName): + # Staging Area Slots cannot intersect with the h/s + return False + pipette_deck_slot = ancestor.as_int() else: pipette_deck_slot = ( self._addressable_areas.get_addressable_area_base_slot( @@ -317,6 +332,10 @@ def get_touch_tip_waypoints( """Get a list of touch points for a touch tip operation.""" mount = self._pipettes.get_mount(pipette_id) labware_slot = self._geometry.get_ancestor_slot_name(labware_id) + if isinstance(labware_slot, StagingSlotName): + raise errors.LocationIsStagingSlotError( + "Cannot perform Touch Tip on labware in Staging Area Slot." + ) next_to_module = self._modules.is_edge_move_unsafe(mount, labware_slot) edge_path_type = self._labware.get_edge_path_type( labware_id, well_name, mount, labware_slot, next_to_module @@ -326,7 +345,7 @@ def get_touch_tip_waypoints( labware_id, well_name, radius ) - positions = move_types.get_edge_point_list( + positions = _move_types.get_edge_point_list( center_point, x_offset, y_offset, edge_path_type ) critical_point: Optional[CriticalPoint] = None diff --git a/api/src/opentrons/protocol_engine/state/pipettes.py b/api/src/opentrons/protocol_engine/state/pipettes.py index 58a798e90bd..9b7d289e890 100644 --- a/api/src/opentrons/protocol_engine/state/pipettes.py +++ b/api/src/opentrons/protocol_engine/state/pipettes.py @@ -1,26 +1,33 @@ """Basic pipette data state and store.""" + from __future__ import annotations -from dataclasses import dataclass -from typing import Dict, List, Mapping, Optional, Tuple, Union -from typing_extensions import assert_type + +import dataclasses +from logging import getLogger +from typing import ( + Dict, + List, + Mapping, + Optional, + Tuple, + cast, +) + +from typing_extensions import assert_never from opentrons_shared_data.pipette import pipette_definition +from opentrons_shared_data.pipette.ul_per_mm import calculate_ul_per_mm +from opentrons_shared_data.pipette.types import UlPerMmAction + from opentrons.config.defaults_ot2 import Z_RETRACT_DISTANCE from opentrons.hardware_control.dev_types import PipetteDict +from opentrons.hardware_control import CriticalPoint from opentrons.hardware_control.nozzle_manager import ( - NozzleConfigurationType, NozzleMap, ) -from opentrons.protocol_engine.actions.actions import FailCommandAction -from opentrons.protocol_engine.commands.command import DefinedErrorData -from opentrons.protocol_engine.commands.pipetting_common import ( - LiquidNotFoundError, - OverpressureError, - OverpressureErrorInternalData, -) -from opentrons.types import MountType, Mount as HwMount, Point +from opentrons.types import MountType, Mount as HwMount, Point, NozzleConfigurationType -from .. import commands +from . import update_types, fluid_stack from .. import errors from ..types import ( LoadedPipette, @@ -32,19 +39,17 @@ CurrentPipetteLocation, TipGeometry, ) -from ..commands.configuring_common import ( - PipetteConfigUpdateResultMixin, - PipetteNozzleLayoutResultMixin, -) from ..actions import ( Action, SetPipetteMovementSpeedAction, - SucceedCommandAction, + get_state_updates, ) -from .abstract_store import HasState, HandlesActions +from ._abstract_store import HasState, HandlesActions +LOG = getLogger(__name__) -@dataclass(frozen=True) + +@dataclasses.dataclass(frozen=True) class HardwarePipette: """Hardware pipette data.""" @@ -52,7 +57,7 @@ class HardwarePipette: config: PipetteDict -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class CurrentDeckPoint: """The latest deck point and mount the robot has accessed.""" @@ -60,7 +65,7 @@ class CurrentDeckPoint: deck_point: Optional[DeckPoint] -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class BoundingNozzlesOffsets: """Offsets of the bounding nozzles of the pipette.""" @@ -68,7 +73,7 @@ class BoundingNozzlesOffsets: front_right_offset: Point -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class PipetteBoundingBoxOffsets: """Offsets of the corners of the pipette's bounding box.""" @@ -78,7 +83,7 @@ class PipetteBoundingBoxOffsets: front_left_corner: Point -@dataclass(frozen=True) +@dataclasses.dataclass(frozen=True) class StaticPipetteConfig: """Static config for a pipette.""" @@ -96,23 +101,29 @@ class StaticPipetteConfig: nozzle_offset_z: float pipette_bounding_box_offsets: PipetteBoundingBoxOffsets bounding_nozzle_offsets: BoundingNozzlesOffsets - default_nozzle_map: NozzleMap + default_nozzle_map: NozzleMap # todo(mm, 2024-10-14): unused, remove? lld_settings: Optional[Dict[str, Dict[str, float]]] + plunger_positions: Dict[str, float] + shaft_ul_per_mm: float + available_sensors: pipette_definition.AvailableSensorDefinition -@dataclass +@dataclasses.dataclass class PipetteState: """Basic pipette data state and getter methods.""" + # todo(mm, 2024-10-14): It's getting difficult to ensure that all of these + # attributes are populated at the appropriate times. Refactor to a + # single dict-of-many-things instead of many dicts-of-single-things. pipettes_by_id: Dict[str, LoadedPipette] - aspirated_volume_by_id: Dict[str, Optional[float]] + pipette_contents_by_id: Dict[str, Optional[fluid_stack.FluidStack]] current_location: Optional[CurrentPipetteLocation] current_deck_point: CurrentDeckPoint attached_tip_by_id: Dict[str, Optional[TipGeometry]] movement_speed_by_id: Dict[str, Optional[float]] static_config_by_id: Dict[str, StaticPipetteConfig] flow_rates_by_id: Dict[str, FlowRates] - nozzle_configuration_by_id: Dict[str, Optional[NozzleMap]] + nozzle_configuration_by_id: Dict[str, NozzleMap] liquid_presence_detection_by_id: Dict[str, bool] @@ -125,7 +136,7 @@ def __init__(self) -> None: """Initialize a PipetteStore and its state.""" self._state = PipetteState( pipettes_by_id={}, - aspirated_volume_by_id={}, + pipette_contents_by_id={}, attached_tip_by_id={}, current_location=None, current_deck_point=CurrentDeckPoint(mount=None, deck_point=None), @@ -138,29 +149,123 @@ def __init__(self) -> None: def handle_action(self, action: Action) -> None: """Modify state in reaction to an action.""" - if isinstance(action, (SucceedCommandAction, FailCommandAction)): - self._handle_command(action) - elif isinstance(action, SetPipetteMovementSpeedAction): + for state_update in get_state_updates(action): + self._set_load_pipette(state_update) + self._update_current_location(state_update) + self._update_pipette_config(state_update) + self._update_pipette_nozzle_map(state_update) + self._update_tip_state(state_update) + self._update_volumes(state_update) + + if isinstance(action, SetPipetteMovementSpeedAction): self._state.movement_speed_by_id[action.pipette_id] = action.speed - def _handle_command( # noqa: C901 - self, action: Union[SucceedCommandAction, FailCommandAction] - ) -> None: - self._update_current_location(action) - self._update_deck_point(action) - self._update_volumes(action) + def _set_load_pipette(self, state_update: update_types.StateUpdate) -> None: + if state_update.loaded_pipette != update_types.NO_CHANGE: + pipette_id = state_update.loaded_pipette.pipette_id - if not isinstance(action, SucceedCommandAction): - return + self._state.pipettes_by_id[pipette_id] = LoadedPipette( + id=pipette_id, + pipetteName=state_update.loaded_pipette.pipette_name, + mount=state_update.loaded_pipette.mount, + ) + self._state.liquid_presence_detection_by_id[pipette_id] = ( + state_update.loaded_pipette.liquid_presence_detection or False + ) + self._state.movement_speed_by_id[pipette_id] = None + self._state.attached_tip_by_id[pipette_id] = None - command, private_result = action.command, action.private_result + def _update_tip_state(self, state_update: update_types.StateUpdate) -> None: + if state_update.pipette_tip_state != update_types.NO_CHANGE: + pipette_id = state_update.pipette_tip_state.pipette_id + if state_update.pipette_tip_state.tip_geometry: + attached_tip = state_update.pipette_tip_state.tip_geometry + + self._state.attached_tip_by_id[pipette_id] = attached_tip + + static_config = self._state.static_config_by_id.get(pipette_id) + if static_config: + try: + tip_configuration = ( + static_config.tip_configuration_lookup_table[ + attached_tip.volume + ] + ) + except KeyError: + # TODO(seth,9/11/2023): this is a bad way of doing defaults but better than max volume. + # we used to look up a default tip config via the pipette max volume, but if that isn't + # tip volume (as it isn't when we're in low-volume mode) then that lookup fails. Using + # the first entry in the table is ok I guess but we really need to generally rethink how + # we identify tip classes - looking things up by volume is not enough. + tip_configuration = list( + static_config.tip_configuration_lookup_table.values() + )[0] + self._state.flow_rates_by_id[pipette_id] = FlowRates( + default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level, + default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level, + default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level, + ) + + else: + pipette_id = state_update.pipette_tip_state.pipette_id + self._state.attached_tip_by_id[pipette_id] = None + + static_config = self._state.static_config_by_id.get(pipette_id) + if static_config: + # TODO(seth,9/11/2023): bad way to do defaulting, see above. + tip_configuration = list( + static_config.tip_configuration_lookup_table.values() + )[0] + self._state.flow_rates_by_id[pipette_id] = FlowRates( + default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level, + default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level, + default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level, + ) + + def _update_current_location(self, state_update: update_types.StateUpdate) -> None: + location_update = state_update.pipette_location + + if location_update is update_types.NO_CHANGE: + pass + elif location_update is update_types.CLEAR: + self._state.current_location = None + self._state.current_deck_point = CurrentDeckPoint( + mount=None, deck_point=None + ) + else: + new_logical_location = location_update.new_location + new_deck_point = location_update.new_deck_point + match new_logical_location: + case update_types.Well(labware_id=labware_id, well_name=well_name): + self._state.current_location = CurrentWell( + pipette_id=location_update.pipette_id, + labware_id=labware_id, + well_name=well_name, + ) + case update_types.AddressableArea( + addressable_area_name=addressable_area_name + ): + self._state.current_location = CurrentAddressableArea( + pipette_id=location_update.pipette_id, + addressable_area_name=addressable_area_name, + ) + case None: + self._state.current_location = None + case update_types.NO_CHANGE: + pass + if new_deck_point is not update_types.NO_CHANGE: + loaded_pipette = self._state.pipettes_by_id[location_update.pipette_id] + self._state.current_deck_point = CurrentDeckPoint( + mount=loaded_pipette.mount, deck_point=new_deck_point + ) - if isinstance(private_result, PipetteConfigUpdateResultMixin): - config = private_result.config + def _update_pipette_config(self, state_update: update_types.StateUpdate) -> None: + if state_update.pipette_config != update_types.NO_CHANGE: + config = state_update.pipette_config.config self._state.static_config_by_id[ - private_result.pipette_id + state_update.pipette_config.pipette_id ] = StaticPipetteConfig( - serial_number=private_result.serial_number, + serial_number=state_update.pipette_config.serial_number, model=config.model, display_name=config.display_name, min_volume=config.min_volume, @@ -190,323 +295,62 @@ def _handle_command( # noqa: C901 ), default_nozzle_map=config.nozzle_map, lld_settings=config.pipette_lld_settings, + plunger_positions=config.plunger_positions, + shaft_ul_per_mm=config.shaft_ul_per_mm, + available_sensors=config.available_sensors, ) - self._state.flow_rates_by_id[private_result.pipette_id] = config.flow_rates + self._state.flow_rates_by_id[ + state_update.pipette_config.pipette_id + ] = config.flow_rates self._state.nozzle_configuration_by_id[ - private_result.pipette_id + state_update.pipette_config.pipette_id ] = config.nozzle_map - elif isinstance(private_result, PipetteNozzleLayoutResultMixin): - self._state.nozzle_configuration_by_id[ - private_result.pipette_id - ] = private_result.nozzle_map - - if isinstance(command.result, commands.LoadPipetteResult): - pipette_id = command.result.pipetteId - - self._state.pipettes_by_id[pipette_id] = LoadedPipette( - id=pipette_id, - pipetteName=command.params.pipetteName, - mount=command.params.mount, - ) - self._state.liquid_presence_detection_by_id[pipette_id] = ( - command.params.liquidPresenceDetection or False - ) - self._state.aspirated_volume_by_id[pipette_id] = None - self._state.movement_speed_by_id[pipette_id] = None - self._state.attached_tip_by_id[pipette_id] = None - static_config = self._state.static_config_by_id.get(pipette_id) - if static_config: - self._state.nozzle_configuration_by_id[ - pipette_id - ] = static_config.default_nozzle_map - - elif isinstance(command.result, commands.PickUpTipResult): - pipette_id = command.params.pipetteId - attached_tip = TipGeometry( - length=command.result.tipLength, - volume=command.result.tipVolume, - diameter=command.result.tipDiameter, - ) - - self._state.attached_tip_by_id[pipette_id] = attached_tip - self._state.aspirated_volume_by_id[pipette_id] = 0 - - static_config = self._state.static_config_by_id.get(pipette_id) - if static_config: - try: - tip_configuration = static_config.tip_configuration_lookup_table[ - attached_tip.volume - ] - except KeyError: - # TODO(seth,9/11/2023): this is a bad way of doing defaults but better than max volume. - # we used to look up a default tip config via the pipette max volume, but if that isn't - # tip volume (as it isn't when we're in low-volume mode) then that lookup fails. Using - # the first entry in the table is ok I guess but we really need to generally rethink how - # we identify tip classes - looking things up by volume is not enough. - tip_configuration = list( - static_config.tip_configuration_lookup_table.values() - )[0] - self._state.flow_rates_by_id[pipette_id] = FlowRates( - default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level, - default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level, - default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level, - ) - elif isinstance( - command.result, - ( - commands.DropTipResult, - commands.DropTipInPlaceResult, - commands.unsafe.UnsafeDropTipInPlaceResult, - ), - ): - pipette_id = command.params.pipetteId - self._state.aspirated_volume_by_id[pipette_id] = None - self._state.attached_tip_by_id[pipette_id] = None - - static_config = self._state.static_config_by_id.get(pipette_id) - if static_config: - # TODO(seth,9/11/2023): bad way to do defaulting, see above. - tip_configuration = list( - static_config.tip_configuration_lookup_table.values() - )[0] - self._state.flow_rates_by_id[pipette_id] = FlowRates( - default_blow_out=tip_configuration.default_blowout_flowrate.values_by_api_level, - default_aspirate=tip_configuration.default_aspirate_flowrate.values_by_api_level, - default_dispense=tip_configuration.default_dispense_flowrate.values_by_api_level, - ) - - def _update_current_location( # noqa: C901 - self, action: Union[SucceedCommandAction, FailCommandAction] + def _update_pipette_nozzle_map( + self, state_update: update_types.StateUpdate ) -> None: - # These commands leave the pipette in a new location. - # Update current_location to reflect that. - if isinstance(action, SucceedCommandAction) and isinstance( - action.command.result, - ( - commands.MoveToWellResult, - commands.PickUpTipResult, - commands.DropTipResult, - commands.AspirateResult, - commands.DispenseResult, - commands.BlowOutResult, - commands.TouchTipResult, - commands.LiquidProbeResult, - commands.TryLiquidProbeResult, - ), - ): - self._state.current_location = CurrentWell( - pipette_id=action.command.params.pipetteId, - labware_id=action.command.params.labwareId, - well_name=action.command.params.wellName, - ) - elif isinstance(action, FailCommandAction) and ( - isinstance(action.error, DefinedErrorData) - and ( - ( - isinstance( - action.running_command, (commands.Aspirate, commands.Dispense) - ) - and isinstance(action.error.public, OverpressureError) - ) - or ( - isinstance(action.running_command, commands.LiquidProbe) - and isinstance(action.error.public, LiquidNotFoundError) - ) - ) - ): - self._state.current_location = CurrentWell( - pipette_id=action.running_command.params.pipetteId, - labware_id=action.running_command.params.labwareId, - well_name=action.running_command.params.wellName, - ) - elif isinstance(action, SucceedCommandAction) and isinstance( - action.command.result, - ( - commands.MoveToAddressableAreaResult, - commands.MoveToAddressableAreaForDropTipResult, - ), - ): - self._state.current_location = CurrentAddressableArea( - pipette_id=action.command.params.pipetteId, - addressable_area_name=action.command.params.addressableAreaName, - ) - - # These commands leave the pipette in a place that we can't logically associate - # with a well. Clear current_location to reflect the fact that it's now unknown. - # - # TODO(mc, 2021-11-12): Wipe out current_location on movement failures, too. - # TODO(jbl 2023-02-14): Need to investigate whether move relative should clear current location - elif isinstance(action, SucceedCommandAction) and isinstance( - action.command.result, - ( - commands.HomeResult, - commands.RetractAxisResult, - commands.MoveToCoordinatesResult, - commands.thermocycler.OpenLidResult, - commands.thermocycler.CloseLidResult, - ), - ): - self._state.current_location = None - - # Heater-Shaker commands may have left the pipette in a place that we can't - # associate with a logical location, depending on their result. - elif isinstance(action, SucceedCommandAction) and isinstance( - action.command.result, - ( - commands.heater_shaker.SetAndWaitForShakeSpeedResult, - commands.heater_shaker.OpenLabwareLatchResult, - ), - ): - if action.command.result.pipetteRetracted: - self._state.current_location = None - - # A moveLabware command may have moved the labware that contains the current - # well out from under the pipette. Clear the current location to reflect the - # fact that the pipette is no longer over any labware. - # - # This is necessary for safe motion planning in case the next movement - # goes to the same labware (now in a new place). - elif isinstance(action, SucceedCommandAction) and isinstance( - action.command.result, commands.MoveLabwareResult - ): - moved_labware_id = action.command.params.labwareId - if action.command.params.strategy == "usingGripper": - # All mounts will have been retracted. - self._state.current_location = None - elif ( - isinstance(self._state.current_location, CurrentWell) - and self._state.current_location.labware_id == moved_labware_id - ): - self._state.current_location = None - - def _update_deck_point( - self, action: Union[SucceedCommandAction, FailCommandAction] - ) -> None: - # This function mostly mirrors self._update_current_location(). - # See there for explanations. - - if isinstance(action, SucceedCommandAction) and isinstance( - action.command.result, - ( - commands.MoveToWellResult, - commands.MoveToCoordinatesResult, - commands.MoveRelativeResult, - commands.MoveToAddressableAreaResult, - commands.MoveToAddressableAreaForDropTipResult, - commands.PickUpTipResult, - commands.DropTipResult, - commands.AspirateResult, - commands.DispenseResult, - commands.BlowOutResult, - commands.TouchTipResult, - ), - ): - pipette_id = action.command.params.pipetteId - deck_point = action.command.result.position - loaded_pipette = self._state.pipettes_by_id[pipette_id] - self._state.current_deck_point = CurrentDeckPoint( - mount=loaded_pipette.mount, deck_point=deck_point - ) - elif ( - isinstance(action, FailCommandAction) - and isinstance( - action.running_command, - ( - commands.Aspirate, - commands.Dispense, - commands.AspirateInPlace, - commands.DispenseInPlace, - ), - ) - and isinstance(action.error, DefinedErrorData) - and isinstance(action.error.public, OverpressureError) - ): - assert_type(action.error.private, OverpressureErrorInternalData) - pipette_id = action.running_command.params.pipetteId - deck_point = action.error.private.position - loaded_pipette = self._state.pipettes_by_id[pipette_id] - self._state.current_deck_point = CurrentDeckPoint( - mount=loaded_pipette.mount, deck_point=deck_point - ) - - elif isinstance(action, SucceedCommandAction) and isinstance( - action.command.result, - ( - commands.HomeResult, - commands.RetractAxisResult, - commands.thermocycler.OpenLidResult, - commands.thermocycler.CloseLidResult, - ), - ): - self._clear_deck_point() - - elif isinstance(action, SucceedCommandAction) and isinstance( - action.command.result, - ( - commands.heater_shaker.SetAndWaitForShakeSpeedResult, - commands.heater_shaker.OpenLabwareLatchResult, - ), - ): - if action.command.result.pipetteRetracted: - self._clear_deck_point() + if state_update.pipette_nozzle_map != update_types.NO_CHANGE: + self._state.nozzle_configuration_by_id[ + state_update.pipette_nozzle_map.pipette_id + ] = state_update.pipette_nozzle_map.nozzle_map - elif isinstance(action, SucceedCommandAction) and isinstance( - action.command.result, commands.MoveLabwareResult - ): - if action.command.params.strategy == "usingGripper": - # All mounts will have been retracted. - self._clear_deck_point() + def _update_volumes(self, state_update: update_types.StateUpdate) -> None: + if state_update.pipette_aspirated_fluid == update_types.NO_CHANGE: + return + if state_update.pipette_aspirated_fluid.type == "aspirated": + self._update_aspirated(state_update.pipette_aspirated_fluid) + elif state_update.pipette_aspirated_fluid.type == "ejected": + self._update_ejected(state_update.pipette_aspirated_fluid) + elif state_update.pipette_aspirated_fluid.type == "empty": + self._update_empty(state_update.pipette_aspirated_fluid) + elif state_update.pipette_aspirated_fluid.type == "unknown": + self._update_unknown(state_update.pipette_aspirated_fluid) + else: + assert_never(state_update.pipette_aspirated_fluid.type) - def _update_volumes( - self, action: Union[SucceedCommandAction, FailCommandAction] + def _update_aspirated( + self, update: update_types.PipetteAspiratedFluidUpdate ) -> None: - if isinstance(action, SucceedCommandAction) and isinstance( - action.command.result, - (commands.AspirateResult, commands.AspirateInPlaceResult), - ): - pipette_id = action.command.params.pipetteId - previous_volume = self._state.aspirated_volume_by_id[pipette_id] or 0 - # PipetteHandler will have clamped action.command.result.volume for us, so - # next_volume should always be in bounds. - next_volume = previous_volume + action.command.result.volume + self._fluid_stack_log_if_empty(update.pipette_id).add_fluid(update.fluid) - self._state.aspirated_volume_by_id[pipette_id] = next_volume + def _update_ejected(self, update: update_types.PipetteEjectedFluidUpdate) -> None: + self._fluid_stack_log_if_empty(update.pipette_id).remove_fluid(update.volume) - elif isinstance(action, SucceedCommandAction) and isinstance( - action.command.result, - (commands.DispenseResult, commands.DispenseInPlaceResult), - ): - pipette_id = action.command.params.pipetteId - previous_volume = self._state.aspirated_volume_by_id[pipette_id] or 0 - # PipetteHandler will have clamped action.command.result.volume for us, so - # next_volume should always be in bounds. - next_volume = previous_volume - action.command.result.volume - self._state.aspirated_volume_by_id[pipette_id] = next_volume - - elif isinstance(action, SucceedCommandAction) and isinstance( - action.command.result, - ( - commands.BlowOutResult, - commands.BlowOutInPlaceResult, - commands.unsafe.UnsafeBlowOutInPlaceResult, - ), - ): - pipette_id = action.command.params.pipetteId - self._state.aspirated_volume_by_id[pipette_id] = None + def _update_empty(self, update: update_types.PipetteEmptyFluidUpdate) -> None: + self._state.pipette_contents_by_id[update.pipette_id] = fluid_stack.FluidStack() - elif isinstance(action, SucceedCommandAction) and isinstance( - action.command.result, commands.PrepareToAspirateResult - ): - pipette_id = action.command.params.pipetteId - self._state.aspirated_volume_by_id[pipette_id] = 0 + def _update_unknown(self, update: update_types.PipetteUnknownFluidUpdate) -> None: + self._state.pipette_contents_by_id[update.pipette_id] = None - def _clear_deck_point(self) -> None: - """Reset last deck point to default None value for mount and point.""" - self._state.current_deck_point = CurrentDeckPoint(mount=None, deck_point=None) + def _fluid_stack_log_if_empty(self, pipette_id: str) -> fluid_stack.FluidStack: + stack = self._state.pipette_contents_by_id[pipette_id] + if stack is None: + LOG.error("Pipette state tried to alter an unknown-contents pipette") + return fluid_stack.FluidStack() + return stack -class PipetteView(HasState[PipetteState]): +class PipetteView: """Read-only view of computed pipettes state.""" _state: PipetteState @@ -608,9 +452,13 @@ def get_all_attached_tips(self) -> List[Tuple[str, TipGeometry]]: def get_aspirated_volume(self, pipette_id: str) -> Optional[float]: """Get the currently aspirated volume of a pipette by ID. + This is the volume currently displaced by the plunger relative to its bottom position, + regardless of whether that volume likely contains liquid or air. This makes it the right + function to call to know how much more volume the plunger may displace. + Returns: The volume the pipette has aspirated. - None, after blow-out and the plunger is in an unsafe position or drop-tip and there is no tip attached. + None, after blow-out and the plunger is in an unsafe position. Raises: PipetteNotLoadedError: pipette ID does not exist. @@ -619,13 +467,50 @@ def get_aspirated_volume(self, pipette_id: str) -> Optional[float]: self.validate_tip_state(pipette_id, True) try: - return self._state.aspirated_volume_by_id[pipette_id] + stack = self._state.pipette_contents_by_id[pipette_id] + if stack is None: + return None + return stack.aspirated_volume() except KeyError as e: raise errors.PipetteNotLoadedError( f"Pipette {pipette_id} not found; unable to get current volume." ) from e + def get_liquid_dispensed_by_ejecting_volume( + self, pipette_id: str, volume: float + ) -> Optional[float]: + """Get the amount of liquid (not air) that will be dispensed if the pipette ejects a specified volume. + + For instance, if the pipette contains, in vertical order, + 10 ul air + 80 ul liquid + 5 ul air + + then dispensing 10ul would result in 5ul of liquid; dispensing 85 ul would result in 80ul liquid; dispensing + 95ul would result in 80ul liquid. + + Returns: + The volume of liquid that would be dispensed by the requested volume. + None, after blow-out or when the plunger is in an unsafe position. + + Raises: + PipetteNotLoadedError: pipette ID does not exist. + TipnotAttachedError: No tip is attached to the pipette. + """ + self.validate_tip_state(pipette_id, True) + + try: + stack = self._state.pipette_contents_by_id[pipette_id] + if stack is None: + return None + return stack.liquid_part_of_dispense_volume(volume) + + except KeyError as e: + raise errors.PipetteNotLoadedError( + f"Pipette {pipette_id} not found; unable to get current liquid volume." + ) from e + def get_working_volume(self, pipette_id: str) -> float: """Get the working maximum volume of a pipette by ID. @@ -780,32 +665,38 @@ def get_plunger_axis(self, pipette_id: str) -> MotorAxis: def get_nozzle_layout_type(self, pipette_id: str) -> NozzleConfigurationType: """Get the current set nozzle layout configuration.""" - nozzle_map_for_pipette = self._state.nozzle_configuration_by_id.get(pipette_id) - if nozzle_map_for_pipette: - return nozzle_map_for_pipette.configuration - else: - return NozzleConfigurationType.FULL + nozzle_map_for_pipette = self._state.nozzle_configuration_by_id[pipette_id] + return nozzle_map_for_pipette.configuration def get_is_partially_configured(self, pipette_id: str) -> bool: """Determine if the provided pipette is partially configured.""" return self.get_nozzle_layout_type(pipette_id) != NozzleConfigurationType.FULL - def get_primary_nozzle(self, pipette_id: str) -> Optional[str]: + def get_primary_nozzle(self, pipette_id: str) -> str: """Get the primary nozzle, if any, related to the given pipette's nozzle configuration.""" - nozzle_map = self._state.nozzle_configuration_by_id.get(pipette_id) - return nozzle_map.starting_nozzle if nozzle_map else None - - def get_primary_nozzle_offset(self, pipette_id: str) -> Point: - """Get the pipette's current primary nozzle's offset.""" - nozzle_map = self._state.nozzle_configuration_by_id.get(pipette_id) - if nozzle_map: - primary_nozzle_offset = nozzle_map.starting_nozzle_offset - else: - # When not in partial configuration, back-left nozzle is the primary - primary_nozzle_offset = self.get_config( - pipette_id - ).bounding_nozzle_offsets.back_left_offset - return primary_nozzle_offset + nozzle_map = self._state.nozzle_configuration_by_id[pipette_id] + return nozzle_map.starting_nozzle + + def get_nozzle_configuration(self, pipette_id: str) -> NozzleMap: + """Get the nozzle map of the pipette.""" + return self._state.nozzle_configuration_by_id[pipette_id] + + def _get_critical_point_offset_without_tip( + self, pipette_id: str, critical_point: Optional[CriticalPoint] + ) -> Point: + """Get the offset of the specified critical point from pipette's mount position.""" + nozzle_map = self._state.nozzle_configuration_by_id[pipette_id] + match critical_point: + case CriticalPoint.INSTRUMENT_XY_CENTER: + return nozzle_map.instrument_xy_center_offset + case CriticalPoint.XY_CENTER: + return nozzle_map.xy_center_offset + case CriticalPoint.Y_CENTER: + return nozzle_map.y_center_offset + case CriticalPoint.FRONT_NOZZLE: + return nozzle_map.front_nozzle_offset + case _: + return nozzle_map.starting_nozzle_offset def get_pipette_bounding_nozzle_offsets( self, pipette_id: str @@ -817,36 +708,48 @@ def get_pipette_bounding_box(self, pipette_id: str) -> PipetteBoundingBoxOffsets """Get the bounding box of the pipette.""" return self.get_config(pipette_id).pipette_bounding_box_offsets + # TODO (spp, 2024-09-17): in order to find the position of pipette at destination, + # this method repeats the same steps that waypoints builder does while finding + # waypoints to move to. We should consolidate these steps into a shared entity + # so that the deck conflict checker and movement plan builder always remain in sync. def get_pipette_bounds_at_specified_move_to_position( self, pipette_id: str, destination_position: Point, + critical_point: Optional[CriticalPoint], ) -> Tuple[Point, Point, Point, Point]: - """Get the pipette's bounding offsets when primary nozzle is at the given position.""" - primary_nozzle_offset = self.get_primary_nozzle_offset(pipette_id) + """Get the pipette's bounding box position when critical point is at the destination position. + + Returns a tuple of the pipette's bounding box position in deck coordinates as- + (back_left_bound, front_right_bound, back_right_bound, front_left_bound) + Bounding box of the pipette includes the pipette's outer casing as well as nozzles. + """ tip = self.get_attached_tip(pipette_id) - # TODO update this for pipette robot stackup - # Primary nozzle position at destination, in deck coordinates - primary_nozzle_position = destination_position + Point( + + # *Offset* of pipette's critical point w.r.t pipette mount + critical_point_offset = self._get_critical_point_offset_without_tip( + pipette_id, critical_point + ) + + # Position of the above critical point at destination, in deck coordinates + critical_point_position = destination_position + Point( x=0, y=0, z=tip.length if tip else 0 ) - # Get the pipette bounding box based on total nozzles + # Get the pipette bounding box coordinates pipette_bounds_offsets = self.get_config( pipette_id ).pipette_bounding_box_offsets pip_back_left_bound = ( - primary_nozzle_position - - primary_nozzle_offset + critical_point_position + - critical_point_offset + pipette_bounds_offsets.back_left_corner ) pip_front_right_bound = ( - primary_nozzle_position - - primary_nozzle_offset + critical_point_position + - critical_point_offset + pipette_bounds_offsets.front_right_corner ) - # TODO (spp, 2024-02-27): remove back right & front left; - # return only back left and front right points. pip_back_right_bound = Point( pip_front_right_bound.x, pip_back_left_bound.y, pip_front_right_bound.z ) @@ -860,6 +763,13 @@ def get_pipette_bounds_at_specified_move_to_position( pip_front_left_bound, ) + def get_pipette_supports_pressure(self, pipette_id: str) -> bool: + """Return if this pipette supports a pressure sensor.""" + return ( + "pressure" + in self._state.static_config_by_id[pipette_id].available_sensors.sensors + ) + def get_liquid_presence_detection(self, pipette_id: str) -> bool: """Determine if liquid presence detection is enabled for this pipette.""" try: @@ -868,3 +778,42 @@ def get_liquid_presence_detection(self, pipette_id: str) -> bool: raise errors.PipetteNotLoadedError( f"Pipette {pipette_id} not found; unable to determine if pipette liquid presence detection enabled." ) from e + + def get_nozzle_configuration_supports_lld(self, pipette_id: str) -> bool: + """Determine if the current partial tip configuration supports LLD.""" + nozzle_map = self.get_nozzle_configuration(pipette_id) + if ( + nozzle_map.physical_nozzle_count == 96 + and nozzle_map.back_left != nozzle_map.full_instrument_back_left + and nozzle_map.front_right != nozzle_map.full_instrument_front_right + ): + return False + return True + + def lookup_volume_to_mm_conversion( + self, pipette_id: str, volume: float, action: str + ) -> float: + """Get the volumn to mm conversion for a pipette.""" + try: + lookup_volume = self.get_working_volume(pipette_id) + except errors.TipNotAttachedError: + lookup_volume = self.get_maximum_volume(pipette_id) + + pipette_config = self.get_config(pipette_id) + lookup_table_from_config = pipette_config.tip_configuration_lookup_table + try: + tip_settings = lookup_table_from_config[lookup_volume] + except KeyError: + tip_settings = list(lookup_table_from_config.values())[0] + return calculate_ul_per_mm( + volume, + cast(UlPerMmAction, action), + tip_settings, + shaft_ul_per_mm=pipette_config.shaft_ul_per_mm, + ) + + def lookup_plunger_position_name( + self, pipette_id: str, position_name: str + ) -> float: + """Get the plunger position provided for the given pipette id.""" + return self.get_config(pipette_id).plunger_positions[position_name] diff --git a/api/src/opentrons/protocol_engine/state/state.py b/api/src/opentrons/protocol_engine/state/state.py index 4244931efd1..58e977cc2f4 100644 --- a/api/src/opentrons/protocol_engine/state/state.py +++ b/api/src/opentrons/protocol_engine/state/state.py @@ -9,12 +9,12 @@ from opentrons_shared_data.robot.types import RobotDefinition from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryPolicy -from opentrons.protocol_engine.types import ModuleOffsetData +from opentrons.protocol_engine.types import LiquidClassRecordWithId, ModuleOffsetData from opentrons.util.change_notifier import ChangeNotifier from ..resources import DeckFixedLabware from ..actions import Action, ActionHandler -from .abstract_store import HasState, HandlesActions +from ._abstract_store import HasState, HandlesActions from .commands import CommandState, CommandStore, CommandView from .addressable_areas import ( AddressableAreaState, @@ -25,9 +25,12 @@ from .pipettes import PipetteState, PipetteStore, PipetteView from .modules import ModuleState, ModuleStore, ModuleView from .liquids import LiquidState, LiquidView, LiquidStore +from .liquid_classes import LiquidClassState, LiquidClassStore, LiquidClassView from .tips import TipState, TipView, TipStore +from .wells import WellState, WellView, WellStore from .geometry import GeometryView from .motion import MotionView +from .files import FileView, FileState, FileStore from .config import Config from .state_summary import StateSummary from ..types import DeckConfigurationType @@ -47,7 +50,10 @@ class State: pipettes: PipetteState modules: ModuleState liquids: LiquidState + liquid_classes: LiquidClassState tips: TipState + wells: WellState + files: FileState class StateView(HasState[State]): @@ -60,9 +66,12 @@ class StateView(HasState[State]): _pipettes: PipetteView _modules: ModuleView _liquid: LiquidView + _liquid_classes: LiquidClassView _tips: TipView + _wells: WellView _geometry: GeometryView _motion: MotionView + _files: FileView _config: Config @property @@ -95,11 +104,21 @@ def liquid(self) -> LiquidView: """Get state view selectors for liquid state.""" return self._liquid + @property + def liquid_classes(self) -> LiquidClassView: + """Get state view selectors for liquid class state.""" + return self._liquid_classes + @property def tips(self) -> TipView: """Get state view selectors for tip state.""" return self._tips + @property + def wells(self) -> WellView: + """Get state view selectors for well state.""" + return self._wells + @property def geometry(self) -> GeometryView: """Get state view selectors for derived geometry state.""" @@ -110,6 +129,11 @@ def motion(self) -> MotionView: """Get state view selectors for derived motion state.""" return self._motion + @property + def files(self) -> FileView: + """Get state view selectors for engine create file state.""" + return self._files + @property def config(self) -> Config: """Get ProtocolEngine configuration.""" @@ -129,7 +153,15 @@ def get_summary(self) -> StateSummary: completedAt=self._state.commands.run_completed_at, startedAt=self._state.commands.run_started_at, liquids=self._liquid.get_all(), + wells=self._wells.get_all(), hasEverEnteredErrorRecovery=self._commands.get_has_entered_recovery_mode(), + files=self._state.files.file_ids, + liquidClasses=[ + LiquidClassRecordWithId( + liquidClassId=liquid_class_id, **dict(liquid_class_record) + ) + for liquid_class_id, liquid_class_record in self._liquid_classes.get_all().items() + ], ) @@ -195,7 +227,10 @@ def __init__( module_calibration_offsets=module_calibration_offsets, ) self._liquid_store = LiquidStore() + self._liquid_class_store = LiquidClassStore() self._tip_store = TipStore() + self._well_store = WellStore() + self._file_store = FileStore() self._substores: List[HandlesActions] = [ self._command_store, @@ -204,7 +239,10 @@ def __init__( self._labware_store, self._module_store, self._liquid_store, + self._liquid_class_store, self._tip_store, + self._well_store, + self._file_store, ] self._config = config self._change_notifier = change_notifier or ChangeNotifier() @@ -320,7 +358,10 @@ def _get_next_state(self) -> State: pipettes=self._pipette_store.state, modules=self._module_store.state, liquids=self._liquid_store.state, + liquid_classes=self._liquid_class_store.state, tips=self._tip_store.state, + wells=self._well_store.state, + files=self._file_store.state, ) def _initialize_state(self) -> None: @@ -335,12 +376,16 @@ def _initialize_state(self) -> None: self._pipettes = PipetteView(state.pipettes) self._modules = ModuleView(state.modules) self._liquid = LiquidView(state.liquids) + self._liquid_classes = LiquidClassView(state.liquid_classes) self._tips = TipView(state.tips) + self._wells = WellView(state.wells) + self._files = FileView(state.files) # Derived states self._geometry = GeometryView( config=self._config, labware_view=self._labware, + well_view=self._wells, module_view=self._modules, pipette_view=self._pipettes, addressable_area_view=self._addressable_areas, @@ -364,7 +409,9 @@ def _update_state_views(self) -> None: self._pipettes._state = next_state.pipettes self._modules._state = next_state.modules self._liquid._state = next_state.liquids + self._liquid_classes._state = next_state.liquid_classes self._tips._state = next_state.tips + self._wells._state = next_state.wells self._change_notifier.notify() if self._notify_robot_server is not None: self._notify_robot_server() diff --git a/api/src/opentrons/protocol_engine/state/state_summary.py b/api/src/opentrons/protocol_engine/state/state_summary.py index 7e6e003aaa8..d6b18613071 100644 --- a/api/src/opentrons/protocol_engine/state/state_summary.py +++ b/api/src/opentrons/protocol_engine/state/state_summary.py @@ -11,6 +11,8 @@ LoadedModule, LoadedPipette, Liquid, + LiquidClassRecordWithId, + WellInfoSummary, ) @@ -29,3 +31,6 @@ class StateSummary(BaseModel): startedAt: Optional[datetime] completedAt: Optional[datetime] liquids: List[Liquid] = Field(default_factory=list) + wells: List[WellInfoSummary] = Field(default_factory=list) + files: List[str] = Field(default_factory=list) + liquidClasses: List[LiquidClassRecordWithId] = Field(default_factory=list) diff --git a/api/src/opentrons/protocol_engine/state/tips.py b/api/src/opentrons/protocol_engine/state/tips.py index 9911b1f85b3..214e2a9bc07 100644 --- a/api/src/opentrons/protocol_engine/state/tips.py +++ b/api/src/opentrons/protocol_engine/state/tips.py @@ -1,29 +1,15 @@ """Tip state tracking.""" + from dataclasses import dataclass from enum import Enum from typing import Dict, Optional, List, Union -from .abstract_store import HasState, HandlesActions -from ..actions import ( - Action, - SucceedCommandAction, - FailCommandAction, - ResetTipsAction, -) -from ..commands import ( - Command, - LoadLabwareResult, - PickUpTip, - PickUpTipResult, - DropTipResult, - DropTipInPlaceResult, - unsafe, -) -from ..commands.configuring_common import ( - PipetteConfigUpdateResultMixin, - PipetteNozzleLayoutResultMixin, -) -from ..error_recovery_policy import ErrorRecoveryType +from opentrons.types import NozzleMapInterface +from opentrons.protocol_engine.state import update_types + +from ._abstract_store import HasState, HandlesActions +from ._well_math import wells_covered_dense +from ..actions import Action, ResetTipsAction, get_state_updates from opentrons.hardware_control.nozzle_manager import NozzleMap @@ -38,16 +24,26 @@ class TipRackWellState(Enum): TipRackStateByWellName = Dict[str, TipRackWellState] +# todo(mm, 2024-10-10): This info is duplicated between here and PipetteState because +# TipStore is using it to compute which tips a PickUpTip removes from the tip rack, +# given the pipette's current nozzle map. We could avoid this duplication by moving the +# computation to TipView, calling it from PickUpTipImplementation, and passing the +# precomputed list of wells to TipStore. +@dataclass +class _PipetteInfo: + channels: int + active_channels: int + nozzle_map: NozzleMap + + @dataclass class TipState: """State of all tips.""" tips_by_labware_id: Dict[str, TipRackStateByWellName] column_by_labware_id: Dict[str, List[List[str]]] - channels_by_pipette_id: Dict[str, int] - length_by_pipette_id: Dict[str, float] - active_channels_by_pipette_id: Dict[str, int] - nozzle_map_by_pipette_id: Dict[str, NozzleMap] + + pipette_info_by_pipette_id: Dict[str, _PipetteInfo] class TipStore(HasState[TipState], HandlesActions): @@ -60,40 +56,15 @@ def __init__(self) -> None: self._state = TipState( tips_by_labware_id={}, column_by_labware_id={}, - channels_by_pipette_id={}, - length_by_pipette_id={}, - active_channels_by_pipette_id={}, - nozzle_map_by_pipette_id={}, + pipette_info_by_pipette_id={}, ) def handle_action(self, action: Action) -> None: """Modify state in reaction to an action.""" - if isinstance(action, SucceedCommandAction): - if isinstance(action.private_result, PipetteConfigUpdateResultMixin): - pipette_id = action.private_result.pipette_id - config = action.private_result.config - self._state.channels_by_pipette_id[pipette_id] = config.channels - self._state.active_channels_by_pipette_id[pipette_id] = config.channels - self._state.nozzle_map_by_pipette_id[pipette_id] = config.nozzle_map - self._handle_succeeded_command(action.command) - - if isinstance(action.private_result, PipetteNozzleLayoutResultMixin): - pipette_id = action.private_result.pipette_id - nozzle_map = action.private_result.nozzle_map - if nozzle_map: - self._state.active_channels_by_pipette_id[ - pipette_id - ] = nozzle_map.tip_count - self._state.nozzle_map_by_pipette_id[pipette_id] = nozzle_map - else: - self._state.active_channels_by_pipette_id[ - pipette_id - ] = self._state.channels_by_pipette_id[pipette_id] + for state_update in get_state_updates(action): + self._handle_state_update(state_update) - elif isinstance(action, FailCommandAction): - self._handle_failed_command(action) - - elif isinstance(action, ResetTipsAction): + if isinstance(action, ResetTipsAction): labware_id = action.labware_id for well_name in self._state.tips_by_labware_id[labware_id].keys(): @@ -101,104 +72,54 @@ def handle_action(self, action: Action) -> None: well_name ] = TipRackWellState.CLEAN - def _handle_succeeded_command(self, command: Command) -> None: - if ( - isinstance(command.result, LoadLabwareResult) - and command.result.definition.parameters.isTiprack - ): - labware_id = command.result.labwareId - definition = command.result.definition - self._state.tips_by_labware_id[labware_id] = { - well_name: TipRackWellState.CLEAN - for column in definition.ordering - for well_name in column - } - self._state.column_by_labware_id[labware_id] = [ - column for column in definition.ordering - ] - - elif isinstance(command.result, PickUpTipResult): - labware_id = command.params.labwareId - well_name = command.params.wellName - pipette_id = command.params.pipetteId - length = command.result.tipLength - self._set_used_tips( - pipette_id=pipette_id, well_name=well_name, labware_id=labware_id + def _handle_state_update(self, state_update: update_types.StateUpdate) -> None: + if state_update.pipette_config != update_types.NO_CHANGE: + self._state.pipette_info_by_pipette_id[ + state_update.pipette_config.pipette_id + ] = _PipetteInfo( + channels=state_update.pipette_config.config.channels, + active_channels=state_update.pipette_config.config.channels, + nozzle_map=state_update.pipette_config.config.nozzle_map, ) - self._state.length_by_pipette_id[pipette_id] = length - - elif isinstance( - command.result, - (DropTipResult, DropTipInPlaceResult, unsafe.UnsafeDropTipInPlaceResult), - ): - pipette_id = command.params.pipetteId - self._state.length_by_pipette_id.pop(pipette_id, None) - def _handle_failed_command( - self, - action: FailCommandAction, - ) -> None: - # If a pickUpTip command fails recoverably, mark the tips as used. This way, - # when the protocol is resumed and the Python Protocol API calls - # `get_next_tip()`, we'll move on to other tips as expected. - # - # We don't attempt this for nonrecoverable errors because maybe the failure - # was due to a bad labware ID or well name. - if ( - isinstance(action.running_command, PickUpTip) - and action.type != ErrorRecoveryType.FAIL_RUN - ): + if state_update.tips_used != update_types.NO_CHANGE: self._set_used_tips( - pipette_id=action.running_command.params.pipetteId, - labware_id=action.running_command.params.labwareId, - well_name=action.running_command.params.wellName, + pipette_id=state_update.tips_used.pipette_id, + labware_id=state_update.tips_used.labware_id, + well_name=state_update.tips_used.well_name, ) - # Note: We're logically removing the tip from the tip rack, - # but we're not logically updating the pipette to have that tip on it. - def _set_used_tips( # noqa: C901 - self, pipette_id: str, well_name: str, labware_id: str - ) -> None: + if state_update.pipette_nozzle_map != update_types.NO_CHANGE: + pipette_info = self._state.pipette_info_by_pipette_id[ + state_update.pipette_nozzle_map.pipette_id + ] + pipette_info.active_channels = ( + state_update.pipette_nozzle_map.nozzle_map.tip_count + ) + pipette_info.nozzle_map = state_update.pipette_nozzle_map.nozzle_map + + if state_update.loaded_labware != update_types.NO_CHANGE: + labware_id = state_update.loaded_labware.labware_id + definition = state_update.loaded_labware.definition + if definition.parameters.isTiprack: + self._state.tips_by_labware_id[labware_id] = { + well_name: TipRackWellState.CLEAN + for column in definition.ordering + for well_name in column + } + self._state.column_by_labware_id[labware_id] = [ + column for column in definition.ordering + ] + + def _set_used_tips(self, pipette_id: str, well_name: str, labware_id: str) -> None: columns = self._state.column_by_labware_id.get(labware_id, []) wells = self._state.tips_by_labware_id.get(labware_id, {}) - nozzle_map = self._state.nozzle_map_by_pipette_id[pipette_id] - - # TODO (cb, 02-28-2024): Transition from using partial nozzle map to full instrument map for the set used logic - num_nozzle_cols = len(nozzle_map.columns) - num_nozzle_rows = len(nozzle_map.rows) - - critical_column = 0 - critical_row = 0 - for column in columns: - if well_name in column: - critical_row = column.index(well_name) - critical_column = columns.index(column) - - for i in range(num_nozzle_cols): - for j in range(num_nozzle_rows): - if nozzle_map.starting_nozzle == "A1": - if (critical_column + i < len(columns)) and ( - critical_row + j < len(columns[critical_column]) - ): - well = columns[critical_column + i][critical_row + j] - wells[well] = TipRackWellState.USED - elif nozzle_map.starting_nozzle == "A12": - if (critical_column - i >= 0) and ( - critical_row + j < len(columns[critical_column]) - ): - well = columns[critical_column - i][critical_row + j] - wells[well] = TipRackWellState.USED - elif nozzle_map.starting_nozzle == "H1": - if (critical_column + i < len(columns)) and (critical_row - j >= 0): - well = columns[critical_column + i][critical_row - j] - wells[well] = TipRackWellState.USED - elif nozzle_map.starting_nozzle == "H12": - if (critical_column - i >= 0) and (critical_row - j >= 0): - well = columns[critical_column - i][critical_row - j] - wells[well] = TipRackWellState.USED + nozzle_map = self._state.pipette_info_by_pipette_id[pipette_id].nozzle_map + for well in wells_covered_dense(nozzle_map, well_name, columns): + wells[well] = TipRackWellState.USED -class TipView(HasState[TipState]): +class TipView: """Read-only tip state view.""" _state: TipState @@ -216,12 +137,13 @@ def get_next_tip( # noqa: C901 labware_id: str, num_tips: int, starting_tip_name: Optional[str], - nozzle_map: Optional[NozzleMap], + nozzle_map: Optional[NozzleMapInterface], ) -> Optional[str]: """Get the next available clean tip. Does not support use of a starting tip if the pipette used is in a partial configuration.""" wells = self._state.tips_by_labware_id.get(labware_id, {}) columns = self._state.column_by_labware_id.get(labware_id, []) + # TODO(sf): I'm pretty sure this can be replaced with wells_covered_96 but I'm not quite sure how def _identify_tip_cluster( active_columns: int, active_rows: int, @@ -229,7 +151,7 @@ def _identify_tip_cluster( critical_row: int, entry_well: str, ) -> Optional[List[str]]: - tip_cluster = [] + tip_cluster: list[str] = [] for i in range(active_columns): if entry_well == "A1" or entry_well == "H1": if critical_column - i >= 0: @@ -272,20 +194,17 @@ def _validate_tip_cluster( return None else: # In the case of an 8ch pipette where a column has mixed state tips we may simply progress to the next column in our search - if ( - nozzle_map is not None - and len(nozzle_map.full_instrument_map_store) == 8 - ): + if nozzle_map is not None and nozzle_map.physical_nozzle_count == 8: return None # In the case of a 96ch we can attempt to index in by singular rows and columns assuming that indexed direction is safe # The tip cluster list is ordered: Each row from a column in order by columns - tip_cluster_final_column = [] + tip_cluster_final_column: list[str] = [] for i in range(active_rows): tip_cluster_final_column.append( tip_cluster[((active_columns * active_rows) - 1) - i] ) - tip_cluster_final_row = [] + tip_cluster_final_row: list[str] = [] for i in range(active_columns): tip_cluster_final_row.append( tip_cluster[(active_rows - 1) + (i * active_rows)] @@ -405,7 +324,7 @@ def _cluster_search_H12(active_columns: int, active_rows: int) -> Optional[str]: return None if starting_tip_name is None and nozzle_map is not None and columns: - num_channels = len(nozzle_map.full_instrument_map_store) + num_channels = nozzle_map.physical_nozzle_count num_nozzle_cols = len(nozzle_map.columns) num_nozzle_rows = len(nozzle_map.rows) # Each pipette's cluster search is determined by the point of entry for a given pipette/configuration: @@ -476,15 +395,22 @@ def _cluster_search_H12(active_columns: int, active_rows: int) -> Optional[str]: def get_pipette_channels(self, pipette_id: str) -> int: """Return the given pipette's number of channels.""" - return self._state.channels_by_pipette_id[pipette_id] + return self._state.pipette_info_by_pipette_id[pipette_id].channels def get_pipette_active_channels(self, pipette_id: str) -> int: """Get the number of channels being used in the given pipette's configuration.""" - return self._state.active_channels_by_pipette_id[pipette_id] + return self._state.pipette_info_by_pipette_id[pipette_id].active_channels def get_pipette_nozzle_map(self, pipette_id: str) -> NozzleMap: """Get the current nozzle map the given pipette's configuration.""" - return self._state.nozzle_map_by_pipette_id[pipette_id] + return self._state.pipette_info_by_pipette_id[pipette_id].nozzle_map + + def get_pipette_nozzle_maps(self) -> Dict[str, NozzleMap]: + """Get current nozzle maps keyed by pipette id.""" + return { + pipette_id: pipette_info.nozzle_map + for pipette_id, pipette_info in self._state.pipette_info_by_pipette_id.items() + } def has_clean_tip(self, labware_id: str, well_name: str) -> bool: """Get whether a well in a labware has a clean tip. @@ -502,10 +428,6 @@ def has_clean_tip(self, labware_id: str, well_name: str) -> bool: return well_state == TipRackWellState.CLEAN - def get_tip_length(self, pipette_id: str) -> float: - """Return the given pipette's tip length.""" - return self._state.length_by_pipette_id.get(pipette_id, 0) - def _drop_wells_before_starting_tip( wells: TipRackStateByWellName, starting_tip_name: str diff --git a/api/src/opentrons/protocol_engine/state/update_types.py b/api/src/opentrons/protocol_engine/state/update_types.py new file mode 100644 index 00000000000..1e81881a2b4 --- /dev/null +++ b/api/src/opentrons/protocol_engine/state/update_types.py @@ -0,0 +1,569 @@ +"""Structures to represent changes that commands want to make to engine state.""" + +import dataclasses +import enum +import typing +from typing_extensions import Self +from datetime import datetime + +from opentrons.hardware_control.nozzle_manager import NozzleMap +from opentrons.protocol_engine.resources import pipette_data_provider +from opentrons.protocol_engine.types import ( + DeckPoint, + LabwareLocation, + TipGeometry, + AspiratedFluid, + LiquidClassRecord, +) +from opentrons.types import MountType +from opentrons_shared_data.labware.labware_definition import LabwareDefinition +from opentrons_shared_data.pipette.types import PipetteNameType + + +class _NoChangeEnum(enum.Enum): + NO_CHANGE = enum.auto() + + +NO_CHANGE: typing.Final = _NoChangeEnum.NO_CHANGE +"""A sentinel value to indicate that a value shouldn't be changed. + +Useful when `None` is semantically unclear or already has some other meaning. +""" + + +NoChangeType: typing.TypeAlias = typing.Literal[_NoChangeEnum.NO_CHANGE] +"""The type of `NO_CHANGE`, as `NoneType` is to `None`. + +Unfortunately, mypy doesn't let us write `Literal[NO_CHANGE]`. Use this instead. +""" + + +class _ClearEnum(enum.Enum): + CLEAR = enum.auto() + + +CLEAR: typing.Final = _ClearEnum.CLEAR +"""A sentinel value to indicate that a value should be cleared. + +Useful when `None` is semantically unclear or has some other meaning. +""" + + +ClearType: typing.TypeAlias = typing.Literal[_ClearEnum.CLEAR] +"""The type of `CLEAR`, as `NoneType` is to `None`. + +Unfortunately, mypy doesn't let us write `Literal[CLEAR]`. Use this instead. +""" + + +@dataclasses.dataclass(frozen=True) +class Well: + """Designates a well in a labware.""" + + labware_id: str + well_name: str + + +@dataclasses.dataclass(frozen=True) +class AddressableArea: + """Designates an addressable area.""" + + addressable_area_name: str + + +@dataclasses.dataclass +class PipetteLocationUpdate: + """An update to a pipette's location.""" + + pipette_id: str + """The ID of the already-loaded pipette.""" + + new_location: Well | AddressableArea | None | NoChangeType + """The pipette's new logical location. + + Note: `new_location=None` means "change the location to `None` (unknown)", + not "do not change the location". + """ + + new_deck_point: DeckPoint | NoChangeType + + +@dataclasses.dataclass +class LabwareLocationUpdate: + """An update to a labware's location.""" + + labware_id: str + """The ID of the already-loaded labware.""" + + new_location: LabwareLocation + """The labware's new location.""" + + offset_id: str | None + """The ID of the labware's new offset, for its new location.""" + + +@dataclasses.dataclass +class LoadedLabwareUpdate: + """An update that loads a new labware.""" + + labware_id: str + """The unique ID of the new labware.""" + + new_location: LabwareLocation + """The labware's initial location.""" + + offset_id: str | None + """The ID of the labware's offset.""" + + display_name: str | None + + definition: LabwareDefinition + + +@dataclasses.dataclass +class LoadPipetteUpdate: + """An update that loads a new pipette. + + NOTE: Currently, if this is provided, a PipetteConfigUpdate must always be + provided alongside it to fully initialize everything. + """ + + pipette_id: str + """The unique ID of the new pipette.""" + + pipette_name: PipetteNameType + mount: MountType + liquid_presence_detection: bool | None + + +@dataclasses.dataclass +class PipetteConfigUpdate: + """An update to a pipette's config.""" + + pipette_id: str + """The ID of the already-loaded pipette.""" + + # todo(mm, 2024-10-14): Does serial_number belong in LoadPipetteUpdate? + serial_number: str + + config: pipette_data_provider.LoadedStaticPipetteData + + +@dataclasses.dataclass +class PipetteNozzleMapUpdate: + """Update pipette nozzle map.""" + + pipette_id: str + nozzle_map: NozzleMap + + +@dataclasses.dataclass +class PipetteTipStateUpdate: + """Update pipette tip state.""" + + pipette_id: str + tip_geometry: TipGeometry | None + + +@dataclasses.dataclass +class TipsUsedUpdate: + """Represents an update that marks tips in a tip rack as used.""" + + pipette_id: str + """The pipette that did the tip pickup.""" + + labware_id: str + + well_name: str + """The well that the pipette's primary nozzle targeted. + + Wells in addition to this one will also be marked as used, depending on the + pipette's nozzle layout. + """ + + +@dataclasses.dataclass +class LiquidLoadedUpdate: + """An update from loading a liquid.""" + + labware_id: str + volumes: typing.Dict[str, float] + last_loaded: datetime + + +@dataclasses.dataclass +class LiquidProbedUpdate: + """An update from probing a liquid.""" + + labware_id: str + well_name: str + last_probed: datetime + height: float | ClearType + volume: float | ClearType + + +@dataclasses.dataclass +class LiquidOperatedUpdate: + """An update from operating a liquid.""" + + labware_id: str + well_names: list[str] + volume_added: float | ClearType + + +@dataclasses.dataclass +class PipetteAspiratedFluidUpdate: + """Represents the pipette aspirating something. Might be air or liquid from a well.""" + + pipette_id: str + fluid: AspiratedFluid + type: typing.Literal["aspirated"] = "aspirated" + + +@dataclasses.dataclass +class PipetteEjectedFluidUpdate: + """Represents the pipette pushing something out. Might be air or liquid.""" + + pipette_id: str + volume: float + type: typing.Literal["ejected"] = "ejected" + + +@dataclasses.dataclass +class PipetteUnknownFluidUpdate: + """Represents the amount of fluid in the pipette becoming unknown.""" + + pipette_id: str + type: typing.Literal["unknown"] = "unknown" + + +@dataclasses.dataclass +class PipetteEmptyFluidUpdate: + """Sets the pipette to be valid and empty.""" + + pipette_id: str + type: typing.Literal["empty"] = "empty" + + +@dataclasses.dataclass +class AbsorbanceReaderLidUpdate: + """An update to an absorbance reader's lid location.""" + + module_id: str + is_lid_on: bool + + +@dataclasses.dataclass +class LiquidClassLoadedUpdate: + """The state update from loading a liquid class.""" + + liquid_class_id: str + liquid_class_record: LiquidClassRecord + + +@dataclasses.dataclass +class FilesAddedUpdate: + """An update that adds a new data file.""" + + file_ids: list[str] + + +@dataclasses.dataclass +class StateUpdate: + """Represents an update to perform on engine state.""" + + pipette_location: PipetteLocationUpdate | NoChangeType | ClearType = NO_CHANGE + + loaded_pipette: LoadPipetteUpdate | NoChangeType = NO_CHANGE + + pipette_config: PipetteConfigUpdate | NoChangeType = NO_CHANGE + + pipette_nozzle_map: PipetteNozzleMapUpdate | NoChangeType = NO_CHANGE + + pipette_tip_state: PipetteTipStateUpdate | NoChangeType = NO_CHANGE + + pipette_aspirated_fluid: ( + PipetteAspiratedFluidUpdate + | PipetteEjectedFluidUpdate + | PipetteUnknownFluidUpdate + | PipetteEmptyFluidUpdate + | NoChangeType + ) = NO_CHANGE + + labware_location: LabwareLocationUpdate | NoChangeType = NO_CHANGE + + loaded_labware: LoadedLabwareUpdate | NoChangeType = NO_CHANGE + + tips_used: TipsUsedUpdate | NoChangeType = NO_CHANGE + + liquid_loaded: LiquidLoadedUpdate | NoChangeType = NO_CHANGE + + liquid_probed: LiquidProbedUpdate | NoChangeType = NO_CHANGE + + liquid_operated: LiquidOperatedUpdate | NoChangeType = NO_CHANGE + + absorbance_reader_lid: AbsorbanceReaderLidUpdate | NoChangeType = NO_CHANGE + + liquid_class_loaded: LiquidClassLoadedUpdate | NoChangeType = NO_CHANGE + + files_added: FilesAddedUpdate | NoChangeType = NO_CHANGE + + def append(self, other: Self) -> Self: + """Apply another `StateUpdate` "on top of" this one. + + This object is mutated in-place, taking values from `other`. + If an attribute in `other` is `NO_CHANGE`, the value in this object is kept. + """ + fields = dataclasses.fields(other) + for field in fields: + other_value = other.__dict__[field.name] + if other_value != NO_CHANGE: + self.__dict__[field.name] = other_value + return self + + @classmethod + def reduce(cls: typing.Type[Self], *args: Self) -> Self: + """Fuse multiple state updates into a single one. + + State updates that are later in the parameter list are preferred to those that are earlier; + NO_CHANGE is ignored. + """ + accumulator = cls() + for arg in args: + accumulator.append(arg) + return accumulator + + # These convenience functions let the caller avoid the boilerplate of constructing a + # complicated dataclass tree. + @typing.overload + def set_pipette_location( + self: Self, *, pipette_id: str, new_deck_point: DeckPoint + ) -> Self: + """Schedule a pipette's coordinates to be changed while preserving its logical location.""" + + @typing.overload + def set_pipette_location( + self: Self, + *, + pipette_id: str, + new_labware_id: str, + new_well_name: str, + new_deck_point: DeckPoint, + ) -> Self: + """Schedule a pipette's location to be set to a well.""" + + @typing.overload + def set_pipette_location( + self: Self, + *, + pipette_id: str, + new_addressable_area_name: str, + new_deck_point: DeckPoint, + ) -> Self: + """Schedule a pipette's location to be set to an addressable area.""" + pass + + def set_pipette_location( # noqa: D102 + self: Self, + *, + pipette_id: str, + new_labware_id: str | NoChangeType = NO_CHANGE, + new_well_name: str | NoChangeType = NO_CHANGE, + new_addressable_area_name: str | NoChangeType = NO_CHANGE, + new_deck_point: DeckPoint, + ) -> Self: + if new_addressable_area_name != NO_CHANGE: + self.pipette_location = PipetteLocationUpdate( + pipette_id=pipette_id, + new_location=AddressableArea( + addressable_area_name=new_addressable_area_name + ), + new_deck_point=new_deck_point, + ) + elif new_labware_id == NO_CHANGE or new_well_name == NO_CHANGE: + self.pipette_location = PipetteLocationUpdate( + pipette_id=pipette_id, + new_location=NO_CHANGE, + new_deck_point=new_deck_point, + ) + else: + self.pipette_location = PipetteLocationUpdate( + pipette_id=pipette_id, + new_location=Well(labware_id=new_labware_id, well_name=new_well_name), + new_deck_point=new_deck_point, + ) + return self + + def clear_all_pipette_locations(self) -> Self: + """Mark all pipettes as having an unknown location.""" + self.pipette_location = CLEAR + return self + + def set_labware_location( + self: Self, + *, + labware_id: str, + new_location: LabwareLocation, + new_offset_id: str | None, + ) -> Self: + """Set a labware's location. See `LabwareLocationUpdate`.""" + self.labware_location = LabwareLocationUpdate( + labware_id=labware_id, + new_location=new_location, + offset_id=new_offset_id, + ) + return self + + def set_loaded_labware( + self: Self, + definition: LabwareDefinition, + labware_id: str, + offset_id: str | None, + display_name: str | None, + location: LabwareLocation, + ) -> Self: + """Add a new labware to state. See `LoadedLabwareUpdate`.""" + self.loaded_labware = LoadedLabwareUpdate( + definition=definition, + labware_id=labware_id, + offset_id=offset_id, + new_location=location, + display_name=display_name, + ) + return self + + def set_load_pipette( + self: Self, + pipette_id: str, + pipette_name: PipetteNameType, + mount: MountType, + liquid_presence_detection: bool | None, + ) -> Self: + """Add a new pipette to state. See `LoadPipetteUpdate`.""" + self.loaded_pipette = LoadPipetteUpdate( + pipette_id=pipette_id, + pipette_name=pipette_name, + mount=mount, + liquid_presence_detection=liquid_presence_detection, + ) + return self + + def update_pipette_config( + self: Self, + pipette_id: str, + config: pipette_data_provider.LoadedStaticPipetteData, + serial_number: str, + ) -> Self: + """Update a pipette's config. See `PipetteConfigUpdate`.""" + self.pipette_config = PipetteConfigUpdate( + pipette_id=pipette_id, config=config, serial_number=serial_number + ) + return self + + def update_pipette_nozzle( + self: Self, pipette_id: str, nozzle_map: NozzleMap + ) -> Self: + """Update a pipette's nozzle map. See `PipetteNozzleMapUpdate`.""" + self.pipette_nozzle_map = PipetteNozzleMapUpdate( + pipette_id=pipette_id, nozzle_map=nozzle_map + ) + return self + + def update_pipette_tip_state( + self: Self, pipette_id: str, tip_geometry: TipGeometry | None + ) -> Self: + """Update a pipette's tip state. See `PipetteTipStateUpdate`.""" + self.pipette_tip_state = PipetteTipStateUpdate( + pipette_id=pipette_id, tip_geometry=tip_geometry + ) + return self + + def mark_tips_as_used( + self: Self, pipette_id: str, labware_id: str, well_name: str + ) -> Self: + """Mark tips in a tip rack as used. See `TipsUsedUpdate`.""" + self.tips_used = TipsUsedUpdate( + pipette_id=pipette_id, labware_id=labware_id, well_name=well_name + ) + return self + + def set_liquid_loaded( + self: Self, + labware_id: str, + volumes: typing.Dict[str, float], + last_loaded: datetime, + ) -> Self: + """Add liquid volumes to well state. See `LoadLiquidUpdate`.""" + self.liquid_loaded = LiquidLoadedUpdate( + labware_id=labware_id, + volumes=volumes, + last_loaded=last_loaded, + ) + return self + + def set_liquid_probed( + self: Self, + labware_id: str, + well_name: str, + last_probed: datetime, + height: float | ClearType, + volume: float | ClearType, + ) -> Self: + """Add a liquid height and volume to well state. See `ProbeLiquidUpdate`.""" + self.liquid_probed = LiquidProbedUpdate( + labware_id=labware_id, + well_name=well_name, + height=height, + volume=volume, + last_probed=last_probed, + ) + return self + + def set_liquid_operated( + self: Self, + labware_id: str, + well_names: list[str], + volume_added: float | ClearType, + ) -> Self: + """Update liquid volumes in well state. See `OperateLiquidUpdate`.""" + self.liquid_operated = LiquidOperatedUpdate( + labware_id=labware_id, + well_names=well_names, + volume_added=volume_added, + ) + return self + + def set_fluid_aspirated(self: Self, pipette_id: str, fluid: AspiratedFluid) -> Self: + """Update record of fluid held inside a pipette. See `PipetteAspiratedFluidUpdate`.""" + self.pipette_aspirated_fluid = PipetteAspiratedFluidUpdate( + type="aspirated", pipette_id=pipette_id, fluid=fluid + ) + return self + + def set_fluid_ejected(self: Self, pipette_id: str, volume: float) -> Self: + """Update record of fluid held inside a pipette. See `PipetteEjectedFluidUpdate`.""" + self.pipette_aspirated_fluid = PipetteEjectedFluidUpdate( + type="ejected", pipette_id=pipette_id, volume=volume + ) + return self + + def set_fluid_unknown(self: Self, pipette_id: str) -> Self: + """Update record of fluid held inside a pipette. See `PipetteUnknownFluidUpdate`.""" + self.pipette_aspirated_fluid = PipetteUnknownFluidUpdate( + type="unknown", pipette_id=pipette_id + ) + return self + + def set_fluid_empty(self: Self, pipette_id: str) -> Self: + """Update record fo fluid held inside a pipette. See `PipetteEmptyFluidUpdate`.""" + self.pipette_aspirated_fluid = PipetteEmptyFluidUpdate( + type="empty", pipette_id=pipette_id + ) + return self + + def set_absorbance_reader_lid(self: Self, module_id: str, is_lid_on: bool) -> Self: + """Update an absorbance reader's lid location. See `AbsorbanceReaderLidUpdate`.""" + self.absorbance_reader_lid = AbsorbanceReaderLidUpdate( + module_id=module_id, is_lid_on=is_lid_on + ) + return self diff --git a/api/src/opentrons/protocol_engine/state/wells.py b/api/src/opentrons/protocol_engine/state/wells.py new file mode 100644 index 00000000000..fdcb8322094 --- /dev/null +++ b/api/src/opentrons/protocol_engine/state/wells.py @@ -0,0 +1,244 @@ +"""Basic well data state and store.""" + +from dataclasses import dataclass +from typing import Dict, List, Union, Iterator, Optional, Tuple, overload, TypeVar + +from opentrons.protocol_engine.types import ( + ProbedHeightInfo, + ProbedVolumeInfo, + LoadedVolumeInfo, + WellInfoSummary, + WellLiquidInfo, +) + +from . import update_types +from ._abstract_store import HasState, HandlesActions +from ..actions import Action +from ..actions.get_state_update import get_state_updates + + +LabwareId = str +WellName = str + + +@dataclass +class WellState: + """State of all wells.""" + + loaded_volumes: Dict[LabwareId, Dict[WellName, LoadedVolumeInfo]] + probed_heights: Dict[LabwareId, Dict[WellName, ProbedHeightInfo]] + probed_volumes: Dict[LabwareId, Dict[WellName, ProbedVolumeInfo]] + + +class WellStore(HasState[WellState], HandlesActions): + """Well state container.""" + + _state: WellState + + def __init__(self) -> None: + """Initialize a well store and its state.""" + self._state = WellState(loaded_volumes={}, probed_heights={}, probed_volumes={}) + + def handle_action(self, action: Action) -> None: + """Modify state in reaction to an action.""" + for state_update in get_state_updates(action): + if state_update.liquid_loaded != update_types.NO_CHANGE: + self._handle_liquid_loaded_update(state_update.liquid_loaded) + if state_update.liquid_probed != update_types.NO_CHANGE: + self._handle_liquid_probed_update(state_update.liquid_probed) + if state_update.liquid_operated != update_types.NO_CHANGE: + self._handle_liquid_operated_update(state_update.liquid_operated) + + def _handle_liquid_loaded_update( + self, state_update: update_types.LiquidLoadedUpdate + ) -> None: + labware_id = state_update.labware_id + if labware_id not in self._state.loaded_volumes: + self._state.loaded_volumes[labware_id] = {} + for well, volume in state_update.volumes.items(): + self._state.loaded_volumes[labware_id][well] = LoadedVolumeInfo( + volume=_none_from_clear(volume), + last_loaded=state_update.last_loaded, + operations_since_load=0, + ) + + def _handle_liquid_probed_update( + self, state_update: update_types.LiquidProbedUpdate + ) -> None: + labware_id = state_update.labware_id + well_name = state_update.well_name + if labware_id not in self._state.probed_heights: + self._state.probed_heights[labware_id] = {} + if labware_id not in self._state.probed_volumes: + self._state.probed_volumes[labware_id] = {} + self._state.probed_heights[labware_id][well_name] = ProbedHeightInfo( + height=_none_from_clear(state_update.height), + last_probed=state_update.last_probed, + ) + self._state.probed_volumes[labware_id][well_name] = ProbedVolumeInfo( + volume=_none_from_clear(state_update.volume), + last_probed=state_update.last_probed, + operations_since_probe=0, + ) + + def _handle_liquid_operated_update( + self, state_update: update_types.LiquidOperatedUpdate + ) -> None: + for well_name in state_update.well_names: + self._handle_well_operated( + state_update.labware_id, well_name, state_update.volume_added + ) + + def _handle_well_operated( + self, + labware_id: str, + well_name: str, + volume_added: float | update_types.ClearType, + ) -> None: + if ( + labware_id in self._state.loaded_volumes + and well_name in self._state.loaded_volumes[labware_id] + ): + if volume_added is update_types.CLEAR: + del self._state.loaded_volumes[labware_id][well_name] + else: + prev_loaded_vol_info = self._state.loaded_volumes[labware_id][well_name] + assert prev_loaded_vol_info.volume is not None + self._state.loaded_volumes[labware_id][well_name] = LoadedVolumeInfo( + volume=prev_loaded_vol_info.volume + volume_added, + last_loaded=prev_loaded_vol_info.last_loaded, + operations_since_load=prev_loaded_vol_info.operations_since_load + + 1, + ) + if ( + labware_id in self._state.probed_heights + and well_name in self._state.probed_heights[labware_id] + ): + del self._state.probed_heights[labware_id][well_name] + if ( + labware_id in self._state.probed_volumes + and well_name in self._state.probed_volumes[labware_id] + ): + if volume_added is update_types.CLEAR: + del self._state.probed_volumes[labware_id][well_name] + else: + prev_probed_vol_info = self._state.probed_volumes[labware_id][well_name] + if prev_probed_vol_info.volume is None: + new_vol_info: float | None = None + else: + new_vol_info = prev_probed_vol_info.volume + volume_added + self._state.probed_volumes[labware_id][well_name] = ProbedVolumeInfo( + volume=new_vol_info, + last_probed=prev_probed_vol_info.last_probed, + operations_since_probe=prev_probed_vol_info.operations_since_probe + + 1, + ) + + +class WellView: + """Read-only well state view.""" + + _state: WellState + + def __init__(self, state: WellState) -> None: + """Initialize the computed view of well state. + + Arguments: + state: Well state dataclass used for all calculations. + """ + self._state = state + + def get_well_liquid_info(self, labware_id: str, well_name: str) -> WellLiquidInfo: + """Return all the liquid info for a well.""" + if ( + labware_id not in self._state.loaded_volumes + or well_name not in self._state.loaded_volumes[labware_id] + ): + loaded_volume_info = None + else: + loaded_volume_info = self._state.loaded_volumes[labware_id][well_name] + if ( + labware_id not in self._state.probed_heights + or well_name not in self._state.probed_heights[labware_id] + ): + probed_height_info = None + else: + probed_height_info = self._state.probed_heights[labware_id][well_name] + if ( + labware_id not in self._state.probed_volumes + or well_name not in self._state.probed_volumes[labware_id] + ): + probed_volume_info = None + else: + probed_volume_info = self._state.probed_volumes[labware_id][well_name] + return WellLiquidInfo( + loaded_volume=loaded_volume_info, + probed_height=probed_height_info, + probed_volume=probed_volume_info, + ) + + def get_all(self) -> List[WellInfoSummary]: + """Get all well liquid info summaries.""" + + def _all_well_combos() -> Iterator[Tuple[str, str, str]]: + for labware, lv_wells in self._state.loaded_volumes.items(): + for well_name in lv_wells.keys(): + yield f"{labware}{well_name}", labware, well_name + for labware, ph_wells in self._state.probed_heights.items(): + for well_name in ph_wells.keys(): + yield f"{labware}{well_name}", labware, well_name + for labware, pv_wells in self._state.probed_volumes.items(): + for well_name in pv_wells.keys(): + yield f"{labware}{well_name}", labware, well_name + + wells = { + key: (labware_id, well_name) + for key, labware_id, well_name in _all_well_combos() + } + return [ + self._summarize_well(labware_id, well_name) + for labware_id, well_name in wells.values() + ] + + def _summarize_well(self, labware_id: str, well_name: str) -> WellInfoSummary: + well_liquid_info = self.get_well_liquid_info(labware_id, well_name) + return WellInfoSummary( + labware_id=labware_id, + well_name=well_name, + loaded_volume=_volume_from_info(well_liquid_info.loaded_volume), + probed_volume=_volume_from_info(well_liquid_info.probed_volume), + probed_height=_height_from_info(well_liquid_info.probed_height), + ) + + +@overload +def _volume_from_info(info: Optional[ProbedVolumeInfo]) -> Optional[float]: + ... + + +@overload +def _volume_from_info(info: Optional[LoadedVolumeInfo]) -> Optional[float]: + ... + + +def _volume_from_info( + info: Union[ProbedVolumeInfo, LoadedVolumeInfo, None], +) -> Optional[float]: + if info is None: + return None + return info.volume + + +def _height_from_info(info: Optional[ProbedHeightInfo]) -> Optional[float]: + if info is None: + return None + return info.height + + +MaybeClear = TypeVar("MaybeClear") + + +def _none_from_clear(inval: MaybeClear | update_types.ClearType) -> MaybeClear | None: + if inval == update_types.CLEAR: + return None + return inval diff --git a/api/src/opentrons/protocol_engine/types.py b/api/src/opentrons/protocol_engine/types.py index 44eca85866b..a34178e2a00 100644 --- a/api/src/opentrons/protocol_engine/types.py +++ b/api/src/opentrons/protocol_engine/types.py @@ -37,7 +37,9 @@ from opentrons.hardware_control.modules import ( ModuleType as ModuleType, ) - +from opentrons_shared_data.liquid_classes.liquid_class_definition import ( + ByTipTypeSetting, +) from opentrons_shared_data.pipette.types import ( # noqa: F401 # convenience re-export of LabwareUri type LabwareUri as LabwareUri, @@ -202,6 +204,22 @@ class OnLabwareLocation(BaseModel): class WellOrigin(str, Enum): """Origin of WellLocation offset. + Props: + TOP: the top-center of the well + BOTTOM: the bottom-center of the well + CENTER: the middle-center of the well + MENISCUS: the meniscus-center of the well + """ + + TOP = "top" + BOTTOM = "bottom" + CENTER = "center" + MENISCUS = "meniscus" + + +class PickUpTipWellOrigin(str, Enum): + """The origin of a PickUpTipWellLocation offset. + Props: TOP: the top-center of the well BOTTOM: the bottom-center of the well @@ -244,6 +262,34 @@ class WellLocation(BaseModel): origin: WellOrigin = WellOrigin.TOP offset: WellOffset = Field(default_factory=WellOffset) + volumeOffset: float = Field( + default=0.0, + description="""A volume of liquid, in µL, to offset the z-axis offset.""", + ) + + +class LiquidHandlingWellLocation(BaseModel): + """A relative location in reference to a well's location. + + To be used with commands that handle liquids. + """ + + origin: WellOrigin = WellOrigin.TOP + offset: WellOffset = Field(default_factory=WellOffset) + volumeOffset: Union[float, Literal["operationVolume"]] = Field( + default=0.0, + description="""A volume of liquid, in µL, to offset the z-axis offset. When "operationVolume" is specified, this volume is pulled from the command volume parameter.""", + ) + + +class PickUpTipWellLocation(BaseModel): + """A relative location in reference to a well's location. + + To be used for picking up tips. + """ + + origin: PickUpTipWellOrigin = PickUpTipWellOrigin.TOP + offset: WellOffset = Field(default_factory=WellOffset) class DropTipWellLocation(BaseModel): @@ -312,6 +358,48 @@ class CurrentWell: well_name: str +class LoadedVolumeInfo(BaseModel): + """A well's liquid volume, initialized by a LoadLiquid, updated by Aspirate and Dispense.""" + + volume: Optional[float] = None + last_loaded: datetime + operations_since_load: int + + +class ProbedHeightInfo(BaseModel): + """A well's liquid height, initialized by a LiquidProbe, cleared by Aspirate and Dispense.""" + + height: Optional[float] = None + last_probed: datetime + + +class ProbedVolumeInfo(BaseModel): + """A well's liquid volume, initialized by a LiquidProbe, updated by Aspirate and Dispense.""" + + volume: Optional[float] = None + last_probed: datetime + operations_since_probe: int + + +class WellInfoSummary(BaseModel): + """Payload for a well's liquid info in StateSummary.""" + + labware_id: str + well_name: str + loaded_volume: Optional[float] = None + probed_height: Optional[float] = None + probed_volume: Optional[float] = None + + +@dataclass +class WellLiquidInfo: + """Tracked and sensed information about liquid in a well.""" + + probed_height: Optional[ProbedHeightInfo] + loaded_volume: Optional[LoadedVolumeInfo] + probed_volume: Optional[ProbedVolumeInfo] + + @dataclass(frozen=True) class CurrentAddressableArea: """The latest addressable area the robot has accessed.""" @@ -338,6 +426,21 @@ class TipGeometry: volume: float +class FluidKind(str, Enum): + """A kind of fluid that can be inside a pipette.""" + + LIQUID = "LIQUID" + AIR = "AIR" + + +@dataclass(frozen=True) +class AspiratedFluid: + """Fluid inside a pipette.""" + + kind: FluidKind + volume: float + + class MovementAxis(str, Enum): """Axis on which to issue a relative movement.""" @@ -357,6 +460,7 @@ class MotorAxis(str, Enum): RIGHT_PLUNGER = "rightPlunger" EXTENSION_Z = "extensionZ" EXTENSION_JAW = "extensionJaw" + AXIS_96_CHANNEL_CAM = "axis96ChannelCam" # TODO(mc, 2022-01-18): use opentrons_shared_data.module.types.ModuleModel @@ -728,6 +832,10 @@ def _color_is_a_valid_hex(cls, v: str) -> str: return v +EmptyLiquidId = Literal["EMPTY"] +LiquidId = str | EmptyLiquidId + + class Liquid(BaseModel): """Payload required to create a liquid.""" @@ -737,6 +845,58 @@ class Liquid(BaseModel): displayColor: Optional[HexColor] +class LiquidClassRecord(ByTipTypeSetting, frozen=True): + """LiquidClassRecord is our internal representation of an (immutable) liquid class. + + Conceptually, a liquid class record is the tuple (name, pipette, tip, transfer properties). + We consider two liquid classes to be the same if every entry in that tuple is the same; and liquid + classes are different if any entry in the tuple is different. + + This class defines the tuple via inheritance so that we can reuse the definitions from shared_data. + """ + + liquidClassName: str = Field( + ..., + description="Identifier for the liquid of this liquid class, e.g. glycerol50.", + ) + pipetteModel: str = Field( + ..., + description="Identifier for the pipette of this liquid class.", + ) + # The other fields like tiprack ID, aspirate properties, etc. are pulled in from ByTipTypeSetting. + + def __hash__(self) -> int: + """Hash function for LiquidClassRecord.""" + # Within the Protocol Engine, LiquidClassRecords are immutable, and we'd like to be able to + # look up LiquidClassRecords by value, which involves hashing. However, Pydantic does not + # generate a usable hash function if any of the subfields (like Coordinate) are not frozen. + # So we have to implement the hash function ourselves. + # Our strategy is to recursively convert this object into a list of (key, value) tuples. + def dict_to_tuple(d: dict[str, Any]) -> tuple[tuple[str, Any], ...]: + return tuple( + ( + field_name, + dict_to_tuple(value) + if isinstance(value, dict) + else tuple(value) + if isinstance(value, list) + else value, + ) + for field_name, value in d.items() + ) + + return hash(dict_to_tuple(self.dict())) + + +class LiquidClassRecordWithId(LiquidClassRecord, frozen=True): + """A LiquidClassRecord with its ID, for use in summary lists.""" + + liquidClassId: str = Field( + ..., + description="Unique identifier for this liquid class.", + ) + + class SpeedRange(NamedTuple): """Minimum and maximum allowed speeds for a shaking module.""" @@ -1122,3 +1282,6 @@ class CSVParameter(RTPBase): CSVRunTimeParamFilesType = Mapping[StrictStr, StrictStr] CSVRuntimeParamPaths = Dict[str, Path] + + +ABSMeasureMode = Literal["single", "multi"] diff --git a/api/src/opentrons/protocol_reader/file_format_validator.py b/api/src/opentrons/protocol_reader/file_format_validator.py index f13d1339041..df119ac3ffa 100644 --- a/api/src/opentrons/protocol_reader/file_format_validator.py +++ b/api/src/opentrons/protocol_reader/file_format_validator.py @@ -1,5 +1,5 @@ """File format validation interface.""" - +from __future__ import annotations from typing import Iterable @@ -29,6 +29,16 @@ class FileFormatValidationError(ProtocolFilesInvalidError): """Raised when a file does not conform to the format it's supposed to.""" + @classmethod + def _generic_json_failure( + cls, info: IdentifiedJsonMain, exc: Exception + ) -> FileFormatValidationError: + return cls( + message=f"{info.original_file.name} could not be read as a JSON protocol.", + detail={"kind": "bad-json-protocol"}, + wrapping=[PythonException(exc)], + ) + class FileFormatValidator: """File format validation interface.""" @@ -61,22 +71,80 @@ def validate_sync() -> None: await anyio.to_thread.run_sync(validate_sync) +def _handle_v8_json_protocol_validation_error( + info: IdentifiedJsonMain, pve: PydanticValidationError +) -> None: + for error in pve.errors(): + if error["loc"] == ("commandSchemaId",) and error["type"] == "type_error.enum": + # type_error.enum is for "this entry is not in this enum" and happens if you constrain a field by + # annotating it with Enum, as we now do for command schema IDs + raise FileFormatValidationError( + message=( + f"{info.original_file.name} could not be read as a JSON protocol, in part because its command schema " + "id is unknown. This protocol may have been exported from a future version of authorship software. " + "Updating your Opentrons software may help." + ), + detail={ + "kind": "bad-command-schema-id", + "command-schema-id": info.unvalidated_json.get( + "commandSchemaId", "" + ), + }, + wrapping=[PythonException(pve)], + ) from pve + if ( + error["loc"] == ("labwareDefinitionSchemaId",) + and error["type"] == "value_error.const" + ): + # value_error.const is for "this entry is not one of these const values", which is different from type_error.enum + # for I'm sure a very good reason, and happens if you constrain a field by annotating it with a Literal + raise FileFormatValidationError( + message=( + f"{info.original_file.name} could not be read as a JSON protocol, in part because its labware schema " + "id is unknown. This protocol may have been exported from a future version of authorship software. " + "Updating your Opentrons software may help." + ), + detail={ + "kind": "bad-labware-schema-id", + "labware-schema-id": info.unvalidated_json.get( + "labwareDefinitionSchemaId", "" + ), + }, + ) + if error["loc"] == ("liquidSchemaId",) and error["type"] == "value_error.const": + raise FileFormatValidationError( + message=( + f"{info.original_file.name} could not be read as a JSON protocol, in part because its liquid schema " + "id is unknown. This protocol may have been exported from a future version of authorship software. " + "Updating your Opentrons software may help." + ), + detail={ + "kind": "bad-liquid-schema-id", + "liquid-schema-id": info.unvalidated_json.get( + "liquidSchemaId", "" + ), + }, + ) + else: + raise FileFormatValidationError._generic_json_failure(info, pve) from pve + + async def _validate_json_protocol(info: IdentifiedJsonMain) -> None: def validate_sync() -> None: - try: - if info.schema_version == 8: + if info.schema_version == 8: + try: JsonProtocolV8.parse_obj(info.unvalidated_json) - elif info.schema_version == 7: - JsonProtocolV7.parse_obj(info.unvalidated_json) - elif info.schema_version == 6: - JsonProtocolV6.parse_obj(info.unvalidated_json) - else: - JsonProtocolUpToV5.parse_obj(info.unvalidated_json) - except PydanticValidationError as e: - raise FileFormatValidationError( - message=f"{info.original_file.name} could not be read as a JSON protocol.", - detail={"kind": "bad-json-protocol"}, - wrapping=[PythonException(e)], - ) from e + except PydanticValidationError as pve: + _handle_v8_json_protocol_validation_error(info, pve) + else: + try: + if info.schema_version == 7: + JsonProtocolV7.parse_obj(info.unvalidated_json) + elif info.schema_version == 6: + JsonProtocolV6.parse_obj(info.unvalidated_json) + else: + JsonProtocolUpToV5.parse_obj(info.unvalidated_json) + except PydanticValidationError as e: + raise FileFormatValidationError._generic_json_failure(info, e) from e await anyio.to_thread.run_sync(validate_sync) diff --git a/api/src/opentrons/protocol_runner/json_translator.py b/api/src/opentrons/protocol_runner/json_translator.py index e73c07b2adf..36d1904dec3 100644 --- a/api/src/opentrons/protocol_runner/json_translator.py +++ b/api/src/opentrons/protocol_runner/json_translator.py @@ -1,6 +1,6 @@ """Translation of JSON protocol commands into ProtocolEngine commands.""" -from typing import cast, List, Union -from pydantic import parse_obj_as +from typing import cast, List, Union, Iterator +from pydantic import parse_obj_as, ValidationError as PydanticValidationError from opentrons_shared_data.pipette.types import PipetteNameType from opentrons_shared_data.protocol.models import ( @@ -12,6 +12,7 @@ protocol_schema_v8, ) from opentrons_shared_data import command as command_schema +from opentrons_shared_data.errors.exceptions import InvalidProtocolData, PythonException from opentrons.types import MountType from opentrons.protocol_engine import ( @@ -258,7 +259,8 @@ def _translate_v8_commands( self, protocol: ProtocolSchemaV8 ) -> List[pe_commands.CommandCreate]: """Translate commands in json protocol schema v8, which might be of different command schemas.""" - command_schema_ref = protocol.commandSchemaId + command_schema_ref = protocol.commandSchemaId.value + # these calls will raise if the command schema version is invalid or unknown command_schema_version = command_schema.schema_version_from_ref( command_schema_ref @@ -267,7 +269,21 @@ def _translate_v8_commands( command_schema_version ) - return [_translate_simple_command(command) for command in protocol.commands] + def translate_all_commands() -> Iterator[pe_commands.CommandCreate]: + for command in protocol.commands: + try: + yield _translate_simple_command(command) + except PydanticValidationError as pve: + raise InvalidProtocolData( + message=( + "The protocol is invalid because it contains an unknown or malformed command, " + f'"{command.commandType}".' + ), + detail={"kind": "invalid-command"}, + wrapping=[PythonException(pve)], + ) + + return list(translate_all_commands()) def translate_command_annotations( self, diff --git a/api/src/opentrons/protocol_runner/legacy_command_mapper.py b/api/src/opentrons/protocol_runner/legacy_command_mapper.py index b744c03351c..27b1c7ea331 100644 --- a/api/src/opentrons/protocol_runner/legacy_command_mapper.py +++ b/api/src/opentrons/protocol_runner/legacy_command_mapper.py @@ -34,6 +34,9 @@ ModuleDataProvider, pipette_data_provider, ) +from opentrons.protocol_engine.state.update_types import ( + StateUpdate, +) from opentrons_shared_data.labware.labware_definition import LabwareDefinition from opentrons_shared_data.errors import ErrorCodes, EnumeratedError, PythonException @@ -267,7 +270,8 @@ def map_command( # noqa: C901 ) results.append( pe_actions.SucceedCommandAction( - completed_command, private_result=None + completed_command, + state_update=StateUpdate(), ) ) @@ -672,9 +676,19 @@ def _map_labware_load( # We just set this above, so we know it's not None. started_at=succeeded_command.startedAt, # type: ignore[arg-type] ) + state_update = StateUpdate() + assert succeeded_command.result is not None + state_update.set_loaded_labware( + labware_id=labware_id, + definition=succeeded_command.result.definition, + display_name=labware_load_info.labware_display_name, + offset_id=labware_load_info.offset_id, + location=location, + ) + succeed_action = pe_actions.SucceedCommandAction( command=succeeded_command, - private_result=None, + state_update=state_update, ) self._command_count["LOAD_LABWARE"] = count + 1 @@ -715,7 +729,14 @@ def _map_instrument_load( result=pe_commands.LoadPipetteResult.construct(pipetteId=pipette_id), ) serial = instrument_load_info.pipette_dict.get("pipette_id", None) or "" - pipette_config_result = pe_commands.LoadPipettePrivateResult( + state_update = StateUpdate() + state_update.set_load_pipette( + pipette_id=pipette_id, + mount=succeeded_command.params.mount, + pipette_name=succeeded_command.params.pipetteName, + liquid_presence_detection=succeeded_command.params.liquidPresenceDetection, + ) + state_update.update_pipette_config( pipette_id=pipette_id, serial_number=serial, config=pipette_data_provider.get_pipette_static_config( @@ -738,9 +759,10 @@ def _map_instrument_load( # We just set this above, so we know it's not None. started_at=succeeded_command.startedAt, # type: ignore[arg-type] ) + succeed_action = pe_actions.SucceedCommandAction( command=succeeded_command, - private_result=pipette_config_result, + state_update=state_update, ) self._command_count["LOAD_PIPETTE"] = count + 1 @@ -805,8 +827,7 @@ def _map_module_load( started_at=succeeded_command.startedAt, # type: ignore[arg-type] ) succeed_action = pe_actions.SucceedCommandAction( - command=succeeded_command, - private_result=None, + command=succeeded_command, state_update=StateUpdate() ) self._command_count["LOAD_MODULE"] = count + 1 diff --git a/api/src/opentrons/protocol_runner/legacy_context_plugin.py b/api/src/opentrons/protocol_runner/legacy_context_plugin.py index baf6ccbc716..4e23038de4f 100644 --- a/api/src/opentrons/protocol_runner/legacy_context_plugin.py +++ b/api/src/opentrons/protocol_runner/legacy_context_plugin.py @@ -1,9 +1,9 @@ """Customize the ProtocolEngine to monitor and control legacy (APIv2) protocols.""" from __future__ import annotations -from asyncio import create_task, Task +import asyncio from contextlib import ExitStack -from typing import List, Optional +from typing import Optional from opentrons.legacy_commands.types import CommandMessage as LegacyCommand from opentrons.legacy_broker import LegacyBroker @@ -12,7 +12,6 @@ from opentrons.util.broker import ReadOnlyBroker from .legacy_command_mapper import LegacyCommandMapper -from .thread_async_queue import ThreadAsyncQueue class LegacyContextPlugin(AbstractPlugin): @@ -21,59 +20,36 @@ class LegacyContextPlugin(AbstractPlugin): In the legacy ProtocolContext, protocol execution is accomplished by direct communication with the HardwareControlAPI, as opposed to an intermediate layer like the ProtocolEngine. This plugin wraps up - and hides this behavior, so the ProtocolEngine can monitor and control + and hides this behavior, so the ProtocolEngine can monitor the run of a legacy protocol without affecting the execution of the protocol commands themselves. - This plugin allows a ProtocolEngine to: - - 1. Play/pause the protocol run using the HardwareControlAPI, as was done before - the ProtocolEngine existed. - 2. Subscribe to what is being done with the legacy ProtocolContext, - and insert matching commands into ProtocolEngine state for - purely progress-tracking purposes. + This plugin allows a ProtocolEngine to subscribe to what is being done with the + legacy ProtocolContext, and insert matching commands into ProtocolEngine state for + purely progress-tracking purposes. """ def __init__( self, + engine_loop: asyncio.AbstractEventLoop, broker: LegacyBroker, equipment_broker: ReadOnlyBroker[LoadInfo], legacy_command_mapper: Optional[LegacyCommandMapper] = None, ) -> None: """Initialize the plugin with its dependencies.""" + self._engine_loop = engine_loop + self._broker = broker self._equipment_broker = equipment_broker self._legacy_command_mapper = legacy_command_mapper or LegacyCommandMapper() - # We use a non-blocking queue to communicate activity - # from the APIv2 protocol, which is running in its own thread, - # to the ProtocolEngine, which is running in the main thread's async event loop. - # - # The queue being non-blocking lets the protocol communicate its activity - # instantly *even if the event loop is currently occupied by something else.* - # Various things can accidentally occupy the event loop for too long. - # So if the protocol had to wait for the event loop to be free - # every time it reported some activity, - # it could visibly stall for a moment, making its motion jittery. - # - # TODO(mm, 2024-03-22): See if we can remove this non-blockingness now. - # It was one of several band-aids introduced in ~v5.0.0 to mitigate performance - # problems. v6.3.0 started running some Python protocols directly through - # Protocol Engine, without this plugin, and without any non-blocking queue. - # If performance is sufficient for those, that probably means the - # performance problems have been resolved in better ways elsewhere - # and we don't need this anymore. - self._actions_to_dispatch = ThreadAsyncQueue[List[pe_actions.Action]]() - self._action_dispatching_task: Optional[Task[None]] = None - self._subscription_exit_stack: Optional[ExitStack] = None def setup(self) -> None: """Set up the plugin. - * Subscribe to the APIv2 context's message brokers to be informed - of the APIv2 protocol's activity. - * Kick off a background task to inform Protocol Engine of that activity. + Subscribe to the APIv2 context's message brokers to be informed + of the APIv2 protocol's activity. """ # Subscribe to activity on the APIv2 context, # and arrange to unsubscribe when this plugin is torn down. @@ -97,24 +73,16 @@ def setup(self) -> None: # to clean up these subscriptions. self._subscription_exit_stack = exit_stack.pop_all() - # Kick off a background task to report activity to the ProtocolEngine. - self._action_dispatching_task = create_task(self._dispatch_all_actions()) - + # todo(mm, 2024-08-21): This no longer needs to be async. async def teardown(self) -> None: """Tear down the plugin, undoing the work done in `setup()`. Called by Protocol Engine. At this point, the APIv2 protocol script must have exited. """ - self._actions_to_dispatch.done_putting() - try: - if self._action_dispatching_task is not None: - await self._action_dispatching_task - self._action_dispatching_task = None - finally: - if self._subscription_exit_stack is not None: - self._subscription_exit_stack.close() - self._subscription_exit_stack = None + if self._subscription_exit_stack is not None: + self._subscription_exit_stack.close() + self._subscription_exit_stack = None def handle_action(self, action: pe_actions.Action) -> None: """React to a ProtocolEngine action.""" @@ -127,7 +95,10 @@ def _handle_legacy_command(self, command: LegacyCommand) -> None: Used as a broker callback, so this will run in the APIv2 protocol's thread. """ pe_actions = self._legacy_command_mapper.map_command(command=command) - self._actions_to_dispatch.put(pe_actions) + future = asyncio.run_coroutine_threadsafe( + self._dispatch_action_list(pe_actions), self._engine_loop + ) + future.result() def _handle_equipment_loaded(self, load_info: LoadInfo) -> None: """Handle an equipment load reported by the legacy APIv2 protocol. @@ -135,26 +106,11 @@ def _handle_equipment_loaded(self, load_info: LoadInfo) -> None: Used as a broker callback, so this will run in the APIv2 protocol's thread. """ pe_actions = self._legacy_command_mapper.map_equipment_load(load_info=load_info) - self._actions_to_dispatch.put(pe_actions) - - async def _dispatch_all_actions(self) -> None: - """Dispatch all actions to the `ProtocolEngine`. - - Exits only when `self._actions_to_dispatch` is closed - (or an unexpected exception is raised). - """ - async for action_batch in self._actions_to_dispatch.get_async_until_closed(): - # It's critical that we dispatch this batch of actions as one atomic - # sequence, without yielding to the event loop. - # Although this plugin only means to use the ProtocolEngine as a way of - # passively exposing the protocol's progress, the ProtocolEngine is still - # theoretically active, which means it's constantly watching in the - # background to execute any commands that it finds `queued`. - # - # For example, one of these action batches will often want to - # instantaneously create a running command by having a queue action - # immediately followed by a run action. We cannot let the - # ProtocolEngine's background task see the command in the `queued` state, - # or it will try to execute it, which the legacy protocol is already doing. - for action in action_batch: - self.dispatch(action) + future = asyncio.run_coroutine_threadsafe( + self._dispatch_action_list(pe_actions), self._engine_loop + ) + future.result() + + async def _dispatch_action_list(self, actions: list[pe_actions.Action]) -> None: + for action in actions: + self.dispatch(action) diff --git a/api/src/opentrons/protocol_runner/protocol_runner.py b/api/src/opentrons/protocol_runner/protocol_runner.py index 6ad759f5f6d..ed540020d24 100644 --- a/api/src/opentrons/protocol_runner/protocol_runner.py +++ b/api/src/opentrons/protocol_runner/protocol_runner.py @@ -1,4 +1,5 @@ """Protocol run control and management.""" +import asyncio from typing import List, NamedTuple, Optional, Union from abc import ABC, abstractmethod @@ -129,9 +130,9 @@ async def stop(self) -> None: post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE, ) - def resume_from_recovery(self) -> None: + def resume_from_recovery(self, reconcile_false_positive: bool) -> None: """See `ProtocolEngine.resume_from_recovery()`.""" - self._protocol_engine.resume_from_recovery() + self._protocol_engine.resume_from_recovery(reconcile_false_positive) @abstractmethod async def run( @@ -227,7 +228,9 @@ async def load( equipment_broker = Broker[LoadInfo]() self._protocol_engine.add_plugin( LegacyContextPlugin( - broker=self._broker, equipment_broker=equipment_broker + engine_loop=asyncio.get_running_loop(), + broker=self._broker, + equipment_broker=equipment_broker, ) ) self._hardware_api.should_taskify_movement_execution(taskify=True) diff --git a/api/src/opentrons/protocol_runner/run_orchestrator.py b/api/src/opentrons/protocol_runner/run_orchestrator.py index 5b7e77d73ed..28266a9c485 100644 --- a/api/src/opentrons/protocol_runner/run_orchestrator.py +++ b/api/src/opentrons/protocol_runner/run_orchestrator.py @@ -1,11 +1,13 @@ """Engine/Runner provider.""" + from __future__ import annotations import enum -from typing import Optional, Union, List, Dict, AsyncGenerator +from typing import Optional, Union, List, Dict, AsyncGenerator, Mapping from anyio import move_on_after +from opentrons.types import NozzleMapInterface from opentrons_shared_data.labware.types import LabwareUri from opentrons_shared_data.labware.labware_definition import LabwareDefinition from opentrons_shared_data.errors import GeneralError @@ -205,9 +207,9 @@ async def stop(self) -> None: post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE, ) - def resume_from_recovery(self) -> None: + def resume_from_recovery(self, reconcile_false_positive: bool) -> None: """Resume the run from recovery.""" - self._protocol_engine.resume_from_recovery() + self._protocol_engine.resume_from_recovery(reconcile_false_positive) async def finish( self, @@ -265,19 +267,34 @@ def get_current_command(self) -> Optional[CommandPointer]: """Get the "current" command, if any.""" return self._protocol_engine.state_view.commands.get_current() + def get_most_recently_finalized_command(self) -> Optional[CommandPointer]: + """Get the most recently finalized command, if any.""" + most_recently_finalized_command = ( + self._protocol_engine.state_view.commands.get_most_recently_finalized_command() + ) + return ( + CommandPointer( + command_id=most_recently_finalized_command.command.id, + command_key=most_recently_finalized_command.command.key, + created_at=most_recently_finalized_command.command.createdAt, + index=most_recently_finalized_command.index, + ) + if most_recently_finalized_command + else None + ) + def get_command_slice( - self, - cursor: Optional[int], - length: int, + self, cursor: Optional[int], length: int, include_fixit_commands: bool ) -> CommandSlice: """Get a slice of run commands. Args: cursor: Requested index of first command in the returned slice. length: Length of slice to return. + include_fixit_commands: Get all command intents. """ return self._protocol_engine.state_view.commands.get_slice( - cursor=cursor, length=length + cursor=cursor, length=length, include_fixit_commands=include_fixit_commands ) def get_command_error_slice( @@ -407,6 +424,25 @@ def get_deck_type(self) -> DeckType: """Get engine deck type.""" return self._protocol_engine.state_view.config.deck_type + def get_nozzle_maps(self) -> Mapping[str, NozzleMapInterface]: + """Get current nozzle maps keyed by pipette id.""" + return self._protocol_engine.state_view.tips.get_pipette_nozzle_maps() + + def get_tip_attached(self) -> Dict[str, bool]: + """Get current tip state keyed by pipette id.""" + + def has_tip_attached(pipette_id: str) -> bool: + return ( + self._protocol_engine.state_view.pipettes.get_attached_tip(pipette_id) + is not None + ) + + pipette_ids = ( + pipette.id + for pipette in self._protocol_engine.state_view.pipettes.get_all() + ) + return {pipette_id: has_tip_attached(pipette_id) for pipette_id in pipette_ids} + def set_error_recovery_policy(self, policy: ErrorRecoveryPolicy) -> None: """Create error recovery policy for the run.""" self._protocol_engine.set_error_recovery_policy(policy) diff --git a/api/src/opentrons/protocol_runner/thread_async_queue.py b/api/src/opentrons/protocol_runner/thread_async_queue.py deleted file mode 100644 index 6b31a3f4c5c..00000000000 --- a/api/src/opentrons/protocol_runner/thread_async_queue.py +++ /dev/null @@ -1,174 +0,0 @@ -"""Safely pass values between threads and async tasks.""" - - -from __future__ import annotations - -from collections import deque -from threading import Condition -from typing import AsyncIterable, Deque, Generic, Iterable, TypeVar - -from anyio.to_thread import run_sync - - -_T = TypeVar("_T") - - -class ThreadAsyncQueue(Generic[_T]): - """A queue to safely pass values of type `_T` between threads and async tasks. - - All methods are safe to call concurrently from any thread or task. - - Compared to queue.Queue: - - * This class lets you close the queue to signal that no more values will be added, - which makes common producer/consumer patterns easier. - (This is like Golang channels and AnyIO memory object streams.) - * This class has built-in support for async consumers. - - Compared to asyncio.Queue and AnyIO memory object streams: - - * You can use this class to communicate between async tasks and threads - without the threads having to wait for the event loop to be free - every time they access the queue. - """ - - def __init__(self) -> None: - """Initialize the queue.""" - self._is_closed = False - self._deque: Deque[_T] = deque() - self._condition = Condition() - - def put(self, value: _T) -> None: - """Add a value to the back of the queue. - - Returns immediately, without blocking. The queue can grow without bound. - - Raises: - QueueClosed: If the queue is already closed. - """ - with self._condition: - if self._is_closed: - raise QueueClosed("Can't add more values when queue is already closed.") - else: - self._deque.append(value) - self._condition.notify() - - def get(self) -> _T: - """Remove and return the value at the front of the queue. - - If the queue is empty, this blocks until a new value is available. - If you're calling from an async task, use one of the async methods instead - to avoid blocking the event loop. - - Raises: - QueueClosed: If all values have been consumed - and the queue has been closed with `done_putting()`. - """ - with self._condition: - while True: - if len(self._deque) > 0: - return self._deque.popleft() - elif self._is_closed: - raise QueueClosed("Queue closed; no more items to get.") - else: - # We don't have anything to return. - # Wait for something to change, then check again. - self._condition.wait() - - def get_until_closed(self) -> Iterable[_T]: - """Remove and return values from the front of the queue until it's closed. - - Example: - for value in queue.get_until_closed(): - print(value) - """ - while True: - try: - yield self.get() - except QueueClosed: - break - - async def get_async(self) -> _T: - """Like `get()`, except yield to the event loop while waiting. - - Warning: - A waiting `get_async()` won't be interrupted by an async cancellation. - The proper way to interrupt a waiting `get_async()` - is to close the queue, just like you have to do with `get()`. - """ - return await run_sync( - self.get, - # We keep `cancellable` False so we don't leak this helper thread. - # If we made it True, an async cancellation here would detach us - # from the helper thread and allow the thread to "run to completion"-- - # but if no more values are ever enqueued, and the queue is never closed, - # completion would never happen and it would hang around forever. - cancellable=False, - ) - - async def get_async_until_closed(self) -> AsyncIterable[_T]: - """Like `get_until_closed()`, except yield to the event loop while waiting. - - Example: - async for value in queue.get_async_until_closed(): - print(value) - - Warning: - While the ``async for`` is waiting for a new value, - it won't be interrupted by an async cancellation. - The proper way to interrupt a waiting `get_async_until_closed()` - is to close the queue, just like you have to do with `get()`. - """ - while True: - try: - yield await self.get_async() - except QueueClosed: - break - - def done_putting(self) -> None: - """Close the queue, i.e. signal that no more values will be `put()`. - - You normally *must* close the queue eventually - to inform consumers that they can stop waiting for new values. - Forgetting to do this can leave them waiting forever, - leaking tasks or threads or causing deadlocks. - - Consider using a ``with`` block instead. See `__enter__()`. - - Raises: - QueueClosed: If the queue is already closed. - """ - with self._condition: - if self._is_closed: - raise QueueClosed("Can't close when queue is already closed.") - else: - self._is_closed = True - self._condition.notify_all() - - def __enter__(self) -> ThreadAsyncQueue[_T]: - """Use the queue as a context manager, closing the queue upon exit. - - Example: - This: - - with queue: - do_stuff() - - Is equivalent to: - - try: - do_stuff() - finally: - queue.done_putting() - """ - return self - - def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None: - """See `__enter__()`.""" - self.done_putting() - - -class QueueClosed(Exception): - """See `ThreadAsyncQueue.done_putting()`.""" - - pass diff --git a/api/src/opentrons/protocols/advanced_control/common.py b/api/src/opentrons/protocols/advanced_control/common.py new file mode 100644 index 00000000000..09e7d7a4adc --- /dev/null +++ b/api/src/opentrons/protocols/advanced_control/common.py @@ -0,0 +1,38 @@ +"""Common resources for all advanced control functions.""" +import enum +from typing import NamedTuple, Optional + + +class MixStrategy(enum.Enum): + BOTH = enum.auto() + BEFORE = enum.auto() + AFTER = enum.auto() + NEVER = enum.auto() + + +class MixOpts(NamedTuple): + """ + Options to customize behavior of mix. + + These options will be passed to + :py:meth:`InstrumentContext.mix` when it is called during the + transfer. + """ + + repetitions: Optional[int] = None + volume: Optional[float] = None + rate: Optional[float] = None + + +MixOpts.repetitions.__doc__ = ":py:class:`int`" +MixOpts.volume.__doc__ = ":py:class:`float`" +MixOpts.rate.__doc__ = ":py:class:`float`" + + +class Mix(NamedTuple): + """ + Options to control mix behavior before aspirate and after dispense. + """ + + mix_before: MixOpts = MixOpts() + mix_after: MixOpts = MixOpts() diff --git a/api/src/opentrons/protocols/advanced_control/mix.py b/api/src/opentrons/protocols/advanced_control/mix.py index 587916e98bc..8ddc3035b48 100644 --- a/api/src/opentrons/protocols/advanced_control/mix.py +++ b/api/src/opentrons/protocols/advanced_control/mix.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Tuple -from opentrons.protocols.advanced_control.transfers import MixStrategy, Mix +from .common import MixStrategy, Mix def mix_from_kwargs(top_kwargs: Dict[str, Any]) -> Tuple[MixStrategy, Mix]: diff --git a/api/src/opentrons/protocols/advanced_control/transfers.py b/api/src/opentrons/protocols/advanced_control/transfers.py deleted file mode 100644 index 5ad9dd64d24..00000000000 --- a/api/src/opentrons/protocols/advanced_control/transfers.py +++ /dev/null @@ -1,1047 +0,0 @@ -import enum -from typing import ( - Any, - Dict, - List, - Optional, - Union, - NamedTuple, - Callable, - Generator, - Iterator, - Iterable, - Sequence, - Tuple, - TypedDict, - TypeAlias, - TYPE_CHECKING, - TypeVar, -) -from opentrons.protocol_api.labware import Labware, Well -from opentrons import types -from opentrons.protocols.api_support.types import APIVersion -from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType - - -AdvancedLiquidHandling = Union[ - Well, - types.Location, - Sequence[Union[Well, types.Location]], - Sequence[Sequence[Well]], -] - - -class TransferStep(TypedDict): - method: str - args: Optional[List[Any]] - kwargs: Optional[Dict[Any, Any]] - - -if TYPE_CHECKING: - from opentrons.protocol_api import InstrumentContext - -_PARTIAL_TIP_SUPPORT_ADDED = APIVersion(2, 18) -"""The version after which partial tip support and nozzle maps were made available.""" - - -class MixStrategy(enum.Enum): - BOTH = enum.auto() - BEFORE = enum.auto() - AFTER = enum.auto() - NEVER = enum.auto() - - -class DropTipStrategy(enum.Enum): - TRASH = enum.auto() - RETURN = enum.auto() - - -class TouchTipStrategy(enum.Enum): - NEVER = enum.auto() - ALWAYS = enum.auto() - - -class BlowOutStrategy(enum.Enum): - NONE = enum.auto() - TRASH = enum.auto() - DEST = enum.auto() - SOURCE = enum.auto() - CUSTOM_LOCATION = enum.auto() - - -class TransferMode(enum.Enum): - DISTRIBUTE = enum.auto() - CONSOLIDATE = enum.auto() - TRANSFER = enum.auto() - - -class Transfer(NamedTuple): - """ - Options pertaining to behavior of the transfer. - - """ - - new_tip: types.TransferTipPolicy = types.TransferTipPolicy.ONCE - air_gap: float = 0 - carryover: bool = True - gradient_function: Optional[Callable[[float], float]] = None - disposal_volume: float = 0 - mix_strategy: MixStrategy = MixStrategy.NEVER - drop_tip_strategy: DropTipStrategy = DropTipStrategy.TRASH - blow_out_strategy: BlowOutStrategy = BlowOutStrategy.NONE - touch_tip_strategy: TouchTipStrategy = TouchTipStrategy.NEVER - - -Transfer.new_tip.__doc__ = """ - Control when or if to pick up tip during a transfer - - :py:attr:`types.TransferTipPolicy.ALWAYS` - Drop and pick up a new tip after each dispense. - - :py:attr:`types.TransferTipPolicy.ONCE` - Pick up tip at the beginning of the transfer and use it - throughout the transfer. This would speed up the transfer. - - :py:attr:`types.TransferTipPolicy.NEVER` - Do not ever pick up or drop tip. The protocol should explicitly - pick up a tip before transfer and drop it afterwards. - - To customize where to drop tip, see :py:attr:`.drop_tip_strategy`. - To customize the behavior of pickup tip, see - :py:attr:`.TransferOptions.pick_up_tip`. - """ - -Transfer.air_gap.__doc__ = """ - Controls the volume (in uL) of air gap aspirated when moving to - dispense. - - Adding an air gap would slow down a transfer since less liquid will - now fit in the pipette but it prevents the loss of liquid while - moving between wells. - """ - -Transfer.carryover.__doc__ = """ - Controls whether volumes larger than pipette's max volume will be - split into smaller volumes. - """ - -Transfer.gradient_function.__doc__ = """ - Specify a nonlinear gradient for volumes. - - This should be a function that takes a single float between 0 and 1 - and returns a single float between 0 and 1. This function is used - to determine the path the transfer takes between the volume - gradient minimum and maximum if the transfer volume is specified as - a gradient. For instance, specifying the function as - - .. code-block:: python - - def gradient(a): - if a > 0.5: - return 1.0 - else: - return 0.0 - - would transfer the minimum volume of the gradient to the first half - of the target wells, and the maximum to the other half. - """ - -Transfer.disposal_volume.__doc__ = """ - The amount of liquid (in uL) to aspirate as a buffer. - - The remaining buffer will be blown out into the location specified - by :py:attr:`.blow_out_strategy`. - - This is useful to avoid under-pipetting but can waste reagent and - slow down transfer. - """ - -Transfer.mix_strategy.__doc__ = """ - If and when to mix during a transfer. - - :py:attr:`MixStrategy.NEVER` - Do not ever perform a mix during the transfer. - - :py:attr:`MixStrategy.BEFORE` - Mix before each aspirate. - - :py:attr:`MixStrategy.AFTER` - Mix after each dispense. - - :py:attr:`MixStrategy.BOTH` - Mix before each aspirate and after each dispense. - - To customize the mix behavior, see :py:attr:`.TransferOptions.mix` - """ - -Transfer.drop_tip_strategy.__doc__ = """ - Specifies the location to drop tip into. - - :py:attr:`DropTipStrategy.TRASH` - Drop the tip into the trash container. - - :py:attr:`DropTipStrategy.RETURN` - Return the tip to tiprack. - """ - -Transfer.blow_out_strategy.__doc__ = """ - Specifies the location to blow out the liquid in the pipette to. - - :py:attr:`BlowOutStrategy.TRASH` - Blow out to trash container. - - :py:attr:`BlowOutStrategy.SOURCE` - Blow out into the source well in order to dispense any leftover - liquid. - - :py:attr:`BlowOutStrategy.DEST` - Blow out into the destination well in order to dispense any leftover - liquid. - - :py:attr:`BlowOutStrategy.CUSTOM_LOCATION` - If using any other location to blow out to. Specify the location in - :py:attr:`.TransferOptions.blow_out`. - """ - -Transfer.touch_tip_strategy.__doc__ = """ - Controls whether to touch tip during the transfer - - This helps in getting rid of any droplets clinging to the pipette - tip at the cost of slowing down the transfer. - - :py:attr:`TouchTipStrategy.NEVER` - Do not touch tip ever during the transfer. - - :py:attr:`TouchTipStrategy.ALWAYS` - Touch tip after each aspirate. - - To customize the behavior of touch tips, see - :py:attr:`.TransferOptions.touch_tip`. - """ - - -class PickUpTipOpts(NamedTuple): - """ - Options to customize :py:attr:`.Transfer.new_tip`. - - These options will be passed to - :py:meth:`InstrumentContext.pick_up_tip` when it is called during - the transfer. - """ - - location: Optional[types.Location] = None - presses: Optional[int] = None - increment: Optional[int] = None - - -PickUpTipOpts.location.__doc__ = ":py:class:`types.Location`" -PickUpTipOpts.presses.__doc__ = ":py:class:`int`" -PickUpTipOpts.increment.__doc__ = ":py:class:`int`" - - -class MixOpts(NamedTuple): - """ - Options to customize behavior of mix. - - These options will be passed to - :py:meth:`InstrumentContext.mix` when it is called during the - transfer. - """ - - repetitions: Optional[int] = None - volume: Optional[float] = None - rate: Optional[float] = None - - -MixOpts.repetitions.__doc__ = ":py:class:`int`" -MixOpts.volume.__doc__ = ":py:class:`float`" -MixOpts.rate.__doc__ = ":py:class:`float`" - - -class Mix(NamedTuple): - """ - Options to control mix behavior before aspirate and after dispense. - """ - - mix_before: MixOpts = MixOpts() - mix_after: MixOpts = MixOpts() - - -Mix.mix_before.__doc__ = """ - Options applied to mix before aspirate. - See :py:class:`.Mix.MixOpts`. - """ - -Mix.mix_after.__doc__ = """ - Options applied to mix after dispense. See :py:class:`.Mix.MixOpts`. - """ - - -class BlowOutOpts(NamedTuple): - """ - Location where to blow out instead of the trash. - - This location will be passed to :py:meth:`InstrumentContext.blow_out` - when called during the transfer - """ - - location: Optional[Union[types.Location, Well]] = None - - -BlowOutOpts.location.__doc__ = ":py:class:`types.Location`" - - -class TouchTipOpts(NamedTuple): - """ - Options to customize touch tip. - - These options will be passed to - :py:meth:`InstrumentContext.touch_tip` when called during the - transfer. - """ - - radius: Optional[float] = None - v_offset: Optional[float] = None - speed: Optional[float] = None - - -TouchTipOpts.radius.__doc__ = ":py:class:`float`" -TouchTipOpts.v_offset.__doc__ = ":py:class:`float`" -TouchTipOpts.speed.__doc__ = ":py:class:`float`" - - -class AspirateOpts(NamedTuple): - """ - Option to customize aspirate rate. - - This option will be passed to :py:meth:`InstrumentContext.aspirate` - when called during the transfer. - """ - - rate: Optional[float] = 1.0 - - -AspirateOpts.rate.__doc__ = ":py:class:`float`" - - -class DispenseOpts(NamedTuple): - """ - Option to customize dispense rate. - - This option will be passed to :py:meth:`InstrumentContext.dispense` - when called during the transfer. - """ - - rate: Optional[float] = 1.0 - - -DispenseOpts.rate.__doc__ = ":py:class:`float`" - - -class TransferOptions(NamedTuple): - """ - All available options for a transfer, distribute or consolidate function - """ - - transfer: Transfer = Transfer() - pick_up_tip: PickUpTipOpts = PickUpTipOpts() - mix: Mix = Mix() - blow_out: BlowOutOpts = BlowOutOpts() - touch_tip: TouchTipOpts = TouchTipOpts() - aspirate: AspirateOpts = AspirateOpts() - dispense: DispenseOpts = DispenseOpts() - - -FormatDictArgs: TypeAlias = Union[ - PickUpTipOpts, MixOpts, BlowOutOpts, TouchTipOpts, AspirateOpts, DispenseOpts -] - - -TransferOptions.transfer.__doc__ = """ - Options pertaining to behavior of the transfer. - - For instance you can control how frequently to get a new tip using - :py:attr:`.Transfer.new_tip`. For documentation of all transfer options - see :py:class:`.Transfer`. - """ - -TransferOptions.pick_up_tip.__doc__ = """ - Options used when picking up a tip during transfer. - See :py:class:`.PickUpTipOpts`. - """ - -TransferOptions.mix.__doc__ = """ - Options to control mix behavior before aspirate and after dispense. - See :py:class:`.Mix`. - """ - -TransferOptions.blow_out.__doc__ = """ - Option to specify custom location for blow out. See - :py:class:`.BlowOutOpts`. - """ - -TransferOptions.touch_tip.__doc__ = """ - Options to customize touch tip. See - :py:class:`.TouchTipOpts`. - """ - -TransferOptions.aspirate.__doc__ = """ - Option to customize aspirate rate. See - :py:class:`.AspirateOpts`. - """ - -TransferOptions.dispense.__doc__ = """ - Option to customize dispense rate. See - :py:class:`.DispenseOpts`. - """ - - -class TransferPlan: - """Calculate and carry state for an arbitrary transfer - - This class encapsulates the logic around planning an M:N transfer. - - It handles calculations based on pipette channels, tip management, and all - the various little commands that can be involved in a transfer. It can be - iterated to resolve methods to call to execute the plan. - """ - - def __init__( - self, - volume: Union[float, Sequence[float]], - srcs: AdvancedLiquidHandling, - dsts: AdvancedLiquidHandling, - # todo(mm, 2021-03-10): - # Refactor to not need an InstrumentContext, so we can more - # easily test this class's logic on its own. - instr: "InstrumentContext", - max_volume: float, - api_version: APIVersion, - mode: str, - options: Optional[TransferOptions] = None, - ) -> None: - """Build the transfer plan. - - This method initializes the object and does the work of preparing the - transfer plan. Its arguments are as those of - :py:meth:`.InstrumentContext.transfer`. - """ - self._instr = instr - self._api_version = api_version - # Convert sources & dests into proper format - # CASES: - # i. if using multi-channel pipette, - # and the source or target is a row/column of Wells (i.e list of Wells) - # then avoid iterating through its Wells. - # ii. if using single channel pipettes, flatten a multi-dimensional - # list of Wells into a 1 dimensional list of Wells - pipette_configuration_type = NozzleConfigurationType.FULL - normalized_sources: List[Union[Well, types.Location]] - normalized_dests: List[Union[Well, types.Location]] - if self._api_version >= _PARTIAL_TIP_SUPPORT_ADDED: - pipette_configuration_type = ( - self._instr._core.get_nozzle_map().configuration - ) - if ( - self._instr.channels > 1 - and pipette_configuration_type == NozzleConfigurationType.FULL - ): - normalized_sources, normalized_dests = self._multichannel_transfer( - srcs, dsts - ) - else: - if isinstance(srcs, List): - if isinstance(srcs[0], List): - # Source is a List[List[Well]] - normalized_sources = [ - well for well_list in srcs for well in well_list - ] - else: - normalized_sources = srcs - elif isinstance(srcs, Well) or isinstance(srcs, types.Location): - normalized_sources = [srcs] - if isinstance(dsts, List): - if isinstance(dsts[0], List): - # Dest is a List[List[Well]] - normalized_dests = [ - well for well_list in dsts for well in well_list - ] - else: - normalized_dests = dsts - elif isinstance(dsts, Well) or isinstance(dsts, types.Location): - normalized_dests = [dsts] - - total_xfers = max(len(normalized_sources), len(normalized_dests)) - - self._volumes = self._create_volume_list(volume, total_xfers) - self._sources = normalized_sources - self._dests = normalized_dests - self._options = options or TransferOptions() - self._strategy = self._options.transfer - self._tip_opts = self._options.pick_up_tip - self._blow_opts = self._options.blow_out - self._touch_tip_opts = self._options.touch_tip - self._mix_before_opts = self._options.mix.mix_before - self._mix_after_opts = self._options.mix.mix_after - self._max_volume = max_volume - - self._mode = TransferMode[mode.upper()] - - def __iter__(self) -> Iterator[TransferStep]: - if self._strategy.new_tip == types.TransferTipPolicy.ONCE: - yield self._format_dict("pick_up_tip", kwargs=self._tip_opts) - yield from { - TransferMode.CONSOLIDATE: self._plan_consolidate, - TransferMode.DISTRIBUTE: self._plan_distribute, - TransferMode.TRANSFER: self._plan_transfer, - }[self._mode]() - if self._strategy.new_tip == types.TransferTipPolicy.ONCE: - if self._strategy.drop_tip_strategy == DropTipStrategy.RETURN: - yield self._format_dict("return_tip") - else: - yield self._format_dict("drop_tip") - - def _plan_transfer(self) -> Generator[TransferStep, None, None]: - """ - * **Source/ Dest:** Multiple sources to multiple destinations. - Src & dest should be equal length - - * **Volume:** Single volume or List of volumes is acceptable. This list - should be same length as sources/destinations - - * **Behavior with transfer options:** - - - New_tip: can be either NEVER or ONCE or ALWAYS - - Air_gap: if specified, will be performed after every aspirate - - Blow_out: can be performed after each dispense (after mix, before - touch_tip) at the location specified. If there is - liquid present in the tip (as in the case of nonzero - disposal volume), blow_out will be performed at either - user-defined location or (default) trash. - If no liquid is supposed to be present in the tip after - dispense, blow_out will be performed at dispense well - location (if blow out strategy is DEST) - - Touch_tip: can be performed after each aspirate and/or after - each dispense - - Mix: can be performed before aspirate and/or after dispense - if there is no disposal volume (i.e. can be performed - only when the tip is supposed to be empty) - - Considering all options, the sequence of actions is: - *New Tip -> Mix -> Aspirate (with disposal volume) -> Air gap -> - -> Touch tip -> Dispense air gap -> Dispense -> Mix if empty -> - -> Blow out -> Touch tip -> Drop tip* - """ - # reform source target lists - sources, dests = self._extend_source_target_lists(self._sources, self._dests) - self._check_valid_volume_parameters( - disposal_volume=self._strategy.disposal_volume, - air_gap=self._strategy.air_gap, - max_volume=self._instr.max_volume, - ) - plan_iter = self._expand_for_volume_constraints( - self._volumes, - zip(sources, dests), - self._instr.max_volume - - self._strategy.disposal_volume - - self._strategy.air_gap, - ) - for step_vol, (src, dest) in plan_iter: - if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS: - yield self._format_dict("pick_up_tip", kwargs=self._tip_opts) - max_vol = ( - self._max_volume - - self._strategy.disposal_volume - - self._strategy.air_gap - ) - xferred_vol = 0.0 - while xferred_vol < step_vol: - # TODO: account for unequal length sources, dests - # TODO: ensure last transfer is > min_vol - vol = min(max_vol, step_vol - xferred_vol) - yield from self._aspirate_actions(vol, src) - yield from self._dispense_actions(vol=vol, dest=dest, src=src) - xferred_vol += vol - yield from self._new_tip_action() - - @staticmethod - def _extend_source_target_lists( - sources: List[Union[Well, types.Location]], - targets: List[Union[Well, types.Location]], - ) -> Tuple[List[Union[Well, types.Location]], List[Union[Well, types.Location]]]: - """Extend source or target list to match the length of the other""" - if len(sources) < len(targets): - if len(targets) % len(sources) != 0: - raise ValueError("Source and destination lists must be divisible") - sources = [ - source - for source in sources - for i in range(int(len(targets) / len(sources))) - ] - elif len(sources) > len(targets): - if len(sources) % len(targets) != 0: - raise ValueError("Source and destination lists must be divisible") - targets = [ - target - for target in targets - for i in range(int(len(sources) / len(targets))) - ] - return sources, targets - - def _plan_distribute(self) -> Generator[TransferStep, None, None]: - """ - * **Source/ Dest:** One source to many destinations - * **Volume:** Single volume or List of volumes is acceptable. This list - should be same length as destinations - * **Behavior with transfer options:** - - - New_tip: can be either NEVER or ONCE - (ALWAYS will fallback to ONCE) - - Air_gap: if specified, will be performed after every aspirate and - also in-between dispenses (to keep air gap while moving - between wells) - - Blow_out: can be performed at the end of distribute (after mix, - before touch_tip) at the location specified. If there - is liquid present in the tip, blow_out will be - performed at either user-defined location or (default) - trash. If no liquid is supposed to be present in the - tip at the end of distribute, blow_out will be - performed at the last well the liquid was dispensed to - (if strategy is DEST) - - Touch_tip: can be performed after each aspirate and/or after - every dispense - - Mix: can be performed before aspirate and/or after the last - dispense if there is no disposal volume (i.e. can be - performed only when the tip is supposed to be empty) - - Considering all options, the sequence of actions is: - - 1. Going from source to dest1: - *New Tip -> Mix -> Aspirate (with disposal volume) -> Air gap -> - -> Touch tip -> Dispense air gap -> Dispense -> Mix if empty -> - -> Blow out -> Touch tip -> Drop tip* - 2. Going from destn to destn+1: - *.. Dispense air gap -> Dispense -> Touch tip -> Air gap -> - .. Dispense air gap -> ...* - - """ - - self._check_valid_volume_parameters( - disposal_volume=self._strategy.disposal_volume, - air_gap=self._strategy.air_gap, - max_volume=self._instr.max_volume, - ) - - # TODO: decide whether default disposal vol for distribute should be - # pipette min_vol or should we leave it to being 0 by default and - # recommend users to specify a disposal vol when using distribute. - # First method keeps distribute consistent with current behavior while - # the other maintains consistency in default behaviors of all functions - plan_iter = self._expand_for_volume_constraints( - self._volumes, - self._dests, - # todo(mm, 2021-03-09): Is it right for this to be - # _instr_.max_volume? Does/should this take the tip maximum volume - # into account? - self._instr.max_volume - - self._strategy.disposal_volume - - self._strategy.air_gap, - ) - - done = False - current_xfer = next(plan_iter) - if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS: - yield self._format_dict("pick_up_tip", kwargs=self._tip_opts) - while not done: - asp_grouped: List[Tuple[float, Well | types.Location]] = [] - try: - while ( - sum(a[0] for a in asp_grouped) - + self._strategy.disposal_volume - + self._strategy.air_gap - + current_xfer[0] - ) <= self._max_volume: - append_xfer = self._check_volume_not_zero( - self._api_version, current_xfer[0] - ) - if append_xfer: - asp_grouped.append(current_xfer) - current_xfer = next(plan_iter) - except StopIteration: - done = True - if not asp_grouped: - break - - yield from self._aspirate_actions( - sum(a[0] for a in asp_grouped) + self._strategy.disposal_volume, - self._sources[0], - ) - for step in asp_grouped: - - yield from self._dispense_actions( - vol=step[0], - src=self._sources[0], - dest=step[1], - is_disp_next=step is not asp_grouped[-1], - ) - yield from self._new_tip_action() - - Target = TypeVar("Target") - - @staticmethod - def _expand_for_volume_constraints( - volumes: Iterable[float], targets: Iterable[Target], max_volume: float - ) -> Generator[Tuple[float, "Target"], None, None]: - """Split a sequence of proposed transfers if necessary to keep each - transfer under the given max volume. - """ - # A final defense against an infinite loop. - # Raising a proper exception with a helpful message is left to calling code, - # because it has more context about what the user is trying to do. - assert max_volume > 0 - for volume, target in zip(volumes, targets): - while volume > max_volume * 2: - yield max_volume, target - volume -= max_volume - - if volume > max_volume: - volume /= 2 - yield volume, target - yield volume, target - - def _plan_consolidate(self) -> Generator[TransferStep, None, None]: - """ - * **Source/ Dest:** Many sources to one destination - * **Volume:** Single volume or List of volumes is acceptable. This list - should be same length as sources - * **Behavior with transfer options:** - - - New_tip: can be either NEVER or ONCE - (ALWAYS will fallback to ONCE) - - Air_gap: if specified, will be performed after every aspirate - so that the aspirated liquids do not mix inside the tip. - The air gap will be dispensed while dispensing the - liquid into the destination well. - - Blow_out: can be performed after a dispense (after mix, - before touch_tip) at the location specified. If there - is liquid present in the tip (which shouldn't happen - since consolidate doesn't take a disposal vol, yet), - blow_out will be performed at either user-defined - location or (default) trash. - If no liquid is supposed to be present in the tip after - dispense, blow_out will be performed at dispense well - loc (if blow out strategy is DEST) - - Touch_tip: can be performed after each aspirate and/or after - dispense - - Mix: can be performed before the first aspirate and/or after - dispense if there is no disposal volume (i.e. can be - performed only when the tip is supposed to be empty) - - Considering all options, the sequence of actions is: - 1. Going from source to dest1: - *New Tip -> Mix -> Aspirate (with disposal volume?) -> Air gap - -> Touch tip -> Dispense air gap -> Dispense -> Mix if empty -> - -> Blow out -> Touch tip -> Drop tip* - 2. Going from source(n) to source(n+1): - *.. Aspirate -> Air gap -> Touch tip ->.. - .. Aspirate -> .....* - """ - # TODO: verify if _check_valid_volume_parameters should be re-enabled here - # self._check_valid_volume_parameters( - # disposal_volume=self._strategy.disposal_volume, - # air_gap=self._strategy.air_gap, - # max_volume=self._instr.max_volume, - # ) - plan_iter = self._expand_for_volume_constraints( - # todo(mm, 2021-03-09): Is it right to use _instr.max_volume here? - # Why don't we account for tip max volume, disposal volume, or air - # gap? - self._volumes, - self._sources, - self._instr.max_volume, - ) - current_xfer = next(plan_iter) - if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS: - yield self._format_dict("pick_up_tip", kwargs=self._tip_opts) - done = False - while not done: - asp_grouped: List[Tuple[float, Union[Well, types.Location]]] = [] - try: - while ( - sum([a[0] for a in asp_grouped]) - + self._strategy.disposal_volume - + self._strategy.air_gap * len(asp_grouped) - + current_xfer[0] - ) <= self._max_volume: - append_xfer = self._check_volume_not_zero( - self._api_version, current_xfer[0] - ) - if append_xfer: - asp_grouped.append(current_xfer) - current_xfer = next(plan_iter) - except StopIteration: - done = True - if not asp_grouped: - break - # Q: What accounts as disposal volume in a consolidate action? - # yield self._format_dict('aspirate', - # self._strategy.disposal_volume, loc) - for step in asp_grouped: - yield from self._aspirate_actions(step[0], step[1]) - yield from self._dispense_actions( - vol=sum([a[0] + self._strategy.air_gap for a in asp_grouped]) - - self._strategy.air_gap, - src=None, - dest=self._dests[0], - ) - yield from self._new_tip_action() - - def _aspirate_actions( - self, vol: float, loc: Union[Well, types.Location] - ) -> Generator[TransferStep, None, None]: - yield from self._before_aspirate(loc) - yield self._format_dict("aspirate", [vol, loc, self._options.aspirate.rate]) - yield from self._after_aspirate() - - def _dispense_actions( - self, - vol: float, - dest: Union[Well, types.Location], - src: Optional[Union[Well, types.Location]] = None, - is_disp_next: bool = False, - ) -> Generator[TransferStep, None, None]: - if self._strategy.air_gap: - vol += self._strategy.air_gap - yield self._format_dict("dispense", [vol, dest, self._options.dispense.rate]) - yield from self._after_dispense(dest=dest, src=src, is_disp_next=is_disp_next) - - def _before_aspirate( - self, loc: Union[Well, types.Location] - ) -> Generator[TransferStep, None, None]: - if ( - self._strategy.mix_strategy == MixStrategy.BEFORE - or self._strategy.mix_strategy == MixStrategy.BOTH - ): - if self._instr.current_volume == 0: - mix_before_opts = self._mix_before_opts._asdict() - mix_before_opts["location"] = loc - yield self._format_dict("mix", kwargs=mix_before_opts) - - def _after_aspirate(self) -> Generator[TransferStep, None, None]: - if self._strategy.air_gap: - yield self._format_dict("air_gap", [self._strategy.air_gap]) - if self._strategy.touch_tip_strategy == TouchTipStrategy.ALWAYS: - yield self._format_dict("touch_tip", kwargs=self._touch_tip_opts) - - def _after_dispense_trash(self) -> Generator[TransferStep, None, None]: - if isinstance(self._instr.trash_container, Labware): - yield self._format_dict( - "blow_out", [self._instr.trash_container.wells()[0]] - ) - else: - yield self._format_dict("blow_out", [self._instr.trash_container]) - - def _after_dispense_helper(self) -> Generator[TransferStep, None, None]: - # Used by distribute - if self._strategy.air_gap: - yield self._format_dict("air_gap", [self._strategy.air_gap]) - if self._strategy.touch_tip_strategy == TouchTipStrategy.ALWAYS: - yield self._format_dict("touch_tip", kwargs=self._touch_tip_opts) - - def _after_dispense( - self, - dest: Union[Well, types.Location], - src: Optional[Union[types.Location, Well]], - is_disp_next: bool = False, - ) -> Generator[TransferStep, None, None]: - # This sequence of actions is subject to change - if not is_disp_next: - # If the next command is an aspirate, we are switching - # between aspirate and dispense. - if self._instr.current_volume == 0: - # If we're empty, then this is when after mixes come into play - if ( - self._strategy.mix_strategy == MixStrategy.AFTER - or self._strategy.mix_strategy == MixStrategy.BOTH - ): - mix_after_opts = self._mix_after_opts._asdict() - mix_after_opts["location"] = dest - yield self._format_dict("mix", kwargs=mix_after_opts) - if self._strategy.touch_tip_strategy == TouchTipStrategy.ALWAYS: - yield self._format_dict("touch_tip", kwargs=self._touch_tip_opts) - - if self._strategy.blow_out_strategy == BlowOutStrategy.SOURCE: - yield self._format_dict("blow_out", [src]) - elif self._strategy.blow_out_strategy == BlowOutStrategy.DEST: - yield self._format_dict("blow_out", [dest]) - elif self._strategy.blow_out_strategy == BlowOutStrategy.CUSTOM_LOCATION: - yield self._format_dict("blow_out", kwargs=self._blow_opts) - elif ( - self._strategy.blow_out_strategy == BlowOutStrategy.TRASH - or self._strategy.disposal_volume - ): - yield from self._after_dispense_trash() - else: - yield from self._after_dispense_helper() - - def _new_tip_action(self) -> Generator[TransferStep, None, None]: - if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS: - if self._strategy.drop_tip_strategy == DropTipStrategy.RETURN: - yield self._format_dict("return_tip") - else: - yield self._format_dict("drop_tip") - - def _format_dict( - self, - method: str, - args: Optional[List[Any]] = None, - kwargs: Optional[Union[Dict[Any, Any], FormatDictArgs]] = None, - ) -> TransferStep: - if kwargs: - if isinstance(kwargs, Dict): - params = {key: val for key, val in kwargs.items() if val} - else: - params = {key: val for key, val in kwargs._asdict().items() if val} - else: - params = {} - if not args: - args = [] - return {"method": method, "args": args, "kwargs": params} - - def _create_volume_list( - self, volume: Union[Union[float, int], Sequence[float]], total_xfers: int - ) -> List[float]: - if isinstance(volume, (float, int)): - return [float(volume)] * total_xfers - elif isinstance(volume, tuple): - return self._create_volume_gradient( - volume[0], volume[-1], total_xfers, self._strategy.gradient_function - ) - else: - if not isinstance(volume, List): - raise TypeError( - "Volume expected as a number or List or" - " tuple but got {}".format(volume) - ) - elif not len(volume) == total_xfers: - raise RuntimeError( - "List of volumes should be equal to number " "of transfers" - ) - return volume - - def _create_volume_gradient( - self, - min_v: float, - max_v: float, - total: int, - gradient: Optional[Callable[[float], float]] = None, - ) -> List[float]: - - diff_vol = max_v - min_v - - def _map_volume(i: int) -> float: - nonlocal diff_vol, total - rel_x = i / (total - 1) - rel_y = gradient(rel_x) if gradient else rel_x - return (rel_y * diff_vol) + min_v - - return [_map_volume(i) for i in range(total)] - - def _check_valid_volume_parameters( - self, disposal_volume: float, air_gap: float, max_volume: float - ) -> None: - if air_gap >= max_volume: - raise ValueError( - "The air gap must be less than the maximum volume of the pipette" - ) - elif disposal_volume >= max_volume: - raise ValueError( - "The disposal volume must be less than the maximum volume of the pipette" - ) - elif disposal_volume + air_gap >= max_volume: - raise ValueError( - "The sum of the air gap and disposal volume must be less than the maximum volume of the pipette" - ) - - def _check_valid_well_list( - self, well_list: List[Any], id: str, old_well_list: List[Any] - ) -> None: - if self._api_version >= APIVersion(2, 2) and len(well_list) < 1: - raise RuntimeError( - f"Invalid {id} for multichannel transfer: {old_well_list}" - ) - - @staticmethod - def _check_volume_not_zero(api_version: APIVersion, volume: float) -> bool: - # We should only be adding volumes to transfer plans if it is - # greater than zero to prevent extraneous robot movements. - if api_version < APIVersion(2, 8): - return True - elif volume > 0: - return True - return False - - def _multichannel_transfer( - self, s: AdvancedLiquidHandling, d: AdvancedLiquidHandling - ) -> Tuple[List[Union[Well, types.Location]], List[Union[Well, types.Location]]]: - # TODO: add a check for container being multi-channel compatible? - # Helper function for multi-channel use-case - assert ( - isinstance(s, Well) - or isinstance(s, types.Location) - or (isinstance(s, List) and isinstance(s[0], Well)) - or (isinstance(s, List) and isinstance(s[0], List)) - or (isinstance(s, List) and isinstance(s[0], types.Location)) - ), "Source should be a Well or List[Well] but is {}".format(s) - assert ( - isinstance(d, Well) - or isinstance(d, types.Location) - or (isinstance(d, List) and isinstance(d[0], Well)) - or (isinstance(d, List) and isinstance(d[0], List)) - or (isinstance(d, List) and isinstance(d[0], types.Location)) - ), "Target should be a Well or List[Well] but is {}".format(d) - - # TODO: Account for cases where a src/dest list has a non-first-row - # well (eg, 'B1') and would expect the robot/pipette to - # understand that it is referring to the whole first column - if isinstance(s, List) and isinstance(s[0], List): - # s is a List[List]]; flatten to 1D list - s = [well for list_elem in s for well in list_elem] - elif isinstance(s, Well) or isinstance(s, types.Location): - s = [s] - new_src = [] - for well in s: - if self._is_valid_row(well): - new_src.append(well) - self._check_valid_well_list(new_src, "source", s) - - if isinstance(d, List) and isinstance(d[0], List): - # s is a List[List]]; flatten to 1D list - d = [well for list_elem in d for well in list_elem] - elif isinstance(d, Well) or isinstance(d, types.Location): - d = [d] - new_dst = [] - for well in d: - if self._is_valid_row(well): - new_dst.append(well) - self._check_valid_well_list(new_dst, "target", d) - return new_src, new_dst - - def _is_valid_row(self, well: Union[Well, types.Location]) -> bool: - if isinstance(well, types.Location): - test_well = well.labware.as_well() - else: - test_well = well - - if self._api_version < APIVersion(2, 2): - return test_well in test_well.parent.rows()[0] - else: - # Allow the first 2 rows to be accessible to 384-well plates; - # otherwise, only the first row is accessible - if test_well.parent.parameters["format"] == "384Standard": - valid_wells = [ - well for row in test_well.parent.rows()[:2] for well in row - ] - return test_well in valid_wells - else: - return test_well in test_well.parent.rows()[0] diff --git a/api/src/opentrons/protocols/advanced_control/transfers/__init__.py b/api/src/opentrons/protocols/advanced_control/transfers/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/api/src/opentrons/protocols/advanced_control/transfers/common.py b/api/src/opentrons/protocols/advanced_control/transfers/common.py new file mode 100644 index 00000000000..c40a55beacd --- /dev/null +++ b/api/src/opentrons/protocols/advanced_control/transfers/common.py @@ -0,0 +1,56 @@ +"""Common functions between v1 transfer and liquid-class-based transfer.""" +import enum +from typing import Iterable, Generator, Tuple, TypeVar, Literal + + +class TransferTipPolicyV2(enum.Enum): + ONCE = "once" + NEVER = "never" + ALWAYS = "always" + PER_SOURCE = "per source" + + +TransferTipPolicyV2Type = Literal["once", "always", "per source", "never"] + +Target = TypeVar("Target") + + +def check_valid_volume_parameters( + disposal_volume: float, air_gap: float, max_volume: float +) -> None: + if air_gap >= max_volume: + raise ValueError( + "The air gap must be less than the maximum volume of the pipette" + ) + elif disposal_volume >= max_volume: + raise ValueError( + "The disposal volume must be less than the maximum volume of the pipette" + ) + elif disposal_volume + air_gap >= max_volume: + raise ValueError( + "The sum of the air gap and disposal volume must be less than" + " the maximum volume of the pipette" + ) + + +def expand_for_volume_constraints( + volumes: Iterable[float], + targets: Iterable[Target], + max_volume: float, +) -> Generator[Tuple[float, "Target"], None, None]: + """Split a sequence of proposed transfers if necessary to keep each + transfer under the given max volume. + """ + # A final defense against an infinite loop. + # Raising a proper exception with a helpful message is left to calling code, + # because it has more context about what the user is trying to do. + assert max_volume > 0 + for volume, target in zip(volumes, targets): + while volume > max_volume * 2: + yield max_volume, target + volume -= max_volume + + if volume > max_volume: + volume /= 2 + yield volume, target + yield volume, target diff --git a/api/src/opentrons/protocols/advanced_control/transfers/transfer.py b/api/src/opentrons/protocols/advanced_control/transfers/transfer.py new file mode 100644 index 00000000000..3f5f90ab550 --- /dev/null +++ b/api/src/opentrons/protocols/advanced_control/transfers/transfer.py @@ -0,0 +1,972 @@ +import enum +from typing import ( + Any, + Dict, + List, + Optional, + Union, + NamedTuple, + Callable, + Generator, + Iterator, + Sequence, + Tuple, + TypedDict, + TypeAlias, + TYPE_CHECKING, +) +from opentrons.protocol_api.labware import Labware, Well +from opentrons import types +from opentrons.protocols.api_support.types import APIVersion + +from . import common as tx_commons +from ..common import Mix, MixOpts, MixStrategy + +AdvancedLiquidHandling = Union[ + Well, + types.Location, + Sequence[Union[Well, types.Location]], + Sequence[Sequence[Well]], +] + + +class TransferStep(TypedDict): + method: str + args: Optional[List[Any]] + kwargs: Optional[Dict[Any, Any]] + + +if TYPE_CHECKING: + from opentrons.protocol_api import InstrumentContext + +_PARTIAL_TIP_SUPPORT_ADDED = APIVersion(2, 18) +"""The version after which partial tip support and nozzle maps were made available.""" + + +class DropTipStrategy(enum.Enum): + TRASH = enum.auto() + RETURN = enum.auto() + + +class TouchTipStrategy(enum.Enum): + NEVER = enum.auto() + ALWAYS = enum.auto() + + +class BlowOutStrategy(enum.Enum): + NONE = enum.auto() + TRASH = enum.auto() + DEST = enum.auto() + SOURCE = enum.auto() + CUSTOM_LOCATION = enum.auto() + + +class TransferMode(enum.Enum): + DISTRIBUTE = enum.auto() + CONSOLIDATE = enum.auto() + TRANSFER = enum.auto() + + +class Transfer(NamedTuple): + """ + Options pertaining to behavior of the transfer. + + """ + + new_tip: types.TransferTipPolicy = types.TransferTipPolicy.ONCE + air_gap: float = 0 + carryover: bool = True + gradient_function: Optional[Callable[[float], float]] = None + disposal_volume: float = 0 + mix_strategy: MixStrategy = MixStrategy.NEVER + drop_tip_strategy: DropTipStrategy = DropTipStrategy.TRASH + blow_out_strategy: BlowOutStrategy = BlowOutStrategy.NONE + touch_tip_strategy: TouchTipStrategy = TouchTipStrategy.NEVER + + +Transfer.new_tip.__doc__ = """ + Control when or if to pick up tip during a transfer + + :py:attr:`types.TransferTipPolicy.ALWAYS` + Drop and pick up a new tip after each dispense. + + :py:attr:`types.TransferTipPolicy.ONCE` + Pick up tip at the beginning of the transfer and use it + throughout the transfer. This would speed up the transfer. + + :py:attr:`types.TransferTipPolicy.NEVER` + Do not ever pick up or drop tip. The protocol should explicitly + pick up a tip before transfer and drop it afterwards. + + To customize where to drop tip, see :py:attr:`.drop_tip_strategy`. + To customize the behavior of pickup tip, see + :py:attr:`.TransferOptions.pick_up_tip`. + """ + +Transfer.air_gap.__doc__ = """ + Controls the volume (in uL) of air gap aspirated when moving to + dispense. + + Adding an air gap would slow down a transfer since less liquid will + now fit in the pipette but it prevents the loss of liquid while + moving between wells. + """ + +Transfer.carryover.__doc__ = """ + Controls whether volumes larger than pipette's max volume will be + split into smaller volumes. + """ + +Transfer.gradient_function.__doc__ = """ + Specify a nonlinear gradient for volumes. + + This should be a function that takes a single float between 0 and 1 + and returns a single float between 0 and 1. This function is used + to determine the path the transfer takes between the volume + gradient minimum and maximum if the transfer volume is specified as + a gradient. For instance, specifying the function as + + .. code-block:: python + + def gradient(a): + if a > 0.5: + return 1.0 + else: + return 0.0 + + would transfer the minimum volume of the gradient to the first half + of the target wells, and the maximum to the other half. + """ + +Transfer.disposal_volume.__doc__ = """ + The amount of liquid (in uL) to aspirate as a buffer. + + The remaining buffer will be blown out into the location specified + by :py:attr:`.blow_out_strategy`. + + This is useful to avoid under-pipetting but can waste reagent and + slow down transfer. + """ + +Transfer.mix_strategy.__doc__ = """ + If and when to mix during a transfer. + + :py:attr:`MixStrategy.NEVER` + Do not ever perform a mix during the transfer. + + :py:attr:`MixStrategy.BEFORE` + Mix before each aspirate. + + :py:attr:`MixStrategy.AFTER` + Mix after each dispense. + + :py:attr:`MixStrategy.BOTH` + Mix before each aspirate and after each dispense. + + To customize the mix behavior, see :py:attr:`.TransferOptions.mix` + """ + +Transfer.drop_tip_strategy.__doc__ = """ + Specifies the location to drop tip into. + + :py:attr:`DropTipStrategy.TRASH` + Drop the tip into the trash container. + + :py:attr:`DropTipStrategy.RETURN` + Return the tip to tiprack. + """ + +Transfer.blow_out_strategy.__doc__ = """ + Specifies the location to blow out the liquid in the pipette to. + + :py:attr:`BlowOutStrategy.TRASH` + Blow out to trash container. + + :py:attr:`BlowOutStrategy.SOURCE` + Blow out into the source well in order to dispense any leftover + liquid. + + :py:attr:`BlowOutStrategy.DEST` + Blow out into the destination well in order to dispense any leftover + liquid. + + :py:attr:`BlowOutStrategy.CUSTOM_LOCATION` + If using any other location to blow out to. Specify the location in + :py:attr:`.TransferOptions.blow_out`. + """ + +Transfer.touch_tip_strategy.__doc__ = """ + Controls whether to touch tip during the transfer + + This helps in getting rid of any droplets clinging to the pipette + tip at the cost of slowing down the transfer. + + :py:attr:`TouchTipStrategy.NEVER` + Do not touch tip ever during the transfer. + + :py:attr:`TouchTipStrategy.ALWAYS` + Touch tip after each aspirate. + + To customize the behavior of touch tips, see + :py:attr:`.TransferOptions.touch_tip`. + """ + + +class PickUpTipOpts(NamedTuple): + """ + Options to customize :py:attr:`.Transfer.new_tip`. + + These options will be passed to + :py:meth:`InstrumentContext.pick_up_tip` when it is called during + the transfer. + """ + + location: Optional[types.Location] = None + presses: Optional[int] = None + increment: Optional[int] = None + + +PickUpTipOpts.location.__doc__ = ":py:class:`types.Location`" +PickUpTipOpts.presses.__doc__ = ":py:class:`int`" +PickUpTipOpts.increment.__doc__ = ":py:class:`int`" + + +Mix.mix_before.__doc__ = """ + Options applied to mix before aspirate. + See :py:class:`.Mix.MixOpts`. + """ + +Mix.mix_after.__doc__ = """ + Options applied to mix after dispense. See :py:class:`.Mix.MixOpts`. + """ + + +class BlowOutOpts(NamedTuple): + """ + Location where to blow out instead of the trash. + + This location will be passed to :py:meth:`InstrumentContext.blow_out` + when called during the transfer + """ + + location: Optional[Union[types.Location, Well]] = None + + +BlowOutOpts.location.__doc__ = ":py:class:`types.Location`" + + +class TouchTipOpts(NamedTuple): + """ + Options to customize touch tip. + + These options will be passed to + :py:meth:`InstrumentContext.touch_tip` when called during the + transfer. + """ + + radius: Optional[float] = None + v_offset: Optional[float] = None + speed: Optional[float] = None + + +TouchTipOpts.radius.__doc__ = ":py:class:`float`" +TouchTipOpts.v_offset.__doc__ = ":py:class:`float`" +TouchTipOpts.speed.__doc__ = ":py:class:`float`" + + +class AspirateOpts(NamedTuple): + """ + Option to customize aspirate rate. + + This option will be passed to :py:meth:`InstrumentContext.aspirate` + when called during the transfer. + """ + + rate: Optional[float] = 1.0 + + +AspirateOpts.rate.__doc__ = ":py:class:`float`" + + +class DispenseOpts(NamedTuple): + """ + Option to customize dispense rate. + + This option will be passed to :py:meth:`InstrumentContext.dispense` + when called during the transfer. + """ + + rate: Optional[float] = 1.0 + + +DispenseOpts.rate.__doc__ = ":py:class:`float`" + + +class TransferOptions(NamedTuple): + """ + All available options for a transfer, distribute or consolidate function + """ + + transfer: Transfer = Transfer() + pick_up_tip: PickUpTipOpts = PickUpTipOpts() + mix: Mix = Mix() + blow_out: BlowOutOpts = BlowOutOpts() + touch_tip: TouchTipOpts = TouchTipOpts() + aspirate: AspirateOpts = AspirateOpts() + dispense: DispenseOpts = DispenseOpts() + + +FormatDictArgs: TypeAlias = Union[ + PickUpTipOpts, MixOpts, BlowOutOpts, TouchTipOpts, AspirateOpts, DispenseOpts +] + + +TransferOptions.transfer.__doc__ = """ + Options pertaining to behavior of the transfer. + + For instance you can control how frequently to get a new tip using + :py:attr:`.Transfer.new_tip`. For documentation of all transfer options + see :py:class:`.Transfer`. + """ + +TransferOptions.pick_up_tip.__doc__ = """ + Options used when picking up a tip during transfer. + See :py:class:`.PickUpTipOpts`. + """ + +TransferOptions.mix.__doc__ = """ + Options to control mix behavior before aspirate and after dispense. + See :py:class:`.Mix`. + """ + +TransferOptions.blow_out.__doc__ = """ + Option to specify custom location for blow out. See + :py:class:`.BlowOutOpts`. + """ + +TransferOptions.touch_tip.__doc__ = """ + Options to customize touch tip. See + :py:class:`.TouchTipOpts`. + """ + +TransferOptions.aspirate.__doc__ = """ + Option to customize aspirate rate. See + :py:class:`.AspirateOpts`. + """ + +TransferOptions.dispense.__doc__ = """ + Option to customize dispense rate. See + :py:class:`.DispenseOpts`. + """ + + +class TransferPlan: + """Calculate and carry state for an arbitrary transfer + + This class encapsulates the logic around planning an M:N transfer. + + It handles calculations based on pipette channels, tip management, and all + the various little commands that can be involved in a transfer. It can be + iterated to resolve methods to call to execute the plan. + """ + + def __init__( + self, + volume: Union[float, Sequence[float]], + srcs: AdvancedLiquidHandling, + dsts: AdvancedLiquidHandling, + # todo(mm, 2021-03-10): + # Refactor to not need an InstrumentContext, so we can more + # easily test this class's logic on its own. + instr: "InstrumentContext", + max_volume: float, + api_version: APIVersion, + mode: str, + options: Optional[TransferOptions] = None, + ) -> None: + """Build the transfer plan. + + This method initializes the object and does the work of preparing the + transfer plan. Its arguments are as those of + :py:meth:`.InstrumentContext.transfer`. + """ + self._instr = instr + self._api_version = api_version + # Convert sources & dests into proper format + # CASES: + # i. if using multi-channel pipette, + # and the source or target is a row/column of Wells (i.e list of Wells) + # then avoid iterating through its Wells. + # ii. if using single channel pipettes, flatten a multi-dimensional + # list of Wells into a 1 dimensional list of Wells + pipette_configuration_type = types.NozzleConfigurationType.FULL + normalized_sources: List[Union[Well, types.Location]] + normalized_dests: List[Union[Well, types.Location]] + if self._api_version >= _PARTIAL_TIP_SUPPORT_ADDED: + pipette_configuration_type = ( + self._instr._core.get_nozzle_map().configuration + ) + if ( + self._instr.channels > 1 + and pipette_configuration_type == types.NozzleConfigurationType.FULL + ): + normalized_sources, normalized_dests = self._multichannel_transfer( + srcs, dsts + ) + else: + if isinstance(srcs, List): + if isinstance(srcs[0], List): + # Source is a List[List[Well]] + normalized_sources = [ + well for well_list in srcs for well in well_list + ] + else: + normalized_sources = srcs + elif isinstance(srcs, Well) or isinstance(srcs, types.Location): + normalized_sources = [srcs] + if isinstance(dsts, List): + if isinstance(dsts[0], List): + # Dest is a List[List[Well]] + normalized_dests = [ + well for well_list in dsts for well in well_list + ] + else: + normalized_dests = dsts + elif isinstance(dsts, Well) or isinstance(dsts, types.Location): + normalized_dests = [dsts] + + total_xfers = max(len(normalized_sources), len(normalized_dests)) + + self._volumes = self._create_volume_list(volume, total_xfers) + self._sources = normalized_sources + self._dests = normalized_dests + self._options = options or TransferOptions() + self._strategy = self._options.transfer + self._tip_opts = self._options.pick_up_tip + self._blow_opts = self._options.blow_out + self._touch_tip_opts = self._options.touch_tip + self._mix_before_opts = self._options.mix.mix_before + self._mix_after_opts = self._options.mix.mix_after + self._max_volume = max_volume + + self._mode = TransferMode[mode.upper()] + + def __iter__(self) -> Iterator[TransferStep]: + if self._strategy.new_tip == types.TransferTipPolicy.ONCE: + yield self._format_dict("pick_up_tip", kwargs=self._tip_opts) + yield from { + TransferMode.CONSOLIDATE: self._plan_consolidate, + TransferMode.DISTRIBUTE: self._plan_distribute, + TransferMode.TRANSFER: self._plan_transfer, + }[self._mode]() + if self._strategy.new_tip == types.TransferTipPolicy.ONCE: + if self._strategy.drop_tip_strategy == DropTipStrategy.RETURN: + yield self._format_dict("return_tip") + else: + yield self._format_dict("drop_tip") + + def _plan_transfer(self) -> Generator[TransferStep, None, None]: + """ + * **Source/ Dest:** Multiple sources to multiple destinations. + Src & dest should be equal length + + * **Volume:** Single volume or List of volumes is acceptable. This list + should be same length as sources/destinations + + * **Behavior with transfer options:** + + - New_tip: can be either NEVER or ONCE or ALWAYS + - Air_gap: if specified, will be performed after every aspirate + - Blow_out: can be performed after each dispense (after mix, before + touch_tip) at the location specified. If there is + liquid present in the tip (as in the case of nonzero + disposal volume), blow_out will be performed at either + user-defined location or (default) trash. + If no liquid is supposed to be present in the tip after + dispense, blow_out will be performed at dispense well + location (if blow out strategy is DEST) + - Touch_tip: can be performed after each aspirate and/or after + each dispense + - Mix: can be performed before aspirate and/or after dispense + if there is no disposal volume (i.e. can be performed + only when the tip is supposed to be empty) + + Considering all options, the sequence of actions is: + *New Tip -> Mix -> Aspirate (with disposal volume) -> Air gap -> + -> Touch tip -> Dispense air gap -> Dispense -> Mix if empty -> + -> Blow out -> Touch tip -> Drop tip* + """ + # reform source target lists + sources, dests = self._extend_source_target_lists(self._sources, self._dests) + tx_commons.check_valid_volume_parameters( + disposal_volume=self._strategy.disposal_volume, + air_gap=self._strategy.air_gap, + max_volume=self._instr.max_volume, + ) + plan_iter = tx_commons.expand_for_volume_constraints( + self._volumes, + zip(sources, dests), + self._instr.max_volume + - self._strategy.disposal_volume + - self._strategy.air_gap, + ) + for step_vol, (src, dest) in plan_iter: + if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS: + yield self._format_dict("pick_up_tip", kwargs=self._tip_opts) + max_vol = ( + self._max_volume + - self._strategy.disposal_volume + - self._strategy.air_gap + ) + xferred_vol = 0.0 + while xferred_vol < step_vol: + # TODO: account for unequal length sources, dests + # TODO: ensure last transfer is > min_vol + vol = min(max_vol, step_vol - xferred_vol) + yield from self._aspirate_actions(vol, src) + yield from self._dispense_actions(vol=vol, dest=dest, src=src) + xferred_vol += vol + yield from self._new_tip_action() + + @staticmethod + def _extend_source_target_lists( + sources: List[Union[Well, types.Location]], + targets: List[Union[Well, types.Location]], + ) -> Tuple[List[Union[Well, types.Location]], List[Union[Well, types.Location]]]: + """Extend source or target list to match the length of the other""" + if len(sources) < len(targets): + if len(targets) % len(sources) != 0: + raise ValueError("Source and destination lists must be divisible") + sources = [ + source + for source in sources + for i in range(int(len(targets) / len(sources))) + ] + elif len(sources) > len(targets): + if len(sources) % len(targets) != 0: + raise ValueError("Source and destination lists must be divisible") + targets = [ + target + for target in targets + for i in range(int(len(sources) / len(targets))) + ] + return sources, targets + + def _plan_distribute(self) -> Generator[TransferStep, None, None]: + """ + * **Source/ Dest:** One source to many destinations + * **Volume:** Single volume or List of volumes is acceptable. This list + should be same length as destinations + * **Behavior with transfer options:** + + - New_tip: can be either NEVER or ONCE + (ALWAYS will fallback to ONCE) + - Air_gap: if specified, will be performed after every aspirate and + also in-between dispenses (to keep air gap while moving + between wells) + - Blow_out: can be performed at the end of distribute (after mix, + before touch_tip) at the location specified. If there + is liquid present in the tip, blow_out will be + performed at either user-defined location or (default) + trash. If no liquid is supposed to be present in the + tip at the end of distribute, blow_out will be + performed at the last well the liquid was dispensed to + (if strategy is DEST) + - Touch_tip: can be performed after each aspirate and/or after + every dispense + - Mix: can be performed before aspirate and/or after the last + dispense if there is no disposal volume (i.e. can be + performed only when the tip is supposed to be empty) + + Considering all options, the sequence of actions is: + + 1. Going from source to dest1: + *New Tip -> Mix -> Aspirate (with disposal volume) -> Air gap -> + -> Touch tip -> Dispense air gap -> Dispense -> Mix if empty -> + -> Blow out -> Touch tip -> Drop tip* + 2. Going from destn to destn+1: + *.. Dispense air gap -> Dispense -> Touch tip -> Air gap -> + .. Dispense air gap -> ...* + + """ + + tx_commons.check_valid_volume_parameters( + disposal_volume=self._strategy.disposal_volume, + air_gap=self._strategy.air_gap, + max_volume=self._instr.max_volume, + ) + + # TODO: decide whether default disposal vol for distribute should be + # pipette min_vol or should we leave it to being 0 by default and + # recommend users to specify a disposal vol when using distribute. + # First method keeps distribute consistent with current behavior while + # the other maintains consistency in default behaviors of all functions + plan_iter = tx_commons.expand_for_volume_constraints( + self._volumes, + self._dests, + # todo(mm, 2021-03-09): Is it right for this to be + # _instr_.max_volume? Does/should this take the tip maximum volume + # into account? + self._instr.max_volume + - self._strategy.disposal_volume + - self._strategy.air_gap, + ) + + done = False + current_xfer = next(plan_iter) + if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS: + yield self._format_dict("pick_up_tip", kwargs=self._tip_opts) + while not done: + asp_grouped: List[Tuple[float, Well | types.Location]] = [] + try: + while ( + sum(a[0] for a in asp_grouped) + + self._strategy.disposal_volume + + self._strategy.air_gap + + current_xfer[0] + ) <= self._max_volume: + append_xfer = self._check_volume_not_zero( + self._api_version, current_xfer[0] + ) + if append_xfer: + asp_grouped.append(current_xfer) + current_xfer = next(plan_iter) + except StopIteration: + done = True + if not asp_grouped: + break + + yield from self._aspirate_actions( + sum(a[0] for a in asp_grouped) + self._strategy.disposal_volume, + self._sources[0], + ) + for step in asp_grouped: + + yield from self._dispense_actions( + vol=step[0], + src=self._sources[0], + dest=step[1], + is_disp_next=step is not asp_grouped[-1], + ) + yield from self._new_tip_action() + + def _plan_consolidate(self) -> Generator[TransferStep, None, None]: + """ + * **Source/ Dest:** Many sources to one destination + * **Volume:** Single volume or List of volumes is acceptable. This list + should be same length as sources + * **Behavior with transfer options:** + + - New_tip: can be either NEVER or ONCE + (ALWAYS will fallback to ONCE) + - Air_gap: if specified, will be performed after every aspirate + so that the aspirated liquids do not mix inside the tip. + The air gap will be dispensed while dispensing the + liquid into the destination well. + - Blow_out: can be performed after a dispense (after mix, + before touch_tip) at the location specified. If there + is liquid present in the tip (which shouldn't happen + since consolidate doesn't take a disposal vol, yet), + blow_out will be performed at either user-defined + location or (default) trash. + If no liquid is supposed to be present in the tip after + dispense, blow_out will be performed at dispense well + loc (if blow out strategy is DEST) + - Touch_tip: can be performed after each aspirate and/or after + dispense + - Mix: can be performed before the first aspirate and/or after + dispense if there is no disposal volume (i.e. can be + performed only when the tip is supposed to be empty) + + Considering all options, the sequence of actions is: + 1. Going from source to dest1: + *New Tip -> Mix -> Aspirate (with disposal volume?) -> Air gap + -> Touch tip -> Dispense air gap -> Dispense -> Mix if empty -> + -> Blow out -> Touch tip -> Drop tip* + 2. Going from source(n) to source(n+1): + *.. Aspirate -> Air gap -> Touch tip ->.. + .. Aspirate -> .....* + """ + # TODO: verify if _check_valid_volume_parameters should be re-enabled here + # self._check_valid_volume_parameters( + # disposal_volume=self._strategy.disposal_volume, + # air_gap=self._strategy.air_gap, + # max_volume=self._instr.max_volume, + # ) + plan_iter = tx_commons.expand_for_volume_constraints( + # todo(mm, 2021-03-09): Is it right to use _instr.max_volume here? + # Why don't we account for tip max volume, disposal volume, or air + # gap? + self._volumes, + self._sources, + self._instr.max_volume, + ) + current_xfer = next(plan_iter) + if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS: + yield self._format_dict("pick_up_tip", kwargs=self._tip_opts) + done = False + while not done: + asp_grouped: List[Tuple[float, Union[Well, types.Location]]] = [] + try: + while ( + sum([a[0] for a in asp_grouped]) + + self._strategy.disposal_volume + + self._strategy.air_gap * len(asp_grouped) + + current_xfer[0] + ) <= self._max_volume: + append_xfer = self._check_volume_not_zero( + self._api_version, current_xfer[0] + ) + if append_xfer: + asp_grouped.append(current_xfer) + current_xfer = next(plan_iter) + except StopIteration: + done = True + if not asp_grouped: + break + # Q: What accounts as disposal volume in a consolidate action? + # yield self._format_dict('aspirate', + # self._strategy.disposal_volume, loc) + for step in asp_grouped: + yield from self._aspirate_actions(step[0], step[1]) + yield from self._dispense_actions( + vol=sum([a[0] + self._strategy.air_gap for a in asp_grouped]) + - self._strategy.air_gap, + src=None, + dest=self._dests[0], + ) + yield from self._new_tip_action() + + def _aspirate_actions( + self, vol: float, loc: Union[Well, types.Location] + ) -> Generator[TransferStep, None, None]: + yield from self._before_aspirate(loc) + yield self._format_dict("aspirate", [vol, loc, self._options.aspirate.rate]) + yield from self._after_aspirate() + + def _dispense_actions( + self, + vol: float, + dest: Union[Well, types.Location], + src: Optional[Union[Well, types.Location]] = None, + is_disp_next: bool = False, + ) -> Generator[TransferStep, None, None]: + if self._strategy.air_gap: + vol += self._strategy.air_gap + yield self._format_dict("dispense", [vol, dest, self._options.dispense.rate]) + yield from self._after_dispense(dest=dest, src=src, is_disp_next=is_disp_next) + + def _before_aspirate( + self, loc: Union[Well, types.Location] + ) -> Generator[TransferStep, None, None]: + if ( + self._strategy.mix_strategy == MixStrategy.BEFORE + or self._strategy.mix_strategy == MixStrategy.BOTH + ): + if self._instr.current_volume == 0: + mix_before_opts = self._mix_before_opts._asdict() + mix_before_opts["location"] = loc + yield self._format_dict("mix", kwargs=mix_before_opts) + + def _after_aspirate(self) -> Generator[TransferStep, None, None]: + if self._strategy.air_gap: + yield self._format_dict("air_gap", [self._strategy.air_gap]) + if self._strategy.touch_tip_strategy == TouchTipStrategy.ALWAYS: + yield self._format_dict("touch_tip", kwargs=self._touch_tip_opts) + + def _after_dispense_trash(self) -> Generator[TransferStep, None, None]: + if isinstance(self._instr.trash_container, Labware): + yield self._format_dict( + "blow_out", [self._instr.trash_container.wells()[0]] + ) + else: + yield self._format_dict("blow_out", [self._instr.trash_container]) + + def _after_dispense_helper(self) -> Generator[TransferStep, None, None]: + # Used by distribute + if self._strategy.air_gap: + yield self._format_dict("air_gap", [self._strategy.air_gap]) + if self._strategy.touch_tip_strategy == TouchTipStrategy.ALWAYS: + yield self._format_dict("touch_tip", kwargs=self._touch_tip_opts) + + def _after_dispense( + self, + dest: Union[Well, types.Location], + src: Optional[Union[types.Location, Well]], + is_disp_next: bool = False, + ) -> Generator[TransferStep, None, None]: + # This sequence of actions is subject to change + if not is_disp_next: + # If the next command is an aspirate, we are switching + # between aspirate and dispense. + if self._instr.current_volume == 0: + # If we're empty, then this is when after mixes come into play + if ( + self._strategy.mix_strategy == MixStrategy.AFTER + or self._strategy.mix_strategy == MixStrategy.BOTH + ): + mix_after_opts = self._mix_after_opts._asdict() + mix_after_opts["location"] = dest + yield self._format_dict("mix", kwargs=mix_after_opts) + if self._strategy.touch_tip_strategy == TouchTipStrategy.ALWAYS: + yield self._format_dict("touch_tip", kwargs=self._touch_tip_opts) + + if self._strategy.blow_out_strategy == BlowOutStrategy.SOURCE: + yield self._format_dict("blow_out", [src]) + elif self._strategy.blow_out_strategy == BlowOutStrategy.DEST: + yield self._format_dict("blow_out", [dest]) + elif self._strategy.blow_out_strategy == BlowOutStrategy.CUSTOM_LOCATION: + yield self._format_dict("blow_out", kwargs=self._blow_opts) + elif ( + self._strategy.blow_out_strategy == BlowOutStrategy.TRASH + or self._strategy.disposal_volume + ): + yield from self._after_dispense_trash() + else: + yield from self._after_dispense_helper() + + def _new_tip_action(self) -> Generator[TransferStep, None, None]: + if self._strategy.new_tip == types.TransferTipPolicy.ALWAYS: + if self._strategy.drop_tip_strategy == DropTipStrategy.RETURN: + yield self._format_dict("return_tip") + else: + yield self._format_dict("drop_tip") + + def _format_dict( + self, + method: str, + args: Optional[List[Any]] = None, + kwargs: Optional[Union[Dict[Any, Any], FormatDictArgs]] = None, + ) -> TransferStep: + if kwargs: + if isinstance(kwargs, Dict): + params = {key: val for key, val in kwargs.items() if val} + else: + params = {key: val for key, val in kwargs._asdict().items() if val} + else: + params = {} + if not args: + args = [] + return {"method": method, "args": args, "kwargs": params} + + def _create_volume_list( + self, volume: Union[Union[float, int], Sequence[float]], total_xfers: int + ) -> List[float]: + if isinstance(volume, (float, int)): + return [float(volume)] * total_xfers + elif isinstance(volume, tuple): + return self._create_volume_gradient( + volume[0], volume[-1], total_xfers, self._strategy.gradient_function + ) + else: + if not isinstance(volume, List): + raise TypeError( + "Volume expected as a number or List or" + " tuple but got {}".format(volume) + ) + elif not len(volume) == total_xfers: + raise RuntimeError( + "List of volumes should be equal to number " "of transfers" + ) + return volume + + @staticmethod + def _create_volume_gradient( + min_v: float, + max_v: float, + total: int, + gradient: Optional[Callable[[float], float]] = None, + ) -> List[float]: + + diff_vol = max_v - min_v + + def _map_volume(i: int) -> float: + nonlocal diff_vol, total + rel_x = i / (total - 1) + rel_y = gradient(rel_x) if gradient else rel_x + return (rel_y * diff_vol) + min_v + + return [_map_volume(i) for i in range(total)] + + def _check_valid_well_list( + self, well_list: List[Any], id: str, old_well_list: List[Any] + ) -> None: + if self._api_version >= APIVersion(2, 2) and len(well_list) < 1: + raise RuntimeError( + f"Invalid {id} for multichannel transfer: {old_well_list}" + ) + + @staticmethod + def _check_volume_not_zero(api_version: APIVersion, volume: float) -> bool: + # We should only be adding volumes to transfer plans if it is + # greater than zero to prevent extraneous robot movements. + if api_version < APIVersion(2, 8): + return True + elif volume > 0: + return True + return False + + def _multichannel_transfer( + self, s: AdvancedLiquidHandling, d: AdvancedLiquidHandling + ) -> Tuple[List[Union[Well, types.Location]], List[Union[Well, types.Location]]]: + # TODO: add a check for container being multi-channel compatible? + # Helper function for multi-channel use-case + assert ( + isinstance(s, Well) + or isinstance(s, types.Location) + or (isinstance(s, List) and isinstance(s[0], Well)) + or (isinstance(s, List) and isinstance(s[0], List)) + or (isinstance(s, List) and isinstance(s[0], types.Location)) + ), "Source should be a Well or List[Well] but is {}".format(s) + assert ( + isinstance(d, Well) + or isinstance(d, types.Location) + or (isinstance(d, List) and isinstance(d[0], Well)) + or (isinstance(d, List) and isinstance(d[0], List)) + or (isinstance(d, List) and isinstance(d[0], types.Location)) + ), "Target should be a Well or List[Well] but is {}".format(d) + + # TODO: Account for cases where a src/dest list has a non-first-row + # well (eg, 'B1') and would expect the robot/pipette to + # understand that it is referring to the whole first column + if isinstance(s, List) and isinstance(s[0], List): + # s is a List[List]]; flatten to 1D list + s = [well for list_elem in s for well in list_elem] + elif isinstance(s, Well) or isinstance(s, types.Location): + s = [s] + new_src = [] + for well in s: + if self._is_valid_row(well): + new_src.append(well) + self._check_valid_well_list(new_src, "source", s) + + if isinstance(d, List) and isinstance(d[0], List): + # s is a List[List]]; flatten to 1D list + d = [well for list_elem in d for well in list_elem] + elif isinstance(d, Well) or isinstance(d, types.Location): + d = [d] + new_dst = [] + for well in d: + if self._is_valid_row(well): + new_dst.append(well) + self._check_valid_well_list(new_dst, "target", d) + return new_src, new_dst + + def _is_valid_row(self, well: Union[Well, types.Location]) -> bool: + if isinstance(well, types.Location): + test_well = well.labware.as_well() + else: + test_well = well + + if self._api_version < APIVersion(2, 2): + return test_well in test_well.parent.rows()[0] + else: + # Allow the first 2 rows to be accessible to 384-well plates; + # otherwise, only the first row is accessible + if test_well.parent.parameters["format"] == "384Standard": + valid_wells = [ + well for row in test_well.parent.rows()[:2] for well in row + ] + return test_well in valid_wells + else: + return test_well in test_well.parent.rows()[0] diff --git a/api/src/opentrons/protocols/api_support/constants.py b/api/src/opentrons/protocols/api_support/constants.py index b350d970055..8da286acb62 100644 --- a/api/src/opentrons/protocols/api_support/constants.py +++ b/api/src/opentrons/protocols/api_support/constants.py @@ -4,5 +4,5 @@ OPENTRONS_NAMESPACE = "opentrons" CUSTOM_NAMESPACE = "custom_beta" -STANDARD_DEFS_PATH = Path("labware/definitions/2") +STANDARD_DEFS_PATH = Path("labware/definitions") USER_DEFS_PATH = get_opentrons_path("labware_user_definitions_dir_v2") diff --git a/api/src/opentrons/protocols/api_support/definitions.py b/api/src/opentrons/protocols/api_support/definitions.py index 799af1993f3..e2f6aee1a2a 100644 --- a/api/src/opentrons/protocols/api_support/definitions.py +++ b/api/src/opentrons/protocols/api_support/definitions.py @@ -1,6 +1,6 @@ from .types import APIVersion -MAX_SUPPORTED_VERSION = APIVersion(2, 20) +MAX_SUPPORTED_VERSION = APIVersion(2, 22) """The maximum supported protocol API version in this release.""" MIN_SUPPORTED_VERSION = APIVersion(2, 0) diff --git a/api/src/opentrons/protocols/api_support/instrument.py b/api/src/opentrons/protocols/api_support/instrument.py index 0137b43a4c8..3299b8512f9 100644 --- a/api/src/opentrons/protocols/api_support/instrument.py +++ b/api/src/opentrons/protocols/api_support/instrument.py @@ -73,7 +73,7 @@ def tip_length_for( VALID_PIP_TIPRACK_VOL = { - "FLEX": {"p50": [50], "p1000": [50, 200, 1000]}, + "FLEX": {"p50": [50], "p200": [50, 200], "p1000": [50, 200, 1000]}, "OT2": { "p10": [10, 20], "p20": [10, 20], diff --git a/api/src/opentrons/protocols/api_support/types.py b/api/src/opentrons/protocols/api_support/types.py index 6d3af89bcf9..d16fa8ddf73 100644 --- a/api/src/opentrons/protocols/api_support/types.py +++ b/api/src/opentrons/protocols/api_support/types.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import NamedTuple +from typing import NamedTuple, TypedDict class APIVersion(NamedTuple): @@ -17,3 +17,16 @@ def from_string(cls, inp: str) -> APIVersion: def __str__(self) -> str: return f"{self.major}.{self.minor}" + + +class ThermocyclerStepBase(TypedDict): + """Required elements of a thermocycler step: the temperature.""" + + temperature: float + + +class ThermocyclerStep(ThermocyclerStepBase, total=False): + """Optional elements of a thermocycler step: the hold time. One of these must be present.""" + + hold_time_seconds: float + hold_time_minutes: float diff --git a/api/src/opentrons/protocols/api_support/util.py b/api/src/opentrons/protocols/api_support/util.py index 3438692de2f..da4ceff7360 100644 --- a/api/src/opentrons/protocols/api_support/util.py +++ b/api/src/opentrons/protocols/api_support/util.py @@ -391,13 +391,3 @@ def _check_version_wrapper(*args: Any, **kwargs: Any) -> Any: return cast(FuncT, _check_version_wrapper) return _set_version - - -class ModifiedList(list[str]): - def __contains__(self, item: object) -> bool: - if not isinstance(item, str): - return False - for name in self: - if name == item.replace("-", "_").lower(): - return True - return False diff --git a/api/src/opentrons/protocols/labware.py b/api/src/opentrons/protocols/labware.py index 02f617fd72c..ed1b7d15219 100644 --- a/api/src/opentrons/protocols/labware.py +++ b/api/src/opentrons/protocols/labware.py @@ -2,14 +2,12 @@ import logging import json -import os from pathlib import Path -from typing import Any, AnyStr, List, Dict, Optional, Union +from typing import Any, AnyStr, Dict, Optional, Union import jsonschema # type: ignore -from opentrons.protocols.api_support.util import ModifiedList from opentrons_shared_data import load_shared_data, get_shared_data_root from opentrons.protocols.api_support.constants import ( OPENTRONS_NAMESPACE, @@ -63,29 +61,6 @@ def get_labware_definition( return _get_standard_labware_definition(load_name, namespace, version) -def get_all_labware_definitions() -> List[str]: - """ - Return a list of standard and custom labware definitions with load_name + - name_space + version existing on the robot - """ - labware_list = ModifiedList() - - def _check_for_subdirectories(path: Union[str, Path, os.DirEntry[str]]) -> None: - with os.scandir(path) as top_path: - for sub_dir in top_path: - if sub_dir.is_dir(): - labware_list.append(sub_dir.name) - - # check for standard labware - _check_for_subdirectories(get_shared_data_root() / STANDARD_DEFS_PATH) - - # check for custom labware - for namespace in os.scandir(USER_DEFS_PATH): - _check_for_subdirectories(namespace) - - return labware_list - - def save_definition( labware_def: LabwareDefinition, force: bool = False, location: Optional[Path] = None ) -> None: @@ -114,7 +89,6 @@ def save_definition( f'Saving definitions to the "{OPENTRONS_NAMESPACE}" namespace ' + "is not permitted" ) - def_path = _get_path_to_labware(load_name, namespace, version, location) if not force and def_path.is_file(): @@ -219,7 +193,6 @@ def _get_standard_labware_definition( Definitions Folder from the Opentrons App before uploading your protocol. """ - if namespace is None: for fallback_namespace in [OPENTRONS_NAMESPACE, CUSTOM_NAMESPACE]: try: @@ -252,9 +225,21 @@ def _get_path_to_labware( ) -> Path: if namespace == OPENTRONS_NAMESPACE: # all labware in OPENTRONS_NAMESPACE is stored in shared data - return ( - get_shared_data_root() / STANDARD_DEFS_PATH / load_name / f"{version}.json" + schema_3_path = ( + get_shared_data_root() + / STANDARD_DEFS_PATH + / "3" + / load_name + / f"{version}.json" + ) + schema_2_path = ( + get_shared_data_root() + / STANDARD_DEFS_PATH + / "2" + / load_name + / f"{version}.json" ) + return schema_3_path if schema_3_path.exists() else schema_2_path if not base_path: base_path = USER_DEFS_PATH def_path = base_path / namespace / load_name / f"{version}.json" diff --git a/api/src/opentrons/protocols/parameters/csv_parameter_interface.py b/api/src/opentrons/protocols/parameters/csv_parameter_interface.py index a1b9e7b4df7..ff460b48f21 100644 --- a/api/src/opentrons/protocols/parameters/csv_parameter_interface.py +++ b/api/src/opentrons/protocols/parameters/csv_parameter_interface.py @@ -60,7 +60,9 @@ def parse_as_csv( as appropriate. :param detect_dialect: If ``True``, examine the file and try to assign it a - :py:class:`csv.Dialect` to improve parsing behavior. + :py:class:`csv.Dialect` to improve parsing behavior. Set this to ``False`` + when using the file output of :py:meth:`.AbsorbanceReaderContext.read` as + a runtime parameter. :param kwargs: For advanced CSV handling, you can pass any of the `formatting parameters `_ accepted by :py:func:`csv.reader` from the Python standard library. @@ -84,4 +86,11 @@ def parse_as_csv( rows.append(row) except (UnicodeDecodeError, csv.Error): raise ParameterValueError("Cannot parse provided CSV contents.") + return self._remove_trailing_empty_rows(rows) + + @staticmethod + def _remove_trailing_empty_rows(rows: List[List[str]]) -> List[List[str]]: + """Removes any trailing empty rows.""" + while rows and rows[-1] == []: + rows.pop() return rows diff --git a/api/src/opentrons/simulate.py b/api/src/opentrons/simulate.py index 62806edb048..e565bab83e0 100644 --- a/api/src/opentrons/simulate.py +++ b/api/src/opentrons/simulate.py @@ -815,6 +815,7 @@ def _create_live_context_pe( robot_type, use_pe_virtual_hardware=use_pe_virtual_hardware ), deck_configuration=None, + file_provider=None, error_recovery_policy=error_recovery_policy.never_recover, drop_tips_after_run=False, post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE, @@ -883,8 +884,6 @@ def _run_file_non_pe( context.home() with scraper.scrape(): try: - # TODO (spp, 2024-03-18): use true run-time param overrides once enabled - # for cli protocol simulation/ execution execute.run_protocol( protocol, context, run_time_parameters_with_overrides=None ) @@ -914,6 +913,7 @@ def _run_file_pe( log_level: str, ) -> _SimulateResult: """Run a protocol file with Protocol Engine.""" + # TODO (spp, 2024-03-18): use run-time param overrides once enabled for cli protocol simulation. async def run(protocol_source: ProtocolSource) -> _SimulateResult: hardware_api_wrapped = hardware_api.wrapped() diff --git a/api/src/opentrons/types.py b/api/src/opentrons/types.py index 324b6a23d23..1f73d63c8c6 100644 --- a/api/src/opentrons/types.py +++ b/api/src/opentrons/types.py @@ -1,7 +1,17 @@ from __future__ import annotations import enum from math import sqrt, isclose -from typing import TYPE_CHECKING, Any, NamedTuple, Iterator, Union, List +from typing import ( + TYPE_CHECKING, + Any, + NamedTuple, + Iterator, + Union, + List, + Optional, + Protocol, + Dict, +) from opentrons_shared_data.robot.types import RobotType @@ -79,7 +89,9 @@ def magnitude_to(self, other: Any) -> float: class Location: - """A location to target as a motion. + """Location(point: Point, labware: Union["Labware", "Well", str, "ModuleGeometry", LabwareLike, None, "ModuleContext"]) + + A location to target as a motion. The location contains a :py:class:`.Point` (in :ref:`protocol-api-deck-coords`) and possibly an associated @@ -116,10 +128,13 @@ def __init__( None, "ModuleContext", ], + *, + _ot_internal_is_meniscus: Optional[bool] = None, ): self._point = point self._given_labware = labware self._labware = LabwareLike(labware) + self._is_meniscus = _ot_internal_is_meniscus # todo(mm, 2021-10-01): Figure out how to get .point and .labware to show up # in the rendered docs, and then update the class docstring to use cross-references. @@ -132,6 +147,10 @@ def point(self) -> Point: def labware(self) -> LabwareLike: return self._labware + @property + def is_meniscus(self) -> Optional[bool]: + return self._is_meniscus + def __iter__(self) -> Iterator[Union[Point, LabwareLike]]: """Iterable interface to support unpacking. Like a tuple. @@ -148,6 +167,7 @@ def __eq__(self, other: object) -> bool: isinstance(other, Location) and other._point == self._point and other._labware == self._labware + and other._is_meniscus == self._is_meniscus ) def move(self, point: Point) -> "Location": @@ -173,7 +193,7 @@ def move(self, point: Point) -> "Location": return Location(point=self.point + point, labware=self._given_labware) def __repr__(self) -> str: - return f"Location(point={repr(self._point)}, labware={self._labware})" + return f"Location(point={repr(self._point)}, labware={self._labware}, is_meniscus={self._is_meniscus if self._is_meniscus is not None else False})" # TODO(mc, 2020-10-22): use MountType implementation for Mount @@ -243,6 +263,75 @@ class OT3MountType(str, enum.Enum): GRIPPER = "gripper" +class AxisType(enum.Enum): + X = "X" # gantry + Y = "Y" + Z_L = "Z_L" # left pipette mount Z + Z_R = "Z_R" # right pipette mount Z + Z_G = "Z_G" # gripper mount Z + P_L = "P_L" # left pipette plunger + P_R = "P_R" # right pipette plunger + Q = "Q" # hi-throughput pipette tiprack grab + G = "G" # gripper grab + + @classmethod + def axis_for_mount(cls, mount: Mount) -> "AxisType": + map_axis_to_mount = { + Mount.LEFT: cls.Z_L, + Mount.RIGHT: cls.Z_R, + Mount.EXTENSION: cls.Z_G, + } + return map_axis_to_mount[mount] + + @classmethod + def mount_for_axis(cls, axis: "AxisType") -> Mount: + map_mount_to_axis = { + cls.Z_L: Mount.LEFT, + cls.Z_R: Mount.RIGHT, + cls.Z_G: Mount.EXTENSION, + } + return map_mount_to_axis[axis] + + @classmethod + def plunger_axis_for_mount(cls, mount: Mount) -> "AxisType": + map_plunger_axis_mount = {Mount.LEFT: cls.P_L, Mount.RIGHT: cls.P_R} + return map_plunger_axis_mount[mount] + + @classmethod + def ot2_axes(cls) -> List["AxisType"]: + return [ + AxisType.X, + AxisType.Y, + AxisType.Z_L, + AxisType.Z_R, + AxisType.P_L, + AxisType.P_R, + ] + + @classmethod + def flex_gantry_axes(cls) -> List["AxisType"]: + return [ + AxisType.X, + AxisType.Y, + AxisType.Z_L, + AxisType.Z_R, + AxisType.Z_G, + ] + + @classmethod + def ot2_gantry_axes(cls) -> List["AxisType"]: + return [ + AxisType.X, + AxisType.Y, + AxisType.Z_L, + AxisType.Z_R, + ] + + +AxisMapType = Dict[AxisType, float] +StringAxisMap = Dict[str, float] + + # TODO(mc, 2020-11-09): this makes sense in shared-data or other common # model library # https://github.com/Opentrons/opentrons/pull/6943#discussion_r519029833 @@ -416,3 +505,84 @@ class TransferTipPolicy(enum.Enum): DeckLocation = Union[int, str] ALLOWED_PRIMARY_NOZZLES = ["A1", "H1", "A12", "H12"] + + +class NozzleConfigurationType(enum.Enum): + """Short names for types of nozzle configurations. + + Represents the current nozzle configuration stored in a NozzleMap. + """ + + COLUMN = "COLUMN" + ROW = "ROW" + SINGLE = "SINGLE" + FULL = "FULL" + SUBRECT = "SUBRECT" + + +class NozzleMapInterface(Protocol): + """ + A NozzleMap instance represents a specific configuration of active nozzles on a pipette. + + It exposes properties of the configuration like the configuration's front-right, front-left, + back-left and starting nozzles as well as a map of all the nozzles active in the configuration. + + Because NozzleMaps represent configurations directly, the properties of the NozzleMap may not + match the properties of the physical pipette. For instance, a NozzleMap for a single channel + configuration of an 8-channel pipette - say, A1 only - will have its front left, front right, + and active channels all be A1, while the physical configuration would have the front right + channel be H1. + """ + + @property + def starting_nozzle(self) -> str: + """The nozzle that automated operations that count nozzles should start at.""" + ... + + @property + def rows(self) -> dict[str, list[str]]: + """A map of all the rows active in this configuration.""" + ... + + @property + def columns(self) -> dict[str, list[str]]: + """A map of all the columns active in this configuration.""" + ... + + @property + def back_left(self) -> str: + """The backest, leftest (i.e. back if it's a column, left if it's a row) nozzle of the configuration. + + Note: This is the value relevant for this particular configuration, and it may not represent the back left nozzle + of the underlying physical pipette. For instance, the back-left nozzle of a configuration representing nozzles + D7 to H12 of a 96-channel pipette is D7, which is not the back-left nozzle of the physical pipette (A1). + """ + ... + + @property + def configuration(self) -> NozzleConfigurationType: + """The kind of configuration represented by this nozzle map.""" + ... + + @property + def front_right(self) -> str: + """The frontest, rightest (i.e. front if it's a column, right if it's a row) nozzle of the configuration. + + Note: This is the value relevant for this configuration, not the physical pipette. See the note on back_left. + """ + ... + + @property + def tip_count(self) -> int: + """The total number of active nozzles in the configuration, and thus the number of tips that will be picked up.""" + ... + + @property + def physical_nozzle_count(self) -> int: + """The number of actual physical nozzles on the pipette, regardless of configuration.""" + ... + + @property + def active_nozzles(self) -> list[str]: + """An unstructured list of all nozzles active in the configuration.""" + ... diff --git a/api/src/opentrons/util/logging_config.py b/api/src/opentrons/util/logging_config.py index e9a4d2042a2..f9a59799d9d 100644 --- a/api/src/opentrons/util/logging_config.py +++ b/api/src/opentrons/util/logging_config.py @@ -5,10 +5,17 @@ from opentrons.config import CONFIG, ARCHITECTURE, SystemArchitecture +if ARCHITECTURE is SystemArchitecture.YOCTO: + from opentrons_hardware.sensors import SENSOR_LOG_NAME +else: + # we don't use the sensor log on ot2 or host + SENSOR_LOG_NAME = "unused" + def _host_config(level_value: int) -> Dict[str, Any]: serial_log_filename = CONFIG["serial_log_file"] api_log_filename = CONFIG["api_log_file"] + sensor_log_filename = CONFIG["sensor_log_file"] return { "version": 1, "disable_existing_loggers": False, @@ -29,7 +36,7 @@ def _host_config(level_value: int) -> Dict[str, Any]: "class": "logging.handlers.RotatingFileHandler", "formatter": "basic", "filename": serial_log_filename, - "maxBytes": 5000000, + "maxBytes": 1000000, "level": logging.DEBUG, "backupCount": 3, }, @@ -41,6 +48,14 @@ def _host_config(level_value: int) -> Dict[str, Any]: "level": logging.DEBUG, "backupCount": 5, }, + "sensor": { + "class": "logging.handlers.RotatingFileHandler", + "formatter": "basic", + "filename": sensor_log_filename, + "maxBytes": 1000000, + "level": logging.DEBUG, + "backupCount": 5, + }, }, "loggers": { "opentrons": { @@ -66,6 +81,11 @@ def _host_config(level_value: int) -> Dict[str, Any]: "level": logging.DEBUG, "propagate": False, }, + SENSOR_LOG_NAME: { + "handlers": ["sensor"], + "level": logging.DEBUG, + "propagate": False, + }, "__main__": {"handlers": ["api"], "level": level_value}, }, } @@ -75,6 +95,7 @@ def _buildroot_config(level_value: int) -> Dict[str, Any]: # Import systemd.journald here since it is generally unavailble on non # linux systems and we probably don't want to use it on linux desktops # either + sensor_log_filename = CONFIG["sensor_log_file"] return { "version": 1, "disable_existing_loggers": False, @@ -106,6 +127,14 @@ def _buildroot_config(level_value: int) -> Dict[str, Any]: "formatter": "message_only", "SYSLOG_IDENTIFIER": "opentrons-api-serial-usbbin", }, + "sensor": { + "class": "logging.handlers.RotatingFileHandler", + "formatter": "message_only", + "filename": sensor_log_filename, + "maxBytes": 1000000, + "level": logging.DEBUG, + "backupCount": 3, + }, }, "loggers": { "opentrons.drivers.asyncio.communication.serial_connection": { @@ -131,6 +160,11 @@ def _buildroot_config(level_value: int) -> Dict[str, Any]: "level": logging.DEBUG, "propagate": False, }, + SENSOR_LOG_NAME: { + "handlers": ["sensor"], + "level": logging.DEBUG, + "propagate": False, + }, "__main__": {"handlers": ["api"], "level": level_value}, }, } diff --git a/api/tests/opentrons/cli/test_cli.py b/api/tests/opentrons/cli/test_cli.py index 79d46dc1000..717e2ac6a2d 100644 --- a/api/tests/opentrons/cli/test_cli.py +++ b/api/tests/opentrons/cli/test_cli.py @@ -28,7 +28,11 @@ class _AnalysisCLIResult: def _get_analysis_result( - protocol_files: List[Path], output_type: str, check: bool = False + protocol_files: List[Path], + output_type: str, + check: bool = False, + rtp_values: Optional[str] = None, + rtp_files: Optional[str] = None, ) -> _AnalysisCLIResult: """Run `protocol_files` as a single protocol through the analysis CLI. @@ -41,11 +45,16 @@ def _get_analysis_result( with tempfile.TemporaryDirectory() as temp_dir: analysis_output_file = Path(temp_dir) / "analysis_output.json" runner = CliRunner() - args = [ - output_type, - str(analysis_output_file), - *[str(p.resolve()) for p in protocol_files], - ] + args = [output_type, str(analysis_output_file)] + + if rtp_values is not None: + args.extend(["--rtp-values", rtp_values]) + + if rtp_files is not None: + args.extend(["--rtp-files", rtp_files]) + + args.extend([str(p.resolve()) for p in protocol_files]) + if check: args.append("--check") @@ -262,6 +271,61 @@ def test_python_error_line_numbers( assert error["detail"] == expected_detail +@pytest.mark.parametrize("output", ["--json-output", "--human-json-output"]) +def test_run_time_parameter_setting( + tmp_path: Path, + output: str, +) -> None: + """Test that a RTP can be set to a non default value for analysis. + + Also verify that analysis result contains all static data about the protocol. + """ + python_protocol_source = textwrap.dedent( + """\ + requirements = {"robotType": "OT-2", "apiLevel": "2.18"} + + def add_parameters(parameters): + parameters.add_bool( + display_name="Dry Run", + variable_name="dry_run", + default=False, + ) + def run(protocol): + pass + """ + ) + protocol_source_file = tmp_path / "protocol.py" + protocol_source_file.write_text(python_protocol_source, encoding="utf-8") + result = _get_analysis_result( + [protocol_source_file], output, rtp_values=json.dumps({"dry_run": True}) + ) + + assert result.exit_code == 0 + + assert result.json_output is not None + assert result.json_output["robotType"] == "OT-2 Standard" + assert result.json_output["result"] == AnalysisResult.OK + assert result.json_output["pipettes"] == [] + assert result.json_output["commands"] # There should be a home command + assert result.json_output["labware"] == [] + assert result.json_output["liquids"] == [] + assert result.json_output["modules"] == [] + assert result.json_output["config"] == { + "apiVersion": [2, 18], + "protocolType": "python", + } + assert result.json_output["files"] == [{"name": "protocol.py", "role": "main"}] + assert result.json_output["runTimeParameters"] == [ + { + "displayName": "Dry Run", + "variableName": "dry_run", + "type": "bool", + "value": True, + "default": False, + } + ] + + @pytest.mark.parametrize("output", ["--json-output", "--human-json-output"]) def test_run_time_parameter_error( tmp_path: Path, @@ -312,6 +376,64 @@ def run(protocol): ) +@pytest.mark.parametrize("output", ["--json-output", "--human-json-output"]) +def test_rtp_csv_file_setting( + tmp_path: Path, + output: str, +) -> None: + """Test that a CSV file can be set for analysis. + + Also verify that analysis result contains all static data about the protocol. + """ + python_protocol_source = textwrap.dedent( + """\ + requirements = {"robotType": "OT-2", "apiLevel": "2.20"} + + def add_parameters(parameters): + parameters.add_csv_file( + display_name="CSV File", + variable_name="csv_file", + ) + def run(protocol): + protocol.params.csv_file.contents + """ + ) + protocol_source_file = tmp_path / "protocol.py" + protocol_source_file.write_text(python_protocol_source, encoding="utf-8") + csv_source_file = tmp_path / "csv_file.csv" + csv_source_file.write_text("a,b,c", encoding="utf-8") + + result = _get_analysis_result( + [protocol_source_file], + output, + rtp_files=json.dumps({"csv_file": str(csv_source_file.resolve())}), + ) + + assert result.exit_code == 0 + + assert result.json_output is not None + assert result.json_output["robotType"] == "OT-2 Standard" + assert result.json_output["result"] == AnalysisResult.OK + assert result.json_output["pipettes"] == [] + assert result.json_output["commands"] # There should be a home command + assert result.json_output["labware"] == [] + assert result.json_output["liquids"] == [] + assert result.json_output["modules"] == [] + assert result.json_output["config"] == { + "apiVersion": [2, 20], + "protocolType": "python", + } + assert result.json_output["files"] == [{"name": "protocol.py", "role": "main"}] + assert result.json_output["runTimeParameters"] == [ + { + "displayName": "CSV File", + "variableName": "csv_file", + "type": "csv_file", + "file": {"id": "", "name": "csv_file.csv"}, + } + ] + + @pytest.mark.parametrize("output", ["--json-output", "--human-json-output"]) def test_file_required_error( tmp_path: Path, diff --git a/api/tests/opentrons/config/ot3_settings.py b/api/tests/opentrons/config/ot3_settings.py index 38353c05a3c..04370fd6c09 100644 --- a/api/tests/opentrons/config/ot3_settings.py +++ b/api/tests/opentrons/config/ot3_settings.py @@ -1,5 +1,3 @@ -from opentrons.config.types import OutputOptions - ot3_dummy_settings = { "name": "Marie Curie", "model": "OT-3 Standard", @@ -122,13 +120,11 @@ "plunger_speed": 10, "plunger_impulse_time": 0.2, "sensor_threshold_pascals": 17, - "output_option": OutputOptions.stream_to_csv, "aspirate_while_sensing": False, "z_overlap_between_passes_mm": 0.1, "plunger_reset_offset": 2.0, "samples_for_baselining": 20, "sample_time_sec": 0.004, - "data_files": {"PRIMARY": "/data/pressure_sensor_data.csv"}, }, "calibration": { "z_offset": { @@ -137,8 +133,6 @@ "max_overrun_distance_mm": 2, "speed_mm_per_s": 3, "sensor_threshold_pf": 4, - "output_option": OutputOptions.sync_only, - "data_files": None, }, }, "edge_sense": { @@ -149,8 +143,6 @@ "max_overrun_distance_mm": 5, "speed_mm_per_s": 6, "sensor_threshold_pf": 7, - "output_option": OutputOptions.sync_only, - "data_files": None, }, "search_initial_tolerance_mm": 18, "search_iteration_limit": 3, diff --git a/api/tests/opentrons/config/test_advanced_settings_migration.py b/api/tests/opentrons/config/test_advanced_settings_migration.py index 92a45a6d610..a2bcf71a1fb 100644 --- a/api/tests/opentrons/config/test_advanced_settings_migration.py +++ b/api/tests/opentrons/config/test_advanced_settings_migration.py @@ -8,7 +8,7 @@ @pytest.fixture def migrated_file_version() -> int: - return 35 + return 36 # make sure to set a boolean value in default_file_settings only if @@ -30,6 +30,7 @@ def default_file_settings() -> Dict[str, Any]: "enableErrorRecoveryExperiments": None, "enableOEMMode": None, "enablePerformanceMetrics": None, + "allowLiquidClasses": None, } @@ -68,6 +69,7 @@ def v2_config(v1_config: Dict[str, Any]) -> Dict[str, Any]: r.update( { "_version": 2, + "disableLogAggregation": None, } ) return r @@ -410,6 +412,26 @@ def v34_config(v33_config: Dict[str, Any]) -> Dict[str, Any]: return r +@pytest.fixture +def v35_config(v34_config: Dict[str, Any]) -> Dict[str, Any]: + r = v34_config.copy() + r.pop("disableLogAggregation") + r["_version"] = 35 + return r + + +@pytest.fixture +def v36_config(v35_config: Dict[str, Any]) -> Dict[str, Any]: + r = v35_config.copy() + r.update( + { + "_version": 36, + "allowLiquidClasses": None, + } + ) + return r + + @pytest.fixture( scope="session", params=[ @@ -449,6 +471,8 @@ def v34_config(v33_config: Dict[str, Any]) -> Dict[str, Any]: lazy_fixture("v32_config"), lazy_fixture("v33_config"), lazy_fixture("v34_config"), + lazy_fixture("v35_config"), + lazy_fixture("v36_config"), ], ) def old_settings(request: SubRequest) -> Dict[str, Any]: @@ -539,4 +563,5 @@ def test_ensures_config() -> None: "enableErrorRecoveryExperiments": None, "enableOEMMode": None, "enablePerformanceMetrics": None, + "allowLiquidClasses": None, } diff --git a/api/tests/opentrons/conftest.py b/api/tests/opentrons/conftest.py index b1ff1133978..7be480cfe0b 100755 --- a/api/tests/opentrons/conftest.py +++ b/api/tests/opentrons/conftest.py @@ -20,6 +20,7 @@ Union, cast, ) + from typing_extensions import TypedDict import pytest @@ -37,6 +38,23 @@ from opentrons_shared_data.protocol.types import JsonProtocol from opentrons_shared_data.labware.types import LabwareDefinition from opentrons_shared_data.module.types import ModuleDefinitionV3 +from opentrons_shared_data.liquid_classes.liquid_class_definition import ( + LiquidClassSchemaV1, + ByPipetteSetting, + ByTipTypeSetting, + AspirateProperties, + Submerge, + PositionReference, + DelayProperties, + DelayParams, + RetractAspirate, + SingleDispenseProperties, + RetractDispense, + Coordinate, + MixProperties, + TouchTipProperties, + BlowoutProperties, +) from opentrons_shared_data.deck.types import ( RobotModel, DeckDefinitionV3, @@ -317,6 +335,7 @@ def _make_ot3_pe_ctx( block_on_door_open=False, ), deck_configuration=None, + file_provider=None, error_recovery_policy=error_recovery_policy.never_recover, drop_tips_after_run=False, post_run_hardware_state=PostRunHardwareState.STAY_ENGAGED_IN_PLACE, @@ -763,3 +782,86 @@ def minimal_module_def() -> ModuleDefinitionV3: "cornerOffsetFromSlot": {"x": 0.1, "y": 0.1, "z": 0.0}, "twoDimensionalRendering": {}, } + + +@pytest.fixture +def minimal_liquid_class_def1() -> LiquidClassSchemaV1: + return LiquidClassSchemaV1( + liquidClassName="water1", + displayName="water 1", + schemaVersion=1, + namespace="test-fixture-1", + byPipette=[], + ) + + +@pytest.fixture +def minimal_liquid_class_def2() -> LiquidClassSchemaV1: + return LiquidClassSchemaV1( + liquidClassName="water2", + displayName="water 2", + schemaVersion=1, + namespace="test-fixture-2", + byPipette=[ + ByPipetteSetting( + pipetteModel="flex_1channel_50", + byTipType=[ + ByTipTypeSetting( + tiprack="opentrons_flex_96_tiprack_50ul", + aspirate=AspirateProperties( + submerge=Submerge( + positionReference=PositionReference.LIQUID_MENISCUS, + offset=Coordinate(x=0, y=0, z=-5), + speed=100, + delay=DelayProperties( + enable=True, params=DelayParams(duration=1.5) + ), + ), + retract=RetractAspirate( + positionReference=PositionReference.WELL_TOP, + offset=Coordinate(x=0, y=0, z=5), + speed=100, + airGapByVolume=[(5.0, 3.0), (10.0, 4.0)], + touchTip=TouchTipProperties(enable=False), + delay=DelayProperties(enable=False), + ), + positionReference=PositionReference.WELL_BOTTOM, + offset=Coordinate(x=0, y=0, z=-5), + flowRateByVolume=[(10.0, 40.0), (20.0, 30.0)], + correctionByVolume=[(15.0, 1.5), (30.0, -5.0)], + preWet=True, + mix=MixProperties(enable=False), + delay=DelayProperties( + enable=True, params=DelayParams(duration=2) + ), + ), + singleDispense=SingleDispenseProperties( + submerge=Submerge( + positionReference=PositionReference.LIQUID_MENISCUS, + offset=Coordinate(x=0, y=0, z=-5), + speed=100, + delay=DelayProperties(enable=False), + ), + retract=RetractDispense( + positionReference=PositionReference.WELL_TOP, + offset=Coordinate(x=0, y=0, z=5), + speed=100, + airGapByVolume=[(5.0, 3.0), (10.0, 4.0)], + blowout=BlowoutProperties(enable=False), + touchTip=TouchTipProperties(enable=False), + delay=DelayProperties(enable=False), + ), + positionReference=PositionReference.WELL_BOTTOM, + offset=Coordinate(x=0, y=0, z=-5), + flowRateByVolume=[(10.0, 40.0), (20.0, 30.0)], + correctionByVolume=[(15.0, -1.5), (30.0, 5.0)], + mix=MixProperties(enable=False), + pushOutByVolume=[(10.0, 7.0), (20.0, 10.0)], + delay=DelayProperties(enable=False), + ), + multiDispense=None, + ) + ], + ) + ], + ) diff --git a/api/tests/opentrons/drivers/absorbance_reader/test_driver.py b/api/tests/opentrons/drivers/absorbance_reader/test_driver.py index ff633e38760..b4db8d604b2 100644 --- a/api/tests/opentrons/drivers/absorbance_reader/test_driver.py +++ b/api/tests/opentrons/drivers/absorbance_reader/test_driver.py @@ -1,3 +1,4 @@ +from typing import cast from mock import MagicMock import pytest import asyncio @@ -9,7 +10,7 @@ AbsorbanceHidInterface, ) from opentrons.drivers.absorbance_reader.async_byonoy import AsyncByonoy -from opentrons.drivers.types import AbsorbanceReaderLidStatus +from opentrons.drivers.types import ABSMeasurementMode, AbsorbanceReaderLidStatus @pytest.fixture @@ -23,8 +24,8 @@ def mock_device() -> MagicMock: class MockErrorCode(Enum): - BYONOY_ERROR_NO_ERROR = "no_error" - BYONOY_ERROR = "error" + NO_ERROR = "no_error" + ERROR = "error" @pytest.fixture @@ -52,20 +53,20 @@ async def test_driver_connect_disconnect( mock_interface: MagicMock, driver: AbsorbanceReaderDriver, ) -> None: - mock_interface.byonoy_open_device.return_value = ( - MockErrorCode.BYONOY_ERROR_NO_ERROR, + mock_interface.open_device.return_value = ( + MockErrorCode.NO_ERROR, 1, ) assert not await driver.is_connected() await driver.connect() - mock_interface.byonoy_open_device.assert_called_once() + mock_interface.open_device.assert_called_once() assert await driver.is_connected() assert driver._connection._verify_device_handle() assert driver._connection._device_handle == 1 - mock_interface.byonoy_free_device.return_value = MockErrorCode.BYONOY_ERROR_NO_ERROR + mock_interface.free_device.return_value = MockErrorCode.NO_ERROR await driver.disconnect() assert not await driver.is_connected() @@ -78,19 +79,80 @@ async def test_driver_get_device_info( ) -> None: DEVICE_INFO = MagicMock(AbsorbanceHidInterface.DeviceInfo) - DEVICE_INFO.ref_no = "" - DEVICE_INFO.sn = "SN: BYOMAA00013 REF: DE MAA 001" + DEVICE_INFO.ref_no = "DE MAA 001" + DEVICE_INFO.sn = "BYOMAA00013" DEVICE_INFO.version = "Absorbance V1.0.2 2024-04-18" - mock_interface.byonoy_get_device_information.return_value = ( - MockErrorCode.BYONOY_ERROR_NO_ERROR, + mock_interface.get_device_information.return_value = ( + MockErrorCode.NO_ERROR, DEVICE_INFO, ) info = await connected_driver.get_device_info() - mock_interface.byonoy_get_device_information.assert_called_once() assert info == {"serial": "BYOMAA00013", "model": "ABS96", "version": "v1.0.2"} + mock_interface.get_device_information.assert_called_once() + mock_interface.reset_mock() + + # Test Device info with updated serial format + DEVICE_INFO.sn = "OPTMAA00034" + DEVICE_INFO.version = "Absorbance V1.0.2 2024-04-18" + + mock_interface.get_device_information.return_value = ( + MockErrorCode.NO_ERROR, + DEVICE_INFO, + ) + + info = await connected_driver.get_device_info() + + assert info == {"serial": "OPTMAA00034", "model": "ABS96", "version": "v1.0.2"} + mock_interface.get_device_information.assert_called_once() + mock_interface.reset_mock() + + # Test Device info with invalid serial format + DEVICE_INFO.sn = "YRFGHVMAA00034" + DEVICE_INFO.version = "Absorbance V1.0.2 2024-04-18" + + mock_interface.get_device_information.return_value = ( + MockErrorCode.NO_ERROR, + DEVICE_INFO, + ) + + info = await connected_driver.get_device_info() + + assert info == {"serial": "OPTMAA00000", "model": "ABS96", "version": "v1.0.2"} + mock_interface.get_device_information.assert_called_once() + mock_interface.reset_mock() + + # Test Device info with updated version format + DEVICE_INFO.sn = "OPTMAA00034" + DEVICE_INFO.version = "8" + + mock_interface.get_device_information.return_value = ( + MockErrorCode.NO_ERROR, + DEVICE_INFO, + ) + + info = await connected_driver.get_device_info() + + assert info == {"serial": "OPTMAA00034", "model": "ABS96", "version": "8"} + mock_interface.get_device_information.assert_called_once() + mock_interface.reset_mock() + + # Test Device info with invalid version format + DEVICE_INFO.sn = "OPTMAA00034" + DEVICE_INFO.version = "asd" + + mock_interface.get_device_information.return_value = ( + MockErrorCode.NO_ERROR, + DEVICE_INFO, + ) + + info = await connected_driver.get_device_info() + + assert info == {"serial": "OPTMAA00034", "model": "ABS96", "version": "v0"} + mock_interface.get_device_information.assert_called_once() + mock_interface.reset_mock() @pytest.mark.parametrize( @@ -104,14 +166,14 @@ async def test_driver_get_lid_status( module_status: AbsorbanceReaderLidStatus, ) -> None: - mock_interface.byonoy_get_device_parts_aligned.return_value = ( - MockErrorCode.BYONOY_ERROR_NO_ERROR, + mock_interface.get_device_parts_aligned.return_value = ( + MockErrorCode.NO_ERROR, parts_aligned, ) status = await connected_driver.get_lid_status() - mock_interface.byonoy_get_device_parts_aligned.assert_called_once() + mock_interface.get_device_parts_aligned.assert_called_once() assert status == module_status @@ -120,8 +182,8 @@ async def test_driver_get_supported_wavelengths( connected_driver: AbsorbanceReaderDriver, ) -> None: SUPPORTED_WAVELENGTHS = [450, 500] - mock_interface.byonoy_abs96_get_available_wavelengths.return_value = ( - MockErrorCode.BYONOY_ERROR_NO_ERROR, + mock_interface.abs96_get_available_wavelengths.return_value = ( + MockErrorCode.NO_ERROR, SUPPORTED_WAVELENGTHS, ) @@ -129,42 +191,91 @@ async def test_driver_get_supported_wavelengths( wavelengths = await connected_driver.get_available_wavelengths() - mock_interface.byonoy_abs96_get_available_wavelengths.assert_called_once() + mock_interface.abs96_get_available_wavelengths.assert_called_once() assert connected_driver._connection._supported_wavelengths == SUPPORTED_WAVELENGTHS assert wavelengths == SUPPORTED_WAVELENGTHS -async def test_driver_initialize_and_read( +async def test_driver_initialize_and_read_single( mock_interface: MagicMock, connected_driver: AbsorbanceReaderDriver, ) -> None: # set up mock interface connected_driver._connection._supported_wavelengths = [450, 500] - mock_interface.byonoy_abs96_initialize_single_measurement.return_value = ( - MockErrorCode.BYONOY_ERROR_NO_ERROR - ) - mock_interface.ByonoyAbs96SingleMeasurementConfig = MagicMock( - spec=AbsorbanceHidInterface.MeasurementConfig + mock_interface.abs96_initialize_single_measurement.return_value = ( + MockErrorCode.NO_ERROR ) + class MeasurementConfig(AbsorbanceHidInterface.SingleMeasurementConfig): + def __init__(self) -> None: + self.sample_wavelength = 0 + self.reference_wavelength = 0 + + mock_interface.Abs96SingleMeasurementConfig = MeasurementConfig + # current config should not have been setup yet assert not connected_driver._connection._current_config - await connected_driver.initialize_measurement(450) + await connected_driver.initialize_measurement([450], mode=ABSMeasurementMode.SINGLE) - conf = connected_driver._connection._current_config + conf = cast( + AbsorbanceHidInterface.SingleMeasurementConfig, + connected_driver._connection._current_config, + ) assert conf and conf.sample_wavelength == 450 - mock_interface.byonoy_abs96_initialize_single_measurement.assert_called_once_with( + mock_interface.abs96_initialize_single_measurement.assert_called_once_with(1, conf) + + # setup up mock interface with a single reading + MEASURE_RESULT = [[0.1] * 96] + mock_interface.abs96_single_measure.return_value = ( + MockErrorCode.NO_ERROR, + MEASURE_RESULT, + ) + + result = await connected_driver.get_measurement() + mock_interface.abs96_single_measure.assert_called_once_with(1, conf) + + assert result == MEASURE_RESULT + + +async def test_driver_initialize_and_read_multi( + mock_interface: MagicMock, + connected_driver: AbsorbanceReaderDriver, +) -> None: + # set up mock interface + connected_driver._connection._supported_wavelengths = [450, 500, 600] + mock_interface.abs96_initialize_multiple_measurement.return_value = ( + MockErrorCode.NO_ERROR + ) + + class MeasurementConfig(AbsorbanceHidInterface.MultiMeasurementConfig): + def __init__(self) -> None: + self.sample_wavelengths = [0] + + mock_interface.Abs96MultipleMeasurementConfig = MeasurementConfig + + # current config should not have been setup yet + assert not connected_driver._connection._current_config + await connected_driver.initialize_measurement( + [450, 500, 600], mode=ABSMeasurementMode.MULTI + ) + + conf = cast( + AbsorbanceHidInterface.MultiMeasurementConfig, + connected_driver._connection._current_config, + ) + assert conf and conf.sample_wavelengths == [450, 500, 600] + mock_interface.abs96_initialize_multiple_measurement.assert_called_once_with( 1, conf ) - # setup up mock interface - MEASURE_RESULT = [0.1] * 96 - mock_interface.byonoy_abs96_single_measure.return_value = ( - MockErrorCode.BYONOY_ERROR_NO_ERROR, + # setup up mock interface with multiple readings + MEASURE_RESULT = [[0.1] * 96, [0.2] * 96, [0.3] * 96] + mock_interface.abs96_multiple_measure.return_value = ( + MockErrorCode.NO_ERROR, MEASURE_RESULT, ) - result = await connected_driver.get_single_measurement(450) - mock_interface.byonoy_abs96_single_measure.assert_called_once_with(1, conf) + result = await connected_driver.get_measurement() + mock_interface.abs96_multiple_measure.assert_called_once_with(1, conf) assert result == MEASURE_RESULT diff --git a/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py b/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py index a1c493218aa..9c03bed68b2 100644 --- a/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py +++ b/api/tests/opentrons/hardware_control/backends/test_ot3_controller.py @@ -39,7 +39,6 @@ OT3Config, GantryLoad, LiquidProbeSettings, - OutputOptions, ) from opentrons.config.robot_configs import build_config_ot3 from opentrons_hardware.firmware_bindings.arbitration_id import ArbitrationId @@ -61,7 +60,6 @@ UpdateState, EstopState, CurrentConfig, - InstrumentProbeType, ) from opentrons.hardware_control.errors import ( InvalidPipetteName, @@ -180,13 +178,11 @@ def fake_liquid_settings() -> LiquidProbeSettings: plunger_speed=10, plunger_impulse_time=0.2, sensor_threshold_pascals=15, - output_option=OutputOptions.can_bus_only, aspirate_while_sensing=False, z_overlap_between_passes_mm=0.1, plunger_reset_offset=2.0, samples_for_baselining=20, sample_time_sec=0.004, - data_files={InstrumentProbeType.PRIMARY: "fake_file_name"}, ) @@ -201,6 +197,7 @@ def mock_send_stop_threshold() -> Iterator[mock.AsyncMock]: @pytest.fixture def mock_move_group_run() -> Iterator[mock.AsyncMock]: + with mock.patch( "opentrons.hardware_control.backends.ot3controller.MoveGroupRunner.run", autospec=True, @@ -342,7 +339,7 @@ def fw_node_info() -> Dict[NodeId, DeviceInfoCache]: ] -def move_group_run_side_effect( +def move_group_run_side_effect_home( controller: OT3Controller, axes_to_home: List[Axis] ) -> Iterator[Dict[NodeId, MotorPositionStatus]]: """Return homed position for axis that is present and was commanded to home.""" @@ -370,13 +367,15 @@ async def test_home_execute( mock_present_devices: None, mock_check_overpressure: None, ) -> None: - config = {"run.side_effect": move_group_run_side_effect(controller, axes)} + config = {"run.side_effect": move_group_run_side_effect_home(controller, axes)} with mock.patch( # type: ignore [call-overload] "opentrons.hardware_control.backends.ot3controller.MoveGroupRunner", spec=MoveGroupRunner, **config ) as mock_runner: present_axes = set(ax for ax in axes if controller.axis_is_present(ax)) + controller.set_pressure_sensor_available(Axis.P_L, True) + controller.set_pressure_sensor_available(Axis.P_R, True) # nothing has been homed assert not controller._motor_status @@ -488,8 +487,10 @@ async def test_home_only_present_devices( homed_position = {} controller._position = starting_position + controller.set_pressure_sensor_available(Axis.P_L, True) + controller.set_pressure_sensor_available(Axis.P_R, True) - mock_move_group_run.side_effect = move_group_run_side_effect(controller, axes) + mock_move_group_run.side_effect = move_group_run_side_effect_home(controller, axes) # nothing has been homed assert not controller._motor_status @@ -707,6 +708,17 @@ async def test_ready_for_movement( assert controller.check_motor_status(axes) == ready +def probe_move_group_run_side_effect( + head: NodeId, tool: NodeId +) -> Iterator[Dict[NodeId, MotorPositionStatus]]: + """Return homed position for axis that is present and was commanded to home.""" + positions = { + head: MotorPositionStatus(0.0, 0.0, True, True, MoveCompleteAck(1)), + tool: MotorPositionStatus(0.0, 0.0, True, True, MoveCompleteAck(1)), + } + yield positions + + @pytest.mark.parametrize("mount", [OT3Mount.LEFT, OT3Mount.RIGHT]) async def test_liquid_probe( mount: OT3Mount, @@ -716,6 +728,14 @@ async def test_liquid_probe( mock_send_stop_threshold: mock.AsyncMock, ) -> None: fake_max_p_dist = 70 + head_node = axis_to_node(Axis.by_mount(mount)) + tool_node = sensor_node_for_mount(mount) + mock_move_group_run.side_effect = probe_move_group_run_side_effect( + head_node, tool_node + ) + controller._pipettes_to_monitor_pressure = mock.MagicMock( # type: ignore[method-assign] + return_value=[sensor_node_for_mount(mount)] + ) try: await controller.liquid_probe( mount=mount, @@ -724,18 +744,18 @@ async def test_liquid_probe( plunger_speed=fake_liquid_settings.plunger_speed, threshold_pascals=fake_liquid_settings.sensor_threshold_pascals, plunger_impulse_time=fake_liquid_settings.plunger_impulse_time, - output_option=fake_liquid_settings.output_option, + num_baseline_reads=fake_liquid_settings.samples_for_baselining, ) except PipetteLiquidNotFoundError: # the move raises a liquid not found now since we don't call the move group and it doesn't # get any positions back pass move_groups = mock_move_group_run.call_args_list[0][0][0]._move_groups - head_node = axis_to_node(Axis.by_mount(mount)) - tool_node = sensor_node_for_mount(mount) # in tool_sensors, pipette moves down, then sensor move goes assert move_groups[0][0][tool_node].stop_condition == MoveStopCondition.none - assert move_groups[1][0][tool_node].stop_condition == MoveStopCondition.sync_line + assert ( + move_groups[1][0][tool_node].stop_condition == MoveStopCondition.sensor_report + ) assert len(move_groups) == 2 assert move_groups[0][0][tool_node] assert move_groups[1][0][head_node], move_groups[2][0][tool_node] @@ -1280,3 +1300,154 @@ def test_grip_error_detection( hard_max, hard_min, ) + + +def move_group_run_side_effect( + controller: OT3Controller, target_pos: Dict[Axis, float] +) -> Iterator[Dict[NodeId, MotorPositionStatus]]: + """Return homed position for axis that is present and was commanded to home.""" + motor_nodes = controller._motor_nodes() + target_nodes = {axis_to_node(ax): ax for ax in target_pos.keys() if ax != Axis.Q} + res = {} + for node in motor_nodes: + pos = 0.0 + if target_nodes.get(node): + pos = target_pos[target_nodes[node]] + res[node] = MotorPositionStatus(pos, pos, True, True, MoveCompleteAck(1)) + yield res + + +@pytest.mark.parametrize( + argnames=["origin_pos", "target_pos", "expected_pos", "gear_position"], + argvalues=[ + [ + { + Axis.X: 0, + Axis.Y: 0, + Axis.Z_L: 0, + Axis.Z_R: 0, + Axis.P_L: 0, + Axis.P_R: 0, + Axis.Z_G: 0, + Axis.G: 0, + Axis.Q: 0, + }, + {Axis.Q: 10}, + { + Axis.X: 0, + Axis.Y: 0, + Axis.Z_L: 0, + Axis.Z_R: 0, + Axis.P_L: 0, + Axis.P_R: 0, + Axis.Z_G: 0, + Axis.G: 0, + }, + 10, + ], + [ + { + Axis.X: 0, + Axis.Y: 0, + Axis.Z_L: 0, + Axis.Z_R: 0, + Axis.P_L: 0, + Axis.P_R: 0, + Axis.Z_G: 0, + Axis.G: 0, + }, + {Axis.Q: 10}, + { + Axis.X: 0, + Axis.Y: 0, + Axis.Z_L: 0, + Axis.Z_R: 0, + Axis.P_L: 0, + Axis.P_R: 0, + Axis.Z_G: 0, + Axis.G: 0, + }, + None, + ], + [ + { + Axis.X: 0, + Axis.Y: 0, + Axis.Z_L: 0, + Axis.Z_R: 0, + }, + { + Axis.X: 10, + Axis.Y: 10, + Axis.Z_L: 10, + }, + { + Axis.X: 10, + Axis.Y: 10, + Axis.Z_L: 10, + Axis.Z_R: 0, + Axis.P_L: 0, + Axis.P_R: 0, + Axis.Z_G: 0, + Axis.G: 0, + }, + None, + ], + ], +) +async def test_controller_move( + controller: OT3Controller, + mock_present_devices: mock.AsyncMock, + origin_pos: Dict[Axis, float], + target_pos: Dict[Axis, float], + expected_pos: Dict[Axis, float], + gear_position: Optional[float], +) -> None: + from copy import deepcopy + + controller.update_constraints_for_gantry_load(GantryLoad.HIGH_THROUGHPUT) + + run_target_pos = deepcopy(target_pos) + config = {"run.side_effect": move_group_run_side_effect(controller, run_target_pos)} + with mock.patch( # type: ignore [call-overload] + "opentrons.hardware_control.backends.ot3controller.MoveGroupRunner", + spec=MoveGroupRunner, + **config + ): + await controller.move(origin_pos, target_pos, 100) + position = await controller.update_position() + gear_position = controller.gear_motor_position + + assert position == expected_pos + assert gear_position == gear_position + + +@pytest.mark.parametrize( + argnames=["axes", "pipette_has_sensor"], + argvalues=[[[Axis.P_L, Axis.P_R], True], [[Axis.P_L, Axis.P_R], False]], +) +async def test_pressure_disable( + controller: OT3Controller, + axes: List[Axis], + mock_present_devices: None, + mock_check_overpressure: None, + pipette_has_sensor: bool, +) -> None: + config = {"run.side_effect": move_group_run_side_effect_home(controller, axes)} + with mock.patch( # type: ignore [call-overload] + "opentrons.hardware_control.backends.ot3controller.MoveGroupRunner", + spec=MoveGroupRunner, + **config + ): + with mock.patch.object(controller, "_monitor_overpressure") as monitor: + controller.set_pressure_sensor_available(Axis.P_L, pipette_has_sensor) + controller.set_pressure_sensor_available(Axis.P_R, True) + + await controller.home(axes, GantryLoad.LOW_THROUGHPUT) + + if pipette_has_sensor: + monitor.assert_called_once_with( + [NodeId.pipette_left, NodeId.pipette_right] + ) + else: + monitor.assert_called_once_with([NodeId.pipette_right]) diff --git a/api/tests/opentrons/hardware_control/backends/test_ot3_utils.py b/api/tests/opentrons/hardware_control/backends/test_ot3_utils.py index 0d081878dd1..d7125cfb027 100644 --- a/api/tests/opentrons/hardware_control/backends/test_ot3_utils.py +++ b/api/tests/opentrons/hardware_control/backends/test_ot3_utils.py @@ -1,12 +1,9 @@ import pytest from typing import List from opentrons_hardware.hardware_control.motion_planning import Move -from opentrons_hardware.hardware_control.motion import ( - create_step, -) from opentrons.hardware_control.backends import ot3utils from opentrons_hardware.firmware_bindings.constants import NodeId -from opentrons.hardware_control.types import Axis, OT3Mount +from opentrons.hardware_control.types import Axis, OT3Mount, OT3AxisKind from numpy import float64 as f64 from opentrons.config import defaults_ot3, types as conf_types @@ -39,31 +36,6 @@ def test_create_step() -> None: assert set(present_nodes) == set(step.keys()) -def test_get_moving_nodes() -> None: - """Test that we can filter out the nonmoving nodes.""" - # Create a dummy group where X has velocity but no accel, and Y has accel but no velocity. - present_nodes = [NodeId.gantry_x, NodeId.gantry_y, NodeId.head_l, NodeId.head_r] - move_group = [ - create_step( - distance={NodeId.gantry_x: f64(100), NodeId.gantry_y: f64(100)}, - velocity={NodeId.gantry_x: f64(100), NodeId.gantry_y: f64(0)}, - acceleration={NodeId.gantry_x: f64(0), NodeId.gantry_y: f64(100)}, - duration=f64(1), - present_nodes=present_nodes, - ) - ] - assert len(move_group[0]) == 4 - - print(move_group) - - moving_nodes = ot3utils.moving_axes_in_move_group(move_group) - assert len(moving_nodes) == 2 - assert NodeId.gantry_x in moving_nodes - assert NodeId.gantry_y in moving_nodes - assert NodeId.head_l not in moving_nodes - assert NodeId.head_r not in moving_nodes - - def test_filter_zero_duration_step() -> None: origin = { Axis.X: 0, @@ -123,6 +95,22 @@ def test_get_system_contraints_for_plunger() -> None: assert updated_contraints[axis].max_acceleration == set_acceleration +@pytest.mark.parametrize(["mount"], [[OT3Mount.LEFT], [OT3Mount.RIGHT]]) +def test_get_system_constraints_for_emulsifying_pipette(mount: OT3Mount) -> None: + set_max_speed = 90 + config = defaults_ot3.build_with_defaults({}) + pipette_ax = Axis.of_main_tool_actuator(mount) + default_pip_max_speed = config.motion_settings.default_max_speed[ + conf_types.GantryLoad.LOW_THROUGHPUT + ][OT3AxisKind.P] + updated_constraints = ot3utils.get_system_constraints_for_emulsifying_pipette( + config.motion_settings, conf_types.GantryLoad.LOW_THROUGHPUT, mount + ) + other_pipette = list(set(Axis.pipette_axes()) - {pipette_ax})[0] + assert updated_constraints[pipette_ax].max_speed == set_max_speed + assert updated_constraints[other_pipette].max_speed == default_pip_max_speed + + @pytest.mark.parametrize( ["moving", "expected"], [ @@ -154,15 +142,8 @@ def test_moving_pipettes_in_move_group( NodeId.gripper_g, NodeId.gripper_z, ] - move_group = [ - create_step( - distance={node: f64(100) for node in moving}, - velocity={node: f64(100) for node in moving}, - acceleration={node: f64(0) for node in moving}, - duration=f64(1), - present_nodes=present_nodes, - ) - ] - moving_pipettes = ot3utils.moving_pipettes_in_move_group(move_group) + moving_pipettes = ot3utils.moving_pipettes_in_move_group( + set(present_nodes), set(moving) + ) assert set(moving_pipettes) == set(expected) diff --git a/api/tests/opentrons/hardware_control/instruments/test_nozzle_manager.py b/api/tests/opentrons/hardware_control/instruments/test_nozzle_manager.py index 5030bec31fe..ba1f10aaaef 100644 --- a/api/tests/opentrons/hardware_control/instruments/test_nozzle_manager.py +++ b/api/tests/opentrons/hardware_control/instruments/test_nozzle_manager.py @@ -3,7 +3,7 @@ from opentrons.hardware_control import nozzle_manager -from opentrons.types import Point +from opentrons.types import Point, NozzleConfigurationType from opentrons_shared_data.pipette.load_data import load_definition from opentrons_shared_data.pipette.types import ( @@ -258,7 +258,7 @@ ], ) def test_single_pipettes_always_full( - pipette_details: Tuple[PipetteModelType, PipetteVersionType] + pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( pipette_details[0], PipetteChannelType.SINGLE_CHANNEL, pipette_details[1] @@ -266,22 +266,13 @@ def test_single_pipettes_always_full( subject = nozzle_manager.NozzleConfigurationManager.build_from_config( config, ValidNozzleMaps(maps=A1) ) - assert ( - subject.current_configuration.configuration - == nozzle_manager.NozzleConfigurationType.FULL - ) + assert subject.current_configuration.configuration == NozzleConfigurationType.FULL subject.update_nozzle_configuration("A1", "A1", "A1") - assert ( - subject.current_configuration.configuration - == nozzle_manager.NozzleConfigurationType.FULL - ) + assert subject.current_configuration.configuration == NozzleConfigurationType.FULL subject.reset_to_default_configuration() - assert ( - subject.current_configuration.configuration - == nozzle_manager.NozzleConfigurationType.FULL - ) + assert subject.current_configuration.configuration == NozzleConfigurationType.FULL @pytest.mark.parametrize( @@ -295,7 +286,7 @@ def test_single_pipettes_always_full( ], ) def test_single_pipette_map_entries( - pipette_details: Tuple[PipetteModelType, PipetteVersionType] + pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( pipette_details[0], PipetteChannelType.SINGLE_CHANNEL, pipette_details[1] @@ -332,7 +323,7 @@ def test_map_entries(nozzlemap: nozzle_manager.NozzleMap) -> None: ], ) def test_single_pipette_map_geometry( - pipette_details: Tuple[PipetteModelType, PipetteVersionType] + pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( pipette_details[0], PipetteChannelType.SINGLE_CHANNEL, pipette_details[1] @@ -365,7 +356,7 @@ def test_map_geometry(nozzlemap: nozzle_manager.NozzleMap) -> None: ], ) def test_multi_config_identification( - pipette_details: Tuple[PipetteModelType, PipetteVersionType] + pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( pipette_details[0], PipetteChannelType.EIGHT_CHANNEL, pipette_details[1] @@ -375,55 +366,43 @@ def test_multi_config_identification( ValidNozzleMaps(maps=EIGHT_CHANNEL_FULL | A1_D1 | A1 | H1), ) - assert ( - subject.current_configuration.configuration - == nozzle_manager.NozzleConfigurationType.FULL - ) + assert subject.current_configuration.configuration == NozzleConfigurationType.FULL subject.update_nozzle_configuration("A1", "H1", "A1") - assert ( - subject.current_configuration.configuration - == nozzle_manager.NozzleConfigurationType.FULL - ) + assert subject.current_configuration.configuration == NozzleConfigurationType.FULL subject.reset_to_default_configuration() - assert ( - subject.current_configuration.configuration - == nozzle_manager.NozzleConfigurationType.FULL - ) + assert subject.current_configuration.configuration == NozzleConfigurationType.FULL subject.update_nozzle_configuration("A1", "D1", "A1") assert ( cast( - nozzle_manager.NozzleConfigurationType, + NozzleConfigurationType, subject.current_configuration.configuration, ) - == nozzle_manager.NozzleConfigurationType.COLUMN + == NozzleConfigurationType.COLUMN ) subject.update_nozzle_configuration("A1", "A1", "A1") assert ( cast( - nozzle_manager.NozzleConfigurationType, + NozzleConfigurationType, subject.current_configuration.configuration, ) - == nozzle_manager.NozzleConfigurationType.SINGLE + == NozzleConfigurationType.SINGLE ) subject.update_nozzle_configuration("H1", "H1", "H1") assert ( cast( - nozzle_manager.NozzleConfigurationType, + NozzleConfigurationType, subject.current_configuration.configuration, ) - == nozzle_manager.NozzleConfigurationType.SINGLE + == NozzleConfigurationType.SINGLE ) subject.reset_to_default_configuration() - assert ( - subject.current_configuration.configuration - == nozzle_manager.NozzleConfigurationType.FULL - ) + assert subject.current_configuration.configuration == NozzleConfigurationType.FULL @pytest.mark.parametrize( @@ -437,7 +416,7 @@ def test_multi_config_identification( ], ) def test_multi_config_map_entries( - pipette_details: Tuple[PipetteModelType, PipetteVersionType] + pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( pipette_details[0], PipetteChannelType.EIGHT_CHANNEL, pipette_details[1] @@ -503,7 +482,7 @@ def assert_offset_in_center_of( ], ) def test_multi_config_geometry( - pipette_details: Tuple[PipetteModelType, PipetteVersionType] + pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( pipette_details[0], PipetteChannelType.EIGHT_CHANNEL, pipette_details[1] @@ -554,7 +533,7 @@ def test_map_geometry( "pipette_details", [(PipetteModelType.p1000, PipetteVersionType(major=3, minor=5))] ) def test_96_config_identification( - pipette_details: Tuple[PipetteModelType, PipetteVersionType] + pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( pipette_details[0], PipetteChannelType.NINETY_SIX_CHANNEL, pipette_details[1] @@ -577,97 +556,91 @@ def test_96_config_identification( ), ) - assert ( - subject.current_configuration.configuration - == nozzle_manager.NozzleConfigurationType.FULL - ) + assert subject.current_configuration.configuration == NozzleConfigurationType.FULL subject.update_nozzle_configuration("A1", "H12") - assert ( - subject.current_configuration.configuration - == nozzle_manager.NozzleConfigurationType.FULL - ) + assert subject.current_configuration.configuration == NozzleConfigurationType.FULL subject.update_nozzle_configuration("A1", "H1") assert ( cast( - nozzle_manager.NozzleConfigurationType, + NozzleConfigurationType, subject.current_configuration.configuration, ) - == nozzle_manager.NozzleConfigurationType.COLUMN + == NozzleConfigurationType.COLUMN ) subject.update_nozzle_configuration("A12", "H12") assert ( cast( - nozzle_manager.NozzleConfigurationType, + NozzleConfigurationType, subject.current_configuration.configuration, ) - == nozzle_manager.NozzleConfigurationType.COLUMN + == NozzleConfigurationType.COLUMN ) subject.update_nozzle_configuration("A1", "A12") assert ( cast( - nozzle_manager.NozzleConfigurationType, + NozzleConfigurationType, subject.current_configuration.configuration, ) - == nozzle_manager.NozzleConfigurationType.ROW + == NozzleConfigurationType.ROW ) subject.update_nozzle_configuration("H1", "H12") assert ( cast( - nozzle_manager.NozzleConfigurationType, + NozzleConfigurationType, subject.current_configuration.configuration, ) - == nozzle_manager.NozzleConfigurationType.ROW + == NozzleConfigurationType.ROW ) subject.update_nozzle_configuration("E1", "H6") assert ( cast( - nozzle_manager.NozzleConfigurationType, + NozzleConfigurationType, subject.current_configuration.configuration, ) - == nozzle_manager.NozzleConfigurationType.SUBRECT + == NozzleConfigurationType.SUBRECT ) subject.update_nozzle_configuration("E7", "H12") assert ( cast( - nozzle_manager.NozzleConfigurationType, + NozzleConfigurationType, subject.current_configuration.configuration, ) - == nozzle_manager.NozzleConfigurationType.SUBRECT + == NozzleConfigurationType.SUBRECT ) subject.update_nozzle_configuration("A1", "B12") assert ( cast( - nozzle_manager.NozzleConfigurationType, + NozzleConfigurationType, subject.current_configuration.configuration, ) - == nozzle_manager.NozzleConfigurationType.SUBRECT + == NozzleConfigurationType.SUBRECT ) subject.update_nozzle_configuration("G1", "H12") assert ( cast( - nozzle_manager.NozzleConfigurationType, + NozzleConfigurationType, subject.current_configuration.configuration, ) - == nozzle_manager.NozzleConfigurationType.SUBRECT + == NozzleConfigurationType.SUBRECT ) subject.update_nozzle_configuration("A1", "H3") assert ( cast( - nozzle_manager.NozzleConfigurationType, + NozzleConfigurationType, subject.current_configuration.configuration, ) - == nozzle_manager.NozzleConfigurationType.SUBRECT + == NozzleConfigurationType.SUBRECT ) subject.update_nozzle_configuration("A10", "H12") assert ( cast( - nozzle_manager.NozzleConfigurationType, + NozzleConfigurationType, subject.current_configuration.configuration, ) - == nozzle_manager.NozzleConfigurationType.SUBRECT + == NozzleConfigurationType.SUBRECT ) @@ -675,7 +648,7 @@ def test_96_config_identification( "pipette_details", [(PipetteModelType.p1000, PipetteVersionType(major=3, minor=5))] ) def test_96_config_map_entries( - pipette_details: Tuple[PipetteModelType, PipetteVersionType] + pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( pipette_details[0], PipetteChannelType.NINETY_SIX_CHANNEL, pipette_details[1] @@ -1012,7 +985,7 @@ def _nozzles() -> Iterator[str]: "pipette_details", [(PipetteModelType.p1000, PipetteVersionType(major=3, minor=5))] ) def test_96_config_geometry( - pipette_details: Tuple[PipetteModelType, PipetteVersionType] + pipette_details: Tuple[PipetteModelType, PipetteVersionType], ) -> None: config = load_definition( pipette_details[0], PipetteChannelType.NINETY_SIX_CHANNEL, pipette_details[1] diff --git a/api/tests/opentrons/hardware_control/modules/test_hc_thermocycler.py b/api/tests/opentrons/hardware_control/modules/test_hc_thermocycler.py index d893d9912d0..6e90068ac1f 100644 --- a/api/tests/opentrons/hardware_control/modules/test_hc_thermocycler.py +++ b/api/tests/opentrons/hardware_control/modules/test_hc_thermocycler.py @@ -329,6 +329,67 @@ async def test_cycle_temperature( ) +async def test_execute_profile( + set_temperature_subject: modules.Thermocycler, set_plate_temp_spy: mock.AsyncMock +) -> None: + """It should send a series of set_plate_temperatures from a profile.""" + await set_temperature_subject.execute_profile( + [ + {"temperature": 42, "hold_time_seconds": 30}, + { + "repetitions": 5, + "steps": [ + {"temperature": 20, "hold_time_minutes": 1}, + {"temperature": 30, "hold_time_seconds": 1}, + ], + }, + {"temperature": 90, "hold_time_seconds": 2}, + { + "repetitions": 10, + "steps": [ + {"temperature": 10, "hold_time_minutes": 2}, + {"temperature": 20, "hold_time_seconds": 5}, + ], + }, + ], + volume=123, + ) + assert set_plate_temp_spy.call_args_list == [ + mock.call(temp=42, hold_time=30, volume=123), + mock.call(temp=20, hold_time=60, volume=123), + mock.call(temp=30, hold_time=1, volume=123), + mock.call(temp=20, hold_time=60, volume=123), + mock.call(temp=30, hold_time=1, volume=123), + mock.call(temp=20, hold_time=60, volume=123), + mock.call(temp=30, hold_time=1, volume=123), + mock.call(temp=20, hold_time=60, volume=123), + mock.call(temp=30, hold_time=1, volume=123), + mock.call(temp=20, hold_time=60, volume=123), + mock.call(temp=30, hold_time=1, volume=123), + mock.call(temp=90, hold_time=2, volume=123), + mock.call(temp=10, hold_time=120, volume=123), + mock.call(temp=20, hold_time=5, volume=123), + mock.call(temp=10, hold_time=120, volume=123), + mock.call(temp=20, hold_time=5, volume=123), + mock.call(temp=10, hold_time=120, volume=123), + mock.call(temp=20, hold_time=5, volume=123), + mock.call(temp=10, hold_time=120, volume=123), + mock.call(temp=20, hold_time=5, volume=123), + mock.call(temp=10, hold_time=120, volume=123), + mock.call(temp=20, hold_time=5, volume=123), + mock.call(temp=10, hold_time=120, volume=123), + mock.call(temp=20, hold_time=5, volume=123), + mock.call(temp=10, hold_time=120, volume=123), + mock.call(temp=20, hold_time=5, volume=123), + mock.call(temp=10, hold_time=120, volume=123), + mock.call(temp=20, hold_time=5, volume=123), + mock.call(temp=10, hold_time=120, volume=123), + mock.call(temp=20, hold_time=5, volume=123), + mock.call(temp=10, hold_time=120, volume=123), + mock.call(temp=20, hold_time=5, volume=123), + ] + + async def test_sync_error_response_to_poller( subject_mocked_driver: modules.Thermocycler, mock_driver: mock.AsyncMock, diff --git a/api/tests/opentrons/hardware_control/test_moves.py b/api/tests/opentrons/hardware_control/test_moves.py index 32bab459f88..cca9e166324 100644 --- a/api/tests/opentrons/hardware_control/test_moves.py +++ b/api/tests/opentrons/hardware_control/test_moves.py @@ -491,7 +491,7 @@ async def test_shake_during_drop( }, } await hardware_api.cache_instruments() - await hardware_api.add_tip(types.Mount.RIGHT, 50.0) + hardware_api.add_tip(types.Mount.RIGHT, 50.0) hardware_api.set_current_tiprack_diameter(types.Mount.RIGHT, 2.0 * 4) shake_tips_drop = mock.Mock(side_effect=hardware_api._shake_off_tips_drop) @@ -515,7 +515,7 @@ async def test_shake_during_drop( # Test drop tip shake with 25% of tiprack well diameter # over upper (2.25 mm) limit - await hardware_api.add_tip(types.Mount.RIGHT, 20) + hardware_api.add_tip(types.Mount.RIGHT, 20) hardware_api.set_current_tiprack_diameter(types.Mount.RIGHT, 2.3 * 4) shake_tips_drop.reset_mock() move_rel.reset_move() @@ -530,7 +530,7 @@ async def test_shake_during_drop( # Test drop tip shake with 25% of tiprack well diameter # below lower (1.0 mm) limit - await hardware_api.add_tip(types.Mount.RIGHT, 50) + hardware_api.add_tip(types.Mount.RIGHT, 50) hardware_api.set_current_tiprack_diameter(types.Mount.RIGHT, 0.9 * 4) shake_tips_drop.reset_mock() move_rel.reset_mock() diff --git a/api/tests/opentrons/hardware_control/test_ot3_api.py b/api/tests/opentrons/hardware_control/test_ot3_api.py index cd9e62d44e2..2fd3fb4377c 100644 --- a/api/tests/opentrons/hardware_control/test_ot3_api.py +++ b/api/tests/opentrons/hardware_control/test_ot3_api.py @@ -1,5 +1,6 @@ """ Tests for behaviors specific to the OT3 hardware controller. """ +import asyncio from typing import ( AsyncIterator, Iterator, @@ -26,7 +27,6 @@ GantryLoad, CapacitivePassSettings, LiquidProbeSettings, - OutputOptions, ) from opentrons.hardware_control.dev_types import ( AttachedGripper, @@ -59,14 +59,13 @@ EstopStateNotification, TipStateType, ) -from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType from opentrons.hardware_control.errors import InvalidCriticalPoint from opentrons.hardware_control.ot3api import OT3API from opentrons.hardware_control import ThreadManager from opentrons.hardware_control.backends.ot3simulator import OT3Simulator from opentrons_hardware.firmware_bindings.constants import NodeId -from opentrons.types import Point, Mount +from opentrons.types import Point, Mount, NozzleConfigurationType from opentrons_hardware.hardware_control.motion_planning.types import Move @@ -98,6 +97,8 @@ from opentrons.hardware_control.module_control import AttachedModulesControl from opentrons.hardware_control.backends.types import HWStopCondition +from opentrons_hardware.firmware_bindings.constants import SensorId +from opentrons_hardware.sensors.types import SensorDataType # TODO (spp, 2023-08-22): write tests for ot3api.stop & ot3api.halt @@ -109,7 +110,6 @@ def fake_settings() -> CapacitivePassSettings: max_overrun_distance_mm=2, speed_mm_per_s=4, sensor_threshold_pf=1.0, - output_option=OutputOptions.sync_only, ) @@ -120,13 +120,11 @@ def fake_liquid_settings() -> LiquidProbeSettings: plunger_speed=15, plunger_impulse_time=0.2, sensor_threshold_pascals=15, - output_option=OutputOptions.can_bus_only, aspirate_while_sensing=False, z_overlap_between_passes_mm=0.1, plunger_reset_offset=2.0, samples_for_baselining=20, sample_time_sec=0.004, - data_files={InstrumentProbeType.PRIMARY: "fake_file_name"}, ) @@ -488,8 +486,6 @@ def _update_position( speed_mm_per_s: float, threshold_pf: float, probe: InstrumentProbeType, - output_option: OutputOptions = OutputOptions.sync_only, - data_file: Optional[str] = None, ) -> None: hardware_backend._position[moving] += distance_mm / 2 @@ -671,7 +667,11 @@ async def test_pickup_moves( @pytest.mark.parametrize("load_configs", load_pipette_configs) @given(blowout_volume=strategies.floats(min_value=0, max_value=10)) -@settings(suppress_health_check=[HealthCheck.function_scoped_fixture], max_examples=10) +@settings( + suppress_health_check=[HealthCheck.function_scoped_fixture], + max_examples=10, + deadline=400, +) @example(blowout_volume=0.0) async def test_blow_out_position( ot3_hardware: ThreadManager[OT3API], @@ -812,7 +812,7 @@ async def test_liquid_probe( pipette = ot3_hardware.hardware_pipettes[mount.to_mount()] assert pipette - await ot3_hardware.add_tip(mount, 100) + ot3_hardware.add_tip(mount, 100) await ot3_hardware.home() mock_move_to.return_value = None @@ -827,13 +827,11 @@ async def test_liquid_probe( plunger_speed=15, plunger_impulse_time=0.2, sensor_threshold_pascals=15, - output_option=OutputOptions.can_bus_only, aspirate_while_sensing=True, z_overlap_between_passes_mm=0.1, plunger_reset_offset=2.0, samples_for_baselining=20, sample_time_sec=0.004, - data_files={InstrumentProbeType.PRIMARY: "fake_file_name"}, ) fake_max_z_dist = 10.0 non_responsive_z_mm = ot3_hardware.liquid_probe_non_responsive_z_distance( @@ -859,10 +857,10 @@ async def test_liquid_probe( (fake_settings_aspirate.plunger_speed * -1), fake_settings_aspirate.sensor_threshold_pascals, fake_settings_aspirate.plunger_impulse_time, - fake_settings_aspirate.output_option, - fake_settings_aspirate.data_files, + fake_settings_aspirate.samples_for_baselining, probe=InstrumentProbeType.PRIMARY, force_both_sensors=False, + response_queue=None, ) await ot3_hardware.liquid_probe( @@ -904,7 +902,7 @@ async def test_liquid_probe_plunger_moves( pipette = ot3_hardware.hardware_pipettes[mount.to_mount()] assert pipette - await ot3_hardware.add_tip(mount, 100) + ot3_hardware.add_tip(mount, 100) await ot3_hardware.home() mock_move_to.return_value = None @@ -1011,7 +1009,7 @@ async def test_liquid_probe_mount_moves( pipette = ot3_hardware.hardware_pipettes[mount.to_mount()] assert pipette - await ot3_hardware.add_tip(mount, 100) + ot3_hardware.add_tip(mount, 100) await ot3_hardware.home() mock_move_to.return_value = None @@ -1072,7 +1070,7 @@ async def test_multi_liquid_probe( await ot3_hardware.cache_pipette(OT3Mount.LEFT, instr_data, None) pipette = ot3_hardware.hardware_pipettes[OT3Mount.LEFT.to_mount()] assert pipette - await ot3_hardware.add_tip(OT3Mount.LEFT, 100) + ot3_hardware.add_tip(OT3Mount.LEFT, 100) await ot3_hardware.home() mock_move_to.return_value = None @@ -1097,13 +1095,11 @@ async def test_multi_liquid_probe( plunger_speed=71.5, plunger_impulse_time=0.2, sensor_threshold_pascals=15, - output_option=OutputOptions.can_bus_only, aspirate_while_sensing=True, z_overlap_between_passes_mm=0.1, plunger_reset_offset=2.0, samples_for_baselining=20, sample_time_sec=0.004, - data_files={InstrumentProbeType.PRIMARY: "fake_file_name"}, ) fake_max_z_dist = 10.0 await ot3_hardware.liquid_probe( @@ -1117,10 +1113,10 @@ async def test_multi_liquid_probe( (fake_settings_aspirate.plunger_speed * -1), fake_settings_aspirate.sensor_threshold_pascals, fake_settings_aspirate.plunger_impulse_time, - fake_settings_aspirate.output_option, - fake_settings_aspirate.data_files, + fake_settings_aspirate.samples_for_baselining, probe=InstrumentProbeType.PRIMARY, force_both_sensors=False, + response_queue=None, ) assert mock_liquid_probe.call_count == 3 @@ -1140,7 +1136,7 @@ async def test_liquid_not_found( await ot3_hardware.cache_pipette(OT3Mount.LEFT, instr_data, None) pipette = ot3_hardware.hardware_pipettes[OT3Mount.LEFT.to_mount()] assert pipette - await ot3_hardware.add_tip(OT3Mount.LEFT, 100) + ot3_hardware.add_tip(OT3Mount.LEFT, 100) await ot3_hardware.home() await ot3_hardware.move_to(OT3Mount.LEFT, Point(10, 10, 10)) @@ -1152,10 +1148,12 @@ async def _fake_pos_update_and_raise( plunger_speed: float, threshold_pascals: float, plunger_impulse_time: float, - output_format: OutputOptions = OutputOptions.can_bus_only, - data_files: Optional[Dict[InstrumentProbeType, str]] = None, + num_baseline_reads: int, probe: InstrumentProbeType = InstrumentProbeType.PRIMARY, force_both_sensors: bool = False, + response_queue: Optional[ + asyncio.Queue[Dict[SensorId, List[SensorDataType]]] + ] = None, ) -> float: pos = self._position pos[Axis.by_mount(mount)] += mount_speed * ( @@ -1173,13 +1171,11 @@ async def _fake_pos_update_and_raise( plunger_speed=71.5, plunger_impulse_time=0.2, sensor_threshold_pascals=15, - output_option=OutputOptions.can_bus_only, aspirate_while_sensing=True, z_overlap_between_passes_mm=0.1, plunger_reset_offset=2.0, samples_for_baselining=20, sample_time_sec=0.004, - data_files={InstrumentProbeType.PRIMARY: "fake_file_name"}, ) # with a mount speed of 5, pass overlap of 0.5 and a 0.2s delay on z # the actual distance traveled is 3.5mm per pass @@ -1230,8 +1226,6 @@ async def test_capacitive_probe( 4, 1.0, InstrumentProbeType.PRIMARY, - fake_settings.output_option, - fake_settings.data_files, ) original = moving.set_in_point(here, 0) @@ -1630,7 +1624,7 @@ async def test_prepare_for_aspirate( await ot3_hardware.cache_pipette(mount, instr_data, None) assert ot3_hardware.hardware_pipettes[mount.to_mount()] - await ot3_hardware.add_tip(mount, 100) + ot3_hardware.add_tip(mount, 100) await ot3_hardware.prepare_for_aspirate(OT3Mount.LEFT) mock_move_to_plunger_bottom.assert_called_once_with(OT3Mount.LEFT, 1.0) @@ -1665,7 +1659,7 @@ async def test_plunger_ready_to_aspirate_after_dispense( await ot3_hardware.cache_pipette(mount, instr_data, None) assert ot3_hardware.hardware_pipettes[mount.to_mount()] - await ot3_hardware.add_tip(mount, 100) + ot3_hardware.add_tip(mount, 100) await ot3_hardware.prepare_for_aspirate(OT3Mount.LEFT) assert ot3_hardware.hardware_pipettes[mount.to_mount()].ready_to_aspirate @@ -1726,7 +1720,7 @@ async def test_move_to_plunger_bottom( # tip attached, moving DOWN towards "bottom" position await ot3_hardware.home() - await ot3_hardware.add_tip(mount, 100) + ot3_hardware.add_tip(mount, 100) mock_move.reset_mock() await ot3_hardware.prepare_for_aspirate(mount) # make sure we've done the backlash compensation @@ -2035,34 +2029,45 @@ def set_mock_plunger_configs() -> None: assert len(tip_action.call_args_list) == 2 # first call should be "clamp", moving down first_target = tip_action.call_args_list[0][-1]["targets"][0][0] - assert list(first_target.keys()) == [Axis.Q] - assert first_target[Axis.Q] == 10 + assert first_target == 10 # next call should be "clamp", moving back up second_target = tip_action.call_args_list[1][-1]["targets"][0][0] - assert list(second_target.keys()) == [Axis.Q] - assert second_target[Axis.Q] < 10 + assert second_target < 10 # home should be called after tip_action is done assert len(mock_home_gear_motors.call_args_list) == 1 @pytest.mark.parametrize( - "axes", - [[Axis.X], [Axis.X, Axis.Y], [Axis.X, Axis.Y, Axis.P_L], None], + ("axes_in", "axes_present", "expected_axes"), + [ + ([Axis.X, Axis.Y], [Axis.X, Axis.Y], [Axis.X, Axis.Y]), + ([Axis.X, Axis.Y], [Axis.Y, Axis.Z_L], [Axis.Y]), + (None, list(Axis), list(Axis)), + (None, [Axis.Y, Axis.Z_L], [Axis.Y, Axis.Z_L]), + ], ) async def test_update_position_estimation( ot3_hardware: ThreadManager[OT3API], hardware_backend: OT3Simulator, - axes: List[Axis], + axes_in: List[Axis], + axes_present: List[Axis], + expected_axes: List[Axis], ) -> None: + def _axis_is_present(axis: Axis) -> bool: + return axis in axes_present + with patch.object( hardware_backend, "update_motor_estimation", AsyncMock(spec=hardware_backend.update_motor_estimation), - ) as mock_update: - await ot3_hardware._update_position_estimation(axes) - if axes is None: - axes = [ax for ax in Axis] - mock_update.assert_called_once_with(axes) + ) as mock_update, patch.object( + hardware_backend, + "axis_is_present", + Mock(spec=hardware_backend.axis_is_present), + ) as mock_axis_is_present: + mock_axis_is_present.side_effect = _axis_is_present + await ot3_hardware._update_position_estimation(axes_in) + mock_update.assert_called_once_with(expected_axes) async def test_refresh_positions( diff --git a/api/tests/opentrons/protocol_api/core/engine/test_absorbance_reader_core.py b/api/tests/opentrons/protocol_api/core/engine/test_absorbance_reader_core.py index 613ee3cfbe3..9bc195296a2 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_absorbance_reader_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_absorbance_reader_core.py @@ -11,6 +11,12 @@ from opentrons.protocol_engine.clients import SyncClient as EngineClient from opentrons.protocol_api.core.engine.module_core import AbsorbanceReaderCore from opentrons.protocol_api import MAX_SUPPORTED_VERSION +from opentrons.protocol_engine.errors.exceptions import CannotPerformModuleAction +from opentrons.protocol_engine.state.module_substates import AbsorbanceReaderSubState +from opentrons.protocol_engine.state.module_substates.absorbance_reader_substate import ( + AbsorbanceReaderId, + AbsorbanceReaderMeasureMode, +) SyncAbsorbanceReaderHardware = SynchronousAdapter[AbsorbanceReader] @@ -62,32 +68,98 @@ def test_initialize( decoy: Decoy, mock_engine_client: EngineClient, subject: AbsorbanceReaderCore ) -> None: """It should set the sample wavelength with the engine client.""" - subject.initialize(wavelength=123) + subject._ready_to_initialize = True + subject.initialize("single", [350]) + + decoy.verify( + mock_engine_client.execute_command( + cmd.absorbance_reader.InitializeParams( + moduleId="1234", + measureMode="single", + sampleWavelengths=[350], + referenceWavelength=None, + ), + ), + times=1, + ) + assert subject._initialized_value == [350] + + # Test reference wavelength + subject.initialize("single", [350], 450) + + decoy.verify( + mock_engine_client.execute_command( + cmd.absorbance_reader.InitializeParams( + moduleId="1234", + measureMode="single", + sampleWavelengths=[350], + referenceWavelength=450, + ), + ), + times=1, + ) + assert subject._initialized_value == [350] + + # Test initialize multi + subject.initialize("multi", [350, 400, 450]) decoy.verify( mock_engine_client.execute_command( cmd.absorbance_reader.InitializeParams( moduleId="1234", - sampleWavelength=123, + measureMode="multi", + sampleWavelengths=[350, 400, 450], + referenceWavelength=None, ), ), times=1, ) - assert subject._initialized_value == 123 + assert subject._initialized_value == [350, 400, 450] + + +def test_initialize_not_ready(subject: AbsorbanceReaderCore) -> None: + """It should raise CannotPerformModuleAction if you dont call .close_lid() command.""" + subject._ready_to_initialize = False + with pytest.raises(CannotPerformModuleAction): + subject.initialize("single", [350]) + + +@pytest.mark.parametrize("wavelength", [-350, 0, 1200, "wda"]) +def test_invalid_wavelengths(wavelength: int, subject: AbsorbanceReaderCore) -> None: + """It should raise ValueError if you provide an invalid wavelengthi.""" + subject._ready_to_initialize = True + with pytest.raises(ValueError): + subject.initialize("single", [wavelength]) def test_read( decoy: Decoy, mock_engine_client: EngineClient, subject: AbsorbanceReaderCore ) -> None: """It should call absorbance reader to read with the engine client.""" - subject._initialized_value = 123 - subject.read() + subject._ready_to_initialize = True + subject._initialized_value = [350] + substate = AbsorbanceReaderSubState( + module_id=AbsorbanceReaderId(subject.module_id), + configured=True, + measured=False, + is_lid_on=True, + data=None, + configured_wavelengths=subject._initialized_value, + measure_mode=AbsorbanceReaderMeasureMode("single"), + reference_wavelength=None, + ) + decoy.when( + mock_engine_client.state.modules.get_absorbance_reader_substate( + subject.module_id + ) + ).then_return(substate) + subject.read(filename=None) decoy.verify( mock_engine_client.execute_command( cmd.absorbance_reader.ReadAbsorbanceParams( moduleId="1234", - sampleWavelength=123, + fileName=None, ), ), times=1, diff --git a/api/tests/opentrons/protocol_api/core/engine/test_deck_conflict.py b/api/tests/opentrons/protocol_api/core/engine/test_deck_conflict.py index d0171bff798..208ac843b94 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_deck_conflict.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_deck_conflict.py @@ -6,7 +6,7 @@ from opentrons_shared_data.labware.types import LabwareUri from opentrons_shared_data.robot.types import RobotType -from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType +from opentrons.hardware_control import CriticalPoint from opentrons.motion_planning import deck_conflict as wrapped_deck_conflict from opentrons.motion_planning import adjacent_slots_getters from opentrons.motion_planning.adjacent_slots_getters import _MixedTypeSlots @@ -18,7 +18,7 @@ _TRASH_BIN_CUTOUT_FIXTURE, ) from opentrons.protocol_api.labware import Labware -from opentrons.protocol_api.core.engine import deck_conflict +from opentrons.protocol_api.core.engine import deck_conflict, pipette_movement_conflict from opentrons.protocol_engine import ( Config, DeckSlotLocation, @@ -30,7 +30,13 @@ from opentrons.protocol_engine.clients import SyncClient from opentrons.protocol_engine.errors import LabwareNotLoadedOnModuleError -from opentrons.types import DeckSlotName, Point, StagingSlotName, MountType +from opentrons.types import ( + DeckSlotName, + Point, + StagingSlotName, + MountType, + NozzleConfigurationType, +) from opentrons.protocol_engine.types import ( DeckType, @@ -440,7 +446,7 @@ def test_maps_trash_bins( Point(x=50, y=50, z=40), ), pytest.raises( - deck_conflict.PartialTipMovementNotAllowedError, + pipette_movement_conflict.PartialTipMovementNotAllowedError, match="collision with items in deck slot D1", ), 0, @@ -453,7 +459,7 @@ def test_maps_trash_bins( Point(x=101, y=50, z=40), ), pytest.raises( - deck_conflict.PartialTipMovementNotAllowedError, + pipette_movement_conflict.PartialTipMovementNotAllowedError, match="collision with items in deck slot D2", ), 0, @@ -466,7 +472,7 @@ def test_maps_trash_bins( Point(x=250, y=150, z=40), ), pytest.raises( - deck_conflict.PartialTipMovementNotAllowedError, + pipette_movement_conflict.PartialTipMovementNotAllowedError, match="will result in collision with items in staging slot C4.", ), 170, @@ -510,6 +516,11 @@ def test_deck_conflict_raises_for_bad_pipette_move( MountType.LEFT: Point(463.7, 433.3, 0.0), MountType.RIGHT: Point(517.7, 433.3), }, + deck_extents=Point(477.2, 493.8, 0.0), + padding_rear=-181.21, + padding_front=55.8, + padding_left_side=31.88, + padding_right_side=-80.32, ) ) decoy.when( @@ -540,9 +551,21 @@ def test_deck_conflict_raises_for_bad_pipette_move( well_location=WellLocation(origin=WellOrigin.TOP, offset=WellOffset(z=10)), ) ).then_return(destination_well_point) + decoy.when( + mock_state_view.labware.get_should_center_column_on_target_well( + "destination-labware-id" + ) + ).then_return(False) + decoy.when( + mock_state_view.labware.get_should_center_pipette_on_target_well( + "destination-labware-id" + ) + ).then_return(False) decoy.when( mock_state_view.pipettes.get_pipette_bounds_at_specified_move_to_position( - pipette_id="pipette-id", destination_position=destination_well_point + pipette_id="pipette-id", + destination_position=destination_well_point, + critical_point=None, ) ).then_return(pipette_bounds) @@ -605,7 +628,7 @@ def test_deck_conflict_raises_for_bad_pipette_move( ).then_return(Dimensions(90, 90, 0)) with expected_raise: - deck_conflict.check_safe_for_pipette_movement( + pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=mock_state_view, pipette_id="pipette-id", labware_id="destination-labware-id", @@ -648,9 +671,17 @@ def test_deck_conflict_raises_for_collision_with_tc_lid( well_location=WellLocation(origin=WellOrigin.TOP, offset=WellOffset(z=10)), ) ).then_return(destination_well_point) + + decoy.when( + mock_state_view.labware.get_should_center_column_on_target_well( + "destination-labware-id" + ) + ).then_return(True) decoy.when( mock_state_view.pipettes.get_pipette_bounds_at_specified_move_to_position( - pipette_id="pipette-id", destination_position=destination_well_point + pipette_id="pipette-id", + destination_position=destination_well_point, + critical_point=CriticalPoint.Y_CENTER, ) ).then_return(pipette_bounds_at_destination) decoy.when(mock_state_view.pipettes.get_mount("pipette-id")).then_return( @@ -677,6 +708,11 @@ def test_deck_conflict_raises_for_collision_with_tc_lid( MountType.LEFT: Point(463.7, 433.3, 0.0), MountType.RIGHT: Point(517.7, 433.3), }, + deck_extents=Point(477.2, 493.8, 0.0), + padding_rear=-181.21, + padding_front=55.8, + padding_left_side=31.88, + padding_right_side=-80.32, ) ) @@ -695,10 +731,10 @@ def test_deck_conflict_raises_for_collision_with_tc_lid( True ) with pytest.raises( - deck_conflict.PartialTipMovementNotAllowedError, - match="collision with thermocycler lid in deck slot A1.", + pipette_movement_conflict.PartialTipMovementNotAllowedError, + match="Requested motion with the A12 nozzle partial configuration is outside of robot bounds for the pipette.", ): - deck_conflict.check_safe_for_pipette_movement( + pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=mock_state_view, pipette_id="pipette-id", labware_id="destination-labware-id", @@ -798,7 +834,7 @@ class PipetteMovementSpec(NamedTuple): is_on_flex_adapter=False, is_partial_config=False, expected_raise=pytest.raises( - deck_conflict.UnsuitableTiprackForPipetteMotion, + pipette_movement_conflict.UnsuitableTiprackForPipetteMotion, match="A cool tiprack must be on an Opentrons Flex 96 Tip Rack Adapter", ), ), @@ -815,7 +851,7 @@ class PipetteMovementSpec(NamedTuple): is_on_flex_adapter=False, is_partial_config=False, expected_raise=pytest.raises( - deck_conflict.UnsuitableTiprackForPipetteMotion, + pipette_movement_conflict.UnsuitableTiprackForPipetteMotion, match="A cool tiprack must be on an Opentrons Flex 96 Tip Rack Adapter", ), ), @@ -825,7 +861,7 @@ class PipetteMovementSpec(NamedTuple): is_on_flex_adapter=True, is_partial_config=True, expected_raise=pytest.raises( - deck_conflict.PartialTipMovementNotAllowedError, + pipette_movement_conflict.PartialTipMovementNotAllowedError, match="A cool tiprack cannot be on an adapter taller than the tip rack", ), ), @@ -865,9 +901,9 @@ def test_valid_96_pipette_movement_for_tiprack_and_adapter( ) -> None: """It should raise appropriate error for unsuitable tiprack parent when moving 96 channel to it.""" decoy.when(mock_state_view.pipettes.get_channels("pipette-id")).then_return(96) - decoy.when(mock_state_view.labware.get_dimensions("adapter-id")).then_return( - Dimensions(x=0, y=0, z=100) - ) + decoy.when( + mock_state_view.labware.get_dimensions(labware_id="adapter-id") + ).then_return(Dimensions(x=0, y=0, z=100)) decoy.when(mock_state_view.labware.get_display_name("labware-id")).then_return( "A cool tiprack" ) @@ -877,9 +913,9 @@ def test_valid_96_pipette_movement_for_tiprack_and_adapter( decoy.when(mock_state_view.labware.get_location("labware-id")).then_return( tiprack_parent ) - decoy.when(mock_state_view.labware.get_dimensions("labware-id")).then_return( - tiprack_dim - ) + decoy.when( + mock_state_view.labware.get_dimensions(labware_id="labware-id") + ).then_return(tiprack_dim) decoy.when( mock_state_view.labware.get_has_quirk( labware_id="adapter-id", quirk="tiprackAdapterFor96Channel" @@ -887,7 +923,7 @@ def test_valid_96_pipette_movement_for_tiprack_and_adapter( ).then_return(is_on_flex_adapter) with expected_raise: - deck_conflict.check_safe_for_tip_pickup_and_return( + pipette_movement_conflict.check_safe_for_tip_pickup_and_return( engine_state=mock_state_view, pipette_id="pipette-id", labware_id="labware-id", diff --git a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py index 8854c070ef0..352dcb35c58 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_instrument_core.py @@ -1,23 +1,29 @@ """Test for the ProtocolEngine-based instrument API core.""" -from typing import cast, Optional, Union +from typing import cast, Optional from opentrons_shared_data.errors.exceptions import PipetteLiquidNotFoundError import pytest from decoy import Decoy from decoy import errors +from opentrons_shared_data.liquid_classes.liquid_class_definition import ( + LiquidClassSchemaV1, +) from opentrons_shared_data.pipette.types import PipetteNameType from opentrons.hardware_control import SyncHardwareAPI from opentrons.hardware_control.dev_types import PipetteDict -from opentrons.hardware_control.nozzle_manager import NozzleConfigurationType +from opentrons.protocol_api._liquid_properties import TransferProperties from opentrons.protocol_engine import ( DeckPoint, LoadedPipette, MotorAxis, WellLocation, + LiquidHandlingWellLocation, + PickUpTipWellLocation, WellOffset, WellOrigin, + PickUpTipWellOrigin, DropTipWellLocation, DropTipWellOrigin, ) @@ -33,6 +39,7 @@ SingleNozzleLayoutConfiguration, ColumnNozzleLayoutConfiguration, AddressableOffsetVector, + LiquidClassRecord, ) from opentrons.protocol_api.disposal_locations import ( TrashBin, @@ -40,15 +47,16 @@ DisposalOffset, ) from opentrons.protocol_api._nozzle_layout import NozzleLayout +from opentrons.protocol_api._liquid import LiquidClass from opentrons.protocol_api.core.engine import ( InstrumentCore, WellCore, ProtocolCore, - deck_conflict, + pipette_movement_conflict, ) from opentrons.protocols.api_support.definitions import MAX_SUPPORTED_VERSION from opentrons.protocols.api_support.types import APIVersion -from opentrons.types import Location, Mount, MountType, Point +from opentrons.types import Location, Mount, MountType, Point, NozzleConfigurationType from ... import versions_below, versions_at_or_above @@ -76,8 +84,10 @@ def patch_mock_pipette_movement_safety_check( decoy: Decoy, monkeypatch: pytest.MonkeyPatch ) -> None: """Replace deck_conflict.check() with a mock.""" - mock = decoy.mock(func=deck_conflict.check_safe_for_pipette_movement) - monkeypatch.setattr(deck_conflict, "check_safe_for_pipette_movement", mock) + mock = decoy.mock(func=pipette_movement_conflict.check_safe_for_pipette_movement) + monkeypatch.setattr( + pipette_movement_conflict, "check_safe_for_pipette_movement", mock + ) @pytest.fixture @@ -256,12 +266,16 @@ def test_pick_up_tip( ) decoy.when( - mock_engine_client.state.geometry.get_relative_well_location( + mock_engine_client.state.geometry.get_relative_pick_up_tip_well_location( labware_id="labware-id", well_name="well-name", absolute_point=Point(1, 2, 3), ) - ).then_return(WellLocation(origin=WellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1))) + ).then_return( + PickUpTipWellLocation( + origin=PickUpTipWellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1) + ) + ) subject.pick_up_tip( location=location, @@ -271,18 +285,18 @@ def test_pick_up_tip( ) decoy.verify( - deck_conflict.check_safe_for_tip_pickup_and_return( + pipette_movement_conflict.check_safe_for_tip_pickup_and_return( engine_state=mock_engine_client.state, pipette_id="abc123", labware_id="labware-id", ), - deck_conflict.check_safe_for_pipette_movement( + pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=mock_engine_client.state, pipette_id="abc123", labware_id="labware-id", well_name="well-name", - well_location=WellLocation( - origin=WellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1) + well_location=PickUpTipWellLocation( + origin=PickUpTipWellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1) ), ), mock_engine_client.execute_command( @@ -290,8 +304,8 @@ def test_pick_up_tip( pipetteId="abc123", labwareId="labware-id", wellName="well-name", - wellLocation=WellLocation( - origin=WellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1) + wellLocation=PickUpTipWellLocation( + origin=PickUpTipWellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1) ), ) ), @@ -325,7 +339,7 @@ def test_drop_tip_no_location( subject.drop_tip(location=None, well_core=well_core, home_after=True) decoy.verify( - deck_conflict.check_safe_for_pipette_movement( + pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=mock_engine_client.state, pipette_id="abc123", labware_id="labware-id", @@ -376,12 +390,12 @@ def test_drop_tip_with_location( subject.drop_tip(location=location, well_core=well_core, home_after=True) decoy.verify( - deck_conflict.check_safe_for_tip_pickup_and_return( + pipette_movement_conflict.check_safe_for_tip_pickup_and_return( engine_state=mock_engine_client.state, pipette_id="abc123", labware_id="labware-id", ), - deck_conflict.check_safe_for_pipette_movement( + pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=mock_engine_client.state, pipette_id="abc123", labware_id="labware-id", @@ -489,10 +503,17 @@ def test_aspirate_from_well( ) decoy.when( - mock_engine_client.state.geometry.get_relative_well_location( - labware_id="123abc", well_name="my cool well", absolute_point=Point(1, 2, 3) + mock_engine_client.state.geometry.get_relative_liquid_handling_well_location( + labware_id="123abc", + well_name="my cool well", + absolute_point=Point(1, 2, 3), + is_meniscus=None, ) - ).then_return(WellLocation(origin=WellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1))) + ).then_return( + LiquidHandlingWellLocation( + origin=WellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1) + ) + ) subject.aspirate( location=location, @@ -504,7 +525,7 @@ def test_aspirate_from_well( ) decoy.verify( - deck_conflict.check_safe_for_pipette_movement( + pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=mock_engine_client.state, pipette_id="abc123", labware_id="123abc", @@ -518,7 +539,7 @@ def test_aspirate_from_well( pipetteId="abc123", labwareId="123abc", wellName="my cool well", - wellLocation=WellLocation( + wellLocation=LiquidHandlingWellLocation( origin=WellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1) ), volume=12.34, @@ -529,7 +550,7 @@ def test_aspirate_from_well( ) -def test_aspirate_from_location( +def test_aspirate_from_coordinates( decoy: Decoy, mock_engine_client: EngineClient, mock_protocol_core: ProtocolCore, @@ -567,6 +588,72 @@ def test_aspirate_from_location( ) +def test_aspirate_from_meniscus( + decoy: Decoy, + mock_engine_client: EngineClient, + mock_protocol_core: ProtocolCore, + subject: InstrumentCore, +) -> None: + """It should aspirate from a well.""" + location = Location(point=Point(1, 2, 3), labware=None) + + well_core = WellCore( + name="my cool well", labware_id="123abc", engine_client=mock_engine_client + ) + + decoy.when( + mock_engine_client.state.geometry.get_relative_liquid_handling_well_location( + labware_id="123abc", + well_name="my cool well", + absolute_point=Point(1, 2, 3), + is_meniscus=True, + ) + ).then_return( + LiquidHandlingWellLocation( + origin=WellOrigin.MENISCUS, offset=WellOffset(x=3, y=2, z=1), volumeOffset=0 + ) + ) + + subject.aspirate( + location=location, + well_core=well_core, + volume=12.34, + rate=5.6, + flow_rate=7.8, + in_place=False, + is_meniscus=True, + ) + + decoy.verify( + pipette_movement_conflict.check_safe_for_pipette_movement( + engine_state=mock_engine_client.state, + pipette_id="abc123", + labware_id="123abc", + well_name="my cool well", + well_location=LiquidHandlingWellLocation( + origin=WellOrigin.MENISCUS, + offset=WellOffset(x=3, y=2, z=1), + volumeOffset="operationVolume", + ), + ), + mock_engine_client.execute_command( + cmd.AspirateParams( + pipetteId="abc123", + labwareId="123abc", + wellName="my cool well", + wellLocation=LiquidHandlingWellLocation( + origin=WellOrigin.MENISCUS, + offset=WellOffset(x=3, y=2, z=1), + volumeOffset="operationVolume", + ), + volume=12.34, + flowRate=7.8, + ) + ), + mock_protocol_core.set_last_location(location=location, mount=Mount.LEFT), + ) + + def test_aspirate_in_place( decoy: Decoy, mock_engine_client: EngineClient, @@ -618,7 +705,7 @@ def test_blow_out_to_well( subject.blow_out(location=location, well_core=well_core, in_place=False) decoy.verify( - deck_conflict.check_safe_for_pipette_movement( + pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=mock_engine_client.state, pipette_id="abc123", labware_id="123abc", @@ -713,10 +800,17 @@ def test_dispense_to_well( decoy.when(mock_protocol_core.api_version).then_return(MAX_SUPPORTED_VERSION) decoy.when( - mock_engine_client.state.geometry.get_relative_well_location( - labware_id="123abc", well_name="my cool well", absolute_point=Point(1, 2, 3) + mock_engine_client.state.geometry.get_relative_liquid_handling_well_location( + labware_id="123abc", + well_name="my cool well", + absolute_point=Point(1, 2, 3), + is_meniscus=None, ) - ).then_return(WellLocation(origin=WellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1))) + ).then_return( + LiquidHandlingWellLocation( + origin=WellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1) + ) + ) subject.dispense( location=location, @@ -729,7 +823,7 @@ def test_dispense_to_well( ) decoy.verify( - deck_conflict.check_safe_for_pipette_movement( + pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=mock_engine_client.state, pipette_id="abc123", labware_id="123abc", @@ -743,7 +837,7 @@ def test_dispense_to_well( pipetteId="abc123", labwareId="123abc", wellName="my cool well", - wellLocation=WellLocation( + wellLocation=LiquidHandlingWellLocation( origin=WellOrigin.TOP, offset=WellOffset(x=3, y=2, z=1) ), volume=12.34, @@ -1113,7 +1207,7 @@ def test_touch_tip( ) decoy.verify( - deck_conflict.check_safe_for_pipette_movement( + pipette_movement_conflict.check_safe_for_pipette_movement( engine_state=mock_engine_client.state, pipette_id="abc123", labware_id="123abc", @@ -1225,17 +1319,14 @@ def test_configure_nozzle_layout( argnames=["pipette_channels", "nozzle_layout", "primary_nozzle", "expected_result"], argvalues=[ (96, NozzleConfigurationType.FULL, "A1", True), - (96, NozzleConfigurationType.FULL, None, True), (96, NozzleConfigurationType.ROW, "A1", True), (96, NozzleConfigurationType.COLUMN, "A1", True), (96, NozzleConfigurationType.COLUMN, "A12", True), (96, NozzleConfigurationType.SINGLE, "H12", True), (96, NozzleConfigurationType.SINGLE, "A1", True), (8, NozzleConfigurationType.FULL, "A1", True), - (8, NozzleConfigurationType.FULL, None, True), (8, NozzleConfigurationType.SINGLE, "H1", True), (8, NozzleConfigurationType.SINGLE, "A1", True), - (1, NozzleConfigurationType.FULL, None, True), ], ) def test_is_tip_tracking_available( @@ -1244,7 +1335,7 @@ def test_is_tip_tracking_available( subject: InstrumentCore, pipette_channels: int, nozzle_layout: NozzleConfigurationType, - primary_nozzle: Union[str, None], + primary_nozzle: str, expected_result: bool, ) -> None: """It should return whether tip tracking is available based on nozzle configuration.""" @@ -1409,3 +1500,59 @@ def test_liquid_probe_with_recovery( ) ) ) + + +def test_load_liquid_class( + decoy: Decoy, + mock_engine_client: EngineClient, + subject: InstrumentCore, + minimal_liquid_class_def2: LiquidClassSchemaV1, +) -> None: + """It should send the load liquid class command to the engine.""" + sample_aspirate_data = minimal_liquid_class_def2.byPipette[0].byTipType[0].aspirate + sample_single_dispense_data = ( + minimal_liquid_class_def2.byPipette[0].byTipType[0].singleDispense + ) + sample_multi_dispense_data = ( + minimal_liquid_class_def2.byPipette[0].byTipType[0].multiDispense + ) + + test_liq_class = decoy.mock(cls=LiquidClass) + test_transfer_props = decoy.mock(cls=TransferProperties) + + decoy.when( + test_liq_class.get_for("flex_1channel_50", "opentrons_flex_96_tiprack_50ul") + ).then_return(test_transfer_props) + decoy.when(test_liq_class.name).then_return("water") + decoy.when( + mock_engine_client.state.pipettes.get_model_name(subject.pipette_id) + ).then_return("flex_1channel_50") + decoy.when(test_transfer_props.aspirate.as_shared_data_model()).then_return( + sample_aspirate_data + ) + decoy.when(test_transfer_props.dispense.as_shared_data_model()).then_return( + sample_single_dispense_data + ) + decoy.when(test_transfer_props.multi_dispense.as_shared_data_model()).then_return( # type: ignore[union-attr] + sample_multi_dispense_data + ) + decoy.when( + mock_engine_client.execute_command_without_recovery( + cmd.LoadLiquidClassParams( + liquidClassRecord=LiquidClassRecord( + liquidClassName="water", + pipetteModel="flex_1channel_50", + tiprack="opentrons_flex_96_tiprack_50ul", + aspirate=sample_aspirate_data, + singleDispense=sample_single_dispense_data, + multiDispense=sample_multi_dispense_data, + ) + ) + ) + ).then_return(cmd.LoadLiquidClassResult(liquidClassId="liquid-class-id")) + result = subject.load_liquid_class( + liquid_class=test_liq_class, + pipette_load_name="flex_1channel_50", + tiprack_uri="opentrons_flex_96_tiprack_50ul", + ) + assert result == "liquid-class-id" diff --git a/api/tests/opentrons/protocol_api/core/engine/test_labware_core.py b/api/tests/opentrons/protocol_api/core/engine/test_labware_core.py index 847c80d2125..6f4458f87ff 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_labware_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_labware_core.py @@ -1,4 +1,5 @@ """Tests for opentrons.protocol_api.core.engine.LabwareCore.""" + from typing import cast import pytest @@ -25,7 +26,7 @@ LabwareOffsetLocation, LabwareOffsetVector, ) - +from opentrons.protocol_api._liquid import Liquid from opentrons.protocol_api.core.labware import LabwareLoadParams from opentrons.protocol_api.core.engine import LabwareCore, WellCore from opentrons.calibration_storage.helpers import uri_from_details @@ -80,7 +81,9 @@ def test_get_load_params(subject: LabwareCore) -> None: version=42, parameters=LabwareDefinitionParameters.construct(loadName="world"), # type: ignore[call-arg] ordering=[], - metadata=LabwareDefinitionMetadata.construct(displayName="what a cool labware"), # type: ignore[call-arg] + metadata=LabwareDefinitionMetadata.construct( + displayName="what a cool labware" + ), # type: ignore[call-arg] ) ], ) @@ -455,3 +458,40 @@ def test_get_deck_slot( ).then_raise(LabwareNotOnDeckError("oh no")) assert subject.get_deck_slot() is None + + +def test_load_liquid( + decoy: Decoy, mock_engine_client: EngineClient, subject: LabwareCore +) -> None: + """It should pass loaded liquids to the engine.""" + mock_liquid = Liquid( + _id="liquid-id", name="water", description=None, display_color=None + ) + subject.load_liquid(volumes={"A1": 20, "B1": 30, "C1": 40}, liquid=mock_liquid) + + decoy.verify( + mock_engine_client.execute_command( + cmd.LoadLiquidParams( + labwareId="cool-labware", + liquidId="liquid-id", + volumeByWell={"A1": 20, "B1": 30, "C1": 40}, + ) + ), + times=1, + ) + + +def test_load_empty( + decoy: Decoy, mock_engine_client: EngineClient, subject: LabwareCore +) -> None: + """It should pass empty liquids to the engine.""" + subject.load_empty(wells=["A1", "B1", "C1"]) + decoy.verify( + mock_engine_client.execute_command( + cmd.LoadLiquidParams( + labwareId="cool-labware", + liquidId="EMPTY", + volumeByWell={"A1": 0.0, "B1": 0.0, "C1": 0.0}, + ) + ) + ) diff --git a/api/tests/opentrons/protocol_api/core/engine/test_protocol_core.py b/api/tests/opentrons/protocol_api/core/engine/test_protocol_core.py index aa575ea1f16..1cf6bc57049 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_protocol_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_protocol_core.py @@ -3,7 +3,10 @@ from typing import Optional, Type, cast, Tuple import pytest -from pytest_lazyfixture import lazy_fixture # type: ignore[import-untyped] +from opentrons_shared_data import liquid_classes +from opentrons_shared_data.liquid_classes.liquid_class_definition import ( + LiquidClassSchemaV1, +) from decoy import Decoy from opentrons_shared_data.deck import load as load_deck @@ -22,7 +25,7 @@ from opentrons.types import DeckSlotName, StagingSlotName, Mount, MountType, Point from opentrons.protocol_api import OFF_DECK from opentrons.hardware_control import SyncHardwareAPI, SynchronousAdapter -from opentrons.hardware_control.modules import AbstractModule, ModuleType +from opentrons.hardware_control.modules import AbstractModule from opentrons.hardware_control.modules.types import ( ModuleModel, TemperatureModuleModel, @@ -69,7 +72,7 @@ ModuleCore, load_labware_params, ) -from opentrons.protocol_api._liquid import Liquid +from opentrons.protocol_api._liquid import Liquid, LiquidClass from opentrons.protocol_api.disposal_locations import TrashBin, WasteChute from opentrons.protocol_api.core.engine.exceptions import InvalidModuleLocationError from opentrons.protocol_api.core.engine.module_core import ( @@ -111,6 +114,15 @@ def patch_mock_load_labware_params( monkeypatch.setattr(load_labware_params, name, decoy.mock(func=func)) +@pytest.fixture(autouse=True) +def patch_mock_load_liquid_class_def( + decoy: Decoy, monkeypatch: pytest.MonkeyPatch +) -> None: + """Mock out liquid_classes.load_definition() function.""" + mock_load = decoy.mock(func=liquid_classes.load_definition) + monkeypatch.setattr(liquid_classes, "load_definition", mock_load) + + @pytest.fixture(autouse=True) def patch_mock_validation(decoy: Decoy, monkeypatch: pytest.MonkeyPatch) -> None: """Mock out validation.py functions.""" @@ -1187,7 +1199,6 @@ def test_add_labware_definition( "requested_model", "engine_model", "expected_core_cls", - "deck_def", "slot_name", "robot_type", ), @@ -1196,7 +1207,6 @@ def test_add_labware_definition( TemperatureModuleModel.TEMPERATURE_V1, EngineModuleModel.TEMPERATURE_MODULE_V1, TemperatureModuleCore, - lazy_fixture("ot2_standard_deck_def"), DeckSlotName.SLOT_1, "OT-2 Standard", ), @@ -1204,7 +1214,6 @@ def test_add_labware_definition( TemperatureModuleModel.TEMPERATURE_V2, EngineModuleModel.TEMPERATURE_MODULE_V2, TemperatureModuleCore, - lazy_fixture("ot3_standard_deck_def"), DeckSlotName.SLOT_D1, "OT-3 Standard", ), @@ -1212,7 +1221,6 @@ def test_add_labware_definition( MagneticModuleModel.MAGNETIC_V1, EngineModuleModel.MAGNETIC_MODULE_V1, MagneticModuleCore, - lazy_fixture("ot2_standard_deck_def"), DeckSlotName.SLOT_1, "OT-2 Standard", ), @@ -1220,7 +1228,6 @@ def test_add_labware_definition( ThermocyclerModuleModel.THERMOCYCLER_V1, EngineModuleModel.THERMOCYCLER_MODULE_V1, ThermocyclerModuleCore, - lazy_fixture("ot2_standard_deck_def"), DeckSlotName.SLOT_7, "OT-2 Standard", ), @@ -1228,7 +1235,6 @@ def test_add_labware_definition( ThermocyclerModuleModel.THERMOCYCLER_V2, EngineModuleModel.THERMOCYCLER_MODULE_V2, ThermocyclerModuleCore, - lazy_fixture("ot3_standard_deck_def"), DeckSlotName.SLOT_B1, "OT-3 Standard", ), @@ -1236,7 +1242,6 @@ def test_add_labware_definition( HeaterShakerModuleModel.HEATER_SHAKER_V1, EngineModuleModel.HEATER_SHAKER_MODULE_V1, HeaterShakerModuleCore, - lazy_fixture("ot3_standard_deck_def"), DeckSlotName.SLOT_A1, "OT-3 Standard", ), @@ -1252,7 +1257,6 @@ def test_load_module( engine_model: EngineModuleModel, expected_core_cls: Type[ModuleCore], subject: ProtocolCore, - deck_def: DeckDefinitionV5, slot_name: DeckSlotName, robot_type: RobotType, ) -> None: @@ -1268,23 +1272,6 @@ def test_load_module( [mock_hw_mod_1, mock_hw_mod_2] ) - if robot_type == "OT-2 Standard": - decoy.when(subject.get_slot_definition(slot_name)).then_return( - cast( - SlotDefV3, - {"compatibleModuleTypes": [ModuleType.from_model(requested_model)]}, - ) - ) - else: - decoy.when( - mock_engine_client.state.addressable_areas.state.deck_definition - ).then_return(deck_def) - decoy.when( - mock_engine_client.state.addressable_areas.get_cutout_id_by_deck_slot_name( - slot_name - ) - ).then_return("cutout" + slot_name.value) - decoy.when(mock_engine_client.state.config.robot_type).then_return(robot_type) decoy.when( @@ -1343,34 +1330,13 @@ def test_load_module( def test_load_mag_block( decoy: Decoy, mock_engine_client: EngineClient, - mock_sync_hardware_api: SyncHardwareAPI, subject: ProtocolCore, - ot3_standard_deck_def: DeckDefinitionV5, ) -> None: """It should issue a load module engine command.""" definition = ModuleDefinition.construct() # type: ignore[call-arg] decoy.when(mock_engine_client.state.config.robot_type).then_return("OT-3 Standard") - decoy.when(subject.get_slot_definition(DeckSlotName.SLOT_A2)).then_return( - cast( - SlotDefV3, - { - "compatibleModuleTypes": [ - ModuleType.from_model(MagneticBlockModel.MAGNETIC_BLOCK_V1) - ] - }, - ) - ) - decoy.when( - mock_engine_client.state.addressable_areas.state.deck_definition - ).then_return(ot3_standard_deck_def) - decoy.when( - mock_engine_client.state.addressable_areas.get_cutout_id_by_deck_slot_name( - DeckSlotName.SLOT_A2 - ) - ).then_return("cutout" + DeckSlotName.SLOT_A2.value) - decoy.when( mock_engine_client.execute_command_without_recovery( cmd.LoadModuleParams( @@ -1423,18 +1389,16 @@ def test_load_mag_block( @pytest.mark.parametrize( - ("requested_model", "engine_model", "deck_def", "expected_slot"), + ("requested_model", "engine_model", "expected_slot"), [ ( ThermocyclerModuleModel.THERMOCYCLER_V1, EngineModuleModel.THERMOCYCLER_MODULE_V1, - lazy_fixture("ot3_standard_deck_def"), DeckSlotName.SLOT_B1, ), ( ThermocyclerModuleModel.THERMOCYCLER_V2, EngineModuleModel.THERMOCYCLER_MODULE_V2, - lazy_fixture("ot3_standard_deck_def"), DeckSlotName.SLOT_B1, ), ], @@ -1446,7 +1410,6 @@ def test_load_module_thermocycler_with_no_location( requested_model: ModuleModel, engine_model: EngineModuleModel, subject: ProtocolCore, - deck_def: DeckDefinitionV5, expected_slot: DeckSlotName, ) -> None: """It should issue a load module engine command with location at 7.""" @@ -1456,14 +1419,6 @@ def test_load_module_thermocycler_with_no_location( decoy.when(mock_hw_mod.device_info).then_return({"serial": "xyz789"}) decoy.when(mock_sync_hardware_api.attached_modules).then_return([mock_hw_mod]) decoy.when(mock_engine_client.state.config.robot_type).then_return("OT-3 Standard") - decoy.when( - mock_engine_client.state.addressable_areas.state.deck_definition - ).then_return(deck_def) - decoy.when( - mock_engine_client.state.addressable_areas.get_cutout_id_by_deck_slot_name( - expected_slot - ) - ).then_return("cutout" + expected_slot.value) decoy.when( mock_engine_client.execute_command_without_recovery( @@ -1742,6 +1697,27 @@ def test_add_liquid( assert result == expected_result +def test_define_liquid_class( + decoy: Decoy, + subject: ProtocolCore, + minimal_liquid_class_def1: LiquidClassSchemaV1, + minimal_liquid_class_def2: LiquidClassSchemaV1, +) -> None: + """It should create a LiquidClass and cache the definition.""" + expected_liquid_class = LiquidClass( + _name="water1", _display_name="water 1", _by_pipette_setting={} + ) + decoy.when(liquid_classes.load_definition("water")).then_return( + minimal_liquid_class_def1 + ) + assert subject.define_liquid_class("water") == expected_liquid_class + + decoy.when(liquid_classes.load_definition("water")).then_return( + minimal_liquid_class_def2 + ) + assert subject.define_liquid_class("water") == expected_liquid_class + + def test_get_labware_location_deck_slot( decoy: Decoy, mock_engine_client: EngineClient, diff --git a/api/tests/opentrons/protocol_api/core/engine/test_thermocycler_core.py b/api/tests/opentrons/protocol_api/core/engine/test_thermocycler_core.py index eb429065d0a..1ee868ad84b 100644 --- a/api/tests/opentrons/protocol_api/core/engine/test_thermocycler_core.py +++ b/api/tests/opentrons/protocol_api/core/engine/test_thermocycler_core.py @@ -1,5 +1,7 @@ """Tests for the engine based Protocol API module core implementations.""" +from typing import cast import pytest +from _pytest.fixtures import SubRequest from decoy import Decoy from opentrons.drivers.types import ThermocyclerLidStatus @@ -13,6 +15,8 @@ from opentrons.protocol_engine.clients import SyncClient as EngineClient from opentrons.protocol_api.core.engine.module_core import ThermocyclerModuleCore from opentrons.protocol_api import MAX_SUPPORTED_VERSION +from opentrons.protocols.api_support.types import APIVersion +from ... import versions_below, versions_at_or_above SyncThermocyclerHardware = SynchronousAdapter[Thermocycler] @@ -34,7 +38,7 @@ def subject( mock_engine_client: EngineClient, mock_sync_module_hardware: SyncThermocyclerHardware, ) -> ThermocyclerModuleCore: - """Get a HeaterShakerModuleCore test subject.""" + """Get a ThermocyclerModuleCore test subject.""" return ThermocyclerModuleCore( module_id="1234", engine_client=mock_engine_client, @@ -43,6 +47,36 @@ def subject( ) +@pytest.fixture(params=versions_below(APIVersion(2, 21), flex_only=False)) +def subject_below_221( + request: SubRequest, + mock_engine_client: EngineClient, + mock_sync_module_hardware: SyncThermocyclerHardware, +) -> ThermocyclerModuleCore: + """Get a ThermocyclerCore below API version 2.21.""" + return ThermocyclerModuleCore( + module_id="1234", + engine_client=mock_engine_client, + api_version=cast(APIVersion, request.param), + sync_module_hardware=mock_sync_module_hardware, + ) + + +@pytest.fixture(params=versions_at_or_above(APIVersion(2, 21))) +def subject_at_or_above_221( + request: SubRequest, + mock_engine_client: EngineClient, + mock_sync_module_hardware: SyncThermocyclerHardware, +) -> ThermocyclerModuleCore: + """Get a ThermocyclerCore below API version 2.21.""" + return ThermocyclerModuleCore( + module_id="1234", + engine_client=mock_engine_client, + api_version=cast(APIVersion, request.param), + sync_module_hardware=mock_sync_module_hardware, + ) + + def test_create( decoy: Decoy, mock_engine_client: EngineClient, @@ -159,11 +193,13 @@ def test_wait_for_lid_temperature( ) -def test_execute_profile( - decoy: Decoy, mock_engine_client: EngineClient, subject: ThermocyclerModuleCore +def test_execute_profile_below_221( + decoy: Decoy, + mock_engine_client: EngineClient, + subject_below_221: ThermocyclerModuleCore, ) -> None: """It should run a thermocycler profile with the engine client.""" - subject.execute_profile( + subject_below_221.execute_profile( steps=[{"temperature": 45.6, "hold_time_seconds": 12.3}], repetitions=2, block_max_volume=78.9, @@ -187,6 +223,43 @@ def test_execute_profile( ) +def test_execute_profile_above_221( + decoy: Decoy, + mock_engine_client: EngineClient, + subject_at_or_above_221: ThermocyclerModuleCore, +) -> None: + """It should run a thermocycler profile with the engine client.""" + subject_at_or_above_221.execute_profile( + steps=[ + {"temperature": 45.6, "hold_time_seconds": 12.3}, + {"temperature": 78.9, "hold_time_seconds": 45.6}, + ], + repetitions=2, + block_max_volume=25, + ) + decoy.verify( + mock_engine_client.execute_command( + cmd.thermocycler.RunExtendedProfileParams( + moduleId="1234", + profileElements=[ + cmd.thermocycler.ProfileCycle( + repetitions=2, + steps=[ + cmd.thermocycler.ProfileStep( + celsius=45.6, holdSeconds=12.3 + ), + cmd.thermocycler.ProfileStep( + celsius=78.9, holdSeconds=45.6 + ), + ], + ) + ], + blockMaxVolumeUl=25, + ) + ) + ) + + def test_deactivate_lid( decoy: Decoy, mock_engine_client: EngineClient, subject: ThermocyclerModuleCore ) -> None: diff --git a/api/tests/opentrons/protocol_api/core/legacy/test_labware_offset_provider.py b/api/tests/opentrons/protocol_api/core/legacy/test_labware_offset_provider.py index 95cb07aa2cf..1cf0bb360fb 100644 --- a/api/tests/opentrons/protocol_api/core/legacy/test_labware_offset_provider.py +++ b/api/tests/opentrons/protocol_api/core/legacy/test_labware_offset_provider.py @@ -15,7 +15,7 @@ LabwareOffsetLocation, ModuleModel, ) -from opentrons.protocol_engine.state import LabwareView +from opentrons.protocol_engine.state.labware import LabwareView from opentrons.protocol_api.core.labware import LabwareLoadParams from opentrons.protocol_api.core.legacy.labware_offset_provider import ( diff --git a/api/tests/opentrons/protocol_api/test_instrument_context.py b/api/tests/opentrons/protocol_api/test_instrument_context.py index 3478ceb9a86..3f639aff922 100644 --- a/api/tests/opentrons/protocol_api/test_instrument_context.py +++ b/api/tests/opentrons/protocol_api/test_instrument_context.py @@ -1,4 +1,5 @@ """Tests for the InstrumentContext public interface.""" + import inspect import pytest from collections import OrderedDict @@ -9,14 +10,15 @@ from decoy import Decoy from pytest_lazyfixture import lazy_fixture # type: ignore[import-untyped] +from opentrons.config import feature_flags as ff from opentrons.protocol_engine.commands.pipetting_common import LiquidNotFoundError from opentrons.protocol_engine.errors.error_occurrence import ( ProtocolCommandFailedError, ) from opentrons.legacy_broker import LegacyBroker +from opentrons.protocols.advanced_control.transfers.common import TransferTipPolicyV2 -from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError from tests.opentrons.protocol_api.partial_tip_configurations import ( PipetteReliantNozzleConfigSpec, PIPETTE_RELIANT_TEST_SPECS, @@ -27,11 +29,6 @@ INSTRUMENT_CORE_NOZZLE_LAYOUT_TEST_SPECS, ExpectedCoreArgs, ) -from tests.opentrons.protocol_engine.pipette_fixtures import ( - NINETY_SIX_COLS, - NINETY_SIX_MAP, - NINETY_SIX_ROWS, -) from opentrons.protocols.api_support import instrument as mock_instrument_support from opentrons.protocols.api_support.types import APIVersion from opentrons.protocols.api_support.util import ( @@ -47,6 +44,7 @@ Well, labware, validation as mock_validation, + LiquidClass, ) from opentrons.protocol_api.validation import WellTarget, PointTarget from opentrons.protocol_api.core.common import InstrumentCore, ProtocolCore @@ -56,12 +54,17 @@ from opentrons.hardware_control.nozzle_manager import NozzleMap from opentrons.protocol_api.disposal_locations import TrashBin, WasteChute -from opentrons_shared_data.pipette.pipette_definition import ValidNozzleMaps from opentrons.types import Location, Mount, Point +from opentrons_shared_data.pipette.pipette_definition import ValidNozzleMaps from opentrons_shared_data.errors.exceptions import ( CommandPreconditionViolated, ) +from opentrons_shared_data.liquid_classes.liquid_class_definition import ( + LiquidClassSchemaV1, +) +from opentrons_shared_data.robot.types import RobotTypeEnum, RobotType +from . import versions_at_or_above, versions_between @pytest.fixture(autouse=True) @@ -89,6 +92,20 @@ def mock_instrument_core(decoy: Decoy) -> InstrumentCore: """Get a mock instrument implementation core.""" instrument_core = decoy.mock(cls=InstrumentCore) decoy.when(instrument_core.get_mount()).then_return(Mount.LEFT) + decoy.when(instrument_core._pressure_supported_by_pipette()).then_return(True) + # we need to add this for the mock of liquid_presence detection to actually work + # this replaces the mock with a a property again + instrument_core._liquid_presence_detection = False # type: ignore[attr-defined] + + def _setter(enable: bool) -> None: + instrument_core._liquid_presence_detection = enable # type: ignore[attr-defined] + + def _getter() -> bool: + return instrument_core._liquid_presence_detection # type: ignore[attr-defined, no-any-return] + + instrument_core.get_liquid_presence_detection = _getter # type: ignore[method-assign] + instrument_core.set_liquid_presence_detection = _setter # type: ignore[method-assign] + return instrument_core @@ -325,6 +342,7 @@ def test_aspirate( volume=42.0, rate=1.23, flow_rate=5.67, + is_meniscus=None, ), times=1, ) @@ -362,6 +380,47 @@ def test_aspirate_well_location( volume=42.0, rate=1.23, flow_rate=5.67, + is_meniscus=None, + ), + times=1, + ) + + +def test_aspirate_meniscus_well_location( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, +) -> None: + """It should aspirate to a well.""" + mock_well = decoy.mock(cls=Well) + input_location = Location( + point=Point(2, 2, 2), labware=mock_well, _ot_internal_is_meniscus=True + ) + last_location = Location(point=Point(9, 9, 9), labware=None) + decoy.when(mock_instrument_core.get_mount()).then_return(Mount.RIGHT) + + decoy.when(mock_protocol_core.get_last_location(Mount.RIGHT)).then_return( + last_location + ) + decoy.when( + mock_validation.validate_location( + location=input_location, last_location=last_location + ) + ).then_return(WellTarget(well=mock_well, location=input_location, in_place=False)) + decoy.when(mock_instrument_core.get_aspirate_flow_rate(1.23)).then_return(5.67) + + subject.aspirate(volume=42.0, location=input_location, rate=1.23) + + decoy.verify( + mock_instrument_core.aspirate( + location=input_location, + well_core=mock_well._core, + in_place=False, + volume=42.0, + rate=1.23, + flow_rate=5.67, + is_meniscus=True, ), times=1, ) @@ -398,6 +457,7 @@ def test_aspirate_from_coordinates( volume=42.0, rate=1.23, flow_rate=5.67, + is_meniscus=None, ), times=1, ) @@ -911,6 +971,7 @@ def test_dispense_with_location( rate=1.23, flow_rate=5.67, push_out=None, + is_meniscus=None, ), times=1, ) @@ -949,6 +1010,7 @@ def test_dispense_with_well_location( rate=1.23, flow_rate=3.0, push_out=7, + is_meniscus=None, ), times=1, ) @@ -989,6 +1051,7 @@ def test_dispense_with_well( rate=1.23, flow_rate=5.67, push_out=None, + is_meniscus=None, ), times=1, ) @@ -1243,6 +1306,7 @@ def test_dispense_0_volume_means_dispense_everything( rate=1.23, flow_rate=5.67, push_out=None, + is_meniscus=None, ), times=1, ) @@ -1272,6 +1336,7 @@ def test_dispense_0_volume_means_dispense_nothing( rate=1.23, flow_rate=5.67, push_out=None, + is_meniscus=None, ), times=1, ) @@ -1311,6 +1376,7 @@ def test_aspirate_0_volume_means_aspirate_everything( volume=200, rate=1.23, flow_rate=5.67, + is_meniscus=None, ), times=1, ) @@ -1350,6 +1416,7 @@ def test_aspirate_0_volume_means_aspirate_nothing( volume=0, rate=1.23, flow_rate=5.67, + is_meniscus=None, ), times=1, ) @@ -1425,54 +1492,515 @@ def test_measure_liquid_height( assert pcfe.value is errorToRaise -def test_96_tip_config_valid( - decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext +@pytest.mark.parametrize( + "api_version", + versions_between( + low_exclusive_bound=APIVersion(2, 13), high_inclusive_bound=APIVersion(2, 21) + ), +) +def test_mix_no_lpd( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, ) -> None: - """It should error when there's no tips on the correct corner nozzles.""" - nozzle_map = NozzleMap.build( - physical_nozzles=NINETY_SIX_MAP, - physical_rows=NINETY_SIX_ROWS, - physical_columns=NINETY_SIX_COLS, - starting_nozzle="A5", - back_left_nozzle="A5", - front_right_nozzle="H5", - valid_nozzle_maps=ValidNozzleMaps(maps={"Column12": NINETY_SIX_COLS["5"]}), - ) - decoy.when(mock_instrument_core.get_nozzle_map()).then_return(nozzle_map) - decoy.when(mock_instrument_core.get_active_channels()).then_return(96) - with pytest.raises(TipNotAttachedError): - subject._96_tip_config_valid() - - -def test_96_tip_config_invalid( - decoy: Decoy, mock_instrument_core: InstrumentCore, subject: InstrumentContext + """It should aspirate/dispense to a well several times.""" + mock_well = decoy.mock(cls=Well) + + bottom_location = Location(point=Point(1, 2, 3), labware=mock_well) + top_location = Location(point=Point(3, 2, 1), labware=None) + input_location = Location(point=Point(2, 2, 2), labware=None) + last_location = Location(point=Point(9, 9, 9), labware=None) + + decoy.when(mock_protocol_core.get_last_location(Mount.LEFT)).then_return( + last_location + ) + decoy.when( + mock_validation.validate_location( + location=input_location, last_location=last_location + ) + ).then_return(WellTarget(well=mock_well, location=None, in_place=False)) + decoy.when( + mock_validation.validate_location(location=None, last_location=last_location) + ).then_return(WellTarget(well=mock_well, location=None, in_place=False)) + decoy.when(mock_well.bottom(z=1.0)).then_return(bottom_location) + decoy.when(mock_well.top()).then_return(top_location) + decoy.when(mock_instrument_core.get_aspirate_flow_rate(1.23)).then_return(5.67) + decoy.when(mock_instrument_core.get_dispense_flow_rate(1.23)).then_return(5.67) + decoy.when(mock_instrument_core.has_tip()).then_return(True) + decoy.when(mock_instrument_core.get_current_volume()).then_return(0.0) + + subject.mix(repetitions=10, volume=10.0, location=input_location, rate=1.23) + decoy.verify( + mock_instrument_core.aspirate( + bottom_location, + mock_well._core, + 10.0, + 1.23, + 5.67, + False, + None, + ), + times=10, + ) + + # Slight differences in dispense push-out logic for 2.14 and 2.15 api levels + if subject.api_version < APIVersion(2, 16): + decoy.verify( + mock_instrument_core.dispense( + bottom_location, + mock_well._core, + 10.0, + 1.23, + 5.67, + False, + None, + None, + ), + times=10, + ) + else: + decoy.verify( + mock_instrument_core.dispense( + bottom_location, + mock_well._core, + 10.0, + 1.23, + 5.67, + False, + 0.0, + None, + ), + times=9, + ) + decoy.verify( + mock_instrument_core.dispense( + bottom_location, + mock_well._core, + 10.0, + 1.23, + 5.67, + False, + None, + None, + ), + times=1, + ) + + decoy.verify( + mock_instrument_core.liquid_probe_with_recovery(mock_well._core, top_location), + times=0, + ) + + +@pytest.mark.ot3_only +@pytest.mark.parametrize("api_version", versions_at_or_above(APIVersion(2, 21))) +def test_mix_with_lpd( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_protocol_core: ProtocolCore, ) -> None: - """It should return True when there are tips on the correct corner nozzles.""" - nozzle_map = NozzleMap.build( - physical_nozzles=NINETY_SIX_MAP, - physical_rows=NINETY_SIX_ROWS, - physical_columns=NINETY_SIX_COLS, - starting_nozzle="A1", - back_left_nozzle="A1", - front_right_nozzle="H12", - valid_nozzle_maps=ValidNozzleMaps( - maps={ - "Full": sum( - [ - NINETY_SIX_ROWS["A"], - NINETY_SIX_ROWS["B"], - NINETY_SIX_ROWS["C"], - NINETY_SIX_ROWS["D"], - NINETY_SIX_ROWS["E"], - NINETY_SIX_ROWS["F"], - NINETY_SIX_ROWS["G"], - NINETY_SIX_ROWS["H"], - ], - [], - ) - } + """It should aspirate/dispense to a well several times and do 1 lpd.""" + mock_well = decoy.mock(cls=Well) + bottom_location = Location(point=Point(1, 2, 3), labware=mock_well) + top_location = Location(point=Point(3, 2, 1), labware=None) + input_location = Location(point=Point(2, 2, 2), labware=None) + last_location = Location(point=Point(9, 9, 9), labware=None) + + decoy.when(mock_protocol_core.get_last_location(Mount.LEFT)).then_return( + last_location + ) + decoy.when( + mock_validation.validate_location( + location=input_location, last_location=last_location + ) + ).then_return(WellTarget(well=mock_well, location=None, in_place=False)) + decoy.when( + mock_validation.validate_location(location=None, last_location=last_location) + ).then_return(WellTarget(well=mock_well, location=None, in_place=False)) + decoy.when(mock_well.bottom(z=1.0)).then_return(bottom_location) + decoy.when(mock_well.top()).then_return(top_location) + decoy.when(mock_instrument_core.get_aspirate_flow_rate(1.23)).then_return(5.67) + decoy.when(mock_instrument_core.get_dispense_flow_rate(1.23)).then_return(5.67) + decoy.when(mock_instrument_core.has_tip()).then_return(True) + decoy.when(mock_instrument_core.get_current_volume()).then_return(0.0) + decoy.when(mock_instrument_core.nozzle_configuration_valid_for_lld()).then_return( + True + ) + + subject.liquid_presence_detection = True + subject.mix(repetitions=10, volume=10.0, location=input_location, rate=1.23) + decoy.verify( + mock_instrument_core.aspirate( + bottom_location, + mock_well._core, + 10.0, + 1.23, + 5.67, + False, + None, + ), + times=10, + ) + decoy.verify( + mock_instrument_core.dispense( + bottom_location, + mock_well._core, + 10.0, + 1.23, + 5.67, + False, + 0.0, + None, + ), + times=9, + ) + decoy.verify( + mock_instrument_core.dispense( + bottom_location, + mock_well._core, + 10.0, + 1.23, + 5.67, + False, + None, + None, ), + times=1, + ) + decoy.verify( + mock_instrument_core.liquid_probe_with_recovery(mock_well._core, top_location), + times=1, + ) + + +@pytest.mark.parametrize( + "api_version", + versions_between( + low_exclusive_bound=APIVersion(2, 13), high_inclusive_bound=APIVersion(2, 21) + ), +) +def test_air_gap_uses_aspirate( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + mock_protocol_core: ProtocolCore, + subject: InstrumentContext, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """It should use its own aspirate function to aspirate air.""" + mock_well = decoy.mock(cls=Well) + top_location = Location(point=Point(9, 9, 14), labware=mock_well) + last_location = Location(point=Point(9, 9, 9), labware=mock_well) + mock_aspirate = decoy.mock(func=subject.aspirate) + mock_move_to = decoy.mock(func=subject.move_to) + monkeypatch.setattr(subject, "aspirate", mock_aspirate) + monkeypatch.setattr(subject, "move_to", mock_move_to) + + decoy.when(mock_instrument_core.has_tip()).then_return(True) + decoy.when(mock_protocol_core.get_last_location()).then_return(last_location) + decoy.when(mock_well.top(z=5.0)).then_return(top_location) + subject.air_gap(volume=10, height=5) + + decoy.verify(mock_move_to(top_location, publish=False)) + decoy.verify(mock_aspirate(10)) + + +@pytest.mark.parametrize("api_version", versions_at_or_above(APIVersion(2, 22))) +def test_air_gap_uses_air_gap( + decoy: Decoy, + mock_instrument_core: InstrumentCore, + mock_protocol_core: ProtocolCore, + subject: InstrumentContext, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """It should use its own aspirate function to aspirate air.""" + mock_well = decoy.mock(cls=Well) + top_location = Location(point=Point(9, 9, 14), labware=mock_well) + last_location = Location(point=Point(9, 9, 9), labware=mock_well) + mock_move_to = decoy.mock(func=subject.move_to) + monkeypatch.setattr(subject, "move_to", mock_move_to) + + decoy.when(mock_instrument_core.has_tip()).then_return(True) + decoy.when(mock_protocol_core.get_last_location()).then_return(last_location) + decoy.when(mock_well.top(z=5.0)).then_return(top_location) + decoy.when(mock_instrument_core.get_aspirate_flow_rate()).then_return(11) + + subject.air_gap(volume=10, height=5) + + decoy.verify(mock_move_to(top_location, publish=False)) + decoy.verify(mock_instrument_core.air_gap_in_place(10, 11)) + + +@pytest.mark.parametrize("robot_type", ["OT-2 Standard", "OT-3 Standard"]) +def test_transfer_liquid_raises_for_invalid_locations( + decoy: Decoy, + mock_protocol_core: ProtocolCore, + subject: InstrumentContext, + mock_feature_flags: None, + robot_type: RobotType, + minimal_liquid_class_def2: LiquidClassSchemaV1, +) -> None: + """It should raise errors if source or destination is invalid.""" + test_liq_class = LiquidClass.create(minimal_liquid_class_def2) + mock_well = decoy.mock(cls=Well) + decoy.when(mock_protocol_core.robot_type).then_return(robot_type) + decoy.when( + ff.allow_liquid_classes(RobotTypeEnum.robot_literal_to_enum(robot_type)) + ).then_return(True) + decoy.when( + mock_validation.ensure_valid_flat_wells_list_for_transfer_v2([mock_well]) + ).then_raise(ValueError("Oh no")) + with pytest.raises(ValueError): + subject.transfer_liquid( + liquid_class=test_liq_class, + volume=10, + source=[mock_well], + dest=[[mock_well]], + ) + + +@pytest.mark.parametrize("robot_type", ["OT-2 Standard", "OT-3 Standard"]) +def test_transfer_liquid_raises_for_unequal_source_and_dest( + decoy: Decoy, + mock_protocol_core: ProtocolCore, + subject: InstrumentContext, + mock_feature_flags: None, + robot_type: RobotType, + minimal_liquid_class_def2: LiquidClassSchemaV1, +) -> None: + """It should raise errors if source and destination are not of same length.""" + test_liq_class = LiquidClass.create(minimal_liquid_class_def2) + mock_well = decoy.mock(cls=Well) + decoy.when(mock_protocol_core.robot_type).then_return(robot_type) + decoy.when( + ff.allow_liquid_classes(RobotTypeEnum.robot_literal_to_enum(robot_type)) + ).then_return(True) + decoy.when( + mock_validation.ensure_valid_flat_wells_list_for_transfer_v2(mock_well) + ).then_return([mock_well, mock_well]) + decoy.when( + mock_validation.ensure_valid_flat_wells_list_for_transfer_v2([mock_well]) + ).then_return([mock_well]) + with pytest.raises( + ValueError, match="Sources and destinations should be of the same length" + ): + subject.transfer_liquid( + liquid_class=test_liq_class, volume=10, source=mock_well, dest=[mock_well] + ) + + +@pytest.mark.parametrize("robot_type", ["OT-2 Standard", "OT-3 Standard"]) +def test_transfer_liquid_raises_for_non_liquid_handling_locations( + decoy: Decoy, + mock_protocol_core: ProtocolCore, + subject: InstrumentContext, + mock_feature_flags: None, + robot_type: RobotType, + minimal_liquid_class_def2: LiquidClassSchemaV1, +) -> None: + """It should raise errors if source and destination are not of same length.""" + test_liq_class = LiquidClass.create(minimal_liquid_class_def2) + mock_well = decoy.mock(cls=Well) + decoy.when(mock_protocol_core.robot_type).then_return(robot_type) + decoy.when( + ff.allow_liquid_classes(RobotTypeEnum.robot_literal_to_enum(robot_type)) + ).then_return(True) + decoy.when( + mock_validation.ensure_valid_flat_wells_list_for_transfer_v2([mock_well]) + ).then_return([mock_well]) + decoy.when( + mock_instrument_support.validate_takes_liquid( + mock_well.top(), reject_module=True, reject_adapter=True + ) + ).then_raise(ValueError("Uh oh")) + with pytest.raises(ValueError, match="Uh oh"): + subject.transfer_liquid( + liquid_class=test_liq_class, volume=10, source=[mock_well], dest=[mock_well] + ) + + +@pytest.mark.parametrize("robot_type", ["OT-2 Standard", "OT-3 Standard"]) +def test_transfer_liquid_raises_for_bad_tip_policy( + decoy: Decoy, + mock_protocol_core: ProtocolCore, + subject: InstrumentContext, + mock_feature_flags: None, + robot_type: RobotType, + minimal_liquid_class_def2: LiquidClassSchemaV1, +) -> None: + """It should raise errors if new_tip is invalid.""" + test_liq_class = LiquidClass.create(minimal_liquid_class_def2) + mock_well = decoy.mock(cls=Well) + decoy.when(mock_protocol_core.robot_type).then_return(robot_type) + decoy.when( + ff.allow_liquid_classes(RobotTypeEnum.robot_literal_to_enum(robot_type)) + ).then_return(True) + decoy.when( + mock_validation.ensure_valid_flat_wells_list_for_transfer_v2([mock_well]) + ).then_return([mock_well]) + decoy.when(mock_validation.ensure_new_tip_policy("once")).then_raise( + ValueError("Uh oh") + ) + with pytest.raises(ValueError, match="Uh oh"): + subject.transfer_liquid( + liquid_class=test_liq_class, + volume=10, + source=[mock_well], + dest=[mock_well], + new_tip="once", + ) + + +@pytest.mark.parametrize("robot_type", ["OT-2 Standard", "OT-3 Standard"]) +def test_transfer_liquid_raises_for_no_tip( + decoy: Decoy, + mock_protocol_core: ProtocolCore, + subject: InstrumentContext, + mock_feature_flags: None, + robot_type: RobotType, + minimal_liquid_class_def2: LiquidClassSchemaV1, +) -> None: + """It should raise errors if there is no tip attached.""" + test_liq_class = LiquidClass.create(minimal_liquid_class_def2) + mock_well = decoy.mock(cls=Well) + decoy.when(mock_protocol_core.robot_type).then_return(robot_type) + decoy.when( + ff.allow_liquid_classes(RobotTypeEnum.robot_literal_to_enum(robot_type)) + ).then_return(True) + decoy.when( + mock_validation.ensure_valid_flat_wells_list_for_transfer_v2([mock_well]) + ).then_return([mock_well]) + decoy.when(mock_validation.ensure_new_tip_policy("never")).then_return( + TransferTipPolicyV2.NEVER + ) + with pytest.raises(RuntimeError, match="Pipette has no tip"): + subject.transfer_liquid( + liquid_class=test_liq_class, + volume=10, + source=[mock_well], + dest=[mock_well], + new_tip="never", + ) + + +@pytest.mark.parametrize("robot_type", ["OT-2 Standard", "OT-3 Standard"]) +def test_transfer_liquid_raises_if_tip_has_liquid( + decoy: Decoy, + mock_protocol_core: ProtocolCore, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_feature_flags: None, + robot_type: RobotType, + minimal_liquid_class_def2: LiquidClassSchemaV1, +) -> None: + """It should raise errors if there is no tip attached.""" + test_liq_class = LiquidClass.create(minimal_liquid_class_def2) + mock_well = decoy.mock(cls=Well) + tip_racks = [decoy.mock(cls=Labware)] + + subject.starting_tip = None + subject.tip_racks = tip_racks + + decoy.when(mock_protocol_core.robot_type).then_return(robot_type) + decoy.when( + ff.allow_liquid_classes(RobotTypeEnum.robot_literal_to_enum(robot_type)) + ).then_return(True) + decoy.when( + mock_validation.ensure_valid_flat_wells_list_for_transfer_v2([mock_well]) + ).then_return([mock_well]) + decoy.when(mock_validation.ensure_new_tip_policy("never")).then_return( + TransferTipPolicyV2.ONCE + ) + decoy.when(mock_instrument_core.get_nozzle_map()).then_return(MOCK_MAP) + decoy.when(mock_instrument_core.get_active_channels()).then_return(2) + decoy.when( + labware.next_available_tip( + starting_tip=None, + tip_racks=tip_racks, + channels=2, + nozzle_map=MOCK_MAP, + ) + ).then_return((decoy.mock(cls=Labware), decoy.mock(cls=Well))) + decoy.when(mock_instrument_core.get_current_volume()).then_return(1000) + with pytest.raises(RuntimeError, match="liquid already in the tip"): + subject.transfer_liquid( + liquid_class=test_liq_class, + volume=10, + source=[mock_well], + dest=[mock_well], + new_tip="never", + ) + + +@pytest.mark.parametrize("robot_type", ["OT-2 Standard", "OT-3 Standard"]) +def test_transfer_liquid_delegates_to_engine_core( + decoy: Decoy, + mock_protocol_core: ProtocolCore, + mock_instrument_core: InstrumentCore, + subject: InstrumentContext, + mock_feature_flags: None, + robot_type: RobotType, + minimal_liquid_class_def2: LiquidClassSchemaV1, +) -> None: + """It should load liquid class into engine and delegate the transfer execution to core.""" + test_liq_class = LiquidClass.create(minimal_liquid_class_def2) + mock_well = decoy.mock(cls=Well) + tip_racks = [decoy.mock(cls=Labware)] + trash_location = Location(point=Point(1, 2, 3), labware=mock_well) + next_tiprack = decoy.mock(cls=Labware) + subject.starting_tip = None + subject.tip_racks = tip_racks + + decoy.when(mock_protocol_core.robot_type).then_return(robot_type) + decoy.when( + ff.allow_liquid_classes(RobotTypeEnum.robot_literal_to_enum(robot_type)) + ).then_return(True) + decoy.when( + mock_validation.ensure_valid_flat_wells_list_for_transfer_v2([mock_well]) + ).then_return([mock_well]) + decoy.when(mock_validation.ensure_new_tip_policy("never")).then_return( + TransferTipPolicyV2.ONCE + ) + decoy.when(mock_instrument_core.get_nozzle_map()).then_return(MOCK_MAP) + decoy.when(mock_instrument_core.get_active_channels()).then_return(2) + decoy.when( + labware.next_available_tip( + starting_tip=None, + tip_racks=tip_racks, + channels=2, + nozzle_map=MOCK_MAP, + ) + ).then_return((next_tiprack, decoy.mock(cls=Well))) + decoy.when(mock_instrument_core.get_current_volume()).then_return(0) + decoy.when( + mock_validation.ensure_valid_tip_drop_location_for_transfer_v2(trash_location) + ).then_return(trash_location.move(Point(1, 2, 3))) + decoy.when(next_tiprack.uri).then_return("tiprack-uri") + decoy.when(mock_instrument_core.get_pipette_name()).then_return("pipette-name") + decoy.when( + mock_instrument_core.load_liquid_class( + liquid_class=test_liq_class, + pipette_load_name="pipette-name", + tiprack_uri="tiprack-uri", + ) + ).then_return("liq-class-id") + + subject.transfer_liquid( + liquid_class=test_liq_class, + volume=10, + source=[mock_well], + dest=[mock_well], + new_tip="never", + tip_drop_location=trash_location, + ) + decoy.verify( + mock_instrument_core.transfer_liquid( + liquid_class_id="liq-class-id", + volume=10, + source=[mock_well._core], + dest=[mock_well._core], + new_tip=TransferTipPolicyV2.ONCE, + trash_location=trash_location.move(Point(1, 2, 3)), + ) ) - decoy.when(mock_instrument_core.get_nozzle_map()).then_return(nozzle_map) - decoy.when(mock_instrument_core.get_active_channels()).then_return(96) - assert subject._96_tip_config_valid() is True diff --git a/api/tests/opentrons/protocol_api/test_labware.py b/api/tests/opentrons/protocol_api/test_labware.py index 4610145162f..5e49cd29947 100644 --- a/api/tests/opentrons/protocol_api/test_labware.py +++ b/api/tests/opentrons/protocol_api/test_labware.py @@ -1,4 +1,5 @@ """Tests for the InstrumentContext public interface.""" + import inspect from typing import cast @@ -21,6 +22,7 @@ from opentrons.protocol_api.core.labware import LabwareLoadParams from opentrons.protocol_api.core.core_map import LoadedCoreMap from opentrons.protocol_api import TemperatureModuleContext +from opentrons.protocol_api._liquid import Liquid from opentrons.types import Point @@ -364,3 +366,318 @@ def test_separate_calibration_raises_on_high_api_version( """It should raise an error, on high API versions.""" with pytest.raises(UnsupportedAPIError): subject.separate_calibration + + +@pytest.mark.parametrize("api_version", versions_at_or_above(APIVersion(2, 22))) +def test_load_liquid_handles_valid_inputs( + decoy: Decoy, + mock_labware_core: LabwareCore, + api_version: APIVersion, + mock_protocol_core: ProtocolCore, + mock_map_core: LoadedCoreMap, +) -> None: + """It should load volumes for list of wells.""" + mock_well_core_1 = decoy.mock(cls=WellCore) + mock_well_core_2 = decoy.mock(cls=WellCore) + grid = well_grid.WellGrid( + columns_by_name={"1": ["A1", "B1"]}, + rows_by_name={"A": ["A1"], "B": ["B1"]}, + ) + decoy.when(mock_well_core_1.get_name()).then_return("A1") + decoy.when(mock_well_core_2.get_name()).then_return("B1") + + decoy.when(mock_labware_core.get_well_columns()).then_return([["A1", "B1"]]) + decoy.when(mock_labware_core.get_well_core("A1")).then_return(mock_well_core_1) + decoy.when(mock_labware_core.get_well_core("B1")).then_return(mock_well_core_2) + decoy.when(well_grid.create([["A1", "B1"]])).then_return(grid) + + subject = Labware( + core=mock_labware_core, + api_version=api_version, + protocol_core=mock_protocol_core, + core_map=mock_map_core, + ) + mock_liquid = decoy.mock(cls=Liquid) + + subject.load_liquid(["A1", subject["B1"]], 10, mock_liquid) + decoy.verify( + mock_labware_core.load_liquid( + { + "A1": 10, + "B1": 10, + }, + mock_liquid, + ) + ) + + +@pytest.mark.parametrize("api_version", versions_at_or_above(APIVersion(2, 22))) +def test_load_liquid_rejects_invalid_inputs( + decoy: Decoy, + mock_labware_core: LabwareCore, + api_version: APIVersion, + mock_protocol_core: ProtocolCore, + mock_map_core: LoadedCoreMap, +) -> None: + """It should require valid load inputs.""" + mock_well_core_1 = decoy.mock(cls=WellCore) + mock_well_core_2 = decoy.mock(cls=WellCore) + + grid = well_grid.WellGrid( + columns_by_name={"1": ["A1", "B1"]}, + rows_by_name={"A": ["A1"], "B": ["B1"]}, + ) + decoy.when(mock_well_core_1.get_name()).then_return("A1") + decoy.when(mock_well_core_2.get_name()).then_return("B1") + decoy.when(mock_labware_core.get_well_columns()).then_return([["A1", "B1"]]) + decoy.when(mock_labware_core.get_well_core("A1")).then_return(mock_well_core_1) + decoy.when(mock_labware_core.get_well_core("B1")).then_return(mock_well_core_2) + decoy.when(well_grid.create([["A1", "B1"]])).then_return(grid) + subject = Labware( + core=mock_labware_core, + api_version=api_version, + protocol_core=mock_protocol_core, + core_map=mock_map_core, + ) + + core_2 = decoy.mock(cls=LabwareCore) + mock_well_core_3 = decoy.mock(cls=WellCore) + grid_2 = well_grid.WellGrid( + columns_by_name={"1": ["A1"]}, rows_by_name={"A": ["A1"]} + ) + decoy.when(mock_well_core_3.get_name()).then_return("A1") + decoy.when(core_2.get_well_columns()).then_return([["A1", "B1"]]) + decoy.when(core_2.get_well_core("A1")).then_return(mock_well_core_1) + decoy.when(core_2.get_well_core("B1")).then_return(mock_well_core_2) + + decoy.when(well_grid.create([["A1"]])).then_return(grid_2) + other_labware = Labware( + core=core_2, + api_version=api_version, + protocol_core=mock_protocol_core, + core_map=mock_map_core, + ) + mock_liquid = decoy.mock(cls=Liquid) + with pytest.raises(KeyError): + subject.load_liquid(["A1", "C1"], 10, mock_liquid) + + with pytest.raises(KeyError): + subject.load_liquid([subject["A1"], other_labware["A1"]], 10, mock_liquid) + + with pytest.raises(TypeError): + subject.load_liquid([2], 10, mock_liquid) # type: ignore[list-item] + + with pytest.raises(TypeError): + subject.load_liquid(["A1"], "A1", mock_liquid) # type: ignore[arg-type] + mock_liquid = decoy.mock(cls=Liquid) + + subject.load_liquid(["A1", subject["B1"]], 10, mock_liquid) + decoy.verify( + mock_labware_core.load_liquid( + { + "A1": 10, + "B1": 10, + }, + mock_liquid, + ) + ) + + +@pytest.mark.parametrize("api_version", versions_at_or_above(APIVersion(2, 22))) +def test_load_liquid_by_well_handles_valid_inputs( + decoy: Decoy, + mock_labware_core: LabwareCore, + api_version: APIVersion, + mock_protocol_core: ProtocolCore, + mock_map_core: LoadedCoreMap, +) -> None: + """It should load liquids of different volumes in different wells.""" + mock_well_core_1 = decoy.mock(cls=WellCore) + mock_well_core_2 = decoy.mock(cls=WellCore) + grid = well_grid.WellGrid( + columns_by_name={"1": ["A1", "B1"]}, + rows_by_name={"A": ["A1"], "B": ["B1"]}, + ) + decoy.when(mock_well_core_1.get_name()).then_return("A1") + decoy.when(mock_well_core_2.get_name()).then_return("B1") + + decoy.when(mock_labware_core.get_well_columns()).then_return([["A1", "B1"]]) + decoy.when(mock_labware_core.get_well_core("A1")).then_return(mock_well_core_1) + decoy.when(mock_labware_core.get_well_core("B1")).then_return(mock_well_core_2) + decoy.when(well_grid.create([["A1", "B1"]])).then_return(grid) + decoy.when(mock_well_core_2.get_display_name()).then_return("well 2") + subject = Labware( + core=mock_labware_core, + api_version=api_version, + protocol_core=mock_protocol_core, + core_map=mock_map_core, + ) + mock_liquid = decoy.mock(cls=Liquid) + + subject.load_liquid_by_well({"A1": 10, subject["B1"]: 11}, mock_liquid) + decoy.verify( + mock_labware_core.load_liquid( + { + "A1": 10, + "B1": 11, + }, + mock_liquid, + ) + ) + + +@pytest.mark.parametrize("api_version", versions_at_or_above(APIVersion(2, 22))) +def test_load_liquid_by_well_rejects_invalid_inputs( + decoy: Decoy, + mock_labware_core: LabwareCore, + api_version: APIVersion, + mock_protocol_core: ProtocolCore, + mock_map_core: LoadedCoreMap, +) -> None: + """It should require valid well specs.""" + mock_well_core_1 = decoy.mock(cls=WellCore) + mock_well_core_2 = decoy.mock(cls=WellCore) + + grid = well_grid.WellGrid( + columns_by_name={"1": ["A1", "B1"]}, + rows_by_name={"A": ["A1"], "B": ["B1"]}, + ) + decoy.when(mock_well_core_1.get_name()).then_return("A1") + decoy.when(mock_well_core_2.get_name()).then_return("B1") + decoy.when(mock_well_core_1.get_display_name()).then_return("well 1") + decoy.when(mock_well_core_2.get_display_name()).then_return("well 2") + decoy.when(mock_well_core_1.get_top(z_offset=0.0)).then_return(Point(4, 5, 6)) + decoy.when(mock_well_core_1.get_top(z_offset=0.0)).then_return(Point(7, 8, 9)) + decoy.when(mock_labware_core.get_well_columns()).then_return([["A1", "B1"]]) + decoy.when(mock_labware_core.get_well_core("A1")).then_return(mock_well_core_1) + decoy.when(mock_labware_core.get_well_core("B1")).then_return(mock_well_core_2) + decoy.when(well_grid.create([["A1", "B1"]])).then_return(grid) + subject = Labware( + core=mock_labware_core, + api_version=api_version, + protocol_core=mock_protocol_core, + core_map=mock_map_core, + ) + core_2 = decoy.mock(cls=LabwareCore) + mock_well_core_3 = decoy.mock(cls=WellCore) + decoy.when(mock_well_core_3.get_display_name()).then_return("well 3") + grid_2 = well_grid.WellGrid( + columns_by_name={"1": ["A1"]}, rows_by_name={"A": ["A1"]} + ) + decoy.when(mock_well_core_3.get_name()).then_return("A1") + decoy.when(core_2.get_well_columns()).then_return([["A1"]]) + decoy.when(core_2.get_well_core("A1")).then_return(mock_well_core_3) + decoy.when(mock_well_core_3.get_top(z_offset=0.0)).then_return(Point(1, 2, 3)) + + decoy.when(well_grid.create([["A1"]])).then_return(grid_2) + other_labware = Labware( + core=core_2, + api_version=api_version, + protocol_core=mock_protocol_core, + core_map=mock_map_core, + ) + + mock_liquid = decoy.mock(cls=Liquid) + with pytest.raises(KeyError): + subject.load_liquid_by_well({"A1": 10, "C1": 11}, mock_liquid) + + with pytest.raises(KeyError): + subject.load_liquid_by_well( + {subject["A1"]: 10, other_labware["A1"]: 11}, mock_liquid + ) + + with pytest.raises(TypeError): + subject.load_liquid_by_well({2: 10}, mock_liquid) # type: ignore[dict-item] + + with pytest.raises(TypeError): + subject.load_liquid_by_well({"A1": "A3"}, mock_liquid) # type: ignore[dict-item] + + +@pytest.mark.parametrize("api_version", versions_at_or_above(APIVersion(2, 22))) +def test_load_empty_handles_valid_inputs( + decoy: Decoy, + mock_labware_core: LabwareCore, + api_version: APIVersion, + mock_protocol_core: ProtocolCore, + mock_map_core: LoadedCoreMap, +) -> None: + """It should load lists of wells as empty.""" + mock_well_core_1 = decoy.mock(cls=WellCore) + mock_well_core_2 = decoy.mock(cls=WellCore) + grid = well_grid.WellGrid( + columns_by_name={"1": ["A1", "B1"]}, + rows_by_name={"A": ["A1"], "B": ["B1"]}, + ) + decoy.when(mock_well_core_1.get_name()).then_return("A1") + decoy.when(mock_well_core_2.get_name()).then_return("B1") + + decoy.when(mock_labware_core.get_well_columns()).then_return([["A1", "B1"]]) + decoy.when(mock_labware_core.get_well_core("A1")).then_return(mock_well_core_1) + decoy.when(mock_labware_core.get_well_core("B1")).then_return(mock_well_core_2) + decoy.when(well_grid.create([["A1", "B1"]])).then_return(grid) + + subject = Labware( + core=mock_labware_core, + api_version=api_version, + protocol_core=mock_protocol_core, + core_map=mock_map_core, + ) + + subject.load_empty(["A1", subject["B1"]]) + decoy.verify(mock_labware_core.load_empty(["A1", "B1"])) + + +@pytest.mark.parametrize("api_version", versions_at_or_above(APIVersion(2, 22))) +def test_load_empty_rejects_invalid_inputs( + decoy: Decoy, + mock_labware_core: LabwareCore, + api_version: APIVersion, + mock_protocol_core: ProtocolCore, + mock_map_core: LoadedCoreMap, +) -> None: + """It should require valid well specs.""" + mock_well_core_1 = decoy.mock(cls=WellCore) + mock_well_core_2 = decoy.mock(cls=WellCore) + + grid = well_grid.WellGrid( + columns_by_name={"1": ["A1", "B1"]}, + rows_by_name={"A": ["A1"], "B": ["B1"]}, + ) + decoy.when(mock_well_core_1.get_name()).then_return("A1") + decoy.when(mock_well_core_2.get_name()).then_return("B1") + decoy.when(mock_labware_core.get_well_columns()).then_return([["A1", "B1"]]) + decoy.when(mock_labware_core.get_well_core("A1")).then_return(mock_well_core_1) + decoy.when(mock_labware_core.get_well_core("B1")).then_return(mock_well_core_2) + decoy.when(well_grid.create([["A1", "B1"]])).then_return(grid) + subject = Labware( + core=mock_labware_core, + api_version=api_version, + protocol_core=mock_protocol_core, + core_map=mock_map_core, + ) + + core_2 = decoy.mock(cls=LabwareCore) + mock_well_core_3 = decoy.mock(cls=WellCore) + grid_2 = well_grid.WellGrid( + columns_by_name={"1": ["A1"]}, rows_by_name={"A": ["A1"]} + ) + decoy.when(mock_well_core_3.get_name()).then_return("A1") + decoy.when(core_2.get_well_columns()).then_return([["A1", "B1"]]) + decoy.when(core_2.get_well_core("A1")).then_return(mock_well_core_1) + decoy.when(core_2.get_well_core("B1")).then_return(mock_well_core_2) + + decoy.when(well_grid.create([["A1"]])).then_return(grid_2) + other_labware = Labware( + core=core_2, + api_version=api_version, + protocol_core=mock_protocol_core, + core_map=mock_map_core, + ) + with pytest.raises(KeyError): + subject.load_empty(["A1", "C1"]) + + with pytest.raises(KeyError): + subject.load_empty([subject["A1"], other_labware["A1"]]) + + with pytest.raises(TypeError): + subject.load_empty([2]) # type: ignore[list-item] diff --git a/api/tests/opentrons/protocol_api/test_liquid_class.py b/api/tests/opentrons/protocol_api/test_liquid_class.py new file mode 100644 index 00000000000..7118080eda0 --- /dev/null +++ b/api/tests/opentrons/protocol_api/test_liquid_class.py @@ -0,0 +1,41 @@ +"""Tests for LiquidClass methods.""" +import pytest + +from opentrons_shared_data.liquid_classes.liquid_class_definition import ( + LiquidClassSchemaV1, +) +from opentrons.protocol_api import LiquidClass + + +def test_create_liquid_class( + minimal_liquid_class_def1: LiquidClassSchemaV1, +) -> None: + """It should create a LiquidClass from provided definition.""" + assert LiquidClass.create(minimal_liquid_class_def1) == LiquidClass( + _name="water1", _display_name="water 1", _by_pipette_setting={} + ) + + +def test_get_for_pipette_and_tip( + minimal_liquid_class_def2: LiquidClassSchemaV1, +) -> None: + """It should get the properties for the specified pipette and tip.""" + liq_class = LiquidClass.create(minimal_liquid_class_def2) + result = liq_class.get_for("flex_1channel_50", "opentrons_flex_96_tiprack_50ul") + assert result.aspirate.flow_rate_by_volume.as_dict() == { + 10.0: 40.0, + 20.0: 30.0, + } + + +def test_get_for_raises_for_incorrect_pipette_or_tip( + minimal_liquid_class_def2: LiquidClassSchemaV1, +) -> None: + """It should raise an error when accessing non-existent properties.""" + liq_class = LiquidClass.create(minimal_liquid_class_def2) + + with pytest.raises(ValueError): + liq_class.get_for("flex_1channel_50", "no_such_tiprack") + + with pytest.raises(ValueError): + liq_class.get_for("no_such_pipette", "opentrons_flex_96_tiprack_50ul") diff --git a/api/tests/opentrons/protocol_api/test_liquid_class_properties.py b/api/tests/opentrons/protocol_api/test_liquid_class_properties.py new file mode 100644 index 00000000000..f335cb385bc --- /dev/null +++ b/api/tests/opentrons/protocol_api/test_liquid_class_properties.py @@ -0,0 +1,206 @@ +"""Tests for LiquidClass properties and related functions.""" +import pytest +from opentrons_shared_data import load_shared_data +from opentrons_shared_data.liquid_classes.liquid_class_definition import ( + LiquidClassSchemaV1, + Coordinate, +) + +from opentrons.protocol_api._liquid_properties import ( + build_aspirate_properties, + build_single_dispense_properties, + build_multi_dispense_properties, + LiquidHandlingPropertyByVolume, +) + + +def test_build_aspirate_settings() -> None: + """It should convert the shared data aspirate settings to the PAPI type.""" + fixture_data = load_shared_data("liquid-class/fixtures/1/fixture_glycerol50.json") + liquid_class_model = LiquidClassSchemaV1.parse_raw(fixture_data) + aspirate_data = liquid_class_model.byPipette[0].byTipType[0].aspirate + + aspirate_properties = build_aspirate_properties(aspirate_data) + + assert aspirate_properties.submerge.position_reference.value == "liquid-meniscus" + assert aspirate_properties.submerge.offset == Coordinate(x=0, y=0, z=-5) + assert aspirate_properties.submerge.speed == 100 + assert aspirate_properties.submerge.delay.enabled is True + assert aspirate_properties.submerge.delay.duration == 1.5 + + assert aspirate_properties.retract.position_reference.value == "well-top" + assert aspirate_properties.retract.offset == Coordinate(x=0, y=0, z=5) + assert aspirate_properties.retract.speed == 100 + assert aspirate_properties.retract.air_gap_by_volume.as_dict() == { + 5.0: 3.0, + 10.0: 4.0, + } + assert aspirate_properties.retract.touch_tip.enabled is True + assert aspirate_properties.retract.touch_tip.z_offset == 2 + assert aspirate_properties.retract.touch_tip.mm_to_edge == 1 + assert aspirate_properties.retract.touch_tip.speed == 50 + assert aspirate_properties.retract.delay.enabled is True + assert aspirate_properties.retract.delay.duration == 1 + + assert aspirate_properties.position_reference.value == "well-bottom" + assert aspirate_properties.offset == Coordinate(x=0, y=0, z=-5) + assert aspirate_properties.flow_rate_by_volume.as_dict() == {10: 50.0} + assert aspirate_properties.correction_by_volume.as_dict() == { + 1.0: -2.5, + 10.0: 3, + } + assert aspirate_properties.pre_wet is True + assert aspirate_properties.mix.enabled is True + assert aspirate_properties.mix.repetitions == 3 + assert aspirate_properties.mix.volume == 15 + assert aspirate_properties.delay.enabled is True + assert aspirate_properties.delay.duration == 2 + assert aspirate_properties.as_shared_data_model() == aspirate_data + + +def test_build_single_dispense_settings() -> None: + """It should convert the shared data single dispense settings to the PAPI type.""" + fixture_data = load_shared_data("liquid-class/fixtures/1/fixture_glycerol50.json") + liquid_class_model = LiquidClassSchemaV1.parse_raw(fixture_data) + single_dispense_data = liquid_class_model.byPipette[0].byTipType[0].singleDispense + + single_dispense_properties = build_single_dispense_properties(single_dispense_data) + + assert ( + single_dispense_properties.submerge.position_reference.value + == "liquid-meniscus" + ) + assert single_dispense_properties.submerge.offset == Coordinate(x=0, y=0, z=-5) + assert single_dispense_properties.submerge.speed == 100 + assert single_dispense_properties.submerge.delay.enabled is True + assert single_dispense_properties.submerge.delay.duration == 1.5 + + assert single_dispense_properties.retract.position_reference.value == "well-top" + assert single_dispense_properties.retract.offset == Coordinate(x=0, y=0, z=5) + assert single_dispense_properties.retract.speed == 100 + assert single_dispense_properties.retract.air_gap_by_volume.as_dict() == { + 5.0: 3.0, + 10.0: 4.0, + } + assert single_dispense_properties.retract.touch_tip.enabled is True + assert single_dispense_properties.retract.touch_tip.z_offset == 2 + assert single_dispense_properties.retract.touch_tip.mm_to_edge == 1 + assert single_dispense_properties.retract.touch_tip.speed == 50 + assert single_dispense_properties.retract.blowout.enabled is True + assert single_dispense_properties.retract.blowout.location is not None + assert single_dispense_properties.retract.blowout.location.value == "trash" + assert single_dispense_properties.retract.blowout.flow_rate == 100 + assert single_dispense_properties.retract.delay.enabled is True + assert single_dispense_properties.retract.delay.duration == 1 + + assert single_dispense_properties.position_reference.value == "well-bottom" + assert single_dispense_properties.offset == Coordinate(x=0, y=0, z=-5) + assert single_dispense_properties.flow_rate_by_volume.as_dict() == { + 10.0: 40.0, + 20.0: 30.0, + } + assert single_dispense_properties.correction_by_volume.as_dict() == { + 2.0: -1.5, + 20.0: 2, + } + assert single_dispense_properties.mix.enabled is True + assert single_dispense_properties.mix.repetitions == 3 + assert single_dispense_properties.mix.volume == 15 + assert single_dispense_properties.push_out_by_volume.as_dict() == { + 10.0: 7.0, + 20.0: 10.0, + } + assert single_dispense_properties.delay.enabled is True + assert single_dispense_properties.delay.duration == 2.5 + assert single_dispense_properties.as_shared_data_model() == single_dispense_data + + +def test_build_multi_dispense_settings() -> None: + """It should convert the shared data multi dispense settings to the PAPI type.""" + fixture_data = load_shared_data("liquid-class/fixtures/1/fixture_glycerol50.json") + liquid_class_model = LiquidClassSchemaV1.parse_raw(fixture_data) + multi_dispense_data = liquid_class_model.byPipette[0].byTipType[0].multiDispense + + assert multi_dispense_data is not None + multi_dispense_properties = build_multi_dispense_properties(multi_dispense_data) + assert multi_dispense_properties is not None + + assert ( + multi_dispense_properties.submerge.position_reference.value == "liquid-meniscus" + ) + assert multi_dispense_properties.submerge.offset == Coordinate(x=0, y=0, z=-5) + assert multi_dispense_properties.submerge.speed == 100 + assert multi_dispense_properties.submerge.delay.enabled is True + assert multi_dispense_properties.submerge.delay.duration == 1.5 + + assert multi_dispense_properties.retract.position_reference.value == "well-top" + assert multi_dispense_properties.retract.offset == Coordinate(x=0, y=0, z=5) + assert multi_dispense_properties.retract.speed == 100 + assert multi_dispense_properties.retract.air_gap_by_volume.as_dict() == { + 5.0: 3.0, + 10.0: 4.0, + } + assert multi_dispense_properties.retract.touch_tip.enabled is True + assert multi_dispense_properties.retract.touch_tip.z_offset == 2 + assert multi_dispense_properties.retract.touch_tip.mm_to_edge == 1 + assert multi_dispense_properties.retract.touch_tip.speed == 50 + assert multi_dispense_properties.retract.blowout.enabled is False + assert multi_dispense_properties.retract.blowout.location is None + assert multi_dispense_properties.retract.blowout.flow_rate is None + assert multi_dispense_properties.retract.delay.enabled is True + assert multi_dispense_properties.retract.delay.duration == 1 + + assert multi_dispense_properties.position_reference.value == "well-bottom" + assert multi_dispense_properties.offset == Coordinate(x=0, y=0, z=-5) + assert multi_dispense_properties.flow_rate_by_volume.as_dict() == { + 10.0: 40.0, + 20.0: 30.0, + } + assert multi_dispense_properties.correction_by_volume.as_dict() == { + 3.0: -0.5, + 30.0: 1, + } + assert multi_dispense_properties.conditioning_by_volume.as_dict() == { + 5.0: 5.0, + } + assert multi_dispense_properties.disposal_by_volume.as_dict() == { + 5.0: 3.0, + } + assert multi_dispense_properties.delay.enabled is True + assert multi_dispense_properties.delay.duration == 1 + assert multi_dispense_properties.as_shared_data_model() == multi_dispense_data + + +def test_build_multi_dispense_settings_none( + minimal_liquid_class_def2: LiquidClassSchemaV1, +) -> None: + """It should return None if there are no multi dispense properties in the model.""" + transfer_settings = minimal_liquid_class_def2.byPipette[0].byTipType[0] + assert build_multi_dispense_properties(transfer_settings.multiDispense) is None + + +def test_liquid_handling_property_by_volume() -> None: + """It should create a class that can interpolate values and add and delete new points.""" + subject = LiquidHandlingPropertyByVolume([(5.0, 50.0), (10.0, 250.0)]) + assert subject.as_dict() == {5.0: 50, 10.0: 250} + assert subject.get_for_volume(7) == 130.0 + assert subject.as_list_of_tuples() == [(5.0, 50.0), (10.0, 250.0)] + + subject.set_for_volume(volume=7, value=175.5) + assert subject.as_dict() == { + 5.0: 50, + 10.0: 250, + 7.0: 175.5, + } + assert subject.get_for_volume(7) == 175.5 + + subject.delete_for_volume(7) + assert subject.as_dict() == {5.0: 50, 10.0: 250} + assert subject.get_for_volume(7) == 130.0 + + with pytest.raises(KeyError, match="No value set for volume"): + subject.delete_for_volume(7) + + # Test bounds + assert subject.get_for_volume(1) == 50.0 + assert subject.get_for_volume(1000) == 250.0 diff --git a/api/tests/opentrons/protocol_api/test_protocol_context.py b/api/tests/opentrons/protocol_api/test_protocol_context.py index 1e1dda706c6..e804ac9dd11 100644 --- a/api/tests/opentrons/protocol_api/test_protocol_context.py +++ b/api/tests/opentrons/protocol_api/test_protocol_context.py @@ -7,8 +7,11 @@ from opentrons_shared_data.pipette.types import PipetteNameType from opentrons_shared_data.labware.types import LabwareDefinition as LabwareDefDict +from opentrons_shared_data.robot.types import RobotTypeEnum, RobotType +from opentrons.protocol_api._liquid import LiquidClass from opentrons.types import Mount, DeckSlotName, StagingSlotName +from opentrons.config import feature_flags as ff from opentrons.protocol_api import OFF_DECK from opentrons.legacy_broker import LegacyBroker from opentrons.hardware_control.modules.types import ModuleType, TemperatureModuleModel @@ -46,6 +49,8 @@ from opentrons.protocols.api_support.deck_type import ( NoTrashDefinedError, ) +from opentrons.protocol_engine.errors import LabwareMovementNotAllowedError +from opentrons.protocol_engine.clients import SyncClient as EngineClient @pytest.fixture(autouse=True) @@ -98,6 +103,12 @@ def api_version() -> APIVersion: return MAX_SUPPORTED_VERSION +@pytest.fixture +def mock_engine_client(decoy: Decoy) -> EngineClient: + """Get a mock ProtocolEngine synchronous client.""" + return decoy.mock(cls=EngineClient) + + @pytest.fixture def subject( mock_core: ProtocolCore, @@ -941,6 +952,74 @@ def test_move_labware_off_deck_raises( subject.move_labware(labware=movable_labware, new_location=OFF_DECK) +def test_move_labware_to_trash_raises( + subject: ProtocolContext, + decoy: Decoy, + mock_core: ProtocolCore, + mock_core_map: LoadedCoreMap, + mock_engine_client: EngineClient, +) -> None: + """It should raise an LabwareMovementNotAllowedError if using move_labware to move something that is not a lid to a TrashBin.""" + mock_labware_core = decoy.mock(cls=LabwareCore) + trash_location = TrashBin( + location=DeckSlotName.SLOT_D3, + addressable_area_name="moveableTrashD3", + api_version=MAX_SUPPORTED_VERSION, + engine_client=mock_engine_client, + ) + + decoy.when(mock_labware_core.get_well_columns()).then_return([]) + + movable_labware = Labware( + core=mock_labware_core, + api_version=MAX_SUPPORTED_VERSION, + protocol_core=mock_core, + core_map=mock_core_map, + ) + + with pytest.raises(LabwareMovementNotAllowedError): + subject.move_labware(labware=movable_labware, new_location=trash_location) + + +def test_move_lid_to_trash_passes( + decoy: Decoy, + mock_core: ProtocolCore, + mock_core_map: LoadedCoreMap, + subject: ProtocolContext, + mock_engine_client: EngineClient, +) -> None: + """It should move a lid labware into a trashbin successfully.""" + mock_labware_core = decoy.mock(cls=LabwareCore) + trash_location = TrashBin( + location=DeckSlotName.SLOT_D3, + addressable_area_name="moveableTrashD3", + api_version=MAX_SUPPORTED_VERSION, + engine_client=mock_engine_client, + ) + + decoy.when(mock_labware_core.get_well_columns()).then_return([]) + decoy.when(mock_labware_core.is_lid()).then_return(True) + + movable_labware = Labware( + core=mock_labware_core, + api_version=MAX_SUPPORTED_VERSION, + protocol_core=mock_core, + core_map=mock_core_map, + ) + + subject.move_labware(labware=movable_labware, new_location=trash_location) + decoy.verify( + mock_core.move_labware( + labware_core=mock_labware_core, + new_location=trash_location, + use_gripper=False, + pause_for_manual_move=True, + pick_up_offset=None, + drop_offset=None, + ) + ) + + def test_load_trash_bin( decoy: Decoy, mock_core: ProtocolCore, @@ -1214,6 +1293,28 @@ def test_define_liquid_arg_defaulting( ) +@pytest.mark.parametrize("robot_type", ["OT-2 Standard", "OT-3 Standard"]) +def test_define_liquid_class( + decoy: Decoy, + mock_core: ProtocolCore, + subject: ProtocolContext, + robot_type: RobotType, + mock_feature_flags: None, +) -> None: + """It should create the liquid class definition.""" + expected_liquid_class = LiquidClass( + _name="volatile_100", _display_name="volatile 100%", _by_pipette_setting={} + ) + decoy.when(mock_core.define_liquid_class("volatile_90")).then_return( + expected_liquid_class + ) + decoy.when(mock_core.robot_type).then_return(robot_type) + decoy.when( + ff.allow_liquid_classes(RobotTypeEnum.robot_literal_to_enum(robot_type)) + ).then_return(True) + assert subject.define_liquid_class("volatile_90") == expected_liquid_class + + def test_bundled_data( decoy: Decoy, mock_core_map: LoadedCoreMap, mock_deck: Deck, mock_core: ProtocolCore ) -> None: diff --git a/api/tests/opentrons/protocol_api/test_robot_context.py b/api/tests/opentrons/protocol_api/test_robot_context.py new file mode 100644 index 00000000000..36b94c52b15 --- /dev/null +++ b/api/tests/opentrons/protocol_api/test_robot_context.py @@ -0,0 +1,256 @@ +"""Test the functionality of the `RobotContext`.""" +import pytest +from decoy import Decoy +from typing import Union, Optional + +from opentrons.types import ( + DeckLocation, + Mount, + Point, + Location, + DeckSlotName, + AxisType, + StringAxisMap, + AxisMapType, +) +from opentrons.protocols.api_support.types import APIVersion +from opentrons.protocol_api.core.common import ProtocolCore, RobotCore +from opentrons.protocol_api import RobotContext, ModuleContext +from opentrons.protocol_api.deck import Deck +from opentrons_shared_data.pipette.types import PipetteNameType + +from opentrons.protocol_api._types import PipetteActionTypes, PlungerPositionTypes + + +@pytest.fixture +def mock_core(decoy: Decoy) -> RobotCore: + """Get a mock module implementation core.""" + return decoy.mock(cls=RobotCore) + + +@pytest.fixture +def api_version() -> APIVersion: + """Get the API version to test at.""" + return APIVersion(2, 22) + + +@pytest.fixture +def mock_deck(decoy: Decoy) -> Deck: + """Get a mocked deck object.""" + deck = decoy.mock(cls=Deck) + decoy.when(deck.get_slot_center(DeckSlotName.SLOT_D1.value)).then_return( + Point(3, 3, 3) + ) + return deck + + +@pytest.fixture +def mock_protocol(decoy: Decoy, mock_deck: Deck, mock_core: RobotCore) -> ProtocolCore: + """Get a mock protocol implementation core without a 96 channel attached.""" + protocol_core = decoy.mock(cls=ProtocolCore) + decoy.when(protocol_core.robot_type).then_return("OT-3 Standard") + decoy.when(protocol_core.load_robot()).then_return(mock_core) + return protocol_core + + +@pytest.fixture +def subject( + decoy: Decoy, + mock_core: RobotCore, + mock_protocol: ProtocolCore, + api_version: APIVersion, +) -> RobotContext: + """Get a RobotContext test subject with its dependencies mocked out.""" + decoy.when(mock_core.get_pipette_type_from_engine(Mount.LEFT)).then_return( + PipetteNameType.P1000_SINGLE_FLEX + ) + decoy.when(mock_core.get_pipette_type_from_engine(Mount.RIGHT)).then_return( + PipetteNameType.P1000_SINGLE_FLEX + ) + return RobotContext( + core=mock_core, api_version=api_version, protocol_core=mock_protocol + ) + + +@pytest.mark.parametrize( + argnames=["mount", "destination", "speed"], + argvalues=[ + ("left", Location(point=Point(1, 2, 3), labware=None), None), + (Mount.RIGHT, Location(point=Point(1, 2, 3), labware=None), 100), + ], +) +def test_move_to( + decoy: Decoy, + subject: RobotContext, + mount: Union[str, Mount], + destination: Location, + speed: Optional[float], +) -> None: + """Test `RobotContext.move_to`.""" + subject.move_to(mount, destination, speed) + core_mount: Mount + if isinstance(mount, str): + core_mount = Mount.string_to_mount(mount) + else: + core_mount = mount + decoy.verify(subject._core.move_to(core_mount, destination.point, speed)) + + +@pytest.mark.parametrize( + argnames=[ + "axis_map", + "critical_point", + "expected_axis_map", + "expected_critical_point", + "speed", + ], + argvalues=[ + ( + {"x": 100, "Y": 50, "z_g": 80}, + {"x": 5, "Y": 5, "z_g": 5}, + {AxisType.X: 100, AxisType.Y: 50, AxisType.Z_G: 80}, + {AxisType.X: 5, AxisType.Y: 5, AxisType.Z_G: 5}, + None, + ), + ( + {"x": 5, "Y": 5}, + {"x": 5, "Y": 5}, + {AxisType.X: 5, AxisType.Y: 5}, + {AxisType.X: 5, AxisType.Y: 5}, + None, + ), + ], +) +def test_move_axes_to( + decoy: Decoy, + subject: RobotContext, + axis_map: Union[StringAxisMap, AxisMapType], + critical_point: Union[StringAxisMap, AxisMapType], + expected_axis_map: AxisMapType, + expected_critical_point: AxisMapType, + speed: Optional[float], +) -> None: + """Test `RobotContext.move_axes_to`.""" + subject.move_axes_to(axis_map, critical_point, speed) + decoy.verify( + subject._core.move_axes_to(expected_axis_map, expected_critical_point, speed) + ) + + +@pytest.mark.parametrize( + argnames=["axis_map", "converted_map", "speed"], + argvalues=[ + ( + {"x": 10, "Y": 10, "z_g": 10}, + {AxisType.X: 10, AxisType.Y: 10, AxisType.Z_G: 10}, + None, + ), + ({AxisType.P_L: 10}, {AxisType.P_L: 10}, 5), + ], +) +def test_move_axes_relative( + decoy: Decoy, + subject: RobotContext, + axis_map: Union[StringAxisMap, AxisMapType], + converted_map: AxisMapType, + speed: Optional[float], +) -> None: + """Test `RobotContext.move_axes_relative`.""" + subject.move_axes_relative(axis_map, speed) + decoy.verify(subject._core.move_axes_relative(converted_map, speed)) + + +@pytest.mark.parametrize( + argnames=["mount", "location_to_move", "expected_axis_map"], + argvalues=[ + ( + "left", + Location(point=Point(1, 2, 3), labware=None), + {AxisType.Z_L: 3, AxisType.X: 1, AxisType.Y: 2}, + ), + ( + Mount.EXTENSION, + Location(point=Point(1, 2, 3), labware=None), + {AxisType.Z_G: 3, AxisType.X: 1, AxisType.Y: 2}, + ), + ], +) +def test_get_axes_coordinates_for( + subject: RobotContext, + mount: Union[Mount, str], + location_to_move: Union[Location, ModuleContext, DeckLocation], + expected_axis_map: AxisMapType, +) -> None: + """Test `RobotContext.get_axis_coordinates_for`.""" + res = subject.axis_coordinates_for(mount, location_to_move) + assert res == expected_axis_map + + +@pytest.mark.parametrize( + argnames=["mount", "volume", "action", "expected_axis_map"], + argvalues=[ + (Mount.RIGHT, 200, PipetteActionTypes.ASPIRATE_ACTION, {AxisType.P_R: 100}), + (Mount.LEFT, 100, PipetteActionTypes.DISPENSE_ACTION, {AxisType.P_L: 100}), + ], +) +def test_plunger_coordinates_for_volume( + decoy: Decoy, + subject: RobotContext, + mount: Mount, + volume: float, + action: PipetteActionTypes, + expected_axis_map: AxisMapType, +) -> None: + """Test `RobotContext.plunger_coordinates_for_volume`.""" + decoy.when( + subject._core.get_plunger_position_from_volume( + mount, volume, action, "OT-3 Standard" + ) + ).then_return(100) + + result = subject.plunger_coordinates_for_volume(mount, volume, action) + assert result == expected_axis_map + + +@pytest.mark.parametrize( + argnames=["mount", "position_name", "expected_axis_map"], + argvalues=[ + (Mount.RIGHT, PlungerPositionTypes.PLUNGER_TOP, {AxisType.P_R: 3}), + ( + Mount.RIGHT, + PlungerPositionTypes.PLUNGER_BOTTOM, + {AxisType.P_R: 3}, + ), + ], +) +def test_plunger_coordinates_for_named_position( + decoy: Decoy, + subject: RobotContext, + mount: Mount, + position_name: PlungerPositionTypes, + expected_axis_map: AxisMapType, +) -> None: + """Test `RobotContext.plunger_coordinates_for_named_position`.""" + decoy.when( + subject._core.get_plunger_position_from_name(mount, position_name) + ).then_return(3) + result = subject.plunger_coordinates_for_named_position(mount, position_name) + assert result == expected_axis_map + + +def test_plunger_methods_raise_without_pipette( + mock_core: RobotCore, mock_protocol: ProtocolCore, api_version: APIVersion +) -> None: + """Test that `RobotContext` plunger functions raise without pipette attached.""" + subject = RobotContext( + core=mock_core, api_version=api_version, protocol_core=mock_protocol + ) + with pytest.raises(ValueError): + subject.plunger_coordinates_for_named_position( + Mount.LEFT, PlungerPositionTypes.PLUNGER_TOP + ) + + with pytest.raises(ValueError): + subject.plunger_coordinates_for_volume( + Mount.LEFT, 200, PipetteActionTypes.ASPIRATE_ACTION + ) diff --git a/api/tests/opentrons/protocol_api/test_validation.py b/api/tests/opentrons/protocol_api/test_validation.py index 2a2ed6375b0..342e197535b 100644 --- a/api/tests/opentrons/protocol_api/test_validation.py +++ b/api/tests/opentrons/protocol_api/test_validation.py @@ -1,11 +1,12 @@ """Tests for Protocol API input validation.""" -from typing import ContextManager, List, Type, Union, Optional, Dict, Any +from typing import ContextManager, List, Type, Union, Optional, Dict, Sequence, Any from contextlib import nullcontext as do_not_raise from decoy import Decoy import pytest import re +from opentrons.protocols.advanced_control.transfers.common import TransferTipPolicyV2 from opentrons_shared_data.labware.labware_definition import ( LabwareRole, Parameters as LabwareDefinitionParameters, @@ -13,7 +14,16 @@ from opentrons_shared_data.pipette.types import PipetteNameType from opentrons_shared_data.robot.types import RobotType -from opentrons.types import Mount, DeckSlotName, StagingSlotName, Location, Point +from opentrons.types import ( + Mount, + DeckSlotName, + AxisType, + AxisMapType, + StringAxisMap, + StagingSlotName, + Location, + Point, +) from opentrons.hardware_control.modules.types import ( ModuleModel, MagneticModuleModel, @@ -25,7 +35,13 @@ from opentrons.protocols.models import LabwareDefinition from opentrons.protocols.api_support.types import APIVersion from opentrons.protocols.api_support.util import APIVersionError -from opentrons.protocol_api import validation as subject, Well, Labware +from opentrons.protocol_api import ( + validation as subject, + Well, + Labware, + TrashBin, + WasteChute, +) @pytest.mark.parametrize( @@ -456,7 +472,7 @@ def test_validate_well_no_location(decoy: Decoy) -> None: assert result == expected_result -def test_validate_coordinates(decoy: Decoy) -> None: +def test_validate_well_coordinates(decoy: Decoy) -> None: """Should return a WellTarget with no location.""" input_location = Location(point=Point(x=1, y=1, z=2), labware=None) expected_result = subject.PointTarget(location=input_location, in_place=False) @@ -559,3 +575,298 @@ def test_validate_last_location_with_labware(decoy: Decoy) -> None: result = subject.validate_location(location=None, last_location=input_last_location) assert result == subject.PointTarget(location=input_last_location, in_place=True) + + +def test_ensure_boolean() -> None: + """It should return a boolean value.""" + assert subject.ensure_boolean(False) is False + + +@pytest.mark.parametrize("value", [0, "False", "f", 0.0]) +def test_ensure_boolean_raises(value: Union[str, int, float]) -> None: + """It should raise if the value is not a boolean.""" + with pytest.raises(ValueError, match="must be a boolean"): + subject.ensure_boolean(value) # type: ignore[arg-type] + + +@pytest.mark.parametrize("value", [-1.23, -1, 0, 0.0, 1, 1.23]) +def test_ensure_float(value: Union[int, float]) -> None: + """It should return a float value.""" + assert subject.ensure_float(value) == float(value) + + +def test_ensure_float_raises() -> None: + """It should raise if the value is not a float or an integer.""" + with pytest.raises(ValueError, match="must be a floating point"): + subject.ensure_float("1.23") # type: ignore[arg-type] + + +@pytest.mark.parametrize("value", [0, 0.1, 1, 1.0]) +def test_ensure_positive_float(value: Union[int, float]) -> None: + """It should return a positive float.""" + assert subject.ensure_positive_float(value) == float(value) + + +@pytest.mark.parametrize("value", [-1, -1.0, float("inf"), float("-inf"), float("nan")]) +def test_ensure_positive_float_raises(value: Union[int, float]) -> None: + """It should raise if value is not a positive float.""" + with pytest.raises(ValueError, match="(non-infinite|positive float)"): + subject.ensure_positive_float(value) + + +def test_ensure_positive_int() -> None: + """It should return a positive int.""" + assert subject.ensure_positive_int(42) == 42 + + +@pytest.mark.parametrize("value", [1.0, -1.0, -1]) +def test_ensure_positive_int_raises(value: Union[int, float]) -> None: + """It should raise if value is not a positive integer.""" + with pytest.raises(ValueError, match="integer"): + subject.ensure_positive_int(value) # type: ignore[arg-type] + + +def test_validate_coordinates() -> None: + """It should validate the coordinates and return them as a tuple.""" + assert subject.validate_coordinates([1, 2.0, 3.3]) == (1.0, 2.0, 3.3) + + +@pytest.mark.parametrize("value", [[1, 2.0], [1, 2.0, 3.3, 4.2], ["1", 2, 3]]) +def test_validate_coordinates_raises(value: Sequence[Union[int, float, str]]) -> None: + """It should raise if value is not a valid sequence of three numbers.""" + with pytest.raises(ValueError, match="(exactly three|must be floats)"): + subject.validate_coordinates(value) # type: ignore[arg-type] + + +@pytest.mark.parametrize( + argnames=["axis_map", "robot_type", "is_96_channel", "expected_axis_map"], + argvalues=[ + ( + {"x": 100, "Y": 50, "z_g": 80}, + "OT-3 Standard", + True, + {AxisType.X: 100, AxisType.Y: 50, AxisType.Z_G: 80}, + ), + ({"z_r": 80}, "OT-2 Standard", False, {AxisType.Z_R: 80}), + ( + {"Z_L": 19, "P_L": 20}, + "OT-2 Standard", + False, + {AxisType.Z_L: 19, AxisType.P_L: 20}, + ), + ({"Q": 5}, "OT-3 Standard", True, {AxisType.Q: 5}), + ], +) +def test_ensure_axis_map_type_success( + axis_map: Union[AxisMapType, StringAxisMap], + robot_type: RobotType, + is_96_channel: bool, + expected_axis_map: AxisMapType, +) -> None: + """Check that axis map type validation returns the correct shape.""" + res = subject.ensure_axis_map_type(axis_map, robot_type, is_96_channel) + assert res == expected_axis_map + + +@pytest.mark.parametrize( + argnames=["axis_map", "robot_type", "is_96_channel", "error_message"], + argvalues=[ + ( + {AxisType.X: 100, "y": 50}, + "OT-3 Standard", + True, + "Please provide an `axis_map` with only string or only AxisType keys", + ), + ( + {AxisType.Z_R: 60}, + "OT-3 Standard", + True, + "A 96 channel is attached. You cannot move the `Z_R` mount.", + ), + ( + {"Z_G": 19, "P_L": 20}, + "OT-2 Standard", + False, + "An OT-2 Robot only accepts the following axes ", + ), + ( + {"Q": 5}, + "OT-3 Standard", + False, + "A 96 channel is not attached. The clamp `Q` motor does not exist.", + ), + ], +) +def test_ensure_axis_map_type_failure( + axis_map: Union[AxisMapType, StringAxisMap], + robot_type: RobotType, + is_96_channel: bool, + error_message: str, +) -> None: + """Check that axis_map validation occurs for the given scenarios.""" + with pytest.raises(subject.IncorrectAxisError, match=error_message): + subject.ensure_axis_map_type(axis_map, robot_type, is_96_channel) + + +@pytest.mark.parametrize( + argnames=["axis_map", "robot_type", "error_message"], + argvalues=[ + ( + {AxisType.X: 100, AxisType.P_L: 50}, + "OT-3 Standard", + "A critical point only accepts Flex gantry axes which are ", + ), + ( + {AxisType.Z_G: 60}, + "OT-2 Standard", + "A critical point only accepts OT-2 gantry axes which are ", + ), + ], +) +def test_ensure_only_gantry_axis_map_type( + axis_map: AxisMapType, robot_type: RobotType, error_message: str +) -> None: + """Check that gantry axis_map validation occurs for the given scenarios.""" + with pytest.raises(subject.IncorrectAxisError, match=error_message): + subject.ensure_only_gantry_axis_map_type(axis_map, robot_type) + + +@pytest.mark.parametrize( + ["value", "expected_result"], + [ + ("once", TransferTipPolicyV2.ONCE), + ("NEVER", TransferTipPolicyV2.NEVER), + ("alWaYs", TransferTipPolicyV2.ALWAYS), + ("Per Source", TransferTipPolicyV2.PER_SOURCE), + ], +) +def test_ensure_new_tip_policy( + value: str, expected_result: TransferTipPolicyV2 +) -> None: + """It should return the expected tip policy.""" + assert subject.ensure_new_tip_policy(value) == expected_result + + +def test_ensure_new_tip_policy_raises() -> None: + """It should raise ValueError for invalid new_tip value.""" + with pytest.raises(ValueError, match="is invalid value for 'new_tip'"): + subject.ensure_new_tip_policy("blah") + + +@pytest.mark.parametrize( + ["target", "expected_raise"], + [ + ( + "a", + pytest.raises( + ValueError, match="'a' is not a valid location for transfer." + ), + ), + ( + ["a"], + pytest.raises( + ValueError, match="'a' is not a valid location for transfer." + ), + ), + ( + [("a",)], + pytest.raises( + ValueError, match="'a' is not a valid location for transfer." + ), + ), + ( + [], + pytest.raises( + ValueError, match="No target well\\(s\\) specified for transfer." + ), + ), + ], +) +def test_ensure_valid_flat_wells_list_raises_for_invalid_targets( + target: Any, + expected_raise: ContextManager[Any], +) -> None: + """It should raise an error if target location is invalid.""" + with expected_raise: + subject.ensure_valid_flat_wells_list_for_transfer_v2(target) + + +def test_ensure_valid_flat_wells_list_raises_for_mixed_targets(decoy: Decoy) -> None: + """It should raise appropriate error if target has mixed valid and invalid wells.""" + target1 = [decoy.mock(cls=Well), "a"] + with pytest.raises(ValueError, match="'a' is not a valid location for transfer."): + subject.ensure_valid_flat_wells_list_for_transfer_v2(target1) # type: ignore[arg-type] + + target2 = [[decoy.mock(cls=Well)], ["a"]] + with pytest.raises(ValueError, match="'a' is not a valid location for transfer."): + subject.ensure_valid_flat_wells_list_for_transfer_v2(target2) # type: ignore[arg-type] + + +def test_ensure_valid_flat_wells_list(decoy: Decoy) -> None: + """It should convert the locations to flat lists correctly.""" + target1 = decoy.mock(cls=Well) + target2 = decoy.mock(cls=Well) + + assert subject.ensure_valid_flat_wells_list_for_transfer_v2(target1) == [target1] + assert subject.ensure_valid_flat_wells_list_for_transfer_v2([target1, target2]) == [ + target1, + target2, + ] + assert subject.ensure_valid_flat_wells_list_for_transfer_v2( + [ + [target1, target1], + [target2, target2], + ] + ) == [target1, target1, target2, target2] + assert subject.ensure_valid_flat_wells_list_for_transfer_v2((target1, target2)) == [ + target1, + target2, + ] + assert subject.ensure_valid_flat_wells_list_for_transfer_v2( + ( + [target1, target1], + [target2, target2], + ) + ) == [target1, target1, target2, target2] + + +def test_ensure_valid_tip_drop_location_for_transfer_v2( + decoy: Decoy, +) -> None: + """It should check that the tip drop location is valid.""" + mock_well = decoy.mock(cls=Well) + mock_location = Location(point=Point(x=1, y=1, z=1), labware=mock_well) + mock_trash_bin = decoy.mock(cls=TrashBin) + mock_waste_chute = decoy.mock(cls=WasteChute) + assert ( + subject.ensure_valid_tip_drop_location_for_transfer_v2(mock_well) == mock_well + ) + assert ( + subject.ensure_valid_tip_drop_location_for_transfer_v2(mock_location) + == mock_location + ) + assert ( + subject.ensure_valid_tip_drop_location_for_transfer_v2(mock_trash_bin) + == mock_trash_bin + ) + assert ( + subject.ensure_valid_tip_drop_location_for_transfer_v2(mock_waste_chute) + == mock_waste_chute + ) + + +def test_ensure_valid_tip_drop_location_for_transfer_v2_raises(decoy: Decoy) -> None: + """It should raise an error for invalid tip drop locations.""" + with pytest.raises(TypeError, match="However, it is '\\['a'\\]'"): + subject.ensure_valid_tip_drop_location_for_transfer_v2(["a"]) # type: ignore[arg-type] + + mock_labware = decoy.mock(cls=Labware) + with pytest.raises(TypeError, match=f"However, it is '{mock_labware}'"): + subject.ensure_valid_tip_drop_location_for_transfer_v2(mock_labware) # type: ignore[arg-type] + + with pytest.raises( + TypeError, match="However, the given location doesn't refer to any well." + ): + subject.ensure_valid_tip_drop_location_for_transfer_v2( + Location(point=Point(x=1, y=1, z=1), labware=None) + ) diff --git a/api/tests/opentrons/protocol_api/test_well.py b/api/tests/opentrons/protocol_api/test_well.py index 00cbbac8fa7..c0ef530289b 100644 --- a/api/tests/opentrons/protocol_api/test_well.py +++ b/api/tests/opentrons/protocol_api/test_well.py @@ -1,4 +1,5 @@ """Tests for the InstrumentContext public interface.""" + import pytest from decoy import Decoy @@ -101,6 +102,16 @@ def test_well_center(decoy: Decoy, mock_well_core: WellCore, subject: Well) -> N assert result.labware.as_well() is subject +def test_well_meniscus(decoy: Decoy, mock_well_core: WellCore, subject: Well) -> None: + """It should get a Location representing the meniscus of the well.""" + result = subject.meniscus(4.2) + + assert isinstance(result, Location) + assert result.point == Point(0, 0, 4.2) + assert result.is_meniscus is True + assert result.labware.as_well() is subject + + def test_has_tip(decoy: Decoy, mock_well_core: WellCore, subject: Well) -> None: """It should get tip state from the core.""" decoy.when(mock_well_core.has_tip()).then_return(True) diff --git a/api/tests/opentrons/protocol_api_integration/conftest.py b/api/tests/opentrons/protocol_api_integration/conftest.py new file mode 100644 index 00000000000..fa98ccbb039 --- /dev/null +++ b/api/tests/opentrons/protocol_api_integration/conftest.py @@ -0,0 +1,28 @@ +"""Fixtures for protocol api integration tests.""" + +import pytest +from _pytest.fixtures import SubRequest +from typing import Generator + +from opentrons import simulate, protocol_api +from opentrons.protocol_api.core.engine import ENGINE_CORE_API_VERSION + + +@pytest.fixture +def simulated_protocol_context( + request: SubRequest, +) -> Generator[protocol_api.ProtocolContext, None, None]: + """Return a protocol context with requested version and robot.""" + version, robot_type = request.param + context = simulate.get_protocol_api(version=version, robot_type=robot_type) + try: + yield context + finally: + if context.api_version >= ENGINE_CORE_API_VERSION: + # TODO(jbl, 2024-11-14) this is a hack of a hack to close the hardware and the PE thread when a test is + # complete. At some point this should be replaced with a more holistic way of safely cleaning up these + # threads so they don't leak and cause tests to fail when `get_protocol_api` is called too many times. + simulate._LIVE_PROTOCOL_ENGINE_CONTEXTS.close() + else: + # If this is a non-PE context we need to clean up the hardware thread manually + context._hw_manager.hardware.clean_up() diff --git a/api/tests/opentrons/protocol_api_integration/test_liquid_classes.py b/api/tests/opentrons/protocol_api_integration/test_liquid_classes.py new file mode 100644 index 00000000000..20bbd2b646c --- /dev/null +++ b/api/tests/opentrons/protocol_api_integration/test_liquid_classes.py @@ -0,0 +1,65 @@ +"""Tests for the APIs around liquid classes.""" +import pytest +from decoy import Decoy +from opentrons_shared_data.robot.types import RobotTypeEnum + +from opentrons.protocol_api import ProtocolContext +from opentrons.config import feature_flags as ff + + +@pytest.mark.ot3_only +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.20", "Flex")], indirect=True +) +def test_liquid_class_creation_and_property_fetching( + decoy: Decoy, + mock_feature_flags: None, + simulated_protocol_context: ProtocolContext, +) -> None: + """It should create the liquid class and provide access to its properties.""" + decoy.when(ff.allow_liquid_classes(RobotTypeEnum.FLEX)).then_return(True) + pipette_load_name = "flex_8channel_50" + simulated_protocol_context.load_instrument(pipette_load_name, mount="left") + tiprack = simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_50ul", "D1" + ) + water = simulated_protocol_context.define_liquid_class("water") + + assert water.name == "water" + assert water.display_name == "Water" + + # TODO (spp, 2024-10-17): update this to fetch pipette load name from instrument context + assert ( + water.get_for( + pipette_load_name, tiprack.load_name + ).dispense.flow_rate_by_volume.get_for_volume(1) + == 50 + ) + assert ( + water.get_for(pipette_load_name, tiprack.load_name).aspirate.submerge.speed + == 100 + ) + + with pytest.raises( + ValueError, + match="No properties found for non-existent-pipette in water liquid class", + ): + water.get_for("non-existent-pipette", tiprack.load_name) + + with pytest.raises(AttributeError): + water.name = "foo" # type: ignore + + with pytest.raises(AttributeError): + water.display_name = "bar" # type: ignore + + with pytest.raises(ValueError, match="Liquid class definition not found"): + simulated_protocol_context.define_liquid_class("non-existent-liquid") + + +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.20", "OT-2")], indirect=True +) +def test_liquid_class_feature_flag(simulated_protocol_context: ProtocolContext) -> None: + """It should raise a not implemented error without the allowLiquidClass flag set.""" + with pytest.raises(NotImplementedError): + simulated_protocol_context.define_liquid_class("water") diff --git a/api/tests/opentrons/protocol_api_integration/test_modules.py b/api/tests/opentrons/protocol_api_integration/test_modules.py new file mode 100644 index 00000000000..72ee8ed8c52 --- /dev/null +++ b/api/tests/opentrons/protocol_api_integration/test_modules.py @@ -0,0 +1,83 @@ +"""Tests for modules.""" + +import typing +import pytest + +from opentrons import protocol_api + + +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.21", "Flex")], indirect=True +) +def test_absorbance_reader_labware_load_conflict( + simulated_protocol_context: protocol_api.ProtocolContext, +) -> None: + """It should prevent loading a labware onto a closed absorbance reader.""" + module = simulated_protocol_context.load_module("absorbanceReaderV1", "A3") + + # The lid should be treated as initially closed. + with pytest.raises(Exception): + module.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt") + + module.open_lid() # type: ignore[union-attr] + # Should not raise after opening the lid. + labware_1 = module.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt") + + simulated_protocol_context.move_labware(labware_1, protocol_api.OFF_DECK) + + # Should raise after closing the lid again. + module.close_lid() # type: ignore[union-attr] + with pytest.raises(Exception): + module.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt") + + +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.21", "Flex")], indirect=True +) +def test_absorbance_reader_labware_move_conflict( + simulated_protocol_context: protocol_api.ProtocolContext, +) -> None: + """It should prevent moving a labware onto a closed absorbance reader.""" + module = simulated_protocol_context.load_module("absorbanceReaderV1", "A3") + labware = simulated_protocol_context.load_labware( + "opentrons_96_wellplate_200ul_pcr_full_skirt", "A1" + ) + + with pytest.raises(Exception): + # The lid should be treated as initially closed. + simulated_protocol_context.move_labware(labware, module, use_gripper=True) + + module.open_lid() # type: ignore[union-attr] + # Should not raise after opening the lid. + simulated_protocol_context.move_labware(labware, module, use_gripper=True) + + simulated_protocol_context.move_labware(labware, "A1", use_gripper=True) + + # Should raise after closing the lid again. + module.close_lid() # type: ignore[union-attr] + with pytest.raises(Exception): + simulated_protocol_context.move_labware(labware, module, use_gripper=True) + + +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.21", "Flex")], indirect=True +) +def test_absorbance_reader_read_preconditions( + simulated_protocol_context: protocol_api.ProtocolContext, +) -> None: + """Test the preconditions for triggering an absorbance reader read.""" + module = typing.cast( + protocol_api.AbsorbanceReaderContext, + simulated_protocol_context.load_module("absorbanceReaderV1", "A3"), + ) + + with pytest.raises(Exception, match="initialize"): + module.read() # .initialize() must be called first. + + with pytest.raises(Exception, match="close"): + module.initialize("single", [500]) # .close_lid() must be called first. + + module.close_lid() + module.initialize("single", [500]) + + module.read() # Should not raise now. diff --git a/api/tests/opentrons/protocol_api_integration/test_pipette_movement_deck_conflicts.py b/api/tests/opentrons/protocol_api_integration/test_pipette_movement_deck_conflicts.py index 59523fd2c91..2b7fc11ca91 100644 --- a/api/tests/opentrons/protocol_api_integration/test_pipette_movement_deck_conflicts.py +++ b/api/tests/opentrons/protocol_api_integration/test_pipette_movement_deck_conflicts.py @@ -2,54 +2,59 @@ import pytest -from opentrons import simulate -from opentrons.protocol_api import COLUMN, ALL, SINGLE -from opentrons.protocol_api.core.engine.deck_conflict import ( +from opentrons.protocol_api import COLUMN, ALL, SINGLE, ROW, ProtocolContext +from opentrons.protocol_api.core.engine.pipette_movement_conflict import ( PartialTipMovementNotAllowedError, ) @pytest.mark.ot3_only -def test_deck_conflicts_for_96_ch_a12_column_configuration() -> None: +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.16", "Flex")], indirect=True +) +def test_deck_conflicts_for_96_ch_a12_column_configuration( + simulated_protocol_context: ProtocolContext, +) -> None: """It should raise errors for the expected deck conflicts.""" - protocol_context = simulate.get_protocol_api(version="2.16", robot_type="Flex") - trash_labware = protocol_context.load_labware( + trash_labware = simulated_protocol_context.load_labware( "opentrons_1_trash_3200ml_fixed", "A3" ) - badly_placed_tiprack = protocol_context.load_labware( + badly_placed_tiprack = simulated_protocol_context.load_labware( "opentrons_flex_96_tiprack_50ul", "C2" ) - well_placed_tiprack = protocol_context.load_labware( + well_placed_tiprack = simulated_protocol_context.load_labware( "opentrons_flex_96_tiprack_50ul", "C1" ) - tiprack_on_adapter = protocol_context.load_labware( + tiprack_on_adapter = simulated_protocol_context.load_labware( "opentrons_flex_96_tiprack_50ul", "C3", adapter="opentrons_flex_96_tiprack_adapter", ) - thermocycler = protocol_context.load_module("thermocyclerModuleV2") - tc_adjacent_plate = protocol_context.load_labware( + thermocycler = simulated_protocol_context.load_module("thermocyclerModuleV2") + tc_adjacent_plate = simulated_protocol_context.load_labware( "opentrons_96_wellplate_200ul_pcr_full_skirt", "A2" ) accessible_plate = thermocycler.load_labware( "opentrons_96_wellplate_200ul_pcr_full_skirt" ) - instrument = protocol_context.load_instrument("flex_96channel_1000", mount="left") + instrument = simulated_protocol_context.load_instrument( + "flex_96channel_1000", mount="left" + ) instrument.trash_container = trash_labware # ############ SHORT LABWARE ################ # These labware should be to the west of tall labware to avoid any partial tip deck conflicts - badly_placed_labware = protocol_context.load_labware( + badly_placed_labware = simulated_protocol_context.load_labware( "nest_96_wellplate_200ul_flat", "D2" ) - well_placed_labware = protocol_context.load_labware( + well_placed_labware = simulated_protocol_context.load_labware( "nest_96_wellplate_200ul_flat", "D3" ) # ############ TALL LABWARE ############## - protocol_context.load_labware( + simulated_protocol_context.load_labware( "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", "D1" ) @@ -61,13 +66,6 @@ def test_deck_conflicts_for_96_ch_a12_column_configuration() -> None: ): instrument.pick_up_tip(badly_placed_tiprack.wells_by_name()["A1"]) - with pytest.raises( - PartialTipMovementNotAllowedError, match="outside of robot bounds" - ): - # Picking up from A1 in an east-most slot using a configuration with column 12 would - # result in a collision with the side of the robot. - instrument.pick_up_tip(well_placed_tiprack.wells_by_name()["A1"]) - instrument.pick_up_tip(well_placed_tiprack.wells_by_name()["A12"]) instrument.aspirate(50, well_placed_labware.wells_by_name()["A4"]) @@ -111,24 +109,30 @@ def test_deck_conflicts_for_96_ch_a12_column_configuration() -> None: @pytest.mark.ot3_only -def test_close_shave_deck_conflicts_for_96_ch_a12_column_configuration() -> None: +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.20", "Flex")], indirect=True +) +def test_close_shave_deck_conflicts_for_96_ch_a12_column_configuration( + simulated_protocol_context: ProtocolContext, +) -> None: """Shouldn't raise errors for "almost collision"s.""" - protocol_context = simulate.get_protocol_api(version="2.20", robot_type="Flex") - res12 = protocol_context.load_labware("nest_12_reservoir_15ml", "C3") + res12 = simulated_protocol_context.load_labware("nest_12_reservoir_15ml", "C3") # Mag block and tiprack adapter are very close to the destination reservoir labware - protocol_context.load_module("magneticBlockV1", "D2") - protocol_context.load_labware( + simulated_protocol_context.load_module("magneticBlockV1", "D2") + simulated_protocol_context.load_labware( "opentrons_flex_96_tiprack_200ul", "B3", adapter="opentrons_flex_96_tiprack_adapter", ) - tiprack_8 = protocol_context.load_labware("opentrons_flex_96_tiprack_200ul", "B2") - hs = protocol_context.load_module("heaterShakerModuleV1", "C1") + tiprack_8 = simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_200ul", "B2" + ) + hs = simulated_protocol_context.load_module("heaterShakerModuleV1", "C1") hs_adapter = hs.load_adapter("opentrons_96_deep_well_adapter") deepwell = hs_adapter.load_labware("nest_96_wellplate_2ml_deep") - protocol_context.load_trash_bin("A3") - p1000_96 = protocol_context.load_instrument("flex_96channel_1000") + simulated_protocol_context.load_trash_bin("A3") + p1000_96 = simulated_protocol_context.load_instrument("flex_96channel_1000") p1000_96.configure_nozzle_layout(style=SINGLE, start="A12", tip_racks=[tiprack_8]) hs.close_labware_latch() # type: ignore[union-attr] @@ -142,16 +146,28 @@ def test_close_shave_deck_conflicts_for_96_ch_a12_column_configuration() -> None @pytest.mark.ot3_only -def test_deck_conflicts_for_96_ch_a1_column_configuration() -> None: +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.16", "Flex")], indirect=True +) +def test_deck_conflicts_for_96_ch_a1_column_configuration( + simulated_protocol_context: ProtocolContext, +) -> None: """It should raise errors for expected deck conflicts.""" - protocol = simulate.get_protocol_api(version="2.16", robot_type="Flex") - instrument = protocol.load_instrument("flex_96channel_1000", mount="left") - trash_labware = protocol.load_labware("opentrons_1_trash_3200ml_fixed", "A3") + instrument = simulated_protocol_context.load_instrument( + "flex_96channel_1000", mount="left" + ) + trash_labware = simulated_protocol_context.load_labware( + "opentrons_1_trash_3200ml_fixed", "A3" + ) instrument.trash_container = trash_labware - badly_placed_tiprack = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "C2") - well_placed_tiprack = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "A1") - tiprack_on_adapter = protocol.load_labware( + badly_placed_tiprack = simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_50ul", "C2" + ) + well_placed_tiprack = simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_50ul", "A1" + ) + tiprack_on_adapter = simulated_protocol_context.load_labware( "opentrons_flex_96_tiprack_50ul", "C3", adapter="opentrons_flex_96_tiprack_adapter", @@ -159,11 +175,15 @@ def test_deck_conflicts_for_96_ch_a1_column_configuration() -> None: # ############ SHORT LABWARE ################ # These labware should be to the east of tall labware to avoid any partial tip deck conflicts - badly_placed_plate = protocol.load_labware("nest_96_wellplate_200ul_flat", "B1") - well_placed_plate = protocol.load_labware("nest_96_wellplate_200ul_flat", "B3") + badly_placed_plate = simulated_protocol_context.load_labware( + "nest_96_wellplate_200ul_flat", "B1" + ) + well_placed_plate = simulated_protocol_context.load_labware( + "nest_96_wellplate_200ul_flat", "B3" + ) # ############ TALL LABWARE ############### - my_tuberack = protocol.load_labware( + my_tuberack = simulated_protocol_context.load_labware( "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", "B2" ) @@ -215,7 +235,7 @@ def test_deck_conflicts_for_96_ch_a1_column_configuration() -> None: instrument.drop_tip() instrument.trash_container = None # type: ignore - protocol.load_trash_bin("C1") + simulated_protocol_context.load_trash_bin("C1") # This doesn't raise an error because it now treats the trash bin as an addressable area # and the bounds check doesn't yet check moves to addressable areas. @@ -233,3 +253,118 @@ def test_deck_conflicts_for_96_ch_a1_column_configuration() -> None: # No error NOW because of full config instrument.dispense(50, badly_placed_plate.wells_by_name()["A1"].bottom()) + + +@pytest.mark.ot3_only +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.20", "Flex")], indirect=True +) +def test_deck_conflicts_for_96_ch_and_reservoirs( + simulated_protocol_context: ProtocolContext, +) -> None: + """It should raise errors for expected deck conflicts when moving to reservoirs. + + This test checks that the critical point of the pipette is taken into account, + specifically when it differs from the primary nozzle. + """ + instrument = simulated_protocol_context.load_instrument( + "flex_96channel_1000", mount="left" + ) + # trash_labware = protocol.load_labware("opentrons_1_trash_3200ml_fixed", "A3") + # instrument.trash_container = trash_labware + + simulated_protocol_context.load_trash_bin("A3") + right_tiprack = simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_50ul", "C3" + ) + front_tiprack = simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_50ul", "D2" + ) + # Tall deck item in B3 + simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_50ul", + "B3", + adapter="opentrons_flex_96_tiprack_adapter", + ) + # Tall deck item in B1 + simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_50ul", + "B1", + adapter="opentrons_flex_96_tiprack_adapter", + ) + + # ############ RESERVOIRS ################ + # These labware should be to the east of tall labware to avoid any partial tip deck conflicts + reservoir_1_well = simulated_protocol_context.load_labware( + "nest_1_reservoir_195ml", "C2" + ) + reservoir_12_well = simulated_protocol_context.load_labware( + "nest_12_reservoir_15ml", "B2" + ) + + # ########### Use COLUMN A1 Config ############# + instrument.configure_nozzle_layout(style=COLUMN, start="A1") + + instrument.pick_up_tip(front_tiprack.wells_by_name()["A12"]) + + with pytest.raises( + PartialTipMovementNotAllowedError, match="collision with items in deck slot" + ): + instrument.aspirate(10, reservoir_1_well.wells()[0]) + + instrument.aspirate(25, reservoir_12_well.wells()[0]) + instrument.dispense(10, reservoir_12_well.wells()[1]) + + with pytest.raises( + PartialTipMovementNotAllowedError, match="collision with items in deck slot" + ): + instrument.dispense(15, reservoir_12_well.wells()[3]) + + instrument.drop_tip() + front_tiprack.reset() + + # ########### Use COLUMN A12 Config ############# + instrument.configure_nozzle_layout(style=COLUMN, start="A12") + + instrument.pick_up_tip(front_tiprack.wells_by_name()["A1"]) + instrument.aspirate(50, reservoir_1_well.wells()[0]) + with pytest.raises( + PartialTipMovementNotAllowedError, match="collision with items in deck slot" + ): + instrument.dispense(10, reservoir_12_well.wells()[8]) + + instrument.dispense(15, reservoir_12_well.wells()[11]) + instrument.dispense(10, reservoir_1_well.wells()[0]) + + instrument.drop_tip() + front_tiprack.reset() + + # ######## CHANGE CONFIG TO ROW H1 ######### + instrument.configure_nozzle_layout(style=ROW, start="H1", tip_racks=[front_tiprack]) + with pytest.raises( + PartialTipMovementNotAllowedError, match="collision with items in deck slot" + ): + instrument.pick_up_tip(right_tiprack.wells_by_name()["A1"]) + + instrument.pick_up_tip() + instrument.aspirate(25, reservoir_1_well.wells()[0]) + + instrument.drop_tip() + front_tiprack.reset() + + # ######## CHANGE CONFIG TO ROW A1 ######### + instrument.configure_nozzle_layout(style=ROW, start="A1", tip_racks=[front_tiprack]) + + with pytest.raises( + PartialTipMovementNotAllowedError, match="outside of robot bounds" + ): + instrument.pick_up_tip() + instrument.pick_up_tip(right_tiprack.wells_by_name()["H1"]) + + with pytest.raises( + PartialTipMovementNotAllowedError, match="collision with items in deck slot" + ): + instrument.aspirate(25, reservoir_1_well.wells()[0]) + + instrument.drop_tip() + front_tiprack.reset() diff --git a/api/tests/opentrons/protocol_api_integration/test_trashes.py b/api/tests/opentrons/protocol_api_integration/test_trashes.py index 18dfa62170d..1166ba01c70 100644 --- a/api/tests/opentrons/protocol_api_integration/test_trashes.py +++ b/api/tests/opentrons/protocol_api_integration/test_trashes.py @@ -1,46 +1,42 @@ """Tests for the APIs around waste chutes and trash bins.""" -from opentrons import protocol_api, simulate +from opentrons import protocol_api from opentrons.protocols.api_support.types import APIVersion from opentrons.protocols.api_support.util import UnsupportedAPIError import contextlib from typing import ContextManager, Optional, Type -from typing_extensions import Literal import re import pytest @pytest.mark.parametrize( - ("version", "robot_type", "expected_trash_class"), + ("simulated_protocol_context", "expected_trash_class"), [ - ("2.13", "OT-2", protocol_api.Labware), - ("2.14", "OT-2", protocol_api.Labware), - ("2.15", "OT-2", protocol_api.Labware), + (("2.13", "OT-2"), protocol_api.Labware), + (("2.14", "OT-2"), protocol_api.Labware), + (("2.15", "OT-2"), protocol_api.Labware), pytest.param( - "2.15", - "Flex", + ("2.15", "Flex"), protocol_api.Labware, marks=pytest.mark.ot3_only, # Simulating a Flex protocol requires a Flex hardware API. ), pytest.param( - "2.16", - "OT-2", + ("2.16", "OT-2"), protocol_api.TrashBin, ), pytest.param( - "2.16", - "Flex", + ("2.16", "Flex"), None, marks=pytest.mark.ot3_only, # Simulating a Flex protocol requires a Flex hardware API. ), ], + indirect=["simulated_protocol_context"], ) def test_fixed_trash_presence( - robot_type: Literal["OT-2", "Flex"], - version: str, + simulated_protocol_context: protocol_api.ProtocolContext, expected_trash_class: Optional[Type[object]], ) -> None: """Test the presence of the fixed trash. @@ -49,9 +45,10 @@ def test_fixed_trash_presence( For those that do, ProtocolContext.fixed_trash and InstrumentContext.trash_container should point to it. The type of the object depends on the API version. """ - protocol = simulate.get_protocol_api(version=version, robot_type=robot_type) - instrument = protocol.load_instrument( - "p300_single_gen2" if robot_type == "OT-2" else "flex_1channel_50", + instrument = simulated_protocol_context.load_instrument( + "p300_single_gen2" + if simulated_protocol_context._core.robot_type == "OT-2 Standard" + else "flex_1channel_50", mount="left", ) @@ -59,46 +56,53 @@ def test_fixed_trash_presence( with pytest.raises( UnsupportedAPIError, match=re.escape( - "Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16. You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols." + "Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16." + " You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols." ), ): - protocol.fixed_trash + simulated_protocol_context.fixed_trash with pytest.raises(Exception, match="No trash container has been defined"): instrument.trash_container else: - assert isinstance(protocol.fixed_trash, expected_trash_class) - assert instrument.trash_container is protocol.fixed_trash + assert isinstance(simulated_protocol_context.fixed_trash, expected_trash_class) + assert instrument.trash_container is simulated_protocol_context.fixed_trash @pytest.mark.ot3_only # Simulating a Flex protocol requires a Flex hardware API. -def test_trash_search() -> None: +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.16", "Flex")], indirect=True +) +def test_trash_search(simulated_protocol_context: protocol_api.ProtocolContext) -> None: """Test the automatic trash search for protocols without a fixed trash.""" - protocol = simulate.get_protocol_api(version="2.16", robot_type="Flex") - instrument = protocol.load_instrument("flex_1channel_50", mount="left") + instrument = simulated_protocol_context.load_instrument( + "flex_1channel_50", mount="left" + ) # By default, there should be no trash. with pytest.raises( UnsupportedAPIError, match=re.escape( - "Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16. You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols." + "Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16." + " You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols." ), ): - protocol.fixed_trash + simulated_protocol_context.fixed_trash with pytest.raises(Exception, match="No trash container has been defined"): instrument.trash_container - loaded_first = protocol.load_trash_bin("A1") - loaded_second = protocol.load_trash_bin("B1") + loaded_first = simulated_protocol_context.load_trash_bin("A1") + loaded_second = simulated_protocol_context.load_trash_bin("B1") # After loading some trashes, there should still be no protocol.fixed_trash... with pytest.raises( UnsupportedAPIError, match=re.escape( - "Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16. You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols." + "Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16." + " You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols." ), ): - protocol.fixed_trash + simulated_protocol_context.fixed_trash # ...but instrument.trash_container should automatically update to point to # the first trash that we loaded. assert instrument.trash_container is loaded_first @@ -109,40 +113,36 @@ def test_trash_search() -> None: @pytest.mark.parametrize( - ("version", "robot_type", "expect_load_to_succeed"), + ("simulated_protocol_context", "expect_load_to_succeed"), [ pytest.param( - "2.13", - "OT-2", + ("2.13", "OT-2"), False, # This xfail (the system does let you load a labware onto slot 12, and does not raise) # is surprising to me. It may be be a bug in old PAPI versions. marks=pytest.mark.xfail(strict=True, raises=pytest.fail.Exception), ), - ("2.14", "OT-2", False), - ("2.15", "OT-2", False), + (("2.14", "OT-2"), False), + (("2.15", "OT-2"), False), pytest.param( - "2.15", - "Flex", + ("2.15", "Flex"), False, marks=pytest.mark.ot3_only, # Simulating a Flex protocol requires a Flex hardware API. ), pytest.param( - "2.16", - "OT-2", + ("2.16", "OT-2"), False, ), pytest.param( - "2.16", - "Flex", + ("2.16", "Flex"), True, marks=pytest.mark.ot3_only, # Simulating a Flex protocol requires a Flex hardware API. ), ], + indirect=["simulated_protocol_context"], ) def test_fixed_trash_load_conflicts( - robot_type: Literal["Flex", "OT-2"], - version: str, + simulated_protocol_context: protocol_api.ProtocolContext, expect_load_to_succeed: bool, ) -> None: """Test loading something onto the location historically used for the fixed trash. @@ -150,14 +150,12 @@ def test_fixed_trash_load_conflicts( In configurations where there is a fixed trash, this should be disallowed. In configurations without a fixed trash, this should be allowed. """ - protocol = simulate.get_protocol_api(version=version, robot_type=robot_type) - if expect_load_to_succeed: expected_error: ContextManager[object] = contextlib.nullcontext() else: # If we're expecting an error, it'll be a LocationIsOccupied for 2.15 and below, otherwise # it will fail with an IncompatibleAddressableAreaError, since slot 12 will not be in the deck config - if APIVersion.from_string(version) < APIVersion(2, 16): + if simulated_protocol_context.api_version < APIVersion(2, 16): error_name = "LocationIsOccupiedError" else: error_name = "IncompatibleAddressableAreaError" @@ -169,4 +167,6 @@ def test_fixed_trash_load_conflicts( ) with expected_error: - protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", 12) + simulated_protocol_context.load_labware( + "opentrons_96_wellplate_200ul_pcr_full_skirt", 12 + ) diff --git a/api/tests/opentrons/protocol_api_old/test_context.py b/api/tests/opentrons/protocol_api_old/test_context.py index 098fbf9634b..2ceab400ab4 100644 --- a/api/tests/opentrons/protocol_api_old/test_context.py +++ b/api/tests/opentrons/protocol_api_old/test_context.py @@ -31,7 +31,8 @@ from opentrons.hardware_control.types import Axis, CriticalPoint from opentrons.protocol_api.core.legacy.deck import Deck from opentrons.hardware_control.modules import SimulatingModule -from opentrons.protocols.advanced_control import transfers as tf +from opentrons.protocols.advanced_control.common import MixStrategy, Mix, MixOpts +from opentrons.protocols.advanced_control.transfers import transfer as tf from opentrons.protocols.api_support.types import APIVersion @@ -852,15 +853,15 @@ def fake_execute_transfer(xfer_plan: tf.TransferPlan) -> None: carryover=True, gradient_function=None, disposal_volume=0, - mix_strategy=tf.MixStrategy.BOTH, + mix_strategy=MixStrategy.BOTH, drop_tip_strategy=tf.DropTipStrategy.TRASH, blow_out_strategy=tf.BlowOutStrategy.TRASH, touch_tip_strategy=tf.TouchTipStrategy.NEVER, ), pick_up_tip=tf.PickUpTipOpts(), - mix=tf.Mix( - mix_before=tf.MixOpts(repetitions=2, volume=10, rate=None), - mix_after=tf.MixOpts(repetitions=3, volume=20, rate=None), + mix=Mix( + mix_before=MixOpts(repetitions=2, volume=10, rate=None), + mix_after=MixOpts(repetitions=3, volume=20, rate=None), ), blow_out=tf.BlowOutOpts(), touch_tip=tf.TouchTipOpts(), @@ -889,15 +890,15 @@ def fake_execute_transfer(xfer_plan: tf.TransferPlan) -> None: carryover=True, gradient_function=None, disposal_volume=10, - mix_strategy=tf.MixStrategy.BEFORE, + mix_strategy=MixStrategy.BEFORE, drop_tip_strategy=tf.DropTipStrategy.RETURN, blow_out_strategy=tf.BlowOutStrategy.NONE, touch_tip_strategy=tf.TouchTipStrategy.ALWAYS, ), pick_up_tip=tf.PickUpTipOpts(), - mix=tf.Mix( - mix_before=tf.MixOpts(repetitions=2, volume=30, rate=None), - mix_after=tf.MixOpts(), + mix=Mix( + mix_before=MixOpts(repetitions=2, volume=30, rate=None), + mix_after=MixOpts(), ), blow_out=tf.BlowOutOpts(), touch_tip=tf.TouchTipOpts(), diff --git a/api/tests/opentrons/protocol_api_old/test_instrument.py b/api/tests/opentrons/protocol_api_old/test_instrument.py index 5f274f513b4..fbb98cdce24 100644 --- a/api/tests/opentrons/protocol_api_old/test_instrument.py +++ b/api/tests/opentrons/protocol_api_old/test_instrument.py @@ -4,7 +4,7 @@ from typing import Any, Callable, Dict from opentrons.types import Mount -from opentrons.protocols.advanced_control import transfers +from opentrons.protocols.advanced_control.transfers import transfer as v1_transfer from opentrons.protocols.api_support.types import APIVersion from opentrons.hardware_control import ThreadManagedHardware @@ -89,13 +89,13 @@ def test_blowout_location_invalid( @pytest.mark.parametrize( argnames="liquid_handling_command," "blowout_location," "expected_strat,", argvalues=[ - ["transfer", "destination well", transfers.BlowOutStrategy.DEST], - ["transfer", "source well", transfers.BlowOutStrategy.SOURCE], - ["transfer", "trash", transfers.BlowOutStrategy.TRASH], - ["consolidate", "destination well", transfers.BlowOutStrategy.DEST], - ["consolidate", "trash", transfers.BlowOutStrategy.TRASH], - ["distribute", "source well", transfers.BlowOutStrategy.SOURCE], - ["distribute", "trash", transfers.BlowOutStrategy.TRASH], + ["transfer", "destination well", v1_transfer.BlowOutStrategy.DEST], + ["transfer", "source well", v1_transfer.BlowOutStrategy.SOURCE], + ["transfer", "trash", v1_transfer.BlowOutStrategy.TRASH], + ["consolidate", "destination well", v1_transfer.BlowOutStrategy.DEST], + ["consolidate", "trash", v1_transfer.BlowOutStrategy.TRASH], + ["distribute", "source well", v1_transfer.BlowOutStrategy.SOURCE], + ["distribute", "trash", v1_transfer.BlowOutStrategy.TRASH], ], ) def test_valid_blowout_location( diff --git a/api/tests/opentrons/protocol_engine/commands/calibration/test_calibrate_gripper.py b/api/tests/opentrons/protocol_engine/commands/calibration/test_calibrate_gripper.py index 6ecf768c4eb..4145e1f0b5c 100644 --- a/api/tests/opentrons/protocol_engine/commands/calibration/test_calibrate_gripper.py +++ b/api/tests/opentrons/protocol_engine/commands/calibration/test_calibrate_gripper.py @@ -72,7 +72,6 @@ async def test_calibrate_gripper( result = await subject.execute(params) assert result == SuccessData( public=CalibrateGripperResult(jawOffset=Vec3f(x=1.1, y=2.2, z=3.3)), - private=None, ) diff --git a/api/tests/opentrons/protocol_engine/commands/calibration/test_calibrate_module.py b/api/tests/opentrons/protocol_engine/commands/calibration/test_calibrate_module.py index 0226453c72e..0713bfa37d1 100644 --- a/api/tests/opentrons/protocol_engine/commands/calibration/test_calibrate_module.py +++ b/api/tests/opentrons/protocol_engine/commands/calibration/test_calibrate_module.py @@ -95,7 +95,6 @@ async def test_calibrate_module_implementation( ), location=location, ), - private=None, ) diff --git a/api/tests/opentrons/protocol_engine/commands/calibration/test_calibrate_pipette.py b/api/tests/opentrons/protocol_engine/commands/calibration/test_calibrate_pipette.py index ba949f0e2df..073db3bf295 100644 --- a/api/tests/opentrons/protocol_engine/commands/calibration/test_calibrate_pipette.py +++ b/api/tests/opentrons/protocol_engine/commands/calibration/test_calibrate_pipette.py @@ -64,7 +64,6 @@ async def test_calibrate_pipette_implementation( public=CalibratePipetteResult( pipetteOffset=InstrumentOffsetVector(x=3, y=4, z=6) ), - private=None, ) diff --git a/api/tests/opentrons/protocol_engine/commands/calibration/test_move_to_maintenance_position.py b/api/tests/opentrons/protocol_engine/commands/calibration/test_move_to_maintenance_position.py index dd057d1cf8a..7051d1e44fc 100644 --- a/api/tests/opentrons/protocol_engine/commands/calibration/test_move_to_maintenance_position.py +++ b/api/tests/opentrons/protocol_engine/commands/calibration/test_move_to_maintenance_position.py @@ -14,7 +14,7 @@ ) from opentrons.protocol_engine.commands.command import SuccessData -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.types import MountType, Mount, Point from opentrons.hardware_control.types import Axis, CriticalPoint @@ -35,7 +35,7 @@ def subject( @pytest.mark.ot3_only @pytest.mark.parametrize("mount_type", [MountType.LEFT, MountType.RIGHT]) -async def test_calibration_move_to_location_implementatio_for_attach_instrument( +async def test_calibration_move_to_location_implementation_for_attach_instrument( decoy: Decoy, subject: MoveToMaintenancePositionImplementation, state_view: StateView, @@ -56,7 +56,9 @@ async def test_calibration_move_to_location_implementatio_for_attach_instrument( decoy.when(ot3_hardware_api.get_instrument_max_height(Mount.LEFT)).then_return(300) result = await subject.execute(params=params) - assert result == SuccessData(public=MoveToMaintenancePositionResult(), private=None) + assert result == SuccessData( + public=MoveToMaintenancePositionResult(), + ) hw_mount = mount_type.to_hw_mount() decoy.verify( @@ -79,7 +81,7 @@ async def test_calibration_move_to_location_implementatio_for_attach_instrument( @pytest.mark.ot3_only @pytest.mark.parametrize("mount_type", [MountType.LEFT, MountType.RIGHT]) -async def test_calibration_move_to_location_implementatio_for_attach_plate( +async def test_calibration_move_to_location_implementation_for_attach_plate( decoy: Decoy, subject: MoveToMaintenancePositionImplementation, state_view: StateView, @@ -100,7 +102,9 @@ async def test_calibration_move_to_location_implementatio_for_attach_plate( decoy.when(ot3_hardware_api.get_instrument_max_height(Mount.LEFT)).then_return(300) result = await subject.execute(params=params) - assert result == SuccessData(public=MoveToMaintenancePositionResult(), private=None) + assert result == SuccessData( + public=MoveToMaintenancePositionResult(), + ) decoy.verify( await ot3_hardware_api.prepare_for_mount_movement(Mount.LEFT), @@ -113,11 +117,18 @@ async def test_calibration_move_to_location_implementatio_for_attach_plate( await ot3_hardware_api.move_axes( position={ Axis.Z_L: 90, + } + ), + await ot3_hardware_api.disengage_axes( + [Axis.Z_L], + ), + await ot3_hardware_api.move_axes( + position={ Axis.Z_R: 105, } ), await ot3_hardware_api.disengage_axes( - [Axis.Z_L, Axis.Z_R], + [Axis.Z_R], ), ) @@ -143,7 +154,9 @@ async def test_calibration_move_to_location_implementation_for_gripper( decoy.when(ot3_hardware_api.get_instrument_max_height(Mount.LEFT)).then_return(300) result = await subject.execute(params=params) - assert result == SuccessData(public=MoveToMaintenancePositionResult(), private=None) + assert result == SuccessData( + public=MoveToMaintenancePositionResult(), + ) decoy.verify( await ot3_hardware_api.prepare_for_mount_movement(Mount.LEFT), diff --git a/api/tests/opentrons/protocol_engine/commands/conftest.py b/api/tests/opentrons/protocol_engine/commands/conftest.py index 8749023c96f..1d27dea0536 100644 --- a/api/tests/opentrons/protocol_engine/commands/conftest.py +++ b/api/tests/opentrons/protocol_engine/commands/conftest.py @@ -16,7 +16,7 @@ GantryMover, ) from opentrons.protocol_engine.resources.model_utils import ModelUtils -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView @pytest.fixture diff --git a/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_close_labware_latch.py b/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_close_labware_latch.py index d728b97cb4d..d481ef33b9b 100644 --- a/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_close_labware_latch.py +++ b/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_close_labware_latch.py @@ -3,7 +3,7 @@ from opentrons.hardware_control.modules import HeaterShaker -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( HeaterShakerModuleSubState, HeaterShakerModuleId, @@ -45,7 +45,7 @@ async def test_close_labware_latch( result = await subject.execute(data) decoy.verify(await heater_shaker_hardware.close_labware_latch(), times=1) assert result == SuccessData( - public=heater_shaker.CloseLabwareLatchResult(), private=None + public=heater_shaker.CloseLabwareLatchResult(), ) @@ -77,5 +77,5 @@ async def test_close_labware_latch_virtual( result = await subject.execute(data) assert result == SuccessData( - public=heater_shaker.CloseLabwareLatchResult(), private=None + public=heater_shaker.CloseLabwareLatchResult(), ) diff --git a/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_deactivate_heater.py b/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_deactivate_heater.py index 0da296f71d6..6ce4336c9a3 100644 --- a/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_deactivate_heater.py +++ b/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_deactivate_heater.py @@ -3,7 +3,7 @@ from opentrons.hardware_control.modules import HeaterShaker -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( HeaterShakerModuleSubState, HeaterShakerModuleId, @@ -46,5 +46,5 @@ async def test_deactivate_heater( result = await subject.execute(data) decoy.verify(await hs_hardware.deactivate_heater(), times=1) assert result == SuccessData( - public=heater_shaker.DeactivateHeaterResult(), private=None + public=heater_shaker.DeactivateHeaterResult(), ) diff --git a/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_deactivate_shaker.py b/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_deactivate_shaker.py index 3ab339f97e7..466fa79dcc5 100644 --- a/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_deactivate_shaker.py +++ b/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_deactivate_shaker.py @@ -3,7 +3,7 @@ from opentrons.hardware_control.modules import HeaterShaker -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( HeaterShakerModuleSubState, HeaterShakerModuleId, @@ -46,7 +46,7 @@ async def test_deactivate_shaker( result = await subject.execute(data) decoy.verify(await hs_hardware.deactivate_shaker(), times=1) assert result == SuccessData( - public=heater_shaker.DeactivateShakerResult(), private=None + public=heater_shaker.DeactivateShakerResult(), ) @@ -78,5 +78,5 @@ async def test_deactivate_shaker_virtual( result = await subject.execute(data) assert result == SuccessData( - public=heater_shaker.DeactivateShakerResult(), private=None + public=heater_shaker.DeactivateShakerResult(), ) diff --git a/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_open_labware_latch.py b/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_open_labware_latch.py index 6894c1d7e80..4b122f2d7e2 100644 --- a/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_open_labware_latch.py +++ b/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_open_labware_latch.py @@ -1,9 +1,11 @@ """Test Heater Shaker open labware latch command implementation.""" from decoy import Decoy +import pytest from opentrons.hardware_control.modules import HeaterShaker -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state import update_types +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( HeaterShakerModuleSubState, HeaterShakerModuleId, @@ -17,11 +19,20 @@ from opentrons.protocol_engine.types import MotorAxis +@pytest.mark.parametrize( + ("pipette_blocking_hs_latch", "expect_pipette_retracted"), + [ + (False, False), + (True, True), + ], +) async def test_open_labware_latch( decoy: Decoy, state_view: StateView, equipment: EquipmentHandler, movement: MovementHandler, + pipette_blocking_hs_latch: bool, + expect_pipette_retracted: bool, ) -> None: """It should be able to open the module's labware latch.""" subject = OpenLabwareLatchImpl( @@ -46,7 +57,7 @@ async def test_open_labware_latch( state_view.motion.check_pipette_blocking_hs_latch( HeaterShakerModuleId("heater-shaker-id") ) - ).then_return(True) + ).then_return(pipette_blocking_hs_latch) # Get stubbed hardware module decoy.when( @@ -55,14 +66,22 @@ async def test_open_labware_latch( decoy.when(state_view.motion.get_robot_mount_axes()).then_return( [MotorAxis.EXTENSION_Z] ) + result = await subject.execute(data) - decoy.verify( - hs_module_substate.raise_if_shaking(), - await movement.home( - [MotorAxis.EXTENSION_Z], - ), - await hs_hardware.open_labware_latch(), - ) + + decoy.verify(hs_module_substate.raise_if_shaking()) + if expect_pipette_retracted: + decoy.verify( + await movement.home( + [MotorAxis.EXTENSION_Z], + ) + ) + decoy.verify(await hs_hardware.open_labware_latch()) assert result == SuccessData( - public=heater_shaker.OpenLabwareLatchResult(pipetteRetracted=True), private=None + public=heater_shaker.OpenLabwareLatchResult( + pipetteRetracted=expect_pipette_retracted + ), + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR) + if expect_pipette_retracted + else update_types.StateUpdate(), ) diff --git a/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_set_and_wait_for_shake_speed.py b/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_set_and_wait_for_shake_speed.py index 85e92ffd5b0..9db4bb27d00 100644 --- a/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_set_and_wait_for_shake_speed.py +++ b/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_set_and_wait_for_shake_speed.py @@ -1,9 +1,11 @@ """Test Heater Shaker set shake speed command implementation.""" from decoy import Decoy +import pytest from opentrons.hardware_control.modules import HeaterShaker -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state import update_types +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( HeaterShakerModuleSubState, HeaterShakerModuleId, @@ -17,11 +19,20 @@ from opentrons.protocol_engine.types import MotorAxis +@pytest.mark.parametrize( + ("pipette_blocking_hs_shaker", "expect_pipette_retracted"), + [ + (False, False), + (True, True), + ], +) async def test_set_and_wait_for_shake_speed( decoy: Decoy, state_view: StateView, equipment: EquipmentHandler, movement: MovementHandler, + pipette_blocking_hs_shaker: bool, + expect_pipette_retracted: bool, ) -> None: """It should be able to set the module's shake speed.""" subject = SetAndWaitForShakeSpeedImpl( @@ -49,7 +60,7 @@ async def test_set_and_wait_for_shake_speed( state_view.motion.check_pipette_blocking_hs_shaker( HeaterShakerModuleId("heater-shaker-id") ) - ).then_return(True) + ).then_return(pipette_blocking_hs_shaker) # Stub speed validation from hs module view decoy.when(hs_module_substate.validate_target_speed(rpm=1234.56)).then_return(1234) @@ -61,15 +72,19 @@ async def test_set_and_wait_for_shake_speed( decoy.when(state_view.motion.get_robot_mount_axes()).then_return( [MotorAxis.EXTENSION_Z] ) + result = await subject.execute(data) - decoy.verify( - hs_module_substate.raise_if_labware_latch_not_closed(), - await movement.home( - [MotorAxis.EXTENSION_Z], - ), - await hs_hardware.set_speed(rpm=1234), - ) + + decoy.verify(hs_module_substate.raise_if_labware_latch_not_closed()) + if expect_pipette_retracted: + decoy.verify(await movement.home([MotorAxis.EXTENSION_Z])) + decoy.verify(await hs_hardware.set_speed(rpm=1234)) + assert result == SuccessData( - public=heater_shaker.SetAndWaitForShakeSpeedResult(pipetteRetracted=True), - private=None, + public=heater_shaker.SetAndWaitForShakeSpeedResult( + pipetteRetracted=expect_pipette_retracted + ), + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR) + if expect_pipette_retracted + else update_types.StateUpdate(), ) diff --git a/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_set_target_temperature.py b/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_set_target_temperature.py index b220c15ebef..977a76bfdf2 100644 --- a/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_set_target_temperature.py +++ b/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_set_target_temperature.py @@ -3,7 +3,7 @@ from opentrons.hardware_control.modules import HeaterShaker -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( HeaterShakerModuleSubState, HeaterShakerModuleId, @@ -55,5 +55,5 @@ async def test_set_target_temperature( result = await subject.execute(data) decoy.verify(await hs_hardware.start_set_temperature(celsius=45.6), times=1) assert result == SuccessData( - public=heater_shaker.SetTargetTemperatureResult(), private=None + public=heater_shaker.SetTargetTemperatureResult(), ) diff --git a/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_wait_for_temperature.py b/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_wait_for_temperature.py index a575e8d4795..f9804b90944 100644 --- a/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_wait_for_temperature.py +++ b/api/tests/opentrons/protocol_engine/commands/heater_shaker/test_wait_for_temperature.py @@ -3,7 +3,7 @@ from opentrons.hardware_control.modules import HeaterShaker -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( HeaterShakerModuleSubState, HeaterShakerModuleId, @@ -50,5 +50,5 @@ async def test_wait_for_temperature( await hs_hardware.await_temperature(awaiting_temperature=123.45), times=1 ) assert result == SuccessData( - public=heater_shaker.WaitForTemperatureResult(), private=None + public=heater_shaker.WaitForTemperatureResult(), ) diff --git a/api/tests/opentrons/protocol_engine/commands/magnetic_module/test_disengage.py b/api/tests/opentrons/protocol_engine/commands/magnetic_module/test_disengage.py index b87cd5d3f3b..03d76db9e03 100644 --- a/api/tests/opentrons/protocol_engine/commands/magnetic_module/test_disengage.py +++ b/api/tests/opentrons/protocol_engine/commands/magnetic_module/test_disengage.py @@ -4,7 +4,7 @@ from opentrons.hardware_control.modules import MagDeck from opentrons.protocol_engine.execution import EquipmentHandler -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( MagneticModuleSubState, MagneticModuleId, @@ -46,4 +46,4 @@ async def test_magnetic_module_disengage_implementation( result = await subject.execute(params=params) decoy.verify(await magnetic_module_hw.deactivate(), times=1) - assert result == SuccessData(public=DisengageResult(), private=None) + assert result == SuccessData(public=DisengageResult()) diff --git a/api/tests/opentrons/protocol_engine/commands/magnetic_module/test_engage.py b/api/tests/opentrons/protocol_engine/commands/magnetic_module/test_engage.py index 6563371345e..e1f14cb3f24 100644 --- a/api/tests/opentrons/protocol_engine/commands/magnetic_module/test_engage.py +++ b/api/tests/opentrons/protocol_engine/commands/magnetic_module/test_engage.py @@ -3,7 +3,7 @@ from decoy import Decoy from opentrons.hardware_control.modules import MagDeck -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( MagneticModuleId, MagneticModuleSubState, @@ -51,4 +51,4 @@ async def test_magnetic_module_engage_implementation( result = await subject.execute(params=params) decoy.verify(await magnetic_module_hw.engage(9001), times=1) - assert result == SuccessData(public=EngageResult(), private=None) + assert result == SuccessData(public=EngageResult()) diff --git a/api/tests/opentrons/protocol_engine/commands/robot/__init__.py b/api/tests/opentrons/protocol_engine/commands/robot/__init__.py new file mode 100644 index 00000000000..36375876456 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/robot/__init__.py @@ -0,0 +1 @@ +"""Tests for Robot Module commands.""" diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_close_gripper_jaw.py b/api/tests/opentrons/protocol_engine/commands/robot/test_close_gripper_jaw.py new file mode 100644 index 00000000000..c5ccd4bf48d --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_close_gripper_jaw.py @@ -0,0 +1,28 @@ +"""Test robot.open-gripper-jaw commands.""" +from decoy import Decoy + +from opentrons.hardware_control import OT3HardwareControlAPI + +from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.robot.close_gripper_jaw import ( + closeGripperJawParams, + closeGripperJawResult, + closeGripperJawImplementation, +) + + +async def test_close_gripper_jaw_implementation( + decoy: Decoy, + ot3_hardware_api: OT3HardwareControlAPI, +) -> None: + """Test the `robot.closeGripperJaw` implementation.""" + subject = closeGripperJawImplementation( + hardware_api=ot3_hardware_api, + ) + + params = closeGripperJawParams(force=10) + + result = await subject.execute(params=params) + + assert result == SuccessData(public=closeGripperJawResult()) + decoy.verify(await ot3_hardware_api.grip(force_newtons=10)) diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py new file mode 100644 index 00000000000..11c6e13b54f --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_relative_to.py @@ -0,0 +1,52 @@ +"""Test robot.move-axes-relative commands.""" +from decoy import Decoy + +from opentrons.hardware_control import HardwareControlAPI + +from opentrons.protocol_engine.execution import GantryMover +from opentrons.protocol_engine.types import MotorAxis +from opentrons.hardware_control.protocols.types import FlexRobotType + +from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.robot.move_axes_relative import ( + MoveAxesRelativeParams, + MoveAxesRelativeResult, + MoveAxesRelativeImplementation, +) + + +async def test_move_axes_to_implementation( + decoy: Decoy, + gantry_mover: GantryMover, + ot3_hardware_api: HardwareControlAPI, +) -> None: + """Test the `robot.moveAxesRelative` implementation. + + It should call `MovementHandler.move_mount_to` with the + correct coordinates. + """ + subject = MoveAxesRelativeImplementation( + gantry_mover=gantry_mover, + hardware_api=ot3_hardware_api, + ) + + params = MoveAxesRelativeParams( + axis_map={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20}, + speed=567.8, + ) + + # Flex shape + decoy.when(ot3_hardware_api.get_robot_type()).then_return(FlexRobotType) + decoy.when( + await gantry_mover.move_axes( + axis_map=params.axis_map, speed=params.speed, relative_move=True + ) + ).then_return({MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20}) + + result = await subject.execute(params=params) + + assert result == SuccessData( + public=MoveAxesRelativeResult( + position={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20} + ) + ) diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py new file mode 100644 index 00000000000..3caa8b03ec8 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_axes_to.py @@ -0,0 +1,54 @@ +"""Test robot.move-axes-to commands.""" +from decoy import Decoy + +from opentrons.hardware_control import HardwareControlAPI + +from opentrons.protocol_engine.execution import GantryMover +from opentrons.protocol_engine.types import MotorAxis +from opentrons.hardware_control.protocols.types import FlexRobotType + +from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.robot.move_axes_to import ( + MoveAxesToParams, + MoveAxesToResult, + MoveAxesToImplementation, +) + + +async def test_move_axes_to_implementation( + decoy: Decoy, + gantry_mover: GantryMover, + ot3_hardware_api: HardwareControlAPI, +) -> None: + """Test the `robot.moveAxesTo` implementation. + + It should call `MovementHandler.move_mount_to` with the + correct coordinates. + """ + subject = MoveAxesToImplementation( + gantry_mover=gantry_mover, + hardware_api=ot3_hardware_api, + ) + + params = MoveAxesToParams( + axis_map={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20}, + critical_point={MotorAxis.X: 1, MotorAxis.Y: 1, MotorAxis.EXTENSION_Z: 0}, + speed=567.8, + ) + + # Flex shape + decoy.when(ot3_hardware_api.get_robot_type()).then_return(FlexRobotType) + decoy.when( + await gantry_mover.move_axes( + axis_map=params.axis_map, + speed=params.speed, + critical_point=params.critical_point, + ) + ).then_return({MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20}) + result = await subject.execute(params=params) + + assert result == SuccessData( + public=MoveAxesToResult( + position={MotorAxis.X: 10, MotorAxis.Y: 10, MotorAxis.EXTENSION_Z: 20} + ) + ) diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_move_to.py b/api/tests/opentrons/protocol_engine/commands/robot/test_move_to.py new file mode 100644 index 00000000000..28bd5b6df33 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_move_to.py @@ -0,0 +1,47 @@ +"""Test robot.move-to commands.""" +from decoy import Decoy + +from opentrons.protocol_engine.execution import MovementHandler +from opentrons.protocol_engine.types import DeckPoint +from opentrons.types import Point, MountType + +from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.robot.move_to import ( + MoveToParams, + MoveToResult, + MoveToImplementation, +) + + +async def test_move_to_implementation( + decoy: Decoy, + movement: MovementHandler, +) -> None: + """Test the `robot.moveTo` implementation. + + It should call `MovementHandler.move_mount_to` with the + correct coordinates. + """ + subject = MoveToImplementation( + movement=movement, + ) + + params = MoveToParams( + mount=MountType.LEFT, + destination=DeckPoint(x=1.11, y=2.22, z=3.33), + speed=567.8, + ) + + decoy.when( + await movement.move_mount_to( + mount=MountType.LEFT, + destination=DeckPoint(x=1.11, y=2.22, z=3.33), + speed=567.8, + ) + ).then_return(Point(x=4.44, y=5.55, z=6.66)) + + result = await subject.execute(params=params) + + assert result == SuccessData( + public=MoveToResult(position=DeckPoint(x=4.44, y=5.55, z=6.66)) + ) diff --git a/api/tests/opentrons/protocol_engine/commands/robot/test_open_gripper_jaw.py b/api/tests/opentrons/protocol_engine/commands/robot/test_open_gripper_jaw.py new file mode 100644 index 00000000000..6ded7932963 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/robot/test_open_gripper_jaw.py @@ -0,0 +1,28 @@ +"""Test robot.open-gripper-jaw commands.""" +from decoy import Decoy + +from opentrons.hardware_control import OT3HardwareControlAPI + +from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.robot.open_gripper_jaw import ( + openGripperJawParams, + openGripperJawResult, + openGripperJawImplementation, +) + + +async def test_open_gripper_jaw_implementation( + decoy: Decoy, + ot3_hardware_api: OT3HardwareControlAPI, +) -> None: + """Test the `robot.openGripperJaw` implementation.""" + subject = openGripperJawImplementation( + hardware_api=ot3_hardware_api, + ) + + params = openGripperJawParams() + + result = await subject.execute(params=params) + + assert result == SuccessData(public=openGripperJawResult()) + decoy.verify(await ot3_hardware_api.home_gripper_jaw()) diff --git a/api/tests/opentrons/protocol_engine/commands/temperature_module/test_deactivate.py b/api/tests/opentrons/protocol_engine/commands/temperature_module/test_deactivate.py index 7e73ec94dc6..91dc274f14c 100644 --- a/api/tests/opentrons/protocol_engine/commands/temperature_module/test_deactivate.py +++ b/api/tests/opentrons/protocol_engine/commands/temperature_module/test_deactivate.py @@ -3,7 +3,7 @@ from opentrons.hardware_control.modules import TempDeck -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( TemperatureModuleSubState, TemperatureModuleId, @@ -45,5 +45,5 @@ async def test_await_temperature( result = await subject.execute(data) decoy.verify(await tempdeck_hardware.deactivate(), times=1) assert result == SuccessData( - public=temperature_module.DeactivateTemperatureResult(), private=None + public=temperature_module.DeactivateTemperatureResult(), ) diff --git a/api/tests/opentrons/protocol_engine/commands/temperature_module/test_set_target_temperature.py b/api/tests/opentrons/protocol_engine/commands/temperature_module/test_set_target_temperature.py index cd57f86a4c6..0bbd31f7a1d 100644 --- a/api/tests/opentrons/protocol_engine/commands/temperature_module/test_set_target_temperature.py +++ b/api/tests/opentrons/protocol_engine/commands/temperature_module/test_set_target_temperature.py @@ -3,7 +3,7 @@ from opentrons.hardware_control.modules import TempDeck -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( TemperatureModuleSubState, TemperatureModuleId, @@ -52,5 +52,4 @@ async def test_set_target_temperature( decoy.verify(await tempdeck_hardware.start_set_temperature(celsius=1), times=1) assert result == SuccessData( public=temperature_module.SetTargetTemperatureResult(targetTemperature=1), - private=None, ) diff --git a/api/tests/opentrons/protocol_engine/commands/temperature_module/test_wait_for_temperature.py b/api/tests/opentrons/protocol_engine/commands/temperature_module/test_wait_for_temperature.py index df18e8a144c..99e76f68774 100644 --- a/api/tests/opentrons/protocol_engine/commands/temperature_module/test_wait_for_temperature.py +++ b/api/tests/opentrons/protocol_engine/commands/temperature_module/test_wait_for_temperature.py @@ -3,7 +3,7 @@ from opentrons.hardware_control.modules import TempDeck -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( TemperatureModuleSubState, TemperatureModuleId, @@ -48,7 +48,7 @@ async def test_wait_for_temperature( await tempdeck_hardware.await_temperature(awaiting_temperature=123), times=1 ) assert result == SuccessData( - public=temperature_module.WaitForTemperatureResult(), private=None + public=temperature_module.WaitForTemperatureResult(), ) @@ -90,5 +90,5 @@ async def test_wait_for_temperature_requested_celsius( await tempdeck_hardware.await_temperature(awaiting_temperature=12), times=1 ) assert result == SuccessData( - public=temperature_module.WaitForTemperatureResult(), private=None + public=temperature_module.WaitForTemperatureResult(), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_air_gap_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_air_gap_in_place.py new file mode 100644 index 00000000000..5d66a845dcc --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/test_air_gap_in_place.py @@ -0,0 +1,284 @@ +"""Test aspirate-in-place commands.""" +from datetime import datetime + +import pytest +from decoy import Decoy, matchers + +from opentrons_shared_data.errors.exceptions import PipetteOverpressureError + +from opentrons.types import Point +from opentrons.hardware_control import API as HardwareAPI + +from opentrons.protocol_engine.execution import PipettingHandler, GantryMover +from opentrons.protocol_engine.commands.air_gap_in_place import ( + AirGapInPlaceParams, + AirGapInPlaceResult, + AirGapInPlaceImplementation, +) +from opentrons.protocol_engine.commands.command import SuccessData, DefinedErrorData +from opentrons.protocol_engine.errors.exceptions import PipetteNotReadyToAspirateError +from opentrons.protocol_engine.notes import CommandNoteAdder +from opentrons.protocol_engine.resources import ModelUtils +from opentrons.protocol_engine.state.state import StateStore +from opentrons.protocol_engine.commands.pipetting_common import OverpressureError +from opentrons.protocol_engine.types import ( + CurrentWell, + CurrentPipetteLocation, + CurrentAddressableArea, + AspiratedFluid, + FluidKind, +) +from opentrons.protocol_engine.state import update_types + + +@pytest.fixture +def hardware_api(decoy: Decoy) -> HardwareAPI: + """Get a mock in the shape of a HardwareAPI.""" + return decoy.mock(cls=HardwareAPI) + + +@pytest.fixture +def state_store(decoy: Decoy) -> StateStore: + """Get a mock in the shape of a StateStore.""" + return decoy.mock(cls=StateStore) + + +@pytest.fixture +def pipetting(decoy: Decoy) -> PipettingHandler: + """Get a mock in the shape of a PipettingHandler.""" + return decoy.mock(cls=PipettingHandler) + + +@pytest.fixture +def subject( + pipetting: PipettingHandler, + state_store: StateStore, + hardware_api: HardwareAPI, + mock_command_note_adder: CommandNoteAdder, + model_utils: ModelUtils, + gantry_mover: GantryMover, +) -> AirGapInPlaceImplementation: + """Get the impelementation subject.""" + return AirGapInPlaceImplementation( + pipetting=pipetting, + hardware_api=hardware_api, + state_view=state_store, + command_note_adder=mock_command_note_adder, + model_utils=model_utils, + gantry_mover=gantry_mover, + ) + + +@pytest.mark.parametrize( + "location,stateupdateLabware,stateupdateWell", + [ + ( + CurrentWell( + pipette_id="pipette-id-abc", + labware_id="labware-id-1", + well_name="well-name-1", + ), + "labware-id-1", + "well-name-1", + ), + (None, None, None), + (CurrentAddressableArea("pipette-id-abc", "addressable-area-1"), None, None), + ], +) +async def test_air_gap_in_place_implementation( + decoy: Decoy, + pipetting: PipettingHandler, + state_store: StateStore, + hardware_api: HardwareAPI, + mock_command_note_adder: CommandNoteAdder, + subject: AirGapInPlaceImplementation, + location: CurrentPipetteLocation | None, + stateupdateLabware: str, + stateupdateWell: str, +) -> None: + """It should aspirate in place.""" + data = AirGapInPlaceParams( + pipetteId="pipette-id-abc", + volume=123, + flowRate=1.234, + ) + + decoy.when( + pipetting.get_is_ready_to_aspirate( + pipette_id="pipette-id-abc", + ) + ).then_return(True) + + decoy.when( + await pipetting.aspirate_in_place( + pipette_id="pipette-id-abc", + volume=123, + flow_rate=1.234, + command_note_adder=mock_command_note_adder, + ) + ).then_return(123) + + decoy.when(state_store.pipettes.get_current_location()).then_return(location) + + result = await subject.execute(params=data) + + if isinstance(location, CurrentWell): + assert result == SuccessData( + public=AirGapInPlaceResult(volume=123), + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteAspiratedFluidUpdate( + pipette_id="pipette-id-abc", + fluid=AspiratedFluid(kind=FluidKind.AIR, volume=123), + ) + ), + ) + else: + assert result == SuccessData( + public=AirGapInPlaceResult(volume=123), + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteAspiratedFluidUpdate( + pipette_id="pipette-id-abc", + fluid=AspiratedFluid(kind=FluidKind.AIR, volume=123), + ) + ), + ) + + +async def test_handle_air_gap_in_place_request_not_ready_to_aspirate( + decoy: Decoy, + pipetting: PipettingHandler, + state_store: StateStore, + hardware_api: HardwareAPI, + subject: AirGapInPlaceImplementation, +) -> None: + """Should raise an exception for not ready to aspirate.""" + data = AirGapInPlaceParams( + pipetteId="pipette-id-abc", + volume=123, + flowRate=1.234, + ) + + decoy.when( + pipetting.get_is_ready_to_aspirate( + pipette_id="pipette-id-abc", + ) + ).then_return(False) + + with pytest.raises( + PipetteNotReadyToAspirateError, + match="Pipette cannot air gap in place because of a previous blow out." + " The first aspirate following a blow-out must be from a specific well" + " so the plunger can be reset in a known safe position.", + ): + await subject.execute(params=data) + + +async def test_aspirate_raises_volume_error( + decoy: Decoy, + pipetting: PipettingHandler, + subject: AirGapInPlaceImplementation, + mock_command_note_adder: CommandNoteAdder, +) -> None: + """Should raise an assertion error for volume larger than working volume.""" + data = AirGapInPlaceParams( + pipetteId="abc", + volume=50, + flowRate=1.23, + ) + + decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id="abc")).then_return(True) + + decoy.when( + await pipetting.aspirate_in_place( + pipette_id="abc", + volume=50, + flow_rate=1.23, + command_note_adder=mock_command_note_adder, + ) + ).then_raise(AssertionError("blah blah")) + + with pytest.raises(AssertionError): + await subject.execute(data) + + +@pytest.mark.parametrize( + "location,stateupdateLabware,stateupdateWell", + [ + ( + CurrentWell( + pipette_id="pipette-id", + labware_id="labware-id-1", + well_name="well-name-1", + ), + "labware-id-1", + "well-name-1", + ), + (None, None, None), + (CurrentAddressableArea("pipette-id", "addressable-area-1"), None, None), + ], +) +async def test_overpressure_error( + decoy: Decoy, + gantry_mover: GantryMover, + pipetting: PipettingHandler, + subject: AirGapInPlaceImplementation, + model_utils: ModelUtils, + mock_command_note_adder: CommandNoteAdder, + state_store: StateStore, + location: CurrentPipetteLocation | None, + stateupdateLabware: str, + stateupdateWell: str, +) -> None: + """It should return an overpressure error if the hardware API indicates that.""" + pipette_id = "pipette-id" + + position = Point(x=1, y=2, z=3) + + error_id = "error-id" + error_timestamp = datetime(year=2020, month=1, day=2) + + data = AirGapInPlaceParams( + pipetteId=pipette_id, + volume=50, + flowRate=1.23, + ) + + decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id)).then_return( + True + ) + + decoy.when( + await pipetting.aspirate_in_place( + pipette_id=pipette_id, + volume=50, + flow_rate=1.23, + command_note_adder=mock_command_note_adder, + ), + ).then_raise(PipetteOverpressureError()) + + decoy.when(model_utils.generate_id()).then_return(error_id) + decoy.when(model_utils.get_timestamp()).then_return(error_timestamp) + decoy.when(await gantry_mover.get_position(pipette_id)).then_return(position) + decoy.when(state_store.pipettes.get_current_location()).then_return(location) + + result = await subject.execute(data) + + if isinstance(location, CurrentWell): + assert result == DefinedErrorData( + public=OverpressureError.construct( + id=error_id, + createdAt=error_timestamp, + wrappedErrors=[matchers.Anything()], + errorInfo={"retryLocation": (position.x, position.y, position.z)}, + ), + state_update=update_types.StateUpdate(), + ) + else: + assert result == DefinedErrorData( + public=OverpressureError.construct( + id=error_id, + createdAt=error_timestamp, + wrappedErrors=[matchers.Anything()], + errorInfo={"retryLocation": (position.x, position.y, position.z)}, + ), + ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_aspirate.py b/api/tests/opentrons/protocol_engine/commands/test_aspirate.py index b1e3c1e52df..8e50d1825ae 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_aspirate.py +++ b/api/tests/opentrons/protocol_engine/commands/test_aspirate.py @@ -1,16 +1,24 @@ """Test aspirate commands.""" + from datetime import datetime -from opentrons_shared_data.errors.exceptions import PipetteOverpressureError +from opentrons_shared_data.errors.exceptions import ( + PipetteOverpressureError, + StallOrCollisionDetectedError, +) from decoy import matchers, Decoy import pytest -from opentrons.protocol_engine.commands.pipetting_common import ( - OverpressureError, - OverpressureErrorInternalData, +from opentrons.protocol_engine.commands.pipetting_common import OverpressureError +from opentrons.protocol_engine.commands.movement_common import StallOrCollisionError +from opentrons.protocol_engine.state import update_types +from opentrons.types import Point +from opentrons.protocol_engine import ( + LiquidHandlingWellLocation, + WellOrigin, + WellOffset, + DeckPoint, ) -from opentrons.types import MountType, Point -from opentrons.protocol_engine import WellLocation, WellOrigin, WellOffset, DeckPoint from opentrons.protocol_engine.commands.aspirate import ( AspirateParams, @@ -19,14 +27,19 @@ ) from opentrons.protocol_engine.commands.command import DefinedErrorData, SuccessData -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import ( MovementHandler, PipettingHandler, ) from opentrons.protocol_engine.resources.model_utils import ModelUtils -from opentrons.protocol_engine.types import CurrentWell, LoadedPipette +from opentrons.protocol_engine.types import ( + CurrentWell, + AspiratedFluid, + FluidKind, + WellLocation, +) from opentrons.hardware_control import HardwareControlAPI from opentrons.protocol_engine.notes import CommandNoteAdder @@ -54,145 +67,262 @@ def subject( async def test_aspirate_implementation_no_prep( decoy: Decoy, state_view: StateView, - hardware_api: HardwareControlAPI, movement: MovementHandler, pipetting: PipettingHandler, subject: AspirateImplementation, mock_command_note_adder: CommandNoteAdder, ) -> None: """An Aspirate should have an execution implementation without preparing to aspirate.""" - location = WellLocation(origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1)) - - data = AspirateParams( - pipetteId="abc", - labwareId="123", - wellName="A3", + pipette_id = "pipette-id" + labware_id = "labware-id" + well_name = "well-name" + location = LiquidHandlingWellLocation( + origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1) + ) + params = AspirateParams( + pipetteId=pipette_id, + labwareId=labware_id, + wellName=well_name, wellLocation=location, volume=50, flowRate=1.23, ) - decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id="abc")).then_return(True) + decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id)).then_return( + True + ) + + decoy.when( + state_view.geometry.get_nozzles_per_well( + labware_id=labware_id, + target_well_name=well_name, + pipette_id=pipette_id, + ) + ).then_return(2) + + decoy.when( + state_view.geometry.get_wells_covered_by_pipette_with_active_well( + labware_id, well_name, pipette_id + ) + ).then_return(["covered-well-1", "covered-well-2"]) decoy.when( await movement.move_to_well( - pipette_id="abc", - labware_id="123", - well_name="A3", + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, well_location=location, current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=-50, ), ).then_return(Point(x=1, y=2, z=3)) decoy.when( await pipetting.aspirate_in_place( - pipette_id="abc", + pipette_id=pipette_id, volume=50, flow_rate=1.23, command_note_adder=mock_command_note_adder, ), ).then_return(50) - result = await subject.execute(data) + result = await subject.execute(params) assert result == SuccessData( public=AspirateResult(volume=50, position=DeckPoint(x=1, y=2, z=3)), - private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id=pipette_id, + new_location=update_types.Well( + labware_id=labware_id, well_name=well_name + ), + new_deck_point=DeckPoint(x=1, y=2, z=3), + ), + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id=labware_id, + well_names=["covered-well-1", "covered-well-2"], + volume_added=-100, + ), + pipette_aspirated_fluid=update_types.PipetteAspiratedFluidUpdate( + pipette_id=pipette_id, + fluid=AspiratedFluid(kind=FluidKind.LIQUID, volume=50), + ), + ), ) async def test_aspirate_implementation_with_prep( decoy: Decoy, state_view: StateView, - hardware_api: HardwareControlAPI, movement: MovementHandler, pipetting: PipettingHandler, mock_command_note_adder: CommandNoteAdder, subject: AspirateImplementation, ) -> None: """An Aspirate should have an execution implementation with preparing to aspirate.""" - location = WellLocation(origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1)) - - data = AspirateParams( - pipetteId="abc", - labwareId="123", - wellName="A3", + pipette_id = "pipette-id" + labware_id = "labware-id" + well_name = "well-name" + location = LiquidHandlingWellLocation( + origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1) + ) + volume = 50 + flow_rate = 1.23 + params = AspirateParams( + pipetteId=pipette_id, + labwareId=labware_id, + wellName=well_name, wellLocation=location, - volume=50, - flowRate=1.23, + volume=volume, + flowRate=flow_rate, ) - decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id="abc")).then_return(False) + decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id)).then_return( + False + ) - decoy.when(state_view.pipettes.get(pipette_id="abc")).then_return( - LoadedPipette.construct( # type:ignore[call-arg] - mount=MountType.LEFT + decoy.when( + state_view.geometry.get_nozzles_per_well( + labware_id=labware_id, + target_well_name=well_name, + pipette_id=pipette_id, ) - ) + ).then_return(2) + + decoy.when( + state_view.geometry.get_wells_covered_by_pipette_with_active_well( + labware_id, well_name, pipette_id + ) + ).then_return(["covered-well-1", "covered-well-2"]) + + decoy.when( + await movement.move_to_well( + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=WellLocation(origin=WellOrigin.TOP), + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, + ), + ).then_return(Point()) + decoy.when( await movement.move_to_well( - pipette_id="abc", - labware_id="123", - well_name="A3", + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, well_location=location, current_well=CurrentWell( - pipette_id="abc", - labware_id="123", - well_name="A3", + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, ), + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=-volume, ), ).then_return(Point(x=1, y=2, z=3)) decoy.when( await pipetting.aspirate_in_place( - pipette_id="abc", - volume=50, - flow_rate=1.23, + pipette_id=pipette_id, + volume=volume, + flow_rate=flow_rate, command_note_adder=mock_command_note_adder, ), - ).then_return(50) + ).then_return(volume) - result = await subject.execute(data) + result = await subject.execute(params) assert result == SuccessData( public=AspirateResult(volume=50, position=DeckPoint(x=1, y=2, z=3)), - private=None, - ) - - decoy.verify( - await movement.move_to_well( - pipette_id="abc", - labware_id="123", - well_name="A3", - well_location=WellLocation(origin=WellOrigin.TOP), + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id=pipette_id, + new_location=update_types.Well( + labware_id=labware_id, well_name=well_name + ), + new_deck_point=DeckPoint(x=1, y=2, z=3), + ), + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id=labware_id, + well_names=["covered-well-1", "covered-well-2"], + volume_added=-100, + ), + pipette_aspirated_fluid=update_types.PipetteAspiratedFluidUpdate( + pipette_id=pipette_id, + fluid=AspiratedFluid(kind=FluidKind.LIQUID, volume=50), + ), ), - await pipetting.prepare_for_aspirate(pipette_id="abc"), ) async def test_aspirate_raises_volume_error( decoy: Decoy, pipetting: PipettingHandler, + movement: MovementHandler, mock_command_note_adder: CommandNoteAdder, + state_view: StateView, subject: AspirateImplementation, ) -> None: """Should raise an assertion error for volume larger than working volume.""" - location = WellLocation(origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1)) - - data = AspirateParams( - pipetteId="abc", - labwareId="123", - wellName="A3", + pipette_id = "pipette-id" + labware_id = "labware-id" + well_name = "well-name" + location = LiquidHandlingWellLocation( + origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1) + ) + params = AspirateParams( + pipetteId=pipette_id, + labwareId=labware_id, + wellName=well_name, wellLocation=location, volume=50, flowRate=1.23, ) - decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id="abc")).then_return(True) + decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id)).then_return( + True + ) + + decoy.when( + state_view.geometry.get_nozzles_per_well( + labware_id=labware_id, + target_well_name=well_name, + pipette_id=pipette_id, + ) + ).then_return(2) + + decoy.when( + state_view.geometry.get_wells_covered_by_pipette_with_active_well( + labware_id, well_name, pipette_id + ) + ).then_return(["covered-well-1", "covered-well-2"]) + + decoy.when( + await movement.move_to_well( + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=location, + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=-50, + ), + ).then_return(Point(1, 2, 3)) decoy.when( await pipetting.aspirate_in_place( - pipette_id="abc", + pipette_id=pipette_id, volume=50, flow_rate=1.23, command_note_adder=mock_command_note_adder, @@ -200,7 +330,7 @@ async def test_aspirate_raises_volume_error( ).then_raise(AssertionError("blah blah")) with pytest.raises(AssertionError): - await subject.execute(data) + await subject.execute(params) async def test_overpressure_error( @@ -210,12 +340,13 @@ async def test_overpressure_error( subject: AspirateImplementation, model_utils: ModelUtils, mock_command_note_adder: CommandNoteAdder, + state_view: StateView, ) -> None: """It should return an overpressure error if the hardware API indicates that.""" pipette_id = "pipette-id" labware_id = "labware-id" well_name = "well-name" - well_location = WellLocation( + well_location = LiquidHandlingWellLocation( origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1) ) @@ -224,7 +355,7 @@ async def test_overpressure_error( error_id = "error-id" error_timestamp = datetime(year=2020, month=1, day=2) - data = AspirateParams( + params = AspirateParams( pipetteId=pipette_id, labwareId=labware_id, wellName=well_name, @@ -233,6 +364,20 @@ async def test_overpressure_error( flowRate=1.23, ) + decoy.when( + state_view.geometry.get_nozzles_per_well( + labware_id=labware_id, + target_well_name=well_name, + pipette_id=pipette_id, + ) + ).then_return(2) + + decoy.when( + state_view.geometry.get_wells_covered_by_pipette_with_active_well( + labware_id, well_name, pipette_id + ) + ).then_return(["covered-well-1", "covered-well-2"]) + decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id)).then_return( True ) @@ -244,6 +389,10 @@ async def test_overpressure_error( well_name=well_name, well_location=well_location, current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=-50, ), ).then_return(position) @@ -259,7 +408,7 @@ async def test_overpressure_error( decoy.when(model_utils.generate_id()).then_return(error_id) decoy.when(model_utils.get_timestamp()).then_return(error_timestamp) - result = await subject.execute(data) + result = await subject.execute(params) assert result == DefinedErrorData( public=OverpressureError.construct( @@ -268,7 +417,325 @@ async def test_overpressure_error( wrappedErrors=[matchers.Anything()], errorInfo={"retryLocation": (position.x, position.y, position.z)}, ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=position.x, y=position.y, z=position.z) + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id=pipette_id, + new_location=update_types.Well( + labware_id=labware_id, well_name=well_name + ), + new_deck_point=DeckPoint(x=position.x, y=position.y, z=position.z), + ), + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id=labware_id, + well_names=["covered-well-1", "covered-well-2"], + volume_added=update_types.CLEAR, + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id=pipette_id + ), + ), + ) + + +async def test_aspirate_implementation_meniscus( + decoy: Decoy, + state_view: StateView, + hardware_api: HardwareControlAPI, + movement: MovementHandler, + pipetting: PipettingHandler, + subject: AspirateImplementation, + mock_command_note_adder: CommandNoteAdder, +) -> None: + """Aspirate should update WellVolumeOffset when called with WellOrigin.MENISCUS.""" + pipette_id = "pipette-id" + labware_id = "labware-id" + well_name = "well-name" + location = LiquidHandlingWellLocation( + origin=WellOrigin.MENISCUS, + offset=WellOffset(x=0, y=0, z=-1), + volumeOffset="operationVolume", + ) + params = AspirateParams( + pipetteId=pipette_id, + labwareId=labware_id, + wellName=well_name, + wellLocation=location, + volume=50, + flowRate=1.23, + ) + + decoy.when( + state_view.geometry.get_nozzles_per_well( + labware_id=labware_id, + target_well_name=well_name, + pipette_id=pipette_id, + ) + ).then_return(2) + + decoy.when( + state_view.geometry.get_wells_covered_by_pipette_with_active_well( + labware_id, well_name, pipette_id + ) + ).then_return(["covered-well-1", "covered-well-2"]) + + decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id)).then_return( + True + ) + + decoy.when( + await movement.move_to_well( + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=location, + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=-50, + ), + ).then_return(Point(x=1, y=2, z=3)) + + decoy.when( + await pipetting.aspirate_in_place( + pipette_id=pipette_id, + volume=50, + flow_rate=1.23, + command_note_adder=mock_command_note_adder, + ), + ).then_return(50) + + result = await subject.execute(params) + + assert result == SuccessData( + public=AspirateResult(volume=50, position=DeckPoint(x=1, y=2, z=3)), + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id=pipette_id, + new_location=update_types.Well( + labware_id=labware_id, well_name=well_name + ), + new_deck_point=DeckPoint(x=1, y=2, z=3), + ), + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id=labware_id, + well_names=["covered-well-1", "covered-well-2"], + volume_added=-100, + ), + pipette_aspirated_fluid=update_types.PipetteAspiratedFluidUpdate( + pipette_id=pipette_id, + fluid=AspiratedFluid(kind=FluidKind.LIQUID, volume=50), + ), + ), + ) + + +async def test_stall_during_final_movement( + decoy: Decoy, + movement: MovementHandler, + pipetting: PipettingHandler, + subject: AspirateImplementation, + model_utils: ModelUtils, + state_view: StateView, +) -> None: + """It should propagate a stall error that happens when moving to the final position.""" + pipette_id = "pipette-id" + labware_id = "labware-id" + well_name = "well-name" + well_location = LiquidHandlingWellLocation( + origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1) + ) + + error_id = "error-id" + error_timestamp = datetime(year=2020, month=1, day=2) + decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id)).then_return( + True + ) + + params = AspirateParams( + pipetteId=pipette_id, + labwareId=labware_id, + wellName=well_name, + wellLocation=well_location, + volume=50, + flowRate=1.23, + ) + + decoy.when( + await movement.move_to_well( + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=well_location, + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=-50, + ), + ).then_raise(StallOrCollisionDetectedError()) + + decoy.when(model_utils.generate_id()).then_return(error_id) + decoy.when(model_utils.get_timestamp()).then_return(error_timestamp) + + result = await subject.execute(params) + + assert result == DefinedErrorData( + public=StallOrCollisionError.construct( + id=error_id, + createdAt=error_timestamp, + wrappedErrors=[matchers.Anything()], + ), + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), + ) + + +async def test_stall_during_preparation( + decoy: Decoy, + movement: MovementHandler, + pipetting: PipettingHandler, + subject: AspirateImplementation, + model_utils: ModelUtils, +) -> None: + """It should propagate a stall error that happens during the prepare-to-aspirate part.""" + pipette_id = "pipette-id" + labware_id = "labware-id" + well_name = "well-name" + well_location = LiquidHandlingWellLocation( + origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1) + ) + + error_id = "error-id" + error_timestamp = datetime(year=2020, month=1, day=2) + + params = AspirateParams( + pipetteId=pipette_id, + labwareId=labware_id, + wellName=well_name, + wellLocation=well_location, + volume=50, + flowRate=1.23, + ) + + decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id)).then_return( + False + ) + + decoy.when( + await movement.move_to_well( + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=WellLocation(origin=WellOrigin.TOP), + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, + ), + ).then_raise(StallOrCollisionDetectedError()) + decoy.when(model_utils.generate_id()).then_return(error_id) + decoy.when(model_utils.get_timestamp()).then_return(error_timestamp) + + result = await subject.execute(params) + assert result == DefinedErrorData( + public=StallOrCollisionError.construct( + id=error_id, createdAt=error_timestamp, wrappedErrors=[matchers.Anything()] + ), + state_update=update_types.StateUpdate( + pipette_location=update_types.CLEAR, + ), + state_update_if_false_positive=update_types.StateUpdate(), + ) + + +async def test_overpressure_during_preparation( + decoy: Decoy, + movement: MovementHandler, + pipetting: PipettingHandler, + subject: AspirateImplementation, + state_view: StateView, + model_utils: ModelUtils, +) -> None: + """It should propagate an overpressure error that happens during the prepare-to-aspirate part.""" + pipette_id = "pipette-id" + labware_id = "labware-id" + well_name = "well-name" + well_location = LiquidHandlingWellLocation( + origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1) + ) + + error_id = "error-id" + error_timestamp = datetime(year=2020, month=1, day=2) + + params = AspirateParams( + pipetteId=pipette_id, + labwareId=labware_id, + wellName=well_name, + wellLocation=well_location, + volume=50, + flowRate=1.23, + ) + + decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id)).then_return( + False + ) + + retry_location = Point(1, 2, 3) + decoy.when( + state_view.geometry.get_well_position( + labware_id=labware_id, + well_name=well_name, + well_location=well_location, + operation_volume=-params.volume, + pipette_id=pipette_id, + ) + ).then_return(retry_location) + + prep_location = Point(4, 5, 6) + decoy.when( + await movement.move_to_well( + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=WellLocation(origin=WellOrigin.TOP), + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, + ), + ).then_return(prep_location) + + decoy.when(await pipetting.prepare_for_aspirate(pipette_id)).then_raise( + PipetteOverpressureError() + ) + decoy.when(model_utils.generate_id()).then_return(error_id) + decoy.when(model_utils.get_timestamp()).then_return(error_timestamp) + + result = await subject.execute(params) + assert result == DefinedErrorData( + public=OverpressureError.construct( + id=error_id, + createdAt=error_timestamp, + wrappedErrors=[matchers.Anything()], + errorInfo={ + "retryLocation": (retry_location.x, retry_location.y, retry_location.z) + }, + ), + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id=pipette_id, + new_location=update_types.Well( + labware_id=labware_id, well_name=well_name + ), + new_deck_point=DeckPoint( + x=prep_location.x, y=prep_location.y, z=prep_location.z + ), + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id=pipette_id + ), ), + state_update_if_false_positive=update_types.StateUpdate(), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py index 26f62231a56..48dba2e0c3e 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/test_aspirate_in_place.py @@ -1,4 +1,5 @@ """Test aspirate-in-place commands.""" + from datetime import datetime import pytest @@ -19,14 +20,16 @@ from opentrons.protocol_engine.errors.exceptions import PipetteNotReadyToAspirateError from opentrons.protocol_engine.notes import CommandNoteAdder from opentrons.protocol_engine.resources import ModelUtils -from opentrons.protocol_engine.state import ( - StateStore, -) -from opentrons.protocol_engine.types import DeckPoint -from opentrons.protocol_engine.commands.pipetting_common import ( - OverpressureError, - OverpressureErrorInternalData, +from opentrons.protocol_engine.state.state import StateStore +from opentrons.protocol_engine.commands.pipetting_common import OverpressureError +from opentrons.protocol_engine.types import ( + CurrentWell, + CurrentPipetteLocation, + CurrentAddressableArea, + AspiratedFluid, + FluidKind, ) +from opentrons.protocol_engine.state import update_types @pytest.fixture @@ -67,13 +70,33 @@ def subject( ) +@pytest.mark.parametrize( + "location,stateupdateLabware,stateupdateWell", + [ + ( + CurrentWell( + pipette_id="pipette-id-abc", + labware_id="labware-id-1", + well_name="well-name-1", + ), + "labware-id-1", + "well-name-1", + ), + (None, None, None), + (CurrentAddressableArea("pipette-id-abc", "addressable-area-1"), None, None), + ], +) async def test_aspirate_in_place_implementation( decoy: Decoy, + gantry_mover: GantryMover, pipetting: PipettingHandler, state_store: StateStore, hardware_api: HardwareAPI, mock_command_note_adder: CommandNoteAdder, subject: AspirateInPlaceImplementation, + location: CurrentPipetteLocation | None, + stateupdateLabware: str, + stateupdateWell: str, ) -> None: """It should aspirate in place.""" data = AspirateInPlaceParams( @@ -81,7 +104,19 @@ async def test_aspirate_in_place_implementation( volume=123, flowRate=1.234, ) + decoy.when( + state_store.geometry.get_nozzles_per_well( + labware_id=stateupdateLabware, + target_well_name=stateupdateWell, + pipette_id="pipette-id-abc", + ) + ).then_return(2) + decoy.when( + state_store.geometry.get_wells_covered_by_pipette_with_active_well( + stateupdateLabware, stateupdateWell, "pipette-id-abc" + ) + ).then_return(["A3", "A4"]) decoy.when( pipetting.get_is_ready_to_aspirate( pipette_id="pipette-id-abc", @@ -97,13 +132,44 @@ async def test_aspirate_in_place_implementation( ) ).then_return(123) + decoy.when(await gantry_mover.get_position("pipette-id-abc")).then_return( + Point(1, 2, 3) + ) + + decoy.when(state_store.pipettes.get_current_location()).then_return(location) + result = await subject.execute(params=data) - assert result == SuccessData(public=AspirateInPlaceResult(volume=123), private=None) + if isinstance(location, CurrentWell): + assert result == SuccessData( + public=AspirateInPlaceResult(volume=123), + state_update=update_types.StateUpdate( + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id=stateupdateLabware, + well_names=["A3", "A4"], + volume_added=-246, + ), + pipette_aspirated_fluid=update_types.PipetteAspiratedFluidUpdate( + pipette_id="pipette-id-abc", + fluid=AspiratedFluid(kind=FluidKind.LIQUID, volume=123), + ), + ), + ) + else: + assert result == SuccessData( + public=AspirateInPlaceResult(volume=123), + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteAspiratedFluidUpdate( + pipette_id="pipette-id-abc", + fluid=AspiratedFluid(kind=FluidKind.LIQUID, volume=123), + ) + ), + ) async def test_handle_aspirate_in_place_request_not_ready_to_aspirate( decoy: Decoy, + gantry_mover: GantryMover, pipetting: PipettingHandler, state_store: StateStore, hardware_api: HardwareAPI, @@ -115,7 +181,9 @@ async def test_handle_aspirate_in_place_request_not_ready_to_aspirate( volume=123, flowRate=1.234, ) - + decoy.when(await gantry_mover.get_position("pipette-id-abc")).then_return( + Point(1, 2, 3) + ) decoy.when( pipetting.get_is_ready_to_aspirate( pipette_id="pipette-id-abc", @@ -136,6 +204,7 @@ async def test_aspirate_raises_volume_error( pipetting: PipettingHandler, subject: AspirateInPlaceImplementation, mock_command_note_adder: CommandNoteAdder, + gantry_mover: GantryMover, ) -> None: """Should raise an assertion error for volume larger than working volume.""" data = AspirateInPlaceParams( @@ -143,7 +212,7 @@ async def test_aspirate_raises_volume_error( volume=50, flowRate=1.23, ) - + decoy.when(await gantry_mover.get_position("abc")).then_return(Point(x=1, y=2, z=3)) decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id="abc")).then_return(True) decoy.when( @@ -159,6 +228,22 @@ async def test_aspirate_raises_volume_error( await subject.execute(data) +@pytest.mark.parametrize( + "location,stateupdateLabware,stateupdateWell", + [ + ( + CurrentWell( + pipette_id="pipette-id", + labware_id="labware-id-1", + well_name="well-name-1", + ), + "labware-id-1", + "well-name-1", + ), + (None, None, None), + (CurrentAddressableArea("pipette-id", "addressable-area-1"), None, None), + ], +) async def test_overpressure_error( decoy: Decoy, gantry_mover: GantryMover, @@ -166,6 +251,10 @@ async def test_overpressure_error( subject: AspirateInPlaceImplementation, model_utils: ModelUtils, mock_command_note_adder: CommandNoteAdder, + state_store: StateStore, + location: CurrentPipetteLocation | None, + stateupdateLabware: str, + stateupdateWell: str, ) -> None: """It should return an overpressure error if the hardware API indicates that.""" pipette_id = "pipette-id" @@ -174,7 +263,19 @@ async def test_overpressure_error( error_id = "error-id" error_timestamp = datetime(year=2020, month=1, day=2) + decoy.when( + state_store.geometry.get_nozzles_per_well( + labware_id=stateupdateLabware, + target_well_name=stateupdateWell, + pipette_id="pipette-id", + ) + ).then_return(2) + decoy.when( + state_store.geometry.get_wells_covered_by_pipette_with_active_well( + stateupdateLabware, stateupdateWell, "pipette-id" + ) + ).then_return(["A3", "A4"]) data = AspirateInPlaceParams( pipetteId=pipette_id, volume=50, @@ -197,17 +298,40 @@ async def test_overpressure_error( decoy.when(model_utils.generate_id()).then_return(error_id) decoy.when(model_utils.get_timestamp()).then_return(error_timestamp) decoy.when(await gantry_mover.get_position(pipette_id)).then_return(position) + decoy.when(state_store.pipettes.get_current_location()).then_return(location) result = await subject.execute(data) - assert result == DefinedErrorData( - public=OverpressureError.construct( - id=error_id, - createdAt=error_timestamp, - wrappedErrors=[matchers.Anything()], - errorInfo={"retryLocation": (position.x, position.y, position.z)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=position.x, y=position.y, z=position.z) - ), - ) + if isinstance(location, CurrentWell): + assert result == DefinedErrorData( + public=OverpressureError.construct( + id=error_id, + createdAt=error_timestamp, + wrappedErrors=[matchers.Anything()], + errorInfo={"retryLocation": (position.x, position.y, position.z)}, + ), + state_update=update_types.StateUpdate( + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id=stateupdateLabware, + well_names=["A3", "A4"], + volume_added=update_types.CLEAR, + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="pipette-id" + ), + ), + ) + else: + assert result == DefinedErrorData( + public=OverpressureError.construct( + id=error_id, + createdAt=error_timestamp, + wrappedErrors=[matchers.Anything()], + errorInfo={"retryLocation": (position.x, position.y, position.z)}, + ), + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="pipette-id" + ) + ), + ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_blow_out.py b/api/tests/opentrons/protocol_engine/commands/test_blow_out.py index 919d37e9a76..c06b62ace97 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_blow_out.py +++ b/api/tests/opentrons/protocol_engine/commands/test_blow_out.py @@ -1,37 +1,64 @@ """Test blow-out command.""" -from decoy import Decoy +from datetime import datetime + +from decoy import Decoy, matchers +import pytest + +from opentrons.protocol_engine.commands.pipetting_common import OverpressureError +from opentrons.protocol_engine.commands.movement_common import StallOrCollisionError +from opentrons.protocol_engine.resources.model_utils import ModelUtils from opentrons.types import Point -from opentrons.protocol_engine import WellLocation, WellOrigin, WellOffset, DeckPoint -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine import ( + WellLocation, + WellOrigin, + WellOffset, + DeckPoint, +) +from opentrons.protocol_engine.state import update_types +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.commands import ( BlowOutResult, BlowOutImplementation, BlowOutParams, ) -from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.command import DefinedErrorData, SuccessData from opentrons.protocol_engine.execution import ( MovementHandler, PipettingHandler, ) from opentrons.hardware_control import HardwareControlAPI +from opentrons_shared_data.errors.exceptions import ( + PipetteOverpressureError, + StallOrCollisionDetectedError, +) -async def test_blow_out_implementation( - decoy: Decoy, +@pytest.fixture +def subject( state_view: StateView, hardware_api: HardwareControlAPI, movement: MovementHandler, + model_utils: ModelUtils, pipetting: PipettingHandler, -) -> None: - """Test BlowOut command execution.""" - subject = BlowOutImplementation( +) -> BlowOutImplementation: + """Get the impelementation subject.""" + return BlowOutImplementation( state_view=state_view, movement=movement, hardware_api=hardware_api, pipetting=pipetting, + model_utils=model_utils, ) + +async def test_blow_out_implementation( + decoy: Decoy, + movement: MovementHandler, + pipetting: PipettingHandler, + subject: BlowOutImplementation, +) -> None: + """Test BlowOut command execution.""" location = WellLocation(origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1)) data = BlowOutParams( @@ -48,16 +75,170 @@ async def test_blow_out_implementation( labware_id="labware-id", well_name="C6", well_location=location, + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, ) ).then_return(Point(x=1, y=2, z=3)) result = await subject.execute(data) assert result == SuccessData( - public=BlowOutResult(position=DeckPoint(x=1, y=2, z=3)), private=None + public=BlowOutResult(position=DeckPoint(x=1, y=2, z=3)), + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.Well( + labware_id="labware-id", + well_name="C6", + ), + new_deck_point=DeckPoint(x=1, y=2, z=3), + ), + pipette_aspirated_fluid=update_types.PipetteEmptyFluidUpdate( + pipette_id="pipette-id" + ), + ), ) decoy.verify( await pipetting.blow_out_in_place(pipette_id="pipette-id", flow_rate=1.234), times=1, ) + + +async def test_overpressure_error( + decoy: Decoy, + pipetting: PipettingHandler, + subject: BlowOutImplementation, + model_utils: ModelUtils, + movement: MovementHandler, +) -> None: + """It should return an overpressure error if the hardware API indicates that.""" + pipette_id = "pipette-id" + + error_id = "error-id" + error_timestamp = datetime(year=2020, month=1, day=2) + + location = WellLocation(origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1)) + + data = BlowOutParams( + pipetteId="pipette-id", + labwareId="labware-id", + wellName="C6", + wellLocation=location, + flowRate=1.234, + ) + + decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id)).then_return( + True + ) + + decoy.when( + await pipetting.blow_out_in_place(pipette_id="pipette-id", flow_rate=1.234) + ).then_raise(PipetteOverpressureError()) + + decoy.when(model_utils.generate_id()).then_return(error_id) + decoy.when(model_utils.get_timestamp()).then_return(error_timestamp) + decoy.when( + await movement.move_to_well( + pipette_id="pipette-id", + labware_id="labware-id", + well_name="C6", + well_location=location, + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, + ) + ).then_return(Point(x=1, y=2, z=3)) + + result = await subject.execute(data) + + assert result == DefinedErrorData( + public=OverpressureError.construct( + id=error_id, + createdAt=error_timestamp, + wrappedErrors=[matchers.Anything()], + errorInfo={"retryLocation": (1, 2, 3)}, + ), + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.Well( + labware_id="labware-id", + well_name="C6", + ), + new_deck_point=DeckPoint(x=1, y=2, z=3), + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="pipette-id" + ), + ), + state_update_if_false_positive=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.Well( + labware_id="labware-id", + well_name="C6", + ), + new_deck_point=DeckPoint(x=1, y=2, z=3), + ), + ), + ) + + +async def test_stall_error( + decoy: Decoy, + pipetting: PipettingHandler, + subject: BlowOutImplementation, + model_utils: ModelUtils, + movement: MovementHandler, +) -> None: + """It should return an overpressure error if the hardware API indicates that.""" + pipette_id = "pipette-id" + labware_id = "labware-id" + well_name = "C6" + error_id = "error-id" + error_timestamp = datetime(year=2020, month=1, day=2) + + location = WellLocation(origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1)) + + data = BlowOutParams( + pipetteId=pipette_id, + labwareId=labware_id, + wellName=well_name, + wellLocation=location, + flowRate=1.234, + ) + + decoy.when(model_utils.generate_id()).then_return(error_id) + decoy.when(model_utils.get_timestamp()).then_return(error_timestamp) + decoy.when( + await movement.move_to_well( + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=location, + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, + ) + ).then_raise(StallOrCollisionDetectedError()) + + result = await subject.execute(data) + + assert result == DefinedErrorData( + public=StallOrCollisionError.construct( + id=error_id, + createdAt=error_timestamp, + wrappedErrors=[matchers.Anything()], + ), + state_update=update_types.StateUpdate( + pipette_location=update_types.CLEAR, + ), + ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_blow_out_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_blow_out_in_place.py index a14bcdc8019..97e8e8c0851 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_blow_out_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/test_blow_out_in_place.py @@ -1,43 +1,120 @@ """Test blow-out-in-place commands.""" -from decoy import Decoy +from datetime import datetime -from opentrons.protocol_engine.state import StateView +import pytest +from decoy import Decoy, matchers + +from opentrons.protocol_engine.commands.pipetting_common import OverpressureError +from opentrons.protocol_engine.execution.gantry_mover import GantryMover +from opentrons.protocol_engine.resources.model_utils import ModelUtils +from opentrons.protocol_engine.state.state import StateView +from opentrons.protocol_engine.state import update_types from opentrons.protocol_engine.commands.blow_out_in_place import ( BlowOutInPlaceParams, BlowOutInPlaceResult, BlowOutInPlaceImplementation, ) -from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.command import DefinedErrorData, SuccessData from opentrons.protocol_engine.execution import ( - MovementHandler, PipettingHandler, ) from opentrons.hardware_control import HardwareControlAPI +from opentrons.types import Point +from opentrons_shared_data.errors.exceptions import PipetteOverpressureError -async def test_blow_out_in_place_implementation( - decoy: Decoy, +@pytest.fixture +def subject( + pipetting: PipettingHandler, state_view: StateView, hardware_api: HardwareControlAPI, - movement: MovementHandler, - pipetting: PipettingHandler, -) -> None: - """Test BlowOut command execution.""" - subject = BlowOutInPlaceImplementation( - state_view=state_view, - hardware_api=hardware_api, + model_utils: ModelUtils, + gantry_mover: GantryMover, +) -> BlowOutInPlaceImplementation: + """Get the impelementation subject.""" + return BlowOutInPlaceImplementation( pipetting=pipetting, + hardware_api=hardware_api, + state_view=state_view, + model_utils=model_utils, + gantry_mover=gantry_mover, ) + +async def test_blow_out_in_place_implementation( + decoy: Decoy, + gantry_mover: GantryMover, + subject: BlowOutInPlaceImplementation, + pipetting: PipettingHandler, +) -> None: + """Test BlowOut command execution.""" data = BlowOutInPlaceParams( pipetteId="pipette-id", flowRate=1.234, ) + decoy.when(await gantry_mover.get_position("pipette-id")).then_return( + Point(1, 2, 3) + ) result = await subject.execute(data) - - assert result == SuccessData(public=BlowOutInPlaceResult(), private=None) + assert result == SuccessData( + public=BlowOutInPlaceResult(), + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteEmptyFluidUpdate( + pipette_id="pipette-id" + ) + ), + ) decoy.verify( await pipetting.blow_out_in_place(pipette_id="pipette-id", flow_rate=1.234) ) + + +async def test_overpressure_error( + decoy: Decoy, + gantry_mover: GantryMover, + pipetting: PipettingHandler, + subject: BlowOutInPlaceImplementation, + model_utils: ModelUtils, +) -> None: + """It should return an overpressure error if the hardware API indicates that.""" + pipette_id = "pipette-id" + + position = Point(x=1, y=2, z=3) + + error_id = "error-id" + error_timestamp = datetime(year=2020, month=1, day=2) + + data = BlowOutInPlaceParams( + pipetteId=pipette_id, + flowRate=1.234, + ) + + decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id)).then_return( + True + ) + + decoy.when( + await pipetting.blow_out_in_place(pipette_id="pipette-id", flow_rate=1.234) + ).then_raise(PipetteOverpressureError()) + + decoy.when(model_utils.generate_id()).then_return(error_id) + decoy.when(model_utils.get_timestamp()).then_return(error_timestamp) + decoy.when(await gantry_mover.get_position(pipette_id)).then_return(position) + + result = await subject.execute(data) + + assert result == DefinedErrorData( + public=OverpressureError.construct( + id=error_id, + createdAt=error_timestamp, + wrappedErrors=[matchers.Anything()], + errorInfo={"retryLocation": (position.x, position.y, position.z)}, + ), + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="pipette-id" + ) + ), + ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_comment.py b/api/tests/opentrons/protocol_engine/commands/test_comment.py index 4010f2ec56c..9b62afa7fe3 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_comment.py +++ b/api/tests/opentrons/protocol_engine/commands/test_comment.py @@ -15,4 +15,4 @@ async def test_comment_implementation() -> None: result = await subject.execute(data) - assert result == SuccessData(public=CommentResult(), private=None) + assert result == SuccessData(public=CommentResult()) diff --git a/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py b/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py index 95e1e856bd4..2d8685109ed 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py +++ b/api/tests/opentrons/protocol_engine/commands/test_configure_for_volume.py @@ -1,4 +1,8 @@ """Test load pipette commands.""" +from opentrons.protocol_engine.state.update_types import ( + PipetteConfigUpdate, + StateUpdate, +) import pytest from decoy import Decoy @@ -15,14 +19,20 @@ from opentrons.protocol_engine.commands.configure_for_volume import ( ConfigureForVolumeParams, ConfigureForVolumeResult, - ConfigureForVolumePrivateResult, ConfigureForVolumeImplementation, ) from opentrons_shared_data.pipette.types import PipetteNameType +from opentrons_shared_data.pipette.pipette_definition import AvailableSensorDefinition from ..pipette_fixtures import get_default_nozzle_map from opentrons.types import Point +@pytest.fixture +def available_sensors() -> AvailableSensorDefinition: + """Provide a list of sensors.""" + return AvailableSensorDefinition(sensors=["pressure", "capacitive", "environment"]) + + @pytest.mark.parametrize( "data", [ @@ -38,7 +48,10 @@ ], ) async def test_configure_for_volume_implementation( - decoy: Decoy, equipment: EquipmentHandler, data: ConfigureForVolumeParams + decoy: Decoy, + equipment: EquipmentHandler, + data: ConfigureForVolumeParams, + available_sensors: AvailableSensorDefinition, ) -> None: """A ConfigureForVolume command should have an execution implementation.""" subject = ConfigureForVolumeImplementation(equipment=equipment) @@ -60,6 +73,14 @@ async def test_configure_for_volume_implementation( back_left_corner_offset=Point(10, 20, 30), front_right_corner_offset=Point(40, 50, 60), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ) decoy.when( @@ -81,7 +102,9 @@ async def test_configure_for_volume_implementation( assert result == SuccessData( public=ConfigureForVolumeResult(), - private=ConfigureForVolumePrivateResult( - pipette_id="pipette-id", serial_number="some number", config=config + state_update=StateUpdate( + pipette_config=PipetteConfigUpdate( + pipette_id="pipette-id", serial_number="some number", config=config + ) ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_configure_nozzle_layout.py b/api/tests/opentrons/protocol_engine/commands/test_configure_nozzle_layout.py index 2f318b147ac..cfe6f80c3a8 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_configure_nozzle_layout.py +++ b/api/tests/opentrons/protocol_engine/commands/test_configure_nozzle_layout.py @@ -1,4 +1,8 @@ """Test configure nozzle layout commands.""" +from opentrons.protocol_engine.state.update_types import ( + PipetteNozzleMapUpdate, + StateUpdate, +) import pytest from decoy import Decoy from typing import Union, Dict @@ -15,7 +19,6 @@ from opentrons.protocol_engine.commands.configure_nozzle_layout import ( ConfigureNozzleLayoutParams, ConfigureNozzleLayoutResult, - ConfigureNozzleLayoutPrivateResult, ConfigureNozzleLayoutImplementation, ) @@ -142,8 +145,10 @@ async def test_configure_nozzle_layout_implementation( assert result == SuccessData( public=ConfigureNozzleLayoutResult(), - private=ConfigureNozzleLayoutPrivateResult( - pipette_id="pipette-id", - nozzle_map=expected_nozzlemap, + state_update=StateUpdate( + pipette_nozzle_map=PipetteNozzleMapUpdate( + pipette_id="pipette-id", + nozzle_map=expected_nozzlemap, + ) ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_dispense.py b/api/tests/opentrons/protocol_engine/commands/test_dispense.py index 86c4f6ac93b..e0e18307b69 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_dispense.py +++ b/api/tests/opentrons/protocol_engine/commands/test_dispense.py @@ -1,13 +1,24 @@ """Test dispense commands.""" + from datetime import datetime import pytest from decoy import Decoy, matchers -from opentrons_shared_data.errors.exceptions import PipetteOverpressureError +from opentrons_shared_data.errors.exceptions import ( + PipetteOverpressureError, + StallOrCollisionDetectedError, +) -from opentrons.protocol_engine import WellLocation, WellOrigin, WellOffset, DeckPoint +from opentrons.protocol_engine import ( + LiquidHandlingWellLocation, + WellOrigin, + WellOffset, + DeckPoint, +) from opentrons.protocol_engine.execution import MovementHandler, PipettingHandler +from opentrons.protocol_engine.state import update_types +from opentrons.protocol_engine.state.state import StateView from opentrons.types import Point from opentrons.protocol_engine.commands.command import SuccessData, DefinedErrorData @@ -17,21 +28,23 @@ DispenseImplementation, ) from opentrons.protocol_engine.resources.model_utils import ModelUtils -from opentrons.protocol_engine.commands.pipetting_common import ( - OverpressureError, - OverpressureErrorInternalData, -) +from opentrons.protocol_engine.commands.pipetting_common import OverpressureError +from opentrons.protocol_engine.commands.movement_common import StallOrCollisionError @pytest.fixture def subject( + state_view: StateView, movement: MovementHandler, pipetting: PipettingHandler, model_utils: ModelUtils, ) -> DispenseImplementation: """Get the implementation subject.""" return DispenseImplementation( - movement=movement, pipetting=pipetting, model_utils=model_utils + state_view=state_view, + movement=movement, + pipetting=pipetting, + model_utils=model_utils, ) @@ -40,9 +53,10 @@ async def test_dispense_implementation( movement: MovementHandler, pipetting: PipettingHandler, subject: DispenseImplementation, + state_view: StateView, ) -> None: """It should move to the target location and then dispense.""" - well_location = WellLocation( + well_location = LiquidHandlingWellLocation( origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1) ) @@ -61,20 +75,61 @@ async def test_dispense_implementation( labware_id="labware-id-abc123", well_name="A3", well_location=well_location, + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, ) ).then_return(Point(x=1, y=2, z=3)) + decoy.when( + state_view.geometry.get_nozzles_per_well( + labware_id="labware-id-abc123", + target_well_name="A3", + pipette_id="pipette-id-abc123", + ) + ).then_return(2) + + decoy.when( + state_view.geometry.get_wells_covered_by_pipette_with_active_well( + "labware-id-abc123", "A3", "pipette-id-abc123" + ) + ).then_return(["A3", "A4"]) + decoy.when( await pipetting.dispense_in_place( pipette_id="pipette-id-abc123", volume=50, flow_rate=1.23, push_out=None ) ).then_return(42) + decoy.when( + state_view.pipettes.get_liquid_dispensed_by_ejecting_volume( + pipette_id="pipette-id-abc123", volume=42 + ) + ).then_return(34) result = await subject.execute(data) assert result == SuccessData( public=DispenseResult(volume=42, position=DeckPoint(x=1, y=2, z=3)), - private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id-abc123", + new_location=update_types.Well( + labware_id="labware-id-abc123", + well_name="A3", + ), + new_deck_point=DeckPoint.construct(x=1, y=2, z=3), + ), + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id="labware-id-abc123", + well_names=["A3", "A4"], + volume_added=68, + ), + pipette_aspirated_fluid=update_types.PipetteEjectedFluidUpdate( + pipette_id="pipette-id-abc123", volume=42 + ), + ), ) @@ -84,12 +139,13 @@ async def test_overpressure_error( pipetting: PipettingHandler, subject: DispenseImplementation, model_utils: ModelUtils, + state_view: StateView, ) -> None: """It should return an overpressure error if the hardware API indicates that.""" pipette_id = "pipette-id" labware_id = "labware-id" well_name = "well-name" - well_location = WellLocation( + well_location = LiquidHandlingWellLocation( origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1) ) @@ -107,12 +163,31 @@ async def test_overpressure_error( flowRate=1.23, ) + decoy.when( + state_view.geometry.get_nozzles_per_well( + labware_id=labware_id, + target_well_name=well_name, + pipette_id=pipette_id, + ) + ).then_return(2) + + decoy.when( + state_view.geometry.get_wells_covered_by_pipette_with_active_well( + labware_id, well_name, pipette_id + ) + ).then_return(["A3", "A4"]) + decoy.when( await movement.move_to_well( pipette_id=pipette_id, labware_id=labware_id, well_name=well_name, well_location=well_location, + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, ), ).then_return(position) @@ -134,7 +209,89 @@ async def test_overpressure_error( wrappedErrors=[matchers.Anything()], errorInfo={"retryLocation": (position.x, position.y, position.z)}, ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=position.x, y=position.y, z=position.z) + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.Well( + labware_id="labware-id", + well_name="well-name", + ), + new_deck_point=DeckPoint.construct(x=1, y=2, z=3), + ), + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id="labware-id", + well_names=["A3", "A4"], + volume_added=update_types.CLEAR, + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="pipette-id" + ), + ), + state_update_if_false_positive=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.Well( + labware_id="labware-id", + well_name="well-name", + ), + new_deck_point=DeckPoint.construct(x=1, y=2, z=3), + ), + ), + ) + + +async def test_stall_error( + decoy: Decoy, + movement: MovementHandler, + pipetting: PipettingHandler, + subject: DispenseImplementation, + model_utils: ModelUtils, + state_view: StateView, +) -> None: + """It should return a stall error if the hardware API indicates that.""" + pipette_id = "pipette-id" + labware_id = "labware-id" + well_name = "well-name" + well_location = LiquidHandlingWellLocation( + origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1) + ) + + error_id = "error-id" + error_timestamp = datetime(year=2020, month=1, day=2) + + data = DispenseParams( + pipetteId=pipette_id, + labwareId=labware_id, + wellName=well_name, + wellLocation=well_location, + volume=50, + flowRate=1.23, + ) + + decoy.when( + await movement.move_to_well( + pipette_id=pipette_id, + labware_id=labware_id, + well_name=well_name, + well_location=well_location, + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, + ), + ).then_raise(StallOrCollisionDetectedError()) + + decoy.when(model_utils.generate_id()).then_return(error_id) + decoy.when(model_utils.get_timestamp()).then_return(error_timestamp) + + result = await subject.execute(data) + + assert result == DefinedErrorData( + public=StallOrCollisionError.construct( + id=error_id, + createdAt=error_timestamp, + wrappedErrors=[matchers.Anything()], ), + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_dispense_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_dispense_in_place.py index 3b37e1078b7..bc39fba4a00 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_dispense_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/test_dispense_in_place.py @@ -1,6 +1,8 @@ """Test dispense-in-place commands.""" + from datetime import datetime +import pytest from decoy import Decoy, matchers from opentrons_shared_data.errors.exceptions import PipetteOverpressureError @@ -14,25 +16,60 @@ DispenseInPlaceResult, DispenseInPlaceImplementation, ) -from opentrons.protocol_engine.types import DeckPoint -from opentrons.protocol_engine.commands.pipetting_common import ( - OverpressureError, - OverpressureErrorInternalData, -) +from opentrons.protocol_engine.commands.pipetting_common import OverpressureError from opentrons.protocol_engine.resources import ModelUtils +from opentrons.protocol_engine.state.state import StateView +from opentrons.protocol_engine.types import ( + CurrentWell, + CurrentPipetteLocation, + CurrentAddressableArea, +) +from opentrons.protocol_engine.state import update_types -async def test_dispense_in_place_implementation( - decoy: Decoy, +@pytest.fixture +def subject( pipetting: PipettingHandler, + state_view: StateView, gantry_mover: GantryMover, model_utils: ModelUtils, -) -> None: - """It should dispense in place.""" - subject = DispenseInPlaceImplementation( - pipetting=pipetting, gantry_mover=gantry_mover, model_utils=model_utils +) -> DispenseInPlaceImplementation: + """Build a command implementation.""" + return DispenseInPlaceImplementation( + pipetting=pipetting, + state_view=state_view, + gantry_mover=gantry_mover, + model_utils=model_utils, ) + +@pytest.mark.parametrize( + "location,stateupdateLabware,stateupdateWell", + [ + ( + CurrentWell( + pipette_id="pipette-id-abc", + labware_id="labware-id-1", + well_name="well-name-1", + ), + "labware-id-1", + "well-name-1", + ), + (None, None, None), + (CurrentAddressableArea("pipette-id-abc", "addressable-area-1"), None, None), + ], +) +async def test_dispense_in_place_implementation( + decoy: Decoy, + gantry_mover: GantryMover, + pipetting: PipettingHandler, + state_view: StateView, + subject: DispenseInPlaceImplementation, + location: CurrentPipetteLocation | None, + stateupdateLabware: str, + stateupdateWell: str, +) -> None: + """It should dispense in place.""" data = DispenseInPlaceParams( pipetteId="pipette-id-abc", volume=123, @@ -45,22 +82,85 @@ async def test_dispense_in_place_implementation( ) ).then_return(42) + decoy.when(state_view.pipettes.get_current_location()).then_return(location) + decoy.when( + state_view.pipettes.get_liquid_dispensed_by_ejecting_volume( + pipette_id="pipette-id-abc", volume=42 + ) + ).then_return(34) + + decoy.when( + state_view.geometry.get_nozzles_per_well( + labware_id=stateupdateLabware, + target_well_name=stateupdateWell, + pipette_id="pipette-id-abc", + ) + ).then_return(2) + + decoy.when( + state_view.geometry.get_wells_covered_by_pipette_with_active_well( + stateupdateLabware, stateupdateWell, "pipette-id-abc" + ) + ).then_return(["A3", "A4"]) + decoy.when(await gantry_mover.get_position("pipette-id-abc")).then_return( + Point(1, 2, 3) + ) + result = await subject.execute(data) - assert result == SuccessData(public=DispenseInPlaceResult(volume=42), private=None) + if isinstance(location, CurrentWell): + assert result == SuccessData( + public=DispenseInPlaceResult(volume=42), + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteEjectedFluidUpdate( + pipette_id="pipette-id-abc", volume=42 + ), + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id=stateupdateLabware, + well_names=["A3", "A4"], + volume_added=68, + ), + ), + ) + else: + assert result == SuccessData( + public=DispenseInPlaceResult(volume=42), + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteEjectedFluidUpdate( + pipette_id="pipette-id-abc", volume=42 + ) + ), + ) +@pytest.mark.parametrize( + "location,stateupdateLabware,stateupdateWell", + [ + ( + CurrentWell( + pipette_id="pipette-id", + labware_id="labware-id-1", + well_name="well-name-1", + ), + "labware-id-1", + "well-name-1", + ), + (None, None, None), + (CurrentAddressableArea("pipette-id", "addressable-area-1"), None, None), + ], +) async def test_overpressure_error( decoy: Decoy, gantry_mover: GantryMover, pipetting: PipettingHandler, + state_view: StateView, model_utils: ModelUtils, + subject: DispenseInPlaceImplementation, + location: CurrentPipetteLocation | None, + stateupdateLabware: str, + stateupdateWell: str, ) -> None: """It should return an overpressure error if the hardware API indicates that.""" - subject = DispenseInPlaceImplementation( - pipetting=pipetting, gantry_mover=gantry_mover, model_utils=model_utils - ) - pipette_id = "pipette-id" position = Point(x=1, y=2, z=3) @@ -75,6 +175,20 @@ async def test_overpressure_error( pushOut=10, ) + decoy.when( + state_view.geometry.get_nozzles_per_well( + labware_id=stateupdateLabware, + target_well_name=stateupdateWell, + pipette_id="pipette-id", + ) + ).then_return(2) + + decoy.when( + state_view.geometry.get_wells_covered_by_pipette_with_active_well( + stateupdateLabware, stateupdateWell, "pipette-id" + ) + ).then_return(["A3", "A4"]) + decoy.when( await pipetting.dispense_in_place( pipette_id=pipette_id, @@ -87,17 +201,40 @@ async def test_overpressure_error( decoy.when(model_utils.generate_id()).then_return(error_id) decoy.when(model_utils.get_timestamp()).then_return(error_timestamp) decoy.when(await gantry_mover.get_position(pipette_id)).then_return(position) + decoy.when(state_view.pipettes.get_current_location()).then_return(location) result = await subject.execute(data) - assert result == DefinedErrorData( - public=OverpressureError.construct( - id=error_id, - createdAt=error_timestamp, - wrappedErrors=[matchers.Anything()], - errorInfo={"retryLocation": (position.x, position.y, position.z)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=position.x, y=position.y, z=position.z) - ), - ) + if isinstance(location, CurrentWell): + assert result == DefinedErrorData( + public=OverpressureError.construct( + id=error_id, + createdAt=error_timestamp, + wrappedErrors=[matchers.Anything()], + errorInfo={"retryLocation": (position.x, position.y, position.z)}, + ), + state_update=update_types.StateUpdate( + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id=stateupdateLabware, + well_names=["A3", "A4"], + volume_added=update_types.CLEAR, + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="pipette-id" + ), + ), + ) + else: + assert result == DefinedErrorData( + public=OverpressureError.construct( + id=error_id, + createdAt=error_timestamp, + wrappedErrors=[matchers.Anything()], + errorInfo={"retryLocation": (position.x, position.y, position.z)}, + ), + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="pipette-id" + ) + ), + ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py b/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py index 9690dcc2461..038ea12255b 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py +++ b/api/tests/opentrons/protocol_engine/commands/test_drop_tip.py @@ -1,6 +1,11 @@ """Test drop tip commands.""" + +from datetime import datetime + import pytest -from decoy import Decoy +from decoy import Decoy, matchers + +from opentrons_shared_data.errors.exceptions import StallOrCollisionDetectedError from opentrons.protocol_engine import ( DropTipWellLocation, @@ -9,16 +14,24 @@ WellOffset, DeckPoint, ) -from opentrons.protocol_engine.state import StateView -from opentrons.protocol_engine.execution import MovementHandler, TipHandler -from opentrons.types import Point - -from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.command import DefinedErrorData, SuccessData from opentrons.protocol_engine.commands.drop_tip import ( DropTipParams, DropTipResult, DropTipImplementation, ) +from opentrons.protocol_engine.commands.pipetting_common import ( + TipPhysicallyAttachedError, +) +from opentrons.protocol_engine.commands.movement_common import StallOrCollisionError +from opentrons.protocol_engine.errors.exceptions import TipAttachedError +from opentrons.protocol_engine.resources.model_utils import ModelUtils +from opentrons.protocol_engine.state import update_types +from opentrons.protocol_engine.state.state import StateView +from opentrons.protocol_engine.execution import MovementHandler, TipHandler + + +from opentrons.types import Point @pytest.fixture @@ -39,6 +52,12 @@ def mock_tip_handler(decoy: Decoy) -> TipHandler: return decoy.mock(cls=TipHandler) +@pytest.fixture +def mock_model_utils(decoy: Decoy) -> ModelUtils: + """Get a mock ModelUtils.""" + return decoy.mock(cls=ModelUtils) + + def test_drop_tip_params_defaults() -> None: """A drop tip should use a `WellOrigin.DROP_TIP` by default.""" default_params = DropTipParams.parse_obj( @@ -71,12 +90,14 @@ async def test_drop_tip_implementation( mock_state_view: StateView, mock_movement_handler: MovementHandler, mock_tip_handler: TipHandler, + mock_model_utils: ModelUtils, ) -> None: """A DropTip command should have an execution implementation.""" subject = DropTipImplementation( state_view=mock_state_view, movement=mock_movement_handler, tip_handler=mock_tip_handler, + model_utils=mock_model_utils, ) params = DropTipParams( @@ -106,13 +127,34 @@ async def test_drop_tip_implementation( labware_id="123", well_name="A3", well_location=WellLocation(offset=WellOffset(x=4, y=5, z=6)), + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, ) ).then_return(Point(x=111, y=222, z=333)) result = await subject.execute(params) assert result == SuccessData( - public=DropTipResult(position=DeckPoint(x=111, y=222, z=333)), private=None + public=DropTipResult(position=DeckPoint(x=111, y=222, z=333)), + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.Well( + labware_id="123", + well_name="A3", + ), + new_deck_point=DeckPoint(x=111, y=222, z=333), + ), + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="abc", tip_geometry=None + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="abc" + ), + ), ) decoy.verify( @@ -126,12 +168,14 @@ async def test_drop_tip_with_alternating_locations( mock_state_view: StateView, mock_movement_handler: MovementHandler, mock_tip_handler: TipHandler, + mock_model_utils: ModelUtils, ) -> None: """It should drop tip at random location within the labware every time.""" subject = DropTipImplementation( state_view=mock_state_view, movement=mock_movement_handler, tip_handler=mock_tip_handler, + model_utils=mock_model_utils, ) params = DropTipParams( pipetteId="abc", @@ -169,10 +213,195 @@ async def test_drop_tip_with_alternating_locations( labware_id="123", well_name="A3", well_location=WellLocation(offset=WellOffset(x=4, y=5, z=6)), + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, ) ).then_return(Point(x=111, y=222, z=333)) result = await subject.execute(params) assert result == SuccessData( - public=DropTipResult(position=DeckPoint(x=111, y=222, z=333)), private=None + public=DropTipResult(position=DeckPoint(x=111, y=222, z=333)), + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.Well( + labware_id="123", + well_name="A3", + ), + new_deck_point=DeckPoint(x=111, y=222, z=333), + ), + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="abc", tip_geometry=None + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="abc" + ), + ), + ) + + +async def test_tip_attached_error( + decoy: Decoy, + mock_state_view: StateView, + mock_movement_handler: MovementHandler, + mock_tip_handler: TipHandler, + mock_model_utils: ModelUtils, +) -> None: + """A DropTip command should have an execution implementation.""" + subject = DropTipImplementation( + state_view=mock_state_view, + movement=mock_movement_handler, + tip_handler=mock_tip_handler, + model_utils=mock_model_utils, + ) + + params = DropTipParams( + pipetteId="abc", + labwareId="123", + wellName="A3", + wellLocation=DropTipWellLocation(offset=WellOffset(x=1, y=2, z=3)), + ) + + decoy.when( + mock_state_view.pipettes.get_is_partially_configured(pipette_id="abc") + ).then_return(False) + + decoy.when( + mock_state_view.geometry.get_checked_tip_drop_location( + pipette_id="abc", + labware_id="123", + well_location=DropTipWellLocation(offset=WellOffset(x=1, y=2, z=3)), + partially_configured=False, + ) + ).then_return(WellLocation(offset=WellOffset(x=4, y=5, z=6))) + + decoy.when( + await mock_movement_handler.move_to_well( + pipette_id="abc", + labware_id="123", + well_name="A3", + well_location=WellLocation(offset=WellOffset(x=4, y=5, z=6)), + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, + ) + ).then_return(Point(x=111, y=222, z=333)) + decoy.when( + await mock_tip_handler.drop_tip(pipette_id="abc", home_after=None) + ).then_raise(TipAttachedError("Egads!")) + + decoy.when(mock_model_utils.generate_id()).then_return("error-id") + decoy.when(mock_model_utils.get_timestamp()).then_return( + datetime(year=1, month=2, day=3) + ) + + result = await subject.execute(params) + + assert result == DefinedErrorData( + public=TipPhysicallyAttachedError.construct( + id="error-id", + createdAt=datetime(year=1, month=2, day=3), + wrappedErrors=[matchers.Anything()], + errorInfo={"retryLocation": (111, 222, 333)}, + ), + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.Well( + labware_id="123", + well_name="A3", + ), + new_deck_point=DeckPoint(x=111, y=222, z=333), + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="abc" + ), + ), + state_update_if_false_positive=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="abc", + tip_geometry=None, + ), + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.Well( + labware_id="123", + well_name="A3", + ), + new_deck_point=DeckPoint(x=111, y=222, z=333), + ), + ), + ) + + +async def test_stall_error( + decoy: Decoy, + mock_state_view: StateView, + mock_movement_handler: MovementHandler, + mock_tip_handler: TipHandler, + mock_model_utils: ModelUtils, +) -> None: + """A DropTip command should have an execution implementation.""" + subject = DropTipImplementation( + state_view=mock_state_view, + movement=mock_movement_handler, + tip_handler=mock_tip_handler, + model_utils=mock_model_utils, + ) + + params = DropTipParams( + pipetteId="abc", + labwareId="123", + wellName="A3", + wellLocation=DropTipWellLocation(offset=WellOffset(x=1, y=2, z=3)), + ) + + decoy.when( + mock_state_view.pipettes.get_is_partially_configured(pipette_id="abc") + ).then_return(False) + + decoy.when( + mock_state_view.geometry.get_checked_tip_drop_location( + pipette_id="abc", + labware_id="123", + well_location=DropTipWellLocation(offset=WellOffset(x=1, y=2, z=3)), + partially_configured=False, + ) + ).then_return(WellLocation(offset=WellOffset(x=4, y=5, z=6))) + + decoy.when( + await mock_movement_handler.move_to_well( + pipette_id="abc", + labware_id="123", + well_name="A3", + well_location=WellLocation(offset=WellOffset(x=4, y=5, z=6)), + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, + ) + ).then_raise(StallOrCollisionDetectedError()) + + decoy.when(mock_model_utils.generate_id()).then_return("error-id") + decoy.when(mock_model_utils.get_timestamp()).then_return( + datetime(year=1, month=2, day=3) + ) + + result = await subject.execute(params) + + assert result == DefinedErrorData( + public=StallOrCollisionError.construct( + id="error-id", + createdAt=datetime(year=1, month=2, day=3), + wrappedErrors=[matchers.Anything()], + ), + state_update=update_types.StateUpdate( + pipette_location=update_types.CLEAR, + ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py b/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py index 4bfefe4fdbe..5565ffea88c 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/test_drop_tip_in_place.py @@ -1,15 +1,27 @@ """Test drop tip in place commands.""" -import pytest -from decoy import Decoy +from datetime import datetime -from opentrons.protocol_engine.execution import TipHandler +import pytest +from decoy import Decoy, matchers -from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.pipetting_common import ( + TipPhysicallyAttachedError, +) +from opentrons.protocol_engine.commands.command import DefinedErrorData, SuccessData from opentrons.protocol_engine.commands.drop_tip_in_place import ( DropTipInPlaceParams, DropTipInPlaceResult, DropTipInPlaceImplementation, ) +from opentrons.protocol_engine.errors.exceptions import TipAttachedError +from opentrons.protocol_engine.execution import TipHandler, GantryMover +from opentrons.protocol_engine.resources.model_utils import ModelUtils +from opentrons.protocol_engine.state.update_types import ( + PipetteTipStateUpdate, + StateUpdate, + PipetteUnknownFluidUpdate, +) +from opentrons.types import Point @pytest.fixture @@ -18,20 +30,92 @@ def mock_tip_handler(decoy: Decoy) -> TipHandler: return decoy.mock(cls=TipHandler) -async def test_drop_tip_implementation( +@pytest.fixture +def mock_model_utils(decoy: Decoy) -> ModelUtils: + """Get a mock ModelUtils.""" + return decoy.mock(cls=ModelUtils) + + +@pytest.fixture +def mock_gantry_mover(decoy: Decoy) -> GantryMover: + """Get a mock GantryMover.""" + return decoy.mock(cls=GantryMover) + + +async def test_success( decoy: Decoy, mock_tip_handler: TipHandler, + mock_model_utils: ModelUtils, + mock_gantry_mover: GantryMover, ) -> None: """A DropTip command should have an execution implementation.""" - subject = DropTipInPlaceImplementation(tip_handler=mock_tip_handler) - + subject = DropTipInPlaceImplementation( + tip_handler=mock_tip_handler, + model_utils=mock_model_utils, + gantry_mover=mock_gantry_mover, + ) params = DropTipInPlaceParams(pipetteId="abc", homeAfter=False) result = await subject.execute(params) - assert result == SuccessData(public=DropTipInPlaceResult(), private=None) + assert result == SuccessData( + public=DropTipInPlaceResult(), + state_update=StateUpdate( + pipette_tip_state=PipetteTipStateUpdate( + pipette_id="abc", tip_geometry=None + ), + pipette_aspirated_fluid=PipetteUnknownFluidUpdate(pipette_id="abc"), + ), + ) decoy.verify( await mock_tip_handler.drop_tip(pipette_id="abc", home_after=False), times=1, ) + + +async def test_tip_attached_error( + decoy: Decoy, + mock_tip_handler: TipHandler, + mock_model_utils: ModelUtils, + mock_gantry_mover: GantryMover, +) -> None: + """A DropTip command should have an execution implementation.""" + subject = DropTipInPlaceImplementation( + tip_handler=mock_tip_handler, + model_utils=mock_model_utils, + gantry_mover=mock_gantry_mover, + ) + + params = DropTipInPlaceParams(pipetteId="abc", homeAfter=False) + + decoy.when(await mock_gantry_mover.get_position(pipette_id="abc")).then_return( + Point(9, 8, 7) + ) + decoy.when( + await mock_tip_handler.drop_tip(pipette_id="abc", home_after=False) + ).then_raise(TipAttachedError("Egads!")) + + decoy.when(mock_model_utils.generate_id()).then_return("error-id") + decoy.when(mock_model_utils.get_timestamp()).then_return( + datetime(year=1, month=2, day=3) + ) + + result = await subject.execute(params) + + assert result == DefinedErrorData( + public=TipPhysicallyAttachedError.construct( + id="error-id", + createdAt=datetime(year=1, month=2, day=3), + wrappedErrors=[matchers.Anything()], + errorInfo={"retryLocation": (9, 8, 7)}, + ), + state_update=StateUpdate( + pipette_aspirated_fluid=PipetteUnknownFluidUpdate(pipette_id="abc") + ), + state_update_if_false_positive=StateUpdate( + pipette_tip_state=PipetteTipStateUpdate( + pipette_id="abc", tip_geometry=None + ), + ), + ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_get_tip_presence.py b/api/tests/opentrons/protocol_engine/commands/test_get_tip_presence.py index a1d0230f74a..99e5b231e1a 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_get_tip_presence.py +++ b/api/tests/opentrons/protocol_engine/commands/test_get_tip_presence.py @@ -41,5 +41,5 @@ async def test_get_tip_presence_implementation( result = await subject.execute(data) assert result == SuccessData( - public=GetTipPresenceResult(status=status), private=None + public=GetTipPresenceResult(status=status), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_home.py b/api/tests/opentrons/protocol_engine/commands/test_home.py index f68c1b6de27..b3578c400e5 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_home.py +++ b/api/tests/opentrons/protocol_engine/commands/test_home.py @@ -1,6 +1,7 @@ """Test home commands.""" from decoy import Decoy +from opentrons.protocol_engine.state import update_types from opentrons.protocol_engine.types import MotorAxis from opentrons.types import MountType from opentrons.protocol_engine.execution import MovementHandler @@ -21,7 +22,10 @@ async def test_home_implementation(decoy: Decoy, movement: MovementHandler) -> N result = await subject.execute(data) - assert result == SuccessData(public=HomeResult(), private=None) + assert result == SuccessData( + public=HomeResult(), + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), + ) decoy.verify(await movement.home(axes=[MotorAxis.X, MotorAxis.Y])) @@ -33,7 +37,10 @@ async def test_home_all_implementation(decoy: Decoy, movement: MovementHandler) result = await subject.execute(data) - assert result == SuccessData(public=HomeResult(), private=None) + assert result == SuccessData( + public=HomeResult(), + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), + ) decoy.verify(await movement.home(axes=None)) @@ -52,7 +59,10 @@ async def test_home_with_invalid_position( ) result = await subject.execute(data) - assert result == SuccessData(public=HomeResult(), private=None) + assert result == SuccessData( + public=HomeResult(), + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), + ) decoy.verify(await movement.home(axes=[MotorAxis.X, MotorAxis.Y]), times=1) decoy.reset() @@ -61,6 +71,9 @@ async def test_home_with_invalid_position( await movement.check_for_valid_position(mount=MountType.LEFT) ).then_return(True) result = await subject.execute(data) - assert result == SuccessData(public=HomeResult(), private=None) + assert result == SuccessData( + public=HomeResult(), + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), + ) decoy.verify(await movement.home(axes=[MotorAxis.X, MotorAxis.Y]), times=0) diff --git a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py index 61f4339360d..34b979901aa 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py +++ b/api/tests/opentrons/protocol_engine/commands/test_liquid_probe.py @@ -1,22 +1,36 @@ """Test LiquidProbe commands.""" + from datetime import datetime from typing import Type, Union +from decoy import matchers, Decoy +import pytest + from opentrons.protocol_engine.errors.exceptions import ( MustHomeError, + PipetteNotReadyToAspirateError, TipNotAttachedError, TipNotEmptyError, ) from opentrons_shared_data.errors.exceptions import ( PipetteLiquidNotFoundError, + StallOrCollisionDetectedError, ) -from decoy import matchers, Decoy -import pytest +from opentrons_shared_data.pipette.pipette_definition import ( + AvailableSensorDefinition, + SupportedTipsDefinition, +) + +from opentrons_shared_data.pipette.types import PipetteNameType -from opentrons.protocol_engine.commands.pipetting_common import ( - LiquidNotFoundError, - LiquidNotFoundErrorInternalData, +from opentrons.protocol_engine.commands.pipetting_common import LiquidNotFoundError +from opentrons.protocol_engine.state.state import StateView +from opentrons.protocol_engine.state.pipettes import ( + StaticPipetteConfig, + BoundingNozzlesOffsets, + PipetteBoundingBoxOffsets, ) +from opentrons.protocol_engine.state import update_types from opentrons.types import MountType, Point from opentrons.protocol_engine import WellLocation, WellOrigin, WellOffset, DeckPoint @@ -29,16 +43,16 @@ TryLiquidProbeImplementation, ) from opentrons.protocol_engine.commands.command import DefinedErrorData, SuccessData +from opentrons.protocol_engine.commands.movement_common import StallOrCollisionError -from opentrons.protocol_engine.state import StateView from opentrons.protocol_engine.execution import ( MovementHandler, PipettingHandler, ) from opentrons.protocol_engine.resources.model_utils import ModelUtils -from opentrons.protocol_engine.types import LoadedPipette +from ..pipette_fixtures import get_default_nozzle_map EitherImplementationType = Union[ Type[LiquidProbeImplementation], Type[TryLiquidProbeImplementation] @@ -48,6 +62,12 @@ EitherResultType = Union[Type[LiquidProbeResult], Type[TryLiquidProbeResult]] +@pytest.fixture +def available_sensors() -> AvailableSensorDefinition: + """Provide a list of sensors.""" + return AvailableSensorDefinition(sensors=["pressure", "capacitive", "environment"]) + + @pytest.fixture( params=[ (LiquidProbeImplementation, LiquidProbeParams, LiquidProbeResult), @@ -63,7 +83,7 @@ def types( @pytest.fixture def implementation_type( - types: tuple[EitherImplementationType, object, object] + types: tuple[EitherImplementationType, object, object], ) -> EitherImplementationType: """Return an implementation type. Kept in sync with the params and result types.""" return types[0] @@ -84,27 +104,33 @@ def result_type(types: tuple[object, object, EitherResultType]) -> EitherResultT @pytest.fixture def subject( implementation_type: EitherImplementationType, + state_view: StateView, movement: MovementHandler, pipetting: PipettingHandler, model_utils: ModelUtils, ) -> Union[LiquidProbeImplementation, TryLiquidProbeImplementation]: """Get the implementation subject.""" return implementation_type( + state_view=state_view, pipetting=pipetting, movement=movement, model_utils=model_utils, ) -async def test_liquid_probe_implementation_no_prep( +async def test_liquid_probe_implementation( decoy: Decoy, movement: MovementHandler, + state_view: StateView, pipetting: PipettingHandler, subject: EitherImplementation, params_type: EitherParamsType, result_type: EitherResultType, + model_utils: ModelUtils, + available_sensors: AvailableSensorDefinition, + supported_tip_fixture: SupportedTipsDefinition, ) -> None: - """A Liquid Probe should have an execution implementation without preparing to aspirate.""" + """It should move to the destination and do a liquid probe there.""" location = WellLocation(origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1)) data = params_type( @@ -114,7 +140,9 @@ async def test_liquid_probe_implementation_no_prep( wellLocation=location, ) - decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id="abc")).then_return(True) + decoy.when(state_view.pipettes.get_aspirated_volume(pipette_id="abc")).then_return( + 0 + ) decoy.when( await movement.move_to_well( @@ -122,6 +150,11 @@ async def test_liquid_probe_implementation_no_prep( labware_id="123", well_name="A3", well_location=location, + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, ), ).then_return(Point(x=1, y=2, z=3)) @@ -134,71 +167,72 @@ async def test_liquid_probe_implementation_no_prep( ), ).then_return(15.0) - result = await subject.execute(data) - - assert type(result.public) is result_type # Pydantic v1 only compares the fields. - assert result == SuccessData( - public=result_type(z_position=15.0, position=DeckPoint(x=1, y=2, z=3)), - private=None, - ) - - -async def test_liquid_probe_implementation_with_prep( - decoy: Decoy, - state_view: StateView, - movement: MovementHandler, - pipetting: PipettingHandler, - subject: EitherImplementation, - params_type: EitherParamsType, - result_type: EitherResultType, -) -> None: - """A Liquid Probe should have an execution implementation with preparing to aspirate.""" - location = WellLocation(origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=2)) - - data = params_type( - pipetteId="abc", - labwareId="123", - wellName="A3", - wellLocation=location, - ) - - decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id="abc")).then_return(False) - - decoy.when(state_view.pipettes.get(pipette_id="abc")).then_return( - LoadedPipette.construct( # type:ignore[call-arg] - mount=MountType.LEFT - ) - ) - decoy.when( - await movement.move_to_well( - pipette_id="abc", labware_id="123", well_name="A3", well_location=location - ), - ).then_return(Point(x=1, y=2, z=3)) - decoy.when( - await pipetting.liquid_probe_in_place( - pipette_id="abc", + state_view.geometry.get_well_volume_at_height( labware_id="123", well_name="A3", - well_location=location, + height=15.0, ), - ).then_return(15.0) + ).then_return(30.0) + decoy.when( + state_view.pipettes.get_nozzle_configuration_supports_lld("abc") + ).then_return(True) + + decoy.when(state_view.pipettes.get_config("abc")).then_return( + StaticPipetteConfig( + min_volume=1, + max_volume=9001, + channels=1, + model="blah", + display_name="bleh", + serial_number="", + tip_configuration_lookup_table={9001: supported_tip_fixture}, + nominal_tip_overlap={}, + home_position=0, + nozzle_offset_z=0, + bounding_nozzle_offsets=BoundingNozzlesOffsets( + back_left_offset=Point(x=10, y=20, z=30), + front_right_offset=Point(x=40, y=50, z=60), + ), + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P1000_96), + pipette_bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(x=10, y=20, z=30), + front_right_corner=Point(x=40, y=50, z=60), + front_left_corner=Point(x=10, y=50, z=60), + back_right_corner=Point(x=40, y=20, z=60), + ), + lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ) + ) + + timestamp = datetime(year=2020, month=1, day=2) + decoy.when(model_utils.get_timestamp()).then_return(timestamp) result = await subject.execute(data) assert type(result.public) is result_type # Pydantic v1 only compares the fields. assert result == SuccessData( public=result_type(z_position=15.0, position=DeckPoint(x=1, y=2, z=3)), - private=None, - ) - - decoy.verify( - await movement.move_to_well( - pipette_id="abc", - labware_id="123", - well_name="A3", - well_location=WellLocation( - origin=WellOrigin.TOP, offset=WellOffset(x=0, y=0, z=2) + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.Well(labware_id="123", well_name="A3"), + new_deck_point=DeckPoint(x=1, y=2, z=3), + ), + liquid_probed=update_types.LiquidProbedUpdate( + labware_id="123", + well_name="A3", + height=15.0, + volume=30.0, + last_probed=timestamp, ), ), ) @@ -206,11 +240,14 @@ async def test_liquid_probe_implementation_with_prep( async def test_liquid_not_found_error( decoy: Decoy, + state_view: StateView, movement: MovementHandler, pipetting: PipettingHandler, subject: EitherImplementation, params_type: EitherParamsType, model_utils: ModelUtils, + available_sensors: AvailableSensorDefinition, + supported_tip_fixture: SupportedTipsDefinition, ) -> None: """It should return a liquid not found error if the hardware API indicates that.""" pipette_id = "pipette-id" @@ -232,16 +269,52 @@ async def test_liquid_not_found_error( wellLocation=well_location, ) - decoy.when(pipetting.get_is_ready_to_aspirate(pipette_id=pipette_id)).then_return( - True + decoy.when(state_view.pipettes.get_aspirated_volume(pipette_id)).then_return(0) + decoy.when(state_view.pipettes.get_config("pipette-id")).then_return( + StaticPipetteConfig( + min_volume=1, + max_volume=9001, + channels=1, + model="blah", + display_name="bleh", + serial_number="", + tip_configuration_lookup_table={9001: supported_tip_fixture}, + nominal_tip_overlap={}, + home_position=0, + nozzle_offset_z=0, + bounding_nozzle_offsets=BoundingNozzlesOffsets( + back_left_offset=Point(x=10, y=20, z=30), + front_right_offset=Point(x=40, y=50, z=60), + ), + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P1000_96), + pipette_bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(x=10, y=20, z=30), + front_right_corner=Point(x=40, y=50, z=60), + front_left_corner=Point(x=10, y=50, z=60), + back_right_corner=Point(x=40, y=20, z=60), + ), + lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ) ) - decoy.when( await movement.move_to_well( pipette_id=pipette_id, labware_id=labware_id, well_name=well_name, well_location=well_location, + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, ), ).then_return(position) @@ -253,12 +326,28 @@ async def test_liquid_not_found_error( well_location=well_location, ), ).then_raise(PipetteLiquidNotFoundError()) - + decoy.when( + state_view.pipettes.get_nozzle_configuration_supports_lld(pipette_id) + ).then_return(True) decoy.when(model_utils.generate_id()).then_return(error_id) decoy.when(model_utils.get_timestamp()).then_return(error_timestamp) result = await subject.execute(data) + expected_state_update = update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id=pipette_id, + new_location=update_types.Well(labware_id=labware_id, well_name=well_name), + new_deck_point=DeckPoint(x=position.x, y=position.y, z=position.z), + ), + liquid_probed=update_types.LiquidProbedUpdate( + labware_id=labware_id, + well_name=well_name, + height=update_types.CLEAR, + volume=update_types.CLEAR, + last_probed=error_timestamp, + ), + ) if isinstance(subject, LiquidProbeImplementation): assert result == DefinedErrorData( public=LiquidNotFoundError.construct( @@ -266,9 +355,7 @@ async def test_liquid_not_found_error( createdAt=error_timestamp, wrappedErrors=[matchers.Anything()], ), - private=LiquidNotFoundErrorInternalData( - position=DeckPoint(x=position.x, y=position.y, z=position.z) - ), + state_update=expected_state_update, ) else: assert result == SuccessData( @@ -276,17 +363,19 @@ async def test_liquid_not_found_error( z_position=None, position=DeckPoint(x=position.x, y=position.y, z=position.z), ), - private=None, + state_update=expected_state_update, ) async def test_liquid_probe_tip_checking( decoy: Decoy, - pipetting: PipettingHandler, + state_view: StateView, subject: EitherImplementation, params_type: EitherParamsType, + available_sensors: AvailableSensorDefinition, + supported_tip_fixture: SupportedTipsDefinition, ) -> None: - """It should return a TipNotAttached error if the hardware API indicates that.""" + """It should raise a TipNotAttached error if the state view indicates that.""" pipette_id = "pipette-id" labware_id = "labware-id" well_name = "well-name" @@ -300,21 +389,121 @@ async def test_liquid_probe_tip_checking( wellName=well_name, wellLocation=well_location, ) - decoy.when( - pipetting.get_is_ready_to_aspirate( - pipette_id=pipette_id, - ), - ).then_raise(TipNotAttachedError()) + state_view.pipettes.get_nozzle_configuration_supports_lld(pipette_id) + ).then_return(True) + decoy.when(state_view.pipettes.get_aspirated_volume(pipette_id)).then_raise( + TipNotAttachedError() + ) + decoy.when(state_view.pipettes.get_config("pipette-id")).then_return( + StaticPipetteConfig( + min_volume=1, + max_volume=9001, + channels=1, + model="blah", + display_name="bleh", + serial_number="", + tip_configuration_lookup_table={9001: supported_tip_fixture}, + nominal_tip_overlap={}, + home_position=0, + nozzle_offset_z=0, + bounding_nozzle_offsets=BoundingNozzlesOffsets( + back_left_offset=Point(x=10, y=20, z=30), + front_right_offset=Point(x=40, y=50, z=60), + ), + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P1000_96), + pipette_bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(x=10, y=20, z=30), + front_right_corner=Point(x=40, y=50, z=60), + front_left_corner=Point(x=10, y=50, z=60), + back_right_corner=Point(x=40, y=20, z=60), + ), + lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ) + ) with pytest.raises(TipNotAttachedError): await subject.execute(data) +async def test_liquid_probe_plunger_preparedness_checking( + decoy: Decoy, + state_view: StateView, + subject: EitherImplementation, + params_type: EitherParamsType, + available_sensors: AvailableSensorDefinition, + supported_tip_fixture: SupportedTipsDefinition, +) -> None: + """It should raise a PipetteNotReadyToAspirate error if the state view indicates that.""" + pipette_id = "pipette-id" + labware_id = "labware-id" + well_name = "well-name" + well_location = WellLocation( + origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1) + ) + + data = params_type( + pipetteId=pipette_id, + labwareId=labware_id, + wellName=well_name, + wellLocation=well_location, + ) + decoy.when( + state_view.pipettes.get_nozzle_configuration_supports_lld(pipette_id) + ).then_return(True) + decoy.when(state_view.pipettes.get_config("pipette-id")).then_return( + StaticPipetteConfig( + min_volume=1, + max_volume=9001, + channels=1, + model="blah", + display_name="bleh", + serial_number="", + tip_configuration_lookup_table={9001: supported_tip_fixture}, + nominal_tip_overlap={}, + home_position=0, + nozzle_offset_z=0, + bounding_nozzle_offsets=BoundingNozzlesOffsets( + back_left_offset=Point(x=10, y=20, z=30), + front_right_offset=Point(x=40, y=50, z=60), + ), + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P1000_96), + pipette_bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(x=10, y=20, z=30), + front_right_corner=Point(x=40, y=50, z=60), + front_left_corner=Point(x=10, y=50, z=60), + back_right_corner=Point(x=40, y=20, z=60), + ), + lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ) + ) + decoy.when(state_view.pipettes.get_aspirated_volume(pipette_id)).then_return(None) + with pytest.raises(PipetteNotReadyToAspirateError): + await subject.execute(data) + + async def test_liquid_probe_volume_checking( decoy: Decoy, - pipetting: PipettingHandler, + state_view: StateView, subject: EitherImplementation, params_type: EitherParamsType, + available_sensors: AvailableSensorDefinition, + supported_tip_fixture: SupportedTipsDefinition, ) -> None: """It should return a TipNotEmptyError if the hardware API indicates that.""" pipette_id = "pipette-id" @@ -330,18 +519,67 @@ async def test_liquid_probe_volume_checking( wellName=well_name, wellLocation=well_location, ) + decoy.when( - pipetting.get_is_empty(pipette_id=pipette_id), - ).then_return(False) + state_view.pipettes.get_aspirated_volume(pipette_id=pipette_id), + ).then_return(123) + decoy.when(state_view.pipettes.get_config("pipette-id")).then_return( + StaticPipetteConfig( + min_volume=1, + max_volume=9001, + channels=1, + model="blah", + display_name="bleh", + serial_number="", + tip_configuration_lookup_table={9001: supported_tip_fixture}, + nominal_tip_overlap={}, + home_position=0, + nozzle_offset_z=0, + bounding_nozzle_offsets=BoundingNozzlesOffsets( + back_left_offset=Point(x=10, y=20, z=30), + front_right_offset=Point(x=40, y=50, z=60), + ), + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P1000_96), + pipette_bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(x=10, y=20, z=30), + front_right_corner=Point(x=40, y=50, z=60), + front_left_corner=Point(x=10, y=50, z=60), + back_right_corner=Point(x=40, y=20, z=60), + ), + lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ) + ) + decoy.when( + state_view.pipettes.get_nozzle_configuration_supports_lld(pipette_id) + ).then_return(True) + with pytest.raises(TipNotEmptyError): await subject.execute(data) + decoy.when( + state_view.pipettes.get_aspirated_volume(pipette_id=pipette_id), + ).then_return(None) + + with pytest.raises(PipetteNotReadyToAspirateError): + await subject.execute(data) + async def test_liquid_probe_location_checking( decoy: Decoy, + state_view: StateView, movement: MovementHandler, subject: EitherImplementation, params_type: EitherParamsType, + available_sensors: AvailableSensorDefinition, + supported_tip_fixture: SupportedTipsDefinition, ) -> None: """It should return a PositionUnkownError if the hardware API indicates that.""" pipette_id = "pipette-id" @@ -357,10 +595,139 @@ async def test_liquid_probe_location_checking( wellName=well_name, wellLocation=well_location, ) + decoy.when(state_view.pipettes.get_aspirated_volume(pipette_id)).then_return(0) + decoy.when(state_view.pipettes.get_config("pipette-id")).then_return( + StaticPipetteConfig( + min_volume=1, + max_volume=9001, + channels=1, + model="blah", + display_name="bleh", + serial_number="", + tip_configuration_lookup_table={9001: supported_tip_fixture}, + nominal_tip_overlap={}, + home_position=0, + nozzle_offset_z=0, + bounding_nozzle_offsets=BoundingNozzlesOffsets( + back_left_offset=Point(x=10, y=20, z=30), + front_right_offset=Point(x=40, y=50, z=60), + ), + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P1000_96), + pipette_bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(x=10, y=20, z=30), + front_right_corner=Point(x=40, y=50, z=60), + front_left_corner=Point(x=10, y=50, z=60), + back_right_corner=Point(x=40, y=20, z=60), + ), + lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ) + ) decoy.when( await movement.check_for_valid_position( mount=MountType.LEFT, ), ).then_return(False) + decoy.when( + state_view.pipettes.get_nozzle_configuration_supports_lld(pipette_id) + ).then_return(True) with pytest.raises(MustHomeError): await subject.execute(data) + + +async def test_liquid_probe_stall( + decoy: Decoy, + movement: MovementHandler, + state_view: StateView, + pipetting: PipettingHandler, + subject: EitherImplementation, + params_type: EitherParamsType, + model_utils: ModelUtils, + available_sensors: AvailableSensorDefinition, + supported_tip_fixture: SupportedTipsDefinition, +) -> None: + """It should move to the destination and do a liquid probe there.""" + location = WellLocation(origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=1)) + + data = params_type( + pipetteId="abc", + labwareId="123", + wellName="A3", + wellLocation=location, + ) + + decoy.when(state_view.pipettes.get_aspirated_volume(pipette_id="abc")).then_return( + 0 + ) + decoy.when(state_view.pipettes.get_config("abc")).then_return( + StaticPipetteConfig( + min_volume=1, + max_volume=9001, + channels=1, + model="blah", + display_name="bleh", + serial_number="", + tip_configuration_lookup_table={9001: supported_tip_fixture}, + nominal_tip_overlap={}, + home_position=0, + nozzle_offset_z=0, + bounding_nozzle_offsets=BoundingNozzlesOffsets( + back_left_offset=Point(x=10, y=20, z=30), + front_right_offset=Point(x=40, y=50, z=60), + ), + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P1000_96), + pipette_bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(x=10, y=20, z=30), + front_right_corner=Point(x=40, y=50, z=60), + front_left_corner=Point(x=10, y=50, z=60), + back_right_corner=Point(x=40, y=20, z=60), + ), + lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ) + ) + decoy.when( + state_view.pipettes.get_nozzle_configuration_supports_lld("abc") + ).then_return(True) + + decoy.when( + await movement.move_to_well( + pipette_id="abc", + labware_id="123", + well_name="A3", + well_location=location, + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, + ), + ).then_raise(StallOrCollisionDetectedError()) + + error_id = "error-id" + timestamp = datetime(year=2020, month=1, day=2) + decoy.when(model_utils.get_timestamp()).then_return(timestamp) + decoy.when(model_utils.generate_id()).then_return(error_id) + + result = await subject.execute(data) + + assert result == DefinedErrorData( + public=StallOrCollisionError.construct( + id=error_id, createdAt=timestamp, wrappedErrors=[matchers.Anything()] + ), + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), + ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_load_labware.py b/api/tests/opentrons/protocol_engine/commands/test_load_labware.py index 867e8555386..3873f9854b4 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_load_labware.py +++ b/api/tests/opentrons/protocol_engine/commands/test_load_labware.py @@ -1,5 +1,10 @@ """Test load labware commands.""" import inspect +from typing import Optional +from opentrons.protocol_engine.state.update_types import ( + LoadedLabwareUpdate, + StateUpdate, +) import pytest from decoy import Decoy @@ -18,7 +23,7 @@ ) from opentrons.protocol_engine.execution import LoadedLabwareData, EquipmentHandler from opentrons.protocol_engine.resources import labware_validation -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.commands.command import SuccessData from opentrons.protocol_engine.commands.load_labware import ( @@ -32,16 +37,18 @@ def patch_mock_labware_validation( decoy: Decoy, monkeypatch: pytest.MonkeyPatch ) -> None: - """Mock out move_types.py functions.""" + """Mock out labware_validations.py functions.""" for name, func in inspect.getmembers(labware_validation, inspect.isfunction): monkeypatch.setattr(labware_validation, name, decoy.mock(func=func)) +@pytest.mark.parametrize("display_name", [("My custom display name"), (None)]) async def test_load_labware_implementation( decoy: Decoy, well_plate_def: LabwareDefinition, equipment: EquipmentHandler, state_view: StateView, + display_name: Optional[str], ) -> None: """A LoadLabware command should have an execution implementation.""" subject = LoadLabwareImplementation(equipment=equipment, state_view=state_view) @@ -51,7 +58,7 @@ async def test_load_labware_implementation( loadName="some-load-name", namespace="opentrons-test", version=1, - displayName="My custom display name", + displayName=display_name, ) decoy.when( @@ -87,7 +94,15 @@ async def test_load_labware_implementation( definition=well_plate_def, offsetId="labware-offset-id", ), - private=None, + state_update=StateUpdate( + loaded_labware=LoadedLabwareUpdate( + labware_id="labware-id", + definition=well_plate_def, + offset_id="labware-offset-id", + new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), + display_name=display_name, + ) + ), ) @@ -156,14 +171,21 @@ async def test_load_labware_on_labware( ).then_return(True) result = await subject.execute(data) - assert result == SuccessData( public=LoadLabwareResult( labwareId="labware-id", definition=well_plate_def, offsetId="labware-offset-id", ), - private=None, + state_update=StateUpdate( + loaded_labware=LoadedLabwareUpdate( + labware_id="labware-id", + definition=well_plate_def, + offset_id="labware-offset-id", + new_location=OnLabwareLocation(labwareId="another-labware-id"), + display_name="My custom display name", + ) + ), ) decoy.verify( diff --git a/api/tests/opentrons/protocol_engine/commands/test_load_liquid.py b/api/tests/opentrons/protocol_engine/commands/test_load_liquid.py index f1f998b85e7..6bd61061f3c 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_load_liquid.py +++ b/api/tests/opentrons/protocol_engine/commands/test_load_liquid.py @@ -1,6 +1,7 @@ """Test load-liquid command.""" import pytest from decoy import Decoy +from datetime import datetime from opentrons.protocol_engine.commands.command import SuccessData from opentrons.protocol_engine.commands import ( @@ -8,7 +9,10 @@ LoadLiquidImplementation, LoadLiquidParams, ) -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.errors import InvalidLiquidError +from opentrons.protocol_engine.state.state import StateView +from opentrons.protocol_engine.resources.model_utils import ModelUtils +from opentrons.protocol_engine.state import update_types @pytest.fixture @@ -18,15 +22,18 @@ def mock_state_view(decoy: Decoy) -> StateView: @pytest.fixture -def subject(mock_state_view: StateView) -> LoadLiquidImplementation: +def subject( + mock_state_view: StateView, model_utils: ModelUtils +) -> LoadLiquidImplementation: """Load liquid implementation test subject.""" - return LoadLiquidImplementation(state_view=mock_state_view) + return LoadLiquidImplementation(state_view=mock_state_view, model_utils=model_utils) async def test_load_liquid_implementation( decoy: Decoy, subject: LoadLiquidImplementation, mock_state_view: StateView, + model_utils: ModelUtils, ) -> None: """Test LoadLiquid command execution.""" data = LoadLiquidParams( @@ -34,9 +41,22 @@ async def test_load_liquid_implementation( liquidId="liquid-id", volumeByWell={"A1": 30, "B2": 100}, ) + + timestamp = datetime(year=2020, month=1, day=2) + decoy.when(model_utils.get_timestamp()).then_return(timestamp) + result = await subject.execute(data) - assert result == SuccessData(public=LoadLiquidResult(), private=None) + assert result == SuccessData( + public=LoadLiquidResult(), + state_update=update_types.StateUpdate( + liquid_loaded=update_types.LiquidLoadedUpdate( + labware_id="labware-id", + volumes={"A1": 30, "B2": 100}, + last_loaded=timestamp, + ) + ), + ) decoy.verify(mock_state_view.liquid.validate_liquid_id("liquid-id")) @@ -45,3 +65,37 @@ async def test_load_liquid_implementation( "labware-id", {"A1": 30.0, "B2": 100.0} ) ) + + +async def test_load_empty_liquid_requires_zero_volume( + decoy: Decoy, + subject: LoadLiquidImplementation, + mock_state_view: StateView, + model_utils: ModelUtils, +) -> None: + """Test that loadLiquid requires empty liquids to have 0 volume.""" + data = LoadLiquidParams( + labwareId="labware-id", liquidId="EMPTY", volumeByWell={"A1": 1.0} + ) + timestamp = datetime(year=2020, month=1, day=2) + decoy.when(model_utils.get_timestamp()).then_return(timestamp) + + with pytest.raises(InvalidLiquidError): + await subject.execute(data) + + decoy.verify(mock_state_view.liquid.validate_liquid_id("EMPTY")) + + data2 = LoadLiquidParams( + labwareId="labware-id", liquidId="EMPTY", volumeByWell={"A1": 0.0} + ) + result = await subject.execute(data2) + assert result == SuccessData( + public=LoadLiquidResult(), + state_update=update_types.StateUpdate( + liquid_loaded=update_types.LiquidLoadedUpdate( + labware_id="labware-id", + volumes=data2.volumeByWell, + last_loaded=timestamp, + ) + ), + ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_load_liquid_class.py b/api/tests/opentrons/protocol_engine/commands/test_load_liquid_class.py new file mode 100644 index 00000000000..041a7b2f8ca --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/test_load_liquid_class.py @@ -0,0 +1,161 @@ +"""Test load-liquid command.""" +from decoy import Decoy +import pytest + +from opentrons_shared_data.liquid_classes.liquid_class_definition import ( + LiquidClassSchemaV1, +) +from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.load_liquid_class import ( + LoadLiquidClassImplementation, + LoadLiquidClassParams, + LoadLiquidClassResult, +) +from opentrons.protocol_engine.errors import ( + LiquidClassDoesNotExistError, + LiquidClassRedefinitionError, +) +from opentrons.protocol_engine.resources.model_utils import ModelUtils +from opentrons.protocol_engine.state import update_types +from opentrons.protocol_engine.state.state import StateView +from opentrons.protocol_engine.types import LiquidClassRecord + + +@pytest.fixture +def liquid_class_record( + minimal_liquid_class_def2: LiquidClassSchemaV1, +) -> LiquidClassRecord: + """A dummy LiquidClassRecord for testing.""" + pipette_0 = minimal_liquid_class_def2.byPipette[0] + by_tip_type_0 = pipette_0.byTipType[0] + return LiquidClassRecord( + liquidClassName=minimal_liquid_class_def2.liquidClassName, + pipetteModel=pipette_0.pipetteModel, + tiprack=by_tip_type_0.tiprack, + aspirate=by_tip_type_0.aspirate, + singleDispense=by_tip_type_0.singleDispense, + multiDispense=by_tip_type_0.multiDispense, + ) + + +async def test_load_liquid_class_new_liquid_class_no_id( + decoy: Decoy, + state_view: StateView, + model_utils: ModelUtils, + liquid_class_record: LiquidClassRecord, +) -> None: + """Load a new liquid class with no liquidClassId specified. Should assign a new ID.""" + subject = LoadLiquidClassImplementation( + state_view=state_view, model_utils=model_utils + ) + decoy.when(model_utils.generate_id()).then_return("new-generated-id") + + params = LoadLiquidClassParams(liquidClassRecord=liquid_class_record) + result = await subject.execute(params) + assert result == SuccessData( + public=LoadLiquidClassResult(liquidClassId="new-generated-id"), + state_update=update_types.StateUpdate( + liquid_class_loaded=update_types.LiquidClassLoadedUpdate( + liquid_class_id="new-generated-id", + liquid_class_record=liquid_class_record, + ) + ), + ) + + +async def test_load_liquid_class_existing_liquid_class_no_id( + decoy: Decoy, + state_view: StateView, + model_utils: ModelUtils, + liquid_class_record: LiquidClassRecord, +) -> None: + """Load an existing liquid class with no liquidClassId specified. Should find and reuse existing ID.""" + subject = LoadLiquidClassImplementation( + state_view=state_view, model_utils=model_utils + ) + decoy.when( + state_view.liquid_classes.get_id_for_liquid_class_record(liquid_class_record) + ).then_return("existing-id") + + params = LoadLiquidClassParams(liquidClassRecord=liquid_class_record) + result = await subject.execute(params) + assert result == SuccessData( + public=LoadLiquidClassResult(liquidClassId="existing-id"), + state_update=update_types.StateUpdate(), # no state change since liquid class already loaded + ) + + +async def test_load_liquid_class_new_liquid_class_specified_id( + decoy: Decoy, + state_view: StateView, + model_utils: ModelUtils, + liquid_class_record: LiquidClassRecord, +) -> None: + """Load a new liquid class with the liquidClassId specified. Should store the new liquid class.""" + subject = LoadLiquidClassImplementation( + state_view=state_view, model_utils=model_utils + ) + decoy.when(state_view.liquid_classes.get("liquid-class-1")).then_raise( + LiquidClassDoesNotExistError() + ) + + params = LoadLiquidClassParams( + liquidClassId="liquid-class-1", liquidClassRecord=liquid_class_record + ) + result = await subject.execute(params) + assert result == SuccessData( + public=LoadLiquidClassResult(liquidClassId="liquid-class-1"), + state_update=update_types.StateUpdate( + liquid_class_loaded=update_types.LiquidClassLoadedUpdate( + liquid_class_id="liquid-class-1", + liquid_class_record=liquid_class_record, + ) + ), + ) + + +async def test_load_liquid_class_existing_liquid_class_specified_id( + decoy: Decoy, + state_view: StateView, + model_utils: ModelUtils, + liquid_class_record: LiquidClassRecord, +) -> None: + """Load a liquid class with a liquidClassId that was already loaded before. Should be a no-op.""" + subject = LoadLiquidClassImplementation( + state_view=state_view, model_utils=model_utils + ) + decoy.when(state_view.liquid_classes.get("liquid-class-1")).then_return( + liquid_class_record + ) + + params = LoadLiquidClassParams( + liquidClassId="liquid-class-1", liquidClassRecord=liquid_class_record + ) + result = await subject.execute(params) + assert result == SuccessData( + public=LoadLiquidClassResult(liquidClassId="liquid-class-1"), + state_update=update_types.StateUpdate(), # no state change since liquid class already loaded + ) + + +async def test_load_liquid_class_conflicting_definition_for_id( + decoy: Decoy, + state_view: StateView, + model_utils: ModelUtils, + liquid_class_record: LiquidClassRecord, +) -> None: + """Should raise when we try to load a modified liquid class with the same liquidClassId.""" + subject = LoadLiquidClassImplementation( + state_view=state_view, model_utils=model_utils + ) + decoy.when(state_view.liquid_classes.get("liquid-class-1")).then_return( + liquid_class_record + ) + + new_liquid_class_record = liquid_class_record.copy(deep=True) + new_liquid_class_record.aspirate.offset.x += 123 # make it different + params = LoadLiquidClassParams( + liquidClassId="liquid-class-1", liquidClassRecord=new_liquid_class_record + ) + with pytest.raises(LiquidClassRedefinitionError): + await subject.execute(params) diff --git a/api/tests/opentrons/protocol_engine/commands/test_load_module.py b/api/tests/opentrons/protocol_engine/commands/test_load_module.py index 2dbd0e31e97..e5098b5dc49 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_load_module.py +++ b/api/tests/opentrons/protocol_engine/commands/test_load_module.py @@ -4,7 +4,7 @@ from decoy import Decoy from opentrons.protocol_engine.errors import LocationIsOccupiedError -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons_shared_data.robot.types import RobotType from opentrons.types import DeckSlotName from opentrons.protocol_engine.types import ( @@ -57,7 +57,7 @@ async def test_load_module_implementation( deck_def = load_deck(STANDARD_OT3_DECK, 5) - decoy.when(state_view.addressable_areas.state.deck_definition).then_return(deck_def) + decoy.when(state_view.labware.get_deck_definition()).then_return(deck_def) decoy.when( state_view.addressable_areas.get_cutout_id_by_deck_slot_name( DeckSlotName.SLOT_D1 @@ -92,7 +92,6 @@ async def test_load_module_implementation( model=ModuleModel.TEMPERATURE_MODULE_V2, definition=tempdeck_v2_def, ), - private=None, ) @@ -113,7 +112,7 @@ async def test_load_module_implementation_mag_block( deck_def = load_deck(STANDARD_OT3_DECK, 5) - decoy.when(state_view.addressable_areas.state.deck_definition).then_return(deck_def) + decoy.when(state_view.labware.get_deck_definition()).then_return(deck_def) decoy.when( state_view.addressable_areas.get_cutout_id_by_deck_slot_name( DeckSlotName.SLOT_D1 @@ -148,7 +147,6 @@ async def test_load_module_implementation_mag_block( model=ModuleModel.MAGNETIC_BLOCK_V1, definition=mag_block_v1_def, ), - private=None, ) @@ -169,7 +167,7 @@ async def test_load_module_implementation_abs_reader( deck_def = load_deck(STANDARD_OT3_DECK, 5) - decoy.when(state_view.addressable_areas.state.deck_definition).then_return(deck_def) + decoy.when(state_view.labware.get_deck_definition()).then_return(deck_def) decoy.when( state_view.addressable_areas.get_cutout_id_by_deck_slot_name( DeckSlotName.SLOT_D3 @@ -204,7 +202,6 @@ async def test_load_module_implementation_abs_reader( model=ModuleModel.ABSORBANCE_READER_V1, definition=abs_reader_v1_def, ), - private=None, ) @@ -224,7 +221,7 @@ async def test_load_module_raises_if_location_occupied( deck_def = load_deck(STANDARD_OT3_DECK, 5) - decoy.when(state_view.addressable_areas.state.deck_definition).then_return(deck_def) + decoy.when(state_view.labware.get_deck_definition()).then_return(deck_def) decoy.when( state_view.addressable_areas.get_cutout_id_by_deck_slot_name( DeckSlotName.SLOT_D1 @@ -306,9 +303,7 @@ async def test_load_module_raises_wrong_location( state_view.addressable_areas.get_slot_definition(slot_name.id) ).then_return(cast(SlotDefV3, {"compatibleModuleTypes": []})) else: - decoy.when(state_view.addressable_areas.state.deck_definition).then_return( - deck_def - ) + decoy.when(state_view.labware.get_deck_definition()).then_return(deck_def) decoy.when( state_view.addressable_areas.get_cutout_id_by_deck_slot_name(slot_name) ).then_return("cutout" + slot_name.value) @@ -364,9 +359,7 @@ async def test_load_module_raises_module_fixture_id_does_not_exist( state_view.addressable_areas.get_slot_definition(slot_name.id) ).then_return(cast(SlotDefV3, {"compatibleModuleTypes": []})) else: - decoy.when(state_view.addressable_areas.state.deck_definition).then_return( - deck_def - ) + decoy.when(state_view.labware.get_deck_definition()).then_return(deck_def) decoy.when( state_view.addressable_areas.get_cutout_id_by_deck_slot_name(slot_name) ).then_return("cutout" + slot_name.value) diff --git a/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py b/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py index 72721343478..a251c6aef1f 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py +++ b/api/tests/opentrons/protocol_engine/commands/test_load_pipette.py @@ -1,9 +1,16 @@ """Test load pipette commands.""" +from opentrons.protocol_engine.state.update_types import ( + LoadPipetteUpdate, + PipetteConfigUpdate, + StateUpdate, + PipetteUnknownFluidUpdate, +) import pytest from decoy import Decoy from opentrons_shared_data.pipette.types import PipetteNameType from opentrons_shared_data.robot.types import RobotType +from opentrons_shared_data.pipette.pipette_definition import AvailableSensorDefinition from opentrons.types import MountType, Point from opentrons.protocol_engine.errors import InvalidSpecificationForRobotTypeError @@ -12,17 +19,22 @@ from opentrons.protocol_engine.resources.pipette_data_provider import ( LoadedStaticPipetteData, ) -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.commands.command import SuccessData from opentrons.protocol_engine.commands.load_pipette import ( LoadPipetteParams, LoadPipetteResult, - LoadPipettePrivateResult, LoadPipetteImplementation, ) from ..pipette_fixtures import get_default_nozzle_map +@pytest.fixture +def available_sensors() -> AvailableSensorDefinition: + """Provide a list of sensors.""" + return AvailableSensorDefinition(sensors=["pressure", "capacitive", "environment"]) + + @pytest.mark.parametrize( "data", [ @@ -44,6 +56,7 @@ async def test_load_pipette_implementation( equipment: EquipmentHandler, state_view: StateView, data: LoadPipetteParams, + available_sensors: AvailableSensorDefinition, ) -> None: """A LoadPipette command should have an execution implementation.""" subject = LoadPipetteImplementation(equipment=equipment, state_view=state_view) @@ -64,6 +77,14 @@ async def test_load_pipette_implementation( back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ) decoy.when( @@ -85,8 +106,19 @@ async def test_load_pipette_implementation( assert result == SuccessData( public=LoadPipetteResult(pipetteId="some id"), - private=LoadPipettePrivateResult( - pipette_id="some id", serial_number="some-serial-number", config=config_data + state_update=StateUpdate( + loaded_pipette=LoadPipetteUpdate( + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + pipette_id="some id", + liquid_presence_detection=None, + ), + pipette_config=PipetteConfigUpdate( + pipette_id="some id", + serial_number="some-serial-number", + config=config_data, + ), + pipette_aspirated_fluid=PipetteUnknownFluidUpdate(pipette_id="some id"), ), ) @@ -95,6 +127,7 @@ async def test_load_pipette_implementation_96_channel( decoy: Decoy, equipment: EquipmentHandler, state_view: StateView, + available_sensors: AvailableSensorDefinition, ) -> None: """A LoadPipette command should have an execution implementation.""" subject = LoadPipetteImplementation(equipment=equipment, state_view=state_view) @@ -121,6 +154,14 @@ async def test_load_pipette_implementation_96_channel( back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ) decoy.when( @@ -140,8 +181,19 @@ async def test_load_pipette_implementation_96_channel( assert result == SuccessData( public=LoadPipetteResult(pipetteId="pipette-id"), - private=LoadPipettePrivateResult( - pipette_id="pipette-id", serial_number="some id", config=config_data + state_update=StateUpdate( + loaded_pipette=LoadPipetteUpdate( + pipette_name=PipetteNameType.P1000_96, + mount=MountType.LEFT, + pipette_id="pipette-id", + liquid_presence_detection=None, + ), + pipette_config=PipetteConfigUpdate( + pipette_id="pipette-id", + serial_number="some id", + config=config_data, + ), + pipette_aspirated_fluid=PipetteUnknownFluidUpdate(pipette_id="pipette-id"), ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_move_labware.py b/api/tests/opentrons/protocol_engine/commands/test_move_labware.py index 0872525faf0..a946eccf05d 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_move_labware.py +++ b/api/tests/opentrons/protocol_engine/commands/test_move_labware.py @@ -1,16 +1,26 @@ """Test the ``moveLabware`` command.""" +from datetime import datetime import inspect import pytest -from decoy import Decoy +from decoy import Decoy, matchers +from opentrons_shared_data.errors.exceptions import ( + EnumeratedError, + FailedGripperPickupError, + LabwareDroppedError, + StallOrCollisionDetectedError, +) from opentrons_shared_data.labware.labware_definition import Parameters, Dimensions from opentrons_shared_data.gripper.constants import GRIPPER_PADDLE_WIDTH +from opentrons.protocol_engine.state import update_types from opentrons.types import DeckSlotName, Point from opentrons.protocols.models import LabwareDefinition from opentrons.protocol_engine import errors, Config from opentrons.protocol_engine.resources import labware_validation +from opentrons.protocol_engine.resources.model_utils import ModelUtils from opentrons.protocol_engine.types import ( + CurrentWell, DeckSlotLocation, ModuleLocation, OnLabwareLocation, @@ -21,9 +31,10 @@ DeckType, AddressableAreaLocation, ) -from opentrons.protocol_engine.state import StateView -from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.state.state import StateView +from opentrons.protocol_engine.commands.command import DefinedErrorData, SuccessData from opentrons.protocol_engine.commands.move_labware import ( + GripperMovementError, MoveLabwareParams, MoveLabwareResult, MoveLabwareImplementation, @@ -39,11 +50,29 @@ def patch_mock_labware_validation( decoy: Decoy, monkeypatch: pytest.MonkeyPatch ) -> None: - """Mock out move_types.py functions.""" + """Mock out labware_validation.py functions.""" for name, func in inspect.getmembers(labware_validation, inspect.isfunction): monkeypatch.setattr(labware_validation, name, decoy.mock(func=func)) +@pytest.fixture +def subject( + equipment: EquipmentHandler, + labware_movement: LabwareMovementHandler, + state_view: StateView, + run_control: RunControlHandler, + model_utils: ModelUtils, +) -> MoveLabwareImplementation: + """Return a test subject configured to use mocked-out dependencies.""" + return MoveLabwareImplementation( + state_view=state_view, + equipment=equipment, + labware_movement=labware_movement, + run_control=run_control, + model_utils=model_utils, + ) + + @pytest.mark.parametrize( argnames=["strategy", "times_pause_called"], argvalues=[ @@ -53,21 +82,14 @@ def patch_mock_labware_validation( ) async def test_manual_move_labware_implementation( decoy: Decoy, + subject: MoveLabwareImplementation, equipment: EquipmentHandler, - labware_movement: LabwareMovementHandler, state_view: StateView, run_control: RunControlHandler, strategy: LabwareMovementStrategy, times_pause_called: int, ) -> None: """It should execute a pause and return the new offset.""" - subject = MoveLabwareImplementation( - state_view=state_view, - equipment=equipment, - labware_movement=labware_movement, - run_control=run_control, - ) - data = MoveLabwareParams( labwareId="my-cool-labware-id", newLocation=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), @@ -104,25 +126,24 @@ async def test_manual_move_labware_implementation( public=MoveLabwareResult( offsetId="wowzers-a-new-offset-id", ), - private=None, + state_update=update_types.StateUpdate( + labware_location=update_types.LabwareLocationUpdate( + labware_id="my-cool-labware-id", + offset_id="wowzers-a-new-offset-id", + new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_5), + ) + ), ) async def test_move_labware_implementation_on_labware( decoy: Decoy, + subject: MoveLabwareImplementation, equipment: EquipmentHandler, - labware_movement: LabwareMovementHandler, state_view: StateView, run_control: RunControlHandler, ) -> None: """It should execute a pause and return the new offset.""" - subject = MoveLabwareImplementation( - state_view=state_view, - equipment=equipment, - labware_movement=labware_movement, - run_control=run_control, - ) - data = MoveLabwareParams( labwareId="my-cool-labware-id", newLocation=OnLabwareLocation(labwareId="new-labware-id"), @@ -170,24 +191,24 @@ async def test_move_labware_implementation_on_labware( public=MoveLabwareResult( offsetId="wowzers-a-new-offset-id", ), - private=None, + state_update=update_types.StateUpdate( + labware_location=update_types.LabwareLocationUpdate( + labware_id="my-cool-labware-id", + offset_id="wowzers-a-new-offset-id", + new_location=OnLabwareLocation(labwareId="my-even-cooler-labware-id"), + ) + ), ) async def test_gripper_move_labware_implementation( decoy: Decoy, + subject: MoveLabwareImplementation, equipment: EquipmentHandler, labware_movement: LabwareMovementHandler, state_view: StateView, - run_control: RunControlHandler, ) -> None: """It should delegate to the equipment handler and return the new offset.""" - subject = MoveLabwareImplementation( - state_view=state_view, - equipment=equipment, - labware_movement=labware_movement, - run_control=run_control, - ) from_location = DeckSlotLocation(slotName=DeckSlotName.SLOT_1) new_location = DeckSlotLocation(slotName=DeckSlotName.SLOT_5) @@ -257,24 +278,169 @@ async def test_gripper_move_labware_implementation( public=MoveLabwareResult( offsetId="wowzers-a-new-offset-id", ), - private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.CLEAR, + labware_location=update_types.LabwareLocationUpdate( + labware_id="my-cool-labware-id", + new_location=new_location, + offset_id="wowzers-a-new-offset-id", + ), + ), + ) + + +@pytest.mark.parametrize( + "underlying_exception", + [ + FailedGripperPickupError(), + LabwareDroppedError(), + StallOrCollisionDetectedError(), + ], +) +async def test_gripper_error( + decoy: Decoy, + subject: MoveLabwareImplementation, + state_view: StateView, + model_utils: ModelUtils, + labware_movement: LabwareMovementHandler, + underlying_exception: EnumeratedError, +) -> None: + """Test the handling of errors during a gripper movement.""" + labware_id = "labware-id" + labware_namespace = "labware-namespace" + labware_load_name = "load-name" + labware_definition_uri = "opentrons-test/load-name/1" + labware_def = LabwareDefinition.construct( # type: ignore[call-arg] + namespace=labware_namespace, + ) + original_location = DeckSlotLocation(slotName=DeckSlotName.SLOT_A1) + new_location = DeckSlotLocation(slotName=DeckSlotName.SLOT_A2) + error_id = "error-id" + error_created_at = datetime.now() + + # Common MoveLabwareImplementation boilerplate: + decoy.when(state_view.labware.get_definition(labware_id=labware_id)).then_return( + LabwareDefinition.construct(namespace=labware_namespace) # type: ignore[call-arg] + ) + decoy.when(state_view.labware.get(labware_id=labware_id)).then_return( + LoadedLabware( + id=labware_id, + loadName=labware_load_name, + definitionUri=labware_definition_uri, + location=original_location, + offsetId=None, + ) + ) + decoy.when( + state_view.geometry.ensure_valid_gripper_location(original_location) + ).then_return(original_location) + decoy.when( + state_view.geometry.ensure_valid_gripper_location(new_location) + ).then_return(new_location) + decoy.when( + state_view.geometry.ensure_location_not_occupied( + location=new_location, + ) + ).then_return(new_location) + decoy.when(labware_validation.validate_gripper_compatible(labware_def)).then_return( + True + ) + params = MoveLabwareParams( + labwareId=labware_id, + newLocation=new_location, + strategy=LabwareMovementStrategy.USING_GRIPPER, + ) + + # Actual setup for this test: + decoy.when( + await labware_movement.move_labware_with_gripper( + labware_id=labware_id, + current_location=original_location, + new_location=new_location, + user_offset_data=LabwareMovementOffsetData( + pickUpOffset=LabwareOffsetVector(x=0, y=0, z=0), + dropOffset=LabwareOffsetVector(x=0, y=0, z=0), + ), + post_drop_slide_offset=None, + ) + ).then_raise(underlying_exception) + decoy.when(model_utils.get_timestamp()).then_return(error_created_at) + decoy.when(model_utils.generate_id()).then_return(error_id) + + result = await subject.execute(params) + + assert result == DefinedErrorData( + public=GripperMovementError.construct( + id=error_id, + createdAt=error_created_at, + errorCode=underlying_exception.code.value.code, + detail=underlying_exception.code.value.detail, + wrappedErrors=[matchers.Anything()], + ), + state_update=update_types.StateUpdate( + labware_location=update_types.NO_CHANGE, + pipette_location=update_types.CLEAR, + ), + ) + + +@pytest.mark.parametrize( + ("current_labware_id", "moved_labware_id", "expect_cleared_location"), + [ + ("lw1", "lw2", False), + ("lw1", "lw1", True), + ], +) +async def test_clears_location_if_current_labware_moved_from_under_pipette( + decoy: Decoy, + subject: MoveLabwareImplementation, + state_view: StateView, + current_labware_id: str, + moved_labware_id: str, + expect_cleared_location: bool, +) -> None: + """If it moves the labware that the pipette is currently over, it should clear the location.""" + from_location = DeckSlotLocation(slotName=DeckSlotName.SLOT_A1) + to_location = DeckSlotLocation(slotName=DeckSlotName.SLOT_A2) + + decoy.when(state_view.labware.get(labware_id=moved_labware_id)).then_return( + LoadedLabware( + id=moved_labware_id, + loadName="load-name", + definitionUri="opentrons-test/load-name/1", + location=from_location, + offsetId=None, + ) + ) + + decoy.when(state_view.pipettes.get_current_location()).then_return( + CurrentWell( + pipette_id="pipette-id", labware_id=current_labware_id, well_name="A1" + ) + ) + + result = await subject.execute( + params=MoveLabwareParams( + labwareId=moved_labware_id, + newLocation=to_location, + strategy=LabwareMovementStrategy.MANUAL_MOVE_WITHOUT_PAUSE, + ) + ) + assert ( + result.state_update.pipette_location == update_types.CLEAR + if expect_cleared_location + else update_types.NO_CHANGE ) async def test_gripper_move_to_waste_chute_implementation( decoy: Decoy, + subject: MoveLabwareImplementation, equipment: EquipmentHandler, labware_movement: LabwareMovementHandler, state_view: StateView, - run_control: RunControlHandler, ) -> None: """It should drop the labware with a delay added.""" - subject = MoveLabwareImplementation( - state_view=state_view, - equipment=equipment, - labware_movement=labware_movement, - run_control=run_control, - ) from_location = DeckSlotLocation(slotName=DeckSlotName.SLOT_1) new_location = AddressableAreaLocation(addressableAreaName="gripperWasteChute") labware_width = 50 @@ -347,24 +513,24 @@ async def test_gripper_move_to_waste_chute_implementation( public=MoveLabwareResult( offsetId="wowzers-a-new-offset-id", ), - private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.CLEAR, + labware_location=update_types.LabwareLocationUpdate( + labware_id="my-cool-labware-id", + new_location=new_location, + offset_id="wowzers-a-new-offset-id", + ), + ), ) async def test_move_labware_raises_for_labware_or_module_not_found( decoy: Decoy, + subject: MoveLabwareImplementation, equipment: EquipmentHandler, - labware_movement: LabwareMovementHandler, - run_control: RunControlHandler, state_view: StateView, ) -> None: """It should raise an error when specified labware/ module is not found.""" - subject = MoveLabwareImplementation( - state_view=state_view, - labware_movement=labware_movement, - equipment=equipment, - run_control=run_control, - ) move_non_existent_labware_params = MoveLabwareParams( labwareId="my-cool-labware-id", newLocation=DeckSlotLocation(slotName=DeckSlotName.SLOT_5), @@ -409,19 +575,12 @@ async def test_move_labware_raises_for_labware_or_module_not_found( async def test_move_labware_raises_if_movement_obstructed( decoy: Decoy, + subject: MoveLabwareImplementation, equipment: EquipmentHandler, labware_movement: LabwareMovementHandler, state_view: StateView, - run_control: RunControlHandler, ) -> None: """It should execute a pause and return the new offset.""" - subject = MoveLabwareImplementation( - state_view=state_view, - equipment=equipment, - labware_movement=labware_movement, - run_control=run_control, - ) - data = MoveLabwareParams( labwareId="my-cool-labware-id", newLocation=DeckSlotLocation(slotName=DeckSlotName.SLOT_5), @@ -461,18 +620,10 @@ async def test_move_labware_raises_if_movement_obstructed( async def test_move_labware_raises_when_location_occupied( decoy: Decoy, - equipment: EquipmentHandler, - labware_movement: LabwareMovementHandler, + subject: MoveLabwareImplementation, state_view: StateView, - run_control: RunControlHandler, ) -> None: """It should raise an error when trying to move labware to non-empty location.""" - subject = MoveLabwareImplementation( - state_view=state_view, - labware_movement=labware_movement, - equipment=equipment, - run_control=run_control, - ) move_labware_params = MoveLabwareParams( labwareId="my-cool-labware-id", newLocation=DeckSlotLocation(slotName=DeckSlotName.SLOT_5), @@ -499,19 +650,10 @@ async def test_move_labware_raises_when_location_occupied( async def test_move_labware_raises_when_moving_adapter_with_gripper( decoy: Decoy, - equipment: EquipmentHandler, - labware_movement: LabwareMovementHandler, + subject: MoveLabwareImplementation, state_view: StateView, - run_control: RunControlHandler, ) -> None: """It should raise an error when trying to move an adapter with a gripper.""" - subject = MoveLabwareImplementation( - state_view=state_view, - equipment=equipment, - labware_movement=labware_movement, - run_control=run_control, - ) - data = MoveLabwareParams( labwareId="my-cool-labware-id", newLocation=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), @@ -549,19 +691,10 @@ async def test_move_labware_raises_when_moving_adapter_with_gripper( async def test_move_labware_raises_when_moving_labware_with_gripper_incompatible_quirk( decoy: Decoy, - equipment: EquipmentHandler, - labware_movement: LabwareMovementHandler, + subject: MoveLabwareImplementation, state_view: StateView, - run_control: RunControlHandler, ) -> None: """It should raise an error when trying to move an adapter with a gripper.""" - subject = MoveLabwareImplementation( - state_view=state_view, - equipment=equipment, - labware_movement=labware_movement, - run_control=run_control, - ) - data = MoveLabwareParams( labwareId="my-cool-labware-id", newLocation=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), @@ -597,18 +730,10 @@ async def test_move_labware_raises_when_moving_labware_with_gripper_incompatible async def test_move_labware_with_gripper_raises_on_ot2( decoy: Decoy, - equipment: EquipmentHandler, - labware_movement: LabwareMovementHandler, + subject: MoveLabwareImplementation, state_view: StateView, - run_control: RunControlHandler, ) -> None: """It should raise an error when using a gripper with robot type of OT2.""" - subject = MoveLabwareImplementation( - state_view=state_view, - equipment=equipment, - labware_movement=labware_movement, - run_control=run_control, - ) data = MoveLabwareParams( labwareId="my-cool-labware-id", newLocation=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), @@ -638,19 +763,10 @@ async def test_move_labware_with_gripper_raises_on_ot2( async def test_move_labware_raises_when_moving_fixed_trash_labware( decoy: Decoy, - equipment: EquipmentHandler, - labware_movement: LabwareMovementHandler, + subject: MoveLabwareImplementation, state_view: StateView, - run_control: RunControlHandler, ) -> None: """It should raise an error when trying to move a fixed trash.""" - subject = MoveLabwareImplementation( - state_view=state_view, - equipment=equipment, - labware_movement=labware_movement, - run_control=run_control, - ) - data = MoveLabwareParams( labwareId="my-cool-labware-id", newLocation=DeckSlotLocation(slotName=DeckSlotName.FIXED_TRASH), @@ -683,3 +799,38 @@ async def test_move_labware_raises_when_moving_fixed_trash_labware( match="Cannot move fixed trash labware 'My cool labware'.", ): await subject.execute(data) + + +async def test_labware_raises_when_moved_onto_itself( + decoy: Decoy, + subject: MoveLabwareImplementation, + state_view: StateView, +) -> None: + """It should raise when the OnLabwareLocation has the same labware ID as the labware being moved.""" + data = MoveLabwareParams( + labwareId="the-same-labware-id", + newLocation=OnLabwareLocation(labwareId="a-cool-labware-id"), + strategy=LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE, + ) + + decoy.when(state_view.labware.get(labware_id="the-same-labware-id")).then_return( + LoadedLabware( + id="the-same-labware-id", + loadName="load-name", + definitionUri="opentrons-test/load-name/1", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), + offsetId=None, + ) + ) + + decoy.when( + state_view.geometry.ensure_location_not_occupied( + location=OnLabwareLocation(labwareId="a-cool-labware-id"), + ) + ).then_return(OnLabwareLocation(labwareId="the-same-labware-id")) + + with pytest.raises( + errors.LabwareMovementNotAllowedError, + match="Cannot move a labware onto itself.", + ): + await subject.execute(data) diff --git a/api/tests/opentrons/protocol_engine/commands/test_move_relative.py b/api/tests/opentrons/protocol_engine/commands/test_move_relative.py index f8f49956721..1e2d98ebf21 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_move_relative.py +++ b/api/tests/opentrons/protocol_engine/commands/test_move_relative.py @@ -1,24 +1,38 @@ """Test move relative commands.""" -from decoy import Decoy +from datetime import datetime +from decoy import Decoy, matchers +import pytest + +from opentrons_shared_data.errors.exceptions import StallOrCollisionDetectedError + +from opentrons.protocol_engine.state import update_types from opentrons.protocol_engine.types import DeckPoint, MovementAxis from opentrons.protocol_engine.execution import MovementHandler from opentrons.types import Point -from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.command import SuccessData, DefinedErrorData +from opentrons.protocol_engine.commands.movement_common import StallOrCollisionError from opentrons.protocol_engine.commands.move_relative import ( MoveRelativeParams, MoveRelativeResult, MoveRelativeImplementation, ) +from opentrons.protocol_engine.resources.model_utils import ModelUtils + + +@pytest.fixture +def subject( + movement: MovementHandler, model_utils: ModelUtils +) -> MoveRelativeImplementation: + """Build a MoveRelativeImplementation with injected dependencies.""" + return MoveRelativeImplementation(movement=movement, model_utils=model_utils) async def test_move_relative_implementation( - decoy: Decoy, - movement: MovementHandler, + decoy: Decoy, movement: MovementHandler, subject: MoveRelativeImplementation ) -> None: """A MoveRelative command should have an execution implementation.""" - subject = MoveRelativeImplementation(movement=movement) data = MoveRelativeParams( pipetteId="pipette-id", axis=MovementAxis.X, @@ -36,5 +50,43 @@ async def test_move_relative_implementation( result = await subject.execute(data) assert result == SuccessData( - public=MoveRelativeResult(position=DeckPoint(x=1, y=2, z=3)), private=None + public=MoveRelativeResult(position=DeckPoint(x=1, y=2, z=3)), + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.NO_CHANGE, + new_deck_point=DeckPoint(x=1, y=2, z=3), + ) + ), + ) + + +async def test_move_relative_stalls( + decoy: Decoy, + movement: MovementHandler, + model_utils: ModelUtils, + subject: MoveRelativeImplementation, +) -> None: + """A MoveRelative command should handle stalls.""" + data = MoveRelativeParams(pipetteId="pipette-id", axis=MovementAxis.Y, distance=40) + + decoy.when( + await movement.move_relative( + pipette_id="pipette-id", axis=MovementAxis.Y, distance=40 + ) + ).then_raise(StallOrCollisionDetectedError()) + + timestamp = datetime.now() + test_id = "test-id" + + decoy.when(model_utils.get_timestamp()).then_return(timestamp) + decoy.when(model_utils.generate_id()).then_return(test_id) + + result = await subject.execute(data) + + assert result == DefinedErrorData( + public=StallOrCollisionError.construct( + id=test_id, createdAt=timestamp, wrappedErrors=[matchers.Anything()] + ), + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_move_to_addressable_area.py b/api/tests/opentrons/protocol_engine/commands/test_move_to_addressable_area.py index 20d944b6f87..9f1470b95da 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_move_to_addressable_area.py +++ b/api/tests/opentrons/protocol_engine/commands/test_move_to_addressable_area.py @@ -1,29 +1,123 @@ """Test move to addressable area commands.""" -from decoy import Decoy +from datetime import datetime -from opentrons.protocol_engine import DeckPoint, AddressableOffsetVector +from decoy import Decoy, matchers +import pytest + +from opentrons_shared_data.errors.exceptions import StallOrCollisionDetectedError +from opentrons_shared_data.pipette.types import PipetteNameType +from opentrons.protocol_engine import DeckPoint, AddressableOffsetVector, LoadedPipette from opentrons.protocol_engine.execution import MovementHandler -from opentrons.protocol_engine.state import StateView -from opentrons.types import Point +from opentrons.protocol_engine.state import update_types +from opentrons.protocol_engine.state.state import StateView +from opentrons.types import Point, MountType -from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.command import SuccessData, DefinedErrorData from opentrons.protocol_engine.commands.move_to_addressable_area import ( MoveToAddressableAreaParams, MoveToAddressableAreaResult, MoveToAddressableAreaImplementation, ) +from opentrons.protocol_engine.commands.movement_common import StallOrCollisionError +from opentrons.protocol_engine.resources.model_utils import ModelUtils + +@pytest.fixture +def subject( + movement: MovementHandler, state_view: StateView, model_utils: ModelUtils +) -> MoveToAddressableAreaImplementation: + """Build an execution implementation with injected dependencies.""" + return MoveToAddressableAreaImplementation( + movement=movement, state_view=state_view, model_utils=model_utils + ) -async def test_move_to_addressable_area_implementation( + +@pytest.mark.parametrize( + "pipette_name", + ( + pipette_name + for pipette_name in PipetteNameType + if pipette_name + not in ( + PipetteNameType.P10_SINGLE, + PipetteNameType.P10_MULTI, + PipetteNameType.P50_MULTI, + PipetteNameType.P50_SINGLE, + PipetteNameType.P300_SINGLE, + PipetteNameType.P300_MULTI, + PipetteNameType.P1000_SINGLE, + ) + ), +) +async def test_move_to_addressable_area_implementation_non_gen1( decoy: Decoy, state_view: StateView, movement: MovementHandler, + pipette_name: PipetteNameType, + subject: MoveToAddressableAreaImplementation, ) -> None: """A MoveToAddressableArea command should have an execution implementation.""" - subject = MoveToAddressableAreaImplementation( - movement=movement, state_view=state_view + data = MoveToAddressableAreaParams( + pipetteId="abc", + addressableAreaName="123", + offset=AddressableOffsetVector(x=1, y=2, z=3), + forceDirect=True, + minimumZHeight=4.56, + speed=7.89, + stayAtHighestPossibleZ=True, ) + decoy.when(state_view.pipettes.get("abc")).then_return( + LoadedPipette(id="abc", pipetteName=pipette_name, mount=MountType.LEFT) + ) + decoy.when( + await movement.move_to_addressable_area( + pipette_id="abc", + addressable_area_name="123", + offset=AddressableOffsetVector(x=1, y=2, z=3), + force_direct=True, + minimum_z_height=4.56, + speed=7.89, + stay_at_highest_possible_z=True, + ignore_tip_configuration=True, + highest_possible_z_extra_offset=None, + ) + ).then_return(Point(x=9, y=8, z=7)) + + result = await subject.execute(data) + + assert result == SuccessData( + public=MoveToAddressableAreaResult(position=DeckPoint(x=9, y=8, z=7)), + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.AddressableArea(addressable_area_name="123"), + new_deck_point=DeckPoint(x=9, y=8, z=7), + ) + ), + ) + + +@pytest.mark.parametrize( + "pipette_name", + ( + PipetteNameType.P10_SINGLE, + PipetteNameType.P10_MULTI, + PipetteNameType.P50_MULTI, + PipetteNameType.P50_SINGLE, + PipetteNameType.P300_SINGLE, + PipetteNameType.P300_MULTI, + PipetteNameType.P1000_SINGLE, + ), +) +async def test_move_to_addressable_area_implementation_with_gen1( + decoy: Decoy, + state_view: StateView, + movement: MovementHandler, + pipette_name: PipetteNameType, + subject: MoveToAddressableAreaImplementation, +) -> None: + """A MoveToAddressableArea command should have an execution implementation.""" data = MoveToAddressableAreaParams( pipetteId="abc", addressableAreaName="123", @@ -34,6 +128,9 @@ async def test_move_to_addressable_area_implementation( stayAtHighestPossibleZ=True, ) + decoy.when(state_view.pipettes.get("abc")).then_return( + LoadedPipette(id="abc", pipetteName=pipette_name, mount=MountType.LEFT) + ) decoy.when( await movement.move_to_addressable_area( pipette_id="abc", @@ -43,6 +140,8 @@ async def test_move_to_addressable_area_implementation( minimum_z_height=4.56, speed=7.89, stay_at_highest_possible_z=True, + ignore_tip_configuration=True, + highest_possible_z_extra_offset=5.0, ) ).then_return(Point(x=9, y=8, z=7)) @@ -50,5 +149,62 @@ async def test_move_to_addressable_area_implementation( assert result == SuccessData( public=MoveToAddressableAreaResult(position=DeckPoint(x=9, y=8, z=7)), - private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.AddressableArea(addressable_area_name="123"), + new_deck_point=DeckPoint(x=9, y=8, z=7), + ) + ), + ) + + +async def test_move_to_addressable_area_implementation_handles_stalls( + decoy: Decoy, + state_view: StateView, + movement: MovementHandler, + model_utils: ModelUtils, + subject: MoveToAddressableAreaImplementation, +) -> None: + """A MoveToAddressableArea command should handle stalls.""" + data = MoveToAddressableAreaParams( + pipetteId="abc", + addressableAreaName="123", + offset=AddressableOffsetVector(x=1, y=2, z=3), + forceDirect=True, + minimumZHeight=4.56, + speed=7.89, + stayAtHighestPossibleZ=True, + ) + test_id = "test-id" + timestamp = datetime.now() + + decoy.when(state_view.pipettes.get("abc")).then_return( + LoadedPipette( + id="abc", pipetteName=PipetteNameType.P1000_SINGLE, mount=MountType.LEFT + ) + ) + decoy.when(model_utils.generate_id()).then_return(test_id) + decoy.when(model_utils.get_timestamp()).then_return(timestamp) + decoy.when( + await movement.move_to_addressable_area( + pipette_id="abc", + addressable_area_name="123", + offset=AddressableOffsetVector(x=1, y=2, z=3), + force_direct=True, + minimum_z_height=4.56, + speed=7.89, + stay_at_highest_possible_z=True, + ignore_tip_configuration=True, + highest_possible_z_extra_offset=5.0, + ) + ).then_raise(StallOrCollisionDetectedError()) + + result = await subject.execute(data) + + assert result == DefinedErrorData( + public=StallOrCollisionError.construct( + id=test_id, createdAt=timestamp, wrappedErrors=[matchers.Anything()] + ), + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_move_to_addressable_area_for_drop_tip.py b/api/tests/opentrons/protocol_engine/commands/test_move_to_addressable_area_for_drop_tip.py index 5576b662566..019ec6bec3f 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_move_to_addressable_area_for_drop_tip.py +++ b/api/tests/opentrons/protocol_engine/commands/test_move_to_addressable_area_for_drop_tip.py @@ -1,29 +1,44 @@ """Test move to addressable area for drop tip commands.""" -from decoy import Decoy +from datetime import datetime + +from decoy import Decoy, matchers +import pytest + +from opentrons_shared_data.errors.exceptions import StallOrCollisionDetectedError from opentrons.protocol_engine import DeckPoint, AddressableOffsetVector from opentrons.protocol_engine.execution import MovementHandler -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state import update_types +from opentrons.protocol_engine.state.state import StateView from opentrons.types import Point -from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.resources.model_utils import ModelUtils +from opentrons.protocol_engine.commands.command import SuccessData, DefinedErrorData from opentrons.protocol_engine.commands.move_to_addressable_area_for_drop_tip import ( MoveToAddressableAreaForDropTipParams, MoveToAddressableAreaForDropTipResult, MoveToAddressableAreaForDropTipImplementation, ) +from opentrons.protocol_engine.commands.movement_common import StallOrCollisionError + + +@pytest.fixture +def subject( + state_view: StateView, movement: MovementHandler, model_utils: ModelUtils +) -> MoveToAddressableAreaForDropTipImplementation: + """Get a command implementation with injected dependencies.""" + return MoveToAddressableAreaForDropTipImplementation( + state_view=state_view, movement=movement, model_utils=model_utils + ) async def test_move_to_addressable_area_for_drop_tip_implementation( decoy: Decoy, state_view: StateView, movement: MovementHandler, + subject: MoveToAddressableAreaForDropTipImplementation, ) -> None: """A MoveToAddressableAreaForDropTip command should have an execution implementation.""" - subject = MoveToAddressableAreaForDropTipImplementation( - movement=movement, state_view=state_view - ) - data = MoveToAddressableAreaForDropTipParams( pipetteId="abc", addressableAreaName="123", @@ -49,7 +64,9 @@ async def test_move_to_addressable_area_for_drop_tip_implementation( force_direct=True, minimum_z_height=4.56, speed=7.89, + stay_at_highest_possible_z=False, ignore_tip_configuration=False, + highest_possible_z_extra_offset=None, ) ).then_return(Point(x=9, y=8, z=7)) @@ -57,5 +74,64 @@ async def test_move_to_addressable_area_for_drop_tip_implementation( assert result == SuccessData( public=MoveToAddressableAreaForDropTipResult(position=DeckPoint(x=9, y=8, z=7)), - private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.AddressableArea(addressable_area_name="123"), + new_deck_point=DeckPoint(x=9, y=8, z=7), + ) + ), + ) + + +async def test_move_to_addressable_area_for_drop_tip_handles_stalls( + decoy: Decoy, + state_view: StateView, + movement: MovementHandler, + model_utils: ModelUtils, + subject: MoveToAddressableAreaForDropTipImplementation, +) -> None: + """A MoveToAddressableAreaForDropTip command should have an execution implementation.""" + data = MoveToAddressableAreaForDropTipParams( + pipetteId="abc", + addressableAreaName="123", + offset=AddressableOffsetVector(x=1, y=2, z=3), + forceDirect=True, + minimumZHeight=4.56, + speed=7.89, + alternateDropLocation=True, + ignoreTipConfiguration=False, + ) + + decoy.when( + state_view.geometry.get_next_tip_drop_location_for_addressable_area( + addressable_area_name="123", pipette_id="abc" + ) + ).then_return(AddressableOffsetVector(x=10, y=11, z=12)) + + decoy.when( + await movement.move_to_addressable_area( + pipette_id="abc", + addressable_area_name="123", + offset=AddressableOffsetVector(x=10, y=11, z=12), + force_direct=True, + minimum_z_height=4.56, + speed=7.89, + stay_at_highest_possible_z=False, + ignore_tip_configuration=False, + highest_possible_z_extra_offset=None, + ) + ).then_raise(StallOrCollisionDetectedError()) + timestamp = datetime.now() + test_id = "test-id" + decoy.when(model_utils.generate_id()).then_return(test_id) + decoy.when(model_utils.get_timestamp()).then_return(timestamp) + + result = await subject.execute(data) + + assert result == DefinedErrorData( + public=StallOrCollisionError.construct( + id=test_id, createdAt=timestamp, wrappedErrors=[matchers.Anything()] + ), + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_move_to_coordinates.py b/api/tests/opentrons/protocol_engine/commands/test_move_to_coordinates.py index c630c913480..85afb189988 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_move_to_coordinates.py +++ b/api/tests/opentrons/protocol_engine/commands/test_move_to_coordinates.py @@ -1,13 +1,19 @@ """Test move-to-coordinates commands.""" -from decoy import Decoy +from datetime import datetime + +import pytest +from decoy import Decoy, matchers + +from opentrons_shared_data.errors.exceptions import StallOrCollisionDetectedError -from opentrons.hardware_control import HardwareControlAPI from opentrons.protocol_engine.execution import MovementHandler -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state import update_types from opentrons.protocol_engine.types import DeckPoint +from opentrons.protocol_engine.resources.model_utils import ModelUtils from opentrons.types import Point -from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.movement_common import StallOrCollisionError +from opentrons.protocol_engine.commands.command import SuccessData, DefinedErrorData from opentrons.protocol_engine.commands.move_to_coordinates import ( MoveToCoordinatesParams, MoveToCoordinatesResult, @@ -15,26 +21,18 @@ ) +@pytest.fixture +def subject( + movement: MovementHandler, model_utils: ModelUtils +) -> MoveToCoordinatesImplementation: + """Build a command implementation with injected dependencies.""" + return MoveToCoordinatesImplementation(movement=movement, model_utils=model_utils) + + async def test_move_to_coordinates_implementation( - decoy: Decoy, - state_view: StateView, - hardware_api: HardwareControlAPI, - movement: MovementHandler, + decoy: Decoy, movement: MovementHandler, subject: MoveToCoordinatesImplementation ) -> None: - """Test the `moveToCoordinates` implementation. - - It should: - - 1. Query the hardware controller for the given pipette's current position - and how high it can go with its current tip. - 2. Plan the movement, taking the above into account, plus the input parameters. - 3. Iterate through the waypoints of the movement. - """ - subject = MoveToCoordinatesImplementation( - state_view=state_view, - movement=movement, - ) - + """Test the `moveToCoordinates` implementation.""" params = MoveToCoordinatesParams( pipetteId="pipette-id", coordinates=DeckPoint(x=1.11, y=2.22, z=3.33), @@ -57,5 +55,50 @@ async def test_move_to_coordinates_implementation( assert result == SuccessData( public=MoveToCoordinatesResult(position=DeckPoint(x=4.44, y=5.55, z=6.66)), - private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=None, + new_deck_point=DeckPoint(x=4.44, y=5.55, z=6.66), + ) + ), + ) + + +async def test_move_to_coordinates_stall( + decoy: Decoy, + movement: MovementHandler, + model_utils: ModelUtils, + subject: MoveToCoordinatesImplementation, +) -> None: + """It should handle stall errors.""" + params = MoveToCoordinatesParams( + pipetteId="pipette-id", + coordinates=DeckPoint(x=1.11, y=2.22, z=3.33), + minimumZHeight=1234, + forceDirect=True, + speed=567.8, + ) + + decoy.when( + await movement.move_to_coordinates( + pipette_id="pipette-id", + deck_coordinates=DeckPoint(x=1.11, y=2.22, z=3.33), + direct=True, + additional_min_travel_z=1234, + speed=567.8, + ) + ).then_raise(StallOrCollisionDetectedError()) + test_id = "test-id" + timestamp = datetime.now() + decoy.when(model_utils.get_timestamp()).then_return(timestamp) + decoy.when(model_utils.generate_id()).then_return(test_id) + + result = await subject.execute(params=params) + + assert result == DefinedErrorData( + public=StallOrCollisionError.construct( + id=test_id, createdAt=timestamp, wrappedErrors=[matchers.Anything()] + ), + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_move_to_well.py b/api/tests/opentrons/protocol_engine/commands/test_move_to_well.py index ddd6cf51a21..db91abd5a41 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_move_to_well.py +++ b/api/tests/opentrons/protocol_engine/commands/test_move_to_well.py @@ -1,24 +1,55 @@ """Test move to well commands.""" -from decoy import Decoy -from opentrons.protocol_engine import WellLocation, WellOffset, DeckPoint +from datetime import datetime + +import pytest +from decoy import Decoy, matchers + +from opentrons_shared_data.errors.exceptions import StallOrCollisionDetectedError + +from opentrons.protocol_engine import ( + WellLocation, + WellOffset, + DeckPoint, + errors, +) from opentrons.protocol_engine.execution import MovementHandler +from opentrons.protocol_engine.state import update_types from opentrons.types import Point -from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.command import SuccessData, DefinedErrorData from opentrons.protocol_engine.commands.move_to_well import ( MoveToWellParams, MoveToWellResult, MoveToWellImplementation, ) +from opentrons.protocol_engine.commands.movement_common import StallOrCollisionError +from opentrons.protocol_engine.state.state import StateView +from opentrons.protocol_engine.resources.model_utils import ModelUtils + + +@pytest.fixture +def mock_state_view(decoy: Decoy) -> StateView: + """Get a mock StateView.""" + return decoy.mock(cls=StateView) + + +@pytest.fixture +def mock_model_utils(decoy: Decoy) -> ModelUtils: + """Get a mock ModelUtils.""" + return decoy.mock(cls=ModelUtils) async def test_move_to_well_implementation( decoy: Decoy, + state_view: StateView, movement: MovementHandler, + mock_model_utils: ModelUtils, ) -> None: """A MoveToWell command should have an execution implementation.""" - subject = MoveToWellImplementation(movement=movement) + subject = MoveToWellImplementation( + state_view=state_view, movement=movement, model_utils=mock_model_utils + ) data = MoveToWellParams( pipetteId="abc", @@ -39,11 +70,96 @@ async def test_move_to_well_implementation( force_direct=True, minimum_z_height=4.56, speed=7.89, + current_well=None, + operation_volume=None, ) ).then_return(Point(x=9, y=8, z=7)) result = await subject.execute(data) assert result == SuccessData( - public=MoveToWellResult(position=DeckPoint(x=9, y=8, z=7)), private=None + public=MoveToWellResult(position=DeckPoint(x=9, y=8, z=7)), + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.Well(labware_id="123", well_name="A3"), + new_deck_point=DeckPoint(x=9, y=8, z=7), + ) + ), + ) + + +async def test_move_to_well_with_tip_rack_and_volume_offset( + decoy: Decoy, + mock_state_view: StateView, + movement: MovementHandler, + mock_model_utils: ModelUtils, +) -> None: + """It should disallow movement to a tip rack when volumeOffset is specified.""" + subject = MoveToWellImplementation( + state_view=mock_state_view, movement=movement, model_utils=mock_model_utils + ) + + data = MoveToWellParams( + pipetteId="abc", + labwareId="123", + wellName="A3", + wellLocation=WellLocation(offset=WellOffset(x=1, y=2, z=3), volumeOffset=-40.0), + forceDirect=True, + minimumZHeight=4.56, + speed=7.89, + ) + + decoy.when(mock_state_view.labware.is_tiprack("123")).then_return(True) + + with pytest.raises(errors.LabwareIsTipRackError): + await subject.execute(data) + + +async def test_move_to_well_stall_defined_error( + decoy: Decoy, + mock_state_view: StateView, + movement: MovementHandler, + mock_model_utils: ModelUtils, +) -> None: + """It should catch StallOrCollisionError exceptions and make them DefinedErrors.""" + error_id = "error-id" + error_timestamp = datetime(year=2020, month=1, day=2) + decoy.when( + await movement.move_to_well( + pipette_id="abc", + labware_id="123", + well_name="A3", + well_location=WellLocation(offset=WellOffset(x=1, y=2, z=3)), + force_direct=True, + minimum_z_height=4.56, + speed=7.89, + current_well=None, + operation_volume=None, + ) + ).then_raise(StallOrCollisionDetectedError()) + decoy.when(mock_model_utils.generate_id()).then_return(error_id) + decoy.when(mock_model_utils.get_timestamp()).then_return(error_timestamp) + + subject = MoveToWellImplementation( + state_view=mock_state_view, movement=movement, model_utils=mock_model_utils + ) + + data = MoveToWellParams( + pipetteId="abc", + labwareId="123", + wellName="A3", + wellLocation=WellLocation(offset=WellOffset(x=1, y=2, z=3)), + forceDirect=True, + minimumZHeight=4.56, + speed=7.89, + ) + + result = await subject.execute(data) + assert isinstance(result, DefinedErrorData) + assert result == DefinedErrorData( + public=StallOrCollisionError.construct( + id=error_id, createdAt=error_timestamp, wrappedErrors=[matchers.Anything()] + ), + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py b/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py index 1e24a8033f1..07170e08288 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py +++ b/api/tests/opentrons/protocol_engine/commands/test_pick_up_tip.py @@ -1,24 +1,35 @@ """Test pick up tip commands.""" + from datetime import datetime from decoy import Decoy, matchers +from unittest.mock import sentinel + + +from opentrons_shared_data.errors.exceptions import StallOrCollisionDetectedError from opentrons.types import MountType, Point -from opentrons.protocol_engine import WellLocation, WellOffset, DeckPoint -from opentrons.protocol_engine.errors import TipNotAttachedError +from opentrons.protocol_engine import ( + WellLocation, + PickUpTipWellLocation, + WellOffset, + DeckPoint, +) +from opentrons.protocol_engine.errors import PickUpTipTipNotAttachedError from opentrons.protocol_engine.execution import MovementHandler, TipHandler from opentrons.protocol_engine.resources import ModelUtils -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state import update_types +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.types import TipGeometry +from opentrons.protocol_engine.commands.movement_common import StallOrCollisionError from opentrons.protocol_engine.commands.command import DefinedErrorData, SuccessData from opentrons.protocol_engine.commands.pick_up_tip import ( PickUpTipParams, PickUpTipResult, PickUpTipImplementation, TipPhysicallyMissingError, - TipPhysicallyMissingErrorInternalData, ) @@ -39,12 +50,23 @@ async def test_success( decoy.when(state_view.pipettes.get_mount("pipette-id")).then_return(MountType.LEFT) + decoy.when( + state_view.geometry.convert_pick_up_tip_well_location( + well_location=PickUpTipWellLocation(offset=WellOffset(x=1, y=2, z=3)) + ) + ).then_return(WellLocation(offset=WellOffset(x=1, y=2, z=3))) + decoy.when( await movement.move_to_well( pipette_id="pipette-id", labware_id="labware-id", well_name="A3", well_location=WellLocation(offset=WellOffset(x=1, y=2, z=3)), + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, ) ).then_return(Point(x=111, y=222, z=333)) @@ -61,7 +83,7 @@ async def test_success( pipetteId="pipette-id", labwareId="labware-id", wellName="A3", - wellLocation=WellLocation(offset=WellOffset(x=1, y=2, z=3)), + wellLocation=PickUpTipWellLocation(offset=WellOffset(x=1, y=2, z=3)), ) ) @@ -72,7 +94,23 @@ async def test_success( tipDiameter=5, position=DeckPoint(x=111, y=222, z=333), ), - private=None, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.Well(labware_id="labware-id", well_name="A3"), + new_deck_point=DeckPoint(x=111, y=222, z=333), + ), + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="pipette-id", + tip_geometry=TipGeometry(length=42, diameter=5, volume=300), + ), + tips_used=update_types.TipsUsedUpdate( + pipette_id="pipette-id", labware_id="labware-id", well_name="A3" + ), + pipette_aspirated_fluid=update_types.PipetteEmptyFluidUpdate( + pipette_id="pipette-id" + ), + ), ) @@ -97,11 +135,30 @@ async def test_tip_physically_missing_error( error_id = "error-id" error_created_at = datetime(1234, 5, 6) + decoy.when( + state_view.geometry.convert_pick_up_tip_well_location( + well_location=PickUpTipWellLocation(offset=WellOffset()) + ) + ).then_return(WellLocation(offset=WellOffset())) + + decoy.when( + await movement.move_to_well( + pipette_id="pipette-id", + labware_id="labware-id", + well_name="well-name", + well_location=WellLocation(offset=WellOffset()), + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, + ) + ).then_return(Point(x=111, y=222, z=333)) decoy.when( await tip_handler.pick_up_tip( pipette_id=pipette_id, labware_id=labware_id, well_name=well_name ) - ).then_raise(TipNotAttachedError()) + ).then_raise(PickUpTipTipNotAttachedError(tip_geometry=sentinel.tip_geometry)) decoy.when(model_utils.generate_id()).then_return(error_id) decoy.when(model_utils.get_timestamp()).then_return(error_created_at) @@ -113,7 +170,95 @@ async def test_tip_physically_missing_error( public=TipPhysicallyMissingError.construct( id=error_id, createdAt=error_created_at, wrappedErrors=[matchers.Anything()] ), - private=TipPhysicallyMissingErrorInternalData( - pipette_id=pipette_id, labware_id=labware_id, well_name=well_name + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.Well( + labware_id="labware-id", well_name="well-name" + ), + new_deck_point=DeckPoint(x=111, y=222, z=333), + ), + tips_used=update_types.TipsUsedUpdate( + pipette_id="pipette-id", labware_id="labware-id", well_name="well-name" + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="pipette-id" + ), + ), + state_update_if_false_positive=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="pipette-id", tip_geometry=sentinel.tip_geometry + ), + pipette_aspirated_fluid=update_types.PipetteEmptyFluidUpdate( + pipette_id="pipette-id" + ), + tips_used=update_types.TipsUsedUpdate( + pipette_id="pipette-id", labware_id="labware-id", well_name="well-name" + ), + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.Well( + labware_id="labware-id", well_name="well-name" + ), + new_deck_point=DeckPoint(x=111, y=222, z=333), + ), + ), + ) + + +async def test_stall_error( + decoy: Decoy, + state_view: StateView, + movement: MovementHandler, + tip_handler: TipHandler, + model_utils: ModelUtils, +) -> None: + """It should return a TipPhysicallyMissingError if the HW API indicates that.""" + subject = PickUpTipImplementation( + state_view=state_view, + movement=movement, + tip_handler=tip_handler, + model_utils=model_utils, + ) + + pipette_id = "pipette-id" + labware_id = "labware-id" + well_name = "well-name" + error_id = "error-id" + error_created_at = datetime(1234, 5, 6) + + decoy.when( + state_view.geometry.convert_pick_up_tip_well_location( + well_location=PickUpTipWellLocation(offset=WellOffset()) + ) + ).then_return(WellLocation(offset=WellOffset())) + + decoy.when( + await movement.move_to_well( + pipette_id="pipette-id", + labware_id="labware-id", + well_name="well-name", + well_location=WellLocation(offset=WellOffset()), + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, + ) + ).then_raise(StallOrCollisionDetectedError()) + + decoy.when(model_utils.generate_id()).then_return(error_id) + decoy.when(model_utils.get_timestamp()).then_return(error_created_at) + + result = await subject.execute( + PickUpTipParams(pipetteId=pipette_id, labwareId=labware_id, wellName=well_name) + ) + + assert result == DefinedErrorData( + public=StallOrCollisionError.construct( + id=error_id, createdAt=error_created_at, wrappedErrors=[matchers.Anything()] + ), + state_update=update_types.StateUpdate( + pipette_location=update_types.CLEAR, ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py b/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py index b11254af481..f9eded1ffa0 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py +++ b/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py @@ -1,30 +1,105 @@ """Test prepare to aspirate commands.""" - -from decoy import Decoy +from datetime import datetime +from opentrons.types import Point +import pytest +from decoy import Decoy, matchers from opentrons.protocol_engine.execution import ( PipettingHandler, ) -from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.command import DefinedErrorData, SuccessData from opentrons.protocol_engine.commands.prepare_to_aspirate import ( PrepareToAspirateParams, PrepareToAspirateImplementation, PrepareToAspirateResult, ) +from opentrons.protocol_engine.execution.gantry_mover import GantryMover +from opentrons.protocol_engine.resources.model_utils import ModelUtils +from opentrons.protocol_engine.commands.pipetting_common import OverpressureError +from opentrons.protocol_engine.state import update_types +from opentrons_shared_data.errors.exceptions import PipetteOverpressureError + +@pytest.fixture +def subject( + pipetting: PipettingHandler, + model_utils: ModelUtils, + gantry_mover: GantryMover, +) -> PrepareToAspirateImplementation: + """Get the implementation subject.""" + return PrepareToAspirateImplementation( + pipetting=pipetting, model_utils=model_utils, gantry_mover=gantry_mover + ) -async def test_prepare_to_aspirate_implmenetation( - decoy: Decoy, pipetting: PipettingHandler + +async def test_prepare_to_aspirate_implementation( + decoy: Decoy, + gantry_mover: GantryMover, + subject: PrepareToAspirateImplementation, + pipetting: PipettingHandler, ) -> None: """A PrepareToAspirate command should have an executing implementation.""" - subject = PrepareToAspirateImplementation(pipetting=pipetting) - data = PrepareToAspirateParams(pipetteId="some id") + position = Point(x=1, y=2, z=3) decoy.when(await pipetting.prepare_for_aspirate(pipette_id="some id")).then_return( None ) + decoy.when(await gantry_mover.get_position("some id")).then_return(position) result = await subject.execute(data) - assert result == SuccessData(public=PrepareToAspirateResult(), private=None) + assert result == SuccessData( + public=PrepareToAspirateResult(), + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteEmptyFluidUpdate( + pipette_id="some id" + ) + ), + ) + + +async def test_overpressure_error( + decoy: Decoy, + gantry_mover: GantryMover, + pipetting: PipettingHandler, + subject: PrepareToAspirateImplementation, + model_utils: ModelUtils, +) -> None: + """It should return an overpressure error if the hardware API indicates that.""" + pipette_id = "pipette-id" + + position = Point(x=1, y=2, z=3) + + error_id = "error-id" + error_timestamp = datetime(year=2020, month=1, day=2) + + data = PrepareToAspirateParams( + pipetteId=pipette_id, + ) + + decoy.when( + await pipetting.prepare_for_aspirate( + pipette_id=pipette_id, + ), + ).then_raise(PipetteOverpressureError()) + + decoy.when(model_utils.generate_id()).then_return(error_id) + decoy.when(model_utils.get_timestamp()).then_return(error_timestamp) + decoy.when(await gantry_mover.get_position(pipette_id)).then_return(position) + + result = await subject.execute(data) + + assert result == DefinedErrorData( + public=OverpressureError.construct( + id=error_id, + createdAt=error_timestamp, + wrappedErrors=[matchers.Anything()], + errorInfo={"retryLocation": (position.x, position.y, position.z)}, + ), + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="pipette-id" + ) + ), + ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_reload_labware.py b/api/tests/opentrons/protocol_engine/commands/test_reload_labware.py index 8bafa40d47e..51779c427d7 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_reload_labware.py +++ b/api/tests/opentrons/protocol_engine/commands/test_reload_labware.py @@ -1,5 +1,9 @@ """Test load labware commands.""" import inspect +from opentrons.protocol_engine.state.update_types import ( + LabwareLocationUpdate, + StateUpdate, +) import pytest from decoy import Decoy @@ -16,7 +20,7 @@ ) from opentrons.protocol_engine.execution import ReloadedLabwareData, EquipmentHandler from opentrons.protocol_engine.resources import labware_validation -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.commands.command import SuccessData from opentrons.protocol_engine.commands.reload_labware import ( @@ -30,7 +34,7 @@ def patch_mock_labware_validation( decoy: Decoy, monkeypatch: pytest.MonkeyPatch ) -> None: - """Mock out move_types.py functions.""" + """Mock out labware_validation.py functions.""" for name, func in inspect.getmembers(labware_validation, inspect.isfunction): monkeypatch.setattr(labware_validation, name, decoy.mock(func=func)) @@ -62,7 +66,13 @@ async def test_reload_labware_implementation( labwareId="my-labware-id", offsetId="labware-offset-id", ), - private=None, + state_update=StateUpdate( + labware_location=LabwareLocationUpdate( + labware_id="my-labware-id", + new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), + offset_id="labware-offset-id", + ) + ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_retract_axis.py b/api/tests/opentrons/protocol_engine/commands/test_retract_axis.py index a580875d779..6dedf5b2f19 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_retract_axis.py +++ b/api/tests/opentrons/protocol_engine/commands/test_retract_axis.py @@ -1,6 +1,7 @@ """Test retractAxis command.""" from decoy import Decoy +from opentrons.protocol_engine.state import update_types from opentrons.protocol_engine.types import MotorAxis from opentrons.protocol_engine.execution import MovementHandler @@ -22,5 +23,8 @@ async def test_retract_axis_implementation( data = RetractAxisParams(axis=MotorAxis.Y) result = await subject.execute(data) - assert result == SuccessData(public=RetractAxisResult(), private=None) + assert result == SuccessData( + public=RetractAxisResult(), + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), + ) decoy.verify(await movement.retract_axis(axis=MotorAxis.Y)) diff --git a/api/tests/opentrons/protocol_engine/commands/test_save_position.py b/api/tests/opentrons/protocol_engine/commands/test_save_position.py index c0f5e091e30..bc6d8ed6668 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_save_position.py +++ b/api/tests/opentrons/protocol_engine/commands/test_save_position.py @@ -51,5 +51,4 @@ async def test_save_position_implementation( positionId="456", position=DeckPoint(x=1, y=2, z=3), ), - private=None, ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_set_rail_lights.py b/api/tests/opentrons/protocol_engine/commands/test_set_rail_lights.py index 161fb2d3fcf..956473f264f 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_set_rail_lights.py +++ b/api/tests/opentrons/protocol_engine/commands/test_set_rail_lights.py @@ -26,6 +26,6 @@ async def test_set_rail_lights_implementation( result = await subject.execute(data) - assert result == SuccessData(public=SetRailLightsResult(), private=None) + assert result == SuccessData(public=SetRailLightsResult()) decoy.verify(await rail_lights.set_rail_lights(True), times=1) diff --git a/api/tests/opentrons/protocol_engine/commands/test_set_status_bar.py b/api/tests/opentrons/protocol_engine/commands/test_set_status_bar.py index 53652ce6b87..41ae6703c61 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_set_status_bar.py +++ b/api/tests/opentrons/protocol_engine/commands/test_set_status_bar.py @@ -35,7 +35,7 @@ async def test_status_bar_busy( result = await subject.execute(params=data) - assert result == SuccessData(public=SetStatusBarResult(), private=None) + assert result == SuccessData(public=SetStatusBarResult()) decoy.verify(await status_bar.set_status_bar(status=StatusBarState.OFF), times=0) @@ -63,6 +63,6 @@ async def test_set_status_bar_animation( data = SetStatusBarParams(animation=animation) result = await subject.execute(params=data) - assert result == SuccessData(public=SetStatusBarResult(), private=None) + assert result == SuccessData(public=SetStatusBarResult()) decoy.verify(await status_bar.set_status_bar(status=expected_state), times=1) diff --git a/api/tests/opentrons/protocol_engine/commands/test_touch_tip.py b/api/tests/opentrons/protocol_engine/commands/test_touch_tip.py index 2f440c96f13..0d4071efd6c 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_touch_tip.py +++ b/api/tests/opentrons/protocol_engine/commands/test_touch_tip.py @@ -1,12 +1,15 @@ """Test touch tip commands.""" + import pytest from decoy import Decoy from opentrons.hardware_control.types import CriticalPoint from opentrons.motion_planning import Waypoint from opentrons.protocol_engine import WellLocation, WellOffset, DeckPoint, errors +from opentrons.protocol_engine.resources import ModelUtils from opentrons.protocol_engine.execution import MovementHandler, GantryMover -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state import update_types +from opentrons.protocol_engine.state.state import StateView from opentrons.types import Point from opentrons.protocol_engine.commands.command import SuccessData @@ -23,6 +26,12 @@ def mock_state_view(decoy: Decoy) -> StateView: return decoy.mock(cls=StateView) +@pytest.fixture +def mock_model_utils(decoy: Decoy) -> ModelUtils: + """Get a mock ModelUtils.""" + return decoy.mock(cls=ModelUtils) + + @pytest.fixture def mock_movement_handler(decoy: Decoy) -> MovementHandler: """Get a mock MovementHandler.""" @@ -40,12 +49,14 @@ def subject( mock_state_view: StateView, mock_movement_handler: MovementHandler, mock_gantry_mover: GantryMover, + mock_model_utils: ModelUtils, ) -> TouchTipImplementation: """Get the test subject.""" return TouchTipImplementation( state_view=mock_state_view, movement=mock_movement_handler, gantry_mover=mock_gantry_mover, + model_utils=mock_model_utils, ) @@ -72,6 +83,11 @@ async def test_touch_tip_implementation( labware_id="123", well_name="A3", well_location=WellLocation(offset=WellOffset(x=1, y=2, z=3)), + current_well=None, + force_direct=False, + minimum_z_height=None, + speed=None, + operation_volume=None, ) ).then_return(Point(x=1, y=2, z=3)) @@ -122,7 +138,14 @@ async def test_touch_tip_implementation( result = await subject.execute(params) assert result == SuccessData( - public=TouchTipResult(position=DeckPoint(x=4, y=5, z=6)), private=None + public=TouchTipResult(position=DeckPoint(x=4, y=5, z=6)), + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="abc", + new_location=update_types.Well(labware_id="123", well_name="A3"), + new_deck_point=DeckPoint(x=4, y=5, z=6), + ) + ), ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_verify_tip_presence.py b/api/tests/opentrons/protocol_engine/commands/test_verify_tip_presence.py index 087d924f0d2..ef6d79629be 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_verify_tip_presence.py +++ b/api/tests/opentrons/protocol_engine/commands/test_verify_tip_presence.py @@ -23,13 +23,13 @@ async def test_verify_tip_presence_implementation( expectedState=TipPresenceStatus.PRESENT, ) - decoy.when( + result = await subject.execute(data) + + assert result == SuccessData(public=VerifyTipPresenceResult()) + decoy.verify( await tip_handler.verify_tip_presence( pipette_id="pipette-id", expected=TipPresenceStatus.PRESENT, + follow_singular_sensor=None, ) - ).then_return(None) - - result = await subject.execute(data) - - assert result == SuccessData(public=VerifyTipPresenceResult(), private=None) + ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_wait_for_duration.py b/api/tests/opentrons/protocol_engine/commands/test_wait_for_duration.py index 9d351ce00d3..bc535a4b6a1 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_wait_for_duration.py +++ b/api/tests/opentrons/protocol_engine/commands/test_wait_for_duration.py @@ -22,5 +22,5 @@ async def test_pause_implementation( result = await subject.execute(data) - assert result == SuccessData(public=WaitForDurationResult(), private=None) + assert result == SuccessData(public=WaitForDurationResult()) decoy.verify(await run_control.wait_for_duration(42.0), times=1) diff --git a/api/tests/opentrons/protocol_engine/commands/test_wait_for_resume.py b/api/tests/opentrons/protocol_engine/commands/test_wait_for_resume.py index 752b85d3446..7d4b3a32edd 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_wait_for_resume.py +++ b/api/tests/opentrons/protocol_engine/commands/test_wait_for_resume.py @@ -23,7 +23,7 @@ async def test_wait_for_resume_implementation( result = await subject.execute(data) - assert result == SuccessData(public=WaitForResumeResult(), private=None) + assert result == SuccessData(public=WaitForResumeResult()) decoy.verify(await run_control.wait_for_resume(), times=1) diff --git a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_close_lid.py b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_close_lid.py index a569c18c970..9eb5536632d 100644 --- a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_close_lid.py +++ b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_close_lid.py @@ -3,8 +3,9 @@ from opentrons.hardware_control.modules import Thermocycler +from opentrons.protocol_engine.state import update_types from opentrons.protocol_engine.types import MotorAxis -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( ThermocyclerModuleSubState, ThermocyclerModuleId, @@ -55,4 +56,7 @@ async def test_close_lid( await tc_hardware.close(), times=1, ) - assert result == SuccessData(public=expected_result, private=None) + assert result == SuccessData( + public=expected_result, + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), + ) diff --git a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_deactivate_block.py b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_deactivate_block.py index 75627b93014..676a11731a8 100644 --- a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_deactivate_block.py +++ b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_deactivate_block.py @@ -3,7 +3,7 @@ from opentrons.hardware_control.modules import Thermocycler -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( ThermocyclerModuleSubState, ThermocyclerModuleId, @@ -45,4 +45,4 @@ async def test_deactivate_block( result = await subject.execute(data) decoy.verify(await tc_hardware.deactivate_block(), times=1) - assert result == SuccessData(public=expected_result, private=None) + assert result == SuccessData(public=expected_result) diff --git a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_deactivate_lid.py b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_deactivate_lid.py index 11d6e292370..83fc347236b 100644 --- a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_deactivate_lid.py +++ b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_deactivate_lid.py @@ -3,7 +3,7 @@ from opentrons.hardware_control.modules import Thermocycler -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( ThermocyclerModuleSubState, ThermocyclerModuleId, @@ -45,4 +45,4 @@ async def test_deactivate_lid( result = await subject.execute(data) decoy.verify(await tc_hardware.deactivate_lid(), times=1) - assert result == SuccessData(public=expected_result, private=None) + assert result == SuccessData(public=expected_result) diff --git a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_open_lid.py b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_open_lid.py index 8be2cd89c2d..6c26b7d2877 100644 --- a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_open_lid.py +++ b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_open_lid.py @@ -3,8 +3,9 @@ from opentrons.hardware_control.modules import Thermocycler +from opentrons.protocol_engine.state import update_types from opentrons.protocol_engine.types import MotorAxis -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( ThermocyclerModuleSubState, ThermocyclerModuleId, @@ -53,4 +54,7 @@ async def test_open_lid( await tc_hardware.open(), times=1, ) - assert result == SuccessData(public=expected_result, private=None) + assert result == SuccessData( + public=expected_result, + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), + ) diff --git a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_run_extended_profile.py b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_run_extended_profile.py new file mode 100644 index 00000000000..a4ed38a0dbf --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_run_extended_profile.py @@ -0,0 +1,115 @@ +"""Test Thermocycler run profile command implementation.""" +from typing import List, Union + +from decoy import Decoy + +from opentrons.hardware_control.modules import Thermocycler + +from opentrons.protocol_engine.state.state import StateView +from opentrons.protocol_engine.state.module_substates import ( + ThermocyclerModuleSubState, + ThermocyclerModuleId, +) +from opentrons.protocol_engine.execution import EquipmentHandler +from opentrons.protocol_engine.commands import thermocycler as tc_commands +from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.commands.thermocycler.run_extended_profile import ( + RunExtendedProfileImpl, + ProfileStep, + ProfileCycle, +) + + +async def test_run_extended_profile( + decoy: Decoy, + state_view: StateView, + equipment: EquipmentHandler, +) -> None: + """It should be able to execute the specified module's profile run.""" + subject = RunExtendedProfileImpl(state_view=state_view, equipment=equipment) + + step_data: List[Union[ProfileStep, ProfileCycle]] = [ + ProfileStep(celsius=12.3, holdSeconds=45), + ProfileCycle( + steps=[ + ProfileStep(celsius=78.9, holdSeconds=910), + ProfileStep(celsius=12, holdSeconds=1), + ], + repetitions=2, + ), + ProfileStep(celsius=45.6, holdSeconds=78), + ProfileCycle( + steps=[ + ProfileStep(celsius=56, holdSeconds=11), + ProfileStep(celsius=34, holdSeconds=10), + ], + repetitions=1, + ), + ] + data = tc_commands.RunExtendedProfileParams( + moduleId="input-thermocycler-id", + profileElements=step_data, + blockMaxVolumeUl=56.7, + ) + expected_result = tc_commands.RunExtendedProfileResult() + + tc_module_substate = decoy.mock(cls=ThermocyclerModuleSubState) + tc_hardware = decoy.mock(cls=Thermocycler) + + decoy.when( + state_view.modules.get_thermocycler_module_substate("input-thermocycler-id") + ).then_return(tc_module_substate) + + decoy.when(tc_module_substate.module_id).then_return( + ThermocyclerModuleId("thermocycler-id") + ) + + # Stub temperature validation from hs module view + decoy.when(tc_module_substate.validate_target_block_temperature(12.3)).then_return( + 32.1 + ) + decoy.when(tc_module_substate.validate_target_block_temperature(78.9)).then_return( + 78.9 + ) + decoy.when(tc_module_substate.validate_target_block_temperature(12)).then_return(12) + decoy.when(tc_module_substate.validate_target_block_temperature(45.6)).then_return( + 65.4 + ) + decoy.when(tc_module_substate.validate_target_block_temperature(56)).then_return(56) + decoy.when(tc_module_substate.validate_target_block_temperature(34)).then_return(34) + + # Stub volume validation from hs module view + decoy.when(tc_module_substate.validate_max_block_volume(56.7)).then_return(76.5) + + # Get attached hardware modules + decoy.when( + equipment.get_module_hardware_api(ThermocyclerModuleId("thermocycler-id")) + ).then_return(tc_hardware) + + result = await subject.execute(data) + + decoy.verify( + await tc_hardware.execute_profile( + profile=[ + {"temperature": 32.1, "hold_time_seconds": 45}, + { + "steps": [ + {"temperature": 78.9, "hold_time_seconds": 910}, + {"temperature": 12, "hold_time_seconds": 1}, + ], + "repetitions": 2, + }, + {"temperature": 65.4, "hold_time_seconds": 78}, + { + "steps": [ + {"temperature": 56, "hold_time_seconds": 11}, + {"temperature": 34, "hold_time_seconds": 10}, + ], + "repetitions": 1, + }, + ], + volume=76.5, + ), + times=1, + ) + assert result == SuccessData(public=expected_result) diff --git a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_run_profile.py b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_run_profile.py index d97bacf7c85..6d6234a76e6 100644 --- a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_run_profile.py +++ b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_run_profile.py @@ -3,7 +3,7 @@ from opentrons.hardware_control.modules import Thermocycler -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( ThermocyclerModuleSubState, ThermocyclerModuleId, @@ -75,4 +75,4 @@ async def test_run_profile( ), times=1, ) - assert result == SuccessData(public=expected_result, private=None) + assert result == SuccessData(public=expected_result) diff --git a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_set_target_block_temperature.py b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_set_target_block_temperature.py index 89e00592510..49d6dda3ca9 100644 --- a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_set_target_block_temperature.py +++ b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_set_target_block_temperature.py @@ -3,7 +3,7 @@ from opentrons.hardware_control.modules import Thermocycler -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( ThermocyclerModuleSubState, ThermocyclerModuleId, @@ -67,4 +67,4 @@ async def test_set_target_block_temperature( ), times=1, ) - assert result == SuccessData(public=expected_result, private=None) + assert result == SuccessData(public=expected_result) diff --git a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_set_target_lid_temperature.py b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_set_target_lid_temperature.py index aa558561ac8..372ae6a814c 100644 --- a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_set_target_lid_temperature.py +++ b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_set_target_lid_temperature.py @@ -3,7 +3,7 @@ from opentrons.hardware_control.modules import Thermocycler -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( ThermocyclerModuleSubState, ThermocyclerModuleId, @@ -56,4 +56,4 @@ async def test_set_target_lid_temperature( result = await subject.execute(data) decoy.verify(await tc_hardware.set_target_lid_temperature(celsius=45.6), times=1) - assert result == SuccessData(public=expected_result, private=None) + assert result == SuccessData(public=expected_result) diff --git a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_wait_for_block_temperature.py b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_wait_for_block_temperature.py index 060cc34f2c2..426724cf16f 100644 --- a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_wait_for_block_temperature.py +++ b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_wait_for_block_temperature.py @@ -3,7 +3,7 @@ from opentrons.hardware_control.modules import Thermocycler -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( ThermocyclerModuleSubState, ThermocyclerModuleId, @@ -51,4 +51,4 @@ async def test_set_target_block_temperature( tc_module_substate.get_target_block_temperature(), await tc_hardware.wait_for_block_target(), ) - assert result == SuccessData(public=expected_result, private=None) + assert result == SuccessData(public=expected_result) diff --git a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_wait_for_lid_temperature.py b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_wait_for_lid_temperature.py index 08ad7db94a9..e358e80d6f4 100644 --- a/api/tests/opentrons/protocol_engine/commands/thermocycler/test_wait_for_lid_temperature.py +++ b/api/tests/opentrons/protocol_engine/commands/thermocycler/test_wait_for_lid_temperature.py @@ -3,7 +3,7 @@ from opentrons.hardware_control.modules import Thermocycler -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.state.module_substates import ( ThermocyclerModuleSubState, ThermocyclerModuleId, @@ -51,4 +51,4 @@ async def test_set_target_block_temperature( tc_module_substate.get_target_lid_temperature(), await tc_hardware.wait_for_lid_target(), ) - assert result == SuccessData(public=expected_result, private=None) + assert result == SuccessData(public=expected_result) diff --git a/api/tests/opentrons/protocol_engine/commands/unsafe/test_engage_axes.py b/api/tests/opentrons/protocol_engine/commands/unsafe/test_engage_axes.py new file mode 100644 index 00000000000..1f40523e4e1 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/unsafe/test_engage_axes.py @@ -0,0 +1,59 @@ +"""Test update-position-estimator commands.""" +from decoy import Decoy + +from opentrons.protocol_engine.commands.unsafe.unsafe_engage_axes import ( + UnsafeEngageAxesParams, + UnsafeEngageAxesResult, + UnsafeEngageAxesImplementation, +) +from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.execution import GantryMover +from opentrons.protocol_engine.types import MotorAxis +from opentrons.hardware_control import OT3HardwareControlAPI +from opentrons.hardware_control.types import Axis + + +async def test_engage_axes_implementation( + decoy: Decoy, ot3_hardware_api: OT3HardwareControlAPI, gantry_mover: GantryMover +) -> None: + """Test EngageAxes command execution.""" + subject = UnsafeEngageAxesImplementation( + hardware_api=ot3_hardware_api, gantry_mover=gantry_mover + ) + + data = UnsafeEngageAxesParams( + axes=[ + MotorAxis.LEFT_Z, + MotorAxis.LEFT_PLUNGER, + MotorAxis.X, + MotorAxis.Y, + MotorAxis.RIGHT_Z, + MotorAxis.RIGHT_PLUNGER, + ] + ) + decoy.when( + gantry_mover.motor_axes_to_present_hardware_axes( + [ + MotorAxis.LEFT_Z, + MotorAxis.LEFT_PLUNGER, + MotorAxis.X, + MotorAxis.Y, + MotorAxis.RIGHT_Z, + MotorAxis.RIGHT_PLUNGER, + ] + ) + ).then_return([Axis.Z_L, Axis.P_L, Axis.X, Axis.Y]) + + decoy.when( + await ot3_hardware_api.update_axis_position_estimations( + [Axis.Z_L, Axis.P_L, Axis.X, Axis.Y] + ) + ).then_return(None) + + result = await subject.execute(data) + + assert result == SuccessData(public=UnsafeEngageAxesResult()) + + decoy.verify( + await ot3_hardware_api.engage_axes([Axis.Z_L, Axis.P_L, Axis.X, Axis.Y]), + ) diff --git a/api/tests/opentrons/protocol_engine/commands/unsafe/test_ungrip_labware.py b/api/tests/opentrons/protocol_engine/commands/unsafe/test_ungrip_labware.py new file mode 100644 index 00000000000..bcd77093abf --- /dev/null +++ b/api/tests/opentrons/protocol_engine/commands/unsafe/test_ungrip_labware.py @@ -0,0 +1,41 @@ +"""Test update-position-estimator commands.""" +from decoy import Decoy + +from opentrons.hardware_control.types import Axis +from opentrons.protocol_engine.commands.unsafe.unsafe_ungrip_labware import ( + UnsafeUngripLabwareParams, + UnsafeUngripLabwareResult, + UnsafeUngripLabwareImplementation, +) +from opentrons.protocol_engine.commands.command import SuccessData +from opentrons.protocol_engine.errors.exceptions import GripperNotAttachedError +from opentrons.hardware_control import OT3HardwareControlAPI +import pytest + + +async def test_ungrip_labware_implementation( + decoy: Decoy, ot3_hardware_api: OT3HardwareControlAPI +) -> None: + """Test UngripLabware command execution.""" + subject = UnsafeUngripLabwareImplementation(hardware_api=ot3_hardware_api) + + decoy.when(ot3_hardware_api.has_gripper()).then_return(True) + + result = await subject.execute(params=UnsafeUngripLabwareParams()) + + assert result == SuccessData(public=UnsafeUngripLabwareResult()) + + decoy.verify( + await ot3_hardware_api.home([Axis.G]), + ) + + +async def test_ungrip_labware_implementation_raises_no_gripper_attached( + decoy: Decoy, ot3_hardware_api: OT3HardwareControlAPI +) -> None: + """Test UngripLabware command execution.""" + subject = UnsafeUngripLabwareImplementation(hardware_api=ot3_hardware_api) + + decoy.when(ot3_hardware_api.has_gripper()).then_return(False) + with pytest.raises(GripperNotAttachedError): + await subject.execute(params=UnsafeUngripLabwareParams()) diff --git a/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_blow_out_in_place.py b/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_blow_out_in_place.py index f25d8d06169..88ad9a8ecf8 100644 --- a/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_blow_out_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_blow_out_in_place.py @@ -2,7 +2,8 @@ from decoy import Decoy from opentrons.types import MountType -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView +from opentrons.protocol_engine.state import update_types from opentrons.protocol_engine.commands.unsafe.unsafe_blow_out_in_place import ( UnsafeBlowOutInPlaceParams, UnsafeBlowOutInPlaceResult, @@ -41,7 +42,14 @@ async def test_blow_out_in_place_implementation( result = await subject.execute(data) - assert result == SuccessData(public=UnsafeBlowOutInPlaceResult(), private=None) + assert result == SuccessData( + public=UnsafeBlowOutInPlaceResult(), + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteEmptyFluidUpdate( + pipette_id="pipette-id" + ) + ), + ) decoy.verify( await ot3_hardware_api.update_axis_position_estimations([Axis.P_L]), diff --git a/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_drop_tip_in_place.py b/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_drop_tip_in_place.py index 3659dd2db31..e7c684554c8 100644 --- a/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_drop_tip_in_place.py +++ b/api/tests/opentrons/protocol_engine/commands/unsafe/test_unsafe_drop_tip_in_place.py @@ -1,9 +1,14 @@ """Test unsafe drop tip in place commands.""" +from opentrons.protocol_engine.state.update_types import ( + PipetteTipStateUpdate, + PipetteUnknownFluidUpdate, + StateUpdate, +) import pytest from decoy import Decoy from opentrons.types import MountType -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.execution import TipHandler @@ -44,7 +49,15 @@ async def test_drop_tip_implementation( result = await subject.execute(params) - assert result == SuccessData(public=UnsafeDropTipInPlaceResult(), private=None) + assert result == SuccessData( + public=UnsafeDropTipInPlaceResult(), + state_update=StateUpdate( + pipette_tip_state=PipetteTipStateUpdate( + pipette_id="abc", tip_geometry=None + ), + pipette_aspirated_fluid=PipetteUnknownFluidUpdate(pipette_id="abc"), + ), + ) decoy.verify( await ot3_hardware_api.update_axis_position_estimations([Axis.P_L]), diff --git a/api/tests/opentrons/protocol_engine/commands/unsafe/test_update_position_estimators.py b/api/tests/opentrons/protocol_engine/commands/unsafe/test_update_position_estimators.py index da7ffe75012..e281502308c 100644 --- a/api/tests/opentrons/protocol_engine/commands/unsafe/test_update_position_estimators.py +++ b/api/tests/opentrons/protocol_engine/commands/unsafe/test_update_position_estimators.py @@ -22,30 +22,31 @@ async def test_update_position_estimators_implementation( ) data = UpdatePositionEstimatorsParams( - axes=[MotorAxis.LEFT_Z, MotorAxis.LEFT_PLUNGER, MotorAxis.X, MotorAxis.Y] - ) - - decoy.when(gantry_mover.motor_axis_to_hardware_axis(MotorAxis.LEFT_Z)).then_return( - Axis.Z_L + axes=[ + MotorAxis.LEFT_Z, + MotorAxis.LEFT_PLUNGER, + MotorAxis.X, + MotorAxis.Y, + MotorAxis.RIGHT_Z, + MotorAxis.RIGHT_PLUNGER, + ] ) decoy.when( - gantry_mover.motor_axis_to_hardware_axis(MotorAxis.LEFT_PLUNGER) - ).then_return(Axis.P_L) - decoy.when(gantry_mover.motor_axis_to_hardware_axis(MotorAxis.X)).then_return( - Axis.X - ) - decoy.when(gantry_mover.motor_axis_to_hardware_axis(MotorAxis.Y)).then_return( - Axis.Y - ) - decoy.when( - await ot3_hardware_api.update_axis_position_estimations( - [Axis.Z_L, Axis.P_L, Axis.X, Axis.Y] + gantry_mover.motor_axes_to_present_hardware_axes( + [ + MotorAxis.LEFT_Z, + MotorAxis.LEFT_PLUNGER, + MotorAxis.X, + MotorAxis.Y, + MotorAxis.RIGHT_Z, + MotorAxis.RIGHT_PLUNGER, + ] ) - ).then_return(None) + ).then_return([Axis.Z_L, Axis.P_L, Axis.X, Axis.Y]) result = await subject.execute(data) - assert result == SuccessData(public=UpdatePositionEstimatorsResult(), private=None) + assert result == SuccessData(public=UpdatePositionEstimatorsResult()) decoy.verify( await ot3_hardware_api.update_axis_position_estimations( diff --git a/api/tests/opentrons/protocol_engine/conftest.py b/api/tests/opentrons/protocol_engine/conftest.py index 7040f8497ea..76c5d754f3e 100644 --- a/api/tests/opentrons/protocol_engine/conftest.py +++ b/api/tests/opentrons/protocol_engine/conftest.py @@ -22,6 +22,7 @@ from opentrons.hardware_control.api import API from opentrons.hardware_control.protocols.types import FlexRobotType, OT2RobotType from opentrons.protocol_engine.notes import CommandNoteAdder +from opentrons.protocol_engine.resources.file_provider import FileProvider if TYPE_CHECKING: from opentrons.hardware_control.ot3api import OT3API @@ -252,3 +253,9 @@ def supported_tip_fixture() -> pipette_definition.SupportedTipsDefinition: def mock_command_note_adder(decoy: Decoy) -> CommandNoteAdder: """Get a command note adder.""" return decoy.mock(cls=CommandNoteAdder) + + +@pytest.fixture +def file_provider(decoy: Decoy) -> FileProvider: + """Get a mocked out FileProvider.""" + return decoy.mock(cls=FileProvider) diff --git a/api/tests/opentrons/protocol_engine/execution/test_command_executor.py b/api/tests/opentrons/protocol_engine/execution/test_command_executor.py index bc2e8a0a8fe..eb84ceb018b 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_command_executor.py +++ b/api/tests/opentrons/protocol_engine/execution/test_command_executor.py @@ -18,8 +18,8 @@ from opentrons.protocol_engine.errors.exceptions import ( EStopActivatedError as PE_EStopActivatedError, ) -from opentrons.protocol_engine.resources import ModelUtils -from opentrons.protocol_engine.state import StateStore +from opentrons.protocol_engine.resources import ModelUtils, FileProvider +from opentrons.protocol_engine.state.state import StateStore from opentrons.protocol_engine.actions import ( ActionDispatcher, RunCommandAction, @@ -174,6 +174,7 @@ def subject( state_store: StateStore, action_dispatcher: ActionDispatcher, equipment: EquipmentHandler, + file_provider: FileProvider, movement: MovementHandler, mock_gantry_mover: GantryMover, labware_movement: LabwareMovementHandler, @@ -188,6 +189,7 @@ def subject( """Get a CommandExecutor test subject with its dependencies mocked out.""" return CommandExecutor( hardware_api=hardware_api, + file_provider=file_provider, state_store=state_store, action_dispatcher=action_dispatcher, equipment=equipment, @@ -218,8 +220,8 @@ class _TestCommandDefinedError(ErrorOccurrence): _TestCommandReturn = Union[ - SuccessData[_TestCommandResult, None], - DefinedErrorData[_TestCommandDefinedError, None], + SuccessData[_TestCommandResult], + DefinedErrorData[_TestCommandDefinedError], ] @@ -234,6 +236,7 @@ async def test_execute( state_store: StateStore, action_dispatcher: ActionDispatcher, equipment: EquipmentHandler, + file_provider: FileProvider, movement: MovementHandler, mock_gantry_mover: GantryMover, labware_movement: LabwareMovementHandler, @@ -260,7 +263,7 @@ class _TestCommand( _ImplementationCls: Type[_TestCommandImpl] = TestCommandImplCls command_params = _TestCommandParams() - command_result = SuccessData(public=_TestCommandResult(), private=None) + command_result = SuccessData(public=_TestCommandResult()) queued_command = cast( Command, @@ -329,6 +332,7 @@ class _TestCommand( queued_command._ImplementationCls( state_view=state_store, hardware_api=hardware_api, + file_provider=file_provider, equipment=equipment, movement=movement, gantry_mover=mock_gantry_mover, @@ -358,9 +362,7 @@ class _TestCommand( decoy.verify( action_dispatcher.dispatch( - SucceedCommandAction( - private_result=None, command=expected_completed_command - ) + SucceedCommandAction(command=expected_completed_command) ), ) @@ -392,6 +394,7 @@ async def test_execute_undefined_error( state_store: StateStore, action_dispatcher: ActionDispatcher, equipment: EquipmentHandler, + file_provider: FileProvider, movement: MovementHandler, mock_gantry_mover: GantryMover, labware_movement: LabwareMovementHandler, @@ -474,6 +477,7 @@ class _TestCommand( queued_command._ImplementationCls( state_view=state_store, hardware_api=hardware_api, + file_provider=file_provider, equipment=equipment, movement=movement, gantry_mover=mock_gantry_mover, @@ -528,6 +532,7 @@ async def test_execute_defined_error( state_store: StateStore, action_dispatcher: ActionDispatcher, equipment: EquipmentHandler, + file_provider: FileProvider, movement: MovementHandler, mock_gantry_mover: GantryMover, labware_movement: LabwareMovementHandler, @@ -561,7 +566,6 @@ class _TestCommand( error_id = "error-id" returned_error = DefinedErrorData( public=_TestCommandDefinedError(id=error_id, createdAt=failed_at), - private=None, ) queued_command = cast( Command, @@ -611,6 +615,7 @@ class _TestCommand( queued_command._ImplementationCls( state_view=state_store, hardware_api=hardware_api, + file_provider=file_provider, equipment=equipment, movement=movement, gantry_mover=mock_gantry_mover, diff --git a/api/tests/opentrons/protocol_engine/execution/test_door_watcher.py b/api/tests/opentrons/protocol_engine/execution/test_door_watcher.py index 1e252650957..dcf3db10653 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_door_watcher.py +++ b/api/tests/opentrons/protocol_engine/execution/test_door_watcher.py @@ -16,7 +16,7 @@ ) from opentrons.protocol_engine.actions import ActionDispatcher, DoorChangeAction -from opentrons.protocol_engine.state import StateStore +from opentrons.protocol_engine.state.state import StateStore from opentrons.protocol_engine.execution.door_watcher import ( DoorWatcher, ) diff --git a/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py b/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py index d28ebe700ca..39208184754 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_equipment_handler.py @@ -39,7 +39,8 @@ FlowRates, ) -from opentrons.protocol_engine.state import Config, StateStore +from opentrons.protocol_engine.state.config import Config +from opentrons.protocol_engine.state.state import StateStore from opentrons.protocol_engine.state.modules import HardwareModule from opentrons.protocol_engine.resources import ( ModelUtils, @@ -52,9 +53,9 @@ ) from opentrons.protocol_engine.execution.equipment import ( EquipmentHandler, - LoadedLabwareData, LoadedPipetteData, LoadedModuleData, + LoadedLabwareData, ) from ..pipette_fixtures import get_default_nozzle_map @@ -68,6 +69,14 @@ def _make_config(use_virtual_modules: bool) -> Config: ) +@pytest.fixture +def available_sensors() -> pipette_definition.AvailableSensorDefinition: + """Provide a list of sensors.""" + return pipette_definition.AvailableSensorDefinition( + sensors=["pressure", "capacitive", "environment"] + ) + + @pytest.fixture(autouse=True) def patch_mock_pipette_data_provider( decoy: Decoy, @@ -132,6 +141,7 @@ def tip_overlap_versions(request: SubRequest) -> str: def loaded_static_pipette_data( supported_tip_fixture: pipette_definition.SupportedTipsDefinition, target_tip_overlap_data: Dict[str, float], + available_sensors: pipette_definition.AvailableSensorDefinition, ) -> LoadedStaticPipetteData: """Get a pipette config data value object.""" return LoadedStaticPipetteData( @@ -153,6 +163,14 @@ def loaded_static_pipette_data( back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ) diff --git a/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py b/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py index 01a3ca6e3a5..2c872c003d1 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py +++ b/api/tests/opentrons/protocol_engine/execution/test_gantry_mover.py @@ -3,7 +3,8 @@ import pytest from decoy import Decoy -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional +from collections import OrderedDict from opentrons.types import Mount, MountType, Point from opentrons.hardware_control import API as HardwareAPI @@ -15,8 +16,14 @@ from opentrons.motion_planning import Waypoint -from opentrons.protocol_engine.state import StateView, PipetteLocationData -from opentrons.protocol_engine.types import MotorAxis, DeckPoint, CurrentWell +from opentrons.protocol_engine.state.state import StateView +from opentrons.protocol_engine.state.motion import PipetteLocationData +from opentrons.protocol_engine.types import ( + MotorAxis, + DeckPoint, + CurrentWell, + TipGeometry, +) from opentrons.protocol_engine.errors import MustHomeError, InvalidAxisForRobotType from opentrons.protocol_engine.execution.gantry_mover import ( @@ -460,6 +467,116 @@ async def test_home_z( ) +@pytest.mark.parametrize( + argnames=[ + "axis_map", + "critical_point", + "relative_move", + "expected_mount", + "call_to_hw", + "final_position", + ], + argvalues=[ + [ + {MotorAxis.X: 10.0, MotorAxis.Y: 15.0, MotorAxis.RIGHT_Z: 20.0}, + {MotorAxis.X: 2.0, MotorAxis.Y: 1.0, MotorAxis.RIGHT_Z: 1.0}, + False, + Mount.RIGHT, + OrderedDict( + {HardwareAxis.X: -2.0, HardwareAxis.Y: 4.0, HardwareAxis.A: 19.0} + ), + {HardwareAxis.X: -2.0, HardwareAxis.Y: 4.0, HardwareAxis.A: 9.0}, + ], + [ + {MotorAxis.RIGHT_Z: 20.0}, + None, + True, + Mount.RIGHT, + OrderedDict({HardwareAxis.A: 29.0}), + { + HardwareAxis.X: 10.0, + HardwareAxis.Y: 15.0, + HardwareAxis.Z: 10.0, + HardwareAxis.A: 30.0, + }, + ], + [ + {MotorAxis.AXIS_96_CHANNEL_CAM: 10.0}, + None, + False, + Mount.LEFT, + OrderedDict({HardwareAxis.Q: 10.0}), + {HardwareAxis.Q: 10.0}, + ], + ], +) +@pytest.mark.ot3_only +async def test_move_axes( + decoy: Decoy, + ot3_hardware_api: OT3API, + mock_state_view: StateView, + axis_map: Dict[MotorAxis, float], + critical_point: Optional[Dict[MotorAxis, float]], + expected_mount: Mount, + relative_move: bool, + call_to_hw: "OrderedDict[HardwareAxis, float]", + final_position: Dict[HardwareAxis, float], +) -> None: + """Test the move axes function.""" + subject = HardwareGantryMover( + state_view=mock_state_view, hardware_api=ot3_hardware_api + ) + curr_pos = { + HardwareAxis.X: 10.0, + HardwareAxis.Y: 15.0, + HardwareAxis.Z: 10.0, + HardwareAxis.A: 10.0, + } + call_count = 0 + + def _current_position(mount: Mount, refresh: bool) -> Dict[HardwareAxis, float]: + nonlocal call_count + nonlocal curr_pos + nonlocal final_position + if call_count == 0 and relative_move: + call_count += 1 + return curr_pos + else: + return final_position + + decoy.when( + await ot3_hardware_api.current_position(expected_mount, refresh=True) + ).then_do(_current_position) + + decoy.when(ot3_hardware_api.config.left_mount_offset).then_return(Point(1, 1, 1)) + decoy.when(ot3_hardware_api.config.right_mount_offset).then_return( + Point(10, 10, 10) + ) + decoy.when(ot3_hardware_api.config.gripper_mount_offset).then_return( + Point(0.5, 0.5, 0.5) + ) + + decoy.when(ot3_hardware_api.get_deck_from_machine(curr_pos)).then_return(curr_pos) + + decoy.when(ot3_hardware_api.get_deck_from_machine(final_position)).then_return( + final_position + ) + if not critical_point: + decoy.when(ot3_hardware_api.critical_point_for(expected_mount)).then_return( + Point(1, 1, 1) + ) + + pos = await subject.move_axes(axis_map, critical_point, 100, relative_move) + decoy.verify( + await ot3_hardware_api.move_axes(position=call_to_hw, speed=100), + times=1, + ) + assert pos == { + subject._hardware_axis_to_motor_axis(ax): pos + for ax, pos in final_position.items() + } + + async def test_virtual_get_position( decoy: Decoy, mock_state_view: StateView, @@ -498,7 +615,9 @@ def test_virtual_get_max_travel_z_ot2( decoy.when( mock_state_view.pipettes.get_instrument_max_height_ot2("pipette-id") ).then_return(42) - decoy.when(mock_state_view.tips.get_tip_length("pipette-id")).then_return(20) + decoy.when(mock_state_view.pipettes.get_attached_tip("pipette-id")).then_return( + TipGeometry(length=20, diameter=0, volume=0) + ) result = virtual_subject.get_max_travel_z("pipette-id") @@ -512,7 +631,9 @@ def test_virtual_get_max_travel_z_ot3( ) -> None: """It should get the max travel z height with the state store.""" decoy.when(mock_state_view.config.robot_type).then_return("OT-3 Standard") - decoy.when(mock_state_view.tips.get_tip_length("pipette-id")).then_return(48) + decoy.when(mock_state_view.pipettes.get_attached_tip("pipette-id")).then_return( + TipGeometry(length=48, diameter=0, volume=0) + ) result = virtual_subject.get_max_travel_z("pipette-id") @@ -552,3 +673,39 @@ async def test_virtual_move_to( ) assert result == Point(4, 5, 6) + + +@pytest.mark.parametrize( + argnames=["axis_map", "critical_point", "relative_move", "expected_position"], + argvalues=[ + [ + {MotorAxis.X: 10, MotorAxis.Y: 15, MotorAxis.RIGHT_Z: 20}, + {MotorAxis.X: 2, MotorAxis.Y: 1, MotorAxis.RIGHT_Z: 1}, + False, + {MotorAxis.X: 8, MotorAxis.Y: 14, MotorAxis.RIGHT_Z: 19}, + ], + [ + {MotorAxis.RIGHT_Z: 20}, + None, + True, + {MotorAxis.X: 0.0, MotorAxis.Y: 0.0, MotorAxis.RIGHT_Z: 20}, + ], + [ + {MotorAxis.AXIS_96_CHANNEL_CAM: 10}, + None, + False, + {MotorAxis.AXIS_96_CHANNEL_CAM: 10}, + ], + ], +) +async def test_virtual_move_axes( + decoy: Decoy, + virtual_subject: VirtualGantryMover, + axis_map: Dict[MotorAxis, float], + critical_point: Optional[Dict[MotorAxis, float]], + relative_move: bool, + expected_position: Dict[MotorAxis, float], +) -> None: + """It should simulate moving a set of axis by a certain distance.""" + pos = await virtual_subject.move_axes(axis_map, critical_point, 100, relative_move) + assert pos == expected_position diff --git a/api/tests/opentrons/protocol_engine/execution/test_hardware_stopper.py b/api/tests/opentrons/protocol_engine/execution/test_hardware_stopper.py index 537fd07613c..503d681bced 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_hardware_stopper.py +++ b/api/tests/opentrons/protocol_engine/execution/test_hardware_stopper.py @@ -9,7 +9,7 @@ from opentrons.hardware_control.types import OT3Mount from opentrons.types import PipetteNotAttachedError as HwPipetteNotAttachedError -from opentrons.protocol_engine.state import StateStore +from opentrons.protocol_engine.state.state import StateStore from opentrons.protocol_engine.execution import ( MovementHandler, TipHandler, @@ -158,7 +158,7 @@ async def test_hardware_stopping_sequence_no_tip_drop( decoy.verify(await hardware_api.stop(home_after=False), times=1) decoy.verify( - await mock_tip_handler.add_tip( + mock_tip_handler.cache_tip( pipette_id="pipette-id", tip=TipGeometry(length=1.0, volume=2.0, diameter=3.0), ), @@ -181,7 +181,7 @@ async def test_hardware_stopping_sequence_no_pipette( ) decoy.when( - await mock_tip_handler.add_tip( + mock_tip_handler.cache_tip( pipette_id="pipette-id", tip=TipGeometry(length=1.0, volume=2.0, diameter=3.0), ), @@ -271,7 +271,7 @@ async def test_hardware_stopping_sequence_with_fixed_trash( await movement.home( axes=[MotorAxis.X, MotorAxis.Y, MotorAxis.LEFT_Z, MotorAxis.RIGHT_Z] ), - await mock_tip_handler.add_tip( + mock_tip_handler.cache_tip( pipette_id="pipette-id", tip=TipGeometry(length=1.0, volume=2.0, diameter=3.0), ), @@ -320,7 +320,7 @@ async def test_hardware_stopping_sequence_with_OT2_addressable_area( await movement.home( axes=[MotorAxis.X, MotorAxis.Y, MotorAxis.LEFT_Z, MotorAxis.RIGHT_Z] ), - await mock_tip_handler.add_tip( + mock_tip_handler.cache_tip( pipette_id="pipette-id", tip=TipGeometry(length=1.0, volume=2.0, diameter=3.0), ), diff --git a/api/tests/opentrons/protocol_engine/execution/test_heater_shaker_movement_flagger.py b/api/tests/opentrons/protocol_engine/execution/test_heater_shaker_movement_flagger.py index 91afa9f023c..e5d07fec8eb 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_heater_shaker_movement_flagger.py +++ b/api/tests/opentrons/protocol_engine/execution/test_heater_shaker_movement_flagger.py @@ -26,7 +26,7 @@ from opentrons.protocol_engine.execution.heater_shaker_movement_flagger import ( HeaterShakerMovementFlagger, ) -from opentrons.protocol_engine.state import StateStore +from opentrons.protocol_engine.state.state import StateStore from opentrons.protocol_engine.state.module_substates.heater_shaker_module_substate import ( HeaterShakerModuleId, HeaterShakerModuleSubState, diff --git a/api/tests/opentrons/protocol_engine/execution/test_labware_movement_handler.py b/api/tests/opentrons/protocol_engine/execution/test_labware_movement_handler.py index 58619647f54..3377e39b666 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_labware_movement_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_labware_movement_handler.py @@ -2,10 +2,11 @@ from __future__ import annotations from datetime import datetime +from typing import TYPE_CHECKING, Union, Optional, Tuple +from unittest.mock import sentinel -import pytest from decoy import Decoy, matchers -from typing import TYPE_CHECKING, Union, Optional, Tuple +import pytest from opentrons.protocol_engine.execution import EquipmentHandler, MovementHandler from opentrons.hardware_control import HardwareControlAPI @@ -44,7 +45,7 @@ ThermocyclerNotOpenError, HeaterShakerLabwareLatchNotOpenError, ) -from opentrons.protocol_engine.state import StateStore +from opentrons.protocol_engine.state.state import StateStore if TYPE_CHECKING: from opentrons.hardware_control.ot3api import OT3API @@ -87,9 +88,9 @@ def heater_shaker_movement_flagger(decoy: Decoy) -> HeaterShakerMovementFlagger: @pytest.fixture -def hardware_gripper_offset_data() -> Tuple[ - LabwareMovementOffsetData, LabwareMovementOffsetData -]: +def hardware_gripper_offset_data() -> ( + Tuple[LabwareMovementOffsetData, LabwareMovementOffsetData] +): """Get a set of mocked labware offset data.""" user_offset_data = LabwareMovementOffsetData( pickUpOffset=LabwareOffsetVector(x=123, y=234, z=345), @@ -133,7 +134,7 @@ async def set_up_decoy_hardware_gripper( decoy.when(ot3_hardware_api.hardware_gripper.jaw_width).then_return(89) decoy.when( - state_store.labware.get_grip_force("my-teleporting-labware") + state_store.labware.get_grip_force(sentinel.my_teleporting_labware_def) ).then_return(100) decoy.when(state_store.labware.get_labware_offset("new-offset-id")).then_return( @@ -195,6 +196,10 @@ async def test_raise_error_if_gripper_pickup_failed( starting_location = DeckSlotLocation(slotName=DeckSlotName.SLOT_1) to_location = DeckSlotLocation(slotName=DeckSlotName.SLOT_2) + decoy.when( + state_store.labware.get_definition("my-teleporting-labware") + ).then_return(sentinel.my_teleporting_labware_def) + mock_tc_context_manager = decoy.mock(name="mock_tc_context_manager") decoy.when( thermocycler_plate_lifter.lift_plate_for_labware_movement( @@ -202,32 +207,42 @@ async def test_raise_error_if_gripper_pickup_failed( ) ).then_return(mock_tc_context_manager) + current_labware = state_store.labware.get_definition( + labware_id="my-teleporting-labware" + ) + decoy.when( state_store.geometry.get_final_labware_movement_offset_vectors( from_location=starting_location, to_location=to_location, additional_offset_vector=user_offset_data, + current_labware=current_labware, ) ).then_return(final_offset_data) decoy.when( state_store.geometry.get_labware_grip_point( - labware_id="my-teleporting-labware", location=starting_location + labware_definition=sentinel.my_teleporting_labware_def, + location=starting_location, ) ).then_return(Point(101, 102, 119.5)) decoy.when( state_store.geometry.get_labware_grip_point( - labware_id="my-teleporting-labware", location=to_location + labware_definition=sentinel.my_teleporting_labware_def, location=to_location ) ).then_return(Point(201, 202, 219.5)) decoy.when( - state_store.labware.get_dimensions(labware_id="my-teleporting-labware") + state_store.labware.get_dimensions( + labware_definition=sentinel.my_teleporting_labware_def + ) ).then_return(Dimensions(x=100, y=85, z=0)) decoy.when( - state_store.labware.get_well_bbox(labware_id="my-teleporting-labware") + state_store.labware.get_well_bbox( + labware_definition=sentinel.my_teleporting_labware_def + ) ).then_return(Dimensions(x=99, y=80, z=1)) await subject.move_labware_with_gripper( @@ -315,32 +330,44 @@ async def test_move_labware_with_gripper( # smoke test for gripper labware movement with actual labware and make this a unit test. await set_up_decoy_hardware_gripper(decoy, ot3_hardware_api, state_store) - user_offset_data, final_offset_data = hardware_gripper_offset_data + decoy.when( + state_store.labware.get_definition("my-teleporting-labware") + ).then_return(sentinel.my_teleporting_labware_def) + user_offset_data, final_offset_data = hardware_gripper_offset_data + current_labware = state_store.labware.get_definition( + labware_id="my-teleporting-labware" + ) decoy.when( state_store.geometry.get_final_labware_movement_offset_vectors( from_location=from_location, to_location=to_location, additional_offset_vector=user_offset_data, + current_labware=current_labware, ) ).then_return(final_offset_data) decoy.when( - state_store.labware.get_dimensions(labware_id="my-teleporting-labware") + state_store.labware.get_dimensions( + labware_definition=sentinel.my_teleporting_labware_def + ) ).then_return(Dimensions(x=100, y=85, z=0)) decoy.when( - state_store.labware.get_well_bbox(labware_id="my-teleporting-labware") + state_store.labware.get_well_bbox( + labware_definition=sentinel.my_teleporting_labware_def + ) ).then_return(Dimensions(x=99, y=80, z=1)) decoy.when( state_store.geometry.get_labware_grip_point( - labware_id="my-teleporting-labware", location=from_location + labware_definition=sentinel.my_teleporting_labware_def, + location=from_location, ) ).then_return(Point(101, 102, 119.5)) decoy.when( state_store.geometry.get_labware_grip_point( - labware_id="my-teleporting-labware", location=to_location + labware_definition=sentinel.my_teleporting_labware_def, location=to_location ) ).then_return(Point(201, 202, 219.5)) mock_tc_context_manager = decoy.mock(name="mock_tc_context_manager") diff --git a/api/tests/opentrons/protocol_engine/execution/test_movement_handler.py b/api/tests/opentrons/protocol_engine/execution/test_movement_handler.py index 75205b6e45d..73b293fdbef 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_movement_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_movement_handler.py @@ -1,7 +1,7 @@ """MovementHandler command subject.""" import pytest from decoy import Decoy -from typing import NamedTuple +from typing import NamedTuple, Optional from opentrons.types import MountType, Point, DeckSlotName, Mount from opentrons.hardware_control import API as HardwareAPI @@ -19,10 +19,10 @@ MotorAxis, AddressableOffsetVector, ) -from opentrons.protocol_engine.state import ( +from opentrons.protocol_engine.state.state import ( StateStore, - PipetteLocationData, ) +from opentrons.protocol_engine.state.motion import PipetteLocationData from opentrons.protocol_engine.execution.movement import MovementHandler from opentrons.protocol_engine.execution.thermocycler_movement_flagger import ( ThermocyclerMovementFlagger, @@ -149,6 +149,7 @@ async def test_move_to_well( current_well=None, force_direct=True, minimum_z_height=12.3, + operation_volume=None, ) ).then_return( [Waypoint(Point(1, 2, 3), CriticalPoint.XY_CENTER), Waypoint(Point(4, 5, 6))] @@ -257,6 +258,7 @@ async def test_move_to_well_from_starting_location( well_location=well_location, force_direct=False, minimum_z_height=None, + operation_volume=None, ) ).then_return([Waypoint(Point(1, 2, 3), CriticalPoint.XY_CENTER)]) @@ -297,6 +299,10 @@ async def test_move_to_well_from_starting_location( ) +@pytest.mark.parametrize( + "stay_at_max_z,z_extra_offset", + [(True, None), (True, 5.0), (False, None), (False, 5.0)], +) async def test_move_to_addressable_area( decoy: Decoy, state_store: StateStore, @@ -304,6 +310,8 @@ async def test_move_to_addressable_area( heater_shaker_movement_flagger: HeaterShakerMovementFlagger, mock_gantry_mover: GantryMover, subject: MovementHandler, + stay_at_max_z: bool, + z_extra_offset: Optional[float], ) -> None: """Move requests should call hardware controller with movement data.""" decoy.when( @@ -353,8 +361,9 @@ async def test_move_to_addressable_area( max_travel_z=42.0, force_direct=True, minimum_z_height=12.3, - stay_at_max_travel_z=True, + stay_at_max_travel_z=stay_at_max_z, ignore_tip_configuration=False, + max_travel_z_extra_margin=z_extra_offset, ) ).then_return( [Waypoint(Point(1, 2, 3), CriticalPoint.XY_CENTER), Waypoint(Point(4, 5, 6))] @@ -378,8 +387,9 @@ async def test_move_to_addressable_area( force_direct=True, minimum_z_height=12.3, speed=45.6, - stay_at_highest_possible_z=True, + stay_at_highest_possible_z=stay_at_max_z, ignore_tip_configuration=False, + highest_possible_z_extra_offset=z_extra_offset, ) assert result == Point(x=4, y=5, z=6) diff --git a/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py b/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py index b087084abff..84a425b88fc 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_pipetting_handler.py @@ -8,7 +8,8 @@ from opentrons.hardware_control import API as HardwareAPI from opentrons.hardware_control.dev_types import PipetteDict -from opentrons.protocol_engine.state import StateView, HardwarePipette +from opentrons.protocol_engine.state.state import StateView +from opentrons.protocol_engine.state.pipettes import HardwarePipette from opentrons.protocol_engine.types import TipGeometry from opentrons.protocol_engine.execution.pipetting import ( HardwarePipettingHandler, diff --git a/api/tests/opentrons/protocol_engine/execution/test_queue_worker.py b/api/tests/opentrons/protocol_engine/execution/test_queue_worker.py index aba78a1fb37..e625a5c26b8 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_queue_worker.py +++ b/api/tests/opentrons/protocol_engine/execution/test_queue_worker.py @@ -4,7 +4,7 @@ import pytest from decoy import Decoy, matchers -from opentrons.protocol_engine.state import StateStore +from opentrons.protocol_engine.state.state import StateStore from opentrons.protocol_engine.errors import RunStoppedError from opentrons.protocol_engine.execution import CommandExecutor, QueueWorker diff --git a/api/tests/opentrons/protocol_engine/execution/test_run_control_handler.py b/api/tests/opentrons/protocol_engine/execution/test_run_control_handler.py index b8a51537314..f21c133d485 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_run_control_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_run_control_handler.py @@ -6,7 +6,8 @@ from opentrons.protocol_engine.actions import ActionDispatcher, PauseAction, PauseSource from opentrons.protocol_engine.execution.run_control import RunControlHandler -from opentrons.protocol_engine.state import Config, StateStore +from opentrons.protocol_engine.state.config import Config +from opentrons.protocol_engine.state.state import StateStore from opentrons.protocol_engine.types import DeckType diff --git a/api/tests/opentrons/protocol_engine/execution/test_thermocycler_movement_flagger.py b/api/tests/opentrons/protocol_engine/execution/test_thermocycler_movement_flagger.py index ac8bf1743f4..415ff038b09 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_thermocycler_movement_flagger.py +++ b/api/tests/opentrons/protocol_engine/execution/test_thermocycler_movement_flagger.py @@ -8,7 +8,7 @@ from decoy import Decoy from opentrons.types import DeckSlotName -from opentrons.protocol_engine.state import StateStore +from opentrons.protocol_engine.state.state import StateStore from opentrons.protocol_engine.state.module_substates.thermocycler_module_substate import ( ThermocyclerModuleId, ThermocyclerModuleSubState, diff --git a/api/tests/opentrons/protocol_engine/execution/test_thermocycler_plate_lifter.py b/api/tests/opentrons/protocol_engine/execution/test_thermocycler_plate_lifter.py index 67f8cee04b8..7f05fd070a8 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_thermocycler_plate_lifter.py +++ b/api/tests/opentrons/protocol_engine/execution/test_thermocycler_plate_lifter.py @@ -13,8 +13,8 @@ from opentrons.protocol_engine.execution.thermocycler_plate_lifter import ( ThermocyclerPlateLifter, ) -from opentrons.protocol_engine.state import ( - StateStore, +from opentrons.protocol_engine.state.state import StateStore +from opentrons.protocol_engine.state.module_substates import ( ThermocyclerModuleId, ThermocyclerModuleSubState, ) diff --git a/api/tests/opentrons/protocol_engine/execution/test_tip_handler.py b/api/tests/opentrons/protocol_engine/execution/test_tip_handler.py index dfd02e9dfd5..4e9a10fdfaa 100644 --- a/api/tests/opentrons/protocol_engine/execution/test_tip_handler.py +++ b/api/tests/opentrons/protocol_engine/execution/test_tip_handler.py @@ -1,18 +1,17 @@ """Pipetting execution handler.""" import pytest -from decoy import Decoy -from mock import AsyncMock, patch +from decoy import Decoy, matchers -from typing import Dict, ContextManager, Optional +from typing import Dict, ContextManager, Optional, OrderedDict from contextlib import nullcontext as does_not_raise -from opentrons.types import Mount, MountType +from opentrons.types import Mount, MountType, Point from opentrons.hardware_control import API as HardwareAPI from opentrons.hardware_control.types import TipStateType from opentrons.hardware_control.protocols.types import OT2RobotType, FlexRobotType from opentrons.protocols.models import LabwareDefinition -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.types import TipGeometry, TipPresenceStatus from opentrons.protocol_engine.resources import LabwareDataProvider from opentrons.protocol_engine.errors.exceptions import TipNotAttachedError @@ -25,6 +24,8 @@ VirtualTipHandler, create_tip_handler, ) +from opentrons.hardware_control.nozzle_manager import NozzleMap +from opentrons_shared_data.pipette.pipette_definition import ValidNozzleMaps @pytest.fixture @@ -53,6 +54,17 @@ def tip_rack_definition() -> LabwareDefinition: return LabwareDefinition.construct(namespace="test", version=42) # type: ignore[call-arg] +MOCK_MAP = NozzleMap.build( + physical_nozzles=OrderedDict({"A1": Point(0, 0, 0)}), + physical_rows=OrderedDict({"A": ["A1"]}), + physical_columns=OrderedDict({"1": ["A1"]}), + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A1", + valid_nozzle_maps=ValidNozzleMaps(maps={"Full": ["A1"]}), +) + + async def test_create_tip_handler( decoy: Decoy, mock_state_view: StateView, @@ -78,7 +90,6 @@ async def test_create_tip_handler( ) -@pytest.mark.ot3_only @pytest.mark.parametrize("tip_state", [TipStateType.PRESENT, TipStateType.ABSENT]) async def test_flex_pick_up_tip_state( decoy: Decoy, @@ -86,22 +97,26 @@ async def test_flex_pick_up_tip_state( mock_labware_data_provider: LabwareDataProvider, tip_rack_definition: LabwareDefinition, tip_state: TipStateType, + mock_hardware_api: HardwareAPI, ) -> None: """Test the protocol engine's pick_up_tip logic.""" - from opentrons.hardware_control.ot3api import OT3API - - ot3_hardware_api = decoy.mock(cls=OT3API) - decoy.when(ot3_hardware_api.get_robot_type()).then_return(FlexRobotType) - subject = HardwareTipHandler( state_view=mock_state_view, - hardware_api=ot3_hardware_api, + hardware_api=mock_hardware_api, labware_data_provider=mock_labware_data_provider, ) - decoy.when(subject._state_view.config.robot_type).then_return("OT-3 Standard") decoy.when(mock_state_view.pipettes.get_mount("pipette-id")).then_return( MountType.LEFT ) + decoy.when(mock_state_view.pipettes.get_serial_number("pipette-id")).then_return( + "pipette-serial" + ) + decoy.when(mock_state_view.labware.get_definition("labware-id")).then_return( + tip_rack_definition + ) + decoy.when( + mock_state_view.pipettes.get_nozzle_configuration("pipette-id") + ).then_return(MOCK_MAP) decoy.when( mock_state_view.geometry.get_nominal_tip_geometry( pipette_id="pipette-id", @@ -118,31 +133,34 @@ async def test_flex_pick_up_tip_state( ) ).then_return(42) - with patch.object( - ot3_hardware_api, "cache_tip", AsyncMock(spec=ot3_hardware_api.cache_tip) - ) as mock_add_tip: - - if tip_state == TipStateType.PRESENT: + if tip_state == TipStateType.PRESENT: + await subject.pick_up_tip( + pipette_id="pipette-id", + labware_id="labware-id", + well_name="B2", + ) + decoy.verify(mock_hardware_api.cache_tip(Mount.LEFT, 42), times=1) + else: + decoy.when( + await subject.verify_tip_presence( + pipette_id="pipette-id", expected=TipPresenceStatus.PRESENT + ) + ).then_raise(TipNotAttachedError()) + # if a TipNotAttchedError is caught, we should not add any tip information + with pytest.raises(TipNotAttachedError): await subject.pick_up_tip( pipette_id="pipette-id", labware_id="labware-id", well_name="B2", ) - mock_add_tip.assert_called_once() - else: - decoy.when( - await subject.verify_tip_presence( - pipette_id="pipette-id", expected=TipPresenceStatus.PRESENT - ) - ).then_raise(TipNotAttachedError()) - # if a TipNotAttchedError is caught, we should not add any tip information - with pytest.raises(TipNotAttachedError): - await subject.pick_up_tip( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="B2", - ) - mock_add_tip.assert_not_called() + decoy.verify( + mock_hardware_api.cache_tip( + mount=matchers.Anything(), + tip_length=matchers.Anything(), + ), + ignore_extra_args=True, + times=0, + ) async def test_pick_up_tip( @@ -171,6 +189,10 @@ async def test_pick_up_tip( MountType.LEFT ) + decoy.when( + mock_state_view.pipettes.get_nozzle_configuration(pipette_id="pipette-id") + ).then_return(MOCK_MAP) + decoy.when( mock_state_view.geometry.get_nominal_tip_geometry( pipette_id="pipette-id", @@ -209,6 +231,8 @@ async def test_pick_up_tip( ) +# todo(mm, 2024-10-17): Test that when verify_tip_presence raises, +# the hardware API state is NOT updated. async def test_drop_tip( decoy: Decoy, mock_state_view: StateView, @@ -225,16 +249,24 @@ async def test_drop_tip( decoy.when(mock_state_view.pipettes.get_mount("pipette-id")).then_return( MountType.RIGHT ) + decoy.when( + mock_state_view.pipettes.get_nozzle_configuration("pipette-id") + ).then_return(MOCK_MAP) await subject.drop_tip(pipette_id="pipette-id", home_after=True) decoy.verify( - await mock_hardware_api.drop_tip(mount=Mount.RIGHT, home_after=True), - times=1, + await mock_hardware_api.tip_drop_moves(mount=Mount.RIGHT, home_after=True) + ) + decoy.verify(mock_hardware_api.remove_tip(mount=Mount.RIGHT)) + decoy.verify( + mock_hardware_api.set_current_tiprack_diameter( + mount=Mount.RIGHT, tiprack_diameter=0 + ) ) -async def test_add_tip( +def test_add_tip( decoy: Decoy, mock_state_view: StateView, mock_hardware_api: HardwareAPI, @@ -257,10 +289,10 @@ async def test_add_tip( MountType.LEFT ) - await subject.add_tip(pipette_id="pipette-id", tip=tip) + subject.cache_tip(pipette_id="pipette-id", tip=tip) decoy.verify( - await mock_hardware_api.add_tip(mount=Mount.LEFT, tip_length=50), + mock_hardware_api.cache_tip(mount=Mount.LEFT, tip_length=50), mock_hardware_api.set_current_tiprack_diameter( mount=Mount.LEFT, tiprack_diameter=5, @@ -269,6 +301,31 @@ async def test_add_tip( ) +def test_remove_tip( + decoy: Decoy, + mock_state_view: StateView, + mock_hardware_api: HardwareAPI, + mock_labware_data_provider: LabwareDataProvider, +) -> None: + """It should remove a tip manually from the hardware API.""" + subject = HardwareTipHandler( + state_view=mock_state_view, + hardware_api=mock_hardware_api, + labware_data_provider=mock_labware_data_provider, + ) + + decoy.when(mock_state_view.pipettes.get_mount("pipette-id")).then_return( + MountType.LEFT + ) + + subject.remove_tip(pipette_id="pipette-id") + + decoy.verify( + mock_hardware_api.remove_tip(Mount.LEFT), + mock_hardware_api.set_current_tiprack_diameter(Mount.LEFT, 0), + ) + + @pytest.mark.parametrize( argnames=[ "test_channels", @@ -499,6 +556,11 @@ async def test_verify_tip_presence_on_ot3( decoy.when(mock_state_view.pipettes.get_mount("pipette-id")).then_return( MountType.LEFT ) + + decoy.when( + mock_state_view.pipettes.get_nozzle_configuration("pipette-id") + ).then_return(MOCK_MAP) + await subject.verify_tip_presence("pipette-id", expected, None) decoy.verify( diff --git a/api/tests/opentrons/protocol_engine/mock_circular_frusta.py b/api/tests/opentrons/protocol_engine/mock_circular_frusta.py new file mode 100644 index 00000000000..7586cb604b8 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/mock_circular_frusta.py @@ -0,0 +1,215 @@ +"""Mock representations of potential circular frusta.""" +""" +These are circular frusta whose radii either decay or grow, but always at a constant rate. +Height always decays from the max height to 0 in increments of 1. +""" +example_1 = { + "height": [ + 26, + 25, + 24, + 23, + 22, + 21, + 20, + 19, + 18, + 17, + 16, + 15, + 14, + 13, + 12, + 11, + 10, + 9, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1, + 0, + ], + "radius": [ + 8, + 10, + 12, + 14, + 16, + 18, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, + 48, + 50, + 52, + 54, + 56, + 58, + 60, + ], +} +example_2 = { + "height": [18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], + "radius": [ + 67, + 68.5, + 70, + 71.5, + 73, + 74.5, + 76, + 77.5, + 79, + 80.5, + 82, + 83.5, + 85, + 86.5, + 88, + 89.5, + 91, + 92.5, + 94, + ], +} +example_3 = { + "height": [ + 20, + 19, + 18, + 17, + 16, + 15, + 14, + 13, + 12, + 11, + 10, + 9, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1, + 0, + ], + "radius": [ + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + ], +} +example_4 = { + "height": [ + 34, + 33, + 32, + 31, + 30, + 29, + 28, + 27, + 26, + 25, + 24, + 23, + 22, + 21, + 20, + 19, + 18, + 17, + 16, + 15, + 14, + 13, + 12, + 11, + 10, + 9, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1, + 0, + ], + "radius": [ + 280, + 274.5, + 269, + 263.5, + 258, + 252.5, + 247, + 241.5, + 236, + 230.5, + 225, + 219.5, + 214, + 208.5, + 203, + 197.5, + 192, + 186.5, + 181, + 175.5, + 170, + 164.5, + 159, + 153.5, + 148, + 142.5, + 137, + 131.5, + 126, + 120.5, + 115, + 109.5, + 104, + 98.5, + 93, + ], +} + +TEST_EXAMPLES = [example_1, example_2, example_3, example_4] diff --git a/api/tests/opentrons/protocol_engine/mock_rectangular_frusta.py b/api/tests/opentrons/protocol_engine/mock_rectangular_frusta.py new file mode 100644 index 00000000000..56951ff3219 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/mock_rectangular_frusta.py @@ -0,0 +1,326 @@ +"""Mock representations of potential rectangular frusta.""" +""" +These are rectangular frusta whose length and width decay regularly, though not necessarily at the same rate. +Height always decays from the max height to 0 in increments of 1. +This has frusta with widths and lengths that both grow and decay with respect to positive change in height. +""" +example_1 = { + "height": [ + 26, + 25, + 24, + 23, + 22, + 21, + 20, + 19, + 18, + 17, + 16, + 15, + 14, + 13, + 12, + 11, + 10, + 9, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1, + 0, + ], + "length": [ + 8, + 10, + 12, + 14, + 16, + 18, + 20, + 22, + 24, + 26, + 28, + 30, + 32, + 34, + 36, + 38, + 40, + 42, + 44, + 46, + 48, + 50, + 52, + 54, + 56, + 58, + 60, + ], + "width": [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + ], +} +example_2 = { + "height": [18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], + "length": [ + 67, + 68.5, + 70, + 71.5, + 73, + 74.5, + 76, + 77.5, + 79, + 80.5, + 82, + 83.5, + 85, + 86.5, + 88, + 89.5, + 91, + 92.5, + 94, + ], + "width": [ + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + 50, + ], +} +example_3 = { + "height": [ + 20, + 19, + 18, + 17, + 16, + 15, + 14, + 13, + 12, + 11, + 10, + 9, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1, + 0, + ], + "length": [ + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + ], + "width": [ + 600, + 575, + 550, + 525, + 500, + 475, + 450, + 425, + 400, + 375, + 350, + 325, + 300, + 275, + 250, + 225, + 200, + 175, + 150, + 125, + 100, + ], +} +example_4 = { + "height": [ + 34, + 33, + 32, + 31, + 30, + 29, + 28, + 27, + 26, + 25, + 24, + 23, + 22, + 21, + 20, + 19, + 18, + 17, + 16, + 15, + 14, + 13, + 12, + 11, + 10, + 9, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1, + 0, + ], + "length": [ + 280, + 274.5, + 269, + 263.5, + 258, + 252.5, + 247, + 241.5, + 236, + 230.5, + 225, + 219.5, + 214, + 208.5, + 203, + 197.5, + 192, + 186.5, + 181, + 175.5, + 170, + 164.5, + 159, + 153.5, + 148, + 142.5, + 137, + 131.5, + 126, + 120.5, + 115, + 109.5, + 104, + 98.5, + 93, + ], + "width": [ + 280, + 274.5, + 269, + 263.5, + 258, + 252.5, + 247, + 241.5, + 236, + 230.5, + 225, + 219.5, + 214, + 208.5, + 203, + 197.5, + 192, + 186.5, + 181, + 175.5, + 170, + 164.5, + 159, + 153.5, + 148, + 142.5, + 137, + 131.5, + 126, + 120.5, + 115, + 109.5, + 104, + 98.5, + 93, + ], +} + +TEST_EXAMPLES = [example_1, example_2, example_3, example_4] diff --git a/api/tests/opentrons/protocol_engine/resources/test_deck_data_provider.py b/api/tests/opentrons/protocol_engine/resources/test_deck_data_provider.py index 3c8552cdd6f..e051f155113 100644 --- a/api/tests/opentrons/protocol_engine/resources/test_deck_data_provider.py +++ b/api/tests/opentrons/protocol_engine/resources/test_deck_data_provider.py @@ -10,8 +10,6 @@ from opentrons.protocol_engine.types import ( DeckSlotLocation, DeckType, - DeckConfigurationType, - AddressableAreaLocation, ) from opentrons.protocol_engine.resources import ( LabwareDataProvider, @@ -135,56 +133,3 @@ async def test_get_deck_labware_fixtures_ot3_standard( definition=ot3_fixed_trash_def, ) ] - - -def _make_deck_config_with_plate_reader() -> DeckConfigurationType: - return [ - ("cutoutA1", "singleLeftSlot", None), - ("cutoutB1", "singleLeftSlot", None), - ("cutoutC1", "singleLeftSlot", None), - ("cutoutD1", "singleLeftSlot", None), - ("cutoutA2", "singleCenterSlot", None), - ("cutoutB2", "singleCenterSlot", None), - ("cutoutC2", "singleCenterSlot", None), - ("cutoutD2", "singleCenterSlot", None), - ("cutoutA3", "singleRightSlot", None), - ("cutoutB3", "singleRightSlot", None), - ("cutoutC3", "singleRightSlot", None), - ("cutoutD3", "absorbanceReaderV1", "abc123"), - ] - - -async def test_get_deck_labware_fixtures_ot3_standard_for_plate_reader( - decoy: Decoy, - ot3_standard_deck_def: DeckDefinitionV5, - ot3_absorbance_reader_lid: LabwareDefinition, - mock_labware_data_provider: LabwareDataProvider, -) -> None: - """It should get a lis including the Plate Reader Lid for our deck fixed labware.""" - subject = DeckDataProvider( - deck_type=DeckType.OT3_STANDARD, labware_data=mock_labware_data_provider - ) - - decoy.when( - await mock_labware_data_provider.get_labware_definition( - load_name="opentrons_flex_lid_absorbance_plate_reader_module", - namespace="opentrons", - version=1, - ) - ).then_return(ot3_absorbance_reader_lid) - - deck_config = _make_deck_config_with_plate_reader() - - result = await subject.get_deck_fixed_labware( - False, ot3_standard_deck_def, deck_config - ) - - assert result == [ - DeckFixedLabware( - labware_id="absorbanceReaderV1LidD3", - location=AddressableAreaLocation( - addressableAreaName="absorbanceReaderV1D3" - ), - definition=ot3_absorbance_reader_lid, - ) - ] diff --git a/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py b/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py index 086b3ec297b..ae3d78d2230 100644 --- a/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py +++ b/api/tests/opentrons/protocol_engine/resources/test_pipette_data_provider.py @@ -7,6 +7,7 @@ from opentrons_shared_data.pipette.pipette_definition import ( PipetteBoundingBoxOffsetDefinition, TIP_OVERLAP_VERSION_MAXIMUM, + AvailableSensorDefinition, ) from opentrons.hardware_control.dev_types import PipetteDict @@ -24,6 +25,12 @@ from opentrons.types import Point +@pytest.fixture +def available_sensors() -> AvailableSensorDefinition: + """Provide a list of sensors.""" + return AvailableSensorDefinition(sensors=["pressure", "capacitive", "environment"]) + + @pytest.fixture def subject_instance() -> VirtualPipetteDataProvider: """Instance of a VirtualPipetteDataProvider for test.""" @@ -32,6 +39,7 @@ def subject_instance() -> VirtualPipetteDataProvider: def test_get_virtual_pipette_static_config( subject_instance: VirtualPipetteDataProvider, + available_sensors: AvailableSensorDefinition, ) -> None: """It should return config data given a pipette name.""" result = subject_instance.get_virtual_pipette_static_config( @@ -65,11 +73,20 @@ def test_get_virtual_pipette_static_config( back_left_corner_offset=Point(0, 0, 10.45), front_right_corner_offset=Point(0, 0, 10.45), pipette_lld_settings={}, + plunger_positions={ + "top": 19.5, + "bottom": -8.5, + "blow_out": -13.0, + "drop_tip": -27.0, + }, + shaft_ul_per_mm=0.785, + available_sensors=AvailableSensorDefinition(sensors=[]), ) def test_configure_virtual_pipette_for_volume( subject_instance: VirtualPipetteDataProvider, + available_sensors: AvailableSensorDefinition, ) -> None: """It should return an updated config if the liquid class changes.""" result1 = subject_instance.get_virtual_pipette_static_config( @@ -94,6 +111,14 @@ def test_configure_virtual_pipette_for_volume( back_left_corner_offset=Point(-8.0, -22.0, -259.15), front_right_corner_offset=Point(-8.0, -22.0, -259.15), pipette_lld_settings={"t50": {"minHeight": 1.0, "minVolume": 0.0}}, + plunger_positions={ + "top": 0.0, + "bottom": 71.5, + "blow_out": 76.5, + "drop_tip": 90.5, + }, + shaft_ul_per_mm=0.785, + available_sensors=available_sensors, ) subject_instance.configure_virtual_pipette_for_volume( "my-pipette", 1, result1.model @@ -120,11 +145,20 @@ def test_configure_virtual_pipette_for_volume( back_left_corner_offset=Point(-8.0, -22.0, -259.15), front_right_corner_offset=Point(-8.0, -22.0, -259.15), pipette_lld_settings={"t50": {"minHeight": 1.0, "minVolume": 0.0}}, + plunger_positions={ + "top": 0.0, + "bottom": 61.5, + "blow_out": 76.5, + "drop_tip": 90.5, + }, + shaft_ul_per_mm=0.785, + available_sensors=available_sensors, ) def test_load_virtual_pipette_by_model_string( subject_instance: VirtualPipetteDataProvider, + available_sensors: AvailableSensorDefinition, ) -> None: """It should return config data given a pipette model.""" result = subject_instance.get_virtual_pipette_static_config_by_model_string( @@ -149,6 +183,14 @@ def test_load_virtual_pipette_by_model_string( back_left_corner_offset=Point(-16.0, 43.15, 35.52), front_right_corner_offset=Point(16.0, -43.15, 35.52), pipette_lld_settings={}, + plunger_positions={ + "top": 19.5, + "bottom": -14.5, + "blow_out": -19.0, + "drop_tip": -33.4, + }, + shaft_ul_per_mm=9.621, + available_sensors=AvailableSensorDefinition(sensors=[]), ) @@ -193,6 +235,7 @@ def test_load_virtual_pipette_nozzle_layout( @pytest.fixture def pipette_dict( supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, ) -> PipetteDict: """Get a pipette dict.""" return { @@ -246,6 +289,9 @@ def pipette_dict( "t200": {"minHeight": 0.5, "minVolume": 0}, "t1000": {"minHeight": 0.5, "minVolume": 0}, }, + "plunger_positions": {"top": 100, "bottom": 20, "blow_out": 10, "drop_tip": 0}, + "shaft_ul_per_mm": 5.0, + "available_sensors": available_sensors, } @@ -263,6 +309,7 @@ def test_get_pipette_static_config( pipette_dict: PipetteDict, tip_overlap_version: str, overlap_data: Dict[str, float], + available_sensors: AvailableSensorDefinition, ) -> None: """It should return config data given a PipetteDict.""" result = subject.get_pipette_static_config(pipette_dict, tip_overlap_version) @@ -292,6 +339,9 @@ def test_get_pipette_static_config( "t200": {"minHeight": 0.5, "minVolume": 0}, "t1000": {"minHeight": 0.5, "minVolume": 0}, }, + plunger_positions={"top": 100, "bottom": 20, "blow_out": 10, "drop_tip": 0}, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ) diff --git a/api/tests/opentrons/protocol_engine/state/command_fixtures.py b/api/tests/opentrons/protocol_engine/state/command_fixtures.py index 845b33f18d8..5ac522095f2 100644 --- a/api/tests/opentrons/protocol_engine/state/command_fixtures.py +++ b/api/tests/opentrons/protocol_engine/state/command_fixtures.py @@ -1,7 +1,7 @@ """Command factories to use in tests as data fixtures.""" from datetime import datetime from pydantic import BaseModel -from typing import Optional, cast +from typing import Optional, cast, Dict from opentrons_shared_data.pipette.types import PipetteNameType from opentrons.types import MountType @@ -13,6 +13,7 @@ ModuleDefinition, MovementAxis, WellLocation, + LiquidHandlingWellLocation, LabwareLocation, DeckSlotLocation, LabwareMovementStrategy, @@ -113,6 +114,22 @@ def create_succeeded_command( ) +def create_comment_command(command_id: str = "command-id") -> cmd.Comment: + """Create a completed LoadLabware command.""" + params = cmd.CommentParams(message="hello world!") + + result = cmd.CommentResult() + + return cmd.Comment( + id=command_id, + key="command-key", + status=cmd.CommandStatus.SUCCEEDED, + createdAt=datetime.now(), + params=params, + result=result, + ) + + def create_load_labware_command( labware_id: str, location: LabwareLocation, @@ -195,7 +212,7 @@ def create_aspirate_command( flow_rate: float, labware_id: str = "labware-id", well_name: str = "A1", - well_location: Optional[WellLocation] = None, + well_location: Optional[LiquidHandlingWellLocation] = None, destination: DeckPoint = DeckPoint(x=0, y=0, z=0), ) -> cmd.Aspirate: """Get a completed Aspirate command.""" @@ -203,7 +220,7 @@ def create_aspirate_command( pipetteId=pipette_id, labwareId=labware_id, wellName=well_name, - wellLocation=well_location or WellLocation(), + wellLocation=well_location or LiquidHandlingWellLocation(), volume=volume, flowRate=flow_rate, ) @@ -248,7 +265,7 @@ def create_dispense_command( flow_rate: float, labware_id: str = "labware-id", well_name: str = "A1", - well_location: Optional[WellLocation] = None, + well_location: Optional[LiquidHandlingWellLocation] = None, destination: DeckPoint = DeckPoint(x=0, y=0, z=0), ) -> cmd.Dispense: """Get a completed Dispense command.""" @@ -256,7 +273,7 @@ def create_dispense_command( pipetteId=pipette_id, labwareId=labware_id, wellName=well_name, - wellLocation=well_location or WellLocation(), + wellLocation=well_location or LiquidHandlingWellLocation(), volume=volume, flowRate=flow_rate, ) @@ -295,6 +312,55 @@ def create_dispense_in_place_command( ) +def create_liquid_probe_command( + pipette_id: str = "pippete-id", + labware_id: str = "labware-id", + well_name: str = "well-name", + well_location: Optional[WellLocation] = None, + destination: DeckPoint = DeckPoint(x=0, y=0, z=0), +) -> cmd.LiquidProbe: + """Get a completed Liquid Probe command.""" + params = cmd.LiquidProbeParams( + pipetteId=pipette_id, + labwareId=labware_id, + wellName=well_name, + wellLocation=well_location or WellLocation(), + ) + result = cmd.LiquidProbeResult(position=destination, z_position=0.5) + + return cmd.LiquidProbe( + id="command-id", + key="command-key", + status=cmd.CommandStatus.SUCCEEDED, + createdAt=datetime.now(), + params=params, + result=result, + ) + + +def create_load_liquid_command( + liquid_id: str = "liquid-id", + labware_id: str = "labware-id", + volume_by_well: Dict[str, float] = {"A1": 30, "B2": 100}, +) -> cmd.LoadLiquid: + """Get a completed Load Liquid command.""" + params = cmd.LoadLiquidParams( + liquidId=liquid_id, + labwareId=labware_id, + volumeByWell=volume_by_well, + ) + result = cmd.LoadLiquidResult() + + return cmd.LoadLiquid( + id="command-id", + key="command-key", + status=cmd.CommandStatus.SUCCEEDED, + createdAt=datetime.now(), + params=params, + result=result, + ) + + def create_pick_up_tip_command( pipette_id: str, labware_id: str = "labware-id", diff --git a/api/tests/opentrons/protocol_engine/state/test_addressable_area_state.py b/api/tests/opentrons/protocol_engine/state/test_addressable_area_state.py index 987db0dcba3..da3e0f3d156 100644 --- a/api/tests/opentrons/protocol_engine/state/test_addressable_area_state.py +++ b/api/tests/opentrons/protocol_engine/state/test_addressable_area_state.py @@ -33,6 +33,12 @@ def test_deck_configuration_setting( "robotType": "OT-3 Standard", "models": ["OT-3 Standard"], "extents": [477.2, 493.8, 0.0], + "paddingOffsets": { + "rear": -177.42, + "front": 51.8, + "leftSide": 31.88, + "rightSide": -80.32, + }, "mountOffsets": { "left": [-13.5, -60.5, 255.675], "right": [40.5, -60.5, 255.675], diff --git a/api/tests/opentrons/protocol_engine/state/test_addressable_area_store.py b/api/tests/opentrons/protocol_engine/state/test_addressable_area_store.py deleted file mode 100644 index 9c098cf1c96..00000000000 --- a/api/tests/opentrons/protocol_engine/state/test_addressable_area_store.py +++ /dev/null @@ -1,301 +0,0 @@ -"""Addressable area state store tests. - -DEPRECATED: Testing AddressableAreaStore independently of AddressableAreaView is no -longer helpful. Add new tests to test_addressable_area_state.py, where they can be -tested together. -""" - -import pytest - -from opentrons_shared_data.deck.types import DeckDefinitionV5 -from opentrons_shared_data.labware.labware_definition import Parameters -from opentrons.protocols.models import LabwareDefinition -from opentrons.types import DeckSlotName - -from opentrons.protocol_engine.commands import Command -from opentrons.protocol_engine.actions import ( - SucceedCommandAction, - AddAddressableAreaAction, -) -from opentrons.protocol_engine.state import Config -from opentrons.protocol_engine.state.addressable_areas import ( - AddressableAreaStore, - AddressableAreaState, -) -from opentrons.protocol_engine.types import ( - DeckType, - DeckConfigurationType, - ModuleModel, - LabwareMovementStrategy, - DeckSlotLocation, - AddressableAreaLocation, -) - -from .command_fixtures import ( - create_load_labware_command, - create_load_module_command, - create_move_labware_command, - create_move_to_addressable_area_command, -) - - -def _make_deck_config() -> DeckConfigurationType: - return [ - ("cutoutA1", "singleLeftSlot", None), - ("cutoutB1", "singleLeftSlot", None), - ("cutoutC1", "singleLeftSlot", None), - ("cutoutD1", "singleLeftSlot", None), - ("cutoutA2", "singleCenterSlot", None), - ("cutoutB2", "singleCenterSlot", None), - ("cutoutC2", "singleCenterSlot", None), - ("cutoutD2", "singleCenterSlot", None), - ("cutoutA3", "trashBinAdapter", None), - ("cutoutB3", "singleRightSlot", None), - ("cutoutC3", "stagingAreaRightSlot", None), - ("cutoutD3", "wasteChuteRightAdapterNoCover", None), - ] - - -@pytest.fixture -def simulated_subject( - ot3_standard_deck_def: DeckDefinitionV5, -) -> AddressableAreaStore: - """Get an AddressableAreaStore test subject, under simulated deck conditions.""" - return AddressableAreaStore( - deck_configuration=[], - config=Config( - use_simulated_deck_config=True, - robot_type="OT-3 Standard", - deck_type=DeckType.OT3_STANDARD, - ), - deck_definition=ot3_standard_deck_def, - robot_definition={ - "displayName": "OT-3", - "robotType": "OT-3 Standard", - "models": ["OT-3 Standard"], - "extents": [477.2, 493.8, 0.0], - "mountOffsets": { - "left": [-13.5, -60.5, 255.675], - "right": [40.5, -60.5, 255.675], - "gripper": [84.55, -12.75, 93.85], - }, - }, - ) - - -@pytest.fixture -def subject( - ot3_standard_deck_def: DeckDefinitionV5, -) -> AddressableAreaStore: - """Get an AddressableAreaStore test subject.""" - return AddressableAreaStore( - deck_configuration=_make_deck_config(), - config=Config( - use_simulated_deck_config=False, - robot_type="OT-3 Standard", - deck_type=DeckType.OT3_STANDARD, - ), - deck_definition=ot3_standard_deck_def, - robot_definition={ - "displayName": "OT-3", - "robotType": "OT-3 Standard", - "models": ["OT-3 Standard"], - "extents": [477.2, 493.8, 0.0], - "mountOffsets": { - "left": [-13.5, -60.5, 255.675], - "right": [40.5, -60.5, 255.675], - "gripper": [84.55, -12.75, 93.85], - }, - }, - ) - - -def test_initial_state_simulated( - ot3_standard_deck_def: DeckDefinitionV5, - simulated_subject: AddressableAreaStore, -) -> None: - """It should create the Addressable Area store with no loaded addressable areas.""" - assert simulated_subject.state == AddressableAreaState( - loaded_addressable_areas_by_name={}, - potential_cutout_fixtures_by_cutout_id={}, - deck_definition=ot3_standard_deck_def, - deck_configuration=[], - robot_type="OT-3 Standard", - use_simulated_deck_config=True, - robot_definition={ - "displayName": "OT-3", - "robotType": "OT-3 Standard", - "models": ["OT-3 Standard"], - "extents": [477.2, 493.8, 0.0], - "mountOffsets": { - "left": [-13.5, -60.5, 255.675], - "right": [40.5, -60.5, 255.675], - "gripper": [84.55, -12.75, 93.85], - }, - }, - ) - - -def test_initial_state( - ot3_standard_deck_def: DeckDefinitionV5, - subject: AddressableAreaStore, -) -> None: - """It should create the Addressable Area store with loaded addressable areas.""" - assert subject.state.potential_cutout_fixtures_by_cutout_id == {} - assert not subject.state.use_simulated_deck_config - assert subject.state.deck_definition == ot3_standard_deck_def - assert subject.state.deck_configuration == _make_deck_config() - # Loading 9 regular slots, 1 trash, 2 Staging Area slots and 4 waste chute types - assert len(subject.state.loaded_addressable_areas_by_name) == 16 - - -@pytest.mark.parametrize( - ("command", "expected_area"), - ( - ( - create_load_labware_command( - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1), - labware_id="test-labware-id", - definition=LabwareDefinition.construct( # type: ignore[call-arg] - parameters=Parameters.construct(loadName="blah"), # type: ignore[call-arg] - namespace="bleh", - version=123, - ), - offset_id="offset-id", - display_name="display-name", - ), - "A1", - ), - ( - create_load_labware_command( - location=AddressableAreaLocation(addressableAreaName="A4"), - labware_id="test-labware-id", - definition=LabwareDefinition.construct( # type: ignore[call-arg] - parameters=Parameters.construct(loadName="blah"), # type: ignore[call-arg] - namespace="bleh", - version=123, - ), - offset_id="offset-id", - display_name="display-name", - ), - "A4", - ), - ( - create_load_module_command( - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1), - module_id="test-module-id", - model=ModuleModel.TEMPERATURE_MODULE_V2, - ), - "A1", - ), - ( - create_move_labware_command( - new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1), - strategy=LabwareMovementStrategy.USING_GRIPPER, - ), - "A1", - ), - ( - create_move_labware_command( - new_location=AddressableAreaLocation(addressableAreaName="A4"), - strategy=LabwareMovementStrategy.USING_GRIPPER, - ), - "A4", - ), - ( - create_move_to_addressable_area_command( - pipette_id="pipette-id", addressable_area_name="gripperWasteChute" - ), - "gripperWasteChute", - ), - ), -) -def test_addressable_area_referencing_commands_load_on_simulated_deck( - command: Command, - expected_area: str, - simulated_subject: AddressableAreaStore, -) -> None: - """It should check and store the addressable area when referenced in a command.""" - simulated_subject.handle_action( - SucceedCommandAction(private_result=None, command=command) - ) - assert expected_area in simulated_subject.state.loaded_addressable_areas_by_name - - -@pytest.mark.parametrize( - ("command", "expected_area"), - ( - ( - create_load_labware_command( - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1), - labware_id="test-labware-id", - definition=LabwareDefinition.construct( # type: ignore[call-arg] - parameters=Parameters.construct(loadName="blah"), # type: ignore[call-arg] - namespace="bleh", - version=123, - ), - offset_id="offset-id", - display_name="display-name", - ), - "A1", - ), - ( - create_load_labware_command( - location=AddressableAreaLocation(addressableAreaName="C4"), - labware_id="test-labware-id", - definition=LabwareDefinition.construct( # type: ignore[call-arg] - parameters=Parameters.construct(loadName="blah"), # type: ignore[call-arg] - namespace="bleh", - version=123, - ), - offset_id="offset-id", - display_name="display-name", - ), - "C4", - ), - ( - create_load_module_command( - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1), - module_id="test-module-id", - model=ModuleModel.TEMPERATURE_MODULE_V2, - ), - "A1", - ), - ( - create_move_labware_command( - new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1), - strategy=LabwareMovementStrategy.USING_GRIPPER, - ), - "A1", - ), - ( - create_move_labware_command( - new_location=AddressableAreaLocation(addressableAreaName="C4"), - strategy=LabwareMovementStrategy.USING_GRIPPER, - ), - "C4", - ), - ), -) -def test_addressable_area_referencing_commands_load( - command: Command, - expected_area: str, - subject: AddressableAreaStore, -) -> None: - """It should check that the addressable area is in the deck config.""" - subject.handle_action(SucceedCommandAction(private_result=None, command=command)) - assert expected_area in subject.state.loaded_addressable_areas_by_name - - -def test_add_addressable_area_action( - simulated_subject: AddressableAreaStore, -) -> None: - """It should add the addressable area to the store.""" - simulated_subject.handle_action( - AddAddressableAreaAction( - addressable_area=AddressableAreaLocation( - addressableAreaName="movableTrashA1" - ) - ) - ) - assert "movableTrashA1" in simulated_subject.state.loaded_addressable_areas_by_name diff --git a/api/tests/opentrons/protocol_engine/state/test_addressable_area_store_old.py b/api/tests/opentrons/protocol_engine/state/test_addressable_area_store_old.py new file mode 100644 index 00000000000..b9e3e8f4e78 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_addressable_area_store_old.py @@ -0,0 +1,317 @@ +"""Addressable area state store tests. + +DEPRECATED: Testing AddressableAreaStore independently of AddressableAreaView is no +longer helpful. Try to add new tests to test_addressable_area_state.py, where they can be +tested together, treating AddressableAreaState as a private implementation detail. +""" + +import pytest + +from opentrons_shared_data.deck.types import DeckDefinitionV5 +from opentrons_shared_data.labware.labware_definition import Parameters +from opentrons.protocols.models import LabwareDefinition +from opentrons.types import DeckSlotName + +from opentrons.protocol_engine.commands import Command +from opentrons.protocol_engine.actions import ( + SucceedCommandAction, + AddAddressableAreaAction, +) +from opentrons.protocol_engine.state.config import Config +from opentrons.protocol_engine.state.addressable_areas import ( + AddressableAreaStore, + AddressableAreaState, +) +from opentrons.protocol_engine.types import ( + DeckType, + DeckConfigurationType, + ModuleModel, + LabwareMovementStrategy, + DeckSlotLocation, + AddressableAreaLocation, +) + +from .command_fixtures import ( + create_load_labware_command, + create_load_module_command, + create_move_labware_command, + create_move_to_addressable_area_command, +) + + +def _make_deck_config() -> DeckConfigurationType: + return [ + ("cutoutA1", "singleLeftSlot", None), + ("cutoutB1", "singleLeftSlot", None), + ("cutoutC1", "singleLeftSlot", None), + ("cutoutD1", "singleLeftSlot", None), + ("cutoutA2", "singleCenterSlot", None), + ("cutoutB2", "singleCenterSlot", None), + ("cutoutC2", "singleCenterSlot", None), + ("cutoutD2", "singleCenterSlot", None), + ("cutoutA3", "trashBinAdapter", None), + ("cutoutB3", "singleRightSlot", None), + ("cutoutC3", "stagingAreaRightSlot", None), + ("cutoutD3", "wasteChuteRightAdapterNoCover", None), + ] + + +@pytest.fixture +def simulated_subject( + ot3_standard_deck_def: DeckDefinitionV5, +) -> AddressableAreaStore: + """Get an AddressableAreaStore test subject, under simulated deck conditions.""" + return AddressableAreaStore( + deck_configuration=[], + config=Config( + use_simulated_deck_config=True, + robot_type="OT-3 Standard", + deck_type=DeckType.OT3_STANDARD, + ), + deck_definition=ot3_standard_deck_def, + robot_definition={ + "displayName": "OT-3", + "robotType": "OT-3 Standard", + "models": ["OT-3 Standard"], + "extents": [477.2, 493.8, 0.0], + "paddingOffsets": { + "rear": -177.42, + "front": 51.8, + "leftSide": 31.88, + "rightSide": -80.32, + }, + "mountOffsets": { + "left": [-13.5, -60.5, 255.675], + "right": [40.5, -60.5, 255.675], + "gripper": [84.55, -12.75, 93.85], + }, + }, + ) + + +@pytest.fixture +def subject( + ot3_standard_deck_def: DeckDefinitionV5, +) -> AddressableAreaStore: + """Get an AddressableAreaStore test subject.""" + return AddressableAreaStore( + deck_configuration=_make_deck_config(), + config=Config( + use_simulated_deck_config=False, + robot_type="OT-3 Standard", + deck_type=DeckType.OT3_STANDARD, + ), + deck_definition=ot3_standard_deck_def, + robot_definition={ + "displayName": "OT-3", + "robotType": "OT-3 Standard", + "models": ["OT-3 Standard"], + "extents": [477.2, 493.8, 0.0], + "paddingOffsets": { + "rear": -177.42, + "front": 51.8, + "leftSide": 31.88, + "rightSide": -80.32, + }, + "mountOffsets": { + "left": [-13.5, -60.5, 255.675], + "right": [40.5, -60.5, 255.675], + "gripper": [84.55, -12.75, 93.85], + }, + }, + ) + + +def test_initial_state_simulated( + ot3_standard_deck_def: DeckDefinitionV5, + simulated_subject: AddressableAreaStore, +) -> None: + """It should create the Addressable Area store with no loaded addressable areas.""" + assert simulated_subject.state == AddressableAreaState( + loaded_addressable_areas_by_name={}, + potential_cutout_fixtures_by_cutout_id={}, + deck_definition=ot3_standard_deck_def, + deck_configuration=[], + robot_type="OT-3 Standard", + use_simulated_deck_config=True, + robot_definition={ + "displayName": "OT-3", + "robotType": "OT-3 Standard", + "models": ["OT-3 Standard"], + "extents": [477.2, 493.8, 0.0], + "paddingOffsets": { + "rear": -177.42, + "front": 51.8, + "leftSide": 31.88, + "rightSide": -80.32, + }, + "mountOffsets": { + "left": [-13.5, -60.5, 255.675], + "right": [40.5, -60.5, 255.675], + "gripper": [84.55, -12.75, 93.85], + }, + }, + ) + + +def test_initial_state( + ot3_standard_deck_def: DeckDefinitionV5, + subject: AddressableAreaStore, +) -> None: + """It should create the Addressable Area store with loaded addressable areas.""" + assert subject.state.potential_cutout_fixtures_by_cutout_id == {} + assert not subject.state.use_simulated_deck_config + assert subject.state.deck_definition == ot3_standard_deck_def + assert subject.state.deck_configuration == _make_deck_config() + # Loading 9 regular slots, 1 trash, 2 Staging Area slots and 4 waste chute types + assert len(subject.state.loaded_addressable_areas_by_name) == 16 + + +@pytest.mark.parametrize( + ("command", "expected_area"), + ( + ( + create_load_labware_command( + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1), + labware_id="test-labware-id", + definition=LabwareDefinition.construct( # type: ignore[call-arg] + parameters=Parameters.construct(loadName="blah"), # type: ignore[call-arg] + namespace="bleh", + version=123, + ), + offset_id="offset-id", + display_name="display-name", + ), + "A1", + ), + ( + create_load_labware_command( + location=AddressableAreaLocation(addressableAreaName="A4"), + labware_id="test-labware-id", + definition=LabwareDefinition.construct( # type: ignore[call-arg] + parameters=Parameters.construct(loadName="blah"), # type: ignore[call-arg] + namespace="bleh", + version=123, + ), + offset_id="offset-id", + display_name="display-name", + ), + "A4", + ), + ( + create_load_module_command( + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1), + module_id="test-module-id", + model=ModuleModel.TEMPERATURE_MODULE_V2, + ), + "A1", + ), + ( + create_move_labware_command( + new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1), + strategy=LabwareMovementStrategy.USING_GRIPPER, + ), + "A1", + ), + ( + create_move_labware_command( + new_location=AddressableAreaLocation(addressableAreaName="A4"), + strategy=LabwareMovementStrategy.USING_GRIPPER, + ), + "A4", + ), + ( + create_move_to_addressable_area_command( + pipette_id="pipette-id", addressable_area_name="gripperWasteChute" + ), + "gripperWasteChute", + ), + ), +) +def test_addressable_area_referencing_commands_load_on_simulated_deck( + command: Command, + expected_area: str, + simulated_subject: AddressableAreaStore, +) -> None: + """It should check and store the addressable area when referenced in a command.""" + simulated_subject.handle_action(SucceedCommandAction(command=command)) + assert expected_area in simulated_subject.state.loaded_addressable_areas_by_name + + +@pytest.mark.parametrize( + ("command", "expected_area"), + ( + ( + create_load_labware_command( + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1), + labware_id="test-labware-id", + definition=LabwareDefinition.construct( # type: ignore[call-arg] + parameters=Parameters.construct(loadName="blah"), # type: ignore[call-arg] + namespace="bleh", + version=123, + ), + offset_id="offset-id", + display_name="display-name", + ), + "A1", + ), + ( + create_load_labware_command( + location=AddressableAreaLocation(addressableAreaName="C4"), + labware_id="test-labware-id", + definition=LabwareDefinition.construct( # type: ignore[call-arg] + parameters=Parameters.construct(loadName="blah"), # type: ignore[call-arg] + namespace="bleh", + version=123, + ), + offset_id="offset-id", + display_name="display-name", + ), + "C4", + ), + ( + create_load_module_command( + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1), + module_id="test-module-id", + model=ModuleModel.TEMPERATURE_MODULE_V2, + ), + "A1", + ), + ( + create_move_labware_command( + new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1), + strategy=LabwareMovementStrategy.USING_GRIPPER, + ), + "A1", + ), + ( + create_move_labware_command( + new_location=AddressableAreaLocation(addressableAreaName="C4"), + strategy=LabwareMovementStrategy.USING_GRIPPER, + ), + "C4", + ), + ), +) +def test_addressable_area_referencing_commands_load( + command: Command, + expected_area: str, + subject: AddressableAreaStore, +) -> None: + """It should check that the addressable area is in the deck config.""" + subject.handle_action(SucceedCommandAction(command=command)) + assert expected_area in subject.state.loaded_addressable_areas_by_name + + +def test_add_addressable_area_action( + simulated_subject: AddressableAreaStore, +) -> None: + """It should add the addressable area to the store.""" + simulated_subject.handle_action( + AddAddressableAreaAction( + addressable_area=AddressableAreaLocation( + addressableAreaName="movableTrashA1" + ) + ) + ) + assert "movableTrashA1" in simulated_subject.state.loaded_addressable_areas_by_name diff --git a/api/tests/opentrons/protocol_engine/state/test_addressable_area_view.py b/api/tests/opentrons/protocol_engine/state/test_addressable_area_view.py deleted file mode 100644 index 07552aa4273..00000000000 --- a/api/tests/opentrons/protocol_engine/state/test_addressable_area_view.py +++ /dev/null @@ -1,486 +0,0 @@ -"""Addressable area state view tests. - -DEPRECATED: Testing AddressableAreaView independently of AddressableAreaStore is no -longer helpful. Add new tests to test_addressable_area_state.py, where they can be -tested together. -""" - -import inspect - -import pytest -from decoy import Decoy -from typing import Dict, Set, Optional, cast - -from opentrons_shared_data.robot.types import RobotType -from opentrons_shared_data.deck.types import DeckDefinitionV5 -from opentrons.types import Point, DeckSlotName - -from opentrons.protocol_engine.errors import ( - AreaNotInDeckConfigurationError, - IncompatibleAddressableAreaError, - SlotDoesNotExistError, - AddressableAreaDoesNotExistError, -) -from opentrons.protocol_engine.resources import deck_configuration_provider -from opentrons.protocol_engine.state.addressable_areas import ( - AddressableAreaState, - AddressableAreaView, -) -from opentrons.protocol_engine.types import ( - AddressableArea, - AreaType, - DeckConfigurationType, - PotentialCutoutFixture, - Dimensions, - DeckPoint, - AddressableOffsetVector, -) - - -@pytest.fixture(autouse=True) -def patch_mock_deck_configuration_provider( - decoy: Decoy, monkeypatch: pytest.MonkeyPatch -) -> None: - """Mock out deck_configuration_provider.py functions.""" - for name, func in inspect.getmembers( - deck_configuration_provider, inspect.isfunction - ): - monkeypatch.setattr(deck_configuration_provider, name, decoy.mock(func=func)) - - -def get_addressable_area_view( - loaded_addressable_areas_by_name: Optional[Dict[str, AddressableArea]] = None, - potential_cutout_fixtures_by_cutout_id: Optional[ - Dict[str, Set[PotentialCutoutFixture]] - ] = None, - deck_definition: Optional[DeckDefinitionV5] = None, - deck_configuration: Optional[DeckConfigurationType] = None, - robot_type: RobotType = "OT-3 Standard", - use_simulated_deck_config: bool = False, -) -> AddressableAreaView: - """Get a labware view test subject.""" - state = AddressableAreaState( - loaded_addressable_areas_by_name=loaded_addressable_areas_by_name or {}, - potential_cutout_fixtures_by_cutout_id=potential_cutout_fixtures_by_cutout_id - or {}, - deck_definition=deck_definition or cast(DeckDefinitionV5, {"otId": "fake"}), - robot_definition={ - "displayName": "OT-3", - "robotType": "OT-3 Standard", - "models": ["OT-3 Standard"], - "extents": [477.2, 493.8, 0.0], - "mountOffsets": { - "left": [-13.5, -60.5, 255.675], - "right": [40.5, -60.5, 255.675], - "gripper": [84.55, -12.75, 93.85], - }, - }, - deck_configuration=deck_configuration or [], - robot_type=robot_type, - use_simulated_deck_config=use_simulated_deck_config, - ) - - return AddressableAreaView(state=state) - - -def test_get_all_cutout_fixtures_simulated_deck_config() -> None: - """It should return no cutout fixtures when the deck config is simulated.""" - subject = get_addressable_area_view( - deck_configuration=None, - use_simulated_deck_config=True, - ) - assert subject.get_all_cutout_fixtures() is None - - -def test_get_all_cutout_fixtures_non_simulated_deck_config() -> None: - """It should return the cutout fixtures from the deck config, if it's not simulated.""" - subject = get_addressable_area_view( - deck_configuration=[ - ("cutout-id-1", "cutout-fixture-id-1", None), - ("cutout-id-2", "cutout-fixture-id-2", None), - ], - use_simulated_deck_config=False, - ) - assert subject.get_all_cutout_fixtures() == [ - "cutout-fixture-id-1", - "cutout-fixture-id-2", - ] - - -def test_get_loaded_addressable_area() -> None: - """It should get the loaded addressable area.""" - addressable_area = AddressableArea( - area_name="area", - area_type=AreaType.SLOT, - base_slot=DeckSlotName.SLOT_D3, - display_name="fancy name", - bounding_box=Dimensions(x=1, y=2, z=3), - position=AddressableOffsetVector(x=7, y=8, z=9), - compatible_module_types=["magneticModuleType"], - ) - subject = get_addressable_area_view( - loaded_addressable_areas_by_name={"abc": addressable_area} - ) - - assert subject.get_addressable_area("abc") is addressable_area - - -def test_get_loaded_addressable_area_raises() -> None: - """It should raise if the addressable area does not exist.""" - subject = get_addressable_area_view() - - with pytest.raises(AreaNotInDeckConfigurationError): - subject.get_addressable_area("abc") - - -def test_get_addressable_area_for_simulation_already_loaded() -> None: - """It should get the addressable area for a simulation that has not been loaded yet.""" - addressable_area = AddressableArea( - area_name="area", - area_type=AreaType.SLOT, - base_slot=DeckSlotName.SLOT_D3, - display_name="fancy name", - bounding_box=Dimensions(x=1, y=2, z=3), - position=AddressableOffsetVector(x=7, y=8, z=9), - compatible_module_types=["magneticModuleType"], - ) - subject = get_addressable_area_view( - loaded_addressable_areas_by_name={"abc": addressable_area}, - use_simulated_deck_config=True, - ) - - assert subject.get_addressable_area("abc") is addressable_area - - -def test_get_addressable_area_for_simulation_not_loaded(decoy: Decoy) -> None: - """It should get the addressable area for a simulation that has not been loaded yet.""" - subject = get_addressable_area_view( - potential_cutout_fixtures_by_cutout_id={ - "cutoutA1": { - PotentialCutoutFixture( - cutout_id="cutoutA1", - cutout_fixture_id="blah", - provided_addressable_areas=frozenset(), - ) - } - }, - use_simulated_deck_config=True, - ) - - addressable_area = AddressableArea( - area_name="area", - area_type=AreaType.SLOT, - base_slot=DeckSlotName.SLOT_D3, - display_name="fancy name", - bounding_box=Dimensions(x=1, y=2, z=3), - position=AddressableOffsetVector(x=7, y=8, z=9), - compatible_module_types=["magneticModuleType"], - ) - - decoy.when( - deck_configuration_provider.get_potential_cutout_fixtures( - "abc", subject.state.deck_definition - ) - ).then_return( - ( - "cutoutA1", - { - PotentialCutoutFixture( - cutout_id="cutoutA1", - cutout_fixture_id="blah", - provided_addressable_areas=frozenset(), - ) - }, - ) - ) - - decoy.when( - deck_configuration_provider.get_cutout_position( - "cutoutA1", subject.state.deck_definition - ) - ).then_return(DeckPoint(x=1, y=2, z=3)) - - decoy.when( - deck_configuration_provider.get_addressable_area_from_name( - "abc", - DeckPoint(x=1, y=2, z=3), - DeckSlotName.SLOT_A1, - subject.state.deck_definition, - ) - ).then_return(addressable_area) - - assert subject.get_addressable_area("abc") is addressable_area - - -def test_get_addressable_area_for_simulation_raises(decoy: Decoy) -> None: - """It should raise if the requested addressable area is incompatible with loaded ones.""" - subject = get_addressable_area_view( - potential_cutout_fixtures_by_cutout_id={ - "123": { - PotentialCutoutFixture( - cutout_id="789", - cutout_fixture_id="bleh", - provided_addressable_areas=frozenset(), - ) - } - }, - use_simulated_deck_config=True, - ) - - decoy.when( - deck_configuration_provider.get_potential_cutout_fixtures( - "abc", subject.state.deck_definition - ) - ).then_return( - ( - "123", - { - PotentialCutoutFixture( - cutout_id="123", - cutout_fixture_id="blah", - provided_addressable_areas=frozenset(), - ) - }, - ) - ) - - decoy.when( - deck_configuration_provider.get_provided_addressable_area_names( - "bleh", "789", subject.state.deck_definition - ) - ).then_return([]) - - with pytest.raises(IncompatibleAddressableAreaError): - subject.get_addressable_area("abc") - - -def test_get_addressable_area_position() -> None: - """It should get the absolute location of the addressable area.""" - subject = get_addressable_area_view( - loaded_addressable_areas_by_name={ - "abc": AddressableArea( - area_name="area", - area_type=AreaType.SLOT, - base_slot=DeckSlotName.SLOT_D3, - display_name="fancy name", - bounding_box=Dimensions(x=10, y=20, z=30), - position=AddressableOffsetVector(x=1, y=2, z=3), - compatible_module_types=[], - ) - } - ) - - result = subject.get_addressable_area_position("abc") - assert result == Point(1, 2, 3) - - -def test_get_addressable_area_move_to_location() -> None: - """It should get the absolute location of an addressable area's move to location.""" - subject = get_addressable_area_view( - loaded_addressable_areas_by_name={ - "abc": AddressableArea( - area_name="area", - area_type=AreaType.SLOT, - base_slot=DeckSlotName.SLOT_D3, - display_name="fancy name", - bounding_box=Dimensions(x=10, y=20, z=30), - position=AddressableOffsetVector(x=1, y=2, z=3), - compatible_module_types=[], - ) - } - ) - - result = subject.get_addressable_area_move_to_location("abc") - assert result == Point(6, 12, 33) - - -def test_get_addressable_area_center() -> None: - """It should get the absolute location of an addressable area's center.""" - subject = get_addressable_area_view( - loaded_addressable_areas_by_name={ - "abc": AddressableArea( - area_name="area", - area_type=AreaType.SLOT, - base_slot=DeckSlotName.SLOT_D3, - display_name="fancy name", - bounding_box=Dimensions(x=10, y=20, z=30), - position=AddressableOffsetVector(x=1, y=2, z=3), - compatible_module_types=[], - ) - } - ) - - result = subject.get_addressable_area_center("abc") - assert result == Point(6, 12, 3) - - -def test_get_fixture_height(decoy: Decoy) -> None: - """It should return the height of the requested fixture.""" - subject = get_addressable_area_view() - decoy.when( - deck_configuration_provider.get_cutout_fixture( - "someShortCutoutFixture", subject.state.deck_definition - ) - ).then_return( - { - "height": 10, - # These values don't matter: - "id": "id", - "expectOpentronsModuleSerialNumber": False, - "fixtureGroup": {}, - "mayMountTo": [], - "displayName": "", - "providesAddressableAreas": {}, - } - ) - - decoy.when( - deck_configuration_provider.get_cutout_fixture( - "someTallCutoutFixture", subject.state.deck_definition - ) - ).then_return( - { - "height": 9000.1, - # These values don't matter: - "id": "id", - "expectOpentronsModuleSerialNumber": False, - "fixtureGroup": {}, - "mayMountTo": [], - "displayName": "", - "providesAddressableAreas": {}, - } - ) - - assert subject.get_fixture_height("someShortCutoutFixture") == 10 - assert subject.get_fixture_height("someTallCutoutFixture") == 9000.1 - - -def test_get_slot_definition() -> None: - """It should return a deck slot's definition.""" - subject = get_addressable_area_view( - loaded_addressable_areas_by_name={ - "6": AddressableArea( - area_name="area", - area_type=AreaType.SLOT, - base_slot=DeckSlotName.SLOT_D3, - display_name="fancy name", - bounding_box=Dimensions(x=1, y=2, z=3), - position=AddressableOffsetVector(x=7, y=8, z=9), - compatible_module_types=["magneticModuleType"], - ) - } - ) - - result = subject.get_slot_definition(DeckSlotName.SLOT_6.id) - - assert result == { - "id": "area", - "position": [7, 8, 9], - "boundingBox": { - "xDimension": 1, - "yDimension": 2, - "zDimension": 3, - }, - "displayName": "fancy name", - "compatibleModuleTypes": ["magneticModuleType"], - } - - -def test_get_slot_definition_raises_with_bad_slot_name(decoy: Decoy) -> None: - """It should raise a SlotDoesNotExistError if a bad slot name is given.""" - subject = get_addressable_area_view() - - decoy.when( - deck_configuration_provider.get_potential_cutout_fixtures( - "foo", subject.state.deck_definition - ) - ).then_raise(AddressableAreaDoesNotExistError()) - - with pytest.raises(SlotDoesNotExistError): - subject.get_slot_definition("foo") - - -def test_raise_if_area_not_in_deck_configuration_on_robot(decoy: Decoy) -> None: - """It should raise if the requested addressable area name is not loaded in state.""" - subject = get_addressable_area_view( - loaded_addressable_areas_by_name={"real": decoy.mock(cls=AddressableArea)} - ) - - subject.raise_if_area_not_in_deck_configuration("real") - - with pytest.raises(AreaNotInDeckConfigurationError): - subject.raise_if_area_not_in_deck_configuration("fake") - - -def test_raise_if_area_not_in_deck_configuration_simulated_config(decoy: Decoy) -> None: - """It should raise if the requested addressable area name is not loaded in state.""" - subject = get_addressable_area_view( - use_simulated_deck_config=True, - potential_cutout_fixtures_by_cutout_id={ - "waluigi": { - PotentialCutoutFixture( - cutout_id="fire flower", - cutout_fixture_id="1up", - provided_addressable_areas=frozenset(), - ) - }, - "wario": { - PotentialCutoutFixture( - cutout_id="mushroom", - cutout_fixture_id="star", - provided_addressable_areas=frozenset(), - ) - }, - }, - ) - - decoy.when( - deck_configuration_provider.get_potential_cutout_fixtures( - "mario", subject.state.deck_definition - ) - ).then_return( - ( - "wario", - { - PotentialCutoutFixture( - cutout_id="mushroom", - cutout_fixture_id="star", - provided_addressable_areas=frozenset(), - ) - }, - ) - ) - - subject.raise_if_area_not_in_deck_configuration("mario") - - decoy.when( - deck_configuration_provider.get_potential_cutout_fixtures( - "luigi", subject.state.deck_definition - ) - ).then_return( - ( - "waluigi", - { - PotentialCutoutFixture( - cutout_id="mushroom", - cutout_fixture_id="star", - provided_addressable_areas=frozenset(), - ) - }, - ) - ) - - decoy.when( - deck_configuration_provider.get_provided_addressable_area_names( - "1up", "fire flower", subject.state.deck_definition - ) - ).then_return([]) - - decoy.when( - deck_configuration_provider.get_addressable_area_display_name( - "luigi", subject.state.deck_definition - ) - ).then_return("super luigi") - - with pytest.raises(IncompatibleAddressableAreaError): - subject.raise_if_area_not_in_deck_configuration("luigi") diff --git a/api/tests/opentrons/protocol_engine/state/test_addressable_area_view_old.py b/api/tests/opentrons/protocol_engine/state/test_addressable_area_view_old.py new file mode 100644 index 00000000000..5aa157c59db --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_addressable_area_view_old.py @@ -0,0 +1,501 @@ +"""Addressable area state view tests. + +DEPRECATED: Testing AddressableAreaView independently of AddressableAreaStore is no +longer helpful. Try to add new tests to test_addressable_area_state.py, where they can be +tested together, treating AddressableAreaState as a private implementation detail. +""" + +import inspect +from unittest.mock import sentinel + +import pytest +from decoy import Decoy +from typing import Dict, Set, Optional, cast + +from opentrons_shared_data.robot.types import RobotType +from opentrons_shared_data.deck.types import DeckDefinitionV5 +from opentrons.types import Point, DeckSlotName + +from opentrons.protocol_engine.errors import ( + AreaNotInDeckConfigurationError, + IncompatibleAddressableAreaError, + SlotDoesNotExistError, + AddressableAreaDoesNotExistError, +) +from opentrons.protocol_engine.resources import deck_configuration_provider +from opentrons.protocol_engine.state.addressable_areas import ( + AddressableAreaState, + AddressableAreaView, +) +from opentrons.protocol_engine.types import ( + AddressableArea, + AreaType, + DeckConfigurationType, + PotentialCutoutFixture, + Dimensions, + DeckPoint, + AddressableOffsetVector, +) + + +@pytest.fixture(autouse=True) +def patch_mock_deck_configuration_provider( + decoy: Decoy, monkeypatch: pytest.MonkeyPatch +) -> None: + """Mock out deck_configuration_provider.py functions.""" + for name, func in inspect.getmembers( + deck_configuration_provider, inspect.isfunction + ): + monkeypatch.setattr(deck_configuration_provider, name, decoy.mock(func=func)) + + +def get_addressable_area_view( + loaded_addressable_areas_by_name: Optional[Dict[str, AddressableArea]] = None, + potential_cutout_fixtures_by_cutout_id: Optional[ + Dict[str, Set[PotentialCutoutFixture]] + ] = None, + deck_definition: Optional[DeckDefinitionV5] = None, + deck_configuration: Optional[DeckConfigurationType] = None, + robot_type: RobotType = "OT-3 Standard", + use_simulated_deck_config: bool = False, +) -> AddressableAreaView: + """Get a labware view test subject.""" + state = AddressableAreaState( + loaded_addressable_areas_by_name=loaded_addressable_areas_by_name or {}, + potential_cutout_fixtures_by_cutout_id=potential_cutout_fixtures_by_cutout_id + or {}, + deck_definition=deck_definition or cast(DeckDefinitionV5, {"otId": "fake"}), + robot_definition={ + "displayName": "OT-3", + "robotType": "OT-3 Standard", + "models": ["OT-3 Standard"], + "extents": [477.2, 493.8, 0.0], + "paddingOffsets": { + "rear": -177.42, + "front": 51.8, + "leftSide": 31.88, + "rightSide": -80.32, + }, + "mountOffsets": { + "left": [-13.5, -60.5, 255.675], + "right": [40.5, -60.5, 255.675], + "gripper": [84.55, -12.75, 93.85], + }, + }, + deck_configuration=deck_configuration or [], + robot_type=robot_type, + use_simulated_deck_config=use_simulated_deck_config, + ) + + return AddressableAreaView(state=state) + + +def test_get_all_cutout_fixtures_simulated_deck_config() -> None: + """It should return no cutout fixtures when the deck config is simulated.""" + subject = get_addressable_area_view( + deck_configuration=None, + use_simulated_deck_config=True, + ) + assert subject.get_all_cutout_fixtures() is None + + +def test_get_all_cutout_fixtures_non_simulated_deck_config() -> None: + """It should return the cutout fixtures from the deck config, if it's not simulated.""" + subject = get_addressable_area_view( + deck_configuration=[ + ("cutout-id-1", "cutout-fixture-id-1", None), + ("cutout-id-2", "cutout-fixture-id-2", None), + ], + use_simulated_deck_config=False, + ) + assert subject.get_all_cutout_fixtures() == [ + "cutout-fixture-id-1", + "cutout-fixture-id-2", + ] + + +def test_get_loaded_addressable_area() -> None: + """It should get the loaded addressable area.""" + addressable_area = AddressableArea( + area_name="area", + area_type=AreaType.SLOT, + base_slot=DeckSlotName.SLOT_D3, + display_name="fancy name", + bounding_box=Dimensions(x=1, y=2, z=3), + position=AddressableOffsetVector(x=7, y=8, z=9), + compatible_module_types=["magneticModuleType"], + ) + subject = get_addressable_area_view( + loaded_addressable_areas_by_name={"abc": addressable_area} + ) + + assert subject.get_addressable_area("abc") is addressable_area + + +def test_get_loaded_addressable_area_raises() -> None: + """It should raise if the addressable area does not exist.""" + subject = get_addressable_area_view() + + with pytest.raises(AreaNotInDeckConfigurationError): + subject.get_addressable_area("abc") + + +def test_get_addressable_area_for_simulation_already_loaded() -> None: + """It should get the addressable area for a simulation that has not been loaded yet.""" + addressable_area = AddressableArea( + area_name="area", + area_type=AreaType.SLOT, + base_slot=DeckSlotName.SLOT_D3, + display_name="fancy name", + bounding_box=Dimensions(x=1, y=2, z=3), + position=AddressableOffsetVector(x=7, y=8, z=9), + compatible_module_types=["magneticModuleType"], + ) + subject = get_addressable_area_view( + loaded_addressable_areas_by_name={"abc": addressable_area}, + use_simulated_deck_config=True, + ) + + assert subject.get_addressable_area("abc") is addressable_area + + +def test_get_addressable_area_for_simulation_not_loaded(decoy: Decoy) -> None: + """It should get the addressable area for a simulation that has not been loaded yet.""" + subject = get_addressable_area_view( + potential_cutout_fixtures_by_cutout_id={ + "cutoutA1": { + PotentialCutoutFixture( + cutout_id="cutoutA1", + cutout_fixture_id="blah", + provided_addressable_areas=frozenset(), + ) + } + }, + use_simulated_deck_config=True, + deck_definition=sentinel.deck_definition, + ) + + addressable_area = AddressableArea( + area_name="area", + area_type=AreaType.SLOT, + base_slot=DeckSlotName.SLOT_D3, + display_name="fancy name", + bounding_box=Dimensions(x=1, y=2, z=3), + position=AddressableOffsetVector(x=7, y=8, z=9), + compatible_module_types=["magneticModuleType"], + ) + + decoy.when( + deck_configuration_provider.get_potential_cutout_fixtures( + "abc", + sentinel.deck_definition, + ) + ).then_return( + ( + "cutoutA1", + { + PotentialCutoutFixture( + cutout_id="cutoutA1", + cutout_fixture_id="blah", + provided_addressable_areas=frozenset(), + ) + }, + ) + ) + + decoy.when( + deck_configuration_provider.get_cutout_position( + "cutoutA1", + sentinel.deck_definition, + ) + ).then_return(DeckPoint(x=1, y=2, z=3)) + + decoy.when( + deck_configuration_provider.get_addressable_area_from_name( + "abc", + DeckPoint(x=1, y=2, z=3), + DeckSlotName.SLOT_A1, + sentinel.deck_definition, + ) + ).then_return(addressable_area) + + assert subject.get_addressable_area("abc") is addressable_area + + +def test_get_addressable_area_for_simulation_raises(decoy: Decoy) -> None: + """It should raise if the requested addressable area is incompatible with loaded ones.""" + subject = get_addressable_area_view( + potential_cutout_fixtures_by_cutout_id={ + "123": { + PotentialCutoutFixture( + cutout_id="789", + cutout_fixture_id="bleh", + provided_addressable_areas=frozenset(), + ) + } + }, + use_simulated_deck_config=True, + deck_definition=sentinel.deck_definition, + ) + + decoy.when( + deck_configuration_provider.get_potential_cutout_fixtures( + "abc", sentinel.deck_definition + ) + ).then_return( + ( + "123", + { + PotentialCutoutFixture( + cutout_id="123", + cutout_fixture_id="blah", + provided_addressable_areas=frozenset(), + ) + }, + ) + ) + + decoy.when( + deck_configuration_provider.get_provided_addressable_area_names( + "bleh", "789", sentinel.deck_definition + ) + ).then_return([]) + + with pytest.raises(IncompatibleAddressableAreaError): + subject.get_addressable_area("abc") + + +def test_get_addressable_area_position() -> None: + """It should get the absolute location of the addressable area.""" + subject = get_addressable_area_view( + loaded_addressable_areas_by_name={ + "abc": AddressableArea( + area_name="area", + area_type=AreaType.SLOT, + base_slot=DeckSlotName.SLOT_D3, + display_name="fancy name", + bounding_box=Dimensions(x=10, y=20, z=30), + position=AddressableOffsetVector(x=1, y=2, z=3), + compatible_module_types=[], + ) + } + ) + + result = subject.get_addressable_area_position("abc") + assert result == Point(1, 2, 3) + + +def test_get_addressable_area_move_to_location() -> None: + """It should get the absolute location of an addressable area's move to location.""" + subject = get_addressable_area_view( + loaded_addressable_areas_by_name={ + "abc": AddressableArea( + area_name="area", + area_type=AreaType.SLOT, + base_slot=DeckSlotName.SLOT_D3, + display_name="fancy name", + bounding_box=Dimensions(x=10, y=20, z=30), + position=AddressableOffsetVector(x=1, y=2, z=3), + compatible_module_types=[], + ) + } + ) + + result = subject.get_addressable_area_move_to_location("abc") + assert result == Point(6, 12, 33) + + +def test_get_addressable_area_center() -> None: + """It should get the absolute location of an addressable area's center.""" + subject = get_addressable_area_view( + loaded_addressable_areas_by_name={ + "abc": AddressableArea( + area_name="area", + area_type=AreaType.SLOT, + base_slot=DeckSlotName.SLOT_D3, + display_name="fancy name", + bounding_box=Dimensions(x=10, y=20, z=30), + position=AddressableOffsetVector(x=1, y=2, z=3), + compatible_module_types=[], + ) + } + ) + + result = subject.get_addressable_area_center("abc") + assert result == Point(6, 12, 3) + + +def test_get_fixture_height(decoy: Decoy) -> None: + """It should return the height of the requested fixture.""" + subject = get_addressable_area_view(deck_definition=sentinel.deck_definition) + decoy.when( + deck_configuration_provider.get_cutout_fixture( + "someShortCutoutFixture", sentinel.deck_definition + ) + ).then_return( + { + "height": 10, + # These values don't matter: + "id": "id", + "expectOpentronsModuleSerialNumber": False, + "fixtureGroup": {}, + "mayMountTo": [], + "displayName": "", + "providesAddressableAreas": {}, + } + ) + + decoy.when( + deck_configuration_provider.get_cutout_fixture( + "someTallCutoutFixture", sentinel.deck_definition + ) + ).then_return( + { + "height": 9000.1, + # These values don't matter: + "id": "id", + "expectOpentronsModuleSerialNumber": False, + "fixtureGroup": {}, + "mayMountTo": [], + "displayName": "", + "providesAddressableAreas": {}, + } + ) + + assert subject.get_fixture_height("someShortCutoutFixture") == 10 + assert subject.get_fixture_height("someTallCutoutFixture") == 9000.1 + + +def test_get_slot_definition() -> None: + """It should return a deck slot's definition.""" + subject = get_addressable_area_view( + loaded_addressable_areas_by_name={ + "6": AddressableArea( + area_name="area", + area_type=AreaType.SLOT, + base_slot=DeckSlotName.SLOT_D3, + display_name="fancy name", + bounding_box=Dimensions(x=1, y=2, z=3), + position=AddressableOffsetVector(x=7, y=8, z=9), + compatible_module_types=["magneticModuleType"], + ) + } + ) + + result = subject.get_slot_definition(DeckSlotName.SLOT_6.id) + + assert result == { + "id": "area", + "position": [7, 8, 9], + "boundingBox": { + "xDimension": 1, + "yDimension": 2, + "zDimension": 3, + }, + "displayName": "fancy name", + "compatibleModuleTypes": ["magneticModuleType"], + } + + +def test_get_slot_definition_raises_with_bad_slot_name(decoy: Decoy) -> None: + """It should raise a SlotDoesNotExistError if a bad slot name is given.""" + deck_definition = cast(DeckDefinitionV5, {"otId": "fake"}) + subject = get_addressable_area_view( + deck_definition=deck_definition, + ) + + decoy.when( + deck_configuration_provider.get_potential_cutout_fixtures( + "foo", deck_definition + ) + ).then_raise(AddressableAreaDoesNotExistError()) + + with pytest.raises(SlotDoesNotExistError): + subject.get_slot_definition("foo") + + +def test_raise_if_area_not_in_deck_configuration_on_robot(decoy: Decoy) -> None: + """It should raise if the requested addressable area name is not loaded in state.""" + subject = get_addressable_area_view( + loaded_addressable_areas_by_name={"real": decoy.mock(cls=AddressableArea)} + ) + + subject.raise_if_area_not_in_deck_configuration("real") + + with pytest.raises(AreaNotInDeckConfigurationError): + subject.raise_if_area_not_in_deck_configuration("fake") + + +def test_raise_if_area_not_in_deck_configuration_simulated_config(decoy: Decoy) -> None: + """It should raise if the requested addressable area name is not loaded in state.""" + subject = get_addressable_area_view( + use_simulated_deck_config=True, + potential_cutout_fixtures_by_cutout_id={ + "waluigi": { + PotentialCutoutFixture( + cutout_id="fire flower", + cutout_fixture_id="1up", + provided_addressable_areas=frozenset(), + ) + }, + "wario": { + PotentialCutoutFixture( + cutout_id="mushroom", + cutout_fixture_id="star", + provided_addressable_areas=frozenset(), + ) + }, + }, + deck_definition=sentinel.deck_definition, + ) + + decoy.when( + deck_configuration_provider.get_potential_cutout_fixtures( + "mario", sentinel.deck_definition + ) + ).then_return( + ( + "wario", + { + PotentialCutoutFixture( + cutout_id="mushroom", + cutout_fixture_id="star", + provided_addressable_areas=frozenset(), + ) + }, + ) + ) + + subject.raise_if_area_not_in_deck_configuration("mario") + + decoy.when( + deck_configuration_provider.get_potential_cutout_fixtures( + "luigi", sentinel.deck_definition + ) + ).then_return( + ( + "waluigi", + { + PotentialCutoutFixture( + cutout_id="mushroom", + cutout_fixture_id="star", + provided_addressable_areas=frozenset(), + ) + }, + ) + ) + + decoy.when( + deck_configuration_provider.get_provided_addressable_area_names( + "1up", "fire flower", sentinel.deck_definition + ) + ).then_return([]) + + decoy.when( + deck_configuration_provider.get_addressable_area_display_name( + "luigi", sentinel.deck_definition + ) + ).then_return("super luigi") + + with pytest.raises(IncompatibleAddressableAreaError): + subject.raise_if_area_not_in_deck_configuration("luigi") diff --git a/api/tests/opentrons/protocol_engine/state/test_command_history.py b/api/tests/opentrons/protocol_engine/state/test_command_history.py index 753d69654e1..14eaa2a42f3 100644 --- a/api/tests/opentrons/protocol_engine/state/test_command_history.py +++ b/api/tests/opentrons/protocol_engine/state/test_command_history.py @@ -13,10 +13,14 @@ def create_queued_command_entry( - command_id: str = "command-id", index: int = 0 + command_id: str = "command-id", + intent: CommandIntent = CommandIntent.PROTOCOL, + index: int = 0, ) -> CommandEntry: """Create a command entry for a queued command.""" - return CommandEntry(create_queued_command(command_id=command_id), index) + return CommandEntry( + create_queued_command(command_id=command_id, intent=intent), index + ) def create_fixit_command_entry( @@ -262,3 +266,54 @@ def test_remove_id_from_setup_queue(command_history: CommandHistory) -> None: command_history._add_to_setup_queue("1") command_history._remove_setup_queue_id("0") assert command_history.get_setup_queue_ids() == OrderedSet(["1"]) + + +def test_get_filtered_commands(command_history: CommandHistory) -> None: + """It should return a list of all commands without fixit commands.""" + assert ( + list(command_history.get_filtered_command_ids(include_fixit_commands=False)) + == [] + ) + command_entry_1 = create_queued_command(command_id="0") + command_entry_2 = create_queued_command(command_id="1") + fixit_command_entry_1 = create_queued_command( + intent=CommandIntent.FIXIT, command_id="fixit-1" + ) + command_history.append_queued_command(command_entry_1) + command_history.append_queued_command(command_entry_2) + command_history.append_queued_command(fixit_command_entry_1) + assert list( + command_history.get_filtered_command_ids(include_fixit_commands=False) + ) == ["0", "1"] + + +def test_get_all_filtered_commands(command_history: CommandHistory) -> None: + """It should return a list of all commands without fixit commands.""" + assert ( + list(command_history.get_filtered_command_ids(include_fixit_commands=False)) + == [] + ) + command_entry_1 = create_queued_command_entry() + command_entry_2 = create_queued_command_entry(index=1, intent=CommandIntent.SETUP) + fixit_command_entry_1 = create_queued_command_entry(intent=CommandIntent.FIXIT) + command_history._add("0", command_entry_1) + command_history._add("1", command_entry_2) + command_history._add("fixit-1", fixit_command_entry_1) + assert list( + command_history.get_filtered_command_ids(include_fixit_commands=True) + ) == ["0", "1", "fixit-1"] + + +def test_get_slice_with_filtered_list(command_history: CommandHistory) -> None: + """It should return a slice of filtered commands.""" + assert command_history.get_slice(0, 2) == [] + command_entry_1 = create_queued_command_entry() + command_entry_2 = create_queued_command_entry(index=1) + command_entry_3 = create_queued_command_entry(index=2) + command_history._add("0", command_entry_1) + command_history._add("1", command_entry_2) + command_history._add("2", command_entry_3) + filtered_list = ["0", "1"] + assert command_history.get_slice(1, 3, command_ids=filtered_list) == [ + command_entry_2.command, + ] diff --git a/api/tests/opentrons/protocol_engine/state/test_command_state.py b/api/tests/opentrons/protocol_engine/state/test_command_state.py index afafcc3cabe..c52cd8ca74d 100644 --- a/api/tests/opentrons/protocol_engine/state/test_command_state.py +++ b/api/tests/opentrons/protocol_engine/state/test_command_state.py @@ -19,7 +19,7 @@ PlayAction, SetErrorRecoveryPolicyAction, ) -from opentrons.protocol_engine.commands.command import CommandIntent +from opentrons.protocol_engine.commands.command import CommandIntent, DefinedErrorData from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryType from opentrons.protocol_engine.errors.error_occurrence import ErrorOccurrence from opentrons.protocol_engine.errors.exceptions import ( @@ -30,8 +30,10 @@ from opentrons.protocol_engine.state.commands import ( CommandStore, CommandView, + CommandErrorSlice, ) from opentrons.protocol_engine.state.config import Config +from opentrons.protocol_engine.state.update_types import StateUpdate from opentrons.protocol_engine.types import DeckType, EngineStatus @@ -192,7 +194,7 @@ def test_command_failure(error_recovery_type: ErrorRecoveryType) -> None: ) assert subject_view.get("command-id") == expected_failed_command - assert subject.state.failed_command_errors == [expected_error_occurrence] + assert subject_view.get_all_errors() == [expected_error_occurrence] def test_command_failure_clears_queues() -> None: @@ -254,7 +256,7 @@ def test_command_failure_clears_queues() -> None: assert subject_view.get_running_command_id() is None assert subject_view.get_queue_ids() == OrderedSet() assert subject_view.get_next_to_execute() is None - assert subject.state.failed_command_errors == [expected_error_occurance] + assert subject_view.get_all_errors() == [expected_error_occurance] def test_setup_command_failure_only_clears_setup_command_queue() -> None: @@ -554,7 +556,80 @@ def test_door_during_error_recovery() -> None: subject.handle_action(play) assert subject_view.get_status() == EngineStatus.AWAITING_RECOVERY assert subject_view.get_next_to_execute() == "command-id-2" - assert subject.state.failed_command_errors == [expected_error_occurance] + assert subject_view.get_all_errors() == [expected_error_occurance] + + +@pytest.mark.parametrize("close_door_before_queueing", [False, True]) +def test_door_ungrip_labware(close_door_before_queueing: bool) -> None: + """Ungrip commands should be able to run even when the door is open.""" + subject = CommandStore( + is_door_open=False, + error_recovery_policy=_placeholder_error_recovery_policy, + config=Config( + block_on_door_open=True, + # Choice of robot and deck type are arbitrary. + robot_type="OT-2 Standard", + deck_type=DeckType.OT2_STANDARD, + ), + ) + subject_view = CommandView(subject.state) + + # Fail a command to put the subject in recovery mode. + queue_failing = actions.QueueCommandAction( + request=commands.CommentCreate( + params=commands.CommentParams(message=""), key="command-key-1" + ), + request_hash=None, + created_at=datetime(year=2021, month=1, day=1), + command_id="failing-command-id", + ) + subject.handle_action(queue_failing) + run_failing = actions.RunCommandAction( + command_id="failing-command-id", + started_at=datetime(year=2022, month=2, day=2), + ) + subject.handle_action(run_failing) + expected_error = errors.ProtocolEngineError(message="oh no") + fail_failing = actions.FailCommandAction( + command_id="failing-command-id", + running_command=subject_view.get("failing-command-id"), + error_id="error-id", + failed_at=datetime(year=2023, month=3, day=3), + error=expected_error, + notes=[], + type=ErrorRecoveryType.WAIT_FOR_RECOVERY, + ) + subject.handle_action(fail_failing) + + # Open the door: + subject.handle_action(actions.DoorChangeAction(DoorState.OPEN)) + assert ( + subject_view.get_status() == EngineStatus.AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR + ) + assert subject_view.get_next_to_execute() is None + + if close_door_before_queueing: + subject.handle_action(actions.DoorChangeAction(DoorState.CLOSED)) + + assert subject_view.get_status() in ( + EngineStatus.AWAITING_RECOVERY_PAUSED, # If we closed the door. + EngineStatus.AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, # If we didn't. + ) + + # Make sure the special ungrip command can be queued and that it will be returned + # as next to execute: + queue_fixit = actions.QueueCommandAction( + request=commands.unsafe.UnsafeUngripLabwareCreate( + params=commands.unsafe.UnsafeUngripLabwareParams(), + intent=CommandIntent.FIXIT, + ), + request_hash=None, + created_at=datetime(year=2021, month=1, day=1), + command_id="fixit-command-id", + ) + subject_view.validate_action_allowed(queue_fixit) + subject.handle_action(queue_fixit) + assert subject_view.get_next_to_execute() == "fixit-command-id" @pytest.mark.parametrize( @@ -658,7 +733,7 @@ def test_error_recovery_type_tracking() -> None: id="c2-error", createdAt=datetime(year=2023, month=3, day=3), error=exception ) - assert subject.state.failed_command_errors == [ + assert view.get_all_errors() == [ error_occurrence_1, error_occurrence_2, ] @@ -699,7 +774,7 @@ def test_recovery_target_tracking() -> None: assert recovery_target.command_id == "c1" assert subject_view.get_recovery_in_progress_for_command("c1") - resume_from_1_recovery = actions.ResumeFromRecoveryAction() + resume_from_1_recovery = actions.ResumeFromRecoveryAction(StateUpdate()) subject.handle_action(resume_from_1_recovery) # c1 failed recoverably, but we've already completed its recovery. @@ -735,7 +810,7 @@ def test_recovery_target_tracking() -> None: # even though it failed recoverably before. assert not subject_view.get_recovery_in_progress_for_command("c1") - resume_from_2_recovery = actions.ResumeFromRecoveryAction() + resume_from_2_recovery = actions.ResumeFromRecoveryAction(StateUpdate()) subject.handle_action(resume_from_2_recovery) queue_3 = actions.QueueCommandAction( "c3", @@ -764,6 +839,58 @@ def test_recovery_target_tracking() -> None: assert subject_view.get_has_entered_recovery_mode() is True +@pytest.mark.parametrize( + "ending_action", + [ + actions.StopAction(from_estop=False), + actions.StopAction(from_estop=True), + actions.FinishAction(set_run_status=False), + actions.FinishAction( + set_run_status=True, + error_details=actions.FinishErrorDetails( + error=Exception("blimey"), + error_id="error-id", + created_at=datetime.now(), + ), + ), + ], +) +def test_recovery_target_clears_when_run_ends(ending_action: actions.Action) -> None: + """There should never be an error recovery target when the run is done.""" + subject = CommandStore( + config=_make_config(), + error_recovery_policy=_placeholder_error_recovery_policy, + is_door_open=False, + ) + subject_view = CommandView(subject.state) + + # Setup: Put the run in error recovery mode. + queue = actions.QueueCommandAction( + "c1", + created_at=datetime.now(), + request=commands.CommentCreate(params=commands.CommentParams(message="")), + request_hash=None, + ) + subject.handle_action(queue) + run = actions.RunCommandAction(command_id="c1", started_at=datetime.now()) + subject.handle_action(run) + fail = actions.FailCommandAction( + command_id="c1", + error_id="c1-error", + failed_at=datetime.now(), + error=PythonException(RuntimeError()), + notes=[], + type=ErrorRecoveryType.WAIT_FOR_RECOVERY, + running_command=subject_view.get("c1"), + ) + subject.handle_action(fail) + + # Test: Assert that the ending action clears the recovery target. + assert subject_view.get_recovery_target() is not None + subject.handle_action(ending_action) + assert subject_view.get_recovery_target() is None + + def test_final_state_after_estop() -> None: """Test the final state of the run after it's E-stopped.""" subject = CommandStore( @@ -920,3 +1047,148 @@ def test_set_and_get_error_recovery_policy() -> None: assert subject_view.get_error_recovery_policy() is initial_policy subject.handle_action(SetErrorRecoveryPolicyAction(sentinel.new_policy)) assert subject_view.get_error_recovery_policy() is new_policy + + +def test_get_state_update_for_false_positive() -> None: + """Test storage of false-positive state updates.""" + subject = CommandStore( + config=_make_config(), + error_recovery_policy=_placeholder_error_recovery_policy, + is_door_open=False, + ) + subject_view = CommandView(subject.state) + + empty_state_update = StateUpdate() + + assert subject_view.get_state_update_for_false_positive() == empty_state_update + + queue = actions.QueueCommandAction( + request=commands.CommentCreate( + params=commands.CommentParams(message=""), key="command-key-1" + ), + request_hash=None, + created_at=datetime(year=2021, month=1, day=1), + command_id="command-id-1", + ) + subject.handle_action(queue) + run = actions.RunCommandAction( + command_id="command-id-1", + started_at=datetime(year=2022, month=2, day=2), + ) + subject.handle_action(run) + fail = actions.FailCommandAction( + command_id="command-id-1", + running_command=subject_view.get("command-id-1"), + error_id="error-id", + failed_at=datetime(year=2023, month=3, day=3), + error=DefinedErrorData( + public=sentinel.public, + state_update_if_false_positive=sentinel.state_update_if_false_positive, + ), + type=ErrorRecoveryType.WAIT_FOR_RECOVERY, + notes=[], + ) + subject.handle_action(fail) + + assert ( + subject_view.get_state_update_for_false_positive() + == sentinel.state_update_if_false_positive + ) + + resume_from_recovery = actions.ResumeFromRecoveryAction( + state_update=sentinel.some_other_state_update + ) + subject.handle_action(resume_from_recovery) + + assert subject_view.get_state_update_for_false_positive() == empty_state_update + + +def test_get_errors_slice_empty() -> None: + """It should return an empty error list.""" + subject = CommandStore( + config=_make_config(), + error_recovery_policy=_placeholder_error_recovery_policy, + is_door_open=False, + ) + subject_view = CommandView(subject.state) + result = subject_view.get_errors_slice(cursor=0, length=2) + + assert result == CommandErrorSlice(commands_errors=[], cursor=0, total_length=0) + + +def test_get_errors_slice() -> None: + """It should return a slice of all command errors.""" + subject = CommandStore( + config=_make_config(), + error_recovery_policy=_placeholder_error_recovery_policy, + is_door_open=False, + ) + + subject_view = CommandView(subject.state) + + queue_1 = actions.QueueCommandAction( + request=commands.WaitForResumeCreate( + params=commands.WaitForResumeParams(), key="command-key-1" + ), + request_hash=None, + created_at=datetime(year=2021, month=1, day=1), + command_id="command-id-1", + ) + subject.handle_action(queue_1) + queue_2_setup = actions.QueueCommandAction( + request=commands.WaitForResumeCreate( + params=commands.WaitForResumeParams(), + intent=commands.CommandIntent.SETUP, + key="command-key-2", + ), + request_hash=None, + created_at=datetime(year=2021, month=1, day=1), + command_id="command-id-2", + ) + subject.handle_action(queue_2_setup) + queue_3_setup = actions.QueueCommandAction( + request=commands.WaitForResumeCreate( + params=commands.WaitForResumeParams(), + intent=commands.CommandIntent.SETUP, + key="command-key-3", + ), + request_hash=None, + created_at=datetime(year=2021, month=1, day=1), + command_id="command-id-3", + ) + subject.handle_action(queue_3_setup) + + run_2_setup = actions.RunCommandAction( + command_id="command-id-2", + started_at=datetime(year=2022, month=2, day=2), + ) + subject.handle_action(run_2_setup) + fail_2_setup = actions.FailCommandAction( + command_id="command-id-2", + running_command=subject_view.get("command-id-2"), + error_id="error-id", + failed_at=datetime(year=2023, month=3, day=3), + error=errors.ProtocolEngineError(message="oh no"), + notes=[], + type=ErrorRecoveryType.CONTINUE_WITH_ERROR, + ) + subject.handle_action(fail_2_setup) + + result = subject_view.get_errors_slice(cursor=1, length=3) + + assert result == CommandErrorSlice( + [ + ErrorOccurrence( + id="error-id", + createdAt=datetime(2023, 3, 3, 0, 0), + isDefined=False, + errorType="ProtocolEngineError", + errorCode="4000", + detail="oh no", + errorInfo={}, + wrappedErrors=[], + ) + ], + cursor=0, + total_length=1, + ) diff --git a/api/tests/opentrons/protocol_engine/state/test_command_store_old.py b/api/tests/opentrons/protocol_engine/state/test_command_store_old.py index 018634db435..a9022803bcf 100644 --- a/api/tests/opentrons/protocol_engine/state/test_command_store_old.py +++ b/api/tests/opentrons/protocol_engine/state/test_command_store_old.py @@ -1,7 +1,8 @@ """Tests for CommandStore. DEPRECATED: Testing CommandStore independently of CommandView is no longer helpful. -Add new tests to test_command_state.py, where they can be tested together. +Try to add new tests to test_command_state.py, where they can be tested together, +treating CommandState as a private implementation detail. """ @@ -17,7 +18,7 @@ from opentrons.protocol_engine import commands, errors from opentrons.protocol_engine.types import DeckType -from opentrons.protocol_engine.state import Config +from opentrons.protocol_engine.state.config import Config from opentrons.protocol_engine.state.commands import ( CommandState, CommandStore, @@ -84,7 +85,6 @@ def test_command_queue_and_unqueue() -> None: started_at=datetime(year=2022, month=2, day=2), ) succeed_2 = SucceedCommandAction( - private_result=None, command=create_succeeded_command(command_id="command-id-2"), ) @@ -137,7 +137,6 @@ def test_setup_command_queue_and_unqueue() -> None: command_id="command-id-2", started_at=datetime(year=2022, month=2, day=2) ) succeed_2 = SucceedCommandAction( - private_result=None, command=create_succeeded_command(command_id="command-id-2"), ) @@ -214,7 +213,6 @@ def test_running_command_id() -> None: started_at=datetime(year=2021, month=1, day=1), ) succeed = SucceedCommandAction( - private_result=None, command=create_succeeded_command(command_id="command-id-1"), ) @@ -303,7 +301,6 @@ def test_command_store_keeps_commands_in_queue_order() -> None: command=create_succeeded_command( command_id="command-id-2", ), - private_result=None, ) ) assert subject.state.command_history.get_all_ids() == [ @@ -334,10 +331,9 @@ def test_command_store_handles_pause_action(pause_source: PauseSource) -> None: finish_error=None, failed_command=None, command_error_recovery_types={}, - recovery_target_command_id=None, + recovery_target=None, latest_protocol_command_hash=None, stopped_by_estop=False, - failed_command_errors=[], error_recovery_policy=matchers.Anything(), has_entered_error_recovery=False, ) @@ -363,11 +359,10 @@ def test_command_store_handles_play_action(pause_source: PauseSource) -> None: finish_error=None, failed_command=None, command_error_recovery_types={}, - recovery_target_command_id=None, + recovery_target=None, run_started_at=datetime(year=2021, month=1, day=1), latest_protocol_command_hash=None, stopped_by_estop=False, - failed_command_errors=[], error_recovery_policy=matchers.Anything(), has_entered_error_recovery=False, ) @@ -398,11 +393,10 @@ def test_command_store_handles_finish_action() -> None: finish_error=None, failed_command=None, command_error_recovery_types={}, - recovery_target_command_id=None, + recovery_target=None, run_started_at=datetime(year=2021, month=1, day=1), latest_protocol_command_hash=None, stopped_by_estop=False, - failed_command_errors=[], error_recovery_policy=matchers.Anything(), has_entered_error_recovery=False, ) @@ -453,11 +447,10 @@ def test_command_store_handles_stop_action( finish_error=None, failed_command=None, command_error_recovery_types={}, - recovery_target_command_id=None, + recovery_target=None, run_started_at=datetime(year=2021, month=1, day=1), latest_protocol_command_hash=None, stopped_by_estop=from_estop, - failed_command_errors=[], error_recovery_policy=matchers.Anything(), has_entered_error_recovery=False, ) @@ -491,11 +484,10 @@ def test_command_store_handles_stop_action_when_awaiting_recovery() -> None: finish_error=None, failed_command=None, command_error_recovery_types={}, - recovery_target_command_id=None, + recovery_target=None, run_started_at=datetime(year=2021, month=1, day=1), latest_protocol_command_hash=None, stopped_by_estop=False, - failed_command_errors=[], error_recovery_policy=matchers.Anything(), has_entered_error_recovery=False, ) @@ -525,11 +517,10 @@ def test_command_store_cannot_restart_after_should_stop() -> None: finish_error=None, failed_command=None, command_error_recovery_types={}, - recovery_target_command_id=None, + recovery_target=None, run_started_at=None, latest_protocol_command_hash=None, stopped_by_estop=False, - failed_command_errors=[], error_recovery_policy=matchers.Anything(), has_entered_error_recovery=False, ) @@ -673,10 +664,9 @@ def test_command_store_wraps_unknown_errors() -> None: run_started_at=None, failed_command=None, command_error_recovery_types={}, - recovery_target_command_id=None, + recovery_target=None, latest_protocol_command_hash=None, stopped_by_estop=False, - failed_command_errors=[], error_recovery_policy=matchers.Anything(), has_entered_error_recovery=False, ) @@ -742,11 +732,10 @@ def __init__(self, message: str) -> None: ), failed_command=None, command_error_recovery_types={}, - recovery_target_command_id=None, + recovery_target=None, run_started_at=None, latest_protocol_command_hash=None, stopped_by_estop=False, - failed_command_errors=[], error_recovery_policy=matchers.Anything(), has_entered_error_recovery=False, ) @@ -778,11 +767,10 @@ def test_command_store_ignores_stop_after_graceful_finish() -> None: finish_error=None, failed_command=None, command_error_recovery_types={}, - recovery_target_command_id=None, + recovery_target=None, run_started_at=datetime(year=2021, month=1, day=1), latest_protocol_command_hash=None, stopped_by_estop=False, - failed_command_errors=[], error_recovery_policy=matchers.Anything(), has_entered_error_recovery=False, ) @@ -814,11 +802,10 @@ def test_command_store_ignores_finish_after_non_graceful_stop() -> None: finish_error=None, failed_command=None, command_error_recovery_types={}, - recovery_target_command_id=None, + recovery_target=None, run_started_at=datetime(year=2021, month=1, day=1), latest_protocol_command_hash=None, stopped_by_estop=False, - failed_command_errors=[], error_recovery_policy=matchers.Anything(), has_entered_error_recovery=False, ) @@ -850,11 +837,10 @@ def test_handles_hardware_stopped() -> None: finish_error=None, failed_command=None, command_error_recovery_types={}, - recovery_target_command_id=None, + recovery_target=None, run_started_at=None, latest_protocol_command_hash=None, stopped_by_estop=False, - failed_command_errors=[], error_recovery_policy=matchers.Anything(), has_entered_error_recovery=False, ) diff --git a/api/tests/opentrons/protocol_engine/state/test_command_view_old.py b/api/tests/opentrons/protocol_engine/state/test_command_view_old.py index 5aa7d04a2ee..de242d83f51 100644 --- a/api/tests/opentrons/protocol_engine/state/test_command_view_old.py +++ b/api/tests/opentrons/protocol_engine/state/test_command_view_old.py @@ -1,7 +1,8 @@ """Tests for CommandView. DEPRECATED: Testing CommandView independently of CommandStore is no longer helpful. -Add new tests to test_command_state.py, where they can be tested together. +Try to add new tests to test_command_state.py, where they can be tested together, +treating CommandState as a private implementation detail. """ @@ -22,22 +23,24 @@ from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryType from opentrons.protocol_engine.state.commands import ( + # todo(mm, 2024-10-24): Avoid testing internal implementation details like + # _RecoveryTargetInfo. See note above about porting to test_command_state.py. + _RecoveryTargetInfo, CommandState, CommandView, CommandSlice, - CommandErrorSlice, CommandPointer, RunResult, QueueStatus, ) -from opentrons.protocol_engine.state.command_history import CommandEntry +from opentrons.protocol_engine.state.command_history import CommandEntry, CommandHistory from opentrons.protocol_engine.errors import ProtocolCommandFailedError, ErrorOccurrence from opentrons_shared_data.errors.codes import ErrorCodes -from opentrons.protocol_engine.state.command_history import CommandHistory +from opentrons.protocol_engine.state.update_types import StateUpdate from .command_fixtures import ( create_queued_command, @@ -73,7 +76,6 @@ def get_command_view( # noqa: C901 finish_error: Optional[errors.ErrorOccurrence] = None, commands: Sequence[cmd.Command] = (), latest_command_hash: Optional[str] = None, - failed_command_errors: Optional[List[ErrorOccurrence]] = None, has_entered_error_recovery: bool = False, ) -> CommandView: """Get a command view test subject.""" @@ -81,6 +83,7 @@ def get_command_view( # noqa: C901 if running_command_id: command_history._set_running_command_id(running_command_id) + # TODO(tz, 8-21-24): consolidate all quques into 1 and use append_queued_command if queued_command_ids: for command_id in queued_command_ids: command_history._add_to_queue(command_id) @@ -107,11 +110,15 @@ def get_command_view( # noqa: C901 finish_error=finish_error, failed_command=failed_command, command_error_recovery_types=command_error_recovery_types or {}, - recovery_target_command_id=recovery_target_command_id, + recovery_target=_RecoveryTargetInfo( + command_id=recovery_target_command_id, + state_update_if_false_positive=StateUpdate(), + ) + if recovery_target_command_id is not None + else None, run_started_at=run_started_at, latest_protocol_command_hash=latest_command_hash, stopped_by_estop=False, - failed_command_errors=failed_command_errors or [], has_entered_error_recovery=has_entered_error_recovery, error_recovery_policy=_placeholder_error_recovery_policy, ) @@ -591,9 +598,24 @@ class ActionAllowedSpec(NamedTuple): ), ), ), - action=ResumeFromRecoveryAction(), + action=ResumeFromRecoveryAction(StateUpdate()), expected_error=errors.ResumeFromRecoveryNotAllowedError, ), + ActionAllowedSpec( + subject=get_command_view( + queue_status=QueueStatus.AWAITING_RECOVERY_PAUSED, is_door_blocking=True + ), + action=QueueCommandAction( + request=cmd.unsafe.UnsafeUngripLabwareCreate( + params=cmd.unsafe.UnsafeUngripLabwareParams(), + intent=cmd.CommandIntent.FIXIT, + ), + request_hash=None, + command_id="command-id", + created_at=datetime(year=2021, month=1, day=1), + ), + expected_error=None, + ), ] @@ -871,7 +893,7 @@ def test_get_current() -> None: created_at=datetime(year=2022, month=2, day=2), ) subject = get_command_view(commands=[command_1, command_2]) - subject.state.command_history._set_most_recently_completed_command_id(command_1.id) + subject._state.command_history._set_most_recently_completed_command_id(command_1.id) assert subject.get_current() == CommandPointer( index=1, @@ -891,7 +913,7 @@ def test_get_current() -> None: created_at=datetime(year=2022, month=2, day=2), ) subject = get_command_view(commands=[command_1, command_2]) - subject.state.command_history._set_most_recently_completed_command_id(command_1.id) + subject._state.command_history._set_most_recently_completed_command_id(command_1.id) assert subject.get_current() == CommandPointer( index=1, @@ -904,7 +926,7 @@ def test_get_current() -> None: def test_get_slice_empty() -> None: """It should return a slice from the tail if no current command.""" subject = get_command_view(commands=[]) - result = subject.get_slice(cursor=0, length=2) + result = subject.get_slice(cursor=0, length=2, include_fixit_commands=True) assert result == CommandSlice(commands=[], cursor=0, total_length=0) @@ -918,7 +940,7 @@ def test_get_slice() -> None: subject = get_command_view(commands=[command_1, command_2, command_3, command_4]) - result = subject.get_slice(cursor=1, length=3) + result = subject.get_slice(cursor=1, length=3, include_fixit_commands=True) assert result == CommandSlice( commands=[command_2, command_3, command_4], @@ -926,7 +948,7 @@ def test_get_slice() -> None: total_length=4, ) - result = subject.get_slice(cursor=-3, length=10) + result = subject.get_slice(cursor=-3, length=10, include_fixit_commands=True) assert result == CommandSlice( commands=[command_1, command_2, command_3, command_4], @@ -944,7 +966,7 @@ def test_get_slice_default_cursor_no_current() -> None: subject = get_command_view(commands=[command_1, command_2, command_3, command_4]) - result = subject.get_slice(cursor=None, length=3) + result = subject.get_slice(cursor=None, length=3, include_fixit_commands=True) assert result == CommandSlice( commands=[command_2, command_3, command_4], @@ -975,7 +997,7 @@ def test_get_slice_default_cursor_failed_command() -> None: failed_command=CommandEntry(index=2, command=command_3), ) - result = subject.get_slice(cursor=None, length=3) + result = subject.get_slice(cursor=None, length=3, include_fixit_commands=True) assert result == CommandSlice( commands=[command_3, command_4], @@ -997,7 +1019,7 @@ def test_get_slice_default_cursor_running() -> None: running_command_id="command-id-3", ) - result = subject.get_slice(cursor=None, length=2) + result = subject.get_slice(cursor=None, length=2, include_fixit_commands=True) assert result == CommandSlice( commands=[command_3, command_4], @@ -1006,37 +1028,46 @@ def test_get_slice_default_cursor_running() -> None: ) -def test_get_errors_slice_empty() -> None: - """It should return a slice from the tail if no current command.""" - subject = get_command_view(failed_command_errors=[]) - result = subject.get_errors_slice(cursor=0, length=2) - - assert result == CommandErrorSlice(commands_errors=[], cursor=0, total_length=0) - - -def test_get_errors_slice() -> None: - """It should return a slice of all command errors.""" - error_1 = ErrorOccurrence.construct(id="error-id-1") # type: ignore[call-arg] - error_2 = ErrorOccurrence.construct(id="error-id-2") # type: ignore[call-arg] - error_3 = ErrorOccurrence.construct(id="error-id-3") # type: ignore[call-arg] - error_4 = ErrorOccurrence.construct(id="error-id-4") # type: ignore[call-arg] - - subject = get_command_view( - failed_command_errors=[error_1, error_2, error_3, error_4] +def test_get_slice_without_fixit() -> None: + """It should select a cursor based on the running command, if present.""" + command_1 = create_succeeded_command(command_id="command-id-1") + command_2 = create_succeeded_command(command_id="command-id-2") + command_3 = create_running_command(command_id="command-id-3") + command_4 = create_queued_command(command_id="command-id-4") + command_5 = create_queued_command(command_id="command-id-5") + command_6 = create_queued_command( + command_id="fixit-id-1", intent=cmd.CommandIntent.FIXIT + ) + command_7 = create_queued_command( + command_id="fixit-id-2", intent=cmd.CommandIntent.FIXIT ) - result = subject.get_errors_slice(cursor=1, length=3) - - assert result == CommandErrorSlice( - commands_errors=[error_2, error_3, error_4], - cursor=1, - total_length=4, + subject = get_command_view( + commands=[ + command_1, + command_2, + command_3, + command_4, + command_5, + command_6, + command_7, + ], + queued_command_ids=[ + "command-id-1", + "command-id-2", + "command-id-3", + "command-id-4", + "command-id-5", + "fixit-id-1", + "fixit-id-2", + ], + queued_fixit_command_ids=["fixit-id-1", "fixit-id-2"], ) - result = subject.get_errors_slice(cursor=-3, length=10) + result = subject.get_slice(cursor=None, length=7, include_fixit_commands=False) - assert result == CommandErrorSlice( - commands_errors=[error_1, error_2, error_3, error_4], + assert result == CommandSlice( + commands=[command_1, command_2, command_3, command_4, command_5], cursor=0, - total_length=4, + total_length=5, ) diff --git a/api/tests/opentrons/protocol_engine/state/test_fluid_stack.py b/api/tests/opentrons/protocol_engine/state/test_fluid_stack.py new file mode 100644 index 00000000000..e958b92036d --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_fluid_stack.py @@ -0,0 +1,219 @@ +"""Test pipette internal fluid tracking.""" +import pytest + +from opentrons.protocol_engine.state.fluid_stack import FluidStack +from opentrons.protocol_engine.types import AspiratedFluid, FluidKind + + +@pytest.mark.parametrize( + "fluids,resulting_stack", + [ + ( + [ + AspiratedFluid(FluidKind.LIQUID, 10), + AspiratedFluid(FluidKind.LIQUID, 10), + ], + [AspiratedFluid(FluidKind.LIQUID, 20)], + ), + ( + [AspiratedFluid(FluidKind.AIR, 10), AspiratedFluid(FluidKind.LIQUID, 20)], + [AspiratedFluid(FluidKind.AIR, 10), AspiratedFluid(FluidKind.LIQUID, 20)], + ), + ( + [ + AspiratedFluid(FluidKind.AIR, 10), + AspiratedFluid(FluidKind.LIQUID, 20), + AspiratedFluid(FluidKind.LIQUID, 10), + AspiratedFluid(FluidKind.AIR, 20), + ], + [ + AspiratedFluid(FluidKind.AIR, 10), + AspiratedFluid(FluidKind.LIQUID, 30), + AspiratedFluid(FluidKind.AIR, 20), + ], + ), + ], +) +def test_add_fluid( + fluids: list[AspiratedFluid], resulting_stack: list[AspiratedFluid] +) -> None: + """It should add fluids.""" + stack = FluidStack() + for fluid in fluids: + stack.add_fluid(fluid) + assert stack._fluid_stack == resulting_stack + + +@pytest.mark.parametrize( + "starting_fluids,remove_volume,resulting_stack", + [ + ([], 1, []), + ([], 0, []), + ( + [AspiratedFluid(FluidKind.LIQUID, 10)], + 0, + [AspiratedFluid(FluidKind.LIQUID, 10)], + ), + ( + [AspiratedFluid(FluidKind.LIQUID, 10)], + 5, + [AspiratedFluid(FluidKind.LIQUID, 5)], + ), + ([AspiratedFluid(FluidKind.LIQUID, 10)], 11, []), + ( + [AspiratedFluid(FluidKind.LIQUID, 10), AspiratedFluid(FluidKind.AIR, 10)], + 11, + [AspiratedFluid(FluidKind.LIQUID, 9)], + ), + ( + [AspiratedFluid(FluidKind.LIQUID, 10), AspiratedFluid(FluidKind.AIR, 10)], + 20, + [], + ), + ( + [ + AspiratedFluid(FluidKind.LIQUID, 10), + AspiratedFluid(FluidKind.AIR, 10), + AspiratedFluid(FluidKind.LIQUID, 10), + ], + 28, + [AspiratedFluid(FluidKind.LIQUID, 2)], + ), + ], +) +def test_remove_fluid( + starting_fluids: list[AspiratedFluid], + remove_volume: float, + resulting_stack: list[AspiratedFluid], +) -> None: + """It should remove fluids.""" + stack = FluidStack(_fluid_stack=[f for f in starting_fluids]) + stack.remove_fluid(remove_volume) + assert stack._fluid_stack == resulting_stack + + +@pytest.mark.parametrize( + "starting_fluids,filter,result", + [ + ([], None, 0), + ([], FluidKind.LIQUID, 0), + ([], FluidKind.AIR, 0), + ([AspiratedFluid(FluidKind.LIQUID, 10)], None, 10), + ([AspiratedFluid(FluidKind.LIQUID, 10)], FluidKind.LIQUID, 10), + ([AspiratedFluid(FluidKind.LIQUID, 10)], FluidKind.AIR, 0), + ( + [AspiratedFluid(FluidKind.LIQUID, 10), AspiratedFluid(FluidKind.AIR, 10)], + None, + 20, + ), + ( + [AspiratedFluid(FluidKind.LIQUID, 10), AspiratedFluid(FluidKind.AIR, 10)], + FluidKind.LIQUID, + 10, + ), + ( + [AspiratedFluid(FluidKind.LIQUID, 10), AspiratedFluid(FluidKind.AIR, 10)], + FluidKind.AIR, + 10, + ), + ], +) +def test_aspirated_volume( + starting_fluids: list[AspiratedFluid], filter: FluidKind | None, result: float +) -> None: + """It should represent aspirated volume with filtering.""" + stack = FluidStack(_fluid_stack=starting_fluids) + assert stack.aspirated_volume(kind=filter) == result + + +@pytest.mark.parametrize( + "starting_fluids,dispense_volume,result", + [ + ([], 0, 0), + ([], 1, 0), + ([AspiratedFluid(FluidKind.AIR, 10)], 10, 0), + ([AspiratedFluid(FluidKind.AIR, 10)], 0, 0), + ([AspiratedFluid(FluidKind.LIQUID, 10)], 10, 10), + ([AspiratedFluid(FluidKind.LIQUID, 10)], 0, 0), + ( + [ + AspiratedFluid(FluidKind.AIR, 10), + AspiratedFluid(FluidKind.LIQUID, 10), + ], + 10, + 10, + ), + ( + [ + AspiratedFluid(FluidKind.AIR, 10), + AspiratedFluid(FluidKind.LIQUID, 10), + ], + 20, + 10, + ), + ( + [ + AspiratedFluid(FluidKind.AIR, 10), + AspiratedFluid(FluidKind.LIQUID, 10), + ], + 30, + 10, + ), + ( + [ + AspiratedFluid(FluidKind.AIR, 10), + AspiratedFluid(FluidKind.LIQUID, 10), + ], + 5, + 5, + ), + ( + [ + AspiratedFluid(FluidKind.LIQUID, 10), + AspiratedFluid(FluidKind.AIR, 10), + ], + 5, + 0, + ), + ( + [ + AspiratedFluid(FluidKind.LIQUID, 10), + AspiratedFluid(FluidKind.AIR, 10), + ], + 10, + 0, + ), + ( + [ + AspiratedFluid(FluidKind.LIQUID, 10), + AspiratedFluid(FluidKind.AIR, 10), + ], + 11, + 1, + ), + ( + [ + AspiratedFluid(FluidKind.LIQUID, 10), + AspiratedFluid(FluidKind.AIR, 10), + ], + 20, + 10, + ), + ( + [ + AspiratedFluid(FluidKind.LIQUID, 10), + AspiratedFluid(FluidKind.AIR, 10), + ], + 30, + 10, + ), + ], +) +def test_liquid_part_of_dispense_volume( + starting_fluids: list[AspiratedFluid], + dispense_volume: float, + result: float, +) -> None: + """It should predict resulting liquid from a dispense.""" + stack = FluidStack(_fluid_stack=starting_fluids) + assert stack.liquid_part_of_dispense_volume(dispense_volume) == result diff --git a/api/tests/opentrons/protocol_engine/state/test_geometry_view.py b/api/tests/opentrons/protocol_engine/state/test_geometry_view.py index f23d8f4a6e1..b145458649d 100644 --- a/api/tests/opentrons/protocol_engine/state/test_geometry_view.py +++ b/api/tests/opentrons/protocol_engine/state/test_geometry_view.py @@ -1,11 +1,18 @@ """Test state getters for retrieving geometry views of state.""" import inspect - import json +from datetime import datetime +from math import isclose +from typing import cast, List, Tuple, Optional, NamedTuple, Dict +from unittest.mock import sentinel + import pytest from decoy import Decoy -from typing import cast, List, Tuple, Optional, NamedTuple -from datetime import datetime + +from opentrons.protocol_engine.state.update_types import ( + LoadedLabwareUpdate, + StateUpdate, +) from opentrons_shared_data.deck.types import DeckDefinitionV5 from opentrons_shared_data.deck import load as load_deck @@ -13,7 +20,7 @@ from opentrons_shared_data.pipette import pipette_definition from opentrons.calibration_storage.helpers import uri_from_details from opentrons.protocols.models import LabwareDefinition -from opentrons.types import Point, DeckSlotName, MountType +from opentrons.types import Point, DeckSlotName, MountType, StagingSlotName from opentrons_shared_data.pipette.types import PipetteNameType from opentrons_shared_data.labware.labware_definition import ( Dimensions as LabwareDimensions, @@ -36,6 +43,7 @@ LoadedModule, ModuleModel, WellLocation, + LiquidHandlingWellLocation, WellOrigin, DropTipWellLocation, DropTipWellOrigin, @@ -50,6 +58,9 @@ LoadedPipette, TipGeometry, ModuleDefinition, + ProbedHeightInfo, + LoadedVolumeInfo, + WellLiquidInfo, ) from opentrons.protocol_engine.commands import ( CommandStatus, @@ -61,9 +72,10 @@ LoadModuleParams, ) from opentrons.protocol_engine.actions import SucceedCommandAction -from opentrons.protocol_engine.state import move_types +from opentrons.protocol_engine.state import _move_types from opentrons.protocol_engine.state.config import Config from opentrons.protocol_engine.state.labware import LabwareView, LabwareStore +from opentrons.protocol_engine.state.wells import WellView, WellStore from opentrons.protocol_engine.state.modules import ModuleView, ModuleStore from opentrons.protocol_engine.state.pipettes import ( PipetteView, @@ -77,7 +89,24 @@ AddressableAreaStore, ) from opentrons.protocol_engine.state.geometry import GeometryView, _GripperMoveType +from opentrons.protocol_engine.state.frustum_helpers import ( + _height_from_volume_circular, + _height_from_volume_rectangular, + _volume_from_height_circular, + _volume_from_height_rectangular, +) from ..pipette_fixtures import get_default_nozzle_map +from ..mock_circular_frusta import TEST_EXAMPLES as CIRCULAR_TEST_EXAMPLES +from ..mock_rectangular_frusta import TEST_EXAMPLES as RECTANGULAR_TEST_EXAMPLES +from ...protocol_runner.test_json_translator import _load_labware_definition_data + + +@pytest.fixture +def available_sensors() -> pipette_definition.AvailableSensorDefinition: + """Provide a list of sensors.""" + return pipette_definition.AvailableSensorDefinition( + sensors=["pressure", "capacitive", "environment"] + ) @pytest.fixture @@ -86,6 +115,12 @@ def mock_labware_view(decoy: Decoy) -> LabwareView: return decoy.mock(cls=LabwareView) +@pytest.fixture +def mock_well_view(decoy: Decoy) -> WellView: + """Get a mock in the shape of a WellView.""" + return decoy.mock(cls=WellView) + + @pytest.fixture def mock_module_view(decoy: Decoy) -> ModuleView: """Get a mock in the shape of a ModuleView.""" @@ -105,10 +140,10 @@ def mock_addressable_area_view(decoy: Decoy) -> AddressableAreaView: @pytest.fixture(autouse=True) -def patch_mock_move_types(decoy: Decoy, monkeypatch: pytest.MonkeyPatch) -> None: - """Mock out move_types.py functions.""" - for name, func in inspect.getmembers(move_types, inspect.isfunction): - monkeypatch.setattr(move_types, name, decoy.mock(func=func)) +def patch_mock__move_types(decoy: Decoy, monkeypatch: pytest.MonkeyPatch) -> None: + """Mock out _move_types.py functions.""" + for name, func in inspect.getmembers(_move_types, inspect.isfunction): + monkeypatch.setattr(_move_types, name, decoy.mock(func=func)) @pytest.fixture @@ -144,6 +179,18 @@ def labware_view(labware_store: LabwareStore) -> LabwareView: return LabwareView(labware_store._state) +@pytest.fixture +def well_store() -> WellStore: + """Get a well store that can accept actions.""" + return WellStore() + + +@pytest.fixture +def well_view(well_store: WellStore) -> WellView: + """Get a well view of a real well store.""" + return WellView(well_store._state) + + @pytest.fixture def module_store(state_config: Config) -> ModuleStore: """Get a module store that can accept actions.""" @@ -184,6 +231,12 @@ def addressable_area_store( "robotType": "OT-3 Standard", "models": ["OT-3 Standard"], "extents": [477.2, 493.8, 0.0], + "paddingOffsets": { + "rear": -177.42, + "front": 51.8, + "leftSide": 31.88, + "rightSide": -80.32, + }, "mountOffsets": { "left": [-13.5, -60.5, 255.675], "right": [40.5, -60.5, 255.675], @@ -228,11 +281,13 @@ def nice_adapter_definition() -> LabwareDefinition: @pytest.fixture def subject( mock_labware_view: LabwareView, + mock_well_view: WellView, mock_module_view: ModuleView, mock_pipette_view: PipetteView, mock_addressable_area_view: AddressableAreaView, state_config: Config, labware_view: LabwareView, + well_view: WellView, module_view: ModuleView, pipette_view: PipetteView, addressable_area_view: AddressableAreaView, @@ -253,6 +308,7 @@ def my_cool_test(subject: GeometryView) -> None: return GeometryView( config=state_config, labware_view=mock_labware_view if use_mocks else labware_view, + well_view=mock_well_view if use_mocks else well_view, module_view=mock_module_view if use_mocks else module_view, pipette_view=mock_pipette_view if use_mocks else pipette_view, addressable_area_view=mock_addressable_area_view @@ -321,6 +377,9 @@ def test_get_labware_parent_position_on_module( ) decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + sentinel.labware_def + ) decoy.when(mock_module_view.get_location("module-id")).then_return( DeckSlotLocation(slotName=DeckSlotName.SLOT_3) ) @@ -333,7 +392,7 @@ def test_get_labware_parent_position_on_module( ) decoy.when( - mock_module_view.get_nominal_module_offset( + mock_module_view.get_nominal_offset_to_child( module_id="module-id", addressable_areas=mock_addressable_area_view, ) @@ -344,7 +403,7 @@ def test_get_labware_parent_position_on_module( ) decoy.when( mock_labware_view.get_module_overlap_offsets( - "labware-id", ModuleModel.THERMOCYCLER_MODULE_V2 + sentinel.labware_def, ModuleModel.THERMOCYCLER_MODULE_V2 ) ).then_return(OverlapOffset(x=1, y=2, z=3)) decoy.when(mock_module_view.get_module_calibration_offset("module-id")).then_return( @@ -375,6 +434,11 @@ def test_get_labware_parent_position_on_labware( location=OnLabwareLocation(labwareId="adapter-id"), offsetId=None, ) + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition(labware_data.id)).then_return( + sentinel.labware_def + ) + adapter_data = LoadedLabware( id="adapter-id", loadName="xyz", @@ -382,37 +446,41 @@ def test_get_labware_parent_position_on_labware( location=ModuleLocation(moduleId="module-id"), offsetId=None, ) - decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get("adapter-id")).then_return(adapter_data) + decoy.when(mock_labware_view.get_definition(adapter_data.id)).then_return( + sentinel.adapter_def + ) + decoy.when(mock_module_view.get_location("module-id")).then_return( DeckSlotLocation(slotName=DeckSlotName.SLOT_3) ) decoy.when( mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_3.id) ).then_return(Point(1, 2, 3)) - decoy.when(mock_labware_view.get("adapter-id")).then_return(adapter_data) - decoy.when(mock_labware_view.get_dimensions("adapter-id")).then_return( + + decoy.when(mock_labware_view.get_dimensions(labware_id="adapter-id")).then_return( Dimensions(x=123, y=456, z=5) ) decoy.when( - mock_labware_view.get_labware_overlap_offsets("labware-id", "xyz") + mock_labware_view.get_labware_overlap_offsets(sentinel.labware_def, "xyz") ).then_return(OverlapOffset(x=1, y=2, z=2)) decoy.when(mock_labware_view.get_deck_definition()).then_return( ot2_standard_deck_def ) decoy.when( - mock_module_view.get_nominal_module_offset( + mock_module_view.get_nominal_offset_to_child( module_id="module-id", addressable_areas=mock_addressable_area_view, ) ).then_return(LabwareOffsetVector(x=1, y=2, z=3)) decoy.when(mock_module_view.get_connected_model("module-id")).then_return( - ModuleModel.MAGNETIC_MODULE_V2 + sentinel.connected_model ) decoy.when( mock_labware_view.get_module_overlap_offsets( - "adapter-id", ModuleModel.MAGNETIC_MODULE_V2 + sentinel.adapter_def, sentinel.connected_model ) ).then_return(OverlapOffset(x=-3, y=-2, z=-1)) @@ -592,7 +660,7 @@ def test_get_module_labware_highest_z( ot2_standard_deck_def ) decoy.when( - mock_module_view.get_nominal_module_offset( + mock_module_view.get_nominal_offset_to_child( module_id="module-id", addressable_areas=mock_addressable_area_view, ) @@ -609,7 +677,7 @@ def test_get_module_labware_highest_z( ) decoy.when( mock_labware_view.get_module_overlap_offsets( - "labware-id", ModuleModel.MAGNETIC_MODULE_V2 + well_plate_def, ModuleModel.MAGNETIC_MODULE_V2 ) ).then_return(OverlapOffset(x=0, y=0, z=0)) @@ -958,24 +1026,28 @@ def test_get_highest_z_in_slot_with_stacked_labware_on_slot( decoy.when(mock_labware_view.get_definition("top-labware-id")).then_return( well_plate_def ) + decoy.when(mock_labware_view.get_definition("middle-labware-id")).then_return( + sentinel.middle_labware_def + ) + decoy.when( mock_labware_view.get_labware_offset_vector("top-labware-id") ).then_return(top_lw_lpc_offset) - decoy.when(mock_labware_view.get_dimensions("middle-labware-id")).then_return( - Dimensions(x=10, y=20, z=30) - ) - decoy.when(mock_labware_view.get_dimensions("bottom-labware-id")).then_return( - Dimensions(x=11, y=12, z=13) - ) + decoy.when( + mock_labware_view.get_dimensions(labware_id="middle-labware-id") + ).then_return(Dimensions(x=10, y=20, z=30)) + decoy.when( + mock_labware_view.get_dimensions(labware_id="bottom-labware-id") + ).then_return(Dimensions(x=11, y=12, z=13)) decoy.when( mock_labware_view.get_labware_overlap_offsets( - "top-labware-id", below_labware_name="middle-labware-name" + well_plate_def, below_labware_name="middle-labware-name" ) ).then_return(OverlapOffset(x=4, y=5, z=6)) decoy.when( mock_labware_view.get_labware_overlap_offsets( - "middle-labware-id", below_labware_name="bottom-labware-name" + sentinel.middle_labware_def, below_labware_name="bottom-labware-name" ) ).then_return(OverlapOffset(x=7, y=8, z=9)) @@ -1054,16 +1126,20 @@ def test_get_highest_z_in_slot_with_labware_stack_on_module( ) decoy.when(mock_labware_view.get("adapter-id")).then_return(adapter) + decoy.when(mock_labware_view.get_definition("adapter-id")).then_return( + sentinel.adapter_def + ) decoy.when(mock_labware_view.get("top-labware-id")).then_return(top_labware) + decoy.when( mock_labware_view.get_labware_offset_vector("top-labware-id") ).then_return(top_lw_lpc_offset) - decoy.when(mock_labware_view.get_dimensions("adapter-id")).then_return( + decoy.when(mock_labware_view.get_dimensions(labware_id="adapter-id")).then_return( Dimensions(x=10, y=20, z=30) ) decoy.when( mock_labware_view.get_labware_overlap_offsets( - labware_id="top-labware-id", below_labware_name="adapter-name" + definition=well_plate_def, below_labware_name="adapter-name" ) ).then_return(OverlapOffset(x=4, y=5, z=6)) @@ -1071,7 +1147,7 @@ def test_get_highest_z_in_slot_with_labware_stack_on_module( DeckSlotLocation(slotName=DeckSlotName.SLOT_3) ) decoy.when( - mock_module_view.get_nominal_module_offset( + mock_module_view.get_nominal_offset_to_child( module_id="module-id", addressable_areas=mock_addressable_area_view, ) @@ -1082,7 +1158,7 @@ def test_get_highest_z_in_slot_with_labware_stack_on_module( decoy.when( mock_labware_view.get_module_overlap_offsets( - "adapter-id", ModuleModel.TEMPERATURE_MODULE_V2 + sentinel.adapter_def, ModuleModel.TEMPERATURE_MODULE_V2 ) ).then_return(OverlapOffset(x=1.1, y=2.2, z=3.3)) @@ -1288,7 +1364,7 @@ def test_get_module_labware_well_position( ot2_standard_deck_def ) decoy.when( - mock_module_view.get_nominal_module_offset( + mock_module_view.get_nominal_offset_to_child( module_id="module-id", addressable_areas=mock_addressable_area_view, ) @@ -1304,7 +1380,7 @@ def test_get_module_labware_well_position( ) decoy.when( mock_labware_view.get_module_overlap_offsets( - "labware-id", ModuleModel.MAGNETIC_MODULE_V2 + well_plate_def, ModuleModel.MAGNETIC_MODULE_V2 ) ).then_return(OverlapOffset(x=0, y=0, z=0)) @@ -1463,6 +1539,404 @@ def test_get_well_position_with_center_offset( ) +def test_get_well_position_with_meniscus_offset( + decoy: Decoy, + well_plate_def: LabwareDefinition, + mock_labware_view: LabwareView, + mock_well_view: WellView, + mock_addressable_area_view: AddressableAreaView, + mock_pipette_view: PipetteView, + subject: GeometryView, +) -> None: + """It should be able to get the position of a well meniscus in a labware.""" + labware_data = LoadedLabware( + id="labware-id", + loadName="load-name", + definitionUri="definition-uri", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), + offsetId="offset-id", + ) + calibration_offset = LabwareOffsetVector(x=1, y=-2, z=3) + slot_pos = Point(4, 5, 6) + well_def = well_plate_def.wells["B2"] + + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( + calibration_offset + ) + decoy.when( + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) + ).then_return(slot_pos) + decoy.when(mock_labware_view.get_well_definition("labware-id", "B2")).then_return( + well_def + ) + decoy.when(mock_well_view.get_well_liquid_info("labware-id", "B2")).then_return( + WellLiquidInfo( + probed_volume=None, + probed_height=ProbedHeightInfo(height=70.5, last_probed=datetime.now()), + loaded_volume=None, + ) + ) + decoy.when( + mock_pipette_view.get_current_tip_lld_settings(pipette_id="pipette-id") + ).then_return(0.5) + + result = subject.get_well_position( + labware_id="labware-id", + well_name="B2", + well_location=WellLocation( + origin=WellOrigin.MENISCUS, + offset=WellOffset(x=2, y=3, z=4), + ), + pipette_id="pipette-id", + ) + + assert result == Point( + x=slot_pos[0] + 1 + well_def.x + 2, + y=slot_pos[1] - 2 + well_def.y + 3, + z=slot_pos[2] + 3 + well_def.z + 4 + 70.5, + ) + + +def test_get_well_position_with_volume_offset_raises_error( + decoy: Decoy, + well_plate_def: LabwareDefinition, + mock_labware_view: LabwareView, + mock_well_view: WellView, + mock_addressable_area_view: AddressableAreaView, + mock_pipette_view: PipetteView, + subject: GeometryView, +) -> None: + """Calling get_well_position with any volume offset should raise an error when there's no innerLabwareGeometry.""" + labware_data = LoadedLabware( + id="labware-id", + loadName="load-name", + definitionUri="definition-uri", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), + offsetId="offset-id", + ) + calibration_offset = LabwareOffsetVector(x=1, y=-2, z=3) + slot_pos = Point(4, 5, 6) + well_def = well_plate_def.wells["B2"] + + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( + calibration_offset + ) + decoy.when( + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) + ).then_return(slot_pos) + decoy.when(mock_labware_view.get_well_definition("labware-id", "B2")).then_return( + well_def + ) + decoy.when(mock_well_view.get_well_liquid_info("labware-id", "B2")).then_return( + WellLiquidInfo( + loaded_volume=None, + probed_height=ProbedHeightInfo(height=45.0, last_probed=datetime.now()), + probed_volume=None, + ) + ) + decoy.when( + mock_pipette_view.get_current_tip_lld_settings(pipette_id="pipette-id") + ).then_return(0.5) + decoy.when(mock_labware_view.get_well_geometry("labware-id", "B2")).then_raise( + errors.IncompleteLabwareDefinitionError("Woops!") + ) + + with pytest.raises(errors.IncompleteLabwareDefinitionError): + subject.get_well_position( + labware_id="labware-id", + well_name="B2", + well_location=LiquidHandlingWellLocation( + origin=WellOrigin.MENISCUS, + offset=WellOffset(x=2, y=3, z=4), + volumeOffset="operationVolume", + ), + operation_volume=-1245.833, + pipette_id="pipette-id", + ) + + +def test_get_well_position_with_meniscus_and_literal_volume_offset( + decoy: Decoy, + well_plate_def: LabwareDefinition, + mock_labware_view: LabwareView, + mock_well_view: WellView, + mock_addressable_area_view: AddressableAreaView, + mock_pipette_view: PipetteView, + subject: GeometryView, +) -> None: + """It should be able to get the position of a well meniscus in a labware with a volume offset.""" + labware_data = LoadedLabware( + id="labware-id", + loadName="load-name", + definitionUri="definition-uri", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), + offsetId="offset-id", + ) + calibration_offset = LabwareOffsetVector(x=1, y=-2, z=3) + slot_pos = Point(4, 5, 6) + well_def = well_plate_def.wells["B2"] + + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( + calibration_offset + ) + decoy.when( + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) + ).then_return(slot_pos) + decoy.when(mock_labware_view.get_well_definition("labware-id", "B2")).then_return( + well_def + ) + decoy.when(mock_well_view.get_well_liquid_info("labware-id", "B2")).then_return( + WellLiquidInfo( + loaded_volume=None, + probed_height=ProbedHeightInfo(height=45.0, last_probed=datetime.now()), + probed_volume=None, + ) + ) + labware_def = _load_labware_definition_data() + assert labware_def.innerLabwareGeometry is not None + inner_well_def = labware_def.innerLabwareGeometry["welldefinition1111"] + decoy.when(mock_labware_view.get_well_geometry("labware-id", "B2")).then_return( + inner_well_def + ) + decoy.when( + mock_pipette_view.get_current_tip_lld_settings(pipette_id="pipette-id") + ).then_return(0.5) + + result = subject.get_well_position( + labware_id="labware-id", + well_name="B2", + well_location=LiquidHandlingWellLocation( + origin=WellOrigin.MENISCUS, + offset=WellOffset(x=2, y=3, z=4), + volumeOffset="operationVolume", + ), + operation_volume=-1245.833, + pipette_id="pipette-id", + ) + + assert result == Point( + x=slot_pos[0] + 1 + well_def.x + 2, + y=slot_pos[1] - 2 + well_def.y + 3, + z=slot_pos[2] + 3 + well_def.z + 4 + 20.0, + ) + + +def test_get_well_position_with_meniscus_and_float_volume_offset( + decoy: Decoy, + well_plate_def: LabwareDefinition, + mock_labware_view: LabwareView, + mock_well_view: WellView, + mock_addressable_area_view: AddressableAreaView, + mock_pipette_view: PipetteView, + subject: GeometryView, +) -> None: + """It should be able to get the position of a well meniscus in a labware with a volume offset.""" + labware_data = LoadedLabware( + id="labware-id", + loadName="load-name", + definitionUri="definition-uri", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), + offsetId="offset-id", + ) + calibration_offset = LabwareOffsetVector(x=1, y=-2, z=3) + slot_pos = Point(4, 5, 6) + well_def = well_plate_def.wells["B2"] + + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( + calibration_offset + ) + decoy.when( + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) + ).then_return(slot_pos) + decoy.when(mock_labware_view.get_well_definition("labware-id", "B2")).then_return( + well_def + ) + decoy.when(mock_well_view.get_well_liquid_info("labware-id", "B2")).then_return( + WellLiquidInfo( + loaded_volume=None, + probed_height=ProbedHeightInfo(height=45.0, last_probed=datetime.now()), + probed_volume=None, + ) + ) + labware_def = _load_labware_definition_data() + assert labware_def.innerLabwareGeometry is not None + inner_well_def = labware_def.innerLabwareGeometry["welldefinition1111"] + decoy.when(mock_labware_view.get_well_geometry("labware-id", "B2")).then_return( + inner_well_def + ) + decoy.when( + mock_pipette_view.get_current_tip_lld_settings(pipette_id="pipette-id") + ).then_return(0.5) + + result = subject.get_well_position( + labware_id="labware-id", + well_name="B2", + well_location=LiquidHandlingWellLocation( + origin=WellOrigin.MENISCUS, + offset=WellOffset(x=2, y=3, z=4), + volumeOffset=-1245.833, + ), + pipette_id="pipette-id", + ) + + assert result == Point( + x=slot_pos[0] + 1 + well_def.x + 2, + y=slot_pos[1] - 2 + well_def.y + 3, + z=slot_pos[2] + 3 + well_def.z + 4 + 20.0, + ) + + +def test_get_well_position_raises_validation_error( + decoy: Decoy, + well_plate_def: LabwareDefinition, + mock_labware_view: LabwareView, + mock_well_view: WellView, + mock_addressable_area_view: AddressableAreaView, + mock_pipette_view: PipetteView, + subject: GeometryView, +) -> None: + """It should raise a validation error when a volume offset is too large (ie location is below the well bottom).""" + labware_data = LoadedLabware( + id="labware-id", + loadName="load-name", + definitionUri="definition-uri", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), + offsetId="offset-id", + ) + calibration_offset = LabwareOffsetVector(x=1, y=-2, z=3) + slot_pos = Point(4, 5, 6) + well_def = well_plate_def.wells["B2"] + + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( + calibration_offset + ) + decoy.when( + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) + ).then_return(slot_pos) + decoy.when(mock_labware_view.get_well_definition("labware-id", "B2")).then_return( + well_def + ) + decoy.when(mock_well_view.get_well_liquid_info("labware-id", "B2")).then_return( + WellLiquidInfo( + loaded_volume=None, + probed_height=ProbedHeightInfo(height=40.0, last_probed=datetime.now()), + probed_volume=None, + ) + ) + labware_def = _load_labware_definition_data() + assert labware_def.innerLabwareGeometry is not None + inner_well_def = labware_def.innerLabwareGeometry["welldefinition1111"] + decoy.when(mock_labware_view.get_well_geometry("labware-id", "B2")).then_return( + inner_well_def + ) + decoy.when( + mock_pipette_view.get_current_tip_lld_settings(pipette_id="pipette-id") + ).then_return(0.5) + + with pytest.raises(errors.OperationLocationNotInWellError): + subject.get_well_position( + labware_id="labware-id", + well_name="B2", + well_location=LiquidHandlingWellLocation( + origin=WellOrigin.MENISCUS, + offset=WellOffset(x=2, y=3, z=-40), + volumeOffset="operationVolume", + ), + operation_volume=-100.0, + pipette_id="pipette-id", + ) + + +def test_get_meniscus_height( + decoy: Decoy, + well_plate_def: LabwareDefinition, + mock_labware_view: LabwareView, + mock_well_view: WellView, + mock_addressable_area_view: AddressableAreaView, + mock_pipette_view: PipetteView, + subject: GeometryView, +) -> None: + """It should be able to get the position of a well meniscus in a labware.""" + labware_data = LoadedLabware( + id="labware-id", + loadName="load-name", + definitionUri="definition-uri", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), + offsetId="offset-id", + ) + calibration_offset = LabwareOffsetVector(x=1, y=-2, z=3) + slot_pos = Point(4, 5, 6) + well_def = well_plate_def.wells["B2"] + + decoy.when(mock_labware_view.get("labware-id")).then_return(labware_data) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_labware_view.get_labware_offset_vector("labware-id")).then_return( + calibration_offset + ) + decoy.when( + mock_addressable_area_view.get_addressable_area_position(DeckSlotName.SLOT_4.id) + ).then_return(slot_pos) + decoy.when(mock_labware_view.get_well_definition("labware-id", "B2")).then_return( + well_def + ) + decoy.when(mock_well_view.get_well_liquid_info("labware-id", "B2")).then_return( + WellLiquidInfo( + loaded_volume=LoadedVolumeInfo( + volume=2000.0, last_loaded=datetime.now(), operations_since_load=0 + ), + probed_height=None, + probed_volume=None, + ) + ) + labware_def = _load_labware_definition_data() + assert labware_def.innerLabwareGeometry is not None + inner_well_def = labware_def.innerLabwareGeometry["welldefinition1111"] + decoy.when(mock_labware_view.get_well_geometry("labware-id", "B2")).then_return( + inner_well_def + ) + decoy.when( + mock_pipette_view.get_current_tip_lld_settings(pipette_id="pipette-id") + ).then_return(0.5) + + result = subject.get_well_position( + labware_id="labware-id", + well_name="B2", + well_location=WellLocation( + origin=WellOrigin.MENISCUS, + offset=WellOffset(x=2, y=3, z=4), + ), + pipette_id="pipette-id", + ) + + assert result == Point( + x=slot_pos[0] + 1 + well_def.x + 2, + y=slot_pos[1] - 2 + well_def.y + 3, + z=slot_pos[2] + 3 + well_def.z + 4 + 39.2423, + ) + + def test_get_relative_well_location( decoy: Decoy, well_plate_def: LabwareDefinition, @@ -1516,6 +1990,31 @@ def test_get_relative_well_location( ) +def test_get_relative_liquid_handling_well_location( + decoy: Decoy, + well_plate_def: LabwareDefinition, + mock_labware_view: LabwareView, + mock_addressable_area_view: AddressableAreaView, + subject: GeometryView, +) -> None: + """It should get the relative location of a well given an absolute position.""" + result = subject.get_relative_liquid_handling_well_location( + labware_id="labware-id", + well_name="B2", + absolute_point=Point(x=0, y=0, z=-2), + is_meniscus=True, + ) + + assert result == LiquidHandlingWellLocation( + origin=WellOrigin.MENISCUS, + offset=WellOffset.construct( + x=0.0, + y=0.0, + z=cast(float, pytest.approx(-2)), + ), + ) + + def test_get_nominal_effective_tip_length( decoy: Decoy, mock_labware_view: LabwareView, @@ -1721,6 +2220,33 @@ def test_get_ancestor_slot_name( assert subject.get_ancestor_slot_name("labware-2") == DeckSlotName.SLOT_1 +def test_get_ancestor_slot_for_labware_stack_in_staging_area_slot( + decoy: Decoy, + mock_labware_view: LabwareView, + subject: GeometryView, +) -> None: + """It should get name of ancestor slot of a stack of labware in a staging area slot.""" + decoy.when(mock_labware_view.get("labware-1")).then_return( + LoadedLabware( + id="labware-1", + loadName="load-name", + definitionUri="1234", + location=AddressableAreaLocation( + addressableAreaName=StagingSlotName.SLOT_D4.id + ), + ) + ) + decoy.when(mock_labware_view.get("labware-2")).then_return( + LoadedLabware( + id="labware-2", + loadName="load-name", + definitionUri="1234", + location=OnLabwareLocation(labwareId="labware-1"), + ) + ) + assert subject.get_ancestor_slot_name("labware-2") == StagingSlotName.SLOT_D4 + + def test_ensure_location_not_occupied_raises( decoy: Decoy, mock_labware_view: LabwareView, @@ -1760,21 +2286,22 @@ def test_ensure_location_not_occupied_raises( def test_get_labware_grip_point( decoy: Decoy, mock_labware_view: LabwareView, - mock_module_view: ModuleView, mock_addressable_area_view: AddressableAreaView, - ot2_standard_deck_def: DeckDefinitionV5, subject: GeometryView, ) -> None: """It should get the grip point of the labware at the specified location.""" decoy.when( - mock_labware_view.get_grip_height_from_labware_bottom("labware-id") + mock_labware_view.get_grip_height_from_labware_bottom( + sentinel.labware_definition + ) ).then_return(100) decoy.when( mock_addressable_area_view.get_addressable_area_center(DeckSlotName.SLOT_1.id) ).then_return(Point(x=101, y=102, z=103)) labware_center = subject.get_labware_grip_point( - labware_id="labware-id", location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1) + labware_definition=sentinel.labware_definition, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), ) assert labware_center == Point(101.0, 102.0, 203) @@ -1783,20 +2310,10 @@ def test_get_labware_grip_point( def test_get_labware_grip_point_on_labware( decoy: Decoy, mock_labware_view: LabwareView, - mock_module_view: ModuleView, mock_addressable_area_view: AddressableAreaView, - ot2_standard_deck_def: DeckDefinitionV5, subject: GeometryView, ) -> None: """It should get the grip point of a labware on another labware.""" - decoy.when(mock_labware_view.get(labware_id="labware-id")).then_return( - LoadedLabware( - id="labware-id", - loadName="above-name", - definitionUri="1234", - location=OnLabwareLocation(labwareId="below-id"), - ) - ) decoy.when(mock_labware_view.get(labware_id="below-id")).then_return( LoadedLabware( id="below-id", @@ -1806,14 +2323,16 @@ def test_get_labware_grip_point_on_labware( ) ) - decoy.when(mock_labware_view.get_dimensions("below-id")).then_return( + decoy.when(mock_labware_view.get_dimensions(labware_id="below-id")).then_return( Dimensions(x=1000, y=1001, z=11) ) decoy.when( - mock_labware_view.get_grip_height_from_labware_bottom("labware-id") + mock_labware_view.get_grip_height_from_labware_bottom( + labware_definition=sentinel.definition + ) ).then_return(100) decoy.when( - mock_labware_view.get_labware_overlap_offsets("labware-id", "below-name") + mock_labware_view.get_labware_overlap_offsets(sentinel.definition, "below-name") ).then_return(OverlapOffset(x=0, y=1, z=6)) decoy.when( @@ -1821,7 +2340,8 @@ def test_get_labware_grip_point_on_labware( ).then_return(Point(x=5, y=9, z=10)) grip_point = subject.get_labware_grip_point( - labware_id="labware-id", location=OnLabwareLocation(labwareId="below-id") + labware_definition=sentinel.definition, + location=OnLabwareLocation(labwareId="below-id"), ) assert grip_point == Point(5, 10, 115.0) @@ -1837,7 +2357,9 @@ def test_get_labware_grip_point_for_labware_on_module( ) -> None: """It should return the grip point for labware directly on a module.""" decoy.when( - mock_labware_view.get_grip_height_from_labware_bottom("labware-id") + mock_labware_view.get_grip_height_from_labware_bottom( + sentinel.labware_definition + ) ).then_return(500) decoy.when(mock_module_view.get_location("module-id")).then_return( DeckSlotLocation(slotName=DeckSlotName.SLOT_4) @@ -1846,7 +2368,7 @@ def test_get_labware_grip_point_for_labware_on_module( ot2_standard_deck_def ) decoy.when( - mock_module_view.get_nominal_module_offset( + mock_module_view.get_nominal_offset_to_child( module_id="module-id", addressable_areas=mock_addressable_area_view, ) @@ -1856,7 +2378,7 @@ def test_get_labware_grip_point_for_labware_on_module( ) decoy.when( mock_labware_view.get_module_overlap_offsets( - "labware-id", ModuleModel.MAGNETIC_MODULE_V2 + sentinel.labware_definition, ModuleModel.MAGNETIC_MODULE_V2 ) ).then_return(OverlapOffset(x=10, y=20, z=30)) decoy.when(mock_module_view.get_module_calibration_offset("module-id")).then_return( @@ -1869,7 +2391,8 @@ def test_get_labware_grip_point_for_labware_on_module( mock_addressable_area_view.get_addressable_area_center(DeckSlotName.SLOT_4.id) ).then_return(Point(100, 200, 300)) result_grip_point = subject.get_labware_grip_point( - labware_id="labware-id", location=ModuleLocation(moduleId="module-id") + labware_definition=sentinel.labware_definition, + location=ModuleLocation(moduleId="module-id"), ) assert result_grip_point == Point(x=191, y=382, z=1073) @@ -2060,6 +2583,7 @@ def test_get_next_drop_tip_location( pipette_mount: MountType, expected_locations: List[DropTipWellLocation], supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: pipette_definition.AvailableSensorDefinition, ) -> None: """It should provide the next location to drop tips into within a labware.""" decoy.when(mock_labware_view.is_fixed_trash(labware_id="abc")).then_return(True) @@ -2096,6 +2620,14 @@ def test_get_next_drop_tip_location( back_right_corner=Point(x=40, y=20, z=60), ), lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ) ) decoy.when(mock_pipette_view.get_mount("pip-123")).then_return(pipette_mount) @@ -2140,6 +2672,7 @@ def test_get_final_labware_movement_offset_vectors( mock_module_view: ModuleView, mock_labware_view: LabwareView, subject: GeometryView, + well_plate_def: LabwareDefinition, ) -> None: """It should provide the final labware movement offset data based on locations.""" decoy.when(mock_labware_view.get_deck_default_gripper_offsets()).then_return( @@ -2155,6 +2688,10 @@ def test_get_final_labware_movement_offset_vectors( ) ) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + final_offsets = subject.get_final_labware_movement_offset_vectors( from_location=DeckSlotLocation(slotName=DeckSlotName("D2")), to_location=ModuleLocation(moduleId="module-id"), @@ -2162,6 +2699,7 @@ def test_get_final_labware_movement_offset_vectors( pickUpOffset=LabwareOffsetVector(x=100, y=200, z=300), dropOffset=LabwareOffsetVector(x=400, y=500, z=600), ), + current_labware=mock_labware_view.get_definition("labware-id"), ) assert final_offsets == LabwareMovementOffsetData( pickUpOffset=LabwareOffsetVector(x=101, y=202, z=303), @@ -2192,6 +2730,7 @@ def test_get_total_nominal_gripper_offset( mock_labware_view: LabwareView, mock_module_view: ModuleView, subject: GeometryView, + well_plate_def: LabwareDefinition, ) -> None: """It should calculate the correct gripper offsets given the location and move type..""" decoy.when(mock_labware_view.get_deck_default_gripper_offsets()).then_return( @@ -2208,10 +2747,15 @@ def test_get_total_nominal_gripper_offset( ) ) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + # Case 1: labware on deck result1 = subject.get_total_nominal_gripper_offset_for_move_type( location=DeckSlotLocation(slotName=DeckSlotName.SLOT_3), move_type=_GripperMoveType.PICK_UP_LABWARE, + current_labware=mock_labware_view.get_definition("labware-d"), ) assert result1 == LabwareOffsetVector(x=1, y=2, z=3) @@ -2219,6 +2763,7 @@ def test_get_total_nominal_gripper_offset( result2 = subject.get_total_nominal_gripper_offset_for_move_type( location=ModuleLocation(moduleId="module-id"), move_type=_GripperMoveType.DROP_LABWARE, + current_labware=mock_labware_view.get_definition("labware-id"), ) assert result2 == LabwareOffsetVector(x=33, y=22, z=11) @@ -2228,6 +2773,7 @@ def test_get_stacked_labware_total_nominal_offset_slot_specific( mock_labware_view: LabwareView, mock_module_view: ModuleView, subject: GeometryView, + well_plate_def: LabwareDefinition, ) -> None: """Get nominal offset for stacked labware.""" # Case: labware on adapter on module, adapter has slot-specific offsets @@ -2241,7 +2787,7 @@ def test_get_stacked_labware_total_nominal_offset_slot_specific( DeckSlotLocation(slotName=DeckSlotName.SLOT_C1) ) decoy.when( - mock_labware_view.get_labware_gripper_offsets( + mock_labware_view.get_child_gripper_offsets( labware_id="adapter-id", slot_name=DeckSlotName.SLOT_C1 ) ).then_return( @@ -2253,15 +2799,23 @@ def test_get_stacked_labware_total_nominal_offset_slot_specific( decoy.when(mock_labware_view.get_parent_location("adapter-id")).then_return( ModuleLocation(moduleId="module-id") ) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_module_view._state.requested_model_by_id).then_return( + {"module-id": ModuleModel.HEATER_SHAKER_MODULE_V1} + ) result1 = subject.get_total_nominal_gripper_offset_for_move_type( location=OnLabwareLocation(labwareId="adapter-id"), move_type=_GripperMoveType.PICK_UP_LABWARE, + current_labware=mock_labware_view.get_definition("labware-id"), ) assert result1 == LabwareOffsetVector(x=111, y=222, z=333) result2 = subject.get_total_nominal_gripper_offset_for_move_type( location=OnLabwareLocation(labwareId="adapter-id"), move_type=_GripperMoveType.DROP_LABWARE, + current_labware=mock_labware_view.get_definition("labware-id"), ) assert result2 == LabwareOffsetVector(x=333, y=222, z=111) @@ -2271,6 +2825,7 @@ def test_get_stacked_labware_total_nominal_offset_default( mock_labware_view: LabwareView, mock_module_view: ModuleView, subject: GeometryView, + well_plate_def: LabwareDefinition, ) -> None: """Get nominal offset for stacked labware.""" # Case: labware on adapter on module, adapter has only default offsets @@ -2284,12 +2839,12 @@ def test_get_stacked_labware_total_nominal_offset_default( DeckSlotLocation(slotName=DeckSlotName.SLOT_4) ) decoy.when( - mock_labware_view.get_labware_gripper_offsets( + mock_labware_view.get_child_gripper_offsets( labware_id="adapter-id", slot_name=DeckSlotName.SLOT_C1 ) ).then_return(None) decoy.when( - mock_labware_view.get_labware_gripper_offsets( + mock_labware_view.get_child_gripper_offsets( labware_id="adapter-id", slot_name=None ) ).then_return( @@ -2301,15 +2856,23 @@ def test_get_stacked_labware_total_nominal_offset_default( decoy.when(mock_labware_view.get_parent_location("adapter-id")).then_return( ModuleLocation(moduleId="module-id") ) + decoy.when(mock_labware_view.get_definition("labware-id")).then_return( + well_plate_def + ) + decoy.when(mock_module_view._state.requested_model_by_id).then_return( + {"module-id": ModuleModel.HEATER_SHAKER_MODULE_V1} + ) result1 = subject.get_total_nominal_gripper_offset_for_move_type( location=OnLabwareLocation(labwareId="adapter-id"), move_type=_GripperMoveType.PICK_UP_LABWARE, + current_labware=mock_labware_view.get_definition("labware-id"), ) assert result1 == LabwareOffsetVector(x=111, y=222, z=333) result2 = subject.get_total_nominal_gripper_offset_for_move_type( location=OnLabwareLocation(labwareId="adapter-id"), move_type=_GripperMoveType.DROP_LABWARE, + current_labware=mock_labware_view.get_definition("labware-id"), ) assert result2 == LabwareOffsetVector(x=333, y=222, z=111) @@ -2389,12 +2952,12 @@ def test_check_gripper_labware_tip_collision( ) ).then_return(Point(x=11, y=22, z=33)) decoy.when( - mock_labware_view.get_grip_height_from_labware_bottom("labware-id") + mock_labware_view.get_grip_height_from_labware_bottom(definition) ).then_return(1.0) decoy.when(mock_labware_view.get_definition("labware-id")).then_return(definition) decoy.when( subject.get_labware_grip_point( - labware_id="labware-id", + labware_definition=definition, location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), ) ).then_return(Point(x=100.0, y=100.0, z=0.0)) @@ -2438,7 +3001,15 @@ def test_get_offset_location_deck_slot( version=nice_labware_definition.version, ), ), - private_result=None, + state_update=StateUpdate( + loaded_labware=LoadedLabwareUpdate( + labware_id="labware-id-1", + definition=nice_labware_definition, + offset_id=None, + new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_C2), + display_name=None, + ) + ), ) labware_store.handle_action(action) offset_location = subject.get_offset_location("labware-id-1") @@ -2474,7 +3045,6 @@ def test_get_offset_location_module( model=tempdeck_v2_def.model, ), ), - private_result=None, ) load_labware = SucceedCommandAction( command=LoadLabware( @@ -2494,8 +3064,17 @@ def test_get_offset_location_module( version=nice_labware_definition.version, ), ), - private_result=None, + state_update=StateUpdate( + loaded_labware=LoadedLabwareUpdate( + labware_id="labware-id-1", + definition=nice_labware_definition, + offset_id=None, + new_location=ModuleLocation(moduleId="module-id-1"), + display_name=None, + ) + ), ) + module_store.handle_action(load_module) labware_store.handle_action(load_labware) offset_location = subject.get_offset_location("labware-id-1") @@ -2533,7 +3112,6 @@ def test_get_offset_location_module_with_adapter( model=tempdeck_v2_def.model, ), ), - private_result=None, ) load_adapter = SucceedCommandAction( command=LoadLabware( @@ -2553,7 +3131,15 @@ def test_get_offset_location_module_with_adapter( version=nice_adapter_definition.version, ), ), - private_result=None, + state_update=StateUpdate( + loaded_labware=LoadedLabwareUpdate( + labware_id="adapter-id-1", + definition=nice_adapter_definition, + offset_id=None, + new_location=ModuleLocation(moduleId="module-id-1"), + display_name=None, + ) + ), ) load_labware = SucceedCommandAction( command=LoadLabware( @@ -2573,7 +3159,15 @@ def test_get_offset_location_module_with_adapter( version=nice_labware_definition.version, ), ), - private_result=None, + state_update=StateUpdate( + loaded_labware=LoadedLabwareUpdate( + labware_id="labware-id-1", + definition=nice_labware_definition, + offset_id=None, + new_location=OnLabwareLocation(labwareId="adapter-id-1"), + display_name=None, + ) + ), ) module_store.handle_action(load_module) labware_store.handle_action(load_adapter) @@ -2613,8 +3207,155 @@ def test_get_offset_fails_with_off_deck_labware( version=nice_labware_definition.version, ), ), - private_result=None, + state_update=StateUpdate( + loaded_labware=LoadedLabwareUpdate( + labware_id="labware-id-1", + definition=nice_labware_definition, + offset_id=None, + new_location=OFF_DECK_LOCATION, + display_name=None, + ) + ), ) labware_store.handle_action(action) offset_location = subject.get_offset_location("labware-id-1") assert offset_location is None + + +@pytest.mark.parametrize("frustum", RECTANGULAR_TEST_EXAMPLES) +def test_rectangular_frustum_math_helpers( + decoy: Decoy, + frustum: Dict[str, List[float]], + subject: GeometryView, +) -> None: + """Test both height and volume calculation within a given rectangular frustum.""" + total_frustum_height = frustum["height"][0] + bottom_length = frustum["length"][-1] + bottom_width = frustum["width"][-1] + + def _find_volume_from_height_(index: int) -> None: + nonlocal total_frustum_height, bottom_width, bottom_length + top_length = frustum["length"][index] + top_width = frustum["width"][index] + target_height = frustum["height"][index] + + found_volume = _volume_from_height_rectangular( + target_height=target_height, + total_frustum_height=total_frustum_height, + top_length=top_length, + bottom_length=bottom_length, + top_width=top_width, + bottom_width=bottom_width, + ) + + found_height = _height_from_volume_rectangular( + volume=found_volume, + total_frustum_height=total_frustum_height, + top_length=top_length, + bottom_length=bottom_length, + top_width=top_width, + bottom_width=bottom_width, + ) + + assert isclose(found_height, frustum["height"][index]) + + for i in range(len(frustum["height"])): + _find_volume_from_height_(i) + + +@pytest.mark.parametrize("frustum", CIRCULAR_TEST_EXAMPLES) +def test_circular_frustum_math_helpers( + decoy: Decoy, + frustum: Dict[str, List[float]], + subject: GeometryView, +) -> None: + """Test both height and volume calculation within a given circular frustum.""" + total_frustum_height = frustum["height"][0] + bottom_radius = frustum["radius"][-1] + + def _find_volume_from_height_(index: int) -> None: + nonlocal total_frustum_height, bottom_radius + top_radius = frustum["radius"][index] + target_height = frustum["height"][index] + + found_volume = _volume_from_height_circular( + target_height=target_height, + total_frustum_height=total_frustum_height, + top_radius=top_radius, + bottom_radius=bottom_radius, + ) + + found_height = _height_from_volume_circular( + volume=found_volume, + total_frustum_height=total_frustum_height, + top_radius=top_radius, + bottom_radius=bottom_radius, + ) + + assert isclose(found_height, frustum["height"][index]) + + for i in range(len(frustum["height"])): + _find_volume_from_height_(i) + + +def test_validate_dispense_volume_into_well_bottom( + decoy: Decoy, + well_plate_def: LabwareDefinition, + mock_labware_view: LabwareView, + subject: GeometryView, +) -> None: + """It should raise an InvalidDispenseVolumeError if too much volume is specified.""" + well_def = well_plate_def.wells["B2"] + decoy.when(mock_labware_view.get_well_definition("labware-id", "B2")).then_return( + well_def + ) + + with pytest.raises(errors.InvalidDispenseVolumeError): + subject.validate_dispense_volume_into_well( + labware_id="labware-id", + well_name="B2", + well_location=LiquidHandlingWellLocation( + origin=WellOrigin.BOTTOM, + offset=WellOffset(x=2, y=3, z=4), + ), + volume=400.0, + ) + + +def test_validate_dispense_volume_into_well_meniscus( + decoy: Decoy, + mock_labware_view: LabwareView, + mock_well_view: WellView, + subject: GeometryView, +) -> None: + """It should raise an InvalidDispenseVolumeError if too much volume is specified.""" + labware_def = _load_labware_definition_data() + assert labware_def.wells is not None + well_def = labware_def.wells["A1"] + assert labware_def.innerLabwareGeometry is not None + inner_well_def = labware_def.innerLabwareGeometry["welldefinition1111"] + + decoy.when(mock_labware_view.get_well_definition("labware-id", "A1")).then_return( + well_def + ) + decoy.when(mock_labware_view.get_well_geometry("labware-id", "A1")).then_return( + inner_well_def + ) + decoy.when(mock_well_view.get_well_liquid_info("labware-id", "A1")).then_return( + WellLiquidInfo( + loaded_volume=None, + probed_height=ProbedHeightInfo(height=40.0, last_probed=datetime.now()), + probed_volume=None, + ) + ) + + with pytest.raises(errors.InvalidDispenseVolumeError): + subject.validate_dispense_volume_into_well( + labware_id="labware-id", + well_name="A1", + well_location=LiquidHandlingWellLocation( + origin=WellOrigin.MENISCUS, + offset=WellOffset(x=2, y=3, z=4), + ), + volume=1100000.0, + ) diff --git a/api/tests/opentrons/protocol_engine/state/test_labware_store.py b/api/tests/opentrons/protocol_engine/state/test_labware_store.py deleted file mode 100644 index cb651fc37a7..00000000000 --- a/api/tests/opentrons/protocol_engine/state/test_labware_store.py +++ /dev/null @@ -1,291 +0,0 @@ -"""Labware state store tests.""" -import pytest - -from datetime import datetime - -from opentrons.calibration_storage.helpers import uri_from_details -from opentrons_shared_data.deck.types import DeckDefinitionV5 -from opentrons.protocols.models import LabwareDefinition -from opentrons.types import DeckSlotName - -from opentrons.protocol_engine.types import ( - LabwareOffset, - LabwareOffsetCreate, - LabwareOffsetVector, - LabwareOffsetLocation, - DeckSlotLocation, - LoadedLabware, - OFF_DECK_LOCATION, - LabwareMovementStrategy, -) -from opentrons.protocol_engine.actions import ( - AddLabwareOffsetAction, - AddLabwareDefinitionAction, - SucceedCommandAction, -) -from opentrons.protocol_engine.state.labware import LabwareStore, LabwareState - -from .command_fixtures import ( - create_load_labware_command, - create_move_labware_command, - create_reload_labware_command, -) - - -@pytest.fixture -def subject( - ot2_standard_deck_def: DeckDefinitionV5, -) -> LabwareStore: - """Get a LabwareStore test subject.""" - return LabwareStore( - deck_definition=ot2_standard_deck_def, - deck_fixed_labware=[], - ) - - -def test_initial_state( - ot2_standard_deck_def: DeckDefinitionV5, - subject: LabwareStore, -) -> None: - """It should create the labware store with preloaded fixed labware.""" - assert subject.state == LabwareState( - deck_definition=ot2_standard_deck_def, - labware_by_id={}, - labware_offsets_by_id={}, - definitions_by_uri={}, - ) - - -def test_handles_add_labware_offset( - subject: LabwareStore, -) -> None: - """It should add the labware offset to the state and add the ID.""" - request = LabwareOffsetCreate( - definitionUri="offset-definition-uri", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), - vector=LabwareOffsetVector(x=1, y=2, z=3), - ) - - resolved_offset = LabwareOffset( - id="offset-id", - createdAt=datetime(year=2021, month=1, day=2), - definitionUri="offset-definition-uri", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), - vector=LabwareOffsetVector(x=1, y=2, z=3), - ) - - subject.handle_action( - AddLabwareOffsetAction( - labware_offset_id="offset-id", - created_at=datetime(year=2021, month=1, day=2), - request=request, - ) - ) - - assert subject.state.labware_offsets_by_id == {"offset-id": resolved_offset} - - -def test_handles_load_labware( - subject: LabwareStore, - well_plate_def: LabwareDefinition, -) -> None: - """It should add the labware data to the state.""" - offset_request = LabwareOffsetCreate( - definitionUri="offset-definition-uri", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), - vector=LabwareOffsetVector(x=1, y=2, z=3), - ) - - command = create_load_labware_command( - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - labware_id="test-labware-id", - definition=well_plate_def, - offset_id="offset-id", - display_name="display-name", - ) - - expected_definition_uri = uri_from_details( - load_name=well_plate_def.parameters.loadName, - namespace=well_plate_def.namespace, - version=well_plate_def.version, - ) - - expected_labware_data = LoadedLabware( - id="test-labware-id", - loadName=well_plate_def.parameters.loadName, - definitionUri=expected_definition_uri, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - offsetId="offset-id", - displayName="display-name", - ) - - subject.handle_action( - AddLabwareOffsetAction( - request=offset_request, - labware_offset_id="offset-id", - created_at=datetime(year=2021, month=1, day=2), - ) - ) - subject.handle_action(SucceedCommandAction(private_result=None, command=command)) - - assert subject.state.labware_by_id["test-labware-id"] == expected_labware_data - - assert subject.state.definitions_by_uri[expected_definition_uri] == well_plate_def - - -def test_handles_reload_labware( - subject: LabwareStore, - well_plate_def: LabwareDefinition, -) -> None: - """It should override labware data in the state.""" - load_labware = create_load_labware_command( - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1), - labware_id="test-labware-id", - definition=well_plate_def, - display_name="display-name", - offset_id=None, - ) - - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_labware) - ) - expected_definition_uri = uri_from_details( - load_name=well_plate_def.parameters.loadName, - namespace=well_plate_def.namespace, - version=well_plate_def.version, - ) - assert ( - subject.state.labware_by_id["test-labware-id"].definitionUri - == expected_definition_uri - ) - - offset_request = LabwareOffsetCreate( - definitionUri="offset-definition-uri", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), - vector=LabwareOffsetVector(x=1, y=2, z=3), - ) - subject.handle_action( - AddLabwareOffsetAction( - request=offset_request, - labware_offset_id="offset-id", - created_at=datetime(year=2021, month=1, day=2), - ) - ) - reload_labware = create_reload_labware_command( - labware_id="test-labware-id", - offset_id="offset-id", - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=reload_labware) - ) - - expected_labware_data = LoadedLabware( - id="test-labware-id", - loadName=well_plate_def.parameters.loadName, - definitionUri=expected_definition_uri, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1), - offsetId="offset-id", - displayName="display-name", - ) - assert subject.state.labware_by_id["test-labware-id"] == expected_labware_data - assert subject.state.definitions_by_uri[expected_definition_uri] == well_plate_def - - -def test_handles_add_labware_definition( - subject: LabwareStore, - well_plate_def: LabwareDefinition, -) -> None: - """It should add the labware definition to the state.""" - expected_uri = uri_from_details( - load_name=well_plate_def.parameters.loadName, - namespace=well_plate_def.namespace, - version=well_plate_def.version, - ) - - subject.handle_action(AddLabwareDefinitionAction(definition=well_plate_def)) - - assert subject.state.definitions_by_uri[expected_uri] == well_plate_def - - -def test_handles_move_labware( - subject: LabwareStore, - well_plate_def: LabwareDefinition, -) -> None: - """It should update labware state with new location & offset.""" - load_labware_command = create_load_labware_command( - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - labware_id="my-labware-id", - definition=well_plate_def, - offset_id="old-offset-id", - display_name="display-name", - ) - offset_request = LabwareOffsetCreate( - definitionUri="offset-definition-uri", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), - vector=LabwareOffsetVector(x=1, y=2, z=3), - ) - subject.handle_action( - AddLabwareOffsetAction( - request=offset_request, - labware_offset_id="old-offset-id", - created_at=datetime(year=2021, month=1, day=2), - ) - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_labware_command) - ) - - move_command = create_move_labware_command( - labware_id="my-labware-id", - new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_4), - offset_id="my-new-offset", - strategy=LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE, - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=move_command) - ) - - assert subject.state.labware_by_id["my-labware-id"].location == DeckSlotLocation( - slotName=DeckSlotName.SLOT_4 - ) - assert subject.state.labware_by_id["my-labware-id"].offsetId == "my-new-offset" - - -def test_handles_move_labware_off_deck( - subject: LabwareStore, - well_plate_def: LabwareDefinition, -) -> None: - """It should update labware state with new location & offset.""" - load_labware_command = create_load_labware_command( - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - labware_id="my-labware-id", - definition=well_plate_def, - offset_id="old-offset-id", - display_name="display-name", - ) - offset_request = LabwareOffsetCreate( - definitionUri="offset-definition-uri", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), - vector=LabwareOffsetVector(x=1, y=2, z=3), - ) - subject.handle_action( - AddLabwareOffsetAction( - request=offset_request, - labware_offset_id="old-offset-id", - created_at=datetime(year=2021, month=1, day=2), - ) - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_labware_command) - ) - - move_labware_off_deck_cmd = create_move_labware_command( - labware_id="my-labware-id", - new_location=OFF_DECK_LOCATION, - strategy=LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE, - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=move_labware_off_deck_cmd) - ) - assert subject.state.labware_by_id["my-labware-id"].location == OFF_DECK_LOCATION - assert subject.state.labware_by_id["my-labware-id"].offsetId is None diff --git a/api/tests/opentrons/protocol_engine/state/test_labware_store_old.py b/api/tests/opentrons/protocol_engine/state/test_labware_store_old.py new file mode 100644 index 00000000000..3b539df58e3 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_labware_store_old.py @@ -0,0 +1,343 @@ +"""Labware state store tests. + +DEPRECATED: Testing LabwareStore independently of LabwareView is no +longer helpful. Try to add new tests to test_labware_state.py, where they can be +tested together, treating LabwareState as a private implementation detail. +""" +from typing import Optional +from opentrons.protocol_engine.state import update_types +import pytest + +from datetime import datetime + +from opentrons.calibration_storage.helpers import uri_from_details +from opentrons_shared_data.deck.types import DeckDefinitionV5 +from opentrons.protocols.models import LabwareDefinition +from opentrons.types import DeckSlotName + +from opentrons.protocol_engine.types import ( + LabwareOffset, + LabwareOffsetCreate, + LabwareOffsetVector, + LabwareOffsetLocation, + DeckSlotLocation, + LoadedLabware, + OFF_DECK_LOCATION, +) +from opentrons.protocol_engine.actions import ( + AddLabwareOffsetAction, + AddLabwareDefinitionAction, + SucceedCommandAction, +) +from opentrons.protocol_engine.state.labware import LabwareStore, LabwareState + +from .command_fixtures import ( + create_comment_command, +) + + +@pytest.fixture +def subject( + ot2_standard_deck_def: DeckDefinitionV5, +) -> LabwareStore: + """Get a LabwareStore test subject.""" + return LabwareStore( + deck_definition=ot2_standard_deck_def, + deck_fixed_labware=[], + ) + + +def test_initial_state( + ot2_standard_deck_def: DeckDefinitionV5, + subject: LabwareStore, +) -> None: + """It should create the labware store with preloaded fixed labware.""" + assert subject.state == LabwareState( + deck_definition=ot2_standard_deck_def, + labware_by_id={}, + labware_offsets_by_id={}, + definitions_by_uri={}, + ) + + +def test_handles_add_labware_offset( + subject: LabwareStore, +) -> None: + """It should add the labware offset to the state and add the ID.""" + request = LabwareOffsetCreate( + definitionUri="offset-definition-uri", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + vector=LabwareOffsetVector(x=1, y=2, z=3), + ) + + resolved_offset = LabwareOffset( + id="offset-id", + createdAt=datetime(year=2021, month=1, day=2), + definitionUri="offset-definition-uri", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + vector=LabwareOffsetVector(x=1, y=2, z=3), + ) + + subject.handle_action( + AddLabwareOffsetAction( + labware_offset_id="offset-id", + created_at=datetime(year=2021, month=1, day=2), + request=request, + ) + ) + + assert subject.state.labware_offsets_by_id == {"offset-id": resolved_offset} + + +@pytest.mark.parametrize( + "display_name, offset_id", [("display-name", "offset-id"), (None, None)] +) +def test_handles_load_labware( + subject: LabwareStore, + well_plate_def: LabwareDefinition, + display_name: Optional[str], + offset_id: Optional[str], +) -> None: + """It should add the labware data to the state.""" + offset_request = LabwareOffsetCreate( + definitionUri="offset-definition-uri", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + vector=LabwareOffsetVector(x=1, y=2, z=3), + ) + + command = create_comment_command() + + expected_definition_uri = uri_from_details( + load_name=well_plate_def.parameters.loadName, + namespace=well_plate_def.namespace, + version=well_plate_def.version, + ) + + expected_labware_data = LoadedLabware( + id="test-labware-id", + loadName=well_plate_def.parameters.loadName, + definitionUri=expected_definition_uri, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + offsetId=offset_id, + displayName=display_name, + ) + + subject.handle_action( + AddLabwareOffsetAction( + request=offset_request, + labware_offset_id="offset-id", + created_at=datetime(year=2021, month=1, day=2), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=command, + state_update=update_types.StateUpdate( + loaded_labware=update_types.LoadedLabwareUpdate( + labware_id="test-labware-id", + definition=well_plate_def, + offset_id=offset_id, + display_name=display_name, + new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + ), + ) + ) + + assert subject.state.labware_by_id["test-labware-id"] == expected_labware_data + + assert subject.state.definitions_by_uri[expected_definition_uri] == well_plate_def + + +def test_handles_reload_labware( + subject: LabwareStore, + well_plate_def: LabwareDefinition, +) -> None: + """It should override labware data in the state.""" + command = create_comment_command() + + subject.handle_action( + SucceedCommandAction( + command=command, + state_update=update_types.StateUpdate( + loaded_labware=update_types.LoadedLabwareUpdate( + labware_id="test-labware-id", + definition=well_plate_def, + offset_id=None, + display_name="display-name", + new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + ), + ) + ) + expected_definition_uri = uri_from_details( + load_name=well_plate_def.parameters.loadName, + namespace=well_plate_def.namespace, + version=well_plate_def.version, + ) + assert ( + subject.state.labware_by_id["test-labware-id"].definitionUri + == expected_definition_uri + ) + + offset_request = LabwareOffsetCreate( + definitionUri="offset-definition-uri", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + vector=LabwareOffsetVector(x=1, y=2, z=3), + ) + subject.handle_action( + AddLabwareOffsetAction( + request=offset_request, + labware_offset_id="offset-id", + created_at=datetime(year=2021, month=1, day=2), + ) + ) + comment_command_2 = create_comment_command( + command_id="comment-id-1", + ) + subject.handle_action( + SucceedCommandAction( + command=comment_command_2, + state_update=update_types.StateUpdate( + labware_location=update_types.LabwareLocationUpdate( + new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + offset_id="offset-id", + labware_id="test-labware-id", + ) + ), + ) + ) + + expected_labware_data = LoadedLabware( + id="test-labware-id", + loadName=well_plate_def.parameters.loadName, + definitionUri=expected_definition_uri, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + offsetId="offset-id", + displayName="display-name", + ) + assert subject.state.labware_by_id["test-labware-id"] == expected_labware_data + assert subject.state.definitions_by_uri[expected_definition_uri] == well_plate_def + + +def test_handles_add_labware_definition( + subject: LabwareStore, + well_plate_def: LabwareDefinition, +) -> None: + """It should add the labware definition to the state.""" + expected_uri = uri_from_details( + load_name=well_plate_def.parameters.loadName, + namespace=well_plate_def.namespace, + version=well_plate_def.version, + ) + + subject.handle_action(AddLabwareDefinitionAction(definition=well_plate_def)) + + assert subject.state.definitions_by_uri[expected_uri] == well_plate_def + + +def test_handles_move_labware( + subject: LabwareStore, + well_plate_def: LabwareDefinition, +) -> None: + """It should update labware state with new location & offset.""" + comment_command = create_comment_command() + offset_request = LabwareOffsetCreate( + definitionUri="offset-definition-uri", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + vector=LabwareOffsetVector(x=1, y=2, z=3), + ) + subject.handle_action( + AddLabwareOffsetAction( + request=offset_request, + labware_offset_id="old-offset-id", + created_at=datetime(year=2021, month=1, day=2), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=comment_command, + state_update=update_types.StateUpdate( + loaded_labware=update_types.LoadedLabwareUpdate( + labware_id="my-labware-id", + definition=well_plate_def, + offset_id=None, + display_name="display-name", + new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + ), + ) + ) + + comment_2 = create_comment_command( + command_id="my-command-id", + ) + subject.handle_action( + SucceedCommandAction( + command=comment_2, + state_update=update_types.StateUpdate( + labware_location=update_types.LabwareLocationUpdate( + labware_id="my-labware-id", + offset_id="my-new-offset", + new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + ), + ) + ) + + assert subject.state.labware_by_id["my-labware-id"].location == DeckSlotLocation( + slotName=DeckSlotName.SLOT_1 + ) + assert subject.state.labware_by_id["my-labware-id"].offsetId == "my-new-offset" + + +def test_handles_move_labware_off_deck( + subject: LabwareStore, + well_plate_def: LabwareDefinition, +) -> None: + """It should update labware state with new location & offset.""" + comment_command = create_comment_command() + offset_request = LabwareOffsetCreate( + definitionUri="offset-definition-uri", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + vector=LabwareOffsetVector(x=1, y=2, z=3), + ) + subject.handle_action( + AddLabwareOffsetAction( + request=offset_request, + labware_offset_id="old-offset-id", + created_at=datetime(year=2021, month=1, day=2), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=comment_command, + state_update=update_types.StateUpdate( + loaded_labware=update_types.LoadedLabwareUpdate( + labware_id="my-labware-id", + definition=well_plate_def, + offset_id=None, + display_name="display-name", + new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + ), + ) + ) + + comment_2 = create_comment_command( + command_id="my-command-id", + ) + subject.handle_action( + SucceedCommandAction( + command=comment_2, + state_update=update_types.StateUpdate( + labware_location=update_types.LabwareLocationUpdate( + labware_id="my-labware-id", + new_location=OFF_DECK_LOCATION, + offset_id=None, + ) + ), + ) + ) + assert subject.state.labware_by_id["my-labware-id"].location == OFF_DECK_LOCATION + assert subject.state.labware_by_id["my-labware-id"].offsetId is None diff --git a/api/tests/opentrons/protocol_engine/state/test_labware_view.py b/api/tests/opentrons/protocol_engine/state/test_labware_view.py deleted file mode 100644 index 43c69594422..00000000000 --- a/api/tests/opentrons/protocol_engine/state/test_labware_view.py +++ /dev/null @@ -1,1553 +0,0 @@ -"""Labware state store tests.""" -import pytest -from datetime import datetime -from typing import Dict, Optional, cast, ContextManager, Any, Union, NamedTuple, List -from contextlib import nullcontext as does_not_raise - -from opentrons_shared_data.deck import load as load_deck -from opentrons_shared_data.deck.types import DeckDefinitionV5 -from opentrons_shared_data.pipette.types import LabwareUri -from opentrons_shared_data.labware import load_definition -from opentrons_shared_data.labware.labware_definition import ( - Parameters, - LabwareRole, - OverlapOffset as SharedDataOverlapOffset, - GripperOffsets, - OffsetVector, -) - -from opentrons.protocols.api_support.deck_type import ( - STANDARD_OT2_DECK, - STANDARD_OT3_DECK, -) -from opentrons.protocols.models import LabwareDefinition -from opentrons.types import DeckSlotName, MountType - -from opentrons.protocol_engine import errors -from opentrons.protocol_engine.types import ( - DeckSlotLocation, - Dimensions, - LabwareOffset, - LabwareOffsetVector, - LabwareOffsetLocation, - LoadedLabware, - ModuleModel, - ModuleLocation, - OnLabwareLocation, - LabwareLocation, - AddressableAreaLocation, - OFF_DECK_LOCATION, - OverlapOffset, - LabwareMovementOffsetData, -) -from opentrons.protocol_engine.state.move_types import EdgePathType -from opentrons.protocol_engine.state.labware import ( - LabwareState, - LabwareView, - LabwareLoadParams, -) - -plate = LoadedLabware( - id="plate-id", - loadName="plate-load-name", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - definitionUri="some-plate-uri", - offsetId=None, - displayName="Fancy Plate Name", -) - -flex_tiprack = LoadedLabware( - id="flex-tiprack-id", - loadName="flex-tiprack-load-name", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - definitionUri="some-flex-tiprack-uri", - offsetId=None, - displayName="Flex Tiprack Name", -) - -reservoir = LoadedLabware( - id="reservoir-id", - loadName="reservoir-load-name", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_2), - definitionUri="some-reservoir-uri", - offsetId=None, -) - -trash = LoadedLabware( - id="trash-id", - loadName="trash-load-name", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_2), - definitionUri="some-trash-uri", - offsetId=None, -) - -tube_rack = LoadedLabware( - id="tube-rack-id", - loadName="tube-rack-load-name", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - definitionUri="some-tube-rack-uri", - offsetId=None, -) - -tip_rack = LoadedLabware( - id="tip-rack-id", - loadName="tip-rack-load-name", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - definitionUri="some-tip-rack-uri", - offsetId=None, -) - -adapter_plate = LoadedLabware( - id="adapter-plate-id", - loadName="adapter-load-name", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - definitionUri="some-adapter-uri", - offsetId=None, -) - - -def get_labware_view( - labware_by_id: Optional[Dict[str, LoadedLabware]] = None, - labware_offsets_by_id: Optional[Dict[str, LabwareOffset]] = None, - definitions_by_uri: Optional[Dict[str, LabwareDefinition]] = None, - deck_definition: Optional[DeckDefinitionV5] = None, -) -> LabwareView: - """Get a labware view test subject.""" - state = LabwareState( - labware_by_id=labware_by_id or {}, - labware_offsets_by_id=labware_offsets_by_id or {}, - definitions_by_uri=definitions_by_uri or {}, - deck_definition=deck_definition or cast(DeckDefinitionV5, {"fake": True}), - ) - - return LabwareView(state=state) - - -def test_get_labware_data_bad_id() -> None: - """get_labware_data_by_id should raise if labware ID doesn't exist.""" - subject = get_labware_view() - - with pytest.raises(errors.LabwareNotLoadedError): - subject.get("asdfghjkl") - - -def test_get_labware_data_by_id() -> None: - """It should retrieve labware data from the state.""" - subject = get_labware_view(labware_by_id={"plate-id": plate}) - - assert subject.get("plate-id") == plate - - -def test_get_id_by_module() -> None: - """Should return the labware id associated to the module.""" - subject = get_labware_view( - labware_by_id={ - "labware-id": LoadedLabware( - id="labware-id", - loadName="test", - definitionUri="test-uri", - location=ModuleLocation(moduleId="module-id"), - ) - } - ) - assert subject.get_id_by_module(module_id="module-id") == "labware-id" - - -def test_get_id_by_module_raises_error() -> None: - """Should raise error that labware not found.""" - subject = get_labware_view( - labware_by_id={ - "labware-id": LoadedLabware( - id="labware-id", - loadName="test", - definitionUri="test-uri", - location=ModuleLocation(moduleId="module-id"), - ) - } - ) - with pytest.raises(errors.exceptions.LabwareNotLoadedOnModuleError): - subject.get_id_by_module(module_id="no-module-id") - - -def test_get_id_by_labware() -> None: - """Should return the labware id associated to the labware.""" - subject = get_labware_view( - labware_by_id={ - "labware-id": LoadedLabware( - id="labware-id", - loadName="test", - definitionUri="test-uri", - location=OnLabwareLocation(labwareId="other-labware-id"), - ) - } - ) - assert subject.get_id_by_labware(labware_id="other-labware-id") == "labware-id" - - -def test_get_id_by_labware_raises_error() -> None: - """Should raise error that labware not found.""" - subject = get_labware_view( - labware_by_id={ - "labware-id": LoadedLabware( - id="labware-id", - loadName="test", - definitionUri="test-uri", - location=OnLabwareLocation(labwareId="other-labware-id"), - ) - } - ) - with pytest.raises(errors.exceptions.LabwareNotLoadedOnLabwareError): - subject.get_id_by_labware(labware_id="no-labware-id") - - -def test_raise_if_labware_has_labware_on_top() -> None: - """It should raise if labware has another labware on top.""" - subject = get_labware_view( - labware_by_id={ - "labware-id-1": LoadedLabware( - id="labware-id-1", - loadName="test", - definitionUri="test-uri", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - ), - "labware-id-2": LoadedLabware( - id="labware-id-2", - loadName="test", - definitionUri="test-uri", - location=ModuleLocation(moduleId="module-id"), - ), - "labware-id-3": LoadedLabware( - id="labware-id-3", - loadName="test", - definitionUri="test-uri", - location=OnLabwareLocation(labwareId="labware-id-1"), - ), - } - ) - subject.raise_if_labware_has_labware_on_top("labware-id-2") - subject.raise_if_labware_has_labware_on_top("labware-id-3") - with pytest.raises(errors.exceptions.LabwareIsInStackError): - subject.raise_if_labware_has_labware_on_top("labware-id-1") - - -def test_get_labware_definition(well_plate_def: LabwareDefinition) -> None: - """It should get a labware's definition from the state.""" - subject = get_labware_view( - labware_by_id={"plate-id": plate}, - definitions_by_uri={"some-plate-uri": well_plate_def}, - ) - - assert subject.get_definition("plate-id") == well_plate_def - - -def test_get_labware_definition_bad_id() -> None: - """get_labware_definition should raise if labware definition doesn't exist.""" - subject = get_labware_view() - - with pytest.raises(errors.LabwareDefinitionDoesNotExistError): - subject.get_definition_by_uri(cast(LabwareUri, "not-a-uri")) - - -@pytest.mark.parametrize( - argnames=["namespace", "version"], - argvalues=[("world", 123), (None, 123), ("world", None), (None, None)], -) -def test_find_custom_labware_params( - namespace: Optional[str], version: Optional[int] -) -> None: - """It should find the missing (if any) load labware parameters.""" - labware_def = LabwareDefinition.construct( # type: ignore[call-arg] - parameters=Parameters.construct(loadName="hello"), # type: ignore[call-arg] - namespace="world", - version=123, - ) - standard_def = LabwareDefinition.construct( # type: ignore[call-arg] - parameters=Parameters.construct(loadName="goodbye"), # type: ignore[call-arg] - namespace="opentrons", - version=456, - ) - - subject = get_labware_view( - definitions_by_uri={ - "some-labware-uri": labware_def, - "some-standard-uri": standard_def, - }, - ) - - result = subject.find_custom_labware_load_params() - - assert result == [ - LabwareLoadParams(load_name="hello", namespace="world", version=123) - ] - - -def test_get_all_labware( - well_plate_def: LabwareDefinition, - reservoir_def: LabwareDefinition, -) -> None: - """It should return all labware.""" - subject = get_labware_view( - labware_by_id={ - "plate-id": plate, - "reservoir-id": reservoir, - } - ) - - all_labware = subject.get_all() - - assert all_labware == [plate, reservoir] - - -def test_get_labware_location() -> None: - """It should return labware location.""" - subject = get_labware_view(labware_by_id={"plate-id": plate}) - - result = subject.get_location("plate-id") - - assert result == DeckSlotLocation(slotName=DeckSlotName.SLOT_1) - - -@pytest.mark.parametrize( - argnames="location", - argvalues=[ - DeckSlotLocation(slotName=DeckSlotName.SLOT_D1), - ModuleLocation(moduleId="module-id"), - OFF_DECK_LOCATION, - ], -) -def test_get_parent_location(location: LabwareLocation) -> None: - """It should return the non-OnLabware location of a labware.""" - subject = get_labware_view( - labware_by_id={ - "labware-id": LoadedLabware( - id="plate-id", - loadName="load-name", - location=location, - definitionUri="some-uri", - ) - } - ) - - result = subject.get_parent_location(labware_id="labware-id") - - assert result == location - - -@pytest.mark.parametrize( - argnames="location", - argvalues=[ - DeckSlotLocation(slotName=DeckSlotName.SLOT_D1), - ModuleLocation(moduleId="module-id"), - ], -) -def test_get_parent_location_on_labware(location: LabwareLocation) -> None: - """It should return the non-OnLabware location of a labware.""" - subject = get_labware_view( - labware_by_id={ - "top-id": LoadedLabware( - id="top-id", - loadName="load-name", - location=OnLabwareLocation(labwareId="middle-id"), - definitionUri="some-uri", - ), - "middle-id": LoadedLabware( - id="middle-id", - loadName="load-name", - location=OnLabwareLocation(labwareId="bottom-id"), - definitionUri="some-uri", - ), - "bottom-id": LoadedLabware( - id="bottom-id", - loadName="load-name", - location=location, - definitionUri="some-uri", - ), - } - ) - - result = subject.get_parent_location(labware_id="top-id") - - assert result == location - - -def test_get_has_quirk( - well_plate_def: LabwareDefinition, - reservoir_def: LabwareDefinition, -) -> None: - """It should return whether a labware by ID has a given quirk.""" - subject = get_labware_view( - labware_by_id={ - "plate-id": plate, - "reservoir-id": reservoir, - }, - definitions_by_uri={ - "some-plate-uri": well_plate_def, - "some-reservoir-uri": reservoir_def, - }, - ) - - well_plate_has_center_quirk = subject.get_has_quirk( - labware_id="plate-id", - quirk="centerMultichannelOnWells", - ) - - reservoir_has_center_quirk = subject.get_has_quirk( - labware_id="reservoir-id", - quirk="centerMultichannelOnWells", - ) - - assert well_plate_has_center_quirk is False - assert reservoir_has_center_quirk is True - - -def test_quirks( - well_plate_def: LabwareDefinition, - reservoir_def: LabwareDefinition, -) -> None: - """It should return a labware's quirks.""" - subject = get_labware_view( - labware_by_id={ - "plate-id": plate, - "reservoir-id": reservoir, - }, - definitions_by_uri={ - "some-plate-uri": well_plate_def, - "some-reservoir-uri": reservoir_def, - }, - ) - - well_plate_quirks = subject.get_quirks("plate-id") - reservoir_quirks = subject.get_quirks("reservoir-id") - - assert well_plate_quirks == [] - assert reservoir_quirks == ["centerMultichannelOnWells", "touchTipDisabled"] - - -def test_get_well_definition_bad_name(well_plate_def: LabwareDefinition) -> None: - """get_well_definition should raise if well name doesn't exist.""" - subject = get_labware_view( - labware_by_id={"plate-id": plate}, - definitions_by_uri={"some-plate-uri": well_plate_def}, - ) - - with pytest.raises(errors.WellDoesNotExistError): - subject.get_well_definition(labware_id="plate-id", well_name="foobar") - - -def test_get_well_definition(well_plate_def: LabwareDefinition) -> None: - """It should return a well definition by well name.""" - subject = get_labware_view( - labware_by_id={"plate-id": plate}, - definitions_by_uri={"some-plate-uri": well_plate_def}, - ) - - expected_well_def = well_plate_def.wells["B2"] - result = subject.get_well_definition(labware_id="plate-id", well_name="B2") - - assert result == expected_well_def - - -def test_get_well_definition_get_first(well_plate_def: LabwareDefinition) -> None: - """It should return the first well definition if no given well name.""" - subject = get_labware_view( - labware_by_id={"plate-id": plate}, - definitions_by_uri={"some-plate-uri": well_plate_def}, - ) - - expected_well_def = well_plate_def.wells["A1"] - result = subject.get_well_definition(labware_id="plate-id", well_name=None) - - assert result == expected_well_def - - -def test_get_well_size_circular(well_plate_def: LabwareDefinition) -> None: - """It should return the well dimensions of a circular well.""" - subject = get_labware_view( - labware_by_id={"plate-id": plate}, - definitions_by_uri={"some-plate-uri": well_plate_def}, - ) - expected_well_def = well_plate_def.wells["A2"] - expected_size = ( - expected_well_def.diameter, - expected_well_def.diameter, - expected_well_def.depth, - ) - - result = subject.get_well_size(labware_id="plate-id", well_name="A2") - - assert result == expected_size - - -def test_get_well_size_rectangular(reservoir_def: LabwareDefinition) -> None: - """It should return the well dimensions of a rectangular well.""" - subject = get_labware_view( - labware_by_id={"reservoir-id": reservoir}, - definitions_by_uri={"some-reservoir-uri": reservoir_def}, - ) - expected_well_def = reservoir_def.wells["A2"] - expected_size = ( - expected_well_def.xDimension, - expected_well_def.yDimension, - expected_well_def.depth, - ) - - result = subject.get_well_size(labware_id="reservoir-id", well_name="A2") - - assert result == expected_size - - -def test_labware_has_well(falcon_tuberack_def: LabwareDefinition) -> None: - """It should return a list of wells from definition.""" - subject = get_labware_view( - labware_by_id={"tube-rack-id": tube_rack}, - definitions_by_uri={"some-tube-rack-uri": falcon_tuberack_def}, - ) - - result = subject.validate_liquid_allowed_in_labware( - labware_id="tube-rack-id", wells={"A1": 30, "B1": 100} - ) - assert result == ["A1", "B1"] - - with pytest.raises(errors.WellDoesNotExistError): - subject.validate_liquid_allowed_in_labware( - labware_id="tube-rack-id", wells={"AA": 30} - ) - - with pytest.raises(errors.LabwareNotLoadedError): - subject.validate_liquid_allowed_in_labware(labware_id="no-id", wells={"A1": 30}) - - -def test_validate_liquid_allowed_raises_incompatible_labware() -> None: - """It should raise when validating labware that is a tiprack or an adapter.""" - subject = get_labware_view( - labware_by_id={ - "tiprack-id": LoadedLabware( - id="tiprack-id", - loadName="test1", - definitionUri="some-tiprack-uri", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - ), - "adapter-id": LoadedLabware( - id="adapter-id", - loadName="test2", - definitionUri="some-adapter-uri", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_2), - ), - }, - definitions_by_uri={ - "some-tiprack-uri": LabwareDefinition.construct( # type: ignore[call-arg] - parameters=Parameters.construct(isTiprack=True), # type: ignore[call-arg] - wells={}, - ), - "some-adapter-uri": LabwareDefinition.construct( # type: ignore[call-arg] - parameters=Parameters.construct(isTiprack=False), # type: ignore[call-arg] - allowedRoles=[LabwareRole.adapter], - wells={}, - ), - }, - ) - - with pytest.raises(errors.LabwareIsTipRackError): - subject.validate_liquid_allowed_in_labware(labware_id="tiprack-id", wells={}) - - with pytest.raises(errors.LabwareIsAdapterError): - subject.validate_liquid_allowed_in_labware(labware_id="adapter-id", wells={}) - - -def test_get_tip_length_raises_with_non_tip_rack( - well_plate_def: LabwareDefinition, -) -> None: - """It should raise if you try to get the tip length of a regular labware.""" - subject = get_labware_view( - labware_by_id={"plate-id": plate}, - definitions_by_uri={"some-plate-uri": well_plate_def}, - ) - - with pytest.raises(errors.LabwareIsNotTipRackError): - subject.get_tip_length("plate-id") - - -def test_get_tip_length_gets_length_from_definition( - tip_rack_def: LabwareDefinition, -) -> None: - """It should return the tip length from the definition.""" - subject = get_labware_view( - labware_by_id={"tip-rack-id": tip_rack}, - definitions_by_uri={"some-tip-rack-uri": tip_rack_def}, - ) - - length = subject.get_tip_length("tip-rack-id", 12.3) - assert length == tip_rack_def.parameters.tipLength - 12.3 # type: ignore[operator] - - -def test_get_tip_drop_z_offset() -> None: - """It should get a tip drop z offset by scaling the tip length.""" - tip_rack_def = LabwareDefinition.construct( # type: ignore[call-arg] - parameters=Parameters.construct( # type: ignore[call-arg] - tipLength=100, - ) - ) - - subject = get_labware_view( - labware_by_id={"tip-rack-id": tip_rack}, - definitions_by_uri={"some-tip-rack-uri": tip_rack_def}, - ) - - result = subject.get_tip_drop_z_offset( - labware_id="tip-rack-id", length_scale=0.5, additional_offset=-0.123 - ) - - assert result == -50.123 - - -def test_get_labware_uri_from_definition(tip_rack_def: LabwareDefinition) -> None: - """It should return the labware's definition URI.""" - tip_rack = LoadedLabware( - id="tip-rack-id", - loadName="tip-rack-load-name", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - definitionUri="some-tip-rack-uri", - offsetId=None, - ) - - subject = get_labware_view( - labware_by_id={"tip-rack-id": tip_rack}, - definitions_by_uri={"some-tip-rack-uri": tip_rack_def}, - ) - - result = subject.get_definition_uri(labware_id="tip-rack-id") - assert result == "some-tip-rack-uri" - - -def test_get_labware_uri_from_full_definition(tip_rack_def: LabwareDefinition) -> None: - """It should be able to construct a URI given a full definition.""" - subject = get_labware_view() - result = subject.get_uri_from_definition(tip_rack_def) - assert result == "opentrons/opentrons_96_tiprack_300ul/1" - - -def test_is_tiprack( - tip_rack_def: LabwareDefinition, reservoir_def: LabwareDefinition -) -> None: - """It should determine if labware is a tip rack.""" - subject = get_labware_view( - labware_by_id={ - "tip-rack-id": tip_rack, - "reservoir-id": reservoir, - }, - definitions_by_uri={ - "some-tip-rack-uri": tip_rack_def, - "some-reservoir-uri": reservoir_def, - }, - ) - - assert subject.is_tiprack(labware_id="tip-rack-id") is True - assert subject.is_tiprack(labware_id="reservoir-id") is False - - -def test_get_load_name(reservoir_def: LabwareDefinition) -> None: - """It should return the load name.""" - subject = get_labware_view( - labware_by_id={"reservoir-id": reservoir}, - definitions_by_uri={"some-reservoir-uri": reservoir_def}, - ) - - result = subject.get_load_name("reservoir-id") - - assert result == reservoir_def.parameters.loadName - - -def test_get_dimensions(well_plate_def: LabwareDefinition) -> None: - """It should compute the dimensions of a labware.""" - subject = get_labware_view( - labware_by_id={"plate-id": plate}, - definitions_by_uri={"some-plate-uri": well_plate_def}, - ) - - result = subject.get_dimensions(labware_id="plate-id") - - assert result == Dimensions( - x=well_plate_def.dimensions.xDimension, - y=well_plate_def.dimensions.yDimension, - z=well_plate_def.dimensions.zDimension, - ) - - -def test_get_labware_overlap_offsets() -> None: - """It should get the labware overlap offsets.""" - subject = get_labware_view( - labware_by_id={"plate-id": plate}, - definitions_by_uri={ - "some-plate-uri": LabwareDefinition.construct( # type: ignore[call-arg] - stackingOffsetWithLabware={ - "bottom-labware-name": SharedDataOverlapOffset(x=1, y=2, z=3) - } - ) - }, - ) - - result = subject.get_labware_overlap_offsets( - labware_id="plate-id", below_labware_name="bottom-labware-name" - ) - - assert result == OverlapOffset(x=1, y=2, z=3) - - -class ModuleOverlapSpec(NamedTuple): - """Spec data to test LabwareView.get_module_overlap_offsets.""" - - spec_deck_definition: DeckDefinitionV5 - module_model: ModuleModel - stacking_offset_with_module: Dict[str, SharedDataOverlapOffset] - expected_offset: OverlapOffset - - -module_overlap_specs: List[ModuleOverlapSpec] = [ - ModuleOverlapSpec( - # Labware on temp module on OT2, with stacking overlap for temp module - spec_deck_definition=load_deck(STANDARD_OT2_DECK, 5), - module_model=ModuleModel.TEMPERATURE_MODULE_V2, - stacking_offset_with_module={ - str(ModuleModel.TEMPERATURE_MODULE_V2.value): SharedDataOverlapOffset( - x=1, y=2, z=3 - ), - }, - expected_offset=OverlapOffset(x=1, y=2, z=3), - ), - ModuleOverlapSpec( - # Labware on TC Gen1 on OT2, with stacking overlap for TC Gen1 - spec_deck_definition=load_deck(STANDARD_OT2_DECK, 5), - module_model=ModuleModel.THERMOCYCLER_MODULE_V1, - stacking_offset_with_module={ - str(ModuleModel.THERMOCYCLER_MODULE_V1.value): SharedDataOverlapOffset( - x=11, y=22, z=33 - ), - }, - expected_offset=OverlapOffset(x=11, y=22, z=33), - ), - ModuleOverlapSpec( - # Labware on TC Gen2 on OT2, with no stacking overlap - spec_deck_definition=load_deck(STANDARD_OT2_DECK, 5), - module_model=ModuleModel.THERMOCYCLER_MODULE_V2, - stacking_offset_with_module={}, - expected_offset=OverlapOffset(x=0, y=0, z=10.7), - ), - ModuleOverlapSpec( - # Labware on TC Gen2 on Flex, with no stacking overlap - spec_deck_definition=load_deck(STANDARD_OT3_DECK, 5), - module_model=ModuleModel.THERMOCYCLER_MODULE_V2, - stacking_offset_with_module={}, - expected_offset=OverlapOffset(x=0, y=0, z=0), - ), - ModuleOverlapSpec( - # Labware on TC Gen2 on Flex, with stacking overlap for TC Gen2 - spec_deck_definition=load_deck(STANDARD_OT3_DECK, 5), - module_model=ModuleModel.THERMOCYCLER_MODULE_V2, - stacking_offset_with_module={ - str(ModuleModel.THERMOCYCLER_MODULE_V2.value): SharedDataOverlapOffset( - x=111, y=222, z=333 - ), - }, - expected_offset=OverlapOffset(x=111, y=222, z=333), - ), -] - - -@pytest.mark.parametrize( - argnames=ModuleOverlapSpec._fields, - argvalues=module_overlap_specs, -) -def test_get_module_overlap_offsets( - spec_deck_definition: DeckDefinitionV5, - module_model: ModuleModel, - stacking_offset_with_module: Dict[str, SharedDataOverlapOffset], - expected_offset: OverlapOffset, -) -> None: - """It should get the labware overlap offsets.""" - subject = get_labware_view( - deck_definition=spec_deck_definition, - labware_by_id={"plate-id": plate}, - definitions_by_uri={ - "some-plate-uri": LabwareDefinition.construct( # type: ignore[call-arg] - stackingOffsetWithModule=stacking_offset_with_module - ) - }, - ) - result = subject.get_module_overlap_offsets( - labware_id="plate-id", module_model=module_model - ) - - assert result == expected_offset - - -def test_get_default_magnet_height( - magdeck_well_plate_def: LabwareDefinition, -) -> None: - """Should get get the default value for magnetic height.""" - well_plate = LoadedLabware( - id="well-plate-id", - loadName="load-name", - location=ModuleLocation(moduleId="module-id"), - definitionUri="well-plate-uri", - offsetId=None, - ) - - subject = get_labware_view( - labware_by_id={"well-plate-id": well_plate}, - definitions_by_uri={"well-plate-uri": magdeck_well_plate_def}, - ) - - assert subject.get_default_magnet_height(module_id="module-id", offset=2) == 12.0 - - -def test_get_deck_definition(ot2_standard_deck_def: DeckDefinitionV5) -> None: - """It should get the deck definition from the state.""" - subject = get_labware_view(deck_definition=ot2_standard_deck_def) - - assert subject.get_deck_definition() == ot2_standard_deck_def - - -def test_get_labware_offset_vector() -> None: - """It should get a labware's offset vector.""" - labware_without_offset = LoadedLabware( - id="without-offset-labware-id", - loadName="labware-load-name", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - definitionUri="some-labware-uri", - offsetId=None, - ) - - labware_with_offset = LoadedLabware( - id="with-offset-labware-id", - loadName="labware-load-name", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - definitionUri="some-labware-uri", - offsetId="offset-id", - ) - - offset_vector = LabwareOffsetVector(x=1, y=2, z=3) - offset = LabwareOffset( - id="offset-id", - createdAt=datetime(year=2021, month=1, day=2), - definitionUri="some-labware-uri", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), - vector=offset_vector, - ) - - subject = get_labware_view( - labware_by_id={ - labware_without_offset.id: labware_without_offset, - labware_with_offset.id: labware_with_offset, - }, - labware_offsets_by_id={"offset-id": offset}, - ) - - assert subject.get_labware_offset_vector(labware_with_offset.id) == offset.vector - - assert subject.get_labware_offset_vector( - labware_without_offset.id - ) == LabwareOffsetVector(x=0, y=0, z=0) - - with pytest.raises(errors.LabwareNotLoadedError): - subject.get_labware_offset_vector("wrong-labware-id") - - -def test_get_labware_offset() -> None: - """It should return the requested labware offset, if it exists.""" - offset_a = LabwareOffset( - id="id-a", - createdAt=datetime(year=2021, month=1, day=1), - definitionUri="uri-a", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), - vector=LabwareOffsetVector(x=1, y=1, z=1), - ) - - offset_b = LabwareOffset( - id="id-b", - createdAt=datetime(year=2022, month=2, day=2), - definitionUri="uri-b", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_2), - vector=LabwareOffsetVector(x=2, y=2, z=2), - ) - - subject = get_labware_view( - labware_offsets_by_id={"id-a": offset_a, "id-b": offset_b} - ) - - assert subject.get_labware_offset("id-a") == offset_a - assert subject.get_labware_offset("id-b") == offset_b - with pytest.raises(errors.LabwareOffsetDoesNotExistError): - subject.get_labware_offset("wrong-labware-offset-id") - - -def test_get_labware_offsets() -> None: - """It should return a list of all labware offsets, in order.""" - offset_a = LabwareOffset( - id="id-a", - createdAt=datetime(year=2021, month=1, day=1), - definitionUri="uri-a", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), - vector=LabwareOffsetVector(x=1, y=1, z=1), - ) - - offset_b = LabwareOffset( - id="id-b", - createdAt=datetime(year=2022, month=2, day=2), - definitionUri="uri-b", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_2), - vector=LabwareOffsetVector(x=2, y=2, z=2), - ) - - empty_subject = get_labware_view() - assert empty_subject.get_labware_offsets() == [] - - filled_subject_a_before_b = get_labware_view( - labware_offsets_by_id={"id-a": offset_a, "id-b": offset_b} - ) - assert filled_subject_a_before_b.get_labware_offsets() == [offset_a, offset_b] - - filled_subject_b_before_a = get_labware_view( - labware_offsets_by_id={"id-b": offset_b, "id-a": offset_a} - ) - assert filled_subject_b_before_a.get_labware_offsets() == [offset_b, offset_a] - - -def test_find_applicable_labware_offset() -> None: - """It should return the most recent offset with matching URI and location.""" - offset_1 = LabwareOffset( - id="id-1", - createdAt=datetime(year=2021, month=1, day=1), - definitionUri="definition-uri", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), - vector=LabwareOffsetVector(x=1, y=1, z=1), - ) - - # Same definitionUri and location; different id, createdAt, and offset. - offset_2 = LabwareOffset( - id="id-2", - createdAt=datetime(year=2022, month=2, day=2), - definitionUri="definition-uri", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), - vector=LabwareOffsetVector(x=2, y=2, z=2), - ) - - offset_3 = LabwareOffset( - id="id-3", - createdAt=datetime(year=2023, month=3, day=3), - definitionUri="on-module-definition-uri", - location=LabwareOffsetLocation( - slotName=DeckSlotName.SLOT_1, - moduleModel=ModuleModel.TEMPERATURE_MODULE_V1, - ), - vector=LabwareOffsetVector(x=3, y=3, z=3), - ) - - subject = get_labware_view( - # Simulate offset_2 having been added after offset_1. - labware_offsets_by_id={"id-1": offset_1, "id-2": offset_2, "id-3": offset_3} - ) - - # Matching both definitionURI and location. Should return 2nd (most recent) offset. - assert ( - subject.find_applicable_labware_offset( - definition_uri="definition-uri", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), - ) - == offset_2 - ) - - assert ( - subject.find_applicable_labware_offset( - definition_uri="on-module-definition-uri", - location=LabwareOffsetLocation( - slotName=DeckSlotName.SLOT_1, - moduleModel=ModuleModel.TEMPERATURE_MODULE_V1, - ), - ) - == offset_3 - ) - - # Doesn't match anything, since definitionUri is different. - assert ( - subject.find_applicable_labware_offset( - definition_uri="different-definition-uri", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), - ) - is None - ) - - # Doesn't match anything, since location is different. - assert ( - subject.find_applicable_labware_offset( - definition_uri="different-definition-uri", - location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_2), - ) - is None - ) - - -def test_get_user_specified_display_name() -> None: - """It should get a labware's user-specified display name.""" - subject = get_labware_view( - labware_by_id={ - "plate_with_display_name": plate, - "reservoir_without_display_name": reservoir, - }, - ) - - assert ( - subject.get_user_specified_display_name("plate_with_display_name") - == "Fancy Plate Name" - ) - assert ( - subject.get_user_specified_display_name("reservoir_without_display_name") - is None - ) - - -def test_get_display_name( - well_plate_def: LabwareDefinition, - reservoir_def: LabwareDefinition, -) -> None: - """It should get the labware's display name.""" - subject = get_labware_view( - labware_by_id={ - "plate_with_custom_display_name": plate, - "reservoir_with_default_display_name": reservoir, - }, - definitions_by_uri={ - "some-plate-uri": well_plate_def, - "some-reservoir-uri": reservoir_def, - }, - ) - assert ( - subject.get_display_name("plate_with_custom_display_name") == "Fancy Plate Name" - ) - assert ( - subject.get_display_name("reservoir_with_default_display_name") - == "NEST 12 Well Reservoir 15 mL" - ) - - -def test_get_fixed_trash_id() -> None: - """It should return the ID of the labware loaded into the fixed trash slot.""" - # OT-2 fixed trash slot: - subject = get_labware_view( - labware_by_id={ - "abc123": LoadedLabware( - id="abc123", - loadName="trash-load-name", - location=DeckSlotLocation(slotName=DeckSlotName.FIXED_TRASH), - definitionUri="trash-definition-uri", - offsetId=None, - displayName=None, - ) - }, - ) - assert subject.get_fixed_trash_id() == "abc123" - - # OT-3 fixed trash slot: - subject = get_labware_view( - labware_by_id={ - "abc123": LoadedLabware( - id="abc123", - loadName="trash-load-name", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A3), - definitionUri="trash-definition-uri", - offsetId=None, - displayName=None, - ) - }, - ) - assert subject.get_fixed_trash_id() == "abc123" - - # Nothing in the fixed trash slot: - subject = get_labware_view( - labware_by_id={ - "abc123": LoadedLabware( - id="abc123", - loadName="trash-load-name", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - definitionUri="trash-definition-uri", - offsetId=None, - displayName=None, - ) - }, - ) - assert subject.get_fixed_trash_id() is None - - -@pytest.mark.parametrize( - argnames=["location", "expected_raise"], - argvalues=[ - ( - DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - pytest.raises(errors.LocationIsOccupiedError), - ), - ( - ModuleLocation(moduleId="module-id"), - pytest.raises(errors.LocationIsOccupiedError), - ), - (DeckSlotLocation(slotName=DeckSlotName.SLOT_2), does_not_raise()), - (ModuleLocation(moduleId="non-matching-id"), does_not_raise()), - ], -) -def test_raise_if_labware_in_location( - location: Union[DeckSlotLocation, ModuleLocation], - expected_raise: ContextManager[Any], -) -> None: - """It should raise if there is labware in specified location.""" - subject = get_labware_view( - labware_by_id={ - "abc123": LoadedLabware( - id="abc123", - loadName="labware-1", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - definitionUri="labware-definition-uri", - offsetId=None, - displayName=None, - ), - "xyz456": LoadedLabware( - id="xyz456", - loadName="labware-2", - location=ModuleLocation(moduleId="module-id"), - definitionUri="labware-definition-uri", - offsetId=None, - displayName=None, - ), - } - ) - with expected_raise: - subject.raise_if_labware_in_location(location=location) - - -def test_get_by_slot() -> None: - """It should get the labware in a given slot.""" - labware_1 = LoadedLabware.construct( # type: ignore[call-arg] - id="1", location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1) - ) - labware_2 = LoadedLabware.construct( # type: ignore[call-arg] - id="2", location=DeckSlotLocation(slotName=DeckSlotName.SLOT_2) - ) - labware_3 = LoadedLabware.construct( # type: ignore[call-arg] - id="3", location=ModuleLocation(moduleId="cool-module") - ) - - subject = get_labware_view( - labware_by_id={"1": labware_1, "2": labware_2, "3": labware_3} - ) - - assert subject.get_by_slot(DeckSlotName.SLOT_1) == labware_1 - assert subject.get_by_slot(DeckSlotName.SLOT_2) == labware_2 - assert subject.get_by_slot(DeckSlotName.SLOT_3) is None - - -@pytest.mark.parametrize( - ["well_name", "mount", "labware_slot", "next_to_module", "expected_result"], - [ - ("abc", MountType.RIGHT, DeckSlotName.SLOT_3, False, EdgePathType.LEFT), - ("abc", MountType.RIGHT, DeckSlotName.SLOT_D3, False, EdgePathType.LEFT), - ("abc", MountType.RIGHT, DeckSlotName.SLOT_1, True, EdgePathType.LEFT), - ("abc", MountType.RIGHT, DeckSlotName.SLOT_D1, True, EdgePathType.LEFT), - ("pqr", MountType.LEFT, DeckSlotName.SLOT_3, True, EdgePathType.RIGHT), - ("pqr", MountType.LEFT, DeckSlotName.SLOT_D3, True, EdgePathType.RIGHT), - ("pqr", MountType.LEFT, DeckSlotName.SLOT_3, False, EdgePathType.DEFAULT), - ("pqr", MountType.LEFT, DeckSlotName.SLOT_D3, False, EdgePathType.DEFAULT), - ("pqr", MountType.RIGHT, DeckSlotName.SLOT_3, True, EdgePathType.DEFAULT), - ("pqr", MountType.RIGHT, DeckSlotName.SLOT_D3, True, EdgePathType.DEFAULT), - ("def", MountType.LEFT, DeckSlotName.SLOT_3, True, EdgePathType.DEFAULT), - ("def", MountType.LEFT, DeckSlotName.SLOT_D3, True, EdgePathType.DEFAULT), - ], -) -def test_get_edge_path_type( - well_name: str, - mount: MountType, - labware_slot: DeckSlotName, - next_to_module: bool, - expected_result: EdgePathType, -) -> None: - """It should get the proper edge path type based on well name, mount, and labware position.""" - labware = LoadedLabware( - id="tip-rack-id", - loadName="load-name", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - definitionUri="some-labware-uri", - offsetId=None, - ) - - labware_def = LabwareDefinition.construct( # type: ignore[call-arg] - ordering=[["abc", "def"], ["ghi", "jkl"], ["mno", "pqr"]] - ) - - subject = get_labware_view( - labware_by_id={"labware-id": labware}, - definitions_by_uri={ - "some-labware-uri": labware_def, - }, - ) - - result = subject.get_edge_path_type( - "labware-id", well_name, mount, labware_slot, next_to_module - ) - - assert result == expected_result - - -def test_get_all_labware_definition( - tip_rack_def: LabwareDefinition, falcon_tuberack_def: LabwareDefinition -) -> None: - """It should return the loaded labware definition list.""" - subject = get_labware_view( - labware_by_id={ - "labware-id": LoadedLabware( - id="labware-id", - loadName="test", - definitionUri="opentrons_96_tiprack_300ul", - location=ModuleLocation(moduleId="module-id"), - ) - }, - definitions_by_uri={ - "opentrons_96_tiprack_300ul": tip_rack_def, - "falcon-definition": falcon_tuberack_def, - }, - ) - - result = subject.get_loaded_labware_definitions() - - assert result == [tip_rack_def] - - -def test_get_all_labware_definition_empty() -> None: - """It should return an empty list.""" - subject = get_labware_view( - labware_by_id={}, - ) - - result = subject.get_loaded_labware_definitions() - - assert result == [] - - -def test_raise_if_labware_inaccessible_by_pipette_staging_area() -> None: - """It should raise if the labware is on a staging slot.""" - subject = get_labware_view( - labware_by_id={ - "labware-id": LoadedLabware( - id="labware-id", - loadName="test", - definitionUri="def-uri", - location=AddressableAreaLocation(addressableAreaName="B4"), - ) - }, - ) - - with pytest.raises( - errors.LocationNotAccessibleByPipetteError, match="on staging slot" - ): - subject.raise_if_labware_inaccessible_by_pipette("labware-id") - - -def test_raise_if_labware_inaccessible_by_pipette_off_deck() -> None: - """It should raise if the labware is off-deck.""" - subject = get_labware_view( - labware_by_id={ - "labware-id": LoadedLabware( - id="labware-id", - loadName="test", - definitionUri="def-uri", - location=OFF_DECK_LOCATION, - ) - }, - ) - - with pytest.raises(errors.LocationNotAccessibleByPipetteError, match="off-deck"): - subject.raise_if_labware_inaccessible_by_pipette("labware-id") - - -def test_raise_if_labware_inaccessible_by_pipette_stacked_labware_on_staging_area() -> None: - """It should raise if the labware is stacked on a staging slot.""" - subject = get_labware_view( - labware_by_id={ - "labware-id": LoadedLabware( - id="labware-id", - loadName="test", - definitionUri="def-uri", - location=OnLabwareLocation(labwareId="lower-labware-id"), - ), - "lower-labware-id": LoadedLabware( - id="lower-labware-id", - loadName="test", - definitionUri="def-uri", - location=AddressableAreaLocation(addressableAreaName="B4"), - ), - }, - ) - - with pytest.raises( - errors.LocationNotAccessibleByPipetteError, match="on staging slot" - ): - subject.raise_if_labware_inaccessible_by_pipette("labware-id") - - -def test_raise_if_labware_cannot_be_stacked_is_adapter() -> None: - """It should raise if the labware trying to be stacked is an adapter.""" - subject = get_labware_view() - - with pytest.raises( - errors.LabwareCannotBeStackedError, match="defined as an adapter" - ): - subject.raise_if_labware_cannot_be_stacked( - top_labware_definition=LabwareDefinition.construct( # type: ignore[call-arg] - parameters=Parameters.construct( # type: ignore[call-arg] - loadName="name" - ), - allowedRoles=[LabwareRole.adapter], - ), - bottom_labware_id="labware-id", - ) - - -def test_raise_if_labware_cannot_be_stacked_not_validated() -> None: - """It should raise if the labware name is not in the definition stacking overlap.""" - subject = get_labware_view( - labware_by_id={ - "labware-id": LoadedLabware( - id="labware-id", - loadName="test", - definitionUri="def-uri", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - ) - }, - ) - - with pytest.raises( - errors.LabwareCannotBeStackedError, match="loaded onto labware test" - ): - subject.raise_if_labware_cannot_be_stacked( - top_labware_definition=LabwareDefinition.construct( # type: ignore[call-arg] - parameters=Parameters.construct( # type: ignore[call-arg] - loadName="name" - ), - stackingOffsetWithLabware={}, - ), - bottom_labware_id="labware-id", - ) - - -def test_raise_if_labware_cannot_be_stacked_on_module_not_adapter() -> None: - """It should raise if the below labware on a module is not an adapter.""" - subject = get_labware_view( - labware_by_id={ - "labware-id": LoadedLabware( - id="labware-id", - loadName="test", - definitionUri="def-uri", - location=ModuleLocation(moduleId="module-id"), - ) - }, - definitions_by_uri={ - "def-uri": LabwareDefinition.construct( # type: ignore[call-arg] - allowedRoles=[LabwareRole.labware] - ) - }, - ) - - with pytest.raises(errors.LabwareCannotBeStackedError, match="module"): - subject.raise_if_labware_cannot_be_stacked( - top_labware_definition=LabwareDefinition.construct( # type: ignore[call-arg] - parameters=Parameters.construct( # type: ignore[call-arg] - loadName="name" - ), - stackingOffsetWithLabware={ - "test": SharedDataOverlapOffset(x=0, y=0, z=0) - }, - ), - bottom_labware_id="labware-id", - ) - - -def test_raise_if_labware_cannot_be_stacked_on_labware_on_adapter() -> None: - """It should raise if the OnLabware location is on an adapter.""" - subject = get_labware_view( - labware_by_id={ - "labware-id": LoadedLabware( - id="labware-id", - loadName="test", - definitionUri="def-uri-1", - location=OnLabwareLocation(labwareId="below-id"), - ), - "below-id": LoadedLabware( - id="below-id", - loadName="adapter-name", - definitionUri="def-uri-2", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - ), - }, - definitions_by_uri={ - "def-uri-2": LabwareDefinition.construct( # type: ignore[call-arg] - allowedRoles=[LabwareRole.adapter] - ) - }, - ) - - with pytest.raises(errors.LabwareCannotBeStackedError, match="on top of adapter"): - subject.raise_if_labware_cannot_be_stacked( - top_labware_definition=LabwareDefinition.construct( # type: ignore[call-arg] - parameters=Parameters.construct( # type: ignore[call-arg] - loadName="name" - ), - stackingOffsetWithLabware={ - "test": SharedDataOverlapOffset(x=0, y=0, z=0) - }, - ), - bottom_labware_id="labware-id", - ) - - -def test_get_deck_gripper_offsets(ot3_standard_deck_def: DeckDefinitionV5) -> None: - """It should get the deck's gripper offsets.""" - subject = get_labware_view(deck_definition=ot3_standard_deck_def) - - assert subject.get_deck_default_gripper_offsets() == LabwareMovementOffsetData( - pickUpOffset=LabwareOffsetVector(x=0, y=0, z=0), - dropOffset=LabwareOffsetVector(x=0, y=0, z=-0.75), - ) - - -def test_get_labware_gripper_offsets( - well_plate_def: LabwareDefinition, - adapter_plate_def: LabwareDefinition, -) -> None: - """It should get the labware's gripper offsets.""" - subject = get_labware_view( - labware_by_id={"plate-id": plate, "adapter-plate-id": adapter_plate}, - definitions_by_uri={ - "some-plate-uri": well_plate_def, - "some-adapter-uri": adapter_plate_def, - }, - ) - - assert ( - subject.get_labware_gripper_offsets(labware_id="plate-id", slot_name=None) - is None - ) - assert subject.get_labware_gripper_offsets( - labware_id="adapter-plate-id", slot_name=DeckSlotName.SLOT_D1 - ) == LabwareMovementOffsetData( - pickUpOffset=LabwareOffsetVector(x=0, y=0, z=0), - dropOffset=LabwareOffsetVector(x=2, y=0, z=0), - ) - - -def test_get_labware_gripper_offsets_default_no_slots( - well_plate_def: LabwareDefinition, - adapter_plate_def: LabwareDefinition, -) -> None: - """It should get the labware's gripper offsets with only a default gripper offset entry.""" - subject = get_labware_view( - labware_by_id={ - "labware-id": LoadedLabware( - id="labware-id", - loadName="labware-load-name", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - definitionUri="some-labware-uri", - offsetId=None, - displayName="Fancy Labware Name", - ) - }, - definitions_by_uri={ - "some-labware-uri": LabwareDefinition.construct( # type: ignore[call-arg] - gripperOffsets={ - "default": GripperOffsets( - pickUpOffset=OffsetVector(x=1, y=2, z=3), - dropOffset=OffsetVector(x=4, y=5, z=6), - ) - } - ), - }, - ) - - assert ( - subject.get_labware_gripper_offsets( - labware_id="labware-id", slot_name=DeckSlotName.SLOT_D1 - ) - is None - ) - - assert subject.get_labware_gripper_offsets( - labware_id="labware-id", slot_name=None - ) == LabwareMovementOffsetData( - pickUpOffset=LabwareOffsetVector(x=1, y=2, z=3), - dropOffset=LabwareOffsetVector(x=4, y=5, z=6), - ) - - -def test_get_grip_force( - flex_50uL_tiprack: LabwareDefinition, - reservoir_def: LabwareDefinition, -) -> None: - """It should get the grip force, if present, from labware definition or return default.""" - subject = get_labware_view( - labware_by_id={"flex-tiprack-id": flex_tiprack, "reservoir-id": reservoir}, - definitions_by_uri={ - "some-flex-tiprack-uri": flex_50uL_tiprack, - "some-reservoir-uri": reservoir_def, - }, - ) - - assert subject.get_grip_force("flex-tiprack-id") == 16 # from definition - assert subject.get_grip_force("reservoir-id") == 15 # default - - -def test_get_grip_height_from_labware_bottom( - well_plate_def: LabwareDefinition, - reservoir_def: LabwareDefinition, -) -> None: - """It should get the grip height, if present, from labware definition or return default.""" - subject = get_labware_view( - labware_by_id={"plate-id": plate, "reservoir-id": reservoir}, - definitions_by_uri={ - "some-plate-uri": well_plate_def, - "some-reservoir-uri": reservoir_def, - }, - ) - - assert ( - subject.get_grip_height_from_labware_bottom("plate-id") == 12.2 - ) # from definition - assert ( - subject.get_grip_height_from_labware_bottom("reservoir-id") == 15.7 - ) # default - - -@pytest.mark.parametrize( - "labware_to_check,well_bbox", - [ - ("opentrons_universal_flat_adapter", Dimensions(0, 0, 0)), - ( - "corning_96_wellplate_360ul_flat", - Dimensions(116.81 - 10.95, 77.67 - 7.81, 14.22), - ), - ("nest_12_reservoir_15ml", Dimensions(117.48 - 10.28, 78.38 - 7.18, 31.4)), - ], -) -def test_calculates_well_bounding_box( - labware_to_check: str, well_bbox: Dimensions -) -> None: - """It should be able to calculate well bounding boxes.""" - definition = LabwareDefinition.parse_obj(load_definition(labware_to_check, 1)) - labware = LoadedLabware( - id="test-labware-id", - loadName=labware_to_check, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - definitionUri="test-labware-uri", - offsetId=None, - displayName="Fancy Plate Name", - ) - subject = get_labware_view( - labware_by_id={"test-labware-id": labware}, - definitions_by_uri={"test-labware-uri": definition}, - ) - assert subject.get_well_bbox("test-labware-id").x == pytest.approx(well_bbox.x) - assert subject.get_well_bbox("test-labware-id").y == pytest.approx(well_bbox.y) - assert subject.get_well_bbox("test-labware-id").z == pytest.approx(well_bbox.z) diff --git a/api/tests/opentrons/protocol_engine/state/test_labware_view_old.py b/api/tests/opentrons/protocol_engine/state/test_labware_view_old.py new file mode 100644 index 00000000000..0b6886040c6 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_labware_view_old.py @@ -0,0 +1,1625 @@ +"""Labware state store tests. + +DEPRECATED: Testing LabwareView independently of LabwareStore is no +longer helpful. Try to add new tests to test_labware_state.py, where they can be +tested together, treating LabwareState as a private implementation detail. +""" +import pytest +from datetime import datetime +from typing import Dict, Optional, cast, ContextManager, Any, Union, NamedTuple, List +from contextlib import nullcontext as does_not_raise + +from opentrons_shared_data.deck import load as load_deck +from opentrons_shared_data.deck.types import DeckDefinitionV5 +from opentrons_shared_data.pipette.types import LabwareUri +from opentrons_shared_data.labware import load_definition +from opentrons_shared_data.labware.labware_definition import ( + Parameters, + LabwareRole, + OverlapOffset as SharedDataOverlapOffset, + GripperOffsets, + OffsetVector, +) + +from opentrons.protocols.api_support.deck_type import ( + STANDARD_OT2_DECK, + STANDARD_OT3_DECK, +) +from opentrons.protocols.models import LabwareDefinition +from opentrons.types import DeckSlotName, MountType + +from opentrons.protocol_engine import errors +from opentrons.protocol_engine.types import ( + DeckSlotLocation, + Dimensions, + LabwareOffset, + LabwareOffsetVector, + LabwareOffsetLocation, + LoadedLabware, + ModuleModel, + ModuleLocation, + OnLabwareLocation, + LabwareLocation, + AddressableAreaLocation, + OFF_DECK_LOCATION, + OverlapOffset, + LabwareMovementOffsetData, +) +from opentrons.protocol_engine.state._move_types import EdgePathType +from opentrons.protocol_engine.state.labware import ( + LabwareState, + LabwareView, + LabwareLoadParams, +) + +plate = LoadedLabware( + id="plate-id", + loadName="plate-load-name", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + definitionUri="some-plate-uri", + offsetId=None, + displayName="Fancy Plate Name", +) + +flex_tiprack = LoadedLabware( + id="flex-tiprack-id", + loadName="flex-tiprack-load-name", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + definitionUri="some-flex-tiprack-uri", + offsetId=None, + displayName="Flex Tiprack Name", +) + +reservoir = LoadedLabware( + id="reservoir-id", + loadName="reservoir-load-name", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_2), + definitionUri="some-reservoir-uri", + offsetId=None, +) + +trash = LoadedLabware( + id="trash-id", + loadName="trash-load-name", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_2), + definitionUri="some-trash-uri", + offsetId=None, +) + +tube_rack = LoadedLabware( + id="tube-rack-id", + loadName="tube-rack-load-name", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + definitionUri="some-tube-rack-uri", + offsetId=None, +) + +tip_rack = LoadedLabware( + id="tip-rack-id", + loadName="tip-rack-load-name", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + definitionUri="some-tip-rack-uri", + offsetId=None, +) + +adapter_plate = LoadedLabware( + id="adapter-plate-id", + loadName="adapter-load-name", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + definitionUri="some-adapter-uri", + offsetId=None, +) + + +def get_labware_view( + labware_by_id: Optional[Dict[str, LoadedLabware]] = None, + labware_offsets_by_id: Optional[Dict[str, LabwareOffset]] = None, + definitions_by_uri: Optional[Dict[str, LabwareDefinition]] = None, + deck_definition: Optional[DeckDefinitionV5] = None, +) -> LabwareView: + """Get a labware view test subject.""" + state = LabwareState( + labware_by_id=labware_by_id or {}, + labware_offsets_by_id=labware_offsets_by_id or {}, + definitions_by_uri=definitions_by_uri or {}, + deck_definition=deck_definition or cast(DeckDefinitionV5, {"fake": True}), + ) + + return LabwareView(state=state) + + +def test_get_labware_data_bad_id() -> None: + """get_labware_data_by_id should raise if labware ID doesn't exist.""" + subject = get_labware_view() + + with pytest.raises(errors.LabwareNotLoadedError): + subject.get("asdfghjkl") + + +def test_get_labware_data_by_id() -> None: + """It should retrieve labware data from the state.""" + subject = get_labware_view(labware_by_id={"plate-id": plate}) + + assert subject.get("plate-id") == plate + + +def test_get_id_by_module() -> None: + """Should return the labware id associated to the module.""" + subject = get_labware_view( + labware_by_id={ + "labware-id": LoadedLabware( + id="labware-id", + loadName="test", + definitionUri="test-uri", + location=ModuleLocation(moduleId="module-id"), + ) + } + ) + assert subject.get_id_by_module(module_id="module-id") == "labware-id" + + +def test_get_id_by_module_raises_error() -> None: + """Should raise error that labware not found.""" + subject = get_labware_view( + labware_by_id={ + "labware-id": LoadedLabware( + id="labware-id", + loadName="test", + definitionUri="test-uri", + location=ModuleLocation(moduleId="module-id"), + ) + } + ) + with pytest.raises(errors.exceptions.LabwareNotLoadedOnModuleError): + subject.get_id_by_module(module_id="no-module-id") + + +def test_get_id_by_labware() -> None: + """Should return the labware id associated to the labware.""" + subject = get_labware_view( + labware_by_id={ + "labware-id": LoadedLabware( + id="labware-id", + loadName="test", + definitionUri="test-uri", + location=OnLabwareLocation(labwareId="other-labware-id"), + ) + } + ) + assert subject.get_id_by_labware(labware_id="other-labware-id") == "labware-id" + + +def test_get_id_by_labware_raises_error() -> None: + """Should raise error that labware not found.""" + subject = get_labware_view( + labware_by_id={ + "labware-id": LoadedLabware( + id="labware-id", + loadName="test", + definitionUri="test-uri", + location=OnLabwareLocation(labwareId="other-labware-id"), + ) + } + ) + with pytest.raises(errors.exceptions.LabwareNotLoadedOnLabwareError): + subject.get_id_by_labware(labware_id="no-labware-id") + + +def test_raise_if_labware_has_labware_on_top() -> None: + """It should raise if labware has another labware on top.""" + subject = get_labware_view( + labware_by_id={ + "labware-id-1": LoadedLabware( + id="labware-id-1", + loadName="test", + definitionUri="test-uri", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + "labware-id-2": LoadedLabware( + id="labware-id-2", + loadName="test", + definitionUri="test-uri", + location=ModuleLocation(moduleId="module-id"), + ), + "labware-id-3": LoadedLabware( + id="labware-id-3", + loadName="test", + definitionUri="test-uri", + location=OnLabwareLocation(labwareId="labware-id-1"), + ), + } + ) + subject.raise_if_labware_has_labware_on_top("labware-id-2") + subject.raise_if_labware_has_labware_on_top("labware-id-3") + with pytest.raises(errors.exceptions.LabwareIsInStackError): + subject.raise_if_labware_has_labware_on_top("labware-id-1") + + +def test_get_labware_definition(well_plate_def: LabwareDefinition) -> None: + """It should get a labware's definition from the state.""" + subject = get_labware_view( + labware_by_id={"plate-id": plate}, + definitions_by_uri={"some-plate-uri": well_plate_def}, + ) + + assert subject.get_definition("plate-id") == well_plate_def + + +def test_get_labware_definition_bad_id() -> None: + """get_labware_definition should raise if labware definition doesn't exist.""" + subject = get_labware_view() + + with pytest.raises(errors.LabwareDefinitionDoesNotExistError): + subject.get_definition_by_uri(cast(LabwareUri, "not-a-uri")) + + +@pytest.mark.parametrize( + argnames=["namespace", "version"], + argvalues=[("world", 123), (None, 123), ("world", None), (None, None)], +) +def test_find_custom_labware_params( + namespace: Optional[str], version: Optional[int] +) -> None: + """It should find the missing (if any) load labware parameters.""" + labware_def = LabwareDefinition.construct( # type: ignore[call-arg] + parameters=Parameters.construct(loadName="hello"), # type: ignore[call-arg] + namespace="world", + version=123, + ) + standard_def = LabwareDefinition.construct( # type: ignore[call-arg] + parameters=Parameters.construct(loadName="goodbye"), # type: ignore[call-arg] + namespace="opentrons", + version=456, + ) + + subject = get_labware_view( + definitions_by_uri={ + "some-labware-uri": labware_def, + "some-standard-uri": standard_def, + }, + ) + + result = subject.find_custom_labware_load_params() + + assert result == [ + LabwareLoadParams(load_name="hello", namespace="world", version=123) + ] + + +def test_get_all_labware( + well_plate_def: LabwareDefinition, + reservoir_def: LabwareDefinition, +) -> None: + """It should return all labware.""" + subject = get_labware_view( + labware_by_id={ + "plate-id": plate, + "reservoir-id": reservoir, + } + ) + + all_labware = subject.get_all() + + assert all_labware == [plate, reservoir] + + +def test_get_labware_location() -> None: + """It should return labware location.""" + subject = get_labware_view(labware_by_id={"plate-id": plate}) + + result = subject.get_location("plate-id") + + assert result == DeckSlotLocation(slotName=DeckSlotName.SLOT_1) + + +@pytest.mark.parametrize( + argnames="location", + argvalues=[ + DeckSlotLocation(slotName=DeckSlotName.SLOT_D1), + ModuleLocation(moduleId="module-id"), + OFF_DECK_LOCATION, + ], +) +def test_get_parent_location(location: LabwareLocation) -> None: + """It should return the non-OnLabware location of a labware.""" + subject = get_labware_view( + labware_by_id={ + "labware-id": LoadedLabware( + id="plate-id", + loadName="load-name", + location=location, + definitionUri="some-uri", + ) + } + ) + + result = subject.get_parent_location(labware_id="labware-id") + + assert result == location + + +@pytest.mark.parametrize( + argnames="location", + argvalues=[ + DeckSlotLocation(slotName=DeckSlotName.SLOT_D1), + ModuleLocation(moduleId="module-id"), + ], +) +def test_get_parent_location_on_labware(location: LabwareLocation) -> None: + """It should return the non-OnLabware location of a labware.""" + subject = get_labware_view( + labware_by_id={ + "top-id": LoadedLabware( + id="top-id", + loadName="load-name", + location=OnLabwareLocation(labwareId="middle-id"), + definitionUri="some-uri", + ), + "middle-id": LoadedLabware( + id="middle-id", + loadName="load-name", + location=OnLabwareLocation(labwareId="bottom-id"), + definitionUri="some-uri", + ), + "bottom-id": LoadedLabware( + id="bottom-id", + loadName="load-name", + location=location, + definitionUri="some-uri", + ), + } + ) + + result = subject.get_parent_location(labware_id="top-id") + + assert result == location + + +def test_get_has_quirk( + well_plate_def: LabwareDefinition, + reservoir_def: LabwareDefinition, +) -> None: + """It should return whether a labware by ID has a given quirk.""" + subject = get_labware_view( + labware_by_id={ + "plate-id": plate, + "reservoir-id": reservoir, + }, + definitions_by_uri={ + "some-plate-uri": well_plate_def, + "some-reservoir-uri": reservoir_def, + }, + ) + + well_plate_has_center_quirk = subject.get_has_quirk( + labware_id="plate-id", + quirk="centerMultichannelOnWells", + ) + + reservoir_has_center_quirk = subject.get_has_quirk( + labware_id="reservoir-id", + quirk="centerMultichannelOnWells", + ) + + assert well_plate_has_center_quirk is False + assert reservoir_has_center_quirk is True + + +def test_quirks( + well_plate_def: LabwareDefinition, + reservoir_def: LabwareDefinition, +) -> None: + """It should return a labware's quirks.""" + subject = get_labware_view( + labware_by_id={ + "plate-id": plate, + "reservoir-id": reservoir, + }, + definitions_by_uri={ + "some-plate-uri": well_plate_def, + "some-reservoir-uri": reservoir_def, + }, + ) + + well_plate_quirks = subject.get_quirks("plate-id") + reservoir_quirks = subject.get_quirks("reservoir-id") + + assert well_plate_quirks == [] + assert reservoir_quirks == ["centerMultichannelOnWells", "touchTipDisabled"] + + +def test_get_well_definition_bad_name(well_plate_def: LabwareDefinition) -> None: + """get_well_definition should raise if well name doesn't exist.""" + subject = get_labware_view( + labware_by_id={"plate-id": plate}, + definitions_by_uri={"some-plate-uri": well_plate_def}, + ) + + with pytest.raises(errors.WellDoesNotExistError): + subject.get_well_definition(labware_id="plate-id", well_name="foobar") + + +def test_get_well_definition(well_plate_def: LabwareDefinition) -> None: + """It should return a well definition by well name.""" + subject = get_labware_view( + labware_by_id={"plate-id": plate}, + definitions_by_uri={"some-plate-uri": well_plate_def}, + ) + + expected_well_def = well_plate_def.wells["B2"] + result = subject.get_well_definition(labware_id="plate-id", well_name="B2") + + assert result == expected_well_def + + +def test_get_well_definition_get_first(well_plate_def: LabwareDefinition) -> None: + """It should return the first well definition if no given well name.""" + subject = get_labware_view( + labware_by_id={"plate-id": plate}, + definitions_by_uri={"some-plate-uri": well_plate_def}, + ) + + expected_well_def = well_plate_def.wells["A1"] + result = subject.get_well_definition(labware_id="plate-id", well_name=None) + + assert result == expected_well_def + + +def test_get_well_geometry_raises_error(well_plate_def: LabwareDefinition) -> None: + """It should raise an IncompleteLabwareDefinitionError when there's no innerLabwareGeometry.""" + subject = get_labware_view( + labware_by_id={"plate-id": plate}, + definitions_by_uri={"some-plate-uri": well_plate_def}, + ) + + with pytest.raises(errors.IncompleteLabwareDefinitionError): + subject.get_well_geometry(labware_id="plate-id") + + +def test_get_well_size_circular(well_plate_def: LabwareDefinition) -> None: + """It should return the well dimensions of a circular well.""" + subject = get_labware_view( + labware_by_id={"plate-id": plate}, + definitions_by_uri={"some-plate-uri": well_plate_def}, + ) + expected_well_def = well_plate_def.wells["A2"] + expected_size = ( + expected_well_def.diameter, + expected_well_def.diameter, + expected_well_def.depth, + ) + + result = subject.get_well_size(labware_id="plate-id", well_name="A2") + + assert result == expected_size + + +def test_get_well_size_rectangular(reservoir_def: LabwareDefinition) -> None: + """It should return the well dimensions of a rectangular well.""" + subject = get_labware_view( + labware_by_id={"reservoir-id": reservoir}, + definitions_by_uri={"some-reservoir-uri": reservoir_def}, + ) + expected_well_def = reservoir_def.wells["A2"] + expected_size = ( + expected_well_def.xDimension, + expected_well_def.yDimension, + expected_well_def.depth, + ) + + result = subject.get_well_size(labware_id="reservoir-id", well_name="A2") + + assert result == expected_size + + +def test_labware_has_well(falcon_tuberack_def: LabwareDefinition) -> None: + """It should return a list of wells from definition.""" + subject = get_labware_view( + labware_by_id={"tube-rack-id": tube_rack}, + definitions_by_uri={"some-tube-rack-uri": falcon_tuberack_def}, + ) + + result = subject.validate_liquid_allowed_in_labware( + labware_id="tube-rack-id", wells={"A1": 30, "B1": 100} + ) + assert result == ["A1", "B1"] + + with pytest.raises(errors.WellDoesNotExistError): + subject.validate_liquid_allowed_in_labware( + labware_id="tube-rack-id", wells={"AA": 30} + ) + + with pytest.raises(errors.LabwareNotLoadedError): + subject.validate_liquid_allowed_in_labware(labware_id="no-id", wells={"A1": 30}) + + +def test_validate_liquid_allowed_raises_incompatible_labware() -> None: + """It should raise when validating labware that is a tiprack or an adapter.""" + subject = get_labware_view( + labware_by_id={ + "tiprack-id": LoadedLabware( + id="tiprack-id", + loadName="test1", + definitionUri="some-tiprack-uri", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + "adapter-id": LoadedLabware( + id="adapter-id", + loadName="test2", + definitionUri="some-adapter-uri", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_2), + ), + }, + definitions_by_uri={ + "some-tiprack-uri": LabwareDefinition.construct( # type: ignore[call-arg] + parameters=Parameters.construct(isTiprack=True), # type: ignore[call-arg] + wells={}, + ), + "some-adapter-uri": LabwareDefinition.construct( # type: ignore[call-arg] + parameters=Parameters.construct(isTiprack=False), # type: ignore[call-arg] + allowedRoles=[LabwareRole.adapter], + wells={}, + ), + }, + ) + + with pytest.raises(errors.LabwareIsTipRackError): + subject.validate_liquid_allowed_in_labware(labware_id="tiprack-id", wells={}) + + with pytest.raises(errors.LabwareIsAdapterError): + subject.validate_liquid_allowed_in_labware(labware_id="adapter-id", wells={}) + + +def test_get_tip_length_raises_with_non_tip_rack( + well_plate_def: LabwareDefinition, +) -> None: + """It should raise if you try to get the tip length of a regular labware.""" + subject = get_labware_view( + labware_by_id={"plate-id": plate}, + definitions_by_uri={"some-plate-uri": well_plate_def}, + ) + + with pytest.raises(errors.LabwareIsNotTipRackError): + subject.get_tip_length("plate-id") + + +def test_get_tip_length_gets_length_from_definition( + tip_rack_def: LabwareDefinition, +) -> None: + """It should return the tip length from the definition.""" + subject = get_labware_view( + labware_by_id={"tip-rack-id": tip_rack}, + definitions_by_uri={"some-tip-rack-uri": tip_rack_def}, + ) + + length = subject.get_tip_length("tip-rack-id", 12.3) + assert length == tip_rack_def.parameters.tipLength - 12.3 # type: ignore[operator] + + +def test_get_tip_drop_z_offset() -> None: + """It should get a tip drop z offset by scaling the tip length.""" + tip_rack_def = LabwareDefinition.construct( # type: ignore[call-arg] + parameters=Parameters.construct( # type: ignore[call-arg] + tipLength=100, + ) + ) + + subject = get_labware_view( + labware_by_id={"tip-rack-id": tip_rack}, + definitions_by_uri={"some-tip-rack-uri": tip_rack_def}, + ) + + result = subject.get_tip_drop_z_offset( + labware_id="tip-rack-id", length_scale=0.5, additional_offset=-0.123 + ) + + assert result == -50.123 + + +def test_get_labware_uri_from_definition(tip_rack_def: LabwareDefinition) -> None: + """It should return the labware's definition URI.""" + tip_rack = LoadedLabware( + id="tip-rack-id", + loadName="tip-rack-load-name", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + definitionUri="some-tip-rack-uri", + offsetId=None, + ) + + subject = get_labware_view( + labware_by_id={"tip-rack-id": tip_rack}, + definitions_by_uri={"some-tip-rack-uri": tip_rack_def}, + ) + + result = subject.get_definition_uri(labware_id="tip-rack-id") + assert result == "some-tip-rack-uri" + + +def test_get_labware_uri_from_full_definition(tip_rack_def: LabwareDefinition) -> None: + """It should be able to construct a URI given a full definition.""" + subject = get_labware_view() + result = subject.get_uri_from_definition(tip_rack_def) + assert result == "opentrons/opentrons_96_tiprack_300ul/1" + + +def test_is_tiprack( + tip_rack_def: LabwareDefinition, reservoir_def: LabwareDefinition +) -> None: + """It should determine if labware is a tip rack.""" + subject = get_labware_view( + labware_by_id={ + "tip-rack-id": tip_rack, + "reservoir-id": reservoir, + }, + definitions_by_uri={ + "some-tip-rack-uri": tip_rack_def, + "some-reservoir-uri": reservoir_def, + }, + ) + + assert subject.is_tiprack(labware_id="tip-rack-id") is True + assert subject.is_tiprack(labware_id="reservoir-id") is False + + +def test_get_load_name(reservoir_def: LabwareDefinition) -> None: + """It should return the load name.""" + subject = get_labware_view( + labware_by_id={"reservoir-id": reservoir}, + definitions_by_uri={"some-reservoir-uri": reservoir_def}, + ) + + result = subject.get_load_name("reservoir-id") + + assert result == reservoir_def.parameters.loadName + + +def test_get_dimensions(well_plate_def: LabwareDefinition) -> None: + """It should compute the dimensions of a labware.""" + subject = get_labware_view( + labware_by_id={"plate-id": plate}, + definitions_by_uri={"some-plate-uri": well_plate_def}, + ) + + result = subject.get_dimensions(labware_id="plate-id") + + assert result == Dimensions( + x=well_plate_def.dimensions.xDimension, + y=well_plate_def.dimensions.yDimension, + z=well_plate_def.dimensions.zDimension, + ) + + +def test_get_labware_overlap_offsets() -> None: + """It should get the labware overlap offsets.""" + subject = get_labware_view() + result = subject.get_labware_overlap_offsets( + definition=LabwareDefinition.construct( # type: ignore[call-arg] + stackingOffsetWithLabware={ + "bottom-labware-name": SharedDataOverlapOffset(x=1, y=2, z=3) + } + ), + below_labware_name="bottom-labware-name", + ) + + assert result == OverlapOffset(x=1, y=2, z=3) + + +class ModuleOverlapSpec(NamedTuple): + """Spec data to test LabwareView.get_module_overlap_offsets.""" + + spec_deck_definition: DeckDefinitionV5 + module_model: ModuleModel + stacking_offset_with_module: Dict[str, SharedDataOverlapOffset] + expected_offset: OverlapOffset + + +module_overlap_specs: List[ModuleOverlapSpec] = [ + ModuleOverlapSpec( + # Labware on temp module on OT2, with stacking overlap for temp module + spec_deck_definition=load_deck(STANDARD_OT2_DECK, 5), + module_model=ModuleModel.TEMPERATURE_MODULE_V2, + stacking_offset_with_module={ + str(ModuleModel.TEMPERATURE_MODULE_V2.value): SharedDataOverlapOffset( + x=1, y=2, z=3 + ), + }, + expected_offset=OverlapOffset(x=1, y=2, z=3), + ), + ModuleOverlapSpec( + # Labware on TC Gen1 on OT2, with stacking overlap for TC Gen1 + spec_deck_definition=load_deck(STANDARD_OT2_DECK, 5), + module_model=ModuleModel.THERMOCYCLER_MODULE_V1, + stacking_offset_with_module={ + str(ModuleModel.THERMOCYCLER_MODULE_V1.value): SharedDataOverlapOffset( + x=11, y=22, z=33 + ), + }, + expected_offset=OverlapOffset(x=11, y=22, z=33), + ), + ModuleOverlapSpec( + # Labware on TC Gen2 on OT2, with no stacking overlap + spec_deck_definition=load_deck(STANDARD_OT2_DECK, 5), + module_model=ModuleModel.THERMOCYCLER_MODULE_V2, + stacking_offset_with_module={}, + expected_offset=OverlapOffset(x=0, y=0, z=10.7), + ), + ModuleOverlapSpec( + # Labware on TC Gen2 on Flex, with no stacking overlap + spec_deck_definition=load_deck(STANDARD_OT3_DECK, 5), + module_model=ModuleModel.THERMOCYCLER_MODULE_V2, + stacking_offset_with_module={}, + expected_offset=OverlapOffset(x=0, y=0, z=0), + ), + ModuleOverlapSpec( + # Labware on TC Gen2 on Flex, with stacking overlap for TC Gen2 + spec_deck_definition=load_deck(STANDARD_OT3_DECK, 5), + module_model=ModuleModel.THERMOCYCLER_MODULE_V2, + stacking_offset_with_module={ + str(ModuleModel.THERMOCYCLER_MODULE_V2.value): SharedDataOverlapOffset( + x=111, y=222, z=333 + ), + }, + expected_offset=OverlapOffset(x=111, y=222, z=333), + ), +] + + +@pytest.mark.parametrize( + argnames=ModuleOverlapSpec._fields, + argvalues=module_overlap_specs, +) +def test_get_module_overlap_offsets( + spec_deck_definition: DeckDefinitionV5, + module_model: ModuleModel, + stacking_offset_with_module: Dict[str, SharedDataOverlapOffset], + expected_offset: OverlapOffset, +) -> None: + """It should get the labware overlap offsets.""" + subject = get_labware_view( + deck_definition=spec_deck_definition, + ) + result = subject.get_module_overlap_offsets( + definition=LabwareDefinition.construct( # type: ignore[call-arg] + stackingOffsetWithModule=stacking_offset_with_module + ), + module_model=module_model, + ) + + assert result == expected_offset + + +def test_get_default_magnet_height( + magdeck_well_plate_def: LabwareDefinition, +) -> None: + """Should get get the default value for magnetic height.""" + well_plate = LoadedLabware( + id="well-plate-id", + loadName="load-name", + location=ModuleLocation(moduleId="module-id"), + definitionUri="well-plate-uri", + offsetId=None, + ) + + subject = get_labware_view( + labware_by_id={"well-plate-id": well_plate}, + definitions_by_uri={"well-plate-uri": magdeck_well_plate_def}, + ) + + assert subject.get_default_magnet_height(module_id="module-id", offset=2) == 12.0 + + +def test_get_deck_definition(ot2_standard_deck_def: DeckDefinitionV5) -> None: + """It should get the deck definition from the state.""" + subject = get_labware_view(deck_definition=ot2_standard_deck_def) + + assert subject.get_deck_definition() == ot2_standard_deck_def + + +def test_get_labware_offset_vector() -> None: + """It should get a labware's offset vector.""" + labware_without_offset = LoadedLabware( + id="without-offset-labware-id", + loadName="labware-load-name", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + definitionUri="some-labware-uri", + offsetId=None, + ) + + labware_with_offset = LoadedLabware( + id="with-offset-labware-id", + loadName="labware-load-name", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + definitionUri="some-labware-uri", + offsetId="offset-id", + ) + + offset_vector = LabwareOffsetVector(x=1, y=2, z=3) + offset = LabwareOffset( + id="offset-id", + createdAt=datetime(year=2021, month=1, day=2), + definitionUri="some-labware-uri", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + vector=offset_vector, + ) + + subject = get_labware_view( + labware_by_id={ + labware_without_offset.id: labware_without_offset, + labware_with_offset.id: labware_with_offset, + }, + labware_offsets_by_id={"offset-id": offset}, + ) + + assert subject.get_labware_offset_vector(labware_with_offset.id) == offset.vector + + assert subject.get_labware_offset_vector( + labware_without_offset.id + ) == LabwareOffsetVector(x=0, y=0, z=0) + + with pytest.raises(errors.LabwareNotLoadedError): + subject.get_labware_offset_vector("wrong-labware-id") + + +def test_get_labware_offset() -> None: + """It should return the requested labware offset, if it exists.""" + offset_a = LabwareOffset( + id="id-a", + createdAt=datetime(year=2021, month=1, day=1), + definitionUri="uri-a", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + vector=LabwareOffsetVector(x=1, y=1, z=1), + ) + + offset_b = LabwareOffset( + id="id-b", + createdAt=datetime(year=2022, month=2, day=2), + definitionUri="uri-b", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_2), + vector=LabwareOffsetVector(x=2, y=2, z=2), + ) + + subject = get_labware_view( + labware_offsets_by_id={"id-a": offset_a, "id-b": offset_b} + ) + + assert subject.get_labware_offset("id-a") == offset_a + assert subject.get_labware_offset("id-b") == offset_b + with pytest.raises(errors.LabwareOffsetDoesNotExistError): + subject.get_labware_offset("wrong-labware-offset-id") + + +def test_get_labware_offsets() -> None: + """It should return a list of all labware offsets, in order.""" + offset_a = LabwareOffset( + id="id-a", + createdAt=datetime(year=2021, month=1, day=1), + definitionUri="uri-a", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + vector=LabwareOffsetVector(x=1, y=1, z=1), + ) + + offset_b = LabwareOffset( + id="id-b", + createdAt=datetime(year=2022, month=2, day=2), + definitionUri="uri-b", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_2), + vector=LabwareOffsetVector(x=2, y=2, z=2), + ) + + empty_subject = get_labware_view() + assert empty_subject.get_labware_offsets() == [] + + filled_subject_a_before_b = get_labware_view( + labware_offsets_by_id={"id-a": offset_a, "id-b": offset_b} + ) + assert filled_subject_a_before_b.get_labware_offsets() == [offset_a, offset_b] + + filled_subject_b_before_a = get_labware_view( + labware_offsets_by_id={"id-b": offset_b, "id-a": offset_a} + ) + assert filled_subject_b_before_a.get_labware_offsets() == [offset_b, offset_a] + + +def test_find_applicable_labware_offset() -> None: + """It should return the most recent offset with matching URI and location.""" + offset_1 = LabwareOffset( + id="id-1", + createdAt=datetime(year=2021, month=1, day=1), + definitionUri="definition-uri", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + vector=LabwareOffsetVector(x=1, y=1, z=1), + ) + + # Same definitionUri and location; different id, createdAt, and offset. + offset_2 = LabwareOffset( + id="id-2", + createdAt=datetime(year=2022, month=2, day=2), + definitionUri="definition-uri", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + vector=LabwareOffsetVector(x=2, y=2, z=2), + ) + + offset_3 = LabwareOffset( + id="id-3", + createdAt=datetime(year=2023, month=3, day=3), + definitionUri="on-module-definition-uri", + location=LabwareOffsetLocation( + slotName=DeckSlotName.SLOT_1, + moduleModel=ModuleModel.TEMPERATURE_MODULE_V1, + ), + vector=LabwareOffsetVector(x=3, y=3, z=3), + ) + + subject = get_labware_view( + # Simulate offset_2 having been added after offset_1. + labware_offsets_by_id={"id-1": offset_1, "id-2": offset_2, "id-3": offset_3} + ) + + # Matching both definitionURI and location. Should return 2nd (most recent) offset. + assert ( + subject.find_applicable_labware_offset( + definition_uri="definition-uri", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + ) + == offset_2 + ) + + assert ( + subject.find_applicable_labware_offset( + definition_uri="on-module-definition-uri", + location=LabwareOffsetLocation( + slotName=DeckSlotName.SLOT_1, + moduleModel=ModuleModel.TEMPERATURE_MODULE_V1, + ), + ) + == offset_3 + ) + + # Doesn't match anything, since definitionUri is different. + assert ( + subject.find_applicable_labware_offset( + definition_uri="different-definition-uri", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_1), + ) + is None + ) + + # Doesn't match anything, since location is different. + assert ( + subject.find_applicable_labware_offset( + definition_uri="different-definition-uri", + location=LabwareOffsetLocation(slotName=DeckSlotName.SLOT_2), + ) + is None + ) + + +def test_get_user_specified_display_name() -> None: + """It should get a labware's user-specified display name.""" + subject = get_labware_view( + labware_by_id={ + "plate_with_display_name": plate, + "reservoir_without_display_name": reservoir, + }, + ) + + assert ( + subject.get_user_specified_display_name("plate_with_display_name") + == "Fancy Plate Name" + ) + assert ( + subject.get_user_specified_display_name("reservoir_without_display_name") + is None + ) + + +def test_get_display_name( + well_plate_def: LabwareDefinition, + reservoir_def: LabwareDefinition, +) -> None: + """It should get the labware's display name.""" + subject = get_labware_view( + labware_by_id={ + "plate_with_custom_display_name": plate, + "reservoir_with_default_display_name": reservoir, + }, + definitions_by_uri={ + "some-plate-uri": well_plate_def, + "some-reservoir-uri": reservoir_def, + }, + ) + assert ( + subject.get_display_name("plate_with_custom_display_name") == "Fancy Plate Name" + ) + assert ( + subject.get_display_name("reservoir_with_default_display_name") + == "NEST 12 Well Reservoir 15 mL" + ) + + +def test_get_fixed_trash_id() -> None: + """It should return the ID of the labware loaded into the fixed trash slot.""" + # OT-2 fixed trash slot: + subject = get_labware_view( + labware_by_id={ + "abc123": LoadedLabware( + id="abc123", + loadName="trash-load-name", + location=DeckSlotLocation(slotName=DeckSlotName.FIXED_TRASH), + definitionUri="trash-definition-uri", + offsetId=None, + displayName=None, + ) + }, + ) + assert subject.get_fixed_trash_id() == "abc123" + + # OT-3 fixed trash slot: + subject = get_labware_view( + labware_by_id={ + "abc123": LoadedLabware( + id="abc123", + loadName="trash-load-name", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A3), + definitionUri="trash-definition-uri", + offsetId=None, + displayName=None, + ) + }, + ) + assert subject.get_fixed_trash_id() == "abc123" + + # Nothing in the fixed trash slot: + subject = get_labware_view( + labware_by_id={ + "abc123": LoadedLabware( + id="abc123", + loadName="trash-load-name", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + definitionUri="trash-definition-uri", + offsetId=None, + displayName=None, + ) + }, + ) + assert subject.get_fixed_trash_id() is None + + +@pytest.mark.parametrize( + argnames=["location", "expected_raise"], + argvalues=[ + ( + DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + pytest.raises(errors.LocationIsOccupiedError), + ), + ( + ModuleLocation(moduleId="module-id"), + pytest.raises(errors.LocationIsOccupiedError), + ), + (DeckSlotLocation(slotName=DeckSlotName.SLOT_2), does_not_raise()), + (ModuleLocation(moduleId="non-matching-id"), does_not_raise()), + ], +) +def test_raise_if_labware_in_location( + location: Union[DeckSlotLocation, ModuleLocation], + expected_raise: ContextManager[Any], +) -> None: + """It should raise if there is labware in specified location.""" + subject = get_labware_view( + labware_by_id={ + "abc123": LoadedLabware( + id="abc123", + loadName="labware-1", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + definitionUri="labware-definition-uri", + offsetId=None, + displayName=None, + ), + "xyz456": LoadedLabware( + id="xyz456", + loadName="labware-2", + location=ModuleLocation(moduleId="module-id"), + definitionUri="labware-definition-uri", + offsetId=None, + displayName=None, + ), + } + ) + with expected_raise: + subject.raise_if_labware_in_location(location=location) + + +def test_get_by_slot() -> None: + """It should get the labware in a given slot.""" + labware_1 = LoadedLabware.construct( # type: ignore[call-arg] + id="1", location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1) + ) + labware_2 = LoadedLabware.construct( # type: ignore[call-arg] + id="2", location=DeckSlotLocation(slotName=DeckSlotName.SLOT_2) + ) + labware_3 = LoadedLabware.construct( # type: ignore[call-arg] + id="3", location=ModuleLocation(moduleId="cool-module") + ) + + subject = get_labware_view( + labware_by_id={"1": labware_1, "2": labware_2, "3": labware_3} + ) + + assert subject.get_by_slot(DeckSlotName.SLOT_1) == labware_1 + assert subject.get_by_slot(DeckSlotName.SLOT_2) == labware_2 + assert subject.get_by_slot(DeckSlotName.SLOT_3) is None + + +@pytest.mark.parametrize( + ["well_name", "mount", "labware_slot", "next_to_module", "expected_result"], + [ + ("abc", MountType.RIGHT, DeckSlotName.SLOT_3, False, EdgePathType.LEFT), + ("abc", MountType.RIGHT, DeckSlotName.SLOT_D3, False, EdgePathType.LEFT), + ("abc", MountType.RIGHT, DeckSlotName.SLOT_1, True, EdgePathType.LEFT), + ("abc", MountType.RIGHT, DeckSlotName.SLOT_D1, True, EdgePathType.LEFT), + ("pqr", MountType.LEFT, DeckSlotName.SLOT_3, True, EdgePathType.RIGHT), + ("pqr", MountType.LEFT, DeckSlotName.SLOT_D3, True, EdgePathType.RIGHT), + ("pqr", MountType.LEFT, DeckSlotName.SLOT_3, False, EdgePathType.DEFAULT), + ("pqr", MountType.LEFT, DeckSlotName.SLOT_D3, False, EdgePathType.DEFAULT), + ("pqr", MountType.RIGHT, DeckSlotName.SLOT_3, True, EdgePathType.DEFAULT), + ("pqr", MountType.RIGHT, DeckSlotName.SLOT_D3, True, EdgePathType.DEFAULT), + ("def", MountType.LEFT, DeckSlotName.SLOT_3, True, EdgePathType.DEFAULT), + ("def", MountType.LEFT, DeckSlotName.SLOT_D3, True, EdgePathType.DEFAULT), + ], +) +def test_get_edge_path_type( + well_name: str, + mount: MountType, + labware_slot: DeckSlotName, + next_to_module: bool, + expected_result: EdgePathType, +) -> None: + """It should get the proper edge path type based on well name, mount, and labware position.""" + labware = LoadedLabware( + id="tip-rack-id", + loadName="load-name", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + definitionUri="some-labware-uri", + offsetId=None, + ) + + labware_def = LabwareDefinition.construct( # type: ignore[call-arg] + ordering=[["abc", "def"], ["ghi", "jkl"], ["mno", "pqr"]] + ) + + subject = get_labware_view( + labware_by_id={"labware-id": labware}, + definitions_by_uri={ + "some-labware-uri": labware_def, + }, + ) + + result = subject.get_edge_path_type( + "labware-id", well_name, mount, labware_slot, next_to_module + ) + + assert result == expected_result + + +def test_get_all_labware_definition( + tip_rack_def: LabwareDefinition, falcon_tuberack_def: LabwareDefinition +) -> None: + """It should return the loaded labware definition list.""" + subject = get_labware_view( + labware_by_id={ + "labware-id": LoadedLabware( + id="labware-id", + loadName="test", + definitionUri="opentrons_96_tiprack_300ul", + location=ModuleLocation(moduleId="module-id"), + ) + }, + definitions_by_uri={ + "opentrons_96_tiprack_300ul": tip_rack_def, + "falcon-definition": falcon_tuberack_def, + }, + ) + + result = subject.get_loaded_labware_definitions() + + assert result == [tip_rack_def] + + +def test_get_all_labware_definition_empty() -> None: + """It should return an empty list.""" + subject = get_labware_view( + labware_by_id={}, + ) + + result = subject.get_loaded_labware_definitions() + + assert result == [] + + +def test_raise_if_labware_inaccessible_by_pipette_staging_area() -> None: + """It should raise if the labware is on a staging slot.""" + subject = get_labware_view( + labware_by_id={ + "labware-id": LoadedLabware( + id="labware-id", + loadName="test", + definitionUri="def-uri", + location=AddressableAreaLocation(addressableAreaName="B4"), + ) + }, + ) + + with pytest.raises( + errors.LocationNotAccessibleByPipetteError, match="on staging slot" + ): + subject.raise_if_labware_inaccessible_by_pipette("labware-id") + + +def test_raise_if_labware_inaccessible_by_pipette_off_deck() -> None: + """It should raise if the labware is off-deck.""" + subject = get_labware_view( + labware_by_id={ + "labware-id": LoadedLabware( + id="labware-id", + loadName="test", + definitionUri="def-uri", + location=OFF_DECK_LOCATION, + ) + }, + ) + + with pytest.raises(errors.LocationNotAccessibleByPipetteError, match="off-deck"): + subject.raise_if_labware_inaccessible_by_pipette("labware-id") + + +def test_raise_if_labware_inaccessible_by_pipette_stacked_labware_on_staging_area() -> ( + None +): + """It should raise if the labware is stacked on a staging slot.""" + subject = get_labware_view( + labware_by_id={ + "labware-id": LoadedLabware( + id="labware-id", + loadName="test", + definitionUri="def-uri", + location=OnLabwareLocation(labwareId="lower-labware-id"), + ), + "lower-labware-id": LoadedLabware( + id="lower-labware-id", + loadName="test", + definitionUri="def-uri", + location=AddressableAreaLocation(addressableAreaName="B4"), + ), + }, + ) + + with pytest.raises( + errors.LocationNotAccessibleByPipetteError, match="on staging slot" + ): + subject.raise_if_labware_inaccessible_by_pipette("labware-id") + + +def test_raise_if_labware_cannot_be_stacked_is_adapter() -> None: + """It should raise if the labware trying to be stacked is an adapter.""" + subject = get_labware_view() + + with pytest.raises( + errors.LabwareCannotBeStackedError, match="defined as an adapter" + ): + subject.raise_if_labware_cannot_be_stacked( + top_labware_definition=LabwareDefinition.construct( # type: ignore[call-arg] + parameters=Parameters.construct( # type: ignore[call-arg] + loadName="name" + ), + allowedRoles=[LabwareRole.adapter], + ), + bottom_labware_id="labware-id", + ) + + +def test_raise_if_labware_cannot_be_stacked_not_validated() -> None: + """It should raise if the labware name is not in the definition stacking overlap.""" + subject = get_labware_view( + labware_by_id={ + "labware-id": LoadedLabware( + id="labware-id", + loadName="test", + definitionUri="def-uri", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ) + }, + ) + + with pytest.raises( + errors.LabwareCannotBeStackedError, match="loaded onto labware test" + ): + subject.raise_if_labware_cannot_be_stacked( + top_labware_definition=LabwareDefinition.construct( # type: ignore[call-arg] + parameters=Parameters.construct( # type: ignore[call-arg] + loadName="name" + ), + stackingOffsetWithLabware={}, + ), + bottom_labware_id="labware-id", + ) + + +def test_raise_if_labware_cannot_be_stacked_on_module_not_adapter() -> None: + """It should raise if the below labware on a module is not an adapter.""" + subject = get_labware_view( + labware_by_id={ + "labware-id": LoadedLabware( + id="labware-id", + loadName="test", + definitionUri="def-uri", + location=ModuleLocation(moduleId="module-id"), + ) + }, + definitions_by_uri={ + "def-uri": LabwareDefinition.construct( # type: ignore[call-arg] + allowedRoles=[LabwareRole.labware] + ) + }, + ) + + with pytest.raises(errors.LabwareCannotBeStackedError, match="module"): + subject.raise_if_labware_cannot_be_stacked( + top_labware_definition=LabwareDefinition.construct( # type: ignore[call-arg] + parameters=Parameters.construct( # type: ignore[call-arg] + loadName="name" + ), + stackingOffsetWithLabware={ + "test": SharedDataOverlapOffset(x=0, y=0, z=0) + }, + ), + bottom_labware_id="labware-id", + ) + + +def test_raise_if_labware_cannot_be_stacked_on_labware_on_adapter() -> None: + """It should raise if the OnLabware location is on an adapter.""" + subject = get_labware_view( + labware_by_id={ + "labware-id": LoadedLabware( + id="labware-id", + loadName="test", + definitionUri="def-uri-1", + location=OnLabwareLocation(labwareId="below-id"), + ), + "below-id": LoadedLabware( + id="below-id", + loadName="adapter-name", + definitionUri="def-uri-2", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + }, + definitions_by_uri={ + "def-uri-1": LabwareDefinition.construct( # type: ignore[call-arg] + allowedRoles=[LabwareRole.labware] + ), + "def-uri-2": LabwareDefinition.construct( # type: ignore[call-arg] + allowedRoles=[LabwareRole.adapter] + ), + }, + ) + + with pytest.raises( + errors.LabwareCannotBeStackedError, match="cannot be loaded to stack" + ): + subject.raise_if_labware_cannot_be_stacked( + top_labware_definition=LabwareDefinition.construct( # type: ignore[call-arg] + parameters=Parameters.construct( # type: ignore[call-arg] + loadName="name" + ), + stackingOffsetWithLabware={ + "test": SharedDataOverlapOffset(x=0, y=0, z=0) + }, + ), + bottom_labware_id="labware-id", + ) + + +@pytest.mark.parametrize( + argnames=[ + "allowed_roles", + "stacking_quirks", + "exception", + ], + argvalues=[ + [ + [LabwareRole.labware], + [], + pytest.raises(errors.LabwareCannotBeStackedError), + ], + [ + [LabwareRole.lid], + ["stackingMaxFive"], + does_not_raise(), + ], + ], +) +def test_labware_stacking_height_passes_or_raises( + allowed_roles: List[LabwareRole], + stacking_quirks: List[str], + exception: ContextManager[None], +) -> None: + """It should raise if the labware is stacked too high, and pass if the labware definition allowed this.""" + subject = get_labware_view( + labware_by_id={ + "labware-id4": LoadedLabware( + id="labware-id4", + loadName="test", + definitionUri="def-uri-1", + location=OnLabwareLocation(labwareId="labware-id3"), + ), + "labware-id3": LoadedLabware( + id="labware-id3", + loadName="test", + definitionUri="def-uri-1", + location=OnLabwareLocation(labwareId="labware-id2"), + ), + "labware-id2": LoadedLabware( + id="labware-id2", + loadName="test", + definitionUri="def-uri-1", + location=OnLabwareLocation(labwareId="labware-id1"), + ), + "labware-id1": LoadedLabware( + id="labware-id1", + loadName="test", + definitionUri="def-uri-1", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + }, + definitions_by_uri={ + "def-uri-1": LabwareDefinition.construct( # type: ignore[call-arg] + allowedRoles=allowed_roles, + parameters=Parameters.construct( + format="irregular", + quirks=stacking_quirks, + isTiprack=False, + loadName="name", + isMagneticModuleCompatible=False, + ), + ) + }, + ) + + with exception: + subject.raise_if_labware_cannot_be_stacked( + top_labware_definition=LabwareDefinition.construct( # type: ignore[call-arg] + parameters=Parameters.construct( + format="irregular", + quirks=stacking_quirks, + isTiprack=False, + loadName="name", + isMagneticModuleCompatible=False, + ), + stackingOffsetWithLabware={ + "test": SharedDataOverlapOffset(x=0, y=0, z=0) + }, + ), + bottom_labware_id="labware-id4", + ) + + +def test_get_deck_gripper_offsets(ot3_standard_deck_def: DeckDefinitionV5) -> None: + """It should get the deck's gripper offsets.""" + subject = get_labware_view(deck_definition=ot3_standard_deck_def) + + assert subject.get_deck_default_gripper_offsets() == LabwareMovementOffsetData( + pickUpOffset=LabwareOffsetVector(x=0, y=0, z=0), + dropOffset=LabwareOffsetVector(x=0, y=0, z=-0.75), + ) + + +def test_get_labware_gripper_offsets( + well_plate_def: LabwareDefinition, + adapter_plate_def: LabwareDefinition, +) -> None: + """It should get the labware's gripper offsets.""" + subject = get_labware_view( + labware_by_id={"plate-id": plate, "adapter-plate-id": adapter_plate}, + definitions_by_uri={ + "some-plate-uri": well_plate_def, + "some-adapter-uri": adapter_plate_def, + }, + ) + + assert ( + subject.get_child_gripper_offsets(labware_id="plate-id", slot_name=None) is None + ) + assert subject.get_child_gripper_offsets( + labware_id="adapter-plate-id", slot_name=DeckSlotName.SLOT_D1 + ) == LabwareMovementOffsetData( + pickUpOffset=LabwareOffsetVector(x=0, y=0, z=0), + dropOffset=LabwareOffsetVector(x=2, y=0, z=0), + ) + + +def test_get_labware_gripper_offsets_default_no_slots( + well_plate_def: LabwareDefinition, + adapter_plate_def: LabwareDefinition, +) -> None: + """It should get the labware's gripper offsets with only a default gripper offset entry.""" + subject = get_labware_view( + labware_by_id={ + "labware-id": LoadedLabware( + id="labware-id", + loadName="labware-load-name", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + definitionUri="some-labware-uri", + offsetId=None, + displayName="Fancy Labware Name", + ) + }, + definitions_by_uri={ + "some-labware-uri": LabwareDefinition.construct( # type: ignore[call-arg] + gripperOffsets={ + "default": GripperOffsets( + pickUpOffset=OffsetVector(x=1, y=2, z=3), + dropOffset=OffsetVector(x=4, y=5, z=6), + ) + } + ), + }, + ) + + assert ( + subject.get_child_gripper_offsets( + labware_id="labware-id", slot_name=DeckSlotName.SLOT_D1 + ) + is None + ) + + assert subject.get_child_gripper_offsets( + labware_id="labware-id", slot_name=None + ) == LabwareMovementOffsetData( + pickUpOffset=LabwareOffsetVector(x=1, y=2, z=3), + dropOffset=LabwareOffsetVector(x=4, y=5, z=6), + ) + + +def test_get_grip_force( + flex_50uL_tiprack: LabwareDefinition, + reservoir_def: LabwareDefinition, +) -> None: + """It should get the grip force, if present, from labware definition or return default.""" + subject = get_labware_view() + + assert subject.get_grip_force(flex_50uL_tiprack) == 16 # from definition + assert subject.get_grip_force(reservoir_def) == 15 # default + + +def test_get_grip_height_from_labware_bottom( + well_plate_def: LabwareDefinition, + reservoir_def: LabwareDefinition, +) -> None: + """It should get the grip height, if present, from labware definition or return default.""" + subject = get_labware_view() + assert ( + subject.get_grip_height_from_labware_bottom(well_plate_def) == 12.2 + ) # from definition + assert subject.get_grip_height_from_labware_bottom(reservoir_def) == 15.7 # default + + +@pytest.mark.parametrize( + "labware_to_check,well_bbox", + [ + ("opentrons_universal_flat_adapter", Dimensions(0, 0, 0)), + ( + "corning_96_wellplate_360ul_flat", + Dimensions(116.81 - 10.95, 77.67 - 7.81, 14.22), + ), + ("nest_12_reservoir_15ml", Dimensions(117.48 - 10.28, 78.38 - 7.18, 31.4)), + ], +) +def test_calculates_well_bounding_box( + labware_to_check: str, well_bbox: Dimensions +) -> None: + """It should be able to calculate well bounding boxes.""" + definition = LabwareDefinition.parse_obj(load_definition(labware_to_check, 1)) + subject = get_labware_view() + assert subject.get_well_bbox(definition).x == pytest.approx(well_bbox.x) + assert subject.get_well_bbox(definition).y == pytest.approx(well_bbox.y) + assert subject.get_well_bbox(definition).z == pytest.approx(well_bbox.z) diff --git a/api/tests/opentrons/protocol_engine/state/test_liquid_class_store_old.py b/api/tests/opentrons/protocol_engine/state/test_liquid_class_store_old.py new file mode 100644 index 00000000000..57397ec61cb --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_liquid_class_store_old.py @@ -0,0 +1,64 @@ +"""Liquid state store tests. + +DEPRECATED: Testing LiquidClassStore independently of LiquidClassView is no +longer helpful. Try to add new tests to test_liquid_class_state.py, where they can be +tested together, treating LiquidClassState as a private implementation detail. +""" +import pytest + +from opentrons_shared_data.liquid_classes.liquid_class_definition import ( + LiquidClassSchemaV1, +) +from opentrons.protocol_engine import actions +from opentrons.protocol_engine.commands.load_liquid_class import LoadLiquidClass +from opentrons.protocol_engine.state import update_types +from opentrons.protocol_engine.state.liquid_classes import LiquidClassStore +from opentrons.protocol_engine.types import LiquidClassRecord + + +@pytest.fixture +def subject() -> LiquidClassStore: + """The LiquidClassStore test subject.""" + return LiquidClassStore() + + +def test_handles_add_liquid_class( + subject: LiquidClassStore, minimal_liquid_class_def2: LiquidClassSchemaV1 +) -> None: + """Should add the LiquidClassRecord to the store.""" + pipette_0 = minimal_liquid_class_def2.byPipette[0] + by_tip_type_0 = pipette_0.byTipType[0] + liquid_class_record = LiquidClassRecord( + liquidClassName=minimal_liquid_class_def2.liquidClassName, + pipetteModel=pipette_0.pipetteModel, + tiprack=by_tip_type_0.tiprack, + aspirate=by_tip_type_0.aspirate, + singleDispense=by_tip_type_0.singleDispense, + multiDispense=by_tip_type_0.multiDispense, + ) + + subject.handle_action( + actions.SucceedCommandAction( + command=LoadLiquidClass.construct(), # type: ignore[call-arg] + state_update=update_types.StateUpdate( + liquid_class_loaded=update_types.LiquidClassLoadedUpdate( + liquid_class_id="liquid-class-id", + liquid_class_record=liquid_class_record, + ), + ), + ) + ) + + assert len(subject.state.liquid_class_record_by_id) == 1 + assert ( + subject.state.liquid_class_record_by_id["liquid-class-id"] + == liquid_class_record + ) + + assert len(subject.state.liquid_class_record_to_id) == 1 + # Make sure that LiquidClassRecords are hashable, and that we can query for LiquidClassRecords by value: + assert ( + subject.state.liquid_class_record_to_id[liquid_class_record] + == "liquid-class-id" + ) + # If this fails with an error like "TypeError: unhashable type: AspirateProperties", then you broke something. diff --git a/api/tests/opentrons/protocol_engine/state/test_liquid_class_view_old.py b/api/tests/opentrons/protocol_engine/state/test_liquid_class_view_old.py new file mode 100644 index 00000000000..f28438b3756 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_liquid_class_view_old.py @@ -0,0 +1,67 @@ +"""Liquid view tests. + +DEPRECATED: Testing LiquidClassView independently of LiquidClassStore is no +longer helpful. Try to add new tests to test_liquid_class_state.py, where they can be +tested together, treating LiquidClassState as a private implementation detail. +""" +import pytest + +from opentrons_shared_data.liquid_classes.liquid_class_definition import ( + LiquidClassSchemaV1, +) + +from opentrons.protocol_engine.state.liquid_classes import ( + LiquidClassState, + LiquidClassView, +) +from opentrons.protocol_engine.types import LiquidClassRecord + + +@pytest.fixture +def liquid_class_record( + minimal_liquid_class_def2: LiquidClassSchemaV1, +) -> LiquidClassRecord: + """An example LiquidClassRecord for tests.""" + pipette_0 = minimal_liquid_class_def2.byPipette[0] + by_tip_type_0 = pipette_0.byTipType[0] + return LiquidClassRecord( + liquidClassName=minimal_liquid_class_def2.liquidClassName, + pipetteModel=pipette_0.pipetteModel, + tiprack=by_tip_type_0.tiprack, + aspirate=by_tip_type_0.aspirate, + singleDispense=by_tip_type_0.singleDispense, + multiDispense=by_tip_type_0.multiDispense, + ) + + +@pytest.fixture +def subject(liquid_class_record: LiquidClassRecord) -> LiquidClassView: + """The LiquidClassView test subject.""" + state = LiquidClassState( + liquid_class_record_by_id={"liquid-class-id": liquid_class_record}, + liquid_class_record_to_id={liquid_class_record: "liquid-class-id"}, + ) + return LiquidClassView(state) + + +def test_get_by_id( + subject: LiquidClassView, liquid_class_record: LiquidClassRecord +) -> None: + """Should look up LiquidClassRecord by ID.""" + assert subject.get("liquid-class-id") == liquid_class_record + + +def test_get_by_liquid_class_record( + subject: LiquidClassView, liquid_class_record: LiquidClassRecord +) -> None: + """Should look up existing ID given a LiquidClassRecord.""" + assert ( + subject.get_id_for_liquid_class_record(liquid_class_record) == "liquid-class-id" + ) + + +def test_get_all( + subject: LiquidClassView, liquid_class_record: LiquidClassRecord +) -> None: + """Should get all LiquidClassRecords in the store.""" + assert subject.get_all() == {"liquid-class-id": liquid_class_record} diff --git a/api/tests/opentrons/protocol_engine/state/test_liquid_store.py b/api/tests/opentrons/protocol_engine/state/test_liquid_store.py deleted file mode 100644 index fe21b43193e..00000000000 --- a/api/tests/opentrons/protocol_engine/state/test_liquid_store.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Liquid state store tests.""" -import pytest -from opentrons.protocol_engine.state.liquids import LiquidStore -from opentrons.protocol_engine import Liquid -from opentrons.protocol_engine.actions.actions import AddLiquidAction - - -@pytest.fixture -def subject() -> LiquidStore: - """Liquid store test subject.""" - return LiquidStore() - - -def test_handles_add_liquid(subject: LiquidStore) -> None: - """It should add the liquid to the state.""" - expected_liquid = Liquid( - id="water-id", displayName="water", description="water-desc" - ) - subject.handle_action( - AddLiquidAction( - Liquid(id="water-id", displayName="water", description="water-desc") - ) - ) - - assert len(subject.state.liquids_by_id) == 1 - - assert subject.state.liquids_by_id["water-id"] == expected_liquid diff --git a/api/tests/opentrons/protocol_engine/state/test_liquid_store_old.py b/api/tests/opentrons/protocol_engine/state/test_liquid_store_old.py new file mode 100644 index 00000000000..18417fe7570 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_liquid_store_old.py @@ -0,0 +1,32 @@ +"""Liquid state store tests. + +DEPRECATED: Testing LiquidStore independently of LiquidView is no longer helpful. +Try to add new tests to test_liquid_state.py, where they can be tested together, +treating LiquidState as a private implementation detail. +""" +import pytest +from opentrons.protocol_engine.state.liquids import LiquidStore +from opentrons.protocol_engine import Liquid +from opentrons.protocol_engine.actions.actions import AddLiquidAction + + +@pytest.fixture +def subject() -> LiquidStore: + """Liquid store test subject.""" + return LiquidStore() + + +def test_handles_add_liquid(subject: LiquidStore) -> None: + """It should add the liquid to the state.""" + expected_liquid = Liquid( + id="water-id", displayName="water", description="water-desc" + ) + subject.handle_action( + AddLiquidAction( + Liquid(id="water-id", displayName="water", description="water-desc") + ) + ) + + assert len(subject.state.liquids_by_id) == 1 + + assert subject.state.liquids_by_id["water-id"] == expected_liquid diff --git a/api/tests/opentrons/protocol_engine/state/test_liquid_view.py b/api/tests/opentrons/protocol_engine/state/test_liquid_view.py deleted file mode 100644 index f3424932b0e..00000000000 --- a/api/tests/opentrons/protocol_engine/state/test_liquid_view.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Liquid view tests.""" -import pytest - -from opentrons.protocol_engine.state.liquids import LiquidState, LiquidView -from opentrons.protocol_engine import Liquid -from opentrons.protocol_engine.errors import LiquidDoesNotExistError - - -@pytest.fixture -def subject() -> LiquidView: - """Get a liquid view test subject.""" - state = LiquidState( - liquids_by_id={ - "water-id": Liquid( - id="water-id", displayName="water", description="water desc" - ) - } - ) - - return LiquidView(state) - - -def test_get_all(subject: LiquidView) -> None: - """Should return a list of liquids.""" - assert subject.get_all() == [ - Liquid(id="water-id", displayName="water", description="water desc") - ] - - -def test_has_liquid(subject: LiquidView) -> None: - """Should return true if an item exists in the liquids list.""" - assert subject.validate_liquid_id("water-id") == "water-id" - - with pytest.raises(LiquidDoesNotExistError): - subject.validate_liquid_id("no-id") diff --git a/api/tests/opentrons/protocol_engine/state/test_liquid_view_old.py b/api/tests/opentrons/protocol_engine/state/test_liquid_view_old.py new file mode 100644 index 00000000000..92a9a2bd667 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_liquid_view_old.py @@ -0,0 +1,59 @@ +"""Liquid view tests. + +DEPRECATED: Testing LiquidView independently of LiquidStore is no longer helpful. +Try to add new tests to test_liquid_state.py, where they can be tested together, +treating LiquidState as a private implementation detail. +""" +import pytest + +from opentrons.protocol_engine.state.liquids import LiquidState, LiquidView +from opentrons.protocol_engine import Liquid +from opentrons.protocol_engine.errors import LiquidDoesNotExistError, InvalidLiquidError + + +@pytest.fixture +def subject() -> LiquidView: + """Get a liquid view test subject.""" + state = LiquidState( + liquids_by_id={ + "water-id": Liquid( + id="water-id", displayName="water", description="water desc" + ) + } + ) + + return LiquidView(state) + + +def test_get_all(subject: LiquidView) -> None: + """Should return a list of liquids.""" + assert subject.get_all() == [ + Liquid(id="water-id", displayName="water", description="water desc") + ] + + +def test_has_liquid(subject: LiquidView) -> None: + """Should return true if an item exists in the liquids list.""" + assert subject.validate_liquid_id("water-id") == "water-id" + + with pytest.raises(LiquidDoesNotExistError): + subject.validate_liquid_id("no-id") + + +def test_validate_liquid_prevents_empty(subject: LiquidView) -> None: + """It should not allow loading a liquid with the special id EMPTY.""" + with pytest.raises(InvalidLiquidError): + subject.validate_liquid_allowed( + Liquid(id="EMPTY", displayName="empty", description="nothing") + ) + + +def test_validate_liquid_allows_non_empty(subject: LiquidView) -> None: + """It should allow a valid liquid.""" + valid_liquid = Liquid( + id="some-id", + displayName="some-display-name", + description="some-description", + displayColor=None, + ) + assert subject.validate_liquid_allowed(valid_liquid) == valid_liquid diff --git a/api/tests/opentrons/protocol_engine/state/test_module_store.py b/api/tests/opentrons/protocol_engine/state/test_module_store.py deleted file mode 100644 index 5a26fc97d1a..00000000000 --- a/api/tests/opentrons/protocol_engine/state/test_module_store.py +++ /dev/null @@ -1,756 +0,0 @@ -"""Module state store tests.""" -from typing import List, Set, cast, Dict, Optional - -import pytest -from opentrons_shared_data.robot.types import RobotType -from opentrons_shared_data.deck.types import DeckDefinitionV5 -from pytest_lazyfixture import lazy_fixture # type: ignore[import-untyped] - -from opentrons.types import DeckSlotName -from opentrons.protocol_engine import commands, actions -from opentrons.protocol_engine.commands import ( - heater_shaker as hs_commands, - temperature_module as temp_commands, - thermocycler as tc_commands, -) -from opentrons.protocol_engine.types import ( - DeckSlotLocation, - ModuleDefinition, - ModuleModel, - HeaterShakerLatchStatus, - DeckType, - AddressableArea, - DeckConfigurationType, - PotentialCutoutFixture, -) - -from opentrons.protocol_engine.state.modules import ( - ModuleStore, - ModuleState, - HardwareModule, -) - -from opentrons.protocol_engine.state.module_substates import ( - MagneticModuleId, - MagneticModuleSubState, - HeaterShakerModuleId, - HeaterShakerModuleSubState, - TemperatureModuleId, - TemperatureModuleSubState, - ThermocyclerModuleId, - ThermocyclerModuleSubState, - ModuleSubStateType, -) - -from opentrons.protocol_engine.state.addressable_areas import ( - AddressableAreaView, - AddressableAreaState, -) -from opentrons.protocol_engine.state.config import Config -from opentrons.hardware_control.modules.types import LiveData - - -_OT2_STANDARD_CONFIG = Config( - use_simulated_deck_config=False, - robot_type="OT-2 Standard", - deck_type=DeckType.OT2_STANDARD, -) - - -def get_addressable_area_view( - loaded_addressable_areas_by_name: Optional[Dict[str, AddressableArea]] = None, - potential_cutout_fixtures_by_cutout_id: Optional[ - Dict[str, Set[PotentialCutoutFixture]] - ] = None, - deck_definition: Optional[DeckDefinitionV5] = None, - deck_configuration: Optional[DeckConfigurationType] = None, - robot_type: RobotType = "OT-3 Standard", - use_simulated_deck_config: bool = False, -) -> AddressableAreaView: - """Get a labware view test subject.""" - state = AddressableAreaState( - loaded_addressable_areas_by_name=loaded_addressable_areas_by_name or {}, - potential_cutout_fixtures_by_cutout_id=potential_cutout_fixtures_by_cutout_id - or {}, - deck_definition=deck_definition or cast(DeckDefinitionV5, {"otId": "fake"}), - deck_configuration=deck_configuration or [], - robot_definition={ - "displayName": "OT-3", - "robotType": "OT-3 Standard", - "models": ["OT-3 Standard"], - "extents": [477.2, 493.8, 0.0], - "mountOffsets": { - "left": [-13.5, -60.5, 255.675], - "right": [40.5, -60.5, 255.675], - "gripper": [84.55, -12.75, 93.85], - }, - }, - robot_type=robot_type, - use_simulated_deck_config=use_simulated_deck_config, - ) - - return AddressableAreaView(state=state) - - -def test_initial_state() -> None: - """It should initialize the module state.""" - subject = ModuleStore( - config=_OT2_STANDARD_CONFIG, - deck_fixed_labware=[], - ) - - assert subject.state == ModuleState( - deck_type=DeckType.OT2_STANDARD, - requested_model_by_id={}, - slot_by_module_id={}, - hardware_by_module_id={}, - substate_by_module_id={}, - module_offset_by_serial={}, - additional_slots_occupied_by_module_id={}, - deck_fixed_labware=[], - ) - - -@pytest.mark.parametrize( - argnames=["module_definition", "params_model", "result_model", "expected_substate"], - argvalues=[ - ( - lazy_fixture("magdeck_v2_def"), - ModuleModel.MAGNETIC_MODULE_V2, - ModuleModel.MAGNETIC_MODULE_V2, - MagneticModuleSubState( - module_id=MagneticModuleId("module-id"), - model=ModuleModel.MAGNETIC_MODULE_V2, - ), - ), - ( - lazy_fixture("heater_shaker_v1_def"), - ModuleModel.HEATER_SHAKER_MODULE_V1, - ModuleModel.HEATER_SHAKER_MODULE_V1, - HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=False, - plate_target_temperature=None, - ), - ), - ( - lazy_fixture("tempdeck_v1_def"), - ModuleModel.TEMPERATURE_MODULE_V1, - ModuleModel.TEMPERATURE_MODULE_V1, - TemperatureModuleSubState( - module_id=TemperatureModuleId("module-id"), - plate_target_temperature=None, - ), - ), - ( - lazy_fixture("tempdeck_v1_def"), - ModuleModel.TEMPERATURE_MODULE_V2, - ModuleModel.TEMPERATURE_MODULE_V1, - TemperatureModuleSubState( - module_id=TemperatureModuleId("module-id"), - plate_target_temperature=None, - ), - ), - ( - lazy_fixture("tempdeck_v2_def"), - ModuleModel.TEMPERATURE_MODULE_V1, - ModuleModel.TEMPERATURE_MODULE_V2, - TemperatureModuleSubState( - module_id=TemperatureModuleId("module-id"), - plate_target_temperature=None, - ), - ), - ( - lazy_fixture("tempdeck_v2_def"), - ModuleModel.TEMPERATURE_MODULE_V2, - ModuleModel.TEMPERATURE_MODULE_V2, - TemperatureModuleSubState( - module_id=TemperatureModuleId("module-id"), - plate_target_temperature=None, - ), - ), - ( - lazy_fixture("thermocycler_v1_def"), - ModuleModel.THERMOCYCLER_MODULE_V1, - ModuleModel.THERMOCYCLER_MODULE_V1, - ThermocyclerModuleSubState( - module_id=ThermocyclerModuleId("module-id"), - is_lid_open=False, - target_block_temperature=None, - target_lid_temperature=None, - ), - ), - ], -) -def test_load_module( - module_definition: ModuleDefinition, - params_model: ModuleModel, - result_model: ModuleModel, - expected_substate: ModuleSubStateType, -) -> None: - """It should handle a successful LoadModule command.""" - action = actions.SucceedCommandAction( - private_result=None, - command=commands.LoadModule.construct( # type: ignore[call-arg] - params=commands.LoadModuleParams( - model=params_model, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - ), - result=commands.LoadModuleResult( - moduleId="module-id", - model=result_model, - serialNumber="serial-number", - definition=module_definition, - ), - ), - ) - - subject = ModuleStore( - config=_OT2_STANDARD_CONFIG, - deck_fixed_labware=[], - ) - subject.handle_action(action) - - assert subject.state == ModuleState( - deck_type=DeckType.OT2_STANDARD, - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - requested_model_by_id={"module-id": params_model}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=module_definition, - ) - }, - substate_by_module_id={"module-id": expected_substate}, - module_offset_by_serial={}, - additional_slots_occupied_by_module_id={}, - deck_fixed_labware=[], - ) - - -@pytest.mark.parametrize( - argnames=["tc_slot", "deck_type", "robot_type", "expected_additional_slots"], - argvalues=[ - ( - DeckSlotName.SLOT_7, - DeckType.OT2_STANDARD, - "OT-2 Standard", - [DeckSlotName.SLOT_8, DeckSlotName.SLOT_10, DeckSlotName.SLOT_11], - ), - ( - DeckSlotName.SLOT_B1, - DeckType.OT3_STANDARD, - "OT-3 Standard", - [DeckSlotName.SLOT_A1], - ), - ], -) -def test_load_thermocycler_in_thermocycler_slot( - tc_slot: DeckSlotName, - deck_type: DeckType, - robot_type: RobotType, - expected_additional_slots: List[DeckSlotName], - thermocycler_v2_def: ModuleDefinition, -) -> None: - """It should update additional slots for thermocycler module.""" - action = actions.SucceedCommandAction( - private_result=None, - command=commands.LoadModule.construct( # type: ignore[call-arg] - params=commands.LoadModuleParams( - model=ModuleModel.THERMOCYCLER_MODULE_V2, - location=DeckSlotLocation(slotName=tc_slot), - ), - result=commands.LoadModuleResult( - moduleId="module-id", - model=ModuleModel.THERMOCYCLER_MODULE_V2, - serialNumber="serial-number", - definition=thermocycler_v2_def, - ), - ), - ) - - subject = ModuleStore( - Config( - use_simulated_deck_config=False, - robot_type=robot_type, - deck_type=deck_type, - ), - deck_fixed_labware=[], - ) - subject.handle_action(action) - - assert subject.state.slot_by_module_id == {"module-id": tc_slot} - assert subject.state.additional_slots_occupied_by_module_id == { - "module-id": expected_additional_slots - } - - -@pytest.mark.parametrize( - argnames=["module_definition", "live_data", "expected_substate"], - argvalues=[ - ( - lazy_fixture("magdeck_v2_def"), - {}, - MagneticModuleSubState( - module_id=MagneticModuleId("module-id"), - model=ModuleModel.MAGNETIC_MODULE_V2, - ), - ), - ( - lazy_fixture("heater_shaker_v1_def"), - { - "status": "abc", - "data": { - "labwareLatchStatus": "idle_closed", - "speedStatus": "holding at target", - "targetSpeed": 123, - "targetTemp": 123, - }, - }, - HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.CLOSED, - is_plate_shaking=True, - plate_target_temperature=123, - ), - ), - ( - lazy_fixture("tempdeck_v2_def"), - {"status": "abc", "data": {"targetTemp": 123}}, - TemperatureModuleSubState( - module_id=TemperatureModuleId("module-id"), - plate_target_temperature=123, - ), - ), - ( - lazy_fixture("thermocycler_v1_def"), - { - "status": "abc", - "data": { - "targetTemp": 123, - "lidTarget": 321, - "lid": "open", - }, - }, - ThermocyclerModuleSubState( - module_id=ThermocyclerModuleId("module-id"), - is_lid_open=True, - target_block_temperature=123, - target_lid_temperature=321, - ), - ), - ], -) -def test_add_module_action( - module_definition: ModuleDefinition, - live_data: LiveData, - expected_substate: ModuleSubStateType, -) -> None: - """It should be able to add attached modules directly into state.""" - action = actions.AddModuleAction( - module_id="module-id", - serial_number="serial-number", - definition=module_definition, - module_live_data=live_data, - ) - - subject = ModuleStore( - config=_OT2_STANDARD_CONFIG, - deck_fixed_labware=[], - ) - subject.handle_action(action) - - assert subject.state == ModuleState( - deck_type=DeckType.OT2_STANDARD, - slot_by_module_id={"module-id": None}, - requested_model_by_id={"module-id": None}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=module_definition, - ) - }, - substate_by_module_id={"module-id": expected_substate}, - module_offset_by_serial={}, - additional_slots_occupied_by_module_id={}, - deck_fixed_labware=[], - ) - - -def test_handle_hs_temperature_commands(heater_shaker_v1_def: ModuleDefinition) -> None: - """It should update `plate_target_temperature` correctly.""" - load_module_cmd = commands.LoadModule.construct( # type: ignore[call-arg] - params=commands.LoadModuleParams( - model=ModuleModel.HEATER_SHAKER_MODULE_V1, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - ), - result=commands.LoadModuleResult( - moduleId="module-id", - model=ModuleModel.HEATER_SHAKER_MODULE_V1, - serialNumber="serial-number", - definition=heater_shaker_v1_def, - ), - ) - set_temp_cmd = hs_commands.SetTargetTemperature.construct( # type: ignore[call-arg] - params=hs_commands.SetTargetTemperatureParams(moduleId="module-id", celsius=42), - result=hs_commands.SetTargetTemperatureResult(), - ) - deactivate_cmd = hs_commands.DeactivateHeater.construct( # type: ignore[call-arg] - params=hs_commands.DeactivateHeaterParams(moduleId="module-id"), - result=hs_commands.DeactivateHeaterResult(), - ) - subject = ModuleStore( - config=_OT2_STANDARD_CONFIG, - deck_fixed_labware=[], - ) - - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_module_cmd) - ) - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=set_temp_cmd) - ) - assert subject.state.substate_by_module_id == { - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=False, - plate_target_temperature=42, - ) - } - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=deactivate_cmd) - ) - assert subject.state.substate_by_module_id == { - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=False, - plate_target_temperature=None, - ) - } - - -def test_handle_hs_shake_commands(heater_shaker_v1_def: ModuleDefinition) -> None: - """It should update heater-shaker's `is_plate_shaking` correctly.""" - load_module_cmd = commands.LoadModule.construct( # type: ignore[call-arg] - params=commands.LoadModuleParams( - model=ModuleModel.HEATER_SHAKER_MODULE_V1, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - ), - result=commands.LoadModuleResult( - moduleId="module-id", - model=ModuleModel.HEATER_SHAKER_MODULE_V1, - serialNumber="serial-number", - definition=heater_shaker_v1_def, - ), - ) - set_shake_cmd = hs_commands.SetAndWaitForShakeSpeed.construct( # type: ignore[call-arg] - params=hs_commands.SetAndWaitForShakeSpeedParams(moduleId="module-id", rpm=111), - result=hs_commands.SetAndWaitForShakeSpeedResult(pipetteRetracted=False), - ) - deactivate_cmd = hs_commands.DeactivateShaker.construct( # type: ignore[call-arg] - params=hs_commands.DeactivateShakerParams(moduleId="module-id"), - result=hs_commands.DeactivateShakerResult(), - ) - subject = ModuleStore( - config=_OT2_STANDARD_CONFIG, - deck_fixed_labware=[], - ) - - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_module_cmd) - ) - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=set_shake_cmd) - ) - assert subject.state.substate_by_module_id == { - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=True, - plate_target_temperature=None, - ) - } - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=deactivate_cmd) - ) - assert subject.state.substate_by_module_id == { - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=False, - plate_target_temperature=None, - ) - } - - -def test_handle_hs_labware_latch_commands( - heater_shaker_v1_def: ModuleDefinition, -) -> None: - """It should update heater-shaker's `is_labware_latch_closed` correctly.""" - load_module_cmd = commands.LoadModule.construct( # type: ignore[call-arg] - params=commands.LoadModuleParams( - model=ModuleModel.HEATER_SHAKER_MODULE_V1, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - ), - result=commands.LoadModuleResult( - moduleId="module-id", - model=ModuleModel.HEATER_SHAKER_MODULE_V1, - serialNumber="serial-number", - definition=heater_shaker_v1_def, - ), - ) - close_latch_cmd = hs_commands.CloseLabwareLatch.construct( # type: ignore[call-arg] - params=hs_commands.CloseLabwareLatchParams(moduleId="module-id"), - result=hs_commands.CloseLabwareLatchResult(), - ) - open_latch_cmd = hs_commands.OpenLabwareLatch.construct( # type: ignore[call-arg] - params=hs_commands.OpenLabwareLatchParams(moduleId="module-id"), - result=hs_commands.OpenLabwareLatchResult(pipetteRetracted=False), - ) - subject = ModuleStore( - config=_OT2_STANDARD_CONFIG, - deck_fixed_labware=[], - ) - - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_module_cmd) - ) - assert subject.state.substate_by_module_id == { - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=False, - plate_target_temperature=None, - ) - } - - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=close_latch_cmd) - ) - assert subject.state.substate_by_module_id == { - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.CLOSED, - is_plate_shaking=False, - plate_target_temperature=None, - ) - } - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=open_latch_cmd) - ) - assert subject.state.substate_by_module_id == { - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.OPEN, - is_plate_shaking=False, - plate_target_temperature=None, - ) - } - - -def test_handle_tempdeck_temperature_commands( - tempdeck_v2_def: ModuleDefinition, -) -> None: - """It should update Tempdeck's `plate_target_temperature` correctly.""" - load_module_cmd = commands.LoadModule.construct( # type: ignore[call-arg] - params=commands.LoadModuleParams( - model=ModuleModel.TEMPERATURE_MODULE_V2, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - ), - result=commands.LoadModuleResult( - moduleId="module-id", - model=ModuleModel.TEMPERATURE_MODULE_V2, - serialNumber="serial-number", - definition=tempdeck_v2_def, - ), - ) - set_temp_cmd = temp_commands.SetTargetTemperature.construct( # type: ignore[call-arg] - params=temp_commands.SetTargetTemperatureParams( - moduleId="module-id", celsius=42.4 - ), - result=temp_commands.SetTargetTemperatureResult(targetTemperature=42), - ) - deactivate_cmd = temp_commands.DeactivateTemperature.construct( # type: ignore[call-arg] - params=temp_commands.DeactivateTemperatureParams(moduleId="module-id"), - result=temp_commands.DeactivateTemperatureResult(), - ) - subject = ModuleStore( - config=_OT2_STANDARD_CONFIG, - deck_fixed_labware=[], - ) - - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_module_cmd) - ) - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=set_temp_cmd) - ) - assert subject.state.substate_by_module_id == { - "module-id": TemperatureModuleSubState( - module_id=TemperatureModuleId("module-id"), plate_target_temperature=42 - ) - } - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=deactivate_cmd) - ) - assert subject.state.substate_by_module_id == { - "module-id": TemperatureModuleSubState( - module_id=TemperatureModuleId("module-id"), plate_target_temperature=None - ) - } - - -def test_handle_thermocycler_temperature_commands( - thermocycler_v1_def: ModuleDefinition, -) -> None: - """It should update thermocycler's temperature statuses correctly.""" - load_module_cmd = commands.LoadModule.construct( # type: ignore[call-arg] - params=commands.LoadModuleParams( - model=ModuleModel.THERMOCYCLER_MODULE_V1, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - ), - result=commands.LoadModuleResult( - moduleId="module-id", - model=ModuleModel.THERMOCYCLER_MODULE_V1, - serialNumber="serial-number", - definition=thermocycler_v1_def, - ), - ) - set_block_temp_cmd = tc_commands.SetTargetBlockTemperature.construct( # type: ignore[call-arg] - params=tc_commands.SetTargetBlockTemperatureParams( - moduleId="module-id", celsius=42.4 - ), - result=tc_commands.SetTargetBlockTemperatureResult(targetBlockTemperature=42.4), - ) - deactivate_block_cmd = tc_commands.DeactivateBlock.construct( # type: ignore[call-arg] - params=tc_commands.DeactivateBlockParams(moduleId="module-id"), - result=tc_commands.DeactivateBlockResult(), - ) - set_lid_temp_cmd = tc_commands.SetTargetLidTemperature.construct( # type: ignore[call-arg] - params=tc_commands.SetTargetLidTemperatureParams( - moduleId="module-id", celsius=35.3 - ), - result=tc_commands.SetTargetLidTemperatureResult(targetLidTemperature=35.3), - ) - deactivate_lid_cmd = tc_commands.DeactivateLid.construct( # type: ignore[call-arg] - params=tc_commands.DeactivateLidParams(moduleId="module-id"), - result=tc_commands.DeactivateLidResult(), - ) - subject = ModuleStore( - config=_OT2_STANDARD_CONFIG, - deck_fixed_labware=[], - ) - - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_module_cmd) - ) - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=set_block_temp_cmd) - ) - assert subject.state.substate_by_module_id == { - "module-id": ThermocyclerModuleSubState( - module_id=ThermocyclerModuleId("module-id"), - is_lid_open=False, - target_block_temperature=42.4, - target_lid_temperature=None, - ) - } - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=set_lid_temp_cmd) - ) - assert subject.state.substate_by_module_id == { - "module-id": ThermocyclerModuleSubState( - module_id=ThermocyclerModuleId("module-id"), - is_lid_open=False, - target_block_temperature=42.4, - target_lid_temperature=35.3, - ) - } - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=deactivate_lid_cmd) - ) - assert subject.state.substate_by_module_id == { - "module-id": ThermocyclerModuleSubState( - module_id=ThermocyclerModuleId("module-id"), - is_lid_open=False, - target_block_temperature=42.4, - target_lid_temperature=None, - ) - } - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=deactivate_block_cmd) - ) - assert subject.state.substate_by_module_id == { - "module-id": ThermocyclerModuleSubState( - module_id=ThermocyclerModuleId("module-id"), - is_lid_open=False, - target_block_temperature=None, - target_lid_temperature=None, - ) - } - - -def test_handle_thermocycler_lid_commands( - thermocycler_v1_def: ModuleDefinition, -) -> None: - """It should update thermocycler's lid status after executing lid commands.""" - load_module_cmd = commands.LoadModule.construct( # type: ignore[call-arg] - params=commands.LoadModuleParams( - model=ModuleModel.THERMOCYCLER_MODULE_V1, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - ), - result=commands.LoadModuleResult( - moduleId="module-id", - model=ModuleModel.THERMOCYCLER_MODULE_V1, - serialNumber="serial-number", - definition=thermocycler_v1_def, - ), - ) - - open_lid_cmd = tc_commands.OpenLid.construct( # type: ignore[call-arg] - params=tc_commands.OpenLidParams(moduleId="module-id"), - result=tc_commands.OpenLidResult(), - ) - close_lid_cmd = tc_commands.CloseLid.construct( # type: ignore[call-arg] - params=tc_commands.CloseLidParams(moduleId="module-id"), - result=tc_commands.CloseLidResult(), - ) - - subject = ModuleStore( - Config( - use_simulated_deck_config=False, - robot_type="OT-3 Standard", - deck_type=DeckType.OT3_STANDARD, - ), - deck_fixed_labware=[], - ) - - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_module_cmd) - ) - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=open_lid_cmd) - ) - assert subject.state.substate_by_module_id == { - "module-id": ThermocyclerModuleSubState( - module_id=ThermocyclerModuleId("module-id"), - is_lid_open=True, - target_block_temperature=None, - target_lid_temperature=None, - ) - } - - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=close_lid_cmd) - ) - assert subject.state.substate_by_module_id == { - "module-id": ThermocyclerModuleSubState( - module_id=ThermocyclerModuleId("module-id"), - is_lid_open=False, - target_block_temperature=None, - target_lid_temperature=None, - ) - } diff --git a/api/tests/opentrons/protocol_engine/state/test_module_store_old.py b/api/tests/opentrons/protocol_engine/state/test_module_store_old.py new file mode 100644 index 00000000000..e4ab52ebaf8 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_module_store_old.py @@ -0,0 +1,725 @@ +"""Module state store tests. + +DEPRECATED: Testing ModuleStore independently of ModuleView is no longer helpful. +Try to add new tests to test_module_state.py, where they can be tested together, +treating ModuleState as a private implementation detail. +""" +from typing import List, Set, cast, Dict, Optional + +import pytest +from opentrons_shared_data.robot.types import RobotType +from opentrons_shared_data.deck.types import DeckDefinitionV5 +from pytest_lazyfixture import lazy_fixture # type: ignore[import-untyped] + +from opentrons.types import DeckSlotName +from opentrons.protocol_engine import commands, actions +from opentrons.protocol_engine.commands import ( + heater_shaker as hs_commands, + temperature_module as temp_commands, + thermocycler as tc_commands, +) +from opentrons.protocol_engine.types import ( + DeckSlotLocation, + ModuleDefinition, + ModuleModel, + HeaterShakerLatchStatus, + DeckType, + AddressableArea, + DeckConfigurationType, + PotentialCutoutFixture, +) + +from opentrons.protocol_engine.state.modules import ( + ModuleStore, + ModuleState, + HardwareModule, +) + +from opentrons.protocol_engine.state.module_substates import ( + MagneticModuleId, + MagneticModuleSubState, + HeaterShakerModuleId, + HeaterShakerModuleSubState, + TemperatureModuleId, + TemperatureModuleSubState, + ThermocyclerModuleId, + ThermocyclerModuleSubState, + ModuleSubStateType, +) + +from opentrons.protocol_engine.state.addressable_areas import ( + AddressableAreaView, + AddressableAreaState, +) +from opentrons.protocol_engine.state.config import Config +from opentrons.hardware_control.modules.types import LiveData + + +_OT2_STANDARD_CONFIG = Config( + use_simulated_deck_config=False, + robot_type="OT-2 Standard", + deck_type=DeckType.OT2_STANDARD, +) + + +def get_addressable_area_view( + loaded_addressable_areas_by_name: Optional[Dict[str, AddressableArea]] = None, + potential_cutout_fixtures_by_cutout_id: Optional[ + Dict[str, Set[PotentialCutoutFixture]] + ] = None, + deck_definition: Optional[DeckDefinitionV5] = None, + deck_configuration: Optional[DeckConfigurationType] = None, + robot_type: RobotType = "OT-3 Standard", + use_simulated_deck_config: bool = False, +) -> AddressableAreaView: + """Get a labware view test subject.""" + state = AddressableAreaState( + loaded_addressable_areas_by_name=loaded_addressable_areas_by_name or {}, + potential_cutout_fixtures_by_cutout_id=potential_cutout_fixtures_by_cutout_id + or {}, + deck_definition=deck_definition or cast(DeckDefinitionV5, {"otId": "fake"}), + deck_configuration=deck_configuration or [], + robot_definition={ + "displayName": "OT-3", + "robotType": "OT-3 Standard", + "models": ["OT-3 Standard"], + "extents": [477.2, 493.8, 0.0], + "paddingOffsets": { + "rear": -177.42, + "front": 51.8, + "leftSide": 31.88, + "rightSide": -80.32, + }, + "mountOffsets": { + "left": [-13.5, -60.5, 255.675], + "right": [40.5, -60.5, 255.675], + "gripper": [84.55, -12.75, 93.85], + }, + }, + robot_type=robot_type, + use_simulated_deck_config=use_simulated_deck_config, + ) + + return AddressableAreaView(state=state) + + +def test_initial_state() -> None: + """It should initialize the module state.""" + subject = ModuleStore( + config=_OT2_STANDARD_CONFIG, + deck_fixed_labware=[], + ) + + assert subject.state == ModuleState( + deck_type=DeckType.OT2_STANDARD, + requested_model_by_id={}, + slot_by_module_id={}, + hardware_by_module_id={}, + substate_by_module_id={}, + module_offset_by_serial={}, + additional_slots_occupied_by_module_id={}, + deck_fixed_labware=[], + ) + + +@pytest.mark.parametrize( + argnames=["module_definition", "params_model", "result_model", "expected_substate"], + argvalues=[ + ( + lazy_fixture("magdeck_v2_def"), + ModuleModel.MAGNETIC_MODULE_V2, + ModuleModel.MAGNETIC_MODULE_V2, + MagneticModuleSubState( + module_id=MagneticModuleId("module-id"), + model=ModuleModel.MAGNETIC_MODULE_V2, + ), + ), + ( + lazy_fixture("heater_shaker_v1_def"), + ModuleModel.HEATER_SHAKER_MODULE_V1, + ModuleModel.HEATER_SHAKER_MODULE_V1, + HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=False, + plate_target_temperature=None, + ), + ), + ( + lazy_fixture("tempdeck_v1_def"), + ModuleModel.TEMPERATURE_MODULE_V1, + ModuleModel.TEMPERATURE_MODULE_V1, + TemperatureModuleSubState( + module_id=TemperatureModuleId("module-id"), + plate_target_temperature=None, + ), + ), + ( + lazy_fixture("tempdeck_v1_def"), + ModuleModel.TEMPERATURE_MODULE_V2, + ModuleModel.TEMPERATURE_MODULE_V1, + TemperatureModuleSubState( + module_id=TemperatureModuleId("module-id"), + plate_target_temperature=None, + ), + ), + ( + lazy_fixture("tempdeck_v2_def"), + ModuleModel.TEMPERATURE_MODULE_V1, + ModuleModel.TEMPERATURE_MODULE_V2, + TemperatureModuleSubState( + module_id=TemperatureModuleId("module-id"), + plate_target_temperature=None, + ), + ), + ( + lazy_fixture("tempdeck_v2_def"), + ModuleModel.TEMPERATURE_MODULE_V2, + ModuleModel.TEMPERATURE_MODULE_V2, + TemperatureModuleSubState( + module_id=TemperatureModuleId("module-id"), + plate_target_temperature=None, + ), + ), + ( + lazy_fixture("thermocycler_v1_def"), + ModuleModel.THERMOCYCLER_MODULE_V1, + ModuleModel.THERMOCYCLER_MODULE_V1, + ThermocyclerModuleSubState( + module_id=ThermocyclerModuleId("module-id"), + is_lid_open=False, + target_block_temperature=None, + target_lid_temperature=None, + ), + ), + ], +) +def test_load_module( + module_definition: ModuleDefinition, + params_model: ModuleModel, + result_model: ModuleModel, + expected_substate: ModuleSubStateType, +) -> None: + """It should handle a successful LoadModule command.""" + action = actions.SucceedCommandAction( + command=commands.LoadModule.construct( # type: ignore[call-arg] + params=commands.LoadModuleParams( + model=params_model, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + result=commands.LoadModuleResult( + moduleId="module-id", + model=result_model, + serialNumber="serial-number", + definition=module_definition, + ), + ), + ) + + subject = ModuleStore( + config=_OT2_STANDARD_CONFIG, + deck_fixed_labware=[], + ) + subject.handle_action(action) + + assert subject.state == ModuleState( + deck_type=DeckType.OT2_STANDARD, + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + requested_model_by_id={"module-id": params_model}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=module_definition, + ) + }, + substate_by_module_id={"module-id": expected_substate}, + module_offset_by_serial={}, + additional_slots_occupied_by_module_id={}, + deck_fixed_labware=[], + ) + + +@pytest.mark.parametrize( + argnames=["tc_slot", "deck_type", "robot_type", "expected_additional_slots"], + argvalues=[ + ( + DeckSlotName.SLOT_7, + DeckType.OT2_STANDARD, + "OT-2 Standard", + [DeckSlotName.SLOT_8, DeckSlotName.SLOT_10, DeckSlotName.SLOT_11], + ), + ( + DeckSlotName.SLOT_B1, + DeckType.OT3_STANDARD, + "OT-3 Standard", + [DeckSlotName.SLOT_A1], + ), + ], +) +def test_load_thermocycler_in_thermocycler_slot( + tc_slot: DeckSlotName, + deck_type: DeckType, + robot_type: RobotType, + expected_additional_slots: List[DeckSlotName], + thermocycler_v2_def: ModuleDefinition, +) -> None: + """It should update additional slots for thermocycler module.""" + action = actions.SucceedCommandAction( + command=commands.LoadModule.construct( # type: ignore[call-arg] + params=commands.LoadModuleParams( + model=ModuleModel.THERMOCYCLER_MODULE_V2, + location=DeckSlotLocation(slotName=tc_slot), + ), + result=commands.LoadModuleResult( + moduleId="module-id", + model=ModuleModel.THERMOCYCLER_MODULE_V2, + serialNumber="serial-number", + definition=thermocycler_v2_def, + ), + ), + ) + + subject = ModuleStore( + Config( + use_simulated_deck_config=False, + robot_type=robot_type, + deck_type=deck_type, + ), + deck_fixed_labware=[], + ) + subject.handle_action(action) + + assert subject.state.slot_by_module_id == {"module-id": tc_slot} + assert subject.state.additional_slots_occupied_by_module_id == { + "module-id": expected_additional_slots + } + + +@pytest.mark.parametrize( + argnames=["module_definition", "live_data", "expected_substate"], + argvalues=[ + ( + lazy_fixture("magdeck_v2_def"), + {}, + MagneticModuleSubState( + module_id=MagneticModuleId("module-id"), + model=ModuleModel.MAGNETIC_MODULE_V2, + ), + ), + ( + lazy_fixture("heater_shaker_v1_def"), + { + "status": "abc", + "data": { + "labwareLatchStatus": "idle_closed", + "speedStatus": "holding at target", + "targetSpeed": 123, + "targetTemp": 123, + }, + }, + HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.CLOSED, + is_plate_shaking=True, + plate_target_temperature=123, + ), + ), + ( + lazy_fixture("tempdeck_v2_def"), + {"status": "abc", "data": {"targetTemp": 123}}, + TemperatureModuleSubState( + module_id=TemperatureModuleId("module-id"), + plate_target_temperature=123, + ), + ), + ( + lazy_fixture("thermocycler_v1_def"), + { + "status": "abc", + "data": { + "targetTemp": 123, + "lidTarget": 321, + "lid": "open", + }, + }, + ThermocyclerModuleSubState( + module_id=ThermocyclerModuleId("module-id"), + is_lid_open=True, + target_block_temperature=123, + target_lid_temperature=321, + ), + ), + ], +) +def test_add_module_action( + module_definition: ModuleDefinition, + live_data: LiveData, + expected_substate: ModuleSubStateType, +) -> None: + """It should be able to add attached modules directly into state.""" + action = actions.AddModuleAction( + module_id="module-id", + serial_number="serial-number", + definition=module_definition, + module_live_data=live_data, + ) + + subject = ModuleStore( + config=_OT2_STANDARD_CONFIG, + deck_fixed_labware=[], + ) + subject.handle_action(action) + + assert subject.state == ModuleState( + deck_type=DeckType.OT2_STANDARD, + slot_by_module_id={"module-id": None}, + requested_model_by_id={"module-id": None}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=module_definition, + ) + }, + substate_by_module_id={"module-id": expected_substate}, + module_offset_by_serial={}, + additional_slots_occupied_by_module_id={}, + deck_fixed_labware=[], + ) + + +def test_handle_hs_temperature_commands(heater_shaker_v1_def: ModuleDefinition) -> None: + """It should update `plate_target_temperature` correctly.""" + load_module_cmd = commands.LoadModule.construct( # type: ignore[call-arg] + params=commands.LoadModuleParams( + model=ModuleModel.HEATER_SHAKER_MODULE_V1, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + result=commands.LoadModuleResult( + moduleId="module-id", + model=ModuleModel.HEATER_SHAKER_MODULE_V1, + serialNumber="serial-number", + definition=heater_shaker_v1_def, + ), + ) + set_temp_cmd = hs_commands.SetTargetTemperature.construct( # type: ignore[call-arg] + params=hs_commands.SetTargetTemperatureParams(moduleId="module-id", celsius=42), + result=hs_commands.SetTargetTemperatureResult(), + ) + deactivate_cmd = hs_commands.DeactivateHeater.construct( # type: ignore[call-arg] + params=hs_commands.DeactivateHeaterParams(moduleId="module-id"), + result=hs_commands.DeactivateHeaterResult(), + ) + subject = ModuleStore( + config=_OT2_STANDARD_CONFIG, + deck_fixed_labware=[], + ) + + subject.handle_action(actions.SucceedCommandAction(command=load_module_cmd)) + subject.handle_action(actions.SucceedCommandAction(command=set_temp_cmd)) + assert subject.state.substate_by_module_id == { + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=False, + plate_target_temperature=42, + ) + } + subject.handle_action(actions.SucceedCommandAction(command=deactivate_cmd)) + assert subject.state.substate_by_module_id == { + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=False, + plate_target_temperature=None, + ) + } + + +def test_handle_hs_shake_commands(heater_shaker_v1_def: ModuleDefinition) -> None: + """It should update heater-shaker's `is_plate_shaking` correctly.""" + load_module_cmd = commands.LoadModule.construct( # type: ignore[call-arg] + params=commands.LoadModuleParams( + model=ModuleModel.HEATER_SHAKER_MODULE_V1, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + result=commands.LoadModuleResult( + moduleId="module-id", + model=ModuleModel.HEATER_SHAKER_MODULE_V1, + serialNumber="serial-number", + definition=heater_shaker_v1_def, + ), + ) + set_shake_cmd = hs_commands.SetAndWaitForShakeSpeed.construct( # type: ignore[call-arg] + params=hs_commands.SetAndWaitForShakeSpeedParams(moduleId="module-id", rpm=111), + result=hs_commands.SetAndWaitForShakeSpeedResult(pipetteRetracted=False), + ) + deactivate_cmd = hs_commands.DeactivateShaker.construct( # type: ignore[call-arg] + params=hs_commands.DeactivateShakerParams(moduleId="module-id"), + result=hs_commands.DeactivateShakerResult(), + ) + subject = ModuleStore( + config=_OT2_STANDARD_CONFIG, + deck_fixed_labware=[], + ) + + subject.handle_action(actions.SucceedCommandAction(command=load_module_cmd)) + subject.handle_action(actions.SucceedCommandAction(command=set_shake_cmd)) + assert subject.state.substate_by_module_id == { + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=True, + plate_target_temperature=None, + ) + } + subject.handle_action(actions.SucceedCommandAction(command=deactivate_cmd)) + assert subject.state.substate_by_module_id == { + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=False, + plate_target_temperature=None, + ) + } + + +def test_handle_hs_labware_latch_commands( + heater_shaker_v1_def: ModuleDefinition, +) -> None: + """It should update heater-shaker's `is_labware_latch_closed` correctly.""" + load_module_cmd = commands.LoadModule.construct( # type: ignore[call-arg] + params=commands.LoadModuleParams( + model=ModuleModel.HEATER_SHAKER_MODULE_V1, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + result=commands.LoadModuleResult( + moduleId="module-id", + model=ModuleModel.HEATER_SHAKER_MODULE_V1, + serialNumber="serial-number", + definition=heater_shaker_v1_def, + ), + ) + close_latch_cmd = hs_commands.CloseLabwareLatch.construct( # type: ignore[call-arg] + params=hs_commands.CloseLabwareLatchParams(moduleId="module-id"), + result=hs_commands.CloseLabwareLatchResult(), + ) + open_latch_cmd = hs_commands.OpenLabwareLatch.construct( # type: ignore[call-arg] + params=hs_commands.OpenLabwareLatchParams(moduleId="module-id"), + result=hs_commands.OpenLabwareLatchResult(pipetteRetracted=False), + ) + subject = ModuleStore( + config=_OT2_STANDARD_CONFIG, + deck_fixed_labware=[], + ) + + subject.handle_action(actions.SucceedCommandAction(command=load_module_cmd)) + assert subject.state.substate_by_module_id == { + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=False, + plate_target_temperature=None, + ) + } + + subject.handle_action(actions.SucceedCommandAction(command=close_latch_cmd)) + assert subject.state.substate_by_module_id == { + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.CLOSED, + is_plate_shaking=False, + plate_target_temperature=None, + ) + } + subject.handle_action(actions.SucceedCommandAction(command=open_latch_cmd)) + assert subject.state.substate_by_module_id == { + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.OPEN, + is_plate_shaking=False, + plate_target_temperature=None, + ) + } + + +def test_handle_tempdeck_temperature_commands( + tempdeck_v2_def: ModuleDefinition, +) -> None: + """It should update Tempdeck's `plate_target_temperature` correctly.""" + load_module_cmd = commands.LoadModule.construct( # type: ignore[call-arg] + params=commands.LoadModuleParams( + model=ModuleModel.TEMPERATURE_MODULE_V2, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + result=commands.LoadModuleResult( + moduleId="module-id", + model=ModuleModel.TEMPERATURE_MODULE_V2, + serialNumber="serial-number", + definition=tempdeck_v2_def, + ), + ) + set_temp_cmd = temp_commands.SetTargetTemperature.construct( # type: ignore[call-arg] + params=temp_commands.SetTargetTemperatureParams( + moduleId="module-id", celsius=42.4 + ), + result=temp_commands.SetTargetTemperatureResult(targetTemperature=42), + ) + deactivate_cmd = temp_commands.DeactivateTemperature.construct( # type: ignore[call-arg] + params=temp_commands.DeactivateTemperatureParams(moduleId="module-id"), + result=temp_commands.DeactivateTemperatureResult(), + ) + subject = ModuleStore( + config=_OT2_STANDARD_CONFIG, + deck_fixed_labware=[], + ) + + subject.handle_action(actions.SucceedCommandAction(command=load_module_cmd)) + subject.handle_action(actions.SucceedCommandAction(command=set_temp_cmd)) + assert subject.state.substate_by_module_id == { + "module-id": TemperatureModuleSubState( + module_id=TemperatureModuleId("module-id"), plate_target_temperature=42 + ) + } + subject.handle_action(actions.SucceedCommandAction(command=deactivate_cmd)) + assert subject.state.substate_by_module_id == { + "module-id": TemperatureModuleSubState( + module_id=TemperatureModuleId("module-id"), plate_target_temperature=None + ) + } + + +def test_handle_thermocycler_temperature_commands( + thermocycler_v1_def: ModuleDefinition, +) -> None: + """It should update thermocycler's temperature statuses correctly.""" + load_module_cmd = commands.LoadModule.construct( # type: ignore[call-arg] + params=commands.LoadModuleParams( + model=ModuleModel.THERMOCYCLER_MODULE_V1, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + result=commands.LoadModuleResult( + moduleId="module-id", + model=ModuleModel.THERMOCYCLER_MODULE_V1, + serialNumber="serial-number", + definition=thermocycler_v1_def, + ), + ) + set_block_temp_cmd = tc_commands.SetTargetBlockTemperature.construct( # type: ignore[call-arg] + params=tc_commands.SetTargetBlockTemperatureParams( + moduleId="module-id", celsius=42.4 + ), + result=tc_commands.SetTargetBlockTemperatureResult(targetBlockTemperature=42.4), + ) + deactivate_block_cmd = tc_commands.DeactivateBlock.construct( # type: ignore[call-arg] + params=tc_commands.DeactivateBlockParams(moduleId="module-id"), + result=tc_commands.DeactivateBlockResult(), + ) + set_lid_temp_cmd = tc_commands.SetTargetLidTemperature.construct( # type: ignore[call-arg] + params=tc_commands.SetTargetLidTemperatureParams( + moduleId="module-id", celsius=35.3 + ), + result=tc_commands.SetTargetLidTemperatureResult(targetLidTemperature=35.3), + ) + deactivate_lid_cmd = tc_commands.DeactivateLid.construct( # type: ignore[call-arg] + params=tc_commands.DeactivateLidParams(moduleId="module-id"), + result=tc_commands.DeactivateLidResult(), + ) + subject = ModuleStore( + config=_OT2_STANDARD_CONFIG, + deck_fixed_labware=[], + ) + + subject.handle_action(actions.SucceedCommandAction(command=load_module_cmd)) + subject.handle_action(actions.SucceedCommandAction(command=set_block_temp_cmd)) + assert subject.state.substate_by_module_id == { + "module-id": ThermocyclerModuleSubState( + module_id=ThermocyclerModuleId("module-id"), + is_lid_open=False, + target_block_temperature=42.4, + target_lid_temperature=None, + ) + } + subject.handle_action(actions.SucceedCommandAction(command=set_lid_temp_cmd)) + assert subject.state.substate_by_module_id == { + "module-id": ThermocyclerModuleSubState( + module_id=ThermocyclerModuleId("module-id"), + is_lid_open=False, + target_block_temperature=42.4, + target_lid_temperature=35.3, + ) + } + subject.handle_action(actions.SucceedCommandAction(command=deactivate_lid_cmd)) + assert subject.state.substate_by_module_id == { + "module-id": ThermocyclerModuleSubState( + module_id=ThermocyclerModuleId("module-id"), + is_lid_open=False, + target_block_temperature=42.4, + target_lid_temperature=None, + ) + } + subject.handle_action(actions.SucceedCommandAction(command=deactivate_block_cmd)) + assert subject.state.substate_by_module_id == { + "module-id": ThermocyclerModuleSubState( + module_id=ThermocyclerModuleId("module-id"), + is_lid_open=False, + target_block_temperature=None, + target_lid_temperature=None, + ) + } + + +def test_handle_thermocycler_lid_commands( + thermocycler_v1_def: ModuleDefinition, +) -> None: + """It should update thermocycler's lid status after executing lid commands.""" + load_module_cmd = commands.LoadModule.construct( # type: ignore[call-arg] + params=commands.LoadModuleParams( + model=ModuleModel.THERMOCYCLER_MODULE_V1, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + result=commands.LoadModuleResult( + moduleId="module-id", + model=ModuleModel.THERMOCYCLER_MODULE_V1, + serialNumber="serial-number", + definition=thermocycler_v1_def, + ), + ) + + open_lid_cmd = tc_commands.OpenLid.construct( # type: ignore[call-arg] + params=tc_commands.OpenLidParams(moduleId="module-id"), + result=tc_commands.OpenLidResult(), + ) + close_lid_cmd = tc_commands.CloseLid.construct( # type: ignore[call-arg] + params=tc_commands.CloseLidParams(moduleId="module-id"), + result=tc_commands.CloseLidResult(), + ) + + subject = ModuleStore( + Config( + use_simulated_deck_config=False, + robot_type="OT-3 Standard", + deck_type=DeckType.OT3_STANDARD, + ), + deck_fixed_labware=[], + ) + + subject.handle_action(actions.SucceedCommandAction(command=load_module_cmd)) + subject.handle_action(actions.SucceedCommandAction(command=open_lid_cmd)) + assert subject.state.substate_by_module_id == { + "module-id": ThermocyclerModuleSubState( + module_id=ThermocyclerModuleId("module-id"), + is_lid_open=True, + target_block_temperature=None, + target_lid_temperature=None, + ) + } + + subject.handle_action(actions.SucceedCommandAction(command=close_lid_cmd)) + assert subject.state.substate_by_module_id == { + "module-id": ThermocyclerModuleSubState( + module_id=ThermocyclerModuleId("module-id"), + is_lid_open=False, + target_block_temperature=None, + target_lid_temperature=None, + ) + } diff --git a/api/tests/opentrons/protocol_engine/state/test_module_view.py b/api/tests/opentrons/protocol_engine/state/test_module_view.py deleted file mode 100644 index 95b868497d2..00000000000 --- a/api/tests/opentrons/protocol_engine/state/test_module_view.py +++ /dev/null @@ -1,1977 +0,0 @@ -"""Tests for module state accessors in the protocol engine state store.""" -import pytest -from math import isclose -from pytest_lazyfixture import lazy_fixture # type: ignore[import-untyped] - -from contextlib import nullcontext as does_not_raise -from typing import ( - ContextManager, - Dict, - NamedTuple, - Optional, - Type, - Union, - Any, - List, - Set, - cast, -) - -from opentrons_shared_data.robot.types import RobotType -from opentrons_shared_data.deck.types import DeckDefinitionV5 - -from opentrons_shared_data import load_shared_data -from opentrons.types import DeckSlotName, MountType -from opentrons.protocol_engine import errors -from opentrons.protocol_engine.types import ( - LoadedModule, - DeckSlotLocation, - ModuleDefinition, - ModuleModel, - LabwareOffsetVector, - DeckType, - ModuleOffsetData, - HeaterShakerLatchStatus, - LabwareMovementOffsetData, - AddressableArea, - DeckConfigurationType, - PotentialCutoutFixture, -) -from opentrons.protocol_engine.state.modules import ( - ModuleView, - ModuleState, - HardwareModule, -) -from opentrons.protocol_engine.state.addressable_areas import ( - AddressableAreaView, - AddressableAreaState, -) - -from opentrons.protocol_engine.state.module_substates import ( - HeaterShakerModuleSubState, - HeaterShakerModuleId, - MagneticModuleSubState, - MagneticModuleId, - TemperatureModuleSubState, - TemperatureModuleId, - ThermocyclerModuleSubState, - ThermocyclerModuleId, - ModuleSubStateType, -) -from opentrons_shared_data.deck import load as load_deck -from opentrons.protocols.api_support.deck_type import ( - STANDARD_OT3_DECK, -) - - -@pytest.fixture(scope="session") -def ot3_standard_deck_def() -> DeckDefinitionV5: - """Get the OT-2 standard deck definition.""" - return load_deck(STANDARD_OT3_DECK, 5) - - -def get_addressable_area_view( - loaded_addressable_areas_by_name: Optional[Dict[str, AddressableArea]] = None, - potential_cutout_fixtures_by_cutout_id: Optional[ - Dict[str, Set[PotentialCutoutFixture]] - ] = None, - deck_definition: Optional[DeckDefinitionV5] = None, - deck_configuration: Optional[DeckConfigurationType] = None, - robot_type: RobotType = "OT-3 Standard", - use_simulated_deck_config: bool = False, -) -> AddressableAreaView: - """Get a labware view test subject.""" - state = AddressableAreaState( - loaded_addressable_areas_by_name=loaded_addressable_areas_by_name or {}, - potential_cutout_fixtures_by_cutout_id=potential_cutout_fixtures_by_cutout_id - or {}, - deck_definition=deck_definition or cast(DeckDefinitionV5, {"otId": "fake"}), - deck_configuration=deck_configuration or [], - robot_definition={ - "displayName": "OT-3", - "robotType": "OT-3 Standard", - "models": ["OT-3 Standard"], - "extents": [477.2, 493.8, 0.0], - "mountOffsets": { - "left": [-13.5, -60.5, 255.675], - "right": [40.5, -60.5, 255.675], - "gripper": [84.55, -12.75, 93.85], - }, - }, - robot_type=robot_type, - use_simulated_deck_config=use_simulated_deck_config, - ) - - return AddressableAreaView(state=state) - - -def make_module_view( - deck_type: Optional[DeckType] = None, - slot_by_module_id: Optional[Dict[str, Optional[DeckSlotName]]] = None, - requested_model_by_module_id: Optional[Dict[str, Optional[ModuleModel]]] = None, - hardware_by_module_id: Optional[Dict[str, HardwareModule]] = None, - substate_by_module_id: Optional[Dict[str, ModuleSubStateType]] = None, - module_offset_by_serial: Optional[Dict[str, ModuleOffsetData]] = None, - additional_slots_occupied_by_module_id: Optional[ - Dict[str, List[DeckSlotName]] - ] = None, -) -> ModuleView: - """Get a module view test subject with the specified state.""" - state = ModuleState( - deck_type=deck_type or DeckType.OT2_STANDARD, - slot_by_module_id=slot_by_module_id or {}, - requested_model_by_id=requested_model_by_module_id or {}, - hardware_by_module_id=hardware_by_module_id or {}, - substate_by_module_id=substate_by_module_id or {}, - module_offset_by_serial=module_offset_by_serial or {}, - additional_slots_occupied_by_module_id=additional_slots_occupied_by_module_id - or {}, - deck_fixed_labware=[], - ) - - return ModuleView(state=state) - - -def get_sample_parent_module_view( - matching_module_def: ModuleDefinition, - matching_module_id: str, -) -> ModuleView: - """Get a ModuleView with attached modules including a requested matching module.""" - definition = load_shared_data("module/definitions/2/magneticModuleV1.json") - magdeck_def = ModuleDefinition.parse_raw(definition) - - return make_module_view( - slot_by_module_id={ - "id-non-matching": DeckSlotName.SLOT_1, - matching_module_id: DeckSlotName.SLOT_2, - "id-another-non-matching": DeckSlotName.SLOT_3, - }, - hardware_by_module_id={ - "id-non-matching": HardwareModule( - serial_number="serial-non-matching", - definition=magdeck_def, - ), - matching_module_id: HardwareModule( - serial_number="serial-matching", - definition=matching_module_def, - ), - "id-another-non-matching": HardwareModule( - serial_number="serial-another-non-matching", - definition=magdeck_def, - ), - }, - ) - - -def test_initial_module_data_by_id() -> None: - """It should raise if module ID doesn't exist.""" - subject = make_module_view() - - with pytest.raises(errors.ModuleNotLoadedError): - subject.get("helloWorld") - - -def test_get_missing_hardware() -> None: - """It should raise if no loaded hardware.""" - subject = make_module_view(slot_by_module_id={"module-id": DeckSlotName.SLOT_1}) - - with pytest.raises(errors.ModuleNotLoadedError): - subject.get("module-id") - - -def test_get_module_data(tempdeck_v1_def: ModuleDefinition) -> None: - """It should get module data from state by ID.""" - subject = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=tempdeck_v1_def, - ) - }, - ) - - assert subject.get("module-id") == LoadedModule( - id="module-id", - model=ModuleModel.TEMPERATURE_MODULE_V1, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - serialNumber="serial-number", - ) - - -def test_get_location(tempdeck_v1_def: ModuleDefinition) -> None: - """It should return the module's location or raise.""" - subject = make_module_view( - slot_by_module_id={ - "module-1": DeckSlotName.SLOT_1, - "module-2": None, - }, - hardware_by_module_id={ - "module-1": HardwareModule( - serial_number="serial-1", - definition=tempdeck_v1_def, - ), - "module-2": HardwareModule( - serial_number="serial-2", - definition=tempdeck_v1_def, - ), - }, - ) - - assert subject.get_location("module-1") == DeckSlotLocation( - slotName=DeckSlotName.SLOT_1 - ) - - with pytest.raises(errors.ModuleNotOnDeckError): - assert subject.get_location("module-2") - - -def test_get_all_modules( - tempdeck_v1_def: ModuleDefinition, - tempdeck_v2_def: ModuleDefinition, -) -> None: - """It should return all modules in state.""" - subject = make_module_view( - slot_by_module_id={ - "module-1": DeckSlotName.SLOT_1, - "module-2": DeckSlotName.SLOT_2, - }, - hardware_by_module_id={ - "module-1": HardwareModule( - serial_number="serial-1", - definition=tempdeck_v1_def, - ), - "module-2": HardwareModule( - serial_number="serial-2", - definition=tempdeck_v2_def, - ), - }, - ) - - assert subject.get_all() == [ - LoadedModule( - id="module-1", - serialNumber="serial-1", - model=ModuleModel.TEMPERATURE_MODULE_V1, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - ), - LoadedModule( - id="module-2", - serialNumber="serial-2", - model=ModuleModel.TEMPERATURE_MODULE_V2, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_2), - ), - ] - - -def test_get_properties_by_id( - tempdeck_v2_def: ModuleDefinition, - magdeck_v1_def: ModuleDefinition, - mag_block_v1_def: ModuleDefinition, -) -> None: - """It should return a loaded module's properties by ID.""" - subject = make_module_view( - slot_by_module_id={ - "module-1": DeckSlotName.SLOT_1, - "module-2": DeckSlotName.SLOT_2, - "module-3": DeckSlotName.SLOT_3, - }, - requested_model_by_module_id={ - "module-1": ModuleModel.TEMPERATURE_MODULE_V1, - "module-2": ModuleModel.MAGNETIC_MODULE_V1, - "module-3": ModuleModel.MAGNETIC_BLOCK_V1, - }, - hardware_by_module_id={ - "module-1": HardwareModule( - serial_number="serial-1", - # Intentionally different from requested model. - definition=tempdeck_v2_def, - ), - "module-2": HardwareModule( - serial_number="serial-2", - definition=magdeck_v1_def, - ), - "module-3": HardwareModule(serial_number=None, definition=mag_block_v1_def), - }, - ) - - assert subject.get_definition("module-1") == tempdeck_v2_def - assert subject.get_dimensions("module-1") == tempdeck_v2_def.dimensions - assert subject.get_requested_model("module-1") == ModuleModel.TEMPERATURE_MODULE_V1 - assert subject.get_connected_model("module-1") == ModuleModel.TEMPERATURE_MODULE_V2 - assert subject.get_serial_number("module-1") == "serial-1" - assert subject.get_location("module-1") == DeckSlotLocation( - slotName=DeckSlotName.SLOT_1 - ) - - assert subject.get_definition("module-2") == magdeck_v1_def - assert subject.get_dimensions("module-2") == magdeck_v1_def.dimensions - assert subject.get_requested_model("module-2") == ModuleModel.MAGNETIC_MODULE_V1 - assert subject.get_connected_model("module-2") == ModuleModel.MAGNETIC_MODULE_V1 - assert subject.get_serial_number("module-2") == "serial-2" - assert subject.get_location("module-2") == DeckSlotLocation( - slotName=DeckSlotName.SLOT_2 - ) - - assert subject.get_definition("module-3") == mag_block_v1_def - assert subject.get_dimensions("module-3") == mag_block_v1_def.dimensions - assert subject.get_requested_model("module-3") == ModuleModel.MAGNETIC_BLOCK_V1 - assert subject.get_connected_model("module-3") == ModuleModel.MAGNETIC_BLOCK_V1 - assert subject.get_location("module-3") == DeckSlotLocation( - slotName=DeckSlotName.SLOT_3 - ) - - with pytest.raises(errors.ModuleNotConnectedError): - subject.get_serial_number("module-3") - - with pytest.raises(errors.ModuleNotLoadedError): - subject.get_definition("Not a module ID oh no") - - -@pytest.mark.parametrize( - argnames=["module_def", "slot", "expected_offset"], - argvalues=[ - ( - lazy_fixture("tempdeck_v1_def"), - DeckSlotName.SLOT_1, - LabwareOffsetVector(x=-0.15, y=-0.15, z=80.09), - ), - ( - lazy_fixture("tempdeck_v2_def"), - DeckSlotName.SLOT_1, - LabwareOffsetVector(x=-1.45, y=-0.15, z=80.09), - ), - ( - lazy_fixture("tempdeck_v2_def"), - DeckSlotName.SLOT_3, - LabwareOffsetVector(x=1.15, y=-0.15, z=80.09), - ), - ( - lazy_fixture("magdeck_v1_def"), - DeckSlotName.SLOT_1, - LabwareOffsetVector(x=0.125, y=-0.125, z=82.25), - ), - ( - lazy_fixture("magdeck_v2_def"), - DeckSlotName.SLOT_1, - LabwareOffsetVector(x=-1.175, y=-0.125, z=82.25), - ), - ( - lazy_fixture("magdeck_v2_def"), - DeckSlotName.SLOT_3, - LabwareOffsetVector(x=1.425, y=-0.125, z=82.25), - ), - ( - lazy_fixture("thermocycler_v1_def"), - DeckSlotName.SLOT_7, - LabwareOffsetVector(x=0, y=82.56, z=97.8), - ), - ( - lazy_fixture("thermocycler_v2_def"), - DeckSlotName.SLOT_7, - LabwareOffsetVector(x=0, y=68.8, z=108.96), - ), - ( - lazy_fixture("heater_shaker_v1_def"), - DeckSlotName.SLOT_1, - LabwareOffsetVector(x=-0.125, y=1.125, z=68.275), - ), - ( - lazy_fixture("heater_shaker_v1_def"), - DeckSlotName.SLOT_3, - LabwareOffsetVector(x=0.125, y=-1.125, z=68.275), - ), - ], -) -def test_get_module_offset_for_ot2_standard( - module_def: ModuleDefinition, - slot: DeckSlotName, - expected_offset: LabwareOffsetVector, -) -> None: - """It should return the correct labware offset for module in specified slot.""" - subject = make_module_view( - deck_type=DeckType.OT2_STANDARD, - slot_by_module_id={"module-id": slot}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="module-serial", - definition=module_def, - ) - }, - ) - assert ( - subject.get_nominal_module_offset("module-id", get_addressable_area_view()) - == expected_offset - ) - - -@pytest.mark.parametrize( - argnames=["module_def", "slot", "expected_offset", "deck_definition"], - argvalues=[ - ( - lazy_fixture("tempdeck_v2_def"), - DeckSlotName.SLOT_1.to_ot3_equivalent(), - LabwareOffsetVector(x=0, y=0, z=9), - lazy_fixture("ot3_standard_deck_def"), - ), - ( - lazy_fixture("tempdeck_v2_def"), - DeckSlotName.SLOT_3.to_ot3_equivalent(), - LabwareOffsetVector(x=0, y=0, z=9), - lazy_fixture("ot3_standard_deck_def"), - ), - ( - lazy_fixture("thermocycler_v2_def"), - DeckSlotName.SLOT_7.to_ot3_equivalent(), - LabwareOffsetVector(x=-20.005, y=67.96, z=10.96), - lazy_fixture("ot3_standard_deck_def"), - ), - ( - lazy_fixture("heater_shaker_v1_def"), - DeckSlotName.SLOT_1.to_ot3_equivalent(), - LabwareOffsetVector(x=0, y=0, z=18.95), - lazy_fixture("ot3_standard_deck_def"), - ), - ( - lazy_fixture("heater_shaker_v1_def"), - DeckSlotName.SLOT_3.to_ot3_equivalent(), - LabwareOffsetVector(x=0, y=0, z=18.95), - lazy_fixture("ot3_standard_deck_def"), - ), - ( - lazy_fixture("mag_block_v1_def"), - DeckSlotName.SLOT_2.to_ot3_equivalent(), - LabwareOffsetVector(x=0, y=0, z=38.0), - lazy_fixture("ot3_standard_deck_def"), - ), - ], -) -def test_get_module_offset_for_ot3_standard( - module_def: ModuleDefinition, - slot: DeckSlotName, - expected_offset: LabwareOffsetVector, - deck_definition: DeckDefinitionV5, -) -> None: - """It should return the correct labware offset for module in specified slot.""" - subject = make_module_view( - deck_type=DeckType.OT3_STANDARD, - slot_by_module_id={"module-id": slot}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="module-serial", - definition=module_def, - ) - }, - ) - - result_offset = subject.get_nominal_module_offset( - "module-id", - get_addressable_area_view( - deck_configuration=None, - deck_definition=deck_definition, - use_simulated_deck_config=True, - ), - ) - - assert (result_offset.x, result_offset.y, result_offset.z) == pytest.approx( - (expected_offset.x, expected_offset.y, expected_offset.z) - ) - - -def test_get_magnetic_module_substate( - magdeck_v1_def: ModuleDefinition, - magdeck_v2_def: ModuleDefinition, - heater_shaker_v1_def: ModuleDefinition, -) -> None: - """It should return a substate for the given Magnetic Module, if valid.""" - subject = make_module_view( - slot_by_module_id={ - "magnetic-module-gen1-id": DeckSlotName.SLOT_1, - "magnetic-module-gen2-id": DeckSlotName.SLOT_2, - "heatshake-module-id": DeckSlotName.SLOT_3, - }, - hardware_by_module_id={ - "magnetic-module-gen1-id": HardwareModule( - serial_number="magnetic-module-gen1-serial", - definition=magdeck_v1_def, - ), - "magnetic-module-gen2-id": HardwareModule( - serial_number="magnetic-module-gen2-serial", - definition=magdeck_v2_def, - ), - "heatshake-module-id": HardwareModule( - serial_number="heatshake-module-serial", - definition=heater_shaker_v1_def, - ), - }, - substate_by_module_id={ - "magnetic-module-gen1-id": MagneticModuleSubState( - module_id=MagneticModuleId("magnetic-module-gen1-id"), - model=ModuleModel.MAGNETIC_MODULE_V1, - ), - "magnetic-module-gen2-id": MagneticModuleSubState( - module_id=MagneticModuleId("magnetic-module-gen2-id"), - model=ModuleModel.MAGNETIC_MODULE_V2, - ), - "heatshake-module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("heatshake-module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=False, - plate_target_temperature=None, - ), - }, - ) - - module_1_substate = subject.get_magnetic_module_substate( - module_id="magnetic-module-gen1-id" - ) - assert module_1_substate.module_id == "magnetic-module-gen1-id" - assert module_1_substate.model == ModuleModel.MAGNETIC_MODULE_V1 - - module_2_substate = subject.get_magnetic_module_substate( - module_id="magnetic-module-gen2-id" - ) - assert module_2_substate.module_id == "magnetic-module-gen2-id" - assert module_2_substate.model == ModuleModel.MAGNETIC_MODULE_V2 - - with pytest.raises(errors.WrongModuleTypeError): - subject.get_magnetic_module_substate(module_id="heatshake-module-id") - - with pytest.raises(errors.ModuleNotLoadedError): - subject.get_magnetic_module_substate(module_id="nonexistent-module-id") - - -def test_get_heater_shaker_module_substate( - magdeck_v2_def: ModuleDefinition, - heater_shaker_v1_def: ModuleDefinition, -) -> None: - """It should return a heater-shaker module substate.""" - subject = make_module_view( - slot_by_module_id={ - "magnetic-module-gen2-id": DeckSlotName.SLOT_2, - "heatshake-module-id": DeckSlotName.SLOT_3, - }, - hardware_by_module_id={ - "magnetic-module-gen2-id": HardwareModule( - serial_number="magnetic-module-gen2-serial", - definition=magdeck_v2_def, - ), - "heatshake-module-id": HardwareModule( - serial_number="heatshake-module-serial", - definition=heater_shaker_v1_def, - ), - }, - substate_by_module_id={ - "magnetic-module-gen2-id": MagneticModuleSubState( - module_id=MagneticModuleId("magnetic-module-gen2-id"), - model=ModuleModel.MAGNETIC_MODULE_V2, - ), - "heatshake-module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("heatshake-module-id"), - plate_target_temperature=432, - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=True, - ), - }, - ) - - hs_substate = subject.get_heater_shaker_module_substate( - module_id="heatshake-module-id" - ) - assert hs_substate.module_id == "heatshake-module-id" - assert hs_substate.plate_target_temperature == 432 - assert hs_substate.is_plate_shaking is True - assert hs_substate.labware_latch_status == HeaterShakerLatchStatus.UNKNOWN - - with pytest.raises(errors.WrongModuleTypeError): - subject.get_heater_shaker_module_substate(module_id="magnetic-module-gen2-id") - - with pytest.raises(errors.ModuleNotLoadedError): - subject.get_heater_shaker_module_substate(module_id="nonexistent-module-id") - - -def test_get_temperature_module_substate( - tempdeck_v1_def: ModuleDefinition, - tempdeck_v2_def: ModuleDefinition, - heater_shaker_v1_def: ModuleDefinition, -) -> None: - """It should return a substate for the given Temperature Module, if valid.""" - subject = make_module_view( - slot_by_module_id={ - "temp-module-gen1-id": DeckSlotName.SLOT_1, - "temp-module-gen2-id": DeckSlotName.SLOT_2, - "heatshake-module-id": DeckSlotName.SLOT_3, - }, - hardware_by_module_id={ - "temp-module-gen1-id": HardwareModule( - serial_number="temp-module-gen1-serial", - definition=tempdeck_v1_def, - ), - "temp-module-gen2-id": HardwareModule( - serial_number="temp-module-gen2-serial", - definition=tempdeck_v2_def, - ), - "heatshake-module-id": HardwareModule( - serial_number="heatshake-module-serial", - definition=heater_shaker_v1_def, - ), - }, - substate_by_module_id={ - "temp-module-gen1-id": TemperatureModuleSubState( - module_id=TemperatureModuleId("temp-module-gen1-id"), - plate_target_temperature=None, - ), - "temp-module-gen2-id": TemperatureModuleSubState( - module_id=TemperatureModuleId("temp-module-gen2-id"), - plate_target_temperature=123, - ), - "heatshake-module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("heatshake-module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=False, - plate_target_temperature=None, - ), - }, - ) - - module_1_substate = subject.get_temperature_module_substate( - module_id="temp-module-gen1-id" - ) - assert module_1_substate.module_id == "temp-module-gen1-id" - assert module_1_substate.plate_target_temperature is None - - module_2_substate = subject.get_temperature_module_substate( - module_id="temp-module-gen2-id" - ) - assert module_2_substate.module_id == "temp-module-gen2-id" - assert module_2_substate.plate_target_temperature == 123 - - with pytest.raises(errors.WrongModuleTypeError): - subject.get_temperature_module_substate(module_id="heatshake-module-id") - - with pytest.raises(errors.ModuleNotLoadedError): - subject.get_temperature_module_substate(module_id="nonexistent-module-id") - - -def test_get_plate_target_temperature(heater_shaker_v1_def: ModuleDefinition) -> None: - """It should return whether target temperature is set.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=heater_shaker_v1_def, - ) - }, - substate_by_module_id={ - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=False, - plate_target_temperature=12.3, - ) - }, - ) - subject = module_view.get_heater_shaker_module_substate("module-id") - assert subject.get_plate_target_temperature() == 12.3 - - -def test_get_plate_target_temperature_no_target( - heater_shaker_v1_def: ModuleDefinition, -) -> None: - """It should raise if no target temperature is set.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=heater_shaker_v1_def, - ) - }, - substate_by_module_id={ - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=False, - plate_target_temperature=None, - ) - }, - ) - subject = module_view.get_heater_shaker_module_substate("module-id") - - with pytest.raises(errors.NoTargetTemperatureSetError): - subject.get_plate_target_temperature() - - -def test_get_magnet_home_to_base_offset() -> None: - """It should return the model-specific offset to bottom.""" - subject = make_module_view() - assert ( - subject.get_magnet_home_to_base_offset( - module_model=ModuleModel.MAGNETIC_MODULE_V1 - ) - == 2.5 - ) - assert ( - subject.get_magnet_home_to_base_offset( - module_model=ModuleModel.MAGNETIC_MODULE_V2 - ) - == 2.5 - ) - - -@pytest.mark.parametrize( - "module_model", [ModuleModel.MAGNETIC_MODULE_V1, ModuleModel.MAGNETIC_MODULE_V2] -) -def test_calculate_magnet_height(module_model: ModuleModel) -> None: - """It should use true millimeters as hardware units.""" - subject = make_module_view() - - assert ( - subject.calculate_magnet_height( - module_model=module_model, - height_from_base=100, - ) - == 100 - ) - - # todo(mm, 2022-02-28): - # It's unclear whether this expected result should actually be the same - # between GEN1 and GEN2. - # The GEN1 homing backoff distance looks accidentally halved, for the same reason - # that its heights are halved. If the limit switch hardware is the same for both - # modules, we'd expect the backoff difference to cause a difference in the - # height_from_home test, even though we're measuring everything in true mm. - # https://github.com/Opentrons/opentrons/issues/9585 - assert ( - subject.calculate_magnet_height( - module_model=module_model, - height_from_home=100, - ) - == 97.5 - ) - - assert ( - subject.calculate_magnet_height( - module_model=module_model, - labware_default_height=100, - offset_from_labware_default=10.0, - ) - == 110 - ) - - -@pytest.mark.parametrize( - argnames=["from_slot", "to_slot", "should_dodge"], - argvalues=[ - (DeckSlotName.SLOT_1, DeckSlotName.FIXED_TRASH, True), - (DeckSlotName.FIXED_TRASH, DeckSlotName.SLOT_1, True), - (DeckSlotName.SLOT_4, DeckSlotName.FIXED_TRASH, True), - (DeckSlotName.FIXED_TRASH, DeckSlotName.SLOT_4, True), - (DeckSlotName.SLOT_4, DeckSlotName.SLOT_9, True), - (DeckSlotName.SLOT_9, DeckSlotName.SLOT_4, True), - (DeckSlotName.SLOT_4, DeckSlotName.SLOT_8, True), - (DeckSlotName.SLOT_8, DeckSlotName.SLOT_4, True), - (DeckSlotName.SLOT_1, DeckSlotName.SLOT_8, True), - (DeckSlotName.SLOT_8, DeckSlotName.SLOT_1, True), - (DeckSlotName.SLOT_4, DeckSlotName.SLOT_11, True), - (DeckSlotName.SLOT_11, DeckSlotName.SLOT_4, True), - (DeckSlotName.SLOT_1, DeckSlotName.SLOT_11, True), - (DeckSlotName.SLOT_11, DeckSlotName.SLOT_1, True), - (DeckSlotName.SLOT_2, DeckSlotName.SLOT_4, False), - ], -) -def test_thermocycler_dodging_by_slots( - thermocycler_v1_def: ModuleDefinition, - from_slot: DeckSlotName, - to_slot: DeckSlotName, - should_dodge: bool, -) -> None: - """It should specify if thermocycler dodging is needed. - - It should return True if thermocycler exists and movement is between bad pairs of - slot locations. - """ - subject = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=thermocycler_v1_def, - ) - }, - ) - - assert ( - subject.should_dodge_thermocycler(from_slot=from_slot, to_slot=to_slot) - is should_dodge - ) - - -@pytest.mark.parametrize( - argnames=["from_slot", "to_slot"], - argvalues=[ - (DeckSlotName.SLOT_8, DeckSlotName.SLOT_1), - (DeckSlotName.SLOT_B2, DeckSlotName.SLOT_D1), - ], -) -@pytest.mark.parametrize( - argnames=["module_definition", "should_dodge"], - argvalues=[ - (lazy_fixture("tempdeck_v1_def"), False), - (lazy_fixture("tempdeck_v2_def"), False), - (lazy_fixture("magdeck_v1_def"), False), - (lazy_fixture("magdeck_v2_def"), False), - (lazy_fixture("thermocycler_v1_def"), True), - (lazy_fixture("thermocycler_v2_def"), True), - (lazy_fixture("heater_shaker_v1_def"), False), - ], -) -def test_thermocycler_dodging_by_modules( - from_slot: DeckSlotName, - to_slot: DeckSlotName, - module_definition: ModuleDefinition, - should_dodge: bool, -) -> None: - """It should specify if thermocycler dodging is needed if there is a thermocycler module.""" - subject = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=module_definition, - ) - }, - ) - assert ( - subject.should_dodge_thermocycler(from_slot=from_slot, to_slot=to_slot) - is should_dodge - ) - - -def test_select_hardware_module_to_load_rejects_missing() -> None: - """It should raise if the correct module isn't attached.""" - subject = make_module_view() - - with pytest.raises(errors.ModuleNotAttachedError): - subject.select_hardware_module_to_load( - model=ModuleModel.TEMPERATURE_MODULE_V1, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - attached_modules=[], - ) - - -@pytest.mark.parametrize( - argnames=["requested_model", "attached_definition"], - argvalues=[ - (ModuleModel.TEMPERATURE_MODULE_V1, lazy_fixture("tempdeck_v1_def")), - (ModuleModel.TEMPERATURE_MODULE_V2, lazy_fixture("tempdeck_v2_def")), - (ModuleModel.TEMPERATURE_MODULE_V1, lazy_fixture("tempdeck_v2_def")), - (ModuleModel.TEMPERATURE_MODULE_V2, lazy_fixture("tempdeck_v1_def")), - (ModuleModel.MAGNETIC_MODULE_V1, lazy_fixture("magdeck_v1_def")), - (ModuleModel.MAGNETIC_MODULE_V2, lazy_fixture("magdeck_v2_def")), - (ModuleModel.THERMOCYCLER_MODULE_V1, lazy_fixture("thermocycler_v1_def")), - (ModuleModel.THERMOCYCLER_MODULE_V2, lazy_fixture("thermocycler_v2_def")), - ], -) -def test_select_hardware_module_to_load( - requested_model: ModuleModel, - attached_definition: ModuleDefinition, -) -> None: - """It should return the first attached module that matches.""" - subject = make_module_view() - - attached_modules = [ - HardwareModule(serial_number="serial-1", definition=attached_definition), - HardwareModule(serial_number="serial-2", definition=attached_definition), - ] - - result = subject.select_hardware_module_to_load( - model=requested_model, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - attached_modules=attached_modules, - ) - - assert result == attached_modules[0] - - -def test_select_hardware_module_to_load_skips_non_matching( - magdeck_v1_def: ModuleDefinition, - magdeck_v2_def: ModuleDefinition, -) -> None: - """It should skip over non-matching modules.""" - subject = make_module_view() - - attached_modules = [ - HardwareModule(serial_number="serial-1", definition=magdeck_v1_def), - HardwareModule(serial_number="serial-2", definition=magdeck_v2_def), - ] - - result = subject.select_hardware_module_to_load( - model=ModuleModel.MAGNETIC_MODULE_V2, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - attached_modules=attached_modules, - ) - - assert result == attached_modules[1] - - -def test_select_hardware_module_to_load_skips_already_loaded( - magdeck_v1_def: ModuleDefinition, -) -> None: - """It should skip over already assigned modules.""" - subject = make_module_view( - hardware_by_module_id={ - "module-1": HardwareModule( - serial_number="serial-1", - definition=magdeck_v1_def, - ) - } - ) - - attached_modules = [ - HardwareModule(serial_number="serial-1", definition=magdeck_v1_def), - HardwareModule(serial_number="serial-2", definition=magdeck_v1_def), - ] - - result = subject.select_hardware_module_to_load( - model=ModuleModel.MAGNETIC_MODULE_V1, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_3), - attached_modules=attached_modules, - ) - - assert result == attached_modules[1] - - -def test_select_hardware_module_to_load_reuses_already_loaded( - magdeck_v1_def: ModuleDefinition, -) -> None: - """It should reuse over already assigned modules in the same location.""" - subject = make_module_view( - slot_by_module_id={ - "module-1": DeckSlotName.SLOT_1, - }, - hardware_by_module_id={ - "module-1": HardwareModule( - serial_number="serial-1", - definition=magdeck_v1_def, - ) - }, - ) - - attached_modules = [ - HardwareModule(serial_number="serial-1", definition=magdeck_v1_def), - HardwareModule(serial_number="serial-2", definition=magdeck_v1_def), - ] - - result = subject.select_hardware_module_to_load( - model=ModuleModel.MAGNETIC_MODULE_V1, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - attached_modules=attached_modules, - ) - - assert result == attached_modules[0] - - -def test_select_hardware_module_to_load_rejects_location_reassignment( - magdeck_v1_def: ModuleDefinition, - tempdeck_v1_def: ModuleDefinition, -) -> None: - """It should raise if a non-matching module is already present in the slot.""" - subject = make_module_view( - slot_by_module_id={ - "module-1": DeckSlotName.SLOT_1, - }, - hardware_by_module_id={ - "module-1": HardwareModule( - serial_number="serial-1", - definition=magdeck_v1_def, - ) - }, - ) - - attached_modules = [ - HardwareModule(serial_number="serial-1", definition=magdeck_v1_def), - HardwareModule(serial_number="serial-2", definition=tempdeck_v1_def), - ] - - with pytest.raises(errors.ModuleAlreadyPresentError): - subject.select_hardware_module_to_load( - model=ModuleModel.TEMPERATURE_MODULE_V1, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - attached_modules=attached_modules, - ) - - -class _CalculateMagnetHardwareHeightTestParams(NamedTuple): - definition: ModuleDefinition - mm_from_base: float - expected_result: Optional[float] - expected_exception_type: Union[Type[Exception], None] - - -@pytest.mark.parametrize( - "definition, mm_from_base, expected_result, expected_exception_type", - [ - # Happy cases: - _CalculateMagnetHardwareHeightTestParams( - definition=lazy_fixture("magdeck_v1_def"), - mm_from_base=10, - # TODO(mm, 2022-03-09): It's unclear if this expected result is correct. - # https://github.com/Opentrons/opentrons/issues/9585 - expected_result=25, - expected_exception_type=None, - ), - _CalculateMagnetHardwareHeightTestParams( - definition=lazy_fixture("magdeck_v2_def"), - mm_from_base=10, - expected_result=12.5, - expected_exception_type=None, - ), - # Boundary conditions: - # - # TODO(mm, 2022-03-09): - # In Python >=3.9, improve precision with math.nextafter(). - # Also consider relying on shared constants instead of hard-coding bounds. - # - # TODO(mm, 2022-03-09): It's unclear if the bounds used for V1 modules - # are physically correct. https://github.com/Opentrons/opentrons/issues/9585 - _CalculateMagnetHardwareHeightTestParams( # V1 barely too low. - definition=lazy_fixture("magdeck_v1_def"), - mm_from_base=-2.51, - expected_result=None, - expected_exception_type=errors.EngageHeightOutOfRangeError, - ), - _CalculateMagnetHardwareHeightTestParams( # V1 lowest allowed. - definition=lazy_fixture("magdeck_v1_def"), - mm_from_base=-2.5, - expected_result=0, - expected_exception_type=None, - ), - _CalculateMagnetHardwareHeightTestParams( # V1 highest allowed. - definition=lazy_fixture("magdeck_v1_def"), - mm_from_base=20, - expected_result=45, - expected_exception_type=None, - ), - _CalculateMagnetHardwareHeightTestParams( # V1 barely too high. - definition=lazy_fixture("magdeck_v1_def"), - mm_from_base=20.01, - expected_result=None, - expected_exception_type=errors.EngageHeightOutOfRangeError, - ), - _CalculateMagnetHardwareHeightTestParams( # V2 barely too low. - definition=lazy_fixture("magdeck_v2_def"), - mm_from_base=-2.51, - expected_result=None, - expected_exception_type=errors.EngageHeightOutOfRangeError, - ), - _CalculateMagnetHardwareHeightTestParams( # V2 lowest allowed. - definition=lazy_fixture("magdeck_v2_def"), - mm_from_base=-2.5, - expected_result=0, - expected_exception_type=None, - ), - _CalculateMagnetHardwareHeightTestParams( # V2 highest allowed. - definition=lazy_fixture("magdeck_v2_def"), - mm_from_base=22.5, - expected_result=25, - expected_exception_type=None, - ), - _CalculateMagnetHardwareHeightTestParams( # V2 barely too high. - definition=lazy_fixture("magdeck_v2_def"), - mm_from_base=22.51, - expected_result=None, - expected_exception_type=errors.EngageHeightOutOfRangeError, - ), - ], -) -def test_magnetic_module_view_calculate_magnet_hardware_height( - definition: ModuleDefinition, - mm_from_base: float, - expected_result: float, - expected_exception_type: Union[Type[Exception], None], -) -> None: - """It should return the expected height or raise the expected exception.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=definition, - ) - }, - substate_by_module_id={ - "module-id": MagneticModuleSubState( - module_id=MagneticModuleId("module-id"), - model=definition.model, # type: ignore [arg-type] - ) - }, - ) - subject = module_view.get_magnetic_module_substate("module-id") - expected_raise: ContextManager[None] = ( - # Not sure why mypy has trouble with this. - does_not_raise() # type: ignore[assignment] - if expected_exception_type is None - else pytest.raises(expected_exception_type) - ) - with expected_raise: - result = subject.calculate_magnet_hardware_height(mm_from_base=mm_from_base) - assert result == expected_result - - -@pytest.mark.parametrize("target_temp", [36.8, 95.1]) -def test_validate_heater_shaker_target_temperature_raises( - heater_shaker_v1_def: ModuleDefinition, - target_temp: float, -) -> None: - """It should verify if a target temperature is valid for the specified module.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=heater_shaker_v1_def, - ) - }, - substate_by_module_id={ - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=False, - plate_target_temperature=None, - ) - }, - ) - subject = module_view.get_heater_shaker_module_substate("module-id") - with pytest.raises(errors.InvalidTargetTemperatureError): - subject.validate_target_temperature(target_temp) - - -@pytest.mark.parametrize("target_temp", [37, 94.8]) -def test_validate_heater_shaker_target_temperature( - heater_shaker_v1_def: ModuleDefinition, - target_temp: float, -) -> None: - """It should verify if a target temperature is valid for the specified module.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=heater_shaker_v1_def, - ) - }, - substate_by_module_id={ - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=False, - plate_target_temperature=None, - ) - }, - ) - subject = module_view.get_heater_shaker_module_substate("module-id") - assert subject.validate_target_temperature(target_temp) == target_temp - - -@pytest.mark.parametrize("target_temp", [-10, 99.9]) -def test_validate_temp_module_target_temperature_raises( - tempdeck_v1_def: ModuleDefinition, - target_temp: float, -) -> None: - """It should verify if a target temperature is valid for the specified module.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=tempdeck_v1_def, - ) - }, - substate_by_module_id={ - "module-id": TemperatureModuleSubState( - module_id=TemperatureModuleId("module-id"), - plate_target_temperature=None, - ) - }, - ) - subject = module_view.get_temperature_module_substate("module-id") - with pytest.raises(errors.InvalidTargetTemperatureError): - subject.validate_target_temperature(target_temp) - - -@pytest.mark.parametrize( - ["target_temp", "validated_temp"], [(-9.431, -9), (0, 0), (99.1, 99)] -) -def test_validate_temp_module_target_temperature( - tempdeck_v2_def: ModuleDefinition, target_temp: float, validated_temp: int -) -> None: - """It should verify if a target temperature is valid for the specified module.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=tempdeck_v2_def, - ) - }, - substate_by_module_id={ - "module-id": TemperatureModuleSubState( - module_id=TemperatureModuleId("module-id"), - plate_target_temperature=None, - ) - }, - ) - subject = module_view.get_temperature_module_substate("module-id") - assert subject.validate_target_temperature(target_temp) == validated_temp - - -@pytest.mark.parametrize( - argnames=["rpm_param", "validated_param"], - argvalues=[(200.1, 200), (250.6, 251), (300.9, 301)], -) -def test_validate_heater_shaker_target_speed_converts_to_int( - rpm_param: float, validated_param: bool, heater_shaker_v1_def: ModuleDefinition -) -> None: - """It should validate heater-shaker target rpm.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=heater_shaker_v1_def, - ) - }, - substate_by_module_id={ - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=False, - plate_target_temperature=None, - ) - }, - ) - subject = module_view.get_heater_shaker_module_substate("module-id") - assert subject.validate_target_speed(rpm_param) == validated_param - - -@pytest.mark.parametrize( - argnames=["rpm_param", "expected_valid"], - argvalues=[(199.4, False), (199.5, True), (3000.7, False), (3000.4, True)], -) -def test_validate_heater_shaker_target_speed_raises_error( - rpm_param: float, expected_valid: bool, heater_shaker_v1_def: ModuleDefinition -) -> None: - """It should validate heater-shaker target rpm.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=heater_shaker_v1_def, - ) - }, - substate_by_module_id={ - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=False, - plate_target_temperature=None, - ) - }, - ) - subject = module_view.get_heater_shaker_module_substate("module-id") - if not expected_valid: - with pytest.raises(errors.InvalidTargetSpeedError): - subject.validate_target_speed(rpm_param) - - -def test_raise_if_labware_latch_not_closed( - heater_shaker_v1_def: ModuleDefinition, -) -> None: - """It should raise an error if labware latch is not closed.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=heater_shaker_v1_def, - ) - }, - substate_by_module_id={ - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.OPEN, - is_plate_shaking=False, - plate_target_temperature=None, - ) - }, - ) - subject = module_view.get_heater_shaker_module_substate("module-id") - with pytest.raises(errors.CannotPerformModuleAction, match="is open"): - subject.raise_if_labware_latch_not_closed() - - -def test_raise_if_labware_latch_unknown( - heater_shaker_v1_def: ModuleDefinition, -) -> None: - """It should raise an error if labware latch is not closed.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=heater_shaker_v1_def, - ) - }, - substate_by_module_id={ - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=False, - plate_target_temperature=None, - ) - }, - ) - subject = module_view.get_heater_shaker_module_substate("module-id") - with pytest.raises(errors.CannotPerformModuleAction, match="set to closed"): - subject.raise_if_labware_latch_not_closed() - - -def test_heater_shaker_raise_if_shaking( - heater_shaker_v1_def: ModuleDefinition, -) -> None: - """It should raise when heater-shaker is shaking.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=heater_shaker_v1_def, - ) - }, - substate_by_module_id={ - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, - is_plate_shaking=True, - plate_target_temperature=None, - ) - }, - ) - subject = module_view.get_heater_shaker_module_substate("module-id") - with pytest.raises(errors.CannotPerformModuleAction): - subject.raise_if_shaking() - - -def test_get_heater_shaker_movement_data( - heater_shaker_v1_def: ModuleDefinition, - tempdeck_v2_def: ModuleDefinition, -) -> None: - """It should get heater-shaker movement data.""" - module_view = make_module_view( - slot_by_module_id={ - "module-id": DeckSlotName.SLOT_1, - "other-module-id": DeckSlotName.SLOT_5, - }, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=heater_shaker_v1_def, - ), - "other-module-id": HardwareModule( - serial_number="other-serial-number", - definition=tempdeck_v2_def, - ), - }, - substate_by_module_id={ - "module-id": HeaterShakerModuleSubState( - module_id=HeaterShakerModuleId("module-id"), - labware_latch_status=HeaterShakerLatchStatus.CLOSED, - is_plate_shaking=False, - plate_target_temperature=None, - ), - "other-module-id": TemperatureModuleSubState( - module_id=TemperatureModuleId("other-module-id"), - plate_target_temperature=None, - ), - }, - ) - subject = module_view.get_heater_shaker_movement_restrictors() - assert len(subject) == 1 - for hs_movement_data in subject: - assert not hs_movement_data.plate_shaking - assert hs_movement_data.latch_status - assert hs_movement_data.deck_slot == 1 - - -def test_tempdeck_get_plate_target_temperature( - tempdeck_v2_def: ModuleDefinition, -) -> None: - """It should return whether target temperature is set.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=tempdeck_v2_def, - ) - }, - substate_by_module_id={ - "module-id": TemperatureModuleSubState( - module_id=TemperatureModuleId("module-id"), - plate_target_temperature=12, - ) - }, - ) - subject = module_view.get_temperature_module_substate("module-id") - assert subject.get_plate_target_temperature() == 12 - - -def test_tempdeck_get_plate_target_temperature_no_target( - tempdeck_v2_def: ModuleDefinition, -) -> None: - """It should raise if no target temperature is set.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=tempdeck_v2_def, - ) - }, - substate_by_module_id={ - "module-id": TemperatureModuleSubState( - module_id=TemperatureModuleId("module-id"), - plate_target_temperature=None, - ) - }, - ) - subject = module_view.get_temperature_module_substate("module-id") - - with pytest.raises(errors.NoTargetTemperatureSetError): - subject.get_plate_target_temperature() - - -def test_thermocycler_get_target_temperatures( - thermocycler_v1_def: ModuleDefinition, -) -> None: - """It should return whether target temperature for thermocycler is set.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=thermocycler_v1_def, - ) - }, - substate_by_module_id={ - "module-id": ThermocyclerModuleSubState( - module_id=ThermocyclerModuleId("module-id"), - is_lid_open=False, - target_block_temperature=14, - target_lid_temperature=28, - ) - }, - ) - subject = module_view.get_thermocycler_module_substate("module-id") - assert subject.get_target_block_temperature() == 14 - assert subject.get_target_lid_temperature() == 28 - - -def test_thermocycler_get_target_temperatures_no_target( - thermocycler_v1_def: ModuleDefinition, -) -> None: - """It should raise if no target temperature is set.""" - module_view = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=thermocycler_v1_def, - ) - }, - substate_by_module_id={ - "module-id": ThermocyclerModuleSubState( - module_id=ThermocyclerModuleId("module-id"), - is_lid_open=False, - target_block_temperature=None, - target_lid_temperature=None, - ) - }, - ) - subject = module_view.get_thermocycler_module_substate("module-id") - - with pytest.raises(errors.NoTargetTemperatureSetError): - subject.get_target_block_temperature() - subject.get_target_lid_temperature() - - -@pytest.fixture -def module_view_with_thermocycler(thermocycler_v1_def: ModuleDefinition) -> ModuleView: - """Get a module state view with a loaded thermocycler.""" - return make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=thermocycler_v1_def, - ) - }, - substate_by_module_id={ - "module-id": ThermocyclerModuleSubState( - module_id=ThermocyclerModuleId("module-id"), - target_block_temperature=None, - target_lid_temperature=None, - is_lid_open=False, - ) - }, - ) - - -@pytest.mark.parametrize("input_temperature", [0, 0.0, 0.001, 98.999, 99, 99.0]) -def test_thermocycler_validate_target_block_temperature( - module_view_with_thermocycler: ModuleView, - input_temperature: float, -) -> None: - """It should return a valid target block temperature.""" - subject = module_view_with_thermocycler.get_thermocycler_module_substate( - "module-id" - ) - result = subject.validate_target_block_temperature(input_temperature) - - assert result == input_temperature - - -@pytest.mark.parametrize( - argnames=["input_time", "validated_time"], - argvalues=[(0.0, 0.0), (0.123, 0.123), (123.456, 123.456), (1234567, 1234567)], -) -def test_thermocycler_validate_hold_time( - module_view_with_thermocycler: ModuleView, - input_time: float, - validated_time: float, -) -> None: - """It should return a valid hold time.""" - subject = module_view_with_thermocycler.get_thermocycler_module_substate( - "module-id" - ) - result = subject.validate_hold_time(input_time) - - assert result == validated_time - - -@pytest.mark.parametrize("input_time", [-0.1, -123]) -def test_thermocycler_validate_hold_time_raises( - module_view_with_thermocycler: ModuleView, - input_time: float, -) -> None: - """It should raise on invalid hold time.""" - subject = module_view_with_thermocycler.get_thermocycler_module_substate( - "module-id" - ) - - with pytest.raises(errors.InvalidHoldTimeError): - subject.validate_hold_time(input_time) - - -@pytest.mark.parametrize("input_temperature", [-0.001, 99.001]) -def test_thermocycler_validate_target_block_temperature_raises( - module_view_with_thermocycler: ModuleView, - input_temperature: float, -) -> None: - """It should raise on invalid target block temperature.""" - subject = module_view_with_thermocycler.get_thermocycler_module_substate( - "module-id" - ) - - with pytest.raises(errors.InvalidTargetTemperatureError): - subject.validate_target_block_temperature(input_temperature) - - -@pytest.mark.parametrize("input_volume", [0, 0.0, 0.001, 50.0, 99.999, 100, 100.0]) -def test_thermocycler_validate_block_max_volume( - module_view_with_thermocycler: ModuleView, - input_volume: float, -) -> None: - """It should return a validated max block volume value.""" - subject = module_view_with_thermocycler.get_thermocycler_module_substate( - "module-id" - ) - result = subject.validate_max_block_volume(input_volume) - - assert result == input_volume - - -@pytest.mark.parametrize("input_volume", [-10, -0.001, 100.001]) -def test_thermocycler_validate_block_max_volume_raises( - module_view_with_thermocycler: ModuleView, - input_volume: float, -) -> None: - """It should raise on invalid block volume temperature.""" - subject = module_view_with_thermocycler.get_thermocycler_module_substate( - "module-id" - ) - - with pytest.raises(errors.InvalidBlockVolumeError): - subject.validate_max_block_volume(input_volume) - - -@pytest.mark.parametrize("input_temperature", [37, 37.0, 37.001, 109.999, 110, 110.0]) -def test_thermocycler_validate_target_lid_temperature( - module_view_with_thermocycler: ModuleView, - input_temperature: float, -) -> None: - """It should return a valid target block temperature.""" - subject = module_view_with_thermocycler.get_thermocycler_module_substate( - "module-id" - ) - result = subject.validate_target_lid_temperature(input_temperature) - - assert result == input_temperature - - -@pytest.mark.parametrize("input_temperature", [36.999, 110.001]) -def test_thermocycler_validate_target_lid_temperature_raises( - module_view_with_thermocycler: ModuleView, - input_temperature: float, -) -> None: - """It should raise on invalid target block temperature.""" - subject = module_view_with_thermocycler.get_thermocycler_module_substate( - "module-id" - ) - - with pytest.raises(errors.InvalidTargetTemperatureError): - subject.validate_target_lid_temperature(input_temperature) - - -@pytest.mark.parametrize( - ("module_definition", "expected_height"), - [ - (lazy_fixture("thermocycler_v1_def"), 98.0), - (lazy_fixture("tempdeck_v1_def"), 84.0), - (lazy_fixture("tempdeck_v2_def"), 84.0), - (lazy_fixture("magdeck_v1_def"), 110.152), - (lazy_fixture("magdeck_v2_def"), 110.152), - (lazy_fixture("heater_shaker_v1_def"), 82.0), - ], -) -def test_get_overall_height( - module_definition: ModuleDefinition, - expected_height: float, -) -> None: - """It should get a module's overall height.""" - subject = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_7}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=module_definition, - ) - }, - ) - - result = subject.get_overall_height("module-id") - assert result == expected_height - - -@pytest.mark.parametrize( - argnames=["location", "expected_raise"], - argvalues=[ - ( - DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - pytest.raises(errors.LocationIsOccupiedError), - ), - (DeckSlotLocation(slotName=DeckSlotName.SLOT_2), does_not_raise()), - (DeckSlotLocation(slotName=DeckSlotName.FIXED_TRASH), does_not_raise()), - ], -) -def test_raise_if_labware_in_location( - location: DeckSlotLocation, - expected_raise: ContextManager[Any], - thermocycler_v1_def: ModuleDefinition, -) -> None: - """It should raise if there is module in specified location.""" - subject = make_module_view( - slot_by_module_id={"module-id-1": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id-1": HardwareModule( - serial_number="serial-number", - definition=thermocycler_v1_def, - ) - }, - substate_by_module_id={ - "module-id-1": ThermocyclerModuleSubState( - module_id=ThermocyclerModuleId("module-id-1"), - is_lid_open=False, - target_block_temperature=None, - target_lid_temperature=None, - ) - }, - ) - with expected_raise: - subject.raise_if_module_in_location(location=location) - - -def test_get_by_slot() -> None: - """It should get the module in a given slot.""" - subject = make_module_view( - slot_by_module_id={ - "1": DeckSlotName.SLOT_1, - "2": DeckSlotName.SLOT_2, - }, - hardware_by_module_id={ - "1": HardwareModule( - serial_number="serial-number-1", - definition=ModuleDefinition.construct( # type: ignore[call-arg] - model=ModuleModel.TEMPERATURE_MODULE_V1 - ), - ), - "2": HardwareModule( - serial_number="serial-number-2", - definition=ModuleDefinition.construct( # type: ignore[call-arg] - model=ModuleModel.TEMPERATURE_MODULE_V2 - ), - ), - }, - ) - - assert subject.get_by_slot(DeckSlotName.SLOT_1) == LoadedModule( - id="1", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - model=ModuleModel.TEMPERATURE_MODULE_V1, - serialNumber="serial-number-1", - ) - assert subject.get_by_slot(DeckSlotName.SLOT_2) == LoadedModule( - id="2", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_2), - model=ModuleModel.TEMPERATURE_MODULE_V2, - serialNumber="serial-number-2", - ) - assert subject.get_by_slot(DeckSlotName.SLOT_3) is None - - -def test_get_by_slot_prefers_later() -> None: - """It should get the module in a slot, preferring later items if locations match.""" - subject = make_module_view( - slot_by_module_id={ - "1": DeckSlotName.SLOT_1, - "1-again": DeckSlotName.SLOT_1, - }, - hardware_by_module_id={ - "1": HardwareModule( - serial_number="serial-number-1", - definition=ModuleDefinition.construct( # type: ignore[call-arg] - model=ModuleModel.TEMPERATURE_MODULE_V1 - ), - ), - "1-again": HardwareModule( - serial_number="serial-number-1-again", - definition=ModuleDefinition.construct( # type: ignore[call-arg] - model=ModuleModel.TEMPERATURE_MODULE_V1 - ), - ), - }, - ) - - assert subject.get_by_slot(DeckSlotName.SLOT_1) == LoadedModule( - id="1-again", - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - model=ModuleModel.TEMPERATURE_MODULE_V1, - serialNumber="serial-number-1-again", - ) - - -@pytest.mark.parametrize( - argnames=["mount", "target_slot", "expected_result"], - argvalues=[ - (MountType.RIGHT, DeckSlotName.SLOT_1, False), - (MountType.RIGHT, DeckSlotName.SLOT_2, True), - (MountType.RIGHT, DeckSlotName.SLOT_5, False), - (MountType.LEFT, DeckSlotName.SLOT_3, False), - (MountType.RIGHT, DeckSlotName.SLOT_5, False), - (MountType.LEFT, DeckSlotName.SLOT_8, True), - ], -) -def test_is_edge_move_unsafe( - mount: MountType, target_slot: DeckSlotName, expected_result: bool -) -> None: - """It should determine if an edge move would be unsafe.""" - subject = make_module_view( - slot_by_module_id={"foo": DeckSlotName.SLOT_1, "bar": DeckSlotName.SLOT_9} - ) - - result = subject.is_edge_move_unsafe(mount=mount, target_slot=target_slot) - - assert result is expected_result - - -@pytest.mark.parametrize( - argnames=["module_def", "expected_offset_data"], - argvalues=[ - ( - lazy_fixture("thermocycler_v2_def"), - LabwareMovementOffsetData( - pickUpOffset=LabwareOffsetVector(x=0, y=0, z=4.6), - dropOffset=LabwareOffsetVector(x=0, y=0, z=5.6), - ), - ), - ( - lazy_fixture("heater_shaker_v1_def"), - LabwareMovementOffsetData( - pickUpOffset=LabwareOffsetVector(x=0, y=0, z=0), - dropOffset=LabwareOffsetVector(x=0, y=0, z=1.0), - ), - ), - ( - lazy_fixture("tempdeck_v1_def"), - None, - ), - ], -) -def test_get_default_gripper_offsets( - module_def: ModuleDefinition, - expected_offset_data: Optional[LabwareMovementOffsetData], -) -> None: - """It should return the correct gripper offsets, if present.""" - subject = make_module_view( - slot_by_module_id={ - "module-1": DeckSlotName.SLOT_1, - }, - requested_model_by_module_id={ - "module-1": ModuleModel.TEMPERATURE_MODULE_V1, # Does not matter - }, - hardware_by_module_id={ - "module-1": HardwareModule( - serial_number="serial-1", - definition=module_def, - ), - }, - ) - assert subject.get_default_gripper_offsets("module-1") == expected_offset_data - - -@pytest.mark.parametrize( - argnames=["deck_type", "slot_name", "expected_highest_z", "deck_definition"], - argvalues=[ - ( - DeckType.OT2_STANDARD, - DeckSlotName.SLOT_1, - 84, - lazy_fixture("ot3_standard_deck_def"), - ), - ( - DeckType.OT3_STANDARD, - DeckSlotName.SLOT_D1, - 12.91, - lazy_fixture("ot3_standard_deck_def"), - ), - ], -) -def test_get_module_highest_z( - tempdeck_v2_def: ModuleDefinition, - deck_type: DeckType, - slot_name: DeckSlotName, - expected_highest_z: float, - deck_definition: DeckDefinitionV5, -) -> None: - """It should get the highest z point of the module.""" - subject = make_module_view( - deck_type=deck_type, - slot_by_module_id={"module-id": slot_name}, - requested_model_by_module_id={ - "module-id": ModuleModel.TEMPERATURE_MODULE_V2, - }, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="module-serial", - definition=tempdeck_v2_def, - ) - }, - ) - assert isclose( - subject.get_module_highest_z( - module_id="module-id", - addressable_areas=get_addressable_area_view( - deck_configuration=None, - deck_definition=deck_definition, - use_simulated_deck_config=True, - ), - ), - expected_highest_z, - ) - - -def test_get_overflowed_module_in_slot(tempdeck_v1_def: ModuleDefinition) -> None: - """It should return the module occupying but not loaded in the given slot.""" - subject = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=tempdeck_v1_def, - ) - }, - additional_slots_occupied_by_module_id={ - "module-id": [DeckSlotName.SLOT_6, DeckSlotName.SLOT_A1], - }, - ) - assert subject.get_overflowed_module_in_slot(DeckSlotName.SLOT_6) == LoadedModule( - id="module-id", - model=ModuleModel.TEMPERATURE_MODULE_V1, - location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - serialNumber="serial-number", - ) - - -@pytest.mark.parametrize( - argnames=["deck_type", "module_def", "module_slot", "expected_result"], - argvalues=[ - ( - DeckType.OT3_STANDARD, - lazy_fixture("thermocycler_v2_def"), - DeckSlotName.SLOT_A1, - True, - ), - ( - DeckType.OT3_STANDARD, - lazy_fixture("tempdeck_v1_def"), - DeckSlotName.SLOT_A1, - False, - ), - ( - DeckType.OT3_STANDARD, - lazy_fixture("thermocycler_v2_def"), - DeckSlotName.SLOT_1, - False, - ), - ( - DeckType.OT2_STANDARD, - lazy_fixture("thermocycler_v2_def"), - DeckSlotName.SLOT_A1, - False, - ), - ], -) -def test_is_flex_deck_with_thermocycler( - deck_type: DeckType, - module_def: ModuleDefinition, - module_slot: DeckSlotName, - expected_result: bool, -) -> None: - """It should return True if there is a thermocycler on Flex.""" - subject = make_module_view( - slot_by_module_id={"module-id": DeckSlotName.SLOT_B1}, - hardware_by_module_id={ - "module-id": HardwareModule( - serial_number="serial-number", - definition=module_def, - ) - }, - additional_slots_occupied_by_module_id={ - "module-id": [module_slot, DeckSlotName.SLOT_C1], - }, - deck_type=deck_type, - ) - assert subject.is_flex_deck_with_thermocycler() == expected_result diff --git a/api/tests/opentrons/protocol_engine/state/test_module_view_old.py b/api/tests/opentrons/protocol_engine/state/test_module_view_old.py new file mode 100644 index 00000000000..3902eedc76f --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_module_view_old.py @@ -0,0 +1,1988 @@ +"""Tests for module state accessors in the protocol engine state store. + +DEPRECATED: Testing ModuleView independently of ModuleView is no longer helpful. +Try to add new tests to test_module_state.py, where they can be tested together, +treating ModuleState as a private implementation detail. +""" +import pytest +from math import isclose +from pytest_lazyfixture import lazy_fixture # type: ignore[import-untyped] + +from contextlib import nullcontext as does_not_raise +from typing import ( + ContextManager, + Dict, + NamedTuple, + Optional, + Type, + Union, + Any, + List, + Set, + cast, +) + +from opentrons_shared_data.robot.types import RobotType +from opentrons_shared_data.deck.types import DeckDefinitionV5 + +from opentrons_shared_data import load_shared_data +from opentrons.types import DeckSlotName, MountType +from opentrons.protocol_engine import errors +from opentrons.protocol_engine.types import ( + LoadedModule, + DeckSlotLocation, + ModuleDefinition, + ModuleModel, + LabwareOffsetVector, + DeckType, + ModuleOffsetData, + HeaterShakerLatchStatus, + LabwareMovementOffsetData, + AddressableArea, + DeckConfigurationType, + PotentialCutoutFixture, +) +from opentrons.protocol_engine.state.modules import ( + ModuleView, + ModuleState, + HardwareModule, +) +from opentrons.protocol_engine.state.addressable_areas import ( + AddressableAreaView, + AddressableAreaState, +) + +from opentrons.protocol_engine.state.module_substates import ( + HeaterShakerModuleSubState, + HeaterShakerModuleId, + MagneticModuleSubState, + MagneticModuleId, + TemperatureModuleSubState, + TemperatureModuleId, + ThermocyclerModuleSubState, + ThermocyclerModuleId, + ModuleSubStateType, +) +from opentrons_shared_data.deck import load as load_deck +from opentrons.protocols.api_support.deck_type import ( + STANDARD_OT3_DECK, +) + + +@pytest.fixture(scope="session") +def ot3_standard_deck_def() -> DeckDefinitionV5: + """Get the OT-2 standard deck definition.""" + return load_deck(STANDARD_OT3_DECK, 5) + + +def get_addressable_area_view( + loaded_addressable_areas_by_name: Optional[Dict[str, AddressableArea]] = None, + potential_cutout_fixtures_by_cutout_id: Optional[ + Dict[str, Set[PotentialCutoutFixture]] + ] = None, + deck_definition: Optional[DeckDefinitionV5] = None, + deck_configuration: Optional[DeckConfigurationType] = None, + robot_type: RobotType = "OT-3 Standard", + use_simulated_deck_config: bool = False, +) -> AddressableAreaView: + """Get a labware view test subject.""" + state = AddressableAreaState( + loaded_addressable_areas_by_name=loaded_addressable_areas_by_name or {}, + potential_cutout_fixtures_by_cutout_id=potential_cutout_fixtures_by_cutout_id + or {}, + deck_definition=deck_definition or cast(DeckDefinitionV5, {"otId": "fake"}), + deck_configuration=deck_configuration or [], + robot_definition={ + "displayName": "OT-3", + "robotType": "OT-3 Standard", + "models": ["OT-3 Standard"], + "extents": [477.2, 493.8, 0.0], + "paddingOffsets": { + "rear": -177.42, + "front": 51.8, + "leftSide": 31.88, + "rightSide": -80.32, + }, + "mountOffsets": { + "left": [-13.5, -60.5, 255.675], + "right": [40.5, -60.5, 255.675], + "gripper": [84.55, -12.75, 93.85], + }, + }, + robot_type=robot_type, + use_simulated_deck_config=use_simulated_deck_config, + ) + + return AddressableAreaView(state=state) + + +def make_module_view( + deck_type: Optional[DeckType] = None, + slot_by_module_id: Optional[Dict[str, Optional[DeckSlotName]]] = None, + requested_model_by_module_id: Optional[Dict[str, Optional[ModuleModel]]] = None, + hardware_by_module_id: Optional[Dict[str, HardwareModule]] = None, + substate_by_module_id: Optional[Dict[str, ModuleSubStateType]] = None, + module_offset_by_serial: Optional[Dict[str, ModuleOffsetData]] = None, + additional_slots_occupied_by_module_id: Optional[ + Dict[str, List[DeckSlotName]] + ] = None, +) -> ModuleView: + """Get a module view test subject with the specified state.""" + state = ModuleState( + deck_type=deck_type or DeckType.OT2_STANDARD, + slot_by_module_id=slot_by_module_id or {}, + requested_model_by_id=requested_model_by_module_id or {}, + hardware_by_module_id=hardware_by_module_id or {}, + substate_by_module_id=substate_by_module_id or {}, + module_offset_by_serial=module_offset_by_serial or {}, + additional_slots_occupied_by_module_id=additional_slots_occupied_by_module_id + or {}, + deck_fixed_labware=[], + ) + + return ModuleView(state=state) + + +def get_sample_parent_module_view( + matching_module_def: ModuleDefinition, + matching_module_id: str, +) -> ModuleView: + """Get a ModuleView with attached modules including a requested matching module.""" + definition = load_shared_data("module/definitions/2/magneticModuleV1.json") + magdeck_def = ModuleDefinition.parse_raw(definition) + + return make_module_view( + slot_by_module_id={ + "id-non-matching": DeckSlotName.SLOT_1, + matching_module_id: DeckSlotName.SLOT_2, + "id-another-non-matching": DeckSlotName.SLOT_3, + }, + hardware_by_module_id={ + "id-non-matching": HardwareModule( + serial_number="serial-non-matching", + definition=magdeck_def, + ), + matching_module_id: HardwareModule( + serial_number="serial-matching", + definition=matching_module_def, + ), + "id-another-non-matching": HardwareModule( + serial_number="serial-another-non-matching", + definition=magdeck_def, + ), + }, + ) + + +def test_initial_module_data_by_id() -> None: + """It should raise if module ID doesn't exist.""" + subject = make_module_view() + + with pytest.raises(errors.ModuleNotLoadedError): + subject.get("helloWorld") + + +def test_get_missing_hardware() -> None: + """It should raise if no loaded hardware.""" + subject = make_module_view(slot_by_module_id={"module-id": DeckSlotName.SLOT_1}) + + with pytest.raises(errors.ModuleNotLoadedError): + subject.get("module-id") + + +def test_get_module_data(tempdeck_v1_def: ModuleDefinition) -> None: + """It should get module data from state by ID.""" + subject = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=tempdeck_v1_def, + ) + }, + ) + + assert subject.get("module-id") == LoadedModule( + id="module-id", + model=ModuleModel.TEMPERATURE_MODULE_V1, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + serialNumber="serial-number", + ) + + +def test_get_location(tempdeck_v1_def: ModuleDefinition) -> None: + """It should return the module's location or raise.""" + subject = make_module_view( + slot_by_module_id={ + "module-1": DeckSlotName.SLOT_1, + "module-2": None, + }, + hardware_by_module_id={ + "module-1": HardwareModule( + serial_number="serial-1", + definition=tempdeck_v1_def, + ), + "module-2": HardwareModule( + serial_number="serial-2", + definition=tempdeck_v1_def, + ), + }, + ) + + assert subject.get_location("module-1") == DeckSlotLocation( + slotName=DeckSlotName.SLOT_1 + ) + + with pytest.raises(errors.ModuleNotOnDeckError): + assert subject.get_location("module-2") + + +def test_get_all_modules( + tempdeck_v1_def: ModuleDefinition, + tempdeck_v2_def: ModuleDefinition, +) -> None: + """It should return all modules in state.""" + subject = make_module_view( + slot_by_module_id={ + "module-1": DeckSlotName.SLOT_1, + "module-2": DeckSlotName.SLOT_2, + }, + hardware_by_module_id={ + "module-1": HardwareModule( + serial_number="serial-1", + definition=tempdeck_v1_def, + ), + "module-2": HardwareModule( + serial_number="serial-2", + definition=tempdeck_v2_def, + ), + }, + ) + + assert subject.get_all() == [ + LoadedModule( + id="module-1", + serialNumber="serial-1", + model=ModuleModel.TEMPERATURE_MODULE_V1, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + ), + LoadedModule( + id="module-2", + serialNumber="serial-2", + model=ModuleModel.TEMPERATURE_MODULE_V2, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_2), + ), + ] + + +def test_get_properties_by_id( + tempdeck_v2_def: ModuleDefinition, + magdeck_v1_def: ModuleDefinition, + mag_block_v1_def: ModuleDefinition, +) -> None: + """It should return a loaded module's properties by ID.""" + subject = make_module_view( + slot_by_module_id={ + "module-1": DeckSlotName.SLOT_1, + "module-2": DeckSlotName.SLOT_2, + "module-3": DeckSlotName.SLOT_3, + }, + requested_model_by_module_id={ + "module-1": ModuleModel.TEMPERATURE_MODULE_V1, + "module-2": ModuleModel.MAGNETIC_MODULE_V1, + "module-3": ModuleModel.MAGNETIC_BLOCK_V1, + }, + hardware_by_module_id={ + "module-1": HardwareModule( + serial_number="serial-1", + # Intentionally different from requested model. + definition=tempdeck_v2_def, + ), + "module-2": HardwareModule( + serial_number="serial-2", + definition=magdeck_v1_def, + ), + "module-3": HardwareModule(serial_number=None, definition=mag_block_v1_def), + }, + ) + + assert subject.get_definition("module-1") == tempdeck_v2_def + assert subject.get_dimensions("module-1") == tempdeck_v2_def.dimensions + assert subject.get_requested_model("module-1") == ModuleModel.TEMPERATURE_MODULE_V1 + assert subject.get_connected_model("module-1") == ModuleModel.TEMPERATURE_MODULE_V2 + assert subject.get_serial_number("module-1") == "serial-1" + assert subject.get_location("module-1") == DeckSlotLocation( + slotName=DeckSlotName.SLOT_1 + ) + + assert subject.get_definition("module-2") == magdeck_v1_def + assert subject.get_dimensions("module-2") == magdeck_v1_def.dimensions + assert subject.get_requested_model("module-2") == ModuleModel.MAGNETIC_MODULE_V1 + assert subject.get_connected_model("module-2") == ModuleModel.MAGNETIC_MODULE_V1 + assert subject.get_serial_number("module-2") == "serial-2" + assert subject.get_location("module-2") == DeckSlotLocation( + slotName=DeckSlotName.SLOT_2 + ) + + assert subject.get_definition("module-3") == mag_block_v1_def + assert subject.get_dimensions("module-3") == mag_block_v1_def.dimensions + assert subject.get_requested_model("module-3") == ModuleModel.MAGNETIC_BLOCK_V1 + assert subject.get_connected_model("module-3") == ModuleModel.MAGNETIC_BLOCK_V1 + assert subject.get_location("module-3") == DeckSlotLocation( + slotName=DeckSlotName.SLOT_3 + ) + + with pytest.raises(errors.ModuleNotConnectedError): + subject.get_serial_number("module-3") + + with pytest.raises(errors.ModuleNotLoadedError): + subject.get_definition("Not a module ID oh no") + + +@pytest.mark.parametrize( + argnames=["module_def", "slot", "expected_offset"], + argvalues=[ + ( + lazy_fixture("tempdeck_v1_def"), + DeckSlotName.SLOT_1, + LabwareOffsetVector(x=-0.15, y=-0.15, z=80.09), + ), + ( + lazy_fixture("tempdeck_v2_def"), + DeckSlotName.SLOT_1, + LabwareOffsetVector(x=-1.45, y=-0.15, z=80.09), + ), + ( + lazy_fixture("tempdeck_v2_def"), + DeckSlotName.SLOT_3, + LabwareOffsetVector(x=1.15, y=-0.15, z=80.09), + ), + ( + lazy_fixture("magdeck_v1_def"), + DeckSlotName.SLOT_1, + LabwareOffsetVector(x=0.125, y=-0.125, z=82.25), + ), + ( + lazy_fixture("magdeck_v2_def"), + DeckSlotName.SLOT_1, + LabwareOffsetVector(x=-1.175, y=-0.125, z=82.25), + ), + ( + lazy_fixture("magdeck_v2_def"), + DeckSlotName.SLOT_3, + LabwareOffsetVector(x=1.425, y=-0.125, z=82.25), + ), + ( + lazy_fixture("thermocycler_v1_def"), + DeckSlotName.SLOT_7, + LabwareOffsetVector(x=0, y=82.56, z=97.8), + ), + ( + lazy_fixture("thermocycler_v2_def"), + DeckSlotName.SLOT_7, + LabwareOffsetVector(x=0, y=68.8, z=108.96), + ), + ( + lazy_fixture("heater_shaker_v1_def"), + DeckSlotName.SLOT_1, + LabwareOffsetVector(x=-0.125, y=1.125, z=68.275), + ), + ( + lazy_fixture("heater_shaker_v1_def"), + DeckSlotName.SLOT_3, + LabwareOffsetVector(x=0.125, y=-1.125, z=68.275), + ), + ], +) +def test_get_module_offset_for_ot2_standard( + module_def: ModuleDefinition, + slot: DeckSlotName, + expected_offset: LabwareOffsetVector, +) -> None: + """It should return the correct labware offset for module in specified slot.""" + subject = make_module_view( + deck_type=DeckType.OT2_STANDARD, + slot_by_module_id={"module-id": slot}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="module-serial", + definition=module_def, + ) + }, + ) + assert ( + subject.get_nominal_offset_to_child("module-id", get_addressable_area_view()) + == expected_offset + ) + + +@pytest.mark.parametrize( + argnames=["module_def", "slot", "expected_offset", "deck_definition"], + argvalues=[ + ( + lazy_fixture("tempdeck_v2_def"), + DeckSlotName.SLOT_1.to_ot3_equivalent(), + LabwareOffsetVector(x=0, y=0, z=9), + lazy_fixture("ot3_standard_deck_def"), + ), + ( + lazy_fixture("tempdeck_v2_def"), + DeckSlotName.SLOT_3.to_ot3_equivalent(), + LabwareOffsetVector(x=0, y=0, z=9), + lazy_fixture("ot3_standard_deck_def"), + ), + ( + lazy_fixture("thermocycler_v2_def"), + DeckSlotName.SLOT_7.to_ot3_equivalent(), + LabwareOffsetVector(x=-20.005, y=67.96, z=10.96), + lazy_fixture("ot3_standard_deck_def"), + ), + ( + lazy_fixture("heater_shaker_v1_def"), + DeckSlotName.SLOT_1.to_ot3_equivalent(), + LabwareOffsetVector(x=0, y=0, z=18.95), + lazy_fixture("ot3_standard_deck_def"), + ), + ( + lazy_fixture("heater_shaker_v1_def"), + DeckSlotName.SLOT_3.to_ot3_equivalent(), + LabwareOffsetVector(x=0, y=0, z=18.95), + lazy_fixture("ot3_standard_deck_def"), + ), + ( + lazy_fixture("mag_block_v1_def"), + DeckSlotName.SLOT_2.to_ot3_equivalent(), + LabwareOffsetVector(x=0, y=0, z=38.0), + lazy_fixture("ot3_standard_deck_def"), + ), + ], +) +def test_get_module_offset_for_ot3_standard( + module_def: ModuleDefinition, + slot: DeckSlotName, + expected_offset: LabwareOffsetVector, + deck_definition: DeckDefinitionV5, +) -> None: + """It should return the correct labware offset for module in specified slot.""" + subject = make_module_view( + deck_type=DeckType.OT3_STANDARD, + slot_by_module_id={"module-id": slot}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="module-serial", + definition=module_def, + ) + }, + ) + + result_offset = subject.get_nominal_offset_to_child( + "module-id", + get_addressable_area_view( + deck_configuration=None, + deck_definition=deck_definition, + use_simulated_deck_config=True, + ), + ) + + assert (result_offset.x, result_offset.y, result_offset.z) == pytest.approx( + (expected_offset.x, expected_offset.y, expected_offset.z) + ) + + +def test_get_magnetic_module_substate( + magdeck_v1_def: ModuleDefinition, + magdeck_v2_def: ModuleDefinition, + heater_shaker_v1_def: ModuleDefinition, +) -> None: + """It should return a substate for the given Magnetic Module, if valid.""" + subject = make_module_view( + slot_by_module_id={ + "magnetic-module-gen1-id": DeckSlotName.SLOT_1, + "magnetic-module-gen2-id": DeckSlotName.SLOT_2, + "heatshake-module-id": DeckSlotName.SLOT_3, + }, + hardware_by_module_id={ + "magnetic-module-gen1-id": HardwareModule( + serial_number="magnetic-module-gen1-serial", + definition=magdeck_v1_def, + ), + "magnetic-module-gen2-id": HardwareModule( + serial_number="magnetic-module-gen2-serial", + definition=magdeck_v2_def, + ), + "heatshake-module-id": HardwareModule( + serial_number="heatshake-module-serial", + definition=heater_shaker_v1_def, + ), + }, + substate_by_module_id={ + "magnetic-module-gen1-id": MagneticModuleSubState( + module_id=MagneticModuleId("magnetic-module-gen1-id"), + model=ModuleModel.MAGNETIC_MODULE_V1, + ), + "magnetic-module-gen2-id": MagneticModuleSubState( + module_id=MagneticModuleId("magnetic-module-gen2-id"), + model=ModuleModel.MAGNETIC_MODULE_V2, + ), + "heatshake-module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("heatshake-module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=False, + plate_target_temperature=None, + ), + }, + ) + + module_1_substate = subject.get_magnetic_module_substate( + module_id="magnetic-module-gen1-id" + ) + assert module_1_substate.module_id == "magnetic-module-gen1-id" + assert module_1_substate.model == ModuleModel.MAGNETIC_MODULE_V1 + + module_2_substate = subject.get_magnetic_module_substate( + module_id="magnetic-module-gen2-id" + ) + assert module_2_substate.module_id == "magnetic-module-gen2-id" + assert module_2_substate.model == ModuleModel.MAGNETIC_MODULE_V2 + + with pytest.raises(errors.WrongModuleTypeError): + subject.get_magnetic_module_substate(module_id="heatshake-module-id") + + with pytest.raises(errors.ModuleNotLoadedError): + subject.get_magnetic_module_substate(module_id="nonexistent-module-id") + + +def test_get_heater_shaker_module_substate( + magdeck_v2_def: ModuleDefinition, + heater_shaker_v1_def: ModuleDefinition, +) -> None: + """It should return a heater-shaker module substate.""" + subject = make_module_view( + slot_by_module_id={ + "magnetic-module-gen2-id": DeckSlotName.SLOT_2, + "heatshake-module-id": DeckSlotName.SLOT_3, + }, + hardware_by_module_id={ + "magnetic-module-gen2-id": HardwareModule( + serial_number="magnetic-module-gen2-serial", + definition=magdeck_v2_def, + ), + "heatshake-module-id": HardwareModule( + serial_number="heatshake-module-serial", + definition=heater_shaker_v1_def, + ), + }, + substate_by_module_id={ + "magnetic-module-gen2-id": MagneticModuleSubState( + module_id=MagneticModuleId("magnetic-module-gen2-id"), + model=ModuleModel.MAGNETIC_MODULE_V2, + ), + "heatshake-module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("heatshake-module-id"), + plate_target_temperature=432, + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=True, + ), + }, + ) + + hs_substate = subject.get_heater_shaker_module_substate( + module_id="heatshake-module-id" + ) + assert hs_substate.module_id == "heatshake-module-id" + assert hs_substate.plate_target_temperature == 432 + assert hs_substate.is_plate_shaking is True + assert hs_substate.labware_latch_status == HeaterShakerLatchStatus.UNKNOWN + + with pytest.raises(errors.WrongModuleTypeError): + subject.get_heater_shaker_module_substate(module_id="magnetic-module-gen2-id") + + with pytest.raises(errors.ModuleNotLoadedError): + subject.get_heater_shaker_module_substate(module_id="nonexistent-module-id") + + +def test_get_temperature_module_substate( + tempdeck_v1_def: ModuleDefinition, + tempdeck_v2_def: ModuleDefinition, + heater_shaker_v1_def: ModuleDefinition, +) -> None: + """It should return a substate for the given Temperature Module, if valid.""" + subject = make_module_view( + slot_by_module_id={ + "temp-module-gen1-id": DeckSlotName.SLOT_1, + "temp-module-gen2-id": DeckSlotName.SLOT_2, + "heatshake-module-id": DeckSlotName.SLOT_3, + }, + hardware_by_module_id={ + "temp-module-gen1-id": HardwareModule( + serial_number="temp-module-gen1-serial", + definition=tempdeck_v1_def, + ), + "temp-module-gen2-id": HardwareModule( + serial_number="temp-module-gen2-serial", + definition=tempdeck_v2_def, + ), + "heatshake-module-id": HardwareModule( + serial_number="heatshake-module-serial", + definition=heater_shaker_v1_def, + ), + }, + substate_by_module_id={ + "temp-module-gen1-id": TemperatureModuleSubState( + module_id=TemperatureModuleId("temp-module-gen1-id"), + plate_target_temperature=None, + ), + "temp-module-gen2-id": TemperatureModuleSubState( + module_id=TemperatureModuleId("temp-module-gen2-id"), + plate_target_temperature=123, + ), + "heatshake-module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("heatshake-module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=False, + plate_target_temperature=None, + ), + }, + ) + + module_1_substate = subject.get_temperature_module_substate( + module_id="temp-module-gen1-id" + ) + assert module_1_substate.module_id == "temp-module-gen1-id" + assert module_1_substate.plate_target_temperature is None + + module_2_substate = subject.get_temperature_module_substate( + module_id="temp-module-gen2-id" + ) + assert module_2_substate.module_id == "temp-module-gen2-id" + assert module_2_substate.plate_target_temperature == 123 + + with pytest.raises(errors.WrongModuleTypeError): + subject.get_temperature_module_substate(module_id="heatshake-module-id") + + with pytest.raises(errors.ModuleNotLoadedError): + subject.get_temperature_module_substate(module_id="nonexistent-module-id") + + +def test_get_plate_target_temperature(heater_shaker_v1_def: ModuleDefinition) -> None: + """It should return whether target temperature is set.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=heater_shaker_v1_def, + ) + }, + substate_by_module_id={ + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=False, + plate_target_temperature=12.3, + ) + }, + ) + subject = module_view.get_heater_shaker_module_substate("module-id") + assert subject.get_plate_target_temperature() == 12.3 + + +def test_get_plate_target_temperature_no_target( + heater_shaker_v1_def: ModuleDefinition, +) -> None: + """It should raise if no target temperature is set.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=heater_shaker_v1_def, + ) + }, + substate_by_module_id={ + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=False, + plate_target_temperature=None, + ) + }, + ) + subject = module_view.get_heater_shaker_module_substate("module-id") + + with pytest.raises(errors.NoTargetTemperatureSetError): + subject.get_plate_target_temperature() + + +def test_get_magnet_home_to_base_offset() -> None: + """It should return the model-specific offset to bottom.""" + subject = make_module_view() + assert ( + subject.get_magnet_home_to_base_offset( + module_model=ModuleModel.MAGNETIC_MODULE_V1 + ) + == 2.5 + ) + assert ( + subject.get_magnet_home_to_base_offset( + module_model=ModuleModel.MAGNETIC_MODULE_V2 + ) + == 2.5 + ) + + +@pytest.mark.parametrize( + "module_model", [ModuleModel.MAGNETIC_MODULE_V1, ModuleModel.MAGNETIC_MODULE_V2] +) +def test_calculate_magnet_height(module_model: ModuleModel) -> None: + """It should use true millimeters as hardware units.""" + subject = make_module_view() + + assert ( + subject.calculate_magnet_height( + module_model=module_model, + height_from_base=100, + ) + == 100 + ) + + # todo(mm, 2022-02-28): + # It's unclear whether this expected result should actually be the same + # between GEN1 and GEN2. + # The GEN1 homing backoff distance looks accidentally halved, for the same reason + # that its heights are halved. If the limit switch hardware is the same for both + # modules, we'd expect the backoff difference to cause a difference in the + # height_from_home test, even though we're measuring everything in true mm. + # https://github.com/Opentrons/opentrons/issues/9585 + assert ( + subject.calculate_magnet_height( + module_model=module_model, + height_from_home=100, + ) + == 97.5 + ) + + assert ( + subject.calculate_magnet_height( + module_model=module_model, + labware_default_height=100, + offset_from_labware_default=10.0, + ) + == 110 + ) + + +@pytest.mark.parametrize( + argnames=["from_slot", "to_slot", "should_dodge"], + argvalues=[ + (DeckSlotName.SLOT_1, DeckSlotName.FIXED_TRASH, True), + (DeckSlotName.FIXED_TRASH, DeckSlotName.SLOT_1, True), + (DeckSlotName.SLOT_4, DeckSlotName.FIXED_TRASH, True), + (DeckSlotName.FIXED_TRASH, DeckSlotName.SLOT_4, True), + (DeckSlotName.SLOT_4, DeckSlotName.SLOT_9, True), + (DeckSlotName.SLOT_9, DeckSlotName.SLOT_4, True), + (DeckSlotName.SLOT_4, DeckSlotName.SLOT_8, True), + (DeckSlotName.SLOT_8, DeckSlotName.SLOT_4, True), + (DeckSlotName.SLOT_1, DeckSlotName.SLOT_8, True), + (DeckSlotName.SLOT_8, DeckSlotName.SLOT_1, True), + (DeckSlotName.SLOT_4, DeckSlotName.SLOT_11, True), + (DeckSlotName.SLOT_11, DeckSlotName.SLOT_4, True), + (DeckSlotName.SLOT_1, DeckSlotName.SLOT_11, True), + (DeckSlotName.SLOT_11, DeckSlotName.SLOT_1, True), + (DeckSlotName.SLOT_2, DeckSlotName.SLOT_4, False), + ], +) +def test_thermocycler_dodging_by_slots( + thermocycler_v1_def: ModuleDefinition, + from_slot: DeckSlotName, + to_slot: DeckSlotName, + should_dodge: bool, +) -> None: + """It should specify if thermocycler dodging is needed. + + It should return True if thermocycler exists and movement is between bad pairs of + slot locations. + """ + subject = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=thermocycler_v1_def, + ) + }, + ) + + assert ( + subject.should_dodge_thermocycler(from_slot=from_slot, to_slot=to_slot) + is should_dodge + ) + + +@pytest.mark.parametrize( + argnames=["from_slot", "to_slot"], + argvalues=[ + (DeckSlotName.SLOT_8, DeckSlotName.SLOT_1), + (DeckSlotName.SLOT_B2, DeckSlotName.SLOT_D1), + ], +) +@pytest.mark.parametrize( + argnames=["module_definition", "should_dodge"], + argvalues=[ + (lazy_fixture("tempdeck_v1_def"), False), + (lazy_fixture("tempdeck_v2_def"), False), + (lazy_fixture("magdeck_v1_def"), False), + (lazy_fixture("magdeck_v2_def"), False), + (lazy_fixture("thermocycler_v1_def"), True), + (lazy_fixture("thermocycler_v2_def"), True), + (lazy_fixture("heater_shaker_v1_def"), False), + ], +) +def test_thermocycler_dodging_by_modules( + from_slot: DeckSlotName, + to_slot: DeckSlotName, + module_definition: ModuleDefinition, + should_dodge: bool, +) -> None: + """It should specify if thermocycler dodging is needed if there is a thermocycler module.""" + subject = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=module_definition, + ) + }, + ) + assert ( + subject.should_dodge_thermocycler(from_slot=from_slot, to_slot=to_slot) + is should_dodge + ) + + +def test_select_hardware_module_to_load_rejects_missing() -> None: + """It should raise if the correct module isn't attached.""" + subject = make_module_view() + + with pytest.raises(errors.ModuleNotAttachedError): + subject.select_hardware_module_to_load( + model=ModuleModel.TEMPERATURE_MODULE_V1, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + attached_modules=[], + ) + + +@pytest.mark.parametrize( + argnames=["requested_model", "attached_definition"], + argvalues=[ + (ModuleModel.TEMPERATURE_MODULE_V1, lazy_fixture("tempdeck_v1_def")), + (ModuleModel.TEMPERATURE_MODULE_V2, lazy_fixture("tempdeck_v2_def")), + (ModuleModel.TEMPERATURE_MODULE_V1, lazy_fixture("tempdeck_v2_def")), + (ModuleModel.TEMPERATURE_MODULE_V2, lazy_fixture("tempdeck_v1_def")), + (ModuleModel.MAGNETIC_MODULE_V1, lazy_fixture("magdeck_v1_def")), + (ModuleModel.MAGNETIC_MODULE_V2, lazy_fixture("magdeck_v2_def")), + (ModuleModel.THERMOCYCLER_MODULE_V1, lazy_fixture("thermocycler_v1_def")), + (ModuleModel.THERMOCYCLER_MODULE_V2, lazy_fixture("thermocycler_v2_def")), + ], +) +def test_select_hardware_module_to_load( + requested_model: ModuleModel, + attached_definition: ModuleDefinition, +) -> None: + """It should return the first attached module that matches.""" + subject = make_module_view() + + attached_modules = [ + HardwareModule(serial_number="serial-1", definition=attached_definition), + HardwareModule(serial_number="serial-2", definition=attached_definition), + ] + + result = subject.select_hardware_module_to_load( + model=requested_model, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + attached_modules=attached_modules, + ) + + assert result == attached_modules[0] + + +def test_select_hardware_module_to_load_skips_non_matching( + magdeck_v1_def: ModuleDefinition, + magdeck_v2_def: ModuleDefinition, +) -> None: + """It should skip over non-matching modules.""" + subject = make_module_view() + + attached_modules = [ + HardwareModule(serial_number="serial-1", definition=magdeck_v1_def), + HardwareModule(serial_number="serial-2", definition=magdeck_v2_def), + ] + + result = subject.select_hardware_module_to_load( + model=ModuleModel.MAGNETIC_MODULE_V2, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + attached_modules=attached_modules, + ) + + assert result == attached_modules[1] + + +def test_select_hardware_module_to_load_skips_already_loaded( + magdeck_v1_def: ModuleDefinition, +) -> None: + """It should skip over already assigned modules.""" + subject = make_module_view( + hardware_by_module_id={ + "module-1": HardwareModule( + serial_number="serial-1", + definition=magdeck_v1_def, + ) + } + ) + + attached_modules = [ + HardwareModule(serial_number="serial-1", definition=magdeck_v1_def), + HardwareModule(serial_number="serial-2", definition=magdeck_v1_def), + ] + + result = subject.select_hardware_module_to_load( + model=ModuleModel.MAGNETIC_MODULE_V1, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_3), + attached_modules=attached_modules, + ) + + assert result == attached_modules[1] + + +def test_select_hardware_module_to_load_reuses_already_loaded( + magdeck_v1_def: ModuleDefinition, +) -> None: + """It should reuse over already assigned modules in the same location.""" + subject = make_module_view( + slot_by_module_id={ + "module-1": DeckSlotName.SLOT_1, + }, + hardware_by_module_id={ + "module-1": HardwareModule( + serial_number="serial-1", + definition=magdeck_v1_def, + ) + }, + ) + + attached_modules = [ + HardwareModule(serial_number="serial-1", definition=magdeck_v1_def), + HardwareModule(serial_number="serial-2", definition=magdeck_v1_def), + ] + + result = subject.select_hardware_module_to_load( + model=ModuleModel.MAGNETIC_MODULE_V1, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + attached_modules=attached_modules, + ) + + assert result == attached_modules[0] + + +def test_select_hardware_module_to_load_rejects_location_reassignment( + magdeck_v1_def: ModuleDefinition, + tempdeck_v1_def: ModuleDefinition, +) -> None: + """It should raise if a non-matching module is already present in the slot.""" + subject = make_module_view( + slot_by_module_id={ + "module-1": DeckSlotName.SLOT_1, + }, + hardware_by_module_id={ + "module-1": HardwareModule( + serial_number="serial-1", + definition=magdeck_v1_def, + ) + }, + ) + + attached_modules = [ + HardwareModule(serial_number="serial-1", definition=magdeck_v1_def), + HardwareModule(serial_number="serial-2", definition=tempdeck_v1_def), + ] + + with pytest.raises(errors.ModuleAlreadyPresentError): + subject.select_hardware_module_to_load( + model=ModuleModel.TEMPERATURE_MODULE_V1, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + attached_modules=attached_modules, + ) + + +class _CalculateMagnetHardwareHeightTestParams(NamedTuple): + definition: ModuleDefinition + mm_from_base: float + expected_result: Optional[float] + expected_exception_type: Union[Type[Exception], None] + + +@pytest.mark.parametrize( + "definition, mm_from_base, expected_result, expected_exception_type", + [ + # Happy cases: + _CalculateMagnetHardwareHeightTestParams( + definition=lazy_fixture("magdeck_v1_def"), + mm_from_base=10, + # TODO(mm, 2022-03-09): It's unclear if this expected result is correct. + # https://github.com/Opentrons/opentrons/issues/9585 + expected_result=25, + expected_exception_type=None, + ), + _CalculateMagnetHardwareHeightTestParams( + definition=lazy_fixture("magdeck_v2_def"), + mm_from_base=10, + expected_result=12.5, + expected_exception_type=None, + ), + # Boundary conditions: + # + # TODO(mm, 2022-03-09): + # In Python >=3.9, improve precision with math.nextafter(). + # Also consider relying on shared constants instead of hard-coding bounds. + # + # TODO(mm, 2022-03-09): It's unclear if the bounds used for V1 modules + # are physically correct. https://github.com/Opentrons/opentrons/issues/9585 + _CalculateMagnetHardwareHeightTestParams( # V1 barely too low. + definition=lazy_fixture("magdeck_v1_def"), + mm_from_base=-2.51, + expected_result=None, + expected_exception_type=errors.EngageHeightOutOfRangeError, + ), + _CalculateMagnetHardwareHeightTestParams( # V1 lowest allowed. + definition=lazy_fixture("magdeck_v1_def"), + mm_from_base=-2.5, + expected_result=0, + expected_exception_type=None, + ), + _CalculateMagnetHardwareHeightTestParams( # V1 highest allowed. + definition=lazy_fixture("magdeck_v1_def"), + mm_from_base=20, + expected_result=45, + expected_exception_type=None, + ), + _CalculateMagnetHardwareHeightTestParams( # V1 barely too high. + definition=lazy_fixture("magdeck_v1_def"), + mm_from_base=20.01, + expected_result=None, + expected_exception_type=errors.EngageHeightOutOfRangeError, + ), + _CalculateMagnetHardwareHeightTestParams( # V2 barely too low. + definition=lazy_fixture("magdeck_v2_def"), + mm_from_base=-2.51, + expected_result=None, + expected_exception_type=errors.EngageHeightOutOfRangeError, + ), + _CalculateMagnetHardwareHeightTestParams( # V2 lowest allowed. + definition=lazy_fixture("magdeck_v2_def"), + mm_from_base=-2.5, + expected_result=0, + expected_exception_type=None, + ), + _CalculateMagnetHardwareHeightTestParams( # V2 highest allowed. + definition=lazy_fixture("magdeck_v2_def"), + mm_from_base=22.5, + expected_result=25, + expected_exception_type=None, + ), + _CalculateMagnetHardwareHeightTestParams( # V2 barely too high. + definition=lazy_fixture("magdeck_v2_def"), + mm_from_base=22.51, + expected_result=None, + expected_exception_type=errors.EngageHeightOutOfRangeError, + ), + ], +) +def test_magnetic_module_view_calculate_magnet_hardware_height( + definition: ModuleDefinition, + mm_from_base: float, + expected_result: float, + expected_exception_type: Union[Type[Exception], None], +) -> None: + """It should return the expected height or raise the expected exception.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=definition, + ) + }, + substate_by_module_id={ + "module-id": MagneticModuleSubState( + module_id=MagneticModuleId("module-id"), + model=definition.model, # type: ignore [arg-type] + ) + }, + ) + subject = module_view.get_magnetic_module_substate("module-id") + expected_raise: ContextManager[None] = ( + # Not sure why mypy has trouble with this. + does_not_raise() # type: ignore[assignment] + if expected_exception_type is None + else pytest.raises(expected_exception_type) + ) + with expected_raise: + result = subject.calculate_magnet_hardware_height(mm_from_base=mm_from_base) + assert result == expected_result + + +@pytest.mark.parametrize("target_temp", [36.8, 95.1]) +def test_validate_heater_shaker_target_temperature_raises( + heater_shaker_v1_def: ModuleDefinition, + target_temp: float, +) -> None: + """It should verify if a target temperature is valid for the specified module.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=heater_shaker_v1_def, + ) + }, + substate_by_module_id={ + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=False, + plate_target_temperature=None, + ) + }, + ) + subject = module_view.get_heater_shaker_module_substate("module-id") + with pytest.raises(errors.InvalidTargetTemperatureError): + subject.validate_target_temperature(target_temp) + + +@pytest.mark.parametrize("target_temp", [37, 94.8]) +def test_validate_heater_shaker_target_temperature( + heater_shaker_v1_def: ModuleDefinition, + target_temp: float, +) -> None: + """It should verify if a target temperature is valid for the specified module.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=heater_shaker_v1_def, + ) + }, + substate_by_module_id={ + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=False, + plate_target_temperature=None, + ) + }, + ) + subject = module_view.get_heater_shaker_module_substate("module-id") + assert subject.validate_target_temperature(target_temp) == target_temp + + +@pytest.mark.parametrize("target_temp", [-10, 99.9]) +def test_validate_temp_module_target_temperature_raises( + tempdeck_v1_def: ModuleDefinition, + target_temp: float, +) -> None: + """It should verify if a target temperature is valid for the specified module.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=tempdeck_v1_def, + ) + }, + substate_by_module_id={ + "module-id": TemperatureModuleSubState( + module_id=TemperatureModuleId("module-id"), + plate_target_temperature=None, + ) + }, + ) + subject = module_view.get_temperature_module_substate("module-id") + with pytest.raises(errors.InvalidTargetTemperatureError): + subject.validate_target_temperature(target_temp) + + +@pytest.mark.parametrize( + ["target_temp", "validated_temp"], [(-9.431, -9), (0, 0), (99.1, 99)] +) +def test_validate_temp_module_target_temperature( + tempdeck_v2_def: ModuleDefinition, target_temp: float, validated_temp: int +) -> None: + """It should verify if a target temperature is valid for the specified module.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=tempdeck_v2_def, + ) + }, + substate_by_module_id={ + "module-id": TemperatureModuleSubState( + module_id=TemperatureModuleId("module-id"), + plate_target_temperature=None, + ) + }, + ) + subject = module_view.get_temperature_module_substate("module-id") + assert subject.validate_target_temperature(target_temp) == validated_temp + + +@pytest.mark.parametrize( + argnames=["rpm_param", "validated_param"], + argvalues=[(200.1, 200), (250.6, 251), (300.9, 301)], +) +def test_validate_heater_shaker_target_speed_converts_to_int( + rpm_param: float, validated_param: bool, heater_shaker_v1_def: ModuleDefinition +) -> None: + """It should validate heater-shaker target rpm.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=heater_shaker_v1_def, + ) + }, + substate_by_module_id={ + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=False, + plate_target_temperature=None, + ) + }, + ) + subject = module_view.get_heater_shaker_module_substate("module-id") + assert subject.validate_target_speed(rpm_param) == validated_param + + +@pytest.mark.parametrize( + argnames=["rpm_param", "expected_valid"], + argvalues=[(199.4, False), (199.5, True), (3000.7, False), (3000.4, True)], +) +def test_validate_heater_shaker_target_speed_raises_error( + rpm_param: float, expected_valid: bool, heater_shaker_v1_def: ModuleDefinition +) -> None: + """It should validate heater-shaker target rpm.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=heater_shaker_v1_def, + ) + }, + substate_by_module_id={ + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=False, + plate_target_temperature=None, + ) + }, + ) + subject = module_view.get_heater_shaker_module_substate("module-id") + if not expected_valid: + with pytest.raises(errors.InvalidTargetSpeedError): + subject.validate_target_speed(rpm_param) + + +def test_raise_if_labware_latch_not_closed( + heater_shaker_v1_def: ModuleDefinition, +) -> None: + """It should raise an error if labware latch is not closed.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=heater_shaker_v1_def, + ) + }, + substate_by_module_id={ + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.OPEN, + is_plate_shaking=False, + plate_target_temperature=None, + ) + }, + ) + subject = module_view.get_heater_shaker_module_substate("module-id") + with pytest.raises(errors.CannotPerformModuleAction, match="is open"): + subject.raise_if_labware_latch_not_closed() + + +def test_raise_if_labware_latch_unknown( + heater_shaker_v1_def: ModuleDefinition, +) -> None: + """It should raise an error if labware latch is not closed.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=heater_shaker_v1_def, + ) + }, + substate_by_module_id={ + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=False, + plate_target_temperature=None, + ) + }, + ) + subject = module_view.get_heater_shaker_module_substate("module-id") + with pytest.raises(errors.CannotPerformModuleAction, match="set to closed"): + subject.raise_if_labware_latch_not_closed() + + +def test_heater_shaker_raise_if_shaking( + heater_shaker_v1_def: ModuleDefinition, +) -> None: + """It should raise when heater-shaker is shaking.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=heater_shaker_v1_def, + ) + }, + substate_by_module_id={ + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.UNKNOWN, + is_plate_shaking=True, + plate_target_temperature=None, + ) + }, + ) + subject = module_view.get_heater_shaker_module_substate("module-id") + with pytest.raises(errors.CannotPerformModuleAction): + subject.raise_if_shaking() + + +def test_get_heater_shaker_movement_data( + heater_shaker_v1_def: ModuleDefinition, + tempdeck_v2_def: ModuleDefinition, +) -> None: + """It should get heater-shaker movement data.""" + module_view = make_module_view( + slot_by_module_id={ + "module-id": DeckSlotName.SLOT_1, + "other-module-id": DeckSlotName.SLOT_5, + }, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=heater_shaker_v1_def, + ), + "other-module-id": HardwareModule( + serial_number="other-serial-number", + definition=tempdeck_v2_def, + ), + }, + substate_by_module_id={ + "module-id": HeaterShakerModuleSubState( + module_id=HeaterShakerModuleId("module-id"), + labware_latch_status=HeaterShakerLatchStatus.CLOSED, + is_plate_shaking=False, + plate_target_temperature=None, + ), + "other-module-id": TemperatureModuleSubState( + module_id=TemperatureModuleId("other-module-id"), + plate_target_temperature=None, + ), + }, + ) + subject = module_view.get_heater_shaker_movement_restrictors() + assert len(subject) == 1 + for hs_movement_data in subject: + assert not hs_movement_data.plate_shaking + assert hs_movement_data.latch_status + assert hs_movement_data.deck_slot == 1 + + +def test_tempdeck_get_plate_target_temperature( + tempdeck_v2_def: ModuleDefinition, +) -> None: + """It should return whether target temperature is set.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=tempdeck_v2_def, + ) + }, + substate_by_module_id={ + "module-id": TemperatureModuleSubState( + module_id=TemperatureModuleId("module-id"), + plate_target_temperature=12, + ) + }, + ) + subject = module_view.get_temperature_module_substate("module-id") + assert subject.get_plate_target_temperature() == 12 + + +def test_tempdeck_get_plate_target_temperature_no_target( + tempdeck_v2_def: ModuleDefinition, +) -> None: + """It should raise if no target temperature is set.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=tempdeck_v2_def, + ) + }, + substate_by_module_id={ + "module-id": TemperatureModuleSubState( + module_id=TemperatureModuleId("module-id"), + plate_target_temperature=None, + ) + }, + ) + subject = module_view.get_temperature_module_substate("module-id") + + with pytest.raises(errors.NoTargetTemperatureSetError): + subject.get_plate_target_temperature() + + +def test_thermocycler_get_target_temperatures( + thermocycler_v1_def: ModuleDefinition, +) -> None: + """It should return whether target temperature for thermocycler is set.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=thermocycler_v1_def, + ) + }, + substate_by_module_id={ + "module-id": ThermocyclerModuleSubState( + module_id=ThermocyclerModuleId("module-id"), + is_lid_open=False, + target_block_temperature=14, + target_lid_temperature=28, + ) + }, + ) + subject = module_view.get_thermocycler_module_substate("module-id") + assert subject.get_target_block_temperature() == 14 + assert subject.get_target_lid_temperature() == 28 + + +def test_thermocycler_get_target_temperatures_no_target( + thermocycler_v1_def: ModuleDefinition, +) -> None: + """It should raise if no target temperature is set.""" + module_view = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=thermocycler_v1_def, + ) + }, + substate_by_module_id={ + "module-id": ThermocyclerModuleSubState( + module_id=ThermocyclerModuleId("module-id"), + is_lid_open=False, + target_block_temperature=None, + target_lid_temperature=None, + ) + }, + ) + subject = module_view.get_thermocycler_module_substate("module-id") + + with pytest.raises(errors.NoTargetTemperatureSetError): + subject.get_target_block_temperature() + subject.get_target_lid_temperature() + + +@pytest.fixture +def module_view_with_thermocycler(thermocycler_v1_def: ModuleDefinition) -> ModuleView: + """Get a module state view with a loaded thermocycler.""" + return make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=thermocycler_v1_def, + ) + }, + substate_by_module_id={ + "module-id": ThermocyclerModuleSubState( + module_id=ThermocyclerModuleId("module-id"), + target_block_temperature=None, + target_lid_temperature=None, + is_lid_open=False, + ) + }, + ) + + +@pytest.mark.parametrize("input_temperature", [0, 0.0, 0.001, 98.999, 99, 99.0]) +def test_thermocycler_validate_target_block_temperature( + module_view_with_thermocycler: ModuleView, + input_temperature: float, +) -> None: + """It should return a valid target block temperature.""" + subject = module_view_with_thermocycler.get_thermocycler_module_substate( + "module-id" + ) + result = subject.validate_target_block_temperature(input_temperature) + + assert result == input_temperature + + +@pytest.mark.parametrize( + argnames=["input_time", "validated_time"], + argvalues=[(0.0, 0.0), (0.123, 0.123), (123.456, 123.456), (1234567, 1234567)], +) +def test_thermocycler_validate_hold_time( + module_view_with_thermocycler: ModuleView, + input_time: float, + validated_time: float, +) -> None: + """It should return a valid hold time.""" + subject = module_view_with_thermocycler.get_thermocycler_module_substate( + "module-id" + ) + result = subject.validate_hold_time(input_time) + + assert result == validated_time + + +@pytest.mark.parametrize("input_time", [-0.1, -123]) +def test_thermocycler_validate_hold_time_raises( + module_view_with_thermocycler: ModuleView, + input_time: float, +) -> None: + """It should raise on invalid hold time.""" + subject = module_view_with_thermocycler.get_thermocycler_module_substate( + "module-id" + ) + + with pytest.raises(errors.InvalidHoldTimeError): + subject.validate_hold_time(input_time) + + +@pytest.mark.parametrize("input_temperature", [-0.001, 99.001]) +def test_thermocycler_validate_target_block_temperature_raises( + module_view_with_thermocycler: ModuleView, + input_temperature: float, +) -> None: + """It should raise on invalid target block temperature.""" + subject = module_view_with_thermocycler.get_thermocycler_module_substate( + "module-id" + ) + + with pytest.raises(errors.InvalidTargetTemperatureError): + subject.validate_target_block_temperature(input_temperature) + + +@pytest.mark.parametrize("input_volume", [0, 0.0, 0.001, 50.0, 99.999, 100, 100.0]) +def test_thermocycler_validate_block_max_volume( + module_view_with_thermocycler: ModuleView, + input_volume: float, +) -> None: + """It should return a validated max block volume value.""" + subject = module_view_with_thermocycler.get_thermocycler_module_substate( + "module-id" + ) + result = subject.validate_max_block_volume(input_volume) + + assert result == input_volume + + +@pytest.mark.parametrize("input_volume", [-10, -0.001, 100.001]) +def test_thermocycler_validate_block_max_volume_raises( + module_view_with_thermocycler: ModuleView, + input_volume: float, +) -> None: + """It should raise on invalid block volume temperature.""" + subject = module_view_with_thermocycler.get_thermocycler_module_substate( + "module-id" + ) + + with pytest.raises(errors.InvalidBlockVolumeError): + subject.validate_max_block_volume(input_volume) + + +@pytest.mark.parametrize("input_temperature", [37, 37.0, 37.001, 109.999, 110, 110.0]) +def test_thermocycler_validate_target_lid_temperature( + module_view_with_thermocycler: ModuleView, + input_temperature: float, +) -> None: + """It should return a valid target block temperature.""" + subject = module_view_with_thermocycler.get_thermocycler_module_substate( + "module-id" + ) + result = subject.validate_target_lid_temperature(input_temperature) + + assert result == input_temperature + + +@pytest.mark.parametrize("input_temperature", [36.999, 110.001]) +def test_thermocycler_validate_target_lid_temperature_raises( + module_view_with_thermocycler: ModuleView, + input_temperature: float, +) -> None: + """It should raise on invalid target block temperature.""" + subject = module_view_with_thermocycler.get_thermocycler_module_substate( + "module-id" + ) + + with pytest.raises(errors.InvalidTargetTemperatureError): + subject.validate_target_lid_temperature(input_temperature) + + +@pytest.mark.parametrize( + ("module_definition", "expected_height"), + [ + (lazy_fixture("thermocycler_v1_def"), 98.0), + (lazy_fixture("tempdeck_v1_def"), 84.0), + (lazy_fixture("tempdeck_v2_def"), 84.0), + (lazy_fixture("magdeck_v1_def"), 110.152), + (lazy_fixture("magdeck_v2_def"), 110.152), + (lazy_fixture("heater_shaker_v1_def"), 82.0), + ], +) +def test_get_overall_height( + module_definition: ModuleDefinition, + expected_height: float, +) -> None: + """It should get a module's overall height.""" + subject = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_7}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=module_definition, + ) + }, + ) + + result = subject.get_overall_height("module-id") + assert result == expected_height + + +@pytest.mark.parametrize( + argnames=["location", "expected_raise"], + argvalues=[ + ( + DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + pytest.raises(errors.LocationIsOccupiedError), + ), + (DeckSlotLocation(slotName=DeckSlotName.SLOT_2), does_not_raise()), + (DeckSlotLocation(slotName=DeckSlotName.FIXED_TRASH), does_not_raise()), + ], +) +def test_raise_if_labware_in_location( + location: DeckSlotLocation, + expected_raise: ContextManager[Any], + thermocycler_v1_def: ModuleDefinition, +) -> None: + """It should raise if there is module in specified location.""" + subject = make_module_view( + slot_by_module_id={"module-id-1": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id-1": HardwareModule( + serial_number="serial-number", + definition=thermocycler_v1_def, + ) + }, + substate_by_module_id={ + "module-id-1": ThermocyclerModuleSubState( + module_id=ThermocyclerModuleId("module-id-1"), + is_lid_open=False, + target_block_temperature=None, + target_lid_temperature=None, + ) + }, + ) + with expected_raise: + subject.raise_if_module_in_location(location=location) + + +def test_get_by_slot() -> None: + """It should get the module in a given slot.""" + subject = make_module_view( + slot_by_module_id={ + "1": DeckSlotName.SLOT_1, + "2": DeckSlotName.SLOT_2, + }, + hardware_by_module_id={ + "1": HardwareModule( + serial_number="serial-number-1", + definition=ModuleDefinition.construct( # type: ignore[call-arg] + model=ModuleModel.TEMPERATURE_MODULE_V1 + ), + ), + "2": HardwareModule( + serial_number="serial-number-2", + definition=ModuleDefinition.construct( # type: ignore[call-arg] + model=ModuleModel.TEMPERATURE_MODULE_V2 + ), + ), + }, + ) + + assert subject.get_by_slot(DeckSlotName.SLOT_1) == LoadedModule( + id="1", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + model=ModuleModel.TEMPERATURE_MODULE_V1, + serialNumber="serial-number-1", + ) + assert subject.get_by_slot(DeckSlotName.SLOT_2) == LoadedModule( + id="2", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_2), + model=ModuleModel.TEMPERATURE_MODULE_V2, + serialNumber="serial-number-2", + ) + assert subject.get_by_slot(DeckSlotName.SLOT_3) is None + + +def test_get_by_slot_prefers_later() -> None: + """It should get the module in a slot, preferring later items if locations match.""" + subject = make_module_view( + slot_by_module_id={ + "1": DeckSlotName.SLOT_1, + "1-again": DeckSlotName.SLOT_1, + }, + hardware_by_module_id={ + "1": HardwareModule( + serial_number="serial-number-1", + definition=ModuleDefinition.construct( # type: ignore[call-arg] + model=ModuleModel.TEMPERATURE_MODULE_V1 + ), + ), + "1-again": HardwareModule( + serial_number="serial-number-1-again", + definition=ModuleDefinition.construct( # type: ignore[call-arg] + model=ModuleModel.TEMPERATURE_MODULE_V1 + ), + ), + }, + ) + + assert subject.get_by_slot(DeckSlotName.SLOT_1) == LoadedModule( + id="1-again", + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + model=ModuleModel.TEMPERATURE_MODULE_V1, + serialNumber="serial-number-1-again", + ) + + +@pytest.mark.parametrize( + argnames=["mount", "target_slot", "expected_result"], + argvalues=[ + (MountType.RIGHT, DeckSlotName.SLOT_1, False), + (MountType.RIGHT, DeckSlotName.SLOT_2, True), + (MountType.RIGHT, DeckSlotName.SLOT_5, False), + (MountType.LEFT, DeckSlotName.SLOT_3, False), + (MountType.RIGHT, DeckSlotName.SLOT_5, False), + (MountType.LEFT, DeckSlotName.SLOT_8, True), + ], +) +def test_is_edge_move_unsafe( + mount: MountType, target_slot: DeckSlotName, expected_result: bool +) -> None: + """It should determine if an edge move would be unsafe.""" + subject = make_module_view( + slot_by_module_id={"foo": DeckSlotName.SLOT_1, "bar": DeckSlotName.SLOT_9} + ) + + result = subject.is_edge_move_unsafe(mount=mount, target_slot=target_slot) + + assert result is expected_result + + +@pytest.mark.parametrize( + argnames=["module_def", "expected_offset_data"], + argvalues=[ + ( + lazy_fixture("thermocycler_v2_def"), + LabwareMovementOffsetData( + pickUpOffset=LabwareOffsetVector(x=0, y=0, z=4.6), + dropOffset=LabwareOffsetVector(x=0, y=0, z=5.6), + ), + ), + ( + lazy_fixture("heater_shaker_v1_def"), + LabwareMovementOffsetData( + pickUpOffset=LabwareOffsetVector(x=0, y=0, z=0), + dropOffset=LabwareOffsetVector(x=0, y=0, z=1.0), + ), + ), + ( + lazy_fixture("tempdeck_v1_def"), + None, + ), + ], +) +def test_get_default_gripper_offsets( + module_def: ModuleDefinition, + expected_offset_data: Optional[LabwareMovementOffsetData], +) -> None: + """It should return the correct gripper offsets, if present.""" + subject = make_module_view( + slot_by_module_id={ + "module-1": DeckSlotName.SLOT_1, + }, + requested_model_by_module_id={ + "module-1": ModuleModel.TEMPERATURE_MODULE_V1, # Does not matter + }, + hardware_by_module_id={ + "module-1": HardwareModule( + serial_number="serial-1", + definition=module_def, + ), + }, + ) + assert subject.get_default_gripper_offsets("module-1") == expected_offset_data + + +@pytest.mark.parametrize( + argnames=["deck_type", "slot_name", "expected_highest_z", "deck_definition"], + argvalues=[ + ( + DeckType.OT2_STANDARD, + DeckSlotName.SLOT_1, + 84, + lazy_fixture("ot3_standard_deck_def"), + ), + ( + DeckType.OT3_STANDARD, + DeckSlotName.SLOT_D1, + 12.91, + lazy_fixture("ot3_standard_deck_def"), + ), + ], +) +def test_get_module_highest_z( + tempdeck_v2_def: ModuleDefinition, + deck_type: DeckType, + slot_name: DeckSlotName, + expected_highest_z: float, + deck_definition: DeckDefinitionV5, +) -> None: + """It should get the highest z point of the module.""" + subject = make_module_view( + deck_type=deck_type, + slot_by_module_id={"module-id": slot_name}, + requested_model_by_module_id={ + "module-id": ModuleModel.TEMPERATURE_MODULE_V2, + }, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="module-serial", + definition=tempdeck_v2_def, + ) + }, + ) + assert isclose( + subject.get_module_highest_z( + module_id="module-id", + addressable_areas=get_addressable_area_view( + deck_configuration=None, + deck_definition=deck_definition, + use_simulated_deck_config=True, + ), + ), + expected_highest_z, + ) + + +def test_get_overflowed_module_in_slot(tempdeck_v1_def: ModuleDefinition) -> None: + """It should return the module occupying but not loaded in the given slot.""" + subject = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=tempdeck_v1_def, + ) + }, + additional_slots_occupied_by_module_id={ + "module-id": [DeckSlotName.SLOT_6, DeckSlotName.SLOT_A1], + }, + ) + assert subject.get_overflowed_module_in_slot(DeckSlotName.SLOT_6) == LoadedModule( + id="module-id", + model=ModuleModel.TEMPERATURE_MODULE_V1, + location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + serialNumber="serial-number", + ) + + +@pytest.mark.parametrize( + argnames=["deck_type", "module_def", "module_slot", "expected_result"], + argvalues=[ + ( + DeckType.OT3_STANDARD, + lazy_fixture("thermocycler_v2_def"), + DeckSlotName.SLOT_A1, + True, + ), + ( + DeckType.OT3_STANDARD, + lazy_fixture("tempdeck_v1_def"), + DeckSlotName.SLOT_A1, + False, + ), + ( + DeckType.OT3_STANDARD, + lazy_fixture("thermocycler_v2_def"), + DeckSlotName.SLOT_1, + False, + ), + ( + DeckType.OT2_STANDARD, + lazy_fixture("thermocycler_v2_def"), + DeckSlotName.SLOT_A1, + False, + ), + ], +) +def test_is_flex_deck_with_thermocycler( + deck_type: DeckType, + module_def: ModuleDefinition, + module_slot: DeckSlotName, + expected_result: bool, +) -> None: + """It should return True if there is a thermocycler on Flex.""" + subject = make_module_view( + slot_by_module_id={"module-id": DeckSlotName.SLOT_B1}, + hardware_by_module_id={ + "module-id": HardwareModule( + serial_number="serial-number", + definition=module_def, + ) + }, + additional_slots_occupied_by_module_id={ + "module-id": [module_slot, DeckSlotName.SLOT_C1], + }, + deck_type=deck_type, + ) + assert subject.is_flex_deck_with_thermocycler() == expected_result diff --git a/api/tests/opentrons/protocol_engine/state/test_motion_view.py b/api/tests/opentrons/protocol_engine/state/test_motion_view.py index 278fff82023..9e7307f29a7 100644 --- a/api/tests/opentrons/protocol_engine/state/test_motion_view.py +++ b/api/tests/opentrons/protocol_engine/state/test_motion_view.py @@ -20,13 +20,13 @@ MotorAxis, AddressableOffsetVector, ) -from opentrons.protocol_engine.state import PipetteLocationData, move_types +from opentrons.protocol_engine.state import _move_types from opentrons.protocol_engine.state.config import Config from opentrons.protocol_engine.state.labware import LabwareView from opentrons.protocol_engine.state.pipettes import PipetteView from opentrons.protocol_engine.state.addressable_areas import AddressableAreaView from opentrons.protocol_engine.state.geometry import GeometryView -from opentrons.protocol_engine.state.motion import MotionView +from opentrons.protocol_engine.state.motion import MotionView, PipetteLocationData from opentrons.protocol_engine.state.modules import ModuleView from opentrons.protocol_engine.state.module_substates import HeaterShakerModuleId from opentrons_shared_data.robot.types import RobotType @@ -46,10 +46,10 @@ def patch_mock_get_waypoints(decoy: Decoy, monkeypatch: pytest.MonkeyPatch) -> N @pytest.fixture(autouse=True) -def patch_mock_move_types(decoy: Decoy, monkeypatch: pytest.MonkeyPatch) -> None: - """Mock out move_types.py functions.""" - for name, func in inspect.getmembers(move_types, inspect.isfunction): - monkeypatch.setattr(move_types, name, decoy.mock(func=func)) +def patch_mock__move_types(decoy: Decoy, monkeypatch: pytest.MonkeyPatch) -> None: + """Mock out _move_types.py functions.""" + for name, func in inspect.getmembers(_move_types, inspect.isfunction): + monkeypatch.setattr(_move_types, name, decoy.mock(func=func)) @pytest.fixture @@ -309,11 +309,13 @@ def test_get_movement_waypoints_to_well_for_y_center( ).then_return(False) decoy.when( - geometry_view.get_well_position("labware-id", "well-name", WellLocation()) + geometry_view.get_well_position( + "labware-id", "well-name", WellLocation(), None, "pipette-id" + ) ).then_return(Point(x=4, y=5, z=6)) decoy.when( - move_types.get_move_type_to_well( + _move_types.get_move_type_to_well( "pipette-id", "labware-id", "well-name", location, True ) ).then_return(motion_planning.MoveType.GENERAL_ARC) @@ -391,11 +393,13 @@ def test_get_movement_waypoints_to_well_for_xy_center( ).then_return(True) decoy.when( - geometry_view.get_well_position("labware-id", "well-name", WellLocation()) + geometry_view.get_well_position( + "labware-id", "well-name", WellLocation(), None, "pipette-id" + ) ).then_return(Point(x=4, y=5, z=6)) decoy.when( - move_types.get_move_type_to_well( + _move_types.get_move_type_to_well( "pipette-id", "labware-id", "well-name", location, True ) ).then_return(motion_planning.MoveType.GENERAL_ARC) @@ -460,6 +464,8 @@ def test_get_movement_waypoints_to_well_raises( labware_id="labware-id", well_name="A1", well_location=None, + operation_volume=None, + pipette_id="pipette-id", ) ).then_return(Point(x=4, y=5, z=6)) decoy.when(pipette_view.get_current_location()).then_return(None) @@ -910,18 +916,18 @@ def test_get_touch_tip_waypoints( labware_view.get_edge_path_type( "labware-id", "B2", MountType.LEFT, DeckSlotName.SLOT_4, True ) - ).then_return(move_types.EdgePathType.RIGHT) + ).then_return(_move_types.EdgePathType.RIGHT) decoy.when( labware_view.get_well_radial_offsets("labware-id", "B2", 0.123) ).then_return((1.2, 3.4)) decoy.when( - move_types.get_edge_point_list( + _move_types.get_edge_point_list( center=center_point, x_radius=1.2, y_radius=3.4, - edge_path_type=move_types.EdgePathType.RIGHT, + edge_path_type=_move_types.EdgePathType.RIGHT, ) ).then_return([Point(x=11, y=22, z=33), Point(x=44, y=55, z=66)]) diff --git a/api/tests/opentrons/protocol_engine/state/test_move_types.py b/api/tests/opentrons/protocol_engine/state/test_move_types.py index 875ec0483ba..9d46cb8a1ab 100644 --- a/api/tests/opentrons/protocol_engine/state/test_move_types.py +++ b/api/tests/opentrons/protocol_engine/state/test_move_types.py @@ -4,7 +4,7 @@ from opentrons.types import Point from opentrons.motion_planning.types import MoveType -from opentrons.protocol_engine.state import move_types as subject +from opentrons.protocol_engine.state import _move_types as subject from opentrons.protocol_engine.types import CurrentWell diff --git a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py b/api/tests/opentrons/protocol_engine/state/test_pipette_store.py deleted file mode 100644 index a49c9255605..00000000000 --- a/api/tests/opentrons/protocol_engine/state/test_pipette_store.py +++ /dev/null @@ -1,1307 +0,0 @@ -"""Tests for pipette state changes in the protocol_engine state store.""" -import pytest -from datetime import datetime -from typing import Optional, Union - -from opentrons_shared_data.pipette.types import PipetteNameType -from opentrons_shared_data.pipette import pipette_definition - -from opentrons.types import DeckSlotName, MountType, Point -from opentrons.protocol_engine import commands as cmd -from opentrons.protocol_engine.commands.command import DefinedErrorData -from opentrons.protocol_engine.commands.pipetting_common import ( - LiquidNotFoundError, - LiquidNotFoundErrorInternalData, - OverpressureError, - OverpressureErrorInternalData, -) -from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryType -from opentrons.protocol_engine.types import ( - DeckPoint, - DeckSlotLocation, - LoadedPipette, - OFF_DECK_LOCATION, - LabwareMovementStrategy, - FlowRates, - CurrentWell, - TipGeometry, -) -from opentrons.protocol_engine.actions import ( - FailCommandAction, - SetPipetteMovementSpeedAction, - SucceedCommandAction, -) -from opentrons.protocol_engine.state.pipettes import ( - PipetteStore, - PipetteState, - CurrentDeckPoint, - StaticPipetteConfig, - BoundingNozzlesOffsets, - PipetteBoundingBoxOffsets, -) -from opentrons.protocol_engine.resources.pipette_data_provider import ( - LoadedStaticPipetteData, -) - -from .command_fixtures import ( - create_load_pipette_command, - create_aspirate_command, - create_aspirate_in_place_command, - create_dispense_command, - create_dispense_in_place_command, - create_pick_up_tip_command, - create_drop_tip_command, - create_drop_tip_in_place_command, - create_unsafe_drop_tip_in_place_command, - create_touch_tip_command, - create_move_to_well_command, - create_blow_out_command, - create_blow_out_in_place_command, - create_move_labware_command, - create_move_to_coordinates_command, - create_move_relative_command, - create_prepare_to_aspirate_command, - create_unsafe_blow_out_in_place_command, -) -from ..pipette_fixtures import get_default_nozzle_map - - -@pytest.fixture -def subject() -> PipetteStore: - """Get a PipetteStore test subject for all subsequent tests.""" - return PipetteStore() - - -def test_sets_initial_state(subject: PipetteStore) -> None: - """It should initialize its state object properly.""" - result = subject.state - - assert result == PipetteState( - pipettes_by_id={}, - aspirated_volume_by_id={}, - current_location=None, - current_deck_point=CurrentDeckPoint(mount=None, deck_point=None), - attached_tip_by_id={}, - movement_speed_by_id={}, - static_config_by_id={}, - flow_rates_by_id={}, - nozzle_configuration_by_id={}, - liquid_presence_detection_by_id={}, - ) - - -def test_handles_load_pipette(subject: PipetteStore) -> None: - """It should add the pipette data to the state.""" - command = create_load_pipette_command( - pipette_id="pipette-id", - pipette_name=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - - subject.handle_action(SucceedCommandAction(private_result=None, command=command)) - - result = subject.state - - assert result.pipettes_by_id["pipette-id"] == LoadedPipette( - id="pipette-id", - pipetteName=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - assert result.aspirated_volume_by_id["pipette-id"] is None - assert result.movement_speed_by_id["pipette-id"] is None - assert result.attached_tip_by_id["pipette-id"] is None - - -def test_handles_pick_up_and_drop_tip(subject: PipetteStore) -> None: - """It should set tip and volume details on pick up and drop tip.""" - load_pipette_command = create_load_pipette_command( - pipette_id="abc", - pipette_name=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - - pick_up_tip_command = create_pick_up_tip_command( - pipette_id="abc", tip_volume=42, tip_length=101, tip_diameter=8.0 - ) - - drop_tip_command = create_drop_tip_command( - pipette_id="abc", - ) - - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=pick_up_tip_command) - ) - assert subject.state.attached_tip_by_id["abc"] == TipGeometry( - volume=42, length=101, diameter=8.0 - ) - assert subject.state.aspirated_volume_by_id["abc"] == 0 - - subject.handle_action( - SucceedCommandAction(private_result=None, command=drop_tip_command) - ) - assert subject.state.attached_tip_by_id["abc"] is None - assert subject.state.aspirated_volume_by_id["abc"] is None - - -def test_handles_drop_tip_in_place(subject: PipetteStore) -> None: - """It should clear tip and volume details after a drop tip in place.""" - load_pipette_command = create_load_pipette_command( - pipette_id="xyz", - pipette_name=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - - pick_up_tip_command = create_pick_up_tip_command( - pipette_id="xyz", tip_volume=42, tip_length=101, tip_diameter=8.0 - ) - - drop_tip_in_place_command = create_drop_tip_in_place_command( - pipette_id="xyz", - ) - - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=pick_up_tip_command) - ) - assert subject.state.attached_tip_by_id["xyz"] == TipGeometry( - volume=42, length=101, diameter=8.0 - ) - assert subject.state.aspirated_volume_by_id["xyz"] == 0 - - subject.handle_action( - SucceedCommandAction(private_result=None, command=drop_tip_in_place_command) - ) - assert subject.state.attached_tip_by_id["xyz"] is None - assert subject.state.aspirated_volume_by_id["xyz"] is None - - -def test_handles_unsafe_drop_tip_in_place(subject: PipetteStore) -> None: - """It should clear tip and volume details after a drop tip in place.""" - load_pipette_command = create_load_pipette_command( - pipette_id="xyz", - pipette_name=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - - pick_up_tip_command = create_pick_up_tip_command( - pipette_id="xyz", tip_volume=42, tip_length=101, tip_diameter=8.0 - ) - - unsafe_drop_tip_in_place_command = create_unsafe_drop_tip_in_place_command( - pipette_id="xyz", - ) - - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=pick_up_tip_command) - ) - assert subject.state.attached_tip_by_id["xyz"] == TipGeometry( - volume=42, length=101, diameter=8.0 - ) - assert subject.state.aspirated_volume_by_id["xyz"] == 0 - - subject.handle_action( - SucceedCommandAction( - private_result=None, command=unsafe_drop_tip_in_place_command - ) - ) - assert subject.state.attached_tip_by_id["xyz"] is None - assert subject.state.aspirated_volume_by_id["xyz"] is None - - -@pytest.mark.parametrize( - "aspirate_command", - [ - create_aspirate_command(pipette_id="pipette-id", volume=42, flow_rate=1.23), - create_aspirate_in_place_command( - pipette_id="pipette-id", volume=42, flow_rate=1.23 - ), - ], -) -def test_aspirate_adds_volume( - subject: PipetteStore, aspirate_command: cmd.Command -) -> None: - """It should add volume to pipette after an aspirate.""" - load_command = create_load_pipette_command( - pipette_id="pipette-id", - pipette_name=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_command) - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=aspirate_command) - ) - - assert subject.state.aspirated_volume_by_id["pipette-id"] == 42 - - subject.handle_action( - SucceedCommandAction(private_result=None, command=aspirate_command) - ) - - assert subject.state.aspirated_volume_by_id["pipette-id"] == 84 - - -@pytest.mark.parametrize( - "dispense_command", - [ - create_dispense_command(pipette_id="pipette-id", volume=21, flow_rate=1.23), - create_dispense_in_place_command( - pipette_id="pipette-id", - volume=21, - flow_rate=1.23, - ), - ], -) -def test_dispense_subtracts_volume( - subject: PipetteStore, dispense_command: cmd.Command -) -> None: - """It should subtract volume from pipette after a dispense.""" - load_command = create_load_pipette_command( - pipette_id="pipette-id", - pipette_name=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - aspirate_command = create_aspirate_command( - pipette_id="pipette-id", - volume=42, - flow_rate=1.23, - ) - - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_command) - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=aspirate_command) - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=dispense_command) - ) - - assert subject.state.aspirated_volume_by_id["pipette-id"] == 21 - - subject.handle_action( - SucceedCommandAction(private_result=None, command=dispense_command) - ) - - assert subject.state.aspirated_volume_by_id["pipette-id"] == 0 - - -@pytest.mark.parametrize( - "blow_out_command", - [ - create_blow_out_command("pipette-id", 1.23), - create_blow_out_in_place_command("pipette-id", 1.23), - create_unsafe_blow_out_in_place_command("pipette-id", 1.23), - ], -) -def test_blow_out_clears_volume( - subject: PipetteStore, blow_out_command: cmd.Command -) -> None: - """It should wipe out the aspirated volume after a blowOut.""" - load_command = create_load_pipette_command( - pipette_id="pipette-id", - pipette_name=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - aspirate_command = create_aspirate_command( - pipette_id="pipette-id", - volume=42, - flow_rate=1.23, - ) - - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_command) - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=aspirate_command) - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=blow_out_command) - ) - - assert subject.state.aspirated_volume_by_id["pipette-id"] is None - - -@pytest.mark.parametrize( - ("action", "expected_location"), - ( - ( - SucceedCommandAction( - command=create_aspirate_command( - pipette_id="pipette-id", - labware_id="aspirate-labware-id", - well_name="aspirate-well-name", - volume=1337, - flow_rate=1.23, - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="aspirate-labware-id", - well_name="aspirate-well-name", - ), - ), - ( - FailCommandAction( - running_command=cmd.Aspirate( - params=cmd.AspirateParams( - pipetteId="pipette-id", - labwareId="aspirate-labware-id", - wellName="aspirate-well-name", - volume=99999, - flowRate=1.23, - ), - id="command-id", - key="command-key", - createdAt=datetime.now(), - status=cmd.CommandStatus.RUNNING, - ), - error=DefinedErrorData( - public=OverpressureError( - id="error-id", - createdAt=datetime.now(), - errorInfo={"retryLocation": (0, 0, 0)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=0, y=0, z=0) - ), - ), - command_id="command-id", - error_id="error-id", - failed_at=datetime.now(), - notes=[], - type=ErrorRecoveryType.WAIT_FOR_RECOVERY, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="aspirate-labware-id", - well_name="aspirate-well-name", - ), - ), - ( - SucceedCommandAction( - command=create_dispense_command( - pipette_id="pipette-id", - labware_id="dispense-labware-id", - well_name="dispense-well-name", - volume=1337, - flow_rate=1.23, - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="dispense-labware-id", - well_name="dispense-well-name", - ), - ), - ( - SucceedCommandAction( - command=create_pick_up_tip_command( - pipette_id="pipette-id", - labware_id="pick-up-tip-labware-id", - well_name="pick-up-tip-well-name", - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="pick-up-tip-labware-id", - well_name="pick-up-tip-well-name", - ), - ), - ( - SucceedCommandAction( - command=create_drop_tip_command( - pipette_id="pipette-id", - labware_id="drop-tip-labware-id", - well_name="drop-tip-well-name", - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="drop-tip-labware-id", - well_name="drop-tip-well-name", - ), - ), - ( - SucceedCommandAction( - command=create_move_to_well_command( - pipette_id="pipette-id", - labware_id="move-to-well-labware-id", - well_name="move-to-well-well-name", - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="move-to-well-labware-id", - well_name="move-to-well-well-name", - ), - ), - ( - SucceedCommandAction( - command=create_blow_out_command( - pipette_id="pipette-id", - labware_id="move-to-well-labware-id", - well_name="move-to-well-well-name", - flow_rate=1.23, - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="move-to-well-labware-id", - well_name="move-to-well-well-name", - ), - ), - ( - FailCommandAction( - running_command=cmd.Dispense( - params=cmd.DispenseParams( - pipetteId="pipette-id", - labwareId="dispense-labware-id", - wellName="dispense-well-name", - volume=50, - flowRate=1.23, - ), - id="command-id", - key="command-key", - createdAt=datetime.now(), - status=cmd.CommandStatus.RUNNING, - ), - error=DefinedErrorData( - public=OverpressureError( - id="error-id", - createdAt=datetime.now(), - errorInfo={"retryLocation": (0, 0, 0)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=0, y=0, z=0) - ), - ), - command_id="command-id", - error_id="error-id", - failed_at=datetime.now(), - notes=[], - type=ErrorRecoveryType.WAIT_FOR_RECOVERY, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="dispense-labware-id", - well_name="dispense-well-name", - ), - ), - # liquidProbe and tryLiquidProbe succeeding and with overpressure error - ( - SucceedCommandAction( - command=cmd.LiquidProbe( - id="command-id", - createdAt=datetime.now(), - startedAt=datetime.now(), - completedAt=datetime.now(), - key="command-key", - status=cmd.CommandStatus.SUCCEEDED, - params=cmd.LiquidProbeParams( - labwareId="liquid-probe-labware-id", - wellName="liquid-probe-well-name", - pipetteId="pipette-id", - ), - result=cmd.LiquidProbeResult( - position=DeckPoint(x=0, y=0, z=0), z_position=0 - ), - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="liquid-probe-labware-id", - well_name="liquid-probe-well-name", - ), - ), - ( - FailCommandAction( - running_command=cmd.LiquidProbe( - id="command-id", - createdAt=datetime.now(), - startedAt=datetime.now(), - key="command-key", - status=cmd.CommandStatus.RUNNING, - params=cmd.LiquidProbeParams( - labwareId="liquid-probe-labware-id", - wellName="liquid-probe-well-name", - pipetteId="pipette-id", - ), - ), - error=DefinedErrorData( - public=LiquidNotFoundError( - id="error-id", - createdAt=datetime.now(), - ), - private=LiquidNotFoundErrorInternalData( - position=DeckPoint(x=0, y=0, z=0) - ), - ), - command_id="command-id", - error_id="error-id", - failed_at=datetime.now(), - notes=[], - type=ErrorRecoveryType.WAIT_FOR_RECOVERY, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="liquid-probe-labware-id", - well_name="liquid-probe-well-name", - ), - ), - ( - SucceedCommandAction( - command=cmd.TryLiquidProbe( - id="command-id", - createdAt=datetime.now(), - startedAt=datetime.now(), - completedAt=datetime.now(), - key="command-key", - status=cmd.CommandStatus.SUCCEEDED, - params=cmd.TryLiquidProbeParams( - labwareId="try-liquid-probe-labware-id", - wellName="try-liquid-probe-well-name", - pipetteId="pipette-id", - ), - result=cmd.TryLiquidProbeResult( - position=DeckPoint(x=0, y=0, z=0), - z_position=0, - ), - ), - private_result=None, - ), - CurrentWell( - pipette_id="pipette-id", - labware_id="try-liquid-probe-labware-id", - well_name="try-liquid-probe-well-name", - ), - ), - ), -) -def test_movement_commands_update_current_well( - action: Union[SucceedCommandAction, FailCommandAction], - expected_location: CurrentWell, - subject: PipetteStore, -) -> None: - """It should save the last used pipette, labware, and well for movement commands.""" - load_pipette_command = create_load_pipette_command( - pipette_id="pipette-id", - pipette_name=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) - ) - subject.handle_action(action) - - assert subject.state.current_location == expected_location - - -@pytest.mark.parametrize( - "command", - [ - cmd.Home( - id="command-id-2", - key="command-key-2", - status=cmd.CommandStatus.SUCCEEDED, - createdAt=datetime(year=2021, month=1, day=1), - params=cmd.HomeParams(), - result=cmd.HomeResult(), - ), - cmd.MoveToCoordinates( - id="command-id-2", - key="command-key-2", - status=cmd.CommandStatus.SUCCEEDED, - createdAt=datetime(year=2021, month=1, day=1), - params=cmd.MoveToCoordinatesParams( - pipetteId="pipette-id", - coordinates=DeckPoint(x=1.1, y=2.2, z=3.3), - ), - result=cmd.MoveToCoordinatesResult(), - ), - cmd.thermocycler.OpenLid( - id="command-id-2", - key="command-key-2", - status=cmd.CommandStatus.SUCCEEDED, - createdAt=datetime(year=2021, month=1, day=1), - params=cmd.thermocycler.OpenLidParams(moduleId="xyz"), - result=cmd.thermocycler.OpenLidResult(), - ), - cmd.thermocycler.CloseLid( - id="command-id-2", - key="command-key-2", - status=cmd.CommandStatus.SUCCEEDED, - createdAt=datetime(year=2021, month=1, day=1), - params=cmd.thermocycler.CloseLidParams(moduleId="xyz"), - result=cmd.thermocycler.CloseLidResult(), - ), - cmd.heater_shaker.SetAndWaitForShakeSpeed( - id="command-id-2", - key="command-key-2", - status=cmd.CommandStatus.SUCCEEDED, - createdAt=datetime(year=2021, month=1, day=1), - params=cmd.heater_shaker.SetAndWaitForShakeSpeedParams( - moduleId="xyz", - rpm=123, - ), - result=cmd.heater_shaker.SetAndWaitForShakeSpeedResult( - pipetteRetracted=True - ), - ), - cmd.heater_shaker.OpenLabwareLatch( - id="command-id-2", - key="command-key-2", - status=cmd.CommandStatus.SUCCEEDED, - createdAt=datetime(year=2021, month=1, day=1), - params=cmd.heater_shaker.OpenLabwareLatchParams(moduleId="xyz"), - result=cmd.heater_shaker.OpenLabwareLatchResult(pipetteRetracted=True), - ), - ], -) -def test_movement_commands_without_well_clear_current_well( - subject: PipetteStore, command: cmd.Command -) -> None: - """Commands that make the current well unknown should clear the current well.""" - load_pipette_command = create_load_pipette_command( - pipette_id="pipette-id", - pipette_name=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - move_command = create_move_to_well_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - ) - - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=move_command) - ) - subject.handle_action(SucceedCommandAction(private_result=None, command=command)) - - assert subject.state.current_location is None - - -@pytest.mark.parametrize( - "command", - [ - cmd.heater_shaker.SetAndWaitForShakeSpeed( - id="command-id-2", - key="command-key-2", - status=cmd.CommandStatus.SUCCEEDED, - createdAt=datetime(year=2021, month=1, day=1), - params=cmd.heater_shaker.SetAndWaitForShakeSpeedParams( - moduleId="xyz", - rpm=123, - ), - result=cmd.heater_shaker.SetAndWaitForShakeSpeedResult( - pipetteRetracted=False - ), - ), - cmd.heater_shaker.OpenLabwareLatch( - id="command-id-2", - key="command-key-2", - status=cmd.CommandStatus.SUCCEEDED, - createdAt=datetime(year=2021, month=1, day=1), - params=cmd.heater_shaker.OpenLabwareLatchParams(moduleId="xyz"), - result=cmd.heater_shaker.OpenLabwareLatchResult(pipetteRetracted=False), - ), - ], -) -def test_heater_shaker_command_without_movement( - subject: PipetteStore, command: cmd.Command -) -> None: - """Heater Shaker commands that don't move pipettes shouldn't clear current_well or deck point.""" - load_pipette_command = create_load_pipette_command( - pipette_id="pipette-id", - pipette_name=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - move_command = create_move_to_well_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - destination=DeckPoint(x=1, y=2, z=3), - ) - - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=move_command) - ) - subject.handle_action(SucceedCommandAction(private_result=None, command=command)) - - assert subject.state.current_location == CurrentWell( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - ) - - assert subject.state.current_deck_point == CurrentDeckPoint( - mount=MountType.LEFT, deck_point=DeckPoint(x=1, y=2, z=3) - ) - - -@pytest.mark.parametrize( - ("move_labware_command", "expected_current_well"), - ( - ( - create_move_labware_command( - labware_id="non-matching-labware-id", - strategy=LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE, - new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - offset_id=None, - ), - # Current well NOT cleared, - # because MoveLabware command had "non-matching-labware-id". - CurrentWell( - pipette_id="pipette-id", - labware_id="matching-labware-id", - well_name="well-name", - ), - ), - ( - create_move_labware_command( - labware_id="matching-labware-id", - strategy=LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE, - new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - offset_id=None, - ), - # Current well IS cleared, - # because MoveLabware command had "matching-labware-id". - None, - ), - ( - create_move_labware_command( - labware_id="non-matching-labware-id", - strategy=LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE, - new_location=OFF_DECK_LOCATION, - offset_id=None, - ), - # Current well NOT cleared, - # because MoveLabware command had "non-matching-labware-id". - CurrentWell( - pipette_id="pipette-id", - labware_id="matching-labware-id", - well_name="well-name", - ), - ), - ( - create_move_labware_command( - labware_id="matching-labware-id", - strategy=LabwareMovementStrategy.MANUAL_MOVE_WITH_PAUSE, - new_location=OFF_DECK_LOCATION, - offset_id=None, - ), - # Current well IS cleared, - # because MoveLabware command had "matching-labware-id". - None, - ), - ( - create_move_labware_command( - labware_id="non-matching-labware-id", - new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - strategy=LabwareMovementStrategy.USING_GRIPPER, - offset_id=None, - ), - # Current well IS cleared, - # because MoveLabware command used gripper. - None, - ), - ), -) -def test_move_labware_clears_current_well( - subject: PipetteStore, - move_labware_command: cmd.MoveLabware, - expected_current_well: Optional[CurrentWell], -) -> None: - """Labware movement commands should sometimes clear the current well. - - It should be cleared when- - * the current well belongs to the labware that was moved, - * or gripper was used to move labware - - Otherwise, it should be left alone. - """ - load_pipette_command = create_load_pipette_command( - pipette_id="pipette-id", - pipette_name=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - move_to_well_command = create_move_to_well_command( - pipette_id="pipette-id", - labware_id="matching-labware-id", - well_name="well-name", - ) - - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=move_to_well_command) - ) - - subject.handle_action( - SucceedCommandAction(private_result=None, command=move_labware_command) - ) - assert subject.state.current_location == expected_current_well - - -def test_set_movement_speed(subject: PipetteStore) -> None: - """It should issue an action to set the movement speed.""" - pipette_id = "pipette-id" - load_pipette_command = create_load_pipette_command( - pipette_id=pipette_id, - pipette_name=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) - ) - subject.handle_action( - SetPipetteMovementSpeedAction(pipette_id=pipette_id, speed=123.456) - ) - assert subject.state.movement_speed_by_id[pipette_id] == 123.456 - - -def test_add_pipette_config( - subject: PipetteStore, - supported_tip_fixture: pipette_definition.SupportedTipsDefinition, -) -> None: - """It should update state from any pipette config private result.""" - command = cmd.LoadPipette.construct( # type: ignore[call-arg] - params=cmd.LoadPipetteParams.construct( - mount=MountType.LEFT, pipetteName="p300_single" # type: ignore[arg-type] - ), - result=cmd.LoadPipetteResult(pipetteId="pipette-id"), - ) - private_result = cmd.LoadPipettePrivateResult( - pipette_id="pipette-id", - serial_number="pipette-serial", - config=LoadedStaticPipetteData( - model="pipette-model", - display_name="pipette name", - min_volume=1.23, - max_volume=4.56, - channels=7, - flow_rates=FlowRates( - default_aspirate={"a": 1}, - default_dispense={"b": 2}, - default_blow_out={"c": 3}, - ), - tip_configuration_lookup_table={4: supported_tip_fixture}, - nominal_tip_overlap={"default": 5}, - home_position=8.9, - nozzle_offset_z=10.11, - nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), - back_left_corner_offset=Point(x=1, y=2, z=3), - front_right_corner_offset=Point(x=4, y=5, z=6), - pipette_lld_settings={}, - ), - ) - subject.handle_action( - SucceedCommandAction(command=command, private_result=private_result) - ) - - assert subject.state.static_config_by_id["pipette-id"] == StaticPipetteConfig( - model="pipette-model", - serial_number="pipette-serial", - display_name="pipette name", - min_volume=1.23, - max_volume=4.56, - channels=7, - tip_configuration_lookup_table={4: supported_tip_fixture}, - nominal_tip_overlap={"default": 5}, - home_position=8.9, - nozzle_offset_z=10.11, - bounding_nozzle_offsets=BoundingNozzlesOffsets( - back_left_offset=Point(x=0, y=0, z=0), - front_right_offset=Point(x=0, y=0, z=0), - ), - default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), - pipette_bounding_box_offsets=PipetteBoundingBoxOffsets( - back_left_corner=Point(x=1, y=2, z=3), - front_right_corner=Point(x=4, y=5, z=6), - front_left_corner=Point(x=1, y=5, z=3), - back_right_corner=Point(x=4, y=2, z=3), - ), - lld_settings={}, - ) - assert subject.state.flow_rates_by_id["pipette-id"].default_aspirate == {"a": 1.0} - assert subject.state.flow_rates_by_id["pipette-id"].default_dispense == {"b": 2.0} - assert subject.state.flow_rates_by_id["pipette-id"].default_blow_out == {"c": 3.0} - - -@pytest.mark.parametrize( - "action", - ( - SucceedCommandAction( - command=create_aspirate_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - volume=1337, - flow_rate=1.23, - destination=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), - FailCommandAction( - running_command=cmd.Aspirate( - params=cmd.AspirateParams( - pipetteId="pipette-id", - labwareId="labware-id", - wellName="well-name", - volume=99999, - flowRate=1.23, - ), - id="command-id", - key="command-key", - createdAt=datetime.now(), - status=cmd.CommandStatus.RUNNING, - ), - error=DefinedErrorData( - public=OverpressureError( - id="error-id", - detail="error-detail", - createdAt=datetime.now(), - errorInfo={"retryLocation": (11, 22, 33)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=11, y=22, z=33) - ), - ), - command_id="command-id", - error_id="error-id", - failed_at=datetime.now(), - notes=[], - type=ErrorRecoveryType.WAIT_FOR_RECOVERY, - ), - SucceedCommandAction( - command=create_dispense_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - volume=1337, - flow_rate=1.23, - destination=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), - SucceedCommandAction( - command=create_blow_out_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - flow_rate=1.23, - destination=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), - SucceedCommandAction( - command=create_pick_up_tip_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - destination=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), - SucceedCommandAction( - command=create_drop_tip_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - destination=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), - SucceedCommandAction( - command=create_touch_tip_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - destination=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), - SucceedCommandAction( - command=create_move_to_well_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - destination=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), - SucceedCommandAction( - command=create_move_to_coordinates_command( - pipette_id="pipette-id", - coordinates=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), - SucceedCommandAction( - command=create_move_relative_command( - pipette_id="pipette-id", - destination=DeckPoint(x=11, y=22, z=33), - ), - private_result=None, - ), - FailCommandAction( - running_command=cmd.Dispense( - params=cmd.DispenseParams( - pipetteId="pipette-id", - labwareId="labware-id", - wellName="well-name", - volume=125, - flowRate=1.23, - ), - id="command-id", - key="command-key", - createdAt=datetime.now(), - status=cmd.CommandStatus.RUNNING, - ), - error=DefinedErrorData( - public=OverpressureError( - id="error-id", - detail="error-detail", - createdAt=datetime.now(), - errorInfo={"retryLocation": (11, 22, 33)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=11, y=22, z=33) - ), - ), - command_id="command-id", - error_id="error-id", - failed_at=datetime.now(), - notes=[], - type=ErrorRecoveryType.WAIT_FOR_RECOVERY, - ), - FailCommandAction( - running_command=cmd.AspirateInPlace( - params=cmd.AspirateInPlaceParams( - pipetteId="pipette-id", - volume=125, - flowRate=1.23, - ), - id="command-id", - key="command-key", - createdAt=datetime.now(), - status=cmd.CommandStatus.RUNNING, - ), - error=DefinedErrorData( - public=OverpressureError( - id="error-id", - detail="error-detail", - createdAt=datetime.now(), - errorInfo={"retryLocation": (11, 22, 33)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=11, y=22, z=33) - ), - ), - command_id="command-id", - error_id="error-id", - failed_at=datetime.now(), - notes=[], - type=ErrorRecoveryType.WAIT_FOR_RECOVERY, - ), - FailCommandAction( - running_command=cmd.DispenseInPlace( - params=cmd.DispenseInPlaceParams( - pipetteId="pipette-id", - volume=125, - flowRate=1.23, - ), - id="command-id", - key="command-key", - createdAt=datetime.now(), - status=cmd.CommandStatus.RUNNING, - ), - error=DefinedErrorData( - public=OverpressureError( - id="error-id", - detail="error-detail", - createdAt=datetime.now(), - errorInfo={"retryLocation": (11, 22, 33)}, - ), - private=OverpressureErrorInternalData( - position=DeckPoint(x=11, y=22, z=33) - ), - ), - command_id="command-id", - error_id="error-id", - failed_at=datetime.now(), - notes=[], - type=ErrorRecoveryType.WAIT_FOR_RECOVERY, - ), - ), -) -def test_movement_commands_update_deck_point( - action: Union[SucceedCommandAction, FailCommandAction], - subject: PipetteStore, -) -> None: - """It should save the last used pipette, labware, and well for movement commands.""" - load_pipette_command = create_load_pipette_command( - pipette_id="pipette-id", - pipette_name=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) - ) - subject.handle_action(action) - - assert subject.state.current_deck_point == CurrentDeckPoint( - mount=MountType.LEFT, deck_point=DeckPoint(x=11, y=22, z=33) - ) - - -@pytest.mark.parametrize( - "command", - ( - cmd.Home( - id="command-id-2", - key="command-key-2", - status=cmd.CommandStatus.SUCCEEDED, - createdAt=datetime(year=2021, month=1, day=1), - params=cmd.HomeParams(), - result=cmd.HomeResult(), - ), - cmd.thermocycler.OpenLid( - id="command-id-2", - key="command-key-2", - status=cmd.CommandStatus.SUCCEEDED, - createdAt=datetime(year=2021, month=1, day=1), - params=cmd.thermocycler.OpenLidParams(moduleId="xyz"), - result=cmd.thermocycler.OpenLidResult(), - ), - cmd.thermocycler.CloseLid( - id="command-id-2", - key="command-key-2", - status=cmd.CommandStatus.SUCCEEDED, - createdAt=datetime(year=2021, month=1, day=1), - params=cmd.thermocycler.CloseLidParams(moduleId="xyz"), - result=cmd.thermocycler.CloseLidResult(), - ), - cmd.heater_shaker.SetAndWaitForShakeSpeed( - id="command-id-2", - key="command-key-2", - status=cmd.CommandStatus.SUCCEEDED, - createdAt=datetime(year=2021, month=1, day=1), - params=cmd.heater_shaker.SetAndWaitForShakeSpeedParams( - moduleId="xyz", - rpm=123, - ), - result=cmd.heater_shaker.SetAndWaitForShakeSpeedResult( - pipetteRetracted=True - ), - ), - cmd.heater_shaker.OpenLabwareLatch( - id="command-id-2", - key="command-key-2", - status=cmd.CommandStatus.SUCCEEDED, - createdAt=datetime(year=2021, month=1, day=1), - params=cmd.heater_shaker.OpenLabwareLatchParams(moduleId="xyz"), - result=cmd.heater_shaker.OpenLabwareLatchResult(pipetteRetracted=True), - ), - create_move_labware_command( - new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), - strategy=LabwareMovementStrategy.USING_GRIPPER, - ), - ), -) -def test_homing_commands_clear_deck_point( - command: cmd.Command, - subject: PipetteStore, -) -> None: - """It should save the last used pipette, labware, and well for movement commands.""" - load_pipette_command = create_load_pipette_command( - pipette_id="pipette-id", - pipette_name=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - move_command = create_move_to_well_command( - pipette_id="pipette-id", - labware_id="labware-id", - well_name="well-name", - destination=DeckPoint(x=1, y=2, z=3), - ) - - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=move_command) - ) - - assert subject.state.current_deck_point == CurrentDeckPoint( - mount=MountType.LEFT, deck_point=DeckPoint(x=1, y=2, z=3) - ) - - subject.handle_action(SucceedCommandAction(private_result=None, command=command)) - - assert subject.state.current_deck_point == CurrentDeckPoint( - mount=None, deck_point=None - ) - - -@pytest.mark.parametrize( - "previous", - [ - create_blow_out_command(pipette_id="pipette-id", flow_rate=1.0), - create_dispense_command(pipette_id="pipette-id", volume=10, flow_rate=1.0), - ], -) -def test_prepare_to_aspirate_marks_pipette_ready( - subject: PipetteStore, previous: cmd.Command -) -> None: - """It should mark a pipette as ready to aspirate.""" - load_pipette_command = create_load_pipette_command( - pipette_id="pipette-id", - pipette_name=PipetteNameType.P50_MULTI_FLEX, - mount=MountType.LEFT, - ) - pick_up_tip_command = create_pick_up_tip_command( - pipette_id="pipette-id", tip_volume=42, tip_length=101, tip_diameter=8.0 - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=load_pipette_command) - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=pick_up_tip_command) - ) - - subject.handle_action(SucceedCommandAction(private_result=None, command=previous)) - - prepare_to_aspirate_command = create_prepare_to_aspirate_command( - pipette_id="pipette-id" - ) - subject.handle_action( - SucceedCommandAction(private_result=None, command=prepare_to_aspirate_command) - ) - assert subject.state.aspirated_volume_by_id["pipette-id"] == 0.0 diff --git a/api/tests/opentrons/protocol_engine/state/test_pipette_store_old.py b/api/tests/opentrons/protocol_engine/state/test_pipette_store_old.py new file mode 100644 index 00000000000..b88844bb53d --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_pipette_store_old.py @@ -0,0 +1,941 @@ +"""Tests for pipette state changes in the protocol_engine state store. + +DEPRECATED: Testing PipetteStore independently of PipetteView is no longer helpful. +Try to add new tests to test_pipette_state.py, where they can be tested together, +treating PipetteState as a private implementation detail. +""" +import pytest + +from opentrons_shared_data.pipette.types import PipetteNameType +from opentrons_shared_data.pipette import pipette_definition + +from opentrons.protocol_engine.state import update_types +from opentrons.types import MountType, Point +from opentrons.protocol_engine import commands as cmd +from opentrons.protocol_engine.types import ( + CurrentAddressableArea, + DeckPoint, + LoadedPipette, + FlowRates, + CurrentWell, + TipGeometry, + AspiratedFluid, + FluidKind, +) +from opentrons.protocol_engine.actions import ( + SetPipetteMovementSpeedAction, + SucceedCommandAction, +) +from opentrons.protocol_engine.state.pipettes import ( + PipetteStore, + PipetteState, + CurrentDeckPoint, + StaticPipetteConfig, + BoundingNozzlesOffsets, + PipetteBoundingBoxOffsets, +) +from opentrons.protocol_engine.resources.pipette_data_provider import ( + LoadedStaticPipetteData, +) +from opentrons.protocol_engine.state.fluid_stack import FluidStack + +from .command_fixtures import ( + create_load_pipette_command, + create_aspirate_command, + create_aspirate_in_place_command, + create_dispense_command, + create_dispense_in_place_command, + create_pick_up_tip_command, + create_drop_tip_command, + create_drop_tip_in_place_command, + create_succeeded_command, + create_unsafe_drop_tip_in_place_command, + create_blow_out_command, + create_blow_out_in_place_command, + create_prepare_to_aspirate_command, + create_unsafe_blow_out_in_place_command, +) +from ..pipette_fixtures import get_default_nozzle_map + + +@pytest.fixture +def available_sensors() -> pipette_definition.AvailableSensorDefinition: + """Provide a list of sensors.""" + return pipette_definition.AvailableSensorDefinition( + sensors=["pressure", "capacitive", "environment"] + ) + + +@pytest.fixture +def subject() -> PipetteStore: + """Get a PipetteStore test subject for all subsequent tests.""" + return PipetteStore() + + +def test_sets_initial_state(subject: PipetteStore) -> None: + """It should initialize its state object properly.""" + result = subject.state + + assert result == PipetteState( + pipettes_by_id={}, + pipette_contents_by_id={}, + current_location=None, + current_deck_point=CurrentDeckPoint(mount=None, deck_point=None), + attached_tip_by_id={}, + movement_speed_by_id={}, + static_config_by_id={}, + flow_rates_by_id={}, + nozzle_configuration_by_id={}, + liquid_presence_detection_by_id={}, + ) + + +def test_location_state_update(subject: PipetteStore) -> None: + """It should update pipette locations.""" + load_command = create_load_pipette_command( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.RIGHT, + ) + subject.handle_action(SucceedCommandAction(command=load_command)) + + # Update the location to a well: + dummy_command = create_succeeded_command() + subject.handle_action( + SucceedCommandAction( + command=dummy_command, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.Well( + labware_id="come on barbie", + well_name="let's go party", + ), + new_deck_point=DeckPoint(x=111, y=222, z=333), + ), + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="pipette-id", + liquid_presence_detection=None, + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.RIGHT, + ), + ), + ) + ) + assert subject.state.current_location == CurrentWell( + pipette_id="pipette-id", labware_id="come on barbie", well_name="let's go party" + ) + assert subject.state.current_deck_point == CurrentDeckPoint( + mount=MountType.RIGHT, deck_point=DeckPoint(x=111, y=222, z=333) + ) + + # Update the location to an addressable area: + subject.handle_action( + SucceedCommandAction( + command=dummy_command, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.AddressableArea( + addressable_area_name="na na na na na" + ), + new_deck_point=DeckPoint(x=333, y=444, z=555), + ) + ), + ) + ) + assert subject.state.current_location == CurrentAddressableArea( + pipette_id="pipette-id", addressable_area_name="na na na na na" + ) + assert subject.state.current_deck_point == CurrentDeckPoint( + mount=MountType.RIGHT, deck_point=DeckPoint(x=333, y=444, z=555) + ) + + # Clear the logical location: + subject.handle_action( + SucceedCommandAction( + command=dummy_command, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=None, + new_deck_point=update_types.NO_CHANGE, + ) + ), + ) + ) + assert subject.state.current_location is None + assert subject.state.current_deck_point == CurrentDeckPoint( + mount=MountType.RIGHT, deck_point=DeckPoint(x=333, y=444, z=555) + ) + + # Repopulate the locations, then test clearing all pipette locations: + subject.handle_action( + SucceedCommandAction( + command=dummy_command, + state_update=update_types.StateUpdate( + pipette_location=update_types.PipetteLocationUpdate( + pipette_id="pipette-id", + new_location=update_types.AddressableArea( + addressable_area_name="na na na na na" + ), + new_deck_point=DeckPoint(x=333, y=444, z=555), + ) + ), + ) + ) + assert subject.state.current_location is not None + assert subject.state.current_deck_point != CurrentDeckPoint( + mount=None, deck_point=None + ) + subject.handle_action( + SucceedCommandAction( + command=dummy_command, + state_update=update_types.StateUpdate(pipette_location=update_types.CLEAR), + ) + ) + assert subject.state.current_location is None + assert subject.state.current_deck_point == CurrentDeckPoint( + mount=None, deck_point=None + ) + + +def test_handles_load_pipette( + subject: PipetteStore, + supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: pipette_definition.AvailableSensorDefinition, +) -> None: + """It should add the pipette data to the state.""" + dummy_command = create_succeeded_command() + + load_pipette_update = update_types.LoadPipetteUpdate( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + liquid_presence_detection=None, + ) + + config = LoadedStaticPipetteData( + model="pipette-model", + display_name="pipette name", + min_volume=1.23, + max_volume=4.56, + channels=7, + flow_rates=FlowRates( + default_aspirate={"a": 1}, + default_dispense={"b": 2}, + default_blow_out={"c": 3}, + ), + tip_configuration_lookup_table={4: supported_tip_fixture}, + nominal_tip_overlap={"default": 5}, + home_position=8.9, + nozzle_offset_z=10.11, + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), + back_left_corner_offset=Point(x=1, y=2, z=3), + front_right_corner_offset=Point(x=4, y=5, z=6), + pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ) + config_update = update_types.PipetteConfigUpdate( + pipette_id="pipette-id", + config=config, + serial_number="pipette-serial", + ) + contents_update = update_types.PipetteUnknownFluidUpdate(pipette_id="pipette-id") + + subject.handle_action( + SucceedCommandAction( + command=dummy_command, + state_update=update_types.StateUpdate( + loaded_pipette=load_pipette_update, + pipette_config=config_update, + pipette_aspirated_fluid=contents_update, + ), + ) + ) + + result = subject.state + + assert result.pipettes_by_id["pipette-id"] == LoadedPipette( + id="pipette-id", + pipetteName=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + ) + assert result.pipette_contents_by_id["pipette-id"] is None + assert result.movement_speed_by_id["pipette-id"] is None + assert result.attached_tip_by_id["pipette-id"] is None + + +def test_handles_pick_up_and_drop_tip(subject: PipetteStore) -> None: + """It should set tip and volume details on pick up and drop tip.""" + load_pipette_command = create_load_pipette_command( + pipette_id="abc", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + ) + + pick_up_tip_command = create_pick_up_tip_command( + pipette_id="abc", tip_volume=42, tip_length=101, tip_diameter=8.0 + ) + + drop_tip_command = create_drop_tip_command( + pipette_id="abc", + ) + + subject.handle_action( + SucceedCommandAction( + command=load_pipette_command, + state_update=update_types.StateUpdate( + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="abc", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + liquid_presence_detection=None, + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="abc" + ), + ), + ) + ) + + subject.handle_action( + SucceedCommandAction( + command=pick_up_tip_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="abc", + tip_geometry=TipGeometry(volume=42, length=101, diameter=8.0), + ), + pipette_aspirated_fluid=update_types.PipetteEmptyFluidUpdate( + pipette_id="abc" + ), + ), + ) + ) + assert subject.state.attached_tip_by_id["abc"] == TipGeometry( + volume=42, length=101, diameter=8.0 + ) + assert subject.state.pipette_contents_by_id["abc"] == FluidStack() + + subject.handle_action( + SucceedCommandAction( + command=drop_tip_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="abc", tip_geometry=None + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="abc" + ), + ), + ) + ) + assert subject.state.attached_tip_by_id["abc"] is None + assert subject.state.pipette_contents_by_id["abc"] is None + + +def test_handles_drop_tip_in_place(subject: PipetteStore) -> None: + """It should clear tip and volume details after a drop tip in place.""" + load_pipette_command = create_load_pipette_command( + pipette_id="xyz", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + ) + + pick_up_tip_command = create_pick_up_tip_command( + pipette_id="xyz", tip_volume=42, tip_length=101, tip_diameter=8.0 + ) + + drop_tip_in_place_command = create_drop_tip_in_place_command( + pipette_id="xyz", + ) + + subject.handle_action( + SucceedCommandAction( + command=load_pipette_command, + state_update=update_types.StateUpdate( + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="xyz", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + liquid_presence_detection=None, + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="xyz" + ), + ), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=pick_up_tip_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="xyz", + tip_geometry=TipGeometry(volume=42, length=101, diameter=8.0), + ), + pipette_aspirated_fluid=update_types.PipetteEmptyFluidUpdate( + pipette_id="xyz" + ), + ), + ) + ) + assert subject.state.attached_tip_by_id["xyz"] == TipGeometry( + volume=42, length=101, diameter=8.0 + ) + assert subject.state.pipette_contents_by_id["xyz"] == FluidStack() + + subject.handle_action( + SucceedCommandAction( + command=drop_tip_in_place_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="xyz", tip_geometry=None + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="xyz" + ), + ), + ) + ) + assert subject.state.attached_tip_by_id["xyz"] is None + assert subject.state.pipette_contents_by_id["xyz"] is None + + +def test_handles_unsafe_drop_tip_in_place(subject: PipetteStore) -> None: + """It should clear tip and volume details after a drop tip in place.""" + load_pipette_command = create_load_pipette_command( + pipette_id="xyz", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + ) + + pick_up_tip_command = create_pick_up_tip_command( + pipette_id="xyz", tip_volume=42, tip_length=101, tip_diameter=8.0 + ) + + unsafe_drop_tip_in_place_command = create_unsafe_drop_tip_in_place_command( + pipette_id="xyz", + ) + + subject.handle_action( + SucceedCommandAction( + command=load_pipette_command, + state_update=update_types.StateUpdate( + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="xyz", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + liquid_presence_detection=None, + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="xyz" + ), + ), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=pick_up_tip_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="xyz", + tip_geometry=TipGeometry(volume=42, length=101, diameter=8.0), + ), + pipette_aspirated_fluid=update_types.PipetteEmptyFluidUpdate( + pipette_id="xyz" + ), + ), + ) + ) + assert subject.state.attached_tip_by_id["xyz"] == TipGeometry( + volume=42, length=101, diameter=8.0 + ) + assert subject.state.pipette_contents_by_id["xyz"] == FluidStack() + + subject.handle_action( + SucceedCommandAction( + command=unsafe_drop_tip_in_place_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="xyz", tip_geometry=None + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="xyz" + ), + ), + ) + ) + assert subject.state.attached_tip_by_id["xyz"] is None + assert subject.state.pipette_contents_by_id["xyz"] is None + + +@pytest.mark.parametrize( + "aspirate_command,aspirate_update", + [ + ( + create_aspirate_command(pipette_id="pipette-id", volume=42, flow_rate=1.23), + update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteAspiratedFluidUpdate( + pipette_id="pipette-id", + fluid=AspiratedFluid(kind=FluidKind.LIQUID, volume=42), + ) + ), + ), + ( + create_aspirate_in_place_command( + pipette_id="pipette-id", volume=42, flow_rate=1.23 + ), + update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteAspiratedFluidUpdate( + pipette_id="pipette-id", + fluid=AspiratedFluid(kind=FluidKind.LIQUID, volume=42), + ) + ), + ), + ], +) +def test_aspirate_adds_volume( + subject: PipetteStore, + aspirate_command: cmd.Command, + aspirate_update: update_types.StateUpdate, +) -> None: + """It should add volume to pipette after an aspirate.""" + load_command = create_load_pipette_command( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + ) + pick_up_tip_command = create_pick_up_tip_command( + pipette_id="pipette-id", tip_volume=42, tip_length=101, tip_diameter=8.0 + ) + + subject.handle_action( + SucceedCommandAction( + command=load_command, + state_update=update_types.StateUpdate( + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + liquid_presence_detection=None, + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="pipette-id" + ), + ), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=pick_up_tip_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="pipette-id", + tip_geometry=TipGeometry(volume=42, length=101, diameter=8.0), + ), + pipette_aspirated_fluid=update_types.PipetteEmptyFluidUpdate( + pipette_id="pipette-id" + ), + ), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=aspirate_command, + state_update=aspirate_update, + ) + ) + + assert subject.state.pipette_contents_by_id["pipette-id"] == FluidStack( + _fluid_stack=[AspiratedFluid(kind=FluidKind.LIQUID, volume=42)] + ) + + subject.handle_action( + SucceedCommandAction(command=aspirate_command, state_update=aspirate_update) + ) + + assert subject.state.pipette_contents_by_id["pipette-id"] == FluidStack( + _fluid_stack=[AspiratedFluid(kind=FluidKind.LIQUID, volume=84)] + ) + + +@pytest.mark.parametrize( + "dispense_command,dispense_update", + [ + ( + create_dispense_command(pipette_id="pipette-id", volume=21, flow_rate=1.23), + update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteEjectedFluidUpdate( + pipette_id="pipette-id", volume=21 + ) + ), + ), + ( + create_dispense_in_place_command( + pipette_id="pipette-id", + volume=21, + flow_rate=1.23, + ), + update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteEjectedFluidUpdate( + pipette_id="pipette-id", volume=21 + ) + ), + ), + ], +) +def test_dispense_subtracts_volume( + subject: PipetteStore, + dispense_command: cmd.Command, + dispense_update: update_types.StateUpdate, +) -> None: + """It should subtract volume from pipette after a dispense.""" + load_command = create_load_pipette_command( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + ) + pick_up_tip_command = create_pick_up_tip_command( + pipette_id="pipette-id", tip_volume=47, tip_length=101, tip_diameter=8.0 + ) + + aspirate_command = create_aspirate_command( + pipette_id="pipette-id", + volume=42, + flow_rate=1.23, + ) + + subject.handle_action( + SucceedCommandAction( + command=load_command, + state_update=update_types.StateUpdate( + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + liquid_presence_detection=None, + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="pipette-id" + ), + ), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=pick_up_tip_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="pipette-id", + tip_geometry=TipGeometry(volume=47, length=101, diameter=8.0), + ), + pipette_aspirated_fluid=update_types.PipetteEmptyFluidUpdate( + pipette_id="pipette-id" + ), + ), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=aspirate_command, + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteAspiratedFluidUpdate( + pipette_id="pipette-id", + fluid=AspiratedFluid(kind=FluidKind.LIQUID, volume=42), + ) + ), + ) + ) + subject.handle_action( + SucceedCommandAction(command=dispense_command, state_update=dispense_update) + ) + + assert subject.state.pipette_contents_by_id["pipette-id"] == FluidStack( + _fluid_stack=[AspiratedFluid(kind=FluidKind.LIQUID, volume=21)] + ) + + subject.handle_action( + SucceedCommandAction(command=dispense_command, state_update=dispense_update) + ) + + assert subject.state.pipette_contents_by_id["pipette-id"] == FluidStack() + + +@pytest.mark.parametrize( + "blow_out_command", + [ + create_blow_out_command("pipette-id", 1.23), + create_blow_out_in_place_command("pipette-id", 1.23), + create_unsafe_blow_out_in_place_command("pipette-id", 1.23), + ], +) +def test_blow_out_clears_volume( + subject: PipetteStore, blow_out_command: cmd.Command +) -> None: + """It should wipe out the aspirated volume after a blowOut.""" + load_command = create_load_pipette_command( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + ) + pick_up_tip_command = create_pick_up_tip_command( + pipette_id="pipette-id", tip_volume=47, tip_length=101, tip_diameter=8.0 + ) + + aspirate_command = create_aspirate_command( + pipette_id="pipette-id", + volume=42, + flow_rate=1.23, + ) + + subject.handle_action( + SucceedCommandAction( + command=load_command, + state_update=update_types.StateUpdate( + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + liquid_presence_detection=None, + ) + ), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=pick_up_tip_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="pipette-id", + tip_geometry=TipGeometry(volume=47, length=101, diameter=8.0), + ), + pipette_aspirated_fluid=update_types.PipetteEmptyFluidUpdate( + pipette_id="pipette-id" + ), + ), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=aspirate_command, + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteAspiratedFluidUpdate( + pipette_id="pipette-id", + fluid=AspiratedFluid(kind=FluidKind.LIQUID, volume=42), + ) + ), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=blow_out_command, + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="pipette-id" + ) + ), + ) + ) + + assert subject.state.pipette_contents_by_id["pipette-id"] is None + + +def test_set_movement_speed(subject: PipetteStore) -> None: + """It should issue an action to set the movement speed.""" + pipette_id = "pipette-id" + load_pipette_command = create_load_pipette_command( + pipette_id=pipette_id, + pipette_name=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + ) + subject.handle_action(SucceedCommandAction(command=load_pipette_command)) + subject.handle_action( + SetPipetteMovementSpeedAction(pipette_id=pipette_id, speed=123.456) + ) + assert subject.state.movement_speed_by_id[pipette_id] == 123.456 + + +def test_add_pipette_config( + subject: PipetteStore, + supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: pipette_definition.AvailableSensorDefinition, +) -> None: + """It should update state from any pipette config private result.""" + command = cmd.LoadPipette.construct( # type: ignore[call-arg] + params=cmd.LoadPipetteParams.construct( + mount=MountType.LEFT, pipetteName="p300_single" # type: ignore[arg-type] + ), + result=cmd.LoadPipetteResult(pipetteId="pipette-id"), + ) + config = LoadedStaticPipetteData( + model="pipette-model", + display_name="pipette name", + min_volume=1.23, + max_volume=4.56, + channels=7, + flow_rates=FlowRates( + default_aspirate={"a": 1}, + default_dispense={"b": 2}, + default_blow_out={"c": 3}, + ), + tip_configuration_lookup_table={4: supported_tip_fixture}, + nominal_tip_overlap={"default": 5}, + home_position=8.9, + nozzle_offset_z=10.11, + nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), + back_left_corner_offset=Point(x=1, y=2, z=3), + front_right_corner_offset=Point(x=4, y=5, z=6), + pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ) + + subject.handle_action( + SucceedCommandAction( + command=command, + state_update=update_types.StateUpdate( + pipette_config=update_types.PipetteConfigUpdate( + pipette_id="pipette-id", + config=config, + serial_number="pipette-serial", + ) + ), + ) + ) + + assert subject.state.static_config_by_id["pipette-id"] == StaticPipetteConfig( + model="pipette-model", + serial_number="pipette-serial", + display_name="pipette name", + min_volume=1.23, + max_volume=4.56, + channels=7, + tip_configuration_lookup_table={4: supported_tip_fixture}, + nominal_tip_overlap={"default": 5}, + home_position=8.9, + nozzle_offset_z=10.11, + bounding_nozzle_offsets=BoundingNozzlesOffsets( + back_left_offset=Point(x=0, y=0, z=0), + front_right_offset=Point(x=0, y=0, z=0), + ), + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), + pipette_bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(x=1, y=2, z=3), + front_right_corner=Point(x=4, y=5, z=6), + front_left_corner=Point(x=1, y=5, z=3), + back_right_corner=Point(x=4, y=2, z=3), + ), + lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ) + assert subject.state.flow_rates_by_id["pipette-id"].default_aspirate == {"a": 1.0} + assert subject.state.flow_rates_by_id["pipette-id"].default_dispense == {"b": 2.0} + assert subject.state.flow_rates_by_id["pipette-id"].default_blow_out == {"c": 3.0} + + +@pytest.mark.parametrize( + "previous_cmd,previous_state", + [ + ( + create_blow_out_command(pipette_id="pipette-id", flow_rate=1.0), + update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="pipette-id" + ) + ), + ), + ( + create_dispense_command(pipette_id="pipette-id", volume=10, flow_rate=1.0), + update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteEjectedFluidUpdate( + pipette_id="pipette-id", volume=10 + ) + ), + ), + ], +) +def test_prepare_to_aspirate_marks_pipette_ready( + subject: PipetteStore, + previous_cmd: cmd.Command, + previous_state: update_types.StateUpdate, +) -> None: + """It should mark a pipette as ready to aspirate.""" + load_pipette_command = create_load_pipette_command( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P50_MULTI_FLEX, + mount=MountType.LEFT, + ) + pick_up_tip_command = create_pick_up_tip_command( + pipette_id="pipette-id", tip_volume=42, tip_length=101, tip_diameter=8.0 + ) + subject.handle_action( + SucceedCommandAction( + command=load_pipette_command, + state_update=update_types.StateUpdate( + loaded_pipette=update_types.LoadPipetteUpdate( + pipette_id="pipette-id", + pipette_name=PipetteNameType.P50_MULTI_FLEX, + mount=MountType.LEFT, + liquid_presence_detection=None, + ), + pipette_aspirated_fluid=update_types.PipetteUnknownFluidUpdate( + pipette_id="pipette-id" + ), + ), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=pick_up_tip_command, + state_update=update_types.StateUpdate( + pipette_tip_state=update_types.PipetteTipStateUpdate( + pipette_id="pipette-id", + tip_geometry=TipGeometry(volume=42, length=101, diameter=8.0), + ), + pipette_aspirated_fluid=update_types.PipetteEmptyFluidUpdate( + pipette_id="xyz" + ), + ), + ) + ) + + subject.handle_action( + SucceedCommandAction(command=previous_cmd, state_update=previous_state) + ) + + prepare_to_aspirate_command = create_prepare_to_aspirate_command( + pipette_id="pipette-id" + ) + subject.handle_action( + SucceedCommandAction( + command=prepare_to_aspirate_command, + state_update=update_types.StateUpdate( + pipette_aspirated_fluid=update_types.PipetteEmptyFluidUpdate( + pipette_id="pipette-id" + ) + ), + ) + ) + assert subject.state.pipette_contents_by_id["pipette-id"] == FluidStack() diff --git a/api/tests/opentrons/protocol_engine/state/test_pipette_view.py b/api/tests/opentrons/protocol_engine/state/test_pipette_view.py deleted file mode 100644 index e8823c3c6ad..00000000000 --- a/api/tests/opentrons/protocol_engine/state/test_pipette_view.py +++ /dev/null @@ -1,816 +0,0 @@ -"""Tests for pipette state accessors in the protocol_engine state store.""" -from collections import OrderedDict - -import pytest -from typing import cast, Dict, List, Optional, Tuple, NamedTuple - -from opentrons_shared_data.pipette.types import PipetteNameType -from opentrons_shared_data.pipette import pipette_definition -from opentrons_shared_data.pipette.pipette_definition import ValidNozzleMaps - -from opentrons.config.defaults_ot2 import Z_RETRACT_DISTANCE -from opentrons.types import MountType, Mount as HwMount, Point -from opentrons.hardware_control.dev_types import PipetteDict -from opentrons.protocol_engine import errors -from opentrons.protocol_engine.types import ( - LoadedPipette, - MotorAxis, - FlowRates, - DeckPoint, - CurrentPipetteLocation, - TipGeometry, -) -from opentrons.protocol_engine.state.pipettes import ( - PipetteState, - PipetteView, - CurrentDeckPoint, - HardwarePipette, - StaticPipetteConfig, - BoundingNozzlesOffsets, - PipetteBoundingBoxOffsets, -) -from opentrons.hardware_control.nozzle_manager import NozzleMap, NozzleConfigurationType -from opentrons.protocol_engine.errors import TipNotAttachedError, PipetteNotLoadedError - -from ..pipette_fixtures import ( - NINETY_SIX_ROWS, - NINETY_SIX_COLS, - NINETY_SIX_MAP, - EIGHT_CHANNEL_ROWS, - EIGHT_CHANNEL_COLS, - EIGHT_CHANNEL_MAP, - get_default_nozzle_map, -) - -_SAMPLE_NOZZLE_BOUNDS_OFFSETS = BoundingNozzlesOffsets( - back_left_offset=Point(x=10, y=20, z=30), front_right_offset=Point(x=40, y=50, z=60) -) -_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS = PipetteBoundingBoxOffsets( - back_left_corner=Point(x=10, y=20, z=30), - front_right_corner=Point(x=40, y=50, z=60), - front_left_corner=Point(x=10, y=50, z=60), - back_right_corner=Point(x=40, y=20, z=60), -) - - -def get_pipette_view( - pipettes_by_id: Optional[Dict[str, LoadedPipette]] = None, - aspirated_volume_by_id: Optional[Dict[str, Optional[float]]] = None, - current_well: Optional[CurrentPipetteLocation] = None, - current_deck_point: CurrentDeckPoint = CurrentDeckPoint( - mount=None, deck_point=None - ), - attached_tip_by_id: Optional[Dict[str, Optional[TipGeometry]]] = None, - movement_speed_by_id: Optional[Dict[str, Optional[float]]] = None, - static_config_by_id: Optional[Dict[str, StaticPipetteConfig]] = None, - flow_rates_by_id: Optional[Dict[str, FlowRates]] = None, - nozzle_layout_by_id: Optional[Dict[str, Optional[NozzleMap]]] = None, - liquid_presence_detection_by_id: Optional[Dict[str, bool]] = None, -) -> PipetteView: - """Get a pipette view test subject with the specified state.""" - state = PipetteState( - pipettes_by_id=pipettes_by_id or {}, - aspirated_volume_by_id=aspirated_volume_by_id or {}, - current_location=current_well, - current_deck_point=current_deck_point, - attached_tip_by_id=attached_tip_by_id or {}, - movement_speed_by_id=movement_speed_by_id or {}, - static_config_by_id=static_config_by_id or {}, - flow_rates_by_id=flow_rates_by_id or {}, - nozzle_configuration_by_id=nozzle_layout_by_id or {}, - liquid_presence_detection_by_id=liquid_presence_detection_by_id or {}, - ) - - return PipetteView(state=state) - - -def create_pipette_config( - name: str, - back_compat_names: Optional[List[str]] = None, - ready_to_aspirate: bool = False, -) -> PipetteDict: - """Create a fake but valid (enough) PipetteDict object.""" - return cast( - PipetteDict, - { - "name": name, - "back_compat_names": back_compat_names or [], - "ready_to_aspirate": ready_to_aspirate, - }, - ) - - -def test_initial_pipette_data_by_id() -> None: - """It should should raise if pipette ID doesn't exist.""" - subject = get_pipette_view() - - with pytest.raises(errors.PipetteNotLoadedError): - subject.get("asdfghjkl") - - -def test_initial_pipette_data_by_mount() -> None: - """It should return None if mount isn't present.""" - subject = get_pipette_view() - - assert subject.get_by_mount(MountType.LEFT) is None - assert subject.get_by_mount(MountType.RIGHT) is None - - -def test_get_pipette_data() -> None: - """It should get pipette data by ID and mount from the state.""" - pipette_data = LoadedPipette( - id="pipette-id", - pipetteName=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - - subject = get_pipette_view(pipettes_by_id={"pipette-id": pipette_data}) - - result_by_id = subject.get("pipette-id") - result_by_mount = subject.get_by_mount(MountType.LEFT) - - assert result_by_id == pipette_data - assert result_by_mount == pipette_data - assert subject.get_mount("pipette-id") == MountType.LEFT - - -def test_get_hardware_pipette() -> None: - """It maps a pipette ID to a config given the HC's attached pipettes.""" - pipette_config = create_pipette_config("p300_single") - attached_pipettes: Dict[HwMount, PipetteDict] = { - HwMount.LEFT: pipette_config, - HwMount.RIGHT: cast(PipetteDict, {}), - } - - subject = get_pipette_view( - pipettes_by_id={ - "left-id": LoadedPipette( - id="left-id", - mount=MountType.LEFT, - pipetteName=PipetteNameType.P300_SINGLE, - ), - "right-id": LoadedPipette( - id="right-id", - mount=MountType.RIGHT, - pipetteName=PipetteNameType.P300_MULTI, - ), - } - ) - - result = subject.get_hardware_pipette( - pipette_id="left-id", - attached_pipettes=attached_pipettes, - ) - - assert result == HardwarePipette(mount=HwMount.LEFT, config=pipette_config) - - with pytest.raises(errors.PipetteNotAttachedError): - subject.get_hardware_pipette( - pipette_id="right-id", - attached_pipettes=attached_pipettes, - ) - - -def test_get_hardware_pipette_with_back_compat() -> None: - """It maps a pipette ID to a config given the HC's attached pipettes. - - In this test, the hardware config specs "p300_single_gen2", and the - loaded pipette name in state is "p300_single," which is is allowed. - """ - pipette_config = create_pipette_config( - "p300_single_gen2", - back_compat_names=["p300_single"], - ) - attached_pipettes: Dict[HwMount, PipetteDict] = { - HwMount.LEFT: pipette_config, - HwMount.RIGHT: cast(PipetteDict, {}), - } - - subject = get_pipette_view( - pipettes_by_id={ - "pipette-id": LoadedPipette( - id="pipette-id", - mount=MountType.LEFT, - pipetteName=PipetteNameType.P300_SINGLE, - ), - } - ) - - result = subject.get_hardware_pipette( - pipette_id="pipette-id", - attached_pipettes=attached_pipettes, - ) - - assert result == HardwarePipette(mount=HwMount.LEFT, config=pipette_config) - - -def test_get_hardware_pipette_raises_with_name_mismatch() -> None: - """It maps a pipette ID to a config given the HC's attached pipettes. - - In this test, the hardware config specs "p300_single_gen2", but the - loaded pipette name in state is "p10_single," which does not match. - """ - pipette_config = create_pipette_config("p300_single_gen2") - attached_pipettes: Dict[HwMount, Optional[PipetteDict]] = { - HwMount.LEFT: pipette_config, - HwMount.RIGHT: cast(PipetteDict, {}), - } - - subject = get_pipette_view( - pipettes_by_id={ - "pipette-id": LoadedPipette( - id="pipette-id", - mount=MountType.LEFT, - pipetteName=PipetteNameType.P10_SINGLE, - ), - } - ) - - with pytest.raises(errors.PipetteNotAttachedError): - subject.get_hardware_pipette( - pipette_id="pipette-id", - attached_pipettes=attached_pipettes, - ) - - -def test_get_aspirated_volume() -> None: - """It should get the aspirate volume for a pipette.""" - subject = get_pipette_view( - aspirated_volume_by_id={ - "pipette-id": 42, - "pipette-id-none": None, - "pipette-id-no-tip": None, - }, - attached_tip_by_id={ - "pipette-id": TipGeometry(length=1, volume=2, diameter=3), - "pipette-id-none": TipGeometry(length=4, volume=5, diameter=6), - "pipette-id-no-tip": None, - }, - ) - - assert subject.get_aspirated_volume("pipette-id") == 42 - assert subject.get_aspirated_volume("pipette-id-none") is None - - with pytest.raises(errors.PipetteNotLoadedError): - subject.get_aspirated_volume("not-an-id") - - with pytest.raises(errors.TipNotAttachedError): - subject.get_aspirated_volume("pipette-id-no-tip") - - -def test_get_pipette_working_volume( - supported_tip_fixture: pipette_definition.SupportedTipsDefinition, -) -> None: - """It should get the minimum value of tip volume and max volume.""" - subject = get_pipette_view( - attached_tip_by_id={ - "pipette-id": TipGeometry(length=1, volume=1337, diameter=42.0), - }, - static_config_by_id={ - "pipette-id": StaticPipetteConfig( - min_volume=1, - max_volume=9001, - channels=5, - model="blah", - display_name="bleh", - serial_number="", - tip_configuration_lookup_table={9001: supported_tip_fixture}, - nominal_tip_overlap={}, - home_position=0, - nozzle_offset_z=0, - bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, - default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), - pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, - lld_settings={}, - ) - }, - ) - - assert subject.get_working_volume("pipette-id") == 1337 - - -def test_get_pipette_working_volume_raises_if_tip_volume_is_none( - supported_tip_fixture: pipette_definition.SupportedTipsDefinition, -) -> None: - """Should raise an exception that no tip is attached.""" - subject = get_pipette_view( - attached_tip_by_id={ - "pipette-id": None, - }, - static_config_by_id={ - "pipette-id": StaticPipetteConfig( - min_volume=1, - max_volume=9001, - channels=5, - model="blah", - display_name="bleh", - serial_number="", - tip_configuration_lookup_table={9001: supported_tip_fixture}, - nominal_tip_overlap={}, - home_position=0, - nozzle_offset_z=0, - bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, - default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), - pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, - lld_settings={}, - ) - }, - ) - - with pytest.raises(TipNotAttachedError): - subject.get_working_volume("pipette-id") - - with pytest.raises(PipetteNotLoadedError): - subject.get_working_volume("wrong-id") - - -def test_get_pipette_available_volume( - supported_tip_fixture: pipette_definition.SupportedTipsDefinition, -) -> None: - """It should get the available volume for a pipette.""" - subject = get_pipette_view( - attached_tip_by_id={ - "pipette-id": TipGeometry( - length=1, - diameter=2, - volume=100, - ), - }, - aspirated_volume_by_id={"pipette-id": 58}, - static_config_by_id={ - "pipette-id": StaticPipetteConfig( - min_volume=1, - max_volume=123, - channels=3, - model="blah", - display_name="bleh", - serial_number="", - tip_configuration_lookup_table={123: supported_tip_fixture}, - nominal_tip_overlap={}, - home_position=0, - nozzle_offset_z=0, - bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, - default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), - pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, - lld_settings={}, - ), - "pipette-id-none": StaticPipetteConfig( - min_volume=1, - max_volume=123, - channels=5, - model="blah", - display_name="bleh", - serial_number="", - tip_configuration_lookup_table={123: supported_tip_fixture}, - nominal_tip_overlap={}, - home_position=0, - nozzle_offset_z=0, - bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, - default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), - pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, - lld_settings={}, - ), - }, - ) - - assert subject.get_available_volume("pipette-id") == 42 - - -def test_get_attached_tip() -> None: - """It should get the tip-rack ID map of a pipette's attached tip.""" - subject = get_pipette_view( - attached_tip_by_id={ - "foo": TipGeometry(length=1, volume=2, diameter=3), - "bar": None, - } - ) - - assert subject.get_attached_tip("foo") == TipGeometry( - length=1, volume=2, diameter=3 - ) - assert subject.get_attached_tip("bar") is None - assert subject.get_all_attached_tips() == [ - ("foo", TipGeometry(length=1, volume=2, diameter=3)), - ] - - -def test_validate_tip_state() -> None: - """It should validate a pipette's tip attached state.""" - subject = get_pipette_view( - attached_tip_by_id={ - "has-tip": TipGeometry(length=1, volume=2, diameter=3), - "no-tip": None, - } - ) - - subject.validate_tip_state(pipette_id="has-tip", expected_has_tip=True) - subject.validate_tip_state(pipette_id="no-tip", expected_has_tip=False) - - with pytest.raises(errors.TipAttachedError): - subject.validate_tip_state(pipette_id="has-tip", expected_has_tip=False) - - with pytest.raises(errors.TipNotAttachedError): - subject.validate_tip_state(pipette_id="no-tip", expected_has_tip=True) - - -def test_get_movement_speed() -> None: - """It should return the movement speed that was set for the given pipette.""" - subject = get_pipette_view( - movement_speed_by_id={ - "pipette-with-movement-speed": 123.456, - "pipette-without-movement-speed": None, - } - ) - - assert ( - subject.get_movement_speed(pipette_id="pipette-with-movement-speed") == 123.456 - ) - assert ( - subject.get_movement_speed(pipette_id="pipette-without-movement-speed") is None - ) - - -@pytest.mark.parametrize( - ("mount", "deck_point", "expected_deck_point"), - [ - (MountType.LEFT, DeckPoint(x=1, y=2, z=3), DeckPoint(x=1, y=2, z=3)), - (MountType.LEFT, None, None), - (MountType.RIGHT, DeckPoint(x=1, y=2, z=3), None), - (None, DeckPoint(x=1, y=2, z=3), None), - (None, None, None), - ], -) -def test_get_deck_point( - mount: Optional[MountType], - deck_point: Optional[DeckPoint], - expected_deck_point: Optional[DeckPoint], -) -> None: - """It should return the deck point for the given pipette.""" - pipette_data = LoadedPipette( - id="pipette-id", - pipetteName=PipetteNameType.P300_SINGLE, - mount=MountType.LEFT, - ) - - subject = get_pipette_view( - pipettes_by_id={"pipette-id": pipette_data}, - current_deck_point=CurrentDeckPoint( - mount=MountType.LEFT, deck_point=DeckPoint(x=1, y=2, z=3) - ), - ) - - assert subject.get_deck_point(pipette_id="pipette-id") == DeckPoint(x=1, y=2, z=3) - - -def test_get_static_config( - supported_tip_fixture: pipette_definition.SupportedTipsDefinition, -) -> None: - """It should return the static pipette configuration that was set for the given pipette.""" - config = StaticPipetteConfig( - model="pipette-model", - display_name="display name", - serial_number="serial-number", - min_volume=1.23, - max_volume=4.56, - channels=9, - tip_configuration_lookup_table={4.56: supported_tip_fixture}, - nominal_tip_overlap={}, - home_position=10.12, - nozzle_offset_z=12.13, - bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, - default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), - pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, - lld_settings={}, - ) - - subject = get_pipette_view( - pipettes_by_id={ - "pipette-id": LoadedPipette( - id="pipette-id", - mount=MountType.LEFT, - pipetteName=PipetteNameType.P300_SINGLE, - ) - }, - attached_tip_by_id={ - "pipette-id": TipGeometry(length=1, volume=4.56, diameter=3), - }, - static_config_by_id={"pipette-id": config}, - ) - - assert subject.get_config("pipette-id") == config - assert subject.get_model_name("pipette-id") == "pipette-model" - assert subject.get_display_name("pipette-id") == "display name" - assert subject.get_serial_number("pipette-id") == "serial-number" - assert subject.get_minimum_volume("pipette-id") == 1.23 - assert subject.get_maximum_volume("pipette-id") == 4.56 - assert subject.get_return_tip_scale("pipette-id") == 0.5 - assert ( - subject.get_instrument_max_height_ot2("pipette-id") - == 22.25 - Z_RETRACT_DISTANCE - ) - - -def test_get_nominal_tip_overlap( - supported_tip_fixture: pipette_definition.SupportedTipsDefinition, -) -> None: - """It should return the static pipette configuration that was set for the given pipette.""" - config = StaticPipetteConfig( - model="", - display_name="", - serial_number="", - min_volume=0, - max_volume=0, - channels=10, - tip_configuration_lookup_table={0: supported_tip_fixture}, - nominal_tip_overlap={ - "some-uri": 100, - "default": 10, - }, - home_position=0, - nozzle_offset_z=0, - bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, - default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), - pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, - lld_settings={}, - ) - - subject = get_pipette_view(static_config_by_id={"pipette-id": config}) - - assert subject.get_nominal_tip_overlap("pipette-id", "some-uri") == 100 - assert subject.get_nominal_tip_overlap("pipette-id", "missing-uri") == 10 - - -@pytest.mark.parametrize( - ("mount", "expected_z_axis", "expected_plunger_axis"), - [ - (MountType.LEFT, MotorAxis.LEFT_Z, MotorAxis.LEFT_PLUNGER), - (MountType.RIGHT, MotorAxis.RIGHT_Z, MotorAxis.RIGHT_PLUNGER), - ], -) -def test_get_motor_axes( - mount: MountType, expected_z_axis: MotorAxis, expected_plunger_axis: MotorAxis -) -> None: - """It should get a pipette's motor axes.""" - subject = get_pipette_view( - pipettes_by_id={ - "pipette-id": LoadedPipette( - id="pipette-id", - mount=mount, - pipetteName=PipetteNameType.P300_SINGLE, - ), - }, - ) - - assert subject.get_z_axis("pipette-id") == expected_z_axis - assert subject.get_plunger_axis("pipette-id") == expected_plunger_axis - - -def test_nozzle_configuration_getters() -> None: - """Test that pipette view returns correct nozzle configuration data.""" - nozzle_map = NozzleMap.build( - physical_nozzles=OrderedDict({"A1": Point(0, 0, 0)}), - physical_rows=OrderedDict({"A": ["A1"]}), - physical_columns=OrderedDict({"1": ["A1"]}), - starting_nozzle="A1", - back_left_nozzle="A1", - front_right_nozzle="A1", - valid_nozzle_maps=ValidNozzleMaps(maps={"A1": ["A1"]}), - ) - subject = get_pipette_view(nozzle_layout_by_id={"pipette-id": nozzle_map}) - assert subject.get_nozzle_layout_type("pipette-id") == NozzleConfigurationType.FULL - assert subject.get_is_partially_configured("pipette-id") is False - assert subject.get_primary_nozzle("pipette-id") == "A1" - - -class _PipetteSpecs(NamedTuple): - tip_length: float - bounding_box_offsets: PipetteBoundingBoxOffsets - nozzle_map: NozzleMap - destination_position: Point - nozzle_bounds_result: Tuple[Point, Point, Point, Point] - - -_pipette_spec_cases = [ - _PipetteSpecs( - # 8-channel P300, full configuration - tip_length=42, - bounding_box_offsets=PipetteBoundingBoxOffsets( - back_left_corner=Point(0.0, 31.5, 35.52), - front_right_corner=Point(0.0, -31.5, 35.52), - front_left_corner=Point(0.0, -31.5, 35.52), - back_right_corner=Point(0.0, 31.5, 35.52), - ), - nozzle_map=NozzleMap.build( - physical_nozzles=EIGHT_CHANNEL_MAP, - physical_rows=EIGHT_CHANNEL_ROWS, - physical_columns=EIGHT_CHANNEL_COLS, - starting_nozzle="A1", - back_left_nozzle="A1", - front_right_nozzle="H1", - valid_nozzle_maps=ValidNozzleMaps(maps={"Full": EIGHT_CHANNEL_COLS["1"]}), - ), - destination_position=Point(100, 200, 300), - nozzle_bounds_result=( - ( - Point(x=100.0, y=200.0, z=342.0), - Point(x=100.0, y=137.0, z=342.0), - Point(x=100.0, y=200.0, z=342.0), - Point(x=100.0, y=137.0, z=342.0), - ) - ), - ), - _PipetteSpecs( - # 8-channel P300, single configuration - tip_length=42, - bounding_box_offsets=PipetteBoundingBoxOffsets( - back_left_corner=Point(0.0, 31.5, 35.52), - front_right_corner=Point(0.0, -31.5, 35.52), - front_left_corner=Point(0.0, -31.5, 35.52), - back_right_corner=Point(0.0, 31.5, 35.52), - ), - nozzle_map=NozzleMap.build( - physical_nozzles=EIGHT_CHANNEL_MAP, - physical_rows=EIGHT_CHANNEL_ROWS, - physical_columns=EIGHT_CHANNEL_COLS, - starting_nozzle="H1", - back_left_nozzle="H1", - front_right_nozzle="H1", - valid_nozzle_maps=ValidNozzleMaps(maps={"H1": ["H1"]}), - ), - destination_position=Point(100, 200, 300), - nozzle_bounds_result=( - ( - Point(x=100.0, y=263.0, z=342.0), - Point(x=100.0, y=200.0, z=342.0), - Point(x=100.0, y=263.0, z=342.0), - Point(x=100.0, y=200.0, z=342.0), - ) - ), - ), - _PipetteSpecs( - # 96-channel P1000, full configuration - tip_length=42, - bounding_box_offsets=PipetteBoundingBoxOffsets( - back_left_corner=Point(-36.0, -25.5, -259.15), - front_right_corner=Point(63.0, -88.5, -259.15), - front_left_corner=Point(-36.0, -88.5, -259.15), - back_right_corner=Point(63.0, -25.5, -259.15), - ), - nozzle_map=NozzleMap.build( - physical_nozzles=NINETY_SIX_MAP, - physical_rows=NINETY_SIX_ROWS, - physical_columns=NINETY_SIX_COLS, - starting_nozzle="A1", - back_left_nozzle="A1", - front_right_nozzle="H12", - valid_nozzle_maps=ValidNozzleMaps( - maps={ - "Full": sum( - [ - NINETY_SIX_ROWS["A"], - NINETY_SIX_ROWS["B"], - NINETY_SIX_ROWS["C"], - NINETY_SIX_ROWS["D"], - NINETY_SIX_ROWS["E"], - NINETY_SIX_ROWS["F"], - NINETY_SIX_ROWS["G"], - NINETY_SIX_ROWS["H"], - ], - [], - ) - } - ), - ), - destination_position=Point(100, 200, 300), - nozzle_bounds_result=( - ( - Point(x=100.0, y=200.0, z=342.0), - Point(x=199.0, y=137.0, z=342.0), - Point(x=199.0, y=200.0, z=342.0), - Point(x=100.0, y=137.0, z=342.0), - ) - ), - ), - _PipetteSpecs( - # 96-channel P1000, A1 COLUMN configuration - tip_length=42, - bounding_box_offsets=PipetteBoundingBoxOffsets( - back_left_corner=Point(-36.0, -25.5, -259.15), - front_right_corner=Point(63.0, -88.5, -259.15), - front_left_corner=Point(-36.0, -88.5, -259.15), - back_right_corner=Point(63.0, -25.5, -259.15), - ), - nozzle_map=NozzleMap.build( - physical_nozzles=NINETY_SIX_MAP, - physical_rows=NINETY_SIX_ROWS, - physical_columns=NINETY_SIX_COLS, - starting_nozzle="A1", - back_left_nozzle="A1", - front_right_nozzle="H1", - valid_nozzle_maps=ValidNozzleMaps(maps={"Column1": NINETY_SIX_COLS["1"]}), - ), - destination_position=Point(100, 200, 300), - nozzle_bounds_result=( - Point(100, 200, 342), - Point(199, 137, 342), - Point(199, 200, 342), - Point(100, 137, 342), - ), - ), - _PipetteSpecs( - # 96-channel P1000, A12 COLUMN configuration - tip_length=42, - bounding_box_offsets=PipetteBoundingBoxOffsets( - back_left_corner=Point(-36.0, -25.5, -259.15), - front_right_corner=Point(63.0, -88.5, -259.15), - front_left_corner=Point(-36.0, -88.5, -259.15), - back_right_corner=Point(63.0, -25.5, -259.15), - ), - nozzle_map=NozzleMap.build( - physical_nozzles=NINETY_SIX_MAP, - physical_rows=NINETY_SIX_ROWS, - physical_columns=NINETY_SIX_COLS, - starting_nozzle="A12", - back_left_nozzle="A12", - front_right_nozzle="H12", - valid_nozzle_maps=ValidNozzleMaps(maps={"Column12": NINETY_SIX_COLS["12"]}), - ), - destination_position=Point(100, 200, 300), - nozzle_bounds_result=( - Point(1, 200, 342), - Point(100, 137, 342), - Point(100, 200, 342), - Point(1, 137, 342), - ), - ), - _PipetteSpecs( - # 96-channel P1000, ROW configuration - tip_length=42, - bounding_box_offsets=PipetteBoundingBoxOffsets( - back_left_corner=Point(-36.0, -25.5, -259.15), - front_right_corner=Point(63.0, -88.5, -259.15), - front_left_corner=Point(-36.0, -88.5, -259.15), - back_right_corner=Point(63.0, -25.5, -259.15), - ), - nozzle_map=NozzleMap.build( - physical_nozzles=NINETY_SIX_MAP, - physical_rows=NINETY_SIX_ROWS, - physical_columns=NINETY_SIX_COLS, - starting_nozzle="A1", - back_left_nozzle="A1", - front_right_nozzle="A12", - valid_nozzle_maps=ValidNozzleMaps(maps={"RowA": NINETY_SIX_ROWS["A"]}), - ), - destination_position=Point(100, 200, 300), - nozzle_bounds_result=( - Point(100, 200, 342), - Point(199, 137, 342), - Point(199, 200, 342), - Point(100, 137, 342), - ), - ), -] - - -@pytest.mark.parametrize( - argnames=_PipetteSpecs._fields, - argvalues=_pipette_spec_cases, -) -def test_get_nozzle_bounds_at_location( - tip_length: float, - bounding_box_offsets: PipetteBoundingBoxOffsets, - nozzle_map: NozzleMap, - destination_position: Point, - nozzle_bounds_result: Tuple[Point, Point, Point, Point], -) -> None: - """It should get the pipette's nozzle's bounds at the given location.""" - subject = get_pipette_view( - nozzle_layout_by_id={"pipette-id": nozzle_map}, - attached_tip_by_id={ - "pipette-id": TipGeometry(length=tip_length, diameter=123, volume=123), - }, - static_config_by_id={ - "pipette-id": StaticPipetteConfig( - min_volume=1, - max_volume=9001, - channels=5, - model="blah", - display_name="bleh", - serial_number="", - tip_configuration_lookup_table={}, - nominal_tip_overlap={}, - home_position=0, - nozzle_offset_z=0, - default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), - bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, - pipette_bounding_box_offsets=bounding_box_offsets, - lld_settings={}, - ) - }, - ) - assert ( - subject.get_pipette_bounds_at_specified_move_to_position( - pipette_id="pipette-id", destination_position=destination_position - ) - == nozzle_bounds_result - ) diff --git a/api/tests/opentrons/protocol_engine/state/test_pipette_view_old.py b/api/tests/opentrons/protocol_engine/state/test_pipette_view_old.py new file mode 100644 index 00000000000..ce07e5fda8e --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_pipette_view_old.py @@ -0,0 +1,1191 @@ +"""Tests for pipette state accessors in the protocol_engine state store. + +DEPRECATED: Testing PipetteView independently of PipetteStore is no longer helpful. +Try to add new tests to test_pipette_state.py, where they can be tested together, +treating PipetteState as a private implementation detail. +""" + +from collections import OrderedDict +from typing import cast, Dict, List, Optional, Tuple, NamedTuple + +import pytest +from decoy import Decoy + + +from opentrons_shared_data.pipette.types import PipetteNameType +from opentrons_shared_data.pipette import pipette_definition +from opentrons_shared_data.pipette.pipette_definition import ( + ValidNozzleMaps, + AvailableSensorDefinition, +) + +from opentrons.config.defaults_ot2 import Z_RETRACT_DISTANCE +from opentrons.hardware_control import CriticalPoint +from opentrons.types import MountType, Mount as HwMount, Point, NozzleConfigurationType +from opentrons.hardware_control.dev_types import PipetteDict +from opentrons.protocol_engine import errors +from opentrons.protocol_engine.types import ( + LoadedPipette, + MotorAxis, + FlowRates, + DeckPoint, + CurrentPipetteLocation, + TipGeometry, +) +from opentrons.protocol_engine.state.pipettes import ( + PipetteState, + PipetteView, + CurrentDeckPoint, + HardwarePipette, + StaticPipetteConfig, + BoundingNozzlesOffsets, + PipetteBoundingBoxOffsets, +) +from opentrons.protocol_engine.state import fluid_stack +from opentrons.hardware_control.nozzle_manager import NozzleMap +from opentrons.protocol_engine.errors import TipNotAttachedError, PipetteNotLoadedError + +from ..pipette_fixtures import ( + NINETY_SIX_ROWS, + NINETY_SIX_COLS, + NINETY_SIX_MAP, + EIGHT_CHANNEL_ROWS, + EIGHT_CHANNEL_COLS, + EIGHT_CHANNEL_MAP, + get_default_nozzle_map, +) + +_SAMPLE_NOZZLE_BOUNDS_OFFSETS = BoundingNozzlesOffsets( + back_left_offset=Point(x=10, y=20, z=30), front_right_offset=Point(x=40, y=50, z=60) +) +_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS = PipetteBoundingBoxOffsets( + back_left_corner=Point(x=10, y=20, z=30), + front_right_corner=Point(x=40, y=50, z=60), + front_left_corner=Point(x=10, y=50, z=60), + back_right_corner=Point(x=40, y=20, z=60), +) + + +@pytest.fixture +def available_sensors() -> AvailableSensorDefinition: + """Provide a list of sensors.""" + return AvailableSensorDefinition(sensors=["pressure", "capacitive", "environment"]) + + +def get_pipette_view( + pipettes_by_id: Optional[Dict[str, LoadedPipette]] = None, + current_well: Optional[CurrentPipetteLocation] = None, + current_deck_point: CurrentDeckPoint = CurrentDeckPoint( + mount=None, deck_point=None + ), + attached_tip_by_id: Optional[Dict[str, Optional[TipGeometry]]] = None, + movement_speed_by_id: Optional[Dict[str, Optional[float]]] = None, + static_config_by_id: Optional[Dict[str, StaticPipetteConfig]] = None, + flow_rates_by_id: Optional[Dict[str, FlowRates]] = None, + nozzle_layout_by_id: Optional[Dict[str, NozzleMap]] = None, + liquid_presence_detection_by_id: Optional[Dict[str, bool]] = None, + pipette_contents_by_id: Optional[ + Dict[str, Optional[fluid_stack.FluidStack]] + ] = None, +) -> PipetteView: + """Get a pipette view test subject with the specified state.""" + state = PipetteState( + pipettes_by_id=pipettes_by_id or {}, + pipette_contents_by_id=pipette_contents_by_id or {}, + current_location=current_well, + current_deck_point=current_deck_point, + attached_tip_by_id=attached_tip_by_id or {}, + movement_speed_by_id=movement_speed_by_id or {}, + static_config_by_id=static_config_by_id or {}, + flow_rates_by_id=flow_rates_by_id or {}, + nozzle_configuration_by_id=nozzle_layout_by_id or {}, + liquid_presence_detection_by_id=liquid_presence_detection_by_id or {}, + ) + + return PipetteView(state=state) + + +def create_pipette_config( + name: str, + back_compat_names: Optional[List[str]] = None, + ready_to_aspirate: bool = False, +) -> PipetteDict: + """Create a fake but valid (enough) PipetteDict object.""" + return cast( + PipetteDict, + { + "name": name, + "back_compat_names": back_compat_names or [], + "ready_to_aspirate": ready_to_aspirate, + }, + ) + + +def test_initial_pipette_data_by_id() -> None: + """It should should raise if pipette ID doesn't exist.""" + subject = get_pipette_view() + + with pytest.raises(errors.PipetteNotLoadedError): + subject.get("asdfghjkl") + + +def test_initial_pipette_data_by_mount() -> None: + """It should return None if mount isn't present.""" + subject = get_pipette_view() + + assert subject.get_by_mount(MountType.LEFT) is None + assert subject.get_by_mount(MountType.RIGHT) is None + + +def test_get_pipette_data() -> None: + """It should get pipette data by ID and mount from the state.""" + pipette_data = LoadedPipette( + id="pipette-id", + pipetteName=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + ) + + subject = get_pipette_view(pipettes_by_id={"pipette-id": pipette_data}) + + result_by_id = subject.get("pipette-id") + result_by_mount = subject.get_by_mount(MountType.LEFT) + + assert result_by_id == pipette_data + assert result_by_mount == pipette_data + assert subject.get_mount("pipette-id") == MountType.LEFT + + +def test_get_hardware_pipette() -> None: + """It maps a pipette ID to a config given the HC's attached pipettes.""" + pipette_config = create_pipette_config("p300_single") + attached_pipettes: Dict[HwMount, PipetteDict] = { + HwMount.LEFT: pipette_config, + HwMount.RIGHT: cast(PipetteDict, {}), + } + + subject = get_pipette_view( + pipettes_by_id={ + "left-id": LoadedPipette( + id="left-id", + mount=MountType.LEFT, + pipetteName=PipetteNameType.P300_SINGLE, + ), + "right-id": LoadedPipette( + id="right-id", + mount=MountType.RIGHT, + pipetteName=PipetteNameType.P300_MULTI, + ), + } + ) + + result = subject.get_hardware_pipette( + pipette_id="left-id", + attached_pipettes=attached_pipettes, + ) + + assert result == HardwarePipette(mount=HwMount.LEFT, config=pipette_config) + + with pytest.raises(errors.PipetteNotAttachedError): + subject.get_hardware_pipette( + pipette_id="right-id", + attached_pipettes=attached_pipettes, + ) + + +def test_get_hardware_pipette_with_back_compat() -> None: + """It maps a pipette ID to a config given the HC's attached pipettes. + + In this test, the hardware config specs "p300_single_gen2", and the + loaded pipette name in state is "p300_single," which is is allowed. + """ + pipette_config = create_pipette_config( + "p300_single_gen2", + back_compat_names=["p300_single"], + ) + attached_pipettes: Dict[HwMount, PipetteDict] = { + HwMount.LEFT: pipette_config, + HwMount.RIGHT: cast(PipetteDict, {}), + } + + subject = get_pipette_view( + pipettes_by_id={ + "pipette-id": LoadedPipette( + id="pipette-id", + mount=MountType.LEFT, + pipetteName=PipetteNameType.P300_SINGLE, + ), + } + ) + + result = subject.get_hardware_pipette( + pipette_id="pipette-id", + attached_pipettes=attached_pipettes, + ) + + assert result == HardwarePipette(mount=HwMount.LEFT, config=pipette_config) + + +def test_get_hardware_pipette_raises_with_name_mismatch() -> None: + """It maps a pipette ID to a config given the HC's attached pipettes. + + In this test, the hardware config specs "p300_single_gen2", but the + loaded pipette name in state is "p10_single," which does not match. + """ + pipette_config = create_pipette_config("p300_single_gen2") + attached_pipettes: Dict[HwMount, Optional[PipetteDict]] = { + HwMount.LEFT: pipette_config, + HwMount.RIGHT: cast(PipetteDict, {}), + } + + subject = get_pipette_view( + pipettes_by_id={ + "pipette-id": LoadedPipette( + id="pipette-id", + mount=MountType.LEFT, + pipetteName=PipetteNameType.P10_SINGLE, + ), + } + ) + + with pytest.raises(errors.PipetteNotAttachedError): + subject.get_hardware_pipette( + pipette_id="pipette-id", + attached_pipettes=attached_pipettes, + ) + + +def test_get_aspirated_volume(decoy: Decoy) -> None: + """It should get the aspirate volume for a pipette.""" + stack = decoy.mock(cls=fluid_stack.FluidStack) + subject = get_pipette_view( + pipette_contents_by_id={ + "pipette-id": stack, + "pipette-id-none": None, + "pipette-id-no-tip": None, + }, + attached_tip_by_id={ + "pipette-id": TipGeometry(length=1, volume=2, diameter=3), + "pipette-id-none": TipGeometry(length=4, volume=5, diameter=6), + "pipette-id-no-tip": None, + }, + ) + decoy.when(stack.aspirated_volume()).then_return(42) + + assert subject.get_aspirated_volume("pipette-id") == 42 + assert subject.get_aspirated_volume("pipette-id-none") is None + + with pytest.raises(errors.PipetteNotLoadedError): + subject.get_aspirated_volume("not-an-id") + + with pytest.raises(errors.TipNotAttachedError): + subject.get_aspirated_volume("pipette-id-no-tip") + + +def test_get_pipette_working_volume( + supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, +) -> None: + """It should get the minimum value of tip volume and max volume.""" + subject = get_pipette_view( + attached_tip_by_id={ + "pipette-id": TipGeometry(length=1, volume=1337, diameter=42.0), + }, + static_config_by_id={ + "pipette-id": StaticPipetteConfig( + min_volume=1, + max_volume=9001, + channels=5, + model="blah", + display_name="bleh", + serial_number="", + tip_configuration_lookup_table={9001: supported_tip_fixture}, + nominal_tip_overlap={}, + home_position=0, + nozzle_offset_z=0, + bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), + pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, + lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ) + }, + ) + + assert subject.get_working_volume("pipette-id") == 1337 + + +def test_get_pipette_working_volume_raises_if_tip_volume_is_none( + supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, +) -> None: + """Should raise an exception that no tip is attached.""" + subject = get_pipette_view( + attached_tip_by_id={ + "pipette-id": None, + }, + static_config_by_id={ + "pipette-id": StaticPipetteConfig( + min_volume=1, + max_volume=9001, + channels=5, + model="blah", + display_name="bleh", + serial_number="", + tip_configuration_lookup_table={9001: supported_tip_fixture}, + nominal_tip_overlap={}, + home_position=0, + nozzle_offset_z=0, + bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), + pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, + lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ) + }, + ) + + with pytest.raises(TipNotAttachedError): + subject.get_working_volume("pipette-id") + + with pytest.raises(PipetteNotLoadedError): + subject.get_working_volume("wrong-id") + + +def test_get_pipette_available_volume( + supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + decoy: Decoy, + available_sensors: AvailableSensorDefinition, +) -> None: + """It should get the available volume for a pipette.""" + stack = decoy.mock(cls=fluid_stack.FluidStack) + decoy.when(stack.aspirated_volume()).then_return(58) + subject = get_pipette_view( + attached_tip_by_id={ + "pipette-id": TipGeometry( + length=1, + diameter=2, + volume=100, + ), + }, + pipette_contents_by_id={"pipette-id": stack}, + static_config_by_id={ + "pipette-id": StaticPipetteConfig( + min_volume=1, + max_volume=123, + channels=3, + model="blah", + display_name="bleh", + serial_number="", + tip_configuration_lookup_table={123: supported_tip_fixture}, + nominal_tip_overlap={}, + home_position=0, + nozzle_offset_z=0, + bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), + pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, + lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ), + "pipette-id-none": StaticPipetteConfig( + min_volume=1, + max_volume=123, + channels=5, + model="blah", + display_name="bleh", + serial_number="", + tip_configuration_lookup_table={123: supported_tip_fixture}, + nominal_tip_overlap={}, + home_position=0, + nozzle_offset_z=0, + bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), + pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, + lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ), + }, + ) + + assert subject.get_available_volume("pipette-id") == 42 + + +def test_get_attached_tip() -> None: + """It should get the tip-rack ID map of a pipette's attached tip.""" + subject = get_pipette_view( + attached_tip_by_id={ + "foo": TipGeometry(length=1, volume=2, diameter=3), + "bar": None, + } + ) + + assert subject.get_attached_tip("foo") == TipGeometry( + length=1, volume=2, diameter=3 + ) + assert subject.get_attached_tip("bar") is None + assert subject.get_all_attached_tips() == [ + ("foo", TipGeometry(length=1, volume=2, diameter=3)), + ] + + +def test_validate_tip_state() -> None: + """It should validate a pipette's tip attached state.""" + subject = get_pipette_view( + attached_tip_by_id={ + "has-tip": TipGeometry(length=1, volume=2, diameter=3), + "no-tip": None, + } + ) + + subject.validate_tip_state(pipette_id="has-tip", expected_has_tip=True) + subject.validate_tip_state(pipette_id="no-tip", expected_has_tip=False) + + with pytest.raises(errors.TipAttachedError): + subject.validate_tip_state(pipette_id="has-tip", expected_has_tip=False) + + with pytest.raises(errors.TipNotAttachedError): + subject.validate_tip_state(pipette_id="no-tip", expected_has_tip=True) + + +def test_get_movement_speed() -> None: + """It should return the movement speed that was set for the given pipette.""" + subject = get_pipette_view( + movement_speed_by_id={ + "pipette-with-movement-speed": 123.456, + "pipette-without-movement-speed": None, + } + ) + + assert ( + subject.get_movement_speed(pipette_id="pipette-with-movement-speed") == 123.456 + ) + assert ( + subject.get_movement_speed(pipette_id="pipette-without-movement-speed") is None + ) + + +@pytest.mark.parametrize( + ("mount", "deck_point", "expected_deck_point"), + [ + (MountType.LEFT, DeckPoint(x=1, y=2, z=3), DeckPoint(x=1, y=2, z=3)), + (MountType.LEFT, None, None), + (MountType.RIGHT, DeckPoint(x=1, y=2, z=3), None), + (None, DeckPoint(x=1, y=2, z=3), None), + (None, None, None), + ], +) +def test_get_deck_point( + mount: Optional[MountType], + deck_point: Optional[DeckPoint], + expected_deck_point: Optional[DeckPoint], +) -> None: + """It should return the deck point for the given pipette.""" + pipette_data = LoadedPipette( + id="pipette-id", + pipetteName=PipetteNameType.P300_SINGLE, + mount=MountType.LEFT, + ) + + subject = get_pipette_view( + pipettes_by_id={"pipette-id": pipette_data}, + current_deck_point=CurrentDeckPoint( + mount=MountType.LEFT, deck_point=DeckPoint(x=1, y=2, z=3) + ), + ) + + assert subject.get_deck_point(pipette_id="pipette-id") == DeckPoint(x=1, y=2, z=3) + + +def test_get_static_config( + supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, +) -> None: + """It should return the static pipette configuration that was set for the given pipette.""" + config = StaticPipetteConfig( + model="pipette-model", + display_name="display name", + serial_number="serial-number", + min_volume=1.23, + max_volume=4.56, + channels=9, + tip_configuration_lookup_table={4.56: supported_tip_fixture}, + nominal_tip_overlap={}, + home_position=10.12, + nozzle_offset_z=12.13, + bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), + pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, + lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ) + + subject = get_pipette_view( + pipettes_by_id={ + "pipette-id": LoadedPipette( + id="pipette-id", + mount=MountType.LEFT, + pipetteName=PipetteNameType.P300_SINGLE, + ) + }, + attached_tip_by_id={ + "pipette-id": TipGeometry(length=1, volume=4.56, diameter=3), + }, + static_config_by_id={"pipette-id": config}, + ) + + assert subject.get_config("pipette-id") == config + assert subject.get_model_name("pipette-id") == "pipette-model" + assert subject.get_display_name("pipette-id") == "display name" + assert subject.get_serial_number("pipette-id") == "serial-number" + assert subject.get_minimum_volume("pipette-id") == 1.23 + assert subject.get_maximum_volume("pipette-id") == 4.56 + assert subject.get_return_tip_scale("pipette-id") == 0.5 + assert ( + subject.get_instrument_max_height_ot2("pipette-id") + == 22.25 - Z_RETRACT_DISTANCE + ) + + +def test_get_nominal_tip_overlap( + supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, +) -> None: + """It should return the static pipette configuration that was set for the given pipette.""" + config = StaticPipetteConfig( + model="", + display_name="", + serial_number="", + min_volume=0, + max_volume=0, + channels=10, + tip_configuration_lookup_table={0: supported_tip_fixture}, + nominal_tip_overlap={ + "some-uri": 100, + "default": 10, + }, + home_position=0, + nozzle_offset_z=0, + bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), + pipette_bounding_box_offsets=_SAMPLE_PIPETTE_BOUNDING_BOX_OFFSETS, + lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ) + + subject = get_pipette_view(static_config_by_id={"pipette-id": config}) + + assert subject.get_nominal_tip_overlap("pipette-id", "some-uri") == 100 + assert subject.get_nominal_tip_overlap("pipette-id", "missing-uri") == 10 + + +@pytest.mark.parametrize( + ("mount", "expected_z_axis", "expected_plunger_axis"), + [ + (MountType.LEFT, MotorAxis.LEFT_Z, MotorAxis.LEFT_PLUNGER), + (MountType.RIGHT, MotorAxis.RIGHT_Z, MotorAxis.RIGHT_PLUNGER), + ], +) +def test_get_motor_axes( + mount: MountType, expected_z_axis: MotorAxis, expected_plunger_axis: MotorAxis +) -> None: + """It should get a pipette's motor axes.""" + subject = get_pipette_view( + pipettes_by_id={ + "pipette-id": LoadedPipette( + id="pipette-id", + mount=mount, + pipetteName=PipetteNameType.P300_SINGLE, + ), + }, + ) + + assert subject.get_z_axis("pipette-id") == expected_z_axis + assert subject.get_plunger_axis("pipette-id") == expected_plunger_axis + + +def test_nozzle_configuration_getters() -> None: + """Test that pipette view returns correct nozzle configuration data.""" + nozzle_map = NozzleMap.build( + physical_nozzles=OrderedDict({"A1": Point(0, 0, 0)}), + physical_rows=OrderedDict({"A": ["A1"]}), + physical_columns=OrderedDict({"1": ["A1"]}), + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A1", + valid_nozzle_maps=ValidNozzleMaps(maps={"A1": ["A1"]}), + ) + subject = get_pipette_view(nozzle_layout_by_id={"pipette-id": nozzle_map}) + assert subject.get_nozzle_layout_type("pipette-id") == NozzleConfigurationType.FULL + assert subject.get_is_partially_configured("pipette-id") is False + assert subject.get_primary_nozzle("pipette-id") == "A1" + + +class _PipetteSpecs(NamedTuple): + tip_length: float + bounding_box_offsets: PipetteBoundingBoxOffsets + nozzle_map: NozzleMap + critical_point: Optional[CriticalPoint] + destination_position: Point + pipette_bounds_result: Tuple[Point, Point, Point, Point] + + +_pipette_spec_cases = [ + _PipetteSpecs( + # 8-channel P300, full configuration + tip_length=42, + bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(0.0, 31.5, 35.52), + front_right_corner=Point(0.0, -31.5, 35.52), + front_left_corner=Point(0.0, -31.5, 35.52), + back_right_corner=Point(0.0, 31.5, 35.52), + ), + nozzle_map=NozzleMap.build( + physical_nozzles=EIGHT_CHANNEL_MAP, + physical_rows=EIGHT_CHANNEL_ROWS, + physical_columns=EIGHT_CHANNEL_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="H1", + valid_nozzle_maps=ValidNozzleMaps(maps={"Full": EIGHT_CHANNEL_COLS["1"]}), + ), + critical_point=None, + destination_position=Point(100, 200, 300), + pipette_bounds_result=( + ( + Point(x=100.0, y=200.0, z=342.0), + Point(x=100.0, y=137.0, z=342.0), + Point(x=100.0, y=200.0, z=342.0), + Point(x=100.0, y=137.0, z=342.0), + ) + ), + ), + _PipetteSpecs( + # 8-channel P300, single configuration + tip_length=42, + bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(0.0, 31.5, 35.52), + front_right_corner=Point(0.0, -31.5, 35.52), + front_left_corner=Point(0.0, -31.5, 35.52), + back_right_corner=Point(0.0, 31.5, 35.52), + ), + nozzle_map=NozzleMap.build( + physical_nozzles=EIGHT_CHANNEL_MAP, + physical_rows=EIGHT_CHANNEL_ROWS, + physical_columns=EIGHT_CHANNEL_COLS, + starting_nozzle="H1", + back_left_nozzle="H1", + front_right_nozzle="H1", + valid_nozzle_maps=ValidNozzleMaps(maps={"H1": ["H1"]}), + ), + critical_point=None, + destination_position=Point(100, 200, 300), + pipette_bounds_result=( + ( + Point(x=100.0, y=263.0, z=342.0), + Point(x=100.0, y=200.0, z=342.0), + Point(x=100.0, y=263.0, z=342.0), + Point(x=100.0, y=200.0, z=342.0), + ) + ), + ), + _PipetteSpecs( + # 8-channel P300, full configuration. Critical point of XY_CENTER + tip_length=42, + bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(0.0, 31.5, 35.52), + front_right_corner=Point(0.0, -31.5, 35.52), + front_left_corner=Point(0.0, -31.5, 35.52), + back_right_corner=Point(0.0, 31.5, 35.52), + ), + nozzle_map=NozzleMap.build( + physical_nozzles=EIGHT_CHANNEL_MAP, + physical_rows=EIGHT_CHANNEL_ROWS, + physical_columns=EIGHT_CHANNEL_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="H1", + valid_nozzle_maps=ValidNozzleMaps(maps={"Full": EIGHT_CHANNEL_COLS["1"]}), + ), + critical_point=CriticalPoint.XY_CENTER, + destination_position=Point(100, 200, 300), + pipette_bounds_result=( + ( + Point(x=100.0, y=231.5, z=342.0), + Point(x=100.0, y=168.5, z=342.0), + Point(x=100.0, y=231.5, z=342.0), + Point(x=100.0, y=168.5, z=342.0), + ) + ), + ), + _PipetteSpecs( + # 8-channel P300, Partial A1-E1 configuration. Critical point of XY_CENTER + tip_length=42, + bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(0.0, 31.5, 35.52), + front_right_corner=Point(0.0, -31.5, 35.52), + front_left_corner=Point(0.0, -31.5, 35.52), + back_right_corner=Point(0.0, 31.5, 35.52), + ), + nozzle_map=NozzleMap.build( + physical_nozzles=EIGHT_CHANNEL_MAP, + physical_rows=EIGHT_CHANNEL_ROWS, + physical_columns=EIGHT_CHANNEL_COLS, + starting_nozzle="H1", + back_left_nozzle="E1", + front_right_nozzle="H1", + valid_nozzle_maps=ValidNozzleMaps( + maps={ + "H1toE1": ["E1", "F1", "G1", "H1"], + } + ), + ), + critical_point=CriticalPoint.XY_CENTER, + destination_position=Point(100, 200, 300), + pipette_bounds_result=( + ( + Point(x=100.0, y=249.5, z=342.0), + Point(x=100.0, y=186.5, z=342.0), + Point(x=100.0, y=249.5, z=342.0), + Point(x=100.0, y=186.5, z=342.0), + ) + ), + ), + _PipetteSpecs( + # 96-channel P1000, full configuration + tip_length=42, + bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(-36.0, -25.5, -259.15), + front_right_corner=Point(63.0, -88.5, -259.15), + front_left_corner=Point(-36.0, -88.5, -259.15), + back_right_corner=Point(63.0, -25.5, -259.15), + ), + nozzle_map=NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="H12", + valid_nozzle_maps=ValidNozzleMaps( + maps={ + "Full": sum( + [ + NINETY_SIX_ROWS["A"], + NINETY_SIX_ROWS["B"], + NINETY_SIX_ROWS["C"], + NINETY_SIX_ROWS["D"], + NINETY_SIX_ROWS["E"], + NINETY_SIX_ROWS["F"], + NINETY_SIX_ROWS["G"], + NINETY_SIX_ROWS["H"], + ], + [], + ) + } + ), + ), + critical_point=None, + destination_position=Point(100, 200, 300), + pipette_bounds_result=( + ( + Point(x=100.0, y=200.0, z=342.0), + Point(x=199.0, y=137.0, z=342.0), + Point(x=199.0, y=200.0, z=342.0), + Point(x=100.0, y=137.0, z=342.0), + ) + ), + ), + _PipetteSpecs( + # 96-channel P1000, A1 COLUMN configuration + tip_length=42, + bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(-36.0, -25.5, -259.15), + front_right_corner=Point(63.0, -88.5, -259.15), + front_left_corner=Point(-36.0, -88.5, -259.15), + back_right_corner=Point(63.0, -25.5, -259.15), + ), + nozzle_map=NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="H1", + valid_nozzle_maps=ValidNozzleMaps(maps={"Column1": NINETY_SIX_COLS["1"]}), + ), + critical_point=None, + destination_position=Point(100, 200, 300), + pipette_bounds_result=( + Point(100, 200, 342), + Point(199, 137, 342), + Point(199, 200, 342), + Point(100, 137, 342), + ), + ), + _PipetteSpecs( + # 96-channel P1000, A12 COLUMN configuration + tip_length=42, + bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(-36.0, -25.5, -259.15), + front_right_corner=Point(63.0, -88.5, -259.15), + front_left_corner=Point(-36.0, -88.5, -259.15), + back_right_corner=Point(63.0, -25.5, -259.15), + ), + nozzle_map=NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A12", + back_left_nozzle="A12", + front_right_nozzle="H12", + valid_nozzle_maps=ValidNozzleMaps(maps={"Column12": NINETY_SIX_COLS["12"]}), + ), + critical_point=None, + destination_position=Point(100, 200, 300), + pipette_bounds_result=( + Point(1, 200, 342), + Point(100, 137, 342), + Point(100, 200, 342), + Point(1, 137, 342), + ), + ), + _PipetteSpecs( + # 96-channel P1000, ROW configuration + tip_length=42, + bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(-36.0, -25.5, -259.15), + front_right_corner=Point(63.0, -88.5, -259.15), + front_left_corner=Point(-36.0, -88.5, -259.15), + back_right_corner=Point(63.0, -25.5, -259.15), + ), + nozzle_map=NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A12", + valid_nozzle_maps=ValidNozzleMaps(maps={"RowA": NINETY_SIX_ROWS["A"]}), + ), + critical_point=None, + destination_position=Point(100, 200, 300), + pipette_bounds_result=( + Point(100, 200, 342), + Point(199, 137, 342), + Point(199, 200, 342), + Point(100, 137, 342), + ), + ), + _PipetteSpecs( + # 96-channel P1000, ROW configuration. Critical point of XY_CENTER + tip_length=42, + bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(-36.0, -25.5, -259.15), + front_right_corner=Point(63.0, -88.5, -259.15), + front_left_corner=Point(-36.0, -88.5, -259.15), + back_right_corner=Point(63.0, -25.5, -259.15), + ), + nozzle_map=NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A12", + valid_nozzle_maps=ValidNozzleMaps(maps={"RowA": NINETY_SIX_ROWS["A"]}), + ), + critical_point=CriticalPoint.XY_CENTER, + destination_position=Point(100, 200, 300), + pipette_bounds_result=( + Point(x=50.5, y=200, z=342), + Point(x=149.5, y=137, z=342), + Point(x=149.5, y=200, z=342), + Point(x=50.5, y=137, z=342), + ), + ), + _PipetteSpecs( + # 96-channel P1000, A12 COLUMN configuration. Critical point of Y_CENTER + tip_length=42, + bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(-36.0, -25.5, -259.15), + front_right_corner=Point(63.0, -88.5, -259.15), + front_left_corner=Point(-36.0, -88.5, -259.15), + back_right_corner=Point(63.0, -25.5, -259.15), + ), + nozzle_map=NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A12", + back_left_nozzle="A12", + front_right_nozzle="H12", + valid_nozzle_maps=ValidNozzleMaps(maps={"Column12": NINETY_SIX_COLS["12"]}), + ), + critical_point=CriticalPoint.Y_CENTER, + destination_position=Point(100, 200, 300), + pipette_bounds_result=( + Point(1, 231.5, 342), + Point(100, 168.5, 342), + Point(100, 231.5, 342), + Point(1, 168.5, 342), + ), + ), + _PipetteSpecs( + # 96-channel P1000, A1 COLUMN configuration. Critical point of XY_CENTER + tip_length=42, + bounding_box_offsets=PipetteBoundingBoxOffsets( + back_left_corner=Point(-36.0, -25.5, -259.15), + front_right_corner=Point(63.0, -88.5, -259.15), + front_left_corner=Point(-36.0, -88.5, -259.15), + back_right_corner=Point(63.0, -25.5, -259.15), + ), + nozzle_map=NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="H1", + valid_nozzle_maps=ValidNozzleMaps(maps={"Column1": NINETY_SIX_COLS["1"]}), + ), + critical_point=CriticalPoint.XY_CENTER, + destination_position=Point(100, 200, 300), + pipette_bounds_result=( + Point(100, 231.5, 342), + Point(199, 168.5, 342), + Point(199, 231.5, 342), + Point(100, 168.5, 342), + ), + ), +] + + +@pytest.mark.parametrize( + argnames=_PipetteSpecs._fields, + argvalues=_pipette_spec_cases, +) +def test_get_pipette_bounds_at_location( + tip_length: float, + bounding_box_offsets: PipetteBoundingBoxOffsets, + nozzle_map: NozzleMap, + destination_position: Point, + critical_point: Optional[CriticalPoint], + pipette_bounds_result: Tuple[Point, Point, Point, Point], + available_sensors: AvailableSensorDefinition, +) -> None: + """It should get the pipette's nozzle's bounds at the given location.""" + subject = get_pipette_view( + nozzle_layout_by_id={"pipette-id": nozzle_map}, + attached_tip_by_id={ + "pipette-id": TipGeometry(length=tip_length, diameter=123, volume=123), + }, + static_config_by_id={ + "pipette-id": StaticPipetteConfig( + min_volume=1, + max_volume=9001, + channels=5, + model="blah", + display_name="bleh", + serial_number="", + tip_configuration_lookup_table={}, + nominal_tip_overlap={}, + home_position=0, + nozzle_offset_z=0, + default_nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE), + bounding_nozzle_offsets=_SAMPLE_NOZZLE_BOUNDS_OFFSETS, + pipette_bounding_box_offsets=bounding_box_offsets, + lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, + ) + }, + ) + assert ( + subject.get_pipette_bounds_at_specified_move_to_position( + pipette_id="pipette-id", + destination_position=destination_position, + critical_point=critical_point, + ) + == pipette_bounds_result + ) + + +@pytest.mark.parametrize( + "nozzle_map,allowed", + [ + ( + NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="H12", + valid_nozzle_maps=ValidNozzleMaps( + maps={ + "Full": sum( + [ + NINETY_SIX_ROWS["A"], + NINETY_SIX_ROWS["B"], + NINETY_SIX_ROWS["C"], + NINETY_SIX_ROWS["D"], + NINETY_SIX_ROWS["E"], + NINETY_SIX_ROWS["F"], + NINETY_SIX_ROWS["G"], + NINETY_SIX_ROWS["H"], + ], + [], + ) + } + ), + ), + True, + ), + ( + NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="H1", + valid_nozzle_maps=ValidNozzleMaps( + maps={"Column1": NINETY_SIX_COLS["1"]} + ), + ), + True, + ), + ( + NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A12", + back_left_nozzle="A12", + front_right_nozzle="H12", + valid_nozzle_maps=ValidNozzleMaps( + maps={"Column12": NINETY_SIX_COLS["12"]} + ), + ), + True, + ), + ( + NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A1", + valid_nozzle_maps=ValidNozzleMaps(maps={"Single": ["A1"]}), + ), + True, + ), + ( + NozzleMap.build( + physical_nozzles=OrderedDict((("A1", Point(0.0, 1.0, 2.0)),)), + physical_rows=OrderedDict((("1", ["A1"]),)), + physical_columns=OrderedDict((("A", ["A1"]),)), + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A1", + valid_nozzle_maps=ValidNozzleMaps(maps={"Single": ["A1"]}), + ), + True, + ), + ( + NozzleMap.build( + physical_nozzles=EIGHT_CHANNEL_MAP, + physical_rows=EIGHT_CHANNEL_ROWS, + physical_columns=EIGHT_CHANNEL_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="H1", + valid_nozzle_maps=ValidNozzleMaps( + maps={"Full": EIGHT_CHANNEL_COLS["1"]} + ), + ), + True, + ), + ( + NozzleMap.build( + physical_nozzles=EIGHT_CHANNEL_MAP, + physical_rows=EIGHT_CHANNEL_ROWS, + physical_columns=EIGHT_CHANNEL_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A1", + valid_nozzle_maps=ValidNozzleMaps(maps={"Full": ["A1"]}), + ), + True, + ), + ( + NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A5", + back_left_nozzle="A5", + front_right_nozzle="H5", + valid_nozzle_maps=ValidNozzleMaps( + maps={"Column12": NINETY_SIX_COLS["5"]} + ), + ), + False, + ), + ], +) +def test_lld_config_validation(nozzle_map: NozzleMap, allowed: bool) -> None: + """It should validate partial tip configurations for LLD.""" + pipette_id = "pipette-id" + subject = get_pipette_view( + nozzle_layout_by_id={pipette_id: nozzle_map}, + ) + assert subject.get_nozzle_configuration_supports_lld(pipette_id) == allowed diff --git a/api/tests/opentrons/protocol_engine/state/test_state_store.py b/api/tests/opentrons/protocol_engine/state/test_state_store.py index 6cd24564795..9cebd0a80d2 100644 --- a/api/tests/opentrons/protocol_engine/state/test_state_store.py +++ b/api/tests/opentrons/protocol_engine/state/test_state_store.py @@ -9,7 +9,8 @@ from opentrons.util.change_notifier import ChangeNotifier from opentrons.protocol_engine.actions import PlayAction -from opentrons.protocol_engine.state import State, StateStore, Config +from opentrons.protocol_engine.state.config import Config +from opentrons.protocol_engine.state.state import State, StateStore from opentrons.protocol_engine.types import DeckType @@ -48,6 +49,12 @@ def placeholder_error_recovery_policy(*args: object, **kwargs: object) -> Any: "robotType": "OT-2 Standard", "models": ["OT-2 Standard", "OT-2 Refresh"], "extents": [446.75, 347.5, 0.0], + "paddingOffsets": { + "rear": -35.91, + "front": 31.89, + "leftSide": 0, + "rightSide": 0, + }, "mountOffsets": {"left": [-34.0, 0.0, 0.0], "right": [0.0, 0.0, 0.0]}, }, deck_fixed_labware=[], diff --git a/api/tests/opentrons/protocol_engine/state/test_tip_state.py b/api/tests/opentrons/protocol_engine/state/test_tip_state.py index da570c940cd..7a958a37e5f 100644 --- a/api/tests/opentrons/protocol_engine/state/test_tip_state.py +++ b/api/tests/opentrons/protocol_engine/state/test_tip_state.py @@ -14,13 +14,17 @@ from opentrons.hardware_control.nozzle_manager import NozzleMap from opentrons.protocol_engine import actions, commands +from opentrons.protocol_engine.state import update_types from opentrons.protocol_engine.state.tips import TipStore, TipView -from opentrons.protocol_engine.types import FlowRates, DeckPoint +from opentrons.protocol_engine.types import DeckSlotLocation, FlowRates from opentrons.protocol_engine.resources.pipette_data_provider import ( LoadedStaticPipetteData, ) -from opentrons.types import Point +from opentrons.types import DeckSlotName, Point from opentrons_shared_data.pipette.types import PipetteNameType +from opentrons_shared_data.pipette.pipette_definition import ( + AvailableSensorDefinition, +) from ..pipette_fixtures import ( NINETY_SIX_MAP, NINETY_SIX_COLS, @@ -31,6 +35,12 @@ _tip_rack_parameters = LabwareParameters.construct(isTiprack=True) # type: ignore[call-arg] +@pytest.fixture +def available_sensors() -> AvailableSensorDefinition: + """Provide a list of sensors.""" + return AvailableSensorDefinition(sensors=["pressure", "capacitive", "environment"]) + + @pytest.fixture def subject() -> TipStore: """Get a TipStore test subject.""" @@ -60,64 +70,27 @@ def labware_definition() -> LabwareDefinition: @pytest.fixture -def load_labware_command(labware_definition: LabwareDefinition) -> commands.LoadLabware: +def load_labware_action( + labware_definition: LabwareDefinition, +) -> actions.SucceedCommandAction: """Get a load labware command value object.""" - return commands.LoadLabware.construct( # type: ignore[call-arg] - result=commands.LoadLabwareResult.construct( - labwareId="cool-labware", - definition=labware_definition, - ) - ) - - -@pytest.fixture -def pick_up_tip_command() -> commands.PickUpTip: - """Get a pick-up tip command value object.""" - return commands.PickUpTip.construct( # type: ignore[call-arg] - params=commands.PickUpTipParams.construct( - pipetteId="pipette-id", - labwareId="cool-labware", - wellName="A1", - ), - result=commands.PickUpTipResult.construct( - position=DeckPoint(x=0, y=0, z=0), tipLength=1.23 - ), - ) - - -@pytest.fixture -def drop_tip_command() -> commands.DropTip: - """Get a drop tip command value object.""" - return commands.DropTip.construct( # type: ignore[call-arg] - params=commands.DropTipParams.construct( - pipetteId="pipette-id", - labwareId="cool-labware", - wellName="A1", - ), - result=commands.DropTipResult.construct(position=DeckPoint(x=0, y=0, z=0)), - ) - - -@pytest.fixture -def drop_tip_in_place_command() -> commands.DropTipInPlace: - """Get a drop tip in place command object.""" - return commands.DropTipInPlace.construct( # type: ignore[call-arg] - params=commands.DropTipInPlaceParams.construct( - pipetteId="pipette-id", + return actions.SucceedCommandAction( + command=_dummy_command(), + state_update=update_types.StateUpdate( + loaded_labware=update_types.LoadedLabwareUpdate( + labware_id="cool-labware", + definition=labware_definition, + new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_A1), + display_name=None, + offset_id=None, + ) ), - result=commands.DropTipInPlaceResult.construct(), ) -@pytest.fixture -def unsafe_drop_tip_in_place_command() -> commands.unsafe.UnsafeDropTipInPlace: - """Get an unsafe drop-tip-in-place command.""" - return commands.unsafe.UnsafeDropTipInPlace.construct( # type: ignore[call-arg] - params=commands.unsafe.UnsafeDropTipInPlaceParams.construct( - pipetteId="pipette-id" - ), - result=commands.unsafe.UnsafeDropTipInPlaceResult.construct(), - ) +def _dummy_command() -> commands.Command: + """Return a placeholder command.""" + return commands.Comment.construct() # type: ignore[call-arg] @pytest.mark.parametrize( @@ -127,18 +100,14 @@ def unsafe_drop_tip_in_place_command() -> commands.unsafe.UnsafeDropTipInPlace: ], ) def test_get_next_tip_returns_none( - load_labware_command: commands.LoadLabware, + load_labware_action: actions.SucceedCommandAction, subject: TipStore, supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, ) -> None: """It should start at the first tip in the labware.""" - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) - load_pipette_private_result = commands.LoadPipettePrivateResult( + subject.handle_action(load_labware_action) + config_update = update_types.PipetteConfigUpdate( pipette_id="pipette-id", serial_number="pipette-serial", config=LoadedStaticPipetteData( @@ -160,11 +129,20 @@ def test_get_next_tip_returns_none( back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command + state_update=update_types.StateUpdate(pipette_config=config_update), + command=_dummy_command(), ) ) @@ -180,18 +158,15 @@ def test_get_next_tip_returns_none( @pytest.mark.parametrize("input_tip_amount", [1, 8, 96]) def test_get_next_tip_returns_first_tip( - load_labware_command: commands.LoadLabware, + load_labware_action: actions.SucceedCommandAction, subject: TipStore, input_tip_amount: int, supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, ) -> None: """It should start at the first tip in the labware.""" - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) + subject.handle_action(load_labware_action) + pipette_name_type = PipetteNameType.P1000_96 if input_tip_amount == 1: pipette_name_type = PipetteNameType.P300_SINGLE_GEN2 @@ -199,7 +174,7 @@ def test_get_next_tip_returns_first_tip( pipette_name_type = PipetteNameType.P300_MULTI_GEN2 else: pipette_name_type = PipetteNameType.P1000_96 - load_pipette_private_result = commands.LoadPipettePrivateResult( + config_update = update_types.PipetteConfigUpdate( pipette_id="pipette-id", serial_number="pipette-serial", config=LoadedStaticPipetteData( @@ -221,11 +196,20 @@ def test_get_next_tip_returns_first_tip( back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command + state_update=update_types.StateUpdate(pipette_config=config_update), + command=_dummy_command(), ) ) @@ -241,20 +225,17 @@ def test_get_next_tip_returns_first_tip( @pytest.mark.parametrize("input_tip_amount, result_well_name", [(1, "B1"), (8, "A2")]) def test_get_next_tip_used_starting_tip( - load_labware_command: commands.LoadLabware, + load_labware_action: actions.SucceedCommandAction, subject: TipStore, input_tip_amount: int, result_well_name: str, supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, ) -> None: """It should start searching at the given starting tip.""" - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) - load_pipette_private_result = commands.LoadPipettePrivateResult( + subject.handle_action(load_labware_action) + + config_update = update_types.PipetteConfigUpdate( pipette_id="pipette-id", serial_number="pipette-serial", config=LoadedStaticPipetteData( @@ -276,11 +257,20 @@ def test_get_next_tip_used_starting_tip( back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command + state_update=update_types.StateUpdate(pipette_config=config_update), + command=_dummy_command(), ) ) @@ -309,22 +299,18 @@ def test_get_next_tip_used_starting_tip( ], ) def test_get_next_tip_skips_picked_up_tip( - load_labware_command: commands.LoadLabware, - pick_up_tip_command: commands.PickUpTip, + load_labware_action: actions.SucceedCommandAction, subject: TipStore, input_tip_amount: int, get_next_tip_tips: int, input_starting_tip: Optional[str], result_well_name: Optional[str], supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, ) -> None: """It should get the next tip in the column if one has been picked up.""" - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) + subject.handle_action(load_labware_action) + channels_num = input_tip_amount if input_starting_tip is not None: pipette_name_type = PipetteNameType.P1000_96 @@ -343,7 +329,7 @@ def test_get_next_tip_skips_picked_up_tip( pipette_name_type = PipetteNameType.P300_MULTI_GEN2 else: pipette_name_type = PipetteNameType.P1000_96 - load_pipette_private_result = commands.LoadPipettePrivateResult( + config_update = update_types.PipetteConfigUpdate( pipette_id="pipette-id", serial_number="pipette-serial", config=LoadedStaticPipetteData( @@ -365,22 +351,42 @@ def test_get_next_tip_skips_picked_up_tip( back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command + state_update=update_types.StateUpdate(pipette_config=config_update), + command=_dummy_command(), + ) + ) + + pick_up_tip_state_update = update_types.StateUpdate( + tips_used=update_types.TipsUsedUpdate( + pipette_id="pipette-id", + labware_id="cool-labware", + well_name="A1", ) ) subject.handle_action( - actions.SucceedCommandAction(command=pick_up_tip_command, private_result=None) + actions.SucceedCommandAction( + command=_dummy_command(), + state_update=pick_up_tip_state_update, + ) ) result = TipView(subject.state).get_next_tip( labware_id="cool-labware", num_tips=get_next_tip_tips, starting_tip_name=input_starting_tip, - nozzle_map=load_pipette_private_result.config.nozzle_map, + nozzle_map=config_update.config.nozzle_map, ) assert result == result_well_name @@ -388,17 +394,14 @@ def test_get_next_tip_skips_picked_up_tip( def test_get_next_tip_with_starting_tip( subject: TipStore, - load_labware_command: commands.LoadLabware, + load_labware_action: actions.SucceedCommandAction, supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, ) -> None: """It should return the starting tip, and then the following tip after that.""" - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) - load_pipette_private_result = commands.LoadPipettePrivateResult( + subject.handle_action(load_labware_action) + + config_update = update_types.PipetteConfigUpdate( pipette_id="pipette-id", serial_number="pipette-serial", config=LoadedStaticPipetteData( @@ -420,42 +423,46 @@ def test_get_next_tip_with_starting_tip( back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command + state_update=update_types.StateUpdate(pipette_config=config_update), + command=_dummy_command(), ) ) result = TipView(subject.state).get_next_tip( labware_id="cool-labware", num_tips=1, starting_tip_name="B2", - nozzle_map=load_pipette_private_result.config.nozzle_map, + nozzle_map=config_update.config.nozzle_map, ) assert result == "B2" - pick_up_tip = commands.PickUpTip.construct( # type: ignore[call-arg] - params=commands.PickUpTipParams.construct( - pipetteId="pipette-id", - labwareId="cool-labware", - wellName="B2", - ), - result=commands.PickUpTipResult.construct( - position=DeckPoint(x=0, y=0, z=0), tipLength=1.23 - ), + pick_up_tip_state_update = update_types.StateUpdate( + tips_used=update_types.TipsUsedUpdate("pipette-id", "cool-labware", "B2") ) - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=pick_up_tip) + actions.SucceedCommandAction( + command=_dummy_command(), + state_update=pick_up_tip_state_update, + ) ) result = TipView(subject.state).get_next_tip( labware_id="cool-labware", num_tips=1, starting_tip_name="B2", - nozzle_map=load_pipette_private_result.config.nozzle_map, + nozzle_map=config_update.config.nozzle_map, ) assert result == "C2" @@ -463,17 +470,14 @@ def test_get_next_tip_with_starting_tip( def test_get_next_tip_with_starting_tip_8_channel( subject: TipStore, - load_labware_command: commands.LoadLabware, + load_labware_action: actions.SucceedCommandAction, supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, ) -> None: """It should return the starting tip, and then the following tip after that.""" - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) - load_pipette_private_result = commands.LoadPipettePrivateResult( + subject.handle_action(load_labware_action) + + config_update = update_types.PipetteConfigUpdate( pipette_id="pipette-id", serial_number="pipette-serial", config=LoadedStaticPipetteData( @@ -495,11 +499,20 @@ def test_get_next_tip_with_starting_tip_8_channel( back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command + state_update=update_types.StateUpdate(pipette_config=config_update), + command=_dummy_command(), ) ) @@ -512,19 +525,16 @@ def test_get_next_tip_with_starting_tip_8_channel( assert result == "A2" - pick_up_tip = commands.PickUpTip.construct( # type: ignore[call-arg] - params=commands.PickUpTipParams.construct( - pipetteId="pipette-id", - labwareId="cool-labware", - wellName="A2", - ), - result=commands.PickUpTipResult.construct( - position=DeckPoint(x=0, y=0, z=0), tipLength=1.23 - ), + pick_up_tip_state_update = update_types.StateUpdate( + tips_used=update_types.TipsUsedUpdate( + pipette_id="pipette-id", labware_id="cool-labware", well_name="A2" + ) ) - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=pick_up_tip) + actions.SucceedCommandAction( + command=_dummy_command(), + state_update=pick_up_tip_state_update, + ) ) result = TipView(subject.state).get_next_tip( @@ -539,17 +549,14 @@ def test_get_next_tip_with_starting_tip_8_channel( def test_get_next_tip_with_1_channel_followed_by_8_channel( subject: TipStore, - load_labware_command: commands.LoadLabware, + load_labware_action: actions.SucceedCommandAction, supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, ) -> None: """It should return the first tip of column 2 for the 8 channel after performing a single tip pickup on column 1.""" - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) - load_pipette_private_result = commands.LoadPipettePrivateResult( + subject.handle_action(load_labware_action) + + config_update = update_types.PipetteConfigUpdate( pipette_id="pipette-id", serial_number="pipette-serial", config=LoadedStaticPipetteData( @@ -571,17 +578,24 @@ def test_get_next_tip_with_1_channel_followed_by_8_channel( back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command + state_update=update_types.StateUpdate(pipette_config=config_update), + command=_dummy_command(), ) ) - load_pipette_command2 = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id2") - ) - load_pipette_private_result2 = commands.LoadPipettePrivateResult( + + config_update_2 = update_types.PipetteConfigUpdate( pipette_id="pipette-id2", serial_number="pipette-serial2", config=LoadedStaticPipetteData( @@ -603,11 +617,20 @@ def test_get_next_tip_with_1_channel_followed_by_8_channel( back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result2, command=load_pipette_command2 + state_update=update_types.StateUpdate(pipette_config=config_update_2), + command=_dummy_command(), ) ) @@ -620,19 +643,16 @@ def test_get_next_tip_with_1_channel_followed_by_8_channel( assert result == "A1" - pick_up_tip2 = commands.PickUpTip.construct( # type: ignore[call-arg] - params=commands.PickUpTipParams.construct( - pipetteId="pipette-id2", - labwareId="cool-labware", - wellName="A1", - ), - result=commands.PickUpTipResult.construct( - position=DeckPoint(x=0, y=0, z=0), tipLength=1.23 - ), + pick_up_tip_2_state_update = update_types.StateUpdate( + tips_used=update_types.TipsUsedUpdate( + pipette_id="pipette-id2", labware_id="cool-labware", well_name="A1" + ) ) - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=pick_up_tip2) + actions.SucceedCommandAction( + command=_dummy_command(), + state_update=pick_up_tip_2_state_update, + ) ) result = TipView(subject.state).get_next_tip( @@ -647,17 +667,14 @@ def test_get_next_tip_with_1_channel_followed_by_8_channel( def test_get_next_tip_with_starting_tip_out_of_tips( subject: TipStore, - load_labware_command: commands.LoadLabware, + load_labware_action: actions.SucceedCommandAction, supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, ) -> None: """It should return the starting tip of H12 and then None after that.""" - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) - load_pipette_private_result = commands.LoadPipettePrivateResult( + subject.handle_action(load_labware_action) + + config_update = update_types.PipetteConfigUpdate( pipette_id="pipette-id", serial_number="pipette-serial", config=LoadedStaticPipetteData( @@ -679,11 +696,20 @@ def test_get_next_tip_with_starting_tip_out_of_tips( back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command + state_update=update_types.StateUpdate(pipette_config=config_update), + command=_dummy_command(), ) ) @@ -696,19 +722,16 @@ def test_get_next_tip_with_starting_tip_out_of_tips( assert result == "H12" - pick_up_tip = commands.PickUpTip.construct( # type: ignore[call-arg] - params=commands.PickUpTipParams.construct( - pipetteId="pipette-id", - labwareId="cool-labware", - wellName="H12", - ), - result=commands.PickUpTipResult.construct( - position=DeckPoint(x=0, y=0, z=0), tipLength=1.23 - ), + pick_up_tip_state_update = update_types.StateUpdate( + tips_used=update_types.TipsUsedUpdate( + pipette_id="pipette-id", labware_id="cool-labware", well_name="H12" + ) ) - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=pick_up_tip) + actions.SucceedCommandAction( + command=_dummy_command(), + state_update=pick_up_tip_state_update, + ) ) result = TipView(subject.state).get_next_tip( @@ -723,17 +746,14 @@ def test_get_next_tip_with_starting_tip_out_of_tips( def test_get_next_tip_with_column_and_starting_tip( subject: TipStore, - load_labware_command: commands.LoadLabware, + load_labware_action: actions.SucceedCommandAction, supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, ) -> None: """It should return the first tip in a column, taking starting tip into account.""" - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) - load_pipette_private_result = commands.LoadPipettePrivateResult( + subject.handle_action(load_labware_action) + + config_update = update_types.PipetteConfigUpdate( pipette_id="pipette-id", serial_number="pipette-serial", config=LoadedStaticPipetteData( @@ -755,11 +775,20 @@ def test_get_next_tip_with_column_and_starting_tip( back_left_corner_offset=Point(0, 0, 0), front_right_corner_offset=Point(0, 0, 0), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command + state_update=update_types.StateUpdate(pipette_config=config_update), + command=_dummy_command(), ) ) @@ -775,18 +804,14 @@ def test_get_next_tip_with_column_and_starting_tip( def test_reset_tips( subject: TipStore, - load_labware_command: commands.LoadLabware, - pick_up_tip_command: commands.PickUpTip, + load_labware_action: actions.SucceedCommandAction, supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, ) -> None: """It should be able to reset tip tracking state.""" - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) - load_pipette_private_result = commands.LoadPipettePrivateResult( + subject.handle_action(load_labware_action) + + config_update = update_types.PipetteConfigUpdate( pipette_id="pipette-id", serial_number="pipette-serial", config=LoadedStaticPipetteData( @@ -808,38 +833,57 @@ def test_reset_tips( back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command + state_update=update_types.StateUpdate(pipette_config=config_update), + command=_dummy_command(), ) ) subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=pick_up_tip_command) + actions.SucceedCommandAction( + command=_dummy_command(), + state_update=update_types.StateUpdate( + tips_used=update_types.TipsUsedUpdate( + pipette_id="pipette-id", + labware_id="cool-labware", + well_name="A1", + ) + ), + ) ) - subject.handle_action(actions.ResetTipsAction(labware_id="cool-labware")) - result = TipView(subject.state).get_next_tip( - labware_id="cool-labware", - num_tips=1, - starting_tip_name=None, - nozzle_map=None, - ) + def get_result() -> str | None: + return TipView(subject.state).get_next_tip( + labware_id="cool-labware", + num_tips=1, + starting_tip_name=None, + nozzle_map=None, + ) - assert result == "A1" + assert get_result() != "A1" + subject.handle_action(actions.ResetTipsAction(labware_id="cool-labware")) + assert get_result() == "A1" def test_handle_pipette_config_action( - subject: TipStore, supported_tip_fixture: pipette_definition.SupportedTipsDefinition + subject: TipStore, + supported_tip_fixture: pipette_definition.SupportedTipsDefinition, + available_sensors: AvailableSensorDefinition, ) -> None: """Should add pipette channel to state.""" - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) - load_pipette_private_result = commands.LoadPipettePrivateResult( + config_update = update_types.PipetteConfigUpdate( pipette_id="pipette-id", serial_number="pipette-serial", config=LoadedStaticPipetteData( @@ -861,11 +905,20 @@ def test_handle_pipette_config_action( back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command + state_update=update_types.StateUpdate(pipette_config=config_update), + command=_dummy_command(), ) ) @@ -883,12 +936,10 @@ def test_handle_pipette_config_action( ], ) def test_has_tip_not_tip_rack( - load_labware_command: commands.LoadLabware, subject: TipStore + load_labware_action: actions.SucceedCommandAction, subject: TipStore ) -> None: """It should return False if labware isn't a tip rack.""" - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) + subject.handle_action(load_labware_action) result = TipView(state=subject.state).has_clean_tip("cool-labware", "A1") @@ -896,105 +947,16 @@ def test_has_tip_not_tip_rack( def test_has_tip_tip_rack( - load_labware_command: commands.LoadLabware, subject: TipStore + load_labware_action: actions.SucceedCommandAction, subject: TipStore ) -> None: """It should return False if labware isn't a tip rack.""" - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) + subject.handle_action(load_labware_action) result = TipView(state=subject.state).has_clean_tip("cool-labware", "A1") assert result is True -def test_drop_tip( - subject: TipStore, - load_labware_command: commands.LoadLabware, - pick_up_tip_command: commands.PickUpTip, - drop_tip_command: commands.DropTip, - drop_tip_in_place_command: commands.DropTipInPlace, - unsafe_drop_tip_in_place_command: commands.unsafe.UnsafeDropTipInPlace, - supported_tip_fixture: pipette_definition.SupportedTipsDefinition, -) -> None: - """It should be clear tip length when a tip is dropped.""" - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) - load_pipette_private_result = commands.LoadPipettePrivateResult( - pipette_id="pipette-id", - serial_number="pipette-serial", - config=LoadedStaticPipetteData( - channels=8, - max_volume=15, - min_volume=3, - model="gen a", - display_name="display name", - flow_rates=FlowRates( - default_aspirate={}, - default_dispense={}, - default_blow_out={}, - ), - tip_configuration_lookup_table={15: supported_tip_fixture}, - nominal_tip_overlap={}, - nozzle_offset_z=1.23, - home_position=4.56, - nozzle_map=get_default_nozzle_map(PipetteNameType.P300_SINGLE_GEN2), - back_left_corner_offset=Point(x=1, y=2, z=3), - front_right_corner_offset=Point(x=4, y=5, z=6), - pipette_lld_settings={}, - ), - ) - subject.handle_action( - actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command - ) - ) - - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=pick_up_tip_command) - ) - result = TipView(subject.state).get_tip_length("pipette-id") - assert result == 1.23 - - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=drop_tip_command) - ) - result = TipView(subject.state).get_tip_length("pipette-id") - assert result == 0 - - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=pick_up_tip_command) - ) - result = TipView(subject.state).get_tip_length("pipette-id") - assert result == 1.23 - - subject.handle_action( - actions.SucceedCommandAction( - private_result=None, command=drop_tip_in_place_command - ) - ) - result = TipView(subject.state).get_tip_length("pipette-id") - assert result == 0 - - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=pick_up_tip_command) - ) - result = TipView(subject.state).get_tip_length("pipette-id") - assert result == 1.23 - - subject.handle_action( - actions.SucceedCommandAction( - private_result=None, command=unsafe_drop_tip_in_place_command - ) - ) - result = TipView(subject.state).get_tip_length("pipette-id") - assert result == 0 - - @pytest.mark.parametrize( argnames=["nozzle_map", "expected_channels"], argvalues=[ @@ -1059,13 +1021,11 @@ def test_active_channels( supported_tip_fixture: pipette_definition.SupportedTipsDefinition, nozzle_map: NozzleMap, expected_channels: int, + available_sensors: AvailableSensorDefinition, ) -> None: """Should update active channels after pipette configuration change.""" # Load pipette to update state - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) - load_pipette_private_result = commands.LoadPipettePrivateResult( + config_update = update_types.PipetteConfigUpdate( pipette_id="pipette-id", serial_number="pipette-serial", config=LoadedStaticPipetteData( @@ -1087,26 +1047,34 @@ def test_active_channels( back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command + state_update=update_types.StateUpdate(pipette_config=config_update), + command=_dummy_command(), ) ) # Configure nozzle for partial configuration - configure_nozzle_layout_cmd = commands.ConfigureNozzleLayout.construct( # type: ignore[call-arg] - result=commands.ConfigureNozzleLayoutResult() - ) - configure_nozzle_private_result = commands.ConfigureNozzleLayoutPrivateResult( - pipette_id="pipette-id", - nozzle_map=nozzle_map, + state_update = update_types.StateUpdate( + pipette_nozzle_map=update_types.PipetteNozzleMapUpdate( + pipette_id="pipette-id", + nozzle_map=nozzle_map, + ) ) subject.handle_action( actions.SucceedCommandAction( - private_result=configure_nozzle_private_result, - command=configure_nozzle_layout_cmd, + command=_dummy_command(), + state_update=state_update, ) ) assert ( @@ -1118,20 +1086,15 @@ def test_active_channels( def test_next_tip_uses_active_channels( subject: TipStore, supported_tip_fixture: pipette_definition.SupportedTipsDefinition, - load_labware_command: commands.LoadLabware, - pick_up_tip_command: commands.PickUpTip, + load_labware_action: actions.SucceedCommandAction, + available_sensors: AvailableSensorDefinition, ) -> None: """Test that tip tracking logic uses pipette's active channels.""" # Load labware - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) + subject.handle_action(load_labware_action) # Load pipette - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) - load_pipette_private_result = commands.LoadPipettePrivateResult( + config_update = update_types.PipetteConfigUpdate( pipette_id="pipette-id", serial_number="pipette-serial", config=LoadedStaticPipetteData( @@ -1153,43 +1116,69 @@ def test_next_tip_uses_active_channels( back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command + state_update=update_types.StateUpdate(pipette_config=config_update), + command=_dummy_command(), ) ) # Configure nozzle for partial configuration - configure_nozzle_layout_cmd = commands.ConfigureNozzleLayout.construct( # type: ignore[call-arg] - result=commands.ConfigureNozzleLayoutResult() - ) - configure_nozzle_private_result = commands.ConfigureNozzleLayoutPrivateResult( - pipette_id="pipette-id", - nozzle_map=NozzleMap.build( - physical_nozzles=NINETY_SIX_MAP, - physical_rows=NINETY_SIX_ROWS, - physical_columns=NINETY_SIX_COLS, - starting_nozzle="A12", - back_left_nozzle="A12", - front_right_nozzle="H12", - valid_nozzle_maps=ValidNozzleMaps( - maps={ - "A12_H12": ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] - } + state_update = update_types.StateUpdate( + pipette_nozzle_map=update_types.PipetteNozzleMapUpdate( + pipette_id="pipette-id", + nozzle_map=NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle="A12", + back_left_nozzle="A12", + front_right_nozzle="H12", + valid_nozzle_maps=ValidNozzleMaps( + maps={ + "A12_H12": [ + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12", + ] + } + ), ), - ), + ) ) subject.handle_action( actions.SucceedCommandAction( - private_result=configure_nozzle_private_result, - command=configure_nozzle_layout_cmd, + command=_dummy_command(), + state_update=state_update, ) ) # Pick up partial tips subject.handle_action( - actions.SucceedCommandAction(command=pick_up_tip_command, private_result=None) + actions.SucceedCommandAction( + command=_dummy_command(), + state_update=update_types.StateUpdate( + tips_used=update_types.TipsUsedUpdate( + pipette_id="pipette-id", + labware_id="cool-labware", + well_name="A1", + ) + ), + ) ) result = TipView(subject.state).get_next_tip( @@ -1204,20 +1193,15 @@ def test_next_tip_uses_active_channels( def test_next_tip_automatic_tip_tracking_with_partial_configurations( subject: TipStore, supported_tip_fixture: pipette_definition.SupportedTipsDefinition, - load_labware_command: commands.LoadLabware, - pick_up_tip_command: commands.PickUpTip, + load_labware_action: actions.SucceedCommandAction, + available_sensors: AvailableSensorDefinition, ) -> None: """Test tip tracking logic using multiple pipette configurations.""" # Load labware - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) + subject.handle_action(load_labware_action) # Load pipette - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) - load_pipette_private_result = commands.LoadPipettePrivateResult( + config_update = update_types.PipetteConfigUpdate( pipette_id="pipette-id", serial_number="pipette-serial", config=LoadedStaticPipetteData( @@ -1239,11 +1223,20 @@ def test_next_tip_automatic_tip_tracking_with_partial_configurations( back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command + state_update=update_types.StateUpdate(pipette_config=config_update), + command=_dummy_command(), ) ) @@ -1254,95 +1247,93 @@ def _assert_and_pickup(well: str, nozzle_map: NozzleMap) -> None: starting_tip_name=None, nozzle_map=nozzle_map, ) - assert result == well + assert result is not None and result == well - pick_up_tip = commands.PickUpTip.construct( # type: ignore[call-arg] - params=commands.PickUpTipParams.construct( - pipetteId="pipette-id", - labwareId="cool-labware", - wellName=result, - ), - result=commands.PickUpTipResult.construct( - position=DeckPoint(x=0, y=0, z=0), tipLength=1.23 - ), + pick_up_tip_state_update = update_types.StateUpdate( + tips_used=update_types.TipsUsedUpdate( + pipette_id="pipette-id", + labware_id="cool-labware", + well_name=result, + ) ) subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=pick_up_tip) + actions.SucceedCommandAction( + command=_dummy_command(), + state_update=pick_up_tip_state_update, + ) ) - # Configure nozzle for partial configurations - configure_nozzle_layout_cmd = commands.ConfigureNozzleLayout.construct( # type: ignore[call-arg] - result=commands.ConfigureNozzleLayoutResult() - ) - def _reconfigure_nozzle_layout(start: str, back_l: str, front_r: str) -> NozzleMap: - configure_nozzle_private_result = commands.ConfigureNozzleLayoutPrivateResult( - pipette_id="pipette-id", - nozzle_map=NozzleMap.build( - physical_nozzles=NINETY_SIX_MAP, - physical_rows=NINETY_SIX_ROWS, - physical_columns=NINETY_SIX_COLS, - starting_nozzle=start, - back_left_nozzle=back_l, - front_right_nozzle=front_r, - valid_nozzle_maps=ValidNozzleMaps( - maps={ - "A1": ["A1"], - "H1": ["H1"], - "A12": ["A12"], - "H12": ["H12"], - "A1_H3": [ - "A1", - "A2", - "A3", - "B1", - "B2", - "B3", - "C1", - "C2", - "C3", - "D1", - "D2", - "D3", - "E1", - "E2", - "E3", - "F1", - "F2", - "F3", - "G1", - "G2", - "G3", - "H1", - "H2", - "H3", - ], - "A1_F2": [ - "A1", - "A2", - "B1", - "B2", - "C1", - "C2", - "D1", - "D2", - "E1", - "E2", - "F1", - "F2", - ], - } - ), + nozzle_map = NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle=start, + back_left_nozzle=back_l, + front_right_nozzle=front_r, + valid_nozzle_maps=ValidNozzleMaps( + maps={ + "A1": ["A1"], + "H1": ["H1"], + "A12": ["A12"], + "H12": ["H12"], + "A1_H3": [ + "A1", + "A2", + "A3", + "B1", + "B2", + "B3", + "C1", + "C2", + "C3", + "D1", + "D2", + "D3", + "E1", + "E2", + "E3", + "F1", + "F2", + "F3", + "G1", + "G2", + "G3", + "H1", + "H2", + "H3", + ], + "A1_F2": [ + "A1", + "A2", + "B1", + "B2", + "C1", + "C2", + "D1", + "D2", + "E1", + "E2", + "F1", + "F2", + ], + } ), ) + state_update = update_types.StateUpdate( + pipette_nozzle_map=update_types.PipetteNozzleMapUpdate( + pipette_id="pipette-id", + nozzle_map=nozzle_map, + ) + ) subject.handle_action( actions.SucceedCommandAction( - private_result=configure_nozzle_private_result, - command=configure_nozzle_layout_cmd, + command=_dummy_command(), + state_update=state_update, ) ) - return configure_nozzle_private_result.nozzle_map + return nozzle_map map = _reconfigure_nozzle_layout("A1", "A1", "H3") _assert_and_pickup("A10", map) @@ -1363,20 +1354,15 @@ def _reconfigure_nozzle_layout(start: str, back_l: str, front_r: str) -> NozzleM def test_next_tip_automatic_tip_tracking_tiprack_limits( subject: TipStore, supported_tip_fixture: pipette_definition.SupportedTipsDefinition, - load_labware_command: commands.LoadLabware, - pick_up_tip_command: commands.PickUpTip, + load_labware_action: actions.SucceedCommandAction, + available_sensors: AvailableSensorDefinition, ) -> None: """Test tip tracking logic to ensure once a tiprack is consumed it returns None when consuming tips using multiple pipette configurations.""" # Load labware - subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=load_labware_command) - ) + subject.handle_action(load_labware_action) # Load pipette - load_pipette_command = commands.LoadPipette.construct( # type: ignore[call-arg] - result=commands.LoadPipetteResult(pipetteId="pipette-id") - ) - load_pipette_private_result = commands.LoadPipettePrivateResult( + config_update = update_types.PipetteConfigUpdate( pipette_id="pipette-id", serial_number="pipette-serial", config=LoadedStaticPipetteData( @@ -1398,11 +1384,20 @@ def test_next_tip_automatic_tip_tracking_tiprack_limits( back_left_corner_offset=Point(x=1, y=2, z=3), front_right_corner_offset=Point(x=4, y=5, z=6), pipette_lld_settings={}, + plunger_positions={ + "top": 0.0, + "bottom": 5.0, + "blow_out": 19.0, + "drop_tip": 20.0, + }, + shaft_ul_per_mm=5.0, + available_sensors=available_sensors, ), ) subject.handle_action( actions.SucceedCommandAction( - private_result=load_pipette_private_result, command=load_pipette_command + state_update=update_types.StateUpdate(pipette_config=config_update), + command=_dummy_command(), ) ) @@ -1414,88 +1409,82 @@ def _get_next_and_pickup(nozzle_map: NozzleMap) -> str | None: nozzle_map=nozzle_map, ) if result is not None: - pick_up_tip = commands.PickUpTip.construct( # type: ignore[call-arg] - params=commands.PickUpTipParams.construct( - pipetteId="pipette-id", - labwareId="cool-labware", - wellName=result, - ), - result=commands.PickUpTipResult.construct( - position=DeckPoint(x=0, y=0, z=0), tipLength=1.23 - ), + pick_up_tip_state_update = update_types.StateUpdate( + tips_used=update_types.TipsUsedUpdate( + pipette_id="pipette-id", labware_id="cool-labware", well_name=result + ) ) subject.handle_action( - actions.SucceedCommandAction(private_result=None, command=pick_up_tip) + actions.SucceedCommandAction( + command=_dummy_command(), + state_update=pick_up_tip_state_update, + ) ) return result - # Configure nozzle for partial configurations - configure_nozzle_layout_cmd = commands.ConfigureNozzleLayout.construct( # type: ignore[call-arg] - result=commands.ConfigureNozzleLayoutResult() - ) - def _reconfigure_nozzle_layout(start: str, back_l: str, front_r: str) -> NozzleMap: - configure_nozzle_private_result = commands.ConfigureNozzleLayoutPrivateResult( - pipette_id="pipette-id", - nozzle_map=NozzleMap.build( - physical_nozzles=NINETY_SIX_MAP, - physical_rows=NINETY_SIX_ROWS, - physical_columns=NINETY_SIX_COLS, - starting_nozzle=start, - back_left_nozzle=back_l, - front_right_nozzle=front_r, - valid_nozzle_maps=ValidNozzleMaps( - maps={ - "A1": ["A1"], - "H1": ["H1"], - "A12": ["A12"], - "H12": ["H12"], - "Full": sum( - [ - NINETY_SIX_ROWS["A"], - NINETY_SIX_ROWS["B"], - NINETY_SIX_ROWS["C"], - NINETY_SIX_ROWS["D"], - NINETY_SIX_ROWS["E"], - NINETY_SIX_ROWS["F"], - NINETY_SIX_ROWS["G"], - NINETY_SIX_ROWS["H"], - ], - [], - ), - } - ), + nozzle_map = NozzleMap.build( + physical_nozzles=NINETY_SIX_MAP, + physical_rows=NINETY_SIX_ROWS, + physical_columns=NINETY_SIX_COLS, + starting_nozzle=start, + back_left_nozzle=back_l, + front_right_nozzle=front_r, + valid_nozzle_maps=ValidNozzleMaps( + maps={ + "A1": ["A1"], + "H1": ["H1"], + "A12": ["A12"], + "H12": ["H12"], + "Full": sum( + [ + NINETY_SIX_ROWS["A"], + NINETY_SIX_ROWS["B"], + NINETY_SIX_ROWS["C"], + NINETY_SIX_ROWS["D"], + NINETY_SIX_ROWS["E"], + NINETY_SIX_ROWS["F"], + NINETY_SIX_ROWS["G"], + NINETY_SIX_ROWS["H"], + ], + [], + ), + } ), ) + state_update = update_types.StateUpdate( + pipette_nozzle_map=update_types.PipetteNozzleMapUpdate( + pipette_id="pipette-id", nozzle_map=nozzle_map + ) + ) subject.handle_action( actions.SucceedCommandAction( - private_result=configure_nozzle_private_result, - command=configure_nozzle_layout_cmd, + command=_dummy_command(), state_update=state_update ) ) - return configure_nozzle_private_result.nozzle_map + return nozzle_map map = _reconfigure_nozzle_layout("A1", "A1", "A1") - for x in range(96): + for _ in range(96): _get_next_and_pickup(map) assert _get_next_and_pickup(map) is None subject.handle_action(actions.ResetTipsAction(labware_id="cool-labware")) map = _reconfigure_nozzle_layout("A12", "A12", "A12") - for x in range(96): + for _ in range(96): _get_next_and_pickup(map) assert _get_next_and_pickup(map) is None subject.handle_action(actions.ResetTipsAction(labware_id="cool-labware")) map = _reconfigure_nozzle_layout("H1", "H1", "H1") - for x in range(96): + for _ in range(96): _get_next_and_pickup(map) assert _get_next_and_pickup(map) is None subject.handle_action(actions.ResetTipsAction(labware_id="cool-labware")) map = _reconfigure_nozzle_layout("H12", "H12", "H12") - for x in range(96): + for _ in range(96): _get_next_and_pickup(map) assert _get_next_and_pickup(map) is None diff --git a/api/tests/opentrons/protocol_engine/state/test_update_types.py b/api/tests/opentrons/protocol_engine/state/test_update_types.py new file mode 100644 index 00000000000..741df813e19 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_update_types.py @@ -0,0 +1,75 @@ +"""Unit tests for the utilities in `update_types`.""" + + +from opentrons.protocol_engine.state import update_types + + +def test_append() -> None: + """Test `StateUpdate.append()`.""" + state_update = update_types.StateUpdate( + absorbance_reader_lid=update_types.AbsorbanceReaderLidUpdate( + module_id="module_id", is_lid_on=True + ) + ) + + # Populating a new field should leave the original ones unchanged. + result = state_update.append( + update_types.StateUpdate(pipette_location=update_types.CLEAR) + ) + assert result is state_update + assert state_update.absorbance_reader_lid == update_types.AbsorbanceReaderLidUpdate( + module_id="module_id", is_lid_on=True + ) + assert state_update.pipette_location == update_types.CLEAR + + # Populating a field that's already been populated should overwrite it. + result = state_update.append( + update_types.StateUpdate( + absorbance_reader_lid=update_types.AbsorbanceReaderLidUpdate( + module_id="module_id", is_lid_on=False + ) + ) + ) + assert result is state_update + assert state_update.absorbance_reader_lid == update_types.AbsorbanceReaderLidUpdate( + module_id="module_id", is_lid_on=False + ) + assert state_update.pipette_location == update_types.CLEAR + + +def test_reduce() -> None: + """Test `StateUpdate.reduce()`.""" + assert update_types.StateUpdate.reduce() == update_types.StateUpdate() + + # It should union all the set fields together. + assert update_types.StateUpdate.reduce( + update_types.StateUpdate( + absorbance_reader_lid=update_types.AbsorbanceReaderLidUpdate( + module_id="module_id", is_lid_on=True + ) + ), + update_types.StateUpdate(pipette_location=update_types.CLEAR), + ) == update_types.StateUpdate( + absorbance_reader_lid=update_types.AbsorbanceReaderLidUpdate( + module_id="module_id", is_lid_on=True + ), + pipette_location=update_types.CLEAR, + ) + + # When one field appears multiple times, the last write wins. + assert update_types.StateUpdate.reduce( + update_types.StateUpdate( + absorbance_reader_lid=update_types.AbsorbanceReaderLidUpdate( + module_id="module_id", is_lid_on=True + ) + ), + update_types.StateUpdate( + absorbance_reader_lid=update_types.AbsorbanceReaderLidUpdate( + module_id="module_id", is_lid_on=False + ) + ), + ) == update_types.StateUpdate( + absorbance_reader_lid=update_types.AbsorbanceReaderLidUpdate( + module_id="module_id", is_lid_on=False + ) + ) diff --git a/api/tests/opentrons/protocol_engine/state/test_well_math.py b/api/tests/opentrons/protocol_engine/state/test_well_math.py new file mode 100644 index 00000000000..bb3dd545514 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_well_math.py @@ -0,0 +1,421 @@ +"""Tests for well math.""" + +import json +import pathlib +from itertools import chain +from typing import Any, cast +from collections import OrderedDict + +import pytest + +from opentrons_shared_data.pipette.pipette_definition import ValidNozzleMaps + +from opentrons.types import Point +from opentrons.hardware_control.nozzle_manager import NozzleMap +from opentrons.protocol_engine.state._well_math import ( + wells_covered_dense, + wells_covered_sparse, + nozzles_per_well, +) + +from .. import pipette_fixtures + +_96_FULL_MAP = NozzleMap.build( + physical_nozzles=pipette_fixtures.NINETY_SIX_MAP, + physical_rows=pipette_fixtures.NINETY_SIX_ROWS, + physical_columns=pipette_fixtures.NINETY_SIX_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="H12", + valid_nozzle_maps=ValidNozzleMaps( + maps={ + "Full": sum( + [ + pipette_fixtures.NINETY_SIX_ROWS["A"], + pipette_fixtures.NINETY_SIX_ROWS["B"], + pipette_fixtures.NINETY_SIX_ROWS["C"], + pipette_fixtures.NINETY_SIX_ROWS["D"], + pipette_fixtures.NINETY_SIX_ROWS["E"], + pipette_fixtures.NINETY_SIX_ROWS["F"], + pipette_fixtures.NINETY_SIX_ROWS["G"], + pipette_fixtures.NINETY_SIX_ROWS["H"], + ], + [], + ) + } + ), +) +_96_COL1_MAP = NozzleMap.build( + physical_nozzles=pipette_fixtures.NINETY_SIX_MAP, + physical_rows=pipette_fixtures.NINETY_SIX_ROWS, + physical_columns=pipette_fixtures.NINETY_SIX_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="H1", + valid_nozzle_maps=ValidNozzleMaps( + maps={"Column1": pipette_fixtures.NINETY_SIX_COLS["1"]} + ), +) + +_96_COL12_MAP = NozzleMap.build( + physical_nozzles=pipette_fixtures.NINETY_SIX_MAP, + physical_rows=pipette_fixtures.NINETY_SIX_ROWS, + physical_columns=pipette_fixtures.NINETY_SIX_COLS, + starting_nozzle="A12", + back_left_nozzle="A12", + front_right_nozzle="H12", + valid_nozzle_maps=ValidNozzleMaps( + maps={"Column12": pipette_fixtures.NINETY_SIX_COLS["12"]} + ), +) + +_96_SINGLE_FR_MAP = NozzleMap.build( + physical_nozzles=pipette_fixtures.NINETY_SIX_MAP, + physical_rows=pipette_fixtures.NINETY_SIX_ROWS, + physical_columns=pipette_fixtures.NINETY_SIX_COLS, + starting_nozzle="H12", + back_left_nozzle="H12", + front_right_nozzle="H12", + valid_nozzle_maps=ValidNozzleMaps(maps={"Single": ["H12"]}), +) +_96_SINGLE_BL_MAP = NozzleMap.build( + physical_nozzles=pipette_fixtures.NINETY_SIX_MAP, + physical_rows=pipette_fixtures.NINETY_SIX_ROWS, + physical_columns=pipette_fixtures.NINETY_SIX_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A1", + valid_nozzle_maps=ValidNozzleMaps(maps={"Single": ["A1"]}), +) +_96_RECTANGLE_MAP = NozzleMap.build( + physical_nozzles=pipette_fixtures.NINETY_SIX_MAP, + physical_rows=pipette_fixtures.NINETY_SIX_ROWS, + physical_columns=pipette_fixtures.NINETY_SIX_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="E2", + valid_nozzle_maps=ValidNozzleMaps( + maps={ + "Subrect": [ + "A1", + "A2", + "B1", + "B2", + "C1", + "C2", + "D1", + "D2", + "E1", + "E2", + ] + } + ), +) +_8_FULL_MAP = NozzleMap.build( + physical_nozzles=pipette_fixtures.EIGHT_CHANNEL_MAP, + physical_rows=pipette_fixtures.EIGHT_CHANNEL_ROWS, + physical_columns=pipette_fixtures.EIGHT_CHANNEL_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="H1", + valid_nozzle_maps=ValidNozzleMaps( + maps={"Full": pipette_fixtures.EIGHT_CHANNEL_COLS["1"]} + ), +) +_8_SINGLE_MAP = NozzleMap.build( + physical_nozzles=pipette_fixtures.EIGHT_CHANNEL_MAP, + physical_rows=pipette_fixtures.EIGHT_CHANNEL_ROWS, + physical_columns=pipette_fixtures.EIGHT_CHANNEL_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A1", + valid_nozzle_maps=ValidNozzleMaps(maps={"Full": ["A1"]}), +) +_8_HALF_MAP = NozzleMap.build( + physical_nozzles=pipette_fixtures.EIGHT_CHANNEL_MAP, + physical_rows=pipette_fixtures.EIGHT_CHANNEL_ROWS, + physical_columns=pipette_fixtures.EIGHT_CHANNEL_COLS, + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="D1", + valid_nozzle_maps=ValidNozzleMaps(maps={"Half": ["A1", "B1", "C1", "D1"]}), +) +_SINGLE_MAP = NozzleMap.build( + physical_nozzles=OrderedDict((("A1", Point(0.0, 1.0, 2.0)),)), + physical_rows=OrderedDict((("1", ["A1"]),)), + physical_columns=OrderedDict((("A", ["A1"]),)), + starting_nozzle="A1", + back_left_nozzle="A1", + front_right_nozzle="A1", + valid_nozzle_maps=ValidNozzleMaps(maps={"Single": ["A1"]}), +) + + +def _fixture(fixture_name: str) -> Any: + return json.load( + open( + pathlib.Path(__file__).parent + / f"../../../../../shared-data/labware/fixtures/2/{fixture_name}.json" + ) + ) + + +@pytest.fixture +def _96_wells() -> list[list[str]]: + return fixture_map("fixture_96_plate") + + +@pytest.fixture +def _384_wells() -> list[list[str]]: + return fixture_map("fixture_384_plate") + + +@pytest.fixture +def _12_reservoir() -> list[list[str]]: + return fixture_map("fixture_12_trough_v2") + + +@pytest.fixture +def _1_reservoir() -> list[list[str]]: + return [["A1"]] + + +def all_wells(fixture_name: str) -> list[str]: + """All wells in a labware as a flat list.""" + ordering = fixture_map(fixture_name) + return list(chain(*ordering)) + + +def fixture_map(fixture_name: str) -> list[list[str]]: + """The ordering map.""" + return cast(list[list[str]], _fixture(fixture_name)["ordering"]) + + +@pytest.mark.parametrize( + "nozzle_map,target_well,result", + [ + # these configurations have all the nozzles on/in the wellplate + (_SINGLE_MAP, "A1", ["A1"]), + (_SINGLE_MAP, "D7", ["D7"]), + (_8_FULL_MAP, "A1", ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"]), + (_8_FULL_MAP, "A10", ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"]), + (_96_FULL_MAP, "A1", all_wells("fixture_96_plate")), + ( + _96_RECTANGLE_MAP, + "C8", + ["C8", "D8", "E8", "F8", "G8", "C9", "D9", "E9", "F9", "G9"], + ), + # these configurations have some nozzles below or to the right + (_8_FULL_MAP, "D1", ["D1", "E1", "F1", "G1", "H1"]), + ( + _96_FULL_MAP, + "C8", + [ + well + for well in all_wells("fixture_96_plate") + if ord(well[0]) >= ord("C") and int(well[1:]) >= 8 + ], + ), + ], +) +def test_wells_covered_dense_96( + nozzle_map: NozzleMap, + target_well: str, + result: list[str], + _96_wells: list[list[str]], +) -> None: + """It should calculate well coverage for an SBS 96.""" + assert list(wells_covered_dense(nozzle_map, target_well, _96_wells)) == result + + +@pytest.mark.parametrize( + "nozzle_map,target_well,result", + [ + # these configurations have all the nozzles on/in the wellplate + (_SINGLE_MAP, "A1", ["A1"]), + (_SINGLE_MAP, "D7", ["D7"]), + (_8_FULL_MAP, "A1", ["A1", "C1", "E1", "G1", "I1", "K1", "M1", "O1"]), + (_8_FULL_MAP, "B1", ["B1", "D1", "F1", "H1", "J1", "L1", "N1", "P1"]), + (_8_FULL_MAP, "A10", ["A10", "C10", "E10", "G10", "I10", "K10", "M10", "O10"]), + # well offsets inside the four-well clusters that are the size of each 96-well well + ( + _96_FULL_MAP, + "A1", + [ + well + for well in all_wells("fixture_384_plate") + if well[0] in "ACEGIKMO" + and int(well[1:]) in [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23] + ], + ), + ( + _96_FULL_MAP, + "A2", + [ + well + for well in all_wells("fixture_384_plate") + if well[0] in "ACEGIKMO" + and int(well[1:]) in [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24] + ], + ), + ( + _96_FULL_MAP, + "B1", + [ + well + for well in all_wells("fixture_384_plate") + if well[0] in "BDFHJLNP" + and int(well[1:]) in [1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23] + ], + ), + ( + _96_FULL_MAP, + "B2", + [ + well + for well in all_wells("fixture_384_plate") + if well[0] in "BDFHJLNP" + and int(well[1:]) in [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24] + ], + ), + ( + _96_RECTANGLE_MAP, + "C8", + ["C8", "E8", "G8", "I8", "K8", "C10", "E10", "G10", "I10", "K10"], + ), + ( + _96_RECTANGLE_MAP, + "C9", + ["C9", "E9", "G9", "I9", "K9", "C11", "E11", "G11", "I11", "K11"], + ), + ( + _96_RECTANGLE_MAP, + "D8", + ["D8", "F8", "H8", "J8", "L8", "D10", "F10", "H10", "J10", "L10"], + ), + ( + _96_RECTANGLE_MAP, + "D9", + ["D9", "F9", "H9", "J9", "L9", "D11", "F11", "H11", "J11", "L11"], + ), + ], +) +def test_wells_covered_dense_384( + nozzle_map: NozzleMap, + target_well: str, + result: list[str], + _384_wells: list[list[str]], +) -> None: + """It should calculate well coverage for an SBS 384.""" + assert list(wells_covered_dense(nozzle_map, target_well, _384_wells)) == result + + +@pytest.mark.parametrize( + "nozzle_map,target_well,result", + [ + (_SINGLE_MAP, "A1", ["A1"]), + (_SINGLE_MAP, "A8", ["A8"]), + (_8_FULL_MAP, "A1", ["A1"]), + (_8_FULL_MAP, "A8", ["A8"]), + ( + _96_FULL_MAP, + "A1", + all_wells("fixture_12_trough_v2"), + ), + ( + _96_FULL_MAP, + "A8", + [well for well in all_wells("fixture_12_trough_v2") if int(well[1:]) >= 8], + ), + (_96_RECTANGLE_MAP, "A1", ["A1", "A2"]), + (_96_RECTANGLE_MAP, "A8", ["A8", "A9"]), + ], +) +def test_wells_covered_sparse_12( + nozzle_map: NozzleMap, + target_well: str, + result: list[str], + _12_reservoir: list[list[str]], +) -> None: + """It should calculate well coverage for a 12 column reservoir.""" + assert list(wells_covered_sparse(nozzle_map, target_well, _12_reservoir)) == result + + +@pytest.mark.parametrize( + "nozzle_map", + [ + _SINGLE_MAP, + _8_FULL_MAP, + _96_FULL_MAP, + _96_RECTANGLE_MAP, + ], +) +def test_wells_covered_sparse_1( + nozzle_map: NozzleMap, _1_reservoir: list[list[str]] +) -> None: + """It should calculate well coverage for a 1 column reservoir.""" + assert list(wells_covered_sparse(nozzle_map, "A1", _1_reservoir)) == ["A1"] + + +@pytest.mark.parametrize( + "nozzle_map", + [ + _SINGLE_MAP, + _8_FULL_MAP, + _96_FULL_MAP, + _96_RECTANGLE_MAP, + _8_SINGLE_MAP, + _96_SINGLE_BL_MAP, + _96_SINGLE_FR_MAP, + ], +) +@pytest.mark.parametrize("fixture_name", ["fixture_384_plate", "fixture_96_plate"]) +def test_nozzles_per_well_dense_force_1( + nozzle_map: NozzleMap, fixture_name: str +) -> None: + """It should calculate nozzles per well for SBS dense labware.""" + all_fixture_wells = all_wells(fixture_name) + # it's a bit unreasonable to test every well of a 384 plate so walk the diagonal + well_name = "A1" + while True: + if well_name not in all_fixture_wells: + break + assert nozzles_per_well(nozzle_map, well_name, fixture_map(fixture_name)) == 1 + well_name = f"{chr(ord(well_name[0])+1)}{str(int(well_name[1:])+1)}" + + +@pytest.mark.parametrize( + "nozzle_map,target_well,result", + [ + (_SINGLE_MAP, "A1", 1), + (_SINGLE_MAP, "A12", 1), + (_8_FULL_MAP, "A1", 8), + (_96_FULL_MAP, "A1", 8), + (_96_FULL_MAP, "A12", 8), + (_96_RECTANGLE_MAP, "A4", 5), + ], +) +def test_nozzles_per_well_12column( + nozzle_map: NozzleMap, target_well: str, result: int +) -> None: + """It should calculate nozzles per well for a 12 column.""" + assert ( + nozzles_per_well(nozzle_map, target_well, fixture_map("fixture_12_trough_v2")) + == result + ) + + +@pytest.mark.parametrize( + "nozzle_map,result", + [ + (_SINGLE_MAP, 1), + (_SINGLE_MAP, 1), + (_8_FULL_MAP, 8), + (_96_FULL_MAP, 96), + (_96_FULL_MAP, 96), + (_96_RECTANGLE_MAP, 10), + ], +) +def test_nozzles_per_well_1column(nozzle_map: NozzleMap, result: int) -> None: + """It should calculate nozzles per well for a 1-well reservoir.""" + assert nozzles_per_well(nozzle_map, "A1", [["A1"]]) == result diff --git a/api/tests/opentrons/protocol_engine/state/test_well_store_old.py b/api/tests/opentrons/protocol_engine/state/test_well_store_old.py new file mode 100644 index 00000000000..51142afd9da --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_well_store_old.py @@ -0,0 +1,235 @@ +"""Well state store tests. + +DEPRECATED: Testing WellStore independently of WellView is no longer helpful. +Try to add new tests to well_state.py, where they can be tested together, +treating WellState as a private implementation detail. +""" + +import pytest +from datetime import datetime +from opentrons.protocol_engine.state.wells import WellStore +from opentrons.protocol_engine.actions.actions import SucceedCommandAction +from opentrons.protocol_engine.state import update_types + +from .command_fixtures import ( + create_liquid_probe_command, + create_load_liquid_command, + create_aspirate_command, +) + + +@pytest.fixture +def subject() -> WellStore: + """Well store test subject.""" + return WellStore() + + +def test_handles_liquid_probe_success(subject: WellStore) -> None: + """It should add the well to the state after a successful liquid probe.""" + labware_id = "labware-id" + well_name = "well-name" + liquid_probe = create_liquid_probe_command() + timestamp = datetime(year=2020, month=1, day=2) + + subject.handle_action( + SucceedCommandAction( + command=liquid_probe, + state_update=update_types.StateUpdate( + liquid_probed=update_types.LiquidProbedUpdate( + labware_id="labware-id", + well_name="well-name", + height=15.0, + volume=30.0, + last_probed=timestamp, + ) + ), + ) + ) + + assert len(subject.state.probed_heights) == 1 + assert len(subject.state.probed_volumes) == 1 + + assert subject.state.probed_heights[labware_id][well_name].height == 15.0 + assert subject.state.probed_heights[labware_id][well_name].last_probed == timestamp + assert subject.state.probed_volumes[labware_id][well_name].volume == 30.0 + assert subject.state.probed_volumes[labware_id][well_name].last_probed == timestamp + assert ( + subject.state.probed_volumes[labware_id][well_name].operations_since_probe == 0 + ) + + +def test_handles_load_liquid_success(subject: WellStore) -> None: + """It should add the well to the state after a successful load liquid.""" + labware_id = "labware-id" + well_name_1 = "well-name-1" + well_name_2 = "well-name-2" + load_liquid = create_load_liquid_command( + labware_id=labware_id, volume_by_well={well_name_1: 30, well_name_2: 100} + ) + timestamp = datetime(year=2020, month=1, day=2) + + subject.handle_action( + SucceedCommandAction( + command=load_liquid, + state_update=update_types.StateUpdate( + liquid_loaded=update_types.LiquidLoadedUpdate( + labware_id=labware_id, + volumes={well_name_1: 30, well_name_2: 100}, + last_loaded=timestamp, + ) + ), + ) + ) + + assert len(subject.state.loaded_volumes) == 1 + assert len(subject.state.loaded_volumes[labware_id]) == 2 + + assert subject.state.loaded_volumes[labware_id][well_name_1].volume == 30.0 + assert ( + subject.state.loaded_volumes[labware_id][well_name_1].last_loaded == timestamp + ) + assert ( + subject.state.loaded_volumes[labware_id][well_name_1].operations_since_load == 0 + ) + assert subject.state.loaded_volumes[labware_id][well_name_2].volume == 100.0 + assert ( + subject.state.loaded_volumes[labware_id][well_name_2].last_loaded == timestamp + ) + assert ( + subject.state.loaded_volumes[labware_id][well_name_2].operations_since_load == 0 + ) + + +def test_handles_load_liquid_and_aspirate(subject: WellStore) -> None: + """It should populate the well state after load liquid and update the well state after aspirate.""" + pipette_id = "pipette-id" + labware_id = "labware-id" + well_name_1 = "well-name-1" + well_name_2 = "well-name-2" + aspirated_volume = 10.0 + load_liquid = create_load_liquid_command( + labware_id=labware_id, volume_by_well={well_name_1: 30, well_name_2: 100} + ) + aspirate_1 = create_aspirate_command( + pipette_id=pipette_id, + volume=aspirated_volume, + flow_rate=1.0, + labware_id=labware_id, + well_name=well_name_1, + ) + aspirate_2 = create_aspirate_command( + pipette_id=pipette_id, + volume=aspirated_volume, + flow_rate=1.0, + labware_id=labware_id, + well_name=well_name_2, + ) + timestamp = datetime(year=2020, month=1, day=2) + + subject.handle_action( + SucceedCommandAction( + command=load_liquid, + state_update=update_types.StateUpdate( + liquid_loaded=update_types.LiquidLoadedUpdate( + labware_id=labware_id, + volumes={well_name_1: 30, well_name_2: 100}, + last_loaded=timestamp, + ) + ), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=aspirate_1, + state_update=update_types.StateUpdate( + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id=labware_id, + well_names=[well_name_1, well_name_2], + volume_added=-aspirated_volume, + ) + ), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=aspirate_2, + state_update=update_types.StateUpdate( + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id=labware_id, + well_names=[well_name_2], + volume_added=-aspirated_volume, + ) + ), + ) + ) + + assert len(subject.state.loaded_volumes) == 1 + assert len(subject.state.loaded_volumes[labware_id]) == 2 + + assert subject.state.loaded_volumes[labware_id][well_name_1].volume == 20.0 + assert ( + subject.state.loaded_volumes[labware_id][well_name_1].last_loaded == timestamp + ) + assert ( + subject.state.loaded_volumes[labware_id][well_name_1].operations_since_load == 1 + ) + assert subject.state.loaded_volumes[labware_id][well_name_2].volume == 80.0 + assert ( + subject.state.loaded_volumes[labware_id][well_name_2].last_loaded == timestamp + ) + assert ( + subject.state.loaded_volumes[labware_id][well_name_2].operations_since_load == 2 + ) + + +def test_handles_liquid_probe_and_aspirate(subject: WellStore) -> None: + """It should populate the well state after liquid probe and update the well state after aspirate.""" + pipette_id = "pipette-id" + labware_id = "labware-id" + well_name = "well-name" + aspirated_volume = 10.0 + liquid_probe = create_liquid_probe_command() + aspirate = create_aspirate_command( + pipette_id=pipette_id, + volume=aspirated_volume, + flow_rate=1.0, + labware_id=labware_id, + well_name=well_name, + ) + timestamp = datetime(year=2020, month=1, day=2) + + subject.handle_action( + SucceedCommandAction( + command=liquid_probe, + state_update=update_types.StateUpdate( + liquid_probed=update_types.LiquidProbedUpdate( + labware_id="labware-id", + well_name="well-name", + height=15.0, + volume=30.0, + last_probed=timestamp, + ) + ), + ) + ) + subject.handle_action( + SucceedCommandAction( + command=aspirate, + state_update=update_types.StateUpdate( + liquid_operated=update_types.LiquidOperatedUpdate( + labware_id="labware-id", + well_names=["well-name"], + volume_added=-aspirated_volume, + ) + ), + ) + ) + + assert len(subject.state.probed_heights[labware_id]) == 0 + assert len(subject.state.probed_volumes) == 1 + + assert subject.state.probed_volumes[labware_id][well_name].volume == 20.0 + assert subject.state.probed_volumes[labware_id][well_name].last_probed == timestamp + assert ( + subject.state.probed_volumes[labware_id][well_name].operations_since_probe == 1 + ) diff --git a/api/tests/opentrons/protocol_engine/state/test_well_view_old.py b/api/tests/opentrons/protocol_engine/state/test_well_view_old.py new file mode 100644 index 00000000000..9ced4db9df0 --- /dev/null +++ b/api/tests/opentrons/protocol_engine/state/test_well_view_old.py @@ -0,0 +1,63 @@ +"""Well view tests. + +DEPRECATED: Testing WellView independently of WellStore is no longer helpful. +Try to add new tests to test_well_state.py, where they can be tested together, +treating WellState as a private implementation detail. +""" +from datetime import datetime +from opentrons.protocol_engine.types import ( + LoadedVolumeInfo, + ProbedHeightInfo, + ProbedVolumeInfo, +) +import pytest +from opentrons.protocol_engine.state.wells import WellState, WellView + + +@pytest.fixture +def subject() -> WellView: + """Get a well view test subject.""" + loaded_volume_info = LoadedVolumeInfo( + volume=30.0, last_loaded=datetime.now(), operations_since_load=0 + ) + probed_height_info = ProbedHeightInfo(height=5.5, last_probed=datetime.now()) + probed_volume_info = ProbedVolumeInfo( + volume=25.0, last_probed=datetime.now(), operations_since_probe=0 + ) + state = WellState( + loaded_volumes={"labware_id_1": {"well_name": loaded_volume_info}}, + probed_heights={"labware_id_2": {"well_name": probed_height_info}}, + probed_volumes={"labware_id_2": {"well_name": probed_volume_info}}, + ) + + return WellView(state) + + +def test_get_well_liquid_info(subject: WellView) -> None: + """Should return a tuple of well infos.""" + volume_info = subject.get_well_liquid_info( + labware_id="labware_id_1", well_name="well_name" + ) + assert volume_info.loaded_volume is not None + assert volume_info.probed_height is None + assert volume_info.probed_volume is None + assert volume_info.loaded_volume.volume == 30.0 + + volume_info = subject.get_well_liquid_info( + labware_id="labware_id_2", well_name="well_name" + ) + assert volume_info.loaded_volume is None + assert volume_info.probed_height is not None + assert volume_info.probed_volume is not None + assert volume_info.probed_height.height == 5.5 + assert volume_info.probed_volume.volume == 25.0 + + +def test_get_all(subject: WellView) -> None: + """Should return a list of well summaries.""" + summaries = subject.get_all() + + assert len(summaries) == 2, f"{summaries}" + assert summaries[0].loaded_volume == 30.0 + assert summaries[1].probed_height == 5.5 + assert summaries[1].probed_volume == 25.0 diff --git a/api/tests/opentrons/protocol_engine/test_plugins.py b/api/tests/opentrons/protocol_engine/test_plugins.py index 0da44ab62bc..5efe4736cfa 100644 --- a/api/tests/opentrons/protocol_engine/test_plugins.py +++ b/api/tests/opentrons/protocol_engine/test_plugins.py @@ -3,7 +3,7 @@ from decoy import Decoy from datetime import datetime -from opentrons.protocol_engine.state import StateView +from opentrons.protocol_engine.state.state import StateView from opentrons.protocol_engine.plugins import AbstractPlugin, PluginStarter from opentrons.protocol_engine.actions import ActionDispatcher, Action, PlayAction diff --git a/api/tests/opentrons/protocol_engine/test_protocol_engine.py b/api/tests/opentrons/protocol_engine/test_protocol_engine.py index 2669640e649..34dac853ef8 100644 --- a/api/tests/opentrons/protocol_engine/test_protocol_engine.py +++ b/api/tests/opentrons/protocol_engine/test_protocol_engine.py @@ -10,6 +10,7 @@ from opentrons_shared_data.robot.types import RobotType from opentrons.protocol_engine.actions.actions import SetErrorRecoveryPolicyAction +from opentrons.protocol_engine.state.update_types import StateUpdate from opentrons.types import DeckSlotName from opentrons.hardware_control import HardwareControlAPI, OT2HardwareControlAPI from opentrons.hardware_control.modules import MagDeck, TempDeck @@ -38,8 +39,13 @@ HardwareStopper, DoorWatcher, ) -from opentrons.protocol_engine.resources import ModelUtils, ModuleDataProvider -from opentrons.protocol_engine.state import Config, StateStore +from opentrons.protocol_engine.resources import ( + FileProvider, + ModelUtils, + ModuleDataProvider, +) +from opentrons.protocol_engine.state.config import Config +from opentrons.protocol_engine.state.state import StateStore from opentrons.protocol_engine.plugins import AbstractPlugin, PluginStarter from opentrons.protocol_engine.errors import ProtocolCommandFailedError, ErrorOccurrence @@ -117,6 +123,12 @@ def module_data_provider(decoy: Decoy) -> ModuleDataProvider: return decoy.mock(cls=ModuleDataProvider) +@pytest.fixture +def file_provider(decoy: Decoy) -> FileProvider: + """Get a mock FileProvider.""" + return decoy.mock(cls=FileProvider) + + @pytest.fixture(autouse=True) def _mock_slot_standardization_module( decoy: Decoy, monkeypatch: pytest.MonkeyPatch @@ -147,6 +159,7 @@ def subject( hardware_stopper: HardwareStopper, door_watcher: DoorWatcher, module_data_provider: ModuleDataProvider, + file_provider: FileProvider, ) -> ProtocolEngine: """Get a ProtocolEngine test subject with its dependencies stubbed out.""" return ProtocolEngine( @@ -159,6 +172,7 @@ def subject( hardware_stopper=hardware_stopper, door_watcher=door_watcher, module_data_provider=module_data_provider, + file_provider=file_provider, ) @@ -612,20 +626,31 @@ def test_pause( ) +@pytest.mark.parametrize("reconcile_false_positive", [True, False]) def test_resume_from_recovery( decoy: Decoy, state_store: StateStore, action_dispatcher: ActionDispatcher, subject: ProtocolEngine, + reconcile_false_positive: bool, ) -> None: """It should dispatch a ResumeFromRecoveryAction.""" - expected_action = ResumeFromRecoveryAction() + decoy.when(state_store.commands.get_state_update_for_false_positive()).then_return( + sentinel.state_update_for_false_positive + ) + empty_state_update = StateUpdate() + + expected_action = ResumeFromRecoveryAction( + sentinel.state_update_for_false_positive + if reconcile_false_positive + else empty_state_update + ) decoy.when( state_store.commands.validate_action_allowed(expected_action) ).then_return(expected_action) - subject.resume_from_recovery() + subject.resume_from_recovery(reconcile_false_positive) decoy.verify(action_dispatcher.dispatch(expected_action)) @@ -659,7 +684,7 @@ async def test_finish( """It should be able to gracefully tell the engine it's done.""" completed_at = datetime(2021, 1, 1, 0, 0) - decoy.when(state_store.commands.state.stopped_by_estop).then_return(False) + decoy.when(state_store.commands.get_is_stopped_by_estop()).then_return(False) decoy.when(model_utils.get_timestamp()).then_return(completed_at) await subject.finish( @@ -694,7 +719,7 @@ async def test_finish_with_defaults( state_store: StateStore, ) -> None: """It should be able to gracefully tell the engine it's done.""" - decoy.when(state_store.commands.state.stopped_by_estop).then_return(False) + decoy.when(state_store.commands.get_is_stopped_by_estop()).then_return(False) await subject.finish() decoy.verify( @@ -736,7 +761,7 @@ async def test_finish_with_error( error=error, ) - decoy.when(state_store.commands.state.stopped_by_estop).then_return( + decoy.when(state_store.commands.get_is_stopped_by_estop()).then_return( stopped_by_estop ) decoy.when(model_utils.generate_id()).then_return("error-id") @@ -836,7 +861,7 @@ async def test_finish_stops_hardware_if_queue_worker_join_fails( await queue_worker.join(), ).then_raise(exception) - decoy.when(state_store.commands.state.stopped_by_estop).then_return(False) + decoy.when(state_store.commands.get_is_stopped_by_estop()).then_return(False) error_id = "error-id" completed_at = datetime(2021, 1, 1, 0, 0) @@ -972,8 +997,7 @@ async def test_estop_noops_if_invalid( subject.estop() # Should not raise. decoy.verify( - action_dispatcher.dispatch(), # type: ignore - ignore_extra_args=True, + action_dispatcher.dispatch(expected_action), times=0, ) decoy.verify( @@ -1108,21 +1132,18 @@ def test_add_liquid( decoy: Decoy, action_dispatcher: ActionDispatcher, subject: ProtocolEngine, + state_store: StateStore, ) -> None: """It should dispatch an AddLiquidAction action.""" + liquid_obj = Liquid(id="water-id", displayName="water", description="water desc") + decoy.when( + state_store.liquid.validate_liquid_allowed(liquid=liquid_obj) + ).then_return(liquid_obj) subject.add_liquid( id="water-id", name="water", description="water desc", color=None ) - decoy.verify( - action_dispatcher.dispatch( - AddLiquidAction( - liquid=Liquid( - id="water-id", displayName="water", description="water desc" - ) - ) - ) - ) + decoy.verify(action_dispatcher.dispatch(AddLiquidAction(liquid=liquid_obj))) async def test_use_attached_temp_and_mag_modules( diff --git a/api/tests/opentrons/protocol_runner/test_json_translator.py b/api/tests/opentrons/protocol_runner/test_json_translator.py index c18943e8f5c..e2735e4cdbc 100644 --- a/api/tests/opentrons/protocol_runner/test_json_translator.py +++ b/api/tests/opentrons/protocol_runner/test_json_translator.py @@ -13,8 +13,7 @@ Group, Metadata1, WellDefinition, - BoundedSection, - RectangularCrossSection, + CuboidalFrustum, InnerWellGeometry, SphericalSegment, ) @@ -41,6 +40,8 @@ DeckPoint, DeckSlotLocation, WellLocation, + LiquidHandlingWellLocation, + PickUpTipWellLocation, DropTipWellLocation, WellOrigin, DropTipWellOrigin, @@ -86,17 +87,17 @@ protocol_schema_v8.Command( commandType="aspirate", key=None, - params=protocol_schema_v8.Params( - pipetteId="pipette-id-1", - labwareId="labware-id-2", - volume=1.23, - flowRate=4.56, - wellName="A1", - wellLocation=SD_WellLocation( - origin="bottom", - offset=OffsetVector(x=0, y=0, z=7.89), - ), - ), + params={ + "pipetteId": "pipette-id-1", + "labwareId": "labware-id-2", + "volume": 1.23, + "flowRate": 4.56, + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": {"x": 0, "y": 0, "z": 7.89}, + }, + }, ), pe_commands.AspirateCreate( key=None, @@ -107,7 +108,7 @@ volume=1.23, flowRate=4.56, wellName="A1", - wellLocation=WellLocation( + wellLocation=LiquidHandlingWellLocation( origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=7.89), ), @@ -148,17 +149,17 @@ protocol_schema_v8.Command( commandType="dispense", key="dispense-key", - params=protocol_schema_v8.Params( - pipetteId="pipette-id-1", - labwareId="labware-id-2", - volume=1.23, - flowRate=4.56, - wellName="A1", - wellLocation=SD_WellLocation( - origin="bottom", - offset=OffsetVector(x=0, y=0, z=7.89), - ), - ), + params={ + "pipetteId": "pipette-id-1", + "labwareId": "labware-id-2", + "volume": 1.23, + "flowRate": 4.56, + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": {"x": 0, "y": 0, "z": 7.89}, + }, + }, ), pe_commands.DispenseCreate( key="dispense-key", @@ -168,7 +169,7 @@ volume=1.23, flowRate=4.56, wellName="A1", - wellLocation=WellLocation( + wellLocation=LiquidHandlingWellLocation( origin=WellOrigin.BOTTOM, offset=WellOffset(x=0, y=0, z=7.89), ), @@ -194,11 +195,11 @@ ), protocol_schema_v8.Command( commandType="dropTip", - params=protocol_schema_v8.Params( - pipetteId="pipette-id-1", - labwareId="labware-id-2", - wellName="A1", - ), + params={ + "pipetteId": "pipette-id-1", + "labwareId": "labware-id-2", + "wellName": "A1", + }, ), pe_commands.DropTipCreate( params=pe_commands.DropTipParams( @@ -231,18 +232,18 @@ ), protocol_schema_v8.Command( commandType="pickUpTip", - params=protocol_schema_v8.Params( - pipetteId="pipette-id-1", - labwareId="labware-id-2", - wellName="A1", - ), + params={ + "pipetteId": "pipette-id-1", + "labwareId": "labware-id-2", + "wellName": "A1", + }, ), pe_commands.PickUpTipCreate( params=pe_commands.PickUpTipParams( pipetteId="pipette-id-1", labwareId="labware-id-2", wellName="A1", - wellLocation=WellLocation(), + wellLocation=PickUpTipWellLocation(), ) ), ), @@ -273,15 +274,15 @@ ), protocol_schema_v8.Command( commandType="touchTip", - params=protocol_schema_v8.Params( - pipetteId="pipette-id-1", - labwareId="labware-id-2", - wellName="A1", - wellLocation=SD_WellLocation( - origin="bottom", - offset=OffsetVector(x=0, y=0, z=-1.23), - ), - ), + params={ + "pipetteId": "pipette-id-1", + "labwareId": "labware-id-2", + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": {"x": 0, "y": 0, "z": -1.23}, + }, + }, ), pe_commands.TouchTipCreate( params=pe_commands.TouchTipParams( @@ -308,9 +309,11 @@ ), protocol_schema_v8.Command( commandType="loadPipette", - params=protocol_schema_v8.Params( - pipetteId="pipette-id-1", mount="left", pipetteName="p10_single" - ), + params={ + "pipetteId": "pipette-id-1", + "mount": "left", + "pipetteName": "p10_single", + }, ), pe_commands.LoadPipetteCreate( params=pe_commands.LoadPipetteParams( @@ -338,11 +341,11 @@ ), protocol_schema_v8.Command( commandType="loadModule", - params=protocol_schema_v8.Params( - moduleId="module-id-1", - model="magneticModuleV2", - location=Location(slotName="3"), - ), + params={ + "moduleId": "module-id-1", + "model": "magneticModuleV2", + "location": {"slotName": "3"}, + }, ), pe_commands.LoadModuleCreate( params=pe_commands.LoadModuleParams( @@ -373,14 +376,14 @@ ), protocol_schema_v8.Command( commandType="loadLabware", - params=protocol_schema_v8.Params( - labwareId="labware-id-2", - version=1, - namespace="example", - loadName="foo_8_plate_33ul", - location=Location(moduleId="temperatureModuleId"), - displayName="Trash", - ), + params={ + "labwareId": "labware-id-2", + "version": 1, + "namespace": "example", + "loadName": "foo_8_plate_33ul", + "location": {"moduleId": "temperatureModuleId"}, + "displayName": "Trash", + }, ), pe_commands.LoadLabwareCreate( params=pe_commands.LoadLabwareParams( @@ -422,16 +425,16 @@ ), protocol_schema_v8.Command( commandType="blowout", - params=protocol_schema_v8.Params( - pipetteId="pipette-id-1", - labwareId="labware-id-2", - wellName="A1", - wellLocation=SD_WellLocation( - origin="bottom", - offset=OffsetVector(x=0, y=0, z=7.89), - ), - flowRate=1.23, - ), + params={ + "pipetteId": "pipette-id-1", + "labwareId": "labware-id-2", + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": {"x": 0, "y": 0, "z": 7.89}, + }, + "flowRate": 1.23, + }, ), pe_commands.BlowOutCreate( params=pe_commands.BlowOutParams( @@ -457,7 +460,7 @@ ), protocol_schema_v8.Command( commandType="delay", - params=protocol_schema_v8.Params(waitForResume=True, message="hello world"), + params={"waitForResume": True, "message": "hello world"}, ), pe_commands.WaitForResumeCreate( params=pe_commands.WaitForResumeParams(message="hello world") @@ -474,7 +477,7 @@ ), protocol_schema_v8.Command( commandType="delay", - params=protocol_schema_v8.Params(seconds=12.34, message="hello world"), + params={"seconds": 12.34, "message": "hello world"}, ), pe_commands.WaitForDurationCreate( params=pe_commands.WaitForDurationParams( @@ -494,7 +497,7 @@ ), protocol_schema_v8.Command( commandType="waitForResume", - params=protocol_schema_v8.Params(message="hello world"), + params={"message": "hello world"}, ), pe_commands.WaitForResumeCreate( params=pe_commands.WaitForResumeParams(message="hello world") @@ -511,7 +514,7 @@ ), protocol_schema_v8.Command( commandType="waitForDuration", - params=protocol_schema_v8.Params(seconds=12.34, message="hello world"), + params={"seconds": 12.34, "message": "hello world"}, ), pe_commands.WaitForDurationCreate( params=pe_commands.WaitForDurationParams( @@ -541,12 +544,12 @@ ), protocol_schema_v8.Command( commandType="moveToCoordinates", - params=protocol_schema_v8.Params( - pipetteId="pipette-id-1", - coordinates=OffsetVector(x=1.1, y=2.2, z=3.3), - minimumZHeight=123.4, - forceDirect=True, - ), + params={ + "pipetteId": "pipette-id-1", + "coordinates": {"x": 1.1, "y": 2.2, "z": 3.3}, + "minimumZHeight": 123.4, + "forceDirect": True, + }, ), pe_commands.MoveToCoordinatesCreate( params=pe_commands.MoveToCoordinatesParams( @@ -594,20 +597,20 @@ ), protocol_schema_v8.Command( commandType="thermocycler/runProfile", - params=protocol_schema_v8.Params( - moduleId="module-id-2", - blockMaxVolumeUl=1.11, - profile=[ - ProfileStep( - celsius=2.22, - holdSeconds=3.33, - ), - ProfileStep( - celsius=4.44, - holdSeconds=5.55, - ), + params={ + "moduleId": "module-id-2", + "blockMaxVolumeUl": 1.11, + "profile": [ + { + "celsius": 2.22, + "holdSeconds": 3.33, + }, + { + "celsius": 4.44, + "holdSeconds": 5.55, + }, ], - ), + }, ), pe_commands.thermocycler.RunProfileCreate( params=pe_commands.thermocycler.RunProfileParams( @@ -646,11 +649,11 @@ protocol_schema_v8.Command( commandType="loadLiquid", key=None, - params=protocol_schema_v8.Params( - labwareId="labware-id-2", - liquidId="liquid-id-555", - volumeByWell={"A1": 32, "B2": 50}, - ), + params={ + "labwareId": "labware-id-2", + "liquidId": "liquid-id-555", + "volumeByWell": {"A1": 32, "B2": 50}, + }, ), pe_commands.LoadLiquidCreate( key=None, @@ -684,36 +687,39 @@ def _load_labware_definition_data() -> LabwareDefinition: y=75.43, z=75, totalLiquidVolume=1100000, - shape="rectangular", + shape="circular", ) }, dimensions=Dimensions(yDimension=85.5, zDimension=100, xDimension=127.75), cornerOffsetFromSlot=CornerOffsetFromSlot(x=0, y=0, z=0), innerLabwareGeometry={ "welldefinition1111": InnerWellGeometry( - frusta=[ - BoundedSection( - geometry=RectangularCrossSection( - shape="rectangular", - xDimension=7.6, - yDimension=8.5, - ), + sections=[ + CuboidalFrustum( + shape="cuboidal", + topXDimension=7.6, + topYDimension=8.5, + bottomXDimension=5.6, + bottomYDimension=6.5, topHeight=45, + bottomHeight=20, ), - BoundedSection( - geometry=RectangularCrossSection( - shape="rectangular", - xDimension=5.6, - yDimension=6.5, - ), + CuboidalFrustum( + shape="cuboidal", + topXDimension=5.6, + topYDimension=6.5, + bottomXDimension=4.5, + bottomYDimension=4.0, topHeight=20, + bottomHeight=10, + ), + SphericalSegment( + shape="spherical", + radiusOfCurvature=6, + topHeight=10, + bottomHeight=0.0, ), ], - bottomShape=SphericalSegment( - shape="spherical", - radius_of_curvature=6, - depth=10, - ), ) }, brand=BrandData(brand="foo"), @@ -821,7 +827,7 @@ def _make_v8_json_protocol( ), labwareDefinitions=labware_definitions, labwareDefinitionSchemaId="opentronsLabwareSchemaV2", - commandSchemaId="opentronsCommandSchemaV8", + commandSchemaId=protocol_schema_v8.CommandSchemaId("opentronsCommandSchemaV8"), commands=commands, liquidSchemaId="opentronsLiquidSchemaV1", liquids=liquids, diff --git a/api/tests/opentrons/protocol_runner/test_legacy_command_mapper.py b/api/tests/opentrons/protocol_runner/test_legacy_command_mapper.py index ed171280d17..42c589ba7d3 100644 --- a/api/tests/opentrons/protocol_runner/test_legacy_command_mapper.py +++ b/api/tests/opentrons/protocol_runner/test_legacy_command_mapper.py @@ -3,6 +3,12 @@ from datetime import datetime from typing import cast +from opentrons.protocol_engine.state.update_types import ( + LoadPipetteUpdate, + LoadedLabwareUpdate, + PipetteConfigUpdate, + StateUpdate, +) import pytest from decoy import matchers, Decoy @@ -111,7 +117,6 @@ def test_map_after_command() -> None: assert result == [ pe_actions.SucceedCommandAction( - private_result=None, command=pe_commands.Comment.construct( id="command.COMMENT-0", key="command.COMMENT-0", @@ -235,7 +240,6 @@ def test_command_stack() -> None: command_id="command.COMMENT-1", started_at=matchers.IsA(datetime) ), pe_actions.SucceedCommandAction( - private_result=None, command=pe_commands.Comment.construct( id="command.COMMENT-0", key="command.COMMENT-0", @@ -315,7 +319,15 @@ def test_map_labware_load(minimal_labware_def: LabwareDefinition) -> None: ), notes=[], ), - private_result=None, + state_update=StateUpdate( + loaded_labware=LoadedLabwareUpdate( + labware_id="labware-0", + definition=matchers.Anything(), + offset_id="labware-offset-id-123", + new_location=DeckSlotLocation(slotName=DeckSlotName.SLOT_1), + display_name="My special labware", + ) + ), ) result_queue, result_run, result_succeed = LegacyCommandMapper().map_equipment_load( input @@ -366,8 +378,18 @@ def test_map_instrument_load(decoy: Decoy) -> None: result=pe_commands.LoadPipetteResult(pipetteId="pipette-0"), notes=[], ), - private_result=pe_commands.LoadPipettePrivateResult( - pipette_id="pipette-0", serial_number="fizzbuzz", config=pipette_config + state_update=StateUpdate( + loaded_pipette=LoadPipetteUpdate( + pipette_id="pipette-0", + mount=expected_params.mount, + pipette_name=expected_params.pipetteName, + liquid_presence_detection=expected_params.liquidPresenceDetection, + ), + pipette_config=PipetteConfigUpdate( + pipette_id="pipette-0", + serial_number="fizzbuzz", + config=pipette_config, + ), ), ) @@ -434,7 +456,6 @@ def test_map_module_load( ), notes=[], ), - private_result=None, ) [result_queue, result_run, result_succeed] = LegacyCommandMapper( @@ -499,7 +520,15 @@ def test_map_module_labware_load(minimal_labware_def: LabwareDefinition) -> None ), notes=[], ), - private_result=None, + state_update=StateUpdate( + loaded_labware=LoadedLabwareUpdate( + labware_id="labware-0", + definition=matchers.Anything(), + offset_id="labware-offset-id-123", + new_location=ModuleLocation(moduleId="module-123"), + display_name="My very special module labware", + ) + ), ) subject = LegacyCommandMapper() @@ -549,7 +578,6 @@ def test_map_pause() -> None: started_at=matchers.IsA(datetime), ), pe_actions.SucceedCommandAction( - private_result=None, command=pe_commands.WaitForResume.construct( id="command.PAUSE-0", key="command.PAUSE-0", diff --git a/api/tests/opentrons/protocol_runner/test_legacy_context_plugin.py b/api/tests/opentrons/protocol_runner/test_legacy_context_plugin.py index 620b7afa1ba..0ccc616012a 100644 --- a/api/tests/opentrons/protocol_runner/test_legacy_context_plugin.py +++ b/api/tests/opentrons/protocol_runner/test_legacy_context_plugin.py @@ -1,4 +1,5 @@ """Tests for the PythonAndLegacyRunner's LegacyContextPlugin.""" +import asyncio import pytest from anyio import to_thread from decoy import Decoy, matchers @@ -60,7 +61,7 @@ def mock_action_dispatcher(decoy: Decoy) -> pe_actions.ActionDispatcher: @pytest.fixture -def subject( +async def subject( mock_legacy_broker: LegacyBroker, mock_equipment_broker: ReadOnlyBroker[LoadInfo], mock_legacy_command_mapper: LegacyCommandMapper, @@ -69,6 +70,7 @@ def subject( ) -> LegacyContextPlugin: """Get a configured LegacyContextPlugin with its dependencies mocked out.""" plugin = LegacyContextPlugin( + engine_loop=asyncio.get_running_loop(), broker=mock_legacy_broker, equipment_broker=mock_equipment_broker, legacy_command_mapper=mock_legacy_command_mapper, @@ -161,18 +163,14 @@ async def test_command_broker_messages( decoy.when( mock_legacy_command_mapper.map_command(command=legacy_command) - ).then_return( - [pe_actions.SucceedCommandAction(engine_command, private_result=None)] - ) + ).then_return([pe_actions.SucceedCommandAction(engine_command)]) await to_thread.run_sync(handler, legacy_command) await subject.teardown() decoy.verify( - mock_action_dispatcher.dispatch( - pe_actions.SucceedCommandAction(engine_command, private_result=None) - ) + mock_action_dispatcher.dispatch(pe_actions.SucceedCommandAction(engine_command)) ) @@ -220,9 +218,7 @@ async def test_equipment_broker_messages( decoy.when( mock_legacy_command_mapper.map_equipment_load(load_info=load_info) - ).then_return( - [pe_actions.SucceedCommandAction(command=engine_command, private_result=None)] - ) + ).then_return([pe_actions.SucceedCommandAction(command=engine_command)]) await to_thread.run_sync(handler, load_info) @@ -230,6 +226,6 @@ async def test_equipment_broker_messages( decoy.verify( mock_action_dispatcher.dispatch( - pe_actions.SucceedCommandAction(command=engine_command, private_result=None) + pe_actions.SucceedCommandAction(command=engine_command) ), ) diff --git a/api/tests/opentrons/protocol_runner/test_protocol_runner.py b/api/tests/opentrons/protocol_runner/test_protocol_runner.py index cd945c33e64..15e0192175e 100644 --- a/api/tests/opentrons/protocol_runner/test_protocol_runner.py +++ b/api/tests/opentrons/protocol_runner/test_protocol_runner.py @@ -313,9 +313,16 @@ def test_resume_from_recovery( subject: AnyRunner, ) -> None: """It should call `resume_from_recovery()` on the underlying engine.""" - subject.resume_from_recovery() + subject.resume_from_recovery( + reconcile_false_positive=sentinel.reconcile_false_positive + ) - decoy.verify(protocol_engine.resume_from_recovery(), times=1) + decoy.verify( + protocol_engine.resume_from_recovery( + reconcile_false_positive=sentinel.reconcile_false_positive + ), + times=1, + ) async def test_run_json_runner( @@ -441,6 +448,7 @@ async def test_run_json_runner_stop_requested_stops_enqueuing( await run_func() +@pytest.mark.filterwarnings("ignore::decoy.warnings.RedundantVerifyWarning") @pytest.mark.parametrize( "schema_version, json_protocol", [ diff --git a/api/tests/opentrons/protocol_runner/test_run_orchestrator.py b/api/tests/opentrons/protocol_runner/test_run_orchestrator.py index 6e1c04949f8..c2cea3e0e7e 100644 --- a/api/tests/opentrons/protocol_runner/test_run_orchestrator.py +++ b/api/tests/opentrons/protocol_runner/test_run_orchestrator.py @@ -10,7 +10,7 @@ from opentrons.protocol_engine.error_recovery_policy import ErrorRecoveryPolicy from opentrons.protocol_engine.errors import RunStoppedError -from opentrons.protocol_engine.state import StateStore +from opentrons.protocol_engine.state.state import StateStore from opentrons.protocols.api_support.types import APIVersion from opentrons.protocol_engine import ProtocolEngine from opentrons.protocol_engine.types import PostRunHardwareState @@ -525,7 +525,7 @@ def get_next_to_execute() -> Generator[str, None, None]: index = index + 1 -async def test_create_error_recovery_policy( +def test_create_error_recovery_policy( decoy: Decoy, mock_protocol_engine: ProtocolEngine, live_protocol_subject: RunOrchestrator, diff --git a/api/tests/opentrons/protocol_runner/test_thread_async_queue.py b/api/tests/opentrons/protocol_runner/test_thread_async_queue.py deleted file mode 100644 index 2cf31939348..00000000000 --- a/api/tests/opentrons/protocol_runner/test_thread_async_queue.py +++ /dev/null @@ -1,200 +0,0 @@ -"""Tests for thread_async_queue.""" - -from __future__ import annotations - -import asyncio -from concurrent.futures import ThreadPoolExecutor -from itertools import chain -from typing import List, NamedTuple - -import pytest - -from opentrons.protocol_runner.thread_async_queue import ( - ThreadAsyncQueue, - QueueClosed, -) - - -def test_basic_single_threaded_behavior() -> None: - """Test basic queue behavior in a single thread.""" - subject = ThreadAsyncQueue[int]() - - with subject: - subject.put(1) - subject.put(2) - subject.put(3) - - # Putting isn't allowed after closing. - with pytest.raises(QueueClosed): - subject.put(4) - with pytest.raises(QueueClosed): - subject.put(5) - - # Closing isn't allowed after closing. - with pytest.raises(QueueClosed): - subject.done_putting() - - # Values are retrieved in order. - assert [subject.get(), subject.get(), subject.get()] == [1, 2, 3] - - # After retrieving all values, further retrievals raise. - with pytest.raises(QueueClosed): - subject.get() - with pytest.raises(QueueClosed): - # If closing were naively implemented as a sentinel value being inserted - # into the queue, it might be that the first get() after the close - # correctly raises but the second get() doesn't. - subject.get() - - -def test_multi_thread_producer_consumer() -> None: - """Stochastically smoke-test thread safety. - - Use the queue to pass values between threads - in a multi-producer, multi-consumer setup. - Verify that all the values make it through in the correct order. - """ - num_producers = 3 - num_consumers = 3 - - producer_ids = list(range(num_producers)) - - # The values that each producer will put into the queue. - # Anecdotally, threads interleave meaningfully with at least 10000 values. - values_per_producer = list(range(30000)) - - all_expected_values = [ - _ProducedValue(producer_id=p, value=v) - for p in producer_ids - for v in values_per_producer - ] - - subject = ThreadAsyncQueue[_ProducedValue]() - - # Run producers concurrently with consumers. - with ThreadPoolExecutor(max_workers=num_producers + num_consumers) as executor: - # `with subject` needs to be inside `with ThreadPoolExecutor` - # to avoid deadlocks in case something in here raises. - # Consumers need to see the queue closed eventually to terminate, - # and `with ThreadPoolExecutor` will wait until all threads are terminated - # before exiting. - with subject: - producers = [ - executor.submit( - _produce, - queue=subject, - values=values_per_producer, - producer_id=producer_id, - ) - for producer_id in producer_ids - ] - consumers = [ - executor.submit(_consume, queue=subject) for i in range(num_consumers) - ] - - # Ensure all producers are done before we exit the `with subject` block - # and close off the queue to further submissions. - for c in producers: - c.result() - - consumer_results = [consumer.result() for consumer in consumers] - all_values = list(chain(*consumer_results)) - - # Assert that the total set of consumed values is as expected: - # No duplicates, no extras, and nothing missing. - assert sorted(all_values) == sorted(all_expected_values) - - def assert_consumer_result_correctly_ordered( - consumer_result: List[_ProducedValue], - ) -> None: - # Assert that the consumer got values in the order the producer provided them. - # Allow values from different producers to be interleaved, - # and tolerate skipped values (assume they were given to a different consumer). - - # [[All consumed from producer 0], [All consumed from producer 1], etc.] - consumed_values_per_producer = [ - [pv for pv in consumer_result if pv.producer_id == producer_id] - for producer_id in producer_ids - ] - for values_from_single_producer in consumed_values_per_producer: - assert values_from_single_producer == sorted(values_from_single_producer) - - for consumer_result in consumer_results: - assert_consumer_result_correctly_ordered(consumer_result) - - -async def test_async() -> None: - """Smoke-test async support. - - Use the queue to pass values - from a single async producer to a single async consumer, - running concurrently in the same event loop. - - This verifies two things: - - 1. That async retrieval returns basically the expected values. - 2. That async retrieval keeps the event loop free while waiting. - If it didn't, this test would reveal the problem by deadlocking. - - We trust that more complicated multi-producer/multi-consumer interactions - are covered by the non-async tests. - """ - expected_values = list(range(1000)) - - subject = ThreadAsyncQueue[_ProducedValue]() - - consumer = asyncio.create_task(_consume_async(queue=subject)) - try: - with subject: - await _produce_async(queue=subject, values=expected_values, producer_id=0) - finally: - consumed = await consumer - - assert consumed == [_ProducedValue(producer_id=0, value=v) for v in expected_values] - - -class _ProducedValue(NamedTuple): - producer_id: int - value: int - - -def _produce( - queue: ThreadAsyncQueue[_ProducedValue], - values: List[int], - producer_id: int, -) -> None: - """Put values in the queue, tagged with an ID representing this producer.""" - for v in values: - queue.put(_ProducedValue(producer_id=producer_id, value=v)) - - -def _consume(queue: ThreadAsyncQueue[_ProducedValue]) -> List[_ProducedValue]: - """Consume values from the queue indiscriminately until it's closed. - - Return everything consumed, in the order that this function consumed it. - """ - result = [] - for value in queue.get_until_closed(): - result.append(value) - return result - - -async def _produce_async( - queue: ThreadAsyncQueue[_ProducedValue], - values: List[int], - producer_id: int, -) -> None: - """Like `_produce()`, except yield to the event loop after each insertion.""" - for value in values: - queue.put(_ProducedValue(producer_id=producer_id, value=value)) - await asyncio.sleep(0) - - -async def _consume_async( - queue: ThreadAsyncQueue[_ProducedValue], -) -> List[_ProducedValue]: - """Like _consume()`, except yield to the event loop while waiting.""" - result = [] - async for value in queue.get_async_until_closed(): - result.append(value) - return result diff --git a/api/tests/opentrons/protocols/advanced_control/transfers/__init__.py b/api/tests/opentrons/protocols/advanced_control/transfers/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/api/tests/opentrons/protocols/advanced_control/transfers/test_common_functions.py b/api/tests/opentrons/protocols/advanced_control/transfers/test_common_functions.py new file mode 100644 index 00000000000..644c2b7094f --- /dev/null +++ b/api/tests/opentrons/protocols/advanced_control/transfers/test_common_functions.py @@ -0,0 +1,77 @@ +"""Test the common utility functions used in transfers.""" +import pytest +from contextlib import nullcontext as does_not_raise +from typing import ContextManager, Any, Iterable, List, Tuple + +from opentrons.protocols.advanced_control.transfers.common import ( + Target, + check_valid_volume_parameters, + expand_for_volume_constraints, +) + + +@pytest.mark.parametrize( + argnames=["disposal_volume", "air_gap", "max_volume", "expected_raise"], + argvalues=[ + (9.9, 9.9, 10, pytest.raises(ValueError, match="The sum of")), + (9.9, 10, 10, pytest.raises(ValueError, match="The air gap must be less than")), + ( + 10, + 9.9, + 10, + pytest.raises(ValueError, match="The disposal volume must be less than"), + ), + (9.9, 9.9, 20, does_not_raise()), + ], +) +def test_check_valid_volume_parameters( + disposal_volume: float, + air_gap: float, + max_volume: float, + expected_raise: ContextManager[Any], +) -> None: + """It should raise the expected error for invalid parameters.""" + with expected_raise: + check_valid_volume_parameters( + disposal_volume=disposal_volume, + air_gap=air_gap, + max_volume=max_volume, + ) + + +@pytest.mark.parametrize( + argnames=["volumes", "targets", "max_volume", "expanded_list_result"], + argvalues=[ + ( + [60, 70, 75], + [("a", "b"), ("c", "d"), ("e", "f")], + 20, + [ + (20, ("a", "b")), + (20, ("a", "b")), + (20, ("a", "b")), + (20, ("c", "d")), + (20, ("c", "d")), + (15, ("c", "d")), + (15, ("c", "d")), + (20, ("e", "f")), + (20, ("e", "f")), + (17.5, ("e", "f")), + (17.5, ("e", "f")), + ], + ), + ], +) +def test_expand_for_volume_constraints( + volumes: Iterable[float], + targets: Iterable[Target], + max_volume: float, + expanded_list_result: List[Tuple[float, Target]], +) -> None: + """It should raise the expected error for invalid parameters.""" + result = expand_for_volume_constraints( + volumes=volumes, + targets=targets, + max_volume=max_volume, + ) + assert list(result) == expanded_list_result diff --git a/api/tests/opentrons/protocols/advanced_control/test_transfers.py b/api/tests/opentrons/protocols/advanced_control/transfers/test_transfers.py similarity index 99% rename from api/tests/opentrons/protocols/advanced_control/test_transfers.py rename to api/tests/opentrons/protocols/advanced_control/transfers/test_transfers.py index 7fc02d36aab..d54ad38a62b 100644 --- a/api/tests/opentrons/protocols/advanced_control/test_transfers.py +++ b/api/tests/opentrons/protocols/advanced_control/transfers/test_transfers.py @@ -3,7 +3,8 @@ from typing import TypedDict from opentrons.types import Mount, TransferTipPolicy -from opentrons.protocols.advanced_control import transfers as tx +from opentrons.protocols.advanced_control.transfers import transfer as tx +from opentrons.protocols.advanced_control.common import MixStrategy from opentrons.protocols.api_support.types import APIVersion from opentrons.hardware_control import ThreadManagedHardware from opentrons.protocol_api.protocol_context import ProtocolContext @@ -633,7 +634,7 @@ def test_touchtip_mix(_instr_labware: InstrLabware) -> None: transfer=options.transfer._replace( new_tip=TransferTipPolicy.NEVER, touch_tip_strategy=tx.TouchTipStrategy.ALWAYS, - mix_strategy=tx.MixStrategy.AFTER, + mix_strategy=MixStrategy.AFTER, ) ) @@ -759,7 +760,7 @@ def test_all_options(_instr_labware: InstrLabware) -> None: new_tip=TransferTipPolicy.ONCE, drop_tip_strategy=tx.DropTipStrategy.RETURN, touch_tip_strategy=tx.TouchTipStrategy.ALWAYS, - mix_strategy=tx.MixStrategy.AFTER, + mix_strategy=MixStrategy.AFTER, ), pick_up_tip=options.pick_up_tip._replace(presses=4, increment=2), touch_tip=options.touch_tip._replace(speed=1.6), diff --git a/api/tests/opentrons/protocols/geometry/test_frustum_helpers.py b/api/tests/opentrons/protocols/geometry/test_frustum_helpers.py new file mode 100644 index 00000000000..0b8d3429527 --- /dev/null +++ b/api/tests/opentrons/protocols/geometry/test_frustum_helpers.py @@ -0,0 +1,340 @@ +import pytest +from math import pi, isclose +from typing import Any, List + +from opentrons_shared_data.labware.labware_definition import ( + ConicalFrustum, + CuboidalFrustum, + SphericalSegment, +) +from opentrons.protocol_engine.state.frustum_helpers import ( + _cross_section_area_rectangular, + _cross_section_area_circular, + _reject_unacceptable_heights, + _circular_frustum_polynomial_roots, + _rectangular_frustum_polynomial_roots, + _volume_from_height_rectangular, + _volume_from_height_circular, + _volume_from_height_spherical, + _height_from_volume_circular, + _height_from_volume_rectangular, + _height_from_volume_spherical, + height_at_volume_within_section, + _get_segment_capacity, +) +from opentrons.protocol_engine.errors.exceptions import InvalidLiquidHeightFound + + +def fake_frusta() -> List[List[Any]]: + """A bunch of weird fake well shapes.""" + frusta = [] + frusta.append( + [ + CuboidalFrustum( + shape="cuboidal", + topXDimension=9.0, + topYDimension=10.0, + bottomXDimension=8.0, + bottomYDimension=9.0, + topHeight=10.0, + bottomHeight=5.0, + ), + CuboidalFrustum( + shape="cuboidal", + topXDimension=8.0, + topYDimension=9.0, + bottomXDimension=15.0, + bottomYDimension=18.0, + topHeight=5.0, + bottomHeight=1.0, + ), + ConicalFrustum( + shape="conical", + topDiameter=23.0, + bottomDiameter=3.0, + topHeight=2.0, + bottomHeight=1.0, + ), + SphericalSegment( + shape="spherical", + radiusOfCurvature=4.0, + topHeight=1.0, + bottomHeight=0.0, + ), + ] + ) + frusta.append( + [ + CuboidalFrustum( + shape="cuboidal", + topXDimension=8.0, + topYDimension=70.0, + bottomXDimension=7.0, + bottomYDimension=75.0, + topHeight=3.5, + bottomHeight=2.0, + ), + CuboidalFrustum( + shape="cuboidal", + topXDimension=8.0, + topYDimension=80.0, + bottomXDimension=8.0, + bottomYDimension=90.0, + topHeight=1.0, + bottomHeight=0.0, + ), + ] + ) + frusta.append( + [ + ConicalFrustum( + shape="conical", + topDiameter=23.0, + bottomDiameter=11.5, + topHeight=7.5, + bottomHeight=5.0, + ), + ConicalFrustum( + shape="conical", + topDiameter=11.5, + bottomDiameter=23.0, + topHeight=5.0, + bottomHeight=2.5, + ), + ConicalFrustum( + shape="conical", + topDiameter=23.0, + bottomDiameter=11.5, + topHeight=2.5, + bottomHeight=0.0, + ), + ] + ) + frusta.append( + [ + ConicalFrustum( + shape="conical", + topDiameter=4.0, + bottomDiameter=5.0, + topHeight=3.0, + bottomHeight=2.0, + ), + SphericalSegment( + shape="spherical", + radiusOfCurvature=3.5, + topHeight=2.0, + bottomHeight=0.0, + ), + ] + ) + frusta.append( + [ + SphericalSegment( + shape="spherical", + radiusOfCurvature=4.0, + topHeight=3.0, + bottomHeight=0.0, + ) + ] + ) + frusta.append( + [ + CuboidalFrustum( + shape="cuboidal", + topXDimension=27.0, + topYDimension=36.0, + bottomXDimension=36.0, + bottomYDimension=26.0, + topHeight=3.5, + bottomHeight=1.5, + ), + SphericalSegment( + shape="spherical", + radiusOfCurvature=4.0, + topHeight=1.5, + bottomHeight=0.0, + ), + ] + ) + return frusta + + +@pytest.mark.parametrize( + ["max_height", "potential_heights", "expected_heights"], + [ + (34, [complex(4, 5), complex(5, 0), 35, 34, 33, 10, 0], [5, 34, 33, 10, 0]), + (2934, [complex(4, 5), complex(5, 0)], [5]), + (100, [-99, -1, complex(99.99, 0), 101], [99.99]), + (2, [0, -1, complex(-1.5, 0)], [0]), + (8, [complex(7, 1), -0.01], []), + ], +) +def test_reject_unacceptable_heights( + max_height: float, potential_heights: List[Any], expected_heights: List[float] +) -> None: + """Make sure we reject all mathematical solutions that are physically not possible.""" + if len(expected_heights) != 1: + with pytest.raises(InvalidLiquidHeightFound): + _reject_unacceptable_heights( + max_height=max_height, potential_heights=potential_heights + ) + else: + found_heights = _reject_unacceptable_heights( + max_height=max_height, potential_heights=potential_heights + ) + assert found_heights == expected_heights[0] + + +@pytest.mark.parametrize("diameter", [2, 5, 8, 356, 1000]) +def test_cross_section_area_circular(diameter: float) -> None: + """Test circular area calculation.""" + expected_area = pi * (diameter / 2) ** 2 + assert _cross_section_area_circular(diameter) == expected_area + + +@pytest.mark.parametrize( + ["x_dimension", "y_dimension"], [(1, 38402), (234, 983), (94857, 40), (234, 999)] +) +def test_cross_section_area_rectangular(x_dimension: float, y_dimension: float) -> None: + """Test rectangular area calculation.""" + expected_area = x_dimension * y_dimension + assert ( + _cross_section_area_rectangular( + x_dimension=x_dimension, y_dimension=y_dimension + ) + == expected_area + ) + + +@pytest.mark.parametrize("well", fake_frusta()) +def test_volume_and_height_circular(well: List[Any]) -> None: + """Test both volume and height calculations for circular frusta.""" + if well[-1].shape == "spherical": + return + total_height = well[0].topHeight + for segment in well: + if segment.shape == "conical": + top_radius = segment.topDiameter / 2 + bottom_radius = segment.bottomDiameter / 2 + a = pi * ((top_radius - bottom_radius) ** 2) / (3 * total_height**2) + b = pi * bottom_radius * (top_radius - bottom_radius) / total_height + c = pi * bottom_radius**2 + assert _circular_frustum_polynomial_roots( + top_radius=top_radius, + bottom_radius=bottom_radius, + total_frustum_height=total_height, + ) == (a, b, c) + # test volume within a bunch of arbitrary heights + for target_height in range(round(total_height)): + expected_volume = ( + a * (target_height**3) + + b * (target_height**2) + + c * target_height + ) + found_volume = _volume_from_height_circular( + target_height=target_height, + total_frustum_height=total_height, + bottom_radius=bottom_radius, + top_radius=top_radius, + ) + assert found_volume == expected_volume + # test going backwards to get height back + found_height = _height_from_volume_circular( + volume=found_volume, + total_frustum_height=total_height, + bottom_radius=bottom_radius, + top_radius=top_radius, + ) + assert isclose(found_height, target_height) + + +@pytest.mark.parametrize("well", fake_frusta()) +def test_volume_and_height_rectangular(well: List[Any]) -> None: + """Test both volume and height calculations for rectangular frusta.""" + if well[-1].shape == "spherical": + return + total_height = well[0].topHeight + for segment in well: + if segment.shape == "cuboidal": + top_length = segment.topYDimension + top_width = segment.topXDimension + bottom_length = segment.bottomYDimension + bottom_width = segment.bottomXDimension + a = ( + (top_length - bottom_length) + * (top_width - bottom_width) + / (3 * total_height**2) + ) + b = ( + (bottom_length * (top_width - bottom_width)) + + (bottom_width * (top_length - bottom_length)) + ) / (2 * total_height) + c = bottom_length * bottom_width + assert _rectangular_frustum_polynomial_roots( + top_length=top_length, + bottom_length=bottom_length, + top_width=top_width, + bottom_width=bottom_width, + total_frustum_height=total_height, + ) == (a, b, c) + # test volume within a bunch of arbitrary heights + for target_height in range(round(total_height)): + expected_volume = ( + a * (target_height**3) + + b * (target_height**2) + + c * target_height + ) + found_volume = _volume_from_height_rectangular( + target_height=target_height, + total_frustum_height=total_height, + bottom_length=bottom_length, + bottom_width=bottom_width, + top_length=top_length, + top_width=top_width, + ) + assert found_volume == expected_volume + # test going backwards to get height back + found_height = _height_from_volume_rectangular( + volume=found_volume, + total_frustum_height=total_height, + bottom_length=bottom_length, + bottom_width=bottom_width, + top_length=top_length, + top_width=top_width, + ) + assert isclose(found_height, target_height) + + +@pytest.mark.parametrize("well", fake_frusta()) +def test_volume_and_height_spherical(well: List[Any]) -> None: + """Test both volume and height calculations for spherical segments.""" + if well[0].shape == "spherical": + for target_height in range(round(well[0].topHeight)): + expected_volume = ( + (1 / 3) + * pi + * (target_height**2) + * (3 * well[0].radiusOfCurvature - target_height) + ) + found_volume = _volume_from_height_spherical( + target_height=target_height, + radius_of_curvature=well[0].radiusOfCurvature, + ) + assert found_volume == expected_volume + found_height = _height_from_volume_spherical( + volume=found_volume, + radius_of_curvature=well[0].radiusOfCurvature, + total_frustum_height=well[0].topHeight, + ) + assert isclose(found_height, target_height) + + +@pytest.mark.parametrize("well", fake_frusta()) +def test_height_at_volume_within_section(well: List[Any]) -> None: + """Test that finding the height when volume ~= capacity works.""" + for segment in well: + segment_height = segment.topHeight - segment.bottomHeight + height = height_at_volume_within_section( + segment, _get_segment_capacity(segment), segment_height + ) + assert isclose(height, segment_height) diff --git a/api/tests/opentrons/protocols/parameters/test_csv_parameter_interface.py b/api/tests/opentrons/protocols/parameters/test_csv_parameter_interface.py index 81bffd0028e..f0bf7d89f32 100644 --- a/api/tests/opentrons/protocols/parameters/test_csv_parameter_interface.py +++ b/api/tests/opentrons/protocols/parameters/test_csv_parameter_interface.py @@ -1,3 +1,4 @@ +from typing import List, Tuple import pytest import platform from decoy import Decoy @@ -44,6 +45,42 @@ def csv_file_different_delimiter() -> bytes: return b"x:y:z\na,:1,:2\nb,:3,:4\nc,:5,:6" +@pytest.fixture +def csv_file_basic_trailing_empty() -> Tuple[bytes, List[List[str]]]: + """A basic CSV file with quotes around strings and a trailing newline.""" + return ( + b'"x","y","z"\n"a",1,2\n"b",3,4\n"c",5,6\n', + [["x", "y", "z"], ["a", "1", "2"], ["b", "3", "4"], ["c", "5", "6"]], + ) + + +@pytest.fixture +def csv_file_basic_three_trailing_empty() -> Tuple[bytes, List[List[str]]]: + """A basic CSV file with quotes around strings and three trailing newlines.""" + return ( + b'"x","y","z"\n"a",1,2\n"b",3,4\n"c",5,6\n\n\n', + [["x", "y", "z"], ["a", "1", "2"], ["b", "3", "4"], ["c", "5", "6"]], + ) + + +@pytest.fixture +def csv_file_empty_row_and_trailing_empty() -> Tuple[bytes, List[List[str]]]: + """A basic CSV file with quotes around strings, an empty row, and a trailing newline.""" + return ( + b'"x","y","z"\n\n"b",3,4\n"c",5,6\n', + [["x", "y", "z"], [], ["b", "3", "4"], ["c", "5", "6"]], + ) + + +@pytest.fixture +def csv_file_windows_empty_row_trailing_empty() -> Tuple[bytes, List[List[str]]]: + """A basic CSV file with quotes around strings, Windows-style newlines, an empty row, and a trailing newline.""" + return ( + b'"x","y","z"\r\n\r\n"b",3,4\r\n"c",5,6\r\n', + [["x", "y", "z"], [], ["b", "3", "4"], ["c", "5", "6"]], + ) + + def test_csv_parameter( decoy: Decoy, api_version: APIVersion, csv_file_basic: bytes ) -> None: @@ -125,3 +162,35 @@ def test_csv_parameter_dont_detect_dialect( assert rows[0] == ["x", ' "y"', ' "z"'] assert rows[1] == ["a", " 1", " 2"] + + +@pytest.mark.parametrize( + "csv_file_fixture", + [ + "csv_file_basic_trailing_empty", + "csv_file_basic_three_trailing_empty", + "csv_file_empty_row_and_trailing_empty", + "csv_file_windows_empty_row_trailing_empty", + ], +) +def test_csv_parameter_trailing_empties( + decoy: Decoy, + api_version: APIVersion, + request: pytest.FixtureRequest, + csv_file_fixture: str, +) -> None: + """It should load the rows as all strings. Empty rows are allowed in the middle of the data but all trailing empty rows are removed.""" + # Get the fixture value + csv_file: bytes + expected_output: List[List[str]] + csv_file, expected_output = request.getfixturevalue(csv_file_fixture) + + subject = CSVParameter(csv_file, api_version) + parsed_csv = subject.parse_as_csv() + + assert ( + parsed_csv == expected_output + ), f"Expected {expected_output}, but got {parsed_csv}" + assert len(parsed_csv) == len( + expected_output + ), f"Expected {len(expected_output)} rows, but got {len(parsed_csv)}" diff --git a/api/tests/opentrons/test_types.py b/api/tests/opentrons/test_types.py index 6cd93dce125..77249fa0492 100644 --- a/api/tests/opentrons/test_types.py +++ b/api/tests/opentrons/test_types.py @@ -29,7 +29,7 @@ def test_location_repr_labware(min_lw: Labware) -> None: loc = Location(point=Point(x=1.1, y=2.1, z=3.5), labware=min_lw) assert ( f"{loc}" - == "Location(point=Point(x=1.1, y=2.1, z=3.5), labware=minimal labware on deck)" + == "Location(point=Point(x=1.1, y=2.1, z=3.5), labware=minimal labware on deck, is_meniscus=False)" ) @@ -38,14 +38,17 @@ def test_location_repr_well(min_lw: Labware) -> None: loc = Location(point=Point(x=1, y=2, z=3), labware=min_lw.wells()[0]) assert ( f"{loc}" - == "Location(point=Point(x=1, y=2, z=3), labware=A1 of minimal labware on deck)" + == "Location(point=Point(x=1, y=2, z=3), labware=A1 of minimal labware on deck, is_meniscus=False)" ) def test_location_repr_slot() -> None: """It should represent labware as a slot""" loc = Location(point=Point(x=-1, y=2, z=3), labware="1") - assert f"{loc}" == "Location(point=Point(x=-1, y=2, z=3), labware=1)" + assert ( + f"{loc}" + == "Location(point=Point(x=-1, y=2, z=3), labware=1, is_meniscus=False)" + ) @pytest.mark.parametrize( diff --git a/app-shell-odd/Makefile b/app-shell-odd/Makefile index 543ed2de95f..f94cab4a611 100644 --- a/app-shell-odd/Makefile +++ b/app-shell-odd/Makefile @@ -65,12 +65,14 @@ deps: .PHONY: package-deps package-deps: clean lib deps +# Note: keep the push dep separate from the dist target so it doesn't accidentally +# do a js dist when we want to only build electron .PHONY: dist-ot3 -dist-ot3: package-deps +dist-ot3: clean lib NO_USB_DETECTION=true OT_APP_DEPLOY_BUCKET=opentrons-app OT_APP_DEPLOY_FOLDER=builds OPENTRONS_PROJECT=$(OPENTRONS_PROJECT) $(builder) --linux --arm64 .PHONY: push-ot3 -push-ot3: dist-ot3 +push-ot3: deps dist-ot3 tar -zcvf opentrons-robot-app.tar.gz -C ./dist/linux-arm64-unpacked/ ./ scp $(if $(ssh_key),-i $(ssh_key)) $(ssh_opts) -r ./opentrons-robot-app.tar.gz root@$(host): ssh $(if $(ssh_key),-i $(ssh_key)) $(ssh_opts) root@$(host) "mount -o remount,rw / && systemctl stop opentrons-robot-app && rm -rf /opt/opentrons-app && mkdir -p /opt/opentrons-app" diff --git a/app-shell-odd/electron-builder.config.js b/app-shell-odd/electron-builder.config.js index d5cd4ac7eea..10faa01b3b0 100644 --- a/app-shell-odd/electron-builder.config.js +++ b/app-shell-odd/electron-builder.config.js @@ -2,7 +2,7 @@ module.exports = { appId: 'com.opentrons.odd', - electronVersion: '27.0.0', + electronVersion: '33.2.1', npmRebuild: false, files: [ '**/*', diff --git a/app-shell-odd/package.json b/app-shell-odd/package.json index e080060ca7c..9835b30df79 100644 --- a/app-shell-odd/package.json +++ b/app-shell-odd/package.json @@ -8,7 +8,7 @@ "types": "lib/main.d.ts", "scripts": { "start": "make dev", - "rebuild": "electron-rebuild" + "rebuild": "electron-rebuild --force-abi=3.71.0" }, "repository": { "type": "git", @@ -24,9 +24,7 @@ }, "homepage": "https://github.com/Opentrons/opentrons", "workspaces": { - "nohoist": [ - "**" - ] + "nohoist": ["**"] }, "devDependencies": { "@opentrons/app": "link:../app" @@ -44,7 +42,6 @@ "dateformat": "3.0.3", "electron-devtools-installer": "3.2.0", "electron-store": "5.1.1", - "electron-updater": "4.1.2", "execa": "4.0.0", "form-data": "2.5.0", "fs-extra": "10.0.0", @@ -55,10 +52,10 @@ "node-fetch": "2.6.7", "node-stream-zip": "1.8.2", "pump": "3.0.0", - "semver": "5.5.0", + "semver": "5.7.2", "tempy": "1.0.1", "uuid": "3.2.1", - "winston": "3.1.0", + "winston": "3.15.0", "yargs-parser": "13.1.2" } } diff --git a/app-shell-odd/src/__tests__/http.test.ts b/app-shell-odd/src/__tests__/http.test.ts index 7b2c72578c0..c7ea4443a96 100644 --- a/app-shell-odd/src/__tests__/http.test.ts +++ b/app-shell-odd/src/__tests__/http.test.ts @@ -9,6 +9,7 @@ import type { Request, Response } from 'node-fetch' vi.mock('../config') vi.mock('node-fetch') +vi.mock('../log') describe('app-shell main http module', () => { beforeEach(() => { diff --git a/app-shell-odd/src/__tests__/update.test.ts b/app-shell-odd/src/__tests__/update.test.ts deleted file mode 100644 index 26adb67684b..00000000000 --- a/app-shell-odd/src/__tests__/update.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -// app-shell self-update tests -import { when } from 'vitest-when' -import { describe, it, vi, beforeEach, afterEach, expect } from 'vitest' -import * as http from '../http' -import { registerUpdate, FLEX_MANIFEST_URL } from '../update' -import * as Cfg from '../config' - -import type { Dispatch } from '../types' - -vi.unmock('electron-updater') -vi.mock('electron-updater') -vi.mock('../log') -vi.mock('../config') -vi.mock('../http') -vi.mock('fs-extra') - -describe('update', () => { - let dispatch: Dispatch - let handleAction: Dispatch - - beforeEach(() => { - dispatch = vi.fn() - handleAction = registerUpdate(dispatch) - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - it('handles shell:CHECK_UPDATE with available update', () => { - when(vi.mocked(Cfg.getConfig)) - // @ts-expect-error getConfig mock not recognizing correct type overload - .calledWith('update') - .thenReturn({ - channel: 'latest', - } as any) - - when(vi.mocked(http.fetchJson)) - .calledWith(FLEX_MANIFEST_URL) - .thenResolve({ production: { '5.0.0': {}, '6.0.0': {} } }) - handleAction({ type: 'shell:CHECK_UPDATE', meta: { shell: true } }) - - expect(vi.mocked(Cfg.getConfig)).toHaveBeenCalledWith('update') - - expect(vi.mocked(http.fetchJson)).toHaveBeenCalledWith(FLEX_MANIFEST_URL) - }) -}) diff --git a/app-shell-odd/src/actions.ts b/app-shell-odd/src/actions.ts index 588dc88b3e4..bb7c0450210 100644 --- a/app-shell-odd/src/actions.ts +++ b/app-shell-odd/src/actions.ts @@ -119,6 +119,7 @@ import type { export const configInitialized = (config: Config): ConfigInitializedAction => ({ type: CONFIG_INITIALIZED, payload: { config }, + meta: { shell: true }, }) // config value has been updated @@ -128,6 +129,7 @@ export const configValueUpdated = ( ): ConfigValueUpdatedAction => ({ type: VALUE_UPDATED, payload: { path, value }, + meta: { shell: true }, }) export const customLabwareList = ( diff --git a/app-shell-odd/src/config/__fixtures__/index.ts b/app-shell-odd/src/config/__fixtures__/index.ts index d670234ebbc..7f9a48dc02c 100644 --- a/app-shell-odd/src/config/__fixtures__/index.ts +++ b/app-shell-odd/src/config/__fixtures__/index.ts @@ -12,6 +12,7 @@ import type { ConfigV22, ConfigV23, ConfigV24, + ConfigV25, } from '@opentrons/app/src/redux/config/types' const PKG_VERSION: string = _PKG_VERSION_ @@ -171,3 +172,12 @@ export const MOCK_CONFIG_V24: ConfigV24 = { userId: 'MOCK_UUIDv4', }, } + +export const MOCK_CONFIG_V25: ConfigV25 = { + ...MOCK_CONFIG_V24, + version: 25, + language: { + appLanguage: null, + systemLanguage: null, + }, +} diff --git a/app-shell-odd/src/config/__tests__/migrate.test.ts b/app-shell-odd/src/config/__tests__/migrate.test.ts index dcc8eb03708..7ea91ee8d53 100644 --- a/app-shell-odd/src/config/__tests__/migrate.test.ts +++ b/app-shell-odd/src/config/__tests__/migrate.test.ts @@ -16,13 +16,14 @@ import { MOCK_CONFIG_V22, MOCK_CONFIG_V23, MOCK_CONFIG_V24, + MOCK_CONFIG_V25, } from '../__fixtures__' import { migrate } from '../migrate' vi.mock('uuid/v4') -const NEWEST_VERSION = 24 -const NEWEST_MOCK_CONFIG = MOCK_CONFIG_V24 +const NEWEST_VERSION = 25 +const NEWEST_MOCK_CONFIG = MOCK_CONFIG_V25 describe('config migration', () => { beforeEach(() => { @@ -121,10 +122,17 @@ describe('config migration', () => { expect(result.version).toBe(NEWEST_VERSION) expect(result).toEqual(NEWEST_MOCK_CONFIG) }) - it('should keep version 24', () => { + it('should migrate version 24 to latest', () => { const v24Config = MOCK_CONFIG_V24 const result = migrate(v24Config) + expect(result.version).toBe(NEWEST_VERSION) + expect(result).toEqual(NEWEST_MOCK_CONFIG) + }) + it('should keep version 25', () => { + const v25Config = MOCK_CONFIG_V25 + const result = migrate(v25Config) + expect(result.version).toBe(NEWEST_VERSION) expect(result).toEqual(NEWEST_MOCK_CONFIG) }) diff --git a/app-shell-odd/src/config/index.ts b/app-shell-odd/src/config/index.ts index df8e0cf317d..a67655976d9 100644 --- a/app-shell-odd/src/config/index.ts +++ b/app-shell-odd/src/config/index.ts @@ -5,7 +5,6 @@ import get from 'lodash/get' import forEach from 'lodash/forEach' import mergeOptions from 'merge-options' import yargsParser from 'yargs-parser' - import { UI_INITIALIZED } from '../constants' import * as Cfg from '../constants' import { configInitialized, configValueUpdated } from '../actions' @@ -13,6 +12,7 @@ import systemd from '../systemd' import { createLogger } from '../log' import { DEFAULTS_V12, migrate } from './migrate' import { shouldUpdate, getNextValue } from './update' +import { setUserDataPath } from '../early' import type { ConfigV12, @@ -24,8 +24,6 @@ import type { Config, Overrides } from './types' export * from './types' -export const ODD_DIR = '/data/ODD' - // make sure all arguments are included in production const argv = process.argv0.endsWith('defaultApp') ? process.argv.slice(2) @@ -48,8 +46,7 @@ const store = (): Store => { // perform store migration if loading for the first time _store = (new Store({ defaults: DEFAULTS_V12, - // dont overwrite config dir if in dev mode because it causes issues - ...(process.env.NODE_ENV === 'production' && { cwd: ODD_DIR }), + cwd: setUserDataPath(), }) as unknown) as Store _store.store = migrate((_store.store as unknown) as ConfigV12) } @@ -66,7 +63,14 @@ const log = (): Logger => _log ?? (_log = createLogger('config')) export function registerConfig(dispatch: Dispatch): (action: Action) => void { return function handleIncomingAction(action: Action) { if (action.type === UI_INITIALIZED) { + log().info('initializing configuration') dispatch(configInitialized(getFullConfig())) + log().info( + `flow route: ${ + getConfig('onDeviceDisplaySettings').unfinishedUnboxingFlowRoute + }` + ) + log().info('configuration initialized') } else if ( action.type === Cfg.UPDATE_VALUE || action.type === Cfg.RESET_VALUE || @@ -120,8 +124,8 @@ export function getOverrides(path?: string): unknown { return path != null ? get(overrides(), path) : overrides() } -export function getConfig

(path: P): Config[P] export function getConfig(): Config +export function getConfig

(path: P): Config[P] export function getConfig(path?: any): any { const result = store().get(path) const over = getOverrides(path as string | undefined) diff --git a/app-shell-odd/src/config/migrate.ts b/app-shell-odd/src/config/migrate.ts index d1e9103d430..b6977fbf489 100644 --- a/app-shell-odd/src/config/migrate.ts +++ b/app-shell-odd/src/config/migrate.ts @@ -17,13 +17,14 @@ import type { ConfigV22, ConfigV23, ConfigV24, + ConfigV25, } from '@opentrons/app/src/redux/config/types' // format // base config v12 defaults // any default values for later config versions are specified in the migration // functions for those version below -const CONFIG_VERSION_LATEST = 23 // update this after each config version bump +const CONFIG_VERSION_LATEST = 25 // update this after each config version bump const PKG_VERSION: string = _PKG_VERSION_ export const DEFAULTS_V12: ConfigV12 = { @@ -226,6 +227,18 @@ const toVersion24 = (prevConfig: ConfigV23): ConfigV24 => { } } +const toVersion25 = (prevConfig: ConfigV24): ConfigV25 => { + const nextConfig = { + ...prevConfig, + version: 25 as const, + language: { + appLanguage: null, + systemLanguage: null, + }, + } + return nextConfig +} + const MIGRATIONS: [ (prevConfig: ConfigV12) => ConfigV13, (prevConfig: ConfigV13) => ConfigV14, @@ -238,7 +251,8 @@ const MIGRATIONS: [ (prevConfig: ConfigV20) => ConfigV21, (prevConfig: ConfigV21) => ConfigV22, (prevConfig: ConfigV22) => ConfigV23, - (prevConfig: ConfigV23) => ConfigV24 + (prevConfig: ConfigV23) => ConfigV24, + (prevConfig: ConfigV24) => ConfigV25 ] = [ toVersion13, toVersion14, @@ -252,6 +266,7 @@ const MIGRATIONS: [ toVersion22, toVersion23, toVersion24, + toVersion25, ] export const DEFAULTS: Config = migrate(DEFAULTS_V12) @@ -271,6 +286,7 @@ export function migrate( | ConfigV22 | ConfigV23 | ConfigV24 + | ConfigV25 ): Config { let result = prevConfig // loop through the migrations, skipping any migrations that are unnecessary diff --git a/app-shell-odd/src/constants.ts b/app-shell-odd/src/constants.ts index a78e9274ae0..8b92e639cf6 100644 --- a/app-shell-odd/src/constants.ts +++ b/app-shell-odd/src/constants.ts @@ -257,3 +257,5 @@ export const FAILURE_STATUSES = { } as const export const SEND_FILE_PATHS: 'shell:SEND_FILE_PATHS' = 'shell:SEND_FILE_PATHS' + +export const ODD_DATA_DIR = '/data/ODD' diff --git a/app-shell-odd/src/early.ts b/app-shell-odd/src/early.ts new file mode 100644 index 00000000000..134c8957804 --- /dev/null +++ b/app-shell-odd/src/early.ts @@ -0,0 +1,22 @@ +// things intended to execute early in app-shell initialization +// do as little as possible in this file and do none of it at import time + +import { app } from 'electron' +import { ODD_DATA_DIR } from './constants' + +let path: string + +export const setUserDataPath = (): string => { + if (path == null) { + console.log( + `node env is ${process.env.NODE_ENV}, path is ${app.getPath('userData')}` + ) + if (process.env.NODE_ENV === 'production') { + console.log(`setting app path to ${ODD_DATA_DIR}`) + app.setPath('userData', ODD_DATA_DIR) + } + path = app.getPath('userData') + console.log(`app path becomes ${app.getPath('userData')}`) + } + return app.getPath('userData') +} diff --git a/app-shell-odd/src/http.ts b/app-shell-odd/src/http.ts index 6392340fbe7..90d01530da8 100644 --- a/app-shell-odd/src/http.ts +++ b/app-shell-odd/src/http.ts @@ -7,10 +7,13 @@ import FormData from 'form-data' import { Transform } from 'stream' import { HTTP_API_VERSION } from './constants' +import { createLogger } from './log' import type { Readable } from 'stream' import type { Request, RequestInit, Response } from 'node-fetch' +const log = createLogger('http') + type RequestInput = Request | string export interface DownloadProgress { @@ -18,6 +21,16 @@ export interface DownloadProgress { size: number | null } +export class LocalAbortError extends Error { + declare readonly name: 'LocalAbortError' + declare readonly type: 'aborted' + constructor(message: string) { + super(message) + this.name = 'LocalAbortError' + this.type = 'aborted' + } +} + export function fetch( input: RequestInput, init?: RequestInit @@ -35,21 +48,29 @@ export function fetch( }) } -export function fetchJson(input: RequestInput): Promise { - return fetch(input).then(response => response.json()) +export function fetchJson( + input: RequestInput, + init?: RequestInit +): Promise { + return fetch(input, init).then(response => response.json()) +} + +export function fetchText(input: Request, init?: RequestInit): Promise { + return fetch(input, init).then(response => response.text()) } -export function fetchText(input: Request): Promise { - return fetch(input).then(response => response.text()) +export interface FetchToFileOptions { + onProgress: (progress: DownloadProgress) => unknown + signal: AbortSignal } // TODO(mc, 2019-07-02): break this function up and test its components export function fetchToFile( input: RequestInput, destination: string, - options?: Partial<{ onProgress: (progress: DownloadProgress) => unknown }> + options?: Partial ): Promise { - return fetch(input).then(response => { + return fetch(input, { signal: options?.signal }).then(response => { let downloaded = 0 const size = Number(response.headers.get('Content-Length')) || null @@ -75,13 +96,26 @@ export function fetchToFile( // pump calls stream.pipe, handles teardown if streams error, and calls // its callbacks when the streams are done pump(inputStream, progressReader, outputStream, error => { - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - if (error) { + const handleError = (problem: Error): void => { // if we error out, delete the temp dir to clean up - return remove(destination).then(() => { + log.error(`Aborting fetchToFile: ${problem.name}: ${problem.message}`) + remove(destination).then(() => { reject(error) }) } + const listener = (): void => { + handleError( + new LocalAbortError( + (options?.signal?.reason as string | null) ?? 'aborted' + ) + ) + } + options?.signal?.addEventListener('abort', listener, { once: true }) + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + if (error) { + handleError(error) + } + options?.signal?.removeEventListener('abort', listener, {}) resolve(destination) }) }) diff --git a/app-shell-odd/src/log.ts b/app-shell-odd/src/log.ts index 4312000f424..100c7f275fb 100644 --- a/app-shell-odd/src/log.ts +++ b/app-shell-odd/src/log.ts @@ -1,90 +1,74 @@ // create logger function import { inspect } from 'util' -import fse from 'fs-extra' import path from 'path' import dateFormat from 'dateformat' import winston from 'winston' +import { setUserDataPath } from './early' import { getConfig } from './config' import type Transport from 'winston-transport' import type { Config } from './config' -const ODD_DIR = '/data/ODD' -const LOG_DIR = path.join(ODD_DIR, 'logs') +const LOG_DIR = path.join(setUserDataPath(), 'logs') const ERROR_LOG = path.join(LOG_DIR, 'error.log') const COMBINED_LOG = path.join(LOG_DIR, 'combined.log') -const FILE_OPTIONS = { - // JSON logs - format: winston.format.json(), - // 1 MB max log file size (to ensure emailablity) - maxsize: 1024 * 1024, - // keep 10 backups at most - maxFiles: 10, - // roll filenames in accending order (larger the number, older the log) - tailable: true, -} - -let config: Config['log'] -let transports: Transport[] -let log: winston.Logger -export function createLogger(filename: string): winston.Logger { - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - if (!config) config = getConfig('log') - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - if (!transports) initializeTransports() +// Use our own logger type because winston (a) doesn't allow these by default +// but (b) does it by binding something other than a function to these props. +export type OTLogger = Omit< + winston.Logger, + 'emerg' | 'alert' | 'crit' | 'warning' | 'notice' +> - return createWinstonLogger(filename) +export function createLogger(label: string): OTLogger { + const rootLogger = ensureRootLogger() + return rootLogger.child({ label }) } -function initializeTransports(): void { - let error = null +let _rootLog: OTLogger | null = null - // sync is ok here because this only happens once - try { - fse.ensureDirSync(LOG_DIR) - } catch (e: unknown) { - error = e +function ensureRootLogger(): OTLogger { + if (_rootLog == null) { + return buildRootLogger() + } else { + return _rootLog } +} + +function buildRootLogger(): OTLogger { + const config = getConfig('log') - transports = createTransports() - log = createWinstonLogger('log') + const transports = createTransports(config) + + const formats = [ + winston.format.timestamp(), + winston.format.metadata({ + key: 'meta', + fillExcept: ['level', 'message', 'timestamp', 'label'], + }), + ] - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - if (error) log.error('Could not create log directory', { error }) - log.info(`Level "error" and higher logging to ${ERROR_LOG}`) - log.info(`Level "${config.level.file}" and higher logging to ${COMBINED_LOG}`) - log.info(`Level "${config.level.console}" and higher logging to console`) + _rootLog = winston.createLogger({ + transports, + format: winston.format.combine(...formats), + }) + const loggingLog = _rootLog.child({ label: 'logging' }) + loggingLog.info(`Level "error" and higher logging to ${ERROR_LOG}`) + loggingLog.info( + `Level "${config.level.file}" and higher logging to ${COMBINED_LOG}` + ) + loggingLog.info( + `Level "${config.level.console}" and higher logging to console` + ) + return _rootLog } -function createTransports(): Transport[] { +function createTransports(config: Config['log']): Transport[] { const timeFromStamp = (ts: string): string => dateFormat(new Date(ts), 'HH:MM:ss.l') return [ - // error file log - new winston.transports.File( - Object.assign( - { - level: 'error', - filename: ERROR_LOG, - }, - FILE_OPTIONS - ) - ), - - // regular combined file log - new winston.transports.File( - Object.assign( - { - level: config.level.file, - filename: COMBINED_LOG, - }, - FILE_OPTIONS - ) - ), - // console log new winston.transports.Console({ level: config.level.console, @@ -105,22 +89,3 @@ function createTransports(): Transport[] { }), ] } - -function createWinstonLogger(label: string): winston.Logger { - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions, @typescript-eslint/prefer-optional-chain - log && log.debug(`Creating logger for ${label}`) - - const formats = [ - winston.format.label({ label }), - winston.format.timestamp(), - winston.format.metadata({ - key: 'meta', - fillExcept: ['level', 'message', 'timestamp', 'label'], - }), - ] - - return winston.createLogger({ - transports, - format: winston.format.combine(...formats), - }) -} diff --git a/app-shell-odd/src/main.ts b/app-shell-odd/src/main.ts index d271bb1dc87..9eb17a016cc 100644 --- a/app-shell-odd/src/main.ts +++ b/app-shell-odd/src/main.ts @@ -6,11 +6,7 @@ import path from 'path' import { createUi, waitForRobotServerAndShowMainWindow } from './ui' import { createLogger } from './log' import { registerDiscovery } from './discovery' -import { - registerUpdate, - updateLatestVersion, - registerUpdateBrightness, -} from './update' +import { registerUpdateBrightness } from './system' import { registerRobotSystemUpdate } from './system-update' import { registerAppRestart } from './restart' import { @@ -19,7 +15,6 @@ import { getOverrides, registerConfig, resetStore, - ODD_DIR, } from './config' import systemd from './systemd' import { registerDataFiles, watchForMassStorage } from './usb' @@ -28,7 +23,10 @@ import { establishBrokerConnection, closeBrokerConnection, } from './notifications' +import { setUserDataPath } from './early' +import { registerResourceMonitor } from './monitor' +import type { OTLogger } from './log' import type { BrowserWindow } from 'electron' import type { Action, Dispatch, Logger } from './types' import type { LogEntry } from 'winston' @@ -39,6 +37,7 @@ import type { LogEntry } from 'winston' * https://github.com/node-fetch/node-fetch/issues/1624 */ dns.setDefaultResultOrder('ipv4first') +setUserDataPath() systemd.sendStatus('starting app') const config = getConfig() @@ -87,12 +86,14 @@ function startUp(): void { log.info('Starting App') console.log('Starting App') const storeNeedsReset = fse.existsSync( - path.join(ODD_DIR, `_CONFIG_TO_BE_DELETED_ON_REBOOT`) + path.join(setUserDataPath(), `_CONFIG_TO_BE_DELETED_ON_REBOOT`) ) if (storeNeedsReset) { log.debug('store marked to be reset, resetting store') resetStore() - fse.removeSync(path.join(ODD_DIR, `_CONFIG_TO_BE_DELETED_ON_REBOOT`)) + fse.removeSync( + path.join(app.getPath('userData'), `_CONFIG_TO_BE_DELETED_ON_REBOOT`) + ) } systemd.sendStatus('loading app') process.on('uncaughtException', error => log.error('Uncaught: ', { error })) @@ -102,11 +103,28 @@ function startUp(): void { // wire modules to UI dispatches const dispatch: Dispatch = action => { - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - if (mainWindow) { - log.silly('Sending action via IPC to renderer', { action }) - mainWindow.webContents.send('dispatch', action) - } + // This function now dispatches actions to all the handlers in the app shell. That would make it + // vulnerable to infinite recursion: + // - handler handles action A + // - handler dispatches action A as a response (calls this function) + // - this function calls handler with action A + // By deferring to nextTick(), we would still be executing the code over and over but we should have + // broken the stack. + process.nextTick(() => { + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + if (mainWindow) { + log.silly('Sending action via IPC to renderer', { action }) + mainWindow.webContents.send('dispatch', action) + } + log.debug( + `bouncing action ${action.type} to ${actionHandlers.length} handlers` + ) + // Make actions that are sourced from the shell also go to the app shell without needing + // round tripping. This call is the reason for the nextTick() above. + actionHandlers.forEach(handler => { + handler(action) + }) + }) } mainWindow = createUi(dispatch) @@ -114,16 +132,11 @@ function startUp(): void { void establishBrokerConnection() mainWindow.once('closed', () => (mainWindow = null)) - log.info('Fetching latest software version') - updateLatestVersion().catch((error: Error) => { - log.error('Error fetching latest software version: ', { error }) - }) - const actionHandlers: Dispatch[] = [ registerConfig(dispatch), registerDiscovery(dispatch), - registerUpdate(dispatch), registerRobotSystemUpdate(dispatch), + registerResourceMonitor(dispatch), registerAppRestart(), registerUpdateBrightness(), registerNotify(dispatch, mainWindow), @@ -143,8 +156,19 @@ function startUp(): void { log.info('First dispatch, showing') systemd.sendStatus('started') systemd.ready() - const stopWatching = watchForMassStorage(dispatch) - ipcMain.once('quit', stopWatching) + try { + const stopWatching = watchForMassStorage(dispatch) + ipcMain.once('quit', stopWatching) + } catch (err: any) { + if (err instanceof Error) { + console.log( + `Failed to watch for mass storage: ${err.name}: ${err.message}`, + err + ) + } else { + console.log(`Failed to watch for mass storage: ${err}`) + } + } // TODO: This is where we render the main window for the first time. See ui.ts // in the createUI function for more. if (!!!mainWindow) { @@ -155,7 +179,7 @@ function startUp(): void { }) } -function createRendererLogger(): Logger { +function createRendererLogger(): OTLogger { log.info('Creating renderer logger') const logger = createLogger('renderer') @@ -173,7 +197,10 @@ function installDevtools(): void { log.debug('Installing devtools') - install(extensions, forceReinstall) + install(extensions, { + loadExtensionOptions: { allowFileAccess: true }, + forceDownload: forceReinstall, + }) .then(() => log.debug('Devtools extensions installed')) .catch((error: unknown) => { log.warn('Failed to install devtools extensions', { diff --git a/app-shell-odd/src/monitor/ResourceMonitor.ts b/app-shell-odd/src/monitor/ResourceMonitor.ts new file mode 100644 index 00000000000..e093ccbb716 --- /dev/null +++ b/app-shell-odd/src/monitor/ResourceMonitor.ts @@ -0,0 +1,305 @@ +import { exec } from 'child_process' +import { promises as fs } from 'fs' +import path from 'path' + +import { createLogger } from '../log' +import { UI_INITIALIZED } from '../constants' + +import type { Action, Dispatch } from '../types' + +export const PARENT_PROCESSES = [ + 'opentrons-robot-server.service', + 'opentrons-robot-app.service', +] as const +const REPORTING_INTERVAL_MS = 3600000 // 1 hour +const MAX_CMD_STR_LENGTH = 100 +const MAX_REPORTED_PROCESSES = 15 + +interface ProcessTreeNode { + pid: number + cmd: string + children: ProcessTreeNode[] +} + +interface ProcessDetails { + name: string + memRssMb: string +} + +interface ResourceMonitorDetails { + systemAvailMemMb: string + systemUptimeHrs: string + processesDetails: ProcessDetails[] +} + +interface ResourceMonitorOptions { + procPath?: string +} + +// Scrapes system and select process resource metrics, reporting those metrics to the browser layer. +// Note that only MAX_REPORTED_PROCESSES are actually dispatched. +export class ResourceMonitor { + private readonly monitoredProcesses: Set + private readonly log: ReturnType + private readonly procPath: string + private intervalId: NodeJS.Timeout | null + + constructor(options: ResourceMonitorOptions = {}) { + this.monitoredProcesses = new Set(PARENT_PROCESSES) + this.log = createLogger('monitor') + this.intervalId = null + this.procPath = options.procPath ?? '/proc' // Override used for testing purposes. + } + + start(dispatch: Dispatch): Dispatch { + // Scrape and report metrics on an interval. + const beginMonitor = (): void => { + if (this.intervalId == null) { + this.intervalId = setInterval(() => { + this.getResourceDetails() + .then(resourceDetails => { + this.log.debug('resource monitor report', { + resourceDetails, + }) + this.dispatchResourceDetails(resourceDetails, dispatch) + }) + .catch(error => { + this.log.error('Error monitoring process: ', error) + }) + }, REPORTING_INTERVAL_MS) + } else { + this.log.warn( + 'Attempted to start an already started instance of ResourceMonitor.' + ) + } + } + + return function handleAction(action: Action) { + switch (action.type) { + case UI_INITIALIZED: + beginMonitor() + } + } + } + + // Manually stop reporting, clearing internal state. + stop(): void { + if (this.intervalId != null) { + clearInterval(this.intervalId) + this.intervalId = null + this.monitoredProcesses.clear() + } + } + + private dispatchResourceDetails( + details: ResourceMonitorDetails, + dispatch: Dispatch + ): void { + const { processesDetails, systemUptimeHrs, systemAvailMemMb } = details + dispatch({ + type: 'analytics:RESOURCE_MONITOR_REPORT', + payload: { + systemUptimeHrs, + systemAvailMemMb, + processesDetails: processesDetails.slice(0, MAX_REPORTED_PROCESSES), // don't accidentally send too many items to mixpanel. + }, + }) + } + + private getResourceDetails(): Promise { + return Promise.all([ + this.getSystemAvailableMemory(), + this.getSystemUptimeHrs(), + this.getProcessDetails(), + ]).then(([systemAvailMemMb, systemUptimeHrs, processesDetails]) => ({ + systemAvailMemMb, + systemUptimeHrs, + processesDetails, + })) + } + + // Scrape system uptime from /proc/uptime. + private getSystemUptimeHrs(): Promise { + return fs + .readFile(path.join(this.procPath, 'uptime'), 'utf8') + .then(uptime => { + // First value is uptime in seconds, second is idle time + const uptimeSeconds = Math.floor(parseFloat(uptime.split(' ')[0])) + return (uptimeSeconds / 3600).toFixed(2) + }) + .catch(error => { + throw new Error( + `Failed to read system uptime: ${ + error instanceof Error ? error.message : String(error) + }` + ) + }) + } + + // Scrape system available memory from /proc/meminfo. + private getSystemAvailableMemory(): Promise { + return fs + .readFile(path.join(this.procPath, 'meminfo'), 'utf8') + .then(meminfo => { + const match = meminfo.match(/MemAvailable:\s+(\d+)\s+kB/) + if (match == null) { + throw new Error('Could not find MemAvailable in meminfo file') + } else { + const memInKb = parseInt(match[1], 10) + return (memInKb / 1024).toFixed(2) + } + }) + .catch(error => { + throw new Error( + `Failed to read available memory info: ${ + error instanceof Error ? error.message : String(error) + }` + ) + }) + } + + // Given parent process names, get metrics for parent and all spawned processes. + private getProcessDetails(): Promise { + return Promise.all( + Array.from(this.monitoredProcesses).map(parentProcess => + this.getProcessTree(parentProcess) + .then(processTree => { + if (processTree == null) { + return [] + } else { + return this.getProcessDetailsFlattened(processTree) + } + }) + .catch(error => { + this.log.error('Failed to get process tree', { + parentProcess, + error, + }) + return [] + }) + ) + ).then(detailsArrays => detailsArrays.flat()) + } + + private getProcessTree( + parentProcess: string + ): Promise { + return this.getProcessPid(parentProcess).then(parentPid => { + if (parentPid == null) { + return null + } else { + return this.buildProcessTree(parentPid) + } + }) + } + + private getProcessPid(serviceName: string): Promise { + return new Promise((resolve, reject) => { + exec(`systemctl show ${serviceName} -p MainPID`, (error, stdout) => { + if (error != null) { + reject( + new Error(`Failed to get PID for ${serviceName}: ${error.message}`) + ) + } else { + const match = stdout.match(/MainPID=(\d+)/) + + if (match == null) { + resolve(null) + } else { + const pid = parseInt(match[1], 10) + resolve(pid > 1 ? pid : null) + } + } + }) + }) + } + + // Recursively build the process tree, scraping the cmdline string for each pid. + private buildProcessTree(pid: number): Promise { + return Promise.all([ + this.getProcessCmdline(pid), + this.getChildProcessesFrom(pid), + ]).then(([cmd, childPids]) => { + return Promise.all( + childPids.map(childPid => this.buildProcessTree(childPid)) + ).then(children => ({ + pid, + cmd, + children, + })) + }) + } + + // Get the exact cmdline string for the given pid, truncating if necessary. + private getProcessCmdline(pid: number): Promise { + return fs + .readFile(path.join(this.procPath, String(pid), 'cmdline'), 'utf8') + .then(cmdline => { + const cmd = cmdline.replace(/\0/g, ' ').trim() + return cmd.length > MAX_CMD_STR_LENGTH + ? `${cmd.substring(0, MAX_CMD_STR_LENGTH)}...` + : cmd + }) + .catch(error => { + this.log.error(`Failed to read cmdline for PID ${pid}`, error) + return `PID ${pid}` + }) + } + + private getChildProcessesFrom(parentPid: number): Promise { + return new Promise((resolve, reject) => { + exec(`pgrep -P ${parentPid}`, (error, stdout) => { + // code 1 means no children found + if (error != null && error.code !== 1) { + reject(error) + } else { + const children = stdout + .trim() + .split('\n') + .filter(line => line.length > 0) + .map(pid => parseInt(pid, 10)) + + resolve(children) + } + }) + }) + } + + // Get the actual metric(s) for a given node and recursively get metric(s) for all child nodes. + private getProcessDetailsFlattened( + node: ProcessTreeNode + ): Promise { + return this.getProcessMemory(node.pid).then(memRssMb => { + const currentNodeDetails: ProcessDetails = { + name: node.cmd, + memRssMb, + } + + return Promise.all( + node.children.map(child => this.getProcessDetailsFlattened(child)) + ).then(childDetailsArrays => { + return [currentNodeDetails, ...childDetailsArrays.flat()] + }) + }) + } + + // Scrape VmRSS from /proc/pid/status for a given pid. + private getProcessMemory(pid: number): Promise { + return fs + .readFile(path.join(this.procPath, String(pid), 'status'), 'utf8') + .then(status => { + const match = status.match(/VmRSS:\s+(\d+)\s+kB/) + if (match == null) { + throw new Error('Could not find VmRSS in status file') + } else { + const memInKb = parseInt(match[1], 10) + return (memInKb / 1024).toFixed(2) + } + }) + .catch(error => { + throw new Error( + `Failed to read memory info for PID ${pid}: ${error.message}` + ) + }) + } +} diff --git a/app-shell-odd/src/monitor/__tests__/ResourceMonitor.test.ts b/app-shell-odd/src/monitor/__tests__/ResourceMonitor.test.ts new file mode 100644 index 00000000000..416ddd15204 --- /dev/null +++ b/app-shell-odd/src/monitor/__tests__/ResourceMonitor.test.ts @@ -0,0 +1,162 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck -- Get around private method access warnings. + +import path from 'path' +import fs from 'fs-extra' +import tempy from 'tempy' +import { + vi, + describe, + beforeEach, + afterEach, + afterAll, + it, + expect, +} from 'vitest' +import { exec } from 'child_process' + +import { ResourceMonitor, PARENT_PROCESSES } from '../ResourceMonitor' +import { UI_INITIALIZED } from '../../constants' + +vi.mock('child_process') +vi.mock('../../log', async importOriginal => { + const actual = await importOriginal() + return { + ...actual, + createLogger: () => ({ + debug: vi.fn(), + error: vi.fn(), + }), + } +}) + +describe('ResourceMonitor', () => { + let procDir: string + let monitor: ResourceMonitor + const tempDirs: string[] = [] + + beforeEach(async () => { + procDir = tempy.directory() + tempDirs.push(procDir) + + vi.mocked(exec).mockImplementation((cmd, callback) => { + if (cmd.startsWith('systemctl')) { + callback(null, 'MainPID=1234\n') + } else if (cmd.startsWith('pgrep')) { + callback({ code: 1 } as any, '') + } + return {} as any + }) + + // Populate mock files with some mock data. + await fs.writeFile(path.join(procDir, 'uptime'), '3600.00 7200.00\n') + await fs.writeFile( + path.join(procDir, 'meminfo'), + 'MemTotal: 8192000 kB\nMemAvailable: 4096000 kB\n' + ) + + const parentPidDir = path.join(procDir, '1234') + await fs.ensureDir(parentPidDir) + await fs.writeFile( + path.join(parentPidDir, 'cmdline'), + 'process1234\0arg1\0arg2' + ) + await fs.writeFile( + path.join(parentPidDir, 'status'), + 'Name:\tprocess\nVmRSS:\t2048 kB\n' + ) + + monitor = new ResourceMonitor({ procPath: procDir }) + }) + + afterEach(() => { + monitor.stop() + }) + + afterAll(() => { + vi.resetAllMocks() + return Promise.all(tempDirs.map(d => fs.remove(d))) + }) + + describe('getSystemUptimeHrs', () => { + it('reads and parses system uptime', () => { + return monitor.getResourceDetails().then(details => { + expect(details.systemUptimeHrs).toBe('1.00') + }) + }) + + it('handles error reading uptime file', async () => { + await fs.remove(path.join(procDir, 'uptime')) + await expect(monitor.getResourceDetails()).rejects.toThrow( + 'Failed to read system uptime' + ) + }) + }) + + describe('getSystemAvailableMemory', () => { + it('reads and parses available memory', () => { + return monitor.getResourceDetails().then(details => { + expect(details.systemAvailMemMb).toBe('4000.00') + }) + }) + + it('handles missing MemAvailable in meminfo', async () => { + await fs.writeFile( + path.join(procDir, 'meminfo'), + 'MemTotal: 8192000 kB\n' + ) + + await expect(monitor.getResourceDetails()).rejects.toThrow( + 'Could not find MemAvailable in meminfo file' + ) + }) + }) + + describe('getProcessDetails', () => { + it('collects process details for parent process', () => { + return monitor.getResourceDetails().then(details => { + expect(details.processesDetails).toHaveLength(PARENT_PROCESSES.length) + expect(details.processesDetails[0]).toEqual({ + name: 'process1234 arg1 arg2', + memRssMb: '2.00', + }) + }) + }) + + it('handles missing process', () => { + // Mock exec to return non-existent PID + vi.mocked(exec).mockImplementation((cmd, callback) => { + if (cmd.startsWith('systemctl')) { + callback(null, 'MainPID=9999\n') + } else { + callback({ code: 1 } as any, '') + } + return {} as any + }) + + return monitor.getResourceDetails().then(details => { + expect(details.processesDetails).toHaveLength(0) + }) + }) + + it('handles errors reading process details', async () => { + await fs.remove(path.join(procDir, '1234', 'status')) + await monitor.getResourceDetails().then(details => { + expect(details.processesDetails).toHaveLength(0) + }) + }) + }) + + describe('start', () => { + it(`handler correctly updates internal state when ${UI_INITIALIZED} is dispatched`, () => { + const dispatch = vi.fn() + const handler = monitor.start(dispatch) + + expect(typeof handler).toBe('function') + + handler({ type: UI_INITIALIZED }) + + expect(monitor.intervalId).not.toBeNull() + }) + }) +}) diff --git a/app-shell-odd/src/monitor/index.ts b/app-shell-odd/src/monitor/index.ts new file mode 100644 index 00000000000..8f257b19aa1 --- /dev/null +++ b/app-shell-odd/src/monitor/index.ts @@ -0,0 +1,8 @@ +import { ResourceMonitor } from './ResourceMonitor' + +import type { Dispatch } from '../types' + +export function registerResourceMonitor(dispatch: Dispatch): Dispatch { + const resourceMonitor = new ResourceMonitor() + return resourceMonitor.start(dispatch) +} diff --git a/app-shell-odd/src/system-update/__tests__/handler.test.ts b/app-shell-odd/src/system-update/__tests__/handler.test.ts new file mode 100644 index 00000000000..65769c93729 --- /dev/null +++ b/app-shell-odd/src/system-update/__tests__/handler.test.ts @@ -0,0 +1,777 @@ +// app-shell self-update tests +import { when } from 'vitest-when' +import { rm } from 'fs-extra' +import { describe, it, vi, beforeEach, afterEach, expect } from 'vitest' +import tempy from 'tempy' + +import * as Cfg from '../../config' +import { CONFIG_INITIALIZED, VALUE_UPDATED } from '../../constants' +import { + manageDriver, + createUpdateDriver, + CURRENT_SYSTEM_VERSION, +} from '../handler' +import { FLEX_MANIFEST_URL } from '../constants' +import { getSystemUpdateDir as _getSystemUpdateDir } from '../directories' +import { getProvider as _getWebProvider } from '../from-web' +import { getProvider as _getUsbProvider } from '../from-usb' + +import type { UpdateProvider } from '../types' +import type { UpdateDriver } from '../handler' +import type { WebUpdateSource } from '../from-web' +import type { USBUpdateSource } from '../from-usb' +import type { Dispatch } from '../../types' + +import type { + ConfigInitializedAction, + ConfigValueUpdatedAction, +} from '@opentrons/app/src/redux/config' + +vi.unmock('electron-updater') // ? +vi.mock('electron-updater') +vi.mock('../../log') +vi.mock('../../config') +vi.mock('../../http') +vi.mock('../directories') +vi.mock('../from-web') +vi.mock('../from-usb') + +const getSystemUpdateDir = vi.mocked(_getSystemUpdateDir) +const getConfig = vi.mocked(Cfg.getConfig) +const getWebProvider = vi.mocked(_getWebProvider) +const getUsbProvider = vi.mocked(_getUsbProvider) + +describe('update driver manager', () => { + let dispatch: Dispatch + let testDir: string = '' + beforeEach(() => { + const thisTd = tempy.directory() + testDir = thisTd + dispatch = vi.fn() + when(getSystemUpdateDir).calledWith().thenReturn(thisTd) + }) + + afterEach(() => { + vi.resetAllMocks() + const oldTd = testDir + testDir = '' + return oldTd === '' + ? new Promise(resolve => resolve()) + : rm(oldTd, { recursive: true, force: true }) + }) + + it('creates a driver once config is loaded', () => { + when(getConfig) + .calledWith('update') + .thenReturn(({ channel: 'alpha' } as any) as Cfg.Config['update']) + const driver = manageDriver(dispatch) + expect(driver.getUpdateDriver()).toBeNull() + expect(getConfig).not.toHaveBeenCalled() + return driver + .handleAction({ + type: CONFIG_INITIALIZED, + } as ConfigInitializedAction) + .then(() => { + expect(driver.getUpdateDriver()).not.toBeNull() + expect(getConfig).toHaveBeenCalledOnce() + expect(getWebProvider).toHaveBeenCalledWith({ + manifestUrl: FLEX_MANIFEST_URL, + channel: 'alpha', + updateCacheDirectory: testDir, + currentVersion: CURRENT_SYSTEM_VERSION, + }) + }) + }) + + it('reloads the web driver when appropriate', () => { + when(getConfig) + .calledWith('update') + .thenReturn(({ channel: 'alpha' } as any) as Cfg.Config['update']) + const fakeProvider = { + teardown: vi.fn(), + refreshUpdateCache: vi.fn(), + getUpdateDetails: vi.fn(), + lockUpdateCache: vi.fn(), + unlockUpdateCache: vi.fn(), + name: vi.fn(), + source: () => (({ channel: 'alpha' } as any) as WebUpdateSource), + } + const fakeProvider2 = { + ...fakeProvider, + source: () => (({ channel: 'beta' } as any) as WebUpdateSource), + } + when(getWebProvider) + .calledWith({ + manifestUrl: FLEX_MANIFEST_URL, + channel: 'alpha', + updateCacheDirectory: testDir, + currentVersion: CURRENT_SYSTEM_VERSION, + }) + .thenReturn(fakeProvider) + when(getWebProvider) + .calledWith({ + manifestUrl: FLEX_MANIFEST_URL, + channel: 'beta', + updateCacheDirectory: testDir, + currentVersion: CURRENT_SYSTEM_VERSION, + }) + .thenReturn(fakeProvider2) + const driverManager = manageDriver(dispatch) + return driverManager + .handleAction({ + type: CONFIG_INITIALIZED, + } as ConfigInitializedAction) + .then(() => { + expect(getWebProvider).toHaveBeenCalledWith({ + manifestUrl: FLEX_MANIFEST_URL, + channel: 'alpha', + updateCacheDirectory: testDir, + currentVersion: CURRENT_SYSTEM_VERSION, + }) + expect(driverManager.getUpdateDriver()).not.toBeNull() + when(fakeProvider.teardown).calledWith().thenResolve() + return driverManager.handleAction({ + type: VALUE_UPDATED, + } as ConfigValueUpdatedAction) + }) + .then(() => { + expect(getWebProvider).toHaveBeenCalledOnce() + when(getConfig) + .calledWith('update') + .thenReturn(({ + channel: 'beta', + } as any) as Cfg.Config['update']) + return driverManager.handleAction({ + type: VALUE_UPDATED, + } as ConfigValueUpdatedAction) + }) + .then(() => { + expect(getWebProvider).toHaveBeenCalledWith({ + manifestUrl: FLEX_MANIFEST_URL, + channel: 'alpha', + updateCacheDirectory: testDir, + currentVersion: CURRENT_SYSTEM_VERSION, + }) + }) + }) +}) + +describe('update driver', () => { + let dispatch: Dispatch + let testDir: string = '' + let subject: UpdateDriver | null = null + const fakeProvider: UpdateProvider = { + teardown: vi.fn(), + refreshUpdateCache: vi.fn(), + getUpdateDetails: vi.fn(), + lockUpdateCache: vi.fn(), + unlockUpdateCache: vi.fn(), + name: vi.fn(), + source: () => (({ channel: 'alpha' } as any) as WebUpdateSource), + } + const fakeUsbProviders: Record> = { + first: { + teardown: vi.fn(), + refreshUpdateCache: vi.fn(), + getUpdateDetails: vi.fn(), + lockUpdateCache: vi.fn(), + unlockUpdateCache: vi.fn(), + name: () => '/some/usb/path', + source: () => + (({ + massStorageRootPath: '/some/usb/path', + } as any) as USBUpdateSource), + }, + } + + beforeEach(() => { + const thisTd = tempy.directory() + testDir = thisTd + dispatch = vi.fn() + when(getSystemUpdateDir).calledWith().thenReturn(thisTd) + when(getConfig) + .calledWith('update') + .thenReturn(({ channel: 'alpha' } as any) as Cfg.Config['update']) + when(getWebProvider) + .calledWith({ + manifestUrl: FLEX_MANIFEST_URL, + channel: 'alpha', + updateCacheDirectory: testDir, + currentVersion: CURRENT_SYSTEM_VERSION, + }) + .thenReturn(fakeProvider) + fakeUsbProviders.first = { + teardown: vi.fn(), + refreshUpdateCache: vi.fn(), + getUpdateDetails: vi.fn(), + lockUpdateCache: vi.fn(), + unlockUpdateCache: vi.fn(), + name: () => '/some/usb/path', + source: () => + (({ + massStorageRootPath: '/some/usb/path', + } as any) as USBUpdateSource), + } + fakeUsbProviders.second = { + teardown: vi.fn(), + refreshUpdateCache: vi.fn(), + getUpdateDetails: vi.fn(), + lockUpdateCache: vi.fn(), + unlockUpdateCache: vi.fn(), + name: () => '/some/other/usb/path', + source: () => + (({ + massStorageRootPath: '/some/other/usb/path', + } as any) as USBUpdateSource), + } + subject = createUpdateDriver(dispatch) + }) + + afterEach(() => { + vi.resetAllMocks() + const oldTd = testDir + testDir = '' + return ( + subject?.teardown() || new Promise(resolve => resolve()) + ).then(() => + oldTd === '' + ? new Promise(resolve => resolve()) + : rm(oldTd, { recursive: true, force: true }) + ) + }) + + it('checks updates when told to check updates', () => { + const thisSubject = subject as UpdateDriver + when(fakeProvider.refreshUpdateCache) + .calledWith(expect.any(Function)) + .thenDo( + progress => + new Promise(resolve => { + progress({ + version: null, + files: null, + downloadProgress: 0, + releaseNotes: null, + }) + resolve({ + version: null, + files: null, + downloadProgress: 0, + releaseNotes: null, + }) + }) + ) + return thisSubject + .handleAction({ type: 'shell:CHECK_UPDATE', meta: { shell: true } }) + .then(() => { + expect(dispatch).toHaveBeenCalledWith({ + type: 'robotUpdate:UPDATE_INFO', + payload: { + version: null, + releaseNotes: null, + force: false, + target: 'flex', + }, + }) + expect(dispatch).toHaveBeenCalledWith({ + type: 'robotUpdate:UPDATE_VERSION', + payload: { version: null, force: false, target: 'flex' }, + }) + }) + }) + it('forwards in-progress downloads when no USB updates are present', () => { + const thisSubject = subject as UpdateDriver + when(fakeProvider.refreshUpdateCache) + .calledWith(expect.any(Function)) + .thenDo( + progress => + new Promise(resolve => { + progress({ + version: null, + files: null, + downloadProgress: 0, + releaseNotes: null, + }) + progress({ + version: '1.2.3', + files: null, + downloadProgress: 0, + releaseNotes: null, + }) + progress({ + version: '1.2.3', + files: null, + downloadProgress: 50, + releaseNotes: null, + }) + progress({ + version: '1.2.3', + files: { + system: '/some/path', + releaseNotes: '/some/other/path', + }, + downloadProgress: 100, + releaseNotes: 'some release notes', + }) + resolve({ + version: '1.2.3', + files: { + system: '/some/path', + releaseNotes: '/some/other/path', + }, + downloadProgress: 100, + releaseNotes: 'some release notes', + }) + }) + ) + return thisSubject + .handleAction({ type: 'shell:CHECK_UPDATE', meta: { shell: true } }) + .then(() => { + expect(dispatch).toHaveBeenNthCalledWith(1, { + type: 'robotUpdate:UPDATE_VERSION', + payload: { version: '1.2.3', force: false, target: 'flex' }, + }) + expect(dispatch).toHaveBeenNthCalledWith(2, { + type: 'robotUpdate:DOWNLOAD_PROGRESS', + payload: { progress: 50, target: 'flex' }, + }) + expect(dispatch).toHaveBeenNthCalledWith(3, { + type: 'robotUpdate:UPDATE_INFO', + payload: { + version: '1.2.3', + releaseNotes: 'some release notes', + force: false, + target: 'flex', + }, + }) + expect(dispatch).toHaveBeenNthCalledWith(4, { + type: 'robotUpdate:UPDATE_VERSION', + payload: { version: '1.2.3', force: false, target: 'flex' }, + }) + expect(dispatch).toHaveBeenNthCalledWith(5, { + type: 'robotUpdate:UPDATE_INFO', + payload: { + version: '1.2.3', + releaseNotes: 'some release notes', + force: false, + target: 'flex', + }, + }) + expect(dispatch).toHaveBeenNthCalledWith(6, { + type: 'robotUpdate:UPDATE_VERSION', + payload: { version: '1.2.3', force: false, target: 'flex' }, + }) + }) + }) + it('creates a usb provider when it gets a message that a usb device was added', () => { + const thisSubject = subject as UpdateDriver + when(getUsbProvider) + .calledWith({ + currentVersion: CURRENT_SYSTEM_VERSION, + massStorageDeviceRoot: '/some/usb/path', + massStorageDeviceFiles: ['/some/file', '/some/other/file'], + }) + .thenReturn(fakeUsbProviders.first) + when(fakeUsbProviders.first.refreshUpdateCache) + .calledWith(expect.any(Function)) + .thenResolve({ + version: '1.2.3', + files: { system: '/some/file', releaseNotes: null }, + releaseNotes: 'some fake notes', + downloadProgress: 100, + }) + return thisSubject + .handleAction({ + type: 'shell:ROBOT_MASS_STORAGE_DEVICE_ENUMERATED', + payload: { + rootPath: '/some/usb/path', + filePaths: ['/some/file', '/some/other/file'], + }, + meta: { shell: true }, + }) + .then(() => { + expect(getUsbProvider).toHaveBeenCalledWith({ + currentVersion: CURRENT_SYSTEM_VERSION, + massStorageDeviceRoot: '/some/usb/path', + massStorageDeviceFiles: ['/some/file', '/some/other/file'], + }) + }) + }) + it('does not create a usb provider if it already has one for a path', () => { + const thisSubject = subject as UpdateDriver + when(getUsbProvider) + .calledWith({ + currentVersion: CURRENT_SYSTEM_VERSION, + massStorageDeviceRoot: '/some/usb/path', + massStorageDeviceFiles: ['/some/file', '/some/other/file'], + }) + .thenReturn(fakeUsbProviders.first) + when(fakeUsbProviders.first.refreshUpdateCache) + .calledWith(expect.any(Function)) + .thenResolve({ + version: '0.1.2', + files: { system: '/some/file', releaseNotes: null }, + releaseNotes: 'some fake notes', + downloadProgress: 100, + }) + when(fakeUsbProviders.first.getUpdateDetails) + .calledWith() + .thenReturn({ + version: '0.1.2', + files: { system: '/some/file', releaseNotes: null }, + releaseNotes: 'some fake notes', + downloadProgress: 100, + }) + return thisSubject + .handleAction({ + type: 'shell:ROBOT_MASS_STORAGE_DEVICE_ENUMERATED', + payload: { + rootPath: '/some/usb/path', + filePaths: ['/some/file', '/some/other/file'], + }, + meta: { shell: true }, + }) + .then(() => { + expect(getUsbProvider).toHaveBeenCalledWith({ + currentVersion: CURRENT_SYSTEM_VERSION, + massStorageDeviceRoot: '/some/usb/path', + massStorageDeviceFiles: ['/some/file', '/some/other/file'], + }) + return thisSubject.handleAction({ + type: 'shell:ROBOT_MASS_STORAGE_DEVICE_ENUMERATED', + payload: { + rootPath: '/some/usb/path', + filePaths: ['/some/file', '/some/other/file'], + }, + meta: { shell: true }, + }) + }) + .then(() => { + expect(getUsbProvider).toHaveBeenCalledOnce() + expect(dispatch).toHaveBeenCalledWith({ + type: 'robotUpdate:UPDATE_INFO', + payload: { + releaseNotes: 'some fake notes', + version: '0.1.2', + force: true, + target: 'flex', + }, + }) + expect(dispatch).toHaveBeenCalledWith({ + type: 'robotUpdate:UPDATE_VERSION', + payload: { + version: '0.1.2', + force: true, + target: 'flex', + }, + }) + }) + .then(() => { + vi.mocked(dispatch).mockReset() + return thisSubject.handleAction({ + type: 'robotUpdate:READ_SYSTEM_FILE', + payload: { target: 'flex' }, + meta: { shell: true }, + }) + }) + .then(() => { + expect(dispatch).toHaveBeenCalledWith({ + type: 'robotUpdate:FILE_INFO', + payload: { + systemFile: '/some/file', + version: '0.1.2', + isManualFile: false, + }, + }) + }) + }) + it('tears down a usb provider when it is removed', () => { + const thisSubject = subject as UpdateDriver + when(getUsbProvider) + .calledWith({ + currentVersion: CURRENT_SYSTEM_VERSION, + massStorageDeviceRoot: '/some/usb/path', + massStorageDeviceFiles: ['/some/file', '/some/other/file'], + }) + .thenReturn(fakeUsbProviders.first) + when(fakeUsbProviders.first.refreshUpdateCache) + .calledWith(expect.any(Function)) + .thenResolve({ + version: '1.2.3', + files: { system: '/some/file', releaseNotes: null }, + releaseNotes: 'some fake notes', + downloadProgress: 100, + }) + return thisSubject + .handleAction({ + type: 'shell:ROBOT_MASS_STORAGE_DEVICE_ENUMERATED', + payload: { + rootPath: '/some/usb/path', + filePaths: ['/some/file', '/some/other/file'], + }, + meta: { shell: true }, + }) + .then(() => { + expect(getUsbProvider).toHaveBeenCalledWith({ + currentVersion: CURRENT_SYSTEM_VERSION, + massStorageDeviceRoot: '/some/usb/path', + massStorageDeviceFiles: ['/some/file', '/some/other/file'], + }) + when(fakeUsbProviders.first.teardown).calledWith().thenResolve() + return thisSubject.handleAction({ + type: 'shell:ROBOT_MASS_STORAGE_DEVICE_REMOVED', + payload: { rootPath: '/some/usb/path' }, + meta: { shell: true }, + }) + }) + .then(() => { + expect(fakeUsbProviders.first.teardown).toHaveBeenCalledOnce() + }) + }) + it('re-adds a usb provider if it is inserted after being removed', () => { + const thisSubject = subject as UpdateDriver + when(getUsbProvider) + .calledWith({ + currentVersion: CURRENT_SYSTEM_VERSION, + massStorageDeviceRoot: '/some/usb/path', + massStorageDeviceFiles: ['/some/file', '/some/other/file'], + }) + .thenReturn(fakeUsbProviders.first) + when(fakeUsbProviders.first.refreshUpdateCache) + .calledWith(expect.any(Function)) + .thenResolve({ + version: '1.2.3', + files: { system: '/some/file', releaseNotes: null }, + releaseNotes: 'some fake notes', + downloadProgress: 100, + }) + return thisSubject + .handleAction({ + type: 'shell:ROBOT_MASS_STORAGE_DEVICE_ENUMERATED', + payload: { + rootPath: '/some/usb/path', + filePaths: ['/some/file', '/some/other/file'], + }, + meta: { shell: true }, + }) + .then(() => { + expect(getUsbProvider).toHaveBeenCalledWith({ + currentVersion: CURRENT_SYSTEM_VERSION, + massStorageDeviceRoot: '/some/usb/path', + massStorageDeviceFiles: ['/some/file', '/some/other/file'], + }) + when(fakeUsbProviders.first.teardown).calledWith().thenResolve() + return thisSubject.handleAction({ + type: 'shell:ROBOT_MASS_STORAGE_DEVICE_REMOVED', + payload: { rootPath: '/some/usb/path' }, + meta: { shell: true }, + }) + }) + .then(() => { + expect(fakeUsbProviders.first.teardown).toHaveBeenCalledOnce() + return thisSubject.handleAction({ + type: 'shell:ROBOT_MASS_STORAGE_DEVICE_ENUMERATED', + payload: { + rootPath: '/some/usb/path', + filePaths: ['/some/file', '/some/other/file'], + }, + meta: { shell: true }, + }) + }) + .then(() => { + expect(getUsbProvider).toHaveBeenCalledTimes(2) + }) + }) + it('prefers usb updates to web updates', () => { + const thisSubject = subject as UpdateDriver + when(getUsbProvider) + .calledWith({ + currentVersion: CURRENT_SYSTEM_VERSION, + massStorageDeviceRoot: '/some/usb/path', + massStorageDeviceFiles: ['/some/file', '/some/other/file'], + }) + .thenReturn(fakeUsbProviders.first) + when(fakeUsbProviders.first.getUpdateDetails) + .calledWith() + .thenReturn({ + version: '0.1.2', + files: { system: '/some/file', releaseNotes: null }, + releaseNotes: 'some fake notes', + downloadProgress: 100, + }) + when(fakeUsbProviders.first.refreshUpdateCache) + .calledWith(expect.any(Function)) + .thenResolve({ + version: '0.1.2', + files: { system: '/some/file', releaseNotes: null }, + releaseNotes: 'some fake notes', + downloadProgress: 100, + }) + when(fakeProvider.refreshUpdateCache) + .calledWith(expect.any(Function)) + .thenResolve({ + version: '1.2.3', + files: { + system: '/some/file/from/the/web', + releaseNotes: null, + }, + releaseNotes: 'some other notes', + downloadProgress: 100, + }) + return thisSubject + .handleAction({ + type: 'shell:ROBOT_MASS_STORAGE_DEVICE_ENUMERATED', + payload: { + rootPath: '/some/usb/path', + filePaths: ['/some/file', '/some/other/file'], + }, + meta: { shell: true }, + }) + .then(() => + thisSubject.handleAction({ + type: 'shell:CHECK_UPDATE', + meta: { shell: true }, + }) + ) + .then(() => { + expect(dispatch).toHaveBeenLastCalledWith({ + type: 'robotUpdate:UPDATE_VERSION', + payload: { version: '0.1.2', force: true, target: 'flex' }, + }) + }) + .then(() => { + vi.mocked(dispatch).mockReset() + return thisSubject.handleAction({ + type: 'robotUpdate:READ_SYSTEM_FILE', + payload: { target: 'flex' }, + meta: { shell: true }, + }) + }) + .then(() => { + expect(dispatch).toHaveBeenCalledWith({ + type: 'robotUpdate:FILE_INFO', + payload: { + systemFile: '/some/file', + version: '0.1.2', + isManualFile: false, + }, + }) + }) + }) + it('selects the highest version usb update', () => { + const thisSubject = subject as UpdateDriver + when(getUsbProvider) + .calledWith({ + currentVersion: CURRENT_SYSTEM_VERSION, + massStorageDeviceRoot: '/some/usb/path', + massStorageDeviceFiles: ['/some/file', '/some/other/file'], + }) + .thenReturn(fakeUsbProviders.first) + when(getUsbProvider) + .calledWith({ + currentVersion: CURRENT_SYSTEM_VERSION, + massStorageDeviceRoot: '/some/other/usb/path', + massStorageDeviceFiles: ['/some/third/file', '/some/fourth/file'], + }) + .thenReturn(fakeUsbProviders.second) + when(fakeUsbProviders.first.refreshUpdateCache) + .calledWith(expect.any(Function)) + .thenResolve({ + version: '1.2.3', + files: { system: '/some/file', releaseNotes: null }, + releaseNotes: 'some fake notes', + downloadProgress: 100, + }) + when(fakeUsbProviders.second.refreshUpdateCache) + .calledWith(expect.any(Function)) + .thenResolve({ + version: '0.1.2', + files: { system: '/some/other/file', releaseNotes: null }, + releaseNotes: 'some other fake notes', + downloadProgress: 100, + }) + when(fakeUsbProviders.first.getUpdateDetails) + .calledWith() + .thenReturn({ + version: '1.2.3', + files: { system: '/some/file', releaseNotes: null }, + releaseNotes: 'some fake notes', + downloadProgress: 100, + }) + when(fakeUsbProviders.second.getUpdateDetails) + .calledWith() + .thenReturn({ + version: '0.1.2', + files: { system: '/some/other/filefile', releaseNotes: null }, + releaseNotes: 'some other fake notes', + downloadProgress: 100, + }) + return thisSubject + .handleAction({ + type: 'shell:ROBOT_MASS_STORAGE_DEVICE_ENUMERATED', + payload: { + rootPath: '/some/usb/path', + filePaths: ['/some/file', '/some/other/file'], + }, + meta: { shell: true }, + }) + .then(() => { + expect(getUsbProvider).toHaveBeenCalledWith({ + currentVersion: CURRENT_SYSTEM_VERSION, + massStorageDeviceRoot: '/some/usb/path', + massStorageDeviceFiles: ['/some/file', '/some/other/file'], + }) + vi.mocked(dispatch).mockReset() + return thisSubject.handleAction({ + type: 'shell:ROBOT_MASS_STORAGE_DEVICE_ENUMERATED', + payload: { + rootPath: '/some/other/usb/path', + filePaths: ['/some/third/file', '/some/fourth/file'], + }, + meta: { shell: true }, + }) + }) + .then(() => { + expect(getUsbProvider).toHaveBeenCalledWith({ + currentVersion: CURRENT_SYSTEM_VERSION, + massStorageDeviceRoot: '/some/usb/path', + massStorageDeviceFiles: ['/some/file', '/some/other/file'], + }) + expect(dispatch).toHaveBeenNthCalledWith(1, { + type: 'robotUpdate:UPDATE_INFO', + payload: { + releaseNotes: 'some fake notes', + version: '1.2.3', + force: true, + target: 'flex', + }, + }) + expect(dispatch).toHaveBeenNthCalledWith(2, { + type: 'robotUpdate:UPDATE_VERSION', + payload: { + version: '1.2.3', + force: true, + target: 'flex', + }, + }) + }) + .then(() => { + vi.mocked(dispatch).mockReset() + return thisSubject.handleAction({ + type: 'robotUpdate:READ_SYSTEM_FILE', + payload: { target: 'flex' }, + meta: { shell: true }, + }) + }) + .then(() => { + expect(dispatch).toHaveBeenCalledWith({ + type: 'robotUpdate:FILE_INFO', + payload: { + systemFile: '/some/file', + version: '1.2.3', + isManualFile: false, + }, + }) + }) + }) +}) diff --git a/app-shell-odd/src/system-update/__tests__/release-files.test.ts b/app-shell-odd/src/system-update/__tests__/release-files.test.ts deleted file mode 100644 index bd2a421b910..00000000000 --- a/app-shell-odd/src/system-update/__tests__/release-files.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -// TODO(mc, 2020-06-11): test all release-files functions -import { vi, describe, it, expect, afterAll } from 'vitest' -import path from 'path' -import { promises as fs } from 'fs' -import fse from 'fs-extra' -import tempy from 'tempy' - -import { cleanupReleaseFiles } from '../release-files' -vi.mock('electron-store') -vi.mock('../../log') - -describe('system release files utilities', () => { - const tempDirs: string[] = [] - const makeEmptyDir = (): string => { - const dir: string = tempy.directory() - tempDirs.push(dir) - return dir - } - - afterAll(async () => { - await Promise.all(tempDirs.map(d => fse.remove(d))) - }) - - describe('cleanupReleaseFiles', () => { - it('should leave current version files alone', () => { - const dir = makeEmptyDir() - const releaseDir = path.join(dir, '4.0.0') - - return fs - .mkdir(releaseDir) - .then(() => cleanupReleaseFiles(dir, '4.0.0')) - .then(() => fs.readdir(dir)) - .then(files => { - expect(files).toEqual(['4.0.0']) - }) - }) - - it('should leave support files alone', () => { - const dir = makeEmptyDir() - const releaseDir = path.join(dir, '4.0.0') - const releaseManifest = path.join(dir, 'releases.json') - - return Promise.all([ - fs.mkdir(releaseDir), - fse.writeJson(releaseManifest, { hello: 'world' }), - ]) - .then(() => cleanupReleaseFiles(dir, '4.0.0')) - .then(() => fs.readdir(dir)) - .then(files => { - expect(files).toEqual(['4.0.0', 'releases.json']) - }) - }) - - it('should delete other directories', () => { - const dir = makeEmptyDir() - const releaseDir = path.join(dir, '4.0.0') - const oldReleaseDir = path.join(dir, '3.9.0') - const olderReleaseDir = path.join(dir, '3.8.0') - - return Promise.all([ - fs.mkdir(releaseDir), - fs.mkdir(oldReleaseDir), - fs.mkdir(olderReleaseDir), - ]) - .then(() => cleanupReleaseFiles(dir, '4.0.0')) - .then(() => fs.readdir(dir)) - .then(files => { - expect(files).toEqual(['4.0.0']) - }) - }) - }) -}) diff --git a/app-shell-odd/src/system-update/__tests__/release-manifest.test.ts b/app-shell-odd/src/system-update/__tests__/release-manifest.test.ts deleted file mode 100644 index 89091d2731c..00000000000 --- a/app-shell-odd/src/system-update/__tests__/release-manifest.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { describe, it, vi, beforeEach, afterEach, expect } from 'vitest' -import * as Http from '../../http' -import * as Dirs from '../directories' -import { downloadAndCacheReleaseManifest } from '../release-manifest' - -vi.mock('../../http') -vi.mock('../directories') -vi.mock('../../log') -vi.mock('electron-store') -const fetchJson = Http.fetchJson -const getManifestCacheDir = Dirs.getManifestCacheDir - -const MOCK_DIR = 'mock_dir' -const MANIFEST_URL = 'http://example.com/releases.json' -const MOCK_MANIFEST = {} as any - -describe('release manifest utilities', () => { - beforeEach(() => { - vi.mocked(getManifestCacheDir).mockReturnValue(MOCK_DIR) - vi.mocked(fetchJson).mockResolvedValue(MOCK_MANIFEST) - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - it('should download and save the manifest from a url', async () => { - await expect( - downloadAndCacheReleaseManifest(MANIFEST_URL) - ).resolves.toEqual(MOCK_MANIFEST) - expect(fetchJson).toHaveBeenCalledWith(MANIFEST_URL) - }) - - it('should pull the manifest from the file if the manifest download fails', async () => { - const error = new Error('Failed to download') - vi.mocked(fetchJson).mockRejectedValue(error) - await expect( - downloadAndCacheReleaseManifest(MANIFEST_URL) - ).resolves.toEqual(MOCK_MANIFEST) - expect(fetchJson).toHaveBeenCalledWith(MANIFEST_URL) - }) -}) diff --git a/app-shell-odd/src/system-update/constants.ts b/app-shell-odd/src/system-update/constants.ts new file mode 100644 index 00000000000..575b64230b5 --- /dev/null +++ b/app-shell-odd/src/system-update/constants.ts @@ -0,0 +1,11 @@ +const OPENTRONS_PROJECT: string = _OPENTRONS_PROJECT_ + +export const FLEX_MANIFEST_URL = + OPENTRONS_PROJECT && OPENTRONS_PROJECT.includes('robot-stack') + ? 'https://builds.opentrons.com/ot3-oe/releases.json' + : 'https://ot3-development.builds.opentrons.com/ot3-oe/releases.json' + +export const SYSTEM_UPDATE_DIRECTORY = '__ot_system_update__' +export const VERSION_FILENAME = 'VERSION.json' +export const REASONABLE_VERSION_FILE_SIZE_B = 4096 +export const SYSTEM_FILENAME = 'system-update.zip' diff --git a/app-shell-odd/src/system-update/directories.ts b/app-shell-odd/src/system-update/directories.ts index c2723153505..757f47bc44a 100644 --- a/app-shell-odd/src/system-update/directories.ts +++ b/app-shell-odd/src/system-update/directories.ts @@ -1,15 +1,6 @@ import { app } from 'electron' import path from 'path' +import { SYSTEM_UPDATE_DIRECTORY } from './constants' -const SYSTEM_UPDATE_DIRECTORY = path.join( - app.getPath('sessionData'), - '__ot_system_update__' -) - -export const getSystemUpdateDir = (): string => SYSTEM_UPDATE_DIRECTORY - -export const getFileDownloadDir = (version: string): string => - path.join(SYSTEM_UPDATE_DIRECTORY, version) - -export const getManifestCacheDir = (): string => - path.join(SYSTEM_UPDATE_DIRECTORY, 'releases.json') +export const getSystemUpdateDir = (): string => + path.join(app.getPath('userData'), SYSTEM_UPDATE_DIRECTORY) diff --git a/app-shell-odd/src/system-update/from-usb/__tests__/provider.test.ts b/app-shell-odd/src/system-update/from-usb/__tests__/provider.test.ts new file mode 100644 index 00000000000..cbdf79435dc --- /dev/null +++ b/app-shell-odd/src/system-update/from-usb/__tests__/provider.test.ts @@ -0,0 +1,205 @@ +import { it, describe, vi, afterEach, expect } from 'vitest' +import { when } from 'vitest-when' +import { getProvider } from '../provider' +import { getLatestMassStorageUpdateFile as _getLatestMassStorageUpdateFile } from '../scan-device' + +vi.mock('../scan-device') +vi.mock('../../../log') + +const getLatestMassStorageUpdateFile = vi.mocked( + _getLatestMassStorageUpdateFile +) + +describe('system-update/from-usb/provider', () => { + afterEach(() => { + vi.resetAllMocks() + }) + it('signals available updates when given available updates', () => { + when(getLatestMassStorageUpdateFile) + .calledWith(['/storage/valid-release.zip']) + .thenResolve({ path: '/storage/valid-release.zip', version: '1.2.3' }) + const progress = vi.fn() + const provider = getProvider({ + currentVersion: '1.0.0', + massStorageDeviceRoot: '/storage', + massStorageDeviceFiles: ['/storage/valid-release.zip'], + }) + const expectedUpdate = { + version: '1.2.3', + files: { + system: '/storage/valid-release.zip', + releaseNotes: expect.any(String), + }, + releaseNotes: expect.any(String), + downloadProgress: 100, + } + return expect(provider.refreshUpdateCache(progress)) + .resolves.toEqual(expectedUpdate) + .then(() => { + expect(progress).toHaveBeenLastCalledWith(expectedUpdate) + }) + }) + it('signals no available update when given no available updates', () => { + when(getLatestMassStorageUpdateFile) + .calledWith(['/storage/blahblah']) + .thenResolve(null) + const progress = vi.fn() + const provider = getProvider({ + currentVersion: '1.0.0', + massStorageDeviceRoot: '/storage', + massStorageDeviceFiles: ['/storage/blahblah'], + }) + const expectedUpdate = { + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + } + return expect(provider.refreshUpdateCache(progress)) + .resolves.toEqual(expectedUpdate) + .then(() => { + expect(progress).toHaveBeenLastCalledWith(expectedUpdate) + }) + }) + it('signals no available update when the scan throws', () => { + when(getLatestMassStorageUpdateFile) + .calledWith(['/storage/blahblah']) + .thenReject(new Error('oh no')) + const progress = vi.fn() + const provider = getProvider({ + currentVersion: '1.0.0', + massStorageDeviceRoot: '/storage', + massStorageDeviceFiles: ['/storage/blahblah'], + }) + const expectedUpdate = { + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + } + return expect(provider.refreshUpdateCache(progress)) + .resolves.toEqual(expectedUpdate) + .then(() => { + expect(progress).toHaveBeenLastCalledWith(expectedUpdate) + }) + }) + it('signals no available update when the highest version update is the same version as current', () => { + when(getLatestMassStorageUpdateFile) + .calledWith(['/storage/valid-release.zip']) + .thenResolve({ path: '/storage/valid-release.zip', version: '1.0.0' }) + const progress = vi.fn() + const provider = getProvider({ + currentVersion: '1.0.0', + massStorageDeviceRoot: '/storage', + massStorageDeviceFiles: ['/storage/valid-release.zip'], + }) + const expectedUpdate = { + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + } + return expect(provider.refreshUpdateCache(progress)) + .resolves.toEqual(expectedUpdate) + .then(() => { + expect(progress).toHaveBeenLastCalledWith(expectedUpdate) + }) + }) + it('throws when torn down before scanning', () => { + const provider = getProvider({ + currentVersion: '1.0.0', + massStorageDeviceRoot: '/', + massStorageDeviceFiles: [], + }) + const progress = vi.fn() + return provider + .teardown() + .then(() => + expect(provider.refreshUpdateCache(progress)).rejects.toThrow() + ) + .then(() => + expect(progress).toHaveBeenLastCalledWith({ + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + }) + ) + }) + it('throws when torn down right after scanning', () => { + const provider = getProvider({ + currentVersion: '1.0.0', + massStorageDeviceRoot: '/', + massStorageDeviceFiles: [], + }) + const progress = vi.fn() + when(getLatestMassStorageUpdateFile) + .calledWith(['/storage/valid-release.zip']) + .thenDo(() => + provider.teardown().then(() => ({ + path: '/storage/valid-release.zip', + version: '1.0.0', + })) + ) + return provider + .teardown() + .then(() => + expect(provider.refreshUpdateCache(progress)).rejects.toThrow() + ) + .then(() => + expect(progress).toHaveBeenLastCalledWith({ + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + }) + ) + }) + it('will not run two checks at once', () => { + when(getLatestMassStorageUpdateFile) + .calledWith(['/storage/valid-release.zip']) + .thenResolve({ path: '/storage/valid-release.zip', version: '1.0.0' }) + const progress = vi.fn() + const provider = getProvider({ + currentVersion: '1.0.0', + massStorageDeviceRoot: '/storage', + massStorageDeviceFiles: ['/storage/valid-release.zip'], + }) + const expectedUpdate = { + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + } + const first = provider.refreshUpdateCache(progress) + const second = provider.refreshUpdateCache(progress) + return Promise.all([ + expect(first).resolves.toEqual(expectedUpdate), + expect(second).rejects.toThrow(), + ]).then(() => expect(getLatestMassStorageUpdateFile).toHaveBeenCalledOnce()) + }) + it('will run a second check after the first ends', () => { + when(getLatestMassStorageUpdateFile) + .calledWith(['/storage/valid-release.zip']) + .thenResolve({ path: '/storage/valid-release.zip', version: '1.0.0' }) + const progress = vi.fn() + const provider = getProvider({ + currentVersion: '1.0.0', + massStorageDeviceRoot: '/storage', + massStorageDeviceFiles: ['/storage/valid-release.zip'], + }) + const expectedUpdate = { + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + } + return expect(provider.refreshUpdateCache(progress)) + .resolves.toEqual(expectedUpdate) + .then(() => + expect(provider.refreshUpdateCache(progress)).resolves.toEqual( + expectedUpdate + ) + ) + }) +}) diff --git a/app-shell-odd/src/system-update/from-usb/__tests__/scan-device.test.ts b/app-shell-odd/src/system-update/from-usb/__tests__/scan-device.test.ts new file mode 100644 index 00000000000..ff51e89abf3 --- /dev/null +++ b/app-shell-odd/src/system-update/from-usb/__tests__/scan-device.test.ts @@ -0,0 +1,59 @@ +import { describe, it, expect, vi, afterEach } from 'vitest' +import { when } from 'vitest-when' + +import { getVersionFromZipIfValid as _getVersionFromZipIfValid } from '../scan-zip' +import { getLatestMassStorageUpdateFile } from '../scan-device' +vi.mock('../../../log') +vi.mock('../scan-zip') +const getVersionFromZipIfValid = vi.mocked(_getVersionFromZipIfValid) + +describe('system-update/from-usb/scan-device', () => { + afterEach(() => { + vi.resetAllMocks() + }) + it('returns the single file passed in', () => { + when(getVersionFromZipIfValid) + .calledWith('/some/random/zip/file.zip') + .thenResolve({ path: '/some/random/zip/file.zip', version: '0.0.1' }) + return expect( + getLatestMassStorageUpdateFile(['/some/random/zip/file.zip']) + ).resolves.toEqual({ path: '/some/random/zip/file.zip', version: '0.0.1' }) + }) + it('returns null if no files are passed in', () => + expect(getLatestMassStorageUpdateFile([])).resolves.toBeNull()) + it('returns null if no suitable zips are found', () => { + when(getVersionFromZipIfValid) + .calledWith('/some/random/zip/file.zip') + .thenReject(new Error('no version found')) + return expect( + getLatestMassStorageUpdateFile(['/some/random/zip/file.zip']) + ).resolves.toBeNull() + }) + it('checks only the zip file', () => { + when(getVersionFromZipIfValid) + .calledWith('/some/random/zip/file.zip') + .thenResolve({ path: '/some/random/zip/file.zip', version: '0.0.1' }) + return expect( + getLatestMassStorageUpdateFile([ + '/some/random/zip/file.zip', + '/some/other/random/file', + ]) + ) + .resolves.toEqual({ path: '/some/random/zip/file.zip', version: '0.0.1' }) + .then(() => expect(getVersionFromZipIfValid).toHaveBeenCalledOnce()) + }) + it('returns the highest version', () => { + when(getVersionFromZipIfValid) + .calledWith('higher-version.zip') + .thenResolve({ path: 'higher-version.zip', version: '1.0.0' }) + when(getVersionFromZipIfValid) + .calledWith('lower-version.zip') + .thenResolve({ path: 'higher-version.zip', version: '1.0.0-alpha.0' }) + return expect( + getLatestMassStorageUpdateFile([ + 'higher-version.zip', + 'lower-version.zip', + ]) + ).resolves.toEqual({ path: 'higher-version.zip', version: '1.0.0' }) + }) +}) diff --git a/app-shell-odd/src/system-update/from-usb/__tests__/scan-zip.test.ts b/app-shell-odd/src/system-update/from-usb/__tests__/scan-zip.test.ts new file mode 100644 index 00000000000..226267a5a11 --- /dev/null +++ b/app-shell-odd/src/system-update/from-usb/__tests__/scan-zip.test.ts @@ -0,0 +1,151 @@ +import { it, describe, expect, vi } from 'vitest' +import path from 'path' +import { exec as _exec } from 'child_process' +import { promisify } from 'util' +import { writeFile, mkdir } from 'fs/promises' +import { REASONABLE_VERSION_FILE_SIZE_B } from '../../constants' +import { directoryWithCleanup } from '../../utils' +import { getVersionFromZipIfValid } from '../scan-zip' + +vi.mock('../../../log') +const exec = promisify(_exec) + +const zipCommand = ( + tempDir: string, + zipName?: string, + zipContentSubDirectory?: string +): string => + `zip -j ${path.join(tempDir, zipName ?? 'test.zip')} ${path.join( + tempDir, + zipContentSubDirectory ?? 'test', + '*' + )}` + +describe('system-update/from-usb/scan-zip', () => { + it('should read version data from a valid zip file', () => + directoryWithCleanup(directory => + mkdir(path.join(directory, 'test')) + .then(() => + writeFile( + path.join(directory, 'test', 'VERSION.json'), + JSON.stringify({ + robot_type: 'OT-3 Standard', + opentrons_api_version: '1.2.3', + }) + ) + ) + .then(() => exec(zipCommand(directory))) + .then(() => + expect( + getVersionFromZipIfValid(path.join(directory, 'test.zip')) + ).resolves.toEqual({ + path: path.join(directory, 'test.zip'), + version: '1.2.3', + }) + ) + )) + + it('should throw if there is no version file', () => + directoryWithCleanup(directory => + mkdir(path.join(directory, 'test')) + .then(() => writeFile(path.join(directory, 'test', 'dummy'), 'lalala')) + .then(() => exec(zipCommand(directory))) + .then(() => + expect( + getVersionFromZipIfValid(path.join(directory, 'test.zip')) + ).rejects.toThrow() + ) + )) + it('should throw if the version file is too big', () => + directoryWithCleanup(directory => + mkdir(path.join(directory, 'test')) + .then(() => + writeFile( + path.join(directory, 'test', 'VERSION.json'), + `{data: "${'a'.repeat(REASONABLE_VERSION_FILE_SIZE_B + 1)}"}` + ) + ) + .then(() => + exec( + `head -c ${ + REASONABLE_VERSION_FILE_SIZE_B + 1 + } /dev/zero > ${path.join(directory, 'test', 'VERSION.json')} ` + ) + ) + .then(() => exec(zipCommand(directory))) + .then(() => + expect( + getVersionFromZipIfValid(path.join(directory, 'test.zip')) + ).rejects.toThrow() + ) + )) + it('should throw if the version file is not valid json', () => + directoryWithCleanup(directory => + mkdir(path.join(directory, 'test')) + .then(() => + writeFile(path.join(directory, 'test', 'VERSION.json'), 'asdaasdas') + ) + .then(() => exec(zipCommand(directory))) + .then(() => + expect( + getVersionFromZipIfValid(path.join(directory, 'test.zip')) + ).rejects.toThrow() + ) + )) + it('should throw if the version file is for OT-2', () => + directoryWithCleanup(directory => + mkdir(path.join(directory, 'test')) + .then(() => + writeFile( + path.join(directory, 'test', 'VERSION.json'), + JSON.stringify({ + robot_type: 'OT-2 Standard', + opentrons_api_version: '1.2.3', + }) + ) + ) + .then(() => exec(zipCommand(directory))) + .then(() => + expect( + getVersionFromZipIfValid(path.join(directory, 'test.zip')) + ).rejects.toThrow() + ) + )) + it('should throw if not given a zip file', () => + directoryWithCleanup(directory => + mkdir(path.join(directory, 'test')) + .then(() => writeFile(path.join(directory, 'test.zip'), 'aosidasdasd')) + .then(() => + expect( + getVersionFromZipIfValid(path.join(directory, 'test.zip')) + ).rejects.toThrow() + ) + )) + it('should throw if given a zip file with internal directories', () => + directoryWithCleanup(directory => + mkdir(path.join(directory, 'test')) + .then(() => + writeFile( + path.join(directory, 'test', 'VERSION.json'), + JSON.stringify({ + robot_type: 'OT-3 Standard', + opentrons_api_version: '1.2.3', + }) + ) + ) + .then(() => + exec( + `zip ${path.join(directory, 'test.zip')} ${path.join( + directory, + 'test', + '*' + )}` + ) + ) + .then(() => + expect( + getVersionFromZipIfValid(path.join(directory, 'test.zip')) + ).rejects.toThrow() + ) + )) +}) diff --git a/app-shell-odd/src/system-update/from-usb/index.ts b/app-shell-odd/src/system-update/from-usb/index.ts new file mode 100644 index 00000000000..9ae1d7e4751 --- /dev/null +++ b/app-shell-odd/src/system-update/from-usb/index.ts @@ -0,0 +1,2 @@ +export { getProvider } from './provider' +export type { USBUpdateSource } from './provider' diff --git a/app-shell-odd/src/system-update/from-usb/provider.ts b/app-shell-odd/src/system-update/from-usb/provider.ts new file mode 100644 index 00000000000..53913fab790 --- /dev/null +++ b/app-shell-odd/src/system-update/from-usb/provider.ts @@ -0,0 +1,111 @@ +import tempy from 'tempy' +import path from 'path' +import { rm, writeFile } from 'fs/promises' +import type { UpdateProvider, ResolvedUpdate, ProgressCallback } from '../types' +import { getLatestMassStorageUpdateFile } from './scan-device' +import { createLogger } from '../../log' + +export interface USBUpdateSource { + currentVersion: string + massStorageDeviceRoot: string + massStorageDeviceFiles: string[] +} + +const fakeReleaseNotesForMassStorage = (version: string): string => ` +# Opentrons Robot Software Version ${version} + +This update is from a USB mass storage device connected to your Flex, and release notes cannot be shown. + +Don't remove the USB mass storage device while the update is in progress. +` +const log = createLogger('system-updates/from-usb') + +export function getProvider( + from: USBUpdateSource +): UpdateProvider { + const noUpdate = { + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + } as const + let currentUpdate: ResolvedUpdate = noUpdate + let canceller = new AbortController() + let currentCheck: Promise | null = null + const tempdir = tempy.directory() + let tornDown = false + + const checkUpdates = async ( + progress: ProgressCallback + ): Promise => { + const myCanceller = canceller + if (myCanceller.signal.aborted || tornDown) { + progress(noUpdate) + throw new Error('cache torn down') + } + const updateFile = await getLatestMassStorageUpdateFile( + from.massStorageDeviceFiles + ).catch(() => null) + if (myCanceller.signal.aborted) { + progress(noUpdate) + throw new Error('cache torn down') + } + if (updateFile == null) { + log.info(`No update file in presented files`) + progress(noUpdate) + currentUpdate = noUpdate + return noUpdate + } + log.info(`Update file found for version ${updateFile.version}`) + if (updateFile.version === from.currentVersion) { + progress(noUpdate) + currentUpdate = noUpdate + return noUpdate + } + await writeFile( + path.join(tempdir, 'dummy-release-notes.md'), + fakeReleaseNotesForMassStorage(updateFile.version) + ) + if (myCanceller.signal.aborted) { + progress(noUpdate) + throw new Error('cache torn down') + } + const update = { + version: updateFile.version, + files: { + system: updateFile.path, + releaseNotes: path.join(tempdir, 'dummy-release-notes.md'), + }, + releaseNotes: fakeReleaseNotesForMassStorage(updateFile.version), + downloadProgress: 100, + } as const + currentUpdate = update + progress(update) + return update + } + return { + refreshUpdateCache: progressCallback => { + if (currentCheck != null) { + return new Promise((resolve, reject) => { + reject(new Error('Check already ongoing')) + }) + } + const updatePromise = checkUpdates(progressCallback) + currentCheck = updatePromise + return updatePromise.finally(() => { + currentCheck = null + }) + }, + getUpdateDetails: () => currentUpdate, + lockUpdateCache: () => {}, + unlockUpdateCache: () => {}, + teardown: () => { + canceller.abort() + tornDown = true + canceller = new AbortController() + return rm(tempdir, { recursive: true, force: true }) + }, + name: () => `USBUpdateProvider from ${from.massStorageDeviceRoot}`, + source: () => from, + } +} diff --git a/app-shell-odd/src/system-update/from-usb/scan-device.ts b/app-shell-odd/src/system-update/from-usb/scan-device.ts new file mode 100644 index 00000000000..0c0e7f3e40c --- /dev/null +++ b/app-shell-odd/src/system-update/from-usb/scan-device.ts @@ -0,0 +1,37 @@ +import Semver from 'semver' +import { getVersionFromZipIfValid } from './scan-zip' +import type { FileDetails } from './scan-zip' + +import { createLogger } from '../../log' +const log = createLogger('system-udpate/from-usb/scan-device') + +const higherVersion = (a: FileDetails | null, b: FileDetails): FileDetails => + a == null ? b : Semver.gt(a.version, b.version) ? a : b + +const mostRecentUpdateOf = (candidates: FileDetails[]): FileDetails | null => + candidates.reduce( + (prev, current) => higherVersion(prev, current), + null + ) + +const getMassStorageUpdateFiles = ( + filePaths: string[] +): Promise => + Promise.all( + filePaths.map(path => + path.endsWith('.zip') + ? getVersionFromZipIfValid(path).catch(() => null) + : new Promise(resolve => { + resolve(null) + }) + ) + ).then(values => { + const filtered = values.filter(entry => entry != null) as FileDetails[] + log.debug(`scan device found ${filtered}`) + return filtered + }) + +export const getLatestMassStorageUpdateFile = ( + filePaths: string[] +): Promise => + getMassStorageUpdateFiles(filePaths).then(mostRecentUpdateOf) diff --git a/app-shell-odd/src/system-update/from-usb/scan-zip.ts b/app-shell-odd/src/system-update/from-usb/scan-zip.ts new file mode 100644 index 00000000000..b6bce376096 --- /dev/null +++ b/app-shell-odd/src/system-update/from-usb/scan-zip.ts @@ -0,0 +1,88 @@ +import StreamZip from 'node-stream-zip' +import Semver from 'semver' +import { createLogger } from '../../log' +import { REASONABLE_VERSION_FILE_SIZE_B, VERSION_FILENAME } from '../constants' + +const log = createLogger('system-update/from-usb/scan-zip') + +export interface FileDetails { + path: string + version: string +} + +export const getVersionFromZipIfValid = (path: string): Promise => + new Promise((resolve, reject) => { + const zip = new StreamZip({ file: path, storeEntries: true }) + zip.on('ready', () => { + log.info(`Reading zip from ${path}`) + getVersionFromOpenedZipIfValid(zip) + .then(version => { + log.info(`Zip at ${path} has version ${version}`) + zip.close() + resolve({ version, path }) + }) + .catch(err => { + log.info( + `Zip at ${path} was read but could not be parsed: ${err.name}: ${err.message}` + ) + zip.close() + reject(err) + }) + }) + zip.on('error', err => { + log.info(`Zip at ${path} could not be read: ${err.name}: ${err.message}`) + zip.close() + reject(err) + }) + }) + +export const getVersionFromOpenedZipIfValid = ( + zip: StreamZip +): Promise => + new Promise((resolve, reject) => { + const found = Object.values(zip.entries()).reduce((prev, entry) => { + log.debug( + `Checking if ${entry.name} is ${VERSION_FILENAME}, is a file (${entry.isFile}), and ${entry.size}<${REASONABLE_VERSION_FILE_SIZE_B}` + ) + if ( + entry.isFile && + entry.name === VERSION_FILENAME && + entry.size < REASONABLE_VERSION_FILE_SIZE_B + ) { + log.debug(`${entry.name} is a version file candidate`) + const contents = zip.entryDataSync(entry.name).toString('ascii') + log.debug(`version contents: ${contents}`) + try { + const parsedContents = JSON.parse(contents) + if (parsedContents?.robot_type !== 'OT-3 Standard') { + reject(new Error('not a Flex release file')) + } + const fileVersion = parsedContents?.opentrons_api_version + const version = Semver.valid(fileVersion as string) + if (version === null) { + reject(new Error(`${fileVersion} is not a valid version`)) + return prev + } else { + log.info(`Found version file version ${version}`) + resolve(version) + return true + } + } catch (err: any) { + if (err instanceof Error) { + log.error( + `Failed to read ${entry.name}: ${err.name}: ${err.message}` + ) + } else { + log.error(`Failed to ready ${entry.name}: ${err}`) + } + reject(err) + return prev + } + } else { + return prev + } + }, false) + if (!found) { + reject(new Error('No version file found in zip')) + } + }) diff --git a/app-shell-odd/src/system-update/from-web/__tests__/latest-update.test.ts b/app-shell-odd/src/system-update/from-web/__tests__/latest-update.test.ts new file mode 100644 index 00000000000..b07d6947861 --- /dev/null +++ b/app-shell-odd/src/system-update/from-web/__tests__/latest-update.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect } from 'vitest' +import { latestVersionForChannel } from '../latest-update' + +describe('latest-update', () => { + it.each([ + ['8.0.0', '7.0.0', '8.0.0', ''], + ['7.0.0', '8.0.0', '8.0.0', ''], + ['8.10.0', '8.9.0', '8.10.0', ''], + ['8.9.0', '8.10.0', '8.10.0', ''], + ['8.0.0-alpha.0', '8.0.0-alpha.1', '8.0.0-alpha.1', 'alpha'], + ['8.0.0-alpha.1', '8.0.0-alpha.0', '8.0.0-alpha.1', 'alpha'], + ['8.1.0-alpha.0', '8.0.0-alpha.1', '8.1.0-alpha.0', 'alpha'], + ['8.0.0-alpha.1', '8.1.0-alpha.0', '8.1.0-alpha.0', 'alpha'], + ])( + 'choosing between %s and %s should result in %s', + (first, second, higher, channel) => { + expect(latestVersionForChannel([first, second], channel)).toEqual(higher) + } + ) + it('ignores updates from different channels', () => { + expect( + latestVersionForChannel( + ['8.0.0', '9.0.0-alpha.0', '10.0.0-beta.1', '2.0.0'], + 'production' + ) + ).toEqual('8.0.0') + expect( + latestVersionForChannel( + ['8.0.0', '9.0.0-alpha.0', '10.0.0-beta.1', '2.0.0'], + 'alpha' + ) + ).toEqual('9.0.0-alpha.0') + expect( + latestVersionForChannel( + ['8.0.0', '9.0.0-alpha.0', '10.0.0-beta.1', '2.0.0'], + 'beta' + ) + ).toEqual('10.0.0-beta.1') + }) +}) diff --git a/app-shell-odd/src/system-update/from-web/__tests__/provider.test.ts b/app-shell-odd/src/system-update/from-web/__tests__/provider.test.ts new file mode 100644 index 00000000000..3ffe2e4ec08 --- /dev/null +++ b/app-shell-odd/src/system-update/from-web/__tests__/provider.test.ts @@ -0,0 +1,774 @@ +import { vi, describe, it, expect, afterEach } from 'vitest' +import { when } from 'vitest-when' + +import { LocalAbortError } from '../../../http' +import { getProvider } from '../provider' +import { getOrDownloadManifest as _getOrDownloadManifest } from '../release-manifest' +import { cleanUpAndGetOrDownloadReleaseFiles as _cleanUpAndGetOrDownloadReleaseFiles } from '../release-files' + +vi.mock('../../../log') +vi.mock('../release-manifest', async importOriginal => { + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const original = await importOriginal() + return { + ...original, + getOrDownloadManifest: vi.fn(), + } +}) +vi.mock('../release-files') + +const getOrDownloadManifest = vi.mocked(_getOrDownloadManifest) +const cleanUpAndGetOrDownloadReleaseFiles = vi.mocked( + _cleanUpAndGetOrDownloadReleaseFiles +) + +describe('provider.refreshUpdateCache happy paths', () => { + afterEach(() => { + vi.resetAllMocks() + }) + it('says there is no update if the latest version is the current version', () => { + when(getOrDownloadManifest) + .calledWith( + 'http://opentrons.com/releases.json', + '/some/random/directory', + expect.any(AbortController) + ) + .thenResolve({ + production: { + '1.2.3': { + system: 'http://opentrons.com/system.zip', + fullImage: 'http://opentrons.com/fullImage.zip', + version: 'http://opentrons.com/version.json', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + }, + }, + }) + const progressCallback = vi.fn() + const provider = getProvider({ + manifestUrl: 'http://opentrons.com/releases.json', + channel: 'release', + updateCacheDirectory: '/some/random/directory', + currentVersion: '1.2.3', + }) + expect(provider.getUpdateDetails()).toEqual({ + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + }) + return expect(provider.refreshUpdateCache(progressCallback)) + .resolves.toEqual({ + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + }) + .then(() => { + expect(progressCallback).toHaveBeenCalledWith({ + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + }) + expect(provider.getUpdateDetails()).toEqual({ + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + }) + expect(cleanUpAndGetOrDownloadReleaseFiles).not.toHaveBeenCalled() + }) + }) + it('says there is an update if a cached update is needed', () => { + const releaseUrls = { + system: 'http://opentrons.com/system.zip', + fullImage: 'http://opentrons.com/fullImage.zip', + version: 'http://opentrons.com/version.json', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + } + const releaseFiles = { + system: '/some/random/directory/cached-release-1.2.3/ot3-system.zip', + releaseNotes: + '/some/random/directory/cached-release-1.2.3/releaseNotes.md', + } + const releaseData = { + ...releaseFiles, + releaseNotesContent: 'oh look some release notes cool', + } + when(getOrDownloadManifest) + .calledWith( + 'http://opentrons.com/releases.json', + '/some/random/directory', + expect.any(AbortController) + ) + .thenResolve({ + production: { + '1.2.3': releaseUrls, + }, + }) + + when(cleanUpAndGetOrDownloadReleaseFiles) + .calledWith( + releaseUrls, + '/some/random/directory/versions', + '1.2.3', + expect.any(Function), + expect.any(Object) + ) + .thenResolve(releaseData) + + const progressCallback = vi.fn() + const provider = getProvider({ + manifestUrl: 'http://opentrons.com/releases.json', + channel: 'release', + updateCacheDirectory: '/some/random/directory', + currentVersion: '1.0.0', + }) + expect(provider.getUpdateDetails()).toEqual({ + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + }) + return expect(provider.refreshUpdateCache(progressCallback)) + .resolves.toEqual({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'oh look some release notes cool', + downloadProgress: 100, + }) + .then(() => + expect(progressCallback).toHaveBeenCalledWith({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'oh look some release notes cool', + downloadProgress: 100, + }) + ) + }) + it('says there is an update and forwards progress if an update download is needed', () => { + const releaseUrls = { + system: 'http://opentrons.com/system.zip', + fullImage: 'http://opentrons.com/fullImage.zip', + version: 'http://opentrons.com/version.json', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + } + const releaseFiles = { + system: '/some/random/directory/cached-release-1.2.3/ot3-system.zip', + releaseNotes: + '/some/random/directory/cached-release-1.2.3/releaseNotes.md', + } + const releaseData = { + ...releaseFiles, + releaseNotesContent: 'oh look some release notes sweet', + } + when(getOrDownloadManifest) + .calledWith( + 'http://opentrons.com/releases.json', + '/some/random/directory', + expect.any(AbortController) + ) + .thenResolve({ + production: { + '1.2.3': releaseUrls, + }, + }) + + when(cleanUpAndGetOrDownloadReleaseFiles) + .calledWith( + releaseUrls, + '/some/random/directory/versions', + '1.2.3', + expect.any(Function), + expect.any(Object) + ) + .thenDo( + ( + _releaseUrls, + _cacheDir, + _version, + progressCallback, + _abortController + ) => + new Promise(resolve => { + progressCallback({ size: 100, downloaded: 0 }) + resolve() + }) + .then( + () => + new Promise(resolve => { + progressCallback({ size: 100, downloaded: 50 }) + resolve() + }) + ) + .then( + () => + new Promise(resolve => { + progressCallback({ size: 100, downloaded: 100 }) + resolve(releaseData) + }) + ) + ) + + const progressCallback = vi.fn() + const provider = getProvider({ + manifestUrl: 'http://opentrons.com/releases.json', + channel: 'release', + updateCacheDirectory: '/some/random/directory', + currentVersion: '1.0.0', + }) + expect(provider.getUpdateDetails()).toEqual({ + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + }) + return expect(provider.refreshUpdateCache(progressCallback)) + .resolves.toEqual({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'oh look some release notes sweet', + downloadProgress: 100, + }) + .then(() => { + expect(progressCallback).toHaveBeenCalledWith({ + version: '1.2.3', + files: null, + releaseNotes: null, + downloadProgress: 0, + }) + expect(progressCallback).toHaveBeenCalledWith({ + version: '1.2.3', + files: null, + releaseNotes: null, + downloadProgress: 50, + }) + expect(progressCallback).toHaveBeenCalledWith({ + version: '1.2.3', + files: null, + releaseNotes: null, + downloadProgress: 100, + }) + expect(progressCallback).toHaveBeenCalledWith({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'oh look some release notes sweet', + downloadProgress: 100, + }) + expect(provider.getUpdateDetails()).toEqual({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'oh look some release notes sweet', + downloadProgress: 100, + }) + }) + }) +}) + +describe('provider.refreshUpdateCache locking', () => { + afterEach(() => { + vi.resetAllMocks() + }) + it('will not start a refresh when locked', () => { + const provider = getProvider({ + manifestUrl: 'http://opentrons.com/releases.json', + channel: 'release', + updateCacheDirectory: '/some/random/directory', + currentVersion: '1.0.0', + }) + provider.lockUpdateCache() + return expect(provider.refreshUpdateCache(vi.fn())).rejects.toThrow() + }) + it('will start a refresh when locked then unlocked', () => { + const provider = getProvider({ + manifestUrl: 'http://opentrons.com/releases.json', + channel: 'release', + updateCacheDirectory: '/some/random/directory', + currentVersion: '1.2.3', + }) + when(getOrDownloadManifest) + .calledWith( + 'http://opentrons.com/releases.json', + '/some/random/directory', + expect.any(AbortController) + ) + .thenResolve({ + production: { + '1.2.3': { + system: 'http://opentrons.com/system.zip', + fullImage: 'http://opentrons.com/fullImage.zip', + version: 'http://opentrons.com/version.json', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + }, + }, + }) + provider.lockUpdateCache() + provider.unlockUpdateCache() + return expect(provider.refreshUpdateCache(vi.fn())).resolves.toEqual({ + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + }) + }) + it('will abort when locked in the manifest phase and return the previous update', () => { + const provider = getProvider({ + manifestUrl: 'http://opentrons.com/releases.json', + channel: 'release', + updateCacheDirectory: '/some/random/directory', + currentVersion: '1.0.0', + }) + const releaseUrls = { + system: 'http://opentrons.com/system.zip', + fullImage: 'http://opentrons.com/fullImage.zip', + version: 'http://opentrons.com/version.json', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + } + when(getOrDownloadManifest) + .calledWith( + 'http://opentrons.com/releases.json', + '/some/random/directory', + expect.any(AbortController) + ) + .thenResolve({ + production: { + '1.2.3': releaseUrls, + }, + }) + const releaseFiles = { + system: '/some/random/directory/cached-release-1.2.3/ot3-system.zip', + releaseNotes: + '/some/random/directory/cached-release-1.2.3/releaseNotes.md', + } + const releaseData = { ...releaseFiles, releaseNotesContent: 'oh hello' } + when(cleanUpAndGetOrDownloadReleaseFiles) + .calledWith( + releaseUrls, + '/some/random/directory/versions', + '1.2.3', + expect.any(Function), + expect.any(Object) + ) + .thenResolve(releaseData) + + return expect(provider.refreshUpdateCache(vi.fn())) + .resolves.toEqual({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'oh hello', + downloadProgress: 100, + }) + .then(() => { + when(getOrDownloadManifest) + .calledWith( + 'http://opentrons.com/releases.json', + '/some/random/directory', + expect.any(AbortController) + ) + .thenDo( + (_manifestUrl, _cacheDirectory, abortController) => + new Promise((resolve, reject) => { + abortController.signal.addEventListener( + 'abort', + () => { + reject(new LocalAbortError(abortController.signal.reason)) + }, + { once: true } + ) + provider.lockUpdateCache() + }) + ) + const progress = vi.fn() + return expect(provider.refreshUpdateCache(progress)) + .rejects.toThrow() + .then(() => + expect(progress).toHaveBeenCalledWith({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'oh hello', + downloadProgress: 100, + }) + ) + }) + .then(() => + expect(provider.getUpdateDetails()).toEqual({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'oh hello', + downloadProgress: 100, + }) + ) + }) + it('will abort when locked between manifest and download phases and return the previous update', () => { + const provider = getProvider({ + manifestUrl: 'http://opentrons.com/releases.json', + channel: 'release', + updateCacheDirectory: '/some/random/directory', + currentVersion: '1.0.0', + }) + const releaseUrls = { + system: 'http://opentrons.com/system.zip', + fullImage: 'http://opentrons.com/fullImage.zip', + version: 'http://opentrons.com/version.json', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + } + when(getOrDownloadManifest) + .calledWith( + 'http://opentrons.com/releases.json', + '/some/random/directory', + expect.any(AbortController) + ) + .thenResolve({ + production: { + '1.2.3': releaseUrls, + }, + }) + const releaseFiles = { + system: '/some/random/directory/cached-release-1.2.3/ot3-system.zip', + releaseNotes: + '/some/random/directory/cached-release-1.2.3/releaseNotes.md', + } + const releaseData = { ...releaseFiles, releaseNotesContent: 'hi' } + when(cleanUpAndGetOrDownloadReleaseFiles) + .calledWith( + releaseUrls, + '/some/random/directory/versions', + '1.2.3', + expect.any(Function), + expect.any(Object) + ) + .thenResolve(releaseData) + + return expect(provider.refreshUpdateCache(vi.fn())) + .resolves.toEqual({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'hi', + downloadProgress: 100, + }) + .then(() => { + when(getOrDownloadManifest) + .calledWith( + expect.any(String), + expect.any(String), + expect.any(AbortController) + ) + .thenDo( + () => + new Promise(resolve => { + provider.lockUpdateCache() + resolve({ production: { '1.2.3': releaseUrls } }) + }) + ) + const progress = vi.fn() + return expect(provider.refreshUpdateCache(progress)) + .rejects.toThrow() + .then(() => + expect(progress).toHaveBeenCalledWith({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'hi', + downloadProgress: 100, + }) + ) + }) + .then(() => + expect(provider.getUpdateDetails()).toEqual({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'hi', + downloadProgress: 100, + }) + ) + }) + it('will abort when locked in the file download phase and return the previous update', () => { + const provider = getProvider({ + manifestUrl: 'http://opentrons.com/releases.json', + channel: 'release', + updateCacheDirectory: '/some/random/directory', + currentVersion: '1.0.0', + }) + const releaseUrls = { + system: 'http://opentrons.com/system.zip', + fullImage: 'http://opentrons.com/fullImage.zip', + version: 'http://opentrons.com/version.json', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + } + when(getOrDownloadManifest) + .calledWith( + 'http://opentrons.com/releases.json', + '/some/random/directory', + expect.any(AbortController) + ) + .thenResolve({ + production: { + '1.2.3': releaseUrls, + }, + }) + const releaseFiles = { + system: '/some/random/directory/cached-release-1.2.3/ot3-system.zip', + releaseNotes: + '/some/random/directory/cached-release-1.2.3/releaseNotes.md', + } + const releaseData = { + ...releaseFiles, + releaseNotesContent: 'content', + } + when(cleanUpAndGetOrDownloadReleaseFiles) + .calledWith( + releaseUrls, + '/some/random/directory/versions', + '1.2.3', + expect.any(Function), + expect.any(Object) + ) + .thenResolve(releaseData) + + return expect(provider.refreshUpdateCache(vi.fn())) + .resolves.toEqual({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'content', + downloadProgress: 100, + }) + .then(() => { + when(getOrDownloadManifest) + .calledWith( + 'http://opentrons.com/releases.json', + '/some/random/directory', + expect.any(AbortController) + ) + .thenResolve({ + production: { + '1.2.3': releaseUrls, + }, + }) + when(cleanUpAndGetOrDownloadReleaseFiles) + .calledWith( + expect.any(Object), + expect.any(String), + expect.any(String), + expect.any(Function), + expect.any(AbortController) + ) + .thenDo( + ( + _releaseUrls, + _cacheDirectory, + _version, + _progress, + abortController + ) => + new Promise((resolve, reject) => { + abortController.signal.addEventListener( + 'abort', + () => { + reject(new LocalAbortError(abortController.signal.reason)) + }, + { once: true } + ) + provider.lockUpdateCache() + }) + ) + const progress = vi.fn() + return expect(provider.refreshUpdateCache(progress)) + .rejects.toThrow() + .then(() => + expect(progress).toHaveBeenCalledWith({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'content', + downloadProgress: 100, + }) + ) + }) + .then(() => { + expect(provider.getUpdateDetails()).toEqual({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'content', + downloadProgress: 100, + }) + }) + }) + it('will abort when locked in the last-chance phase and return the previous update', () => { + const provider = getProvider({ + manifestUrl: 'http://opentrons.com/releases.json', + channel: 'release', + updateCacheDirectory: '/some/random/directory', + currentVersion: '1.0.0', + }) + const releaseUrls = { + system: 'http://opentrons.com/system.zip', + fullImage: 'http://opentrons.com/fullImage.zip', + version: 'http://opentrons.com/version.json', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + } + when(getOrDownloadManifest) + .calledWith( + 'http://opentrons.com/releases.json', + '/some/random/directory', + expect.any(AbortController) + ) + .thenResolve({ + production: { + '1.2.3': releaseUrls, + }, + }) + const releaseFiles = { + system: '/some/random/directory/cached-release-1.2.3/ot3-system.zip', + releaseNotes: + '/some/random/directory/cached-release-1.2.3/releaseNotes.md', + } + const releaseData = { + ...releaseFiles, + releaseNotesContent: 'there is some', + } + when(cleanUpAndGetOrDownloadReleaseFiles) + .calledWith( + releaseUrls, + '/some/random/directory/versions', + '1.2.3', + expect.any(Function), + expect.any(Object) + ) + .thenResolve(releaseData) + + return expect(provider.refreshUpdateCache(vi.fn())) + .resolves.toEqual({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'there is some', + downloadProgress: 100, + }) + .then(() => { + when(getOrDownloadManifest) + .calledWith( + 'http://opentrons.com/releases.json', + '/some/random/directory', + expect.any(AbortController) + ) + .thenResolve({ + production: { + '1.2.3': releaseUrls, + }, + }) + when(cleanUpAndGetOrDownloadReleaseFiles) + .calledWith( + expect.any(Object), + expect.any(String), + expect.any(String), + expect.any(Function), + expect.any(AbortController) + ) + .thenDo( + ( + _releaseUrls, + _cacheDirectory, + _version, + _progress, + _abortController + ) => + new Promise(resolve => { + provider.lockUpdateCache() + resolve(releaseData) + }) + ) + const progress = vi.fn() + return expect(provider.refreshUpdateCache(progress)) + .rejects.toThrow() + .then(() => + expect(progress).toHaveBeenCalledWith({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'there is some', + downloadProgress: 100, + }) + ) + }) + .then(() => + expect(provider.getUpdateDetails()).toEqual({ + version: '1.2.3', + files: releaseFiles, + releaseNotes: 'there is some', + downloadProgress: 100, + }) + ) + }) + it('will not run two checks at once', () => { + when(getOrDownloadManifest) + .calledWith( + 'http://opentrons.com/releases.json', + '/some/random/directory', + expect.any(AbortController) + ) + .thenResolve({ + production: { + '1.2.3': { + system: 'http://opentrons.com/system.zip', + fullImage: 'http://opentrons.com/fullImage.zip', + version: 'http://opentrons.com/version.json', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + }, + }, + }) + const progressCallback = vi.fn() + const provider = getProvider({ + manifestUrl: 'http://opentrons.com/releases.json', + channel: 'release', + updateCacheDirectory: '/some/random/directory', + currentVersion: '1.2.3', + }) + const first = provider.refreshUpdateCache(progressCallback) + const second = provider.refreshUpdateCache(progressCallback) + return Promise.all([ + expect(first).resolves.toEqual({ + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + }), + expect(second).rejects.toThrow(), + ]).then(() => expect(getOrDownloadManifest).toHaveBeenCalledOnce()) + }) + it('will run a second check after the first completes', () => { + when(getOrDownloadManifest) + .calledWith( + 'http://opentrons.com/releases.json', + '/some/random/directory', + expect.any(AbortController) + ) + .thenResolve({ + production: { + '1.2.3': { + system: 'http://opentrons.com/system.zip', + fullImage: 'http://opentrons.com/fullImage.zip', + version: 'http://opentrons.com/version.json', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + }, + }, + }) + const progressCallback = vi.fn() + const provider = getProvider({ + manifestUrl: 'http://opentrons.com/releases.json', + channel: 'release', + updateCacheDirectory: '/some/random/directory', + currentVersion: '1.2.3', + }) + return expect(provider.refreshUpdateCache(progressCallback)) + .resolves.toEqual({ + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + }) + .then(() => + expect(provider.refreshUpdateCache(progressCallback)).resolves.toEqual({ + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + }) + ) + }) +}) diff --git a/app-shell-odd/src/system-update/from-web/__tests__/release-files.test.ts b/app-shell-odd/src/system-update/from-web/__tests__/release-files.test.ts new file mode 100644 index 00000000000..34df59eaf49 --- /dev/null +++ b/app-shell-odd/src/system-update/from-web/__tests__/release-files.test.ts @@ -0,0 +1,514 @@ +// TODO(mc, 2020-06-11): test all release-files functions +import { vi, describe, it, expect, afterEach } from 'vitest' +import { when } from 'vitest-when' +import path from 'path' +import { promises as fs } from 'fs' + +import { fetchToFile as httpFetchToFile } from '../../../http' +import { + ensureCleanReleaseCacheForVersion, + getReleaseFiles, + downloadReleaseFiles, + getOrDownloadReleaseFiles, +} from '../release-files' + +import { directoryWithCleanup } from '../../utils' +import type { ReleaseSetUrls } from '../../types' + +vi.mock('../../../http') +vi.mock('../../../log') + +const fetchToFile = vi.mocked(httpFetchToFile) + +describe('ensureCleanReleaseCacheForVersion', () => { + it('should create the appropriate directory tree if it does not exist', () => + directoryWithCleanup(directory => + ensureCleanReleaseCacheForVersion( + path.join(directory, 'somerandomdirectory', 'someotherrandomdirectory'), + '1.2.3' + ) + .then(cacheDirectory => { + expect(cacheDirectory).toEqual( + path.join( + directory, + 'somerandomdirectory', + 'someotherrandomdirectory', + 'cached-release-1.2.3' + ) + ) + return fs.stat(cacheDirectory) + }) + .then(stats => expect(stats.isDirectory()).toBeTruthy()) + )) + it('should create the appropriate directory if the base directory entry is occupied by a file', () => + directoryWithCleanup(directory => + fs + .writeFile( + path.join(directory, 'somerandomdirectory'), + 'somerandomdata' + ) + .then(() => + ensureCleanReleaseCacheForVersion( + path.join(directory, 'somerandomdirectory'), + '1.2.3' + ) + ) + .then(cacheDirectory => { + expect(cacheDirectory).toEqual( + path.join(directory, 'somerandomdirectory', 'cached-release-1.2.3') + ) + return fs.stat(cacheDirectory) + }) + .then(stats => expect(stats.isDirectory()).toBeTruthy()) + )) + it('should create the appropriate directory if the version directory entry is occupied by a file', () => + directoryWithCleanup(directory => + fs + .mkdir(path.join(directory, 'somerandomdirectory')) + .then(() => + fs.writeFile( + path.join(directory, 'somerandomdirectory', 'cached-release-1.2.3'), + 'somerandomdata' + ) + ) + .then(() => + ensureCleanReleaseCacheForVersion( + path.join(directory, 'somerandomdirectory'), + '1.2.3' + ) + ) + .then(baseDirectory => { + expect(baseDirectory).toEqual( + path.join(directory, 'somerandomdirectory', 'cached-release-1.2.3') + ) + return fs.stat(baseDirectory) + }) + .then(stats => expect(stats.isDirectory()).toBeTruthy()) + )) + it('should remove caches for other versions from the cache directory', () => + directoryWithCleanup(directory => + fs + .mkdir(path.join(directory, 'cached-release-0.1.2')) + .then(() => fs.mkdir(path.join(directory, 'cached-release-4.5.6'))) + .then(() => + fs.writeFile( + path.join(directory, 'cached-release-4.5.6', 'test.zip'), + 'asfjohasda' + ) + ) + .then(() => ensureCleanReleaseCacheForVersion(directory, '1.2.3')) + .then(cacheDirectory => { + expect(cacheDirectory).toEqual( + path.join(directory, 'cached-release-1.2.3') + ) + return fs.readdir(directory) + }) + .then(contents => expect(contents).toEqual(['cached-release-1.2.3'])) + )) + it('should leave already-existing correct version cache directories untouched', () => + directoryWithCleanup(directory => + fs + .mkdir(path.join(directory, 'cached-release-1.2.3')) + .then(() => + fs.writeFile( + path.join(directory, 'cached-release-1.2.3', 'system.zip'), + '123123' + ) + ) + .then(() => ensureCleanReleaseCacheForVersion(directory, '1.2.3')) + .then(cacheDirectory => fs.readdir(cacheDirectory)) + .then(contents => { + expect(contents).toEqual(['system.zip']) + return fs.readFile( + path.join(directory, 'cached-release-1.2.3', 'system.zip'), + { encoding: 'utf-8' } + ) + }) + .then(contents => expect(contents).toEqual('123123')) + )) +}) + +describe('getReleaseFiles', () => { + it('should fail if no release files are cached', () => + directoryWithCleanup(directory => + expect( + getReleaseFiles( + { + fullImage: 'http://opentrons.com/fullImage.zip', + system: 'http://opentrons.com/ot3-system.zip', + version: 'http//opentrons.com/VERSION.json', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + }, + directory + ) + ).rejects.toThrow() + )) + it('should fail if system is not present but all others are', () => + directoryWithCleanup(directory => + fs + .writeFile(path.join(directory, 'fullImage.zip'), 'aslkdjasd') + .then(() => fs.writeFile(path.join(directory, 'VERSION.json'), 'asdas')) + .then(() => + fs.writeFile(path.join(directory, 'releaseNotes.md'), 'asdalsda') + ) + .then(() => + expect( + getReleaseFiles( + { + fullImage: 'http://opentrons.com/fullImage.zip', + system: 'http://opentrons.com/ot3-system.zip', + version: 'http//opentrons.com/VERSION.json', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + }, + directory + ) + ).rejects.toThrow() + ) + )) + it('should return available files if system.zip is one of them', () => + directoryWithCleanup(directory => + fs + .writeFile(path.join(directory, 'ot3-system.zip'), 'asdjlhasd') + .then(() => + expect( + getReleaseFiles( + { + fullImage: 'http://opentrons.com/fullImage.zip', + system: 'http://opentrons.com/ot3-system.zip', + version: 'http//opentrons.com/VERSION.json', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + }, + directory + ) + ).resolves.toEqual({ + system: path.join(directory, 'ot3-system.zip'), + releaseNotes: null, + releaseNotesContent: null, + }) + ) + )) + it('should find release notes if available', () => + directoryWithCleanup(directory => + fs + .writeFile(path.join(directory, 'ot3-system.zip'), 'asdjlhasd') + .then(() => + fs.writeFile(path.join(directory, 'releaseNotes.md'), 'asdasda') + ) + .then(() => + expect( + getReleaseFiles( + { + fullImage: 'http://opentrons.com/fullImage.zip', + system: 'http://opentrons.com/ot3-system.zip', + version: 'http//opentrons.com/VERSION.json', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + }, + directory + ) + ).resolves.toEqual({ + system: path.join(directory, 'ot3-system.zip'), + releaseNotes: path.join(directory, 'releaseNotes.md'), + releaseNotesContent: 'asdasda', + }) + ) + )) +}) + +describe('downloadReleaseFiles', () => { + afterEach(() => { + vi.resetAllMocks() + }) + it('should try and fetch both system zip and release notes', () => + directoryWithCleanup(directory => { + let tempSystemPath = '' + when(fetchToFile) + .calledWith( + 'http://opentrons.com/ot3-system.zip', + expect.any(String), + expect.any(Object) + ) + .thenDo((_url, dest, _opts) => { + tempSystemPath = dest + return fs + .writeFile(dest, 'this is the contents of the system.zip') + .then(() => dest) + }) + when(fetchToFile) + .calledWith( + 'http://opentrons.com/releaseNotes.md', + expect.any(String), + expect.any(Object) + ) + .thenDo((_url, dest) => { + return fs + .writeFile(dest, 'this is the contents of the release notes') + .then(() => dest) + }) + const progress = vi.fn() + return downloadReleaseFiles( + { + system: 'http://opentrons.com/ot3-system.zip', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + } as ReleaseSetUrls, + directory, + progress, + new AbortController() + ).then(files => { + expect(files).toEqual({ + system: path.join(directory, 'ot3-system.zip'), + releaseNotes: path.join(directory, 'releaseNotes.md'), + releaseNotesContent: 'this is the contents of the release notes', + }) + return Promise.all([ + fs + .readFile(files.system, { encoding: 'utf-8' }) + .then(contents => + expect(contents).toEqual('this is the contents of the system.zip') + ), + fs + .readFile(files.releaseNotes as string, { encoding: 'utf-8' }) + .then(contents => + expect(contents).toEqual( + 'this is the contents of the release notes' + ) + ), + expect(fs.stat(path.dirname(tempSystemPath))).rejects.toThrow(), + ]) + }) + })) + it('should fetch only system zip if only system is available', () => + directoryWithCleanup(directory => { + when(fetchToFile) + .calledWith( + 'http://opentrons.com/ot3-system.zip', + expect.any(String), + expect.any(Object) + ) + .thenDo((_url, dest, _opts) => { + return fs + .writeFile(dest, 'this is the contents of the system.zip') + .then(() => dest) + }) + const progress = vi.fn() + return downloadReleaseFiles( + { + system: 'http://opentrons.com/ot3-system.zip', + } as ReleaseSetUrls, + directory, + progress, + new AbortController() + ).then(files => { + expect(files).toEqual({ + system: path.join(directory, 'ot3-system.zip'), + releaseNotes: null, + releaseNotesContent: null, + }) + return fs + .readFile(files.system, { encoding: 'utf-8' }) + .then(contents => + expect(contents).toEqual('this is the contents of the system.zip') + ) + }) + })) + it('should tolerate failing to fetch release notes', () => + directoryWithCleanup(directory => { + when(fetchToFile) + .calledWith( + 'http://opentrons.com/ot3-system.zip', + expect.any(String), + expect.any(Object) + ) + .thenDo((_url, dest, _opts) => { + return fs + .writeFile(dest, 'this is the contents of the system.zip') + .then(() => dest) + }) + when(fetchToFile) + .calledWith( + 'http://opentrons.com/releaseNotes.md', + expect.any(String), + expect.any(Object) + ) + .thenReject(new Error('oh no!')) + const progress = vi.fn() + return downloadReleaseFiles( + { + system: 'http://opentrons.com/ot3-system.zip', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + } as ReleaseSetUrls, + directory, + progress, + new AbortController() + ).then(files => { + expect(files).toEqual({ + system: path.join(directory, 'ot3-system.zip'), + releaseNotes: null, + releaseNotesContent: null, + }) + return fs + .readFile(files.system, { encoding: 'utf-8' }) + .then(contents => + expect(contents).toEqual('this is the contents of the system.zip') + ) + }) + })) + it('should fail if it cannot fetch system zip', () => + directoryWithCleanup(directory => { + let tempSystemPath = '' + when(fetchToFile) + .calledWith( + 'http://opentrons.com/ot3-system.zip', + expect.any(String), + expect.any(Object) + ) + .thenReject(new Error('oh no')) + when(fetchToFile) + .calledWith( + 'http://opentrons.com/releaseNotes.md', + expect.any(String), + expect.any(Object) + ) + .thenDo((_url, dest) => { + tempSystemPath = dest + return fs + .writeFile(dest, 'this is the contents of the release notes') + .then(() => dest) + }) + const progress = vi.fn() + return expect( + downloadReleaseFiles( + { + system: 'http://opentrons.com/ot3-system.zip', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + } as ReleaseSetUrls, + directory, + progress, + new AbortController() + ) + ) + .rejects.toThrow() + .then(() => + expect(fs.stat(path.dirname(tempSystemPath))).rejects.toThrow() + ) + })) + it('should allow the http requests to be aborted', () => + directoryWithCleanup(directory => { + const aborter = new AbortController() + const progressCallback = vi.fn() + when(fetchToFile) + .calledWith('http://opentrons.com/ot3-system.zip', expect.any(String), { + onProgress: progressCallback, + signal: aborter.signal, + }) + .thenDo( + (_url, dest, options) => + new Promise((resolve, reject) => { + const listener = () => { + reject(options.signal.reason) + } + options.signal.addEventListener('abort', listener, { once: true }) + aborter.abort('oh no!') + return fs + .writeFile(dest, 'this is the contents of the system.zip') + .then(() => dest) + }) + ) + return expect( + downloadReleaseFiles( + { + system: 'http://opentrons.com/ot3-system.zip', + } as ReleaseSetUrls, + directory, + progressCallback, + aborter + ) + ).rejects.toThrow() + })) +}) + +describe('getOrDownloadReleaseFiles', () => { + it('should not download release files if they are cached', () => + directoryWithCleanup(directory => + fs + .writeFile(path.join(directory, 'ot3-system.zip'), 'asdjlhasd') + .then(() => + expect( + getOrDownloadReleaseFiles( + { + system: 'http://opentrons.com/ot3-system.zip', + releaseNotes: 'http://opentrons.com/releaseNotes.md', + } as ReleaseSetUrls, + directory, + vi.fn(), + new AbortController() + ) + ) + .resolves.toEqual({ + system: path.join(directory, 'ot3-system.zip'), + releaseNotes: null, + releaseNotesContent: null, + }) + .then(() => expect(fetchToFile).not.toHaveBeenCalled()) + ) + )) + it('should download release files if they are not cached', () => + directoryWithCleanup(directory => { + when(fetchToFile) + .calledWith( + 'http://opentrons.com/ot3-system.zip', + expect.any(String), + expect.any(Object) + ) + .thenDo((_url, dest, _opts) => { + return fs + .writeFile(dest, 'this is the contents of the system.zip') + .then(() => dest) + }) + + return expect( + getOrDownloadReleaseFiles( + { + system: 'http://opentrons.com/ot3-system.zip', + } as ReleaseSetUrls, + directory, + vi.fn(), + new AbortController() + ) + ) + .resolves.toEqual({ + system: path.join(directory, 'ot3-system.zip'), + releaseNotes: null, + releaseNotesContent: null, + }) + .then(() => + fs + .readFile(path.join(directory, 'ot3-system.zip'), { + encoding: 'utf-8', + }) + .then(contents => + expect(contents).toEqual('this is the contents of the system.zip') + ) + ) + })) + it('should fail if the file is not cached and can not be downloaded', () => + directoryWithCleanup(directory => { + when(fetchToFile) + .calledWith( + 'http://opentrons.com/ot3-system.zip', + expect.any(String), + expect.any(Object) + ) + .thenReject(new Error('oh no')) + + return expect( + getOrDownloadReleaseFiles( + { + system: 'http://opentrons.com/ot3-system.zip', + } as ReleaseSetUrls, + directory, + vi.fn(), + new AbortController() + ) + ).rejects.toThrow() + })) +}) diff --git a/app-shell-odd/src/system-update/from-web/__tests__/release-manifest.test.ts b/app-shell-odd/src/system-update/from-web/__tests__/release-manifest.test.ts new file mode 100644 index 00000000000..8062cd6b28b --- /dev/null +++ b/app-shell-odd/src/system-update/from-web/__tests__/release-manifest.test.ts @@ -0,0 +1,185 @@ +import { describe, it, vi, expect } from 'vitest' +import { when } from 'vitest-when' +import path from 'path' +import { readdir, writeFile, mkdir, readFile } from 'fs/promises' +import { fetchJson as _fetchJson } from '../../../http' +import { ensureCacheDir, getOrDownloadManifest } from '../release-manifest' +import { directoryWithCleanup } from '../../utils' + +vi.mock('../../../http') +// note: this doesn't look like it's needed but it is because http uses log +vi.mock('../../../log') +const fetchJson = vi.mocked(_fetchJson) + +const MOCK_MANIFEST = { + production: { + '1.2.3': { + fullImage: 'https://opentrons.com/no', + system: 'https://opentrons.com/no2', + version: 'https://opentrons.com/no3', + releaseNotes: 'https://opentrons.com/no4', + }, + }, +} + +describe('ensureCacheDirectory', () => { + it('should create the cache directory if it or its parents do not exist', () => + directoryWithCleanup(directory => + ensureCacheDir( + path.join(directory as string, 'somerandomname', 'someotherrandomname') + ) + .then(ensuredDirectory => { + expect(ensuredDirectory).toEqual( + path.join(directory, 'somerandomname', 'someotherrandomname') + ) + return readdir(path.join(directory, 'somerandomname'), { + withFileTypes: true, + }) + }) + .then(contents => { + expect(contents).toHaveLength(1) + expect(contents[0].isDirectory()).toBeTruthy() + expect(contents[0].name).toEqual('someotherrandomname') + return readdir(path.join(contents[0].path, contents[0].name)) + }) + .then(contents => { + expect(contents).toHaveLength(0) + }) + )) + it('should delete and recreate the cache directory if it is a file', () => + directoryWithCleanup(directory => + writeFile(path.join(directory, 'somerandomname'), 'alsdasda') + .then(() => ensureCacheDir(path.join(directory, 'somerandomname'))) + .then(ensuredDirectory => { + expect(ensuredDirectory).toEqual( + path.join(directory, 'somerandomname') + ) + return readdir(directory, { withFileTypes: true }) + }) + .then(contents => { + expect(contents).toHaveLength(1) + expect(contents[0].isDirectory()).toBeTruthy() + expect(contents[0].name).toEqual('somerandomname') + return readdir(path.join(contents[0].path, contents[0].name)) + }) + .then(contents => { + expect(contents).toHaveLength(0) + }) + )) + + it('should remove a non-file with the same name as the manifest file', () => + directoryWithCleanup(directory => + mkdir(path.join(directory, 'somerandomname', 'manifest.json'), { + recursive: true, + }) + .then(() => + writeFile( + path.join(directory, 'somerandomname', 'testfile'), + 'testdata' + ) + ) + .then(() => ensureCacheDir(path.join(directory, 'somerandomname'))) + .then(ensuredDirectory => readdir(ensuredDirectory)) + .then(contents => { + expect(contents).not.toContain('manifest.json') + return readFile(path.join(directory, 'somerandomname', 'testfile'), { + encoding: 'utf-8', + }) + }) + .then(contents => expect(contents).toEqual('testdata')) + )) + + it('should preserve extra contents of the directory if the directory exists', () => + directoryWithCleanup(directory => + mkdir(path.join(directory, 'somerandomname'), { recursive: true }) + .then(() => + writeFile( + path.join(directory, 'somerandomname', 'somerandomfile'), + 'somerandomdata' + ) + ) + .then(() => ensureCacheDir(path.join(directory, 'somerandomname'))) + .then(ensuredDirectory => { + expect(ensuredDirectory).toEqual( + path.join(directory, 'somerandomname') + ) + return readFile( + path.join(directory, 'somerandomname', 'somerandomfile'), + { encoding: 'utf-8' } + ) + }) + .then(contents => { + expect(contents).toEqual('somerandomdata') + return readdir(directory) + }) + .then(contents => expect(contents).toEqual(['somerandomname'])) + )) +}) + +describe('getOrDownloadManifest', () => { + const localManifest = { + production: { + '4.5.6': { + fullImage: 'https://opentrons.com/no', + system: 'https://opentrons.com/no2', + version: 'https://opentrons.com/no3', + releaseNotes: 'https://opentrons.com/no4', + }, + }, + } + it('should download a new manifest if possible', () => + directoryWithCleanup(directory => + writeFile( + path.join(directory, 'manifest.json'), + JSON.stringify(localManifest) + ) + .then(() => { + when(fetchJson) + .calledWith( + 'http://opentrons.com/releases.json', + expect.any(Object) + ) + .thenResolve(MOCK_MANIFEST) + return getOrDownloadManifest( + 'http://opentrons.com/releases.json', + directory, + new AbortController() + ) + }) + .then(manifest => expect(manifest).toEqual(MOCK_MANIFEST)) + )) + it('should use a cached manifest if the download fails', () => + directoryWithCleanup(directory => + writeFile( + path.join(directory, 'manifest.json'), + JSON.stringify(localManifest) + ) + .then(() => { + when(fetchJson) + .calledWith( + 'http://opentrons.com/releases.json', + expect.any(Object) + ) + .thenReject(new Error('oh no!')) + return getOrDownloadManifest( + 'http://opentrons.com/releases.json', + directory, + new AbortController() + ) + }) + .then(manifest => expect(manifest).toEqual(localManifest)) + )) + it('should reject if no manifest is available', () => + directoryWithCleanup(directory => { + when(fetchJson) + .calledWith('http://opentrons.com/releases.json', expect.any(Object)) + .thenReject(new Error('oh no!')) + return expect( + getOrDownloadManifest( + 'http://opentrons.com/releases.json', + directory, + new AbortController() + ) + ).rejects.toThrow() + })) +}) diff --git a/app-shell-odd/src/system-update/from-web/index.ts b/app-shell-odd/src/system-update/from-web/index.ts new file mode 100644 index 00000000000..0a9c34e3370 --- /dev/null +++ b/app-shell-odd/src/system-update/from-web/index.ts @@ -0,0 +1,2 @@ +export { getProvider } from './provider' +export type { WebUpdateSource } from './provider' diff --git a/app-shell-odd/src/system-update/from-web/latest-update.ts b/app-shell-odd/src/system-update/from-web/latest-update.ts new file mode 100644 index 00000000000..1a270c85ddd --- /dev/null +++ b/app-shell-odd/src/system-update/from-web/latest-update.ts @@ -0,0 +1,28 @@ +import semver from 'semver' + +const channelFinder = (version: string, channel: string): boolean => { + // return the latest alpha/beta if a user subscribes to alpha/beta updates + if (['alpha', 'beta'].includes(channel)) { + return version.includes(channel) + } else { + // otherwise get the latest stable version + return !version.includes('alpha') && !version.includes('beta') + } +} + +export const latestVersionForChannel = ( + availableVersions: string[], + channel: string +): string | null => + availableVersions + .filter(version => channelFinder(version, channel)) + .sort((a, b) => (semver.gt(a, b) ? 1 : -1)) + .pop() ?? null + +export const shouldUpdate = ( + currentVersion: string, + availableVersion: string | null +): string | null => + availableVersion != null && currentVersion !== availableVersion + ? availableVersion + : null diff --git a/app-shell-odd/src/system-update/from-web/provider.ts b/app-shell-odd/src/system-update/from-web/provider.ts new file mode 100644 index 00000000000..ca5c8da9fc9 --- /dev/null +++ b/app-shell-odd/src/system-update/from-web/provider.ts @@ -0,0 +1,209 @@ +import path from 'path' +import { rm } from 'fs/promises' + +import { createLogger } from '../../log' +import { LocalAbortError } from '../../http' + +import type { + UpdateProvider, + ResolvedUpdate, + UnresolvedUpdate, + ProgressCallback, + NoUpdate, +} from '../types' + +import { getOrDownloadManifest, getReleaseSet } from './release-manifest' +import { cleanUpAndGetOrDownloadReleaseFiles } from './release-files' +import { latestVersionForChannel, shouldUpdate } from './latest-update' + +import type { DownloadProgress } from '../../http' + +const log = createLogger('systemUpdate/from-web/provider') + +export interface WebUpdateSource { + manifestUrl: string + channel: string + updateCacheDirectory: string + currentVersion: string +} + +export function getProvider( + from: WebUpdateSource +): UpdateProvider { + let locked = false + let canceller = new AbortController() + const lockCache = (): void => { + locked = true + canceller.abort('cache locked') + canceller = new AbortController() + } + const versionCacheDir = path.join(from.updateCacheDirectory, 'versions') + const noUpdate = { + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + } as const + let currentUpdate: UnresolvedUpdate = noUpdate + let currentCheck: Promise | null = null + const updater = async ( + progress: ProgressCallback + ): Promise => { + const myCanceller = canceller + // this needs to be an `as`-assertion on the value because we can only guarantee that + // currentUpdate is resolved by the function of the program: we know that this function, + // which is the only thing that can alter currentUpdate, will always end with a resolved update, + // and we know that this function will not be running twice at the same time. + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + const previousUpdate = { + version: currentUpdate.version, + files: currentUpdate.files == null ? null : { ...currentUpdate.files }, + releaseNotes: currentUpdate.releaseNotes, + downloadProgress: currentUpdate.downloadProgress, + } as ResolvedUpdate + if (locked) { + throw new Error('cache locked') + } + const returnNoUpdate = (): NoUpdate => { + currentUpdate = noUpdate + progress(noUpdate) + return noUpdate + } + const manifest = await getOrDownloadManifest( + from.manifestUrl, + from.updateCacheDirectory, + myCanceller + ).catch((error: Error) => { + if (myCanceller.signal.aborted) { + log.info('aborted cache update because cache was locked') + currentUpdate = previousUpdate + progress(previousUpdate) + throw error + } + log.info( + `Failed to get or download update manifest: ${error.name}: ${error.message}` + ) + return null + }) + if (manifest == null) { + log.info(`no manifest found, returning`) + return returnNoUpdate() + } + const latestVersion = latestVersionForChannel( + Object.keys(manifest.production), + from.channel + ) + + const versionToUpdate = shouldUpdate(from.currentVersion, latestVersion) + if (versionToUpdate == null) { + log.debug(`no update found, returning`) + return returnNoUpdate() + } + const releaseUrls = getReleaseSet(manifest, versionToUpdate) + if (releaseUrls == null) { + log.debug(`no release urls found, returning`) + return returnNoUpdate() + } + log.info(`Finding version ${latestVersion}`) + const downloadingUpdate = { + version: latestVersion, + files: null, + releaseNotes: null, + downloadProgress: 0, + } as const + progress(downloadingUpdate) + currentUpdate = downloadingUpdate + + if (myCanceller.signal.aborted) { + log.info('aborted cache update because cache was locked') + currentUpdate = previousUpdate + progress(previousUpdate) + throw new LocalAbortError('cache locked') + } + const localFiles = await cleanUpAndGetOrDownloadReleaseFiles( + releaseUrls, + versionCacheDir, + versionToUpdate, + (downloadProgress: DownloadProgress): void => { + const downloadProgressPercent = + downloadProgress.size == null || downloadProgress.size === 0.0 + ? 0 + : (downloadProgress.downloaded / downloadProgress.size) * 100 + log.debug( + `Downloading update ${versionToUpdate}: ${downloadProgress.downloaded}/${downloadProgress.size}B (${downloadProgressPercent}%)` + ) + const update = { + version: versionToUpdate, + files: null, + releaseNotes: null, + downloadProgress: downloadProgressPercent, + } + currentUpdate = update + progress(update) + }, + myCanceller + ).catch((err: Error) => { + if (myCanceller.signal.aborted) { + currentUpdate = previousUpdate + progress(previousUpdate) + throw err + } else { + log.warn(`Failed to fetch update data: ${err.name}: ${err.message}`) + } + return null + }) + + if (localFiles == null) { + log.info( + `Download of ${versionToUpdate} failed, no release data is available` + ) + return returnNoUpdate() + } + if (myCanceller.signal.aborted) { + currentUpdate = previousUpdate + progress(previousUpdate) + throw new LocalAbortError('cache locked') + } + + const updateDetails = { + version: versionToUpdate, + files: { + system: localFiles.system, + releaseNotes: localFiles.releaseNotes, + }, + releaseNotes: localFiles.releaseNotesContent, + downloadProgress: 100, + } as const + currentUpdate = updateDetails + progress(updateDetails) + return updateDetails + } + return { + getUpdateDetails: () => currentUpdate, + refreshUpdateCache: (progress: ProgressCallback) => { + if (currentCheck != null) { + return new Promise((resolve, reject) => { + reject(new Error('Check already ongoing')) + }) + } else { + const updaterPromise = updater(progress) + currentCheck = updaterPromise + return updaterPromise.finally(() => { + currentCheck = null + }) + } + }, + + teardown: () => { + lockCache() + return rm(from.updateCacheDirectory, { recursive: true, force: true }) + }, + lockUpdateCache: lockCache, + unlockUpdateCache: () => { + locked = false + }, + name: () => + `WebUpdateProvider from ${from.manifestUrl} channel ${from.channel}`, + source: () => from, + } +} diff --git a/app-shell-odd/src/system-update/from-web/release-files.ts b/app-shell-odd/src/system-update/from-web/release-files.ts new file mode 100644 index 00000000000..a3c45cf5d42 --- /dev/null +++ b/app-shell-odd/src/system-update/from-web/release-files.ts @@ -0,0 +1,243 @@ +// functions for downloading and storing release files + +import path from 'path' +import tempy from 'tempy' +import { move, readdir, rm, mkdirp, readFile } from 'fs-extra' +import { fetchToFile } from '../../http' +import { createLogger } from '../../log' + +import type { DownloadProgress } from '../../http' +import type { ReleaseSetUrls, ReleaseSetFilepaths } from '../types' +import type { Dirent } from 'fs' + +const log = createLogger('systemUpdate/from-web/release-files') +const outPath = (dir: string, url: string): string => { + return path.join(dir, path.basename(url)) +} + +const RELEASE_DIRECTORY_PREFIX = 'cached-release-' + +export const directoryNameForRelease = (version: string): string => + `${RELEASE_DIRECTORY_PREFIX}${version}` + +export const directoryForRelease = ( + baseDirectory: string, + version: string +): string => path.join(baseDirectory, directoryNameForRelease(version)) + +async function ensureReleaseCache(baseDirectory: string): Promise { + try { + return await readdir(baseDirectory, { withFileTypes: true }) + } catch (error: any) { + console.log( + `Could not read download cache base directory: ${error.name}: ${error.message}: remaking` + ) + await rm(baseDirectory, { force: true, recursive: true }) + await mkdirp(baseDirectory) + return [] + } +} + +export const ensureCleanReleaseCacheForVersion = ( + baseDirectory: string, + version: string +): Promise => + ensureReleaseCache(baseDirectory) + .then(contents => + Promise.all( + contents.map(contained => + !contained.isDirectory() || + contained.name !== directoryNameForRelease(version) + ? rm(path.join(baseDirectory, contained.name), { + force: true, + recursive: true, + }) + : new Promise(resolve => { + resolve() + }) + ) + ) + ) + .then(() => mkdirp(directoryForRelease(baseDirectory, version))) + .then(() => directoryForRelease(baseDirectory, version)) + +export interface ReleaseSetData extends ReleaseSetFilepaths { + releaseNotesContent: string | null +} + +export const augmentWithReleaseNotesContent = ( + releaseFiles: ReleaseSetFilepaths +): Promise => + releaseFiles.releaseNotes == null + ? new Promise(resolve => { + resolve({ ...releaseFiles, releaseNotesContent: null }) + }) + : readReleaseNotes(releaseFiles.releaseNotes) + .then(releaseNotesContent => ({ ...releaseFiles, releaseNotesContent })) + .catch(err => { + log.error( + `Release notes should be present but cannot be read: ${err.name}: ${err.message}` + ) + return { ...releaseFiles, releaseNotesContent: null } + }) + +// checks `directory` for system update files matching the given `urls`, and +// downloads them if they can't be found +export function getReleaseFiles( + urls: ReleaseSetUrls, + directory: string +): Promise { + return readdir(directory).then((files: string[]) => { + log.info(`Files in system update download directory ${directory}: ${files}`) + const expected = { + system: path.basename(urls.system), + releaseNotes: + urls?.releaseNotes == null ? null : path.basename(urls.releaseNotes), + } + const foundFiles = files.reduce>( + ( + releaseSetFilePaths: Partial, + thisFile: string + ): Partial => { + if (thisFile === expected.system) { + return { ...releaseSetFilePaths, system: thisFile } + } + if ( + expected.releaseNotes != null && + thisFile === expected.releaseNotes + ) { + return { ...releaseSetFilePaths, releaseNotes: thisFile } + } + return releaseSetFilePaths + }, + {} + ) + if (foundFiles?.system != null) { + const files = { + system: outPath(directory, foundFiles.system), + releaseNotes: + foundFiles?.releaseNotes != null + ? outPath(directory, foundFiles.releaseNotes) + : null, + } + log.info( + `Found system file ${foundFiles.system} in cache directory ${directory}` + ) + return augmentWithReleaseNotesContent(files) + } + + throw new Error( + `no release files cached: could not find system file ${outPath( + directory, + urls.system + )} in ${files}` + ) + }) +} + +// downloads the entire release set to a temporary directory, and once they're +// all successfully downloaded, renames the directory to `directory` +export function downloadReleaseFiles( + urls: ReleaseSetUrls, + directory: string, + // `onProgress` will be called with download progress as the files are read + onProgress: (progress: DownloadProgress) => void, + canceller: AbortController +): Promise { + const tempDir: string = tempy.directory() + const tempSystemPath = outPath(tempDir, urls.system) + const tempNotesPath = outPath(tempDir, urls.releaseNotes ?? '') + // downloads are streamed directly to the filesystem to avoid loading them + // all into memory simultaneously + const notesReq = + urls.releaseNotes != null + ? fetchToFile(urls.releaseNotes, tempNotesPath, { + signal: canceller.signal, + }).catch(err => { + log.warn( + `release notes not available from ${urls.releaseNotes}: ${err.name}: ${err.message}` + ) + return null + }) + : Promise.resolve(null) + if (urls.releaseNotes != null) { + log.info(`Downloading ${urls.releaseNotes} to ${tempNotesPath}`) + } else { + log.info('No release notes available, not downloading') + } + log.info(`Downloading ${urls.system} to ${tempSystemPath}`) + const systemReq = fetchToFile(urls.system, tempSystemPath, { + onProgress, + signal: canceller.signal, + }) + return Promise.all([systemReq, notesReq]) + .then(results => { + const [systemTemp, releaseNotesTemp] = results + const systemPath = outPath(directory, systemTemp) + const notesPath = releaseNotesTemp + ? outPath(directory, releaseNotesTemp) + : null + + log.info(`Download complete, ${tempDir}=>${directory}`) + + return move(tempDir, directory, { overwrite: true }).then(() => { + log.info(`Move complete`) + return augmentWithReleaseNotesContent({ + system: systemPath, + releaseNotes: notesPath, + }) + }) + }) + .catch(error => { + log.error( + `Failed to download release files: ${error.name}: ${error.message}` + ) + return rm(tempDir, { force: true, recursive: true }).then(() => { + throw error + }) + }) +} + +export async function getOrDownloadReleaseFiles( + urls: ReleaseSetUrls, + releaseCacheDirectory: string, + onProgress: (progress: DownloadProgress) => void, + canceller: AbortController +): Promise { + try { + return await getReleaseFiles(urls, releaseCacheDirectory) + } catch (error: any) { + log.info( + `Could not find cached release files for ${releaseCacheDirectory}: ${error.name}: ${error.message}, attempting to download` + ) + return await downloadReleaseFiles( + urls, + releaseCacheDirectory, + onProgress, + canceller + ) + } +} + +export const cleanUpAndGetOrDownloadReleaseFiles = ( + urls: ReleaseSetUrls, + baseDirectory: string, + version: string, + onProgress: (progress: DownloadProgress) => void, + canceller: AbortController +): Promise => + ensureCleanReleaseCacheForVersion(baseDirectory, version).then(versionCache => + getOrDownloadReleaseFiles(urls, versionCache, onProgress, canceller) + ) + +const readReleaseNotes = (path: string | null): Promise => + path == null + ? new Promise(resolve => { + resolve(null) + }) + : readFile(path, { encoding: 'utf-8' }).catch(err => { + log.warn( + `Could not read release notes from ${path}: ${err.name}: ${err.message}` + ) + return null + }) diff --git a/app-shell-odd/src/system-update/from-web/release-manifest.ts b/app-shell-odd/src/system-update/from-web/release-manifest.ts new file mode 100644 index 00000000000..9433067cb17 --- /dev/null +++ b/app-shell-odd/src/system-update/from-web/release-manifest.ts @@ -0,0 +1,101 @@ +import * as FS from 'fs/promises' +import path from 'path' +import { readJson, outputJson } from 'fs-extra' + +import type { Stats } from 'fs' +import { fetchJson, LocalAbortError } from '../../http' +import type { ReleaseManifest, ReleaseSetUrls } from '../types' +import { createLogger } from '../../log' + +const log = createLogger('systemUpdate/from-web/provider') + +export function getReleaseSet( + manifest: ReleaseManifest, + version: string +): ReleaseSetUrls | null { + return manifest.production[version] ?? null +} + +export const getCachedReleaseManifest = ( + cacheDir: string +): Promise => readJson(`${cacheDir}/manifest.json`) + +const removeAndRemake = (directory: string): Promise => + FS.rm(directory, { recursive: true, force: true }) + .then(() => FS.mkdir(directory, { recursive: true })) + .then(() => FS.stat(directory)) + +export const ensureCacheDir = (directory: string): Promise => + FS.stat(directory) + .catch(() => removeAndRemake(directory)) + .then(stats => + stats.isDirectory() + ? new Promise(resolve => { + resolve(stats) + }) + : removeAndRemake(directory) + ) + .then(() => FS.readdir(directory, { withFileTypes: true })) + .then(contents => { + const manifestCandidate = contents.find( + entry => entry.name === 'manifest.json' + ) + if (manifestCandidate == null || manifestCandidate.isFile()) { + return new Promise(resolve => { + resolve(directory) + }) + } + return FS.rm(path.join(directory, 'manifest.json'), { + force: true, + recursive: true, + }).then(() => directory) + }) + +export const downloadManifest = ( + manifestUrl: string, + cacheDir: string, + cancel: AbortController +): Promise => { + log.info(`Attempting to fetch release manifest from ${manifestUrl}`) + return fetchJson(manifestUrl, { + signal: cancel.signal, + }).then(manifest => { + log.info('Fetched release manifest OK') + return outputJson(path.join(cacheDir, 'manifest.json'), manifest).then( + () => manifest + ) + }) +} + +export const ensureCacheDirAndDownloadManifest = ( + manifestUrl: string, + cacheDir: string, + cancel: AbortController +): Promise => + ensureCacheDir(cacheDir).then(ensuredCacheDir => + downloadManifest(manifestUrl, ensuredCacheDir, cancel) + ) + +export async function getOrDownloadManifest( + manifestUrl: string, + cacheDir: string, + cancel: AbortController +): Promise { + try { + return await ensureCacheDirAndDownloadManifest( + manifestUrl, + cacheDir, + cancel + ) + } catch (error: any) { + if (error instanceof LocalAbortError) { + log.info('Aborted during manifest fetch') + throw error + } else { + log.info( + `Could not fetch manifest: ${error.name}: ${error.message}, falling back to cached` + ) + return await getCachedReleaseManifest(cacheDir) + } + } +} diff --git a/app-shell-odd/src/system-update/handler.ts b/app-shell-odd/src/system-update/handler.ts new file mode 100644 index 00000000000..8344578e9fa --- /dev/null +++ b/app-shell-odd/src/system-update/handler.ts @@ -0,0 +1,380 @@ +// system update handler + +import Semver from 'semver' + +import { CONFIG_INITIALIZED, VALUE_UPDATED } from '../constants' +import { createLogger } from '../log' +import { postFile } from '../http' +import { getConfig } from '../config' +import { getSystemUpdateDir } from './directories' +import { SYSTEM_FILENAME, FLEX_MANIFEST_URL } from './constants' +import { getProvider as getWebUpdateProvider } from './from-web' +import { getProvider as getUsbUpdateProvider } from './from-usb' + +import type { Action, Dispatch } from '../types' +import type { UpdateProvider, UnresolvedUpdate, ReadyUpdate } from './types' +import type { USBUpdateSource } from './from-usb' + +export const CURRENT_SYSTEM_VERSION = _PKG_VERSION_ + +const log = createLogger('system-update/handler') + +export interface UpdateDriver { + handleAction: (action: Action) => Promise + reload: () => Promise + shouldReload: () => boolean + teardown: () => Promise +} + +export function createUpdateDriver(dispatch: Dispatch): UpdateDriver { + log.info(`Running robot system updates storing to ${getSystemUpdateDir()}`) + + let webUpdate: UnresolvedUpdate = { + version: null, + files: null, + releaseNotes: null, + downloadProgress: 0, + } + let webProvider = getWebUpdateProvider({ + manifestUrl: FLEX_MANIFEST_URL, + channel: getConfig('update').channel, + updateCacheDirectory: getSystemUpdateDir(), + currentVersion: CURRENT_SYSTEM_VERSION, + }) + const usbProviders: Record> = {} + let currentBestUsbUpdate: + | (ReadyUpdate & { providerName: string }) + | null = null + + const updateBestUsbUpdate = (): void => { + currentBestUsbUpdate = null + Object.values(usbProviders).forEach(provider => { + const providerUpdate = provider.getUpdateDetails() + if (providerUpdate.files == null) { + // nothing to do, keep null + } else if (currentBestUsbUpdate == null) { + currentBestUsbUpdate = { + ...(providerUpdate as ReadyUpdate), + providerName: provider.name(), + } + } else if ( + Semver.gt(providerUpdate.version, currentBestUsbUpdate.version) + ) { + currentBestUsbUpdate = { + ...(providerUpdate as ReadyUpdate), + providerName: provider.name(), + } + } + }) + } + + const dispatchStaticUpdateData = (): void => { + if (currentBestUsbUpdate != null) { + dispatchUpdateInfo( + { + version: currentBestUsbUpdate.version, + releaseNotes: currentBestUsbUpdate.releaseNotes, + force: true, + }, + dispatch + ) + } else { + dispatchUpdateInfo( + { + version: webUpdate.version, + releaseNotes: webUpdate.releaseNotes, + force: false, + }, + dispatch + ) + } + } + + return { + handleAction: (action: Action): Promise => { + switch (action.type) { + case 'shell:CHECK_UPDATE': + return webProvider + .refreshUpdateCache(updateStatus => { + webUpdate = updateStatus + if (currentBestUsbUpdate == null) { + if ( + updateStatus.version != null && + updateStatus.files == null && + updateStatus.downloadProgress === 0 + ) { + dispatch({ + type: 'robotUpdate:UPDATE_VERSION', + payload: { + version: updateStatus.version, + force: false, + target: 'flex', + }, + }) + } else if ( + updateStatus.version != null && + updateStatus.files == null && + updateStatus.downloadProgress !== 0 + ) { + dispatch({ + // TODO: change this action type to 'systemUpdate:DOWNLOAD_PROGRESS' + type: 'robotUpdate:DOWNLOAD_PROGRESS', + payload: { + progress: updateStatus.downloadProgress, + target: 'flex', + }, + }) + } else if (updateStatus.files != null) { + dispatchStaticUpdateData() + } + } + }) + .catch(err => { + log.warn( + `Error finding updates with ${webProvider.name()}: ${ + err.name + }: ${err.message}` + ) + return { + version: null, + files: null, + downloadProgress: 0, + releaseNotes: null, + } as const + }) + .then(result => { + webUpdate = result + dispatchStaticUpdateData() + }) + case 'shell:ROBOT_MASS_STORAGE_DEVICE_ENUMERATED': + log.info( + `mass storage device enumerated at ${action.payload.rootPath}` + ) + if (usbProviders[action.payload.rootPath] != null) { + return new Promise(resolve => { + resolve() + }) + } + usbProviders[action.payload.rootPath] = getUsbUpdateProvider({ + currentVersion: CURRENT_SYSTEM_VERSION, + massStorageDeviceRoot: action.payload.rootPath, + massStorageDeviceFiles: action.payload.filePaths, + }) + return usbProviders[action.payload.rootPath] + .refreshUpdateCache(() => {}) + .then(() => { + updateBestUsbUpdate() + dispatchStaticUpdateData() + }) + .catch(err => { + log.error( + `Failed to get updates from ${action.payload.rootPath}: ${err.name}: ${err.message}` + ) + }) + + case 'shell:ROBOT_MASS_STORAGE_DEVICE_REMOVED': + log.info(`mass storage removed at ${action.payload.rootPath}`) + const provider = usbProviders[action.payload.rootPath] + if (provider != null) { + return provider + .teardown() + .then(() => { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete usbProviders[action.payload.rootPath] + updateBestUsbUpdate() + }) + .catch(err => { + log.error( + `Failed to tear down provider ${provider.name()}: ${ + err.name + }: ${err.message}` + ) + }) + .then(() => { + dispatchStaticUpdateData() + }) + } + return new Promise(resolve => { + resolve() + }) + case 'robotUpdate:UPLOAD_FILE': { + const { host, path, systemFile } = action.payload + // eslint-disable-next-line @typescript-eslint/no-floating-promises + return postFile( + `http://${host.ip}:${host.port}${path}`, + SYSTEM_FILENAME, + systemFile + ) + .then(() => ({ + type: 'robotUpdate:FILE_UPLOAD_DONE' as const, + payload: host.name, + })) + .catch((error: Error) => { + log.warn('Error uploading update to robot', { + path, + systemFile, + error, + }) + + return { + type: 'robotUpdate:UNEXPECTED_ERROR' as const, + payload: { + message: `Error uploading update to robot: ${error.message}`, + }, + } + }) + .then(dispatch) + } + case 'robotUpdate:READ_SYSTEM_FILE': { + const getDetails = (): { + systemFile: string + version: string + isManualFile: false + } | null => { + if (currentBestUsbUpdate) { + return { + systemFile: currentBestUsbUpdate.files.system, + version: currentBestUsbUpdate.version, + isManualFile: false, + } + } else if (webUpdate.files?.system != null) { + return { + systemFile: webUpdate.files.system, + version: webUpdate.version as string, // version is string if files is not null + isManualFile: false, + } + } else { + return null + } + } + return new Promise(resolve => { + const details = getDetails() + if (details == null) { + dispatch({ + type: 'robotUpdate:UNEXPECTED_ERROR', + payload: { message: 'System update file not downloaded' }, + }) + resolve() + return + } + + dispatch({ + type: 'robotUpdate:FILE_INFO' as const, + payload: details, + }) + resolve() + }) + } + case 'robotUpdate:READ_USER_FILE': { + return new Promise(resolve => { + dispatch({ + type: 'robotUpdate:UNEXPECTED_ERROR', + payload: { + message: 'Updates of this kind are not implemented for ODD', + }, + }) + resolve() + }) + } + } + return new Promise(resolve => { + resolve() + }) + }, + reload: () => { + webProvider.lockUpdateCache() + return webProvider + .teardown() + .catch(err => { + log.error( + `Failed to tear down web provider ${webProvider.name()}: ${ + err.name + }: ${err.message}` + ) + }) + .then(() => { + webProvider = getWebUpdateProvider({ + manifestUrl: FLEX_MANIFEST_URL, + channel: getConfig('update').channel, + updateCacheDirectory: getSystemUpdateDir(), + currentVersion: CURRENT_SYSTEM_VERSION, + }) + }) + .catch(err => { + const message = `System updates failed to handle config change: ${err.name}: ${err.message}` + log.error(message) + dispatch({ + type: 'robotUpdate:UNEXPECTED_ERROR', + payload: { message: message }, + }) + }) + }, + shouldReload: () => + getConfig('update').channel !== webProvider.source().channel, + teardown: () => { + return Promise.allSettled([ + webProvider.teardown(), + ...Object.values(usbProviders).map(provider => provider.teardown()), + ]) + .catch(errs => { + log.error(`Failed to tear down some providers: ${errs}`) + }) + .then(results => { + log.info('all providers torn down') + }) + }, + } +} + +export interface UpdatableDriver { + getUpdateDriver: () => UpdateDriver | null + handleAction: (action: Action) => Promise +} + +export function manageDriver(dispatch: Dispatch): UpdatableDriver { + let updateDriver: UpdateDriver | null = null + return { + handleAction: action => { + if (action.type === CONFIG_INITIALIZED) { + log.info('Initializing update driver') + return new Promise(resolve => { + updateDriver = createUpdateDriver(dispatch) + resolve() + }) + } else if (updateDriver != null) { + if (action.type === VALUE_UPDATED && updateDriver.shouldReload()) { + return updateDriver.reload() + } else { + return updateDriver.handleAction(action) + } + } else { + return new Promise(resolve => { + log.warn( + `update driver manager received action ${action.type} before initialization` + ) + resolve() + }) + } + }, + getUpdateDriver: () => updateDriver, + } +} + +export function registerRobotSystemUpdate(dispatch: Dispatch): Dispatch { + return manageDriver(dispatch).handleAction +} + +const dispatchUpdateInfo = ( + info: { version: string | null; releaseNotes: string | null; force: boolean }, + dispatch: Dispatch +): void => { + const { version, releaseNotes, force } = info + dispatch({ + type: 'robotUpdate:UPDATE_INFO', + payload: { releaseNotes, version, force, target: 'flex' }, + }) + dispatch({ + type: 'robotUpdate:UPDATE_VERSION', + payload: { version, force, target: 'flex' }, + }) +} diff --git a/app-shell-odd/src/system-update/index.ts b/app-shell-odd/src/system-update/index.ts index 7d8e62fb8ac..4ec36b05a57 100644 --- a/app-shell-odd/src/system-update/index.ts +++ b/app-shell-odd/src/system-update/index.ts @@ -1,394 +1,2 @@ // system update files -import path from 'path' -import { ensureDir } from 'fs-extra' -import { readFile } from 'fs/promises' -import StreamZip from 'node-stream-zip' -import Semver from 'semver' -import { UI_INITIALIZED } from '../constants' -import { createLogger } from '../log' -import { - getLatestSystemUpdateUrls, - getLatestVersion, - isUpdateAvailable, - updateLatestVersion, -} from '../update' -import { - getReleaseFiles, - readUserFileInfo, - cleanupReleaseFiles, -} from './release-files' -import { uploadSystemFile } from './update' -import { getSystemUpdateDir } from './directories' - -import type { DownloadProgress } from '../http' -import type { Action, Dispatch } from '../types' -import type { ReleaseSetFilepaths } from './types' - -const log = createLogger('systemUpdate/index') -const REASONABLE_VERSION_FILE_SIZE_B = 4096 - -let isGettingLatestSystemFiles = false -const isGettingMassStorageUpdatesFrom: Set = new Set() -let massStorageUpdateSet: ReleaseSetFilepaths | null = null -let systemUpdateSet: ReleaseSetFilepaths | null = null - -const readFileInfoAndDispatch = ( - dispatch: Dispatch, - fileName: string, - isManualFile: boolean = false -): Promise => - readUserFileInfo(fileName) - .then(fileInfo => ({ - type: 'robotUpdate:FILE_INFO' as const, - payload: { - systemFile: fileInfo.systemFile, - version: fileInfo.versionInfo.opentrons_api_version, - isManualFile, - }, - })) - .catch((error: Error) => ({ - type: 'robotUpdate:UNEXPECTED_ERROR' as const, - payload: { message: error.message }, - })) - .then(dispatch) - -export function registerRobotSystemUpdate(dispatch: Dispatch): Dispatch { - log.info(`Running robot system updates storing to ${getSystemUpdateDir()}`) - return function handleAction(action: Action) { - switch (action.type) { - case UI_INITIALIZED: - case 'shell:CHECK_UPDATE': - // short circuit early if we're already downloading the latest system files - if (isGettingLatestSystemFiles) { - log.info(`system update download already in progress`) - return - } - updateLatestVersion() - .then(() => { - if (isUpdateAvailable() && !isGettingLatestSystemFiles) { - isGettingLatestSystemFiles = true - return getLatestSystemUpdateFiles(dispatch) - } - }) - .then(() => { - isGettingLatestSystemFiles = false - }) - .catch((error: Error) => { - log.warn('Error checking for update', { - error, - }) - isGettingLatestSystemFiles = false - }) - - break - - case 'robotUpdate:UPLOAD_FILE': { - const { host, path, systemFile } = action.payload - // eslint-disable-next-line @typescript-eslint/no-floating-promises - uploadSystemFile(host, path, systemFile) - .then(() => ({ - type: 'robotUpdate:FILE_UPLOAD_DONE' as const, - payload: host.name, - })) - .catch((error: Error) => { - log.warn('Error uploading update to robot', { - path, - systemFile, - error, - }) - - return { - type: 'robotUpdate:UNEXPECTED_ERROR' as const, - payload: { - message: `Error uploading update to robot: ${error.message}`, - }, - } - }) - .then(dispatch) - - break - } - - case 'robotUpdate:READ_USER_FILE': { - const { systemFile } = action.payload as { systemFile: string } - // eslint-disable-next-line @typescript-eslint/no-floating-promises - readFileInfoAndDispatch(dispatch, systemFile, true) - break - } - case 'robotUpdate:READ_SYSTEM_FILE': { - const systemFile = - massStorageUpdateSet?.system ?? systemUpdateSet?.system - if (systemFile == null) { - dispatch({ - type: 'robotUpdate:UNEXPECTED_ERROR', - payload: { message: 'System update file not downloaded' }, - }) - return - } - // eslint-disable-next-line @typescript-eslint/no-floating-promises - readFileInfoAndDispatch(dispatch, systemFile) - break - } - case 'shell:ROBOT_MASS_STORAGE_DEVICE_ENUMERATED': - if (isGettingMassStorageUpdatesFrom.has(action.payload.rootPath)) { - return - } - isGettingMassStorageUpdatesFrom.add(action.payload.rootPath) - getLatestMassStorageUpdateFiles(action.payload.filePaths, dispatch) - .then(() => { - isGettingMassStorageUpdatesFrom.delete(action.payload.rootPath) - }) - .catch(() => { - isGettingMassStorageUpdatesFrom.delete(action.payload.rootPath) - }) - break - case 'shell:ROBOT_MASS_STORAGE_DEVICE_REMOVED': - if ( - massStorageUpdateSet !== null && - massStorageUpdateSet.system.startsWith(action.payload.rootPath) - ) { - console.log( - `Mass storage device ${action.payload.rootPath} removed, reverting to non-usb updates` - ) - massStorageUpdateSet = null - getCachedSystemUpdateFiles(dispatch) - } else { - console.log( - `Mass storage device ${action.payload.rootPath} removed but this was not an update source` - ) - } - break - } - } -} - -const getVersionFromOpenedZipIfValid = (zip: StreamZip): Promise => - new Promise((resolve, reject) => { - Object.values(zip.entries()).forEach(entry => { - if ( - entry.isFile && - entry.name === 'VERSION.json' && - entry.size < REASONABLE_VERSION_FILE_SIZE_B - ) { - const contents = zip.entryDataSync(entry.name).toString('ascii') - try { - const parsedContents = JSON.parse(contents) - if (parsedContents?.robot_type !== 'OT-3 Standard') { - reject(new Error('not a Flex release file')) - } - const fileVersion = parsedContents?.opentrons_api_version - const version = Semver.valid(fileVersion as string) - if (version === null) { - reject(new Error(`${fileVersion} is not a valid version`)) - } else { - resolve(version) - } - } catch (error) { - reject(error) - } - } - }) - }) - -interface FileDetails { - path: string - version: string -} - -const getVersionFromZipIfValid = (path: string): Promise => - new Promise((resolve, reject) => { - const zip = new StreamZip({ file: path, storeEntries: true }) - zip.on('ready', () => { - getVersionFromOpenedZipIfValid(zip) - .then(version => { - zip.close() - resolve({ version, path }) - }) - .catch(err => { - zip.close() - reject(err) - }) - }) - zip.on('error', err => { - zip.close() - reject(err) - }) - }) - -const fakeReleaseNotesForMassStorage = (version: string): string => ` -# Opentrons Robot Software Version ${version} - -This update is from a USB mass storage device connected to your Flex, and release notes cannot be shown. - -Don't remove the USB mass storage device while the update is in progress. -` - -export const getLatestMassStorageUpdateFiles = ( - filePaths: string[], - dispatch: Dispatch -): Promise => - Promise.all( - filePaths.map(path => - path.endsWith('.zip') - ? getVersionFromZipIfValid(path).catch(() => null) - : new Promise(resolve => { - resolve(null) - }) - ) - ).then(values => { - const update = values.reduce( - (prev, current) => - prev === null - ? current === null - ? prev - : current - : current === null - ? prev - : Semver.gt(current.version, prev.version) - ? current - : prev, - null - ) - if (update === null) { - console.log('no updates found in mass storage device') - } else { - console.log(`found update to version ${update.version} on mass storage`) - const releaseNotes = fakeReleaseNotesForMassStorage(update.version) - massStorageUpdateSet = { system: update.path, releaseNotes } - dispatchUpdateInfo( - { version: update.version, releaseNotes, force: true }, - dispatch - ) - } - }) - -const dispatchUpdateInfo = ( - info: { version: string | null; releaseNotes: string | null; force: boolean }, - dispatch: Dispatch -): void => { - const { version, releaseNotes, force } = info - dispatch({ - type: 'robotUpdate:UPDATE_INFO', - payload: { releaseNotes, version, force, target: 'flex' }, - }) - dispatch({ - type: 'robotUpdate:UPDATE_VERSION', - payload: { version, force, target: 'flex' }, - }) -} - -// Get latest system update version -// 1. Ensure the system update directory exists -// 2. Get the manifest file from the local cache -// 3. Get the release files according to the manifest -// a. If the files need downloading, dispatch progress updates to UI -// 4. Cache the filepaths of the update files in memory -// 5. Dispatch info or error to UI -export function getLatestSystemUpdateFiles( - dispatch: Dispatch -): Promise { - const fileDownloadDir = path.join( - getSystemUpdateDir(), - 'robot-system-updates' - ) - - return ensureDir(getSystemUpdateDir()) - .then(() => getLatestSystemUpdateUrls()) - .then(urls => { - if (urls === null) { - const latestVersion = getLatestVersion() - log.warn('No release files in manifest', { - version: latestVersion, - }) - return Promise.reject( - new Error(`No release files in manifest for version ${latestVersion}`) - ) - } - - let prevPercentDone = 0 - - const handleProgress = (progress: DownloadProgress): void => { - const { downloaded, size } = progress - if (size !== null) { - const percentDone = Math.round((downloaded / size) * 100) - if (Math.abs(percentDone - prevPercentDone) > 0) { - if (massStorageUpdateSet === null) { - dispatch({ - // TODO: change this action type to 'systemUpdate:DOWNLOAD_PROGRESS' - type: 'robotUpdate:DOWNLOAD_PROGRESS', - payload: { progress: percentDone, target: 'flex' }, - }) - } - prevPercentDone = percentDone - } - } - } - - return getReleaseFiles(urls, fileDownloadDir, handleProgress) - .then(filepaths => { - return cacheUpdateSet(filepaths) - }) - .then(updateInfo => { - massStorageUpdateSet === null && - dispatchUpdateInfo({ force: false, ...updateInfo }, dispatch) - }) - .catch((error: Error) => { - dispatch({ - type: 'robotUpdate:DOWNLOAD_ERROR', - payload: { error: error.message, target: 'flex' }, - }) - }) - .then(() => - cleanupReleaseFiles(getSystemUpdateDir(), 'robot-system-updates') - ) - .catch((error: Error) => { - log.warn('Unable to cleanup old release files', { error }) - }) - }) -} - -export function getCachedSystemUpdateFiles( - dispatch: Dispatch -): Promise { - if (systemUpdateSet) { - return getInfoFromUpdateSet(systemUpdateSet) - .then(updateInfo => { - dispatchUpdateInfo({ force: false, ...updateInfo }, dispatch) - }) - .catch(err => { - console.log(`Could not get info from update set: ${err}`) - }) - } else { - dispatchUpdateInfo( - { version: null, releaseNotes: null, force: false }, - dispatch - ) - return new Promise(resolve => { - resolve('no files') - }) - } -} - -function getInfoFromUpdateSet( - filepaths: ReleaseSetFilepaths -): Promise<{ version: string; releaseNotes: string | null }> { - const version = getLatestVersion() - const releaseNotesContentPromise = filepaths.releaseNotes - ? readFile(filepaths.releaseNotes, 'utf8') - : new Promise(resolve => { - resolve(null) - }) - return releaseNotesContentPromise - .then(releaseNotes => ({ - version: version, - releaseNotes, - })) - .catch(() => ({ version: version, releaseNotes: '' })) -} - -function cacheUpdateSet( - filepaths: ReleaseSetFilepaths -): Promise<{ version: string; releaseNotes: string | null }> { - systemUpdateSet = filepaths - return getInfoFromUpdateSet(systemUpdateSet) -} +export { registerRobotSystemUpdate } from './handler' diff --git a/app-shell-odd/src/system-update/release-files.ts b/app-shell-odd/src/system-update/release-files.ts deleted file mode 100644 index 6ea57648d05..00000000000 --- a/app-shell-odd/src/system-update/release-files.ts +++ /dev/null @@ -1,148 +0,0 @@ -// functions for downloading and storing release files -import assert from 'assert' -import path from 'path' -import { promisify } from 'util' -import tempy from 'tempy' -import { move, readdir, remove } from 'fs-extra' -import StreamZip from 'node-stream-zip' -import getStream from 'get-stream' - -import { createLogger } from '../log' -import { fetchToFile } from '../http' -import type { DownloadProgress } from '../http' -import type { ReleaseSetUrls, ReleaseSetFilepaths, UserFileInfo } from './types' - -const VERSION_FILENAME = 'VERSION.json' - -const log = createLogger('systemUpdate/release-files') -const outPath = (dir: string, url: string): string => { - return path.join(dir, path.basename(url)) -} - -// checks `directory` for system update files matching the given `urls`, and -// downloads them if they can't be found -export function getReleaseFiles( - urls: ReleaseSetUrls, - directory: string, - onProgress: (progress: DownloadProgress) => unknown -): Promise { - return readdir(directory) - .catch(error => { - log.warn('Error retrieving files from filesystem', { error }) - return [] - }) - .then((files: string[]) => { - log.debug('Files in system update download directory', { files }) - const system = outPath(directory, urls.system) - const releaseNotes = outPath(directory, urls.releaseNotes ?? '') - - // TODO: check for release notes when OT-3 manifest points to real release notes - if (files.some(f => f === path.basename(system))) { - return { system, releaseNotes } - } - - return downloadReleaseFiles(urls, directory, onProgress) - }) -} - -// downloads the entire release set to a temporary directory, and once they're -// all successfully downloaded, renames the directory to `directory` -// TODO(mc, 2019-07-09): DRY this up if/when more than 2 files are required -export function downloadReleaseFiles( - urls: ReleaseSetUrls, - directory: string, - // `onProgress` will be called with download progress as the files are read - onProgress: (progress: DownloadProgress) => unknown -): Promise { - const tempDir: string = tempy.directory() - const tempSystemPath = outPath(tempDir, urls.system) - const tempNotesPath = outPath(tempDir, urls.releaseNotes ?? '') - - log.debug('directory created for robot update downloads', { tempDir }) - - // downloads are streamed directly to the filesystem to avoid loading them - // all into memory simultaneously - const systemReq = fetchToFile(urls.system, tempSystemPath, { onProgress }) - const notesReq = urls.releaseNotes - ? fetchToFile(urls.releaseNotes, tempNotesPath) - : Promise.resolve(null) - - return Promise.all([systemReq, notesReq]).then(results => { - const [systemTemp, releaseNotesTemp] = results - const systemPath = outPath(directory, systemTemp) - const notesPath = releaseNotesTemp - ? outPath(directory, releaseNotesTemp) - : null - - log.debug('renaming directory', { from: tempDir, to: directory }) - - return move(tempDir, directory, { overwrite: true }).then(() => ({ - system: systemPath, - releaseNotes: notesPath, - })) - }) -} - -export function readUserFileInfo(systemFile: string): Promise { - const openZip = new Promise((resolve, reject) => { - const zip = new StreamZip({ file: systemFile, storeEntries: true }) - .once('ready', handleReady) - .once('error', handleError) - - function handleReady(): void { - cleanup() - resolve(zip) - } - - function handleError(error: Error): void { - cleanup() - zip.close() - reject(error) - } - - function cleanup(): void { - zip.removeListener('ready', handleReady) - zip.removeListener('error', handleError) - } - }) - - return openZip.then(zip => { - const entries = zip.entries() - const streamFromZip = promisify(zip.stream.bind(zip)) - - assert(VERSION_FILENAME in entries, `${VERSION_FILENAME} not in archive`) - - const result = streamFromZip(VERSION_FILENAME) - // @ts-expect-error(mc, 2021-02-17): stream may be undefined - .then(getStream) - .then(JSON.parse) - .then(versionInfo => ({ - systemFile, - versionInfo, - })) - - result.finally(() => { - zip.close() - }) - - return result - }) -} - -export function cleanupReleaseFiles( - downloadsDir: string, - currentRelease: string -): Promise { - log.debug('deleting release files not part of release ', currentRelease) - - return readdir(downloadsDir, { withFileTypes: true }) - .then(files => { - return ( - files - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - .filter(f => f.isDirectory() && f.name !== currentRelease) - .map(f => path.join(downloadsDir, f.name)) - ) - }) - .then(removals => Promise.all(removals.map(f => remove(f)))) -} diff --git a/app-shell-odd/src/system-update/release-manifest.ts b/app-shell-odd/src/system-update/release-manifest.ts deleted file mode 100644 index d27c8a04449..00000000000 --- a/app-shell-odd/src/system-update/release-manifest.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { readJson, outputJson } from 'fs-extra' -import { fetchJson } from '../http' -import { createLogger } from '../log' -import { getManifestCacheDir } from './directories' -import type { ReleaseManifest, ReleaseSetUrls } from './types' - -const log = createLogger('systemUpdate/release-manifest') - -export function getReleaseSet( - manifest: ReleaseManifest, - version: string -): ReleaseSetUrls | null { - return manifest.production[version] ?? null -} - -export const getCachedReleaseManifest = (): Promise => - readJson(getManifestCacheDir()) - -export const downloadAndCacheReleaseManifest = ( - manifestUrl: string -): Promise => - fetchJson(manifestUrl) - .then(manifest => { - return outputJson(getManifestCacheDir(), manifest).then(() => manifest) - }) - .catch((error: Error) => { - log.error('Error downloading the release manifest', { error }) - return readJson(getManifestCacheDir()) - }) diff --git a/app-shell-odd/src/system-update/types.ts b/app-shell-odd/src/system-update/types.ts index 8555d980791..12c2f5dc674 100644 --- a/app-shell-odd/src/system-update/types.ts +++ b/app-shell-odd/src/system-update/types.ts @@ -16,24 +16,47 @@ export interface ReleaseSetFilepaths { releaseNotes: string | null } -// shape of VERSION.json in update file -export interface VersionInfo { - buildroot_version: string - buildroot_sha: string - buildroot_branch: string - buildroot_buildid: string - build_type: string - opentrons_api_version: string - opentrons_api_sha: string - opentrons_api_branch: string - update_server_version: string - update_server_sha: string - update_server_branch: string +export interface NoUpdate { + version: null + files: null + releaseNotes: null + downloadProgress: 0 } -export interface UserFileInfo { - // filepath of update file - systemFile: string - // parsed contents of VERSION.json - versionInfo: VersionInfo +export interface FoundUpdate { + version: string + files: null + releaseNotes: null + downloadProgress: number +} + +export interface ReadyUpdate { + version: string + files: ReleaseSetFilepaths + releaseNotes: string | null + downloadProgress: 100 +} + +export type ResolvedUpdate = NoUpdate | ReadyUpdate +export type UnresolvedUpdate = ResolvedUpdate | FoundUpdate +export type ProgressCallback = (status: UnresolvedUpdate) => void + +// Interface provided by the web and usb sourced updaters. Type variable is +// specified by the updater implementation. +export interface UpdateProvider { + // Call before disposing to make sure any temporary storage is removed + teardown: () => Promise + // Scan an implementation-defined location for updates + refreshUpdateCache: (progress: ProgressCallback) => Promise + // Get the details of a found update, if any. + getUpdateDetails: () => UnresolvedUpdate + // Lock the update cache, which will prevent anything from accidentally overwriting stuff + // while it's being sent as an update + lockUpdateCache: () => void + // Reverse lockUpdateCache() + unlockUpdateCache: () => void + // get an identifier for logging + name: () => string + // get the current source + source: () => UpdateSourceDetails } diff --git a/app-shell-odd/src/system-update/update.ts b/app-shell-odd/src/system-update/update.ts deleted file mode 100644 index d1adb6e9c3d..00000000000 --- a/app-shell-odd/src/system-update/update.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { postFile } from '../http' -import type { - RobotModel, - ViewableRobot, -} from '@opentrons/app/src/redux/discovery/types' - -const OT2_FILENAME = 'ot2-system.zip' -const SYSTEM_FILENAME = 'system-update.zip' - -const getSystemFileName = (robotModel: RobotModel): string => { - if (robotModel === 'OT-2 Standard' || robotModel === null) { - return OT2_FILENAME - } - return SYSTEM_FILENAME -} - -export function uploadSystemFile( - robot: ViewableRobot, - urlPath: string, - file: string -): Promise { - const url = `http://${robot.ip}:${robot.port}${urlPath}` - - return postFile(url, getSystemFileName(robot.robotModel), file) -} diff --git a/app-shell-odd/src/system-update/utils.ts b/app-shell-odd/src/system-update/utils.ts new file mode 100644 index 00000000000..e0a334ba5d4 --- /dev/null +++ b/app-shell-odd/src/system-update/utils.ts @@ -0,0 +1,18 @@ +import { rm } from 'fs/promises' +import tempy from 'tempy' + +export const directoryWithCleanup = ( + task: (directory: string) => Promise +): Promise => { + const directory = tempy.directory() + return new Promise((resolve, reject) => + task(directory as string) + .then(result => { + resolve(result) + }) + .catch(err => { + reject(err) + }) + .finally(() => rm(directory as string, { recursive: true, force: true })) + ) +} diff --git a/app-shell-odd/src/system.ts b/app-shell-odd/src/system.ts new file mode 100644 index 00000000000..36c427a7e94 --- /dev/null +++ b/app-shell-odd/src/system.ts @@ -0,0 +1,22 @@ +import { UPDATE_BRIGHTNESS } from './constants' +import { createLogger } from './log' +import systemd from './systemd' + +import type { Action } from './types' + +const log = createLogger('system') + +export function registerUpdateBrightness(): (action: Action) => void { + return function handleAction(action: Action) { + switch (action.type) { + case UPDATE_BRIGHTNESS: + console.log('update the brightness') + systemd + .updateBrightness(action.payload.message) + .catch(err => + log.debug('Something wrong when updating the brightness', err) + ) + break + } + } +} diff --git a/app-shell-odd/src/types.ts b/app-shell-odd/src/types.ts index 2899171a08b..5d8f8a9502a 100644 --- a/app-shell-odd/src/types.ts +++ b/app-shell-odd/src/types.ts @@ -112,11 +112,13 @@ export type CLEAR_CACHE_TYPE = 'discovery:CLEAR_CACHE' export interface ConfigInitializedAction { type: CONFIG_INITIALIZED_TYPE payload: { config: Config } + meta: { shell: true } } export interface ConfigValueUpdatedAction { type: CONFIG_VALUE_UPDATED_TYPE payload: { path: string; value: any } + meta: { shell: true } } export interface StartDiscoveryAction { diff --git a/app-shell-odd/src/update.ts b/app-shell-odd/src/update.ts deleted file mode 100644 index d1ea2f154b3..00000000000 --- a/app-shell-odd/src/update.ts +++ /dev/null @@ -1,113 +0,0 @@ -import semver from 'semver' -import { UI_INITIALIZED, UPDATE_BRIGHTNESS } from './constants' -import { createLogger } from './log' -import { getConfig } from './config' -import { - downloadAndCacheReleaseManifest, - getCachedReleaseManifest, - getReleaseSet, -} from './system-update/release-manifest' -import systemd from './systemd' - -import type { Action, Dispatch } from './types' -import type { ReleaseSetUrls } from './system-update/types' - -const log = createLogger('update') - -const OPENTRONS_PROJECT: string = _OPENTRONS_PROJECT_ - -export const FLEX_MANIFEST_URL = - OPENTRONS_PROJECT && OPENTRONS_PROJECT.includes('robot-stack') - ? 'https://builds.opentrons.com/ot3-oe/releases.json' - : 'https://ot3-development.builds.opentrons.com/ot3-oe/releases.json' - -const PKG_VERSION = _PKG_VERSION_ -let LATEST_OT_SYSTEM_VERSION = PKG_VERSION - -const channelFinder = (version: string, channel: string): boolean => { - // return the latest alpha/beta if a user subscribes to alpha/beta updates - if (['alpha', 'beta'].includes(channel)) { - return version.includes(channel) - } else { - // otherwise get the latest stable version - return !version.includes('alpha') && !version.includes('beta') - } -} - -export const getLatestSystemUpdateUrls = (): Promise => { - return getCachedReleaseManifest() - .then(manifest => getReleaseSet(manifest, getLatestVersion())) - .catch((error: Error) => { - log.warn('Error retrieving release manifest', { - version: getLatestVersion(), - error, - }) - return Promise.reject(error) - }) -} - -export const updateLatestVersion = (): Promise => { - const channel = getConfig('update').channel - - return downloadAndCacheReleaseManifest(FLEX_MANIFEST_URL) - .then(response => { - const latestAvailableVersion = Object.keys(response.production) - .sort((a, b) => { - if (semver.lt(a, b)) { - return 1 - } - return -1 - }) - .find(verson => channelFinder(verson, channel)) - const changed = LATEST_OT_SYSTEM_VERSION !== latestAvailableVersion - LATEST_OT_SYSTEM_VERSION = latestAvailableVersion ?? PKG_VERSION - if (changed) { - log.info( - `Update: latest version available from ${FLEX_MANIFEST_URL} is ${latestAvailableVersion}` - ) - } - return LATEST_OT_SYSTEM_VERSION - }) - .catch((e: Error) => { - log.warn( - `Update: error fetching latest system version from ${FLEX_MANIFEST_URL}: ${e.message}, keeping latest version at ${LATEST_OT_SYSTEM_VERSION}` - ) - return LATEST_OT_SYSTEM_VERSION - }) -} - -export const getLatestVersion = (): string => { - return LATEST_OT_SYSTEM_VERSION -} - -export const getCurrentVersion = (): string => PKG_VERSION - -export const isUpdateAvailable = (): boolean => - getLatestVersion() !== getCurrentVersion() - -export function registerUpdate( - dispatch: Dispatch -): (action: Action) => unknown { - return function handleAction(action: Action) { - switch (action.type) { - case UI_INITIALIZED: - case 'shell:CHECK_UPDATE': - return updateLatestVersion() - } - } -} - -export function registerUpdateBrightness(): (action: Action) => unknown { - return function handleAction(action: Action) { - switch (action.type) { - case UPDATE_BRIGHTNESS: - console.log('update the brightness') - systemd - .updateBrightness(action.payload.message) - .catch(err => - log.debug('Something wrong when updating the brightness', err) - ) - break - } - } -} diff --git a/app-shell-odd/src/usb.ts b/app-shell-odd/src/usb.ts index 44252c6a339..1c5e6bd14a7 100644 --- a/app-shell-odd/src/usb.ts +++ b/app-shell-odd/src/usb.ts @@ -2,6 +2,7 @@ import * as fs from 'fs' import * as fsPromises from 'fs/promises' import { join } from 'path' import { flatten } from 'lodash' +import { createLogger } from './log' import { robotMassStorageDeviceAdded, robotMassStorageDeviceEnumerated, @@ -16,7 +17,12 @@ import type { Dispatch, Action } from './types' const FLEX_USB_MOUNT_DIR = '/media/' const FLEX_USB_DEVICE_DIR = '/dev/' -const FLEX_USB_MOUNT_FILTER = /sd[a-z]+[0-9]+$/ +// filter matches sda0, sdc9, sdb +const FLEX_USB_DEVICE_FILTER = /sd[a-z]+[0-9]*$/ +// filter matches sda0, sdc9, sdb, VOLUME-sdc10 +const FLEX_USB_MOUNT_FILTER = /([^/]+-)?(sd[a-z]+[0-9]*)$/ + +const log = createLogger('mass-storage') // These are for backoff algorithm // apply the delay from 1 sec 64 sec @@ -48,11 +54,15 @@ const isWeirdDirectoryAndShouldSkip = (dirName: string): boolean => .map(keyword => dirName.includes(keyword)) .reduce((prev, current) => prev || current, false) -const enumerateMassStorage = (path: string): Promise => { +const doEnumerateMassStorage = ( + path: string, + depth: number +): Promise => { + log.info(`Enumerating mass storage path ${path}`) return callWithRetry(() => fsPromises.readdir(path).then(entries => { - if (entries.length === 0) { - throw new Error('No entries found, retrying...') + if (entries.length === 0 && depth === 0) { + throw new Error('No entries found for top level, retrying...') } return entries }) @@ -62,29 +72,44 @@ const enumerateMassStorage = (path: string): Promise => { Promise.all( entries.map(entry => entry.isDirectory() && !isWeirdDirectoryAndShouldSkip(entry.name) - ? enumerateMassStorage(join(path, entry.name)) + ? doEnumerateMassStorage(join(path, entry.name), depth + 1) : new Promise(resolve => { resolve([join(path, entry.name)]) }) ) ) ) - .catch(error => { - console.error(`Error enumerating mass storage: ${error}`) + .catch((error: Error) => { + log.error( + `Error enumerating mass storage path ${path}: ${error.name}: ${error.message}` + ) return [] }) .then(flatten) - .then(result => { - return result - }) + .then(result => result) +} + +const enumerateMassStorage = (path: string): Promise => { + log.info(`Beginning scan of mass storage device at ${path}`) + return doEnumerateMassStorage(path, 0).then(results => { + log.info(`Found ${results.length} files in ${path}`) + return results + }) } + export function watchForMassStorage(dispatch: Dispatch): () => void { - console.log('watching for mass storage') + log.info('watching for mass storage') let prevDirs: string[] = [] const handleNewlyPresent = (path: string): Promise => { dispatch(robotMassStorageDeviceAdded(path)) return enumerateMassStorage(path) .then(contents => { + log.debug( + `mass storage device at ${path} enumerated: ${JSON.stringify( + contents + )}` + ) + log.info(`Enumerated ${path} with ${contents.length} results`) dispatch(robotMassStorageDeviceEnumerated(path, contents)) }) .then(() => path) @@ -101,6 +126,9 @@ export function watchForMassStorage(dispatch: Dispatch): () => void { const newlyAbsent = prevDirs.filter( entry => !sortedEntries.includes(entry) ) + log.info( + `rescan: newly present: ${newlyPresent} newly absent: ${newlyAbsent}` + ) return Promise.all([ ...newlyAbsent.map(entry => { if (entry.match(FLEX_USB_MOUNT_FILTER)) { @@ -119,6 +147,7 @@ export function watchForMassStorage(dispatch: Dispatch): () => void { ]) }) .then(present => { + log.info(`now present: ${present}`) prevDirs = present.filter((entry): entry is string => entry !== null) }) @@ -133,6 +162,9 @@ export function watchForMassStorage(dispatch: Dispatch): () => void { return } if (!fileName.match(FLEX_USB_MOUNT_FILTER)) { + log.debug( + `mediaWatcher: filename ${fileName} does not match ${FLEX_USB_MOUNT_FILTER}` + ) return } const fullPath = join(FLEX_USB_MOUNT_DIR, fileName) @@ -140,25 +172,36 @@ export function watchForMassStorage(dispatch: Dispatch): () => void { .stat(fullPath) .then(info => { if (!info.isDirectory) { + log.debug(`mediaWatcher: ${fullPath} is not a directory`) return } if (prevDirs.includes(fullPath)) { + log.debug(`mediaWatcher: ${fullPath} is known`) return } - console.log(`New mass storage device ${fileName} detected`) + log.info(`New mass storage device ${fileName} detected`) prevDirs.push(fullPath) return handleNewlyPresent(fullPath) }) - .catch(() => { + .catch(err => { if (prevDirs.includes(fullPath)) { - console.log(`Mass storage device at ${fileName} removed`) + log.info( + `Mass storage device at ${fileName} removed because its mount point disappeared`, + err + ) prevDirs = prevDirs.filter(entry => entry !== fullPath) dispatch(robotMassStorageDeviceRemoved(fullPath)) + } else { + log.debug( + `Mass storage device candidate mountpoint at ${fileName} disappeared`, + err + ) } }) } ) } catch { + log.error(`Failed to start watcher for ${FLEX_USB_MOUNT_DIR}`) return null } } @@ -170,21 +213,42 @@ export function watchForMassStorage(dispatch: Dispatch): () => void { { persistent: true }, (event, fileName) => { if (!!!fileName) return - if (!fileName.match(FLEX_USB_MOUNT_FILTER)) return - const fullPath = join(FLEX_USB_DEVICE_DIR, fileName) - const mountPath = join(FLEX_USB_MOUNT_DIR, fileName) - fsPromises.stat(fullPath).catch(() => { - if (prevDirs.includes(mountPath)) { - console.log(`Mass storage device at ${fileName} removed`) - prevDirs = prevDirs.filter(entry => entry !== mountPath) - dispatch( - robotMassStorageDeviceRemoved(join(FLEX_USB_MOUNT_DIR, fileName)) + if (!fileName.match(FLEX_USB_DEVICE_FILTER)) return + if (event !== 'rename') { + log.debug( + `devWatcher: ignoring ${event} event for ${fileName} (not rename)` + ) + return + } + log.debug(`devWatcher: ${event} event for ${fileName}`) + fsPromises + .readdir(FLEX_USB_DEVICE_DIR) + .then(contents => { + if (contents.includes(fileName)) { + log.debug( + `devWatcher: ${fileName} found in /dev, this is an attach` + ) + // this is an attach + return + } + const prevDir = prevDirs.filter(dir => dir.includes(fileName)).at(0) + log.debug( + `devWatcher: ${fileName} not in /dev, this is a remove, previously mounted at ${prevDir}` ) - // we don't care if this fails because it's racing the system removing - // the mount dir in the common case - fsPromises.unlink(mountPath).catch(() => {}) - } - }) + if (prevDir != null) { + log.info(`Mass storage device at ${fileName} removed`) + prevDirs = prevDirs.filter(entry => entry !== prevDir) + dispatch(robotMassStorageDeviceRemoved(prevDir)) + // we don't care if this fails because it's racing the system removing + // the mount dir in the common case + fsPromises.unlink(prevDir).catch(() => {}) + } + }) + .catch(err => { + log.info( + `Failed to handle mass storage device ${fileName}: ${err.name}: ${err.message}` + ) + }) } ) diff --git a/app-shell-odd/tsconfig.json b/app-shell-odd/tsconfig.json index 58241ce4dd0..a4a45531dac 100644 --- a/app-shell-odd/tsconfig.json +++ b/app-shell-odd/tsconfig.json @@ -11,7 +11,10 @@ "compilerOptions": { "composite": true, "rootDir": "src", - "outDir": "lib" + "outDir": "lib", + "paths": { + "/app/*": ["../app/src/*"] + } }, "include": ["typings", "src"] } diff --git a/app-shell-odd/vite.config.mts b/app-shell-odd/vite.config.mts new file mode 100644 index 00000000000..ef20c33b0ed --- /dev/null +++ b/app-shell-odd/vite.config.mts @@ -0,0 +1,92 @@ +import { versionForProject } from '../scripts/git-version.mjs' +import pkg from './package.json' +import path from 'path' +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import postCssImport from 'postcss-import' +import postCssApply from 'postcss-apply' +import postColorModFunction from 'postcss-color-mod-function' +import postCssPresetEnv from 'postcss-preset-env' +import lostCss from 'lost' +import type { UserConfig } from 'vite' + +export default defineConfig( + async (): Promise => { + const project = process.env.OPENTRONS_PROJECT ?? 'robot-stack' + const version = await versionForProject(project) + return { + publicDir: false, + build: { + // Relative to the root + ssr: 'src/main.ts', + outDir: 'lib', + commonjsOptions: { + transformMixedEsModules: true, + esmExternals: true, + }, + lib: { + entry: { + main: 'src/main.ts', + preload: 'src/preload.ts', + }, + + formats: ['cjs'], + }, + }, + plugins: [ + react({ + include: '**/*.tsx', + babel: { + // Use babel.config.js files + configFile: true, + }, + }), + ], + optimizeDeps: { + esbuildOptions: { + target: 'CommonJs', + }, + }, + css: { + postcss: { + plugins: [ + postCssImport({ root: 'src/' }), + postCssApply(), + postColorModFunction(), + postCssPresetEnv({ stage: 0 }), + lostCss(), + ], + }, + }, + define: { + 'process.env': { + NODE_ENV: process.env.NODE_ENV, + OPENTRONS_PROJECT: process.env.OPENTRONS_PROJECT, + }, + global: 'globalThis', + _PKG_VERSION_: JSON.stringify(version), + _PKG_PRODUCT_NAME_: JSON.stringify(pkg.productName), + _PKG_BUGS_URL_: JSON.stringify(pkg.bugs.url), + _OPENTRONS_PROJECT_: JSON.stringify(project), + }, + resolve: { + alias: { + '@opentrons/components/styles': path.resolve( + '../components/src/index.module.css' + ), + '@opentrons/components': path.resolve('../components/src/index.ts'), + '@opentrons/shared-data': path.resolve('../shared-data/js/index.ts'), + '@opentrons/step-generation': path.resolve( + '../step-generation/src/index.ts' + ), + '@opentrons/discovery-client': path.resolve( + '../discovery-client/src/index.ts' + ), + '@opentrons/usb-bridge/node-client': path.resolve( + '../usb-bridge/node-client/src/index.ts' + ), + }, + }, + } + } +) diff --git a/app-shell-odd/vite.config.ts b/app-shell-odd/vite.config.ts deleted file mode 100644 index 7848c92bd8d..00000000000 --- a/app-shell-odd/vite.config.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { versionForProject } from '../scripts/git-version.mjs' -import pkg from './package.json' -import path from 'path' -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import postCssImport from 'postcss-import' -import postCssApply from 'postcss-apply' -import postColorModFunction from 'postcss-color-mod-function' -import postCssPresetEnv from 'postcss-preset-env' -import lostCss from 'lost' -import type { UserConfig } from 'vite' - -export default defineConfig( - async (): Promise => { - const project = process.env.OPENTRONS_PROJECT ?? 'robot-stack' - const version = await versionForProject(project) - return { - publicDir: false, - build: { - // Relative to the root - ssr: 'src/main.ts', - outDir: 'lib', - commonjsOptions: { - transformMixedEsModules: true, - esmExternals: true, - }, - lib: { - entry: { - main: 'src/main.ts', - preload: 'src/preload.ts', - }, - - formats: ['cjs'], - }, - }, - plugins: [ - react({ - include: '**/*.tsx', - babel: { - // Use babel.config.js files - configFile: true, - }, - }), - ], - optimizeDeps: { - esbuildOptions: { - target: 'CommonJs', - }, - }, - css: { - postcss: { - plugins: [ - postCssImport({ root: 'src/' }), - postCssApply(), - postColorModFunction(), - postCssPresetEnv({ stage: 0 }), - lostCss(), - ], - }, - }, - define: { - 'process.env': process.env, - global: 'globalThis', - _PKG_VERSION_: JSON.stringify(version), - _PKG_PRODUCT_NAME_: JSON.stringify(pkg.productName), - _PKG_BUGS_URL_: JSON.stringify(pkg.bugs.url), - _OPENTRONS_PROJECT_: JSON.stringify(project), - }, - resolve: { - alias: { - '@opentrons/components/styles': path.resolve( - '../components/src/index.module.css' - ), - '@opentrons/components': path.resolve('../components/src/index.ts'), - '@opentrons/shared-data': path.resolve('../shared-data/js/index.ts'), - '@opentrons/step-generation': path.resolve( - '../step-generation/src/index.ts' - ), - '@opentrons/discovery-client': path.resolve( - '../discovery-client/src/index.ts' - ), - '@opentrons/usb-bridge/node-client': path.resolve( - '../usb-bridge/node-client/src/index.ts' - ), - }, - }, - } - } -) diff --git a/app-shell/Makefile b/app-shell/Makefile index 5daafd82f44..ec54213bf69 100644 --- a/app-shell/Makefile +++ b/app-shell/Makefile @@ -59,7 +59,7 @@ no_python_bundle ?= builder := yarn electron-builder \ --config electron-builder.config.js \ - --config.electronVersion=27.0.0 \ + --config.electronVersion=33.2.1 \ --publish never @@ -121,32 +121,34 @@ package dist-posix dist-osx dist-linux dist-win: export BUILD_ID := $(build_id) package dist-posix dist-osx dist-linux dist-win: export NO_PYTHON := $(if $(no_python_bundle),true,false) package dist-posix dist-osx dist-linux dist-win: export USE_HARD_LINKS := false +# Note: these depend on make -C app dist having been run; do not do this automatically because we separate these +# tasks in CI and even if you have a file dep it's easy to accidentally make the dist run. .PHONY: package -package: package-deps +package: $(builder) --dir .PHONY: dist-posix -dist-posix: package-deps +dist-posix: clean lib $(builder) --linux --mac $(MAKE) _dist-collect-artifacts .PHONY: dist-osx -dist-osx: package-deps +dist-osx: clean lib $(builder) --mac --x64 $(MAKE) _dist-collect-artifacts .PHONY: dist-linux -dist-linux: package-deps +dist-linux: clean lib $(builder) --linux $(MAKE) _dist-collect-artifacts .PHONY: dist-win -dist-win: package-deps +dist-win: clean lib $(builder) --win --x64 $(MAKE) _dist-collect-artifacts .PHONY: dist-ot3 -dist-ot3: package-deps +dist-ot3: clean lib NO_PYTHON=true $(builder) --linux --arm64 --dir cd dist/linux-arm64-unpacked diff --git a/app-shell/build/release-notes-internal.md b/app-shell/build/release-notes-internal.md index ce6e62da888..2a2b46ebb7c 100644 --- a/app-shell/build/release-notes-internal.md +++ b/app-shell/build/release-notes-internal.md @@ -1,6 +1,22 @@ For more details about this release, please see the full [technical changelog][]. [technical change log]: https://github.com/Opentrons/opentrons/releases +## Internal Release 2.3.0-alpha.0 + +This internal release, pulled from the `edge` branch, contains features being developed for evo tip functionality. It's for internal testing only. + +## Internal Release 2.2.0-alpha.1 + +This internal release, pulled from the `edge` branch, contains features being developed for 8.2.0. It's for internal testing only. + +## Internal Release 2.2.0-alpha.0 + +This internal release, pulled from the `edge` branch, contains features being developed for 8.2.0. It's for internal testing only. + +## Internal Release 2.1.0-alpha.0 + +This internal release contains features being developed for 8.1.0. It's for internal testing only. + ## Internal Release 2.0.0-alpha.4 This internal release, pulled from the `edge` branch, contains features being developed for 8.0.0. It's for internal testing only. There are no changes to `buildroot`, `ot3-firmware`, or `oe-core` since the last internal release. diff --git a/app-shell/build/release-notes.md b/app-shell/build/release-notes.md index ffdb26485e8..9b9231e9709 100644 --- a/app-shell/build/release-notes.md +++ b/app-shell/build/release-notes.md @@ -8,6 +8,46 @@ By installing and using Opentrons software, you agree to the Opentrons End-User --- +## Opentrons App Changes in 8.2.0 + +Welcome to the v8.2.0 release of the Opentrons App! This release adds support for the Opentrons Absorbance Plate Reader Module, as well as other features. + +### New Features + +- Run protocols that use the Absorbance Plate Reader and check the status of the module on the robot details screen for your Flex. +- Run protocols that use the new Opentrons Tough PCR Auto-Sealing Lid with the Thermocycler Module GEN2. Stacks of these lids appear in a consolidated view when setting up labware. + +### Improved Features + +- Error recovery now works in more situations and has more options. + - Recover from gripper errors. + - Recover from failure to drop tips. + - Indicate that an error was improperly detected and skip similar errors later in the run. + - Choose from more options of where to drop tips as part of recovery. + - Disable error recovery entirely, if your application requires it. Runs will fail on any error. + +### Bug Fixes + +- Fixed an app crash when performing certain error recovery steps with Python API version 2.15 protocols. + +### Known Issues + +- If you attach an Absorbance Plate Reader to _any_ Flex on your local network, you must update all copies of the Opentrons App on the same network to at least v8.1.0. + +--- + +## Opentrons App Changes in 8.1.0 + +Welcome to the v8.1.0 release of the Opentrons App! + +There are no new features in the Opentrons App in v8.1.0, but it is required for updating the robot software to support the latest production version of Flex robots. + +### Bug Fixes + +- Prevented Flex from showing the first-run screen when powering on a robot that's already set up. + +--- + ## Opentrons App Changes in 8.0.0 Welcome to the v8.0.0 release of the Opentrons App! @@ -27,7 +67,9 @@ Welcome to the v8.0.0 release of the Opentrons App! ### Known Issues -- Labware offsets can't be applied to protocols that require selecting a CSV file as a runtime parameter value. Write the protocol in such a way that it passes analysis with or without the CSV file, or run Labware Position Check after confirming parameter values. +- Stored labware offsets can't be applied to protocols that require selecting a CSV file as a runtime parameter value. Write the protocol in such a way that it passes analysis with or without the CSV file, or run Labware Position Check after confirming parameter values. +- Error recovery can't perform partial tip pickup, because it doesn't account for the pipette nozzle configuration of 8- and 96-channel pipettes. If a recovery step requires partial tip pickup, cancel the protocol instead. +- Downloading robot logs via USB may take up to 2 minutes on macOS, and may fail entirely on Windows. Use an Ethernet or Wi-Fi connection to download logs if needed. --- diff --git a/app-shell/electron-builder.config.js b/app-shell/electron-builder.config.js index 1b048915255..decac1f66b9 100644 --- a/app-shell/electron-builder.config.js +++ b/app-shell/electron-builder.config.js @@ -25,7 +25,7 @@ const publishConfig = module.exports = async () => ({ appId: project === 'robot-stack' ? 'com.opentrons.app' : 'com.opentrons.appot3', - electronVersion: '27.0.0', + electronVersion: '33.2.1', npmRebuild: false, releaseInfo: { releaseNotesFile: diff --git a/app-shell/package.json b/app-shell/package.json index e93babb3342..8c474d7247c 100644 --- a/app-shell/package.json +++ b/app-shell/package.json @@ -8,7 +8,7 @@ "types": "lib/main.d.ts", "scripts": { "start": "make dev", - "rebuild": "electron-rebuild" + "rebuild": "electron-rebuild --force-abi=3.71.0" }, "repository": { "type": "git", @@ -24,9 +24,7 @@ }, "homepage": "https://github.com/Opentrons/opentrons", "workspaces": { - "nohoist": [ - "**" - ] + "nohoist": ["**"] }, "devDependencies": { "electron-notarize": "^1.2.1", @@ -52,7 +50,6 @@ "electron-localshortcut": "3.2.1", "electron-devtools-installer": "3.2.0", "electron-store": "5.1.1", - "electron-updater": "4.1.2", "execa": "4.0.0", "form-data": "2.5.0", "fs-extra": "10.0.0", @@ -63,12 +60,12 @@ "node-fetch": "2.6.7", "node-stream-zip": "1.8.2", "pump": "3.0.0", - "semver": "5.5.0", + "semver": "5.7.2", "serialport": "10.5.0", "tempy": "1.0.1", "usb": "^2.11.0", "uuid": "3.2.1", - "winston": "3.1.0", + "winston": "3.15.0", "yargs-parser": "13.1.2" } } diff --git a/app-shell/src/__fixtures__/config.ts b/app-shell/src/__fixtures__/config.ts index 23ef4f56f90..dd344c78532 100644 --- a/app-shell/src/__fixtures__/config.ts +++ b/app-shell/src/__fixtures__/config.ts @@ -24,6 +24,7 @@ import type { ConfigV22, ConfigV23, ConfigV24, + ConfigV25, } from '@opentrons/app/src/redux/config/types' export const MOCK_CONFIG_V0: ConfigV0 = { @@ -302,3 +303,12 @@ export const MOCK_CONFIG_V24: ConfigV24 = { userId: 'MOCK_UUIDv4', }, } + +export const MOCK_CONFIG_V25: ConfigV25 = { + ...MOCK_CONFIG_V24, + version: 25, + language: { + appLanguage: null, + systemLanguage: null, + }, +} diff --git a/app-shell/src/__tests__/update.test.ts b/app-shell/src/__tests__/update.test.ts index 19d22e65b8f..3c0afe3e514 100644 --- a/app-shell/src/__tests__/update.test.ts +++ b/app-shell/src/__tests__/update.test.ts @@ -38,7 +38,7 @@ describe('update', () => { vi.mocked(ElectronUpdater.autoUpdater).emit('update-available', { version: '1.0.0', - }) + } as any) expect(dispatch).toHaveBeenCalledWith({ type: 'shell:CHECK_UPDATE_RESULT', @@ -50,7 +50,7 @@ describe('update', () => { handleAction({ type: 'shell:CHECK_UPDATE', meta: { shell: true } }) vi.mocked(ElectronUpdater.autoUpdater).emit('update-not-available', { version: '1.0.0', - }) + } as any) expect(dispatch).toHaveBeenCalledWith({ type: 'shell:CHECK_UPDATE_RESULT', @@ -82,7 +82,7 @@ describe('update', () => { vi.mocked(ElectronUpdater.autoUpdater).downloadUpdate ).toHaveBeenCalledTimes(1) - const progress = { + const progress: any = { percent: 20, } @@ -97,7 +97,7 @@ describe('update', () => { vi.mocked(ElectronUpdater.autoUpdater).emit('update-downloaded', { version: '1.0.0', - }) + } as any) expect(dispatch).toHaveBeenCalledWith({ type: 'shell:DOWNLOAD_UPDATE_RESULT', diff --git a/app-shell/src/config/__tests__/migrate.test.ts b/app-shell/src/config/__tests__/migrate.test.ts index dee16e0dae4..ddc151fc2cf 100644 --- a/app-shell/src/config/__tests__/migrate.test.ts +++ b/app-shell/src/config/__tests__/migrate.test.ts @@ -28,13 +28,14 @@ import { MOCK_CONFIG_V22, MOCK_CONFIG_V23, MOCK_CONFIG_V24, + MOCK_CONFIG_V25, } from '../../__fixtures__' import { migrate } from '../migrate' vi.mock('uuid/v4') -const NEWEST_VERSION = 24 -const NEWEST_MOCK_CONFIG = MOCK_CONFIG_V24 +const NEWEST_VERSION = 25 +const NEWEST_MOCK_CONFIG = MOCK_CONFIG_V25 describe('config migration', () => { beforeEach(() => { @@ -226,10 +227,17 @@ describe('config migration', () => { expect(result.version).toBe(NEWEST_VERSION) expect(result).toEqual(NEWEST_MOCK_CONFIG) }) - it('should keep version 24', () => { + it('should migrate version 24 to latest', () => { const v24Config = MOCK_CONFIG_V24 const result = migrate(v24Config) + expect(result.version).toBe(NEWEST_VERSION) + expect(result).toEqual(NEWEST_MOCK_CONFIG) + }) + it('should keep version 25', () => { + const v25Config = MOCK_CONFIG_V25 + const result = migrate(v25Config) + expect(result.version).toBe(NEWEST_VERSION) expect(result).toEqual(NEWEST_MOCK_CONFIG) }) diff --git a/app-shell/src/config/actions.ts b/app-shell/src/config/actions.ts index eabc9b47a16..5d96e6c1171 100644 --- a/app-shell/src/config/actions.ts +++ b/app-shell/src/config/actions.ts @@ -111,6 +111,7 @@ import type { export const configInitialized = (config: Config): ConfigInitializedAction => ({ type: CONFIG_INITIALIZED, payload: { config }, + meta: { shell: true }, }) // config value has been updated @@ -120,6 +121,7 @@ export const configValueUpdated = ( ): ConfigValueUpdatedAction => ({ type: VALUE_UPDATED, payload: { path, value }, + meta: { shell: true }, }) export const customLabwareList = ( diff --git a/app-shell/src/config/migrate.ts b/app-shell/src/config/migrate.ts index fa9ed4a91dd..69c53ab2e72 100644 --- a/app-shell/src/config/migrate.ts +++ b/app-shell/src/config/migrate.ts @@ -28,13 +28,14 @@ import type { ConfigV22, ConfigV23, ConfigV24, + ConfigV25, } from '@opentrons/app/src/redux/config/types' // format // base config v0 defaults // any default values for later config versions are specified in the migration // functions for those version below -const CONFIG_VERSION_LATEST = 23 +const CONFIG_VERSION_LATEST = 25 export const DEFAULTS_V0: ConfigV0 = { version: 0, @@ -430,6 +431,18 @@ const toVersion24 = (prevConfig: ConfigV23): ConfigV24 => { } } +const toVersion25 = (prevConfig: ConfigV24): ConfigV25 => { + const nextConfig = { + ...prevConfig, + version: 25 as const, + language: { + appLanguage: null, + systemLanguage: null, + }, + } + return nextConfig +} + const MIGRATIONS: [ (prevConfig: ConfigV0) => ConfigV1, (prevConfig: ConfigV1) => ConfigV2, @@ -454,7 +467,8 @@ const MIGRATIONS: [ (prevConfig: ConfigV20) => ConfigV21, (prevConfig: ConfigV21) => ConfigV22, (prevConfig: ConfigV22) => ConfigV23, - (prevConfig: ConfigV23) => ConfigV24 + (prevConfig: ConfigV23) => ConfigV24, + (prevConfig: ConfigV24) => ConfigV25 ] = [ toVersion1, toVersion2, @@ -480,6 +494,7 @@ const MIGRATIONS: [ toVersion22, toVersion23, toVersion24, + toVersion25, ] export const DEFAULTS: Config = migrate(DEFAULTS_V0) @@ -511,6 +526,7 @@ export function migrate( | ConfigV22 | ConfigV23 | ConfigV24 + | ConfigV25 ): Config { const prevVersion = prevConfig.version let result = prevConfig diff --git a/app-shell/src/main.ts b/app-shell/src/main.ts index 10d3f02cda0..e09b9d0ae4c 100644 --- a/app-shell/src/main.ts +++ b/app-shell/src/main.ts @@ -5,7 +5,7 @@ import dns from 'dns' import contextMenu from 'electron-context-menu' import * as electronDevtoolsInstaller from 'electron-devtools-installer' -import { createUi, registerReloadUi } from './ui' +import { createUi, registerReloadUi, registerSystemLanguage } from './ui' import { initializeMenu } from './menu' import { createLogger } from './log' import { registerProtocolAnalysis } from './protocol-analysis' @@ -18,7 +18,6 @@ import { registerProtocolStorage } from './protocol-storage' import { getConfig, getStore, getOverrides, registerConfig } from './config' import { registerUsb } from './usb' import { registerNotify, closeAllNotifyConnections } from './notifications' - import type { BrowserWindow } from 'electron' import type { Action, Dispatch, Logger } from './types' import type { LogEntry } from 'winston' @@ -110,6 +109,7 @@ function startUp(): void { registerUsb(dispatch), registerNotify(dispatch, mainWindow), registerReloadUi(mainWindow), + registerSystemLanguage(dispatch), ] ipcMain.on('dispatch', (_, action) => { @@ -145,7 +145,10 @@ function installDevtools(): Promise { log.debug('Installing devtools') if (typeof install === 'function') { - return install(extensions, forceReinstall) + return install(extensions, { + loadExtensionOptions: { allowFileAccess: true }, + forceDownload: forceReinstall, + }) .then(() => log.debug('Devtools extensions installed')) .catch((error: unknown) => { log.warn('Failed to install devtools extensions', { diff --git a/app-shell/src/preload.ts b/app-shell/src/preload.ts index cf1f4ef7bef..16ef6b7aa30 100644 --- a/app-shell/src/preload.ts +++ b/app-shell/src/preload.ts @@ -1,7 +1,15 @@ // preload script for renderer process // defines subset of Electron API that renderer process is allowed to access // for security reasons -import { ipcRenderer } from 'electron' +import { ipcRenderer, webUtils } from 'electron' + +// The renderer process is not permitted the file path for any type "file" input +// post Electron v32. The correct way of doing this involves the context bridge, +// see comments in Electron settings. +// See https://www.electronjs.org/docs/latest/breaking-changes#removed-filepath +const getFilePathFrom = (file: File): Promise => { + return Promise.resolve(webUtils.getPathForFile(file)) +} // @ts-expect-error can't get TS to recognize global.d.ts -global.APP_SHELL_REMOTE = { ipcRenderer } +global.APP_SHELL_REMOTE = { ipcRenderer, getFilePathFrom } diff --git a/app-shell/src/robot-update/index.ts b/app-shell/src/robot-update/index.ts index 6e6d9b03363..01afaa15960 100644 --- a/app-shell/src/robot-update/index.ts +++ b/app-shell/src/robot-update/index.ts @@ -254,7 +254,7 @@ export function checkForRobotUpdate( }) }) .then(() => - cleanupReleaseFiles(cacheDirForMachineFiles(target), CURRENT_VERSION) + cleanupReleaseFiles(cacheDirForMachine(target), CURRENT_VERSION) ) } diff --git a/app-shell/src/types.ts b/app-shell/src/types.ts index 8a1bea51a20..f608b4512af 100644 --- a/app-shell/src/types.ts +++ b/app-shell/src/types.ts @@ -96,9 +96,11 @@ export type CLEAR_CACHE_TYPE = 'discovery:CLEAR_CACHE' export interface ConfigInitializedAction { type: CONFIG_INITIALIZED_TYPE payload: { config: Config } + meta: { shell: true } } export interface ConfigValueUpdatedAction { type: CONFIG_VALUE_UPDATED_TYPE payload: { path: string; value: any } + meta: { shell: true } } diff --git a/app-shell/src/ui.ts b/app-shell/src/ui.ts index 6f7a2a360fd..25dcf133ad4 100644 --- a/app-shell/src/ui.ts +++ b/app-shell/src/ui.ts @@ -3,10 +3,10 @@ import { app, shell, BrowserWindow } from 'electron' import path from 'path' import { getConfig } from './config' -import { RELOAD_UI } from './constants' +import { RELOAD_UI, UI_INITIALIZED } from './constants' import { createLogger } from './log' -import type { Action } from './types' +import type { Action, Dispatch } from './types' const config = getConfig('ui') const log = createLogger('ui') @@ -78,3 +78,23 @@ export function registerReloadUi( } } } + +export function registerSystemLanguage( + dispatch: Dispatch +): (action: Action) => unknown { + return function handleAction(action: Action) { + switch (action.type) { + case UI_INITIALIZED: { + const systemLanguage = app.getPreferredSystemLanguages() + + dispatch({ + type: 'shell:SYSTEM_LANGUAGE', + payload: { systemLanguage }, + meta: { shell: true }, + }) + + break + } + } + } +} diff --git a/app-shell/src/update.ts b/app-shell/src/update.ts index 5742904ae8b..5b926a17c71 100644 --- a/app-shell/src/update.ts +++ b/app-shell/src/update.ts @@ -11,6 +11,7 @@ const autoUpdater = updater.autoUpdater autoUpdater.logger = createLogger('update') autoUpdater.autoDownload = false +autoUpdater.forceDevUpdateConfig = true export const CURRENT_VERSION: string = autoUpdater.currentVersion.version @@ -77,16 +78,9 @@ interface ProgressInfo { percent: number bytesPerSecond: number } -interface DownloadingPayload { - progress: ProgressInfo - bytesPerSecond: number - percent: number - total: number - transferred: number -} function downloadUpdate(dispatch: Dispatch): void { - const onDownloading = (payload: DownloadingPayload): void => { + const onDownloading = (payload: ProgressInfo): void => { dispatch({ type: 'shell:DOWNLOAD_PERCENTAGE', payload }) } const onDownloaded = (): void => { diff --git a/app-shell/tsconfig.json b/app-shell/tsconfig.json index bb29d546ddb..afe7e4d4182 100644 --- a/app-shell/tsconfig.json +++ b/app-shell/tsconfig.json @@ -17,7 +17,10 @@ "rootDir": "src", "outDir": "lib", "target": "esnext", - "module": "ESNext" + "module": "ESNext", + "paths": { + "/app/*": ["../app/src/*"] + } }, "include": ["typings", "src"] } diff --git a/app-shell/vite.config.mts b/app-shell/vite.config.mts new file mode 100644 index 00000000000..8e2883cca0d --- /dev/null +++ b/app-shell/vite.config.mts @@ -0,0 +1,66 @@ +import { versionForProject } from '../scripts/git-version.mjs' +import pkg from './package.json' +import path from 'path' +import { defineConfig } from 'vite' +import type { UserConfig } from 'vite' + +export default defineConfig( + async (): Promise => { + const project = process.env.OPENTRONS_PROJECT ?? 'robot-stack' + const version = await versionForProject(project) + return { + // this makes imports relative rather than absolute + base: '', + publicDir: false, + build: { + // Relative to the root + ssr: 'src/main.ts', + outDir: 'lib', + commonjsOptions: { + transformMixedEsModules: true, + esmExternals: true, + exclude: [/node_modules/], + }, + lib: { + entry: { + main: 'src/main.ts', + preload: 'src/preload.ts', + }, + + formats: ['cjs'], + }, + }, + optimizeDeps: { + esbuildOptions: { + target: 'CommonJs', + }, + exclude: ['node_modules'], + }, + define: { + 'process.env': { + NODE_ENV: process.env.NODE_ENV, + OPENTRONS_PROJECT: process.env.OPENTRONS_PROJECT, + }, + global: 'globalThis', + _PKG_VERSION_: JSON.stringify(version), + _PKG_PRODUCT_NAME_: JSON.stringify(pkg.productName), + _PKG_BUGS_URL_: JSON.stringify(pkg.bugs.url), + _OPENTRONS_PROJECT_: JSON.stringify(project), + }, + resolve: { + alias: { + '@opentrons/shared-data': path.resolve('../shared-data/js/index.ts'), + '@opentrons/step-generation': path.resolve( + '../step-generation/src/index.ts' + ), + '@opentrons/discovery-client': path.resolve( + '../discovery-client/src/index.ts' + ), + '@opentrons/usb-bridge/node-client': path.resolve( + '../usb-bridge/node-client/src/index.ts' + ), + }, + }, + } + } +) diff --git a/app-shell/vite.config.ts b/app-shell/vite.config.ts deleted file mode 100644 index 546fe19e23f..00000000000 --- a/app-shell/vite.config.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { versionForProject } from '../scripts/git-version.mjs' -import pkg from './package.json' -import path from 'path' -import { defineConfig } from 'vite' -import type { UserConfig } from 'vite' - -export default defineConfig( - async (): Promise => { - const project = process.env.OPENTRONS_PROJECT ?? 'robot-stack' - const version = await versionForProject(project) - return { - // this makes imports relative rather than absolute - base: '', - publicDir: false, - build: { - // Relative to the root - ssr: 'src/main.ts', - outDir: 'lib', - commonjsOptions: { - transformMixedEsModules: true, - esmExternals: true, - exclude: [/node_modules/], - }, - lib: { - entry: { - main: 'src/main.ts', - preload: 'src/preload.ts', - }, - - formats: ['cjs'], - }, - }, - optimizeDeps: { - esbuildOptions: { - target: 'CommonJs', - }, - exclude: ['node_modules'] - }, - define: { - 'process.env': process.env, - global: 'globalThis', - _PKG_VERSION_: JSON.stringify(version), - _PKG_PRODUCT_NAME_: JSON.stringify(pkg.productName), - _PKG_BUGS_URL_: JSON.stringify(pkg.bugs.url), - _OPENTRONS_PROJECT_: JSON.stringify(project), - }, - resolve: { - alias: { - '@opentrons/shared-data': path.resolve('../shared-data/js/index.ts'), - '@opentrons/step-generation': path.resolve( - '../step-generation/src/index.ts' - ), - '@opentrons/discovery-client': path.resolve( - '../discovery-client/src/index.ts' - ), - '@opentrons/usb-bridge/node-client': path.resolve( - '../usb-bridge/node-client/src/index.ts' - ), - }, - }, - } - } -) diff --git a/app/Makefile b/app/Makefile index 51305c9b3b0..b11fed0b2b3 100644 --- a/app/Makefile +++ b/app/Makefile @@ -21,7 +21,7 @@ discovery_client_dir := ../discovery-client # These variables can be overriden when make is invoked to customize the # behavior of jest. For instance, -# make test tests=src/pages/Labware/__tests__/hooks.test.tsx would run only the +# make test tests=src/pages/Desktop/Labware/__tests__/hooks.test.tsx would run only the # specified test tests ?= cov_opts ?= --coverage=true diff --git a/app/package.json b/app/package.json index ad78d15a779..e87da02e4b0 100644 --- a/app/package.json +++ b/app/package.json @@ -62,12 +62,14 @@ "redux-thunk": "2.3.0", "reselect": "4.0.0", "rxjs": "^6.5.1", - "semver": "5.5.0", + "semver": "5.7.2", + "simple-keyboard-layouts": "3.4.41", "styled-components": "5.3.6", "typeface-open-sans": "0.0.75", "uuid": "3.2.1" }, "devDependencies": { + "@tanstack/react-query-devtools": "5.59.16", "@types/classnames": "2.2.5", "@types/file-saver": "2.0.1", "@types/jszip": "3.1.7", diff --git a/app/src/App/DesktopApp.tsx b/app/src/App/DesktopApp.tsx index 9bcae5fd201..df2105007b3 100644 --- a/app/src/App/DesktopApp.tsx +++ b/app/src/App/DesktopApp.tsx @@ -1,7 +1,6 @@ -import * as React from 'react' +import { useState, Fragment } from 'react' import { Navigate, Route, Routes, useMatch } from 'react-router-dom' import { ErrorBoundary } from 'react-error-boundary' -import { I18nextProvider } from 'react-i18next' import { Box, @@ -12,43 +11,48 @@ import { import { ApiHostProvider } from '@opentrons/react-api-client' import NiceModal from '@ebay/nice-modal-react' -import { i18n } from '../i18n' -import { Alerts } from '../organisms/Alerts' -import { Breadcrumbs } from '../organisms/Breadcrumbs' -import { ToasterOven } from '../organisms/ToasterOven' -import { CalibrationDashboard } from '../pages/Devices/CalibrationDashboard' -import { DeviceDetails } from '../pages/Devices/DeviceDetails' -import { DevicesLanding } from '../pages/Devices/DevicesLanding' -import { ProtocolRunDetails } from '../pages/Devices/ProtocolRunDetails' -import { RobotSettings } from '../pages/Devices/RobotSettings' -import { ProtocolsLanding } from '../pages/Protocols/ProtocolsLanding' -import { ProtocolDetails } from '../pages/Protocols/ProtocolDetails' -import { AppSettings } from '../pages/AppSettings' -import { Labware } from '../pages/Labware' +import { LocalizationProvider } from '/app/LocalizationProvider' +import { Alerts } from '/app/organisms/Desktop/Alerts' +import { Breadcrumbs } from '/app/organisms/Desktop/Breadcrumbs' +import { SystemLanguagePreferenceModal } from '/app/organisms/Desktop/SystemLanguagePreferenceModal' +import { ToasterOven } from '/app/organisms/ToasterOven' +import { CalibrationDashboard } from '/app/pages/Desktop/Devices/CalibrationDashboard' +import { DeviceDetails } from '/app/pages/Desktop/Devices/DeviceDetails' +import { DevicesLanding } from '/app/pages/Desktop/Devices/DevicesLanding' +import { ProtocolRunDetails } from '/app/pages/Desktop/Devices/ProtocolRunDetails' +import { RobotSettings } from '/app/pages/Desktop/Devices/RobotSettings' +import { ProtocolsLanding } from '/app/pages/Desktop/Protocols/ProtocolsLanding' +import { ProtocolDetails } from '/app/pages/Desktop/Protocols/ProtocolDetails' +import { AppSettings } from '/app/pages/Desktop/AppSettings' +import { Labware } from '/app/pages/Desktop/Labware' import { useSoftwareUpdatePoll } from './hooks' import { Navbar } from './Navbar' -import { EstopTakeover, EmergencyStopContext } from '../organisms/EmergencyStop' -import { IncompatibleModuleTakeover } from '../organisms/IncompatibleModule' -import { OPENTRONS_USB } from '../redux/discovery' -import { appShellRequestor } from '../redux/shell/remote' -import { useRobot, useIsFlex } from '../organisms/Devices/hooks' -import { ProtocolTimeline } from '../pages/Protocols/ProtocolDetails/ProtocolTimeline' +import { + EstopTakeover, + EmergencyStopContext, +} from '/app/organisms/EmergencyStop' +import { IncompatibleModuleTakeover } from '/app/organisms/IncompatibleModule' +import { OPENTRONS_USB } from '/app/redux/discovery' +import { appShellRequestor } from '/app/redux/shell/remote' +import { useRobot, useIsFlex } from '/app/redux-resources/robots' +import { ProtocolTimeline } from '/app/pages/Desktop/Protocols/ProtocolDetails/ProtocolTimeline' import { PortalRoot as ModalPortalRoot } from './portal' import { DesktopAppFallback } from './DesktopAppFallback' +import { ReactQueryDevtools } from './tools' -import type { RouteProps, DesktopRouteParams } from './types' +import type { RouteProps } from './types' export const DesktopApp = (): JSX.Element => { useSoftwareUpdatePoll() const [ isEmergencyStopModalDismissed, setIsEmergencyStopModalDismissed, - ] = React.useState(false) + ] = useState(false) const desktopRoutes: RouteProps[] = [ { Component: ProtocolsLanding, - name: 'Protocols', + name: 'protocols', navLinkTo: '/protocols', path: '/protocols', }, @@ -64,13 +68,13 @@ export const DesktopApp = (): JSX.Element => { }, { Component: Labware, - name: 'Labware', + name: 'labware', navLinkTo: '/labware', path: '/labware', }, { Component: DevicesLanding, - name: 'Devices', + name: 'devices', navLinkTo: '/devices', path: '/devices', }, @@ -103,8 +107,10 @@ export const DesktopApp = (): JSX.Element => { return ( - + + + { + { - + } path={path} /> @@ -152,17 +158,16 @@ export const DesktopApp = (): JSX.Element => { - + ) } function RobotControlTakeover(): JSX.Element | null { - const deviceRouteMatch = useMatch('/devices/:robotName') - const params = deviceRouteMatch?.params as DesktopRouteParams - const robotName = params?.robotName + const deviceRouteMatch = useMatch('/devices/:robotName/*') + const params = deviceRouteMatch?.params + const robotName = params?.robotName ?? null const robot = useRobot(robotName) - if (deviceRouteMatch == null || robot == null || robotName == null) return null diff --git a/app/src/App/DesktopAppFallback.tsx b/app/src/App/DesktopAppFallback.tsx index d39552323a3..1bf2645e8b1 100644 --- a/app/src/App/DesktopAppFallback.tsx +++ b/app/src/App/DesktopAppFallback.tsx @@ -1,9 +1,11 @@ -import * as React from 'react' import { useDispatch } from 'react-redux' import { useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' -import { useTrackEvent, ANALYTICS_DESKTOP_APP_ERROR } from '../redux/analytics' +import { + useTrackEvent, + ANALYTICS_DESKTOP_APP_ERROR, +} from '/app/redux/analytics' import type { FallbackProps } from 'react-error-boundary' @@ -18,9 +20,9 @@ import { Modal, } from '@opentrons/components' -import { reloadUi } from '../redux/shell' +import { reloadUi } from '/app/redux/shell' -import type { Dispatch } from '../redux/types' +import type { Dispatch } from '/app/redux/types' export function DesktopAppFallback({ error }: FallbackProps): JSX.Element { const { t } = useTranslation('app_settings') diff --git a/app/src/App/Navbar.tsx b/app/src/App/Navbar.tsx index eaea2575836..1471ca4c593 100644 --- a/app/src/App/Navbar.tsx +++ b/app/src/App/Navbar.tsx @@ -1,4 +1,5 @@ -import * as React from 'react' +import { useCallback } from 'react' +import { useTranslation } from 'react-i18next' import { NavLink, useNavigate } from 'react-router-dom' import styled from 'styled-components' import debounce from 'lodash/debounce' @@ -20,8 +21,8 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import logoSvg from '../assets/images/logo_nav.svg' -import logoSvgThree from '../assets/images/logo_nav_three.svg' +import logoSvg from '/app/assets/images/logo_nav.svg' +import logoSvgThree from '/app/assets/images/logo_nav_three.svg' import { NAV_BAR_WIDTH } from './constants' @@ -110,12 +111,14 @@ const LogoImg = styled('img')` ` export function Navbar({ routes }: { routes: RouteProps[] }): JSX.Element { + const { t } = useTranslation('top_navigation') + const navigate = useNavigate() const navRoutes = routes.filter( ({ navLinkTo }: RouteProps) => navLinkTo != null ) - const debouncedNavigate = React.useCallback( + const debouncedNavigate = useCallback( debounce((path: string) => { navigate(path) }, DEBOUNCE_DURATION_MS), @@ -148,7 +151,7 @@ export function Navbar({ routes }: { routes: RouteProps[] }): JSX.Element { as="h3" margin={`${SPACING.spacing8} 0 ${SPACING.spacing8} ${SPACING.spacing12}`} > - {name} + {t(name)} ))} diff --git a/app/src/App/ODDTopLevelRedirects/constants.ts b/app/src/App/ODDTopLevelRedirects/constants.ts new file mode 100644 index 00000000000..002bea95326 --- /dev/null +++ b/app/src/App/ODDTopLevelRedirects/constants.ts @@ -0,0 +1 @@ +export const CURRENT_RUN_POLL = 5000 diff --git a/app/src/App/ODDTopLevelRedirects/hooks/__tests__/useCurrentRunRoute.test.ts b/app/src/App/ODDTopLevelRedirects/hooks/__tests__/useCurrentRunRoute.test.ts new file mode 100644 index 00000000000..8be03f904d3 --- /dev/null +++ b/app/src/App/ODDTopLevelRedirects/hooks/__tests__/useCurrentRunRoute.test.ts @@ -0,0 +1,146 @@ +import { renderHook } from '@testing-library/react' +import { describe, it, expect, vi } from 'vitest' +import { useCurrentRunRoute } from '../useCurrentRunRoute' +import { useNotifyRunQuery } from '/app/resources/runs' +import { + RUN_STATUS_BLOCKED_BY_OPEN_DOOR, + RUN_STATUS_FAILED, + RUN_STATUS_IDLE, + RUN_STATUS_STOPPED, + RUN_STATUS_SUCCEEDED, +} from '@opentrons/api-client' + +vi.mock('/app/resources/runs') + +const MOCK_RUN_ID = 'MOCK_RUN_ID' + +describe('useCurrentRunRoute', () => { + it('returns null when the run record is null', () => { + vi.mocked(useNotifyRunQuery).mockReturnValue({ + data: null, + isFetching: false, + } as any) + + const { result } = renderHook(() => useCurrentRunRoute(MOCK_RUN_ID)) + + expect(result.current).toBeNull() + }) + + it('returns null when isFetching is true', () => { + vi.mocked(useNotifyRunQuery).mockReturnValue({ + data: { data: { startedAt: '123' } }, + isFetching: true, + } as any) + + const { result } = renderHook(() => useCurrentRunRoute(MOCK_RUN_ID)) + + expect(result.current).toBeNull() + }) + + it('returns the summary route for a run with succeeded status', () => { + vi.mocked(useNotifyRunQuery).mockReturnValue({ + data: { + data: { + id: MOCK_RUN_ID, + status: RUN_STATUS_SUCCEEDED, + startedAt: '123', + }, + }, + isFetching: false, + } as any) + + const { result } = renderHook(() => useCurrentRunRoute(MOCK_RUN_ID)) + + expect(result.current).toBe(`/runs/${MOCK_RUN_ID}/summary`) + }) + + it('returns the summary route for a started run that has a stopped status', () => { + vi.mocked(useNotifyRunQuery).mockReturnValue({ + data: { + data: { + id: MOCK_RUN_ID, + status: RUN_STATUS_STOPPED, + startedAt: '123', + }, + }, + isFetching: false, + } as any) + + const { result } = renderHook(() => useCurrentRunRoute(MOCK_RUN_ID)) + + expect(result.current).toBe(`/runs/${MOCK_RUN_ID}/summary`) + }) + + it('returns summary route for a run with failed status', () => { + vi.mocked(useNotifyRunQuery).mockReturnValue({ + data: { + data: { id: MOCK_RUN_ID, status: RUN_STATUS_FAILED, startedAt: '123' }, + }, + isFetching: false, + } as any) + + const { result } = renderHook(() => useCurrentRunRoute(MOCK_RUN_ID)) + + expect(result.current).toBe(`/runs/${MOCK_RUN_ID}/summary`) + }) + + it('returns the setup route for a run with an idle status', () => { + vi.mocked(useNotifyRunQuery).mockReturnValue({ + data: { + data: { id: MOCK_RUN_ID, status: RUN_STATUS_IDLE, startedAt: null }, + }, + isFetching: false, + } as any) + + const { result } = renderHook(() => useCurrentRunRoute(MOCK_RUN_ID)) + + expect(result.current).toBe(`/runs/${MOCK_RUN_ID}/setup`) + }) + + it('returns the setup route for a "blocked by open door" run that has not been started yet', () => { + vi.mocked(useNotifyRunQuery).mockReturnValue({ + data: { + data: { + id: MOCK_RUN_ID, + status: RUN_STATUS_BLOCKED_BY_OPEN_DOOR, + startedAt: null, + }, + }, + isFetching: false, + } as any) + + const { result } = renderHook(() => useCurrentRunRoute(MOCK_RUN_ID)) + + expect(result.current).toBe(`/runs/${MOCK_RUN_ID}/setup`) + }) + + it('returns run route for a started run', () => { + vi.mocked(useNotifyRunQuery).mockReturnValue({ + data: { + data: { + id: MOCK_RUN_ID, + status: RUN_STATUS_BLOCKED_BY_OPEN_DOOR, + startedAt: '123', + }, + }, + isFetching: false, + } as any) + + const { result } = renderHook(() => useCurrentRunRoute(MOCK_RUN_ID)) + + expect(result.current).toBe(`/runs/${MOCK_RUN_ID}/run`) + }) + + it('returns null for cancelled run before starting', () => { + vi.mocked(useNotifyRunQuery).mockReturnValue({ + data: { + data: { id: MOCK_RUN_ID, status: RUN_STATUS_STOPPED, startedAt: null }, + }, + isFetching: false, + } as any) + + const { result } = renderHook(() => useCurrentRunRoute(MOCK_RUN_ID)) + + expect(result.current).toBeNull() + }) +}) diff --git a/app/src/App/ODDTopLevelRedirects/hooks/index.ts b/app/src/App/ODDTopLevelRedirects/hooks/index.ts new file mode 100644 index 00000000000..d93d3540a9c --- /dev/null +++ b/app/src/App/ODDTopLevelRedirects/hooks/index.ts @@ -0,0 +1 @@ +export { useCurrentRunRoute } from './useCurrentRunRoute' diff --git a/app/src/App/ODDTopLevelRedirects/hooks/useCurrentRunRoute.ts b/app/src/App/ODDTopLevelRedirects/hooks/useCurrentRunRoute.ts new file mode 100644 index 00000000000..a2b407cb581 --- /dev/null +++ b/app/src/App/ODDTopLevelRedirects/hooks/useCurrentRunRoute.ts @@ -0,0 +1,42 @@ +import { + RUN_STATUS_BLOCKED_BY_OPEN_DOOR, + RUN_STATUS_FAILED, + RUN_STATUS_IDLE, + RUN_STATUS_STOPPED, + RUN_STATUS_SUCCEEDED, +} from '@opentrons/api-client' + +import { useNotifyRunQuery } from '/app/resources/runs' +import { CURRENT_RUN_POLL } from '../constants' + +// Returns the route to which React Router should navigate, if any. +export function useCurrentRunRoute(currentRunId: string): string | null { + const { data: runRecord, isFetching } = useNotifyRunQuery(currentRunId, { + refetchInterval: CURRENT_RUN_POLL, + }) + + // grabbing run id off of the run query to have all routing info come from one source of truth + const runId = runRecord?.data.id + const hasRunStarted = runRecord?.data.startedAt != null + const runStatus = runRecord?.data.status + + if (isFetching) { + return null + } else if ( + runStatus === RUN_STATUS_SUCCEEDED || + (runStatus === RUN_STATUS_STOPPED && hasRunStarted) || + runStatus === RUN_STATUS_FAILED + ) { + return `/runs/${runId}/summary` + } else if ( + runStatus === RUN_STATUS_IDLE || + (!hasRunStarted && runStatus === RUN_STATUS_BLOCKED_BY_OPEN_DOOR) + ) { + return `/runs/${runId}/setup` + } else if (hasRunStarted) { + return `/runs/${runId}/run` + } else { + // includes runs cancelled before starting and runs not yet started + return null + } +} diff --git a/app/src/App/ODDTopLevelRedirects/index.tsx b/app/src/App/ODDTopLevelRedirects/index.tsx new file mode 100644 index 00000000000..fa0692bd1c4 --- /dev/null +++ b/app/src/App/ODDTopLevelRedirects/index.tsx @@ -0,0 +1,27 @@ +import { Navigate, Route, Routes } from 'react-router-dom' + +import { useCurrentRunId } from '/app/resources/runs' +import { CURRENT_RUN_POLL } from './constants' +import { useCurrentRunRoute } from './hooks' + +export function ODDTopLevelRedirects(): JSX.Element | null { + const currentRunId = useCurrentRunId({ refetchInterval: CURRENT_RUN_POLL }) + + return currentRunId != null ? ( + + ) : null +} + +function CurrentRunRoute({ + currentRunId, +}: { + currentRunId: string +}): JSX.Element | null { + const currentRunRoute = useCurrentRunRoute(currentRunId) + + return currentRunRoute != null ? ( + + } /> + + ) : null +} diff --git a/app/src/App/OnDeviceDisplayApp.tsx b/app/src/App/OnDeviceDisplayApp.tsx index 77aceabce20..6e8c78449f0 100644 --- a/app/src/App/OnDeviceDisplayApp.tsx +++ b/app/src/App/OnDeviceDisplayApp.tsx @@ -1,71 +1,74 @@ -import * as React from 'react' +import { useEffect, useState, useCallback } from 'react' import { useDispatch, useSelector } from 'react-redux' -import { Routes, Route, Navigate } from 'react-router-dom' +import { Routes, Route, Navigate, useLocation } from 'react-router-dom' import { css } from 'styled-components' import { ErrorBoundary } from 'react-error-boundary' import { Box, - POSITION_RELATIVE, COLORS, OVERFLOW_AUTO, + POSITION_RELATIVE, useIdle, useScrolling, } from '@opentrons/components' import { ApiHostProvider } from '@opentrons/react-api-client' import NiceModal from '@ebay/nice-modal-react' -import { SleepScreen } from '../atoms/SleepScreen' -import { OnDeviceLocalizationProvider } from '../LocalizationProvider' -import { ToasterOven } from '../organisms/ToasterOven' -import { MaintenanceRunTakeover } from '../organisms/TakeoverModal' -import { FirmwareUpdateTakeover } from '../organisms/FirmwareUpdateModal/FirmwareUpdateTakeover' -import { IncompatibleModuleTakeover } from '../organisms/IncompatibleModule' -import { EstopTakeover } from '../organisms/EmergencyStop' -import { ConnectViaEthernet } from '../pages/ConnectViaEthernet' -import { ConnectViaUSB } from '../pages/ConnectViaUSB' -import { ConnectViaWifi } from '../pages/ConnectViaWifi' -import { EmergencyStop } from '../pages/EmergencyStop' -import { NameRobot } from '../pages/NameRobot' -import { NetworkSetupMenu } from '../pages/NetworkSetupMenu' -import { ProtocolSetup } from '../pages/ProtocolSetup' -import { RobotDashboard } from '../pages/RobotDashboard' -import { RobotSettingsDashboard } from '../pages/RobotSettingsDashboard' -import { ProtocolDashboard } from '../pages/ProtocolDashboard' -import { ProtocolDetails } from '../pages/ProtocolDetails' -import { QuickTransferFlow } from '../organisms/QuickTransferFlow' -import { QuickTransferDashboard } from '../pages/QuickTransferDashboard' -import { QuickTransferDetails } from '../pages/QuickTransferDetails' -import { RunningProtocol } from '../pages/RunningProtocol' -import { RunSummary } from '../pages/RunSummary' -import { UpdateRobot } from '../pages/UpdateRobot/UpdateRobot' -import { UpdateRobotDuringOnboarding } from '../pages/UpdateRobot/UpdateRobotDuringOnboarding' -import { InstrumentsDashboard } from '../pages/InstrumentsDashboard' -import { InstrumentDetail } from '../pages/InstrumentDetail' -import { Welcome } from '../pages/Welcome' -import { InitialLoadingScreen } from '../pages/InitialLoadingScreen' -import { DeckConfigurationEditor } from '../pages/DeckConfiguration' +import { SleepScreen } from '/app/atoms/SleepScreen' +import { LocalizationProvider } from '../LocalizationProvider' +import { ToasterOven } from '/app/organisms/ToasterOven' +import { MaintenanceRunTakeover } from '/app/organisms/TakeoverModal' +import { FirmwareUpdateTakeover } from '/app/organisms/FirmwareUpdateModal/FirmwareUpdateTakeover' +import { IncompatibleModuleTakeover } from '/app/organisms/IncompatibleModule' +import { EstopTakeover } from '/app/organisms/EmergencyStop' +import { ChooseLanguage } from '/app/pages/ODD/ChooseLanguage' +import { ConnectViaEthernet } from '/app/pages/ODD/ConnectViaEthernet' +import { ConnectViaUSB } from '/app/pages/ODD/ConnectViaUSB' +import { ConnectViaWifi } from '/app/pages/ODD/ConnectViaWifi' +import { EmergencyStop } from '/app/pages/ODD/EmergencyStop' +import { NameRobot } from '/app/pages/ODD/NameRobot' +import { NetworkSetupMenu } from '/app/pages/ODD/NetworkSetupMenu' +import { ProtocolSetup } from '/app/pages/ODD/ProtocolSetup' +import { RobotDashboard } from '/app/pages/ODD/RobotDashboard' +import { RobotSettingsDashboard } from '/app/pages/ODD/RobotSettingsDashboard' +import { ProtocolDashboard } from '/app/pages/ODD/ProtocolDashboard' +import { ProtocolDetails } from '/app/pages/ODD/ProtocolDetails' +import { QuickTransferFlow } from '/app/organisms/ODD/QuickTransferFlow' +import { QuickTransferDashboard } from '/app/pages/ODD/QuickTransferDashboard' +import { QuickTransferDetails } from '/app/pages/ODD/QuickTransferDetails' +import { RunningProtocol } from '/app/pages/ODD/RunningProtocol' +import { RunSummary } from '/app/pages/ODD/RunSummary' +import { UpdateRobot } from '/app/pages/ODD/UpdateRobot/UpdateRobot' +import { UpdateRobotDuringOnboarding } from '/app/pages/ODD/UpdateRobot/UpdateRobotDuringOnboarding' +import { InstrumentsDashboard } from '/app/pages/ODD/InstrumentsDashboard' +import { InstrumentDetail } from '/app/pages/ODD/InstrumentDetail' +import { Welcome } from '/app/pages/ODD/Welcome' +import { InitialLoadingScreen } from '/app/pages/ODD/InitialLoadingScreen' +import { DeckConfigurationEditor } from '/app/pages/ODD/DeckConfiguration' import { PortalRoot as ModalPortalRoot } from './portal' -import { getOnDeviceDisplaySettings, updateConfigValue } from '../redux/config' -import { updateBrightness } from '../redux/shell' -import { SLEEP_NEVER_MS } from './constants' import { - useCurrentRunRoute, - useProtocolReceiptToast, - useSoftwareUpdatePoll, -} from './hooks' + getOnDeviceDisplaySettings, + updateConfigValue, +} from '/app/redux/config' +import { updateBrightness } from '/app/redux/shell' +import { SLEEP_NEVER_MS } from '/app/local-resources/config' +import { useProtocolReceiptToast, useSoftwareUpdatePoll } from './hooks' +import { ODDTopLevelRedirects } from './ODDTopLevelRedirects' +import { ReactQueryDevtools } from '/app/App/tools' import { OnDeviceDisplayAppFallback } from './OnDeviceDisplayAppFallback' import { hackWindowNavigatorOnLine } from './hacks' -import type { Dispatch } from '../redux/types' +import type { Dispatch } from '/app/redux/types' // forces electron to think we're online which means axios won't elide // network calls to localhost. see ./hacks.ts for more. hackWindowNavigatorOnLine() export const ON_DEVICE_DISPLAY_PATHS = [ + '/choose-language', '/dashboard', '/deck-configuration', '/emergency-stop', @@ -94,6 +97,8 @@ function getPathComponent( path: typeof ON_DEVICE_DISPLAY_PATHS[number] ): JSX.Element { switch (path) { + case '/choose-language': + return case '/dashboard': return case '/deck-configuration': @@ -163,7 +168,7 @@ export const OnDeviceDisplayApp = (): JSX.Element => { const dispatch = useDispatch() const isIdle = useIdle(sleepTime, options) - React.useEffect(() => { + useEffect(() => { if (isIdle) { dispatch(updateBrightness(TURN_OFF_BACKLIGHT)) } else { @@ -179,17 +184,18 @@ export const OnDeviceDisplayApp = (): JSX.Element => { // TODO (sb:6/12/23) Create a notification manager to set up preference and order of takeover modals return ( + - + {isIdle ? ( ) : ( <> - + @@ -201,9 +207,9 @@ export const OnDeviceDisplayApp = (): JSX.Element => { )} - + - + ) @@ -220,11 +226,19 @@ const getTargetPath = (unfinishedUnboxingFlowRoute: string | null): string => { // split to a separate function because scrollRef rerenders on every route change // this avoids rerendering parent providers as well export function OnDeviceDisplayAppRoutes(): JSX.Element { - const [currentNode, setCurrentNode] = React.useState(null) - const scrollRef = React.useCallback((node: HTMLElement | null) => { + const [currentNode, setCurrentNode] = useState(null) + const scrollRef = useCallback((node: HTMLElement | null) => { setCurrentNode(node) }, []) const isScrolling = useScrolling(currentNode) + const location = useLocation() + useEffect(() => { + currentNode?.scrollTo({ + top: 0, + left: 0, + behavior: 'auto', + }) + }, [location.pathname]) const { unfinishedUnboxingFlowRoute } = useSelector( getOnDeviceDisplaySettings @@ -272,15 +286,6 @@ export function OnDeviceDisplayAppRoutes(): JSX.Element { ) } -function TopLevelRedirects(): JSX.Element | null { - const currentRunRoute = useCurrentRunRoute() - return currentRunRoute != null ? ( - - } /> - - ) : null -} - function ProtocolReceiptToasts(): null { useProtocolReceiptToast() return null diff --git a/app/src/App/OnDeviceDisplayAppFallback.tsx b/app/src/App/OnDeviceDisplayAppFallback.tsx index c69e581f5c5..6077db2d4da 100644 --- a/app/src/App/OnDeviceDisplayAppFallback.tsx +++ b/app/src/App/OnDeviceDisplayAppFallback.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import { useEffect } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' -import { useTrackEvent, ANALYTICS_ODD_APP_ERROR } from '../redux/analytics' -import { getLocalRobot, getRobotSerialNumber } from '../redux/discovery' +import { useTrackEvent, ANALYTICS_ODD_APP_ERROR } from '/app/redux/analytics' +import { getLocalRobot, getRobotSerialNumber } from '/app/redux/discovery' import type { FallbackProps } from 'react-error-boundary' @@ -17,12 +17,12 @@ import { LegacyStyledText, } from '@opentrons/components' -import { MediumButton } from '../atoms/buttons' -import { OddModal } from '../molecules/OddModal' -import { appRestart, sendLog } from '../redux/shell' +import { MediumButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' +import { appRestart, sendLog } from '/app/redux/shell' -import type { Dispatch } from '../redux/types' -import type { OddModalHeaderBaseProps } from '../molecules/OddModal/types' +import type { Dispatch } from '/app/redux/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' export function OnDeviceDisplayAppFallback({ error, @@ -47,7 +47,7 @@ export function OnDeviceDisplayAppFallback({ } // immediately report to robot logs that something fatal happened - React.useEffect(() => { + useEffect(() => { dispatch(sendLog(`ODD app encountered a fatal error: ${error.message}`)) }, []) diff --git a/app/src/App/__mocks__/portal.tsx b/app/src/App/__mocks__/portal.tsx index 67fd57af2de..f80b1deb44e 100644 --- a/app/src/App/__mocks__/portal.tsx +++ b/app/src/App/__mocks__/portal.tsx @@ -1,5 +1,5 @@ // mock portal for enzyme tests -import * as React from 'react' +import type * as React from 'react' interface Props { children: React.ReactNode diff --git a/app/src/App/__tests__/App.test.tsx b/app/src/App/__tests__/App.test.tsx index 485c2497949..0f5faf53894 100644 --- a/app/src/App/__tests__/App.test.tsx +++ b/app/src/App/__tests__/App.test.tsx @@ -1,19 +1,18 @@ -import * as React from 'react' import { vi, describe, beforeEach, afterEach, expect, it } from 'vitest' import { when } from 'vitest-when' import { screen } from '@testing-library/react' -import { i18n } from '../../i18n' -import { getIsOnDevice, getConfig } from '../../redux/config' +import { i18n } from '/app/i18n' +import { getIsOnDevice, getConfig } from '/app/redux/config' import { DesktopApp } from '../DesktopApp' import { OnDeviceDisplayApp } from '../OnDeviceDisplayApp' import { App } from '../' -import type { State } from '../../redux/types' -import { renderWithProviders } from '../../__testing-utils__' +import type { State } from '/app/redux/types' +import { renderWithProviders } from '/app/__testing-utils__' -vi.mock('../../redux/config') +vi.mock('/app/redux/config') vi.mock('../DesktopApp') vi.mock('../OnDeviceDisplayApp') diff --git a/app/src/App/__tests__/DesktopApp.test.tsx b/app/src/App/__tests__/DesktopApp.test.tsx index 8def97d4534..085ff9ef7ba 100644 --- a/app/src/App/__tests__/DesktopApp.test.tsx +++ b/app/src/App/__tests__/DesktopApp.test.tsx @@ -1,38 +1,43 @@ -import * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { screen } from '@testing-library/react' import { when } from 'vitest-when' import { vi, describe, beforeEach, afterEach, expect, it } from 'vitest' -import { renderWithProviders } from '../../__testing-utils__' -import { i18n } from '../../i18n' -import { Breadcrumbs } from '../../organisms/Breadcrumbs' -import { CalibrationDashboard } from '../../pages/Devices/CalibrationDashboard' -import { DeviceDetails } from '../../pages/Devices/DeviceDetails' -import { DevicesLanding } from '../../pages/Devices/DevicesLanding' -import { ProtocolsLanding } from '../../pages/Protocols/ProtocolsLanding' -import { ProtocolRunDetails } from '../../pages/Devices/ProtocolRunDetails' -import { RobotSettings } from '../../pages/Devices/RobotSettings' -import { GeneralSettings } from '../../pages/AppSettings/GeneralSettings' -import { AlertsModal } from '../../organisms/Alerts/AlertsModal' -import { useFeatureFlag } from '../../redux/config' -import { useIsFlex } from '../../organisms/Devices/hooks' -import { ProtocolTimeline } from '../../pages/Protocols/ProtocolDetails/ProtocolTimeline' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { LocalizationProvider } from '/app/LocalizationProvider' +import { Breadcrumbs } from '/app/organisms/Desktop/Breadcrumbs' +import { SystemLanguagePreferenceModal } from '/app/organisms/Desktop/SystemLanguagePreferenceModal' +import { CalibrationDashboard } from '/app/pages/Desktop/Devices/CalibrationDashboard' +import { DeviceDetails } from '/app/pages/Desktop/Devices/DeviceDetails' +import { DevicesLanding } from '/app/pages/Desktop/Devices/DevicesLanding' +import { ProtocolsLanding } from '/app/pages/Desktop/Protocols/ProtocolsLanding' +import { ProtocolRunDetails } from '/app/pages/Desktop/Devices/ProtocolRunDetails' +import { RobotSettings } from '/app/pages/Desktop/Devices/RobotSettings' +import { GeneralSettings } from '/app/pages/Desktop/AppSettings/GeneralSettings' +import { AlertsModal } from '/app/organisms/Desktop/Alerts/AlertsModal' +import { useFeatureFlag } from '/app/redux/config' +import { useIsFlex } from '/app/redux-resources/robots' +import { ProtocolTimeline } from '/app/pages/Desktop/Protocols/ProtocolDetails/ProtocolTimeline' import { useSoftwareUpdatePoll } from '../hooks' import { DesktopApp } from '../DesktopApp' -vi.mock('../../organisms/Breadcrumbs') -vi.mock('../../organisms/Devices/hooks') -vi.mock('../../pages/AppSettings/GeneralSettings') -vi.mock('../../pages/Devices/CalibrationDashboard') -vi.mock('../../pages/Devices/DeviceDetails') -vi.mock('../../pages/Devices/DevicesLanding') -vi.mock('../../pages/Protocols/ProtocolsLanding') -vi.mock('../../pages/Devices/ProtocolRunDetails') -vi.mock('../../pages/Devices/RobotSettings') -vi.mock('../../organisms/Alerts/AlertsModal') -vi.mock('../../pages/Protocols/ProtocolDetails/ProtocolTimeline') -vi.mock('../../redux/config') +import type { LocalizationProviderProps } from '/app/LocalizationProvider' + +vi.mock('/app/LocalizationProvider') +vi.mock('/app/organisms/Desktop/Breadcrumbs') +vi.mock('/app/organisms/Desktop/SystemLanguagePreferenceModal') +vi.mock('/app/pages/Desktop/AppSettings/GeneralSettings') +vi.mock('/app/pages/Desktop/Devices/CalibrationDashboard') +vi.mock('/app/pages/Desktop/Devices/DeviceDetails') +vi.mock('/app/pages/Desktop/Devices/DevicesLanding') +vi.mock('/app/pages/Desktop/Protocols/ProtocolsLanding') +vi.mock('/app/pages/Desktop/Devices/ProtocolRunDetails') +vi.mock('/app/pages/Desktop/Devices/RobotSettings') +vi.mock('/app/organisms/Desktop/Alerts/AlertsModal') +vi.mock('/app/pages/Desktop/Protocols/ProtocolDetails/ProtocolTimeline') +vi.mock('/app/redux/config') +vi.mock('/app/redux-resources/robots') vi.mock('../hooks') const render = (path = '/') => { @@ -66,8 +71,16 @@ describe('DesktopApp', () => { vi.mocked(RobotSettings).mockReturnValue(

Mock RobotSettings
) vi.mocked(GeneralSettings).mockReturnValue(
Mock AppSettings
) vi.mocked(Breadcrumbs).mockReturnValue(
Mock Breadcrumbs
) + vi.mocked(SystemLanguagePreferenceModal).mockReturnValue( +
Mock SystemLanguagePreferenceModal
+ ) vi.mocked(AlertsModal).mockReturnValue(<>) vi.mocked(useIsFlex).mockReturnValue(true) + vi.mocked( + LocalizationProvider + ).mockImplementation((props: LocalizationProviderProps) => ( + <>{props.children} + )) }) afterEach(() => { vi.resetAllMocks() @@ -77,6 +90,11 @@ describe('DesktopApp', () => { screen.getByText('Mock Breadcrumbs') }) + it('renders a SystemLanguagePreferenceModal component', () => { + render('/protocols') + screen.getByText('Mock SystemLanguagePreferenceModal') + }) + it('renders an AppSettings component', () => { render('/app-settings/general') screen.getByText('Mock AppSettings') diff --git a/app/src/App/__tests__/Navbar.test.tsx b/app/src/App/__tests__/Navbar.test.tsx index db7eedc744b..508323c739c 100644 --- a/app/src/App/__tests__/Navbar.test.tsx +++ b/app/src/App/__tests__/Navbar.test.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { describe, it } from 'vitest' import { screen, render } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' diff --git a/app/src/App/__tests__/OnDeviceDisplayApp.test.tsx b/app/src/App/__tests__/OnDeviceDisplayApp.test.tsx index f088e2e6ac1..2d2d09ca15e 100644 --- a/app/src/App/__tests__/OnDeviceDisplayApp.test.tsx +++ b/app/src/App/__tests__/OnDeviceDisplayApp.test.tsx @@ -1,39 +1,40 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { vi, describe, beforeEach, afterEach, expect, it } from 'vitest' import { MemoryRouter } from 'react-router-dom' -import { renderWithProviders } from '../../__testing-utils__' -import { i18n } from '../../i18n' -import { OnDeviceLocalizationProvider } from '../../LocalizationProvider' -import { ConnectViaEthernet } from '../../pages/ConnectViaEthernet' -import { ConnectViaUSB } from '../../pages/ConnectViaUSB' -import { ConnectViaWifi } from '../../pages/ConnectViaWifi' -import { NetworkSetupMenu } from '../../pages/NetworkSetupMenu' -import { InstrumentsDashboard } from '../../pages/InstrumentsDashboard' -import { RobotDashboard } from '../../pages/RobotDashboard' -import { RobotSettingsDashboard } from '../../pages/RobotSettingsDashboard' -import { ProtocolDashboard } from '../../pages/ProtocolDashboard' -import { ProtocolSetup } from '../../pages/ProtocolSetup' -import { ProtocolDetails } from '../../pages/ProtocolDetails' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { LocalizationProvider } from '../../LocalizationProvider' +import { ChooseLanguage } from '/app/pages/ODD/ChooseLanguage' +import { ConnectViaEthernet } from '/app/pages/ODD/ConnectViaEthernet' +import { ConnectViaUSB } from '/app/pages/ODD/ConnectViaUSB' +import { ConnectViaWifi } from '/app/pages/ODD/ConnectViaWifi' +import { NetworkSetupMenu } from '/app/pages/ODD/NetworkSetupMenu' +import { InstrumentsDashboard } from '/app/pages/ODD/InstrumentsDashboard' +import { RobotDashboard } from '/app/pages/ODD/RobotDashboard' +import { RobotSettingsDashboard } from '/app/pages/ODD/RobotSettingsDashboard' +import { ProtocolDashboard } from '/app/pages/ODD/ProtocolDashboard' +import { ProtocolSetup } from '/app/pages/ODD/ProtocolSetup' +import { ProtocolDetails } from '/app/pages/ODD/ProtocolDetails' import { OnDeviceDisplayApp } from '../OnDeviceDisplayApp' -import { RunningProtocol } from '../../pages/RunningProtocol' -import { RunSummary } from '../../pages/RunSummary' -import { Welcome } from '../../pages/Welcome' -import { NameRobot } from '../../pages/NameRobot' -import { EmergencyStop } from '../../pages/EmergencyStop' -import { DeckConfigurationEditor } from '../../pages/DeckConfiguration' -import { getOnDeviceDisplaySettings } from '../../redux/config' -import { getIsShellReady } from '../../redux/shell' -import { getLocalRobot } from '../../redux/discovery' -import { mockConnectedRobot } from '../../redux/discovery/__fixtures__' -import { useCurrentRunRoute, useProtocolReceiptToast } from '../hooks' -import { useNotifyCurrentMaintenanceRun } from '../../resources/maintenance_runs' +import { RunningProtocol } from '/app/pages/ODD/RunningProtocol' +import { RunSummary } from '/app/pages/ODD/RunSummary' +import { Welcome } from '/app/pages/ODD/Welcome' +import { NameRobot } from '/app/pages/ODD/NameRobot' +import { EmergencyStop } from '/app/pages/ODD/EmergencyStop' +import { DeckConfigurationEditor } from '/app/pages/ODD/DeckConfiguration' +import { getOnDeviceDisplaySettings } from '/app/redux/config' +import { getIsShellReady } from '/app/redux/shell' +import { getLocalRobot } from '/app/redux/discovery' +import { mockConnectedRobot } from '/app/redux/discovery/__fixtures__' +import { useProtocolReceiptToast } from '../hooks' +import { useNotifyCurrentMaintenanceRun } from '/app/resources/maintenance_runs' +import { ODDTopLevelRedirects } from '../ODDTopLevelRedirects' import type { UseQueryResult } from 'react-query' import type { RobotSettingsResponse } from '@opentrons/api-client' -import type { OnDeviceLocalizationProviderProps } from '../../LocalizationProvider' -import type { OnDeviceDisplaySettings } from '../../redux/config/schema-types' +import type { LocalizationProviderProps } from '../../LocalizationProvider' +import type { OnDeviceDisplaySettings } from '/app/redux/config/schema-types' vi.mock('@opentrons/react-api-client', async () => { const actual = await vi.importActual('@opentrons/react-api-client') @@ -46,27 +47,29 @@ vi.mock('@opentrons/react-api-client', async () => { } }) vi.mock('../../LocalizationProvider') -vi.mock('../../pages/Welcome') -vi.mock('../../pages/NetworkSetupMenu') -vi.mock('../../pages/ConnectViaEthernet') -vi.mock('../../pages/ConnectViaUSB') -vi.mock('../../pages/ConnectViaWifi') -vi.mock('../../pages/RobotDashboard') -vi.mock('../../pages/RobotSettingsDashboard') -vi.mock('../../pages/ProtocolDashboard') -vi.mock('../../pages/ProtocolSetup') -vi.mock('../../pages/ProtocolDetails') -vi.mock('../../pages/InstrumentsDashboard') -vi.mock('../../pages/RunningProtocol') -vi.mock('../../pages/RunSummary') -vi.mock('../../pages/NameRobot') -vi.mock('../../pages/EmergencyStop') -vi.mock('../../pages/DeckConfiguration') -vi.mock('../../redux/config') -vi.mock('../../redux/shell') -vi.mock('../../redux/discovery') -vi.mock('../../resources/maintenance_runs') +vi.mock('/app/pages/ODD/Welcome') +vi.mock('/app/pages/ODD/NetworkSetupMenu') +vi.mock('/app/pages/ODD/ChooseLanguage') +vi.mock('/app/pages/ODD/ConnectViaEthernet') +vi.mock('/app/pages/ODD/ConnectViaUSB') +vi.mock('/app/pages/ODD/ConnectViaWifi') +vi.mock('/app/pages/ODD/RobotDashboard') +vi.mock('/app/pages/ODD/RobotSettingsDashboard') +vi.mock('/app/pages/ODD/ProtocolDashboard') +vi.mock('/app/pages/ODD/ProtocolSetup') +vi.mock('/app/pages/ODD/ProtocolDetails') +vi.mock('/app/pages/ODD/InstrumentsDashboard') +vi.mock('/app/pages/ODD/RunningProtocol') +vi.mock('/app/pages/ODD/RunSummary') +vi.mock('/app/pages/ODD/NameRobot') +vi.mock('/app/pages/ODD/EmergencyStop') +vi.mock('/app/pages/ODD/DeckConfiguration') +vi.mock('/app/redux/config') +vi.mock('/app/redux/shell') +vi.mock('/app/redux/discovery') +vi.mock('/app/resources/maintenance_runs') vi.mock('../hooks') +vi.mock('../ODDTopLevelRedirects') const mockSettings = { sleepMs: 60 * 1000 * 60 * 24 * 7, @@ -88,7 +91,7 @@ describe('OnDeviceDisplayApp', () => { beforeEach(() => { vi.mocked(getOnDeviceDisplaySettings).mockReturnValue(mockSettings as any) vi.mocked(getIsShellReady).mockReturnValue(true) - vi.mocked(useCurrentRunRoute).mockReturnValue(null) + vi.mocked(ODDTopLevelRedirects).mockReturnValue(null) vi.mocked(getLocalRobot).mockReturnValue(mockConnectedRobot) vi.mocked(useNotifyCurrentMaintenanceRun).mockReturnValue({ data: { @@ -99,8 +102,8 @@ describe('OnDeviceDisplayApp', () => { } as any) // TODO(bh, 2024-03-27): implement testing of branded and anonymous i18n, but for now pass through vi.mocked( - OnDeviceLocalizationProvider - ).mockImplementation((props: OnDeviceLocalizationProviderProps) => ( + LocalizationProvider + ).mockImplementation((props: LocalizationProviderProps) => ( <>{props.children} )) }) @@ -108,6 +111,10 @@ describe('OnDeviceDisplayApp', () => { vi.resetAllMocks() }) + it('renders ChooseLanguage component from /choose-language', () => { + render('/choose-language') + expect(vi.mocked(ChooseLanguage)).toHaveBeenCalled() + }) it('renders Welcome component from /welcome', () => { render('/welcome') expect(vi.mocked(Welcome)).toHaveBeenCalled() @@ -162,14 +169,14 @@ describe('OnDeviceDisplayApp', () => { }) it('renders the localization provider and not the loading screen when app-shell is ready', () => { render('/') - expect(vi.mocked(OnDeviceLocalizationProvider)).toHaveBeenCalled() + expect(vi.mocked(LocalizationProvider)).toHaveBeenCalled() expect(screen.queryByLabelText('loading indicator')).toBeNull() }) it('renders the loading screen when app-shell is not ready', () => { vi.mocked(getIsShellReady).mockReturnValue(false) render('/') screen.getByLabelText('loading indicator') - expect(vi.mocked(OnDeviceLocalizationProvider)).not.toHaveBeenCalled() + expect(vi.mocked(LocalizationProvider)).not.toHaveBeenCalled() }) it('renders EmergencyStop component from /emergency-stop', () => { render('/emergency-stop') @@ -187,4 +194,9 @@ describe('OnDeviceDisplayApp', () => { render('/') expect(vi.mocked(useProtocolReceiptToast)).toHaveBeenCalled() }) + it('renders TopLevelRedirects when it should conditionally render', () => { + vi.mocked(ODDTopLevelRedirects).mockReturnValue(
MOCK_REDIRECTS
) + render('/') + screen.getByText('MOCK_REDIRECTS') + }) }) diff --git a/app/src/App/__tests__/OnDeviceDisplayAppFallback.test.tsx b/app/src/App/__tests__/OnDeviceDisplayAppFallback.test.tsx index 03d58ddcc46..864e714694a 100644 --- a/app/src/App/__tests__/OnDeviceDisplayAppFallback.test.tsx +++ b/app/src/App/__tests__/OnDeviceDisplayAppFallback.test.tsx @@ -1,21 +1,20 @@ -import * as React from 'react' import { vi, describe, beforeEach, it, expect } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { getLocalRobot } from '../../redux/discovery' -import { mockConnectableRobot } from '../../redux/discovery/__fixtures__' -import { i18n } from '../../i18n' -import { appRestart } from '../../redux/shell' -import { useTrackEvent, ANALYTICS_ODD_APP_ERROR } from '../../redux/analytics' +import { getLocalRobot } from '/app/redux/discovery' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' +import { i18n } from '/app/i18n' +import { appRestart } from '/app/redux/shell' +import { useTrackEvent, ANALYTICS_ODD_APP_ERROR } from '/app/redux/analytics' import { OnDeviceDisplayAppFallback } from '../OnDeviceDisplayAppFallback' import type { FallbackProps } from 'react-error-boundary' import type { Mock } from 'vitest' -vi.mock('../../redux/shell') -vi.mock('../../redux/analytics') -vi.mock('../../redux/discovery', async importOriginal => { +vi.mock('/app/redux/shell') +vi.mock('/app/redux/analytics') +vi.mock('/app/redux/discovery', async importOriginal => { const actual = await importOriginal() return { ...actual, diff --git a/app/src/App/__tests__/hooks.test.tsx b/app/src/App/__tests__/hooks.test.tsx index 1311d8bc039..5b3f315049b 100644 --- a/app/src/App/__tests__/hooks.test.tsx +++ b/app/src/App/__tests__/hooks.test.tsx @@ -1,16 +1,16 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, describe, beforeEach, afterEach, expect, it } from 'vitest' import { renderHook } from '@testing-library/react' import { createStore } from 'redux' import { I18nextProvider } from 'react-i18next' import { Provider } from 'react-redux' -import { i18n } from '../../i18n' -import { checkShellUpdate } from '../../redux/shell' +import { i18n } from '/app/i18n' +import { checkShellUpdate } from '/app/redux/shell' import { useSoftwareUpdatePoll } from '../hooks' import type { Store } from 'redux' -import type { State } from '../../redux/types' +import type { State } from '/app/redux/types' describe('useSoftwareUpdatePoll', () => { let wrapper: React.FunctionComponent<{ children: React.ReactNode }> diff --git a/app/src/App/constants.ts b/app/src/App/constants.ts index 73380a6da99..86dc3aa3128 100644 --- a/app/src/App/constants.ts +++ b/app/src/App/constants.ts @@ -1,6 +1,5 @@ // defines a constant for the nav bar width - used in run log component to calculate centering export const NAV_BAR_WIDTH = '5.625rem' -export const SLEEP_NEVER_MS = 604800000 // What is the maximum number of protocols one can pin? This many. export const MAXIMUM_PINNED_PROTOCOLS = 8 diff --git a/app/src/App/hooks.ts b/app/src/App/hooks.ts index 181f91a9a09..d01082d8dc1 100644 --- a/app/src/App/hooks.ts +++ b/app/src/App/hooks.ts @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useCallback, useRef, useEffect } from 'react' import difference from 'lodash/difference' import { useTranslation } from 'react-i18next' import { useQueryClient } from 'react-query' @@ -10,30 +10,20 @@ import { useHost, useCreateLiveCommandMutation, } from '@opentrons/react-api-client' -import { - getProtocol, - RUN_ACTION_TYPE_PLAY, - RUN_STATUS_BLOCKED_BY_OPEN_DOOR, - RUN_STATUS_IDLE, - RUN_STATUS_STOPPED, - RUN_STATUS_FAILED, - RUN_STATUS_SUCCEEDED, -} from '@opentrons/api-client' +import { getProtocol } from '@opentrons/api-client' -import { checkShellUpdate } from '../redux/shell' -import { useToaster } from '../organisms/ToasterOven' -import { useCurrentRunId, useNotifyRunQuery } from '../resources/runs' +import { checkShellUpdate } from '/app/redux/shell' +import { useToaster } from '/app/organisms/ToasterOven' import type { SetStatusBarCreateCommand } from '@opentrons/shared-data' -import type { Dispatch } from '../redux/types' +import type { Dispatch } from '/app/redux/types' -const CURRENT_RUN_POLL = 5000 const UPDATE_RECHECK_INTERVAL_MS = 60000 const PROTOCOL_IDS_RECHECK_INTERVAL_MS = 3000 export function useSoftwareUpdatePoll(): void { const dispatch = useDispatch() - const checkAppUpdate = React.useCallback(() => dispatch(checkShellUpdate()), [ + const checkAppUpdate = useCallback(() => dispatch(checkShellUpdate()), [ dispatch, ]) useInterval(checkAppUpdate, UPDATE_RECHECK_INTERVAL_MS) @@ -51,8 +41,8 @@ export function useProtocolReceiptToast(): void { true ) const protocolIds = protocolIdsQuery.data?.data ?? [] - const protocolIdsRef = React.useRef(protocolIds) - const hasRefetched = React.useRef(true) + const protocolIdsRef = useRef(protocolIds) + const hasRefetched = useRef(true) const { createLiveCommand } = useCreateLiveCommandMutation() const animationCommand: SetStatusBarCreateCommand = { commandType: 'setStatusBar', @@ -63,7 +53,7 @@ export function useProtocolReceiptToast(): void { hasRefetched.current = false } - React.useEffect(() => { + useEffect(() => { const newProtocolIds = difference(protocolIds, protocolIdsRef.current) if (!hasRefetched.current && newProtocolIds.length > 0) { Promise.all( @@ -123,43 +113,3 @@ export function useProtocolReceiptToast(): void { // eslint-disable-next-line react-hooks/exhaustive-deps }, [protocolIds]) } - -export function useCurrentRunRoute(): string | null { - const currentRunId = useCurrentRunId({ refetchInterval: CURRENT_RUN_POLL }) - const { data: runRecord, isFetching } = useNotifyRunQuery(currentRunId, { - refetchInterval: CURRENT_RUN_POLL, - }) - - const runStatus = runRecord?.data.status - const runActions = runRecord?.data.actions - if ( - runRecord == null || - runStatus == null || - runActions == null || - isFetching - ) { - return null - } - // grabbing run id off of the run query to have all routing info come from one source of truth - const runId = runRecord.data.id - const hasRunStarted = runActions?.some( - action => action.actionType === RUN_ACTION_TYPE_PLAY - ) - if ( - runStatus === RUN_STATUS_SUCCEEDED || - (runStatus === RUN_STATUS_STOPPED && hasRunStarted) || - runStatus === RUN_STATUS_FAILED - ) { - return `/runs/${runId}/summary` - } else if ( - runStatus === RUN_STATUS_IDLE || - (!hasRunStarted && runStatus === RUN_STATUS_BLOCKED_BY_OPEN_DOOR) - ) { - return `/runs/${runId}/setup` - } else if (hasRunStarted) { - return `/runs/${runId}/run` - } else { - // includes runs cancelled before starting and runs not yet started - return null - } -} diff --git a/app/src/App/index.tsx b/app/src/App/index.tsx index 42a32892df8..f0ba1de0304 100644 --- a/app/src/App/index.tsx +++ b/app/src/App/index.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { useSelector } from 'react-redux' import { Flex, POSITION_FIXED, DIRECTION_ROW } from '@opentrons/components' -import { GlobalStyle } from '../atoms/GlobalStyle' -import { getConfig, getIsOnDevice } from '../redux/config' +import { GlobalStyle } from '/app/atoms/GlobalStyle' +import { getConfig, getIsOnDevice } from '/app/redux/config' import { DesktopApp } from './DesktopApp' import { OnDeviceDisplayApp } from './OnDeviceDisplayApp' import { TopPortalRoot } from './portal' diff --git a/app/src/App/portal.tsx b/app/src/App/portal.tsx index 346d5842d81..d9e17199f56 100644 --- a/app/src/App/portal.tsx +++ b/app/src/App/portal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Box } from '@opentrons/components' export const TOP_PORTAL_ID = '__otAppTopPortalRoot' diff --git a/app/src/App/tools/ReactQueryDevtools.tsx b/app/src/App/tools/ReactQueryDevtools.tsx new file mode 100644 index 00000000000..a57f2df3d4d --- /dev/null +++ b/app/src/App/tools/ReactQueryDevtools.tsx @@ -0,0 +1,22 @@ +import { lazy, Suspense } from 'react' + +import { useFeatureFlag } from '/app/redux/config' + +// Lazily load to enable devtools when env.process.DEV is false (ex, when dev code is pushed to a physical ODD) +const ReactQueryTools = lazy(() => + import('react-query/devtools/development').then(d => ({ + default: d.ReactQueryDevtools, + })) +) + +export function ReactQueryDevtools(): JSX.Element { + const enableRQTools = useFeatureFlag('reactQueryDevtools') + + return ( + + {enableRQTools && ( + + )} + + ) +} diff --git a/app/src/App/tools/__tests__/ReactQueryDevtools.test.tsx b/app/src/App/tools/__tests__/ReactQueryDevtools.test.tsx new file mode 100644 index 00000000000..dcca081d19e --- /dev/null +++ b/app/src/App/tools/__tests__/ReactQueryDevtools.test.tsx @@ -0,0 +1,52 @@ +import { screen } from '@testing-library/react' +import { vi, describe, it, expect, beforeEach } from 'vitest' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { ReactQueryDevtools } from '/app/App/tools' +import { useFeatureFlag } from '/app/redux/config' + +vi.mock('react-query/devtools/development', () => ({ + ReactQueryDevtools: vi + .fn() + .mockReturnValue(
MOCK_REACT_QUERY_DEVTOOLS
), +})) +vi.mock('/app/redux/config') + +const render = () => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} + +describe('ReactQueryDevtools', () => { + const mockUseFF = vi.fn() + + beforeEach(() => { + vi.mocked(useFeatureFlag).mockReturnValue(true) + }) + + it('uses the correct feature flag', () => { + vi.mocked(useFeatureFlag).mockImplementation(mockUseFF) + + render() + + expect(mockUseFF).toHaveBeenCalledWith('reactQueryDevtools') + }) + + it('renders the devtools if the FF is enabled', async () => { + render() + + await screen.findByText('MOCK_REACT_QUERY_DEVTOOLS') + }) + + it('does not the devtools if the FF is disabled', async () => { + vi.mocked(useFeatureFlag).mockReturnValue(false) + + render() + + expect( + screen.queryByText('MOCK_REACT_QUERY_DEVTOOLS') + ).not.toBeInTheDocument() + }) +}) diff --git a/app/src/App/tools/index.ts b/app/src/App/tools/index.ts new file mode 100644 index 00000000000..99f2b6dc3fd --- /dev/null +++ b/app/src/App/tools/index.ts @@ -0,0 +1 @@ +export * from './ReactQueryDevtools' diff --git a/app/src/DesignTokens/BorderRadius/BorderRadius.stories.tsx b/app/src/DesignTokens/BorderRadius/BorderRadius.stories.tsx index 677e26b0c77..29e655c3a11 100644 --- a/app/src/DesignTokens/BorderRadius/BorderRadius.stories.tsx +++ b/app/src/DesignTokens/BorderRadius/BorderRadius.stories.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { ALIGN_FLEX_START, BORDERS, diff --git a/app/src/DesignTokens/Colors/Colors.stories.tsx b/app/src/DesignTokens/Colors/Colors.stories.tsx index 3a28cda86c3..98601ccac19 100644 --- a/app/src/DesignTokens/Colors/Colors.stories.tsx +++ b/app/src/DesignTokens/Colors/Colors.stories.tsx @@ -1,14 +1,14 @@ -import * as React from 'react' import { ALIGN_FLEX_START, BORDERS, COLORS, + CURSOR_POINTER, DIRECTION_COLUMN, DIRECTION_ROW, Flex, JUSTIFY_SPACE_BETWEEN, - SPACING, LegacyStyledText, + SPACING, TYPOGRAPHY, } from '@opentrons/components' @@ -74,7 +74,7 @@ const Template: Story = args => { margin={SPACING.spacing2} // Add some margin between color rows borderRadius={BORDERS.borderRadius4} style={{ - cursor: 'pointer', + cursor: CURSOR_POINTER, border: `1px solid ${COLORS.grey20}`, }} > diff --git a/app/src/DesignTokens/Spacing/Spacing.stories.tsx b/app/src/DesignTokens/Spacing/Spacing.stories.tsx index 5a0d0d4da81..3b12ae833db 100644 --- a/app/src/DesignTokens/Spacing/Spacing.stories.tsx +++ b/app/src/DesignTokens/Spacing/Spacing.stories.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { ALIGN_FLEX_START, Box, diff --git a/app/src/DesignTokens/Typography/Typography.stories.tsx b/app/src/DesignTokens/Typography/Typography.stories.tsx index ac95fa3a369..787d558bf52 100644 --- a/app/src/DesignTokens/Typography/Typography.stories.tsx +++ b/app/src/DesignTokens/Typography/Typography.stories.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { css } from 'styled-components' import type { FlattenSimpleInterpolation } from 'styled-components' import { diff --git a/app/src/LocalizationProvider.tsx b/app/src/LocalizationProvider.tsx index e2a30c95cd7..df2bbc8bc40 100644 --- a/app/src/LocalizationProvider.tsx +++ b/app/src/LocalizationProvider.tsx @@ -1,24 +1,28 @@ -import * as React from 'react' import { I18nextProvider } from 'react-i18next' +import { useSelector } from 'react-redux' import reduce from 'lodash/reduce' -import { resources } from './assets/localization' -import { useIsOEMMode } from './resources/robot-settings/hooks' -import { i18n, i18nCb, i18nConfig } from './i18n' +import { resources } from '/app/assets/localization' +import { i18n, i18nCb, i18nConfig } from '/app/i18n' +import { getAppLanguage } from '/app/redux/config' +import { useIsOEMMode } from '/app/resources/robot-settings/hooks' -export interface OnDeviceLocalizationProviderProps { +import type * as React from 'react' + +export interface LocalizationProviderProps { children?: React.ReactNode } -const BRANDED_RESOURCE = 'branded' -const ANONYMOUS_RESOURCE = 'anonymous' +export const BRANDED_RESOURCE = 'branded' +export const ANONYMOUS_RESOURCE = 'anonymous' -// TODO(bh, 2024-03-26): anonymization limited to ODD for now, may change in future OEM phases -export function OnDeviceLocalizationProvider( - props: OnDeviceLocalizationProviderProps +export function LocalizationProvider( + props: LocalizationProviderProps ): JSX.Element | null { const isOEMMode = useIsOEMMode() + const language = useSelector(getAppLanguage) + // iterate through language resources, nested files, substitute anonymous file for branded file for OEM mode const anonResources = reduce( resources, @@ -44,6 +48,7 @@ export function OnDeviceLocalizationProvider( const anonI18n = i18n.createInstance( { ...i18nConfig, + lng: language ?? 'en', resources: anonResources, }, i18nCb diff --git a/app/src/__testing-utils__/renderWithProviders.tsx b/app/src/__testing-utils__/renderWithProviders.tsx index 65a2e01855e..11e3ba16d9b 100644 --- a/app/src/__testing-utils__/renderWithProviders.tsx +++ b/app/src/__testing-utils__/renderWithProviders.tsx @@ -1,6 +1,6 @@ // render using targetted component using @testing-library/react // with wrapping providers for i18next and redux -import * as React from 'react' +import type * as React from 'react' import { QueryClient, QueryClientProvider } from 'react-query' import { I18nextProvider } from 'react-i18next' import { Provider } from 'react-redux' diff --git a/app/src/assets/images/absorbance_reader_instruction_manual_code.png b/app/src/assets/images/absorbance_reader_instruction_manual_code.png new file mode 100644 index 00000000000..70c45ab3b56 Binary files /dev/null and b/app/src/assets/images/absorbance_reader_instruction_manual_code.png differ diff --git a/app/src/organisms/LabwareDetails/images/agilent_1_reservoir_290ml_side_view.jpg b/app/src/assets/images/labware/agilent_1_reservoir_290ml_side_view.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/agilent_1_reservoir_290ml_side_view.jpg rename to app/src/assets/images/labware/agilent_1_reservoir_290ml_side_view.jpg diff --git a/app/src/organisms/LabwareDetails/images/appliedbiosystemsmicroamp_384_wellplate_40ul.jpg b/app/src/assets/images/labware/appliedbiosystemsmicroamp_384_wellplate_40ul.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/appliedbiosystemsmicroamp_384_wellplate_40ul.jpg rename to app/src/assets/images/labware/appliedbiosystemsmicroamp_384_wellplate_40ul.jpg diff --git a/app/src/organisms/LabwareDetails/images/axygen_1_reservoir_90ml_side_view.jpg b/app/src/assets/images/labware/axygen_1_reservoir_90ml_side_view.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/axygen_1_reservoir_90ml_side_view.jpg rename to app/src/assets/images/labware/axygen_1_reservoir_90ml_side_view.jpg diff --git a/app/src/organisms/LabwareDetails/images/biorad_384_wellplate_50ul.jpg b/app/src/assets/images/labware/biorad_384_wellplate_50ul.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/biorad_384_wellplate_50ul.jpg rename to app/src/assets/images/labware/biorad_384_wellplate_50ul.jpg diff --git a/app/src/organisms/LabwareDetails/images/biorad_96_wellplate_200ul_pcr_photo_three_quarters.jpg b/app/src/assets/images/labware/biorad_96_wellplate_200ul_pcr_photo_three_quarters.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/biorad_96_wellplate_200ul_pcr_photo_three_quarters.jpg rename to app/src/assets/images/labware/biorad_96_wellplate_200ul_pcr_photo_three_quarters.jpg diff --git a/app/src/organisms/LabwareDetails/images/circularWell.svg b/app/src/assets/images/labware/circularWell.svg similarity index 100% rename from app/src/organisms/LabwareDetails/images/circularWell.svg rename to app/src/assets/images/labware/circularWell.svg diff --git a/app/src/organisms/LabwareDetails/images/corning_12_wellplate_6.9ml_flat_photo_three_quarters.jpg b/app/src/assets/images/labware/corning_12_wellplate_6.9ml_flat_photo_three_quarters.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/corning_12_wellplate_6.9ml_flat_photo_three_quarters.jpg rename to app/src/assets/images/labware/corning_12_wellplate_6.9ml_flat_photo_three_quarters.jpg diff --git a/app/src/organisms/LabwareDetails/images/corning_24_wellplate_3.4ml_flat_photo_three_quarters.jpg b/app/src/assets/images/labware/corning_24_wellplate_3.4ml_flat_photo_three_quarters.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/corning_24_wellplate_3.4ml_flat_photo_three_quarters.jpg rename to app/src/assets/images/labware/corning_24_wellplate_3.4ml_flat_photo_three_quarters.jpg diff --git a/app/src/organisms/LabwareDetails/images/corning_384_wellplate_112ul_flat_photo_three_quarters.jpg b/app/src/assets/images/labware/corning_384_wellplate_112ul_flat_photo_three_quarters.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/corning_384_wellplate_112ul_flat_photo_three_quarters.jpg rename to app/src/assets/images/labware/corning_384_wellplate_112ul_flat_photo_three_quarters.jpg diff --git a/app/src/organisms/LabwareDetails/images/corning_48_wellplate_1.6ml_flat_photo_three_quarters.jpg b/app/src/assets/images/labware/corning_48_wellplate_1.6ml_flat_photo_three_quarters.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/corning_48_wellplate_1.6ml_flat_photo_three_quarters.jpg rename to app/src/assets/images/labware/corning_48_wellplate_1.6ml_flat_photo_three_quarters.jpg diff --git a/app/src/organisms/LabwareDetails/images/corning_6_wellplate_16.8ml_flat_photo_three_quarters.jpg b/app/src/assets/images/labware/corning_6_wellplate_16.8ml_flat_photo_three_quarters.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/corning_6_wellplate_16.8ml_flat_photo_three_quarters.jpg rename to app/src/assets/images/labware/corning_6_wellplate_16.8ml_flat_photo_three_quarters.jpg diff --git a/app/src/organisms/LabwareDetails/images/corning_96_wellplate_360ul_flat_three_quarters.jpg b/app/src/assets/images/labware/corning_96_wellplate_360ul_flat_three_quarters.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/corning_96_wellplate_360ul_flat_three_quarters.jpg rename to app/src/assets/images/labware/corning_96_wellplate_360ul_flat_three_quarters.jpg diff --git a/app/src/organisms/LabwareDetails/images/deep_well_plate_adapter.jpg b/app/src/assets/images/labware/deep_well_plate_adapter.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/deep_well_plate_adapter.jpg rename to app/src/assets/images/labware/deep_well_plate_adapter.jpg diff --git a/app/src/organisms/LabwareDetails/images/eppendorf_1.5ml_safelock_snapcap_tube.jpg b/app/src/assets/images/labware/eppendorf_1.5ml_safelock_snapcap_tube.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/eppendorf_1.5ml_safelock_snapcap_tube.jpg rename to app/src/assets/images/labware/eppendorf_1.5ml_safelock_snapcap_tube.jpg diff --git a/app/src/organisms/LabwareDetails/images/eppendorf_2ml_safelock_snapcap_tube.jpg b/app/src/assets/images/labware/eppendorf_2ml_safelock_snapcap_tube.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/eppendorf_2ml_safelock_snapcap_tube.jpg rename to app/src/assets/images/labware/eppendorf_2ml_safelock_snapcap_tube.jpg diff --git a/app/src/organisms/LabwareDetails/images/falcon_15ml_conical_tube.jpg b/app/src/assets/images/labware/falcon_15ml_conical_tube.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/falcon_15ml_conical_tube.jpg rename to app/src/assets/images/labware/falcon_15ml_conical_tube.jpg diff --git a/app/src/organisms/LabwareDetails/images/falcon_50ml_15ml_conical_tubes.jpg b/app/src/assets/images/labware/falcon_50ml_15ml_conical_tubes.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/falcon_50ml_15ml_conical_tubes.jpg rename to app/src/assets/images/labware/falcon_50ml_15ml_conical_tubes.jpg diff --git a/app/src/organisms/LabwareDetails/images/falcon_50ml_conical_tube.jpg b/app/src/assets/images/labware/falcon_50ml_conical_tube.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/falcon_50ml_conical_tube.jpg rename to app/src/assets/images/labware/falcon_50ml_conical_tube.jpg diff --git a/app/src/organisms/LabwareDetails/images/favicon.ico b/app/src/assets/images/labware/favicon.ico similarity index 100% rename from app/src/organisms/LabwareDetails/images/favicon.ico rename to app/src/assets/images/labware/favicon.ico diff --git a/app/src/organisms/LabwareDetails/images/flat_bottom_aluminum.png b/app/src/assets/images/labware/flat_bottom_aluminum.png similarity index 100% rename from app/src/organisms/LabwareDetails/images/flat_bottom_aluminum.png rename to app/src/assets/images/labware/flat_bottom_aluminum.png diff --git a/app/src/organisms/LabwareDetails/images/flat_bottom_plate_adapter.jpg b/app/src/assets/images/labware/flat_bottom_plate_adapter.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/flat_bottom_plate_adapter.jpg rename to app/src/assets/images/labware/flat_bottom_plate_adapter.jpg diff --git a/app/src/organisms/LabwareDetails/images/geb_1000ul_tip_side_view.jpg b/app/src/assets/images/labware/geb_1000ul_tip_side_view.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/geb_1000ul_tip_side_view.jpg rename to app/src/assets/images/labware/geb_1000ul_tip_side_view.jpg diff --git a/app/src/organisms/LabwareDetails/images/geb_10ul_tip_side_view.jpg b/app/src/assets/images/labware/geb_10ul_tip_side_view.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/geb_10ul_tip_side_view.jpg rename to app/src/assets/images/labware/geb_10ul_tip_side_view.jpg diff --git a/app/src/organisms/LabwareDetails/images/generic_2ml_screwcap_tube.jpg b/app/src/assets/images/labware/generic_2ml_screwcap_tube.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/generic_2ml_screwcap_tube.jpg rename to app/src/assets/images/labware/generic_2ml_screwcap_tube.jpg diff --git a/app/src/organisms/LabwareDetails/images/generic_pcr_strip_200ul_tubes.jpg b/app/src/assets/images/labware/generic_pcr_strip_200ul_tubes.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/generic_pcr_strip_200ul_tubes.jpg rename to app/src/assets/images/labware/generic_pcr_strip_200ul_tubes.jpg diff --git a/app/src/organisms/LabwareDetails/images/nest_0.5ml_screwcap_tube.jpg b/app/src/assets/images/labware/nest_0.5ml_screwcap_tube.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/nest_0.5ml_screwcap_tube.jpg rename to app/src/assets/images/labware/nest_0.5ml_screwcap_tube.jpg diff --git a/app/src/organisms/LabwareDetails/images/nest_1.5ml_screwcap_tube.jpg b/app/src/assets/images/labware/nest_1.5ml_screwcap_tube.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/nest_1.5ml_screwcap_tube.jpg rename to app/src/assets/images/labware/nest_1.5ml_screwcap_tube.jpg diff --git a/app/src/organisms/LabwareDetails/images/nest_1.5ml_snapcap_tube.jpg b/app/src/assets/images/labware/nest_1.5ml_snapcap_tube.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/nest_1.5ml_snapcap_tube.jpg rename to app/src/assets/images/labware/nest_1.5ml_snapcap_tube.jpg diff --git a/app/src/organisms/LabwareDetails/images/nest_12_reservoir_15ml_three_quarters.jpg b/app/src/assets/images/labware/nest_12_reservoir_15ml_three_quarters.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/nest_12_reservoir_15ml_three_quarters.jpg rename to app/src/assets/images/labware/nest_12_reservoir_15ml_three_quarters.jpg diff --git a/app/src/organisms/LabwareDetails/images/nest_15ml_conical_tube.jpg b/app/src/assets/images/labware/nest_15ml_conical_tube.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/nest_15ml_conical_tube.jpg rename to app/src/assets/images/labware/nest_15ml_conical_tube.jpg diff --git a/app/src/organisms/LabwareDetails/images/nest_1_reservoir_195ml_three_quarters.jpg b/app/src/assets/images/labware/nest_1_reservoir_195ml_three_quarters.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/nest_1_reservoir_195ml_three_quarters.jpg rename to app/src/assets/images/labware/nest_1_reservoir_195ml_three_quarters.jpg diff --git a/app/src/organisms/LabwareDetails/images/nest_1_reservoir_290ml.jpg b/app/src/assets/images/labware/nest_1_reservoir_290ml.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/nest_1_reservoir_290ml.jpg rename to app/src/assets/images/labware/nest_1_reservoir_290ml.jpg diff --git a/app/src/organisms/LabwareDetails/images/nest_2ml_screwcap_tube.jpg b/app/src/assets/images/labware/nest_2ml_screwcap_tube.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/nest_2ml_screwcap_tube.jpg rename to app/src/assets/images/labware/nest_2ml_screwcap_tube.jpg diff --git a/app/src/organisms/LabwareDetails/images/nest_2ml_snapcap_tube.jpg b/app/src/assets/images/labware/nest_2ml_snapcap_tube.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/nest_2ml_snapcap_tube.jpg rename to app/src/assets/images/labware/nest_2ml_snapcap_tube.jpg diff --git a/app/src/organisms/LabwareDetails/images/nest_50ml_15ml_conical_tubes.jpg b/app/src/assets/images/labware/nest_50ml_15ml_conical_tubes.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/nest_50ml_15ml_conical_tubes.jpg rename to app/src/assets/images/labware/nest_50ml_15ml_conical_tubes.jpg diff --git a/app/src/organisms/LabwareDetails/images/nest_50ml_conical_tube.jpg b/app/src/assets/images/labware/nest_50ml_conical_tube.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/nest_50ml_conical_tube.jpg rename to app/src/assets/images/labware/nest_50ml_conical_tube.jpg diff --git a/app/src/organisms/LabwareDetails/images/nest_96_wellplate_100ul_pcr_full_skirt_three_quarters.jpg b/app/src/assets/images/labware/nest_96_wellplate_100ul_pcr_full_skirt_three_quarters.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/nest_96_wellplate_100ul_pcr_full_skirt_three_quarters.jpg rename to app/src/assets/images/labware/nest_96_wellplate_100ul_pcr_full_skirt_three_quarters.jpg diff --git a/app/src/organisms/LabwareDetails/images/nest_96_wellplate_200ul_flat_three_quarters.jpg b/app/src/assets/images/labware/nest_96_wellplate_200ul_flat_three_quarters.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/nest_96_wellplate_200ul_flat_three_quarters.jpg rename to app/src/assets/images/labware/nest_96_wellplate_200ul_flat_three_quarters.jpg diff --git a/app/src/organisms/LabwareDetails/images/nest_96_wellplate_2ml_deep.jpg b/app/src/assets/images/labware/nest_96_wellplate_2ml_deep.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/nest_96_wellplate_2ml_deep.jpg rename to app/src/assets/images/labware/nest_96_wellplate_2ml_deep.jpg diff --git a/app/src/organisms/LabwareDetails/images/opentrons_10_tuberack_4_6_side_view.jpg b/app/src/assets/images/labware/opentrons_10_tuberack_4_6_side_view.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/opentrons_10_tuberack_4_6_side_view.jpg rename to app/src/assets/images/labware/opentrons_10_tuberack_4_6_side_view.jpg diff --git a/app/src/organisms/LabwareDetails/images/opentrons_15_tuberack_side_view.jpg b/app/src/assets/images/labware/opentrons_15_tuberack_side_view.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/opentrons_15_tuberack_side_view.jpg rename to app/src/assets/images/labware/opentrons_15_tuberack_side_view.jpg diff --git a/app/src/organisms/LabwareDetails/images/opentrons_24_aluminumblock_side_view.jpg b/app/src/assets/images/labware/opentrons_24_aluminumblock_side_view.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/opentrons_24_aluminumblock_side_view.jpg rename to app/src/assets/images/labware/opentrons_24_aluminumblock_side_view.jpg diff --git a/app/src/organisms/LabwareDetails/images/opentrons_24_tuberack_side_view.jpg b/app/src/assets/images/labware/opentrons_24_tuberack_side_view.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/opentrons_24_tuberack_side_view.jpg rename to app/src/assets/images/labware/opentrons_24_tuberack_side_view.jpg diff --git a/app/src/organisms/LabwareDetails/images/opentrons_6_tuberack_side_view.jpg b/app/src/assets/images/labware/opentrons_6_tuberack_side_view.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/opentrons_6_tuberack_side_view.jpg rename to app/src/assets/images/labware/opentrons_6_tuberack_side_view.jpg diff --git a/app/src/organisms/LabwareDetails/images/opentrons_96_aluminumblock_side_view.jpg b/app/src/assets/images/labware/opentrons_96_aluminumblock_side_view.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/opentrons_96_aluminumblock_side_view.jpg rename to app/src/assets/images/labware/opentrons_96_aluminumblock_side_view.jpg diff --git a/app/src/organisms/LabwareDetails/images/opentrons_96_deep_well_temp_mod_adapter.png b/app/src/assets/images/labware/opentrons_96_deep_well_temp_mod_adapter.png similarity index 100% rename from app/src/organisms/LabwareDetails/images/opentrons_96_deep_well_temp_mod_adapter.png rename to app/src/assets/images/labware/opentrons_96_deep_well_temp_mod_adapter.png diff --git a/app/src/organisms/LabwareDetails/images/opentrons_flat_aluminumblock_side_view.jpg b/app/src/assets/images/labware/opentrons_flat_aluminumblock_side_view.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/opentrons_flat_aluminumblock_side_view.jpg rename to app/src/assets/images/labware/opentrons_flat_aluminumblock_side_view.jpg diff --git a/app/src/assets/images/labware/opentrons_flex_deck_riser.png b/app/src/assets/images/labware/opentrons_flex_deck_riser.png new file mode 100644 index 00000000000..1fecdf8ca4b Binary files /dev/null and b/app/src/assets/images/labware/opentrons_flex_deck_riser.png differ diff --git a/app/src/assets/images/labware/opentrons_tough_pcr_auto_sealing_lid.png b/app/src/assets/images/labware/opentrons_tough_pcr_auto_sealing_lid.png new file mode 100644 index 00000000000..bc0cffa3df6 Binary files /dev/null and b/app/src/assets/images/labware/opentrons_tough_pcr_auto_sealing_lid.png differ diff --git a/app/src/organisms/LabwareDetails/images/pcr_plate_adapter.jpg b/app/src/assets/images/labware/pcr_plate_adapter.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/pcr_plate_adapter.jpg rename to app/src/assets/images/labware/pcr_plate_adapter.jpg diff --git a/app/src/organisms/LabwareDetails/images/rectangularWell.svg b/app/src/assets/images/labware/rectangularWell.svg similarity index 100% rename from app/src/organisms/LabwareDetails/images/rectangularWell.svg rename to app/src/assets/images/labware/rectangularWell.svg diff --git a/app/src/organisms/LabwareDetails/images/thermoscientificnunc_96_wellplate_1300ul.jpg b/app/src/assets/images/labware/thermoscientificnunc_96_wellplate_1300ul.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/thermoscientificnunc_96_wellplate_1300ul.jpg rename to app/src/assets/images/labware/thermoscientificnunc_96_wellplate_1300ul.jpg diff --git a/app/src/organisms/LabwareDetails/images/thermoscientificnunc_96_wellplate_2000ul.jpg b/app/src/assets/images/labware/thermoscientificnunc_96_wellplate_2000ul.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/thermoscientificnunc_96_wellplate_2000ul.jpg rename to app/src/assets/images/labware/thermoscientificnunc_96_wellplate_2000ul.jpg diff --git a/app/src/organisms/LabwareDetails/images/tipone_200ul_tip_side_view.jpg b/app/src/assets/images/labware/tipone_200ul_tip_side_view.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/tipone_200ul_tip_side_view.jpg rename to app/src/assets/images/labware/tipone_200ul_tip_side_view.jpg diff --git a/app/src/assets/images/labware/tipone_96_tiprack_200ul_side_view.jpg b/app/src/assets/images/labware/tipone_96_tiprack_200ul_side_view.jpg old mode 100755 new mode 100644 diff --git a/app/src/organisms/LabwareDetails/images/universal_flat_adapter.jpg b/app/src/assets/images/labware/universal_flat_adapter.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/universal_flat_adapter.jpg rename to app/src/assets/images/labware/universal_flat_adapter.jpg diff --git a/app/src/organisms/LabwareDetails/images/usascientific_12_reservoir_22ml_side_view.jpg b/app/src/assets/images/labware/usascientific_12_reservoir_22ml_side_view.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/usascientific_12_reservoir_22ml_side_view.jpg rename to app/src/assets/images/labware/usascientific_12_reservoir_22ml_side_view.jpg diff --git a/app/src/organisms/LabwareDetails/images/usascientific_96_wellplate_2.4ml_deep_side_view.jpg b/app/src/assets/images/labware/usascientific_96_wellplate_2.4ml_deep_side_view.jpg similarity index 100% rename from app/src/organisms/LabwareDetails/images/usascientific_96_wellplate_2.4ml_deep_side_view.jpg rename to app/src/assets/images/labware/usascientific_96_wellplate_2.4ml_deep_side_view.jpg diff --git a/app/src/organisms/LabwareDetails/images/wellShapeFlat.svg b/app/src/assets/images/labware/wellShapeFlat.svg similarity index 100% rename from app/src/organisms/LabwareDetails/images/wellShapeFlat.svg rename to app/src/assets/images/labware/wellShapeFlat.svg diff --git a/app/src/organisms/LabwareDetails/images/wellShapeU.svg b/app/src/assets/images/labware/wellShapeU.svg similarity index 100% rename from app/src/organisms/LabwareDetails/images/wellShapeU.svg rename to app/src/assets/images/labware/wellShapeU.svg diff --git a/app/src/organisms/LabwareDetails/images/wellShapeV.svg b/app/src/assets/images/labware/wellShapeV.svg similarity index 100% rename from app/src/organisms/LabwareDetails/images/wellShapeV.svg rename to app/src/assets/images/labware/wellShapeV.svg diff --git a/app/src/assets/images/magnetic_block_gen_1@3x.png b/app/src/assets/images/magnetic_block_gen_1@3x.png new file mode 100644 index 00000000000..860966e07a3 Binary files /dev/null and b/app/src/assets/images/magnetic_block_gen_1@3x.png differ diff --git a/app/src/assets/images/opentrons_plate_reader.png b/app/src/assets/images/opentrons_plate_reader.png new file mode 100644 index 00000000000..68a3b406984 Binary files /dev/null and b/app/src/assets/images/opentrons_plate_reader.png differ diff --git a/app/src/assets/localization/__tests__/branded.test.ts b/app/src/assets/localization/__tests__/branded.test.ts new file mode 100644 index 00000000000..ed53aabbaec --- /dev/null +++ b/app/src/assets/localization/__tests__/branded.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect } from 'vitest' +import { + ANONYMOUS_RESOURCE, + BRANDED_RESOURCE, +} from '../../../LocalizationProvider' +import { resources } from '..' + +describe('branded copy', () => { + it('branded and anonymous resources contain the same translation keys', () => { + const brandedKeys = Object.keys(resources.en[BRANDED_RESOURCE]) + const anonymousKeys = Object.keys(resources.en[ANONYMOUS_RESOURCE]) + + brandedKeys.forEach((brandedKey, i) => { + const anonymousKey = anonymousKeys[i] + expect(brandedKey).toEqual(anonymousKey) + }) + }) + + it('non-branded copy does not contain "Opentrons" or "Flex"', () => { + const nonBrandedResources = Object.entries(resources.en).filter( + resource => + resource[0] !== BRANDED_RESOURCE && resource[0] !== ANONYMOUS_RESOURCE + ) + + const nonBrandedCopy = nonBrandedResources + .map(resource => Object.values(resource[1])) + .flat() + + nonBrandedCopy.forEach(phrase => { + expect(phrase.match(/opentrons/i)).toBeNull() + expect(phrase.match(/flex/i)).toBeNull() + }) + }) +}) diff --git a/app/src/assets/localization/en/anonymous.json b/app/src/assets/localization/en/anonymous.json index 12e57d595fa..280e602088d 100644 --- a/app/src/assets/localization/en/anonymous.json +++ b/app/src/assets/localization/en/anonymous.json @@ -23,13 +23,14 @@ "find_your_robot": "Find your robot in the Devices section of the app to install software updates.", "firmware_update_download_logs": "Contact support for assistance.", "general_error_message": "If you keep getting this message, try restarting your app and robot. If this does not resolve the issue, contact support.", + "gripper": "Gripper", "gripper_still_attached": "Gripper still attached", "gripper_successfully_attached_and_calibrated": "Gripper successfully attached and calibrated", "gripper_successfully_calibrated": "Gripper successfully calibrated", "gripper_successfully_detached": "Gripper successfully detached", - "gripper": "Gripper", "help_us_improve_send_error_report": "Help us improve your experience by sending an error report to support", "ip_description_second": "Work with your network administrator to assign a static IP address to the robot.", + "language_preference_description": "The app matches your system language unless you select another language below. You can change the language later in the app settings.", "learn_uninstalling": "Learn more about uninstalling the app", "loosen_screws_and_detach": "Loosen screws and detach gripper", "modal_instructions": "For step-by-step instructions on setting up your module, consult the Quickstart Guide that came in its box.", @@ -37,6 +38,7 @@ "module_calibration_get_started": "To get started, remove labware from the deck and clean up the working area to make the calibration easier. Also gather the needed equipment shown to the right.The calibration adapter came with your module. The pipette probe came with your pipette.", "module_error_contact_support": "Try powering the module off and on again. If the error persists, contact support.", "network_setup_menu_description": "You’ll use this connection to run software updates and load protocols onto your robot.", + "new_robot_instructions": "When setting up a new robot, follow the instructions on the touchscreen. For more information, consult the Quickstart Guide for your robot.", "oem_mode_description": "Enable OEM Mode to remove all instances of Opentrons from the Flex touchscreen.", "opentrons_app_successfully_updated": "The app was successfully updated.", "opentrons_app_update": "app update", @@ -45,9 +47,10 @@ "opentrons_app_will_use_interpreter": "If specified, the app will use the Python interpreter at this path instead of the default bundled Python interpreter.", "opentrons_cares_about_privacy": "We care about your privacy. We anonymize all data and only use it to improve our products.", "opentrons_def": "Verified Definition", + "opentrons_flex_quickstart_guide": "Quickstart Guide", "opentrons_labware_def": "Verified labware definition", - "opentrons_tip_racks_recommended": "Opentrons tip racks are highly recommended. Accuracy cannot be guaranteed with other tip racks.", "opentrons_tip_rack_name": "opentrons", + "opentrons_tip_racks_recommended": "Opentrons tip racks are highly recommended. Accuracy cannot be guaranteed with other tip racks.", "previous_releases": "View previous releases", "receive_alert": "Receive an alert when a software update is available.", "restore_description": "Reverting to previous software versions is not recommended, but you can access previous releases below. For best results, uninstall the existing app and remove its configuration files before installing the previous version.", @@ -66,7 +69,9 @@ "show_labware_offset_snippets_description": "Only for users who need to apply labware offset data outside of the app. When enabled, code snippets for Jupyter Notebook and SSH are available during protocol setup.", "something_seems_wrong": "There may be a problem with your pipette. Exit setup and contact support for assistance.", "storage_limit_reached_description": "Your robot has reached the limit of quick transfers that it can store. You must delete an existing quick transfer before creating a new one.", + "system_language_preferences_update_description": "Your system’s language was recently updated. Would you like to use the updated language as the default for the app?", "these_are_advanced_settings": "These are advanced settings. Please do not attempt to adjust without assistance from support. Changing these settings may affect the lifespan of your pipette.These settings do not override any pipette settings defined in protocols.", + "unexpected_error": "An unexpected error has occurred. If the issue persists, contact customer support for assistance.", "update_requires_restarting_app": "Updating requires restarting the app.", "update_robot_software_description": "Bypass the auto-update process and update the robot software manually.", "update_robot_software_link": "Launch software update page", diff --git a/app/src/assets/localization/en/app_settings.json b/app/src/assets/localization/en/app_settings.json index adbc00d3181..f73ac990fc6 100644 --- a/app/src/assets/localization/en/app_settings.json +++ b/app/src/assets/localization/en/app_settings.json @@ -1,9 +1,11 @@ { + "__dev_internal__enableLabwareCreator": "Enable App Labware Creator", + "__dev_internal__enableLocalization": "Enable App Localization", + "__dev_internal__enableRunNotes": "Display Notes During a Protocol Run", "__dev_internal__forceHttpPolling": "Poll all network requests instead of using MQTT", "__dev_internal__protocolStats": "Protocol Stats", "__dev_internal__protocolTimeline": "Protocol Timeline", - "__dev_internal__enableRunNotes": "Display Notes During a Protocol Run", - "__dev_internal__enableLabwareCreator": "Enable App Labware Creator", + "__dev_internal__reactQueryDevtools": "Enable React Query Devtools", "add_folder_button": "Add labware source folder", "add_ip_button": "Add", "add_ip_error": "Enter an IP Address or Hostname", @@ -14,11 +16,14 @@ "additional_labware_folder_title": "Additional Custom Labware Source Folder", "advanced": "Advanced", "app_changes": "App Changes in ", + "app_language_description": "All app features use this language. Protocols and other user content will not change language.", + "app_language_preferences": "App Language Preferences", "app_settings": "App Settings", "bug_fixes": "Bug Fixes", "cal_block": "Always use calibration block to calibrate", "change_folder_button": "Change labware source folder", "channel": "Channel", + "choose_your_language": "Choose your language", "clear_confirm": "Clear unavailable robots", "clear_robots_button": "Clear unavailable robots list", "clear_robots_description": "Clear the list of unavailable robots on the Devices page. This action cannot be undone.", @@ -30,11 +35,14 @@ "connect_ip_button": "Done", "connect_ip_link": "Learn more about connecting a robot manually", "discovery_timeout": "Discovery timed out.", + "dont_change": "Don’t change", "download_update": "Downloading update...", "enable_dev_tools": "Developer Tools", "enable_dev_tools_description": "Enabling this setting opens Developer Tools on app launch, enables additional logging and gives access to feature flags.", "error_boundary_desktop_app_description": "You need to reload the app. Contact support with the following error message:", "error_boundary_title": "An unknown error has occurred", + "error_recovery_mode": "Recovery mode", + "error_recovery_mode_description": "Pause on protocol errors instead of canceling the run.", "feature_flags": "Feature Flags", "general": "General", "heater_shaker_attach_description": "Display a reminder to attach the Heater-Shaker properly before running a test shake or using it in a protocol.", @@ -43,6 +51,8 @@ "installing_update": "Installing update...", "ip_available": "Available", "ip_description_first": "Enter an IP address or hostname to connect to a robot.", + "language": "Language", + "language_preference": "Language preference", "manage_versions": "It is very important for the robot and app software to be on the same version. Manage the robot software versions via Robot Settings > Advanced.", "new_features": "New Features", "no_folder": "No additional source folder specified", @@ -68,6 +78,8 @@ "restarting_app": "Download complete, restarting the app...", "restore_previous": "See how to restore a previous software version", "searching": "Searching for 30s", + "select_a_language": "Select a language to personalize your experience.", + "select_language": "Select language", "setup_connection": "Set up connection", "share_display_usage": "Share display usage", "share_robot_logs": "Share robot logs", @@ -76,6 +88,7 @@ "software_update_available": "Software Update Available", "software_version": "App Software Version", "successfully_deleted_unavail_robots": "Successfully deleted unavailable robots", + "system_language_preferences_update": "Update to your system language preferences", "tip_length_cal_method": "Tip Length Calibration Method", "trash_bin": "Always use trash bin to calibrate", "try_restarting_the_update": "Try restarting the update.", @@ -97,6 +110,7 @@ "usb_to_ethernet_not_connected": "No USB-to-Ethernet adapter connected", "usb_to_ethernet_unknown_manufacturer": "Unknown Manufacturer", "usb_to_ethernet_unknown_product": "Unknown Adapter", + "use_system_language": "Use system language", "view_software_update": "View software update", "view_update": "View Update" } diff --git a/app/src/assets/localization/en/branded.json b/app/src/assets/localization/en/branded.json index 6a65184183f..0760c3061b4 100644 --- a/app/src/assets/localization/en/branded.json +++ b/app/src/assets/localization/en/branded.json @@ -1,9 +1,9 @@ { "a_robot_software_update_is_available": "A robot software update is required to run protocols with this version of the Opentrons App. Go to Robot", - "attach_a_pipette": "Attach a pipette to your Flex", - "attach_a_pipette_for_quick_transfer": "To create a quick transfer, you need to attach a pipette to your Opentrons Flex.", "about_flex_gripper": "About Flex Gripper", "alternative_security_types_description": "The Opentrons App supports connecting Flex to various enterprise access points. Connect via USB and finish setup in the app.", + "attach_a_pipette": "Attach a pipette to your Flex", + "attach_a_pipette_for_quick_transfer": "To create a quick transfer, you need to attach a pipette to your Opentrons Flex.", "calibration_block_description": "This block is a specially made tool that fits perfectly on your deck and helps with calibration.If you do not have a Calibration Block, please email support@opentrons.com so we can send you one. In your message, be sure to include your name, company or institution name, and shipping address. While you wait for the block to arrive, you can use the flat surface on the trash bin of your robot instead.", "calibration_on_opentrons_tips_is_important": "It’s extremely important to perform this calibration using the Opentrons tips and tip racks specified above, as the robot determines accuracy based on the known measurements of these tips.", "choose_what_data_to_share": "Choose what data to share with Opentrons.", @@ -23,13 +23,14 @@ "find_your_robot": "Find your robot in the Opentrons App to install software updates.", "firmware_update_download_logs": "Download the robot logs from the Opentrons App and send them to support@opentrons.com for assistance.", "general_error_message": "If you keep getting this message, try restarting your app and robot. If this does not resolve the issue, contact Opentrons Support.", + "gripper": "Flex Gripper", "gripper_still_attached": "Flex Gripper still attached", "gripper_successfully_attached_and_calibrated": "Flex Gripper successfully attached and calibrated", "gripper_successfully_calibrated": "Flex Gripper successfully calibrated", "gripper_successfully_detached": "Flex Gripper successfully detached", - "gripper": "Flex Gripper", "help_us_improve_send_error_report": "Help us improve your experience by sending an error report to {{support_email}}", "ip_description_second": "Opentrons recommends working with your network administrator to assign a static IP address to the robot.", + "language_preference_description": "The Opentrons App matches your system language unless you select another language below. You can change the language later in the app settings.", "learn_uninstalling": "Learn more about uninstalling the Opentrons App", "loosen_screws_and_detach": "Loosen screws and detach Flex Gripper", "modal_instructions": "For step-by-step instructions on setting up your module, consult the Quickstart Guide that came in its box. You can also click the link below or scan the QR code to visit the modules section of the Opentrons Help Center.", @@ -37,6 +38,7 @@ "module_calibration_get_started": "To get started, remove labware from the deck and clean up the working area to make the calibration easier. Also gather the needed equipment shown to the right.The calibration adapter came with your module. The pipette probe came with your Flex pipette.", "module_error_contact_support": "Try powering the module off and on again. If the error persists, contact Opentrons Support.", "network_setup_menu_description": "You’ll use this connection to run software updates and load protocols onto your Opentrons Flex.", + "new_robot_instructions": "When setting up a new Flex, follow the instructions on the touchscreen. For more information, consult the Quickstart Guide for your robot.", "oem_mode_description": "Enable OEM Mode to remove all instances of Opentrons from the Flex touchscreen.", "opentrons_app_successfully_updated": "The Opentrons App was successfully updated.", "opentrons_app_update": "Opentrons App update", @@ -45,6 +47,7 @@ "opentrons_app_will_use_interpreter": "If specified, the Opentrons App will use the Python interpreter at this path instead of the default bundled Python interpreter.", "opentrons_cares_about_privacy": "Opentrons cares about your privacy. We anonymize all data and only use it to improve our products.", "opentrons_def": "Opentrons Definition", + "opentrons_flex_quickstart_guide": "Opentrons Flex Quickstart Guide", "opentrons_labware_def": "Opentrons labware definition", "opentrons_tip_rack_name": "opentrons", "opentrons_tip_racks_recommended": "Opentrons tip racks are highly recommended. Accuracy cannot be guaranteed with other tip racks.", @@ -66,7 +69,9 @@ "show_labware_offset_snippets_description": "Only for users who need to apply labware offset data outside of the Opentrons App. When enabled, code snippets for Jupyter Notebook and SSH are available during protocol setup.", "something_seems_wrong": "There may be a problem with your pipette. Exit setup and contact Opentrons Support for assistance.", "storage_limit_reached_description": "Your Opentrons Flex has reached the limit of quick transfers that it can store. You must delete an existing quick transfer before creating a new one.", + "system_language_preferences_update_description": "Your system’s language was recently updated. Would you like to use the updated language as the default for the Opentrons App?", "these_are_advanced_settings": "These are advanced settings. Please do not attempt to adjust without assistance from Opentrons Support. Changing these settings may affect the lifespan of your pipette.These settings do not override any pipette settings defined in protocols.", + "unexpected_error": "An unexpected error has occurred. If the issue persists, contact Opentrons Support for assistance.", "update_requires_restarting_app": "Updating requires restarting the Opentrons App.", "update_robot_software_description": "Bypass the Opentrons App auto-update process and update the robot software manually.", "update_robot_software_link": "Launch Opentrons software update page", diff --git a/app/src/assets/localization/en/device_details.json b/app/src/assets/localization/en/device_details.json index 70a31da2a0d..17daa2c5955 100644 --- a/app/src/assets/localization/en/device_details.json +++ b/app/src/assets/localization/en/device_details.json @@ -4,6 +4,7 @@ "about_pipette_name": "About {{name}} Pipette", "about_pipette": "About pipette", "abs_reader_status": "Absorbance Plate Reader Status", + "abs_reader_lid_status": "Lid status: {{status}}", "add_fixture_description": "Add this hardware to your deck configuration. It will be referenced during protocol analysis.", "add_to_slot": "Add to slot {{slotName}}", "add": "Add", @@ -73,7 +74,7 @@ "instruments_and_modules": "Instruments and Modules", "labware_bottom": "Labware Bottom", "last_run_time": "last run {{number}}", - "left_right": "Left+Right Mounts", + "left_right": "Left + Right Mounts", "left": "left", "lights": "Lights", "link_firmware_update": "View Firmware Update", @@ -139,6 +140,7 @@ "recalibrate_pipette": "Recalibrate pipette", "recent_protocol_runs": "Recent Protocol Runs", "rerun_now": "Rerun protocol now", + "rerun_loading": "Protocol re-run is disabled until data connection fully loads", "reset_all": "Reset all", "reset_estop": "Reset E-stop", "resume_operation": "Resume operation", diff --git a/app/src/assets/localization/en/device_settings.json b/app/src/assets/localization/en/device_settings.json index 5e40c7ce5e2..79416a09f73 100644 --- a/app/src/assets/localization/en/device_settings.json +++ b/app/src/assets/localization/en/device_settings.json @@ -177,6 +177,7 @@ "never": "Never", "new_features": "New Features", "next_step": "Next step", + "no_calibration_required": "No calibration required", "no_connection_found": "No connection found", "no_gripper_attached": "No gripper attached", "no_modules_attached": "No modules attached", diff --git a/app/src/assets/localization/en/devices_landing.json b/app/src/assets/localization/en/devices_landing.json index dfd92d23030..1ff5ef11fd6 100644 --- a/app/src/assets/localization/en/devices_landing.json +++ b/app/src/assets/localization/en/devices_landing.json @@ -25,11 +25,9 @@ "looking_for_robots": "Looking for robots", "make_sure_robot_is_connected": "Make sure the robot is connected to this computer", "modules": "Modules", - "new_robot_instructions": "When setting up a new Flex, follow the instructions on the touchscreen. For more information, consult the Quickstart Guide for your robot.", "ninety_six_mount": "Left + Right Mount", "no_robots_found": "No robots found", "not_available": "Not available ({{count}})", - "opentrons_flex_quickstart_guide": "Opentrons Flex Quickstart Guide", "ot2_quickstart_guide": "OT-2 Quickstart Guide", "refresh": "Refresh", "restart_the_app": "Restart the app", diff --git a/app/src/assets/localization/en/drop_tip_wizard.json b/app/src/assets/localization/en/drop_tip_wizard.json index e622fbce4cf..e384ccf971c 100644 --- a/app/src/assets/localization/en/drop_tip_wizard.json +++ b/app/src/assets/localization/en/drop_tip_wizard.json @@ -4,28 +4,32 @@ "blowout_complete": "Blowout complete", "blowout_liquid": "Blow out liquid", "cant_safely_drop_tips": "Can't safely drop tips", - "choose_blowout_location": "choose blowout location", - "choose_drop_tip_location": "choose tip-drop location", + "choose_blowout_location": "Choose blowout location", + "choose_deck_location": "Choose deck location", + "choose_drop_tip_location": "Choose tip-drop location", "confirm_blowout_location": "Is the pipette positioned where the liquids should be blown out?", "confirm_drop_tip_location": "Is the pipette positioned where the tips should be dropped?", + "confirm_position": "Confirm position", "confirm_removal_and_home": "Confirm removal and home", + "continue": "Continue", "drop_tip_complete": "Tip drop complete", "drop_tip_failed": "The drop tip could not be completed. Contact customer support for assistance.", - "drop_tips": "drop tips", + "drop_tips": "Drop tips", "error_dropping_tips": "Error dropping tips", + "exit": "Exit", "exit_and_home_pipette": "Exit and home pipette", + "fixed_trash_in_12": "Fixed trash in 12", "getting_ready": "Getting ready…", - "go_back": "go back", + "go_back": "Go back", "jog_too_far": "Jog too far?", "liquid_damages_pipette": "Homing the pipette with liquid in the tips may damage it. You must remove all tips before using the pipette again.", "liquid_damages_this_pipette": "Homing the {{mount}} pipette with liquid in the tips may damage it. You must remove all tips before using the pipette again.", - "move_to_slot": "move to slot", + "move_to_slot": "Move to slot", "no_proceed_to_drop_tip": "No, proceed to tip removal", "position_and_blowout": "Ensure that the pipette tip is centered above and level with where you want the liquid to be blown out. If it isn't, use the controls below or your keyboard to jog the pipette until it is properly aligned.", "position_and_drop_tip": "Ensure that the pipette tip is centered above and level with where you want to drop the tips. If it isn't, use the controls below or your keyboard to jog the pipette until it is properly aligned.", - "position_the_pipette": "position the pipette", + "position_the_pipette": "Position the pipette", "remove_any_attached_tips": "Remove any attached tips", - "remove_attached_tips": "Remove any attached tips", "remove_the_tips_from_pipette": "You may want to remove the tips from the pipette before using it again in a protocol.", "remove_the_tips_manually": "Remove the tips manually. Then home the gantry. Homing with tips attached could pull liquid into the pipette and damage it.", "remove_tips": "Remove tips", @@ -39,5 +43,9 @@ "stand_back_dropping_tips": "Stand back, robot is dropping tips", "stand_back_robot_in_motion": "Stand back, robot is in motion", "start_over": "Start over", - "yes_blow_out_liquid": "Yes, blow out liquid in labware" + "trash_bin_in_slot": "Trash bin in {{slot}}", + "waste_chute_in_slot": "Waste chute in {{slot}}", + "where_to_blowout": "Where do you want to blow out the aspirated liquid?", + "where_to_drop_tips": "Where do you want to drop the attached tips?", + "yes_blow_out_liquid": "Yes, choose blowout location" } diff --git a/app/src/assets/localization/en/error_recovery.json b/app/src/assets/localization/en/error_recovery.json index 605c2b55527..e4e1b5164eb 100644 --- a/app/src/assets/localization/en/error_recovery.json +++ b/app/src/assets/localization/en/error_recovery.json @@ -8,57 +8,88 @@ "blowout_failed": "Blowout failed", "cancel_run": "Cancel run", "canceling_run": "Canceling run", + "carefully_move_labware": "Carefully move any misplaced labware and clean up any spilled liquid.Close the robot door before proceeding.", "change_location": "Change location", "change_tip_pickup_location": "Change tip pick-up location", "choose_a_recovery_action": "Choose a recovery action", + "close_door_to_resume": "Close robot door to resume", + "close_robot_door": "Close the robot door", "close_the_robot_door": "Close the robot door, and then resume the recovery action.", "confirm": "Confirm", "continue": "Continue", "continue_run_now": "Continue run now", "continue_to_drop_tip": "Continue to drop tip", + "do_you_need_to_blowout": "First, do you need to blow out aspirated liquid?", + "door_open_robot_home": "The robot needs to safely move to its home location before you manually move the labware.", + "ensure_lw_is_accurately_placed": "Ensure labware is accurately placed in the slot to prevent further errors.", "error": "Error", "error_details": "Error details", "error_on_robot": "Error on {{robot}}", "failed_dispense_step_not_completed": "The failed dispense step will not be completed. The run will continue from the next step with the attached tips.Close the robot door before proceeding.", "failed_step": "Failed step", - "first_take_any_necessary_actions": "First, take any necessary actions to prepare the robot to retry the failed step.Then, close the robot door before proceeding.", + "first_is_gripper_holding_labware": "First, is the gripper holding labware?", "go_back": "Go back", + "gripper_error": "Gripper error", + "gripper_errors_occur_when": "Gripper errors occur when the gripper stalls or collides with another object on the deck and are usually caused by improperly placed labware or inaccurate labware offsets", + "gripper_releasing_labware": "Gripper releasing labware", + "gripper_will_release_in_s": "Gripper will release labware in {{seconds}} seconds", + "home_and_retry": "Home gantry and retry step", + "home_gantry": "Home gantry", + "home_now": "Home now", "homing_pipette_dangerous": "Homing the {{mount}} pipette with liquid in the tips may damage it. You must remove all tips before using the pipette again.", + "if_issue_persists_gripper_error": " If the issue persists, cancel the run and rerun gripper calibration", + "if_issue_persists_overpressure": " If the issue persists, cancel the run and make the necessary changes to the protocol", + "if_issue_persists_tip_not_detected": " If the issue persists, cancel the run and perform Labware Position Check", "if_tips_are_attached": "If tips are attached, you can choose to blow out any aspirated liquid and drop tips before the run is terminated.", "ignore_all_errors_of_this_type": "Ignore all errors of this type", "ignore_error_and_skip": "Ignore error and skip to next step", "ignore_only_this_error": "Ignore only this error", "ignore_similar_errors_later_in_run": "Ignore similar errors later in the run?", - "launch_recovery_mode": "Launch Recovery Mode", + "inspect_the_robot": "First, inspect the robot to ensure it's prepared to continue the run from the next step.Then, close the robot door before proceeding.", + "labware_released_from_current_height": "The labware will be released from its current height.", + "launch_recovery_mode": "Launch recovery mode", "manually_fill_liquid_in_well": "Manually fill liquid in well {{well}}", "manually_fill_well_and_skip": "Manually fill well and skip to next step", + "manually_move_lw_and_skip": "Manually move labware and skip to next step", + "manually_move_lw_on_deck": "Manually move labware on deck", + "manually_replace_lw_and_retry": "Manually replace labware on deck and retry step", + "manually_replace_lw_on_deck": "Manually replace labware on deck", + "na": "N/A", "next_step": "Next step", "next_try_another_action": "Next, you can try another recovery action or cancel the run.", "no_liquid_detected": "No liquid detected", "overpressure_is_usually_caused": "Overpressure is usually caused by a tip contacting labware, a clog, or moving viscous liquid too quickly", - "if_issue_persists": " If the issue persists, cancel the run and make the necessary changes to the protocol", "pick_up_tips": "Pick up tips", "pipette_overpressure": "Pipette overpressure", - "preserve_aspirated_liquid": "First, do you need to preserve aspirated liquid?", + "prepare_deck_for_homing": "Prepare deck for homing", "proceed_to_cancel": "Proceed to cancel", + "proceed_to_home": "Proceed to home", "proceed_to_tip_selection": "Proceed to tip selection", "recovery_action_failed": "{{action}} failed", - "recovery_mode": "Recovery Mode", + "recovery_mode": "Recovery mode", "recovery_mode_explanation": "Recovery Mode provides you with guided and manual controls for handling errors at runtime.
You can make changes to ensure the step in progress when the error occurred can be completed or choose to cancel the protocol. When changes are made and no subsequent errors are detected, the method completes. Depending on the conditions that caused the error, you will only be provided with appropriate options.", + "release": "Release", + "release_labware_from_gripper": "Release labware from gripper", "remove_any_attached_tips": "Remove any attached tips", + "replace_tips_and_select_loc_partial_tip": "Replace tips and select the last location used for partial tip pickup.", "replace_tips_and_select_location": "It's best to replace tips and select the last location used for tip pickup.", - "replace_used_tips_in_rack_location": "Replace used tips in rack location {{location}} in slot {{slot}}", - "replace_with_new_tip_rack": "Replace with new tip rack in slot {{slot}}", + "replace_used_tips_in_rack_location": "Replace used tips in rack location {{location}} in {{slot}}", + "replace_with_new_tip_rack": "Replace with new tip rack in {{slot}}", "resume": "Resume", + "retry_dropping_tip": "Retry dropping tip", "retry_now": "Retry now", + "retry_picking_up_tip": "Retry picking up tip", "retry_step": "Retry step", "retry_with_new_tips": "Retry with new tips", "retry_with_same_tips": "Retry with same tips", "retrying_step_succeeded": "Retrying step {{step}} succeeded.", + "retrying_step_succeeded_na": "Retrying current step succeeded.", "return_to_menu": "Return to menu", "robot_door_is_open": "Robot door is open", "robot_is_canceling_run": "Robot is canceling the run", "robot_is_in_recovery_mode": "Robot is in recovery mode", + "robot_not_attempt_to_move_lw": "The robot will not attempt to move the labware again. The run will continue from the next step.Close the robot door before proceeding.", + "robot_retry_failed_lw_movement": "The robot will retry the failed labware movement step from where the labware was replaced on the deck.Close the robot door before proceeding.", "robot_will_not_check_for_liquid": "The robot will not check for liquid again. The run will continue from the next step.Close the robot door before proceeding.", "robot_will_retry_with_new_tips": "The robot will retry the failed step with the new tips.Close the robot door before proceeding.", "robot_will_retry_with_same_tips": "The robot will retry the failed step with the same tips.Close the robot door before proceeding.", @@ -70,14 +101,24 @@ "skip_to_next_step_new_tips": "Skip to next step with new tips", "skip_to_next_step_same_tips": "Skip to next step with same tips", "skipping_to_step_succeeded": "Skipping to step {{step}} succeeded.", + "skipping_to_step_succeeded_na": "Skipping to next step succeeded.", + "stall_or_collision_detected_when": "A stall or collision is detected when the robot's motors are blocked", + "stall_or_collision_error": "Stall or collision", "stand_back": "Stand back, robot is in motion", "stand_back_picking_up_tips": "Stand back, picking up tips", "stand_back_resuming": "Stand back, resuming current step", "stand_back_retrying": "Stand back, retrying failed step", "stand_back_skipping_to_next_step": "Stand back, skipping to next step", + "take_any_necessary_precautions": "Take any necessary precautions before positioning yourself to stabilize or catch the labware. Once confirmed, a countdown will begin before the gripper releases.", + "take_necessary_actions": "First, take any necessary actions to prepare the robot to retry the failed step.Then, close the robot door before proceeding.", + "take_necessary_actions_failed_pickup": "First, take any necessary actions to prepare the robot to retry the failed tip pickup.Then, close the robot door before proceeding.", + "take_necessary_actions_failed_tip_drop": "First, take any necessary actions to prepare the robot to retry the failed tip drop.Then, close the robot door before proceeding.", + "take_necessary_actions_home": "Take any necessary actions to prepare the robot to move the gantry to its home position.Close the robot door before proceeding.", "terminate_remote_activity": "Terminate remote activity", + "the_robot_must_return_to_home_position": "The robot must return to its home position before proceeding", "tip_drop_failed": "Tip drop failed", "tip_not_detected": "Tip not detected", + "tip_presence_errors_are_caused": "Tip presence errors are usually caused by improperly placed labware or inaccurate labware offsets", "view_error_details": "View error details", "view_recovery_options": "View recovery options", "you_can_still_drop_tips": "You can still drop the attached tips before proceeding to tip selection." diff --git a/app/src/assets/localization/en/pipette_wizard_flows.json b/app/src/assets/localization/en/pipette_wizard_flows.json index 53ae23d07e2..78dc2b852a6 100644 --- a/app/src/assets/localization/en/pipette_wizard_flows.json +++ b/app/src/assets/localization/en/pipette_wizard_flows.json @@ -49,7 +49,7 @@ "install_probe": "Take the calibration probe from its storage location. Ensure its collar is unlocked. Push the pipette ejector up and press the probe firmly onto the {{location}} pipette nozzle. Twist the collar to lock the probe. Test that the probe is secure by gently pulling it back and forth.", "loose_detach": "Loosen screws and detach ", "move_gantry_to_front": "Move gantry to front", - "must_detach_mounting_plate": "You must detach the mounting plate and reattach the z-axis carraige before using other pipettes. We do not recommend exiting this process before completion.", + "must_detach_mounting_plate": "You must detach the mounting plate and reattach the z-axis carriage before using other pipettes. We do not recommend exiting this process before completion.", "name_and_volume_detected": "{{name}} Pipette Detected", "next": "next", "ninety_six_channel": "{{ninetySix}} pipette", diff --git a/app/src/assets/localization/en/protocol_command_text.json b/app/src/assets/localization/en/protocol_command_text.json index b050ff7bc7a..2842f9dc30d 100644 --- a/app/src/assets/localization/en/protocol_command_text.json +++ b/app/src/assets/localization/en/protocol_command_text.json @@ -1,6 +1,11 @@ { - "adapter_in_mod_in_slot": "{{adapter}} on {{module}} in {{slot}}", - "adapter_in_slot": "{{adapter}} in {{slot}}", + "absorbance_reader_close_lid": "Closing Absorbance Reader lid", + "absorbance_reader_initialize": "Initializing Absorbance Reader to perform {{mode}} measurement at {{wavelengths}}", + "absorbance_reader_open_lid": "Opening Absorbance Reader lid", + "absorbance_reader_read": "Reading plate in Absorbance Reader", + "adapter_in_mod_in_slot": "{{adapter}} on {{module}} in Slot {{slot}}", + "adapter_in_slot": "{{adapter}} in Slot {{slot}}", + "air_gap_in_place": "Air gapping {{volume}} µL", "aspirate": "Aspirating {{volume}} µL from well {{well_name}} of {{labware}} in {{labware_location}} at {{flow_rate}} µL/sec", "aspirate_in_place": "Aspirating {{volume}} µL in place at {{flow_rate}} µL/sec ", "blowout": "Blowing out at well {{well_name}} of {{labware}} in {{labware_location}} at {{flow_rate}} µL/sec", @@ -8,7 +13,7 @@ "closing_tc_lid": "Closing Thermocycler lid", "comment": "Comment", "configure_for_volume": "Configure {{pipette}} to aspirate {{volume}} µL", - "configure_nozzle_layout": "Configure {{pipette}} to use {{amount}} nozzles", + "configure_nozzle_layout": "Configure {{pipette}} to use {{layout}}", "confirm_and_resume": "Confirm and resume", "deactivate_hs_shake": "Deactivating shaker", "deactivate_temperature_module": "Deactivating Temperature Module", @@ -23,17 +28,14 @@ "dispense_push_out": "Dispensing {{volume}} µL into well {{well_name}} of {{labware}} in {{labware_location}} at {{flow_rate}} µL/sec and pushing out {{push_out_volume}} µL", "drop_tip": "Dropping tip in {{well_name}} of {{labware}}", "drop_tip_in_place": "Dropping tip in place", + "dropping_tip_in_trash": "Dropping tip in {{trash}}", "engaging_magnetic_module": "Engaging Magnetic Module", "fixed_trash": "Fixed Trash", "home_gantry": "Homing all gantry, pipette, and plunger axes", + "in_location": "in {{location}}", "latching_hs_latch": "Latching labware on Heater-Shaker", "left": "Left", - "load_labware_info_protocol_setup": "Load {{labware}} in {{module_name}} in Slot {{slot_name}}", - "load_labware_info_protocol_setup_adapter": "Load {{labware}} in {{adapter_name}} in Slot {{slot_name}}", - "load_labware_info_protocol_setup_adapter_module": "Load {{labware}} in {{adapter_name}} in {{module_name}} in Slot {{slot_name}}", - "load_labware_info_protocol_setup_adapter_off_deck": "Load {{labware}} in {{adapter_name}} off deck", - "load_labware_info_protocol_setup_no_module": "Load {{labware}} in Slot {{slot_name}}", - "load_labware_info_protocol_setup_off_deck": "Load {{labware}} off deck", + "load_labware_to_display_location": "Load {{labware}} {{display_location}}", "load_liquids_info_protocol_setup": "Load {{liquid}} into {{labware}}", "load_module_protocol_setup": "Load {{module}} in Slot {{slot_name}}", "load_pipette_protocol_setup": "Load {{pipette_name}} in {{mount_name}} Mount", @@ -49,9 +51,11 @@ "move_to_coordinates": "Moving to (X: {{x}}, Y: {{y}}, Z: {{z}})", "move_to_slot": "Moving to Slot {{slot_name}}", "move_to_well": "Moving to well {{well_name}} of {{labware}} in {{labware_location}}", + "multiple": "multiple", "notes": "notes", "off_deck": "off deck", "offdeck": "offdeck", + "on_location": "on {{location}}", "opening_tc_lid": "Opening Thermocycler lid", "pause": "Pause", "pause_on": "Pause on {{robot_name}}", @@ -66,13 +70,19 @@ "setting_temperature_module_temp": "Setting Temperature Module to {{temp}} (rounded to nearest integer)", "setting_thermocycler_block_temp": "Setting Thermocycler block temperature to {{temp}} with hold time of {{hold_time_seconds}} seconds after target reached", "setting_thermocycler_lid_temp": "Setting Thermocycler lid temperature to {{temp}}", + "single": "single", "slot": "Slot {{slot_name}}", "target_temperature": "target temperature", "tc_awaiting_for_duration": "Waiting for Thermocycler profile to complete", - "tc_run_profile_steps": "temperature: {{celsius}}°C, seconds: {{seconds}}", - "tc_starting_profile": "Thermocycler starting {{repetitions}} repetitions of cycle composed of the following steps:", + "tc_run_profile_steps": "Temperature: {{celsius}}°C, hold time: {{duration}}", + "tc_starting_extended_profile": "Running thermocycler profile with {{elementCount}} total steps and cycles:", + "tc_starting_extended_profile_cycle": "{{repetitions}} repetitions of the following steps:", + "tc_starting_profile": "Running thermocycler profile with {{stepCount}} steps:", "touch_tip": "Touching tip", + "trash_bin": "Trash Bin", "trash_bin_in_slot": "Trash Bin in {{slot_name}}", + "turning_rail_lights_off": "Turning rail lights off", + "turning_rail_lights_on": "Turning rail lights on", "unlatching_hs_latch": "Unlatching labware on Heater-Shaker", "wait_for_duration": "Pausing for {{seconds}} seconds. {{message}}", "wait_for_resume": "Pausing protocol", @@ -80,5 +90,6 @@ "waiting_for_tc_block_to_reach": "Waiting for Thermocycler block to reach target temperature and holding for specified time", "waiting_for_tc_lid_to_reach": "Waiting for Thermocycler lid to reach target temperature", "waiting_to_reach_temp_module": "Waiting for Temperature Module to reach {{temp}}", - "waste_chute": "Waste Chute" + "waste_chute": "Waste Chute", + "with_reference_of": "with reference of {{wavelength}} nm" } diff --git a/app/src/assets/localization/en/protocol_details.json b/app/src/assets/localization/en/protocol_details.json index 19345f0b8a5..b93e675a1c5 100644 --- a/app/src/assets/localization/en/protocol_details.json +++ b/app/src/assets/localization/en/protocol_details.json @@ -42,6 +42,7 @@ "name": "Name", "no_available_robots_found": "No available robots found", "no_custom_values": "No custom values specified", + "no_labware_specified": "No labware specified in this protocol", "no_parameters": "No parameters specified in this protocol", "no_summary": "no summary specified for this protocol.", "not_connected": "not connected", diff --git a/app/src/assets/localization/en/protocol_info.json b/app/src/assets/localization/en/protocol_info.json index 3307c45363f..66c2fc9aa89 100644 --- a/app/src/assets/localization/en/protocol_info.json +++ b/app/src/assets/localization/en/protocol_info.json @@ -10,6 +10,7 @@ "creation_method": "Creation Method", "custom_labware_not_supported": "Robot doesn't support custom labware", "date_added": "Date Added", + "date_added_date": "Date added {{date}}", "delete_protocol": "Delete protocol", "description": "Description", "drag_file_here": "Drag and drop protocol file here", @@ -41,6 +42,7 @@ "labware_position_check_complete_toast_with_offsets": "Labware Position Check complete. {{count}} Labware Offset created.", "labware_title": "Required Labware", "last_run": "Last Run", + "last_run_time": "Last run {{time}}", "last_updated": "Last Updated", "launch_protocol_designer": "Open Protocol Designer", "manual_steps_learn_more": "Learn more about manual steps", diff --git a/app/src/assets/localization/en/protocol_setup.json b/app/src/assets/localization/en/protocol_setup.json index ee00797352e..17f60958d55 100644 --- a/app/src/assets/localization/en/protocol_setup.json +++ b/app/src/assets/localization/en/protocol_setup.json @@ -1,26 +1,29 @@ { "96_mount": "left + right mount", "action_needed": "Action needed", - "adapter_slot_location_module": "Slot {{slotName}}, {{adapterName}} on {{moduleName}}", "adapter_slot_location": "Slot {{slotName}}, {{adapterName}}", + "adapter_slot_location_module": "Slot {{slotName}}, {{adapterName}} on {{moduleName}}", "add_fixture": "Add {{fixtureName}} to {{locationName}}", "add_this_deck_hardware": "Add this hardware to your deck configuration. It will be referenced during protocol analysis.", "add_to_slot": "Add to slot {{slotName}}", "additional_labware": "{{count}} additional labware", "additional_off_deck_labware": "Additional Off-Deck Labware", + "all_files_associated": "All files associated with the protocol run are available on the robot detail screen.", + "applied_labware_offset_data": "Applied labware offset data", "applied_labware_offsets": "applied labware offsets", "are_you_sure_you_want_to_proceed": "Are you sure you want to proceed to run?", - "attach_gripper_failure_reason": "Attach the required gripper to continue", + "attach": "attach", "attach_gripper": "attach gripper", + "attach_gripper_failure_reason": "Attach the required gripper to continue", "attach_module": "Attach module before calibrating", "attach_pipette_before_module_calibration": "Attach a pipette before running module calibration", "attach_pipette_calibration": "Attach pipette to see calibration information", "attach_pipette_cta": "Attach Pipette", "attach_pipette_failure_reason": "Attach the required pipette(s) to continue", "attach_pipette_tip_length_calibration": "Attach pipette to see tip length calibration information", - "attach": "attach", "back_to_top": "Back to top", "cal_all_pip": "Calibrate pipettes first", + "calibrate": "calibrate", "calibrate_deck_failure_reason": "Calibrate the deck to continue", "calibrate_deck_to_proceed_to_pipette_calibration": "Calibrate your deck in order to proceed to pipette calibration", "calibrate_deck_to_proceed_to_tip_length_calibration": "Calibrate your deck in order to proceed to tip length calibration", @@ -30,16 +33,15 @@ "calibrate_pipette_before_module_calibration": "Calibrate pipette before running module calibration", "calibrate_pipette_failure_reason": "Calibrate the required pipette(s) to continue", "calibrate_tiprack_failure_reason": "Calibrate the required tip lengths to continue", - "calibrate": "calibrate", "calibrated": "calibrated", + "calibration": "Calibration", "calibration_data_not_available": "Calibration data not available once run has started", "calibration_needed": "Calibration needed", "calibration_ready": "Calibration ready", + "calibration_required": "Calibration required", "calibration_required_attach_pipette_first": "Calibration required Attach pipette first", "calibration_required_calibrate_pipette_first": "Calibration required Calibrate pipette first", - "calibration_required": "Calibration required", "calibration_status": "calibration status", - "calibration": "Calibration", "cancel_and_restart_to_edit": "Cancel the run and restart setup to edit", "choose_csv_file": "Choose CSV file", "choose_enum": "Choose {{displayName}}", @@ -49,9 +51,9 @@ "configured": "configured", "confirm_heater_shaker_module_modal_description": "Before the run begins, module should have both anchors fully extended for a firm attachment. The thermal adapter should be attached to the module. ", "confirm_heater_shaker_module_modal_title": "Confirm Heater-Shaker Module is attached", - "confirm_offsets": "Confirm offsets", "confirm_liquids": "Confirm liquids", "confirm_locations_and_volumes": "Confirm locations and volumes", + "confirm_offsets": "Confirm offsets", "confirm_placements": "Confirm placements", "confirm_selection": "Confirm selection", "confirm_values": "Confirm values", @@ -66,79 +68,81 @@ "currently_configured": "Currently configured", "currently_unavailable": "Currently unavailable", "custom_values": "Custom values", + "deck_cal_description": "This measures the deck X and Y values relative to the gantry. Deck Calibration is the foundation for Tip Length Calibration and Pipette Offset Calibration.", "deck_cal_description_bullet_1": "Perform Deck Calibration during new robot setup.", "deck_cal_description_bullet_2": "Redo Deck Calibration if you relocate your robot.", - "deck_cal_description": "This measures the deck X and Y values relative to the gantry. Deck Calibration is the foundation for Tip Length Calibration and Pipette Offset Calibration.", "deck_calibration_title": "Deck Calibration", - "deck_conflict_info_thermocycler": "Update the deck configuration by removing the fixtures in locations A1 and B1. Either remove the fixtures from the deck configuration or update the protocol.", - "deck_conflict_info": "Update the deck configuration by removing the {{currentFixture}} in location {{cutout}}. Either remove the fixture from the deck configuration or update the protocol.", "deck_conflict": "Deck location conflict", + "deck_conflict_info": "Update the deck configuration by removing the {{currentFixture}} in location {{cutout}}. Either remove the fixture from the deck configuration or update the protocol.", + "deck_conflict_info_thermocycler": "Update the deck configuration by removing the fixtures in locations A1 and B1. Either remove the fixtures from the deck configuration or update the protocol.", "deck_hardware": "Deck hardware", "deck_hardware_ready": "Deck hardware ready", "deck_map": "Deck Map", "default_values": "Default values", + "download_files": "Download files", "example": "Example", "exit_to_deck_configuration": "Exit to deck configuration", "extension_mount": "extension mount", "extra_attention_warning_title": "Secure labware and modules before proceeding to run", "extra_module_attached": "Extra module attached", "feedback_form_link": "Let us know!", - "fixture_name": "fixture", "fixture": "Fixture", - "fixtures_connected_plural": "{{count}} fixtures attached", + "fixture_name": "fixture", "fixtures_connected": "{{count}} fixture attached", + "fixtures_connected_plural": "{{count}} fixtures attached", "get_labware_offset_data": "Get Labware Offset Data", "hardware_missing": "Missing hardware", "heater_shaker_extra_attention": "Use latch controls for easy placement of labware.", "heater_shaker_labware_list_view": "To add labware, use the toggle to control the latch", "how_offset_data_works": "How labware offsets work", "individiual_well_volume": "Individual well volume", - "initial_liquids_num_plural": "{{count}} initial liquids", "initial_liquids_num": "{{count}} initial liquid", + "initial_liquids_num_plural": "{{count}} initial liquids", "initial_location": "Initial Location", + "install_modules": "Install the required module.", "install_modules_and_fixtures": "Install and calibrate the required modules. Install the required fixtures.", "install_modules_plural": "Install the required modules.", - "install_modules": "Install the required module.", - "instrument_calibrations_missing_plural": "Missing {{count}} calibrations", "instrument_calibrations_missing": "Missing {{count}} calibration", - "instruments_connected_plural": "{{count}} instruments attached", - "instruments_connected": "{{count}} instrument attached", + "instrument_calibrations_missing_plural": "Missing {{count}} calibrations", "instruments": "Instruments", - "labware_latch_instructions": "Use latch control for easy placement of labware.", + "instruments_connected": "{{count}} instrument attached", + "instruments_connected_plural": "{{count}} instruments attached", + "labware": "Labware", "labware_latch": "Labware Latch", + "labware_latch_instructions": "Use latch control for easy placement of labware.", "labware_location": "Labware Location", "labware_name": "Labware name", "labware_placement": "labware placement", + "labware_position_check": "Labware Position Check", + "labware_position_check_not_available": "Labware Position Check is not available after run has started", "labware_position_check_not_available_analyzing_on_robot": "Labware Position Check is not available while protocol is analyzing on robot", "labware_position_check_not_available_empty_protocol": "Labware Position Check requires that the protocol loads labware and pipettes", - "labware_position_check_not_available": "Labware Position Check is not available after run has started", "labware_position_check_step_description": "Recommended workflow that helps you verify the position of each labware on the deck.", "labware_position_check_step_title": "Labware Position Check", "labware_position_check_text": "Labware Position Check is a recommended workflow that helps you verify the position of each labware on the deck. During this check, you can create Labware Offsets that adjust how the robot moves to each labware in the X, Y and Z directions.", - "labware_position_check": "Labware Position Check", + "labware_quantity": "Quantity: {{quantity}}", "labware_setup_step_description": "Gather the following labware and full tip racks. To run your protocol without Labware Position Check, place and secure labware in their initial locations.", "labware_setup_step_title": "Labware", - "labware": "Labware", "last_calibrated": "Last calibrated: {{date}}", "learn_how_it_works": "Learn how it works", + "learn_more": "Learn more", "learn_more_about_offset_data": "Learn more about Labware Offset Data", "learn_more_about_robot_cal_link": "Learn more about robot calibration", - "learn_more": "Learn more", "liquid_information": "Liquid information", "liquid_name": "Liquid name", - "liquids": "liquids", "liquid_setup_step_description": "View liquid starting locations and volumes", "liquid_setup_step_title": "Liquids", + "liquids": "liquids", + "liquids_confirmed": "Liquids confirmed", "liquids_not_in_setup": "No liquids used in this protocol", "liquids_not_in_the_protocol": "no liquids are specified for this protocol.", "liquids_ready": "Liquids ready", - "liquids_confirmed": "Liquids confirmed", "list_view": "List View", "loading_data": "Loading data...", "loading_labware_offsets": "Loading labware offsets", "loading_protocol_details": "Loading details...", - "location_conflict": "Location conflict", "location": "Location", + "location_conflict": "Location conflict", "lpc_and_offset_data_title": "Labware Position Check and Labware Offset Data", "lpc_disabled_calibration_not_complete": "Make sure robot calibration is complete before running Labware Position Check", "lpc_disabled_modules_and_calibration_not_complete": "Make sure robot calibration is complete and all modules are connected before running Labware Position Check", @@ -146,36 +150,37 @@ "lpc_disabled_no_tipracks_loaded": "Labware Position Check requires that the protocol loads a tip rack", "lpc_disabled_no_tipracks_used": "Labware Position Check requires that the protocol has at least one pipette that picks up a tip", "map_view": "Map View", + "missing": "Missing", "missing_gripper": "Missing gripper", "missing_instruments": "Missing {{count}}", - "missing_pipettes_plural": "Missing {{count}} pipettes", "missing_pipettes": "Missing {{count}} pipette", - "missing": "Missing", + "missing_pipettes_plural": "Missing {{count}} pipettes", "modal_instructions_title": "{{moduleName}} Setup Instructions", + "module": "Module", "module_connected": "Connected", "module_disconnected": "Disconnected", "module_instructions_link": "{{moduleName}} setup instructions", + "module_instructions_manual": "For step-by-step instructions on setting up your module, consult the Quickstart Guide that came in its box. You can also click the link below or scan the QR code to read the module Instruction Manual.", "module_mismatch_body": "Check that the modules connected to this robot are of the right type and generation", "module_name": "Module", "module_not_connected": "Not connected", - "module_setup_step_title": "Deck hardware", "module_setup_step_ready": "Calibration ready", + "module_setup_step_title": "Deck hardware", "module_slot_location": "Slot {{slotName}}, {{moduleName}}", - "module": "Module", - "modules_connected_plural": "{{count}} modules attached", + "modules": "Modules", "modules_connected": "{{count}} module attached", + "modules_connected_plural": "{{count}} modules attached", "modules_setup_step_title": "Module Setup", - "modules": "Modules", - "mount_title": "{{mount}} MOUNT:", "mount": "{{mount}} mount", + "mount_title": "{{mount}} MOUNT:", "multiple_fixtures_missing": "{{count}} fixtures missing", + "multiple_modules": "Multiple modules of the same type", "multiple_modules_example": "Your protocol has two Temperature Modules. The Temperature Module attached to the first port starting from the left will be related to the first Temperature Module in your protocol while the second Temperature Module loaded would be related to the Temperature Module connected to the next port to the right. If using a hub, follow the same logic with the port ordering.", "multiple_modules_explanation": "To use more than one of the same module in a protocol, you first need to plug in the module that’s called first in your protocol to the lowest numbered USB port on the robot. Continue in the same manner with additional modules.", "multiple_modules_help_link_title": "See How To Set Up Modules of the Same Type", "multiple_modules_learn_more": "Learn more about using multiple modules of the same type", "multiple_modules_missing_plural": "Missing {{count}} modules", "multiple_modules_modal": "Setting up multiple modules of the same type", - "multiple_modules": "Multiple modules of the same type", "multiple_of_most_modules": "You can use multiples of most module types within a single Python protocol by connecting and loading the modules in a specific order. The robot will initialize the matching module attached to the lowest numbered port first, regardless of what deck slot it occupies.", "must_have_labware_and_pip": "Protocol must load labware and a pipette", "n_a": "N/A", @@ -188,8 +193,8 @@ "no_modules_or_fixtures": "No modules or fixtures are specified for this protocol.", "no_modules_specified": "no modules are specified for this protocol.", "no_modules_used_in_this_protocol": "No hardware used in this protocol", - "no_parameters_specified_in_protocol": "No parameters specified in this protocol", "no_parameters_specified": "No parameters specified", + "no_parameters_specified_in_protocol": "No parameters specified in this protocol", "no_tiprack_loaded": "Protocol must load a tip rack", "no_tiprack_used": "Protocol must pick up a tip", "no_usb_connection_required": "No USB connection required", @@ -197,32 +202,32 @@ "no_usb_required": "No USB required", "not_calibrated": "Not calibrated yet", "not_configured": "not configured", - "off_deck": "Off deck", "off": "Off", + "off_deck": "Off deck", "offset_data": "Offset Data", - "offsets_applied_plural": "{{count}} offsets applied", - "offsets_applied": "{{count}} offset applied", + "offsets_applied": "{{count}} offsets applied", + "offsets_confirmed": "Offsets confirmed", "offsets_ready": "Offsets ready", - "on_adapter_in_mod": "on {{adapterName}} in {{moduleName}}", + "on": "On", + "on-deck_labware": "{{count}} on-deck labware", "on_adapter": "on {{adapterName}}", + "on_adapter_in_mod": "on {{adapterName}} in {{moduleName}}", "on_deck": "On deck", - "on-deck_labware": "{{count}} on-deck labware", - "on": "On", "opening": "Opening...", "parameters": "Parameters", "pipette_mismatch": "Pipette generation mismatch.", "pipette_missing": "Pipette missing", + "pipette_offset_cal": "Pipette Offset Calibration", + "pipette_offset_cal_description": "This measures a pipette’s X, Y and Z values in relation to the pipette mount and the deck. Pipette Offset Calibration relies on Deck Calibration and Tip Length Calibration. ", "pipette_offset_cal_description_bullet_1": "Perform Pipette Offset calibration the first time you attach a pipette to a new mount.", "pipette_offset_cal_description_bullet_2": "Redo Pipette Offset Calibration after performing Deck Calibration.", "pipette_offset_cal_description_bullet_3": "Redo Pipette Offset Calibration after performing Tip Length Calibration for the tip you used to calibrate the pipette.", - "pipette_offset_cal_description": "This measures a pipette’s X, Y and Z values in relation to the pipette mount and the deck. Pipette Offset Calibration relies on Deck Calibration and Tip Length Calibration. ", - "pipette_offset_cal": "Pipette Offset Calibration", "placement": "Placement", - "placements_ready": "Placements ready", "placements_confirmed": "Placements confirmed", + "placements_ready": "Placements ready", "plug_in_module_to_configure": "Plug in a {{module}} to add it to the slot", - "plug_in_required_module_plural": "Plug in and power up the required modules to continue", "plug_in_required_module": "Plug in and power up the required module to continue", + "plug_in_required_module_plural": "Plug in and power up the required modules to continue", "prepare_to_run": "Prepare to run", "proceed_to_labware_position_check": "Proceed to labware position check", "proceed_to_labware_setup_step": "Proceed to labware", @@ -244,34 +249,34 @@ "recalibrating_not_available": "Recalibrating Tip Length calibrations and Labware Position Check is not available.", "recalibrating_tip_length_not_available": "Recalibrating a tip length is not available once a run has started", "recommended": "Recommended", + "required": "Required", "required_instrument_calibrations": "required instrument calibrations", "required_tip_racks_title": "Required Tip Length Calibrations", - "required": "Required", - "reset_parameter_values_body": "This will discard any changes you have made. All parameters will have their default values.", "reset_parameter_values": "Reset parameter values?", + "reset_parameter_values_body": "This will discard any changes you have made. All parameters will have their default values.", "reset_setup": "Restart setup to edit", "reset_values": "Reset values", "resolve": "Resolve", - "restart_setup_and_try": "Restart setup and try using different parameter values.", "restart_setup": "Restart setup", + "restart_setup_and_try": "Restart setup and try using different parameter values.", "restore_default": "Restore default value", "restore_defaults": "Restore default values", "robot_cal_description": "Robot calibration establishes how the robot knows where it is in relation to the deck. Accurate Robot calibration is essential to run protocols successfully. Robot calibration has 3 parts: Deck calibration, Tip Length calibration and Pipette Offset calibration.", "robot_cal_help_title": "How Robot Calibration Works", "robot_calibration_step_description_pipettes_only": "Review required instruments and calibrations for this protocol.", "robot_calibration_step_description": "Review required pipettes and tip length calibrations for this protocol.", - "robot_calibration_step_title": "Instruments", "robot_calibration_step_ready": "Calibration ready", + "robot_calibration_step_title": "Instruments", + "run": "Run", "run_disabled_calibration_not_complete": "Make sure robot calibration is complete before proceeding to run", "run_disabled_modules_and_calibration_not_complete": "Make sure robot calibration is complete and all modules are connected before proceeding to run", "run_disabled_modules_not_connected": "Make sure all modules are connected before proceeding to run", "run_labware_position_check": "run labware position check", "run_labware_position_check_to_get_offsets": "Run Labware Position Check to get your labware offset data.", "run_never_started": "Run was never started", - "run": "Run", + "secure": "Secure", "secure_labware_instructions": "Secure labware instructions", "secure_labware_modal": "Securing labware to the {{name}}", - "secure": "Secure", "setup_for_run": "Setup for Run", "setup_instructions": "setup instructions", "setup_is_view_only": "Setup is view-only once run has started", @@ -283,22 +288,22 @@ "step": "STEP {{index}}", "there_are_no_unconfigured_modules": "No {{module}} is connected. Attach one and place it in {{slot}}.", "there_are_other_configured_modules": "A {{module}} is already configured in a different slot. Exit run setup and update your deck configuration to move to an already connected module. Or attach another {{module}} to continue setup.", - "tip_length_cal_description_bullet": "Perform Tip Length Calibration for each new tip type used on a pipette.", "tip_length_cal_description": "This measures the Z distance between the bottom of the tip and the pipette’s nozzle. If you redo the tip length calibration for the tip you used to calibrate a pipette, you will also have to redo that Pipette Offset Calibration.", + "tip_length_cal_description_bullet": "Perform Tip Length Calibration for each new tip type used on a pipette.", "tip_length_cal_title": "Tip Length Calibration", "tip_length_calibration": "tip length calibration", "total_liquid_volume": "Total volume", - "update_deck_config": "Update deck configuration", "update_deck": "Update deck", + "update_deck_config": "Update deck configuration", "update_offsets": "Update offsets", "updated": "Updated", "usb_connected_no_port_info": "USB Port Connected", "usb_drive_notification": "Leave USB drive attached until run starts", "usb_port_connected": "USB Port {{port}}", "usb_port_number": "USB-{{port}}", - "value_out_of_range_generic": "Value must be in range", - "value_out_of_range": "Value must be between {{min}}-{{max}}", "value": "Value", + "value_out_of_range": "Value must be between {{min}}-{{max}}", + "value_out_of_range_generic": "Value must be in range", "values_are_view_only": "Values are view-only", "variable_well_amount": "Variable well amount", "view_current_offsets": "View current offsets", diff --git a/app/src/assets/localization/en/quick_transfer.json b/app/src/assets/localization/en/quick_transfer.json index 99e7bb0d6ad..2ebdb5699b8 100644 --- a/app/src/assets/localization/en/quick_transfer.json +++ b/app/src/assets/localization/en/quick_transfer.json @@ -5,7 +5,7 @@ "advanced_setting_disabled": "Advanced setting disabled for this transfer", "advanced_settings": "Advanced settings", "air_gap": "Air gap", - "air_gap_before_aspirating": "Air gap before aspirating", + "air_gap_after_aspirating": "Air gap after aspirating", "air_gap_before_dispensing": "Air gap before dispensing", "air_gap_capacity_error": "The tip is too full to add an air gap.", "air_gap_value": "{{volume}} µL", @@ -39,7 +39,7 @@ "create_new_transfer": "Create new quick transfer", "create_transfer": "Create transfer", "delay": "Delay", - "delay_before_aspirating": "Delay before aspirating", + "delay_after_aspirating": "Delay after aspirating", "delay_before_dispensing": "Delay before dispensing", "delay_duration_s": "Delay duration (seconds)", "delay_position_mm": "Delay position from bottom of well (mm)", @@ -96,7 +96,7 @@ "pipette_path": "Pipette path", "pipette_path_multi_aspirate": "Multi-aspirate", "pipette_path_multi_dispense": "Multi-dispense", - "pipette_path_multi_dispense_volume_blowout": "Multi-dispense, {{volume}} disposal volume, blowout into {{blowOutLocation}}", + "pipette_path_multi_dispense_volume_blowout": "Multi-dispense, {{volume}} µL disposal volume, blowout {{blowOutLocation}}", "pipette_path_single": "Single transfers", "pre_wet_tip": "Pre-wet tip", "quick_transfer": "Quick transfer", @@ -121,7 +121,7 @@ "set_transfer_volume": "Set transfer volume", "source": "Source", "source_labware": "Source labware", - "source_labware_d2": "Source labware in D2", + "source_labware_c2": "Source labware in C2", "starting_well": "starting well", "storage_limit_reached": "Storage limit reached", "tip_drop_location": "Tip drop location", @@ -130,7 +130,7 @@ "tip_position_value": "{{position}} mm from the bottom", "tip_rack": "Tip rack", "touch_tip": "Touch tip", - "touch_tip_before_aspirating": "Touch tip before aspirating", + "touch_tip_after_aspirating": "Touch tip after aspirating", "touch_tip_before_dispensing": "Touch tip before dispensing", "touch_tip_position_mm": "Touch tip position from bottom of well (mm)", "touch_tip_value": "{{position}} mm from bottom", diff --git a/app/src/assets/localization/en/robot_calibration.json b/app/src/assets/localization/en/robot_calibration.json index 723e1526adc..a8333470d94 100644 --- a/app/src/assets/localization/en/robot_calibration.json +++ b/app/src/assets/localization/en/robot_calibration.json @@ -53,6 +53,8 @@ "download_calibration_data_unavailable": "No calibration data available.", "download_calibration_title": "Download Calibration Data", "download_details": "Download details JSON Calibration Check summary", + "error": "Error", + "exit": "Exit", "finish": "Finish", "get_started": "Get started", "good_calibration": "Good calibration", diff --git a/app/src/assets/localization/en/run_details.json b/app/src/assets/localization/en/run_details.json index ef56e913613..98443901364 100644 --- a/app/src/assets/localization/en/run_details.json +++ b/app/src/assets/localization/en/run_details.json @@ -18,7 +18,8 @@ "clear_protocol": "Clear protocol", "clear_protocol_to_make_available": "Clear protocol from robot to make it available.", "close_door": "Close robot door", - "close_door_to_resume": "Close robot door to resume run", + "close_door_to_resume": "Close robot door to resume", + "close_door_to_resume_run": "Close robot door to resume run", "closing_protocol": "Closing Protocol", "comment": "Comment", "comment_step": "Comment", @@ -30,9 +31,11 @@ "custom_values": "Custom values", "data_out_of_date": "This data is likely out of date", "date": "Date", + "device_details": "Device details", "door_is_open": "Robot door is open", "door_open_pause": "Current Step - Paused - Door Open", "download": "Download", + "download_files": "Download files", "download_run_log": "Download run log", "downloading_run_log": "Downloading run log", "drop_tip": "Dropping tip in {{well_name}} of {{labware}} in {{labware_location}}", @@ -44,6 +47,7 @@ "error_info": "Error {{errorCode}}: {{errorType}}", "error_type": "Error: {{errorType}}", "failed_step": "Failed step", + "files_available_robot_details": "All files associated with the protocol run are available on the robot detail screen.", "final_step": "Final Step", "ignore_stored_data": "Ignore stored data", "labware": "labware", @@ -58,6 +62,7 @@ "module_controls": "Module Controls", "module_slot_number": "Slot {{slot_number}}", "move_labware": "Move Labware", + "na": "N/A", "name": "Name", "no_files_included": "No protocol files included", "no_of_error": "{{count}} error", @@ -105,7 +110,6 @@ "run_complete": "Run completed", "run_completed": "Run completed.", "run_completed_splash": "Run completed", - "run_completed_with_errors": "Run completed with errors.", "run_completed_with_warnings": "Run completed with warnings.", "run_completed_with_warnings_splash": "Run completed with warnings", "run_cta_disabled": "Complete required steps on Protocol tab before starting the run", @@ -141,6 +145,7 @@ "status_succeeded": "Completed", "step": "Step", "step_failed": "Step failed", + "step_na": "Step: N/A", "step_number": "Step {{step_number}}:", "steps_total": "{{count}} steps total", "stored_labware_offset_data": "Stored Labware Offset data that applies to this protocol", diff --git a/app/src/assets/localization/en/shared.json b/app/src/assets/localization/en/shared.json index 0b580a612e8..f4e542e761b 100644 --- a/app/src/assets/localization/en/shared.json +++ b/app/src/assets/localization/en/shared.json @@ -11,6 +11,7 @@ "change_robot": "Change robot", "clear_data": "clear data", "close": "close", + "closed": "closed", "close_robot_door": "Close the robot door before starting the run.", "confirm": "Confirm", "confirm_placement": "Confirm placement", diff --git a/app/src/assets/localization/index.ts b/app/src/assets/localization/index.ts index e92a7077ed9..2b16ff8c61b 100644 --- a/app/src/assets/localization/index.ts +++ b/app/src/assets/localization/index.ts @@ -1,5 +1,7 @@ import { en } from './en' +import { zh } from './zh' export const resources = { en, + zh, } diff --git a/app/src/assets/localization/zh/anonymous.json b/app/src/assets/localization/zh/anonymous.json new file mode 100644 index 00000000000..045245c84f7 --- /dev/null +++ b/app/src/assets/localization/zh/anonymous.json @@ -0,0 +1,80 @@ +{ + "a_robot_software_update_is_available": "需要更新工作站软件版本才能使用该版本的桌面应用程序运行协议。转到工作站这个金属块是一个特制的工具,完美适配您的甲板,有助于校准。如果您没有校准块,请发送电子邮件给支持团队,以便我们寄送一个给您。在您提供的信息中,请确保包括您的姓名、公司或机构名称和寄送地址。在等待校准块到达过程中,您可以暂时利用工作站里垃圾桶上的平面进行校准。", + "calibration_on_opentrons_tips_is_important": "使用上述吸头和吸头盒进行校准非常重要,因为工作站的准确性是基于这些吸头的已知尺寸来确定的。", + "choose_what_data_to_share": "选择要共享的工作站数据。", + "computer_in_app_is_controlling_robot": "当前正在由一台已联网的计算机控制此工作站。", + "confirm_terminate": "这将立即停止计算机上进行的活动。您或其他用户可能会丢失进度或在该计算机上出现报错提示。", + "connect_and_screw_in_gripper": "连接并固定转板抓手", + "connect_via_usb_description_3": "3. 在连接的计算机上启动工作站应用程序以继续。", + "connection_description_usb": "直接连接到计算机。", + "connection_lost_description": "应用程序现在无法与此工作站通信。请检查与工作站的USB或Wi-Fi连接,然后尝试重新连接。", + "contact_information": "请与支持人员联系以获得帮助。", + "contact_support_for_connection_help": "如果以上方法都无法解决问题,请联系支持人员寻求帮助(通过此应用程序中的问号链接,或发送电子邮件至{{support_email}}。)", + "deck_fixture_setup_modal_bottom_description": "有关安装不同类型固定装置的详细信息,请与支持人员联系。", + "delete_protocol_from_app": "删除协议,针对错误进行修改,然后从桌面应用程序将协议重新发送到此工作站。", + "delete_transfer_from_app": "删除快速移液,修改并解决错误,在工作站显示屏幕上重新创建此移液。", + "error_boundary_description": "您需要重新启动触摸屏。联系支持人员以获取帮助。", + "estop_pressed_description": "首先,安全清理甲板上的任何实验耗材和洒出的液体。然后,顺时针旋转急停开关。最后,让工作站将龙门架移动到其原位。", + "find_your_robot": "在应用程序的“设备”栏找到您的工作站,以安装软件更新。", + "firmware_update_download_logs": "请与支持人员联系以获得帮助。", + "general_error_message": "如果该消息反复出现,请尝试重新启动您的应用程序和工作站。如果这不能解决问题,请与支持人员联系。", + "gripper_still_attached": "转板抓手仍处于连接状态", + "gripper_successfully_attached_and_calibrated": "转板抓手已成功连接并校准", + "gripper_successfully_calibrated": "转板抓手已成功校准", + "gripper_successfully_detached": "转板抓手已成功卸下", + "gripper": "转板抓手", + "help_us_improve_send_error_report": "通过向支持团队发送错误报告,帮助我们改进您的使用体验", + "ip_description_second": "请联系网络管理员,为工作站分配静态IP地址。", + "learn_uninstalling": "了解更多有关卸载应用程序的信息", + "loosen_screws_and_detach": "松开螺丝并卸下转板抓手", + "modal_instructions": "有关设置模块的分步说明,请参阅随包装附带的快速指引。", + "module_calibration_failed": "模块校准失败。请确保校准适配器正确放置在模块上,然后重试。如果仍然有问题,请与支持人员联系。{{error}}", + "module_calibration_get_started": "开始前,请从工作台上移除实验耗材并清理工作区,以便于校准。还需准备好右侧显示的所需设备。校准适配器随模块一起提供。移液器探头随移液器一起提供。", + "module_error_contact_support": "尝试关闭模块电源,然后再打开。如果报错仍然存在,请与支持人员联系。", + "network_setup_menu_description": "您将使用此连接来运行软件更新,并将协议加载到您的工作站上。", + "new_robot_instructions": "设置新工作站时,请遵循触摸屏上的指示。有关更多信息,请参阅您的工作站快速入门指南。", + "oem_mode_description": "启用OEM模式,以从Flex触摸屏中移除Opentrons的相关信息。", + "opentrons_app_successfully_updated": "应用程序已成功更新。", + "opentrons_app_update": "应用程序更新", + "opentrons_app_update_available": "应用程序可更新", + "opentrons_app_update_available_variation": "有可用的应用程序更新。", + "opentrons_app_will_use_interpreter": "在指定路径后,应用程序将使用此路径的Python解释器,而不是默认绑定的Python解释器。", + "opentrons_cares_about_privacy": "我们注重您的隐私。我们匿名化所有数据,仅用于改进我们的产品。", + "opentrons_def": "已验证的数据", + "opentrons_flex_quickstart_guide": "快速入门指南", + "opentrons_labware_def": "已验证的实验耗材数据", + "opentrons_tip_racks_recommended": "建议使用Opentrons吸头盒。其他吸头盒无法保证精度。", + "opentrons_tip_rack_name": "opentrons", + "previous_releases": "查看以前的版本", + "receive_alert": "当软件更新可用时接收提醒。", + "restore_description": "不建议恢复到过往的软件版本,但您可以访问下方的过往版本。为了获得最佳效果,请在安装过往版本之前卸载现有应用程序并删除其配置文件。", + "robot_server_version_ot3_description": "工作站软件包括工作站服务器和触摸屏显示界面。", + "robot_software_update_required": "需要对工作站软件进行更新才能运行此版本应用程序的协议。", + "run_failed_modal_description_desktop": "请与支持人员联系以获得帮助。", + "secure_labware_explanation_magnetic_module": "通过调整模块顶部的黑色固定支架,确保您的实验耗材固定在磁性模块上。 您的模块提供了两种尺寸的板固定支架:标准和深孔。这些固定支架可以通过拧开模块的拇指螺钉(前面的银色旋钮)来移除和更换。", + "secure_labware_explanation_thermocycler": "通过关闭热循环仪模块的闩锁将您的实验耗材固定在其上。这样可以确保水平以及板位置的准确,从而获得最佳结果。", + "send_a_protocol_to_store": "向工作站发送协议以开始。", + "setup_instructions_description": "有关设置模块的分步说明,请参阅随包装附带的快速指引。", + "share_app_analytics": "共享应用程序分析数据", + "share_app_analytics_description": "通过自动发送匿名诊断和使用数据来帮助改进此产品。", + "share_display_usage_description": "关于工作站触摸屏的交互数据。", + "share_logs_with_opentrons": "共享工作站日志", + "share_logs_with_opentrons_description": "通过自动发送匿名的工作站日志来帮助改进此产品。这些日志用于解决工作站问题和发现错误趋势。", + "show_labware_offset_snippets_description": "仅适用于需要在应用程序之外应用耗材校准数据的用户。启用后,在设置协议过程中可访问Jupyter Notebook和SSH的代码片段。", + "something_seems_wrong": "您的移液器可能有问题。退出设置并联系支持人员以获取帮助。", + "storage_limit_reached_description": "您的工作站已达到可存储的快速移液数量上限。在创建新的快速移液之前,您必须删除一个现有的快速移液。", + "these_are_advanced_settings": "这些是高级设置。请勿在没有支持团队帮助的情况下尝试调整这些设置。更改这些设置可能会影响您的移液器寿命。这些设置不会覆盖协议中定义的任何移液器设置。", + "update_requires_restarting_app": "更新需要重新启动应用程序。", + "update_robot_software_description": "绕过自动更新过程并手动更新工作站软件", + "update_robot_software_link": "启动软件更新页面", + "versions_sync": "了解更多有关保持应用程序和工作站软件同步的信息", + "view_latest_release_notes_at": "请联系支持人员以获取发布说明。", + "want_to_help_out": "想要帮助吗?", + "welcome_title": "欢迎!", + "why_use_lpc": "耗材位置校准旨在纠正微小的偏差,请不要使用该校准方式来进行较大的位置补偿调整。如果需要进行较大调整的耗材数据校准可能表明工作站的校准存在问题。" +} diff --git a/app/src/assets/localization/zh/app_settings.json b/app/src/assets/localization/zh/app_settings.json new file mode 100644 index 00000000000..3405d5edbfd --- /dev/null +++ b/app/src/assets/localization/zh/app_settings.json @@ -0,0 +1,103 @@ +{ + "__dev_internal__enableLabwareCreator": "启用应用实验耗材创建器", + "__dev_internal__enableLocalization": "Enable App Localization", + "__dev_internal__forceHttpPolling": "强制轮询所有网络请求,而不是使用MQTT", + "__dev_internal__enableRunNotes": "在协议运行期间显示备注", + "__dev_internal__protocolStats": "协议统计", + "__dev_internal__protocolTimeline": "协议时间线", + "add_folder_button": "添加实验耗材源文件夹", + "add_ip_button": "添加", + "add_ip_error": "输入IP地址或主机名", + "add_ip_hostname": "添加IP地址或主机名", + "add_override_path": "添加覆盖路径", + "additional_folder_description": "如果要指定一个文件夹来手动管理自定义实验耗材文件,可以在此处添加目录。", + "additional_folder_location": "附加源文件夹", + "additional_labware_folder_title": "其他定制实验耗材源文件夹", + "advanced": "高级", + "app_changes": "应用程序更改于", + "app_settings": "应用设置", + "bug_fixes": "错误修复", + "cal_block": "始终使用校准块进行校准", + "change_folder_button": "更改实验耗材源文件夹", + "channel": "通道", + "clear_confirm": "清除不可用的工作站", + "clear_robots_button": "清除不可用工作站列表", + "clear_robots_description": "清除设备页面上不可用工作站的列表。此操作无法撤消。", + "clear_unavail_robots": "清除不可用工作站", + "clear_unavailable_robots": "清除不可用的工作站?", + "clearing_cannot_be_undone": "在设备页面清除不可用工作站列表的操作无法撤销。", + "close": "关闭", + "connect_ip": "通过IP地址连接到工作站", + "connect_ip_button": "完成", + "connect_ip_link": "了解更多关于手动连接工作站的信息", + "discovery_timeout": "发现超时。", + "download_update": "正在下载更新...", + "enable_dev_tools": "开发者工具", + "enable_dev_tools_description": "启用此设置将在应用启动时打开开发者工具,打开额外的日志记录并访问功能标志。", + "error_boundary_desktop_app_description": "您需要重新加载应用程序。出现以下错误信息,请联系技术支持:", + "error_boundary_title": "发生未知错误", + "feature_flags": "功能标志", + "general": "通用", + "heater_shaker_attach_description": "在进行测试振荡功能或在协议中使用热震荡模块功能之前,显示正确连接热震荡模块的提醒。", + "heater_shaker_attach_visible": "确认热震荡模块连接", + "how_to_restore": "如何恢复过往的软件版本", + "installing_update": "正在安装更新...", + "ip_available": "可用", + "ip_description_first": "输入IP地址或主机名以连接到工作站。", + "manage_versions": "工作站版本和应用程序软件版本必须一致。通过工作站设置 > 高级查看工作站软件版本。", + "new_features": "新功能", + "no_folder": "未指定其他源文件夹", + "no_specified_folder": "未指定路径", + "no_unavail_robots_to_clear": "无待清除的不可用工作站", + "not_found": "未找到", + "opt_in": "加入", + "opt_in_description": "自动向我们发送匿名诊断和使用数据。我们仅将这些信息用于改进我们的产品。", + "opt_out": "退出", + "ot2_advanced_settings": "OT-2高级设置", + "override_path": "覆盖路径", + "override_path_to_python": "覆盖Python路径", + "prevent_robot_caching": "阻止工作站进行缓存", + "prevent_robot_caching_description": "启用此功能后,应用程序将立即清除不可用的工作站,并且不会记住它们。在网络上有许多工作站的情况下,防止缓存可能会提高网络性能,但代价是在应用程序启动时工作站发现的速度变慢且可靠性降低。", + "privacy": "隐私", + "problem_during_update": "此次更新耗时较长。", + "prompt": "始终显示选择校准块或垃圾桶的提示", + "release_notes": "发行说明", + "reload_app": "重新加载应用程序", + "remind_later": "稍后提醒我", + "reset_to_default": "恢复默认设置", + "restart_touchscreen": "重启触摸屏", + "restarting_app": "下载完成,正在重启应用程序...", + "restore_previous": "查看如何恢复过往软件版本", + "searching": "正在搜索30秒", + "setup_connection": "设置连接", + "share_display_usage": "分享屏幕使用情况", + "share_robot_logs": "分享工作站日志", + "share_robot_logs_description": "工作站执行的操作数据,如运行协议。", + "show_labware_offset_snippets": "显示实验耗材校准数据代码片段", + "software_update_available": "有可用的软件更新", + "software_version": "应用程序软件版本", + "successfully_deleted_unavail_robots": "成功删除不可用的工作站", + "tip_length_cal_method": "吸头长度校准方法", + "trash_bin": "始终使用垃圾桶进行校准", + "try_restarting_the_update": "尝试重新启动更新。", + "turn_off_updates": "在应用程序设置中关闭软件更新通知。", + "up_to_date": "最新", + "update_alerts": "软件更新提醒", + "update_app_now": "立即更新应用程序", + "update_available": "更新可用", + "update_channel": "更新通道", + "update_description": "稳定版将接收最新的稳定版本。测试版将允许您在稳定版发布之前尝试正在开发的新功能,但这些功能尚未完成测试。", + "usb_to_ethernet_adapter_description": "描述", + "usb_to_ethernet_adapter_driver_version": "驱动程序版本", + "usb_to_ethernet_adapter_info": "USB-to-Ethernet适配器信息", + "usb_to_ethernet_adapter_info_description": "OT-2内置了一些USB-to-Ethernet适配器。如果您使用的OT-2有这个适配器,当您建立有线连接时,它将添加到您的计算机设备列表中。如果您使用的是 Realtek 适配器,驱动程序必须是最新的。", + "usb_to_ethernet_adapter_link": "前往Realtek.com", + "usb_to_ethernet_adapter_manufacturer": "制造商", + "usb_to_ethernet_adapter_no_driver_version": "未知", + "usb_to_ethernet_adapter_toast_message": "Realtek USB-to-Ethernet适配器驱动程序有更新", + "usb_to_ethernet_not_connected": "没有连接USB-to-Ethernet适配器", + "usb_to_ethernet_unknown_manufacturer": "未知制造商", + "usb_to_ethernet_unknown_product": "未知适配器", + "view_software_update": "查看软件更新", + "view_update": "查看更新" +} diff --git a/app/src/assets/localization/zh/branded.json b/app/src/assets/localization/zh/branded.json new file mode 100644 index 00000000000..c38888398f1 --- /dev/null +++ b/app/src/assets/localization/zh/branded.json @@ -0,0 +1,80 @@ +{ + "a_robot_software_update_is_available": "需要更新工作站软件才能使用此版本的Opentrons应用程序运行协议。转到工作站", + "about_flex_gripper": "关于Flex转板抓手", + "alternative_security_types_description": "Opentrons应用程序支持将Flex连接到各种企业接入点。通过USB连接并在应用程序中完成设置。", + "attach_a_pipette_for_quick_transfer": "要创建快速移液,您需要将移液器安装到您的Opentrons Flex上。", + "attach_a_pipette": "将移液器连接到Flex", + "calibration_block_description": "这个金属块是一个特制的工具,完美适配您的甲板,有助于校准。如果您没有校准块,请发送电子邮件至support@opentrons.com,以便我们寄送一个给您。在您提供的信息中,请确保包括您的姓名、公司或机构名称和寄送地址。在等待校准块到达过程中,您可以暂时利用工作站里垃圾桶上的平面进行校准。", + "calibration_on_opentrons_tips_is_important": "使用上述Opentrons吸头和吸头盒进行校准非常重要,因为工作站的准确性是基于这些吸头的已知尺寸来确定的。", + "choose_what_data_to_share": "选择要与Opentrons共享的数据。", + "computer_in_app_is_controlling_robot": "计算机当前正在通过Opentrons应用程序控制此工作站。", + "confirm_terminate": "这将立即停止计算机上开始的活动。您或其他用户可能会丢失进度或在Opentrons应用程序中看到报错", + "connect_and_screw_in_gripper": "连接并固定Flex转板抓手", + "connect_via_usb_description_3": "3. 在计算机上启动Opentrons应用程序以继续。", + "connection_description_usb": "直接连接到计算机(运行Opentrons应用程序)。", + "connection_lost_description": "Opentrons应用程序现在无法与此工作站通信。请仔细检查与工作站的USB或Wi-Fi连接,然后尝试重新连接。", + "contact_information": "从Opentrons应用程序下载工作站日志,并将其发送到support@opentrons.com寻求帮助。", + "contact_support_for_connection_help": "如果以上方法都无法解决问题,请联系Opentrons支持人员寻求帮助(通过此应用程序中的问号链接,或发送电子邮件至{{support_email}}。)", + "deck_fixture_setup_modal_bottom_description": "有关安装不同类型固定装置的详细信息,请扫描二维码或在support.opentrons.com上搜索“deck configuration”", + "delete_protocol_from_app": "删除协议,针对错误进行修改,然后从Opentrons应用程序将协议重新发送到此工作站。", + "delete_transfer_from_app": "删除快速移液,修改并解决错误,在Flex显示屏幕上重新创建此移液。", + "error_boundary_description": "您需要重新启动触摸屏。然后从Opentrons应用程序下载工作站日志并将其发送到support@opentrons.com寻求帮助。", + "estop_pressed_description": "首先,安全清理甲板上的任何实验耗材或洒出液体。然后,顺时针旋转急停开关。最后,让Flex将龙门架移动到其原位。", + "find_your_robot": "在Opentrons应用程序中找到您的工作站以安装软件更新。", + "firmware_update_download_logs": "从Opentrons应用程序下载工作站日志并将其发送到support@opentrons.com寻求帮助。", + "general_error_message": "如果您一直收到此消息,请尝试重新启动您的应用程序和工作站。如果这不能解决问题,请与Opentrons支持人员联系。", + "gripper_still_attached": "Flex转板抓手仍处于连接状态", + "gripper_successfully_attached_and_calibrated": "Flex转板抓手已成功连接并校准", + "gripper_successfully_calibrated": "Flex转板抓手已成功校准", + "gripper_successfully_detached": "Flex转板抓手已成功卸下", + "gripper": "Flex转板抓手", + "help_us_improve_send_error_report": "通过向{{support_email}}发送错误报告,帮助我们改进您的使用体验", + "ip_description_second": "Opentrons建议您联系网络管理员,为工作站分配静态IP地址。", + "learn_uninstalling": "了解更多有关卸载Opentrons应用程序的信息", + "loosen_screws_and_detach": "松开螺丝并卸下Flex转板抓手", + "modal_instructions": "有关设置模块的分步说明,请参阅随包装附带的快速指引。您也可以单击下面的链接或扫描二维码访问Opentrons帮助中心的模块部分。", + "module_calibration_failed": "模块校准失败。请确保校准适配器正确放置在模块上,然后重试。如果仍然有问题,请与Opentrons支持人员联系。{{error}}", + "module_calibration_get_started": "开始前,请从工作台上移除实验耗材并清理工作区,以便于校准。还需准备好右侧显示的所需设备。校准适配器随模块一起提供。移液器探头随Flex移液器一起提供。", + "module_error_contact_support": "尝试关闭模块电源,然后再打开。如果报错仍然存在,请与Opentrons支持人员联系。", + "network_setup_menu_description": "您将使用此连接来运行软件更新,并将协议加载到您的Opentrons Flex上。", + "new_robot_instructions": "设置新的Flex时,请遵循触摸屏上的指示。有关更多信息,请参阅您的工作站快速入门指南。", + "oem_mode_description": "启用OEM模式,以从Flex触摸屏中移除Opentrons的所有信息。", + "opentrons_app_successfully_updated": "Opentrons应用程序已成功更新。.", + "opentrons_app_update": "Opentrons应用程序更新", + "opentrons_app_update_available": "Opentrons应用程序可更新", + "opentrons_app_update_available_variation": "有可用的Opentrons应用程序更新。", + "opentrons_app_will_use_interpreter": "如果指定,Opentrons应用程序将在此路径使用Python解释器,而不是默认绑定的Python解释器。", + "opentrons_cares_about_privacy": "Opentrons关心您的隐私。我们匿名化所有数据,仅用于改进我们的产品。", + "opentrons_def": "已验证的Opentrons数据", + "opentrons_flex_quickstart_guide": "Opentrons Flex 快速入门指南", + "opentrons_labware_def": "已验证的Opentrons实验耗材数据", + "opentrons_tip_rack_name": "opentrons", + "opentrons_tip_racks_recommended": "建议使用Opentrons吸头盒。其他吸头盒无法保证精度。", + "previous_releases": "查看过往的Opentrons版本", + "receive_alert": "当Opentrons软件更新可用时接收提醒。", + "restore_description": "Opentrons不建议恢复到过往软件版本,但您可以访问下方的过往版本。为了获得最佳效果,请在安装过往版本之前卸载现有应用程序并删除其配置文件。", + "robot_server_version_ot3_description": "Opentrons Flex软件包括工作站服务器和触摸屏显示界面。", + "robot_software_update_required": "需要更新工作站软件才能运行此版本Opentrons应用程序的协议。", + "run_failed_modal_description_desktop": "下载运行日志并将其发送到support@opentrons.com寻求帮助。", + "secure_labware_explanation_magnetic_module": "Opentrons建议通过调整模块顶部的黑色固定支架,确保您的实验耗材固定在磁性模块上。 您的模块提供了两种尺寸的板固定支架:标准和深孔。这些固定支架可以通过拧开模块的拇指螺钉(前面的银色旋钮)来移除和更换。", + "secure_labware_explanation_thermocycler": "Opentrons建议通过关闭热循环仪模块的闩锁将您的实验耗材固定在其上。这样可以确保水平以及板位置的准确,从而获得最佳结果。", + "send_a_protocol_to_store": "从Opentrons应用程序发送协议以开始。", + "setup_instructions_description": "有关设置模块的分步说明,请参阅随包装附带的快速指引,或扫描二维码访问Opentrons帮助中心的模块部分。", + "share_app_analytics": "向Opentrons共享应用分析", + "share_app_analytics_description": "通过自动发送匿名诊断和使用数据,帮助Opentrons改进产品和服务。", + "share_display_usage_description": "关于您如何在Flex上与触摸屏交互的数据。", + "share_logs_with_opentrons": "向opentrons共享工作站日志", + "share_logs_with_opentrons_description": "通过自动发送匿名的工作站日志,帮助Opentrons改进产品和服务。Opentrons使用这些日志来解决工作站问题并发现错误趋势。", + "show_labware_offset_snippets_description": "仅适用于需要在Opentrons应用程序之外应用耗材校准数据的用户。启用后,在设置协议过程中可访问Jupyter Notebook和SSH的代码片段。", + "something_seems_wrong": "您的移液器可能有问题。退出设置并联系Opentrons支持人员以获取帮助。", + "storage_limit_reached_description": "您的 Opentrons Flex 已达到可存储的快速移液数量上限。在创建新的快速移液之前,您必须删除一个现有的快速移液。", + "these_are_advanced_settings": "这些是高级设置。请勿在没有Opentrons支持团队帮助的情况下尝试调整这些设置。更改这些设置可能会影响您的移液器寿命。这些设置不会覆盖协议中定义的任何移液器设置。", + "update_requires_restarting_app": "更新需要重新启动Opentrons应用程序。", + "update_robot_software_description": "绕过Opentrons应用程序自动更新过程并手动更新工作站软件", + "update_robot_software_link": "启动Opentrons软件更新页面", + "versions_sync": "了解更多有关保持Opentrons应用程序和工作站软件同步的信息", + "view_latest_release_notes_at": "在{{url}}上查看最新发布说明", + "want_to_help_out": "想帮助Opentrons吗?", + "welcome_title": "欢迎使用您的Opentrons Flex!", + "why_use_lpc": "耗材位置校准旨在纠正微小的偏差,Opentrons不建议使用该校准方式来进行较大的位置补偿调整。如果需要进行较大调整的耗材数据校准可能表明工作站的校准存在问题。" +} diff --git a/app/src/assets/localization/zh/change_pipette.json b/app/src/assets/localization/zh/change_pipette.json new file mode 100644 index 00000000000..9818fd56f85 --- /dev/null +++ b/app/src/assets/localization/zh/change_pipette.json @@ -0,0 +1,52 @@ +{ + "are_you_sure_exit": "您确定要在{{direction}}移液器之前退出吗?", + "attach_name_pipette": "安装一个{{pipette}}移液器", + "attach_pipette_type": "安装一个{{pipetteName}}移液器", + "attach_pipette": "安装一个移液器", + "attach_the_pipette": "

连接移液器

推入白色连接器,直到感觉它插入移液器。", + "attached_pipette_does_not_match": "连接的{{name}}与您最初选择的{{pipette}}不匹配。", + "attaching": "正在连接", + "calibrate_pipette_offset": "校准移液器偏移", + "cancel_attachment": "取消连接", + "check_pipette_is_unplugged": "再次检查以确保移液器连接线已拔下并完全从工作站上卸下。", + "choose_pipette": "选择一个要连接的移液器", + "confirm_attachment": "确认连接", + "confirm_detachment": "确认卸下", + "confirm_level": "确认水平", + "confirming_attachment": "正在确认连接", + "confirming_detachment": "正在确认拆卸", + "continue": "继续", + "detach_pipette_from_mount": "从{{mount}}安装支架上卸下移液器", + "detach_pipette": "从{{mount}}安装支架上卸下{{pipette}}", + "detach_try_again": "卸下并重试", + "detach": "卸下移液器", + "detaching": "正在卸下", + "get_started": "开始", + "go_back": "返回", + "homing": "工作站正在归位", + "incorrect_pipette_attached": "连接了错误的移液器", + "insert_screws": "

插入螺钉

使用 2.5 毫米螺丝刀,插入移液器背面的三个螺丝。", + "leave_attached": "保持连接", + "level_the_pipette": "

调整移液器水平

用您的手,轻轻地、慢慢地将移液器向上推。将移液器向下拉,使 8 个喷嘴都接触到校准块的表面。按住移液器的同时,拧紧三个螺钉。", + "loosen_the_screws": "

松开螺丝

用一把 2.5 毫米的螺丝刀,拧松移液器背面的三颗螺丝。.", + "mount": "{{mount}}安装支架", + "moving_gantry": "移动龙门架...", + "pipette_attached": "移液器已连接!", + "pipette_is_ready_to_use": "{{pipette}}现在可以使用。", + "pipette_movement": "{{mount}}移液器托架正在向{{location}}移动。", + "pipette_setup": "移液器设置", + "pipette_still_detected": "仍然检测到移液器", + "press_white_connector": "确保将白色连接器按到底,并感觉到它与移液器连接。", + "progress_will_be_lost": "进度将丢失", + "recheck_connection": "重新检查连接", + "remove_labware_before_start": "

开始之前

开始前,请从平台上取下所有实验耗材,并从移液器上取下所有吸头。龙门将移动到工作站前端.", + "remove_pipette": "

卸下移液器

抓紧移液器,以免其掉落。拉动白色连接器,断开移液器与工作站的连接。", + "successfully_detached_pipette": "成功卸下移液器!", + "tighten_screws_multi": "从 1 号螺丝开始,以顺时针方向轻轻地拧紧螺丝。您将在后面的步骤中完全拧紧。", + "tighten_screws_single": "从第1号螺丝开始,顺时针方向拧紧螺丝。", + "to_front_left": "到左前方", + "to_front_right": "到右前方", + "unable_to_detect_pipette": "无法检测到{{pipette}}", + "up": "上", + "use_attached_pipette": "使用连接的移液器" +} diff --git a/app/src/assets/localization/zh/device_details.json b/app/src/assets/localization/zh/device_details.json new file mode 100644 index 00000000000..a19e61a365b --- /dev/null +++ b/app/src/assets/localization/zh/device_details.json @@ -0,0 +1,197 @@ +{ + "about_gripper": "关于转板抓手", + "about_module": "关于{{name}}", + "about_pipette_name": "关于{{name}}移液器", + "about_pipette": "关于移液器", + "abs_reader_status": "吸光度读板器状态", + "add_fixture_description": "将此硬件添加至甲板配置。它在协议分析期间将会被引用。", + "add_to_slot": "添加到板位{{slotName}}", + "add": "添加", + "an_error_occurred_while_updating_module": "更新{{moduleName}}时出现错误,请重试。", + "an_error_occurred_while_updating_please_try_again": "更新移液器设置时出错,请重试。", + "an_error_occurred_while_updating": "更新移液器设置时发生错误。", + "attach_gripper": "安装转板抓手", + "attach_pipette": "安装移液器", + "bad_run": "无法加载运行", + "both_mounts": "两侧支架", + "bundle_firmware_file_not_found": "未找到类型为{{module}}的模块固件包文件。", + "calibrate_gripper": "校准转板抓手", + "calibrate_now": "立即校准", + "calibrate_pipette_offset": "校准移液器数据", + "calibrate_pipette": "校准移液器", + "calibration_needed_without_link": "需要校准。", + "calibration_needed": "需要校准。 立即校准", + "canceled": "已取消", + "changes_will_be_lost_description": "确定不保存甲板配置直接退出而吗?", + "changes_will_be_lost": "更改将丢失", + "choose_protocol_to_run": "选择在{{name}}上运行的协议", + "close_lid": "关闭上盖", + "completed": "已完成", + "confirm": "确认", + "continue_editing": "继续编辑", + "controls": "控制", + "csv_required_for_analysis": "分析需要CSV文件。请在运行设置时添加。", + "current_speed": "当前:{{speed}}rpm", + "current_temp": "当前:{{temp}}°C", + "current_version": "当前版本", + "deck_cal_missing": "缺少移液器校准数据,请先校准甲板。", + "deck_configuration_is_not_available_when_robot_is_busy": "工作站忙碌时,甲板配置不可用", + "deck_configuration_is_not_available_when_run_is_in_progress": "工作站运行时,甲板配置不可用", + "deck_configuration": "甲板配置", + "deck_fixture_setup_instructions": "甲板配置安装说明", + "deck_fixture_setup_modal_bottom_description_desktop": "针对不同类型的配置,扫描二维码或访问下方链接获取详细说明。", + "deck_fixture_setup_modal_top_description": "首先,拧松并移除计划安装模组的甲板。然后放置模组,并进行固定。", + "deck_hardware": "甲板硬件", + "deck_slot": "甲板板位{{slot}}", + "delete_run": "删除协议运行记录", + "detach_gripper": "卸下转板抓手", + "detach_pipette": "卸下移液器", + "discard_changes": "放弃更改", + "disengaged": "不启用", + "download_run_log": "下载协议运行日志", + "drop_tips": "丢弃吸头", + "empty": "空", + "error_details": "错误详情", + "estop_disconnected": "急停断开。工作站运动已停止。", + "estop_disengaged": "急停解除,但工作站操作仍暂停中。", + "estop_pressed": "急停按钮被按下。工作站运动已停止。", + "failed": "失败", + "files": "文件", + "firmware_update_needed": "需要更新工作站固件。请在工作站的触摸屏上开始更新。", + "firmware_update_available": "固件更新可用。", + "firmware_update_failed": "未能更新模块固件", + "firmware_updated_successfully": "固件更新成功", + "firmware_update_occurring": "固件更新正在进行中...", + "fixture": "配置模组", + "have_not_run_description": "运行一些协议后,它们会在这里显示。", + "have_not_run": "无最近运行记录", + "heater": "加热器", + "height_ranges": "{{gen}}高度范围", + "hot_to_the_touch": "模块接触时很热", + "input_out_of_range": "输入超出范围", + "instrument_attached": "设备已连接", + "instruments_and_modules": "设备与模块", + "labware_bottom": "耗材底部", + "last_run_time": "最后一次运行{{number}}", + "left_right": "左右支架", + "left": "左侧", + "lights": "灯光", + "link_firmware_update": "查看固件更新", + "location_conflicts": "位置冲突", + "location": "位置", + "magdeck_gen1_height": "高度:{{height}}", + "magdeck_gen2_height": "高度:{{height}}毫米", + "max_engage_height": "最大可用高度", + "missing_fixture": "缺少{{num}}个配置", + "missing_fixtures_plural": "缺少{{count}}个配置", + "missing_hardware": "缺少硬件", + "missing_instrument": "缺少{{num}}个设备", + "missing_instruments_plural": "缺少{{count}}个设备", + "missing_module_plural": "缺少{{count}}个模块", + "missing_module": "缺少{{num}}个模块", + "module_actions_unavailable": "协议运行时模块操作不可用", + "module_calibration_required_no_pipette_attached": "需要模块校准。在运行模块校准前,请连接移液器。", + "module_calibration_required_no_pipette_calibrated": "需要模块校准。在校准模块前,请先校准移液器。", + "module_calibration_required_update_pipette_FW": "在进行必要的模块校准前,请先更新移液器固件。", + "module_calibration_required": "需要模块校准。", + "module_controls": "模块控制", + "module_error": "模块错误", + "module_name_error": "{{moduleName}}错误", + "module_status_range": "介于{{min}}至{{max}}{{unit}}之间", + "mount": "{{side}}安装座", + "na_speed": "目标速度: N/A", + "na_temp": "目标温度: N/A", + "no_deck_fixtures": "无甲板配置", + "no_protocol_runs": "暂无协议运行记录!", + "no_protocols_found": "未找到协议", + "no_recent_runs_description": "运行一些协议后,它们将显示在此处。", + "no_recent_runs": "无最近运行记录", + "num_units": "{{num}}毫米", + "offline_deck_configuration": "工作站必须连接网络才能查看甲板配置", + "offline_instruments_and_modules": "工作站必须连接网络才能查看已连接的设备和模块", + "offline_recent_protocol_runs": "工作站必须连接网络才能查看协议运行情况", + "open_lid": "打开上盖", + "overflow_menu_about": "关于模块", + "overflow_menu_deactivate_block": "停用模块", + "overflow_menu_deactivate_lid": "停用上盖", + "overflow_menu_deactivate_temp": "停用模块", + "overflow_menu_disengage": "模块下降", + "overflow_menu_engage": "设置模块高度", + "overflow_menu_lid_temp": "设置上盖温度", + "overflow_menu_mod_temp": "设置模块温度", + "overflow_menu_set_block_temp": "设置模块温度", + "pipette_cal_recommended": "建议进行移液器校准。", + "pipette_calibrations_differ": "所连接的移液器校准数据差异过大。正确校准后,这些数据应相近。", + "pipette_offset_calibration_needed": "需要进行移液器校准。", + "pipette_recalibration_recommended": "建议重新校准移液器", + "pipette_settings": "{{pipetteName}}设置", + "plunger_positions": "活塞位置", + "power_force": "功率 / 力量", + "protocol": "协议", + "protocol_analysis_failed": "协议APP分析失败。", + "protocol_analysis_stale": "协议分析已过期。", + "protocol_details_page_reanalyze": "前往协议详情页面重新分析。", + "ready_to_run": "运行工作准备完成", + "ready": "准备就绪", + "recalibrate_gripper": "重新校准转板抓手", + "recalibrate_now": "立即重新校准", + "recalibrate_pipette_offset": "重新校准移液器偏移", + "recalibrate_pipette": "重新校准移液器", + "recent_protocol_runs": "最近的协议运行", + "rerun_loading": "数据完全加载前,禁止协议重新运行", + "rerun_now": "立即重新运行协议", + "reset_all": "重置全部", + "reset_estop": "重置急停", + "resume_operation": "继续操作", + "right": "右侧", + "robot_control_not_available": "运行过程中某些工作站控制功能不可用", + "robot_initializing": "初始化中...", + "run_a_protocol": "运行协议", + "run_again": "再次运行", + "run_duration": "运行时长", + "run": "运行", + "select_options": "选择选项", + "serial_number": "序列号", + "set_block_temp": "设置温度", + "set_block_temperature": "设置模块温度", + "set_engage_height_and_enter_integer": "为此磁力模块设置启用高度。请输入一个介于{{lower}}和{{higher}}之间的整数。", + "set_engage_height_for_module": "为{{name}}设置启用高度", + "set_engage_height": "设置启用高度", + "set_lid_temperature": "设置上盖温度", + "set_shake_of_hs": "为此模块设置转速。", + "set_shake_speed": "设置震荡速度", + "set_status_heater_shaker": "为{{name}}设置{{part}}", + "set_target_temp_of_hs": "设置目标温度。此模块主动加热,但被动冷却至室温。", + "set_temp_or_shake": "设置{{part}}", + "set_temperature": "设置温度", + "setup_instructions": "设置说明", + "shake_speed": "震荡速度", + "shaker": "震荡仪", + "staging_area_slot": "暂存区板位", + "status": "状态", + "target_speed": "目标转速:{{speed}}rpm", + "target_temp": "目标温度:{{temp}}°C", + "tc_block": "模块", + "tc_lid": "上盖", + "tc_set_temperature_body": "预热或预冷您的热循环模块的{{part}}。请输入介于{{min}}°C 和{{max}}°C之间的一个整数。", + "tc_set_temperature": "为{{name}}设置温度{{part}}", + "tempdeck_slideout_body": "预热或冷却您的{{model}}。输入4°C至96°C之间的一个整数。", + "tempdeck_slideout_title": "为{{name}}设置温度", + "temperature": "温度", + "this_robot_will_restart_with_update": "此工作站需要重启以更新软件。重启会立即终止当前的运行或校准。您仍然要现在更新吗?", + "tip_pickup_drop": "吸头拾取/丢弃", + "to_run_protocol_go_to_protocols_page": "要在该工作站上运行协议,请在协议页面导入协议。", + "trash": "垃圾桶", + "update_now": "立即更新", + "updating_firmware": "正在更新固件...", + "usb_port_not_connected": "USB未连接", + "usb_port": "USB端口-{{port}}", + "version": "版本{{version}}", + "view_pipette_setting": "移液器设置", + "view_run_record": "查看协议运行记录", + "view": "查看", + "waste_chute": "外置垃圾槽", + "welcome_modal_description": "运行协议、管理设备及查看工作站状态的地方。", + "welcome_to_your_dashboard": "欢迎来到您的控制面板!", + "yes_update_now": "是的,现在更新" +} diff --git a/app/src/assets/localization/zh/device_settings.json b/app/src/assets/localization/zh/device_settings.json new file mode 100644 index 00000000000..ecd81c941dd --- /dev/null +++ b/app/src/assets/localization/zh/device_settings.json @@ -0,0 +1,325 @@ +{ + "about_advanced": "关于", + "about_calibration_description": "为了让工作站精确移动,您需要对其进行校准。位置校准分为三部分:甲板校准、移液器偏移校准和吸头长度校准。", + "about_calibration_description_ot3": "为了让工作站精确移动,您需要对其进行校准。移液器和转板抓手校准是一个自动化过程,使用校准探头或销钉。校准完成后,您可以将校准数据以JSON文件的形式保存到计算机中。", + "about_calibration_title": "关于校准", + "advanced": "高级", + "alpha_description": "警告:alpha版本功能完整,但可能包含重大错误。", + "alternative_security_types": "可选的安全类型", + "app_change_in": "应用程序在{{version}}中的更改", + "apply_historic_offsets": "应用耗材偏移校准数据", + "are_you_sure_you_want_to_disconnect": "您确定要断开与{{ssid}}的连接吗?", + "attach_a_pipette_before_calibrating": "在执行校准之前,请安装移液器", + "boot_scripts": "启动脚本", + "both": "两者", + "browse_file_system": "浏览文件系统", + "bug_fixes": "错误修复", + "calibrate_deck": "校准甲板", + "calibrate_deck_description": "适用于没有在甲板上蚀刻十字的2019年之前的工作站。", + "calibrate_deck_to_dots": "根据校准点校准甲板", + "calibrate_gripper": "校准转板抓手", + "calibrate_module": "校准模块", + "calibrate_now": "立即校准", + "calibrate_pipette": "校准移液器偏移", + "calibration": "校准", + "calibration_health_check_description": "检查关键校准点的精度,无需重新校准工作站。", + "calibration_health_check_title": "校准运行状况检查", + "change_network": "更改网络", + "characters_max": "最多17个字符", + "check_for_updates": "检查更新", + "checking_for_updates": "正在检查更新", + "choose": "选择...", + "choose_file": "选择文件", + "choose_network_type": "选择网络类型", + "choose_reset_settings": "选择重置设置", + "clear_all_data": "清除所有数据", + "clear_all_stored_data": "清除所有存储的数据", + "clear_all_stored_data_description": "清除校准、协议和所有设置,保留工作站名称和网络设置。", + "clear_calibration_data": "清除校准数据", + "clear_data_and_restart_robot": "清除数据并重新启动工作站", + "clear_individual_data": "清除单个数据", + "clear_option_authorized_keys": "清除SSH公钥", + "clear_option_boot_scripts": "清除自定义启动脚本", + "clear_option_boot_scripts_description": "清除修改工作站开机行为的脚本", + "clear_option_deck_calibration": "清除甲板校准", + "clear_option_gripper_calibration": "清除转板抓手校准", + "clear_option_gripper_offset_calibrations": "清除转板抓手校准", + "clear_option_module_calibration": "清除模块校准", + "clear_option_pipette_calibrations": "清除移液器校准", + "clear_option_pipette_offset_calibrations": "清除移液器偏移校准", + "clear_option_runs_history": "清除协议运行历史", + "clear_option_runs_history_subtext": "清除所有协议的过往运行信息。点击并应用", + "clear_option_tip_length_calibrations": "清除吸头长度校准", + "cancel_software_update": "取消软件更新", + "complete_and_restart_robot": "完成并重新启动工作站", + "confirm_device_reset_description": "这将永久删除所有协议、校准和其他数据。您需要重新进行初始设置才能再次使用工作站。", + "confirm_device_reset_heading": "您确定要重置您的设备吗?", + "connect": "连接", + "connect_the_estop_to_continue": "连接紧急停止按钮以继续", + "connect_to_wifi_network": "连接到Wi-Fi网络", + "connect_via": "通过{{type}}连接", + "connect_via_usb_description_1": "1. 将 USB A-to-B 连接线连接到工作站的 USB-B 端口。", + "connect_via_usb_description_2": "2. 将电缆连接到计算机上的一个空闲USB端口。", + "connected": "已连接", + "connected_network": "已连接网络", + "connected_to_ssid": "已连接到{{ssid}}", + "connected_via": "通过{{networkInterface}}连接", + "connecting_to": "正在连接到{{ssid}}...", + "connection_description_ethernet": "连接到您实验室的有线网络。", + "connection_description_wifi": "在您的实验室中找到一个网络,或者输入您自己的网络。", + "connection_to_robot_lost": "与工作站的连接中断", + "deck_calibration_description": "新工作站或搬迁工作站后需要校准甲板。重新校准甲板后也将需要重新校准移液器偏移。", + "deck_calibration_missing": "缺少甲板校准", + "deck_calibration_missing_no_pipette": "缺少甲板校准。安装移液器以执行甲板校准。", + "deck_calibration_modal_description": "若同时需要校准甲板及校准移液器偏移,不建议在校准甲板之前校准移液器偏移,因为校准甲板会清除所有其他校准数据。", + "deck_calibration_modal_pipette_description": "您想要继续进行移液器偏移校准吗?", + "deck_calibration_modal_title": "您确定要进行校准吗?", + "deck_calibration_recommended": "建议进行甲板校准", + "deck_calibration_title": "甲板校准", + "dev_tools_description": "访问额外的日志记录和功能标志。", + "device_reset": "设备重置", + "device_reset_description": "将耗材校准、启动脚本和/或工作站校准重置为出厂设置。", + "device_reset_slideout_description": "选择单独的设置以仅清除特定的数据类型。", + "device_resets_cannot_be_undone": "重置无法撤销", + "release_notes": "发行说明", + "directly_connected_to_this_computer": "直接连接到这台计算机。", + "disconnect": "断开连接", + "disconnect_from_ssid": "断开与{{ssid}}的连接", + "disconnect_from_wifi": "断开Wi-Fi连接", + "disconnect_from_wifi_network_failure": "您的工作站无法断开与Wi-Fi网络{{ssid}}的连接。", + "disconnect_from_wifi_network_success": "您的工作站已成功断开与Wi-Fi网络的连接。", + "disconnected_from_wifi": "已断开Wi-Fi连接", + "disconnecting_from_wifi_network": "正在断开与Wi-Fi网络{{ssid}}的连接", + "disengaged": "已解除", + "display_brightness": "屏幕亮度", + "display_led_lights": "状态LED灯", + "display_led_lights_description": "控制工作站前部的指示灯条。", + "display_sleep_settings": "屏幕睡眠设置", + "do_not_turn_off": "这可能需要最多{{minutes}}分钟。请不要关闭工作站。", + "done": "完成", + "download": "下载", + "download_calibration_data": "下载校准日志", + "download_error": "下载错误日志", + "download_logs": "下载日志", + "downloading_logs": "正在下载日志...", + "downloading_software": "正在下载软件...", + "downloading_update": "正在下载更新...", + "e_stop_connected": "紧急停止按钮成功连接", + "e_stop_not_connected": "将紧急停止按钮连接到工作站背面的端口。", + "enable_status_light": "启用状态灯", + "enable_status_light_description": "打开或关闭工作站前部的指示LED灯条。", + "engaged": "已连接", + "enter_factory_password": "输入工厂密码", + "enter_network_name": "输入网络名称", + "enter_password": "输入密码", + "estop": "紧急停止按钮", + "estop_disengaged": "紧急停止按钮已解除", + "estop_engaged": "紧急停止按钮已连接", + "estop_missing": "缺少紧急停止按钮", + "estop_missing_description": "您的紧急停止按钮可能已损坏或脱落。{{robotName}}与紧急停止按钮失去连接,因此取消了协议。连接一个功能正常的紧急停止按钮以继续。", + "estop_pressed": "紧急停止按钮被按下", + "ethernet": "以太网", + "ethernet_connection_description": "将以太网线从工作站背面接口连接到网络交换机或集线器。", + "exit": "退出", + "factory_mode": "工厂模式", + "factory_reset": "工厂重置", + "factory_reset_description": "重置所有设置。在再次使用工作站之前,您必须重新进行初始设置。", + "factory_reset_modal_description": "这些数据以后无法检索。", + "factory_resets_cannot_be_undone": "工厂重置无法撤销。", + "failed_to_connect_to_ssid": "无法连接到{{ssid}}", + "feature_flags": "功能标志", + "finish_setup": "完成设置", + "firmware_version": "固件版本", + "fully_calibrate_before_checking_health": "在检查校准健康之前,请完全校准您的工作站", + "gantry_homing": "重启时归位龙门架", + "gantry_homing_description": "沿Z轴归位龙门架。", + "go_to_advanced_settings": "转到高级应用设置", + "gripper_calibration_description": "使用金属销来确定转板抓手相对于甲板槽上的精密切割方格的确切位置。", + "gripper_calibration_title": "转板抓手校准", + "gripper_serial": "转板抓手序列号", + "health_check": "检查健康状态", + "hide": "隐藏", + "historic_offsets_description": "在设置协议时使用存储的数据。", + "incorrect_password_for_ssid": "哎呀!{{ssid}}的密码不正确", + "install_e_stop": "安装紧急停止按钮", + "installing_software": "正在安装软件...", + "installing_update": "正在安装更新...", + "invalid_password": "无效密码", + "ip_address": "IP地址", + "join_other_network": "加入其他网络", + "join_other_network_error_message": "长度必须为2-32个字符", + "jupyter_notebook": "Jupyter Notebook", + "jupyter_notebook_description": "在网页浏览器中打开运行在此工作站上的Jupyter Notebook。这是一个实验性功能。", + "jupyter_notebook_link": "了解更多关于使用Jupyter Notebook的信息", + "last_calibrated": "最后校准时间:{{date}}", + "last_calibrated_label": "最后校准", + "launch_jupyter_notebook": "启动Jupyter Notebook", + "legacy_settings": "遗留设置", + "mac_address": "MAC地址", + "manage_oem_settings": "管理OEM设置", + "minutes": "{{minute}}分钟", + "missing_calibration": "缺少校准", + "model_and_serial": "移液器型号和序列号", + "module": "模块", + "module_calibration": "模块校准", + "module_calibration_description": "模块校准使用移液器和连接的探头来确定模块相对于甲板的确切位置。", + "mount": "安装", + "name_love_it": "{{name}},喜欢它!", + "name_rule_description": "输入最多17个字符(仅限字母和数字)", + "name_rule_error_exist": "哎呀!名称已被使用。选择一个不同的名称。", + "name_rule_error_name_length": "哎呀!工作站名称必须遵循字符计数和限制。", + "name_rule_error_too_short": "哎呀!太短了。工作站名称至少需要1个字符。", + "name_your_robot": "给您的工作站起个名字", + "name_your_robot_description": "别担心,您可以在设置中随时更改这个名称。", + "need_another_security_type": "需要另一种安全类型吗?", + "network_name": "网络名称", + "network_settings": "网络设置", + "networking": "网络连接", + "never": "从不", + "new_features": "新功能", + "next_step": "下一步", + "no_connection_found": "未找到连接", + "no_gripper_attached": "未连接转板抓手", + "no_modules_attached": "未连接模块", + "no_network_found": "未找到网络", + "no_pipette_attached": "未连接移液器", + "none_description": "不推荐", + "not_calibrated": "尚未校准", + "not_calibrated_short": "未校准", + "not_connected": "未连接", + "not_connected_via_ethernet": "未通过以太网连接", + "not_connected_via_usb": "未通过USB连接", + "not_connected_via_wifi": "未通过Wi-Fi连接", + "not_connected_via_wired_usb": "未通过有线USB连接", + "not_now": "不是现在", + "oem_mode": "OEM模式", + "off": "关闭", + "one_hour": "1小时", + "on": "开启", + "other_networks": "其他网络", + "password": "密码", + "password_error_message": "至少需要8个字符", + "pause_protocol": "当工作站前门打开时暂停协议", + "pause_protocol_description": "启用后,在运行过程中打开工作站前门,工作站会在完成当前动作后暂停。", + "pipette_calibrations_description": "使用校准探头来确定移液器相对于甲板槽上的精密切割方格的确切位置。", + "pipette_calibrations_title": "移液器校准", + "pipette_offset_calibration": "移液器偏移校准", + "pipette_offset_calibration_missing": "缺少移液器偏移校准", + "pipette_offset_calibration_recommended": "建议进行移液器偏移校准", + "pipette_offset_calibrations_history": "查看所有移液器偏移校准历史", + "pipette_offset_calibrations_title": "移液器偏移校准", + "privacy": "隐私", + "problem_during_update": "此次更新耗时比平常要长。", + "proceed_without_updating": "跳过更新以继续", + "protocol_run_history": "协议运行历史", + "recalibrate_deck": "重新校准甲板", + "recalibrate_gripper": "重新校准转板抓手", + "recalibrate_module": "重新校准模块", + "recalibrate_now": "立即重新校准", + "recalibrate_pipette": "重新校准移液器偏移数据", + "recalibrate_tip_and_pipette": "重新校准吸头长度和移液器偏移量", + "recalibration_recommended": "建议重新校准", + "reinstall": "重新安装", + "remind_me_later": "稍后提醒我", + "rename_robot": "重命名工作站", + "rename_robot_input_error": "哎呀!工作站名称必须遵循字符计数和限制。", + "rename_robot_input_limitation_detail": "请输入最多17个字符:字母和数字。", + "rename_robot_prefer_usb_connection": "为确保工作站名称更改的可靠性,请通过USB连接。", + "rename_robot_title": "重命名工作站", + "requires_restarting_the_robot": "更新工作站软件需要重启工作站", + "reset_to_factory_settings": "重置为出厂设置?", + "resets_cannot_be_undone": "重置操作无法撤销", + "restart_now": "现在重启?", + "restart_robot_confirmation_description": "重启{{robotName}}将需要几分钟时间。", + "restart_taking_too_long": "{{robotName}}重启所需时间超出预期。请检查其设置页面中的“高级”选项卡,以确认是否已成功更新。如果工作站无响应,请手动重启。", + "restarting_robot": "安装完成,工作站正在重启...", + "resume_robot_operations": "恢复工作站操作", + "returns_your_device_to_new_state": "这将使您的设备恢复到新的状态。", + "robot_busy_protocol": "当协议正在运行时,此工作站无法更新", + "robot_calibration_data": "工作站校准数据", + "robot_initializing": "正在初始化工作站...", + "robot_name": "工作站名称", + "robot_operating_update_available": "工作站操作系统更新可用", + "robot_serial_number": "工作站序列号", + "robot_server_version": "工作站服务器版本", + "robot_settings": "工作站设置", + "robot_settings_advanced_unknown": "未知", + "robot_successfully_connected": "工作站已成功连接到{{networkName}}。", + "robot_system_version": "工作站系统版本", + "robot_system_version_available": "工作站系统版本{{releaseVersion}}可用", + "robot_up_to_date": "工作站已更新至最新版本", + "robot_up_to_date_description": "您的工作站似乎已经是最新版本,但如果您遇到问题,可以重新应用最新更新。", + "robot_update_available": "工作站更新可用", + "robot_update_success": "工作站软件已成功更新", + "search_again": "再次搜索", + "searching": "搜索中", + "searching_for_networks": "正在搜索网络...", + "security_type": "安全类型", + "select_a_network": "选择一个网络", + "select_a_security_type": "选择一个安全类型", + "select_all_settings": "选择所有设置", + "select_authentication_method": "为您所选的网络选择身份验证方法。", + "sending_software": "正在发送软件...", + "serial": "序列号", + "setup_mode": "设置模式", + "short_trash_bin": "短垃圾桶", + "short_trash_bin_description": "适用于2019年之前带有55mm高垃圾桶的工作站(而非默认的77mm)", + "show": "显示", + "show_password": "显示密码", + "sign_into_wifi": "登录Wi-Fi", + "software_is_up_to_date": "您的软件已经是最新版本!", + "software_update_error": "软件更新错误", + "some_robot_controls_are_not_available": "运行过程中无法使用工作站的控制功能", + "ssh_public_keys": "SSH公钥", + "subnet_mask": "子网掩码", + "successfully_connected": "成功连接!", + "successfully_connected_to_network": "已成功连接到{{ssid}}!", + "supported_protocol_api_versions": "支持的协议API版本", + "text_size": "文本大小", + "text_size_description": "所有屏幕上的文本都会根据您在下方选择的大小进行调整。", + "tip_length_calibrations_history": "查看所有吸头长度校准历史", + "tip_length_calibrations_title": "吸头长度校准", + "tiprack": "吸头架", + "touchscreen_brightness": "触摸屏亮度", + "touchscreen_sleep": "触摸屏休眠", + "troubleshooting": "故障排查", + "try_again": "再试一次", + "try_restarting_the_update": "尝试重新启动更新。", + "up_to_date": "最新的", + "update_available": "有可用更新", + "update_channel_description": "稳定版接收最新的稳定版发布。Beta 版允许您在新功能在稳定版发布前先行试用,但这些新功能尚未完成测试。", + "update_complete": "更新完成!", + "update_found": "发现更新!", + "update_robot_now": "现在更新工作站", + "update_robot_software": "使用本地文件(.zip)手动更新工作站软件", + "updating": "正在更新", + "update_requires_restarting_robot": "更新工作站软件需要重启工作站", + "upload_custom_logo_description": "上传一个Logo,用于工作站启动时显示。", + "upload_custom_logo_dimensions": "Logo必须符合 1024 x 600 的尺寸,且是 PNG 文件(.png)。", + "upload_custom_logo": "上传自定义Logo", + "usage_settings": "使用设置", + "usb": "USB", + "usb_to_ethernet_description": "正在查找 USB-to-Ethernet 适配器信息?", + "use_older_aspirate": "使用旧版吸液动作", + "use_older_aspirate_description": "使用在 3.7.0 版本之前使用的较不准确的体积校准进行吸取。如果需要与 3.7.0 版本之前的结果保持一致,请使用此设置。这仅影响 GEN1 P10S、P10M、P50M 和 P300S 移液器。", + "validating_software": "正在验证软件...", + "view_details": "查看详细信息", + "view_network_details": "查看网络详细信息", + "view_update": "查看更新", + "welcome_description": "在实验室台面上快速运行协议并检查工作站状态。", + "wifi": "Wi-Fi", + "wired_ip": "有线IP", + "wired_mac_address": "有线MAC地址", + "wired_subnet_mask": "有线子网掩码", + "wired_usb": "有线USB", + "wired_usb_description": "了解如何通过USB连接到工作站", + "wireless_ip": "无线IP", + "wireless_mac_address": "无线MAC地址", + "wireless_subnet_mask": "无线子网掩码", + "wpa2_personal": "WPA2个人", + "wpa2_personal_description": "大多数实验室都使用此方法", + "yes_clear_data_and_restart_robot": "是,清除数据并重新启动工作站", + "you_should_not_downgrade": "您不应降级到工作站制造日期之前的软件版本。", + "your_mac_address_is": "您的MAC地址是{{macAddress}}", + "your_robot_is_ready_to_go": "您的工作站已准备就绪。" +} diff --git a/app/src/assets/localization/zh/devices_landing.json b/app/src/assets/localization/zh/devices_landing.json new file mode 100644 index 00000000000..8e6af9d5ba9 --- /dev/null +++ b/app/src/assets/localization/zh/devices_landing.json @@ -0,0 +1,47 @@ +{ + "active": "激活", + "available": "可用({{count}})", + "check_same_network": "检查电脑和工作站是否连接在同一网络上", + "connect_to_network": "连接到网络", + "connection_troubleshooting_intro": "如果工作站的连接出现问题,请尝试执行以下故障排除任务。首先,仔细检查工作站电源是否打开", + "deck_configuration": "甲板配置", + "devices": "设备", + "disconnect_from_network": "断开网络连接", + "empty": "空", + "forget_unavailable_robot": "删除无法使用的工作站", + "go_to_run": "返回运行界面", + "home_gantry": "龙门架归位", + "how_to_setup_a_robot": "如何设置新工作站", + "idle": "空闲", + "if_connecting_via_usb": "如果通过USB连接", + "if_connecting_wirelessly": "如果通过无线方式连接", + "if_still_having_issues": "如果您仍然有问题", + "learn_more_about_new_robot_setup": "了解有关设置新工作站的更多信息", + "learn_more_about_troubleshooting_connection": "了解有关连接问题故障排除的更多信息", + "left_mount": "左侧安装支架", + "lights_off": "关闭灯光", + "lights_on": "开启灯光", + "loading": "加载中", + "looking_for_robots": "寻找工作站", + "ninety_six_mount": "左侧+右侧安装支架", + "make_sure_robot_is_connected": "确保工作站已连接到此计算机", + "modules": "模块", + "no_robots_found": "未找到工作站", + "not_available": "不可用({{count}})", + "ot2_quickstart_guide": "OT-2 快速入门指南", + "refresh": "刷新", + "restart_the_app": "重启应用程序", + "restart_the_robot": "重启工作站", + "right_mount": "右侧安装支架", + "robot_settings": "工作站设置", + "run_a_protocol": "运行一个协议", + "running": "运行中", + "see_how_to_setup_new_robot": "查看如何设置新工作站", + "setting_up_new_robot": "了解如何设置新工作站", + "this_robot_has_connected_and_power_on_module": "工作站已连接并开启{{moduleName}}", + "troubleshooting_connection_problems": "了解有关连接问题故障排除的更多信息", + "update_robot_software": "更新工作站软件", + "use_usb_cable_for_new_robot": "设置新工作站时,请使用附带的USB线进行首次连接。", + "wait_after_connecting": "将工作站连接到电脑后等待一会", + "why_is_this_robot_unavailable": "为什么这个工作站不可用?" +} diff --git a/app/src/assets/localization/zh/drop_tip_wizard.json b/app/src/assets/localization/zh/drop_tip_wizard.json new file mode 100644 index 00000000000..a5ae8faebfc --- /dev/null +++ b/app/src/assets/localization/zh/drop_tip_wizard.json @@ -0,0 +1,52 @@ +{ + "before_you_begin_do_you_want_to_blowout": "开始前,您是否需要保留已吸取的液体?", + "begin_removal": "开始移除", + "blowout_complete": "吹液完成", + "blowout_liquid": "吹出液体", + "cant_safely_drop_tips": "无法安全丢弃吸头", + "choose_blowout_location": "选择吹液位置", + "choose_deck_location": "选择甲板位置", + "choose_drop_tip_location": "选择吸头丢弃位置", + "confirm_blowout_location": "移液器是否位于应吹出液体的位置?", + "confirm_drop_tip_location": "移液器是否位于应丢弃吸头的位置?", + "confirm_position": "确认位置", + "confirm_removal_and_home": "确认移除并回到原点", + "continue": "继续", + "drop_tip_complete": "吸头丢弃完成", + "drop_tip_failed": "丢弃吸头操作未能完成,请联系技术支持获取帮助。", + "drop_tips": "丢弃吸头", + "error_dropping_tips": "丢弃吸头时发生错误", + "exit_and_home_pipette": "退出并归位移液器", + "exit_screen_title": "在完成吸头丢弃前退出?", + "exit": "退出", + "getting_ready": "正在准备…", + "go_back": "返回", + "jog_too_far": "移动过远?", + "liquid_damages_pipette": "如果移液器中有液体,将移液器归位可能会损坏它。您必须在使用移液器之前取下所有吸头。", + "liquid_damages_this_pipette": "如果{{mount}}移液器的移液器中有液体,将其归位可能会损坏它。您必须在使用移液器之前取下所有吸头。", + "move_to_slot": "移至板位", + "no_proceed_to_drop_tip": "否,继续进行吸头移除", + "position_and_blowout": "确保移液器吸头尖端位于指定位置的正上方并保持水平。如果不是,请使用下面的控制键或键盘微调移液器直到正确位置。", + "position_and_drop_tip": "确保移液器吸头尖端位于指定位置的正上方并保持水平。如果不是,请使用下面的控制键或键盘微调移液器直到正确位置。", + "position_the_pipette": "调整移液器位置", + "remove_any_attached_tips": "移除任何已安装的吸头", + "remove_the_tips": "在协议中再次使用前,您可能需要从{{mount}}移液器上移除吸头。", + "remove_the_tips_from_pipette": "在协议中再次使用前,您可能需要从移液器上移除吸头。", + "remove_the_tips_manually": "手动移除吸头,然后使龙门架回原点。在拾取吸头的状态下归位可能导致移液器吸入液体并损坏。", + "remove_tips": "移除吸头", + "select_blowout_slot": "您可以将液体吹入实验容器中。在右侧的甲板图上选择您想要吹出液体的板位。确认后龙门架将移动到选定的板位。", + "select_blowout_slot_odd": "您可以将液体吹入耗材中。
龙门架移动到选定的板位后,使用位置控制按键将移液器移动到吹出液体的确切位置。", + "select_drop_tip_slot": "您可以将吸头返回吸头架或丢弃它们。在右侧的甲板图上选择您想丢弃吸头的板位。确认后龙门架将移动到选定的板位。", + "select_drop_tip_slot_odd": "您可以将吸头放回吸头架或丢弃它们。
龙门架移动到选定的板位后,使用位置控制按键将移液器移动到丢弃吸头的确切位置。", + "skip_and_home_pipette": "跳过并归位移液器", + "skip": "跳过", + "stand_back_blowing_out": "请远离,工作站正在吹出液体", + "stand_back_dropping_tips": "请远离,工作站正在丢弃吸头", + "stand_back_robot_in_motion": "请远离,工作站正在移动", + "start_over": "重新开始", + "trash_bin_in_slot": "垃圾桶在 {{slot}}", + "waste_chute_in_slot": "外置垃圾槽在 {{slot}}", + "where_to_blowout": "您希望在哪里吹出液体?", + "where_to_drop_tips": "您希望在哪里丢弃吸头?", + "yes_blow_out_liquid": "是的,将液体吹入耗材中" +} diff --git a/app/src/assets/localization/zh/error_recovery.json b/app/src/assets/localization/zh/error_recovery.json new file mode 100644 index 00000000000..dddf7923d4b --- /dev/null +++ b/app/src/assets/localization/zh/error_recovery.json @@ -0,0 +1,107 @@ +{ + "another_app_controlling_robot": "工作站的触摸屏或另一台装有应用程序的电脑正在控制这个工作站。", + "are_you_sure_you_want_to_cancel": "您确定要取消吗?", + "at_step": "在步骤", + "back_to_menu": "返回菜单", + "before_you_begin": "开始前", + "begin_removal": "开始移除", + "blowout_failed": "吹出液体失败", + "overpressure_is_usually_caused": "探测器感应到压力过大通常是由吸头碰撞到实验用品、吸头堵塞或吸取/排出粘稠液体速度过快引起。如果问题持续存在,请取消运行并对协议进行必要的修改。", + "cancel_run": "取消运行", + "canceling_run": "正在取消运行", + "change_location": "更改位置", + "change_tip_pickup_location": "更换拾取吸头的位置", + "choose_a_recovery_action": "选择恢复操作", + "close_door_to_resume": "关闭工作站门以继续", + "close_robot_door": "关闭移液工作站前门", + "close_the_robot_door": "关闭工作站门,然后继续恢复操作。", + "confirm": "确认", + "continue": "继续", + "continue_run_now": "现在继续运行", + "continue_to_drop_tip": "继续丢弃吸头", + "ensure_lw_is_accurately_placed": "确保实验耗材已准确放置在甲板槽中,防止进一步出现错误", + "error_details": "错误详情", + "error_on_robot": "{{robot}}上的错误", + "error": "错误", + "failed_dispense_step_not_completed": "中断运行的最后一步液体排出失败,恢复程序将不会继续运行这一步骤,请手动完成这一步的移液操作。运行将继续从下一步开始。继续之前,请关闭工作站门。", + "failed_step": "失败步骤", + "first_is_gripper_holding_labware": "首先,抓扳手是否夹着实验耗材?", + "go_back": "返回", + "gripper_error": "抓扳手错误", + "gripper_errors_occur_when": "当抓扳手停滞或与甲板上另一物体碰撞时,会发生抓扳手错误,这通常是由于实验耗材放置不当或实验耗材偏移不准确所致", + "gripper_releasing_labware": "抓扳手正在释放实验耗材", + "gripper_will_release_in_s": "抓扳手将在{{seconds}}秒后释放实验耗材", + "homing_pipette_dangerous": "如果移液器中有液体,将{{mount}}移液器归位可能会损坏它。您必须在使用移液器之前取下所有吸头。", + "if_issue_persists_gripper_error": "如果问题持续存在,请取消运行并重新运行抓扳手校准", + "if_issue_persists_overpressure": "如果问题持续存在,请取消运行并对协议进行必要的更改", + "if_issue_persists_tip_not_detected": "如果问题持续存在,请取消运行并启动实验耗材位置检查", + "if_tips_are_attached": "如果吸头还在移液器上,您可以在运行终止前选择吹出已吸取的液体并丢弃吸头。", + "ignore_all_errors_of_this_type": "忽略所有此类错误", + "ignore_error_and_skip": "忽略错误并跳到下一步", + "ignore_only_this_error": "仅忽略此错误", + "ignore_similar_errors_later_in_run": "要在后续的运行中忽略类似错误吗?", + "labware_released_from_current_height": "将从当前高度释放实验耗材", + "launch_recovery_mode": "启动恢复模式", + "manually_fill_liquid_in_well": "手动填充孔位{{well}}中的液体", + "manually_fill_well_and_skip": "手动填充孔位并跳到下一步", + "manually_move_lw_and_skip": "手动移动实验耗材并跳至下一步", + "manually_move_lw_on_deck": "手动移动实验耗材", + "manually_replace_lw_and_retry": "手动更换实验耗材并重试此步骤", + "manually_replace_lw_on_deck": "手动更换实验耗材", + "next_step": "下一步", + "next_try_another_action": "接下来,您可以尝试另一个恢复操作或取消运行。", + "no_liquid_detected": "未检测到液体", + "pick_up_tips": "取吸头", + "pipette_overpressure": "移液器超压", + "preserve_aspirated_liquid": "首先,您需要保留已吸取的液体吗?", + "proceed_to_cancel": "继续取消", + "proceed_to_tip_selection": "继续选择吸头", + "recovery_action_failed": "{{action}}失败", + "recovery_mode": "恢复模式", + "recovery_mode_explanation": "恢复模式为您提供运行错误后的手动处理引导。
您可以进行调整以确保发生错误时正在进行的步骤可以完成,或者选择取消协议。当做出调整且未检测到后续错误时,该模式操作完成。根据导致错误的条件,系统将提供相应的调整选项。", + "release_labware_from_gripper": "从抓板手中释放实验耗材", + "release": "释放", + "remove_any_attached_tips": "移除任何已安装的吸头", + "replace_tips_and_select_location": "建议更换吸头并选择最后一次取吸头的位置。", + "replace_used_tips_in_rack_location": "在吸头板位{{location}}更换已使用的吸头", + "replace_with_new_tip_rack": "更换新的吸头盒", + "resume": "继续", + "retrying_step_succeeded": "重试步骤{{step}}成功", + "retry_now": "现在重试", + "retry_step": "重试步骤", + "retry_with_new_tips": "使用新吸头重试", + "retry_with_same_tips": "使用相同吸头重试", + "return_to_menu": "返回菜单", + "robot_door_is_open": "工作站前门已打开", + "robot_is_canceling_run": "工作站正在取消运行", + "robot_is_in_recovery_mode": "工作站正在恢复模式", + "robot_not_attempt_to_move_lw": "工作站将不再尝试移动实验耗材。运行将从下一步继续。在继续之前,请关闭工作站门。", + "robot_retry_failed_lw_movement": "工作站将会从更换耗材的位置重新尝试失败的移液步骤。在继续之前,请关闭工作站门。", + "robot_will_not_check_for_liquid": "工作站将不再检查液体。运行将从下一步继续。继续前请关闭工作站前门。", + "robot_will_retry_with_new_tips": "工作站将使用新吸头重试失败的步骤。继续前请关闭工作站前门。", + "robot_will_retry_with_same_tips": "工作站将使用相同的吸头重试失败的步骤。继续前请关闭工作站前门。", + "robot_will_retry_with_tips": "工作站将使用新吸头重试失败的步骤。", + "run_paused": "运行暂停", + "select_tip_pickup_location": "选择取吸头位置", + "skipping_to_step_succeeded": "跳转到步骤{{step}}成功", + "skip_and_home_pipette": "跳过并归位移液器", + "skip_to_next_step": "跳到下一步", + "skip_to_next_step_new_tips": "使用新吸头跳到下一步", + "skip_to_next_step_same_tips": "使用相同吸头跳到下一步", + "stand_back": "请远离,工作站正在运行", + "stand_back_picking_up_tips": "请远离,正在拾取吸头", + "stand_back_resuming": "请远离,正在恢复当前步骤", + "stand_back_retrying": "请远离,正在重试失败步骤", + "stand_back_skipping_to_next_step": "请远离,正在跳到下一步骤", + "take_any_necessary_precautions": "在接住实验耗材之前,请采取必要的预防措施。确认后,夹爪将开始倒计时再释放。", + "take_necessary_actions_failed_pickup": "首先,采取任何必要的行动,让工作站重新尝试移液器拾取。然后,在继续之前关闭工作站门。", + "take_necessary_actions": "首先,采取任何必要的行动,让工作站重新尝试失败的步骤。然后,在继续之前关闭工作站门。", + "terminate_remote_activity": "终止远程活动", + "tip_drop_failed": "丢弃吸头失败", + "tip_not_detected": "未检测到吸头", + "tip_presence_errors_are_caused": "吸头识别错误通常是由实验器皿放置不当或器皿偏移不准确引起的。", + "view_error_details": "查看错误详情", + "view_recovery_options": "查看恢复选项", + "you_can_still_drop_tips": "在继续选择吸头之前,您仍然可以丢弃移液器上现存的吸头。", + "you_may_want_to_remove": "在协议中再次使用之前,您可能需要从{{mount}}移液器上移除吸头。" +} diff --git a/app/src/assets/localization/zh/firmware_update.json b/app/src/assets/localization/zh/firmware_update.json new file mode 100644 index 00000000000..efed676a952 --- /dev/null +++ b/app/src/assets/localization/zh/firmware_update.json @@ -0,0 +1,17 @@ +{ + "firmware_out_of_date": "用于{{mount}}{{instrument}}的固件已过期。在运行使用此设备的协议之前,请更新该固件。", + "gantry_x": "龙门架X轴", + "gantry_y": "龙门架Y轴", + "gripper": "转板抓手", + "head": "头部组件", + "hepa_uv": "HEPA/UV模块", + "pipette_left": "左侧移液器", + "pipette_right": "右侧移液器", + "ready_to_use": "您的{{instrument}}已经准备就绪!", + "rear_panel": "后置面板", + "successful_update": "更新成功!", + "update_failed": "更新失败", + "update_firmware": "更新固件", + "update_needed": "需要更新设备固件", + "updating_firmware": "正在更新{{subsystem}}固件..." +} diff --git a/app/src/assets/localization/zh/gripper_wizard_flows.json b/app/src/assets/localization/zh/gripper_wizard_flows.json new file mode 100644 index 00000000000..6617e15d83a --- /dev/null +++ b/app/src/assets/localization/zh/gripper_wizard_flows.json @@ -0,0 +1,39 @@ +{ + "are_you_sure_exit": "在完成{{flow}}之前,您确定要退出吗?", + "attach_gripper": "安装转板抓手", + "attached_gripper_and_screw_in": "将转板抓手对准接口并压紧固定抓手,以确保安全连接。首先拧紧上方螺丝,接着拧紧下方螺丝。然后轻轻拉动,测试抓手是否稳固安装。", + "before_you_begin": "开始之前", + "begin_calibration": "开始校准", + "calibrate_gripper": "校准转板抓手", + "calibration_pin": "校准钉", + "calibration_pin_touching": "校准钉将触碰甲板{{slot}}中的校准块,以确定其精确位置。", + "complete_calibration": "完成校准", + "continue": "继续", + "continue_calibration": "继续校准", + "detach_gripper": "卸下转板抓手", + "firmware_updating": "需要固件更新,设备正在更新中...", + "firmware_up_to_date": "固件已为最新版本。", + "get_started": "开始操作", + "gripper_calibration": "转板抓手校准", + "gripper_recalibration": "转板抓手重新校准", + "gripper_successfully_attached": "转板抓手已成功安装", + "hex_screwdriver": "2.5mm 六角螺丝刀", + "hold_gripper_and_loosen_screws": "用手扶住转板抓手,首先松开上方螺丝,再松开下方螺丝。(螺丝固定于抓手上,不会脱落。)之后小心卸下抓手。", + "insert_pin_into_front_jaw": "将校准钉插入前夹爪", + "insert_pin_into_rear_jaw": "将校准钉插入后夹爪", + "move_gantry_to_front": "将龙门架移至前端", + "move_pin_from_front_to_rear_jaw": "从前夹爪上取下校准钉并装到后夹爪上。", + "move_pin_from_rear_jaw_to_storage": "从后夹爪上取下校准钉并放回其存放位置。", + "move_pin_from_storage_to_front_jaw": "从存放位置取下校准钉,利用磁性将其放到前夹爪下方的孔洞中。", + "progress_will_be_lost": "{{flow}}的进度将丢失", + "provided_with_robot_use_right_size": "随工作站提供。使用非标准尺寸可能导致仪器螺丝损坏。", + "remove_calibration_pin": "卸下校准钉", + "remove_labware_to_get_started_attaching": "开始前,请清空甲板,移除所有实验器材,以便于安装和校准。同时根据屏幕提示,准备所需工具。校准钉随转板抓手附带,应存放于转板抓手右侧上方。", + "remove_labware_to_get_started_detaching": "开始前,请移除甲板上的所有耗材,并清理工作区,以便于拆卸操作。同时根据屏幕提示,准备相关工具。", + "remove_labware_to_get_started_recalibrating": "开始前,请移除甲板上的所有耗材,并清理工作区,以便于校准。同时根据屏幕提示,准备相关工具。校准钉随转板抓手附带,应存放在转板抓手右侧上方。", + "remove_probe": "解锁校准探头,从移液器喷嘴卸下,并归位存放。", + "return_pin_error": "退出前,请务必将校准钉放回存放位置。 {{error}}", + "stand_back_gripper_is_calibrating": "请远离,转板抓手正在进行校准", + "try_again": "重试", + "unable_to_detect_gripper": "未能检测到转板抓手" +} diff --git a/app/src/assets/localization/zh/heater_shaker.json b/app/src/assets/localization/zh/heater_shaker.json new file mode 100644 index 00000000000..249f1cc6138 --- /dev/null +++ b/app/src/assets/localization/zh/heater_shaker.json @@ -0,0 +1,48 @@ +{ + "back": "返回", + "cannot_open_latch": "模块震荡混匀期间闩锁不能打开", + "cannot_shake": "模块闩锁打开时无法执行震荡混匀命令", + "close_labware_latch": "关闭耗材闩锁", + "close_latch": "关闭闩锁", + "closed_and_locked": "关闭并锁定", + "closed": "已关闭", + "closing": "正在关闭", + "complete": "完成", + "confirm_attachment": "确认已连接", + "confirm_heater_shaker_modal_attachment": "确认热震荡模块已连接", + "continue_shaking_protocol_start_prompt": "协议启动时是否继续执行震荡混匀命令?", + "deactivate_heater": "停止加热", + "deactivate_shaker": "停止震荡混匀", + "deactivate": "停用", + "heater_shaker_in_slot": "在继续前,请在板位{{slotName}}中安装{{moduleName}}", + "heater_shaker_is_shaking": "热震荡模块当前正在震荡混匀", + "keep_shaking_start_run": "继续震荡混匀并开始运行", + "labware_latch": "耗材闩锁", + "labware": "耗材", + "min_max_rpm": "{{min}}-{{max}}rpm", + "module_anchors_extended": "运行开始前,应将模块下方的固定位的漏丝拧紧,确保模块稳固跟甲板结合", + "module_in_slot": "{{moduleName}}位于板位{{slotName}}槽中", + "module_should_have_anchors": "模块应将两个固定锚完全伸出,以确保稳固地连接到甲板上", + "open_labware_latch": "打开耗材闩锁", + "open_latch": "打开闩锁", + "open": "打开", + "opening": "正在打开", + "proceed_to_run": "继续运行", + "set_shake_speed": "设置震荡混匀速度", + "set_temperature": "设置模块温度", + "shake_speed": "震荡混匀速度", + "show_attachment_instructions": "显示固定操作安装说明", + "stop_shaking_start_run": "停止震荡混匀并开始运行", + "stop_shaking": "停止震荡混匀", + "t10_torx_screwdriver": "{{name}}型螺丝刀", + "t10_torx_screwdriver_subtitle": "热震荡模块附带。使用其他尺寸可能会损坏模块螺丝", + "test_shake_banner_information": "测试震荡混匀功能前,如果想往热震荡模块转移耗材,需要在控制页面控制模块开启闩锁", + "test_shake_banner_labware_information": "测试震荡混匀功能前,如果想往热震荡模块转移{{labware}},需要控制模块闩锁", + "test_shake_slideout_banner_info": "测试震荡混匀功能前,如果想往热震荡模块转移耗材,需要在控制页面控制模块开启闩锁", + "test_shake_troubleshooting_slideout_description": "请重新查看模块固定安装及其适配器安装相关说明", + "test_shake": "测试震荡混匀", + "thermal_adapter_attached_to_module": "适配器应已连接到模块上", + "troubleshoot_step_1": "返回步骤1查看模块固定安装相关说明", + "troubleshoot_step_3": "返回步骤3查看模块适配器安装相关说明", + "troubleshooting": "故障排除" +} diff --git a/app/src/assets/localization/zh/incompatible_modules.json b/app/src/assets/localization/zh/incompatible_modules.json new file mode 100644 index 00000000000..7e6da2f253e --- /dev/null +++ b/app/src/assets/localization/zh/incompatible_modules.json @@ -0,0 +1,7 @@ +{ + "incompatible_modules_attached": "检测到不适用模块,", + "remove_before_running_protocol": "运行协议前请移除以下硬件:", + "needs_your_assistance": "{{robot_name}}需要您的协助", + "remove_before_using": "使用此工作站前,请移除不适用模块.", + "is_not_compatible": "{{module_name}}不适用于{{robot_type}}," +} diff --git a/app/src/assets/localization/zh/index.ts b/app/src/assets/localization/zh/index.ts new file mode 100644 index 00000000000..5cb1ab2d10b --- /dev/null +++ b/app/src/assets/localization/zh/index.ts @@ -0,0 +1,63 @@ +import shared from './shared.json' +import anonymous from './anonymous.json' +import app_settings from './app_settings.json' +import branded from './branded.json' +import change_pipette from './change_pipette.json' +import protocol_command_text from './protocol_command_text.json' +import device_details from './device_details.json' +import device_settings from './device_settings.json' +import devices_landing from './devices_landing.json' +import drop_tip_wizard from './drop_tip_wizard.json' +import firmware_update from './firmware_update.json' +import gripper_wizard_flows from './gripper_wizard_flows.json' +import heater_shaker from './heater_shaker.json' +import instruments_dashboard from './instruments_dashboard.json' +import labware_details from './labware_details.json' +import labware_landing from './labware_landing.json' +import labware_position_check from './labware_position_check.json' +import module_wizard_flows from './module_wizard_flows.json' +import pipette_wizard_flows from './pipette_wizard_flows.json' +import protocol_details from './protocol_details.json' +import protocol_info from './protocol_info.json' +import protocol_list from './protocol_list.json' +import protocol_setup from './protocol_setup.json' +import quick_transfer from './quick_transfer.json' +import robot_calibration from './robot_calibration.json' +import robot_controls from './robot_controls.json' +import run_details from './run_details.json' +import top_navigation from './top_navigation.json' +import error_recovery from './error_recovery.json' +import incompatible_modules from './incompatible_modules.json' + +export const zh = { + shared, + anonymous, + app_settings, + branded, + change_pipette, + protocol_command_text, + device_details, + device_settings, + devices_landing, + drop_tip_wizard, + firmware_update, + gripper_wizard_flows, + heater_shaker, + instruments_dashboard, + labware_details, + labware_landing, + labware_position_check, + module_wizard_flows, + pipette_wizard_flows, + protocol_details, + protocol_info, + protocol_list, + protocol_setup, + quick_transfer, + robot_calibration, + robot_controls, + run_details, + top_navigation, + error_recovery, + incompatible_modules, +} diff --git a/app/src/assets/localization/zh/instruments_dashboard.json b/app/src/assets/localization/zh/instruments_dashboard.json new file mode 100644 index 00000000000..f8b6c4f7c29 --- /dev/null +++ b/app/src/assets/localization/zh/instruments_dashboard.json @@ -0,0 +1,10 @@ +{ + "calibrate": "校准", + "detach": "卸载", + "firmware_version": "固件版本", + "last_calibrated": "上次校准", + "no_cal_data": "无校准数据", + "pipette_calibrations_differ": "建议重新校准移液器 已连接的移液器校准数据差异很大。正确校准时,这些数据应该近似。", + "recalibrate": "重新校准", + "serial_number": "序列号" +} diff --git a/app/src/assets/localization/zh/labware_details.json b/app/src/assets/localization/zh/labware_details.json new file mode 100644 index 00000000000..ba1183c2ea4 --- /dev/null +++ b/app/src/assets/localization/zh/labware_details.json @@ -0,0 +1,33 @@ +{ + "all": "全部", + "count": "数量", + "depth": "深度", + "diameter": "直径", + "flat": "平底", + "footprint": "占用空间 (mm)", + "generic": "通用型", + "height": "高度", + "length": "长度", + "manufacturer_number": "制造商/目录编号", + "manufacturer": "制造商", + "max_volume": "最大容量", + "measurements": "尺寸 (mm)", + "na": "不适用", + "shape": "形状", + "spacing": "间距 (mm)", + "tip": "吸头", + "total_length": "总长度", + "tube": "离心管", + "u": "U型底", + "v": "V型底", + "various": "多种", + "well_count": "孔数", + "well": "孔", + "width": "宽度", + "x_offset": "X-初始位置", + "x_size": "X-尺寸", + "x_spacing": "X-间距", + "y_offset": "Y-初始位置", + "y_size": "Y-尺寸", + "y_spacing": "Y-间距" +} diff --git a/app/src/assets/localization/zh/labware_landing.json b/app/src/assets/localization/zh/labware_landing.json new file mode 100644 index 00000000000..386114541f3 --- /dev/null +++ b/app/src/assets/localization/zh/labware_landing.json @@ -0,0 +1,28 @@ +{ + "api_name": "API 名称", + "cancel": "取消", + "cannot-run-python-missing-labware": "工作站缺少耗材定义,无法运行Python协议。", + "category": "类别", + "choose_file_to_upload": "或从您的计算机中选择文件上传。", + "choose_file": "选择文件", + "copied": "已复制!", + "create_new_def": "创建新的自定义耗材", + "custom_def": "自定义耗材", + "date_added": "添加日期", + "def_moved_to_trash": "该耗材将被转移到这台电脑的回收站,可能无法恢复。", + "delete_this_labware": "删除此耗材?", + "delete": "删除", + "duplicate_labware_def": "复制耗材", + "error_importing_file": "{{filename}}导入错误。", + "go_to_def": "前往耗材页面", + "import_custom_def": "导入自定义耗材", + "import": "导入", + "imported": "{{filename}}已导入。", + "invalid_labware_def": "无效耗材", + "labware": "耗材", + "last_updated": "最近更新", + "open_labware_creator": "打开耗材创建工具", + "show_in_folder": "在文件夹中显示", + "unable_to_upload": "无法上传文件", + "yes_delete_def": "是的,删除耗材" +} diff --git a/app/src/assets/localization/zh/labware_position_check.json b/app/src/assets/localization/zh/labware_position_check.json new file mode 100644 index 00000000000..cb27f78e66c --- /dev/null +++ b/app/src/assets/localization/zh/labware_position_check.json @@ -0,0 +1,116 @@ +{ + "adapter_in_mod_in_slot": "{{slot}}中{{module}}上的{{adapter}}", + "adapter_in_slot": "{{slot}}上的{{adapter}}", + "adapter_in_tc": "{{module}}上的{{adapter}}", + "all_modules_and_labware_from_protocol": "{{protocol_name}}协议中使用的所有模块与耗材 ", + "applied_offset_data": "已应用的耗材校准数据", + "apply_offset_data": "应用耗材校准数据", + "apply_offsets": "应用校准数据", + "attach_probe": "安装校准探头", + "backmost": "最靠后的", + "calibration_probe": "校准探头", + "check_item_in_location": "检查{{location}}上的{{item}}", + "check_labware_in_slot_title": "检查板位{{slot}}中的耗材{{labware_display_name}}", + "check_remaining_labware_with_primary_pipette_section": "使用{{primary_mount}}移液器和吸头检查剩余耗材", + "check_tip_location": "A1的吸头尖端", + "check_well_location": "耗材上的A1孔", + "clear_all_slots": "清空甲板上所有的耗材,模块原位保留", + "clear_all_slots_odd": "清空甲板上所有的耗材及模块", + "cli_ssh": "命令行界面 (SSH)", + "close_and_apply_offset_data": "关闭并保存耗材校准数据", + "confirm_detached": "确认移除", + "confirm_pick_up_tip_modal_title": "移液器是否成功拾取了吸头?", + "confirm_pick_up_tip_modal_try_again_text": "否,重试", + "confirm_position_and_move": "确认位置,移动到板位{{next_slot}}", + "confirm_position_and_pick_up_tip": "确认位置,取吸头", + "confirm_position_and_return_tip": "确认位置,将吸头返回至板位{{next_slot}}并复位", + "detach_probe": "移除校准探头", + "ensure_nozzle_position_odd": "请检查并确认{{tip_type}}位于{{item_location}}正上方并水平对齐。如果位置不正确,请点击移动移液器,然后微调移液器直至完全对齐。", + "ensure_nozzle_position_desktop": "请检查并确认{{tip_type}}位于{{item_location}}正上方并水平对齐。如果位置不正确,请使用下方的控制按键或键盘微调移液器直至完全对齐。", + "exit_screen_confirm_exit": "不保存耗材校准数据并退出", + "exit_screen_go_back": "返回耗材位置校准", + "exit_screen_subtitle": "如果您现在退出,所有耗材校准数据都将不保留,且无法恢复。", + "exit_screen_title": "确定要在完成耗材位置校准前退出?", + "get_labware_offset_data": "获取耗材校准数据", + "install_probe": "从存储位置取出校准探头,将探头的锁套旋钮按顺时针方向拧松。对准图示位置,将校准探头向上轻推并压到顶部,使探头在{{location}}移液器吸嘴上压紧。随后将锁套旋钮按逆时针方向拧紧,并轻拉确认是否固定稳妥。", + "jog_controls_adjustment": "需要进行调整吗?", + "jupyter_notebook": "Jupyter Notebook", + "labware_display_location_text": "甲板板位{{slot}}", + "labware_offset_data": "耗材校准数据", + "labware_offset": "耗材校准数据", + "labware_offsets_deleted_warning": "一旦开始耗材位置校准,之前创建的耗材校准数据将会丢失。", + "labware_offsets_summary_labware": "耗材", + "labware_offsets_summary_location": "位置", + "labware_offsets_summary_offset": "耗材校准数据", + "labware_offsets_summary_title": "将应用于本次运行的耗材校准数据", + "labware_position_check_description": "耗材位置校准是一个引导式工作流程,为了提高实验中移液器的位置精确度,它会检查甲板上的每一个耗材位置。首先检查吸头盒,然后检查协议中使用到的其它所有耗材。", + "labware_position_check_overview": "耗材位置校准概览", + "labware_position_check_title": "耗材位置校准", + "labware_step_detail_labware_plural": "吸头应位于 {{labware_name}} 第一列正上方,居中对齐,并且与耗材顶部水平对齐。", + "labware_step_detail_labware": "吸头应位于 {{labware_name}} 的A1孔正上方,居中对齐,并且与耗材顶部水平对齐。", + "labware_step_detail_link": "查看如何判断移液器是否居中", + "labware_step_detail_modal_heading": "如何判断移液器是否居中且水平对齐", + "labware_step_detail_modal_nozzle_image_1_text": "从正前方看,似乎已经居中...", + "labware_step_detail_modal_nozzle_image_2_nozzle_text": "移液器吸嘴未居中", + "labware_step_detail_modal_nozzle_image_2_text": "...但从侧面看,需要调整", + "labware_step_detail_modal_nozzle_or_tip_image_1_text": "从俯视角度看,似乎是水平的...", + "labware_step_detail_modal_nozzle_or_tip_image_2_nozzle_text": "移液器吸嘴不水平", + "labware_step_detail_modal_nozzle_or_tip_image_2_text": "...但从水平高度看,需要调整", + "labware_step_detail_modal_nozzle_or_tip_image_3_text": "如遇觉得难以判断,请在吸嘴与吸头之间放入一张常规纸张。当这张纸能刚好卡在两者之间时,可确认高度位置。", + "labware_step_detail_modal_nozzle_or_tip": "为确保吸嘴或吸头与耗材顶部水平对齐,请水平直视进行判断,或在吸嘴与吸头间插入一张纸片辅助判断。", + "labware_step_detail_modal_nozzle": "为确保移液器吸嘴完全居中,请额外从OT-2的另一侧进行检查。", + "labware_step_detail_tiprack_plural": "移液器吸嘴应位于{{tiprack_name}}第一列正上方并居中对齐,并且与吸头顶部水平对齐。", + "labware_step_detail_tiprack": "移液器吸嘴应居中于{{tiprack_name}}的A1位置上方,并且与吸头顶部水平对齐。", + "labware": "耗材", + "learn_more": "了解更多", + "location": "位置", + "lpc_complete_summary_screen_heading": "耗材位置校准完成", + "module_display_location_text": "{{moduleName}}位于甲板板位{{slot}}", + "module_in_slot": "{{module}}位于板位{{slot}}", + "move_pipette": "移动移液器", + "move_to_a1_position": "将移液器移到A1位置并对齐", + "moving_to_slot_title": "正在移动到板位{{slot}}", + "new_labware_offset_data": "新的耗材校准数据", + "ninety_six_probe_location": "A1(左上角)", + "no_labware_offsets": "无耗材校准数据", + "no_offset_data_available": "没有可用的耗材校准数据", + "no_offset_data_on_robot": "这轮运行中此工作站没有可用的耗材校准数据。", + "no_offset_data": "没有可用的校准数据", + "offsets": "校准数据", + "pick_up_tip_from_rack_in_location": "从位于{{location}}的吸头盒上拾取吸头", + "picking_up_tip_title": "在板位{{slot}}拾取吸头", + "pipette_nozzle": "离您最远的移液器吸嘴", + "place_a_full_tip_rack_in_location": "将装满吸头的{{tip_rack}}放入{{location}}", + "place_labware_in_adapter_in_location": "在{{location}}先放置{{adapter}},再放置{{labware}}", + "place_labware_in_location": "将{{labware}}放入{{location}}", + "place_modules": "在甲板上放置模块", + "place_previous_tip_rack_in_location": "将之前使用的{{tip_rack}}放回{{location}}。移液器将把吸头放回原来的位置。", + "position_check_description": "耗材位置校准是一种工作引导流程,旨在对协议中用到的每个耗材在甲板上的具体位置做进一步确认。当检查耗材时,OT-2的移液器吸嘴或连接的吸头将停在A1孔的正中心。如果吸嘴或吸头未居中,您可以用OT-2的调整面板进行调整。此耗材校准数据将全面应用。校准精确度为0.1mm,可调方向包括X、Y和/或Z。", + "prepare_item_in_location": "在{{location}}准备{{item}}", + "primary_pipette_tipracks_section": "使用{{primary_mount}}移液器检查吸头盒并拾取吸头", + "remove_calibration_probe": "移除校准探头", + "remove_probe": "将校准探头解锁,将其从吸嘴上拆下,并放回存储位置。", + "remove_probe_before_exit": "退出前请移除校准探头", + "return_tip_rack_to_location": "将吸头盒放回{{location}}", + "return_tip_section": "放回吸头", + "returning_tip_title": "正在板位{{slot}}放回吸头", + "reveal_jog_controls": "显示调整面板", + "robot_has_no_offsets_from_previous_runs": "耗材校准数据引用自之前运行的协议,以节省您的时间。如果本协议中的所有耗材已在之前的运行中检查过,这些数据将应用于本次运行。 您可以在后面的步骤中使用耗材位置校准添加新的偏移量。", + "robot_has_offsets_from_previous_runs": "此工作站具有本协议中所用耗材的校准数据。如果您应用了这些校准数据,仍可通过耗材位置校准程序进行调整。", + "robot_in_motion": "工作站正在运行,请远离。", + "run_labware_position_check": "运行耗材位置校准程序", + "run": "运行", + "secondary_pipette_tipracks_section": "使用{{secondary_mount}}移液器检查吸头盒", + "see_how_offsets_work": "了解耗材校准的工作原理", + "slot_location": "板位位置", + "slot_name": "板位{{slotName}}", + "slot": "板位{{slotName}}", + "start_position_check": "开始耗材位置校准程序,移至板位{{initial_labware_slot}}", + "stored_offset_data": "应用已存储的耗材校准数据?", + "stored_offsets_for_this_protocol": "适用于本协议的已存储耗材校准数据", + "table_view": "表格视图", + "tip_rack": "吸头盒", + "view_current_offsets": "查看当前校准数据", + "view_data": "查看数据", + "what_is_labware_offset_data": "什么是耗材校准数据?" +} diff --git a/app/src/assets/localization/zh/module_wizard_flows.json b/app/src/assets/localization/zh/module_wizard_flows.json new file mode 100644 index 00000000000..b1cd0b086d6 --- /dev/null +++ b/app/src/assets/localization/zh/module_wizard_flows.json @@ -0,0 +1,47 @@ +{ + "attach_probe": "将校准探头安装到移液器", + "begin_calibration": "开始校准", + "calibrate_pipette": "在进行模块校准之前,请先校准移液器", + "calibrate": "校准", + "calibration_adapter_heatershaker": "校准适配器", + "calibration_adapter_temperature": "校准适配器", + "calibration_adapter_thermocycler": "校准适配器", + "calibration_probe_touching_thermocycler": "校准探头将接触探测热循环仪的校准方块,以确定其确切位置", + "calibration_probe_touching": "校准探头将接触探测{{slotNumber}}板位中{{module}}的校准方块,以确定其确切位置", + "calibration_probe": "从存储位置取出校准探头,将探头的锁套旋钮按顺时针方向拧松。对准图示位置,将校准探头向上轻推并压到顶部。随后将锁套旋钮按逆时针方向拧紧,并轻拉确认是否固定稳妥。", + "calibration": "{{module}}校准", + "checking_firmware": "检查{{module}}固件", + "complete_calibration": "完成校准", + "confirm_location": "确认位置", + "confirm_placement": "确认已放置", + "detach_probe_description": "解锁校准探头,将其从吸嘴上卸下并放回原储存位置。", + "detach_probe": "卸下移液器校准探头", + "error_during_calibration": "校准过程中出现错误", + "error_prepping_module": "模块校准出错", + "exit": "退出", + "firmware_up_to_date": "{{module}}固件已是最新版本。", + "firmware_updated": "{{module}}固件已更新!", + "install_adapter": "将校准适配器放置在{{module}}中", + "install_calibration_adapter": "安装校准适配器", + "location_occupied": "当前甲板配置中,在此处的硬件为{{fixture}}", + "module_calibrating": "{{moduleName}}正在校准中,请远离", + "module_calibration": "模块校准", + "module_heating_or_cooling": "模块正在升降温时无法进行模块校准", + "module_secured": "模块必须完全固定在其托架中,然后再安装到甲板对应板位。", + "module_too_hot": "模块温度过高,无法进行模块校准", + "move_gantry_to_front": "将龙门架移至前端", + "next": "下一步", + "pipette_probe": "移液器校准探头", + "place_flush_heater_shaker": "将适配器水平放到模块上。使用热震荡模块专用螺丝和 T10 Torx 螺丝刀将适配器固定到模块上。", + "place_flush_thermocycler": "确保热循环仪盖子已打开,并将适配器水平放置到模块上,即通常放置pcr板的位置。", + "place_flush": "将适配器水平放到模块上。", + "prepping_module": "正在准备{{module}}进行模块校准", + "recalibrate": "重新校准", + "select_location": "选择模块放置位置", + "select_the_slot": "请在右侧的甲板图中选择放置了{{module}}的板位。请确认所选择位置的正确性以确保顺利完成校准。", + "slot_unavailable": "板位不可用", + "stand_back_robot_in_motion": "工作站正在运行,请远离", + "stand_back": "正在进行校准,请远离", + "start_setup": "开始设置", + "successfully_calibrated": "{{module}}已成功校准" +} diff --git a/app/src/assets/localization/zh/pipette_wizard_flows.json b/app/src/assets/localization/zh/pipette_wizard_flows.json new file mode 100644 index 00000000000..1e75fe6223d --- /dev/null +++ b/app/src/assets/localization/zh/pipette_wizard_flows.json @@ -0,0 +1,90 @@ +{ + "align_the_connector": "对准对接孔,将移液器安装到工作站上。使用六角螺丝刀拧紧螺丝以固定移液器。然后手动检查其是否已完全固定。", + "all_pipette_detached": "所有移液器已成功卸下", + "are_you_sure_exit": "您确定要在完成{{flow}}之前退出吗?", + "attach_96_channel_plus_detach": "卸下{{pipetteName}}并安装96通道移液器", + "attach_96_channel": "安装96通道移液器", + "attach_mounting_plate_instructions": "对准对接孔,将移液器安装到工作站上。为了准确对齐,您可能需要调整右侧移液器支架的位置。", + "attach_mounting_plate": "安装固定板", + "attach_pip": "安装移液器", + "attach_pipette": "安装{{mount}}移液器", + "attach_probe": "安装校准探头", + "attach": "正在安装移液器", + "backmost": "最后面的", + "before_you_begin": "开始之前", + "begin_calibration": "开始校准", + "cal_pipette": "校准移液器", + "calibrate_96_channel": "校准96通道移液器", + "calibrate_pipette": "校准{{mount}}移液器", + "calibration_probe_touching": "校准探头将在甲板位{{slotNumber}}中的校准方框侧面进行接触,以确定其精确位置。", + "cancel_attachment": "取消安装", + "cancel_detachment": "取消卸下", + "choose_pipette": "选择要安装的移液器", + "complete_cal": "完成校准", + "connect_96_channel": "连接并安装96通道移液器", + "connect_and_secure_pipette": "连接并固定移液器", + "continue": "继续", + "critical_unskippable_step": "这是关键步骤,不应跳过", + "detach_96_attach_mount": "卸下96通道移液器并安装{{mount}}移液器", + "detach_96_channel": "卸下96通道移液器", + "detach_and_reattach": "卸下并重新安装移液器", + "detach_and_retry": "卸下并重试", + "detach_mount_attach_96": "卸下{{mount}}移液器并安装96通道移液器", + "detach_mounting_plate_instructions": "抓住板子,防止其掉落。拧开移液器固定板的销钉。", + "detach_next_pipette": "卸下下一个移液器", + "detach_pipette_to_attach_96": "卸下{{pipetteName}}并安装96通道移液器", + "detach_pipette": "卸下{{mount}}移液器", + "detach_pipettes_attach_96": "卸下移液器并安装96通道移液器", + "detach_z_axis_screw_again": "在安装96通道移液器前,先拧开Z轴螺丝。", + "detach": "正在卸下移液器", + "exit_cal": "退出校准", + "firmware_updating": "需要固件更新,仪器正在更新中...", + "firmware_up_to_date": "已是最新版本。", + "gantry_empty_for_96_channel_success": "现在两个移液器支架都为空,您可以开始进行96通道移液器的安装。", + "get_started_detach": "开始前,请移除甲板上的实验耗材,清理工作区以便卸载。同时准备好屏幕所示的所需设备。", + "grab_screwdriver": "保持原位不动,用2.5毫米螺丝刀,按照引导动画所示拧紧螺丝。在继续操作前,手动测试移液器安装和固定情况。", + "hold_and_loosen": "用手扶住移液器并拧松移液器螺丝。(螺丝是固定的,不会与移液器分离。)然后小心地卸下移液器。", + "hold_pipette_carefully": "用手扶住移液器防止掉落。对准对接孔,安装移液器。使用螺丝刀拧紧前面的四个螺丝,确保稳固连接。", + "how_to_reattach": "将右侧移液器支架推到Z轴的顶部。然后拧紧移液器支架右上方的固定螺丝。拧紧固定螺丝后,右侧支架应不能再自由上下移动。", + "install_probe": "从存放位置取出校准探头。确保其锁套旋钮已拧松。将校准探头对准移液器{{location}}位置,然后向上轻推并压到顶部。拧紧锁套旋钮。用手测试校准探头是否已固定。", + "loose_detach": "拧松螺丝并卸下", + "move_gantry_to_front": "将龙门架移至前方", + "must_detach_mounting_plate": "在使用其他移液器之前,您必须卸下安装板并重新连接移液器支架z轴板。我们不建议在完成前退出此过程。", + "name_and_volume_detected": "检测到{{name}}移液器", + "next": "下一步", + "ninety_six_channel": "{{ninetySix}}通道移液器", + "ninety_six_detached_success": "{{pipetteName}}移液器成功卸下", + "ninety_six_probe_location": "A1(左后方)", + "pip_cal_failed": "移液器校准失败", + "pip_cal_success": "{{pipetteName}}校准成功", + "pip_recal_success": "{{pipetteName}}重新校准成功", + "pipette_attached": "{{pipetteName}}成功安装", + "pipette_calibrating": "请远离,{{pipetteName}}正在校准", + "pipette_calibration": "移液器校准", + "pipette_detached": "移液器成功卸下", + "pipette_failed_to_attach": "无法检测到移液器", + "pipette_failed_to_detach": "{{pipetteName}}仍未卸下", + "pipette_heavy": "96通道移液器较重({{weight}})。如有需要,可以请其他人员帮忙。", + "please_install_correct_pip": "请改用{{pipetteName}}", + "progress_will_be_lost": "{{flow}}的进度将会丢失", + "reattach_carriage": "重新连接Z轴板", + "recalibrate_pipette": "重新校准{{mount}}移液器", + "remove_cal_probe": "移除校准探头", + "remove_labware_to_get_started": "开始前,请清除甲板上的实验耗材,清理工作区,以便校准。同时收集屏幕显示的所需设备。校准探头随工作站提供,应存放在工作站的右前方支柱上。", + "remove_labware": "开始前,请清除甲板上的实验耗材,清理工作区,以便校准。同时收集屏幕显示的所需设备。校准探头随工作站提供,应存放在工作站的右前方支柱上。", + "remove_probe": "拧松校准探头,将其从喷嘴上拆下,并放回存储位置。", + "replace_pipette": "更换{{mount}}移液器", + "return_probe_error": "退出前,请将校准探头放回其存放位置。 {{error}}", + "single_mount_attached_error": "当此为96通道流程时,请选择单支架移液器", + "single_or_8_channel": "{{single}}或{{eight}}通道移液器", + "stand_back": "请远离,工作站正在移动", + "try_again": "再试一次", + "unable_to_detect_probe": "无法检测到校准探头", + "unscrew_and_detach": "拧松螺丝并卸下安装板", + "unscrew_at_top": "拧松z轴板右上角的固定螺丝。这将解除右侧移液器支架的锁定,使其能够自由上下移动。", + "unscrew_carriage": "拧松Z轴板螺丝", + "waste_chute_error": "在继续之前,请卸下外置垃圾槽。", + "waste_chute_warning": "如果安装了外置垃圾槽,请在继续之前将其卸下。", + "wrong_pip": "安装了错误的设备", + "z_axis_still_attached": "Z轴板螺丝仍然处于锁定状态" +} diff --git a/app/src/assets/localization/zh/protocol_command_text.json b/app/src/assets/localization/zh/protocol_command_text.json new file mode 100644 index 00000000000..9d976c2bc88 --- /dev/null +++ b/app/src/assets/localization/zh/protocol_command_text.json @@ -0,0 +1,80 @@ +{ + "adapter_in_mod_in_slot": "{{adapter}}在{{slot}}的{{module}}上", + "adapter_in_slot": "{{adapter}}在{{slot}}上", + "aspirate": "从{{labware_location}}的{{labware}}的{{well_name}}孔中以{{flow_rate}}µL/秒的速度吸液{{volume}}µL", + "aspirate_in_place": "在原位以{{flow_rate}}µL/秒的速度吸液{{volume}}µL", + "blowout": "在{{labware_location}}的{{labware}}的{{well_name}}孔中以{{flow_rate}}µL/秒的速度排空", + "blowout_in_place": "在原位以{{flow_rate}}µL/秒的速度排空", + "closing_tc_lid": "关闭热循环仪热盖", + "comment": "解释", + "configure_for_volume": "配置{{pipette}}以吸液{{volume}}µL", + "configure_nozzle_layout": "配置{{pipette}}以使用{{amount}}个通道", + "confirm_and_resume": "确认并继续", + "deactivate_hs_shake": "停用震荡功能", + "deactivate_temperature_module": "停用温控模块", + "deactivating_hs_heater": "停用加热功能", + "deactivating_tc_block": "停用热循环仪温控功能", + "deactivating_tc_lid": "停用热循环仪热盖功能", + "degrees_c": "{{temp}}°C", + "detect_liquid_presence": "正在检测{{labware}}在{{labware_location}}中的{{well_name}}孔中的液体存在", + "disengaging_magnetic_module": "下降磁力架模块", + "dispense_push_out": "以{{flow_rate}}µL/秒的速度将{{volume}}µL 排液至{{labware_location}}的{{labware}}的{{well_name}}孔中,并推出{{push_out_volume}}µL", + "dispense": "以{{flow_rate}}µL/秒的速度将{{volume}}µL 排液至{{labware_location}}的{{labware}}的{{well_name}}孔中", + "dispense_in_place": "在原位以{{flow_rate}}µL/秒的速度排液{{volume}}µL", + "drop_tip": "将吸头丢入{{labware}}的{{well_name}}孔中", + "drop_tip_in_place": "在原位丢弃吸头", + "engaging_magnetic_module": "抬升磁力架模块", + "fixed_trash": "垃圾桶", + "home_gantry": "复位所有龙门架、移液器和柱塞轴", + "latching_hs_latch": "在热震荡模块上锁定实验耗材", + "left": "左", + "load_liquids_info_protocol_setup": "将{{liquid}}加载到{{labware}}中", + "load_module_protocol_setup": "在甲板槽{{slot_name}}中加载模块{{module}}", + "load_pipette_protocol_setup": "在{{mount_name}}支架上加载{{pipette_name}}", + "module_in_slot_plural": "{{module}}", + "module_in_slot": "{{module}}在{{slot_name}}号板位", + "move_labware_manually": "手动将{{labware}}从{{old_location}}移动到{{new_location}}", + "move_labware_on": "在{{robot_name}}上移动实验耗材", + "move_labware_using_gripper": "使用转板抓手将{{labware}}从{{old_location}}移动到{{new_location}}", + "move_labware": "移动实验耗材", + "move_relative": "沿{{axis}}轴移动{{distance}}毫米", + "move_to_coordinates": "移动到 (X:{{x}}, Y:{{y}}, Z:{{z}})", + "move_to_slot": "移动到{{slot_name}}号板位", + "move_to_well": "移动到{{labware_location}}的{{labware}}的{{well_name}}孔", + "move_to_addressable_area": "移动到{{addressable_area}}", + "move_to_addressable_area_drop_tip": "移动到{{addressable_area}}", + "notes": "备注", + "off_deck": "甲板外", + "offdeck": "甲板外", + "opening_tc_lid": "打开热循环仪热盖", + "pause_on": "在{{robot_name}}上暂停", + "pause": "暂停", + "pickup_tip": "从{{labware_location}}的{{labware}}的{{well_range}}孔中拾取吸头", + "prepare_to_aspirate": "准备使用{{pipette}}吸液", + "reloading_labware": "正在重新加载{{labware}}", + "return_tip": "将吸头返回到{{labware_location}}的{{labware}}的{{well_name}}孔中", + "right": "右", + "save_position": "保存位置", + "set_and_await_hs_shake": "设置热震荡模块以{{rpm}}rpm 震动并等待达到该转速", + "setting_hs_temp": "将热震荡模块的目标温度设置为{{temp}}", + "setting_temperature_module_temp": "将温控模块设置为{{temp}}(四舍五入到最接近的整数)", + "setting_thermocycler_block_temp": "将热循环仪加热块温度设置为{{temp}},并在达到目标后保持{{hold_time_seconds}}秒", + "setting_thermocycler_lid_temp": "将热循环仪热盖温度设置为{{temp}}", + "slot": "板位{{slot_name}}", + "target_temperature": "目标温度", + "tc_awaiting_for_duration": "等待热循环仪程序完成", + "tc_run_profile_steps": "温度:{{celsius}}°C,时间:{{seconds}}秒", + "tc_starting_profile": "热循环仪开始进行由以下步骤组成的{{repetitions}}次循环:", + "trash_bin_in_slot": "垃圾桶在{{slot_name}}", + "touch_tip": "吸头接触内壁", + "turning_rail_lights_off": "正在关闭导轨灯", + "turning_rail_lights_on": "正在打开导轨灯", + "unlatching_hs_latch": "解锁热震荡模块上的实验耗材", + "wait_for_duration": "暂停{{seconds}}秒。{{message}}", + "wait_for_resume": "暂停协议", + "waiting_for_hs_to_reach": "等待热震荡模块达到目标温度", + "waiting_for_tc_block_to_reach": "等待热循环仪加热块达到目标温度并保持指定时间", + "waiting_for_tc_lid_to_reach": "等待热循环仪热盖达到目标温度", + "waiting_to_reach_temp_module": "等待温控模块达到{{temp}}", + "waste_chute": "外置垃圾槽" +} diff --git a/app/src/assets/localization/zh/protocol_details.json b/app/src/assets/localization/zh/protocol_details.json new file mode 100644 index 00000000000..22f4c0566fc --- /dev/null +++ b/app/src/assets/localization/zh/protocol_details.json @@ -0,0 +1,99 @@ +{ + "add_required_csv_file": "添加所需的CSV文件以继续。", + "author": "作者", + "both_mounts": "两个移液器支架", + "choices": "{{count}}个选择", + "choose_file": "选择文件", + "choose_robot_to_run": "选择要运行{{protocol_name}}的工作站", + "clear_and_proceed_to_setup": "清除并继续设置", + "connect_modules_to_see_controls": "连接模块以查看控制", + "connected": "已连接", + "connection_status": "连接状态", + "creation_method": "创建方法", + "csv_file_type_required": "需要CSV文件类型", + "csv_file": "CSV文件", + "csv_required": "该协议需要CSV文件才能继续。", + "deck_view": "甲板视图", + "default_value": "默认值", + "delete_protocol_perm": "{{name}}及其运行历史将被永久删除。", + "delete_protocol": "删除协议", + "delete_this_protocol": "删除此协议?", + "description": "描述", + "extension_mount": "扩展安装支架", + "file_required": "需要文件", + "go_to_labware_definition": "转到实验耗材定义", + "go_to_timeline": "转到时间线", + "gripper_pick_up_count_description": "使用转板抓手移动单个耗材的指令。", + "gripper_pick_up_count": "转板次数", + "hardware": "硬件", + "labware_name": "耗材名称", + "labware": "耗材", + "last_analyzed": "上一次分析", + "last_updated": "上一次更新", + "left_and_right_mounts": "左+右安装架", + "left_mount": "左移液器安装位", + "left_right": "左,右", + "liquid_name": "液体名称", + "liquids_not_in_protocol": "此协议未指定任何液体", + "liquids": "液体", + "listed_values_are_view_only": "列出的值仅供查看", + "location": "位置", + "modules": "模块", + "n_a": "—", + "name": "名称", + "no_available_robots_found": "未找到可用的工作站", + "no_custom_values": "未指定任何自定义值", + "no_labware_specified": "此协议中未指定实验耗材", + "no_parameters": "该协议未指定任何参数", + "no_summary": "没有为此协议指定摘要。", + "not_connected": "未连接", + "not_in_protocol": "该协议未指定{{section}}", + "num_choices": "{{num}}个选择", + "num_options": "{{num}}个选项", + "off": "关闭", + "on_off": "开,关", + "on": "开启", + "org_or_author": "组织/作者", + "parameters": "参数", + "pipette_aspirate_count_description": "每个移液器的单个吸液指令。", + "pipette_aspirate_count": "{{pipette}}吸液次数", + "pipette_dispense_count_description": "每个移液器的单个排液指令。", + "pipette_dispense_count": "{{pipette}}分液次数", + "pipette_pick_up_count_description": "每个移液器的单个拾取吸头指令。", + "pipette_pick_up_count": "{{pipette}}拾取吸头次数", + "proceed_to_setup": "继续进行设置", + "protocol_designer_version": "在线协议编辑器{{version}}", + "protocol_failed_app_analysis": "该协议在应用程序内分析失败。它可能在没有自定义软件配置的工作站上无法使用。", + "protocol_outdated_app_analysis": "该协议的分析已过期。如果您现在运行它,可能会产生不同的结果。", + "python_api_version": "Python API {{version}}", + "quantity": "数量", + "range": "范围", + "read_less": "读取更少", + "read_more": "读取更多", + "requires_csv": "需要CSV", + "requires_upload": "需要上传", + "restore_defaults": "恢复默认值", + "right_mount": "右移液器安装位", + "robot_configuration": "工作站配置", + "robot_is_busy_with_protocol": "{{robotName}}正在运行{{protocolName}},状态为{{runStatus}}。是否要清除并继续?", + "robot_is_busy": "{{robotName}}正在工作", + "robot": "工作站", + "run_protocol": "运行协议", + "select_parameters_for_robot": "选择{{robot_name}}的参数", + "send": "发送", + "sending": "发送中", + "show_in_folder": "在文件夹中显示", + "slot": "{{slotName}}号板位", + "start_setup_customize_values": "开始设置以自定义值", + "start_setup": "开始设置", + "successfully_sent": "发送成功", + "total_volume": "总体积", + "unavailable_or_busy_robot_not_listed_plural": "{{count}}台不可用或工作中的工作站未列出。", + "unavailable_or_busy_robot_not_listed": "{{count}}台不可用或工作中的工作站未列出。", + "unavailable_robot_not_listed_plural": "{{count}}台不可用的工作站未列出。", + "unavailable_robot_not_listed": "{{count}}台不可用的工作站未列出。.", + "unsuccessfully_sent": "发送失败", + "value_out_of_range": "值必须在{{min}}-{{max}}之间", + "view_run_details": "查看运行详情", + "view_unavailable_robots": "在设备页面查看不可用的工作站" +} diff --git a/app/src/assets/localization/zh/protocol_info.json b/app/src/assets/localization/zh/protocol_info.json new file mode 100644 index 00000000000..073b4fd6319 --- /dev/null +++ b/app/src/assets/localization/zh/protocol_info.json @@ -0,0 +1,87 @@ +{ + "all_protocols": "所有协议", + "browse_protocol_library": "打开在线协议库", + "cancel_run": "取消运行", + "choose_file": "选择文件...", + "choose_snippet_type": "根据目标执行环境选择耗材校准数据的Python代码片段。", + "continue_proceed_to_calibrate": "继续校准", + "continue_verify_calibrations": "验证移液器和耗材的校准", + "create_or_download": "创建或下载新协议", + "creation_method": "创建方法", + "custom_labware_not_supported": "工作站不支持自定义耗材", + "date_added": "添加日期", + "delete_protocol": "删除协议", + "description": "描述", + "drag_file_here": "将协议文件拖放到此处", + "error_analyzing": "尝试分析时出错:{{protocolName}}", + "error_message_no_steps": "此协议没有任何步骤——您的工作站无法执行任何操作!您的协议至少需要一个吸液/排液步骤才能正确导入", + "estimated_run_time": "预计运行时间", + "exit_modal_body": "您确定要关闭此协议吗?", + "exit_modal_exit": "是,现在关闭", + "exit_modal_go_back": "不,返回", + "exit_modal_heading": "确认关闭协议", + "failed_analysis": "分析失败", + "get_labware_offset_data": "获取耗材校准数据", + "import_a_file": "导入协议以开始", + "import_new_protocol": "导入协议", + "import": "导入", + "incompatible_file_type": "不兼容的文件类型", + "instrument_cal_data_title": "校准数据", + "instrument_not_attached": "未连接", + "instruments_title": "所需移液器", + "intervention_modal_learn_more": "了解更多关于用户干预的信息", + "invalid_file_type": "无效的文件类型。协议文件必须是“.py”或“.json”格式。", + "invalid_json_file": "无效的JSON文件。", + "labware_cal_description": "以耗材原点的校准数据", + "labware_legacy_definition": "不适用", + "labware_offset_data_title": "耗材校准数据", + "labware_offsets_info": "{{number}}组耗材校准数据", + "labware_position_check_complete_toast_no_offsets": "耗材位置校准完成。无新建的耗材校准数据。", + "labware_position_check_complete_toast_with_offsets_plural": "耗材位置校准完成。新建了{{count}}组耗材校准数据。", + "labware_position_check_complete_toast_with_offsets": "耗材位置校准完成。创建了{{count}}组耗材校准数据。", + "labware_title": "所需耗材", + "last_run": "上次运行", + "last_updated": "最近更新", + "launch_protocol_designer": "打开在线协议编辑器", + "manual_steps_learn_more": "了解更多关于手动步骤的信息", + "modules_title": "所需模块", + "most_recent_updates": "按时间排序", + "no_history": "无运行历史", + "no_labware_offset_data": "无耗材校准数据", + "no_protocol_yet": "还没有协议?", + "nothing_here_yet": "没有可显示的协议!", + "oldest_updates": "按时间倒序排序", + "open_a_protocol": "打开一个协议以开始", + "open_api_docs": "打开Python API文档", + "organization_and_author": "组织/作者", + "pin_protocol": "固定协议", + "pinned_protocol": "固定的协议", + "pinned_protocols": "固定的协议", + "protocol_added": "已成功接收{{protocol_name}}", + "protocol_analysis_failed": "协议分析失败", + "protocol_finishing": "在{{robot_name}}上完成协议", + "protocol_loading": "在{{robot_name}}上打开协议", + "protocol_name_title": "协议名称", + "protocol_title": "协议 -{{protocol_name}}", + "protocol_upload_failed": "协议上传失败。请修复错误后重试", + "protocols": "协议", + "required_cal_data_title": "校准数据", + "required_quantity_title": "数量", + "required_type_title": "类型", + "requires_csv": "需要CSV", + "robot_name_last_run": "{{robot_name}}的上次运行", + "robot_type_first": "{{robotType}}协议优先", + "run_again": "再次运行", + "run_protocol": "运行协议", + "run_timestamp_title": "运行时间戳", + "simulation_in_progress": "正在进行模拟", + "timestamp": "+{{index}}", + "too_many_pins_body": "要添加更多协议到您的固定列表,请先移除一个协议。", + "too_many_pins_header": "您已达到最大限额!", + "unpin_protocol": "取消固定协议", + "unpinned_protocol": "取消固定的协议", + "update_robot_for_custom_labware": "您已将自定义耗材保存到您的应用程序中,但需要更新此工作站才能将这些自定义耗材与Python协议一起使用", + "upload": "上传", + "upload_and_simulate": "打开要在{{robot_name}}上运行的协议", + "valid_file_types": "有效的文件类型:Python文件(.py)或在线协议编辑器文件(.json)" +} diff --git a/app/src/assets/localization/zh/protocol_list.json b/app/src/assets/localization/zh/protocol_list.json new file mode 100644 index 00000000000..9c2b3be0f59 --- /dev/null +++ b/app/src/assets/localization/zh/protocol_list.json @@ -0,0 +1,26 @@ +{ + "csv_file_required": "需要CSV文件进行分析。请在运行设置时添加CSV文件。", + "delete_protocol_message": " 并且其运行历史记录将被永久删除。", + "delete_this_protocol": "删除此协议?", + "edit": "编辑", + "go_to_timeline": "转到时间轴", + "last_updated_at": "更新于{{date}}", + "left_mount": "左移液器安装位", + "loading_data": "正在加载数据...", + "modules": "模块", + "no_data": "无数据", + "protocol_analysis_failure": "协议分析失败。", + "protocol_analysis_outdated": "协议分析已过期。", + "protocol_deleted": "协议已删除", + "reanalyze_or_view_error": " 重新分析 协议或查看 错误详情 ", + "reanalyze_to_view": " 重新分析 协议", + "right_mount": "右移液器安装位", + "robot": "工作站", + "send_to_robot_overflow": "发送到{{robot_display_name}}", + "send_to_robot": "将协议发送到{{robot_display_name}}", + "show_in_folder": "在文件夹中显示", + "start_setup": "开始设置", + "this_protocol_will_be_trashed": "该协议将被移至此计算机的回收站,可能无法恢复。", + "updated": "已更新", + "yes_delete_this_protocol": "是的,删除协议" +} diff --git a/app/src/assets/localization/zh/protocol_setup.json b/app/src/assets/localization/zh/protocol_setup.json new file mode 100644 index 00000000000..40a5c8c00f9 --- /dev/null +++ b/app/src/assets/localization/zh/protocol_setup.json @@ -0,0 +1,315 @@ +{ + "96_mount": "左+右移液器安装位", + "action_needed": "需要操作", + "adapter_slot_location_module": "{{slotName}}号板位,{{adapterName}}在{{moduleName}}上", + "adapter_slot_location": "{{slotName}}号板位,{{adapterName}}", + "add_fixture": "将{{fixtureName}}添加到{{locationName}}", + "add_this_deck_hardware": "将此硬件添加到您的甲板配置中。它将在协议分析期间被引用。", + "add_to_slot": "添加到{{slotName}}号板位", + "additional_labware": "{{count}}个额外的耗材", + "additional_off_deck_labware": "额外的甲板外耗材", + "all_files_associated": "与协议运行相关文件的所有详细信息均可在工作站屏幕上获得。", + "applied_labware_offset_data": "已应用的实验耗材偏移数据", + "applied_labware_offsets": "已应用的实验耗材偏移", + "are_you_sure_you_want_to_proceed": "您确定要继续运行吗?", + "attach_gripper_failure_reason": "连接所需的转板抓手以继续", + "attach_gripper": "连接转板抓手", + "attach_module": "校准前连接模块", + "attach_pipette_before_module_calibration": "在进行模块校准前连接移液器", + "attach_pipette_calibration": "连接移液器以查看校准信息", + "attach_pipette_cta": "连接移液器", + "attach_pipette_failure_reason": "连接所需的移液器以继续", + "attach_pipette_tip_length_calibration": "连接移液器以查看吸头长度校准信息", + "attach": "连接", + "back_to_top": "回到顶部", + "cal_all_pip": "首先校准移液器", + "calibrate_deck_failure_reason": "校准甲板以继续", + "calibrate_deck_to_proceed_to_pipette_calibration": "校准甲板以继续进行移液器校准", + "calibrate_deck_to_proceed_to_tip_length_calibration": "校准甲板以继续进行吸头长度校准", + "calibrate_gripper_failure_reason": "校准所需的转板抓手以继续", + "calibrate_module_failure_reason": "校准所需的模块以继续", + "calibrate_now": "现在校准", + "calibrate_pipette_before_module_calibration": "在进行模块校准前校准移液器", + "calibrate_pipette_failure_reason": "校准所需的移液器以继续", + "calibrate_tiprack_failure_reason": "校准所需的吸头长度以继续", + "calibrate": "校准", + "calibrated": "已校准", + "calibration_data_not_available": "一旦运行开始,校准数据不可用", + "calibration_needed": "需要校准", + "calibration_ready": "校准就绪", + "calibration_required_attach_pipette_first": "需要校准,请先连接移液器", + "calibration_required_calibrate_pipette_first": "需要校准,请先校准移液器", + "calibration_required": "需要校准", + "calibration_status": "校准状态", + "calibration": "校准", + "cancel_and_restart_to_edit": "取消运行并重新启动设置以进行编辑", + "choose_csv_file": "选择CSV文件", + "choose_enum": "选择{{displayName}}", + "closing": "关闭中...", + "complete_setup_before_proceeding": "完成设置后继续运行", + "configure": "配置", + "configured": "已配置", + "confirm_heater_shaker_module_modal_description": "在开始运行之前,应使模块的两个锚固件完全伸出,以确保牢固连接。导热适配器应连接到模块上。", + "confirm_heater_shaker_module_modal_title": "确认已连接热震荡模块", + "confirm_liquids": "确认液体", + "confirm_locations_and_volumes": "确认位置和体积", + "confirm_offsets": "确认偏移校准数据", + "confirm_placements": "确认放置位置", + "confirm_selection": "确认选择", + "confirm_values": "确认这些值", + "connect_all_hardware": "首先连接并校准所有硬件", + "connect_all_mod": "首先连接所有模块", + "connect_modules_for_controls": "连接模块以查看控制", + "connection_info_not_available": "一旦运行开始,连接信息不可用", + "connection_status": "连接状态", + "csv_files_on_robot": "工作站上的CSV文件", + "csv_files_on_usb": "USB上的CSV文件", + "csv_file": "CSV 文件", + "currently_configured": "当前已配置", + "currently_unavailable": "当前不可用", + "custom_values": "自定义值", + "deck_cal_description_bullet_1": "在新工作站设置期间执行甲板校准。", + "deck_cal_description_bullet_2": "如果您搬迁了工作站,请重新进行甲板校准。", + "deck_cal_description": "这测量了甲板的 X 和 Y 值相对于门架的值。甲板校准是吸头长度校准和移液器偏移校准的基础。", + "deck_calibration_title": "甲板校准(Deck Calibration)", + "deck_conflict_info_thermocycler": "通过移除位置 A1 和 B1 中的固定装置来更新甲板配置。从甲板配置中移除对应装置或更新协议。", + "deck_conflict_info": "通过移除位置 {{cutout}} 中的 {{currentFixture}} 来更新甲板配置。从甲板配置中移除对应装置或更新协议。", + "deck_conflict": "甲板位置冲突", + "deck_hardware_ready": "甲板硬件准备", + "deck_hardware": "甲板硬件", + "deck_map": "甲板布局图", + "default_values": "默认值", + "download_files": "下载文件", + "example": "示例", + "exit_to_deck_configuration": "退出到甲板配置", + "extension_mount": "扩展安装支架", + "extra_attention_warning_title": "在继续运行前固定耗材和模块", + "extra_module_attached": "附加额外模块", + "feedback_form_link": "请告诉我们", + "fixture_name": "装置", + "fixture": "装置", + "fixtures_connected_plural": "已连接{{count}}个装置", + "fixtures_connected": "已连接{{count}}个装置", + "get_labware_offset_data": "获取耗材校准数据", + "hardware_missing": "缺少硬件", + "heater_shaker_extra_attention": "使用闩锁控制,便于放置耗材。", + "heater_shaker_labware_list_view": "要添加耗材,请使用切换键来控制闩锁", + "how_offset_data_works": "耗材校准数据如何工作", + "individiual_well_volume": "单个孔体积", + "initial_liquids_num_plural": "{{count}}种初始液体", + "initial_liquids_num": "{{count}}种初始液体", + "initial_location": "初始位置", + "install_modules_and_fixtures": "安装并校准所需的模块。安装所需的装置。", + "install_modules_plural": "安装所需的模块。", + "install_modules": "安装所需的模块。", + "instrument_calibrations_missing_plural": "缺少{{count}}个校准", + "instrument_calibrations_missing": "缺少{{count}}个校准", + "instruments_connected_plural": "已连接{{count}}个硬件", + "instruments_connected": "已连接{{count}}个硬件", + "instruments": "硬件", + "labware_latch_instructions": "使用闩锁控制,便于放置耗材。", + "labware_latch": "耗材闩锁", + "labware_location": "耗材位置", + "labware_name": "耗材名称", + "labware_placement": "实验耗材放置", + "labware_position_check_not_available_analyzing_on_robot": "在工作站上分析协议时,耗材位置校准不可用", + "labware_position_check_not_available_empty_protocol": "耗材位置校准需要协议加载耗材和移液器", + "labware_position_check_not_available": "运行开始后,耗材位置校准不可用", + "labware_position_check_step_description": "建议的工作流程可帮助您验证每个耗材在甲板上的位置。", + "labware_position_check_step_title": "耗材位置校准", + "labware_position_check_text": "耗材位置校准流程可帮助您验证甲板上每个耗材的位置。在此位置校准过程中,您可以创建耗材校准数据,以调整工作站在 X、Y 和 Z 方向上的移动。", + "labware_position_check": "耗材位置校准", + "labware_setup_step_description": "准备好以下耗材和完整的吸头盒。若不进行耗材位置校准直接运行协议,请将耗材放置在其初始位置并固定。", + "labware_setup_step_title": "耗材", + "labware": "耗材", + "last_calibrated": "最后校准:{{date}}", + "learn_how_it_works": "了解它的工作原理", + "learn_more_about_offset_data": "了解更多关于耗材校准数据的信息", + "learn_more_about_robot_cal_link": "了解更多关于工作站校准的信息", + "learn_more": "了解更多", + "liquid_information": "液体信息", + "liquid_name": "液体名称", + "liquid_setup_step_description": "查看液体的起始位置和体积", + "liquid_setup_step_title": "液体", + "liquids_confirmed": "液体已确认", + "liquids_not_in_setup": "此协议未使用液体", + "liquids_not_in_the_protocol": "此协议未指定液体。", + "liquids_ready": "液体准备", + "liquids": "液体", + "list_view": "列表视图", + "loading_data": "加载数据...", + "loading_labware_offsets": "加载耗材校准数据", + "loading_protocol_details": "加载详情...", + "location_conflict": "位置冲突", + "location": "位置", + "lpc_and_offset_data_title": "耗材位置校准和耗材校准数据", + "lpc_disabled_calibration_not_complete": "确保工作站校准完成后再运行耗材位置校准", + "lpc_disabled_modules_and_calibration_not_complete": "确保工作站校准完成并且所有模块已连接后再运行耗材位置校准", + "lpc_disabled_modules_not_connected": "确保所有模块已连接后再运行耗材位置校准", + "lpc_disabled_no_tipracks_loaded": "耗材位置校准需要在协议中加载一个吸头盒", + "lpc_disabled_no_tipracks_used": "耗材位置校准要求协议中至少有一个吸头可供使用", + "map_view": "布局视图", + "missing_gripper": "缺少转板抓手", + "missing_instruments": "缺少{{count}}个", + "missing_pipettes_plural": "缺少{{count}}个移液器", + "missing_pipettes": "缺少{{count}}个移液器", + "missing": "缺少", + "modal_instructions_title": "{{moduleName}}设置说明", + "module_connected": "已连接", + "module_disconnected": "未连接", + "module_instructions_link": "{{moduleName}}设置说明", + "module_mismatch_body": "检查连接到该工作站的模块型号是否正确", + "module_name": "模块", + "module_not_connected": "未连接", + "module_setup_step_ready": "校准准备", + "module_setup_step_title": "甲板硬件", + "module_slot_location": "{{slotName}}号板位,{{moduleName}}", + "module": "模块", + "modules_connected_plural": "连接了{{count}}个模块", + "modules_connected": "连接了{{count}}个模块", + "modules_setup_step_title": "模块设置", + "modules": "模块", + "mount_title": "{{mount}}安装支架:", + "mount": "{{mount}}安装支架", + "multiple_fixtures_missing": "缺少{{count}}个装置", + "multiple_modules_example": "您的协议包含两个温控模块。连接到左侧第一个端口的温控模块对应协议中的第一个温控模块,连接到下一个端口的温控模块对应协议中的第二个温控模块。如果使用集线器,遵循相同的端口排序逻辑。", + "multiple_modules_explanation": "在协议中使用多个相同类型的模块时,首先需要将协议中第一个模块连接到工作站编号最小的USB端口,然后以相同方式连接其他模块。", + "multiple_modules_help_link_title": "查看如何设置相同类型的多个模块", + "multiple_modules_learn_more": "了解更多关于使用相同类型的多个模块的信息", + "multiple_modules_missing_plural": "缺少{{count}}个模块", + "multiple_modules_modal": "设置相同类型的多个模块", + "multiple_modules": "相同类型的多个模块", + "multiple_of_most_modules": "通过以特定顺序连接和加载模块,可以在单个Python协议中使用多种模块类型。无论模块占用哪个甲板板位,工作站都将首先初始化连接到最小编号端口的匹配模块,。", + "must_have_labware_and_pip": "协议中必须加载耗材和移液器", + "n_a": "不可用", + "name": "名称", + "no_custom_values": "未指定自定义值", + "no_data": "无数据", + "no_deck_hardware_specified": "该协议中未指定任何甲板硬件。", + "no_files_found": "未找到文件", + "no_labware_offset_data": "尚无耗材校准数据", + "no_modules_or_fixtures": "该协议中未指定任何模块或装置。", + "no_modules_specified": "该协议中未指定任何模块。", + "no_modules_used_in_this_protocol": "该协议中未使用硬件", + "no_parameters_specified_in_protocol": "协议中未指定任何参数", + "no_parameters_specified": "未指定参数", + "no_tiprack_loaded": "协议中必须加载一个吸头盒", + "no_tiprack_used": "协议中必须拾取一个吸头", + "no_usb_connection_required": "无需USB连接", + "no_usb_port_yet": "尚无USB端口", + "no_usb_required": "无需USB", + "not_calibrated": "尚未校准", + "not_configured": "未配置", + "off_deck": "甲板外", + "off": "关闭", + "offset_data": "偏移校准数据", + "offsets_applied_plural": "应用了{{count}}个偏移校准数据", + "offsets_applied": "应用了{{count}}个偏移校准数据", + "offsets_confirmed": "偏移校准数据已确认", + "offsets_ready": "偏移校准数据准备", + "on_adapter_in_mod": "在{{moduleName}}中的{{adapterName}}上", + "on_adapter": "在{{adapterName}}上", + "on_deck": "在甲板上", + "on-deck_labware": "{{count}}个在甲板上的耗材", + "on": "开启", + "opening": "打开中...", + "parameters": "参数", + "pipette_mismatch": "移液器型号不匹配。", + "pipette_missing": "移液器缺失", + "pipette_offset_cal_description_bullet_1": "首次将移液器连接到新安装支架时执行移液器偏移校准。", + "pipette_offset_cal_description_bullet_2": "在执行甲板校准后重新进行移液器偏移校准。", + "pipette_offset_cal_description_bullet_3": "对用于校准移液器的吸头执行吸头长度校准后,重新进行移液器偏移校准。", + "pipette_offset_cal_description": "这会测量移液器相对于移液器安装支架和甲板的X、Y和Z值。移液器偏移校准依赖于甲板校准和吸头长度校准。 ", + "pipette_offset_cal": "移液器偏移校准", + "placements_confirmed": "位置已确认", + "placements_ready": "位置准备", + "placement": "放置", + "plug_in_module_to_configure": "插入{{module}}以将其添加到板位", + "plug_in_required_module_plural": "插入并启动所需模块以继续", + "plug_in_required_module": "插入并启动所需模块以继续", + "prepare_to_run": "准备运行", + "proceed_to_labware_position_check": "继续进行耗材位置校准", + "proceed_to_labware_setup_step": "继续进行耗材设置", + "proceed_to_liquid_setup_step": "继续进行液体设置", + "proceed_to_module_setup_step": "继续进行模块设置", + "proceed_to_run": "继续运行", + "protocol_analysis_failed": "协议分析失败", + "protocol_can_be_closed": "该协议现在可以关闭。", + "protocol_requires_csv": "此协议需要一个CSV文件。点击下面的CSV文件进行选择。", + "protocol_run_canceled": "协议运行已取消。", + "protocol_run_complete": "协议运行完成。", + "protocol_run_failed": "协议运行失败。", + "protocol_run_started": "协议运行已开始。", + "protocol_run_stopped": "协议运行已停止。", + "protocol_specifies": "协议指定", + "protocol_upload_revamp_feedback": "对此体验有反馈吗?", + "quantity": "数量", + "recalibrate": "重新校准", + "recalibrating_not_available": "无法重新进行吸头长度校准和耗材位置校准。", + "recalibrating_tip_length_not_available": "运行开始后无法重新校准吸头长度", + "recommended": "推荐", + "required_instrument_calibrations": "所需的硬件校准", + "required": "必需", + "required_tip_racks_title": "所需的吸头长度校准", + "reset_parameter_values_body": "这将丢弃您所做的任何更改。所有参数将恢复默认值。", + "reset_parameter_values": "重置参数值?", + "reset_setup": "重新开始设置以进行编辑", + "reset_values": "重置值", + "resolve": "解决", + "restart_setup_and_try": "重新开始设置并尝试使用不同的参数值。", + "restart_setup": "重新开始设置", + "restore_default": "恢复默认值", + "restore_defaults": "恢复默认值", + "robot_cal_description": "工作站校准用于确定其相对于甲板的位置。良好的工作站校准对于成功运行协议至关重要。工作站校准包括3个部分:甲板校准、吸头长度校准和移液器偏移校准。", + "robot_cal_help_title": "工作站校准的工作原理", + "robot_calibration_step_description_pipettes_only": "查看该协议所需的硬件和校准。", + "robot_calibration_step_description": "查看该协议所需的移液器和吸头长度校准。", + "robot_calibration_step_ready": "校准准备", + "robot_calibration_step_title": "硬件", + "run_disabled_calibration_not_complete": "确保工作站校准完成后再继续运行", + "run_disabled_modules_and_calibration_not_complete": "确保工作站校准完成并且所有模块已连接后再继续运行", + "run_disabled_modules_not_connected": "确保所有模块已连接后再继续运行", + "run_labware_position_check_to_get_offsets": "运行实验室位置检查以获取实验室偏移数据。", + "run_labware_position_check": "运行耗材位置校准", + "run_never_started": "运行未开始", + "run": "运行", + "secure_labware_instructions": "固定耗材说明", + "secure_labware_modal": "将耗材固定到{{name}}", + "secure": "固定", + "setup_for_run": "运行设置", + "setup_instructions": "设置说明", + "setup_is_view_only": "运行开始后设置仅供查看", + "slot_location": "{{slotName}}号板位", + "slot_number": "板位编号", + "stacked_slot": "堆叠槽", + "start_run": "开始运行", + "status": "状态", + "step": "步骤{{index}}", + "there_are_no_unconfigured_modules": "没有连接{{module}}。请连接一个模块并放置在{{slot}}号板位中。", + "there_are_other_configured_modules": "已有一个{{module}}配置在不同的板位中。退出运行设置,并更新甲板配置以转到已连接的模块。或连接另一个{{module}}继续设置。", + "tip_length_cal_description_bullet": "对移液器上将会用到的每种类型的吸头执行吸头长度校准。", + "tip_length_cal_description": "这将测量吸头底部与移液器喷嘴之间的Z轴距离。如果对用于校准移液器的吸头重新进行吸头长度校准,也需要重新进行移液器偏移校准。", + "tip_length_cal_title": "吸头长度校准", + "tip_length_calibration": "吸头长度校准", + "total_liquid_volume": "总体积", + "update_deck_config": "更新甲板配置", + "update_deck": "更新甲板", + "update_offsets": "更新偏移校准数据", + "updated": "已更新", + "usb_connected_no_port_info": "USB端口已连接", + "usb_drive_notification": "在运行开始前,请保持USB处于连接状态", + "usb_port_connected": "USB端口{{port}}", + "usb_port_number": "USB-{{port}}", + "value_out_of_range_generic": "值必须在范围内", + "value_out_of_range": "值必须在{{min}}-{{max}}之间", + "value": "值", + "values_are_view_only": "值仅供查看", + "variable_well_amount": "可变孔数", + "view_current_offsets": "查看当前偏移量", + "view_moam": "查看工作站中放置同类型模块的设置说明。", + "view_setup_instructions": "查看设置说明", + "volume": "体积", + "what_labware_offset_is": "耗材偏移校准是一种位置调整类型,用于补偿甲板上耗材整体位置的微小实际差异。耗材偏移校准数据是耗材、甲板位和工作站的特定组合。", + "with_the_chosen_value": "使用选定的值时,发生以下错误:", + "you_havent_confirmed": "您尚未确认 {{missingSteps}}。在继续运行协议之前,请确保这些步骤正确无误。" +} diff --git a/app/src/assets/localization/zh/quick_transfer.json b/app/src/assets/localization/zh/quick_transfer.json new file mode 100644 index 00000000000..5ecce11bb9c --- /dev/null +++ b/app/src/assets/localization/zh/quick_transfer.json @@ -0,0 +1,156 @@ +{ + "a_way_to_move_liquid": "一种将单一液体从一个实验耗材移动到另一个实验耗材的方法。", + "add_or_remove_columns": "添加或移除列", + "add_or_remove": "添加或移除", + "advanced_setting_disabled": "此移液的高级设置已禁用", + "advanced_settings": "高级设置", + "air_gap_before_dispensing": "在分液前设置空气间隙", + "air_gap_capacity_error": "移液器空间已满,无法添加空气间隙。", + "air_gap_value": "{{volume}} µL", + "air_gap_volume_µL": "空气间隙体积(µL)", + "air_gap": "空气间隙", + "all": "所有实验耗材", + "always": "每次吸液前", + "aspirate_flow_rate_µL": "吸取流速(µL/s)", + "aspirate_flow_rate": "吸取流速", + "aspirate_settings": "吸取设置", + "aspirate_tip_position": "吸取移液器位置", + "aspirate_volume": "每孔吸液体积", + "aspirate_volume_µL": "每孔吸液体积(µL)", + "attach_pipette": "连接移液器", + "blow_out_after_dispensing": "分液后吹出", + "blow_out_destination_well": "目标孔", + "blow_out_into_destination_well": "到目标孔", + "blow_out_into_source_well": "到吸液孔", + "blow_out_into_trash_bin": "到垃圾桶", + "blow_out_into_waste_chute": "到外置垃圾槽", + "blow_out_source_well": "源孔", + "blow_out_trash_bin": "垃圾桶", + "blow_out_waste_chute": "外置垃圾槽", + "blow_out": "吹出", + "both_mounts": "左侧+右侧支架", + "change_tip": "更换吸头", + "character_limit_error": "字数超出限制", + "column": "列", + "columns": "列", + "consolidate_volume_error": "所选的目标孔太小,无法合并。请尝试从更少的孔中合并。", + "create_new_to_edit": "创建新的快速移液以进行编辑", + "create_new_transfer": "创建新的快速移液命令", + "create_to_get_started": "创建新的快速移液以开始操作。", + "create_transfer": "创建移液命令", + "delay_before_dispensing": "分液前的延迟", + "delay_duration_s": "延迟时长(秒)", + "delay_position_mm": "距孔底延迟时的位置(mm)", + "delay_value": "{{delay}}秒,距离孔底{{position}}mm", + "delay": "延迟", + "delete_this_transfer": "确定删除此这个快速移液?", + "delete_transfer": "删除快速移液", + "deleted_transfer": "已删除快速移液", + "destination": "目标", + "destination_labware": "目标实验耗材", + "disabled": "已禁用", + "dispense_flow_rate_µL": "分液流速(µL/s)", + "dispense_flow_rate": "分液流速", + "dispense_settings": "分液设置", + "dispense_tip_position": "分液吸头位置", + "dispense_volume_µL": "每孔排液体积(µL)", + "dispense_volume": "每孔排液体积", + "disposal_volume_µL": "废液量(µL)", + "distance_bottom_of_well_mm": "距离孔底的高度(mm)", + "distribute_volume_error": "所选源孔太小,无法从中分液。请尝试向更少的孔中分液。", + "enter_characters": "输入最多60个字符", + "error_analyzing": "在尝试分析{{transferName}}时发生错误。", + "exit_quick_transfer": "退出快速移液?", + "failed_analysis": "分析失败", + "flow_rate_value": "{{flow_rate}} µL/s", + "got_it": "明白了", + "grid": "网格", + "grids": "网格", + "labware": "实验耗材", + "learn_more": "了解更多", + "left_mount": "左侧支架", + "lose_all_progress": "您将失去所有此快速移液流程进度.", + "mix_before_aspirating": "在吸液前混匀", + "mix_before_dispensing": "在分液前混匀", + "mix_repetitions": "混匀重复次数", + "mix_value": "{{volume}} µL,混匀{{reps}}次", + "mix_volume_µL": "混匀体积(µL)", + "mix": "混匀", + "name_your_transfer": "为您的快速移液流程命名", + "none_to_show": "没有快速移液可显示!", + "number_wells_selected_error_learn_more": "具有多个源孔{{selectionUnits}}的快速移液是可以进行一对一或者多对多移液的(为此移液流程同样选择{{wellCount}}个目标孔位{{selectionUnits}})或进行多对一移液,即合并为单个孔位(选择1个目标孔{{selectionUnit}})。", + "number_wells_selected_error_message": "选择1个或{{wellCount}}个{{selectionUnits}}来进行此移液操作.", + "once": "在移液开始时", + "option_disabled": "已禁用", + "option_enabled": "已启用", + "overview": "概览", + "perDest": "每个目标孔位", + "perSource": "每个源孔位", + "pin_transfer": "快速移液", + "pinned_transfer": "固定快速移液", + "pinned_transfers": "固定快速移液", + "pipette_path_multi_aspirate": "多次吸取", + "pipette_path_multi_dispense_volume_blowout": "多次分液,{{volume}} 微升废弃量,在{{blowOutLocation}}吹出", + "pipette_path_multi_dispense": "多次分液", + "pipette_path_single": "单次转移", + "pipette_path": "移液器路径", + "pipette_currently_attached": "快速移液移液器选项取决于当前您工作站上安装的移液器.", + "pipette": "移液器", + "pre_wet_tip": "润湿吸头", + "quick_transfer_volume": "快速移液{{volume}}µL", + "quick_transfer": "快速移液", + "right_mount": "右侧支架", + "reservoir": "储液槽", + "run_now": "立即运行", + "run_quick_transfer_now": "您想立即运行快速移液流程吗?", + "run_transfer": "运行快速移液", + "save": "保存", + "save_to_run_later": "保存您的快速移液流程以备后续运行.", + "save_for_later": "保存备用", + "select_attached_pipette": "选择已连接的移液器", + "select_by": "按...选择", + "select_dest_labware": "选择目标实验耗材", + "select_dest_wells": "选择目标孔位", + "select_source_labware": "选择源实验耗材", + "select_source_wells": "选择源孔位", + "select_tip_rack": "选择吸头盒", + "set_aspirate_volume": "设置吸液体积", + "set_dispense_volume": "设置排液体积", + "set_transfer_volume": "设置移液体积", + "source_labware": "源实验耗材", + "source_labware_c2": "C2 中的源实验耗材", + "source": "源", + "starting_well": "起始孔", + "storage_limit_reached": "已达到存储限制", + "use_deck_slots": "快速移液将使用板位B2-D2。这些板位将用于放置吸头盒、源实验耗材和目标实验耗材。请确保使用最新的甲板配置,避免碰撞。", + "tip_drop_location": "吸头丢弃位置", + "tip_management": "吸头管理", + "tip_position_value": "距底部 {{position}} mm", + "tip_position": "移液器位置", + "tip_rack": "吸头盒", + "too_many_pins_body": "删除一个快速移液,以便向您的固定列表中添加更多传输。", + "too_many_pins_header": "您已达到上限!", + "touch_tip_before_dispensing": "在分液前做碰壁动作", + "touch_tip_position_mm": "在孔底部做碰壁动作的高度(mm)", + "touch_tip_value": "距底部 {{position}} mm", + "touch_tip": "碰壁动作", + "transfer_analysis_failed": "快速移液分析失败", + "transfer_name": "移液名称", + "trashBin": "垃圾桶", + "trashBin_location": "位于{{slotName}}的垃圾桶", + "tubeRack": "试管架", + "unpin_transfer": "取消固定的快速移液", + "unpinned_transfer": "已取消固定的快速移液", + "volume_per_well": "每孔体积", + "volume_per_well_µL": "每孔体积(µL)", + "value_out_of_range": "值必须在{{min}}-{{max}}之间", + "wasteChute": "外置垃圾槽", + "wasteChute_location": "位于{{slotName}}的外置垃圾槽", + "welcome_to_quick_transfer": "欢迎使用快速移液!", + "wellPlate": "孔板", + "well_selection": "孔位选择", + "well_ratio": "快速移液可以一对一或者多对多进行移液的(为此移液操作选择同样数量的{{wells}})或可以多对一,也就是合并为单孔(选择1个目标孔位)。", + "well": "孔", + "wells": "孔", + "will_be_deleted": "{{transferName}} 将被永久删除。" +} diff --git a/app/src/assets/localization/zh/robot_calibration.json b/app/src/assets/localization/zh/robot_calibration.json new file mode 100644 index 00000000000..d5959a113c6 --- /dev/null +++ b/app/src/assets/localization/zh/robot_calibration.json @@ -0,0 +1,133 @@ +{ + "attached_pipettes": "已连接的移液器校准", + "before_you_begin": "开始之前", + "calibrate": "校准", + "calibrate_deck": "校准甲板", + "calibrate_pipette": "校准移液器", + "calibrate_tip_length": "校准移液器的吸头长度。", + "calibrate_tip_on_block": "在校准块上校准吸头", + "calibrate_tip_on_trash": "在垃圾桶上校准吸头", + "calibrate_xy_axes": "在{{slotName}}号板位中校准x轴和y轴", + "calibrate_z_axis_on_block": "在校准块上校准z轴", + "calibrate_z_axis_on_slot": "在5号板位中校准z轴", + "calibrate_z_axis_on_trash": "在垃圾桶上校准z轴", + "calibration_complete": "校准完成", + "calibration_dashboard": "校准面板", + "calibration_health_check": "校准健康检查", + "calibration_health_check_intro_body": "校准健康检查诊断甲板、吸头长度和移液器偏移校准的问题。您将移液器移动到不同位置,这将与您现有的校准数据进行比较。如果差异很大,系统会提示您重做部分或全部校准。", + "calibration_health_check_results": "校准健康检查结果", + "calibration_recommended": "建议校准", + "calibration_status": "校准状态", + "calibration_status_description": "为了准确和精确的运动,请校准工作站的甲板、移液器偏移数据和吸头长度。", + "calibrations_complete": "校准完成!", + "change_tip_rack": "更换吸头架", + "check_tip_on_block": "在校准块上检查吸头", + "check_tip_on_trash": "在垃圾桶上检查吸头", + "check_xy_axes": "在{{slotName}}号板位中检查x轴和y轴", + "check_z_axis_on_block": "在校准块上检查z轴", + "check_z_axis_on_slot": "在5号板位上检查z轴", + "check_z_axis_on_trash": "在垃圾桶上检查z轴", + "choose_a_tip_rack": "选择一个吸头盒", + "choose_tip_rack": "选择您想要用来校准吸头长度的吸头盒。想要使用这里没有列出的吸头盒吗?请转到实验耗材>导入以添加实验耗材。", + "clear_other_slots": "清除所有其他甲板位", + "confirm_exit_before_completion": "您确定要在完成{{sessionType}}之前退出吗?", + "confirm_placement": "确认放置位置", + "confirm_tip_rack": "确认吸头盒", + "custom": "自定义", + "deck_calibration": "甲板校准", + "deck_calibration_description": "校准工作站甲板的位置。建议用于所有新工作站和搬迁后的工作站。", + "deck_calibration_error_occurred": "尝试启动甲板校准时发生错误", + "deck_calibration_failure": "无法启动甲板校准", + "deck_calibration_intro_body": "甲板校准确保位置准确性,以便您的工作站按预期移动。它将准确建立OT-2的甲板相对于龙门架的方向。", + "deck_calibration_missing": "您还没有校准甲板", + "deck_calibration_redo": "重新校准甲板", + "deck_calibration_spinner": "甲板校准正在进行{{ongoing_action}}", + "deck_invalidates_pipette_offset": "重新校准甲板将清除移液器校准数据", + "definition": "您的OT-2将根据其校准的数据使移液器在内部空间进行移动。了解更多关于OT-2上校准是如何工作的。", + "delete_calibration_data": "删除校准数据", + "did_pipette_pick_up_tip": "移液器是否成功吸取吸头?", + "direction_controls": "方向控制", + "do_you_have_a_cal_block": "您有校准块吗?", + "download_calibration": "下载您的校准数据", + "download_calibration_data_available": "将所有三种类型的校准数据保存为JSON文件。", + "download_calibration_data_unavailable": "无校准数据可用。", + "download_calibration_title": "下载校准数据", + "download_details": "下载详情JSON校准文件查看摘要", + "error": "错误", + "exit": "退出", + "finish": "完成", + "get_started": "开始", + "good_calibration": "良好校准", + "health_check_button": "检查健康", + "health_check_description": "检查当前校准设置的健康状况。", + "health_check_title": "校准健康检查", + "if_tip_bent_replace_it": "如果吸头发生了弯折,在继续校准前,请确保用未损坏的吸头替换A1位置的吸头。", + "important_to_use_listed_equipment": "使用上述指定的设备进行校准非常重要,因为工作站将根据这些物品的已知数据来确保准确性。", + "jog_controls": "微调控制", + "jog_nozzle_to_block": "微调移液器,直到喷嘴几乎接触(小于0.1毫米){{slotName}}板位中的校准块。", + "jog_nozzle_to_trash": "微调移液器,直到喷嘴几乎接触(小于0.1毫米)垃圾桶的平坦表面。", + "jog_pipette_to_touch_block": "微调移液器,直到吸头几乎接触(小于0.1毫米)6号板位的校准块。", + "jog_pipette_to_touch_cross": "微调移液器,直到吸头精确地位于{{slotName}}板位的十字中心。", + "jog_pipette_to_touch_slot": "微调移液器,直到吸头几乎接触(小于0.1毫米)5号板位的甲板表面。如果移液器在5号板位的编号或边缘处,或较难看清的位置,请切换到x轴和y轴控制,将移液器移过甲板。", + "jog_pipette_to_touch_trash": "微调移液器,直到吸头几乎接触(小于0.1毫米)垃圾桶的平坦表面。", + "jog_too_far_or_bend_tip": "微调过多或吸头发生了弯折?", + "jump_size": "跳跃尺寸", + "large": "大", + "last_calibrated": "上次校准", + "last_completed_on": "上次完成时间{{timestamp}}", + "last_migrated": "上次已知校准迁移", + "launch_calibration": "启动校准", + "launch_calibration_link_text": "前往校准", + "manage_pipettes": "管理移液器", + "missing_calibration_data": "缺少校准数据", + "missing_calibration_data_long": "工作站缺少校准数据", + "need_help": "需要帮助?", + "no_pipette": "没有连接移液器", + "no_tip_length": "校准您的移液器以查看保存的吸头长度", + "pick_up_tip": "拾取吸头", + "pipette_name_and_serial": "{{name}},{{serial}}", + "pipette_offset_calibration": "移液器偏移校准", + "pipette_offset_calibration_intro_body": "校准移液器偏移数据将测量移液器与移液器支架和甲板的相对位置。", + "pipette_offset_calibration_on_mount": "当移液器连接到工作站的{{mount}}支架上时,校准此移液器的偏移数据。", + "pipette_offset_description": "校准移液器拾取默认吸头后的位置。", + "pipette_offset_recalibrate_both_mounts": "两个支架的移液器偏移数据都需要重新校准。", + "pipette_offset_requires_tip_length": "您还没有为此移液器保存吸头长度数据。在校准移液器偏移数据之前,您需要校准吸头长度。", + "pipette_offset_title": "移液器偏移校准", + "place_cal_block": "将校准块放入指定板位", + "place_full_tip_rack": "在8号板位中放置一盒完整的{{tip_rack}}", + "position_pipette_over_tip": "将移液器定位在A1上方", + "prepare_the_space": "预留足够空间", + "progress_will_be_lost": "{{sessionType}}进度将丢失", + "recalibrate": "重新校准", + "recalibrate_pipette": "重新校准移液器", + "recalibrate_warning_body": "执行甲板校准将清除所有移液器偏移数据和吸头长度数据。在完成甲板校准后,您需要重新校准移液器偏移数据和吸头长度。", + "recalibrate_warning_heading": "您确定要重新校准甲板吗?", + "recalibration_recommended": "建议重新校准", + "return_tip": "回收吸头", + "return_tip_and_continue": "回收吸头并继续下一把移液器", + "return_tip_and_exit": "回收吸头并查看校准健康检查结果", + "see_how_robot_calibration_works": "了解工作站校准是如何工作的", + "select_tip_rack": "选择吸头盒", + "serial_number": "序列号", + "small": "小", + "start_over": "重新开始", + "start_over_question": "重新开始?", + "start_with_deck_calibration": "从甲板校准开始,这是其他校准的基础。", + "starting_over_loses_progress": "重新开始将取消您的校准进度。", + "this_is_the_tip_used_in_pipette_offset_cal": "请注意:您必须使用与上面列出的移液器偏移校准中使用的相同吸头。", + "tiny": "微小", + "tip_length": "吸头长度校准", + "tip_length_and_pipette_offset_calibration": "吸头长度和移液器偏移校准", + "tip_length_calibration": "吸头长度校准", + "tip_length_calibration_intro_body": "吸头长度校准将测量吸头底部和移液器喷嘴之间的距离。", + "tip_length_invalidates_pipette_offset": "重新校准吸头长度将清除移液器偏移数据。", + "tip_pick_up_instructions": "使用下方的控制键或键盘,微调移液器,直到最近的喷嘴位于A1位置的正上方,并与吸头顶部平齐。
当移液器正确对齐时,吸取吸头。", + "title": "工作站校准", + "to_check": "检查{{mount}}移液器:", + "unknown_custom_tiprack": "未知的自定义吸头盒", + "use_calibration_block": "使用校准块", + "use_trash_bin": "使用垃圾桶", + "using_current_calibrations": "使用当前校准。", + "you_can_remove_cal_block": "您现在可以从甲板上移除校准块。", + "you_will_need": "您将需要:" +} diff --git a/app/src/assets/localization/zh/robot_controls.json b/app/src/assets/localization/zh/robot_controls.json new file mode 100644 index 00000000000..ecb31d2adf6 --- /dev/null +++ b/app/src/assets/localization/zh/robot_controls.json @@ -0,0 +1,14 @@ +{ + "confirm_location": "确认位置", + "drop_tips": "丢弃吸头", + "home_button": "归位", + "home_description": "将工作站返回起始位置。", + "home_label": "归位所有轴", + "lights_description": "控制灯光", + "lights_label": "灯光", + "recalibrate": "重新校准", + "restart_button": "重新启动", + "restart_description": "重新启动工作站。", + "restart_label": "重新启动工作站", + "title": "工作站控制" +} diff --git a/app/src/assets/localization/zh/run_details.json b/app/src/assets/localization/zh/run_details.json new file mode 100644 index 00000000000..2bfa9c1a5e1 --- /dev/null +++ b/app/src/assets/localization/zh/run_details.json @@ -0,0 +1,164 @@ +{ + "analysis_failure_on_robot": "尝试在{{robotName}}上分析{{protocolName}}时发生错误。请修复以下错误,然后再次尝试运行此协议。", + "analyzing_on_robot": "移液工作站分析中", + "anticipated_step": "预期步骤", + "anticipated": "预期步骤", + "apply_stored_data": "应用存储的数据", + "apply_stored_labware_offset_data": "应用已储存的耗材校准数据?", + "cancel_run_alert_info_flex": "该动作将终止本次运行并使移液器归位。", + "cancel_run_alert_info_ot2": "该动作将终止本次运行,已拾取的吸头将被丢弃,移液器将归位。", + "cancel_run_and_restart": "取消运行,重新进行设置以进行编辑", + "cancel_run_modal_back": "否,返回", + "cancel_run_modal_confirm": "是,取消运行", + "cancel_run_modal_heading": "确定要取消吗?", + "cancel_run_module_info": "此外,协议中使用的模块将保持激活状态,直到被禁用。", + "cancel_run": "取消运行", + "canceling_run_dot": "正在取消运行...", + "canceling_run": "正在取消运行", + "clear_protocol_to_make_available": "清除工作站的协议以使其可用", + "clear_protocol": "清除协议", + "close_door_to_resume_run": "关闭工作站门以继续运行", + "close_door_to_resume": "关闭移液工作站的前门以继续运行", + "close_door": "关闭移液工作站前门", + "closing_protocol": "正在关闭协议", + "comment_step": "注释", + "comment": "注释", + "complete_protocol_to_download": "完成协议以下载运行日志", + "current_step_pause_timer": "计时器", + "current_step_pause": "当前步骤 - 用户暂停", + "current_step": "当前步骤", + "current_temperature": "当前:{{temperature}}°C", + "custom_values": "自定义值", + "data_out_of_date": "此数据可能已过期", + "date": "日期", + "door_is_open": "工作站前门已打开", + "door_open_pause": "当前步骤 - 暂停 - 前门已打开", + "download_files": "下载文件", + "download": "下载", + "download_run_log": "下载运行日志", + "downloading_run_log": "正在下载运行日志", + "drop_tip": "在{{labware_location}}内的{{labware}}中的{{well_name}}中丢弃吸头", + "duration": "持续时间", + "end_of_protocol": "协议结束", + "end_step_time": "结束", + "end": "结束", + "error_details": "错误详情", + "error_info": "错误{{errorCode}}:{{errorType}}", + "error_type": "错误:{{errorType}}", + "failed_step": "步骤失败", + "final_step": "最后一步", + "ignore_stored_data": "忽略已存储的数据", + "labware_offset_data": "耗材校准数据", + "labware": "耗材", + "left": "左", + "listed_values": "列出的值仅供查看", + "load_liquids_info_protocol_setup": "将{{liquid}}加载到{{labware}}中", + "load_module_protocol_setup_plural": "加载{{module}}", + "load_module_protocol_setup": "在{{slot_name}}号板位中加载{{module}}", + "load_pipette_protocol_setup": "在{{mount_name}}安装位上加载{{pipette_name}}", + "loading_protocol": "正在加载协议", + "loading_data": "正在加载数据...", + "location": "位置", + "module_controls": "模块控制", + "module_slot_number": "板位{{slot_number}}", + "move_labware": "移动耗材", + "name": "名称", + "no_files_included": "未包含协议文件", + "no_of_error": "{{count}}个错误", + "no_of_errors": "{{count}}个错误", + "no_of_warning": "{{count}}个警告", + "no_of_warnings": "{{count}}个警告", + "no_offsets_available": "无耗材校准数据可用", + "not_available_for_a_completed_run": "不适用于已完成的运行", + "not_available_for_a_run_in_progress": "不适用于正在进行的运行", + "not_started_yet": "未开始", + "off_deck": "甲板外", + "parameters": "参数", + "pause_protocol": "暂停协议", + "pause_run": "暂停运行", + "pause": "暂停", + "paused_for": "暂停原因", + "pickup_tip": "从{{labware_location}}内的{{labware}}中的{{well_name}}孔位拾取吸头", + "plus_more": "+{{count}}更多", + "preview_of_protocol_steps": "这是您的协议步骤预览", + "protocol_analysis_failed": "协议分析失败。", + "protocol_analysis_failure": "协议分析失败", + "protocol_completed": "协议已完成", + "protocol_end": "协议结束", + "protocol_files": "协议文件", + "protocol_paused_for": "协议暂停原因", + "protocol_run_canceled": "协议运行已取消", + "protocol_run_complete": "协议运行已完成", + "protocol_run_failed": "协议运行失败", + "protocol_setup": "协议设置", + "protocol_start": "协议开始", + "protocol_steps": "协议步骤", + "protocol_title": "协议 -{{protocol_name}}", + "resume_run": "恢复运行", + "return_to_dashboard": "返回控制面板", + "return_to_quick_transfer": "返回快速移液", + "right": "右", + "robot_has_previous_offsets": "该移液工作站已存储了之前运行协议的耗材校准数据。您想将这些数据应用于此协议的运行吗?您仍然可以通过实验器具位置检查调整校准数据。", + "robot_was_recalibrated": "在储存此耗材校准数据后,移液工作站已重新校准", + "run_again": "再次运行", + "run_canceled_splash": "运行已取消", + "run_canceled_with_errors_splash": "因错误取消运行。", + "run_canceled_with_errors": "因错误取消运行。", + "run_canceled": "运行已取消。", + "run_completed_splash": "运行完成", + "run_completed_with_warnings_splash": "运行完成,并伴有警告。", + "run_completed_with_warnings": "运行完成,并伴有警告。", + "run_completed": "运行已完成。", + "run_complete_splash": "运行已完成", + "run_complete": "运行已完成", + "run_cta_disabled": "在开始运行之前,请完成协议选项卡上的所有必要步骤。", + "run_failed_modal_body": "在协议执行{{command}}时发生错误。", + "run_failed_modal_header": "{{errorName}}:{{errorCode}}协议步骤{{count}}", + "run_failed_modal_title": "运行失败", + "run_failed_splash": "运行失败", + "run_failed": "运行失败。", + "run_has_diverged_from_predicted": "运行已偏离预期状态。无法执行新的预期步骤。", + "run_preview": "运行预览", + "run_protocol": "运行协议", + "run_status": "状态:{{status}}", + "run_time": "运行时间", + "run": "运行", + "setup_incomplete": "完成“设置”选项卡中所需的步骤", + "setup": "设置", + "slot": "板位{{slotName}}", + "start_run": "开始运行", + "start_step_time": "开始", + "start_time": "开始时间", + "start": "开始", + "status_awaiting-recovery-blocked-by-open-door": "暂停 - 门已打开", + "status_awaiting-recovery-paused": "暂停", + "status_awaiting-recovery": "等待恢复", + "status_blocked-by-open-door": "暂停 - 前门打开", + "status_failed": "失败", + "status_finishing": "结束中", + "status_idle": "未开始", + "status_paused": "暂停", + "status_running": "运行中", + "status_stop-requested": "请求停止", + "status_stopped": "已取消", + "status_succeeded": "已完成", + "status": "状态", + "step": "步骤", + "step_failed": "步骤失败", + "step_number": "步骤{{step_number}}:", + "steps_total": "总计{{count}}步", + "stored_labware_offset_data": "已储存适用于此协议的耗材校准数据", + "target_temperature": "目标温度:{{temperature}}°C", + "temperature_not_available": "{{temperature_type}}: n/a", + "thermocycler_error_tooltip": "模块遇到异常,请联系技术支持。", + "total_elapsed_time": "总耗时", + "total_step_count_plural": "总计{{count}}步", + "total_step_count": "总计{{count}}步", + "unable_to_determine_steps": "无法确定步骤", + "view_analysis_error_details": "查看 错误详情", + "view_current_step": "查看当前步骤", + "view_error_details": "查看错误详情", + "view_error": "查看错误", + "view_warning_details": "查看警告详情", + "warning_details": "警告详情" +} diff --git a/app/src/assets/localization/zh/shared.json b/app/src/assets/localization/zh/shared.json new file mode 100644 index 00000000000..90b597b2820 --- /dev/null +++ b/app/src/assets/localization/zh/shared.json @@ -0,0 +1,85 @@ +{ + "a_software_update_is_available": "此工作站有可用的软件更新。更新以运行协议。", + "add": "添加", + "alphabetical": "按字母排序", + "another_app_controlling_robot": "工作站的触摸屏或另一台装有应用程序的电脑正在控制这个工作站。", + "back": "返回", + "before_you_begin": "在您开始之前", + "browse": "浏览", + "cancel": "取消", + "change_protocol": "更改协议", + "change_robot": "更换工作站", + "clear_data": "清除数据", + "close_robot_door": "开始运行前请关闭工作站前门。", + "close": "关闭", + "confirm_placement": "确认放置", + "confirm_position": "确认位置", + "confirm_values": "确认这些值", + "confirm": "确认", + "continue_activity": "继续活动", + "continue_to_param": "继续设置参数", + "continue": "继续", + "delete": "删除", + "did_pipette_pick_up_tip": "移液器是否成功拾取吸头?", + "disabled_cannot_connect": "无法连接到工作站", + "disabled_connect_to_robot": "连接到工作站以进行控制", + "disabled_no_pipette_attached": "安装移液器以继续", + "disabled_protocol_is_running": "协议正在运行", + "dont_show_me_again": "不再显示", + "drag_and_drop": "拖放或 浏览 您的文件", + "empty": "空闲", + "ending": "结束中", + "error_encountered": "遇到错误", + "error": "错误", + "exit": "退出", + "extension_mount": "扩展安装支架", + "flow_complete": "{{flowName}}完成!", + "get_started": "开始", + "github": "GitHub", + "go_back": "返回", + "instruments": "硬件", + "loading": "加载中...", + "next": "下一步", + "no_data": "无数据", + "no": "否", + "none": "无", + "not_used": "未使用", + "off": "关闭", + "ok": "好的", + "on": "开启", + "open": "打开", + "proceed_to_setup": "继续设置", + "protocol_run_general_error_msg": "无法在工作站上创建协议运行。", + "reanalyze": "重新分析", + "refresh_list": "刷新列表", + "refresh": "刷新", + "remember_my_selection_and_do_not_ask_again": "记住我的选择,不再询问", + "reset_all": "全部重置", + "reset": "重置", + "restart": "重新启动", + "resume": "继续", + "return": "返回", + "reverse": "按字母倒序排序", + "robot_is_analyzing": "工作站正在分析", + "robot_is_busy_no_protocol_run_allowed": "此工作站正忙,无法运行此协议。转到工作站", + "robot_is_busy": "工作站正忙", + "robot_is_reachable_but_not_responding": "此工作站的API服务器未能正确响应IP地址{{hostname}}处的请求", + "robot_was_seen_but_is_unreachable": "最近看到此工作站,但当前无法访问IP地址{{hostname}}", + "save": "保存", + "something_went_wrong": "出现问题", + "sort_by": "排序方式", + "stand_back_robot_is_in_motion": "请离开,工作站在运动", + "start": "开始", + "starting": "启动中", + "step": "步骤{{current}}/{{max}}", + "stop": "停止", + "terminate_activity": "终止活动", + "terminate": "终止远程活动", + "try_again": "重试", + "unknown_error": "发生未知错误", + "unknown": "未知", + "update": "更新", + "view_latest_release_notes": "查看最新发布说明:", + "yes": "是", + "you_will_need": "您将需要:" +} diff --git a/app/src/assets/localization/zh/top_navigation.json b/app/src/assets/localization/zh/top_navigation.json new file mode 100644 index 00000000000..cb831731be9 --- /dev/null +++ b/app/src/assets/localization/zh/top_navigation.json @@ -0,0 +1,20 @@ +{ + "all_protocols": "全部协议", + "attached_pipettes_do_not_match": "安装的移液器与加载的协议中指定的移液器不匹配", + "calibrate_deck_to_proceed": "校准甲板以继续", + "deck_setup": "甲板设置", + "devices": "设备", + "instruments": "硬件", + "labware": "耗材", + "modules": "模块", + "pipettes_not_calibrated": "请校准加载的协议中指定的所有移液器以继续", + "pipettes": "移液器", + "please_connect_to_a_robot": "请连接到工作站以继续", + "please_load_a_protocol": "请加载协议以继续", + "protocol_runs": "协议运行", + "protocols": "协议", + "quick_transfer": "快速移液", + "robot_settings": "工作站设置", + "run": "运行", + "settings": "设置" +} diff --git a/app/src/assets/videos/error-recovery/Gripper_Release.webm b/app/src/assets/videos/error-recovery/Gripper_Release.webm new file mode 100644 index 00000000000..a3ba721fd70 Binary files /dev/null and b/app/src/assets/videos/error-recovery/Gripper_Release.webm differ diff --git a/app/src/atoms/Banner/Banner.stories.tsx b/app/src/atoms/Banner/Banner.stories.tsx deleted file mode 100644 index de756b10de6..00000000000 --- a/app/src/atoms/Banner/Banner.stories.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import * as React from 'react' -import { LegacyStyledText, TYPOGRAPHY } from '@opentrons/components' -import { Banner } from './index' -import type { Meta, StoryObj } from '@storybook/react' - -const meta: Meta = { - title: 'App/Atoms/Banner', - component: Banner, -} - -export default meta - -type Story = StoryObj - -export const Primary: Story = { - args: { - children: 'Banner component', - type: 'success', - }, -} - -export const OverriddenIcon: Story = { - args: { - type: 'warning', - children: 'Banner component', - icon: { name: 'ot-hot-to-touch' }, - }, -} - -export const OverriddenExitIcon: Story = { - args: { - type: 'informing', - children: 'Banner component', - onCloseClick: () => { - console.log('close') - }, - closeButton: ( - - {'Exit'} - - ), - }, -} diff --git a/app/src/atoms/Banner/index.tsx b/app/src/atoms/Banner/index.tsx deleted file mode 100644 index 1061dd50ac9..00000000000 --- a/app/src/atoms/Banner/index.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import * as React from 'react' -import { css } from 'styled-components' -import { - ALIGN_CENTER, - BORDERS, - Btn, - COLORS, - DIRECTION_ROW, - Flex, - Icon, - JUSTIFY_SPACE_BETWEEN, - RESPONSIVENESS, - SPACING, - TYPOGRAPHY, -} from '@opentrons/components' -import type { IconProps, StyleProps } from '@opentrons/components' - -export type BannerType = - | 'success' - | 'warning' - | 'error' - | 'updating' - | 'informing' - -export interface BannerProps extends StyleProps { - /** name constant of the icon to display */ - type: BannerType - /** Banner contents */ - children?: React.ReactNode - /** optional handler to show close button/clear alert */ - onCloseClick?: (() => void) | React.MouseEventHandler - /** Override the default Alert Icon */ - icon?: IconProps - /** some banner onCloseClicks fire events, this allows a spinner after click but before event finishes */ - isCloseActionLoading?: boolean - /** Override the Exit icon */ - closeButton?: React.ReactNode - /** Icon margin right for large banners */ - iconMarginRight?: string - /** Icon margin left for large banners */ - iconMarginLeft?: string -} - -const BANNER_PROPS_BY_TYPE: Record< - BannerType, - { icon: IconProps; backgroundColor: string; color: string } -> = { - success: { - icon: { name: 'check-circle' }, - backgroundColor: COLORS.green30, - color: COLORS.green60, - }, - error: { - icon: { name: 'alert-circle' }, - backgroundColor: COLORS.red30, - color: COLORS.red60, - }, - warning: { - icon: { name: 'alert-circle' }, - backgroundColor: COLORS.yellow30, - color: COLORS.yellow60, - }, - updating: { - icon: { name: 'ot-spinner' }, - backgroundColor: COLORS.grey30, - color: COLORS.grey60, - }, - informing: { - icon: { name: 'information' }, - backgroundColor: COLORS.blue30, - color: COLORS.blue60, - }, -} - -export function Banner(props: BannerProps): JSX.Element { - const { - type, - onCloseClick, - icon, - children, - isCloseActionLoading = false, - padding, - closeButton, - iconMarginLeft, - iconMarginRight, - size, - ...styleProps - } = props - const bannerProps = BANNER_PROPS_BY_TYPE[type] - const iconProps = { - ...(icon ?? bannerProps.icon), - size: size ?? '1rem', - marginRight: iconMarginRight ?? SPACING.spacing8, - marginLeft: iconMarginLeft ?? '0rem', - color: BANNER_PROPS_BY_TYPE[type].color, - } - const BANNER_STYLE = css` - font-size: ${TYPOGRAPHY.fontSizeP}; - font-weight: ${TYPOGRAPHY.fontWeightRegular}; - border-radius: ${SPACING.spacing4}; - - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - font-size: 1.25rem; - font-weight: ${TYPOGRAPHY.fontWeightSemiBold}; - background-color: ${COLORS.yellow35}; - border-radius: ${BORDERS.borderRadius12}; - line-height: 1.5rem; - } - ` - return ( - { - e.stopPropagation() - }} - data-testid={`Banner_${type}`} - {...styleProps} - > - - - {props.children} - - {onCloseClick != null && !(isCloseActionLoading ?? false) ? ( - - {closeButton ?? ( - - )} - - ) : null} - {(isCloseActionLoading ?? false) && ( - - )} - - ) -} diff --git a/app/src/atoms/InlineNotification/InlineNotification.stories.tsx b/app/src/atoms/InlineNotification/InlineNotification.stories.tsx index b993fd13205..ff4e4de6999 100644 --- a/app/src/atoms/InlineNotification/InlineNotification.stories.tsx +++ b/app/src/atoms/InlineNotification/InlineNotification.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { VIEWPORT } from '@opentrons/components' import { InlineNotification } from '.' import type { Story, Meta } from '@storybook/react' diff --git a/app/src/atoms/InlineNotification/__tests__/InlineNotification.test.tsx b/app/src/atoms/InlineNotification/__tests__/InlineNotification.test.tsx index 73b40a8a1c5..0e91433e7b2 100644 --- a/app/src/atoms/InlineNotification/__tests__/InlineNotification.test.tsx +++ b/app/src/atoms/InlineNotification/__tests__/InlineNotification.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, beforeEach, expect } from 'vitest' import { screen, fireEvent } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { InlineNotification } from '..' const render = (props: React.ComponentProps) => { diff --git a/app/src/atoms/InlineNotification/index.tsx b/app/src/atoms/InlineNotification/index.tsx index 1605abd990d..5b5bf21aafa 100644 --- a/app/src/atoms/InlineNotification/index.tsx +++ b/app/src/atoms/InlineNotification/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { ALIGN_CENTER, diff --git a/app/src/atoms/InstrumentContainer/InstrumentContainer.stories.tsx b/app/src/atoms/InstrumentContainer/InstrumentContainer.stories.tsx index 680b104c6b8..d5daeb2d6d7 100644 --- a/app/src/atoms/InstrumentContainer/InstrumentContainer.stories.tsx +++ b/app/src/atoms/InstrumentContainer/InstrumentContainer.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { InstrumentContainer as InstrumentContainerComponent } from './index' diff --git a/app/src/atoms/InstrumentContainer/__tests__/InstrumentContainer.test.tsx b/app/src/atoms/InstrumentContainer/__tests__/InstrumentContainer.test.tsx index 4b71e56326d..e5fa872ab54 100644 --- a/app/src/atoms/InstrumentContainer/__tests__/InstrumentContainer.test.tsx +++ b/app/src/atoms/InstrumentContainer/__tests__/InstrumentContainer.test.tsx @@ -1,7 +1,7 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { InstrumentContainer } from '..' const render = (props: React.ComponentProps) => { diff --git a/app/src/atoms/InstrumentContainer/index.tsx b/app/src/atoms/InstrumentContainer/index.tsx index 3efb742fa76..475dfed3b23 100644 --- a/app/src/atoms/InstrumentContainer/index.tsx +++ b/app/src/atoms/InstrumentContainer/index.tsx @@ -1,5 +1,3 @@ -import * as React from 'react' - import { BORDERS, COLORS, diff --git a/app/src/atoms/Link/ExternalLink.stories.tsx b/app/src/atoms/Link/ExternalLink.stories.tsx index 8f664d257f5..d5d2dccaee9 100644 --- a/app/src/atoms/Link/ExternalLink.stories.tsx +++ b/app/src/atoms/Link/ExternalLink.stories.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { COLORS, Flex, SPACING } from '@opentrons/components' import { ExternalLink as ExternalLinkComponent } from './ExternalLink' diff --git a/app/src/atoms/Link/ExternalLink.tsx b/app/src/atoms/Link/ExternalLink.tsx index e35e3515277..5d24a06fdb4 100644 --- a/app/src/atoms/Link/ExternalLink.tsx +++ b/app/src/atoms/Link/ExternalLink.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Link, Icon, TYPOGRAPHY, SPACING } from '@opentrons/components' import type { LinkProps } from '@opentrons/components' diff --git a/app/src/atoms/Link/__tests__/ExternalLink.test.tsx b/app/src/atoms/Link/__tests__/ExternalLink.test.tsx index 6d39662a104..e245541c514 100644 --- a/app/src/atoms/Link/__tests__/ExternalLink.test.tsx +++ b/app/src/atoms/Link/__tests__/ExternalLink.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, beforeEach } from 'vitest' import { screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' import { COLORS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { ExternalLink } from '../ExternalLink' const TEST_URL = 'https://opentrons.com' diff --git a/app/src/atoms/ProgressBar/__tests__/ProgressBar.test.tsx b/app/src/atoms/ProgressBar/__tests__/ProgressBar.test.tsx index 2b9f19f76cc..6d5b3d3fa40 100644 --- a/app/src/atoms/ProgressBar/__tests__/ProgressBar.test.tsx +++ b/app/src/atoms/ProgressBar/__tests__/ProgressBar.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { screen } from '@testing-library/react' import { css } from 'styled-components' import { COLORS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { ProgressBar } from '..' const render = (props: React.ComponentProps) => { diff --git a/app/src/atoms/ProgressBar/index.tsx b/app/src/atoms/ProgressBar/index.tsx index 01054384094..5852dadf2b5 100644 --- a/app/src/atoms/ProgressBar/index.tsx +++ b/app/src/atoms/ProgressBar/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { COLORS, Box } from '@opentrons/components' diff --git a/app/src/atoms/SelectField/Select.stories.tsx b/app/src/atoms/SelectField/Select.stories.tsx index 2df1ac462fa..461bab59b22 100644 --- a/app/src/atoms/SelectField/Select.stories.tsx +++ b/app/src/atoms/SelectField/Select.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Select } from './Select' diff --git a/app/src/atoms/SelectField/Select.tsx b/app/src/atoms/SelectField/Select.tsx index 2f1a6ef8879..110fbbbf857 100644 --- a/app/src/atoms/SelectField/Select.tsx +++ b/app/src/atoms/SelectField/Select.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import ReactSelect, { components } from 'react-select' import { diff --git a/app/src/atoms/SelectField/index.tsx b/app/src/atoms/SelectField/index.tsx index 36c425cbeae..50deed28266 100644 --- a/app/src/atoms/SelectField/index.tsx +++ b/app/src/atoms/SelectField/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import find from 'lodash/find' import { Select } from './Select' import { diff --git a/app/src/atoms/Skeleton/Skeleton.stories.tsx b/app/src/atoms/Skeleton/Skeleton.stories.tsx index f3c4ab231c4..c91679c1a3a 100644 --- a/app/src/atoms/Skeleton/Skeleton.stories.tsx +++ b/app/src/atoms/Skeleton/Skeleton.stories.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Flex, DIRECTION_COLUMN, diff --git a/app/src/atoms/Skeleton/__tests__/Skeleton.test.tsx b/app/src/atoms/Skeleton/__tests__/Skeleton.test.tsx index 45f90200330..b03bd72e98d 100644 --- a/app/src/atoms/Skeleton/__tests__/Skeleton.test.tsx +++ b/app/src/atoms/Skeleton/__tests__/Skeleton.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect } from 'vitest' import '@testing-library/jest-dom/vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { Skeleton } from '..' const render = (props: React.ComponentProps) => { diff --git a/app/src/atoms/Skeleton/index.tsx b/app/src/atoms/Skeleton/index.tsx index 556a5653a77..3c9ccb09a7b 100644 --- a/app/src/atoms/Skeleton/index.tsx +++ b/app/src/atoms/Skeleton/index.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { css } from 'styled-components' import { BORDERS, Box, COLORS } from '@opentrons/components' diff --git a/app/src/atoms/SleepScreen/__tests__/SleepScreen.test.tsx b/app/src/atoms/SleepScreen/__tests__/SleepScreen.test.tsx index e38a866b35e..79356e879e0 100644 --- a/app/src/atoms/SleepScreen/__tests__/SleepScreen.test.tsx +++ b/app/src/atoms/SleepScreen/__tests__/SleepScreen.test.tsx @@ -1,9 +1,8 @@ -import * as React from 'react' import { describe, it, expect } from 'vitest' import '@testing-library/jest-dom/vitest' import { screen } from '@testing-library/react' import { COLORS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { SleepScreen } from '..' diff --git a/app/src/atoms/SleepScreen/index.tsx b/app/src/atoms/SleepScreen/index.tsx index 2600f9d2de9..64ff2ff7cbe 100644 --- a/app/src/atoms/SleepScreen/index.tsx +++ b/app/src/atoms/SleepScreen/index.tsx @@ -1,5 +1,3 @@ -import * as React from 'react' - import { Flex, COLORS } from '@opentrons/components' export function SleepScreen(): JSX.Element { diff --git a/app/src/atoms/Slideout/MultiSlideout.tsx b/app/src/atoms/Slideout/MultiSlideout.tsx index 73054a10a45..65865edec53 100644 --- a/app/src/atoms/Slideout/MultiSlideout.tsx +++ b/app/src/atoms/Slideout/MultiSlideout.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Slideout } from './index' import type { MultiSlideoutSpecs, SlideoutProps } from './index' diff --git a/app/src/atoms/Slideout/Slideout.stories.tsx b/app/src/atoms/Slideout/Slideout.stories.tsx index 6d1b828573c..9cee5939d15 100644 --- a/app/src/atoms/Slideout/Slideout.stories.tsx +++ b/app/src/atoms/Slideout/Slideout.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { Fragment } from 'react' import { action } from '@storybook/addon-actions' import { COLORS, @@ -24,7 +24,7 @@ export default meta type Story = StoryObj const Children = ( - + - + ) export const Slideout: Story = { diff --git a/app/src/atoms/Slideout/__tests__/Slideout.test.tsx b/app/src/atoms/Slideout/__tests__/Slideout.test.tsx index ce929b72296..8e3301ad374 100644 --- a/app/src/atoms/Slideout/__tests__/Slideout.test.tsx +++ b/app/src/atoms/Slideout/__tests__/Slideout.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { screen, fireEvent } from '@testing-library/react' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { Slideout } from '..' const render = (props: React.ComponentProps) => { diff --git a/app/src/atoms/Slideout/index.tsx b/app/src/atoms/Slideout/index.tsx index b834bd1c6e5..53a6888efa0 100644 --- a/app/src/atoms/Slideout/index.tsx +++ b/app/src/atoms/Slideout/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useRef, useState, useEffect } from 'react' import { css } from 'styled-components' import { useTranslation } from 'react-i18next' @@ -124,9 +124,9 @@ export const Slideout = (props: SlideoutProps): JSX.Element => { multiSlideoutSpecs, } = props const { t } = useTranslation('shared') - const slideOutRef = React.useRef(null) - const [isReachedBottom, setIsReachedBottom] = React.useState(false) - const hasBeenExpanded = React.useRef(isExpanded ?? false) + const slideOutRef = useRef(null) + const [isReachedBottom, setIsReachedBottom] = useState(false) + const hasBeenExpanded = useRef(isExpanded ?? false) const handleScroll = (): void => { if (slideOutRef.current == null) return const { scrollTop, scrollHeight, clientHeight } = slideOutRef.current @@ -137,7 +137,7 @@ export const Slideout = (props: SlideoutProps): JSX.Element => { } } - React.useEffect(() => { + useEffect(() => { handleScroll() }, [slideOutRef]) diff --git a/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/AlphanumericKeyboard.stories.tsx b/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/AlphanumericKeyboard.stories.tsx index dac73384226..5d00c0ac298 100644 --- a/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/AlphanumericKeyboard.stories.tsx +++ b/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/AlphanumericKeyboard.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useRef } from 'react' import { DIRECTION_COLUMN, Flex, @@ -22,9 +22,9 @@ export default meta type Story = StoryObj const Keyboard = (): JSX.Element => { - const [showKeyboard, setShowKeyboard] = React.useState(false) - const [value, setValue] = React.useState('') - const keyboardRef = React.useRef(null) + const [showKeyboard, setShowKeyboard] = useState(false) + const [value, setValue] = useState('') + const keyboardRef = useRef(null) return (
diff --git a/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/__tests__/CustomKeyboard.test.tsx b/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/__tests__/CustomKeyboard.test.tsx index 336e0c86026..2fdf2e30b7e 100644 --- a/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/__tests__/CustomKeyboard.test.tsx +++ b/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/__tests__/CustomKeyboard.test.tsx @@ -1,17 +1,19 @@ -import * as React from 'react' +import { useRef } from 'react' import { describe, it, expect, vi } from 'vitest' import '@testing-library/jest-dom/vitest' import { fireEvent, renderHook, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { AlphanumericKeyboard } from '..' -const render = (props: React.ComponentProps) => { +import type { ComponentProps } from 'react' + +const render = (props: ComponentProps) => { return renderWithProviders()[0] } describe('AlphanumericKeyboard', () => { it('should render alphanumeric keyboard - lower case', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, @@ -55,7 +57,7 @@ describe('AlphanumericKeyboard', () => { }) }) it('should render alphanumeric keyboard - upper case, when clicking ABC key', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, @@ -103,7 +105,7 @@ describe('AlphanumericKeyboard', () => { }) it('should render alphanumeric keyboard - numbers, when clicking number key', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, @@ -133,7 +135,7 @@ describe('AlphanumericKeyboard', () => { }) it('should render alphanumeric keyboard - lower case when layout is numbers and clicking abc ', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, @@ -182,7 +184,7 @@ describe('AlphanumericKeyboard', () => { }) it('should switch each alphanumeric keyboard properly', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, diff --git a/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/index.css b/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/index.css index 1fa59e2230a..61e4f80d0ca 100644 --- a/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/index.css +++ b/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/index.css @@ -68,3 +68,12 @@ the rest is the same */ height: 44.75px; width: 330px !important; } + +.hg-candidate-box { + max-width: 400px; +} + +li.hg-candidate-box-list-item { + height: 60px; + width: 60px; +} diff --git a/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/index.tsx b/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/index.tsx index dccad085c08..73ec9306fee 100644 --- a/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/index.tsx +++ b/app/src/atoms/SoftwareKeyboard/AlphanumericKeyboard/index.tsx @@ -1,6 +1,12 @@ -import * as React from 'react' +import { useState } from 'react' import Keyboard from 'react-simple-keyboard' -import { alphanumericKeyboardLayout, customDisplay } from '../constants' +import { useSelector } from 'react-redux' +import { getAppLanguage } from '/app/redux/config' +import { + alphanumericKeyboardLayout, + layoutCandidates, + customDisplay, +} from '../constants' import type { KeyboardReactInterface } from 'react-simple-keyboard' import '../index.css' @@ -18,7 +24,8 @@ export function AlphanumericKeyboard({ keyboardRef, debug = false, // If true, will input a \n }: AlphanumericKeyboardProps): JSX.Element { - const [layoutName, setLayoutName] = React.useState('default') + const [layoutName, setLayoutName] = useState('default') + const appLanguage = useSelector(getAppLanguage) const onKeyPress = (button: string): void => { if (button === '{ABC}') handleShift() if (button === '{numbers}') handleNumber() @@ -47,6 +54,9 @@ export function AlphanumericKeyboard({ onKeyPress={onKeyPress} layoutName={layoutName} layout={alphanumericKeyboardLayout} + layoutCandidates={ + appLanguage != null ? layoutCandidates[appLanguage] : undefined + } display={customDisplay} mergeDisplay={true} useButtonTag={true} diff --git a/app/src/atoms/SoftwareKeyboard/FullKeyboard/FullKeyboard.stories.tsx b/app/src/atoms/SoftwareKeyboard/FullKeyboard/FullKeyboard.stories.tsx index 3734a895231..27d79e602f8 100644 --- a/app/src/atoms/SoftwareKeyboard/FullKeyboard/FullKeyboard.stories.tsx +++ b/app/src/atoms/SoftwareKeyboard/FullKeyboard/FullKeyboard.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useRef } from 'react' import { DIRECTION_COLUMN, Flex, @@ -21,9 +21,9 @@ export default meta type Story = StoryObj const Keyboard = (): JSX.Element => { - const [showKeyboard, setShowKeyboard] = React.useState(false) - const [value, setValue] = React.useState('') - const keyboardRef = React.useRef(null) + const [showKeyboard, setShowKeyboard] = useState(false) + const [value, setValue] = useState('') + const keyboardRef = useRef(null) return ( diff --git a/app/src/atoms/SoftwareKeyboard/FullKeyboard/__tests__/FullKeyboard.test.tsx b/app/src/atoms/SoftwareKeyboard/FullKeyboard/__tests__/FullKeyboard.test.tsx index c84a33a2796..90786ff6a6f 100644 --- a/app/src/atoms/SoftwareKeyboard/FullKeyboard/__tests__/FullKeyboard.test.tsx +++ b/app/src/atoms/SoftwareKeyboard/FullKeyboard/__tests__/FullKeyboard.test.tsx @@ -1,17 +1,19 @@ -import * as React from 'react' +import { useRef } from 'react' import { describe, it, expect, vi } from 'vitest' import '@testing-library/jest-dom/vitest' import { fireEvent, renderHook, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { FullKeyboard } from '..' -const render = (props: React.ComponentProps) => { +import type { ComponentProps } from 'react' + +const render = (props: ComponentProps) => { return renderWithProviders()[0] } describe('FullKeyboard', () => { it('should render FullKeyboard keyboard', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, @@ -59,7 +61,7 @@ describe('FullKeyboard', () => { }) it('should render full keyboard when hitting ABC key', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, @@ -108,7 +110,7 @@ describe('FullKeyboard', () => { }) it('should render full keyboard when hitting 123 key', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, @@ -158,7 +160,7 @@ describe('FullKeyboard', () => { }) it('should render the software keyboards when hitting #+= key', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, @@ -204,7 +206,7 @@ describe('FullKeyboard', () => { }) it('should call mock function when clicking a key', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, diff --git a/app/src/atoms/SoftwareKeyboard/FullKeyboard/index.css b/app/src/atoms/SoftwareKeyboard/FullKeyboard/index.css index b3ff8968da4..4fb38eb50db 100644 --- a/app/src/atoms/SoftwareKeyboard/FullKeyboard/index.css +++ b/app/src/atoms/SoftwareKeyboard/FullKeyboard/index.css @@ -103,3 +103,12 @@ color: #16212d; background-color: #e3e3e3; /* grey30 */ } + +.hg-candidate-box { + max-width: 400px; +} + +li.hg-candidate-box-list-item { + height: 60px; + width: 60px; +} diff --git a/app/src/atoms/SoftwareKeyboard/FullKeyboard/index.tsx b/app/src/atoms/SoftwareKeyboard/FullKeyboard/index.tsx index 663efdd9c24..2846930ad1e 100644 --- a/app/src/atoms/SoftwareKeyboard/FullKeyboard/index.tsx +++ b/app/src/atoms/SoftwareKeyboard/FullKeyboard/index.tsx @@ -1,6 +1,14 @@ -import * as React from 'react' +import { useState } from 'react' import { KeyboardReact as Keyboard } from 'react-simple-keyboard' -import { customDisplay, fullKeyboardLayout } from '../constants' +import { useSelector } from 'react-redux' +import { getAppLanguage } from '/app/redux/config' +import { + customDisplay, + layoutCandidates, + fullKeyboardLayout, +} from '../constants' + +import type { MutableRefObject } from 'react' import type { KeyboardReactInterface } from 'react-simple-keyboard' import '../index.css' @@ -9,7 +17,7 @@ import './index.css' // TODO (kk:04/05/2024) add debug to make debugging easy interface FullKeyboardProps { onChange: (input: string) => void - keyboardRef: React.MutableRefObject + keyboardRef: MutableRefObject debug?: boolean } @@ -18,7 +26,8 @@ export function FullKeyboard({ keyboardRef, debug = false, }: FullKeyboardProps): JSX.Element { - const [layoutName, setLayoutName] = React.useState('default') + const [layoutName, setLayoutName] = useState('default') + const appLanguage = useSelector(getAppLanguage) const handleShift = (button: string): void => { switch (button) { case '{shift}': @@ -56,6 +65,9 @@ export function FullKeyboard({ onKeyPress={onKeyPress} layoutName={layoutName} layout={fullKeyboardLayout} + layoutCandidates={ + appLanguage != null ? layoutCandidates[appLanguage] : undefined + } display={customDisplay} mergeDisplay={true} useButtonTag={true} diff --git a/app/src/atoms/SoftwareKeyboard/IndividualKey/IndividualKey.stories.tsx b/app/src/atoms/SoftwareKeyboard/IndividualKey/IndividualKey.stories.tsx index 18978b2318f..e19ee9ecc57 100644 --- a/app/src/atoms/SoftwareKeyboard/IndividualKey/IndividualKey.stories.tsx +++ b/app/src/atoms/SoftwareKeyboard/IndividualKey/IndividualKey.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useRef } from 'react' import { DIRECTION_COLUMN, Flex, @@ -22,9 +22,9 @@ export default meta type Story = StoryObj const Keyboard = ({ ...args }): JSX.Element => { - const [showKeyboard, setShowKeyboard] = React.useState(false) - const [value, setValue] = React.useState('') - const keyboardRef = React.useRef(null) + const [showKeyboard, setShowKeyboard] = useState(false) + const [value, setValue] = useState('') + const keyboardRef = useRef(null) return ( diff --git a/app/src/atoms/SoftwareKeyboard/IndividualKey/__tests__/IndividualKey.test.tsx b/app/src/atoms/SoftwareKeyboard/IndividualKey/__tests__/IndividualKey.test.tsx index f08c7e4566f..ecdbdf9aa78 100644 --- a/app/src/atoms/SoftwareKeyboard/IndividualKey/__tests__/IndividualKey.test.tsx +++ b/app/src/atoms/SoftwareKeyboard/IndividualKey/__tests__/IndividualKey.test.tsx @@ -1,17 +1,19 @@ -import * as React from 'react' +import { useRef } from 'react' import { describe, it, vi, expect } from 'vitest' import { fireEvent, renderHook, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { IndividualKey } from '..' -const render = (props: React.ComponentProps) => { +import type { ComponentProps } from 'react' + +const render = (props: ComponentProps) => { return renderWithProviders()[0] } describe('IndividualKey', () => { it('should render the text key', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, @@ -22,7 +24,7 @@ describe('IndividualKey', () => { }) it('should call mock function when clicking text key', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, diff --git a/app/src/atoms/SoftwareKeyboard/IndividualKey/index.tsx b/app/src/atoms/SoftwareKeyboard/IndividualKey/index.tsx index 310008cddc8..1ba1ec5e150 100644 --- a/app/src/atoms/SoftwareKeyboard/IndividualKey/index.tsx +++ b/app/src/atoms/SoftwareKeyboard/IndividualKey/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { KeyboardReact as Keyboard } from 'react-simple-keyboard' import type { KeyboardReactInterface } from 'react-simple-keyboard' import '../index.css' diff --git a/app/src/atoms/SoftwareKeyboard/NumericalKeyboard/NumericalKeyboard.stories.tsx b/app/src/atoms/SoftwareKeyboard/NumericalKeyboard/NumericalKeyboard.stories.tsx index 22bca18ca66..216aaac86c5 100644 --- a/app/src/atoms/SoftwareKeyboard/NumericalKeyboard/NumericalKeyboard.stories.tsx +++ b/app/src/atoms/SoftwareKeyboard/NumericalKeyboard/NumericalKeyboard.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useRef } from 'react' import { DIRECTION_COLUMN, Flex, @@ -37,9 +37,9 @@ type Story = StoryObj const Keyboard = (args): JSX.Element => { const { isDecimal, hasHyphen } = args - const [showKeyboard, setShowKeyboard] = React.useState(false) - const [value, setValue] = React.useState('') - const keyboardRef = React.useRef(null) + const [showKeyboard, setShowKeyboard] = useState(false) + const [value, setValue] = useState('') + const keyboardRef = useRef(null) return ( diff --git a/app/src/atoms/SoftwareKeyboard/NumericalKeyboard/__tests__/NumericalKeyboard.test.tsx b/app/src/atoms/SoftwareKeyboard/NumericalKeyboard/__tests__/NumericalKeyboard.test.tsx index 0b3143554fa..722f50dfcf1 100644 --- a/app/src/atoms/SoftwareKeyboard/NumericalKeyboard/__tests__/NumericalKeyboard.test.tsx +++ b/app/src/atoms/SoftwareKeyboard/NumericalKeyboard/__tests__/NumericalKeyboard.test.tsx @@ -1,17 +1,19 @@ -import * as React from 'react' +import { useRef } from 'react' import { describe, it, expect, vi } from 'vitest' import '@testing-library/jest-dom/vitest' import { fireEvent, renderHook, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { NumericalKeyboard } from '..' -const render = (props: React.ComponentProps) => { +import type { ComponentProps } from 'react' + +const render = (props: ComponentProps) => { return renderWithProviders()[0] } describe('NumericalKeyboard', () => { it('should render numerical keyboard isDecimal: false and hasHyphen: false', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, @@ -41,7 +43,7 @@ describe('NumericalKeyboard', () => { }) it('should render numerical keyboard isDecimal: false and hasHyphen: true', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, @@ -72,7 +74,7 @@ describe('NumericalKeyboard', () => { }) it('should render numerical keyboard isDecimal: true and hasHyphen: false', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, @@ -103,7 +105,7 @@ describe('NumericalKeyboard', () => { }) it('should render numerical keyboard isDecimal: true and hasHyphen: true', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, @@ -135,7 +137,7 @@ describe('NumericalKeyboard', () => { }) it('should call mock function when clicking num key', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, @@ -149,7 +151,7 @@ describe('NumericalKeyboard', () => { }) it('should call mock function when clicking decimal point key', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, @@ -163,7 +165,7 @@ describe('NumericalKeyboard', () => { }) it('should call mock function when clicking hyphen key', () => { - const { result } = renderHook(() => React.useRef(null)) + const { result } = renderHook(() => useRef(null)) const props = { onChange: vi.fn(), keyboardRef: result.current, diff --git a/app/src/atoms/SoftwareKeyboard/NumericalKeyboard/index.tsx b/app/src/atoms/SoftwareKeyboard/NumericalKeyboard/index.tsx index 8c41120d536..f0025b6c972 100644 --- a/app/src/atoms/SoftwareKeyboard/NumericalKeyboard/index.tsx +++ b/app/src/atoms/SoftwareKeyboard/NumericalKeyboard/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { KeyboardReact as Keyboard } from 'react-simple-keyboard' import { numericalKeyboardLayout, numericalCustom } from '../constants' @@ -13,6 +13,7 @@ interface NumericalKeyboardProps { isDecimal?: boolean hasHyphen?: boolean debug?: boolean + initialValue?: string } // the default keyboard layout intKeyboard that doesn't have decimal point and hyphen. @@ -22,6 +23,7 @@ export function NumericalKeyboard({ isDecimal = false, hasHyphen = false, debug = false, + initialValue = '', }: NumericalKeyboardProps): JSX.Element { const layoutName = `${isDecimal ? 'float' : 'int'}${ hasHyphen ? 'NegKeyboard' : 'Keyboard' @@ -35,6 +37,9 @@ export function NumericalKeyboard({ (keyboardRef.current = r)} theme={'hg-theme-default oddTheme1 numerical-keyboard'} + onInit={keyboard => { + keyboard.setInput(initialValue) + }} onChange={onChange} display={numericalCustom} useButtonTag={true} diff --git a/app/src/atoms/SoftwareKeyboard/constants.ts b/app/src/atoms/SoftwareKeyboard/constants.ts index 1808f4bd2f3..6fccfd21b81 100644 --- a/app/src/atoms/SoftwareKeyboard/constants.ts +++ b/app/src/atoms/SoftwareKeyboard/constants.ts @@ -1,3 +1,11 @@ +import chineseLayout from 'simple-keyboard-layouts/build/layouts/chinese' + +type LayoutCandidates = + | { + [key: string]: string + } + | undefined + export const customDisplay = { '{numbers}': '123', '{shift}': 'ABC', @@ -69,3 +77,12 @@ export const numericalKeyboardLayout = { export const numericalCustom = { '{backspace}': 'del', } + +export const layoutCandidates: { + [key: string]: LayoutCandidates +} = { + // @ts-expect-error layout candidates exists but is not on the type + // in the simple-keyboard-layouts package + 'zh-CN': chineseLayout.layoutCandidates, + 'en-US': undefined, +} diff --git a/app/src/atoms/StatusLabel/StatusLabel.stories.tsx b/app/src/atoms/StatusLabel/StatusLabel.stories.tsx index 0be5f54b519..e4ef9485fbf 100644 --- a/app/src/atoms/StatusLabel/StatusLabel.stories.tsx +++ b/app/src/atoms/StatusLabel/StatusLabel.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { COLORS } from '@opentrons/components' import { StatusLabel } from './index' import type { Story, Meta } from '@storybook/react' diff --git a/app/src/atoms/StatusLabel/__tests__/StatusLabel.test.tsx b/app/src/atoms/StatusLabel/__tests__/StatusLabel.test.tsx index a75018cbbea..568326f0065 100644 --- a/app/src/atoms/StatusLabel/__tests__/StatusLabel.test.tsx +++ b/app/src/atoms/StatusLabel/__tests__/StatusLabel.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect } from 'vitest' import '@testing-library/jest-dom/vitest' import { screen } from '@testing-library/react' import { C_SKY_BLUE, COLORS } from '@opentrons/components' import { StatusLabel } from '..' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' const render = (props: React.ComponentProps) => { return renderWithProviders()[0] diff --git a/app/src/atoms/StatusLabel/index.tsx b/app/src/atoms/StatusLabel/index.tsx index a1561e5e75d..0cf2af6e384 100644 --- a/app/src/atoms/StatusLabel/index.tsx +++ b/app/src/atoms/StatusLabel/index.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import capitalize from 'lodash/capitalize' import { diff --git a/app/src/atoms/StepMeter/StepMeter.stories.tsx b/app/src/atoms/StepMeter/StepMeter.stories.tsx index 44fdf79c43f..1491451794b 100644 --- a/app/src/atoms/StepMeter/StepMeter.stories.tsx +++ b/app/src/atoms/StepMeter/StepMeter.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { StepMeter } from './index' diff --git a/app/src/atoms/StepMeter/__tests__/StepMeter.test.tsx b/app/src/atoms/StepMeter/__tests__/StepMeter.test.tsx index 92780c102f1..90f027b0f7a 100644 --- a/app/src/atoms/StepMeter/__tests__/StepMeter.test.tsx +++ b/app/src/atoms/StepMeter/__tests__/StepMeter.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { screen } from '@testing-library/react' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { StepMeter } from '..' const render = (props: React.ComponentProps) => { diff --git a/app/src/atoms/StepMeter/index.tsx b/app/src/atoms/StepMeter/index.tsx index 2c2854dc4d6..371e92b9393 100644 --- a/app/src/atoms/StepMeter/index.tsx +++ b/app/src/atoms/StepMeter/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useRef } from 'react' import { css } from 'styled-components' import { Box, @@ -16,7 +16,7 @@ interface StepMeterProps { export const StepMeter = (props: StepMeterProps): JSX.Element => { const { totalSteps, currentStep } = props - const prevPercentComplete = React.useRef(0) + const prevPercentComplete = useRef(0) const progress = currentStep != null ? currentStep : 0 const percentComplete = // this logic puts a cap at 100% percentComplete which we should never run into diff --git a/app/src/atoms/buttons/BackButton.tsx b/app/src/atoms/buttons/BackButton.tsx index 05df2fd800b..29657e1f1b2 100644 --- a/app/src/atoms/buttons/BackButton.tsx +++ b/app/src/atoms/buttons/BackButton.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' diff --git a/app/src/atoms/buttons/FloatingActionButton.stories.tsx b/app/src/atoms/buttons/FloatingActionButton.stories.tsx index a7526805a20..31e5402c13f 100644 --- a/app/src/atoms/buttons/FloatingActionButton.stories.tsx +++ b/app/src/atoms/buttons/FloatingActionButton.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { ICON_DATA_BY_NAME, VIEWPORT } from '@opentrons/components' import { FloatingActionButton } from './' diff --git a/app/src/atoms/buttons/FloatingActionButton.tsx b/app/src/atoms/buttons/FloatingActionButton.tsx index 5905bdd8fce..b7e870eab16 100644 --- a/app/src/atoms/buttons/FloatingActionButton.tsx +++ b/app/src/atoms/buttons/FloatingActionButton.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { @@ -6,6 +6,7 @@ import { BORDERS, Btn, COLORS, + CURSOR_DEFAULT, DIRECTION_ROW, Flex, Icon, @@ -33,7 +34,7 @@ export function FloatingActionButton( border-radius: ${BORDERS.borderRadius40}; box-shadow: ${BORDERS.shadowBig}; color: ${contentColor}; - cursor: default; + cursor: ${CURSOR_DEFAULT}; &:active { background-color: ${COLORS.purple55}; diff --git a/app/src/atoms/buttons/IconButton.tsx b/app/src/atoms/buttons/IconButton.tsx index 6506b267529..ee754472ff1 100644 --- a/app/src/atoms/buttons/IconButton.tsx +++ b/app/src/atoms/buttons/IconButton.tsx @@ -1,9 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { BORDERS, Btn, COLORS, + CURSOR_DEFAULT, Icon, RESPONSIVENESS, } from '@opentrons/components' @@ -39,7 +40,7 @@ export function IconButton(props: IconButtonProps): JSX.Element { color: ${COLORS.grey50}; } @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - cursor: default; + cursor: ${CURSOR_DEFAULT}; } `} {...buttonProps} diff --git a/app/src/atoms/buttons/MediumButton.tsx b/app/src/atoms/buttons/MediumButton.tsx index 7634f3e428f..d784029696a 100644 --- a/app/src/atoms/buttons/MediumButton.tsx +++ b/app/src/atoms/buttons/MediumButton.tsx @@ -1,16 +1,17 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { ALIGN_CENTER, BORDERS, Btn, COLORS, + CURSOR_DEFAULT, DIRECTION_ROW, DISPLAY_FLEX, Icon, JUSTIFY_CENTER, - SPACING, LegacyStyledText, + SPACING, TYPOGRAPHY, } from '@opentrons/components' import { ODD_FOCUS_VISIBLE } from './constants' @@ -111,7 +112,7 @@ export function MediumButton(props: MediumButtonProps): JSX.Element { : BORDERS.borderRadius16}; box-shadow: none; color: ${MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType].defaultColor}; - cursor: default; + cursor: ${CURSOR_DEFAULT}; &:focus { background-color: ${MEDIUM_BUTTON_PROPS_BY_TYPE[buttonType] diff --git a/app/src/atoms/buttons/SmallButton.tsx b/app/src/atoms/buttons/SmallButton.tsx index 25a494a1488..e659d52fd58 100644 --- a/app/src/atoms/buttons/SmallButton.tsx +++ b/app/src/atoms/buttons/SmallButton.tsx @@ -1,10 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { ALIGN_CENTER, BORDERS, Btn, COLORS, + CURSOR_DEFAULT, DIRECTION_ROW, Flex, Icon, @@ -105,7 +106,7 @@ export function SmallButton(props: SmallButtonProps): JSX.Element { color: ${SMALL_BUTTON_PROPS_BY_TYPE[buttonType].defaultColor}; background-color: ${SMALL_BUTTON_PROPS_BY_TYPE[buttonType] .defaultBackgroundColor}; - cursor: default; + cursor: ${CURSOR_DEFAULT}; border-radius: ${buttonCategory === 'rounded' ? BORDERS.borderRadius40 : BORDERS.borderRadius16}; diff --git a/app/src/atoms/buttons/SubmitPrimaryButton.tsx b/app/src/atoms/buttons/SubmitPrimaryButton.tsx index 246ec6f9051..cdbf3442a65 100644 --- a/app/src/atoms/buttons/SubmitPrimaryButton.tsx +++ b/app/src/atoms/buttons/SubmitPrimaryButton.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { SPACING, diff --git a/app/src/atoms/buttons/TextOnlyButton.tsx b/app/src/atoms/buttons/TextOnlyButton.tsx index 45174218cd9..de3bbc969ab 100644 --- a/app/src/atoms/buttons/TextOnlyButton.tsx +++ b/app/src/atoms/buttons/TextOnlyButton.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Btn, StyledText, COLORS, RESPONSIVENESS } from '@opentrons/components' import type { StyleProps } from '@opentrons/components' import { css } from 'styled-components' diff --git a/app/src/atoms/buttons/ToggleButton.tsx b/app/src/atoms/buttons/ToggleButton.tsx index 5299e332845..b814f45da1d 100644 --- a/app/src/atoms/buttons/ToggleButton.tsx +++ b/app/src/atoms/buttons/ToggleButton.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { Btn, Icon, COLORS, SIZE_1, SIZE_2 } from '@opentrons/components' diff --git a/app/src/atoms/buttons/__tests__/BackButton.test.tsx b/app/src/atoms/buttons/__tests__/BackButton.test.tsx index 7b1595d0b83..510abd0ee7d 100644 --- a/app/src/atoms/buttons/__tests__/BackButton.test.tsx +++ b/app/src/atoms/buttons/__tests__/BackButton.test.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi } from 'vitest' import '@testing-library/jest-dom/vitest' import { MemoryRouter, Route, Routes } from 'react-router-dom' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { BackButton } from '..' const render = (props?: React.HTMLProps) => { diff --git a/app/src/atoms/buttons/__tests__/FloatingActionButton.test.tsx b/app/src/atoms/buttons/__tests__/FloatingActionButton.test.tsx index 4d479fd93cf..de2bcd49e24 100644 --- a/app/src/atoms/buttons/__tests__/FloatingActionButton.test.tsx +++ b/app/src/atoms/buttons/__tests__/FloatingActionButton.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { screen } from '@testing-library/react' import { BORDERS, COLORS, SPACING, TYPOGRAPHY } from '@opentrons/components' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { FloatingActionButton } from '..' const render = (props: React.ComponentProps) => { diff --git a/app/src/atoms/buttons/__tests__/MediumButton.test.tsx b/app/src/atoms/buttons/__tests__/MediumButton.test.tsx index 1c85bf34eb1..988248413d9 100644 --- a/app/src/atoms/buttons/__tests__/MediumButton.test.tsx +++ b/app/src/atoms/buttons/__tests__/MediumButton.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import '@testing-library/jest-dom/vitest' import { describe, it, expect, vi, beforeEach } from 'vitest' import { fireEvent, screen } from '@testing-library/react' import { COLORS, BORDERS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { MediumButton } from '../MediumButton' diff --git a/app/src/atoms/buttons/__tests__/QuaternaryButton.test.tsx b/app/src/atoms/buttons/__tests__/QuaternaryButton.test.tsx index e9dd204ca2f..4e10831c73c 100644 --- a/app/src/atoms/buttons/__tests__/QuaternaryButton.test.tsx +++ b/app/src/atoms/buttons/__tests__/QuaternaryButton.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import '@testing-library/jest-dom/vitest' import { describe, it, expect, beforeEach, vi } from 'vitest' import { screen } from '@testing-library/react' import { COLORS, SPACING, TYPOGRAPHY, BORDERS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { QuaternaryButton } from '..' diff --git a/app/src/atoms/buttons/__tests__/SmallButton.test.tsx b/app/src/atoms/buttons/__tests__/SmallButton.test.tsx index 7875a12f68a..283f21daf4e 100644 --- a/app/src/atoms/buttons/__tests__/SmallButton.test.tsx +++ b/app/src/atoms/buttons/__tests__/SmallButton.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { fireEvent, screen } from '@testing-library/react' import { COLORS, BORDERS } from '@opentrons/components' import { SmallButton } from '../SmallButton' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' const render = (props: React.ComponentProps) => { return renderWithProviders()[0] diff --git a/app/src/atoms/buttons/__tests__/SubmitPrimaryButton.test.tsx b/app/src/atoms/buttons/__tests__/SubmitPrimaryButton.test.tsx index c4d33d2aef5..a62ba9c4a95 100644 --- a/app/src/atoms/buttons/__tests__/SubmitPrimaryButton.test.tsx +++ b/app/src/atoms/buttons/__tests__/SubmitPrimaryButton.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { fireEvent, screen } from '@testing-library/react' import { COLORS, SPACING, TYPOGRAPHY, BORDERS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { SubmitPrimaryButton } from '..' diff --git a/app/src/atoms/buttons/__tests__/TertiaryButton.test.tsx b/app/src/atoms/buttons/__tests__/TertiaryButton.test.tsx index 11790f63c47..4a3f95369c8 100644 --- a/app/src/atoms/buttons/__tests__/TertiaryButton.test.tsx +++ b/app/src/atoms/buttons/__tests__/TertiaryButton.test.tsx @@ -1,7 +1,7 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, beforeEach } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import '@testing-library/jest-dom/vitest' import { COLORS, SPACING, TYPOGRAPHY, BORDERS } from '@opentrons/components' diff --git a/app/src/atoms/buttons/__tests__/ToggleButton.test.tsx b/app/src/atoms/buttons/__tests__/ToggleButton.test.tsx index bc47fabe48c..3f61d43c5d0 100644 --- a/app/src/atoms/buttons/__tests__/ToggleButton.test.tsx +++ b/app/src/atoms/buttons/__tests__/ToggleButton.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import { screen, fireEvent } from '@testing-library/react' import { COLORS, SIZE_2 } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { ToggleButton } from '..' diff --git a/app/src/atoms/structure/Divider.stories.tsx b/app/src/atoms/structure/Divider.stories.tsx index b38372a575c..747c21bec09 100644 --- a/app/src/atoms/structure/Divider.stories.tsx +++ b/app/src/atoms/structure/Divider.stories.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { ALIGN_CENTER, Box, diff --git a/app/src/atoms/structure/Divider.tsx b/app/src/atoms/structure/Divider.tsx index 2f8eef69cfc..db55ad84f44 100644 --- a/app/src/atoms/structure/Divider.tsx +++ b/app/src/atoms/structure/Divider.tsx @@ -1,13 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { Box, COLORS, SPACING } from '@opentrons/components' type Props = React.ComponentProps export function Divider(props: Props): JSX.Element { + const { marginY } = props return ( diff --git a/app/src/atoms/structure/Line.stories.tsx b/app/src/atoms/structure/Line.stories.tsx index 4a37a6ee4f5..0383e5e64a2 100644 --- a/app/src/atoms/structure/Line.stories.tsx +++ b/app/src/atoms/structure/Line.stories.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { ALIGN_CENTER, Box, diff --git a/app/src/atoms/structure/Line.tsx b/app/src/atoms/structure/Line.tsx index 59f0de712bf..ecbbecc24cd 100644 --- a/app/src/atoms/structure/Line.tsx +++ b/app/src/atoms/structure/Line.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Box, BORDERS } from '@opentrons/components' type Props = React.ComponentProps diff --git a/app/src/atoms/structure/__tests__/Divider.test.tsx b/app/src/atoms/structure/__tests__/Divider.test.tsx index ff4e80c655f..27460be938d 100644 --- a/app/src/atoms/structure/__tests__/Divider.test.tsx +++ b/app/src/atoms/structure/__tests__/Divider.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { screen } from '@testing-library/react' import { SPACING, COLORS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { Divider } from '../index' const render = (props: React.ComponentProps) => { diff --git a/app/src/atoms/structure/__tests__/Line.test.tsx b/app/src/atoms/structure/__tests__/Line.test.tsx index f6fd5064ca6..d9a9caefba2 100644 --- a/app/src/atoms/structure/__tests__/Line.test.tsx +++ b/app/src/atoms/structure/__tests__/Line.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { screen } from '@testing-library/react' import { SPACING, COLORS } from '@opentrons/components' import { Line } from '../index' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' const render = (props: React.ComponentProps) => { return renderWithProviders()[0] diff --git a/app/src/i18n.ts b/app/src/i18n.ts index 0a9701a8e87..e1c772cb582 100644 --- a/app/src/i18n.ts +++ b/app/src/i18n.ts @@ -7,6 +7,20 @@ import { titleCase } from '@opentrons/shared-data' import type { InitOptions } from 'i18next' +export const US_ENGLISH = 'en-US' +export const SIMPLIFIED_CHINESE = 'zh-CN' + +// these strings will not be translated so should not be localized +export const US_ENGLISH_DISPLAY_NAME = 'English (US)' +export const SIMPLIFIED_CHINESE_DISPLAY_NAME = '中文' + +export type Language = typeof US_ENGLISH | typeof SIMPLIFIED_CHINESE + +export const LANGUAGES: Array<{ name: string; value: Language }> = [ + { name: US_ENGLISH_DISPLAY_NAME, value: US_ENGLISH }, + { name: SIMPLIFIED_CHINESE_DISPLAY_NAME, value: SIMPLIFIED_CHINESE }, +] + const i18nConfig: InitOptions = { resources, lng: 'en', diff --git a/app/src/index.tsx b/app/src/index.tsx index cf4fcbfc44c..cdb9a3c067d 100644 --- a/app/src/index.tsx +++ b/app/src/index.tsx @@ -1,11 +1,12 @@ +/* eslint-disable opentrons/no-imports-across-applications */ // client entry point and application manifest -import React from 'react' import ReactDom from 'react-dom/client' import { Provider } from 'react-redux' import { HashRouter } from 'react-router-dom' import { ApiClientProvider } from '@opentrons/react-api-client' +import { App } from './App' import { createLogger } from './logger' import { uiInitialized } from './redux/shell' @@ -16,8 +17,10 @@ import '../src/atoms/SoftwareKeyboard/FullKeyboard/index.css' import '../src/atoms/SoftwareKeyboard/IndividualKey/index.css' import '../src/atoms/SoftwareKeyboard/NumericalKeyboard/index.css' +// export public types so they can be accessed by external deps +export * from './redux/types' + // component tree -import { App } from './App' const log = createLogger(new URL('', import.meta.url).pathname) diff --git a/app/src/local-resources/commands/hooks/index.ts b/app/src/local-resources/commands/hooks/index.ts new file mode 100644 index 00000000000..6f0457c174f --- /dev/null +++ b/app/src/local-resources/commands/hooks/index.ts @@ -0,0 +1,9 @@ +export { useCommandTextString } from './useCommandTextString' + +export type { + UseCommandTextStringParams, + GetCommandText, + GetCommandTextResult, + GetTCRunExtendedProfileCommandTextResult, + GetTCRunProfileCommandTextResult, +} from './useCommandTextString' diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/index.ts b/app/src/local-resources/commands/hooks/useCommandTextString/index.ts new file mode 100644 index 00000000000..3b77c607052 --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/index.ts @@ -0,0 +1,301 @@ +import { useTranslation } from 'react-i18next' +import * as utils from './utils' + +import type { TFunction } from 'i18next' +import type { + RunTimeCommand, + RobotType, + LabwareDefinition2, +} from '@opentrons/shared-data' +import type { + TCProfileStepText, + TCProfileCycleText, + GetDirectTranslationCommandText, +} from './utils' +import type { CommandTextData } from '/app/local-resources/commands/types' + +export interface UseCommandTextStringParams { + command: RunTimeCommand | null + allRunDefs: LabwareDefinition2[] + commandTextData: CommandTextData | null + robotType: RobotType +} + +export type GetCommandText = UseCommandTextStringParams & { t: TFunction } +export interface GetGenericCommandTextResult { + kind: 'generic' + /* The actual command text. Ex "Homing all gantry, pipette, and plunger axes" */ + commandText: string +} +export interface GetTCRunProfileCommandTextResult { + kind: 'thermocycler/runProfile' + commandText: string + /* The TC run profile steps. */ + stepTexts: string[] +} +export interface GetTCRunExtendedProfileCommandTextResult { + kind: 'thermocycler/runExtendedProfile' + commandText: string + profileElementTexts: Array +} +export type GetCommandTextResult = + | GetGenericCommandTextResult + | GetTCRunProfileCommandTextResult + | GetTCRunExtendedProfileCommandTextResult + +// TODO(jh, 07-18-24): Move the testing that covers this from CommandText to a new file, and verify that all commands are +// properly tested. + +// Get the full user-facing command text string from a given command. +export function useCommandTextString( + params: UseCommandTextStringParams +): GetCommandTextResult { + const { command } = params + const { t } = useTranslation('protocol_command_text') + + const fullParams = { ...params, t } + + switch (command?.commandType) { + case 'touchTip': + case 'home': + case 'savePosition': + case 'magneticModule/engage': + case 'magneticModule/disengage': + case 'temperatureModule/deactivate': + case 'thermocycler/waitForBlockTemperature': + case 'thermocycler/waitForLidTemperature': + case 'thermocycler/openLid': + case 'thermocycler/closeLid': + case 'thermocycler/deactivateBlock': + case 'thermocycler/deactivateLid': + case 'thermocycler/awaitProfileComplete': + case 'heaterShaker/deactivateHeater': + case 'heaterShaker/openLabwareLatch': + case 'heaterShaker/closeLabwareLatch': + case 'heaterShaker/deactivateShaker': + case 'heaterShaker/waitForTemperature': + return { + kind: 'generic', + commandText: utils.getDirectTranslationCommandText( + fullParams as GetDirectTranslationCommandText + ), + } + + case 'aspirate': + case 'aspirateInPlace': + case 'dispense': + case 'dispenseInPlace': + case 'blowout': + case 'blowOutInPlace': + case 'dropTip': + case 'dropTipInPlace': + case 'pickUpTip': + case 'airGapInPlace': + return { + kind: 'generic', + commandText: utils.getPipettingCommandText(fullParams), + } + + case 'loadLabware': + case 'reloadLabware': + case 'loadPipette': + case 'loadModule': + case 'loadLiquid': + return { + kind: 'generic', + commandText: utils.getLoadCommandText(fullParams), + } + + case 'liquidProbe': + case 'tryLiquidProbe': + return { + kind: 'generic', + commandText: utils.getLiquidProbeCommandText({ + ...fullParams, + command, + }), + } + + case 'temperatureModule/setTargetTemperature': + case 'temperatureModule/waitForTemperature': + case 'thermocycler/setTargetBlockTemperature': + case 'thermocycler/setTargetLidTemperature': + case 'heaterShaker/setTargetTemperature': + return { + kind: 'generic', + commandText: utils.getTemperatureCommandText({ + ...fullParams, + command, + }), + } + case 'absorbanceReader/openLid': + case 'absorbanceReader/closeLid': + case 'absorbanceReader/initialize': + case 'absorbanceReader/read': + return { + kind: 'generic', + commandText: utils.getAbsorbanceReaderCommandText({ + ...fullParams, + command, + }), + } + case 'thermocycler/runProfile': + return utils.getTCRunProfileCommandText({ ...fullParams, command }) + + case 'thermocycler/runExtendedProfile': + return utils.getTCRunExtendedProfileCommandText({ + ...fullParams, + command, + }) + + case 'heaterShaker/setAndWaitForShakeSpeed': + return { + kind: 'generic', + commandText: utils.getHSShakeSpeedCommandText({ + ...fullParams, + command, + }), + } + + case 'moveToSlot': + return { + kind: 'generic', + commandText: utils.getMoveToSlotCommandText({ ...fullParams, command }), + } + + case 'moveRelative': + return { + kind: 'generic', + commandText: utils.getMoveRelativeCommandText({ + ...fullParams, + command, + }), + } + + case 'moveToCoordinates': + return { + kind: 'generic', + commandText: utils.getMoveToCoordinatesCommandText({ + ...fullParams, + command, + }), + } + + case 'moveToWell': + return { + kind: 'generic', + commandText: utils.getMoveToWellCommandText({ ...fullParams, command }), + } + + case 'moveLabware': + return { + kind: 'generic', + commandText: utils.getMoveLabwareCommandText({ + ...fullParams, + command, + }), + } + + case 'configureForVolume': + return { + kind: 'generic', + commandText: utils.getConfigureForVolumeCommandText({ + ...fullParams, + command, + }), + } + + case 'configureNozzleLayout': + return { + kind: 'generic', + commandText: utils.getConfigureNozzleLayoutCommandText({ + ...fullParams, + command, + }), + } + + case 'prepareToAspirate': + return { + kind: 'generic', + commandText: utils.getPrepareToAspirateCommandText({ + ...fullParams, + command, + }), + } + + case 'moveToAddressableArea': + return { + kind: 'generic', + commandText: utils.getMoveToAddressableAreaCommandText({ + ...fullParams, + command, + }), + } + + case 'moveToAddressableAreaForDropTip': + return { + kind: 'generic', + commandText: utils.getMoveToAddressableAreaForDropTipCommandText({ + ...fullParams, + command, + }), + } + + case 'waitForDuration': + return { + kind: 'generic', + commandText: utils.getWaitForDurationCommandText({ + ...fullParams, + command, + }), + } + + case 'pause': // legacy pause command + case 'waitForResume': + return { + kind: 'generic', + commandText: utils.getWaitForResumeCommandText({ + ...fullParams, + command, + }), + } + + case 'delay': + return { + kind: 'generic', + commandText: utils.getDelayCommandText({ ...fullParams, command }), + } + + case 'comment': + return { + kind: 'generic', + commandText: utils.getCommentCommandText({ ...fullParams, command }), + } + + case 'custom': + return { + kind: 'generic', + commandText: utils.getCustomCommandText({ ...fullParams, command }), + } + + case 'setRailLights': + return { + kind: 'generic', + commandText: utils.getRailLightsCommandText({ ...fullParams, command }), + } + + case undefined: + case null: + return { kind: 'generic', commandText: '' } + + default: + console.warn( + 'CommandText encountered a command with an unrecognized commandType: ', + command + ) + return { + kind: 'generic', + commandText: utils.getUnknownCommandText({ ...fullParams, command }), + } + } +} diff --git a/app/src/molecules/Command/utils/__tests__/getFinalLabwareLocation.test.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/__tests__/getFinalLabwareLocation.test.ts similarity index 100% rename from app/src/molecules/Command/utils/__tests__/getFinalLabwareLocation.test.ts rename to app/src/local-resources/commands/hooks/useCommandTextString/utils/__tests__/getFinalLabwareLocation.test.ts diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/__tests__/getPipettingCommandText.test.tsx b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/__tests__/getPipettingCommandText.test.tsx new file mode 100644 index 00000000000..82b269bd581 --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/__tests__/getPipettingCommandText.test.tsx @@ -0,0 +1,186 @@ +import { screen } from '@testing-library/react' +import { vi, describe, it, beforeEach } from 'vitest' +import { useTranslation } from 'react-i18next' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { + getLabwareDefinitionsFromCommands, + getLabwareName, + getLoadedLabware, + getLabwareDisplayLocation, +} from '/app/local-resources/labware' +import { getPipettingCommandText } from '../getPipettingCommandText' +import { getLabwareDefURI } from '@opentrons/shared-data' +import { getFinalLabwareLocation } from '../../getFinalLabwareLocation' +import { getWellRange } from '../../getWellRange' +import { getFinalMoveToAddressableAreaCmd } from '../../getFinalAddressableAreaCmd' +import { getAddressableAreaDisplayName } from '../../getAddressableAreaDisplayName' + +vi.mock('@opentrons/shared-data') +vi.mock('../../getFinalLabwareLocation') +vi.mock('../../getWellRange') +vi.mock('/app/local-resources/labware') +vi.mock('../../getFinalAddressableAreaCmd') +vi.mock('../../getAddressableAreaDisplayName') + +const baseCommandData = { + allRunDefs: {}, + robotType: 'OT-2', + commandTextData: { + commands: [], + labware: [], + modules: [], + pipettes: [{ id: 'pipette-1', pipetteName: 'p300_single' }], + }, +} as any + +function TestWrapper({ command }: { command: any }): JSX.Element { + const { t } = useTranslation('protocol_command_text') + const text = getPipettingCommandText({ + command, + ...baseCommandData, + t, + }) + + return
{text}
+} + +const render = (command: any) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} + +describe('getPipettingCommandText', () => { + beforeEach(() => { + vi.mocked(getLabwareDefURI).mockImplementation((def: any) => def.uri) + vi.mocked(getFinalLabwareLocation).mockReturnValue('slot-1' as any) + vi.mocked(getWellRange).mockReturnValue('A1') + vi.mocked(getLabwareDefinitionsFromCommands).mockReturnValue([ + { uri: 'tiprack-uri', parameters: { isTiprack: true } }, + { uri: 'plate-uri', parameters: { isTiprack: false } }, + ] as any) + vi.mocked(getLabwareName).mockReturnValue('Test Labware') + vi.mocked(getLoadedLabware).mockImplementation( + (labware, id) => + ({ + definitionUri: id === 'tiprack-id' ? 'tiprack-uri' : 'plate-uri', + } as any) + ) + vi.mocked(getLabwareDisplayLocation).mockReturnValue('Slot 1') + vi.mocked(getFinalMoveToAddressableAreaCmd).mockReturnValue({ + id: 'cmd-1', + commandType: 'moveToAddressableArea', + } as any) + vi.mocked(getAddressableAreaDisplayName).mockReturnValue('Fixed Trash') + }) + + it('should render aspirate command text correctly', () => { + const command = { + id: 'cmd-1', + commandType: 'aspirate', + params: { + labwareId: 'labware-1', + wellName: 'A1', + volume: 100, + flowRate: 150, + }, + } + + render(command) + screen.getByText( + /Aspirating 100 µL from well A1 of Test Labware in Slot 1 at 150 µL\/sec/ + ) + }) + + it('should render dispense command text correctly', () => { + const command = { + id: 'cmd-1', + commandType: 'dispense', + params: { + labwareId: 'labware-1', + wellName: 'A1', + volume: 100, + flowRate: 150, + }, + } + + render(command) + screen.getByText( + /Dispensing 100 µL into well A1 of Test Labware in Slot 1 at 150 µL\/sec/ + ) + }) + + it('should render dispense with push out command text correctly', () => { + const command = { + id: 'cmd-1', + commandType: 'dispense', + params: { + labwareId: 'labware-1', + wellName: 'A1', + volume: 100, + flowRate: 150, + pushOut: 10, + }, + } + + render(command) + screen.getByText( + /Dispensing 100 µL into well A1 of Test Labware in Slot 1 at 150 µL\/sec and pushing out 10 µL/ + ) + }) + + it('should render pickup tip command text correctly', () => { + const command = { + id: 'cmd-1', + commandType: 'pickUpTip', + params: { + labwareId: 'tiprack-id', + wellName: 'A1', + pipetteId: 'pipette-1', + }, + } + + render(command) + screen.getByText(/Picking up tip\(s\) from A1 of Test Labware in Slot 1/) + }) + + it('should render drop tip in tiprack command text correctly', () => { + const command = { + id: 'cmd-1', + commandType: 'dropTip', + params: { + labwareId: 'tiprack-id', + wellName: 'A1', + }, + } + + render(command) + screen.getByText(/Returning tip to A1 of Test Labware in Slot 1/) + }) + + it('should render drop tip in place command text correctly if there is an addressable area name', () => { + const command = { + id: 'cmd-1', + commandType: 'dropTipInPlace', + params: {}, + } + + render(command) + screen.getByText('Dropping tip in Fixed Trash') + }) + + it('should render drop tip in place command text correctly if there is not an addressable area name', () => { + const command = { + id: 'cmd-1', + commandType: 'dropTipInPlace', + params: {}, + } + + vi.mocked(getFinalMoveToAddressableAreaCmd).mockReturnValue(null) + + render(command) + screen.getByText('Dropping tip in place') + }) +}) diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getAbsorbanceReaderCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getAbsorbanceReaderCommandText.ts new file mode 100644 index 00000000000..926ed749609 --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getAbsorbanceReaderCommandText.ts @@ -0,0 +1,53 @@ +import type { + AbsorbanceReaderOpenLidRunTimeCommand, + AbsorbanceReaderCloseLidRunTimeCommand, + AbsorbanceReaderInitializeRunTimeCommand, + AbsorbanceReaderReadRunTimeCommand, + RunTimeCommand, +} from '@opentrons/shared-data' +import type { HandlesCommands } from '../types' + +export type AbsorbanceCreateCommand = + | AbsorbanceReaderOpenLidRunTimeCommand + | AbsorbanceReaderCloseLidRunTimeCommand + | AbsorbanceReaderInitializeRunTimeCommand + | AbsorbanceReaderReadRunTimeCommand + +const KEYS_BY_COMMAND_TYPE: { + [commandType in AbsorbanceCreateCommand['commandType']]: string +} = { + 'absorbanceReader/openLid': 'absorbance_reader_open_lid', + 'absorbanceReader/closeLid': 'absorbance_reader_close_lid', + 'absorbanceReader/initialize': 'absorbance_reader_initialize', + 'absorbanceReader/read': 'absorbance_reader_read', +} + +type HandledCommands = Extract< + RunTimeCommand, + { commandType: keyof typeof KEYS_BY_COMMAND_TYPE } +> + +type GetAbsorbanceReaderCommandText = HandlesCommands + +export const getAbsorbanceReaderCommandText = ({ + command, + t, +}: GetAbsorbanceReaderCommandText): string => { + if (command.commandType === 'absorbanceReader/initialize') { + const wavelengths = command.params.sampleWavelengths.join(' nm, ') + ` nm` + const mode = + command.params.measureMode === 'multi' ? t('multiple') : t('single') + + return `${t('absorbance_reader_initialize', { + mode, + wavelengths, + })} ${ + command.params.referenceWavelength != null + ? t('with_reference_of', { + wavelength: command.params.referenceWavelength, + }) + : '' + }` + } + return t(KEYS_BY_COMMAND_TYPE[command.commandType]) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getCommentCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getCommentCommandText.ts similarity index 83% rename from app/src/molecules/Command/hooks/useCommandTextString/utils/getCommentCommandText.ts rename to app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getCommentCommandText.ts index 3a1b7ce7e8a..f50de82c96e 100644 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getCommentCommandText.ts +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getCommentCommandText.ts @@ -1,5 +1,5 @@ import type { CommentRunTimeCommand } from '@opentrons/shared-data/command' -import type { HandlesCommands } from './types' +import type { HandlesCommands } from '../types' export function getCommentCommandText({ command, diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getConfigureForVolumeCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getConfigureForVolumeCommandText.ts similarity index 92% rename from app/src/molecules/Command/hooks/useCommandTextString/utils/getConfigureForVolumeCommandText.ts rename to app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getConfigureForVolumeCommandText.ts index 1a4ee2e7c0e..fd9456b4cc3 100644 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getConfigureForVolumeCommandText.ts +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getConfigureForVolumeCommandText.ts @@ -1,7 +1,7 @@ import { getPipetteSpecsV2 } from '@opentrons/shared-data' import type { ConfigureForVolumeRunTimeCommand } from '@opentrons/shared-data/command' -import type { HandlesCommands } from './types' +import type { HandlesCommands } from '../types' export function getConfigureForVolumeCommandText({ command, diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getConfigureNozzleLayoutCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getConfigureNozzleLayoutCommandText.ts new file mode 100644 index 00000000000..8c9e12f3d5b --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getConfigureNozzleLayoutCommandText.ts @@ -0,0 +1,30 @@ +import { getPipetteSpecsV2 } from '@opentrons/shared-data' + +import type { ConfigureNozzleLayoutRunTimeCommand } from '@opentrons/shared-data' +import type { HandlesCommands } from '../types' + +export function getConfigureNozzleLayoutCommandText({ + command, + commandTextData, + t, +}: HandlesCommands): string { + const { configurationParams, pipetteId } = command.params + const pipetteName = commandTextData?.pipettes.find( + pip => pip.id === pipetteId + )?.pipetteName + + // TODO(cb, 2024-09-10): confirm these strings for copy consistency and add them to i18n + const ConfigAmount = { + SINGLE: 'single nozzle layout', + COLUMN: 'column layout', + ROW: 'row layout', + QUADRANT: 'partial layout', + ALL: 'all nozzles', + } + + return t('configure_nozzle_layout', { + layout: ConfigAmount[configurationParams.style], + pipette: + pipetteName != null ? getPipetteSpecsV2(pipetteName)?.displayName : '', + }) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getCustomCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getCustomCommandText.ts similarity index 91% rename from app/src/molecules/Command/hooks/useCommandTextString/utils/getCustomCommandText.ts rename to app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getCustomCommandText.ts index da6d5a1d506..97d60249f7e 100644 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getCustomCommandText.ts +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getCustomCommandText.ts @@ -1,5 +1,5 @@ import type { CustomRunTimeCommand } from '@opentrons/shared-data/command' -import type { HandlesCommands } from './types' +import type { HandlesCommands } from '../types' export function getCustomCommandText({ command, diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getDelayCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getDelayCommandText.ts similarity index 91% rename from app/src/molecules/Command/hooks/useCommandTextString/utils/getDelayCommandText.ts rename to app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getDelayCommandText.ts index 8bb24d99661..f421a163b36 100644 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getDelayCommandText.ts +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getDelayCommandText.ts @@ -1,5 +1,5 @@ import type { DeprecatedDelayRunTimeCommand } from '@opentrons/shared-data/command' -import type { HandlesCommands } from './types' +import type { HandlesCommands } from '../types' export function getDelayCommandText({ command, diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getDirectTranslationCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getDirectTranslationCommandText.ts similarity index 97% rename from app/src/molecules/Command/hooks/useCommandTextString/utils/getDirectTranslationCommandText.ts rename to app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getDirectTranslationCommandText.ts index fd586136e90..92e969f402b 100644 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getDirectTranslationCommandText.ts +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getDirectTranslationCommandText.ts @@ -1,5 +1,5 @@ import type { RunTimeCommand } from '@opentrons/shared-data/command' -import type { HandlesCommands } from './types' +import type { HandlesCommands } from '../types' const SIMPLE_TRANSLATION_KEY_BY_COMMAND_TYPE: { [commandType in RunTimeCommand['commandType']]?: string diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getHSShakeSpeedCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getHSShakeSpeedCommandText.ts similarity index 87% rename from app/src/molecules/Command/hooks/useCommandTextString/utils/getHSShakeSpeedCommandText.ts rename to app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getHSShakeSpeedCommandText.ts index 3710e7f0930..157cde89212 100644 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getHSShakeSpeedCommandText.ts +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getHSShakeSpeedCommandText.ts @@ -1,5 +1,5 @@ import type { HeaterShakerSetAndWaitForShakeSpeedRunTimeCommand } from '@opentrons/shared-data/command' -import type { HandlesCommands } from './types' +import type { HandlesCommands } from '../types' export function getHSShakeSpeedCommandText({ command, diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getLiquidProbeCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getLiquidProbeCommandText.ts new file mode 100644 index 00000000000..014d30318eb --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getLiquidProbeCommandText.ts @@ -0,0 +1,63 @@ +import { + getLabwareName, + getLabwareDisplayLocation, +} from '/app/local-resources/labware' +import { getFinalLabwareLocation } from '../getFinalLabwareLocation' + +import type { + LiquidProbeRunTimeCommand, + RunTimeCommand, + TryLiquidProbeRunTimeCommand, +} from '@opentrons/shared-data' +import type { HandlesCommands } from '../types' + +type LiquidProbeRunTimeCommands = + | LiquidProbeRunTimeCommand + | TryLiquidProbeRunTimeCommand + +export function getLiquidProbeCommandText({ + command, + allRunDefs, + commandTextData, + t, + robotType, +}: HandlesCommands): string { + const { wellName, labwareId } = command.params + + const allPreviousCommands = commandTextData?.commands.slice( + 0, + commandTextData.commands.findIndex(c => c.id === command?.id) + ) + + const labwareLocation = + allPreviousCommands != null + ? getFinalLabwareLocation( + labwareId as string, + allPreviousCommands as RunTimeCommand[] + ) + : null + + const displayLocation = getLabwareDisplayLocation({ + loadedLabwares: commandTextData?.labware ?? [], + location: labwareLocation, + robotType, + allRunDefs, + loadedModules: commandTextData?.modules ?? [], + t, + }) + + const labware = + commandTextData != null + ? getLabwareName({ + loadedLabwares: commandTextData?.labware ?? [], + labwareId, + allRunDefs, + }) + : null + + return t('detect_liquid_presence', { + labware, + labware_location: displayLocation, + well_name: wellName, + }) +} diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getLoadCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getLoadCommandText.ts new file mode 100644 index 00000000000..52663e94305 --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getLoadCommandText.ts @@ -0,0 +1,113 @@ +import { + getModuleDisplayName, + getModuleType, + getOccludedSlotCountForModule, + getPipetteSpecsV2, +} from '@opentrons/shared-data' + +import { getPipetteNameOnMount } from '../getPipetteNameOnMount' +import { getLiquidDisplayName } from '../getLiquidDisplayName' + +import { + getLabwareName, + getLabwareDisplayLocation, +} from '/app/local-resources/labware' + +import type { GetCommandText } from '../..' + +export const getLoadCommandText = ({ + command, + commandTextData, + robotType, + t, + allRunDefs, +}: GetCommandText): string => { + switch (command?.commandType) { + case 'loadPipette': { + const pipetteModel = + commandTextData != null + ? getPipetteNameOnMount( + commandTextData.pipettes, + command.params.mount + ) + : null + return t('load_pipette_protocol_setup', { + pipette_name: + pipetteModel != null + ? getPipetteSpecsV2(pipetteModel)?.displayName ?? '' + : '', + mount_name: command.params.mount === 'left' ? t('left') : t('right'), + }) + } + case 'loadModule': { + const occludedSlotCount = getOccludedSlotCountForModule( + getModuleType(command.params.model), + robotType + ) + return t('load_module_protocol_setup', { + count: occludedSlotCount, + module: getModuleDisplayName(command.params.model), + slot_name: command.params.location.slotName, + }) + } + case 'loadLabware': { + const location = getLabwareDisplayLocation({ + location: command.params.location, + robotType, + allRunDefs, + loadedLabwares: commandTextData?.labware ?? [], + loadedModules: commandTextData?.modules ?? [], + t, + }) + const labwareName = command.result?.definition.metadata.displayName + // use in preposition for modules and slots, on for labware and adapters + let displayLocation = t('in_location', { location }) + if (command.params.location === 'offDeck') { + displayLocation = location + } else if ('labwareId' in command.params.location) { + displayLocation = t('on_location', { location }) + } + + return t('load_labware_to_display_location', { + labware: labwareName, + display_location: displayLocation, + }) + } + case 'reloadLabware': { + const { labwareId } = command.params + const labware = + commandTextData != null + ? getLabwareName({ + loadedLabwares: commandTextData?.labware ?? [], + labwareId, + allRunDefs, + }) + : null + return t('reloading_labware', { labware }) + } + case 'loadLiquid': { + const { liquidId, labwareId } = command.params + return t('load_liquids_info_protocol_setup', { + liquid: + commandTextData != null + ? getLiquidDisplayName(commandTextData.liquids ?? [], liquidId) + : null, + labware: + commandTextData != null + ? getLabwareName({ + loadedLabwares: commandTextData?.labware ?? [], + labwareId, + allRunDefs, + }) + : null, + }) + } + default: { + console.warn( + 'LoadCommandText encountered a command with an unrecognized commandType: ', + command + ) + return '' + } + } +} diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveLabwareCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveLabwareCommandText.ts new file mode 100644 index 00000000000..29e90946bb4 --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveLabwareCommandText.ts @@ -0,0 +1,78 @@ +import { GRIPPER_WASTE_CHUTE_ADDRESSABLE_AREA } from '@opentrons/shared-data' + +import { getFinalLabwareLocation } from '../getFinalLabwareLocation' +import { + getLabwareName, + getLabwareDisplayLocation, +} from '/app/local-resources/labware' + +import type { MoveLabwareRunTimeCommand } from '@opentrons/shared-data' +import type { HandlesCommands } from '../types' + +export function getMoveLabwareCommandText({ + command, + allRunDefs, + t, + commandTextData, + robotType, +}: HandlesCommands): string { + const { labwareId, newLocation, strategy } = command.params + + const allPreviousCommands = commandTextData?.commands.slice( + 0, + commandTextData.commands.findIndex(c => c.id === command.id) + ) + const oldLocation = + allPreviousCommands != null + ? getFinalLabwareLocation(labwareId, allPreviousCommands) + : null + + const oldDisplayLocation = getLabwareDisplayLocation({ + location: oldLocation, + robotType, + allRunDefs, + loadedLabwares: commandTextData?.labware ?? [], + loadedModules: commandTextData?.modules ?? [], + t, + }) + const newDisplayLocation = getLabwareDisplayLocation({ + location: newLocation, + robotType, + allRunDefs, + loadedLabwares: commandTextData?.labware ?? [], + loadedModules: commandTextData?.modules ?? [], + t, + }) + + const location = newDisplayLocation?.includes( + GRIPPER_WASTE_CHUTE_ADDRESSABLE_AREA + ) + ? 'Waste Chute' + : newDisplayLocation + + return strategy === 'usingGripper' + ? t('move_labware_using_gripper', { + labware: + commandTextData != null + ? getLabwareName({ + allRunDefs, + loadedLabwares: commandTextData.labware ?? [], + labwareId, + }) + : null, + old_location: oldDisplayLocation, + new_location: location, + }) + : t('move_labware_manually', { + labware: + commandTextData != null + ? getLabwareName({ + allRunDefs, + loadedLabwares: commandTextData.labware ?? [], + labwareId, + }) + : null, + old_location: oldDisplayLocation, + new_location: location, + }) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveRelativeCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveRelativeCommandText.ts similarity index 86% rename from app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveRelativeCommandText.ts rename to app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveRelativeCommandText.ts index 7f3f8bf0aaa..d104e522fcd 100644 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveRelativeCommandText.ts +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveRelativeCommandText.ts @@ -1,5 +1,5 @@ import type { MoveRelativeRunTimeCommand } from '@opentrons/shared-data/command' -import type { HandlesCommands } from './types' +import type { HandlesCommands } from '../types' export function getMoveRelativeCommandText({ command, diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveToAddressableAreaCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveToAddressableAreaCommandText.ts new file mode 100644 index 00000000000..5dd4adb4ca4 --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveToAddressableAreaCommandText.ts @@ -0,0 +1,19 @@ +import { getAddressableAreaDisplayName } from '../getAddressableAreaDisplayName' + +import type { MoveToAddressableAreaRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from '../types' + +export function getMoveToAddressableAreaCommandText({ + command, + commandTextData, + t, +}: HandlesCommands): string { + const addressableAreaDisplayName = + commandTextData != null + ? getAddressableAreaDisplayName(commandTextData.commands, command.id, t) + : null + + return t('move_to_addressable_area', { + addressable_area: addressableAreaDisplayName, + }) +} diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveToAddressableAreaForDropTipCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveToAddressableAreaForDropTipCommandText.ts new file mode 100644 index 00000000000..29cd446a9ad --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveToAddressableAreaForDropTipCommandText.ts @@ -0,0 +1,19 @@ +import { getAddressableAreaDisplayName } from '../getAddressableAreaDisplayName' + +import type { MoveToAddressableAreaForDropTipRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from '../types' + +export function getMoveToAddressableAreaForDropTipCommandText({ + command, + commandTextData, + t, +}: HandlesCommands): string { + const addressableAreaDisplayName = + commandTextData != null + ? getAddressableAreaDisplayName(commandTextData.commands, command.id, t) + : null + + return t('move_to_addressable_area_drop_tip', { + addressable_area: addressableAreaDisplayName, + }) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToCoordinatesCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveToCoordinatesCommandText.ts similarity index 86% rename from app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToCoordinatesCommandText.ts rename to app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveToCoordinatesCommandText.ts index a3dc5ace9fe..fde6e5aff22 100644 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToCoordinatesCommandText.ts +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveToCoordinatesCommandText.ts @@ -1,5 +1,5 @@ import type { MoveToCoordinatesRunTimeCommand } from '@opentrons/shared-data/command' -import type { HandlesCommands } from './types' +import type { HandlesCommands } from '../types' export function getMoveToCoordinatesCommandText({ command, diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToSlotCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveToSlotCommandText.ts similarity index 85% rename from app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToSlotCommandText.ts rename to app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveToSlotCommandText.ts index b66f5d78513..75904b7cb43 100644 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToSlotCommandText.ts +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveToSlotCommandText.ts @@ -1,5 +1,5 @@ import type { MoveToSlotRunTimeCommand } from '@opentrons/shared-data/command' -import type { HandlesCommands } from './types' +import type { HandlesCommands } from '../types' export function getMoveToSlotCommandText({ command, diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveToWellCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveToWellCommandText.ts new file mode 100644 index 00000000000..50bdba0a52f --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getMoveToWellCommandText.ts @@ -0,0 +1,49 @@ +import { + getLabwareName, + getLabwareDisplayLocation, +} from '/app/local-resources/labware' + +import { getFinalLabwareLocation } from '../getFinalLabwareLocation' + +import type { MoveToWellRunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from '../types' + +export function getMoveToWellCommandText({ + command, + allRunDefs, + t, + commandTextData, + robotType, +}: HandlesCommands): string { + const { wellName, labwareId } = command.params + const allPreviousCommands = commandTextData?.commands.slice( + 0, + commandTextData.commands.findIndex(c => c.id === command.id) + ) + const labwareLocation = + allPreviousCommands != null + ? getFinalLabwareLocation(labwareId, allPreviousCommands) + : null + + const displayLocation = getLabwareDisplayLocation({ + location: labwareLocation, + robotType, + allRunDefs, + loadedLabwares: commandTextData?.labware ?? [], + loadedModules: commandTextData?.modules ?? [], + t, + }) + + return t('move_to_well', { + well_name: wellName, + labware: + commandTextData != null + ? getLabwareName({ + loadedLabwares: commandTextData.labware ?? [], + labwareId, + allRunDefs, + }) + : null, + labware_location: displayLocation, + }) +} diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getPipettingCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getPipettingCommandText.ts new file mode 100644 index 00000000000..2a0d87762b2 --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getPipettingCommandText.ts @@ -0,0 +1,224 @@ +import { getLabwareDefURI } from '@opentrons/shared-data' + +import { getFinalLabwareLocation } from '../getFinalLabwareLocation' +import { getWellRange } from '../getWellRange' + +import { + getLabwareDefinitionsFromCommands, + getLabwareName, + getLoadedLabware, + getLabwareDisplayLocation, +} from '/app/local-resources/labware' + +import type { PipetteName, RunTimeCommand } from '@opentrons/shared-data' +import type { GetCommandText } from '../..' +import { getFinalMoveToAddressableAreaCmd } from '/app/local-resources/commands/hooks/useCommandTextString/utils/getFinalAddressableAreaCmd' +import { getAddressableAreaDisplayName } from '/app/local-resources/commands/hooks/useCommandTextString/utils/getAddressableAreaDisplayName' + +export const getPipettingCommandText = ({ + command, + allRunDefs, + commandTextData, + robotType, + t, +}: GetCommandText): string => { + const labwareId = + command != null && 'labwareId' in command.params + ? (command.params.labwareId as string) + : '' + const wellName = + command != null && 'wellName' in command.params + ? command.params.wellName + : '' + + const allPreviousCommands = commandTextData?.commands.slice( + 0, + commandTextData.commands.findIndex(c => c.id === command?.id) + ) + const labwareLocation = + allPreviousCommands != null + ? getFinalLabwareLocation( + labwareId, + allPreviousCommands as RunTimeCommand[] + ) + : null + + const displayLocation = getLabwareDisplayLocation({ + location: labwareLocation, + robotType, + allRunDefs, + loadedLabwares: commandTextData?.labware ?? [], + loadedModules: commandTextData?.modules ?? [], + t, + }) + + switch (command?.commandType) { + case 'aspirate': { + const { volume, flowRate } = command.params + return t('aspirate', { + well_name: wellName, + labware: + commandTextData != null + ? getLabwareName({ + loadedLabwares: commandTextData.labware ?? [], + labwareId, + allRunDefs, + }) + : null, + labware_location: displayLocation, + volume, + flow_rate: flowRate, + }) + } + case 'dispense': { + const { volume, flowRate, pushOut } = command.params + return pushOut != null + ? t('dispense_push_out', { + well_name: wellName, + labware: + commandTextData != null + ? getLabwareName({ + loadedLabwares: commandTextData.labware ?? [], + labwareId, + allRunDefs, + }) + : null, + labware_location: displayLocation, + volume, + flow_rate: flowRate, + push_out_volume: pushOut, + }) + : t('dispense', { + well_name: wellName, + labware: + commandTextData != null + ? getLabwareName({ + loadedLabwares: commandTextData.labware ?? [], + labwareId, + allRunDefs, + }) + : null, + labware_location: displayLocation, + volume, + flow_rate: flowRate, + }) + } + case 'blowout': { + const { flowRate } = command.params + return t('blowout', { + well_name: wellName, + labware: + commandTextData != null + ? getLabwareName({ + loadedLabwares: commandTextData.labware ?? [], + labwareId, + allRunDefs, + }) + : null, + labware_location: displayLocation, + flow_rate: flowRate, + }) + } + case 'dropTip': { + const loadedLabware = + commandTextData != null + ? getLoadedLabware(commandTextData.labware ?? [], labwareId) + : null + const labwareDefinitions = + commandTextData != null + ? getLabwareDefinitionsFromCommands( + commandTextData.commands as RunTimeCommand[] + ) + : null + const labwareDef = labwareDefinitions?.find( + lw => getLabwareDefURI(lw) === loadedLabware?.definitionUri + ) + return Boolean(labwareDef?.parameters.isTiprack) + ? t('return_tip', { + well_name: wellName, + labware: + commandTextData != null + ? getLabwareName({ + loadedLabwares: commandTextData.labware ?? [], + labwareId, + allRunDefs, + }) + : null, + labware_location: displayLocation, + }) + : t('drop_tip', { + well_name: wellName, + labware: + commandTextData != null + ? getLabwareName({ + loadedLabwares: commandTextData.labware ?? [], + labwareId, + allRunDefs, + }) + : null, + }) + } + case 'pickUpTip': { + const pipetteId = command.params.pipetteId + const pipetteName: + | PipetteName + | undefined = commandTextData?.pipettes.find( + pipette => pipette.id === pipetteId + )?.pipetteName + + return t('pickup_tip', { + well_range: + allPreviousCommands != null + ? getWellRange( + pipetteId, + allPreviousCommands as RunTimeCommand[], + wellName as string, + pipetteName + ) + : null, + labware: + commandTextData != null + ? getLabwareName({ + loadedLabwares: commandTextData.labware ?? [], + labwareId, + allRunDefs, + }) + : null, + labware_location: displayLocation, + }) + } + case 'dropTipInPlace': { + const cmd = getFinalMoveToAddressableAreaCmd(allPreviousCommands ?? []) + + if (cmd != null) { + const displayName = getAddressableAreaDisplayName([cmd], cmd?.id, t) + return t('dropping_tip_in_trash', { trash: displayName }) + } else { + return t('drop_tip_in_place') + } + } + case 'dispenseInPlace': { + const { volume, flowRate } = command.params + return t('dispense_in_place', { volume, flow_rate: flowRate }) + } + case 'blowOutInPlace': { + const { flowRate } = command.params + return t('blowout_in_place', { flow_rate: flowRate }) + } + case 'aspirateInPlace': { + const { flowRate, volume } = command.params + return t('aspirate_in_place', { volume, flow_rate: flowRate }) + } + case 'airGapInPlace': { + const { volume } = command.params + return t('air_gap_in_place', { volume }) + } + default: { + console.warn( + 'PipettingCommandText encountered a command with an unrecognized commandType: ', + command + ) + return '' + } + } +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getPrepareToAspirateCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getPrepareToAspirateCommandText.ts similarity index 92% rename from app/src/molecules/Command/hooks/useCommandTextString/utils/getPrepareToAspirateCommandText.ts rename to app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getPrepareToAspirateCommandText.ts index 13d32b6b7d6..f0d68c3fd4d 100644 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getPrepareToAspirateCommandText.ts +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getPrepareToAspirateCommandText.ts @@ -1,7 +1,7 @@ import { getPipetteSpecsV2 } from '@opentrons/shared-data' import type { PrepareToAspirateRunTimeCommand } from '@opentrons/shared-data/command' -import type { HandlesCommands } from './types' +import type { HandlesCommands } from '../types' export function getPrepareToAspirateCommandText({ command, diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getRailLightsCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getRailLightsCommandText.ts new file mode 100644 index 00000000000..8fe4c18aa4b --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getRailLightsCommandText.ts @@ -0,0 +1,15 @@ +import type { RunTimeCommand } from '@opentrons/shared-data/command' +import type { HandlesCommands } from '../types' + +type HandledCommands = Extract + +export type GetRailLightsCommandText = HandlesCommands + +export function getRailLightsCommandText({ + command, + t, +}: GetRailLightsCommandText): string { + return command.params.on + ? t('turning_rail_lights_on') + : t('turning_rail_lights_off') +} diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getTCRunExtendedProfileCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getTCRunExtendedProfileCommandText.ts new file mode 100644 index 00000000000..2d09f07f28c --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getTCRunExtendedProfileCommandText.ts @@ -0,0 +1,67 @@ +import { formatDurationLabeled } from '/app/transformations/commands' +import type { + TCRunExtendedProfileRunTimeCommand, + TCProfileCycle, + AtomicProfileStep, +} from '@opentrons/shared-data/command' +import type { GetTCRunExtendedProfileCommandTextResult } from '../..' +import type { HandlesCommands } from '../types' + +export interface TCProfileStepText { + kind: 'step' + stepText: string +} + +export interface TCProfileCycleText { + kind: 'cycle' + cycleText: string + stepTexts: TCProfileStepText[] +} + +export function getTCRunExtendedProfileCommandText({ + command, + t, +}: HandlesCommands): GetTCRunExtendedProfileCommandTextResult { + const { profileElements } = command.params + + const stepText = ({ + celsius, + holdSeconds, + }: AtomicProfileStep): TCProfileStepText => ({ + kind: 'step', + stepText: t('tc_run_profile_steps', { + celsius, + duration: formatDurationLabeled({ seconds: holdSeconds }), + }).trim(), + }) + + const stepTexts = (cycle: AtomicProfileStep[]): TCProfileStepText[] => + cycle.map(stepText) + + const startingCycleText = (cycle: TCProfileCycle): string => + t('tc_starting_extended_profile_cycle', { + repetitions: cycle.repetitions, + }) + + const cycleText = (cycle: TCProfileCycle): TCProfileCycleText => ({ + kind: 'cycle', + cycleText: startingCycleText(cycle), + stepTexts: stepTexts(cycle.steps), + }) + const profileElementTexts = ( + profile: Array + ): Array => + profile.map(element => + Object.hasOwn(element, 'repetitions') + ? cycleText(element as TCProfileCycle) + : stepText(element as AtomicProfileStep) + ) + + return { + kind: 'thermocycler/runExtendedProfile', + commandText: t('tc_starting_extended_profile', { + elementCount: profileElements.length, + }), + profileElementTexts: profileElementTexts(profileElements), + } +} diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getTCRunProfileCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getTCRunProfileCommandText.ts new file mode 100644 index 00000000000..a98ce9cfa4a --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getTCRunProfileCommandText.ts @@ -0,0 +1,29 @@ +import { formatDurationLabeled } from '/app/transformations/commands' +import type { TCRunProfileRunTimeCommand } from '@opentrons/shared-data/command' +import type { GetTCRunProfileCommandTextResult } from '../..' +import type { HandlesCommands } from '../types' + +export function getTCRunProfileCommandText({ + command, + t, +}: HandlesCommands): GetTCRunProfileCommandTextResult { + const { profile } = command.params + + const stepTexts = profile.map( + ({ holdSeconds, celsius }: { holdSeconds: number; celsius: number }) => + t('tc_run_profile_steps', { + celsius, + duration: formatDurationLabeled({ seconds: holdSeconds }), + }).trim() + ) + + const startingProfileText = t('tc_starting_profile', { + stepCount: Object.keys(stepTexts).length, + }) + + return { + kind: 'thermocycler/runProfile', + commandText: startingProfileText, + stepTexts, + } +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getTemperatureCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getTemperatureCommandText.ts similarity index 97% rename from app/src/molecules/Command/hooks/useCommandTextString/utils/getTemperatureCommandText.ts rename to app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getTemperatureCommandText.ts index ee60a76c289..1b5a03745c3 100644 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getTemperatureCommandText.ts +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getTemperatureCommandText.ts @@ -6,7 +6,7 @@ import type { HeaterShakerSetTargetTemperatureCreateCommand, RunTimeCommand, } from '@opentrons/shared-data' -import type { HandlesCommands } from './types' +import type { HandlesCommands } from '../types' export type TemperatureCreateCommand = | TemperatureModuleSetTargetTemperatureCreateCommand diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getUnknownCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getUnknownCommandText.ts new file mode 100644 index 00000000000..17b69b84c6a --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getUnknownCommandText.ts @@ -0,0 +1,5 @@ +import type { GetCommandText } from '../..' + +export function getUnknownCommandText({ command }: GetCommandText): string { + return JSON.stringify(command) +} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getWaitForDurationCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getWaitForDurationCommandText.ts similarity index 87% rename from app/src/molecules/Command/hooks/useCommandTextString/utils/getWaitForDurationCommandText.ts rename to app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getWaitForDurationCommandText.ts index d3b3136be1f..18ccc55540a 100644 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getWaitForDurationCommandText.ts +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getWaitForDurationCommandText.ts @@ -1,5 +1,5 @@ import type { WaitForDurationRunTimeCommand } from '@opentrons/shared-data/command' -import type { HandlesCommands } from './types' +import type { HandlesCommands } from '../types' export function getWaitForDurationCommandText({ command, diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getWaitForResumeCommandText.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getWaitForResumeCommandText.ts similarity index 87% rename from app/src/molecules/Command/hooks/useCommandTextString/utils/getWaitForResumeCommandText.ts rename to app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getWaitForResumeCommandText.ts index f1c7b7fcef6..a591504b244 100644 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getWaitForResumeCommandText.ts +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/getWaitForResumeCommandText.ts @@ -1,5 +1,5 @@ import type { WaitForResumeRunTimeCommand } from '@opentrons/shared-data/command' -import type { HandlesCommands } from './types' +import type { HandlesCommands } from '../types' export function getWaitForResumeCommandText({ command, diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/index.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/index.ts new file mode 100644 index 00000000000..c2926c880c6 --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/commandText/index.ts @@ -0,0 +1,26 @@ +export * from './getLoadCommandText' +export * from './getTemperatureCommandText' +export * from './getTCRunProfileCommandText' +export * from './getTCRunExtendedProfileCommandText' +export * from './getHSShakeSpeedCommandText' +export * from './getMoveToSlotCommandText' +export * from './getMoveRelativeCommandText' +export * from './getMoveToCoordinatesCommandText' +export * from './getMoveToWellCommandText' +export * from './getMoveLabwareCommandText' +export * from './getConfigureForVolumeCommandText' +export * from './getConfigureNozzleLayoutCommandText' +export * from './getPrepareToAspirateCommandText' +export * from './getMoveToAddressableAreaCommandText' +export * from './getMoveToAddressableAreaForDropTipCommandText' +export * from './getDirectTranslationCommandText' +export * from './getWaitForDurationCommandText' +export * from './getWaitForResumeCommandText' +export * from './getDelayCommandText' +export * from './getCommentCommandText' +export * from './getCustomCommandText' +export * from './getUnknownCommandText' +export * from './getPipettingCommandText' +export * from './getLiquidProbeCommandText' +export * from './getRailLightsCommandText' +export * from './getAbsorbanceReaderCommandText' diff --git a/app/src/molecules/Command/utils/getAddressableAreaDisplayName.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/getAddressableAreaDisplayName.ts similarity index 83% rename from app/src/molecules/Command/utils/getAddressableAreaDisplayName.ts rename to app/src/local-resources/commands/hooks/useCommandTextString/utils/getAddressableAreaDisplayName.ts index 6bfdd2fc850..c3160de6223 100644 --- a/app/src/molecules/Command/utils/getAddressableAreaDisplayName.ts +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/getAddressableAreaDisplayName.ts @@ -1,16 +1,16 @@ import type { AddressableAreaName, MoveToAddressableAreaParams, + RunTimeCommand, } from '@opentrons/shared-data' import type { TFunction } from 'i18next' -import type { CommandTextData } from '../types' export function getAddressableAreaDisplayName( - commandTextData: CommandTextData, + commands: RunTimeCommand[] | undefined, commandId: string, t: TFunction ): string { - const addressableAreaCommand = (commandTextData?.commands ?? []).find( + const addressableAreaCommand = (commands ?? []).find( command => command.id === commandId ) @@ -29,8 +29,11 @@ export function getAddressableAreaDisplayName( return t('trash_bin_in_slot', { slot_name: slotName }) } else if (addressableAreaName.includes('WasteChute')) { return t('waste_chute') - } else if (addressableAreaName === 'fixedTrash') return t('fixed_trash') - else return addressableAreaName + } else if (addressableAreaName === 'fixedTrash') { + return t('fixed_trash') + } else { + return addressableAreaName + } } const getMovableTrashSlot = ( diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/getFinalAddressableAreaCmd.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/getFinalAddressableAreaCmd.ts new file mode 100644 index 00000000000..3471073a8b9 --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/getFinalAddressableAreaCmd.ts @@ -0,0 +1,21 @@ +import { findLastAt } from '/app/local-resources/commands/hooks/useCommandTextString/utils/helpers' + +import type { RunTimeCommand } from '@opentrons/shared-data' +/** + * given a list of commands and a labwareId, calculate the resulting location + * of the corresponding labware after all given commands are executed + * @param commands list of commands to search within + * @returns The last command related to addressable areas. + */ +export function getFinalMoveToAddressableAreaCmd( + commands: RunTimeCommand[] +): RunTimeCommand | null { + const [cmd] = findLastAt( + commands, + (c: RunTimeCommand) => + c.commandType === 'moveToAddressableArea' || + c.commandType === 'moveToAddressableAreaForDropTip' + ) + + return cmd ?? null +} diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/getFinalLabwareLocation.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/getFinalLabwareLocation.ts new file mode 100644 index 00000000000..7e73770cbbd --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/getFinalLabwareLocation.ts @@ -0,0 +1,39 @@ +import { findLastAt } from './helpers' + +import type { + LabwareLocation, + RunTimeCommand, + LoadLabwareRunTimeCommand, + MoveLabwareRunTimeCommand, +} from '@opentrons/shared-data' + +/** + * given a list of commands and a labwareId, calculate the resulting location + * of the corresponding labware after all given commands are executed + * @param labwareId target labware + * @param commands list of commands to search within + * @returns LabwareLocation object of the resulting location of the target labware after all commands execute + */ +export function getFinalLabwareLocation( + labwareId: string, + commands: RunTimeCommand[] +): LabwareLocation | null { + const [lastMove, lastMoveIndex] = findLastAt( + commands, + (c: RunTimeCommand): c is MoveLabwareRunTimeCommand => + c.commandType === 'moveLabware' && c.params.labwareId === labwareId + ) + + const [lastLoad, lastLoadIndex] = findLastAt( + commands, + (c: RunTimeCommand): c is LoadLabwareRunTimeCommand => + c.commandType === 'loadLabware' && c.result?.labwareId === labwareId + ) + if (lastMoveIndex > lastLoadIndex) { + return lastMove?.params?.newLocation ?? null + } else if (lastLoadIndex > lastMoveIndex) { + return lastLoad?.params?.location ?? null + } else { + return null + } +} diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/getLiquidDisplayName.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/getLiquidDisplayName.ts new file mode 100644 index 00000000000..2fcec940a55 --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/getLiquidDisplayName.ts @@ -0,0 +1,10 @@ +import type { Liquid } from '@opentrons/shared-data' + +export function getLiquidDisplayName( + liquids: Liquid[], + liquidId: string +): string { + const liquidDisplayName = liquids.find(liquid => liquid.id === liquidId) + ?.displayName + return liquidDisplayName ?? '' +} diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/getPipetteNameOnMount.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/getPipetteNameOnMount.ts new file mode 100644 index 00000000000..e4ae2519374 --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/getPipetteNameOnMount.ts @@ -0,0 +1,12 @@ +import { getLoadedPipette } from '/app/local-resources/instruments' + +import type { PipetteName } from '@opentrons/shared-data' +import type { LoadedPipettes } from '/app/local-resources/instruments/types' + +export function getPipetteNameOnMount( + loadedPipettes: LoadedPipettes, + mount: string +): PipetteName | null { + const loadedPipette = getLoadedPipette(loadedPipettes, mount) + return loadedPipette != null ? loadedPipette.pipetteName : null +} diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/getWellRange.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/getWellRange.ts new file mode 100644 index 00000000000..791ddf2f4c3 --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/getWellRange.ts @@ -0,0 +1,64 @@ +import { getPipetteNameSpecs } from '@opentrons/shared-data' +import type { + PipetteName, + RunTimeCommand, + ConfigureNozzleLayoutRunTimeCommand, +} from '@opentrons/shared-data' + +const usedChannelsFromCommand = ( + command: ConfigureNozzleLayoutRunTimeCommand | undefined, + defaultChannels: number +): number => + command?.params?.configurationParams?.style === 'SINGLE' + ? 1 + : command?.params?.configurationParams?.style === 'COLUMN' + ? 8 + : defaultChannels + +const usedChannelsForPipette = ( + pipetteId: string, + commands: RunTimeCommand[], + defaultChannels: number +): number => + usedChannelsFromCommand( + commands.findLast( + (c: RunTimeCommand): c is ConfigureNozzleLayoutRunTimeCommand => + c.commandType === 'configureNozzleLayout' && + c.params?.pipetteId === pipetteId + ), + defaultChannels + ) + +const usedChannels = ( + pipetteId: string, + commands: RunTimeCommand[], + pipetteChannels: number +): number => + pipetteChannels === 96 + ? usedChannelsForPipette(pipetteId, commands, pipetteChannels) + : pipetteChannels + +/** + * @param pipetteName name of pipette being used + * @param commands list of commands to search within + * @param wellName the target well for pickup tip + * @returns WellRange string of wells pipette will pickup tips from + */ +export function getWellRange( + pipetteId: string, + commands: RunTimeCommand[], + wellName: string, + pipetteName?: PipetteName +): string { + const pipetteChannels = pipetteName + ? getPipetteNameSpecs(pipetteName)?.channels ?? 1 + : 1 + const channelCount = usedChannels(pipetteId, commands, pipetteChannels) + if (channelCount === 96) { + return 'A1 - H12' + } else if (channelCount === 8) { + const column = wellName.substr(1) + return `A${column} - H${column}` + } + return wellName +} diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/helpers.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/helpers.ts new file mode 100644 index 00000000000..5d28c41b82c --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/helpers.ts @@ -0,0 +1,15 @@ +export const findLastAt = ( + arr: readonly T[], + pred: ((el: T) => boolean) | ((el: T) => el is U) +): [U, number] | [undefined, -1] => { + let arrayLoc = -1 + const lastEl = arr.findLast((el: T, idx: number): el is U => { + arrayLoc = idx + return pred(el) + }) + if (lastEl === undefined) { + return [undefined, -1] + } else { + return [lastEl, arrayLoc] + } +} diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/index.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/index.ts new file mode 100644 index 00000000000..44f99055f08 --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/index.ts @@ -0,0 +1,2 @@ +export * from './commandText' +export * from './types' diff --git a/app/src/local-resources/commands/hooks/useCommandTextString/utils/types.ts b/app/src/local-resources/commands/hooks/useCommandTextString/utils/types.ts new file mode 100644 index 00000000000..c10e5e1f866 --- /dev/null +++ b/app/src/local-resources/commands/hooks/useCommandTextString/utils/types.ts @@ -0,0 +1,8 @@ +import type { RunTimeCommand } from '@opentrons/shared-data' +import type { GetCommandText, UseCommandTextStringParams } from '..' + +export type HandlesCommands = Omit< + GetCommandText, + 'command' +> & + UseCommandTextStringParams & { command: T } diff --git a/app/src/local-resources/commands/index.ts b/app/src/local-resources/commands/index.ts new file mode 100644 index 00000000000..02b71828cfa --- /dev/null +++ b/app/src/local-resources/commands/index.ts @@ -0,0 +1,4 @@ +export * from './hooks' +export * from './utils' + +export * from './types' diff --git a/app/src/molecules/Command/types.ts b/app/src/local-resources/commands/types.ts similarity index 100% rename from app/src/molecules/Command/types.ts rename to app/src/local-resources/commands/types.ts diff --git a/app/src/molecules/Command/utils/getCommandTextData.ts b/app/src/local-resources/commands/utils/getCommandTextData.ts similarity index 88% rename from app/src/molecules/Command/utils/getCommandTextData.ts rename to app/src/local-resources/commands/utils/getCommandTextData.ts index cfa8c6961ee..2750cd0d074 100644 --- a/app/src/molecules/Command/utils/getCommandTextData.ts +++ b/app/src/local-resources/commands/utils/getCommandTextData.ts @@ -4,7 +4,7 @@ import type { ProtocolAnalysisOutput, RunTimeCommand, } from '@opentrons/shared-data' -import type { CommandTextData } from '../types' +import type { CommandTextData } from '/app/local-resources/commands/types' export function getCommandTextData( protocolData: diff --git a/app/src/local-resources/commands/utils/index.ts b/app/src/local-resources/commands/utils/index.ts new file mode 100644 index 00000000000..cc4e9c2579a --- /dev/null +++ b/app/src/local-resources/commands/utils/index.ts @@ -0,0 +1,2 @@ +export * from './getCommandTextData' +export * from './lastRunCommandPromptedErrorRecovery' diff --git a/app/src/local-resources/commands/utils/lastRunCommandPromptedErrorRecovery.ts b/app/src/local-resources/commands/utils/lastRunCommandPromptedErrorRecovery.ts new file mode 100644 index 00000000000..dd07756ef43 --- /dev/null +++ b/app/src/local-resources/commands/utils/lastRunCommandPromptedErrorRecovery.ts @@ -0,0 +1,13 @@ +import type { RunCommandSummary } from '@opentrons/api-client' + +// Whether the last run protocol command prompted Error Recovery. +export function lastRunCommandPromptedErrorRecovery( + summary: RunCommandSummary[] +): boolean { + const lastProtocolCommand = summary.findLast( + command => command.intent !== 'fixit' && command.error != null + ) + + // All recoverable protocol commands have defined errors. + return lastProtocolCommand?.error?.isDefined ?? false +} diff --git a/app/src/local-resources/config/constants.ts b/app/src/local-resources/config/constants.ts new file mode 100644 index 00000000000..1c7b0e2727d --- /dev/null +++ b/app/src/local-resources/config/constants.ts @@ -0,0 +1 @@ +export const SLEEP_NEVER_MS = 604800000 diff --git a/app/src/local-resources/config/index.ts b/app/src/local-resources/config/index.ts new file mode 100644 index 00000000000..f87cf0102a1 --- /dev/null +++ b/app/src/local-resources/config/index.ts @@ -0,0 +1 @@ +export * from './constants' diff --git a/app/src/organisms/Devices/ProtocolRun/utils/getStandardDeckViewLayerBlockList.ts b/app/src/local-resources/deck_configuration/getStandardDeckViewLayerBlockList.ts similarity index 100% rename from app/src/organisms/Devices/ProtocolRun/utils/getStandardDeckViewLayerBlockList.ts rename to app/src/local-resources/deck_configuration/getStandardDeckViewLayerBlockList.ts diff --git a/app/src/local-resources/deck_configuration/index.ts b/app/src/local-resources/deck_configuration/index.ts new file mode 100644 index 00000000000..9028755b545 --- /dev/null +++ b/app/src/local-resources/deck_configuration/index.ts @@ -0,0 +1 @@ +export * from './getStandardDeckViewLayerBlockList' diff --git a/app/src/local-resources/dom-utils/hooks/__tests__/useScrollPosition.test.ts b/app/src/local-resources/dom-utils/hooks/__tests__/useScrollPosition.test.ts new file mode 100644 index 00000000000..f5a47b2518c --- /dev/null +++ b/app/src/local-resources/dom-utils/hooks/__tests__/useScrollPosition.test.ts @@ -0,0 +1,76 @@ +import { renderHook, act } from '@testing-library/react' +import { describe, it, expect, vi, beforeEach } from 'vitest' + +import { useScrollPosition } from '../useScrollPosition' + +describe('useScrollPosition', () => { + const mockObserve = vi.fn() + const mockDisconnect = vi.fn() + let intersectionCallback: (entries: IntersectionObserverEntry[]) => void + + beforeEach(() => { + vi.stubGlobal( + 'IntersectionObserver', + vi.fn(callback => { + intersectionCallback = callback + return { + observe: mockObserve, + disconnect: mockDisconnect, + unobserve: vi.fn(), + } + }) + ) + }) + + it('should return initial state and ref', () => { + const { result } = renderHook(() => useScrollPosition()) + + expect(result.current.isScrolled).toBe(false) + expect(result.current.scrollRef).toBeDefined() + expect(result.current.scrollRef.current).toBe(null) + }) + + it('should observe when ref is set', async () => { + const { result } = renderHook(() => useScrollPosition()) + + const div = document.createElement('div') + + await act(async () => { + // @ts-expect-error we're forcibly setting readonly ref + result.current.scrollRef.current = div + + const observer = new IntersectionObserver(intersectionCallback) + observer.observe(div) + }) + + expect(mockObserve).toHaveBeenCalledWith(div) + }) + + it('should update isScrolled when intersection changes for both scrolled and unscrolled cases', () => { + const { result } = renderHook(() => useScrollPosition()) + + act(() => { + intersectionCallback([ + { isIntersecting: false } as IntersectionObserverEntry, + ]) + }) + + expect(result.current.isScrolled).toBe(true) + + act(() => { + intersectionCallback([ + { isIntersecting: true } as IntersectionObserverEntry, + ]) + }) + + expect(result.current.isScrolled).toBe(false) + }) + + it('should disconnect observer on unmount', () => { + const { unmount } = renderHook(() => useScrollPosition()) + + unmount() + + expect(mockDisconnect).toHaveBeenCalled() + }) +}) diff --git a/app/src/local-resources/dom-utils/hooks/index.ts b/app/src/local-resources/dom-utils/hooks/index.ts new file mode 100644 index 00000000000..2098c90e0c3 --- /dev/null +++ b/app/src/local-resources/dom-utils/hooks/index.ts @@ -0,0 +1 @@ +export * from './useScrollPosition' diff --git a/app/src/local-resources/dom-utils/hooks/useScrollPosition.ts b/app/src/local-resources/dom-utils/hooks/useScrollPosition.ts new file mode 100644 index 00000000000..8b3aa945947 --- /dev/null +++ b/app/src/local-resources/dom-utils/hooks/useScrollPosition.ts @@ -0,0 +1,27 @@ +import { useRef, useState, useEffect } from 'react' + +import type { RefObject } from 'react' + +export function useScrollPosition(): { + scrollRef: RefObject + isScrolled: boolean +} { + const scrollRef = useRef(null) + const [isScrolled, setIsScrolled] = useState(false) + + useEffect(() => { + const observer = new IntersectionObserver(([entry]) => { + setIsScrolled(!entry.isIntersecting) + }) + + if (scrollRef.current != null) { + observer.observe(scrollRef.current) + } + + return () => { + observer.disconnect() + } + }, []) + + return { scrollRef, isScrolled } +} diff --git a/app/src/local-resources/dom-utils/index.ts b/app/src/local-resources/dom-utils/index.ts new file mode 100644 index 00000000000..fc78d35129c --- /dev/null +++ b/app/src/local-resources/dom-utils/index.ts @@ -0,0 +1 @@ +export * from './hooks' diff --git a/app/src/local-resources/instruments/__tests__/hooks.test.ts b/app/src/local-resources/instruments/__tests__/hooks.test.ts new file mode 100644 index 00000000000..94b6043a125 --- /dev/null +++ b/app/src/local-resources/instruments/__tests__/hooks.test.ts @@ -0,0 +1,178 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { useIsOEMMode } from '/app/resources/robot-settings/hooks' + +import { + useGripperDisplayName, + usePipetteModelSpecs, + usePipetteNameSpecs, + usePipetteSpecsV2, +} from '../hooks' + +import type { PipetteV2Specs } from '@opentrons/shared-data' + +vi.mock('/app/resources/robot-settings/hooks') + +const BRANDED_P1000_FLEX_DISPLAY_NAME = 'Flex 1-Channel 1000 μL' +const ANONYMOUS_P1000_FLEX_DISPLAY_NAME = '1-Channel 1000 μL' + +const mockP1000V2Specs = { + $otSharedSchema: '#/pipette/schemas/2/pipetteGeometrySchema.json', + availableSensors: { + sensors: ['pressure', 'capacitive', 'environment'], + capacitive: { count: 1 }, + environment: { count: 1 }, + pressure: { count: 1 }, + }, + backCompatNames: [], + backlashDistance: 0.1, + channels: 1, + displayCategory: 'FLEX', + displayName: BRANDED_P1000_FLEX_DISPLAY_NAME, + dropTipConfigurations: { plungerEject: { current: 1, speed: 15 } }, + liquids: { + default: { + $otSharedSchema: '#/pipette/schemas/2/pipetteLiquidPropertiesSchema.json', + defaultTipracks: [ + 'opentrons/opentrons_flex_96_tiprack_1000ul/1', + 'opentrons/opentrons_flex_96_tiprack_200ul/1', + 'opentrons/opentrons_flex_96_tiprack_50ul/1', + 'opentrons/opentrons_flex_96_filtertiprack_1000ul/1', + 'opentrons/opentrons_flex_96_filtertiprack_200ul/1', + 'opentrons/opentrons_flex_96_filtertiprack_50ul/1', + ], + minVolume: 5, + maxVolume: 1000, + supportedTips: expect.anything(), + }, + }, + model: 'p1000', + nozzleMap: expect.anything(), + pathTo3D: + 'pipette/definitions/2/geometry/single_channel/p1000/placeholder.gltf', + validNozzleMaps: { + maps: { + SingleA1: ['A1'], + }, + }, + pickUpTipConfigurations: { + pressFit: { + presses: 1, + increment: 0, + configurationsByNozzleMap: { + SingleA1: { + default: { + speed: 10, + distance: 13, + current: 0.2, + tipOverlaps: { + v0: { + default: 10.5, + 'opentrons/opentrons_flex_96_tiprack_1000ul/1': 9.65, + 'opentrons/opentrons_flex_96_tiprack_200ul/1': 9.76, + 'opentrons/opentrons_flex_96_tiprack_50ul/1': 10.09, + 'opentrons/opentrons_flex_96_filtertiprack_1000ul/1': 9.65, + 'opentrons/opentrons_flex_96_filtertiprack_200ul/1': 9.76, + 'opentrons/opentrons_flex_96_filtertiprack_50ul/1': 10.09, + }, + }, + }, + }, + }, + }, + }, + partialTipConfigurations: { + availableConfigurations: null, + partialTipSupported: false, + }, + plungerHomingConfigurations: { current: 1, speed: 30 }, + plungerMotorConfigurations: { idle: 0.3, run: 1 }, + plungerPositionsConfigurations: { + default: { blowout: 76.5, bottom: 71.5, drop: 90.5, top: 0 }, + }, + quirks: [], + shaftDiameter: 4.5, + shaftULperMM: 15.904, + nozzleOffset: [-8, -22, -259.15], + orderedColumns: expect.anything(), + orderedRows: expect.anything(), + pipetteBoundingBoxOffsets: { + backLeftCorner: [-8, -22, -259.15], + frontRightCorner: [-8, -22, -259.15], + }, + lldSettings: { + t50: { + minHeight: 1.0, + minVolume: 0, + }, + t200: { + minHeight: 1.0, + minVolume: 0, + }, + t1000: { + minHeight: 1.5, + minVolume: 0, + }, + }, +} as PipetteV2Specs + +describe('pipette data accessor hooks', () => { + beforeEach(() => { + vi.mocked(useIsOEMMode).mockReturnValue(false) + }) + + describe('usePipetteNameSpecs', () => { + it('returns the branded display name for P1000 single flex', () => { + expect(usePipetteNameSpecs('p1000_single_flex')?.displayName).toEqual( + BRANDED_P1000_FLEX_DISPLAY_NAME + ) + }) + + it('returns an anonymized display name in OEM mode', () => { + vi.mocked(useIsOEMMode).mockReturnValue(true) + expect(usePipetteNameSpecs('p1000_single_flex')?.displayName).toEqual( + ANONYMOUS_P1000_FLEX_DISPLAY_NAME + ) + }) + }) + + describe('usePipetteModelSpecs', () => { + it('returns the branded display name for P1000 single flex', () => { + expect(usePipetteModelSpecs('p1000_single_v3.6')?.displayName).toEqual( + BRANDED_P1000_FLEX_DISPLAY_NAME + ) + }) + + it('returns an anonymized display name in OEM mode', () => { + vi.mocked(useIsOEMMode).mockReturnValue(true) + expect(usePipetteModelSpecs('p1000_single_v3.6')?.displayName).toEqual( + ANONYMOUS_P1000_FLEX_DISPLAY_NAME + ) + }) + }) + + describe('usePipetteSpecsV2', () => { + it('returns the correct info for p1000_single_flex which should be the latest model version 3.7', () => { + expect(usePipetteSpecsV2('p1000_single_flex')).toStrictEqual( + mockP1000V2Specs + ) + }) + it('returns an anonymized display name in OEM mode', () => { + vi.mocked(useIsOEMMode).mockReturnValue(true) + expect(usePipetteSpecsV2('p1000_single_flex')).toStrictEqual({ + ...mockP1000V2Specs, + displayName: ANONYMOUS_P1000_FLEX_DISPLAY_NAME, + }) + }) + }) + + describe('useGripperDisplayName', () => { + it('returns the branded gripper display name', () => { + expect(useGripperDisplayName('gripperV1.3')).toEqual('Flex Gripper') + }) + it('returns an anonymized display name in OEM mode', () => { + vi.mocked(useIsOEMMode).mockReturnValue(true) + expect(useGripperDisplayName('gripperV1.3')).toEqual('Gripper') + }) + }) +}) diff --git a/app/src/local-resources/instruments/hooks/index.ts b/app/src/local-resources/instruments/hooks/index.ts new file mode 100644 index 00000000000..6cfd0af2293 --- /dev/null +++ b/app/src/local-resources/instruments/hooks/index.ts @@ -0,0 +1,5 @@ +export * from './useGripperDisplayName' +export * from './useHomePipettes' +export * from './usePipetteModelSpecs' +export * from './usePipetteNameSpecs' +export * from './usePipetteSpecsv2' diff --git a/app/src/local-resources/instruments/hooks/useGripperDisplayName.ts b/app/src/local-resources/instruments/hooks/useGripperDisplayName.ts new file mode 100644 index 00000000000..fd1b8262a79 --- /dev/null +++ b/app/src/local-resources/instruments/hooks/useGripperDisplayName.ts @@ -0,0 +1,19 @@ +import { getGripperDisplayName, GRIPPER_MODELS } from '@opentrons/shared-data' +import { useIsOEMMode } from '/app/resources/robot-settings' + +import type { GripperModel } from '@opentrons/shared-data' + +export function useGripperDisplayName(gripperModel: GripperModel): string { + const isOEMMode = useIsOEMMode() + + let brandedDisplayName = '' + + // check to only call display name helper for a gripper model + if (GRIPPER_MODELS.includes(gripperModel)) { + brandedDisplayName = getGripperDisplayName(gripperModel) + } + + const anonymizedDisplayName = brandedDisplayName.replace('Flex ', '') + + return isOEMMode ? anonymizedDisplayName : brandedDisplayName +} diff --git a/app/src/local-resources/instruments/hooks/useHomePipettes.ts b/app/src/local-resources/instruments/hooks/useHomePipettes.ts new file mode 100644 index 00000000000..da139c14651 --- /dev/null +++ b/app/src/local-resources/instruments/hooks/useHomePipettes.ts @@ -0,0 +1,39 @@ +import { useRobotControlCommands } from '/app/resources/maintenance_runs' + +import type { CreateCommand } from '@opentrons/shared-data' + +import type { + UseRobotControlCommandsProps, + UseRobotControlCommandsResult, +} from '/app/resources/maintenance_runs' + +export interface UseHomePipettesResult { + isHoming: UseRobotControlCommandsResult['isExecuting'] + homePipettes: UseRobotControlCommandsResult['executeCommands'] +} + +export type UseHomePipettesProps = Pick< + UseRobotControlCommandsProps, + 'pipetteInfo' | 'onSettled' +> + +// Home pipettes except for plungers. +export function useHomePipettes( + props: UseHomePipettesProps +): UseHomePipettesResult { + const { executeCommands, isExecuting } = useRobotControlCommands({ + ...props, + commands: [HOME_EXCEPT_PLUNGERS], + continuePastCommandFailure: true, + }) + + return { + isHoming: isExecuting, + homePipettes: executeCommands, + } +} + +const HOME_EXCEPT_PLUNGERS: CreateCommand = { + commandType: 'home' as const, + params: { axes: ['leftZ', 'rightZ', 'x', 'y'] }, +} diff --git a/app/src/local-resources/instruments/hooks/usePipetteModelSpecs.ts b/app/src/local-resources/instruments/hooks/usePipetteModelSpecs.ts new file mode 100644 index 00000000000..afbc2f205fa --- /dev/null +++ b/app/src/local-resources/instruments/hooks/usePipetteModelSpecs.ts @@ -0,0 +1,24 @@ +import { getPipetteModelSpecs } from '@opentrons/shared-data' + +import { usePipetteNameSpecs } from './usePipetteNameSpecs' + +import type { + PipetteModel, + PipetteModelSpecs, + PipetteName, +} from '@opentrons/shared-data' + +export function usePipetteModelSpecs( + model: PipetteModel +): PipetteModelSpecs | null { + const modelSpecificFields = getPipetteModelSpecs(model) + const pipetteNameSpecs = usePipetteNameSpecs( + modelSpecificFields?.name as PipetteName + ) + + if (modelSpecificFields == null || pipetteNameSpecs == null) { + return null + } + + return { ...modelSpecificFields, displayName: pipetteNameSpecs.displayName } +} diff --git a/app/src/local-resources/instruments/hooks/usePipetteNameSpecs.ts b/app/src/local-resources/instruments/hooks/usePipetteNameSpecs.ts new file mode 100644 index 00000000000..85a29b2fef7 --- /dev/null +++ b/app/src/local-resources/instruments/hooks/usePipetteNameSpecs.ts @@ -0,0 +1,26 @@ +import { getPipetteNameSpecs } from '@opentrons/shared-data' + +import { useIsOEMMode } from '/app/resources/robot-settings' + +import type { PipetteName, PipetteNameSpecs } from '@opentrons/shared-data' + +export function usePipetteNameSpecs( + name: PipetteName +): PipetteNameSpecs | null { + const isOEMMode = useIsOEMMode() + const pipetteNameSpecs = getPipetteNameSpecs(name) + + if (pipetteNameSpecs == null) { + return null + } + + const brandedDisplayName = pipetteNameSpecs.displayName + const anonymizedDisplayName = pipetteNameSpecs.displayName.replace( + 'Flex ', + '' + ) + + const displayName = isOEMMode ? anonymizedDisplayName : brandedDisplayName + + return { ...pipetteNameSpecs, displayName } +} diff --git a/app/src/local-resources/instruments/hooks/usePipetteSpecsv2.ts b/app/src/local-resources/instruments/hooks/usePipetteSpecsv2.ts new file mode 100644 index 00000000000..951c1d857f1 --- /dev/null +++ b/app/src/local-resources/instruments/hooks/usePipetteSpecsv2.ts @@ -0,0 +1,27 @@ +import { getPipetteSpecsV2 } from '@opentrons/shared-data' + +import { useIsOEMMode } from '/app/resources/robot-settings' + +import type { + PipetteModel, + PipetteName, + PipetteV2Specs, +} from '@opentrons/shared-data' + +export function usePipetteSpecsV2( + name?: PipetteName | PipetteModel +): PipetteV2Specs | null { + const isOEMMode = useIsOEMMode() + const pipetteSpecs = getPipetteSpecsV2(name) + + if (pipetteSpecs == null) { + return null + } + + const brandedDisplayName = pipetteSpecs.displayName + const anonymizedDisplayName = pipetteSpecs.displayName.replace('Flex ', '') + + const displayName = isOEMMode ? anonymizedDisplayName : brandedDisplayName + + return { ...pipetteSpecs, displayName } +} diff --git a/app/src/local-resources/instruments/index.ts b/app/src/local-resources/instruments/index.ts new file mode 100644 index 00000000000..f3723b374bf --- /dev/null +++ b/app/src/local-resources/instruments/index.ts @@ -0,0 +1,2 @@ +export * from './hooks' +export * from './utils' diff --git a/app/src/local-resources/instruments/types.ts b/app/src/local-resources/instruments/types.ts new file mode 100644 index 00000000000..3c4a313f9e5 --- /dev/null +++ b/app/src/local-resources/instruments/types.ts @@ -0,0 +1,3 @@ +import type { LoadedPipette } from '@opentrons/shared-data' + +export type LoadedPipettes = LoadedPipette[] | Record diff --git a/app/src/local-resources/instruments/utils.ts b/app/src/local-resources/instruments/utils.ts new file mode 100644 index 00000000000..c93ad39b078 --- /dev/null +++ b/app/src/local-resources/instruments/utils.ts @@ -0,0 +1,31 @@ +import type { LoadedPipette } from '@opentrons/shared-data' +import type { LoadedPipettes } from '/app/local-resources/instruments/types' + +export interface IsPartialTipConfigParams { + channel: 1 | 8 | 96 + activeNozzleCount: number +} + +export function isPartialTipConfig({ + channel, + activeNozzleCount, +}: IsPartialTipConfigParams): boolean { + switch (channel) { + case 1: + return false + case 8: + return activeNozzleCount !== 8 + case 96: + return activeNozzleCount !== 96 + } +} + +export function getLoadedPipette( + loadedPipettes: LoadedPipettes, + mount: string +): LoadedPipette | undefined { + // NOTE: old analysis contains a object dictionary of pipette entities by id, this case is supported for backwards compatibility purposes + return Array.isArray(loadedPipettes) + ? loadedPipettes.find(l => l.mount === mount) + : loadedPipettes[mount] +} diff --git a/app/src/local-resources/labware/hooks/index.ts b/app/src/local-resources/labware/hooks/index.ts new file mode 100644 index 00000000000..86c1116e270 --- /dev/null +++ b/app/src/local-resources/labware/hooks/index.ts @@ -0,0 +1 @@ +export * from './useAllLabware' diff --git a/app/src/local-resources/labware/hooks/useAllLabware.ts b/app/src/local-resources/labware/hooks/useAllLabware.ts new file mode 100644 index 00000000000..28d4325b2eb --- /dev/null +++ b/app/src/local-resources/labware/hooks/useAllLabware.ts @@ -0,0 +1,50 @@ +import { useSelector } from 'react-redux' +import { getValidCustomLabware } from '/app/redux/custom-labware' +import { getAllDefinitions } from '../utils' +import type { LabwareSort, LabwareFilter, LabwareDefAndDate } from '../types' + +export function useAllLabware( + sortBy: LabwareSort, + filterBy: LabwareFilter +): LabwareDefAndDate[] { + const fullLabwareList: LabwareDefAndDate[] = [] + const labwareDefinitions = getAllDefinitions() + labwareDefinitions.forEach(def => fullLabwareList.push({ definition: def })) + const customLabwareList = useSelector(getValidCustomLabware) + customLabwareList.forEach(customLabware => + 'definition' in customLabware + ? fullLabwareList.push({ + modified: customLabware.modified, + definition: customLabware.definition, + filename: customLabware.filename, + }) + : null + ) + const sortLabware = (a: LabwareDefAndDate, b: LabwareDefAndDate): number => { + if ( + a.definition.metadata.displayName.toUpperCase() < + b.definition.metadata.displayName.toUpperCase() + ) { + return sortBy === 'alphabetical' ? -1 : 1 + } + if ( + a.definition.metadata.displayName.toUpperCase() > + b.definition.metadata.displayName.toUpperCase() + ) { + return sortBy === 'alphabetical' ? 1 : -1 + } + return 0 + } + + if (filterBy === 'customLabware') { + return (customLabwareList as LabwareDefAndDate[]).sort(sortLabware) + } + fullLabwareList.sort(sortLabware) + if (filterBy !== 'all') { + return fullLabwareList.filter( + labwareItem => + labwareItem.definition.metadata.displayCategory === filterBy + ) + } + return fullLabwareList +} diff --git a/app/src/local-resources/labware/index.ts b/app/src/local-resources/labware/index.ts new file mode 100644 index 00000000000..505591b2909 --- /dev/null +++ b/app/src/local-resources/labware/index.ts @@ -0,0 +1,3 @@ +export * from './hooks' +export * from './utils' +export type * from './types' diff --git a/app/src/local-resources/labware/types.ts b/app/src/local-resources/labware/types.ts new file mode 100644 index 00000000000..3ab026b9603 --- /dev/null +++ b/app/src/local-resources/labware/types.ts @@ -0,0 +1,41 @@ +import type { + LabwareDefinition2 as LabwareDefinition, + LabwareWellShapeProperties, + LabwareWellGroupMetadata, + LabwareBrand, + LoadedLabware, +} from '@opentrons/shared-data' + +export interface LabwareDefAndDate { + definition: LabwareDefinition + modified?: number + filename?: string +} + +export type LabwareFilter = + | 'all' + | 'wellPlate' + | 'tipRack' + | 'tubeRack' + | 'reservoir' + | 'aluminumBlock' + | 'customLabware' + | 'adapter' + | 'lid' + +export type LabwareSort = 'alphabetical' | 'reverse' + +export interface LabwareWellGroupProperties { + xOffsetFromLeft: number + yOffsetFromTop: number + xSpacing: number | null + ySpacing: number | null + wellCount: number + shape: LabwareWellShapeProperties | null + depth: number | null + totalLiquidVolume: number | null + metadata: LabwareWellGroupMetadata + brand: LabwareBrand | null +} + +export type LoadedLabwares = LoadedLabware[] | Record diff --git a/app/src/pages/Labware/helpers/__mocks__/getAllDefs.ts b/app/src/local-resources/labware/utils/__mocks__/getAllDefs.ts similarity index 100% rename from app/src/pages/Labware/helpers/__mocks__/getAllDefs.ts rename to app/src/local-resources/labware/utils/__mocks__/getAllDefs.ts diff --git a/app/src/local-resources/labware/utils/__tests__/getLabwareDisplayLocation.test.tsx b/app/src/local-resources/labware/utils/__tests__/getLabwareDisplayLocation.test.tsx new file mode 100644 index 00000000000..beed2d012c0 --- /dev/null +++ b/app/src/local-resources/labware/utils/__tests__/getLabwareDisplayLocation.test.tsx @@ -0,0 +1,191 @@ +import { describe, it, expect, vi } from 'vitest' +import { screen } from '@testing-library/react' +import { useTranslation } from 'react-i18next' + +import { + FLEX_ROBOT_TYPE, + getModuleDisplayName, + getModuleType, + getOccludedSlotCountForModule, + getLabwareDefURI, + getLabwareDisplayName, +} from '@opentrons/shared-data' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getLabwareDisplayLocation } from '/app/local-resources/labware' +import { + getModuleModel, + getModuleDisplayLocation, +} from '/app/local-resources/modules' + +import type { ComponentProps } from 'react' +import type { LabwareLocation } from '@opentrons/shared-data' + +vi.mock('@opentrons/shared-data', async () => { + const actual = await vi.importActual('@opentrons/shared-data') + return { + ...actual, + getModuleDisplayName: vi.fn(), + getModuleType: vi.fn(), + getOccludedSlotCountForModule: vi.fn(), + getLabwareDefURI: vi.fn(), + getLabwareDisplayName: vi.fn(), + } +}) + +vi.mock('/app/local-resources/modules', () => ({ + getModuleModel: vi.fn(), + getModuleDisplayLocation: vi.fn(), +})) + +const TestWrapper = ({ + location, + params, +}: { + location: LabwareLocation | null + params: any +}) => { + const { t } = useTranslation('protocol_command_text') + const displayLocation = getLabwareDisplayLocation({ ...params, location, t }) + return
{displayLocation}
+} + +const render = (props: ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] +} + +describe('getLabwareDisplayLocation with translations', () => { + const defaultParams = { + loadedLabwares: [], + loadedModules: [], + robotType: FLEX_ROBOT_TYPE, + allRunDefs: [], + } + + it('should return an empty string for null location', () => { + render({ location: null, params: defaultParams }) + expect(screen.queryByText(/.+/)).toBeNull() + }) + + it('should return "off deck" for offDeck location', () => { + render({ location: 'offDeck', params: defaultParams }) + + screen.getByText('off deck') + }) + + it('should return a slot name for slot location', () => { + render({ location: { slotName: 'A1' }, params: defaultParams }) + + screen.getByText('Slot A1') + }) + + it('should return an addressable area name for an addressable area location', () => { + render({ location: { addressableAreaName: 'B2' }, params: defaultParams }) + + screen.getByText('Slot B2') + }) + + it('should return a module location for a module location', () => { + const mockModuleModel = 'temperatureModuleV2' + vi.mocked(getModuleModel).mockReturnValue(mockModuleModel) + vi.mocked(getModuleDisplayLocation).mockReturnValue('3') + vi.mocked(getModuleDisplayName).mockReturnValue('Temperature Module') + vi.mocked(getModuleType).mockReturnValue('temperatureModuleType') + vi.mocked(getOccludedSlotCountForModule).mockReturnValue(1) + + render({ location: { moduleId: 'temp123' }, params: defaultParams }) + + screen.getByText('Temperature Module in Slot 3') + }) + + it('should return an adapter location for an adapter location', () => { + const mockLoadedLabwares = [ + { + id: 'adapter123', + definitionUri: 'adapter-uri', + location: { slotName: 'D1' }, + }, + ] + const mockAllRunDefs = [ + { uri: 'adapter-uri', metadata: { displayName: 'Mock Adapter' } }, + ] + vi.mocked(getLabwareDefURI).mockReturnValue('adapter-uri') + vi.mocked(getLabwareDisplayName).mockReturnValue('Mock Adapter') + + render({ + location: { labwareId: 'adapter123' }, + params: { + ...defaultParams, + loadedLabwares: mockLoadedLabwares, + allRunDefs: mockAllRunDefs, + detailLevel: 'full', + }, + }) + + screen.getByText('Mock Adapter in Slot D1') + }) + + it('should return a slot-only location when detailLevel is "slot-only"', () => { + render({ + location: { slotName: 'C1' }, + params: { ...defaultParams, detailLevel: 'slot-only' }, + }) + + screen.getByText('Slot C1') + }) + + it('should special case the slotName if it contains "waste chute"', () => { + render({ + location: { slotName: 'gripperWasteChute' }, + params: { ...defaultParams, detailLevel: 'slot-only' }, + }) + + screen.getByText('Waste Chute') + }) + + it('should special case the slotName if it contains "trash bin"', () => { + render({ + location: { slotName: 'trashBin' }, + params: { ...defaultParams, detailLevel: 'slot-only' }, + }) + + screen.getByText('Trash Bin') + }) + + it('should handle an adapter on module location when the detail level is full', () => { + const mockLoadedLabwares = [ + { + id: 'adapter123', + definitionUri: 'adapter-uri', + location: { moduleId: 'temp123' }, + }, + ] + const mockLoadedModules = [{ id: 'temp123', model: 'temperatureModuleV2' }] + const mockAllRunDefs = [ + { uri: 'adapter-uri', metadata: { displayName: 'Mock Adapter' } }, + ] + + vi.mocked(getLabwareDefURI).mockReturnValue('adapter-uri') + vi.mocked(getLabwareDisplayName).mockReturnValue('Mock Adapter') + vi.mocked(getModuleDisplayLocation).mockReturnValue('2') + vi.mocked(getModuleDisplayName).mockReturnValue('Temperature Module') + vi.mocked(getModuleType).mockReturnValue('temperatureModuleType') + vi.mocked(getOccludedSlotCountForModule).mockReturnValue(1) + + render({ + location: { labwareId: 'adapter123' }, + params: { + ...defaultParams, + loadedLabwares: mockLoadedLabwares, + loadedModules: mockLoadedModules, + allRunDefs: mockAllRunDefs, + detailLevel: 'full', + }, + }) + + screen.getByText('Mock Adapter on Temperature Module in Slot 2') + }) +}) diff --git a/app/src/local-resources/labware/utils/getAllDefinitions.ts b/app/src/local-resources/labware/utils/getAllDefinitions.ts new file mode 100644 index 00000000000..24a28ef44e1 --- /dev/null +++ b/app/src/local-resources/labware/utils/getAllDefinitions.ts @@ -0,0 +1,37 @@ +import groupBy from 'lodash/groupBy' + +import { LABWAREV2_DO_NOT_LIST } from '@opentrons/shared-data' + +import { getAllDefs } from './getAllDefs' + +import type { LabwareDefinition2 } from '@opentrons/shared-data' + +const getOnlyLatestDefs = ( + labwareList: LabwareDefinition2[] +): LabwareDefinition2[] => { + // group by namespace + loadName + const labwareDefGroups: { + [groupKey: string]: LabwareDefinition2[] + } = groupBy( + labwareList, + d => `${d.namespace}/${d.parameters.loadName}` + ) + return Object.keys(labwareDefGroups).map((groupKey: string) => { + const group = labwareDefGroups[groupKey] + const allVersions = group.map(d => d.version) + const highestVersionNum = Math.max(...allVersions) + const resultIdx = group.findIndex(d => d.version === highestVersionNum) + return group[resultIdx] + }) +} + +export function getAllDefinitions(): LabwareDefinition2[] { + const allDefs = getAllDefs().filter( + (d: LabwareDefinition2) => + // eslint-disable-next-line @typescript-eslint/prefer-includes + LABWAREV2_DO_NOT_LIST.indexOf(d.parameters.loadName) === -1 + ) + const definitions = getOnlyLatestDefs(allDefs) + + return definitions +} diff --git a/app/src/pages/Labware/helpers/getAllDefs.ts b/app/src/local-resources/labware/utils/getAllDefs.ts similarity index 99% rename from app/src/pages/Labware/helpers/getAllDefs.ts rename to app/src/local-resources/labware/utils/getAllDefs.ts index 58ccbae8b74..307cb18b014 100644 --- a/app/src/pages/Labware/helpers/getAllDefs.ts +++ b/app/src/local-resources/labware/utils/getAllDefs.ts @@ -1,4 +1,5 @@ import { getAllDefinitions } from '@opentrons/shared-data' + import type { LabwareDefinition2 } from '@opentrons/shared-data' export function getAllDefs(): LabwareDefinition2[] { diff --git a/app/src/molecules/Command/utils/getLabwareDefinitionsFromCommands.ts b/app/src/local-resources/labware/utils/getLabwareDefinitionsFromCommands.ts similarity index 94% rename from app/src/molecules/Command/utils/getLabwareDefinitionsFromCommands.ts rename to app/src/local-resources/labware/utils/getLabwareDefinitionsFromCommands.ts index 238302e78e5..6016b0c5dd8 100644 --- a/app/src/molecules/Command/utils/getLabwareDefinitionsFromCommands.ts +++ b/app/src/local-resources/labware/utils/getLabwareDefinitionsFromCommands.ts @@ -1,6 +1,8 @@ -import type { LabwareDefinition2, RunTimeCommand } from '@opentrons/shared-data' import { getLabwareDefURI } from '@opentrons/shared-data' +import type { LabwareDefinition2, RunTimeCommand } from '@opentrons/shared-data' + +// Note: This is an O(n) operation. export function getLabwareDefinitionsFromCommands( commands: RunTimeCommand[] ): LabwareDefinition2[] { diff --git a/app/src/local-resources/labware/utils/getLabwareDisplayLocation.ts b/app/src/local-resources/labware/utils/getLabwareDisplayLocation.ts new file mode 100644 index 00000000000..63804cba764 --- /dev/null +++ b/app/src/local-resources/labware/utils/getLabwareDisplayLocation.ts @@ -0,0 +1,114 @@ +import { + getModuleDisplayName, + getModuleType, + getOccludedSlotCountForModule, + THERMOCYCLER_MODULE_V1, + THERMOCYCLER_MODULE_V2, + TRASH_BIN_FIXTURE, + WASTE_CHUTE_ADDRESSABLE_AREAS, +} from '@opentrons/shared-data' +import { getLabwareLocation } from './getLabwareLocation' + +import type { TFunction } from 'i18next' +import type { + LocationSlotOnlyParams, + LocationFullParams, +} from './getLabwareLocation' +import type { AddressableAreaName } from '@opentrons/shared-data' + +export interface DisplayLocationSlotOnlyParams extends LocationSlotOnlyParams { + t: TFunction + isOnDevice?: boolean +} + +export interface DisplayLocationFullParams extends LocationFullParams { + t: TFunction + isOnDevice?: boolean +} + +export type DisplayLocationParams = + | DisplayLocationSlotOnlyParams + | DisplayLocationFullParams + +// detailLevel applies to nested labware. If 'full', return copy that includes the actual peripheral that nests the +// labware, ex, "in module XYZ in slot C1". +// If 'slot-only', return only the slot name, ex "in slot C1". +export function getLabwareDisplayLocation( + params: DisplayLocationParams +): string { + const { t, isOnDevice = false } = params + const locationResult = getLabwareLocation(params) + + if (locationResult == null) { + return '' + } + + const { slotName, moduleModel, adapterName } = locationResult + + if (slotName === 'offDeck') { + return t('off_deck') + } + // Simple slot location + else if (moduleModel == null && adapterName == null) { + const validatedSlotCopy = handleSpecialSlotNames(slotName, t) + return isOnDevice ? validatedSlotCopy.odd : validatedSlotCopy.desktop + } + // Module location without adapter + else if (moduleModel != null && adapterName == null) { + if (params.detailLevel === 'slot-only') { + return moduleModel === THERMOCYCLER_MODULE_V1 || + moduleModel === THERMOCYCLER_MODULE_V2 + ? t('slot', { slot_name: 'A1+B1' }) + : t('slot', { slot_name: slotName }) + } else { + return isOnDevice + ? `${getModuleDisplayName(moduleModel)}, ${slotName}` + : t('module_in_slot', { + count: getOccludedSlotCountForModule( + getModuleType(moduleModel), + params.robotType + ), + module: getModuleDisplayName(moduleModel), + slot_name: slotName, + }) + } + } + // Adapter locations + else if (adapterName != null) { + if (moduleModel == null) { + return t('adapter_in_slot', { + adapter: adapterName, + slot: slotName, + }) + } else { + return t('adapter_in_mod_in_slot', { + count: getOccludedSlotCountForModule( + getModuleType(moduleModel), + params.robotType + ), + module: getModuleDisplayName(moduleModel), + adapter: adapterName, + slot: slotName, + }) + } + } else { + return '' + } +} + +// Sometimes we don't want to show the actual slotName, so we special case the text here. +function handleSpecialSlotNames( + slotName: string, + t: TFunction +): { odd: string; desktop: string } { + if (WASTE_CHUTE_ADDRESSABLE_AREAS.includes(slotName as AddressableAreaName)) { + return { odd: t('waste_chute'), desktop: t('waste_chute') } + } else if (slotName === TRASH_BIN_FIXTURE) { + return { odd: t('trash_bin'), desktop: t('trash_bin') } + } else { + return { + odd: slotName, + desktop: t('slot', { slot_name: slotName }), + } + } +} diff --git a/app/src/local-resources/labware/utils/getLabwareLocation.ts b/app/src/local-resources/labware/utils/getLabwareLocation.ts new file mode 100644 index 00000000000..aec9e30a186 --- /dev/null +++ b/app/src/local-resources/labware/utils/getLabwareLocation.ts @@ -0,0 +1,152 @@ +import { getLabwareDefURI, getLabwareDisplayName } from '@opentrons/shared-data' + +import { + getModuleDisplayLocation, + getModuleModel, +} from '/app/local-resources/modules' + +import type { + LabwareDefinition2, + LabwareLocation, + ModuleModel, + RobotType, +} from '@opentrons/shared-data' +import type { LoadedLabwares } from '/app/local-resources/labware' +import type { LoadedModules } from '/app/local-resources/modules' + +export interface LocationResult { + slotName: string + moduleModel?: ModuleModel + adapterName?: string +} + +interface BaseParams { + location: LabwareLocation | null + loadedModules: LoadedModules + loadedLabwares: LoadedLabwares + robotType: RobotType +} + +export interface LocationSlotOnlyParams extends BaseParams { + detailLevel: 'slot-only' +} + +export interface LocationFullParams extends BaseParams { + allRunDefs: LabwareDefinition2[] + detailLevel?: 'full' +} + +export type GetLabwareLocationParams = + | LocationSlotOnlyParams + | LocationFullParams + +// detailLevel returns additional information about the module and adapter in the same location, if applicable. +// if 'slot-only', returns the underlying slot location. +export function getLabwareLocation( + params: GetLabwareLocationParams +): LocationResult | null { + const { + loadedLabwares, + loadedModules, + location, + detailLevel = 'full', + } = params + + if (location == null) { + return null + } else if (location === 'offDeck') { + return { slotName: 'offDeck' } + } else if ('slotName' in location) { + return { slotName: location.slotName } + } else if ('addressableAreaName' in location) { + return { slotName: location.addressableAreaName } + } else if ('moduleId' in location) { + const moduleModel = getModuleModel(loadedModules, location.moduleId) + if (moduleModel == null) { + console.error('labware is located on an unknown module model') + return null + } + const slotName = getModuleDisplayLocation(loadedModules, location.moduleId) + + return { + slotName, + moduleModel, + } + } else if ('labwareId' in location) { + if (!Array.isArray(loadedLabwares)) { + console.error('Cannot get location from loaded labwares object') + return null + } + + const adapter = loadedLabwares.find(lw => lw.id === location.labwareId) + + if (adapter == null) { + console.error('labware is located on an unknown adapter') + return null + } else if (detailLevel === 'slot-only') { + return getLabwareLocation({ + ...params, + location: adapter.location, + }) + } else if (detailLevel === 'full') { + const { allRunDefs } = params as LocationFullParams + const adapterDef = allRunDefs.find( + def => getLabwareDefURI(def) === adapter?.definitionUri + ) + const adapterName = + adapterDef != null ? getLabwareDisplayName(adapterDef) : '' + + if (adapter.location === 'offDeck') { + return { slotName: 'offDeck', adapterName } + } else if ( + 'slotName' in adapter.location || + 'addressableAreaName' in adapter.location + ) { + const slotName = + 'slotName' in adapter.location + ? adapter.location.slotName + : adapter.location.addressableAreaName + return { slotName, adapterName } + } else if ('moduleId' in adapter.location) { + const moduleIdUnderAdapter = adapter.location.moduleId + + if (!Array.isArray(loadedModules)) { + console.error('Cannot get location from loaded modules object') + return null + } + + const moduleModel = loadedModules.find( + module => module.id === moduleIdUnderAdapter + )?.model + + if (moduleModel == null) { + console.error('labware is located on an adapter on an unknown module') + return null + } + + const slotName = getModuleDisplayLocation( + loadedModules, + adapter.location.moduleId + ) + + return { + slotName, + moduleModel, + adapterName, + } + } else if ('labwareId' in adapter.location) { + return getLabwareLocation({ + ...params, + location: adapter.location, + }) + } else { + return null + } + } else { + console.error('Unhandled detailLevel.') + return null + } + } else { + return null + } +} diff --git a/app/src/local-resources/labware/utils/getLabwareName.ts b/app/src/local-resources/labware/utils/getLabwareName.ts new file mode 100644 index 00000000000..af51fbc5fbc --- /dev/null +++ b/app/src/local-resources/labware/utils/getLabwareName.ts @@ -0,0 +1,38 @@ +import { getLabwareDefURI, getLabwareDisplayName } from '@opentrons/shared-data' + +import { getLoadedLabware } from './getLoadedLabware' + +import type { LabwareDefinition2 } from '@opentrons/shared-data' +import type { LoadedLabwares } from '/app/local-resources/labware' + +const FIXED_TRASH_DEF_URIS = [ + 'opentrons/opentrons_1_trash_850ml_fixed/1', + 'opentrons/opentrons_1_trash_1100ml_fixed/1', + 'opentrons/opentrons_1_trash_3200ml_fixed/1', +] + +export interface GetLabwareNameParams { + allRunDefs: LabwareDefinition2[] + loadedLabwares: LoadedLabwares + labwareId: string +} + +export function getLabwareName({ + allRunDefs, + loadedLabwares, + labwareId, +}: GetLabwareNameParams): string { + const loadedLabware = getLoadedLabware(loadedLabwares, labwareId) + if (loadedLabware == null) { + return '' + } else if (FIXED_TRASH_DEF_URIS.includes(loadedLabware.definitionUri)) { + return 'Fixed Trash' + } else if (loadedLabware.displayName != null) { + return loadedLabware.displayName + } else { + const labwareDef = allRunDefs.find( + def => getLabwareDefURI(def) === loadedLabware.definitionUri + ) + return labwareDef != null ? getLabwareDisplayName(labwareDef) : '' + } +} diff --git a/app/src/local-resources/labware/utils/getLoadedLabware.ts b/app/src/local-resources/labware/utils/getLoadedLabware.ts new file mode 100644 index 00000000000..efd6981837a --- /dev/null +++ b/app/src/local-resources/labware/utils/getLoadedLabware.ts @@ -0,0 +1,12 @@ +import type { LoadedLabware } from '@opentrons/shared-data' +import type { LoadedLabwares } from '/app/local-resources/labware' + +export function getLoadedLabware( + loadedLabware: LoadedLabwares, + labwareId: string +): LoadedLabware | undefined { + // NOTE: old analysis contains a object dictionary of labware entities by id, this case is supported for backwards compatibility purposes + return Array.isArray(loadedLabware) + ? loadedLabware.find(l => l.id === labwareId) + : loadedLabware[labwareId] +} diff --git a/app/src/local-resources/labware/utils/index.ts b/app/src/local-resources/labware/utils/index.ts new file mode 100644 index 00000000000..290e953d50f --- /dev/null +++ b/app/src/local-resources/labware/utils/index.ts @@ -0,0 +1,8 @@ +export * from './getAllDefinitions' +export * from './labwareImages' +export * from './getAllDefs' +export * from './getLabwareDefinitionsFromCommands' +export * from './getLabwareName' +export * from './getLoadedLabware' +export * from './getLabwareDisplayLocation' +export * from './getLabwareLocation' diff --git a/app/src/local-resources/labware/utils/labwareImages.ts b/app/src/local-resources/labware/utils/labwareImages.ts new file mode 100644 index 00000000000..95cf103f1d1 --- /dev/null +++ b/app/src/local-resources/labware/utils/labwareImages.ts @@ -0,0 +1,35 @@ +// images by labware load name + +// TODO: BC 2020-04-01): this mapping should live in shared-data, +// it is now following the existing pattern in labware-library +import opentrons_96_tiprack_1000ul_side_view from '/app/assets/images/labware/opentrons_96_tiprack_1000ul_side_view.jpg' +import opentrons_96_tiprack_10ul_side_view from '/app/assets/images/labware/opentrons_96_tiprack_10ul_side_view.jpg' +import opentrons_96_tiprack_300ul_side_view from '/app/assets/images/labware/opentrons_96_tiprack_300ul_side_view.jpg' +import geb_96_tiprack_1000ul from '/app/assets/images/labware/geb_96_tiprack_1000ul_side_view.jpg' +import geb_96_tiprack_10ul from '/app/assets/images/labware/geb_96_tiprack_10ul_side_view.jpg' +import tipone_96_tiprack_200ul from '/app/assets/images/labware/tipone_96_tiprack_200ul_side_view.jpg' +import eppendorf_96_tiprack_1000ul_eptips from '/app/assets/images/labware/eppendorf_1000ul_tip_eptips_side_view.jpg' +import eppendorf_96_tiprack_10ul_eptips from '/app/assets/images/labware/eppendorf_10ul_tips_eptips_side_view.jpg' +import opentrons_calibrationblock from '/app/assets/images/labware/opentrons_calibration_block.png' +import generic_custom_tiprack from '/app/assets/images/labware/generic_tiprack_side_view.png' +import removable_black_plastic_trash_bin from '/app/assets/images/labware/removable_black_plastic_trash_bin.png' + +export const labwareImages = { + opentrons_96_tiprack_1000ul: opentrons_96_tiprack_1000ul_side_view, + opentrons_96_filtertiprack_1000ul: opentrons_96_tiprack_1000ul_side_view, + opentrons_96_tiprack_10ul: opentrons_96_tiprack_10ul_side_view, + opentrons_96_filtertiprack_10ul: opentrons_96_tiprack_10ul_side_view, + opentrons_96_tiprack_20ul: opentrons_96_tiprack_10ul_side_view, + opentrons_96_filtertiprack_20ul: opentrons_96_tiprack_10ul_side_view, + opentrons_96_tiprack_300ul: opentrons_96_tiprack_300ul_side_view, + opentrons_96_filtertiprack_200ul: opentrons_96_tiprack_300ul_side_view, + geb_96_tiprack_1000ul, + geb_96_tiprack_10ul, + tipone_96_tiprack_200ul, + eppendorf_96_tiprack_1000ul_eptips, + eppendorf_96_tiprack_10ul_eptips, + opentrons_calibrationblock_short_side_right: opentrons_calibrationblock, + opentrons_calibrationblock_short_side_left: opentrons_calibrationblock, + generic_custom_tiprack, + removable_black_plastic_trash_bin, +} diff --git a/app/src/local-resources/modules/index.ts b/app/src/local-resources/modules/index.ts new file mode 100644 index 00000000000..85dcaa20ea5 --- /dev/null +++ b/app/src/local-resources/modules/index.ts @@ -0,0 +1,3 @@ +export * from './utils' + +export * from './types' diff --git a/app/src/local-resources/modules/types.ts b/app/src/local-resources/modules/types.ts new file mode 100644 index 00000000000..8317beac7e8 --- /dev/null +++ b/app/src/local-resources/modules/types.ts @@ -0,0 +1,3 @@ +import type { LoadedModule } from '@opentrons/shared-data' + +export type LoadedModules = LoadedModule[] | Record diff --git a/app/src/local-resources/modules/utils/__tests__/getModuleImage.test.ts b/app/src/local-resources/modules/utils/__tests__/getModuleImage.test.ts new file mode 100644 index 00000000000..4fc7b870253 --- /dev/null +++ b/app/src/local-resources/modules/utils/__tests__/getModuleImage.test.ts @@ -0,0 +1,84 @@ +import { describe, it, expect } from 'vitest' +import { getModuleImage } from '../getModuleImage' + +describe('getModuleImage', () => { + it('should render the magnetic module image when the model is a magnetic module gen 1', () => { + const result = getModuleImage('magneticModuleV1') + expect(result).toEqual( + '/app/src/assets/images/magnetic_module_gen_2_transparent.png' + ) + }) + + it('should render the high res magnetic module image when the model is a magnetic module gen 1 high res', () => { + const result = getModuleImage('magneticModuleV1', true) + expect(result).toEqual( + '/app/src/assets/images/modules/magneticModuleV2@3x.png' + ) + }) + + it('should render the magnetic module image when the model is a magnetic module gen 2', () => { + const result = getModuleImage('magneticModuleV2') + expect(result).toEqual( + '/app/src/assets/images/magnetic_module_gen_2_transparent.png' + ) + }) + + it('should render the temperature module image when the model is a temperature module gen 1', () => { + const result = getModuleImage('temperatureModuleV1') + expect(result).toEqual( + '/app/src/assets/images/temp_deck_gen_2_transparent.png' + ) + }) + + it('should render the temperature module image when the model is a temperature module gen 2', () => { + const result = getModuleImage('temperatureModuleV2') + expect(result).toEqual( + '/app/src/assets/images/temp_deck_gen_2_transparent.png' + ) + }) + + it('should render the high res temperature module image when the model is a temperature module high res', () => { + const result = getModuleImage('temperatureModuleV2', true) + expect(result).toEqual( + '/app/src/assets/images/modules/temperatureModuleV2@3x.png' + ) + }) + + it('should render the heater-shaker module image when the model is a heater-shaker module gen 1', () => { + const result = getModuleImage('heaterShakerModuleV1') + expect(result).toEqual( + '/app/src/assets/images/heater_shaker_module_transparent.png' + ) + }) + + it('should render the high res heater-shaker module image when the model is a heater-shaker module gen 1 high res', () => { + const result = getModuleImage('heaterShakerModuleV1', true) + expect(result).toEqual( + '/app/src/assets/images/modules/heaterShakerModuleV1@3x.png' + ) + }) + + it('should render the thermocycler module image when the model is a thermocycler module gen 1', () => { + const result = getModuleImage('thermocyclerModuleV1') + expect(result).toEqual('/app/src/assets/images/thermocycler_closed.png') + }) + + it('should render the high res thermocycler module image when the model is a thermocycler module gen 1 high res', () => { + const result = getModuleImage('thermocyclerModuleV1', true) + expect(result).toEqual( + '/app/src/assets/images/modules/thermocyclerModuleV1@3x.png' + ) + }) + + it('should render the thermocycler module image when the model is a thermocycler module gen 2', () => { + const result = getModuleImage('thermocyclerModuleV2') + expect(result).toEqual( + '/app/src/assets/images/thermocycler_gen_2_closed.png' + ) + }) + + it('should render the magnetic block v1 image when the module is magneticBlockV1', () => { + const result = getModuleImage('magneticBlockV1') + expect(result).toEqual('/app/src/assets/images/magnetic_block_gen_1.png') + }) +}) diff --git a/app/src/local-resources/modules/utils/getLoadedModule.ts b/app/src/local-resources/modules/utils/getLoadedModule.ts new file mode 100644 index 00000000000..70047e095e6 --- /dev/null +++ b/app/src/local-resources/modules/utils/getLoadedModule.ts @@ -0,0 +1,12 @@ +import type { LoadedModule } from '@opentrons/shared-data' +import type { LoadedModules } from '/app/local-resources/modules/types' + +export function getLoadedModule( + loadedModules: LoadedModules, + moduleId: string +): LoadedModule | undefined { + // NOTE: old analysis contains a object dictionary of module entities by id, this case is supported for backwards compatibility purposes + return Array.isArray(loadedModules) + ? loadedModules.find(l => l.id === moduleId) + : loadedModules[moduleId] +} diff --git a/app/src/local-resources/modules/utils/getModuleDisplayLocation.ts b/app/src/local-resources/modules/utils/getModuleDisplayLocation.ts new file mode 100644 index 00000000000..665e31d8975 --- /dev/null +++ b/app/src/local-resources/modules/utils/getModuleDisplayLocation.ts @@ -0,0 +1,11 @@ +import { getLoadedModule } from './getLoadedModule' + +import type { LoadedModules } from '../types' + +export function getModuleDisplayLocation( + loadedModules: LoadedModules, + moduleId: string +): string { + const loadedModule = getLoadedModule(loadedModules, moduleId) + return loadedModule != null ? loadedModule.location.slotName : '' +} diff --git a/app/src/local-resources/modules/utils/getModuleImage.ts b/app/src/local-resources/modules/utils/getModuleImage.ts new file mode 100644 index 00000000000..b68d5aa8562 --- /dev/null +++ b/app/src/local-resources/modules/utils/getModuleImage.ts @@ -0,0 +1,40 @@ +import magneticModule from '/app/assets/images/magnetic_module_gen_2_transparent.png' +import temperatureModule from '/app/assets/images/temp_deck_gen_2_transparent.png' +import thermoModuleGen1 from '/app/assets/images/thermocycler_closed.png' +import heaterShakerModule from '/app/assets/images/heater_shaker_module_transparent.png' +import magneticModuleHighRes from '/app/assets/images/modules/magneticModuleV2@3x.png' +import temperatureModuleHighRes from '/app/assets/images/modules/temperatureModuleV2@3x.png' +import thermoModuleGen1HighRes from '/app/assets/images/modules/thermocyclerModuleV1@3x.png' +import heaterShakerModuleHighRes from '/app/assets/images/modules/heaterShakerModuleV1@3x.png' +import thermoModuleGen2 from '/app/assets/images/thermocycler_gen_2_closed.png' +import magneticBlockGen1 from '/app/assets/images/magnetic_block_gen_1.png' +import magneticBlockGen1HighRes from '/app/assets/images/magnetic_block_gen_1@3x.png' +import absorbanceReader from '/app/assets/images/opentrons_plate_reader.png' + +import type { ModuleModel } from '@opentrons/shared-data' + +export function getModuleImage( + model: ModuleModel, + highRes: boolean = false +): string { + switch (model) { + case 'magneticModuleV1': + case 'magneticModuleV2': + return highRes ? magneticModuleHighRes : magneticModule + case 'temperatureModuleV1': + case 'temperatureModuleV2': + return highRes ? temperatureModuleHighRes : temperatureModule + case 'heaterShakerModuleV1': + return highRes ? heaterShakerModuleHighRes : heaterShakerModule + case 'thermocyclerModuleV1': + return highRes ? thermoModuleGen1HighRes : thermoModuleGen1 + case 'thermocyclerModuleV2': + return thermoModuleGen2 + case 'magneticBlockV1': + return highRes ? magneticBlockGen1HighRes : magneticBlockGen1 + case 'absorbanceReaderV1': + return absorbanceReader + default: + return 'Error: unknown module model' + } +} diff --git a/app/src/local-resources/modules/utils/getModuleModel.ts b/app/src/local-resources/modules/utils/getModuleModel.ts new file mode 100644 index 00000000000..18302253499 --- /dev/null +++ b/app/src/local-resources/modules/utils/getModuleModel.ts @@ -0,0 +1,12 @@ +import { getLoadedModule } from './getLoadedModule' + +import type { ModuleModel } from '@opentrons/shared-data' +import type { LoadedModules } from '/app/local-resources/modules/types' + +export function getModuleModel( + loadedModules: LoadedModules, + moduleId: string +): ModuleModel | null { + const loadedModule = getLoadedModule(loadedModules, moduleId) + return loadedModule != null ? loadedModule.model : null +} diff --git a/app/src/organisms/Devices/getModulePrepCommands.ts b/app/src/local-resources/modules/utils/getModulePrepCommands.ts similarity index 97% rename from app/src/organisms/Devices/getModulePrepCommands.ts rename to app/src/local-resources/modules/utils/getModulePrepCommands.ts index cb55081533e..251eedc4785 100644 --- a/app/src/organisms/Devices/getModulePrepCommands.ts +++ b/app/src/local-resources/modules/utils/getModulePrepCommands.ts @@ -14,7 +14,7 @@ import type { HeaterShakerCloseLatchCreateCommand, TCCloseLidCreateCommand, } from '@opentrons/shared-data' -import type { AttachedModule } from '../../redux/modules/types' +import type { AttachedModule } from '/app/redux/modules/types' export type ModulePrepCommandsType = | TemperatureModuleDeactivateCreateCommand diff --git a/app/src/local-resources/modules/utils/index.ts b/app/src/local-resources/modules/utils/index.ts new file mode 100644 index 00000000000..7f3f558738d --- /dev/null +++ b/app/src/local-resources/modules/utils/index.ts @@ -0,0 +1,5 @@ +export * from './getLoadedModule' +export * from './getModuleDisplayLocation' +export * from './getModuleImage' +export * from './getModuleModel' +export * from './getModulePrepCommands' diff --git a/app/src/molecules/BackgroundOverlay/__tests__/BackgroundOverlay.test.tsx b/app/src/molecules/BackgroundOverlay/__tests__/BackgroundOverlay.test.tsx index 65d4743f5ad..e09b3c11765 100644 --- a/app/src/molecules/BackgroundOverlay/__tests__/BackgroundOverlay.test.tsx +++ b/app/src/molecules/BackgroundOverlay/__tests__/BackgroundOverlay.test.tsx @@ -1,7 +1,7 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, vi } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { BackgroundOverlay } from '..' const render = (props: React.ComponentProps) => { diff --git a/app/src/molecules/BackgroundOverlay/index.tsx b/app/src/molecules/BackgroundOverlay/index.tsx index ccfdb273fc4..3d6c6d976c6 100644 --- a/app/src/molecules/BackgroundOverlay/index.tsx +++ b/app/src/molecules/BackgroundOverlay/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { COLORS, Flex, POSITION_FIXED } from '@opentrons/components' diff --git a/app/src/molecules/CardButton/CardButton.stories.tsx b/app/src/molecules/CardButton/CardButton.stories.tsx index 67256590b4b..3d80e18a3bb 100644 --- a/app/src/molecules/CardButton/CardButton.stories.tsx +++ b/app/src/molecules/CardButton/CardButton.stories.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Flex, ICON_DATA_BY_NAME, diff --git a/app/src/molecules/CardButton/__tests__/CardButton.test.tsx b/app/src/molecules/CardButton/__tests__/CardButton.test.tsx index 04a841ccc23..820dfbdc4e9 100644 --- a/app/src/molecules/CardButton/__tests__/CardButton.test.tsx +++ b/app/src/molecules/CardButton/__tests__/CardButton.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' import { describe, it, expect, vi, beforeEach } from 'vitest' import { MemoryRouter } from 'react-router-dom' import { COLORS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { CardButton } from '..' import type { NavigateFunction } from 'react-router-dom' diff --git a/app/src/molecules/CardButton/index.tsx b/app/src/molecules/CardButton/index.tsx index 1181985f772..86ed6e3731a 100644 --- a/app/src/molecules/CardButton/index.tsx +++ b/app/src/molecules/CardButton/index.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useNavigate } from 'react-router-dom' import { css } from 'styled-components' import { @@ -14,7 +13,7 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { ODD_FOCUS_VISIBLE } from '../../atoms/buttons/constants' +import { ODD_FOCUS_VISIBLE } from '/app/atoms/buttons/constants' import type { IconName } from '@opentrons/components' diff --git a/app/src/molecules/CollapsibleSection/__tests__/CollapsibleSection.test.tsx b/app/src/molecules/CollapsibleSection/__tests__/CollapsibleSection.test.tsx index 2a444b25a65..3acef8b4a29 100644 --- a/app/src/molecules/CollapsibleSection/__tests__/CollapsibleSection.test.tsx +++ b/app/src/molecules/CollapsibleSection/__tests__/CollapsibleSection.test.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import '@testing-library/jest-dom/vitest' import { describe, it, expect } from 'vitest' import { fireEvent, render, screen } from '@testing-library/react' diff --git a/app/src/molecules/CollapsibleSection/index.tsx b/app/src/molecules/CollapsibleSection/index.tsx index a11e73be358..3a359edeb4f 100644 --- a/app/src/molecules/CollapsibleSection/index.tsx +++ b/app/src/molecules/CollapsibleSection/index.tsx @@ -1,9 +1,10 @@ -import * as React from 'react' +import { useState } from 'react' import { css } from 'styled-components' import { Btn, COLORS, + CURSOR_POINTER, DIRECTION_COLUMN, Flex, Icon, @@ -11,6 +12,8 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' + +import type { ReactNode } from 'react' import type { StyleProps } from '@opentrons/components' const ACCORDION_STYLE = css` @@ -25,7 +28,7 @@ const ACCORDION_STYLE = css` interface CollapsibleSectionProps extends StyleProps { title: string - children: React.ReactNode + children: ReactNode isExpandedInitially?: boolean } @@ -33,7 +36,7 @@ export function CollapsibleSection( props: CollapsibleSectionProps ): JSX.Element { const { title, children, isExpandedInitially = true, ...styleProps } = props - const [isExpanded, setIsExpanded] = React.useState(isExpandedInitially) + const [isExpanded, setIsExpanded] = useState(isExpandedInitially) return ( command.commandType) + Fixtures.mockDoItAllTextData.commands.map(command => command.commandType) ) const commandsByType: Partial> = {} @@ -27,7 +26,7 @@ function commandsOfType(type: CommandType): RunTimeCommand[] { if (type in commandsByType) { return commandsByType[type] } - commandsByType[type] = Fixtures.mockQIASeqTextData.commands.filter( + commandsByType[type] = Fixtures.mockDoItAllTextData.commands.filter( command => command.commandType === type ) return commandsByType[type] @@ -44,8 +43,8 @@ function safeCommandOfType(type: CommandType, index: number): RunTimeCommand { function Wrapper(props: StorybookArgs): JSX.Element { const command = props.selectCommandBy === 'protocol index' - ? Fixtures.mockQIASeqTextData.commands[ - props.commandIndex < Fixtures.mockQIASeqTextData.commands.length + ? Fixtures.mockDoItAllTextData.commands[ + props.commandIndex < Fixtures.mockDoItAllTextData.commands.length ? props.commandIndex : -1 ] @@ -53,7 +52,7 @@ function Wrapper(props: StorybookArgs): JSX.Element { return command == null ? null : ( = { control: { type: 'range', min: 0, - max: Fixtures.mockQIASeqTextData.commands.length - 1, + max: Fixtures.mockDoItAllTextData.commands.length - 1, }, defaultValue: 0, if: { arg: 'selectCommandBy', eq: 'protocol index' }, @@ -162,6 +161,16 @@ export const ThermocyclerProfile: Story = { }, } +export const ThermocyclerExtendedProfile: Story = { + args: { + selectCommandBy: 'command type', + commandType: 'thermocycler/runExtendedProfile', + commandTypeIndex: 0, + aligned: 'left', + state: 'current', + }, +} + export const VeryLongCommand: Story = { args: { selectCommandBy: 'command type', diff --git a/app/src/molecules/Command/Command.tsx b/app/src/molecules/Command/Command.tsx index fb8452f2a92..e71757313a4 100644 --- a/app/src/molecules/Command/Command.tsx +++ b/app/src/molecules/Command/Command.tsx @@ -1,4 +1,5 @@ -import * as React from 'react' +import { omit } from 'lodash' + import { Flex, JUSTIFY_CENTER, @@ -9,13 +10,18 @@ import { SPACING, RESPONSIVENESS, } from '@opentrons/components' -import type { RobotType, RunTimeCommand } from '@opentrons/shared-data' + import { CommandText } from './CommandText' import { CommandIcon } from './CommandIcon' -import type { CommandTextData } from './types' -import { Skeleton } from '../../atoms/Skeleton' +import { Skeleton } from '/app/atoms/Skeleton' + +import type { + LabwareDefinition2, + RobotType, + RunTimeCommand, +} from '@opentrons/shared-data' +import type { CommandTextData } from '/app/local-resources/commands' import type { StyleProps } from '@opentrons/components' -import { omit } from 'lodash' export type CommandState = NonSkeletonCommandState | 'loading' export type NonSkeletonCommandState = 'current' | 'failed' | 'future' @@ -35,6 +41,7 @@ interface SkeletonCommandProps extends FundamentalProps { interface NonSkeletonCommandProps extends FundamentalProps { state: NonSkeletonCommandState command: RunTimeCommand + allRunDefs: LabwareDefinition2[] commandTextData: CommandTextData } diff --git a/app/src/molecules/Command/CommandIcon.stories.tsx b/app/src/molecules/Command/CommandIcon.stories.tsx index 4312d585554..4452a67cefb 100644 --- a/app/src/molecules/Command/CommandIcon.stories.tsx +++ b/app/src/molecules/Command/CommandIcon.stories.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { CommandIcon as CommandIconComponent } from './CommandIcon' import type { ICON_BY_COMMAND_TYPE } from './CommandIcon' diff --git a/app/src/molecules/Command/CommandIcon.tsx b/app/src/molecules/Command/CommandIcon.tsx index 77eb4ef3914..4002de50973 100644 --- a/app/src/molecules/Command/CommandIcon.tsx +++ b/app/src/molecules/Command/CommandIcon.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Icon } from '@opentrons/components' import type { IconName, StyleProps } from '@opentrons/components' import type { RunTimeCommand } from '@opentrons/shared-data' diff --git a/app/src/molecules/Command/CommandIndex.tsx b/app/src/molecules/Command/CommandIndex.tsx index bcabae9ef90..6357d372486 100644 --- a/app/src/molecules/Command/CommandIndex.tsx +++ b/app/src/molecules/Command/CommandIndex.tsx @@ -1,5 +1,3 @@ -import * as React from 'react' - import { StyledText, RESPONSIVENESS, diff --git a/app/src/molecules/Command/CommandText.stories.tsx b/app/src/molecules/Command/CommandText.stories.tsx index fa361be59dc..a76acd5fa92 100644 --- a/app/src/molecules/Command/CommandText.stories.tsx +++ b/app/src/molecules/Command/CommandText.stories.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' import { Box } from '@opentrons/components' import { CommandText as CommandTextComponent } from '.' import type { RobotType } from '@opentrons/shared-data' import * as Fixtures from './__fixtures__' +import { getLabwareDefinitionsFromCommands } from '../../local-resources/labware' import type { Meta, StoryObj } from '@storybook/react' @@ -13,6 +13,10 @@ interface StorybookArgs { } function Wrapper(props: StorybookArgs): JSX.Element { + const allRunDefs = getLabwareDefinitionsFromCommands( + Fixtures.mockDoItAllTextData.commands + ) + return ( ) diff --git a/app/src/molecules/Command/CommandText.tsx b/app/src/molecules/Command/CommandText.tsx index 75b12733eca..e690115a88b 100644 --- a/app/src/molecules/Command/CommandText.tsx +++ b/app/src/molecules/Command/CommandText.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { pick } from 'lodash' import { @@ -11,11 +11,19 @@ import { RESPONSIVENESS, } from '@opentrons/components' -import { useCommandTextString } from './hooks' +import { useCommandTextString } from '/app/local-resources/commands' -import type { RobotType, RunTimeCommand } from '@opentrons/shared-data' +import type { + LabwareDefinition2, + RobotType, + RunTimeCommand, +} from '@opentrons/shared-data' import type { StyleProps } from '@opentrons/components' -import type { CommandTextData } from './types' +import type { + GetTCRunExtendedProfileCommandTextResult, + GetTCRunProfileCommandTextResult, + CommandTextData, +} from '/app/local-resources/commands' interface LegacySTProps { as?: React.ComponentProps['as'] @@ -32,6 +40,7 @@ type STProps = LegacySTProps | ModernSTProps interface BaseProps extends StyleProps { command: RunTimeCommand + allRunDefs: LabwareDefinition2[] commandTextData: CommandTextData robotType: RobotType isOnDevice?: boolean @@ -39,22 +48,35 @@ interface BaseProps extends StyleProps { propagateTextLimit?: boolean } export function CommandText(props: BaseProps & STProps): JSX.Element | null { - const { commandText, stepTexts } = useCommandTextString({ + const commandText = useCommandTextString({ ...props, }) - switch (props.command.commandType) { + switch (commandText.kind) { case 'thermocycler/runProfile': { return ( + ) + } + case 'thermocycler/runExtendedProfile': { + return ( + ) } default: { - return {commandText} + return ( + + {commandText.commandText} + + ) } } } @@ -91,11 +113,18 @@ function CommandStyledText( } } +const shouldPropagateCenter = ( + propagateCenter: boolean, + isOnDevice?: boolean +): boolean => isOnDevice === true || propagateCenter +const shouldPropagateTextLimit = ( + propagateTextLimit: boolean, + isOnDevice?: boolean +): boolean => isOnDevice === true || propagateTextLimit + type ThermocyclerRunProfileProps = BaseProps & - STProps & { - commandText: string - stepTexts?: string[] - } + STProps & + Omit function ThermocyclerRunProfile( props: ThermocyclerRunProfileProps @@ -109,9 +138,6 @@ function ThermocyclerRunProfile( ...styleProps } = props - const shouldPropagateCenter = isOnDevice === true || propagateCenter - const shouldPropagateTextLimit = isOnDevice === true || propagateTextLimit - // TODO(sfoster): Command sometimes wraps this in a cascaded display: -webkit-box // to achieve multiline text clipping with an automatically inserted ellipsis, which works // everywhere except for here where it overrides this property in the flex since this is @@ -124,7 +150,11 @@ function ThermocyclerRunProfile(
    - {shouldPropagateTextLimit ? ( + {shouldPropagateTextLimit(propagateTextLimit, isOnDevice) ? (
  • - {stepTexts?.[0]} + {stepTexts[0]}
  • ) : ( - stepTexts?.map((step: string, index: number) => ( + stepTexts.map((step: string, index: number) => (
  • ) } + +type ThermocyclerRunExtendedProfileProps = BaseProps & + STProps & + Omit + +function ThermocyclerRunExtendedProfile( + props: ThermocyclerRunExtendedProfileProps +): JSX.Element { + const { + isOnDevice, + propagateCenter = false, + propagateTextLimit = false, + commandText, + profileElementTexts, + ...styleProps + } = props + + // TODO(sfoster): Command sometimes wraps this in a cascaded display: -webkit-box + // to achieve multiline text clipping with an automatically inserted ellipsis, which works + // everywhere except for here where it overrides this property in the flex since this is + // the only place where CommandText uses a flex. + // The right way to handle this is probably to take the css that's in Command and make it + // live here instead, but that should be done in a followup since it would touch everything. + // See also the margin-left on the
  • s, which is needed to prevent their bullets from + // clipping if a container set overflow: hidden. + return ( + + + {commandText} + + +
      + {shouldPropagateTextLimit(propagateTextLimit, isOnDevice) ? ( +
    • + {profileElementTexts[0].kind === 'step' + ? profileElementTexts[0].stepText + : profileElementTexts[0].cycleText} +
    • + ) : ( + profileElementTexts.map((element, index: number) => + element.kind === 'step' ? ( +
    • + {' '} + {element.stepText} +
    • + ) : ( +
    • + {element.cycleText} +
        + {element.stepTexts.map( + ({ stepText }, stepIndex: number) => ( +
      • + {' '} + {stepText} +
      • + ) + )} +
      +
    • + ) + ) + )} +
    +
    +
    + ) +} diff --git a/app/src/molecules/Command/__fixtures__/doItAllV10.json b/app/src/molecules/Command/__fixtures__/doItAllV10.json new file mode 100644 index 00000000000..83030179e79 --- /dev/null +++ b/app/src/molecules/Command/__fixtures__/doItAllV10.json @@ -0,0 +1,4863 @@ +{ + "id": "lasdlakjjflaksjdlkajsldkasd", + "result": "ok", + "status": "completed", + "createdAt": "2024-06-12T17:21:56.919263+00:00", + "files": [ + { "name": "doItAllV8.json", "role": "main" }, + { "name": "cpx_4_tuberack_100ul.json", "role": "labware" } + ], + "config": { "protocolType": "json", "schemaVersion": 8 }, + "metadata": { + "protocolName": "doItAllV10", + "author": "", + "description": "", + "created": 1701659107408, + "lastModified": 1714570438503, + "category": null, + "subcategory": null, + "tags": [] + }, + "robotType": "OT-3 Standard", + "runTimeParameters": [], + "commands": [ + { + "id": "70fbbc6c-d86a-4e66-9361-25de75552da6", + "createdAt": "2024-06-12T17:21:57.915484+00:00", + "commandType": "thermocycler/runProfile", + "key": "5ec88b6a-2c2c-4ffc-961f-c6e0dd300b49", + "status": "succeeded", + "params": { + "moduleId": "fd6da9f1-d63b-414b-929e-c646b64790e9:thermocyclerModuleType", + "profile": [ + { "holdSeconds": 1, "celsius": 9 }, + { "holdSeconds": 2, "celsius": 10 }, + { "holdSeconds": 3, "celsius": 11 }, + { "holdSeconds": 4, "celsius": 12 }, + { "holdSeconds": 5, "celsius": 13 }, + { "holdSeconds": 6, "celsius": 14 }, + { "holdSeconds": 7, "celsius": 15 }, + { "holdSeconds": 8, "celsius": 16 }, + { "holdSeconds": 9, "celsius": 17 }, + { "holdSeconds": 10, "celsius": 18 }, + { "holdSeconds": 11, "celsius": 19 }, + { "holdSeconds": 12, "celsius": 20 }, + { "holdSeconds": 1, "celsius": 9 }, + { "holdSeconds": 2, "celsius": 10 }, + { "holdSeconds": 3, "celsius": 11 }, + { "holdSeconds": 4, "celsius": 12 }, + { "holdSeconds": 5, "celsius": 13 }, + { "holdSeconds": 6, "celsius": 14 }, + { "holdSeconds": 7, "celsius": 15 }, + { "holdSeconds": 8, "celsius": 16 }, + { "holdSeconds": 9, "celsius": 17 }, + { "holdSeconds": 10, "celsius": 18 }, + { "holdSeconds": 11, "celsius": 19 }, + { "holdSeconds": 12, "celsius": 20 } + ], + "blockMaxVolumeUl": 10 + }, + "result": {}, + "startedAt": "2024-06-12T17:21:57.915517+00:00", + "completedAt": "2024-06-12T17:21:57.915543+00:00", + "notes": [] + }, + { + "id": "70fbbc6c-d86a-4e66-9361-25de75552da6", + "createdAt": "2024-06-12T17:21:57.915484+00:00", + "commandType": "thermocycler/runExtendedProfile", + "key": "5ec22b6a-2c2c-4bbc-961f-c6e0aa211b49", + "status": "succeeded", + "params": { + "moduleId": "f99da9f1-d63b-414b-929e-c646b23790fd:thermocyclerModuleType", + "profileElements": [ + { + "repetitions": 10, + "steps": [ + { "holdSeconds": 2, "celsius": 10 }, + { "holdSeconds": 3, "celsius": 11 }, + { "holdSeconds": 4, "celsius": 12 } + ] + }, + { "holdSeconds": 1, "celsius": 9 }, + { "holdSeconds": 5, "celsius": 13 }, + { + "repetitions": 20, + "steps": [ + { "holdSeconds": 6, "celsius": 14 }, + { "holdSeconds": 7, "celsius": 15 }, + { "holdSeconds": 8, "celsius": 16 } + ] + }, + { "holdSeconds": 9, "celsius": 17 }, + { + "repetitions": 30, + "steps": [ + { "holdSeconds": 10, "celsius": 18 }, + { "holdSeconds": 11, "celsius": 19 }, + { "holdSeconds": 12, "celsius": 20 } + ] + }, + { "holdSeconds": 1, "celsius": 9 }, + { + "repetitions": 40, + "steps": [ + { "holdSeconds": 2, "celsius": 10 }, + { "holdSeconds": 3, "celsius": 11 }, + { "holdSeconds": 4, "celsius": 12 } + ] + }, + { "holdSeconds": 5, "celsius": 13 }, + { + "repetitions": 50, + "steps": [ + { "holdSeconds": 6, "celsius": 14 }, + { "holdSeconds": 7, "celsius": 15 }, + { "holdSeconds": 8, "celsius": 16 } + ] + }, + { "holdSeconds": 9, "celsius": 17 }, + { + "repetitions": 60, + "steps": [ + { "holdSeconds": 10, "celsius": 18 }, + { "holdSeconds": 11, "celsius": 19 }, + { "holdSeconds": 12, "celsius": 20 } + ] + } + ], + "blockMaxVolumeUl": 10 + }, + "result": {}, + "startedAt": "2024-06-12T17:21:57.915517+00:00", + "completedAt": "2024-06-12T17:21:57.915543+00:00", + "notes": [] + }, + { + "id": "8f0be368-dc25-4f2a-92b9-a734ad622d4b", + "createdAt": "2024-06-12T17:21:56.894269+00:00", + "commandType": "home", + "key": "50c7ae73a4e3f7129874f39dfb514803", + "status": "succeeded", + "params": {}, + "result": {}, + "startedAt": "2024-06-12T17:21:56.894479+00:00", + "completedAt": "2024-06-12T17:21:56.894522+00:00", + "notes": [] + }, + { + "id": "c744767e-f5a4-408d-bc28-d46a2bd17297", + "createdAt": "2024-06-12T17:21:56.894706+00:00", + "commandType": "loadPipette", + "key": "a1b95079-5b17-428d-b40c-a8236a9890c5", + "status": "succeeded", + "params": { + "pipetteName": "p1000_single_flex", + "mount": "left", + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" }, + "startedAt": "2024-06-12T17:21:56.894767+00:00", + "completedAt": "2024-06-12T17:21:56.898018+00:00", + "notes": [] + }, + { + "id": "684793b1-7401-4291-ac0f-e95d61e49e07", + "createdAt": "2024-06-12T17:21:56.898252+00:00", + "commandType": "loadModule", + "key": "6f1e3ad3-8f03-4583-8031-be6be2fcd903", + "status": "succeeded", + "params": { + "model": "heaterShakerModuleV1", + "location": { "slotName": "D1" }, + "moduleId": "23347241-80bb-4a7e-9c91-5d9727a9e483:heaterShakerModuleType" + }, + "result": { + "moduleId": "23347241-80bb-4a7e-9c91-5d9727a9e483:heaterShakerModuleType", + "definition": { + "otSharedSchema": "module/schemas/2", + "moduleType": "heaterShakerModuleType", + "model": "heaterShakerModuleV1", + "labwareOffset": { "x": -0.125, "y": 1.125, "z": 68.275 }, + "dimensions": { "bareOverallHeight": 82.0, "overLabwareHeight": 0.0 }, + "calibrationPoint": { "x": 12.0, "y": 8.75, "z": 68.275 }, + "displayName": "Heater-Shaker Module GEN1", + "quirks": [], + "slotTransforms": { + "ot2_standard": { + "3": { + "labwareOffset": [ + [-1, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ] + }, + "6": { + "labwareOffset": [ + [-1, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ] + }, + "9": { + "labwareOffset": [ + [-1, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ] + } + }, + "ot2_short_trash": { + "3": { + "labwareOffset": [ + [-1, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ] + }, + "6": { + "labwareOffset": [ + [-1, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ] + }, + "9": { + "labwareOffset": [ + [-1, 0, 0, 0], + [0, -1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ] + } + }, + "ot3_standard": { + "D1": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + }, + "C1": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + }, + "B1": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + }, + "A1": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + }, + "D3": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + }, + "C3": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + }, + "B3": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + }, + "A3": { + "labwareOffset": [ + [1, 0, 0, 0.125], + [0, 1, 0, -1.125], + [0, 0, 1, -49.325], + [0, 0, 0, 1] + ] + } + } + }, + "compatibleWith": [], + "gripperOffsets": { + "default": { + "pickUpOffset": { "x": 0.0, "y": 0.0, "z": 0.0 }, + "dropOffset": { "x": 0.0, "y": 0.0, "z": 1.0 } + } + } + }, + "model": "heaterShakerModuleV1", + "serialNumber": "fake-serial-number-0d06c2c1-3ee4-467d-b1cb-31ce8a260ff5" + }, + "startedAt": "2024-06-12T17:21:56.898296+00:00", + "completedAt": "2024-06-12T17:21:56.898586+00:00", + "notes": [] + }, + { + "id": "a72762a6-d894-4a48-91a0-1e0e46ae41e4", + "createdAt": "2024-06-12T17:21:56.898811+00:00", + "commandType": "loadModule", + "key": "4997a543-7788-434f-8eae-1c4aa3a2a805", + "status": "succeeded", + "params": { + "model": "thermocyclerModuleV2", + "location": { "slotName": "B1" }, + "moduleId": "fd6da9f1-d63b-414b-929e-c646b64790e9:thermocyclerModuleType" + }, + "result": { + "moduleId": "fd6da9f1-d63b-414b-929e-c646b64790e9:thermocyclerModuleType", + "definition": { + "otSharedSchema": "module/schemas/2", + "moduleType": "thermocyclerModuleType", + "model": "thermocyclerModuleV2", + "labwareOffset": { "x": 0.0, "y": 68.8, "z": 108.96 }, + "dimensions": { + "bareOverallHeight": 108.96, + "overLabwareHeight": 0.0, + "lidHeight": 61.7 + }, + "calibrationPoint": { "x": 14.4, "y": 64.93, "z": 97.8 }, + "displayName": "Thermocycler Module GEN2", + "quirks": [], + "slotTransforms": { + "ot3_standard": { + "B1": { + "labwareOffset": [ + [1, 0, 0, -20.005], + [0, 1, 0, -0.84], + [0, 0, 1, -98], + [0, 0, 0, 1] + ], + "cornerOffsetFromSlot": [ + [1, 0, 0, -20.005], + [0, 1, 0, -0.84], + [0, 0, 1, -98], + [0, 0, 0, 1] + ] + } + } + }, + "compatibleWith": [], + "gripperOffsets": { + "default": { + "pickUpOffset": { "x": 0.0, "y": 0.0, "z": 4.6 }, + "dropOffset": { "x": 0.0, "y": 0.0, "z": 5.6 } + } + } + }, + "model": "thermocyclerModuleV2", + "serialNumber": "fake-serial-number-abe43d5b-1dd7-4fa9-bf8b-b089236d5adb" + }, + "startedAt": "2024-06-12T17:21:56.898850+00:00", + "completedAt": "2024-06-12T17:21:56.899485+00:00", + "notes": [] + }, + { + "id": "e141cd5f-7c92-4c89-aea6-79057400ee5d", + "createdAt": "2024-06-12T17:21:56.899614+00:00", + "commandType": "loadLabware", + "key": "8bfb6d48-4d08-4ea0-8ce7-f8efe90e202c", + "status": "succeeded", + "params": { + "location": { + "moduleId": "23347241-80bb-4a7e-9c91-5d9727a9e483:heaterShakerModuleType" + }, + "loadName": "opentrons_96_pcr_adapter", + "namespace": "opentrons", + "version": 1, + "labwareId": "7c4d59fa-0e50-442f-adce-9e4b0c7f0b88:opentrons/opentrons_96_pcr_adapter/1", + "displayName": "Opentrons 96 PCR Heater-Shaker Adapter" + }, + "result": { + "labwareId": "7c4d59fa-0e50-442f-adce-9e4b0c7f0b88:opentrons/opentrons_96_pcr_adapter/1", + "definition": { + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "metadata": { + "displayName": "Opentrons 96 PCR Heater-Shaker Adapter", + "displayCategory": "adapter", + "displayVolumeUnits": "\u00b5L", + "tags": [] + }, + "brand": { "brand": "Opentrons", "brandId": [] }, + "parameters": { + "format": "96Standard", + "quirks": [], + "isTiprack": false, + "loadName": "opentrons_96_pcr_adapter", + "isMagneticModuleCompatible": false + }, + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "cornerOffsetFromSlot": { "x": 8.5, "y": 5.5, "z": 0 }, + "dimensions": { + "yDimension": 75, + "zDimension": 13.85, + "xDimension": 111 + }, + "wells": { + "A1": { + "depth": 12, + "x": 6, + "y": 69, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "B1": { + "depth": 12, + "x": 6, + "y": 60, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "C1": { + "depth": 12, + "x": 6, + "y": 51, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "D1": { + "depth": 12, + "x": 6, + "y": 42, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "E1": { + "depth": 12, + "x": 6, + "y": 33, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "F1": { + "depth": 12, + "x": 6, + "y": 24, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "G1": { + "depth": 12, + "x": 6, + "y": 15, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "H1": { + "depth": 12, + "x": 6, + "y": 6, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "A2": { + "depth": 12, + "x": 15, + "y": 69, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "B2": { + "depth": 12, + "x": 15, + "y": 60, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "C2": { + "depth": 12, + "x": 15, + "y": 51, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "D2": { + "depth": 12, + "x": 15, + "y": 42, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "E2": { + "depth": 12, + "x": 15, + "y": 33, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "F2": { + "depth": 12, + "x": 15, + "y": 24, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "G2": { + "depth": 12, + "x": 15, + "y": 15, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "H2": { + "depth": 12, + "x": 15, + "y": 6, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "A3": { + "depth": 12, + "x": 24, + "y": 69, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "B3": { + "depth": 12, + "x": 24, + "y": 60, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "C3": { + "depth": 12, + "x": 24, + "y": 51, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "D3": { + "depth": 12, + "x": 24, + "y": 42, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "E3": { + "depth": 12, + "x": 24, + "y": 33, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "F3": { + "depth": 12, + "x": 24, + "y": 24, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "G3": { + "depth": 12, + "x": 24, + "y": 15, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "H3": { + "depth": 12, + "x": 24, + "y": 6, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "A4": { + "depth": 12, + "x": 33, + "y": 69, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "B4": { + "depth": 12, + "x": 33, + "y": 60, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "C4": { + "depth": 12, + "x": 33, + "y": 51, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "D4": { + "depth": 12, + "x": 33, + "y": 42, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "E4": { + "depth": 12, + "x": 33, + "y": 33, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "F4": { + "depth": 12, + "x": 33, + "y": 24, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "G4": { + "depth": 12, + "x": 33, + "y": 15, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "H4": { + "depth": 12, + "x": 33, + "y": 6, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "A5": { + "depth": 12, + "x": 42, + "y": 69, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "B5": { + "depth": 12, + "x": 42, + "y": 60, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "C5": { + "depth": 12, + "x": 42, + "y": 51, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "D5": { + "depth": 12, + "x": 42, + "y": 42, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "E5": { + "depth": 12, + "x": 42, + "y": 33, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "F5": { + "depth": 12, + "x": 42, + "y": 24, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "G5": { + "depth": 12, + "x": 42, + "y": 15, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "H5": { + "depth": 12, + "x": 42, + "y": 6, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "A6": { + "depth": 12, + "x": 51, + "y": 69, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "B6": { + "depth": 12, + "x": 51, + "y": 60, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "C6": { + "depth": 12, + "x": 51, + "y": 51, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "D6": { + "depth": 12, + "x": 51, + "y": 42, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "E6": { + "depth": 12, + "x": 51, + "y": 33, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "F6": { + "depth": 12, + "x": 51, + "y": 24, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "G6": { + "depth": 12, + "x": 51, + "y": 15, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "H6": { + "depth": 12, + "x": 51, + "y": 6, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "A7": { + "depth": 12, + "x": 60, + "y": 69, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "B7": { + "depth": 12, + "x": 60, + "y": 60, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "C7": { + "depth": 12, + "x": 60, + "y": 51, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "D7": { + "depth": 12, + "x": 60, + "y": 42, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "E7": { + "depth": 12, + "x": 60, + "y": 33, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "F7": { + "depth": 12, + "x": 60, + "y": 24, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "G7": { + "depth": 12, + "x": 60, + "y": 15, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "H7": { + "depth": 12, + "x": 60, + "y": 6, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "A8": { + "depth": 12, + "x": 69, + "y": 69, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "B8": { + "depth": 12, + "x": 69, + "y": 60, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "C8": { + "depth": 12, + "x": 69, + "y": 51, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "D8": { + "depth": 12, + "x": 69, + "y": 42, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "E8": { + "depth": 12, + "x": 69, + "y": 33, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "F8": { + "depth": 12, + "x": 69, + "y": 24, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "G8": { + "depth": 12, + "x": 69, + "y": 15, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "H8": { + "depth": 12, + "x": 69, + "y": 6, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "A9": { + "depth": 12, + "x": 78, + "y": 69, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "B9": { + "depth": 12, + "x": 78, + "y": 60, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "C9": { + "depth": 12, + "x": 78, + "y": 51, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "D9": { + "depth": 12, + "x": 78, + "y": 42, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "E9": { + "depth": 12, + "x": 78, + "y": 33, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "F9": { + "depth": 12, + "x": 78, + "y": 24, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "G9": { + "depth": 12, + "x": 78, + "y": 15, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "H9": { + "depth": 12, + "x": 78, + "y": 6, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "A10": { + "depth": 12, + "x": 87, + "y": 69, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "B10": { + "depth": 12, + "x": 87, + "y": 60, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "C10": { + "depth": 12, + "x": 87, + "y": 51, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "D10": { + "depth": 12, + "x": 87, + "y": 42, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "E10": { + "depth": 12, + "x": 87, + "y": 33, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "F10": { + "depth": 12, + "x": 87, + "y": 24, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "G10": { + "depth": 12, + "x": 87, + "y": 15, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "H10": { + "depth": 12, + "x": 87, + "y": 6, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "A11": { + "depth": 12, + "x": 96, + "y": 69, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "B11": { + "depth": 12, + "x": 96, + "y": 60, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "C11": { + "depth": 12, + "x": 96, + "y": 51, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "D11": { + "depth": 12, + "x": 96, + "y": 42, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "E11": { + "depth": 12, + "x": 96, + "y": 33, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "F11": { + "depth": 12, + "x": 96, + "y": 24, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "G11": { + "depth": 12, + "x": 96, + "y": 15, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "H11": { + "depth": 12, + "x": 96, + "y": 6, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "A12": { + "depth": 12, + "x": 105, + "y": 69, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "B12": { + "depth": 12, + "x": 105, + "y": 60, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "C12": { + "depth": 12, + "x": 105, + "y": 51, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "D12": { + "depth": 12, + "x": 105, + "y": 42, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "E12": { + "depth": 12, + "x": 105, + "y": 33, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "F12": { + "depth": 12, + "x": 105, + "y": 24, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "G12": { + "depth": 12, + "x": 105, + "y": 15, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + }, + "H12": { + "depth": 12, + "x": 105, + "y": 6, + "z": 1.85, + "totalLiquidVolume": 0, + "diameter": 5.64, + "shape": "circular" + } + }, + "groups": [ + { + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + "metadata": { "wellBottomShape": "v" } + } + ], + "allowedRoles": ["adapter"], + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "gripperOffsets": { + "default": { + "pickUpOffset": { "x": 0, "y": 0, "z": 0 }, + "dropOffset": { "x": 0, "y": 0, "z": 1 } + } + } + } + }, + "startedAt": "2024-06-12T17:21:56.899718+00:00", + "completedAt": "2024-06-12T17:21:56.899792+00:00", + "notes": [] + }, + { + "id": "edd39a48-10ea-4cf1-848f-9484c136714f", + "createdAt": "2024-06-12T17:21:56.899904+00:00", + "commandType": "loadLabware", + "key": "988395e3-9b85-4bb0-89a4-3afc1d7330fd", + "status": "succeeded", + "params": { + "location": { "slotName": "C2" }, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "namespace": "opentrons", + "version": 1, + "labwareId": "f2d371ea-5146-4c89-8200-9c056a7f321a:opentrons/opentrons_flex_96_tiprack_1000ul/1", + "displayName": "Opentrons Flex 96 Tip Rack 1000 \u00b5L" + }, + "result": { + "labwareId": "f2d371ea-5146-4c89-8200-9c056a7f321a:opentrons/opentrons_flex_96_tiprack_1000ul/1", + "definition": { + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "metadata": { + "displayName": "Opentrons Flex 96 Tip Rack 1000 \u00b5L", + "displayCategory": "tipRack", + "displayVolumeUnits": "\u00b5L", + "tags": [] + }, + "brand": { "brand": "Opentrons", "brandId": [] }, + "parameters": { + "format": "96Standard", + "quirks": [], + "isTiprack": true, + "tipLength": 95.6, + "tipOverlap": 10.5, + "loadName": "opentrons_flex_96_tiprack_1000ul", + "isMagneticModuleCompatible": false + }, + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 }, + "dimensions": { + "yDimension": 85.75, + "zDimension": 99, + "xDimension": 127.75 + }, + "wells": { + "A1": { + "depth": 97.5, + "x": 14.38, + "y": 74.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "B1": { + "depth": 97.5, + "x": 14.38, + "y": 65.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "C1": { + "depth": 97.5, + "x": 14.38, + "y": 56.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "D1": { + "depth": 97.5, + "x": 14.38, + "y": 47.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "E1": { + "depth": 97.5, + "x": 14.38, + "y": 38.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "F1": { + "depth": 97.5, + "x": 14.38, + "y": 29.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "G1": { + "depth": 97.5, + "x": 14.38, + "y": 20.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "H1": { + "depth": 97.5, + "x": 14.38, + "y": 11.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "A2": { + "depth": 97.5, + "x": 23.38, + "y": 74.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "B2": { + "depth": 97.5, + "x": 23.38, + "y": 65.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "C2": { + "depth": 97.5, + "x": 23.38, + "y": 56.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "D2": { + "depth": 97.5, + "x": 23.38, + "y": 47.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "E2": { + "depth": 97.5, + "x": 23.38, + "y": 38.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "F2": { + "depth": 97.5, + "x": 23.38, + "y": 29.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "G2": { + "depth": 97.5, + "x": 23.38, + "y": 20.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "H2": { + "depth": 97.5, + "x": 23.38, + "y": 11.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "A3": { + "depth": 97.5, + "x": 32.38, + "y": 74.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "B3": { + "depth": 97.5, + "x": 32.38, + "y": 65.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "C3": { + "depth": 97.5, + "x": 32.38, + "y": 56.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "D3": { + "depth": 97.5, + "x": 32.38, + "y": 47.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "E3": { + "depth": 97.5, + "x": 32.38, + "y": 38.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "F3": { + "depth": 97.5, + "x": 32.38, + "y": 29.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "G3": { + "depth": 97.5, + "x": 32.38, + "y": 20.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "H3": { + "depth": 97.5, + "x": 32.38, + "y": 11.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "A4": { + "depth": 97.5, + "x": 41.38, + "y": 74.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "B4": { + "depth": 97.5, + "x": 41.38, + "y": 65.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "C4": { + "depth": 97.5, + "x": 41.38, + "y": 56.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "D4": { + "depth": 97.5, + "x": 41.38, + "y": 47.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "E4": { + "depth": 97.5, + "x": 41.38, + "y": 38.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "F4": { + "depth": 97.5, + "x": 41.38, + "y": 29.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "G4": { + "depth": 97.5, + "x": 41.38, + "y": 20.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "H4": { + "depth": 97.5, + "x": 41.38, + "y": 11.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "A5": { + "depth": 97.5, + "x": 50.38, + "y": 74.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "B5": { + "depth": 97.5, + "x": 50.38, + "y": 65.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "C5": { + "depth": 97.5, + "x": 50.38, + "y": 56.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "D5": { + "depth": 97.5, + "x": 50.38, + "y": 47.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "E5": { + "depth": 97.5, + "x": 50.38, + "y": 38.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "F5": { + "depth": 97.5, + "x": 50.38, + "y": 29.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "G5": { + "depth": 97.5, + "x": 50.38, + "y": 20.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "H5": { + "depth": 97.5, + "x": 50.38, + "y": 11.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "A6": { + "depth": 97.5, + "x": 59.38, + "y": 74.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "B6": { + "depth": 97.5, + "x": 59.38, + "y": 65.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "C6": { + "depth": 97.5, + "x": 59.38, + "y": 56.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "D6": { + "depth": 97.5, + "x": 59.38, + "y": 47.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "E6": { + "depth": 97.5, + "x": 59.38, + "y": 38.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "F6": { + "depth": 97.5, + "x": 59.38, + "y": 29.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "G6": { + "depth": 97.5, + "x": 59.38, + "y": 20.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "H6": { + "depth": 97.5, + "x": 59.38, + "y": 11.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "A7": { + "depth": 97.5, + "x": 68.38, + "y": 74.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "B7": { + "depth": 97.5, + "x": 68.38, + "y": 65.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "C7": { + "depth": 97.5, + "x": 68.38, + "y": 56.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "D7": { + "depth": 97.5, + "x": 68.38, + "y": 47.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "E7": { + "depth": 97.5, + "x": 68.38, + "y": 38.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "F7": { + "depth": 97.5, + "x": 68.38, + "y": 29.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "G7": { + "depth": 97.5, + "x": 68.38, + "y": 20.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "H7": { + "depth": 97.5, + "x": 68.38, + "y": 11.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "A8": { + "depth": 97.5, + "x": 77.38, + "y": 74.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "B8": { + "depth": 97.5, + "x": 77.38, + "y": 65.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "C8": { + "depth": 97.5, + "x": 77.38, + "y": 56.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "D8": { + "depth": 97.5, + "x": 77.38, + "y": 47.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "E8": { + "depth": 97.5, + "x": 77.38, + "y": 38.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "F8": { + "depth": 97.5, + "x": 77.38, + "y": 29.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "G8": { + "depth": 97.5, + "x": 77.38, + "y": 20.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "H8": { + "depth": 97.5, + "x": 77.38, + "y": 11.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "A9": { + "depth": 97.5, + "x": 86.38, + "y": 74.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "B9": { + "depth": 97.5, + "x": 86.38, + "y": 65.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "C9": { + "depth": 97.5, + "x": 86.38, + "y": 56.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "D9": { + "depth": 97.5, + "x": 86.38, + "y": 47.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "E9": { + "depth": 97.5, + "x": 86.38, + "y": 38.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "F9": { + "depth": 97.5, + "x": 86.38, + "y": 29.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "G9": { + "depth": 97.5, + "x": 86.38, + "y": 20.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "H9": { + "depth": 97.5, + "x": 86.38, + "y": 11.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "A10": { + "depth": 97.5, + "x": 95.38, + "y": 74.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "B10": { + "depth": 97.5, + "x": 95.38, + "y": 65.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "C10": { + "depth": 97.5, + "x": 95.38, + "y": 56.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "D10": { + "depth": 97.5, + "x": 95.38, + "y": 47.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "E10": { + "depth": 97.5, + "x": 95.38, + "y": 38.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "F10": { + "depth": 97.5, + "x": 95.38, + "y": 29.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "G10": { + "depth": 97.5, + "x": 95.38, + "y": 20.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "H10": { + "depth": 97.5, + "x": 95.38, + "y": 11.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "A11": { + "depth": 97.5, + "x": 104.38, + "y": 74.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "B11": { + "depth": 97.5, + "x": 104.38, + "y": 65.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "C11": { + "depth": 97.5, + "x": 104.38, + "y": 56.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "D11": { + "depth": 97.5, + "x": 104.38, + "y": 47.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "E11": { + "depth": 97.5, + "x": 104.38, + "y": 38.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "F11": { + "depth": 97.5, + "x": 104.38, + "y": 29.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "G11": { + "depth": 97.5, + "x": 104.38, + "y": 20.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "H11": { + "depth": 97.5, + "x": 104.38, + "y": 11.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "A12": { + "depth": 97.5, + "x": 113.38, + "y": 74.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "B12": { + "depth": 97.5, + "x": 113.38, + "y": 65.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "C12": { + "depth": 97.5, + "x": 113.38, + "y": 56.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "D12": { + "depth": 97.5, + "x": 113.38, + "y": 47.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "E12": { + "depth": 97.5, + "x": 113.38, + "y": 38.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "F12": { + "depth": 97.5, + "x": 113.38, + "y": 29.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "G12": { + "depth": 97.5, + "x": 113.38, + "y": 20.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + }, + "H12": { + "depth": 97.5, + "x": 113.38, + "y": 11.38, + "z": 1.5, + "totalLiquidVolume": 1000, + "diameter": 5.47, + "shape": "circular" + } + }, + "groups": [ + { + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + "metadata": {} + } + ], + "allowedRoles": [], + "stackingOffsetWithLabware": { + "opentrons_flex_96_tiprack_adapter": { "x": 0, "y": 0, "z": 121 } + }, + "stackingOffsetWithModule": {}, + "gripperOffsets": {}, + "gripHeightFromLabwareBottom": 23.9, + "gripForce": 16.0 + } + }, + "startedAt": "2024-06-12T17:21:56.899933+00:00", + "completedAt": "2024-06-12T17:21:56.900034+00:00", + "notes": [] + }, + { + "id": "5607c182-a90d-466e-96fd-45aafaf45b7b", + "createdAt": "2024-06-12T17:21:56.900159+00:00", + "commandType": "loadLabware", + "key": "0d60425e-5a6f-4205-ac59-d38a080f2e92", + "status": "succeeded", + "params": { + "location": { + "moduleId": "fd6da9f1-d63b-414b-929e-c646b64790e9:thermocyclerModuleType" + }, + "loadName": "opentrons_96_wellplate_200ul_pcr_full_skirt", + "namespace": "opentrons", + "version": 2, + "labwareId": "54370838-4fca-4a14-b88a-7840e4903649:opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "displayName": "Opentrons Tough 96 Well Plate 200 \u00b5L PCR Full Skirt" + }, + "result": { + "labwareId": "54370838-4fca-4a14-b88a-7840e4903649:opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "definition": { + "schemaVersion": 2, + "version": 2, + "namespace": "opentrons", + "metadata": { + "displayName": "Opentrons Tough 96 Well Plate 200 \u00b5L PCR Full Skirt", + "displayCategory": "wellPlate", + "displayVolumeUnits": "\u00b5L", + "tags": [] + }, + "brand": { + "brand": "Opentrons", + "brandId": ["991-00076"], + "links": [ + "https://shop.opentrons.com/tough-0.2-ml-96-well-pcr-plate-full-skirt/" + ] + }, + "parameters": { + "format": "96Standard", + "isTiprack": false, + "loadName": "opentrons_96_wellplate_200ul_pcr_full_skirt", + "isMagneticModuleCompatible": true + }, + "ordering": [ + ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"], + ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"], + ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"], + ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"], + ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"], + ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"], + ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"], + ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"], + ["A9", "B9", "C9", "D9", "E9", "F9", "G9", "H9"], + ["A10", "B10", "C10", "D10", "E10", "F10", "G10", "H10"], + ["A11", "B11", "C11", "D11", "E11", "F11", "G11", "H11"], + ["A12", "B12", "C12", "D12", "E12", "F12", "G12", "H12"] + ], + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 }, + "dimensions": { + "yDimension": 85.48, + "zDimension": 16, + "xDimension": 127.76 + }, + "wells": { + "A1": { + "depth": 14.95, + "x": 14.38, + "y": 74.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "B1": { + "depth": 14.95, + "x": 14.38, + "y": 65.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "C1": { + "depth": 14.95, + "x": 14.38, + "y": 56.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "D1": { + "depth": 14.95, + "x": 14.38, + "y": 47.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "E1": { + "depth": 14.95, + "x": 14.38, + "y": 38.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "F1": { + "depth": 14.95, + "x": 14.38, + "y": 29.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "G1": { + "depth": 14.95, + "x": 14.38, + "y": 20.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "H1": { + "depth": 14.95, + "x": 14.38, + "y": 11.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "A2": { + "depth": 14.95, + "x": 23.38, + "y": 74.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "B2": { + "depth": 14.95, + "x": 23.38, + "y": 65.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "C2": { + "depth": 14.95, + "x": 23.38, + "y": 56.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "D2": { + "depth": 14.95, + "x": 23.38, + "y": 47.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "E2": { + "depth": 14.95, + "x": 23.38, + "y": 38.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "F2": { + "depth": 14.95, + "x": 23.38, + "y": 29.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "G2": { + "depth": 14.95, + "x": 23.38, + "y": 20.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "H2": { + "depth": 14.95, + "x": 23.38, + "y": 11.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "A3": { + "depth": 14.95, + "x": 32.38, + "y": 74.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "B3": { + "depth": 14.95, + "x": 32.38, + "y": 65.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "C3": { + "depth": 14.95, + "x": 32.38, + "y": 56.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "D3": { + "depth": 14.95, + "x": 32.38, + "y": 47.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "E3": { + "depth": 14.95, + "x": 32.38, + "y": 38.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "F3": { + "depth": 14.95, + "x": 32.38, + "y": 29.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "G3": { + "depth": 14.95, + "x": 32.38, + "y": 20.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "H3": { + "depth": 14.95, + "x": 32.38, + "y": 11.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "A4": { + "depth": 14.95, + "x": 41.38, + "y": 74.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "B4": { + "depth": 14.95, + "x": 41.38, + "y": 65.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "C4": { + "depth": 14.95, + "x": 41.38, + "y": 56.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "D4": { + "depth": 14.95, + "x": 41.38, + "y": 47.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "E4": { + "depth": 14.95, + "x": 41.38, + "y": 38.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "F4": { + "depth": 14.95, + "x": 41.38, + "y": 29.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "G4": { + "depth": 14.95, + "x": 41.38, + "y": 20.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "H4": { + "depth": 14.95, + "x": 41.38, + "y": 11.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "A5": { + "depth": 14.95, + "x": 50.38, + "y": 74.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "B5": { + "depth": 14.95, + "x": 50.38, + "y": 65.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "C5": { + "depth": 14.95, + "x": 50.38, + "y": 56.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "D5": { + "depth": 14.95, + "x": 50.38, + "y": 47.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "E5": { + "depth": 14.95, + "x": 50.38, + "y": 38.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "F5": { + "depth": 14.95, + "x": 50.38, + "y": 29.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "G5": { + "depth": 14.95, + "x": 50.38, + "y": 20.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "H5": { + "depth": 14.95, + "x": 50.38, + "y": 11.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "A6": { + "depth": 14.95, + "x": 59.38, + "y": 74.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "B6": { + "depth": 14.95, + "x": 59.38, + "y": 65.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "C6": { + "depth": 14.95, + "x": 59.38, + "y": 56.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "D6": { + "depth": 14.95, + "x": 59.38, + "y": 47.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "E6": { + "depth": 14.95, + "x": 59.38, + "y": 38.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "F6": { + "depth": 14.95, + "x": 59.38, + "y": 29.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "G6": { + "depth": 14.95, + "x": 59.38, + "y": 20.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "H6": { + "depth": 14.95, + "x": 59.38, + "y": 11.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "A7": { + "depth": 14.95, + "x": 68.38, + "y": 74.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "B7": { + "depth": 14.95, + "x": 68.38, + "y": 65.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "C7": { + "depth": 14.95, + "x": 68.38, + "y": 56.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "D7": { + "depth": 14.95, + "x": 68.38, + "y": 47.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "E7": { + "depth": 14.95, + "x": 68.38, + "y": 38.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "F7": { + "depth": 14.95, + "x": 68.38, + "y": 29.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "G7": { + "depth": 14.95, + "x": 68.38, + "y": 20.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "H7": { + "depth": 14.95, + "x": 68.38, + "y": 11.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "A8": { + "depth": 14.95, + "x": 77.38, + "y": 74.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "B8": { + "depth": 14.95, + "x": 77.38, + "y": 65.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "C8": { + "depth": 14.95, + "x": 77.38, + "y": 56.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "D8": { + "depth": 14.95, + "x": 77.38, + "y": 47.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "E8": { + "depth": 14.95, + "x": 77.38, + "y": 38.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "F8": { + "depth": 14.95, + "x": 77.38, + "y": 29.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "G8": { + "depth": 14.95, + "x": 77.38, + "y": 20.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "H8": { + "depth": 14.95, + "x": 77.38, + "y": 11.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "A9": { + "depth": 14.95, + "x": 86.38, + "y": 74.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "B9": { + "depth": 14.95, + "x": 86.38, + "y": 65.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "C9": { + "depth": 14.95, + "x": 86.38, + "y": 56.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "D9": { + "depth": 14.95, + "x": 86.38, + "y": 47.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "E9": { + "depth": 14.95, + "x": 86.38, + "y": 38.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "F9": { + "depth": 14.95, + "x": 86.38, + "y": 29.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "G9": { + "depth": 14.95, + "x": 86.38, + "y": 20.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "H9": { + "depth": 14.95, + "x": 86.38, + "y": 11.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "A10": { + "depth": 14.95, + "x": 95.38, + "y": 74.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "B10": { + "depth": 14.95, + "x": 95.38, + "y": 65.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "C10": { + "depth": 14.95, + "x": 95.38, + "y": 56.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "D10": { + "depth": 14.95, + "x": 95.38, + "y": 47.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "E10": { + "depth": 14.95, + "x": 95.38, + "y": 38.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "F10": { + "depth": 14.95, + "x": 95.38, + "y": 29.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "G10": { + "depth": 14.95, + "x": 95.38, + "y": 20.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "H10": { + "depth": 14.95, + "x": 95.38, + "y": 11.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "A11": { + "depth": 14.95, + "x": 104.38, + "y": 74.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "B11": { + "depth": 14.95, + "x": 104.38, + "y": 65.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "C11": { + "depth": 14.95, + "x": 104.38, + "y": 56.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "D11": { + "depth": 14.95, + "x": 104.38, + "y": 47.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "E11": { + "depth": 14.95, + "x": 104.38, + "y": 38.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "F11": { + "depth": 14.95, + "x": 104.38, + "y": 29.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "G11": { + "depth": 14.95, + "x": 104.38, + "y": 20.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "H11": { + "depth": 14.95, + "x": 104.38, + "y": 11.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "A12": { + "depth": 14.95, + "x": 113.38, + "y": 74.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "B12": { + "depth": 14.95, + "x": 113.38, + "y": 65.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "C12": { + "depth": 14.95, + "x": 113.38, + "y": 56.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "D12": { + "depth": 14.95, + "x": 113.38, + "y": 47.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "E12": { + "depth": 14.95, + "x": 113.38, + "y": 38.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "F12": { + "depth": 14.95, + "x": 113.38, + "y": 29.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "G12": { + "depth": 14.95, + "x": 113.38, + "y": 20.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + }, + "H12": { + "depth": 14.95, + "x": 113.38, + "y": 11.24, + "z": 1.05, + "totalLiquidVolume": 200, + "diameter": 5.5, + "shape": "circular" + } + }, + "groups": [ + { + "wells": [ + "A1", + "B1", + "C1", + "D1", + "E1", + "F1", + "G1", + "H1", + "A2", + "B2", + "C2", + "D2", + "E2", + "F2", + "G2", + "H2", + "A3", + "B3", + "C3", + "D3", + "E3", + "F3", + "G3", + "H3", + "A4", + "B4", + "C4", + "D4", + "E4", + "F4", + "G4", + "H4", + "A5", + "B5", + "C5", + "D5", + "E5", + "F5", + "G5", + "H5", + "A6", + "B6", + "C6", + "D6", + "E6", + "F6", + "G6", + "H6", + "A7", + "B7", + "C7", + "D7", + "E7", + "F7", + "G7", + "H7", + "A8", + "B8", + "C8", + "D8", + "E8", + "F8", + "G8", + "H8", + "A9", + "B9", + "C9", + "D9", + "E9", + "F9", + "G9", + "H9", + "A10", + "B10", + "C10", + "D10", + "E10", + "F10", + "G10", + "H10", + "A11", + "B11", + "C11", + "D11", + "E11", + "F11", + "G11", + "H11", + "A12", + "B12", + "C12", + "D12", + "E12", + "F12", + "G12", + "H12" + ], + "metadata": { "wellBottomShape": "v" } + } + ], + "allowedRoles": [], + "stackingOffsetWithLabware": { + "opentrons_96_pcr_adapter": { "x": 0, "y": 0, "z": 10.95 }, + "opentrons_96_well_aluminum_block": { "x": 0, "y": 0, "z": 11.91 } + }, + "stackingOffsetWithModule": { + "magneticBlockV1": { "x": 0, "y": 0, "z": 3.54 }, + "thermocyclerModuleV2": { "x": 0, "y": 0, "z": 10.7 } + }, + "gripperOffsets": {}, + "gripHeightFromLabwareBottom": 10.0, + "gripForce": 15.0 + } + }, + "startedAt": "2024-06-12T17:21:56.900184+00:00", + "completedAt": "2024-06-12T17:21:56.900240+00:00", + "notes": [] + }, + { + "id": "8632c181-e545-42a6-8379-9f1feb0dc46f", + "createdAt": "2024-06-12T17:21:56.900318+00:00", + "commandType": "loadLabware", + "key": "eba272e9-3eed-46bb-91aa-d1aee8da58da", + "status": "succeeded", + "params": { + "location": { "addressableAreaName": "A4" }, + "loadName": "axygen_1_reservoir_90ml", + "namespace": "opentrons", + "version": 1, + "labwareId": "8bacda22-9e05-45e8-bef4-cc04414a204f:opentrons/axygen_1_reservoir_90ml/1", + "displayName": "Axygen 1 Well Reservoir 90 mL" + }, + "result": { + "labwareId": "8bacda22-9e05-45e8-bef4-cc04414a204f:opentrons/axygen_1_reservoir_90ml/1", + "definition": { + "schemaVersion": 2, + "version": 1, + "namespace": "opentrons", + "metadata": { + "displayName": "Axygen 1 Well Reservoir 90 mL", + "displayCategory": "reservoir", + "displayVolumeUnits": "mL", + "tags": [] + }, + "brand": { + "brand": "Axygen", + "brandId": ["RES-SW1-LP"], + "links": [ + "https://ecatalog.corning.com/life-sciences/b2c/US/en/Genomics-%26-Molecular-Biology/Automation-Consumables/Automation-Reservoirs/Axygen%C2%AE-Reagent-Reservoirs/p/RES-SW1-LP?clear=true" + ] + }, + "parameters": { + "format": "trough", + "quirks": ["centerMultichannelOnWells", "touchTipDisabled"], + "isTiprack": false, + "loadName": "axygen_1_reservoir_90ml", + "isMagneticModuleCompatible": false + }, + "ordering": [["A1"]], + "cornerOffsetFromSlot": { "x": 0, "y": 0, "z": 0 }, + "dimensions": { + "yDimension": 85.47, + "zDimension": 19.05, + "xDimension": 127.76 + }, + "wells": { + "A1": { + "depth": 12.42, + "x": 63.88, + "y": 42.735, + "z": 6.63, + "totalLiquidVolume": 90000, + "xDimension": 106.76, + "yDimension": 70.52, + "shape": "rectangular" + } + }, + "groups": [ + { "wells": ["A1"], "metadata": { "wellBottomShape": "flat" } } + ], + "allowedRoles": [], + "stackingOffsetWithLabware": {}, + "stackingOffsetWithModule": {}, + "gripperOffsets": {} + } + }, + "startedAt": "2024-06-12T17:21:56.900343+00:00", + "completedAt": "2024-06-12T17:21:56.900400+00:00", + "notes": [] + }, + { + "id": "30aea4e1-6750-4933-9937-525cf52352fc", + "createdAt": "2024-06-12T17:21:56.900502+00:00", + "commandType": "loadLiquid", + "key": "45d432f8-581b-4272-9813-e73b9168a0ad", + "status": "succeeded", + "params": { + "liquidId": "1", + "labwareId": "54370838-4fca-4a14-b88a-7840e4903649:opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "volumeByWell": { + "A1": 100.0, + "B1": 100.0, + "C1": 100.0, + "D1": 100.0, + "E1": 100.0, + "F1": 100.0, + "G1": 100.0, + "H1": 100.0 + } + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.900534+00:00", + "completedAt": "2024-06-12T17:21:56.900568+00:00", + "notes": [] + }, + { + "id": "efe0a627-243f-4144-9eb4-6653b7592119", + "createdAt": "2024-06-12T17:21:56.900654+00:00", + "commandType": "loadLiquid", + "key": "7ec93f2a-3d22-4d30-b37a-e9f0d41a1847", + "status": "succeeded", + "params": { + "liquidId": "0", + "labwareId": "8bacda22-9e05-45e8-bef4-cc04414a204f:opentrons/axygen_1_reservoir_90ml/1", + "volumeByWell": { "A1": 10000.0 } + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.900680+00:00", + "completedAt": "2024-06-12T17:21:56.900703+00:00", + "notes": [] + }, + { + "id": "b6461e22-2c6c-4869-8b12-3818bd259f7f", + "createdAt": "2024-06-12T17:21:56.900769+00:00", + "commandType": "thermocycler/openLid", + "key": "ba1731c6-2906-4987-b948-ea1931ad3e64", + "status": "succeeded", + "params": { + "moduleId": "fd6da9f1-d63b-414b-929e-c646b64790e9:thermocyclerModuleType" + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.900799+00:00", + "completedAt": "2024-06-12T17:21:56.900827+00:00", + "notes": [] + }, + { + "id": "60097671-da13-4e65-8a39-b56ee46eaeaf", + "createdAt": "2024-06-12T17:21:56.900923+00:00", + "commandType": "moveLabware", + "key": "134cdae8-8ba1-45e4-98d7-cb931358eb01", + "status": "succeeded", + "params": { + "labwareId": "8bacda22-9e05-45e8-bef4-cc04414a204f:opentrons/axygen_1_reservoir_90ml/1", + "newLocation": { "slotName": "C1" }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.900952+00:00", + "completedAt": "2024-06-12T17:21:56.901073+00:00", + "notes": [] + }, + { + "id": "9dcd7b3e-c893-4509-b7d3-29915655a3d6", + "createdAt": "2024-06-12T17:21:56.901207+00:00", + "commandType": "pickUpTip", + "key": "6a5f30cc-8bea-4899-b058-7bf2095efe86", + "status": "succeeded", + "params": { + "labwareId": "f2d371ea-5146-4c89-8200-9c056a7f321a:opentrons/opentrons_flex_96_tiprack_1000ul/1", + "wellName": "A1", + "wellLocation": { + "origin": "top", + "offset": { "x": 0, "y": 0, "z": 0 } + }, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 178.38, "y": 181.38, "z": 99.0 }, + "tipVolume": 1000.0, + "tipLength": 85.94999999999999, + "tipDiameter": 5.47 + }, + "startedAt": "2024-06-12T17:21:56.901247+00:00", + "completedAt": "2024-06-12T17:21:56.901658+00:00", + "notes": [] + }, + { + "id": "14bca710-bce8-48ec-adf2-2667fbb7a84e", + "createdAt": "2024-06-12T17:21:56.901839+00:00", + "commandType": "aspirate", + "key": "71fc15e9-ad19-4c77-a32f-abba4ea5e6f9", + "status": "succeeded", + "params": { + "labwareId": "8bacda22-9e05-45e8-bef4-cc04414a204f:opentrons/axygen_1_reservoir_90ml/1", + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 63.88, "y": 149.735, "z": 7.63 }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.901881+00:00", + "completedAt": "2024-06-12T17:21:56.902199+00:00", + "notes": [] + }, + { + "id": "d96a443f-7246-4666-81b5-fa1ffa345421", + "createdAt": "2024-06-12T17:21:56.902289+00:00", + "commandType": "dispense", + "key": "a94a08b1-ed23-4c91-a853-27192da2aa70", + "status": "succeeded", + "params": { + "labwareId": "54370838-4fca-4a14-b88a-7840e4903649:opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { + "x": -5.624999999999998, + "y": 356.2, + "z": 2.3100000000000014 + }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.902318+00:00", + "completedAt": "2024-06-12T17:21:56.902634+00:00", + "notes": [] + }, + { + "id": "6f9b30ba-63ea-4528-9a5b-c03886bc1126", + "createdAt": "2024-06-12T17:21:56.902719+00:00", + "commandType": "moveToAddressableArea", + "key": "9f8c952b-88e2-4a6d-b6a2-e943f9b032e0", + "status": "succeeded", + "params": { + "forceDirect": false, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc", + "addressableAreaName": "1ChannelWasteChute", + "offset": { "x": 0.0, "y": 0.0, "z": 0.0 }, + "stayAtHighestPossibleZ": false + }, + "result": { "position": { "x": 392.0, "y": 36.0, "z": 114.5 } }, + "startedAt": "2024-06-12T17:21:56.902754+00:00", + "completedAt": "2024-06-12T17:21:56.903132+00:00", + "notes": [] + }, + { + "id": "d9de157e-fad0-4ec5-950a-c0a96d1406de", + "createdAt": "2024-06-12T17:21:56.903240+00:00", + "commandType": "dropTipInPlace", + "key": "734f7c4e-be2c-4a45-ae26-d81fb6b58729", + "status": "succeeded", + "params": { "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.903270+00:00", + "completedAt": "2024-06-12T17:21:56.903297+00:00", + "notes": [] + }, + { + "id": "028f033a-64de-4e23-bb81-2de8a3c7e1e2", + "createdAt": "2024-06-12T17:21:56.903385+00:00", + "commandType": "pickUpTip", + "key": "e3f54bb0-ef58-4e56-ad44-1dc944d2ebd8", + "status": "succeeded", + "params": { + "labwareId": "f2d371ea-5146-4c89-8200-9c056a7f321a:opentrons/opentrons_flex_96_tiprack_1000ul/1", + "wellName": "B1", + "wellLocation": { + "origin": "top", + "offset": { "x": 0, "y": 0, "z": 0 } + }, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 178.38, "y": 172.38, "z": 99.0 }, + "tipVolume": 1000.0, + "tipLength": 85.94999999999999, + "tipDiameter": 5.47 + }, + "startedAt": "2024-06-12T17:21:56.903411+00:00", + "completedAt": "2024-06-12T17:21:56.903695+00:00", + "notes": [] + }, + { + "id": "37b9334c-3e0c-47dd-8d64-f0ae37fa243d", + "createdAt": "2024-06-12T17:21:56.903769+00:00", + "commandType": "aspirate", + "key": "d5dee037-06a2-4f63-a5dd-08f285db802f", + "status": "succeeded", + "params": { + "labwareId": "8bacda22-9e05-45e8-bef4-cc04414a204f:opentrons/axygen_1_reservoir_90ml/1", + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 63.88, "y": 149.735, "z": 7.63 }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.903798+00:00", + "completedAt": "2024-06-12T17:21:56.904073+00:00", + "notes": [] + }, + { + "id": "e3ccca85-cb2b-4a62-be22-f5d2d14c1d65", + "createdAt": "2024-06-12T17:21:56.904141+00:00", + "commandType": "dispense", + "key": "db77cb48-9d63-4eb9-bac9-82c7137c7940", + "status": "succeeded", + "params": { + "labwareId": "54370838-4fca-4a14-b88a-7840e4903649:opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "wellName": "B1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { + "x": -5.624999999999998, + "y": 347.2, + "z": 2.3100000000000014 + }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.904171+00:00", + "completedAt": "2024-06-12T17:21:56.904472+00:00", + "notes": [] + }, + { + "id": "22dd3872-7d25-4673-bbe1-e30b33388525", + "createdAt": "2024-06-12T17:21:56.904542+00:00", + "commandType": "moveToAddressableArea", + "key": "c4a205b9-6a31-4993-a7dd-de84e3c40fab", + "status": "succeeded", + "params": { + "forceDirect": false, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc", + "addressableAreaName": "1ChannelWasteChute", + "offset": { "x": 0.0, "y": 0.0, "z": 0.0 }, + "stayAtHighestPossibleZ": false + }, + "result": { "position": { "x": 392.0, "y": 36.0, "z": 114.5 } }, + "startedAt": "2024-06-12T17:21:56.904571+00:00", + "completedAt": "2024-06-12T17:21:56.904811+00:00", + "notes": [] + }, + { + "id": "e034f4a1-ec53-46fb-8f99-a23d97641764", + "createdAt": "2024-06-12T17:21:56.904883+00:00", + "commandType": "dropTipInPlace", + "key": "c1a58bc4-c922-4989-8259-3a011cb6548e", + "status": "succeeded", + "params": { "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.904912+00:00", + "completedAt": "2024-06-12T17:21:56.904934+00:00", + "notes": [] + }, + { + "id": "9846e62c-626b-4fdc-a6dd-419841b0df4b", + "createdAt": "2024-06-12T17:21:56.905004+00:00", + "commandType": "pickUpTip", + "key": "4660a8b7-c24f-4cbd-b5f7-0fff091af818", + "status": "succeeded", + "params": { + "labwareId": "f2d371ea-5146-4c89-8200-9c056a7f321a:opentrons/opentrons_flex_96_tiprack_1000ul/1", + "wellName": "C1", + "wellLocation": { + "origin": "top", + "offset": { "x": 0, "y": 0, "z": 0 } + }, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 178.38, "y": 163.38, "z": 99.0 }, + "tipVolume": 1000.0, + "tipLength": 85.94999999999999, + "tipDiameter": 5.47 + }, + "startedAt": "2024-06-12T17:21:56.905029+00:00", + "completedAt": "2024-06-12T17:21:56.905304+00:00", + "notes": [] + }, + { + "id": "cb7b007b-eebc-441b-b4be-8be684988eaf", + "createdAt": "2024-06-12T17:21:56.905374+00:00", + "commandType": "aspirate", + "key": "9ac1cb1d-2876-4816-87bc-bcbeb3d0cc45", + "status": "succeeded", + "params": { + "labwareId": "8bacda22-9e05-45e8-bef4-cc04414a204f:opentrons/axygen_1_reservoir_90ml/1", + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 63.88, "y": 149.735, "z": 7.63 }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.905402+00:00", + "completedAt": "2024-06-12T17:21:56.905662+00:00", + "notes": [] + }, + { + "id": "9962d0e7-e491-4515-b243-35322e0c263c", + "createdAt": "2024-06-12T17:21:56.905725+00:00", + "commandType": "dispense", + "key": "1e8856de-95c7-483f-bf9a-a8a08dbd51b5", + "status": "succeeded", + "params": { + "labwareId": "54370838-4fca-4a14-b88a-7840e4903649:opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "wellName": "C1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { + "x": -5.624999999999998, + "y": 338.2, + "z": 2.3100000000000014 + }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.905754+00:00", + "completedAt": "2024-06-12T17:21:56.906051+00:00", + "notes": [] + }, + { + "id": "9167fce8-9c39-40d2-af5c-635f0b356428", + "createdAt": "2024-06-12T17:21:56.906114+00:00", + "commandType": "moveToAddressableArea", + "key": "df45d90b-b122-4a73-8166-7c36cb4b1739", + "status": "succeeded", + "params": { + "forceDirect": false, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc", + "addressableAreaName": "1ChannelWasteChute", + "offset": { "x": 0.0, "y": 0.0, "z": 0.0 }, + "stayAtHighestPossibleZ": false + }, + "result": { "position": { "x": 392.0, "y": 36.0, "z": 114.5 } }, + "startedAt": "2024-06-12T17:21:56.906142+00:00", + "completedAt": "2024-06-12T17:21:56.906377+00:00", + "notes": [] + }, + { + "id": "8bc60fba-40d9-4693-b7e0-0a4f5f1e9137", + "createdAt": "2024-06-12T17:21:56.906448+00:00", + "commandType": "dropTipInPlace", + "key": "893249ff-853b-4294-bd2c-12da0e5cb8af", + "status": "succeeded", + "params": { "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.906475+00:00", + "completedAt": "2024-06-12T17:21:56.906496+00:00", + "notes": [] + }, + { + "id": "837a423d-9154-44f1-b872-9637498b8688", + "createdAt": "2024-06-12T17:21:56.906563+00:00", + "commandType": "pickUpTip", + "key": "2e4913f4-1f2e-4039-964b-ca6f8905e551", + "status": "succeeded", + "params": { + "labwareId": "f2d371ea-5146-4c89-8200-9c056a7f321a:opentrons/opentrons_flex_96_tiprack_1000ul/1", + "wellName": "D1", + "wellLocation": { + "origin": "top", + "offset": { "x": 0, "y": 0, "z": 0 } + }, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 178.38, "y": 154.38, "z": 99.0 }, + "tipVolume": 1000.0, + "tipLength": 85.94999999999999, + "tipDiameter": 5.47 + }, + "startedAt": "2024-06-12T17:21:56.906589+00:00", + "completedAt": "2024-06-12T17:21:56.906909+00:00", + "notes": [] + }, + { + "id": "cfe2c27a-9840-443e-80c4-daf21ab4fc81", + "createdAt": "2024-06-12T17:21:56.907016+00:00", + "commandType": "aspirate", + "key": "bd2ac396-b44d-41a8-b050-ff8ab4a25575", + "status": "succeeded", + "params": { + "labwareId": "8bacda22-9e05-45e8-bef4-cc04414a204f:opentrons/axygen_1_reservoir_90ml/1", + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 63.88, "y": 149.735, "z": 7.63 }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.907054+00:00", + "completedAt": "2024-06-12T17:21:56.907338+00:00", + "notes": [] + }, + { + "id": "561e682c-adfa-4317-8aa0-190d58bca085", + "createdAt": "2024-06-12T17:21:56.907406+00:00", + "commandType": "dispense", + "key": "df68ab20-61c0-4077-bf0e-b1ef2997251a", + "status": "succeeded", + "params": { + "labwareId": "54370838-4fca-4a14-b88a-7840e4903649:opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "wellName": "D1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { + "x": -5.624999999999998, + "y": 329.2, + "z": 2.3100000000000014 + }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.907432+00:00", + "completedAt": "2024-06-12T17:21:56.907727+00:00", + "notes": [] + }, + { + "id": "f23327d3-7e6f-44df-9917-73e9afe63ffc", + "createdAt": "2024-06-12T17:21:56.907789+00:00", + "commandType": "moveToAddressableArea", + "key": "4b7f1a58-2bf5-45e8-a312-e165130f208c", + "status": "succeeded", + "params": { + "forceDirect": false, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc", + "addressableAreaName": "1ChannelWasteChute", + "offset": { "x": 0.0, "y": 0.0, "z": 0.0 }, + "stayAtHighestPossibleZ": false + }, + "result": { "position": { "x": 392.0, "y": 36.0, "z": 114.5 } }, + "startedAt": "2024-06-12T17:21:56.907816+00:00", + "completedAt": "2024-06-12T17:21:56.908053+00:00", + "notes": [] + }, + { + "id": "1d3981ee-2c87-4a95-b81c-1c7213809f07", + "createdAt": "2024-06-12T17:21:56.908118+00:00", + "commandType": "dropTipInPlace", + "key": "2fc06e3a-f20d-47b9-ac7f-0a062b45beeb", + "status": "succeeded", + "params": { "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.908144+00:00", + "completedAt": "2024-06-12T17:21:56.908164+00:00", + "notes": [] + }, + { + "id": "d9bfc9d0-2c6f-490d-900e-cf1c6d5b8a25", + "createdAt": "2024-06-12T17:21:56.908230+00:00", + "commandType": "pickUpTip", + "key": "9b4955da-0d09-40da-83b2-6c398dcf5e6e", + "status": "succeeded", + "params": { + "labwareId": "f2d371ea-5146-4c89-8200-9c056a7f321a:opentrons/opentrons_flex_96_tiprack_1000ul/1", + "wellName": "E1", + "wellLocation": { + "origin": "top", + "offset": { "x": 0, "y": 0, "z": 0 } + }, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 178.38, "y": 145.38, "z": 99.0 }, + "tipVolume": 1000.0, + "tipLength": 85.94999999999999, + "tipDiameter": 5.47 + }, + "startedAt": "2024-06-12T17:21:56.908255+00:00", + "completedAt": "2024-06-12T17:21:56.908508+00:00", + "notes": [] + }, + { + "id": "b94fd309-ceea-4f83-a40a-86be60a5fb75", + "createdAt": "2024-06-12T17:21:56.908576+00:00", + "commandType": "aspirate", + "key": "05a4a082-6381-4107-bb26-0e64351d3263", + "status": "succeeded", + "params": { + "labwareId": "8bacda22-9e05-45e8-bef4-cc04414a204f:opentrons/axygen_1_reservoir_90ml/1", + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 63.88, "y": 149.735, "z": 7.63 }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.908601+00:00", + "completedAt": "2024-06-12T17:21:56.908860+00:00", + "notes": [] + }, + { + "id": "614b8fb5-7979-4e01-9616-525906ed8b2a", + "createdAt": "2024-06-12T17:21:56.908927+00:00", + "commandType": "dispense", + "key": "a494e205-1cf5-4718-b5f0-43fe74c962bc", + "status": "succeeded", + "params": { + "labwareId": "54370838-4fca-4a14-b88a-7840e4903649:opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "wellName": "E1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { + "x": -5.624999999999998, + "y": 320.2, + "z": 2.3100000000000014 + }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.908956+00:00", + "completedAt": "2024-06-12T17:21:56.909249+00:00", + "notes": [] + }, + { + "id": "40abff26-69b1-4910-8d89-1cd97c3eb39a", + "createdAt": "2024-06-12T17:21:56.909312+00:00", + "commandType": "moveToAddressableArea", + "key": "e4cf4c42-d1c3-40e7-9848-3e02e01250a8", + "status": "succeeded", + "params": { + "forceDirect": false, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc", + "addressableAreaName": "1ChannelWasteChute", + "offset": { "x": 0.0, "y": 0.0, "z": 0.0 }, + "stayAtHighestPossibleZ": false + }, + "result": { "position": { "x": 392.0, "y": 36.0, "z": 114.5 } }, + "startedAt": "2024-06-12T17:21:56.909338+00:00", + "completedAt": "2024-06-12T17:21:56.909575+00:00", + "notes": [] + }, + { + "id": "7d6c20be-e94a-4e87-a231-9b6a50827add", + "createdAt": "2024-06-12T17:21:56.909639+00:00", + "commandType": "dropTipInPlace", + "key": "397d6c15-97ae-4ab5-a2dc-e0fe75562d17", + "status": "succeeded", + "params": { "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.909665+00:00", + "completedAt": "2024-06-12T17:21:56.909684+00:00", + "notes": [] + }, + { + "id": "1e6e1c47-ac38-4233-8e1a-58b4524ae3cc", + "createdAt": "2024-06-12T17:21:56.909751+00:00", + "commandType": "pickUpTip", + "key": "86178307-33f6-4902-9207-51fc704d579c", + "status": "succeeded", + "params": { + "labwareId": "f2d371ea-5146-4c89-8200-9c056a7f321a:opentrons/opentrons_flex_96_tiprack_1000ul/1", + "wellName": "F1", + "wellLocation": { + "origin": "top", + "offset": { "x": 0, "y": 0, "z": 0 } + }, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 178.38, "y": 136.38, "z": 99.0 }, + "tipVolume": 1000.0, + "tipLength": 85.94999999999999, + "tipDiameter": 5.47 + }, + "startedAt": "2024-06-12T17:21:56.909776+00:00", + "completedAt": "2024-06-12T17:21:56.910096+00:00", + "notes": [] + }, + { + "id": "d19101fe-7e76-4d06-a45e-16a6037e7b7b", + "createdAt": "2024-06-12T17:21:56.910198+00:00", + "commandType": "aspirate", + "key": "f2964ad3-9dac-4566-b636-afb59de61116", + "status": "succeeded", + "params": { + "labwareId": "8bacda22-9e05-45e8-bef4-cc04414a204f:opentrons/axygen_1_reservoir_90ml/1", + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 63.88, "y": 149.735, "z": 7.63 }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.910236+00:00", + "completedAt": "2024-06-12T17:21:56.910619+00:00", + "notes": [] + }, + { + "id": "56cfc2a6-75e0-4e54-998f-a70f1ae513ce", + "createdAt": "2024-06-12T17:21:56.910690+00:00", + "commandType": "dispense", + "key": "68c9104b-3796-4ca1-9bc5-22afec8024d9", + "status": "succeeded", + "params": { + "labwareId": "54370838-4fca-4a14-b88a-7840e4903649:opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "wellName": "F1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { + "x": -5.624999999999998, + "y": 311.2, + "z": 2.3100000000000014 + }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.910718+00:00", + "completedAt": "2024-06-12T17:21:56.911011+00:00", + "notes": [] + }, + { + "id": "9c48d4ca-f623-48ef-91d6-3a551e1b80c3", + "createdAt": "2024-06-12T17:21:56.911135+00:00", + "commandType": "moveToAddressableArea", + "key": "9a10a801-1aaa-4238-89a9-c256f09deea0", + "status": "succeeded", + "params": { + "forceDirect": false, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc", + "addressableAreaName": "1ChannelWasteChute", + "offset": { "x": 0.0, "y": 0.0, "z": 0.0 }, + "stayAtHighestPossibleZ": false + }, + "result": { "position": { "x": 392.0, "y": 36.0, "z": 114.5 } }, + "startedAt": "2024-06-12T17:21:56.911164+00:00", + "completedAt": "2024-06-12T17:21:56.911590+00:00", + "notes": [] + }, + { + "id": "453fc49c-d8c6-4d7d-b30e-7d0c85e839e3", + "createdAt": "2024-06-12T17:21:56.911701+00:00", + "commandType": "dropTipInPlace", + "key": "73d1b9c9-4c1f-40a2-8932-7f0110da78dc", + "status": "succeeded", + "params": { "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.911737+00:00", + "completedAt": "2024-06-12T17:21:56.911762+00:00", + "notes": [] + }, + { + "id": "076b217b-516b-4dcd-9e52-c8b3816612b3", + "createdAt": "2024-06-12T17:21:56.911841+00:00", + "commandType": "pickUpTip", + "key": "5818e249-0b61-4f76-af80-c835a4ad0033", + "status": "succeeded", + "params": { + "labwareId": "f2d371ea-5146-4c89-8200-9c056a7f321a:opentrons/opentrons_flex_96_tiprack_1000ul/1", + "wellName": "G1", + "wellLocation": { + "origin": "top", + "offset": { "x": 0, "y": 0, "z": 0 } + }, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 178.38, "y": 127.38, "z": 99.0 }, + "tipVolume": 1000.0, + "tipLength": 85.94999999999999, + "tipDiameter": 5.47 + }, + "startedAt": "2024-06-12T17:21:56.911869+00:00", + "completedAt": "2024-06-12T17:21:56.912289+00:00", + "notes": [] + }, + { + "id": "76aea1a4-0b4f-4ecc-9833-e52849ec71f5", + "createdAt": "2024-06-12T17:21:56.912368+00:00", + "commandType": "aspirate", + "key": "38df8344-789d-4490-bd8a-cbe9121b2692", + "status": "succeeded", + "params": { + "labwareId": "8bacda22-9e05-45e8-bef4-cc04414a204f:opentrons/axygen_1_reservoir_90ml/1", + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 63.88, "y": 149.735, "z": 7.63 }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.912396+00:00", + "completedAt": "2024-06-12T17:21:56.912745+00:00", + "notes": [] + }, + { + "id": "4ade0517-70d3-4003-8c19-8a3f0ab36d58", + "createdAt": "2024-06-12T17:21:56.912846+00:00", + "commandType": "dispense", + "key": "13593038-b554-447e-9963-0f3666ccd11a", + "status": "succeeded", + "params": { + "labwareId": "54370838-4fca-4a14-b88a-7840e4903649:opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "wellName": "G1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { + "x": -5.624999999999998, + "y": 302.2, + "z": 2.3100000000000014 + }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.912881+00:00", + "completedAt": "2024-06-12T17:21:56.913205+00:00", + "notes": [] + }, + { + "id": "837623e0-a69e-4b04-bba9-2e9668edfc8e", + "createdAt": "2024-06-12T17:21:56.913278+00:00", + "commandType": "moveToAddressableArea", + "key": "361985e0-7e23-4651-b0ed-5277cb5f1bec", + "status": "succeeded", + "params": { + "forceDirect": false, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc", + "addressableAreaName": "1ChannelWasteChute", + "offset": { "x": 0.0, "y": 0.0, "z": 0.0 }, + "stayAtHighestPossibleZ": false + }, + "result": { "position": { "x": 392.0, "y": 36.0, "z": 114.5 } }, + "startedAt": "2024-06-12T17:21:56.913306+00:00", + "completedAt": "2024-06-12T17:21:56.913538+00:00", + "notes": [] + }, + { + "id": "375f3fae-d802-437b-92ea-48dcbb0c42b7", + "createdAt": "2024-06-12T17:21:56.913608+00:00", + "commandType": "dropTipInPlace", + "key": "0d1c0aa2-d5f6-45d9-9341-bc623c07f366", + "status": "succeeded", + "params": { "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.913640+00:00", + "completedAt": "2024-06-12T17:21:56.913662+00:00", + "notes": [] + }, + { + "id": "43d96227-c3bb-475d-a7f9-f53d28c9e6f6", + "createdAt": "2024-06-12T17:21:56.913730+00:00", + "commandType": "pickUpTip", + "key": "ef384b08-03fd-4ec1-8ea9-f7741ac9050e", + "status": "succeeded", + "params": { + "labwareId": "f2d371ea-5146-4c89-8200-9c056a7f321a:opentrons/opentrons_flex_96_tiprack_1000ul/1", + "wellName": "H1", + "wellLocation": { + "origin": "top", + "offset": { "x": 0, "y": 0, "z": 0 } + }, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 178.38, "y": 118.38, "z": 99.0 }, + "tipVolume": 1000.0, + "tipLength": 85.94999999999999, + "tipDiameter": 5.47 + }, + "startedAt": "2024-06-12T17:21:56.913755+00:00", + "completedAt": "2024-06-12T17:21:56.914009+00:00", + "notes": [] + }, + { + "id": "00a7bfbe-fe2e-41b4-ab1c-21f12b39fe5c", + "createdAt": "2024-06-12T17:21:56.914082+00:00", + "commandType": "aspirate", + "key": "29bcc74a-cbba-4d19-9150-889378a34530", + "status": "succeeded", + "params": { + "labwareId": "8bacda22-9e05-45e8-bef4-cc04414a204f:opentrons/axygen_1_reservoir_90ml/1", + "wellName": "A1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { "x": 63.88, "y": 149.735, "z": 7.63 }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.914107+00:00", + "completedAt": "2024-06-12T17:21:56.914395+00:00", + "notes": [] + }, + { + "id": "56e93ce7-7974-4471-ac60-a02b05f279da", + "createdAt": "2024-06-12T17:21:56.914488+00:00", + "commandType": "dispense", + "key": "e1f51c21-1522-4538-af60-b97dc37d7b9a", + "status": "succeeded", + "params": { + "labwareId": "54370838-4fca-4a14-b88a-7840e4903649:opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "wellName": "H1", + "wellLocation": { + "origin": "bottom", + "offset": { "x": 0.0, "y": 0.0, "z": 1.0 } + }, + "flowRate": 716.0, + "volume": 100.0, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" + }, + "result": { + "position": { + "x": -5.624999999999998, + "y": 293.2, + "z": 2.3100000000000014 + }, + "volume": 100.0 + }, + "startedAt": "2024-06-12T17:21:56.914523+00:00", + "completedAt": "2024-06-12T17:21:56.914832+00:00", + "notes": [] + }, + { + "id": "d6c42331-c874-4b92-ad6e-148493aab2f3", + "createdAt": "2024-06-12T17:21:56.914898+00:00", + "commandType": "moveToAddressableArea", + "key": "93516cec-406e-41e8-8c4c-9b2b145509f7", + "status": "succeeded", + "params": { + "forceDirect": false, + "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc", + "addressableAreaName": "1ChannelWasteChute", + "offset": { "x": 0.0, "y": 0.0, "z": 0.0 }, + "stayAtHighestPossibleZ": false + }, + "result": { "position": { "x": 392.0, "y": 36.0, "z": 114.5 } }, + "startedAt": "2024-06-12T17:21:56.914924+00:00", + "completedAt": "2024-06-12T17:21:56.915153+00:00", + "notes": [] + }, + { + "id": "694e494a-6df4-4780-9547-e09f902be8bf", + "createdAt": "2024-06-12T17:21:56.915216+00:00", + "commandType": "dropTipInPlace", + "key": "d9a0a1d2-f813-488e-a28a-daae69cbc072", + "status": "succeeded", + "params": { "pipetteId": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc" }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.915242+00:00", + "completedAt": "2024-06-12T17:21:56.915269+00:00", + "notes": [] + }, + { + "id": "c28f4796-8853-4438-958f-84a85a120cf1", + "createdAt": "2024-06-12T17:21:56.915340+00:00", + "commandType": "thermocycler/closeLid", + "key": "6c34d1f1-bfeb-46d9-9669-c9b71732b6ab", + "status": "succeeded", + "params": { + "moduleId": "fd6da9f1-d63b-414b-929e-c646b64790e9:thermocyclerModuleType" + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.915370+00:00", + "completedAt": "2024-06-12T17:21:56.915396+00:00", + "notes": [] + }, + { + "id": "70faac6c-d96a-4d66-9361-25de74162da6", + "createdAt": "2024-06-12T17:21:56.915484+00:00", + "commandType": "thermocycler/setTargetBlockTemperature", + "key": "5ec65b6a-2b1c-4f8c-961f-c6e0ee700b49", + "status": "succeeded", + "params": { + "moduleId": "fd6da9f1-d63b-414b-929e-c646b64790e9:thermocyclerModuleType", + "celsius": 40.0 + }, + "result": { "targetBlockTemperature": 40.0 }, + "startedAt": "2024-06-12T17:21:56.915517+00:00", + "completedAt": "2024-06-12T17:21:56.915543+00:00", + "notes": [] + }, + { + "id": "21ccc284-a287-4cdd-9045-cbf74b752723", + "createdAt": "2024-06-12T17:21:56.915625+00:00", + "commandType": "thermocycler/waitForBlockTemperature", + "key": "9f90e933-131f-44eb-ab12-efb152c9cb83", + "status": "succeeded", + "params": { + "moduleId": "fd6da9f1-d63b-414b-929e-c646b64790e9:thermocyclerModuleType" + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.915651+00:00", + "completedAt": "2024-06-12T17:21:56.915674+00:00", + "notes": [] + }, + { + "id": "fbfe223a-9ca6-43ae-aff5-d784e84877a1", + "createdAt": "2024-06-12T17:21:56.915765+00:00", + "commandType": "waitForDuration", + "key": "f580c50f-08bb-42c4-b4a2-2764ed2fc090", + "status": "succeeded", + "params": { "seconds": 60.0, "message": "" }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.915794+00:00", + "completedAt": "2024-06-12T17:21:56.915816+00:00", + "notes": [] + }, + { + "id": "4b76af6d-3e05-4b56-85de-a49bab41e3c7", + "createdAt": "2024-06-12T17:21:56.915900+00:00", + "commandType": "thermocycler/openLid", + "key": "f739bfc8-f438-4fa2-8d57-dc839ac29f24", + "status": "succeeded", + "params": { + "moduleId": "fd6da9f1-d63b-414b-929e-c646b64790e9:thermocyclerModuleType" + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.915925+00:00", + "completedAt": "2024-06-12T17:21:56.915947+00:00", + "notes": [] + }, + { + "id": "d678170a-be83-466e-b898-2aa00d963086", + "createdAt": "2024-06-12T17:21:56.916011+00:00", + "commandType": "thermocycler/deactivateBlock", + "key": "4561d98c-b565-48db-a7af-6bcd31520340", + "status": "succeeded", + "params": { + "moduleId": "fd6da9f1-d63b-414b-929e-c646b64790e9:thermocyclerModuleType" + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.916037+00:00", + "completedAt": "2024-06-12T17:21:56.916059+00:00", + "notes": [] + }, + { + "id": "81f0bd93-bae3-449b-904d-027fbe7d4864", + "createdAt": "2024-06-12T17:21:56.916138+00:00", + "commandType": "heaterShaker/deactivateHeater", + "key": "79dd17bf-f86a-4fe9-990a-e4e567798c87", + "status": "succeeded", + "params": { + "moduleId": "23347241-80bb-4a7e-9c91-5d9727a9e483:heaterShakerModuleType" + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.916171+00:00", + "completedAt": "2024-06-12T17:21:56.916195+00:00", + "notes": [] + }, + { + "id": "5d194127-7eb6-4ef4-a26e-bbf25ef5af7f", + "createdAt": "2024-06-12T17:21:56.916279+00:00", + "commandType": "heaterShaker/openLabwareLatch", + "key": "995a2630-7a9c-4b70-aef8-ddccb7ce26ce", + "status": "succeeded", + "params": { + "moduleId": "23347241-80bb-4a7e-9c91-5d9727a9e483:heaterShakerModuleType" + }, + "result": { "pipetteRetracted": true }, + "startedAt": "2024-06-12T17:21:56.916307+00:00", + "completedAt": "2024-06-12T17:21:56.916335+00:00", + "notes": [] + }, + { + "id": "e0a7f92e-affe-481c-8749-93909ddbfb3b", + "createdAt": "2024-06-12T17:21:56.916454+00:00", + "commandType": "moveLabware", + "key": "9d1035a4-617f-4fcc-a7a3-1b7a8c52b4c6", + "status": "succeeded", + "params": { + "labwareId": "54370838-4fca-4a14-b88a-7840e4903649:opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "newLocation": { + "labwareId": "7c4d59fa-0e50-442f-adce-9e4b0c7f0b88:opentrons/opentrons_96_pcr_adapter/1" + }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.916480+00:00", + "completedAt": "2024-06-12T17:21:56.916597+00:00", + "notes": [] + }, + { + "id": "57399cf0-794d-4dfa-a38b-e5d678454f0f", + "createdAt": "2024-06-12T17:21:56.916667+00:00", + "commandType": "heaterShaker/closeLabwareLatch", + "key": "a244eacc-4cbc-48af-b54a-6c08cd534a51", + "status": "succeeded", + "params": { + "moduleId": "23347241-80bb-4a7e-9c91-5d9727a9e483:heaterShakerModuleType" + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.916694+00:00", + "completedAt": "2024-06-12T17:21:56.916715+00:00", + "notes": [] + }, + { + "id": "487eee54-9fdc-4dcd-b948-6ff860855887", + "createdAt": "2024-06-12T17:21:56.916808+00:00", + "commandType": "heaterShaker/deactivateHeater", + "key": "a6970f26-4800-4949-8592-d977df547d8b", + "status": "succeeded", + "params": { + "moduleId": "23347241-80bb-4a7e-9c91-5d9727a9e483:heaterShakerModuleType" + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.916832+00:00", + "completedAt": "2024-06-12T17:21:56.916851+00:00", + "notes": [] + }, + { + "id": "917f4e1b-d622-4602-9c13-75177b2119b2", + "createdAt": "2024-06-12T17:21:56.916913+00:00", + "commandType": "heaterShaker/setAndWaitForShakeSpeed", + "key": "ef808dac-1e14-47a1-843d-ce4ce63bdfce", + "status": "succeeded", + "params": { + "moduleId": "23347241-80bb-4a7e-9c91-5d9727a9e483:heaterShakerModuleType", + "rpm": 200.0 + }, + "result": { "pipetteRetracted": true }, + "startedAt": "2024-06-12T17:21:56.916939+00:00", + "completedAt": "2024-06-12T17:21:56.916969+00:00", + "notes": [] + }, + { + "id": "0807dfa8-3c0c-4125-964d-985c642afc34", + "createdAt": "2024-06-12T17:21:56.917053+00:00", + "commandType": "waitForDuration", + "key": "5b47f11e-0755-47d2-b844-f1363e28a54e", + "status": "succeeded", + "params": { "seconds": 60.0 }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.917077+00:00", + "completedAt": "2024-06-12T17:21:56.917095+00:00", + "notes": [] + }, + { + "id": "0a40eee2-8902-4914-86f1-af3d961e7dcd", + "createdAt": "2024-06-12T17:21:56.917156+00:00", + "commandType": "heaterShaker/deactivateShaker", + "key": "614ec8d0-8abf-4aa4-b771-23ff2bde2881", + "status": "succeeded", + "params": { + "moduleId": "23347241-80bb-4a7e-9c91-5d9727a9e483:heaterShakerModuleType" + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.917187+00:00", + "completedAt": "2024-06-12T17:21:56.917208+00:00", + "notes": [] + }, + { + "id": "ddffeaa1-9f97-4bd3-99ee-5e0d8aee28bc", + "createdAt": "2024-06-12T17:21:56.917290+00:00", + "commandType": "heaterShaker/deactivateHeater", + "key": "dbbe307e-d361-4cb9-afe7-afeab944bfce", + "status": "succeeded", + "params": { + "moduleId": "23347241-80bb-4a7e-9c91-5d9727a9e483:heaterShakerModuleType" + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.917314+00:00", + "completedAt": "2024-06-12T17:21:56.917333+00:00", + "notes": [] + }, + { + "id": "176fe5ea-4a4f-4a78-a2b9-160fdd1a7be1", + "createdAt": "2024-06-12T17:21:56.917404+00:00", + "commandType": "heaterShaker/deactivateHeater", + "key": "62f98610-cbff-4acb-ba36-a3fbb9527ba9", + "status": "succeeded", + "params": { + "moduleId": "23347241-80bb-4a7e-9c91-5d9727a9e483:heaterShakerModuleType" + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.917427+00:00", + "completedAt": "2024-06-12T17:21:56.917445+00:00", + "notes": [] + }, + { + "id": "26022284-0a3c-48ad-b864-91f7c48ea19c", + "createdAt": "2024-06-12T17:21:56.917509+00:00", + "commandType": "heaterShaker/openLabwareLatch", + "key": "81cfeab1-175f-4501-8732-1ea1bc9b528b", + "status": "succeeded", + "params": { + "moduleId": "23347241-80bb-4a7e-9c91-5d9727a9e483:heaterShakerModuleType" + }, + "result": { "pipetteRetracted": true }, + "startedAt": "2024-06-12T17:21:56.917532+00:00", + "completedAt": "2024-06-12T17:21:56.917554+00:00", + "notes": [] + }, + { + "id": "5e8711de-7231-47d1-ab55-541dd21cef48", + "createdAt": "2024-06-12T17:21:56.917627+00:00", + "commandType": "moveLabware", + "key": "279df4d0-2c87-4f01-b016-5c42d5edce96", + "status": "succeeded", + "params": { + "labwareId": "54370838-4fca-4a14-b88a-7840e4903649:opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "newLocation": { "addressableAreaName": "B4" }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.917651+00:00", + "completedAt": "2024-06-12T17:21:56.917738+00:00", + "notes": [] + }, + { + "id": "abf5283b-dc04-457e-b1ea-4dc848620954", + "createdAt": "2024-06-12T17:21:56.917841+00:00", + "commandType": "moveLabware", + "key": "f88f41dc-ddf9-4242-9ba4-21bd728ca25f", + "status": "succeeded", + "params": { + "labwareId": "f2d371ea-5146-4c89-8200-9c056a7f321a:opentrons/opentrons_flex_96_tiprack_1000ul/1", + "newLocation": { "addressableAreaName": "gripperWasteChute" }, + "strategy": "usingGripper" + }, + "result": {}, + "startedAt": "2024-06-12T17:21:56.917866+00:00", + "completedAt": "2024-06-12T17:21:56.917941+00:00", + "notes": [] + } + ], + "labware": [ + { + "id": "7c4d59fa-0e50-442f-adce-9e4b0c7f0b88:opentrons/opentrons_96_pcr_adapter/1", + "loadName": "opentrons_96_pcr_adapter", + "definitionUri": "opentrons/opentrons_96_pcr_adapter/1", + "location": { + "moduleId": "23347241-80bb-4a7e-9c91-5d9727a9e483:heaterShakerModuleType" + }, + "displayName": "Opentrons 96 PCR Heater-Shaker Adapter" + }, + { + "id": "f2d371ea-5146-4c89-8200-9c056a7f321a:opentrons/opentrons_flex_96_tiprack_1000ul/1", + "loadName": "opentrons_flex_96_tiprack_1000ul", + "definitionUri": "opentrons/opentrons_flex_96_tiprack_1000ul/1", + "location": "offDeck", + "displayName": "Opentrons Flex 96 Tip Rack 1000 \u00b5L" + }, + { + "id": "54370838-4fca-4a14-b88a-7840e4903649:opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "loadName": "opentrons_96_wellplate_200ul_pcr_full_skirt", + "definitionUri": "opentrons/opentrons_96_wellplate_200ul_pcr_full_skirt/2", + "location": { "addressableAreaName": "B4" }, + "displayName": "Opentrons Tough 96 Well Plate 200 \u00b5L PCR Full Skirt" + }, + { + "id": "8bacda22-9e05-45e8-bef4-cc04414a204f:opentrons/axygen_1_reservoir_90ml/1", + "loadName": "axygen_1_reservoir_90ml", + "definitionUri": "opentrons/axygen_1_reservoir_90ml/1", + "location": { "slotName": "C1" }, + "displayName": "Axygen 1 Well Reservoir 90 mL" + } + ], + "pipettes": [ + { + "id": "9fcd50d9-92b2-45ac-acf1-e2cf773feffc", + "pipetteName": "p1000_single_flex", + "mount": "left" + } + ], + "modules": [ + { + "id": "23347241-80bb-4a7e-9c91-5d9727a9e483:heaterShakerModuleType", + "model": "heaterShakerModuleV1", + "location": { "slotName": "D1" }, + "serialNumber": "fake-serial-number-0d06c2c1-3ee4-467d-b1cb-31ce8a260ff5" + }, + { + "id": "fd6da9f1-d63b-414b-929e-c646b64790e9:thermocyclerModuleType", + "model": "thermocyclerModuleV2", + "location": { "slotName": "B1" }, + "serialNumber": "fake-serial-number-abe43d5b-1dd7-4fa9-bf8b-b089236d5adb" + } + ], + "liquids": [ + { + "id": "0", + "displayName": "h20", + "description": "", + "displayColor": "#b925ff" + }, + { + "id": "1", + "displayName": "sample", + "description": "", + "displayColor": "#ffd600" + } + ], + "errors": [] +} diff --git a/app/src/molecules/Command/__fixtures__/index.ts b/app/src/molecules/Command/__fixtures__/index.ts index 447a935d3dc..894208e8e68 100644 --- a/app/src/molecules/Command/__fixtures__/index.ts +++ b/app/src/molecules/Command/__fixtures__/index.ts @@ -1,8 +1,8 @@ import robotSideAnalysis from './mockRobotSideAnalysis.json' -import doItAllAnalysis from './doItAllV8.json' +import doItAllAnalysis from './doItAllV10.json' import qiaseqAnalysis from './analysis_QIAseqFX24xv4_8.json' import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' -import type { CommandTextData } from '../types' +import type { CommandTextData } from '/app/local-resources/commands' export const mockRobotSideAnalysis: CompletedProtocolAnalysis = robotSideAnalysis as CompletedProtocolAnalysis export const mockDoItAllAnalysis: CompletedProtocolAnalysis = doItAllAnalysis as CompletedProtocolAnalysis diff --git a/app/src/molecules/Command/__fixtures__/mockRobotSideAnalysis.json b/app/src/molecules/Command/__fixtures__/mockRobotSideAnalysis.json index cd2bd35c802..848be62365c 100644 --- a/app/src/molecules/Command/__fixtures__/mockRobotSideAnalysis.json +++ b/app/src/molecules/Command/__fixtures__/mockRobotSideAnalysis.json @@ -71,6 +71,15 @@ "slotName": "5" }, "displayName": "NEST 1 Well Reservoir 195 mL" + }, + { + "id": "29444782-bdc8-4ad8-92fe-5e28872e85e5:opentrons/opentrons_96_flat_bottom_adapter/1", + "loadName": "opentrons_96_flat_bottom_adapter", + "definitionUri": "opentrons/opentrons_96_flat_bottom_adapter/1", + "location": { + "slotName": "2" + }, + "displayName": "Opentrons 96 Flat Bottom Adapter" } ], "modules": [ diff --git a/app/src/molecules/Command/__tests__/CommandText.test.tsx b/app/src/molecules/Command/__tests__/CommandText.test.tsx index 09856d77ba1..483e739bbcb 100644 --- a/app/src/molecules/Command/__tests__/CommandText.test.tsx +++ b/app/src/molecules/Command/__tests__/CommandText.test.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { it, expect, describe } from 'vitest' import { screen } from '@testing-library/react' @@ -7,11 +6,11 @@ import { OT2_ROBOT_TYPE, GRIPPER_WASTE_CHUTE_ADDRESSABLE_AREA, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { CommandText } from '../CommandText' import { mockCommandTextData } from '../__fixtures__' -import { getCommandTextData } from '../utils/getCommandTextData' +import { getCommandTextData } from '/app/local-resources/commands/utils' import type { AspirateInPlaceRunTimeCommand, @@ -42,6 +41,7 @@ describe('CommandText', () => { if (command != null) { renderWithProviders( { if (command != null) { renderWithProviders( { if (pushOutDispenseCommand != null) { renderWithProviders( { it('renders correct text for dispenseInPlace', () => { renderWithProviders( { if (blowoutCommand != null) { renderWithProviders( { it('renders correct text for blowOutInPlace', () => { renderWithProviders( { it('renders correct text for aspirateInPlace', () => { renderWithProviders( { if (moveToWellCommand != null) { renderWithProviders( { it('renders correct text for labware involving an addressable area slot', () => { renderWithProviders( { it('renders correct text for moveToAddressableArea for Waste Chutes', () => { renderWithProviders( { it('renders correct text for moveToAddressableArea for Fixed Trash', () => { renderWithProviders( { it('renders correct text for moveToAddressableArea for Trash Bins', () => { renderWithProviders( { it('renders correct text for moveToAddressableAreaForDropTip for Trash Bin', () => { renderWithProviders( { it('renders correct text for moveToAddressableArea for slots', () => { renderWithProviders( { renderWithProviders( { renderWithProviders( { if (command != null) { renderWithProviders( { it('renders correct text for dropTip into a labware', () => { renderWithProviders( { it('renders correct text for dropTipInPlace', () => { renderWithProviders( { />, { i18nInstance: i18n } ) - screen.getByText('Dropping tip in place') + screen.getByText('Dropping tip in D3') }) it('renders correct text for pickUpTip', () => { const command = mockCommandTextData.commands.find( @@ -436,6 +454,7 @@ describe('CommandText', () => { if (command != null) { renderWithProviders( { if (command != null) { renderWithProviders( { if (command != null) { renderWithProviders( { const loadLabwareCommand = loadLabwareCommands[0] renderWithProviders( { const loadTipRackCommand = loadLabwareCommands[2] renderWithProviders( { const loadOnModuleCommand = loadLabwareCommands[3] renderWithProviders( { ) }) it('renders correct text for loadLabware in adapter', () => { + const flatBottomAdapterCommand = mockCommandTextData.commands.find( + c => + c.commandType === 'loadLabware' && + c.params.loadName === 'opentrons_96_flat_bottom_adapter' + ) + renderWithProviders( { } ) screen.getByText( - 'Load mock displayName in Opentrons 96 Flat Bottom Adapter in Slot 2' + 'Load mock displayName on Opentrons 96 Flat Bottom Adapter in Slot 2' ) }) it('renders correct text for loadLabware off deck', () => { @@ -580,6 +611,7 @@ describe('CommandText', () => { } as LoadLabwareRunTimeCommand renderWithProviders( { if (reloadLabwareCommand != null) { renderWithProviders( { } renderWithProviders( { const mockTemp = 20 renderWithProviders( { const mockTemp = 20 renderWithProviders( { it('renders correct text for temperatureModule/waitForTemperature with no specified temp', () => { renderWithProviders( { const mockTemp = 20 renderWithProviders( { const mockTemp = 20 renderWithProviders( { const mockTemp = 20 renderWithProviders( { ] renderWithProviders( { i18nInstance: i18n, } ) - screen.getByText( - 'Thermocycler starting 2 repetitions of cycle composed of the following steps:' - ) - screen.getByText('temperature: 20°C, seconds: 10') - screen.getByText('temperature: 40°C, seconds: 30') + screen.getByText('Running thermocycler profile with 2 steps:') + screen.getByText('Temperature: 20°C, hold time: 0h 00m 10s') + screen.getByText('Temperature: 40°C, hold time: 0h 00m 30s') }) it('renders correct text for thermocycler/runProfile on ODD', () => { const mockProfileSteps = [ @@ -835,6 +874,7 @@ describe('CommandText', () => { ] renderWithProviders( { i18nInstance: i18n, } ) + screen.getByText('Running thermocycler profile with 2 steps:') + screen.getByText('Temperature: 20°C, hold time: 0h 00m 10s') + expect( + screen.queryByText('Temperature: 40°C, hold time: 0h 00m 30s') + ).not.toBeInTheDocument() + }) + it('renders correct text for thermocycler/runExtendedProfile on Desktop', () => { + const mockProfileSteps = [ + { holdSeconds: 10, celsius: 20 }, + { + repetitions: 10, + steps: [ + { holdSeconds: 15, celsius: 10 }, + { holdSeconds: 12, celsius: 11 }, + ], + }, + { holdSeconds: 30, celsius: 40 }, + { + repetitions: 9, + steps: [ + { holdSeconds: 13000, celsius: 12 }, + { holdSeconds: 14, celsius: 13 }, + ], + }, + ] + renderWithProviders( + , + { + i18nInstance: i18n, + } + ) screen.getByText( - 'Thermocycler starting 2 repetitions of cycle composed of the following steps:' + 'Running thermocycler profile with 4 total steps and cycles:' + ) + screen.getByText('Temperature: 20°C, hold time: 0h 00m 10s') + screen.getByText('10 repetitions of the following steps:') + screen.getByText('Temperature: 10°C, hold time: 0h 00m 15s') + screen.getByText('Temperature: 11°C, hold time: 0h 00m 12s') + screen.getByText('Temperature: 40°C, hold time: 0h 00m 30s') + screen.getByText('9 repetitions of the following steps:') + screen.getByText('Temperature: 12°C, hold time: 3h 36m 40s') + screen.getByText('Temperature: 13°C, hold time: 0h 00m 14s') + }) + it('renders correct text for thermocycler/runExtendedProfile on ODD', () => { + const mockProfileSteps = [ + { holdSeconds: 10, celsius: 20 }, + { + repetitions: 10, + steps: [ + { holdSeconds: 15, celsius: 10 }, + { holdSeconds: 12, celsius: 11 }, + ], + }, + { holdSeconds: 30, celsius: 40 }, + { + repetitions: 9, + steps: [ + { holdSeconds: 13, celsius: 12 }, + { holdSeconds: 14, celsius: 13 }, + ], + }, + ] + renderWithProviders( + , + { + i18nInstance: i18n, + } + ) + screen.getByText( + 'Running thermocycler profile with 4 total steps and cycles:' ) - screen.getByText('temperature: 20°C, seconds: 10') + screen.getByText('Temperature: 20°C, hold time: 0h 00m 10s') + + expect( + screen.queryByText('10 repetitions of the following steps:') + ).not.toBeInTheDocument() + expect( + screen.queryByText('Temperature: 10°C, hold time: 0h 00m 15s') + ).not.toBeInTheDocument() + expect( + screen.queryByText('Temperature: 11°C, hold time: 0h 00m 12s') + ).not.toBeInTheDocument() + expect( + screen.queryByText('Temperature: 40°C, hold time: 0h 00m 30s') + ).not.toBeInTheDocument() + expect( + screen.queryByText('9 repetitions of the following steps:') + ).not.toBeInTheDocument() + expect( + screen.queryByText('Temperature: 12°C, hold time: 0h 00m 13s') + ).not.toBeInTheDocument() expect( - screen.queryByText('temperature: 40°C, seconds: 30') + screen.queryByText('Temperature: 13°C, hold time: 0h 00m 14s') ).not.toBeInTheDocument() }) it('renders correct text for heaterShaker/setAndWaitForShakeSpeed', () => { renderWithProviders( { it('renders correct text for moveToSlot', () => { renderWithProviders( { it('renders correct text for moveRelative', () => { renderWithProviders( { it('renders correct text for moveToCoordinates', () => { renderWithProviders( { ([commandType, expectedCopy]) => { renderWithProviders( { it('renders correct text for waitForDuration', () => { renderWithProviders( { it('renders correct text for legacy pause with message', () => { renderWithProviders( { it('renders correct text for legacy pause without message', () => { renderWithProviders( { it('renders correct text for waitForResume with message', () => { renderWithProviders( { it('renders correct text for waitForResume without message', () => { renderWithProviders( { it('renders correct text for legacy delay with time', () => { renderWithProviders( { it('renders correct text for legacy delay wait for resume with message', () => { renderWithProviders( { it('renders correct text for legacy delay wait for resume without message', () => { renderWithProviders( { it('renders correct text for comment', () => { renderWithProviders( { it('renders correct text for custom command type with legacy command text', () => { renderWithProviders( { it('renders correct text for custom command type with arbitrary params', () => { renderWithProviders( { it('renders correct text for move labware manually off deck', () => { renderWithProviders( { it('renders correct text for move labware manually to module', () => { renderWithProviders( { it('renders correct text for move labware with gripper off deck', () => { renderWithProviders( { it('renders correct text for move labware with gripper to waste chute', () => { renderWithProviders( { it('renders correct text for move labware with gripper to module', () => { renderWithProviders( { if (command != null) { renderWithProviders( { if (command != null) { renderWithProviders( ): string { - const { configurationParams, pipetteId } = command.params - const pipetteName = commandTextData?.pipettes.find( - pip => pip.id === pipetteId - )?.pipetteName - - return t('configure_nozzle_layout', { - amount: configurationParams.style === 'COLUMN' ? '8' : 'all', - pipette: - pipetteName != null ? getPipetteSpecsV2(pipetteName)?.displayName : '', - }) -} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getLiquidProbeCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getLiquidProbeCommandText.ts deleted file mode 100644 index a61a4bdf2a3..00000000000 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getLiquidProbeCommandText.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { - getFinalLabwareLocation, - getLabwareDisplayLocation, - getLabwareName, -} from '../../../utils' - -import type { - LiquidProbeRunTimeCommand, - RunTimeCommand, - TryLiquidProbeRunTimeCommand, -} from '@opentrons/shared-data' -import type { HandlesCommands } from './types' -import type { TFunction } from 'i18next' - -type LiquidProbeRunTimeCommands = - | LiquidProbeRunTimeCommand - | TryLiquidProbeRunTimeCommand - -export function getLiquidProbeCommandText({ - command, - commandTextData, - t, - robotType, -}: HandlesCommands): string { - const { wellName, labwareId } = command.params - - const allPreviousCommands = commandTextData?.commands.slice( - 0, - commandTextData.commands.findIndex(c => c.id === command?.id) - ) - - const labwareLocation = - allPreviousCommands != null - ? getFinalLabwareLocation( - labwareId as string, - allPreviousCommands as RunTimeCommand[] - ) - : null - - const displayLocation = - labwareLocation != null && commandTextData != null - ? getLabwareDisplayLocation( - commandTextData, - labwareLocation, - t as TFunction, - robotType - ) - : '' - - const labware = - commandTextData != null - ? getLabwareName(commandTextData, labwareId as string) - : null - - return t('detect_liquid_presence', { - labware, - labware_location: displayLocation, - well_name: wellName, - }) -} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getLoadCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getLoadCommandText.ts deleted file mode 100644 index e28d52f3959..00000000000 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getLoadCommandText.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { - getModuleDisplayName, - getModuleType, - getOccludedSlotCountForModule, - getPipetteSpecsV2, -} from '@opentrons/shared-data' - -import { - getLabwareName, - getPipetteNameOnMount, - getModuleModel, - getModuleDisplayLocation, - getLiquidDisplayName, -} from '../../../utils' - -import type { LoadLabwareRunTimeCommand } from '@opentrons/shared-data' -import type { GetCommandText } from '..' - -export const getLoadCommandText = ({ - command, - commandTextData, - robotType, - t, -}: GetCommandText): string => { - switch (command?.commandType) { - case 'loadPipette': { - const pipetteModel = - commandTextData != null - ? getPipetteNameOnMount(commandTextData, command.params.mount) - : null - return t('load_pipette_protocol_setup', { - pipette_name: - pipetteModel != null - ? getPipetteSpecsV2(pipetteModel)?.displayName ?? '' - : '', - mount_name: command.params.mount === 'left' ? t('left') : t('right'), - }) - } - case 'loadModule': { - const occludedSlotCount = getOccludedSlotCountForModule( - getModuleType(command.params.model), - robotType - ) - return t('load_module_protocol_setup', { - count: occludedSlotCount, - module: getModuleDisplayName(command.params.model), - slot_name: command.params.location.slotName, - }) - } - case 'loadLabware': { - if ( - command.params.location !== 'offDeck' && - 'moduleId' in command.params.location - ) { - const moduleModel = - commandTextData != null - ? getModuleModel(commandTextData, command.params.location.moduleId) - : null - const moduleName = - moduleModel != null ? getModuleDisplayName(moduleModel) : '' - - return t('load_labware_info_protocol_setup', { - count: - moduleModel != null - ? getOccludedSlotCountForModule( - getModuleType(moduleModel), - robotType - ) - : 1, - labware: command.result?.definition.metadata.displayName, - slot_name: - commandTextData != null - ? getModuleDisplayLocation( - commandTextData, - command.params.location.moduleId - ) - : null, - module_name: moduleName, - }) - } else if ( - command.params.location !== 'offDeck' && - 'labwareId' in command.params.location - ) { - const labwareId = command.params.location.labwareId - const labwareName = command.result?.definition.metadata.displayName - const matchingAdapter = commandTextData?.commands.find( - (command): command is LoadLabwareRunTimeCommand => - command.commandType === 'loadLabware' && - command.result?.labwareId === labwareId - ) - const adapterName = - matchingAdapter?.result?.definition.metadata.displayName - const adapterLoc = matchingAdapter?.params.location - if (adapterLoc === 'offDeck') { - return t('load_labware_info_protocol_setup_adapter_off_deck', { - labware: labwareName, - adapter_name: adapterName, - }) - } else if (adapterLoc != null && 'slotName' in adapterLoc) { - return t('load_labware_info_protocol_setup_adapter', { - labware: labwareName, - adapter_name: adapterName, - slot_name: adapterLoc?.slotName, - }) - } else if (adapterLoc != null && 'moduleId' in adapterLoc) { - const moduleModel = - commandTextData != null - ? getModuleModel(commandTextData, adapterLoc?.moduleId ?? '') - : null - const moduleName = - moduleModel != null ? getModuleDisplayName(moduleModel) : '' - return t('load_labware_info_protocol_setup_adapter_module', { - labware: labwareName, - adapter_name: adapterName, - module_name: moduleName, - slot_name: - commandTextData != null - ? getModuleDisplayLocation( - commandTextData, - adapterLoc?.moduleId ?? '' - ) - : null, - }) - } else { - // shouldn't reach here, adapter shouldn't have location type labwareId - return '' - } - } else { - const labware = - command.result?.definition.metadata.displayName ?? - command.params.displayName - return command.params.location === 'offDeck' - ? t('load_labware_info_protocol_setup_off_deck', { labware }) - : t('load_labware_info_protocol_setup_no_module', { - labware, - slot_name: - 'addressableAreaName' in command.params.location - ? command.params.location.addressableAreaName - : command.params.location.slotName, - }) - } - } - case 'reloadLabware': { - const { labwareId } = command.params - const labware = - commandTextData != null - ? getLabwareName(commandTextData, labwareId) - : null - return t('reloading_labware', { labware }) - } - case 'loadLiquid': { - const { liquidId, labwareId } = command.params - return t('load_liquids_info_protocol_setup', { - liquid: - commandTextData != null - ? getLiquidDisplayName(commandTextData, liquidId) - : null, - labware: - commandTextData != null - ? getLabwareName(commandTextData, labwareId) - : null, - }) - } - default: { - console.warn( - 'LoadCommandText encountered a command with an unrecognized commandType: ', - command - ) - return '' - } - } -} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveLabwareCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveLabwareCommandText.ts deleted file mode 100644 index 71a0ac3e7d6..00000000000 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveLabwareCommandText.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { GRIPPER_WASTE_CHUTE_ADDRESSABLE_AREA } from '@opentrons/shared-data' - -import { - getLabwareName, - getLabwareDisplayLocation, - getFinalLabwareLocation, -} from '../../../utils' - -import type { MoveLabwareRunTimeCommand } from '@opentrons/shared-data' -import type { HandlesCommands } from './types' - -export function getMoveLabwareCommandText({ - command, - t, - commandTextData, - robotType, -}: HandlesCommands): string { - const { labwareId, newLocation, strategy } = command.params - - const allPreviousCommands = commandTextData?.commands.slice( - 0, - commandTextData.commands.findIndex(c => c.id === command.id) - ) - const oldLocation = - allPreviousCommands != null - ? getFinalLabwareLocation(labwareId, allPreviousCommands) - : null - const newDisplayLocation = - commandTextData != null - ? getLabwareDisplayLocation(commandTextData, newLocation, t, robotType) - : null - - const location = newDisplayLocation?.includes( - GRIPPER_WASTE_CHUTE_ADDRESSABLE_AREA - ) - ? 'Waste Chute' - : newDisplayLocation - - return strategy === 'usingGripper' - ? t('move_labware_using_gripper', { - labware: - commandTextData != null - ? getLabwareName(commandTextData, labwareId) - : null, - old_location: - oldLocation != null && commandTextData != null - ? getLabwareDisplayLocation( - commandTextData, - oldLocation, - t, - robotType - ) - : '', - new_location: location, - }) - : t('move_labware_manually', { - labware: - commandTextData != null - ? getLabwareName(commandTextData, labwareId) - : null, - old_location: - oldLocation != null && commandTextData != null - ? getLabwareDisplayLocation( - commandTextData, - oldLocation, - t, - robotType - ) - : '', - new_location: location, - }) -} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToAddressabelAreaForDropTipCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToAddressabelAreaForDropTipCommandText.ts deleted file mode 100644 index 5788fbbdf62..00000000000 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToAddressabelAreaForDropTipCommandText.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { getAddressableAreaDisplayName } from '../../../utils' - -import type { MoveToAddressableAreaForDropTipRunTimeCommand } from '@opentrons/shared-data/command' -import type { HandlesCommands } from './types' - -export function getMoveToAddressableAreaForDropTipCommandText({ - command, - commandTextData, - t, -}: HandlesCommands): string { - const addressableAreaDisplayName = - commandTextData != null - ? getAddressableAreaDisplayName(commandTextData, command.id, t) - : null - - return t('move_to_addressable_area_drop_tip', { - addressable_area: addressableAreaDisplayName, - }) -} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToAddressableAreaCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToAddressableAreaCommandText.ts deleted file mode 100644 index e8366120a23..00000000000 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToAddressableAreaCommandText.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { getAddressableAreaDisplayName } from '../../../utils' - -import type { MoveToAddressableAreaRunTimeCommand } from '@opentrons/shared-data/command' -import type { HandlesCommands } from './types' - -export function getMoveToAddressableAreaCommandText({ - command, - commandTextData, - t, -}: HandlesCommands): string { - const addressableAreaDisplayName = - commandTextData != null - ? getAddressableAreaDisplayName(commandTextData, command.id, t) - : null - - return t('move_to_addressable_area', { - addressable_area: addressableAreaDisplayName, - }) -} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToWellCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToWellCommandText.ts deleted file mode 100644 index 8c191f34b40..00000000000 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getMoveToWellCommandText.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - getFinalLabwareLocation, - getLabwareDisplayLocation, - getLabwareName, -} from '../../../utils' - -import type { TFunction } from 'i18next' -import type { MoveToWellRunTimeCommand } from '@opentrons/shared-data/command' -import type { HandlesCommands } from './types' - -export function getMoveToWellCommandText({ - command, - t, - commandTextData, - robotType, -}: HandlesCommands): string { - const { wellName, labwareId } = command.params - const allPreviousCommands = commandTextData?.commands.slice( - 0, - commandTextData.commands.findIndex(c => c.id === command.id) - ) - const labwareLocation = - allPreviousCommands != null - ? getFinalLabwareLocation(labwareId, allPreviousCommands) - : null - const displayLocation = - labwareLocation != null && commandTextData != null - ? getLabwareDisplayLocation( - commandTextData, - labwareLocation, - t as TFunction, - robotType - ) - : '' - - return t('move_to_well', { - well_name: wellName, - labware: - commandTextData != null - ? getLabwareName(commandTextData, labwareId) - : null, - labware_location: displayLocation, - }) -} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getPipettingCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getPipettingCommandText.ts deleted file mode 100644 index 02a957d5ae4..00000000000 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getPipettingCommandText.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { getLabwareDefURI } from '@opentrons/shared-data' - -import { getLoadedLabware } from '../../../utils/accessors' -import { - getLabwareName, - getLabwareDisplayLocation, - getFinalLabwareLocation, - getWellRange, - getLabwareDefinitionsFromCommands, -} from '../../../utils' - -import type { PipetteName, RunTimeCommand } from '@opentrons/shared-data' -import type { TFunction } from 'i18next' -import type { GetCommandText } from '..' - -export const getPipettingCommandText = ({ - command, - commandTextData, - robotType, - t, -}: GetCommandText): string => { - const labwareId = - command != null && 'labwareId' in command.params - ? (command.params.labwareId as string) - : '' - const wellName = - command != null && 'wellName' in command.params - ? command.params.wellName - : '' - - const allPreviousCommands = commandTextData?.commands.slice( - 0, - commandTextData.commands.findIndex(c => c.id === command?.id) - ) - const labwareLocation = - allPreviousCommands != null - ? getFinalLabwareLocation( - labwareId, - allPreviousCommands as RunTimeCommand[] - ) - : null - const displayLocation = - labwareLocation != null && commandTextData != null - ? getLabwareDisplayLocation( - commandTextData, - labwareLocation, - t as TFunction, - robotType - ) - : '' - - switch (command?.commandType) { - case 'aspirate': { - const { volume, flowRate } = command.params - return t('aspirate', { - well_name: wellName, - labware: - commandTextData != null - ? getLabwareName(commandTextData, labwareId) - : null, - labware_location: displayLocation, - volume, - flow_rate: flowRate, - }) - } - case 'dispense': { - const { volume, flowRate, pushOut } = command.params - return pushOut != null - ? t('dispense_push_out', { - well_name: wellName, - labware: - commandTextData != null - ? getLabwareName(commandTextData, labwareId) - : null, - labware_location: displayLocation, - volume, - flow_rate: flowRate, - push_out_volume: pushOut, - }) - : t('dispense', { - well_name: wellName, - labware: - commandTextData != null - ? getLabwareName(commandTextData, labwareId) - : null, - labware_location: displayLocation, - volume, - flow_rate: flowRate, - }) - } - case 'blowout': { - const { flowRate } = command.params - return t('blowout', { - well_name: wellName, - labware: - commandTextData != null - ? getLabwareName(commandTextData, labwareId) - : null, - labware_location: displayLocation, - flow_rate: flowRate, - }) - } - case 'dropTip': { - const loadedLabware = - commandTextData != null - ? getLoadedLabware(commandTextData, labwareId) - : null - const labwareDefinitions = - commandTextData != null - ? getLabwareDefinitionsFromCommands( - commandTextData.commands as RunTimeCommand[] - ) - : null - const labwareDef = labwareDefinitions?.find( - lw => getLabwareDefURI(lw) === loadedLabware?.definitionUri - ) - return Boolean(labwareDef?.parameters.isTiprack) - ? t('return_tip', { - well_name: wellName, - labware: - commandTextData != null - ? getLabwareName(commandTextData, labwareId) - : null, - labware_location: displayLocation, - }) - : t('drop_tip', { - well_name: wellName, - labware: - commandTextData != null - ? getLabwareName(commandTextData, labwareId) - : null, - }) - } - case 'pickUpTip': { - const pipetteId = command.params.pipetteId - const pipetteName: - | PipetteName - | undefined = commandTextData?.pipettes.find( - pipette => pipette.id === pipetteId - )?.pipetteName - - return t('pickup_tip', { - well_range: - allPreviousCommands != null - ? getWellRange( - pipetteId, - allPreviousCommands as RunTimeCommand[], - wellName as string, - pipetteName - ) - : null, - labware: - commandTextData != null - ? getLabwareName(commandTextData, labwareId) - : null, - labware_location: displayLocation, - }) - } - case 'dropTipInPlace': { - return t('drop_tip_in_place') - } - case 'dispenseInPlace': { - const { volume, flowRate } = command.params - return t('dispense_in_place', { volume, flow_rate: flowRate }) - } - case 'blowOutInPlace': { - const { flowRate } = command.params - return t('blowout_in_place', { flow_rate: flowRate }) - } - case 'aspirateInPlace': { - const { flowRate, volume } = command.params - return t('aspirate_in_place', { volume, flow_rate: flowRate }) - } - default: { - console.warn( - 'PipettingCommandText encountered a command with an unrecognized commandType: ', - command - ) - return '' - } - } -} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getTCRunProfileCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getTCRunProfileCommandText.ts deleted file mode 100644 index 2d279fca850..00000000000 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getTCRunProfileCommandText.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { TCRunProfileRunTimeCommand } from '@opentrons/shared-data/command' -import type { GetCommandTextResult } from '..' -import type { HandlesCommands } from './types' - -export function getTCRunProfileCommandText({ - command, - t, -}: HandlesCommands): GetCommandTextResult { - const { profile } = command.params - - const stepTexts = profile.map( - ({ holdSeconds, celsius }: { holdSeconds: number; celsius: number }) => - t('tc_run_profile_steps', { - celsius, - seconds: holdSeconds, - }).trim() - ) - - const startingProfileText = t('tc_starting_profile', { - repetitions: Object.keys(stepTexts).length, - }) - - return { commandText: startingProfileText, stepTexts } -} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/getUnknownCommandText.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/getUnknownCommandText.ts deleted file mode 100644 index 4f2346c7c01..00000000000 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/getUnknownCommandText.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { GetCommandText } from '..' - -export function getUnknownCommandText({ command }: GetCommandText): string { - return JSON.stringify(command) -} diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/index.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/index.ts deleted file mode 100644 index f7946ff1e47..00000000000 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -export { getLoadCommandText } from './getLoadCommandText' -export { getTemperatureCommandText } from './getTemperatureCommandText' -export { getTCRunProfileCommandText } from './getTCRunProfileCommandText' -export { getHSShakeSpeedCommandText } from './getHSShakeSpeedCommandText' -export { getMoveToSlotCommandText } from './getMoveToSlotCommandText' -export { getMoveRelativeCommandText } from './getMoveRelativeCommandText' -export { getMoveToCoordinatesCommandText } from './getMoveToCoordinatesCommandText' -export { getMoveToWellCommandText } from './getMoveToWellCommandText' -export { getMoveLabwareCommandText } from './getMoveLabwareCommandText' -export { getConfigureForVolumeCommandText } from './getConfigureForVolumeCommandText' -export { getConfigureNozzleLayoutCommandText } from './getConfigureNozzleLayoutCommandText' -export { getPrepareToAspirateCommandText } from './getPrepareToAspirateCommandText' -export { getMoveToAddressableAreaCommandText } from './getMoveToAddressableAreaCommandText' -export { getMoveToAddressableAreaForDropTipCommandText } from './getMoveToAddressabelAreaForDropTipCommandText' -export { getDirectTranslationCommandText } from './getDirectTranslationCommandText' -export { getWaitForDurationCommandText } from './getWaitForDurationCommandText' -export { getWaitForResumeCommandText } from './getWaitForResumeCommandText' -export { getDelayCommandText } from './getDelayCommandText' -export { getCommentCommandText } from './getCommentCommandText' -export { getCustomCommandText } from './getCustomCommandText' -export { getUnknownCommandText } from './getUnknownCommandText' -export { getPipettingCommandText } from './getPipettingCommandText' -export { getLiquidProbeCommandText } from './getLiquidProbeCommandText' diff --git a/app/src/molecules/Command/hooks/useCommandTextString/utils/types.ts b/app/src/molecules/Command/hooks/useCommandTextString/utils/types.ts deleted file mode 100644 index 37dde8c783a..00000000000 --- a/app/src/molecules/Command/hooks/useCommandTextString/utils/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { RunTimeCommand } from '@opentrons/shared-data' -import type { GetCommandText } from '..' - -export type HandlesCommands = Omit< - GetCommandText, - 'command' -> & { command: T } diff --git a/app/src/molecules/Command/index.ts b/app/src/molecules/Command/index.ts index b4223d82beb..9fc833953c8 100644 --- a/app/src/molecules/Command/index.ts +++ b/app/src/molecules/Command/index.ts @@ -2,6 +2,3 @@ export * from './CommandText' export * from './Command' export * from './CommandIcon' export * from './CommandIndex' -export * from './utils' -export * from './types' -export * from './hooks' diff --git a/app/src/molecules/Command/utils/accessors.ts b/app/src/molecules/Command/utils/accessors.ts deleted file mode 100644 index 2ca6fda7efb..00000000000 --- a/app/src/molecules/Command/utils/accessors.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { RunData } from '@opentrons/api-client' -import type { - CompletedProtocolAnalysis, - LoadedLabware, - LoadedModule, - LoadedPipette, -} from '@opentrons/shared-data' -import type { CommandTextData } from '../types' - -export function getLoadedLabware( - commandTextData: CompletedProtocolAnalysis | RunData | CommandTextData, - labwareId: string -): LoadedLabware | undefined { - // NOTE: old analysis contains a object dictionary of labware entities by id, this case is supported for backwards compatibility purposes - return Array.isArray(commandTextData.labware) - ? commandTextData.labware.find(l => l.id === labwareId) - : commandTextData.labware[labwareId] -} -export function getLoadedPipette( - commandTextData: CommandTextData, - mount: string -): LoadedPipette | undefined { - // NOTE: old analysis contains a object dictionary of pipette entities by id, this case is supported for backwards compatibility purposes - return Array.isArray(commandTextData.pipettes) - ? commandTextData.pipettes.find(l => l.mount === mount) - : commandTextData.pipettes[mount] -} -export function getLoadedModule( - commandTextData: CompletedProtocolAnalysis | RunData | CommandTextData, - moduleId: string -): LoadedModule | undefined { - // NOTE: old analysis contains a object dictionary of module entities by id, this case is supported for backwards compatibility purposes - return Array.isArray(commandTextData.modules) - ? commandTextData.modules.find(l => l.id === moduleId) - : commandTextData.modules[moduleId] -} diff --git a/app/src/molecules/Command/utils/getFinalLabwareLocation.ts b/app/src/molecules/Command/utils/getFinalLabwareLocation.ts deleted file mode 100644 index 80cd4e26a4e..00000000000 --- a/app/src/molecules/Command/utils/getFinalLabwareLocation.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { LabwareLocation, RunTimeCommand } from '@opentrons/shared-data' - -/** - * given a list of commands and a labwareId, calculate the resulting location - * of the corresponding labware after all given commands are executed - * @param labwareId target labware - * @param commands list of commands to search within - * @returns LabwareLocation object of the resulting location of the target labware after all commands execute - */ -export function getFinalLabwareLocation( - labwareId: string, - commands: RunTimeCommand[] -): LabwareLocation | null { - for (const c of commands.reverse()) { - if (c.commandType === 'loadLabware' && c.result?.labwareId === labwareId) { - return c.params.location - } else if ( - c.commandType === 'moveLabware' && - c.params.labwareId === labwareId - ) { - return c.params.newLocation - } - } - return null -} diff --git a/app/src/molecules/Command/utils/getLabwareDisplayLocation.ts b/app/src/molecules/Command/utils/getLabwareDisplayLocation.ts deleted file mode 100644 index f86ff3473a8..00000000000 --- a/app/src/molecules/Command/utils/getLabwareDisplayLocation.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { - getLabwareDefURI, - getLabwareDisplayName, - getModuleDisplayName, - getModuleType, - getOccludedSlotCountForModule, -} from '@opentrons/shared-data' -import { getModuleDisplayLocation } from './getModuleDisplayLocation' -import { getModuleModel } from './getModuleModel' -import { getLabwareDefinitionsFromCommands } from './getLabwareDefinitionsFromCommands' -import type { RobotType, LabwareLocation } from '@opentrons/shared-data' -import type { TFunction } from 'i18next' -import type { CommandTextData } from '../types' - -export function getLabwareDisplayLocation( - commandTextData: CommandTextData, - location: LabwareLocation, - t: TFunction, - robotType: RobotType, - isOnDevice?: boolean -): string { - if (location === 'offDeck') { - return t('off_deck') - } else if ('slotName' in location) { - return isOnDevice - ? location.slotName - : t('slot', { slot_name: location.slotName }) - } else if ('addressableAreaName' in location) { - return isOnDevice - ? location.addressableAreaName - : t('slot', { slot_name: location.addressableAreaName }) - } else if ('moduleId' in location) { - const moduleModel = getModuleModel(commandTextData, location.moduleId) - if (moduleModel == null) { - console.warn('labware is located on an unknown module model') - return '' - } else { - const slotName = getModuleDisplayLocation( - commandTextData, - location.moduleId - ) - return isOnDevice - ? `${getModuleDisplayName(moduleModel)}, ${slotName}` - : t('module_in_slot', { - count: getOccludedSlotCountForModule( - getModuleType(moduleModel), - robotType - ), - module: getModuleDisplayName(moduleModel), - slot_name: slotName, - }) - } - } else if ('labwareId' in location) { - const adapter = commandTextData.labware.find( - lw => lw.id === location.labwareId - ) - const allDefs = getLabwareDefinitionsFromCommands(commandTextData.commands) - const adapterDef = allDefs.find( - def => getLabwareDefURI(def) === adapter?.definitionUri - ) - const adapterDisplayName = - adapterDef != null ? getLabwareDisplayName(adapterDef) : '' - - if (adapter == null) { - console.warn('labware is located on an unknown adapter') - return '' - } else if (adapter.location === 'offDeck') { - return t('off_deck') - } else if ('slotName' in adapter.location) { - return t('adapter_in_slot', { - adapter: adapterDisplayName, - slot: adapter.location.slotName, - }) - } else if ('addressableAreaName' in adapter.location) { - return t('adapter_in_slot', { - adapter: adapterDisplayName, - slot: adapter.location.addressableAreaName, - }) - } else if ('moduleId' in adapter.location) { - const moduleIdUnderAdapter = adapter.location.moduleId - const moduleModel = commandTextData.modules.find( - module => module.id === moduleIdUnderAdapter - )?.model - if (moduleModel == null) { - console.warn('labware is located on an adapter on an unknown module') - return '' - } - const slotName = getModuleDisplayLocation( - commandTextData, - adapter.location.moduleId - ) - return t('adapter_in_mod_in_slot', { - count: getOccludedSlotCountForModule( - getModuleType(moduleModel), - robotType - ), - module: getModuleDisplayName(moduleModel), - adapter: adapterDisplayName, - slot: slotName, - }) - } else { - console.warn( - 'display location on adapter could not be established: ', - location - ) - return '' - } - } else { - console.warn('display location could not be established: ', location) - return '' - } -} diff --git a/app/src/molecules/Command/utils/getLabwareName.ts b/app/src/molecules/Command/utils/getLabwareName.ts deleted file mode 100644 index 03c6feb1367..00000000000 --- a/app/src/molecules/Command/utils/getLabwareName.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { getLoadedLabware } from './accessors' - -import { getLabwareDefURI, getLabwareDisplayName } from '@opentrons/shared-data' -import { getLabwareDefinitionsFromCommands } from './getLabwareDefinitionsFromCommands' -import type { CommandTextData } from '../types' - -const FIXED_TRASH_DEF_URIS = [ - 'opentrons/opentrons_1_trash_850ml_fixed/1', - 'opentrons/opentrons_1_trash_1100ml_fixed/1', - 'opentrons/opentrons_1_trash_3200ml_fixed/1', -] -export function getLabwareName( - commandTextData: CommandTextData, - labwareId: string -): string { - const loadedLabware = getLoadedLabware(commandTextData, labwareId) - if (loadedLabware == null) { - return '' - } else if (FIXED_TRASH_DEF_URIS.includes(loadedLabware.definitionUri)) { - return 'Fixed Trash' - } else if (loadedLabware.displayName != null) { - return loadedLabware.displayName - } else { - const labwareDef = getLabwareDefinitionsFromCommands( - commandTextData.commands - ).find(def => getLabwareDefURI(def) === loadedLabware.definitionUri) - return labwareDef != null ? getLabwareDisplayName(labwareDef) : '' - } -} diff --git a/app/src/molecules/Command/utils/getLiquidDisplayName.ts b/app/src/molecules/Command/utils/getLiquidDisplayName.ts deleted file mode 100644 index 1b4b15a854b..00000000000 --- a/app/src/molecules/Command/utils/getLiquidDisplayName.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { CommandTextData } from '../types' - -export function getLiquidDisplayName( - commandTextData: CommandTextData, - liquidId: string -): CommandTextData['liquids'][number]['displayName'] { - const liquidDisplayName = (commandTextData?.liquids ?? []).find( - liquid => liquid.id === liquidId - )?.displayName - return liquidDisplayName ?? '' -} diff --git a/app/src/molecules/Command/utils/getModuleDisplayLocation.ts b/app/src/molecules/Command/utils/getModuleDisplayLocation.ts deleted file mode 100644 index c71c74c4c86..00000000000 --- a/app/src/molecules/Command/utils/getModuleDisplayLocation.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { getLoadedModule } from './accessors' - -import type { CommandTextData } from '../types' - -export function getModuleDisplayLocation( - commandTextData: CommandTextData, - moduleId: string -): string { - const loadedModule = getLoadedModule(commandTextData, moduleId) - return loadedModule != null ? loadedModule.location.slotName : '' -} diff --git a/app/src/molecules/Command/utils/getModuleModel.ts b/app/src/molecules/Command/utils/getModuleModel.ts deleted file mode 100644 index 3e95e05ebeb..00000000000 --- a/app/src/molecules/Command/utils/getModuleModel.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { getLoadedModule } from './accessors' - -import type { ModuleModel } from '@opentrons/shared-data' -import type { CommandTextData } from '../types' - -export function getModuleModel( - commandTextData: CommandTextData, - moduleId: string -): ModuleModel | null { - const loadedModule = getLoadedModule(commandTextData, moduleId) - return loadedModule != null ? loadedModule.model : null -} diff --git a/app/src/molecules/Command/utils/getPipetteNameOnMount.ts b/app/src/molecules/Command/utils/getPipetteNameOnMount.ts deleted file mode 100644 index f1c09d73caf..00000000000 --- a/app/src/molecules/Command/utils/getPipetteNameOnMount.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { getLoadedPipette } from './accessors' - -import type { PipetteName } from '@opentrons/shared-data' -import type { CommandTextData } from '../types' - -export function getPipetteNameOnMount( - commandTextData: CommandTextData, - mount: string -): PipetteName | null { - const loadedPipette = getLoadedPipette(commandTextData, mount) - return loadedPipette != null ? loadedPipette.pipetteName : null -} diff --git a/app/src/molecules/Command/utils/getWellRange.ts b/app/src/molecules/Command/utils/getWellRange.ts deleted file mode 100644 index a0700357413..00000000000 --- a/app/src/molecules/Command/utils/getWellRange.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { getPipetteNameSpecs } from '@opentrons/shared-data' -import type { PipetteName, RunTimeCommand } from '@opentrons/shared-data' - -/** - * @param pipetteName name of pipette being used - * @param commands list of commands to search within - * @param wellName the target well for pickup tip - * @returns WellRange string of wells pipette will pickup tips from - */ -export function getWellRange( - pipetteId: string, - commands: RunTimeCommand[], - wellName: string, - pipetteName?: PipetteName -): string { - const pipetteChannels = pipetteName - ? getPipetteNameSpecs(pipetteName)?.channels ?? 1 - : 1 - let usedChannels = pipetteChannels - if (pipetteChannels === 96) { - for (const c of commands.reverse()) { - if ( - c.commandType === 'configureNozzleLayout' && - c.params?.pipetteId === pipetteId - ) { - // TODO(sb, 11/9/23): add support for quadrant and row configurations when needed - if (c.params.configurationParams.style === 'SINGLE') { - usedChannels = 1 - } else if (c.params.configurationParams.style === 'COLUMN') { - usedChannels = 8 - } - break - } - } - } - if (usedChannels === 96) { - return 'A1 - H12' - } else if (usedChannels === 8) { - const column = wellName.substr(1) - return `A${column} - H${column}` - } - return wellName -} diff --git a/app/src/molecules/Command/utils/index.ts b/app/src/molecules/Command/utils/index.ts deleted file mode 100644 index 8e8bbfd9119..00000000000 --- a/app/src/molecules/Command/utils/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -export * from './getAddressableAreaDisplayName' -export * from './getLabwareName' -export * from './getPipetteNameOnMount' -export * from './getModuleModel' -export * from './getModuleDisplayLocation' -export * from './getLiquidDisplayName' -export * from './getLabwareDisplayLocation' -export * from './getFinalLabwareLocation' -export * from './getWellRange' -export * from './getLabwareDefinitionsFromCommands' diff --git a/app/src/molecules/FileUpload/FileUpload.stories.tsx b/app/src/molecules/FileUpload/FileUpload.stories.tsx index 5ddaab2698f..855a50c5629 100644 --- a/app/src/molecules/FileUpload/FileUpload.stories.tsx +++ b/app/src/molecules/FileUpload/FileUpload.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import testFile from './__tests__/test-file.png' import { FileUpload } from '.' diff --git a/app/src/molecules/FileUpload/__tests__/FileUpload.test.tsx b/app/src/molecules/FileUpload/__tests__/FileUpload.test.tsx index 3e14d809631..cd5b5adfd82 100644 --- a/app/src/molecules/FileUpload/__tests__/FileUpload.test.tsx +++ b/app/src/molecules/FileUpload/__tests__/FileUpload.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { FileUpload } from '..' import testFile from './test-file.png' diff --git a/app/src/molecules/FileUpload/index.tsx b/app/src/molecules/FileUpload/index.tsx index dc953027235..777d186ccb0 100644 --- a/app/src/molecules/FileUpload/index.tsx +++ b/app/src/molecules/FileUpload/index.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { css } from 'styled-components' import { diff --git a/app/src/molecules/GenericWizardTile/GenericWizardTile.stories.tsx b/app/src/molecules/GenericWizardTile/GenericWizardTile.stories.tsx index b5c61246cd1..5444feff20b 100644 --- a/app/src/molecules/GenericWizardTile/GenericWizardTile.stories.tsx +++ b/app/src/molecules/GenericWizardTile/GenericWizardTile.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Provider } from 'react-redux' import { createStore } from 'redux' import { @@ -9,9 +9,9 @@ import { LegacyStyledText, ModalShell, } from '@opentrons/components' -import { Skeleton } from '../../atoms/Skeleton' +import { Skeleton } from '/app/atoms/Skeleton' import { WizardHeader } from '../WizardHeader' -import { configReducer } from '../../redux/config/reducer' +import { configReducer } from '/app/redux/config/reducer' import { GenericWizardTile } from './index' import type { Store, StoreEnhancer } from 'redux' diff --git a/app/src/molecules/GenericWizardTile/__tests__/GenericWizardTile.test.tsx b/app/src/molecules/GenericWizardTile/__tests__/GenericWizardTile.test.tsx index 63b6cd1cf92..1f53800ff6d 100644 --- a/app/src/molecules/GenericWizardTile/__tests__/GenericWizardTile.test.tsx +++ b/app/src/molecules/GenericWizardTile/__tests__/GenericWizardTile.test.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import type * as React from 'react' import '@testing-library/jest-dom/vitest' import { describe, it, expect, vi, beforeEach } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' -import { getIsOnDevice } from '../../../redux/config' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { getIsOnDevice } from '/app/redux/config' import { GenericWizardTile } from '..' -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/molecules/GenericWizardTile/index.tsx b/app/src/molecules/GenericWizardTile/index.tsx index 2f7dd88939e..24883a6ffea 100644 --- a/app/src/molecules/GenericWizardTile/index.tsx +++ b/app/src/molecules/GenericWizardTile/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useSelector } from 'react-redux' import styled, { css } from 'styled-components' import { useTranslation } from 'react-i18next' @@ -22,9 +22,9 @@ import { useHoverTooltip, } from '@opentrons/components' -import { getIsOnDevice } from '../../redux/config' -import { NeedHelpLink } from '../../organisms/CalibrationPanels' -import { SmallButton, TextOnlyButton } from '../../atoms/buttons' +import { getIsOnDevice } from '/app/redux/config' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' +import { SmallButton, TextOnlyButton } from '/app/atoms/buttons' const ALIGN_BUTTONS = css` align-items: ${ALIGN_FLEX_END}; diff --git a/app/src/molecules/InProgressModal/InProgressModal.stories.tsx b/app/src/molecules/InProgressModal/InProgressModal.stories.tsx index 9fc5f5b30c9..cfa241a95fc 100644 --- a/app/src/molecules/InProgressModal/InProgressModal.stories.tsx +++ b/app/src/molecules/InProgressModal/InProgressModal.stories.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import type { Meta, StoryObj } from '@storybook/react' import { InProgressModal as InProgressModalComponent } from './' import { SimpleWizardInProgressBody } from '../SimpleWizardBody' diff --git a/app/src/molecules/InProgressModal/InProgressModal.tsx b/app/src/molecules/InProgressModal/InProgressModal.tsx index 70d6c3eadc4..c6fefe761a2 100644 --- a/app/src/molecules/InProgressModal/InProgressModal.tsx +++ b/app/src/molecules/InProgressModal/InProgressModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { ALIGN_CENTER, @@ -52,6 +52,7 @@ const MODAL_STYLE = css` justify-content: ${JUSTIFY_CENTER}; padding: ${SPACING.spacing32}; height: 24.625rem; + width: 100%; @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { max-height: 29.5rem; height: 100%; diff --git a/app/src/molecules/InProgressModal/__tests__/InProgressModal.test.tsx b/app/src/molecules/InProgressModal/__tests__/InProgressModal.test.tsx index b8644d2bb83..f670fa221c3 100644 --- a/app/src/molecules/InProgressModal/__tests__/InProgressModal.test.tsx +++ b/app/src/molecules/InProgressModal/__tests__/InProgressModal.test.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, beforeEach, vi } from 'vitest' -import { i18n } from '../../../i18n' -import { getIsOnDevice } from '../../../redux/config' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { getIsOnDevice } from '/app/redux/config' +import { renderWithProviders } from '/app/__testing-utils__' import { InProgressModal } from '../InProgressModal' -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/molecules/InfoMessage/__tests__/InfoMessage.test.tsx b/app/src/molecules/InfoMessage/__tests__/InfoMessage.test.tsx index 7d6a1d851a3..5ff6948976f 100644 --- a/app/src/molecules/InfoMessage/__tests__/InfoMessage.test.tsx +++ b/app/src/molecules/InfoMessage/__tests__/InfoMessage.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { screen } from '@testing-library/react' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { InfoMessage } from '..' const render = (props: React.ComponentProps) => { diff --git a/app/src/molecules/InfoMessage/index.tsx b/app/src/molecules/InfoMessage/index.tsx index 618233b0a20..c5d8bda1666 100644 --- a/app/src/molecules/InfoMessage/index.tsx +++ b/app/src/molecules/InfoMessage/index.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { ALIGN_CENTER, ALIGN_FLEX_START, diff --git a/app/src/molecules/InstrumentCard/InstrumentCard.stories.tsx b/app/src/molecules/InstrumentCard/InstrumentCard.stories.tsx index 7a56a1eba9c..7db21c46afc 100644 --- a/app/src/molecules/InstrumentCard/InstrumentCard.stories.tsx +++ b/app/src/molecules/InstrumentCard/InstrumentCard.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { InstrumentCard } from './' import type { Story, Meta } from '@storybook/react' diff --git a/app/src/molecules/InstrumentCard/MenuOverlay.tsx b/app/src/molecules/InstrumentCard/MenuOverlay.tsx index 9ad0c35fd51..18db4780d18 100644 --- a/app/src/molecules/InstrumentCard/MenuOverlay.tsx +++ b/app/src/molecules/InstrumentCard/MenuOverlay.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { Fragment } from 'react' import { BORDERS, @@ -6,16 +6,18 @@ import { DIRECTION_COLUMN, Flex, MenuItem, + NO_WRAP, POSITION_ABSOLUTE, } from '@opentrons/components' -import { Divider } from '../../atoms/structure' +import { Divider } from '/app/atoms/structure' +import type { MouseEventHandler, MouseEvent, ReactNode } from 'react' import type { StyleProps } from '@opentrons/components' export interface MenuOverlayItemProps { - label: React.ReactNode - onClick: React.MouseEventHandler + label: ReactNode + onClick: MouseEventHandler disabled?: boolean } @@ -38,16 +40,16 @@ export function MenuOverlay(props: MenuOverlayProps): JSX.Element { position={POSITION_ABSOLUTE} top="2.25rem" right="0" - whiteSpace="nowrap" + whiteSpace={NO_WRAP} zIndex={10} - onClick={(e: React.MouseEvent) => { + onClick={(e: MouseEvent) => { e.preventDefault() e.stopPropagation() setShowMenuOverlay(false) }} > {menuOverlayItems.map((menuOverlayItem, i) => ( - + {/* insert a divider before the last item if desired */} {hasDivider && i === menuOverlayItems.length - 1 ? ( @@ -58,7 +60,7 @@ export function MenuOverlay(props: MenuOverlayProps): JSX.Element { > {menuOverlayItem.label} - + ))} ) diff --git a/app/src/molecules/InstrumentCard/__tests__/InstrumentCard.test.tsx b/app/src/molecules/InstrumentCard/__tests__/InstrumentCard.test.tsx index 6efa70a7752..ca5905829f0 100644 --- a/app/src/molecules/InstrumentCard/__tests__/InstrumentCard.test.tsx +++ b/app/src/molecules/InstrumentCard/__tests__/InstrumentCard.test.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { fireEvent, render, screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' import { describe, it, expect, vi } from 'vitest' diff --git a/app/src/molecules/InstrumentCard/index.tsx b/app/src/molecules/InstrumentCard/index.tsx index e2a434850d2..bcb2ccb47ac 100644 --- a/app/src/molecules/InstrumentCard/index.tsx +++ b/app/src/molecules/InstrumentCard/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { ALIGN_CENTER, @@ -18,7 +18,7 @@ import { TYPOGRAPHY, useMenuHandleClickOutside, } from '@opentrons/components' -import flexGripper from '../../assets/images/flex_gripper.png' +import flexGripper from '/app/assets/images/flex_gripper.png' import { MenuOverlay } from './MenuOverlay' diff --git a/app/src/molecules/InterventionModal/CategorizedStepContent.stories.tsx b/app/src/molecules/InterventionModal/CategorizedStepContent.stories.tsx index 4037532cf6f..79d83fd57ef 100644 --- a/app/src/molecules/InterventionModal/CategorizedStepContent.stories.tsx +++ b/app/src/molecules/InterventionModal/CategorizedStepContent.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { CategorizedStepContent, TwoColumn } from '.' @@ -14,7 +14,7 @@ import type { Meta, StoryObj } from '@storybook/react' type CommandType = RunTimeCommand['commandType'] const availableCommandTypes = uniq( - Fixtures.mockQIASeqTextData.commands.map(command => command.commandType) + Fixtures.mockDoItAllTextData.commands.map(command => command.commandType) ) const commandsByType: Partial> = {} @@ -22,7 +22,7 @@ function commandsOfType(type: CommandType): RunTimeCommand[] { if (type in commandsByType) { return commandsByType[type] } - commandsByType[type] = Fixtures.mockQIASeqTextData.commands.filter( + commandsByType[type] = Fixtures.mockDoItAllTextData.commands.filter( command => command.commandType === type ) return commandsByType[type] @@ -62,22 +62,22 @@ function Wrapper(props: WrapperProps): JSX.Element { const topCommandIndex = topCommand == null ? undefined - : Fixtures.mockQIASeqTextData.commands.indexOf(topCommand) + : Fixtures.mockDoItAllTextData.commands.indexOf(topCommand) const bottomCommand1Index = bottomCommand1 == null ? undefined - : Fixtures.mockQIASeqTextData.commands.indexOf(bottomCommand1) + : Fixtures.mockDoItAllTextData.commands.indexOf(bottomCommand1) const bottomCommand2Index = bottomCommand2 == null ? undefined - : Fixtures.mockQIASeqTextData.commands.indexOf(bottomCommand2) + : Fixtures.mockDoItAllTextData.commands.indexOf(bottomCommand2) return ( command == null || commandTextData == null ? EMPTY_COMMAND - : { state, command: command.command, commandTextData } + : { state, command: command.command, commandTextData, allRunDefs } export function CategorizedStepContent( props: CategorizedStepContentProps @@ -98,7 +109,8 @@ export function CategorizedStepContent( {...commandAndState( props.topCategoryCommand, props.topCategory, - props.commandTextData + props.commandTextData, + props.allRunDefs )} robotType={props.robotType} aligned="left" @@ -138,7 +150,8 @@ export function CategorizedStepContent( {...commandAndState( command, props.bottomCategory, - props.commandTextData + props.commandTextData, + props.allRunDefs )} robotType={props.robotType} aligned="left" diff --git a/app/src/molecules/InterventionModal/DeckMapContent.stories.tsx b/app/src/molecules/InterventionModal/DeckMapContent.stories.tsx index 56c896655e0..a41339004e4 100644 --- a/app/src/molecules/InterventionModal/DeckMapContent.stories.tsx +++ b/app/src/molecules/InterventionModal/DeckMapContent.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { DeckMapContent } from '.' diff --git a/app/src/molecules/InterventionModal/DeckMapContent.tsx b/app/src/molecules/InterventionModal/DeckMapContent.tsx index a45bc920e0a..6bafed02bd1 100644 --- a/app/src/molecules/InterventionModal/DeckMapContent.tsx +++ b/app/src/molecules/InterventionModal/DeckMapContent.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useEffect } from 'react' import { css } from 'styled-components' import { Box, @@ -11,6 +11,7 @@ import { useDeckLocationSelect, } from '@opentrons/components' +import type { ComponentProps } from 'react' import type { LabwareDefinition2, RobotType, @@ -22,7 +23,7 @@ export type MapKind = 'intervention' | 'deck-config' export interface InterventionStyleDeckMapContentProps extends Pick< - React.ComponentProps, + ComponentProps, 'deckConfig' | 'robotType' | 'labwareOnDeck' | 'modulesOnDeck' > { kind: 'intervention' @@ -107,7 +108,7 @@ function DeckConfigStyleDeckMapContent({ robotType, 'default' ) - React.useEffect(() => { + useEffect(() => { setSelectedLocation != null && setSelectedLocation(selectedLocation) }, [selectedLocation, setSelectedLocation]) return <>{DeckLocationSelect} diff --git a/app/src/molecules/InterventionModal/DescriptionContent.tsx b/app/src/molecules/InterventionModal/DescriptionContent.tsx index aa1ca395814..a3da661da0e 100644 --- a/app/src/molecules/InterventionModal/DescriptionContent.tsx +++ b/app/src/molecules/InterventionModal/DescriptionContent.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Flex, SPACING, @@ -6,7 +5,7 @@ import { StyledText, RESPONSIVENESS, } from '@opentrons/components' -import { InlineNotification } from '../../atoms/InlineNotification' +import { InlineNotification } from '/app/atoms/InlineNotification' interface NotificationProps { notificationHeader?: string diff --git a/app/src/molecules/InterventionModal/InterventionContent/InterventionContent.stories.tsx b/app/src/molecules/InterventionModal/InterventionContent/InterventionContent.stories.tsx index d910847c2d3..47cfea68317 100644 --- a/app/src/molecules/InterventionModal/InterventionContent/InterventionContent.stories.tsx +++ b/app/src/molecules/InterventionModal/InterventionContent/InterventionContent.stories.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { ICON_DATA_BY_NAME } from '@opentrons/components' import { InterventionContent } from '.' import { TwoColumn } from '../TwoColumn' diff --git a/app/src/molecules/InterventionModal/InterventionContent/InterventionInfo.stories.tsx b/app/src/molecules/InterventionModal/InterventionContent/InterventionInfo.stories.tsx index caac9a06d5c..33a6ddbcac1 100644 --- a/app/src/molecules/InterventionModal/InterventionContent/InterventionInfo.stories.tsx +++ b/app/src/molecules/InterventionModal/InterventionContent/InterventionInfo.stories.tsx @@ -1,5 +1,3 @@ -import * as React from 'react' - import { Box, ICON_DATA_BY_NAME } from '@opentrons/components' import { InterventionInfo } from './InterventionInfo' diff --git a/app/src/molecules/InterventionModal/InterventionContent/InterventionInfo.tsx b/app/src/molecules/InterventionModal/InterventionContent/InterventionInfo.tsx index 1031519602a..0d9c0d2a191 100644 --- a/app/src/molecules/InterventionModal/InterventionContent/InterventionInfo.tsx +++ b/app/src/molecules/InterventionModal/InterventionContent/InterventionInfo.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { css } from 'styled-components' import { @@ -13,7 +12,7 @@ import { ALIGN_CENTER, RESPONSIVENESS, } from '@opentrons/components' -import { Divider } from '../../../atoms/structure/Divider' +import { Divider } from '/app/atoms/structure/Divider' import type { DeckInfoLabelProps } from '@opentrons/components' @@ -83,7 +82,7 @@ const buildContent = (props: InterventionInfoProps): JSX.Element => { } const buildLocArrowLoc = (props: InterventionInfoProps): JSX.Element => { - const { currentLocationProps, newLocationProps } = props + const { currentLocationProps, newLocationProps, type } = props if (newLocationProps != null) { return ( @@ -102,6 +101,9 @@ const buildLocArrowLoc = (props: InterventionInfoProps): JSX.Element => { ) } else { + console.error( + `InterventionInfo type is ${type}, but no newLocation was specified.` + ) return buildLoc(props) } } @@ -117,7 +119,7 @@ const buildLoc = ({ } const buildLocColonLoc = (props: InterventionInfoProps): JSX.Element => { - const { currentLocationProps, newLocationProps } = props + const { currentLocationProps, newLocationProps, type } = props if (newLocationProps != null) { return ( @@ -136,6 +138,9 @@ const buildLocColonLoc = (props: InterventionInfoProps): JSX.Element => { ) } else { + console.error( + `InterventionInfo type is ${type}, but no newLocation was specified.` + ) return buildLoc(props) } } diff --git a/app/src/molecules/InterventionModal/InterventionContent/index.tsx b/app/src/molecules/InterventionModal/InterventionContent/index.tsx index cc52255e4f9..8d2bbba9c8c 100644 --- a/app/src/molecules/InterventionModal/InterventionContent/index.tsx +++ b/app/src/molecules/InterventionModal/InterventionContent/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Flex, StyledText, @@ -6,7 +6,7 @@ import { SPACING, RESPONSIVENESS, } from '@opentrons/components' -import { InlineNotification } from '../../../atoms/InlineNotification' +import { InlineNotification } from '/app/atoms/InlineNotification' import { InterventionInfo } from './InterventionInfo' export type { InterventionInfoProps } from './InterventionInfo' diff --git a/app/src/molecules/InterventionModal/InterventionModal.stories.tsx b/app/src/molecules/InterventionModal/InterventionModal.stories.tsx index 60c4cd820f1..75b025faaf7 100644 --- a/app/src/molecules/InterventionModal/InterventionModal.stories.tsx +++ b/app/src/molecules/InterventionModal/InterventionModal.stories.tsx @@ -1,9 +1,8 @@ -import * as React from 'react' import { Provider } from 'react-redux' import { createStore } from 'redux' import { LegacyStyledText } from '@opentrons/components' -import { configReducer } from '../../redux/config/reducer' +import { configReducer } from '/app/redux/config/reducer' import { InterventionModal as InterventionModalComponent } from './' import type { Store, StoreEnhancer } from 'redux' diff --git a/app/src/molecules/InterventionModal/ModalContentMixed.stories.tsx b/app/src/molecules/InterventionModal/ModalContentMixed.stories.tsx index cf21d519089..b2e9b48e370 100644 --- a/app/src/molecules/InterventionModal/ModalContentMixed.stories.tsx +++ b/app/src/molecules/InterventionModal/ModalContentMixed.stories.tsx @@ -1,4 +1,4 @@ -import successIcon from '../../assets/images/icon_success.png' +import successIcon from '/app/assets/images/icon_success.png' import type { Meta, StoryObj } from '@storybook/react' import { customViewports } from '../../../../.storybook/preview' diff --git a/app/src/molecules/InterventionModal/ModalContentMixed.tsx b/app/src/molecules/InterventionModal/ModalContentMixed.tsx index 08c5787b1db..4c41003c3d8 100644 --- a/app/src/molecules/InterventionModal/ModalContentMixed.tsx +++ b/app/src/molecules/InterventionModal/ModalContentMixed.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Icon, Flex, diff --git a/app/src/molecules/InterventionModal/ModalContentOneColSimpleButtons.stories.tsx b/app/src/molecules/InterventionModal/ModalContentOneColSimpleButtons.stories.tsx index 731024ef5e2..626b6bf5243 100644 --- a/app/src/molecules/InterventionModal/ModalContentOneColSimpleButtons.stories.tsx +++ b/app/src/molecules/InterventionModal/ModalContentOneColSimpleButtons.stories.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { ModalContentOneColSimpleButtons as ModalContentOneColSimpleButtonsComponent } from './ModalContentOneColSimpleButtons' import type { Meta, StoryObj } from '@storybook/react' diff --git a/app/src/molecules/InterventionModal/ModalContentOneColSimpleButtons.tsx b/app/src/molecules/InterventionModal/ModalContentOneColSimpleButtons.tsx index c70ddbea7d1..9302192aa68 100644 --- a/app/src/molecules/InterventionModal/ModalContentOneColSimpleButtons.tsx +++ b/app/src/molecules/InterventionModal/ModalContentOneColSimpleButtons.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { Flex, DIRECTION_COLUMN, @@ -9,10 +9,12 @@ import { } from '@opentrons/components' import { OneColumn } from './OneColumn' +import type { ChangeEventHandler } from 'react' + export interface ButtonProps { label: string value: string - onChange?: React.ChangeEventHandler + onChange?: ChangeEventHandler } export interface ModalContentOneColSimpleButtonsProps { @@ -20,14 +22,14 @@ export interface ModalContentOneColSimpleButtonsProps { firstButton: ButtonProps secondButton: ButtonProps furtherButtons?: ButtonProps[] - onSelect?: React.ChangeEventHandler + onSelect?: ChangeEventHandler initialSelected?: string } export function ModalContentOneColSimpleButtons( props: ModalContentOneColSimpleButtonsProps ): JSX.Element { - const [selected, setSelected] = React.useState( + const [selected, setSelected] = useState( props.initialSelected ?? null ) const furtherButtons = props.furtherButtons ?? [] diff --git a/app/src/molecules/InterventionModal/OneColumn.stories.tsx b/app/src/molecules/InterventionModal/OneColumn.stories.tsx index ef4e8a6a02f..156eb44b514 100644 --- a/app/src/molecules/InterventionModal/OneColumn.stories.tsx +++ b/app/src/molecules/InterventionModal/OneColumn.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Box, RESPONSIVENESS } from '@opentrons/components' diff --git a/app/src/molecules/InterventionModal/OneColumn.tsx b/app/src/molecules/InterventionModal/OneColumn.tsx index e92f3ffd51e..35a37dd10f9 100644 --- a/app/src/molecules/InterventionModal/OneColumn.tsx +++ b/app/src/molecules/InterventionModal/OneColumn.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Flex, diff --git a/app/src/molecules/InterventionModal/OneColumnOrTwoColumn.stories.tsx b/app/src/molecules/InterventionModal/OneColumnOrTwoColumn.stories.tsx index 791edcbdb83..95e9ac3ff98 100644 --- a/app/src/molecules/InterventionModal/OneColumnOrTwoColumn.stories.tsx +++ b/app/src/molecules/InterventionModal/OneColumnOrTwoColumn.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { OneColumnOrTwoColumn } from './' diff --git a/app/src/molecules/InterventionModal/OneColumnOrTwoColumn.tsx b/app/src/molecules/InterventionModal/OneColumnOrTwoColumn.tsx index 8a6455d67e3..db25d343be5 100644 --- a/app/src/molecules/InterventionModal/OneColumnOrTwoColumn.tsx +++ b/app/src/molecules/InterventionModal/OneColumnOrTwoColumn.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { diff --git a/app/src/molecules/InterventionModal/TwoColumn.stories.tsx b/app/src/molecules/InterventionModal/TwoColumn.stories.tsx index 84722fbf00b..a1d83ca750d 100644 --- a/app/src/molecules/InterventionModal/TwoColumn.stories.tsx +++ b/app/src/molecules/InterventionModal/TwoColumn.stories.tsx @@ -1,6 +1,6 @@ -import * as React from 'react' +import type * as React from 'react' -import SuccessIcon from '../../assets/images/icon_success.png' +import SuccessIcon from '/app/assets/images/icon_success.png' import { LegacyStyledText, @@ -8,7 +8,7 @@ import { DIRECTION_COLUMN, Box, } from '@opentrons/components' -import { InlineNotification } from '../../atoms/InlineNotification' +import { InlineNotification } from '/app/atoms/InlineNotification' import { TwoColumn as TwoColumnComponent } from './' import { StandInContent } from './story-utils/StandIn' diff --git a/app/src/molecules/InterventionModal/TwoColumn.tsx b/app/src/molecules/InterventionModal/TwoColumn.tsx index f0ed10ebf2a..fc9072232be 100644 --- a/app/src/molecules/InterventionModal/TwoColumn.tsx +++ b/app/src/molecules/InterventionModal/TwoColumn.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Flex, Box, DIRECTION_ROW, SPACING, WRAP } from '@opentrons/components' import type { StyleProps } from '@opentrons/components' diff --git a/app/src/molecules/InterventionModal/__tests__/InterventionModal.test.tsx b/app/src/molecules/InterventionModal/__tests__/InterventionModal.test.tsx index 060a96eb401..a063bee13bc 100644 --- a/app/src/molecules/InterventionModal/__tests__/InterventionModal.test.tsx +++ b/app/src/molecules/InterventionModal/__tests__/InterventionModal.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, describe, it, expect, beforeEach } from 'vitest' import { when } from 'vitest-when' import '@testing-library/jest-dom/vitest' @@ -6,16 +6,16 @@ import '@testing-library/jest-dom/vitest' import { screen, fireEvent } from '@testing-library/react' import { COLORS, BORDERS } from '@opentrons/components' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' -import { getIsOnDevice } from '../../../redux/config' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { getIsOnDevice } from '/app/redux/config' import { InterventionModal } from '../' import type { ModalType } from '../' -import type { State } from '../../../redux/types' +import type { State } from '/app/redux/types' -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') const MOCK_STATE: State = { config: { diff --git a/app/src/molecules/InterventionModal/__tests__/ModalContentOneColSimpleButtons.test.tsx b/app/src/molecules/InterventionModal/__tests__/ModalContentOneColSimpleButtons.test.tsx index 236b236b390..27a40d82ad4 100644 --- a/app/src/molecules/InterventionModal/__tests__/ModalContentOneColSimpleButtons.test.tsx +++ b/app/src/molecules/InterventionModal/__tests__/ModalContentOneColSimpleButtons.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, describe, it, expect } from 'vitest' import { render, screen, fireEvent } from '@testing-library/react' diff --git a/app/src/molecules/InterventionModal/index.tsx b/app/src/molecules/InterventionModal/index.tsx index aec8c9fea22..679d3b3d1f8 100644 --- a/app/src/molecules/InterventionModal/index.tsx +++ b/app/src/molecules/InterventionModal/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useSelector } from 'react-redux' import { css } from 'styled-components' @@ -6,6 +6,9 @@ import { ALIGN_CENTER, BORDERS, COLORS, + CURSOR_DEFAULT, + CURSOR_POINTER, + DIRECTION_COLUMN, Flex, Icon, JUSTIFY_CENTER, @@ -14,14 +17,12 @@ import { POSITION_ABSOLUTE, POSITION_RELATIVE, POSITION_STICKY, - SPACING, - DIRECTION_COLUMN, RESPONSIVENESS, + SPACING, } from '@opentrons/components' -import { getIsOnDevice } from '../../redux/config' +import { getIsOnDevice } from '/app/redux/config' -import type { IconName } from '@opentrons/components' import { ModalContentOneColSimpleButtons } from './ModalContentOneColSimpleButtons' import { TwoColumn } from './TwoColumn' import { OneColumn } from './OneColumn' @@ -30,6 +31,10 @@ import { ModalContentMixed } from './ModalContentMixed' import { DescriptionContent } from './DescriptionContent' import { DeckMapContent } from './DeckMapContent' import { CategorizedStepContent } from './CategorizedStepContent' + +import type { FlattenSimpleInterpolation } from 'styled-components' +import type { IconName } from '@opentrons/components' + export { ModalContentOneColSimpleButtons, TwoColumn, @@ -51,6 +56,7 @@ const BASE_STYLE = { right: 0, bottom: 0, left: 0, + padding: '1rem', width: '100%', height: '100%', 'data-testid': '__otInterventionModalHeaderBase', @@ -101,7 +107,7 @@ const WRAPPER_STYLE = { bottom: '0', zIndex: '1', backgroundColor: `${COLORS.black90}${COLORS.opacity40HexCode}`, - cursor: 'default', + cursor: CURSOR_DEFAULT, 'data-testid': '__otInterventionModalWrapper', } as const @@ -119,6 +125,8 @@ export interface InterventionModalProps { type?: ModalType /** optional icon name */ iconName?: IconName | null | undefined + /* Optional icon size override. */ + iconSize?: string /** modal contents */ children: React.ReactNode } @@ -130,6 +138,7 @@ export function InterventionModal({ iconName, iconHeading, children, + iconSize, }: InterventionModalProps): JSX.Element { const modalType = type ?? 'intervention-required' const headerColor = @@ -163,7 +172,7 @@ export function InterventionModal({ {titleHeading} {iconName != null ? ( - + ) : null} {iconHeading != null ? iconHeading : null} @@ -175,15 +184,15 @@ export function InterventionModal({ ) } -const ICON_STYLE = css` - width: ${SPACING.spacing16}; - height: ${SPACING.spacing16}; +const buildIconStyle = ( + iconSize: string | undefined +): FlattenSimpleInterpolation => css` + width: ${iconSize ?? SPACING.spacing16}; + height: ${iconSize ?? SPACING.spacing16}; margin: ${SPACING.spacing4}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; @media (${RESPONSIVENESS.touchscreenMediaQuerySpecs}) { - width: ${SPACING.spacing32}; - height: ${SPACING.spacing32}; margin: ${SPACING.spacing12}; } ` diff --git a/app/src/molecules/InterventionModal/story-utils/StandIn.tsx b/app/src/molecules/InterventionModal/story-utils/StandIn.tsx index 0fb46f44b8c..33eb868816d 100644 --- a/app/src/molecules/InterventionModal/story-utils/StandIn.tsx +++ b/app/src/molecules/InterventionModal/story-utils/StandIn.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Box, BORDERS } from '@opentrons/components' export function StandInContent({ diff --git a/app/src/molecules/InterventionModal/story-utils/VisibleContainer.tsx b/app/src/molecules/InterventionModal/story-utils/VisibleContainer.tsx index b716b3335ee..8701f074c8a 100644 --- a/app/src/molecules/InterventionModal/story-utils/VisibleContainer.tsx +++ b/app/src/molecules/InterventionModal/story-utils/VisibleContainer.tsx @@ -1,5 +1,3 @@ -import * as React from 'react' - import { Box, BORDERS, SPACING } from '@opentrons/components' import type { StyleProps } from '@opentrons/components' diff --git a/app/src/molecules/JogControls/ControlContainer.tsx b/app/src/molecules/JogControls/ControlContainer.tsx index 6d47b3df405..02e3c0b1692 100644 --- a/app/src/molecules/JogControls/ControlContainer.tsx +++ b/app/src/molecules/JogControls/ControlContainer.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { BORDERS, diff --git a/app/src/molecules/JogControls/DirectionControl.tsx b/app/src/molecules/JogControls/DirectionControl.tsx index 046ef173eed..d51615fd97e 100644 --- a/app/src/molecules/JogControls/DirectionControl.tsx +++ b/app/src/molecules/JogControls/DirectionControl.tsx @@ -1,5 +1,5 @@ // jog controls component -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' @@ -13,16 +13,17 @@ import { COLORS, DIRECTION_COLUMN, DIRECTION_ROW, + DISPLAY_GRID, Flex, HandleKeypress, Icon, JUSTIFY_CENTER, JUSTIFY_FLEX_START, JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, PrimaryButton, RESPONSIVENESS, SPACING, - LegacyStyledText, TEXT_ALIGN_LEFT, TYPOGRAPHY, } from '@opentrons/components' @@ -30,8 +31,9 @@ import { ControlContainer } from './ControlContainer' import { HORIZONTAL_PLANE, VERTICAL_PLANE } from './constants' import { TouchControlButton } from './TouchControlButton' -import type { IconName } from '@opentrons/components' +import type { MouseEvent } from 'react' import type { CSSProperties } from 'styled-components' +import type { IconName } from '@opentrons/components' import type { Jog, Plane, Sign, Bearing, Axis, StepSize } from './types' interface Control { @@ -222,12 +224,12 @@ interface DirectionControlProps { export function DirectionControl(props: DirectionControlProps): JSX.Element { const { planes, jog, stepSize, initialPlane } = props - const [currentPlane, setCurrentPlane] = React.useState( + const [currentPlane, setCurrentPlane] = useState( initialPlane ?? planes[0] ) const { t } = useTranslation(['robot_calibration']) - const handlePlane = (event: React.MouseEvent): void => { + const handlePlane = (event: MouseEvent): void => { setCurrentPlane(event.currentTarget.value as Plane) event.currentTarget.blur() } @@ -302,7 +304,7 @@ export function DirectionControl(props: DirectionControlProps): JSX.Element { } const ARROW_GRID_STYLES = css` - display: grid; + display: ${DISPLAY_GRID}; max-width: 8.75rem; grid-template-columns: repeat(6, 1fr); grid-template-areas: @@ -448,7 +450,7 @@ export function TouchDirectionControl( props: DirectionControlProps ): JSX.Element { const { planes, jog, stepSize, initialPlane } = props - const [currentPlane, setCurrentPlane] = React.useState( + const [currentPlane, setCurrentPlane] = useState( initialPlane ?? planes[0] ) const { i18n, t } = useTranslation(['robot_calibration']) diff --git a/app/src/molecules/JogControls/JogControls.stories.tsx b/app/src/molecules/JogControls/JogControls.stories.tsx index a4008ea6256..55b84d51947 100644 --- a/app/src/molecules/JogControls/JogControls.stories.tsx +++ b/app/src/molecules/JogControls/JogControls.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { JogControls } from './index' diff --git a/app/src/molecules/JogControls/StepSizeControl.tsx b/app/src/molecules/JogControls/StepSizeControl.tsx index 29f455b4f55..77d1e65b870 100644 --- a/app/src/molecules/JogControls/StepSizeControl.tsx +++ b/app/src/molecules/JogControls/StepSizeControl.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' import { @@ -8,12 +8,13 @@ import { COLORS, DIRECTION_COLUMN, DIRECTION_ROW, + DISPLAY_GRID, Flex, HandleKeypress, Icon, + LegacyStyledText, PrimaryButton, SPACING, - LegacyStyledText, TEXT_TRANSFORM_CAPITALIZE, TYPOGRAPHY, } from '@opentrons/components' @@ -35,7 +36,7 @@ const stepSizeTranslationKeyByStep: { [stepSize: number]: string } = { } const BUTTON_WRAPPER_STYLE = css` - display: grid; + display: ${DISPLAY_GRID}; grid-auto-flow: column; grid-gap: ${SPACING.spacing8}; margin-top: ${SPACING.spacing16}; diff --git a/app/src/molecules/JogControls/TouchControlButton.tsx b/app/src/molecules/JogControls/TouchControlButton.tsx index 0ad85f1de7d..88862e58158 100644 --- a/app/src/molecules/JogControls/TouchControlButton.tsx +++ b/app/src/molecules/JogControls/TouchControlButton.tsx @@ -1,12 +1,12 @@ import styled from 'styled-components' -import { COLORS, SPACING, BORDERS } from '@opentrons/components' +import { BORDERS, COLORS, CURSOR_DEFAULT, SPACING } from '@opentrons/components' -import { ODD_FOCUS_VISIBLE } from '../../atoms/buttons/constants' +import { ODD_FOCUS_VISIBLE } from '/app/atoms/buttons/constants' export const TouchControlButton = styled.button<{ selected: boolean }>` background-color: ${({ selected }) => selected ? COLORS.blue50 : COLORS.blue35}; - cursor: default; + cursor: ${CURSOR_DEFAULT}; border-radius: ${BORDERS.borderRadius16}; box-shadow: none; padding: ${SPACING.spacing8} ${SPACING.spacing20}; diff --git a/app/src/molecules/JogControls/index.tsx b/app/src/molecules/JogControls/index.tsx index c9d0d7b49f0..0208739d025 100644 --- a/app/src/molecules/JogControls/index.tsx +++ b/app/src/molecules/JogControls/index.tsx @@ -1,5 +1,5 @@ // jog controls component -import * as React from 'react' +import { useState } from 'react' import { css } from 'styled-components' import { Flex, @@ -20,15 +20,16 @@ import { DEFAULT_STEP_SIZES, } from './constants' -import type { Jog, Plane, StepSize } from './types' +import type { ReactNode } from 'react' import type { StyleProps } from '@opentrons/components' +import type { Jog, Plane, StepSize } from './types' export type { Jog } export interface JogControlsProps extends StyleProps { jog: Jog planes?: Plane[] stepSizes?: StepSize[] - auxiliaryControl?: React.ReactNode | null + auxiliaryControl?: ReactNode | null directionControlButtonColor?: string initialPlane?: Plane isOnDevice?: boolean @@ -53,9 +54,7 @@ export function JogControls(props: JogControlsProps): JSX.Element { isOnDevice = false, ...styleProps } = props - const [currentStepSize, setCurrentStepSize] = React.useState( - stepSizes[0] - ) + const [currentStepSize, setCurrentStepSize] = useState(stepSizes[0]) const controls = isOnDevice ? ( <> diff --git a/app/src/molecules/LabwareStackModal/LabwareStackModal.tsx b/app/src/molecules/LabwareStackModal/LabwareStackModal.tsx new file mode 100644 index 00000000000..83b588f4a6e --- /dev/null +++ b/app/src/molecules/LabwareStackModal/LabwareStackModal.tsx @@ -0,0 +1,325 @@ +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' +import { css } from 'styled-components' +import { + ALIGN_CENTER, + Box, + COLORS, + DeckInfoLabel, + DIRECTION_COLUMN, + Flex, + JUSTIFY_CENTER, + JUSTIFY_SPACE_BETWEEN, + LabwareStackRender, + SPACING, + StyledText, + Modal, +} from '@opentrons/components' +import { OddModal } from '/app/molecules/OddModal' +import { getIsOnDevice } from '/app/redux/config' +import { + getLocationInfoNames, + getSlotLabwareDefinition, +} from '/app/transformations/commands' +import { Divider } from '/app/atoms/structure' +import { getModuleImage } from '/app/local-resources/modules' +import { + FLEX_ROBOT_TYPE, + getModuleDisplayName, + getModuleType, + TC_MODULE_LOCATION_OT2, + TC_MODULE_LOCATION_OT3, + THERMOCYCLER_MODULE_TYPE, +} from '@opentrons/shared-data' +import tiprackAdapter from '/app/assets/images/labware/opentrons_flex_96_tiprack_adapter.png' +import tcLid from '/app/assets/images/labware/opentrons_tough_pcr_auto_sealing_lid.png' +import deckRiser from '/app/assets/images/labware/opentrons_flex_deck_riser.png' + +import type { RobotType, RunTimeCommand } from '@opentrons/shared-data' + +const HIDE_SCROLLBAR = css` + ::-webkit-scrollbar { + display: none; + } +` + +const IMAGE_STYLE = css` + max-width: 11.5rem; + max-height: 6.875rem; +` + +const IMAGE_CONTAINER_STYLE = css` + width: 11.5rem; + height: 100%; + justify-content: ${JUSTIFY_CENTER}; +` + +const LIST_ITEM_STYLE = css` + align-items: ${ALIGN_CENTER}; + height: 6.875rem; + justify-content: ${JUSTIFY_SPACE_BETWEEN}; +` + +const ADAPTER_LOAD_NAMES_TO_SHOW_IMAGE: { [key: string]: string } = { + opentrons_flex_96_tiprack_adapter: tiprackAdapter, + opentrons_flex_deck_riser: deckRiser, +} +const LABWARE_LOAD_NAMES_TO_SHOW_IMAGE: { [key: string]: string } = { + opentrons_tough_pcr_auto_sealing_lid: tcLid, +} +interface LabwareStackModalProps { + labwareIdTop: string + commands: RunTimeCommand[] | null + closeModal: () => void + robotType?: RobotType +} + +export const LabwareStackModal = ( + props: LabwareStackModalProps +): JSX.Element | null => { + const { + labwareIdTop, + commands, + closeModal, + robotType = FLEX_ROBOT_TYPE, + } = props + const { t } = useTranslation('protocol_setup') + const isOnDevice = useSelector(getIsOnDevice) + + if (commands == null) { + return null + } + const { + slotName, + adapterName, + adapterId, + moduleModel, + labwareName, + labwareNickname, + labwareQuantity, + } = getLocationInfoNames(labwareIdTop, commands) + + const topDefinition = getSlotLabwareDefinition(labwareIdTop, commands) + const adapterDef = + adapterId != null + ? getSlotLabwareDefinition(adapterId ?? '', commands) + : null + const isModuleThermocycler = + moduleModel == null + ? false + : getModuleType(moduleModel) === THERMOCYCLER_MODULE_TYPE + const thermocyclerLocation = + robotType === FLEX_ROBOT_TYPE + ? TC_MODULE_LOCATION_OT3 + : TC_MODULE_LOCATION_OT2 + const moduleDisplayName = + moduleModel != null ? getModuleDisplayName(moduleModel) : null ?? '' + const isAdapterForTiprack = + adapterDef?.parameters.loadName === 'opentrons_flex_96_tiprack_adapter' + + const labwareImg = + topDefinition.parameters.loadName in LABWARE_LOAD_NAMES_TO_SHOW_IMAGE ? ( + + ) : null + + const adapterImg = + adapterDef != null && + adapterDef.parameters.loadName in ADAPTER_LOAD_NAMES_TO_SHOW_IMAGE ? ( + + ) : null + const moduleImg = + moduleModel != null ? ( + + ) : null + + return isOnDevice ? ( + + + + + ), + onClick: closeModal, + padding: `${SPACING.spacing32} ${SPACING.spacing32} ${SPACING.spacing12}`, + }} + > + + <> + + 1 + ? t('labware_quantity', { quantity: labwareQuantity }) + : labwareNickname + } + /> + {labwareImg != null ? ( + {labwareImg} + ) : ( + + + + )} + + + {adapterDef != null ? ( + <> + + + + {adapterImg != null ? ( + {adapterImg} + ) : ( + + + + )} + + + ) : null} + {moduleModel != null ? ( + <> + + + + {moduleImg} + + + ) : null} + + + ) : ( + + } + titleElement2={} + childrenPadding={0} + marginLeft="0" + > + + + <> + + 1 + ? t('labware_quantity', { quantity: labwareQuantity }) + : labwareNickname + } + /> + {labwareImg != null ? ( + {labwareImg} + ) : ( + + + + )} + + + {adapterDef != null ? ( + <> + + + + {adapterImg != null ? ( + {adapterImg} + ) : ( + + + + )} + + + ) : null} + {moduleModel != null ? ( + <> + + + + {moduleImg} + + + ) : null} + + + + ) +} + +interface LabwareStackLabelProps { + text: string + subText?: string + isOnDevice?: boolean +} +function LabwareStackLabel(props: LabwareStackLabelProps): JSX.Element { + const { text, subText, isOnDevice = false } = props + return ( + + + {text} + + {subText != null ? ( + + {subText} + + ) : null} + + ) +} diff --git a/app/src/molecules/LabwareStackModal/index.ts b/app/src/molecules/LabwareStackModal/index.ts new file mode 100644 index 00000000000..23f4662e721 --- /dev/null +++ b/app/src/molecules/LabwareStackModal/index.ts @@ -0,0 +1 @@ +export * from './LabwareStackModal' diff --git a/app/src/molecules/MiniCard/MiniCard.stories.tsx b/app/src/molecules/MiniCard/MiniCard.stories.tsx index 0a84fd6683f..8045c567d13 100644 --- a/app/src/molecules/MiniCard/MiniCard.stories.tsx +++ b/app/src/molecules/MiniCard/MiniCard.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { ALIGN_CENTER, Box, @@ -9,9 +9,9 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import OT2_PNG from '../../assets/images/OT2-R_HERO.png' +import OT2_PNG from '/app/assets/images/OT2-R_HERO.png' import { MiniCard } from './' -import { Slideout } from '../../atoms/Slideout' +import { Slideout } from '/app/atoms/Slideout' import type { Story, Meta } from '@storybook/react' diff --git a/app/src/molecules/MiniCard/__tests__/MiniCard.test.tsx b/app/src/molecules/MiniCard/__tests__/MiniCard.test.tsx index 6a7c3efd034..fcc76f38505 100644 --- a/app/src/molecules/MiniCard/__tests__/MiniCard.test.tsx +++ b/app/src/molecules/MiniCard/__tests__/MiniCard.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { fireEvent, screen } from '@testing-library/react' -import { COLORS, SPACING, BORDERS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' +import { COLORS, SPACING, BORDERS, CURSOR_POINTER } from '@opentrons/components' +import { renderWithProviders } from '/app/__testing-utils__' import { MiniCard } from '../' const render = (props: React.ComponentProps) => { @@ -30,7 +30,7 @@ describe('MiniCard', () => { expect(miniCard).toHaveStyle(`border-radius: ${BORDERS.borderRadius8}`) expect(miniCard).toHaveStyle(`padding: ${SPACING.spacing8}`) expect(miniCard).toHaveStyle(`width: 100%`) - expect(miniCard).toHaveStyle(`cursor: pointer`) + expect(miniCard).toHaveStyle(`cursor: ${CURSOR_POINTER}`) }) it('renders the correct style selectedOptionStyles', () => { @@ -42,7 +42,7 @@ describe('MiniCard', () => { expect(miniCard).toHaveStyle(`border-radius: ${BORDERS.borderRadius8}`) expect(miniCard).toHaveStyle(`padding: ${SPACING.spacing8}`) expect(miniCard).toHaveStyle(`width: 100%`) - expect(miniCard).toHaveStyle(`cursor: pointer`) + expect(miniCard).toHaveStyle(`cursor: ${CURSOR_POINTER}`) }) it('renders the correct style errorOptionStyles', () => { @@ -55,7 +55,7 @@ describe('MiniCard', () => { expect(miniCard).toHaveStyle(`border-radius: ${BORDERS.borderRadius8}`) expect(miniCard).toHaveStyle(`padding: ${SPACING.spacing8}`) expect(miniCard).toHaveStyle(`width: 100%`) - expect(miniCard).toHaveStyle(`cursor: pointer`) + expect(miniCard).toHaveStyle(`cursor: ${CURSOR_POINTER}`) }) it('calls mock function when clicking mini card', () => { diff --git a/app/src/molecules/MiniCard/index.tsx b/app/src/molecules/MiniCard/index.tsx index 2ae0f6724ad..b65ccbbb59d 100644 --- a/app/src/molecules/MiniCard/index.tsx +++ b/app/src/molecules/MiniCard/index.tsx @@ -1,6 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' -import { SPACING, Flex, COLORS, BORDERS } from '@opentrons/components' +import { + BORDERS, + COLORS, + CURSOR_POINTER, + Flex, + SPACING, +} from '@opentrons/components' import type { StyleProps } from '@opentrons/components' @@ -17,7 +23,7 @@ const unselectedOptionStyles = css` border-radius: ${BORDERS.borderRadius8}; padding: ${SPACING.spacing8}; width: 100%; - cursor: pointer; + cursor: ${CURSOR_POINTER}; &:hover { background-color: ${COLORS.grey10}; diff --git a/app/src/molecules/ModuleIcon/ModuleIcon.stories.tsx b/app/src/molecules/ModuleIcon/ModuleIcon.stories.tsx index 312b71a4936..002c0971e8f 100644 --- a/app/src/molecules/ModuleIcon/ModuleIcon.stories.tsx +++ b/app/src/molecules/ModuleIcon/ModuleIcon.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { DIRECTION_COLUMN, @@ -11,7 +11,7 @@ import { import { ModuleIcon } from './index' import type { Story, Meta } from '@storybook/react' -import type { AttachedModule } from '../../redux/modules/types' +import type { AttachedModule } from '/app/redux/modules/types' export default { title: 'App/Molecules/ModuleIcon', diff --git a/app/src/molecules/ModuleIcon/__tests__/ModuleIcon.test.tsx b/app/src/molecules/ModuleIcon/__tests__/ModuleIcon.test.tsx index 49e0ed4c118..73c44639e51 100644 --- a/app/src/molecules/ModuleIcon/__tests__/ModuleIcon.test.tsx +++ b/app/src/molecules/ModuleIcon/__tests__/ModuleIcon.test.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { COLORS, SPACING } from '@opentrons/components' import { describe, it, expect, vi, beforeEach } from 'vitest' import { screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { ModuleIcon } from '../' -import type { AttachedModule } from '../../../redux/modules/types' +import type { AttachedModule } from '/app/redux/modules/types' import type * as OpentronsComponents from '@opentrons/components' vi.mock('@opentrons/components', async importOriginal => { diff --git a/app/src/molecules/ModuleIcon/index.tsx b/app/src/molecules/ModuleIcon/index.tsx index 1e08bd8e981..671b7b9210c 100644 --- a/app/src/molecules/ModuleIcon/index.tsx +++ b/app/src/molecules/ModuleIcon/index.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { css } from 'styled-components' import { @@ -11,7 +10,7 @@ import { useHoverTooltip, } from '@opentrons/components' -import type { AttachedModule } from '../../redux/modules/types' +import type { AttachedModule } from '/app/redux/modules/types' const MODULE_ICON_STYLE = css` &:hover { diff --git a/app/src/organisms/Devices/ModuleInfo.tsx b/app/src/molecules/ModuleInfo/ModuleInfo.tsx similarity index 95% rename from app/src/organisms/Devices/ModuleInfo.tsx rename to app/src/molecules/ModuleInfo/ModuleInfo.tsx index b7e678310f4..3e68079bd5f 100644 --- a/app/src/organisms/Devices/ModuleInfo.tsx +++ b/app/src/molecules/ModuleInfo/ModuleInfo.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, @@ -19,9 +18,9 @@ import { MAGNETIC_BLOCK_V1, } from '@opentrons/shared-data' -import { useRunHasStarted } from './hooks' +import { useRunHasStarted } from '/app/resources/runs' import type { ModuleModel } from '@opentrons/shared-data' -import type { PhysicalPort } from '../../redux/modules/api-types' +import type { PhysicalPort } from '/app/redux/modules/api-types' export interface ModuleInfoProps { moduleModel: ModuleModel diff --git a/app/src/organisms/Devices/__tests__/ModuleInfo.test.tsx b/app/src/molecules/ModuleInfo/__tests__/ModuleInfo.test.tsx similarity index 91% rename from app/src/organisms/Devices/__tests__/ModuleInfo.test.tsx rename to app/src/molecules/ModuleInfo/__tests__/ModuleInfo.test.tsx index c1d494690e8..1d732faeb18 100644 --- a/app/src/organisms/Devices/__tests__/ModuleInfo.test.tsx +++ b/app/src/molecules/ModuleInfo/__tests__/ModuleInfo.test.tsx @@ -1,15 +1,15 @@ -import React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { when } from 'vitest-when' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { ModuleInfo } from '../ModuleInfo' -import { useRunHasStarted } from '../hooks' +import { useRunHasStarted } from '/app/resources/runs' import type { ModuleModel, ModuleType } from '@opentrons/shared-data' -vi.mock('../hooks') +vi.mock('/app/resources/runs') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/molecules/ModuleInfo/index.ts b/app/src/molecules/ModuleInfo/index.ts new file mode 100644 index 00000000000..0fdce59dc8a --- /dev/null +++ b/app/src/molecules/ModuleInfo/index.ts @@ -0,0 +1 @@ +export * from './ModuleInfo' diff --git a/app/src/molecules/NavTab/NavTab.stories.tsx b/app/src/molecules/NavTab/NavTab.stories.tsx index a9b54818b51..d4847a1ed0f 100644 --- a/app/src/molecules/NavTab/NavTab.stories.tsx +++ b/app/src/molecules/NavTab/NavTab.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { Flex, diff --git a/app/src/molecules/NavTab/__tests__/NavTab.test.tsx b/app/src/molecules/NavTab/__tests__/NavTab.test.tsx index 0c5e608363d..176c76b60cc 100644 --- a/app/src/molecules/NavTab/__tests__/NavTab.test.tsx +++ b/app/src/molecules/NavTab/__tests__/NavTab.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' import { describe, it, expect, beforeEach } from 'vitest' import { MemoryRouter } from 'react-router-dom' import { SPACING, COLORS, TYPOGRAPHY, BORDERS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { NavTab } from '..' const render = (props: React.ComponentProps) => { diff --git a/app/src/molecules/NavTab/index.tsx b/app/src/molecules/NavTab/index.tsx index 97d6e4a9f12..e170da56e43 100644 --- a/app/src/molecules/NavTab/index.tsx +++ b/app/src/molecules/NavTab/index.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import styled, { css } from 'styled-components' import { NavLink } from 'react-router-dom' diff --git a/app/src/molecules/ODDBackButton/ODDBackButton.stories.tsx b/app/src/molecules/ODDBackButton/ODDBackButton.stories.tsx index 6fad4d7ae4a..32719ce1665 100644 --- a/app/src/molecules/ODDBackButton/ODDBackButton.stories.tsx +++ b/app/src/molecules/ODDBackButton/ODDBackButton.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { VIEWPORT } from '@opentrons/components' import { ODDBackButton } from '.' import type { Story, Meta } from '@storybook/react' diff --git a/app/src/molecules/ODDBackButton/__tests__/ODDBackButton.test.tsx b/app/src/molecules/ODDBackButton/__tests__/ODDBackButton.test.tsx index 6ff9a730ba7..2b520279cf8 100644 --- a/app/src/molecules/ODDBackButton/__tests__/ODDBackButton.test.tsx +++ b/app/src/molecules/ODDBackButton/__tests__/ODDBackButton.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import '@testing-library/jest-dom/vitest' import { describe, it, expect, vi, beforeEach } from 'vitest' import { fireEvent, screen } from '@testing-library/react' import { COLORS } from '@opentrons/components' import { ODDBackButton } from '..' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' const render = (props: React.ComponentProps) => { return renderWithProviders()[0] diff --git a/app/src/molecules/ODDBackButton/index.tsx b/app/src/molecules/ODDBackButton/index.tsx index 9dff1968160..396feaa5e19 100644 --- a/app/src/molecules/ODDBackButton/index.tsx +++ b/app/src/molecules/ODDBackButton/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { ALIGN_CENTER, diff --git a/app/src/organisms/CalibrationPanels/NeedHelpLink.tsx b/app/src/molecules/OT2CalibrationNeedHelpLink/NeedHelpLink.tsx similarity index 96% rename from app/src/organisms/CalibrationPanels/NeedHelpLink.tsx rename to app/src/molecules/OT2CalibrationNeedHelpLink/NeedHelpLink.tsx index 01dac649b39..a87a7808827 100644 --- a/app/src/organisms/CalibrationPanels/NeedHelpLink.tsx +++ b/app/src/molecules/OT2CalibrationNeedHelpLink/NeedHelpLink.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { Flex, diff --git a/app/src/molecules/OT2CalibrationNeedHelpLink/index.ts b/app/src/molecules/OT2CalibrationNeedHelpLink/index.ts new file mode 100644 index 00000000000..38a22581118 --- /dev/null +++ b/app/src/molecules/OT2CalibrationNeedHelpLink/index.ts @@ -0,0 +1 @@ +export * from './NeedHelpLink' diff --git a/app/src/molecules/OddModal/ModalHeader.stories.tsx b/app/src/molecules/OddModal/ModalHeader.stories.tsx index adf9524988e..d6e5d95c598 100644 --- a/app/src/molecules/OddModal/ModalHeader.stories.tsx +++ b/app/src/molecules/OddModal/ModalHeader.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { COLORS, VIEWPORT } from '@opentrons/components' import { OddModalHeader } from './OddModalHeader' import type { Story, Meta } from '@storybook/react' diff --git a/app/src/molecules/OddModal/OddModal.stories.tsx b/app/src/molecules/OddModal/OddModal.stories.tsx index 1ba0914779b..1210ad31005 100644 --- a/app/src/molecules/OddModal/OddModal.stories.tsx +++ b/app/src/molecules/OddModal/OddModal.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { COLORS, Flex, BORDERS, SPACING, VIEWPORT } from '@opentrons/components' import { OddModal } from './OddModal' import type { Story, Meta } from '@storybook/react' diff --git a/app/src/molecules/OddModal/OddModal.tsx b/app/src/molecules/OddModal/OddModal.tsx index 1f18e3e1fd9..9b32974000c 100644 --- a/app/src/molecules/OddModal/OddModal.tsx +++ b/app/src/molecules/OddModal/OddModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { ALIGN_CENTER, BORDERS, diff --git a/app/src/molecules/OddModal/OddModalHeader.tsx b/app/src/molecules/OddModal/OddModalHeader.tsx index cd79d1a81f5..5f2a403a8af 100644 --- a/app/src/molecules/OddModal/OddModalHeader.tsx +++ b/app/src/molecules/OddModal/OddModalHeader.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { ALIGN_CENTER, BORDERS, @@ -35,7 +34,7 @@ export function OddModalHeader(props: OddModalHeaderBaseProps): JSX.Element { borderRadius={`${BORDERS.borderRadius12} ${BORDERS.borderRadius12} 0px 0px`} {...styleProps} > - + {iconName != null && iconColor != null ? ( ) => { diff --git a/app/src/molecules/OddModal/__tests__/SmallModalChildren.test.tsx b/app/src/molecules/OddModal/__tests__/SmallModalChildren.test.tsx index bb22935b47d..77a6e56095b 100644 --- a/app/src/molecules/OddModal/__tests__/SmallModalChildren.test.tsx +++ b/app/src/molecules/OddModal/__tests__/SmallModalChildren.test.tsx @@ -1,8 +1,7 @@ -import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' import { describe, it, expect, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { SmallModalChildren } from '../SmallModalChildren' const props = { diff --git a/app/src/molecules/OffsetVector/__tests__/OffsetVector.test.tsx b/app/src/molecules/OffsetVector/__tests__/OffsetVector.test.tsx index 4981fcc8138..bb247c0175a 100644 --- a/app/src/molecules/OffsetVector/__tests__/OffsetVector.test.tsx +++ b/app/src/molecules/OffsetVector/__tests__/OffsetVector.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' import { describe, it, expect, beforeEach } from 'vitest' import { SPACING, TYPOGRAPHY } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { OffsetVector } from '../' diff --git a/app/src/molecules/OffsetVector/index.tsx b/app/src/molecules/OffsetVector/index.tsx index 47d4cf639d6..155019e8074 100644 --- a/app/src/molecules/OffsetVector/index.tsx +++ b/app/src/molecules/OffsetVector/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Flex, SPACING, diff --git a/app/src/molecules/PipetteSelect/index.tsx b/app/src/molecules/PipetteSelect/index.tsx index 75e3e3bb985..aec4e45300f 100644 --- a/app/src/molecules/PipetteSelect/index.tsx +++ b/app/src/molecules/PipetteSelect/index.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import groupBy from 'lodash/groupBy' import { useTranslation } from 'react-i18next' import { @@ -10,11 +9,11 @@ import { EIGHT_CHANNEL, } from '@opentrons/shared-data' import { Box, Flex } from '@opentrons/components' -import { Select } from '../../atoms/SelectField/Select' +import { Select } from '/app/atoms/SelectField/Select' import type { PipetteNameSpecs } from '@opentrons/shared-data' import type { ActionMeta, SingleValue, MultiValue } from 'react-select' -import type { SelectOption } from '../../atoms/SelectField/Select' +import type { SelectOption } from '/app/atoms/SelectField/Select' export interface PipetteSelectProps { /** currently selected value, optional in case selecting triggers immediate action */ diff --git a/app/src/molecules/PythonLabwareOffsetSnippet/createSnippet.ts b/app/src/molecules/PythonLabwareOffsetSnippet/createSnippet.ts index 624e46ffaf6..79a5a70fad2 100644 --- a/app/src/molecules/PythonLabwareOffsetSnippet/createSnippet.ts +++ b/app/src/molecules/PythonLabwareOffsetSnippet/createSnippet.ts @@ -1,7 +1,7 @@ import isEqual from 'lodash/isEqual' import { getLoadedLabwareDefinitionsByUri } from '@opentrons/shared-data' -import { getLabwareDefinitionUri } from '../../organisms/Devices/ProtocolRun/utils/getLabwareDefinitionUri' -import { getLabwareOffsetLocation } from '../../organisms/Devices/ProtocolRun/utils/getLabwareOffsetLocation' +import { getLabwareDefinitionUri } from '/app/transformations/protocols' +import { getLabwareOffsetLocation } from '/app/transformations/analysis' import type { LabwareOffset } from '@opentrons/api-client' import type { LoadedLabware, diff --git a/app/src/molecules/PythonLabwareOffsetSnippet/index.tsx b/app/src/molecules/PythonLabwareOffsetSnippet/index.tsx index 00862359a37..d6e1e4bbd7d 100644 --- a/app/src/molecules/PythonLabwareOffsetSnippet/index.tsx +++ b/app/src/molecules/PythonLabwareOffsetSnippet/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import styled from 'styled-components' import { TYPOGRAPHY, SPACING, BORDERS, COLORS } from '@opentrons/components' import { createSnippet } from './createSnippet' @@ -32,8 +32,8 @@ export function PythonLabwareOffsetSnippet( props: PythonLabwareOffsetSnippetProps ): JSX.Element | null { const { commands, labware, modules, labwareOffsets, mode } = props - const [snippet, setSnippet] = React.useState(null) - React.useEffect(() => { + const [snippet, setSnippet] = useState(null) + useEffect(() => { if (labware.length > 0 && labwareOffsets != null) { setSnippet( createSnippet(mode, commands, labware, modules, labwareOffsets) diff --git a/app/src/molecules/ReleaseNotes/index.tsx b/app/src/molecules/ReleaseNotes/index.tsx index eec5149b75e..0c6207f84a1 100644 --- a/app/src/molecules/ReleaseNotes/index.tsx +++ b/app/src/molecules/ReleaseNotes/index.tsx @@ -1,9 +1,8 @@ -import * as React from 'react' import Markdown from 'react-markdown' import { Box, COLORS, SPACING, LegacyStyledText } from '@opentrons/components' -import { useIsOEMMode } from '../../resources/robot-settings/hooks' +import { useIsOEMMode } from '/app/resources/robot-settings/hooks' import styles from './styles.module.css' diff --git a/app/src/organisms/Devices/ProtocolRun/RunTimer.tsx b/app/src/molecules/RunTimer/RunTimer.tsx similarity index 81% rename from app/src/organisms/Devices/ProtocolRun/RunTimer.tsx rename to app/src/molecules/RunTimer/RunTimer.tsx index c5abc0c831f..74962c82bb5 100644 --- a/app/src/organisms/Devices/ProtocolRun/RunTimer.tsx +++ b/app/src/molecules/RunTimer/RunTimer.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { RUN_STATUS_STOP_REQUESTED } from '@opentrons/api-client' import { @@ -7,8 +7,8 @@ import { LegacyStyledText, } from '@opentrons/components' -import { formatInterval } from '../../../organisms/RunTimeControl/utils' -import { EMPTY_TIMESTAMP } from '../constants' +import { formatInterval } from '/app/transformations/commands' +import { EMPTY_TIMESTAMP } from '/app/resources/runs' import type { CSSProp } from 'styled-components' export function RunTimer({ @@ -24,7 +24,7 @@ export function RunTimer({ completedAt: string | null style?: CSSProp }): JSX.Element { - const [now, setNow] = React.useState(Date()) + const [now, setNow] = useState(Date()) useInterval( () => { setNow(Date()) diff --git a/app/src/molecules/RunTimer/index.ts b/app/src/molecules/RunTimer/index.ts new file mode 100644 index 00000000000..c48cfaa132c --- /dev/null +++ b/app/src/molecules/RunTimer/index.ts @@ -0,0 +1 @@ +export * from './RunTimer' diff --git a/app/src/molecules/SimpleWizardBody/SimpleWizardBody.stories.tsx b/app/src/molecules/SimpleWizardBody/SimpleWizardBody.stories.tsx index 240be51e27e..7e73f357d45 100644 --- a/app/src/molecules/SimpleWizardBody/SimpleWizardBody.stories.tsx +++ b/app/src/molecules/SimpleWizardBody/SimpleWizardBody.stories.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { Provider } from 'react-redux' import { createStore } from 'redux' import { COLORS, PrimaryButton, ModalShell } from '@opentrons/components' import { WizardHeader } from '../WizardHeader' -import { configReducer } from '../../redux/config/reducer' +import { configReducer } from '/app/redux/config/reducer' import { SimpleWizardBody } from './index' import type { Store, StoreEnhancer } from 'redux' diff --git a/app/src/molecules/SimpleWizardBody/SimpleWizardBodyContainer.tsx b/app/src/molecules/SimpleWizardBody/SimpleWizardBodyContainer.tsx index 11644dba212..fef84670b19 100644 --- a/app/src/molecules/SimpleWizardBody/SimpleWizardBodyContainer.tsx +++ b/app/src/molecules/SimpleWizardBody/SimpleWizardBodyContainer.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { css } from 'styled-components' import { diff --git a/app/src/molecules/SimpleWizardBody/SimpleWizardBodyContent.tsx b/app/src/molecules/SimpleWizardBody/SimpleWizardBodyContent.tsx index 5e79b1ff8bd..16113e28c9a 100644 --- a/app/src/molecules/SimpleWizardBody/SimpleWizardBodyContent.tsx +++ b/app/src/molecules/SimpleWizardBody/SimpleWizardBodyContent.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useSelector } from 'react-redux' import { css } from 'styled-components' import { @@ -17,10 +17,10 @@ import { TYPOGRAPHY, } from '@opentrons/components' import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' -import SuccessIcon from '../../assets/images/icon_success.png' -import { getIsOnDevice } from '../../redux/config' +import SuccessIcon from '/app/assets/images/icon_success.png' +import { getIsOnDevice } from '/app/redux/config' -import { Skeleton } from '../../atoms/Skeleton' +import { Skeleton } from '/app/atoms/Skeleton' import type { RobotType } from '@opentrons/shared-data' interface Props { diff --git a/app/src/molecules/SimpleWizardBody/SimpleWizardInProgressBody.tsx b/app/src/molecules/SimpleWizardBody/SimpleWizardInProgressBody.tsx index 55bd83b534b..10882025dfc 100644 --- a/app/src/molecules/SimpleWizardBody/SimpleWizardInProgressBody.tsx +++ b/app/src/molecules/SimpleWizardBody/SimpleWizardInProgressBody.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import type { StyleProps } from '@opentrons/components' import { InProgressModal } from '../InProgressModal/InProgressModal' import { SimpleWizardBodyContainer } from './SimpleWizardBodyContainer' diff --git a/app/src/molecules/SimpleWizardBody/__tests__/SimpleWizardBody.test.tsx b/app/src/molecules/SimpleWizardBody/__tests__/SimpleWizardBody.test.tsx index 5ffe283d5e9..9849e8fa03c 100644 --- a/app/src/molecules/SimpleWizardBody/__tests__/SimpleWizardBody.test.tsx +++ b/app/src/molecules/SimpleWizardBody/__tests__/SimpleWizardBody.test.tsx @@ -1,14 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import { COLORS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' -import { Skeleton } from '../../../atoms/Skeleton' -import { getIsOnDevice } from '../../../redux/config' +import { renderWithProviders } from '/app/__testing-utils__' +import { Skeleton } from '/app/atoms/Skeleton' +import { getIsOnDevice } from '/app/redux/config' import { SimpleWizardBody } from '..' -vi.mock('../../../atoms/Skeleton') -vi.mock('../../../redux/config') +vi.mock('/app/atoms/Skeleton') +vi.mock('/app/redux/config') const render = (props: React.ComponentProps) => { return renderWithProviders()[0] diff --git a/app/src/molecules/SimpleWizardBody/index.tsx b/app/src/molecules/SimpleWizardBody/index.tsx index c0408417030..b554b71b90e 100644 --- a/app/src/molecules/SimpleWizardBody/index.tsx +++ b/app/src/molecules/SimpleWizardBody/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { SimpleWizardBodyContainer } from './SimpleWizardBodyContainer' import { SimpleWizardBodyContent } from './SimpleWizardBodyContent' diff --git a/app/src/organisms/TaskList/TaskList.stories.tsx b/app/src/molecules/TaskList/TaskList.stories.tsx similarity index 99% rename from app/src/organisms/TaskList/TaskList.stories.tsx rename to app/src/molecules/TaskList/TaskList.stories.tsx index b17bf6204d2..5c6775b41cf 100644 --- a/app/src/organisms/TaskList/TaskList.stories.tsx +++ b/app/src/molecules/TaskList/TaskList.stories.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { TaskList as TaskListComponent } from './' import type { Story, Meta } from '@storybook/react' export default { - title: 'App/Organisms/TaskList', + title: 'App/Molecules/TaskList', component: TaskListComponent, } as Meta diff --git a/app/src/molecules/TaskList/index.tsx b/app/src/molecules/TaskList/index.tsx new file mode 100644 index 00000000000..3f45de76bcb --- /dev/null +++ b/app/src/molecules/TaskList/index.tsx @@ -0,0 +1,545 @@ +import { useState, useEffect, Fragment } from 'react' + +import { + ALIGN_CENTER, + ALIGN_FLEX_START, + BORDERS, + COLORS, + DIRECTION_COLUMN, + DIRECTION_ROW, + FLEX_NONE, + Flex, + Icon, + JUSTIFY_CENTER, + JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, + Link, + SPACING, + Tooltip, + TYPOGRAPHY, + useHoverTooltip, +} from '@opentrons/components' + +import { TertiaryButton } from '/app/atoms/buttons' + +import type { SubTaskProps, TaskListProps, TaskProps } from './types' +export type * from './types' + +const TASK_CONNECTOR_STYLE = `1px solid ${COLORS.grey40}` + +interface ProgressTrackerItemProps { + activeIndex: [number, number] | null + subTasks: SubTaskProps[] + taskIndex: number + taskListLength: number + isComplete?: boolean +} + +function ProgressTrackerItem({ + activeIndex, + subTasks, + taskIndex, + taskListLength, + isComplete = false, +}: ProgressTrackerItemProps): JSX.Element { + const [activeTaskIndex, activeSubTaskIndex] = activeIndex ?? [] + + const isTaskListComplete = activeIndex == null + const isPastTask = activeTaskIndex != null && taskIndex < activeTaskIndex + const isLastTask = taskIndex === taskListLength - 1 + const hasSubTasks = subTasks.length > 0 + const isActiveTaskWithSubtasks = taskIndex === activeTaskIndex && hasSubTasks + const isFutureTask = activeTaskIndex != null && taskIndex > activeTaskIndex + + // a connector between task icons + const taskConnector = ( + + ) + + const noSubTaskConnector = !isLastTask ? taskConnector : null + + return ( + + {isComplete || isTaskListComplete || isPastTask ? ( + + ) : ( + + + {(taskIndex + 1).toString()} + + + )} + {!hasSubTasks ? ( + noSubTaskConnector + ) : ( + <> + {/** + * iterate subtask completion list - + * APPROXIMATION: average amount of space via flex-grow to position the substep connectors/icons + * ASSUMPTION: substeps don't vary much in size for current use case - maybe one line of wrapped text at most + * TODO (bh, 9/28/2022): this could change in the future if the task list is used for tasks that contain differently sized children, like deck map rendering, etc + * a more robust solution to subtask icon layout could implement an n x 2 grid where n is the combined number of tasks/subtasks, in two columns (fixed size, 1fr) + * this would require top level coordination of both the number of tasks/subtasks and the open status of each task + * which is possible, but nice to avoid + * */} + {taskConnector} + {subTasks.map((subTask, subTaskIndex) => { + const isPastSubTask = + (activeTaskIndex != null && + activeSubTaskIndex != null && + subTaskIndex <= activeSubTaskIndex && + taskIndex < activeTaskIndex) || + (activeTaskIndex != null && + subTask.isComplete === true && + taskIndex <= activeTaskIndex) + const isFutureSubTask = + (activeSubTaskIndex != null && + activeTaskIndex != null && + subTaskIndex > activeSubTaskIndex && + taskIndex >= activeTaskIndex) || + isFutureTask + // last subtask of the parent task + const isLastSubTask = subTaskIndex === subTasks.length - 1 + // last subtask of the last task of the entire list + const isFinalSubTaskOfTaskList = isLastSubTask && isLastTask + + return ( + + {/* subtask circle icon component */} + + {/* subtask connector component */} + + + ) + })} + + )} + + ) +} + +function SubTask({ + activeIndex, + subTaskIndex, + taskIndex, + title, + description, + cta, + footer, + markedBad, + generalClickHandler, + generalTaskDisabledReason, +}: SubTaskProps): JSX.Element { + const [targetProps, tooltipProps] = useHoverTooltip() + + const [activeTaskIndex, activeSubTaskIndex] = activeIndex ?? [] + + const isTaskListComplete = activeIndex == null + const isActiveSubTask = + activeSubTaskIndex === subTaskIndex && activeTaskIndex === taskIndex + const isPastSubTask = + activeTaskIndex != null && + activeSubTaskIndex != null && + ((activeSubTaskIndex > subTaskIndex && activeTaskIndex === taskIndex) || + activeTaskIndex > taskIndex) + const isDisabled = generalTaskDisabledReason != null + + return ( + + + + + {title} + + + {description} + {footer != null ? ( + + + {markedBad === true && ( + + )} + {footer} + + + ) : null} + + {(isTaskListComplete || isPastSubTask) && cta != null ? ( + <> + { + if (isDisabled) { + return + } + if (generalClickHandler != null) { + generalClickHandler() + } + cta.onClick() + }} + > + {cta.label} + + {isDisabled ? ( + + {generalTaskDisabledReason} + + ) : null} + + ) : null} + {isActiveSubTask && cta != null ? ( + <> + { + if (generalClickHandler != null) { + generalClickHandler() + } + cta.onClick() + }} + > + {cta.label} + + {isDisabled ? ( + + {generalTaskDisabledReason} + + ) : null} + + ) : null} + + ) +} + +function Task({ + activeIndex, + taskIndex, + title, + description, + cta, + footer, + subTasks, + taskListLength, + isComplete, + markedBad, + generalClickHandler, + generalTaskDisabledReason, +}: TaskProps): JSX.Element { + const [targetProps, tooltipProps] = useHoverTooltip() + const [activeTaskIndex] = activeIndex ?? [] + + // TODO(bh, 2022-10-18): pass booleans to children as props + const isTaskListComplete = activeIndex == null + const isPastTask = activeTaskIndex != null && taskIndex < activeTaskIndex + const isActiveTask = activeTaskIndex === taskIndex + const hasSubTasks = subTasks.length > 0 + const isDisabled = generalTaskDisabledReason != null + + const [isTaskOpen, setIsTaskOpen] = useState( + hasSubTasks && isActiveTask + ) + + useEffect(() => { + setIsTaskOpen(hasSubTasks && isActiveTask) + }, [isActiveTask, hasSubTasks]) + + return ( + + + + { + if (hasSubTasks) setIsTaskOpen(!isTaskOpen) + }} + > + + + + {markedBad === true && ( + + )} + {title} + + + {description} + {footer != null ? ( + + + {footer} + + + ) : null} + + {/* if subtasks, caret, otherwise show cta as link or button */} + {hasSubTasks ? ( + + ) : (isTaskListComplete || isPastTask) && cta != null ? ( + <> + { + if (isDisabled) { + return + } + if (generalClickHandler != null) { + generalClickHandler() + } + cta.onClick() + }} + > + {cta.label} + + {isDisabled ? ( + + {generalTaskDisabledReason} + + ) : null} + + ) : null} + {isActiveTask && cta != null ? ( + <> + { + if (generalClickHandler != null) { + generalClickHandler() + } + cta.onClick() + }} + > + {cta.label} + + {isDisabled ? ( + + {generalTaskDisabledReason} + + ) : null} + + ) : null} + + {isTaskOpen ? ( + + {subTasks.map( + ( + { title, description, cta, footer, markedBad }, + subTaskIndex + ) => ( + + ) + )} + + ) : null} + + + ) +} + +export function TaskList({ + activeIndex, + taskList, + generalTaskClickHandler, + generalTaskDisabledReason, +}: TaskListProps): JSX.Element { + return ( + + {taskList.map( + ( + { title, description, cta, footer, subTasks, isComplete, markedBad }, + taskIndex + ) => ( + + ) + )} + + ) +} diff --git a/app/src/organisms/TaskList/types.ts b/app/src/molecules/TaskList/types.ts similarity index 100% rename from app/src/organisms/TaskList/types.ts rename to app/src/molecules/TaskList/types.ts diff --git a/app/src/molecules/ToggleGroup/__tests__/useToggleGroup.test.tsx b/app/src/molecules/ToggleGroup/__tests__/useToggleGroup.test.tsx index e7c71d35c6d..c4829b79615 100644 --- a/app/src/molecules/ToggleGroup/__tests__/useToggleGroup.test.tsx +++ b/app/src/molecules/ToggleGroup/__tests__/useToggleGroup.test.tsx @@ -1,16 +1,16 @@ -import * as React from 'react' +import type * as React from 'react' import { Provider } from 'react-redux' import { createStore } from 'redux' import '@testing-library/jest-dom/vitest' import { describe, it, expect, vi, beforeEach } from 'vitest' import { renderHook, render, fireEvent, screen } from '@testing-library/react' -import { useTrackEvent } from '../../../redux/analytics' +import { useTrackEvent } from '/app/redux/analytics' import { useToggleGroup } from '../useToggleGroup' import type { Store } from 'redux' -import type { State } from '../../../redux/types' +import type { State } from '/app/redux/types' -vi.mock('../../../redux/analytics') +vi.mock('/app/redux/analytics') let mockTrackEvent: any diff --git a/app/src/molecules/ToggleGroup/useToggleGroup.tsx b/app/src/molecules/ToggleGroup/useToggleGroup.tsx index 7051871807e..5b356ba74cd 100644 --- a/app/src/molecules/ToggleGroup/useToggleGroup.tsx +++ b/app/src/molecules/ToggleGroup/useToggleGroup.tsx @@ -1,13 +1,15 @@ -import * as React from 'react' +import { useState } from 'react' import { ToggleGroup } from '@opentrons/components' -import { useTrackEvent } from '../../redux/analytics' +import { useTrackEvent } from '/app/redux/analytics' + +import type { ReactNode } from 'react' export const useToggleGroup = ( left: string, right: string, trackEventName?: string -): [string, React.ReactNode] => { - const [selectedValue, setSelectedValue] = React.useState(left) +): [string, ReactNode] => { + const [selectedValue, setSelectedValue] = useState(left) const trackEvent = useTrackEvent() const handleLeftClick = (): void => { setSelectedValue(left) diff --git a/app/src/molecules/UnorderedList/index.tsx b/app/src/molecules/UnorderedList/index.tsx index 685adc170f5..cf9937266a8 100644 --- a/app/src/molecules/UnorderedList/index.tsx +++ b/app/src/molecules/UnorderedList/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { SPACING, LegacyStyledText } from '@opentrons/components' diff --git a/app/src/molecules/UpdateBanner/__tests__/UpdateBanner.test.tsx b/app/src/molecules/UpdateBanner/__tests__/UpdateBanner.test.tsx index 22663443a5e..52b074b37d8 100644 --- a/app/src/molecules/UpdateBanner/__tests__/UpdateBanner.test.tsx +++ b/app/src/molecules/UpdateBanner/__tests__/UpdateBanner.test.tsx @@ -1,16 +1,16 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { when } from 'vitest-when' import { fireEvent, screen } from '@testing-library/react' -import { i18n } from '../../../i18n' -import { useIsFlex } from '../../../organisms/Devices/hooks' -import { useIsEstopNotDisengaged } from '../../../resources/devices/hooks/useIsEstopNotDisengaged' +import { i18n } from '/app/i18n' +import { useIsFlex } from '/app/redux-resources/robots' +import { useIsEstopNotDisengaged } from '/app/resources/devices/hooks/useIsEstopNotDisengaged' import { UpdateBanner } from '..' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -vi.mock('../../../organisms/Devices/hooks') -vi.mock('../../../resources/devices/hooks/useIsEstopNotDisengaged') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/resources/devices/hooks/useIsEstopNotDisengaged') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/molecules/UpdateBanner/index.tsx b/app/src/molecules/UpdateBanner/index.tsx index abe657e2237..31a7dbe3d49 100644 --- a/app/src/molecules/UpdateBanner/index.tsx +++ b/app/src/molecules/UpdateBanner/index.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { ALIGN_START, Btn, + Banner, DIRECTION_COLUMN, Flex, SPACING, @@ -11,10 +11,9 @@ import { TYPOGRAPHY, useHoverTooltip, } from '@opentrons/components' +import { useIsFlex } from '/app/redux-resources/robots' -import { Banner } from '../../atoms/Banner' -import { useIsFlex } from '../../organisms/Devices/hooks' -import { useIsEstopNotDisengaged } from '../../resources/devices/hooks/useIsEstopNotDisengaged' +import { useIsEstopNotDisengaged } from '/app/resources/devices/hooks/useIsEstopNotDisengaged' interface UpdateBannerProps { robotName: string diff --git a/app/src/molecules/UploadInput/UploadInput.stories.tsx b/app/src/molecules/UploadInput/UploadInput.stories.tsx index d27cb0198d8..461e23fd01a 100644 --- a/app/src/molecules/UploadInput/UploadInput.stories.tsx +++ b/app/src/molecules/UploadInput/UploadInput.stories.tsx @@ -1,5 +1,3 @@ -import * as React from 'react' - import { ALIGN_CENTER, COLORS, diff --git a/app/src/molecules/UploadInput/__tests__/UploadInput.test.tsx b/app/src/molecules/UploadInput/__tests__/UploadInput.test.tsx index ad51fdd8a08..5c99328ade3 100644 --- a/app/src/molecules/UploadInput/__tests__/UploadInput.test.tsx +++ b/app/src/molecules/UploadInput/__tests__/UploadInput.test.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { fireEvent, screen } from '@testing-library/react' @@ -10,9 +9,9 @@ import { SPACING, LegacyStyledText, } from '@opentrons/components' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { UploadInput } from '..' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' describe('UploadInput', () => { let onUpload: any diff --git a/app/src/molecules/UploadInput/index.tsx b/app/src/molecules/UploadInput/index.tsx index 62a6cccfe0e..89877f38c33 100644 --- a/app/src/molecules/UploadInput/index.tsx +++ b/app/src/molecules/UploadInput/index.tsx @@ -1,25 +1,32 @@ -import * as React from 'react' +import { useRef, useState } from 'react' import styled, { css } from 'styled-components' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, BORDERS, COLORS, + CURSOR_POINTER, DIRECTION_COLUMN, DISPLAY_FLEX, Flex, Icon, JUSTIFY_CENTER, + LegacyStyledText, POSITION_FIXED, PrimaryButton, SPACING, - LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' +import type { + ChangeEventHandler, + DragEventHandler, + MouseEventHandler, +} from 'react' + const StyledLabel = styled.label` display: ${DISPLAY_FLEX}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; flex-direction: ${DIRECTION_COLUMN}; align-items: ${ALIGN_CENTER}; width: 100%; @@ -29,8 +36,7 @@ const StyledLabel = styled.label` text-align: ${TYPOGRAPHY.textAlignCenter}; background-color: ${COLORS.white}; - &:hover, - &:focus-within { + &:hover { border: 2px dashed ${COLORS.blue50}; } ` @@ -66,39 +72,37 @@ export function UploadInput(props: UploadInputProps): JSX.Element | null { } = props const { t } = useTranslation('protocol_info') - const fileInput = React.useRef(null) - const [isFileOverDropZone, setIsFileOverDropZone] = React.useState( - false - ) - const [isHover, setIsHover] = React.useState(false) - const handleDrop: React.DragEventHandler = e => { + const fileInput = useRef(null) + const [isFileOverDropZone, setIsFileOverDropZone] = useState(false) + const [isHover, setIsHover] = useState(false) + const handleDrop: DragEventHandler = e => { e.preventDefault() e.stopPropagation() Array.from(e.dataTransfer.files).forEach(f => onUpload(f)) setIsFileOverDropZone(false) } - const handleDragEnter: React.DragEventHandler = e => { + const handleDragEnter: DragEventHandler = e => { e.preventDefault() e.stopPropagation() } - const handleDragLeave: React.DragEventHandler = e => { + const handleDragLeave: DragEventHandler = e => { e.preventDefault() e.stopPropagation() setIsFileOverDropZone(false) setIsHover(false) } - const handleDragOver: React.DragEventHandler = e => { + const handleDragOver: DragEventHandler = e => { e.preventDefault() e.stopPropagation() setIsFileOverDropZone(true) setIsHover(true) } - const handleClick: React.MouseEventHandler = _event => { + const handleClick: MouseEventHandler = _event => { onClick != null ? onClick() : fileInput.current?.click() } - const onChange: React.ChangeEventHandler = event => { + const onChange: ChangeEventHandler = event => { ;[...(event.target.files ?? [])].forEach(f => onUpload(f)) if ('value' in event.currentTarget) event.currentTarget.value = '' } diff --git a/app/src/molecules/WizardHeader/WizardHeader.stories.tsx b/app/src/molecules/WizardHeader/WizardHeader.stories.tsx index 70db49aa835..0d26e4f967d 100644 --- a/app/src/molecules/WizardHeader/WizardHeader.stories.tsx +++ b/app/src/molecules/WizardHeader/WizardHeader.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { WizardHeader } from './index' import type { Story, Meta } from '@storybook/react' diff --git a/app/src/molecules/WizardHeader/__tests__/WizardHeader.test.tsx b/app/src/molecules/WizardHeader/__tests__/WizardHeader.test.tsx index 38f431930cf..0e11447cac2 100644 --- a/app/src/molecules/WizardHeader/__tests__/WizardHeader.test.tsx +++ b/app/src/molecules/WizardHeader/__tests__/WizardHeader.test.tsx @@ -1,15 +1,15 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { fireEvent, screen } from '@testing-library/react' -import { i18n } from '../../../i18n' -import { getIsOnDevice } from '../../../redux/config' -import { StepMeter } from '../../../atoms/StepMeter' +import { i18n } from '/app/i18n' +import { getIsOnDevice } from '/app/redux/config' +import { StepMeter } from '/app/atoms/StepMeter' import { WizardHeader } from '..' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -vi.mock('../../../atoms/StepMeter') -vi.mock('../../../redux/config') +vi.mock('/app/atoms/StepMeter') +vi.mock('/app/redux/config') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/molecules/WizardHeader/index.tsx b/app/src/molecules/WizardHeader/index.tsx index 04dc2baeae0..1b0d23baa39 100644 --- a/app/src/molecules/WizardHeader/index.tsx +++ b/app/src/molecules/WizardHeader/index.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' import { @@ -15,7 +14,7 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { StepMeter } from '../../atoms/StepMeter' +import { StepMeter } from '/app/atoms/StepMeter' interface WizardHeaderProps { title: string diff --git a/app/src/molecules/WizardRequiredEquipmentList/equipmentImages.ts b/app/src/molecules/WizardRequiredEquipmentList/equipmentImages.ts index 25f1d4a4df6..ac4609bdc45 100644 --- a/app/src/molecules/WizardRequiredEquipmentList/equipmentImages.ts +++ b/app/src/molecules/WizardRequiredEquipmentList/equipmentImages.ts @@ -1,16 +1,16 @@ // images by equipment load name -import calibration_pin from '../../assets/images/gripper_cal_pin.png' -import calibration_probe from '../../assets/images/change-pip/calibration_probe.png' -import calibration_adapter_heatershaker from '../../assets/images/heatershaker_calibration_adapter.png' -import calibration_adapter_temperature from '../../assets/images/temperature_module_calibration_adapter.png' -import calibration_adapter_thermocycler from '../../assets/images/thermocycler_calibration_adapter.png' -import t10_torx_screwdriver from '../../assets/images/t10_torx_screwdriver.png' -import hex_screwdriver from '../../assets/images/change-pip/hex_screwdriver.png' -import flex_pipette from '../../assets/images/change-pip/single_mount_pipettes.png' -import pipette_96 from '../../assets/images/change-pip/ninety-six-channel.png' -import mounting_plate_96_channel from '../../assets/images/change-pip/mounting-plate-96-channel.png' -import flex_gripper from '../../assets/images/flex_gripper.png' +import calibration_pin from '/app/assets/images/gripper_cal_pin.png' +import calibration_probe from '/app/assets/images/change-pip/calibration_probe.png' +import calibration_adapter_heatershaker from '/app/assets/images/heatershaker_calibration_adapter.png' +import calibration_adapter_temperature from '/app/assets/images/temperature_module_calibration_adapter.png' +import calibration_adapter_thermocycler from '/app/assets/images/thermocycler_calibration_adapter.png' +import t10_torx_screwdriver from '/app/assets/images/t10_torx_screwdriver.png' +import hex_screwdriver from '/app/assets/images/change-pip/hex_screwdriver.png' +import flex_pipette from '/app/assets/images/change-pip/single_mount_pipettes.png' +import pipette_96 from '/app/assets/images/change-pip/ninety-six-channel.png' +import mounting_plate_96_channel from '/app/assets/images/change-pip/mounting-plate-96-channel.png' +import flex_gripper from '/app/assets/images/flex_gripper.png' export const equipmentImages = { calibration_pin, diff --git a/app/src/molecules/WizardRequiredEquipmentList/index.tsx b/app/src/molecules/WizardRequiredEquipmentList/index.tsx index 9701110cbdd..f6f7457a71a 100644 --- a/app/src/molecules/WizardRequiredEquipmentList/index.tsx +++ b/app/src/molecules/WizardRequiredEquipmentList/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' @@ -18,9 +18,9 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { getIsOnDevice } from '../../redux/config' -import { Divider } from '../../atoms/structure' -import { labwareImages } from '../../organisms/CalibrationPanels/labwareImages' +import { getIsOnDevice } from '/app/redux/config' +import { Divider } from '/app/atoms/structure' +import { labwareImages } from '/app/local-resources/labware' import { equipmentImages } from './equipmentImages' import type { StyleProps } from '@opentrons/components' @@ -155,7 +155,7 @@ function RequiredEquipmentCard(props: RequiredEquipmentCardProps): JSX.Element { ) : null} diff --git a/app/src/molecules/modals/BottomButtonBar.tsx b/app/src/molecules/modals/BottomButtonBar.tsx index 5abb73828aa..d5c202e943c 100644 --- a/app/src/molecules/modals/BottomButtonBar.tsx +++ b/app/src/molecules/modals/BottomButtonBar.tsx @@ -1,6 +1,6 @@ // bottom button bar for modals // TODO(mc, 2018-08-18): maybe make this the default AlertModal behavior -import * as React from 'react' +import type * as React from 'react' import cx from 'classnames' import { OutlineButton } from '@opentrons/components' diff --git a/app/src/molecules/modals/ErrorModal.tsx b/app/src/molecules/modals/ErrorModal.tsx index f4a273a5bdb..3e740a73d2c 100644 --- a/app/src/molecules/modals/ErrorModal.tsx +++ b/app/src/molecules/modals/ErrorModal.tsx @@ -1,8 +1,7 @@ -import * as React from 'react' import { createPortal } from 'react-dom' import { Link } from 'react-router-dom' import { AlertModal } from '@opentrons/components' -import { getModalPortalEl } from '../../App/portal' +import { getModalPortalEl } from '/app/App/portal' import styles from './styles.module.css' import type { ButtonProps } from '@opentrons/components' diff --git a/app/src/molecules/modals/ScrollableAlertModal.tsx b/app/src/molecules/modals/ScrollableAlertModal.tsx index 32aae3def4f..c98846899b8 100644 --- a/app/src/molecules/modals/ScrollableAlertModal.tsx +++ b/app/src/molecules/modals/ScrollableAlertModal.tsx @@ -1,5 +1,5 @@ // AlertModal with vertical scrolling -import * as React from 'react' +import type * as React from 'react' import omit from 'lodash/omit' import { AlertModal } from '@opentrons/components' diff --git a/app/src/organisms/AddCustomLabwareSlideout/index.tsx b/app/src/organisms/AddCustomLabwareSlideout/index.tsx deleted file mode 100644 index 2e663d6cd97..00000000000 --- a/app/src/organisms/AddCustomLabwareSlideout/index.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import * as React from 'react' -import { useDispatch } from 'react-redux' -import { useTranslation, Trans } from 'react-i18next' -import { - ALIGN_CENTER, - COLORS, - DIRECTION_COLUMN, - Flex, - Link, - SPACING, - LegacyStyledText, -} from '@opentrons/components' -import { - addCustomLabwareFile, - addCustomLabware, -} from '../../redux/custom-labware' -import { Slideout } from '../../atoms/Slideout' -import { - useTrackEvent, - ANALYTICS_ADD_CUSTOM_LABWARE, -} from '../../redux/analytics' -import { UploadInput } from '../../molecules/UploadInput' -import type { Dispatch } from '../../redux/types' - -export interface AddCustomLabwareSlideoutProps { - isExpanded: boolean - onCloseClick: () => void -} - -export function AddCustomLabwareSlideout( - props: AddCustomLabwareSlideoutProps -): JSX.Element { - const { t } = useTranslation(['labware_landing', 'shared']) - const dispatch = useDispatch() - const trackEvent = useTrackEvent() - - return ( - - - { - dispatch(addCustomLabwareFile(file.path)) - }} - onClick={() => { - dispatch(addCustomLabware()) - trackEvent({ - name: ANALYTICS_ADD_CUSTOM_LABWARE, - properties: {}, - }) - }} - uploadText={t('choose_file_to_upload')} - dragAndDropText={ - - dispatch(addCustomLabware())} - role="button" - /> - ), - }} - /> - - } - /> - - - ) -} diff --git a/app/src/organisms/Alerts/AlertsProvider.tsx b/app/src/organisms/Alerts/AlertsProvider.tsx deleted file mode 100644 index 58215bc1e73..00000000000 --- a/app/src/organisms/Alerts/AlertsProvider.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import * as React from 'react' -import { AlertsModal } from '.' -import { useToaster } from '../ToasterOven' - -export interface AlertsContextProps { - removeActiveAppUpdateToast: () => void -} - -export const AlertsContext = React.createContext({ - removeActiveAppUpdateToast: () => null, -}) - -interface AlertsProps { - children: React.ReactNode -} - -export function Alerts({ children }: AlertsProps): JSX.Element { - const toastRef = React.useRef(null) - const { eatToast } = useToaster() - - const removeActiveAppUpdateToast = (): void => { - if (toastRef.current) { - eatToast(toastRef.current) - toastRef.current = null - } - } - - return ( - - - {children} - - ) -} diff --git a/app/src/organisms/Alerts/useRemoveActiveAppUpdateToast.ts.ts b/app/src/organisms/Alerts/useRemoveActiveAppUpdateToast.ts.ts deleted file mode 100644 index 030639ededa..00000000000 --- a/app/src/organisms/Alerts/useRemoveActiveAppUpdateToast.ts.ts +++ /dev/null @@ -1,7 +0,0 @@ -import * as React from 'react' -import { AlertsContext } from '.' -import type { AlertsContextProps } from '.' - -export function useRemoveActiveAppUpdateToast(): AlertsContextProps { - return React.useContext(AlertsContext) -} diff --git a/app/src/organisms/ApplyHistoricOffsets/LabwareOffsetTable.tsx b/app/src/organisms/ApplyHistoricOffsets/LabwareOffsetTable.tsx index b14075a904a..3a7276a2ed8 100644 --- a/app/src/organisms/ApplyHistoricOffsets/LabwareOffsetTable.tsx +++ b/app/src/organisms/ApplyHistoricOffsets/LabwareOffsetTable.tsx @@ -1,10 +1,9 @@ -import * as React from 'react' import styled from 'styled-components' import { useTranslation } from 'react-i18next' import { SPACING, TYPOGRAPHY, COLORS } from '@opentrons/components' -import { OffsetVector } from '../../molecules/OffsetVector' -import { formatTimestamp } from '../Devices/utils' -import { getDisplayLocation } from '../LabwarePositionCheck/utils/getDisplayLocation' +import { OffsetVector } from '/app/molecules/OffsetVector' +import { formatTimestamp } from '/app/transformations/runs' +import { getDisplayLocation } from '/app/organisms/LabwarePositionCheck/utils/getDisplayLocation' import type { LabwareDefinition2 } from '@opentrons/shared-data' import type { OffsetCandidate } from './hooks/useOffsetCandidatesForAnalysis' import type { TFunction } from 'i18next' diff --git a/app/src/organisms/ApplyHistoricOffsets/__tests__/ApplyHistoricOffsets.test.tsx b/app/src/organisms/ApplyHistoricOffsets/__tests__/ApplyHistoricOffsets.test.tsx index f96a6f8df84..74f77291834 100644 --- a/app/src/organisms/ApplyHistoricOffsets/__tests__/ApplyHistoricOffsets.test.tsx +++ b/app/src/organisms/ApplyHistoricOffsets/__tests__/ApplyHistoricOffsets.test.tsx @@ -1,21 +1,21 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi } from 'vitest' import { opentrons96PcrAdapterV1, fixture96Plate } from '@opentrons/shared-data' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' -import { getIsLabwareOffsetCodeSnippetsOn } from '../../../redux/config' -import { getLabwareDefinitionsFromCommands } from '../../../molecules/Command/utils/getLabwareDefinitionsFromCommands' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { getIsLabwareOffsetCodeSnippetsOn } from '/app/redux/config' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' import { ApplyHistoricOffsets } from '..' import type { LabwareDefinition2 } from '@opentrons/shared-data' import type { OffsetCandidate } from '../hooks/useOffsetCandidatesForAnalysis' -vi.mock('../../../redux/config') -vi.mock('../../LabwarePositionCheck/utils/labware') -vi.mock('../../../molecules/Command/utils/getLabwareDefinitionsFromCommands') +vi.mock('/app/redux/config') +vi.mock('/app/organisms/LabwarePositionCheck/utils/labware') +vi.mock('/app/local-resources/labware') const mockLabwareDef = fixture96Plate as LabwareDefinition2 const mockAdapterDef = opentrons96PcrAdapterV1 as LabwareDefinition2 diff --git a/app/src/organisms/ApplyHistoricOffsets/__tests__/LabwareOffsetTable.test.tsx b/app/src/organisms/ApplyHistoricOffsets/__tests__/LabwareOffsetTable.test.tsx index 89cd694ad30..8b527e239f1 100644 --- a/app/src/organisms/ApplyHistoricOffsets/__tests__/LabwareOffsetTable.test.tsx +++ b/app/src/organisms/ApplyHistoricOffsets/__tests__/LabwareOffsetTable.test.tsx @@ -1,9 +1,8 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, expect } from 'vitest' import { fixture96Plate, fixtureTiprackAdapter } from '@opentrons/shared-data' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { LabwareOffsetTable } from '../LabwareOffsetTable' import type { LabwareDefinition2 } from '@opentrons/shared-data' import type { OffsetCandidate } from '../hooks/useOffsetCandidatesForAnalysis' diff --git a/app/src/organisms/ApplyHistoricOffsets/hooks/__tests__/useHistoricRunDetails.test.tsx b/app/src/organisms/ApplyHistoricOffsets/hooks/__tests__/useHistoricRunDetails.test.tsx index f7fb89db7e6..a697fabb2d3 100644 --- a/app/src/organisms/ApplyHistoricOffsets/hooks/__tests__/useHistoricRunDetails.test.tsx +++ b/app/src/organisms/ApplyHistoricOffsets/hooks/__tests__/useHistoricRunDetails.test.tsx @@ -1,16 +1,16 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, vi } from 'vitest' import { when } from 'vitest-when' import { renderHook, waitFor } from '@testing-library/react' -import { useNotifyAllRunsQuery } from '../../../../resources/runs/useNotifyAllRunsQuery' +import { useNotifyAllRunsQuery } from '/app/resources/runs/useNotifyAllRunsQuery' import { useHistoricRunDetails } from '../useHistoricRunDetails' -import { mockRunningRun } from '../../../RunTimeControl/__fixtures__' +import { mockRunningRun } from '/app/resources/runs/__fixtures__' import { mockSuccessQueryResults } from '../../../../__fixtures__' import type { RunData } from '@opentrons/api-client' -vi.mock('../../../../resources/runs/useNotifyAllRunsQuery') +vi.mock('/app/resources/runs/useNotifyAllRunsQuery') const MOCK_RUN_LATER: RunData = { ...mockRunningRun, diff --git a/app/src/organisms/ApplyHistoricOffsets/hooks/__tests__/useOffsetCandidatesForAnalysis.test.tsx b/app/src/organisms/ApplyHistoricOffsets/hooks/__tests__/useOffsetCandidatesForAnalysis.test.tsx index 4ad6738c944..832417cb9af 100644 --- a/app/src/organisms/ApplyHistoricOffsets/hooks/__tests__/useOffsetCandidatesForAnalysis.test.tsx +++ b/app/src/organisms/ApplyHistoricOffsets/hooks/__tests__/useOffsetCandidatesForAnalysis.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import { when } from 'vitest-when' import { renderHook, waitFor } from '@testing-library/react' @@ -11,7 +11,7 @@ import { useAllHistoricOffsets } from '../useAllHistoricOffsets' import { getLabwareLocationCombos } from '../getLabwareLocationCombos' import { useOffsetCandidatesForAnalysis } from '../useOffsetCandidatesForAnalysis' -import { storedProtocolData as storedProtocolDataFixture } from '../../../../redux/protocol-storage/__fixtures__' +import { storedProtocolData as storedProtocolDataFixture } from '/app/redux/protocol-storage/__fixtures__' import type { LabwareDefinition2 } from '@opentrons/shared-data' import type { OffsetCandidate } from '../useOffsetCandidatesForAnalysis' @@ -19,8 +19,8 @@ import type { OffsetCandidate } from '../useOffsetCandidatesForAnalysis' vi.mock('../useAllHistoricOffsets') vi.mock('../getLabwareLocationCombos') vi.mock('@opentrons/shared-data') -vi.mock('../../../../resources/runs') -vi.mock('../../../../resources/useNotifyDataReady') +vi.mock('/app/resources/runs') +vi.mock('/app/resources/useNotifyDataReady') const mockLabwareDef = fixtureTiprack300ul as LabwareDefinition2 diff --git a/app/src/organisms/ApplyHistoricOffsets/hooks/useHistoricRunDetails.ts b/app/src/organisms/ApplyHistoricOffsets/hooks/useHistoricRunDetails.ts index c86e5b3df92..39f6d5e59b8 100644 --- a/app/src/organisms/ApplyHistoricOffsets/hooks/useHistoricRunDetails.ts +++ b/app/src/organisms/ApplyHistoricOffsets/hooks/useHistoricRunDetails.ts @@ -1,4 +1,4 @@ -import { useNotifyAllRunsQuery } from '../../../resources/runs/useNotifyAllRunsQuery' +import { useNotifyAllRunsQuery } from '/app/resources/runs/useNotifyAllRunsQuery' import type { HostConfig, RunData } from '@opentrons/api-client' @@ -6,11 +6,13 @@ export function useHistoricRunDetails( hostOverride?: HostConfig | null ): RunData[] { const { data: allHistoricRuns } = useNotifyAllRunsQuery({}, {}, hostOverride) - return allHistoricRuns == null ? [] - : allHistoricRuns.data.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() - ) + : // TODO(sf): figure out why .toSorted() doesn't work in vitest + allHistoricRuns.data + .map(t => t) + .sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() + ) } diff --git a/app/src/organisms/ApplyHistoricOffsets/index.tsx b/app/src/organisms/ApplyHistoricOffsets/index.tsx index 846ee9af827..60b166fe8d8 100644 --- a/app/src/organisms/ApplyHistoricOffsets/index.tsx +++ b/app/src/organisms/ApplyHistoricOffsets/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useSelector } from 'react-redux' import pick from 'lodash/pick' @@ -18,13 +18,15 @@ import { ModalHeader, ModalShell, } from '@opentrons/components' -import { getTopPortalEl } from '../../App/portal' -import { ExternalLink } from '../../atoms/Link/ExternalLink' -import { PythonLabwareOffsetSnippet } from '../../molecules/PythonLabwareOffsetSnippet' -import { LabwareOffsetTabs } from '../LabwareOffsetTabs' -import { getLabwareDefinitionsFromCommands } from '../../molecules/Command/utils/getLabwareDefinitionsFromCommands' +import { getTopPortalEl } from '/app/App/portal' +import { ExternalLink } from '/app/atoms/Link/ExternalLink' +import { PythonLabwareOffsetSnippet } from '/app/molecules/PythonLabwareOffsetSnippet' +import { LabwareOffsetTabs } from '/app/organisms/LabwareOffsetTabs' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' import { LabwareOffsetTable } from './LabwareOffsetTable' -import { getIsLabwareOffsetCodeSnippetsOn } from '../../redux/config' +import { getIsLabwareOffsetCodeSnippetsOn } from '/app/redux/config' + +import type { ChangeEvent } from 'react' import type { LabwareOffset } from '@opentrons/api-client' import type { LoadedLabware, @@ -58,7 +60,7 @@ export function ApplyHistoricOffsets( modules, commands, } = props - const [showOffsetDataModal, setShowOffsetDataModal] = React.useState(false) + const [showOffsetDataModal, setShowOffsetDataModal] = useState(false) const { t } = useTranslation('labware_position_check') const isLabwareOffsetCodeSnippetsOn = useSelector( getIsLabwareOffsetCodeSnippetsOn @@ -85,7 +87,7 @@ export function ApplyHistoricOffsets( return ( ) => { + onChange={(e: ChangeEvent) => { setShouldApplyOffsets(e.currentTarget.checked) }} value={shouldApplyOffsets} diff --git a/app/src/organisms/Breadcrumbs/index.tsx b/app/src/organisms/Breadcrumbs/index.tsx deleted file mode 100644 index e760cf8beb4..00000000000 --- a/app/src/organisms/Breadcrumbs/index.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { Link, useParams, useLocation } from 'react-router-dom' -import styled from 'styled-components' - -import { - Box, - Flex, - Icon, - ALIGN_CENTER, - ALIGN_FLEX_START, - BORDERS, - COLORS, - DIRECTION_ROW, - SPACING, - TYPOGRAPHY, -} from '@opentrons/components' -import { ApiHostProvider } from '@opentrons/react-api-client' - -import { - useRobot, - useRunCreatedAtTimestamp, -} from '../../organisms/Devices/hooks' -import { getProtocolDisplayName } from '../../organisms/ProtocolsLanding/utils' -import { getIsOnDevice } from '../../redux/config' -import { OPENTRONS_USB } from '../../redux/discovery' -import { getStoredProtocol } from '../../redux/protocol-storage' -import { appShellRequestor } from '../../redux/shell/remote' - -import type { DesktopRouteParams } from '../../App/types' -import type { State } from '../../redux/types' - -interface CrumbNameProps { - crumbName: string - isLastCrumb: boolean -} - -function CrumbName({ crumbName, isLastCrumb }: CrumbNameProps): JSX.Element { - return ( - - - {crumbName} - - {!isLastCrumb ? ( - - ) : null} - - ) -} - -const CrumbLink = styled(Link)` - &:hover { - opacity: 0.8; - } -` - -const CrumbLinkInactive = styled(Flex)` - &:hover { - opacity: 1; - } -` - -function BreadcrumbsComponent(): JSX.Element | null { - const { t } = useTranslation('top_navigation') - const isOnDevice = useSelector(getIsOnDevice) - const { protocolKey, robotName, runId } = useParams< - keyof DesktopRouteParams - >() as DesktopRouteParams - const runCreatedAtTimestamp = useRunCreatedAtTimestamp(runId) - - const storedProtocol = useSelector((state: State) => - getStoredProtocol(state, protocolKey) - ) - const protocolDisplayName = - storedProtocol != null - ? getProtocolDisplayName( - storedProtocol.protocolKey, - storedProtocol.srcFileNames, - storedProtocol.mostRecentAnalysis - ) - : protocolKey - - // determines whether a crumb is displayed for a path, and the displayed name - const crumbNameByPath: { [index: string]: string | null } = { - '/devices': !(isOnDevice ?? false) ? t('devices') : null, - [`/devices/${robotName}`]: robotName, - [`/devices/${robotName}/robot-settings`]: t('robot_settings'), - [`/devices/${robotName}/protocol-runs/${runId}`]: runCreatedAtTimestamp, - - '/protocols': t('protocols'), - [`/protocols/${protocolKey}`]: protocolDisplayName, - } - - // create an array of crumbs based on the pathname and defined names by path - const { pathname } = useLocation() - const pathArray = pathname.split('/') - - const pathCrumbs = pathArray.flatMap((_, i) => { - const linkPath = pathArray.slice(0, i + 1).join('/') - const crumbName = crumbNameByPath[linkPath] - - // filter out null or undefined crumb names - return crumbName != null - ? [ - { - linkPath, - crumbName, - }, - ] - : [] - }) - - return pathCrumbs.length > 1 ? ( - - {pathCrumbs.map((crumb, i) => { - const isLastCrumb = i === pathCrumbs.length - 1 - - return ( - - - - - - ) - })} - - ) : null -} - -export function Breadcrumbs(): JSX.Element | null { - const { robotName } = useParams< - keyof DesktopRouteParams - >() as DesktopRouteParams - const robot = useRobot(robotName) - - return ( - - - - ) -} diff --git a/app/src/organisms/CalibrateDeck/index.tsx b/app/src/organisms/CalibrateDeck/index.tsx deleted file mode 100644 index 56dcb31dcf2..00000000000 --- a/app/src/organisms/CalibrateDeck/index.tsx +++ /dev/null @@ -1,195 +0,0 @@ -// Deck Calibration Orchestration Component -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' -import { useQueryClient } from 'react-query' - -import { useHost } from '@opentrons/react-api-client' -import { getPipetteModelSpecs } from '@opentrons/shared-data' -import { useConditionalConfirm, ModalShell } from '@opentrons/components' - -import * as Sessions from '../../redux/sessions' -import { - Introduction, - DeckSetup, - TipPickUp, - TipConfirmation, - SaveZPoint, - SaveXYPoint, - ConfirmExit, - LoadingState, - CompleteConfirmation, -} from '../../organisms/CalibrationPanels' -import { WizardHeader } from '../../molecules/WizardHeader' -import { getTopPortalEl } from '../../App/portal' - -import type { Mount } from '@opentrons/components' -import type { - CalibrationLabware, - CalibrationSessionStep, - SessionCommandParams, -} from '../../redux/sessions/types' -import type { CalibrationPanelProps } from '../../organisms/CalibrationPanels/types' -import type { CalibrateDeckParentProps } from './types' - -const PANEL_BY_STEP: Partial< - Record> -> = { - [Sessions.DECK_STEP_SESSION_STARTED]: Introduction, - [Sessions.DECK_STEP_LABWARE_LOADED]: DeckSetup, - [Sessions.DECK_STEP_PREPARING_PIPETTE]: TipPickUp, - [Sessions.DECK_STEP_INSPECTING_TIP]: TipConfirmation, - [Sessions.DECK_STEP_JOGGING_TO_DECK]: SaveZPoint, - [Sessions.DECK_STEP_SAVING_POINT_ONE]: SaveXYPoint, - [Sessions.DECK_STEP_SAVING_POINT_TWO]: SaveXYPoint, - [Sessions.DECK_STEP_SAVING_POINT_THREE]: SaveXYPoint, - [Sessions.DECK_STEP_CALIBRATION_COMPLETE]: DeckCalibrationComplete, -} -const STEPS_IN_ORDER: CalibrationSessionStep[] = [ - Sessions.DECK_STEP_SESSION_STARTED, - Sessions.DECK_STEP_LABWARE_LOADED, - Sessions.DECK_STEP_PREPARING_PIPETTE, - Sessions.DECK_STEP_INSPECTING_TIP, - Sessions.DECK_STEP_JOGGING_TO_DECK, - Sessions.DECK_STEP_SAVING_POINT_ONE, - Sessions.DECK_STEP_SAVING_POINT_TWO, - Sessions.DECK_STEP_SAVING_POINT_THREE, - Sessions.DECK_STEP_CALIBRATION_COMPLETE, -] - -export function CalibrateDeck( - props: CalibrateDeckParentProps -): JSX.Element | null { - const { t } = useTranslation('robot_calibration') - const { - session, - robotName, - dispatchRequests, - showSpinner, - isJogging, - exitBeforeDeckConfigCompletion, - offsetInvalidationHandler, - } = props - const { currentStep, instrument, labware, supportedCommands } = - session?.details || {} - - const queryClient = useQueryClient() - const host = useHost() - - const { - showConfirmation: showConfirmExit, - confirm: confirmExit, - cancel: cancelExit, - } = useConditionalConfirm(() => { - cleanUpAndExit() - }, true) - - const isMulti = React.useMemo(() => { - const spec = instrument && getPipetteModelSpecs(instrument.model) - return spec ? spec.channels > 1 : false - }, [instrument]) - - function sendCommands(...commands: SessionCommandParams[]): void { - if (session?.id && !isJogging) { - const sessionCommandActions = commands.map(c => - Sessions.createSessionCommand(robotName, session.id, { - command: c.command, - data: c.data || {}, - }) - ) - dispatchRequests(...sessionCommandActions) - } - } - - function cleanUpAndExit(): void { - queryClient.invalidateQueries([host, 'calibration']).catch((e: Error) => { - console.error(`error invalidating calibration queries: ${e.message}`) - }) - if ( - exitBeforeDeckConfigCompletion && - currentStep !== Sessions.DECK_STEP_CALIBRATION_COMPLETE - ) { - exitBeforeDeckConfigCompletion.current = true - } - if (session?.id) { - dispatchRequests( - Sessions.createSessionCommand(robotName, session.id, { - command: Sessions.sharedCalCommands.EXIT, - data: {}, - }), - Sessions.deleteSession(robotName, session.id) - ) - } - } - - const tipRack: CalibrationLabware | null = - (labware && labware.find(l => l.isTiprack)) ?? null - - if (!session || !tipRack) { - return null - } - - const Panel = - currentStep != null && currentStep in PANEL_BY_STEP - ? PANEL_BY_STEP[currentStep] - : null - return createPortal( - step === currentStep) ?? 0 - } - totalSteps={STEPS_IN_ORDER.length - 1} - onExit={confirmExit} - /> - } - > - {showSpinner || currentStep == null || Panel == null ? ( - - ) : showConfirmExit ? ( - - ) : ( - - )} - , - getTopPortalEl() - ) -} - -function DeckCalibrationComplete(props: CalibrationPanelProps): JSX.Element { - const { t } = useTranslation('robot_calibration') - const { cleanUpAndExit } = props - - return ( - - ) -} diff --git a/app/src/organisms/CalibrateDeck/types.ts b/app/src/organisms/CalibrateDeck/types.ts deleted file mode 100644 index 52467a1726d..00000000000 --- a/app/src/organisms/CalibrateDeck/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { DispatchRequestsType } from '../../redux/robot-api' -import type { MutableRefObject } from 'react' -import type { DeckCalibrationSession } from '../../redux/sessions/types' -export interface CalibrateDeckParentProps { - robotName: string - session: DeckCalibrationSession | null - dispatchRequests: DispatchRequestsType - showSpinner: boolean - isJogging: boolean - exitBeforeDeckConfigCompletion?: MutableRefObject - offsetInvalidationHandler?: () => void -} diff --git a/app/src/organisms/CalibratePipetteOffset/index.tsx b/app/src/organisms/CalibratePipetteOffset/index.tsx deleted file mode 100644 index a945a856ef7..00000000000 --- a/app/src/organisms/CalibratePipetteOffset/index.tsx +++ /dev/null @@ -1,183 +0,0 @@ -// Pipette Offset Calibration Orchestration Component -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' -import { useQueryClient } from 'react-query' - -import { useHost } from '@opentrons/react-api-client' -import { getPipetteModelSpecs } from '@opentrons/shared-data' -import { useConditionalConfirm, ModalShell } from '@opentrons/components' - -import * as Sessions from '../../redux/sessions' -import { - Introduction, - DeckSetup, - TipPickUp, - TipConfirmation, - SaveZPoint, - SaveXYPoint, - ConfirmExit, - LoadingState, - CompleteConfirmation, -} from '../../organisms/CalibrationPanels' -import { WizardHeader } from '../../molecules/WizardHeader' -import { getTopPortalEl } from '../../App/portal' - -import type { Mount } from '@opentrons/components' -import type { - CalibrationLabware, - CalibrationSessionStep, - SessionCommandParams, -} from '../../redux/sessions/types' -import type { CalibratePipetteOffsetParentProps } from './types' -import type { CalibrationPanelProps } from '../../organisms/CalibrationPanels/types' - -const PANEL_BY_STEP: Partial< - Record> -> = { - [Sessions.PIP_OFFSET_STEP_SESSION_STARTED]: Introduction, - [Sessions.PIP_OFFSET_STEP_LABWARE_LOADED]: DeckSetup, - [Sessions.PIP_OFFSET_STEP_PREPARING_PIPETTE]: TipPickUp, - [Sessions.PIP_OFFSET_STEP_INSPECTING_TIP]: TipConfirmation, - [Sessions.PIP_OFFSET_STEP_JOGGING_TO_DECK]: SaveZPoint, - [Sessions.PIP_OFFSET_STEP_SAVING_POINT_ONE]: SaveXYPoint, - [Sessions.PIP_OFFSET_STEP_TIP_LENGTH_COMPLETE]: PipetteOffsetCalibrationComplete, - [Sessions.PIP_OFFSET_STEP_CALIBRATION_COMPLETE]: PipetteOffsetCalibrationComplete, -} -const STEPS_IN_ORDER: CalibrationSessionStep[] = [ - Sessions.PIP_OFFSET_STEP_SESSION_STARTED, - Sessions.PIP_OFFSET_STEP_LABWARE_LOADED, - Sessions.PIP_OFFSET_STEP_PREPARING_PIPETTE, - Sessions.PIP_OFFSET_STEP_INSPECTING_TIP, - Sessions.PIP_OFFSET_STEP_JOGGING_TO_DECK, - Sessions.PIP_OFFSET_STEP_SAVING_POINT_ONE, - Sessions.PIP_OFFSET_STEP_CALIBRATION_COMPLETE, -] - -export function CalibratePipetteOffset( - props: CalibratePipetteOffsetParentProps -): JSX.Element | null { - const { t } = useTranslation('robot_calibration') - const { session, robotName, dispatchRequests, showSpinner, isJogging } = props - const { currentStep, instrument, labware, supportedCommands } = - session?.details ?? {} - - const queryClient = useQueryClient() - const host = useHost() - - const { - showConfirmation: showConfirmExit, - confirm: confirmExit, - cancel: cancelExit, - } = useConditionalConfirm(() => { - cleanUpAndExit() - }, true) - - const tipRack: CalibrationLabware | null = - labware != null ? labware.find(l => l.isTiprack) ?? null : null - const calBlock: CalibrationLabware | null = - labware != null ? labware.find(l => !l.isTiprack) ?? null : null - - const isMulti = React.useMemo(() => { - const spec = - instrument != null ? getPipetteModelSpecs(instrument.model) : null - return spec != null ? spec.channels > 1 : false - }, [instrument]) - - function sendCommands(...commands: SessionCommandParams[]): void { - if (session?.id != null && !isJogging) { - const sessionCommandActions = commands.map(c => - Sessions.createSessionCommand(robotName, session.id, { - command: c.command, - data: c.data ?? {}, - }) - ) - dispatchRequests(...sessionCommandActions) - } - } - - function cleanUpAndExit(): void { - queryClient.invalidateQueries([host, 'calibration']).catch((e: Error) => { - console.error(`error invalidating calibration queries: ${e.message}`) - }) - if (session?.id != null) { - dispatchRequests( - Sessions.createSessionCommand(robotName, session.id, { - command: Sessions.sharedCalCommands.EXIT, - data: {}, - }), - Sessions.deleteSession(robotName, session.id) - ) - } - } - - if (session == null || tipRack == null) { - return null - } - - const Panel = - currentStep != null && currentStep in PANEL_BY_STEP - ? PANEL_BY_STEP[currentStep] - : null - return createPortal( - step === currentStep) ?? 0 - } - totalSteps={STEPS_IN_ORDER.length - 1} - onExit={confirmExit} - /> - } - > - {showSpinner || currentStep == null || Panel == null ? ( - - ) : showConfirmExit ? ( - - ) : ( - - )} - , - getTopPortalEl() - ) -} - -function PipetteOffsetCalibrationComplete( - props: CalibrationPanelProps -): JSX.Element { - const { t } = useTranslation('robot_calibration') - const { cleanUpAndExit } = props - - return ( - - ) -} diff --git a/app/src/organisms/CalibratePipetteOffset/types.ts b/app/src/organisms/CalibratePipetteOffset/types.ts deleted file mode 100644 index 39df36d6ca5..00000000000 --- a/app/src/organisms/CalibratePipetteOffset/types.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { - SessionCommandParams, - PipetteOffsetCalibrationSession, - CalibrationLabware, -} from '../../redux/sessions/types' - -import type { PipetteOffsetCalibrationStep } from '../../redux/sessions/pipette-offset-calibration/types' -import type { DispatchRequestsType } from '../../redux/robot-api' - -export interface CalibratePipetteOffsetParentProps { - robotName: string - session: PipetteOffsetCalibrationSession | null - dispatchRequests: DispatchRequestsType - showSpinner: boolean - isJogging: boolean -} - -export interface CalibratePipetteOffsetChildProps { - sendSessionCommands: (...params: SessionCommandParams[]) => void - deleteSession: () => void - tipRack: CalibrationLabware - isMulti: boolean - mount: string - currentStep: PipetteOffsetCalibrationStep -} diff --git a/app/src/organisms/CalibrateTipLength/index.tsx b/app/src/organisms/CalibrateTipLength/index.tsx deleted file mode 100644 index 3fc318d91a1..00000000000 --- a/app/src/organisms/CalibrateTipLength/index.tsx +++ /dev/null @@ -1,222 +0,0 @@ -// Tip Length Calibration Orchestration Component -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' -import { useQueryClient } from 'react-query' -import { css } from 'styled-components' - -import { useHost } from '@opentrons/react-api-client' -import { getPipetteModelSpecs } from '@opentrons/shared-data' -import { useConditionalConfirm, ModalShell } from '@opentrons/components' - -import * as Sessions from '../../redux/sessions' -import { - Introduction, - DeckSetup, - TipPickUp, - TipConfirmation, - MeasureNozzle, - MeasureTip, - ConfirmExit, - LoadingState, - CompleteConfirmation, -} from '../../organisms/CalibrationPanels' -import { WizardHeader } from '../../molecules/WizardHeader' -import { getTopPortalEl } from '../../App/portal' - -import slotOneRemoveBlockAsset from '../../assets/videos/tip-length-cal/Slot_1_Remove_CalBlock_(330x260)REV1.webm' -import slotThreeRemoveBlockAsset from '../../assets/videos/tip-length-cal/Slot_3_Remove_CalBlock_(330x260)REV1.webm' - -import type { Mount } from '@opentrons/components' -import type { - SessionCommandParams, - CalibrationLabware, - CalibrationSessionStep, -} from '../../redux/sessions/types' -import type { CalibrationPanelProps } from '../../organisms/CalibrationPanels/types' -import type { CalibrateTipLengthParentProps } from './types' - -export { AskForCalibrationBlockModal } from './AskForCalibrationBlockModal' -export { ConfirmRecalibrationModal } from './ConfirmRecalibrationModal' - -const PANEL_BY_STEP: Partial< - Record> -> = { - sessionStarted: Introduction, - labwareLoaded: DeckSetup, - measuringNozzleOffset: MeasureNozzle, - preparingPipette: TipPickUp, - inspectingTip: TipConfirmation, - measuringTipOffset: MeasureTip, - calibrationComplete: TipLengthCalibrationComplete, -} -const STEPS_IN_ORDER: CalibrationSessionStep[] = [ - Sessions.TIP_LENGTH_STEP_SESSION_STARTED, - Sessions.TIP_LENGTH_STEP_LABWARE_LOADED, - Sessions.TIP_LENGTH_STEP_MEASURING_NOZZLE_OFFSET, - Sessions.TIP_LENGTH_STEP_PREPARING_PIPETTE, - Sessions.TIP_LENGTH_STEP_INSPECTING_TIP, - Sessions.TIP_LENGTH_STEP_MEASURING_TIP_OFFSET, - Sessions.TIP_LENGTH_STEP_CALIBRATION_COMPLETE, -] - -export function CalibrateTipLength( - props: CalibrateTipLengthParentProps -): JSX.Element | null { - const { t } = useTranslation('robot_calibration') - const { - session, - robotName, - showSpinner, - dispatchRequests, - isJogging, - offsetInvalidationHandler, - allowChangeTipRack = false, - } = props - const { currentStep, instrument, labware, supportedCommands } = - session?.details ?? {} - - const queryClient = useQueryClient() - const host = useHost() - - const isMulti = React.useMemo(() => { - const spec = - instrument != null ? getPipetteModelSpecs(instrument.model) : null - return spec != null ? spec.channels > 1 : false - }, [instrument]) - - const tipRack: CalibrationLabware | null = - labware != null ? labware.find(l => l.isTiprack) ?? null : null - const calBlock: CalibrationLabware | null = - labware != null ? labware.find(l => !l.isTiprack) ?? null : null - - function sendCommands(...commands: SessionCommandParams[]): void { - if (session?.id != null && !isJogging) { - const sessionCommandActions = commands.map(c => - Sessions.createSessionCommand(robotName, session.id, { - command: c.command, - data: c.data ?? {}, - }) - ) - dispatchRequests(...sessionCommandActions) - } - } - - function cleanUpAndExit(): void { - queryClient.invalidateQueries([host, 'calibration']).catch((e: Error) => { - console.error(`error invalidating calibration queries: ${e.message}`) - }) - if (session?.id != null) { - dispatchRequests( - Sessions.createSessionCommand(robotName, session.id, { - command: Sessions.sharedCalCommands.EXIT, - data: {}, - }), - Sessions.deleteSession(robotName, session.id) - ) - } - } - - const { - showConfirmation: showConfirmExit, - confirm: confirmExit, - cancel: cancelExit, - } = useConditionalConfirm(() => { - cleanUpAndExit() - }, true) - - if (session == null || tipRack == null) { - return null - } - - const Panel = - currentStep != null && currentStep in PANEL_BY_STEP - ? PANEL_BY_STEP[currentStep] - : null - return createPortal( - step === currentStep) ?? 0 - } - totalSteps={STEPS_IN_ORDER.length - 1} - onExit={confirmExit} - /> - } - > - {showSpinner || currentStep == null || Panel == null ? ( - - ) : showConfirmExit ? ( - - ) : ( - - )} - , - getTopPortalEl() - ) -} - -const blockRemovalAssetBySlot: { - [slot in CalibrationLabware['slot']]: string -} = { - '1': slotOneRemoveBlockAsset, - '3': slotThreeRemoveBlockAsset, -} - -function TipLengthCalibrationComplete( - props: CalibrationPanelProps -): JSX.Element { - const { t } = useTranslation('robot_calibration') - const { calBlock, cleanUpAndExit } = props - - const visualAid = - calBlock != null ? ( - - ) : null - - return ( - - ) -} diff --git a/app/src/organisms/CalibrateTipLength/types.ts b/app/src/organisms/CalibrateTipLength/types.ts deleted file mode 100644 index 829793ed8be..00000000000 --- a/app/src/organisms/CalibrateTipLength/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { DispatchRequestsType } from '../../redux/robot-api' -import type { TipLengthCalibrationSession } from '../../redux/sessions/types' - -export interface CalibrateTipLengthParentProps { - robotName: string - session: TipLengthCalibrationSession | null - dispatchRequests: DispatchRequestsType - showSpinner: boolean - isJogging: boolean - allowChangeTipRack?: boolean - offsetInvalidationHandler?: () => void -} diff --git a/app/src/organisms/CalibrationPanels/Introduction/index.tsx b/app/src/organisms/CalibrationPanels/Introduction/index.tsx deleted file mode 100644 index c72ef897af8..00000000000 --- a/app/src/organisms/CalibrationPanels/Introduction/index.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { getLabwareDisplayName } from '@opentrons/shared-data' -import { - Flex, - DIRECTION_COLUMN, - JUSTIFY_SPACE_BETWEEN, - SPACING, - ALIGN_CENTER, - PrimaryButton, - SecondaryButton, - LegacyStyledText, -} from '@opentrons/components' - -import * as Sessions from '../../../redux/sessions' -import { NeedHelpLink } from '../NeedHelpLink' -import { ChooseTipRack } from '../ChooseTipRack' - -import { TRASH_BIN_LOAD_NAME } from '../constants' -import { WizardRequiredEquipmentList } from '../../../molecules/WizardRequiredEquipmentList' -import { Body } from './Body' -import { InvalidationWarning } from './InvalidationWarning' - -import type { LabwareDefinition2 } from '@opentrons/shared-data' -import type { CalibrationPanelProps } from '../types' - -const TRASH_BIN = 'Removable black plastic trash bin' - -export function Introduction(props: CalibrationPanelProps): JSX.Element { - const { - tipRack, - calBlock, - sendCommands, - sessionType, - instruments, - supportedCommands, - calInvalidationHandler, - allowChangeTipRack = false, - } = props - const { t } = useTranslation('robot_calibration') - - const [showChooseTipRack, setShowChooseTipRack] = React.useState(false) - const [ - chosenTipRack, - setChosenTipRack, - ] = React.useState(null) - - const handleChosenTipRack = (value: LabwareDefinition2 | null): void => { - value != null && setChosenTipRack(value) - } - const uniqueTipRacks = new Set( - instruments?.map(instr => instr.tipRackLoadName) - ) - - let equipmentList: Array<{ loadName: string; displayName: string }> = - uniqueTipRacks.size > 1 - ? instruments?.map(instr => ({ - loadName: instr.tipRackLoadName, - displayName: instr.tipRackDisplay, - })) ?? [] - : [ - { - loadName: tipRack.loadName, - displayName: getLabwareDisplayName(tipRack.definition), - }, - ] - - if (chosenTipRack != null) { - equipmentList = [ - { - loadName: chosenTipRack.parameters.loadName, - displayName: chosenTipRack.metadata.displayName, - }, - ] - } - if (calBlock != null) { - equipmentList = [ - ...equipmentList, - { - loadName: calBlock.loadName, - displayName: getLabwareDisplayName(calBlock.definition), - }, - ] - } else if ( - sessionType === Sessions.SESSION_TYPE_CALIBRATION_HEALTH_CHECK || - sessionType === Sessions.SESSION_TYPE_TIP_LENGTH_CALIBRATION - ) { - equipmentList = [ - ...equipmentList, - { - loadName: TRASH_BIN_LOAD_NAME, - displayName: TRASH_BIN, - }, - ] - } - - const proceed = (): void => { - if ( - (sessionType === Sessions.SESSION_TYPE_DECK_CALIBRATION || - sessionType === Sessions.SESSION_TYPE_TIP_LENGTH_CALIBRATION) && - calInvalidationHandler !== undefined - ) { - calInvalidationHandler() - } - if ( - supportedCommands?.includes(Sessions.sharedCalCommands.LOAD_LABWARE) ?? - false - ) { - sendCommands({ - command: Sessions.sharedCalCommands.LOAD_LABWARE, - data: { tiprackDefinition: chosenTipRack ?? tipRack.definition }, - }) - } else { - sendCommands({ command: Sessions.sharedCalCommands.LOAD_LABWARE }) - } - } - - return showChooseTipRack ? ( - { - setShowChooseTipRack(false) - }} - robotName={props.robotName} - defaultTipracks={props.defaultTipracks} - /> - ) : ( - - - - - {t('before_you_begin')} - - - {(sessionType === Sessions.SESSION_TYPE_DECK_CALIBRATION || - sessionType === Sessions.SESSION_TYPE_TIP_LENGTH_CALIBRATION) && - calInvalidationHandler !== undefined && ( - - )} - - - - - - - - - - {allowChangeTipRack ? ( - { - setShowChooseTipRack(true) - }} - > - {t('change_tip_rack')} - - ) : null} - {t('get_started')} - - - - ) -} diff --git a/app/src/organisms/CalibrationPanels/MeasureNozzle.tsx b/app/src/organisms/CalibrationPanels/MeasureNozzle.tsx deleted file mode 100644 index c0a16f60719..00000000000 --- a/app/src/organisms/CalibrationPanels/MeasureNozzle.tsx +++ /dev/null @@ -1,185 +0,0 @@ -/* eslint-disable no-return-assign */ -import * as React from 'react' -import { css } from 'styled-components' -import { useTranslation } from 'react-i18next' -import { - ALIGN_FLEX_END, - ALIGN_STRETCH, - Box, - DIRECTION_COLUMN, - Flex, - JUSTIFY_SPACE_BETWEEN, - PrimaryButton, - SPACING, - LegacyStyledText, -} from '@opentrons/components' - -import { - JogControls, - SMALL_STEP_SIZE_MM, - MEDIUM_STEP_SIZE_MM, -} from '../../molecules/JogControls' -import * as Sessions from '../../redux/sessions' -import type { Axis, Sign, StepSize } from '../../molecules/JogControls/types' -import type { CalibrationPanelProps } from './types' - -import { NeedHelpLink } from './NeedHelpLink' -import { useConfirmCrashRecovery } from './useConfirmCrashRecovery' -import { formatJogVector } from './utils' -import leftMultiBlockAssetTLC from '../../assets/videos/tip-length-cal/Left_Multi_CalBlock_NO_TIP_(330x260)REV1.webm' -import leftMultiTrashAsset from '../../assets/videos/tip-length-cal/Left_Multi_Trash_NO_TIP_(330x260)REV1.webm' -import leftSingleBlockAssetTLC from '../../assets/videos/tip-length-cal/Left_Single_CalBlock_NO_TIP_(330x260)REV1.webm' -import leftSingleTrashAsset from '../../assets/videos/tip-length-cal/Left_Single_Trash_NO_TIP_(330x260)REV1.webm' -import rightMultiBlockAssetTLC from '../../assets/videos/tip-length-cal/Right_Multi_CalBlock_NO_TIP_(330x260)REV1.webm' -import rightMultiTrashAsset from '../../assets/videos/tip-length-cal/Right_Multi_Trash_NO_TIP_(330x260)REV1.webm' -import rightSingleBlockAssetTLC from '../../assets/videos/tip-length-cal/Right_Single_CalBlock_NO_TIP_(330x260)REV1.webm' -import rightSingleTrashAsset from '../../assets/videos/tip-length-cal/Right_Single_Trash_NO_TIP_(330x260)REV1.webm' -import leftMultiBlockAssetHealth from '../../assets/videos/health-check/Left_Multi_CalBlock_NO_TIP_(330x260)REV2.webm' -import rightMultiBlockAssetHealth from '../../assets/videos/health-check/Right_Multi_CalBlock_NO_TIP_(330x260)REV2.webm' -import leftSingleBlockAssetHealth from '../../assets/videos/health-check/Left_Single_CalBlock_NO_TIP_(330x260)REV2.webm' -import rightSingleBlockAssetHealth from '../../assets/videos/health-check/Right_Single_CalBlock_NO_TIP_(330x260)REV2.webm' -import type { Mount } from '@opentrons/components' - -const assetMapTrash = { - left: { - multi: leftMultiTrashAsset, - single: leftSingleTrashAsset, - }, - right: { - multi: rightMultiTrashAsset, - single: rightSingleTrashAsset, - }, -} - -const assetMapBlock: { - [sessionType in Sessions.SessionType]?: { - [mount in Mount]: { [channels in 'multi' | 'single']: string } - } -} = { - [Sessions.SESSION_TYPE_TIP_LENGTH_CALIBRATION]: { - left: { - multi: leftMultiBlockAssetTLC, - single: leftSingleBlockAssetTLC, - }, - right: { - multi: rightMultiBlockAssetTLC, - single: rightSingleBlockAssetTLC, - }, - }, - [Sessions.SESSION_TYPE_CALIBRATION_HEALTH_CHECK]: { - left: { - multi: leftMultiBlockAssetHealth, - single: leftSingleBlockAssetHealth, - }, - right: { - multi: rightMultiBlockAssetHealth, - single: rightSingleBlockAssetHealth, - }, - }, -} - -export function MeasureNozzle(props: CalibrationPanelProps): JSX.Element { - const { t } = useTranslation('robot_calibration') - const { sendCommands, calBlock, mount, isMulti, sessionType } = props - - const demoAsset = React.useMemo( - () => - calBlock != null - ? assetMapBlock[sessionType]?.[mount]?.[isMulti ? 'multi' : 'single'] - : assetMapTrash[mount]?.[isMulti ? 'multi' : 'single'], - [mount, isMulti, calBlock, sessionType] - ) - - const jog = (axis: Axis, dir: Sign, step: StepSize): void => { - sendCommands({ - command: Sessions.sharedCalCommands.JOG, - data: { - vector: formatJogVector(axis, dir, step), - }, - }) - } - - const isHealthCheck = - sessionType === Sessions.SESSION_TYPE_CALIBRATION_HEALTH_CHECK - - const proceed = (): void => { - isHealthCheck - ? sendCommands({ command: Sessions.sharedCalCommands.MOVE_TO_TIP_RACK }) - : sendCommands( - { command: Sessions.sharedCalCommands.SAVE_OFFSET }, - { command: Sessions.sharedCalCommands.MOVE_TO_TIP_RACK } - ) - } - - const [confirmLink, crashRecoveryConfirmation] = useConfirmCrashRecovery( - props - ) - - let titleText = - calBlock != null - ? t('calibrate_z_axis_on_block') - : t('calibrate_z_axis_on_trash') - if (isHealthCheck) { - titleText = - calBlock != null ? t('check_z_axis_on_block') : t('check_z_axis_on_trash') - } - - return ( - crashRecoveryConfirmation ?? ( - - - - - {titleText} - - - {calBlock != null - ? t('jog_nozzle_to_block', { slotName: calBlock.slot }) - : t('jog_nozzle_to_trash')} - - - - - - - - - {confirmLink} - - - - - {t('confirm_placement')} - - - - ) - ) -} diff --git a/app/src/organisms/CalibrationPanels/MeasureTip.tsx b/app/src/organisms/CalibrationPanels/MeasureTip.tsx deleted file mode 100644 index ded6069f523..00000000000 --- a/app/src/organisms/CalibrationPanels/MeasureTip.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import * as React from 'react' -import { css } from 'styled-components' -import { useTranslation } from 'react-i18next' -import { - ALIGN_FLEX_END, - ALIGN_STRETCH, - Box, - DIRECTION_COLUMN, - Flex, - JUSTIFY_SPACE_BETWEEN, - PrimaryButton, - SPACING, - LegacyStyledText, -} from '@opentrons/components' - -import * as Sessions from '../../redux/sessions' -import { - JogControls, - MEDIUM_STEP_SIZE_MM, - SMALL_STEP_SIZE_MM, -} from '../../molecules/JogControls' -import { NeedHelpLink } from './NeedHelpLink' -import { useConfirmCrashRecovery } from './useConfirmCrashRecovery' -import { formatJogVector } from './utils' - -import leftMultiBlockAssetTLC from '../../assets/videos/tip-length-cal/Left_Multi_CalBlock_WITH_TIP_(330x260)REV1.webm' -import leftMultiTrashAsset from '../../assets/videos/tip-length-cal/Left_Multi_Trash_WITH_TIP_(330x260)REV1.webm' -import leftSingleBlockAssetTLC from '../../assets/videos/tip-length-cal/Left_Single_CalBlock_WITH_TIP_(330x260)REV1.webm' -import leftSingleTrashAsset from '../../assets/videos/tip-length-cal/Left_Single_Trash_WITH_TIP_(330x260)REV1.webm' -import rightMultiBlockAssetTLC from '../../assets/videos/tip-length-cal/Right_Multi_CalBlock_WITH_TIP_(330x260)REV1.webm' -import rightMultiTrashAsset from '../../assets/videos/tip-length-cal/Right_Multi_Trash_WITH_TIP_(330x260)REV1.webm' -import rightSingleBlockAssetTLC from '../../assets/videos/tip-length-cal/Right_Single_CalBlock_WITH_TIP_(330x260)REV1.webm' -import rightSingleTrashAsset from '../../assets/videos/tip-length-cal/Right_Single_Trash_WITH_TIP_(330x260)REV1.webm' -import leftMultiBlockAssetHealth from '../../assets/videos/health-check/Left_Multi_CalBlock_WITH_TIP_(330x260)REV2.webm' -import rightMultiBlockAssetHealth from '../../assets/videos/health-check/Right_Multi_CalBlock_WITH_TIP_(330x260)REV2.webm' -import leftSingleBlockAssetHealth from '../../assets/videos/health-check/Left_Single_CalBlock_WITH_TIP_(330x260)REV2.webm' -import rightSingleBlockAssetHealth from '../../assets/videos/health-check/Right_Single_CalBlock_WITH_TIP_(330x260)REV2.webm' - -import type { Mount } from '@opentrons/components' -import type { Axis, Sign, StepSize } from '../../molecules/JogControls/types' -import type { CalibrationPanelProps } from './types' - -const assetMapTrash = { - left: { - multi: leftMultiTrashAsset, - single: leftSingleTrashAsset, - }, - right: { - multi: rightMultiTrashAsset, - single: rightSingleTrashAsset, - }, -} - -const assetMapBlock: { - [sessionType in Sessions.SessionType]?: { - [mount in Mount]: { [channels in 'multi' | 'single']: string } - } -} = { - [Sessions.SESSION_TYPE_TIP_LENGTH_CALIBRATION]: { - left: { - multi: leftMultiBlockAssetTLC, - single: leftSingleBlockAssetTLC, - }, - right: { - multi: rightMultiBlockAssetTLC, - single: rightSingleBlockAssetTLC, - }, - }, - [Sessions.SESSION_TYPE_CALIBRATION_HEALTH_CHECK]: { - left: { - multi: leftMultiBlockAssetHealth, - single: leftSingleBlockAssetHealth, - }, - right: { - multi: rightMultiBlockAssetHealth, - single: rightSingleBlockAssetHealth, - }, - }, -} - -export function MeasureTip(props: CalibrationPanelProps): JSX.Element { - const { t } = useTranslation('robot_calibration') - const { sendCommands, calBlock, isMulti, mount, sessionType } = props - - const demoAsset = React.useMemo( - () => - calBlock != null - ? assetMapBlock[sessionType]?.[mount]?.[isMulti ? 'multi' : 'single'] - : assetMapTrash[mount]?.[isMulti ? 'multi' : 'single'], - [mount, isMulti, calBlock, sessionType] - ) - - const jog = (axis: Axis, dir: Sign, step: StepSize): void => { - sendCommands({ - command: Sessions.sharedCalCommands.JOG, - data: { - vector: formatJogVector(axis, dir, step), - }, - }) - } - - const isHealthCheck = - sessionType === Sessions.SESSION_TYPE_CALIBRATION_HEALTH_CHECK - - const proceed = (): void => { - isHealthCheck - ? sendCommands( - { command: Sessions.checkCommands.COMPARE_POINT }, - { command: Sessions.sharedCalCommands.MOVE_TO_DECK } - ) - : sendCommands( - { command: Sessions.sharedCalCommands.SAVE_OFFSET }, - { command: Sessions.sharedCalCommands.MOVE_TO_TIP_RACK } - ) - } - - const [confirmLink, crashRecoveryConfirmation] = useConfirmCrashRecovery( - props - ) - - let titleText = - calBlock != null ? t('calibrate_tip_on_block') : t('calibrate_tip_on_trash') - if (isHealthCheck) { - titleText = - calBlock != null ? t('check_tip_on_block') : t('check_tip_on_trash') - } - return ( - crashRecoveryConfirmation ?? ( - - - - - {titleText} - - - {calBlock != null - ? t('jog_nozzle_to_block', { slotName: calBlock.slot }) - : t('jog_nozzle_to_trash')} - - - - - - - - - {confirmLink} - - - - - {t('confirm_placement')} - - - - ) - ) -} diff --git a/app/src/organisms/CalibrationPanels/index.ts b/app/src/organisms/CalibrationPanels/index.ts deleted file mode 100644 index 820e397e9e5..00000000000 --- a/app/src/organisms/CalibrationPanels/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export { Introduction } from './Introduction' -export { DeckSetup } from './DeckSetup' -export { TipPickUp } from './TipPickUp' -export { TipConfirmation } from './TipConfirmation' -export { MeasureNozzle } from './MeasureNozzle' -export { MeasureTip } from './MeasureTip' -export { SaveZPoint } from './SaveZPoint' -export { SaveXYPoint } from './SaveXYPoint' -export { CompleteConfirmation } from './CompleteConfirmation' -export { NeedHelpLink } from './NeedHelpLink' -export { LoadingState } from './LoadingState' -export { ConfirmExit } from './ConfirmExit' -export * from './constants' diff --git a/app/src/organisms/CalibrationPanels/labwareImages.ts b/app/src/organisms/CalibrationPanels/labwareImages.ts deleted file mode 100644 index a5cf3612440..00000000000 --- a/app/src/organisms/CalibrationPanels/labwareImages.ts +++ /dev/null @@ -1,35 +0,0 @@ -// images by labware load name - -// TODO: BC 2020-04-01): this mapping should live in shared-data, -// it is now following the existing pattern in labware-library -import opentrons_96_tiprack_1000ul_side_view from '../../assets/images/labware/opentrons_96_tiprack_1000ul_side_view.jpg' -import opentrons_96_tiprack_10ul_side_view from '../../assets/images/labware/opentrons_96_tiprack_10ul_side_view.jpg' -import opentrons_96_tiprack_300ul_side_view from '../../assets/images/labware/opentrons_96_tiprack_300ul_side_view.jpg' -import geb_96_tiprack_1000ul from '../../assets/images/labware/geb_96_tiprack_1000ul_side_view.jpg' -import geb_96_tiprack_10ul from '../../assets/images/labware/geb_96_tiprack_10ul_side_view.jpg' -import tipone_96_tiprack_200ul from '../../assets/images/labware/tipone_96_tiprack_200ul_side_view.jpg' -import eppendorf_96_tiprack_1000ul_eptips from '../../assets/images/labware/eppendorf_1000ul_tip_eptips_side_view.jpg' -import eppendorf_96_tiprack_10ul_eptips from '../../assets/images/labware/eppendorf_10ul_tips_eptips_side_view.jpg' -import opentrons_calibrationblock from '../../assets/images/labware/opentrons_calibration_block.png' -import generic_custom_tiprack from '../../assets/images/labware/generic_tiprack_side_view.png' -import removable_black_plastic_trash_bin from '../../assets/images/labware/removable_black_plastic_trash_bin.png' - -export const labwareImages = { - opentrons_96_tiprack_1000ul: opentrons_96_tiprack_1000ul_side_view, - opentrons_96_filtertiprack_1000ul: opentrons_96_tiprack_1000ul_side_view, - opentrons_96_tiprack_10ul: opentrons_96_tiprack_10ul_side_view, - opentrons_96_filtertiprack_10ul: opentrons_96_tiprack_10ul_side_view, - opentrons_96_tiprack_20ul: opentrons_96_tiprack_10ul_side_view, - opentrons_96_filtertiprack_20ul: opentrons_96_tiprack_10ul_side_view, - opentrons_96_tiprack_300ul: opentrons_96_tiprack_300ul_side_view, - opentrons_96_filtertiprack_200ul: opentrons_96_tiprack_300ul_side_view, - geb_96_tiprack_1000ul, - geb_96_tiprack_10ul, - tipone_96_tiprack_200ul, - eppendorf_96_tiprack_1000ul_eptips, - eppendorf_96_tiprack_10ul_eptips, - opentrons_calibrationblock_short_side_right: opentrons_calibrationblock, - opentrons_calibrationblock_short_side_left: opentrons_calibrationblock, - generic_custom_tiprack, - removable_black_plastic_trash_bin, -} diff --git a/app/src/organisms/CalibrationPanels/types.ts b/app/src/organisms/CalibrationPanels/types.ts deleted file mode 100644 index cc8700aa8be..00000000000 --- a/app/src/organisms/CalibrationPanels/types.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { - SessionCommandParams, - SessionType, - SessionCommandString, - CalibrationSessionStep, - CalibrationLabware, - CalibrationCheckInstrument, - CalibrationCheckComparisonByPipette, -} from '../../redux/sessions/types' - -import type { LabwareDefinition2 } from '@opentrons/shared-data' -import type { Mount } from '../../redux/pipettes/types' - -// TODO (lc 10-20-2020) Given there are lots of optional -// keys here now we should split these panel props out -// into different session types and combine them into -// a union object -export interface CalibrationPanelProps { - sendCommands: (...params: SessionCommandParams[]) => void - cleanUpAndExit: () => void - tipRack: CalibrationLabware - isMulti: boolean - mount: Mount - currentStep: CalibrationSessionStep - sessionType: SessionType - calBlock?: CalibrationLabware | null - checkBothPipettes?: boolean | null - instruments?: CalibrationCheckInstrument[] | null - comparisonsByPipette?: CalibrationCheckComparisonByPipette | null - activePipette?: CalibrationCheckInstrument - robotName?: string | null - supportedCommands?: SessionCommandString[] | null - defaultTipracks?: LabwareDefinition2[] | null - calInvalidationHandler?: () => void - allowChangeTipRack?: boolean -} diff --git a/app/src/organisms/CalibrationPanels/utils.ts b/app/src/organisms/CalibrationPanels/utils.ts deleted file mode 100644 index ba44eda6055..00000000000 --- a/app/src/organisms/CalibrationPanels/utils.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { format } from 'date-fns' -import type { Axis } from '../../molecules/JogControls/types' -import type { VectorTuple } from '../../redux/sessions/types' - -const ORDERED_AXES: [Axis, Axis, Axis] = ['x', 'y', 'z'] - -// e.g. reformat from ['x', -1, 0.1] to [-0.1, 0, 0] -export function formatJogVector( - axis: string, - direction: number, - step: number -): VectorTuple { - const vector: VectorTuple = [0, 0, 0] - const index = ORDERED_AXES.findIndex(a => a === axis) - if (index >= 0) { - vector[index] = step * direction - } - return vector -} - -export function formatLastModified(lastModified: string | null): string { - return typeof lastModified === 'string' - ? format(new Date(lastModified), 'MMMM dd, yyyy HH:mm') - : 'unknown' -} diff --git a/app/src/organisms/CalibrationStatusCard/index.tsx b/app/src/organisms/CalibrationStatusCard/index.tsx deleted file mode 100644 index aa0ad28d8c5..00000000000 --- a/app/src/organisms/CalibrationStatusCard/index.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { Link as RouterLink } from 'react-router-dom' - -import { - ALIGN_CENTER, - ALIGN_FLEX_START, - BORDERS, - COLORS, - DIRECTION_COLUMN, - Flex, - JUSTIFY_CENTER, - JUSTIFY_SPACE_BETWEEN, - Link, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' - -import { TertiaryButton } from '../../atoms/buttons' -import { StatusLabel } from '../../atoms/StatusLabel' - -import { useCalibrationTaskList } from '../Devices/hooks' - -export interface CalibrationStatusCardProps { - robotName: string - setShowHowCalibrationWorksModal: ( - showHowCalibrationWorksModal: boolean - ) => void -} - -export function CalibrationStatusCard({ - robotName, - setShowHowCalibrationWorksModal, -}: CalibrationStatusCardProps): JSX.Element { - const { t } = useTranslation('robot_calibration') - const { taskListStatus } = useCalibrationTaskList() - - // start off assuming we are missing calibrations - let statusLabelBackgroundColor: string = COLORS.red30 - let statusLabelIconColor: string = COLORS.red60 - let statusLabelText = t('missing_calibration_data') - let statusLabelTextColor = COLORS.red60 - - // if the tasklist is empty, though, all calibrations are good - if (taskListStatus === 'complete') { - statusLabelBackgroundColor = COLORS.green30 - statusLabelIconColor = COLORS.green60 - statusLabelText = t('calibration_complete') - statusLabelTextColor = COLORS.green60 - // if we have tasks and they are all marked bad, then we should - // strongly suggest they re-do those calibrations - } else if (taskListStatus === 'bad') { - statusLabelBackgroundColor = COLORS.yellow30 - statusLabelIconColor = COLORS.yellow60 - statusLabelText = t('calibration_recommended') - statusLabelTextColor = COLORS.yellow60 - } - - return ( - - - - - {t('calibration_status')} - - - - - {t('calibration_status_description')} - - { - setShowHowCalibrationWorksModal(true) - }} - > - {t('see_how_robot_calibration_works')} - - - - {t('launch_calibration')} - - - ) -} diff --git a/app/src/organisms/CalibrationTaskList/index.tsx b/app/src/organisms/CalibrationTaskList/index.tsx deleted file mode 100644 index 77f0590c304..00000000000 --- a/app/src/organisms/CalibrationTaskList/index.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import * as React from 'react' -import { css } from 'styled-components' -import { useTranslation } from 'react-i18next' -import { useNavigate } from 'react-router-dom' - -import { - ALIGN_CENTER, - COLORS, - DIRECTION_COLUMN, - Flex, - Icon, - JUSTIFY_CENTER, - PrimaryButton, - SPACING, - LegacyStyledText, - TYPOGRAPHY, - Modal, -} from '@opentrons/components' - -import { StatusLabel } from '../../atoms/StatusLabel' -import { TaskList } from '../TaskList' - -import { - useAttachedPipettes, - useCalibrationTaskList, - useRunHasStarted, -} from '../Devices/hooks' -import { useCurrentRunId } from '../../resources/runs' - -import type { DashboardCalOffsetInvoker } from '../../pages/Devices/CalibrationDashboard/hooks/useDashboardCalibratePipOffset' -import type { DashboardCalTipLengthInvoker } from '../../pages/Devices/CalibrationDashboard/hooks/useDashboardCalibrateTipLength' -import type { DashboardCalDeckInvoker } from '../../pages/Devices/CalibrationDashboard/hooks/useDashboardCalibrateDeck' - -interface CalibrationTaskListProps { - robotName: string - pipOffsetCalLauncher: DashboardCalOffsetInvoker - tipLengthCalLauncher: DashboardCalTipLengthInvoker - deckCalLauncher: DashboardCalDeckInvoker - exitBeforeDeckConfigCompletion: boolean -} - -export function CalibrationTaskList({ - robotName, - pipOffsetCalLauncher, - tipLengthCalLauncher, - deckCalLauncher, - exitBeforeDeckConfigCompletion, -}: CalibrationTaskListProps): JSX.Element { - const prevActiveIndex = React.useRef<[number, number] | null>(null) - const [hasLaunchedWizard, setHasLaunchedWizard] = React.useState( - false - ) - const [ - showCompletionScreen, - setShowCompletionScreen, - ] = React.useState(false) - const { t } = useTranslation(['robot_calibration', 'device_settings']) - const navigate = useNavigate() - const { activeIndex, taskList, taskListStatus } = useCalibrationTaskList( - pipOffsetCalLauncher, - tipLengthCalLauncher, - deckCalLauncher - ) - const runId = useCurrentRunId() - - let generalTaskDisabledReason = null - - const attachedPipettes = useAttachedPipettes() - if (attachedPipettes.left == null && attachedPipettes.right == null) { - generalTaskDisabledReason = t( - 'device_settings:attach_a_pipette_before_calibrating' - ) - } - - const runHasStarted = useRunHasStarted(runId) - if (runHasStarted) - generalTaskDisabledReason = t( - 'device_settings:some_robot_controls_are_not_available' - ) - - React.useEffect(() => { - if ( - prevActiveIndex.current !== null && - activeIndex === null && - hasLaunchedWizard - ) { - setShowCompletionScreen(true) - } - prevActiveIndex.current = activeIndex - }, [activeIndex, hasLaunchedWizard]) - - // start off assuming we are missing calibrations - let statusLabelBackgroundColor: string = COLORS.red30 - let statusLabelIconColor: string = COLORS.red50 - let statusLabelText = t('missing_calibration_data') - - // if the tasklist is empty, though, all calibrations are good - if (taskListStatus === 'complete') { - statusLabelBackgroundColor = COLORS.green30 - statusLabelIconColor = COLORS.green50 - statusLabelText = t('calibration_complete') - // if we have tasks and they are all marked bad, then we should - // strongly suggest they re-do those calibrations - } else if (taskListStatus === 'bad') { - statusLabelBackgroundColor = COLORS.yellow30 - statusLabelIconColor = COLORS.yellow50 - statusLabelText = t('calibration_recommended') - } - - return ( - { - navigate(`/devices/${robotName}/robot-settings/calibration`) - }} - fullPage - backgroundColor={COLORS.grey10} - childrenPadding={`${SPACING.spacing16} ${SPACING.spacing24} ${SPACING.spacing24} ${SPACING.spacing4}`} - css={css` - width: 50rem; - height: 47.5rem; - `} - > - {showCompletionScreen ? ( - - - {exitBeforeDeckConfigCompletion ? ( - - ) : ( - - )} - - {exitBeforeDeckConfigCompletion - ? t('using_current_calibrations') - : t('calibrations_complete')} - - { - navigate(`/devices/${robotName}/robot-settings/calibration`) - }} - > - {t('device_settings:done')} - - - - ) : ( - <> - - - {t('calibration_status')} - - - - { - setHasLaunchedWizard(true) - }} - generalTaskDisabledReason={generalTaskDisabledReason} - /> - - )} - - ) -} diff --git a/app/src/organisms/ChangePipette/index.tsx b/app/src/organisms/ChangePipette/index.tsx deleted file mode 100644 index a3de4f6a8dd..00000000000 --- a/app/src/organisms/ChangePipette/index.tsx +++ /dev/null @@ -1,339 +0,0 @@ -import * as React from 'react' -import capitalize from 'lodash/capitalize' -import { useSelector, useDispatch } from 'react-redux' -import { useNavigate } from 'react-router-dom' -import { useTranslation } from 'react-i18next' -import { getPipetteNameSpecs } from '@opentrons/shared-data' -import { - SPACING, - TYPOGRAPHY, - LegacyStyledText, - ModalShell, -} from '@opentrons/components' - -import { - useDispatchApiRequests, - getRequestById, - SUCCESS, -} from '../../redux/robot-api' -import { getCalibrationForPipette } from '../../redux/calibration' -import { - home, - move, - getMovementStatus, - HOMING, - MOVING, - ROBOT, - PIPETTE, - CHANGE_PIPETTE, - HOME, -} from '../../redux/robot-controls' - -import { WizardHeader } from '../../molecules/WizardHeader' -import { InProgressModal } from '../../molecules/InProgressModal/InProgressModal' -import { useAttachedPipettes } from '../Devices/hooks' -import { ExitModal } from './ExitModal' -import { Instructions } from './Instructions' -import { ConfirmPipette } from './ConfirmPipette' -import { ClearDeckModal } from './ClearDeckModal' - -import { - ATTACH, - DETACH, - CLEAR_DECK, - INSTRUCTIONS, - CONFIRM, - SINGLE_CHANNEL_STEPS, - EIGHT_CHANNEL_STEPS, -} from './constants' - -import type { PipetteNameSpecs } from '@opentrons/shared-data' -import type { State, Dispatch } from '../../redux/types' -import type { Mount } from '../../redux/pipettes/types' -import type { WizardStep } from './types' - -interface Props { - robotName: string - mount: Mount - closeModal: () => void -} - -export function ChangePipette(props: Props): JSX.Element | null { - const { robotName, mount, closeModal } = props - const { t } = useTranslation(['change_pipette', 'shared']) - const navigate = useNavigate() - const dispatch = useDispatch() - const finalRequestId = React.useRef(null) - const [dispatchApiRequests] = useDispatchApiRequests(dispatchedAction => { - if ( - dispatchedAction.type === HOME && - dispatchedAction.payload.target === PIPETTE - ) { - // track final home pipette request, its success closes modal - // @ts-expect-error(sa, 2021-05-27): avoiding src code change, use in operator to type narrow - finalRequestId.current = dispatchedAction.meta.requestId - } - }) - const [wizardStep, setWizardStep] = React.useState(CLEAR_DECK) - const [wantedName, setWantedName] = React.useState(null) - const [confirmExit, setConfirmExit] = React.useState(false) - const [currentStepCount, setCurrentStepCount] = React.useState(0) - // @ts-expect-error(sa, 2021-05-27): avoiding src code change, use in operator to type narrow - const wantedPipette = wantedName ? getPipetteNameSpecs(wantedName) : null - const attachedPipette = useAttachedPipettes()[mount] - const actualPipette = attachedPipette?.modelSpecs || null - const actualPipetteOffset = useSelector((state: State) => - attachedPipette?.id - ? getCalibrationForPipette(state, robotName, attachedPipette.id, mount) - : null - ) - const [ - wrongWantedPipette, - setWrongWantedPipette, - ] = React.useState(wantedPipette) - const [confirmPipetteLevel, setConfirmPipetteLevel] = React.useState( - false - ) - - const movementStatus = useSelector((state: State) => { - return getMovementStatus(state, robotName) - }) - - const homePipStatus = useSelector((state: State) => { - return finalRequestId.current - ? getRequestById(state, finalRequestId.current) - : null - })?.status - - React.useEffect(() => { - if (homePipStatus === SUCCESS) closeModal() - }, [homePipStatus, closeModal]) - - const homePipAndExit = React.useCallback(() => { - dispatchApiRequests(home(robotName, PIPETTE, mount)) - }, [dispatchApiRequests, robotName, mount]) - - const baseProps = { - title: t('pipette_setup'), - subtitle: t('mount', { mount: mount }), - mount, - } - - const basePropsWithPipettes = { - ...baseProps, - robotName, - wantedPipette, - actualPipette, - displayName: actualPipette?.displayName || wantedPipette?.displayName || '', - displayCategory: - actualPipette?.displayCategory || wantedPipette?.displayCategory || null, - } - - let direction - if (currentStepCount === 0) { - direction = actualPipette != null ? DETACH : ATTACH - } else { - direction = wantedPipette != null ? ATTACH : DETACH - } - let eightChannel = wantedPipette?.channels === 8 - // if the user selects a single channel but attaches and accepts an 8 channel - if (actualPipette != null && currentStepCount >= 3 && direction === ATTACH) { - eightChannel = actualPipette?.channels === 8 - } - - const isButtonDisabled = - movementStatus === HOMING || movementStatus === MOVING - - const exitModal = ( - { - setConfirmExit(false) - }} - isDisabled={isButtonDisabled} - exit={homePipAndExit} - direction={direction} - /> - ) - - const success = - // success if we were trying to detach and nothing's attached - (!actualPipette && !wantedPipette) || - // or if the names of wanted and attached match - actualPipette?.name === wantedPipette?.name - - const attachedIncorrectPipette = Boolean( - !success && wantedPipette && actualPipette - ) - - const noPipetteDetach = - direction === DETACH && actualPipette === null && wantedPipette === null - - let exitWizardHeader - let wizardTitle: string = - actualPipette?.displayName != null && - wantedPipette === null && - direction === DETACH - ? t('detach_pipette', { - pipette: actualPipette.displayName, - mount: capitalize(mount), - }) - : t('attach_pipette') - - let contents: JSX.Element | null = null - - if (movementStatus === MOVING) { - contents = ( - - - {t('shared:stand_back_robot_is_in_motion')} - - - ) - } else if (wizardStep === CLEAR_DECK) { - exitWizardHeader = closeModal - contents = ( - { - dispatch(move(robotName, CHANGE_PIPETTE, mount, true)) - setWizardStep(INSTRUCTIONS) - }} - /> - ) - } else if (wizardStep === INSTRUCTIONS) { - const noPipetteSelectedAttach = - direction === ATTACH && wantedPipette === null - - let title - if (currentStepCount === 3) { - title = t('attach_pipette_type', { - pipetteName: wantedPipette?.displayName ?? '', - }) - } else if (actualPipette?.displayName != null) { - title = noPipetteDetach - ? t('detach') - : t('detach_pipette', { - pipette: actualPipette?.displayName ?? wantedPipette?.displayName, - mount: capitalize(mount), - }) - } else { - title = noPipetteSelectedAttach - ? t('attach_pipette') - : t('attach_pipette_type', { - pipetteName: wantedPipette?.displayName ?? '', - }) - } - - exitWizardHeader = confirmExit - ? undefined - : () => { - setConfirmExit(true) - } - wizardTitle = title - - contents = confirmExit ? ( - exitModal - ) : ( - { - setWizardStep(CONFIRM) - }, - back: () => { - setWizardStep(CLEAR_DECK) - }, - currentStepCount, - nextStep: () => { - setCurrentStepCount(currentStepCount + 1) - }, - prevStep: () => { - setCurrentStepCount(currentStepCount - 1) - }, - totalSteps: eightChannel ? EIGHT_CHANNEL_STEPS : SINGLE_CHANNEL_STEPS, - title: - actualPipette?.displayName != null - ? t('detach_pipette', { - pipette: actualPipette.displayName, - mount: capitalize(mount), - }) - : t('attach_pipette'), - }} - /> - ) - } else if (wizardStep === CONFIRM) { - const toCalDashboard = (): void => { - dispatchApiRequests(home(robotName, ROBOT)) - closeModal() - navigate(`/devices/${robotName}/robot-settings/calibration/dashboard`) - } - - exitWizardHeader = - success || confirmExit - ? undefined - : () => { - setConfirmExit(true) - } - - let wizardTitleConfirmPipette - if (wantedPipette == null && actualPipette == null) { - wizardTitleConfirmPipette = t('detach_pipette_from_mount', { - mount: capitalize(mount), - }) - } else if (wantedPipette == null && actualPipette != null) { - wizardTitleConfirmPipette = t('detach') - } else { - wizardTitleConfirmPipette = t('attach_name_pipette', { - pipette: - wrongWantedPipette != null - ? wrongWantedPipette.displayName - : wantedPipette?.displayName, - }) - } - wizardTitle = wizardTitleConfirmPipette - - contents = confirmExit ? ( - exitModal - ) : ( - { - setWizardStep(INSTRUCTIONS) - setCurrentStepCount(currentStepCount - 1) - }, - nextStep: () => { - setCurrentStepCount(currentStepCount + 1) - }, - wrongWantedPipette: wrongWantedPipette, - setWrongWantedPipette: setWrongWantedPipette, - setConfirmPipetteLevel: setConfirmPipetteLevel, - confirmPipetteLevel: confirmPipetteLevel, - exit: homePipAndExit, - actualPipetteOffset: actualPipetteOffset, - toCalibrationDashboard: toCalDashboard, - isDisabled: isButtonDisabled, - }} - /> - ) - } - return ( - - - {contents} - - ) -} diff --git a/app/src/organisms/CheckCalibration/ResultsSummary/index.tsx b/app/src/organisms/CheckCalibration/ResultsSummary/index.tsx deleted file mode 100644 index 54ba1e6110b..00000000000 --- a/app/src/organisms/CheckCalibration/ResultsSummary/index.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { saveAs } from 'file-saver' - -import { - Box, - Flex, - Link, - ALIGN_CENTER, - DIRECTION_COLUMN, - TYPOGRAPHY, - SPACING, - PrimaryButton, - DIRECTION_ROW, - JUSTIFY_SPACE_BETWEEN, -} from '@opentrons/components' - -import find from 'lodash/find' -import { LEFT, RIGHT } from '../../../redux/pipettes' -import { CHECK_STATUS_OUTSIDE_THRESHOLD } from '../../../redux/sessions' -import { CalibrationHealthCheckResults } from './CalibrationHealthCheckResults' -import { RenderMountInformation } from './RenderMountInformation' -import { CalibrationResult } from './CalibrationResult' - -import type { Mount } from '../../../redux/pipettes/types' -import type { CalibrationPanelProps } from '../../../organisms/CalibrationPanels/types' -import type { - CalibrationCheckInstrument, - CalibrationCheckComparisonsPerCalibration, -} from '../../../redux/sessions/types' - -export function ResultsSummary( - props: CalibrationPanelProps -): JSX.Element | null { - const { - comparisonsByPipette, - instruments, - checkBothPipettes, - cleanUpAndExit, - } = props - const { t } = useTranslation('robot_calibration') - if (comparisonsByPipette == null || instruments == null) { - return null - } - - const handleDownloadButtonClick = (): void => { - const now = new Date() - const report = { - comparisonsByPipette, - instruments, - savedAt: now.toISOString(), - } - const data = new Blob([JSON.stringify(report, null, 4)], { - type: 'application/json', - }) - saveAs(data, 'Robot Calibration Check Report.json') - } - - const leftPipette = find( - instruments, - (p: CalibrationCheckInstrument) => p.mount.toLowerCase() === LEFT - ) - const rightPipette = find( - instruments, - (p: CalibrationCheckInstrument) => p.mount.toLowerCase() === RIGHT - ) - type CalibrationByMount = { - [m in Mount]: { - pipette: CalibrationCheckInstrument | undefined - calibration: CalibrationCheckComparisonsPerCalibration | null - } - } - - const calibrationsByMount: CalibrationByMount = { - left: { - pipette: leftPipette, - calibration: - leftPipette != null - ? comparisonsByPipette?.[leftPipette.rank] ?? null - : null, - }, - right: { - pipette: rightPipette, - calibration: - rightPipette != null - ? comparisonsByPipette?.[rightPipette.rank] ?? null - : null, - }, - } - - const getDeckCalibration = checkBothPipettes - ? comparisonsByPipette.second.deck?.status - : comparisonsByPipette.first.deck?.status - const deckCalibrationResult = getDeckCalibration ?? null - - const pipetteResultsBad = ( - perPipette: CalibrationCheckComparisonsPerCalibration | null - ): { offsetBad: boolean; tipLengthBad: boolean } => ({ - offsetBad: perPipette?.pipetteOffset?.status - ? perPipette.pipetteOffset.status === CHECK_STATUS_OUTSIDE_THRESHOLD - : false, - tipLengthBad: perPipette?.tipLength?.status - ? perPipette.tipLength.status === CHECK_STATUS_OUTSIDE_THRESHOLD - : false, - }) - - const isDeckResultBad = - deckCalibrationResult != null - ? deckCalibrationResult === CHECK_STATUS_OUTSIDE_THRESHOLD - : false - - // check all calibration status - // if all of them are good, this returns true. otherwise return false - const isCalibrationRecommended = (): boolean => { - const isOffsetsBad = - pipetteResultsBad(calibrationsByMount.left.calibration).offsetBad && - pipetteResultsBad(calibrationsByMount.right.calibration).offsetBad - const isTipLensBad = - pipetteResultsBad(calibrationsByMount.left.calibration).tipLengthBad && - pipetteResultsBad(calibrationsByMount.right.calibration).tipLengthBad - return isDeckResultBad && isOffsetsBad && isTipLensBad - } - - return ( - - - - - - - - - - {leftPipette != null && ( - <> - - - - )} - - - - {rightPipette != null && ( - <> - - - - )} - - - - {t('download_details')} - - {t('finish')} - - - ) -} diff --git a/app/src/organisms/CheckCalibration/index.tsx b/app/src/organisms/CheckCalibration/index.tsx deleted file mode 100644 index 595afa1b8cf..00000000000 --- a/app/src/organisms/CheckCalibration/index.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' - -import { getPipetteModelSpecs } from '@opentrons/shared-data' -import { useConditionalConfirm, ModalShell } from '@opentrons/components' - -import * as Sessions from '../../redux/sessions' -import { - Introduction, - DeckSetup, - TipPickUp, - TipConfirmation, - SaveZPoint, - SaveXYPoint, - MeasureNozzle, - MeasureTip, - LoadingState, - ConfirmExit, -} from '../../organisms/CalibrationPanels' -import { WizardHeader } from '../../molecules/WizardHeader' -import { getTopPortalEl } from '../../App/portal' -import { ReturnTip } from './ReturnTip' -import { ResultsSummary } from './ResultsSummary' - -import type { Mount } from '@opentrons/components' -import type { - CalibrationLabware, - RobotCalibrationCheckPipetteRank, - RobotCalibrationCheckStep, - SessionCommandParams, -} from '../../redux/sessions/types' - -import type { CalibrationPanelProps } from '../../organisms/CalibrationPanels/types' -import type { CalibrationCheckParentProps } from './types' -import { CHECK_PIPETTE_RANK_FIRST } from '../../redux/sessions' - -const ROBOT_CALIBRATION_CHECK_SUBTITLE = 'Calibration health check' - -const PANEL_BY_STEP: { - [step in RobotCalibrationCheckStep]?: React.ComponentType -} = { - [Sessions.CHECK_STEP_SESSION_STARTED]: Introduction, - [Sessions.CHECK_STEP_LABWARE_LOADED]: DeckSetup, - [Sessions.CHECK_STEP_COMPARING_NOZZLE]: MeasureNozzle, - [Sessions.CHECK_STEP_PREPARING_PIPETTE]: TipPickUp, - [Sessions.CHECK_STEP_INSPECTING_TIP]: TipConfirmation, - [Sessions.CHECK_STEP_COMPARING_TIP]: MeasureTip, - [Sessions.CHECK_STEP_COMPARING_HEIGHT]: SaveZPoint, - [Sessions.CHECK_STEP_COMPARING_POINT_ONE]: SaveXYPoint, - [Sessions.CHECK_STEP_COMPARING_POINT_TWO]: SaveXYPoint, - [Sessions.CHECK_STEP_COMPARING_POINT_THREE]: SaveXYPoint, - [Sessions.CHECK_STEP_RETURNING_TIP]: ReturnTip, - [Sessions.CHECK_STEP_RESULTS_SUMMARY]: ResultsSummary, -} - -const STEPS_IN_ORDER_ONE_PIPETTE: RobotCalibrationCheckStep[] = [ - Sessions.CHECK_STEP_SESSION_STARTED, - Sessions.CHECK_STEP_LABWARE_LOADED, - Sessions.CHECK_STEP_COMPARING_NOZZLE, - Sessions.CHECK_STEP_PREPARING_PIPETTE, - Sessions.CHECK_STEP_INSPECTING_TIP, - Sessions.CHECK_STEP_COMPARING_TIP, - Sessions.CHECK_STEP_COMPARING_HEIGHT, - Sessions.CHECK_STEP_COMPARING_POINT_ONE, - Sessions.CHECK_STEP_COMPARING_POINT_TWO, - Sessions.CHECK_STEP_COMPARING_POINT_THREE, - Sessions.CHECK_STEP_RETURNING_TIP, - Sessions.CHECK_STEP_RESULTS_SUMMARY, -] -const STEPS_IN_ORDER_BOTH_PIPETTES: RobotCalibrationCheckStep[] = [ - Sessions.CHECK_STEP_SESSION_STARTED, - Sessions.CHECK_STEP_LABWARE_LOADED, - Sessions.CHECK_STEP_COMPARING_NOZZLE, - Sessions.CHECK_STEP_PREPARING_PIPETTE, - Sessions.CHECK_STEP_INSPECTING_TIP, - Sessions.CHECK_STEP_COMPARING_TIP, - Sessions.CHECK_STEP_COMPARING_HEIGHT, - Sessions.CHECK_STEP_COMPARING_POINT_ONE, - Sessions.CHECK_STEP_RETURNING_TIP, - Sessions.CHECK_STEP_LABWARE_LOADED, - Sessions.CHECK_STEP_COMPARING_NOZZLE, - Sessions.CHECK_STEP_PREPARING_PIPETTE, - Sessions.CHECK_STEP_INSPECTING_TIP, - Sessions.CHECK_STEP_COMPARING_TIP, - Sessions.CHECK_STEP_COMPARING_HEIGHT, - Sessions.CHECK_STEP_COMPARING_POINT_ONE, - Sessions.CHECK_STEP_COMPARING_POINT_TWO, - Sessions.CHECK_STEP_COMPARING_POINT_THREE, - Sessions.CHECK_STEP_RETURNING_TIP, - Sessions.CHECK_STEP_RESULTS_SUMMARY, -] -function getStepIndexCheckingBothPipettes( - currentStep: RobotCalibrationCheckStep | null, - rank: RobotCalibrationCheckPipetteRank | null -): number { - if (currentStep == null || rank == null) return 0 - return rank === CHECK_PIPETTE_RANK_FIRST - ? STEPS_IN_ORDER_BOTH_PIPETTES.findIndex(step => step === currentStep) - : STEPS_IN_ORDER_BOTH_PIPETTES.slice(9).findIndex( - step => step === currentStep - ) + 9 -} - -export function CheckCalibration( - props: CalibrationCheckParentProps -): JSX.Element | null { - const { t } = useTranslation('robot_calibration') - const { session, robotName, dispatchRequests, showSpinner, isJogging } = props - const { - currentStep, - activePipette, - activeTipRack, - instruments, - comparisonsByPipette, - labware, - } = session?.details || {} - - const { - showConfirmation: showConfirmExit, - confirm: confirmExit, - cancel: cancelExit, - } = useConditionalConfirm(() => { - cleanUpAndExit() - }, true) - - const isMulti = React.useMemo(() => { - const spec = activePipette && getPipetteModelSpecs(activePipette.model) - return spec ? spec.channels > 1 : false - }, [activePipette]) - - const calBlock: CalibrationLabware | null = labware - ? labware.find(l => !l.isTiprack) ?? null - : null - - function sendCommands(...commands: SessionCommandParams[]): void { - if (session?.id && !isJogging) { - const sessionCommandActions = commands.map(c => - Sessions.createSessionCommand(robotName, session.id, { - command: c.command, - data: c.data || {}, - }) - ) - dispatchRequests(...sessionCommandActions) - } - } - - function cleanUpAndExit(): void { - if (session?.id) { - dispatchRequests( - Sessions.createSessionCommand(robotName, session.id, { - command: Sessions.sharedCalCommands.EXIT, - data: {}, - }), - Sessions.deleteSession(robotName, session.id) - ) - } - } - - const checkBothPipettes = instruments?.length === 2 - const stepIndex = checkBothPipettes - ? getStepIndexCheckingBothPipettes( - currentStep ?? null, - activePipette?.rank ?? null - ) - : STEPS_IN_ORDER_ONE_PIPETTE.findIndex(step => step === currentStep) ?? 0 - - if (!session || !activeTipRack) { - return null - } - - const Panel = - currentStep != null && currentStep in PANEL_BY_STEP - ? PANEL_BY_STEP[currentStep] - : null - return createPortal( - - } - > - {showSpinner || currentStep == null || Panel == null ? ( - - ) : showConfirmExit ? ( - - ) : ( - - )} - , - getTopPortalEl() - ) -} diff --git a/app/src/organisms/CheckCalibration/types.ts b/app/src/organisms/CheckCalibration/types.ts deleted file mode 100644 index 03a70b2127c..00000000000 --- a/app/src/organisms/CheckCalibration/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { CalibrationCheckSession } from '../../redux/sessions/types' -import type { DispatchRequestsType } from '../../redux/robot-api' - -export interface CalibrationCheckParentProps { - robotName: string - session: CalibrationCheckSession | null - dispatchRequests: DispatchRequestsType - isJogging: boolean - showSpinner: boolean - hasBlock?: boolean -} diff --git a/app/src/organisms/ChildNavigation/index.tsx b/app/src/organisms/ChildNavigation/index.tsx deleted file mode 100644 index 00e8d02c36b..00000000000 --- a/app/src/organisms/ChildNavigation/index.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import * as React from 'react' -import styled from 'styled-components' - -import { - ALIGN_CENTER, - COLORS, - DIRECTION_ROW, - Flex, - Icon, - JUSTIFY_FLEX_START, - JUSTIFY_SPACE_BETWEEN, - POSITION_FIXED, - RESPONSIVENESS, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' -import { ODD_FOCUS_VISIBLE } from '../../atoms/buttons/constants' - -import { SmallButton } from '../../atoms/buttons' -import { InlineNotification } from '../../atoms/InlineNotification' - -import type { IconName, StyleProps } from '@opentrons/components' -import type { InlineNotificationProps } from '../../atoms/InlineNotification' -import type { - IconPlacement, - SmallButtonTypes, -} from '../../atoms/buttons/SmallButton' - -interface ChildNavigationProps extends StyleProps { - header: string - onClickBack?: React.MouseEventHandler - buttonText?: React.ReactNode - inlineNotification?: InlineNotificationProps - onClickButton?: React.MouseEventHandler - buttonType?: SmallButtonTypes - buttonIsDisabled?: boolean - iconName?: IconName - iconPlacement?: IconPlacement - secondaryButtonProps?: React.ComponentProps - ariaDisabled?: boolean -} - -export function ChildNavigation({ - buttonText, - header, - inlineNotification, - onClickBack, - onClickButton, - buttonType = 'primary', - iconName, - iconPlacement, - secondaryButtonProps, - buttonIsDisabled, - ariaDisabled = false, - ...styleProps -}: ChildNavigationProps): JSX.Element { - return ( - - - {onClickBack != null ? ( - - - - ) : null} - - {header} - - - {onClickButton != null && buttonText != null ? ( - - {secondaryButtonProps != null ? ( - - ) : null} - - - - ) : null} - {inlineNotification != null ? ( - - ) : null} - - ) -} - -const IconButton = styled('button')` - border-radius: ${SPACING.spacing4}; - max-height: 100%; - background-color: ${COLORS.white}; - - &:focus-visible { - box-shadow: ${ODD_FOCUS_VISIBLE}; - background-color: ${COLORS.grey35}; - } - &:disabled { - background-color: transparent; - } - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - cursor: default; - } -` diff --git a/app/src/organisms/ChooseProtocolSlideout/index.tsx b/app/src/organisms/ChooseProtocolSlideout/index.tsx deleted file mode 100644 index a5c19d08e86..00000000000 --- a/app/src/organisms/ChooseProtocolSlideout/index.tsx +++ /dev/null @@ -1,935 +0,0 @@ -import * as React from 'react' -import first from 'lodash/first' -import { Trans, useTranslation } from 'react-i18next' -import { Link, NavLink, useNavigate } from 'react-router-dom' -import { useSelector } from 'react-redux' -import { css } from 'styled-components' - -import { - ALIGN_CENTER, - BORDERS, - Box, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - DISPLAY_BLOCK, - DropdownMenu, - Flex, - Icon, - InputField, - JUSTIFY_CENTER, - JUSTIFY_END, - JUSTIFY_FLEX_START, - LegacyStyledText, - Link as LinkComponent, - OVERFLOW_WRAP_ANYWHERE, - PrimaryButton, - ProtocolDeck, - SecondaryButton, - SPACING, - Tooltip, - TYPOGRAPHY, - useHoverTooltip, - useTooltip, -} from '@opentrons/components' -import { - ApiHostProvider, - useUploadCsvFileMutation, -} from '@opentrons/react-api-client' -import { sortRuntimeParameters } from '@opentrons/shared-data' - -import { useLogger } from '../../logger' -import { OPENTRONS_USB } from '../../redux/discovery' -import { getStoredProtocols } from '../../redux/protocol-storage' -import { appShellRequestor } from '../../redux/shell/remote' -import { MultiSlideout } from '../../atoms/Slideout/MultiSlideout' -import { ToggleButton } from '../../atoms/buttons' -import { MiniCard } from '../../molecules/MiniCard' -import { UploadInput } from '../../molecules/UploadInput' -import { useTrackCreateProtocolRunEvent } from '../Devices/hooks' -import { useCreateRunFromProtocol } from '../ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol' -import { ApplyHistoricOffsets } from '../ApplyHistoricOffsets' -import { useOffsetCandidatesForAnalysis } from '../ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' -import { FileCard } from '../ChooseRobotSlideout/FileCard' -import { - getRunTimeParameterFilesForRun, - getRunTimeParameterValuesForRun, -} from '../Devices/utils' -import { getAnalysisStatus } from '../ProtocolsLanding/utils' - -import type { DropdownOption } from '@opentrons/components' -import type { RunTimeParameter } from '@opentrons/shared-data' -import type { Robot } from '../../redux/discovery/types' -import type { StoredProtocolData } from '../../redux/protocol-storage' -import type { State } from '../../redux/types' - -export const CARD_OUTLINE_BORDER_STYLE = css` - border-style: ${BORDERS.styleSolid}; - border-width: 1px; - border-color: ${COLORS.grey30}; - border-radius: ${BORDERS.borderRadius4}; - &:hover { - border-color: ${COLORS.grey55}; - } -` - -const TOOLTIP_DELAY_MS = 2000 - -const _getFileBaseName = (filePath: string): string => { - return filePath.split('/').reverse()[0] -} - -interface ChooseProtocolSlideoutProps { - robot: Robot - onCloseClick: () => void - showSlideout: boolean -} -export function ChooseProtocolSlideoutComponent( - props: ChooseProtocolSlideoutProps -): JSX.Element | null { - const { t } = useTranslation(['device_details', 'shared']) - const navigate = useNavigate() - const logger = useLogger(new URL('', import.meta.url).pathname) - const [targetProps, tooltipProps] = useTooltip() - const [targetPropsHover, tooltipPropsHover] = useHoverTooltip() - const [ - showRestoreValuesTooltip, - setShowRestoreValuesTooltip, - ] = React.useState(false) - - const { robot, showSlideout, onCloseClick } = props - const { name } = robot - - const [ - selectedProtocol, - setSelectedProtocol, - ] = React.useState(null) - const [ - runTimeParametersOverrides, - setRunTimeParametersOverrides, - ] = React.useState([]) - const [currentPage, setCurrentPage] = React.useState(1) - const [hasParamError, setHasParamError] = React.useState(false) - const [hasMissingFileParam, setHasMissingFileParam] = React.useState( - runTimeParametersOverrides?.some( - parameter => parameter.type === 'csv_file' - ) ?? false - ) - const [isInputFocused, setIsInputFocused] = React.useState(false) - - React.useEffect(() => { - setRunTimeParametersOverrides( - selectedProtocol?.mostRecentAnalysis?.runTimeParameters ?? [] - ) - }, [selectedProtocol]) - React.useEffect(() => { - setHasParamError(errors.length > 0) - setHasMissingFileParam( - runTimeParametersOverrides.some( - parameter => - parameter.type === 'csv_file' && parameter.file?.file == null - ) - ) - }, [runTimeParametersOverrides]) - - const runTimeParametersFromAnalysis = - selectedProtocol?.mostRecentAnalysis?.runTimeParameters ?? [] - - const hasRunTimeParameters = runTimeParametersFromAnalysis.length > 0 - - const analysisStatus = getAnalysisStatus( - false, - selectedProtocol?.mostRecentAnalysis - ) - const missingAnalysisData = - analysisStatus === 'error' || analysisStatus === 'stale' - - const [shouldApplyOffsets, setShouldApplyOffsets] = React.useState(true) - const offsetCandidates = useOffsetCandidatesForAnalysis( - (!missingAnalysisData ? selectedProtocol?.mostRecentAnalysis : null) ?? - null, - robot.ip - ) - - const { uploadCsvFile } = useUploadCsvFileMutation( - {}, - robot != null - ? { - hostname: robot.ip, - requestor: - robot?.ip === OPENTRONS_USB ? appShellRequestor : undefined, - } - : null - ) - - const srcFileObjects = - selectedProtocol != null - ? selectedProtocol.srcFiles.map((srcFileBuffer, index) => { - const srcFilePath = selectedProtocol.srcFileNames[index] - return new File([srcFileBuffer], _getFileBaseName(srcFilePath)) - }) - : [] - - const { trackCreateProtocolRunEvent } = useTrackCreateProtocolRunEvent( - selectedProtocol, - name - ) - - const { - createRunFromProtocolSource, - runCreationError, - isCreatingRun, - reset: resetCreateRun, - runCreationErrorCode, - } = useCreateRunFromProtocol( - { - onSuccess: ({ data: runData }) => { - trackCreateProtocolRunEvent({ - name: 'createProtocolRecordResponse', - properties: { success: true }, - }) - navigate(`/devices/${name}/protocol-runs/${runData.id}`) - }, - onError: (error: Error) => { - trackCreateProtocolRunEvent({ - name: 'createProtocolRecordResponse', - properties: { success: false, error: error.message }, - }) - }, - }, - { hostname: robot.ip }, - shouldApplyOffsets - ? offsetCandidates.map(({ vector, location, definitionUri }) => ({ - vector, - location, - definitionUri, - })) - : [] - ) - const handleProceed: React.MouseEventHandler = () => { - if (selectedProtocol != null) { - trackCreateProtocolRunEvent({ name: 'createProtocolRecordRequest' }) - const dataFilesForProtocolMap = runTimeParametersOverrides.reduce< - Record - >( - (acc, parameter) => - parameter.type === 'csv_file' && parameter.file?.file != null - ? { ...acc, [parameter.variableName]: parameter.file.file } - : acc, - {} - ) - void Promise.all( - Object.entries(dataFilesForProtocolMap).map(([key, file]) => { - const fileResponse = uploadCsvFile(file) - const varName = Promise.resolve(key) - return Promise.all([fileResponse, varName]) - }) - ).then(responseTuples => { - const mappedResolvedCsvVariableToFileId = responseTuples.reduce< - Record - >((acc, [uploadedFileResponse, variableName]) => { - return { ...acc, [variableName]: uploadedFileResponse.data.id } - }, {}) - const runTimeParameterValues = getRunTimeParameterValuesForRun( - runTimeParametersOverrides - ) - const runTimeParameterFiles = getRunTimeParameterFilesForRun( - runTimeParametersOverrides, - mappedResolvedCsvVariableToFileId - ) - createRunFromProtocolSource({ - files: srcFileObjects, - protocolKey: selectedProtocol.protocolKey, - runTimeParameterValues, - runTimeParameterFiles, - }) - }) - } else { - logger.warn('failed to create protocol, no protocol selected') - } - } - - const isRestoreDefaultsLinkEnabled = - runTimeParametersOverrides?.some(parameter => - parameter.type === 'csv_file' - ? parameter.file != null - : parameter.value !== parameter.default - ) ?? false - - const errors: string[] = [] - const runTimeParametersInputs = - runTimeParametersOverrides != null - ? sortRuntimeParameters(runTimeParametersOverrides).map( - (runtimeParam, index) => { - if ('choices' in runtimeParam) { - const dropdownOptions = runtimeParam.choices.map(choice => { - return { name: choice.displayName, value: choice.value } - }) as DropdownOption[] - return ( - { - return choice.value === runtimeParam.value - }) ?? dropdownOptions[0] - } - onClick={choice => { - const clone = runTimeParametersOverrides.map(parameter => { - if ( - runtimeParam.variableName === parameter.variableName && - 'choices' in parameter - ) { - return { - ...parameter, - value: - dropdownOptions.find( - option => option.value === choice - )?.value ?? parameter.default, - } - } - return parameter - }) - setRunTimeParametersOverrides?.(clone as RunTimeParameter[]) - }} - title={runtimeParam.displayName} - width="100%" - dropdownType="neutral" - tooltipText={runtimeParam.description} - /> - ) - } else if ( - runtimeParam.type === 'int' || - runtimeParam.type === 'float' - ) { - const value = runtimeParam.value as number - const id = `InputField_${runtimeParam.variableName}_${index}` - const error = - (Number.isNaN(value) && !isInputFocused) || - value < runtimeParam.min || - value > runtimeParam.max - ? t(`value_out_of_range`, { - min: - runtimeParam.type === 'int' - ? runtimeParam.min - : runtimeParam.min.toFixed(1), - max: - runtimeParam.type === 'int' - ? runtimeParam.max - : runtimeParam.max.toFixed(1), - }) - : null - if (error != null) { - errors.push(error as string) - } - return ( - { - setIsInputFocused(false) - }} - onFocus={() => { - setIsInputFocused(true) - }} - onChange={e => { - const clone = runTimeParametersOverrides.map(parameter => { - if ( - runtimeParam.variableName === parameter.variableName && - (parameter.type === 'int' || parameter.type === 'float') - ) { - return { - ...parameter, - value: - runtimeParam.type === 'int' - ? Math.round(e.target.valueAsNumber) - : e.target.valueAsNumber, - } - } - return parameter - }) - setRunTimeParametersOverrides?.(clone) - }} - /> - ) - } else if (runtimeParam.type === 'bool') { - return ( - - - {runtimeParam.displayName} - - - { - const clone = runTimeParametersOverrides.map( - parameter => { - if ( - runtimeParam.variableName === - parameter.variableName && - parameter.type === 'bool' - ) { - return { - ...parameter, - value: !Boolean(parameter.value), - } - } - return parameter - } - ) - setRunTimeParametersOverrides?.(clone) - }} - height="0.813rem" - label={ - Boolean(runtimeParam.value) - ? t('protocol_details:on') - : t('protocol_details:off') - } - paddingTop={SPACING.spacing2} // manual alignment of SVG with value label - /> - - {Boolean(runtimeParam.value) - ? t('protocol_details:on') - : t('protocol_details:off')} - - - - {runtimeParam.description} - - - ) - } else if (runtimeParam.type === 'csv_file') { - const error = - runtimeParam.file?.file?.type === 'text/csv' - ? null - : t('protocol_details:csv_file_type_required') - if (error != null) { - errors.push(error as string) - } - return ( - - - - {t('protocol_details:csv_file')} - - - {t('protocol_details:csv_required')} - - - {runtimeParam.file == null ? ( - { - const clone = runTimeParametersOverrides.map( - parameter => { - if ( - runtimeParam.variableName === - parameter.variableName - ) { - return { - ...parameter, - file: { file }, - } - } - return parameter - } - ) - setRunTimeParametersOverrides?.(clone) - }} - dragAndDropText={ - - - ), - }} - /> - - } - /> - ) : ( - - )} - - ) - } - } - ) - : null - - const resetRunTimeParameters = (): void => { - const clone = runTimeParametersOverrides.map(parameter => - parameter.type === 'csv_file' - ? { ...parameter, file: null } - : { ...parameter, value: parameter.default } - ) - setRunTimeParametersOverrides(clone as RunTimeParameter[]) - } - - const pageTwoBody = ( - - - { - if (isRestoreDefaultsLinkEnabled) { - resetRunTimeParameters?.() - } else { - setShowRestoreValuesTooltip(true) - setTimeout(() => { - setShowRestoreValuesTooltip(false) - }, TOOLTIP_DELAY_MS) - } - }} - paddingBottom={SPACING.spacing10} - {...targetProps} - > - {t('protocol_details:restore_defaults')} - - - {t('protocol_details:no_custom_values')} - {' '} - - - {runTimeParametersInputs} - - - ) - - const singlePageFooter = ( - - {isCreatingRun ? ( - - ) : ( - t('shared:proceed_to_setup') - )} - - ) - - const multiPageFooter = - currentPage === 1 ? ( - { - setCurrentPage(2) - }} - width="100%" - disabled={isCreatingRun || selectedProtocol == null} - > - {t('shared:continue_to_param')} - - ) : ( - - { - setCurrentPage(1) - }} - width="51%" - > - {t('shared:change_protocol')} - - - {isCreatingRun ? ( - - - {t('shared:confirm_values')} - - ) : ( - t('shared:confirm_values') - )} - - {hasMissingFileParam ? ( - - {t('protocol_details:add_required_csv_file')} - - ) : null} - - ) - - return ( - { - onCloseClick() - setCurrentPage(1) - resetRunTimeParameters() - }} - currentStep={currentPage} - maxSteps={hasRunTimeParameters ? 2 : 1} - title={t('choose_protocol_to_run', { name })} - footer={ - - {currentPage === 1 ? ( - - ) : null} - {hasRunTimeParameters ? multiPageFooter : singlePageFooter} - - } - > - {showSlideout ? ( - currentPage === 1 ? ( - { - if (!isCreatingRun) { - resetCreateRun() - setSelectedProtocol(storedProtocol) - } - }} - robot={robot} - {...{ selectedProtocol, runCreationError, runCreationErrorCode }} - /> - ) : ( - pageTwoBody - ) - ) : null} - - ) -} - -export function ChooseProtocolSlideout( - props: ChooseProtocolSlideoutProps -): JSX.Element | null { - return -} - -interface StoredProtocolListProps { - selectedProtocol: StoredProtocolData | null - handleSelectProtocol: (storedProtocol: StoredProtocolData | null) => void - runCreationError: string | null - runCreationErrorCode: number | null - robot: Robot -} - -function StoredProtocolList(props: StoredProtocolListProps): JSX.Element { - const { - selectedProtocol, - handleSelectProtocol, - runCreationError, - runCreationErrorCode, - robot, - } = props - const { t } = useTranslation(['device_details', 'protocol_details', 'shared']) - const storedProtocols = useSelector((state: State) => - getStoredProtocols(state) - ).filter( - protocol => protocol.mostRecentAnalysis?.robotType === robot.robotModel - ) - React.useEffect(() => { - handleSelectProtocol(first(storedProtocols) ?? null) - }, []) - - return storedProtocols.length > 0 ? ( - - {storedProtocols.map(storedProtocol => { - const isSelected = - selectedProtocol != null && - storedProtocol.protocolKey === selectedProtocol.protocolKey - const analysisStatus = getAnalysisStatus( - false, - storedProtocol.mostRecentAnalysis - ) - const missingAnalysisData = - analysisStatus === 'error' || analysisStatus === 'stale' - const requiresCsvRunTimeParameter = - analysisStatus === 'parameterRequired' - return ( - - - { - handleSelectProtocol(storedProtocol) - }} - > - - {!missingAnalysisData && !requiresCsvRunTimeParameter ? ( - - - - ) : ( - - )} - - {storedProtocol.mostRecentAnalysis?.metadata - ?.protocolName ?? - first(storedProtocol.srcFileNames) ?? - storedProtocol.protocolKey} - - - {(runCreationError != null || - missingAnalysisData || - requiresCsvRunTimeParameter) && - isSelected ? ( - <> - - - - ) : null} - - - {runCreationError != null && isSelected ? ( - - {runCreationErrorCode === 409 ? ( - - ), - }} - /> - ) : ( - runCreationError - )} - - ) : null} - {requiresCsvRunTimeParameter && isSelected ? ( - - {t('csv_required_for_analysis')} - - ) : null} - {missingAnalysisData && isSelected ? ( - - {analysisStatus === 'stale' - ? t('protocol_analysis_stale') - : t('protocol_analysis_failed')} - { - - ), - }} - /> - } - - ) : null} - - ) - })} - - ) : ( - - - - {t('no_protocols_found')} - - - , - }} - /> - - - ) -} - -const ENABLED_LINK_CSS = css` - ${TYPOGRAPHY.linkPSemiBold} - cursor: pointer; -` - -const DISABLED_LINK_CSS = css` - ${TYPOGRAPHY.linkPSemiBold} - color: ${COLORS.grey40}; - cursor: default; - - &:hover { - color: ${COLORS.grey40}; - } -` diff --git a/app/src/organisms/ChooseRobotSlideout/index.tsx b/app/src/organisms/ChooseRobotSlideout/index.tsx deleted file mode 100644 index 532be4f7813..00000000000 --- a/app/src/organisms/ChooseRobotSlideout/index.tsx +++ /dev/null @@ -1,696 +0,0 @@ -import * as React from 'react' -import { useTranslation, Trans } from 'react-i18next' -import { useSelector, useDispatch } from 'react-redux' -import { NavLink } from 'react-router-dom' -import { css } from 'styled-components' - -import { - ALIGN_CENTER, - ALIGN_FLEX_END, - BORDERS, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - DISPLAY_INLINE_BLOCK, - DropdownMenu, - Flex, - Icon, - InputField, - JUSTIFY_CENTER, - JUSTIFY_END, - JUSTIFY_FLEX_START, - LegacyStyledText, - Link, - OVERFLOW_WRAP_ANYWHERE, - SIZE_1, - SIZE_4, - SPACING, - Tooltip, - TYPOGRAPHY, - useTooltip, -} from '@opentrons/components' - -import { - FLEX_ROBOT_TYPE, - OT2_ROBOT_TYPE, - sortRuntimeParameters, -} from '@opentrons/shared-data' -import { - getConnectableRobots, - getReachableRobots, - getUnreachableRobots, - getScanning, - startDiscovery, - RE_ROBOT_MODEL_OT2, - RE_ROBOT_MODEL_OT3, -} from '../../redux/discovery' -import { Banner } from '../../atoms/Banner' -import { Slideout } from '../../atoms/Slideout' -import { MultiSlideout } from '../../atoms/Slideout/MultiSlideout' -import { ToggleButton } from '../../atoms/buttons' -import { AvailableRobotOption } from './AvailableRobotOption' -import { UploadInput } from '../../molecules/UploadInput' -import { FileCard } from './FileCard' - -import type { RobotType, RunTimeParameter } from '@opentrons/shared-data' -import type { DropdownOption } from '@opentrons/components' -import type { SlideoutProps } from '../../atoms/Slideout' -import type { UseCreateRun } from '../../organisms/ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol' -import type { State, Dispatch } from '../../redux/types' -import type { Robot } from '../../redux/discovery/types' - -export const CARD_OUTLINE_BORDER_STYLE = css` - border-style: ${BORDERS.styleSolid}; - border-width: 1px; - border-color: ${COLORS.grey30}; - border-radius: ${BORDERS.borderRadius8}; - &:hover { - border-color: ${COLORS.grey55}; - } -` - -const TOOLTIP_DELAY_MS = 2000 - -interface RobotIsBusyAction { - type: 'robotIsBusy' - robotName: string -} - -interface RobotIsIdleAction { - type: 'robotIsIdle' - robotName: string -} - -interface RobotBusyStatusByName { - [robotName: string]: boolean -} - -export type RobotBusyStatusAction = RobotIsBusyAction | RobotIsIdleAction - -function robotBusyStatusByNameReducer( - state: RobotBusyStatusByName, - action: RobotBusyStatusAction -): RobotBusyStatusByName { - switch (action.type) { - case 'robotIsBusy': { - return { - ...state, - [action.robotName]: true, - } - } - case 'robotIsIdle': { - return { - ...state, - [action.robotName]: false, - } - } - } -} - -interface ChooseRobotSlideoutProps - extends Omit, - Partial { - isSelectedRobotOnDifferentSoftwareVersion: boolean - robotType: RobotType | null - selectedRobot: Robot | null - setSelectedRobot: (robot: Robot | null) => void - runTimeParametersOverrides?: RunTimeParameter[] - setRunTimeParametersOverrides?: (parameters: RunTimeParameter[]) => void - isAnalysisError?: boolean - isAnalysisStale?: boolean - showIdleOnly?: boolean - multiSlideout?: { currentPage: number } | null - setHasParamError?: (isError: boolean) => void - resetRunTimeParameters?: () => void - setHasMissingFileParam?: (isMissing: boolean) => void -} - -export function ChooseRobotSlideout( - props: ChooseRobotSlideoutProps -): JSX.Element { - const { t } = useTranslation(['protocol_details', 'shared', 'app_settings']) - const { - isExpanded, - onCloseClick, - title, - footer, - isAnalysisError = false, - isAnalysisStale = false, - isCreatingRun = false, - isSelectedRobotOnDifferentSoftwareVersion, - reset: resetCreateRun, - runCreationError, - runCreationErrorCode, - selectedRobot, - setSelectedRobot, - robotType, - showIdleOnly = false, - multiSlideout = null, - runTimeParametersOverrides, - setRunTimeParametersOverrides, - setHasParamError, - resetRunTimeParameters, - setHasMissingFileParam, - } = props - - const dispatch = useDispatch() - const isScanning = useSelector((state: State) => getScanning(state)) - const [targetProps, tooltipProps] = useTooltip() - const [ - showRestoreValuesTooltip, - setShowRestoreValuesTooltip, - ] = React.useState(false) - const [isInputFocused, setIsInputFocused] = React.useState(false) - - const unhealthyReachableRobots = useSelector((state: State) => - getReachableRobots(state) - ).filter(robot => { - if (robotType === FLEX_ROBOT_TYPE) { - return RE_ROBOT_MODEL_OT3.test(robot.robotModel) - } else if (robotType === OT2_ROBOT_TYPE) { - return RE_ROBOT_MODEL_OT2.test(robot.robotModel) - } else { - return true - } - }) - const unreachableRobots = useSelector((state: State) => - getUnreachableRobots(state) - ).filter(robot => { - if (robotType === FLEX_ROBOT_TYPE) { - return RE_ROBOT_MODEL_OT3.test(robot.robotModel) - } else if (robotType === OT2_ROBOT_TYPE) { - return RE_ROBOT_MODEL_OT2.test(robot.robotModel) - } else { - return true - } - }) - const healthyReachableRobots = useSelector((state: State) => - getConnectableRobots(state) - ).filter(robot => { - if (robotType === FLEX_ROBOT_TYPE) { - return robot.robotModel === FLEX_ROBOT_TYPE - } else if (robotType === OT2_ROBOT_TYPE) { - return robot.robotModel === OT2_ROBOT_TYPE - } else { - return true - } - }) - - const [robotBusyStatusByName, registerRobotBusyStatus] = React.useReducer( - robotBusyStatusByNameReducer, - {} - ) - - const reducerAvailableRobots = healthyReachableRobots.filter(robot => - showIdleOnly ? !robotBusyStatusByName[robot.name] : robot - ) - const reducerBusyCount = healthyReachableRobots.filter( - robot => robotBusyStatusByName[robot.name] - ).length - - // this useEffect sets the default selection to the first robot in the list. state is managed by the caller - React.useEffect(() => { - if ( - (selectedRobot == null || - !reducerAvailableRobots.some( - robot => robot.name === selectedRobot.name - )) && - reducerAvailableRobots.length > 0 - ) { - setSelectedRobot(reducerAvailableRobots[0]) - } else if (reducerAvailableRobots.length === 0) { - setSelectedRobot(null) - } - }, [reducerAvailableRobots, selectedRobot, setSelectedRobot]) - - const unavailableCount = - unhealthyReachableRobots.length + unreachableRobots.length - - const pageOneBody = ( - - {isAnalysisError ? ( - {t('protocol_failed_app_analysis')} - ) : null} - {isAnalysisStale ? ( - {t('protocol_outdated_app_analysis')} - ) : null} - - {isScanning ? ( - - - {t('app_settings:searching')} - - - - ) : ( - dispatch(startDiscovery())} - textTransform={TYPOGRAPHY.textTransformCapitalize} - role="button" - css={TYPOGRAPHY.linkPSemiBold} - > - {t('shared:refresh')} - - )} - - {!isScanning && healthyReachableRobots.length === 0 ? ( - - - - {t('no_available_robots_found')} - - - ) : ( - healthyReachableRobots.map(robot => { - const isSelected = - selectedRobot != null && selectedRobot.ip === robot.ip - return ( - - { - if (!isCreatingRun) { - resetCreateRun?.() - setSelectedRobot(robot) - } - }} - isError={runCreationError != null} - isSelected={isSelected} - isSelectedRobotOnDifferentSoftwareVersion={ - isSelectedRobotOnDifferentSoftwareVersion - } - showIdleOnly={showIdleOnly} - registerRobotBusyStatus={registerRobotBusyStatus} - /> - {runCreationError != null && isSelected && ( - - {runCreationErrorCode === 409 ? ( - - ), - }} - /> - ) : ( - runCreationError - )} - - )} - - ) - }) - )} - {!isScanning && unavailableCount > 0 ? ( - - - {showIdleOnly - ? t('unavailable_or_busy_robot_not_listed', { - count: unavailableCount + reducerBusyCount, - }) - : t('unavailable_robot_not_listed', { - count: unavailableCount, - })} - - - {t('view_unavailable_robots')} - - - ) : null} - - ) - - const errors: string[] = [] - const runTimeParameters = - runTimeParametersOverrides != null - ? sortRuntimeParameters(runTimeParametersOverrides).map( - (runtimeParam, index) => { - if ('choices' in runtimeParam) { - const dropdownOptions = runtimeParam.choices.map(choice => { - return { name: choice.displayName, value: choice.value } - }) as DropdownOption[] - return ( - { - return choice.value === runtimeParam.value - }) ?? dropdownOptions[0] - } - onClick={choice => { - const clone = runTimeParametersOverrides.map(parameter => { - if ( - runtimeParam.variableName === parameter.variableName && - 'choices' in parameter - ) { - return { - ...parameter, - value: - dropdownOptions.find( - option => option.value === choice - )?.value ?? parameter.default, - } - } - return parameter - }) - setRunTimeParametersOverrides?.(clone as RunTimeParameter[]) - }} - title={runtimeParam.displayName} - width="100%" - dropdownType="neutral" - tooltipText={runtimeParam.description} - /> - ) - } else if ( - runtimeParam.type === 'int' || - runtimeParam.type === 'float' - ) { - const value = runtimeParam.value as number - const id = `InputField_${runtimeParam.variableName}_${index}` - const error = - (Number.isNaN(value) && !isInputFocused) || - value < runtimeParam.min || - value > runtimeParam.max - ? t(`value_out_of_range`, { - min: - runtimeParam.type === 'int' - ? runtimeParam.min - : runtimeParam.min.toFixed(1), - max: - runtimeParam.type === 'int' - ? runtimeParam.max - : runtimeParam.max.toFixed(1), - }) - : null - if (error != null) { - errors.push(error as string) - } - return ( - { - setIsInputFocused(false) - }} - onFocus={() => { - setIsInputFocused(true) - }} - onChange={e => { - const clone = runTimeParametersOverrides.map(parameter => { - if ( - runtimeParam.variableName === parameter.variableName && - (parameter.type === 'int' || parameter.type === 'float') - ) { - return { - ...parameter, - value: - runtimeParam.type === 'int' - ? Math.round(e.target.valueAsNumber) - : e.target.valueAsNumber, - } - } - return parameter - }) - setRunTimeParametersOverrides?.(clone) - }} - /> - ) - } else if (runtimeParam.type === 'bool') { - return ( - - - {runtimeParam.displayName} - - - { - const clone = runTimeParametersOverrides.map( - parameter => { - if ( - runtimeParam.variableName === - parameter.variableName && - parameter.type === 'bool' - ) { - return { - ...parameter, - value: !Boolean(parameter.value), - } - } - return parameter - } - ) - setRunTimeParametersOverrides?.(clone) - }} - height="0.813rem" - label={Boolean(runtimeParam.value) ? t('on') : t('off')} - paddingTop={SPACING.spacing2} // manual alignment of SVG with value label - /> - - {Boolean(runtimeParam.value) ? t('on') : t('off')} - - - - {runtimeParam.description} - - - ) - } else if (runtimeParam.type === 'csv_file') { - if (runtimeParam.file?.file != null) { - setHasMissingFileParam?.(false) - } - const error = - runtimeParam.file?.file?.type === 'text/csv' - ? null - : t('csv_file_type_required') - if (error != null) { - errors.push(error as string) - } - return ( - - - - {t('csv_file')} - - - {runtimeParam.file == null ? ( - { - const clone = runTimeParametersOverrides.map( - parameter => { - if ( - runtimeParam.variableName === - parameter.variableName - ) { - return { - ...parameter, - file: { file }, - } - } - return parameter - } - ) - setRunTimeParametersOverrides?.(clone) - }} - dragAndDropText={ - - , - }} - /> - - } - /> - ) : ( - - )} - - ) - } - } - ) - : null - - const hasEmptyRtpFile = - runTimeParametersOverrides?.some( - runtimeParam => - runtimeParam.type === 'csv_file' && runtimeParam.file == null - ) ?? false - setHasParamError?.(errors.length > 0 || hasEmptyRtpFile) - - const isRestoreDefaultsLinkEnabled = - runTimeParametersOverrides?.some(parameter => { - return parameter.type === 'csv_file' - ? parameter.file != null - : parameter.value !== parameter.default - }) ?? false - - const pageTwoBody = - runTimeParametersOverrides != null ? ( - - - { - if (isRestoreDefaultsLinkEnabled) { - resetRunTimeParameters?.() - } else { - setShowRestoreValuesTooltip(true) - setTimeout(() => { - setShowRestoreValuesTooltip(false) - }, TOOLTIP_DELAY_MS) - } - }} - paddingBottom={SPACING.spacing10} - {...targetProps} - > - {t('restore_defaults')} - - - {t('no_custom_values')} - - - - {runTimeParameters} - - - ) : null - - return multiSlideout != null ? ( - - {multiSlideout.currentPage === 1 ? pageOneBody : pageTwoBody} - - ) : ( - - {pageOneBody} - - ) -} - -const ENABLED_LINK_CSS = css` - ${TYPOGRAPHY.linkPSemiBold} - cursor: pointer; -` - -const DISABLED_LINK_CSS = css` - ${TYPOGRAPHY.linkPSemiBold} - color: ${COLORS.grey40}; - cursor: default; - - &:hover { - color: ${COLORS.grey40}; - } -` diff --git a/app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx b/app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx deleted file mode 100644 index 0b90ba4270b..00000000000 --- a/app/src/organisms/ChooseRobotToRunProtocolSlideout/index.tsx +++ /dev/null @@ -1,360 +0,0 @@ -import * as React from 'react' -import first from 'lodash/first' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { useNavigate } from 'react-router-dom' - -import { - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - Icon, - PrimaryButton, - SecondaryButton, - SPACING, - Tooltip, - useHoverTooltip, - ALIGN_CENTER, -} from '@opentrons/components' -import { useUploadCsvFileMutation } from '@opentrons/react-api-client' - -import { getRobotUpdateDisplayInfo } from '../../redux/robot-update' -import { OPENTRONS_USB } from '../../redux/discovery' -import { appShellRequestor } from '../../redux/shell/remote' -import { useTrackCreateProtocolRunEvent } from '../Devices/hooks' -import { - getRunTimeParameterFilesForRun, - getRunTimeParameterValuesForRun, -} from '../Devices/utils' -import { ApplyHistoricOffsets } from '../ApplyHistoricOffsets' -import { useOffsetCandidatesForAnalysis } from '../ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' -import { ChooseRobotSlideout } from '../ChooseRobotSlideout' -import { useCreateRunFromProtocol } from './useCreateRunFromProtocol' -import type { StyleProps } from '@opentrons/components' -import type { RunTimeParameter } from '@opentrons/shared-data' -import type { State } from '../../redux/types' -import type { Robot } from '../../redux/discovery/types' -import type { StoredProtocolData } from '../../redux/protocol-storage' - -const _getFileBaseName = (filePath: string): string => { - return filePath.split('/').reverse()[0] -} -interface ChooseRobotToRunProtocolSlideoutProps extends StyleProps { - storedProtocolData: StoredProtocolData - onCloseClick: () => void - showSlideout: boolean -} - -export function ChooseRobotToRunProtocolSlideoutComponent( - props: ChooseRobotToRunProtocolSlideoutProps -): JSX.Element | null { - const { t } = useTranslation(['protocol_details', 'shared', 'app_settings']) - const { storedProtocolData, showSlideout, onCloseClick } = props - const navigate = useNavigate() - const [shouldApplyOffsets, setShouldApplyOffsets] = React.useState( - true - ) - const { - protocolKey, - srcFileNames, - srcFiles, - mostRecentAnalysis, - } = storedProtocolData - const [currentPage, setCurrentPage] = React.useState(1) - const [selectedRobot, setSelectedRobot] = React.useState(null) - const { trackCreateProtocolRunEvent } = useTrackCreateProtocolRunEvent( - storedProtocolData, - selectedRobot?.name ?? '' - ) - const runTimeParameters = - storedProtocolData.mostRecentAnalysis?.runTimeParameters ?? [] - - const [ - runTimeParametersOverrides, - setRunTimeParametersOverrides, - ] = React.useState(runTimeParameters) - const [hasParamError, setHasParamError] = React.useState(false) - const [hasMissingFileParam, setHasMissingFileParam] = React.useState( - runTimeParameters?.some(parameter => parameter.type === 'csv_file') ?? false - ) - - const [targetProps, tooltipProps] = useHoverTooltip() - - const offsetCandidates = useOffsetCandidatesForAnalysis( - mostRecentAnalysis, - selectedRobot?.ip ?? null - ) - - const { uploadCsvFile } = useUploadCsvFileMutation( - {}, - selectedRobot != null - ? { - hostname: selectedRobot.ip, - requestor: - selectedRobot?.ip === OPENTRONS_USB ? appShellRequestor : undefined, - } - : null - ) - - const { - createRunFromProtocolSource, - runCreationError, - reset: resetCreateRun, - isCreatingRun, - runCreationErrorCode, - } = useCreateRunFromProtocol( - { - onSuccess: ({ data: runData }) => { - if (selectedRobot != null) { - trackCreateProtocolRunEvent({ - name: 'createProtocolRecordResponse', - properties: { success: true }, - }) - navigate(`/devices/${selectedRobot.name}/protocol-runs/${runData.id}`) - } - }, - onError: (error: Error) => { - trackCreateProtocolRunEvent({ - name: 'createProtocolRecordResponse', - properties: { success: false, error: error.message }, - }) - }, - }, - selectedRobot != null - ? { - hostname: selectedRobot.ip, - requestor: - selectedRobot?.ip === OPENTRONS_USB ? appShellRequestor : undefined, - } - : null, - shouldApplyOffsets - ? offsetCandidates.map(({ vector, location, definitionUri }) => ({ - vector, - location, - definitionUri, - })) - : [] - ) - const handleProceed: React.MouseEventHandler = () => { - trackCreateProtocolRunEvent({ name: 'createProtocolRecordRequest' }) - const dataFilesForProtocolMap = runTimeParametersOverrides.reduce< - Record - >( - (acc, parameter) => - parameter.type === 'csv_file' && parameter.file?.file != null - ? { ...acc, [parameter.variableName]: parameter.file.file } - : acc, - {} - ) - void Promise.all( - Object.entries(dataFilesForProtocolMap).map(([key, file]) => { - const fileResponse = uploadCsvFile(file) - const varName = Promise.resolve(key) - return Promise.all([fileResponse, varName]) - }) - ).then(responseTuples => { - const mappedResolvedCsvVariableToFileId = responseTuples.reduce< - Record - >((acc, [uploadedFileResponse, variableName]) => { - return { ...acc, [variableName]: uploadedFileResponse.data.id } - }, {}) - const runTimeParameterValues = getRunTimeParameterValuesForRun( - runTimeParametersOverrides - ) - const runTimeParameterFiles = getRunTimeParameterFilesForRun( - runTimeParametersOverrides, - mappedResolvedCsvVariableToFileId - ) - createRunFromProtocolSource({ - files: srcFileObjects, - protocolKey, - runTimeParameterValues, - runTimeParameterFiles, - }) - }) - } - - const { autoUpdateAction } = useSelector((state: State) => - getRobotUpdateDisplayInfo(state, selectedRobot?.name ?? '') - ) - - const isSelectedRobotOnDifferentSoftwareVersion = [ - 'upgrade', - 'downgrade', - ].includes(autoUpdateAction) - - const hasRunTimeParameters = runTimeParameters.length > 0 - - if ( - protocolKey == null || - srcFileNames == null || - srcFiles == null || - mostRecentAnalysis == null - ) { - // TODO: do more robust corrupt file catching and handling here - return null - } - const srcFileObjects = srcFiles.map((srcFileBuffer, index) => { - const srcFilePath = srcFileNames[index] - return new File([srcFileBuffer], _getFileBaseName(srcFilePath)) - }) - const protocolDisplayName = - mostRecentAnalysis?.metadata?.protocolName ?? - first(srcFileNames) ?? - protocolKey - - // intentionally show both robot types if analysis has any error - const robotType = - mostRecentAnalysis != null && mostRecentAnalysis.errors.length === 0 - ? mostRecentAnalysis?.robotType ?? null - : null - - const singlePageButton = ( - - {isCreatingRun ? ( - - ) : ( - t('shared:proceed_to_setup') - )} - - ) - - const offsetsComponent = ( - - ) - - const footer = ( - - {hasRunTimeParameters ? ( - currentPage === 1 ? ( - <> - {offsetsComponent} - { - setCurrentPage(2) - }} - width="100%" - disabled={ - isCreatingRun || - selectedRobot == null || - isSelectedRobotOnDifferentSoftwareVersion - } - > - {t('shared:continue_to_param')} - - - ) : ( - - { - setCurrentPage(1) - }} - width="50%" - > - {t('shared:change_robot')} - - - {isCreatingRun ? ( - - - {t('shared:confirm_values')} - - ) : ( - t('shared:confirm_values') - )} - - {hasMissingFileParam ? ( - - {t('add_required_csv_file')} - - ) : null} - - ) - ) : ( - <> - {offsetsComponent} - {singlePageButton} - - )} - - ) - - const resetRunTimeParameters = (): void => { - const clone = runTimeParametersOverrides.map(parameter => - parameter.type === 'csv_file' - ? { ...parameter, file: null } - : { ...parameter, value: parameter.default } - ) - setRunTimeParametersOverrides(clone as RunTimeParameter[]) - } - - return ( - { - onCloseClick() - resetRunTimeParameters() - setCurrentPage(1) - setSelectedRobot(null) - }} - title={ - hasRunTimeParameters && currentPage === 2 - ? t('select_parameters_for_robot', { - robot_name: selectedRobot?.name, - }) - : t('choose_robot_to_run', { - protocol_name: protocolDisplayName, - }) - } - runTimeParametersOverrides={runTimeParametersOverrides} - setRunTimeParametersOverrides={setRunTimeParametersOverrides} - footer={footer} - selectedRobot={selectedRobot} - setSelectedRobot={setSelectedRobot} - robotType={robotType} - isCreatingRun={isCreatingRun} - reset={resetCreateRun} - runCreationError={runCreationError} - runCreationErrorCode={runCreationErrorCode} - showIdleOnly - setHasParamError={setHasParamError} - resetRunTimeParameters={resetRunTimeParameters} - setHasMissingFileParam={setHasMissingFileParam} - /> - ) -} - -export function ChooseRobotToRunProtocolSlideout( - props: ChooseRobotToRunProtocolSlideoutProps -): JSX.Element | null { - return -} diff --git a/app/src/organisms/ConfigurePipette/index.tsx b/app/src/organisms/ConfigurePipette/index.tsx deleted file mode 100644 index 326f9e5792e..00000000000 --- a/app/src/organisms/ConfigurePipette/index.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { Box } from '@opentrons/components' - -import { ConfigForm } from './ConfigForm' -import { ConfigErrorBanner } from './ConfigErrorBanner' -import type { - PipetteSettingsFieldsMap, - UpdatePipetteSettingsData, -} from '@opentrons/api-client' - -interface Props { - closeModal: () => void - updateSettings: (params: UpdatePipetteSettingsData) => void - updateError: Error | null - isUpdateLoading: boolean - robotName: string - formId: string - settings: PipetteSettingsFieldsMap -} - -export function ConfigurePipette(props: Props): JSX.Element { - const { - updateSettings, - updateError, - isUpdateLoading, - formId, - settings, - } = props - const { t } = useTranslation('device_details') - - const groupLabels = [ - t('plunger_positions'), - t('tip_pickup_drop'), - t('power_force'), - ] - - return ( - - {updateError != null && ( - - )} - - - ) -} diff --git a/app/src/organisms/AdvancedSettings/AdditionalCustomLabwareSourceFolder.tsx b/app/src/organisms/Desktop/AdvancedSettings/AdditionalCustomLabwareSourceFolder.tsx similarity index 92% rename from app/src/organisms/AdvancedSettings/AdditionalCustomLabwareSourceFolder.tsx rename to app/src/organisms/Desktop/AdvancedSettings/AdditionalCustomLabwareSourceFolder.tsx index b43f70e9826..57708f2854d 100644 --- a/app/src/organisms/AdvancedSettings/AdditionalCustomLabwareSourceFolder.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/AdditionalCustomLabwareSourceFolder.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' @@ -16,18 +15,18 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { TertiaryButton } from '../../atoms/buttons' +import { TertiaryButton } from '/app/atoms/buttons' import { changeCustomLabwareDirectory, getCustomLabwareDirectory, openCustomLabwareDirectory, -} from '../../redux/custom-labware' +} from '/app/redux/custom-labware' import { useTrackEvent, ANALYTICS_CHANGE_CUSTOM_LABWARE_SOURCE_FOLDER, -} from '../../redux/analytics' +} from '/app/redux/analytics' -import type { Dispatch } from '../../redux/types' +import type { Dispatch } from '/app/redux/types' export function AdditionalCustomLabwareSourceFolder(): JSX.Element { const { t } = useTranslation('app_settings') diff --git a/app/src/organisms/AdvancedSettings/ClearUnavailableRobots.tsx b/app/src/organisms/Desktop/AdvancedSettings/ClearUnavailableRobots.tsx similarity index 93% rename from app/src/organisms/AdvancedSettings/ClearUnavailableRobots.tsx rename to app/src/organisms/Desktop/AdvancedSettings/ClearUnavailableRobots.tsx index c0c066f7715..f71392df640 100644 --- a/app/src/organisms/AdvancedSettings/ClearUnavailableRobots.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/ClearUnavailableRobots.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' @@ -23,16 +22,16 @@ import { Modal, } from '@opentrons/components' -import { TertiaryButton } from '../../atoms/buttons' -import { useToaster } from '../../organisms/ToasterOven' -import { getTopPortalEl } from '../../App/portal' +import { TertiaryButton } from '/app/atoms/buttons' +import { useToaster } from '/app/organisms/ToasterOven' +import { getTopPortalEl } from '/app/App/portal' import { clearDiscoveryCache, getReachableRobots, getUnreachableRobots, -} from '../../redux/discovery' +} from '/app/redux/discovery' -import type { Dispatch, State } from '../../redux/types' +import type { Dispatch, State } from '/app/redux/types' export function ClearUnavailableRobots(): JSX.Element { const { t } = useTranslation('app_settings') diff --git a/app/src/organisms/AdvancedSettings/EnableDevTools.tsx b/app/src/organisms/Desktop/AdvancedSettings/EnableDevTools.tsx similarity index 84% rename from app/src/organisms/AdvancedSettings/EnableDevTools.tsx rename to app/src/organisms/Desktop/AdvancedSettings/EnableDevTools.tsx index 920f7b652c1..55d832027e9 100644 --- a/app/src/organisms/AdvancedSettings/EnableDevTools.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/EnableDevTools.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { useSelector, useDispatch } from 'react-redux' @@ -12,10 +11,10 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ToggleButton } from '../../atoms/buttons' -import { getDevtoolsEnabled, toggleDevtools } from '../../redux/config' +import { ToggleButton } from '/app/atoms/buttons' +import { getDevtoolsEnabled, toggleDevtools } from '/app/redux/config' -import type { Dispatch } from '../../redux/types' +import type { Dispatch } from '/app/redux/types' export function EnableDevTools(): JSX.Element { const { t } = useTranslation('app_settings') diff --git a/app/src/organisms/AdvancedSettings/OT2AdvancedSettings.tsx b/app/src/organisms/Desktop/AdvancedSettings/OT2AdvancedSettings.tsx similarity index 93% rename from app/src/organisms/AdvancedSettings/OT2AdvancedSettings.tsx rename to app/src/organisms/Desktop/AdvancedSettings/OT2AdvancedSettings.tsx index 80b05b36e9b..7ab3ebb0bd0 100644 --- a/app/src/organisms/AdvancedSettings/OT2AdvancedSettings.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/OT2AdvancedSettings.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useSelector, useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' @@ -14,10 +14,10 @@ import { import { resetUseTrashSurfaceForTipCal, setUseTrashSurfaceForTipCal, -} from '../../redux/calibration' -import { getUseTrashSurfaceForTipCal } from '../../redux/config' +} from '/app/redux/calibration' +import { getUseTrashSurfaceForTipCal } from '/app/redux/config' -import type { Dispatch, State } from '../../redux/types' +import type { Dispatch, State } from '/app/redux/types' const ALWAYS_BLOCK: 'always-block' = 'always-block' const ALWAYS_TRASH: 'always-trash' = 'always-trash' diff --git a/app/src/organisms/AdvancedSettings/OverridePathToPython.tsx b/app/src/organisms/Desktop/AdvancedSettings/OverridePathToPython.tsx similarity index 91% rename from app/src/organisms/AdvancedSettings/OverridePathToPython.tsx rename to app/src/organisms/Desktop/AdvancedSettings/OverridePathToPython.tsx index b3b3a1c2eaa..d0e5b7d6d93 100644 --- a/app/src/organisms/AdvancedSettings/OverridePathToPython.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/OverridePathToPython.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { useSelector, useDispatch } from 'react-redux' @@ -16,18 +16,18 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { TertiaryButton } from '../../atoms/buttons' -import { getPathToPythonOverride, resetConfigValue } from '../../redux/config' +import { TertiaryButton } from '/app/atoms/buttons' +import { getPathToPythonOverride, resetConfigValue } from '/app/redux/config' import { openPythonInterpreterDirectory, changePythonPathOverrideConfig, -} from '../../redux/protocol-analysis' +} from '/app/redux/protocol-analysis' import { useTrackEvent, ANALYTICS_CHANGE_PATH_TO_PYTHON_DIRECTORY, -} from '../../redux/analytics' +} from '/app/redux/analytics' -import type { Dispatch } from '../../redux/types' +import type { Dispatch } from '/app/redux/types' export function OverridePathToPython(): JSX.Element { const { t } = useTranslation(['app_settings', 'branded']) diff --git a/app/src/organisms/AdvancedSettings/PreventRobotCaching.tsx b/app/src/organisms/Desktop/AdvancedSettings/PreventRobotCaching.tsx similarity index 88% rename from app/src/organisms/AdvancedSettings/PreventRobotCaching.tsx rename to app/src/organisms/Desktop/AdvancedSettings/PreventRobotCaching.tsx index 50361568904..0b65e78854e 100644 --- a/app/src/organisms/AdvancedSettings/PreventRobotCaching.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/PreventRobotCaching.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' @@ -12,10 +11,10 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ToggleButton } from '../../atoms/buttons' -import { getConfig, toggleConfigValue } from '../../redux/config' +import { ToggleButton } from '/app/atoms/buttons' +import { getConfig, toggleConfigValue } from '/app/redux/config' -import type { Dispatch, State } from '../../redux/types' +import type { Dispatch, State } from '/app/redux/types' export function PreventRobotCaching(): JSX.Element { const { t } = useTranslation('app_settings') diff --git a/app/src/organisms/AdvancedSettings/ShowHeaterShakerAttachmentModal.tsx b/app/src/organisms/Desktop/AdvancedSettings/ShowHeaterShakerAttachmentModal.tsx similarity index 86% rename from app/src/organisms/AdvancedSettings/ShowHeaterShakerAttachmentModal.tsx rename to app/src/organisms/Desktop/AdvancedSettings/ShowHeaterShakerAttachmentModal.tsx index 3598aa2d655..578872b389b 100644 --- a/app/src/organisms/AdvancedSettings/ShowHeaterShakerAttachmentModal.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/ShowHeaterShakerAttachmentModal.tsx @@ -1,11 +1,7 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' -import { - getIsHeaterShakerAttached, - updateConfigValue, -} from '../../redux/config' +import { getIsHeaterShakerAttached, updateConfigValue } from '/app/redux/config' import { ALIGN_CENTER, @@ -17,9 +13,9 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ToggleButton } from '../../atoms/buttons' +import { ToggleButton } from '/app/atoms/buttons' -import type { Dispatch } from '../../redux/types' +import type { Dispatch } from '/app/redux/types' export function ShowHeaterShakerAttachmentModal(): JSX.Element { const { t } = useTranslation('app_settings') diff --git a/app/src/organisms/AdvancedSettings/ShowLabwareOffsetSnippets.tsx b/app/src/organisms/Desktop/AdvancedSettings/ShowLabwareOffsetSnippets.tsx similarity index 90% rename from app/src/organisms/AdvancedSettings/ShowLabwareOffsetSnippets.tsx rename to app/src/organisms/Desktop/AdvancedSettings/ShowLabwareOffsetSnippets.tsx index b9f8e76df34..61dc9a62788 100644 --- a/app/src/organisms/AdvancedSettings/ShowLabwareOffsetSnippets.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/ShowLabwareOffsetSnippets.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { useSelector, useDispatch } from 'react-redux' @@ -12,13 +11,13 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ToggleButton } from '../../atoms/buttons' +import { ToggleButton } from '/app/atoms/buttons' import { getIsLabwareOffsetCodeSnippetsOn, updateConfigValue, -} from '../../redux/config' +} from '/app/redux/config' -import type { Dispatch } from '../../redux/types' +import type { Dispatch } from '/app/redux/types' export function ShowLabwareOffsetSnippets(): JSX.Element { const { t } = useTranslation(['app_settings', 'shared', 'branded']) diff --git a/app/src/organisms/AdvancedSettings/U2EInformation.tsx b/app/src/organisms/Desktop/AdvancedSettings/U2EInformation.tsx similarity index 96% rename from app/src/organisms/AdvancedSettings/U2EInformation.tsx rename to app/src/organisms/Desktop/AdvancedSettings/U2EInformation.tsx index 32cd56f08b8..fb55e883dba 100644 --- a/app/src/organisms/AdvancedSettings/U2EInformation.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/U2EInformation.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, Box, + Banner, COLORS, DIRECTION_COLUMN, Flex, @@ -15,14 +15,13 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { Banner } from '../../atoms/Banner' import { getU2EAdapterDevice, getU2EWindowsDriverStatus, OUTDATED, -} from '../../redux/system-info' +} from '/app/redux/system-info' -import type { State } from '../../redux/types' +import type { State } from '/app/redux/types' const REALTEK_URL = 'https://www.realtek.com/en/' diff --git a/app/src/organisms/AdvancedSettings/UpdatedChannel.tsx b/app/src/organisms/Desktop/AdvancedSettings/UpdatedChannel.tsx similarity index 90% rename from app/src/organisms/AdvancedSettings/UpdatedChannel.tsx rename to app/src/organisms/Desktop/AdvancedSettings/UpdatedChannel.tsx index 532a663428d..aca0348cb7b 100644 --- a/app/src/organisms/AdvancedSettings/UpdatedChannel.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/UpdatedChannel.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' @@ -12,15 +12,15 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { SelectField } from '../../atoms/SelectField' +import { SelectField } from '/app/atoms/SelectField' import { getUpdateChannel, getUpdateChannelOptions, updateConfigValue, -} from '../../redux/config' +} from '/app/redux/config' -import type { SelectOption } from '../../atoms/SelectField/Select' -import type { Dispatch } from '../../redux/types' +import type { SelectOption } from '/app/atoms/SelectField/Select' +import type { Dispatch } from '/app/redux/types' export function UpdatedChannel(): JSX.Element { const { t } = useTranslation('app_settings') diff --git a/app/src/organisms/AdvancedSettings/__tests__/AdditionalCustomLabwareSourceFolder.test.tsx b/app/src/organisms/Desktop/AdvancedSettings/__tests__/AdditionalCustomLabwareSourceFolder.test.tsx similarity index 82% rename from app/src/organisms/AdvancedSettings/__tests__/AdditionalCustomLabwareSourceFolder.test.tsx rename to app/src/organisms/Desktop/AdvancedSettings/__tests__/AdditionalCustomLabwareSourceFolder.test.tsx index 82583c017cf..48a365afa0b 100644 --- a/app/src/organisms/AdvancedSettings/__tests__/AdditionalCustomLabwareSourceFolder.test.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/__tests__/AdditionalCustomLabwareSourceFolder.test.tsx @@ -1,18 +1,17 @@ -import * as React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { i18n } from '../../../i18n' -import { getCustomLabwareDirectory } from '../../../redux/custom-labware' +import { i18n } from '/app/i18n' +import { getCustomLabwareDirectory } from '/app/redux/custom-labware' import { useTrackEvent, ANALYTICS_CHANGE_CUSTOM_LABWARE_SOURCE_FOLDER, -} from '../../../redux/analytics' +} from '/app/redux/analytics' import { AdditionalCustomLabwareSourceFolder } from '../AdditionalCustomLabwareSourceFolder' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -vi.mock('../../../redux/custom-labware') -vi.mock('../../../redux/analytics') +vi.mock('/app/redux/custom-labware') +vi.mock('/app/redux/analytics') const render = () => { return renderWithProviders(, { diff --git a/app/src/organisms/AdvancedSettings/__tests__/ClearUnavailableRobots.test.tsx b/app/src/organisms/Desktop/AdvancedSettings/__tests__/ClearUnavailableRobots.test.tsx similarity index 89% rename from app/src/organisms/AdvancedSettings/__tests__/ClearUnavailableRobots.test.tsx rename to app/src/organisms/Desktop/AdvancedSettings/__tests__/ClearUnavailableRobots.test.tsx index c90eab6f329..008a74ad734 100644 --- a/app/src/organisms/AdvancedSettings/__tests__/ClearUnavailableRobots.test.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/__tests__/ClearUnavailableRobots.test.tsx @@ -1,17 +1,13 @@ -import * as React from 'react' import { screen, fireEvent } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import { useConditionalConfirm } from '@opentrons/components' -import { i18n } from '../../../i18n' -import { - getReachableRobots, - getUnreachableRobots, -} from '../../../redux/discovery' +import { i18n } from '/app/i18n' +import { getReachableRobots, getUnreachableRobots } from '/app/redux/discovery' import { mockReachableRobot, mockUnreachableRobot, -} from '../../../redux/discovery/__fixtures__' -import { renderWithProviders } from '../../../__testing-utils__' +} from '/app/redux/discovery/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' import { ClearUnavailableRobots } from '../ClearUnavailableRobots' import type * as OpentronsComponents from '@opentrons/components' @@ -30,7 +26,7 @@ vi.mock('@opentrons/components', async importOriginal => { } }) -vi.mock('../../../redux/discovery') +vi.mock('/app/redux/discovery') const render = () => { return renderWithProviders(, { diff --git a/app/src/organisms/AdvancedSettings/__tests__/EnableDevTools.test.tsx b/app/src/organisms/Desktop/AdvancedSettings/__tests__/EnableDevTools.test.tsx similarity index 80% rename from app/src/organisms/AdvancedSettings/__tests__/EnableDevTools.test.tsx rename to app/src/organisms/Desktop/AdvancedSettings/__tests__/EnableDevTools.test.tsx index 81707fadcc7..25014b0cedf 100644 --- a/app/src/organisms/AdvancedSettings/__tests__/EnableDevTools.test.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/__tests__/EnableDevTools.test.tsx @@ -1,13 +1,12 @@ -import * as React from 'react' import { screen, fireEvent } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' -import { getDevtoolsEnabled, toggleDevtools } from '../../../redux/config' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { getDevtoolsEnabled, toggleDevtools } from '/app/redux/config' import { EnableDevTools } from '../EnableDevTools' -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') const render = () => { return renderWithProviders(, { diff --git a/app/src/organisms/AdvancedSettings/__tests__/OT2AdvancedSettings.test.tsx b/app/src/organisms/Desktop/AdvancedSettings/__tests__/OT2AdvancedSettings.test.tsx similarity index 87% rename from app/src/organisms/AdvancedSettings/__tests__/OT2AdvancedSettings.test.tsx rename to app/src/organisms/Desktop/AdvancedSettings/__tests__/OT2AdvancedSettings.test.tsx index 70d8d67699b..3cff50268bd 100644 --- a/app/src/organisms/AdvancedSettings/__tests__/OT2AdvancedSettings.test.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/__tests__/OT2AdvancedSettings.test.tsx @@ -1,19 +1,18 @@ -import * as React from 'react' import { screen, fireEvent } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { resetUseTrashSurfaceForTipCal, setUseTrashSurfaceForTipCal, -} from '../../../redux/calibration' -import { getUseTrashSurfaceForTipCal } from '../../../redux/config' -import { renderWithProviders } from '../../../__testing-utils__' +} from '/app/redux/calibration' +import { getUseTrashSurfaceForTipCal } from '/app/redux/config' +import { renderWithProviders } from '/app/__testing-utils__' import { OT2AdvancedSettings } from '../OT2AdvancedSettings' -vi.mock('../../../redux/calibration') -vi.mock('../../../redux/config') +vi.mock('/app/redux/calibration') +vi.mock('/app/redux/config') const render = () => { return renderWithProviders(, { diff --git a/app/src/organisms/AdvancedSettings/__tests__/OverridePathToPython.test.tsx b/app/src/organisms/Desktop/AdvancedSettings/__tests__/OverridePathToPython.test.tsx similarity index 82% rename from app/src/organisms/AdvancedSettings/__tests__/OverridePathToPython.test.tsx rename to app/src/organisms/Desktop/AdvancedSettings/__tests__/OverridePathToPython.test.tsx index 6a94076c68c..78574ff0eca 100644 --- a/app/src/organisms/AdvancedSettings/__tests__/OverridePathToPython.test.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/__tests__/OverridePathToPython.test.tsx @@ -1,21 +1,20 @@ -import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' -import { getPathToPythonOverride } from '../../../redux/config' +import { i18n } from '/app/i18n' +import { getPathToPythonOverride } from '/app/redux/config' import { useTrackEvent, ANALYTICS_CHANGE_PATH_TO_PYTHON_DIRECTORY, -} from '../../../redux/analytics' -import { renderWithProviders } from '../../../__testing-utils__' -import { openPythonInterpreterDirectory } from '../../../redux/protocol-analysis' +} from '/app/redux/analytics' +import { renderWithProviders } from '/app/__testing-utils__' +import { openPythonInterpreterDirectory } from '/app/redux/protocol-analysis' import { OverridePathToPython } from '../OverridePathToPython' -vi.mock('../../../redux/config') -vi.mock('../../../redux/analytics') -vi.mock('../../../redux/protocol-analysis') +vi.mock('/app/redux/config') +vi.mock('/app/redux/analytics') +vi.mock('/app/redux/protocol-analysis') const render = () => { return ( diff --git a/app/src/organisms/AdvancedSettings/__tests__/PreventRobotCaching.test.tsx b/app/src/organisms/Desktop/AdvancedSettings/__tests__/PreventRobotCaching.test.tsx similarity index 83% rename from app/src/organisms/AdvancedSettings/__tests__/PreventRobotCaching.test.tsx rename to app/src/organisms/Desktop/AdvancedSettings/__tests__/PreventRobotCaching.test.tsx index f088efd0f52..094a1ffac90 100644 --- a/app/src/organisms/AdvancedSettings/__tests__/PreventRobotCaching.test.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/__tests__/PreventRobotCaching.test.tsx @@ -1,17 +1,16 @@ -import * as React from 'react' import { when } from 'vitest-when' import { describe, it, expect, vi, beforeEach } from 'vitest' import { screen, fireEvent } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' -import { getConfig, toggleConfigValue } from '../../../redux/config' +import { getConfig, toggleConfigValue } from '/app/redux/config' import { PreventRobotCaching } from '../PreventRobotCaching' -import type { State } from '../../../redux/types' +import type { State } from '/app/redux/types' -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') const MOCK_STATE: State = { config: { diff --git a/app/src/organisms/AdvancedSettings/__tests__/ShowHeaterShakerAttachmentModal.test.tsx b/app/src/organisms/Desktop/AdvancedSettings/__tests__/ShowHeaterShakerAttachmentModal.test.tsx similarity index 86% rename from app/src/organisms/AdvancedSettings/__tests__/ShowHeaterShakerAttachmentModal.test.tsx rename to app/src/organisms/Desktop/AdvancedSettings/__tests__/ShowHeaterShakerAttachmentModal.test.tsx index 3d64290093e..a91eec5495f 100644 --- a/app/src/organisms/AdvancedSettings/__tests__/ShowHeaterShakerAttachmentModal.test.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/__tests__/ShowHeaterShakerAttachmentModal.test.tsx @@ -1,15 +1,11 @@ -import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' -import { - getIsHeaterShakerAttached, - updateConfigValue, -} from '../../../redux/config' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { getIsHeaterShakerAttached, updateConfigValue } from '/app/redux/config' +import { renderWithProviders } from '/app/__testing-utils__' import { ShowHeaterShakerAttachmentModal } from '../ShowHeaterShakerAttachmentModal' -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') const render = () => { return renderWithProviders(, { diff --git a/app/src/organisms/AdvancedSettings/__tests__/ShowLabwareOffsetSnippets.test.tsx b/app/src/organisms/Desktop/AdvancedSettings/__tests__/ShowLabwareOffsetSnippets.test.tsx similarity index 87% rename from app/src/organisms/AdvancedSettings/__tests__/ShowLabwareOffsetSnippets.test.tsx rename to app/src/organisms/Desktop/AdvancedSettings/__tests__/ShowLabwareOffsetSnippets.test.tsx index 3353a497cc1..047e310c628 100644 --- a/app/src/organisms/AdvancedSettings/__tests__/ShowLabwareOffsetSnippets.test.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/__tests__/ShowLabwareOffsetSnippets.test.tsx @@ -1,15 +1,14 @@ -import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { getIsLabwareOffsetCodeSnippetsOn, updateConfigValue, -} from '../../../redux/config' -import { renderWithProviders } from '../../../__testing-utils__' +} from '/app/redux/config' +import { renderWithProviders } from '/app/__testing-utils__' import { ShowLabwareOffsetSnippets } from '../ShowLabwareOffsetSnippets' -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') const render = () => { return ( diff --git a/app/src/organisms/AdvancedSettings/__tests__/U2EInformation.test.tsx b/app/src/organisms/Desktop/AdvancedSettings/__tests__/U2EInformation.test.tsx similarity index 90% rename from app/src/organisms/AdvancedSettings/__tests__/U2EInformation.test.tsx rename to app/src/organisms/Desktop/AdvancedSettings/__tests__/U2EInformation.test.tsx index 01b5a80305d..a885226096d 100644 --- a/app/src/organisms/AdvancedSettings/__tests__/U2EInformation.test.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/__tests__/U2EInformation.test.tsx @@ -1,20 +1,19 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { getU2EAdapterDevice, getU2EWindowsDriverStatus, NOT_APPLICABLE, OUTDATED, UP_TO_DATE, -} from '../../../redux/system-info' -import * as Fixtures from '../../../redux/system-info/__fixtures__' -import { renderWithProviders } from '../../../__testing-utils__' +} from '/app/redux/system-info' +import * as Fixtures from '/app/redux/system-info/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' import { U2EInformation } from '../U2EInformation' -vi.mock('../../../redux/system-info') +vi.mock('/app/redux/system-info') const render = () => { return renderWithProviders(, { diff --git a/app/src/organisms/AdvancedSettings/__tests__/UpdatedChannel.test.tsx b/app/src/organisms/Desktop/AdvancedSettings/__tests__/UpdatedChannel.test.tsx similarity index 87% rename from app/src/organisms/AdvancedSettings/__tests__/UpdatedChannel.test.tsx rename to app/src/organisms/Desktop/AdvancedSettings/__tests__/UpdatedChannel.test.tsx index 666b1088283..c6feb1c0c8d 100644 --- a/app/src/organisms/AdvancedSettings/__tests__/UpdatedChannel.test.tsx +++ b/app/src/organisms/Desktop/AdvancedSettings/__tests__/UpdatedChannel.test.tsx @@ -1,16 +1,15 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { getUpdateChannelOptions, getUpdateChannel, // updateConfigValue, -} from '../../../redux/config' +} from '/app/redux/config' import { UpdatedChannel } from '../UpdatedChannel' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') const render = () => { return renderWithProviders(, { i18nInstance: i18n }) diff --git a/app/src/organisms/AdvancedSettings/index.ts b/app/src/organisms/Desktop/AdvancedSettings/index.ts similarity index 100% rename from app/src/organisms/AdvancedSettings/index.ts rename to app/src/organisms/Desktop/AdvancedSettings/index.ts diff --git a/app/src/organisms/Alerts/AlertsModal.tsx b/app/src/organisms/Desktop/Alerts/AlertsModal.tsx similarity index 84% rename from app/src/organisms/Alerts/AlertsModal.tsx rename to app/src/organisms/Desktop/Alerts/AlertsModal.tsx index 956cc6d9293..f6639444a5a 100644 --- a/app/src/organisms/Alerts/AlertsModal.tsx +++ b/app/src/organisms/Desktop/Alerts/AlertsModal.tsx @@ -1,20 +1,20 @@ -import * as React from 'react' +import type { MutableRefObject } from 'react' +import { useState, useEffect } from 'react' import { useSelector, useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next' import head from 'lodash/head' -import { SUCCESS_TOAST, WARNING_TOAST } from '@opentrons/components' -import * as AppAlerts from '../../redux/alerts' -import { getHasJustUpdated, toggleConfigValue } from '../../redux/config' -import { getAvailableShellUpdate } from '../../redux/shell' -import { useToaster } from '../ToasterOven' +import { SUCCESS_TOAST, WARNING_TOAST } from '@opentrons/components' +import * as AppAlerts from '/app/redux/alerts' +import { getHasJustUpdated, toggleConfigValue } from '/app/redux/config' +import { getAvailableShellUpdate } from '/app/redux/shell' +import { useToaster } from '/app/organisms/ToasterOven' import { UpdateAppModal } from '../UpdateAppModal' import { U2EDriverOutdatedAlert } from './U2EDriverOutdatedAlert' -import { useRemoveActiveAppUpdateToast } from '.' -import type { State, Dispatch } from '../../redux/types' -import type { AlertId } from '../../redux/alerts/types' -import type { MutableRefObject } from 'react' +import { useRemoveActiveAppUpdateToast } from '.' +import type { State, Dispatch } from '/app/redux/types' +import type { AlertId } from '/app/redux/alerts/types' interface AlertsModalProps { toastIdRef: MutableRefObject @@ -22,7 +22,7 @@ interface AlertsModalProps { export function AlertsModal({ toastIdRef }: AlertsModalProps): JSX.Element { const dispatch = useDispatch() - const [showUpdateModal, setShowUpdateModal] = React.useState(false) + const [showUpdateModal, setShowUpdateModal] = useState(false) const { t } = useTranslation(['app_settings', 'branded']) const { makeToast } = useToaster() const { removeActiveAppUpdateToast } = useRemoveActiveAppUpdateToast() @@ -51,7 +51,7 @@ export function AlertsModal({ toastIdRef }: AlertsModalProps): JSX.Element { isAppUpdateAvailable && !isAppUpdateIgnored // Only run this hook on app startup - React.useEffect(() => { + useEffect(() => { if (hasJustUpdated) { makeToast( t('branded:opentrons_app_successfully_updated') as string, @@ -65,7 +65,7 @@ export function AlertsModal({ toastIdRef }: AlertsModalProps): JSX.Element { } }, []) - React.useEffect(() => { + useEffect(() => { if (createAppUpdateAvailableToast) { toastIdRef.current = makeToast( t('branded:opentrons_app_update_available_variation') as string, diff --git a/app/src/organisms/Desktop/Alerts/AlertsProvider.tsx b/app/src/organisms/Desktop/Alerts/AlertsProvider.tsx new file mode 100644 index 00000000000..79b6088b445 --- /dev/null +++ b/app/src/organisms/Desktop/Alerts/AlertsProvider.tsx @@ -0,0 +1,36 @@ +import { createContext, useRef } from 'react' +import { AlertsModal } from '.' +import { useToaster } from '/app/organisms/ToasterOven' + +import type { ReactNode } from 'react' + +export interface AlertsContextProps { + removeActiveAppUpdateToast: () => void +} + +export const AlertsContext = createContext({ + removeActiveAppUpdateToast: () => null, +}) + +interface AlertsProps { + children: ReactNode +} + +export function Alerts({ children }: AlertsProps): JSX.Element { + const toastRef = useRef(null) + const { eatToast } = useToaster() + + const removeActiveAppUpdateToast = (): void => { + if (toastRef.current) { + eatToast(toastRef.current) + toastRef.current = null + } + } + + return ( + + + {children} + + ) +} diff --git a/app/src/organisms/Alerts/U2EDriverOutdatedAlert.tsx b/app/src/organisms/Desktop/Alerts/U2EDriverOutdatedAlert.tsx similarity index 96% rename from app/src/organisms/Alerts/U2EDriverOutdatedAlert.tsx rename to app/src/organisms/Desktop/Alerts/U2EDriverOutdatedAlert.tsx index f8880cfdbf3..fbb79a6b935 100644 --- a/app/src/organisms/Alerts/U2EDriverOutdatedAlert.tsx +++ b/app/src/organisms/Desktop/Alerts/U2EDriverOutdatedAlert.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Link as InternalLink } from 'react-router-dom' import styled from 'styled-components' @@ -12,13 +11,13 @@ import { useTrackEvent, ANALYTICS_U2E_DRIVE_ALERT_DISMISSED, ANALYTICS_U2E_DRIVE_LINK_CLICKED, -} from '../../redux/analytics' +} from '/app/redux/analytics' import { U2E_DRIVER_UPDATE_URL, U2E_DRIVER_OUTDATED_MESSAGE, U2E_DRIVER_DESCRIPTION, U2E_DRIVER_OUTDATED_CTA, -} from '../../redux/system-info' +} from '/app/redux/system-info' import type { AlertProps } from './types' // TODO(mc, 2020-05-07): i18n diff --git a/app/src/organisms/Alerts/__tests__/Alerts.test.tsx b/app/src/organisms/Desktop/Alerts/__tests__/Alerts.test.tsx similarity index 100% rename from app/src/organisms/Alerts/__tests__/Alerts.test.tsx rename to app/src/organisms/Desktop/Alerts/__tests__/Alerts.test.tsx diff --git a/app/src/organisms/Alerts/__tests__/U2EDriverOutdatedAlert.test.tsx b/app/src/organisms/Desktop/Alerts/__tests__/U2EDriverOutdatedAlert.test.tsx similarity index 100% rename from app/src/organisms/Alerts/__tests__/U2EDriverOutdatedAlert.test.tsx rename to app/src/organisms/Desktop/Alerts/__tests__/U2EDriverOutdatedAlert.test.tsx diff --git a/app/src/organisms/Alerts/index.ts b/app/src/organisms/Desktop/Alerts/index.ts similarity index 100% rename from app/src/organisms/Alerts/index.ts rename to app/src/organisms/Desktop/Alerts/index.ts diff --git a/app/src/organisms/Alerts/types.ts b/app/src/organisms/Desktop/Alerts/types.ts similarity index 100% rename from app/src/organisms/Alerts/types.ts rename to app/src/organisms/Desktop/Alerts/types.ts diff --git a/app/src/organisms/Desktop/Alerts/useRemoveActiveAppUpdateToast.ts.ts b/app/src/organisms/Desktop/Alerts/useRemoveActiveAppUpdateToast.ts.ts new file mode 100644 index 00000000000..a45a88716e8 --- /dev/null +++ b/app/src/organisms/Desktop/Alerts/useRemoveActiveAppUpdateToast.ts.ts @@ -0,0 +1,7 @@ +import { useContext } from 'react' +import { AlertsContext } from '.' +import type { AlertsContextProps } from '.' + +export function useRemoveActiveAppUpdateToast(): AlertsContextProps { + return useContext(AlertsContext) +} diff --git a/app/src/organisms/AppSettings/ConnectRobotSlideout.tsx b/app/src/organisms/Desktop/AppSettings/ConnectRobotSlideout.tsx similarity index 88% rename from app/src/organisms/AppSettings/ConnectRobotSlideout.tsx rename to app/src/organisms/Desktop/AppSettings/ConnectRobotSlideout.tsx index f919f535488..7c3148796d9 100644 --- a/app/src/organisms/AppSettings/ConnectRobotSlideout.tsx +++ b/app/src/organisms/Desktop/AppSettings/ConnectRobotSlideout.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { useSelector, useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next' @@ -18,12 +18,12 @@ import { import { ManualIpHostnameForm } from './ManualIpHostnameForm' import { ManualIpHostnameList } from './ManualIpHostnameList' -import { Slideout } from '../../atoms/Slideout' -import { ExternalLink } from '../../atoms/Link/ExternalLink' -import { Divider } from '../../atoms/structure' -import { getScanning, startDiscovery } from '../../redux/discovery' +import { Slideout } from '/app/atoms/Slideout' +import { ExternalLink } from '/app/atoms/Link/ExternalLink' +import { Divider } from '/app/atoms/structure' +import { getScanning, startDiscovery } from '/app/redux/discovery' -import type { Dispatch, State } from '../../redux/types' +import type { Dispatch, State } from '/app/redux/types' const SUPPORT_PAGE_LINK = 'https://support.opentrons.com/s/article/Manually-adding-a-robot-s-IP-address' @@ -37,10 +37,10 @@ export function ConnectRobotSlideout({ isExpanded, onCloseClick, }: ConnectRobotSlideoutProps): JSX.Element | null { - const [mostRecentAddition, setMostRecentAddition] = React.useState< - string | null - >(null) - const [mostRecentDiscovered, setMostRecentDiscovered] = React.useState< + const [mostRecentAddition, setMostRecentAddition] = useState( + null + ) + const [mostRecentDiscovered, setMostRecentDiscovered] = useState< boolean | null >(null) const { t } = useTranslation(['app_settings', 'shared', 'branded']) @@ -62,7 +62,7 @@ export function ConnectRobotSlideout({ ) } - React.useEffect(() => { + useEffect(() => { dispatch(startDiscovery()) }, [dispatch]) diff --git a/app/src/organisms/AppSettings/FeatureFlags.tsx b/app/src/organisms/Desktop/AppSettings/FeatureFlags.tsx similarity index 82% rename from app/src/organisms/AppSettings/FeatureFlags.tsx rename to app/src/organisms/Desktop/AppSettings/FeatureFlags.tsx index 0738d9f08d1..d86b52534ab 100644 --- a/app/src/organisms/AppSettings/FeatureFlags.tsx +++ b/app/src/organisms/Desktop/AppSettings/FeatureFlags.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { Fragment } from 'react' import { useTranslation } from 'react-i18next' import { useSelector, useDispatch } from 'react-redux' import { @@ -10,12 +10,12 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { Divider } from '../../atoms/structure' -import { ToggleButton } from '../../atoms/buttons' -import * as Config from '../../redux/config' +import { Divider } from '/app/atoms/structure' +import { ToggleButton } from '/app/atoms/buttons' +import * as Config from '/app/redux/config' -import type { DevInternalFlag } from '../../redux/config/types' -import type { Dispatch } from '../../redux/types' +import type { DevInternalFlag } from '/app/redux/config/types' +import type { Dispatch } from '/app/redux/types' export function FeatureFlags(): JSX.Element { const { t } = useTranslation('app_settings') @@ -32,7 +32,7 @@ export function FeatureFlags(): JSX.Element { paddingY={SPACING.spacing24} > {Config.DEV_INTERNAL_FLAGS.map((flag, index) => ( - + )} - + ))} ) diff --git a/app/src/organisms/AppSettings/ManualIpHostnameField.tsx b/app/src/organisms/Desktop/AppSettings/ManualIpHostnameField.tsx similarity index 92% rename from app/src/organisms/AppSettings/ManualIpHostnameField.tsx rename to app/src/organisms/Desktop/AppSettings/ManualIpHostnameField.tsx index 6c2cb561e85..c7b179ab4cd 100644 --- a/app/src/organisms/AppSettings/ManualIpHostnameField.tsx +++ b/app/src/organisms/Desktop/AppSettings/ManualIpHostnameField.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -10,7 +9,7 @@ import { SPACING, } from '@opentrons/components' -import { TertiaryButton } from '../../atoms/buttons' +import { TertiaryButton } from '/app/atoms/buttons' interface IpHostnameFieldProps { field: any diff --git a/app/src/organisms/AppSettings/ManualIpHostnameForm.tsx b/app/src/organisms/Desktop/AppSettings/ManualIpHostnameForm.tsx similarity index 93% rename from app/src/organisms/AppSettings/ManualIpHostnameForm.tsx rename to app/src/organisms/Desktop/AppSettings/ManualIpHostnameForm.tsx index 7e99b2ab293..52892422dce 100644 --- a/app/src/organisms/AppSettings/ManualIpHostnameForm.tsx +++ b/app/src/organisms/Desktop/AppSettings/ManualIpHostnameForm.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next' import { useForm } from 'react-hook-form' @@ -15,12 +14,12 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { TertiaryButton } from '../../atoms/buttons' -import { addManualIp } from '../../redux/config' -import { startDiscovery } from '../../redux/discovery' +import { TertiaryButton } from '/app/atoms/buttons' +import { addManualIp } from '/app/redux/config' +import { startDiscovery } from '/app/redux/discovery' import type { FieldError, Resolver } from 'react-hook-form' -import type { Dispatch } from '../../redux/types' +import type { Dispatch } from '/app/redux/types' const FlexForm = styled.form` display: flex; diff --git a/app/src/organisms/AppSettings/ManualIpHostnameItem.tsx b/app/src/organisms/Desktop/AppSettings/ManualIpHostnameItem.tsx similarity index 96% rename from app/src/organisms/AppSettings/ManualIpHostnameItem.tsx rename to app/src/organisms/Desktop/AppSettings/ManualIpHostnameItem.tsx index 1249bd58f51..5a1dde3b663 100644 --- a/app/src/organisms/AppSettings/ManualIpHostnameItem.tsx +++ b/app/src/organisms/Desktop/AppSettings/ManualIpHostnameItem.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' @@ -14,7 +14,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { Divider } from '../../atoms/structure' +import { Divider } from '/app/atoms/structure' const IpItem = styled.div` flex: 1 1 auto; @@ -68,7 +68,7 @@ export function ManualIpHostnameItem({ } } - React.useEffect(() => { + useEffect(() => { if (justAdded) { setMostRecentDiscovered(discovered) // Note this is to avoid the case that not found but not display the message diff --git a/app/src/organisms/AppSettings/ManualIpHostnameList.tsx b/app/src/organisms/Desktop/AppSettings/ManualIpHostnameList.tsx similarity index 85% rename from app/src/organisms/AppSettings/ManualIpHostnameList.tsx rename to app/src/organisms/Desktop/AppSettings/ManualIpHostnameList.tsx index 33f5cde1eab..1e96b60010d 100644 --- a/app/src/organisms/AppSettings/ManualIpHostnameList.tsx +++ b/app/src/organisms/Desktop/AppSettings/ManualIpHostnameList.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import { Fragment } from 'react' import { useSelector, useDispatch } from 'react-redux' -import { getConfig, removeManualIp } from '../../redux/config' -import { getViewableRobots } from '../../redux/discovery' +import { getConfig, removeManualIp } from '/app/redux/config' +import { getViewableRobots } from '/app/redux/discovery' import { ManualIpHostnameItem } from './ManualIpHostnameItem' -import type { State, Dispatch } from '../../redux/types' +import type { State, Dispatch } from '/app/redux/types' interface IpHostnameListProps { mostRecentAddition: string | null @@ -38,7 +38,7 @@ export function ManualIpHostnameList({ bDiscovered && !aDiscovered ? -1 : 1 ) .map(([candidate, discovered], index) => ( - + dispatch(removeManualIp(candidate))} @@ -48,7 +48,7 @@ export function ManualIpHostnameList({ setMostRecentDiscovered={setMostRecentDiscovered} isLast={index === candidates.length - 1} /> - + )) : null} diff --git a/app/src/organisms/AppSettings/PreviousVersionModal.tsx b/app/src/organisms/Desktop/AppSettings/PreviousVersionModal.tsx similarity index 94% rename from app/src/organisms/AppSettings/PreviousVersionModal.tsx rename to app/src/organisms/Desktop/AppSettings/PreviousVersionModal.tsx index de0af38bcd3..3b3a58f2cc1 100644 --- a/app/src/organisms/AppSettings/PreviousVersionModal.tsx +++ b/app/src/organisms/Desktop/AppSettings/PreviousVersionModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { Box, @@ -10,7 +9,7 @@ import { Modal, } from '@opentrons/components' -import { ExternalLink } from '../../atoms/Link/ExternalLink' +import { ExternalLink } from '/app/atoms/Link/ExternalLink' export const UNINSTALL_APP_URL = 'https://support.opentrons.com/s/article/Uninstall-the-Opentrons-App' diff --git a/app/src/organisms/AppSettings/__tests__/ConnectRobotSlideout.test.tsx b/app/src/organisms/Desktop/AppSettings/__tests__/ConnectRobotSlideout.test.tsx similarity index 94% rename from app/src/organisms/AppSettings/__tests__/ConnectRobotSlideout.test.tsx rename to app/src/organisms/Desktop/AppSettings/__tests__/ConnectRobotSlideout.test.tsx index 006eb52fd23..c92e1f495a0 100644 --- a/app/src/organisms/AppSettings/__tests__/ConnectRobotSlideout.test.tsx +++ b/app/src/organisms/Desktop/AppSettings/__tests__/ConnectRobotSlideout.test.tsx @@ -1,15 +1,15 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' -import { getScanning, getViewableRobots } from '../../../redux/discovery' -import { getConfig } from '../../../redux/config' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { getScanning, getViewableRobots } from '/app/redux/discovery' +import { getConfig } from '/app/redux/config' +import { renderWithProviders } from '/app/__testing-utils__' import { ConnectRobotSlideout } from '../ConnectRobotSlideout' -vi.mock('../../../redux/discovery') -vi.mock('../../../redux/config') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/config') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/AppSettings/__tests__/PreviousVersionModal.test.tsx b/app/src/organisms/Desktop/AppSettings/__tests__/PreviousVersionModal.test.tsx similarity index 92% rename from app/src/organisms/AppSettings/__tests__/PreviousVersionModal.test.tsx rename to app/src/organisms/Desktop/AppSettings/__tests__/PreviousVersionModal.test.tsx index 3202528f2ef..b4449623c05 100644 --- a/app/src/organisms/AppSettings/__tests__/PreviousVersionModal.test.tsx +++ b/app/src/organisms/Desktop/AppSettings/__tests__/PreviousVersionModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { PreviousVersionModal, UNINSTALL_APP_URL, diff --git a/app/src/organisms/Breadcrumbs/__tests__/Breadcrumbs.test.tsx b/app/src/organisms/Desktop/Breadcrumbs/__tests__/Breadcrumbs.test.tsx similarity index 80% rename from app/src/organisms/Breadcrumbs/__tests__/Breadcrumbs.test.tsx rename to app/src/organisms/Desktop/Breadcrumbs/__tests__/Breadcrumbs.test.tsx index bd06ebcd9df..5efb526a4a5 100644 --- a/app/src/organisms/Breadcrumbs/__tests__/Breadcrumbs.test.tsx +++ b/app/src/organisms/Desktop/Breadcrumbs/__tests__/Breadcrumbs.test.tsx @@ -1,29 +1,27 @@ -import * as React from 'react' import { MemoryRouter, Route, Routes } from 'react-router-dom' import { when } from 'vitest-when' import { describe, it, expect, beforeEach, vi } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { i18n } from '../../../i18n' -import { - useRobot, - useRunCreatedAtTimestamp, -} from '../../../organisms/Devices/hooks' -import { getProtocolDisplayName } from '../../../organisms/ProtocolsLanding/utils' -import { getIsOnDevice } from '../../../redux/config' -import { renderWithProviders } from '../../../__testing-utils__' -import { mockConnectableRobot } from '../../../redux/discovery/__fixtures__' -import { getStoredProtocol } from '../../../redux/protocol-storage' -import { storedProtocolData as storedProtocolDataFixture } from '../../../redux/protocol-storage/__fixtures__' +import { i18n } from '/app/i18n' +import { useRunCreatedAtTimestamp } from '/app/resources/runs' +import { useRobot } from '/app/redux-resources/robots' +import { getProtocolDisplayName } from '/app/transformations/protocols' +import { getIsOnDevice } from '/app/redux/config' +import { renderWithProviders } from '/app/__testing-utils__' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' +import { getStoredProtocol } from '/app/redux/protocol-storage' +import { storedProtocolData as storedProtocolDataFixture } from '/app/redux/protocol-storage/__fixtures__' import { Breadcrumbs } from '..' -import type { State } from '../../../redux/types' +import type { State } from '/app/redux/types' -vi.mock('../../../organisms/Devices/hooks') -vi.mock('../../../organisms/ProtocolsLanding/utils') -vi.mock('../../../redux/config') -vi.mock('../../../redux/protocol-storage') +vi.mock('/app/resources/runs') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/transformations/protocols') +vi.mock('/app/redux/config') +vi.mock('/app/redux/protocol-storage') const ROBOT_NAME = 'otie' const RUN_ID = '95e67900-bc9f-4fbf-92c6-cc4d7226a51b' diff --git a/app/src/organisms/Desktop/Breadcrumbs/index.tsx b/app/src/organisms/Desktop/Breadcrumbs/index.tsx new file mode 100644 index 00000000000..02ea58a483f --- /dev/null +++ b/app/src/organisms/Desktop/Breadcrumbs/index.tsx @@ -0,0 +1,162 @@ +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' +import { Link, useParams, useLocation } from 'react-router-dom' +import styled from 'styled-components' + +import { + Box, + Flex, + Icon, + ALIGN_CENTER, + ALIGN_FLEX_START, + BORDERS, + COLORS, + DIRECTION_ROW, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' +import { ApiHostProvider } from '@opentrons/react-api-client' + +import { useRunCreatedAtTimestamp } from '/app/resources/runs' +import { getProtocolDisplayName } from '/app/transformations/protocols' +import { getIsOnDevice } from '/app/redux/config' +import { OPENTRONS_USB } from '/app/redux/discovery' +import { getStoredProtocol } from '/app/redux/protocol-storage' +import { appShellRequestor } from '/app/redux/shell/remote' +import { useRobot } from '/app/redux-resources/robots' + +import type { DesktopRouteParams } from '/app/App/types' +import type { State } from '/app/redux/types' + +interface CrumbNameProps { + crumbName: string + isLastCrumb: boolean +} + +function CrumbName({ crumbName, isLastCrumb }: CrumbNameProps): JSX.Element { + return ( + + + {crumbName} + + {!isLastCrumb ? ( + + ) : null} + + ) +} + +const CrumbLink = styled(Link)` + &:hover { + opacity: 0.8; + } +` + +const CrumbLinkInactive = styled(Flex)` + &:hover { + opacity: 1; + } +` + +function BreadcrumbsComponent(): JSX.Element | null { + const { t } = useTranslation('top_navigation') + const isOnDevice = useSelector(getIsOnDevice) + const { protocolKey, robotName, runId } = useParams< + keyof DesktopRouteParams + >() as DesktopRouteParams + const runCreatedAtTimestamp = useRunCreatedAtTimestamp(runId) + + const storedProtocol = useSelector((state: State) => + getStoredProtocol(state, protocolKey) + ) + const protocolDisplayName = + storedProtocol != null + ? getProtocolDisplayName( + storedProtocol.protocolKey, + storedProtocol.srcFileNames, + storedProtocol.mostRecentAnalysis + ) + : protocolKey + + // determines whether a crumb is displayed for a path, and the displayed name + const crumbNameByPath: { [index: string]: string | null } = { + '/devices': !(isOnDevice ?? false) ? t('devices') : null, + [`/devices/${robotName}`]: robotName, + [`/devices/${robotName}/robot-settings`]: t('robot_settings'), + [`/devices/${robotName}/protocol-runs/${runId}`]: runCreatedAtTimestamp, + + '/protocols': t('protocols'), + [`/protocols/${protocolKey}`]: protocolDisplayName, + } + + // create an array of crumbs based on the pathname and defined names by path + const { pathname } = useLocation() + const pathArray = pathname.split('/') + + const pathCrumbs = pathArray.flatMap((_, i) => { + const linkPath = pathArray.slice(0, i + 1).join('/') + const crumbName = crumbNameByPath[linkPath] + + // filter out null or undefined crumb names + return crumbName != null + ? [ + { + linkPath, + crumbName, + }, + ] + : [] + }) + + return pathCrumbs.length > 1 ? ( + + {pathCrumbs.map((crumb, i) => { + const isLastCrumb = i === pathCrumbs.length - 1 + + return ( + + + + + + ) + })} + + ) : null +} + +export function Breadcrumbs(): JSX.Element | null { + const { robotName } = useParams< + keyof DesktopRouteParams + >() as DesktopRouteParams + const robot = useRobot(robotName) + + return ( + + + + ) +} diff --git a/app/src/organisms/CalibrateDeck/__tests__/CalibrateDeck.test.tsx b/app/src/organisms/Desktop/CalibrateDeck/__tests__/CalibrateDeck.test.tsx similarity index 82% rename from app/src/organisms/CalibrateDeck/__tests__/CalibrateDeck.test.tsx rename to app/src/organisms/Desktop/CalibrateDeck/__tests__/CalibrateDeck.test.tsx index 797cd700c7f..3f90064c03c 100644 --- a/app/src/organisms/CalibrateDeck/__tests__/CalibrateDeck.test.tsx +++ b/app/src/organisms/Desktop/CalibrateDeck/__tests__/CalibrateDeck.test.tsx @@ -1,21 +1,26 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, describe, beforeEach, expect, it } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { getDeckDefinitions } from '@opentrons/shared-data' -import { i18n } from '../../../i18n' -import * as Sessions from '../../../redux/sessions' -import { mockDeckCalibrationSessionAttributes } from '../../../redux/sessions/__fixtures__' +import { i18n } from '/app/i18n' +import * as Sessions from '/app/redux/sessions' +import { mockDeckCalibrationSessionAttributes } from '/app/redux/sessions/__fixtures__' import { CalibrateDeck } from '../index' +import { + useCalibrationError, + CalibrationError, +} from '/app/organisms/Desktop/CalibrationError' -import type { DeckCalibrationStep } from '../../../redux/sessions/types' -import type { DispatchRequestsType } from '../../../redux/robot-api' +import type { DeckCalibrationStep } from '/app/redux/sessions/types' +import type { DispatchRequestsType } from '/app/redux/robot-api' -vi.mock('../../../redux/sessions/selectors') -vi.mock('../../../redux/robot-api/selectors') -vi.mock('../../../redux/config') +vi.mock('/app/redux/sessions/selectors') +vi.mock('/app/redux/robot-api/selectors') +vi.mock('/app/redux/config') +vi.mock('/app/organisms/Desktop/CalibrationError') vi.mock('@opentrons/shared-data', async importOriginal => { const actual = await importOriginal() return { @@ -50,6 +55,7 @@ describe('CalibrateDeck', () => { dispatchRequests={dispatchRequests} showSpinner={showSpinner} isJogging={isJogging} + requestIds={[]} />, { i18nInstance: i18n } ) @@ -85,6 +91,10 @@ describe('CalibrateDeck', () => { beforeEach(() => { dispatchRequests = vi.fn() vi.mocked(getDeckDefinitions).mockReturnValue({}) + vi.mocked(useCalibrationError).mockReturnValue(null) + vi.mocked(CalibrationError).mockReturnValue( +
    MOCK_CALIBRATION_ERROR
    + ) }) SPECS.forEach(spec => { @@ -182,4 +192,15 @@ describe('CalibrateDeck', () => { }) ) }) + + it('renders an error modal if there is an error', () => { + vi.mocked(useCalibrationError).mockReturnValue({ + title: 'test', + subText: 'test', + }) + + render() + + screen.getByText('MOCK_CALIBRATION_ERROR') + }) }) diff --git a/app/src/organisms/Desktop/CalibrateDeck/index.tsx b/app/src/organisms/Desktop/CalibrateDeck/index.tsx new file mode 100644 index 00000000000..b1201dc4553 --- /dev/null +++ b/app/src/organisms/Desktop/CalibrateDeck/index.tsx @@ -0,0 +1,203 @@ +// Deck Calibration Orchestration Component +import { useMemo } from 'react' +import { createPortal } from 'react-dom' +import { useTranslation } from 'react-i18next' +import { useQueryClient } from 'react-query' + +import { useHost } from '@opentrons/react-api-client' +import { getPipetteModelSpecs } from '@opentrons/shared-data' +import { useConditionalConfirm, ModalShell } from '@opentrons/components' + +import * as Sessions from '/app/redux/sessions' +import { + Introduction, + DeckSetup, + TipPickUp, + TipConfirmation, + SaveZPoint, + SaveXYPoint, + ConfirmExit, + LoadingState, + CompleteConfirmation, +} from '/app/organisms/Desktop/CalibrationPanels' +import { WizardHeader } from '/app/molecules/WizardHeader' +import { getTopPortalEl } from '/app/App/portal' +import { + CalibrationError, + useCalibrationError, +} from '/app/organisms/Desktop/CalibrationError' + +import type { ComponentType } from 'react' +import type { Mount } from '@opentrons/components' +import type { + CalibrationLabware, + CalibrationSessionStep, + SessionCommandParams, +} from '/app/redux/sessions/types' +import type { CalibrationPanelProps } from '/app/organisms/Desktop/CalibrationPanels/types' +import type { CalibrateDeckParentProps } from './types' + +const PANEL_BY_STEP: Partial< + Record> +> = { + [Sessions.DECK_STEP_SESSION_STARTED]: Introduction, + [Sessions.DECK_STEP_LABWARE_LOADED]: DeckSetup, + [Sessions.DECK_STEP_PREPARING_PIPETTE]: TipPickUp, + [Sessions.DECK_STEP_INSPECTING_TIP]: TipConfirmation, + [Sessions.DECK_STEP_JOGGING_TO_DECK]: SaveZPoint, + [Sessions.DECK_STEP_SAVING_POINT_ONE]: SaveXYPoint, + [Sessions.DECK_STEP_SAVING_POINT_TWO]: SaveXYPoint, + [Sessions.DECK_STEP_SAVING_POINT_THREE]: SaveXYPoint, + [Sessions.DECK_STEP_CALIBRATION_COMPLETE]: DeckCalibrationComplete, +} +const STEPS_IN_ORDER: CalibrationSessionStep[] = [ + Sessions.DECK_STEP_SESSION_STARTED, + Sessions.DECK_STEP_LABWARE_LOADED, + Sessions.DECK_STEP_PREPARING_PIPETTE, + Sessions.DECK_STEP_INSPECTING_TIP, + Sessions.DECK_STEP_JOGGING_TO_DECK, + Sessions.DECK_STEP_SAVING_POINT_ONE, + Sessions.DECK_STEP_SAVING_POINT_TWO, + Sessions.DECK_STEP_SAVING_POINT_THREE, + Sessions.DECK_STEP_CALIBRATION_COMPLETE, +] + +export function CalibrateDeck({ + session, + robotName, + dispatchRequests, + requestIds, + showSpinner, + isJogging, + exitBeforeDeckConfigCompletion, + offsetInvalidationHandler, +}: CalibrateDeckParentProps): JSX.Element | null { + const { t } = useTranslation('robot_calibration') + + const { currentStep, instrument, labware, supportedCommands } = + session?.details || {} + + const queryClient = useQueryClient() + const host = useHost() + + const { + showConfirmation: showConfirmExit, + confirm: confirmExit, + cancel: cancelExit, + } = useConditionalConfirm(() => { + cleanUpAndExit() + }, true) + + const errorInfo = useCalibrationError(requestIds, session?.id) + + const isMulti = useMemo(() => { + const spec = instrument && getPipetteModelSpecs(instrument.model) + return spec ? spec.channels > 1 : false + }, [instrument]) + + function sendCommands(...commands: SessionCommandParams[]): void { + if (session?.id && !isJogging) { + const sessionCommandActions = commands.map(c => + Sessions.createSessionCommand(robotName, session.id, { + command: c.command, + data: c.data || {}, + }) + ) + dispatchRequests(...sessionCommandActions) + } + } + + function cleanUpAndExit(): void { + queryClient.invalidateQueries([host, 'calibration']).catch((e: Error) => { + console.error(`error invalidating calibration queries: ${e.message}`) + }) + if ( + exitBeforeDeckConfigCompletion && + currentStep !== Sessions.DECK_STEP_CALIBRATION_COMPLETE + ) { + exitBeforeDeckConfigCompletion.current = true + } + if (session?.id) { + dispatchRequests( + Sessions.createSessionCommand(robotName, session.id, { + command: Sessions.sharedCalCommands.EXIT, + data: {}, + }), + Sessions.deleteSession(robotName, session.id) + ) + } + } + + const tipRack: CalibrationLabware | null = + (labware && labware.find(l => l.isTiprack)) ?? null + + if (!session || !tipRack) { + return null + } + + const Panel = + currentStep != null && currentStep in PANEL_BY_STEP + ? PANEL_BY_STEP[currentStep] + : null + return createPortal( + step === currentStep) ?? 0 + } + totalSteps={STEPS_IN_ORDER.length - 1} + onExit={confirmExit} + /> + } + > + {showSpinner || currentStep == null || Panel == null ? ( + + ) : showConfirmExit ? ( + + ) : errorInfo != null ? ( + + ) : ( + + )} + , + getTopPortalEl() + ) +} + +function DeckCalibrationComplete(props: CalibrationPanelProps): JSX.Element { + const { t } = useTranslation('robot_calibration') + const { cleanUpAndExit } = props + + return ( + + ) +} diff --git a/app/src/organisms/Desktop/CalibrateDeck/types.ts b/app/src/organisms/Desktop/CalibrateDeck/types.ts new file mode 100644 index 00000000000..e2200747903 --- /dev/null +++ b/app/src/organisms/Desktop/CalibrateDeck/types.ts @@ -0,0 +1,13 @@ +import type { DispatchRequestsType } from '/app/redux/robot-api' +import type { MutableRefObject } from 'react' +import type { DeckCalibrationSession } from '/app/redux/sessions/types' +export interface CalibrateDeckParentProps { + robotName: string + session: DeckCalibrationSession | null + dispatchRequests: DispatchRequestsType + requestIds: string[] + showSpinner: boolean + isJogging: boolean + exitBeforeDeckConfigCompletion?: MutableRefObject + offsetInvalidationHandler?: () => void +} diff --git a/app/src/organisms/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.tsx b/app/src/organisms/Desktop/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.tsx similarity index 82% rename from app/src/organisms/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.tsx rename to app/src/organisms/Desktop/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.tsx index a4ea115fa73..15117cac92f 100644 --- a/app/src/organisms/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.tsx +++ b/app/src/organisms/Desktop/CalibratePipetteOffset/__tests__/CalibratePipetteOffset.test.tsx @@ -1,15 +1,19 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, describe, expect, beforeEach } from 'vitest' import { when } from 'vitest-when' import { fireEvent, screen } from '@testing-library/react' import { getDeckDefinitions } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import * as Sessions from '../../../redux/sessions' -import { mockPipetteOffsetCalibrationSessionAttributes } from '../../../redux/sessions/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import * as Sessions from '/app/redux/sessions' +import { mockPipetteOffsetCalibrationSessionAttributes } from '/app/redux/sessions/__fixtures__' import { CalibratePipetteOffset } from '../index' -import type { PipetteOffsetCalibrationStep } from '../../../redux/sessions/types' -import type { DispatchRequestsType } from '../../../redux/robot-api' +import type { PipetteOffsetCalibrationStep } from '/app/redux/sessions/types' +import type { DispatchRequestsType } from '/app/redux/robot-api' +import { + useCalibrationError, + CalibrationError, +} from '/app/organisms/Desktop/CalibrationError' vi.mock('@opentrons/shared-data', async importOriginal => { const actual = await importOriginal() @@ -18,9 +22,10 @@ vi.mock('@opentrons/shared-data', async importOriginal => { getDeckDefinitions: vi.fn(), } }) -vi.mock('../../../redux/sessions/selectors') -vi.mock('../../../redux/robot-api/selectors') -vi.mock('../../../redux/config') +vi.mock('/app/redux/sessions/selectors') +vi.mock('/app/redux/robot-api/selectors') +vi.mock('/app/organisms/Desktop/CalibrationError') +vi.mock('/app/redux/config') interface CalibratePipetteOffsetSpec { heading: string @@ -46,6 +51,7 @@ describe('CalibratePipetteOffset', () => { dispatchRequests={dispatchRequests} showSpinner={showSpinner} isJogging={isJogging} + requestIds={[]} />, { i18nInstance: i18n } ) @@ -73,6 +79,10 @@ describe('CalibratePipetteOffset', () => { beforeEach(() => { dispatchRequests = vi.fn() when(vi.mocked(getDeckDefinitions)).calledWith().thenReturn({}) + vi.mocked(useCalibrationError).mockReturnValue(null) + vi.mocked(CalibrationError).mockReturnValue( +
    MOCK_CALIBRATION_ERROR
    + ) mockPipOffsetCalSession = { id: 'fake_session_id', @@ -175,4 +185,15 @@ describe('CalibratePipetteOffset', () => { }) ) }) + + it('renders an error modal if there is an error', () => { + vi.mocked(useCalibrationError).mockReturnValue({ + title: 'test', + subText: 'test', + }) + + render() + + screen.getByText('MOCK_CALIBRATION_ERROR') + }) }) diff --git a/app/src/organisms/CalibratePipetteOffset/__tests__/useCalibratePipetteOffset.test.tsx b/app/src/organisms/Desktop/CalibratePipetteOffset/__tests__/useCalibratePipetteOffset.test.tsx similarity index 100% rename from app/src/organisms/CalibratePipetteOffset/__tests__/useCalibratePipetteOffset.test.tsx rename to app/src/organisms/Desktop/CalibratePipetteOffset/__tests__/useCalibratePipetteOffset.test.tsx diff --git a/app/src/organisms/Desktop/CalibratePipetteOffset/index.tsx b/app/src/organisms/Desktop/CalibratePipetteOffset/index.tsx new file mode 100644 index 00000000000..2359d275384 --- /dev/null +++ b/app/src/organisms/Desktop/CalibratePipetteOffset/index.tsx @@ -0,0 +1,196 @@ +// Pipette Offset Calibration Orchestration Component +import { useMemo } from 'react' +import { createPortal } from 'react-dom' +import { useTranslation } from 'react-i18next' +import { useQueryClient } from 'react-query' + +import { useHost } from '@opentrons/react-api-client' +import { getPipetteModelSpecs } from '@opentrons/shared-data' +import { useConditionalConfirm, ModalShell } from '@opentrons/components' + +import * as Sessions from '/app/redux/sessions' +import { + Introduction, + DeckSetup, + TipPickUp, + TipConfirmation, + SaveZPoint, + SaveXYPoint, + ConfirmExit, + LoadingState, + CompleteConfirmation, +} from '/app/organisms/Desktop/CalibrationPanels' +import { WizardHeader } from '/app/molecules/WizardHeader' +import { getTopPortalEl } from '/app/App/portal' +import { + CalibrationError, + useCalibrationError, +} from '/app/organisms/Desktop/CalibrationError' + +import type { ComponentType } from 'react' +import type { Mount } from '@opentrons/components' +import type { + CalibrationLabware, + CalibrationSessionStep, + SessionCommandParams, +} from '/app/redux/sessions/types' +import type { CalibratePipetteOffsetParentProps } from './types' +import type { CalibrationPanelProps } from '/app/organisms/Desktop/CalibrationPanels/types' + +const PANEL_BY_STEP: Partial< + Record> +> = { + [Sessions.PIP_OFFSET_STEP_SESSION_STARTED]: Introduction, + [Sessions.PIP_OFFSET_STEP_LABWARE_LOADED]: DeckSetup, + [Sessions.PIP_OFFSET_STEP_PREPARING_PIPETTE]: TipPickUp, + [Sessions.PIP_OFFSET_STEP_INSPECTING_TIP]: TipConfirmation, + [Sessions.PIP_OFFSET_STEP_JOGGING_TO_DECK]: SaveZPoint, + [Sessions.PIP_OFFSET_STEP_SAVING_POINT_ONE]: SaveXYPoint, + [Sessions.PIP_OFFSET_STEP_TIP_LENGTH_COMPLETE]: PipetteOffsetCalibrationComplete, + [Sessions.PIP_OFFSET_STEP_CALIBRATION_COMPLETE]: PipetteOffsetCalibrationComplete, +} +const STEPS_IN_ORDER: CalibrationSessionStep[] = [ + Sessions.PIP_OFFSET_STEP_SESSION_STARTED, + Sessions.PIP_OFFSET_STEP_LABWARE_LOADED, + Sessions.PIP_OFFSET_STEP_PREPARING_PIPETTE, + Sessions.PIP_OFFSET_STEP_INSPECTING_TIP, + Sessions.PIP_OFFSET_STEP_JOGGING_TO_DECK, + Sessions.PIP_OFFSET_STEP_SAVING_POINT_ONE, + Sessions.PIP_OFFSET_STEP_CALIBRATION_COMPLETE, +] + +export function CalibratePipetteOffset({ + session, + robotName, + dispatchRequests, + showSpinner, + isJogging, + requestIds, +}: CalibratePipetteOffsetParentProps): JSX.Element | null { + const { t } = useTranslation('robot_calibration') + const { currentStep, instrument, labware, supportedCommands } = + session?.details ?? {} + + const queryClient = useQueryClient() + const host = useHost() + + const { + showConfirmation: showConfirmExit, + confirm: confirmExit, + cancel: cancelExit, + } = useConditionalConfirm(() => { + cleanUpAndExit() + }, true) + + const errorInfo = useCalibrationError(requestIds, session?.id) + + const tipRack: CalibrationLabware | null = + labware != null ? labware.find(l => l.isTiprack) ?? null : null + const calBlock: CalibrationLabware | null = + labware != null ? labware.find(l => !l.isTiprack) ?? null : null + + const isMulti = useMemo(() => { + const spec = + instrument != null ? getPipetteModelSpecs(instrument.model) : null + return spec != null ? spec.channels > 1 : false + }, [instrument]) + + function sendCommands(...commands: SessionCommandParams[]): void { + if (session?.id != null && !isJogging) { + const sessionCommandActions = commands.map(c => + Sessions.createSessionCommand(robotName, session.id, { + command: c.command, + data: c.data ?? {}, + }) + ) + dispatchRequests(...sessionCommandActions) + } + } + + function cleanUpAndExit(): void { + queryClient.invalidateQueries([host, 'calibration']).catch((e: Error) => { + console.error(`error invalidating calibration queries: ${e.message}`) + }) + if (session?.id != null) { + dispatchRequests( + Sessions.createSessionCommand(robotName, session.id, { + command: Sessions.sharedCalCommands.EXIT, + data: {}, + }), + Sessions.deleteSession(robotName, session.id) + ) + } + } + + if (session == null || tipRack == null) { + return null + } + + const Panel = + currentStep != null && currentStep in PANEL_BY_STEP + ? PANEL_BY_STEP[currentStep] + : null + return createPortal( + step === currentStep) ?? 0 + } + totalSteps={STEPS_IN_ORDER.length - 1} + onExit={confirmExit} + /> + } + > + {showSpinner || currentStep == null || Panel == null ? ( + + ) : showConfirmExit ? ( + + ) : errorInfo != null ? ( + + ) : ( + + )} + , + getTopPortalEl() + ) +} + +function PipetteOffsetCalibrationComplete( + props: CalibrationPanelProps +): JSX.Element { + const { t } = useTranslation('robot_calibration') + const { cleanUpAndExit } = props + + return ( + + ) +} diff --git a/app/src/organisms/Desktop/CalibratePipetteOffset/types.ts b/app/src/organisms/Desktop/CalibratePipetteOffset/types.ts new file mode 100644 index 00000000000..fdf12073486 --- /dev/null +++ b/app/src/organisms/Desktop/CalibratePipetteOffset/types.ts @@ -0,0 +1,26 @@ +import type { + SessionCommandParams, + PipetteOffsetCalibrationSession, + CalibrationLabware, +} from '/app/redux/sessions/types' + +import type { PipetteOffsetCalibrationStep } from '/app/redux/sessions/pipette-offset-calibration/types' +import type { DispatchRequestsType } from '/app/redux/robot-api' + +export interface CalibratePipetteOffsetParentProps { + robotName: string + session: PipetteOffsetCalibrationSession | null + dispatchRequests: DispatchRequestsType + requestIds: string[] + showSpinner: boolean + isJogging: boolean +} + +export interface CalibratePipetteOffsetChildProps { + sendSessionCommands: (...params: SessionCommandParams[]) => void + deleteSession: () => void + tipRack: CalibrationLabware + isMulti: boolean + mount: string + currentStep: PipetteOffsetCalibrationStep +} diff --git a/app/src/organisms/CalibratePipetteOffset/useCalibratePipetteOffset.tsx b/app/src/organisms/Desktop/CalibratePipetteOffset/useCalibratePipetteOffset.tsx similarity index 86% rename from app/src/organisms/CalibratePipetteOffset/useCalibratePipetteOffset.tsx rename to app/src/organisms/Desktop/CalibratePipetteOffset/useCalibratePipetteOffset.tsx index 2f34ea51af0..a16758d3ed5 100644 --- a/app/src/organisms/CalibratePipetteOffset/useCalibratePipetteOffset.tsx +++ b/app/src/organisms/Desktop/CalibratePipetteOffset/useCalibratePipetteOffset.tsx @@ -1,24 +1,24 @@ -import * as React from 'react' +import { useRef, useEffect } from 'react' import { createPortal } from 'react-dom' import { useSelector, useDispatch } from 'react-redux' import { SpinnerModalPage } from '@opentrons/components' -import * as RobotApi from '../../redux/robot-api' -import * as Sessions from '../../redux/sessions' -import { getPipetteOffsetCalibrationSession } from '../../redux/sessions/pipette-offset-calibration/selectors' +import * as RobotApi from '/app/redux/robot-api' +import * as Sessions from '/app/redux/sessions' +import { getPipetteOffsetCalibrationSession } from '/app/redux/sessions/pipette-offset-calibration/selectors' -import type { State } from '../../redux/types' +import type { State } from '/app/redux/types' import type { SessionCommandString, PipetteOffsetCalibrationSession, PipetteOffsetCalibrationSessionParams, -} from '../../redux/sessions/types' -import type { RequestState } from '../../redux/robot-api/types' +} from '/app/redux/sessions/types' +import type { RequestState } from '/app/redux/robot-api/types' -import { getTopPortalEl } from '../../App/portal' +import { getTopPortalEl } from '/app/App/portal' import { CalibratePipetteOffset } from '.' -import { pipetteOffsetCalibrationStarted } from '../../redux/analytics' +import { pipetteOffsetCalibrationStarted } from '/app/redux/analytics' import { useTranslation } from 'react-i18next' // pipette calibration commands for which the full page spinner should not appear @@ -38,10 +38,10 @@ export function useCalibratePipetteOffset( onComplete: (() => unknown) | null = null ): [Invoker, JSX.Element | null] { const { t } = useTranslation(['robot_calibration', 'shared']) - const createRequestId = React.useRef(null) - const deleteRequestId = React.useRef(null) - const jogRequestId = React.useRef(null) - const spinnerRequestId = React.useRef(null) + const createRequestId = useRef(null) + const deleteRequestId = useRef(null) + const jogRequestId = useRef(null) + const spinnerRequestId = useRef(null) const dispatch = useDispatch() const pipOffsetCalSession: PipetteOffsetCalibrationSession | null = useSelector( @@ -50,7 +50,7 @@ export function useCalibratePipetteOffset( } ) - const [dispatchRequests] = RobotApi.useDispatchApiRequests( + const [dispatchRequests, requestIds] = RobotApi.useDispatchApiRequests( dispatchedAction => { if ( dispatchedAction.type === Sessions.ENSURE_SESSION && @@ -112,7 +112,7 @@ export function useCalibratePipetteOffset( : null )?.status === RobotApi.PENDING - React.useEffect(() => { + useEffect(() => { if (shouldClose) { onComplete?.() deleteRequestId.current = null @@ -175,6 +175,7 @@ export function useCalibratePipetteOffset( showSpinner={startingSession || showSpinner} dispatchRequests={dispatchRequests} isJogging={isJogging} + requestIds={requestIds} /> ), getTopPortalEl() diff --git a/app/src/organisms/CalibrateTipLength/AskForCalibrationBlockModal.tsx b/app/src/organisms/Desktop/CalibrateTipLength/AskForCalibrationBlockModal.tsx similarity index 87% rename from app/src/organisms/CalibrateTipLength/AskForCalibrationBlockModal.tsx rename to app/src/organisms/Desktop/CalibrateTipLength/AskForCalibrationBlockModal.tsx index 1269104cec4..85eb76ae126 100644 --- a/app/src/organisms/CalibrateTipLength/AskForCalibrationBlockModal.tsx +++ b/app/src/organisms/Desktop/CalibrateTipLength/AskForCalibrationBlockModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { Trans, useTranslation } from 'react-i18next' import { @@ -19,12 +19,13 @@ import { import { useDispatch } from 'react-redux' import styles from './styles.module.css' -import { labwareImages } from '../../organisms/CalibrationPanels/labwareImages' -import { WizardHeader } from '../../molecules/WizardHeader' -import { getTopPortalEl } from '../../App/portal' -import { setUseTrashSurfaceForTipCal } from '../../redux/calibration' +import { labwareImages } from '/app/local-resources/labware' +import { WizardHeader } from '/app/molecules/WizardHeader' +import { getTopPortalEl } from '/app/App/portal' +import { setUseTrashSurfaceForTipCal } from '/app/redux/calibration' -import type { Dispatch } from '../../redux/types' +import type { ChangeEvent } from 'react' +import type { Dispatch } from '/app/redux/types' const BLOCK_REQUEST_EMAIL_BODY = '• Full name\n• Company or institution name\n• Shipping address\n• VAT ID (if outside the US)' @@ -41,9 +42,7 @@ interface Props { export function AskForCalibrationBlockModal(props: Props): JSX.Element { const { t } = useTranslation(['robot_calibration', 'shared', 'branded']) - const [rememberPreference, setRememberPreference] = React.useState( - true - ) + const [rememberPreference, setRememberPreference] = useState(true) const dispatch = useDispatch() const makeSetHasBlock = (hasBlock: boolean) => (): void => { @@ -108,7 +107,7 @@ export function AskForCalibrationBlockModal(props: Props): JSX.Element { > ) => { + onChange={(e: ChangeEvent) => { setRememberPreference(e.currentTarget.checked) }} value={rememberPreference} diff --git a/app/src/organisms/CalibrateTipLength/ConfirmRecalibrationModal.tsx b/app/src/organisms/Desktop/CalibrateTipLength/ConfirmRecalibrationModal.tsx similarity index 95% rename from app/src/organisms/CalibrateTipLength/ConfirmRecalibrationModal.tsx rename to app/src/organisms/Desktop/CalibrateTipLength/ConfirmRecalibrationModal.tsx index fb413eed6cb..9806297af9a 100644 --- a/app/src/organisms/CalibrateTipLength/ConfirmRecalibrationModal.tsx +++ b/app/src/organisms/Desktop/CalibrateTipLength/ConfirmRecalibrationModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { createPortal } from 'react-dom' import { @@ -12,7 +11,7 @@ import { Text, } from '@opentrons/components' -import { getModalPortalEl } from '../../App/portal' +import { getModalPortalEl } from '/app/App/portal' import styles from './styles.module.css' diff --git a/app/src/organisms/CalibrateTipLength/TipLengthCalibrationInfoBox.tsx b/app/src/organisms/Desktop/CalibrateTipLength/TipLengthCalibrationInfoBox.tsx similarity index 95% rename from app/src/organisms/CalibrateTipLength/TipLengthCalibrationInfoBox.tsx rename to app/src/organisms/Desktop/CalibrateTipLength/TipLengthCalibrationInfoBox.tsx index 0869293922c..7bcf3bd81e3 100644 --- a/app/src/organisms/CalibrateTipLength/TipLengthCalibrationInfoBox.tsx +++ b/app/src/organisms/Desktop/CalibrateTipLength/TipLengthCalibrationInfoBox.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Box, Text, diff --git a/app/src/organisms/CalibrateTipLength/__tests__/AskForCalibrationBlockModal.test.tsx b/app/src/organisms/Desktop/CalibrateTipLength/__tests__/AskForCalibrationBlockModal.test.tsx similarity index 91% rename from app/src/organisms/CalibrateTipLength/__tests__/AskForCalibrationBlockModal.test.tsx rename to app/src/organisms/Desktop/CalibrateTipLength/__tests__/AskForCalibrationBlockModal.test.tsx index 7d1b87f1fcb..199ec279988 100644 --- a/app/src/organisms/CalibrateTipLength/__tests__/AskForCalibrationBlockModal.test.tsx +++ b/app/src/organisms/Desktop/CalibrateTipLength/__tests__/AskForCalibrationBlockModal.test.tsx @@ -1,10 +1,9 @@ -import * as React from 'react' import { vi, it, describe, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../i18n' -import { setUseTrashSurfaceForTipCal } from '../../../redux/calibration' +import { i18n } from '/app/i18n' +import { setUseTrashSurfaceForTipCal } from '/app/redux/calibration' import { AskForCalibrationBlockModal } from '../AskForCalibrationBlockModal' import { fireEvent, screen } from '@testing-library/react' diff --git a/app/src/organisms/CalibrateTipLength/__tests__/CalibrateTipLength.test.tsx b/app/src/organisms/Desktop/CalibrateTipLength/__tests__/CalibrateTipLength.test.tsx similarity index 83% rename from app/src/organisms/CalibrateTipLength/__tests__/CalibrateTipLength.test.tsx rename to app/src/organisms/Desktop/CalibrateTipLength/__tests__/CalibrateTipLength.test.tsx index 40da64ff3bc..7324f182f66 100644 --- a/app/src/organisms/CalibrateTipLength/__tests__/CalibrateTipLength.test.tsx +++ b/app/src/organisms/Desktop/CalibrateTipLength/__tests__/CalibrateTipLength.test.tsx @@ -1,17 +1,21 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' import { when } from 'vitest-when' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { getDeckDefinitions } from '@opentrons/shared-data' -import { i18n } from '../../../i18n' -import * as Sessions from '../../../redux/sessions' -import { mockTipLengthCalibrationSessionAttributes } from '../../../redux/sessions/__fixtures__' - +import { i18n } from '/app/i18n' +import * as Sessions from '/app/redux/sessions' +import { mockTipLengthCalibrationSessionAttributes } from '/app/redux/sessions/__fixtures__' import { CalibrateTipLength } from '../index' -import type { TipLengthCalibrationStep } from '../../../redux/sessions/types' +import { + useCalibrationError, + CalibrationError, +} from '/app/organisms/Desktop/CalibrationError' + +import type { TipLengthCalibrationStep } from '/app/redux/sessions/types' vi.mock('@opentrons/shared-data', async importOriginal => { const actual = await importOriginal() @@ -20,9 +24,10 @@ vi.mock('@opentrons/shared-data', async importOriginal => { getDeckDefinitions: vi.fn(), } }) -vi.mock('../../../redux/sessions/selectors') -vi.mock('../../../redux/robot-api/selectors') -vi.mock('../../../redux/config') +vi.mock('/app/redux/sessions/selectors') +vi.mock('/app/redux/robot-api/selectors') +vi.mock('/app/redux/config') +vi.mock('/app/organisms/Desktop/CalibrationError') interface CalibrateTipLengthSpec { heading: string @@ -50,6 +55,7 @@ describe('CalibrateTipLength', () => { dispatchRequests={dispatchRequests} showSpinner={showSpinner} isJogging={isJogging} + requestIds={[]} />, { i18nInstance: i18n } ) @@ -76,6 +82,10 @@ describe('CalibrateTipLength', () => { beforeEach(() => { when(vi.mocked(getDeckDefinitions)).calledWith().thenReturn({}) + vi.mocked(useCalibrationError).mockReturnValue(null) + vi.mocked(CalibrationError).mockReturnValue( +
    MOCK_CALIBRATION_ERROR
    + ) }) afterEach(() => { vi.resetAllMocks() @@ -176,4 +186,15 @@ describe('CalibrateTipLength', () => { }) ) }) + + it('renders an error modal if there is an error', () => { + vi.mocked(useCalibrationError).mockReturnValue({ + title: 'test', + subText: 'test', + }) + + render() + + screen.getByText('MOCK_CALIBRATION_ERROR') + }) }) diff --git a/app/src/organisms/Desktop/CalibrateTipLength/index.tsx b/app/src/organisms/Desktop/CalibrateTipLength/index.tsx new file mode 100644 index 00000000000..e905f1b77c7 --- /dev/null +++ b/app/src/organisms/Desktop/CalibrateTipLength/index.tsx @@ -0,0 +1,228 @@ +// Tip Length Calibration Orchestration Component +import { useMemo } from 'react' +import { createPortal } from 'react-dom' +import { useTranslation } from 'react-i18next' +import { useQueryClient } from 'react-query' +import { css } from 'styled-components' + +import { useHost } from '@opentrons/react-api-client' +import { getPipetteModelSpecs } from '@opentrons/shared-data' +import { useConditionalConfirm, ModalShell } from '@opentrons/components' + +import * as Sessions from '/app/redux/sessions' +import { + Introduction, + DeckSetup, + TipPickUp, + TipConfirmation, + MeasureNozzle, + MeasureTip, + ConfirmExit, + LoadingState, + CompleteConfirmation, +} from '/app/organisms/Desktop/CalibrationPanels' +import { WizardHeader } from '/app/molecules/WizardHeader' +import { getTopPortalEl } from '/app/App/portal' +import { + CalibrationError, + useCalibrationError, +} from '/app/organisms/Desktop/CalibrationError' +import slotOneRemoveBlockAsset from '/app/assets/videos/tip-length-cal/Slot_1_Remove_CalBlock_(330x260)REV1.webm' +import slotThreeRemoveBlockAsset from '/app/assets/videos/tip-length-cal/Slot_3_Remove_CalBlock_(330x260)REV1.webm' + +import type { ComponentType } from 'react' +import type { Mount } from '@opentrons/components' +import type { + SessionCommandParams, + CalibrationLabware, + CalibrationSessionStep, +} from '/app/redux/sessions/types' +import type { CalibrationPanelProps } from '/app/organisms/Desktop/CalibrationPanels/types' +import type { CalibrateTipLengthParentProps } from './types' + +export { AskForCalibrationBlockModal } from './AskForCalibrationBlockModal' +export { ConfirmRecalibrationModal } from './ConfirmRecalibrationModal' + +const PANEL_BY_STEP: Partial< + Record> +> = { + sessionStarted: Introduction, + labwareLoaded: DeckSetup, + measuringNozzleOffset: MeasureNozzle, + preparingPipette: TipPickUp, + inspectingTip: TipConfirmation, + measuringTipOffset: MeasureTip, + calibrationComplete: TipLengthCalibrationComplete, +} +const STEPS_IN_ORDER: CalibrationSessionStep[] = [ + Sessions.TIP_LENGTH_STEP_SESSION_STARTED, + Sessions.TIP_LENGTH_STEP_LABWARE_LOADED, + Sessions.TIP_LENGTH_STEP_MEASURING_NOZZLE_OFFSET, + Sessions.TIP_LENGTH_STEP_PREPARING_PIPETTE, + Sessions.TIP_LENGTH_STEP_INSPECTING_TIP, + Sessions.TIP_LENGTH_STEP_MEASURING_TIP_OFFSET, + Sessions.TIP_LENGTH_STEP_CALIBRATION_COMPLETE, +] + +export function CalibrateTipLength({ + session, + robotName, + showSpinner, + dispatchRequests, + requestIds, + isJogging, + offsetInvalidationHandler, + allowChangeTipRack = false, +}: CalibrateTipLengthParentProps): JSX.Element | null { + const { t } = useTranslation('robot_calibration') + const { currentStep, instrument, labware, supportedCommands } = + session?.details ?? {} + + const queryClient = useQueryClient() + const host = useHost() + + const isMulti = useMemo(() => { + const spec = + instrument != null ? getPipetteModelSpecs(instrument.model) : null + return spec != null ? spec.channels > 1 : false + }, [instrument]) + + const tipRack: CalibrationLabware | null = + labware != null ? labware.find(l => l.isTiprack) ?? null : null + const calBlock: CalibrationLabware | null = + labware != null ? labware.find(l => !l.isTiprack) ?? null : null + + const errorInfo = useCalibrationError(requestIds, session?.id) + + function sendCommands(...commands: SessionCommandParams[]): void { + if (session?.id != null && !isJogging) { + const sessionCommandActions = commands.map(c => + Sessions.createSessionCommand(robotName, session.id, { + command: c.command, + data: c.data ?? {}, + }) + ) + dispatchRequests(...sessionCommandActions) + } + } + + function cleanUpAndExit(): void { + queryClient.invalidateQueries([host, 'calibration']).catch((e: Error) => { + console.error(`error invalidating calibration queries: ${e.message}`) + }) + if (session?.id != null) { + dispatchRequests( + Sessions.createSessionCommand(robotName, session.id, { + command: Sessions.sharedCalCommands.EXIT, + data: {}, + }), + Sessions.deleteSession(robotName, session.id) + ) + } + } + + const { + showConfirmation: showConfirmExit, + confirm: confirmExit, + cancel: cancelExit, + } = useConditionalConfirm(() => { + cleanUpAndExit() + }, true) + + if (session == null || tipRack == null) { + return null + } + + const Panel = + currentStep != null && currentStep in PANEL_BY_STEP + ? PANEL_BY_STEP[currentStep] + : null + return createPortal( + step === currentStep) ?? 0 + } + totalSteps={STEPS_IN_ORDER.length - 1} + onExit={confirmExit} + /> + } + > + {showSpinner || currentStep == null || Panel == null ? ( + + ) : showConfirmExit ? ( + + ) : errorInfo != null ? ( + + ) : ( + + )} + , + getTopPortalEl() + ) +} + +const blockRemovalAssetBySlot: { + [slot in CalibrationLabware['slot']]: string +} = { + '1': slotOneRemoveBlockAsset, + '3': slotThreeRemoveBlockAsset, +} + +function TipLengthCalibrationComplete( + props: CalibrationPanelProps +): JSX.Element { + const { t } = useTranslation('robot_calibration') + const { calBlock, cleanUpAndExit } = props + + const visualAid = + calBlock != null ? ( + + ) : null + + return ( + + ) +} diff --git a/app/src/organisms/CalibrateTipLength/styles.module.css b/app/src/organisms/Desktop/CalibrateTipLength/styles.module.css similarity index 100% rename from app/src/organisms/CalibrateTipLength/styles.module.css rename to app/src/organisms/Desktop/CalibrateTipLength/styles.module.css diff --git a/app/src/organisms/Desktop/CalibrateTipLength/types.ts b/app/src/organisms/Desktop/CalibrateTipLength/types.ts new file mode 100644 index 00000000000..cff33722db1 --- /dev/null +++ b/app/src/organisms/Desktop/CalibrateTipLength/types.ts @@ -0,0 +1,13 @@ +import type { DispatchRequestsType } from '/app/redux/robot-api' +import type { TipLengthCalibrationSession } from '/app/redux/sessions/types' + +export interface CalibrateTipLengthParentProps { + robotName: string + session: TipLengthCalibrationSession | null + dispatchRequests: DispatchRequestsType + requestIds: string[] + showSpinner: boolean + isJogging: boolean + allowChangeTipRack?: boolean + offsetInvalidationHandler?: () => void +} diff --git a/app/src/organisms/Desktop/CalibrationError/__tests__/CalibrationError.test.tsx b/app/src/organisms/Desktop/CalibrationError/__tests__/CalibrationError.test.tsx new file mode 100644 index 00000000000..d8213c793ca --- /dev/null +++ b/app/src/organisms/Desktop/CalibrationError/__tests__/CalibrationError.test.tsx @@ -0,0 +1,50 @@ +import { describe, it, vi, expect, beforeEach } from 'vitest' +import { screen, fireEvent } from '@testing-library/react' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { CalibrationError } from '..' + +import type { ComponentProps } from 'react' + +describe('CalibrationError', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + title: 'Error Title', + subText: 'Error Description', + onClose: vi.fn(), + } + }) + + const render = (props: ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] + } + + it('displays expected copy', () => { + render(props) + + screen.getByText('Error Title') + screen.getByText('Error Description') + }) + + it('calls onClose when exit button is clicked', () => { + render(props) + + fireEvent.click(screen.getByRole('button', { name: 'Exit' })) + + expect(props.onClose).toHaveBeenCalled() + }) + + it('disables the exit button after it is clicked', () => { + render(props) + + const exitButton = screen.getByRole('button', { name: 'Exit' }) + fireEvent.click(exitButton) + + expect(exitButton).toBeDisabled() + }) +}) diff --git a/app/src/organisms/Desktop/CalibrationError/__tests__/useCalibrationError.test.ts b/app/src/organisms/Desktop/CalibrationError/__tests__/useCalibrationError.test.ts new file mode 100644 index 00000000000..ee86cde480e --- /dev/null +++ b/app/src/organisms/Desktop/CalibrationError/__tests__/useCalibrationError.test.ts @@ -0,0 +1,99 @@ +import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest' +import { renderHook } from '@testing-library/react' +import { useDispatch, useSelector } from 'react-redux' + +import { getRequests, dismissAllRequests } from '/app/redux/robot-api' +import { useCalibrationError } from '/app/organisms/Desktop/CalibrationError' + +vi.mock('react-redux', () => ({ + useDispatch: vi.fn(), + useSelector: vi.fn(), +})) + +vi.mock('/app/redux/robot-api', () => ({ + dismissAllRequests: vi.fn(), + getRequests: vi.fn(), +})) + +describe('useCalibrationError', () => { + const mockDispatch = vi.fn() + const mockRequestIds = ['req1', 'req2'] + const mockSessionId = 'session1' + + beforeEach(() => { + vi.mocked(useDispatch).mockReturnValue(mockDispatch) + vi.mocked(useSelector).mockImplementation(selector => selector({} as any)) + vi.mocked(getRequests).mockReturnValue([]) + }) + + afterEach(() => { + vi.resetAllMocks() + }) + + it('should return null when there are no errored requests', () => { + const { result } = renderHook(() => + useCalibrationError(mockRequestIds, mockSessionId) + ) + expect(result.current).toBeNull() + }) + + it('should dispatch dismissAllRequests when sessionId is provided', () => { + renderHook(() => useCalibrationError(mockRequestIds, mockSessionId)) + expect(mockDispatch).toHaveBeenCalledWith(dismissAllRequests()) + }) + + it('should return error info when there is an errored request with errors', () => { + vi.mocked(getRequests).mockReturnValue([ + { + status: 'failure', + error: { + errors: [{ title: 'Test Error', detail: 'Test Detail' }], + }, + }, + ] as any) + + const { result } = renderHook(() => + useCalibrationError(mockRequestIds, mockSessionId) + ) + expect(result.current).toEqual({ + title: 'Test Error', + subText: 'Test Detail', + }) + }) + + it('should return error info when there is an errored request with message', () => { + vi.mocked(getRequests).mockReturnValue([ + { + status: 'failure', + error: { + message: 'Test Message', + }, + }, + ] as any) + + const { result } = renderHook(() => + useCalibrationError(mockRequestIds, mockSessionId) + ) + expect(result.current).toEqual({ + title: 'robot_calibration:error', + subText: 'Test Message', + }) + }) + + it('should return default error info when error details are missing', () => { + vi.mocked(getRequests).mockReturnValue([ + { + status: 'failure', + error: {}, + }, + ] as any) + + const { result } = renderHook(() => + useCalibrationError(mockRequestIds, mockSessionId) + ) + expect(result.current).toEqual({ + title: 'robot_calibration:error', + subText: 'branded:unexpected_error', + }) + }) +}) diff --git a/app/src/organisms/Desktop/CalibrationError/index.tsx b/app/src/organisms/Desktop/CalibrationError/index.tsx new file mode 100644 index 00000000000..b3bfc7d1745 --- /dev/null +++ b/app/src/organisms/Desktop/CalibrationError/index.tsx @@ -0,0 +1,142 @@ +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { css } from 'styled-components' +import { useDispatch, useSelector } from 'react-redux' + +import { + DISPLAY_FLEX, + DIRECTION_COLUMN, + SPACING, + ALIGN_CENTER, + COLORS, + Icon, + Flex, + StyledText, + JUSTIFY_CENTER, + JUSTIFY_FLEX_END, + TEXT_ALIGN_CENTER, + AlertPrimaryButton, +} from '@opentrons/components' + +import { dismissAllRequests, getRequests } from '/app/redux/robot-api' + +import type { State } from '/app/redux/types' + +export interface CalibrationErrorInfo { + title: string + subText: string +} + +export type UseCalibrationErrorInfoResult = CalibrationErrorInfo | null + +// Returns relevant copy derived from the error response, if any. +export function useCalibrationError( + requestIds: string[], + sessionId: string | undefined +): UseCalibrationErrorInfoResult { + const { t } = useTranslation(['robot_calibration', 'branded']) + const dispatch = useDispatch() + + // Dismiss all network requests during a unique session to prevent stale error state. + useEffect(() => { + if (sessionId != null) { + dispatch(dismissAllRequests()) + } + }, [sessionId]) + + const reqs = useSelector((state: State) => { + return getRequests(state, requestIds) + }) + const erroredReqs = reqs.filter(req => req?.status === 'failure') + + if (erroredReqs.length > 0) { + const erroredReq = erroredReqs[0] + if (erroredReq != null && erroredReq.status === 'failure') { + if ('errors' in erroredReq.error) { + const title = + erroredReq.error.errors[0].title ?? + (t('robot_calibration:error') as string) + const subText = + erroredReq.error.errors[0].detail ?? + (t('branded:unexpected_error') as string) + + return { title, subText } + } else if ('message' in erroredReq.error) { + const title = t('robot_calibration:error') + const subText = + erroredReq.error.message ?? (t('branded:unexpected_error') as string) + + return { title, subText } + } else { + return { + title: t('robot_calibration:error'), + subText: t('branded:unexpected_error'), + } + } + } + } + + return null +} + +export type CalibrationErrorProps = CalibrationErrorInfo & { + onClose: () => void +} + +export function CalibrationError({ + subText, + title, + onClose, +}: CalibrationErrorProps): JSX.Element { + const [isClosing, setIsClosing] = useState(false) + const { t } = useTranslation('robot_calibration') + + return ( + + + + + {title} + + + {subText} + + + + { + setIsClosing(true) + onClose() + }} + disabled={isClosing} + > + {t('exit')} + + + + ) +} + +const CONTAINER_STYLE = css` + flex-direction: ${DIRECTION_COLUMN}; + padding: ${SPACING.spacing32}; +` + +const CONTENT_CONTAINER_STYLE = css` + display: ${DISPLAY_FLEX}; + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing16}; + padding: ${SPACING.spacing40} ${SPACING.spacing16}; + align-items: ${ALIGN_CENTER}; + justify-content: ${JUSTIFY_CENTER}; + text-align: ${TEXT_ALIGN_CENTER}; + margin-top: ${SPACING.spacing16}; +` + +const ICON_STYLE = css` + width: 40px; + height: 40px; +` diff --git a/app/src/organisms/CalibrationPanels/CalibrationLabwareRender.tsx b/app/src/organisms/Desktop/CalibrationPanels/CalibrationLabwareRender.tsx similarity index 99% rename from app/src/organisms/CalibrationPanels/CalibrationLabwareRender.tsx rename to app/src/organisms/Desktop/CalibrationPanels/CalibrationLabwareRender.tsx index e81adfe525b..2e789e7f116 100644 --- a/app/src/organisms/CalibrationPanels/CalibrationLabwareRender.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/CalibrationLabwareRender.tsx @@ -1,5 +1,3 @@ -import * as React from 'react' - import { LabwareRender, LabwareNameOverlay, diff --git a/app/src/organisms/CalibrationPanels/ChooseTipRack.tsx b/app/src/organisms/Desktop/CalibrationPanels/ChooseTipRack.tsx similarity index 92% rename from app/src/organisms/CalibrationPanels/ChooseTipRack.tsx rename to app/src/organisms/Desktop/CalibrationPanels/ChooseTipRack.tsx index 354b5d1b099..072293f22b4 100644 --- a/app/src/organisms/CalibrationPanels/ChooseTipRack.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/ChooseTipRack.tsx @@ -1,10 +1,11 @@ -import * as React from 'react' +import { useState } from 'react' import { useSelector } from 'react-redux' import { Trans, useTranslation } from 'react-i18next' import head from 'lodash/head' import isEqual from 'lodash/isEqual' import { ALIGN_CENTER, + Banner, Box, COLORS, DIRECTION_COLUMN, @@ -18,25 +19,24 @@ import { } from '@opentrons/components' import { usePipettesQuery } from '@opentrons/react-api-client' import { getLabwareDefURI } from '@opentrons/shared-data' -import { getCustomTipRackDefinitions } from '../../redux/custom-labware' +import { getCustomTipRackDefinitions } from '/app/redux/custom-labware' import { getCalibrationForPipette, getTipLengthCalibrations, getTipLengthForPipetteAndTiprack, -} from '../../redux/calibration/' -import { Select } from '../../atoms/SelectField/Select' -import { Banner } from '../../atoms/Banner' -import { Divider } from '../../atoms/structure' -import { NeedHelpLink } from './NeedHelpLink' +} from '/app/redux/calibration/' +import { Select } from '/app/atoms/SelectField/Select' +import { Divider } from '/app/atoms/structure' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' import { ChosenTipRackRender } from './ChosenTipRackRender' import type { MultiValue, SingleValue } from 'react-select' import type { LabwareDefinition2 } from '@opentrons/shared-data' import type { SelectOption, SelectOptionOrGroup } from '@opentrons/components' -import type { CalibrationLabware } from '../../redux/sessions/types' -import type { State } from '../../redux/types' -import type { Mount } from '../../redux/pipettes/types' -import type { TipLengthCalibration } from '../../redux/calibration/api-types' +import type { CalibrationLabware } from '/app/redux/sessions/types' +import type { State } from '/app/redux/types' +import type { Mount } from '/app/redux/pipettes/types' +import type { TipLengthCalibration } from '/app/redux/calibration/api-types' interface TipRackInfo { definition: LabwareDefinition2 @@ -153,7 +153,7 @@ export function ChooseTipRack(props: ChooseTipRackProps): JSX.Element { ] : [...opentronsTipRacksOptions] - const [selectedValue, setSelectedValue] = React.useState< + const [selectedValue, setSelectedValue] = useState< SingleValue | MultiValue >( chosenTipRack != null diff --git a/app/src/organisms/CalibrationPanels/ChosenTipRackRender.tsx b/app/src/organisms/Desktop/CalibrationPanels/ChosenTipRackRender.tsx similarity index 93% rename from app/src/organisms/CalibrationPanels/ChosenTipRackRender.tsx rename to app/src/organisms/Desktop/CalibrationPanels/ChosenTipRackRender.tsx index 98d1ebb4499..4cc533985d0 100644 --- a/app/src/organisms/CalibrationPanels/ChosenTipRackRender.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/ChosenTipRackRender.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { css } from 'styled-components' import { ALIGN_CENTER, @@ -8,7 +7,7 @@ import { SPACING, LegacyStyledText, } from '@opentrons/components' -import { labwareImages } from './labwareImages' +import { labwareImages } from '/app/local-resources/labware' import type { SelectOption } from '@opentrons/components' export interface ChosenTipRackRenderProps { diff --git a/app/src/organisms/CalibrationPanels/CompleteConfirmation.tsx b/app/src/organisms/Desktop/CalibrationPanels/CompleteConfirmation.tsx similarity index 97% rename from app/src/organisms/CalibrationPanels/CompleteConfirmation.tsx rename to app/src/organisms/Desktop/CalibrationPanels/CompleteConfirmation.tsx index 7d11b2243a9..cbd0e214cd1 100644 --- a/app/src/organisms/CalibrationPanels/CompleteConfirmation.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/CompleteConfirmation.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, diff --git a/app/src/organisms/CalibrationPanels/ConfirmCrashRecovery.tsx b/app/src/organisms/Desktop/CalibrationPanels/ConfirmCrashRecovery.tsx similarity index 98% rename from app/src/organisms/CalibrationPanels/ConfirmCrashRecovery.tsx rename to app/src/organisms/Desktop/CalibrationPanels/ConfirmCrashRecovery.tsx index 43a55cabaa2..b2ac7b52179 100644 --- a/app/src/organisms/CalibrationPanels/ConfirmCrashRecovery.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/ConfirmCrashRecovery.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { diff --git a/app/src/organisms/CalibrationPanels/ConfirmExit.tsx b/app/src/organisms/Desktop/CalibrationPanels/ConfirmExit.tsx similarity index 95% rename from app/src/organisms/CalibrationPanels/ConfirmExit.tsx rename to app/src/organisms/Desktop/CalibrationPanels/ConfirmExit.tsx index 4d52a7424e9..2190852ae4a 100644 --- a/app/src/organisms/CalibrationPanels/ConfirmExit.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/ConfirmExit.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { AlertPrimaryButton, @@ -15,7 +14,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { NeedHelpLink } from './NeedHelpLink' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' interface ConfirmExitProps { back: () => void diff --git a/app/src/organisms/CalibrationPanels/DeckSetup.tsx b/app/src/organisms/Desktop/CalibrationPanels/DeckSetup.tsx similarity index 96% rename from app/src/organisms/CalibrationPanels/DeckSetup.tsx rename to app/src/organisms/Desktop/CalibrationPanels/DeckSetup.tsx index 4acab16ded8..6da2f94044d 100644 --- a/app/src/organisms/CalibrationPanels/DeckSetup.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/DeckSetup.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import map from 'lodash/map' @@ -18,8 +18,8 @@ import { getLabwareDisplayName, getPositionFromSlotId, } from '@opentrons/shared-data' -import * as Sessions from '../../redux/sessions' -import { NeedHelpLink } from './NeedHelpLink' +import * as Sessions from '/app/redux/sessions' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' import { CalibrationLabwareRender } from './CalibrationLabwareRender' import type { AddressableArea } from '@opentrons/shared-data' @@ -28,7 +28,7 @@ import type { CalibrationPanelProps } from './types' const TIPRACK = 'tip rack' export function DeckSetup(props: CalibrationPanelProps): JSX.Element { - const deckDef = React.useMemo(() => getDeckDefinitions().ot2_standard, []) + const deckDef = useMemo(() => getDeckDefinitions().ot2_standard, []) const { t } = useTranslation('robot_calibration') diff --git a/app/src/organisms/CalibrationPanels/Introduction/Body.tsx b/app/src/organisms/Desktop/CalibrationPanels/Introduction/Body.tsx similarity index 89% rename from app/src/organisms/CalibrationPanels/Introduction/Body.tsx rename to app/src/organisms/Desktop/CalibrationPanels/Introduction/Body.tsx index 5ad643dd412..8f67637f6c4 100644 --- a/app/src/organisms/CalibrationPanels/Introduction/Body.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/Introduction/Body.tsx @@ -1,9 +1,8 @@ -import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import { LegacyStyledText } from '@opentrons/components' -import * as Sessions from '../../../redux/sessions' +import * as Sessions from '/app/redux/sessions' -import type { SessionType } from '../../../redux/sessions/types' +import type { SessionType } from '/app/redux/sessions/types' interface BodyProps { sessionType: SessionType diff --git a/app/src/organisms/CalibrationPanels/Introduction/InvalidationWarning.tsx b/app/src/organisms/Desktop/CalibrationPanels/Introduction/InvalidationWarning.tsx similarity index 90% rename from app/src/organisms/CalibrationPanels/Introduction/InvalidationWarning.tsx rename to app/src/organisms/Desktop/CalibrationPanels/Introduction/InvalidationWarning.tsx index 95c345e45fd..dbe27f7358a 100644 --- a/app/src/organisms/CalibrationPanels/Introduction/InvalidationWarning.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/Introduction/InvalidationWarning.tsx @@ -1,13 +1,12 @@ -import * as React from 'react' import { Flex, + Banner, SPACING, TYPOGRAPHY, LegacyStyledText, } from '@opentrons/components' import { useTranslation } from 'react-i18next' -import { Banner } from '../../../atoms/Banner' -import * as Sessions from '../../../redux/sessions' +import * as Sessions from '/app/redux/sessions' interface InvalidationWarningProps { sessionType: diff --git a/app/src/organisms/CalibrationPanels/Introduction/__tests__/Body.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/Introduction/__tests__/Body.test.tsx similarity index 90% rename from app/src/organisms/CalibrationPanels/Introduction/__tests__/Body.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/Introduction/__tests__/Body.test.tsx index b201352a3db..c1300fb3218 100644 --- a/app/src/organisms/CalibrationPanels/Introduction/__tests__/Body.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/Introduction/__tests__/Body.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { it, describe } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' -import * as Sessions from '../../../../redux/sessions' +import { renderWithProviders } from '/app/__testing-utils__' +import * as Sessions from '/app/redux/sessions' -import { i18n } from '../../../../i18n' +import { i18n } from '/app/i18n' import { Body } from '../Body' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/CalibrationPanels/Introduction/__tests__/Introduction.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/Introduction/__tests__/Introduction.test.tsx similarity index 93% rename from app/src/organisms/CalibrationPanels/Introduction/__tests__/Introduction.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/Introduction/__tests__/Introduction.test.tsx index 408d4602466..a34460571ed 100644 --- a/app/src/organisms/CalibrationPanels/Introduction/__tests__/Introduction.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/Introduction/__tests__/Introduction.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, expect, describe, beforeEach, afterEach } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' -import { mockCalibrationCheckLabware } from '../../../../redux/sessions/__fixtures__' -import * as Sessions from '../../../../redux/sessions' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { mockCalibrationCheckLabware } from '/app/redux/sessions/__fixtures__' +import * as Sessions from '/app/redux/sessions' +import { i18n } from '/app/i18n' import { Introduction } from '../' import { ChooseTipRack } from '../../ChooseTipRack' diff --git a/app/src/organisms/CalibrationPanels/Introduction/__tests__/InvalidationWarning.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/Introduction/__tests__/InvalidationWarning.test.tsx similarity index 85% rename from app/src/organisms/CalibrationPanels/Introduction/__tests__/InvalidationWarning.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/Introduction/__tests__/InvalidationWarning.test.tsx index 6ca5eb3340f..6151097eccf 100644 --- a/app/src/organisms/CalibrationPanels/Introduction/__tests__/InvalidationWarning.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/Introduction/__tests__/InvalidationWarning.test.tsx @@ -1,9 +1,8 @@ -import * as React from 'react' import { it, describe } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { InvalidationWarning } from '../InvalidationWarning' const render = (sessionType: 'tipLengthCalibration' | 'deckCalibration') => { diff --git a/app/src/organisms/Desktop/CalibrationPanels/Introduction/index.tsx b/app/src/organisms/Desktop/CalibrationPanels/Introduction/index.tsx new file mode 100644 index 00000000000..8211b54bb57 --- /dev/null +++ b/app/src/organisms/Desktop/CalibrationPanels/Introduction/index.tsx @@ -0,0 +1,186 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { getLabwareDisplayName } from '@opentrons/shared-data' +import { + Flex, + DIRECTION_COLUMN, + JUSTIFY_SPACE_BETWEEN, + SPACING, + ALIGN_CENTER, + PrimaryButton, + SecondaryButton, + LegacyStyledText, +} from '@opentrons/components' + +import * as Sessions from '/app/redux/sessions' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' +import { ChooseTipRack } from '../ChooseTipRack' + +import { TRASH_BIN_LOAD_NAME } from '../constants' +import { WizardRequiredEquipmentList } from '/app/molecules/WizardRequiredEquipmentList' +import { Body } from './Body' +import { InvalidationWarning } from './InvalidationWarning' + +import type { LabwareDefinition2 } from '@opentrons/shared-data' +import type { CalibrationPanelProps } from '../types' + +const TRASH_BIN = 'Removable black plastic trash bin' + +export function Introduction(props: CalibrationPanelProps): JSX.Element { + const { + tipRack, + calBlock, + sendCommands, + sessionType, + instruments, + supportedCommands, + calInvalidationHandler, + allowChangeTipRack = false, + } = props + const { t } = useTranslation('robot_calibration') + + const [showChooseTipRack, setShowChooseTipRack] = useState(false) + const [chosenTipRack, setChosenTipRack] = useState( + null + ) + + const handleChosenTipRack = (value: LabwareDefinition2 | null): void => { + value != null && setChosenTipRack(value) + } + const uniqueTipRacks = new Set( + instruments?.map(instr => instr.tipRackLoadName) + ) + + let equipmentList: Array<{ loadName: string; displayName: string }> = + uniqueTipRacks.size > 1 + ? instruments?.map(instr => ({ + loadName: instr.tipRackLoadName, + displayName: instr.tipRackDisplay, + })) ?? [] + : [ + { + loadName: tipRack.loadName, + displayName: getLabwareDisplayName(tipRack.definition), + }, + ] + + if (chosenTipRack != null) { + equipmentList = [ + { + loadName: chosenTipRack.parameters.loadName, + displayName: chosenTipRack.metadata.displayName, + }, + ] + } + if (calBlock != null) { + equipmentList = [ + ...equipmentList, + { + loadName: calBlock.loadName, + displayName: getLabwareDisplayName(calBlock.definition), + }, + ] + } else if ( + sessionType === Sessions.SESSION_TYPE_CALIBRATION_HEALTH_CHECK || + sessionType === Sessions.SESSION_TYPE_TIP_LENGTH_CALIBRATION + ) { + equipmentList = [ + ...equipmentList, + { + loadName: TRASH_BIN_LOAD_NAME, + displayName: TRASH_BIN, + }, + ] + } + + const proceed = (): void => { + if ( + (sessionType === Sessions.SESSION_TYPE_DECK_CALIBRATION || + sessionType === Sessions.SESSION_TYPE_TIP_LENGTH_CALIBRATION) && + calInvalidationHandler !== undefined + ) { + calInvalidationHandler() + } + if ( + supportedCommands?.includes(Sessions.sharedCalCommands.LOAD_LABWARE) ?? + false + ) { + sendCommands({ + command: Sessions.sharedCalCommands.LOAD_LABWARE, + data: { tiprackDefinition: chosenTipRack ?? tipRack.definition }, + }) + } else { + sendCommands({ command: Sessions.sharedCalCommands.LOAD_LABWARE }) + } + } + + return showChooseTipRack ? ( + { + setShowChooseTipRack(false) + }} + robotName={props.robotName} + defaultTipracks={props.defaultTipracks} + /> + ) : ( + + + + + {t('before_you_begin')} + + + {(sessionType === Sessions.SESSION_TYPE_DECK_CALIBRATION || + sessionType === Sessions.SESSION_TYPE_TIP_LENGTH_CALIBRATION) && + calInvalidationHandler !== undefined && ( + + )} + + + + + + + + + + {allowChangeTipRack ? ( + { + setShowChooseTipRack(true) + }} + > + {t('change_tip_rack')} + + ) : null} + {t('get_started')} + + + + ) +} diff --git a/app/src/organisms/CalibrationPanels/LoadingState.tsx b/app/src/organisms/Desktop/CalibrationPanels/LoadingState.tsx similarity index 96% rename from app/src/organisms/CalibrationPanels/LoadingState.tsx rename to app/src/organisms/Desktop/CalibrationPanels/LoadingState.tsx index 03e491af459..0327e13d86c 100644 --- a/app/src/organisms/CalibrationPanels/LoadingState.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/LoadingState.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { ALIGN_CENTER, COLORS, diff --git a/app/src/organisms/Desktop/CalibrationPanels/MeasureNozzle.tsx b/app/src/organisms/Desktop/CalibrationPanels/MeasureNozzle.tsx new file mode 100644 index 00000000000..9c2c9762735 --- /dev/null +++ b/app/src/organisms/Desktop/CalibrationPanels/MeasureNozzle.tsx @@ -0,0 +1,185 @@ +/* eslint-disable no-return-assign */ +import { useMemo } from 'react' +import { css } from 'styled-components' +import { useTranslation } from 'react-i18next' +import { + ALIGN_FLEX_END, + ALIGN_STRETCH, + Box, + DIRECTION_COLUMN, + Flex, + JUSTIFY_SPACE_BETWEEN, + PrimaryButton, + SPACING, + LegacyStyledText, +} from '@opentrons/components' + +import { + JogControls, + SMALL_STEP_SIZE_MM, + MEDIUM_STEP_SIZE_MM, +} from '/app/molecules/JogControls' +import * as Sessions from '/app/redux/sessions' +import type { Axis, Sign, StepSize } from '/app/molecules/JogControls/types' +import type { CalibrationPanelProps } from './types' + +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' +import { useConfirmCrashRecovery } from './useConfirmCrashRecovery' +import { formatJogVector } from './utils' +import leftMultiBlockAssetTLC from '/app/assets/videos/tip-length-cal/Left_Multi_CalBlock_NO_TIP_(330x260)REV1.webm' +import leftMultiTrashAsset from '/app/assets/videos/tip-length-cal/Left_Multi_Trash_NO_TIP_(330x260)REV1.webm' +import leftSingleBlockAssetTLC from '/app/assets/videos/tip-length-cal/Left_Single_CalBlock_NO_TIP_(330x260)REV1.webm' +import leftSingleTrashAsset from '/app/assets/videos/tip-length-cal/Left_Single_Trash_NO_TIP_(330x260)REV1.webm' +import rightMultiBlockAssetTLC from '/app/assets/videos/tip-length-cal/Right_Multi_CalBlock_NO_TIP_(330x260)REV1.webm' +import rightMultiTrashAsset from '/app/assets/videos/tip-length-cal/Right_Multi_Trash_NO_TIP_(330x260)REV1.webm' +import rightSingleBlockAssetTLC from '/app/assets/videos/tip-length-cal/Right_Single_CalBlock_NO_TIP_(330x260)REV1.webm' +import rightSingleTrashAsset from '/app/assets/videos/tip-length-cal/Right_Single_Trash_NO_TIP_(330x260)REV1.webm' +import leftMultiBlockAssetHealth from '/app/assets/videos/health-check/Left_Multi_CalBlock_NO_TIP_(330x260)REV2.webm' +import rightMultiBlockAssetHealth from '/app/assets/videos/health-check/Right_Multi_CalBlock_NO_TIP_(330x260)REV2.webm' +import leftSingleBlockAssetHealth from '/app/assets/videos/health-check/Left_Single_CalBlock_NO_TIP_(330x260)REV2.webm' +import rightSingleBlockAssetHealth from '/app/assets/videos/health-check/Right_Single_CalBlock_NO_TIP_(330x260)REV2.webm' +import type { Mount } from '@opentrons/components' + +const assetMapTrash = { + left: { + multi: leftMultiTrashAsset, + single: leftSingleTrashAsset, + }, + right: { + multi: rightMultiTrashAsset, + single: rightSingleTrashAsset, + }, +} + +const assetMapBlock: { + [sessionType in Sessions.SessionType]?: { + [mount in Mount]: { [channels in 'multi' | 'single']: string } + } +} = { + [Sessions.SESSION_TYPE_TIP_LENGTH_CALIBRATION]: { + left: { + multi: leftMultiBlockAssetTLC, + single: leftSingleBlockAssetTLC, + }, + right: { + multi: rightMultiBlockAssetTLC, + single: rightSingleBlockAssetTLC, + }, + }, + [Sessions.SESSION_TYPE_CALIBRATION_HEALTH_CHECK]: { + left: { + multi: leftMultiBlockAssetHealth, + single: leftSingleBlockAssetHealth, + }, + right: { + multi: rightMultiBlockAssetHealth, + single: rightSingleBlockAssetHealth, + }, + }, +} + +export function MeasureNozzle(props: CalibrationPanelProps): JSX.Element { + const { t } = useTranslation('robot_calibration') + const { sendCommands, calBlock, mount, isMulti, sessionType } = props + + const demoAsset = useMemo( + () => + calBlock != null + ? assetMapBlock[sessionType]?.[mount]?.[isMulti ? 'multi' : 'single'] + : assetMapTrash[mount]?.[isMulti ? 'multi' : 'single'], + [mount, isMulti, calBlock, sessionType] + ) + + const jog = (axis: Axis, dir: Sign, step: StepSize): void => { + sendCommands({ + command: Sessions.sharedCalCommands.JOG, + data: { + vector: formatJogVector(axis, dir, step), + }, + }) + } + + const isHealthCheck = + sessionType === Sessions.SESSION_TYPE_CALIBRATION_HEALTH_CHECK + + const proceed = (): void => { + isHealthCheck + ? sendCommands({ command: Sessions.sharedCalCommands.MOVE_TO_TIP_RACK }) + : sendCommands( + { command: Sessions.sharedCalCommands.SAVE_OFFSET }, + { command: Sessions.sharedCalCommands.MOVE_TO_TIP_RACK } + ) + } + + const [confirmLink, crashRecoveryConfirmation] = useConfirmCrashRecovery( + props + ) + + let titleText = + calBlock != null + ? t('calibrate_z_axis_on_block') + : t('calibrate_z_axis_on_trash') + if (isHealthCheck) { + titleText = + calBlock != null ? t('check_z_axis_on_block') : t('check_z_axis_on_trash') + } + + return ( + crashRecoveryConfirmation ?? ( + + + + + {titleText} + + + {calBlock != null + ? t('jog_nozzle_to_block', { slotName: calBlock.slot }) + : t('jog_nozzle_to_trash')} + + + + + + + + + {confirmLink} + + + + + {t('confirm_placement')} + + + + ) + ) +} diff --git a/app/src/organisms/Desktop/CalibrationPanels/MeasureTip.tsx b/app/src/organisms/Desktop/CalibrationPanels/MeasureTip.tsx new file mode 100644 index 00000000000..9eac47f89cb --- /dev/null +++ b/app/src/organisms/Desktop/CalibrationPanels/MeasureTip.tsx @@ -0,0 +1,185 @@ +import { useMemo } from 'react' +import { css } from 'styled-components' +import { useTranslation } from 'react-i18next' +import { + ALIGN_FLEX_END, + ALIGN_STRETCH, + Box, + DIRECTION_COLUMN, + Flex, + JUSTIFY_SPACE_BETWEEN, + PrimaryButton, + SPACING, + LegacyStyledText, +} from '@opentrons/components' + +import * as Sessions from '/app/redux/sessions' +import { + JogControls, + MEDIUM_STEP_SIZE_MM, + SMALL_STEP_SIZE_MM, +} from '/app/molecules/JogControls' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' +import { useConfirmCrashRecovery } from './useConfirmCrashRecovery' +import { formatJogVector } from './utils' + +import leftMultiBlockAssetTLC from '/app/assets/videos/tip-length-cal/Left_Multi_CalBlock_WITH_TIP_(330x260)REV1.webm' +import leftMultiTrashAsset from '/app/assets/videos/tip-length-cal/Left_Multi_Trash_WITH_TIP_(330x260)REV1.webm' +import leftSingleBlockAssetTLC from '/app/assets/videos/tip-length-cal/Left_Single_CalBlock_WITH_TIP_(330x260)REV1.webm' +import leftSingleTrashAsset from '/app/assets/videos/tip-length-cal/Left_Single_Trash_WITH_TIP_(330x260)REV1.webm' +import rightMultiBlockAssetTLC from '/app/assets/videos/tip-length-cal/Right_Multi_CalBlock_WITH_TIP_(330x260)REV1.webm' +import rightMultiTrashAsset from '/app/assets/videos/tip-length-cal/Right_Multi_Trash_WITH_TIP_(330x260)REV1.webm' +import rightSingleBlockAssetTLC from '/app/assets/videos/tip-length-cal/Right_Single_CalBlock_WITH_TIP_(330x260)REV1.webm' +import rightSingleTrashAsset from '/app/assets/videos/tip-length-cal/Right_Single_Trash_WITH_TIP_(330x260)REV1.webm' +import leftMultiBlockAssetHealth from '/app/assets/videos/health-check/Left_Multi_CalBlock_WITH_TIP_(330x260)REV2.webm' +import rightMultiBlockAssetHealth from '/app/assets/videos/health-check/Right_Multi_CalBlock_WITH_TIP_(330x260)REV2.webm' +import leftSingleBlockAssetHealth from '/app/assets/videos/health-check/Left_Single_CalBlock_WITH_TIP_(330x260)REV2.webm' +import rightSingleBlockAssetHealth from '/app/assets/videos/health-check/Right_Single_CalBlock_WITH_TIP_(330x260)REV2.webm' + +import type { Mount } from '@opentrons/components' +import type { Axis, Sign, StepSize } from '/app/molecules/JogControls/types' +import type { CalibrationPanelProps } from './types' + +const assetMapTrash = { + left: { + multi: leftMultiTrashAsset, + single: leftSingleTrashAsset, + }, + right: { + multi: rightMultiTrashAsset, + single: rightSingleTrashAsset, + }, +} + +const assetMapBlock: { + [sessionType in Sessions.SessionType]?: { + [mount in Mount]: { [channels in 'multi' | 'single']: string } + } +} = { + [Sessions.SESSION_TYPE_TIP_LENGTH_CALIBRATION]: { + left: { + multi: leftMultiBlockAssetTLC, + single: leftSingleBlockAssetTLC, + }, + right: { + multi: rightMultiBlockAssetTLC, + single: rightSingleBlockAssetTLC, + }, + }, + [Sessions.SESSION_TYPE_CALIBRATION_HEALTH_CHECK]: { + left: { + multi: leftMultiBlockAssetHealth, + single: leftSingleBlockAssetHealth, + }, + right: { + multi: rightMultiBlockAssetHealth, + single: rightSingleBlockAssetHealth, + }, + }, +} + +export function MeasureTip(props: CalibrationPanelProps): JSX.Element { + const { t } = useTranslation('robot_calibration') + const { sendCommands, calBlock, isMulti, mount, sessionType } = props + + const demoAsset = useMemo( + () => + calBlock != null + ? assetMapBlock[sessionType]?.[mount]?.[isMulti ? 'multi' : 'single'] + : assetMapTrash[mount]?.[isMulti ? 'multi' : 'single'], + [mount, isMulti, calBlock, sessionType] + ) + + const jog = (axis: Axis, dir: Sign, step: StepSize): void => { + sendCommands({ + command: Sessions.sharedCalCommands.JOG, + data: { + vector: formatJogVector(axis, dir, step), + }, + }) + } + + const isHealthCheck = + sessionType === Sessions.SESSION_TYPE_CALIBRATION_HEALTH_CHECK + + const proceed = (): void => { + isHealthCheck + ? sendCommands( + { command: Sessions.checkCommands.COMPARE_POINT }, + { command: Sessions.sharedCalCommands.MOVE_TO_DECK } + ) + : sendCommands( + { command: Sessions.sharedCalCommands.SAVE_OFFSET }, + { command: Sessions.sharedCalCommands.MOVE_TO_TIP_RACK } + ) + } + + const [confirmLink, crashRecoveryConfirmation] = useConfirmCrashRecovery( + props + ) + + let titleText = + calBlock != null ? t('calibrate_tip_on_block') : t('calibrate_tip_on_trash') + if (isHealthCheck) { + titleText = + calBlock != null ? t('check_tip_on_block') : t('check_tip_on_trash') + } + return ( + crashRecoveryConfirmation ?? ( + + + + + {titleText} + + + {calBlock != null + ? t('jog_nozzle_to_block', { slotName: calBlock.slot }) + : t('jog_nozzle_to_trash')} + + + + + + + + + {confirmLink} + + + + + {t('confirm_placement')} + + + + ) + ) +} diff --git a/app/src/organisms/CalibrationPanels/SaveXYPoint.tsx b/app/src/organisms/Desktop/CalibrationPanels/SaveXYPoint.tsx similarity index 81% rename from app/src/organisms/CalibrationPanels/SaveXYPoint.tsx rename to app/src/organisms/Desktop/CalibrationPanels/SaveXYPoint.tsx index 3717413bd91..8fda2535ee8 100644 --- a/app/src/organisms/CalibrationPanels/SaveXYPoint.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/SaveXYPoint.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useMemo } from 'react' import { css } from 'styled-components' import { useTranslation } from 'react-i18next' import { @@ -13,38 +13,38 @@ import { LegacyStyledText, } from '@opentrons/components' -import { useLogger } from '../../logger' -import * as Sessions from '../../redux/sessions' +import { useLogger } from '/app/logger' +import * as Sessions from '/app/redux/sessions' import { JogControls, MEDIUM_STEP_SIZE_MM, SMALL_STEP_SIZE_MM, -} from '../../molecules/JogControls' +} from '/app/molecules/JogControls' import { formatJogVector } from './utils' import { useConfirmCrashRecovery } from './useConfirmCrashRecovery' -import { NeedHelpLink } from './NeedHelpLink' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' -import slot1LeftMultiDemoAsset from '../../assets/videos/cal-movement/SLOT_1_LEFT_MULTI_X-Y.webm' -import slot1LeftSingleDemoAsset from '../../assets/videos/cal-movement/SLOT_1_LEFT_SINGLE_X-Y.webm' -import slot1RightMultiDemoAsset from '../../assets/videos/cal-movement/SLOT_1_RIGHT_MULTI_X-Y.webm' -import slot1RightSingleDemoAsset from '../../assets/videos/cal-movement/SLOT_1_RIGHT_SINGLE_X-Y.webm' -import slot3LeftMultiDemoAsset from '../../assets/videos/cal-movement/SLOT_3_LEFT_MULTI_X-Y.webm' -import slot3LeftSingleDemoAsset from '../../assets/videos/cal-movement/SLOT_3_LEFT_SINGLE_X-Y.webm' -import slot3RightMultiDemoAsset from '../../assets/videos/cal-movement/SLOT_3_RIGHT_MULTI_X-Y.webm' -import slot3RightSingleDemoAsset from '../../assets/videos/cal-movement/SLOT_3_RIGHT_SINGLE_X-Y.webm' -import slot7LeftMultiDemoAsset from '../../assets/videos/cal-movement/SLOT_7_LEFT_MULTI_X-Y.webm' -import slot7LeftSingleDemoAsset from '../../assets/videos/cal-movement/SLOT_7_LEFT_SINGLE_X-Y.webm' -import slot7RightMultiDemoAsset from '../../assets/videos/cal-movement/SLOT_7_RIGHT_MULTI_X-Y.webm' -import slot7RightSingleDemoAsset from '../../assets/videos/cal-movement/SLOT_7_RIGHT_SINGLE_X-Y.webm' +import slot1LeftMultiDemoAsset from '/app/assets/videos/cal-movement/SLOT_1_LEFT_MULTI_X-Y.webm' +import slot1LeftSingleDemoAsset from '/app/assets/videos/cal-movement/SLOT_1_LEFT_SINGLE_X-Y.webm' +import slot1RightMultiDemoAsset from '/app/assets/videos/cal-movement/SLOT_1_RIGHT_MULTI_X-Y.webm' +import slot1RightSingleDemoAsset from '/app/assets/videos/cal-movement/SLOT_1_RIGHT_SINGLE_X-Y.webm' +import slot3LeftMultiDemoAsset from '/app/assets/videos/cal-movement/SLOT_3_LEFT_MULTI_X-Y.webm' +import slot3LeftSingleDemoAsset from '/app/assets/videos/cal-movement/SLOT_3_LEFT_SINGLE_X-Y.webm' +import slot3RightMultiDemoAsset from '/app/assets/videos/cal-movement/SLOT_3_RIGHT_MULTI_X-Y.webm' +import slot3RightSingleDemoAsset from '/app/assets/videos/cal-movement/SLOT_3_RIGHT_SINGLE_X-Y.webm' +import slot7LeftMultiDemoAsset from '/app/assets/videos/cal-movement/SLOT_7_LEFT_MULTI_X-Y.webm' +import slot7LeftSingleDemoAsset from '/app/assets/videos/cal-movement/SLOT_7_LEFT_SINGLE_X-Y.webm' +import slot7RightMultiDemoAsset from '/app/assets/videos/cal-movement/SLOT_7_RIGHT_MULTI_X-Y.webm' +import slot7RightSingleDemoAsset from '/app/assets/videos/cal-movement/SLOT_7_RIGHT_SINGLE_X-Y.webm' -import type { Axis, Sign, StepSize } from '../../molecules/JogControls/types' +import type { Axis, Sign, StepSize } from '/app/molecules/JogControls/types' import type { CalibrationPanelProps } from './types' import type { SessionType, CalibrationSessionStep, SessionCommandString, CalibrationLabware, -} from '../../redux/sessions/types' +} from '/app/redux/sessions/types' import type { Mount } from '@opentrons/components' const assetMap: Record< @@ -143,7 +143,7 @@ export function SaveXYPoint(props: CalibrationPanelProps): JSX.Element | null { const { slotNumber, moveCommand } = contentsBySessionTypeByCurrentStep[sessionType]?.[currentStep] ?? {} - const demoAsset = React.useMemo( + const demoAsset = useMemo( () => slotNumber != null ? assetMap[slotNumber][mount][isMulti ? 'multi' : 'single'] diff --git a/app/src/organisms/CalibrationPanels/SaveZPoint.tsx b/app/src/organisms/Desktop/CalibrationPanels/SaveZPoint.tsx similarity index 82% rename from app/src/organisms/CalibrationPanels/SaveZPoint.tsx rename to app/src/organisms/Desktop/CalibrationPanels/SaveZPoint.tsx index bc669b9f735..094b94a2025 100644 --- a/app/src/organisms/CalibrationPanels/SaveZPoint.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/SaveZPoint.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useMemo } from 'react' import { css } from 'styled-components' import { Trans, useTranslation } from 'react-i18next' import { @@ -13,23 +13,24 @@ import { LegacyStyledText, } from '@opentrons/components' -import * as Sessions from '../../redux/sessions' +import * as Sessions from '/app/redux/sessions' import { JogControls, MEDIUM_STEP_SIZE_MM, SMALL_STEP_SIZE_MM, VERTICAL_PLANE, -} from '../../molecules/JogControls' +} from '/app/molecules/JogControls' import { formatJogVector } from './utils' import { useConfirmCrashRecovery } from './useConfirmCrashRecovery' -import { NeedHelpLink } from './NeedHelpLink' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' -import slot5LeftMultiDemoAsset from '../../assets/videos/cal-movement/SLOT_5_LEFT_MULTI_Z.webm' -import slot5LeftSingleDemoAsset from '../../assets/videos/cal-movement/SLOT_5_LEFT_SINGLE_Z.webm' -import slot5RightMultiDemoAsset from '../../assets/videos/cal-movement/SLOT_5_RIGHT_MULTI_Z.webm' -import slot5RightSingleDemoAsset from '../../assets/videos/cal-movement/SLOT_5_RIGHT_SINGLE_Z.webm' +import slot5LeftMultiDemoAsset from '/app/assets/videos/cal-movement/SLOT_5_LEFT_MULTI_Z.webm' +import slot5LeftSingleDemoAsset from '/app/assets/videos/cal-movement/SLOT_5_LEFT_SINGLE_Z.webm' +import slot5RightMultiDemoAsset from '/app/assets/videos/cal-movement/SLOT_5_RIGHT_MULTI_Z.webm' +import slot5RightSingleDemoAsset from '/app/assets/videos/cal-movement/SLOT_5_RIGHT_SINGLE_Z.webm' -import type { Axis, Sign, StepSize } from '../../molecules/JogControls/types' +import type { MouseEventHandler } from 'react' +import type { Axis, Sign, StepSize } from '/app/molecules/JogControls/types' import type { CalibrationPanelProps } from './types' const assetMap = { @@ -46,7 +47,7 @@ const assetMap = { export function SaveZPoint(props: CalibrationPanelProps): JSX.Element { const { t } = useTranslation('robot_calibration') const { isMulti, mount, sendCommands, sessionType } = props - const demoAsset = React.useMemo( + const demoAsset = useMemo( () => mount && assetMap[mount][isMulti ? 'multi' : 'single'], [mount, isMulti] ) @@ -62,7 +63,7 @@ export function SaveZPoint(props: CalibrationPanelProps): JSX.Element { const isHealthCheck = sessionType === Sessions.SESSION_TYPE_CALIBRATION_HEALTH_CHECK - const proceed: React.MouseEventHandler = _event => { + const proceed: MouseEventHandler = _event => { isHealthCheck ? sendCommands( { command: Sessions.checkCommands.COMPARE_POINT }, diff --git a/app/src/organisms/CalibrationPanels/TipConfirmation.tsx b/app/src/organisms/Desktop/CalibrationPanels/TipConfirmation.tsx similarity index 93% rename from app/src/organisms/CalibrationPanels/TipConfirmation.tsx rename to app/src/organisms/Desktop/CalibrationPanels/TipConfirmation.tsx index e8bf4f0f4fc..c4d094eadb7 100644 --- a/app/src/organisms/CalibrationPanels/TipConfirmation.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/TipConfirmation.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { css } from 'styled-components' import { DIRECTION_COLUMN, @@ -11,14 +10,14 @@ import { } from '@opentrons/components' import { useTranslation } from 'react-i18next' -import * as Sessions from '../../redux/sessions' -import { NeedHelpLink } from './NeedHelpLink' +import * as Sessions from '/app/redux/sessions' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' import type { CalibrationPanelProps } from './types' import type { SessionType, SessionCommandString, -} from '../../redux/sessions/types' +} from '/app/redux/sessions/types' const CAPITALIZE_FIRST_LETTER_STYLE = css` &:first-letter { diff --git a/app/src/organisms/CalibrationPanels/TipPickUp.tsx b/app/src/organisms/Desktop/CalibrationPanels/TipPickUp.tsx similarity index 86% rename from app/src/organisms/CalibrationPanels/TipPickUp.tsx rename to app/src/organisms/Desktop/CalibrationPanels/TipPickUp.tsx index 6692c7cfc39..e375398c8cf 100644 --- a/app/src/organisms/CalibrationPanels/TipPickUp.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/TipPickUp.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { css } from 'styled-components' import { Trans, useTranslation } from 'react-i18next' import { @@ -13,17 +12,17 @@ import { LegacyStyledText, } from '@opentrons/components' -import * as Sessions from '../../redux/sessions' -import type { Axis, Sign, StepSize } from '../../molecules/JogControls/types' -import { JogControls } from '../../molecules/JogControls' +import * as Sessions from '/app/redux/sessions' +import type { Axis, Sign, StepSize } from '/app/molecules/JogControls/types' +import { JogControls } from '/app/molecules/JogControls' import type { CalibrationPanelProps } from './types' import { formatJogVector } from './utils' -import { NeedHelpLink } from './NeedHelpLink' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' import { useConfirmCrashRecovery } from './useConfirmCrashRecovery' -import multiDemoAsset from '../../assets/videos/tip-pick-up/A1_Multi_Channel_REV1.webm' -import singleDemoAsset from '../../assets/videos/tip-pick-up/A1_Single_Channel_REV1.webm' +import multiDemoAsset from '/app/assets/videos/tip-pick-up/A1_Multi_Channel_REV1.webm' +import singleDemoAsset from '/app/assets/videos/tip-pick-up/A1_Single_Channel_REV1.webm' const ASSET_MAP = { multi: multiDemoAsset, diff --git a/app/src/organisms/CalibrationPanels/__tests__/ChooseTipRack.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/__tests__/ChooseTipRack.test.tsx similarity index 77% rename from app/src/organisms/CalibrationPanels/__tests__/ChooseTipRack.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/__tests__/ChooseTipRack.test.tsx index f867ca46298..4bb1cecd0ef 100644 --- a/app/src/organisms/CalibrationPanels/__tests__/ChooseTipRack.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/__tests__/ChooseTipRack.test.tsx @@ -1,31 +1,31 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect, beforeEach } from 'vitest' import { usePipettesQuery } from '@opentrons/react-api-client' import { LEFT } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockAttachedPipette } from '../../../redux/pipettes/__fixtures__' -import { mockDeckCalTipRack } from '../../../redux/sessions/__fixtures__' -import { mockTipRackDefinition } from '../../../redux/custom-labware/__fixtures__' -import { Select } from '../../../atoms/SelectField/Select' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockAttachedPipette } from '/app/redux/pipettes/__fixtures__' +import { mockDeckCalTipRack } from '/app/redux/sessions/__fixtures__' +import { mockTipRackDefinition } from '/app/redux/custom-labware/__fixtures__' +import { Select } from '/app/atoms/SelectField/Select' import { getCalibrationForPipette, getTipLengthForPipetteAndTiprack, getTipLengthCalibrations, -} from '../../../redux/calibration' -import { getCustomTipRackDefinitions } from '../../../redux/custom-labware' +} from '/app/redux/calibration' +import { getCustomTipRackDefinitions } from '/app/redux/custom-labware' import { ChooseTipRack } from '../ChooseTipRack' -import type { AttachedPipettesByMount } from '../../../redux/pipettes/types' +import type { AttachedPipettesByMount } from '/app/redux/pipettes/types' vi.mock('@opentrons/react-api-client') -vi.mock('../../../redux/pipettes/selectors') -vi.mock('../../../redux/calibration') -vi.mock('../../../redux/custom-labware/selectors') -vi.mock('../../../atoms/SelectField/Select') +vi.mock('/app/redux/pipettes/selectors') +vi.mock('/app/redux/calibration') +vi.mock('/app/redux/custom-labware/selectors') +vi.mock('/app/atoms/SelectField/Select') const mockAttachedPipettes: AttachedPipettesByMount = { left: mockAttachedPipette, diff --git a/app/src/organisms/CalibrationPanels/__tests__/ChosenTipRackRender.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/__tests__/ChosenTipRackRender.test.tsx similarity index 81% rename from app/src/organisms/CalibrationPanels/__tests__/ChosenTipRackRender.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/__tests__/ChosenTipRackRender.test.tsx index ff57669651d..5eb7fa1db8b 100644 --- a/app/src/organisms/CalibrationPanels/__tests__/ChosenTipRackRender.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/__tests__/ChosenTipRackRender.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { it, describe, beforeEach } from 'vitest' import { screen } from '@testing-library/react' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { ChosenTipRackRender } from '../ChosenTipRackRender' -import type { SelectOption } from '../../../atoms/SelectField/Select' +import type { SelectOption } from '/app/atoms/SelectField/Select' const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/CalibrationPanels/__tests__/CompleteConfirmation.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/__tests__/CompleteConfirmation.test.tsx similarity index 90% rename from app/src/organisms/CalibrationPanels/__tests__/CompleteConfirmation.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/__tests__/CompleteConfirmation.test.tsx index 29b757b8d88..99dc73310d1 100644 --- a/app/src/organisms/CalibrationPanels/__tests__/CompleteConfirmation.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/__tests__/CompleteConfirmation.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { CompleteConfirmation } from '../CompleteConfirmation' diff --git a/app/src/organisms/CalibrationPanels/__tests__/ConfirmCrashRecovery.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/__tests__/ConfirmCrashRecovery.test.tsx similarity index 90% rename from app/src/organisms/CalibrationPanels/__tests__/ConfirmCrashRecovery.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/__tests__/ConfirmCrashRecovery.test.tsx index 8f56a66a7c5..53fb8559e55 100644 --- a/app/src/organisms/CalibrationPanels/__tests__/ConfirmCrashRecovery.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/__tests__/ConfirmCrashRecovery.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ConfirmCrashRecovery } from '../ConfirmCrashRecovery' describe('ConfirmCrashRecovery', () => { diff --git a/app/src/organisms/CalibrationPanels/__tests__/ConfirmExit.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/__tests__/ConfirmExit.test.tsx similarity index 90% rename from app/src/organisms/CalibrationPanels/__tests__/ConfirmExit.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/__tests__/ConfirmExit.test.tsx index b28c224329a..086c122e8bf 100644 --- a/app/src/organisms/CalibrationPanels/__tests__/ConfirmExit.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/__tests__/ConfirmExit.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ConfirmExit } from '../ConfirmExit' diff --git a/app/src/organisms/CalibrationPanels/__tests__/DeckSetup.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/__tests__/DeckSetup.test.tsx similarity index 93% rename from app/src/organisms/CalibrationPanels/__tests__/DeckSetup.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/__tests__/DeckSetup.test.tsx index 0533e492232..bfa641e6b26 100644 --- a/app/src/organisms/CalibrationPanels/__tests__/DeckSetup.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/__tests__/DeckSetup.test.tsx @@ -1,19 +1,19 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockDeckCalTipRack, mockRobotCalibrationCheckSessionDetails, mockTipLengthCalBlock, -} from '../../../redux/sessions/__fixtures__' -import * as Sessions from '../../../redux/sessions' +} from '/app/redux/sessions/__fixtures__' +import * as Sessions from '/app/redux/sessions' import { DeckSetup } from '../DeckSetup' import type { getDeckDefinitions } from '@opentrons/shared-data' -vi.mock('../../../assets/labware/getLabware') +vi.mock('/app/assets/labware/getLabware') vi.mock('@opentrons/shared-data', async importOriginal => { const actual = await importOriginal() return { diff --git a/app/src/organisms/CalibrationPanels/__tests__/MeasureNozzle.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/__tests__/MeasureNozzle.test.tsx similarity index 92% rename from app/src/organisms/CalibrationPanels/__tests__/MeasureNozzle.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/__tests__/MeasureNozzle.test.tsx index 9bd2e580969..d0de251b0ad 100644 --- a/app/src/organisms/CalibrationPanels/__tests__/MeasureNozzle.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/__tests__/MeasureNozzle.test.tsx @@ -1,14 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockTipLengthCalBlock, mockTipLengthTipRack, -} from '../../../redux/sessions/__fixtures__' -import * as Sessions from '../../../redux/sessions' +} from '/app/redux/sessions/__fixtures__' +import * as Sessions from '/app/redux/sessions' import { MeasureNozzle } from '../MeasureNozzle' describe('MeasureNozzle', () => { diff --git a/app/src/organisms/CalibrationPanels/__tests__/MeasureTip.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/__tests__/MeasureTip.test.tsx similarity index 92% rename from app/src/organisms/CalibrationPanels/__tests__/MeasureTip.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/__tests__/MeasureTip.test.tsx index 60787ebdefd..70d8ab54a6b 100644 --- a/app/src/organisms/CalibrationPanels/__tests__/MeasureTip.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/__tests__/MeasureTip.test.tsx @@ -1,14 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockTipLengthCalBlock, mockTipLengthTipRack, -} from '../../../redux/sessions/__fixtures__' -import * as Sessions from '../../../redux/sessions' +} from '/app/redux/sessions/__fixtures__' +import * as Sessions from '/app/redux/sessions' import { MeasureTip } from '../MeasureTip' diff --git a/app/src/organisms/CalibrationPanels/__tests__/SaveXYPoint.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/__tests__/SaveXYPoint.test.tsx similarity index 95% rename from app/src/organisms/CalibrationPanels/__tests__/SaveXYPoint.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/__tests__/SaveXYPoint.test.tsx index 917c22b4c9e..3d7dbda32a9 100644 --- a/app/src/organisms/CalibrationPanels/__tests__/SaveXYPoint.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/__tests__/SaveXYPoint.test.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' -import { mockDeckCalTipRack } from '../../../redux/sessions/__fixtures__' -import * as Sessions from '../../../redux/sessions' +import { mockDeckCalTipRack } from '/app/redux/sessions/__fixtures__' +import * as Sessions from '/app/redux/sessions' import { SaveXYPoint } from '../SaveXYPoint' describe('SaveXYPoint', () => { diff --git a/app/src/organisms/CalibrationPanels/__tests__/SaveZPoint.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/__tests__/SaveZPoint.test.tsx similarity index 96% rename from app/src/organisms/CalibrationPanels/__tests__/SaveZPoint.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/__tests__/SaveZPoint.test.tsx index 2dceb85d562..36f04c1ebb2 100644 --- a/app/src/organisms/CalibrationPanels/__tests__/SaveZPoint.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/__tests__/SaveZPoint.test.tsx @@ -1,15 +1,15 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockDeckCalTipRack, mockTipLengthCalBlock, -} from '../../../redux/sessions/__fixtures__' -import * as Sessions from '../../../redux/sessions' +} from '/app/redux/sessions/__fixtures__' +import * as Sessions from '/app/redux/sessions' import { SaveZPoint } from '../SaveZPoint' describe('SaveZPoint', () => { diff --git a/app/src/organisms/CalibrationPanels/__tests__/TipConfirmation.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/__tests__/TipConfirmation.test.tsx similarity index 91% rename from app/src/organisms/CalibrationPanels/__tests__/TipConfirmation.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/__tests__/TipConfirmation.test.tsx index d6d1fa5e438..f9eee22d78b 100644 --- a/app/src/organisms/CalibrationPanels/__tests__/TipConfirmation.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/__tests__/TipConfirmation.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockDeckCalTipRack } from '../../../redux/sessions/__fixtures__' -import * as Sessions from '../../../redux/sessions' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockDeckCalTipRack } from '/app/redux/sessions/__fixtures__' +import * as Sessions from '/app/redux/sessions' import { TipConfirmation } from '../TipConfirmation' describe('TipConfirmation', () => { diff --git a/app/src/organisms/CalibrationPanels/__tests__/TipPickUp.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/__tests__/TipPickUp.test.tsx similarity index 88% rename from app/src/organisms/CalibrationPanels/__tests__/TipPickUp.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/__tests__/TipPickUp.test.tsx index 30406213d98..b6836cf96fb 100644 --- a/app/src/organisms/CalibrationPanels/__tests__/TipPickUp.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/__tests__/TipPickUp.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockDeckCalTipRack } from '../../../redux/sessions/__fixtures__' -import * as Sessions from '../../../redux/sessions' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockDeckCalTipRack } from '/app/redux/sessions/__fixtures__' +import * as Sessions from '/app/redux/sessions' import { TipPickUp } from '../TipPickUp' describe('TipPickUp', () => { diff --git a/app/src/organisms/CalibrationPanels/__tests__/useConfirmCrashRecovery.test.tsx b/app/src/organisms/Desktop/CalibrationPanels/__tests__/useConfirmCrashRecovery.test.tsx similarity index 93% rename from app/src/organisms/CalibrationPanels/__tests__/useConfirmCrashRecovery.test.tsx rename to app/src/organisms/Desktop/CalibrationPanels/__tests__/useConfirmCrashRecovery.test.tsx index 6f91202bd04..9e385a2c320 100644 --- a/app/src/organisms/CalibrationPanels/__tests__/useConfirmCrashRecovery.test.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/__tests__/useConfirmCrashRecovery.test.tsx @@ -1,19 +1,18 @@ -import * as React from 'react' import { fireEvent, renderHook, screen } from '@testing-library/react' import { I18nextProvider } from 'react-i18next' import { vi, it, describe, expect } from 'vitest' import { LEFT } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { useConfirmCrashRecovery } from '../useConfirmCrashRecovery' -import { mockCalibrationCheckLabware } from '../../../redux/sessions/__fixtures__' +import { mockCalibrationCheckLabware } from '/app/redux/sessions/__fixtures__' import { DECK_STEP_JOGGING_TO_DECK, SESSION_TYPE_DECK_CALIBRATION, sharedCalCommands, -} from '../../../redux/sessions' +} from '/app/redux/sessions' describe('useConfirmCrashRecovery', () => { const mockSendCommands = vi.fn() diff --git a/app/src/organisms/CalibrationPanels/constants.ts b/app/src/organisms/Desktop/CalibrationPanels/constants.ts similarity index 100% rename from app/src/organisms/CalibrationPanels/constants.ts rename to app/src/organisms/Desktop/CalibrationPanels/constants.ts diff --git a/app/src/organisms/Desktop/CalibrationPanels/index.ts b/app/src/organisms/Desktop/CalibrationPanels/index.ts new file mode 100644 index 00000000000..735b87cc473 --- /dev/null +++ b/app/src/organisms/Desktop/CalibrationPanels/index.ts @@ -0,0 +1,12 @@ +export { Introduction } from './Introduction' +export { DeckSetup } from './DeckSetup' +export { TipPickUp } from './TipPickUp' +export { TipConfirmation } from './TipConfirmation' +export { MeasureNozzle } from './MeasureNozzle' +export { MeasureTip } from './MeasureTip' +export { SaveZPoint } from './SaveZPoint' +export { SaveXYPoint } from './SaveXYPoint' +export { CompleteConfirmation } from './CompleteConfirmation' +export { LoadingState } from './LoadingState' +export { ConfirmExit } from './ConfirmExit' +export * from './constants' diff --git a/app/src/organisms/CalibrationPanels/styles.module.css b/app/src/organisms/Desktop/CalibrationPanels/styles.module.css similarity index 100% rename from app/src/organisms/CalibrationPanels/styles.module.css rename to app/src/organisms/Desktop/CalibrationPanels/styles.module.css diff --git a/app/src/organisms/Desktop/CalibrationPanels/types.ts b/app/src/organisms/Desktop/CalibrationPanels/types.ts new file mode 100644 index 00000000000..5f6ce119dfb --- /dev/null +++ b/app/src/organisms/Desktop/CalibrationPanels/types.ts @@ -0,0 +1,36 @@ +import type { + SessionCommandParams, + SessionType, + SessionCommandString, + CalibrationSessionStep, + CalibrationLabware, + CalibrationCheckInstrument, + CalibrationCheckComparisonByPipette, +} from '/app/redux/sessions/types' + +import type { LabwareDefinition2 } from '@opentrons/shared-data' +import type { Mount } from '/app/redux/pipettes/types' + +// TODO (lc 10-20-2020) Given there are lots of optional +// keys here now we should split these panel props out +// into different session types and combine them into +// a union object +export interface CalibrationPanelProps { + sendCommands: (...params: SessionCommandParams[]) => void + cleanUpAndExit: () => void + tipRack: CalibrationLabware + isMulti: boolean + mount: Mount + currentStep: CalibrationSessionStep + sessionType: SessionType + calBlock?: CalibrationLabware | null + checkBothPipettes?: boolean | null + instruments?: CalibrationCheckInstrument[] | null + comparisonsByPipette?: CalibrationCheckComparisonByPipette | null + activePipette?: CalibrationCheckInstrument + robotName?: string | null + supportedCommands?: SessionCommandString[] | null + defaultTipracks?: LabwareDefinition2[] | null + calInvalidationHandler?: () => void + allowChangeTipRack?: boolean +} diff --git a/app/src/organisms/CalibrationPanels/useConfirmCrashRecovery.tsx b/app/src/organisms/Desktop/CalibrationPanels/useConfirmCrashRecovery.tsx similarity index 89% rename from app/src/organisms/CalibrationPanels/useConfirmCrashRecovery.tsx rename to app/src/organisms/Desktop/CalibrationPanels/useConfirmCrashRecovery.tsx index 8cbcf74c8bc..e4b9bb2c13f 100644 --- a/app/src/organisms/CalibrationPanels/useConfirmCrashRecovery.tsx +++ b/app/src/organisms/Desktop/CalibrationPanels/useConfirmCrashRecovery.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { @@ -10,7 +10,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import * as Sessions from '../../redux/sessions' +import * as Sessions from '/app/redux/sessions' import { ConfirmCrashRecovery } from './ConfirmCrashRecovery' import type { CalibrationPanelProps } from './types' @@ -20,7 +20,7 @@ export function useConfirmCrashRecovery( ): [link: JSX.Element, confirmation: JSX.Element | null] { const { t } = useTranslation('robot_calibration') const { sendCommands } = props - const [showModal, setShowModal] = React.useState(false) + const [showModal, setShowModal] = useState(false) const doStartOver = (): void => { sendCommands({ command: Sessions.sharedCalCommands.INVALIDATE_LAST_ACTION }) diff --git a/app/src/organisms/Desktop/CalibrationPanels/utils.ts b/app/src/organisms/Desktop/CalibrationPanels/utils.ts new file mode 100644 index 00000000000..2bf8d69b62b --- /dev/null +++ b/app/src/organisms/Desktop/CalibrationPanels/utils.ts @@ -0,0 +1,25 @@ +import { format } from 'date-fns' +import type { Axis } from '/app/molecules/JogControls/types' +import type { VectorTuple } from '/app/redux/sessions/types' + +const ORDERED_AXES: [Axis, Axis, Axis] = ['x', 'y', 'z'] + +// e.g. reformat from ['x', -1, 0.1] to [-0.1, 0, 0] +export function formatJogVector( + axis: string, + direction: number, + step: number +): VectorTuple { + const vector: VectorTuple = [0, 0, 0] + const index = ORDERED_AXES.findIndex(a => a === axis) + if (index >= 0) { + vector[index] = step * direction + } + return vector +} + +export function formatLastModified(lastModified: string | null): string { + return typeof lastModified === 'string' + ? format(new Date(lastModified), 'MMMM dd, yyyy HH:mm') + : 'unknown' +} diff --git a/app/src/organisms/CalibrationStatusCard/__tests__/CalibrationStatusCard.test.tsx b/app/src/organisms/Desktop/CalibrationStatusCard/__tests__/CalibrationStatusCard.test.tsx similarity index 96% rename from app/src/organisms/CalibrationStatusCard/__tests__/CalibrationStatusCard.test.tsx rename to app/src/organisms/Desktop/CalibrationStatusCard/__tests__/CalibrationStatusCard.test.tsx index b41e8b79599..400e73b3078 100644 --- a/app/src/organisms/CalibrationStatusCard/__tests__/CalibrationStatusCard.test.tsx +++ b/app/src/organisms/Desktop/CalibrationStatusCard/__tests__/CalibrationStatusCard.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { vi, it, describe, expect, beforeEach } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { CalibrationStatusCard } from '..' import { useCalibrationTaskList } from '../../Devices/hooks' import { diff --git a/app/src/organisms/Desktop/CalibrationStatusCard/index.tsx b/app/src/organisms/Desktop/CalibrationStatusCard/index.tsx new file mode 100644 index 00000000000..9be7eecb1c2 --- /dev/null +++ b/app/src/organisms/Desktop/CalibrationStatusCard/index.tsx @@ -0,0 +1,107 @@ +import { useTranslation } from 'react-i18next' +import { Link as RouterLink } from 'react-router-dom' + +import { + ALIGN_CENTER, + ALIGN_FLEX_START, + BORDERS, + COLORS, + DIRECTION_COLUMN, + Flex, + JUSTIFY_CENTER, + JUSTIFY_SPACE_BETWEEN, + Link, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' + +import { TertiaryButton } from '/app/atoms/buttons' +import { StatusLabel } from '/app/atoms/StatusLabel' + +import { useCalibrationTaskList } from '../Devices/hooks' + +export interface CalibrationStatusCardProps { + robotName: string + setShowHowCalibrationWorksModal: ( + showHowCalibrationWorksModal: boolean + ) => void +} + +export function CalibrationStatusCard({ + robotName, + setShowHowCalibrationWorksModal, +}: CalibrationStatusCardProps): JSX.Element { + const { t } = useTranslation('robot_calibration') + const { taskListStatus } = useCalibrationTaskList() + + // start off assuming we are missing calibrations + let statusLabelBackgroundColor: string = COLORS.red30 + let statusLabelIconColor: string = COLORS.red60 + let statusLabelText = t('missing_calibration_data') + let statusLabelTextColor = COLORS.red60 + + // if the tasklist is empty, though, all calibrations are good + if (taskListStatus === 'complete') { + statusLabelBackgroundColor = COLORS.green30 + statusLabelIconColor = COLORS.green60 + statusLabelText = t('calibration_complete') + statusLabelTextColor = COLORS.green60 + // if we have tasks and they are all marked bad, then we should + // strongly suggest they re-do those calibrations + } else if (taskListStatus === 'bad') { + statusLabelBackgroundColor = COLORS.yellow30 + statusLabelIconColor = COLORS.yellow60 + statusLabelText = t('calibration_recommended') + statusLabelTextColor = COLORS.yellow60 + } + + return ( + + + + + {t('calibration_status')} + + + + + {t('calibration_status_description')} + + { + setShowHowCalibrationWorksModal(true) + }} + > + {t('see_how_robot_calibration_works')} + + + + {t('launch_calibration')} + + + ) +} diff --git a/app/src/organisms/CalibrationTaskList/__tests__/CalibrationTaskList.test.tsx b/app/src/organisms/Desktop/CalibrationTaskList/__tests__/CalibrationTaskList.test.tsx similarity index 96% rename from app/src/organisms/CalibrationTaskList/__tests__/CalibrationTaskList.test.tsx rename to app/src/organisms/Desktop/CalibrationTaskList/__tests__/CalibrationTaskList.test.tsx index 8c35b7e5c04..ebada58f1f3 100644 --- a/app/src/organisms/CalibrationTaskList/__tests__/CalibrationTaskList.test.tsx +++ b/app/src/organisms/Desktop/CalibrationTaskList/__tests__/CalibrationTaskList.test.tsx @@ -1,10 +1,9 @@ -import * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { CalibrationTaskList } from '..' import { mockDeckCalLauncher, @@ -18,15 +17,14 @@ import { expectedIncompleteRightMountTaskList, expectedIncompleteLeftMountTaskList, } from '../../Devices/hooks/__fixtures__/taskListFixtures' -import { - useCalibrationTaskList, - useRunHasStarted, - useAttachedPipettes, -} from '../../Devices/hooks' -import { mockLeftProtoPipette } from '../../../redux/pipettes/__fixtures__' +import { useCalibrationTaskList } from '../../Devices/hooks' +import { useAttachedPipettes } from '/app/resources/instruments' +import { mockLeftProtoPipette } from '/app/redux/pipettes/__fixtures__' +import { useRunHasStarted } from '/app/resources/runs' vi.mock('../../Devices/hooks') -vi.mock('../../../resources/runs') +vi.mock('/app/resources/runs') +vi.mock('/app/resources/instruments') const render = (robotName: string = 'otie') => { return renderWithProviders( diff --git a/app/src/organisms/Desktop/CalibrationTaskList/index.tsx b/app/src/organisms/Desktop/CalibrationTaskList/index.tsx new file mode 100644 index 00000000000..f0826dce48d --- /dev/null +++ b/app/src/organisms/Desktop/CalibrationTaskList/index.tsx @@ -0,0 +1,185 @@ +import { useRef, useState, useEffect } from 'react' +import { css } from 'styled-components' +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' + +import { + ALIGN_CENTER, + COLORS, + DIRECTION_COLUMN, + Flex, + Icon, + JUSTIFY_CENTER, + PrimaryButton, + SPACING, + LegacyStyledText, + TYPOGRAPHY, + Modal, +} from '@opentrons/components' + +import { StatusLabel } from '/app/atoms/StatusLabel' +import { TaskList } from '/app/molecules/TaskList' + +import { useCalibrationTaskList } from '/app/organisms/Desktop/Devices/hooks' +import { useAttachedPipettes } from '/app/resources/instruments' +import { useCurrentRunId, useRunHasStarted } from '/app/resources/runs' + +import type { + DashboardCalOffsetInvoker, + DashboardCalTipLengthInvoker, + DashboardCalDeckInvoker, +} from '/app/organisms/Desktop/Devices/hooks' + +interface CalibrationTaskListProps { + robotName: string + pipOffsetCalLauncher: DashboardCalOffsetInvoker + tipLengthCalLauncher: DashboardCalTipLengthInvoker + deckCalLauncher: DashboardCalDeckInvoker + exitBeforeDeckConfigCompletion: boolean +} + +export function CalibrationTaskList({ + robotName, + pipOffsetCalLauncher, + tipLengthCalLauncher, + deckCalLauncher, + exitBeforeDeckConfigCompletion, +}: CalibrationTaskListProps): JSX.Element { + const prevActiveIndex = useRef<[number, number] | null>(null) + const [hasLaunchedWizard, setHasLaunchedWizard] = useState(false) + const [showCompletionScreen, setShowCompletionScreen] = useState( + false + ) + const { t } = useTranslation(['robot_calibration', 'device_settings']) + const navigate = useNavigate() + const { activeIndex, taskList, taskListStatus } = useCalibrationTaskList( + pipOffsetCalLauncher, + tipLengthCalLauncher, + deckCalLauncher + ) + const runId = useCurrentRunId() + + let generalTaskDisabledReason = null + + const attachedPipettes = useAttachedPipettes() + if (attachedPipettes.left == null && attachedPipettes.right == null) { + generalTaskDisabledReason = t( + 'device_settings:attach_a_pipette_before_calibrating' + ) + } + + const runHasStarted = useRunHasStarted(runId) + if (runHasStarted) + generalTaskDisabledReason = t( + 'device_settings:some_robot_controls_are_not_available' + ) + + useEffect(() => { + if ( + prevActiveIndex.current !== null && + activeIndex === null && + hasLaunchedWizard + ) { + setShowCompletionScreen(true) + } + prevActiveIndex.current = activeIndex + }, [activeIndex, hasLaunchedWizard]) + + // start off assuming we are missing calibrations + let statusLabelBackgroundColor: string = COLORS.red30 + let statusLabelIconColor: string = COLORS.red50 + let statusLabelText = t('missing_calibration_data') + + // if the tasklist is empty, though, all calibrations are good + if (taskListStatus === 'complete') { + statusLabelBackgroundColor = COLORS.green30 + statusLabelIconColor = COLORS.green50 + statusLabelText = t('calibration_complete') + // if we have tasks and they are all marked bad, then we should + // strongly suggest they re-do those calibrations + } else if (taskListStatus === 'bad') { + statusLabelBackgroundColor = COLORS.yellow30 + statusLabelIconColor = COLORS.yellow50 + statusLabelText = t('calibration_recommended') + } + + return ( + { + navigate(`/devices/${robotName}/robot-settings/calibration`) + }} + fullPage + backgroundColor={COLORS.grey10} + childrenPadding={`${SPACING.spacing16} ${SPACING.spacing24} ${SPACING.spacing24} ${SPACING.spacing4}`} + css={css` + width: 50rem; + height: 47.5rem; + `} + marginLeft="0" + > + {showCompletionScreen ? ( + + + {exitBeforeDeckConfigCompletion ? ( + + ) : ( + + )} + + {exitBeforeDeckConfigCompletion + ? t('using_current_calibrations') + : t('calibrations_complete')} + + { + navigate(`/devices/${robotName}/robot-settings/calibration`) + }} + > + {t('device_settings:done')} + + + + ) : ( + <> + + + {t('calibration_status')} + + + + { + setHasLaunchedWizard(true) + }} + generalTaskDisabledReason={generalTaskDisabledReason} + /> + + )} + + ) +} diff --git a/app/src/organisms/CheckCalibration/ResultsSummary/CalibrationHealthCheckResults.tsx b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/CalibrationHealthCheckResults.tsx similarity index 92% rename from app/src/organisms/CheckCalibration/ResultsSummary/CalibrationHealthCheckResults.tsx rename to app/src/organisms/Desktop/CheckCalibration/ResultsSummary/CalibrationHealthCheckResults.tsx index f15a33b20ae..957a91fb0eb 100644 --- a/app/src/organisms/CheckCalibration/ResultsSummary/CalibrationHealthCheckResults.tsx +++ b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/CalibrationHealthCheckResults.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -10,7 +9,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { StatusLabel } from '../../../atoms/StatusLabel' +import { StatusLabel } from '/app/atoms/StatusLabel' interface CalibrationHealthCheckResultsProps { isCalibrationRecommended: boolean diff --git a/app/src/organisms/CheckCalibration/ResultsSummary/CalibrationResult.tsx b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/CalibrationResult.tsx similarity index 97% rename from app/src/organisms/CheckCalibration/ResultsSummary/CalibrationResult.tsx rename to app/src/organisms/Desktop/CheckCalibration/ResultsSummary/CalibrationResult.tsx index 9fd0252cc28..6d060e30b72 100644 --- a/app/src/organisms/CheckCalibration/ResultsSummary/CalibrationResult.tsx +++ b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/CalibrationResult.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' diff --git a/app/src/organisms/CheckCalibration/ResultsSummary/RenderMountInformation.tsx b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/RenderMountInformation.tsx similarity index 87% rename from app/src/organisms/CheckCalibration/ResultsSummary/RenderMountInformation.tsx rename to app/src/organisms/Desktop/CheckCalibration/ResultsSummary/RenderMountInformation.tsx index c7725db0c85..fb79709cf33 100644 --- a/app/src/organisms/CheckCalibration/ResultsSummary/RenderMountInformation.tsx +++ b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/RenderMountInformation.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -10,8 +9,8 @@ import { } from '@opentrons/components' import { getPipetteModelSpecs } from '@opentrons/shared-data' -import type { Mount } from '../../../redux/pipettes/types' -import type { CalibrationCheckInstrument } from '../../../redux/sessions/types' +import type { Mount } from '/app/redux/pipettes/types' +import type { CalibrationCheckInstrument } from '/app/redux/sessions/types' interface MountInformationProps { mount: Mount diff --git a/app/src/organisms/CheckCalibration/ResultsSummary/RenderResult.tsx b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/RenderResult.tsx similarity index 96% rename from app/src/organisms/CheckCalibration/ResultsSummary/RenderResult.tsx rename to app/src/organisms/Desktop/CheckCalibration/ResultsSummary/RenderResult.tsx index 9f6fbe5e548..78c8f4e93d2 100644 --- a/app/src/organisms/CheckCalibration/ResultsSummary/RenderResult.tsx +++ b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/RenderResult.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, diff --git a/app/src/organisms/CheckCalibration/ResultsSummary/__tests__/CalibrationHealthCheckResults.test.tsx b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/__tests__/CalibrationHealthCheckResults.test.tsx similarity index 92% rename from app/src/organisms/CheckCalibration/ResultsSummary/__tests__/CalibrationHealthCheckResults.test.tsx rename to app/src/organisms/Desktop/CheckCalibration/ResultsSummary/__tests__/CalibrationHealthCheckResults.test.tsx index d170c31cb73..dbe7c8349a0 100644 --- a/app/src/organisms/CheckCalibration/ResultsSummary/__tests__/CalibrationHealthCheckResults.test.tsx +++ b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/__tests__/CalibrationHealthCheckResults.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { it, describe, expect, beforeEach } from 'vitest' import { screen } from '@testing-library/react' import { COLORS, TYPOGRAPHY } from '@opentrons/components' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { CalibrationHealthCheckResults } from '../CalibrationHealthCheckResults' const render = ( diff --git a/app/src/organisms/CheckCalibration/ResultsSummary/__tests__/CalibrationResult.test.tsx b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/__tests__/CalibrationResult.test.tsx similarity index 93% rename from app/src/organisms/CheckCalibration/ResultsSummary/__tests__/CalibrationResult.test.tsx rename to app/src/organisms/Desktop/CheckCalibration/ResultsSummary/__tests__/CalibrationResult.test.tsx index fd6938fdbcd..70432556944 100644 --- a/app/src/organisms/CheckCalibration/ResultsSummary/__tests__/CalibrationResult.test.tsx +++ b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/__tests__/CalibrationResult.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, describe, beforeEach } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { RenderResult } from '../RenderResult' import { CalibrationResult } from '../CalibrationResult' diff --git a/app/src/organisms/CheckCalibration/ResultsSummary/__tests__/RenderMountInformation.test.tsx b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/__tests__/RenderMountInformation.test.tsx similarity index 85% rename from app/src/organisms/CheckCalibration/ResultsSummary/__tests__/RenderMountInformation.test.tsx rename to app/src/organisms/Desktop/CheckCalibration/ResultsSummary/__tests__/RenderMountInformation.test.tsx index c678cdf4c78..bae133b6522 100644 --- a/app/src/organisms/CheckCalibration/ResultsSummary/__tests__/RenderMountInformation.test.tsx +++ b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/__tests__/RenderMountInformation.test.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, describe, beforeEach } from 'vitest' import { screen } from '@testing-library/react' import { getPipetteModelSpecs } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { LEFT, RIGHT } from '../../../../redux/pipettes' -import * as Fixtures from '../../../../redux/sessions/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { LEFT, RIGHT } from '/app/redux/pipettes' +import * as Fixtures from '/app/redux/sessions/__fixtures__' import { RenderMountInformation } from '../RenderMountInformation' vi.mock('@opentrons/shared-data', async importOriginal => { diff --git a/app/src/organisms/CheckCalibration/ResultsSummary/__tests__/RenderResult.test.tsx b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/__tests__/RenderResult.test.tsx similarity index 90% rename from app/src/organisms/CheckCalibration/ResultsSummary/__tests__/RenderResult.test.tsx rename to app/src/organisms/Desktop/CheckCalibration/ResultsSummary/__tests__/RenderResult.test.tsx index a2b191e07a2..90ed47fc126 100644 --- a/app/src/organisms/CheckCalibration/ResultsSummary/__tests__/RenderResult.test.tsx +++ b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/__tests__/RenderResult.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { it, describe, expect, beforeEach } from 'vitest' import { screen } from '@testing-library/react' import { COLORS, SIZE_1 } from '@opentrons/components' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { RenderResult } from '../RenderResult' diff --git a/app/src/organisms/CheckCalibration/ResultsSummary/__tests__/ResultsSummary.test.tsx b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/__tests__/ResultsSummary.test.tsx similarity index 86% rename from app/src/organisms/CheckCalibration/ResultsSummary/__tests__/ResultsSummary.test.tsx rename to app/src/organisms/Desktop/CheckCalibration/ResultsSummary/__tests__/ResultsSummary.test.tsx index f9bef5d7feb..246513300a9 100644 --- a/app/src/organisms/CheckCalibration/ResultsSummary/__tests__/ResultsSummary.test.tsx +++ b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/__tests__/ResultsSummary.test.tsx @@ -1,19 +1,18 @@ -import * as React from 'react' import { vi, it, describe, expect, beforeEach } from 'vitest' import { saveAs } from 'file-saver' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import * as Fixtures from '../../../../redux/sessions/__fixtures__' -import * as Sessions from '../../../../redux/sessions' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import * as Fixtures from '/app/redux/sessions/__fixtures__' +import * as Sessions from '/app/redux/sessions' import { CalibrationHealthCheckResults } from '../CalibrationHealthCheckResults' import { RenderMountInformation } from '../RenderMountInformation' import { CalibrationResult } from '../CalibrationResult' import { ResultsSummary } from '../' -import type { CalibrationPanelProps } from '../../../../organisms/CalibrationPanels/types' +import type { CalibrationPanelProps } from '/app/organisms/Desktop/CalibrationPanels/types' // file-saver has circular dep, need to mock with factory to prevent error vi.mock('file-saver', async importOriginal => { @@ -23,8 +22,8 @@ vi.mock('file-saver', async importOriginal => { saveAs: vi.fn(), } }) -vi.mock('../../../../redux/sessions') -vi.mock('../../../../redux/pipettes') +vi.mock('/app/redux/sessions') +vi.mock('/app/redux/pipettes') vi.mock('../CalibrationHealthCheckResults') vi.mock('../RenderMountInformation') vi.mock('../CalibrationResult') diff --git a/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/index.tsx b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/index.tsx new file mode 100644 index 00000000000..1bc2446ae4b --- /dev/null +++ b/app/src/organisms/Desktop/CheckCalibration/ResultsSummary/index.tsx @@ -0,0 +1,197 @@ +import { useTranslation } from 'react-i18next' +import { saveAs } from 'file-saver' + +import { + Box, + Flex, + Link, + ALIGN_CENTER, + DIRECTION_COLUMN, + TYPOGRAPHY, + SPACING, + PrimaryButton, + DIRECTION_ROW, + JUSTIFY_SPACE_BETWEEN, +} from '@opentrons/components' + +import find from 'lodash/find' +import { LEFT, RIGHT } from '/app/redux/pipettes' +import { CHECK_STATUS_OUTSIDE_THRESHOLD } from '/app/redux/sessions' +import { CalibrationHealthCheckResults } from './CalibrationHealthCheckResults' +import { RenderMountInformation } from './RenderMountInformation' +import { CalibrationResult } from './CalibrationResult' + +import type { Mount } from '/app/redux/pipettes/types' +import type { CalibrationPanelProps } from '/app/organisms/Desktop/CalibrationPanels/types' +import type { + CalibrationCheckInstrument, + CalibrationCheckComparisonsPerCalibration, +} from '/app/redux/sessions/types' + +export function ResultsSummary( + props: CalibrationPanelProps +): JSX.Element | null { + const { + comparisonsByPipette, + instruments, + checkBothPipettes, + cleanUpAndExit, + } = props + const { t } = useTranslation('robot_calibration') + if (comparisonsByPipette == null || instruments == null) { + return null + } + + const handleDownloadButtonClick = (): void => { + const now = new Date() + const report = { + comparisonsByPipette, + instruments, + savedAt: now.toISOString(), + } + const data = new Blob([JSON.stringify(report, null, 4)], { + type: 'application/json', + }) + saveAs(data, 'Robot Calibration Check Report.json') + } + + const leftPipette = find( + instruments, + (p: CalibrationCheckInstrument) => p.mount.toLowerCase() === LEFT + ) + const rightPipette = find( + instruments, + (p: CalibrationCheckInstrument) => p.mount.toLowerCase() === RIGHT + ) + type CalibrationByMount = { + [m in Mount]: { + pipette: CalibrationCheckInstrument | undefined + calibration: CalibrationCheckComparisonsPerCalibration | null + } + } + + const calibrationsByMount: CalibrationByMount = { + left: { + pipette: leftPipette, + calibration: + leftPipette != null + ? comparisonsByPipette?.[leftPipette.rank] ?? null + : null, + }, + right: { + pipette: rightPipette, + calibration: + rightPipette != null + ? comparisonsByPipette?.[rightPipette.rank] ?? null + : null, + }, + } + + const getDeckCalibration = checkBothPipettes + ? comparisonsByPipette.second.deck?.status + : comparisonsByPipette.first.deck?.status + const deckCalibrationResult = getDeckCalibration ?? null + + const pipetteResultsBad = ( + perPipette: CalibrationCheckComparisonsPerCalibration | null + ): { offsetBad: boolean; tipLengthBad: boolean } => ({ + offsetBad: perPipette?.pipetteOffset?.status + ? perPipette.pipetteOffset.status === CHECK_STATUS_OUTSIDE_THRESHOLD + : false, + tipLengthBad: perPipette?.tipLength?.status + ? perPipette.tipLength.status === CHECK_STATUS_OUTSIDE_THRESHOLD + : false, + }) + + const isDeckResultBad = + deckCalibrationResult != null + ? deckCalibrationResult === CHECK_STATUS_OUTSIDE_THRESHOLD + : false + + // check all calibration status + // if all of them are good, this returns true. otherwise return false + const isCalibrationRecommended = (): boolean => { + const isOffsetsBad = + pipetteResultsBad(calibrationsByMount.left.calibration).offsetBad && + pipetteResultsBad(calibrationsByMount.right.calibration).offsetBad + const isTipLensBad = + pipetteResultsBad(calibrationsByMount.left.calibration).tipLengthBad && + pipetteResultsBad(calibrationsByMount.right.calibration).tipLengthBad + return isDeckResultBad && isOffsetsBad && isTipLensBad + } + + return ( + + + + + + + + + + {leftPipette != null && ( + <> + + + + )} + + + + {rightPipette != null && ( + <> + + + + )} + + + + {t('download_details')} + + {t('finish')} + + + ) +} diff --git a/app/src/organisms/CheckCalibration/ReturnTip.tsx b/app/src/organisms/Desktop/CheckCalibration/ReturnTip.tsx similarity index 87% rename from app/src/organisms/CheckCalibration/ReturnTip.tsx rename to app/src/organisms/Desktop/CheckCalibration/ReturnTip.tsx index 5af67553a93..a996ee7f566 100644 --- a/app/src/organisms/CheckCalibration/ReturnTip.tsx +++ b/app/src/organisms/Desktop/CheckCalibration/ReturnTip.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { ALIGN_STRETCH, DIRECTION_COLUMN, @@ -10,11 +9,11 @@ import { } from '@opentrons/components' import uniq from 'lodash/uniq' -import * as Sessions from '../../redux/sessions' -import type { CalibrationPanelProps } from '../../organisms/CalibrationPanels/types' -import type { SessionCommandString } from '../../redux/sessions/types' +import * as Sessions from '/app/redux/sessions' +import type { CalibrationPanelProps } from '/app/organisms/Desktop/CalibrationPanels/types' +import type { SessionCommandString } from '/app/redux/sessions/types' import { useTranslation } from 'react-i18next' -import { NeedHelpLink } from '../CalibrationPanels' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' export function ReturnTip(props: CalibrationPanelProps): JSX.Element { const { t } = useTranslation('robot_calibration') diff --git a/app/src/organisms/CheckCalibration/ThresholdValue.tsx b/app/src/organisms/Desktop/CheckCalibration/ThresholdValue.tsx similarity index 92% rename from app/src/organisms/CheckCalibration/ThresholdValue.tsx rename to app/src/organisms/Desktop/CheckCalibration/ThresholdValue.tsx index 4ff957fbb47..383f3f4ed31 100644 --- a/app/src/organisms/CheckCalibration/ThresholdValue.tsx +++ b/app/src/organisms/Desktop/CheckCalibration/ThresholdValue.tsx @@ -1,5 +1,3 @@ -import * as React from 'react' - interface Props { thresholdVector: [number, number, number] } diff --git a/app/src/organisms/CheckCalibration/__tests__/CheckCalibration.test.tsx b/app/src/organisms/Desktop/CheckCalibration/__tests__/CheckCalibration.test.tsx similarity index 92% rename from app/src/organisms/CheckCalibration/__tests__/CheckCalibration.test.tsx rename to app/src/organisms/Desktop/CheckCalibration/__tests__/CheckCalibration.test.tsx index 59cdf4ece01..d5f5a9814d7 100644 --- a/app/src/organisms/CheckCalibration/__tests__/CheckCalibration.test.tsx +++ b/app/src/organisms/Desktop/CheckCalibration/__tests__/CheckCalibration.test.tsx @@ -1,20 +1,20 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { when } from 'vitest-when' import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' import { getDeckDefinitions } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import * as Sessions from '../../../redux/sessions' -import { mockCalibrationCheckSessionAttributes } from '../../../redux/sessions/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import * as Sessions from '/app/redux/sessions' +import { mockCalibrationCheckSessionAttributes } from '/app/redux/sessions/__fixtures__' import { CheckCalibration } from '../index' -import type { RobotCalibrationCheckStep } from '../../../redux/sessions/types' +import type { RobotCalibrationCheckStep } from '/app/redux/sessions/types' -vi.mock('../../../redux/calibration/selectors') -vi.mock('../../../redux/config') +vi.mock('/app/redux/calibration/selectors') +vi.mock('/app/redux/config') vi.mock('@opentrons/shared-data', async importOriginal => { const actual = await importOriginal() return { diff --git a/app/src/organisms/CheckCalibration/__tests__/ReturnTip.test.tsx b/app/src/organisms/Desktop/CheckCalibration/__tests__/ReturnTip.test.tsx similarity index 100% rename from app/src/organisms/CheckCalibration/__tests__/ReturnTip.test.tsx rename to app/src/organisms/Desktop/CheckCalibration/__tests__/ReturnTip.test.tsx diff --git a/app/src/organisms/Desktop/CheckCalibration/index.tsx b/app/src/organisms/Desktop/CheckCalibration/index.tsx new file mode 100644 index 00000000000..10bf25e93f9 --- /dev/null +++ b/app/src/organisms/Desktop/CheckCalibration/index.tsx @@ -0,0 +1,224 @@ +import { useMemo } from 'react' +import { createPortal } from 'react-dom' +import { useTranslation } from 'react-i18next' + +import { getPipetteModelSpecs } from '@opentrons/shared-data' +import { useConditionalConfirm, ModalShell } from '@opentrons/components' + +import * as Sessions from '/app/redux/sessions' +import { + Introduction, + DeckSetup, + TipPickUp, + TipConfirmation, + SaveZPoint, + SaveXYPoint, + MeasureNozzle, + MeasureTip, + LoadingState, + ConfirmExit, +} from '/app/organisms/Desktop/CalibrationPanels' +import { WizardHeader } from '/app/molecules/WizardHeader' +import { getTopPortalEl } from '/app/App/portal' +import { ReturnTip } from './ReturnTip' +import { ResultsSummary } from './ResultsSummary' +import { CHECK_PIPETTE_RANK_FIRST } from '/app/redux/sessions' + +import type { ComponentType } from 'react' +import type { Mount } from '@opentrons/components' +import type { + CalibrationLabware, + RobotCalibrationCheckPipetteRank, + RobotCalibrationCheckStep, + SessionCommandParams, +} from '/app/redux/sessions/types' +import type { CalibrationPanelProps } from '/app/organisms/Desktop/CalibrationPanels/types' +import type { CalibrationCheckParentProps } from './types' + +const ROBOT_CALIBRATION_CHECK_SUBTITLE = 'Calibration health check' + +const PANEL_BY_STEP: { + [step in RobotCalibrationCheckStep]?: ComponentType +} = { + [Sessions.CHECK_STEP_SESSION_STARTED]: Introduction, + [Sessions.CHECK_STEP_LABWARE_LOADED]: DeckSetup, + [Sessions.CHECK_STEP_COMPARING_NOZZLE]: MeasureNozzle, + [Sessions.CHECK_STEP_PREPARING_PIPETTE]: TipPickUp, + [Sessions.CHECK_STEP_INSPECTING_TIP]: TipConfirmation, + [Sessions.CHECK_STEP_COMPARING_TIP]: MeasureTip, + [Sessions.CHECK_STEP_COMPARING_HEIGHT]: SaveZPoint, + [Sessions.CHECK_STEP_COMPARING_POINT_ONE]: SaveXYPoint, + [Sessions.CHECK_STEP_COMPARING_POINT_TWO]: SaveXYPoint, + [Sessions.CHECK_STEP_COMPARING_POINT_THREE]: SaveXYPoint, + [Sessions.CHECK_STEP_RETURNING_TIP]: ReturnTip, + [Sessions.CHECK_STEP_RESULTS_SUMMARY]: ResultsSummary, +} + +const STEPS_IN_ORDER_ONE_PIPETTE: RobotCalibrationCheckStep[] = [ + Sessions.CHECK_STEP_SESSION_STARTED, + Sessions.CHECK_STEP_LABWARE_LOADED, + Sessions.CHECK_STEP_COMPARING_NOZZLE, + Sessions.CHECK_STEP_PREPARING_PIPETTE, + Sessions.CHECK_STEP_INSPECTING_TIP, + Sessions.CHECK_STEP_COMPARING_TIP, + Sessions.CHECK_STEP_COMPARING_HEIGHT, + Sessions.CHECK_STEP_COMPARING_POINT_ONE, + Sessions.CHECK_STEP_COMPARING_POINT_TWO, + Sessions.CHECK_STEP_COMPARING_POINT_THREE, + Sessions.CHECK_STEP_RETURNING_TIP, + Sessions.CHECK_STEP_RESULTS_SUMMARY, +] +const STEPS_IN_ORDER_BOTH_PIPETTES: RobotCalibrationCheckStep[] = [ + Sessions.CHECK_STEP_SESSION_STARTED, + Sessions.CHECK_STEP_LABWARE_LOADED, + Sessions.CHECK_STEP_COMPARING_NOZZLE, + Sessions.CHECK_STEP_PREPARING_PIPETTE, + Sessions.CHECK_STEP_INSPECTING_TIP, + Sessions.CHECK_STEP_COMPARING_TIP, + Sessions.CHECK_STEP_COMPARING_HEIGHT, + Sessions.CHECK_STEP_COMPARING_POINT_ONE, + Sessions.CHECK_STEP_RETURNING_TIP, + Sessions.CHECK_STEP_LABWARE_LOADED, + Sessions.CHECK_STEP_COMPARING_NOZZLE, + Sessions.CHECK_STEP_PREPARING_PIPETTE, + Sessions.CHECK_STEP_INSPECTING_TIP, + Sessions.CHECK_STEP_COMPARING_TIP, + Sessions.CHECK_STEP_COMPARING_HEIGHT, + Sessions.CHECK_STEP_COMPARING_POINT_ONE, + Sessions.CHECK_STEP_COMPARING_POINT_TWO, + Sessions.CHECK_STEP_COMPARING_POINT_THREE, + Sessions.CHECK_STEP_RETURNING_TIP, + Sessions.CHECK_STEP_RESULTS_SUMMARY, +] +function getStepIndexCheckingBothPipettes( + currentStep: RobotCalibrationCheckStep | null, + rank: RobotCalibrationCheckPipetteRank | null +): number { + if (currentStep == null || rank == null) return 0 + return rank === CHECK_PIPETTE_RANK_FIRST + ? STEPS_IN_ORDER_BOTH_PIPETTES.findIndex(step => step === currentStep) + : STEPS_IN_ORDER_BOTH_PIPETTES.slice(9).findIndex( + step => step === currentStep + ) + 9 +} + +export function CheckCalibration( + props: CalibrationCheckParentProps +): JSX.Element | null { + const { t } = useTranslation('robot_calibration') + const { session, robotName, dispatchRequests, showSpinner, isJogging } = props + const { + currentStep, + activePipette, + activeTipRack, + instruments, + comparisonsByPipette, + labware, + } = session?.details || {} + + const { + showConfirmation: showConfirmExit, + confirm: confirmExit, + cancel: cancelExit, + } = useConditionalConfirm(() => { + cleanUpAndExit() + }, true) + + const isMulti = useMemo(() => { + const spec = activePipette && getPipetteModelSpecs(activePipette.model) + return spec ? spec.channels > 1 : false + }, [activePipette]) + + const calBlock: CalibrationLabware | null = labware + ? labware.find(l => !l.isTiprack) ?? null + : null + + function sendCommands(...commands: SessionCommandParams[]): void { + if (session?.id && !isJogging) { + const sessionCommandActions = commands.map(c => + Sessions.createSessionCommand(robotName, session.id, { + command: c.command, + data: c.data || {}, + }) + ) + dispatchRequests(...sessionCommandActions) + } + } + + function cleanUpAndExit(): void { + if (session?.id) { + dispatchRequests( + Sessions.createSessionCommand(robotName, session.id, { + command: Sessions.sharedCalCommands.EXIT, + data: {}, + }), + Sessions.deleteSession(robotName, session.id) + ) + } + } + + const checkBothPipettes = instruments?.length === 2 + const stepIndex = checkBothPipettes + ? getStepIndexCheckingBothPipettes( + currentStep ?? null, + activePipette?.rank ?? null + ) + : STEPS_IN_ORDER_ONE_PIPETTE.findIndex(step => step === currentStep) ?? 0 + + if (!session || !activeTipRack) { + return null + } + + const Panel = + currentStep != null && currentStep in PANEL_BY_STEP + ? PANEL_BY_STEP[currentStep] + : null + return createPortal( + + } + > + {showSpinner || currentStep == null || Panel == null ? ( + + ) : showConfirmExit ? ( + + ) : ( + + )} + , + getTopPortalEl() + ) +} diff --git a/app/src/organisms/CheckCalibration/styles.module.css b/app/src/organisms/Desktop/CheckCalibration/styles.module.css similarity index 100% rename from app/src/organisms/CheckCalibration/styles.module.css rename to app/src/organisms/Desktop/CheckCalibration/styles.module.css diff --git a/app/src/organisms/Desktop/CheckCalibration/types.ts b/app/src/organisms/Desktop/CheckCalibration/types.ts new file mode 100644 index 00000000000..c409064c702 --- /dev/null +++ b/app/src/organisms/Desktop/CheckCalibration/types.ts @@ -0,0 +1,11 @@ +import type { CalibrationCheckSession } from '/app/redux/sessions/types' +import type { DispatchRequestsType } from '/app/redux/robot-api' + +export interface CalibrationCheckParentProps { + robotName: string + session: CalibrationCheckSession | null + dispatchRequests: DispatchRequestsType + isJogging: boolean + showSpinner: boolean + hasBlock?: boolean +} diff --git a/app/src/organisms/ChooseProtocolSlideout/__tests__/ChooseProtocolSlideout.test.tsx b/app/src/organisms/Desktop/ChooseProtocolSlideout/__tests__/ChooseProtocolSlideout.test.tsx similarity index 89% rename from app/src/organisms/ChooseProtocolSlideout/__tests__/ChooseProtocolSlideout.test.tsx rename to app/src/organisms/Desktop/ChooseProtocolSlideout/__tests__/ChooseProtocolSlideout.test.tsx index 28063f63f11..fc6a0f281a9 100644 --- a/app/src/organisms/ChooseProtocolSlideout/__tests__/ChooseProtocolSlideout.test.tsx +++ b/app/src/organisms/Desktop/ChooseProtocolSlideout/__tests__/ChooseProtocolSlideout.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, describe, expect, beforeEach } from 'vitest' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen, waitFor } from '@testing-library/react' @@ -7,25 +7,27 @@ import { OT2_ROBOT_TYPE, simpleAnalysisFileFixture, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { getStoredProtocols } from '../../../redux/protocol-storage' -import { mockConnectableRobot } from '../../../redux/discovery/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getStoredProtocols } from '/app/redux/protocol-storage' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' import { storedProtocolData as storedProtocolDataFixture, storedProtocolDataWithoutRunTimeParameters, -} from '../../../redux/protocol-storage/__fixtures__' -import { useTrackCreateProtocolRunEvent } from '../../../organisms/Devices/hooks' -import { useCreateRunFromProtocol } from '../../ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol' +} from '/app/redux/protocol-storage/__fixtures__' +import { useTrackCreateProtocolRunEvent } from '/app/organisms/Desktop/Devices/hooks' +import { useCreateRunFromProtocol } from '/app/organisms/Desktop/ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol' import { ChooseProtocolSlideout } from '../' -import { useNotifyDataReady } from '../../../resources/useNotifyDataReady' +import { useNotifyDataReady } from '/app/resources/useNotifyDataReady' import type { ProtocolAnalysisOutput } from '@opentrons/shared-data' -vi.mock('../../ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol') -vi.mock('../../../redux/protocol-storage') -vi.mock('../../../organisms/Devices/hooks') -vi.mock('../../../redux/config') -vi.mock('../../../resources/useNotifyDataReady') +vi.mock( + '/app/organisms/Desktop/ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol' +) +vi.mock('/app/redux/protocol-storage') +vi.mock('/app/organisms/Desktop/Devices/hooks') +vi.mock('/app/redux/config') +vi.mock('/app/resources/useNotifyDataReady') const render = (props: React.ComponentProps) => { return renderWithProviders( diff --git a/app/src/organisms/Desktop/ChooseProtocolSlideout/index.tsx b/app/src/organisms/Desktop/ChooseProtocolSlideout/index.tsx new file mode 100644 index 00000000000..5fa0536eb60 --- /dev/null +++ b/app/src/organisms/Desktop/ChooseProtocolSlideout/index.tsx @@ -0,0 +1,944 @@ +import { useEffect, useState, Fragment } from 'react' +import first from 'lodash/first' +import { Trans, useTranslation } from 'react-i18next' +import { Link, NavLink, useNavigate } from 'react-router-dom' +import { useSelector } from 'react-redux' +import { css } from 'styled-components' + +import { + ALIGN_CENTER, + BORDERS, + Box, + COLORS, + CURSOR_AUTO, + CURSOR_DEFAULT, + CURSOR_POINTER, + DIRECTION_COLUMN, + DIRECTION_ROW, + DISPLAY_BLOCK, + DISPLAY_GRID, + DropdownMenu, + Flex, + Icon, + InputField, + JUSTIFY_CENTER, + JUSTIFY_END, + JUSTIFY_FLEX_START, + LegacyStyledText, + Link as LinkComponent, + NO_WRAP, + OVERFLOW_WRAP_ANYWHERE, + PrimaryButton, + ProtocolDeck, + SecondaryButton, + SPACING, + Tooltip, + TYPOGRAPHY, + useHoverTooltip, + useTooltip, +} from '@opentrons/components' +import { + ApiHostProvider, + useUploadCsvFileMutation, +} from '@opentrons/react-api-client' +import { sortRuntimeParameters } from '@opentrons/shared-data' + +import { useLogger } from '/app/logger' +import { OPENTRONS_USB } from '/app/redux/discovery' +import { getStoredProtocols } from '/app/redux/protocol-storage' +import { appShellRequestor } from '/app/redux/shell/remote' +import { MultiSlideout } from '/app/atoms/Slideout/MultiSlideout' +import { ToggleButton } from '/app/atoms/buttons' +import { MiniCard } from '/app/molecules/MiniCard' +import { UploadInput } from '/app/molecules/UploadInput' +import { useTrackCreateProtocolRunEvent } from '/app/organisms/Desktop/Devices/hooks' +import { useCreateRunFromProtocol } from '/app/organisms/Desktop/ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol' +import { ApplyHistoricOffsets } from '/app/organisms/ApplyHistoricOffsets' +import { useOffsetCandidatesForAnalysis } from '/app/organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' +import { FileCard } from '../ChooseRobotSlideout/FileCard' +import { + getRunTimeParameterFilesForRun, + getRunTimeParameterValuesForRun, +} from '/app/transformations/runs' +import { getAnalysisStatus } from '/app/organisms/Desktop/ProtocolsLanding/utils' + +import type { MouseEventHandler } from 'react' +import type { DropdownOption } from '@opentrons/components' +import type { RunTimeParameter } from '@opentrons/shared-data' +import type { Robot } from '/app/redux/discovery/types' +import type { StoredProtocolData } from '/app/redux/protocol-storage' +import type { State } from '/app/redux/types' + +export const CARD_OUTLINE_BORDER_STYLE = css` + border-style: ${BORDERS.styleSolid}; + border-width: 1px; + border-color: ${COLORS.grey30}; + border-radius: ${BORDERS.borderRadius4}; + &:hover { + border-color: ${COLORS.grey55}; + } +` + +const TOOLTIP_DELAY_MS = 2000 + +const _getFileBaseName = (filePath: string): string => { + return filePath.split('/').reverse()[0] +} + +interface ChooseProtocolSlideoutProps { + robot: Robot + onCloseClick: () => void + showSlideout: boolean +} +export function ChooseProtocolSlideoutComponent( + props: ChooseProtocolSlideoutProps +): JSX.Element | null { + const { t } = useTranslation(['device_details', 'shared']) + const navigate = useNavigate() + const logger = useLogger(new URL('', import.meta.url).pathname) + const [targetProps, tooltipProps] = useTooltip() + const [targetPropsHover, tooltipPropsHover] = useHoverTooltip() + const [ + showRestoreValuesTooltip, + setShowRestoreValuesTooltip, + ] = useState(false) + + const { robot, showSlideout, onCloseClick } = props + const { name } = robot + + const [ + selectedProtocol, + setSelectedProtocol, + ] = useState(null) + const [runTimeParametersOverrides, setRunTimeParametersOverrides] = useState< + RunTimeParameter[] + >([]) + const [currentPage, setCurrentPage] = useState(1) + const [hasParamError, setHasParamError] = useState(false) + const [hasMissingFileParam, setHasMissingFileParam] = useState( + runTimeParametersOverrides?.some( + parameter => parameter.type === 'csv_file' + ) ?? false + ) + const [isInputFocused, setIsInputFocused] = useState(false) + + useEffect(() => { + setRunTimeParametersOverrides( + selectedProtocol?.mostRecentAnalysis?.runTimeParameters ?? [] + ) + }, [selectedProtocol]) + useEffect(() => { + setHasParamError(errors.length > 0) + setHasMissingFileParam( + runTimeParametersOverrides.some( + parameter => + parameter.type === 'csv_file' && parameter.file?.file == null + ) + ) + }, [runTimeParametersOverrides]) + + const runTimeParametersFromAnalysis = + selectedProtocol?.mostRecentAnalysis?.runTimeParameters ?? [] + + const hasRunTimeParameters = runTimeParametersFromAnalysis.length > 0 + + const analysisStatus = getAnalysisStatus( + false, + selectedProtocol?.mostRecentAnalysis + ) + const missingAnalysisData = + analysisStatus === 'error' || analysisStatus === 'stale' + + const [shouldApplyOffsets, setShouldApplyOffsets] = useState(true) + const offsetCandidates = useOffsetCandidatesForAnalysis( + (!missingAnalysisData ? selectedProtocol?.mostRecentAnalysis : null) ?? + null, + robot.ip + ) + + const { uploadCsvFile } = useUploadCsvFileMutation( + {}, + robot != null + ? { + hostname: robot.ip, + requestor: + robot?.ip === OPENTRONS_USB ? appShellRequestor : undefined, + } + : null + ) + + const srcFileObjects = + selectedProtocol != null + ? selectedProtocol.srcFiles.map((srcFileBuffer, index) => { + const srcFilePath = selectedProtocol.srcFileNames[index] + return new File([srcFileBuffer], _getFileBaseName(srcFilePath)) + }) + : [] + + const { trackCreateProtocolRunEvent } = useTrackCreateProtocolRunEvent( + selectedProtocol, + name + ) + + const { + createRunFromProtocolSource, + runCreationError, + isCreatingRun, + reset: resetCreateRun, + runCreationErrorCode, + } = useCreateRunFromProtocol( + { + onSuccess: ({ data: runData }) => { + trackCreateProtocolRunEvent({ + name: 'createProtocolRecordResponse', + properties: { success: true }, + }) + navigate(`/devices/${name}/protocol-runs/${runData.id}`) + }, + onError: (error: Error) => { + trackCreateProtocolRunEvent({ + name: 'createProtocolRecordResponse', + properties: { success: false, error: error.message }, + }) + }, + }, + { hostname: robot.ip }, + shouldApplyOffsets + ? offsetCandidates.map(({ vector, location, definitionUri }) => ({ + vector, + location, + definitionUri, + })) + : [] + ) + const handleProceed: MouseEventHandler = () => { + if (selectedProtocol != null) { + trackCreateProtocolRunEvent({ name: 'createProtocolRecordRequest' }) + const dataFilesForProtocolMap = runTimeParametersOverrides.reduce< + Record + >( + (acc, parameter) => + parameter.type === 'csv_file' && parameter.file?.file != null + ? { ...acc, [parameter.variableName]: parameter.file.file } + : acc, + {} + ) + void Promise.all( + Object.entries(dataFilesForProtocolMap).map(([key, file]) => { + const fileResponse = uploadCsvFile(file) + const varName = Promise.resolve(key) + return Promise.all([fileResponse, varName]) + }) + ).then(responseTuples => { + const mappedResolvedCsvVariableToFileId = responseTuples.reduce< + Record + >((acc, [uploadedFileResponse, variableName]) => { + return { ...acc, [variableName]: uploadedFileResponse.data.id } + }, {}) + const runTimeParameterValues = getRunTimeParameterValuesForRun( + runTimeParametersOverrides + ) + const runTimeParameterFiles = getRunTimeParameterFilesForRun( + runTimeParametersOverrides, + mappedResolvedCsvVariableToFileId + ) + createRunFromProtocolSource({ + files: srcFileObjects, + protocolKey: selectedProtocol.protocolKey, + runTimeParameterValues, + runTimeParameterFiles, + }) + }) + } else { + logger.warn('failed to create protocol, no protocol selected') + } + } + + const isRestoreDefaultsLinkEnabled = + runTimeParametersOverrides?.some(parameter => + parameter.type === 'csv_file' + ? parameter.file != null + : parameter.value !== parameter.default + ) ?? false + + const errors: string[] = [] + const runTimeParametersInputs = + runTimeParametersOverrides != null + ? sortRuntimeParameters(runTimeParametersOverrides).map( + (runtimeParam, index) => { + if ('choices' in runtimeParam) { + const dropdownOptions = runtimeParam.choices.map(choice => { + return { name: choice.displayName, value: choice.value } + }) as DropdownOption[] + return ( + { + return choice.value === runtimeParam.value + }) ?? dropdownOptions[0] + } + onClick={choice => { + const clone = runTimeParametersOverrides.map(parameter => { + if ( + runtimeParam.variableName === parameter.variableName && + 'choices' in parameter + ) { + return { + ...parameter, + value: + dropdownOptions.find( + option => option.value === choice + )?.value ?? parameter.default, + } + } + return parameter + }) + setRunTimeParametersOverrides?.(clone as RunTimeParameter[]) + }} + title={runtimeParam.displayName} + width="100%" + dropdownType="neutral" + tooltipText={runtimeParam.description} + /> + ) + } else if ( + runtimeParam.type === 'int' || + runtimeParam.type === 'float' + ) { + const value = runtimeParam.value as number + const id = `InputField_${runtimeParam.variableName}_${index}` + const error = + (Number.isNaN(value) && !isInputFocused) || + value < runtimeParam.min || + value > runtimeParam.max + ? t(`value_out_of_range`, { + min: + runtimeParam.type === 'int' + ? runtimeParam.min + : runtimeParam.min.toFixed(1), + max: + runtimeParam.type === 'int' + ? runtimeParam.max + : runtimeParam.max.toFixed(1), + }) + : null + if (error != null) { + errors.push(error as string) + } + return ( + { + setIsInputFocused(false) + }} + onFocus={() => { + setIsInputFocused(true) + }} + onChange={e => { + const clone = runTimeParametersOverrides.map(parameter => { + if ( + runtimeParam.variableName === parameter.variableName && + (parameter.type === 'int' || parameter.type === 'float') + ) { + return { + ...parameter, + value: + runtimeParam.type === 'int' + ? Math.round(e.target.valueAsNumber) + : e.target.valueAsNumber, + } + } + return parameter + }) + setRunTimeParametersOverrides?.(clone) + }} + /> + ) + } else if (runtimeParam.type === 'bool') { + return ( + + + {runtimeParam.displayName} + + + { + const clone = runTimeParametersOverrides.map( + parameter => { + if ( + runtimeParam.variableName === + parameter.variableName && + parameter.type === 'bool' + ) { + return { + ...parameter, + value: !Boolean(parameter.value), + } + } + return parameter + } + ) + setRunTimeParametersOverrides?.(clone) + }} + height="0.813rem" + label={ + Boolean(runtimeParam.value) + ? t('protocol_details:on') + : t('protocol_details:off') + } + paddingTop={SPACING.spacing2} // manual alignment of SVG with value label + /> + + {Boolean(runtimeParam.value) + ? t('protocol_details:on') + : t('protocol_details:off')} + + + + {runtimeParam.description} + + + ) + } else if (runtimeParam.type === 'csv_file') { + const error = + runtimeParam.file?.file?.type === 'text/csv' + ? null + : t('protocol_details:csv_file_type_required') + if (error != null) { + errors.push(error as string) + } + return ( + + + + {t('protocol_details:csv_file')} + + + {t('protocol_details:csv_required')} + + + {runtimeParam.file == null ? ( + { + const clone = runTimeParametersOverrides.map( + parameter => { + if ( + runtimeParam.variableName === + parameter.variableName + ) { + return { + ...parameter, + file: { file }, + } + } + return parameter + } + ) + setRunTimeParametersOverrides?.(clone) + }} + dragAndDropText={ + + + ), + }} + /> + + } + /> + ) : ( + + )} + + ) + } + } + ) + : null + + const resetRunTimeParameters = (): void => { + const clone = runTimeParametersOverrides.map(parameter => + parameter.type === 'csv_file' + ? { ...parameter, file: null } + : { ...parameter, value: parameter.default } + ) + setRunTimeParametersOverrides(clone as RunTimeParameter[]) + } + + const pageTwoBody = ( + + + { + if (isRestoreDefaultsLinkEnabled) { + resetRunTimeParameters?.() + } else { + setShowRestoreValuesTooltip(true) + setTimeout(() => { + setShowRestoreValuesTooltip(false) + }, TOOLTIP_DELAY_MS) + } + }} + paddingBottom={SPACING.spacing10} + {...targetProps} + > + {t('protocol_details:restore_defaults')} + + + {t('protocol_details:no_custom_values')} + {' '} + + + {runTimeParametersInputs} + + + ) + + const singlePageFooter = ( + + {isCreatingRun ? ( + + ) : ( + t('shared:proceed_to_setup') + )} + + ) + + const multiPageFooter = + currentPage === 1 ? ( + { + setCurrentPage(2) + }} + width="100%" + disabled={isCreatingRun || selectedProtocol == null} + > + {t('shared:continue_to_param')} + + ) : ( + + { + setCurrentPage(1) + }} + width="50%" + > + {t('shared:change_protocol')} + + + {isCreatingRun ? ( + + + {t('shared:confirm_values')} + + ) : ( + t('shared:confirm_values') + )} + + {hasMissingFileParam ? ( + + {t('protocol_details:add_required_csv_file')} + + ) : null} + + ) + + return ( + { + onCloseClick() + setCurrentPage(1) + resetRunTimeParameters() + }} + currentStep={currentPage} + maxSteps={hasRunTimeParameters ? 2 : 1} + title={t('choose_protocol_to_run', { name })} + footer={ + + {currentPage === 1 ? ( + + ) : null} + {hasRunTimeParameters ? multiPageFooter : singlePageFooter} + + } + > + {showSlideout ? ( + currentPage === 1 ? ( + { + if (!isCreatingRun) { + resetCreateRun() + setSelectedProtocol(storedProtocol) + } + }} + robot={robot} + {...{ selectedProtocol, runCreationError, runCreationErrorCode }} + /> + ) : ( + pageTwoBody + ) + ) : null} + + ) +} + +export function ChooseProtocolSlideout( + props: ChooseProtocolSlideoutProps +): JSX.Element | null { + return +} + +interface StoredProtocolListProps { + selectedProtocol: StoredProtocolData | null + handleSelectProtocol: (storedProtocol: StoredProtocolData | null) => void + runCreationError: string | null + runCreationErrorCode: number | null + robot: Robot +} + +function StoredProtocolList(props: StoredProtocolListProps): JSX.Element { + const { + selectedProtocol, + handleSelectProtocol, + runCreationError, + runCreationErrorCode, + robot, + } = props + const { t } = useTranslation(['device_details', 'protocol_details', 'shared']) + const storedProtocols = useSelector((state: State) => + getStoredProtocols(state) + ).filter( + protocol => protocol.mostRecentAnalysis?.robotType === robot.robotModel + ) + useEffect(() => { + handleSelectProtocol(first(storedProtocols) ?? null) + }, []) + + return storedProtocols.length > 0 ? ( + + {storedProtocols.map(storedProtocol => { + const isSelected = + selectedProtocol != null && + storedProtocol.protocolKey === selectedProtocol.protocolKey + const analysisStatus = getAnalysisStatus( + false, + storedProtocol.mostRecentAnalysis + ) + const missingAnalysisData = + analysisStatus === 'error' || analysisStatus === 'stale' + const requiresCsvRunTimeParameter = + analysisStatus === 'parameterRequired' + return ( + + + { + handleSelectProtocol(storedProtocol) + }} + > + + {!missingAnalysisData && !requiresCsvRunTimeParameter ? ( + + + + ) : ( + + )} + + {storedProtocol.mostRecentAnalysis?.metadata + ?.protocolName ?? + first(storedProtocol.srcFileNames) ?? + storedProtocol.protocolKey} + + + {(runCreationError != null || + missingAnalysisData || + requiresCsvRunTimeParameter) && + isSelected ? ( + <> + + + + ) : null} + + + {runCreationError != null && isSelected ? ( + + {runCreationErrorCode === 409 ? ( + + ), + }} + /> + ) : ( + runCreationError + )} + + ) : null} + {requiresCsvRunTimeParameter && isSelected ? ( + + {t('csv_required_for_analysis')} + + ) : null} + {missingAnalysisData && isSelected ? ( + + {analysisStatus === 'stale' + ? t('protocol_analysis_stale') + : t('protocol_analysis_failed')} + { + + ), + }} + /> + } + + ) : null} + + ) + })} + + ) : ( + + + + {t('no_protocols_found')} + + + , + }} + /> + + + ) +} + +const ENABLED_LINK_CSS = css` + ${TYPOGRAPHY.linkPSemiBold} + cursor: ${CURSOR_POINTER}; +` + +const DISABLED_LINK_CSS = css` + ${TYPOGRAPHY.linkPSemiBold} + color: ${COLORS.grey40}; + cursor: ${CURSOR_DEFAULT}; + + &:hover { + color: ${COLORS.grey40}; + } +` diff --git a/app/src/organisms/ChooseRobotSlideout/AvailableRobotOption.tsx b/app/src/organisms/Desktop/ChooseRobotSlideout/AvailableRobotOption.tsx similarity index 86% rename from app/src/organisms/ChooseRobotSlideout/AvailableRobotOption.tsx rename to app/src/organisms/Desktop/ChooseRobotSlideout/AvailableRobotOption.tsx index 7953a0cd353..992e25706dc 100644 --- a/app/src/organisms/ChooseRobotSlideout/AvailableRobotOption.tsx +++ b/app/src/organisms/Desktop/ChooseRobotSlideout/AvailableRobotOption.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useEffect, useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { css } from 'styled-components' import { Trans, useTranslation } from 'react-i18next' @@ -16,18 +16,19 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { MiniCard } from '../../molecules/MiniCard' -import { getRobotModelByName, OPENTRONS_USB } from '../../redux/discovery' -import { getNetworkInterfaces, fetchStatus } from '../../redux/networking' -import { appShellRequestor } from '../../redux/shell/remote' -import OT2_PNG from '../../assets/images/OT2-R_HERO.png' -import FLEX_PNG from '../../assets/images/FLEX.png' -import { useCurrentRunId, useNotifyRunQuery } from '../../resources/runs' +import { MiniCard } from '/app/molecules/MiniCard' +import { getRobotModelByName, OPENTRONS_USB } from '/app/redux/discovery' +import { getNetworkInterfaces, fetchStatus } from '/app/redux/networking' +import { appShellRequestor } from '/app/redux/shell/remote' +import OT2_PNG from '/app/assets/images/OT2-R_HERO.png' +import FLEX_PNG from '/app/assets/images/FLEX.png' +import { useCurrentRunId, useNotifyRunQuery } from '/app/resources/runs' +import type { Dispatch as ReactDispatch } from 'react' import type { IconName } from '@opentrons/components' import type { Runs } from '@opentrons/api-client' -import type { Robot } from '../../redux/discovery/types' -import type { Dispatch, State } from '../../redux/types' +import type { Robot } from '/app/redux/discovery/types' +import type { Dispatch, State } from '/app/redux/types' import type { RobotBusyStatusAction } from '.' interface AvailableRobotOptionProps { @@ -35,7 +36,7 @@ interface AvailableRobotOptionProps { onClick: () => void isSelected: boolean isSelectedRobotOnDifferentSoftwareVersion: boolean - registerRobotBusyStatus: React.Dispatch + registerRobotBusyStatus: ReactDispatch isError?: boolean showIdleOnly?: boolean } @@ -59,7 +60,7 @@ export function AvailableRobotOption( getRobotModelByName(state, robotName) ) - const [isBusy, setIsBusy] = React.useState(true) + const [isBusy, setIsBusy] = useState(true) const currentRunId = useCurrentRunId( { @@ -112,7 +113,7 @@ export function AvailableRobotOption( iconName = 'usb' } - React.useEffect(() => { + useEffect(() => { dispatch(fetchStatus(robotName)) // eslint-disable-next-line react-hooks/exhaustive-deps }, []) diff --git a/app/src/organisms/ChooseRobotSlideout/FileCard.tsx b/app/src/organisms/Desktop/ChooseRobotSlideout/FileCard.tsx similarity index 98% rename from app/src/organisms/ChooseRobotSlideout/FileCard.tsx rename to app/src/organisms/Desktop/ChooseRobotSlideout/FileCard.tsx index 0ed23c4aa2f..e21e806c5cb 100644 --- a/app/src/organisms/ChooseRobotSlideout/FileCard.tsx +++ b/app/src/organisms/Desktop/ChooseRobotSlideout/FileCard.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { css } from 'styled-components' import { ALIGN_CENTER, diff --git a/app/src/organisms/ChooseRobotSlideout/__tests__/ChooseRobotSlideout.test.tsx b/app/src/organisms/Desktop/ChooseRobotSlideout/__tests__/ChooseRobotSlideout.test.tsx similarity index 94% rename from app/src/organisms/ChooseRobotSlideout/__tests__/ChooseRobotSlideout.test.tsx rename to app/src/organisms/Desktop/ChooseRobotSlideout/__tests__/ChooseRobotSlideout.test.tsx index 41dff450fb0..998c82462bc 100644 --- a/app/src/organisms/ChooseRobotSlideout/__tests__/ChooseRobotSlideout.test.tsx +++ b/app/src/organisms/Desktop/ChooseRobotSlideout/__tests__/ChooseRobotSlideout.test.tsx @@ -1,33 +1,33 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, describe, expect, beforeEach } from 'vitest' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { OT2_ROBOT_TYPE } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { getConnectableRobots, getReachableRobots, getScanning, getUnreachableRobots, startDiscovery, -} from '../../../redux/discovery' +} from '/app/redux/discovery' import { mockConnectableRobot, mockReachableRobot, mockUnreachableRobot, -} from '../../../redux/discovery/__fixtures__' -import { getNetworkInterfaces } from '../../../redux/networking' +} from '/app/redux/discovery/__fixtures__' +import { getNetworkInterfaces } from '/app/redux/networking' import { ChooseRobotSlideout } from '..' -import { useNotifyDataReady } from '../../../resources/useNotifyDataReady' +import { useNotifyDataReady } from '/app/resources/useNotifyDataReady' import type { RunTimeParameter } from '@opentrons/shared-data' -vi.mock('../../../redux/discovery') -vi.mock('../../../redux/robot-update') -vi.mock('../../../redux/networking') -vi.mock('../../../resources/useNotifyDataReady') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/robot-update') +vi.mock('/app/redux/networking') +vi.mock('/app/resources/useNotifyDataReady') const render = (props: React.ComponentProps) => { return renderWithProviders( diff --git a/app/src/organisms/ChooseRobotSlideout/__tests__/FileCard.test.tsx b/app/src/organisms/Desktop/ChooseRobotSlideout/__tests__/FileCard.test.tsx similarity index 87% rename from app/src/organisms/ChooseRobotSlideout/__tests__/FileCard.test.tsx rename to app/src/organisms/Desktop/ChooseRobotSlideout/__tests__/FileCard.test.tsx index a0774001b4d..9a151cd1704 100644 --- a/app/src/organisms/ChooseRobotSlideout/__tests__/FileCard.test.tsx +++ b/app/src/organisms/Desktop/ChooseRobotSlideout/__tests__/FileCard.test.tsx @@ -1,19 +1,19 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, describe, expect } from 'vitest' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { FileCard } from '../FileCard' import type { CsvFileParameter } from '@opentrons/shared-data' -vi.mock('../../../redux/discovery') -vi.mock('../../../redux/robot-update') -vi.mock('../../../redux/networking') -vi.mock('../../../resources/useNotifyDataReady') -vi.mock('../../../redux/config') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/robot-update') +vi.mock('/app/redux/networking') +vi.mock('/app/resources/useNotifyDataReady') +vi.mock('/app/redux/config') const render = (props: React.ComponentProps) => { return renderWithProviders( diff --git a/app/src/organisms/Desktop/ChooseRobotSlideout/index.tsx b/app/src/organisms/Desktop/ChooseRobotSlideout/index.tsx new file mode 100644 index 00000000000..1d7dc4e983f --- /dev/null +++ b/app/src/organisms/Desktop/ChooseRobotSlideout/index.tsx @@ -0,0 +1,699 @@ +import { useState, useReducer, useEffect, Fragment } from 'react' +import { useTranslation, Trans } from 'react-i18next' +import { useSelector, useDispatch } from 'react-redux' +import { NavLink } from 'react-router-dom' +import { css } from 'styled-components' + +import { + ALIGN_CENTER, + ALIGN_FLEX_END, + Banner, + BORDERS, + COLORS, + CURSOR_AUTO, + CURSOR_DEFAULT, + CURSOR_POINTER, + DIRECTION_COLUMN, + DIRECTION_ROW, + DISPLAY_INLINE_BLOCK, + DropdownMenu, + Flex, + Icon, + InputField, + JUSTIFY_CENTER, + JUSTIFY_END, + JUSTIFY_FLEX_START, + LegacyStyledText, + Link, + OVERFLOW_WRAP_ANYWHERE, + SIZE_1, + SIZE_4, + SPACING, + Tooltip, + TYPOGRAPHY, + useTooltip, +} from '@opentrons/components' + +import { + FLEX_ROBOT_TYPE, + OT2_ROBOT_TYPE, + sortRuntimeParameters, +} from '@opentrons/shared-data' +import { + getConnectableRobots, + getReachableRobots, + getUnreachableRobots, + getScanning, + startDiscovery, + RE_ROBOT_MODEL_OT2, + RE_ROBOT_MODEL_OT3, +} from '/app/redux/discovery' +import { Slideout } from '/app/atoms/Slideout' +import { MultiSlideout } from '/app/atoms/Slideout/MultiSlideout' +import { ToggleButton } from '/app/atoms/buttons' +import { AvailableRobotOption } from './AvailableRobotOption' +import { UploadInput } from '/app/molecules/UploadInput' +import { FileCard } from './FileCard' + +import type { RobotType, RunTimeParameter } from '@opentrons/shared-data' +import type { DropdownOption } from '@opentrons/components' +import type { SlideoutProps } from '/app/atoms/Slideout' +import type { UseCreateRun } from '/app/organisms/Desktop/ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol' +import type { State, Dispatch } from '/app/redux/types' +import type { Robot } from '/app/redux/discovery/types' + +export const CARD_OUTLINE_BORDER_STYLE = css` + border-style: ${BORDERS.styleSolid}; + border-width: 1px; + border-color: ${COLORS.grey30}; + border-radius: ${BORDERS.borderRadius8}; + &:hover { + border-color: ${COLORS.grey55}; + } +` + +const TOOLTIP_DELAY_MS = 2000 + +interface RobotIsBusyAction { + type: 'robotIsBusy' + robotName: string +} + +interface RobotIsIdleAction { + type: 'robotIsIdle' + robotName: string +} + +interface RobotBusyStatusByName { + [robotName: string]: boolean +} + +export type RobotBusyStatusAction = RobotIsBusyAction | RobotIsIdleAction + +function robotBusyStatusByNameReducer( + state: RobotBusyStatusByName, + action: RobotBusyStatusAction +): RobotBusyStatusByName { + switch (action.type) { + case 'robotIsBusy': { + return { + ...state, + [action.robotName]: true, + } + } + case 'robotIsIdle': { + return { + ...state, + [action.robotName]: false, + } + } + } +} + +interface ChooseRobotSlideoutProps + extends Omit, + Partial { + isSelectedRobotOnDifferentSoftwareVersion: boolean + robotType: RobotType | null + selectedRobot: Robot | null + setSelectedRobot: (robot: Robot | null) => void + runTimeParametersOverrides?: RunTimeParameter[] + setRunTimeParametersOverrides?: (parameters: RunTimeParameter[]) => void + isAnalysisError?: boolean + isAnalysisStale?: boolean + showIdleOnly?: boolean + multiSlideout?: { currentPage: number } | null + setHasParamError?: (isError: boolean) => void + resetRunTimeParameters?: () => void + setHasMissingFileParam?: (isMissing: boolean) => void +} + +export function ChooseRobotSlideout( + props: ChooseRobotSlideoutProps +): JSX.Element { + const { t } = useTranslation(['protocol_details', 'shared', 'app_settings']) + const { + isExpanded, + onCloseClick, + title, + footer, + isAnalysisError = false, + isAnalysisStale = false, + isCreatingRun = false, + isSelectedRobotOnDifferentSoftwareVersion, + reset: resetCreateRun, + runCreationError, + runCreationErrorCode, + selectedRobot, + setSelectedRobot, + robotType, + showIdleOnly = false, + multiSlideout = null, + runTimeParametersOverrides, + setRunTimeParametersOverrides, + setHasParamError, + resetRunTimeParameters, + setHasMissingFileParam, + } = props + + const dispatch = useDispatch() + const isScanning = useSelector((state: State) => getScanning(state)) + const [targetProps, tooltipProps] = useTooltip() + const [ + showRestoreValuesTooltip, + setShowRestoreValuesTooltip, + ] = useState(false) + const [isInputFocused, setIsInputFocused] = useState(false) + + const unhealthyReachableRobots = useSelector((state: State) => + getReachableRobots(state) + ).filter(robot => { + if (robotType === FLEX_ROBOT_TYPE) { + return RE_ROBOT_MODEL_OT3.test(robot.robotModel) + } else if (robotType === OT2_ROBOT_TYPE) { + return RE_ROBOT_MODEL_OT2.test(robot.robotModel) + } else { + return true + } + }) + const unreachableRobots = useSelector((state: State) => + getUnreachableRobots(state) + ).filter(robot => { + if (robotType === FLEX_ROBOT_TYPE) { + return RE_ROBOT_MODEL_OT3.test(robot.robotModel) + } else if (robotType === OT2_ROBOT_TYPE) { + return RE_ROBOT_MODEL_OT2.test(robot.robotModel) + } else { + return true + } + }) + const healthyReachableRobots = useSelector((state: State) => + getConnectableRobots(state) + ).filter(robot => { + if (robotType === FLEX_ROBOT_TYPE) { + return robot.robotModel === FLEX_ROBOT_TYPE + } else if (robotType === OT2_ROBOT_TYPE) { + return robot.robotModel === OT2_ROBOT_TYPE + } else { + return true + } + }) + + const [robotBusyStatusByName, registerRobotBusyStatus] = useReducer( + robotBusyStatusByNameReducer, + {} + ) + + const reducerAvailableRobots = healthyReachableRobots.filter(robot => + showIdleOnly ? !robotBusyStatusByName[robot.name] : robot + ) + const reducerBusyCount = healthyReachableRobots.filter( + robot => robotBusyStatusByName[robot.name] + ).length + + // this useEffect sets the default selection to the first robot in the list. state is managed by the caller + useEffect(() => { + if ( + (selectedRobot == null || + !reducerAvailableRobots.some( + robot => robot.name === selectedRobot.name + )) && + reducerAvailableRobots.length > 0 + ) { + setSelectedRobot(reducerAvailableRobots[0]) + } else if (reducerAvailableRobots.length === 0) { + setSelectedRobot(null) + } + }, [reducerAvailableRobots, selectedRobot, setSelectedRobot]) + + const unavailableCount = + unhealthyReachableRobots.length + unreachableRobots.length + + const pageOneBody = ( + + {isAnalysisError ? ( + {t('protocol_failed_app_analysis')} + ) : null} + {isAnalysisStale ? ( + {t('protocol_outdated_app_analysis')} + ) : null} + + {isScanning ? ( + + + {t('app_settings:searching')} + + + + ) : ( + dispatch(startDiscovery())} + textTransform={TYPOGRAPHY.textTransformCapitalize} + role="button" + css={TYPOGRAPHY.linkPSemiBold} + > + {t('shared:refresh')} + + )} + + {!isScanning && healthyReachableRobots.length === 0 ? ( + + + + {t('no_available_robots_found')} + + + ) : ( + healthyReachableRobots.map(robot => { + const isSelected = + selectedRobot != null && selectedRobot.ip === robot.ip + return ( + + { + if (!isCreatingRun) { + resetCreateRun?.() + setSelectedRobot(robot) + } + }} + isError={runCreationError != null} + isSelected={isSelected} + isSelectedRobotOnDifferentSoftwareVersion={ + isSelectedRobotOnDifferentSoftwareVersion + } + showIdleOnly={showIdleOnly} + registerRobotBusyStatus={registerRobotBusyStatus} + /> + {runCreationError != null && isSelected && ( + + {runCreationErrorCode === 409 ? ( + + ), + }} + /> + ) : ( + runCreationError + )} + + )} + + ) + }) + )} + {!isScanning && unavailableCount > 0 ? ( + + + {showIdleOnly + ? t('unavailable_or_busy_robot_not_listed', { + count: unavailableCount + reducerBusyCount, + }) + : t('unavailable_robot_not_listed', { + count: unavailableCount, + })} + + + {t('view_unavailable_robots')} + + + ) : null} + + ) + + const errors: string[] = [] + const runTimeParameters = + runTimeParametersOverrides != null + ? sortRuntimeParameters(runTimeParametersOverrides).map( + (runtimeParam, index) => { + if ('choices' in runtimeParam) { + const dropdownOptions = runtimeParam.choices.map(choice => { + return { name: choice.displayName, value: choice.value } + }) as DropdownOption[] + return ( + { + return choice.value === runtimeParam.value + }) ?? dropdownOptions[0] + } + onClick={choice => { + const clone = runTimeParametersOverrides.map(parameter => { + if ( + runtimeParam.variableName === parameter.variableName && + 'choices' in parameter + ) { + return { + ...parameter, + value: + dropdownOptions.find( + option => option.value === choice + )?.value ?? parameter.default, + } + } + return parameter + }) + setRunTimeParametersOverrides?.(clone as RunTimeParameter[]) + }} + title={runtimeParam.displayName} + width="100%" + dropdownType="neutral" + tooltipText={runtimeParam.description} + /> + ) + } else if ( + runtimeParam.type === 'int' || + runtimeParam.type === 'float' + ) { + const value = runtimeParam.value as number + const id = `InputField_${runtimeParam.variableName}_${index}` + const error = + (Number.isNaN(value) && !isInputFocused) || + value < runtimeParam.min || + value > runtimeParam.max + ? t(`value_out_of_range`, { + min: + runtimeParam.type === 'int' + ? runtimeParam.min + : runtimeParam.min.toFixed(1), + max: + runtimeParam.type === 'int' + ? runtimeParam.max + : runtimeParam.max.toFixed(1), + }) + : null + if (error != null) { + errors.push(error as string) + } + return ( + { + setIsInputFocused(false) + }} + onFocus={() => { + setIsInputFocused(true) + }} + onChange={e => { + const clone = runTimeParametersOverrides.map(parameter => { + if ( + runtimeParam.variableName === parameter.variableName && + (parameter.type === 'int' || parameter.type === 'float') + ) { + return { + ...parameter, + value: + runtimeParam.type === 'int' + ? Math.round(e.target.valueAsNumber) + : e.target.valueAsNumber, + } + } + return parameter + }) + setRunTimeParametersOverrides?.(clone) + }} + /> + ) + } else if (runtimeParam.type === 'bool') { + return ( + + + {runtimeParam.displayName} + + + { + const clone = runTimeParametersOverrides.map( + parameter => { + if ( + runtimeParam.variableName === + parameter.variableName && + parameter.type === 'bool' + ) { + return { + ...parameter, + value: !Boolean(parameter.value), + } + } + return parameter + } + ) + setRunTimeParametersOverrides?.(clone) + }} + height="0.813rem" + label={Boolean(runtimeParam.value) ? t('on') : t('off')} + paddingTop={SPACING.spacing2} // manual alignment of SVG with value label + /> + + {Boolean(runtimeParam.value) ? t('on') : t('off')} + + + + {runtimeParam.description} + + + ) + } else if (runtimeParam.type === 'csv_file') { + if (runtimeParam.file?.file != null) { + setHasMissingFileParam?.(false) + } + const error = + runtimeParam.file?.file?.type === 'text/csv' + ? null + : t('csv_file_type_required') + if (error != null) { + errors.push(error as string) + } + return ( + + + + {t('csv_file')} + + + {runtimeParam.file == null ? ( + { + const clone = runTimeParametersOverrides.map( + parameter => { + if ( + runtimeParam.variableName === + parameter.variableName + ) { + return { + ...parameter, + file: { file }, + } + } + return parameter + } + ) + setRunTimeParametersOverrides?.(clone) + }} + dragAndDropText={ + + , + }} + /> + + } + /> + ) : ( + + )} + + ) + } + } + ) + : null + + const hasEmptyRtpFile = + runTimeParametersOverrides?.some( + runtimeParam => + runtimeParam.type === 'csv_file' && runtimeParam.file == null + ) ?? false + setHasParamError?.(errors.length > 0 || hasEmptyRtpFile) + + const isRestoreDefaultsLinkEnabled = + runTimeParametersOverrides?.some(parameter => { + return parameter.type === 'csv_file' + ? parameter.file != null + : parameter.value !== parameter.default + }) ?? false + + const pageTwoBody = + runTimeParametersOverrides != null ? ( + + + { + if (isRestoreDefaultsLinkEnabled) { + resetRunTimeParameters?.() + } else { + setShowRestoreValuesTooltip(true) + setTimeout(() => { + setShowRestoreValuesTooltip(false) + }, TOOLTIP_DELAY_MS) + } + }} + paddingBottom={SPACING.spacing10} + {...targetProps} + > + {t('restore_defaults')} + + + {t('no_custom_values')} + + + + {runTimeParameters} + + + ) : null + + return multiSlideout != null ? ( + + {multiSlideout.currentPage === 1 ? pageOneBody : pageTwoBody} + + ) : ( + + {pageOneBody} + + ) +} + +const ENABLED_LINK_CSS = css` + ${TYPOGRAPHY.linkPSemiBold} + cursor: ${CURSOR_POINTER}; +` + +const DISABLED_LINK_CSS = css` + ${TYPOGRAPHY.linkPSemiBold} + color: ${COLORS.grey40}; + cursor: ${CURSOR_DEFAULT}; + + &:hover { + color: ${COLORS.grey40}; + } +` diff --git a/app/src/organisms/ChooseRobotToRunProtocolSlideout/__tests__/ChooseRobotToRunProtocolSlideout.test.tsx b/app/src/organisms/Desktop/ChooseRobotToRunProtocolSlideout/__tests__/ChooseRobotToRunProtocolSlideout.test.tsx similarity index 87% rename from app/src/organisms/ChooseRobotToRunProtocolSlideout/__tests__/ChooseRobotToRunProtocolSlideout.test.tsx rename to app/src/organisms/Desktop/ChooseRobotToRunProtocolSlideout/__tests__/ChooseRobotToRunProtocolSlideout.test.tsx index 057533ce778..2bab265eb52 100644 --- a/app/src/organisms/ChooseRobotToRunProtocolSlideout/__tests__/ChooseRobotToRunProtocolSlideout.test.tsx +++ b/app/src/organisms/Desktop/ChooseRobotToRunProtocolSlideout/__tests__/ChooseRobotToRunProtocolSlideout.test.tsx @@ -1,51 +1,52 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen, waitFor } from '@testing-library/react' import { when } from 'vitest-when' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useTrackCreateProtocolRunEvent } from '../../../organisms/Devices/hooks' -import { useCloseCurrentRun } from '../../../organisms/ProtocolUpload/hooks' -import { useCurrentRunStatus } from '../../../organisms/RunTimeControl/hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useTrackCreateProtocolRunEvent } from '/app/organisms/Desktop/Devices/hooks' +import { useCurrentRunStatus } from '/app/organisms/RunTimeControl/hooks' import { getConnectableRobots, getReachableRobots, getScanning, getUnreachableRobots, startDiscovery, -} from '../../../redux/discovery' -import { getRobotUpdateDisplayInfo } from '../../../redux/robot-update' +} from '/app/redux/discovery' +import { useIsRobotOnWrongVersionOfSoftware } from '/app/redux/robot-update' import { mockConnectableRobot, mockReachableRobot, mockUnreachableRobot, -} from '../../../redux/discovery/__fixtures__' -import { getNetworkInterfaces } from '../../../redux/networking' +} from '/app/redux/discovery/__fixtures__' +import { getNetworkInterfaces } from '/app/redux/networking' import { storedProtocolData as storedProtocolDataFixture, storedProtocolDataWithCsvRunTimeParameter, -} from '../../../redux/protocol-storage/__fixtures__' +} from '/app/redux/protocol-storage/__fixtures__' import { useCreateRunFromProtocol } from '../useCreateRunFromProtocol' -import { useOffsetCandidatesForAnalysis } from '../../ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' +import { useOffsetCandidatesForAnalysis } from '/app/organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' import { ChooseRobotToRunProtocolSlideout } from '../' -import { useNotifyDataReady } from '../../../resources/useNotifyDataReady' -import { useCurrentRunId } from '../../../resources/runs' +import { useNotifyDataReady } from '/app/resources/useNotifyDataReady' +import { useCurrentRunId, useCloseCurrentRun } from '/app/resources/runs' -import type { State } from '../../../redux/types' +import type { State } from '/app/redux/types' -vi.mock('../../../organisms/Devices/hooks') -vi.mock('../../../organisms/ProtocolUpload/hooks') -vi.mock('../../../organisms/RunTimeControl/hooks') -vi.mock('../../../redux/config') -vi.mock('../../../redux/discovery') -vi.mock('../../../redux/robot-update') -vi.mock('../../../redux/networking') +vi.mock('/app/organisms/Desktop/Devices/hooks') +vi.mock('/app/organisms/ProtocolUpload/hooks') +vi.mock('/app/organisms/RunTimeControl/hooks') +vi.mock('/app/redux/config') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/robot-update') +vi.mock('/app/redux/networking') vi.mock('../useCreateRunFromProtocol') -vi.mock('../../ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis') -vi.mock('../../../resources/useNotifyDataReady') -vi.mock('../../../resources/runs') +vi.mock( + '/app/organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' +) +vi.mock('/app/resources/useNotifyDataReady') +vi.mock('/app/resources/runs') const render = ( props: React.ComponentProps @@ -72,11 +73,7 @@ describe('ChooseRobotToRunProtocolSlideout', () => { mockTrackCreateProtocolRunEvent = vi.fn( () => new Promise(resolve => resolve({})) ) - vi.mocked(getRobotUpdateDisplayInfo).mockReturnValue({ - autoUpdateAction: '', - autoUpdateDisabledReason: null, - updateFromFileDisabledReason: null, - }) + vi.mocked(useIsRobotOnWrongVersionOfSoftware).mockReturnValue(false) vi.mocked(getConnectableRobots).mockReturnValue([mockConnectableRobot]) vi.mocked(getUnreachableRobots).mockReturnValue([mockUnreachableRobot]) vi.mocked(getReachableRobots).mockReturnValue([mockReachableRobot]) @@ -221,11 +218,7 @@ describe('ChooseRobotToRunProtocolSlideout', () => { expect(mockTrackCreateProtocolRunEvent).toHaveBeenCalled() }) it('if selected robot is on a different version of the software than the app, disable CTA and show link to device details in options', () => { - vi.mocked(getRobotUpdateDisplayInfo).mockReturnValue({ - autoUpdateAction: 'upgrade', - autoUpdateDisabledReason: null, - updateFromFileDisabledReason: null, - }) + vi.mocked(useIsRobotOnWrongVersionOfSoftware).mockReturnValue(true) render({ storedProtocolData: storedProtocolDataFixture, onCloseClick: vi.fn(), @@ -320,7 +313,7 @@ describe('ChooseRobotToRunProtocolSlideout', () => { runCreatedAt: '2022-05-11T13:33:51.012179+00:00', } when(vi.mocked(useOffsetCandidatesForAnalysis)) - .calledWith(storedProtocolDataFixture.mostRecentAnalysis, '127.0.0.1') + .calledWith(storedProtocolDataFixture.mostRecentAnalysis, null) .thenReturn([mockOffsetCandidate]) vi.mocked(getConnectableRobots).mockReturnValue([ mockConnectableRobot, @@ -333,7 +326,7 @@ describe('ChooseRobotToRunProtocolSlideout', () => { }) expect(vi.mocked(useCreateRunFromProtocol)).toHaveBeenCalledWith( expect.any(Object), - { hostname: '127.0.0.1' }, + null, [ { vector: mockOffsetCandidate.vector, @@ -369,11 +362,8 @@ describe('ChooseRobotToRunProtocolSlideout', () => { runCreatedAt: '2022-05-11T13:33:51.012179+00:00', } when(vi.mocked(useOffsetCandidatesForAnalysis)) - .calledWith(storedProtocolDataFixture.mostRecentAnalysis, '127.0.0.1') + .calledWith(storedProtocolDataFixture.mostRecentAnalysis, null) .thenReturn([mockOffsetCandidate]) - when(vi.mocked(useOffsetCandidatesForAnalysis)) - .calledWith(storedProtocolDataFixture.mostRecentAnalysis, 'otherIp') - .thenReturn([]) vi.mocked(getConnectableRobots).mockReturnValue([ mockConnectableRobot, { ...mockConnectableRobot, name: 'otherRobot', ip: 'otherIp' }, @@ -393,10 +383,9 @@ describe('ChooseRobotToRunProtocolSlideout', () => { }) fireEvent.click(proceedButton) fireEvent.click(screen.getByRole('button', { name: 'Confirm values' })) - expect(vi.mocked(useCreateRunFromProtocol)).nthCalledWith( - 3, + expect(vi.mocked(useCreateRunFromProtocol)).toHaveBeenLastCalledWith( expect.any(Object), - { hostname: '127.0.0.1' }, + null, [ { vector: mockOffsetCandidate.vector, @@ -405,11 +394,6 @@ describe('ChooseRobotToRunProtocolSlideout', () => { }, ] ) - expect(vi.mocked(useCreateRunFromProtocol)).toHaveBeenLastCalledWith( - expect.any(Object), - { hostname: 'otherIp' }, - [] - ) }) it('disables proceed button if no available robots', () => { @@ -452,9 +436,7 @@ describe('ChooseRobotToRunProtocolSlideout', () => { fireEvent.click(proceedButton) const confirm = screen.getByRole('button', { name: 'Confirm values' }) fireEvent.pointerEnter(confirm) - await waitFor(() => - screen.findByText('Add the required CSV file to continue.') - ) + await screen.findByText('Add the required CSV file to continue.') }) }) diff --git a/app/src/organisms/Desktop/ChooseRobotToRunProtocolSlideout/index.tsx b/app/src/organisms/Desktop/ChooseRobotToRunProtocolSlideout/index.tsx new file mode 100644 index 00000000000..e3946a4cc99 --- /dev/null +++ b/app/src/organisms/Desktop/ChooseRobotToRunProtocolSlideout/index.tsx @@ -0,0 +1,369 @@ +import { useState } from 'react' +import first from 'lodash/first' +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' + +import { + ALIGN_CENTER, + DIRECTION_COLUMN, + DIRECTION_ROW, + Flex, + Icon, + NO_WRAP, + PrimaryButton, + SecondaryButton, + SPACING, + Tooltip, + useHoverTooltip, +} from '@opentrons/components' +import { + useUploadCsvFileMutation, + ApiHostProvider, +} from '@opentrons/react-api-client' + +import { useIsRobotOnWrongVersionOfSoftware } from '/app/redux/robot-update' +import { OPENTRONS_USB } from '/app/redux/discovery' +import { appShellRequestor } from '/app/redux/shell/remote' +import { useTrackCreateProtocolRunEvent } from '/app/organisms/Desktop/Devices/hooks' +import { + getRunTimeParameterFilesForRun, + getRunTimeParameterValuesForRun, +} from '/app/transformations/runs' +import { ApplyHistoricOffsets } from '/app/organisms/ApplyHistoricOffsets' +import { useOffsetCandidatesForAnalysis } from '/app/organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' +import { ChooseRobotSlideout } from '../ChooseRobotSlideout' +import { useCreateRunFromProtocol } from './useCreateRunFromProtocol' + +import type { MouseEventHandler } from 'react' +import type { StyleProps } from '@opentrons/components' +import type { RunTimeParameter } from '@opentrons/shared-data' +import type { Robot } from '/app/redux/discovery/types' +import type { StoredProtocolData } from '/app/redux/protocol-storage' + +const _getFileBaseName = (filePath: string): string => { + return filePath.split('/').reverse()[0] +} +interface ChooseRobotToRunProtocolSlideoutProps extends StyleProps { + storedProtocolData: StoredProtocolData + onCloseClick: () => void + showSlideout: boolean +} + +interface ChooseRobotToRunProtocolSlideoutComponentProps + extends ChooseRobotToRunProtocolSlideoutProps { + selectedRobot: Robot | null + setSelectedRobot: (robot: Robot | null) => void +} + +export function ChooseRobotToRunProtocolSlideoutComponent( + props: ChooseRobotToRunProtocolSlideoutComponentProps +): JSX.Element | null { + const { t } = useTranslation(['protocol_details', 'shared', 'app_settings']) + const { + storedProtocolData, + showSlideout, + onCloseClick, + selectedRobot, + setSelectedRobot, + } = props + const navigate = useNavigate() + const [shouldApplyOffsets, setShouldApplyOffsets] = useState(true) + const { + protocolKey, + srcFileNames, + srcFiles, + mostRecentAnalysis, + } = storedProtocolData + const [currentPage, setCurrentPage] = useState(1) + const { trackCreateProtocolRunEvent } = useTrackCreateProtocolRunEvent( + storedProtocolData, + selectedRobot?.name ?? '' + ) + const runTimeParameters = + storedProtocolData.mostRecentAnalysis?.runTimeParameters ?? [] + + const [runTimeParametersOverrides, setRunTimeParametersOverrides] = useState< + RunTimeParameter[] + >(runTimeParameters) + const [hasParamError, setHasParamError] = useState(false) + const [hasMissingFileParam, setHasMissingFileParam] = useState( + runTimeParameters?.some(parameter => parameter.type === 'csv_file') ?? false + ) + + const [targetProps, tooltipProps] = useHoverTooltip() + + const offsetCandidates = useOffsetCandidatesForAnalysis( + mostRecentAnalysis, + null + ) + + const { uploadCsvFile } = useUploadCsvFileMutation() + + const { + createRunFromProtocolSource, + runCreationError, + reset: resetCreateRun, + isCreatingRun, + runCreationErrorCode, + } = useCreateRunFromProtocol( + { + onSuccess: ({ data: runData }) => { + if (selectedRobot != null) { + trackCreateProtocolRunEvent({ + name: 'createProtocolRecordResponse', + properties: { success: true }, + }) + navigate(`/devices/${selectedRobot.name}/protocol-runs/${runData.id}`) + } + }, + onError: (error: Error) => { + trackCreateProtocolRunEvent({ + name: 'createProtocolRecordResponse', + properties: { success: false, error: error.message }, + }) + }, + }, + null, + shouldApplyOffsets + ? offsetCandidates.map(({ vector, location, definitionUri }) => ({ + vector, + location, + definitionUri, + })) + : [] + ) + const handleProceed: MouseEventHandler = () => { + trackCreateProtocolRunEvent({ name: 'createProtocolRecordRequest' }) + const dataFilesForProtocolMap = runTimeParametersOverrides.reduce< + Record + >( + (acc, parameter) => + parameter.type === 'csv_file' && parameter.file?.file != null + ? { ...acc, [parameter.variableName]: parameter.file.file } + : acc, + {} + ) + void Promise.all( + Object.entries(dataFilesForProtocolMap).map(([key, file]) => { + const fileResponse = uploadCsvFile(file) + const varName = Promise.resolve(key) + return Promise.all([fileResponse, varName]) + }) + ).then(responseTuples => { + const mappedResolvedCsvVariableToFileId = responseTuples.reduce< + Record + >((acc, [uploadedFileResponse, variableName]) => { + return { ...acc, [variableName]: uploadedFileResponse.data.id } + }, {}) + const runTimeParameterValues = getRunTimeParameterValuesForRun( + runTimeParametersOverrides + ) + const runTimeParameterFiles = getRunTimeParameterFilesForRun( + runTimeParametersOverrides, + mappedResolvedCsvVariableToFileId + ) + createRunFromProtocolSource({ + files: srcFileObjects, + protocolKey, + runTimeParameterValues, + runTimeParameterFiles, + }) + }) + } + + const isSelectedRobotOnDifferentSoftwareVersion = useIsRobotOnWrongVersionOfSoftware( + selectedRobot?.name ?? '' + ) + + const hasRunTimeParameters = runTimeParameters.length > 0 + + if ( + protocolKey == null || + srcFileNames == null || + srcFiles == null || + mostRecentAnalysis == null + ) { + // TODO: do more robust corrupt file catching and handling here + return null + } + const srcFileObjects = srcFiles.map((srcFileBuffer, index) => { + const srcFilePath = srcFileNames[index] + return new File([srcFileBuffer], _getFileBaseName(srcFilePath)) + }) + const protocolDisplayName = + mostRecentAnalysis?.metadata?.protocolName ?? + first(srcFileNames) ?? + protocolKey + + // intentionally show both robot types if analysis fails + const robotType = + mostRecentAnalysis != null && mostRecentAnalysis.result !== 'not-ok' + ? mostRecentAnalysis?.robotType ?? null + : null + + const singlePageButton = ( + + {isCreatingRun ? ( + + ) : ( + t('shared:proceed_to_setup') + )} + + ) + + const offsetsComponent = ( + + ) + + const footer = ( + + {hasRunTimeParameters ? ( + currentPage === 1 ? ( + <> + {offsetsComponent} + { + setCurrentPage(2) + }} + width="100%" + disabled={ + isCreatingRun || + selectedRobot == null || + isSelectedRobotOnDifferentSoftwareVersion + } + > + {t('shared:continue_to_param')} + + + ) : ( + + { + setCurrentPage(1) + }} + width="50%" + > + {t('shared:change_robot')} + + + {isCreatingRun ? ( + + + {t('shared:confirm_values')} + + ) : ( + t('shared:confirm_values') + )} + + {hasMissingFileParam ? ( + + {t('add_required_csv_file')} + + ) : null} + + ) + ) : ( + <> + {offsetsComponent} + {singlePageButton} + + )} + + ) + + const resetRunTimeParameters = (): void => { + const clone = runTimeParametersOverrides.map(parameter => + parameter.type === 'csv_file' + ? { ...parameter, file: null } + : { ...parameter, value: parameter.default } + ) + setRunTimeParametersOverrides(clone as RunTimeParameter[]) + } + + return ( + { + onCloseClick() + resetRunTimeParameters() + setCurrentPage(1) + setSelectedRobot(null) + }} + title={ + hasRunTimeParameters && currentPage === 2 + ? t('select_parameters_for_robot', { + robot_name: selectedRobot?.name, + }) + : t('choose_robot_to_run', { + protocol_name: protocolDisplayName, + }) + } + runTimeParametersOverrides={runTimeParametersOverrides} + setRunTimeParametersOverrides={setRunTimeParametersOverrides} + footer={footer} + selectedRobot={selectedRobot} + setSelectedRobot={setSelectedRobot} + robotType={robotType} + isCreatingRun={isCreatingRun} + reset={resetCreateRun} + runCreationError={runCreationError} + runCreationErrorCode={runCreationErrorCode} + showIdleOnly + setHasParamError={setHasParamError} + resetRunTimeParameters={resetRunTimeParameters} + setHasMissingFileParam={setHasMissingFileParam} + /> + ) +} + +export function ChooseRobotToRunProtocolSlideout( + props: ChooseRobotToRunProtocolSlideoutProps +): JSX.Element | null { + const [selectedRobot, setSelectedRobot] = useState(null) + return ( + + + + ) +} diff --git a/app/src/organisms/ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol.ts b/app/src/organisms/Desktop/ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol.ts similarity index 96% rename from app/src/organisms/ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol.ts rename to app/src/organisms/Desktop/ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol.ts index 1dcddfe12ce..58d1bdf08df 100644 --- a/app/src/organisms/ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol.ts +++ b/app/src/organisms/Desktop/ChooseRobotToRunProtocolSlideout/useCreateRunFromProtocol.ts @@ -7,7 +7,7 @@ import { import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' -import { getValidCustomLabwareFiles } from '../../redux/custom-labware/selectors' +import { getValidCustomLabwareFiles } from '/app/redux/custom-labware/selectors' import type { UseMutateFunction } from 'react-query' import type { @@ -17,7 +17,7 @@ import type { } from '@opentrons/api-client' import type { UseCreateRunMutationOptions } from '@opentrons/react-api-client/src/runs/useCreateRunMutation' import type { CreateProtocolVariables } from '@opentrons/react-api-client/src/protocols/useCreateProtocolMutation' -import type { State } from '../../redux/types' +import type { State } from '/app/redux/types' export interface UseCreateRun { createRunFromProtocolSource: UseMutateFunction< diff --git a/app/src/organisms/Devices/CalibrationStatusBanner.tsx b/app/src/organisms/Desktop/Devices/CalibrationStatusBanner.tsx similarity index 95% rename from app/src/organisms/Devices/CalibrationStatusBanner.tsx rename to app/src/organisms/Desktop/Devices/CalibrationStatusBanner.tsx index 27374e10d5d..e601ea19799 100644 --- a/app/src/organisms/Devices/CalibrationStatusBanner.tsx +++ b/app/src/organisms/Desktop/Devices/CalibrationStatusBanner.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { Link as RouterLink } from 'react-router-dom' import { ALIGN_CENTER, + Banner, COLORS, DIRECTION_ROW, Flex, @@ -14,7 +14,6 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { Banner } from '../../atoms/Banner' import { useCalibrationTaskList } from './hooks' interface CalibrationStatusBannerProps { diff --git a/app/src/organisms/ChangePipette/CheckPipettesButton.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/CheckPipettesButton.tsx similarity index 93% rename from app/src/organisms/ChangePipette/CheckPipettesButton.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/CheckPipettesButton.tsx index a5abf0a9732..873d631e513 100644 --- a/app/src/organisms/ChangePipette/CheckPipettesButton.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/CheckPipettesButton.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { usePipettesQuery } from '@opentrons/react-api-client' import { @@ -14,9 +14,11 @@ import { } from '@opentrons/components' import { DETACH } from './constants' +import type { ReactNode } from 'react' + export interface CheckPipetteButtonProps { robotName: string - children?: React.ReactNode + children?: ReactNode direction?: 'detach' | 'attach' onDone?: () => void } @@ -26,7 +28,7 @@ export function CheckPipettesButton( ): JSX.Element | null { const { onDone, children, direction } = props const { t } = useTranslation('change_pipette') - const [isPending, setIsPending] = React.useState(false) + const [isPending, setIsPending] = useState(false) const { refetch: refetchPipettes } = usePipettesQuery( { refresh: true }, { diff --git a/app/src/organisms/ChangePipette/ClearDeckModal.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/ClearDeckModal.tsx similarity index 97% rename from app/src/organisms/ChangePipette/ClearDeckModal.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/ClearDeckModal.tsx index dc89fc4ff7c..5fb2cd79de1 100644 --- a/app/src/organisms/ChangePipette/ClearDeckModal.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/ClearDeckModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import { DIRECTION_COLUMN, diff --git a/app/src/organisms/ChangePipette/ConfirmPipette.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/ConfirmPipette.tsx similarity index 96% rename from app/src/organisms/ChangePipette/ConfirmPipette.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/ConfirmPipette.tsx index 6e53a5507f3..f756059d003 100644 --- a/app/src/organisms/ChangePipette/ConfirmPipette.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/ConfirmPipette.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { OT2_ROBOT_TYPE } from '@opentrons/shared-data' import { @@ -10,7 +10,7 @@ import { SecondaryButton, } from '@opentrons/components' import { CheckPipettesButton } from './CheckPipettesButton' -import { SimpleWizardBody } from '../../molecules/SimpleWizardBody' +import { SimpleWizardBody } from '/app/molecules/SimpleWizardBody' import { LevelPipette } from './LevelPipette' import type { @@ -18,8 +18,8 @@ import type { PipetteModelSpecs, PipetteDisplayCategory, } from '@opentrons/shared-data' -import type { PipetteOffsetCalibration } from '../../redux/calibration/types' -import type { Mount } from '../../redux/pipettes/types' +import type { PipetteOffsetCalibration } from '/app/redux/calibration/types' +import type { Mount } from '/app/redux/pipettes/types' export interface ConfirmPipetteProps { robotName: string diff --git a/app/src/organisms/ChangePipette/ExitModal.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/ExitModal.tsx similarity index 92% rename from app/src/organisms/ChangePipette/ExitModal.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/ExitModal.tsx index d2848c01bf5..619b35cea36 100644 --- a/app/src/organisms/ChangePipette/ExitModal.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/ExitModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { COLORS, @@ -7,7 +6,7 @@ import { AlertPrimaryButton, TEXT_TRANSFORM_CAPITALIZE, } from '@opentrons/components' -import { SimpleWizardBody } from '../../molecules/SimpleWizardBody' +import { SimpleWizardBody } from '/app/molecules/SimpleWizardBody' import type { Direction } from './types' diff --git a/app/src/organisms/ChangePipette/InstructionStep.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/InstructionStep.tsx similarity index 90% rename from app/src/organisms/ChangePipette/InstructionStep.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/InstructionStep.tsx index d70c6f9e69f..5b6338be6a5 100644 --- a/app/src/organisms/ChangePipette/InstructionStep.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/InstructionStep.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Box, Flex, JUSTIFY_SPACE_EVENLY, SPACING } from '@opentrons/components' import type { PipetteChannels, @@ -30,13 +30,13 @@ export function InstructionStep(props: Props): JSX.Element { const display = displayCategory === 'GEN2' ? new URL( - `../../assets/images/change-pip/${direction}-${String( + `../../../../assets/images/change-pip/${direction}-${String( mount )}-${channelsKey}-GEN2-${diagram}@3x.png`, import.meta.url ).href : new URL( - `../../assets/images/change-pip/${direction}-${String( + `../../../../assets/images/change-pip/${direction}-${String( mount )}-${channelsKey}-${diagram}@3x.png`, import.meta.url diff --git a/app/src/organisms/ChangePipette/Instructions.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/Instructions.tsx similarity index 98% rename from app/src/organisms/ChangePipette/Instructions.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/Instructions.tsx index e526d202996..cffdceca835 100644 --- a/app/src/organisms/ChangePipette/Instructions.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/Instructions.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useEffect } from 'react' import { css } from 'styled-components' import { Trans, useTranslation } from 'react-i18next' import { @@ -23,7 +23,7 @@ import type { PipetteModelSpecs, PipetteDisplayCategory, } from '@opentrons/shared-data' -import type { Mount } from '../../redux/pipettes/types' +import type { Mount } from '/app/redux/pipettes/types' import type { Direction } from './types' interface Props { @@ -69,7 +69,7 @@ export function Instructions(props: Props): JSX.Element { } = props const { t } = useTranslation('change_pipette') - React.useEffect(() => { + useEffect(() => { if (direction === 'detach' && currentStepCount === 0) { nextStep() } diff --git a/app/src/organisms/ChangePipette/LevelPipette.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/LevelPipette.tsx similarity index 90% rename from app/src/organisms/ChangePipette/LevelPipette.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/LevelPipette.tsx index 79faad0f9d9..fb1120daec7 100644 --- a/app/src/organisms/ChangePipette/LevelPipette.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/LevelPipette.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import { css } from 'styled-components' @@ -14,7 +13,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import type { Mount } from '../../redux/pipettes/types' +import type { Mount } from '/app/redux/pipettes/types' interface LevelPipetteProps { mount: Mount @@ -27,6 +26,11 @@ export function LevelingVideo(props: { mount: Mount }): JSX.Element { const { pipetteName, mount } = props + const video = new URL( + `../../../../assets/videos/pip-leveling/${pipetteName}-${mount}.webm`, + import.meta.url + ).href + return ( ) } diff --git a/app/src/organisms/ChangePipette/PipetteSelection.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/PipetteSelection.tsx similarity index 90% rename from app/src/organisms/ChangePipette/PipetteSelection.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/PipetteSelection.tsx index 9ea9817eab6..c306f132154 100644 --- a/app/src/organisms/ChangePipette/PipetteSelection.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/PipetteSelection.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { DIRECTION_COLUMN, @@ -8,7 +8,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' import { OT3_PIPETTES } from '@opentrons/shared-data' -import { PipetteSelect } from '../../molecules/PipetteSelect' +import { PipetteSelect } from '/app/molecules/PipetteSelect' export type PipetteSelectionProps = React.ComponentProps diff --git a/app/src/organisms/ChangePipette/__tests__/ChangePipette.test.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/ChangePipette.test.tsx similarity index 88% rename from app/src/organisms/ChangePipette/__tests__/ChangePipette.test.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/__tests__/ChangePipette.test.tsx index 40b2174194b..89112a484d4 100644 --- a/app/src/organisms/ChangePipette/__tests__/ChangePipette.test.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/ChangePipette.test.tsx @@ -1,21 +1,21 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, describe, expect, beforeEach } from 'vitest' import { fireEvent, screen } from '@testing-library/react' import { getPipetteNameSpecs } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { getHasCalibrationBlock } from '../../../redux/config' -import { getMovementStatus } from '../../../redux/robot-controls' -import { getCalibrationForPipette } from '../../../redux/calibration' -import { InProgressModal } from '../../../molecules/InProgressModal/InProgressModal' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getHasCalibrationBlock } from '/app/redux/config' +import { getMovementStatus } from '/app/redux/robot-controls' +import { getCalibrationForPipette } from '/app/redux/calibration' +import { InProgressModal } from '/app/molecules/InProgressModal/InProgressModal' import { getRequestById, SUCCESS, useDispatchApiRequests, -} from '../../../redux/robot-api' -import { useAttachedPipettes } from '../../Devices/hooks' +} from '/app/redux/robot-api' +import { useAttachedPipettes } from '/app/resources/instruments' import { PipetteSelection } from '../PipetteSelection' import { ExitModal } from '../ExitModal' import { ConfirmPipette } from '../ConfirmPipette' @@ -23,8 +23,8 @@ import { ChangePipette } from '..' import type { NavigateFunction } from 'react-router-dom' import type { PipetteNameSpecs } from '@opentrons/shared-data' -import type { AttachedPipette } from '../../../redux/pipettes/types' -import type { DispatchApiRequestType } from '../../../redux/robot-api' +import type { AttachedPipette } from '/app/redux/pipettes/types' +import type { DispatchApiRequestType } from '/app/redux/robot-api' const mockNavigate = vi.fn() @@ -43,16 +43,16 @@ vi.mock('@opentrons/shared-data', async importOriginal => { getPipetteNameSpecs: vi.fn(), } }) -vi.mock('../../../redux/config') -vi.mock('../../../redux/robot-controls') -vi.mock('../../../redux/calibration') -vi.mock('../../../redux/robot-api') +vi.mock('/app/redux/config') +vi.mock('/app/redux/robot-controls') +vi.mock('/app/redux/calibration') +vi.mock('/app/redux/robot-api') vi.mock('../PipetteSelection') vi.mock('../ExitModal') -vi.mock('../../../molecules/InProgressModal/InProgressModal') +vi.mock('/app/molecules/InProgressModal/InProgressModal') vi.mock('../ConfirmPipette') -vi.mock('../../Devices/hooks') -vi.mock('../../../assets/images') +vi.mock('/app/resources/instruments') +vi.mock('/app/assets/images') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/ChangePipette/__tests__/CheckPipettesButton.test.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/CheckPipettesButton.test.tsx similarity index 95% rename from app/src/organisms/ChangePipette/__tests__/CheckPipettesButton.test.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/__tests__/CheckPipettesButton.test.tsx index bb1b9a1d842..a5fa3a50bd6 100644 --- a/app/src/organisms/ChangePipette/__tests__/CheckPipettesButton.test.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/CheckPipettesButton.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect, beforeEach } from 'vitest' import { usePipettesQuery } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { CheckPipettesButton } from '../CheckPipettesButton' vi.mock('@opentrons/react-api-client') diff --git a/app/src/organisms/ChangePipette/__tests__/ClearDeckModal.test.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/ClearDeckModal.test.tsx similarity index 90% rename from app/src/organisms/ChangePipette/__tests__/ClearDeckModal.test.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/__tests__/ClearDeckModal.test.tsx index 62ef001158a..f4af4566141 100644 --- a/app/src/organisms/ChangePipette/__tests__/ClearDeckModal.test.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/ClearDeckModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ClearDeckModal } from '../ClearDeckModal' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/ChangePipette/__tests__/ConfirmPipette.test.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/ConfirmPipette.test.tsx similarity index 97% rename from app/src/organisms/ChangePipette/__tests__/ConfirmPipette.test.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/__tests__/ConfirmPipette.test.tsx index 363dc5fe29d..a328df38383 100644 --- a/app/src/organisms/ChangePipette/__tests__/ConfirmPipette.test.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/ConfirmPipette.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { LEFT } from '@opentrons/shared-data' -import { mockPipetteInfo } from '../../../redux/pipettes/__fixtures__' +import { mockPipetteInfo } from '/app/redux/pipettes/__fixtures__' import { CheckPipettesButton } from '../CheckPipettesButton' import { ConfirmPipette } from '../ConfirmPipette' @@ -13,7 +13,7 @@ import type { PipetteModelSpecs, PipetteNameSpecs, } from '@opentrons/shared-data' -import type { PipetteOffsetCalibration } from '../../../redux/calibration/types' +import type { PipetteOffsetCalibration } from '/app/redux/calibration/types' import type { LevelingVideo } from '../LevelPipette' vi.mock('../CheckPipettesButton') diff --git a/app/src/organisms/ChangePipette/__tests__/ExitModal.test.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/ExitModal.test.tsx similarity index 92% rename from app/src/organisms/ChangePipette/__tests__/ExitModal.test.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/__tests__/ExitModal.test.tsx index ad061d0f865..29bf417071c 100644 --- a/app/src/organisms/ChangePipette/__tests__/ExitModal.test.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/ExitModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ExitModal } from '../ExitModal' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/ChangePipette/__tests__/InstructionStep.test.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/InstructionStep.test.tsx similarity index 92% rename from app/src/organisms/ChangePipette/__tests__/InstructionStep.test.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/__tests__/InstructionStep.test.tsx index 9cb5cac3b63..80cdde63972 100644 --- a/app/src/organisms/ChangePipette/__tests__/InstructionStep.test.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/InstructionStep.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { it, describe, beforeEach } from 'vitest' import { screen } from '@testing-library/react' import { GEN1, GEN2, LEFT, RIGHT } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { InstructionStep } from '../InstructionStep' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/ChangePipette/__tests__/Instructions.test.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/Instructions.test.tsx similarity index 97% rename from app/src/organisms/ChangePipette/__tests__/Instructions.test.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/__tests__/Instructions.test.tsx index 2c95300838e..024be58e798 100644 --- a/app/src/organisms/ChangePipette/__tests__/Instructions.test.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/Instructions.test.tsx @@ -1,17 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, describe, expect, beforeEach } from 'vitest' import { fireEvent, screen } from '@testing-library/react' import type { PipetteModelSpecs } from '@opentrons/shared-data' -import { - nestedTextMatcher, - renderWithProviders, -} from '../../../__testing-utils__' +import { nestedTextMatcher, renderWithProviders } from '/app/__testing-utils__' import { LEFT } from '@opentrons/shared-data' import { fixtureP10Multi } from '@opentrons/shared-data/pipette/fixtures/name' -import { i18n } from '../../../i18n' -import { mockPipetteInfo } from '../../../redux/pipettes/__fixtures__' +import { i18n } from '/app/i18n' +import { mockPipetteInfo } from '/app/redux/pipettes/__fixtures__' import { Instructions } from '../Instructions' import { CheckPipettesButton } from '../CheckPipettesButton' diff --git a/app/src/organisms/ChangePipette/__tests__/LevelPipette.test.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/LevelPipette.test.tsx similarity index 93% rename from app/src/organisms/ChangePipette/__tests__/LevelPipette.test.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/__tests__/LevelPipette.test.tsx index 7419978c3cc..78c2e9f8d6d 100644 --- a/app/src/organisms/ChangePipette/__tests__/LevelPipette.test.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/LevelPipette.test.tsx @@ -1,13 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, describe, expect, beforeEach } from 'vitest' import { fireEvent, screen } from '@testing-library/react' import { LEFT } from '@opentrons/shared-data' -import { - nestedTextMatcher, - renderWithProviders, -} from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { nestedTextMatcher, renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { LevelPipette } from '../LevelPipette' import type { PipetteNameSpecs } from '@opentrons/shared-data' diff --git a/app/src/organisms/ChangePipette/__tests__/PipetteSelection.test.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/PipetteSelection.test.tsx similarity index 76% rename from app/src/organisms/ChangePipette/__tests__/PipetteSelection.test.tsx rename to app/src/organisms/Desktop/Devices/ChangePipette/__tests__/PipetteSelection.test.tsx index 40c9da2ad3e..f3619e9930d 100644 --- a/app/src/organisms/ChangePipette/__tests__/PipetteSelection.test.tsx +++ b/app/src/organisms/Desktop/Devices/ChangePipette/__tests__/PipetteSelection.test.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, describe, beforeEach } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { PipetteSelect } from '../../../molecules/PipetteSelect' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { PipetteSelect } from '/app/molecules/PipetteSelect' import { PipetteSelection } from '../PipetteSelection' -vi.mock('../../../molecules/PipetteSelect') +vi.mock('/app/molecules/PipetteSelect') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/ChangePipette/constants.ts b/app/src/organisms/Desktop/Devices/ChangePipette/constants.ts similarity index 100% rename from app/src/organisms/ChangePipette/constants.ts rename to app/src/organisms/Desktop/Devices/ChangePipette/constants.ts diff --git a/app/src/organisms/Desktop/Devices/ChangePipette/index.tsx b/app/src/organisms/Desktop/Devices/ChangePipette/index.tsx new file mode 100644 index 00000000000..efcca7d7a35 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ChangePipette/index.tsx @@ -0,0 +1,337 @@ +import { useRef, useState, useEffect, useCallback } from 'react' +import capitalize from 'lodash/capitalize' +import { useSelector, useDispatch } from 'react-redux' +import { useNavigate } from 'react-router-dom' +import { useTranslation } from 'react-i18next' +import { getPipetteNameSpecs } from '@opentrons/shared-data' +import { + SPACING, + TYPOGRAPHY, + LegacyStyledText, + ModalShell, +} from '@opentrons/components' + +import { + useDispatchApiRequests, + getRequestById, + SUCCESS, +} from '/app/redux/robot-api' +import { getCalibrationForPipette } from '/app/redux/calibration' +import { + home, + move, + getMovementStatus, + HOMING, + MOVING, + ROBOT, + PIPETTE, + CHANGE_PIPETTE, + HOME, +} from '/app/redux/robot-controls' + +import { WizardHeader } from '/app/molecules/WizardHeader' +import { InProgressModal } from '/app/molecules/InProgressModal/InProgressModal' +import { useAttachedPipettes } from '/app/resources/instruments' +import { ExitModal } from './ExitModal' +import { Instructions } from './Instructions' +import { ConfirmPipette } from './ConfirmPipette' +import { ClearDeckModal } from './ClearDeckModal' + +import { + ATTACH, + DETACH, + CLEAR_DECK, + INSTRUCTIONS, + CONFIRM, + SINGLE_CHANNEL_STEPS, + EIGHT_CHANNEL_STEPS, +} from './constants' + +import type { PipetteNameSpecs } from '@opentrons/shared-data' +import type { State, Dispatch } from '/app/redux/types' +import type { Mount } from '/app/redux/pipettes/types' +import type { WizardStep } from './types' + +interface Props { + robotName: string + mount: Mount + closeModal: () => void +} + +export function ChangePipette(props: Props): JSX.Element | null { + const { robotName, mount, closeModal } = props + const { t } = useTranslation(['change_pipette', 'shared']) + const navigate = useNavigate() + const dispatch = useDispatch() + const finalRequestId = useRef(null) + const [dispatchApiRequests] = useDispatchApiRequests(dispatchedAction => { + if ( + dispatchedAction.type === HOME && + dispatchedAction.payload.target === PIPETTE + ) { + // track final home pipette request, its success closes modal + // @ts-expect-error(sa, 2021-05-27): avoiding src code change, use in operator to type narrow + finalRequestId.current = dispatchedAction.meta.requestId + } + }) + const [wizardStep, setWizardStep] = useState(CLEAR_DECK) + const [wantedName, setWantedName] = useState(null) + const [confirmExit, setConfirmExit] = useState(false) + const [currentStepCount, setCurrentStepCount] = useState(0) + // @ts-expect-error(sa, 2021-05-27): avoiding src code change, use in operator to type narrow + const wantedPipette = wantedName ? getPipetteNameSpecs(wantedName) : null + const attachedPipette = useAttachedPipettes()[mount] + const actualPipette = attachedPipette?.modelSpecs || null + const actualPipetteOffset = useSelector((state: State) => + attachedPipette?.id + ? getCalibrationForPipette(state, robotName, attachedPipette.id, mount) + : null + ) + const [ + wrongWantedPipette, + setWrongWantedPipette, + ] = useState(wantedPipette) + const [confirmPipetteLevel, setConfirmPipetteLevel] = useState(false) + + const movementStatus = useSelector((state: State) => { + return getMovementStatus(state, robotName) + }) + + const homePipStatus = useSelector((state: State) => { + return finalRequestId.current + ? getRequestById(state, finalRequestId.current) + : null + })?.status + + useEffect(() => { + if (homePipStatus === SUCCESS) closeModal() + }, [homePipStatus, closeModal]) + + const homePipAndExit = useCallback(() => { + dispatchApiRequests(home(robotName, PIPETTE, mount)) + }, [dispatchApiRequests, robotName, mount]) + + const baseProps = { + title: t('pipette_setup'), + subtitle: t('mount', { mount: mount }), + mount, + } + + const basePropsWithPipettes = { + ...baseProps, + robotName, + wantedPipette, + actualPipette, + displayName: actualPipette?.displayName || wantedPipette?.displayName || '', + displayCategory: + actualPipette?.displayCategory || wantedPipette?.displayCategory || null, + } + + let direction + if (currentStepCount === 0) { + direction = actualPipette != null ? DETACH : ATTACH + } else { + direction = wantedPipette != null ? ATTACH : DETACH + } + let eightChannel = wantedPipette?.channels === 8 + // if the user selects a single channel but attaches and accepts an 8 channel + if (actualPipette != null && currentStepCount >= 3 && direction === ATTACH) { + eightChannel = actualPipette?.channels === 8 + } + + const isButtonDisabled = + movementStatus === HOMING || movementStatus === MOVING + + const exitModal = ( + { + setConfirmExit(false) + }} + isDisabled={isButtonDisabled} + exit={homePipAndExit} + direction={direction} + /> + ) + + const success = + // success if we were trying to detach and nothing's attached + (!actualPipette && !wantedPipette) || + // or if the names of wanted and attached match + actualPipette?.name === wantedPipette?.name + + const attachedIncorrectPipette = Boolean( + !success && wantedPipette && actualPipette + ) + + const noPipetteDetach = + direction === DETACH && actualPipette === null && wantedPipette === null + + let exitWizardHeader + let wizardTitle: string = + actualPipette?.displayName != null && + wantedPipette === null && + direction === DETACH + ? t('detach_pipette', { + pipette: actualPipette.displayName, + mount: capitalize(mount), + }) + : t('attach_pipette') + + let contents: JSX.Element | null = null + + if (movementStatus === MOVING) { + contents = ( + + + {t('shared:stand_back_robot_is_in_motion')} + + + ) + } else if (wizardStep === CLEAR_DECK) { + exitWizardHeader = closeModal + contents = ( + { + dispatch(move(robotName, CHANGE_PIPETTE, mount, true)) + setWizardStep(INSTRUCTIONS) + }} + /> + ) + } else if (wizardStep === INSTRUCTIONS) { + const noPipetteSelectedAttach = + direction === ATTACH && wantedPipette === null + + let title + if (currentStepCount === 3) { + title = t('attach_pipette_type', { + pipetteName: wantedPipette?.displayName ?? '', + }) + } else if (actualPipette?.displayName != null) { + title = noPipetteDetach + ? t('detach') + : t('detach_pipette', { + pipette: actualPipette?.displayName ?? wantedPipette?.displayName, + mount: capitalize(mount), + }) + } else { + title = noPipetteSelectedAttach + ? t('attach_pipette') + : t('attach_pipette_type', { + pipetteName: wantedPipette?.displayName ?? '', + }) + } + + exitWizardHeader = confirmExit + ? undefined + : () => { + setConfirmExit(true) + } + wizardTitle = title + + contents = confirmExit ? ( + exitModal + ) : ( + { + setWizardStep(CONFIRM) + }, + back: () => { + setWizardStep(CLEAR_DECK) + }, + currentStepCount, + nextStep: () => { + setCurrentStepCount(currentStepCount + 1) + }, + prevStep: () => { + setCurrentStepCount(currentStepCount - 1) + }, + totalSteps: eightChannel ? EIGHT_CHANNEL_STEPS : SINGLE_CHANNEL_STEPS, + title: + actualPipette?.displayName != null + ? t('detach_pipette', { + pipette: actualPipette.displayName, + mount: capitalize(mount), + }) + : t('attach_pipette'), + }} + /> + ) + } else if (wizardStep === CONFIRM) { + const toCalDashboard = (): void => { + dispatchApiRequests(home(robotName, ROBOT)) + closeModal() + navigate(`/devices/${robotName}/robot-settings/calibration/dashboard`) + } + + exitWizardHeader = + success || confirmExit + ? undefined + : () => { + setConfirmExit(true) + } + + let wizardTitleConfirmPipette + if (wantedPipette == null && actualPipette == null) { + wizardTitleConfirmPipette = t('detach_pipette_from_mount', { + mount: capitalize(mount), + }) + } else if (wantedPipette == null && actualPipette != null) { + wizardTitleConfirmPipette = t('detach') + } else { + wizardTitleConfirmPipette = t('attach_name_pipette', { + pipette: + wrongWantedPipette != null + ? wrongWantedPipette.displayName + : wantedPipette?.displayName, + }) + } + wizardTitle = wizardTitleConfirmPipette + + contents = confirmExit ? ( + exitModal + ) : ( + { + setWizardStep(INSTRUCTIONS) + setCurrentStepCount(currentStepCount - 1) + }, + nextStep: () => { + setCurrentStepCount(currentStepCount + 1) + }, + wrongWantedPipette: wrongWantedPipette, + setWrongWantedPipette: setWrongWantedPipette, + setConfirmPipetteLevel: setConfirmPipetteLevel, + confirmPipetteLevel: confirmPipetteLevel, + exit: homePipAndExit, + actualPipetteOffset: actualPipetteOffset, + toCalibrationDashboard: toCalDashboard, + isDisabled: isButtonDisabled, + }} + /> + ) + } + return ( + + + {contents} + + ) +} diff --git a/app/src/organisms/ChangePipette/types.ts b/app/src/organisms/Desktop/Devices/ChangePipette/types.ts similarity index 100% rename from app/src/organisms/ChangePipette/types.ts rename to app/src/organisms/Desktop/Devices/ChangePipette/types.ts diff --git a/app/src/organisms/ConfigurePipette/ConfigErrorBanner.tsx b/app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigErrorBanner.tsx similarity index 90% rename from app/src/organisms/ConfigurePipette/ConfigErrorBanner.tsx rename to app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigErrorBanner.tsx index 35aac004632..5a7ea72ae7e 100644 --- a/app/src/organisms/ConfigurePipette/ConfigErrorBanner.tsx +++ b/app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigErrorBanner.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { AlertItem } from '@opentrons/components' import styles from './styles.module.css' @@ -10,7 +10,7 @@ const TITLE = 'Error updating pipette settings' export function ConfigErrorBanner(props: Props): JSX.Element | null { const { message } = props - const [dismissed, setDismissed] = React.useState(false) + const [dismissed, setDismissed] = useState(false) if (message == null || dismissed) return null return ( diff --git a/app/src/organisms/ConfigurePipette/ConfigForm.tsx b/app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigForm.tsx similarity index 99% rename from app/src/organisms/ConfigurePipette/ConfigForm.tsx rename to app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigForm.tsx index acc2c943cb9..6cd5691db1e 100644 --- a/app/src/organisms/ConfigurePipette/ConfigForm.tsx +++ b/app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigForm.tsx @@ -1,5 +1,3 @@ -import * as React from 'react' - import startCase from 'lodash/startCase' import mapValues from 'lodash/mapValues' import forOwn from 'lodash/forOwn' diff --git a/app/src/organisms/ConfigurePipette/ConfigFormGroup.tsx b/app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigFormGroup.tsx similarity index 99% rename from app/src/organisms/ConfigurePipette/ConfigFormGroup.tsx rename to app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigFormGroup.tsx index a7f75904ae3..9c8a2c25acf 100644 --- a/app/src/organisms/ConfigurePipette/ConfigFormGroup.tsx +++ b/app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigFormGroup.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Controller } from 'react-hook-form' import { CheckboxField, diff --git a/app/src/organisms/ConfigurePipette/ConfigFormResetButton.tsx b/app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigFormResetButton.tsx similarity index 92% rename from app/src/organisms/ConfigurePipette/ConfigFormResetButton.tsx rename to app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigFormResetButton.tsx index ff6b6510ecc..3d31d8b7bab 100644 --- a/app/src/organisms/ConfigurePipette/ConfigFormResetButton.tsx +++ b/app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigFormResetButton.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import { DIRECTION_COLUMN, @@ -8,7 +7,7 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { Divider } from '../../atoms/structure' +import { Divider } from '/app/atoms/structure' export interface ButtonProps { onClick?: () => unknown diff --git a/app/src/organisms/ConfigurePipette/ConfigFormSubmitButton.tsx b/app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigFormSubmitButton.tsx similarity index 96% rename from app/src/organisms/ConfigurePipette/ConfigFormSubmitButton.tsx rename to app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigFormSubmitButton.tsx index cc608f40e85..97335e736a3 100644 --- a/app/src/organisms/ConfigurePipette/ConfigFormSubmitButton.tsx +++ b/app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigFormSubmitButton.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { DIRECTION_COLUMN, diff --git a/app/src/organisms/ConfigurePipette/ConfigMessage.tsx b/app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigMessage.tsx similarity index 95% rename from app/src/organisms/ConfigurePipette/ConfigMessage.tsx rename to app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigMessage.tsx index d55c2b96c36..d6a32fa6d4b 100644 --- a/app/src/organisms/ConfigurePipette/ConfigMessage.tsx +++ b/app/src/organisms/Desktop/Devices/ConfigurePipette/ConfigMessage.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import styles from './styles.module.css' // TODO (ka 2019-2-12): Add intercom onClick to assistance text diff --git a/app/src/organisms/ConfigurePipette/__tests__/ConfigFormResetButton.test.tsx b/app/src/organisms/Desktop/Devices/ConfigurePipette/__tests__/ConfigFormResetButton.test.tsx similarity index 91% rename from app/src/organisms/ConfigurePipette/__tests__/ConfigFormResetButton.test.tsx rename to app/src/organisms/Desktop/Devices/ConfigurePipette/__tests__/ConfigFormResetButton.test.tsx index 896962d14a0..4c2ec08c7d4 100644 --- a/app/src/organisms/ConfigurePipette/__tests__/ConfigFormResetButton.test.tsx +++ b/app/src/organisms/Desktop/Devices/ConfigurePipette/__tests__/ConfigFormResetButton.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, expect, describe, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ConfigFormResetButton } from '../ConfigFormResetButton' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/ConfigurePipette/__tests__/ConfigFormSubmitButton.test.tsx b/app/src/organisms/Desktop/Devices/ConfigurePipette/__tests__/ConfigFormSubmitButton.test.tsx similarity index 87% rename from app/src/organisms/ConfigurePipette/__tests__/ConfigFormSubmitButton.test.tsx rename to app/src/organisms/Desktop/Devices/ConfigurePipette/__tests__/ConfigFormSubmitButton.test.tsx index ddc9feadf47..3dbab681883 100644 --- a/app/src/organisms/ConfigurePipette/__tests__/ConfigFormSubmitButton.test.tsx +++ b/app/src/organisms/Desktop/Devices/ConfigurePipette/__tests__/ConfigFormSubmitButton.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { it, expect, describe, beforeEach } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ConfigFormSubmitButton } from '../ConfigFormSubmitButton' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/ConfigurePipette/__tests__/ConfigurePipette.test.tsx b/app/src/organisms/Desktop/Devices/ConfigurePipette/__tests__/ConfigurePipette.test.tsx similarity index 75% rename from app/src/organisms/ConfigurePipette/__tests__/ConfigurePipette.test.tsx rename to app/src/organisms/Desktop/Devices/ConfigurePipette/__tests__/ConfigurePipette.test.tsx index 117a6586f76..2d7790bdd24 100644 --- a/app/src/organisms/ConfigurePipette/__tests__/ConfigurePipette.test.tsx +++ b/app/src/organisms/Desktop/Devices/ConfigurePipette/__tests__/ConfigurePipette.test.tsx @@ -1,20 +1,20 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { vi, it, expect, describe, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import * as RobotApi from '../../../redux/robot-api' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import * as RobotApi from '/app/redux/robot-api' import { ConfigurePipette } from '../../ConfigurePipette' -import { mockPipetteSettingsFieldsMap } from '../../../redux/pipettes/__fixtures__' -import { getConfig } from '../../../redux/config' +import { mockPipetteSettingsFieldsMap } from '/app/redux/pipettes/__fixtures__' +import { getConfig } from '/app/redux/config' -import type { DispatchApiRequestType } from '../../../redux/robot-api' -import type { State } from '../../../redux/types' +import type { DispatchApiRequestType } from '/app/redux/robot-api' +import type { State } from '/app/redux/types' import { screen } from '@testing-library/react' -vi.mock('../../../redux/robot-api') -vi.mock('../../../redux/config') +vi.mock('/app/redux/robot-api') +vi.mock('/app/redux/config') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/Desktop/Devices/ConfigurePipette/index.tsx b/app/src/organisms/Desktop/Devices/ConfigurePipette/index.tsx new file mode 100644 index 00000000000..62a90721b26 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ConfigurePipette/index.tsx @@ -0,0 +1,51 @@ +import { useTranslation } from 'react-i18next' +import { Box } from '@opentrons/components' + +import { ConfigForm } from './ConfigForm' +import { ConfigErrorBanner } from './ConfigErrorBanner' +import type { + PipetteSettingsFieldsMap, + UpdatePipetteSettingsData, +} from '@opentrons/api-client' + +interface Props { + closeModal: () => void + updateSettings: (params: UpdatePipetteSettingsData) => void + updateError: Error | null + isUpdateLoading: boolean + robotName: string + formId: string + settings: PipetteSettingsFieldsMap +} + +export function ConfigurePipette(props: Props): JSX.Element { + const { + updateSettings, + updateError, + isUpdateLoading, + formId, + settings, + } = props + const { t } = useTranslation('device_details') + + const groupLabels = [ + t('plunger_positions'), + t('tip_pickup_drop'), + t('power_force'), + ] + + return ( + + {updateError != null && ( + + )} + + + ) +} diff --git a/app/src/organisms/ConfigurePipette/styles.module.css b/app/src/organisms/Desktop/Devices/ConfigurePipette/styles.module.css similarity index 100% rename from app/src/organisms/ConfigurePipette/styles.module.css rename to app/src/organisms/Desktop/Devices/ConfigurePipette/styles.module.css diff --git a/app/src/organisms/Devices/ConnectionTroubleshootingModal.tsx b/app/src/organisms/Desktop/Devices/ConnectionTroubleshootingModal.tsx similarity index 98% rename from app/src/organisms/Devices/ConnectionTroubleshootingModal.tsx rename to app/src/organisms/Desktop/Devices/ConnectionTroubleshootingModal.tsx index c6289ead522..1587524abae 100644 --- a/app/src/organisms/Devices/ConnectionTroubleshootingModal.tsx +++ b/app/src/organisms/Desktop/Devices/ConnectionTroubleshootingModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' import { diff --git a/app/src/organisms/Devices/DevicesEmptyState.tsx b/app/src/organisms/Desktop/Devices/DevicesEmptyState.tsx similarity index 96% rename from app/src/organisms/Devices/DevicesEmptyState.tsx rename to app/src/organisms/Desktop/Devices/DevicesEmptyState.tsx index ded89155cd6..f542fb2f979 100644 --- a/app/src/organisms/Devices/DevicesEmptyState.tsx +++ b/app/src/organisms/Desktop/Devices/DevicesEmptyState.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { useDispatch } from 'react-redux' @@ -20,7 +19,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { startDiscovery } from '../../redux/discovery' +import { startDiscovery } from '/app/redux/discovery' export const TROUBLESHOOTING_CONNECTION_PROBLEMS_URL = 'https://support.opentrons.com/s/article/Troubleshooting-connection-problems' diff --git a/app/src/organisms/Devices/DownloadCsvFileLink.tsx b/app/src/organisms/Desktop/Devices/DownloadCsvFileLink.tsx similarity index 97% rename from app/src/organisms/Devices/DownloadCsvFileLink.tsx rename to app/src/organisms/Desktop/Devices/DownloadCsvFileLink.tsx index 4975db0ce11..e2ab564c0c8 100644 --- a/app/src/organisms/Devices/DownloadCsvFileLink.tsx +++ b/app/src/organisms/Desktop/Devices/DownloadCsvFileLink.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { diff --git a/app/src/organisms/ErrorRecoveryBanner/__tests__/ErrorRecoveryBanner.test.tsx b/app/src/organisms/Desktop/Devices/ErrorRecoveryBanner/__tests__/ErrorRecoveryBanner.test.tsx similarity index 91% rename from app/src/organisms/ErrorRecoveryBanner/__tests__/ErrorRecoveryBanner.test.tsx rename to app/src/organisms/Desktop/Devices/ErrorRecoveryBanner/__tests__/ErrorRecoveryBanner.test.tsx index 78c5da162c3..fc234a52629 100644 --- a/app/src/organisms/ErrorRecoveryBanner/__tests__/ErrorRecoveryBanner.test.tsx +++ b/app/src/organisms/Desktop/Devices/ErrorRecoveryBanner/__tests__/ErrorRecoveryBanner.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, beforeEach } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { useErrorRecoveryBanner, ErrorRecoveryBanner } from '..' vi.mock('..', async importOriginal => { diff --git a/app/src/organisms/ErrorRecoveryBanner/__tests__/useErrorRecoveryBanner.test.ts b/app/src/organisms/Desktop/Devices/ErrorRecoveryBanner/__tests__/useErrorRecoveryBanner.test.ts similarity index 87% rename from app/src/organisms/ErrorRecoveryBanner/__tests__/useErrorRecoveryBanner.test.ts rename to app/src/organisms/Desktop/Devices/ErrorRecoveryBanner/__tests__/useErrorRecoveryBanner.test.ts index 6eedd264499..a82cde21a59 100644 --- a/app/src/organisms/ErrorRecoveryBanner/__tests__/useErrorRecoveryBanner.test.ts +++ b/app/src/organisms/Desktop/Devices/ErrorRecoveryBanner/__tests__/useErrorRecoveryBanner.test.ts @@ -1,15 +1,15 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { useSelector } from 'react-redux' -import { getUserId } from '../../../redux/config' -import { useClientDataRecovery } from '../../../resources/client_data' +import { getUserId } from '/app/redux/config' +import { useClientDataRecovery } from '/app/resources/client_data' import { renderHook } from '@testing-library/react' import { useErrorRecoveryBanner } from '../index' vi.mock('react-redux', () => ({ useSelector: vi.fn(), })) -vi.mock('../../../redux/config') -vi.mock('../../../resources/client_data') +vi.mock('/app/redux/config') +vi.mock('/app/resources/client_data') describe('useErrorRecoveryBanner', () => { beforeEach(() => { diff --git a/app/src/organisms/Desktop/Devices/ErrorRecoveryBanner/index.tsx b/app/src/organisms/Desktop/Devices/ErrorRecoveryBanner/index.tsx new file mode 100644 index 00000000000..f8630d67824 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ErrorRecoveryBanner/index.tsx @@ -0,0 +1,74 @@ +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' + +import { + Flex, + Banner, + DIRECTION_COLUMN, + SPACING, + StyledText, +} from '@opentrons/components' + +import { getUserId } from '/app/redux/config' +import { useClientDataRecovery } from '/app/resources/client_data' + +import type { RecoveryIntent } from '/app/resources/client_data' +import type { StyleProps } from '@opentrons/components' + +const CLIENT_DATA_INTERVAL_MS = 5000 + +export interface UseErrorRecoveryBannerResult { + showRecoveryBanner: boolean + recoveryIntent: RecoveryIntent +} + +export function useErrorRecoveryBanner(): UseErrorRecoveryBannerResult { + const { userId, intent } = useClientDataRecovery({ + refetchInterval: CLIENT_DATA_INTERVAL_MS, + }) + const thisUserId = useSelector(getUserId) + + return { + showRecoveryBanner: userId !== null && thisUserId !== userId, + recoveryIntent: intent ?? 'recovering', + } +} + +export interface ErrorRecoveryBannerProps extends StyleProps { + recoveryIntent: RecoveryIntent +} + +export function ErrorRecoveryBanner({ + recoveryIntent, + ...styleProps +}: ErrorRecoveryBannerProps): JSX.Element { + const { t } = useTranslation(['error_recovery', 'shared']) + + const buildTitleText = (): string => { + switch (recoveryIntent) { + case 'canceling': + return t('robot_is_canceling_run') + case 'recovering': + default: + return t('robot_is_in_recovery_mode') + } + } + + return ( + + + + {buildTitleText()} + + + + {t('another_app_controlling_robot')} + + + + + ) +} diff --git a/app/src/organisms/Devices/EstopBanner.tsx b/app/src/organisms/Desktop/Devices/EstopBanner.tsx similarity index 93% rename from app/src/organisms/Devices/EstopBanner.tsx rename to app/src/organisms/Desktop/Devices/EstopBanner.tsx index accb9f58838..c1d434fa585 100644 --- a/app/src/organisms/Devices/EstopBanner.tsx +++ b/app/src/organisms/Desktop/Devices/EstopBanner.tsx @@ -1,7 +1,7 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { Btn, + Banner, DIRECTION_ROW, Flex, SPACING, @@ -9,13 +9,12 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { Banner } from '../../atoms/Banner' import { NOT_PRESENT, PHYSICALLY_ENGAGED, LOGICALLY_ENGAGED, useEstopContext, -} from '../EmergencyStop' +} from '/app/organisms/EmergencyStop' import type { EstopState } from '@opentrons/api-client' diff --git a/app/src/organisms/GripperCard/AboutGripperSlideout.tsx b/app/src/organisms/Desktop/Devices/GripperCard/AboutGripperSlideout.tsx similarity index 96% rename from app/src/organisms/GripperCard/AboutGripperSlideout.tsx rename to app/src/organisms/Desktop/Devices/GripperCard/AboutGripperSlideout.tsx index 8c583cf75ea..a95a409f759 100644 --- a/app/src/organisms/GripperCard/AboutGripperSlideout.tsx +++ b/app/src/organisms/Desktop/Devices/GripperCard/AboutGripperSlideout.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { COLORS, @@ -9,7 +8,7 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { Slideout } from '../../atoms/Slideout' +import { Slideout } from '/app/atoms/Slideout' interface AboutGripperSlideoutProps { serialNumber: string diff --git a/app/src/organisms/GripperCard/__tests__/AboutGripperSlideout.test.tsx b/app/src/organisms/Desktop/Devices/GripperCard/__tests__/AboutGripperSlideout.test.tsx similarity index 89% rename from app/src/organisms/GripperCard/__tests__/AboutGripperSlideout.test.tsx rename to app/src/organisms/Desktop/Devices/GripperCard/__tests__/AboutGripperSlideout.test.tsx index 8422846ac2c..9ad4381a40c 100644 --- a/app/src/organisms/GripperCard/__tests__/AboutGripperSlideout.test.tsx +++ b/app/src/organisms/Desktop/Devices/GripperCard/__tests__/AboutGripperSlideout.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { screen, fireEvent } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { AboutGripperSlideout } from '../AboutGripperSlideout' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/GripperCard/__tests__/GripperCard.test.tsx b/app/src/organisms/Desktop/Devices/GripperCard/__tests__/GripperCard.test.tsx similarity index 95% rename from app/src/organisms/GripperCard/__tests__/GripperCard.test.tsx rename to app/src/organisms/Desktop/Devices/GripperCard/__tests__/GripperCard.test.tsx index 11767aacb16..ccdbc80c2b3 100644 --- a/app/src/organisms/GripperCard/__tests__/GripperCard.test.tsx +++ b/app/src/organisms/Desktop/Devices/GripperCard/__tests__/GripperCard.test.tsx @@ -1,15 +1,15 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useCurrentSubsystemUpdateQuery } from '@opentrons/react-api-client' -import { i18n } from '../../../i18n' -import { GripperWizardFlows } from '../../GripperWizardFlows' +import { i18n } from '/app/i18n' +import { GripperWizardFlows } from '/app/organisms/GripperWizardFlows' import { AboutGripperSlideout } from '../AboutGripperSlideout' import { GripperCard } from '../' import type { GripperData } from '@opentrons/api-client' -vi.mock('../../GripperWizardFlows') +vi.mock('/app/organisms/GripperWizardFlows') vi.mock('../AboutGripperSlideout') vi.mock('@opentrons/react-api-client') diff --git a/app/src/organisms/Desktop/Devices/GripperCard/index.tsx b/app/src/organisms/Desktop/Devices/GripperCard/index.tsx new file mode 100644 index 00000000000..0d425640c11 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/GripperCard/index.tsx @@ -0,0 +1,218 @@ +import { useState, useEffect } from 'react' +import { Trans, useTranslation } from 'react-i18next' +import { css } from 'styled-components' +import { + Banner, + CURSOR_POINTER, + LegacyStyledText, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' +import { getGripperDisplayName } from '@opentrons/shared-data' +import { useCurrentSubsystemUpdateQuery } from '@opentrons/react-api-client' +import { InstrumentCard } from '/app/molecules/InstrumentCard' +import { GripperWizardFlows } from '/app/organisms/GripperWizardFlows' +import { AboutGripperSlideout } from './AboutGripperSlideout' +import { GRIPPER_FLOW_TYPES } from '/app/organisms/GripperWizardFlows/constants' + +import type { MouseEventHandler } from 'react' +import type { BadGripper, GripperData } from '@opentrons/api-client' +import type { GripperModel } from '@opentrons/shared-data' +import type { GripperWizardFlowType } from '/app/organisms/GripperWizardFlows/types' + +interface GripperCardProps { + attachedGripper: GripperData | BadGripper | null + isCalibrated: boolean + isRunActive: boolean + isEstopNotDisengaged: boolean +} +const BANNER_LINK_CSS = css` + text-decoration: ${TYPOGRAPHY.textDecorationUnderline}; + cursor: ${CURSOR_POINTER}; + margin-left: ${SPACING.spacing8}; +` + +const INSTRUMENT_CARD_STYLE = css` + p { + text-transform: lowercase; + } + + p::first-letter { + text-transform: uppercase; + } +` + +const POLL_DURATION_MS = 5000 + +export function GripperCard({ + attachedGripper, + isCalibrated, + isRunActive, + isEstopNotDisengaged, +}: GripperCardProps): JSX.Element { + const { t, i18n } = useTranslation(['device_details', 'shared']) + const [ + openWizardFlowType, + setOpenWizardFlowType, + ] = useState(null) + const [ + showAboutGripperSlideout, + setShowAboutGripperSlideout, + ] = useState(false) + + const handleAttach: MouseEventHandler = () => { + setOpenWizardFlowType(GRIPPER_FLOW_TYPES.ATTACH) + } + + const handleDetach: MouseEventHandler = () => { + setOpenWizardFlowType(GRIPPER_FLOW_TYPES.DETACH) + } + + const handleCalibrate: MouseEventHandler = () => { + setOpenWizardFlowType(GRIPPER_FLOW_TYPES.RECALIBRATE) + } + const [pollForSubsystemUpdate, setPollForSubsystemUpdate] = useState(false) + const { data: subsystemUpdateData } = useCurrentSubsystemUpdateQuery( + 'gripper', + { + enabled: pollForSubsystemUpdate, + refetchInterval: POLL_DURATION_MS, + } + ) + // we should poll for a subsystem update from the time a bad instrument is + // detected until the update has been done for 5 seconds + // this gives the instruments endpoint time to start reporting + // a good instrument + useEffect(() => { + if (attachedGripper?.ok === false) { + setPollForSubsystemUpdate(true) + } else if ( + subsystemUpdateData != null && + subsystemUpdateData.data.updateStatus === 'done' + ) { + setTimeout(() => { + setPollForSubsystemUpdate(false) + }, POLL_DURATION_MS) + } + }, [attachedGripper?.ok, subsystemUpdateData]) + + const menuOverlayItems = + attachedGripper == null || !attachedGripper.ok + ? [ + { + label: t('attach_gripper'), + disabled: attachedGripper != null || isRunActive, + onClick: handleAttach, + }, + ] + : [ + { + label: + attachedGripper.data.calibratedOffset?.last_modified != null + ? t('recalibrate_gripper') + : t('calibrate_gripper'), + disabled: attachedGripper == null || isRunActive, + onClick: handleCalibrate, + }, + { + label: t('detach_gripper'), + disabled: attachedGripper == null || isRunActive, + onClick: handleDetach, + }, + { + label: t('about_gripper'), + disabled: attachedGripper == null, + onClick: () => { + setShowAboutGripperSlideout(true) + }, + }, + ] + return ( + <> + {(attachedGripper == null || attachedGripper.ok) && + subsystemUpdateData == null ? ( + + {isEstopNotDisengaged ? ( + + {t('calibration_needed_without_link')} + + ) : ( + + ), + }} + /> + )} + + ) : null + } + isGripperAttached={attachedGripper != null} + label={t('shared:extension_mount')} + menuOverlayItems={menuOverlayItems} + isEstopNotDisengaged={isEstopNotDisengaged} + /> + ) : null} + {attachedGripper?.ok === false || + (subsystemUpdateData != null && pollForSubsystemUpdate) ? ( + + + + } + isEstopNotDisengaged={isEstopNotDisengaged} + /> + ) : null} + {openWizardFlowType != null ? ( + { + setOpenWizardFlowType(null) + }} + /> + ) : null} + {attachedGripper?.ok && showAboutGripperSlideout && ( + { + setShowAboutGripperSlideout(false) + }} + /> + )} + + ) +} diff --git a/app/src/organisms/Devices/HistoricalProtocolRun.tsx b/app/src/organisms/Desktop/Devices/HistoricalProtocolRun.tsx similarity index 89% rename from app/src/organisms/Devices/HistoricalProtocolRun.tsx rename to app/src/organisms/Desktop/Devices/HistoricalProtocolRun.tsx index 5fc5b3e2580..b9e05314f80 100644 --- a/app/src/organisms/Devices/HistoricalProtocolRun.tsx +++ b/app/src/organisms/Desktop/Devices/HistoricalProtocolRun.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' import { @@ -12,10 +12,11 @@ import { OVERFLOW_HIDDEN, SPACING, LegacyStyledText, + CURSOR_POINTER, } from '@opentrons/components' -import { formatInterval } from '../RunTimeControl/utils' -import { formatTimestamp } from './utils' -import { EMPTY_TIMESTAMP } from './constants' +import { formatInterval } from '/app/transformations/commands' +import { formatTimestamp } from '/app/transformations/runs' +import { EMPTY_TIMESTAMP } from '/app/resources/runs' import { HistoricalProtocolRunOverflowMenu as OverflowMenu } from './HistoricalProtocolRunOverflowMenu' import { HistoricalProtocolRunDrawer as Drawer } from './HistoricalProtocolRunDrawer' import type { RunData } from '@opentrons/api-client' @@ -39,13 +40,16 @@ export function HistoricalProtocolRun( ): JSX.Element | null { const { t } = useTranslation('run_details') const { run, protocolName, robotIsBusy, robotName, protocolKey } = props - const [drawerOpen, setDrawerOpen] = React.useState(false) - const countRunDataFiles = + const [drawerOpen, setDrawerOpen] = useState(false) + let countRunDataFiles = 'runTimeParameters' in run ? run?.runTimeParameters.filter( parameter => parameter.type === 'csv_file' ).length : 0 + if ('outputFileIds' in run) { + countRunDataFiles += run.outputFileIds.length + } const runStatus = run.status const runDisplayName = formatTimestamp(run.createdAt) let duration = EMPTY_TIMESTAMP @@ -125,7 +129,7 @@ export function HistoricalProtocolRun( 0) { + runDataFileIds.push(...run.outputFileIds) + } + const uniqueLabwareOffsets = allLabwareOffsets?.filter( (offset, index, array) => { return ( @@ -104,7 +107,7 @@ export function HistoricalProtocolRunDrawer( const protocolFilesData = runDataFileIds.length === 0 ? ( - + ) : ( {t('protocol_files')} @@ -151,13 +154,8 @@ export function HistoricalProtocolRunDrawer( const labwareOffsets = uniqueLabwareOffsets == null || uniqueLabwareOffsets.length === 0 ? ( - + ) : ( - // {outOfDateBanner} diff --git a/app/src/organisms/Devices/HistoricalProtocolRunOverflowMenu.tsx b/app/src/organisms/Desktop/Devices/HistoricalProtocolRunOverflowMenu.tsx similarity index 79% rename from app/src/organisms/Devices/HistoricalProtocolRunOverflowMenu.tsx rename to app/src/organisms/Desktop/Devices/HistoricalProtocolRunOverflowMenu.tsx index 2e1a661ccb5..18dc44cb11b 100644 --- a/app/src/organisms/Devices/HistoricalProtocolRunOverflowMenu.tsx +++ b/app/src/organisms/Desktop/Devices/HistoricalProtocolRunOverflowMenu.tsx @@ -1,17 +1,18 @@ -import * as React from 'react' -import { useSelector } from 'react-redux' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { NavLink, useNavigate } from 'react-router-dom' import { ALIGN_CENTER, ALIGN_FLEX_END, + FLEX_MAX_CONTENT, Box, COLORS, DIRECTION_COLUMN, Flex, Icon, MenuItem, + NO_WRAP, OverflowBtn, POSITION_ABSOLUTE, POSITION_RELATIVE, @@ -24,19 +25,20 @@ import { } from '@opentrons/components' import { useDeleteRunMutation } from '@opentrons/react-api-client' -import { Divider } from '../../atoms/structure' -import { useRunControls } from '../../organisms/RunTimeControl/hooks' +import { Divider } from '/app/atoms/structure' +import { useRunControls } from '/app/organisms/RunTimeControl' import { useTrackEvent, ANALYTICS_PROTOCOL_PROCEED_TO_RUN, ANALYTICS_PROTOCOL_RUN_ACTION, -} from '../../redux/analytics' -import { getRobotUpdateDisplayInfo } from '../../redux/robot-update' -import { useDownloadRunLog, useTrackProtocolRunEvent, useRobot } from './hooks' -import { useIsEstopNotDisengaged } from '../../resources/devices/hooks/useIsEstopNotDisengaged' +} from '/app/redux/analytics' +import { useIsRobotOnWrongVersionOfSoftware } from '/app/redux/robot-update' +import { useDownloadRunLog } from './hooks' +import { useIsEstopNotDisengaged } from '/app/resources/devices' +import { useTrackProtocolRunEvent } from '/app/redux-resources/analytics' +import { useRobot } from '/app/redux-resources/robots' import type { Run } from '@opentrons/api-client' -import type { State } from '../../redux/types' export interface HistoricalProtocolRunOverflowMenuProps { runId: string @@ -114,11 +116,10 @@ function MenuDropdown(props: MenuDropdownProps): JSX.Element { isRunLogLoading, } = props - const isRobotOnWrongVersionOfSoftware = ['upgrade', 'downgrade'].includes( - useSelector((state: State) => { - return getRobotUpdateDisplayInfo(state, robotName) - })?.autoUpdateAction + const isRobotOnWrongVersionOfSoftware = useIsRobotOnWrongVersionOfSoftware( + robotName ) + const [targetProps, tooltipProps] = useHoverTooltip() const onResetSuccess = (createRunResponse: Run): void => { navigate( @@ -133,7 +134,10 @@ function MenuDropdown(props: MenuDropdownProps): JSX.Element { } const trackEvent = useTrackEvent() const { trackProtocolRunEvent } = useTrackProtocolRunEvent(runId, robotName) - const { reset } = useRunControls(runId, onResetSuccess) + const { reset, isResetRunLoading, isRunControlLoading } = useRunControls( + runId, + onResetSuccess + ) const { deleteRun } = useDeleteRunMutation() const robot = useRobot(robotName) const robotSerialNumber = @@ -165,7 +169,7 @@ function MenuDropdown(props: MenuDropdownProps): JSX.Element { return ( @@ -183,16 +188,34 @@ function MenuDropdown(props: MenuDropdownProps): JSX.Element { - {t('rerun_now')} + + {t('rerun_now')} + {isResetRunLoading ? ( + + ) : null} + {isRobotOnWrongVersionOfSoftware && ( {t('shared:a_software_update_is_available')} )} + {isRunControlLoading && ( + + {t('rerun_loading')} + + )} (false) - const [showChoosePipette, setShowChoosePipette] = React.useState(false) - const [ - selectedPipette, - setSelectedPipette, - ] = React.useState(SINGLE_MOUNT_PIPETTES) + ] = useState(false) + const [showChoosePipette, setShowChoosePipette] = useState(false) + const [selectedPipette, setSelectedPipette] = useState( + SINGLE_MOUNT_PIPETTES + ) const attachedPipetteIs96Channel = attachedPipette?.ok && attachedPipette.instrumentName === 'p1000_96' const selectedPipetteForWizard = attachedPipetteIs96Channel @@ -89,7 +94,7 @@ export function FlexPipetteCard({ setSelectedPipette(SINGLE_MOUNT_PIPETTES) } - const { showDTWiz, toggleDTWiz } = useDropTipWizardFlows() + const { showDTWiz, enableDTWiz, disableDTWiz } = useDropTipWizardFlows() const handleLaunchPipetteWizardFlows = ( flowType: PipetteWizardFlow @@ -102,7 +107,7 @@ export function FlexPipetteCard({ host, }) } - const handleChoosePipette: React.MouseEventHandler = () => { + const handleChoosePipette: MouseEventHandler = () => { setShowChoosePipette(true) } const handleAttach = (): void => { @@ -110,17 +115,15 @@ export function FlexPipetteCard({ handleLaunchPipetteWizardFlows(FLOWS.ATTACH) } - const handleDetach: React.MouseEventHandler = () => { + const handleDetach: MouseEventHandler = () => { handleLaunchPipetteWizardFlows(FLOWS.DETACH) } - const handleCalibrate: React.MouseEventHandler = () => { + const handleCalibrate: MouseEventHandler = () => { handleLaunchPipetteWizardFlows(FLOWS.CALIBRATE) } - const [pollForSubsystemUpdate, setPollForSubsystemUpdate] = React.useState( - false - ) + const [pollForSubsystemUpdate, setPollForSubsystemUpdate] = useState(false) const subsystem = attachedPipette?.subsystem ?? null const { data: subsystemUpdateData } = useCurrentSubsystemUpdateQuery( subsystem, @@ -134,7 +137,7 @@ export function FlexPipetteCard({ // detected until the update has been done for 5 seconds // this gives the instruments endpoint time to start reporting // a good instrument - React.useEffect(() => { + useEffect(() => { if (attachedPipette?.ok === false) { setPollForSubsystemUpdate(true) } else if ( @@ -181,7 +184,7 @@ export function FlexPipetteCard({ label: i18n.format(t('drop_tips'), 'capitalize'), disabled: attachedPipette == null || isRunActive, onClick: () => { - toggleDTWiz() + enableDTWiz() }, }, ] @@ -265,7 +268,8 @@ export function FlexPipetteCard({ robotType={FLEX_ROBOT_TYPE} mount={mount} instrumentModelSpecs={pipetteModelSpecs} - closeFlow={toggleDTWiz} + closeFlow={disableDTWiz} + modalStyle="simple" /> ) : null} {attachedPipette?.ok && showAboutPipetteSlideout ? ( diff --git a/app/src/organisms/Devices/PipetteCard/PipetteOverflowMenu.tsx b/app/src/organisms/Desktop/Devices/PipetteCard/PipetteOverflowMenu.tsx similarity index 94% rename from app/src/organisms/Devices/PipetteCard/PipetteOverflowMenu.tsx rename to app/src/organisms/Desktop/Devices/PipetteCard/PipetteOverflowMenu.tsx index 8795924accb..58fc6aca8f0 100644 --- a/app/src/organisms/Devices/PipetteCard/PipetteOverflowMenu.tsx +++ b/app/src/organisms/Desktop/Devices/PipetteCard/PipetteOverflowMenu.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -7,16 +6,17 @@ import { DIRECTION_COLUMN, Flex, MenuItem, + NO_WRAP, POSITION_ABSOLUTE, POSITION_RELATIVE, SPACING, } from '@opentrons/components' -import { Divider } from '../../../atoms/structure' +import { Divider } from '/app/atoms/structure' import type { PipetteModelSpecs } from '@opentrons/shared-data' import type { PipetteSettingsFieldsMap } from '@opentrons/api-client' -import type { Mount } from '../../../redux/pipettes/types' +import type { Mount } from '/app/redux/pipettes/types' interface PipetteOverflowMenuProps { pipetteSpecs: PipetteModelSpecs | null @@ -50,7 +50,7 @@ export const PipetteOverflowMenu = ( return ( { const { t } = useTranslation('device_details') - const [showBanner, setShowBanner] = React.useState(true) + const [showBanner, setShowBanner] = useState(true) if (!showBanner) return null return ( diff --git a/app/src/organisms/Devices/PipetteCard/PipetteSettingsSlideout.tsx b/app/src/organisms/Desktop/Devices/PipetteCard/PipetteSettingsSlideout.tsx similarity index 84% rename from app/src/organisms/Devices/PipetteCard/PipetteSettingsSlideout.tsx rename to app/src/organisms/Desktop/Devices/PipetteCard/PipetteSettingsSlideout.tsx index e8681b99812..9a8faa9c061 100644 --- a/app/src/organisms/Devices/PipetteCard/PipetteSettingsSlideout.tsx +++ b/app/src/organisms/Desktop/Devices/PipetteCard/PipetteSettingsSlideout.tsx @@ -1,14 +1,13 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { Flex } from '@opentrons/components' import { useUpdatePipetteSettingsMutation } from '@opentrons/react-api-client' -import { Slideout } from '../../../atoms/Slideout' -import { ConfigFormSubmitButton } from '../../ConfigurePipette/ConfigFormSubmitButton' -import { ConfigurePipette } from '../../ConfigurePipette' +import { Slideout } from '/app/atoms/Slideout' +import { ConfigFormSubmitButton } from '../ConfigurePipette/ConfigFormSubmitButton' +import { ConfigurePipette } from '../ConfigurePipette' import type { PipetteModelSpecs } from '@opentrons/shared-data' import type { PipetteSettingsFieldsMap } from '@opentrons/api-client' -import type { AttachedPipette } from '../../../redux/pipettes/types' +import type { AttachedPipette } from '/app/redux/pipettes/types' interface PipetteSettingsSlideoutProps { robotName: string diff --git a/app/src/organisms/Devices/PipetteCard/__tests__/AboutPipetteSlideout.test.tsx b/app/src/organisms/Desktop/Devices/PipetteCard/__tests__/AboutPipetteSlideout.test.tsx similarity index 86% rename from app/src/organisms/Devices/PipetteCard/__tests__/AboutPipetteSlideout.test.tsx rename to app/src/organisms/Desktop/Devices/PipetteCard/__tests__/AboutPipetteSlideout.test.tsx index 6417775d2e6..dd2274a3ab3 100644 --- a/app/src/organisms/Devices/PipetteCard/__tests__/AboutPipetteSlideout.test.tsx +++ b/app/src/organisms/Desktop/Devices/PipetteCard/__tests__/AboutPipetteSlideout.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { fireEvent, screen } from '@testing-library/react' -import { i18n } from '../../../../i18n' +import { i18n } from '/app/i18n' import { AboutPipetteSlideout } from '../AboutPipetteSlideout' -import { mockLeftSpecs } from '../../../../redux/pipettes/__fixtures__' +import { mockLeftSpecs } from '/app/redux/pipettes/__fixtures__' vi.mock('@opentrons/react-api-client') diff --git a/app/src/organisms/Devices/PipetteCard/__tests__/FlexPipetteCard.test.tsx b/app/src/organisms/Desktop/Devices/PipetteCard/__tests__/FlexPipetteCard.test.tsx similarity index 92% rename from app/src/organisms/Devices/PipetteCard/__tests__/FlexPipetteCard.test.tsx rename to app/src/organisms/Desktop/Devices/PipetteCard/__tests__/FlexPipetteCard.test.tsx index f9dda24287d..bd753e9f9d3 100644 --- a/app/src/organisms/Devices/PipetteCard/__tests__/FlexPipetteCard.test.tsx +++ b/app/src/organisms/Desktop/Devices/PipetteCard/__tests__/FlexPipetteCard.test.tsx @@ -1,23 +1,23 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, afterEach, expect } from 'vitest' -import { renderWithProviders } from '../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useCurrentSubsystemUpdateQuery } from '@opentrons/react-api-client' -import { i18n } from '../../../../i18n' -import { mockLeftSpecs } from '../../../../redux/pipettes/__fixtures__' -import { handlePipetteWizardFlows } from '../../../PipetteWizardFlows' +import { i18n } from '/app/i18n' +import { mockLeftSpecs } from '/app/redux/pipettes/__fixtures__' +import { handlePipetteWizardFlows } from '/app/organisms/PipetteWizardFlows' import { AboutPipetteSlideout } from '../AboutPipetteSlideout' import { FlexPipetteCard } from '../FlexPipetteCard' -import { ChoosePipette } from '../../../PipetteWizardFlows/ChoosePipette' -import { useDropTipWizardFlows } from '../../../DropTipWizardFlows' +import { ChoosePipette } from '/app/organisms/PipetteWizardFlows/ChoosePipette' +import { useDropTipWizardFlows } from '/app/organisms/DropTipWizardFlows' import type { PipetteData } from '@opentrons/api-client' import type { Mock } from 'vitest' -vi.mock('../../../PipetteWizardFlows') -vi.mock('../../../PipetteWizardFlows/ChoosePipette') +vi.mock('/app/organisms/PipetteWizardFlows') +vi.mock('/app/organisms/PipetteWizardFlows/ChoosePipette') vi.mock('../AboutPipetteSlideout') -vi.mock('../../../DropTipWizardFlows') +vi.mock('/app/organisms/DropTipWizardFlows') vi.mock('@opentrons/react-api-client') const render = (props: React.ComponentProps) => { @@ -62,9 +62,9 @@ describe('FlexPipetteCard', () => { data: undefined, } as any) vi.mocked(useDropTipWizardFlows).mockReturnValue({ - toggleDTWiz: mockDTWizToggle, + enableDTWiz: mockDTWizToggle, showDTWiz: false, - }) + } as any) }) afterEach(() => { vi.resetAllMocks() diff --git a/app/src/organisms/Devices/PipetteCard/__tests__/PipetteCard.test.tsx b/app/src/organisms/Desktop/Devices/PipetteCard/__tests__/PipetteCard.test.tsx similarity index 85% rename from app/src/organisms/Devices/PipetteCard/__tests__/PipetteCard.test.tsx rename to app/src/organisms/Desktop/Devices/PipetteCard/__tests__/PipetteCard.test.tsx index ea8269c5de0..e04796bd491 100644 --- a/app/src/organisms/Devices/PipetteCard/__tests__/PipetteCard.test.tsx +++ b/app/src/organisms/Desktop/Devices/PipetteCard/__tests__/PipetteCard.test.tsx @@ -1,31 +1,28 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { LEFT, RIGHT } from '@opentrons/shared-data' import { usePipetteSettingsQuery } from '@opentrons/react-api-client' -import { i18n } from '../../../../i18n' -import { getHasCalibrationBlock } from '../../../../redux/config' -import { useDispatchApiRequest } from '../../../../redux/robot-api' +import { i18n } from '/app/i18n' +import { getHasCalibrationBlock } from '/app/redux/config' +import { useDispatchApiRequest } from '/app/redux/robot-api' import { PipetteOverflowMenu } from '../PipetteOverflowMenu' import { PipetteCard } from '..' -import { useDropTipWizardFlows } from '../../../DropTipWizardFlows' +import { useDropTipWizardFlows } from '/app/organisms/DropTipWizardFlows' -import { - mockLeftSpecs, - mockRightSpecs, -} from '../../../../redux/pipettes/__fixtures__' +import { mockLeftSpecs, mockRightSpecs } from '/app/redux/pipettes/__fixtures__' -import type { DispatchApiRequestType } from '../../../../redux/robot-api' +import type { DispatchApiRequestType } from '/app/redux/robot-api' vi.mock('../PipetteOverflowMenu') -vi.mock('../../../../redux/config') -vi.mock('../../../../redux/robot-api') +vi.mock('/app/redux/config') +vi.mock('/app/redux/robot-api') vi.mock('@opentrons/react-api-client') -vi.mock('../../../../redux/pipettes') -vi.mock('../../../DropTipWizardFlows') +vi.mock('/app/redux/pipettes') +vi.mock('/app/organisms/DropTipWizardFlows') const render = (props: React.ComponentProps) => { return renderWithProviders(, { @@ -58,7 +55,8 @@ describe('PipetteCard', () => { ]) vi.mocked(useDropTipWizardFlows).mockReturnValue({ showDTWiz: false, - toggleDTWiz: vi.fn(), + enableDTWiz: vi.fn(), + disableDTWiz: vi.fn(), }) when(usePipetteSettingsQuery) .calledWith({ refetchInterval: 5000, enabled: true }) diff --git a/app/src/organisms/Devices/PipetteCard/__tests__/PipetteOverflowMenu.test.tsx b/app/src/organisms/Desktop/Devices/PipetteCard/__tests__/PipetteOverflowMenu.test.tsx similarity index 91% rename from app/src/organisms/Devices/PipetteCard/__tests__/PipetteOverflowMenu.test.tsx rename to app/src/organisms/Desktop/Devices/PipetteCard/__tests__/PipetteOverflowMenu.test.tsx index c6f7fef1d5b..155d955b6ea 100644 --- a/app/src/organisms/Devices/PipetteCard/__tests__/PipetteOverflowMenu.test.tsx +++ b/app/src/organisms/Desktop/Devices/PipetteCard/__tests__/PipetteOverflowMenu.test.tsx @@ -1,20 +1,20 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { PipetteOverflowMenu } from '../PipetteOverflowMenu' import { mockLeftProtoPipette, mockPipetteSettingsFieldsMap, -} from '../../../../redux/pipettes/__fixtures__' +} from '/app/redux/pipettes/__fixtures__' import { isFlexPipette } from '@opentrons/shared-data' -import type { Mount } from '../../../../redux/pipettes/types' +import type { Mount } from '/app/redux/pipettes/types' import type * as SharedData from '@opentrons/shared-data' -vi.mock('../../../../redux/config') +vi.mock('/app/redux/config') vi.mock('@opentrons/shared-data', async importOriginal => { const actualSharedData = await importOriginal() return { diff --git a/app/src/organisms/Devices/PipetteCard/__tests__/PipetteSettingsSlideout.test.tsx b/app/src/organisms/Desktop/Devices/PipetteCard/__tests__/PipetteSettingsSlideout.test.tsx similarity index 93% rename from app/src/organisms/Devices/PipetteCard/__tests__/PipetteSettingsSlideout.test.tsx rename to app/src/organisms/Desktop/Devices/PipetteCard/__tests__/PipetteSettingsSlideout.test.tsx index 9394cbe3193..37b6f66b863 100644 --- a/app/src/organisms/Devices/PipetteCard/__tests__/PipetteSettingsSlideout.test.tsx +++ b/app/src/organisms/Desktop/Devices/PipetteCard/__tests__/PipetteSettingsSlideout.test.tsx @@ -1,20 +1,20 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { fireEvent, waitFor, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useHost, useUpdatePipetteSettingsMutation, } from '@opentrons/react-api-client' -import { i18n } from '../../../../i18n' +import { i18n } from '/app/i18n' import { PipetteSettingsSlideout } from '../PipetteSettingsSlideout' import { mockLeftSpecs, mockPipetteSettingsFieldsMap, -} from '../../../../redux/pipettes/__fixtures__' +} from '/app/redux/pipettes/__fixtures__' import type { Mock } from 'vitest' diff --git a/app/src/organisms/Desktop/Devices/PipetteCard/index.tsx b/app/src/organisms/Desktop/Devices/PipetteCard/index.tsx new file mode 100644 index 00000000000..f391cff8b3f --- /dev/null +++ b/app/src/organisms/Desktop/Devices/PipetteCard/index.tsx @@ -0,0 +1,222 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' + +import { + ALIGN_CENTER, + ALIGN_START, + BORDERS, + Box, + COLORS, + DIRECTION_COLUMN, + DIRECTION_ROW, + Flex, + InstrumentDiagram, + LegacyStyledText, + OverflowBtn, + SPACING, + TYPOGRAPHY, + useMenuHandleClickOutside, + useOnClickOutside, +} from '@opentrons/components' +import { OT2_ROBOT_TYPE } from '@opentrons/shared-data' +import { usePipetteSettingsQuery } from '@opentrons/react-api-client' + +import { LEFT } from '/app/redux/pipettes' +import { ChangePipette } from '../ChangePipette' +import { PipetteOverflowMenu } from './PipetteOverflowMenu' +import { PipetteSettingsSlideout } from './PipetteSettingsSlideout' +import { AboutPipetteSlideout } from './AboutPipetteSlideout' +import { + DropTipWizardFlows, + useDropTipWizardFlows, +} from '/app/organisms/DropTipWizardFlows' + +import type { PipetteModelSpecs } from '@opentrons/shared-data' +import type { AttachedPipette, Mount } from '/app/redux/pipettes/types' + +interface PipetteCardProps { + pipetteModelSpecs: PipetteModelSpecs | null + pipetteId?: AttachedPipette['id'] | null + mount: Mount + robotName: string + isRunActive: boolean + isEstopNotDisengaged: boolean +} + +const POLL_DURATION_MS = 5000 + +// The OT-2 pipette card. +export const PipetteCard = (props: PipetteCardProps): JSX.Element => { + const { t } = useTranslation(['device_details', 'protocol_setup']) + const { + pipetteModelSpecs, + mount, + robotName, + pipetteId, + isRunActive, + isEstopNotDisengaged, + } = props + const { + menuOverlay, + handleOverflowClick, + showOverflowMenu, + setShowOverflowMenu, + } = useMenuHandleClickOutside() + const pipetteDisplayName = pipetteModelSpecs?.displayName + const pipetteOverflowWrapperRef = useOnClickOutside({ + onClickOutside: () => { + setShowOverflowMenu(false) + }, + }) + const [showChangePipette, setChangePipette] = useState(false) + const [showSlideout, setShowSlideout] = useState(false) + const [showAboutSlideout, setShowAboutSlideout] = useState(false) + + const { showDTWiz, disableDTWiz, enableDTWiz } = useDropTipWizardFlows() + + const settings = + usePipetteSettingsQuery({ + refetchInterval: POLL_DURATION_MS, + enabled: pipetteId != null, + })?.data?.[pipetteId ?? '']?.fields ?? null + + const handleChangePipette = (): void => { + setChangePipette(true) + } + const handleAboutSlideout = (): void => { + setShowAboutSlideout(true) + } + const handleSettingsSlideout = (): void => { + setShowSlideout(true) + } + return ( + + {showChangePipette && ( + { + setChangePipette(false) + }} + /> + )} + {showDTWiz && pipetteModelSpecs != null ? ( + + ) : null} + {showSlideout && + pipetteModelSpecs != null && + pipetteId != null && + settings != null && ( + { + setShowSlideout(false) + }} + isExpanded={true} + pipetteId={pipetteId} + settings={settings} + /> + )} + {showAboutSlideout && pipetteModelSpecs != null && pipetteId != null && ( + { + setShowAboutSlideout(false) + }} + isExpanded={true} + /> + )} + <> + + + {pipetteModelSpecs !== null ? ( + + + + ) : null} + + + {t('mount', { + side: mount === LEFT ? t('left') : t('right'), + })} + + + + {pipetteDisplayName ?? t('empty')} + + + + + + + + + + {showOverflowMenu && ( + <> + { + setShowOverflowMenu(false) + }} + > + + + {menuOverlay} + + )} + + ) +} diff --git a/app/src/organisms/Devices/ProtocolRun/BackToTopButton.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/BackToTopButton.tsx similarity index 87% rename from app/src/organisms/Devices/ProtocolRun/BackToTopButton.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/BackToTopButton.tsx index 402c566f32a..a8524988bf2 100644 --- a/app/src/organisms/Devices/ProtocolRun/BackToTopButton.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/BackToTopButton.tsx @@ -1,14 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { Link } from 'react-router-dom' -import { useRobot } from '../hooks' -import { getRobotSerialNumber } from '../../../redux/discovery' +import { useRobot } from '/app/redux-resources/robots' +import { getRobotSerialNumber } from '/app/redux/discovery' import { SecondaryButton } from '@opentrons/components' import { useTrackEvent, ANALYTICS_PROTOCOL_PROCEED_TO_RUN, -} from '../../../redux/analytics' +} from '/app/redux/analytics' interface BackToTopButtonProps { protocolRunHeaderRef: React.RefObject | null diff --git a/app/src/organisms/Devices/ProtocolRun/EmptySetupStep.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/EmptySetupStep.tsx similarity index 95% rename from app/src/organisms/Devices/ProtocolRun/EmptySetupStep.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/EmptySetupStep.tsx index eeca6dcf81a..24c2b449083 100644 --- a/app/src/organisms/Devices/ProtocolRun/EmptySetupStep.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/EmptySetupStep.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { COLORS, DIRECTION_COLUMN, diff --git a/app/src/organisms/Devices/ProtocolRun/LabwareInfoOverlay.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/LabwareInfoOverlay.tsx similarity index 97% rename from app/src/organisms/Devices/ProtocolRun/LabwareInfoOverlay.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/LabwareInfoOverlay.tsx index df7f886ebd2..c2d868af33f 100644 --- a/app/src/organisms/Devices/ProtocolRun/LabwareInfoOverlay.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/LabwareInfoOverlay.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { css } from 'styled-components' import { useTranslation } from 'react-i18next' import { getLabwareDisplayName } from '@opentrons/shared-data' @@ -18,7 +17,7 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { OffsetVector } from '../../../molecules/OffsetVector' +import { OffsetVector } from '/app/molecules/OffsetVector' import type { LabwareDefinition2 } from '@opentrons/shared-data' import { useLabwareOffsetForLabware } from './useLabwareOffsetForLabware' diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/DisplayRunStatus.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/DisplayRunStatus.tsx new file mode 100644 index 00000000000..5b5624ffb4e --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/DisplayRunStatus.tsx @@ -0,0 +1,46 @@ +import { useTranslation } from 'react-i18next' + +import { + ALIGN_CENTER, + COLORS, + SPACING, + Icon, + Flex, + StyledText, +} from '@opentrons/components' +import { RUN_STATUS_RUNNING } from '@opentrons/api-client' + +import type { RunStatus } from '@opentrons/api-client' + +interface DisplayRunStatusProps { + runStatus: RunStatus | null +} + +// Styles the run status copy. +export function DisplayRunStatus(props: DisplayRunStatusProps): JSX.Element { + const { t } = useTranslation('run_details') + return ( + + {props.runStatus === RUN_STATUS_RUNNING ? ( + + + + ) : null} + + {props.runStatus != null ? t(`status_${String(props.runStatus)}`) : ''} + + + ) +} diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolAnalysisErrorBanner.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderBannerContainer/ProtocolAnalysisErrorBanner.tsx similarity index 90% rename from app/src/organisms/Devices/ProtocolRun/ProtocolAnalysisErrorBanner.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderBannerContainer/ProtocolAnalysisErrorBanner.tsx index 965b2aee53d..753ed296d86 100644 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolAnalysisErrorBanner.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderBannerContainer/ProtocolAnalysisErrorBanner.tsx @@ -1,9 +1,10 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { Trans, useTranslation } from 'react-i18next' import { ALIGN_CENTER, + Banner, Btn, Flex, JUSTIFY_FLEX_END, @@ -15,9 +16,9 @@ import { Modal, } from '@opentrons/components' -import { getTopPortalEl } from '../../../App/portal' -import { Banner } from '../../../atoms/Banner' +import { getTopPortalEl } from '/app/App/portal' +import type { MouseEventHandler } from 'react' import type { AnalysisError } from '@opentrons/shared-data' interface ProtocolAnalysisErrorBannerProps { @@ -29,9 +30,9 @@ export function ProtocolAnalysisErrorBanner( ): JSX.Element { const { errors } = props const { t } = useTranslation(['run_details']) - const [showErrorDetails, setShowErrorDetails] = React.useState(false) + const [showErrorDetails, setShowErrorDetails] = useState(false) - const handleToggleDetails: React.MouseEventHandler = e => { + const handleToggleDetails: MouseEventHandler = e => { e.preventDefault() e.stopPropagation() setShowErrorDetails(!showErrorDetails) diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderBannerContainer/TerminalRunBannerContainer.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderBannerContainer/TerminalRunBannerContainer.tsx new file mode 100644 index 00000000000..c6428c2f385 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderBannerContainer/TerminalRunBannerContainer.tsx @@ -0,0 +1,165 @@ +import { useTranslation } from 'react-i18next' + +import { + SPACING, + TYPOGRAPHY, + Banner, + JUSTIFY_SPACE_BETWEEN, + Flex, + StyledText, + Link, + ALIGN_CENTER, +} from '@opentrons/components' +import { RUN_STATUS_STOPPED, RUN_STATUS_SUCCEEDED } from '@opentrons/api-client' +import { + useCloseCurrentRun, + useIsRunCurrent, + useMostRecentRunId, +} from '/app/resources/runs' + +import type { RunHeaderBannerContainerProps } from '.' + +type TerminalBannerType = 'success' | 'error' | null + +// Determine which terminal banner to render, if any. +export function useTerminalRunBannerContainer({ + runId, + runStatus, + isResetRunLoading, + runErrors, + enteredER, +}: RunHeaderBannerContainerProps): TerminalBannerType { + const { highestPriorityError, commandErrorList } = runErrors + + const isRunCurrent = useIsRunCurrent(runId) + const mostRecentRunId = useMostRecentRunId() + + const isMostRecentRun = mostRecentRunId === runId + const cancelledWithoutRecovery = + !enteredER && runStatus === RUN_STATUS_STOPPED + const completedWithErrors = + (commandErrorList != null && commandErrorList.length > 0) || + highestPriorityError != null + + const showSuccessBanner = + runStatus === RUN_STATUS_SUCCEEDED && + isRunCurrent && + !isResetRunLoading && + !completedWithErrors + + // TODO(jh, 08-14-24): Ideally, the backend never returns the "user cancelled a run" error and + // cancelledWithoutRecovery becomes unnecessary. + const showErrorBanner = + isMostRecentRun && + !cancelledWithoutRecovery && + !isResetRunLoading && + completedWithErrors + + if (showSuccessBanner) { + return 'success' + } else if (showErrorBanner) { + return 'error' + } else { + return null + } +} + +interface TerminalRunBannerContainerProps + extends RunHeaderBannerContainerProps { + bannerType: TerminalBannerType +} + +// Contains all possible banners that render after the run reaches a terminal run status. +export function TerminalRunBannerContainer( + props: TerminalRunBannerContainerProps +): JSX.Element { + const { bannerType } = props + + switch (bannerType) { + case 'success': + return + case 'error': + return + default: + console.error('Handle banner cases explicitly.') + return
    + } +} + +function ProtocolRunSuccessBanner(): JSX.Element { + const { t } = useTranslation('run_details') + + const { closeCurrentRun, isClosingCurrentRun } = useCloseCurrentRun() + + const handleRunSuccessClick = (): void => { + closeCurrentRun() + } + + return ( + + + {t('run_completed')} + + + ) +} + +function ProtocolRunErrorBanner({ + runErrors, + runStatus, + runHeaderModalContainerUtils, +}: RunHeaderBannerContainerProps): JSX.Element { + const { t } = useTranslation('run_details') + + const { closeCurrentRun } = useCloseCurrentRun() + + const { highestPriorityError, commandErrorList } = runErrors + + const handleFailedRunClick = (): void => { + // TODO(jh, 08-15-24): Revisit the control flow here here after + // commandErrorList may be fetched for a non-current run. + if (commandErrorList == null) { + closeCurrentRun() + } + runHeaderModalContainerUtils.runFailedModalUtils.toggleModal() + } + + return ( + + + + {highestPriorityError != null + ? t('error_info', { + errorType: highestPriorityError?.errorType, + errorCode: highestPriorityError?.errorCode, + }) + : `${ + runStatus === RUN_STATUS_SUCCEEDED + ? t('run_completed_with_warnings') + : t('run_canceled_with_errors') + }`} + + + {runStatus === RUN_STATUS_SUCCEEDED + ? t('view_warning_details') + : t('view_error_details')} + + + + ) +} diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolAnalysisErrorBanner.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderBannerContainer/__tests__/ProtocolAnalysisErrorBanner.test.tsx similarity index 88% rename from app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolAnalysisErrorBanner.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderBannerContainer/__tests__/ProtocolAnalysisErrorBanner.test.tsx index d80ff0b44b3..5b60de044d7 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolAnalysisErrorBanner.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderBannerContainer/__tests__/ProtocolAnalysisErrorBanner.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ProtocolAnalysisErrorBanner } from '../ProtocolAnalysisErrorBanner' const render = ( diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderBannerContainer/getShowGenericRunHeaderBanners.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderBannerContainer/getShowGenericRunHeaderBanners.ts new file mode 100644 index 00000000000..35b63a2020b --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderBannerContainer/getShowGenericRunHeaderBanners.ts @@ -0,0 +1,47 @@ +import { + RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, + RUN_STATUS_AWAITING_RECOVERY_PAUSED, + RUN_STATUS_BLOCKED_BY_OPEN_DOOR, + RUN_STATUS_STOPPED, +} from '@opentrons/api-client' + +import { isCancellableStatus } from '../utils' + +import type { RunHeaderBannerContainerProps } from '.' + +interface ShowGenericRunHeaderBannersParams { + runStatus: RunHeaderBannerContainerProps['runStatus'] + enteredER: RunHeaderBannerContainerProps['enteredER'] + isDoorOpen: boolean +} + +interface ShowGenericRunHeaderBannersResult { + showRunCanceledBanner: boolean + showDoorOpenDuringRunBanner: boolean + showDoorOpenBeforeRunBanner: boolean +} + +// Returns the "should render" scalar for all the generic Banner components used by ProtocolRunHeader. +export function getShowGenericRunHeaderBanners({ + runStatus, + isDoorOpen, + enteredER, +}: ShowGenericRunHeaderBannersParams): ShowGenericRunHeaderBannersResult { + const showRunCanceledBanner = runStatus === RUN_STATUS_STOPPED && !enteredER + + const showDoorOpenBeforeRunBanner = + isDoorOpen && + isCancellableStatus(runStatus) && + runStatus !== RUN_STATUS_BLOCKED_BY_OPEN_DOOR && + runStatus !== RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR && + runStatus !== RUN_STATUS_AWAITING_RECOVERY_PAUSED + + const showDoorOpenDuringRunBanner = + runStatus === RUN_STATUS_BLOCKED_BY_OPEN_DOOR + + return { + showRunCanceledBanner, + showDoorOpenBeforeRunBanner, + showDoorOpenDuringRunBanner, + } +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderBannerContainer/index.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderBannerContainer/index.tsx new file mode 100644 index 00000000000..5c7c6e01621 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderBannerContainer/index.tsx @@ -0,0 +1,129 @@ +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' + +import { + Box, + StyledText, + Link, + SPACING, + Banner, + Flex, + DIRECTION_COLUMN, + JUSTIFY_SPACE_BETWEEN, + DIRECTION_ROW, + ALIGN_CENTER, + TEXT_DECORATION_UNDERLINE, +} from '@opentrons/components' + +import { ProtocolAnalysisErrorBanner } from './ProtocolAnalysisErrorBanner' +import { + TerminalRunBannerContainer, + useTerminalRunBannerContainer, +} from './TerminalRunBannerContainer' +import { getShowGenericRunHeaderBanners } from './getShowGenericRunHeaderBanners' +import { useIsDoorOpen } from '../hooks' + +import type { RunStatus } from '@opentrons/api-client' +import type { ProtocolRunHeaderProps } from '..' +import type { UseRunErrorsResult } from '../hooks' +import type { UseRunHeaderModalContainerResult } from '../RunHeaderModalContainer' + +export type RunHeaderBannerContainerProps = ProtocolRunHeaderProps & { + runStatus: RunStatus | null + enteredER: boolean + isResetRunLoading: boolean + runErrors: UseRunErrorsResult + runHeaderModalContainerUtils: UseRunHeaderModalContainerResult + hasDownloadableFiles: boolean +} + +// Holds all the various banners that render in ProtocolRunHeader. +export function RunHeaderBannerContainer( + props: RunHeaderBannerContainerProps +): JSX.Element | null { + const navigate = useNavigate() + const { + runStatus, + enteredER, + runHeaderModalContainerUtils, + hasDownloadableFiles, + robotName, + } = props + const { analysisErrorModalUtils } = runHeaderModalContainerUtils + + const { t } = useTranslation(['run_details', 'shared']) + const isDoorOpen = useIsDoorOpen(robotName) + + const { + showRunCanceledBanner, + showDoorOpenBeforeRunBanner, + showDoorOpenDuringRunBanner, + } = getShowGenericRunHeaderBanners({ + runStatus, + isDoorOpen, + enteredER, + }) + + const terminalBannerType = useTerminalRunBannerContainer(props) + + return ( + + {analysisErrorModalUtils.showModal ? ( + + ) : null} + {showRunCanceledBanner ? ( + + {t('run_canceled')} + + ) : null} + {showDoorOpenBeforeRunBanner ? ( + + {t('shared:close_robot_door')} + + ) : null} + {showDoorOpenDuringRunBanner ? ( + + {t('close_door_to_resume_run')} + + ) : null} + {terminalBannerType != null ? ( + + ) : null} + {hasDownloadableFiles ? ( + + + + + {t('download_files')} + + + {t('files_available_robot_details')} + + + { + navigate(`/devices/${robotName}`) + }} + > + {t('device_details')} + + + + ) : null} + + ) +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/hooks/index.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/hooks/index.ts new file mode 100644 index 00000000000..d89d78cea56 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/hooks/index.ts @@ -0,0 +1,3 @@ +export { useActionButtonProperties } from './useActionButtonProperties' +export { useActionBtnDisabledUtils } from './useActionBtnDisabledUtils' +export { useIsFixtureMismatch } from './useIsFixtureMismatch' diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/hooks/useActionBtnDisabledUtils.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/hooks/useActionBtnDisabledUtils.ts new file mode 100644 index 00000000000..1c46c10372e --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/hooks/useActionBtnDisabledUtils.ts @@ -0,0 +1,122 @@ +import { useTranslation } from 'react-i18next' + +import { RUN_STATUS_BLOCKED_BY_OPEN_DOOR } from '@opentrons/api-client' + +import { useIsDoorOpen } from '../../../hooks' +import { useIsFixtureMismatch } from './useIsFixtureMismatch' +import { + isCancellableStatus, + isDisabledStatus, + isStartRunStatus, +} from '../../../utils' + +import type { BaseActionButtonProps } from '..' + +interface UseActionButtonDisabledUtilsProps extends BaseActionButtonProps { + isCurrentRun: boolean + isValidRunAgain: boolean + isSetupComplete: boolean + isOtherRunCurrent: boolean + isProtocolNotReady: boolean + isRobotOnWrongVersionOfSoftware: boolean + isClosingCurrentRun: boolean +} + +type UseActionButtonDisabledUtilsResult = + | { isDisabled: true; disabledReason: string | null } + | { isDisabled: false; disabledReason: null } + +// Manages the various reasons the ActionButton may be disabled, returning the disabled state and user-facing disabled +// reason copy if applicable. +export function useActionBtnDisabledUtils( + props: UseActionButtonDisabledUtilsProps +): UseActionButtonDisabledUtilsResult { + const { + isCurrentRun, + isSetupComplete, + isOtherRunCurrent, + isProtocolNotReady, + runStatus, + isRobotOnWrongVersionOfSoftware, + protocolRunControls, + robotName, + runId, + isResetRunLoadingRef, + isClosingCurrentRun, + } = props + + const { + isPlayRunActionLoading, + isPauseRunActionLoading, + } = protocolRunControls + const isDoorOpen = useIsDoorOpen(robotName) + const isFixtureMismatch = useIsFixtureMismatch(runId, robotName) + const isResetRunLoading = isResetRunLoadingRef.current + + const isDisabled = + (isCurrentRun && !isSetupComplete) || + isPlayRunActionLoading || + isPauseRunActionLoading || + isResetRunLoading || + isClosingCurrentRun || + isOtherRunCurrent || + isProtocolNotReady || + isFixtureMismatch || + isDisabledStatus(runStatus) || + isRobotOnWrongVersionOfSoftware || + (isDoorOpen && + runStatus !== RUN_STATUS_BLOCKED_BY_OPEN_DOOR && + isCancellableStatus(runStatus)) + + const disabledReason = useDisabledReason({ + ...props, + isDoorOpen, + isFixtureMismatch, + isResetRunLoading, + }) + + return isDisabled + ? { isDisabled: true, disabledReason } + : { isDisabled: false, disabledReason: null } +} + +type UseDisabledReasonProps = UseActionButtonDisabledUtilsProps & { + isDoorOpen: boolean + isFixtureMismatch: boolean + isResetRunLoading: boolean + isClosingCurrentRun: boolean +} + +// The user-facing disabled explanation for why the ActionButton is disabled, if any. +function useDisabledReason({ + isCurrentRun, + isSetupComplete, + isFixtureMismatch, + isValidRunAgain, + isOtherRunCurrent, + isRobotOnWrongVersionOfSoftware, + isDoorOpen, + runStatus, + isResetRunLoading, + isClosingCurrentRun, +}: UseDisabledReasonProps): string | null { + const { t } = useTranslation(['run_details', 'shared']) + + if ( + isCurrentRun && + (!isSetupComplete || isFixtureMismatch) && + !isValidRunAgain + ) { + return t('setup_incomplete') + } else if (isOtherRunCurrent && !isResetRunLoading) { + return t('shared:robot_is_busy') + } else if (isRobotOnWrongVersionOfSoftware) { + return t('shared:a_software_update_is_available') + } else if (isDoorOpen && isStartRunStatus(runStatus)) { + return t('close_door') + } else if (isClosingCurrentRun) { + return t('shared:robot_is_busy') + } else { + return null + } +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/hooks/useActionButtonProperties.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/hooks/useActionButtonProperties.ts new file mode 100644 index 00000000000..f9ea87080ca --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/hooks/useActionButtonProperties.ts @@ -0,0 +1,147 @@ +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' +import { useSelector } from 'react-redux' + +import { + RUN_STATUS_IDLE, + RUN_STATUS_RUNNING, + RUN_STATUS_STOP_REQUESTED, + RUN_STATUS_STOPPED, +} from '@opentrons/api-client' + +import { + ANALYTICS_PROTOCOL_PROCEED_TO_RUN, + ANALYTICS_PROTOCOL_RUN_ACTION, + useTrackEvent, +} from '/app/redux/analytics' +import { useTrackProtocolRunEvent } from '/app/redux-resources/analytics' +import { getMissingSetupSteps } from '/app/redux/protocol-runs' +import { useIsHeaterShakerInProtocol } from '/app/organisms/ModuleCard/hooks' +import { isAnyHeaterShakerShaking } from '../../../RunHeaderModalContainer/modals' +import { + isRecoveryStatus, + isRunAgainStatus, + isStartRunStatus, +} from '../../../utils' + +import type { IconName } from '@opentrons/components' +import type { BaseActionButtonProps } from '..' +import type { State } from '/app/redux/types' +import type { StepKey } from '/app/redux/protocol-runs' + +interface UseButtonPropertiesProps extends BaseActionButtonProps { + isProtocolNotReady: boolean + confirmMissingSteps: () => void + confirmAttachment: () => void + robotAnalyticsData: any + robotSerialNumber: string + currentRunId: string | null + isValidRunAgain: boolean + isOtherRunCurrent: boolean + isRobotOnWrongVersionOfSoftware: boolean + isClosingCurrentRun: boolean +} + +// Returns ActionButton properties. +export function useActionButtonProperties({ + isProtocolNotReady, + runStatus, + robotName, + runId, + confirmAttachment, + confirmMissingSteps, + robotAnalyticsData, + robotSerialNumber, + protocolRunControls, + attachedModules, + runHeaderModalContainerUtils, + isResetRunLoadingRef, + isClosingCurrentRun, +}: UseButtonPropertiesProps): { + buttonText: string + handleButtonClick: () => void + buttonIconName: IconName | null +} { + const { t } = useTranslation(['run_details', 'shared']) + const navigate = useNavigate() + const { play, pause, reset } = protocolRunControls + const { trackProtocolRunEvent } = useTrackProtocolRunEvent(runId, robotName) + const isHeaterShakerInProtocol = useIsHeaterShakerInProtocol() + const isHeaterShakerShaking = isAnyHeaterShakerShaking(attachedModules) + const trackEvent = useTrackEvent() + const missingSetupSteps = useSelector((state: State) => + getMissingSetupSteps(state, runId) + ) + + let buttonText = '' + let handleButtonClick = (): void => {} + let buttonIconName: IconName | null = null + + if (isProtocolNotReady) { + buttonIconName = 'ot-spinner' + buttonText = t('analyzing_on_robot') + } else if (isClosingCurrentRun) { + buttonIconName = 'ot-spinner' + buttonText = t('canceling_run') + } else if (runStatus === RUN_STATUS_RUNNING || isRecoveryStatus(runStatus)) { + buttonIconName = 'pause' + buttonText = t('pause_run') + handleButtonClick = () => { + pause() + trackProtocolRunEvent({ name: ANALYTICS_PROTOCOL_RUN_ACTION.PAUSE }) + } + } else if (runStatus === RUN_STATUS_STOP_REQUESTED) { + buttonIconName = 'ot-spinner' + buttonText = t('canceling_run') + } else if (isStartRunStatus(runStatus)) { + buttonIconName = 'play' + buttonText = + runStatus === RUN_STATUS_IDLE ? t('start_run') : t('resume_run') + handleButtonClick = () => { + if (isHeaterShakerShaking && isHeaterShakerInProtocol) { + runHeaderModalContainerUtils.HSRunningModalUtils.toggleModal?.() + } else if ( + missingSetupSteps.length !== 0 && + (runStatus === RUN_STATUS_IDLE || runStatus === RUN_STATUS_STOPPED) + ) { + confirmMissingSteps() + } else if ( + isHeaterShakerInProtocol && + !isHeaterShakerShaking && + (runStatus === RUN_STATUS_IDLE || runStatus === RUN_STATUS_STOPPED) + ) { + confirmAttachment() + } else { + play() + navigate(`/devices/${robotName}/protocol-runs/${runId}/run-preview`) + trackProtocolRunEvent({ + name: + runStatus === RUN_STATUS_IDLE + ? ANALYTICS_PROTOCOL_RUN_ACTION.START + : ANALYTICS_PROTOCOL_RUN_ACTION.RESUME, + properties: + runStatus === RUN_STATUS_IDLE && robotAnalyticsData != null + ? robotAnalyticsData + : {}, + }) + } + } + } else if (isRunAgainStatus(runStatus)) { + buttonIconName = isResetRunLoadingRef.current ? 'ot-spinner' : 'play' + buttonText = t('run_again') + handleButtonClick = () => { + isResetRunLoadingRef.current = true + reset() + runHeaderModalContainerUtils.dropTipUtils.resetTipStatus() + trackEvent({ + name: ANALYTICS_PROTOCOL_PROCEED_TO_RUN, + properties: { sourceLocation: 'RunRecordDetail', robotSerialNumber }, + }) + trackProtocolRunEvent({ + name: ANALYTICS_PROTOCOL_RUN_ACTION.AGAIN, + }) + } + } + + return { buttonText, handleButtonClick, buttonIconName } +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/hooks/useIsFixtureMismatch.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/hooks/useIsFixtureMismatch.ts new file mode 100644 index 00000000000..aa49528efa2 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/hooks/useIsFixtureMismatch.ts @@ -0,0 +1,20 @@ +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { useRobotType } from '/app/redux-resources/robots' +import { + getIsFixtureMismatch, + useDeckConfigurationCompatibility, +} from '/app/resources/deck_configuration' + +export function useIsFixtureMismatch( + runId: string, + robotName: string +): boolean { + const robotProtocolAnalysis = useMostRecentCompletedAnalysis(runId) + const robotType = useRobotType(robotName) + const deckConfigCompatibility = useDeckConfigurationCompatibility( + robotType, + robotProtocolAnalysis + ) + + return getIsFixtureMismatch(deckConfigCompatibility) +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/index.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/index.tsx new file mode 100644 index 00000000000..4b8c0f68076 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/index.tsx @@ -0,0 +1,148 @@ +import type * as React from 'react' + +import { RUN_STATUS_STOP_REQUESTED } from '@opentrons/api-client' +import { + ALIGN_CENTER, + DISPLAY_FLEX, + Icon, + JUSTIFY_CENTER, + PrimaryButton, + SIZE_1, + SPACING, + StyledText, + Tooltip, + useHoverTooltip, +} from '@opentrons/components' + +import { useRobot } from '/app/redux-resources/robots' +import { useRobotAnalyticsData } from '/app/redux-resources/analytics' +import { + useCloseCurrentRun, + useCurrentRunId, + useProtocolDetailsForRun, + useRunCalibrationStatus, + useUnmatchedModulesForProtocol, + useModuleCalibrationStatus, +} from '/app/resources/runs' +import { useActionBtnDisabledUtils, useActionButtonProperties } from './hooks' +import { getFallbackRobotSerialNumber, isRunAgainStatus } from '../../utils' +import { useIsRobotOnWrongVersionOfSoftware } from '/app/redux/robot-update' + +import type { RunHeaderContentProps } from '..' + +export type BaseActionButtonProps = RunHeaderContentProps + +interface ActionButtonProps extends BaseActionButtonProps { + isResetRunLoadingRef: React.MutableRefObject +} + +export function ActionButton(props: ActionButtonProps): JSX.Element { + const { + runId, + robotName, + runStatus, + isResetRunLoadingRef, + runHeaderModalContainerUtils, + } = props + const { + missingStepsModalUtils, + HSConfirmationModalUtils, + } = runHeaderModalContainerUtils + + const [targetProps, tooltipProps] = useHoverTooltip() + const { isProtocolAnalyzing, protocolData } = useProtocolDetailsForRun(runId) + const { missingModuleIds } = useUnmatchedModulesForProtocol(robotName, runId) + const { complete: isCalibrationComplete } = useRunCalibrationStatus( + robotName, + runId + ) + const { complete: isModuleCalibrationComplete } = useModuleCalibrationStatus( + robotName, + runId + ) + const isRobotOnWrongVersionOfSoftware = useIsRobotOnWrongVersionOfSoftware( + robotName + ) + const currentRunId = useCurrentRunId() + + const isSetupComplete = + isCalibrationComplete && + isModuleCalibrationComplete && + missingModuleIds.length === 0 + const isCurrentRun = currentRunId === runId + const isOtherRunCurrent = currentRunId != null && currentRunId !== runId + const isProtocolNotReady = protocolData == null || !!isProtocolAnalyzing + const isValidRunAgain = isRunAgainStatus(runStatus) + const { isClosingCurrentRun } = useCloseCurrentRun() + + const { isDisabled, disabledReason } = useActionBtnDisabledUtils({ + isCurrentRun, + isSetupComplete, + isOtherRunCurrent, + isProtocolNotReady, + isRobotOnWrongVersionOfSoftware, + isValidRunAgain, + isClosingCurrentRun, + ...props, + }) + + const robot = useRobot(robotName) + const robotSerialNumber = getFallbackRobotSerialNumber(robot) + const robotAnalyticsData = useRobotAnalyticsData(robotName) + + const validRunAgainButRequiresSetup = isValidRunAgain && !isSetupComplete + + const { + buttonText, + handleButtonClick, + buttonIconName, + } = useActionButtonProperties({ + isProtocolNotReady, + confirmMissingSteps: missingStepsModalUtils.conditionalConfirmUtils.confirm, + confirmAttachment: HSConfirmationModalUtils.conditionalConfirmUtils.confirm, + robotAnalyticsData, + robotSerialNumber, + currentRunId, + isValidRunAgain, + isOtherRunCurrent, + isRobotOnWrongVersionOfSoftware, + isClosingCurrentRun, + ...props, + }) + + return ( + <> + + {buttonIconName != null ? ( + + ) : null} + {buttonText} + + {disabledReason && ( + + {disabledReason} + + )} + + ) +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/LabeledValue.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/LabeledValue.tsx new file mode 100644 index 00000000000..135dd72bbae --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/LabeledValue.tsx @@ -0,0 +1,29 @@ +import type * as React from 'react' + +import { + DIRECTION_COLUMN, + COLORS, + SPACING, + Flex, + StyledText, +} from '@opentrons/components' + +interface LabeledValueProps { + label: string + value: React.ReactNode +} + +export function LabeledValue(props: LabeledValueProps): JSX.Element { + return ( + + + {props.label} + + {typeof props.value === 'string' ? ( + {props.value} + ) : ( + props.value + )} + + ) +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/RunHeaderSectionLower.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/RunHeaderSectionLower.tsx new file mode 100644 index 00000000000..d7757cd4fe9 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/RunHeaderSectionLower.tsx @@ -0,0 +1,77 @@ +import { useTranslation } from 'react-i18next' +import { css } from 'styled-components' + +import { + BORDERS, + Box, + COLORS, + DISPLAY_GRID, + Flex, + JUSTIFY_FLEX_END, + SecondaryButton, + SPACING, +} from '@opentrons/components' +import { RUN_STATUS_RUNNING } from '@opentrons/api-client' + +import { formatTimestamp } from '/app/transformations/runs' +import { useRunControls } from '/app/organisms/RunTimeControl/hooks' +import { + EMPTY_TIMESTAMP, + useRunTimestamps, + useCloseCurrentRun, +} from '/app/resources/runs' +import { LabeledValue } from './LabeledValue' +import { isCancellableStatus } from '../utils' + +import type { RunHeaderContentProps } from '.' + +// The lower row of Protocol Run Header. +export function RunHeaderSectionLower({ + runId, + runStatus, + runHeaderModalContainerUtils, +}: RunHeaderContentProps): JSX.Element { + const { t } = useTranslation('run_details') + + const { startedAt, completedAt } = useRunTimestamps(runId) + const { pause } = useRunControls(runId) + const { isClosingCurrentRun } = useCloseCurrentRun() + + const startedAtTimestamp = + startedAt != null ? formatTimestamp(startedAt) : EMPTY_TIMESTAMP + const completedAtTimestamp = + completedAt != null ? formatTimestamp(completedAt) : EMPTY_TIMESTAMP + + const handleCancelRunClick = (): void => { + if (runStatus === RUN_STATUS_RUNNING) { + pause() + } + runHeaderModalContainerUtils.confirmCancelModalUtils.toggleModal() + } + + return ( + + + + + {isCancellableStatus(runStatus) && ( + + {t('cancel_run')} + + )} + + + ) +} + +const SECTION_STYLE = css` + display: ${DISPLAY_GRID}; + grid-template-columns: 4fr 6fr 4fr; + background-color: ${COLORS.grey10}; + padding: ${SPACING.spacing8}; + border-radius: ${BORDERS.borderRadius4}; +` diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/RunHeaderSectionUpper.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/RunHeaderSectionUpper.tsx new file mode 100644 index 00000000000..ecbee666819 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/RunHeaderSectionUpper.tsx @@ -0,0 +1,58 @@ +import { useTranslation } from 'react-i18next' +import { css } from 'styled-components' + +import { + Box, + DISPLAY_GRID, + Flex, + JUSTIFY_FLEX_END, +} from '@opentrons/components' + +import { LabeledValue } from './LabeledValue' +import { DisplayRunStatus } from '../DisplayRunStatus' +import { RunTimer } from '/app/molecules/RunTimer' +import { ActionButton } from './ActionButton' +import { useRunTimestamps, useRunCreatedAtTimestamp } from '/app/resources/runs' + +import type { RunHeaderContentProps } from '.' + +// The upper row of Protocol Run Header. +export function RunHeaderSectionUpper( + props: RunHeaderContentProps +): JSX.Element { + const { runId, runStatus } = props + + const { t } = useTranslation('run_details') + + const createdAtTimestamp = useRunCreatedAtTimestamp(runId) + const { startedAt, stoppedAt, completedAt } = useRunTimestamps(runId) + + return ( + + + } + /> + + } + /> + + + + + ) +} + +const SECTION_STYLE = css` + display: ${DISPLAY_GRID}; + grid-template-columns: 4fr 3fr 3fr 4fr; +` diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/index.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/index.tsx new file mode 100644 index 00000000000..51908e4435d --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/index.tsx @@ -0,0 +1,26 @@ +import type * as React from 'react' + +import { RunHeaderSectionUpper } from './RunHeaderSectionUpper' +import { RunHeaderSectionLower } from './RunHeaderSectionLower' + +import type { ProtocolRunHeaderProps } from '..' +import type { AttachedModule, RunStatus } from '@opentrons/api-client' +import type { RunControls } from '/app/organisms/RunTimeControl' +import type { UseRunHeaderModalContainerResult } from '../RunHeaderModalContainer' + +export type RunHeaderContentProps = ProtocolRunHeaderProps & { + runStatus: RunStatus | null + isResetRunLoadingRef: React.MutableRefObject + attachedModules: AttachedModule[] + protocolRunControls: RunControls + runHeaderModalContainerUtils: UseRunHeaderModalContainerResult +} + +export function RunHeaderContent(props: RunHeaderContentProps): JSX.Element { + return ( + <> + + {props.runStatus != null ? : null} + + ) +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/RunHeaderModalContainer.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/RunHeaderModalContainer.tsx new file mode 100644 index 00000000000..0c306339f69 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/RunHeaderModalContainer.tsx @@ -0,0 +1,94 @@ +import { ErrorRecoveryFlows } from '/app/organisms/ErrorRecoveryFlows' +import { DropTipWizardFlows } from '/app/organisms/DropTipWizardFlows' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { + ConfirmCancelModal, + HeaterShakerIsRunningModal, + ProtocolAnalysisErrorModal, + ProtocolDropTipModal, + RunFailedModal, + ConfirmMissingStepsModal, +} from './modals' +import { ConfirmAttachmentModal } from '/app/organisms/ModuleCard/ConfirmAttachmentModal' + +import type { RunStatus } from '@opentrons/api-client' +import type { RunControls } from '/app/organisms/RunTimeControl' +import type { UseRunErrorsResult } from '../hooks' +import type { UseRunHeaderModalContainerResult } from '.' + +export interface RunHeaderModalContainerProps { + runId: string + runStatus: RunStatus | null + robotName: string + protocolRunControls: RunControls + runHeaderModalContainerUtils: UseRunHeaderModalContainerResult + runErrors: UseRunErrorsResult +} + +// Contains all the various modals that render in ProtocolRunHeader. +export function RunHeaderModalContainer( + props: RunHeaderModalContainerProps +): JSX.Element | null { + const { runId, runStatus, runHeaderModalContainerUtils } = props + const robotProtocolAnalysis = useMostRecentCompletedAnalysis(runId) + + const { + confirmCancelModalUtils, + analysisErrorModalUtils, + HSConfirmationModalUtils, + HSRunningModalUtils, + runFailedModalUtils, + recoveryModalUtils, + missingStepsModalUtils, + dropTipUtils, + } = runHeaderModalContainerUtils + const { dropTipModalUtils, dropTipWizardUtils } = dropTipUtils + + // TODO(jh, 09-10-24): Instead of having each modal be responsible for its own portal, do all the portaling here. + return ( + <> + {recoveryModalUtils.isERActive ? ( + + ) : null} + {runFailedModalUtils.showRunFailedModal ? ( + + ) : null} + {confirmCancelModalUtils.showModal ? ( + + ) : null} + {dropTipWizardUtils.showDTWiz ? ( + + ) : null} + {analysisErrorModalUtils.showModal ? ( + + ) : null} + {dropTipModalUtils.showModal ? ( + + ) : null} + {HSRunningModalUtils.showModal ? ( + + ) : null} + {HSConfirmationModalUtils.showModal && ( + + )} + {missingStepsModalUtils.showModal && ( + + )} + + ) +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/index.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/index.ts new file mode 100644 index 00000000000..3aac8c93a83 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/index.ts @@ -0,0 +1,3 @@ +export * from './useHeaterShakerConfirmationModal' +export * from './useMissingStepsModal' +export * from './useRunHeaderDropTip' diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useHeaterShakerConfirmationModal.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useHeaterShakerConfirmationModal.ts new file mode 100644 index 00000000000..d8b001f3638 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useHeaterShakerConfirmationModal.ts @@ -0,0 +1,50 @@ +import { useSelector } from 'react-redux' + +import { useConditionalConfirm } from '@opentrons/components' + +import { getIsHeaterShakerAttached } from '/app/redux/config' + +import type { UseConditionalConfirmResult } from '@opentrons/components' +import type { ConfirmAttachmentModalProps } from '/app/organisms/ModuleCard/ConfirmAttachmentModal' + +export type UseHeaterShakerConfirmationModalResult = + | { + showModal: true + modalProps: ConfirmAttachmentModalProps + conditionalConfirmUtils: UseConditionalConfirmResult<[]> + } + | { + showModal: false + modalProps: null + conditionalConfirmUtils: UseConditionalConfirmResult<[]> + } + +export function useHeaterShakerConfirmationModal( + handleProceedToRunClick: () => void +): UseHeaterShakerConfirmationModalResult { + const configBypassHeaterShakerAttachmentConfirmation = useSelector( + getIsHeaterShakerAttached + ) + const conditionalConfirmUtils = useConditionalConfirm( + handleProceedToRunClick, + !configBypassHeaterShakerAttachmentConfirmation + ) + + const modalProps: ConfirmAttachmentModalProps = { + onCloseClick: conditionalConfirmUtils.cancel, + isProceedToRunModal: true, + onConfirmClick: conditionalConfirmUtils.confirm, + } + + return conditionalConfirmUtils.showConfirmation + ? { + showModal: true, + modalProps, + conditionalConfirmUtils, + } + : { + showModal: false, + modalProps: null, + conditionalConfirmUtils, + } +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useMissingStepsModal.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useMissingStepsModal.ts new file mode 100644 index 00000000000..a25a201ef13 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useMissingStepsModal.ts @@ -0,0 +1,86 @@ +import { useSelector } from 'react-redux' +import { RUN_STATUS_IDLE, RUN_STATUS_STOPPED } from '@opentrons/api-client' +import { useConditionalConfirm } from '@opentrons/components' + +import { useIsHeaterShakerInProtocol } from '/app/organisms/ModuleCard/hooks' +import { isAnyHeaterShakerShaking } from '../modals' +import { + getMissingSetupSteps, + MODULE_SETUP_STEP_KEY, + ROBOT_CALIBRATION_STEP_KEY, +} from '/app/redux/protocol-runs' + +import type { UseConditionalConfirmResult } from '@opentrons/components' +import type { RunStatus, AttachedModule } from '@opentrons/api-client' +import type { ConfirmMissingStepsModalProps } from '../modals' +import type { State } from '/app/redux/types' +import type { StepKey } from '/app/redux/protocol-runs' + +const UNCONFIRMABLE_MISSING_STEPS = new Set([ + ROBOT_CALIBRATION_STEP_KEY, + MODULE_SETUP_STEP_KEY, +]) + +interface UseMissingStepsModalProps { + runStatus: RunStatus | null + attachedModules: AttachedModule[] + runId: string + handleProceedToRunClick: () => void +} + +export type UseMissingStepsModalResult = + | { + showModal: true + modalProps: ConfirmMissingStepsModalProps + conditionalConfirmUtils: UseConditionalConfirmResult<[]> + } + | { + showModal: false + modalProps: null + conditionalConfirmUtils: UseConditionalConfirmResult<[]> + } + +export function useMissingStepsModal({ + attachedModules, + runStatus, + runId, + handleProceedToRunClick, +}: UseMissingStepsModalProps): UseMissingStepsModalResult { + const isHeaterShakerInProtocol = useIsHeaterShakerInProtocol() + const isHeaterShakerShaking = isAnyHeaterShakerShaking(attachedModules) + const missingSetupSteps = useSelector((state: State) => + getMissingSetupSteps(state, runId) + ) + const shouldShowHSConfirm = + isHeaterShakerInProtocol && + !isHeaterShakerShaking && + (runStatus === RUN_STATUS_IDLE || runStatus === RUN_STATUS_STOPPED) + + // Certain steps are not confirmed by the app, so don't include these in the modal. + const reportableMissingSetupSteps = missingSetupSteps.filter( + step => !UNCONFIRMABLE_MISSING_STEPS.has(step) + ) + + const conditionalConfirmUtils = useConditionalConfirm( + handleProceedToRunClick, + reportableMissingSetupSteps.length !== 0 + ) + + const modalProps: ConfirmMissingStepsModalProps = { + onCloseClick: conditionalConfirmUtils.cancel, + onConfirmClick: () => { + shouldShowHSConfirm + ? conditionalConfirmUtils.confirm() + : handleProceedToRunClick() + }, + missingSteps: reportableMissingSetupSteps, + } + + return conditionalConfirmUtils.showConfirmation + ? { + showModal: true, + modalProps, + conditionalConfirmUtils, + } + : { showModal: false, modalProps: null, conditionalConfirmUtils } +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useRunHeaderDropTip.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useRunHeaderDropTip.ts new file mode 100644 index 00000000000..82d9c30c84b --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/hooks/useRunHeaderDropTip.ts @@ -0,0 +1,167 @@ +import { useEffect } from 'react' + +import { RUN_STATUS_IDLE, RUN_STATUS_STOPPED } from '@opentrons/api-client' +import { FLEX_ROBOT_TYPE, OT2_ROBOT_TYPE } from '@opentrons/shared-data' + +import { useDropTipWizardFlows } from '/app/organisms/DropTipWizardFlows' +import { useProtocolDropTipModal } from '../modals' +import { + useCloseCurrentRun, + useCurrentRunCommands, + useIsRunCurrent, +} from '/app/resources/runs' +import { isTerminalRunStatus } from '../../utils' +import { lastRunCommandPromptedErrorRecovery } from '/app/local-resources/commands' +import { useTipAttachmentStatus } from '/app/resources/instruments' + +import type { RobotType } from '@opentrons/shared-data' +import type { Run, RunStatus } from '@opentrons/api-client' +import type { + PipetteWithTip, + TipAttachmentStatusResult, +} from '/app/resources/instruments' +import type { DropTipWizardFlowsProps } from '/app/organisms/DropTipWizardFlows' +import type { UseProtocolDropTipModalResult } from '../modals' +import type { PipetteDetails } from '/app/resources/maintenance_runs' + +export type RunHeaderDropTipWizProps = + | { showDTWiz: true; dtWizProps: DropTipWizardFlowsProps } + | { showDTWiz: false; dtWizProps: null } + +export interface UseRunHeaderDropTipParams { + runId: string + runRecord: Run | null + robotType: RobotType + runStatus: RunStatus | null +} + +export interface UseRunHeaderDropTipResult { + dropTipModalUtils: UseProtocolDropTipModalResult + dropTipWizardUtils: RunHeaderDropTipWizProps + resetTipStatus: TipAttachmentStatusResult['resetTipStatus'] +} + +// Handles all the tip related logic during a protocol run on the desktop app. +export function useRunHeaderDropTip({ + runId, + runRecord, + robotType, + runStatus, +}: UseRunHeaderDropTipParams): UseRunHeaderDropTipResult { + const isRunCurrent = useIsRunCurrent(runId) + const enteredER = runRecord?.data.hasEverEnteredErrorRecovery ?? false + + const { closeCurrentRun } = useCloseCurrentRun() + const { showDTWiz, disableDTWiz, enableDTWiz } = useDropTipWizardFlows() + + const { + areTipsAttached, + determineTipStatus, + resetTipStatus, + setTipStatusResolved, + aPipetteWithTip, + initialPipettesWithTipsCount, + } = useTipAttachmentStatus({ + runId, + runRecord: runRecord ?? null, + }) + + const dropTipModalUtils = useProtocolDropTipModal({ + areTipsAttached, + enableDTWiz, + isRunCurrent, + currentRunId: runId, + pipetteInfo: buildPipetteDetails(aPipetteWithTip), + onSkipAndHome: () => { + closeCurrentRun() + }, + }) + + // The onCloseFlow for Drop Tip Wizard + const onCloseFlow = (isTakeover?: boolean): void => { + if (isTakeover) { + disableDTWiz() + } else { + void setTipStatusResolved(() => { + disableDTWiz() + closeCurrentRun() + }, disableDTWiz) + } + } + + const buildDTWizUtils = (): RunHeaderDropTipWizProps => { + return showDTWiz && aPipetteWithTip != null + ? { + showDTWiz: true, + dtWizProps: { + robotType, + mount: aPipetteWithTip.mount, + instrumentModelSpecs: aPipetteWithTip.specs, + closeFlow: onCloseFlow, + modalStyle: 'simple', + }, + } + : { showDTWiz: false, dtWizProps: null } + } + + const runSummaryNoFixit = useCurrentRunCommands( + { + includeFixitCommands: false, + pageLength: 1, + }, + { enabled: isTerminalRunStatus(runStatus) } + ) + // Manage tip checking + useEffect(() => { + // If a user begins a new run without navigating away from the run page, reset tip status. + if (robotType === FLEX_ROBOT_TYPE) { + if (runStatus === RUN_STATUS_IDLE) { + resetTipStatus() + } + // Only determine tip status when necessary as this can be an expensive operation. Error Recovery handles tips, so don't + // have to do it here if done during Error Recovery. + else if ( + runSummaryNoFixit != null && + runSummaryNoFixit.length > 0 && + !lastRunCommandPromptedErrorRecovery(runSummaryNoFixit) && + isTerminalRunStatus(runStatus) + ) { + void determineTipStatus() + } + } + }, [runStatus, robotType, runSummaryNoFixit]) + + // TODO(jh, 08-15-24): The enteredER condition is a hack, because errorCommands are only returned when a run is current. + // Ideally the run should not need to be current to view errorCommands. + + // If the run terminates with a "stopped" status, close the run if no tips are attached after running tip check at least once. + // This marks the robot as "not busy" if drop tip CTAs are unnecessary. + useEffect(() => { + if ( + runStatus === RUN_STATUS_STOPPED && + isRunCurrent && + (initialPipettesWithTipsCount === 0 || robotType === OT2_ROBOT_TYPE) && + !enteredER + ) { + closeCurrentRun() + } + }, [runStatus, isRunCurrent, enteredER, initialPipettesWithTipsCount]) + + return { + dropTipModalUtils, + dropTipWizardUtils: buildDTWizUtils(), + resetTipStatus, + } +} + +// TODO(jh, 09-12-24): Consolidate this with the same utility that exists elsewhere. +function buildPipetteDetails( + aPipetteWithTip: PipetteWithTip | null +): PipetteDetails | null { + return aPipetteWithTip != null + ? { + pipetteId: aPipetteWithTip.specs.name, + mount: aPipetteWithTip.mount, + } + : null +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/index.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/index.ts new file mode 100644 index 00000000000..d26b6f2bfa6 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/index.ts @@ -0,0 +1,2 @@ +export * from './RunHeaderModalContainer' +export * from './useRunHeaderModalContainer' diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ConfirmCancelModal.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ConfirmCancelModal.tsx new file mode 100644 index 00000000000..770217dad82 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ConfirmCancelModal.tsx @@ -0,0 +1,131 @@ +import { useState, useEffect } from 'react' +import { createPortal } from 'react-dom' +import { useTranslation } from 'react-i18next' + +import { + AlertPrimaryButton, + ALIGN_CENTER, + COLORS, + DIRECTION_COLUMN, + Flex, + Icon, + JUSTIFY_FLEX_END, + Link, + Modal, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' +import { + RUN_STATUS_STOPPED, + RUN_STATUS_STOP_REQUESTED, +} from '@opentrons/api-client' +import { useStopRunMutation } from '@opentrons/react-api-client' + +import { getTopPortalEl } from '/app/App/portal' +import { useTrackProtocolRunEvent } from '/app/redux-resources/analytics' +import { useIsFlex } from '/app/redux-resources/robots' +import { ANALYTICS_PROTOCOL_RUN_ACTION } from '/app/redux/analytics' + +import type { MouseEventHandler } from 'react' +import type { RunStatus } from '@opentrons/api-client' + +export interface UseConfirmCancelModalResult { + showModal: boolean + toggleModal: () => void +} + +export function useConfirmCancelModal(): UseConfirmCancelModalResult { + const [showModal, setShowModal] = useState(false) + + const toggleModal = (): void => { + setShowModal(!showModal) + } + + return { showModal, toggleModal } +} + +export interface ConfirmCancelModalProps { + onClose: () => unknown + runId: string + robotName: string + runStatus: RunStatus | null +} + +export function ConfirmCancelModal( + props: ConfirmCancelModalProps +): JSX.Element { + const { onClose, runId, robotName, runStatus } = props + const { stopRun } = useStopRunMutation() + const isFlex = useIsFlex(robotName) + const { trackProtocolRunEvent } = useTrackProtocolRunEvent(runId, robotName) + const [isCanceling, setIsCanceling] = useState(false) + const { t } = useTranslation('run_details') + + const cancelRunAlertInfo = isFlex + ? t('cancel_run_alert_info_flex') + : t('cancel_run_alert_info_ot2') + + const cancelRun: MouseEventHandler = (e): void => { + e.preventDefault() + e.stopPropagation() + setIsCanceling(true) + stopRun(runId, { + onSuccess: () => { + trackProtocolRunEvent({ name: ANALYTICS_PROTOCOL_RUN_ACTION.CANCEL }) + }, + onError: () => { + setIsCanceling(false) + }, + }) + } + + useEffect(() => { + if ( + runStatus === RUN_STATUS_STOP_REQUESTED || + runStatus === RUN_STATUS_STOPPED + ) { + onClose() + } + }, [runStatus, onClose]) + + return createPortal( + + + {cancelRunAlertInfo} + + {t('cancel_run_module_info')} + + + {isCanceling ? null : ( + + {t('cancel_run_modal_back')} + + )} + + {isCanceling ? ( + + ) : ( + t('cancel_run_modal_confirm') + )} + + + + , + getTopPortalEl() + ) +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ConfirmMissingStepsModal.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ConfirmMissingStepsModal.tsx new file mode 100644 index 00000000000..8203e126a2d --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ConfirmMissingStepsModal.tsx @@ -0,0 +1,80 @@ +import { useTranslation } from 'react-i18next' +import { + ALIGN_CENTER, + DIRECTION_COLUMN, + DIRECTION_ROW, + Flex, + JUSTIFY_FLEX_END, + PrimaryButton, + SecondaryButton, + SPACING, + LegacyStyledText, + TYPOGRAPHY, + Modal, +} from '@opentrons/components' +import { + LPC_STEP_KEY, + LABWARE_SETUP_STEP_KEY, + LIQUID_SETUP_STEP_KEY, + MODULE_SETUP_STEP_KEY, + ROBOT_CALIBRATION_STEP_KEY, +} from '/app/redux/protocol-runs' +import type { StepKey } from '/app/redux/protocol-runs' + +const STEP_KEY_TO_I18N_KEY = { + [LPC_STEP_KEY]: 'applied_labware_offsets', + [LABWARE_SETUP_STEP_KEY]: 'labware_placement', + [LIQUID_SETUP_STEP_KEY]: 'liquids', + [MODULE_SETUP_STEP_KEY]: 'module_setup', + [ROBOT_CALIBRATION_STEP_KEY]: 'robot_calibration', +} + +export interface ConfirmMissingStepsModalProps { + onCloseClick: () => void + onConfirmClick: () => void + missingSteps: StepKey[] +} +export const ConfirmMissingStepsModal = ( + props: ConfirmMissingStepsModalProps +): JSX.Element | null => { + const { missingSteps, onCloseClick, onConfirmClick } = props + const { t, i18n } = useTranslation(['protocol_setup', 'shared']) + + const confirmAttached = (): void => { + onConfirmClick() + onCloseClick() + } + + return ( + + + + {t('you_havent_confirmed', { + missingSteps: new Intl.ListFormat('en', { + style: 'short', + type: 'conjunction', + }).format(missingSteps.map(step => t(STEP_KEY_TO_I18N_KEY[step]))), + })} + + + + + {i18n.format(t('shared:go_back'), 'capitalize')} + + + {t('start_run')} + + + + ) +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/HeaterShakerIsRunningModal.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/HeaterShakerIsRunningModal.tsx new file mode 100644 index 00000000000..696014ac8bc --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/HeaterShakerIsRunningModal.tsx @@ -0,0 +1,148 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' + +import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' +import { + Box, + COLORS, + DIRECTION_ROW, + Flex, + Icon, + JUSTIFY_FLEX_END, + PrimaryButton, + SecondaryButton, + SPACING, + LegacyStyledText, + TYPOGRAPHY, + Modal, +} from '@opentrons/components' +import { HEATERSHAKER_MODULE_TYPE } from '@opentrons/shared-data' + +import { useAttachedModules } from '/app/resources/modules' +import { HeaterShakerModuleCard } from './HeaterShakerModuleCard' +import { getActiveHeaterShaker } from './utils' +import { useIsHeaterShakerInProtocol } from '/app/organisms/ModuleCard/hooks' + +import type { HeaterShakerDeactivateShakerCreateCommand } from '@opentrons/shared-data' +import type { AttachedModule } from '@opentrons/api-client' +import type { HeaterShakerModule } from '/app/redux/modules/types' + +export type UseHeaterShakerIsRunningModalResult = + | { showModal: true; module: HeaterShakerModule; toggleModal: () => void } + | { showModal: false; module: null; toggleModal: null } + +export function useHeaterShakerIsRunningModal( + attachedModules: AttachedModule[] +): UseHeaterShakerIsRunningModalResult { + const [showIsShakingModal, setShowIsShakingModal] = useState(false) + + const activeHeaterShaker = getActiveHeaterShaker(attachedModules) + const isHeaterShakerInProtocol = useIsHeaterShakerInProtocol() + + const toggleModal = (): void => { + setShowIsShakingModal(!showIsShakingModal) + } + + const showModal = + showIsShakingModal && activeHeaterShaker != null && isHeaterShakerInProtocol + + return showModal + ? { + showModal: true, + module: activeHeaterShaker, + toggleModal, + } + : { showModal: false, module: null, toggleModal: null } +} + +interface HeaterShakerIsRunningModalProps { + closeModal: () => void + module: HeaterShakerModule + startRun: () => void +} + +export const HeaterShakerIsRunningModal = ( + props: HeaterShakerIsRunningModalProps +): JSX.Element => { + const { closeModal, module, startRun } = props + const { t } = useTranslation('heater_shaker') + const { createLiveCommand } = useCreateLiveCommandMutation() + const attachedModules = useAttachedModules() + const moduleIds = attachedModules + .filter( + (module): module is HeaterShakerModule => + module.moduleType === HEATERSHAKER_MODULE_TYPE && + module?.data != null && + module.data.speedStatus !== 'idle' + ) + .map(module => module.id) + + const title = ( + + + {t('heater_shaker_is_shaking')} + + ) + + const handleContinueShaking = (): void => { + startRun() + closeModal() + } + + const handleStopShake = (): void => { + moduleIds.forEach(moduleId => { + const stopShakeCommand: HeaterShakerDeactivateShakerCreateCommand = { + commandType: 'heaterShaker/deactivateShaker', + params: { + moduleId: moduleId, + }, + } + + createLiveCommand({ + command: stopShakeCommand, + }).catch((e: Error) => { + console.error( + `error setting module status with command type ${stopShakeCommand.commandType}: ${e.message}` + ) + }) + }) + handleContinueShaking() + } + + return ( + + + + + + {t('continue_shaking_protocol_start_prompt')} + + + + + {t('stop_shaking_start_run')} + + + {t('keep_shaking_start_run')} + + + + ) +} diff --git a/app/src/organisms/Devices/HeaterShakerWizard/HeaterShakerModuleCard.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/HeaterShakerModuleCard.tsx similarity index 88% rename from app/src/organisms/Devices/HeaterShakerWizard/HeaterShakerModuleCard.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/HeaterShakerModuleCard.tsx index 5b6f950a906..a0ae29ff7ec 100644 --- a/app/src/organisms/Devices/HeaterShakerWizard/HeaterShakerModuleCard.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/HeaterShakerModuleCard.tsx @@ -1,5 +1,5 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' + import { ALIGN_FLEX_START, COLORS, @@ -13,10 +13,11 @@ import { TYPOGRAPHY, } from '@opentrons/components' import { getModuleDisplayName } from '@opentrons/shared-data' -import heaterShakerModule from '../../../assets/images/heater_shaker_module_transparent.png' -import { HeaterShakerModuleData } from '../../ModuleCard/HeaterShakerModuleData' -import type { HeaterShakerModule } from '../../../redux/modules/types' +import heaterShakerModule from '/app/assets/images/heater_shaker_module_transparent.png' +import { HeaterShakerModuleData } from '/app/organisms/ModuleCard/HeaterShakerModuleData' + +import type { HeaterShakerModule } from '/app/redux/modules/types' interface HeaterShakerModuleCardProps { module: HeaterShakerModule diff --git a/app/src/organisms/Devices/__tests__/HeaterShakerIsRunningModal.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/__tests__/HeaterShakerIsRunningModal.test.tsx similarity index 89% rename from app/src/organisms/Devices/__tests__/HeaterShakerIsRunningModal.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/__tests__/HeaterShakerIsRunningModal.test.tsx index b447ad26ee5..03b59af1b57 100644 --- a/app/src/organisms/Devices/__tests__/HeaterShakerIsRunningModal.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/__tests__/HeaterShakerIsRunningModal.test.tsx @@ -1,16 +1,19 @@ -import * as React from 'react' -import { i18n } from '../../../i18n' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' -import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' + import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' -import { mockHeaterShaker } from '../../../redux/modules/__fixtures__' + +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { mockHeaterShaker } from '/app/redux/modules/__fixtures__' import { HeaterShakerIsRunningModal } from '../HeaterShakerIsRunningModal' -import { HeaterShakerModuleCard } from '../HeaterShakerWizard/HeaterShakerModuleCard' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useAttachedModules } from '../hooks' +import { HeaterShakerModuleCard } from '../HeaterShakerModuleCard' +import { useAttachedModules } from '/app/resources/modules' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' + import type * as ReactApiClient from '@opentrons/react-api-client' + vi.mock('@opentrons/react-api-client', async importOriginal => { const actual = await importOriginal() return { @@ -18,9 +21,9 @@ vi.mock('@opentrons/react-api-client', async importOriginal => { useCreateLiveCommandMutation: vi.fn(), } }) -vi.mock('../hooks') -vi.mock('../../LabwarePositionCheck/useMostRecentCompletedAnalysis') -vi.mock('../HeaterShakerWizard/HeaterShakerModuleCard') +vi.mock('/app/resources/modules') +vi.mock('/app/resources/runs') +vi.mock('../HeaterShakerModuleCard') const mockMovingHeaterShakerOne = { id: 'heatershaker_id_1', diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/__tests__/HeaterShakerModuleCard.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/__tests__/HeaterShakerModuleCard.test.tsx new file mode 100644 index 00000000000..4cd20822890 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/__tests__/HeaterShakerModuleCard.test.tsx @@ -0,0 +1,38 @@ +import type * as React from 'react' +import { screen } from '@testing-library/react' +import { describe, it, vi, beforeEach } from 'vitest' + +import { renderWithProviders } from '/app/__testing-utils__' +import { mockHeaterShaker } from '/app/redux/modules/__fixtures__' +import { i18n } from '/app/i18n' +import { HeaterShakerModuleCard } from '../HeaterShakerModuleCard' +import { HeaterShakerModuleData } from '/app/organisms/ModuleCard/HeaterShakerModuleData' + +vi.mock('/app/organisms/ModuleCard/HeaterShakerModuleData') + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] +} + +describe('HeaterShakerModuleCard', () => { + let props: React.ComponentProps + beforeEach(() => { + props = { + module: mockHeaterShaker, + } + vi.mocked(HeaterShakerModuleData).mockReturnValue( +
    mock heater shaker module data
    + ) + }) + + it('renders the correct info', () => { + render(props) + screen.getByText('usb-1') + screen.getByText('Heater-Shaker Module GEN1') + screen.getByText('mock heater shaker module data') + screen.getByAltText('Heater-Shaker') + screen.getByLabelText('heater-shaker') + }) +}) diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/__tests__/hooks.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/__tests__/hooks.test.tsx new file mode 100644 index 00000000000..c5028c6a821 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/__tests__/hooks.test.tsx @@ -0,0 +1,144 @@ +import type * as React from 'react' +import { Provider } from 'react-redux' +import { describe, it, vi, beforeEach, expect } from 'vitest' +import { createStore } from 'redux' +import { renderHook } from '@testing-library/react' + +import { HEATERSHAKER_MODULE_V1 } from '@opentrons/shared-data' + +import { RUN_ID_1 } from '/app/resources/runs/__fixtures__' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { useHeaterShakerModuleIdsFromRun } from '../hooks' + +import type { Store } from 'redux' +import type { State } from '/app/redux/types' + +vi.mock('/app/resources/runs') + +describe('useHeaterShakerModuleIdsFromRun', () => { + const store: Store = createStore(vi.fn(), {}) + + beforeEach(() => { + store.dispatch = vi.fn() + }) + + it('should return a heater shaker module id from protocol analysis load command result', () => { + vi.mocked(useMostRecentCompletedAnalysis).mockReturnValue({ + pipettes: {}, + labware: {}, + modules: { + heatershaker_id: { + model: HEATERSHAKER_MODULE_V1, + }, + }, + labwareDefinitions: {}, + commands: [ + { + id: 'mock_command_1', + createdAt: '2022-07-27T22:26:33.846399+00:00', + commandType: 'loadModule', + key: '286d7201-bfdc-4c2c-ae67-544367dbbabe', + status: 'succeeded', + params: { + model: HEATERSHAKER_MODULE_V1, + location: { + slotName: '1', + }, + moduleId: 'heatershaker_id', + }, + result: { + moduleId: 'heatershaker_id', + definition: {}, + model: HEATERSHAKER_MODULE_V1, + serialNumber: 'fake-serial-number-1', + }, + startedAt: '2022-07-27T22:26:33.875106+00:00', + completedAt: '2022-07-27T22:26:33.878079+00:00', + }, + ], + } as any) + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => {children} + const { result } = renderHook( + () => useHeaterShakerModuleIdsFromRun(RUN_ID_1), + { wrapper } + ) + + const moduleIdsFromRun = result.current + expect(moduleIdsFromRun.moduleIdsFromRun).toStrictEqual(['heatershaker_id']) + }) + + it('should return two heater shaker module ids if two modules are loaded in the protocol', () => { + vi.mocked(useMostRecentCompletedAnalysis).mockReturnValue({ + pipettes: {}, + labware: {}, + modules: { + heatershaker_id: { + model: HEATERSHAKER_MODULE_V1, + }, + }, + labwareDefinitions: {}, + commands: [ + { + id: 'mock_command_1', + createdAt: '2022-07-27T22:26:33.846399+00:00', + commandType: 'loadModule', + key: '286d7201-bfdc-4c2c-ae67-544367dbbabe', + status: 'succeeded', + params: { + model: HEATERSHAKER_MODULE_V1, + location: { + slotName: '1', + }, + moduleId: 'heatershaker_id_1', + }, + result: { + moduleId: 'heatershaker_id_1', + definition: {}, + model: HEATERSHAKER_MODULE_V1, + serialNumber: 'fake-serial-number-1', + }, + startedAt: '2022-07-27T22:26:33.875106+00:00', + completedAt: '2022-07-27T22:26:33.878079+00:00', + }, + { + id: 'mock_command_2', + createdAt: '2022-07-27T22:26:33.846399+00:00', + commandType: 'loadModule', + key: '286d7201-bfdc-4c2c-ae67-544367dbbabe', + status: 'succeeded', + params: { + model: HEATERSHAKER_MODULE_V1, + location: { + slotName: '1', + }, + moduleId: 'heatershaker_id_2', + }, + result: { + moduleId: 'heatershaker_id_2', + definition: {}, + model: 'heaterShakerModuleV1_2', + serialNumber: 'fake-serial-number-2', + }, + startedAt: '2022-07-27T22:26:33.875106+00:00', + completedAt: '2022-07-27T22:26:33.878079+00:00', + }, + ], + } as any) + + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => {children} + const { result } = renderHook( + () => useHeaterShakerModuleIdsFromRun(RUN_ID_1), + { wrapper } + ) + + const moduleIdsFromRun = result.current + expect(moduleIdsFromRun.moduleIdsFromRun).toStrictEqual([ + 'heatershaker_id_1', + 'heatershaker_id_2', + ]) + }) +}) diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/hooks.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/hooks.tsx new file mode 100644 index 00000000000..903327c8834 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/hooks.tsx @@ -0,0 +1,24 @@ +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' + +export interface ModuleIdsFromRun { + moduleIdsFromRun: string[] +} + +export function useHeaterShakerModuleIdsFromRun( + runId: string | null +): ModuleIdsFromRun { + const protocolData = useMostRecentCompletedAnalysis(runId) + + const loadModuleCommands = protocolData?.commands.filter( + command => + command.commandType === 'loadModule' && + command.params.model === 'heaterShakerModuleV1' + ) + + const moduleIdsFromRun = + loadModuleCommands != null + ? loadModuleCommands?.map(command => command.result?.moduleId) + : [] + + return { moduleIdsFromRun } +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/index.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/index.ts new file mode 100644 index 00000000000..46ea32198c8 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/index.ts @@ -0,0 +1,2 @@ +export * from './HeaterShakerIsRunningModal' +export { isAnyHeaterShakerShaking } from './utils' diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/utils.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/utils.ts new file mode 100644 index 00000000000..63afeb2331c --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/HeaterShakerIsRunningModal/utils.ts @@ -0,0 +1,24 @@ +import type { HeaterShakerModule } from '/app/redux/modules/types' +import type { AttachedModule } from '@opentrons/api-client' + +export function getActiveHeaterShaker( + attachedModules: any[] +): HeaterShakerModule | undefined { + return attachedModules.find( + (module): module is HeaterShakerModule => + module.moduleType === 'heaterShakerModuleType' && + module?.data != null && + module.data.speedStatus !== 'idle' + ) +} + +export function isAnyHeaterShakerShaking( + attachedModules: AttachedModule[] +): boolean { + return attachedModules + .filter( + (module): module is HeaterShakerModule => + module.moduleType === 'heaterShakerModuleType' + ) + .some(module => module?.data != null && module.data.speedStatus !== 'idle') +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ProtocolAnalysisErrorModal.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ProtocolAnalysisErrorModal.tsx new file mode 100644 index 00000000000..5a19c099a34 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ProtocolAnalysisErrorModal.tsx @@ -0,0 +1,114 @@ +import { useState, useEffect } from 'react' +import { createPortal } from 'react-dom' +import { useTranslation } from 'react-i18next' + +import { + Flex, + JUSTIFY_FLEX_END, + OVERFLOW_WRAP_ANYWHERE, + PrimaryButton, + SPACING, + LegacyStyledText, + Modal, + TYPOGRAPHY, +} from '@opentrons/components' + +import { getTopPortalEl } from '/app/App/portal' +import { useProtocolAnalysisErrors } from '/app/resources/runs' + +import type { AnalysisError } from '@opentrons/shared-data' + +export type UseAnalysisErrorsModalProps = Omit< + ProtocolAnalysisErrorModalProps, + 'errors' | 'onClose' +> & { runId: string | null } + +export type UseAnalysisErrorsModalResult = + | { showModal: false; modalProps: null } + | { showModal: true; modalProps: ProtocolAnalysisErrorModalProps } + +// Provides validated modal props. Implicitly set the modal to true if analysis errors are present. +export function useProtocolAnalysisErrorsModal({ + robotName, + displayName, + runId, +}: UseAnalysisErrorsModalProps): UseAnalysisErrorsModalResult { + const { analysisErrors } = useProtocolAnalysisErrors(runId) + const [showModal, setShowModal] = useState(false) + + useEffect(() => { + if (analysisErrors != null && analysisErrors?.length > 0) { + setShowModal(true) + } + }, [analysisErrors]) + + const toggleModal = (): void => { + setShowModal(false) + } + + return showModal && analysisErrors != null && analysisErrors.length > 0 + ? { + showModal: true, + modalProps: { + onClose: toggleModal, + errors: analysisErrors, + robotName, + displayName, + }, + } + : { showModal: false, modalProps: null } +} + +export interface ProtocolAnalysisErrorModalProps { + displayName: string | null + errors: AnalysisError[] + onClose: () => void + robotName: string +} + +export function ProtocolAnalysisErrorModal({ + displayName, + errors, + onClose, + robotName, +}: ProtocolAnalysisErrorModalProps): JSX.Element { + const { t } = useTranslation(['run_details', 'shared']) + + return createPortal( + + + {t('analysis_failure_on_robot', { + protocolName: displayName, + robotName, + })} + + {errors?.map((error, index) => ( + + {error?.detail} + + ))} + + + + {t('shared:close')} + + + + , + getTopPortalEl() + ) +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ProtocolDropTipModal.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ProtocolDropTipModal.tsx new file mode 100644 index 00000000000..b9f30de446f --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/ProtocolDropTipModal.tsx @@ -0,0 +1,164 @@ +import { useState, useEffect } from 'react' +import { Trans, useTranslation } from 'react-i18next' +import { css } from 'styled-components' + +import { + COLORS, + DIRECTION_COLUMN, + Flex, + JUSTIFY_SPACE_BETWEEN, + SPACING, + StyledText, + PrimaryButton, + JUSTIFY_END, + ModalHeader, + ModalShell, +} from '@opentrons/components' + +import { TextOnlyButton } from '/app/atoms/buttons' +import { useHomePipettes } from '/app/local-resources/instruments' + +import type { PipetteData } from '@opentrons/api-client' +import type { IconProps } from '@opentrons/components' +import type { UseHomePipettesProps } from '/app/local-resources/instruments' +import type { TipAttachmentStatusResult } from '/app/resources/instruments' + +type UseProtocolDropTipModalProps = Pick< + UseHomePipettesProps, + 'pipetteInfo' +> & { + areTipsAttached: TipAttachmentStatusResult['areTipsAttached'] + enableDTWiz: () => void + currentRunId: string + onSkipAndHome: () => void + /* True if the most recent run is the current run */ + isRunCurrent: boolean +} + +export type UseProtocolDropTipModalResult = + | { + showModal: true + modalProps: ProtocolDropTipModalProps + } + | { showModal: false; modalProps: null } + +// Wraps functionality required for rendering the related modal. +export function useProtocolDropTipModal({ + areTipsAttached, + enableDTWiz, + isRunCurrent, + onSkipAndHome, + pipetteInfo, +}: UseProtocolDropTipModalProps): UseProtocolDropTipModalResult { + const [showModal, setShowModal] = useState(areTipsAttached) + + const { homePipettes, isHoming } = useHomePipettes({ + pipetteInfo, + onSettled: () => { + onSkipAndHome() + }, + }) + + // Close the modal if a different app closes the run context. + useEffect(() => { + if (isRunCurrent && !isHoming) { + setShowModal(areTipsAttached) + } else if (!isRunCurrent) { + setShowModal(false) + } + }, [isRunCurrent, areTipsAttached, showModal]) // Continue to show the modal if a client dismisses the maintenance run on a different app. + + const onSkip = (): void => { + void homePipettes() + } + + const onBeginRemoval = (): void => { + enableDTWiz() + setShowModal(false) + } + + return showModal + ? { + showModal: true, + modalProps: { + onSkip, + onBeginRemoval, + isDisabled: isHoming, + }, + } + : { showModal: false, modalProps: null } +} + +interface ProtocolDropTipModalProps { + onSkip: () => void + onBeginRemoval: () => void + isDisabled: boolean + mount?: PipetteData['mount'] +} + +export function ProtocolDropTipModal({ + onSkip, + onBeginRemoval, + mount, + isDisabled, +}: ProtocolDropTipModalProps): JSX.Element { + const { t } = useTranslation('drop_tip_wizard') + + const buildIcon = (): IconProps => { + return { + name: 'information', + color: COLORS.red50, + size: SPACING.spacing20, + marginRight: SPACING.spacing8, + } + } + + const buildHeader = (): JSX.Element => { + return ( + + ) + } + + return ( + + + + , + }} + /> + + + + + {t('begin_removal')} + + + + + ) +} + +const MODAL_STYLE = css` + width: 500px; +` diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/RunFailedModal.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/RunFailedModal.tsx new file mode 100644 index 00000000000..33d7949a449 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/RunFailedModal.tsx @@ -0,0 +1,200 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { css } from 'styled-components' + +import { + ALIGN_CENTER, + BORDERS, + COLORS, + DIRECTION_COLUMN, + DIRECTION_ROW, + Flex, + Icon, + JUSTIFY_SPACE_BETWEEN, + Link, + OVERFLOW_AUTO, + OVERFLOW_WRAP_ANYWHERE, + PrimaryButton, + SPACING, + Modal, + LegacyStyledText, + TYPOGRAPHY, + DISPLAY_FLEX, +} from '@opentrons/components' + +import { useDownloadRunLog } from '../../../../hooks' +import { RUN_STATUS_SUCCEEDED } from '@opentrons/api-client' + +import type { MouseEventHandler } from 'react' +import type { RunStatus } from '@opentrons/api-client' +import type { ModalProps } from '@opentrons/components' +import type { RunCommandError } from '@opentrons/shared-data' +import type { UseRunErrorsResult } from '../../hooks' + +// Note(kk:08/07/2023) +// This modal and run failed modal for Touchscreen app will be merged into one component like EstopModals. + +export interface UseRunFailedModalResult { + showRunFailedModal: boolean + toggleModal: () => void +} + +export function useRunFailedModal( + runErrors: UseRunErrorsResult +): UseRunFailedModalResult { + const [showRunFailedModal, setShowRunFailedModal] = useState(false) + + const toggleModal = (): void => { + setShowRunFailedModal(!showRunFailedModal) + } + + const showModal = + showRunFailedModal && + (runErrors.commandErrorList != null || + runErrors.highestPriorityError != null) + + return { showRunFailedModal: showModal, toggleModal } +} + +interface RunFailedModalProps { + robotName: string + runId: string + toggleModal: () => void + runStatus: RunStatus | null + runErrors: UseRunErrorsResult +} + +// TODO(jh, 09-09-24): Consider cleaning up component after the server-side commandErrorList changes are completed. +export function RunFailedModal({ + robotName, + runId, + toggleModal, + runStatus, + runErrors, +}: RunFailedModalProps): JSX.Element | null { + const { commandErrorList, highestPriorityError } = runErrors + + const { i18n, t } = useTranslation(['run_details', 'shared', 'branded']) + const modalProps: ModalProps = { + type: runStatus === RUN_STATUS_SUCCEEDED ? 'warning' : 'error', + title: + commandErrorList == null || commandErrorList?.length === 0 + ? t('run_failed_modal_title') + : runStatus === RUN_STATUS_SUCCEEDED + ? t('warning_details') + : t('error_details'), + onClose: () => { + toggleModal() + }, + closeOnOutsideClick: true, + childrenPadding: SPACING.spacing24, + width: '31.25rem', + } + const { downloadRunLog } = useDownloadRunLog(robotName, runId) + + const handleClick = (): void => { + toggleModal() + } + + const handleDownloadClick: MouseEventHandler = e => { + e.preventDefault() + e.stopPropagation() + downloadRunLog() + } + + interface ErrorContentProps { + errors: RunCommandError[] + isSingleError: boolean + } + const ErrorContent = ({ + errors, + isSingleError, + }: ErrorContentProps): JSX.Element => { + return ( + + + {isSingleError + ? t('error_info', { + errorType: errors[0].errorType, + errorCode: errors[0].errorCode, + }) + : runStatus === RUN_STATUS_SUCCEEDED + ? t(errors.length > 1 ? 'no_of_warnings' : 'no_of_warning', { + count: errors.length, + }) + : t(errors.length > 1 ? 'no_of_errors' : 'no_of_error', { + count: errors.length, + })} + + + {' '} + {errors.map((error, index) => ( + + {' '} + {isSingleError + ? error.detail + : `${error.errorCode}: ${error.detail}`} + + ))} + + + ) + } + + return ( + + + 0 + ? commandErrorList + : [] + } + isSingleError={!!highestPriorityError} + /> + + {t('branded:run_failed_modal_description_desktop')} + + + + + + {i18n.format(t('download_run_log'), 'titleCase')} + + + + {i18n.format(t('shared:close'), 'capitalize')} + + + + + ) +} + +const ERROR_MESSAGE_STYLE = css` + display: ${DISPLAY_FLEX}; + flex-direction: ${DIRECTION_COLUMN}; + max-height: 9.5rem; + overflow-y: ${OVERFLOW_AUTO}; + margin-top: ${SPACING.spacing8}; + margin-bottom: ${SPACING.spacing16}; + padding: ${`${SPACING.spacing8} ${SPACING.spacing12}`}; + background-color: ${COLORS.grey30}; + border-radius: ${BORDERS.borderRadius8}; + overflow-wrap: ${OVERFLOW_WRAP_ANYWHERE}; + + ::-webkit-scrollbar-thumb { + background: ${COLORS.grey40}; + } +` diff --git a/app/src/organisms/RunDetails/__fixtures__/analysis.json b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__fixtures__/analysis.json similarity index 100% rename from app/src/organisms/RunDetails/__fixtures__/analysis.json rename to app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__fixtures__/analysis.json diff --git a/app/src/organisms/RunDetails/__fixtures__/runRecord.json b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__fixtures__/runRecord.json similarity index 100% rename from app/src/organisms/RunDetails/__fixtures__/runRecord.json rename to app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__fixtures__/runRecord.json diff --git a/app/src/organisms/RunDetails/__tests__/ConfirmCancelModal.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/ConfirmCancelModal.test.tsx similarity index 79% rename from app/src/organisms/RunDetails/__tests__/ConfirmCancelModal.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/ConfirmCancelModal.test.tsx index 92fed1f5e4f..c6421040e17 100644 --- a/app/src/organisms/RunDetails/__tests__/ConfirmCancelModal.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/ConfirmCancelModal.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' @@ -10,15 +10,13 @@ import { } from '@opentrons/api-client' import { useStopRunMutation } from '@opentrons/react-api-client' -import { i18n } from '../../../i18n' -import { - useIsFlex, - useTrackProtocolRunEvent, -} from '../../../organisms/Devices/hooks' -import { useTrackEvent } from '../../../redux/analytics' -import { renderWithProviders } from '../../../__testing-utils__' -import { ConfirmCancelModal } from '../../../organisms/RunDetails/ConfirmCancelModal' -import { useRunStatus } from '../../RunTimeControl/hooks' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { useTrackProtocolRunEvent } from '/app/redux-resources/analytics' +import { useIsFlex } from '/app/redux-resources/robots' +import { useTrackEvent } from '/app/redux/analytics' +import { ConfirmCancelModal } from '../ConfirmCancelModal' + import type * as ApiClient from '@opentrons/react-api-client' vi.mock('@opentrons/react-api-client', async importOriginal => { @@ -28,10 +26,9 @@ vi.mock('@opentrons/react-api-client', async importOriginal => { useStopRunMutation: vi.fn(), } }) -vi.mock('../../RunTimeControl/hooks') -vi.mock('../../../organisms/Devices/hooks') -vi.mock('../../../redux/analytics') -vi.mock('../../../redux/config') +vi.mock('/app/redux/analytics') +vi.mock('/app/redux-resources/analytics') +vi.mock('/app/redux-resources/robots') const render = (props: React.ComponentProps) => { return renderWithProviders(, { @@ -54,14 +51,18 @@ describe('ConfirmCancelModal', () => { vi.mocked(useStopRunMutation).mockReturnValue({ stopRun: mockStopRun, } as any) - vi.mocked(useRunStatus).mockReturnValue(RUN_STATUS_RUNNING) vi.mocked(useTrackEvent).mockReturnValue(mockTrackEvent) when(useTrackProtocolRunEvent).calledWith(RUN_ID, ROBOT_NAME).thenReturn({ trackProtocolRunEvent: mockTrackProtocolRunEvent, }) vi.mocked(useIsFlex).mockReturnValue(true) - props = { onClose: vi.fn(), runId: RUN_ID, robotName: ROBOT_NAME } + props = { + onClose: vi.fn(), + runId: RUN_ID, + robotName: ROBOT_NAME, + runStatus: RUN_STATUS_RUNNING, + } }) afterEach(() => { @@ -101,13 +102,11 @@ describe('ConfirmCancelModal', () => { expect(mockTrackProtocolRunEvent).toHaveBeenCalled() }) it('should close modal if run status becomes stop-requested', () => { - vi.mocked(useRunStatus).mockReturnValue(RUN_STATUS_STOP_REQUESTED) - render(props) + render({ ...props, runStatus: RUN_STATUS_STOP_REQUESTED }) expect(props.onClose).toHaveBeenCalled() }) it('should close modal if run status becomes stopped', () => { - vi.mocked(useRunStatus).mockReturnValue(RUN_STATUS_STOPPED) - render(props) + render({ ...props, runStatus: RUN_STATUS_STOPPED }) expect(props.onClose).toHaveBeenCalled() }) it('should call No go back button', () => { diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolAnalysisErrorModal.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/ProtocolAnalysisErrorModal.test.tsx similarity index 89% rename from app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolAnalysisErrorModal.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/ProtocolAnalysisErrorModal.test.tsx index e7d2be4c976..44fcb0278ad 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolAnalysisErrorModal.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/ProtocolAnalysisErrorModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, expect, vi } from 'vitest' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ProtocolAnalysisErrorModal } from '../ProtocolAnalysisErrorModal' const render = ( diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolDropTipModal.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/ProtocolDropTipModal.test.tsx similarity index 75% rename from app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolDropTipModal.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/ProtocolDropTipModal.test.tsx index ca4608b510d..0d95071a969 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolDropTipModal.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/ProtocolDropTipModal.test.tsx @@ -1,21 +1,18 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, expect, beforeEach } from 'vitest' import { renderHook, act, screen, fireEvent } from '@testing-library/react' -import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' - +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useHomePipettes } from '/app/local-resources/instruments' import { useProtocolDropTipModal, ProtocolDropTipModal, } from '../ProtocolDropTipModal' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { mockLeftSpecs } from '../../../../redux/pipettes/__fixtures__' -import { useHomePipettes } from '../../../DropTipWizardFlows/hooks' import type { Mock } from 'vitest' -vi.mock('../../../DropTipWizardFlows/hooks') +vi.mock('/app/local-resources/instruments') describe('useProtocolDropTipModal', () => { let props: Parameters[0] @@ -24,19 +21,21 @@ describe('useProtocolDropTipModal', () => { beforeEach(() => { props = { areTipsAttached: true, - toggleDTWiz: vi.fn(), + enableDTWiz: vi.fn(), isRunCurrent: true, onSkipAndHome: vi.fn(), currentRunId: 'MOCK_ID', - mount: 'left', - instrumentModelSpecs: mockLeftSpecs, - robotType: FLEX_ROBOT_TYPE, + pipetteInfo: { + pipetteId: '123', + pipetteName: 'MOCK_NAME', + mount: 'left', + }, } mockHomePipettes = vi.fn() vi.mocked(useHomePipettes).mockReturnValue({ homePipettes: mockHomePipettes, - isHomingPipettes: false, + isHoming: false, }) }) @@ -44,10 +43,12 @@ describe('useProtocolDropTipModal', () => { const { result } = renderHook(() => useProtocolDropTipModal(props)) expect(result.current).toEqual({ - showDTModal: true, - onDTModalSkip: expect.any(Function), - onDTModalRemoval: expect.any(Function), - isDisabled: false, + showModal: true, + modalProps: { + onSkip: expect.any(Function), + onBeginRemoval: expect.any(Function), + isDisabled: false, + }, }) }) @@ -56,26 +57,26 @@ describe('useProtocolDropTipModal', () => { useProtocolDropTipModal(props) ) - expect(result.current.showDTModal).toBe(true) + expect(result.current.showModal).toBe(true) props.areTipsAttached = false rerender() - expect(result.current.showDTModal).toBe(false) + expect(result.current.showModal).toBe(false) }) it('should not show modal when isRunCurrent is false', () => { props.isRunCurrent = false const { result } = renderHook(() => useProtocolDropTipModal(props)) - expect(result.current.showDTModal).toBe(false) + expect(result.current.showModal).toBe(false) }) it('should call homePipettes when onDTModalSkip is called', () => { const { result } = renderHook(() => useProtocolDropTipModal(props)) act(() => { - result.current.onDTModalSkip() + result.current.modalProps?.onSkip() }) expect(mockHomePipettes).toHaveBeenCalled() @@ -85,21 +86,21 @@ describe('useProtocolDropTipModal', () => { const { result } = renderHook(() => useProtocolDropTipModal(props)) act(() => { - result.current.onDTModalRemoval() + result.current.modalProps?.onBeginRemoval() }) - expect(props.toggleDTWiz).toHaveBeenCalled() + expect(props.enableDTWiz).toHaveBeenCalled() }) it('should set isDisabled to true when isHomingPipettes is true', () => { vi.mocked(useHomePipettes).mockReturnValue({ homePipettes: mockHomePipettes, - isHomingPipettes: true, + isHoming: true, }) const { result } = renderHook(() => useProtocolDropTipModal(props)) - expect(result.current.isDisabled).toBe(true) + expect(result.current.modalProps?.isDisabled).toBe(true) }) }) diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/RunFailedModal.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/RunFailedModal.test.tsx new file mode 100644 index 00000000000..d49875a0859 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__tests__/RunFailedModal.test.tsx @@ -0,0 +1,84 @@ +import type * as React from 'react' +import { describe, it, beforeEach, vi, expect, afterEach } from 'vitest' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useDownloadRunLog } from '../../../../../hooks' +import { RunFailedModal } from '../RunFailedModal' + +import { RUN_STATUS_FAILED } from '@opentrons/api-client' +import type { RunError } from '@opentrons/api-client' +import { fireEvent, screen } from '@testing-library/react' + +vi.mock('../../../../../hooks') + +const RUN_ID = '1' +const ROBOT_NAME = 'mockRobotName' +const mockError: RunError = { + id: '5097b3e6-3900-482d-abb1-0a8d8a0e515d', + errorType: 'ModuleNotAttachedError', + isDefined: false, + createdAt: '2023-08-07T20:16:57.720783+00:00', + detail: 'No available thermocyclerModuleV2 found.', + errorCode: '4000', + errorInfo: {}, + wrappedErrors: [], +} + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} + +describe('RunFailedModal - DesktopApp', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + robotName: ROBOT_NAME, + runId: RUN_ID, + toggleModal: vi.fn(), + runStatus: RUN_STATUS_FAILED, + runErrors: { highestPriorityError: mockError, commandErrorList: null }, + } + vi.mocked(useDownloadRunLog).mockReturnValue({ + downloadRunLog: vi.fn(), + isRunLogLoading: false, + }) + }) + + afterEach(() => { + vi.clearAllMocks() + }) + + it('should render text, link and button', () => { + render(props) + screen.getByText('Run failed') + screen.getByText('Error 4000: ModuleNotAttachedError') + screen.getByText('No available thermocyclerModuleV2 found.') + screen.getByText( + 'Download the run log and send it to support@opentrons.com for assistance.' + ) + screen.getByText('Download Run Log') + screen.getByRole('button', { name: 'Close' }) + }) + + it('should call a mock function when clicking close button', () => { + render(props) + fireEvent.click(screen.getByRole('button', { name: 'Close' })) + expect(props.toggleModal).toHaveBeenCalled() + }) + + it('should close the modal when clicking close icon', () => { + render(props) + fireEvent.click(screen.getByRole('button', { name: '' })) + expect(props.toggleModal).toHaveBeenCalled() + }) + + it('should call a mock function when clicking download run log button', () => { + render(props) + fireEvent.click(screen.getByText('Download Run Log')) + expect(vi.mocked(useDownloadRunLog)).toHaveBeenCalled() + }) +}) diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/index.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/index.ts new file mode 100644 index 00000000000..7de31bc0934 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/index.ts @@ -0,0 +1,6 @@ +export * from './ConfirmCancelModal' +export * from './ProtocolDropTipModal' +export * from './ProtocolAnalysisErrorModal' +export * from './RunFailedModal' +export * from './HeaterShakerIsRunningModal' +export * from './ConfirmMissingStepsModal' diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/useRunHeaderModalContainer.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/useRunHeaderModalContainer.ts new file mode 100644 index 00000000000..48eda0ebfa5 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/useRunHeaderModalContainer.ts @@ -0,0 +1,127 @@ +import { useNavigate } from 'react-router-dom' + +import { + useConfirmCancelModal, + useHeaterShakerIsRunningModal, + useProtocolAnalysisErrorsModal, + useRunFailedModal, +} from './modals' +import { + useHeaterShakerConfirmationModal, + useMissingStepsModal, + useRunHeaderDropTip, +} from './hooks' +import { useErrorRecoveryFlows } from '/app/organisms/ErrorRecoveryFlows' +import { useProtocolDetailsForRun } from '/app/resources/runs' +import { getFallbackRobotSerialNumber } from '../utils' +import { + ANALYTICS_PROTOCOL_PROCEED_TO_RUN, + useTrackEvent, +} from '/app/redux/analytics' +import { useRobot, useRobotType } from '/app/redux-resources/robots' +import type { AttachedModule, RunStatus, Run } from '@opentrons/api-client' +import type { UseErrorRecoveryResult } from '/app/organisms/ErrorRecoveryFlows' +import type { + UseRunHeaderDropTipResult, + UseMissingStepsModalResult, + UseHeaterShakerConfirmationModalResult, +} from './hooks' +import type { + UseAnalysisErrorsModalResult, + UseConfirmCancelModalResult, + UseHeaterShakerIsRunningModalResult, + UseRunFailedModalResult, +} from './modals' +import type { ProtocolRunHeaderProps } from '..' +import type { RunControls } from '/app/organisms/RunTimeControl' +import type { UseRunErrorsResult } from '../hooks' + +interface UseRunHeaderModalContainerProps extends ProtocolRunHeaderProps { + attachedModules: AttachedModule[] + protocolRunControls: RunControls + runStatus: RunStatus | null + runRecord: Run | null + runErrors: UseRunErrorsResult +} + +export interface UseRunHeaderModalContainerResult { + confirmCancelModalUtils: UseConfirmCancelModalResult + runFailedModalUtils: UseRunFailedModalResult + analysisErrorModalUtils: UseAnalysisErrorsModalResult + HSRunningModalUtils: UseHeaterShakerIsRunningModalResult + HSConfirmationModalUtils: UseHeaterShakerConfirmationModalResult + missingStepsModalUtils: UseMissingStepsModalResult + dropTipUtils: UseRunHeaderDropTipResult + recoveryModalUtils: UseErrorRecoveryResult +} + +// Provides all the utilities used by the various modals that render in ProtocolRunHeader. +export function useRunHeaderModalContainer({ + runId, + robotName, + runStatus, + runRecord, + attachedModules, + protocolRunControls, + runErrors, +}: UseRunHeaderModalContainerProps): UseRunHeaderModalContainerResult { + const navigate = useNavigate() + + const { displayName } = useProtocolDetailsForRun(runId) + const robot = useRobot(robotName) + const robotSerialNumber = getFallbackRobotSerialNumber(robot) + const trackEvent = useTrackEvent() + const robotType = useRobotType(robotName) + + function handleProceedToRunClick(): void { + navigate(`/devices/${robotName}/protocol-runs/${runId}/run-preview`) + trackEvent({ + name: ANALYTICS_PROTOCOL_PROCEED_TO_RUN, + properties: { robotSerialNumber }, + }) + protocolRunControls.play() + } + + const confirmCancelModalUtils = useConfirmCancelModal() + + const runFailedModalUtils = useRunFailedModal(runErrors) + + const analysisErrorModalUtils = useProtocolAnalysisErrorsModal({ + robotName, + runId, + displayName, + }) + + const HSRunningModalUtils = useHeaterShakerIsRunningModal(attachedModules) + + const HSConfirmationModalUtils = useHeaterShakerConfirmationModal( + handleProceedToRunClick + ) + + const missingStepsModalUtils = useMissingStepsModal({ + attachedModules, + runStatus, + runId, + handleProceedToRunClick, + }) + + const dropTipUtils = useRunHeaderDropTip({ + runId, + runStatus, + runRecord, + robotType, + }) + + const recoveryModalUtils = useErrorRecoveryFlows(runId, runStatus) + + return { + confirmCancelModalUtils, + analysisErrorModalUtils, + HSConfirmationModalUtils, + HSRunningModalUtils, + runFailedModalUtils, + recoveryModalUtils, + missingStepsModalUtils, + dropTipUtils, + } +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderProtocolName.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderProtocolName.tsx new file mode 100644 index 00000000000..f08a157bba9 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderProtocolName.tsx @@ -0,0 +1,41 @@ +import { Link } from 'react-router-dom' + +import { + COLORS, + Flex, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' + +import { useProtocolDetailsForRun } from '/app/resources/runs' + +interface RunHeaderProtocolNameProps { + runId: string +} + +// Styles the protocol name copy. +export function RunHeaderProtocolName({ + runId, +}: RunHeaderProtocolNameProps): JSX.Element { + const { protocolKey, displayName } = useProtocolDetailsForRun(runId) + + return ( + + {protocolKey != null ? ( + + + {displayName} + + + ) : ( + + {displayName} + + )} + + ) +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/__tests__/ProtocolRunHeader.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/__tests__/ProtocolRunHeader.test.tsx new file mode 100644 index 00000000000..e82d58cb75e --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/__tests__/ProtocolRunHeader.test.tsx @@ -0,0 +1,185 @@ +import type * as React from 'react' +import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest' +import { screen } from '@testing-library/react' +import { useNavigate } from 'react-router-dom' + +import { RUN_STATUS_RUNNING } from '@opentrons/api-client' +import { useModulesQuery } from '@opentrons/react-api-client' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { ProtocolRunHeader } from '..' +import { useIsRobotViewable } from '/app/redux-resources/robots' +import { + useRunStatus, + useProtocolDetailsForRun, + useNotifyRunQuery, +} from '/app/resources/runs' +import { RunHeaderModalContainer } from '../RunHeaderModalContainer' +import { RunHeaderBannerContainer } from '../RunHeaderBannerContainer' +import { RunHeaderContent } from '../RunHeaderContent' +import { RunProgressMeter } from '../../../../RunProgressMeter' +import { RunHeaderProtocolName } from '../RunHeaderProtocolName' +import { + useRunAnalytics, + useRunErrors, + useRunHeaderRunControls, +} from '../hooks' + +vi.mock('react-router-dom') +vi.mock('@opentrons/react-api-client') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/resources/runs') +vi.mock('/app/redux/protocol-runs') +vi.mock('../RunHeaderModalContainer') +vi.mock('../RunHeaderBannerContainer') +vi.mock('../RunHeaderContent') +vi.mock('../../../../RunProgressMeter') +vi.mock('../RunHeaderProtocolName') +vi.mock('../hooks') + +const MOCK_PROTOCOL = 'MOCK_PROTOCOL' +const MOCK_RUN_ID = 'MOCK_RUN_ID' +const MOCK_ROBOT = 'MOCK_ROBOT' + +describe('ProtocolRunHeader', () => { + let props: React.ComponentProps + const mockNavigate = vi.fn() + + beforeEach(() => { + props = { + protocolRunHeaderRef: null, + robotName: MOCK_ROBOT, + runId: MOCK_RUN_ID, + makeHandleJumpToStep: vi.fn(), + } + + vi.mocked(useNavigate).mockReturnValue(mockNavigate) + vi.mocked(useRunStatus).mockReturnValue(RUN_STATUS_RUNNING) + vi.mocked(useIsRobotViewable).mockReturnValue(true) + vi.mocked(useProtocolDetailsForRun).mockReturnValue({ + protocolData: {} as any, + displayName: MOCK_PROTOCOL, + } as any) + vi.mocked(useNotifyRunQuery).mockReturnValue({ + data: { data: { hasEverEnteredErrorRecovery: false } }, + } as any) + vi.mocked(useModulesQuery).mockReturnValue({ + data: { data: [] }, + } as any) + vi.mocked(useRunAnalytics).mockImplementation(() => {}) + vi.mocked(useRunErrors).mockReturnValue([] as any) + vi.mocked(useRunHeaderRunControls).mockReturnValue({} as any) + + vi.mocked(RunHeaderModalContainer).mockReturnValue( +
    MOCK_RUN_HEADER_MODAL_CONTAINER
    + ) + vi.mocked(RunHeaderBannerContainer).mockReturnValue( +
    MOCK_RUN_HEADER_BANNER_CONTAINER
    + ) + vi.mocked(RunHeaderContent).mockReturnValue( +
    MOCK_RUN_HEADER_CONTENT
    + ) + vi.mocked(RunProgressMeter).mockReturnValue( +
    MOCK_RUN_PROGRESS_METER
    + ) + vi.mocked(RunHeaderProtocolName).mockReturnValue( +
    MOCK_RUN_HEADER_PROTOCOL_NAME
    + ) + }) + + afterEach(() => { + vi.resetAllMocks() + }) + + const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] + } + + it('renders all components', () => { + render(props) + + screen.getByText('MOCK_RUN_HEADER_MODAL_CONTAINER') + screen.getByText('MOCK_RUN_HEADER_PROTOCOL_NAME') + screen.getByText('MOCK_RUN_HEADER_BANNER_CONTAINER') + screen.getByText('MOCK_RUN_HEADER_CONTENT') + screen.getByText('MOCK_RUN_PROGRESS_METER') + }) + + it('navigates to /devices if robot is not viewable and protocolData is not null', () => { + vi.mocked(useIsRobotViewable).mockReturnValue(false) + vi.mocked(useProtocolDetailsForRun).mockReturnValue({ + protocolData: {} as any, + displayName: MOCK_PROTOCOL, + } as any) + + render(props) + + expect(mockNavigate).toHaveBeenCalledWith('/devices') + }) + + it('does not navigate if protocolData is null', () => { + vi.mocked(useIsRobotViewable).mockReturnValue(false) + vi.mocked(useProtocolDetailsForRun).mockReturnValue({ + protocolData: null, + displayName: MOCK_PROTOCOL, + } as any) + + render(props) + + expect(mockNavigate).not.toHaveBeenCalledWith('/devices') + }) + + it('calls useRunAnalytics with correct parameters', () => { + render(props) + + expect(useRunAnalytics).toHaveBeenCalledWith({ + runId: MOCK_RUN_ID, + robotName: MOCK_ROBOT, + enteredER: false, + }) + }) + + it('passes correct props to RunHeaderModalContainer', () => { + render(props) + + expect(RunHeaderModalContainer).toHaveBeenCalledWith( + expect.objectContaining({ + runStatus: RUN_STATUS_RUNNING, + runErrors: [], + protocolRunControls: expect.any(Object), + }), + expect.anything() + ) + }) + + it('passes correct props to RunHeaderBannerContainer', () => { + render(props) + + expect(RunHeaderBannerContainer).toHaveBeenCalledWith( + expect.objectContaining({ + runStatus: RUN_STATUS_RUNNING, + enteredER: false, + isResetRunLoading: false, + runErrors: [], + }), + expect.anything() + ) + }) + + it('passes correct props to RunHeaderContent', () => { + render(props) + + expect(RunHeaderContent).toHaveBeenCalledWith( + expect.objectContaining({ + runStatus: RUN_STATUS_RUNNING, + isResetRunLoadingRef: expect.any(Object), + attachedModules: [], + protocolRunControls: expect.any(Object), + }), + expect.anything() + ) + }) +}) diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/constants.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/constants.ts new file mode 100644 index 00000000000..604999b6355 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/constants.ts @@ -0,0 +1 @@ +export const EQUIPMENT_POLL_MS = 5000 diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/index.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/index.ts new file mode 100644 index 00000000000..fa59698a7af --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/index.ts @@ -0,0 +1,4 @@ +export * from './useIsDoorOpen' +export * from './useRunAnalytics' +export * from './useRunHeaderRunControls' +export * from './useRunErrors' diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useIsDoorOpen.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useIsDoorOpen.ts new file mode 100644 index 00000000000..ea852d63c72 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useIsDoorOpen.ts @@ -0,0 +1,36 @@ +import { useSelector } from 'react-redux' + +import { useDoorQuery } from '@opentrons/react-api-client' + +import { getRobotSettings } from '/app/redux/robot-settings' +import { useIsFlex } from '/app/redux-resources/robots' +import { EQUIPMENT_POLL_MS } from '../constants' + +import type { State } from '/app/redux/types' + +export function useIsDoorOpen(robotName: string): boolean { + const robotSettings = useSelector((state: State) => + getRobotSettings(state, robotName) + ) + const isFlex = useIsFlex(robotName) + + const doorSafetySetting = robotSettings.find( + setting => setting.id === 'enableDoorSafetySwitch' + ) + + const { data: doorStatus } = useDoorQuery({ + refetchInterval: EQUIPMENT_POLL_MS, + }) + + let isDoorOpen: boolean + const isStatusOpen = doorStatus?.data.status === 'open' + const isDoorSafetyEnabled = Boolean(doorSafetySetting?.value) + + if (isFlex || (!isFlex && isDoorSafetyEnabled)) { + isDoorOpen = isStatusOpen + } else { + isDoorOpen = false + } + + return isDoorOpen +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunAnalytics.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunAnalytics.ts new file mode 100644 index 00000000000..31399cbc541 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunAnalytics.ts @@ -0,0 +1,50 @@ +import { useEffect } from 'react' +import { + useRobotAnalyticsData, + useTrackProtocolRunEvent, + useRecoveryAnalytics, +} from '/app/redux-resources/analytics' +import { useIsRunCurrent, useRunStatus } from '/app/resources/runs' +import { ANALYTICS_PROTOCOL_RUN_ACTION } from '/app/redux/analytics' + +import { isTerminalRunStatus } from '../utils' + +interface UseRunAnalyticsProps { + runId: string | null + robotName: string + enteredER: boolean +} + +// Implicitly send reports related to the run when the current run is terminal. +export function useRunAnalytics({ + runId, + robotName, + enteredER, +}: UseRunAnalyticsProps): void { + const { trackProtocolRunEvent } = useTrackProtocolRunEvent(runId, robotName) + const robotAnalyticsData = useRobotAnalyticsData(robotName) + const runStatus = useRunStatus(runId) + const isRunCurrent = useIsRunCurrent(runId) + + useEffect(() => { + const areReportConditionsValid = + isRunCurrent && + runId != null && + robotAnalyticsData != null && + isTerminalRunStatus(runStatus) + + if (areReportConditionsValid) { + trackProtocolRunEvent({ + name: ANALYTICS_PROTOCOL_RUN_ACTION.FINISH, + properties: robotAnalyticsData, + }) + } + }, [runStatus, isRunCurrent, runId, robotAnalyticsData]) + + const { reportRecoveredRunResult } = useRecoveryAnalytics() + useEffect(() => { + if (isRunCurrent) { + reportRecoveredRunResult(runStatus, enteredER) + } + }, [isRunCurrent, enteredER]) +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunErrors.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunErrors.ts new file mode 100644 index 00000000000..4d66b367a0e --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunErrors.ts @@ -0,0 +1,50 @@ +import { useRunCommandErrors } from '@opentrons/react-api-client' + +import { isTerminalRunStatus } from '../utils' +import { useMostRecentRunId } from '/app/resources/runs' +import { getHighestPriorityError } from '/app/transformations/runs' + +import type { RunStatus, Run } from '@opentrons/api-client' +import type { RunCommandError } from '@opentrons/shared-data' + +// A reasonably high number of commands that a user would realistically care to examine. +const ALL_COMMANDS_PAGE_LENGTH = 100 + +interface UseRunErrorsProps { + runId: string + runStatus: RunStatus | null + runRecord: Run | null +} + +export interface UseRunErrorsResult { + commandErrorList: RunCommandError[] | null + highestPriorityError: RunCommandError | null +} + +// During a run, a single error or multiple errors may occur, and currently, these are managed under separate endpoints. +export function useRunErrors({ + runId, + runRecord, + runStatus, +}: UseRunErrorsProps): UseRunErrorsResult { + const mostRecentRunId = useMostRecentRunId() + const isMostRecentRun = mostRecentRunId === runId + + const { data: commandErrorList } = useRunCommandErrors( + runId, + { cursor: 0, pageLength: ALL_COMMANDS_PAGE_LENGTH }, + { + enabled: isTerminalRunStatus(runStatus) && isMostRecentRun, + } + ) + + const highestPriorityError = + runRecord?.data.errors?.[0] != null + ? getHighestPriorityError(runRecord.data.errors as RunCommandError[]) + : null + + return { + commandErrorList: commandErrorList?.data ?? null, + highestPriorityError, + } +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunHeaderRunControls.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunHeaderRunControls.ts new file mode 100644 index 00000000000..1b5c76c644f --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunHeaderRunControls.ts @@ -0,0 +1,20 @@ +import { useNavigate } from 'react-router-dom' +import { useRunControls } from '/app/organisms/RunTimeControl' + +import type { Run } from '@opentrons/api-client' +import type { RunControls } from '/app/organisms/RunTimeControl' + +// Provides desktop run controls, routing the user to the run preview tab after a "run again" action. +export function useRunHeaderRunControls( + runId: string, + robotName: string +): RunControls { + const navigate = useNavigate() + + function handleRunReset(createRunResponse: Run): void { + navigate( + `/devices/${robotName}/protocol-runs/${createRunResponse.data.id}/run-preview` + ) + } + return useRunControls(runId, handleRunReset) +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/index.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/index.tsx new file mode 100644 index 00000000000..fa13d31487b --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/index.tsx @@ -0,0 +1,135 @@ +import { useEffect, useRef } from 'react' +import { useNavigate } from 'react-router-dom' +import { css } from 'styled-components' + +import { + BORDERS, + COLORS, + DIRECTION_COLUMN, + Flex, + SPACING, +} from '@opentrons/components' +import { useModulesQuery } from '@opentrons/react-api-client' +import { RUN_STATUS_IDLE, RUN_STATUS_RUNNING } from '@opentrons/api-client' + +import { useIsRobotViewable } from '/app/redux-resources/robots' +import { RunProgressMeter } from '../../../RunProgressMeter' +import { + useNotifyRunQuery, + useProtocolDetailsForRun, + useRunStatus, +} from '/app/resources/runs' +import { RunHeaderProtocolName } from './RunHeaderProtocolName' +import { + RunHeaderModalContainer, + useRunHeaderModalContainer, +} from './RunHeaderModalContainer' +import { RunHeaderBannerContainer } from './RunHeaderBannerContainer' +import { useRunAnalytics, useRunErrors, useRunHeaderRunControls } from './hooks' +import { RunHeaderContent } from './RunHeaderContent' +import { EQUIPMENT_POLL_MS } from './constants' +import { isCancellableStatus } from './utils' + +import type { RefObject } from 'react' + +export interface ProtocolRunHeaderProps { + protocolRunHeaderRef: RefObject | null + robotName: string + runId: string + makeHandleJumpToStep: (index: number) => () => void +} + +export function ProtocolRunHeader( + props: ProtocolRunHeaderProps +): JSX.Element | null { + const { protocolRunHeaderRef, robotName, runId } = props + + const navigate = useNavigate() + + const { data: runRecord } = useNotifyRunQuery(runId, { staleTime: Infinity }) + const { protocolData } = useProtocolDetailsForRun(runId) + const isRobotViewable = useIsRobotViewable(robotName) + const runStatus = useRunStatus(runId) + const attachedModules = + useModulesQuery({ + refetchInterval: EQUIPMENT_POLL_MS, + enabled: isCancellableStatus(runStatus), + })?.data?.data ?? [] + const runErrors = useRunErrors({ + runRecord: runRecord ?? null, + runStatus, + runId, + }) + + const enteredER = runRecord?.data.hasEverEnteredErrorRecovery ?? false + const protocolRunControls = useRunHeaderRunControls(runId, robotName) + const runHeaderModalContainerUtils = useRunHeaderModalContainer({ + ...props, + attachedModules, + runStatus, + protocolRunControls, + runRecord: runRecord ?? null, + runErrors, + }) + + useEffect(() => { + if (protocolData != null && !isRobotViewable) { + navigate('/devices') + } + }, [protocolData, isRobotViewable, navigate]) + + // To persist "run again" loading conditions into a new run, we need a scalar that persists longer than + // the runControl isResetRunLoading, which completes before we want to change user-facing copy/CTAs. + const isResetRunLoadingRef = useRef(false) + if (runStatus === RUN_STATUS_IDLE || runStatus === RUN_STATUS_RUNNING) { + isResetRunLoadingRef.current = false + } + + useRunAnalytics({ runId, robotName, enteredER }) + + return ( + <> + + + + 0 + } + {...props} + /> + + + + + ) +} + +const CONTAINER_STYLE = css` + background-color: ${COLORS.white}; + border-radius: ${BORDERS.borderRadius8}; + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing16}; + margin-bottom: ${SPACING.spacing16}; + padding: ${SPACING.spacing16}; +` diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/utils.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/utils.ts new file mode 100644 index 00000000000..1f4ce9ec665 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/utils.ts @@ -0,0 +1,87 @@ +import { + RUN_STATUS_IDLE, + RUN_STATUS_PAUSED, + RUN_STATUS_BLOCKED_BY_OPEN_DOOR, + RUN_STATUS_FAILED, + RUN_STATUS_STOPPED, + RUN_STATUS_FINISHING, + RUN_STATUS_SUCCEEDED, + RUN_STATUS_RUNNING, + RUN_STATUS_AWAITING_RECOVERY, + RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, + RUN_STATUS_AWAITING_RECOVERY_PAUSED, + RUN_STATUS_STOP_REQUESTED, +} from '@opentrons/api-client' + +import { getRobotSerialNumber } from '/app/redux/discovery' + +import type { RunStatus } from '@opentrons/api-client' +import type { DiscoveredRobot } from '/app/redux/discovery/types' + +const START_RUN_STATUSES: RunStatus[] = [ + RUN_STATUS_IDLE, + RUN_STATUS_PAUSED, + RUN_STATUS_BLOCKED_BY_OPEN_DOOR, +] +const RUN_AGAIN_STATUSES: RunStatus[] = [ + RUN_STATUS_STOPPED, + RUN_STATUS_FINISHING, + RUN_STATUS_FAILED, + RUN_STATUS_SUCCEEDED, +] +const RECOVERY_STATUSES: RunStatus[] = [ + RUN_STATUS_AWAITING_RECOVERY, + RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, + RUN_STATUS_AWAITING_RECOVERY_PAUSED, +] +const DISABLED_STATUSES: RunStatus[] = [ + RUN_STATUS_FINISHING, + RUN_STATUS_STOP_REQUESTED, + RUN_STATUS_BLOCKED_BY_OPEN_DOOR, + ...RECOVERY_STATUSES, +] +const CANCELLABLE_STATUSES: RunStatus[] = [ + RUN_STATUS_RUNNING, + RUN_STATUS_PAUSED, + RUN_STATUS_BLOCKED_BY_OPEN_DOOR, + RUN_STATUS_IDLE, + RUN_STATUS_AWAITING_RECOVERY, + RUN_STATUS_AWAITING_RECOVERY_PAUSED, + RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, +] +const TERMINAL_STATUSES: RunStatus[] = [ + RUN_STATUS_STOPPED, + RUN_STATUS_SUCCEEDED, + RUN_STATUS_FAILED, +] + +export function isTerminalRunStatus(runStatus: RunStatus | null): boolean { + return runStatus !== null && TERMINAL_STATUSES.includes(runStatus) +} + +export function isStartRunStatus(runStatus: RunStatus | null): boolean { + return runStatus !== null && START_RUN_STATUSES.includes(runStatus) +} + +export function isRunAgainStatus(runStatus: RunStatus | null): boolean { + return runStatus !== null && RUN_AGAIN_STATUSES.includes(runStatus) +} + +export function isRecoveryStatus(runStatus: RunStatus | null): boolean { + return runStatus !== null && RECOVERY_STATUSES.includes(runStatus) +} + +export function isDisabledStatus(runStatus: RunStatus | null): boolean { + return runStatus !== null && DISABLED_STATUSES.includes(runStatus) +} + +export function isCancellableStatus(runStatus: RunStatus | null): boolean { + return runStatus !== null && CANCELLABLE_STATUSES.includes(runStatus) +} + +export function getFallbackRobotSerialNumber( + robot: DiscoveredRobot | null +): string { + const sn = robot?.status != null ? getRobotSerialNumber(robot) : null + return sn ?? '' +} diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunModuleControls.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunModuleControls.tsx similarity index 93% rename from app/src/organisms/Devices/ProtocolRun/ProtocolRunModuleControls.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunModuleControls.tsx index 4930efee2d3..c92251cfea1 100644 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunModuleControls.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunModuleControls.tsx @@ -1,5 +1,5 @@ -import * as React from 'react' import { useInstrumentsQuery } from '@opentrons/react-api-client' +import { useTranslation } from 'react-i18next' import { COLORS, DIRECTION_COLUMN, @@ -8,9 +8,9 @@ import { InfoScreen, SPACING, } from '@opentrons/components' -import { ModuleCard } from '../../ModuleCard' -import { useModuleRenderInfoForProtocolById } from '../hooks' -import { useModuleApiRequests } from '../../ModuleCard/utils' +import { ModuleCard } from '/app/organisms/ModuleCard' +import { useModuleRenderInfoForProtocolById } from '/app/resources/runs' +import { useModuleApiRequests } from '/app/organisms/ModuleCard/utils' import type { BadPipette, PipetteData } from '@opentrons/api-client' @@ -74,6 +74,7 @@ export const ProtocolRunModuleControls = ({ robotName, runId, }: ProtocolRunModuleControlsProps): JSX.Element => { + const { t } = useTranslation('protocol_setup') const { attachPipetteRequired, calibratePipetteRequired, @@ -102,7 +103,7 @@ export const ProtocolRunModuleControls = ({ padding={SPACING.spacing16} backgroundColor={COLORS.white} > - + ) : ( diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx similarity index 86% rename from app/src/organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx index 51836841422..a284b044283 100644 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunRunTimeParameters.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' @@ -13,11 +12,13 @@ import { } from '@opentrons/shared-data' import { ALIGN_CENTER, + Banner, BORDERS, Chip, COLORS, DIRECTION_COLUMN, DIRECTION_ROW, + DISPLAY_GRID, DISPLAY_INLINE, Flex, Icon, @@ -30,11 +31,12 @@ import { useHoverTooltip, } from '@opentrons/components' -import { Banner } from '../../../atoms/Banner' -import { Divider } from '../../../atoms/structure' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useRunStatus } from '../../RunTimeControl/hooks' -import { useNotifyRunQuery } from '../../../resources/runs' +import { Divider } from '/app/atoms/structure' +import { + useMostRecentCompletedAnalysis, + useNotifyRunQuery, + useRunStatus, +} from '/app/resources/runs' import type { RunTimeParameter } from '@opentrons/shared-data' import type { RunStatus } from '@opentrons/api-client' @@ -108,32 +110,17 @@ export function ProtocolRunRuntimeParameters({ ) : null} {hasRunTimeParameters ? ( - - - - {t('values_are_view_only')} - - - {t('cancel_and_restart_to_edit')} - - - + ) : null} {!hasRunTimeParameters ? ( ) : ( @@ -171,6 +158,31 @@ export function ProtocolRunRuntimeParameters({ ) } +interface RunTimeParametersBannerProps { + isRunTerminal: boolean +} + +function RunTimeParametersBanner({ + isRunTerminal, +}: RunTimeParametersBannerProps): JSX.Element { + const { t } = useTranslation('protocol_setup') + + return ( + + + + {isRunTerminal ? t('download_files') : t('values_are_view_only')} + + + {isRunTerminal + ? t('all_files_associated') + : t('cancel_and_restart_to_edit')} + + + + ) +} + interface StyledTableRowComponentProps { parameter: RunTimeParameter index: number @@ -249,7 +261,7 @@ const StyledTable = styled.table` text-align: left; ` const StyledTableHeaderContainer = styled.thead` - display: grid; + display: ${DISPLAY_GRID}; grid-template-columns: 0.35fr 0.35fr; grid-gap: ${SPACING.spacing48}; border-bottom: ${BORDERS.lineBorder}; @@ -265,7 +277,7 @@ interface StyledTableRowProps { } const StyledTableRow = styled.tr` - display: grid; + display: ${DISPLAY_GRID}; grid-template-columns: 0.35fr 0.35fr; grid-gap: ${SPACING.spacing48}; border-bottom: ${props => (props.isLast ? 'none' : BORDERS.lineBorder)}; diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunSetup.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunSetup.tsx new file mode 100644 index 00000000000..c1382acb777 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunSetup.tsx @@ -0,0 +1,508 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useDispatch, useSelector } from 'react-redux' + +import { + ALIGN_CENTER, + COLORS, + DIRECTION_COLUMN, + DIRECTION_ROW, + FLEX_MAX_CONTENT, + Flex, + Icon, + LegacyStyledText, + Link, + NO_WRAP, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' +import { + FLEX_ROBOT_TYPE, + OT2_ROBOT_TYPE, + parseAllRequiredModuleModels, +} from '@opentrons/shared-data' + +import { Line } from '/app/atoms/structure' +import { InfoMessage } from '/app/molecules/InfoMessage' +import { INCOMPATIBLE, INEXACT_MATCH } from '/app/redux/pipettes' +import { + getIsFixtureMismatch, + getRequiredDeckConfig, +} from '/app/resources/deck_configuration/utils' +import { useDeckConfigurationCompatibility } from '/app/resources/deck_configuration/hooks' +import { useRobot, useIsFlex } from '/app/redux-resources/robots' +import { useRequiredSetupStepsInOrder } from '/app/redux-resources/runs' +import { useStoredProtocolAnalysis } from '/app/resources/analysis' +import { + useMostRecentCompletedAnalysis, + useRunPipetteInfoByMount, + useRunCalibrationStatus, + useRunHasStarted, + useUnmatchedModulesForProtocol, + useModuleCalibrationStatus, + useProtocolAnalysisErrors, +} from '/app/resources/runs' +import { + ROBOT_CALIBRATION_STEP_KEY, + MODULE_SETUP_STEP_KEY, + LPC_STEP_KEY, + LABWARE_SETUP_STEP_KEY, + LIQUID_SETUP_STEP_KEY, + updateRunSetupStepsComplete, + getMissingSetupSteps, +} from '/app/redux/protocol-runs' +import { SetupLabware } from './SetupLabware' +import { SetupLabwarePositionCheck } from './SetupLabwarePositionCheck' +import { SetupRobotCalibration } from './SetupRobotCalibration' +import { SetupModuleAndDeck } from './SetupModuleAndDeck' +import { SetupStep } from './SetupStep' +import { SetupLiquids } from './SetupLiquids' +import { EmptySetupStep } from './EmptySetupStep' +import { HowLPCWorksModal } from './SetupLabwarePositionCheck/HowLPCWorksModal' + +import type { RefObject } from 'react' +import type { Dispatch, State } from '/app/redux/types' +import type { StepKey } from '/app/redux/protocol-runs' + +interface ProtocolRunSetupProps { + protocolRunHeaderRef: RefObject | null + robotName: string + runId: string +} + +export function ProtocolRunSetup({ + protocolRunHeaderRef, + robotName, + runId, +}: ProtocolRunSetupProps): JSX.Element | null { + const { t, i18n } = useTranslation('protocol_setup') + const dispatch = useDispatch() + const robotProtocolAnalysis = useMostRecentCompletedAnalysis(runId) + const storedProtocolAnalysis = useStoredProtocolAnalysis(runId) + const protocolAnalysis = robotProtocolAnalysis ?? storedProtocolAnalysis + const { + orderedSteps, + orderedApplicableSteps, + } = useRequiredSetupStepsInOrder({ runId, protocolAnalysis }) + const modules = parseAllRequiredModuleModels(protocolAnalysis?.commands ?? []) + + const robot = useRobot(robotName) + const calibrationStatusRobot = useRunCalibrationStatus(robotName, runId) + const calibrationStatusModules = useModuleCalibrationStatus(robotName, runId) + const { missingModuleIds } = useUnmatchedModulesForProtocol(robotName, runId) + const isFlex = useIsFlex(robotName) + const runHasStarted = useRunHasStarted(runId) + const { analysisErrors } = useProtocolAnalysisErrors(runId) + const [expandedStepKey, setExpandedStepKey] = useState(null) + const robotType = isFlex ? FLEX_ROBOT_TYPE : OT2_ROBOT_TYPE + const deckConfigCompatibility = useDeckConfigurationCompatibility( + robotType, + protocolAnalysis + ) + const runPipetteInfoByMount = useRunPipetteInfoByMount(runId) + + const isMissingPipette = + (runPipetteInfoByMount.left != null && + runPipetteInfoByMount.left.requestedPipetteMatch === INCOMPATIBLE) || + (runPipetteInfoByMount.right != null && + runPipetteInfoByMount.right.requestedPipetteMatch === INCOMPATIBLE) || + // for Flex, require exact match + (isFlex && + runPipetteInfoByMount.left != null && + runPipetteInfoByMount.left.requestedPipetteMatch === INEXACT_MATCH) || + (isFlex && + runPipetteInfoByMount.right != null && + runPipetteInfoByMount.right.requestedPipetteMatch === INEXACT_MATCH) + + const isFixtureMismatch = getIsFixtureMismatch(deckConfigCompatibility) + + const isMissingModule = missingModuleIds.length > 0 + + const liquids = protocolAnalysis?.liquids ?? [] + const hasLiquids = liquids.length > 0 + const hasModules = protocolAnalysis != null && modules.length > 0 + // need config compatibility (including check for single slot conflicts) + const requiredDeckConfigCompatibility = getRequiredDeckConfig( + deckConfigCompatibility + ) + const hasFixtures = requiredDeckConfigCompatibility.length > 0 + const flexDeckHardwareDescription = + hasModules || hasFixtures + ? t('install_modules_and_fixtures') + : t('no_deck_hardware_specified') + const ot2DeckHardwareDescription = hasModules + ? t('install_modules', { count: modules.length }) + : t('no_deck_hardware_specified') + + const missingSteps = useSelector( + (state: State): StepKey[] => getMissingSetupSteps(state, runId) + ) + + if (robot == null) { + return null + } + const StepDetailMap: Record< + StepKey, + { + stepInternals: JSX.Element + description: string + rightElProps: StepRightElementProps + } + > = { + [ROBOT_CALIBRATION_STEP_KEY]: { + stepInternals: ( + v === ROBOT_CALIBRATION_STEP_KEY + ) + 1 + ] + } + expandStep={setExpandedStepKey} + calibrationStatus={calibrationStatusRobot} + /> + ), + // change description for Flex + description: isFlex + ? t(`${ROBOT_CALIBRATION_STEP_KEY}_description_pipettes_only`) + : t(`${ROBOT_CALIBRATION_STEP_KEY}_description`), + rightElProps: { + stepKey: ROBOT_CALIBRATION_STEP_KEY, + complete: calibrationStatusRobot.complete, + completeText: t('calibration_ready'), + missingHardware: isMissingPipette, + incompleteText: t('calibration_needed'), + missingHardwareText: t('action_needed'), + incompleteElement: null, + }, + }, + [MODULE_SETUP_STEP_KEY]: { + stepInternals: ( + { + setExpandedStepKey(LPC_STEP_KEY) + }} + robotName={robotName} + runId={runId} + hasModules={hasModules} + protocolAnalysis={protocolAnalysis} + /> + ), + description: isFlex + ? flexDeckHardwareDescription + : ot2DeckHardwareDescription, + rightElProps: { + stepKey: MODULE_SETUP_STEP_KEY, + complete: + calibrationStatusModules.complete && + !isMissingModule && + !isFixtureMismatch, + completeText: + isFlex && hasModules + ? t('calibration_ready') + : t('deck_hardware_ready'), + incompleteText: + isFlex && hasModules ? t('calibration_needed') : t('action_needed'), + missingHardware: isMissingModule || isFixtureMismatch, + missingHardwareText: t('action_needed'), + incompleteElement: null, + }, + }, + [LPC_STEP_KEY]: { + stepInternals: ( + { + dispatch( + updateRunSetupStepsComplete(runId, { [LPC_STEP_KEY]: confirmed }) + ) + if (confirmed) { + setExpandedStepKey(LABWARE_SETUP_STEP_KEY) + } + }} + offsetsConfirmed={!missingSteps.includes(LPC_STEP_KEY)} + /> + ), + description: t('labware_position_check_step_description'), + rightElProps: { + stepKey: LPC_STEP_KEY, + complete: !missingSteps.includes(LPC_STEP_KEY), + completeText: t('offsets_ready'), + incompleteText: null, + incompleteElement: , + }, + }, + [LABWARE_SETUP_STEP_KEY]: { + stepInternals: ( + { + dispatch( + updateRunSetupStepsComplete(runId, { + [LABWARE_SETUP_STEP_KEY]: confirmed, + }) + ) + if (confirmed) { + const nextStep = + orderedApplicableSteps.findIndex( + v => v === LABWARE_SETUP_STEP_KEY + ) === + orderedApplicableSteps.length - 1 + ? null + : LIQUID_SETUP_STEP_KEY + setExpandedStepKey(nextStep) + } + }} + /> + ), + description: t(`${LABWARE_SETUP_STEP_KEY}_description`), + rightElProps: { + stepKey: LABWARE_SETUP_STEP_KEY, + complete: !missingSteps.includes(LABWARE_SETUP_STEP_KEY), + completeText: t('placements_ready'), + incompleteText: null, + incompleteElement: null, + }, + }, + [LIQUID_SETUP_STEP_KEY]: { + stepInternals: ( + { + dispatch( + updateRunSetupStepsComplete(runId, { + [LIQUID_SETUP_STEP_KEY]: confirmed, + }) + ) + if (confirmed) { + setExpandedStepKey(null) + } + }} + /> + ), + description: hasLiquids + ? t(`${LIQUID_SETUP_STEP_KEY}_description`) + : i18n.format(t('liquids_not_in_the_protocol'), 'capitalize'), + rightElProps: { + stepKey: LIQUID_SETUP_STEP_KEY, + complete: !missingSteps.includes(LIQUID_SETUP_STEP_KEY), + completeText: t('liquids_ready'), + incompleteText: null, + incompleteElement: null, + }, + }, + } + + return ( + + {protocolAnalysis != null ? ( + <> + {runHasStarted ? ( + + ) : null} + {analysisErrors != null && analysisErrors?.length > 0 ? ( + + {t('protocol_analysis_failed')} + + ) : ( + orderedSteps.map((stepKey, index) => { + const setupStepTitle = t(`${stepKey}_title`) + const showEmptySetupStep = + (stepKey === 'liquid_setup_step' && !hasLiquids) || + (stepKey === 'module_setup_step' && + ((!isFlex && !hasModules) || + (isFlex && !hasModules && !hasFixtures))) + return ( + + {showEmptySetupStep ? ( + + } + /> + ) : ( + { + stepKey === expandedStepKey + ? setExpandedStepKey(null) + : setExpandedStepKey(stepKey) + }} + rightElement={ + + } + > + {StepDetailMap[stepKey].stepInternals} + + )} + {index !== orderedSteps.length - 1 ? ( + + ) : null} + + ) + }) + )} + + ) : ( + + {t('loading_data')} + + )} + + ) +} + +interface NoHardwareRequiredStepCompletion { + stepKey: Exclude< + StepKey, + typeof ROBOT_CALIBRATION_STEP_KEY | typeof MODULE_SETUP_STEP_KEY + > + complete: boolean + incompleteText: string | null + incompleteElement: JSX.Element | null + completeText: string +} + +interface HardwareRequiredStepCompletion { + stepKey: typeof ROBOT_CALIBRATION_STEP_KEY | typeof MODULE_SETUP_STEP_KEY + complete: boolean + missingHardware: boolean + incompleteText: string | null + incompleteElement: JSX.Element | null + completeText: string + missingHardwareText: string +} + +type StepRightElementProps = + | NoHardwareRequiredStepCompletion + | HardwareRequiredStepCompletion + +const stepRequiresHW = ( + props: StepRightElementProps +): props is HardwareRequiredStepCompletion => + props.stepKey === ROBOT_CALIBRATION_STEP_KEY || + props.stepKey === MODULE_SETUP_STEP_KEY + +function StepRightElement(props: StepRightElementProps): JSX.Element | null { + if (props.complete) { + return ( + + + + {props.completeText} + + + ) + } else if (stepRequiresHW(props) && props.missingHardware) { + return ( + + + + {props.missingHardwareText} + + + ) + } else if (props.incompleteText != null) { + return ( + + + + {props.incompleteText} + + + ) + } else if (props.incompleteElement != null) { + return props.incompleteElement + } else { + return null + } +} + +function LearnAboutLPC(): JSX.Element { + const { t } = useTranslation('protocol_setup') + const [showLPCHelpModal, setShowLPCHelpModal] = useState(false) + return ( + <> + { + // clicking link shouldn't toggle step expanded state + e.preventDefault() + e.stopPropagation() + setShowLPCHelpModal(true) + }} + > + {t('learn_how_it_works')} + + {showLPCHelpModal ? ( + { + setShowLPCHelpModal(false) + }} + /> + ) : null} + + ) +} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupCalibrationItem.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupCalibrationItem.tsx similarity index 95% rename from app/src/organisms/Devices/ProtocolRun/SetupCalibrationItem.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupCalibrationItem.tsx index 89bec05607c..8ac68de4ff1 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupCalibrationItem.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupCalibrationItem.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -16,8 +15,8 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { useRunHasStarted } from '../hooks' -import { formatTimestamp } from '../utils' +import { useRunHasStarted } from '/app/resources/runs' +import { formatTimestamp } from '/app/transformations/runs' interface SetupCalibrationItemProps { calibratedDate: string | null diff --git a/app/src/organisms/Devices/ProtocolRun/SetupDeckCalibration.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupDeckCalibration.tsx similarity index 95% rename from app/src/organisms/Devices/ProtocolRun/SetupDeckCalibration.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupDeckCalibration.tsx index f8c880133d6..3ec0a03e72c 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupDeckCalibration.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupDeckCalibration.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { Link } from 'react-router-dom' @@ -14,7 +13,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { TertiaryButton } from '../../../atoms/buttons' +import { TertiaryButton } from '/app/atoms/buttons' import { useDeckCalibrationData } from '../hooks' import { SetupCalibrationItem } from './SetupCalibrationItem' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupFlexPipetteCalibrationItem.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupFlexPipetteCalibrationItem.tsx similarity index 82% rename from app/src/organisms/Devices/ProtocolRun/SetupFlexPipetteCalibrationItem.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupFlexPipetteCalibrationItem.tsx index 49ed6243cf8..01b6f0303fb 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupFlexPipetteCalibrationItem.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupFlexPipetteCalibrationItem.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { Flex, @@ -14,14 +14,15 @@ import { SINGLE_MOUNT_PIPETTES, } from '@opentrons/shared-data' import { useInstrumentsQuery } from '@opentrons/react-api-client' -import { TertiaryButton } from '../../../atoms/buttons' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { PipetteWizardFlows } from '../../PipetteWizardFlows' -import { FLOWS } from '../../PipetteWizardFlows/constants' +import { TertiaryButton } from '/app/atoms/buttons' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { useStoredProtocolAnalysis } from '/app/resources/analysis' +import { PipetteWizardFlows } from '/app/organisms/PipetteWizardFlows' +import { FLOWS } from '/app/organisms/PipetteWizardFlows/constants' import { SetupCalibrationItem } from './SetupCalibrationItem' import type { PipetteData } from '@opentrons/api-client' import type { LoadPipetteRunTimeCommand } from '@opentrons/shared-data' -import type { Mount } from '../../../redux/pipettes/types' +import type { Mount } from '/app/redux/pipettes/types' interface SetupInstrumentCalibrationItemProps { mount: Mount @@ -35,16 +36,16 @@ export function SetupFlexPipetteCalibrationItem({ instrumentsRefetch, }: SetupInstrumentCalibrationItemProps): JSX.Element | null { const { t } = useTranslation(['protocol_setup', 'devices_landing']) - const [showFlexPipetteFlow, setShowFlexPipetteFlow] = React.useState( - false - ) + const [showFlexPipetteFlow, setShowFlexPipetteFlow] = useState(false) const { data: attachedInstruments } = useInstrumentsQuery() const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) - const loadPipetteCommand = mostRecentAnalysis?.commands.find( + const storedProtocolAnalysis = useStoredProtocolAnalysis(runId) + const completedAnalysis = mostRecentAnalysis ?? storedProtocolAnalysis + const loadPipetteCommand = completedAnalysis?.commands.find( (command): command is LoadPipetteRunTimeCommand => command.commandType === 'loadPipette' && command.params.mount === mount ) - const requestedPipette = mostRecentAnalysis?.pipettes?.find( + const requestedPipette = completedAnalysis?.pipettes?.find( pipette => pipette.id === loadPipetteCommand?.result?.pipetteId ) @@ -120,7 +121,7 @@ export function SetupFlexPipetteCalibrationItem({ ? NINETY_SIX_CHANNEL : SINGLE_MOUNT_PIPETTES } - pipetteInfo={mostRecentAnalysis?.pipettes} + pipetteInfo={completedAnalysis?.pipettes} onComplete={instrumentsRefetch} /> )} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupGripperCalibrationItem.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupGripperCalibrationItem.tsx similarity index 86% rename from app/src/organisms/Devices/ProtocolRun/SetupGripperCalibrationItem.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupGripperCalibrationItem.tsx index 863e62ac2d9..2a106db20e2 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupGripperCalibrationItem.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupGripperCalibrationItem.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { Flex, @@ -9,14 +9,14 @@ import { WRAP, } from '@opentrons/components' import { getGripperDisplayName } from '@opentrons/shared-data' -import { TertiaryButton } from '../../../atoms/buttons' +import { TertiaryButton } from '/app/atoms/buttons' import { SetupCalibrationItem } from './SetupCalibrationItem' -import { GripperWizardFlows } from '../../GripperWizardFlows' -import { GRIPPER_FLOW_TYPES } from '../../GripperWizardFlows/constants' +import { GripperWizardFlows } from '/app/organisms/GripperWizardFlows' +import { GRIPPER_FLOW_TYPES } from '/app/organisms/GripperWizardFlows/constants' import type { GripperData } from '@opentrons/api-client' import type { GripperModel } from '@opentrons/shared-data' -import type { GripperWizardFlowType } from '../../GripperWizardFlows/types' +import type { GripperWizardFlowType } from '/app/organisms/GripperWizardFlows/types' interface SetupGripperCalibrationItemProps { gripperData: GripperData | null @@ -31,7 +31,7 @@ export function SetupGripperCalibrationItem({ const [ openWizardFlowType, setOpenWizardFlowType, - ] = React.useState(null) + ] = useState(null) const gripperCalLastModified = gripperData != null diff --git a/app/src/organisms/Devices/ProtocolRun/SetupInstrumentCalibration.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupInstrumentCalibration.tsx similarity index 87% rename from app/src/organisms/Devices/ProtocolRun/SetupInstrumentCalibration.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupInstrumentCalibration.tsx index 6d6ee43ca92..0ce395e0e46 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupInstrumentCalibration.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupInstrumentCalibration.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -9,23 +8,23 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import * as PipetteConstants from '../../../redux/pipettes/constants' -import { getShowPipetteCalibrationWarning } from '../utils' +import * as PipetteConstants from '/app/redux/pipettes/constants' +import { getShowPipetteCalibrationWarning } from '/app/transformations/instruments' import { PipetteRecalibrationWarning } from '../PipetteCard/PipetteRecalibrationWarning' -import { - useRunPipetteInfoByMount, - useStoredProtocolAnalysis, - useIsFlex, -} from '../hooks' +import { useStoredProtocolAnalysis } from '/app/resources/analysis' +import { useIsFlex } from '/app/redux-resources/robots' import { SetupPipetteCalibrationItem } from './SetupPipetteCalibrationItem' import { SetupFlexPipetteCalibrationItem } from './SetupFlexPipetteCalibrationItem' import { SetupGripperCalibrationItem } from './SetupGripperCalibrationItem' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { + useRunPipetteInfoByMount, + useMostRecentCompletedAnalysis, +} from '/app/resources/runs' import { useInstrumentsQuery } from '@opentrons/react-api-client' -import { isGripperInCommands } from '../../../resources/protocols/utils' +import { isGripperInCommands } from '/app/resources/protocols/utils' import type { GripperData } from '@opentrons/api-client' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' const EQUIPMENT_POLL_MS = 5000 diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/CurrentOffsetsModal.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/CurrentOffsetsModal.tsx similarity index 91% rename from app/src/organisms/Devices/ProtocolRun/SetupLabware/CurrentOffsetsModal.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/CurrentOffsetsModal.tsx index 0e81df5ed5d..402c1e56dd1 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/CurrentOffsetsModal.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/CurrentOffsetsModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import styled from 'styled-components' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' @@ -20,11 +19,11 @@ import { JUSTIFY_SPACE_BETWEEN, } from '@opentrons/components' -import { getIsLabwareOffsetCodeSnippetsOn } from '../../../../redux/config' -import { LabwareOffsetTabs } from '../../../LabwareOffsetTabs' -import { OffsetVector } from '../../../../molecules/OffsetVector' -import { PythonLabwareOffsetSnippet } from '../../../../molecules/PythonLabwareOffsetSnippet' -import { getLatestCurrentOffsets } from '../SetupLabwarePositionCheck/utils' +import { getIsLabwareOffsetCodeSnippetsOn } from '/app/redux/config' +import { LabwareOffsetTabs } from '/app/organisms/LabwareOffsetTabs' +import { OffsetVector } from '/app/molecules/OffsetVector' +import { PythonLabwareOffsetSnippet } from '/app/molecules/PythonLabwareOffsetSnippet' +import { getLatestCurrentOffsets } from '/app/transformations/runs' import type { LabwareOffset } from '@opentrons/api-client' import type { diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx new file mode 100644 index 00000000000..f31a3bcf28d --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx @@ -0,0 +1,407 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import styled, { css } from 'styled-components' + +import { + ALIGN_CENTER, + BORDERS, + Btn, + COLORS, + DeckInfoLabel, + DIRECTION_COLUMN, + DIRECTION_ROW, + DISPLAY_FLEX, + DISPLAY_GRID, + Flex, + Icon, + JUSTIFY_CENTER, + JUSTIFY_SPACE_BETWEEN, + LabwareRender, + MODULE_ICON_NAME_BY_TYPE, + SIZE_AUTO, + SPACING, + StyledText, + TYPOGRAPHY, + WELL_LABEL_OPTIONS, +} from '@opentrons/components' +import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' +import { + getTopLabwareInfo, + getModuleDisplayName, + getModuleType, + HEATERSHAKER_MODULE_TYPE, + MAGNETIC_MODULE_TYPE, + TC_MODULE_LOCATION_OT2, + TC_MODULE_LOCATION_OT3, + THERMOCYCLER_MODULE_TYPE, + THERMOCYCLER_MODULE_V2, +} from '@opentrons/shared-data' + +import { getLocationInfoNames } from '/app/transformations/commands' +import { ToggleButton } from '/app/atoms/buttons' +import { Divider } from '/app/atoms/structure' +import { SecureLabwareModal } from './SecureLabwareModal' + +import type { + HeaterShakerCloseLatchCreateCommand, + HeaterShakerOpenLatchCreateCommand, + RunTimeCommand, + ModuleType, + LabwareDefinition2, + LoadLabwareRunTimeCommand, +} from '@opentrons/shared-data' +import type { ModuleRenderInfoForProtocol } from '/app/resources/runs' +import type { LabwareSetupItem } from '/app/transformations/commands' +import type { ModuleTypesThatRequireExtraAttention } from '../utils/getModuleTypesThatRequireExtraAttention' + +const LabwareRow = styled.div` + display: ${DISPLAY_GRID}; + grid-template-columns: 90px 12fr; + border-style: ${BORDERS.styleSolid}; + border-width: 1px; + border-color: ${COLORS.grey30}; + border-radius: ${BORDERS.borderRadius4}; + padding: ${SPACING.spacing12} ${SPACING.spacing16} ${SPACING.spacing12} + ${SPACING.spacing24}; +` + +interface LabwareListItemProps extends LabwareSetupItem { + attachedModuleInfo: { [moduleId: string]: ModuleRenderInfoForProtocol } + extraAttentionModules: ModuleTypesThatRequireExtraAttention[] + isFlex: boolean + commands: RunTimeCommand[] + showLabwareSVG?: boolean +} + +export function LabwareListItem( + props: LabwareListItemProps +): JSX.Element | null { + const { + attachedModuleInfo, + nickName: bottomLabwareNickname, + initialLocation, + moduleModel, + extraAttentionModules, + isFlex, + commands, + showLabwareSVG, + labwareId: bottomLabwareId, + } = props + const loadLabwareCommands = commands?.filter( + (command): command is LoadLabwareRunTimeCommand => + command.commandType === 'loadLabware' + ) + + const { topLabwareId, topLabwareDefinition } = getTopLabwareInfo( + bottomLabwareId ?? '', + loadLabwareCommands + ) + const { + slotName, + labwareName, + labwareNickname, + labwareQuantity, + adapterName: bottomLabwareName, + } = getLocationInfoNames(topLabwareId, commands) + + const isStacked = + labwareQuantity > 1 || + bottomLabwareId !== topLabwareId || + moduleModel != null + + const { i18n, t } = useTranslation('protocol_setup') + const [ + secureLabwareModalType, + setSecureLabwareModalType, + ] = useState(null) + const { createLiveCommand } = useCreateLiveCommandMutation() + const [isLatchLoading, setIsLatchLoading] = useState(false) + const [isLatchClosed, setIsLatchClosed] = useState(false) + + let slotInfo: string | null = slotName + if (initialLocation === 'offDeck') { + slotInfo = i18n.format(t('off_deck'), 'upperCase') + } + + let moduleDisplayName: string | null = null + let moduleType: ModuleType | null = null + let extraAttentionText: JSX.Element | null = null + let secureLabwareInstructions: JSX.Element | null = null + let isCorrectHeaterShakerAttached: boolean = false + let isHeaterShakerInProtocol: boolean = false + let latchCommand: + | HeaterShakerOpenLatchCreateCommand + | HeaterShakerCloseLatchCreateCommand + + if (moduleModel != null) { + moduleType = getModuleType(moduleModel) + moduleDisplayName = getModuleDisplayName(moduleModel) + + const moduleTypeNeedsAttention = extraAttentionModules.find( + extraAttentionModType => extraAttentionModType === moduleType + ) + + switch (moduleTypeNeedsAttention) { + case MAGNETIC_MODULE_TYPE: + case THERMOCYCLER_MODULE_TYPE: + if (moduleType === THERMOCYCLER_MODULE_TYPE) { + slotInfo = isFlex ? TC_MODULE_LOCATION_OT3 : TC_MODULE_LOCATION_OT2 + } + if (moduleModel !== THERMOCYCLER_MODULE_V2) { + secureLabwareInstructions = ( + { + setSecureLabwareModalType(moduleType) + }} + > + + + + {t('secure_labware_instructions')} + + + + ) + } + break + case HEATERSHAKER_MODULE_TYPE: + isHeaterShakerInProtocol = true + extraAttentionText = ( + + {t('heater_shaker_labware_list_view')} + + ) + const matchingHeaterShaker = + attachedModuleInfo != null && + initialLocation !== 'offDeck' && + 'moduleId' in initialLocation && + attachedModuleInfo[initialLocation.moduleId] != null + ? attachedModuleInfo[initialLocation.moduleId].attachedModuleMatch + : null + if ( + matchingHeaterShaker != null && + matchingHeaterShaker.moduleType === HEATERSHAKER_MODULE_TYPE + ) { + if ( + (!isLatchClosed && + (matchingHeaterShaker.data.labwareLatchStatus === 'idle_closed' || + matchingHeaterShaker.data.labwareLatchStatus === 'closing')) || + (isLatchClosed && + (matchingHeaterShaker.data.labwareLatchStatus === 'idle_open' || + matchingHeaterShaker.data.labwareLatchStatus === 'opening')) + ) { + setIsLatchClosed( + matchingHeaterShaker.data.labwareLatchStatus === 'idle_closed' || + matchingHeaterShaker.data.labwareLatchStatus === 'closing' + ) + setIsLatchLoading(false) + } + latchCommand = { + commandType: isLatchClosed + ? 'heaterShaker/openLabwareLatch' + : 'heaterShaker/closeLabwareLatch', + params: { moduleId: matchingHeaterShaker.id }, + } + // Labware latch button is disabled unless the correct H-S is attached + // this is for MoaM support + isCorrectHeaterShakerAttached = true + } + } + } + const toggleLatch = (): void => { + setIsLatchLoading(true) + createLiveCommand({ + command: latchCommand, + }).catch((e: Error) => { + console.error( + `error setting module status with command type ${latchCommand.commandType}: ${e.message}` + ) + }) + } + const commandType = isLatchClosed + ? 'heaterShaker/openLabwareLatch' + : 'heaterShaker/closeLabwareLatch' + let hsLatchText: string = t('secure') + if (commandType === 'heaterShaker/closeLabwareLatch' && isLatchLoading) { + hsLatchText = t('closing') + } else if ( + commandType === 'heaterShaker/openLabwareLatch' && + isLatchLoading + ) { + hsLatchText = t('opening') + } + + return ( + + + {slotInfo != null && isFlex ? ( + + ) : ( + + {slotInfo} + + )} + {isStacked ? : null} + + + <> + + {showLabwareSVG && topLabwareDefinition != null ? ( + + ) : null} + + + {labwareName} + + + {labwareQuantity > 1 + ? t('labware_quantity', { quantity: labwareQuantity }) + : labwareNickname} + + + + + {bottomLabwareName != null ? ( + <> + + + + {bottomLabwareName} + + + {bottomLabwareNickname} + + + + ) : null} + {moduleDisplayName != null ? ( + <> + + + + {moduleType != null ? ( + + ) : null} + + + {moduleDisplayName} + + {extraAttentionText} + + + {secureLabwareInstructions} + {isHeaterShakerInProtocol ? ( + + + {t('labware_latch')} + + + + + {hsLatchText} + + + + ) : null} + + + ) : null} + + {secureLabwareModalType != null && ( + { + setSecureLabwareModalType(null) + }} + /> + )} + + ) +} + +const LabwareThumbnail = styled.svg` + transform: scale(1, -1); + width: 4.2rem; + flex-shrink: 0; +` + +function StandaloneLabware(props: { + definition: LabwareDefinition2 +}): JSX.Element { + const { definition } = props + return ( + + + + ) +} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/OffDeckLabwareList.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/OffDeckLabwareList.tsx similarity index 89% rename from app/src/organisms/Devices/ProtocolRun/SetupLabware/OffDeckLabwareList.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/OffDeckLabwareList.tsx index 121f4588691..d8507cab656 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/OffDeckLabwareList.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/OffDeckLabwareList.tsx @@ -1,9 +1,8 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { SPACING, TYPOGRAPHY, LegacyStyledText } from '@opentrons/components' import { LabwareListItem } from './LabwareListItem' import type { RunTimeCommand } from '@opentrons/shared-data' -import type { LabwareSetupItem } from '../../../../pages/Protocols/utils' +import type { LabwareSetupItem } from '/app/transformations/commands' interface OffDeckLabwareListProps { labwareItems: LabwareSetupItem[] @@ -34,7 +33,6 @@ export function OffDeckLabwareList( {...labwareItem} isFlex={isFlex} commands={commands} - nestedLabwareInfo={null} showLabwareSVG /> ))} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/SecureLabwareModal.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SecureLabwareModal.tsx similarity index 91% rename from app/src/organisms/Devices/ProtocolRun/SetupLabware/SecureLabwareModal.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SecureLabwareModal.tsx index 6f313f9fb1c..3ce61fb7f2d 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/SecureLabwareModal.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SecureLabwareModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { createPortal } from 'react-dom' import snakeCase from 'lodash/snakeCase' import { Trans, useTranslation } from 'react-i18next' @@ -14,9 +13,9 @@ import { TYPOGRAPHY, Modal, } from '@opentrons/components' -import { getTopPortalEl } from '../../../../App/portal' -import secureMagModBracketImage from '../../../../assets/images/secure_mag_mod_bracket.png' -import secureTCLatchImage from '../../../../assets/images/secure_tc_latch.png' +import { getTopPortalEl } from '/app/App/portal' +import secureMagModBracketImage from '/app/assets/images/secure_mag_mod_bracket.png' +import secureTCLatchImage from '/app/assets/images/secure_tc_latch.png' import { getModuleName } from '../utils/getModuleName' import type { ModuleTypesThatRequireExtraAttention } from '../utils/getModuleTypesThatRequireExtraAttention' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx similarity index 85% rename from app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx index a86da22ace8..b71c84da0f8 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareList.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { DIRECTION_COLUMN, @@ -7,14 +6,13 @@ import { StyledText, COLORS, } from '@opentrons/components' -import { getLabwareSetupItemGroups } from '../../../../pages/Protocols/utils' +import { getLabwareSetupItemGroups } from '/app/transformations/commands' import { LabwareListItem } from './LabwareListItem' -import { getNestedLabwareInfo } from './getNestedLabwareInfo' import type { RunTimeCommand } from '@opentrons/shared-data' -import type { ModuleRenderInfoForProtocol } from '../../hooks' +import type { ModuleRenderInfoForProtocol } from '/app/resources/runs' import type { ModuleTypesThatRequireExtraAttention } from '../utils/getModuleTypesThatRequireExtraAttention' -import type { LabwareSetupItem } from '../../../../pages/Protocols/utils' +import type { LabwareSetupItem } from '/app/transformations/commands' interface SetupLabwareListProps { attachedModuleInfo: { [moduleId: string]: ModuleRenderInfoForProtocol } @@ -55,6 +53,7 @@ export function SetupLabwareList( {allItems.map((labwareItem, index) => { + // filtering out all labware that aren't on a module or the deck const labwareOnAdapter = allItems.find( item => labwareItem.initialLocation !== 'offDeck' && @@ -69,7 +68,6 @@ export function SetupLabwareList( extraAttentionModules={extraAttentionModules} {...labwareItem} isFlex={isFlex} - nestedLabwareInfo={getNestedLabwareInfo(labwareItem, commands)} /> ) })} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx new file mode 100644 index 00000000000..c8bc460bbf4 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx @@ -0,0 +1,207 @@ +import { useState } from 'react' +import map from 'lodash/map' + +import { + BaseDeck, + Flex, + Box, + DIRECTION_COLUMN, + SPACING, +} from '@opentrons/components' +import { + FLEX_ROBOT_TYPE, + getDeckDefFromRobotType, + getSimplestDeckConfigForProtocol, + getTopLabwareInfo, + THERMOCYCLER_MODULE_V1, +} from '@opentrons/shared-data' + +import { getLabwareSetupItemGroups } from '/app/transformations/commands' +import { LabwareInfoOverlay } from '../LabwareInfoOverlay' +import { + getProtocolModulesInfo, + getLabwareRenderInfo, +} from '/app/transformations/analysis' +import { LabwareStackModal } from '/app/molecules/LabwareStackModal' +import { getStandardDeckViewLayerBlockList } from '/app/local-resources/deck_configuration' +import { OffDeckLabwareList } from './OffDeckLabwareList' + +import type { LabwareOnDeck } from '@opentrons/components' +import type { + CompletedProtocolAnalysis, + ProtocolAnalysisOutput, + LoadLabwareRunTimeCommand, + RunTimeCommand, +} from '@opentrons/shared-data' + +interface SetupLabwareMapProps { + runId: string + protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null +} + +export function SetupLabwareMap({ + runId, + protocolAnalysis, +}: SetupLabwareMapProps): JSX.Element | null { + // early return null if no protocol analysis + const [ + labwareStackDetailsLabwareId, + setLabwareStackDetailsLabwareId, + ] = useState(null) + const [hoverLabwareId, setHoverLabwareId] = useState(null) + + if (protocolAnalysis == null) return null + + const commands: RunTimeCommand[] = protocolAnalysis.commands + const loadLabwareCommands = commands?.filter( + (command): command is LoadLabwareRunTimeCommand => + command.commandType === 'loadLabware' + ) + + const robotType = protocolAnalysis.robotType ?? FLEX_ROBOT_TYPE + const deckDef = getDeckDefFromRobotType(robotType) + + const protocolModulesInfo = getProtocolModulesInfo(protocolAnalysis, deckDef) + + const modulesOnDeck = protocolModulesInfo.map(module => { + const isLabwareStacked = + module.nestedLabwareId != null && module.nestedLabwareDef != null + const { + topLabwareId, + topLabwareDefinition, + topLabwareDisplayName, + } = getTopLabwareInfo(module.nestedLabwareId ?? '', loadLabwareCommands) + + return { + moduleModel: module.moduleDef.model, + moduleLocation: { slotName: module.slotName }, + innerProps: + module.moduleDef.model === THERMOCYCLER_MODULE_V1 + ? { lidMotorState: 'open' } + : {}, + + nestedLabwareDef: topLabwareDefinition, + highlightLabware: hoverLabwareId === topLabwareId, + highlightShadowLabware: hoverLabwareId === topLabwareId, + stacked: isLabwareStacked, + moduleChildren: ( + // open modal + { + if (topLabwareDefinition != null && topLabwareId != null) { + setLabwareStackDetailsLabwareId(topLabwareId) + } + }} + onMouseEnter={() => { + if (topLabwareDefinition != null && topLabwareId != null) { + setHoverLabwareId(topLabwareId) + } + }} + onMouseLeave={() => { + setHoverLabwareId(null) + }} + cursor="pointer" + > + {topLabwareDefinition != null && topLabwareId != null ? ( + + ) : null} + + ), + } + }) + + const { offDeckItems } = getLabwareSetupItemGroups(commands) + + const deckConfig = getSimplestDeckConfigForProtocol(protocolAnalysis) + + const labwareRenderInfo = getLabwareRenderInfo(protocolAnalysis, deckDef) + + const labwareOnDeck: Array = map( + labwareRenderInfo, + ({ slotName }, labwareId) => { + const { + topLabwareId, + topLabwareDefinition, + topLabwareDisplayName, + } = getTopLabwareInfo(labwareId, loadLabwareCommands) + const isLabwareInStack = labwareId !== topLabwareId + return topLabwareDefinition != null + ? { + labwareLocation: { slotName }, + definition: topLabwareDefinition, + highlight: isLabwareInStack && hoverLabwareId === topLabwareId, + highlightShadow: + isLabwareInStack && hoverLabwareId === topLabwareId, + stacked: isLabwareInStack, + labwareChildren: ( + { + if (isLabwareInStack) { + setLabwareStackDetailsLabwareId(topLabwareId) + } + }} + onMouseEnter={() => { + if (topLabwareDefinition != null && topLabwareId != null) { + setHoverLabwareId(() => topLabwareId) + } + }} + onMouseLeave={() => { + setHoverLabwareId(null) + }} + > + {topLabwareDefinition != null ? ( + + ) : null} + + ), + } + : null + } + ) + + const labwareOnDeckFiltered: LabwareOnDeck[] = labwareOnDeck.filter( + (labware): labware is LabwareOnDeck => labware != null + ) + + return ( + + + + + + + + {labwareStackDetailsLabwareId != null && ( + { + setLabwareStackDetailsLabwareId(null) + }} + robotType={robotType} + /> + )} + + ) +} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/LabwareListItem.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/LabwareListItem.test.tsx similarity index 86% rename from app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/LabwareListItem.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/LabwareListItem.test.tsx index 0a83231dee5..50afda3d92f 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/LabwareListItem.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/LabwareListItem.test.tsx @@ -1,20 +1,24 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, expect } from 'vitest' import { MemoryRouter } from 'react-router-dom' -import { opentrons96PcrAdapterV1 } from '@opentrons/shared-data' +import { + opentrons96PcrAdapterV1, + getTopLabwareInfo, +} from '@opentrons/shared-data' import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockHeaterShaker, mockMagneticModule, mockTemperatureModule, mockThermocycler, -} from '../../../../../redux/modules/__fixtures__' -import { mockLabwareDef } from '../../../../LabwarePositionCheck/__fixtures__/mockLabwareDef' +} from '/app/redux/modules/__fixtures__' +import { getLocationInfoNames } from '/app/transformations/commands' +import { mockLabwareDef } from '/app/organisms/LabwarePositionCheck/__fixtures__/mockLabwareDef' import { SecureLabwareModal } from '../SecureLabwareModal' import { LabwareListItem } from '../LabwareListItem' import type { @@ -24,11 +28,19 @@ import type { LabwareDefinition2, LoadModuleRunTimeCommand, } from '@opentrons/shared-data' -import type { AttachedModule } from '../../../../../redux/modules/types' -import type { ModuleRenderInfoForProtocol } from '../../../hooks' +import type { AttachedModule } from '/app/redux/modules/types' +import type { ModuleRenderInfoForProtocol } from '/app/resources/runs' vi.mock('../SecureLabwareModal') +vi.mock('/app/transformations/commands') vi.mock('@opentrons/react-api-client') +vi.mock('@opentrons/shared-data', async importOriginal => { + const actualSharedData = await importOriginal() + return { + ...actualSharedData, + getTopLabwareInfo: vi.fn(), + } +}) const mockAdapterDef = opentrons96PcrAdapterV1 as LabwareDefinition2 const mockAdapterId = 'mockAdapterId' @@ -87,12 +99,23 @@ describe('LabwareListItem', () => { vi.mocked(useCreateLiveCommandMutation).mockReturnValue({ createLiveCommand: mockCreateLiveCommand, } as any) + vi.mocked(getLocationInfoNames).mockReturnValue({ + slotName: '7', + labwareName: 'Mock Labware Definition', + labwareNickname: 'nickName', + labwareQuantity: 1, + }) + vi.mocked(getTopLabwareInfo).mockReturnValue({ + topLabwareId: '1', + topLabwareDefinition: mockLabwareDef, + }) }) it('renders the correct info for a thermocycler (OT2), clicking on secure labware instructions opens the modal', () => { render({ commands: [], nickName: mockNickName, + labwareId: '7', definition: mockLabwareDef, initialLocation: { moduleId: mockModuleId }, moduleModel: 'thermocyclerModuleV1' as ModuleModel, @@ -107,7 +130,6 @@ describe('LabwareListItem', () => { } as any) as ModuleRenderInfoForProtocol, }, isFlex: false, - nestedLabwareInfo: null, }) screen.getByText('Mock Labware Definition') screen.getByText('nickName') @@ -137,7 +159,6 @@ describe('LabwareListItem', () => { } as any) as ModuleRenderInfoForProtocol, }, isFlex: true, - nestedLabwareInfo: null, }) screen.getByText('Mock Labware Definition') screen.getByText('A1+B1') @@ -168,7 +189,6 @@ describe('LabwareListItem', () => { } as any) as ModuleRenderInfoForProtocol, }, isFlex: false, - nestedLabwareInfo: null, }) screen.getByText('Mock Labware Definition') screen.getByTestId('slot_info_7') @@ -203,7 +223,6 @@ describe('LabwareListItem', () => { } as any) as ModuleRenderInfoForProtocol, }, isFlex: false, - nestedLabwareInfo: null, }) screen.getByText('Mock Labware Definition') screen.getByTestId('slot_info_7') @@ -245,7 +264,7 @@ describe('LabwareListItem', () => { nickName: mockNickName, definition: mockLabwareDef, initialLocation: { labwareId: mockAdapterId }, - moduleModel: 'temperatureModuleV1' as ModuleModel, + moduleModel: 'temperatureModuleV2' as ModuleModel, moduleLocation: mockModuleSlot, extraAttentionModules: [], attachedModuleInfo: { @@ -262,18 +281,11 @@ describe('LabwareListItem', () => { } as any) as ModuleRenderInfoForProtocol, }, isFlex: false, - nestedLabwareInfo: { - nestedLabwareDisplayName: 'mock nested display name', - sharedSlotId: '7', - nestedLabwareNickName: 'nestedLabwareNickName', - nestedLabwareDefinition: mockLabwareDef, - }, }) screen.getByText('Mock Labware Definition') screen.getAllByText('7') screen.getByText('Temperature Module GEN2') - screen.getByText('mock nested display name') - screen.getByText('nestedLabwareNickName') + screen.getByText('Mock Labware Definition') screen.getByText('nickName') }) @@ -293,10 +305,17 @@ describe('LabwareListItem', () => { z: 1.2, }, } as any + vi.mocked(getLocationInfoNames).mockReturnValue({ + slotName: 'A2', + labwareName: 'Mock Labware Name', + labwareNickname: 'labware nick name', + labwareQuantity: 1, + adapterName: 'mock adapter name', + }) render({ commands: [mockAdapterLoadCommand], - nickName: mockNickName, + nickName: 'mock adapter nick name', definition: mockLabwareDef, initialLocation: { labwareId: mockAdapterId }, moduleModel: null, @@ -304,18 +323,13 @@ describe('LabwareListItem', () => { extraAttentionModules: [], attachedModuleInfo: {}, isFlex: false, - nestedLabwareInfo: { - nestedLabwareDisplayName: 'mock nested display name', - sharedSlotId: 'A2', - nestedLabwareNickName: 'nestedLabwareNickName', - nestedLabwareDefinition: mockLabwareDef, - }, + labwareId: '5', }) - screen.getByText('Mock Labware Definition') + screen.getByText('Mock Labware Name') + screen.getByText('labware nick name') screen.getByText('A2') - screen.getByText('mock nested display name') - screen.getByText('nestedLabwareNickName') - screen.getByText('nickName') + screen.getByText('mock adapter name') + screen.getByText('mock adapter nick name') }) it('renders the correct info for a labware on top of a heater shaker', () => { @@ -341,7 +355,6 @@ describe('LabwareListItem', () => { } as any) as ModuleRenderInfoForProtocol, }, isFlex: false, - nestedLabwareInfo: null, }) screen.getByText('Mock Labware Definition') screen.getByTestId('slot_info_7') @@ -363,6 +376,7 @@ describe('LabwareListItem', () => { }) it('renders the correct info for an off deck labware', () => { + vi.mocked(getTopLabwareInfo) render({ nickName: null, definition: mockLabwareDef, @@ -373,7 +387,6 @@ describe('LabwareListItem', () => { extraAttentionModules: [], attachedModuleInfo: {}, isFlex: false, - nestedLabwareInfo: null, }) screen.getByText('Mock Labware Definition') screen.getByTestId('slot_info_OFF DECK') diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/OffDeckLabwareList.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/OffDeckLabwareList.test.tsx similarity index 85% rename from app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/OffDeckLabwareList.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/OffDeckLabwareList.test.tsx index 59487246732..7c6c839dbaf 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/OffDeckLabwareList.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/OffDeckLabwareList.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { screen } from '@testing-library/react' import { describe, it, beforeEach, vi, expect } from 'vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' -import { mockLabwareDef } from '../../../../LabwarePositionCheck/__fixtures__/mockLabwareDef' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockLabwareDef } from '/app/organisms/LabwarePositionCheck/__fixtures__/mockLabwareDef' import { LabwareListItem } from '../LabwareListItem' import { OffDeckLabwareList } from '../OffDeckLabwareList' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SecureLabwareModal.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SecureLabwareModal.test.tsx similarity index 94% rename from app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SecureLabwareModal.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SecureLabwareModal.test.tsx index 0a24039444d..80147006dc1 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SecureLabwareModal.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SecureLabwareModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, expect } from 'vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { SecureLabwareModal } from '../SecureLabwareModal' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabware.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabware.test.tsx new file mode 100644 index 00000000000..6acaf42445b --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabware.test.tsx @@ -0,0 +1,128 @@ +import { MemoryRouter } from 'react-router-dom' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' +import { when } from 'vitest-when' + +import { useHoverTooltip } from '@opentrons/components' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useLPCSuccessToast } from '../../../hooks/useLPCSuccessToast' +import { LabwarePositionCheck } from '/app/organisms/LabwarePositionCheck' +import { getModuleTypesThatRequireExtraAttention } from '../../utils/getModuleTypesThatRequireExtraAttention' +import { getIsLabwareOffsetCodeSnippetsOn } from '/app/redux/config' +import { SetupLabwareList } from '../SetupLabwareList' +import { SetupLabwareMap } from '../SetupLabwareMap' +import { SetupLabware } from '..' +import { + useNotifyRunQuery, + useRunCalibrationStatus, + useRunHasStarted, + useLPCDisabledReason, + useUnmatchedModulesForProtocol, +} from '/app/resources/runs' + +vi.mock('@opentrons/components', async () => { + const actual = await vi.importActual('@opentrons/components') + return { + ...actual, + useHoverTooltip: vi.fn(), + } +}) +vi.mock('../SetupLabwareList') +vi.mock('../SetupLabwareMap') +vi.mock('/app/organisms/LabwarePositionCheck') +vi.mock('../../utils/getModuleTypesThatRequireExtraAttention') +vi.mock('/app/organisms/RunTimeControl/hooks') +vi.mock('/app/redux/config') +vi.mock('../../../hooks/useLPCSuccessToast') +vi.mock('/app/resources/runs') +vi.mock('/app/redux-resources/robots') + +const ROBOT_NAME = 'otie' +const RUN_ID = '1' + +const render = () => { + let labwareConfirmed = false + const confirmLabware = vi.fn(confirmed => { + labwareConfirmed = confirmed + }) + return renderWithProviders( + + + , + { + i18nInstance: i18n, + } + )[0] +} + +describe('SetupLabware', () => { + beforeEach(() => { + when(vi.mocked(getModuleTypesThatRequireExtraAttention)) + .calledWith(expect.anything()) + .thenReturn([]) + + vi.mocked(LabwarePositionCheck).mockReturnValue( +
    mock Labware Position Check
    + ) + when(vi.mocked(useUnmatchedModulesForProtocol)) + .calledWith(ROBOT_NAME, RUN_ID) + .thenReturn({ + missingModuleIds: [], + remainingAttachedModules: [], + }) + + when(vi.mocked(useLPCSuccessToast)) + .calledWith() + .thenReturn({ setIsShowingLPCSuccessToast: vi.fn() }) + + when(vi.mocked(useRunCalibrationStatus)) + .calledWith(ROBOT_NAME, RUN_ID) + .thenReturn({ + complete: true, + }) + vi.mocked(getIsLabwareOffsetCodeSnippetsOn).mockReturnValue(false) + vi.mocked(SetupLabwareMap).mockReturnValue( +
    mock setup labware map
    + ) + vi.mocked(SetupLabwareList).mockReturnValue( +
    mock setup labware list
    + ) + vi.mocked(useLPCDisabledReason).mockReturnValue(null) + vi.mocked(useNotifyRunQuery).mockReturnValue({} as any) + vi.mocked(useHoverTooltip).mockReturnValue([{}, {}] as any) + vi.mocked(useRunHasStarted).mockReturnValue(false) + }) + + afterEach(() => { + vi.resetAllMocks() + }) + + it('should render the list view, clicking the toggle button will turn to map view', () => { + render() + screen.getByText('mock setup labware list') + screen.getByRole('button', { name: 'List View' }) + screen.getByRole('button', { name: 'Confirm placements' }) + const mapView = screen.getByRole('button', { name: 'Map View' }) + fireEvent.click(mapView) + screen.getByText('mock setup labware map') + }) + + it('disables the confirmation button if the run has already started', () => { + vi.mocked(useRunHasStarted).mockReturnValue(true) + + render() + + const btn = screen.getByRole('button', { + name: 'Confirm placements', + }) + + expect(btn).toBeDisabled() + }) +}) diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareList.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareList.test.tsx similarity index 90% rename from app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareList.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareList.test.tsx index 4228c517134..85068d39a1b 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareList.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareList.test.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { describe, it, beforeEach, vi } from 'vitest' import { screen } from '@testing-library/react' import { multiple_tipacks_with_tc } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { SetupLabwareList } from '../SetupLabwareList' import { LabwareListItem } from '../LabwareListItem' import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareMap.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareMap.test.tsx similarity index 93% rename from app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareMap.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareMap.test.tsx index 2f305f90dab..40f63cbc170 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareMap.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabwareMap.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { MemoryRouter } from 'react-router-dom' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' @@ -11,11 +11,13 @@ import { fixtureTiprack300ul, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' -import { getAttachedProtocolModuleMatches } from '../../../../ProtocolSetupModulesAndDeck/utils' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { LabwareInfoOverlay } from '../../LabwareInfoOverlay' -import { getLabwareRenderInfo } from '../../utils/getLabwareRenderInfo' +import { + getLabwareRenderInfo, + getAttachedProtocolModuleMatches, +} from '/app/transformations/analysis' import { SetupLabwareMap } from '../SetupLabwareMap' import type { @@ -40,11 +42,11 @@ vi.mock('@opentrons/shared-data', async importOriginal => { } }) -vi.mock('../../../../ProtocolSetupModulesAndDeck/utils') vi.mock('../../LabwareInfoOverlay') -vi.mock('../../utils/getLabwareRenderInfo') +vi.mock('/app/transformations/analysis/getLabwareRenderInfo') +vi.mock('/app/transformations/analysis/getAttachedProtocolModuleMatches') vi.mock('../../utils/getModuleTypesThatRequireExtraAttention') -vi.mock('../../../../RunTimeControl/hooks') +vi.mock('/app/organisms/RunTimeControl') vi.mock('../../../hooks') // TODO(jh 03-06-24): We need to rethink this test as we are testing components several layers deep across top-level imports. diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/index.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/index.tsx new file mode 100644 index 00000000000..687c1a739ab --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabware/index.tsx @@ -0,0 +1,94 @@ +import { useTranslation } from 'react-i18next' +import map from 'lodash/map' + +import { + JUSTIFY_CENTER, + Flex, + SPACING, + PrimaryButton, + DIRECTION_COLUMN, + Tooltip, + useHoverTooltip, +} from '@opentrons/components' + +import { useToggleGroup } from '/app/molecules/ToggleGroup/useToggleGroup' +import { getModuleTypesThatRequireExtraAttention } from '../utils/getModuleTypesThatRequireExtraAttention' +import { + useMostRecentCompletedAnalysis, + useModuleRenderInfoForProtocolById, + useRunHasStarted, +} from '/app/resources/runs' +import { useIsFlex } from '/app/redux-resources/robots' +import { useStoredProtocolAnalysis } from '/app/resources/analysis' +import { SetupLabwareMap } from './SetupLabwareMap' +import { SetupLabwareList } from './SetupLabwareList' + +interface SetupLabwareProps { + robotName: string + runId: string + labwareConfirmed: boolean + setLabwareConfirmed: (confirmed: boolean) => void +} + +export function SetupLabware(props: SetupLabwareProps): JSX.Element { + const { robotName, runId, labwareConfirmed, setLabwareConfirmed } = props + const { t } = useTranslation('protocol_setup') + const robotProtocolAnalysis = useMostRecentCompletedAnalysis(runId) + const storedProtocolAnalysis = useStoredProtocolAnalysis(runId) + const protocolAnalysis = robotProtocolAnalysis ?? storedProtocolAnalysis + const [selectedValue, toggleGroup] = useToggleGroup( + t('list_view') as string, + t('map_view') as string + ) + const isFlex = useIsFlex(robotName) + + const moduleRenderInfoById = useModuleRenderInfoForProtocolById(runId) + const moduleModels = map( + moduleRenderInfoById, + ({ moduleDef }) => moduleDef.model + ) + const moduleTypesThatRequireExtraAttention = getModuleTypesThatRequireExtraAttention( + moduleModels + ) + + // TODO(jh, 11-13-24): These disabled tooltips are used throughout setup flows. Let's consolidate them. + const [targetProps, tooltipProps] = useHoverTooltip() + const runHasStarted = useRunHasStarted(runId) + const tooltipText = runHasStarted ? t('protocol_run_started') : null + + return ( + <> + + {toggleGroup} + {selectedValue === t('list_view') ? ( + + ) : ( + + )} + + + { + setLabwareConfirmed(true) + }} + disabled={labwareConfirmed || runHasStarted} + {...targetProps} + > + {t('confirm_placements')} + + {tooltipText != null ? ( + {tooltipText} + ) : null} + + + ) +} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/CurrentOffsetsTable.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/CurrentOffsetsTable.tsx similarity index 88% rename from app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/CurrentOffsetsTable.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/CurrentOffsetsTable.tsx index f965488e607..e86166b0c9b 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/CurrentOffsetsTable.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/CurrentOffsetsTable.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import styled from 'styled-components' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' @@ -19,12 +18,12 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { getIsLabwareOffsetCodeSnippetsOn } from '../../../../redux/config' -import { LabwareOffsetTabs } from '../../../LabwareOffsetTabs' -import { OffsetVector } from '../../../../molecules/OffsetVector' -import { PythonLabwareOffsetSnippet } from '../../../../molecules/PythonLabwareOffsetSnippet' -import { getDisplayLocation } from '../../../LabwarePositionCheck/utils/getDisplayLocation' -import { getLabwareDefinitionsFromCommands } from '../../../../molecules/Command/utils/getLabwareDefinitionsFromCommands' +import { getIsLabwareOffsetCodeSnippetsOn } from '/app/redux/config' +import { LabwareOffsetTabs } from '/app/organisms/LabwareOffsetTabs' +import { OffsetVector } from '/app/molecules/OffsetVector' +import { PythonLabwareOffsetSnippet } from '/app/molecules/PythonLabwareOffsetSnippet' +import { getDisplayLocation } from '/app/organisms/LabwarePositionCheck/utils/getDisplayLocation' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' import type { LabwareOffset } from '@opentrons/api-client' import type { RunTimeCommand, diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/HowLPCWorksModal.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/HowLPCWorksModal.tsx similarity index 95% rename from app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/HowLPCWorksModal.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/HowLPCWorksModal.tsx index 55397d219eb..cdc882a38e5 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/HowLPCWorksModal.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/HowLPCWorksModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { @@ -13,7 +12,7 @@ import { TYPOGRAPHY, Modal, } from '@opentrons/components' -import { getTopPortalEl } from '../../../../App/portal' +import { getTopPortalEl } from '/app/App/portal' const OFFSET_DATA_HELP_ARTICLE = 'https://support.opentrons.com/s/article/How-Labware-Offsets-work-on-the-OT-2' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/CurrentOffsetsTable.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/CurrentOffsetsTable.test.tsx similarity index 90% rename from app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/CurrentOffsetsTable.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/CurrentOffsetsTable.test.tsx index 7eac060cca5..622d649bb04 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/CurrentOffsetsTable.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/CurrentOffsetsTable.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, beforeEach, vi, expect, afterEach } from 'vitest' import { screen } from '@testing-library/react' @@ -7,21 +7,21 @@ import { multiple_tipacks_with_tc, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' -import { getIsLabwareOffsetCodeSnippetsOn } from '../../../../../redux/config' -import { LabwarePositionCheck } from '../../../../LabwarePositionCheck' -import { useLPCDisabledReason } from '../../../hooks' -import { getLatestCurrentOffsets } from '../utils' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getIsLabwareOffsetCodeSnippetsOn } from '/app/redux/config' +import { LabwarePositionCheck } from '/app/organisms/LabwarePositionCheck' +import { useLPCDisabledReason } from '/app/resources/runs' +import { getLatestCurrentOffsets } from '/app/transformations/runs' import { CurrentOffsetsTable } from '../CurrentOffsetsTable' import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' import type { LabwareOffset } from '@opentrons/api-client' -vi.mock('../../../hooks') -vi.mock('../../../../LabwarePositionCheck') -vi.mock('../../../../../redux/config') -vi.mock('../utils') +vi.mock('/app/resources/runs') +vi.mock('/app/organisms/LabwarePositionCheck') +vi.mock('/app/redux/config') +vi.mock('/app/transformations/runs') vi.mock('@opentrons/shared-data', async importOriginal => { const actual = await importOriginal() diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/HowLPCWorksModal.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/HowLPCWorksModal.test.tsx similarity index 93% rename from app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/HowLPCWorksModal.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/HowLPCWorksModal.test.tsx index 187ff1d61de..5dd3d15db69 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/HowLPCWorksModal.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/HowLPCWorksModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { HowLPCWorksModal } from '../HowLPCWorksModal' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/SetupLabwarePositionCheck.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/SetupLabwarePositionCheck.test.tsx similarity index 87% rename from app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/SetupLabwarePositionCheck.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/SetupLabwarePositionCheck.test.tsx index 0c0150937ad..03ec9d990fb 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/SetupLabwarePositionCheck.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/SetupLabwarePositionCheck.test.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { when } from 'vitest-when' import { MemoryRouter } from 'react-router-dom' import { screen, fireEvent } from '@testing-library/react' @@ -10,32 +9,31 @@ import { } from '@opentrons/react-api-client' import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { useLPCSuccessToast } from '../../../hooks/useLPCSuccessToast' import { getModuleTypesThatRequireExtraAttention } from '../../utils/getModuleTypesThatRequireExtraAttention' -import { useLaunchLPC } from '../../../../LabwarePositionCheck/useLaunchLPC' -import { getIsLabwareOffsetCodeSnippetsOn } from '../../../../../redux/config' +import { useLaunchLPC } from '/app/organisms/LabwarePositionCheck/useLaunchLPC' +import { getIsLabwareOffsetCodeSnippetsOn } from '/app/redux/config' +import { SetupLabwarePositionCheck } from '..' import { - useLPCDisabledReason, + useNotifyRunQuery, useRunCalibrationStatus, useRunHasStarted, + useLPCDisabledReason, useUnmatchedModulesForProtocol, - useRobotType, -} from '../../../hooks' -import { SetupLabwarePositionCheck } from '..' -import { useNotifyRunQuery } from '../../../../../resources/runs' +} from '/app/resources/runs' +import { useRobotType } from '/app/redux-resources/robots' import type { Mock } from 'vitest' -vi.mock('../../../../LabwarePositionCheck/useLaunchLPC') +vi.mock('/app/organisms/LabwarePositionCheck/useLaunchLPC') vi.mock('../../utils/getModuleTypesThatRequireExtraAttention') -vi.mock('../../../../RunTimeControl/hooks') -vi.mock('../../../../../redux/config') -vi.mock('../../../hooks') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/redux/config') vi.mock('../../../hooks/useLPCSuccessToast') vi.mock('@opentrons/react-api-client') -vi.mock('../../../../../resources/runs') +vi.mock('/app/resources/runs') const DISABLED_REASON = 'MOCK_DISABLED_REASON' const ROBOT_NAME = 'otie' diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/index.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/index.tsx new file mode 100644 index 00000000000..637a4814936 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLabwarePositionCheck/index.tsx @@ -0,0 +1,165 @@ +import { useTranslation } from 'react-i18next' + +import { + ALIGN_CENTER, + BORDERS, + COLORS, + DIRECTION_COLUMN, + Flex, + JUSTIFY_CENTER, + LegacyStyledText, + PrimaryButton, + SecondaryButton, + SPACING, + TOOLTIP_LEFT, + Tooltip, + TYPOGRAPHY, + useHoverTooltip, +} from '@opentrons/components' +import { useProtocolQuery } from '@opentrons/react-api-client' + +import { useLPCSuccessToast } from '../../hooks/useLPCSuccessToast' +import { useStoredProtocolAnalysis } from '/app/resources/analysis' +import { CurrentOffsetsTable } from './CurrentOffsetsTable' +import { useLaunchLPC } from '/app/organisms/LabwarePositionCheck/useLaunchLPC' +import { getLatestCurrentOffsets } from '/app/transformations/runs' +import { + useNotifyRunQuery, + useMostRecentCompletedAnalysis, + useLPCDisabledReason, +} from '/app/resources/runs' +import { useRobotType } from '/app/redux-resources/robots' +import type { LabwareOffset } from '@opentrons/api-client' + +interface SetupLabwarePositionCheckProps { + offsetsConfirmed: boolean + setOffsetsConfirmed: (confirmed: boolean) => void + robotName: string + runId: string +} + +export function SetupLabwarePositionCheck( + props: SetupLabwarePositionCheckProps +): JSX.Element { + const { robotName, runId, setOffsetsConfirmed, offsetsConfirmed } = props + const { t, i18n } = useTranslation('protocol_setup') + + const robotType = useRobotType(robotName) + const { data: runRecord } = useNotifyRunQuery(runId, { staleTime: Infinity }) + const { data: protocolRecord } = useProtocolQuery( + runRecord?.data.protocolId ?? null, + { + staleTime: Infinity, + } + ) + const protocolName = + protocolRecord?.data.metadata.protocolName ?? + protocolRecord?.data.files[0].name ?? + '' + const currentOffsets = runRecord?.data?.labwareOffsets ?? [] + const sortedOffsets: LabwareOffset[] = + currentOffsets.length > 0 + ? currentOffsets + .map(offset => ({ + ...offset, + // convert into date to sort + createdAt: new Date(offset.createdAt), + })) + .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()) + .map(offset => ({ + ...offset, + // convert back into string + createdAt: offset.createdAt.toISOString(), + })) + : [] + const lpcDisabledReason = useLPCDisabledReason({ robotName, runId }) + const robotProtocolAnalysis = useMostRecentCompletedAnalysis(runId) + const storedProtocolAnalysis = useStoredProtocolAnalysis(runId) + const protocolData = robotProtocolAnalysis ?? storedProtocolAnalysis + const [runLPCTargetProps, runLPCTooltipProps] = useHoverTooltip({ + placement: TOOLTIP_LEFT, + }) + const [ + confirmOffsetsTargetProps, + confirmOffsetsTooltipProps, + ] = useHoverTooltip({ + placement: TOOLTIP_LEFT, + }) + + const { setIsShowingLPCSuccessToast } = useLPCSuccessToast() + + const { launchLPC, LPCWizard } = useLaunchLPC(runId, robotType, protocolName) + + const nonIdentityOffsets = getLatestCurrentOffsets(sortedOffsets) + + return ( + + {nonIdentityOffsets.length > 0 ? ( + + ) : ( + + + {i18n.format(t('no_labware_offset_data'), 'capitalize')} + + + )} + + { + setOffsetsConfirmed(true) + }} + id="LPC_setOffsetsConfirmed" + padding={`${SPACING.spacing8} ${SPACING.spacing16}`} + {...confirmOffsetsTargetProps} + disabled={ + offsetsConfirmed || + lpcDisabledReason !== null || + nonIdentityOffsets.length === 0 + } + > + {t('confirm_offsets')} + + {lpcDisabledReason != null || nonIdentityOffsets.length === 0 ? ( + + {lpcDisabledReason ?? + t('run_labware_position_check_to_get_offsets')} + + ) : null} + { + launchLPC() + setIsShowingLPCSuccessToast(false) + }} + id="LabwareSetup_checkLabwarePositionsButton" + {...runLPCTargetProps} + disabled={lpcDisabledReason !== null} + > + {t('run_labware_position_check')} + + {lpcDisabledReason !== null ? ( + + {lpcDisabledReason} + + ) : null} + + {LPCWizard} + + ) +} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/SetupLiquidsList.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/SetupLiquidsList.tsx similarity index 94% rename from app/src/organisms/Devices/ProtocolRun/SetupLiquids/SetupLiquidsList.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/SetupLiquidsList.tsx index 34aba75a1e5..c362101df85 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/SetupLiquidsList.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/SetupLiquidsList.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' @@ -6,8 +6,9 @@ import { ALIGN_CENTER, BORDERS, Box, - DeckInfoLabel, COLORS, + CURSOR_POINTER, + DeckInfoLabel, DIRECTION_COLUMN, DIRECTION_ROW, Flex, @@ -30,12 +31,15 @@ import { useTrackEvent, ANALYTICS_EXPAND_LIQUID_SETUP_ROW, ANALYTICS_OPEN_LIQUID_LABWARE_DETAIL_MODAL, -} from '../../../../redux/analytics' -import { useIsFlex } from '../../hooks' -import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { getLocationInfoNames } from '../utils/getLocationInfoNames' -import { LiquidsLabwareDetailsModal } from './LiquidsLabwareDetailsModal' -import { getTotalVolumePerLiquidId, getVolumePerWell } from './utils' +} from '/app/redux/analytics' +import { useIsFlex } from '/app/redux-resources/robots' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { getLocationInfoNames } from '/app/transformations/commands' +import { LiquidsLabwareDetailsModal } from '/app/organisms/LiquidsLabwareDetailsModal' +import { + getTotalVolumePerLiquidId, + getVolumePerWell, +} from '/app/transformations/analysis' import type { LabwareByLiquidId } from '@opentrons/shared-data' @@ -75,7 +79,6 @@ export function SetupLiquidsList(props: SetupLiquidsListProps): JSX.Element { (null) const commands = useMostRecentCompletedAnalysis(runId)?.commands @@ -149,14 +152,14 @@ export function LiquidsListItem(props: LiquidsListItemProps): JSX.Element { ${CARD_OUTLINE_BORDER_STYLE} &:hover { - cursor: pointer; + cursor: ${CURSOR_POINTER}; border: 1px solid ${COLORS.grey35}; } ` const LIQUID_CARD_ITEM_STYLE = css` border: 1px solid ${COLORS.white}; &:hover { - cursor: pointer; + cursor: ${CURSOR_POINTER}; border: 1px solid ${COLORS.grey30}; } ` diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/SetupLiquidsMap.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/SetupLiquidsMap.tsx new file mode 100644 index 00000000000..68ce1bdfc22 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/SetupLiquidsMap.tsx @@ -0,0 +1,201 @@ +import { useState, Fragment } from 'react' +import map from 'lodash/map' +import isEmpty from 'lodash/isEmpty' + +import { + ALIGN_CENTER, + BaseDeck, + DIRECTION_COLUMN, + Flex, + JUSTIFY_CENTER, + LabwareRender, + Box, +} from '@opentrons/components' +import { + FLEX_ROBOT_TYPE, + getDeckDefFromRobotType, + getSimplestDeckConfigForProtocol, + getTopLabwareInfo, + parseLabwareInfoByLiquidId, + parseLiquidsInLoadOrder, + THERMOCYCLER_MODULE_V1, +} from '@opentrons/shared-data' + +import { LabwareInfoOverlay } from '../LabwareInfoOverlay' +import { LiquidsLabwareDetailsModal } from '/app/organisms/LiquidsLabwareDetailsModal' +import { getStandardDeckViewLayerBlockList } from '/app/local-resources/deck_configuration' +import { + getProtocolModulesInfo, + getLabwareRenderInfo, + getWellFillFromLabwareId, +} from '/app/transformations/analysis' + +import type { + CompletedProtocolAnalysis, + ProtocolAnalysisOutput, + RunTimeCommand, + LoadLabwareRunTimeCommand, +} from '@opentrons/shared-data' + +interface SetupLiquidsMapProps { + runId: string + protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null +} + +export function SetupLiquidsMap( + props: SetupLiquidsMapProps +): JSX.Element | null { + const { runId, protocolAnalysis } = props + const [hoverLabwareId, setHoverLabwareId] = useState('') + const [liquidDetailsLabwareId, setLiquidDetailsLabwareId] = useState< + string | null + >(null) + + if (protocolAnalysis == null) return null + + const commands: RunTimeCommand[] = protocolAnalysis.commands + const loadLabwareCommands = commands?.filter( + (command): command is LoadLabwareRunTimeCommand => + command.commandType === 'loadLabware' + ) + + const liquids = parseLiquidsInLoadOrder( + protocolAnalysis.liquids != null ? protocolAnalysis.liquids : [], + protocolAnalysis.commands ?? [] + ) + const robotType = protocolAnalysis.robotType ?? FLEX_ROBOT_TYPE + const deckDef = getDeckDefFromRobotType(robotType) + const labwareRenderInfo = getLabwareRenderInfo(protocolAnalysis, deckDef) + const labwareByLiquidId = parseLabwareInfoByLiquidId( + protocolAnalysis.commands ?? [] + ) + const deckConfig = getSimplestDeckConfigForProtocol(protocolAnalysis) + const deckLayerBlocklist = getStandardDeckViewLayerBlockList(robotType) + + const protocolModulesInfo = getProtocolModulesInfo(protocolAnalysis, deckDef) + + const modulesOnDeck = protocolModulesInfo.map(module => { + const { + topLabwareId, + topLabwareDefinition, + topLabwareDisplayName, + } = getTopLabwareInfo(module.nestedLabwareId ?? '', loadLabwareCommands) + const nestedLabwareWellFill = getWellFillFromLabwareId( + topLabwareId ?? '', + liquids, + labwareByLiquidId + ) + const labwareHasLiquid = !isEmpty(nestedLabwareWellFill) + + return { + moduleModel: module.moduleDef.model, + moduleLocation: { slotName: module.slotName }, + innerProps: + module.moduleDef.model === THERMOCYCLER_MODULE_V1 + ? { lidMotorState: 'open' } + : {}, + + nestedLabwareDef: topLabwareDefinition, + nestedLabwareWellFill, + moduleChildren: + topLabwareDefinition != null && topLabwareId != null ? ( + { + setHoverLabwareId(topLabwareId) + }} + onMouseLeave={() => { + setHoverLabwareId('') + }} + onClick={() => { + if (labwareHasLiquid) { + setLiquidDetailsLabwareId(topLabwareId) + } + }} + cursor={labwareHasLiquid ? 'pointer' : ''} + > + + + ) : null, + } + }) + return ( + + + + {map(labwareRenderInfo, ({ x, y }, labwareId) => { + const { + topLabwareId, + topLabwareDefinition, + topLabwareDisplayName, + } = getTopLabwareInfo(labwareId, loadLabwareCommands) + const wellFill = getWellFillFromLabwareId( + topLabwareId ?? '', + liquids, + labwareByLiquidId + ) + const labwareHasLiquid = !isEmpty(wellFill) + return topLabwareDefinition != null ? ( + + { + setHoverLabwareId(topLabwareId) + }} + onMouseLeave={() => { + setHoverLabwareId('') + }} + onClick={() => { + if (labwareHasLiquid) { + setLiquidDetailsLabwareId(topLabwareId) + } + }} + cursor={labwareHasLiquid ? 'pointer' : ''} + > + + + + + ) : null + })} + + + {liquidDetailsLabwareId != null && ( + { + setLiquidDetailsLabwareId(null) + }} + /> + )} + + ) +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquids.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquids.test.tsx new file mode 100644 index 00000000000..736d5f5bc85 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquids.test.tsx @@ -0,0 +1,91 @@ +import type * as React from 'react' +import { describe, it, beforeEach, vi, expect } from 'vitest' +import { screen, fireEvent } from '@testing-library/react' + +import { useHoverTooltip } from '@opentrons/components' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { SetupLiquids } from '../index' +import { SetupLiquidsList } from '../SetupLiquidsList' +import { SetupLiquidsMap } from '../SetupLiquidsMap' +import { useRunHasStarted } from '/app/resources/runs' + +vi.mock('@opentrons/components', async () => { + const actual = await vi.importActual('@opentrons/components') + return { + ...actual, + useHoverTooltip: vi.fn(), + } +}) +vi.mock('../SetupLiquidsList') +vi.mock('../SetupLiquidsMap') +vi.mock('/app/resources/runs') + +describe('SetupLiquids', () => { + const render = ( + props: React.ComponentProps & { + startConfirmed?: boolean + } + ) => { + let isConfirmed = + props?.startConfirmed == null ? false : props.startConfirmed + const confirmFn = vi.fn((confirmed: boolean) => { + isConfirmed = confirmed + }) + return renderWithProviders( + , + { + i18nInstance: i18n, + } + ) + } + + let props: React.ComponentProps + beforeEach(() => { + vi.mocked(SetupLiquidsList).mockReturnValue( +
    Mock setup liquids list
    + ) + vi.mocked(SetupLiquidsMap).mockReturnValue( +
    Mock setup liquids map
    + ) + vi.mocked(useHoverTooltip).mockReturnValue([{}, {}] as any) + vi.mocked(useRunHasStarted).mockReturnValue(false) + }) + + it('renders the list and map view buttons and proceed button', () => { + render(props) + screen.getByRole('button', { name: 'List View' }) + screen.getByRole('button', { name: 'Map View' }) + screen.getByRole('button', { name: 'Confirm locations and volumes' }) + }) + it('renders the map view when you press that toggle button', () => { + render(props) + const mapViewButton = screen.getByRole('button', { name: 'Map View' }) + fireEvent.click(mapViewButton) + screen.getByText('Mock setup liquids map') + }) + it('renders the list view when you press that toggle button', () => { + render(props) + const mapViewButton = screen.getByRole('button', { name: 'List View' }) + fireEvent.click(mapViewButton) + screen.getByText('Mock setup liquids list') + }) + it('disables the confirmation button if the run has already started', () => { + vi.mocked(useRunHasStarted).mockReturnValue(true) + + render(props) + + const btn = screen.getByRole('button', { + name: 'Confirm locations and volumes', + }) + + expect(btn).toBeDisabled() + }) +}) diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsList.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsList.test.tsx similarity index 83% rename from app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsList.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsList.test.tsx index c8fccfe415f..60cb6a759a0 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsList.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsList.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { when } from 'vitest-when' import { describe, it, beforeEach, vi, expect } from 'vitest' @@ -8,22 +8,22 @@ import { parseLiquidsInLoadOrder, } from '@opentrons/shared-data' -import { - nestedTextMatcher, - renderWithProviders, -} from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' +import { nestedTextMatcher, renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { useTrackEvent, ANALYTICS_EXPAND_LIQUID_SETUP_ROW, ANALYTICS_OPEN_LIQUID_LABWARE_DETAIL_MODAL, -} from '../../../../../redux/analytics' -import { useIsFlex } from '../../../hooks' -import { getLocationInfoNames } from '../../utils/getLocationInfoNames' +} from '/app/redux/analytics' +import { useIsFlex } from '/app/redux-resources/robots' +import { getLocationInfoNames } from '/app/transformations/commands' import { SetupLiquidsList } from '../SetupLiquidsList' -import { getTotalVolumePerLiquidId, getVolumePerWell } from '../utils' -import { LiquidsLabwareDetailsModal } from '../LiquidsLabwareDetailsModal' -import { useNotifyRunQuery } from '../../../../../resources/runs' +import { + getTotalVolumePerLiquidId, + getVolumePerWell, +} from '/app/transformations/analysis' +import { LiquidsLabwareDetailsModal } from '/app/organisms/LiquidsLabwareDetailsModal' +import { useNotifyRunQuery } from '/app/resources/runs' import type { Mock } from 'vitest' import type * as SharedData from '@opentrons/shared-data' @@ -55,10 +55,10 @@ const MOCK_LABWARE_INFO_BY_LIQUID_ID = { ], } -vi.mock('../utils') -vi.mock('../../utils/getLocationInfoNames') -vi.mock('../../../hooks') -vi.mock('../LiquidsLabwareDetailsModal') +vi.mock('/app/transformations/analysis') +vi.mock('/app/transformations/commands') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/organisms/LiquidsLabwareDetailsModal') vi.mock('@opentrons/shared-data', async importOriginal => { const actualSharedData = await importOriginal() return { @@ -67,8 +67,8 @@ vi.mock('@opentrons/shared-data', async importOriginal => { parseLiquidsInLoadOrder: vi.fn(), } }) -vi.mock('../../../../../redux/analytics') -vi.mock('../../../../../resources/runs') +vi.mock('/app/redux/analytics') +vi.mock('/app/resources/runs') const render = (props: React.ComponentProps) => { return renderWithProviders(, { @@ -87,6 +87,7 @@ describe('SetupLiquidsList', () => { vi.mocked(getLocationInfoNames).mockReturnValue({ labwareName: 'mock labware name', slotName: '4', + labwareQuantity: 1, }) mockTrackEvent = vi.fn() vi.mocked(useTrackEvent).mockReturnValue(mockTrackEvent) diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsMap.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsMap.test.tsx similarity index 91% rename from app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsMap.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsMap.test.tsx index 098902f046f..023e73af09c 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsMap.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquidsMap.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { screen } from '@testing-library/react' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' @@ -19,16 +19,19 @@ import { simpleAnalysisFileFixture, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' -import { useAttachedModules } from '../../../hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useAttachedModules } from '/app/resources/modules' import { LabwareInfoOverlay } from '../../LabwareInfoOverlay' -import { getLabwareRenderInfo } from '../../utils/getLabwareRenderInfo' -import { getStandardDeckViewLayerBlockList } from '../../utils/getStandardDeckViewLayerBlockList' -import { getAttachedProtocolModuleMatches } from '../../../../ProtocolSetupModulesAndDeck/utils' -import { getProtocolModulesInfo } from '../../utils/getProtocolModulesInfo' -import { mockProtocolModuleInfo } from '../../../../ProtocolSetupLabware/__fixtures__' -import { mockFetchModulesSuccessActionPayloadModules } from '../../../../../redux/modules/__fixtures__' +import { getStandardDeckViewLayerBlockList } from '/app/local-resources/deck_configuration' +import { + getAttachedProtocolModuleMatches, + getProtocolModulesInfo, + getLabwareRenderInfo, +} from '/app/transformations/analysis' +/* eslint-disable-next-line opentrons/no-imports-across-applications */ +import { mockProtocolModuleInfo } from '/app/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__fixtures__' +import { mockFetchModulesSuccessActionPayloadModules } from '/app/redux/modules/__fixtures__' import { SetupLiquidsMap } from '../SetupLiquidsMap' @@ -49,12 +52,10 @@ vi.mock('@opentrons/components', async importOriginal => { vi.mock('@opentrons/components/src/hardware-sim/BaseDeck') vi.mock('../../LabwareInfoOverlay') -vi.mock('../../../hooks') -vi.mock('../utils') -vi.mock('../../utils/getLabwareRenderInfo') -vi.mock('../../../../ProtocolSetupModulesAndDeck/utils') -vi.mock('../../utils/getProtocolModulesInfo') -vi.mock('../../../../../resources/deck_configuration/utils') +vi.mock('/app/resources/modules') +vi.mock('/app/transformations/analysis') +vi.mock('/app/transformations/analysis') +vi.mock('/app/resources/deck_configuration/utils') vi.mock('@opentrons/shared-data', async importOriginal => { const actual = await importOriginal() return { diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/index.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/index.tsx new file mode 100644 index 00000000000..685d14a2ae5 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupLiquids/index.tsx @@ -0,0 +1,79 @@ +import { useTranslation } from 'react-i18next' +import { + JUSTIFY_CENTER, + Flex, + SPACING, + DIRECTION_COLUMN, + ALIGN_CENTER, + PrimaryButton, + useHoverTooltip, + Tooltip, +} from '@opentrons/components' +import { useToggleGroup } from '/app/molecules/ToggleGroup/useToggleGroup' +import { ANALYTICS_LIQUID_SETUP_VIEW_TOGGLE } from '/app/redux/analytics' +import { SetupLiquidsList } from './SetupLiquidsList' +import { SetupLiquidsMap } from './SetupLiquidsMap' +import { useRunHasStarted } from '/app/resources/runs' + +import type { + CompletedProtocolAnalysis, + ProtocolAnalysisOutput, +} from '@opentrons/shared-data' + +interface SetupLiquidsProps { + runId: string + protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null + isLiquidSetupConfirmed: boolean + setLiquidSetupConfirmed: (confirmed: boolean) => void + robotName: string +} + +export function SetupLiquids({ + runId, + protocolAnalysis, + isLiquidSetupConfirmed, + setLiquidSetupConfirmed, + robotName, +}: SetupLiquidsProps): JSX.Element { + const { t } = useTranslation('protocol_setup') + const [selectedValue, toggleGroup] = useToggleGroup( + t('list_view') as string, + t('map_view') as string, + ANALYTICS_LIQUID_SETUP_VIEW_TOGGLE + ) + + // TODO(jh, 11-13-24): These disabled tooltips are used throughout setup flows. Let's consolidate them. + const [targetProps, tooltipProps] = useHoverTooltip() + const runHasStarted = useRunHasStarted(runId) + const tooltipText = runHasStarted ? t('protocol_run_started') : null + + return ( + + {toggleGroup} + {selectedValue === t('list_view') ? ( + + ) : ( + + )} + + { + setLiquidSetupConfirmed(true) + }} + disabled={isLiquidSetupConfirmed || runHasStarted} + {...targetProps} + > + {t('confirm_locations_and_volumes')} + + {tooltipText != null ? ( + {tooltipText} + ) : null} + + + ) +} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/NotConfiguredModal.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/NotConfiguredModal.tsx similarity index 90% rename from app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/NotConfiguredModal.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/NotConfiguredModal.tsx index ad229c25b9e..e3966e8856a 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/NotConfiguredModal.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/NotConfiguredModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { useUpdateDeckConfigurationMutation } from '@opentrons/react-api-client' @@ -18,9 +17,9 @@ import { getCutoutDisplayName, getFixtureDisplayName, } from '@opentrons/shared-data' -import { TertiaryButton } from '../../../../atoms/buttons/TertiaryButton' -import { getTopPortalEl } from '../../../../App/portal' -import { useNotifyDeckConfigurationQuery } from '../../../../resources/deck_configuration' +import { TertiaryButton } from '/app/atoms/buttons/TertiaryButton' +import { getTopPortalEl } from '/app/App/portal' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' import type { CutoutFixtureId, CutoutId } from '@opentrons/shared-data' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/OT2MultipleModulesHelp.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/OT2MultipleModulesHelp.tsx similarity index 93% rename from app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/OT2MultipleModulesHelp.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/OT2MultipleModulesHelp.tsx index 531dc15526b..6533828d803 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/OT2MultipleModulesHelp.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/OT2MultipleModulesHelp.tsx @@ -1,8 +1,9 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { ALIGN_FLEX_END, + Banner, Box, DIRECTION_COLUMN, DIRECTION_ROW, @@ -15,9 +16,8 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { getTopPortalEl } from '../../../../App/portal' -import { Banner } from '../../../../atoms/Banner' -import multipleModuleHelp from '../../../../assets/images/Moam_modal_image.png' +import { getTopPortalEl } from '/app/App/portal' +import multipleModuleHelp from '/app/assets/images/Moam_modal_image.png' const HOW_TO_MULTIPLE_MODULES_HREF = 'https://support.opentrons.com/s/article/Using-modules-of-the-same-type-on-the-OT-2' @@ -27,7 +27,7 @@ export function OT2MultipleModulesHelp(): JSX.Element { const [ showMultipleModulesModal, setShowMultipleModulesModal, - ] = React.useState(false) + ] = useState(false) const onCloseClick = (): void => { setShowMultipleModulesModal(false) diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupFixtureList.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/SetupFixtureList.tsx similarity index 78% rename from app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupFixtureList.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/SetupFixtureList.tsx index 82ec086b983..2999895da91 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/SetupFixtureList.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/SetupFixtureList.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { css } from 'styled-components' import { useTranslation } from 'react-i18next' import { @@ -23,16 +23,19 @@ import { getCutoutDisplayName, getDeckDefFromRobotType, getFixtureDisplayName, + TC_MODULE_LOCATION_OT3, + THERMOCYCLER_V2_FRONT_FIXTURE, + THERMOCYCLER_V2_REAR_FIXTURE, } from '@opentrons/shared-data' -import { StatusLabel } from '../../../../atoms/StatusLabel' -import { TertiaryButton } from '../../../../atoms/buttons/TertiaryButton' -import { LocationConflictModal } from './LocationConflictModal' +import { StatusLabel } from '/app/atoms/StatusLabel' +import { TertiaryButton } from '/app/atoms/buttons/TertiaryButton' +import { LocationConflictModal } from '/app/organisms/LocationConflictModal' import { NotConfiguredModal } from './NotConfiguredModal' import { getFixtureImage } from './utils' -import { DeckFixtureSetupInstructionsModal } from '../../../DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal' +import { DeckFixtureSetupInstructionsModal } from '/app/organisms/DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal' import type { DeckDefinition } from '@opentrons/shared-data' -import type { CutoutConfigAndCompatibility } from '../../../../resources/deck_configuration/hooks' +import type { CutoutConfigAndCompatibility } from '/app/resources/deck_configuration/hooks' interface SetupFixtureListProps { deckConfigCompatibility: CutoutConfigAndCompatibility[] @@ -47,9 +50,32 @@ interface SetupFixtureListProps { export const SetupFixtureList = (props: SetupFixtureListProps): JSX.Element => { const { deckConfigCompatibility, robotName } = props const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE) + + const hasTwoLabwareThermocyclerConflicts = + deckConfigCompatibility.some( + ({ cutoutFixtureId, missingLabwareDisplayName }) => + cutoutFixtureId === THERMOCYCLER_V2_FRONT_FIXTURE && + missingLabwareDisplayName != null + ) && + deckConfigCompatibility.some( + ({ cutoutFixtureId, missingLabwareDisplayName }) => + cutoutFixtureId === THERMOCYCLER_V2_REAR_FIXTURE && + missingLabwareDisplayName != null + ) + + // if there are two labware conflicts with the thermocycler, don't show the conflict with the thermocycler rear fixture + const filteredDeckConfigCompatibility = deckConfigCompatibility.filter( + ({ cutoutFixtureId }) => { + return ( + !hasTwoLabwareThermocyclerConflicts || + !(cutoutFixtureId === THERMOCYCLER_V2_REAR_FIXTURE) + ) + } + ) + return ( <> - {deckConfigCompatibility.map(cutoutConfigAndCompatibility => { + {filteredDeckConfigCompatibility.map(cutoutConfigAndCompatibility => { // filter out all fixtures that only provide usb module addressable areas // (i.e. everything but MagBlockV1 and StagingAreaWithMagBlockV1) // as they're handled in the Modules Table @@ -89,6 +115,11 @@ export function FixtureListItem({ const isRequiredSingleSlotMissing = missingLabwareDisplayName != null const isConflictingFixtureConfigured = cutoutFixtureId != null && !SINGLE_SLOT_FIXTURES.includes(cutoutFixtureId) + + const isThermocyclerCurrentFixture = + cutoutFixtureId === THERMOCYCLER_V2_FRONT_FIXTURE || + cutoutFixtureId === THERMOCYCLER_V2_REAR_FIXTURE + let statusLabel if (!isCurrentFixtureCompatible) { statusLabel = ( @@ -117,16 +148,15 @@ export function FixtureListItem({ const [ showLocationConflictModal, setShowLocationConflictModal, - ] = React.useState(false) - const [ - showNotConfiguredModal, - setShowNotConfiguredModal, - ] = React.useState(false) + ] = useState(false) + const [showNotConfiguredModal, setShowNotConfiguredModal] = useState( + false + ) const [ showSetupInstructionsModal, setShowSetupInstructionsModal, - ] = React.useState(false) + ] = useState(false) return ( <> @@ -215,7 +245,9 @@ export function FixtureListItem({
    - {getCutoutDisplayName(cutoutId)} + {isThermocyclerCurrentFixture && isRequiredSingleSlotMissing + ? TC_MODULE_LOCATION_OT3 + : getCutoutDisplayName(cutoutId)} (false) + const [showModuleSetupModal, setShowModuleSetupModal] = useState< + string | null + >(null) const [ showLocationConflictModal, setShowLocationConflictModal, - ] = React.useState(false) + ] = useState(false) - const [showModuleWizard, setShowModuleWizard] = React.useState(false) + const [showModuleWizard, setShowModuleWizard] = useState(false) const { chainLiveCommands, isCommandMutationLoading } = useChainLiveCommands() const [ prepCommandErrorMessage, setPrepCommandErrorMessage, - ] = React.useState('') + ] = useState('') const handleCalibrateClick = (): void => { if (attachedModuleMatch != null) { @@ -202,7 +204,10 @@ export function ModulesListItem({ }) let subText: JSX.Element | null = null - if (moduleModel === HEATERSHAKER_MODULE_V1) { + if ( + moduleModel === HEATERSHAKER_MODULE_V1 || + moduleModel === ABSORBANCE_READER_V1 + ) { subText = ( { - setShowModuleSetupModal(true) + setShowModuleSetupModal(displayName) }} > @@ -259,6 +264,7 @@ export function ModulesListItem({ if ( isFlex && attachedModuleMatch != null && + attachedModuleMatch.moduleType !== ABSORBANCE_READER_TYPE && attachedModuleMatch.moduleOffset?.last_modified == null ) { renderModuleStatus = ( @@ -325,14 +331,13 @@ export function ModulesListItem({ padding={SPACING.spacing16} backgroundColor={COLORS.white} > - {showModuleSetupModal && heaterShakerModuleFromProtocol != null ? ( + {showModuleSetupModal != null ? ( { - setShowModuleSetupModal(false) + setShowModuleSetupModal(null) }} - moduleDisplayName={ - heaterShakerModuleFromProtocol.moduleDef.displayName - } + moduleDisplayName={showModuleSetupModal} + isAbsorbanceReader={moduleModel === ABSORBANCE_READER_V1} /> ) : null} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/UnMatchedModuleWarning.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/UnMatchedModuleWarning.tsx similarity index 86% rename from app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/UnMatchedModuleWarning.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/UnMatchedModuleWarning.tsx index fadb5c92758..6ebc96dc3c8 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/UnMatchedModuleWarning.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/UnMatchedModuleWarning.tsx @@ -1,17 +1,17 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { DIRECTION_COLUMN, + Banner, Flex, SPACING, LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { Banner } from '../../../../atoms/Banner' export const UnMatchedModuleWarning = (): JSX.Element | null => { const { t } = useTranslation('protocol_setup') - const [showBanner, setShowBanner] = React.useState(true) + const [showBanner, setShowBanner] = useState(true) if (!showBanner) return null return ( diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/NotConfiguredModal.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/NotConfiguredModal.test.tsx similarity index 84% rename from app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/NotConfiguredModal.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/NotConfiguredModal.test.tsx index bf5f7d14812..9f371faaa64 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/NotConfiguredModal.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/NotConfiguredModal.test.tsx @@ -1,19 +1,19 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, expect } from 'vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { TRASH_BIN_ADAPTER_FIXTURE } from '@opentrons/shared-data' import { useUpdateDeckConfigurationMutation } from '@opentrons/react-api-client' -import { i18n } from '../../../../../i18n' +import { i18n } from '/app/i18n' import { NotConfiguredModal } from '../NotConfiguredModal' -import { useNotifyDeckConfigurationQuery } from '../../../../../resources/deck_configuration' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' import type { UseQueryResult } from 'react-query' import type { DeckConfiguration } from '@opentrons/shared-data' vi.mock('@opentrons/react-api-client') -vi.mock('../../../../../resources/deck_configuration') +vi.mock('/app/resources/deck_configuration') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/OT2MultipleModulesHelp.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/OT2MultipleModulesHelp.test.tsx similarity index 90% rename from app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/OT2MultipleModulesHelp.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/OT2MultipleModulesHelp.test.tsx index 984dc1e57e5..27efa17e029 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/OT2MultipleModulesHelp.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/OT2MultipleModulesHelp.test.tsx @@ -1,13 +1,12 @@ -import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' import { describe, it, beforeEach, vi, expect } from 'vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' -import { getIsOnDevice } from '../../../../../redux/config' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getIsOnDevice } from '/app/redux/config' import { OT2MultipleModulesHelp } from '../OT2MultipleModulesHelp' -vi.mock('../../../../../redux/config') +vi.mock('/app/redux/config') const render = () => renderWithProviders(, { diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupFixtureList.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupFixtureList.test.tsx similarity index 87% rename from app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupFixtureList.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupFixtureList.test.tsx index 3724d3fc41c..dd0236ac96d 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupFixtureList.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupFixtureList.test.tsx @@ -1,7 +1,7 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi } from 'vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { MAGNETIC_BLOCK_D3_ADDRESSABLE_AREA, MAGNETIC_BLOCK_V1_FIXTURE, @@ -10,19 +10,19 @@ import { STAGING_AREA_SLOT_WITH_WASTE_CHUTE_RIGHT_ADAPTER_NO_COVER_FIXTURE, TRASH_BIN_ADAPTER_FIXTURE, } from '@opentrons/shared-data' -import { i18n } from '../../../../../i18n' +import { i18n } from '/app/i18n' import { SetupFixtureList } from '../SetupFixtureList' import { NotConfiguredModal } from '../NotConfiguredModal' -import { LocationConflictModal } from '../LocationConflictModal' -import { DeckFixtureSetupInstructionsModal } from '../../../../DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal' +import { LocationConflictModal } from '/app/organisms/LocationConflictModal' +import { DeckFixtureSetupInstructionsModal } from '/app/organisms/DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal' -import type { CutoutConfigAndCompatibility } from '../../../../../resources/deck_configuration/hooks' +import type { CutoutConfigAndCompatibility } from '/app/resources/deck_configuration/hooks' -vi.mock('../../../../../resources/deck_configuration/hooks') -vi.mock('../LocationConflictModal') +vi.mock('/app/resources/deck_configuration/hooks') +vi.mock('/app/organisms/LocationConflictModal') vi.mock('../NotConfiguredModal') vi.mock( - '../../../../DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal' + '/app/organisms/DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal' ) const mockDeckConfigCompatibility: CutoutConfigAndCompatibility[] = [ diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesAndDeck.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesAndDeck.test.tsx similarity index 91% rename from app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesAndDeck.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesAndDeck.test.tsx index 818b46e0c6b..21926d3c823 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesAndDeck.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesAndDeck.test.tsx @@ -1,31 +1,32 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { describe, it, beforeEach, expect, vi } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' -import { mockTemperatureModule } from '../../../../../redux/modules/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockTemperatureModule } from '/app/redux/modules/__fixtures__' import { getIsFixtureMismatch, getRequiredDeckConfig, -} from '../../../../../resources/deck_configuration/utils' +} from '/app/resources/deck_configuration/utils' import { - useIsFlex, useRunHasStarted, - useUnmatchedModulesForProtocol, useModuleCalibrationStatus, -} from '../../../hooks' + useUnmatchedModulesForProtocol, +} from '/app/resources/runs' +import { useIsFlex } from '/app/redux-resources/robots' import { SetupModuleAndDeck } from '../index' import { SetupModulesList } from '../SetupModulesList' import { SetupModulesMap } from '../SetupModulesMap' import { SetupFixtureList } from '../SetupFixtureList' -vi.mock('../../../hooks') +vi.mock('/app/redux-resources/robots') vi.mock('../SetupModulesList') vi.mock('../SetupModulesMap') vi.mock('../SetupFixtureList') -vi.mock('../../../../../redux/config') -vi.mock('../../../../../resources/deck_configuration/utils') +vi.mock('/app/redux/config') +vi.mock('/app/resources/deck_configuration/utils') +vi.mock('/app/resources/runs') const MOCK_ROBOT_NAME = 'otie' const MOCK_RUN_ID = '1' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesList.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesList.test.tsx similarity index 93% rename from app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesList.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesList.test.tsx index ca35acee669..e62d600cea6 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesList.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesList.test.tsx @@ -1,45 +1,44 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { fireEvent, screen, waitFor } from '@testing-library/react' import { describe, it, beforeEach, expect, vi } from 'vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { FLEX_ROBOT_TYPE, OT2_ROBOT_TYPE } from '@opentrons/shared-data' -import { i18n } from '../../../../../i18n' +import { i18n } from '/app/i18n' import { mockMagneticModule as mockMagneticModuleFixture, mockHeaterShaker, -} from '../../../../../redux/modules/__fixtures__/index' +} from '/app/redux/modules/__fixtures__/index' import { mockMagneticModuleGen2, mockThermocycler, -} from '../../../../../redux/modules/__fixtures__' -import { useChainLiveCommands } from '../../../../../resources/runs' -import { ModuleSetupModal } from '../../../../ModuleCard/ModuleSetupModal' -import { ModuleWizardFlows } from '../../../../ModuleWizardFlows' +} from '/app/redux/modules/__fixtures__' +import { useRobot, useIsFlex } from '/app/redux-resources/robots' import { - useIsFlex, + useChainLiveCommands, + useRunCalibrationStatus, useModuleRenderInfoForProtocolById, useUnmatchedModulesForProtocol, - useRunCalibrationStatus, - useRobot, -} from '../../../hooks' +} from '/app/resources/runs' +import { ModuleSetupModal } from '/app/organisms/ModuleCard/ModuleSetupModal' +import { ModuleWizardFlows } from '/app/organisms/ModuleWizardFlows' import { OT2MultipleModulesHelp } from '../OT2MultipleModulesHelp' import { UnMatchedModuleWarning } from '../UnMatchedModuleWarning' import { SetupModulesList } from '../SetupModulesList' -import { LocationConflictModal } from '../LocationConflictModal' +import { LocationConflictModal } from '/app/organisms/LocationConflictModal' import type { ModuleModel, ModuleType } from '@opentrons/shared-data' -import type { DiscoveredRobot } from '../../../../../redux/discovery/types' +import type { DiscoveredRobot } from '/app/redux/discovery/types' vi.mock('@opentrons/react-api-client') -vi.mock('../../../hooks') -vi.mock('../LocationConflictModal') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/organisms/LocationConflictModal') vi.mock('../UnMatchedModuleWarning') -vi.mock('../../../../ModuleCard/ModuleSetupModal') -vi.mock('../../../../ModuleWizardFlows') +vi.mock('/app/organisms/ModuleCard/ModuleSetupModal') +vi.mock('/app/organisms/ModuleWizardFlows') vi.mock('../OT2MultipleModulesHelp') -vi.mock('../../../../../resources/runs') -vi.mock('../../../../../redux/config') +vi.mock('/app/resources/runs') +vi.mock('/app/redux/config') const ROBOT_NAME = 'otie' const RUN_ID = '1' @@ -377,6 +376,7 @@ describe('SetupModulesList', () => { id: 'heatershaker_id', model: 'heaterShakerModuleV1', moduleType: 'heaterShakerModuleType', + displayName: 'mockHeaterShakerName', serialNumber: 'jkl123', hardwareRevision: 'heatershaker_v4.0', firmwareVersion: 'v2.0.0', diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesMap.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesMap.test.tsx similarity index 93% rename from app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesMap.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesMap.test.tsx index e08ff19f415..9d9449283dc 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesMap.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/SetupModulesMap.test.tsx @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { MemoryRouter } from 'react-router-dom' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' @@ -8,17 +8,16 @@ import { screen } from '@testing-library/react' import { OT2_ROBOT_TYPE } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockThermocycler as mockThermocyclerFixture, mockMagneticModule as mockMagneticModuleFixture, -} from '../../../../../redux/modules/__fixtures__/index' -import { useMostRecentCompletedAnalysis } from '../../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { getAttachedProtocolModuleMatches } from '../../../../ProtocolSetupModulesAndDeck/utils' -import { ModuleInfo } from '../../../ModuleInfo' +} from '/app/redux/modules/__fixtures__/index' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { ModuleInfo } from '/app/molecules/ModuleInfo' import { SetupModulesMap } from '../SetupModulesMap' - +import { getAttachedProtocolModuleMatches } from '/app/transformations/analysis' import type { CompletedProtocolAnalysis, ModuleModel, @@ -43,10 +42,10 @@ vi.mock('@opentrons/shared-data', async importOriginal => { inferModuleOrientationFromXCoordinate: vi.fn(), } }) -vi.mock('../../../../LabwarePositionCheck/useMostRecentCompletedAnalysis') -vi.mock('../../../../ProtocolSetupModulesAndDeck/utils') -vi.mock('../../../ModuleInfo') -vi.mock('../../../hooks') +vi.mock('/app/resources/runs/useMostRecentCompletedAnalysis') +vi.mock('/app/transformations/analysis') +vi.mock('/app/molecules/ModuleInfo') +vi.mock('/app/resources/modules') const render = (props: React.ComponentProps) => { return renderWithProviders( diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/UnMatchedModuleWarning.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/UnMatchedModuleWarning.test.tsx similarity index 87% rename from app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/UnMatchedModuleWarning.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/UnMatchedModuleWarning.test.tsx index 2a16a69fb2f..eb73c0955bf 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/UnMatchedModuleWarning.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/UnMatchedModuleWarning.test.tsx @@ -1,10 +1,9 @@ -import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { UnMatchedModuleWarning } from '../UnMatchedModuleWarning' const render = () => { diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/utils.test.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/utils.test.ts new file mode 100644 index 00000000000..98e3dade58a --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/utils.test.ts @@ -0,0 +1,25 @@ +import { describe, it, expect } from 'vitest' +import { getFixtureImage } from '../utils' + +describe('getFixtureImage', () => { + it('should render the staging area image', () => { + const result = getFixtureImage('stagingAreaRightSlot') + expect(result).toEqual('/app/src/assets/images/staging_area_slot.png') + }) + it('should render the waste chute image', () => { + const result = getFixtureImage('wasteChuteRightAdapterNoCover') + expect(result).toEqual('/app/src/assets/images/waste_chute.png') + }) + it('should render the waste chute staging area image', () => { + const result = getFixtureImage( + 'stagingAreaSlotWithWasteChuteRightAdapterCovered' + ) + expect(result).toEqual( + '/app/src/assets/images/waste_chute_with_staging_area.png' + ) + }) + it('should render the trash bin image', () => { + const result = getFixtureImage('trashBinAdapter') + expect(result).toEqual('/app/src/assets/images/flex_trash_bin.png') + }) +}) diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx new file mode 100644 index 00000000000..3d42a645853 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx @@ -0,0 +1,158 @@ +import { useTranslation } from 'react-i18next' + +import { + DIRECTION_COLUMN, + DIRECTION_ROW, + Flex, + JUSTIFY_CENTER, + JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, + PrimaryButton, + SPACING, + Tooltip, + TYPOGRAPHY, + useHoverTooltip, +} from '@opentrons/components' + +import { useToggleGroup } from '/app/molecules/ToggleGroup/useToggleGroup' +import { useDeckConfigurationCompatibility } from '/app/resources/deck_configuration/hooks' +import { + getIsFixtureMismatch, + getRequiredDeckConfig, +} from '/app/resources/deck_configuration/utils' +import { useRobotType } from '/app/redux-resources/robots' +import { + useRunHasStarted, + useUnmatchedModulesForProtocol, + useModuleCalibrationStatus, +} from '/app/resources/runs' +import { SetupModulesMap } from './SetupModulesMap' +import { SetupModulesList } from './SetupModulesList' +import { SetupFixtureList } from './SetupFixtureList' + +import type { + CompletedProtocolAnalysis, + ProtocolAnalysisOutput, +} from '@opentrons/shared-data' + +interface SetupModuleAndDeckProps { + expandLabwarePositionCheckStep: () => void + robotName: string + runId: string + hasModules: boolean + protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null +} + +export const SetupModuleAndDeck = ({ + expandLabwarePositionCheckStep, + robotName, + runId, + hasModules, + protocolAnalysis, +}: SetupModuleAndDeckProps): JSX.Element => { + const { t, i18n } = useTranslation('protocol_setup') + const [selectedValue, toggleGroup] = useToggleGroup( + t('list_view') as string, + t('map_view') as string + ) + + const robotType = useRobotType(robotName) + const { missingModuleIds } = useUnmatchedModulesForProtocol(robotName, runId) + const runHasStarted = useRunHasStarted(runId) + const [targetProps, tooltipProps] = useHoverTooltip() + + const moduleCalibrationStatus = useModuleCalibrationStatus(robotName, runId) + const deckConfigCompatibility = useDeckConfigurationCompatibility( + robotType, + protocolAnalysis + ) + + const isFixtureMismatch = getIsFixtureMismatch(deckConfigCompatibility) + + const requiredDeckConfigCompatibility = getRequiredDeckConfig( + deckConfigCompatibility + ) + + return ( + <> + + {toggleGroup} + {selectedValue === t('list_view') ? ( + <> + + + {i18n.format(t('deck_hardware'), 'capitalize')} + + + {t('location')} + + + {t('status')} + + + + {hasModules ? ( + + ) : null} + {requiredDeckConfigCompatibility.length > 0 ? ( + + ) : null} + + + ) : ( + + )} + + + 0 || + isFixtureMismatch || + runHasStarted || + !moduleCalibrationStatus.complete + } + onClick={expandLabwarePositionCheckStep} + id="ModuleSetup_proceedToLabwarePositionCheck" + padding={`${SPACING.spacing8} ${SPACING.spacing16}`} + {...targetProps} + > + {t('proceed_to_labware_position_check')} + + + {missingModuleIds.length > 0 || + runHasStarted || + !moduleCalibrationStatus.complete ? ( + + {runHasStarted + ? t('protocol_run_started') + : missingModuleIds.length > 0 + ? t('plug_in_required_module', { count: missingModuleIds.length }) + : t('calibrate_module_failure_reason')} + + ) : null} + + ) +} diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/utils.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/utils.ts new file mode 100644 index 00000000000..a6b47b7ff9d --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupModuleAndDeck/utils.ts @@ -0,0 +1,52 @@ +import { + HEATERSHAKER_MODULE_V1_FIXTURE, + MAGNETIC_BLOCK_V1_FIXTURE, + STAGING_AREA_RIGHT_SLOT_FIXTURE, + STAGING_AREA_SLOT_WITH_MAGNETIC_BLOCK_V1_FIXTURE, + TEMPERATURE_MODULE_V2_FIXTURE, + THERMOCYCLER_V2_FRONT_FIXTURE, + THERMOCYCLER_V2_REAR_FIXTURE, + TRASH_BIN_ADAPTER_FIXTURE, + WASTE_CHUTE_ONLY_FIXTURES, + WASTE_CHUTE_STAGING_AREA_FIXTURES, +} from '@opentrons/shared-data' + +import temperatureModule from '/app/assets/images/temp_deck_gen_2_transparent.png' +import heaterShakerModule from '/app/assets/images/heater_shaker_module_transparent.png' +import thermoModuleGen2 from '/app/assets/images/thermocycler_gen_2_closed.png' +import magneticBlockGen1 from '/app/assets/images/magnetic_block_gen_1.png' +import stagingAreaMagneticBlockGen1 from '/app/assets/images/staging_area_magnetic_block_gen_1.png' +import trashBin from '/app/assets/images/flex_trash_bin.png' +import stagingArea from '/app/assets/images/staging_area_slot.png' +import wasteChute from '/app/assets/images/waste_chute.png' +import wasteChuteStagingArea from '/app/assets/images/waste_chute_with_staging_area.png' + +import type { CutoutFixtureId } from '@opentrons/shared-data' + +export function getFixtureImage(cutoutFixtureId: CutoutFixtureId): string { + if (cutoutFixtureId === STAGING_AREA_RIGHT_SLOT_FIXTURE) { + return stagingArea + } else if (WASTE_CHUTE_ONLY_FIXTURES.includes(cutoutFixtureId)) { + return wasteChute + } else if (WASTE_CHUTE_STAGING_AREA_FIXTURES.includes(cutoutFixtureId)) { + return wasteChuteStagingArea + } else if (cutoutFixtureId === TRASH_BIN_ADAPTER_FIXTURE) { + return trashBin + } else if (cutoutFixtureId === THERMOCYCLER_V2_REAR_FIXTURE) { + return thermoModuleGen2 + } else if (cutoutFixtureId === THERMOCYCLER_V2_FRONT_FIXTURE) { + return thermoModuleGen2 + } else if (cutoutFixtureId === HEATERSHAKER_MODULE_V1_FIXTURE) { + return heaterShakerModule + } else if (cutoutFixtureId === TEMPERATURE_MODULE_V2_FIXTURE) { + return temperatureModule + } else if (cutoutFixtureId === MAGNETIC_BLOCK_V1_FIXTURE) { + return magneticBlockGen1 + } else if ( + cutoutFixtureId === STAGING_AREA_SLOT_WITH_MAGNETIC_BLOCK_V1_FIXTURE + ) { + return stagingAreaMagneticBlockGen1 + } else { + return 'Error: unknown fixture' + } +} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupPipetteCalibrationItem.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupPipetteCalibrationItem.tsx similarity index 92% rename from app/src/organisms/Devices/ProtocolRun/SetupPipetteCalibrationItem.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupPipetteCalibrationItem.tsx index 8200519224e..f42066c77d0 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupPipetteCalibrationItem.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupPipetteCalibrationItem.tsx @@ -1,7 +1,7 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { Link as RRDLink } from 'react-router-dom' import { + Banner, Box, Flex, LegacyTooltip, @@ -18,14 +18,12 @@ import { JUSTIFY_FLEX_END, WRAP, } from '@opentrons/components' -import { TertiaryButton } from '../../../atoms/buttons' -import { Banner } from '../../../atoms/Banner' -import * as PipetteConstants from '../../../redux/pipettes/constants' +import { TertiaryButton } from '/app/atoms/buttons' +import * as PipetteConstants from '/app/redux/pipettes/constants' import { useDeckCalibrationData } from '../hooks' import { SetupCalibrationItem } from './SetupCalibrationItem' -import type { Mount } from '../../../redux/pipettes/types' -import type { PipetteInfo } from '../hooks' +import type { Mount, PipetteInfo } from '/app/redux/pipettes' const inexactPipetteSupportArticle = 'https://support.opentrons.com/s/article/GEN2-pipette-compatibility' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupRobotCalibration.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupRobotCalibration.tsx similarity index 89% rename from app/src/organisms/Devices/ProtocolRun/SetupRobotCalibration.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupRobotCalibration.tsx index ce97f0255c5..5202419e290 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupRobotCalibration.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupRobotCalibration.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -15,16 +14,16 @@ import { useTrackEvent, ANALYTICS_PROCEED_TO_MODULE_SETUP_STEP, ANALYTICS_PROCEED_TO_LABWARE_SETUP_STEP, -} from '../../../redux/analytics' -import { useIsFlex, useRunHasStarted } from '../hooks' +} from '/app/redux/analytics' import { SetupDeckCalibration } from './SetupDeckCalibration' import { SetupInstrumentCalibration } from './SetupInstrumentCalibration' import { SetupTipLengthCalibration } from './SetupTipLengthCalibration' -import { useRunStatus } from '../../RunTimeControl/hooks' +import { useRunStatus, useRunHasStarted } from '/app/resources/runs' import { RUN_STATUS_STOPPED } from '@opentrons/api-client' +import { useIsFlex } from '/app/redux-resources/robots' -import type { ProtocolCalibrationStatus } from '../../../redux/calibration/types' -import type { StepKey } from './ProtocolRunSetup' +import type { ProtocolCalibrationStatus } from '/app/redux/calibration/types' +import type { StepKey } from '/app/redux/protocol-runs' interface SetupRobotCalibrationProps { robotName: string diff --git a/app/src/organisms/Devices/ProtocolRun/SetupStep.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupStep.tsx similarity index 85% rename from app/src/organisms/Devices/ProtocolRun/SetupStep.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupStep.tsx index 9ddb1daf9af..25f2baf1d64 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupStep.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupStep.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { @@ -8,9 +8,11 @@ import { COLORS, DIRECTION_COLUMN, DIRECTION_ROW, + DISPLAY_GRID, Flex, Icon, JUSTIFY_SPACE_BETWEEN, + OVERFLOW_HIDDEN, SPACING, StyledText, TYPOGRAPHY, @@ -32,16 +34,14 @@ interface SetupStepProps { } const EXPANDED_STYLE = css` - transition: max-height 300ms ease-in, visibility 400ms ease; + transition: grid-template-rows 300ms ease-in, visibility 400ms ease; + grid-template-rows: 1fr; visibility: visible; - max-height: 180vh; - overflow: hidden; ` const COLLAPSED_STYLE = css` - transition: max-height 500ms ease-out, visibility 600ms ease; + transition: grid-template-rows 500ms ease-out, visibility 600ms ease; + grid-template-rows: 0fr; visibility: hidden; - max-height: 0vh; - overflow: hidden; ` const ACCORDION_STYLE = css` border-radius: 50%; @@ -104,7 +104,12 @@ export function SetupStep({ - {children} + + {children} + ) } diff --git a/app/src/organisms/Devices/ProtocolRun/SetupTipLengthCalibration.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupTipLengthCalibration.tsx similarity index 91% rename from app/src/organisms/Devices/ProtocolRun/SetupTipLengthCalibration.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupTipLengthCalibration.tsx index faeeb9eda9e..c01b79c3a34 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupTipLengthCalibration.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupTipLengthCalibration.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { Fragment } from 'react' import { useTranslation } from 'react-i18next' import { @@ -10,8 +10,8 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import * as PipetteConstants from '../../../redux/pipettes/constants' -import { useRunPipetteInfoByMount } from '../hooks' +import * as PipetteConstants from '/app/redux/pipettes/constants' +import { useRunPipetteInfoByMount } from '/app/resources/runs' import { SetupCalibrationItem } from './SetupCalibrationItem' import { SetupTipLengthCalibrationButton } from './SetupTipLengthCalibrationButton' interface SetupTipLengthCalibrationProps { @@ -41,7 +41,7 @@ export function SetupTipLengthCalibration({ return null } else { return ( - + {pipetteInfo.tipRacksForPipette.map((tipRackInfo, index) => { const pipetteNotAttached = pipetteInfo.requestedPipetteMatch === 'incompatible' @@ -71,7 +71,7 @@ export function SetupTipLengthCalibration({ /> ) })} - + ) } })} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupTipLengthCalibrationButton.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupTipLengthCalibrationButton.tsx similarity index 90% rename from app/src/organisms/Devices/ProtocolRun/SetupTipLengthCalibrationButton.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/SetupTipLengthCalibrationButton.tsx index c9d5575965f..bc4ad701f72 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupTipLengthCalibrationButton.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/SetupTipLengthCalibrationButton.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -20,13 +19,12 @@ import { } from '@opentrons/react-api-client' import { getLabwareDefURI } from '@opentrons/shared-data' -import { TertiaryButton } from '../../../atoms/buttons' -import { - useAttachedPipettes, - useDeckCalibrationData, - useRunHasStarted, -} from '../hooks' -import { useDashboardCalibrateTipLength } from '../../../pages/Devices/CalibrationDashboard/hooks/useDashboardCalibrateTipLength' +import { TertiaryButton } from '/app/atoms/buttons' +import { useAttachedPipettes } from '/app/resources/instruments' +import { useRunHasStarted } from '/app/resources/runs' +import { useDeckCalibrationData } from '../hooks' +// eslint-disable-next-line opentrons/no-imports-up-the-tree-of-life +import { useDashboardCalibrateTipLength } from '/app/pages/Desktop/Devices/CalibrationDashboard/hooks/useDashboardCalibrateTipLength' import type { Mount } from '@opentrons/components' import type { LabwareDefinition2 } from '@opentrons/shared-data' diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/BackToTopButton.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/BackToTopButton.test.tsx similarity index 85% rename from app/src/organisms/Devices/ProtocolRun/__tests__/BackToTopButton.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/BackToTopButton.test.tsx index 65c5f106f1a..0293c0ae5b2 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/BackToTopButton.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/BackToTopButton.test.tsx @@ -1,23 +1,22 @@ -import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { when } from 'vitest-when' import { MemoryRouter } from 'react-router-dom' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' -import { renderWithProviders } from '../../../../__testing-utils__' -import { mockConnectableRobot } from '../../../../redux/discovery/__fixtures__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' +import { i18n } from '/app/i18n' import { useTrackEvent, ANALYTICS_PROTOCOL_PROCEED_TO_RUN, -} from '../../../../redux/analytics' +} from '/app/redux/analytics' import { BackToTopButton } from '../BackToTopButton' -import { useRobot } from '../../hooks' +import { useRobot } from '/app/redux-resources/robots' import type { Mock } from 'vitest' -vi.mock('../../../../redux/analytics') -vi.mock('../../hooks') +vi.mock('/app/redux/analytics') +vi.mock('/app/redux-resources/robots') const ROBOT_NAME = 'otie' const RUN_ID = '1' diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/EmptySetupStep.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/EmptySetupStep.test.tsx similarity index 83% rename from app/src/organisms/Devices/ProtocolRun/__tests__/EmptySetupStep.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/EmptySetupStep.test.tsx index 3c84e76468c..673d8b4806c 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/EmptySetupStep.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/EmptySetupStep.test.tsx @@ -1,9 +1,9 @@ -import React from 'react' +import type * as React from 'react' import { describe, it, beforeEach } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { EmptySetupStep } from '../EmptySetupStep' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/LabwareInfoOverlay.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/LabwareInfoOverlay.test.tsx similarity index 88% rename from app/src/organisms/Devices/ProtocolRun/__tests__/LabwareInfoOverlay.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/LabwareInfoOverlay.test.tsx index 84a5edaca1b..0198aa9e448 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/LabwareInfoOverlay.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/LabwareInfoOverlay.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { screen } from '@testing-library/react' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' @@ -6,15 +6,12 @@ import { getLabwareDisplayName, fixtureTiprack300ul, } from '@opentrons/shared-data' -import { - nestedTextMatcher, - renderWithProviders, -} from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { useCurrentRun } from '../../../ProtocolUpload/hooks' -import { getLabwareLocation } from '../utils/getLabwareLocation' +import { nestedTextMatcher, renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useCurrentRun } from '/app/resources/runs' +import { getLabwareLocation } from '/app/transformations/commands' import { LabwareInfoOverlay } from '../LabwareInfoOverlay' -import { getLabwareDefinitionUri } from '../utils/getLabwareDefinitionUri' +import { getLabwareDefinitionUri } from '/app/transformations/protocols' import { useLabwareOffsetForLabware } from '../useLabwareOffsetForLabware' import type { LabwareDefinition2, @@ -22,10 +19,10 @@ import type { LoadedLabware, } from '@opentrons/shared-data' -vi.mock('../../../ProtocolUpload/hooks') -vi.mock('../utils/getLabwareLocation') +vi.mock('/app/resources/runs') +vi.mock('/app/transformations/commands') vi.mock('../../hooks') -vi.mock('../utils/getLabwareDefinitionUri') +vi.mock('/app/transformations/protocols') vi.mock('../useLabwareOffsetForLabware') vi.mock('@opentrons/shared-data', async importOriginal => { diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunModuleControls.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/ProtocolRunModuleControls.test.tsx similarity index 93% rename from app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunModuleControls.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/ProtocolRunModuleControls.test.tsx index 5de2516791b..5dd1507f893 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunModuleControls.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/ProtocolRunModuleControls.test.tsx @@ -1,25 +1,25 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { describe, it, beforeEach, vi, afterEach } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { useInstrumentsQuery } from '@opentrons/react-api-client' import { ProtocolRunModuleControls } from '../ProtocolRunModuleControls' -import { ModuleCard } from '../../../ModuleCard' -import { useModuleRenderInfoForProtocolById } from '../../hooks' +import { ModuleCard } from '/app/organisms/ModuleCard' +import { useModuleRenderInfoForProtocolById } from '/app/resources/runs' import { mockMagneticModuleGen2, mockTemperatureModuleGen2, mockThermocycler, mockHeaterShaker, -} from '../../../../redux/modules/__fixtures__' +} from '/app/redux/modules/__fixtures__' import type { ModuleModel, ModuleType } from '@opentrons/shared-data' vi.mock('@opentrons/react-api-client') -vi.mock('../../../ModuleCard') -vi.mock('../../hooks') +vi.mock('/app/organisms/ModuleCard') +vi.mock('/app/resources/runs') const RUN_ID = 'test123' const mockTempMod = { diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunRuntimeParameters.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/ProtocolRunRuntimeParameters.test.tsx similarity index 75% rename from app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunRuntimeParameters.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/ProtocolRunRuntimeParameters.test.tsx index 777c263078d..59425f6ff6b 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunRuntimeParameters.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/ProtocolRunRuntimeParameters.test.tsx @@ -1,14 +1,19 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, beforeEach, afterEach, expect } from 'vitest' import { screen } from '@testing-library/react' import { when } from 'vitest-when' import { InfoScreen } from '@opentrons/components' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useRunStatus } from '../../../RunTimeControl/hooks' -import { useNotifyRunQuery } from '../../../../resources/runs' -import { mockSucceededRun } from '../../../RunTimeControl/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { + useMostRecentCompletedAnalysis, + useNotifyRunQuery, + useRunStatus, +} from '/app/resources/runs' +import { + mockSucceededRun, + mockIdleUnstartedRun, +} from '/app/resources/runs/__fixtures__' import { ProtocolRunRuntimeParameters } from '../ProtocolRunRunTimeParameters' import type { UseQueryResult } from 'react-query' @@ -25,10 +30,8 @@ vi.mock('@opentrons/components', async importOriginal => { InfoScreen: vi.fn(), } }) -vi.mock('../../../LabwarePositionCheck/useMostRecentCompletedAnalysis') -vi.mock('../../../RunTimeControl/hooks') -vi.mock('../../../../resources/runs') -vi.mock('../../../../redux/config') +vi.mock('/app/resources/runs') +vi.mock('/app/redux/config') const RUN_ID = 'mockId' @@ -128,6 +131,13 @@ describe('ProtocolRunRuntimeParameters', () => { }) it('should render title, and banner when RunTimeParameters are not empty and all values are default', () => { + when(useNotifyRunQuery) + .calledWith(RUN_ID) + .thenReturn({ + data: { + data: mockIdleUnstartedRun, + }, + } as any) render(props) screen.getByText('Parameters') screen.getByText('Default values') @@ -151,6 +161,13 @@ describe('ProtocolRunRuntimeParameters', () => { }, ], } as CompletedProtocolAnalysis) + when(useNotifyRunQuery) + .calledWith(RUN_ID) + .thenReturn({ + data: { + data: mockIdleUnstartedRun, + }, + } as any) render(props) screen.getByText('Parameters') screen.getByText('Custom values') @@ -160,6 +177,39 @@ describe('ProtocolRunRuntimeParameters', () => { screen.getByText('Value') }) + it('should render title, and banner when RunTimeParameters from view protocol run record overflow menu button', () => { + when(useNotifyRunQuery) + .calledWith(RUN_ID) + .thenReturn({ + data: { + data: { + ...mockSucceededRun, + runTimeParameters: mockRunTimeParameterData, + }, + }, + } as any) + vi.mocked(useMostRecentCompletedAnalysis).mockReturnValue({ + runTimeParameters: [ + ...mockRunTimeParameterData, + { + displayName: 'Dry Run', + variableName: 'DRYRUN', + description: 'Is this a dry or wet run? Wet is true, dry is false', + type: 'bool', + default: false, + value: true, + }, + ], + } as CompletedProtocolAnalysis) + + vi.mocked(useRunStatus).mockReturnValue('succeeded') + render(props) + screen.getByText('Download files') + screen.getByText( + 'All files associated with the protocol run are available on the robot detail screen.' + ) + }) + it('should render RunTimeParameters when RunTimeParameters are not empty', () => { render(props) screen.getByText('Dry Run') diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx similarity index 83% rename from app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx index d6c885a6a84..84e7fb82e65 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/ProtocolRunSetup.test.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { when } from 'vitest-when' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' @@ -12,48 +11,62 @@ import { test_modules_protocol as withModulesProtocol, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { mockConnectedRobot } from '../../../../redux/discovery/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockConnectedRobot } from '/app/redux/discovery/__fixtures__' import { getIsFixtureMismatch, getRequiredDeckConfig, -} from '../../../../resources/deck_configuration/utils' -import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useDeckConfigurationCompatibility } from '../../../../resources/deck_configuration/hooks' +} from '/app/resources/deck_configuration/utils' import { - useIsFlex, - useModuleCalibrationStatus, - useProtocolAnalysisErrors, - useRobot, + useMostRecentCompletedAnalysis, useRunCalibrationStatus, - useRunHasStarted, useRunPipetteInfoByMount, - useStoredProtocolAnalysis, + useNotifyRunQuery, + useRunHasStarted, useUnmatchedModulesForProtocol, -} from '../../hooks' + useModuleCalibrationStatus, + useProtocolAnalysisErrors, +} from '/app/resources/runs' +import { useDeckConfigurationCompatibility } from '/app/resources/deck_configuration/hooks' +import { useRobot, useIsFlex } from '/app/redux-resources/robots' +import { useRequiredSetupStepsInOrder } from '/app/redux-resources/runs' +import { useStoredProtocolAnalysis } from '/app/resources/analysis' +import { getMissingSetupSteps } from '/app/redux/protocol-runs' + import { SetupLabware } from '../SetupLabware' import { SetupRobotCalibration } from '../SetupRobotCalibration' import { SetupLiquids } from '../SetupLiquids' import { SetupModuleAndDeck } from '../SetupModuleAndDeck' import { EmptySetupStep } from '../EmptySetupStep' import { ProtocolRunSetup } from '../ProtocolRunSetup' -import type { MissingSteps } from '../ProtocolRunSetup' -import { useNotifyRunQuery } from '../../../../resources/runs' +import * as ReduxRuns from '/app/redux/protocol-runs' + +import type { State } from '/app/redux/types' import type * as SharedData from '@opentrons/shared-data' -vi.mock('../../hooks') vi.mock('../SetupLabware') vi.mock('../SetupRobotCalibration') vi.mock('../SetupModuleAndDeck') vi.mock('../SetupLiquids') vi.mock('../EmptySetupStep') -vi.mock('../../../LabwarePositionCheck/useMostRecentCompletedAnalysis') -vi.mock('../../../../redux/config') -vi.mock('../../../../resources/deck_configuration/utils') -vi.mock('../../../../resources/deck_configuration/hooks') -vi.mock('../../../../resources/runs/useNotifyRunQuery') +vi.mock('/app/resources/runs/useNotifyRunQuery') +vi.mock('/app/resources/runs/useMostRecentCompletedAnalysis') +vi.mock('/app/resources/runs/useRunCalibrationStatus') +vi.mock('/app/resources/runs/useRunPipetteInfoByMount') +vi.mock('/app/resources/runs/useRunHasStarted') +vi.mock('/app/resources/runs/useUnmatchedModulesForProtocol') +vi.mock('/app/resources/runs/useModuleCalibrationStatus') +vi.mock('/app/resources/runs/useProtocolAnalysisErrors') +vi.mock('/app/redux/config') +vi.mock('/app/redux/protocol-runs') +vi.mock('/app/resources/protocol-runs') +vi.mock('/app/resources/deck_configuration/utils') +vi.mock('/app/resources/deck_configuration/hooks') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/redux-resources/runs') +vi.mock('/app/resources/analysis') vi.mock('@opentrons/shared-data', async importOriginal => { const actualSharedData = await importOriginal() return { @@ -68,20 +81,15 @@ vi.mock('@opentrons/shared-data', async importOriginal => { const ROBOT_NAME = 'otie' const RUN_ID = '1' const MOCK_PROTOCOL_LIQUID_KEY = { liquids: [] } -let mockMissingSteps: MissingSteps = [] -const mockSetMissingSteps = vi.fn((missingSteps: MissingSteps) => { - mockMissingSteps = missingSteps -}) const render = () => { - return renderWithProviders( + return renderWithProviders( , { + initialState: {} as State, i18nInstance: i18n, } )[0] @@ -89,7 +97,6 @@ const render = () => { describe('ProtocolRunSetup', () => { beforeEach(() => { - mockMissingSteps = [] when(vi.mocked(useIsFlex)).calledWith(ROBOT_NAME).thenReturn(false) when(vi.mocked(useMostRecentCompletedAnalysis)) .calledWith(RUN_ID) @@ -97,6 +104,9 @@ describe('ProtocolRunSetup', () => { ...noModulesProtocol, ...MOCK_PROTOCOL_LIQUID_KEY, } as any) + when(vi.mocked(getMissingSetupSteps)) + .calledWith(expect.any(Object), RUN_ID) + .thenReturn([]) when(vi.mocked(useProtocolAnalysisErrors)).calledWith(RUN_ID).thenReturn({ analysisErrors: null, }) @@ -106,6 +116,27 @@ describe('ProtocolRunSetup', () => { ...noModulesProtocol, ...MOCK_PROTOCOL_LIQUID_KEY, } as unknown) as SharedData.ProtocolAnalysisOutput) + when(vi.mocked(useRequiredSetupStepsInOrder)) + .calledWith({ + runId: RUN_ID, + protocolAnalysis: expect.any(Object), + }) + .thenReturn({ + orderedSteps: [ + ReduxRuns.ROBOT_CALIBRATION_STEP_KEY, + ReduxRuns.MODULE_SETUP_STEP_KEY, + ReduxRuns.LPC_STEP_KEY, + ReduxRuns.LABWARE_SETUP_STEP_KEY, + ReduxRuns.LIQUID_SETUP_STEP_KEY, + ], + orderedApplicableSteps: [ + ReduxRuns.ROBOT_CALIBRATION_STEP_KEY, + ReduxRuns.MODULE_SETUP_STEP_KEY, + ReduxRuns.LPC_STEP_KEY, + ReduxRuns.LABWARE_SETUP_STEP_KEY, + ReduxRuns.LIQUID_SETUP_STEP_KEY, + ], + }) vi.mocked(parseAllRequiredModuleModels).mockReturnValue([]) vi.mocked(parseLiquidsInLoadOrder).mockReturnValue([]) when(vi.mocked(useRobot)) @@ -173,6 +204,20 @@ describe('ProtocolRunSetup', () => { when(vi.mocked(useStoredProtocolAnalysis)) .calledWith(RUN_ID) .thenReturn(null) + when(vi.mocked(useRequiredSetupStepsInOrder)) + .calledWith({ runId: RUN_ID, protocolAnalysis: null }) + .thenReturn({ + orderedSteps: [ + ReduxRuns.ROBOT_CALIBRATION_STEP_KEY, + ReduxRuns.LPC_STEP_KEY, + ReduxRuns.LABWARE_SETUP_STEP_KEY, + ], + orderedApplicableSteps: [ + ReduxRuns.ROBOT_CALIBRATION_STEP_KEY, + ReduxRuns.LPC_STEP_KEY, + ReduxRuns.LABWARE_SETUP_STEP_KEY, + ], + }) render() screen.getByText('Loading data...') }) diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupCalibrationItem.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupCalibrationItem.test.tsx similarity index 84% rename from app/src/organisms/Devices/ProtocolRun/__tests__/SetupCalibrationItem.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupCalibrationItem.test.tsx index 0cd4c009bb5..e39e5d7c83c 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupCalibrationItem.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupCalibrationItem.test.tsx @@ -1,16 +1,16 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { when } from 'vitest-when' import { describe, it, beforeEach, vi, afterEach } from 'vitest' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { useRunHasStarted } from '../../hooks' -import { formatTimestamp } from '../../utils' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useRunHasStarted } from '/app/resources/runs' +import { formatTimestamp } from '/app/transformations/runs' import { SetupCalibrationItem } from '../SetupCalibrationItem' -vi.mock('../../hooks') -vi.mock('../../utils') +vi.mock('/app/resources/runs') +vi.mock('/app/transformations/runs') const RUN_ID = '1' diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupDeckCalibration.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupDeckCalibration.test.tsx similarity index 87% rename from app/src/organisms/Devices/ProtocolRun/__tests__/SetupDeckCalibration.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupDeckCalibration.test.tsx index b1462eb9cbd..05e43c3a41f 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupDeckCalibration.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupDeckCalibration.test.tsx @@ -1,12 +1,11 @@ -import * as React from 'react' import { when } from 'vitest-when' import { MemoryRouter } from 'react-router-dom' import { describe, it, beforeEach, afterEach, vi, expect } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { mockDeckCalData } from '../../../../redux/calibration/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockDeckCalData } from '/app/redux/calibration/__fixtures__' import { useDeckCalibrationData } from '../../hooks' import { SetupDeckCalibration } from '../SetupDeckCalibration' diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupFlexPipetteCalibrationItem.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupFlexPipetteCalibrationItem.test.tsx similarity index 85% rename from app/src/organisms/Devices/ProtocolRun/__tests__/SetupFlexPipetteCalibrationItem.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupFlexPipetteCalibrationItem.test.tsx index 13442cb63c5..0719c0c13b6 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupFlexPipetteCalibrationItem.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupFlexPipetteCalibrationItem.test.tsx @@ -1,22 +1,22 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, afterEach } from 'vitest' import { useInstrumentsQuery } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { PipetteWizardFlows } from '../../../PipetteWizardFlows' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { PipetteWizardFlows } from '/app/organisms/PipetteWizardFlows' import { SetupFlexPipetteCalibrationItem } from '../SetupFlexPipetteCalibrationItem' -import _uncastedModifiedSimpleV6Protocol from '../../hooks/__fixtures__/modifiedSimpleV6.json' +import { modifiedSimpleV6Protocol as _uncastedModifiedSimpleV6Protocol } from '/app/resources/runs/__fixtures__' import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' vi.mock('@opentrons/react-api-client') -vi.mock('../../../PipetteWizardFlows') -vi.mock('../../../LabwarePositionCheck/useMostRecentCompletedAnalysis') -vi.mock('../../hooks') +vi.mock('/app/organisms/PipetteWizardFlows') +vi.mock('/app/resources/runs') +vi.mock('/app/resources/analysis') const RUN_ID = '1' const modifiedSimpleV6Protocol = ({ diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupPipetteCalibration.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupPipetteCalibration.test.tsx similarity index 81% rename from app/src/organisms/Devices/ProtocolRun/__tests__/SetupPipetteCalibration.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupPipetteCalibration.test.tsx index 12ee29a86cd..ee59da5370b 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupPipetteCalibration.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupPipetteCalibration.test.tsx @@ -1,21 +1,22 @@ -import * as React from 'react' import { when } from 'vitest-when' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { mockTipRackDefinition } from '../../../../redux/custom-labware/__fixtures__' -import { useRunPipetteInfoByMount } from '../../hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockTipRackDefinition } from '/app/redux/custom-labware/__fixtures__' import { SetupPipetteCalibrationItem } from '../SetupPipetteCalibrationItem' import { SetupInstrumentCalibration } from '../SetupInstrumentCalibration' -import { useNotifyRunQuery } from '../../../../resources/runs' +import { + useNotifyRunQuery, + useRunPipetteInfoByMount, +} from '/app/resources/runs' -import type { PipetteInfo } from '../../hooks' +import type { PipetteInfo } from '/app/redux/pipettes' -vi.mock('../../hooks') vi.mock('../SetupPipetteCalibrationItem') -vi.mock('../../../../resources/runs') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/resources/runs') const ROBOT_NAME = 'otie' const RUN_ID = '1' diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupPipetteCalibrationItem.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupPipetteCalibrationItem.test.tsx similarity index 88% rename from app/src/organisms/Devices/ProtocolRun/__tests__/SetupPipetteCalibrationItem.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupPipetteCalibrationItem.test.tsx index 225dfaddcb4..effcb32b60f 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupPipetteCalibrationItem.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupPipetteCalibrationItem.test.tsx @@ -1,17 +1,18 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { mockDeckCalData } from '../../../../redux/calibration/__fixtures__' -import { mockPipetteInfo } from '../../../../redux/pipettes/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockDeckCalData } from '/app/redux/calibration/__fixtures__' +import { mockPipetteInfo } from '/app/redux/pipettes/__fixtures__' import { useDeckCalibrationData } from '../../hooks' import { SetupPipetteCalibrationItem } from '../SetupPipetteCalibrationItem' import { MemoryRouter } from 'react-router-dom' vi.mock('../../hooks') +vi.mock('/app/redux-resources/robots') const ROBOT_NAME = 'otie' const RUN_ID = '1' diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupRobotCalibration.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupRobotCalibration.test.tsx similarity index 88% rename from app/src/organisms/Devices/ProtocolRun/__tests__/SetupRobotCalibration.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupRobotCalibration.test.tsx index abf516fbc86..8d737f26452 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupRobotCalibration.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupRobotCalibration.test.tsx @@ -1,27 +1,28 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { useTrackEvent, ANALYTICS_PROCEED_TO_MODULE_SETUP_STEP, -} from '../../../../redux/analytics' -import { mockDeckCalData } from '../../../../redux/calibration/__fixtures__' -import { - useDeckCalibrationData, - useIsFlex, - useRunHasStarted, -} from '../../hooks' +} from '/app/redux/analytics' +import { useIsFlex } from '/app/redux-resources/robots' +import { mockDeckCalData } from '/app/redux/calibration/__fixtures__' +import { useDeckCalibrationData } from '../../hooks' +import { useRunHasStarted } from '/app/resources/runs' + import { SetupDeckCalibration } from '../SetupDeckCalibration' import { SetupInstrumentCalibration } from '../SetupInstrumentCalibration' import { SetupTipLengthCalibration } from '../SetupTipLengthCalibration' import { SetupRobotCalibration } from '../SetupRobotCalibration' -vi.mock('../../../../redux/analytics') +vi.mock('/app/redux/analytics') vi.mock('../../hooks') +vi.mock('/app/resources/runs') +vi.mock('/app/redux-resources/robots') vi.mock('../SetupDeckCalibration') vi.mock('../SetupInstrumentCalibration') vi.mock('../SetupTipLengthCalibration') diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupStep.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupStep.test.tsx similarity index 91% rename from app/src/organisms/Devices/ProtocolRun/__tests__/SetupStep.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupStep.test.tsx index 74b5ee7fb8e..705866c51b2 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupStep.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupStep.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { SetupStep } from '../SetupStep' import type { Mock } from 'vitest' diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupTipLengthCalibration.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupTipLengthCalibration.test.tsx similarity index 90% rename from app/src/organisms/Devices/ProtocolRun/__tests__/SetupTipLengthCalibration.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupTipLengthCalibration.test.tsx index f0e796dbf67..2c6783f0d0e 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupTipLengthCalibration.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupTipLengthCalibration.test.tsx @@ -1,19 +1,18 @@ -import * as React from 'react' import { when } from 'vitest-when' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { mockTipRackDefinition } from '../../../../redux/custom-labware/__fixtures__' -import { useRunPipetteInfoByMount } from '../../hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockTipRackDefinition } from '/app/redux/custom-labware/__fixtures__' +import { useRunPipetteInfoByMount } from '/app/resources/runs' import { SetupTipLengthCalibrationButton } from '../SetupTipLengthCalibrationButton' import { SetupTipLengthCalibration } from '../SetupTipLengthCalibration' -import type { PipetteInfo } from '../../hooks' +import type { PipetteInfo } from '/app/redux/pipettes' -vi.mock('../../../../redux/config') -vi.mock('../../hooks') +vi.mock('/app/redux/config') +vi.mock('/app/resources/runs') vi.mock('../SetupTipLengthCalibrationButton') const ROBOT_NAME = 'otie' diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupTipLengthCalibrationButton.test.tsx b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupTipLengthCalibrationButton.test.tsx similarity index 79% rename from app/src/organisms/Devices/ProtocolRun/__tests__/SetupTipLengthCalibrationButton.test.tsx rename to app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupTipLengthCalibrationButton.test.tsx index 993e14080f5..4dac2e3c8d5 100644 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/SetupTipLengthCalibrationButton.test.tsx +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/__tests__/SetupTipLengthCalibrationButton.test.tsx @@ -1,28 +1,31 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { screen, fireEvent } from '@testing-library/react' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' import { fixtureTiprack300ul } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { mockDeckCalData } from '../../../../redux/calibration/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockDeckCalData } from '/app/redux/calibration/__fixtures__' import { mockTipLengthCalLauncher } from '../../hooks/__fixtures__/taskListFixtures' -import { useDeckCalibrationData, useRunHasStarted } from '../../hooks' -import { useDashboardCalibrateTipLength } from '../../../../pages/Devices/CalibrationDashboard/hooks/useDashboardCalibrateTipLength' +import { useDeckCalibrationData } from '../../hooks' +import { useRunHasStarted } from '/app/resources/runs' +// eslint-disable-next-line opentrons/no-imports-up-the-tree-of-life +import { useDashboardCalibrateTipLength } from '/app/pages/Desktop/Devices/CalibrationDashboard/hooks/useDashboardCalibrateTipLength' import { SetupTipLengthCalibrationButton } from '../SetupTipLengthCalibrationButton' import type { LabwareDefinition2 } from '@opentrons/shared-data' vi.mock('@opentrons/components/src/hooks') -vi.mock('../../../../organisms/RunTimeControl/hooks') +vi.mock('/app/organisms/RunTimeControl/hooks') vi.mock( - '../../../../pages/Devices/CalibrationDashboard/hooks/useDashboardCalibrateTipLength' + '/app/pages/Desktop/Devices/CalibrationDashboard/hooks/useDashboardCalibrateTipLength' ) -vi.mock('../../../../redux/config') -vi.mock('../../../../redux/sessions/selectors') +vi.mock('/app/redux/config') +vi.mock('/app/redux/sessions/selectors') vi.mock('../../hooks') +vi.mock('/app/resources/runs') const ROBOT_NAME = 'otie' const RUN_ID = '1' diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/useLabwareOffsetForLabware.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/useLabwareOffsetForLabware.ts new file mode 100644 index 00000000000..d2ba3c19058 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/useLabwareOffsetForLabware.ts @@ -0,0 +1,48 @@ +import { getLoadedLabwareDefinitionsByUri } from '@opentrons/shared-data' + +import { getLabwareDefinitionUri } from '/app/transformations/protocols' +import { + getLabwareOffsetLocation, + getCurrentOffsetForLabwareInLocation, +} from '/app/transformations/analysis' +import { + useNotifyRunQuery, + useMostRecentCompletedAnalysis, +} from '/app/resources/runs' + +import type { LabwareOffset } from '@opentrons/api-client' + +export function useLabwareOffsetForLabware( + runId: string, + labwareId: string +): LabwareOffset | null { + const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) + const { data: runRecord } = useNotifyRunQuery(runId) + if (mostRecentAnalysis == null) return null + + const labwareDefinitionsByUri = getLoadedLabwareDefinitionsByUri( + mostRecentAnalysis.commands + ) + const labwareDefinitionUri = getLabwareDefinitionUri( + labwareId, + mostRecentAnalysis.labware, + labwareDefinitionsByUri + ) + + const labwareLocation = getLabwareOffsetLocation( + labwareId, + mostRecentAnalysis?.commands ?? [], + mostRecentAnalysis?.modules ?? [], + mostRecentAnalysis?.labware ?? [] + ) + if (labwareLocation == null || labwareDefinitionUri == null) return null + const labwareOffsets = runRecord?.data?.labwareOffsets ?? [] + + return ( + getCurrentOffsetForLabwareInLocation( + labwareOffsets, + labwareDefinitionUri, + labwareLocation + ) ?? null + ) +} diff --git a/app/src/organisms/Devices/ProtocolRun/utils/__tests__/getModuleTypesThatRequireExtraAttention.test.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/utils/__tests__/getModuleTypesThatRequireExtraAttention.test.ts similarity index 100% rename from app/src/organisms/Devices/ProtocolRun/utils/__tests__/getModuleTypesThatRequireExtraAttention.test.ts rename to app/src/organisms/Desktop/Devices/ProtocolRun/utils/__tests__/getModuleTypesThatRequireExtraAttention.test.ts diff --git a/app/src/organisms/Devices/ProtocolRun/utils/getInitialLabwareLocation.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/utils/getInitialLabwareLocation.ts similarity index 100% rename from app/src/organisms/Devices/ProtocolRun/utils/getInitialLabwareLocation.ts rename to app/src/organisms/Desktop/Devices/ProtocolRun/utils/getInitialLabwareLocation.ts diff --git a/app/src/organisms/Devices/ProtocolRun/utils/getModuleName.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/utils/getModuleName.ts similarity index 100% rename from app/src/organisms/Devices/ProtocolRun/utils/getModuleName.ts rename to app/src/organisms/Desktop/Devices/ProtocolRun/utils/getModuleName.ts diff --git a/app/src/organisms/Devices/ProtocolRun/utils/getModuleTypesThatRequireExtraAttention.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/utils/getModuleTypesThatRequireExtraAttention.ts similarity index 100% rename from app/src/organisms/Devices/ProtocolRun/utils/getModuleTypesThatRequireExtraAttention.ts rename to app/src/organisms/Desktop/Devices/ProtocolRun/utils/getModuleTypesThatRequireExtraAttention.ts diff --git a/app/src/organisms/Devices/ProtocolRun/utils/getPipetteMount.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/utils/getPipetteMount.ts similarity index 100% rename from app/src/organisms/Devices/ProtocolRun/utils/getPipetteMount.ts rename to app/src/organisms/Desktop/Devices/ProtocolRun/utils/getPipetteMount.ts diff --git a/app/src/organisms/Desktop/Devices/ReachableBanner.tsx b/app/src/organisms/Desktop/Devices/ReachableBanner.tsx new file mode 100644 index 00000000000..8e278092393 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/ReachableBanner.tsx @@ -0,0 +1,27 @@ +import { useTranslation } from 'react-i18next' +import { SPACING, Banner } from '@opentrons/components' +import { REACHABLE } from '/app/redux/discovery' + +import type { DiscoveredRobot } from '/app/redux/discovery/types' + +interface ReachableBannerProps { + robot: DiscoveredRobot +} + +export function ReachableBanner( + props: ReachableBannerProps +): JSX.Element | null { + const { robot } = props + const { t } = useTranslation('shared') + return robot.status === REACHABLE && robot.serverHealthStatus === 'ok' ? ( + + {t('robot_is_reachable_but_not_responding', { + hostname: robot.ip, + })} + + ) : null +} diff --git a/app/src/organisms/Desktop/Devices/RecentProtocolRuns.tsx b/app/src/organisms/Desktop/Devices/RecentProtocolRuns.tsx new file mode 100644 index 00000000000..48d566be520 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/RecentProtocolRuns.tsx @@ -0,0 +1,182 @@ +import { useTranslation } from 'react-i18next' +import { useAllProtocolsQuery } from '@opentrons/react-api-client' +import { + ALIGN_CENTER, + ALIGN_FLEX_START, + BORDERS, + COLORS, + DIRECTION_COLUMN, + DISPLAY_FLEX, + Flex, + JUSTIFY_FLEX_START, + SIZE_4, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' + +import { HistoricalProtocolRun } from './HistoricalProtocolRun' +import { useIsRobotViewable } from '/app/redux-resources/robots' +import { + useNotifyAllRunsQuery, + useCurrentRunId, + useRunStatuses, +} from '/app/resources/runs' + +interface RecentProtocolRunsProps { + robotName: string +} + +export function RecentProtocolRuns({ + robotName, +}: RecentProtocolRunsProps): JSX.Element | null { + const { t } = useTranslation(['device_details', 'shared']) + const isRobotViewable = useIsRobotViewable(robotName) + const runsQueryResponse = useNotifyAllRunsQuery() + const runs = runsQueryResponse?.data?.data + const protocols = useAllProtocolsQuery() + const currentRunId = useCurrentRunId() + const { isRunTerminal } = useRunStatuses() + const robotIsBusy = currentRunId != null ? !isRunTerminal : false + const nonQuickTransferRuns = runs?.filter(run => { + const protocol = protocols?.data?.data.find( + protocol => protocol.id === run.protocolId + ) + return protocol?.protocolKind !== 'quick-transfer' + }) + + return ( + + + {t('recent_protocol_runs')} + + + {isRobotViewable && + nonQuickTransferRuns && + nonQuickTransferRuns?.length > 0 && ( + <> + + + {t('run')} + + + {t('protocol')} + + + {t('files')} + + + {t('status')} + + + {t('run_duration')} + + + {nonQuickTransferRuns + .sort( + (a, b) => + new Date(b.createdAt).getTime() - + new Date(a.createdAt).getTime() + ) + + .map((run, index) => { + const protocol = protocols?.data?.data.find( + protocol => protocol.id === run.protocolId + ) + const protocolName = + protocol?.metadata.protocolName ?? + protocol?.files[0].name ?? + t('shared:loading') ?? + '' + + return ( + + ) + })} + + )} + {!isRobotViewable && ( + + {t('offline_recent_protocol_runs')} + + )} + {isRobotViewable && + (nonQuickTransferRuns == null || + nonQuickTransferRuns.length === 0) && ( + + {t('no_protocol_runs')} + + )} + + + ) +} diff --git a/app/src/organisms/Devices/RobotCard.tsx b/app/src/organisms/Desktop/Devices/RobotCard.tsx similarity index 92% rename from app/src/organisms/Devices/RobotCard.tsx rename to app/src/organisms/Desktop/Devices/RobotCard.tsx index 246cfea101f..a375a2cfa1c 100644 --- a/app/src/organisms/Devices/RobotCard.tsx +++ b/app/src/organisms/Desktop/Devices/RobotCard.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' @@ -31,25 +30,25 @@ import { useModulesQuery, } from '@opentrons/react-api-client' -import OT2_PNG from '../../assets/images/OT2-R_HERO.png' -import FLEX_PNG from '../../assets/images/FLEX.png' -import { InstrumentContainer } from '../../atoms/InstrumentContainer' -import { CONNECTABLE, getRobotModelByName } from '../../redux/discovery' -import { ModuleIcon } from '../../molecules/ModuleIcon' +import OT2_PNG from '/app/assets/images/OT2-R_HERO.png' +import FLEX_PNG from '/app/assets/images/FLEX.png' +import { InstrumentContainer } from '/app/atoms/InstrumentContainer' +import { CONNECTABLE, getRobotModelByName } from '/app/redux/discovery' +import { ModuleIcon } from '/app/molecules/ModuleIcon' import { UpdateRobotBanner } from '../UpdateRobotBanner' -import { useIsFlex } from './hooks' +import { useIsFlex } from '/app/redux-resources/robots' import { ReachableBanner } from './ReachableBanner' import { RobotOverflowMenu } from './RobotOverflowMenu' import { RobotStatusHeader } from './RobotStatusHeader' import { ErrorRecoveryBanner, useErrorRecoveryBanner, -} from '../ErrorRecoveryBanner' +} from './ErrorRecoveryBanner' import type { GripperData } from '@opentrons/api-client' import type { GripperModel } from '@opentrons/shared-data' -import type { DiscoveredRobot } from '../../redux/discovery/types' -import type { State } from '../../redux/types' +import type { DiscoveredRobot } from '/app/redux/discovery/types' +import type { State } from '/app/redux/types' interface RobotCardProps { robot: DiscoveredRobot diff --git a/app/src/organisms/Devices/RobotOverflowMenu.tsx b/app/src/organisms/Desktop/Devices/RobotOverflowMenu.tsx similarity index 82% rename from app/src/organisms/Devices/RobotOverflowMenu.tsx rename to app/src/organisms/Desktop/Devices/RobotOverflowMenu.tsx index 7d13b3cc0a0..d2f5f3e8a82 100644 --- a/app/src/organisms/Devices/RobotOverflowMenu.tsx +++ b/app/src/organisms/Desktop/Devices/RobotOverflowMenu.tsx @@ -1,8 +1,9 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' -import { useDispatch, useSelector } from 'react-redux' +import { useDispatch } from 'react-redux' import { Link } from 'react-router-dom' +import { css } from 'styled-components' import { ALIGN_FLEX_END, @@ -11,6 +12,7 @@ import { DIRECTION_COLUMN, Flex, MenuItem, + NO_WRAP, OverflowBtn, POSITION_ABSOLUTE, POSITION_RELATIVE, @@ -20,19 +22,19 @@ import { useMenuHandleClickOutside, } from '@opentrons/components' -import { CONNECTABLE, removeRobot } from '../../redux/discovery' -import { getRobotUpdateDisplayInfo } from '../../redux/robot-update' -import { Divider } from '../../atoms/structure' -import { getTopPortalEl } from '../../App/portal' -import { ChooseProtocolSlideout } from '../ChooseProtocolSlideout' -import { useCurrentRunId } from '../../resources/runs' +import { CONNECTABLE, removeRobot } from '/app/redux/discovery' +import { useIsRobotOnWrongVersionOfSoftware } from '/app/redux/robot-update' +import { Divider } from '/app/atoms/structure' +import { getTopPortalEl } from '/app/App/portal' +import { ChooseProtocolSlideout } from '/app/organisms/Desktop/ChooseProtocolSlideout' +import { useCurrentRunId } from '/app/resources/runs' import { ConnectionTroubleshootingModal } from './ConnectionTroubleshootingModal' -import { useIsRobotBusy } from './hooks' +import { useIsRobotBusy } from '/app/redux-resources/robots' +import type { MouseEventHandler, MouseEvent, ReactNode } from 'react' import type { StyleProps } from '@opentrons/components' -import type { DiscoveredRobot } from '../../redux/discovery/types' -import type { Dispatch, State } from '../../redux/types' -import { css } from 'styled-components' +import type { DiscoveredRobot } from '/app/redux/discovery/types' +import type { Dispatch } from '/app/redux/types' interface RobotOverflowMenuProps extends StyleProps { robot: DiscoveredRobot @@ -53,34 +55,32 @@ export function RobotOverflowMenu(props: RobotOverflowMenuProps): JSX.Element { const [ showChooseProtocolSlideout, setShowChooseProtocolSlideout, - ] = React.useState(false) + ] = useState(false) const [ showConnectionTroubleshootingModal, setShowConnectionTroubleshootingModal, - ] = React.useState(false) + ] = useState(false) - const { autoUpdateAction } = useSelector((state: State) => { - return getRobotUpdateDisplayInfo(state, robot.name) - }) - const isRobotOnWrongVersionOfSoftware = - autoUpdateAction === 'upgrade' || autoUpdateAction === 'downgrade' + const isRobotOnWrongVersionOfSoftware = useIsRobotOnWrongVersionOfSoftware( + robot.name + ) const isRobotBusy = useIsRobotBusy({ poll: true }) - const handleClickRun: React.MouseEventHandler = e => { + const handleClickRun: MouseEventHandler = e => { e.preventDefault() e.stopPropagation() setShowChooseProtocolSlideout(true) setShowOverflowMenu(false) } - const handleClickConnectionTroubleshooting: React.MouseEventHandler = e => { + const handleClickConnectionTroubleshooting: MouseEventHandler = e => { e.preventDefault() e.stopPropagation() setShowConnectionTroubleshootingModal(true) setShowOverflowMenu(false) } - let menuItems: React.ReactNode + let menuItems: ReactNode if (robot.status === CONNECTABLE && runId == null) { menuItems = ( <> @@ -162,7 +162,7 @@ export function RobotOverflowMenu(props: RobotOverflowMenuProps): JSX.Element { data-testid={`RobotCard_${String(robot.name)}_overflowMenu`} flexDirection={DIRECTION_COLUMN} position={POSITION_RELATIVE} - onClick={(e: React.MouseEvent) => { + onClick={(e: MouseEvent) => { e.stopPropagation() }} {...styleProps} @@ -174,7 +174,7 @@ export function RobotOverflowMenu(props: RobotOverflowMenuProps): JSX.Element { /> {showOverflowMenu && !showConnectionTroubleshootingModal ? ( () - const handleClickRestart: React.MouseEventHandler = () => { + const handleClickRestart: MouseEventHandler = () => { dispatch(restartRobot(robot.name)) } - const handleClickHomeGantry: React.MouseEventHandler = () => { + const handleClickHomeGantry: MouseEventHandler = () => { dispatch(home(robot.name, ROBOT)) } const [ showChooseProtocolSlideout, setShowChooseProtocolSlideout, - ] = React.useState(false) - const [showDisconnectModal, setShowDisconnectModal] = React.useState( - false - ) + ] = useState(false) + const [showDisconnectModal, setShowDisconnectModal] = useState(false) const canDisconnect = useCanDisconnect(robot.name) - const handleClickDisconnect: React.MouseEventHandler = () => { + const handleClickDisconnect: MouseEventHandler = () => { setShowDisconnectModal(true) } @@ -86,15 +87,13 @@ export const RobotOverviewOverflowMenu = ( dispatch(checkShellUpdate()) }) - const handleClickRun: React.MouseEventHandler = () => { + const handleClickRun: MouseEventHandler = () => { setShowChooseProtocolSlideout(true) } - const { autoUpdateAction } = useSelector((state: State) => { - return getRobotUpdateDisplayInfo(state, robot.name) - }) - const isRobotOnWrongVersionOfSoftware = - autoUpdateAction === 'upgrade' || autoUpdateAction === 'downgrade' + const isRobotOnWrongVersionOfSoftware = useIsRobotOnWrongVersionOfSoftware( + robot.name + ) const isRobotUnavailable = isRobotBusy || robot?.status !== CONNECTABLE const isUpdateSoftwareItemVisible = isRobotOnWrongVersionOfSoftware && @@ -117,7 +116,7 @@ export const RobotOverviewOverflowMenu = ( {showOverflowMenu ? ( { + onClick={(e: MouseEvent) => { e.preventDefault() e.stopPropagation() setShowOverflowMenu(false) diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/DeviceResetModal.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/DeviceResetModal.tsx similarity index 91% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/DeviceResetModal.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/DeviceResetModal.tsx index 29e79772be1..49a6531c302 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/DeviceResetModal.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/DeviceResetModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useEffect } from 'react' import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import last from 'lodash/last' @@ -22,15 +22,12 @@ import { getRequestById, SUCCESS, PENDING, -} from '../../../../../redux/robot-api' -import { - getResetConfigOptions, - resetConfig, -} from '../../../../../redux/robot-admin' -import { useIsFlex } from '../../../hooks' +} from '/app/redux/robot-api' +import { getResetConfigOptions, resetConfig } from '/app/redux/robot-admin' +import { useIsFlex } from '/app/redux-resources/robots' -import type { State } from '../../../../../redux/types' -import type { ResetConfigRequest } from '../../../../../redux/robot-admin/types' +import type { State } from '/app/redux/types' +import type { ResetConfigRequest } from '/app/redux/robot-admin/types' interface DeviceResetModalProps { closeModal: () => void @@ -83,7 +80,7 @@ export function DeviceResetModal({ } } - React.useEffect(() => { + useEffect(() => { if (resetRequestStatus === SUCCESS) closeModal() }, [resetRequestStatus, closeModal]) diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/DeviceResetSlideout.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/DeviceResetSlideout.tsx similarity index 93% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/DeviceResetSlideout.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/DeviceResetSlideout.tsx index f29516c0158..763ca9f0cb2 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/DeviceResetSlideout.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/DeviceResetSlideout.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { useSelector, useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next' import snakeCase from 'lodash/snakeCase' @@ -21,28 +21,28 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { Slideout } from '../../../../../atoms/Slideout' -import { Divider } from '../../../../../atoms/structure' -import { UNREACHABLE } from '../../../../../redux/discovery' +import { Slideout } from '/app/atoms/Slideout' +import { Divider } from '/app/atoms/structure' +import { UNREACHABLE } from '/app/redux/discovery' import { getResetConfigOptions, fetchResetConfigOptions, -} from '../../../../../redux/robot-admin' +} from '/app/redux/robot-admin' import { useTrackEvent, ANALYTICS_CALIBRATION_DATA_DOWNLOADED, -} from '../../../../../redux/analytics' +} from '/app/redux/analytics' import { useDeckCalibrationData, - useIsFlex, usePipetteOffsetCalibrations, useTipLengthCalibrations, - useRobot, } from '../../../hooks' -import { useNotifyAllRunsQuery } from '../../../../../resources/runs' +import { useRobot, useIsFlex } from '/app/redux-resources/robots' +import { useNotifyAllRunsQuery } from '/app/resources/runs' -import type { State, Dispatch } from '../../../../../redux/types' -import type { ResetConfigRequest } from '../../../../../redux/robot-admin/types' +import type { MouseEventHandler } from 'react' +import type { State, Dispatch } from '/app/redux/types' +import type { ResetConfigRequest } from '/app/redux/robot-admin/types' interface DeviceResetSlideoutProps { isExpanded: boolean @@ -62,7 +62,7 @@ export function DeviceResetSlideout({ const doTrackEvent = useTrackEvent() const robot = useRobot(robotName) const dispatch = useDispatch() - const [resetOptions, setResetOptions] = React.useState({}) + const [resetOptions, setResetOptions] = useState({}) const runsQueryResponse = useNotifyAllRunsQuery() const isFlex = useIsFlex(robotName) @@ -99,11 +99,11 @@ export function DeviceResetSlideout({ ? options.filter(opt => opt.id.includes('authorizedKeys')) : [] - React.useEffect(() => { + useEffect(() => { dispatch(fetchResetConfigOptions(robotName)) }, [dispatch, robotName]) - const downloadCalibrationLogs: React.MouseEventHandler = e => { + const downloadCalibrationLogs: MouseEventHandler = e => { e.preventDefault() doTrackEvent({ name: ANALYTICS_CALIBRATION_DATA_DOWNLOADED, @@ -121,7 +121,7 @@ export function DeviceResetSlideout({ ) } - const downloadRunHistoryLogs: React.MouseEventHandler = e => { + const downloadRunHistoryLogs: MouseEventHandler = e => { e.preventDefault() const runsHistory = runsQueryResponse != null ? runsQueryResponse.data?.data : [] diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/FactoryModeSlideout.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/FactoryModeSlideout.tsx similarity index 89% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/FactoryModeSlideout.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/FactoryModeSlideout.tsx index 80b2d3e35ed..ff083c9b3e5 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/FactoryModeSlideout.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/FactoryModeSlideout.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { useDispatch } from 'react-redux' import { useForm, Controller } from 'react-hook-form' import { Trans, useTranslation } from 'react-i18next' @@ -22,15 +22,16 @@ import { useUpdateRobotSettingMutation, } from '@opentrons/react-api-client' -import { ToggleButton } from '../../../../../atoms/buttons' -import { MultiSlideout } from '../../../../../atoms/Slideout/MultiSlideout' -import { FileUpload } from '../../../../../molecules/FileUpload' -import { UploadInput } from '../../../../../molecules/UploadInput' -import { restartRobot } from '../../../../../redux/robot-admin' +import { ToggleButton } from '/app/atoms/buttons' +import { MultiSlideout } from '/app/atoms/Slideout/MultiSlideout' +import { FileUpload } from '/app/molecules/FileUpload' +import { UploadInput } from '/app/molecules/UploadInput' +import { restartRobot } from '/app/redux/robot-admin' +import type { ChangeEvent, MouseEventHandler } from 'react' import type { FieldError, Resolver } from 'react-hook-form' import type { RobotSettingsField } from '@opentrons/api-client' -import type { Dispatch } from '../../../../../redux/types' +import type { Dispatch } from '/app/redux/types' interface FactoryModeSlideoutProps { isExpanded: boolean @@ -63,11 +64,11 @@ export function FactoryModeSlideout({ const last = sn?.substring(sn.length - 4) - const [currentStep, setCurrentStep] = React.useState(1) - const [toggleValue, setToggleValue] = React.useState(false) - const [file, setFile] = React.useState(null) - const [fileError, setFileError] = React.useState(null) - const [isUploading, setIsUploading] = React.useState(false) + const [currentStep, setCurrentStep] = useState(1) + const [toggleValue, setToggleValue] = useState(false) + const [file, setFile] = useState(null) + const [fileError, setFileError] = useState(null) + const [isUploading, setIsUploading] = useState(false) const onFinishCompleteClick = (): void => { dispatch(restartRobot(robotName)) @@ -142,11 +143,11 @@ export function FactoryModeSlideout({ void handleSubmit(onSubmit)() } - const handleToggleClick: React.MouseEventHandler = () => { + const handleToggleClick: MouseEventHandler = () => { setToggleValue(toggleValue => !toggleValue) } - const handleCompleteClick: React.MouseEventHandler = () => { + const handleCompleteClick: MouseEventHandler = () => { setIsUploading(true) updateRobotSetting({ id: 'enableOEMMode', value: toggleValue }) } @@ -173,7 +174,7 @@ export function FactoryModeSlideout({ } } - React.useEffect(() => { + useEffect(() => { // initialize local state to OEM mode value if (isOEMMode != null) { setToggleValue(isOEMMode) @@ -229,7 +230,7 @@ export function FactoryModeSlideout({ id="factoryModeInput" name="factoryModeInput" type="text" - onChange={(e: React.ChangeEvent) => { + onChange={(e: ChangeEvent) => { field.onChange(e) clearErrors() }} diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/RenameRobotSlideout.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/RenameRobotSlideout.tsx similarity index 91% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/RenameRobotSlideout.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/RenameRobotSlideout.tsx index 07fc4fa30f5..55d9806a88f 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/RenameRobotSlideout.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/RenameRobotSlideout.tsx @@ -1,10 +1,11 @@ -import * as React from 'react' +import { useState } from 'react' import { useSelector, useDispatch } from 'react-redux' import { useNavigate } from 'react-router-dom' import { useForm, Controller } from 'react-hook-form' import { useTranslation } from 'react-i18next' import { COLORS, + Banner, DIRECTION_COLUMN, Flex, InputField, @@ -18,18 +19,16 @@ import { getConnectableRobots, getReachableRobots, getUnreachableRobots, -} from '../../../../../redux/discovery' -import { - useTrackEvent, - ANALYTICS_RENAME_ROBOT, -} from '../../../../../redux/analytics' -import { Slideout } from '../../../../../atoms/Slideout' -import { Banner } from '../../../../../atoms/Banner' -import { useIsFlex } from '../../../hooks' +} from '/app/redux/discovery' +import { useTrackEvent, ANALYTICS_RENAME_ROBOT } from '/app/redux/analytics' +import { Slideout } from '/app/atoms/Slideout' +import { useIsFlex } from '/app/redux-resources/robots' +import type { ChangeEvent } from 'react' import type { Resolver, FieldError } from 'react-hook-form' import type { UpdatedRobotName } from '@opentrons/api-client' -import type { State, Dispatch } from '../../../../../redux/types' +import type { State, Dispatch } from '/app/redux/types' + interface RenameRobotSlideoutProps { isExpanded: boolean onCloseClick: () => void @@ -52,9 +51,7 @@ export function RenameRobotSlideout({ robotName, }: RenameRobotSlideoutProps): JSX.Element { const { t } = useTranslation('device_settings') - const [previousRobotName, setPreviousRobotName] = React.useState( - robotName - ) + const [previousRobotName, setPreviousRobotName] = useState(robotName) const isFlex = useIsFlex(robotName) const trackEvent = useTrackEvent() const navigate = useNavigate() @@ -193,7 +190,7 @@ export function RenameRobotSlideout({ id="newRobotName" name="newRobotName" type="text" - onChange={(e: React.ChangeEvent) => { + onChange={(e: ChangeEvent) => { field.onChange(e) trigger('newRobotName') }} diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/DeviceResetModal.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/DeviceResetModal.test.tsx similarity index 89% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/DeviceResetModal.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/DeviceResetModal.test.tsx index b741f3ef5c8..a5a3a91d219 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/DeviceResetModal.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/DeviceResetModal.test.tsx @@ -1,19 +1,19 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, expect, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../../__testing-utils__' -import { i18n } from '../../../../../../i18n' -import { resetConfig } from '../../../../../../redux/robot-admin' -import { useDispatchApiRequest } from '../../../../../../redux/robot-api' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { resetConfig } from '/app/redux/robot-admin' +import { useDispatchApiRequest } from '/app/redux/robot-api' import { DeviceResetModal } from '../DeviceResetModal' -import type { DispatchApiRequestType } from '../../../../../../redux/robot-api' +import type { DispatchApiRequestType } from '/app/redux/robot-api' -vi.mock('../../../../hooks') -vi.mock('../../../../../../redux/robot-admin') -vi.mock('../../../../../../redux/robot-api') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/redux/robot-admin') +vi.mock('/app/redux/robot-api') const mockResetOptions = {} const mockCloseModal = vi.fn() diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/DeviceResetSlideout.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/DeviceResetSlideout.test.tsx similarity index 92% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/DeviceResetSlideout.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/DeviceResetSlideout.test.tsx index 15f35b485eb..7cc668503dc 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/DeviceResetSlideout.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/DeviceResetSlideout.test.tsx @@ -1,17 +1,17 @@ -import * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, expect, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../../__testing-utils__' -import { i18n } from '../../../../../../i18n' -import { getResetConfigOptions } from '../../../../../../redux/robot-admin' -import { useIsFlex } from '../../../../hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getResetConfigOptions } from '/app/redux/robot-admin' +import { useIsFlex } from '/app/redux-resources/robots' import { DeviceResetSlideout } from '../DeviceResetSlideout' -vi.mock('../../../../../../redux/config') -vi.mock('../../../../../../redux/discovery') -vi.mock('../../../../../../redux/robot-admin/selectors') +vi.mock('/app/redux/config') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/robot-admin/selectors') +vi.mock('/app/redux-resources/robots') vi.mock('../../../../hooks') const mockOnCloseClick = vi.fn() diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/RenameRobotSlideout.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/RenameRobotSlideout.test.tsx similarity index 93% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/RenameRobotSlideout.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/RenameRobotSlideout.test.tsx index 2fbb730e095..6f7b1b4ab28 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/RenameRobotSlideout.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/AdvancedTabSlideouts/__tests__/RenameRobotSlideout.test.tsx @@ -1,31 +1,27 @@ -import * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen, waitFor } from '@testing-library/react' import { describe, it, vi, expect, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../../__testing-utils__' -import { i18n } from '../../../../../../i18n' -import { - useTrackEvent, - ANALYTICS_RENAME_ROBOT, -} from '../../../../../../redux/analytics' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useTrackEvent, ANALYTICS_RENAME_ROBOT } from '/app/redux/analytics' import { getConnectableRobots, getReachableRobots, getUnreachableRobots, -} from '../../../../../../redux/discovery' +} from '/app/redux/discovery' import { mockConnectableRobot, mockReachableRobot, -} from '../../../../../../redux/discovery/__fixtures__' +} from '/app/redux/discovery/__fixtures__' import { RenameRobotSlideout } from '../RenameRobotSlideout' -import { useIsFlex } from '../../../../hooks' +import { useIsFlex } from '/app/redux-resources/robots' -vi.mock('../../../../../../redux/discovery/selectors') -vi.mock('../../../../../../redux/analytics') -vi.mock('../../../../hooks') -vi.mock('../../../../../../redux/discovery', async importOriginal => { +vi.mock('/app/redux/discovery/selectors') +vi.mock('/app/redux/analytics') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/redux/discovery', async importOriginal => { const actual = await importOriginal() return { ...actual, diff --git a/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/DeviceReset.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/DeviceReset.tsx new file mode 100644 index 00000000000..2249e453fe6 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/DeviceReset.tsx @@ -0,0 +1,61 @@ +import type * as React from 'react' +import { useTranslation } from 'react-i18next' + +import { + ALIGN_CENTER, + Box, + Flex, + JUSTIFY_SPACE_BETWEEN, + SPACING_AUTO, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' + +import { TertiaryButton } from '/app/atoms/buttons' + +interface DeviceResetProps { + updateIsExpanded: ( + isExpanded: boolean, + type: 'deviceReset' | 'renameRobot' + ) => void + isRobotBusy: boolean +} + +export function DeviceReset({ + updateIsExpanded, + isRobotBusy, +}: DeviceResetProps): JSX.Element { + const { t } = useTranslation('device_settings') + + const handleClick: React.MouseEventHandler = () => { + if (!isRobotBusy) { + updateIsExpanded(true, 'deviceReset') + } + } + + return ( + + + + {t('device_reset')} + + + {t('device_reset_description')} + + + + {t('choose_reset_settings')} + + + ) +} diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/DisplayRobotName.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/DisplayRobotName.tsx similarity index 94% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/DisplayRobotName.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/DisplayRobotName.tsx index 8bf48848e2e..c6a380cd6e5 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/DisplayRobotName.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/DisplayRobotName.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -13,7 +13,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { TertiaryButton } from '../../../../atoms/buttons' +import { TertiaryButton } from '/app/atoms/buttons' interface DisplayRobotNameProps { robotName: string updateIsExpanded: ( diff --git a/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/EnableErrorRecoveryMode.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/EnableErrorRecoveryMode.tsx new file mode 100644 index 00000000000..979dc155e6e --- /dev/null +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/EnableErrorRecoveryMode.tsx @@ -0,0 +1,54 @@ +import { useTranslation } from 'react-i18next' + +import { + ALIGN_CENTER, + Box, + DIRECTION_COLUMN, + Flex, + JUSTIFY_SPACE_BETWEEN, + SPACING, + TYPOGRAPHY, + LegacyStyledText, +} from '@opentrons/components' + +import { ToggleButton } from '/app/atoms/buttons' +import { useErrorRecoverySettingsToggle } from '/app/resources/errorRecovery' + +export function EnableErrorRecoveryMode({ + isRobotBusy, +}: { + isRobotBusy: boolean +}): JSX.Element { + const { t } = useTranslation('app_settings') + const { isEREnabled, toggleERSettings } = useErrorRecoverySettingsToggle() + + return ( + + + + + {t('error_recovery_mode')} + + + {t('error_recovery_mode_description')} + + + + + + ) +} diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/EnableStatusLight.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/EnableStatusLight.tsx similarity index 91% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/EnableStatusLight.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/EnableStatusLight.tsx index 1e7d76388c6..4e3c7fcc0f0 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/EnableStatusLight.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/EnableStatusLight.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -11,8 +10,8 @@ import { TYPOGRAPHY, LegacyStyledText, } from '@opentrons/components' -import { ToggleButton } from '../../../../atoms/buttons' -import { useLEDLights } from '../../hooks' +import { ToggleButton } from '/app/atoms/buttons' +import { useLEDLights } from '/app/resources/robot-settings' interface EnableStatusLightProps { robotName: string diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/FactoryMode.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/FactoryMode.tsx similarity index 92% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/FactoryMode.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/FactoryMode.tsx index 5b92ad6f22d..14cb3766040 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/FactoryMode.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/FactoryMode.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -12,7 +12,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { TertiaryButton } from '../../../../atoms/buttons' +import { TertiaryButton } from '/app/atoms/buttons' interface FactoryModeProps { isRobotBusy: boolean diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/GantryHoming.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/GantryHoming.tsx similarity index 83% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/GantryHoming.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/GantryHoming.tsx index 2711d7b1673..96ea82a59cf 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/GantryHoming.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/GantryHoming.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next' @@ -12,11 +12,11 @@ import { LegacyStyledText, } from '@opentrons/components' -import { ToggleButton } from '../../../../atoms/buttons' -import { updateSetting } from '../../../../redux/robot-settings' +import { ToggleButton } from '/app/atoms/buttons' +import { updateSetting } from '/app/redux/robot-settings' -import type { Dispatch } from '../../../../redux/types' -import type { RobotSettingsField } from '../../../../redux/robot-settings/types' +import type { Dispatch } from '/app/redux/types' +import type { RobotSettingsField } from '/app/redux/robot-settings/types' interface GantryHomingProps { settings: RobotSettingsField | undefined diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/LegacySettings.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/LegacySettings.tsx similarity index 85% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/LegacySettings.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/LegacySettings.tsx index 6249845a895..5ea0ae6e5ec 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/LegacySettings.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/LegacySettings.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next' @@ -12,11 +12,11 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ToggleButton } from '../../../../atoms/buttons' -import { updateSetting } from '../../../../redux/robot-settings' +import { ToggleButton } from '/app/atoms/buttons' +import { updateSetting } from '/app/redux/robot-settings' -import type { Dispatch } from '../../../../redux/types' -import type { RobotSettingsField } from '../../../../redux/robot-settings/types' +import type { Dispatch } from '/app/redux/types' +import type { RobotSettingsField } from '/app/redux/robot-settings/types' interface LegacySettingsProps { settings: RobotSettingsField | undefined diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/OpenJupyterControl.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/OpenJupyterControl.tsx similarity index 86% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/OpenJupyterControl.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/OpenJupyterControl.tsx index 56695ebdb55..1fae3e17676 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/OpenJupyterControl.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/OpenJupyterControl.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, @@ -9,12 +8,9 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { - useTrackEvent, - ANALYTICS_JUPYTER_OPEN, -} from '../../../../redux/analytics' -import { TertiaryButton } from '../../../../atoms/buttons' -import { ExternalLink } from '../../../../atoms/Link/ExternalLink' +import { useTrackEvent, ANALYTICS_JUPYTER_OPEN } from '/app/redux/analytics' +import { TertiaryButton } from '/app/atoms/buttons' +import { ExternalLink } from '/app/atoms/Link/ExternalLink' const EVENT_JUPYTER_OPEN = { name: ANALYTICS_JUPYTER_OPEN, properties: {} } diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/RobotInformation.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/RobotInformation.tsx similarity index 95% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/RobotInformation.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/RobotInformation.tsx index 55f53c84413..05d492261ba 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/RobotInformation.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/RobotInformation.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { Box, @@ -9,12 +8,12 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { useRobot } from '../../../../organisms/Devices/hooks' +import { useRobot } from '/app/redux-resources/robots' import { getRobotSerialNumber, getRobotFirmwareVersion, getRobotProtocolApiVersion, -} from '../../../../redux/discovery' +} from '/app/redux/discovery' interface RobotInformationProps { robotName: string diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/RobotServerVersion.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/RobotServerVersion.tsx similarity index 90% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/RobotServerVersion.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/RobotServerVersion.tsx index 53ab38da90b..1353131e794 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/RobotServerVersion.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/RobotServerVersion.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { @@ -13,14 +12,14 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { TertiaryButton } from '../../../../atoms/buttons' -import { getRobotApiVersion } from '../../../../redux/discovery' -import { getRobotUpdateDisplayInfo } from '../../../../redux/robot-update' +import { TertiaryButton } from '/app/atoms/buttons' +import { getRobotApiVersion } from '/app/redux/discovery' +import { getRobotUpdateDisplayInfo } from '/app/redux/robot-update' import { UpdateRobotBanner } from '../../../UpdateRobotBanner' -import { useIsFlex, useRobot } from '../../hooks' import { handleUpdateBuildroot } from '../UpdateBuildroot' +import { useRobot, useIsFlex } from '/app/redux-resources/robots' -import type { State } from '../../../../redux/types' +import type { State } from '/app/redux/types' interface RobotServerVersionProps { robotName: string diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/ShortTrashBin.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/ShortTrashBin.tsx similarity index 83% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/ShortTrashBin.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/ShortTrashBin.tsx index 4fda1e54a7a..5bc00476406 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/ShortTrashBin.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/ShortTrashBin.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next' @@ -12,11 +12,11 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ToggleButton } from '../../../../atoms/buttons' -import { updateSetting } from '../../../../redux/robot-settings' +import { ToggleButton } from '/app/atoms/buttons' +import { updateSetting } from '/app/redux/robot-settings' -import type { Dispatch } from '../../../../redux/types' -import type { RobotSettingsField } from '../../../../redux/robot-settings/types' +import type { Dispatch } from '/app/redux/types' +import type { RobotSettingsField } from '/app/redux/robot-settings/types' interface ShortTrashBinProps { settings: RobotSettingsField | undefined diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/Troubleshooting.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/Troubleshooting.tsx similarity index 85% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/Troubleshooting.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/Troubleshooting.tsx index 39ad04dc044..a216c71cbd4 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/Troubleshooting.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/Troubleshooting.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useRef } from 'react' import { useTranslation } from 'react-i18next' import { saveAs } from 'file-saver' import JSZip from 'jszip' @@ -7,6 +7,7 @@ import last from 'lodash/last' import { GET, request } from '@opentrons/api-client' import { ALIGN_CENTER, + ALIGN_END, Box, Flex, JUSTIFY_SPACE_BETWEEN, @@ -19,11 +20,12 @@ import { } from '@opentrons/components' import { useHost } from '@opentrons/react-api-client' -import { TertiaryButton } from '../../../../atoms/buttons' -import { useToaster } from '../../../../organisms/ToasterOven' -import { CONNECTABLE } from '../../../../redux/discovery' -import { useRobot } from '../../hooks' +import { TertiaryButton } from '/app/atoms/buttons' +import { useToaster } from '/app/organisms/ToasterOven' +import { CONNECTABLE } from '/app/redux/discovery' +import { useRobot } from '/app/redux-resources/robots' +import type { MouseEventHandler } from 'react' import type { IconProps } from '@opentrons/components' interface TroubleshootingProps { @@ -37,16 +39,15 @@ export function Troubleshooting({ const robot = useRobot(robotName) const controlDisabled = robot?.status !== CONNECTABLE const logsAvailable = robot?.health?.logs != null - const [ - isDownloadingRobotLogs, - setIsDownloadingRobotLogs, - ] = React.useState(false) + const [isDownloadingRobotLogs, setIsDownloadingRobotLogs] = useState( + false + ) const { makeToast, eatToast } = useToaster() const toastIcon: IconProps = { name: 'ot-spinner', spin: true } const host = useHost() - const handleClick: React.MouseEventHandler = () => { + const handleClick: MouseEventHandler = () => { setIsDownloadingRobotLogs(true) const toastId = makeToast(t('downloading_logs') as string, INFO_TOAST, { disableTimeout: true, @@ -98,8 +99,8 @@ export function Troubleshooting({ } } - // set ref on component to check if component is mounted https://react.dev/reference/react/useRef#manipulating-the-dom-with-a-ref - const mounted = React.useRef(null) + // set ref on component to check if component is mounted https://dev/reference/react/useRef#manipulating-the-dom-with-a-ref + const mounted = useRef(null) return ( {t('download_logs')} diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/UpdateRobotSoftware.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/UpdateRobotSoftware.tsx similarity index 83% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/UpdateRobotSoftware.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/UpdateRobotSoftware.tsx index 5308068cc5a..a893c616508 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/UpdateRobotSoftware.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/UpdateRobotSoftware.tsx @@ -1,10 +1,11 @@ -import * as React from 'react' +import { useRef } from 'react' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' import { css } from 'styled-components' import { ALIGN_CENTER, + Banner, Box, Flex, JUSTIFY_SPACE_BETWEEN, @@ -18,13 +19,13 @@ import { StyledText, } from '@opentrons/components' -import { ExternalLink } from '../../../../atoms/Link/ExternalLink' -import { TertiaryButton } from '../../../../atoms/buttons' -import { getRobotUpdateDisplayInfo } from '../../../../redux/robot-update' -import { useDispatchStartRobotUpdate } from '../../../../redux/robot-update/hooks' -import { Banner } from '../../../../atoms/Banner' +import { ExternalLink } from '/app/atoms/Link/ExternalLink' +import { TertiaryButton } from '/app/atoms/buttons' +import { getRobotUpdateDisplayInfo } from '/app/redux/robot-update' +import { useDispatchStartRobotUpdate } from '/app/redux/robot-update/hooks' -import type { State } from '../../../../redux/types' +import type { ChangeEventHandler, MouseEventHandler } from 'react' +import type { State } from '/app/redux/types' const OT_APP_UPDATE_PAGE_LINK = 'https://opentrons.com/ot-app/' const HIDDEN_CSS = css` @@ -49,10 +50,10 @@ export function UpdateRobotSoftware({ }) const updateDisabled = updateFromFileDisabledReason !== null const [updateButtonProps, updateButtonTooltipProps] = useHoverTooltip() - const inputRef = React.useRef(null) + const inputRef = useRef(null) const dispatchStartRobotUpdate = useDispatchStartRobotUpdate() - const handleChange: React.ChangeEventHandler = event => { + const handleChange: ChangeEventHandler = event => { const { files } = event.target if (files?.length === 1 && !updateDisabled) { dispatchStartRobotUpdate(robotName, files[0].path) @@ -65,7 +66,7 @@ export function UpdateRobotSoftware({ } } - const handleClick: React.MouseEventHandler = () => { + const handleClick: MouseEventHandler = () => { inputRef.current?.click() } diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/UsageSettings.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/UsageSettings.tsx similarity index 86% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/UsageSettings.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/UsageSettings.tsx index 77d70a66382..e8843af6019 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/UsageSettings.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/UsageSettings.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next' @@ -12,11 +12,11 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ToggleButton } from '../../../../atoms/buttons' -import { updateSetting } from '../../../../redux/robot-settings' +import { ToggleButton } from '/app/atoms/buttons' +import { updateSetting } from '/app/redux/robot-settings' -import type { Dispatch } from '../../../../redux/types' -import type { RobotSettingsField } from '../../../../redux/robot-settings/types' +import type { Dispatch } from '/app/redux/types' +import type { RobotSettingsField } from '/app/redux/robot-settings/types' interface UsageSettingsProps { settings: RobotSettingsField | undefined diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/UseOlderAspirateBehavior.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/UseOlderAspirateBehavior.tsx similarity index 84% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/UseOlderAspirateBehavior.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/UseOlderAspirateBehavior.tsx index b1ad886388f..c3496621f18 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/UseOlderAspirateBehavior.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/UseOlderAspirateBehavior.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next' @@ -12,11 +12,11 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ToggleButton } from '../../../../atoms/buttons' -import { updateSetting } from '../../../../redux/robot-settings' +import { ToggleButton } from '/app/atoms/buttons' +import { updateSetting } from '/app/redux/robot-settings' -import type { Dispatch } from '../../../../redux/types' -import type { RobotSettingsField } from '../../../../redux/robot-settings/types' +import type { Dispatch } from '/app/redux/types' +import type { RobotSettingsField } from '/app/redux/robot-settings/types' interface UseOlderAspirateBehaviorProps { settings: RobotSettingsField | undefined diff --git a/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/DeviceReset.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/DeviceReset.test.tsx new file mode 100644 index 00000000000..fae578451ba --- /dev/null +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/DeviceReset.test.tsx @@ -0,0 +1,55 @@ +import { MemoryRouter } from 'react-router-dom' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, vi, expect } from 'vitest' +import '@testing-library/jest-dom/vitest' +import { renderWithProviders } from '/app/__testing-utils__' + +import { i18n } from '/app/i18n' + +import { DeviceReset } from '../DeviceReset' + +const mockUpdateIsEXpanded = vi.fn() + +vi.mock('/app/resources/runs') + +const render = (isRobotBusy = false) => { + return renderWithProviders( + + + , + { i18nInstance: i18n } + ) +} + +describe('RobotSettings DeviceReset', () => { + it('should render title, description, and butoon', () => { + render() + screen.getByText('Device Reset') + screen.getByText( + 'Reset labware calibration, boot scripts, and/or robot calibration to factory settings.' + ) + expect( + screen.getByRole('button', { name: 'Choose reset settings' }) + ).toBeInTheDocument() + }) + + it('should render a slideout when clicking the button', () => { + render() + const button = screen.getByRole('button', { + name: 'Choose reset settings', + }) + fireEvent.click(button) + expect(mockUpdateIsEXpanded).toHaveBeenCalled() + }) + + it('should call update robot status if a robot is busy', () => { + render(true) + const button = screen.getByRole('button', { + name: 'Choose reset settings', + }) + expect(button).toBeDisabled() + }) +}) diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/DisplayRobotName.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/DisplayRobotName.test.tsx similarity index 89% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/DisplayRobotName.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/DisplayRobotName.test.tsx index a2cabc55f5b..8a4cc86e293 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/DisplayRobotName.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/DisplayRobotName.test.tsx @@ -1,11 +1,10 @@ -import * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../../../i18n' +import { i18n } from '/app/i18n' import { DisplayRobotName } from '../DisplayRobotName' diff --git a/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/EnableErrorRecoveryMode.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/EnableErrorRecoveryMode.test.tsx new file mode 100644 index 00000000000..9406e38f768 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/EnableErrorRecoveryMode.test.tsx @@ -0,0 +1,52 @@ +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, vi, expect, beforeEach } from 'vitest' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useErrorRecoverySettingsToggle } from '/app/resources/errorRecovery' +import { EnableErrorRecoveryMode } from '../EnableErrorRecoveryMode' +import type * as React from 'react' + +vi.mock('/app/resources/errorRecovery') + +const mockToggleERSettings = vi.fn() +const render = ( + props: React.ComponentProps +) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} + +describe('EnableErrorRecoveryMode', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { isRobotBusy: false } + + vi.mocked(useErrorRecoverySettingsToggle).mockReturnValue({ + isEREnabled: false, + toggleERSettings: mockToggleERSettings, + }) + }) + + it('should render text and toggle button', () => { + render(props) + screen.getByText('Recovery mode') + screen.getByText('Pause on protocol errors instead of canceling the run.') + expect( + screen.getByLabelText('enable_error_recovery_mode') + ).toBeInTheDocument() + }) + + it('should call a mock function when clicking toggle button', () => { + render(props) + fireEvent.click(screen.getByLabelText('enable_error_recovery_mode')) + expect(mockToggleERSettings).toHaveBeenCalled() + }) + + it('should disable the toggle if the robot is busy', () => { + render({ isRobotBusy: true }) + expect(screen.getByLabelText('enable_error_recovery_mode')).toBeDisabled() + }) +}) diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/EnableStatusLight.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/EnableStatusLight.test.tsx similarity index 86% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/EnableStatusLight.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/EnableStatusLight.test.tsx index 9efcca029bc..2e2cc956bde 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/EnableStatusLight.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/EnableStatusLight.test.tsx @@ -1,14 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, expect, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../../../i18n' -import { useLEDLights } from '../../../hooks' +import { i18n } from '/app/i18n' +import { useLEDLights } from '/app/resources/robot-settings' import { EnableStatusLight } from '../EnableStatusLight' -vi.mock('../../../hooks') +vi.mock('/app/resources/robot-settings') const ROBOT_NAME = 'otie' const mockToggleLights = vi.fn() diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/GantryHoming.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/GantryHoming.test.tsx similarity index 86% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/GantryHoming.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/GantryHoming.test.tsx index 05d074e833b..87c43f494f9 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/GantryHoming.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/GantryHoming.test.tsx @@ -1,16 +1,15 @@ -import * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, expect, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../../../i18n' -import { getRobotSettings } from '../../../../../redux/robot-settings' +import { i18n } from '/app/i18n' +import { getRobotSettings } from '/app/redux/robot-settings' import { GantryHoming } from '../GantryHoming' -vi.mock('../../../../../redux/robot-settings/selectors') +vi.mock('/app/redux/robot-settings/selectors') vi.mock('../../../hooks') const mockSettings = { diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/LegacySettings.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/LegacySettings.test.tsx similarity index 87% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/LegacySettings.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/LegacySettings.test.tsx index 302cffba675..14237d9e738 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/LegacySettings.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/LegacySettings.test.tsx @@ -1,16 +1,15 @@ -import * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, expect, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../../../i18n' -import { getRobotSettings } from '../../../../../redux/robot-settings' +import { i18n } from '/app/i18n' +import { getRobotSettings } from '/app/redux/robot-settings' import { LegacySettings } from '../LegacySettings' -vi.mock('../../../../../redux/robot-settings/selectors') +vi.mock('/app/redux/robot-settings/selectors') const mockSettings = { id: 'deckCalibrationDots', diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/OpenJupyterControl.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/OpenJupyterControl.test.tsx similarity index 89% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/OpenJupyterControl.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/OpenJupyterControl.test.tsx index ce776c45e4b..57a01e25680 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/OpenJupyterControl.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/OpenJupyterControl.test.tsx @@ -1,17 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' -import { - useTrackEvent, - ANALYTICS_JUPYTER_OPEN, -} from '../../../../../redux/analytics' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useTrackEvent, ANALYTICS_JUPYTER_OPEN } from '/app/redux/analytics' import { OpenJupyterControl } from '../OpenJupyterControl' -vi.mock('../../../../../redux/analytics') +vi.mock('/app/redux/analytics') const mockIpAddress = '1.1.1.1' const mockLink = `http://${mockIpAddress}:48888` diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/RobotInformation.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/RobotInformation.test.tsx similarity index 85% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/RobotInformation.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/RobotInformation.test.tsx index 0b2c2bbc7cd..51769c2b7e5 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/RobotInformation.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/RobotInformation.test.tsx @@ -1,21 +1,20 @@ -import * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { getRobotSerialNumber, getRobotFirmwareVersion, getRobotProtocolApiVersion, -} from '../../../../../redux/discovery' -import { useRobot } from '../../../hooks' -import { mockConnectableRobot } from '../../../../../redux/discovery/__fixtures__' +} from '/app/redux/discovery' +import { useRobot } from '/app/redux-resources/robots' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' import { RobotInformation } from '../RobotInformation' -vi.mock('../../../hooks') -vi.mock('../../../../../redux/discovery/selectors') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/redux/discovery/selectors') const MOCK_ROBOT_SERIAL_NUMBER = '0.0.0' const MOCK_FIRMWARE_VERSION = '4.5.6' diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/RobotServerVersion.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/RobotServerVersion.test.tsx similarity index 84% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/RobotServerVersion.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/RobotServerVersion.test.tsx index d192d7f3310..07f2877ec43 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/RobotServerVersion.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/RobotServerVersion.test.tsx @@ -1,20 +1,19 @@ -import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' -import { getRobotApiVersion } from '../../../../../redux/discovery' -import { getRobotUpdateDisplayInfo } from '../../../../../redux/robot-update' -import { mockConnectableRobot } from '../../../../../redux/discovery/__fixtures__' -import { useRobot } from '../../../hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getRobotApiVersion } from '/app/redux/discovery' +import { getRobotUpdateDisplayInfo } from '/app/redux/robot-update' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' import { handleUpdateBuildroot } from '../../UpdateBuildroot' import { RobotServerVersion } from '../RobotServerVersion' +import { useRobot } from '/app/redux-resources/robots' -vi.mock('../../../hooks') -vi.mock('../../../../../redux/robot-update/selectors') -vi.mock('../../../../../redux/discovery/selectors') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/redux/robot-update/selectors') +vi.mock('/app/redux/discovery/selectors') vi.mock('../../UpdateBuildroot') const MOCK_ROBOT_VERSION = '7.7.7' diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/ShortTrashBin.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/ShortTrashBin.test.tsx similarity index 87% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/ShortTrashBin.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/ShortTrashBin.test.tsx index 8088e3acd29..3465535546b 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/ShortTrashBin.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/ShortTrashBin.test.tsx @@ -1,16 +1,15 @@ -import * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../../../i18n' -import { getRobotSettings } from '../../../../../redux/robot-settings' +import { i18n } from '/app/i18n' +import { getRobotSettings } from '/app/redux/robot-settings' import { ShortTrashBin } from '../ShortTrashBin' -vi.mock('../../../../../redux/robot-settings/selectors') +vi.mock('/app/redux/robot-settings/selectors') const mockSettings = { id: 'shortFixedTrash', diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/Troubleshooting.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/Troubleshooting.test.tsx similarity index 83% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/Troubleshooting.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/Troubleshooting.test.tsx index 0025538279a..a5f28ee7da2 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/Troubleshooting.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/Troubleshooting.test.tsx @@ -1,28 +1,28 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { act, waitFor, screen } from '@testing-library/react' import { when } from 'vitest-when' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useHost } from '@opentrons/react-api-client' -import { i18n } from '../../../../../i18n' -import { useToaster } from '../../../../../organisms/ToasterOven' +import { i18n } from '/app/i18n' +import { useToaster } from '/app/organisms/ToasterOven' import { mockConnectableRobot, mockUnreachableRobot, -} from '../../../../../redux/discovery/__fixtures__' -import { useRobot } from '../../../hooks' +} from '/app/redux/discovery/__fixtures__' +import { useRobot } from '/app/redux-resources/robots' import { Troubleshooting } from '../Troubleshooting' import type { HostConfig } from '@opentrons/api-client' -import type { ToasterContextType } from '../../../../ToasterOven/ToasterContext' +import type { ToasterContextType } from '/app/organisms/ToasterOven/ToasterContext' vi.mock('@opentrons/react-api-client') -vi.mock('../../../../../organisms/ToasterOven') -vi.mock('../../../../../redux/discovery/selectors') -vi.mock('../../../hooks') +vi.mock('/app/organisms/ToasterOven') +vi.mock('/app/redux/discovery/selectors') +vi.mock('/app/redux-resources/robots') const ROBOT_NAME = 'otie' const HOST_CONFIG: HostConfig = { hostname: 'localhost' } diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/UpdateRobotSoftware.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/UpdateRobotSoftware.test.tsx similarity index 85% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/UpdateRobotSoftware.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/UpdateRobotSoftware.test.tsx index 1564a65d80d..d8845e81d06 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/UpdateRobotSoftware.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/UpdateRobotSoftware.test.tsx @@ -1,18 +1,17 @@ /* eslint-disable testing-library/no-node-access */ -import * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' -import { getRobotUpdateDisplayInfo } from '../../../../../redux/robot-update' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getRobotUpdateDisplayInfo } from '/app/redux/robot-update' import { UpdateRobotSoftware } from '../UpdateRobotSoftware' -vi.mock('../../../../../redux/robot-settings/selectors') -vi.mock('../../../../../redux/discovery') -vi.mock('../../../../../redux/robot-update/selectors') +vi.mock('/app/redux/robot-settings/selectors') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/robot-update/selectors') vi.mock('../../../hooks') const mockOnUpdateStart = vi.fn() diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/UsageSettings.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/UsageSettings.test.tsx similarity index 88% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/UsageSettings.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/UsageSettings.test.tsx index ba3c025746d..98790f1b3bc 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/UsageSettings.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/UsageSettings.test.tsx @@ -1,16 +1,15 @@ -import * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { screen, fireEvent } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../../../i18n' -import { getRobotSettings } from '../../../../../redux/robot-settings' +import { i18n } from '/app/i18n' +import { getRobotSettings } from '/app/redux/robot-settings' import { UsageSettings } from '../UsageSettings' -vi.mock('../../../../../redux/robot-settings/selectors') +vi.mock('/app/redux/robot-settings/selectors') const mockSettings = { id: 'enableDoorSafetySwitch', diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/UseOlderAspirateBehavior.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/UseOlderAspirateBehavior.test.tsx similarity index 89% rename from app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/UseOlderAspirateBehavior.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/UseOlderAspirateBehavior.test.tsx index 95d71fcaaae..a44111c1c11 100644 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/UseOlderAspirateBehavior.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/__tests__/UseOlderAspirateBehavior.test.tsx @@ -1,16 +1,15 @@ -import * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { screen, fireEvent } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../../../i18n' -import { getRobotSettings } from '../../../../../redux/robot-settings' +import { i18n } from '/app/i18n' +import { getRobotSettings } from '/app/redux/robot-settings' import { UseOlderAspirateBehavior } from '../UseOlderAspirateBehavior' -vi.mock('../../../../../redux/robot-settings/selectors') +vi.mock('/app/redux/robot-settings/selectors') const mockSettings = { id: 'useOldAspirationFunctions', diff --git a/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/index.ts b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/index.ts new file mode 100644 index 00000000000..d68585a08d3 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/RobotSettings/AdvancedTab/index.ts @@ -0,0 +1,15 @@ +export * from './DeviceReset' +export * from './DisplayRobotName' +export * from './EnableStatusLight' +export * from './FactoryMode' +export * from './GantryHoming' +export * from './LegacySettings' +export * from './OpenJupyterControl' +export * from './RobotInformation' +export * from './RobotServerVersion' +export * from './ShortTrashBin' +export * from './Troubleshooting' +export * from './UpdateRobotSoftware' +export * from './UsageSettings' +export * from './UseOlderAspirateBehavior' +export * from './EnableErrorRecoveryMode' diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/FormModal.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/FormModal.tsx similarity index 97% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/FormModal.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/FormModal.tsx index ee94463bb4c..60ce3d2a88e 100644 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/FormModal.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/FormModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Controller } from 'react-hook-form' import styled, { css } from 'styled-components' @@ -7,7 +6,7 @@ import { BUTTON_TYPE_SUBMIT, Flex, } from '@opentrons/components' -import { ScrollableAlertModal } from '../../../../../molecules/modals' +import { ScrollableAlertModal } from '/app/molecules/modals' import { TextField } from './TextField' import { KeyFileField } from './KeyFileField' import { SecurityField } from './SecurityField' diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/FormRow.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/FormRow.tsx similarity index 96% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/FormRow.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/FormRow.tsx index 40a67ff2c68..1481d3f40f9 100644 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/FormRow.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/FormRow.tsx @@ -1,5 +1,5 @@ // presentational components for the wifi connect form -import * as React from 'react' +import type * as React from 'react' import styled from 'styled-components' import { FONT_WEIGHT_SEMIBOLD, SPACING } from '@opentrons/components' diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/KeyFileField.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/KeyFileField.tsx similarity index 95% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/KeyFileField.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/KeyFileField.tsx index da4e9d4db4b..376048ba420 100644 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/KeyFileField.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/KeyFileField.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useRef } from 'react' import { SelectField } from '@opentrons/components' import { FormRow } from './FormRow' @@ -53,7 +53,7 @@ export const KeyFileField = (props: KeyFileFieldProps): JSX.Element => { fieldState ) const options = [makeKeyOptions(wifiKeys), ADD_NEW_KEY_OPTION_GROUP] - const uploadKeyRef = React.useRef(null) + const uploadKeyRef = useRef(null) const handleValueChange = (_: string, value: string): void => { if (value === ADD_NEW_KEY_VALUE) { diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/SecurityField.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/SecurityField.tsx similarity index 98% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/SecurityField.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/SecurityField.tsx index 142a5e6d624..c9fa4e0c069 100644 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/SecurityField.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/SecurityField.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { SelectField } from '@opentrons/components' import { SECURITY_NONE, SECURITY_WPA_PSK } from '../constants' diff --git a/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/TextField.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/TextField.tsx new file mode 100644 index 00000000000..2242dabea91 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/TextField.tsx @@ -0,0 +1,52 @@ +import { useReducer } from 'react' + +import { + LegacyInputField, + DeprecatedCheckboxField, + INPUT_TYPE_TEXT, + INPUT_TYPE_PASSWORD, +} from '@opentrons/components' + +import { FormRow } from './FormRow' +import { useConnectFormField } from './form-state' +import { LABEL_SHOW_PASSWORD } from '../i18n' +import type { + ControllerFieldState, + ControllerRenderProps, + FieldValues, +} from 'react-hook-form' + +export interface TextFieldProps { + id: string + name: string + label: string + isPassword: boolean + field: ControllerRenderProps + fieldState: ControllerFieldState + className?: string +} + +export const TextField = (props: TextFieldProps): JSX.Element => { + const { id, name, label, isPassword, className, field, fieldState } = props + const { value, error, onChange, onBlur } = useConnectFormField( + field, + fieldState + ) + const [showPw, toggleShowPw] = useReducer(show => !show, false) + const type = isPassword && !showPw ? INPUT_TYPE_PASSWORD : INPUT_TYPE_TEXT + + return ( + + + {isPassword && ( + + )} + + ) +} diff --git a/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/UploadKeyInput.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/UploadKeyInput.tsx new file mode 100644 index 00000000000..4e79bba2cdc --- /dev/null +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/UploadKeyInput.tsx @@ -0,0 +1,73 @@ +import { forwardRef, useEffect, useRef } from 'react' +import styled from 'styled-components' +import { useSelector } from 'react-redux' +import last from 'lodash/last' + +import { useDispatchApiRequest } from '/app/redux/robot-api' +import { postWifiKeys, getWifiKeyByRequestId } from '/app/redux/networking' + +import type { ChangeEventHandler, ForwardedRef } from 'react' +import type { State } from '/app/redux/types' + +export interface UploadKeyInputProps { + robotName: string + label: string + onUpload: (keyId: string) => unknown +} + +// TODO(mc, 2020-03-04): create styled HiddenInput in components library +const HiddenInput = styled.input` + position: absolute; + overflow: hidden; + clip: rect(0 0 0 0); + height: 1px; + width: 1px; + margin: -1px; + padding: 0; + border: 0; +` + +const UploadKeyInputComponent = ( + props: UploadKeyInputProps, + ref: ForwardedRef +): JSX.Element => { + const { robotName, label, onUpload } = props + const [dispatchApi, requestIds] = useDispatchApiRequest() + const handleUpload = useRef<(key: string) => void>() + + const createdKeyId = useSelector((state: State) => { + return getWifiKeyByRequestId(state, robotName, last(requestIds) ?? null) + })?.id + + const handleFileInput: ChangeEventHandler = event => { + if (event.target.files && event.target.files.length > 0) { + const file = event.target.files[0] + event.target.value = '' + + dispatchApi(postWifiKeys(robotName, file)) + } + } + + useEffect(() => { + handleUpload.current = onUpload + }, [onUpload]) + + useEffect(() => { + if (createdKeyId != null && handleUpload.current) { + handleUpload.current(createdKeyId) + } + }, [createdKeyId]) + + return ( + + ) +} + +export const UploadKeyInput = forwardRef( + UploadKeyInputComponent +) diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/ConnectModal.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/ConnectModal.test.tsx similarity index 100% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/ConnectModal.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/ConnectModal.test.tsx diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/FormModal.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/FormModal.test.tsx similarity index 100% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/FormModal.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/FormModal.test.tsx diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/KeyFileField.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/KeyFileField.test.tsx similarity index 100% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/KeyFileField.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/KeyFileField.test.tsx diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/SecurityField.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/SecurityField.test.tsx similarity index 100% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/SecurityField.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/SecurityField.test.tsx diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/TextField.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/TextField.test.tsx similarity index 100% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/TextField.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/TextField.test.tsx diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/UploadKeyInput.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/UploadKeyInput.test.tsx similarity index 100% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/UploadKeyInput.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/UploadKeyInput.test.tsx diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/form-fields.test.ts b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/form-fields.test.ts similarity index 98% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/form-fields.test.ts rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/form-fields.test.ts index cc346be3ad9..80336fb0139 100644 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/form-fields.test.ts +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/form-fields.test.ts @@ -1,4 +1,4 @@ -import * as Fixtures from '../../../../../../redux/networking/__fixtures__' +import * as Fixtures from '/app/redux/networking/__fixtures__' import { describe, it, expect } from 'vitest' import { @@ -8,7 +8,7 @@ import { SECURITY_WPA_EAP, SECURITY_WPA_PSK, SECURITY_NONE, -} from '../../../../../../redux/networking' +} from '/app/redux/networking' import { FIELD_TYPE_TEXT, diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/form-state.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/form-state.test.tsx similarity index 100% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/form-state.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/__tests__/form-state.test.tsx diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/form-fields.ts b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/form-fields.ts similarity index 100% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/form-fields.ts rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/form-fields.ts diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/form-state.ts b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/form-state.ts similarity index 100% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/form-state.ts rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/form-state.ts diff --git a/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/index.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/index.tsx new file mode 100644 index 00000000000..3e1c731d33e --- /dev/null +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ConnectModal/index.tsx @@ -0,0 +1,115 @@ +import { useForm } from 'react-hook-form' + +import { useResetFormOnSecurityChange } from './form-state' +import { + getConnectFormFields, + validateConnectFormFields, + connectFormToConfigureRequest, +} from './form-fields' + +import { FormModal } from './FormModal' + +import type { Control, Resolver } from 'react-hook-form' +import type { + ConnectFormValues, + WifiConfigureRequest, + WifiNetwork, + WifiKey, + EapOption, +} from '../types' + +export interface ConnectModalProps { + robotName: string + network: WifiNetwork | null + wifiKeys: WifiKey[] + eapOptions: EapOption[] + onConnect: (r: WifiConfigureRequest) => void + onCancel: () => void +} + +interface ConnectModalComponentProps extends ConnectModalProps { + isValid: boolean + values: ConnectFormValues + control: Control + id: string +} + +export const ConnectModal = (props: ConnectModalProps): JSX.Element => { + const { network, eapOptions, onConnect } = props + + const onSubmit = (values: ConnectFormValues): void => { + const request = connectFormToConfigureRequest(network, values) + if (request) onConnect(request) + } + + const handleValidate: Resolver = values => { + let errors = {} + + errors = validateConnectFormFields(network, eapOptions, values, errors) + return { values, errors } + } + + const { + handleSubmit, + formState: { isValid }, + getValues, + control, + } = useForm({ + defaultValues: {}, + resolver: handleValidate, + }) + + const values = getValues() + const id = `ConnectForm__${props.robotName}` + + return ( + + + + ) +} + +export const ConnectModalComponent = ( + props: ConnectModalComponentProps +): JSX.Element => { + const { + robotName, + network, + wifiKeys, + eapOptions, + onCancel, + values, + isValid, + id, + control, + } = props + + const fields = getConnectFormFields( + network, + robotName, + eapOptions, + wifiKeys, + values + ) + + useResetFormOnSecurityChange() + + return ( + + ) +} diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/DisconnectModal.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/DisconnectModal.tsx similarity index 93% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/DisconnectModal.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/DisconnectModal.tsx index 57118478b27..d5efae7a976 100644 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/DisconnectModal.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/DisconnectModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' import last from 'lodash/last' @@ -19,14 +19,14 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { useRobot } from '../../../../organisms/Devices/hooks' -import { CONNECTABLE } from '../../../../redux/discovery' +import { useRobot } from '/app/redux-resources/robots' +import { CONNECTABLE } from '/app/redux/discovery' import { clearWifiStatus, getNetworkInterfaces, postWifiDisconnect, -} from '../../../../redux/networking' -import { useWifiList } from '../../../../resources/networking/hooks' +} from '/app/redux/networking' +import { useWifiList } from '/app/resources/networking/hooks' import { dismissRequest, getRequestById, @@ -34,9 +34,9 @@ import { PENDING, FAILURE, SUCCESS, -} from '../../../../redux/robot-api' +} from '/app/redux/robot-api' -import type { Dispatch, State } from '../../../../redux/types' +import type { Dispatch, State } from '/app/redux/types' export interface DisconnectModalProps { onCancel: () => unknown @@ -113,7 +113,7 @@ export const DisconnectModal = ({ disconnectModalBody = t('disconnect_from_wifi_network_failure', { ssid }) } - React.useEffect(() => { + useEffect(() => { if (isDisconnected) { dispatch(clearWifiStatus(robotName)) } diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ResultModal.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ResultModal.tsx similarity index 89% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/ResultModal.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ResultModal.tsx index 1a043507912..6628c35dfc5 100644 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ResultModal.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/ResultModal.tsx @@ -1,14 +1,12 @@ -import * as React from 'react' - import { AlertModal, SpinnerModal } from '@opentrons/components' import * as Copy from './i18n' -import { ErrorModal } from '../../../../molecules/modals' +import { ErrorModal } from '/app/molecules/modals' import { DISCONNECT } from './constants' -import { PENDING, FAILURE } from '../../../../redux/robot-api' +import { PENDING, FAILURE } from '/app/redux/robot-api' import type { NetworkChangeType } from './types' -import type { RequestStatus } from '../../../../redux/robot-api/types' +import type { RequestStatus } from '/app/redux/robot-api/types' export interface ResultModalProps { type: NetworkChangeType diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/SelectSsid/NetworkOptionLabel.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/SelectSsid/NetworkOptionLabel.tsx similarity index 92% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/SelectSsid/NetworkOptionLabel.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/SelectSsid/NetworkOptionLabel.tsx index 42b65948e58..0a982be826f 100644 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/SelectSsid/NetworkOptionLabel.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/SelectSsid/NetworkOptionLabel.tsx @@ -1,11 +1,10 @@ -import * as React from 'react' import styled from 'styled-components' import { Icon, FONT_BODY_1_DARK, SPACING } from '@opentrons/components' -import { SECURITY_NONE } from '../../../../../redux/networking' +import { SECURITY_NONE } from '/app/redux/networking' import type { StyledComponent } from 'styled-components' import type { IconName } from '@opentrons/components' -import type { WifiNetwork } from '../../../../../redux/networking/types' +import type { WifiNetwork } from '/app/redux/networking/types' const SIGNAL_LEVEL_LOW: number = 25 const SIGNAL_LEVEL_MED: number = 50 diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/SelectSsid/__tests__/NetworkOptionLabel.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/SelectSsid/__tests__/NetworkOptionLabel.test.tsx similarity index 100% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/SelectSsid/__tests__/NetworkOptionLabel.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/SelectSsid/__tests__/NetworkOptionLabel.test.tsx diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/SelectSsid/__tests__/SelectSsid.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/SelectSsid/__tests__/SelectSsid.test.tsx similarity index 100% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/SelectSsid/__tests__/SelectSsid.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/SelectSsid/__tests__/SelectSsid.test.tsx diff --git a/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/SelectSsid/index.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/SelectSsid/index.tsx new file mode 100644 index 00000000000..b85cc72d563 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/SelectSsid/index.tsx @@ -0,0 +1,79 @@ +import type * as React from 'react' +import { CONTEXT_MENU } from '@opentrons/components' +import { SelectField } from '/app/atoms/SelectField' +import * as Copy from '../i18n' +import { NetworkOptionLabel, NetworkActionLabel } from './NetworkOptionLabel' + +import type { SelectOptionOrGroup } from '@opentrons/components' + +import type { WifiNetwork } from '../types' + +export interface SelectSsidProps { + list: WifiNetwork[] + value: string | null + onConnect: (ssid: string) => unknown + onJoinOther: () => unknown + isRobotBusy: boolean +} + +const FIELD_NAME = '__SelectSsid__' + +const JOIN_OTHER_VALUE = '__join-other-network__' + +const SELECT_JOIN_OTHER_GROUP = { + options: [{ value: JOIN_OTHER_VALUE, label: Copy.LABEL_JOIN_OTHER_NETWORK }], +} + +const formatOptions = (list: WifiNetwork[]): SelectOptionOrGroup[] => { + const ssidOptionsList = { + options: list?.map(({ ssid }) => ({ value: ssid })), + } + const options = [ssidOptionsList, SELECT_JOIN_OTHER_GROUP] + + return options +} + +export function SelectSsid(props: SelectSsidProps): JSX.Element { + const { list, value, onConnect, onJoinOther, isRobotBusy } = props + + const handleValueChange = (_: string, value: string): void => { + if (value === JOIN_OTHER_VALUE) { + onJoinOther() + } else { + onConnect(value) + } + } + + const formatOptionLabel: React.ComponentProps< + typeof SelectField + >['formatOptionLabel'] = (option, { context }): JSX.Element | null => { + const { value, label } = option + + if (label != null) return + const network = list.find(nw => nw.ssid === value) + + // react-select sets context to tell us if the value is rendered in the + // options menu list or in the currently selected value. If it's being + // rendered in the menu, we want to show a connected icon if the network + // is active, but if the context is value, we want to hide the icon + return network != null ? ( + + ) : null + } + + return ( + + ) +} diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/__tests__/DisconnectModal.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/__tests__/DisconnectModal.test.tsx similarity index 85% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/__tests__/DisconnectModal.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/__tests__/DisconnectModal.test.tsx index 79823d81ef3..7976784878d 100644 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/__tests__/DisconnectModal.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/__tests__/DisconnectModal.test.tsx @@ -1,24 +1,23 @@ -import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { when } from 'vitest-when' -import { i18n } from '../../../../../i18n' -import { useRobot } from '../../../../../organisms/Devices/hooks' -import { useWifiList } from '../../../../../resources/networking/hooks' +import { i18n } from '/app/i18n' +import { useRobot } from '/app/redux-resources/robots' +import { useWifiList } from '/app/resources/networking/hooks' import { mockConnectableRobot, mockReachableRobot, -} from '../../../../../redux/discovery/__fixtures__' +} from '/app/redux/discovery/__fixtures__' import { clearWifiStatus, getNetworkInterfaces, INTERFACE_WIFI, postWifiDisconnect, -} from '../../../../../redux/networking' -import { mockWifiNetwork } from '../../../../../redux/networking/__fixtures__' +} from '/app/redux/networking' +import { mockWifiNetwork } from '/app/redux/networking/__fixtures__' import { dismissRequest, getRequestById, @@ -26,17 +25,17 @@ import { PENDING, FAILURE, SUCCESS, -} from '../../../../../redux/robot-api' +} from '/app/redux/robot-api' import { DisconnectModal } from '../DisconnectModal' -import type { DispatchApiRequestType } from '../../../../../redux/robot-api' -import type { RequestState } from '../../../../../redux/robot-api/types' -import type { State } from '../../../../../redux/types' +import type { DispatchApiRequestType } from '/app/redux/robot-api' +import type { RequestState } from '/app/redux/robot-api/types' +import type { State } from '/app/redux/types' -vi.mock('../../../../../resources/networking/hooks') -vi.mock('../../../../../organisms/Devices/hooks') -vi.mock('../../../../../redux/networking') -vi.mock('../../../../../redux/robot-api') +vi.mock('/app/resources/networking/hooks') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/redux/networking') +vi.mock('/app/redux/robot-api') const ROBOT_NAME = 'otie' const LAST_ID = 'a request id' diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/__tests__/ResultModal.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/__tests__/ResultModal.test.tsx similarity index 100% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/__tests__/ResultModal.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/__tests__/ResultModal.test.tsx diff --git a/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/constants.ts b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/constants.ts new file mode 100644 index 00000000000..3348c427c67 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/constants.ts @@ -0,0 +1,20 @@ +export { + AUTH_TYPE_STRING, + AUTH_TYPE_PASSWORD, + AUTH_TYPE_FILE, + SECURITY_NONE, + SECURITY_WPA_EAP, + SECURITY_WPA_PSK, + CONFIGURE_FIELD_SSID, + CONFIGURE_FIELD_PSK, + CONFIGURE_FIELD_SECURITY_TYPE, + CONFIGURE_PSK_MIN_LENGTH, +} from '/app/redux/networking' + +export const CONNECT: 'connect' = 'connect' +export const DISCONNECT: 'disconnect' = 'disconnect' +export const JOIN_OTHER: 'join-other' = 'join-other' + +export const FIELD_TYPE_TEXT: 'text' = 'text' +export const FIELD_TYPE_KEY_FILE: 'key-file' = 'key-file' +export const FIELD_TYPE_SECURITY: 'security' = 'security' diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/i18n.ts b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/i18n.ts similarity index 98% rename from app/src/organisms/Devices/RobotSettings/ConnectNetwork/i18n.ts rename to app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/i18n.ts index 2ee8344277c..cfee1e77d89 100644 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/i18n.ts +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/i18n.ts @@ -3,7 +3,7 @@ import { SECURITY_WPA_PSK, SECURITY_WPA_EAP, SECURITY_NONE, -} from '../../../../redux/networking' +} from '/app/redux/networking' import type { WifiNetwork } from './types' diff --git a/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/types.ts b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/types.ts new file mode 100644 index 00000000000..050d26c08c3 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/RobotSettings/ConnectNetwork/types.ts @@ -0,0 +1,87 @@ +import type { FieldError } from 'react-hook-form' +import type { + WifiNetwork, + EapOption, + WifiKey, +} from '/app/redux/networking/types' + +import type { + CONNECT, + DISCONNECT, + JOIN_OTHER, + FIELD_TYPE_TEXT, + FIELD_TYPE_KEY_FILE, + FIELD_TYPE_SECURITY, +} from './constants' + +export type { + WifiNetwork, + WifiSecurityType, + WifiAuthField, + WifiEapConfig, + WifiConfigureRequest, + WifiKey, + EapOption, +} from '/app/redux/networking/types' + +export type NetworkChangeType = + | typeof CONNECT + | typeof DISCONNECT + | typeof JOIN_OTHER + +export type NetworkChangeState = + | { type: typeof CONNECT; ssid: string; network: WifiNetwork } + | { type: typeof DISCONNECT; ssid: string } + | { type: typeof JOIN_OTHER; ssid: string | null } + | { type: null } + +export type ConnectFormValues = Partial<{ + ssid?: string + psk?: string + // securityType form value may be securityType or eapConfig.eapType + securityType?: string + eapConfig?: { + [eapOption: string]: string + } +}> + +export type ConnectFormErrors = ConnectFormValues & FieldError + +interface ConnectFormFieldCommon { + name: string + label: string +} + +export interface ConnectFormTextField extends ConnectFormFieldCommon { + type: typeof FIELD_TYPE_TEXT + isPassword: boolean +} + +export interface ConnectFormKeyField extends ConnectFormFieldCommon { + type: typeof FIELD_TYPE_KEY_FILE + robotName: string + wifiKeys: WifiKey[] + placeholder: string +} + +// UI only auth field; server will never return this field type +export interface ConnectFormSecurityField extends ConnectFormFieldCommon { + type: typeof FIELD_TYPE_SECURITY + eapOptions: EapOption[] + showAllOptions: boolean + placeholder: string +} + +export type ConnectFormField = + | ConnectFormTextField + | ConnectFormKeyField + | ConnectFormSecurityField + +export type ConnectFormFieldProps = Readonly<{ + value: string | null + error: string | null + onChange: React.ChangeEventHandler + onBlur: React.FocusEventHandler + setValue: (value: string) => unknown + setTouched: (touched: boolean) => unknown +}> diff --git a/app/src/organisms/Devices/RobotSettings/RobotSettingsAdvanced.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/RobotSettingsAdvanced.tsx similarity index 85% rename from app/src/organisms/Devices/RobotSettings/RobotSettingsAdvanced.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/RobotSettingsAdvanced.tsx index 9ae9afdcee4..adf2c92a9a5 100644 --- a/app/src/organisms/Devices/RobotSettings/RobotSettingsAdvanced.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/RobotSettingsAdvanced.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { createPortal } from 'react-dom' import { useSelector, useDispatch } from 'react-redux' @@ -12,13 +12,13 @@ import { LegacyStyledText, } from '@opentrons/components' -import { Divider } from '../../../atoms/structure' -import { ToggleButton } from '../../../atoms/buttons' -import { useIsFlex, useIsRobotBusy, useRobot } from '../hooks' +import { Divider } from '/app/atoms/structure' +import { ToggleButton } from '/app/atoms/buttons' import { DeviceReset, DisplayRobotName, EnableStatusLight, + EnableErrorRecoveryMode, FactoryMode, GantryHoming, LegacySettings, @@ -31,26 +31,32 @@ import { UsageSettings, UseOlderAspirateBehavior, } from './AdvancedTab' +import { + useRobot, + useIsFlex, + useIsRobotBusy, +} from '/app/redux-resources/robots' import { updateSetting, getRobotSettings, fetchSettings, -} from '../../../redux/robot-settings' +} from '/app/redux/robot-settings' import { RenameRobotSlideout } from './AdvancedTab/AdvancedTabSlideouts/RenameRobotSlideout' import { DeviceResetSlideout } from './AdvancedTab/AdvancedTabSlideouts/DeviceResetSlideout' import { DeviceResetModal } from './AdvancedTab/AdvancedTabSlideouts/DeviceResetModal' import { FactoryModeSlideout } from './AdvancedTab/AdvancedTabSlideouts/FactoryModeSlideout' import { handleUpdateBuildroot } from './UpdateBuildroot' -import { getRobotSerialNumber, UNREACHABLE } from '../../../redux/discovery' -import { getTopPortalEl } from '../../../App/portal' -import { useIsEstopNotDisengaged } from '../../../resources/devices/hooks/useIsEstopNotDisengaged' +import { getRobotSerialNumber, UNREACHABLE } from '/app/redux/discovery' +import { getTopPortalEl } from '/app/App/portal' +import { useIsEstopNotDisengaged } from '/app/resources/devices/hooks/useIsEstopNotDisengaged' -import type { State, Dispatch } from '../../../redux/types' +import type { MouseEventHandler } from 'react' +import type { State, Dispatch } from '/app/redux/types' import type { RobotSettings, RobotSettingsField, -} from '../../../redux/robot-settings/types' -import type { ResetConfigRequest } from '../../../redux/robot-admin/types' +} from '/app/redux/robot-settings/types' +import type { ResetConfigRequest } from '/app/redux/robot-admin/types' interface RobotSettingsAdvancedProps { robotName: string @@ -64,19 +70,18 @@ export function RobotSettingsAdvanced({ const [ showRenameRobotSlideout, setShowRenameRobotSlideout, - ] = React.useState(false) + ] = useState(false) const [ showDeviceResetSlideout, setShowDeviceResetSlideout, - ] = React.useState(false) - const [ - showDeviceResetModal, - setShowDeviceResetModal, - ] = React.useState(false) + ] = useState(false) + const [showDeviceResetModal, setShowDeviceResetModal] = useState( + false + ) const [ showFactoryModeSlideout, setShowFactoryModeSlideout, - ] = React.useState(false) + ] = useState(false) const isRobotBusy = useIsRobotBusy({ poll: true }) const isEstopNotDisengaged = useIsEstopNotDisengaged(robotName) @@ -90,10 +95,8 @@ export function RobotSettingsAdvanced({ const reachable = robot?.status !== UNREACHABLE const sn = robot?.status != null ? getRobotSerialNumber(robot) : null - const [isRobotReachable, setIsRobotReachable] = React.useState( - reachable - ) - const [resetOptions, setResetOptions] = React.useState({}) + const [isRobotReachable, setIsRobotReachable] = useState(reachable) + const [resetOptions, setResetOptions] = useState({}) const findSettings = (id: string): RobotSettingsField | undefined => settings?.find(s => s.id === id) @@ -119,11 +122,11 @@ export function RobotSettingsAdvanced({ const dispatch = useDispatch() - React.useEffect(() => { + useEffect(() => { dispatch(fetchSettings(robotName)) }, [dispatch, robotName]) - React.useEffect(() => { + useEffect(() => { updateRobotStatus(isRobotBusy) }, [isRobotBusy, updateRobotStatus]) @@ -207,6 +210,12 @@ export function RobotSettingsAdvanced({ /> ) : null} + {isFlex ? ( + <> + + + + ) : null} + {isFlex ? ( <> - + ) : null} @@ -279,7 +289,7 @@ export function FeatureFlagToggle({ if (id == null) return null - const handleClick: React.MouseEventHandler = () => { + const handleClick: MouseEventHandler = () => { if (!isRobotBusy) { dispatch(updateSetting(robotName, id, !value)) } diff --git a/app/src/organisms/Devices/RobotSettings/RobotSettingsFeatureFlags.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/RobotSettingsFeatureFlags.tsx similarity index 86% rename from app/src/organisms/Devices/RobotSettings/RobotSettingsFeatureFlags.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/RobotSettingsFeatureFlags.tsx index 90954fc1315..aa1210cdecc 100644 --- a/app/src/organisms/Devices/RobotSettings/RobotSettingsFeatureFlags.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/RobotSettingsFeatureFlags.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useEffect } from 'react' import { useSelector, useDispatch } from 'react-redux' import { @@ -11,17 +11,19 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ToggleButton } from '../../../atoms/buttons' +import { ToggleButton } from '/app/atoms/buttons' import { updateSetting, getRobotSettings, fetchSettings, -} from '../../../redux/robot-settings' -import type { State, Dispatch } from '../../../redux/types' +} from '/app/redux/robot-settings' + +import type { MouseEventHandler } from 'react' +import type { State, Dispatch } from '/app/redux/types' import type { RobotSettings, RobotSettingsField, -} from '../../../redux/robot-settings/types' +} from '/app/redux/robot-settings/types' interface RobotSettingsFeatureFlagsProps { robotName: string @@ -50,7 +52,7 @@ export function RobotSettingsFeatureFlags({ const dispatch = useDispatch() - React.useEffect(() => { + useEffect(() => { dispatch(fetchSettings(robotName)) }, [dispatch, robotName]) @@ -81,7 +83,7 @@ export function FeatureFlagToggle({ if (id == null) return null - const handleClick: React.MouseEventHandler = () => { + const handleClick: MouseEventHandler = () => { dispatch(updateSetting(robotName, id, !value)) } diff --git a/app/src/organisms/Devices/RobotSettings/RobotSettingsNetworking.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/RobotSettingsNetworking.tsx similarity index 93% rename from app/src/organisms/Devices/RobotSettings/RobotSettingsNetworking.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/RobotSettingsNetworking.tsx index 1173432cbb5..f51f90bbb03 100644 --- a/app/src/organisms/Devices/RobotSettings/RobotSettingsNetworking.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/RobotSettingsNetworking.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { useSelector, useDispatch } from 'react-redux' @@ -18,27 +18,23 @@ import { useInterval, } from '@opentrons/components' -import { - useCanDisconnect, - useWifiList, -} from '../../../resources/networking/hooks' -import { ExternalLink } from '../../../atoms/Link/ExternalLink' -import { Divider } from '../../../atoms/structure' +import { useCanDisconnect, useWifiList } from '/app/resources/networking/hooks' +import { ExternalLink } from '/app/atoms/Link/ExternalLink' +import { Divider } from '/app/atoms/structure' import { getRobotAddressesByName, HEALTH_STATUS_OK, OPENTRONS_USB, -} from '../../../redux/discovery' -import { fetchStatus, getNetworkInterfaces } from '../../../redux/networking' - -import { useIsFlex, useIsRobotBusy } from '../hooks' +} from '/app/redux/discovery' +import { fetchStatus, getNetworkInterfaces } from '/app/redux/networking' +import { useIsFlex, useIsRobotBusy } from '/app/redux-resources/robots' import { DisconnectModal } from './ConnectNetwork/DisconnectModal' import { SelectNetwork } from './SelectNetwork' -import { useIsEstopNotDisengaged } from '../../../resources/devices/hooks/useIsEstopNotDisengaged' +import { useIsEstopNotDisengaged } from '/app/resources/devices/hooks/useIsEstopNotDisengaged' -import type { State, Dispatch } from '../../../redux/types' -import { getModalPortalEl } from '../../../App/portal' +import type { State, Dispatch } from '/app/redux/types' +import { getModalPortalEl } from '/app/App/portal' interface NetworkingProps { robotName: string updateRobotStatus: (isRobotBusy: boolean) => void @@ -59,9 +55,7 @@ export function RobotSettingsNetworking({ const isRobotBusy = useIsRobotBusy({ poll: true }) const isFlex = useIsFlex(robotName) - const [showDisconnectModal, setShowDisconnectModal] = React.useState( - false - ) + const [showDisconnectModal, setShowDisconnectModal] = useState(false) const canDisconnect = useCanDisconnect(robotName) @@ -93,7 +87,7 @@ export function RobotSettingsNetworking({ useInterval(() => dispatch(fetchStatus(robotName)), STATUS_REFRESH_MS, true) - React.useEffect(() => { + useEffect(() => { updateRobotStatus(isRobotBusy) }, [isRobotBusy, updateRobotStatus]) diff --git a/app/src/organisms/Devices/RobotSettings/SelectNetwork.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/SelectNetwork.tsx similarity index 89% rename from app/src/organisms/Devices/RobotSettings/SelectNetwork.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/SelectNetwork.tsx index abb73011441..403c88a04bd 100644 --- a/app/src/organisms/Devices/RobotSettings/SelectNetwork.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/SelectNetwork.tsx @@ -1,19 +1,19 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { createPortal } from 'react-dom' import { useDispatch, useSelector } from 'react-redux' import last from 'lodash/last' -import { useWifiList } from '../../../resources/networking/hooks' -import * as RobotApi from '../../../redux/robot-api' -import * as Networking from '../../../redux/networking' -import { getModalPortalEl } from '../../../App/portal' +import { useWifiList } from '/app/resources/networking/hooks' +import * as RobotApi from '/app/redux/robot-api' +import * as Networking from '/app/redux/networking' +import { getModalPortalEl } from '/app/App/portal' import { SelectSsid } from './ConnectNetwork/SelectSsid' import { ConnectModal } from './ConnectNetwork/ConnectModal' import { ResultModal } from './ConnectNetwork/ResultModal' import { CONNECT, JOIN_OTHER } from './ConnectNetwork/constants' -import type { State, Dispatch } from '../../../redux/types' -import type { WifiNetwork } from '../../../redux/networking/types' +import type { State, Dispatch } from '/app/redux/types' +import type { WifiNetwork } from '/app/redux/networking/types' import type { WifiConfigureRequest, NetworkChangeState, @@ -35,7 +35,7 @@ export const SelectNetwork = ({ const eapOptions = useSelector((state: State) => Networking.getEapOptions(state, robotName) ) - const [changeState, setChangeState] = React.useState({ + const [changeState, setChangeState] = useState({ type: null, }) const dispatch = useDispatch() @@ -53,7 +53,7 @@ export const SelectNetwork = ({ } } - React.useEffect(() => { + useEffect(() => { // if we're connecting to a network, ensure we get the info needed to // populate the configuration forms if (changeState.type === CONNECT || changeState.type === JOIN_OTHER) { diff --git a/app/src/organisms/Devices/RobotSettings/SettingToggle.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/SettingToggle.tsx similarity index 83% rename from app/src/organisms/Devices/RobotSettings/SettingToggle.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/SettingToggle.tsx index e63025142a8..4407779d888 100644 --- a/app/src/organisms/Devices/RobotSettings/SettingToggle.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/SettingToggle.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useDispatch } from 'react-redux' import { @@ -11,10 +11,10 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ToggleButton } from '../../../atoms/buttons' -import { updateSetting } from '../../../redux/robot-settings' -import type { Dispatch } from '../../../redux/types' -import type { RobotSettingsField } from '../../../redux/robot-settings/types' +import { ToggleButton } from '/app/atoms/buttons' +import { updateSetting } from '/app/redux/robot-settings' +import type { Dispatch } from '/app/redux/types' +import type { RobotSettingsField } from '/app/redux/robot-settings/types' interface SettingToggleProps extends RobotSettingsField { robotName: string diff --git a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/MigrationWarningModal.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/MigrationWarningModal.tsx similarity index 91% rename from app/src/organisms/Devices/RobotSettings/UpdateBuildroot/MigrationWarningModal.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/MigrationWarningModal.tsx index fb15cc9afc3..cf41e3efe8a 100644 --- a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/MigrationWarningModal.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/MigrationWarningModal.tsx @@ -1,12 +1,11 @@ -import * as React from 'react' import { css } from 'styled-components' import { useTranslation } from 'react-i18next' import { AlertModal } from '@opentrons/components' -import { UPGRADE } from '../../../../redux/robot-update' +import { UPGRADE } from '/app/redux/robot-update' import type { ButtonProps } from '@opentrons/components' -import type { RobotUpdateType } from '../../../../redux/robot-update/types' +import type { RobotUpdateType } from '/app/redux/robot-update/types' export interface MigrationWarningModalProps { notNowButton: ButtonProps diff --git a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx similarity index 89% rename from app/src/organisms/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx index 5bcb3adae0e..8a4fc045a65 100644 --- a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/RobotUpdateProgressModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' import { css } from 'styled-components' @@ -18,25 +18,26 @@ import { } from '@opentrons/components' import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' -import { ProgressBar } from '../../../../atoms/ProgressBar' +import { ProgressBar } from '/app/atoms/ProgressBar' import { FOOTER_BUTTON_STYLE } from './UpdateRobotModal' import { startRobotUpdate, clearRobotUpdateSession, getRobotUpdateDownloadError, -} from '../../../../redux/robot-update' +} from '/app/redux/robot-update' import { useRobotUpdateInfo } from './useRobotUpdateInfo' -import successIcon from '../../../../assets/images/icon_success.png' +import successIcon from '/app/assets/images/icon_success.png' import { useRobotInitializationStatus, INIT_STATUS, -} from '../../../../resources/health/hooks' +} from '/app/resources/health/hooks' -import type { State } from '../../../../redux/types' +import type { ChangeEventHandler } from 'react' +import type { State } from '/app/redux/types' import type { SetStatusBarCreateCommand } from '@opentrons/shared-data/protocol' -import type { RobotUpdateSession } from '../../../../redux/robot-update/types' +import type { RobotUpdateSession } from '/app/redux/robot-update/types' import type { UpdateStep } from './useRobotUpdateInfo' -import type { RobotInitializationStatus } from '../../../../resources/health/hooks' +import type { RobotInitializationStatus } from '/app/resources/health/hooks' const UPDATE_PROGRESS_BAR_STYLE = css` margin-top: ${SPACING.spacing24}; @@ -67,8 +68,8 @@ export function RobotUpdateProgressModal({ }: RobotUpdateProgressModalProps): JSX.Element { const dispatch = useDispatch() const { t } = useTranslation('device_settings') - const [showFileSelect, setShowFileSelect] = React.useState(false) - const installFromFileRef = React.useRef(null) + const [showFileSelect, setShowFileSelect] = useState(false) + const installFromFileRef = useRef(null) const completeRobotUpdateHandler = (): void => { if (closeUpdateBuildroot != null) closeUpdateBuildroot() @@ -85,14 +86,14 @@ export function RobotUpdateProgressModal({ useStatusBarAnimation(error != null) useCleanupRobotUpdateSessionOnDismount() - const handleFileSelect: React.ChangeEventHandler = event => { + const handleFileSelect: ChangeEventHandler = event => { const { files } = event.target if (files?.length === 1) { dispatch(startRobotUpdate(robotName, files[0].path)) } setShowFileSelect(false) } - React.useEffect(() => { + useEffect(() => { if (showFileSelect && installFromFileRef.current) installFromFileRef.current.click() }, [showFileSelect]) @@ -233,13 +234,11 @@ function useAllowExitIfUpdateStalled( progressPercent: number, robotInitStatus: RobotInitializationStatus ): boolean { - const [letUserExitUpdate, setLetUserExitUpdate] = React.useState( - false - ) - const prevSeenUpdateProgress = React.useRef(null) - const exitTimeoutRef = React.useRef(null) + const [letUserExitUpdate, setLetUserExitUpdate] = useState(false) + const prevSeenUpdateProgress = useRef(null) + const exitTimeoutRef = useRef(null) - React.useEffect(() => { + useEffect(() => { if (updateStep === 'initial' && prevSeenUpdateProgress.current !== null) { prevSeenUpdateProgress.current = null } else if (progressPercent !== prevSeenUpdateProgress.current) { @@ -258,7 +257,7 @@ function useAllowExitIfUpdateStalled( } }, [progressPercent, updateStep, robotInitStatus]) - React.useEffect(() => { + useEffect(() => { return () => { if (exitTimeoutRef.current) clearTimeout(exitTimeoutRef.current) } @@ -298,13 +297,13 @@ function useStatusBarAnimation(isError: boolean): void { } } - React.useEffect(startUpdatingAnimation, []) - React.useEffect(startIdleAnimationIfFailed, [isError]) + useEffect(startUpdatingAnimation, []) + useEffect(startIdleAnimationIfFailed, [isError]) } function useCleanupRobotUpdateSessionOnDismount(): void { const dispatch = useDispatch() - React.useEffect(() => { + useEffect(() => { return () => { dispatch(clearRobotUpdateSession()) } diff --git a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/UpdateRobotModal.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/UpdateRobotModal.tsx similarity index 88% rename from app/src/organisms/Devices/RobotSettings/UpdateBuildroot/UpdateRobotModal.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/UpdateRobotModal.tsx index 299bd14be75..4b2225fe868 100644 --- a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/UpdateRobotModal.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/UpdateRobotModal.tsx @@ -1,10 +1,11 @@ -import * as React from 'react' +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' import styled, { css } from 'styled-components' import { ALIGN_CENTER, + Banner, BORDERS, DIRECTION_COLUMN, Flex, @@ -26,15 +27,14 @@ import { REINSTALL, DOWNGRADE, getRobotUpdateVersion, -} from '../../../../redux/robot-update' -import { ExternalLink } from '../../../../atoms/Link/ExternalLink' -import { ReleaseNotes } from '../../../../molecules/ReleaseNotes' -import { useIsRobotBusy } from '../../hooks' -import { Banner } from '../../../../atoms/Banner' -import { useDispatchStartRobotUpdate } from '../../../../redux/robot-update/hooks' +} from '/app/redux/robot-update' +import { ExternalLink } from '/app/atoms/Link/ExternalLink' +import { ReleaseNotes } from '/app/molecules/ReleaseNotes' +import { useIsRobotBusy } from '/app/redux-resources/robots' +import { useDispatchStartRobotUpdate } from '/app/redux/robot-update/hooks' -import type { State, Dispatch } from '../../../../redux/types' -import type { RobotSystemType } from '../../../../redux/robot-update/types' +import type { State, Dispatch } from '/app/redux/types' +import type { RobotSystemType } from '/app/redux/robot-update/types' export const RELEASE_NOTES_URL_BASE = 'https://github.com/Opentrons/opentrons/releases/tag/v' @@ -91,7 +91,7 @@ export function UpdateRobotModal({ disabledReason = updateFromFileDisabledReason else if (isRobotBusy) disabledReason = t('robot_busy_protocol') - React.useEffect(() => { + useEffect(() => { dispatch(robotUpdateChangelogSeen(robotName)) }, [robotName]) diff --git a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/ViewUpdateModal.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/ViewUpdateModal.tsx similarity index 76% rename from app/src/organisms/Devices/RobotSettings/UpdateBuildroot/ViewUpdateModal.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/ViewUpdateModal.tsx index 7a692537a78..dceeb7672f5 100644 --- a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/ViewUpdateModal.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/ViewUpdateModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useSelector } from 'react-redux' @@ -8,15 +8,15 @@ import { getRobotUpdateDownloadError, getRobotSystemType, getRobotUpdateAvailable, -} from '../../../../redux/robot-update' -import { getAvailableShellUpdate } from '../../../../redux/shell' -import { getTopPortalEl } from '../../../../App/portal' -import { UpdateAppModal } from '../../../../organisms/UpdateAppModal' +} from '/app/redux/robot-update' +import { getAvailableShellUpdate } from '/app/redux/shell' +import { getTopPortalEl } from '/app/App/portal' +import { UpdateAppModal } from '/app/organisms/Desktop/UpdateAppModal' import { MigrationWarningModal } from './MigrationWarningModal' import { UpdateRobotModal } from './UpdateRobotModal' -import type { State } from '../../../../redux/types' -import type { ReachableRobot, Robot } from '../../../../redux/discovery/types' +import type { State } from '/app/redux/types' +import type { ReachableRobot, Robot } from '/app/redux/discovery/types' export interface ViewUpdateModalProps { robotName: string @@ -28,7 +28,7 @@ export function ViewUpdateModal( props: ViewUpdateModalProps ): JSX.Element | null { const { robotName, robot, closeModal } = props - const [showAppUpdateModal, setShowAppUpdateModal] = React.useState(true) + const [showAppUpdateModal, setShowAppUpdateModal] = useState(true) const updateInfo = useSelector((state: State) => getRobotUpdateInfo(state, robotName) @@ -44,10 +44,9 @@ export function ViewUpdateModal( useSelector(getAvailableShellUpdate) ) - const [ - showMigrationWarning, - setShowMigrationWarning, - ] = React.useState(robotSystemType === OT2_BALENA) + const [showMigrationWarning, setShowMigrationWarning] = useState( + robotSystemType === OT2_BALENA + ) const notNowButton = { onClick: closeModal, diff --git a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/RobotUpdateProgressModal.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/__tests__/RobotUpdateProgressModal.test.tsx similarity index 92% rename from app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/RobotUpdateProgressModal.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/__tests__/RobotUpdateProgressModal.test.tsx index 9123723358f..a2139f636bd 100644 --- a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/RobotUpdateProgressModal.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/__tests__/RobotUpdateProgressModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' -import { i18n } from '../../../../../i18n' +import type * as React from 'react' +import { i18n } from '/app/i18n' import { act, fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' import { RobotUpdateProgressModal, @@ -14,21 +14,21 @@ import { useRobotUpdateInfo } from '../useRobotUpdateInfo' import { getRobotSessionIsManualFile, getRobotUpdateDownloadError, -} from '../../../../../redux/robot-update' -import { useDispatchStartRobotUpdate } from '../../../../../redux/robot-update/hooks' +} from '/app/redux/robot-update' +import { useDispatchStartRobotUpdate } from '/app/redux/robot-update/hooks' import { useRobotInitializationStatus, INIT_STATUS, -} from '../../../../../resources/health/hooks' +} from '/app/resources/health/hooks' import type { SetStatusBarCreateCommand } from '@opentrons/shared-data' -import type { RobotUpdateSession } from '../../../../../redux/robot-update/types' +import type { RobotUpdateSession } from '/app/redux/robot-update/types' vi.mock('@opentrons/react-api-client') vi.mock('../useRobotUpdateInfo') -vi.mock('../../../../../redux/robot-update') -vi.mock('../../../../../redux/robot-update/hooks') -vi.mock('../../../../../resources/health/hooks') +vi.mock('/app/redux/robot-update') +vi.mock('/app/redux/robot-update/hooks') +vi.mock('/app/resources/health/hooks') const render = ( props: React.ComponentProps diff --git a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/UpdateBuildroot.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/__tests__/UpdateBuildroot.test.tsx similarity index 100% rename from app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/UpdateBuildroot.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/__tests__/UpdateBuildroot.test.tsx diff --git a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/UpdateRobotModal.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/__tests__/UpdateRobotModal.test.tsx similarity index 87% rename from app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/UpdateRobotModal.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/__tests__/UpdateRobotModal.test.tsx index 82df7b0ea19..e77a0df9533 100644 --- a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/UpdateRobotModal.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/__tests__/UpdateRobotModal.test.tsx @@ -1,25 +1,25 @@ -import * as React from 'react' +import type * as React from 'react' import { createStore } from 'redux' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../../../i18n' +import { i18n } from '/app/i18n' import { getRobotUpdateDisplayInfo, getRobotUpdateVersion, -} from '../../../../../redux/robot-update' -import { getDiscoverableRobotByName } from '../../../../../redux/discovery' +} from '/app/redux/robot-update' +import { getDiscoverableRobotByName } from '/app/redux/discovery' import { UpdateRobotModal, RELEASE_NOTES_URL_BASE } from '../UpdateRobotModal' -import { useIsRobotBusy } from '../../../hooks' +import { useIsRobotBusy } from '/app/redux-resources/robots' import type { Store } from 'redux' -import type { State } from '../../../../../redux/types' +import type { State } from '/app/redux/types' -vi.mock('../../../../../redux/robot-update') -vi.mock('../../../../../redux/discovery') -vi.mock('../../../hooks') +vi.mock('/app/redux/robot-update') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux-resources/robots') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/ViewUpdateModal.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/__tests__/ViewUpdateModal.test.tsx similarity index 100% rename from app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/ViewUpdateModal.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/__tests__/ViewUpdateModal.test.tsx diff --git a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/useRobotUpdateInfo.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/__tests__/useRobotUpdateInfo.test.tsx similarity index 94% rename from app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/useRobotUpdateInfo.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/__tests__/useRobotUpdateInfo.test.tsx index 2a05b883301..2897110aacc 100644 --- a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/__tests__/useRobotUpdateInfo.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/__tests__/useRobotUpdateInfo.test.tsx @@ -1,23 +1,23 @@ -import * as React from 'react' +import type * as React from 'react' import { renderHook } from '@testing-library/react' import { createStore } from 'redux' import { I18nextProvider } from 'react-i18next' import { Provider } from 'react-redux' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../../../i18n' +import { i18n } from '/app/i18n' import { useRobotUpdateInfo } from '../useRobotUpdateInfo' -import { getRobotUpdateDownloadProgress } from '../../../../../redux/robot-update' +import { getRobotUpdateDownloadProgress } from '/app/redux/robot-update' import type { Store } from 'redux' -import type { State } from '../../../../../redux/types' +import type { State } from '/app/redux/types' import type { RobotUpdateSession, UpdateSessionStep, UpdateSessionStage, -} from '../../../../../redux/robot-update/types' +} from '/app/redux/robot-update/types' -vi.mock('../../../../../redux/robot-update') +vi.mock('/app/redux/robot-update') describe('useRobotUpdateInfo', () => { let store: Store diff --git a/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/index.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/index.tsx new file mode 100644 index 00000000000..c5802005a82 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/index.tsx @@ -0,0 +1,80 @@ +import { useRef, useEffect, useCallback } from 'react' +import { useSelector, useDispatch } from 'react-redux' +import NiceModal, { useModal } from '@ebay/nice-modal-react' + +import { ApiHostProvider } from '@opentrons/react-api-client' + +import { + setRobotUpdateSeen, + robotUpdateIgnored, + getRobotUpdateSession, +} from '/app/redux/robot-update' +import { ViewUpdateModal } from './ViewUpdateModal' +import { RobotUpdateProgressModal } from './RobotUpdateProgressModal' +import { UNREACHABLE, OPENTRONS_USB } from '/app/redux/discovery' +import { appShellRequestor } from '/app/redux/shell/remote' + +import type { Dispatch } from '/app/redux/types' +import type { DiscoveredRobot } from '/app/redux/discovery/types' + +interface UpdateBuildrootProps { + robot: DiscoveredRobot | null +} + +export const handleUpdateBuildroot = ( + robot: UpdateBuildrootProps['robot'] +): void => { + NiceModal.show(UpdateBuildroot, { robot }) +} + +const UpdateBuildroot = NiceModal.create( + (props: UpdateBuildrootProps): JSX.Element | null => { + const { robot } = props + const hasSeenSessionOnce = useRef(false) + const modal = useModal() + const robotName = useRef(robot?.name ?? '') + const dispatch = useDispatch() + const session = useSelector(getRobotUpdateSession) + if (!hasSeenSessionOnce.current && session) + hasSeenSessionOnce.current = true + + useEffect(() => { + if (robotName.current) { + dispatch(setRobotUpdateSeen(robotName.current)) + } + }, [robotName]) + + const ignoreUpdate = useCallback(() => { + if (robotName.current) { + dispatch(robotUpdateIgnored(robotName.current)) + } + modal.remove() + }, [robotName, close]) + + if (hasSeenSessionOnce.current) + return ( + + + + ) + else if (robot != null && robot.status !== UNREACHABLE) + return ( + + ) + else return null + } +) diff --git a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/useRobotUpdateInfo.ts b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/useRobotUpdateInfo.ts similarity index 85% rename from app/src/organisms/Devices/RobotSettings/UpdateBuildroot/useRobotUpdateInfo.ts rename to app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/useRobotUpdateInfo.ts index 75c35746c9d..d7a01f7d105 100644 --- a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/useRobotUpdateInfo.ts +++ b/app/src/organisms/Desktop/Devices/RobotSettings/UpdateBuildroot/useRobotUpdateInfo.ts @@ -1,10 +1,10 @@ -import * as React from 'react' +import { useMemo, useState, useRef } from 'react' import { useSelector } from 'react-redux' -import { getRobotUpdateDownloadProgress } from '../../../../redux/robot-update' +import { getRobotUpdateDownloadProgress } from '/app/redux/robot-update' -import type { RobotUpdateSession } from '../../../../redux/robot-update/types' -import type { State } from '../../../../redux/types' +import type { RobotUpdateSession } from '/app/redux/robot-update/types' +import type { State } from '/app/redux/types' export function useRobotUpdateInfo( robotName: string, @@ -12,9 +12,7 @@ export function useRobotUpdateInfo( ): { updateStep: UpdateStep | null; progressPercent: number } { const progressPercent = useFindProgressPercentFrom(robotName, session) - const updateStep = React.useMemo(() => determineUpdateStepFrom(session), [ - session, - ]) + const updateStep = useMemo(() => determineUpdateStepFrom(session), [session]) return { updateStep, @@ -26,11 +24,11 @@ function useFindProgressPercentFrom( robotName: string, session: RobotUpdateSession | null ): number { - const [progressPercent, setProgressPercent] = React.useState(0) - const hasSeenDownloadFileStep = React.useRef(false) - const prevSeenUpdateStep = React.useRef(null) - const prevSeenStepProgress = React.useRef(0) - const currentStepWithProgress = React.useRef(-1) + const [progressPercent, setProgressPercent] = useState(0) + const hasSeenDownloadFileStep = useRef(false) + const prevSeenUpdateStep = useRef(null) + const prevSeenStepProgress = useRef(0) + const currentStepWithProgress = useRef(-1) const downloadProgress = useSelector((state: State) => getRobotUpdateDownloadProgress(state, robotName) diff --git a/app/src/organisms/Devices/RobotSettings/__tests__/RobotSettingsAdvanced.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/__tests__/RobotSettingsAdvanced.test.tsx similarity index 91% rename from app/src/organisms/Devices/RobotSettings/__tests__/RobotSettingsAdvanced.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/__tests__/RobotSettingsAdvanced.test.tsx index 825c9c32cde..ee6bb051f28 100644 --- a/app/src/organisms/Devices/RobotSettings/__tests__/RobotSettingsAdvanced.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/__tests__/RobotSettingsAdvanced.test.tsx @@ -1,14 +1,13 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' import { when } from 'vitest-when' import { describe, it, vi, beforeEach, expect, afterEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { getShellUpdateState } from '../../../../redux/shell' -import { useIsFlex, useIsRobotBusy } from '../../hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getShellUpdateState } from '/app/redux/shell' +import { useIsRobotBusy, useIsFlex } from '/app/redux-resources/robots' import { DeviceReset, DisplayRobotName, @@ -26,19 +25,19 @@ import { } from '../AdvancedTab' import { RobotSettingsAdvanced } from '../RobotSettingsAdvanced' -import type { ShellUpdateState } from '../../../../redux/shell/types' -import type * as ShellUpdate from '../../../../redux/shell/update' +import type { ShellUpdateState } from '/app/redux/shell/types' +import type * as ShellUpdate from '/app/redux/shell/update' -vi.mock('../../../../redux/robot-settings/selectors') -vi.mock('../../../../redux/discovery/selectors') -vi.mock('../../../../redux/shell/update', async importOriginal => { +vi.mock('/app/redux-resources/robots') +vi.mock('/app/redux/robot-settings/selectors') +vi.mock('/app/redux/discovery/selectors') +vi.mock('/app/redux/shell/update', async importOriginal => { const actual = await importOriginal() return { ...actual, getShellUpdateState: vi.fn(), } }) -vi.mock('../../hooks') vi.mock('../AdvancedTab/DeviceReset') vi.mock('../AdvancedTab/DisplayRobotName') vi.mock('../AdvancedTab/EnableStatusLight') diff --git a/app/src/organisms/Devices/RobotSettings/__tests__/RobotSettingsFeatureFlags.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/__tests__/RobotSettingsFeatureFlags.test.tsx similarity index 87% rename from app/src/organisms/Devices/RobotSettings/__tests__/RobotSettingsFeatureFlags.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/__tests__/RobotSettingsFeatureFlags.test.tsx index 1c1e6d8a2ea..186148e6b44 100644 --- a/app/src/organisms/Devices/RobotSettings/__tests__/RobotSettingsFeatureFlags.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/__tests__/RobotSettingsFeatureFlags.test.tsx @@ -1,15 +1,14 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { when } from 'vitest-when' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { RobotSettingsFeatureFlags } from '../RobotSettingsFeatureFlags' -import { getRobotSettings } from '../../../../redux/robot-settings' +import { getRobotSettings } from '/app/redux/robot-settings' -vi.mock('../../../../redux/robot-settings') +vi.mock('/app/redux/robot-settings') const MOCK_FF_FIELD = { id: 'ff_1', diff --git a/app/src/organisms/Devices/RobotSettings/__tests__/RobotSettingsNetworking.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/__tests__/RobotSettingsNetworking.test.tsx similarity index 92% rename from app/src/organisms/Devices/RobotSettings/__tests__/RobotSettingsNetworking.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/__tests__/RobotSettingsNetworking.test.tsx index 66ee4c6f373..4918671b49e 100644 --- a/app/src/organisms/Devices/RobotSettings/__tests__/RobotSettingsNetworking.test.tsx +++ b/app/src/organisms/Desktop/Devices/RobotSettings/__tests__/RobotSettingsNetworking.test.tsx @@ -1,39 +1,36 @@ -import * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { when } from 'vitest-when' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../../i18n' +import { i18n } from '/app/i18n' import { getRobotAddressesByName, HEALTH_STATUS_OK, HEALTH_STATUS_NOT_OK, OPENTRONS_USB, -} from '../../../../redux/discovery' -import * as Networking from '../../../../redux/networking' -import { - useCanDisconnect, - useWifiList, -} from '../../../../resources/networking/hooks' -import * as Fixtures from '../../../../redux/networking/__fixtures__' -import { useIsFlex, useIsRobotBusy } from '../../hooks' +} from '/app/redux/discovery' +import * as Networking from '/app/redux/networking' +import { useCanDisconnect, useWifiList } from '/app/resources/networking/hooks' +import * as Fixtures from '/app/redux/networking/__fixtures__' +import { useIsFlex, useIsRobotBusy } from '/app/redux-resources/robots' + import { DisconnectModal } from '../ConnectNetwork/DisconnectModal' -import { useIsEstopNotDisengaged } from '../../../../resources/devices/hooks/useIsEstopNotDisengaged' +import { useIsEstopNotDisengaged } from '/app/resources/devices/hooks/useIsEstopNotDisengaged' import { RobotSettingsNetworking } from '../RobotSettingsNetworking' -import type { DiscoveryClientRobotAddress } from '../../../../redux/discovery/types' -import type { State } from '../../../../redux/types' +import type { DiscoveryClientRobotAddress } from '/app/redux/discovery/types' +import type { State } from '/app/redux/types' -vi.mock('../../../../redux/discovery/selectors') -vi.mock('../../../../redux/networking') -vi.mock('../../../../redux/robot-api/selectors') -vi.mock('../../../../resources/networking/hooks') -vi.mock('../../hooks') +vi.mock('/app/redux/discovery/selectors') +vi.mock('/app/redux/networking') +vi.mock('/app/redux/robot-api/selectors') +vi.mock('/app/resources/networking/hooks') +vi.mock('/app/redux-resources/robots') vi.mock('../ConnectNetwork/DisconnectModal') -vi.mock('../../../../resources/devices/hooks/useIsEstopNotDisengaged') +vi.mock('/app/resources/devices/hooks/useIsEstopNotDisengaged') const mockUpdateRobotStatus = vi.fn() diff --git a/app/src/organisms/Devices/RobotSettings/__tests__/SelectNetwork.test.tsx b/app/src/organisms/Desktop/Devices/RobotSettings/__tests__/SelectNetwork.test.tsx similarity index 100% rename from app/src/organisms/Devices/RobotSettings/__tests__/SelectNetwork.test.tsx rename to app/src/organisms/Desktop/Devices/RobotSettings/__tests__/SelectNetwork.test.tsx diff --git a/app/src/organisms/Devices/RobotStatusHeader.tsx b/app/src/organisms/Desktop/Devices/RobotStatusHeader.tsx similarity index 92% rename from app/src/organisms/Devices/RobotStatusHeader.tsx rename to app/src/organisms/Desktop/Devices/RobotStatusHeader.tsx index 5d9bac76a68..77cad0933ea 100644 --- a/app/src/organisms/Devices/RobotStatusHeader.tsx +++ b/app/src/organisms/Desktop/Devices/RobotStatusHeader.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { useSelector, useDispatch } from 'react-redux' import { Link, useNavigate } from 'react-router-dom' @@ -24,20 +24,20 @@ import { useInterval, } from '@opentrons/components' -import { QuaternaryButton } from '../../atoms/buttons' -import { useIsFlex } from '../../organisms/Devices/hooks' -import { useCurrentRunStatus } from '../../organisms/RunTimeControl/hooks' +import { QuaternaryButton } from '/app/atoms/buttons' +import { useIsFlex } from '/app/redux-resources/robots' +import { useCurrentRunStatus } from '/app/organisms/RunTimeControl/hooks' import { getRobotAddressesByName, HEALTH_STATUS_OK, OPENTRONS_USB, -} from '../../redux/discovery' -import { getNetworkInterfaces, fetchStatus } from '../../redux/networking' -import { useNotifyRunQuery, useCurrentRunId } from '../../resources/runs' +} from '/app/redux/discovery' +import { getNetworkInterfaces, fetchStatus } from '/app/redux/networking' +import { useNotifyRunQuery, useCurrentRunId } from '/app/resources/runs' import type { IconName, StyleProps } from '@opentrons/components' -import type { DiscoveredRobot } from '../../redux/discovery/types' -import type { Dispatch, State } from '../../redux/types' +import type { DiscoveredRobot } from '/app/redux/discovery/types' +import type { Dispatch, State } from '/app/redux/types' type RobotStatusHeaderProps = StyleProps & Pick & { diff --git a/app/src/organisms/Desktop/Devices/RunPreview/index.tsx b/app/src/organisms/Desktop/Devices/RunPreview/index.tsx new file mode 100644 index 00000000000..c2a48aba59a --- /dev/null +++ b/app/src/organisms/Desktop/Devices/RunPreview/index.tsx @@ -0,0 +1,240 @@ +import { useMemo, useState, forwardRef, useRef } from 'react' +import { css } from 'styled-components' +import { useTranslation } from 'react-i18next' +import { ViewportList } from 'react-viewport-list' + +import { RUN_STATUSES_TERMINAL } from '@opentrons/api-client' +import { + ALIGN_CENTER, + BORDERS, + COLORS, + DIRECTION_COLUMN, + DISPLAY_FLEX, + DISPLAY_NONE, + Flex, + InfoScreen, + LegacyStyledText, + OVERFLOW_SCROLL, + POSITION_FIXED, + PrimaryButton, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' + +import { + useNotifyAllCommandsAsPreSerializedList, + useNotifyRunQuery, + useRunStatus, + useMostRecentCompletedAnalysis, + useLastRunCommand, +} from '/app/resources/runs' +import { CommandText, CommandIcon } from '/app/molecules/Command' +import { Divider } from '/app/atoms/structure' +import { NAV_BAR_WIDTH } from '/app/App/constants' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' + +import type { RunStatus } from '@opentrons/api-client' +import type { RobotType } from '@opentrons/shared-data' +import type { ViewportListRef } from 'react-viewport-list' +const COLOR_FADE_MS = 500 +const LIVE_RUN_COMMANDS_POLL_MS = 3000 +// arbitrary large number of commands +const MAX_COMMANDS = 100000 + +interface RunPreviewProps { + runId: string + robotType: RobotType + jumpedIndex: number | null + makeHandleScrollToStep: (index: number) => () => void +} +export const RunPreviewComponent = ( + { runId, jumpedIndex, makeHandleScrollToStep, robotType }: RunPreviewProps, + ref: React.ForwardedRef +): JSX.Element | null => { + const { t } = useTranslation(['run_details', 'protocol_setup']) + const robotSideAnalysis = useMostRecentCompletedAnalysis(runId) + const runStatus = useRunStatus(runId) + const { data: runRecord } = useNotifyRunQuery(runId) + const isRunTerminal = + runStatus != null + ? (RUN_STATUSES_TERMINAL as RunStatus[]).includes(runStatus) + : false + // we only ever want one request done for terminal runs because this is a heavy request + const { + data: commandsFromQueryResponse, + isLoading: isRunCommandDataLoading, + } = useNotifyAllCommandsAsPreSerializedList( + runId, + { cursor: 0, pageLength: MAX_COMMANDS, includeFixitCommands: false }, + { + enabled: isRunTerminal, + } + ) + const commandsFromQuery = commandsFromQueryResponse?.data + const viewPortRef = useRef(null) + const currentRunCommandKey = useLastRunCommand(runId, { + refetchInterval: LIVE_RUN_COMMANDS_POLL_MS, + })?.key + const [ + isCurrentCommandVisible, + setIsCurrentCommandVisible, + ] = useState(true) + + const isValidRobotSideAnalysis = robotSideAnalysis != null + const allRunDefs = useMemo( + () => + robotSideAnalysis != null + ? getLabwareDefinitionsFromCommands(robotSideAnalysis.commands) + : [], + [isValidRobotSideAnalysis] + ) + + if (robotSideAnalysis == null) { + return null + } + const commands = isRunTerminal + ? commandsFromQuery + : robotSideAnalysis.commands + + // pass relevant data from run rather than analysis so that CommandText utilities can properly hash the entities' IDs + // TODO (nd:05/02/2024, AUTH-380): update name and types for CommandText (and children/utilities) use of analysis. + // We should ideally pass only subset of analysis/run data required by these children and utilities + const protocolDataFromAnalysisOrRun = + isRunTerminal && runRecord?.data != null + ? { + ...robotSideAnalysis, + labware: runRecord.data.labware ?? [], + modules: runRecord.data.modules ?? [], + pipettes: runRecord.data.pipettes ?? [], + liquids: runRecord.data.liquids ?? [], + commands: commands ?? [], + } + : robotSideAnalysis + const currentRunCommandIndex = + commands != null + ? commands.findIndex(c => c.key === currentRunCommandKey) + : 0 + if (isRunCommandDataLoading || commands == null) { + return ( + + + {t('protocol_setup:loading_data')} + + + ) + } + return commands.length === 0 ? ( + + + + ) : ( + + <> + + + {t('run_preview')} + + + {t('steps_total', { count: commands.length })} + + + + {t('preview_of_protocol_steps')} + + + { + if (currentRunCommandIndex >= 0) { + setIsCurrentCommandVisible( + currentRunCommandIndex >= lowestVisibleIndex && + currentRunCommandIndex <= highestVisibleIndex + ) + } + }} + initialIndex={currentRunCommandIndex} + > + {(command, index) => { + const isCurrent = index === currentRunCommandIndex + const backgroundColor = isCurrent ? COLORS.blue30 : COLORS.grey20 + const iconColor = isCurrent ? COLORS.blue60 : COLORS.grey50 + return ( + + + {index + 1} + + + + + + + + + ) + }} + + {currentRunCommandIndex >= 0 ? ( + + {t('view_current_step')} + + ) : null} + {currentRunCommandIndex === commands.length - 1 ? ( + + {t('end_of_protocol')} + + ) : null} + + + ) +} + +export const RunPreview = forwardRef(RunPreviewComponent) diff --git a/app/src/organisms/Devices/__tests__/CalibrationStatusBanner.test.tsx b/app/src/organisms/Desktop/Devices/__tests__/CalibrationStatusBanner.test.tsx similarity index 95% rename from app/src/organisms/Devices/__tests__/CalibrationStatusBanner.test.tsx rename to app/src/organisms/Desktop/Devices/__tests__/CalibrationStatusBanner.test.tsx index 160a51a8225..fd50c5185ae 100644 --- a/app/src/organisms/Devices/__tests__/CalibrationStatusBanner.test.tsx +++ b/app/src/organisms/Desktop/Devices/__tests__/CalibrationStatusBanner.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { MemoryRouter } from 'react-router-dom' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { CalibrationStatusBanner } from '../CalibrationStatusBanner' import { useCalibrationTaskList } from '../hooks' diff --git a/app/src/organisms/Devices/__tests__/ConnectionTroubleshootingModal.test.tsx b/app/src/organisms/Desktop/Devices/__tests__/ConnectionTroubleshootingModal.test.tsx similarity index 93% rename from app/src/organisms/Devices/__tests__/ConnectionTroubleshootingModal.test.tsx rename to app/src/organisms/Desktop/Devices/__tests__/ConnectionTroubleshootingModal.test.tsx index 6347ac1170f..d725929a081 100644 --- a/app/src/organisms/Devices/__tests__/ConnectionTroubleshootingModal.test.tsx +++ b/app/src/organisms/Desktop/Devices/__tests__/ConnectionTroubleshootingModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { fireEvent, screen } from '@testing-library/react' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { ConnectionTroubleshootingModal } from '../ConnectionTroubleshootingModal' const render = ( diff --git a/app/src/organisms/Devices/__tests__/DevicesEmptyState.test.tsx b/app/src/organisms/Desktop/Devices/__tests__/DevicesEmptyState.test.tsx similarity index 82% rename from app/src/organisms/Devices/__tests__/DevicesEmptyState.test.tsx rename to app/src/organisms/Desktop/Devices/__tests__/DevicesEmptyState.test.tsx index 817fac6e746..e05d8499550 100644 --- a/app/src/organisms/Devices/__tests__/DevicesEmptyState.test.tsx +++ b/app/src/organisms/Desktop/Devices/__tests__/DevicesEmptyState.test.tsx @@ -1,17 +1,16 @@ -import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../i18n' -import { startDiscovery } from '../../../redux/discovery' +import { i18n } from '/app/i18n' +import { startDiscovery } from '/app/redux/discovery' import { DevicesEmptyState, TROUBLESHOOTING_CONNECTION_PROBLEMS_URL, } from '../DevicesEmptyState' -vi.mock('../../../redux/discovery') +vi.mock('/app/redux/discovery') const render = () => { return renderWithProviders(, { diff --git a/app/src/organisms/Devices/__tests__/EstopBanner.test.tsx b/app/src/organisms/Desktop/Devices/__tests__/EstopBanner.test.tsx similarity index 90% rename from app/src/organisms/Devices/__tests__/EstopBanner.test.tsx rename to app/src/organisms/Desktop/Devices/__tests__/EstopBanner.test.tsx index 727b92d8845..7d052568378 100644 --- a/app/src/organisms/Devices/__tests__/EstopBanner.test.tsx +++ b/app/src/organisms/Desktop/Devices/__tests__/EstopBanner.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { EstopBanner } from '../EstopBanner' const render = (props: React.ComponentProps) => diff --git a/app/src/organisms/Devices/__tests__/HistoricalProtocolRun.test.tsx b/app/src/organisms/Desktop/Devices/__tests__/HistoricalProtocolRun.test.tsx similarity index 85% rename from app/src/organisms/Devices/__tests__/HistoricalProtocolRun.test.tsx rename to app/src/organisms/Desktop/Devices/__tests__/HistoricalProtocolRun.test.tsx index 883abd15a28..070f1c614de 100644 --- a/app/src/organisms/Devices/__tests__/HistoricalProtocolRun.test.tsx +++ b/app/src/organisms/Desktop/Devices/__tests__/HistoricalProtocolRun.test.tsx @@ -1,20 +1,20 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { getStoredProtocols } from '../../../redux/protocol-storage' -import { storedProtocolData as storedProtocolDataFixture } from '../../../redux/protocol-storage/__fixtures__' -import { useRunStatus, useRunTimestamps } from '../../RunTimeControl/hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getStoredProtocols } from '/app/redux/protocol-storage' +import { storedProtocolData as storedProtocolDataFixture } from '/app/redux/protocol-storage/__fixtures__' +import { useRunStatus, useRunTimestamps } from '/app/resources/runs' import { HistoricalProtocolRun } from '../HistoricalProtocolRun' import { HistoricalProtocolRunOverflowMenu } from '../HistoricalProtocolRunOverflowMenu' import type { RunStatus, RunData } from '@opentrons/api-client' import type { RunTimeParameter } from '@opentrons/shared-data' -vi.mock('../../../redux/protocol-storage') -vi.mock('../../RunTimeControl/hooks') +vi.mock('/app/redux/protocol-storage') +vi.mock('/app/resources/runs') vi.mock('../HistoricalProtocolRunOverflowMenu') const run = { diff --git a/app/src/organisms/Desktop/Devices/__tests__/HistoricalProtocolRunOverflowMenu.test.tsx b/app/src/organisms/Desktop/Devices/__tests__/HistoricalProtocolRunOverflowMenu.test.tsx new file mode 100644 index 00000000000..2b2706c9645 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/__tests__/HistoricalProtocolRunOverflowMenu.test.tsx @@ -0,0 +1,187 @@ +import type * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, vi, beforeEach, expect } from 'vitest' +import { when } from 'vitest-when' +import { MemoryRouter } from 'react-router-dom' + +import { useDeleteRunMutation } from '@opentrons/react-api-client' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' +import runRecord from '../ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/modals/__fixtures__/runRecord.json' +import { useDownloadRunLog } from '../hooks' +import { useRobot } from '/app/redux-resources/robots' +import { useTrackProtocolRunEvent } from '/app/redux-resources/analytics' +import { useRunControls } from '/app/organisms/RunTimeControl' +import { + useTrackEvent, + ANALYTICS_PROTOCOL_PROCEED_TO_RUN, +} from '/app/redux/analytics' +import { useIsRobotOnWrongVersionOfSoftware } from '/app/redux/robot-update' +import { useIsEstopNotDisengaged } from '/app/resources/devices' +import { HistoricalProtocolRunOverflowMenu } from '../HistoricalProtocolRunOverflowMenu' +import { useNotifyAllCommandsQuery } from '/app/resources/runs' + +import type { UseQueryResult } from 'react-query' +import type { CommandsData } from '@opentrons/api-client' + +vi.mock('/app/redux/analytics') +vi.mock('/app/redux/robot-update/selectors') +vi.mock('/app/redux-resources/robots') +vi.mock('../hooks') +vi.mock('/app/organisms/RunTimeControl') +vi.mock('/app/redux/analytics') +vi.mock('/app/redux/config') +vi.mock('/app/resources/devices') +vi.mock('/app/resources/runs') +vi.mock('/app/redux/robot-update') +vi.mock('/app/redux-resources/analytics') +vi.mock('@opentrons/react-api-client') + +const render = ( + props: React.ComponentProps +) => { + return renderWithProviders( + + + , + { + i18nInstance: i18n, + } + )[0] +} +const PAGE_LENGTH = 101 +const RUN_ID = 'id' +const ROBOT_NAME = 'otie' +let mockTrackEvent: any +let mockTrackProtocolRunEvent: any +const mockDownloadRunLog = vi.fn() + +describe('HistoricalProtocolRunOverflowMenu', () => { + let props: React.ComponentProps + beforeEach(() => { + mockTrackEvent = vi.fn() + vi.mocked(useTrackEvent).mockReturnValue(mockTrackEvent) + mockTrackProtocolRunEvent = vi.fn(() => new Promise(resolve => resolve({}))) + vi.mocked(useIsRobotOnWrongVersionOfSoftware).mockReturnValue(false) + vi.mocked(useDownloadRunLog).mockReturnValue({ + downloadRunLog: mockDownloadRunLog, + isRunLogLoading: false, + }) + vi.mocked(useDeleteRunMutation).mockReturnValue({ + deleteRun: vi.fn(), + } as any) + + when(useTrackProtocolRunEvent).calledWith(RUN_ID, ROBOT_NAME).thenReturn({ + trackProtocolRunEvent: mockTrackProtocolRunEvent, + }) + when(useRunControls) + .calledWith(RUN_ID, expect.anything()) + .thenReturn({ + play: () => {}, + pause: () => {}, + stop: () => {}, + reset: () => {}, + resumeFromRecovery: () => {}, + isPlayRunActionLoading: false, + isPauseRunActionLoading: false, + isStopRunActionLoading: false, + isResetRunLoading: false, + isResumeRunFromRecoveryActionLoading: false, + isRunControlLoading: false, + }) + when(useNotifyAllCommandsQuery) + .calledWith( + RUN_ID, + { + cursor: 0, + pageLength: PAGE_LENGTH, + }, + { staleTime: Infinity } + ) + .thenReturn(({ + data: { data: runRecord.data.commands, meta: { totalLength: 14 } }, + } as unknown) as UseQueryResult) + when(useIsEstopNotDisengaged).calledWith(ROBOT_NAME).thenReturn(false) + props = { + runId: RUN_ID, + robotName: ROBOT_NAME, + robotIsBusy: false, + } + when(vi.mocked(useRobot)) + .calledWith(ROBOT_NAME) + .thenReturn(mockConnectableRobot) + }) + + it('renders the correct menu when a runId is present', () => { + render(props) + + const btn = screen.getByRole('button') + fireEvent.click(btn) + screen.getByRole('button', { + name: 'View protocol run record', + }) + const rerunBtn = screen.getByRole('button', { name: 'Rerun protocol now' }) + screen.getByRole('button', { name: 'Download protocol run log' }) + const deleteBtn = screen.getByRole('button', { + name: 'Delete protocol run record', + }) + fireEvent.click(rerunBtn) + expect(mockTrackEvent).toHaveBeenCalledWith({ + name: ANALYTICS_PROTOCOL_PROCEED_TO_RUN, + properties: { + robotSerialNumber: 'mock-serial', + sourceLocation: 'HistoricalProtocolRun', + }, + }) + expect(useRunControls).toHaveBeenCalled() + expect(mockTrackProtocolRunEvent).toHaveBeenCalled() + fireEvent.click(deleteBtn) + expect(useDeleteRunMutation).toHaveBeenCalled() + }) + + it('disables the rerun protocol menu item if robot software update is available', () => { + vi.mocked(useIsRobotOnWrongVersionOfSoftware).mockReturnValue(true) + render(props) + const btn = screen.getByRole('button') + fireEvent.click(btn) + screen.getByRole('button', { + name: 'View protocol run record', + }) + const rerunBtn = screen.getByRole('button', { name: 'Rerun protocol now' }) + expect(rerunBtn).toBeDisabled() + }) + + it('disables the rerun protocol menu item if run data is loading', () => { + when(useRunControls) + .calledWith(RUN_ID, expect.anything()) + .thenReturn({ + play: () => {}, + pause: () => {}, + stop: () => {}, + reset: () => {}, + resumeFromRecovery: () => {}, + isPlayRunActionLoading: false, + isPauseRunActionLoading: false, + isStopRunActionLoading: false, + isResetRunLoading: false, + isResumeRunFromRecoveryActionLoading: false, + isRunControlLoading: true, + }) + render(props) + const btn = screen.getByRole('button') + fireEvent.click(btn) + screen.getByRole('button', { + name: 'View protocol run record', + }) + const rerunBtn = screen.getByRole('button', { name: 'Rerun protocol now' }) + expect(rerunBtn).toBeDisabled() + }) + + it('should make overflow menu disabled when e-stop is pressed', () => { + when(useIsEstopNotDisengaged).calledWith(ROBOT_NAME).thenReturn(true) + render(props) + expect(screen.getByRole('button')).toBeDisabled() + }) +}) diff --git a/app/src/organisms/Devices/__tests__/InstrumentsAndModules.test.tsx b/app/src/organisms/Desktop/Devices/__tests__/InstrumentsAndModules.test.tsx similarity index 84% rename from app/src/organisms/Devices/__tests__/InstrumentsAndModules.test.tsx rename to app/src/organisms/Desktop/Devices/__tests__/InstrumentsAndModules.test.tsx index ea24f586fad..4bfb1339d39 100644 --- a/app/src/organisms/Devices/__tests__/InstrumentsAndModules.test.tsx +++ b/app/src/organisms/Desktop/Devices/__tests__/InstrumentsAndModules.test.tsx @@ -1,28 +1,26 @@ -import * as React from 'react' import { when } from 'vitest-when' import { screen } from '@testing-library/react' import { describe, it, vi, beforeEach, afterEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useModulesQuery, useInstrumentsQuery, usePipettesQuery, } from '@opentrons/react-api-client' -import { i18n } from '../../../i18n' -import { Banner } from '../../../atoms/Banner' -import { mockMagneticModule } from '../../../redux/modules/__fixtures__' -import { useIsFlex, useIsRobotViewable, useRunStatuses } from '../hooks' -import { ModuleCard } from '../../ModuleCard' +import { i18n } from '/app/i18n' +import { mockMagneticModule } from '/app/redux/modules/__fixtures__' +import { useIsFlex, useIsRobotViewable } from '/app/redux-resources/robots' +import { ModuleCard } from '/app/organisms/ModuleCard' import { InstrumentsAndModules } from '../InstrumentsAndModules' -import { GripperCard } from '../../GripperCard' +import { GripperCard } from '../GripperCard' import { PipetteCard } from '../PipetteCard' import { FlexPipetteCard } from '../PipetteCard/FlexPipetteCard' import { PipetteRecalibrationWarning } from '../PipetteCard/PipetteRecalibrationWarning' -import { getShowPipetteCalibrationWarning } from '../utils' -import { useIsEstopNotDisengaged } from '../../../resources/devices/hooks/useIsEstopNotDisengaged' -import { useCurrentRunId } from '../../../resources/runs' +import { getShowPipetteCalibrationWarning } from '/app/transformations/instruments' +import { useIsEstopNotDisengaged } from '/app/resources/devices/hooks/useIsEstopNotDisengaged' +import { useCurrentRunId, useRunStatuses } from '/app/resources/runs' import type * as Components from '@opentrons/components' vi.mock('@opentrons/components', async importOriginal => { @@ -33,17 +31,15 @@ vi.mock('@opentrons/components', async importOriginal => { } }) vi.mock('@opentrons/react-api-client') -vi.mock('../hooks') -vi.mock('../../GripperCard') -vi.mock('../../ModuleCard') +vi.mock('../GripperCard') +vi.mock('/app/organisms/ModuleCard') vi.mock('../PipetteCard') vi.mock('../PipetteCard/FlexPipetteCard') vi.mock('../PipetteCard/PipetteRecalibrationWarning') -vi.mock('../../../resources/runs') -vi.mock('../../../atoms/Banner') -vi.mock('../utils') -vi.mock('../../RunTimeControl/hooks') -vi.mock('../../../resources/devices/hooks/useIsEstopNotDisengaged') +vi.mock('/app/resources/runs') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/transformations/instruments') +vi.mock('/app/resources/devices/hooks/useIsEstopNotDisengaged') const ROBOT_NAME = 'otie' @@ -121,7 +117,9 @@ describe('InstrumentsAndModules', () => { it('renders the protocol loaded banner when protocol is loaded and not terminal state', () => { vi.mocked(useCurrentRunId).mockReturnValue('RUNID') render() - expect(vi.mocked(Banner)).toHaveBeenCalled() + screen.getByText( + 'Robot must be on the network to see connected instruments and modules' + ) }) it('renders 1 pipette card when a 96 channel is attached', () => { when(useIsFlex).calledWith(ROBOT_NAME).thenReturn(true) diff --git a/app/src/organisms/Devices/__tests__/RecentProtocolRuns.test.tsx b/app/src/organisms/Desktop/Devices/__tests__/RecentProtocolRuns.test.tsx similarity index 81% rename from app/src/organisms/Devices/__tests__/RecentProtocolRuns.test.tsx rename to app/src/organisms/Desktop/Devices/__tests__/RecentProtocolRuns.test.tsx index 92557e96570..daa1fc10251 100644 --- a/app/src/organisms/Devices/__tests__/RecentProtocolRuns.test.tsx +++ b/app/src/organisms/Desktop/Devices/__tests__/RecentProtocolRuns.test.tsx @@ -1,11 +1,10 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { useNotifyAllRunsQuery } from '../../../resources/runs' -import { i18n } from '../../../i18n' -import { useIsRobotViewable, useRunStatuses } from '../hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { useNotifyAllRunsQuery, useRunStatuses } from '/app/resources/runs' +import { i18n } from '/app/i18n' +import { useIsRobotViewable } from '/app/redux-resources/robots' import { RecentProtocolRuns } from '../RecentProtocolRuns' import { HistoricalProtocolRun } from '../HistoricalProtocolRun' @@ -13,9 +12,8 @@ import type { UseQueryResult } from 'react-query' import type { Runs } from '@opentrons/api-client' import type { AxiosError } from 'axios' -vi.mock('../../../resources/runs') -vi.mock('../hooks') -vi.mock('../../ProtocolUpload/hooks') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/resources/runs') vi.mock('../HistoricalProtocolRun') const render = () => { @@ -54,9 +52,9 @@ describe('RecentProtocolRuns', () => { }) it('renders table headers if there are runs', () => { vi.mocked(useIsRobotViewable).mockReturnValue(true) - vi.mocked(useNotifyAllRunsQuery).mockReturnValue({ + vi.mocked(useNotifyAllRunsQuery).mockReturnValue(({ data: { - data: [ + data: ([ { createdAt: '2022-05-04T18:24:40.833862+00:00', current: false, @@ -64,9 +62,9 @@ describe('RecentProtocolRuns', () => { protocolId: 'test_protocol_id', status: 'succeeded', }, - ], + ] as any) as Runs, }, - } as UseQueryResult) + } as any) as UseQueryResult) render() screen.getByText('Recent Protocol Runs') screen.getByText('Run') diff --git a/app/src/organisms/Devices/__tests__/RobotCard.test.tsx b/app/src/organisms/Desktop/Devices/__tests__/RobotCard.test.tsx similarity index 82% rename from app/src/organisms/Devices/__tests__/RobotCard.test.tsx rename to app/src/organisms/Desktop/Devices/__tests__/RobotCard.test.tsx index d4cf00a61d7..89d59c47cf8 100644 --- a/app/src/organisms/Devices/__tests__/RobotCard.test.tsx +++ b/app/src/organisms/Desktop/Devices/__tests__/RobotCard.test.tsx @@ -1,32 +1,33 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { when } from 'vitest-when' import { screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { mockOT2HealthResponse, mockOT2ServerHealthResponse, mockOT3HealthResponse, mockOT3ServerHealthResponse, -} from '../../../../../discovery-client/src/fixtures' +} from '../../../../../../discovery-client/src/fixtures' -import { i18n } from '../../../i18n' -import { mockFetchModulesSuccessActionPayloadModules } from '../../../redux/modules/__fixtures__' +import { i18n } from '/app/i18n' +import { mockFetchModulesSuccessActionPayloadModules } from '/app/redux/modules/__fixtures__' import { mockLeftProtoPipette, mockRightProtoPipette, -} from '../../../redux/pipettes/__fixtures__' -import { mockConnectableRobot } from '../../../redux/discovery/__fixtures__' -import { getRobotUpdateDisplayInfo } from '../../../redux/robot-update' -import { getRobotModelByName } from '../../../redux/discovery' +} from '/app/redux/pipettes/__fixtures__' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' +import { getRobotUpdateDisplayInfo } from '/app/redux/robot-update' +import { getRobotModelByName } from '/app/redux/discovery' +import { useAttachedModules } from '/app/resources/modules' import { HEALTH_STATUS_OK, ROBOT_MODEL_OT2, ROBOT_MODEL_OT3, -} from '../../../redux/discovery/constants' -import { useAttachedModules, useAttachedPipettes } from '../hooks' +} from '/app/redux/discovery/constants' +import { useAttachedPipettes } from '/app/resources/instruments' import { UpdateRobotBanner } from '../../UpdateRobotBanner' import { RobotOverflowMenu } from '../RobotOverflowMenu' import { RobotStatusHeader } from '../RobotStatusHeader' @@ -34,18 +35,20 @@ import { RobotCard } from '../RobotCard' import { ErrorRecoveryBanner, useErrorRecoveryBanner, -} from '../../ErrorRecoveryBanner' +} from '../ErrorRecoveryBanner' -import type { State } from '../../../redux/types' +import type { State } from '/app/redux/types' -vi.mock('../../../redux/robot-update/selectors') -vi.mock('../../../redux/discovery/selectors') -vi.mock('../hooks') +vi.mock('/app/redux/robot-update/selectors') +vi.mock('/app/redux/discovery/selectors') +vi.mock('/app/resources/instruments') +vi.mock('/app/resources/modules') +vi.mock('/app/redux-resources/robots') vi.mock('../../UpdateRobotBanner') -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') vi.mock('../RobotOverflowMenu') vi.mock('../RobotStatusHeader') -vi.mock('../../ErrorRecoveryBanner') +vi.mock('../ErrorRecoveryBanner') const OT2_PNG_FILE_NAME = '/app/src/assets/images/OT2-R_HERO.png' const FLEX_PNG_FILE_NAME = '/app/src/assets/images/FLEX.png' diff --git a/app/src/organisms/Desktop/Devices/__tests__/RobotOverflowMenu.test.tsx b/app/src/organisms/Desktop/Devices/__tests__/RobotOverflowMenu.test.tsx new file mode 100644 index 00000000000..b3735e72a77 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/__tests__/RobotOverflowMenu.test.tsx @@ -0,0 +1,91 @@ +import type * as React from 'react' +import { MemoryRouter } from 'react-router-dom' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, vi, beforeEach, expect } from 'vitest' +import '@testing-library/jest-dom/vitest' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useCurrentRunId } from '/app/resources/runs' +import { ChooseProtocolSlideout } from '/app/organisms/Desktop/ChooseProtocolSlideout' +import { RobotOverflowMenu } from '../RobotOverflowMenu' +import { useIsRobotOnWrongVersionOfSoftware } from '/app/redux/robot-update' +import { useIsRobotBusy } from '/app/redux-resources/robots' + +import { + mockUnreachableRobot, + mockConnectedRobot, +} from '/app/redux/discovery/__fixtures__' + +vi.mock('/app/redux/robot-update/hooks') +vi.mock('/app/resources/runs') +vi.mock('/app/organisms/Desktop/ChooseProtocolSlideout') +vi.mock('../hooks') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/resources/devices/hooks/useIsEstopNotDisengaged') + +const render = (props: React.ComponentProps) => { + return renderWithProviders( + + + , + { + i18nInstance: i18n, + } + )[0] +} + +describe('RobotOverflowMenu', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + robot: mockConnectedRobot, + } + vi.mocked(useCurrentRunId).mockReturnValue('RUNID') + vi.mocked(ChooseProtocolSlideout).mockReturnValue( +
    choose protocol slideout
    + ) + vi.mocked(useIsRobotOnWrongVersionOfSoftware).mockReturnValue(false) + vi.mocked(useIsRobotBusy).mockReturnValue(false) + }) + + it('renders overflow menu items when the robot is reachable and a run id is present', () => { + render(props) + const btn = screen.getByLabelText('RobotOverflowMenu_button') + fireEvent.click(btn) + screen.getByRole('link', { name: 'Robot settings' }) + }) + + it('renders overflow menu items when the robot is not reachable', () => { + vi.mocked(useCurrentRunId).mockReturnValue(null) + + props = { + robot: mockUnreachableRobot, + } + render(props) + const btn = screen.getByLabelText('RobotOverflowMenu_button') + fireEvent.click(btn) + screen.getByText('Why is this robot unavailable?') + screen.getByText('Forget unavailable robot') + }) + + it('disables the run a protocol menu item if robot software update is available', () => { + vi.mocked(useCurrentRunId).mockReturnValue(null) + vi.mocked(useIsRobotOnWrongVersionOfSoftware).mockReturnValue(true) + render(props) + const btn = screen.getByLabelText('RobotOverflowMenu_button') + fireEvent.click(btn) + const run = screen.getByText('Run a protocol') + expect(run).toBeDisabled() + }) + + it('disables the run a protocol menu item if robot is busy', () => { + vi.mocked(useCurrentRunId).mockReturnValue(null) + vi.mocked(useIsRobotBusy).mockReturnValue(true) + render(props) + const btn = screen.getByLabelText('RobotOverflowMenu_button') + fireEvent.click(btn) + const run = screen.getByText('Run a protocol') + expect(run).toBeDisabled() + }) +}) diff --git a/app/src/organisms/Devices/__tests__/RobotOverview.test.tsx b/app/src/organisms/Desktop/Devices/__tests__/RobotOverview.test.tsx similarity index 91% rename from app/src/organisms/Devices/__tests__/RobotOverview.test.tsx rename to app/src/organisms/Desktop/Devices/__tests__/RobotOverview.test.tsx index e800f9741bf..5d2513bec23 100644 --- a/app/src/organisms/Devices/__tests__/RobotOverview.test.tsx +++ b/app/src/organisms/Desktop/Devices/__tests__/RobotOverview.test.tsx @@ -1,35 +1,34 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { when } from 'vitest-when' import { screen, fireEvent } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import * as DiscoveryClientFixtures from '../../../../../discovery-client/src/fixtures' +import { renderWithProviders } from '/app/__testing-utils__' +import * as DiscoveryClientFixtures from '../../../../../../discovery-client/src/fixtures' import { useAuthorization } from '@opentrons/react-api-client' -import { i18n } from '../../../i18n' -import { useCurrentRunId } from '../../../resources/runs' -import { mockConnectableRobot } from '../../../redux/discovery/__fixtures__' -import { getRobotUpdateDisplayInfo } from '../../../redux/robot-update' -import { getConfig, useFeatureFlag } from '../../../redux/config' +import { i18n } from '/app/i18n' +import { useCurrentRunId, useRunStatuses } from '/app/resources/runs' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' +import { getRobotUpdateDisplayInfo } from '/app/redux/robot-update' +import { getConfig, useFeatureFlag } from '/app/redux/config' import { getRobotAddressesByName, getRobotModelByName, -} from '../../../redux/discovery' +} from '/app/redux/discovery' import { HEALTH_STATUS_OK, OPENTRONS_USB, ROBOT_MODEL_OT3, -} from '../../../redux/discovery/constants' +} from '/app/redux/discovery/constants' import { - useCalibrationTaskList, useIsRobotBusy, - useLights, - useRobot, - useRunStatuses, useIsRobotViewable, -} from '../hooks' + useRobot, +} from '/app/redux-resources/robots' +import { useLights } from '/app/resources/devices' +import { useCalibrationTaskList } from '../hooks' import { expectedBadDeckTaskList, expectedBadDeckAndPipetteOffsetTaskList, @@ -47,11 +46,11 @@ import { RobotOverviewOverflowMenu } from '../RobotOverviewOverflowMenu' import { ErrorRecoveryBanner, useErrorRecoveryBanner, -} from '../../ErrorRecoveryBanner' +} from '../ErrorRecoveryBanner' -import type { Config } from '../../../redux/config/types' -import type { DiscoveryClientRobotAddress } from '../../../redux/discovery/types' -import type { State } from '../../../redux/types' +import type { Config } from '/app/redux/config/types' +import type { DiscoveryClientRobotAddress } from '/app/redux/discovery/types' +import type { State } from '/app/redux/types' import type * as ReactApiClient from '@opentrons/react-api-client' vi.mock('@opentrons/react-api-client', async importOriginal => { @@ -61,16 +60,18 @@ vi.mock('@opentrons/react-api-client', async importOriginal => { useAuthorization: vi.fn(), } }) -vi.mock('../../../redux/robot-controls') -vi.mock('../../../redux/robot-update/selectors') -vi.mock('../../../redux/config') -vi.mock('../../../redux/discovery/selectors') -vi.mock('../../../resources/runs') +vi.mock('/app/redux/robot-controls') +vi.mock('/app/redux/robot-update/selectors') +vi.mock('/app/redux/config') +vi.mock('/app/redux/discovery/selectors') +vi.mock('/app/resources/runs') +vi.mock('/app/resources/devices') vi.mock('../hooks') +vi.mock('/app/redux-resources/robots') vi.mock('../RobotStatusHeader') vi.mock('../../UpdateRobotBanner') vi.mock('../RobotOverviewOverflowMenu') -vi.mock('../../ErrorRecoveryBanner') +vi.mock('../ErrorRecoveryBanner') const OT2_PNG_FILE_NAME = '/app/src/assets/images/OT2-R_HERO.png' const FLEX_PNG_FILE_NAME = '/app/src/assets/images/FLEX.png' diff --git a/app/src/organisms/Devices/__tests__/RobotOverviewOverflowMenu.test.tsx b/app/src/organisms/Desktop/Devices/__tests__/RobotOverviewOverflowMenu.test.tsx similarity index 78% rename from app/src/organisms/Devices/__tests__/RobotOverviewOverflowMenu.test.tsx rename to app/src/organisms/Desktop/Devices/__tests__/RobotOverviewOverflowMenu.test.tsx index 2cbcab8b99c..c4d384d0805 100644 --- a/app/src/organisms/Devices/__tests__/RobotOverviewOverflowMenu.test.tsx +++ b/app/src/organisms/Desktop/Devices/__tests__/RobotOverviewOverflowMenu.test.tsx @@ -1,45 +1,40 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { when } from 'vitest-when' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../i18n' -import { home } from '../../../redux/robot-controls' -import * as Buildroot from '../../../redux/robot-update' -import { restartRobot } from '../../../redux/robot-admin' +import { i18n } from '/app/i18n' +import { home } from '/app/redux/robot-controls' +import { useIsRobotOnWrongVersionOfSoftware } from '/app/redux/robot-update' +import { restartRobot } from '/app/redux/robot-admin' import { mockConnectableRobot, mockReachableRobot, mockUnreachableRobot, -} from '../../../redux/discovery/__fixtures__' -import { useCanDisconnect } from '../../../resources/networking/hooks' -import { DisconnectModal } from '../../../organisms/Devices/RobotSettings/ConnectNetwork/DisconnectModal' -import { ChooseProtocolSlideout } from '../../ChooseProtocolSlideout' -import { useCurrentRunId } from '../../../resources/runs' -import { useIsRobotBusy } from '../hooks' +} from '/app/redux/discovery/__fixtures__' +import { useCanDisconnect } from '/app/resources/networking/hooks' +import { DisconnectModal } from '../RobotSettings/ConnectNetwork/DisconnectModal' +import { ChooseProtocolSlideout } from '/app/organisms/Desktop/ChooseProtocolSlideout' +import { useCurrentRunId } from '/app/resources/runs' +import { useIsRobotBusy } from '/app/redux-resources/robots' import { handleUpdateBuildroot } from '../RobotSettings/UpdateBuildroot' -import { useIsEstopNotDisengaged } from '../../../resources/devices/hooks/useIsEstopNotDisengaged' +import { useIsEstopNotDisengaged } from '/app/resources/devices/hooks/useIsEstopNotDisengaged' import { RobotOverviewOverflowMenu } from '../RobotOverviewOverflowMenu' -import type { State } from '../../../redux/types' - -vi.mock('../../../redux/robot-controls') -vi.mock('../../../redux/robot-admin') +vi.mock('/app/redux/robot-controls') +vi.mock('/app/redux/robot-admin') vi.mock('../hooks') -vi.mock('../../../redux/robot-update') -vi.mock('../../../resources/networking/hooks') -vi.mock( - '../../../organisms/Devices/RobotSettings/ConnectNetwork/DisconnectModal' -) -vi.mock('../../ChooseProtocolSlideout') -vi.mock('../../../resources/runs') +vi.mock('/app/redux/robot-update') +vi.mock('/app/resources/networking/hooks') +vi.mock('../RobotSettings/ConnectNetwork/DisconnectModal') +vi.mock('/app/organisms/Desktop/ChooseProtocolSlideout') +vi.mock('/app/resources/runs') vi.mock('../RobotSettings/UpdateBuildroot') -vi.mock('../../../resources/devices/hooks/useIsEstopNotDisengaged') - -const getBuildrootUpdateDisplayInfo = Buildroot.getRobotUpdateDisplayInfo +vi.mock('/app/resources/devices/hooks/useIsEstopNotDisengaged') +vi.mock('/app/redux-resources/robots') const render = ( props: React.ComponentProps @@ -60,13 +55,7 @@ describe('RobotOverviewOverflowMenu', () => { beforeEach(() => { props = { robot: mockConnectableRobot } - when(getBuildrootUpdateDisplayInfo) - .calledWith({} as State, mockConnectableRobot.name) - .thenReturn({ - autoUpdateAction: 'reinstall', - autoUpdateDisabledReason: null, - updateFromFileDisabledReason: null, - }) + vi.mocked(useIsRobotOnWrongVersionOfSoftware).mockReturnValue(false) vi.mocked(useCurrentRunId).mockReturnValue(null) vi.mocked(useIsRobotBusy).mockReturnValue(false) vi.mocked(handleUpdateBuildroot).mockReturnValue() @@ -107,13 +96,7 @@ describe('RobotOverviewOverflowMenu', () => { }) it('should render update robot software button when robot is on wrong version of software', () => { - when(getBuildrootUpdateDisplayInfo) - .calledWith({} as State, mockConnectableRobot.name) - .thenReturn({ - autoUpdateAction: 'upgrade', - autoUpdateDisabledReason: null, - updateFromFileDisabledReason: null, - }) + vi.mocked(useIsRobotOnWrongVersionOfSoftware).mockReturnValue(true) render(props) @@ -238,11 +221,6 @@ describe('RobotOverviewOverflowMenu', () => { expect(restartRobot).toBeCalled() }) it('render overflow menu buttons without the update robot software button', () => { - vi.mocked(getBuildrootUpdateDisplayInfo).mockReturnValue({ - autoUpdateAction: 'reinstall', - autoUpdateDisabledReason: null, - updateFromFileDisabledReason: null, - }) render(props) const btn = screen.getByRole('button') fireEvent.click(btn) @@ -263,11 +241,6 @@ describe('RobotOverviewOverflowMenu', () => { }) it('should render disabled menu items except restart robot and robot settings when e-stop is pressed', () => { - vi.mocked(getBuildrootUpdateDisplayInfo).mockReturnValue({ - autoUpdateAction: 'reinstall', - autoUpdateDisabledReason: null, - updateFromFileDisabledReason: null, - }) when(useIsEstopNotDisengaged) .calledWith(mockConnectableRobot.name) .thenReturn(true) diff --git a/app/src/organisms/Devices/__tests__/RobotStatusHeader.test.tsx b/app/src/organisms/Desktop/Devices/__tests__/RobotStatusHeader.test.tsx similarity index 87% rename from app/src/organisms/Devices/__tests__/RobotStatusHeader.test.tsx rename to app/src/organisms/Desktop/Devices/__tests__/RobotStatusHeader.test.tsx index b7fee94c37f..465c46cc566 100644 --- a/app/src/organisms/Devices/__tests__/RobotStatusHeader.test.tsx +++ b/app/src/organisms/Desktop/Devices/__tests__/RobotStatusHeader.test.tsx @@ -1,35 +1,35 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { RUN_STATUS_RUNNING } from '@opentrons/api-client' import { when } from 'vitest-when' import { screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useProtocolQuery } from '@opentrons/react-api-client' -import { i18n } from '../../../i18n' -import { useCurrentRunStatus } from '../../../organisms/RunTimeControl/hooks' +import { i18n } from '/app/i18n' +import { useCurrentRunStatus } from '/app/organisms/RunTimeControl/hooks' import { getRobotAddressesByName, HEALTH_STATUS_OK, OPENTRONS_USB, -} from '../../../redux/discovery' -import { getNetworkInterfaces } from '../../../redux/networking' -import { useIsFlex } from '../hooks' +} from '/app/redux/discovery' +import { getNetworkInterfaces } from '/app/redux/networking' +import { useIsFlex } from '/app/redux-resources/robots' import { RobotStatusHeader } from '../RobotStatusHeader' -import { useNotifyRunQuery, useCurrentRunId } from '../../../resources/runs' +import { useNotifyRunQuery, useCurrentRunId } from '/app/resources/runs' -import type { DiscoveryClientRobotAddress } from '../../../redux/discovery/types' -import type { SimpleInterfaceStatus } from '../../../redux/networking/types' -import type { State } from '../../../redux/types' +import type { DiscoveryClientRobotAddress } from '/app/redux/discovery/types' +import type { SimpleInterfaceStatus } from '/app/redux/networking/types' +import type { State } from '/app/redux/types' vi.mock('@opentrons/react-api-client') -vi.mock('../../../organisms/RunTimeControl/hooks') -vi.mock('../../../redux/discovery') -vi.mock('../../../redux/networking') -vi.mock('../hooks') -vi.mock('../../../resources/runs') +vi.mock('/app/organisms/RunTimeControl/hooks') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/networking') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/resources/runs') const MOCK_OTIE = { name: 'otie', diff --git a/app/src/organisms/Desktop/Devices/__tests__/utils.test.tsx b/app/src/organisms/Desktop/Devices/__tests__/utils.test.tsx new file mode 100644 index 00000000000..9df883b3c5d --- /dev/null +++ b/app/src/organisms/Desktop/Devices/__tests__/utils.test.tsx @@ -0,0 +1,99 @@ +import { describe, it, expect } from 'vitest' +import '@testing-library/jest-dom/vitest' +import { + getIs96ChannelPipetteAttached, + getOffsetCalibrationForMount, +} from '../utils' +import { + mockPipetteOffsetCalibration1, + mockPipetteOffsetCalibration2, + mockPipetteOffsetCalibration3, +} from '/app/redux/calibration/pipette-offset/__fixtures__' +import type { + FetchPipettesResponseBody, + FetchPipettesResponsePipette, +} from '/app/redux/pipettes/types' + +describe('getIs96ChannelPipetteAttached hook', () => { + it('returns false when there is no pipette attached on the left mount', () => { + const result = getIs96ChannelPipetteAttached(null) + expect(result).toEqual(false) + }) + + it('returns true when there is a 96 channel pipette attached on the left mount', () => { + const mockLeftMountAttachedPipette = { + name: 'p1000_96', + } as FetchPipettesResponsePipette + + const result = getIs96ChannelPipetteAttached(mockLeftMountAttachedPipette) + expect(result).toEqual(true) + }) + + it('returns false when there is no 96 channel pipette attached on the left mount', () => { + const mockLeftMountAttachedPipette = { + name: 'p10_single_v1', + } as FetchPipettesResponsePipette + + const result = getIs96ChannelPipetteAttached(mockLeftMountAttachedPipette) + expect(result).toEqual(false) + }) +}) + +describe('getOffsetCalibrationForMount', () => { + const mockLeftMountAttachedPipette = { + name: 'mock left pipette', + } as FetchPipettesResponsePipette + const mockRightMountAttachedPipette = { + name: 'mock right pipette', + } as FetchPipettesResponsePipette + it('returns null when not given calibrations', () => { + const result = getOffsetCalibrationForMount( + null, + { + left: mockLeftMountAttachedPipette, + right: mockRightMountAttachedPipette, + }, + 'right' + ) + expect(result).toEqual(null) + }) + + it("returns null when asked for calibrations that don't exist for a mount", () => { + const calibrations = [mockPipetteOffsetCalibration1] + const result = getOffsetCalibrationForMount( + calibrations, + { + left: mockLeftMountAttachedPipette, + right: mockRightMountAttachedPipette, + }, + 'right' + ) + expect(result).toEqual(null) + }) + + it('returns the correct calibrations for a mount', () => { + const { pipette } = mockPipetteOffsetCalibration2 + const mockAttachedPipettes: FetchPipettesResponseBody = { + left: mockLeftMountAttachedPipette, // this one doesn't matter too much since we're looking for the right mount cal + right: { + id: pipette, + name: `test-${pipette}`, + model: 'p10_single_v1', + tip_length: 0, + mount_axis: 'z', + plunger_axis: 'a', + }, + } + const calibrations = [ + mockPipetteOffsetCalibration1, + mockPipetteOffsetCalibration2, + mockPipetteOffsetCalibration3, + ] + const result = getOffsetCalibrationForMount( + calibrations, + mockAttachedPipettes, + 'right' + ) + expect(result).toEqual(mockPipetteOffsetCalibration2) + }) +}) diff --git a/app/src/organisms/Desktop/Devices/constants.ts b/app/src/organisms/Desktop/Devices/constants.ts new file mode 100644 index 00000000000..a923216fd9f --- /dev/null +++ b/app/src/organisms/Desktop/Devices/constants.ts @@ -0,0 +1,29 @@ +import { getPipetteNameSpecs } from '@opentrons/shared-data' +import type { LabwareDefinition2, PipetteName } from '@opentrons/shared-data' +import { getLatestLabwareDef } from '/app/assets/labware/getLabware' + +export const RUN_LOG_WINDOW_SIZE = 60 // number of command items rendered at a time + +// NOTE: this map is a duplicate of the TIP_RACK_LOOKUP_BY_MAX_VOL +// found at robot_server/robot/calibration/constants.py +const TIP_RACK_LOOKUP_BY_MAX_VOL: { + [maxVol: string]: LabwareDefinition2['parameters']['loadName'] +} = { + 10: 'opentrons_96_tiprack_10ul', + 20: 'opentrons_96_tiprack_20ul', + 50: 'opentrons_96_tiprack_300ul', + 300: 'opentrons_96_tiprack_300ul', + 1000: 'opentrons_96_tiprack_1000ul', +} + +export function getDefaultTiprackDefForPipetteName( + pipetteName: PipetteName +): LabwareDefinition2 | null { + const pipetteNameSpecs = getPipetteNameSpecs(pipetteName) + if (pipetteNameSpecs != null) { + return getLatestLabwareDef( + TIP_RACK_LOOKUP_BY_MAX_VOL[pipetteNameSpecs.maxVolume] + ) + } + return null +} diff --git a/app/src/organisms/Devices/hooks/__fixtures__/taskListFixtures.ts b/app/src/organisms/Desktop/Devices/hooks/__fixtures__/taskListFixtures.ts similarity index 99% rename from app/src/organisms/Devices/hooks/__fixtures__/taskListFixtures.ts rename to app/src/organisms/Desktop/Devices/hooks/__fixtures__/taskListFixtures.ts index 821d059d782..de6c33f799f 100644 --- a/app/src/organisms/Devices/hooks/__fixtures__/taskListFixtures.ts +++ b/app/src/organisms/Desktop/Devices/hooks/__fixtures__/taskListFixtures.ts @@ -1,13 +1,13 @@ import { vi } from 'vitest' -import { formatTimestamp } from '../../utils' +import { formatTimestamp } from '/app/transformations/runs' import type { Mock } from 'vitest' import type { TipLengthCalibration, PipetteOffsetCalibration, -} from '../../../../redux/calibration/api-types' -import type { AttachedPipettesByMount } from '../../../../redux/pipettes/types' -import type { TaskListProps } from '../../../TaskList/types' +} from '/app/redux/calibration/api-types' +import type { AttachedPipettesByMount } from '/app/redux/pipettes/types' +import type { TaskListProps } from '/app/molecules/TaskList' import type { PipetteModelSpecs } from '@opentrons/shared-data' export const TASK_COUNT = 3 diff --git a/app/src/organisms/Devices/hooks/__tests__/useCalibrationTaskList.test.tsx b/app/src/organisms/Desktop/Devices/hooks/__tests__/useCalibrationTaskList.test.tsx similarity index 98% rename from app/src/organisms/Devices/hooks/__tests__/useCalibrationTaskList.test.tsx rename to app/src/organisms/Desktop/Devices/hooks/__tests__/useCalibrationTaskList.test.tsx index ae48675a201..9d675c77f7e 100644 --- a/app/src/organisms/Devices/hooks/__tests__/useCalibrationTaskList.test.tsx +++ b/app/src/organisms/Desktop/Devices/hooks/__tests__/useCalibrationTaskList.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { createStore } from 'redux' import { I18nextProvider } from 'react-i18next' import { Provider } from 'react-redux' @@ -12,7 +12,7 @@ import { useCalibrationStatusQuery, } from '@opentrons/react-api-client' import { useCalibrationTaskList } from '../useCalibrationTaskList' -import { useAttachedPipettes } from '..' +import { useAttachedPipettes } from '/app/resources/instruments' import { TASK_COUNT, mockAttachedPipettesResponse, @@ -28,12 +28,12 @@ import { mockIncompleteTipLengthCalibrations, expectedTaskList, } from '../__fixtures__/taskListFixtures' -import { i18n } from '../../../../i18n' +import { i18n } from '/app/i18n' import type { Store } from 'redux' -import type { State } from '../../../../redux/types' +import type { State } from '/app/redux/types' -vi.mock('../') +vi.mock('/app/resources/instruments') vi.mock('@opentrons/react-api-client') const mockPipOffsetCalLauncher = vi.fn() diff --git a/app/src/organisms/Devices/hooks/__tests__/useDeckCalibrationData.test.tsx b/app/src/organisms/Desktop/Devices/hooks/__tests__/useDeckCalibrationData.test.tsx similarity index 85% rename from app/src/organisms/Devices/hooks/__tests__/useDeckCalibrationData.test.tsx rename to app/src/organisms/Desktop/Devices/hooks/__tests__/useDeckCalibrationData.test.tsx index 10ad0539b7b..c08f0e3e1e5 100644 --- a/app/src/organisms/Devices/hooks/__tests__/useDeckCalibrationData.test.tsx +++ b/app/src/organisms/Desktop/Devices/hooks/__tests__/useDeckCalibrationData.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { vi, it, expect, describe, beforeEach, afterEach } from 'vitest' import { Provider } from 'react-redux' @@ -11,21 +11,21 @@ import { DECK_CAL_STATUS_OK, DECK_CAL_STATUS_BAD_CALIBRATION, DECK_CAL_STATUS_IDENTITY, -} from '../../../../redux/calibration' -import { getDiscoverableRobotByName } from '../../../../redux/discovery' -import { mockDeckCalData } from '../../../../redux/calibration/__fixtures__' -import { useDispatchApiRequest } from '../../../../redux/robot-api' +} from '/app/redux/calibration' +import { getDiscoverableRobotByName } from '/app/redux/discovery' +import { mockDeckCalData } from '/app/redux/calibration/__fixtures__' +import { useDispatchApiRequest } from '/app/redux/robot-api' import type { Store } from 'redux' -import type { DispatchApiRequestType } from '../../../../redux/robot-api' +import type { DispatchApiRequestType } from '/app/redux/robot-api' import { useDeckCalibrationData } from '..' -import { mockConnectableRobot } from '../../../../redux/discovery/__fixtures__' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' vi.mock('@opentrons/react-api-client') -vi.mock('../../../../redux/calibration') -vi.mock('../../../../redux/robot-api') -vi.mock('../../../../redux/discovery') +vi.mock('/app/redux/calibration') +vi.mock('/app/redux/robot-api') +vi.mock('/app/redux/discovery') const store: Store = createStore(vi.fn(), {}) diff --git a/app/src/organisms/Devices/hooks/__tests__/useLPCSuccessToast.test.ts b/app/src/organisms/Desktop/Devices/hooks/__tests__/useLPCSuccessToast.test.ts similarity index 77% rename from app/src/organisms/Devices/hooks/__tests__/useLPCSuccessToast.test.ts rename to app/src/organisms/Desktop/Devices/hooks/__tests__/useLPCSuccessToast.test.ts index a64b65252a1..6cb6897d8ca 100644 --- a/app/src/organisms/Devices/hooks/__tests__/useLPCSuccessToast.test.ts +++ b/app/src/organisms/Desktop/Devices/hooks/__tests__/useLPCSuccessToast.test.ts @@ -1,11 +1,10 @@ -import * as React from 'react' +import { useContext } from 'react' import { vi, it, expect, describe } from 'vitest' import { renderHook } from '@testing-library/react' import { useLPCSuccessToast } from '..' -import type * as ReactType from 'react' vi.mock('react', async importOriginal => { - const actualReact = await importOriginal() + const actualReact = await importOriginal() return { ...actualReact, useContext: vi.fn(), @@ -14,7 +13,7 @@ vi.mock('react', async importOriginal => { describe('useLPCSuccessToast', () => { it('return true when useContext returns true', () => { - vi.mocked(React.useContext).mockReturnValue({ + vi.mocked(useContext).mockReturnValue({ setIsShowingLPCSuccessToast: true, }) const { result } = renderHook(() => useLPCSuccessToast()) @@ -23,7 +22,7 @@ describe('useLPCSuccessToast', () => { }) }) it('return false when useContext returns false', () => { - vi.mocked(React.useContext).mockReturnValue({ + vi.mocked(useContext).mockReturnValue({ setIsShowingLPCSuccessToast: false, }) const { result } = renderHook(() => useLPCSuccessToast()) diff --git a/app/src/organisms/Devices/hooks/__tests__/usePipetteOffsetCalibration.test.tsx b/app/src/organisms/Desktop/Devices/hooks/__tests__/usePipetteOffsetCalibration.test.tsx similarity index 79% rename from app/src/organisms/Devices/hooks/__tests__/usePipetteOffsetCalibration.test.tsx rename to app/src/organisms/Desktop/Devices/hooks/__tests__/usePipetteOffsetCalibration.test.tsx index 655b06532b8..2c23dcb0f61 100644 --- a/app/src/organisms/Devices/hooks/__tests__/usePipetteOffsetCalibration.test.tsx +++ b/app/src/organisms/Desktop/Devices/hooks/__tests__/usePipetteOffsetCalibration.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, expect, describe, beforeEach, afterEach } from 'vitest' import { when } from 'vitest-when' import { Provider } from 'react-redux' @@ -9,20 +9,20 @@ import { QueryClient, QueryClientProvider } from 'react-query' import { fetchPipetteOffsetCalibrations, getCalibrationForPipette, -} from '../../../../redux/calibration' -import { mockPipetteOffsetCalibration1 } from '../../../../redux/calibration/pipette-offset/__fixtures__' -import { useDispatchApiRequest } from '../../../../redux/robot-api' -import { useRobot } from '../useRobot' +} from '/app/redux/calibration' +import { mockPipetteOffsetCalibration1 } from '/app/redux/calibration/pipette-offset/__fixtures__' +import { useDispatchApiRequest } from '/app/redux/robot-api' +import { useRobot } from '/app/redux-resources/robots' import { usePipetteOffsetCalibration } from '..' import type { Store } from 'redux' -import type { DiscoveredRobot } from '../../../../redux/discovery/types' -import type { DispatchApiRequestType } from '../../../../redux/robot-api' -import type { AttachedPipette, Mount } from '../../../../redux/pipettes/types' +import type { DiscoveredRobot } from '/app/redux/discovery/types' +import type { DispatchApiRequestType } from '/app/redux/robot-api' +import type { AttachedPipette, Mount } from '/app/redux/pipettes/types' -vi.mock('../../../../redux/calibration') -vi.mock('../../../../redux/robot-api') -vi.mock('../useRobot') +vi.mock('/app/redux/calibration') +vi.mock('/app/redux/robot-api') +vi.mock('/app/redux-resources/robots') const store: Store = createStore(vi.fn(), {}) diff --git a/app/src/organisms/Devices/hooks/__tests__/usePipetteOffsetCalibrations.test.tsx b/app/src/organisms/Desktop/Devices/hooks/__tests__/usePipetteOffsetCalibrations.test.tsx similarity index 95% rename from app/src/organisms/Devices/hooks/__tests__/usePipetteOffsetCalibrations.test.tsx rename to app/src/organisms/Desktop/Devices/hooks/__tests__/usePipetteOffsetCalibrations.test.tsx index 65703fea279..9305b34af38 100644 --- a/app/src/organisms/Devices/hooks/__tests__/usePipetteOffsetCalibrations.test.tsx +++ b/app/src/organisms/Desktop/Devices/hooks/__tests__/usePipetteOffsetCalibrations.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, expect, describe, beforeEach, afterEach } from 'vitest' import { when } from 'vitest-when' import { renderHook } from '@testing-library/react' @@ -8,7 +8,7 @@ import { mockPipetteOffsetCalibration1, mockPipetteOffsetCalibration2, mockPipetteOffsetCalibration3, -} from '../../../../redux/calibration/pipette-offset/__fixtures__' +} from '/app/redux/calibration/pipette-offset/__fixtures__' import { usePipetteOffsetCalibrations } from '..' vi.mock('@opentrons/react-api-client') diff --git a/app/src/organisms/Devices/hooks/__tests__/useRunStartedOrLegacySessionInProgress.test.tsx b/app/src/organisms/Desktop/Devices/hooks/__tests__/useRunStartedOrLegacySessionInProgress.test.tsx similarity index 88% rename from app/src/organisms/Devices/hooks/__tests__/useRunStartedOrLegacySessionInProgress.test.tsx rename to app/src/organisms/Desktop/Devices/hooks/__tests__/useRunStartedOrLegacySessionInProgress.test.tsx index e659e24930a..17aaa591586 100644 --- a/app/src/organisms/Devices/hooks/__tests__/useRunStartedOrLegacySessionInProgress.test.tsx +++ b/app/src/organisms/Desktop/Devices/hooks/__tests__/useRunStartedOrLegacySessionInProgress.test.tsx @@ -2,16 +2,14 @@ import { useAllSessionsQuery } from '@opentrons/react-api-client' import { RUN_STATUS_IDLE, RUN_STATUS_RUNNING } from '@opentrons/api-client' import { vi, it, expect, describe, beforeEach, afterEach } from 'vitest' -import { useCurrentRunId } from '../../../../resources/runs' -import { useRunStatus } from '../../../RunTimeControl/hooks' +import { useCurrentRunId, useRunStatus } from '/app/resources/runs' import { useRunStartedOrLegacySessionInProgress } from '..' import type { UseQueryResult } from 'react-query' import type { Sessions } from '@opentrons/api-client' vi.mock('@opentrons/react-api-client') -vi.mock('../../../../resources/runs') -vi.mock('../../../RunTimeControl/hooks') +vi.mock('/app/resources/runs') describe('useRunStartedOrLegacySessionInProgress', () => { beforeEach(() => { diff --git a/app/src/organisms/Devices/hooks/__tests__/useSyncRobotClock.test.tsx b/app/src/organisms/Desktop/Devices/hooks/__tests__/useSyncRobotClock.test.tsx similarity index 89% rename from app/src/organisms/Devices/hooks/__tests__/useSyncRobotClock.test.tsx rename to app/src/organisms/Desktop/Devices/hooks/__tests__/useSyncRobotClock.test.tsx index 37db5d9e752..02b593fbaab 100644 --- a/app/src/organisms/Devices/hooks/__tests__/useSyncRobotClock.test.tsx +++ b/app/src/organisms/Desktop/Devices/hooks/__tests__/useSyncRobotClock.test.tsx @@ -1,15 +1,15 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, expect, describe, beforeEach, afterEach } from 'vitest' import { Provider } from 'react-redux' import { createStore } from 'redux' import { renderHook } from '@testing-library/react' import { QueryClient, QueryClientProvider } from 'react-query' -import { syncSystemTime } from '../../../../redux/robot-admin' +import { syncSystemTime } from '/app/redux/robot-admin' import { useSyncRobotClock } from '..' import type { Store } from 'redux' -vi.mock('../../../../redux/discovery') +vi.mock('/app/redux/discovery') const store: Store = createStore(vi.fn(), {}) diff --git a/app/src/organisms/Devices/hooks/__tests__/useTipLengthCalibrations.test.tsx b/app/src/organisms/Desktop/Devices/hooks/__tests__/useTipLengthCalibrations.test.tsx similarity index 95% rename from app/src/organisms/Devices/hooks/__tests__/useTipLengthCalibrations.test.tsx rename to app/src/organisms/Desktop/Devices/hooks/__tests__/useTipLengthCalibrations.test.tsx index 1a827267531..7281b009b5c 100644 --- a/app/src/organisms/Devices/hooks/__tests__/useTipLengthCalibrations.test.tsx +++ b/app/src/organisms/Desktop/Devices/hooks/__tests__/useTipLengthCalibrations.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, expect, describe, beforeEach, afterEach } from 'vitest' import { when } from 'vitest-when' import { QueryClient, QueryClientProvider } from 'react-query' @@ -8,7 +8,7 @@ import { mockTipLengthCalibration1, mockTipLengthCalibration2, mockTipLengthCalibration3, -} from '../../../../redux/calibration/tip-length/__fixtures__' +} from '/app/redux/calibration/tip-length/__fixtures__' import { useTipLengthCalibrations } from '..' vi.mock('@opentrons/react-api-client') diff --git a/app/src/organisms/Devices/hooks/__tests__/useTrackCreateProtocolRunEvent.test.tsx b/app/src/organisms/Desktop/Devices/hooks/__tests__/useTrackCreateProtocolRunEvent.test.tsx similarity index 81% rename from app/src/organisms/Devices/hooks/__tests__/useTrackCreateProtocolRunEvent.test.tsx rename to app/src/organisms/Desktop/Devices/hooks/__tests__/useTrackCreateProtocolRunEvent.test.tsx index 0be697813a8..b8cf7fd9896 100644 --- a/app/src/organisms/Devices/hooks/__tests__/useTrackCreateProtocolRunEvent.test.tsx +++ b/app/src/organisms/Desktop/Devices/hooks/__tests__/useTrackCreateProtocolRunEvent.test.tsx @@ -1,28 +1,28 @@ -import * as React from 'react' +import type * as React from 'react' import { createStore } from 'redux' import { Provider } from 'react-redux' import { QueryClient, QueryClientProvider } from 'react-query' import { vi, it, expect, describe, beforeEach, afterEach } from 'vitest' import { waitFor, renderHook } from '@testing-library/react' -import { STORED_PROTOCOL_ANALYSIS } from '../__fixtures__/storedProtocolAnalysis' +import { STORED_PROTOCOL_ANALYSIS } from '/app/resources/analysis/hooks/__fixtures__/storedProtocolAnalysis' import { useTrackCreateProtocolRunEvent } from '../useTrackCreateProtocolRunEvent' -import { parseProtocolRunAnalyticsData } from '../useProtocolRunAnalyticsData' -import { parseProtocolAnalysisOutput } from '../useStoredProtocolAnalysis' -import { useTrackEvent } from '../../../../redux/analytics' -import { storedProtocolData } from '../../../../redux/protocol-storage/__fixtures__' +import { parseProtocolRunAnalyticsData } from '/app/transformations/analytics' +import { parseProtocolAnalysisOutput } from '/app/transformations/analysis' +import { useTrackEvent } from '/app/redux/analytics' +import { storedProtocolData } from '/app/redux/protocol-storage/__fixtures__' import type { Mock } from 'vitest' import type { Store } from 'redux' -import type { ProtocolAnalyticsData } from '../../../../redux/analytics/types' +import type { ProtocolAnalyticsData } from '/app/redux/analytics/types' vi.mock('../../hooks') -vi.mock('../useProtocolRunAnalyticsData') -vi.mock('../useStoredProtocolAnalysis') -vi.mock('../../../../redux/discovery') -vi.mock('../../../../redux/pipettes') -vi.mock('../../../../redux/analytics') -vi.mock('../../../../redux/robot-settings') +vi.mock('/app/transformations/analytics') +vi.mock('/app/transformations/analysis') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/pipettes') +vi.mock('/app/redux/analytics') +vi.mock('/app/redux/robot-settings') const PROTOCOL_PROPERTIES = { protocolType: 'python' } as ProtocolAnalyticsData diff --git a/app/src/organisms/Desktop/Devices/hooks/index.ts b/app/src/organisms/Desktop/Devices/hooks/index.ts new file mode 100644 index 00000000000..41930662ad3 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/hooks/index.ts @@ -0,0 +1,10 @@ +export * from './useDeckCalibrationData' +export * from './useDownloadRunLog' +export * from './useCalibrationTaskList' +export * from './useLPCSuccessToast' +export * from './usePipetteOffsetCalibrations' +export * from './usePipetteOffsetCalibration' +export * from './useTipLengthCalibrations' +export * from './useRunStartedOrLegacySessionInProgress' +export * from './useTrackCreateProtocolRunEvent' +export * from './useSyncRobotClock' diff --git a/app/src/organisms/Devices/hooks/useCalibrationTaskList.ts b/app/src/organisms/Desktop/Devices/hooks/useCalibrationTaskList.ts similarity index 92% rename from app/src/organisms/Devices/hooks/useCalibrationTaskList.ts rename to app/src/organisms/Desktop/Devices/hooks/useCalibrationTaskList.ts index 6cd2e75c4a9..ac76e682459 100644 --- a/app/src/organisms/Devices/hooks/useCalibrationTaskList.ts +++ b/app/src/organisms/Desktop/Devices/hooks/useCalibrationTaskList.ts @@ -7,21 +7,49 @@ import { } from '@opentrons/react-api-client' import { getLabwareDefURI } from '@opentrons/shared-data' -import { useAttachedPipettes } from '.' +import { useAttachedPipettes } from '/app/resources/instruments' import { getDefaultTiprackDefForPipetteName } from '../constants' -import { DECK_CAL_STATUS_OK } from '../../../redux/calibration/constants' -import { formatTimestamp } from '../utils' +import { DECK_CAL_STATUS_OK } from '/app/redux/calibration/constants' +import { formatTimestamp } from '/app/transformations/runs' import type { PipetteName } from '@opentrons/shared-data' import type { SubTaskProps, TaskListProps, TaskProps, -} from '../../TaskList/types' -import type { AttachedPipette } from '../../../redux/pipettes/types' -import type { DashboardCalOffsetInvoker } from '../../../pages/Devices/CalibrationDashboard/hooks/useDashboardCalibratePipOffset' -import type { DashboardCalTipLengthInvoker } from '../../../pages/Devices/CalibrationDashboard/hooks/useDashboardCalibrateTipLength' -import type { DashboardCalDeckInvoker } from '../../../pages/Devices/CalibrationDashboard/hooks/useDashboardCalibrateDeck' +} from '/app/molecules/TaskList' +import type { AttachedPipette } from '/app/redux/pipettes/types' +import type { + PipetteOffsetCalibrationSessionParams, + TipLengthCalibrationSessionParams, +} from '/app/redux/sessions/types' + +export interface DashboardOffsetCalInvokerProps { + params: Pick & + Partial> +} + +export type DashboardCalOffsetInvoker = ( + props: DashboardOffsetCalInvokerProps +) => void + +export interface DashboardTipLengthCalInvokerProps { + params: Pick & + Partial> + hasBlockModalResponse: boolean | null + invalidateHandler?: () => void +} + +export type DashboardCalTipLengthInvoker = ( + props: DashboardTipLengthCalInvokerProps +) => void + +export interface DashboardCalDeckInvokerProps { + invalidateHandler?: () => void +} +export type DashboardCalDeckInvoker = ( + props?: DashboardCalDeckInvokerProps +) => void const CALIBRATION_DATA_POLL_MS = 5000 diff --git a/app/src/organisms/Devices/hooks/useDeckCalibrationData.ts b/app/src/organisms/Desktop/Devices/hooks/useDeckCalibrationData.ts similarity index 93% rename from app/src/organisms/Devices/hooks/useDeckCalibrationData.ts rename to app/src/organisms/Desktop/Devices/hooks/useDeckCalibrationData.ts index 7cd506ce8b3..ea6ef4284dd 100644 --- a/app/src/organisms/Devices/hooks/useDeckCalibrationData.ts +++ b/app/src/organisms/Desktop/Devices/hooks/useDeckCalibrationData.ts @@ -1,10 +1,10 @@ import { DECK_CAL_STATUS_OK, DECK_CAL_STATUS_BAD_CALIBRATION, -} from '../../../redux/calibration' +} from '/app/redux/calibration' import { useCalibrationStatusQuery } from '@opentrons/react-api-client' -import { useRobot } from './useRobot' +import { useRobot } from '/app/redux-resources/robots' import type { DeckCalibrationData } from '@opentrons/api-client' /** diff --git a/app/src/organisms/Devices/hooks/useDownloadRunLog.ts b/app/src/organisms/Desktop/Devices/hooks/useDownloadRunLog.ts similarity index 93% rename from app/src/organisms/Devices/hooks/useDownloadRunLog.ts rename to app/src/organisms/Desktop/Devices/hooks/useDownloadRunLog.ts index 1652efc4442..6e07d5bbfab 100644 --- a/app/src/organisms/Devices/hooks/useDownloadRunLog.ts +++ b/app/src/organisms/Desktop/Devices/hooks/useDownloadRunLog.ts @@ -1,9 +1,9 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { getRun, getCommands, getProtocol } from '@opentrons/api-client' import { useHost } from '@opentrons/react-api-client' import { ERROR_TOAST, INFO_TOAST } from '@opentrons/components' -import { useToaster } from '../../../organisms/ToasterOven' +import { useToaster } from '/app/organisms/ToasterOven' import { downloadFile } from '../utils' import type { IconProps } from '@opentrons/components' @@ -13,7 +13,7 @@ export function useDownloadRunLog( ): { downloadRunLog: () => void; isRunLogLoading: boolean } { const { t } = useTranslation('run_details') const host = useHost() - const [isLoading, setIsLoading] = React.useState(false) + const [isLoading, setIsLoading] = useState(false) const { makeToast } = useToaster() @@ -27,14 +27,15 @@ export function useDownloadRunLog( if (host == null) return // first getCommands to get total length of commands getCommands(host, runId, { - cursor: null, pageLength: 0, + includeFixitCommands: true, }) .then(response => { const { totalLength } = response.data.meta getCommands(host, runId, { cursor: 0, pageLength: totalLength, + includeFixitCommands: true, }) .then(response => { const commands = response.data diff --git a/app/src/organisms/Devices/hooks/useLPCSuccessToast.ts b/app/src/organisms/Desktop/Devices/hooks/useLPCSuccessToast.ts similarity index 100% rename from app/src/organisms/Devices/hooks/useLPCSuccessToast.ts rename to app/src/organisms/Desktop/Devices/hooks/useLPCSuccessToast.ts diff --git a/app/src/organisms/Desktop/Devices/hooks/usePipetteOffsetCalibration.ts b/app/src/organisms/Desktop/Devices/hooks/usePipetteOffsetCalibration.ts new file mode 100644 index 00000000000..c441e618647 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/hooks/usePipetteOffsetCalibration.ts @@ -0,0 +1,39 @@ +import { useEffect } from 'react' +import { useSelector } from 'react-redux' + +import { + getCalibrationForPipette, + fetchPipetteOffsetCalibrations, +} from '/app/redux/calibration' +import { useDispatchApiRequest } from '/app/redux/robot-api' +import { useRobot } from '/app/redux-resources/robots' + +import type { PipetteOffsetCalibration } from '/app/redux/calibration/types' +import type { State } from '/app/redux/types' +import type { AttachedPipette, Mount } from '/app/redux/pipettes/types' + +export function usePipetteOffsetCalibration( + robotName: string | null = null, + pipetteId: AttachedPipette['id'] | null = null, + mount: Mount +): PipetteOffsetCalibration | null { + const [dispatchRequest] = useDispatchApiRequest() + const robot = useRobot(robotName) + + const pipetteOffsetCalibration = useSelector((state: State) => + getCalibrationForPipette( + state, + robotName == null ? '' : robotName, + pipetteId == null ? '' : pipetteId, + mount + ) + ) + + useEffect(() => { + if (robotName != null) { + dispatchRequest(fetchPipetteOffsetCalibrations(robotName)) + } + }, [dispatchRequest, robotName, robot?.status]) + + return pipetteOffsetCalibration +} diff --git a/app/src/organisms/Devices/hooks/usePipetteOffsetCalibrations.ts b/app/src/organisms/Desktop/Devices/hooks/usePipetteOffsetCalibrations.ts similarity index 83% rename from app/src/organisms/Devices/hooks/usePipetteOffsetCalibrations.ts rename to app/src/organisms/Desktop/Devices/hooks/usePipetteOffsetCalibrations.ts index da0ae158dd3..ac25649ac05 100644 --- a/app/src/organisms/Devices/hooks/usePipetteOffsetCalibrations.ts +++ b/app/src/organisms/Desktop/Devices/hooks/usePipetteOffsetCalibrations.ts @@ -1,6 +1,6 @@ import { useAllPipetteOffsetCalibrationsQuery } from '@opentrons/react-api-client' -import type { PipetteOffsetCalibration } from '../../../redux/calibration/types' +import type { PipetteOffsetCalibration } from '/app/redux/calibration/types' const CALIBRATION_DATA_POLL_MS = 5000 diff --git a/app/src/organisms/Devices/hooks/useRunStartedOrLegacySessionInProgress.ts b/app/src/organisms/Desktop/Devices/hooks/useRunStartedOrLegacySessionInProgress.ts similarity index 81% rename from app/src/organisms/Devices/hooks/useRunStartedOrLegacySessionInProgress.ts rename to app/src/organisms/Desktop/Devices/hooks/useRunStartedOrLegacySessionInProgress.ts index e8678518847..c5623bf6725 100644 --- a/app/src/organisms/Devices/hooks/useRunStartedOrLegacySessionInProgress.ts +++ b/app/src/organisms/Desktop/Devices/hooks/useRunStartedOrLegacySessionInProgress.ts @@ -1,7 +1,6 @@ import { useAllSessionsQuery } from '@opentrons/react-api-client' import { RUN_STATUS_IDLE } from '@opentrons/api-client' -import { useCurrentRunId } from '../../../resources/runs' -import { useRunStatus } from '../../RunTimeControl/hooks' +import { useCurrentRunId, useRunStatus } from '/app/resources/runs' export function useRunStartedOrLegacySessionInProgress(): boolean { const runId = useCurrentRunId() diff --git a/app/src/organisms/Desktop/Devices/hooks/useSyncRobotClock.ts b/app/src/organisms/Desktop/Devices/hooks/useSyncRobotClock.ts new file mode 100644 index 00000000000..9553ca4a3cb --- /dev/null +++ b/app/src/organisms/Desktop/Devices/hooks/useSyncRobotClock.ts @@ -0,0 +1,20 @@ +import { useEffect } from 'react' +import { useDispatch } from 'react-redux' +import { syncSystemTime } from '/app/redux/robot-admin' + +import type { Dispatch } from '/app/redux/types' + +/** + * syncs robot system time once on mount + * @param {string} robotName name of robot to sync system time + * @returns {void} + */ +export function useSyncRobotClock(robotName: string | null): void { + const dispatch = useDispatch() + + useEffect(() => { + if (robotName != null) { + dispatch(syncSystemTime(robotName)) + } + }, [robotName, dispatch]) +} diff --git a/app/src/organisms/Devices/hooks/useTipLengthCalibrations.ts b/app/src/organisms/Desktop/Devices/hooks/useTipLengthCalibrations.ts similarity index 82% rename from app/src/organisms/Devices/hooks/useTipLengthCalibrations.ts rename to app/src/organisms/Desktop/Devices/hooks/useTipLengthCalibrations.ts index 843260d1a0c..b764b87d63c 100644 --- a/app/src/organisms/Devices/hooks/useTipLengthCalibrations.ts +++ b/app/src/organisms/Desktop/Devices/hooks/useTipLengthCalibrations.ts @@ -1,6 +1,6 @@ import { useAllTipLengthCalibrationsQuery } from '@opentrons/react-api-client' -import type { TipLengthCalibration } from '../../../redux/calibration/types' +import type { TipLengthCalibration } from '/app/redux/calibration/types' const CALIBRATIONS_FETCH_MS = 5000 diff --git a/app/src/organisms/Devices/hooks/useTrackCreateProtocolRunEvent.ts b/app/src/organisms/Desktop/Devices/hooks/useTrackCreateProtocolRunEvent.ts similarity index 82% rename from app/src/organisms/Devices/hooks/useTrackCreateProtocolRunEvent.ts rename to app/src/organisms/Desktop/Devices/hooks/useTrackCreateProtocolRunEvent.ts index d34da539c8d..0fe34cf4af0 100644 --- a/app/src/organisms/Devices/hooks/useTrackCreateProtocolRunEvent.ts +++ b/app/src/organisms/Desktop/Devices/hooks/useTrackCreateProtocolRunEvent.ts @@ -1,9 +1,9 @@ -import { useTrackEvent } from '../../../redux/analytics' -import { parseProtocolRunAnalyticsData } from './useProtocolRunAnalyticsData' -import { parseProtocolAnalysisOutput } from './useStoredProtocolAnalysis' +import { useTrackEvent } from '/app/redux/analytics' +import { parseProtocolRunAnalyticsData } from '/app/transformations/analytics' +import { parseProtocolAnalysisOutput } from '/app/transformations/analysis' -import type { StoredProtocolData } from '../../../redux/protocol-storage' -import { useRobot } from './useRobot' +import type { StoredProtocolData } from '/app/redux/protocol-storage' +import { useRobot } from '/app/redux-resources/robots' type CreateProtocolRunEventName = | 'createProtocolRecordRequest' diff --git a/app/src/organisms/Desktop/Devices/utils.ts b/app/src/organisms/Desktop/Devices/utils.ts new file mode 100644 index 00000000000..c9818264c18 --- /dev/null +++ b/app/src/organisms/Desktop/Devices/utils.ts @@ -0,0 +1,51 @@ +import type { + FetchPipettesResponseBody, + FetchPipettesResponsePipette, + Mount, +} from '/app/redux/pipettes/types' +import type { PipetteOffsetCalibration } from '@opentrons/api-client' + +export function downloadFile(data: object | string, fileName: string): void { + // Create a blob with the data we want to download as a file + const blobContent = typeof data === 'string' ? data : JSON.stringify(data) + const blob = new Blob([blobContent], { type: 'text/json' }) + // Create an anchor element and dispatch a click event on it + // to trigger a download + const a = document.createElement('a') + a.download = fileName + a.href = window.URL.createObjectURL(blob) + const clickEvt = new MouseEvent('click', { + view: window, + bubbles: true, + cancelable: true, + }) + a.dispatchEvent(clickEvt) + a.remove() +} + +export function getIs96ChannelPipetteAttached( + leftMountAttachedPipette: FetchPipettesResponsePipette | null +): boolean { + const pipetteName = leftMountAttachedPipette?.name + + return pipetteName === 'p1000_96' +} + +export function getOffsetCalibrationForMount( + pipetteOffsetCalibrations: PipetteOffsetCalibration[] | null, + attachedPipettes: + | FetchPipettesResponseBody + | { left: undefined; right: undefined }, + mount: Mount +): PipetteOffsetCalibration | null { + if (pipetteOffsetCalibrations == null) { + return null + } else { + return ( + pipetteOffsetCalibrations.find( + cal => + cal.mount === mount && cal.pipette === attachedPipettes[mount]?.id + ) || null + ) + } +} diff --git a/app/src/organisms/HowCalibrationWorksModal/__tests__/HowCalibrationWorksModal.test.tsx b/app/src/organisms/Desktop/HowCalibrationWorksModal/__tests__/HowCalibrationWorksModal.test.tsx similarity index 96% rename from app/src/organisms/HowCalibrationWorksModal/__tests__/HowCalibrationWorksModal.test.tsx rename to app/src/organisms/Desktop/HowCalibrationWorksModal/__tests__/HowCalibrationWorksModal.test.tsx index 7a37b9177e8..8eee1543c5a 100644 --- a/app/src/organisms/HowCalibrationWorksModal/__tests__/HowCalibrationWorksModal.test.tsx +++ b/app/src/organisms/Desktop/HowCalibrationWorksModal/__tests__/HowCalibrationWorksModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' import { describe, it, vi, beforeEach, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { HowCalibrationWorksModal } from '..' const render = ( diff --git a/app/src/organisms/Desktop/HowCalibrationWorksModal/index.tsx b/app/src/organisms/Desktop/HowCalibrationWorksModal/index.tsx new file mode 100644 index 00000000000..a1693793d2e --- /dev/null +++ b/app/src/organisms/Desktop/HowCalibrationWorksModal/index.tsx @@ -0,0 +1,143 @@ +import { createPortal } from 'react-dom' +import { useTranslation } from 'react-i18next' +import { css } from 'styled-components' + +import { + ALIGN_CENTER, + ALIGN_FLEX_END, + Box, + DIRECTION_COLUMN, + Flex, + PrimaryButton, + SPACING, + TEXT_TRANSFORM_CAPITALIZE, + TYPOGRAPHY, + Modal, + LegacyStyledText, +} from '@opentrons/components' + +import { getTopPortalEl } from '/app/App/portal' +import RobotCalHelpImage from '/app/assets/images/robot_calibration_help.png' +import { ExternalLink } from '/app/atoms/Link/ExternalLink' +import { Divider } from '/app/atoms/structure' + +const ROBOT_CAL_HELP_ARTICLE = + 'https://support.opentrons.com/s/article/How-positional-calibration-works-on-the-OT-2' +interface HowCalibrationWorksModalProps { + onCloseClick: () => unknown +} + +export function HowCalibrationWorksModal({ + onCloseClick, +}: HowCalibrationWorksModalProps): JSX.Element { + const { t } = useTranslation(['protocol_setup', 'shared']) + return createPortal( + + + + {t('robot_cal_description')} + + + {t('learn_more_about_robot_cal_link')} + + + + + {/* deck calibration */} + + {t('deck_calibration_title')} + + + + {t('tip_length_cal_title')} + + + {/* pipette offset calibration */} + + {t('pipette_offset_cal')} + + + + + {t('shared:close')} + + + , + getTopPortalEl() + ) +} + +interface CalibrationStepsProps { + description: string + steps: string[] +} +function CalibrationSteps({ + description, + steps, +}: CalibrationStepsProps): JSX.Element { + return ( + + + {description} + +
      + {steps.map(step => ( +
    • + {step} +
    • + ))} +
    +
    + ) +} diff --git a/app/src/organisms/AddCustomLabwareSlideout/__tests__/AddCustomLabwareSlideout.test.tsx b/app/src/organisms/Desktop/Labware/AddCustomLabwareSlideout/__tests__/AddCustomLabwareSlideout.test.tsx similarity index 79% rename from app/src/organisms/AddCustomLabwareSlideout/__tests__/AddCustomLabwareSlideout.test.tsx rename to app/src/organisms/Desktop/Labware/AddCustomLabwareSlideout/__tests__/AddCustomLabwareSlideout.test.tsx index f3882525ef8..1a4fef0be5c 100644 --- a/app/src/organisms/AddCustomLabwareSlideout/__tests__/AddCustomLabwareSlideout.test.tsx +++ b/app/src/organisms/Desktop/Labware/AddCustomLabwareSlideout/__tests__/AddCustomLabwareSlideout.test.tsx @@ -1,18 +1,23 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { describe, it, expect, vi, beforeEach } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { useTrackEvent, ANALYTICS_ADD_CUSTOM_LABWARE, -} from '../../../redux/analytics' -import { renderWithProviders } from '../../../__testing-utils__' +} from '/app/redux/analytics' +import { renderWithProviders } from '/app/__testing-utils__' import { AddCustomLabwareSlideout } from '..' -vi.mock('../../../redux/custom-labware') -vi.mock('../../../pages/Labware/helpers/getAllDefs') -vi.mock('../../../redux/analytics') +vi.mock('/app/redux/custom-labware') +vi.mock('/app/local-resources/labware') +vi.mock('/app/redux/analytics') +vi.mock('/app/redux/shell/remote', () => ({ + remote: { + getFilePathFrom: vi.fn(), + }, +})) let mockTrackEvent: any diff --git a/app/src/organisms/Desktop/Labware/AddCustomLabwareSlideout/index.tsx b/app/src/organisms/Desktop/Labware/AddCustomLabwareSlideout/index.tsx new file mode 100644 index 00000000000..448a97c4b79 --- /dev/null +++ b/app/src/organisms/Desktop/Labware/AddCustomLabwareSlideout/index.tsx @@ -0,0 +1,84 @@ +import { useDispatch } from 'react-redux' +import { useTranslation, Trans } from 'react-i18next' +import { + ALIGN_CENTER, + COLORS, + DIRECTION_COLUMN, + Flex, + Link, + SPACING, + LegacyStyledText, +} from '@opentrons/components' +import { + addCustomLabwareFile, + addCustomLabware, +} from '/app/redux/custom-labware' +import { Slideout } from '/app/atoms/Slideout' +import { + useTrackEvent, + ANALYTICS_ADD_CUSTOM_LABWARE, +} from '/app/redux/analytics' +import { UploadInput } from '/app/molecules/UploadInput' +import { remote } from '/app/redux/shell/remote' + +import type { Dispatch } from '/app/redux/types' + +export interface AddCustomLabwareSlideoutProps { + isExpanded: boolean + onCloseClick: () => void +} + +export function AddCustomLabwareSlideout( + props: AddCustomLabwareSlideoutProps +): JSX.Element { + const { t } = useTranslation(['labware_landing', 'shared']) + const dispatch = useDispatch() + const trackEvent = useTrackEvent() + + return ( + + + { + void remote.getFilePathFrom(file).then(filePath => { + dispatch(addCustomLabwareFile(filePath)) + }) + }} + onClick={() => { + dispatch(addCustomLabware()) + trackEvent({ + name: ANALYTICS_ADD_CUSTOM_LABWARE, + properties: {}, + }) + }} + uploadText={t('choose_file_to_upload')} + dragAndDropText={ + + dispatch(addCustomLabware())} + role="button" + /> + ), + }} + /> + + } + /> + + + ) +} diff --git a/app/src/organisms/LabwareCard/CustomLabwareOverflowMenu.tsx b/app/src/organisms/Desktop/Labware/LabwareCard/CustomLabwareOverflowMenu.tsx similarity index 85% rename from app/src/organisms/LabwareCard/CustomLabwareOverflowMenu.tsx rename to app/src/organisms/Desktop/Labware/LabwareCard/CustomLabwareOverflowMenu.tsx index 5c80bdd2ec6..08826189798 100644 --- a/app/src/organisms/LabwareCard/CustomLabwareOverflowMenu.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareCard/CustomLabwareOverflowMenu.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next' import { useTrackEvent, ANALYTICS_OPEN_LABWARE_CREATOR_FROM_OVERFLOW_MENU, -} from '../../redux/analytics' +} from '/app/redux/analytics' import { AlertPrimaryButton, ALIGN_CENTER, @@ -19,6 +19,7 @@ import { LegacyStyledText, MenuItem, Modal, + NO_WRAP, OverflowBtn, POSITION_ABSOLUTE, POSITION_RELATIVE, @@ -28,14 +29,15 @@ import { useOnClickOutside, } from '@opentrons/components' -import { Divider } from '../../atoms/structure' -import { getTopPortalEl } from '../../App/portal' +import { Divider } from '/app/atoms/structure' +import { getTopPortalEl } from '/app/App/portal' import { deleteCustomLabwareFile, openCustomLabwareDirectory, -} from '../../redux/custom-labware' +} from '/app/redux/custom-labware' -import type { Dispatch } from '../../redux/types' +import type { MouseEventHandler } from 'react' +import type { Dispatch } from '/app/redux/types' const LABWARE_CREATOR_HREF = 'https://labware.opentrons.com/create/' @@ -50,7 +52,7 @@ export function CustomLabwareOverflowMenu( const { filename, onDelete } = props const { t } = useTranslation(['labware_landing', 'shared']) const dispatch = useDispatch() - const [showOverflowMenu, setShowOverflowMenu] = React.useState(false) + const [showOverflowMenu, setShowOverflowMenu] = useState(false) const overflowMenuRef = useOnClickOutside({ onClickOutside: () => { setShowOverflowMenu(false) @@ -66,24 +68,24 @@ export function CustomLabwareOverflowMenu( dispatch(deleteCustomLabwareFile(filename)) onDelete?.() }, true) - const handleOpenInFolder: React.MouseEventHandler = e => { + const handleOpenInFolder: MouseEventHandler = e => { e.preventDefault() e.stopPropagation() setShowOverflowMenu(false) dispatch(openCustomLabwareDirectory()) } - const handleClickDelete: React.MouseEventHandler = e => { + const handleClickDelete: MouseEventHandler = e => { e.preventDefault() e.stopPropagation() setShowOverflowMenu(false) confirmDeleteLabware() } - const handleOverflowClick: React.MouseEventHandler = e => { + const handleOverflowClick: MouseEventHandler = e => { e.preventDefault() e.stopPropagation() setShowOverflowMenu(currentShowOverflowMenu => !currentShowOverflowMenu) } - const handleClickLabwareCreator: React.MouseEventHandler = e => { + const handleClickLabwareCreator: MouseEventHandler = e => { e.preventDefault() e.stopPropagation() trackEvent({ @@ -94,7 +96,7 @@ export function CustomLabwareOverflowMenu( window.open(LABWARE_CREATOR_HREF, '_blank') } - const handleCancelModal: React.MouseEventHandler = e => { + const handleCancelModal: MouseEventHandler = e => { e.preventDefault() e.stopPropagation() cancelDeleteLabware() @@ -118,7 +120,7 @@ export function CustomLabwareOverflowMenu( top={SPACING.spacing32} right={0} flexDirection={DIRECTION_COLUMN} - whiteSpace="nowrap" + whiteSpace={NO_WRAP} > {t('show_in_folder')} diff --git a/app/src/organisms/LabwareCard/__tests__/CustomLabwareOverflowMenu.test.tsx b/app/src/organisms/Desktop/Labware/LabwareCard/__tests__/CustomLabwareOverflowMenu.test.tsx similarity index 93% rename from app/src/organisms/LabwareCard/__tests__/CustomLabwareOverflowMenu.test.tsx rename to app/src/organisms/Desktop/Labware/LabwareCard/__tests__/CustomLabwareOverflowMenu.test.tsx index b21600a354e..588b47ecea3 100644 --- a/app/src/organisms/LabwareCard/__tests__/CustomLabwareOverflowMenu.test.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareCard/__tests__/CustomLabwareOverflowMenu.test.tsx @@ -1,18 +1,18 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest' import { useConditionalConfirm } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useTrackEvent } from '../../../redux/analytics' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useTrackEvent } from '/app/redux/analytics' import { CustomLabwareOverflowMenu } from '../CustomLabwareOverflowMenu' import type { Mock } from 'vitest' import type * as OpentronsComponents from '@opentrons/components' -vi.mock('../../../redux/analytics') +vi.mock('/app/redux/analytics') const mockConfirm = vi.fn() const mockCancel = vi.fn() diff --git a/app/src/organisms/LabwareCard/__tests__/LabwareCard.test.tsx b/app/src/organisms/Desktop/Labware/LabwareCard/__tests__/LabwareCard.test.tsx similarity index 84% rename from app/src/organisms/LabwareCard/__tests__/LabwareCard.test.tsx rename to app/src/organisms/Desktop/Labware/LabwareCard/__tests__/LabwareCard.test.tsx index e0fdcc361ed..76abb51c72f 100644 --- a/app/src/organisms/LabwareCard/__tests__/LabwareCard.test.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareCard/__tests__/LabwareCard.test.tsx @@ -1,19 +1,16 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, vi, beforeEach } from 'vitest' -import { - renderWithProviders, - nestedTextMatcher, -} from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useAllLabware } from '../../../pages/Labware/hooks' -import { mockDefinition } from '../../../redux/custom-labware/__fixtures__' +import { renderWithProviders, nestedTextMatcher } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useAllLabware } from '/app/local-resources/labware' +import { mockDefinition } from '/app/redux/custom-labware/__fixtures__' import { CustomLabwareOverflowMenu } from '../CustomLabwareOverflowMenu' import { LabwareCard } from '..' import type * as OpentronsComponents from '@opentrons/components' -vi.mock('../../../pages/Labware/hooks') +vi.mock('/app/local-resources/labware') vi.mock('../CustomLabwareOverflowMenu') vi.mock('@opentrons/components', async importOriginal => { diff --git a/app/src/organisms/Desktop/Labware/LabwareCard/hooks.tsx b/app/src/organisms/Desktop/Labware/LabwareCard/hooks.tsx new file mode 100644 index 00000000000..3a5890d7f60 --- /dev/null +++ b/app/src/organisms/Desktop/Labware/LabwareCard/hooks.tsx @@ -0,0 +1,21 @@ +import { useEffect } from 'react' +import type { RefObject } from 'react' + +export function useCloseOnOutsideClick( + ref: RefObject, + onClose: () => void +): void { + const handleClick = (e: MouseEvent): void => { + // @ts-expect-error node and event target types are mismatched + if (ref.current != null && !ref.current.contains(e.target)) { + onClose() + } + } + + useEffect(() => { + document.addEventListener('click', handleClick) + return () => { + document.removeEventListener('click', handleClick) + } + }) +} diff --git a/app/src/organisms/Desktop/Labware/LabwareCard/index.tsx b/app/src/organisms/Desktop/Labware/LabwareCard/index.tsx new file mode 100644 index 00000000000..6d445b4bbd5 --- /dev/null +++ b/app/src/organisms/Desktop/Labware/LabwareCard/index.tsx @@ -0,0 +1,157 @@ +import { useTranslation } from 'react-i18next' +import startCase from 'lodash/startCase' +import { format } from 'date-fns' + +import { + ALIGN_CENTER, + ALIGN_FLEX_END, + Box, + COLORS, + DIRECTION_COLUMN, + Flex, + Icon, + JUSTIFY_SPACE_BETWEEN, + LabwareRender, + OVERFLOW_WRAP_ANYWHERE, + RobotWorkSpace, + SPACING, + LegacyStyledText, + TYPOGRAPHY, + DISPLAY_GRID, +} from '@opentrons/components' + +import { UNIVERSAL_FLAT_ADAPTER_X_DIMENSION } from '../LabwareDetails/Gallery' +import { CustomLabwareOverflowMenu } from './CustomLabwareOverflowMenu' +import type { LabwareDefAndDate } from '/app/local-resources/labware' + +export interface LabwareCardProps { + labware: LabwareDefAndDate + onClick: () => void +} + +export function LabwareCard(props: LabwareCardProps): JSX.Element { + const { t } = useTranslation(['labware_landing', 'branded']) + const { definition, modified, filename } = props.labware + const apiName = definition.parameters.loadName + const displayName = definition?.metadata.displayName + const displayCategory = startCase(definition.metadata.displayCategory) + const isCustomDefinition = definition.namespace !== 'opentrons' + const xDimensionOverride = + definition.parameters.loadName === 'opentrons_universal_flat_adapter' + ? UNIVERSAL_FLAT_ADAPTER_X_DIMENSION + : definition.dimensions.xDimension + + return ( + + + + {() => } + + + {/* labware category name min:7.5 rem for the longest, Aluminum Block */} + + + {displayCategory} + + + {/* labware info */} + + + + + {displayName} + + {isCustomDefinition ? ( + + {t('custom_def')} + + ) : ( + + + + {t('branded:opentrons_def')} + + + )} + + + + {t('api_name')} + + + + {apiName} + + + + + {/* space for custom labware min: 3rem for date */} + {/* Note kj 06/30/2022 currently this section would not be ideal implementation + Once the team have an agreement for grid system, we could refactor */} + + {modified != null && filename != null && ( + + + + + {t('date_added')} + + + {format(new Date(modified), 'MM/dd/yyyy')} + + + + )} + + + ) +} diff --git a/app/src/organisms/LabwareDetails/Dimensions.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/Dimensions.tsx similarity index 93% rename from app/src/organisms/LabwareDetails/Dimensions.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/Dimensions.tsx index c0ef65a3553..ad08946ea42 100644 --- a/app/src/organisms/LabwareDetails/Dimensions.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/Dimensions.tsx @@ -1,10 +1,9 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import round from 'lodash/round' import { Box, SPACING, getFootprintDiagram } from '@opentrons/components' import { LabeledValue } from './StyledComponents/LabeledValue' import { ExpandingTitle } from './StyledComponents/ExpandingTitle' -import type { LabwareDefinition } from '../../pages/Labware/types' +import type { LabwareDefinition2 as LabwareDefinition } from '@opentrons/shared-data' const toFixed = (n: number): string => round(n, 2).toFixed(2) diff --git a/app/src/organisms/LabwareDetails/Gallery.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/Gallery.tsx similarity index 91% rename from app/src/organisms/LabwareDetails/Gallery.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/Gallery.tsx index 6cc4e10c85e..ed418218147 100644 --- a/app/src/organisms/LabwareDetails/Gallery.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/Gallery.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { Box, @@ -11,9 +11,10 @@ import { SPACING_AUTO, SPACING, } from '@opentrons/components' + import { labwareImages } from './labware-images' -import type { LabwareDefinition } from '../../pages/Labware/types' +import type { LabwareDefinition2 as LabwareDefinition } from '@opentrons/shared-data' export const UNIVERSAL_FLAT_ADAPTER_X_DIMENSION = 127.4 @@ -33,7 +34,7 @@ export function Gallery(props: GalleryProps): JSX.Element { ? 127.4 : dims.xDimension - const [currentImage, setCurrentImage] = React.useState(0) + const [currentImage, setCurrentImage] = useState(0) const render = ( (false) + const [diagramVisible, setDiagramVisible] = useState(false) const toggleDiagramVisible = (): void => { setDiagramVisible(currentDiagramVisible => !currentDiagramVisible) } @@ -39,7 +40,7 @@ export function ExpandingTitle(props: ExpandingTitleProps): JSX.Element { )} diff --git a/app/src/organisms/Desktop/Labware/LabwareDetails/StyledComponents/LabeledValue.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/StyledComponents/LabeledValue.tsx new file mode 100644 index 00000000000..4f1486e38ea --- /dev/null +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/StyledComponents/LabeledValue.tsx @@ -0,0 +1,30 @@ +import { + ALIGN_CENTER, + COLORS, + DIRECTION_ROW, + Flex, + JUSTIFY_SPACE_BETWEEN, + SPACING, + LegacyStyledText, +} from '@opentrons/components' + +export interface LabeledValueProps { + label: string + value: number | string +} + +export function LabeledValue({ label, value }: LabeledValueProps): JSX.Element { + return ( + + + {label} + + {value} + + ) +} diff --git a/app/src/organisms/LabwareDetails/StyledComponents/__tests__/ExpandingTitle.test.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/StyledComponents/__tests__/ExpandingTitle.test.tsx similarity index 92% rename from app/src/organisms/LabwareDetails/StyledComponents/__tests__/ExpandingTitle.test.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/StyledComponents/__tests__/ExpandingTitle.test.tsx index 792a8eab2fa..6ee44619bc8 100644 --- a/app/src/organisms/LabwareDetails/StyledComponents/__tests__/ExpandingTitle.test.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/StyledComponents/__tests__/ExpandingTitle.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, beforeEach } from 'vitest' import { getFootprintDiagram } from '@opentrons/components' -import { renderWithProviders } from '../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { ExpandingTitle } from '../ExpandingTitle' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/LabwareDetails/StyledComponents/__tests__/LabeledValue.test.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/StyledComponents/__tests__/LabeledValue.test.tsx similarity index 89% rename from app/src/organisms/LabwareDetails/StyledComponents/__tests__/LabeledValue.test.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/StyledComponents/__tests__/LabeledValue.test.tsx index c410a7f556f..c3a73771f52 100644 --- a/app/src/organisms/LabwareDetails/StyledComponents/__tests__/LabeledValue.test.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/StyledComponents/__tests__/LabeledValue.test.tsx @@ -1,7 +1,7 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { LabeledValue } from '../LabeledValue' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/LabwareDetails/WellCount.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/WellCount.tsx similarity index 96% rename from app/src/organisms/LabwareDetails/WellCount.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/WellCount.tsx index b9ce2823489..f6d0af53c58 100644 --- a/app/src/organisms/LabwareDetails/WellCount.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/WellCount.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, diff --git a/app/src/organisms/LabwareDetails/WellDimensions.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/WellDimensions.tsx similarity index 93% rename from app/src/organisms/LabwareDetails/WellDimensions.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/WellDimensions.tsx index c056cb409f7..3a111482288 100644 --- a/app/src/organisms/LabwareDetails/WellDimensions.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/WellDimensions.tsx @@ -1,14 +1,11 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import round from 'lodash/round' import { Box, SPACING, getMeasurementDiagram } from '@opentrons/components' import { LabeledValue } from './StyledComponents/LabeledValue' import { ExpandingTitle } from './StyledComponents/ExpandingTitle' -import type { - LabwareWellGroupProperties, - LabwareParameters, -} from '../../pages/Labware/types' +import type { LabwareParameters } from '@opentrons/shared-data' +import type { LabwareWellGroupProperties } from '/app/local-resources/labware' const toFixed = (n: number): string => round(n, 2).toFixed(2) diff --git a/app/src/organisms/LabwareDetails/WellProperties.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/WellProperties.tsx similarity index 94% rename from app/src/organisms/LabwareDetails/WellProperties.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/WellProperties.tsx index aa20ad8f0dd..97f34499665 100644 --- a/app/src/organisms/LabwareDetails/WellProperties.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/WellProperties.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { Box, @@ -13,11 +12,12 @@ import { } from '@opentrons/components' import { getDisplayVolume } from '@opentrons/shared-data' +import type { LabwareWellGroupProperties } from '/app/local-resources/labware' + import type { - LabwareDefinition, - LabwareWellGroupProperties, + LabwareDefinition2 as LabwareDefinition, LabwareVolumeUnits, -} from '../../pages/Labware/types' +} from '@opentrons/shared-data' export interface AllWellPropertiesProps { definition: LabwareDefinition diff --git a/app/src/organisms/LabwareDetails/WellSpacing.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/WellSpacing.tsx similarity index 94% rename from app/src/organisms/LabwareDetails/WellSpacing.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/WellSpacing.tsx index 2eb9bb6b028..9769cbb295b 100644 --- a/app/src/organisms/LabwareDetails/WellSpacing.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/WellSpacing.tsx @@ -1,11 +1,10 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import round from 'lodash/round' import { getSpacingDiagram } from '@opentrons/components' import { LabeledValue } from './StyledComponents/LabeledValue' import { ExpandingTitle } from './StyledComponents/ExpandingTitle' -import type { LabwareWellGroupProperties } from '../../pages/Labware/types' +import type { LabwareWellGroupProperties } from '/app/local-resources/labware' const toFixed = (n: number): string => round(n, 2).toFixed(2) diff --git a/app/src/organisms/LabwareDetails/__tests__/Dimensions.test.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/Dimensions.test.tsx similarity index 77% rename from app/src/organisms/LabwareDetails/__tests__/Dimensions.test.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/Dimensions.test.tsx index f6c864c9162..32de832214c 100644 --- a/app/src/organisms/LabwareDetails/__tests__/Dimensions.test.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/Dimensions.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockDefinition } from '../../../redux/custom-labware/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockDefinition } from '/app/redux/custom-labware/__fixtures__' import { Dimensions } from '../Dimensions' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/LabwareDetails/__tests__/Gallery.test.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/Gallery.test.tsx similarity index 90% rename from app/src/organisms/LabwareDetails/__tests__/Gallery.test.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/Gallery.test.tsx index 8af2a4ad0d3..344981336b0 100644 --- a/app/src/organisms/LabwareDetails/__tests__/Gallery.test.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/Gallery.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { mockDefinition } from '../../../redux/custom-labware/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { mockDefinition } from '/app/redux/custom-labware/__fixtures__' import { labwareImages } from '../labware-images' import { Gallery } from '../Gallery' diff --git a/app/src/organisms/LabwareDetails/__tests__/LabwareDetails.test.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/LabwareDetails.test.tsx similarity index 92% rename from app/src/organisms/LabwareDetails/__tests__/LabwareDetails.test.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/LabwareDetails.test.tsx index 6567b404287..78c98c50a46 100644 --- a/app/src/organisms/LabwareDetails/__tests__/LabwareDetails.test.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/LabwareDetails.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, afterEach, vi, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useAllLabware } from '../../../pages/Labware/hooks' -import { mockOpentronsLabwareDetailsDefinition } from '../../../redux/custom-labware/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useAllLabware } from '/app/local-resources/labware' +import { mockOpentronsLabwareDetailsDefinition } from '/app/redux/custom-labware/__fixtures__' import { CustomLabwareOverflowMenu } from '../../LabwareCard/CustomLabwareOverflowMenu' import { Dimensions } from '../Dimensions' import { Gallery } from '../Gallery' @@ -17,7 +17,7 @@ import { WellSpacing } from '../WellSpacing' import { LabwareDetails } from '..' -vi.mock('../../../pages/Labware/hooks') +vi.mock('/app/local-resources/labware') vi.mock('../../LabwareCard/CustomLabwareOverflowMenu') vi.mock('../Dimensions') vi.mock('../Gallery') diff --git a/app/src/organisms/LabwareDetails/__tests__/ManufacturerDetails.test.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/ManufacturerDetails.test.tsx similarity index 91% rename from app/src/organisms/LabwareDetails/__tests__/ManufacturerDetails.test.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/ManufacturerDetails.test.tsx index 925b8351bf4..21dc8c8f1a2 100644 --- a/app/src/organisms/LabwareDetails/__tests__/ManufacturerDetails.test.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/ManufacturerDetails.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, expect, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ManufacturerDetails } from '../ManufacturerDetails' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/LabwareDetails/__tests__/WellCount.test.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/WellCount.test.tsx similarity index 82% rename from app/src/organisms/LabwareDetails/__tests__/WellCount.test.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/WellCount.test.tsx index b02d071a22b..f833c0c6e3f 100644 --- a/app/src/organisms/LabwareDetails/__tests__/WellCount.test.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/WellCount.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { WellCount } from '../WellCount' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/LabwareDetails/__tests__/WellDimensions.test.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/WellDimensions.test.tsx similarity index 90% rename from app/src/organisms/LabwareDetails/__tests__/WellDimensions.test.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/WellDimensions.test.tsx index f31ef09c86b..3269c3ec241 100644 --- a/app/src/organisms/LabwareDetails/__tests__/WellDimensions.test.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/WellDimensions.test.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, beforeEach, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockDefinition, mockCircularLabwareWellGroupProperties, mockRectangularLabwareWellGroupProperties, -} from '../../../redux/custom-labware/__fixtures__' +} from '/app/redux/custom-labware/__fixtures__' import { WellDimensions } from '../WellDimensions' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/LabwareDetails/__tests__/WellProperties.test.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/WellProperties.test.tsx similarity index 85% rename from app/src/organisms/LabwareDetails/__tests__/WellProperties.test.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/WellProperties.test.tsx index 03852fd7f6f..3d21e01f4df 100644 --- a/app/src/organisms/LabwareDetails/__tests__/WellProperties.test.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/WellProperties.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, expect, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockCircularLabwareWellGroupProperties } from '../../../redux/custom-labware/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockCircularLabwareWellGroupProperties } from '/app/redux/custom-labware/__fixtures__' import { WellProperties } from '../WellProperties' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/LabwareDetails/__tests__/WellSpacing.test.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/WellSpacing.test.tsx similarity index 86% rename from app/src/organisms/LabwareDetails/__tests__/WellSpacing.test.tsx rename to app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/WellSpacing.test.tsx index c2273e705ee..5496bbe33f1 100644 --- a/app/src/organisms/LabwareDetails/__tests__/WellSpacing.test.tsx +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/__tests__/WellSpacing.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, beforeEach, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockCircularLabwareWellGroupProperties } from '../../../redux/custom-labware/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockCircularLabwareWellGroupProperties } from '/app/redux/custom-labware/__fixtures__' import { WellSpacing } from '../WellSpacing' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/LabwareDetails/helpers/labels.ts b/app/src/organisms/Desktop/Labware/LabwareDetails/helpers/labels.ts similarity index 86% rename from app/src/organisms/LabwareDetails/helpers/labels.ts rename to app/src/organisms/Desktop/Labware/LabwareDetails/helpers/labels.ts index 06590d2ad80..204c9114dc6 100644 --- a/app/src/organisms/LabwareDetails/helpers/labels.ts +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/helpers/labels.ts @@ -1,8 +1,7 @@ import uniqBy from 'lodash/uniqBy' -import type { - LabwareWellGroupProperties, - LabwareDefinition, -} from '../../../pages/Labware/types' +import type { LabwareWellGroupProperties } from '/app/local-resources/labware' +import type { LabwareDefinition2 as LabwareDefinition } from '@opentrons/shared-data' + const WELL_TYPE_BY_CATEGORY = { tubeRack: 'tube', tipRack: 'tip', diff --git a/app/src/organisms/Desktop/Labware/LabwareDetails/index.tsx b/app/src/organisms/Desktop/Labware/LabwareDetails/index.tsx new file mode 100644 index 00000000000..6e9d9e32ef7 --- /dev/null +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/index.tsx @@ -0,0 +1,257 @@ +import { useState, useEffect, Fragment } from 'react' +import { useTranslation } from 'react-i18next' +import { format } from 'date-fns' +import { css } from 'styled-components' + +import { + ALIGN_CENTER, + BORDERS, + Box, + COLORS, + DIRECTION_COLUMN, + DIRECTION_ROW, + Flex, + Icon, + JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, + Link, + OVERFLOW_WRAP_ANYWHERE, + SIZE_1, + SPACING, + TOOLTIP_TOP_START, + Tooltip, + TYPOGRAPHY, + useHoverTooltip, +} from '@opentrons/components' +import { getUniqueWellProperties } from '@opentrons/shared-data' +import { Slideout } from '/app/atoms/Slideout' +import { getWellLabel } from './helpers/labels' +import { WellCount } from './WellCount' +import { WellProperties } from './WellProperties' +import { Dimensions } from './Dimensions' +import { WellDimensions } from './WellDimensions' +import { WellSpacing } from './WellSpacing' +import { ManufacturerDetails } from './ManufacturerDetails' +import { InsertDetails } from './InsertDetails' +import { Gallery } from './Gallery' +import { CustomLabwareOverflowMenu } from '../LabwareCard/CustomLabwareOverflowMenu' +import type { LabwareDefAndDate } from '/app/local-resources/labware' + +const CLOSE_ICON_STYLE = css` + border-radius: 50%; + + &:hover { + background: ${COLORS.grey30}; + } + &:active { + background: ${COLORS.grey35}; + } +` + +const COPY_ICON_STYLE = css` + transform: translateY(${SPACING.spacing4}); + &:hover { + color: ${COLORS.blue50}; + } + &:active, + &:focus { + color: ${COLORS.black90}; + } +` + +export interface LabwareDetailsProps { + onClose: () => void + labware: LabwareDefAndDate +} + +export function LabwareDetails(props: LabwareDetailsProps): JSX.Element { + const { t } = useTranslation(['labware_landing', 'branded']) + const { definition, modified, filename } = props.labware + const { metadata, parameters, brand, wells, ordering } = definition + const apiName = definition.parameters.loadName + const { displayVolumeUnits } = metadata + const wellGroups = getUniqueWellProperties(definition) + const wellLabel = getWellLabel(definition) + const hasInserts = wellGroups.some(g => g.metadata.displayCategory) + const insert = wellGroups.find(g => g.metadata.displayCategory) + const insertCategory = insert?.metadata.displayCategory + const irregular = wellGroups.length > 1 + const isMultiRow = ordering.some(row => row.length > 1) + const isCustomDefinition = definition.namespace !== 'opentrons' + const [showToolTip, setShowToolTip] = useState(false) + const [targetProps, tooltipProps] = useHoverTooltip({ + placement: TOOLTIP_TOP_START, + }) + + const handleCopy = async (): Promise => { + await navigator.clipboard.writeText(apiName) + setShowToolTip(true) + } + + useEffect(() => { + const timer = setTimeout(() => { + setShowToolTip(false) + }, 2000) + return () => { + clearTimeout(timer) + } + }, [showToolTip]) + + const slideoutHeader = ( + + + + {props.labware.definition.metadata.displayName} + + + + + + {!isCustomDefinition && ( + + {' '} + + {t('branded:opentrons_def')} + + + )} + {modified != null && filename != null && ( + + + {t('last_updated')} {format(new Date(modified), 'MM/dd/yyyy')} + + + + )} + + ) + + return ( + + + + {t('api_name')} + + + + {apiName} + + + + + + {showToolTip && ( + + {t('copied')} + + )} + + + + + + {!hasInserts && !irregular && ( + + )} + + {wellGroups.map((wellProps, index) => { + const { metadata: groupMetadata } = wellProps + const wellLabel = getWellLabel(wellProps, definition) + const groupDisplaySuffix = + groupMetadata.displayName != null + ? ` - ${String(groupMetadata.displayName)}` + : '' + + return ( + + {groupMetadata.displayCategory == null && irregular && ( + <> + + + + )} + {groupMetadata.displayCategory == null && ( + + )} + + + ) + })} + + + + {hasInserts && } + + ) +} diff --git a/app/src/organisms/Desktop/Labware/LabwareDetails/labware-images.ts b/app/src/organisms/Desktop/Labware/LabwareDetails/labware-images.ts new file mode 100644 index 00000000000..4ec15033962 --- /dev/null +++ b/app/src/organisms/Desktop/Labware/LabwareDetails/labware-images.ts @@ -0,0 +1,260 @@ +// images by labware load name + +import agilent_1_reservoir_290ml_side_view from '/app/assets/images/labware/agilent_1_reservoir_290ml_side_view.jpg' +import axygen_1_reservoir_90ml_side_view from '/app/assets/images/labware/axygen_1_reservoir_90ml_side_view.jpg' +import biorad_96_wellplate_200ul_pcr_photo_three_quarters from '/app/assets/images/labware/biorad_96_wellplate_200ul_pcr_photo_three_quarters.jpg' +import corning_12_wellplate_6_9ml_flat_photo_three_quarters from '/app/assets/images/labware/corning_12_wellplate_6.9ml_flat_photo_three_quarters.jpg' +import corning_24_wellplate_3_4ml_flat_photo_three_quarters from '/app/assets/images/labware/corning_24_wellplate_3.4ml_flat_photo_three_quarters.jpg' +import corning_384_wellplate_112ul_flat_photo_three_quarters from '/app/assets/images/labware/corning_384_wellplate_112ul_flat_photo_three_quarters.jpg' +import corning_96_wellplate_360ul_flat_three_quarters from '/app/assets/images/labware/corning_96_wellplate_360ul_flat_three_quarters.jpg' +import corning_48_wellplate_1_6ml_flat_photo_three_quarters from '/app/assets/images/labware/corning_48_wellplate_1.6ml_flat_photo_three_quarters.jpg' +import corning_6_wellplate_16_8ml_flat_photo_three_quarters from '/app/assets/images/labware/corning_6_wellplate_16.8ml_flat_photo_three_quarters.jpg' +import eppendorf_1000ul_tip_eptips_side_view from '/app/assets/images/labware/eppendorf_1000ul_tip_eptips_side_view.jpg' +import eppendorf_10ul_tips_eptips_side_view from '/app/assets/images/labware/eppendorf_10ul_tips_eptips_side_view.jpg' +import geb_96_tiprack_1000ul_side_view from '/app/assets/images/labware/geb_96_tiprack_1000ul_side_view.jpg' +import geb_1000ul_tip_side_view from '/app/assets/images/labware/geb_1000ul_tip_side_view.jpg' +import geb_96_tiprack_10ul_side_view from '/app/assets/images/labware/geb_96_tiprack_10ul_side_view.jpg' +import geb_10ul_tip_side_view from '/app/assets/images/labware/geb_10ul_tip_side_view.jpg' +import nest_1_reservoir_195ml_three_quarters from '/app/assets/images/labware/nest_1_reservoir_195ml_three_quarters.jpg' +import nest_1_reservoir_290ml from '/app/assets/images/labware/nest_1_reservoir_290ml.jpg' +import nest_12_reservoir_15ml_three_quarters from '/app/assets/images/labware/nest_12_reservoir_15ml_three_quarters.jpg' +import nest_96_wellplate_100ul_pcr_full_skirt_three_quarters from '/app/assets/images/labware/nest_96_wellplate_100ul_pcr_full_skirt_three_quarters.jpg' +import nest_96_wellplate_200ul_flat_three_quarters from '/app/assets/images/labware/nest_96_wellplate_200ul_flat_three_quarters.jpg' +import nest_96_wellplate_2ml_deep from '/app/assets/images/labware/nest_96_wellplate_2ml_deep.jpg' +import opentrons_10_tuberack_4_6_side_view from '/app/assets/images/labware/opentrons_10_tuberack_4_6_side_view.jpg' +import falcon_50ml_15ml_conical_tubes from '/app/assets/images/labware/falcon_50ml_15ml_conical_tubes.jpg' +import opentrons_15_tuberack_side_view from '/app/assets/images/labware/opentrons_15_tuberack_side_view.jpg' +import falcon_15ml_conical_tube from '/app/assets/images/labware/falcon_15ml_conical_tube.jpg' +import nest_50ml_15ml_conical_tubes from '/app/assets/images/labware/nest_50ml_15ml_conical_tubes.jpg' +import nest_15ml_conical_tube from '/app/assets/images/labware/nest_15ml_conical_tube.jpg' +import opentrons_6_tuberack_side_view from '/app/assets/images/labware/opentrons_6_tuberack_side_view.jpg' +import nest_50ml_conical_tube from '/app/assets/images/labware/nest_50ml_conical_tube.jpg' +import opentrons_24_aluminumblock_side_view from '/app/assets/images/labware/opentrons_24_aluminumblock_side_view.jpg' +import generic_2ml_screwcap_tube from '/app/assets/images/labware/generic_2ml_screwcap_tube.jpg' +import nest_0_5ml_screwcap_tube from '/app/assets/images/labware/nest_0.5ml_screwcap_tube.jpg' +import nest_1_5ml_screwcap_tube from '/app/assets/images/labware/nest_1.5ml_screwcap_tube.jpg' +import nest_1_5ml_snapcap_tube from '/app/assets/images/labware/nest_1.5ml_snapcap_tube.jpg' +import nest_2ml_screwcap_tube from '/app/assets/images/labware/nest_2ml_screwcap_tube.jpg' +import nest_2ml_snapcap_tube from '/app/assets/images/labware/nest_2ml_snapcap_tube.jpg' +import opentrons_24_tuberack_side_view from '/app/assets/images/labware/opentrons_24_tuberack_side_view.jpg' +import eppendorf_1_5ml_safelock_snapcap_tube from '/app/assets/images/labware/eppendorf_1.5ml_safelock_snapcap_tube.jpg' +import eppendorf_2ml_safelock_snapcap_tube from '/app/assets/images/labware/eppendorf_2ml_safelock_snapcap_tube.jpg' +import falcon_50ml_conical_tube from '/app/assets/images/labware/falcon_50ml_conical_tube.jpg' +import generic_pcr_strip_200ul_tubes from '/app/assets/images/labware/generic_pcr_strip_200ul_tubes.jpg' +import opentrons_96_tiprack_1000ul_side_view from '/app/assets/images/labware/opentrons_96_tiprack_1000ul_side_view.jpg' +import opentrons_96_tiprack_10ul_side_view from '/app/assets/images/labware/opentrons_96_tiprack_10ul_side_view.jpg' +import opentrons_96_tiprack_300ul_side_view from '/app/assets/images/labware/opentrons_96_tiprack_300ul_side_view.jpg' +import tipone_96_tiprack_200ul_side_view from '/app/assets/images/labware/tipone_96_tiprack_200ul_side_view.jpg' +import tipone_200ul_tip_side_view from '/app/assets/images/labware/tipone_200ul_tip_side_view.jpg' +import usascientific_12_reservoir_22ml_side_view from '/app/assets/images/labware/usascientific_12_reservoir_22ml_side_view.jpg' +import usascientific_96_wellplate_2_4ml_deep_side_view from '/app/assets/images/labware/usascientific_96_wellplate_2.4ml_deep_side_view.jpg' +import thermoscientificnunc_96_wellplate_1300ul from '/app/assets/images/labware/thermoscientificnunc_96_wellplate_1300ul.jpg' +import thermoscientificnunc_96_wellplate_2000ul from '/app/assets/images/labware/thermoscientificnunc_96_wellplate_2000ul.jpg' +import appliedbiosystemsmicroamp_384_wellplate_40ul from '/app/assets/images/labware/appliedbiosystemsmicroamp_384_wellplate_40ul.jpg' +import biorad_384_wellplate_50ul from '/app/assets/images/labware/biorad_384_wellplate_50ul.jpg' +import deep_well_plate_adapter from '/app/assets/images/labware/deep_well_plate_adapter.jpg' +import flat_bottom_plate_adapter from '/app/assets/images/labware/flat_bottom_plate_adapter.jpg' +import pcr_plate_adapter from '/app/assets/images/labware/pcr_plate_adapter.jpg' +import universal_flat_adapter from '/app/assets/images/labware/universal_flat_adapter.jpg' +import flat_bottom_aluminum from '/app/assets/images/labware/flat_bottom_aluminum.png' +import opentrons_96_aluminumblock_side_view from '/app/assets/images/labware/opentrons_96_aluminumblock_side_view.jpg' +import opentrons_96_deep_well_temp_mod_adapter_img from '/app/assets/images/labware/opentrons_96_deep_well_temp_mod_adapter.png' +import opentrons_flex_deck_riser_img from '/app/assets/images/labware/opentrons_flex_deck_riser.png' + +export const labwareImages: Record = { + agilent_1_reservoir_290ml: [agilent_1_reservoir_290ml_side_view], + axygen_1_reservoir_90ml: [axygen_1_reservoir_90ml_side_view], + biorad_96_wellplate_200ul_pcr: [ + biorad_96_wellplate_200ul_pcr_photo_three_quarters, + ], + 'corning_12_wellplate_6.9ml_flat': [ + corning_12_wellplate_6_9ml_flat_photo_three_quarters, + ], + 'corning_24_wellplate_3.4ml_flat': [ + corning_24_wellplate_3_4ml_flat_photo_three_quarters, + ], + corning_384_wellplate_112ul_flat: [ + corning_384_wellplate_112ul_flat_photo_three_quarters, + ], + corning_96_wellplate_360ul_flat: [ + corning_96_wellplate_360ul_flat_three_quarters, + ], + 'corning_48_wellplate_1.6ml_flat': [ + corning_48_wellplate_1_6ml_flat_photo_three_quarters, + ], + 'corning_6_wellplate_16.8ml_flat': [ + corning_6_wellplate_16_8ml_flat_photo_three_quarters, + ], + eppendorf_96_tiprack_1000ul_eptips: [eppendorf_1000ul_tip_eptips_side_view], + eppendorf_96_tiprack_10ul_eptips: [eppendorf_10ul_tips_eptips_side_view], + geb_96_tiprack_1000ul: [ + geb_96_tiprack_1000ul_side_view, + geb_1000ul_tip_side_view, + ], + geb_96_tiprack_10ul: [geb_96_tiprack_10ul_side_view, geb_10ul_tip_side_view], + nest_1_reservoir_195ml: [nest_1_reservoir_195ml_three_quarters], + nest_1_reservoir_290ml: [nest_1_reservoir_290ml], + nest_12_reservoir_15ml: [nest_12_reservoir_15ml_three_quarters], + nest_96_wellplate_100ul_pcr_full_skirt: [ + nest_96_wellplate_100ul_pcr_full_skirt_three_quarters, + ], + nest_96_wellplate_200ul_flat: [nest_96_wellplate_200ul_flat_three_quarters], + nest_96_wellplate_2ml_deep: [nest_96_wellplate_2ml_deep], + opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical: [ + opentrons_10_tuberack_4_6_side_view, + falcon_50ml_15ml_conical_tubes, + ], + opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical_acrylic: [ + falcon_50ml_15ml_conical_tubes, + ], + opentrons_15_tuberack_falcon_15ml_conical: [ + opentrons_15_tuberack_side_view, + falcon_15ml_conical_tube, + ], + opentrons_10_tuberack_nest_4x50ml_6x15ml_conical: [ + opentrons_10_tuberack_4_6_side_view, + nest_50ml_15ml_conical_tubes, + ], + opentrons_15_tuberack_nest_15ml_conical: [ + opentrons_15_tuberack_side_view, + nest_15ml_conical_tube, + ], + opentrons_6_tuberack_nest_50ml_conical: [ + opentrons_6_tuberack_side_view, + nest_50ml_conical_tube, + ], + opentrons_1_trash_1100ml_fixed: [], + opentrons_1_trash_850ml_fixed: [], + opentrons_24_aluminumblock_generic_2ml_screwcap: [ + opentrons_24_aluminumblock_side_view, + generic_2ml_screwcap_tube, + ], + 'opentrons_24_aluminumblock_nest_0.5ml_screwcap': [ + opentrons_24_aluminumblock_side_view, + nest_0_5ml_screwcap_tube, + ], + 'opentrons_24_aluminumblock_nest_1.5ml_screwcap': [ + opentrons_24_aluminumblock_side_view, + nest_1_5ml_screwcap_tube, + ], + 'opentrons_24_aluminumblock_nest_1.5ml_snapcap': [ + opentrons_24_aluminumblock_side_view, + nest_1_5ml_snapcap_tube, + ], + opentrons_24_aluminumblock_nest_2ml_screwcap: [ + opentrons_24_aluminumblock_side_view, + nest_2ml_screwcap_tube, + ], + opentrons_24_aluminumblock_nest_2ml_snapcap: [ + opentrons_24_aluminumblock_side_view, + nest_2ml_snapcap_tube, + ], + 'opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap': [ + opentrons_24_tuberack_side_view, + eppendorf_1_5ml_safelock_snapcap_tube, + ], + opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap: [ + opentrons_24_tuberack_side_view, + eppendorf_2ml_safelock_snapcap_tube, + ], + 'opentrons_24_tuberack_nest_0.5ml_screwcap': [ + opentrons_24_tuberack_side_view, + nest_0_5ml_screwcap_tube, + ], + 'opentrons_24_tuberack_nest_1.5ml_screwcap': [ + opentrons_24_tuberack_side_view, + nest_1_5ml_screwcap_tube, + ], + 'opentrons_24_tuberack_nest_1.5ml_snapcap': [ + opentrons_24_tuberack_side_view, + nest_1_5ml_snapcap_tube, + ], + opentrons_24_tuberack_nest_2ml_screwcap: [ + opentrons_24_tuberack_side_view, + nest_2ml_screwcap_tube, + ], + opentrons_24_tuberack_nest_2ml_snapcap: [ + opentrons_24_tuberack_side_view, + nest_2ml_snapcap_tube, + ], + opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap_acrylic: [ + eppendorf_2ml_safelock_snapcap_tube, + ], + 'opentrons_24_tuberack_generic_0.75ml_snapcap_acrylic': [], + opentrons_24_tuberack_generic_2ml_screwcap: [ + opentrons_24_tuberack_side_view, + generic_2ml_screwcap_tube, + ], + 'opentrons_40_aluminumblock_eppendorf_24x2ml_safelock_snapcap_generic_16x0.2ml_pcr_strip': [ + eppendorf_2ml_safelock_snapcap_tube, + generic_pcr_strip_200ul_tubes, + ], + opentrons_6_tuberack_falcon_50ml_conical: [ + opentrons_6_tuberack_side_view, + falcon_50ml_conical_tube, + ], + opentrons_96_aluminumblock_biorad_wellplate_200ul: [ + opentrons_96_aluminumblock_side_view, + biorad_96_wellplate_200ul_pcr_photo_three_quarters, + ], + opentrons_96_aluminumblock_generic_pcr_strip_200ul: [ + opentrons_96_aluminumblock_side_view, + generic_pcr_strip_200ul_tubes, + ], + opentrons_96_aluminumblock_nest_wellplate_100ul: [ + opentrons_96_aluminumblock_side_view, + nest_96_wellplate_100ul_pcr_full_skirt_three_quarters, + ], + opentrons_96_tiprack_1000ul: [opentrons_96_tiprack_1000ul_side_view], + opentrons_96_tiprack_10ul: [opentrons_96_tiprack_10ul_side_view], + opentrons_96_tiprack_20ul: [opentrons_96_tiprack_10ul_side_view], + opentrons_96_tiprack_300ul: [opentrons_96_tiprack_300ul_side_view], + opentrons_96_filtertiprack_1000ul: [opentrons_96_tiprack_1000ul_side_view], + opentrons_96_filtertiprack_10ul: [opentrons_96_tiprack_10ul_side_view], + opentrons_96_filtertiprack_20ul: [opentrons_96_tiprack_10ul_side_view], + opentrons_96_filtertiprack_200ul: [opentrons_96_tiprack_300ul_side_view], + tipone_96_tiprack_200ul: [ + tipone_96_tiprack_200ul_side_view, + tipone_200ul_tip_side_view, + ], + usascientific_12_reservoir_22ml: [usascientific_12_reservoir_22ml_side_view], + 'usascientific_96_wellplate_2.4ml_deep': [ + usascientific_96_wellplate_2_4ml_deep_side_view, + ], + thermoscientificnunc_96_wellplate_1300ul: [ + thermoscientificnunc_96_wellplate_1300ul, + ], + thermoscientificnunc_96_wellplate_2000ul: [ + thermoscientificnunc_96_wellplate_2000ul, + ], + appliedbiosystemsmicroamp_384_wellplate_40ul: [ + appliedbiosystemsmicroamp_384_wellplate_40ul, + ], + biorad_384_wellplate_50ul: [biorad_384_wellplate_50ul], + opentrons_96_deep_well_adapter: [deep_well_plate_adapter], + opentrons_96_flat_bottom_adapter: [flat_bottom_plate_adapter], + opentrons_96_pcr_adapter: [pcr_plate_adapter], + opentrons_universal_flat_adapter: [universal_flat_adapter], + opentrons_aluminum_flat_bottom_plate: [flat_bottom_aluminum], + opentrons_96_well_aluminum_block: [opentrons_96_aluminumblock_side_view], + opentrons_96_deep_well_adapter_nest_wellplate_2ml_deep: [ + deep_well_plate_adapter, + nest_96_wellplate_2ml_deep, + ], + opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat: [ + flat_bottom_plate_adapter, + nest_96_wellplate_200ul_flat_three_quarters, + ], + opentrons_96_pcr_adapter_nest_wellplate_100ul_pcr_full_skirt: [ + pcr_plate_adapter, + nest_96_wellplate_100ul_pcr_full_skirt_three_quarters, + ], + opentrons_universal_flat_adapter_corning_384_wellplate_112ul_flat: [ + universal_flat_adapter, + corning_384_wellplate_112ul_flat_photo_three_quarters, + ], + opentrons_96_deep_well_temp_mod_adapter: [ + opentrons_96_deep_well_temp_mod_adapter_img, + ], + opentrons_flex_deck_riser: [opentrons_flex_deck_riser_img], +} diff --git a/app/src/organisms/ProtocolAnalysisFailure/ProtocolAnalysisStale.tsx b/app/src/organisms/Desktop/ProtocolAnalysisFailure/ProtocolAnalysisStale.tsx similarity index 89% rename from app/src/organisms/ProtocolAnalysisFailure/ProtocolAnalysisStale.tsx rename to app/src/organisms/Desktop/ProtocolAnalysisFailure/ProtocolAnalysisStale.tsx index 230f19f07c7..9b3a7c00d14 100644 --- a/app/src/organisms/ProtocolAnalysisFailure/ProtocolAnalysisStale.tsx +++ b/app/src/organisms/Desktop/ProtocolAnalysisFailure/ProtocolAnalysisStale.tsx @@ -1,9 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { useDispatch } from 'react-redux' import { useTranslation, Trans } from 'react-i18next' import { ALIGN_CENTER, + Banner, Btn, Flex, JUSTIFY_SPACE_BETWEEN, @@ -13,10 +14,8 @@ import { WRAP_REVERSE, } from '@opentrons/components' -import { Banner } from '../../atoms/Banner' - -import type { Dispatch } from '../../redux/types' -import { analyzeProtocol } from '../../redux/protocol-storage' +import type { Dispatch } from '/app/redux/types' +import { analyzeProtocol } from '/app/redux/protocol-storage' interface ProtocolAnalysisStaleProps { protocolKey: string } diff --git a/app/src/organisms/ProtocolAnalysisFailure/__tests__/ProtocolAnalysisFailure.test.tsx b/app/src/organisms/Desktop/ProtocolAnalysisFailure/__tests__/ProtocolAnalysisFailure.test.tsx similarity index 88% rename from app/src/organisms/ProtocolAnalysisFailure/__tests__/ProtocolAnalysisFailure.test.tsx rename to app/src/organisms/Desktop/ProtocolAnalysisFailure/__tests__/ProtocolAnalysisFailure.test.tsx index 86f0feb7373..fbdec0a45ec 100644 --- a/app/src/organisms/ProtocolAnalysisFailure/__tests__/ProtocolAnalysisFailure.test.tsx +++ b/app/src/organisms/Desktop/ProtocolAnalysisFailure/__tests__/ProtocolAnalysisFailure.test.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ProtocolAnalysisFailure } from '..' -import { analyzeProtocol } from '../../../redux/protocol-storage' +import { analyzeProtocol } from '/app/redux/protocol-storage' const render = ( props: Partial> = {} diff --git a/app/src/organisms/Desktop/ProtocolAnalysisFailure/index.tsx b/app/src/organisms/Desktop/ProtocolAnalysisFailure/index.tsx new file mode 100644 index 00000000000..3fe86f0385f --- /dev/null +++ b/app/src/organisms/Desktop/ProtocolAnalysisFailure/index.tsx @@ -0,0 +1,128 @@ +import { useState } from 'react' +import { createPortal } from 'react-dom' +import { useDispatch } from 'react-redux' +import { useTranslation, Trans } from 'react-i18next' +import { css } from 'styled-components' + +import { + ALIGN_CENTER, + Banner, + Btn, + Flex, + JUSTIFY_FLEX_END, + JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, + Modal, + PrimaryButton, + SPACING, + TYPOGRAPHY, + WRAP_REVERSE, +} from '@opentrons/components' + +import { analyzeProtocol } from '/app/redux/protocol-storage' +import { getTopPortalEl } from '/app/App/portal' + +import type { MouseEventHandler } from 'react' +import type { Dispatch } from '/app/redux/types' +interface ProtocolAnalysisFailureProps { + errors: string[] + protocolKey: string +} + +export function ProtocolAnalysisFailure( + props: ProtocolAnalysisFailureProps +): JSX.Element { + const { errors, protocolKey } = props + const { t } = useTranslation(['protocol_list', 'shared']) + const dispatch = useDispatch() + const [showErrorDetails, setShowErrorDetails] = useState(false) + + const handleClickShowDetails: MouseEventHandler = e => { + e.preventDefault() + e.stopPropagation() + setShowErrorDetails(true) + } + const handleClickHideDetails: MouseEventHandler = e => { + e.preventDefault() + e.stopPropagation() + setShowErrorDetails(false) + } + const handleClickReanalyze: MouseEventHandler = e => { + e.preventDefault() + e.stopPropagation() + dispatch(analyzeProtocol(protocolKey)) + } + return ( + + + + {t('protocol_analysis_failure')} + + + + ), + analysisLink: ( + + ), + }} + /> + + + {showErrorDetails + ? createPortal( + + + {errors.map((error, index) => ( + + {error} + + ))} + + + + {t('shared:close')} + + + , + getTopPortalEl() + ) + : null} + + ) +} + +const SCROLL_LONG = css` + overflow: auto; + width: inherit; + max-height: 11.75rem; +` diff --git a/app/src/organisms/ProtocolDetails/AnnotatedSteps.tsx b/app/src/organisms/Desktop/ProtocolDetails/AnnotatedSteps.tsx similarity index 88% rename from app/src/organisms/ProtocolDetails/AnnotatedSteps.tsx rename to app/src/organisms/Desktop/ProtocolDetails/AnnotatedSteps.tsx index ef80dc84a55..0bb0ace72d1 100644 --- a/app/src/organisms/ProtocolDetails/AnnotatedSteps.tsx +++ b/app/src/organisms/Desktop/ProtocolDetails/AnnotatedSteps.tsx @@ -1,5 +1,6 @@ -import * as React from 'react' +import { useMemo } from 'react' import { css } from 'styled-components' + import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' import { ALIGN_CENTER, @@ -14,12 +15,15 @@ import { Icon, ALIGN_FLEX_START, } from '@opentrons/components' -import { CommandIcon, CommandText } from '../../molecules/Command' + +import { CommandIcon, CommandText } from '/app/molecules/Command' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' import type { CompletedProtocolAnalysis, ProtocolAnalysisOutput, RunTimeCommand, + LabwareDefinition2, } from '@opentrons/shared-data' interface AnnotatedStepsProps { @@ -88,6 +92,15 @@ export function AnnotatedSteps(props: AnnotatedStepsProps): JSX.Element { } }, []) + const isValidRobotSideAnalysis = analysis != null + const allRunDefs = useMemo( + () => + analysis != null + ? getLabwareDefinitionsFromCommands(analysis.commands) + : [], + [isValidRobotSideAnalysis] + ) + return ( - {groupedCommands.map((c, i) => + +{/* {groupedCommands.map((c, i) => 'annotationIndex' in c ? ( ) - )} + )}*/} + {analysis.commands.map((c, i) => ( + + ))}
    ) @@ -234,9 +258,15 @@ interface IndividualCommandProps { analysis: ProtocolAnalysisOutput | CompletedProtocolAnalysis stepNumber: string isHighlighted: boolean + allRunDefs: LabwareDefinition2[] } -function IndividualCommand(props: IndividualCommandProps): JSX.Element { - const { command, analysis, stepNumber, isHighlighted } = props +function IndividualCommand({ + command, + analysis, + stepNumber, + isHighlighted, + allRunDefs, +}: IndividualCommandProps): JSX.Element { const backgroundColor = isHighlighted ? COLORS.blue30 : COLORS.grey20 const iconColor = isHighlighted ? COLORS.blue60 : COLORS.grey50 return ( @@ -271,6 +301,7 @@ function IndividualCommand(props: IndividualCommandProps): JSX.Element { robotType={analysis?.robotType ?? FLEX_ROBOT_TYPE} color={COLORS.black90} commandTextData={analysis} + allRunDefs={allRunDefs} />
    diff --git a/app/src/organisms/ProtocolDetails/ProtocolLabwareDetails.tsx b/app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx similarity index 91% rename from app/src/organisms/ProtocolDetails/ProtocolLabwareDetails.tsx rename to app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx index a74d33cf76e..cb74884c89e 100644 --- a/app/src/organisms/ProtocolDetails/ProtocolLabwareDetails.tsx +++ b/app/src/organisms/Desktop/ProtocolDetails/ProtocolLabwareDetails.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { @@ -10,6 +10,7 @@ import { Icon, InfoScreen, MenuItem, + NO_WRAP, OverflowBtn, POSITION_ABSOLUTE, POSITION_RELATIVE, @@ -18,12 +19,13 @@ import { useMenuHandleClickOutside, } from '@opentrons/components' import { getLabwareDefURI } from '@opentrons/shared-data' -import { Divider } from '../../atoms/structure' -import { getTopPortalEl } from '../../App/portal' -import { LabwareDetails } from '../LabwareDetails' +import { Divider } from '/app/atoms/structure' +import { getTopPortalEl } from '/app/App/portal' +import { LabwareDetails } from '/app/organisms/Desktop/Labware/LabwareDetails' +import type { MouseEventHandler } from 'react' import type { LoadLabwareRunTimeCommand } from '@opentrons/shared-data' -import type { LabwareDefAndDate } from '../../pages/Labware/hooks' +import type { LabwareDefAndDate } from '/app/local-resources/labware' interface ProtocolLabwareDetailsProps { requiredLabwareDetails: LoadLabwareRunTimeCommand[] | null @@ -88,7 +90,7 @@ export const ProtocolLabwareDetails = ( ))} ) : ( - + )} ) @@ -163,9 +165,9 @@ export const LabwareDetailOverflowMenu = ( const [ showLabwareDetailSlideout, setShowLabwareDetailSlideout, - ] = React.useState(false) + ] = useState(false) - const handleClickMenuItem: React.MouseEventHandler = e => { + const handleClickMenuItem: MouseEventHandler = e => { e.preventDefault() setShowOverflowMenu(false) setShowLabwareDetailSlideout(true) @@ -182,7 +184,7 @@ export const LabwareDetailOverflowMenu = ( {showOverflowMenu ? ( 0 ? ( liquidsInLoadOrder?.map((liquid, index) => { return ( - + {index < liquidsInLoadOrder.length - 1 && } - + ) }) ) : ( diff --git a/app/src/organisms/ProtocolDetails/ProtocolParameters/__tests__/ProtocolParameters.test.tsx b/app/src/organisms/Desktop/ProtocolDetails/ProtocolParameters/__tests__/ProtocolParameters.test.tsx similarity index 88% rename from app/src/organisms/ProtocolDetails/ProtocolParameters/__tests__/ProtocolParameters.test.tsx rename to app/src/organisms/Desktop/ProtocolDetails/ProtocolParameters/__tests__/ProtocolParameters.test.tsx index 191329bbae8..e52803cc054 100644 --- a/app/src/organisms/ProtocolDetails/ProtocolParameters/__tests__/ProtocolParameters.test.tsx +++ b/app/src/organisms/Desktop/ProtocolDetails/ProtocolParameters/__tests__/ProtocolParameters.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, beforeEach, afterEach } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ProtocolParameters } from '..' import type { RunTimeParameter } from '@opentrons/shared-data' @@ -13,9 +13,7 @@ vi.mock('@opentrons/components', async importOriginal => { const actual = await importOriginal() return { ...actual, - NoParameters: vi.fn(() => ( -
    No parameters specified in this protocol
    - )), + InfoScreen: vi.fn(() =>
    mock InfoScreen
    ), } }) @@ -133,11 +131,11 @@ describe('ProtocolParameters', () => { screen.getByText('Left, Right') }) - it('should render empty display when protocol does not have any parameter', () => { + it('should render InfoScreen component when protocol does not have any parameter', () => { props = { runTimeParameters: [], } render(props) - screen.getByText('No parameters specified in this protocol') + screen.getByText('mock InfoScreen') }) }) diff --git a/app/src/organisms/Desktop/ProtocolDetails/ProtocolParameters/index.tsx b/app/src/organisms/Desktop/ProtocolDetails/ProtocolParameters/index.tsx new file mode 100644 index 00000000000..242d5a8cd14 --- /dev/null +++ b/app/src/organisms/Desktop/ProtocolDetails/ProtocolParameters/index.tsx @@ -0,0 +1,55 @@ +import { useTranslation } from 'react-i18next' + +import { + Banner, + DIRECTION_COLUMN, + Flex, + InfoScreen, + ParametersTable, + SPACING, + StyledText, +} from '@opentrons/components' + +import type { RunTimeParameter } from '@opentrons/shared-data' + +interface ProtocolParametersProps { + runTimeParameters: RunTimeParameter[] +} + +export function ProtocolParameters({ + runTimeParameters, +}: ProtocolParametersProps): JSX.Element { + const { t } = useTranslation(['protocol_details', 'protocol_setup']) + + return ( + + {runTimeParameters.length > 0 ? ( + + + + + {t('listed_values_are_view_only')} + + + {t('start_setup_customize_values')} + + + + + + ) : ( + + )} + + ) +} diff --git a/app/src/organisms/ProtocolDetails/ProtocolStats.tsx b/app/src/organisms/Desktop/ProtocolDetails/ProtocolStats.tsx similarity index 99% rename from app/src/organisms/ProtocolDetails/ProtocolStats.tsx rename to app/src/organisms/Desktop/ProtocolDetails/ProtocolStats.tsx index 935636690c7..589eeb2e2ea 100644 --- a/app/src/organisms/ProtocolDetails/ProtocolStats.tsx +++ b/app/src/organisms/Desktop/ProtocolDetails/ProtocolStats.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' import { diff --git a/app/src/organisms/ProtocolDetails/RobotConfigurationDetails.tsx b/app/src/organisms/Desktop/ProtocolDetails/RobotConfigurationDetails.tsx similarity index 95% rename from app/src/organisms/ProtocolDetails/RobotConfigurationDetails.tsx rename to app/src/organisms/Desktop/ProtocolDetails/RobotConfigurationDetails.tsx index 423bb307a03..d53a6eba889 100644 --- a/app/src/organisms/ProtocolDetails/RobotConfigurationDetails.tsx +++ b/app/src/organisms/Desktop/ProtocolDetails/RobotConfigurationDetails.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import { Fragment } from 'react' import { useTranslation } from 'react-i18next' import { @@ -26,11 +26,12 @@ import { FLEX_USB_MODULE_FIXTURES, } from '@opentrons/shared-data' -import { InstrumentContainer } from '../../atoms/InstrumentContainer' -import { Divider } from '../../atoms/structure' +import { InstrumentContainer } from '/app/atoms/InstrumentContainer' +import { Divider } from '/app/atoms/structure' import { getRobotTypeDisplayName } from '../ProtocolsLanding/utils' import { getSlotsForThermocycler } from './utils' +import type { ReactNode } from 'react' import type { CutoutConfigProtocolSpec, LoadModuleRunTimeCommand, @@ -153,7 +154,7 @@ export const RobotConfigurationDetails = ( ) : null} {requiredModuleDetails.map((module, index) => { return ( - + } /> - + ) })} {nonStandardRequiredFixtureDetails.map((fixture, index) => { return ( - + } /> - + ) })}
    @@ -217,7 +218,7 @@ export const RobotConfigurationDetails = ( interface RobotConfigurationDetailsItemProps { label: string - item: React.ReactNode + item: ReactNode } export const RobotConfigurationDetailsItem = ( diff --git a/app/src/organisms/Desktop/ProtocolDetails/__tests__/ProtocolDetails.test.tsx b/app/src/organisms/Desktop/ProtocolDetails/__tests__/ProtocolDetails.test.tsx new file mode 100644 index 00000000000..8ef02d4d67a --- /dev/null +++ b/app/src/organisms/Desktop/ProtocolDetails/__tests__/ProtocolDetails.test.tsx @@ -0,0 +1,224 @@ +import type * as React from 'react' +import { act, screen, waitFor } from '@testing-library/react' +import { MemoryRouter } from 'react-router-dom' +import { describe, it, beforeEach, vi, expect, afterEach } from 'vitest' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { ChooseRobotToRunProtocolSlideout } from '/app/organisms/Desktop/ChooseRobotToRunProtocolSlideout' +import { + useTrackEvent, + ANALYTICS_PROTOCOL_PROCEED_TO_RUN, +} from '/app/redux/analytics' +import { getValidCustomLabwareFiles } from '/app/redux/custom-labware/selectors' +import { + getConnectableRobots, + getReachableRobots, + getScanning, + getUnreachableRobots, +} from '/app/redux/discovery' +import { getIsProtocolAnalysisInProgress } from '/app/redux/protocol-storage/selectors' +import { + mockConnectableRobot, + mockReachableRobot, + mockUnreachableRobot, +} from '/app/redux/discovery/__fixtures__' +import { storedProtocolData } from '/app/redux/protocol-storage/__fixtures__' +import { ProtocolDetails } from '..' + +import type { Mock } from 'vitest' +import type { ProtocolAnalysisOutput } from '@opentrons/shared-data' + +vi.mock('/app/redux/analytics') +vi.mock('/app/redux/custom-labware/selectors') +vi.mock('/app/redux/discovery/selectors') +vi.mock('/app/redux/protocol-storage/selectors') +vi.mock('/app/organisms/Desktop/ChooseRobotToRunProtocolSlideout') +vi.mock('/app/organisms/Desktop/SendProtocolToFlexSlideout') + +const render = ( + props: Partial> = {} +) => { + return renderWithProviders( + + + , + { + i18nInstance: i18n, + } + )[0] +} + +const protocolType = 'json' +const schemaVersion = 6 +const author = 'Otie' +const createdAt = '2022-05-04T18:33:48.916159+00:00' +const description = 'fake protocol description' + +const mockMostRecentAnalysis: ProtocolAnalysisOutput = storedProtocolData.mostRecentAnalysis as ProtocolAnalysisOutput + +let mockTrackEvent: Mock + +describe('ProtocolDetails', () => { + beforeEach(() => { + mockTrackEvent = vi.fn() + vi.mocked(getValidCustomLabwareFiles).mockReturnValue([]) + vi.mocked(getConnectableRobots).mockReturnValue([mockConnectableRobot]) + vi.mocked(getUnreachableRobots).mockReturnValue([mockUnreachableRobot]) + vi.mocked(getReachableRobots).mockReturnValue([mockReachableRobot]) + vi.mocked(getScanning).mockReturnValue(false) + + vi.mocked(ChooseRobotToRunProtocolSlideout).mockReturnValue( +
    close ChooseRobotToRunProtocolSlideout
    + ) + vi.mocked(getIsProtocolAnalysisInProgress).mockReturnValue(false) + vi.mocked(useTrackEvent).mockReturnValue(mockTrackEvent) + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders protocol title as display name if present in metadata', () => { + const protocolName = 'fakeProtocolDisplayName' + render({ + mostRecentAnalysis: { + ...mockMostRecentAnalysis, + createdAt, + metadata: { + ...mockMostRecentAnalysis.metadata, + protocolName, + }, + config: { + ...mockMostRecentAnalysis.config, + protocolType, + schemaVersion, + }, + }, + }) + screen.getByText('fakeProtocolDisplayName') + }) + it('renders protocol title as file name if not in metadata', () => { + render({ + mostRecentAnalysis: { + ...mockMostRecentAnalysis, + createdAt, + metadata: { + ...mockMostRecentAnalysis.metadata, + author, + }, + config: { + ...mockMostRecentAnalysis.config, + protocolType, + schemaVersion, + }, + }, + }) + expect(screen.getByText('fakeSrcFileName')).toBeInTheDocument() + }) + it('renders deck view section', () => { + render({ + mostRecentAnalysis: { + ...mockMostRecentAnalysis, + createdAt, + metadata: { + ...mockMostRecentAnalysis.metadata, + }, + config: { + ...mockMostRecentAnalysis.config, + protocolType, + schemaVersion, + }, + }, + }) + expect( + screen.getByRole('heading', { name: 'Deck View' }) + ).toBeInTheDocument() + screen.getByText('close ChooseRobotToRunProtocolSlideout') + }) + it('opens choose robot to run protocol slideout when Start setup button is clicked', async () => { + vi.mocked(ChooseRobotToRunProtocolSlideout).mockReturnValue( +
    open ChooseRobotToRunProtocolSlideout
    + ) + render({ + mostRecentAnalysis: { + ...mockMostRecentAnalysis, + createdAt, + metadata: { + ...mockMostRecentAnalysis.metadata, + }, + config: { + ...mockMostRecentAnalysis.config, + protocolType, + schemaVersion, + }, + }, + }) + const runProtocolButton = screen.getByRole('button', { + name: 'Start setup', + }) + act(() => { + runProtocolButton.click() + }) + await waitFor(() => { + expect(mockTrackEvent).toHaveBeenCalledWith({ + name: ANALYTICS_PROTOCOL_PROCEED_TO_RUN, + properties: { sourceLocation: 'ProtocolsDetail' }, + }) + }) + screen.getByText('open ChooseRobotToRunProtocolSlideout') + }) + it('renders the protocol creation method', () => { + render({ + mostRecentAnalysis: { + ...mockMostRecentAnalysis, + createdAt, + metadata: { + ...mockMostRecentAnalysis.metadata, + }, + config: { + ...mockMostRecentAnalysis.config, + protocolType, + schemaVersion, + }, + }, + }) + screen.getByRole('heading', { name: 'creation method' }) + screen.getByText('Protocol Designer 6.0') + }) + it('renders the last analyzed date', () => { + render({ + mostRecentAnalysis: { + ...mockMostRecentAnalysis, + createdAt, + metadata: { + ...mockMostRecentAnalysis.metadata, + }, + config: { + ...mockMostRecentAnalysis.config, + protocolType, + schemaVersion, + }, + }, + }) + screen.getByRole('heading', { name: 'last analyzed' }) + }) + it('renders the protocol description', () => { + render({ + mostRecentAnalysis: { + ...mockMostRecentAnalysis, + createdAt, + metadata: { + ...mockMostRecentAnalysis.metadata, + description, + }, + config: { + ...mockMostRecentAnalysis.config, + protocolType, + schemaVersion, + }, + }, + }) + screen.getByRole('heading', { name: 'description' }) + screen.getByText('fake protocol description') + }) +}) diff --git a/app/src/organisms/ProtocolDetails/__tests__/ProtocolLabwareDetails.test.tsx b/app/src/organisms/Desktop/ProtocolDetails/__tests__/ProtocolLabwareDetails.test.tsx similarity index 94% rename from app/src/organisms/ProtocolDetails/__tests__/ProtocolLabwareDetails.test.tsx rename to app/src/organisms/Desktop/ProtocolDetails/__tests__/ProtocolLabwareDetails.test.tsx index 17498a47ec0..93c215643cf 100644 --- a/app/src/organisms/ProtocolDetails/__tests__/ProtocolLabwareDetails.test.tsx +++ b/app/src/organisms/Desktop/ProtocolDetails/__tests__/ProtocolLabwareDetails.test.tsx @@ -1,18 +1,18 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, beforeEach, vi } from 'vitest' -import { InfoScreen } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ProtocolLabwareDetails } from '../ProtocolLabwareDetails' import type { LoadLabwareRunTimeCommand } from '@opentrons/shared-data' +import type { InfoScreen } from '@opentrons/components' vi.mock('@opentrons/components', async importOriginal => { const actual = await importOriginal() return { ...actual, - InfoScreen: vi.fn(), + InfoScreen: () =>
    mock InfoScreen
    , } }) @@ -79,7 +79,6 @@ describe('ProtocolLabwareDetails', () => { props = { requiredLabwareDetails: mockRequiredLabwareDetails, } - vi.mocked(InfoScreen).mockReturnValue(
    mock InfoScreen
    ) }) it('should render an opentrons labware', () => { diff --git a/app/src/organisms/ProtocolDetails/__tests__/ProtocolLiquidsDetails.test.tsx b/app/src/organisms/Desktop/ProtocolDetails/__tests__/ProtocolLiquidsDetails.test.tsx similarity index 89% rename from app/src/organisms/ProtocolDetails/__tests__/ProtocolLiquidsDetails.test.tsx rename to app/src/organisms/Desktop/ProtocolDetails/__tests__/ProtocolLiquidsDetails.test.tsx index 74aa1a031a9..69e55cc1097 100644 --- a/app/src/organisms/ProtocolDetails/__tests__/ProtocolLiquidsDetails.test.tsx +++ b/app/src/organisms/Desktop/ProtocolDetails/__tests__/ProtocolLiquidsDetails.test.tsx @@ -1,15 +1,15 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, beforeEach, vi } from 'vitest' import { parseLiquidsInLoadOrder } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ProtocolLiquidsDetails } from '../ProtocolLiquidsDetails' import type * as SharedData from '@opentrons/shared-data' -vi.mock('../../Devices/ProtocolRun/SetupLiquids/SetupLiquidsList') +vi.mock('../../Desktop/Devices/ProtocolRun/SetupLiquids/SetupLiquidsList') vi.mock('@opentrons/shared-data', async importOriginal => { const actualSharedData = await importOriginal() return { diff --git a/app/src/organisms/ProtocolDetails/__tests__/RobotConfigurationDetails.test.tsx b/app/src/organisms/Desktop/ProtocolDetails/__tests__/RobotConfigurationDetails.test.tsx similarity index 97% rename from app/src/organisms/ProtocolDetails/__tests__/RobotConfigurationDetails.test.tsx rename to app/src/organisms/Desktop/ProtocolDetails/__tests__/RobotConfigurationDetails.test.tsx index ad491c0b5f5..b823732ce95 100644 --- a/app/src/organisms/ProtocolDetails/__tests__/RobotConfigurationDetails.test.tsx +++ b/app/src/organisms/Desktop/ProtocolDetails/__tests__/RobotConfigurationDetails.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, afterEach, vi } from 'vitest' import { screen } from '@testing-library/react' import { OT2_STANDARD_MODEL, FLEX_STANDARD_MODEL } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { RobotConfigurationDetails } from '../RobotConfigurationDetails' import type { LoadModuleRunTimeCommand } from '@opentrons/shared-data' diff --git a/app/src/organisms/ProtocolDetails/__tests__/utils.test.ts b/app/src/organisms/Desktop/ProtocolDetails/__tests__/utils.test.ts similarity index 100% rename from app/src/organisms/ProtocolDetails/__tests__/utils.test.ts rename to app/src/organisms/Desktop/ProtocolDetails/__tests__/utils.test.ts diff --git a/app/src/organisms/Desktop/ProtocolDetails/index.tsx b/app/src/organisms/Desktop/ProtocolDetails/index.tsx new file mode 100644 index 00000000000..607321f76db --- /dev/null +++ b/app/src/organisms/Desktop/ProtocolDetails/index.tsx @@ -0,0 +1,711 @@ +import { useState, Fragment } from 'react' +import { createPortal } from 'react-dom' +import map from 'lodash/map' +import omit from 'lodash/omit' +import isEmpty from 'lodash/isEmpty' +import startCase from 'lodash/startCase' +import { format } from 'date-fns' +import { css } from 'styled-components' +import { useTranslation } from 'react-i18next' +import { useDispatch, useSelector } from 'react-redux' +import { ErrorBoundary } from 'react-error-boundary' + +import { + ALIGN_CENTER, + BORDERS, + Box, + Btn, + COLORS, + DIRECTION_COLUMN, + DIRECTION_ROW, + DISPLAY_FLEX, + DISPLAY_GRID, + Flex, + Icon, + JUSTIFY_CENTER, + JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, + Link, + Modal, + OVERFLOW_WRAP_ANYWHERE, + POSITION_RELATIVE, + PrimaryButton, + ProtocolDeck, + SIZE_1, + SIZE_5, + SPACING, + Tabs, + TYPOGRAPHY, +} from '@opentrons/components' +import { + MAGNETIC_BLOCK_TYPE, + getGripperDisplayName, + getModuleType, + getSimplestDeckConfigForProtocol, + parseInitialLoadedModulesBySlot, + parseInitialPipetteNamesByMount, +} from '@opentrons/shared-data' + +import { getTopPortalEl } from '/app/App/portal' +import { Divider } from '/app/atoms/structure' +import { + useTrackEvent, + ANALYTICS_PROTOCOL_PROCEED_TO_RUN, +} from '/app/redux/analytics' +import { + getIsProtocolAnalysisInProgress, + analyzeProtocol, +} from '/app/redux/protocol-storage' +import { useFeatureFlag } from '/app/redux/config' +import { ChooseRobotToRunProtocolSlideout } from '/app/organisms/Desktop/ChooseRobotToRunProtocolSlideout' +import { SendProtocolToFlexSlideout } from '../SendProtocolToFlexSlideout' +import { ProtocolAnalysisFailure } from '../ProtocolAnalysisFailure' +import { ProtocolStatusBanner } from '../ProtocolStatusBanner' +import { getAnalysisStatus } from '/app/transformations/analysis' +import { getProtocolDisplayName } from '/app/transformations/protocols' +import { getProtocolUsesGripper } from '/app/transformations/commands' +import { ProtocolOverflowMenu } from '../ProtocolsLanding/ProtocolOverflowMenu' +import { ProtocolStats } from './ProtocolStats' +import { ProtocolLabwareDetails } from './ProtocolLabwareDetails' +import { ProtocolLiquidsDetails } from './ProtocolLiquidsDetails' +import { RobotConfigurationDetails } from './RobotConfigurationDetails' +import { ProtocolParameters } from './ProtocolParameters' +import { AnnotatedSteps } from './AnnotatedSteps' + +import type { + JsonConfig, + PythonConfig, + LoadLabwareRunTimeCommand, +} from '@opentrons/shared-data' +import type { StoredProtocolData } from '/app/redux/protocol-storage' +import type { State, Dispatch } from '/app/redux/types' + +const GRID_STYLE = css` + display: ${DISPLAY_GRID}; + width: 100%; + grid-template-columns: 26.6% 26.6% 26.6% 20.2%; +` + +const TWO_COL_GRID_STYLE = css` + display: ${DISPLAY_GRID}; + grid-gap: ${SPACING.spacing24}; + grid-template-columns: 22.5% 77.5%; +` + +const ZOOM_ICON_STYLE = css` + border-radius: ${BORDERS.borderRadius4}; + &:hover { + background: ${COLORS.grey30}; + } + &:active { + background: ${COLORS.grey35}; + } + &:disabled { + background: ${COLORS.white}; + } + &:focus-visible { + background: ${COLORS.grey35}; + } +` + +interface Metadata { + [key: string]: any +} + +interface MetadataDetailsProps { + description: string + metadata: Metadata + protocolType: string +} + +function MetadataDetails({ + description, + metadata, + protocolType, +}: MetadataDetailsProps): JSX.Element { + if (protocolType === 'json') { + return {description} + } else { + const filteredMetaData = Object.entries( + omit(metadata, ['description', 'protocolName', 'author', 'apiLevel']) + ).map(item => ({ label: item[0], value: item[1] })) + + return ( + + + {description} + + {filteredMetaData.map((item, index) => { + return ( + + + {startCase(item.label)} + + {item.value} + + ) + })} + + ) + } +} + +interface ReadMoreContentProps { + metadata: Metadata + protocolType: 'json' | 'python' +} + +const ReadMoreContent = (props: ReadMoreContentProps): JSX.Element => { + const { metadata, protocolType } = props + const { t, i18n } = useTranslation('protocol_details') + const [isReadMore, setIsReadMore] = useState(true) + + const description = isEmpty(metadata.description) + ? t('shared:no_data') + : metadata.description + + return ( + + {isReadMore ? ( + + {description.slice(0, 160)} + + ) : ( + + )} + {(description.length > 160 || protocolType === 'python') && ( + { + setIsReadMore(!isReadMore) + }} + > + {isReadMore + ? i18n.format(t('read_more'), 'capitalize') + : i18n.format(t('read_less'), 'capitalize')} + + )} + + ) +} + +interface ProtocolDetailsProps extends StoredProtocolData {} + +export function ProtocolDetails( + props: ProtocolDetailsProps +): JSX.Element | null { + const trackEvent = useTrackEvent() + const dispatch = useDispatch() + const { protocolKey, srcFileNames, mostRecentAnalysis, modified } = props + const { t, i18n } = useTranslation(['protocol_details', 'shared']) + const enableProtocolStats = useFeatureFlag('protocolStats') + const enableProtocolTimeline = useFeatureFlag('protocolTimeline') + const runTimeParameters = mostRecentAnalysis?.runTimeParameters ?? [] + const hasRunTimeParameters = runTimeParameters.length > 0 + const [currentTab, setCurrentTab] = useState< + 'robot_config' | 'labware' | 'liquids' | 'stats' | 'parameters' | 'timeline' + >(hasRunTimeParameters ? 'parameters' : 'robot_config') + const [ + showChooseRobotToRunProtocolSlideout, + setShowChooseRobotToRunProtocolSlideout, + ] = useState(false) + const [ + showSendProtocolToFlexSlideout, + setShowSendProtocolToFlexSlideout, + ] = useState(false) + const [showDeckViewModal, setShowDeckViewModal] = useState(false) + + const isAnalyzing = useSelector((state: State) => + getIsProtocolAnalysisInProgress(state, protocolKey) + ) + + const analysisStatus = getAnalysisStatus(isAnalyzing, mostRecentAnalysis) + + if (analysisStatus === 'stale') { + dispatch(analyzeProtocol(protocolKey)) + } else if (analysisStatus === 'missing') return null + + const { left: leftMountPipetteName, right: rightMountPipetteName } = + mostRecentAnalysis != null + ? parseInitialPipetteNamesByMount(mostRecentAnalysis.commands) + : { left: null, right: null } + + const requiredExtensionInstrumentName = + mostRecentAnalysis != null && getProtocolUsesGripper(mostRecentAnalysis) + ? getGripperDisplayName('gripperV1') + : null + + const requiredModuleDetails = + mostRecentAnalysis?.commands != null + ? map( + parseInitialLoadedModulesBySlot(mostRecentAnalysis.commands) + ).filter( + loadedModule => + // filter out magnetic block which is already handled by the required fixture details + getModuleType(loadedModule.params.model) !== MAGNETIC_BLOCK_TYPE + ) + : [] + + const requiredFixtureDetails = getSimplestDeckConfigForProtocol( + analysisStatus !== 'stale' && analysisStatus !== 'loading' + ? mostRecentAnalysis + : null + ) + + const loadLabwareCommands = + mostRecentAnalysis?.commands.filter( + (command): command is LoadLabwareRunTimeCommand => + command.commandType === 'loadLabware' && + command.result?.definition.parameters.format !== 'trash' + ) ?? [] + + const protocolDisplayName = getProtocolDisplayName( + protocolKey, + srcFileNames, + mostRecentAnalysis + ) + + const getCreationMethod = (config: JsonConfig | PythonConfig): string => { + if (config.protocolType === 'json') { + return t('protocol_designer_version', { + version: config.schemaVersion.toFixed(1), + }) + } else { + return t('python_api_version', { + version: + config.apiVersion != null ? config.apiVersion?.join('.') : null, + }) + } + } + + const creationMethod = + mostRecentAnalysis != null + ? getCreationMethod(mostRecentAnalysis.config) ?? t('shared:no_data') + : t('shared:no_data') + const author = + mostRecentAnalysis != null + ? mostRecentAnalysis?.metadata?.author ?? t('shared:no_data') + : t('shared:no_data') + const lastAnalyzed = + mostRecentAnalysis?.createdAt != null + ? format(new Date(mostRecentAnalysis.createdAt), 'M/d/yy HH:mm') + : t('shared:no_data') + const robotType = mostRecentAnalysis?.robotType ?? null + + const contentsByTabName = { + labware: ( + + ), + robot_config: ( + + ), + liquids: ( + + ), + stats: enableProtocolStats ? ( + + ) : null, + timeline: + enableProtocolTimeline && mostRecentAnalysis != null ? ( + + ) : null, + parameters: , + } + + const deckMap = + + const deckViewByAnalysisStatus = { + stale: , + missing: , + loading: , + error: , + parameterRequired: , + complete: ( + + {deckMap} + + ), + } + + const handleRunProtocolButtonClick = (): void => { + trackEvent({ + name: ANALYTICS_PROTOCOL_PROCEED_TO_RUN, + properties: { sourceLocation: 'ProtocolsDetail' }, + }) + setShowChooseRobotToRunProtocolSlideout(true) + } + + const UNKNOWN_ATTACHMENT_ERROR = `${protocolDisplayName} protocol uses + instruments or modules from a future version of Opentrons software. Please update + the app to the most recent version to run this protocol.` + + const UnknownAttachmentError = ( + + ) + + return ( + <> + {showDeckViewModal + ? createPortal( + { + setShowDeckViewModal(false) + }} + > + {deckMap} + , + getTopPortalEl() + ) + : null} + + + { + setShowChooseRobotToRunProtocolSlideout(false) + }} + showSlideout={showChooseRobotToRunProtocolSlideout} + storedProtocolData={props} + /> + { + setShowSendProtocolToFlexSlideout(false) + }} + storedProtocolData={props} + /> + + + + {analysisStatus !== 'loading' && + mostRecentAnalysis?.result === 'parameter-value-required' ? ( + + ) : null} + {mostRecentAnalysis != null && analysisStatus === 'error' ? ( + e.detail)} + /> + ) : null} + + {protocolDisplayName} + + + + + {t('creation_method')} + + + {analysisStatus === 'loading' + ? t('shared:loading') + : creationMethod} + + + + + {t('last_updated')} + + + {analysisStatus === 'loading' + ? t('shared:loading') + : format(new Date(modified), 'M/d/yy HH:mm')} + + + + + {t('last_analyzed')} + + + {analysisStatus === 'loading' + ? t('shared:loading') + : lastAnalyzed} + + + + { + handleRunProtocolButtonClick() + }} + data-testid="ProtocolDetails_runProtocol" + disabled={analysisStatus === 'loading'} + > + {t('start_setup')} + + + + + + + + {t('org_or_author')} + + + {analysisStatus === 'loading' + ? t('shared:loading') + : author} + + + + + {t('description')} + + {analysisStatus === 'loading' ? ( + + {t('shared:loading')} + + ) : null} + {mostRecentAnalysis != null ? ( + + ) : null} + + + + + { + setShowChooseRobotToRunProtocolSlideout(true) + }} + handleSendProtocolToFlex={() => { + setShowSendProtocolToFlexSlideout(true) + }} + storedProtocolData={props} + data-testid="ProtocolDetails_overFlowMenu" + /> + + + + + + + {t('deck_view')} + + { + setShowDeckViewModal(true) + }} + > + + + + + {deckViewByAnalysisStatus[analysisStatus]} + + + + + + {mostRecentAnalysis != null && ( + { + setCurrentTab('parameters') + }, + }, + ]} + /> + )} + { + setCurrentTab('robot_config') + }, + }, + { + text: i18n.format(t('labware'), 'capitalize'), + isActive: currentTab === 'labware', + disabled: false, + onClick: () => { + setCurrentTab('labware') + }, + }, + ]} + /> + {mostRecentAnalysis != null && ( + { + setCurrentTab('liquids') + }, + }, + ]} + /> + )} + {enableProtocolStats && mostRecentAnalysis != null && ( + { + setCurrentTab('stats') + }, + }, + ]} + /> + )} + {enableProtocolTimeline && mostRecentAnalysis != null && ( + { + setCurrentTab('timeline') + }, + }, + ]} + /> + )} + + + {contentsByTabName[currentTab]} + + + + + + + ) +} diff --git a/app/src/organisms/ProtocolDetails/utils.ts b/app/src/organisms/Desktop/ProtocolDetails/utils.ts similarity index 100% rename from app/src/organisms/ProtocolDetails/utils.ts rename to app/src/organisms/Desktop/ProtocolDetails/utils.ts diff --git a/app/src/organisms/ProtocolStatusBanner/ProtocolStatusBanner.stories.tsx b/app/src/organisms/Desktop/ProtocolStatusBanner/ProtocolStatusBanner.stories.tsx similarity index 100% rename from app/src/organisms/ProtocolStatusBanner/ProtocolStatusBanner.stories.tsx rename to app/src/organisms/Desktop/ProtocolStatusBanner/ProtocolStatusBanner.stories.tsx diff --git a/app/src/organisms/ProtocolStatusBanner/__tests__/ProtocolStatusBanner.test.tsx b/app/src/organisms/Desktop/ProtocolStatusBanner/__tests__/ProtocolStatusBanner.test.tsx similarity index 87% rename from app/src/organisms/ProtocolStatusBanner/__tests__/ProtocolStatusBanner.test.tsx rename to app/src/organisms/Desktop/ProtocolStatusBanner/__tests__/ProtocolStatusBanner.test.tsx index 55fd1ba7249..3a0326c5273 100644 --- a/app/src/organisms/ProtocolStatusBanner/__tests__/ProtocolStatusBanner.test.tsx +++ b/app/src/organisms/Desktop/ProtocolStatusBanner/__tests__/ProtocolStatusBanner.test.tsx @@ -1,11 +1,10 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, expect } from 'vitest' import { COLORS } from '@opentrons/components' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { ProtocolStatusBanner } from '../index' const render = () => { diff --git a/app/src/organisms/Desktop/ProtocolStatusBanner/index.tsx b/app/src/organisms/Desktop/ProtocolStatusBanner/index.tsx new file mode 100644 index 00000000000..133cbf35896 --- /dev/null +++ b/app/src/organisms/Desktop/ProtocolStatusBanner/index.tsx @@ -0,0 +1,20 @@ +import { useTranslation } from 'react-i18next' +import { SPACING, Banner, LegacyStyledText } from '@opentrons/components' + +import type { IconProps } from '@opentrons/components' + +export function ProtocolStatusBanner(): JSX.Element { + const { t } = useTranslation('protocol_list') + + const alertIcon: IconProps = { name: 'alert-circle' } + return ( + + {t('csv_file_required')} + + ) +} diff --git a/app/src/organisms/ProtocolTimelineScrubber/CommandItem.tsx b/app/src/organisms/Desktop/ProtocolTimelineScrubber/CommandItem.tsx similarity index 85% rename from app/src/organisms/ProtocolTimelineScrubber/CommandItem.tsx rename to app/src/organisms/Desktop/ProtocolTimelineScrubber/CommandItem.tsx index 0a3fb16f660..e97d30df767 100644 --- a/app/src/organisms/ProtocolTimelineScrubber/CommandItem.tsx +++ b/app/src/organisms/Desktop/ProtocolTimelineScrubber/CommandItem.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { Flex, DIRECTION_COLUMN, @@ -8,12 +8,13 @@ import { LegacyStyledText, OVERFLOW_SCROLL, } from '@opentrons/components' -import { getCommandTextData } from '../../molecules/Command/utils/getCommandTextData' -import { CommandText } from '../../molecules/Command' +import { getCommandTextData } from '/app/local-resources/commands' +import { CommandText } from '/app/molecules/Command' import { COMMAND_WIDTH_PX } from './index' import type { CompletedProtocolAnalysis, + LabwareDefinition2, ProtocolAnalysisOutput, RobotType, RunTimeCommand, @@ -26,17 +27,18 @@ interface CommandItemProps { setCurrentCommandIndex: (index: number) => void analysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput robotType: RobotType + allRunDefs: LabwareDefinition2[] } -export function CommandItem(props: CommandItemProps): JSX.Element { - const [showDetails, setShowDetails] = React.useState(false) - const { - index, - command, - currentCommandIndex, - setCurrentCommandIndex, - analysis, - robotType, - } = props +export function CommandItem({ + index, + command, + currentCommandIndex, + setCurrentCommandIndex, + analysis, + robotType, + allRunDefs, +}: CommandItemProps): JSX.Element { + const [showDetails, setShowDetails] = useState(false) const params: RunTimeCommand['params'] = command.params ?? {} return ( {showDetails ? Object.entries(params).map(([key, value]) => ( diff --git a/app/src/organisms/ProtocolTimelineScrubber/PipetteVisuals.tsx b/app/src/organisms/Desktop/ProtocolTimelineScrubber/PipetteVisuals.tsx similarity index 98% rename from app/src/organisms/ProtocolTimelineScrubber/PipetteVisuals.tsx rename to app/src/organisms/Desktop/ProtocolTimelineScrubber/PipetteVisuals.tsx index 6a9ee8dc566..10224a08e49 100644 --- a/app/src/organisms/ProtocolTimelineScrubber/PipetteVisuals.tsx +++ b/app/src/organisms/Desktop/ProtocolTimelineScrubber/PipetteVisuals.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { Flex, Box, @@ -32,7 +32,7 @@ export function PipetteMountViz( props: PipetteMountVizProps ): JSX.Element | null { const { mount, pipetteEntity, pipetteId, timelineFrame, analysis } = props - const [showPipetteDetails, setShowPipetteDetails] = React.useState(false) + const [showPipetteDetails, setShowPipetteDetails] = useState(false) return pipetteEntity == null ? null : ( (null) + const commandListRef = useRef(null) + const [currentCommandIndex, setCurrentCommandIndex] = useState(0) + const [isPlaying, setIsPlaying] = useState(true) + + const currentCommandsSlice = commands.slice(0, currentCommandIndex + 1) + const { frame, invariantContext } = getResultingTimelineFrameFromRunCommands( + currentCommandsSlice + ) + const handlePlayPause = (): void => { + setIsPlaying(!isPlaying) + } + + useEffect(() => { + if (isPlaying) { + const intervalId = setInterval(() => { + setCurrentCommandIndex(prev => { + const nextIndex = prev < commands.length - 1 ? prev + 1 : 0 + commandListRef.current?.scrollToIndex(nextIndex) + return nextIndex + }) + }, SEC_PER_FRAME) + + return () => { + clearInterval(intervalId) + } + } + }, [isPlaying, commands]) + + const { robotState } = frame + + const [leftPipetteId] = Object.entries(robotState.pipettes).find( + ([_pipetteId, pipette]) => pipette?.mount === 'left' + ) ?? [null] + const leftPipetteEntity = + leftPipetteId != null + ? invariantContext.pipetteEntities[leftPipetteId] + : null + + const [rightPipetteId] = Object.entries(robotState.pipettes).find( + ([_pipetteId, pipette]) => pipette?.mount === 'right' + ) ?? [null] + const rightPipetteEntity = + rightPipetteId != null + ? invariantContext.pipetteEntities[rightPipetteId] + : null + + const allWellContentsForActiveItem = getAllWellContentsForActiveItem( + invariantContext.labwareEntities, + frame + ) + const liquidDisplayColors = analysis.liquids.map( + liquid => liquid.displayColor ?? COLORS.blue50 + ) + + const isValidRobotSideAnalysis = analysis != null + const allRunDefs = useMemo( + () => + analysis != null + ? getLabwareDefinitionsFromCommands(analysis.commands) + : [], + [isValidRobotSideAnalysis] + ) + + return ( + + + + { + const labwareInModuleId = + Object.entries(robotState.labware).find( + ([labwareId, labware]) => labware.slot === moduleId + )?.[0] ?? null + + const getModuleInnerProps = ( + moduleState: ModuleTemporalProperties['moduleState'] + ): ComponentProps['innerProps'] => { + if (moduleState.type === THERMOCYCLER_MODULE_TYPE) { + let lidMotorState = 'unknown' + if (moduleState.lidOpen === true) { + lidMotorState = 'open' + } else if (moduleState.lidOpen === false) { + lidMotorState = 'closed' + } + return { + lidMotorState, + blockTargetTemp: moduleState.blockTargetTemp, + } + } else if ( + 'targetTemperature' in moduleState && + moduleState.type === 'temperatureModuleType' + ) { + return { + targetTemperature: moduleState.targetTemperature, + } + } else if ('targetTemp' in moduleState) { + return { + targetTemp: moduleState.targetTemp, + } + } + } + + const adapterId = + labwareInModuleId != null + ? invariantContext.labwareEntities[labwareInModuleId].id + : null + const labwareTempProperties = + adapterId != null + ? Object.entries(robotState.labware).find( + ([labwareId, labware]) => labware.slot === adapterId + ) + : null + + const labwareDef = + labwareTempProperties != null + ? invariantContext.labwareEntities[labwareTempProperties[0]] + .def + : null + let nestedDef + let labwareId = null + if (labwareDef != null && labwareTempProperties != null) { + labwareId = labwareTempProperties[0] + nestedDef = labwareDef + } else if (labwareInModuleId != null) { + labwareId = labwareInModuleId + nestedDef = + invariantContext.labwareEntities[labwareInModuleId]?.def + } + + const wellContents = + allWellContentsForActiveItem && labwareId != null + ? allWellContentsForActiveItem[labwareId] + : null + const nestedFill = wellFillFromWellContents( + wellContents, + liquidDisplayColors + ) + + return { + moduleModel: invariantContext.moduleEntities[moduleId].model, + moduleLocation: { slotName: module.slot }, + nestedLabwareDef: nestedDef, + nestedLabwareWellFill: nestedFill, + innerProps: getModuleInnerProps(module.moduleState), + } + })} + labwareOnDeck={map(robotState.labware, (labware, labwareId) => { + const definition = invariantContext.labwareEntities[labwareId].def + + const missingTips = definition.parameters.isTiprack + ? reduce( + robotState.tipState.tipracks[labwareId], + (acc, hasTip, wellName) => { + if (!hasTip) return { ...acc, [wellName]: null } + return acc + }, + {} + ) + : {} + const labwareLocation: LabwareLocation = { + slotName: labware.slot, + } + const wellContents = + allWellContentsForActiveItem && labwareId != null + ? allWellContentsForActiveItem[labwareId] + : null + const wellFill = wellFillFromWellContents( + wellContents, + liquidDisplayColors + ) + const labwareOnDeck: LabwareOnDeck = { + labwareLocation, + definition, + wellFill, + missingTips, + } + + return labwareOnDeck + }).filter((i): i is LabwareOnDeck => i != null)} + /> + + + + + + + {(command, index) => ( + + )} + + + + + Jump to command + + + {isPlaying ? 'Pause' : 'Play'} + + + { + const nextIndex = Number(e.target.value) - 1 + setCurrentCommandIndex(nextIndex) + commandListRef.current?.scrollToIndex(nextIndex) + }} + /> + + 1 + {commands.length} + + {currentCommandIndex !== 0 && + currentCommandIndex !== commands.length - 1 ? ( + + {currentCommandIndex + 1} + + ) : null} + + ) +} diff --git a/app/src/organisms/ProtocolTimelineScrubber/utils.ts b/app/src/organisms/Desktop/ProtocolTimelineScrubber/utils.ts similarity index 100% rename from app/src/organisms/ProtocolTimelineScrubber/utils.ts rename to app/src/organisms/Desktop/ProtocolTimelineScrubber/utils.ts diff --git a/app/src/organisms/ProtocolsLanding/ConfirmDeleteProtocolModal.tsx b/app/src/organisms/Desktop/ProtocolsLanding/ConfirmDeleteProtocolModal.tsx similarity index 97% rename from app/src/organisms/ProtocolsLanding/ConfirmDeleteProtocolModal.tsx rename to app/src/organisms/Desktop/ProtocolsLanding/ConfirmDeleteProtocolModal.tsx index 81e3dd97b4d..14d75238bcc 100644 --- a/app/src/organisms/ProtocolsLanding/ConfirmDeleteProtocolModal.tsx +++ b/app/src/organisms/Desktop/ProtocolsLanding/ConfirmDeleteProtocolModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { AlertPrimaryButton, diff --git a/app/src/organisms/ProtocolsLanding/EmptyStateLinks.tsx b/app/src/organisms/Desktop/ProtocolsLanding/EmptyStateLinks.tsx similarity index 98% rename from app/src/organisms/ProtocolsLanding/EmptyStateLinks.tsx rename to app/src/organisms/Desktop/ProtocolsLanding/EmptyStateLinks.tsx index 9d3c6227a45..8b697d0c903 100644 --- a/app/src/organisms/ProtocolsLanding/EmptyStateLinks.tsx +++ b/app/src/organisms/Desktop/ProtocolsLanding/EmptyStateLinks.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, diff --git a/app/src/organisms/Desktop/ProtocolsLanding/ProtocolCard.tsx b/app/src/organisms/Desktop/ProtocolsLanding/ProtocolCard.tsx new file mode 100644 index 00000000000..5785f68044a --- /dev/null +++ b/app/src/organisms/Desktop/ProtocolsLanding/ProtocolCard.tsx @@ -0,0 +1,362 @@ +import { format } from 'date-fns' +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' +import { useNavigate } from 'react-router-dom' +import { ErrorBoundary } from 'react-error-boundary' + +import { + getModuleType, + getPipetteNameSpecs, + FLEX_STANDARD_MODEL, + getGripperDisplayName, + parseAllRequiredModuleModels, + parseInitialPipetteNamesByMount, +} from '@opentrons/shared-data' +import { + ALIGN_FLEX_START, + BORDERS, + Box, + COLORS, + DIRECTION_COLUMN, + Flex, + Icon, + JUSTIFY_FLEX_END, + ModuleIcon, + OVERFLOW_WRAP_ANYWHERE, + POSITION_ABSOLUTE, + ProtocolDeck, + SIZE_2, + SIZE_3, + SPACING, + LegacyStyledText, + TYPOGRAPHY, + WRAP, +} from '@opentrons/components' + +import { getIsProtocolAnalysisInProgress } from '/app/redux/protocol-storage' +import { InstrumentContainer } from '/app/atoms/InstrumentContainer' +import { ProtocolOverflowMenu } from './ProtocolOverflowMenu' +import { ProtocolAnalysisFailure } from '../ProtocolAnalysisFailure' +import { ProtocolStatusBanner } from '../ProtocolStatusBanner' +import { getProtocolUsesGripper } from '/app/transformations/commands' +import { ProtocolAnalysisStale } from '../ProtocolAnalysisFailure/ProtocolAnalysisStale' +import { getRobotTypeDisplayName } from './utils' +import { getAnalysisStatus } from '/app/transformations/analysis' +import { getProtocolDisplayName } from '/app/transformations/protocols' + +import type { ProtocolAnalysisOutput } from '@opentrons/shared-data' +import type { StoredProtocolData } from '/app/redux/protocol-storage' +import type { State } from '/app/redux/types' + +interface ProtocolCardProps { + handleRunProtocol: (storedProtocolData: StoredProtocolData) => void + handleSendProtocolToFlex: (storedProtocolData: StoredProtocolData) => void + storedProtocolData: StoredProtocolData +} +export function ProtocolCard(props: ProtocolCardProps): JSX.Element | null { + const navigate = useNavigate() + const { + handleRunProtocol, + handleSendProtocolToFlex, + storedProtocolData, + } = props + const { + protocolKey, + srcFileNames, + mostRecentAnalysis, + modified, + } = storedProtocolData + const isAnalyzing = useSelector((state: State) => + getIsProtocolAnalysisInProgress(state, protocolKey) + ) + const protocolDisplayName = getProtocolDisplayName( + protocolKey, + srcFileNames, + mostRecentAnalysis + ) + + const UNKNOWN_ATTACHMENT_ERROR = `${protocolDisplayName} protocol uses + instruments or modules from a future version of Opentrons software. Please update + the app to the most recent version to run this protocol.` + + const UnknownAttachmentError = ( + + ) + + return ( + { + navigate(`/protocols/${protocolKey}`) + }} + > + + + + + + + + ) +} + +interface AnalysisInfoProps { + protocolKey: string + protocolDisplayName: string + modified: number + isAnalyzing: boolean + mostRecentAnalysis?: ProtocolAnalysisOutput | null +} +function AnalysisInfo(props: AnalysisInfoProps): JSX.Element { + const { + protocolKey, + protocolDisplayName, + isAnalyzing, + mostRecentAnalysis, + modified, + } = props + const { t } = useTranslation(['protocol_list', 'shared']) + const analysisStatus = getAnalysisStatus(isAnalyzing, mostRecentAnalysis) + + const { left: leftMountPipetteName, right: rightMountPipetteName } = + mostRecentAnalysis != null + ? parseInitialPipetteNamesByMount(mostRecentAnalysis.commands) + : { left: null, right: null } + const requiredModuleModels = parseAllRequiredModuleModels( + mostRecentAnalysis != null ? mostRecentAnalysis.commands : [] + ) + + const requiredModuleTypes = requiredModuleModels.map(getModuleType) + + const robotType = mostRecentAnalysis?.robotType ?? null + + return ( + + + { + { + missing: ( + + ), + loading: ( + + ), + error: ( + + ), + parameterRequired: ( + + ), + stale: ( + + ), + complete: + mostRecentAnalysis != null ? ( + + ) : ( + + ), + }[analysisStatus] + } + + + {/* error and protocol name section */} + + {analysisStatus === 'parameterRequired' ? ( + + ) : null} + {analysisStatus === 'error' ? ( + e.detail) ?? []} + /> + ) : null} + {analysisStatus === 'stale' ? ( + + ) : null} + + {protocolDisplayName} + + + {/* data section */} + {analysisStatus === 'loading' ? ( + + {t('loading_data')} + + ) : ( + + + + + {t('robot')} + + + {getRobotTypeDisplayName(robotType)} + + + + + {t('shared:instruments')} + + { + { + missing: ( + {t('no_data')} + ), + loading: ( + {t('no_data')} + ), + error: ( + {t('no_data')} + ), + parameterRequired: ( + {t('no_data')} + ), + stale: ( + {t('no_data')} + ), + complete: ( + + {/* TODO(bh, 2022-10-14): insert 96-channel pipette if found */} + {leftMountPipetteName != null ? ( + + ) : null} + {rightMountPipetteName != null ? ( + + ) : null} + {mostRecentAnalysis != null && + getProtocolUsesGripper(mostRecentAnalysis) ? ( + + ) : null} + + ), + }[analysisStatus] + } + + + {requiredModuleTypes.length > 0 ? ( + <> + + {t('modules')} + + + {requiredModuleTypes.map((moduleType, index) => ( + + ))} + + + ) : null} + + + + + {`${t('updated')} ${format( + new Date(modified), + 'M/d/yy HH:mm' + )}`} + + + + )} + + + ) +} diff --git a/app/src/organisms/ProtocolsLanding/ProtocolList.tsx b/app/src/organisms/Desktop/ProtocolsLanding/ProtocolList.tsx similarity index 92% rename from app/src/organisms/ProtocolsLanding/ProtocolList.tsx rename to app/src/organisms/Desktop/ProtocolsLanding/ProtocolList.tsx index 74cf536b60c..9cd58925e34 100644 --- a/app/src/organisms/ProtocolsLanding/ProtocolList.tsx +++ b/app/src/organisms/Desktop/ProtocolsLanding/ProtocolList.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' @@ -8,6 +8,7 @@ import { BORDERS, Box, COLORS, + CURSOR_POINTER, DIRECTION_COLUMN, DIRECTION_ROW, Flex, @@ -25,22 +26,25 @@ import { import { getProtocolsDesktopSortKey, updateConfigValue, -} from '../../redux/config' +} from '/app/redux/config' import { useSortedProtocols } from './hooks' -import { Slideout } from '../../atoms/Slideout' -import { ChooseRobotToRunProtocolSlideout } from '../ChooseRobotToRunProtocolSlideout' +import { Slideout } from '/app/atoms/Slideout' +import { ChooseRobotToRunProtocolSlideout } from '/app/organisms/Desktop/ChooseRobotToRunProtocolSlideout' import { SendProtocolToFlexSlideout } from '../SendProtocolToFlexSlideout' import { ProtocolUploadInput } from './ProtocolUploadInput' import { ProtocolCard } from './ProtocolCard' import { EmptyStateLinks } from './EmptyStateLinks' -import type { StoredProtocolData } from '../../redux/protocol-storage' -import type { ProtocolSort } from './hooks' -import type { Dispatch } from '../../redux/types' +import type { MouseEventHandler } from 'react' +import type { + StoredProtocolData, + ProtocolSort, +} from '/app/redux/protocol-storage' +import type { Dispatch } from '/app/redux/types' const SORT_BY_BUTTON_STYLE = css` background-color: ${COLORS.transparent}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; &:hover { background-color: ${COLORS.grey30}; } @@ -58,17 +62,17 @@ export function ProtocolList(props: ProtocolListProps): JSX.Element | null { const [ showImportProtocolSlideout, setShowImportProtocolSlideout, - ] = React.useState(false) + ] = useState(false) const [ showChooseRobotToRunProtocolSlideout, setShowChooseRobotToRunProtocolSlideout, - ] = React.useState(false) + ] = useState(false) const [ showSendProtocolToFlexSlideout, setShowSendProtocolToFlexSlideout, - ] = React.useState(false) + ] = useState(false) const sortBy = useSelector(getProtocolsDesktopSortKey) ?? 'alphabetical' - const [showSortByMenu, setShowSortByMenu] = React.useState(false) + const [showSortByMenu, setShowSortByMenu] = useState(false) const toggleSetShowSortByMenu = (): void => { setShowSortByMenu(!showSortByMenu) } @@ -77,13 +81,13 @@ export function ProtocolList(props: ProtocolListProps): JSX.Element | null { const [ selectedProtocol, setSelectedProtocol, - ] = React.useState(null) + ] = useState(null) const sortedStoredProtocols = useSortedProtocols(sortBy, storedProtocols) const dispatch = useDispatch() - const handleClickOutside: React.MouseEventHandler = e => { + const handleClickOutside: MouseEventHandler = e => { e.preventDefault() setShowSortByMenu(false) } diff --git a/app/src/organisms/ProtocolsLanding/ProtocolOverflowMenu.tsx b/app/src/organisms/Desktop/ProtocolsLanding/ProtocolOverflowMenu.tsx similarity index 94% rename from app/src/organisms/ProtocolsLanding/ProtocolOverflowMenu.tsx rename to app/src/organisms/Desktop/ProtocolsLanding/ProtocolOverflowMenu.tsx index 7cbf2814833..0bcc71f8cc0 100644 --- a/app/src/organisms/ProtocolsLanding/ProtocolOverflowMenu.tsx +++ b/app/src/organisms/Desktop/ProtocolsLanding/ProtocolOverflowMenu.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' @@ -12,6 +12,7 @@ import { DIRECTION_COLUMN, Flex, MenuItem, + NO_WRAP, OverflowBtn, POSITION_ABSOLUTE, POSITION_RELATIVE, @@ -20,23 +21,23 @@ import { } from '@opentrons/components' import { FLEX_DISPLAY_NAME, FLEX_ROBOT_TYPE } from '@opentrons/shared-data' -import { getTopPortalEl } from '../../App/portal' +import { getTopPortalEl } from '/app/App/portal' import { useTrackEvent, ANALYTICS_PROTOCOL_PROCEED_TO_RUN, ANALYTICS_DELETE_PROTOCOL_FROM_APP, -} from '../../redux/analytics' -import { useFeatureFlag } from '../../redux/config' +} from '/app/redux/analytics' +import { useFeatureFlag } from '/app/redux/config' import { analyzeProtocol, removeProtocol, viewProtocolSourceFolder, -} from '../../redux/protocol-storage' +} from '/app/redux/protocol-storage' import { ConfirmDeleteProtocolModal } from './ConfirmDeleteProtocolModal' import type { StyleProps } from '@opentrons/components' -import type { StoredProtocolData } from '../../redux/protocol-storage' -import type { Dispatch } from '../../redux/types' +import type { StoredProtocolData } from '/app/redux/protocol-storage' +import type { Dispatch } from '/app/redux/types' interface ProtocolOverflowMenuProps extends StyleProps { handleRunProtocol: (storedProtocolData: StoredProtocolData) => void @@ -131,7 +132,7 @@ export function ProtocolOverflowMenu( /> {showOverflowMenu ? ( void +} + +const isValidProtocolFileName = (protocolFileName: string): boolean => { + return protocolFileName.endsWith('.py') || protocolFileName.endsWith('.json') +} + +export function ProtocolUploadInput( + props: UploadInputProps +): JSX.Element | null { + const { t } = useTranslation(['protocol_info', 'shared']) + const dispatch = useDispatch() + const logger = useLogger(new URL('', import.meta.url).pathname) + const trackEvent = useTrackEvent() + const { makeToast } = useToaster() + + const handleUpload = (file: File): Promise => { + return remote.getFilePathFrom(file).then(filePath => { + if (filePath == null) { + logger.warn('Failed to upload file, path not found') + } + if (isValidProtocolFileName(file.name)) { + dispatch(addProtocol(filePath)) + } else { + makeToast(t('incompatible_file_type') as string, ERROR_TOAST, { + closeButton: true, + }) + } + props.onUpload?.() + trackEvent({ + name: ANALYTICS_IMPORT_PROTOCOL_TO_APP, + properties: { protocolFileName: file.name }, + }) + }) + } + + return ( + + { + void handleUpload(file) + }} + uploadText={t('valid_file_types')} + dragAndDropText={ + + , + }} + /> + + } + /> + + ) +} diff --git a/app/src/organisms/ProtocolsLanding/ProtocolsEmptyState.tsx b/app/src/organisms/Desktop/ProtocolsLanding/ProtocolsEmptyState.tsx similarity index 96% rename from app/src/organisms/ProtocolsLanding/ProtocolsEmptyState.tsx rename to app/src/organisms/Desktop/ProtocolsLanding/ProtocolsEmptyState.tsx index cc5d7b4fd08..40cdef0acd0 100644 --- a/app/src/organisms/ProtocolsLanding/ProtocolsEmptyState.tsx +++ b/app/src/organisms/Desktop/ProtocolsLanding/ProtocolsEmptyState.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { diff --git a/app/src/organisms/ProtocolsLanding/__tests__/ConfirmDeleteProtocolModal.test.tsx b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/ConfirmDeleteProtocolModal.test.tsx similarity index 91% rename from app/src/organisms/ProtocolsLanding/__tests__/ConfirmDeleteProtocolModal.test.tsx rename to app/src/organisms/Desktop/ProtocolsLanding/__tests__/ConfirmDeleteProtocolModal.test.tsx index ed2f816ded0..dd19a80d133 100644 --- a/app/src/organisms/ProtocolsLanding/__tests__/ConfirmDeleteProtocolModal.test.tsx +++ b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/ConfirmDeleteProtocolModal.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ConfirmDeleteProtocolModal } from '../ConfirmDeleteProtocolModal' const render = ( diff --git a/app/src/organisms/ProtocolsLanding/__tests__/EmptyStateLinks.test.tsx b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/EmptyStateLinks.test.tsx similarity index 87% rename from app/src/organisms/ProtocolsLanding/__tests__/EmptyStateLinks.test.tsx rename to app/src/organisms/Desktop/ProtocolsLanding/__tests__/EmptyStateLinks.test.tsx index 06b11ec4b17..f60357a7c63 100644 --- a/app/src/organisms/ProtocolsLanding/__tests__/EmptyStateLinks.test.tsx +++ b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/EmptyStateLinks.test.tsx @@ -1,10 +1,9 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { BrowserRouter } from 'react-router-dom' import { describe, it, expect, afterEach, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { EmptyStateLinks } from '../EmptyStateLinks' describe('EmptyStateLinks', () => { diff --git a/app/src/organisms/ProtocolsLanding/__tests__/ProtocolList.test.tsx b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/ProtocolList.test.tsx similarity index 94% rename from app/src/organisms/ProtocolsLanding/__tests__/ProtocolList.test.tsx rename to app/src/organisms/Desktop/ProtocolsLanding/__tests__/ProtocolList.test.tsx index 0e6cd8131d4..59271bc279c 100644 --- a/app/src/organisms/ProtocolsLanding/__tests__/ProtocolList.test.tsx +++ b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/ProtocolList.test.tsx @@ -1,24 +1,24 @@ -import * as React from 'react' +import type * as React from 'react' import { BrowserRouter } from 'react-router-dom' import { when } from 'vitest-when' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { getProtocolsDesktopSortKey } from '../../../redux/config' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getProtocolsDesktopSortKey } from '/app/redux/config' import { storedProtocolData, storedProtocolDataTwo, -} from '../../../redux/protocol-storage/__fixtures__' +} from '/app/redux/protocol-storage/__fixtures__' import { ProtocolList } from '../ProtocolList' import { useSortedProtocols } from '../hooks' import { EmptyStateLinks } from '../EmptyStateLinks' import { ProtocolCard } from '../ProtocolCard' vi.mock('../hooks') -vi.mock('../../../redux/protocol-storage') -vi.mock('../../../redux/config') +vi.mock('/app/redux/protocol-storage') +vi.mock('/app/redux/config') vi.mock('../EmptyStateLinks') vi.mock('../ProtocolCard') diff --git a/app/src/organisms/ProtocolsLanding/__tests__/ProtocolOverflowMenu.test.tsx b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/ProtocolOverflowMenu.test.tsx similarity index 91% rename from app/src/organisms/ProtocolsLanding/__tests__/ProtocolOverflowMenu.test.tsx rename to app/src/organisms/Desktop/ProtocolsLanding/__tests__/ProtocolOverflowMenu.test.tsx index dcf090f7c62..0e6166522bb 100644 --- a/app/src/organisms/ProtocolsLanding/__tests__/ProtocolOverflowMenu.test.tsx +++ b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/ProtocolOverflowMenu.test.tsx @@ -1,27 +1,26 @@ -import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' import { describe, it, vi, beforeEach, afterEach, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { useTrackEvent, ANALYTICS_PROTOCOL_PROCEED_TO_RUN, -} from '../../../redux/analytics' -import { storedProtocolData } from '../../../redux/protocol-storage/__fixtures__' +} from '/app/redux/analytics' +import { storedProtocolData } from '/app/redux/protocol-storage/__fixtures__' import { analyzeProtocol, removeProtocol, viewProtocolSourceFolder, -} from '../../../redux/protocol-storage' +} from '/app/redux/protocol-storage' import { ProtocolOverflowMenu } from '../ProtocolOverflowMenu' import type { Mock } from 'vitest' -vi.mock('../../../redux/analytics') -vi.mock('../../../redux/protocol-storage') +vi.mock('/app/redux/analytics') +vi.mock('/app/redux/protocol-storage') const mockHandleRunProtocol = vi.fn() const mockHandleSendProtocolToFlex = vi.fn() diff --git a/app/src/organisms/Desktop/ProtocolsLanding/__tests__/UploadInput.test.tsx b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/UploadInput.test.tsx new file mode 100644 index 00000000000..f03aaf861e7 --- /dev/null +++ b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/UploadInput.test.tsx @@ -0,0 +1,86 @@ +import { fireEvent, screen } from '@testing-library/react' +import { BrowserRouter } from 'react-router-dom' +import { describe, it, vi, beforeEach, afterEach, expect } from 'vitest' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { + useTrackEvent, + ANALYTICS_IMPORT_PROTOCOL_TO_APP, +} from '/app/redux/analytics' +import { ProtocolUploadInput } from '../ProtocolUploadInput' +import { remote } from '/app/redux/shell/remote' + +import type { Mock } from 'vitest' + +vi.mock('/app/redux/analytics') +vi.mock('/app/redux/shell/remote', () => ({ + remote: { + getFilePathFrom: vi.fn(), + }, +})) + +describe('ProtocolUploadInput', () => { + let onUpload: Mock + let trackEvent: Mock + const render = () => { + return renderWithProviders( + + + , + { + i18nInstance: i18n, + } + ) + } + + beforeEach(() => { + onUpload = vi.fn() + trackEvent = vi.fn() + vi.mocked(useTrackEvent).mockReturnValue(trackEvent) + vi.mocked(remote.getFilePathFrom).mockResolvedValue('mockFileName') + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders correct contents for empty state', () => { + render() + + screen.getByRole('button', { name: 'Upload' }) + screen.getByText(/Drag and drop or/i) + screen.getByText(/your files/i) + screen.getByText( + 'Valid file types: Python files (.py) or Protocol Designer files (.json)' + ) + screen.getByRole('button', { name: 'browse' }) + }) + + it('opens file select on button click', () => { + render() + const button = screen.getByRole('button', { name: 'Upload' }) + const input = screen.getByTestId('file_input') + input.click = vi.fn() + fireEvent.click(button) + expect(input.click).toHaveBeenCalled() + }) + it('calls onUpload callback on choose file and trigger analytics event', async () => { + render() + const input = screen.getByTestId('file_input') + + const mockFile = new File(['mockContent'], 'mockFileName', { + type: 'text/plain', + }) + + fireEvent.change(input, { + target: { files: [mockFile] }, + }) + + await vi.waitFor(() => { + expect(onUpload).toHaveBeenCalled() + expect(trackEvent).toHaveBeenCalledWith({ + name: ANALYTICS_IMPORT_PROTOCOL_TO_APP, + properties: { protocolFileName: 'mockFileName' }, + }) + }) + }) +}) diff --git a/app/src/organisms/Desktop/ProtocolsLanding/__tests__/hooks.test.tsx b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/hooks.test.tsx new file mode 100644 index 00000000000..5d1485fe795 --- /dev/null +++ b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/hooks.test.tsx @@ -0,0 +1,438 @@ +import type * as React from 'react' +import { Provider } from 'react-redux' +import { createStore } from 'redux' +import { renderHook } from '@testing-library/react' +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + +import { FLEX_ROBOT_TYPE, OT2_ROBOT_TYPE } from '@opentrons/shared-data' + +import { useSortedProtocols } from '../hooks' + +import type { Store } from 'redux' +import type { ProtocolAnalysisOutput } from '@opentrons/shared-data' +import type { StoredProtocolData } from '/app/redux/protocol-storage' +import type { State } from '/app/redux/types' + +const mockStoredProtocolData = [ + { + protocolKey: '26ed5a82-502f-4074-8981-57cdda1d066d', + modified: 1651613783762.9993, + srcFileNames: ['secondProtocol.json'], + srcFiles: [], + mostRecentAnalysis: { + robotType: FLEX_ROBOT_TYPE, + createdAt: '2022-05-03T21:36:12.494778+00:00', + files: [ + { + name: 'secondProtocol.json', + role: 'main', + }, + ], + config: { + protocolType: 'json', + schemaVersion: 6, + }, + metadata: { + author: 'Otie', + description: 'another mock protocol', + created: 1606853851893, + lastModified: 1619792954015, + tags: [], + }, + commands: [], + labware: [ + { + id: 'labware-0', + loadName: 'opentrons_1_trash_1100ml_fixed', + definitionUri: 'opentrons/opentrons_1_trash_1100ml_fixed/1', + location: { slotName: '12' }, + displayName: 'Trash', + }, + { + id: 'labware-1', + loadName: 'opentrons_96_tiprack_1000ul', + definitionUri: 'opentrons/opentrons_96_tiprack_1000ul/1', + location: { slotName: '1' }, + displayName: 'Opentrons 96 Tip Rack 1000 µL', + }, + ], + pipettes: [ + { + id: 'pipette-0', + pipetteName: 'p1000_single_gen2', + mount: 'left', + }, + ], + modules: [ + { + id: 'module-0', + model: 'magneticModuleV2', + location: { slotName: '6' }, + serialNumber: 'dummySerialMD', + }, + { + id: 'module-1', + model: 'temperatureModuleV2', + location: { slotName: '3' }, + serialNumber: 'dummySerialTD', + }, + { + id: 'module-2', + model: 'thermocyclerModuleV1', + location: { slotName: '7' }, + serialNumber: 'dummySerialTC', + }, + ], + liquids: [ + { + id: '0', + displayName: 'Water', + description: 'liquid H2O', + displayColor: '#50d5ff', + }, + { + id: '1', + displayName: 'Blood', + description: 'human essence', + displayColor: '#ff4f4f', + }, + ], + runTimeParameters: [], + errors: [], + result: 'ok', + } as ProtocolAnalysisOutput, + }, + { + protocolKey: '3dc99ffa-f85e-4c01-ab0a-edecff432dac', + modified: 1652339310312.1985, + srcFileNames: ['testProtocol.json'], + srcFiles: [], + mostRecentAnalysis: { + robotType: OT2_ROBOT_TYPE, + createdAt: '2022-05-10T17:04:43.132768+00:00', + files: [ + { + name: 'testProtocol.json', + role: 'main', + }, + ], + config: { + protocolType: 'json', + schemaVersion: 6, + }, + metadata: { + protocolName: 'Third protocol', + author: 'engineering', + description: 'A short mock protocol', + created: 1223131231, + tags: ['unitTest'], + }, + commands: [], + labware: [ + { + id: 'labware-0', + loadName: 'opentrons_1_trash_1100ml_fixed', + definitionUri: 'opentrons/opentrons_1_trash_1100ml_fixed/1', + location: { slotName: '12' }, + displayName: 'Trash', + }, + { + id: 'labware-1', + loadName: 'opentrons_96_tiprack_1000ul', + definitionUri: 'opentrons/opentrons_96_tiprack_1000ul/1', + location: { slotName: '1' }, + displayName: 'Opentrons 96 Tip Rack 1000 µL', + }, + ], + pipettes: [ + { + id: 'pipette-0', + pipetteName: 'p1000_single_gen2', + mount: 'left', + }, + ], + modules: [ + { + id: 'module-0', + model: 'magneticModuleV2', + location: { slotName: '6' }, + serialNumber: 'dummySerialMD', + }, + { + id: 'module-1', + model: 'temperatureModuleV2', + location: { slotName: '3' }, + serialNumber: 'dummySerialTD', + }, + { + id: 'module-2', + model: 'thermocyclerModuleV1', + location: { slotName: '7' }, + serialNumber: 'dummySerialTC', + }, + ], + liquids: [ + { + id: '0', + displayName: 'Water', + description: 'liquid H2O', + displayColor: '#50d5ff', + }, + { + id: '1', + displayName: 'Blood', + description: 'human essence', + displayColor: '#ff4f4f', + }, + ], + runTimeParameters: [], + errors: [], + result: 'ok', + } as ProtocolAnalysisOutput, + }, + { + protocolKey: 'f130337e-68ad-4b5d-a6d2-cbc20515b1f7', + modified: 1651688428961.8438, + srcFileNames: ['demo.py'], + srcFiles: [], + mostRecentAnalysis: { + createdAt: '2022-05-04T18:20:21.526508+00:00', + files: [ + { + name: 'demo.py', + role: 'main', + }, + ], + config: { + protocolType: 'python', + apiVersion: [2, 10], + }, + metadata: { + protocolName: 'First protocol', + author: 'Otie', + source: 'Custom Protocol Request', + apiLevel: '2.10', + }, + commands: [], + labware: [ + { + id: 'labware-0', + loadName: 'opentrons_1_trash_1100ml_fixed', + definitionUri: 'opentrons/opentrons_1_trash_1100ml_fixed/1', + location: { slotName: '12' }, + displayName: 'Trash', + }, + { + id: 'labware-1', + loadName: 'opentrons_96_tiprack_1000ul', + definitionUri: 'opentrons/opentrons_96_tiprack_1000ul/1', + location: { slotName: '1' }, + displayName: 'Opentrons 96 Tip Rack 1000 µL', + }, + ], + pipettes: [ + { + id: 'pipette-0', + pipetteName: 'p1000_single_gen2', + mount: 'left', + }, + ], + modules: [ + { + id: 'module-0', + model: 'magneticModuleV2', + location: { + slotName: '6', + }, + serialNumber: 'dummySerialMD', + }, + { + id: 'module-1', + model: 'temperatureModuleV2', + location: { + slotName: '3', + }, + serialNumber: 'dummySerialTD', + }, + { + id: 'module-2', + model: 'thermocyclerModuleV1', + location: { + slotName: '7', + }, + serialNumber: 'dummySerialTC', + }, + ], + liquids: [ + { + id: '0', + displayName: 'Water', + description: 'liquid H2O', + displayColor: '#50d5ff', + }, + { + id: '1', + displayName: 'Blood', + description: 'human essence', + displayColor: '#ff4f4f', + }, + ], + runTimeParameters: [], + errors: [], + result: 'ok', + } as ProtocolAnalysisOutput, + }, +] as StoredProtocolData[] + +describe('useSortedProtocols', () => { + const store: Store = createStore(vi.fn(), {}) + beforeEach(() => { + store.dispatch = vi.fn() + }) + afterEach(() => { + vi.restoreAllMocks() + }) + + it('should return an object with protocols sorted alphabetically', () => { + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => {children} + + const { result } = renderHook( + () => useSortedProtocols('alphabetical', mockStoredProtocolData), + { wrapper } + ) + const firstProtocol = result.current[0] + const secondProtocol = result.current[1] + const thirdProtocol = result.current[2] + + expect(firstProtocol.protocolKey).toBe( + 'f130337e-68ad-4b5d-a6d2-cbc20515b1f7' + ) + expect(secondProtocol.protocolKey).toBe( + '26ed5a82-502f-4074-8981-57cdda1d066d' + ) + expect(thirdProtocol.protocolKey).toBe( + '3dc99ffa-f85e-4c01-ab0a-edecff432dac' + ) + }) + + it('should return an object with protocols sorted reverse alphabetically', () => { + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => {children} + + const { result } = renderHook( + () => useSortedProtocols('reverse', mockStoredProtocolData), + { wrapper } + ) + const firstProtocol = result.current[0] + const secondProtocol = result.current[1] + const thirdProtocol = result.current[2] + + expect(firstProtocol.protocolKey).toBe( + '3dc99ffa-f85e-4c01-ab0a-edecff432dac' + ) + expect(secondProtocol.protocolKey).toBe( + '26ed5a82-502f-4074-8981-57cdda1d066d' + ) + expect(thirdProtocol.protocolKey).toBe( + 'f130337e-68ad-4b5d-a6d2-cbc20515b1f7' + ) + }) + + it('should return an object with protocols sorted by most recent modified data', () => { + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => {children} + + const { result } = renderHook( + () => useSortedProtocols('recent', mockStoredProtocolData), + { wrapper } + ) + const firstProtocol = result.current[0] + const secondProtocol = result.current[1] + const thirdProtocol = result.current[2] + + expect(firstProtocol.protocolKey).toBe( + '3dc99ffa-f85e-4c01-ab0a-edecff432dac' + ) + expect(secondProtocol.protocolKey).toBe( + 'f130337e-68ad-4b5d-a6d2-cbc20515b1f7' + ) + expect(thirdProtocol.protocolKey).toBe( + '26ed5a82-502f-4074-8981-57cdda1d066d' + ) + }) + + it('should return an object with protocols sorted by oldest modified data', () => { + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => {children} + + const { result } = renderHook( + () => useSortedProtocols('oldest', mockStoredProtocolData), + { wrapper } + ) + const firstProtocol = result.current[0] + const secondProtocol = result.current[1] + const thirdProtocol = result.current[2] + + expect(firstProtocol.protocolKey).toBe( + '26ed5a82-502f-4074-8981-57cdda1d066d' + ) + expect(secondProtocol.protocolKey).toBe( + 'f130337e-68ad-4b5d-a6d2-cbc20515b1f7' + ) + expect(thirdProtocol.protocolKey).toBe( + '3dc99ffa-f85e-4c01-ab0a-edecff432dac' + ) + }) + + it('should return an object with protocols sorted by flex then ot-2', () => { + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => {children} + + const { result } = renderHook( + () => useSortedProtocols('flex', mockStoredProtocolData), + { wrapper } + ) + const firstProtocol = result.current[0] + const secondProtocol = result.current[1] + const thirdProtocol = result.current[2] + + expect(firstProtocol.protocolKey).toBe( + '26ed5a82-502f-4074-8981-57cdda1d066d' + ) + expect(secondProtocol.protocolKey).toBe( + '3dc99ffa-f85e-4c01-ab0a-edecff432dac' + ) + expect(thirdProtocol.protocolKey).toBe( + 'f130337e-68ad-4b5d-a6d2-cbc20515b1f7' + ) + }) + it('should return an object with protocols sorted by ot-2 then flex', () => { + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => {children} + + const { result } = renderHook( + () => useSortedProtocols('ot2', mockStoredProtocolData), + { wrapper } + ) + const firstProtocol = result.current[0] + const secondProtocol = result.current[1] + const thirdProtocol = result.current[2] + + expect(firstProtocol.protocolKey).toBe( + '3dc99ffa-f85e-4c01-ab0a-edecff432dac' + ) + expect(secondProtocol.protocolKey).toBe( + 'f130337e-68ad-4b5d-a6d2-cbc20515b1f7' + ) + expect(thirdProtocol.protocolKey).toBe( + '26ed5a82-502f-4074-8981-57cdda1d066d' + ) + }) +}) diff --git a/app/src/organisms/Desktop/ProtocolsLanding/__tests__/utils.test.ts b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/utils.test.ts new file mode 100644 index 00000000000..e4383c842b9 --- /dev/null +++ b/app/src/organisms/Desktop/ProtocolsLanding/__tests__/utils.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect } from 'vitest' +import { getisFlexProtocol, getRobotTypeDisplayName } from '../utils' +import type { ProtocolAnalysisOutput } from '@opentrons/shared-data' + +const mockOT3ProtocolAnalysisOutput = { + robotType: 'OT-3 Standard', +} as ProtocolAnalysisOutput + +const mockOT2ProtocolAnalysisOutput = { + robotType: 'OT-2 Standard', +} as ProtocolAnalysisOutput + +describe('getisFlexProtocol', () => { + it('should return true for protocols intended for a Flex', () => { + const result = getisFlexProtocol(mockOT3ProtocolAnalysisOutput) + expect(result).toBe(true) + }) + + it('should return false for protocols intended for an OT-2', () => { + const result = getisFlexProtocol(mockOT2ProtocolAnalysisOutput) + expect(result).toBe(false) + }) + + it('should return false for protocols that do not specify a robot type', () => { + const result = getisFlexProtocol({} as ProtocolAnalysisOutput) + expect(result).toBe(false) + }) + + it('should return false given null', () => { + const result = getisFlexProtocol(null) + expect(result).toBe(false) + }) +}) + +describe('getRobotTypeDisplayName', () => { + it('should return OT-3 for protocols intended for a Flex', () => { + const result = getRobotTypeDisplayName('OT-3 Standard') + expect(result).toBe('Opentrons Flex') + }) + + it('should return OT-2 for protocols intended for an OT-2', () => { + const result = getRobotTypeDisplayName('OT-2 Standard') + expect(result).toBe('OT-2') + }) + + it('should return OT-2 for protocols that do not specify a robot type', () => { + const result = getRobotTypeDisplayName(null) + expect(result).toBe('OT-2') + }) +}) diff --git a/app/src/organisms/Desktop/ProtocolsLanding/hooks.tsx b/app/src/organisms/Desktop/ProtocolsLanding/hooks.tsx new file mode 100644 index 00000000000..fec57e89955 --- /dev/null +++ b/app/src/organisms/Desktop/ProtocolsLanding/hooks.tsx @@ -0,0 +1,69 @@ +import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' +import { getProtocolDisplayName } from '/app/transformations/protocols' +import type { + StoredProtocolData, + ProtocolSort, +} from '/app/redux/protocol-storage' + +export function useSortedProtocols( + sortBy: ProtocolSort, + protocolData: StoredProtocolData[] +): StoredProtocolData[] { + protocolData.sort((a, b) => { + const protocolNameA = getProtocolDisplayName( + a.protocolKey, + a.srcFileNames, + a?.mostRecentAnalysis + ) + const protocolNameB = getProtocolDisplayName( + b.protocolKey, + b.srcFileNames, + b?.mostRecentAnalysis + ) + const protocolRobotTypeA = a?.mostRecentAnalysis?.robotType + const protocolRobotTypeB = b?.mostRecentAnalysis?.robotType + + if (sortBy === 'alphabetical') { + if (protocolNameA.toLowerCase() === protocolNameB.toLowerCase()) { + return b.modified - a.modified + } + return protocolNameA.toLowerCase() > protocolNameB.toLowerCase() ? 1 : -1 + } else if (sortBy === 'reverse') { + return protocolNameA.toLowerCase() > protocolNameB.toLowerCase() ? -1 : 1 + } else if (sortBy === 'recent') { + return b.modified - a.modified + } else if (sortBy === 'oldest') { + return a.modified - b.modified + } else if (sortBy === 'flex') { + if ( + protocolRobotTypeA === FLEX_ROBOT_TYPE && + protocolRobotTypeB !== FLEX_ROBOT_TYPE + ) { + return -1 + } + if ( + protocolRobotTypeA !== FLEX_ROBOT_TYPE && + protocolRobotTypeB === FLEX_ROBOT_TYPE + ) { + return 1 + } + return b.modified - a.modified + } else if (sortBy === 'ot2') { + if ( + protocolRobotTypeA !== FLEX_ROBOT_TYPE && + protocolRobotTypeB === FLEX_ROBOT_TYPE + ) { + return -1 + } + if ( + protocolRobotTypeA === FLEX_ROBOT_TYPE && + protocolRobotTypeB !== FLEX_ROBOT_TYPE + ) { + return 1 + } + return b.modified - a.modified + } + return 0 + }) + return protocolData +} diff --git a/app/src/organisms/Desktop/ProtocolsLanding/utils.ts b/app/src/organisms/Desktop/ProtocolsLanding/utils.ts new file mode 100644 index 00000000000..b3868e53c0d --- /dev/null +++ b/app/src/organisms/Desktop/ProtocolsLanding/utils.ts @@ -0,0 +1,53 @@ +import { FLEX_STANDARD_MODEL } from '@opentrons/shared-data' +import type { ProtocolAnalysisOutput, RobotType } from '@opentrons/shared-data' + +type AnalysisStatus = + | 'missing' + | 'loading' + | 'error' + | 'complete' + | 'stale' + | 'parameterRequired' + +export function getAnalysisStatus( + isAnalyzing: boolean, + analysis?: ProtocolAnalysisOutput | null +): AnalysisStatus { + if (isAnalyzing) { + return 'loading' + } + if (analysis == null || analysis === undefined) { + return 'missing' + } + if (analysis.liquids == null || analysis.runTimeParameters == null) { + return 'stale' + } + if (analysis.result === 'parameter-value-required') { + return 'parameterRequired' + } + if (analysis.errors.length > 0) { + return 'error' + } + return 'complete' +} + +export function getRobotTypeDisplayName( + robotType: RobotType | null +): 'OT-2' | 'Opentrons Flex' { + if (robotType === FLEX_STANDARD_MODEL) { + return 'Opentrons Flex' + } else { + // defaults to OT-2 display name. may want to reconsider for protocols that fail analysis + return 'OT-2' + } +} + +export function getisFlexProtocol( + protocolAnalysis?: ProtocolAnalysisOutput | null +): boolean { + if (protocolAnalysis?.robotType === FLEX_STANDARD_MODEL) { + return true + } else { + return false + } +} diff --git a/app/src/organisms/RobotSettingsCalibration/CalibrationDataDownload.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDataDownload.tsx similarity index 92% rename from app/src/organisms/RobotSettingsCalibration/CalibrationDataDownload.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDataDownload.tsx index 8d32509367d..4731df09ae4 100644 --- a/app/src/organisms/RobotSettingsCalibration/CalibrationDataDownload.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDataDownload.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { saveAs } from 'file-saver' import { useTranslation, Trans } from 'react-i18next' @@ -17,19 +17,18 @@ import { useInstrumentsQuery, useModulesQuery, } from '@opentrons/react-api-client' -import { TertiaryButton } from '../../atoms/buttons' +import { TertiaryButton } from '/app/atoms/buttons' import { useDeckCalibrationData, - useIsFlex, usePipetteOffsetCalibrations, - useRobot, useTipLengthCalibrations, -} from '../../organisms/Devices/hooks' +} from '/app/organisms/Desktop/Devices/hooks' import { useTrackEvent, ANALYTICS_CALIBRATION_DATA_DOWNLOADED, -} from '../../redux/analytics' -import { useIsEstopNotDisengaged } from '../../resources/devices/hooks/useIsEstopNotDisengaged' +} from '/app/redux/analytics' +import { useRobot, useIsFlex } from '/app/redux-resources/robots' +import { useIsEstopNotDisengaged } from '/app/resources/devices/hooks/useIsEstopNotDisengaged' // TODO(bc, 2022-02-08): replace with support article when available const FLEX_CALIBRATION_SUPPORT_URL = 'https://support.opentrons.com' diff --git a/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationItems.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationItems.tsx new file mode 100644 index 00000000000..684b8269784 --- /dev/null +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationItems.tsx @@ -0,0 +1,121 @@ +import { useTranslation } from 'react-i18next' +import styled, { css } from 'styled-components' + +import { + BORDERS, + COLORS, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' +import { + getModuleDisplayName, + ABSORBANCE_READER_TYPE, +} from '@opentrons/shared-data' + +import { formatLastCalibrated } from './utils' +import { ModuleCalibrationOverflowMenu } from './ModuleCalibrationOverflowMenu' + +import type { AttachedModule } from '@opentrons/api-client' +import type { FormattedPipetteOffsetCalibration } from '..' + +interface ModuleCalibrationItemsProps { + attachedModules: AttachedModule[] + updateRobotStatus: (isRobotBusy: boolean) => void + formattedPipetteOffsetCalibrations: FormattedPipetteOffsetCalibration[] + robotName: string +} + +export function ModuleCalibrationItems({ + attachedModules, + updateRobotStatus, + formattedPipetteOffsetCalibrations, + robotName, +}: ModuleCalibrationItemsProps): JSX.Element { + const { t } = useTranslation('device_settings') + + return ( + + + + {t('module')} + {t('serial')} + {t('last_calibrated_label')} + + + + {attachedModules.map(attachedModule => { + const noCalibrationCopy = + attachedModule.moduleType === ABSORBANCE_READER_TYPE + ? t('no_calibration_required') + : t('not_calibrated_short') + + return ( + + + + {getModuleDisplayName(attachedModule.moduleModel)} + + + + + {attachedModule.serialNumber} + + + + + {attachedModule.moduleOffset?.last_modified != null + ? formatLastCalibrated( + attachedModule.moduleOffset?.last_modified + ) + : noCalibrationCopy} + + + + {attachedModule.moduleType !== ABSORBANCE_READER_TYPE ? ( + + ) : null} + + + ) + })} + + + ) +} + +const StyledTable = styled.table` + width: 100%; + border-collapse: collapse; + text-align: left; +` + +const StyledTableHeader = styled.th` + ${TYPOGRAPHY.labelSemiBold} + padding: ${SPACING.spacing8}; +` + +const StyledTableRow = styled.tr` + padding: ${SPACING.spacing8}; + border-bottom: ${BORDERS.lineBorder}; +` + +const StyledTableCell = styled.td` + padding: ${SPACING.spacing8}; + text-overflow: wrap; +` + +const BODY_STYLE = css` + box-shadow: 0 0 0 1px ${COLORS.grey30}; + border-radius: 3px; +` diff --git a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationOverflowMenu.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationOverflowMenu.tsx similarity index 85% rename from app/src/organisms/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationOverflowMenu.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationOverflowMenu.tsx index 8f8a4076a69..c44be36fa4a 100644 --- a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationOverflowMenu.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationOverflowMenu.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' @@ -17,14 +17,13 @@ import { useOnClickOutside, } from '@opentrons/components' -import { useChainLiveCommands } from '../../../resources/runs' -import { useRunStatuses } from '../../Devices/hooks' -import { getModulePrepCommands } from '../../Devices/getModulePrepCommands' -import { ModuleWizardFlows } from '../../ModuleWizardFlows' -import { getModuleTooHot } from '../../Devices/getModuleTooHot' -import { useIsEstopNotDisengaged } from '../../../resources/devices/hooks/useIsEstopNotDisengaged' +import { useChainLiveCommands, useRunStatuses } from '/app/resources/runs' +import { getModulePrepCommands } from '/app/local-resources/modules' +import { ModuleWizardFlows } from '/app/organisms/ModuleWizardFlows' +import { getModuleTooHot } from '/app/transformations/modules' +import { useIsEstopNotDisengaged } from '/app/resources/devices/hooks/useIsEstopNotDisengaged' -import type { AttachedModule } from '../../../redux/modules/types' +import type { AttachedModule } from '/app/redux/modules/types' import type { FormattedPipetteOffsetCalibration } from '..' interface ModuleCalibrationOverflowMenuProps { isCalibrated: boolean @@ -54,7 +53,7 @@ export function ModuleCalibrationOverflowMenu({ setShowOverflowMenu, } = useMenuHandleClickOutside() - const [showModuleWizard, setShowModuleWizard] = React.useState(false) + const [showModuleWizard, setShowModuleWizard] = useState(false) const { isRunRunning: isRunning } = useRunStatuses() const [targetProps, tooltipProps] = useHoverTooltip() @@ -73,7 +72,7 @@ export function ModuleCalibrationOverflowMenu({ const [ prepCommandErrorMessage, setPrepCommandErrorMessage, - ] = React.useState('') + ] = useState('') const isEstopNotDisengaged = useIsEstopNotDisengaged(robotName) @@ -87,7 +86,7 @@ export function ModuleCalibrationOverflowMenu({ setShowModuleWizard(true) } - React.useEffect(() => { + useEffect(() => { if (isRunning) { updateRobotStatus(true) } diff --git a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/OverflowMenu.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/OverflowMenu.tsx similarity index 86% rename from app/src/organisms/RobotSettingsCalibration/CalibrationDetails/OverflowMenu.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/OverflowMenu.tsx index aaa506bd390..1db51029549 100644 --- a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/OverflowMenu.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/OverflowMenu.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { saveAs } from 'file-saver' import { css } from 'styled-components' @@ -10,6 +10,7 @@ import { DIRECTION_COLUMN, Flex, MenuItem, + NO_WRAP, OverflowBtn, POSITION_ABSOLUTE, POSITION_RELATIVE, @@ -23,23 +24,22 @@ import { useAllTipLengthCalibrationsQuery, } from '@opentrons/react-api-client' -import { Divider } from '../../../atoms/structure' +import { Divider } from '/app/atoms/structure' import { useTrackEvent, ANALYTICS_CALIBRATION_DATA_DOWNLOADED, -} from '../../../redux/analytics' -import { - useRunStatuses, - useAttachedPipettesFromInstrumentsQuery, -} from '../../../organisms/Devices/hooks' -import { PipetteWizardFlows } from '../../PipetteWizardFlows' -import { FLOWS } from '../../PipetteWizardFlows/constants' -import { useIsEstopNotDisengaged } from '../../../resources/devices/hooks/useIsEstopNotDisengaged' - +} from '/app/redux/analytics' +import { useRunStatuses } from '/app/resources/runs' +import { PipetteWizardFlows } from '/app/organisms/PipetteWizardFlows' +import { FLOWS } from '/app/organisms/PipetteWizardFlows/constants' +import { useIsEstopNotDisengaged } from '/app/resources/devices' +import { useAttachedPipettesFromInstrumentsQuery } from '/app/resources/instruments' + +import type { MouseEvent } from 'react' import type { Mount } from '@opentrons/components' import type { PipetteName } from '@opentrons/shared-data' import type { DeleteCalRequestParams } from '@opentrons/api-client' -import type { SelectablePipettes } from '../../PipetteWizardFlows/types' +import type { SelectablePipettes } from '/app/organisms/PipetteWizardFlows/types' interface OverflowMenuProps { calType: 'pipetteOffset' | 'tipLength' @@ -83,10 +83,9 @@ export function OverflowMenu({ const tipLengthCalibrations = useAllTipLengthCalibrationsQuery().data?.data const { isRunRunning: isRunning } = useRunStatuses() - const [ - showPipetteWizardFlows, - setShowPipetteWizardFlows, - ] = React.useState(false) + const [showPipetteWizardFlows, setShowPipetteWizardFlows] = useState( + false + ) const isEstopNotDisengaged = useIsEstopNotDisengaged(robotName) const isPipetteForFlex = isFlexPipette(pipetteName as PipetteName) const ot3PipCal = @@ -104,7 +103,7 @@ export function OverflowMenu({ calType === 'pipetteOffset' ? applicablePipetteOffsetCal != null : applicableTipLengthCal != null - const handleRecalibrate = (e: React.MouseEvent): void => { + const handleRecalibrate = (e: MouseEvent): void => { e.preventDefault() if ( !isRunning && @@ -117,7 +116,7 @@ export function OverflowMenu({ setShowOverflowMenu(currentShowOverflowMenu => !currentShowOverflowMenu) } - const handleDownload = (e: React.MouseEvent): void => { + const handleDownload = (e: MouseEvent): void => { e.preventDefault() doTrackEvent({ name: ANALYTICS_CALIBRATION_DATA_DOWNLOADED, @@ -138,19 +137,18 @@ export function OverflowMenu({ setShowOverflowMenu(currentShowOverflowMenu => !currentShowOverflowMenu) } - React.useEffect(() => { + useEffect(() => { if (isRunning) { updateRobotStatus(true) } }, [isRunning, updateRobotStatus]) const { deleteCalibration } = useDeleteCalibrationMutation() - const [ - selectedPipette, - setSelectedPipette, - ] = React.useState(SINGLE_MOUNT_PIPETTES) + const [selectedPipette, setSelectedPipette] = useState( + SINGLE_MOUNT_PIPETTES + ) - const handleDeleteCalibration = (e: React.MouseEvent): void => { + const handleDeleteCalibration = (e: MouseEvent): void => { e.preventDefault() let params: DeleteCalRequestParams if (calType === 'pipetteOffset') { @@ -198,7 +196,7 @@ export function OverflowMenu({ {showOverflowMenu ? ( { render(props) screen.getByText(formatLastCalibrated('2023-06-01T14:42:20.131798+00:00')) }) + + it('should say no calibration required if module is absorbance reader', () => { + const absorbanceReaderAttachedModule = { + ...mockCalibratedModule, + moduleType: ABSORBANCE_READER_TYPE, + moduleOffset: undefined, + } + props = { + ...props, + attachedModules: [ + absorbanceReaderAttachedModule as AttachedModule, + ] as AttachedModule[], + } + render(props) + expect( + screen.queryByText('mock ModuleCalibrationOverflowMenu') + ).not.toBeInTheDocument() + screen.getByText('No calibration required') + }) }) diff --git a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationOverflowMenu.test.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationOverflowMenu.test.tsx similarity index 92% rename from app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationOverflowMenu.test.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationOverflowMenu.test.tsx index 02823e085a0..47b5dbec249 100644 --- a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationOverflowMenu.test.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/ModuleCalibrationOverflowMenu.test.tsx @@ -1,24 +1,22 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen, waitFor } from '@testing-library/react' import { when } from 'vitest-when' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { i18n } from '../../../../i18n' -import { renderWithProviders } from '../../../../__testing-utils__' -import { ModuleWizardFlows } from '../../../ModuleWizardFlows' -import { useChainLiveCommands } from '../../../../resources/runs' -import { mockThermocyclerGen2 } from '../../../../redux/modules/__fixtures__' -import { useRunStatuses } from '../../../Devices/hooks' -import { useIsEstopNotDisengaged } from '../../../../resources/devices/hooks/useIsEstopNotDisengaged' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { ModuleWizardFlows } from '/app/organisms/ModuleWizardFlows' +import { useChainLiveCommands, useRunStatuses } from '/app/resources/runs' +import { mockThermocyclerGen2 } from '/app/redux/modules/__fixtures__' +import { useIsEstopNotDisengaged } from '/app/resources/devices/hooks/useIsEstopNotDisengaged' import { ModuleCalibrationOverflowMenu } from '../ModuleCalibrationOverflowMenu' import type { Mount } from '@opentrons/components' vi.mock('@opentrons/react-api-client') -vi.mock('../../../ModuleWizardFlows') -vi.mock('../../../Devices/hooks') -vi.mock('../../../../resources/runs') -vi.mock('../../../../resources/devices/hooks/useIsEstopNotDisengaged') +vi.mock('/app/organisms/ModuleWizardFlows') +vi.mock('/app/resources/runs') +vi.mock('/app/resources/devices/hooks/useIsEstopNotDisengaged') const mockPipetteOffsetCalibrations = [ { diff --git a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/OverflowMenu.test.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/OverflowMenu.test.tsx similarity index 89% rename from app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/OverflowMenu.test.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/OverflowMenu.test.tsx index 3bdb82574c5..a0d2ff20096 100644 --- a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/OverflowMenu.test.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/OverflowMenu.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { when } from 'vitest-when' import '@testing-library/jest-dom/vitest' @@ -10,22 +10,20 @@ import { useAllTipLengthCalibrationsQuery, } from '@opentrons/react-api-client' -import { i18n } from '../../../../i18n' -import { mockDeckCalData } from '../../../../redux/calibration/__fixtures__' -import { PipetteWizardFlows } from '../../../PipetteWizardFlows' -import { useCalibratePipetteOffset } from '../../../CalibratePipetteOffset/useCalibratePipetteOffset' -import { - useDeckCalibrationData, - useRunStatuses, - useAttachedPipettesFromInstrumentsQuery, -} from '../../../Devices/hooks' -import { mockAttachedPipetteInformation } from '../../../../redux/pipettes/__fixtures__' +import { i18n } from '/app/i18n' +import { mockDeckCalData } from '/app/redux/calibration/__fixtures__' +import { PipetteWizardFlows } from '/app/organisms/PipetteWizardFlows' +import { useCalibratePipetteOffset } from '/app/organisms/Desktop/CalibratePipetteOffset/useCalibratePipetteOffset' +import { useDeckCalibrationData } from '../../../Devices/hooks' +import { useAttachedPipettesFromInstrumentsQuery } from '/app/resources/instruments' +import { mockAttachedPipetteInformation } from '/app/redux/pipettes/__fixtures__' +import { useRunStatuses } from '/app/resources/runs' import { mockPipetteOffsetCalibrationsResponse, mockTipLengthCalibrationResponse, } from '../__fixtures__' -import { renderWithProviders } from '../../../../__testing-utils__' -import { useIsEstopNotDisengaged } from '../../../../resources/devices/hooks/useIsEstopNotDisengaged' +import { renderWithProviders } from '/app/__testing-utils__' +import { useIsEstopNotDisengaged } from '/app/resources/devices' import { OverflowMenu } from '../OverflowMenu' import type { Mount } from '@opentrons/components' @@ -61,16 +59,17 @@ vi.mock('@opentrons/shared-data', async () => { } }) vi.mock('@opentrons/react-api-client') -vi.mock('../../../../redux/sessions/selectors') -vi.mock('../../../../redux/discovery') -vi.mock('../../../../redux/robot-api/selectors') +vi.mock('/app/redux/sessions/selectors') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/robot-api/selectors') vi.mock( - '../../../../organisms/CalibratePipetteOffset/useCalibratePipetteOffset' + '/app/organisms/Desktop/CalibratePipetteOffset/useCalibratePipetteOffset' ) -vi.mock('../../../../organisms/ProtocolUpload/hooks') -vi.mock('../../../../organisms/Devices/hooks') -vi.mock('../../../PipetteWizardFlows') -vi.mock('../../../../resources/devices/hooks/useIsEstopNotDisengaged') +vi.mock('../../../Devices/hooks') +vi.mock('/app/organisms/PipetteWizardFlows') +vi.mock('/app/resources/devices') +vi.mock('/app/resources/instruments') +vi.mock('/app/resources/runs') const RUN_STATUSES = { isRunRunning: false, diff --git a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/PipetteOffsetCalibrationItems.test.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/PipetteOffsetCalibrationItems.test.tsx similarity index 89% rename from app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/PipetteOffsetCalibrationItems.test.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/PipetteOffsetCalibrationItems.test.tsx index 5be4b555fc1..2a6cf82726f 100644 --- a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/PipetteOffsetCalibrationItems.test.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/PipetteOffsetCalibrationItems.test.tsx @@ -1,26 +1,26 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../../i18n' +import { i18n } from '/app/i18n' import { mockAttachedPipette, mockAttachedPipetteInformation, -} from '../../../../redux/pipettes/__fixtures__' +} from '/app/redux/pipettes/__fixtures__' import { useAttachedPipettes, - useIsFlex, useAttachedPipettesFromInstrumentsQuery, -} from '../../../Devices/hooks' -import { renderWithProviders } from '../../../../__testing-utils__' +} from '/app/resources/instruments' +import { useIsFlex } from '/app/redux-resources/robots' +import { renderWithProviders } from '/app/__testing-utils__' import { PipetteOffsetCalibrationItems } from '../PipetteOffsetCalibrationItems' import { OverflowMenu } from '../OverflowMenu' import { formatLastCalibrated } from '../utils' import type { Mount } from '@opentrons/components' -import type { AttachedPipettesByMount } from '../../../../redux/pipettes/types' +import type { AttachedPipettesByMount } from '/app/redux/pipettes/types' const render = ( props: React.ComponentProps @@ -58,11 +58,12 @@ const mockPipetteOffsetCalibrationsForOt3 = [ }, ] -vi.mock('../../../../redux/custom-labware/selectors') -vi.mock('../../../../redux/sessions/selectors') -vi.mock('../../../../redux/discovery') -vi.mock('../../../../assets/labware/findLabware') -vi.mock('../../../../organisms/Devices/hooks') +vi.mock('/app/redux/custom-labware/selectors') +vi.mock('/app/redux/sessions/selectors') +vi.mock('/app/redux/discovery') +vi.mock('/app/assets/labware/findLabware') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/resources/instruments') vi.mock('../OverflowMenu') const mockAttachedPipettes: AttachedPipettesByMount = { diff --git a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/TipLengthCalibrationItems.test.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/TipLengthCalibrationItems.test.tsx similarity index 86% rename from app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/TipLengthCalibrationItems.test.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/TipLengthCalibrationItems.test.tsx index 39bcde401d0..014ec28ee2c 100644 --- a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/TipLengthCalibrationItems.test.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/TipLengthCalibrationItems.test.tsx @@ -1,19 +1,19 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../../i18n' -import { renderWithProviders } from '../../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { TipLengthCalibrationItems } from '../TipLengthCalibrationItems' import { OverflowMenu } from '../OverflowMenu' import type { Mount } from '@opentrons/components' -vi.mock('../../../../redux/custom-labware/selectors') -vi.mock('../../../../redux/config') -vi.mock('../../../../redux/sessions/selectors') -vi.mock('../../../../redux/discovery') -vi.mock('../../../../assets/labware/findLabware') -vi.mock('../../../../organisms/Devices/hooks') +vi.mock('/app/redux/custom-labware/selectors') +vi.mock('/app/redux/config') +vi.mock('/app/redux/sessions/selectors') +vi.mock('/app/redux/discovery') +vi.mock('/app/assets/labware/findLabware') +vi.mock('/app/organisms/Desktop/Devices/hooks') vi.mock('../OverflowMenu') const ROBOT_NAME = 'otie' diff --git a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/utils.test.ts b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/utils.test.ts similarity index 100% rename from app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__tests__/utils.test.ts rename to app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/__tests__/utils.test.ts diff --git a/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/utils.ts b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/utils.ts new file mode 100644 index 00000000000..d37f12fa490 --- /dev/null +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationDetails/utils.ts @@ -0,0 +1,30 @@ +import { format } from 'date-fns' + +import { getLabwareDisplayName } from '@opentrons/shared-data' +import { findLabwareDefWithCustom } from '/app/assets/labware/findLabware' + +import type { LabwareDefinition2 } from '@opentrons/shared-data' + +const UNKNOWN_CUSTOM_LABWARE = 'unknown custom tiprack' + +export function getDisplayNameForTipRack( + tiprackUri: string, + customLabware: LabwareDefinition2[] +): string { + const [namespace, loadName] = tiprackUri ? tiprackUri.split('/') : ['', ''] + const definition = findLabwareDefWithCustom( + namespace, + loadName, + null, + customLabware + ) + return definition + ? getLabwareDisplayName(definition) + : `${UNKNOWN_CUSTOM_LABWARE}` +} + +export const formatLastCalibrated = (lastModified: string): string => { + return typeof lastModified === 'string' + ? format(new Date(lastModified), 'M/d/yyyy HH:mm:ss') + : 'Unknown' +} diff --git a/app/src/organisms/RobotSettingsCalibration/CalibrationHealthCheck.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationHealthCheck.tsx similarity index 85% rename from app/src/organisms/RobotSettingsCalibration/CalibrationHealthCheck.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationHealthCheck.tsx index 76b15e2f7b9..f61b87c81ce 100644 --- a/app/src/organisms/RobotSettingsCalibration/CalibrationHealthCheck.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/CalibrationHealthCheck.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' @@ -16,29 +16,29 @@ import { useHoverTooltip, } from '@opentrons/components' -import { getTopPortalEl } from '../../App/portal' -import { TertiaryButton } from '../../atoms/buttons' -import { AskForCalibrationBlockModal } from '../../organisms/CalibrateTipLength/AskForCalibrationBlockModal' +import { getTopPortalEl } from '/app/App/portal' +import { TertiaryButton } from '/app/atoms/buttons' +import { AskForCalibrationBlockModal } from '../CalibrateTipLength/AskForCalibrationBlockModal' import { useTrackEvent, ANALYTICS_CALIBRATION_HEALTH_CHECK_BUTTON_CLICKED, -} from '../../redux/analytics' -import * as Calibration from '../../redux/calibration' -import * as Config from '../../redux/config' -import * as Pipettes from '../../redux/pipettes' -import * as Sessions from '../../redux/sessions' +} from '/app/redux/analytics' +import * as Calibration from '/app/redux/calibration' +import * as Config from '/app/redux/config' +import * as Pipettes from '/app/redux/pipettes' +import * as Sessions from '/app/redux/sessions' +import { useRunStatuses } from '/app/resources/runs' import { - useDeckCalibrationStatus, useAttachedPipettes, useAttachedPipetteCalibrations, - useRunStatuses, -} from '../../organisms/Devices/hooks' +} from '/app/resources/instruments' +import { useDeckCalibrationStatus } from '/app/resources/calibration' import type { AttachedPipettesByMount, PipetteCalibrationsByMount, -} from '../../redux/pipettes/types' -import type { DispatchRequestsType } from '../../redux/robot-api' +} from '/app/redux/pipettes/types' +import type { DispatchRequestsType } from '/app/redux/robot-api' interface CalibrationHealthCheckProps { buttonDisabledReason: string | null @@ -74,7 +74,7 @@ export function CalibrationHealthCheck({ placement: TOOLTIP_LEFT, }) - const [showCalBlockModal, setShowCalBlockModal] = React.useState(false) + const [showCalBlockModal, setShowCalBlockModal] = useState(false) const deckCalibrationStatus = useDeckCalibrationStatus(robotName) const attachedPipettes = useAttachedPipettes() diff --git a/app/src/organisms/RobotSettingsCalibration/DeckCalibrationConfirmModal.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/DeckCalibrationConfirmModal.tsx similarity index 98% rename from app/src/organisms/RobotSettingsCalibration/DeckCalibrationConfirmModal.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/DeckCalibrationConfirmModal.tsx index c07cef244ed..215ff113fe6 100644 --- a/app/src/organisms/RobotSettingsCalibration/DeckCalibrationConfirmModal.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/DeckCalibrationConfirmModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, diff --git a/app/src/organisms/RobotSettingsCalibration/RobotSettingsDeckCalibration.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/RobotSettingsDeckCalibration.tsx similarity index 80% rename from app/src/organisms/RobotSettingsCalibration/RobotSettingsDeckCalibration.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/RobotSettingsDeckCalibration.tsx index 208eb995a67..c78e53fc3f6 100644 --- a/app/src/organisms/RobotSettingsCalibration/RobotSettingsDeckCalibration.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/RobotSettingsDeckCalibration.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useRef, useEffect } from 'react' import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' @@ -11,11 +11,12 @@ import { LegacyStyledText, } from '@opentrons/components' -import { formatLastModified } from '../../organisms/CalibrationPanels/utils' -import { useDeckCalibrationData, useRobot } from '../../organisms/Devices/hooks' -import * as RobotApi from '../../redux/robot-api' +import { formatLastModified } from '../CalibrationPanels/utils' +import { useDeckCalibrationData } from '/app/organisms/Desktop/Devices/hooks' +import { useRobot } from '/app/redux-resources/robots' +import * as RobotApi from '/app/redux/robot-api' -import type { State } from '../../redux/types' +import type { State } from '/app/redux/types' interface RobotSettingsDeckCalibrationProps { robotName: string @@ -25,7 +26,7 @@ export function RobotSettingsDeckCalibration({ robotName, }: RobotSettingsDeckCalibrationProps): JSX.Element { const { t } = useTranslation('device_settings') - const createRequestId = React.useRef(null) + const createRequestId = useRef(null) const robot = useRobot(robotName) const deckCalibrationData = useDeckCalibrationData(robot?.name) @@ -46,7 +47,7 @@ export function RobotSettingsDeckCalibration({ }) : t('not_calibrated') - React.useEffect(() => { + useEffect(() => { if (createStatus === RobotApi.SUCCESS) { createRequestId.current = null } diff --git a/app/src/organisms/RobotSettingsCalibration/RobotSettingsGripperCalibration.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/RobotSettingsGripperCalibration.tsx similarity index 94% rename from app/src/organisms/RobotSettingsCalibration/RobotSettingsGripperCalibration.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/RobotSettingsGripperCalibration.tsx index 99c2a85dcb6..90cd9c9f4a8 100644 --- a/app/src/organisms/RobotSettingsCalibration/RobotSettingsGripperCalibration.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/RobotSettingsGripperCalibration.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import styled, { css } from 'styled-components' @@ -11,6 +11,7 @@ import { Flex, LegacyStyledText, MenuItem, + NO_WRAP, OverflowBtn, POSITION_ABSOLUTE, POSITION_RELATIVE, @@ -20,9 +21,9 @@ import { useOnClickOutside, } from '@opentrons/components' -import { GripperWizardFlows } from '../../organisms/GripperWizardFlows' +import { GripperWizardFlows } from '/app/organisms/GripperWizardFlows' import { formatLastCalibrated } from './CalibrationDetails/utils' -import { useIsEstopNotDisengaged } from '../../resources/devices/hooks/useIsEstopNotDisengaged' +import { useIsEstopNotDisengaged } from '/app/resources/devices/hooks/useIsEstopNotDisengaged' import type { GripperData } from '@opentrons/api-client' @@ -70,7 +71,7 @@ export function RobotSettingsGripperCalibration( setShowOverflowMenu(false) }, }) - const [showWizardFlow, setShowWizardFlow] = React.useState(false) + const [showWizardFlow, setShowWizardFlow] = useState(false) const isEstopNotDisengaged = useIsEstopNotDisengaged(robotName) const gripperCalibrationLastModified = @@ -150,7 +151,7 @@ export function RobotSettingsGripperCalibration( {showOverflowMenu ? ( { } }) vi.mock('@opentrons/react-api-client') -vi.mock('../../../redux/analytics') -vi.mock('../../../organisms/Devices/hooks') -vi.mock('../../../resources/devices/hooks/useIsEstopNotDisengaged') +vi.mock('/app/redux/analytics') +vi.mock('/app/organisms/Desktop/Devices/hooks') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/resources/devices/hooks/useIsEstopNotDisengaged') let mockTrackEvent: any const mockSetShowHowCalibrationWorksModal = vi.fn() diff --git a/app/src/organisms/RobotSettingsCalibration/__tests__/CalibrationHealthCheck.test.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/CalibrationHealthCheck.test.tsx similarity index 86% rename from app/src/organisms/RobotSettingsCalibration/__tests__/CalibrationHealthCheck.test.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/CalibrationHealthCheck.test.tsx index 2c2232db5d3..ee965c9d042 100644 --- a/app/src/organisms/RobotSettingsCalibration/__tests__/CalibrationHealthCheck.test.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/CalibrationHealthCheck.test.tsx @@ -1,41 +1,44 @@ -import * as React from 'react' +import type * as React from 'react' import userEvent from '@testing-library/user-event' import { fireEvent, screen, waitFor } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { useTrackEvent, ANALYTICS_CALIBRATION_HEALTH_CHECK_BUTTON_CLICKED, -} from '../../../redux/analytics' +} from '/app/redux/analytics' import { mockPipetteOffsetCalibration1, mockPipetteOffsetCalibration2, -} from '../../../redux/calibration/pipette-offset/__fixtures__' +} from '/app/redux/calibration/pipette-offset/__fixtures__' import { mockTipLengthCalibration1, mockTipLengthCalibration2, -} from '../../../redux/calibration/tip-length/__fixtures__' -import { mockAttachedPipette } from '../../../redux/pipettes/__fixtures__' +} from '/app/redux/calibration/tip-length/__fixtures__' +import { mockAttachedPipette } from '/app/redux/pipettes/__fixtures__' +import { useRunStatuses } from '/app/resources/runs' + import { useAttachedPipettes, useAttachedPipetteCalibrations, - useRunStatuses, -} from '../../../organisms/Devices/hooks' +} from '/app/resources/instruments' import { CalibrationHealthCheck } from '../CalibrationHealthCheck' import type { AttachedPipettesByMount, PipetteCalibrationsByMount, -} from '../../../redux/pipettes/types' - -vi.mock('../../../redux/analytics') -vi.mock('../../../redux/config') -vi.mock('../../../redux/pipettes') -vi.mock('../../../organisms/Devices/hooks') +} from '/app/redux/pipettes/types' + +vi.mock('/app/redux/analytics') +vi.mock('/app/redux/config') +vi.mock('/app/redux/pipettes') +vi.mock('/app/resources/runs') +vi.mock('/app/resources/instruments') +vi.mock('/app/redux-resources/robots') const mockAttachedPipettes: AttachedPipettesByMount = { left: mockAttachedPipette, diff --git a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsCalibration.test.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsCalibration.test.tsx similarity index 87% rename from app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsCalibration.test.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsCalibration.test.tsx index 0a075843352..9d072cf1df0 100644 --- a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsCalibration.test.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsCalibration.test.tsx @@ -1,32 +1,30 @@ -import * as React from 'react' import { when } from 'vitest-when' import { screen } from '@testing-library/react' import { describe, it, expect, beforeEach, vi } from 'vitest' import '@testing-library/jest-dom/vitest' import { useInstrumentsQuery } from '@opentrons/react-api-client' -import { i18n } from '../../../i18n' -import { CalibrationStatusCard } from '../../../organisms/CalibrationStatusCard' -import { useFeatureFlag } from '../../../redux/config' -import * as RobotApi from '../../../redux/robot-api' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { CalibrationStatusCard } from '../..//CalibrationStatusCard' +import { useFeatureFlag } from '/app/redux/config' +import * as RobotApi from '/app/redux/robot-api' +import { renderWithProviders } from '/app/__testing-utils__' import { mockPipetteOffsetCalibration1, mockPipetteOffsetCalibration2, mockPipetteOffsetCalibration3, -} from '../../../redux/calibration/pipette-offset/__fixtures__' -import { mockConnectableRobot } from '../../../redux/discovery/__fixtures__' +} from '/app/redux/calibration/pipette-offset/__fixtures__' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' import { mockAttachedPipette, mockAttachedPipetteInformation, -} from '../../../redux/pipettes/__fixtures__' +} from '/app/redux/pipettes/__fixtures__' +import { usePipetteOffsetCalibrations } from '/app/organisms/Desktop/Devices/hooks' import { - useIsFlex, - usePipetteOffsetCalibrations, - useRobot, useAttachedPipettes, - useRunStatuses, useAttachedPipettesFromInstrumentsQuery, -} from '../../../organisms/Devices/hooks' +} from '/app/resources/instruments' +import { useRobot, useIsFlex } from '/app/redux-resources/robots' +import { useRunStatuses } from '/app/resources/runs' import { CalibrationDataDownload } from '../CalibrationDataDownload' import { CalibrationHealthCheck } from '../CalibrationHealthCheck' @@ -37,7 +35,7 @@ import { RobotSettingsTipLengthCalibration } from '../RobotSettingsTipLengthCali import { RobotSettingsModuleCalibration } from '../RobotSettingsModuleCalibration' import { RobotSettingsCalibration } from '..' import type * as ReactApiClient from '@opentrons/react-api-client' -import type { AttachedPipettesByMount } from '../../../redux/pipettes/types' +import type { AttachedPipettesByMount } from '/app/redux/pipettes/types' vi.mock('@opentrons/react-api-client', async importOriginal => { const actual = await importOriginal() @@ -46,11 +44,14 @@ vi.mock('@opentrons/react-api-client', async importOriginal => { useInstrumentsQuery: vi.fn(), } }) -vi.mock('../../../organisms/CalibrationStatusCard') -vi.mock('../../../redux/config') -vi.mock('../../../redux/sessions/selectors') -vi.mock('../../../redux/robot-api/selectors') -vi.mock('../../../organisms/Devices/hooks') +vi.mock('../../CalibrationStatusCard') +vi.mock('/app/redux/config') +vi.mock('/app/redux/sessions/selectors') +vi.mock('/app/redux/robot-api/selectors') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/resources/instruments') +vi.mock('/app/organisms/Desktop/Devices/hooks') +vi.mock('/app/resources/runs') vi.mock('../CalibrationDataDownload') vi.mock('../CalibrationHealthCheck') vi.mock('../RobotSettingsDeckCalibration') @@ -58,6 +59,7 @@ vi.mock('../RobotSettingsGripperCalibration') vi.mock('../RobotSettingsPipetteOffsetCalibration') vi.mock('../RobotSettingsTipLengthCalibration') vi.mock('../RobotSettingsModuleCalibration') +vi.mock('/app/organisms/Desktop/CalibrationError') const mockAttachedPipettes: AttachedPipettesByMount = { left: mockAttachedPipette, diff --git a/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsDeckCalibration.test.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsDeckCalibration.test.tsx new file mode 100644 index 00000000000..2c46916fc3c --- /dev/null +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsDeckCalibration.test.tsx @@ -0,0 +1,82 @@ +import type * as React from 'react' +import { screen } from '@testing-library/react' +import { describe, it, vi, beforeEach } from 'vitest' + +import { i18n } from '/app/i18n' +import * as RobotApi from '/app/redux/robot-api' +import { + mockDeckCalData, + mockWarningDeckCalData, +} from '/app/redux/calibration/__fixtures__' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' +import { mockAttachedPipette } from '/app/redux/pipettes/__fixtures__' +import { useDeckCalibrationData } from '/app/organisms/Desktop/Devices/hooks' +import { useAttachedPipettes } from '/app/resources/instruments' +import { useRobot } from '/app/redux-resources/robots' +import { renderWithProviders } from '/app/__testing-utils__' + +import { RobotSettingsDeckCalibration } from '../RobotSettingsDeckCalibration' + +import type { AttachedPipettesByMount } from '/app/redux/pipettes/types' + +vi.mock('../..//CalibrationStatusCard') +vi.mock('/app/redux/robot-api/selectors') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/organisms/Desktop/Devices/hooks') +vi.mock('/app/resources/instruments') + +const mockAttachedPipettes: AttachedPipettesByMount = { + left: mockAttachedPipette, + right: mockAttachedPipette, +} as any + +const render = ( + props?: Partial> +) => { + return renderWithProviders( + , + { + i18nInstance: i18n, + } + ) +} +const getRequestById = RobotApi.getRequestById + +describe('RobotSettingsDeckCalibration', () => { + beforeEach(() => { + vi.mocked(useDeckCalibrationData).mockReturnValue({ + deckCalibrationData: mockDeckCalData, + isDeckCalibrated: true, + }) + vi.mocked(useRobot).mockReturnValue(mockConnectableRobot) + vi.mocked(useAttachedPipettes).mockReturnValue(mockAttachedPipettes) + vi.mocked(getRequestById).mockReturnValue(null) + }) + + it('renders a title description and button', () => { + render() + screen.getByText('Deck Calibration') + screen.getByText( + 'Calibrating the deck is required for new robots or after you relocate your robot. Recalibrating the deck will require you to also recalibrate pipette offsets.' + ) + screen.getByText('Last calibrated: September 15, 2021 00:00') + }) + + it('renders empty state if yet not calibrated', () => { + vi.mocked(useDeckCalibrationData).mockReturnValue({ + deckCalibrationData: null, + isDeckCalibrated: false, + }) + render() + screen.getByText('Not calibrated yet') + }) + + it('renders the last calibrated when deck calibration is not good', () => { + vi.mocked(useDeckCalibrationData).mockReturnValue({ + deckCalibrationData: mockWarningDeckCalData, + isDeckCalibrated: true, + }) + render() + screen.getByText('Last calibrated: September 22, 2222 00:00') + }) +}) diff --git a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsGripperCalibration.test.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsGripperCalibration.test.tsx similarity index 89% rename from app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsGripperCalibration.test.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsGripperCalibration.test.tsx index 9d02f3894d5..e4a4d48eea1 100644 --- a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsGripperCalibration.test.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsGripperCalibration.test.tsx @@ -1,20 +1,20 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' -import { GripperWizardFlows } from '../../../organisms/GripperWizardFlows' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { GripperWizardFlows } from '/app/organisms/GripperWizardFlows' import { formatLastCalibrated } from '../CalibrationDetails/utils' -import { useIsEstopNotDisengaged } from '../../../resources/devices/hooks/useIsEstopNotDisengaged' +import { useIsEstopNotDisengaged } from '/app/resources/devices/hooks/useIsEstopNotDisengaged' import { RobotSettingsGripperCalibration } from '../RobotSettingsGripperCalibration' import type { GripperData } from '@opentrons/api-client' -vi.mock('../../../organisms/GripperWizardFlows') +vi.mock('/app/organisms/GripperWizardFlows') vi.mock('../CalibrationDetails/utils') -vi.mock('../../../resources/devices/hooks/useIsEstopNotDisengaged') +vi.mock('/app/resources/devices/hooks/useIsEstopNotDisengaged') const mockGripperData = { serialNumber: 'mockSerial123', diff --git a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsModuleCalibration.test.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsModuleCalibration.test.tsx similarity index 88% rename from app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsModuleCalibration.test.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsModuleCalibration.test.tsx index b3795b939f2..95b71a450af 100644 --- a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsModuleCalibration.test.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsModuleCalibration.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' -import { mockFetchModulesSuccessActionPayloadModules } from '../../../redux/modules/__fixtures__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { mockFetchModulesSuccessActionPayloadModules } from '/app/redux/modules/__fixtures__' import { RobotSettingsModuleCalibration } from '../RobotSettingsModuleCalibration' import { ModuleCalibrationItems } from '../CalibrationDetails/ModuleCalibrationItems' diff --git a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsPipetteOffsetCalibration.test.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsPipetteOffsetCalibration.test.tsx similarity index 80% rename from app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsPipetteOffsetCalibration.test.tsx rename to app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsPipetteOffsetCalibration.test.tsx index 230a26c3fe8..0f628dddfef 100644 --- a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsPipetteOffsetCalibration.test.tsx +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsPipetteOffsetCalibration.test.tsx @@ -1,28 +1,28 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { describe, it, vi, beforeEach } from 'vitest' import { screen } from '@testing-library/react' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { mockPipetteOffsetCalibration1, mockPipetteOffsetCalibration2, mockPipetteOffsetCalibration3, -} from '../../../redux/calibration/pipette-offset/__fixtures__' -import { - useIsFlex, - usePipetteOffsetCalibrations, - useAttachedPipettesFromInstrumentsQuery, -} from '../../../organisms/Devices/hooks' -import { renderWithProviders } from '../../../__testing-utils__' -import { mockAttachedPipetteInformation } from '../../../redux/pipettes/__fixtures__' +} from '/app/redux/calibration/pipette-offset/__fixtures__' +import { usePipetteOffsetCalibrations } from '/app/organisms/Desktop/Devices/hooks' +import { useAttachedPipettesFromInstrumentsQuery } from '/app/resources/instruments' +import { renderWithProviders } from '/app/__testing-utils__' +import { mockAttachedPipetteInformation } from '/app/redux/pipettes/__fixtures__' +import { useIsFlex } from '/app/redux-resources/robots' import { RobotSettingsPipetteOffsetCalibration } from '../RobotSettingsPipetteOffsetCalibration' import { PipetteOffsetCalibrationItems } from '../CalibrationDetails/PipetteOffsetCalibrationItems' import type { FormattedPipetteOffsetCalibration } from '..' -vi.mock('../../../organisms/Devices/hooks') +vi.mock('/app/organisms/Desktop/Devices/hooks') +vi.mock('/app/resources/instruments') +vi.mock('/app/redux-resources/robots') vi.mock('../CalibrationDetails/PipetteOffsetCalibrationItems') const mockFormattedPipetteOffsetCalibrations: FormattedPipetteOffsetCalibration[] = [] diff --git a/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsTipLengthCalibration.test.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsTipLengthCalibration.test.tsx new file mode 100644 index 00000000000..d9b229c0f4e --- /dev/null +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/__tests__/RobotSettingsTipLengthCalibration.test.tsx @@ -0,0 +1,67 @@ +import { screen } from '@testing-library/react' +import { describe, it, beforeEach, vi } from 'vitest' +import { i18n } from '/app/i18n' +import { useFeatureFlag } from '/app/redux/config' +import { renderWithProviders } from '/app/__testing-utils__' +import { + mockTipLengthCalibration1, + mockTipLengthCalibration2, + mockTipLengthCalibration3, +} from '/app/redux/calibration/tip-length/__fixtures__' +import { mockAttachedPipette } from '/app/redux/pipettes/__fixtures__' +import { useTipLengthCalibrations } from '/app/organisms/Desktop/Devices/hooks' +import { useAttachedPipettes } from '/app/resources/instruments' + +import { RobotSettingsTipLengthCalibration } from '../RobotSettingsTipLengthCalibration' +import { TipLengthCalibrationItems } from '../CalibrationDetails/TipLengthCalibrationItems' + +import type { FormattedPipetteOffsetCalibration } from '..' +import type { AttachedPipettesByMount } from '/app/redux/pipettes/types' + +vi.mock('/app/redux/config') +vi.mock('/app/organisms/Desktop/Devices/hooks') +vi.mock('../CalibrationDetails/TipLengthCalibrationItems') +vi.mock('/app/resources/instruments') + +const mockFormattedPipetteOffsetCalibrations: FormattedPipetteOffsetCalibration[] = [] + +const mockUpdateRobotStatus = vi.fn() + +const render = () => { + return renderWithProviders( + , + { + i18nInstance: i18n, + } + ) +} + +describe('RobotSettingsTipLengthCalibration', () => { + beforeEach(() => { + vi.mocked(useTipLengthCalibrations).mockReturnValue([ + mockTipLengthCalibration1, + mockTipLengthCalibration2, + mockTipLengthCalibration3, + ]) + vi.mocked(TipLengthCalibrationItems).mockReturnValue( +
    Mock TipLengthCalibrationItems
    + ) + vi.mocked(useFeatureFlag).mockReturnValue(false) + vi.mocked(useAttachedPipettes).mockReturnValue({ + left: mockAttachedPipette, + right: null, + } as AttachedPipettesByMount) + }) + + it('renders a title', () => { + render() + screen.getByText('Tip Length Calibrations') + screen.getByText('Mock TipLengthCalibrationItems') + }) +}) diff --git a/app/src/organisms/Desktop/RobotSettingsCalibration/index.tsx b/app/src/organisms/Desktop/RobotSettingsCalibration/index.tsx new file mode 100644 index 00000000000..7516a262172 --- /dev/null +++ b/app/src/organisms/Desktop/RobotSettingsCalibration/index.tsx @@ -0,0 +1,389 @@ +import { useRef, useState, useEffect } from 'react' +import { createPortal } from 'react-dom' +import { useSelector, useDispatch } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { + AlertModal, + SPACING, + SpinnerModalPage, + LegacyStyledText, +} from '@opentrons/components' +import { + useAllPipetteOffsetCalibrationsQuery, + useAllTipLengthCalibrationsQuery, + useCalibrationStatusQuery, + useInstrumentsQuery, + useModulesQuery, +} from '@opentrons/react-api-client' + +import { getTopPortalEl } from '/app/App/portal' +import { Line } from '/app/atoms/structure' +import { CalibrateDeck } from '../CalibrateDeck' +import { CalibrationStatusCard } from '../CalibrationStatusCard' +import { CheckCalibration } from '../CheckCalibration' +import { useRunStatuses } from '/app/resources/runs' +import { useAttachedPipettesFromInstrumentsQuery } from '/app/resources/instruments' +import { useRobot, useIsFlex } from '/app/redux-resources/robots' +import { HowCalibrationWorksModal } from '../HowCalibrationWorksModal' +import { CONNECTABLE } from '/app/redux/discovery' +import * as RobotApi from '/app/redux/robot-api' +import { getDeckCalibrationSession } from '/app/redux/sessions/deck-calibration/selectors' +import * as Sessions from '/app/redux/sessions' +import { CalibrationDataDownload } from './CalibrationDataDownload' +import { CalibrationHealthCheck } from './CalibrationHealthCheck' +import { RobotSettingsDeckCalibration } from './RobotSettingsDeckCalibration' +import { RobotSettingsGripperCalibration } from './RobotSettingsGripperCalibration' +import { RobotSettingsPipetteOffsetCalibration } from './RobotSettingsPipetteOffsetCalibration' +import { RobotSettingsTipLengthCalibration } from './RobotSettingsTipLengthCalibration' +import { RobotSettingsModuleCalibration } from './RobotSettingsModuleCalibration' + +import type { GripperData } from '@opentrons/api-client' +import type { Mount } from '@opentrons/components' +import type { RequestState } from '/app/redux/robot-api/types' +import type { + SessionCommandString, + DeckCalibrationSession, +} from '/app/redux/sessions/types' +import type { State, Dispatch } from '/app/redux/types' + +const CALS_FETCH_MS = 5000 + +interface CalibrationProps { + robotName: string + updateRobotStatus: (isRobotBusy: boolean) => void +} + +export interface FormattedPipetteOffsetCalibration { + modelName?: string + serialNumber?: string + mount: Mount + tiprack?: string + lastCalibrated?: string + markedBad?: boolean +} + +const spinnerCommandBlockList: SessionCommandString[] = [ + Sessions.sharedCalCommands.JOG, +] + +export function RobotSettingsCalibration({ + robotName, + updateRobotStatus, +}: CalibrationProps): JSX.Element { + const { t } = useTranslation([ + 'device_settings', + 'robot_calibration', + 'shared', + ]) + const trackedRequestId = useRef(null) + const createRequestId = useRef(null) + const jogRequestId = useRef(null) + + const [ + showHowCalibrationWorksModal, + setShowHowCalibrationWorksModal, + ] = useState(false) + + const robot = useRobot(robotName) + const notConnectable = robot?.status !== CONNECTABLE + const isFlex = useIsFlex(robotName) + const dispatch = useDispatch() + + useEffect(() => { + dispatch(Sessions.fetchAllSessions(robotName)) + }, [dispatch, robotName]) + + const [dispatchRequests, requestIds] = RobotApi.useDispatchApiRequests( + dispatchedAction => { + if (dispatchedAction.type === Sessions.ENSURE_SESSION) { + createRequestId.current = + 'requestId' in dispatchedAction.meta + ? dispatchedAction.meta.requestId ?? null + : null + } else if ( + dispatchedAction.type === Sessions.CREATE_SESSION_COMMAND && + dispatchedAction.payload.command.command === + Sessions.sharedCalCommands.JOG + ) { + jogRequestId.current = + 'requestId' in dispatchedAction.meta + ? dispatchedAction.meta.requestId ?? null + : null + } else if ( + dispatchedAction.type !== Sessions.CREATE_SESSION_COMMAND || + !spinnerCommandBlockList.includes( + dispatchedAction.payload.command.command + ) + ) { + trackedRequestId.current = + 'meta' in dispatchedAction && 'requestId' in dispatchedAction.meta + ? dispatchedAction.meta.requestId ?? null + : null + } + } + ) + + // Modules Calibration + const attachedModules = + useModulesQuery({ + refetchInterval: CALS_FETCH_MS, + })?.data?.data ?? [] + + // Note: following fetch need to reflect the latest state of calibrations + // when a user does calibration or rename a robot. + useCalibrationStatusQuery({ refetchInterval: CALS_FETCH_MS }) + useAllTipLengthCalibrationsQuery({ refetchInterval: CALS_FETCH_MS }) + const pipetteOffsetCalibrations = + useAllPipetteOffsetCalibrationsQuery({ refetchInterval: CALS_FETCH_MS }) + .data?.data ?? [] + const attachedInstruments = + useInstrumentsQuery({ refetchInterval: CALS_FETCH_MS }).data?.data ?? [] + const attachedGripper = + (attachedInstruments ?? []).find( + (i): i is GripperData => i.instrumentType === 'gripper' && i.ok + ) ?? null + const attachedPipettes = useAttachedPipettesFromInstrumentsQuery() + const { isRunRunning: isRunning } = useRunStatuses() + const pipettePresent = + !(attachedPipettes.left == null) || !(attachedPipettes.right == null) + + const isPending = + useSelector(state => + trackedRequestId.current != null + ? RobotApi.getRequestById(state, trackedRequestId.current) + : null + )?.status === RobotApi.PENDING + + const createRequest = useSelector((state: State) => + createRequestId.current != null + ? RobotApi.getRequestById(state, createRequestId.current) + : null + ) + + const createStatus = createRequest?.status + + const isJogging = + useSelector((state: State) => + jogRequestId.current != null + ? RobotApi.getRequestById(state, jogRequestId.current) + : null + )?.status === RobotApi.PENDING + + const deckCalibrationSession: DeckCalibrationSession | null = useSelector( + (state: State) => { + return getDeckCalibrationSession(state, robotName) + } + ) + + let buttonDisabledReason: string | null = null + if (notConnectable) { + buttonDisabledReason = t('shared:disabled_cannot_connect') + } else if (isRunning) { + buttonDisabledReason = t('shared:disabled_protocol_is_running') + } else if (!pipettePresent) { + buttonDisabledReason = t('shared:disabled_no_pipette_attached') + } + const checkHealthSession = useSelector((state: State) => { + const session: Sessions.Session | null = Sessions.getRobotSessionOfType( + state, + robotName, + Sessions.SESSION_TYPE_CALIBRATION_HEALTH_CHECK + ) + if ( + session != null && + session.sessionType === Sessions.SESSION_TYPE_CALIBRATION_HEALTH_CHECK + ) { + // TODO: add this analytics event when we deprecate this event firing in redux/analytics makeEvent + return session + } + return null + }) + + const formattedPipetteOffsetCalibrations: FormattedPipetteOffsetCalibration[] = [] + + if (!isFlex && attachedPipettes != null) { + formattedPipetteOffsetCalibrations.push({ + modelName: attachedPipettes.left?.displayName, + serialNumber: attachedPipettes.left?.serialNumber, + mount: 'left' as Mount, + tiprack: pipetteOffsetCalibrations?.find( + p => p.pipette === attachedPipettes.left?.serialNumber + )?.tiprackUri, + lastCalibrated: pipetteOffsetCalibrations?.find( + p => p.pipette === attachedPipettes.left?.serialNumber + )?.lastModified, + markedBad: pipetteOffsetCalibrations?.find( + p => p.pipette === attachedPipettes.left?.serialNumber + )?.status.markedBad, + }) + formattedPipetteOffsetCalibrations.push({ + modelName: attachedPipettes.right?.displayName, + serialNumber: attachedPipettes.right?.serialNumber, + mount: 'right' as Mount, + tiprack: pipetteOffsetCalibrations?.find( + p => p.pipette === attachedPipettes.right?.serialNumber + )?.tiprackUri, + lastCalibrated: pipetteOffsetCalibrations?.find( + p => p.pipette === attachedPipettes.right?.serialNumber + )?.lastModified, + markedBad: pipetteOffsetCalibrations?.find( + p => p.pipette === attachedPipettes.right?.serialNumber + )?.status.markedBad, + }) + } else { + formattedPipetteOffsetCalibrations.push({ + modelName: attachedPipettes.left?.displayName, + serialNumber: attachedPipettes.left?.serialNumber, + mount: 'left' as Mount, + lastCalibrated: + attachedPipettes.left?.data.calibratedOffset?.last_modified, + }) + formattedPipetteOffsetCalibrations.push({ + modelName: attachedPipettes.right?.displayName, + serialNumber: attachedPipettes.right?.serialNumber, + mount: 'right' as Mount, + lastCalibrated: + attachedPipettes.right?.data.calibratedOffset?.last_modified, + }) + } + + useEffect(() => { + if (createStatus === RobotApi.SUCCESS) { + createRequestId.current = null + } + }, [createStatus]) + + return ( + <> + {createPortal( + <> + + {createStatus === RobotApi.PENDING ? ( + + ) : null} + + {createStatus === RobotApi.FAILURE && ( + { + createRequestId.current != null && + dispatch(RobotApi.dismissRequest(createRequestId.current)) + createRequestId.current = null + }, + }, + ]} + > + + {t('deck_calibration_error_occurred')} + + + {createRequest != null && + 'error' in createRequest && + createRequest.error != null && + RobotApi.getErrorResponseMessage(createRequest.error)} + + + )} + , + getTopPortalEl() + )} + {showHowCalibrationWorksModal ? ( + { + setShowHowCalibrationWorksModal(false) + }} + /> + ) : null} + {isFlex ? ( + <> + + + + + + + + + ) : ( + <> + + + + + + + + + + + + )} + + ) +} diff --git a/app/src/organisms/RunProgressMeter/InterventionTicks.tsx b/app/src/organisms/Desktop/RunProgressMeter/InterventionTicks.tsx similarity index 98% rename from app/src/organisms/RunProgressMeter/InterventionTicks.tsx rename to app/src/organisms/Desktop/RunProgressMeter/InterventionTicks.tsx index f8dfd722f17..8bab7b981af 100644 --- a/app/src/organisms/RunProgressMeter/InterventionTicks.tsx +++ b/app/src/organisms/Desktop/RunProgressMeter/InterventionTicks.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import last from 'lodash/last' import { Tick } from './Tick' import type { RunTimeCommand } from '@opentrons/shared-data' diff --git a/app/src/organisms/RunProgressMeter/Tick.tsx b/app/src/organisms/Desktop/RunProgressMeter/Tick.tsx similarity index 97% rename from app/src/organisms/RunProgressMeter/Tick.tsx rename to app/src/organisms/Desktop/RunProgressMeter/Tick.tsx index 5bb644f87b8..ddb3d0a7c79 100644 --- a/app/src/organisms/RunProgressMeter/Tick.tsx +++ b/app/src/organisms/Desktop/RunProgressMeter/Tick.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom' @@ -15,7 +14,7 @@ import { useHoverTooltip, } from '@opentrons/components' -import { getModalPortalEl } from '../../App/portal' +import { getModalPortalEl } from '/app/App/portal' import type { IconName } from '@opentrons/components' import type { RunTimeCommand } from '@opentrons/shared-data' diff --git a/app/src/organisms/RunProgressMeter/__fixtures__/index.ts b/app/src/organisms/Desktop/RunProgressMeter/__fixtures__/index.ts similarity index 100% rename from app/src/organisms/RunProgressMeter/__fixtures__/index.ts rename to app/src/organisms/Desktop/RunProgressMeter/__fixtures__/index.ts diff --git a/app/src/organisms/RunProgressMeter/__tests__/InterventionTicks.test.tsx b/app/src/organisms/Desktop/RunProgressMeter/__tests__/InterventionTicks.test.tsx similarity index 96% rename from app/src/organisms/RunProgressMeter/__tests__/InterventionTicks.test.tsx rename to app/src/organisms/Desktop/RunProgressMeter/__tests__/InterventionTicks.test.tsx index 60f6fb9ecce..c6562476818 100644 --- a/app/src/organisms/RunProgressMeter/__tests__/InterventionTicks.test.tsx +++ b/app/src/organisms/Desktop/RunProgressMeter/__tests__/InterventionTicks.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { InterventionTicks } from '../InterventionTicks' import { Tick } from '../Tick' diff --git a/app/src/organisms/RunProgressMeter/__tests__/RunProgressMeter.test.tsx b/app/src/organisms/Desktop/RunProgressMeter/__tests__/RunProgressMeter.test.tsx similarity index 81% rename from app/src/organisms/RunProgressMeter/__tests__/RunProgressMeter.test.tsx rename to app/src/organisms/Desktop/RunProgressMeter/__tests__/RunProgressMeter.test.tsx index 10ecdb7bf9e..928bd2572a9 100644 --- a/app/src/organisms/RunProgressMeter/__tests__/RunProgressMeter.test.tsx +++ b/app/src/organisms/Desktop/RunProgressMeter/__tests__/RunProgressMeter.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' @@ -12,18 +12,20 @@ import { RUN_STATUS_STOPPED, } from '@opentrons/api-client' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { useInterventionModal, InterventionModal, -} from '../../InterventionModal' -import { ProgressBar } from '../../../atoms/ProgressBar' -import { useRunStatus } from '../../RunTimeControl/hooks' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' +} from '/app/organisms/InterventionModal' +import { ProgressBar } from '/app/atoms/ProgressBar' +import { useRunControls } from '/app/organisms/RunTimeControl' import { useNotifyRunQuery, useNotifyAllCommandsQuery, -} from '../../../resources/runs' + useRunStatus, + useMostRecentCompletedAnalysis, + useLastRunCommand, +} from '/app/resources/runs' import { useDownloadRunLog } from '../../Devices/hooks' import { mockUseAllCommandsResponseNonDeterministic, @@ -32,9 +34,8 @@ import { } from '../__fixtures__' import { RunProgressMeter } from '..' -import { renderWithProviders } from '../../../__testing-utils__' -import { useLastRunCommand } from '../../Devices/hooks/useLastRunCommand' -import { useRunningStepCounts } from '../../../resources/protocols/hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { useRunningStepCounts } from '/app/resources/protocols/hooks' import type { RunCommandSummary } from '@opentrons/api-client' import type * as ApiClient from '@opentrons/react-api-client' @@ -46,14 +47,13 @@ vi.mock('@opentrons/react-api-client', async importOriginal => { useCommandQuery: vi.fn(), } }) -vi.mock('../../RunTimeControl/hooks') -vi.mock('../../LabwarePositionCheck/useMostRecentCompletedAnalysis') -vi.mock('../../../resources/runs') +vi.mock('/app/organisms/RunTimeControl') +vi.mock('/app/resources/runs') +vi.mock('/app/atoms/ProgressBar') +vi.mock('/app/organisms/InterventionModal') vi.mock('../../Devices/hooks') -vi.mock('../../../atoms/ProgressBar') -vi.mock('../../InterventionModal') -vi.mock('../../Devices/hooks/useLastRunCommand') -vi.mock('../../../resources/protocols/hooks') +vi.mock('/app/resources/protocols/hooks') +vi.mock('/app/redux-resources/robots') const render = (props: React.ComponentProps) => { return renderWithProviders(, { @@ -76,7 +76,9 @@ describe('RunProgressMeter', () => { .calledWith(NON_DETERMINISTIC_RUN_ID) .thenReturn(null) when(useNotifyAllCommandsQuery) - .calledWith(NON_DETERMINISTIC_RUN_ID, { cursor: null, pageLength: 1 }) + .calledWith(NON_DETERMINISTIC_RUN_ID, { + pageLength: 1, + }) .thenReturn(mockUseAllCommandsResponseNonDeterministic) when(useCommandQuery) .calledWith(NON_DETERMINISTIC_RUN_ID, NON_DETERMINISTIC_COMMAND_KEY) @@ -99,19 +101,19 @@ describe('RunProgressMeter', () => { showModal: false, modalProps: {} as any, }) + vi.mocked(useRunControls).mockReturnValue({ play: vi.fn() } as any) props = { runId: NON_DETERMINISTIC_RUN_ID, robotName: ROBOT_NAME, makeHandleJumpToStep: vi.fn(), - resumeRunHandler: vi.fn(), } }) it('should show only the total count of commands in run and not show the meter when protocol is non-deterministic', () => { vi.mocked(useCommandQuery).mockReturnValue({ data: null } as any) render(props) - expect(screen.getByText('Current Step ?/?:')).toBeTruthy() + expect(screen.getByText('Current Step N/A:')).toBeTruthy() expect(screen.queryByText('MOCK PROGRESS BAR')).toBeFalsy() }) it('should give the correct info when run status is idle', () => { diff --git a/app/src/organisms/RunProgressMeter/constants.ts b/app/src/organisms/Desktop/RunProgressMeter/constants.ts similarity index 100% rename from app/src/organisms/RunProgressMeter/constants.ts rename to app/src/organisms/Desktop/RunProgressMeter/constants.ts diff --git a/app/src/organisms/RunProgressMeter/hooks/index.ts b/app/src/organisms/Desktop/RunProgressMeter/hooks/index.ts similarity index 100% rename from app/src/organisms/RunProgressMeter/hooks/index.ts rename to app/src/organisms/Desktop/RunProgressMeter/hooks/index.ts diff --git a/app/src/organisms/RunProgressMeter/hooks/useRunProgressCopy.tsx b/app/src/organisms/Desktop/RunProgressMeter/hooks/useRunProgressCopy.tsx similarity index 82% rename from app/src/organisms/RunProgressMeter/hooks/useRunProgressCopy.tsx rename to app/src/organisms/Desktop/RunProgressMeter/hooks/useRunProgressCopy.tsx index 9484a785f82..65e2f27d6b3 100644 --- a/app/src/organisms/RunProgressMeter/hooks/useRunProgressCopy.tsx +++ b/app/src/organisms/Desktop/RunProgressMeter/hooks/useRunProgressCopy.tsx @@ -1,12 +1,16 @@ +import { useMemo } from 'react' + import { RUN_STATUS_BLOCKED_BY_OPEN_DOOR, RUN_STATUS_IDLE, } from '@opentrons/api-client' -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' -import { getCommandTextData } from '../../../molecules/Command/utils/getCommandTextData' + +import { getCommandTextData } from '/app/local-resources/commands' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' import { LegacyStyledText } from '@opentrons/components' -import { CommandText } from '../../../molecules/Command' +import { CommandText } from '/app/molecules/Command' import { TERMINAL_RUN_STATUSES } from '../constants' import type { CommandDetail, RunStatus } from '@opentrons/api-client' @@ -52,6 +56,15 @@ export function useRunProgressCopy({ runStatus === RUN_STATUS_BLOCKED_BY_OPEN_DOOR) || runStatus === RUN_STATUS_IDLE + const isValidRobotSideAnalysis = analysis != null + const allRunDefs = useMemo( + () => + analysis != null + ? getLabwareDefinitionsFromCommands(analysis.commands) + : [], + [isValidRobotSideAnalysis] + ) + const currentStepContents = ((): JSX.Element | null => { if (runHasNotBeenStarted) { return {t('not_started_yet')} @@ -61,6 +74,7 @@ export function useRunProgressCopy({ commandTextData={getCommandTextData(analysis)} command={analysisCommands[(currentStepNumber as number) - 1]} robotType={robotType} + allRunDefs={allRunDefs} /> ) } else if ( @@ -73,6 +87,7 @@ export function useRunProgressCopy({ commandTextData={getCommandTextData(analysis)} command={runCommandDetails.data} robotType={robotType} + allRunDefs={allRunDefs} /> ) } else { @@ -94,7 +109,9 @@ export function useRunProgressCopy({ if (runStatus === RUN_STATUS_IDLE) { return `${stepType}:` } else if (isTerminalStatus && currentStepNumber == null) { - return `${stepType}: N/A` + return `${stepType}: ${t('na')}` + } else if (hasRunDiverged) { + return `${stepType} ${t('na')}:` } else { const getCountString = (): string => { const current = currentStepNumber ?? '?' diff --git a/app/src/organisms/Desktop/RunProgressMeter/index.tsx b/app/src/organisms/Desktop/RunProgressMeter/index.tsx new file mode 100644 index 00000000000..fb158f5d686 --- /dev/null +++ b/app/src/organisms/Desktop/RunProgressMeter/index.tsx @@ -0,0 +1,200 @@ +import type * as React from 'react' +import { createPortal } from 'react-dom' +import { useTranslation } from 'react-i18next' +import { css } from 'styled-components' + +import { + ALIGN_CENTER, + BORDERS, + COLORS, + CURSOR_DEFAULT, + CURSOR_POINTER, + DIRECTION_COLUMN, + Flex, + Icon, + JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, + Link, + SIZE_1, + SPACING, + TOOLTIP_LEFT, + Tooltip, + TYPOGRAPHY, + useHoverTooltip, +} from '@opentrons/components' +import { useCommandQuery } from '@opentrons/react-api-client' +import { + RUN_STATUS_IDLE, + RUN_STATUS_FINISHING, + RUN_STATUS_RUNNING, + RUN_STATUS_BLOCKED_BY_OPEN_DOOR, +} from '@opentrons/api-client' + +import { getModalPortalEl } from '/app/App/portal' +import { useRunControls } from '/app/organisms/RunTimeControl' +import { + InterventionModal, + useInterventionModal, +} from '/app/organisms/InterventionModal' +import { ProgressBar } from '/app/atoms/ProgressBar' +import { useDownloadRunLog } from '../Devices/hooks' +import { InterventionTicks } from './InterventionTicks' +import { + useNotifyRunQuery, + useNotifyAllCommandsQuery, + useRunStatus, + useMostRecentCompletedAnalysis, +} from '/app/resources/runs' +import { useRobotType } from '/app/redux-resources/robots' +import { useRunningStepCounts } from '/app/resources/protocols/hooks' +import { useRunProgressCopy } from './hooks' + +interface RunProgressMeterProps { + runId: string + robotName: string + makeHandleJumpToStep: (index: number) => () => void +} +export function RunProgressMeter(props: RunProgressMeterProps): JSX.Element { + const { runId, robotName, makeHandleJumpToStep } = props + const { t } = useTranslation('run_details') + const robotType = useRobotType(robotName) + const runStatus = useRunStatus(runId) + const { play } = useRunControls(runId) + const [targetProps, tooltipProps] = useHoverTooltip({ + placement: TOOLTIP_LEFT, + }) + const { data: runRecord } = useNotifyRunQuery(runId) + const runData = runRecord?.data ?? null + + const { data: mostRecentCommandData } = useNotifyAllCommandsQuery(runId, { + pageLength: 1, + }) + // This lastRunCommand also includes "fixit" commands. + const lastRunCommand = mostRecentCommandData?.data[0] ?? null + const { data: runCommandDetails } = useCommandQuery( + runId, + lastRunCommand?.id ?? null + ) + + const analysis = useMostRecentCompletedAnalysis(runId) + const analysisCommands = analysis?.commands ?? [] + + const { + currentStepNumber, + totalStepCount, + hasRunDiverged, + } = useRunningStepCounts(runId, mostRecentCommandData) + + const downloadIsDisabled = + runStatus === RUN_STATUS_RUNNING || + runStatus === RUN_STATUS_IDLE || + runStatus === RUN_STATUS_FINISHING + + const { downloadRunLog } = useDownloadRunLog(robotName, runId) + + const onDownloadClick: React.MouseEventHandler = e => { + if (downloadIsDisabled) return false + e.preventDefault() + e.stopPropagation() + downloadRunLog() + } + const { + showModal: showIntervention, + modalProps: interventionProps, + } = useInterventionModal({ + robotName, + runStatus, + runData, + analysis, + lastRunCommand, + doorIsOpen: runStatus === RUN_STATUS_BLOCKED_BY_OPEN_DOOR, + }) + + const { + progressPercentage, + stepCountStr, + currentStepContents, + } = useRunProgressCopy({ + runStatus, + robotType, + currentStepNumber, + totalStepCount, + analysis, + analysisCommands, + runCommandDetails: runCommandDetails ?? null, + hasRunDiverged, + }) + + return ( + <> + {showIntervention + ? createPortal( + , + getModalPortalEl() + ) + : null} + + + + + {stepCountStr} + + + {currentStepContents} + + + + + {t('download_run_log')} + + + {downloadIsDisabled ? ( + + {t('complete_protocol_to_download')} + + ) : null} + + {!hasRunDiverged ? ( + + + + ) : null} + + + ) +} diff --git a/app/src/organisms/SendProtocolToFlexSlideout/__tests__/SendProtocolToFlexSlideout.test.tsx b/app/src/organisms/Desktop/SendProtocolToFlexSlideout/__tests__/SendProtocolToFlexSlideout.test.tsx similarity index 83% rename from app/src/organisms/SendProtocolToFlexSlideout/__tests__/SendProtocolToFlexSlideout.test.tsx rename to app/src/organisms/Desktop/SendProtocolToFlexSlideout/__tests__/SendProtocolToFlexSlideout.test.tsx index cd3ab48d411..5ed8f96fb1a 100644 --- a/app/src/organisms/SendProtocolToFlexSlideout/__tests__/SendProtocolToFlexSlideout.test.tsx +++ b/app/src/organisms/Desktop/SendProtocolToFlexSlideout/__tests__/SendProtocolToFlexSlideout.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' @@ -7,12 +7,12 @@ import { MemoryRouter } from 'react-router-dom' import { mockOT3HealthResponse, mockOT3ServerHealthResponse, -} from '../../../../../discovery-client/src/fixtures' +} from '../../../../../../discovery-client/src/fixtures' import { useCreateProtocolMutation } from '@opentrons/react-api-client' -import { mockSuccessQueryResults } from '../../../__fixtures__' -import { i18n } from '../../../i18n' -import { useToaster } from '../../../organisms/ToasterOven' +import { mockSuccessQueryResults } from '/app/__fixtures__' +import { i18n } from '/app/i18n' +import { useToaster } from '/app/organisms/ToasterOven' import { getConnectableRobots, getReachableRobots, @@ -21,20 +21,20 @@ import { startDiscovery, ROBOT_MODEL_OT2, ROBOT_MODEL_OT3, -} from '../../../redux/discovery' -import { getValidCustomLabwareFiles } from '../../../redux/custom-labware' -import { renderWithProviders } from '../../../__testing-utils__' -import { getRobotUpdateDisplayInfo } from '../../../redux/robot-update' +} from '/app/redux/discovery' +import { getValidCustomLabwareFiles } from '/app/redux/custom-labware' +import { renderWithProviders } from '/app/__testing-utils__' +import { useIsRobotOnWrongVersionOfSoftware } from '/app/redux/robot-update' import { mockConnectableRobot, mockReachableRobot, mockUnreachableRobot, -} from '../../../redux/discovery/__fixtures__' -import { getNetworkInterfaces } from '../../../redux/networking' -import { getIsProtocolAnalysisInProgress } from '../../../redux/protocol-storage/selectors' -import { storedProtocolData as storedProtocolDataFixture } from '../../../redux/protocol-storage/__fixtures__' +} from '/app/redux/discovery/__fixtures__' +import { getNetworkInterfaces } from '/app/redux/networking' +import { getIsProtocolAnalysisInProgress } from '/app/redux/protocol-storage/selectors' +import { storedProtocolData as storedProtocolDataFixture } from '/app/redux/protocol-storage/__fixtures__' import { SendProtocolToFlexSlideout } from '..' -import { useNotifyAllRunsQuery } from '../../../resources/runs' +import { useNotifyAllRunsQuery } from '/app/resources/runs' import type * as ApiClient from '@opentrons/react-api-client' @@ -45,13 +45,13 @@ vi.mock('@opentrons/react-api-client', async importOriginal => { useCreateProtocolMutation: vi.fn(), } }) -vi.mock('../../../organisms/ToasterOven') -vi.mock('../../../redux/robot-update') -vi.mock('../../../redux/discovery') -vi.mock('../../../redux/networking') -vi.mock('../../../redux/custom-labware') -vi.mock('../../../redux/protocol-storage/selectors') -vi.mock('../../../resources/runs') +vi.mock('/app/organisms/ToasterOven') +vi.mock('/app/redux/robot-update') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/networking') +vi.mock('/app/redux/custom-labware') +vi.mock('/app/redux/protocol-storage/selectors') +vi.mock('/app/resources/runs') const render = ( props: React.ComponentProps @@ -93,11 +93,7 @@ const mockCustomLabwareFile: File = { path: 'fake_custom_labware_path' } as any describe('SendProtocolToFlexSlideout', () => { beforeEach(() => { - vi.mocked(getRobotUpdateDisplayInfo).mockReturnValue({ - autoUpdateAction: '', - autoUpdateDisabledReason: null, - updateFromFileDisabledReason: null, - }) + vi.mocked(useIsRobotOnWrongVersionOfSoftware).mockReturnValue(false) vi.mocked(getConnectableRobots).mockReturnValue([mockConnectableOT3]) vi.mocked(getUnreachableRobots).mockReturnValue([mockUnreachableOT3]) vi.mocked(getReachableRobots).mockReturnValue([mockReachableOT3]) @@ -233,11 +229,7 @@ describe('SendProtocolToFlexSlideout', () => { }) }) it('if selected robot is on a different version of the software than the app, disable CTA and show link to device details in options', () => { - vi.mocked(getRobotUpdateDisplayInfo).mockReturnValue({ - autoUpdateAction: 'upgrade', - autoUpdateDisabledReason: null, - updateFromFileDisabledReason: null, - }) + vi.mocked(useIsRobotOnWrongVersionOfSoftware).mockReturnValue(true) render({ storedProtocolData: storedProtocolDataFixture, onCloseClick: vi.fn(), diff --git a/app/src/organisms/Desktop/SendProtocolToFlexSlideout/index.tsx b/app/src/organisms/Desktop/SendProtocolToFlexSlideout/index.tsx new file mode 100644 index 00000000000..0326b750ce4 --- /dev/null +++ b/app/src/organisms/Desktop/SendProtocolToFlexSlideout/index.tsx @@ -0,0 +1,173 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' + +import { useCreateProtocolMutation } from '@opentrons/react-api-client' + +import { FLEX_DISPLAY_NAME, FLEX_ROBOT_TYPE } from '@opentrons/shared-data' + +import { + PrimaryButton, + ERROR_TOAST, + INFO_TOAST, + SUCCESS_TOAST, +} from '@opentrons/components' +import { ChooseRobotSlideout } from '../ChooseRobotSlideout' +import { getAnalysisStatus } from '/app/transformations/analysis' +import { getProtocolDisplayName } from '/app/transformations/protocols' +import { useToaster } from '/app/organisms/ToasterOven' +import { appShellRequestor } from '/app/redux/shell/remote' +import { OPENTRONS_USB } from '/app/redux/discovery' +import { getIsProtocolAnalysisInProgress } from '/app/redux/protocol-storage' +import { useIsRobotOnWrongVersionOfSoftware } from '/app/redux/robot-update' +import { getValidCustomLabwareFiles } from '/app/redux/custom-labware' + +import type { AxiosError } from 'axios' +import type { IconProps, StyleProps } from '@opentrons/components' +import type { Robot } from '/app/redux/discovery/types' +import type { StoredProtocolData } from '/app/redux/protocol-storage' +import type { State } from '/app/redux/types' + +const _getFileBaseName = (filePath: string): string => { + return filePath.split('/').reverse()[0] +} + +interface SendProtocolToFlexSlideoutProps extends StyleProps { + storedProtocolData: StoredProtocolData + onCloseClick: () => void + isExpanded: boolean +} + +export function SendProtocolToFlexSlideout( + props: SendProtocolToFlexSlideoutProps +): JSX.Element | null { + const { isExpanded, onCloseClick, storedProtocolData } = props + const { + protocolKey, + srcFileNames, + srcFiles, + mostRecentAnalysis, + } = storedProtocolData + const { t } = useTranslation(['protocol_details', 'protocol_list']) + + const [selectedRobot, setSelectedRobot] = useState(null) + + const isSelectedRobotOnDifferentSoftwareVersion = useIsRobotOnWrongVersionOfSoftware( + selectedRobot?.name ?? '' + ) + + const { eatToast, makeToast } = useToaster() + + const { mutateAsync: createProtocolAsync } = useCreateProtocolMutation( + {}, + selectedRobot != null + ? { + hostname: selectedRobot.ip, + requestor: + selectedRobot?.ip === OPENTRONS_USB ? appShellRequestor : undefined, + } + : null + ) + + const isAnalyzing = useSelector((state: State) => + getIsProtocolAnalysisInProgress(state, protocolKey) + ) + const customLabwareFiles = useSelector(getValidCustomLabwareFiles) + + const analysisStatus = getAnalysisStatus(isAnalyzing, mostRecentAnalysis) + + if (protocolKey == null || srcFileNames == null || srcFiles == null) { + // TODO: do more robust corrupt file catching and handling here + return null + } + + const srcFileObjects = srcFiles.map((srcFileBuffer, index) => { + const srcFilePath = srcFileNames[index] + return new File([srcFileBuffer], _getFileBaseName(srcFilePath)) + }) + + const protocolDisplayName = getProtocolDisplayName( + protocolKey, + srcFileNames, + mostRecentAnalysis + ) + + const icon: IconProps = { name: 'ot-spinner', spin: true } + + const handleSendClick = (): void => { + const toastId = makeToast(selectedRobot?.name ?? '', INFO_TOAST, { + heading: `${t('sending')} ${protocolDisplayName}`, + icon, + maxWidth: '31.25rem', + disableTimeout: true, + }) + + createProtocolAsync({ + files: [...srcFileObjects, ...customLabwareFiles], + protocolKey, + }) + .then(() => { + eatToast(toastId) + makeToast(selectedRobot?.name ?? '', SUCCESS_TOAST, { + heading: `${t('successfully_sent')} ${protocolDisplayName}`, + }) + onCloseClick() + }) + .catch( + ( + error: AxiosError<{ + errors: Array<{ id: string; detail: string; title: string }> + }> + ) => { + eatToast(toastId) + const [errorDetail] = error?.response?.data?.errors ?? [] + const { id, detail, title } = errorDetail ?? {} + if (id != null && detail != null && title != null) { + makeToast(detail, ERROR_TOAST, { + closeButton: true, + disableTimeout: true, + heading: `${protocolDisplayName} ${title} - ${ + selectedRobot?.name ?? '' + }`, + }) + } else { + makeToast(selectedRobot?.name ?? '', ERROR_TOAST, { + closeButton: true, + disableTimeout: true, + heading: `${t('unsuccessfully_sent')} ${protocolDisplayName}`, + }) + } + onCloseClick() + } + ) + } + + return ( + + {t('protocol_details:send')} + + } + selectedRobot={selectedRobot} + setSelectedRobot={setSelectedRobot} + robotType={FLEX_ROBOT_TYPE} + isAnalysisError={analysisStatus === 'error'} + isAnalysisStale={analysisStatus === 'stale'} + /> + ) +} diff --git a/app/src/organisms/Desktop/SystemLanguagePreferenceModal/__tests__/SystemLanguagePreferenceModal.test.tsx b/app/src/organisms/Desktop/SystemLanguagePreferenceModal/__tests__/SystemLanguagePreferenceModal.test.tsx new file mode 100644 index 00000000000..8ed93c1cb81 --- /dev/null +++ b/app/src/organisms/Desktop/SystemLanguagePreferenceModal/__tests__/SystemLanguagePreferenceModal.test.tsx @@ -0,0 +1,195 @@ +import { useNavigate } from 'react-router-dom' +import { fireEvent, screen } from '@testing-library/react' +import '@testing-library/jest-dom/vitest' +import { describe, it, vi, afterEach, beforeEach, expect } from 'vitest' +import { when } from 'vitest-when' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { + getAppLanguage, + getStoredSystemLanguage, + updateConfigValue, + useFeatureFlag, +} from '/app/redux/config' +import { getSystemLanguage } from '/app/redux/shell' +import { SystemLanguagePreferenceModal } from '..' + +vi.mock('react-router-dom') +vi.mock('/app/redux/config') +vi.mock('/app/redux/shell') + +const render = () => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] +} + +const mockNavigate = vi.fn() + +const MOCK_DEFAULT_LANGUAGE = 'en-US' + +describe('SystemLanguagePreferenceModal', () => { + beforeEach(() => { + vi.mocked(getAppLanguage).mockReturnValue(MOCK_DEFAULT_LANGUAGE) + vi.mocked(getSystemLanguage).mockReturnValue(MOCK_DEFAULT_LANGUAGE) + vi.mocked(getStoredSystemLanguage).mockReturnValue(MOCK_DEFAULT_LANGUAGE) + when(vi.mocked(useFeatureFlag)) + .calledWith('enableLocalization') + .thenReturn(true) + vi.mocked(useNavigate).mockReturnValue(mockNavigate) + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('should render null when app language and system language are set', () => { + render() + expect(screen.queryByText('Language preference')).toBeNull() + expect( + screen.queryByText('Update to your system language preferences') + ).toBeNull() + }) + + it('should render the correct header, description, and buttons on first boot', () => { + vi.mocked(getAppLanguage).mockReturnValue(null) + + render() + + screen.getByText('Language preference') + screen.getByText( + 'The Opentrons App matches your system language unless you select another language below. You can change the language later in the app settings.' + ) + const primaryButton = screen.getByRole('button', { + name: 'Continue', + }) + + fireEvent.click(primaryButton) + expect(updateConfigValue).toBeCalledWith( + 'language.appLanguage', + MOCK_DEFAULT_LANGUAGE + ) + expect(updateConfigValue).toBeCalledWith( + 'language.systemLanguage', + MOCK_DEFAULT_LANGUAGE + ) + }) + + it('should default to English (US) if system language is unsupported', () => { + vi.mocked(getAppLanguage).mockReturnValue(null) + vi.mocked(getSystemLanguage).mockReturnValue('es-MX') + + render() + + screen.getByText('Language preference') + screen.getByText( + 'The Opentrons App matches your system language unless you select another language below. You can change the language later in the app settings.' + ) + const primaryButton = screen.getByRole('button', { + name: 'Continue', + }) + + fireEvent.click(primaryButton) + expect(updateConfigValue).toBeCalledWith( + 'language.appLanguage', + MOCK_DEFAULT_LANGUAGE + ) + expect(updateConfigValue).toBeCalledWith('language.systemLanguage', 'es-MX') + }) + + it('should set a supported app language when system language is an unsupported locale of the same language', () => { + vi.mocked(getAppLanguage).mockReturnValue(null) + vi.mocked(getSystemLanguage).mockReturnValue('en-GB') + + render() + + screen.getByText('Language preference') + screen.getByText( + 'The Opentrons App matches your system language unless you select another language below. You can change the language later in the app settings.' + ) + const primaryButton = screen.getByRole('button', { + name: 'Continue', + }) + + fireEvent.click(primaryButton) + expect(updateConfigValue).toBeCalledWith( + 'language.appLanguage', + MOCK_DEFAULT_LANGUAGE + ) + expect(updateConfigValue).toBeCalledWith('language.systemLanguage', 'en-GB') + }) + + it('should render the correct header, description, and buttons when system language changes', () => { + vi.mocked(getSystemLanguage).mockReturnValue('zh-CN') + + render() + + screen.getByText('Update to your system language preferences') + screen.getByText( + 'Your system’s language was recently updated. Would you like to use the updated language as the default for the Opentrons App?' + ) + const secondaryButton = screen.getByRole('button', { name: 'Don’t change' }) + const primaryButton = screen.getByRole('button', { + name: 'Use system language', + }) + + fireEvent.click(primaryButton) + expect(updateConfigValue).toHaveBeenNthCalledWith( + 1, + 'language.appLanguage', + 'zh-CN' + ) + expect(updateConfigValue).toHaveBeenNthCalledWith( + 2, + 'language.systemLanguage', + 'zh-CN' + ) + fireEvent.click(secondaryButton) + expect(updateConfigValue).toHaveBeenNthCalledWith( + 3, + 'language.systemLanguage', + 'zh-CN' + ) + }) + + it('should set a supported app language when system language changes to an unsupported locale of the same language', () => { + vi.mocked(getSystemLanguage).mockReturnValue('zh-Hant') + + render() + + const secondaryButton = screen.getByRole('button', { name: 'Don’t change' }) + const primaryButton = screen.getByRole('button', { + name: 'Use system language', + }) + + fireEvent.click(primaryButton) + expect(updateConfigValue).toHaveBeenNthCalledWith( + 1, + 'language.appLanguage', + 'zh-CN' + ) + expect(updateConfigValue).toHaveBeenNthCalledWith( + 2, + 'language.systemLanguage', + 'zh-Hant' + ) + fireEvent.click(secondaryButton) + expect(updateConfigValue).toHaveBeenNthCalledWith( + 3, + 'language.systemLanguage', + 'zh-Hant' + ) + }) + + it('should not open update modal when system language changes to an unsuppported language', () => { + vi.mocked(getSystemLanguage).mockReturnValue('es-MX') + render() + + expect(screen.queryByRole('button', { name: 'Don’t change' })).toBeNull() + expect( + screen.queryByRole('button', { + name: 'Use system language', + }) + ).toBeNull() + }) +}) diff --git a/app/src/organisms/Desktop/SystemLanguagePreferenceModal/index.tsx b/app/src/organisms/Desktop/SystemLanguagePreferenceModal/index.tsx new file mode 100644 index 00000000000..b4bf54c0d17 --- /dev/null +++ b/app/src/organisms/Desktop/SystemLanguagePreferenceModal/index.tsx @@ -0,0 +1,163 @@ +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useDispatch, useSelector } from 'react-redux' + +import { + ALIGN_CENTER, + DIRECTION_COLUMN, + DropdownMenu, + Flex, + JUSTIFY_FLEX_END, + Modal, + PrimaryButton, + SecondaryButton, + SPACING, + StyledText, +} from '@opentrons/components' + +import { LANGUAGES } from '/app/i18n' +import { + getAppLanguage, + getStoredSystemLanguage, + updateConfigValue, + useFeatureFlag, +} from '/app/redux/config' +import { getSystemLanguage } from '/app/redux/shell' + +import type { DropdownOption } from '@opentrons/components' +import type { Dispatch } from '/app/redux/types' + +type ArrayElement< + ArrayType extends readonly unknown[] +> = ArrayType extends ReadonlyArray ? ElementType : never + +export function SystemLanguagePreferenceModal(): JSX.Element | null { + const { i18n, t } = useTranslation(['app_settings', 'shared', 'branded']) + const enableLocalization = useFeatureFlag('enableLocalization') + + const [currentOption, setCurrentOption] = useState( + LANGUAGES[0] + ) + + const dispatch = useDispatch() + + const appLanguage = useSelector(getAppLanguage) + const systemLanguage = useSelector(getSystemLanguage) + const storedSystemLanguage = useSelector(getStoredSystemLanguage) + + const showBootModal = appLanguage == null && systemLanguage != null + const [showUpdateModal, setShowUpdateModal] = useState(false) + + const title = showUpdateModal + ? t('system_language_preferences_update') + : t('language_preference') + + const description = showUpdateModal + ? t('branded:system_language_preferences_update_description') + : t('branded:language_preference_description') + + const primaryButtonText = showUpdateModal + ? t('use_system_language') + : i18n.format(t('shared:continue'), 'capitalize') + + const handleSecondaryClick = (): void => { + // if user chooses "Don't change" on system language update, stored the new system language but don't update the app language + dispatch(updateConfigValue('language.systemLanguage', systemLanguage)) + } + + const handlePrimaryClick = (): void => { + dispatch(updateConfigValue('language.appLanguage', currentOption.value)) + dispatch(updateConfigValue('language.systemLanguage', systemLanguage)) + } + + const handleDropdownClick = (value: string): void => { + const selectedOption = LANGUAGES.find(lng => lng.value === value) + + if (selectedOption != null) { + setCurrentOption(selectedOption) + void i18n.changeLanguage(selectedOption.value) + } + } + + // set initial language for boot modal; match app language to supported options + useEffect(() => { + if (systemLanguage != null) { + // prefer match entire locale, then match just language e.g. zh-Hant and zh-CN + const matchSystemLanguage: () => ArrayElement< + typeof LANGUAGES + > | null = () => { + try { + return ( + LANGUAGES.find(lng => lng.value === systemLanguage) ?? + LANGUAGES.find( + lng => + new Intl.Locale(lng.value).language === + new Intl.Locale(systemLanguage).language + ) ?? + null + ) + } catch (error: unknown) { + // Sometimes the language that we get from the shell will not be something + // js i18n can understand. Specifically, some linux systems will have their + // locale set to "C" (https://www.gnu.org/software/libc/manual/html_node/Standard-Locales.html) + // and that will cause Intl.Locale to throw. In this case, we'll treat it as + // unset and fall back to our default. + console.log(`Failed to search languages: ${error}`) + return null + } + } + const matchedSystemLanguageOption = matchSystemLanguage() + + if (matchedSystemLanguageOption != null) { + // initial current option: set to detected system language + setCurrentOption(matchedSystemLanguageOption) + if (showBootModal) { + // for boot modal temp change app display language based on initial system locale + void i18n.changeLanguage(systemLanguage) + } + } + // only show update modal if we support the language their system has updated to + setShowUpdateModal( + appLanguage != null && + matchedSystemLanguageOption != null && + storedSystemLanguage != null && + systemLanguage !== storedSystemLanguage + ) + } + }, [i18n, systemLanguage, showBootModal]) + + return enableLocalization && (showBootModal || showUpdateModal) ? ( + + + + + {description} + + {showBootModal ? ( + + ) : null} + + + {showUpdateModal ? ( + + {t('dont_change')} + + ) : null} + + {primaryButtonText} + + + + + ) : null +} diff --git a/app/src/organisms/UpdateAppModal/__tests__/UpdateAppModal.test.tsx b/app/src/organisms/Desktop/UpdateAppModal/__tests__/UpdateAppModal.test.tsx similarity index 91% rename from app/src/organisms/UpdateAppModal/__tests__/UpdateAppModal.test.tsx rename to app/src/organisms/Desktop/UpdateAppModal/__tests__/UpdateAppModal.test.tsx index 761cb07f0f8..3131bee25a3 100644 --- a/app/src/organisms/UpdateAppModal/__tests__/UpdateAppModal.test.tsx +++ b/app/src/organisms/Desktop/UpdateAppModal/__tests__/UpdateAppModal.test.tsx @@ -1,21 +1,21 @@ -import * as React from 'react' +import type * as React from 'react' import { screen, fireEvent } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../i18n' -import * as Shell from '../../../redux/shell' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import * as Shell from '/app/redux/shell' +import { renderWithProviders } from '/app/__testing-utils__' import { useRemoveActiveAppUpdateToast } from '../../Alerts' import { UpdateAppModal, RELEASE_NOTES_URL_BASE } from '..' -import type { State } from '../../../redux/types' -import type { ShellUpdateState } from '../../../redux/shell/types' -import type * as ShellState from '../../../redux/shell' +import type { State } from '/app/redux/types' +import type { ShellUpdateState } from '/app/redux/shell/types' +import type * as ShellState from '/app/redux/shell' import type * as Dom from 'react-router-dom' import type { UpdateAppModalProps } from '..' -vi.mock('../../../redux/shell/update', async importOriginal => { +vi.mock('/app/redux/shell/update', async importOriginal => { const actual = await importOriginal() return { ...actual, diff --git a/app/src/organisms/Desktop/UpdateAppModal/index.tsx b/app/src/organisms/Desktop/UpdateAppModal/index.tsx new file mode 100644 index 00000000000..99a54ba0dc7 --- /dev/null +++ b/app/src/organisms/Desktop/UpdateAppModal/index.tsx @@ -0,0 +1,208 @@ +import { useSelector, useDispatch } from 'react-redux' +import styled, { css } from 'styled-components' +import { useNavigate } from 'react-router-dom' +import { useTranslation } from 'react-i18next' + +import { + ALIGN_CENTER, + Banner, + BORDERS, + COLORS, + DIRECTION_COLUMN, + Flex, + JUSTIFY_SPACE_AROUND, + JUSTIFY_SPACE_BETWEEN, + PrimaryButton, + SecondaryButton, + SPACING, + LegacyStyledText, + Modal, +} from '@opentrons/components' + +import { + getShellUpdateState, + getAvailableShellUpdate, + downloadShellUpdate, + applyShellUpdate, +} from '/app/redux/shell' + +import { ExternalLink } from '/app/atoms/Link/ExternalLink' +import { ReleaseNotes } from '/app/molecules/ReleaseNotes' +import { ProgressBar } from '/app/atoms/ProgressBar' +import { useRemoveActiveAppUpdateToast } from '../Alerts' + +import type { Dispatch } from '/app/redux/types' + +interface PlaceHolderErrorProps { + errorMessage?: string +} + +const PlaceholderError = ({ + errorMessage, +}: PlaceHolderErrorProps): JSX.Element => { + const SOMETHING_WENT_WRONG = 'Something went wrong while updating your app.' + const AN_UNKNOWN_ERROR_OCCURRED = 'An unknown error occurred.' + const FALLBACK_ERROR_MESSAGE = `If you keep getting this message, try restarting your app and/or + robot. If this does not resolve the issue please contact Opentrons + Support.` + + return ( + <> + {SOMETHING_WENT_WRONG} +
    +
    + {errorMessage ?? AN_UNKNOWN_ERROR_OCCURRED} +
    +
    + {FALLBACK_ERROR_MESSAGE} + + ) +} +export const RELEASE_NOTES_URL_BASE = + 'https://github.com/Opentrons/opentrons/releases/tag/v' +const UPDATE_ERROR = 'Update Error' + +const UpdateAppBanner = styled(Banner)` + border: none; +` +const UPDATE_PROGRESS_BAR_STYLE = css` + margin-top: ${SPACING.spacing24}; + border-radius: ${BORDERS.borderRadius8}; + background: ${COLORS.grey30}; + width: 17.12rem; +` +const LEGACY_MODAL_STYLE = css` + width: 40rem; + margin-left: 5.336rem; +` + +const RESTART_APP_AFTER_TIME = 5000 + +export interface UpdateAppModalProps { + closeModal: (arg0: boolean) => void +} + +export function UpdateAppModal(props: UpdateAppModalProps): JSX.Element { + const { closeModal } = props + const dispatch = useDispatch() + const updateState = useSelector(getShellUpdateState) + const { + downloaded, + downloading, + downloadPercentage, + error, + info: updateInfo, + } = updateState + let releaseNotesText = updateInfo?.releaseNotes + if (Array.isArray(releaseNotesText)) { + // it is unclear to me why/how electron-updater would ever expose + // release notes this way, but this should never happen... + // this string representation should always be returned + releaseNotesText = releaseNotesText[0].note + } + + const { t } = useTranslation(['app_settings', 'branded']) + const navigate = useNavigate() + const { removeActiveAppUpdateToast } = useRemoveActiveAppUpdateToast() + const availableAppUpdateVersion = useSelector(getAvailableShellUpdate) ?? '' + + if (downloaded) + setTimeout(() => dispatch(applyShellUpdate()), RESTART_APP_AFTER_TIME) + + const handleRemindMeLaterClick = (): void => { + navigate('/app-settings/general') + closeModal(true) + } + + removeActiveAppUpdateToast() + + const appUpdateFooter = ( + + + {t('release_notes')} + + + + {t('remind_later')} + + dispatch(downloadShellUpdate())} + marginRight={SPACING.spacing12} + > + {t('update_app_now')} + + + + ) + + return ( + <> + {error != null ? ( + { + closeModal(true) + }} + css={LEGACY_MODAL_STYLE} + > + + + ) : null} + {(downloading || downloaded) && error == null ? ( + + + + {downloading ? t('download_update') : t('restarting_app')} + + + + + ) : null} + {!downloading && !downloaded && error == null ? ( + { + closeModal(true) + }} + closeOnOutsideClick={true} + footer={appUpdateFooter} + maxHeight="80%" + css={LEGACY_MODAL_STYLE} + > + + + {t('branded:update_requires_restarting_app')} + + + + + ) : null} + + ) +} diff --git a/app/src/organisms/UpdateRobotBanner/__tests__/UpdateRobotBanner.test.tsx b/app/src/organisms/Desktop/UpdateRobotBanner/__tests__/UpdateRobotBanner.test.tsx similarity index 90% rename from app/src/organisms/UpdateRobotBanner/__tests__/UpdateRobotBanner.test.tsx rename to app/src/organisms/Desktop/UpdateRobotBanner/__tests__/UpdateRobotBanner.test.tsx index 605965dc379..e7d90f70ea3 100644 --- a/app/src/organisms/UpdateRobotBanner/__tests__/UpdateRobotBanner.test.tsx +++ b/app/src/organisms/Desktop/UpdateRobotBanner/__tests__/UpdateRobotBanner.test.tsx @@ -1,18 +1,18 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { fireEvent, screen } from '@testing-library/react' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' -import * as Buildroot from '../../../redux/robot-update' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import * as Buildroot from '/app/redux/robot-update' import { mockConnectableRobot, mockReachableRobot, -} from '../../../redux/discovery/__fixtures__' +} from '/app/redux/discovery/__fixtures__' import { handleUpdateBuildroot } from '../../Devices/RobotSettings/UpdateBuildroot' import { UpdateRobotBanner } from '..' -vi.mock('../../../redux/robot-update') +vi.mock('/app/redux/robot-update') vi.mock('../../Devices/RobotSettings/UpdateBuildroot') const getUpdateDisplayInfo = Buildroot.getRobotUpdateDisplayInfo diff --git a/app/src/organisms/Desktop/UpdateRobotBanner/index.tsx b/app/src/organisms/Desktop/UpdateRobotBanner/index.tsx new file mode 100644 index 00000000000..e5d7d2d0e85 --- /dev/null +++ b/app/src/organisms/Desktop/UpdateRobotBanner/index.tsx @@ -0,0 +1,59 @@ +import type * as React from 'react' +import { useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { + Btn, + DIRECTION_COLUMN, + Flex, + Banner, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' +import { getRobotUpdateDisplayInfo } from '/app/redux/robot-update' +import { handleUpdateBuildroot } from '../Devices/RobotSettings/UpdateBuildroot' + +import type { StyleProps } from '@opentrons/components' +import type { State } from '/app/redux/types' +import type { DiscoveredRobot } from '/app/redux/discovery/types' + +interface UpdateRobotBannerProps extends StyleProps { + robot: DiscoveredRobot +} + +export function UpdateRobotBanner( + props: UpdateRobotBannerProps +): JSX.Element | null { + const { robot, ...styleProps } = props + const { t } = useTranslation(['device_settings', 'branded']) + + const { autoUpdateAction } = useSelector((state: State) => { + return getRobotUpdateDisplayInfo(state, robot?.name) + }) + + return (autoUpdateAction === 'upgrade' || autoUpdateAction === 'downgrade') && + robot !== null && + robot.healthStatus === 'ok' ? ( + { + e.stopPropagation() + }} + flexDirection={DIRECTION_COLUMN} + > + + + {t('branded:robot_software_update_required')} + + { + handleUpdateBuildroot(robot) + }} + css={TYPOGRAPHY.pRegular} + textDecoration={TYPOGRAPHY.textDecorationUnderline} + > + {t('view_update')} + + + + ) : null +} diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.stories.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.stories.tsx index de7c062f9d1..f14c6d46e96 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.stories.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { QueryClient, QueryClientProvider } from 'react-query' import { VIEWPORT } from '@opentrons/components' import { AddFixtureModal } from './AddFixtureModal' diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx index ff456be6d13..bf3be7e788e 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' @@ -7,13 +7,14 @@ import { BORDERS, Btn, COLORS, + CURSOR_DEFAULT, DIRECTION_COLUMN, DIRECTION_ROW, Flex, JUSTIFY_SPACE_BETWEEN, LegacyStyledText, - SPACING, Modal, + SPACING, TYPOGRAPHY, } from '@opentrons/components' import { @@ -24,11 +25,11 @@ import { getCutoutDisplayName, getFixtureDisplayName, ABSORBANCE_READER_CUTOUTS, - ABSORBANCE_READER_V1, ABSORBANCE_READER_V1_FIXTURE, + ABSORBANCE_READER_V1, HEATER_SHAKER_CUTOUTS, - HEATERSHAKER_MODULE_V1, HEATERSHAKER_MODULE_V1_FIXTURE, + HEATERSHAKER_MODULE_V1, MAGNETIC_BLOCK_V1_FIXTURE, SINGLE_CENTER_CUTOUTS, SINGLE_LEFT_CUTOUTS, @@ -37,8 +38,8 @@ import { STAGING_AREA_RIGHT_SLOT_FIXTURE, STAGING_AREA_SLOT_WITH_MAGNETIC_BLOCK_V1_FIXTURE, TEMPERATURE_MODULE_CUTOUTS, - TEMPERATURE_MODULE_V2, TEMPERATURE_MODULE_V2_FIXTURE, + TEMPERATURE_MODULE_V2, THERMOCYCLER_MODULE_CUTOUTS, THERMOCYCLER_MODULE_V2, THERMOCYCLER_V2_FRONT_FIXTURE, @@ -48,18 +49,19 @@ import { WASTE_CHUTE_FIXTURES, } from '@opentrons/shared-data' -import { ODD_FOCUS_VISIBLE } from '../../atoms/buttons/constants' -import { TertiaryButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration/' +import { ODD_FOCUS_VISIBLE } from '/app/atoms/buttons/constants' +import { TertiaryButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration/' +import type { MouseEventHandler } from 'react' import type { CutoutConfig, CutoutId, CutoutFixtureId, } from '@opentrons/shared-data' import type { ModalProps } from '@opentrons/components' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' interface AddFixtureModalProps { cutoutId: CutoutId @@ -100,9 +102,7 @@ export function AddFixtureModal({ // only show provided options if given as props initialStage = 'providedOptions' } - const [optionStage, setOptionStage] = React.useState( - initialStage - ) + const [optionStage, setOptionStage] = useState(initialStage) const modalHeader: OddModalHeaderBaseProps = { title: t('add_to_slot', { @@ -385,7 +385,7 @@ export function AddFixtureModal({ const FIXTURE_BUTTON_STYLE_ODD = css` background-color: ${COLORS.grey35}; - cursor: default; + cursor: ${CURSOR_DEFAULT}; border-radius: ${BORDERS.borderRadius16}; box-shadow: none; @@ -424,7 +424,7 @@ const GO_BACK_BUTTON_STYLE = css` ` interface FixtureOptionProps { - onClickHandler: React.MouseEventHandler + onClickHandler: MouseEventHandler optionName: string buttonText: string isOnDevice: boolean diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal.stories.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal.stories.tsx index 0fdee52a94e..971950f222c 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal.stories.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { VIEWPORT } from '@opentrons/components' import { DeckConfigurationDiscardChangesModal } from './DeckConfigurationDiscardChangesModal' import type { Story, Meta } from '@storybook/react' diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal.tsx index 31b1db88fba..bdf8339de71 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' @@ -10,10 +9,10 @@ import { LegacyStyledText, } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' +import { SmallButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' interface DeckConfigurationDiscardChangesModalProps { setShowConfirmationModal: (showConfirmationModal: boolean) => void diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal.tsx index 944a74aa394..cfda966a3c1 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { ALIGN_FLEX_END, @@ -11,13 +10,13 @@ import { Modal, LegacyStyledText, } from '@opentrons/components' -import { ExternalLink } from '../../atoms/Link/ExternalLink' -import { OddModal } from '../../molecules/OddModal' +import { ExternalLink } from '/app/atoms/Link/ExternalLink' +import { OddModal } from '/app/molecules/OddModal' import type { ModalProps } from '@opentrons/components' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' -import imgSrc from '../../assets/images/on-device-display/deck_fixture_setup_qrcode.png' +import imgSrc from '/app/assets/images/on-device-display/deck_fixture_setup_qrcode.png' const SETUP_INSTRUCTION_URL = 'https://support.opentrons.com/s/article/Deck-configuration-on-Opentrons-Flex' diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/TouchScreenDeckFixtureSetupInstructionModal.stories.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/TouchScreenDeckFixtureSetupInstructionModal.stories.tsx index ec078d74eea..09586ea57f2 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/TouchScreenDeckFixtureSetupInstructionModal.stories.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/TouchScreenDeckFixtureSetupInstructionModal.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { VIEWPORT } from '@opentrons/components' import { DeckFixtureSetupInstructionsModal } from './DeckFixtureSetupInstructionsModal' import type { Story, Meta } from '@storybook/react' diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/AddFixtureModal.test.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/AddFixtureModal.test.tsx index 17ae8511513..450a64cc0e6 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/AddFixtureModal.test.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/AddFixtureModal.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, expect, afterEach } from 'vitest' @@ -11,17 +11,17 @@ import { WASTE_CHUTE_FIXTURES, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { AddFixtureModal } from '../AddFixtureModal' -import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' import type { UseQueryResult } from 'react-query' import type { DeckConfiguration } from '@opentrons/shared-data' import type { Modules } from '@opentrons/api-client' vi.mock('@opentrons/react-api-client') -vi.mock('../../../resources/deck_configuration') +vi.mock('/app/resources/deck_configuration') const mockCloseModal = vi.fn() const mockUpdateDeckConfiguration = vi.fn() diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeckConfigurationDiscardChangesModal.test.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeckConfigurationDiscardChangesModal.test.tsx index 8b2d6c409d4..fd0e56ffa4d 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeckConfigurationDiscardChangesModal.test.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeckConfigurationDiscardChangesModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { DeckConfigurationDiscardChangesModal } from '../DeckConfigurationDiscardChangesModal' import type { NavigateFunction } from 'react-router-dom' diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeckFixtureSetupInstructionsModal.test.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeckFixtureSetupInstructionsModal.test.tsx index e9a6ce85c2a..ddc9ff33194 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeckFixtureSetupInstructionsModal.test.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeckFixtureSetupInstructionsModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { DeckFixtureSetupInstructionsModal } from '../DeckFixtureSetupInstructionsModal' const mockFunc = vi.fn() diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeviceDetailsDeckConfiguration.test.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeviceDetailsDeckConfiguration.test.tsx index baa497c843d..16ef3db90a7 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeviceDetailsDeckConfiguration.test.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/__tests__/DeviceDetailsDeckConfiguration.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { when } from 'vitest-when' import { describe, it, beforeEach, vi, afterEach } from 'vitest' @@ -10,17 +10,18 @@ import { useUpdateDeckConfigurationMutation, } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useIsRobotViewable, useRunStatuses } from '../../Devices/hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useRunStatuses } from '/app/resources/runs' +import { useIsRobotViewable } from '/app/redux-resources/robots' import { DeckFixtureSetupInstructionsModal } from '../DeckFixtureSetupInstructionsModal' -import { useIsEstopNotDisengaged } from '../../../resources/devices/hooks/useIsEstopNotDisengaged' +import { useIsEstopNotDisengaged } from '/app/resources/devices/hooks/useIsEstopNotDisengaged' import { DeviceDetailsDeckConfiguration } from '../' -import { useNotifyCurrentMaintenanceRun } from '../../../resources/maintenance_runs' +import { useNotifyCurrentMaintenanceRun } from '/app/resources/maintenance_runs' import { useDeckConfigurationEditingTools, useNotifyDeckConfigurationQuery, -} from '../../../resources/deck_configuration' +} from '/app/resources/deck_configuration' import type { UseQueryResult } from 'react-query' import type { MaintenanceRun } from '@opentrons/api-client' @@ -36,10 +37,11 @@ vi.mock('@opentrons/components', async importOriginal => { }) vi.mock('@opentrons/react-api-client') vi.mock('../DeckFixtureSetupInstructionsModal') -vi.mock('../../Devices/hooks') -vi.mock('../../../resources/maintenance_runs') -vi.mock('../../../resources/devices/hooks/useIsEstopNotDisengaged') -vi.mock('../../../resources/deck_configuration') +vi.mock('/app/resources/runs') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/resources/maintenance_runs') +vi.mock('/app/resources/devices/hooks/useIsEstopNotDisengaged') +vi.mock('/app/resources/deck_configuration') const mockDeckConfig = [ { diff --git a/app/src/organisms/DeviceDetailsDeckConfiguration/index.tsx b/app/src/organisms/DeviceDetailsDeckConfiguration/index.tsx index d42f91ed558..1e3ef81960a 100644 --- a/app/src/organisms/DeviceDetailsDeckConfiguration/index.tsx +++ b/app/src/organisms/DeviceDetailsDeckConfiguration/index.tsx @@ -1,10 +1,11 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' import { ALIGN_CENTER, ALIGN_FLEX_START, + Banner, BORDERS, COLORS, DeckConfigurator, @@ -28,15 +29,15 @@ import { FLEX_ROBOT_TYPE, } from '@opentrons/shared-data' -import { useNotifyCurrentMaintenanceRun } from '../../resources/maintenance_runs' -import { Banner } from '../../atoms/Banner' +import { useNotifyCurrentMaintenanceRun } from '/app/resources/maintenance_runs' import { DeckFixtureSetupInstructionsModal } from './DeckFixtureSetupInstructionsModal' -import { useIsRobotViewable, useRunStatuses } from '../Devices/hooks' -import { useIsEstopNotDisengaged } from '../../resources/devices/hooks/useIsEstopNotDisengaged' +import { useRunStatuses } from '/app/resources/runs' +import { useIsRobotViewable } from '/app/redux-resources/robots' +import { useIsEstopNotDisengaged } from '/app/resources/devices/hooks/useIsEstopNotDisengaged' import { useDeckConfigurationEditingTools, useNotifyDeckConfigurationQuery, -} from '../../resources/deck_configuration' +} from '/app/resources/deck_configuration' import type { CutoutId } from '@opentrons/shared-data' @@ -58,7 +59,7 @@ export function DeviceDetailsDeckConfiguration({ const [ showSetupInstructionsModal, setShowSetupInstructionsModal, - ] = React.useState(false) + ] = useState(false) const { data: modulesData } = useModulesQuery() const deckConfig = diff --git a/app/src/organisms/Devices/HeaterShakerIsRunningModal/__tests__/hooks.test.tsx b/app/src/organisms/Devices/HeaterShakerIsRunningModal/__tests__/hooks.test.tsx deleted file mode 100644 index 969d3d1afea..00000000000 --- a/app/src/organisms/Devices/HeaterShakerIsRunningModal/__tests__/hooks.test.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import * as React from 'react' -import { Provider } from 'react-redux' -import { describe, it, vi, beforeEach, expect } from 'vitest' -import '@testing-library/jest-dom/vitest' -import { createStore } from 'redux' -import { renderHook } from '@testing-library/react' -import { HEATERSHAKER_MODULE_V1 } from '@opentrons/shared-data' -import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useHeaterShakerModuleIdsFromRun } from '../hooks' -import { RUN_ID_1 } from '../../../RunTimeControl/__fixtures__' - -import type { Store } from 'redux' -import type { State } from '../../../../redux/types' - -vi.mock('../../hooks') -vi.mock('../../../LabwarePositionCheck/useMostRecentCompletedAnalysis') - -describe('useHeaterShakerModuleIdsFromRun', () => { - const store: Store = createStore(vi.fn(), {}) - - beforeEach(() => { - store.dispatch = vi.fn() - }) - - it('should return a heater shaker module id from protocol analysis load command result', () => { - vi.mocked(useMostRecentCompletedAnalysis).mockReturnValue({ - pipettes: {}, - labware: {}, - modules: { - heatershaker_id: { - model: HEATERSHAKER_MODULE_V1, - }, - }, - labwareDefinitions: {}, - commands: [ - { - id: 'mock_command_1', - createdAt: '2022-07-27T22:26:33.846399+00:00', - commandType: 'loadModule', - key: '286d7201-bfdc-4c2c-ae67-544367dbbabe', - status: 'succeeded', - params: { - model: HEATERSHAKER_MODULE_V1, - location: { - slotName: '1', - }, - moduleId: 'heatershaker_id', - }, - result: { - moduleId: 'heatershaker_id', - definition: {}, - model: HEATERSHAKER_MODULE_V1, - serialNumber: 'fake-serial-number-1', - }, - startedAt: '2022-07-27T22:26:33.875106+00:00', - completedAt: '2022-07-27T22:26:33.878079+00:00', - }, - ], - } as any) - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => {children} - const { result } = renderHook( - () => useHeaterShakerModuleIdsFromRun(RUN_ID_1), - { wrapper } - ) - - const moduleIdsFromRun = result.current - expect(moduleIdsFromRun.moduleIdsFromRun).toStrictEqual(['heatershaker_id']) - }) - - it('should return two heater shaker module ids if two modules are loaded in the protocol', () => { - vi.mocked(useMostRecentCompletedAnalysis).mockReturnValue({ - pipettes: {}, - labware: {}, - modules: { - heatershaker_id: { - model: HEATERSHAKER_MODULE_V1, - }, - }, - labwareDefinitions: {}, - commands: [ - { - id: 'mock_command_1', - createdAt: '2022-07-27T22:26:33.846399+00:00', - commandType: 'loadModule', - key: '286d7201-bfdc-4c2c-ae67-544367dbbabe', - status: 'succeeded', - params: { - model: HEATERSHAKER_MODULE_V1, - location: { - slotName: '1', - }, - moduleId: 'heatershaker_id_1', - }, - result: { - moduleId: 'heatershaker_id_1', - definition: {}, - model: HEATERSHAKER_MODULE_V1, - serialNumber: 'fake-serial-number-1', - }, - startedAt: '2022-07-27T22:26:33.875106+00:00', - completedAt: '2022-07-27T22:26:33.878079+00:00', - }, - { - id: 'mock_command_2', - createdAt: '2022-07-27T22:26:33.846399+00:00', - commandType: 'loadModule', - key: '286d7201-bfdc-4c2c-ae67-544367dbbabe', - status: 'succeeded', - params: { - model: HEATERSHAKER_MODULE_V1, - location: { - slotName: '1', - }, - moduleId: 'heatershaker_id_2', - }, - result: { - moduleId: 'heatershaker_id_2', - definition: {}, - model: 'heaterShakerModuleV1_2', - serialNumber: 'fake-serial-number-2', - }, - startedAt: '2022-07-27T22:26:33.875106+00:00', - completedAt: '2022-07-27T22:26:33.878079+00:00', - }, - ], - } as any) - - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => {children} - const { result } = renderHook( - () => useHeaterShakerModuleIdsFromRun(RUN_ID_1), - { wrapper } - ) - - const moduleIdsFromRun = result.current - expect(moduleIdsFromRun.moduleIdsFromRun).toStrictEqual([ - 'heatershaker_id_1', - 'heatershaker_id_2', - ]) - }) -}) diff --git a/app/src/organisms/Devices/HeaterShakerIsRunningModal/hooks.tsx b/app/src/organisms/Devices/HeaterShakerIsRunningModal/hooks.tsx deleted file mode 100644 index ee391e8c01c..00000000000 --- a/app/src/organisms/Devices/HeaterShakerIsRunningModal/hooks.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' - -export interface ModuleIdsFromRun { - moduleIdsFromRun: string[] -} - -export function useHeaterShakerModuleIdsFromRun( - runId: string | null -): ModuleIdsFromRun { - const protocolData = useMostRecentCompletedAnalysis(runId) - - const loadModuleCommands = protocolData?.commands.filter( - command => - command.commandType === 'loadModule' && - command.params.model === 'heaterShakerModuleV1' - ) - - const moduleIdsFromRun = - loadModuleCommands != null - ? loadModuleCommands?.map(command => command.result?.moduleId) - : [] - - return { moduleIdsFromRun } -} diff --git a/app/src/organisms/Devices/HeaterShakerIsRunningModal/index.tsx b/app/src/organisms/Devices/HeaterShakerIsRunningModal/index.tsx deleted file mode 100644 index 58dc5796f3d..00000000000 --- a/app/src/organisms/Devices/HeaterShakerIsRunningModal/index.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' -import { - Box, - COLORS, - DIRECTION_ROW, - Flex, - Icon, - JUSTIFY_FLEX_END, - PrimaryButton, - SecondaryButton, - SPACING, - LegacyStyledText, - TYPOGRAPHY, - Modal, -} from '@opentrons/components' -import { useAttachedModules } from '../hooks' -import { HeaterShakerModuleCard } from '../HeaterShakerWizard/HeaterShakerModuleCard' -import { HEATERSHAKER_MODULE_TYPE } from '@opentrons/shared-data' - -import type { HeaterShakerDeactivateShakerCreateCommand } from '@opentrons/shared-data' -import type { HeaterShakerModule } from '../../../redux/modules/types' - -interface HeaterShakerIsRunningModalProps { - closeModal: () => void - module: HeaterShakerModule - startRun: () => void -} - -export const HeaterShakerIsRunningModal = ( - props: HeaterShakerIsRunningModalProps -): JSX.Element => { - const { closeModal, module, startRun } = props - const { t } = useTranslation('heater_shaker') - const { createLiveCommand } = useCreateLiveCommandMutation() - const attachedModules = useAttachedModules() - const moduleIds = attachedModules - .filter( - (module): module is HeaterShakerModule => - module.moduleType === HEATERSHAKER_MODULE_TYPE && - module?.data != null && - module.data.speedStatus !== 'idle' - ) - .map(module => module.id) - - const title = ( - - - {t('heater_shaker_is_shaking')} - - ) - - const handleContinueShaking = (): void => { - startRun() - closeModal() - } - - const handleStopShake = (): void => { - moduleIds.forEach(moduleId => { - const stopShakeCommand: HeaterShakerDeactivateShakerCreateCommand = { - commandType: 'heaterShaker/deactivateShaker', - params: { - moduleId: moduleId, - }, - } - - createLiveCommand({ - command: stopShakeCommand, - }).catch((e: Error) => { - console.error( - `error setting module status with command type ${stopShakeCommand.commandType}: ${e.message}` - ) - }) - }) - handleContinueShaking() - } - - return ( - - - - - - {t('continue_shaking_protocol_start_prompt')} - - - - - {t('stop_shaking_start_run')} - - - {t('keep_shaking_start_run')} - - - - ) -} diff --git a/app/src/organisms/Devices/HeaterShakerWizard/__tests__/HeaterShakerModuleCard.test.tsx b/app/src/organisms/Devices/HeaterShakerWizard/__tests__/HeaterShakerModuleCard.test.tsx deleted file mode 100644 index c8fec9076dd..00000000000 --- a/app/src/organisms/Devices/HeaterShakerWizard/__tests__/HeaterShakerModuleCard.test.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import * as React from 'react' -import { screen } from '@testing-library/react' -import { describe, it, vi, beforeEach } from 'vitest' -import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { HeaterShakerModuleCard } from '../HeaterShakerModuleCard' -import { HeaterShakerModuleData } from '../../../ModuleCard/HeaterShakerModuleData' -import { mockHeaterShaker } from '../../../../redux/modules/__fixtures__' - -vi.mock('../../../ModuleCard/HeaterShakerModuleData') - -const render = (props: React.ComponentProps) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} - -describe('HeaterShakerModuleCard', () => { - let props: React.ComponentProps - beforeEach(() => { - props = { - module: mockHeaterShaker, - } - vi.mocked(HeaterShakerModuleData).mockReturnValue( -
    mock heater shaker module data
    - ) - }) - - it('renders the correct info', () => { - render(props) - screen.getByText('usb-1') - screen.getByText('Heater-Shaker Module GEN1') - screen.getByText('mock heater shaker module data') - screen.getByAltText('Heater-Shaker') - screen.getByLabelText('heater-shaker') - }) -}) diff --git a/app/src/organisms/Devices/PipetteCard/index.tsx b/app/src/organisms/Devices/PipetteCard/index.tsx deleted file mode 100644 index 182eb5339b8..00000000000 --- a/app/src/organisms/Devices/PipetteCard/index.tsx +++ /dev/null @@ -1,221 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' - -import { - ALIGN_CENTER, - ALIGN_START, - BORDERS, - Box, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - InstrumentDiagram, - LegacyStyledText, - OverflowBtn, - SPACING, - TYPOGRAPHY, - useMenuHandleClickOutside, - useOnClickOutside, -} from '@opentrons/components' -import { OT2_ROBOT_TYPE } from '@opentrons/shared-data' -import { usePipetteSettingsQuery } from '@opentrons/react-api-client' - -import { LEFT } from '../../../redux/pipettes' -import { ChangePipette } from '../../ChangePipette' -import { PipetteOverflowMenu } from './PipetteOverflowMenu' -import { PipetteSettingsSlideout } from './PipetteSettingsSlideout' -import { AboutPipetteSlideout } from './AboutPipetteSlideout' -import { - DropTipWizardFlows, - useDropTipWizardFlows, -} from '../../DropTipWizardFlows' - -import type { PipetteModelSpecs } from '@opentrons/shared-data' -import type { AttachedPipette, Mount } from '../../../redux/pipettes/types' - -interface PipetteCardProps { - pipetteModelSpecs: PipetteModelSpecs | null - pipetteId?: AttachedPipette['id'] | null - mount: Mount - robotName: string - isRunActive: boolean - isEstopNotDisengaged: boolean -} - -const POLL_DURATION_MS = 5000 - -// The OT-2 pipette card. -export const PipetteCard = (props: PipetteCardProps): JSX.Element => { - const { t } = useTranslation(['device_details', 'protocol_setup']) - const { - pipetteModelSpecs, - mount, - robotName, - pipetteId, - isRunActive, - isEstopNotDisengaged, - } = props - const { - menuOverlay, - handleOverflowClick, - showOverflowMenu, - setShowOverflowMenu, - } = useMenuHandleClickOutside() - const pipetteDisplayName = pipetteModelSpecs?.displayName - const pipetteOverflowWrapperRef = useOnClickOutside({ - onClickOutside: () => { - setShowOverflowMenu(false) - }, - }) - const [showChangePipette, setChangePipette] = React.useState(false) - const [showSlideout, setShowSlideout] = React.useState(false) - const [showAboutSlideout, setShowAboutSlideout] = React.useState(false) - - const { showDTWiz, toggleDTWiz } = useDropTipWizardFlows() - - const settings = - usePipetteSettingsQuery({ - refetchInterval: POLL_DURATION_MS, - enabled: pipetteId != null, - })?.data?.[pipetteId ?? '']?.fields ?? null - - const handleChangePipette = (): void => { - setChangePipette(true) - } - const handleAboutSlideout = (): void => { - setShowAboutSlideout(true) - } - const handleSettingsSlideout = (): void => { - setShowSlideout(true) - } - return ( - - {showChangePipette && ( - { - setChangePipette(false) - }} - /> - )} - {showDTWiz && pipetteModelSpecs != null ? ( - - ) : null} - {showSlideout && - pipetteModelSpecs != null && - pipetteId != null && - settings != null && ( - { - setShowSlideout(false) - }} - isExpanded={true} - pipetteId={pipetteId} - settings={settings} - /> - )} - {showAboutSlideout && pipetteModelSpecs != null && pipetteId != null && ( - { - setShowAboutSlideout(false) - }} - isExpanded={true} - /> - )} - <> - - - {pipetteModelSpecs !== null ? ( - - - - ) : null} - - - {t('mount', { - side: mount === LEFT ? t('left') : t('right'), - })} - - - - {pipetteDisplayName ?? t('empty')} - - - - - - - - - - {showOverflowMenu && ( - <> - { - setShowOverflowMenu(false) - }} - > - - - {menuOverlay} - - )} - - ) -} diff --git a/app/src/organisms/Devices/ProtocolRun/ConfirmMissingStepsModal.tsx b/app/src/organisms/Devices/ProtocolRun/ConfirmMissingStepsModal.tsx deleted file mode 100644 index 6ffe64576b0..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/ConfirmMissingStepsModal.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { - ALIGN_CENTER, - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - JUSTIFY_FLEX_END, - PrimaryButton, - SecondaryButton, - SPACING, - LegacyStyledText, - TYPOGRAPHY, - Modal, -} from '@opentrons/components' - -interface ConfirmMissingStepsModalProps { - onCloseClick: () => void - onConfirmClick: () => void - missingSteps: string[] -} -export const ConfirmMissingStepsModal = ( - props: ConfirmMissingStepsModalProps -): JSX.Element | null => { - const { missingSteps, onCloseClick, onConfirmClick } = props - const { t, i18n } = useTranslation(['protocol_setup', 'shared']) - - const confirmAttached = (): void => { - onConfirmClick() - onCloseClick() - } - - return ( - - - - {t('you_havent_confirmed', { - missingSteps: new Intl.ListFormat('en', { - style: 'short', - type: 'conjunction', - }).format(missingSteps.map(step => t(step))), - })} - - - - - {i18n.format(t('shared:go_back'), 'capitalize')} - - - {t('start_run')} - - - - ) -} diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolAnalysisErrorModal.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolAnalysisErrorModal.tsx deleted file mode 100644 index 6ad5d5af302..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolAnalysisErrorModal.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' - -import { - Flex, - JUSTIFY_FLEX_END, - OVERFLOW_WRAP_ANYWHERE, - PrimaryButton, - SPACING, - LegacyStyledText, - Modal, - TYPOGRAPHY, -} from '@opentrons/components' - -import { getTopPortalEl } from '../../../App/portal' - -import type { AnalysisError } from '@opentrons/shared-data' - -interface ProtocolAnalysisErrorModalProps { - displayName: string | null - errors: AnalysisError[] - onClose: React.MouseEventHandler - robotName: string -} - -export function ProtocolAnalysisErrorModal({ - displayName, - errors, - onClose, - robotName, -}: ProtocolAnalysisErrorModalProps): JSX.Element { - const { t } = useTranslation(['run_details', 'shared']) - - return createPortal( - - - {t('analysis_failure_on_robot', { - protocolName: displayName, - robotName, - })} - - {errors?.map((error, index) => ( - - {error?.detail} - - ))} - - - - {t('shared:close')} - - - - , - getTopPortalEl() - ) -} diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolDropTipModal.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolDropTipModal.tsx deleted file mode 100644 index f367af7412f..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolDropTipModal.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import * as React from 'react' -import { Trans, useTranslation } from 'react-i18next' -import { css } from 'styled-components' - -import { - COLORS, - DIRECTION_COLUMN, - Flex, - JUSTIFY_SPACE_BETWEEN, - SPACING, - StyledText, - PrimaryButton, - JUSTIFY_END, - ModalHeader, - ModalShell, -} from '@opentrons/components' - -import { TextOnlyButton } from '../../../atoms/buttons' -import { useHomePipettes } from '../../DropTipWizardFlows/hooks' - -import type { PipetteData } from '@opentrons/api-client' -import type { IconProps } from '@opentrons/components' -import type { UseHomePipettesProps } from '../../DropTipWizardFlows/hooks' -import type { TipAttachmentStatusResult } from '../../DropTipWizardFlows' - -type UseProtocolDropTipModalProps = Pick< - UseHomePipettesProps, - 'robotType' | 'instrumentModelSpecs' | 'mount' -> & { - areTipsAttached: TipAttachmentStatusResult['areTipsAttached'] - toggleDTWiz: () => void - currentRunId: string - onSkipAndHome: () => void - /* True if the most recent run is the current run */ - isRunCurrent: boolean -} - -interface UseProtocolDropTipModalResult { - showDTModal: boolean - onDTModalSkip: () => void - onDTModalRemoval: () => void - isDisabled: boolean -} - -// Wraps functionality required for rendering the related modal. -export function useProtocolDropTipModal({ - areTipsAttached, - toggleDTWiz, - isRunCurrent, - onSkipAndHome, - ...homePipetteProps -}: UseProtocolDropTipModalProps): UseProtocolDropTipModalResult { - const [showDTModal, setShowDTModal] = React.useState(areTipsAttached) - - const { homePipettes, isHomingPipettes } = useHomePipettes({ - ...homePipetteProps, - isRunCurrent, - onHome: () => { - onSkipAndHome() - setShowDTModal(false) - }, - }) - - // Close the modal if a different app closes the run context. - React.useEffect(() => { - if (isRunCurrent && !isHomingPipettes) { - setShowDTModal(areTipsAttached) - } else if (!isRunCurrent) { - setShowDTModal(false) - } - }, [isRunCurrent, areTipsAttached, showDTModal]) // Continue to show the modal if a client dismisses the maintenance run on a different app. - - const onDTModalSkip = (): void => { - homePipettes() - } - - const onDTModalRemoval = (): void => { - toggleDTWiz() - setShowDTModal(false) - } - - return { - showDTModal, - onDTModalSkip, - onDTModalRemoval, - isDisabled: isHomingPipettes, - } -} - -interface ProtocolDropTipModalProps { - onSkip: UseProtocolDropTipModalResult['onDTModalSkip'] - onBeginRemoval: UseProtocolDropTipModalResult['onDTModalRemoval'] - isDisabled: UseProtocolDropTipModalResult['isDisabled'] - mount?: PipetteData['mount'] -} - -export function ProtocolDropTipModal({ - onSkip, - onBeginRemoval, - mount, - isDisabled, -}: ProtocolDropTipModalProps): JSX.Element { - const { t } = useTranslation('drop_tip_wizard') - - const buildIcon = (): IconProps => { - return { - name: 'information', - color: COLORS.red50, - size: SPACING.spacing20, - marginRight: SPACING.spacing8, - } - } - - const buildHeader = (): JSX.Element => { - return ( - - ) - } - - return ( - - - - , - }} - /> - - - - - {t('begin_removal')} - - - - - ) -} - -const MODAL_STYLE = css` - width: 500px; -` diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx deleted file mode 100644 index 7a59b2a6cd1..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunHeader.tsx +++ /dev/null @@ -1,1059 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { Link, useNavigate } from 'react-router-dom' - -import { - RUN_STATUS_IDLE, - RUN_STATUS_RUNNING, - RUN_STATUS_PAUSED, - RUN_STATUS_STOP_REQUESTED, - RUN_STATUS_STOPPED, - RUN_STATUS_FAILED, - RUN_STATUS_FINISHING, - RUN_STATUS_SUCCEEDED, - RUN_STATUS_BLOCKED_BY_OPEN_DOOR, - RUN_STATUS_AWAITING_RECOVERY, - RUN_STATUSES_TERMINAL, - RUN_STATUS_AWAITING_RECOVERY_PAUSED, - RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, -} from '@opentrons/api-client' -import { - useModulesQuery, - useDoorQuery, - useDeleteRunMutation, - useHost, - useRunCommandErrors, -} from '@opentrons/react-api-client' -import { FLEX_ROBOT_TYPE, OT2_ROBOT_TYPE } from '@opentrons/shared-data' -import { - ALIGN_CENTER, - BORDERS, - Box, - COLORS, - DIRECTION_COLUMN, - DISPLAY_FLEX, - Flex, - Icon, - JUSTIFY_CENTER, - JUSTIFY_FLEX_END, - JUSTIFY_SPACE_BETWEEN, - LegacyStyledText, - Link as LinkButton, - PrimaryButton, - SecondaryButton, - SIZE_1, - SPACING, - Tooltip, - TYPOGRAPHY, - useConditionalConfirm, - useHoverTooltip, -} from '@opentrons/components' - -import { getRobotUpdateDisplayInfo } from '../../../redux/robot-update' -import { getRobotSettings } from '../../../redux/robot-settings' -import { getRobotSerialNumber } from '../../../redux/discovery' -import { ProtocolAnalysisErrorBanner } from './ProtocolAnalysisErrorBanner' -import { - DropTipWizardFlows, - useDropTipWizardFlows, - useTipAttachmentStatus, -} from '../../DropTipWizardFlows' -import { ProtocolAnalysisErrorModal } from './ProtocolAnalysisErrorModal' -import { Banner } from '../../../atoms/Banner' -import { - useTrackEvent, - ANALYTICS_PROTOCOL_PROCEED_TO_RUN, - ANALYTICS_PROTOCOL_RUN_ACTION, -} from '../../../redux/analytics' -import { getIsHeaterShakerAttached } from '../../../redux/config' -import { getStoredProtocol } from '../../../redux/protocol-storage' -import { useCloseCurrentRun } from '../../ProtocolUpload/hooks' -import { ConfirmCancelModal } from '../../RunDetails/ConfirmCancelModal' -import { HeaterShakerIsRunningModal } from '../HeaterShakerIsRunningModal' -import { - useRunControls, - useRunStatus, - useRunTimestamps, -} from '../../../organisms/RunTimeControl/hooks' -import { useIsHeaterShakerInProtocol } from '../../ModuleCard/hooks' -import { ConfirmAttachmentModal } from '../../ModuleCard/ConfirmAttachmentModal' -import { ConfirmMissingStepsModal } from './ConfirmMissingStepsModal' -import { - useProtocolDetailsForRun, - useProtocolAnalysisErrors, - useRunCalibrationStatus, - useRunCreatedAtTimestamp, - useUnmatchedModulesForProtocol, - useIsRobotViewable, - useTrackProtocolRunEvent, - useRobotAnalyticsData, - useIsFlex, - useModuleCalibrationStatus, - useRobot, -} from '../hooks' -import { formatTimestamp } from '../utils' -import { RunTimer } from './RunTimer' -import { EMPTY_TIMESTAMP } from '../constants' -import { getHighestPriorityError } from '../../OnDeviceDisplay/RunningProtocol' -import { RunFailedModal } from './RunFailedModal' -import { RunProgressMeter } from '../../RunProgressMeter' -import { getIsFixtureMismatch } from '../../../resources/deck_configuration/utils' -import { useDeckConfigurationCompatibility } from '../../../resources/deck_configuration/hooks' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useMostRecentRunId } from '../../ProtocolUpload/hooks/useMostRecentRunId' -import { useNotifyRunQuery, useCurrentRunId } from '../../../resources/runs' -import { - useErrorRecoveryFlows, - ErrorRecoveryFlows, -} from '../../ErrorRecoveryFlows' -import { useRecoveryAnalytics } from '../../ErrorRecoveryFlows/hooks' -import { - useProtocolDropTipModal, - ProtocolDropTipModal, -} from './ProtocolDropTipModal' - -import type { - Run, - RunCommandErrors, - RunError, - RunStatus, -} from '@opentrons/api-client' -import type { IconName } from '@opentrons/components' -import type { State } from '../../../redux/types' -import type { HeaterShakerModule } from '../../../redux/modules/types' - -const EQUIPMENT_POLL_MS = 5000 -const CURRENT_RUN_POLL_MS = 5000 -const CANCELLABLE_STATUSES = [ - RUN_STATUS_RUNNING, - RUN_STATUS_PAUSED, - RUN_STATUS_BLOCKED_BY_OPEN_DOOR, - RUN_STATUS_IDLE, - RUN_STATUS_AWAITING_RECOVERY, - RUN_STATUS_AWAITING_RECOVERY_PAUSED, - RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, -] - -interface ProtocolRunHeaderProps { - protocolRunHeaderRef: React.RefObject | null - robotName: string - runId: string - makeHandleJumpToStep: (index: number) => () => void - missingSetupSteps: string[] -} - -export function ProtocolRunHeader({ - protocolRunHeaderRef, - robotName, - runId, - makeHandleJumpToStep, - missingSetupSteps, -}: ProtocolRunHeaderProps): JSX.Element | null { - const { t } = useTranslation(['run_details', 'shared']) - const navigate = useNavigate() - const host = useHost() - const createdAtTimestamp = useRunCreatedAtTimestamp(runId) - const { - protocolData, - displayName, - protocolKey, - isProtocolAnalyzing, - isQuickTransfer, - } = useProtocolDetailsForRun(runId) - const storedProtocol = useSelector((state: State) => - getStoredProtocol(state, protocolKey) - ) - const { reportRecoveredRunResult } = useRecoveryAnalytics() - - const { trackProtocolRunEvent } = useTrackProtocolRunEvent(runId, robotName) - const robotAnalyticsData = useRobotAnalyticsData(robotName) - const isRobotViewable = useIsRobotViewable(robotName) - const runStatus = useRunStatus(runId) - const { deleteRun } = useDeleteRunMutation() - const { analysisErrors } = useProtocolAnalysisErrors(runId) - const isRunCurrent = Boolean( - useNotifyRunQuery(runId, { refetchInterval: CURRENT_RUN_POLL_MS })?.data - ?.data?.current - ) - const mostRecentRunId = useMostRecentRunId() - const isMostRecentRun = mostRecentRunId === runId - const { closeCurrentRun, isClosingCurrentRun } = useCloseCurrentRun() - const { startedAt, stoppedAt, completedAt } = useRunTimestamps(runId) - const [showRunFailedModal, setShowRunFailedModal] = React.useState(false) - const { data: commandErrorList } = useRunCommandErrors( - runId, - { cursor: 0, pageLength: 100 }, - { - enabled: - runStatus != null && - // @ts-expect-error runStatus expected to possibly not be terminal - RUN_STATUSES_TERMINAL.includes(runStatus) && - isMostRecentRun, - } - ) - const isResetRunLoadingRef = React.useRef(false) - const { data: runRecord } = useNotifyRunQuery(runId, { staleTime: Infinity }) - const highestPriorityError = - runRecord?.data.errors?.[0] != null - ? getHighestPriorityError(runRecord?.data?.errors) - : null - - const robotSettings = useSelector((state: State) => - getRobotSettings(state, robotName) - ) - const isFlex = useIsFlex(robotName) - const robotProtocolAnalysis = useMostRecentCompletedAnalysis(runId) - const robotType = isFlex ? FLEX_ROBOT_TYPE : OT2_ROBOT_TYPE - const deckConfigCompatibility = useDeckConfigurationCompatibility( - robotType, - robotProtocolAnalysis - ) - const isFixtureMismatch = getIsFixtureMismatch(deckConfigCompatibility) - const { isERActive, failedCommand } = useErrorRecoveryFlows(runId, runStatus) - - const doorSafetySetting = robotSettings.find( - setting => setting.id === 'enableDoorSafetySwitch' - ) - const { data: doorStatus } = useDoorQuery({ - refetchInterval: EQUIPMENT_POLL_MS, - }) - let isDoorOpen: boolean - if (isFlex) { - isDoorOpen = doorStatus?.data.status === 'open' - } else if (!isFlex && Boolean(doorSafetySetting?.value)) { - isDoorOpen = doorStatus?.data.status === 'open' - } else { - isDoorOpen = false - } - - const { showDTWiz, toggleDTWiz } = useDropTipWizardFlows() - const { - areTipsAttached, - determineTipStatus, - resetTipStatus, - setTipStatusResolved, - aPipetteWithTip, - initialPipettesWithTipsCount, - } = useTipAttachmentStatus({ - runId, - runRecord: runRecord ?? null, - host, - }) - const { - showDTModal, - onDTModalSkip, - onDTModalRemoval, - isDisabled: areDTModalBtnsDisabled, - } = useProtocolDropTipModal({ - areTipsAttached, - toggleDTWiz, - isRunCurrent, - currentRunId: runId, - instrumentModelSpecs: aPipetteWithTip?.specs, - mount: aPipetteWithTip?.mount, - robotType, - onSkipAndHome: () => { - closeCurrentRun({ - onSuccess: () => { - if (isQuickTransfer) { - deleteRun(runId) - navigate(`/devices/${robotName}`) - } - }, - }) - }, - }) - - const enteredER = runRecord?.data.hasEverEnteredErrorRecovery ?? false - const cancelledWithoutRecovery = - !enteredER && runStatus === RUN_STATUS_STOPPED - - React.useEffect(() => { - if (isFlex) { - if (runStatus === RUN_STATUS_IDLE) { - resetTipStatus() - } else if ( - runStatus != null && - // @ts-expect-error runStatus expected to possibly not be terminal - RUN_STATUSES_TERMINAL.includes(runStatus) && - enteredER === false - ) { - void determineTipStatus() - } - } - }, [runStatus]) - - React.useEffect(() => { - if (protocolData != null && !isRobotViewable) { - navigate('/devices') - } - }, [protocolData, isRobotViewable, navigate]) - - React.useEffect(() => { - if (isRunCurrent && typeof enteredER === 'boolean') { - reportRecoveredRunResult(runStatus, enteredER) - } - }, [isRunCurrent, enteredER]) - - // Side effects dependent on the current run state. - React.useEffect(() => { - if (runStatus === RUN_STATUS_STOPPED && isRunCurrent && runId != null) { - trackProtocolRunEvent({ - name: ANALYTICS_PROTOCOL_RUN_ACTION.FINISH, - properties: { - ...robotAnalyticsData, - }, - }) - - // TODO(jh, 08-15-24): The enteredER condition is a hack, because errorCommands are only returned when a run is current. - // Ideally the run should not need to be current to view errorCommands. - - // Close the run if no tips are attached after running tip check at least once. - // This marks the robot as "not busy" as soon as a run is cancelled if drop tip CTAs are unnecessary. - if (initialPipettesWithTipsCount === 0 && !enteredER) { - closeCurrentRun({ - onSuccess: () => { - if (isQuickTransfer) { - deleteRun(runId) - navigate(`/devices/${robotName}`) - } - }, - }) - } - } - }, [runStatus, isRunCurrent, runId, enteredER, initialPipettesWithTipsCount]) - - const startedAtTimestamp = - startedAt != null ? formatTimestamp(startedAt) : EMPTY_TIMESTAMP - - const completedAtTimestamp = - completedAt != null ? formatTimestamp(completedAt) : EMPTY_TIMESTAMP - - // redirect to new run after successful reset - const onResetSuccess = (createRunResponse: Run): void => { - navigate( - `/devices/${robotName}/protocol-runs/${createRunResponse.data.id}/run-preview` - ) - } - - const { pause, play } = useRunControls(runId, onResetSuccess) - - const [showAnalysisErrorModal, setShowAnalysisErrorModal] = React.useState( - false - ) - const handleErrorModalCloseClick: React.MouseEventHandler = e => { - e.preventDefault() - e.stopPropagation() - setShowAnalysisErrorModal(false) - } - React.useEffect(() => { - if (analysisErrors != null && analysisErrors?.length > 0) { - setShowAnalysisErrorModal(true) - } - }, [analysisErrors]) - - const [ - showConfirmCancelModal, - setShowConfirmCancelModal, - ] = React.useState(false) - - const handleCancelClick = (): void => { - if (runStatus === RUN_STATUS_RUNNING) pause() - setShowConfirmCancelModal(true) - } - - const handleClearClick = (): void => { - trackProtocolRunEvent({ - name: ANALYTICS_PROTOCOL_RUN_ACTION.FINISH, - properties: robotAnalyticsData ?? undefined, - }) - closeCurrentRun({ - onSettled: () => { - if (isQuickTransfer) { - deleteRun(runId) - navigate(`/devices/${robotName}`) - } - }, - }) - } - - return ( - <> - {isERActive ? ( - - ) : null} - {showRunFailedModal ? ( - - ) : null} - - {showAnalysisErrorModal && - analysisErrors != null && - analysisErrors.length > 0 && ( - - )} - - {storedProtocol != null ? ( - - - {displayName} - - - ) : ( - - {displayName} - - )} - - {analysisErrors != null && analysisErrors.length > 0 && ( - - )} - {runStatus === RUN_STATUS_BLOCKED_BY_OPEN_DOOR ? ( - - {t('close_door_to_resume')} - - ) : null} - {runStatus === RUN_STATUS_STOPPED && !enteredER ? ( - - {t('run_canceled')} - - ) : null} - {/* Note: This banner is for before running a protocol */} - {isDoorOpen && - runStatus !== RUN_STATUS_BLOCKED_BY_OPEN_DOOR && - runStatus !== RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR && - runStatus != null && - CANCELLABLE_STATUSES.includes(runStatus) ? ( - - {t('shared:close_robot_door')} - - ) : null} - {isMostRecentRun ? ( - - ) : null} - {showDTModal ? ( - - ) : null} - - - } - /> - - } - /> - - - - - {runStatus != null ? ( - - - - - {CANCELLABLE_STATUSES.includes(runStatus) && ( - - {t('cancel_run')} - - )} - - - ) : null} - - {showConfirmCancelModal ? ( - { - setShowConfirmCancelModal(false) - }} - runId={runId} - robotName={robotName} - /> - ) : null} - {showDTWiz && aPipetteWithTip != null ? ( - { - if (isTakeover) { - toggleDTWiz() - } else { - void setTipStatusResolved(() => { - toggleDTWiz() - closeCurrentRun({ - onSuccess: () => { - if (isQuickTransfer) { - deleteRun(runId) - navigate(`/devices/${robotName}`) - } - }, - }) - }, toggleDTWiz) - } - }} - /> - ) : null} - - - ) -} - -interface LabeledValueProps { - label: string - value: React.ReactNode -} - -function LabeledValue(props: LabeledValueProps): JSX.Element { - return ( - - - {props.label} - - {typeof props.value === 'string' ? ( - {props.value} - ) : ( - props.value - )} - - ) -} - -interface DisplayRunStatusProps { - runStatus: RunStatus | null -} - -function DisplayRunStatus(props: DisplayRunStatusProps): JSX.Element { - const { t } = useTranslation('run_details') - return ( - - {props.runStatus === RUN_STATUS_RUNNING ? ( - - - - ) : null} - - {props.runStatus != null ? t(`status_${String(props.runStatus)}`) : ''} - - - ) -} - -const START_RUN_STATUSES: RunStatus[] = [ - RUN_STATUS_IDLE, - RUN_STATUS_PAUSED, - RUN_STATUS_BLOCKED_BY_OPEN_DOOR, -] -const RUN_AGAIN_STATUSES: RunStatus[] = [ - RUN_STATUS_STOPPED, - RUN_STATUS_FINISHING, - RUN_STATUS_FAILED, - RUN_STATUS_SUCCEEDED, -] -const RECOVERY_STATUSES: RunStatus[] = [ - RUN_STATUS_AWAITING_RECOVERY, - RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, - RUN_STATUS_AWAITING_RECOVERY_PAUSED, -] -const DISABLED_STATUSES: RunStatus[] = [ - RUN_STATUS_FINISHING, - RUN_STATUS_STOP_REQUESTED, - RUN_STATUS_BLOCKED_BY_OPEN_DOOR, - ...RECOVERY_STATUSES, -] -interface ActionButtonProps { - runId: string - robotName: string - runStatus: RunStatus | null - isProtocolAnalyzing: boolean - isDoorOpen: boolean - isFixtureMismatch: boolean - isResetRunLoadingRef: React.MutableRefObject - missingSetupSteps: string[] -} - -// TODO(jh, 04-22-2024): Refactor switch cases into separate factories to increase readability and testability. -function ActionButton(props: ActionButtonProps): JSX.Element { - const { - runId, - robotName, - runStatus, - isProtocolAnalyzing, - isDoorOpen, - isFixtureMismatch, - isResetRunLoadingRef, - missingSetupSteps, - } = props - const navigate = useNavigate() - const { t } = useTranslation(['run_details', 'shared']) - const attachedModules = - useModulesQuery({ - refetchInterval: EQUIPMENT_POLL_MS, - enabled: runStatus != null && START_RUN_STATUSES.includes(runStatus), - })?.data?.data ?? [] - const trackEvent = useTrackEvent() - const { trackProtocolRunEvent } = useTrackProtocolRunEvent(runId, robotName) - const [targetProps, tooltipProps] = useHoverTooltip() - const { - play, - pause, - reset, - isPlayRunActionLoading, - isPauseRunActionLoading, - isResetRunLoading, - } = useRunControls(runId, (createRunResponse: Run): void => - // redirect to new run after successful reset - { - navigate( - `/devices/${robotName}/protocol-runs/${createRunResponse.data.id}/run-preview` - ) - } - ) - isResetRunLoadingRef.current = isResetRunLoading - const { missingModuleIds } = useUnmatchedModulesForProtocol(robotName, runId) - const { complete: isCalibrationComplete } = useRunCalibrationStatus( - robotName, - runId - ) - const { complete: isModuleCalibrationComplete } = useModuleCalibrationStatus( - robotName, - runId - ) - const [showIsShakingModal, setShowIsShakingModal] = React.useState(false) - const isSetupComplete = - isCalibrationComplete && - isModuleCalibrationComplete && - missingModuleIds.length === 0 - const isRobotOnWrongVersionOfSoftware = ['upgrade', 'downgrade'].includes( - useSelector((state: State) => { - return getRobotUpdateDisplayInfo(state, robotName) - })?.autoUpdateAction - ) - const currentRunId = useCurrentRunId() - const isCurrentRun = currentRunId === runId - const isOtherRunCurrent = currentRunId != null && currentRunId !== runId - const isRunControlButtonDisabled = - (isCurrentRun && !isSetupComplete) || - isPlayRunActionLoading || - isPauseRunActionLoading || - isResetRunLoading || - isOtherRunCurrent || - isProtocolAnalyzing || - isFixtureMismatch || - (runStatus != null && DISABLED_STATUSES.includes(runStatus)) || - isRobotOnWrongVersionOfSoftware || - // For before running a protocol, "close door to begin". - (isDoorOpen && - runStatus !== RUN_STATUS_BLOCKED_BY_OPEN_DOOR && - runStatus != null && - CANCELLABLE_STATUSES.includes(runStatus)) - const robot = useRobot(robotName) - const robotSerialNumber = - robot?.status != null ? getRobotSerialNumber(robot) : null ?? '' - const handleProceedToRunClick = (): void => { - trackEvent({ - name: ANALYTICS_PROTOCOL_PROCEED_TO_RUN, - properties: { robotSerialNumber }, - }) - play() - } - const configBypassHeaterShakerAttachmentConfirmation = useSelector( - getIsHeaterShakerAttached - ) - const { - confirm: confirmAttachment, - showConfirmation: showHSConfirmationModal, - cancel: cancelExitHSConfirmation, - } = useConditionalConfirm( - handleProceedToRunClick, - !configBypassHeaterShakerAttachmentConfirmation - ) - const { - confirm: confirmMissingSteps, - showConfirmation: showMissingStepsConfirmationModal, - cancel: cancelExitMissingStepsConfirmation, - } = useConditionalConfirm( - handleProceedToRunClick, - missingSetupSteps.length !== 0 - ) - const robotAnalyticsData = useRobotAnalyticsData(robotName) - - const isHeaterShakerInProtocol = useIsHeaterShakerInProtocol() - const activeHeaterShaker = attachedModules.find( - (module): module is HeaterShakerModule => - module.moduleType === 'heaterShakerModuleType' && - module?.data != null && - module.data.speedStatus !== 'idle' - ) - const isHeaterShakerShaking = attachedModules - .filter((module): module is HeaterShakerModule => { - return module.moduleType === 'heaterShakerModuleType' - }) - .some(module => module?.data != null && module.data.speedStatus !== 'idle') - const isValidRunAgain = - runStatus != null && RUN_AGAIN_STATUSES.includes(runStatus) - const validRunAgainButRequiresSetup = isValidRunAgain && !isSetupComplete - const runAgainWithSpinner = validRunAgainButRequiresSetup && isResetRunLoading - - let buttonText: string = '' - let handleButtonClick = (): void => {} - let buttonIconName: IconName | null = null - let disableReason = null - - if ( - currentRunId === runId && - (!isSetupComplete || isFixtureMismatch) && - !isValidRunAgain - ) { - disableReason = t('setup_incomplete') - } else if (isOtherRunCurrent) { - disableReason = t('shared:robot_is_busy') - } else if (isRobotOnWrongVersionOfSoftware) { - disableReason = t('shared:a_software_update_is_available') - } else if ( - isDoorOpen && - runStatus != null && - START_RUN_STATUSES.includes(runStatus) - ) { - disableReason = t('close_door') - } - - const shouldShowHSConfirm = - isHeaterShakerInProtocol && - !isHeaterShakerShaking && - (runStatus === RUN_STATUS_IDLE || runStatus === RUN_STATUS_STOPPED) - - if (isProtocolAnalyzing) { - buttonIconName = 'ot-spinner' - buttonText = t('analyzing_on_robot') - } else if ( - runStatus === RUN_STATUS_RUNNING || - (runStatus != null && RECOVERY_STATUSES.includes(runStatus)) - ) { - buttonIconName = 'pause' - buttonText = t('pause_run') - handleButtonClick = (): void => { - pause() - trackProtocolRunEvent({ name: ANALYTICS_PROTOCOL_RUN_ACTION.PAUSE }) - } - } else if (runStatus === RUN_STATUS_STOP_REQUESTED) { - buttonIconName = 'ot-spinner' - buttonText = t('canceling_run') - } else if (runStatus != null && START_RUN_STATUSES.includes(runStatus)) { - buttonIconName = 'play' - buttonText = - runStatus === RUN_STATUS_IDLE ? t('start_run') : t('resume_run') - handleButtonClick = () => { - if (isHeaterShakerShaking && isHeaterShakerInProtocol) { - setShowIsShakingModal(true) - } else if ( - missingSetupSteps.length !== 0 && - (runStatus === RUN_STATUS_IDLE || runStatus === RUN_STATUS_STOPPED) - ) { - confirmMissingSteps() - } else if (shouldShowHSConfirm) { - confirmAttachment() - } else { - play() - navigate(`/devices/${robotName}/protocol-runs/${runId}/run-preview`) - trackProtocolRunEvent({ - name: - runStatus === RUN_STATUS_IDLE - ? ANALYTICS_PROTOCOL_RUN_ACTION.START - : ANALYTICS_PROTOCOL_RUN_ACTION.RESUME, - properties: - runStatus === RUN_STATUS_IDLE && robotAnalyticsData != null - ? robotAnalyticsData - : {}, - }) - } - } - } else if (runStatus != null && RUN_AGAIN_STATUSES.includes(runStatus)) { - buttonIconName = runAgainWithSpinner ? 'ot-spinner' : 'play' - buttonText = t('run_again') - handleButtonClick = () => { - reset() - trackEvent({ - name: ANALYTICS_PROTOCOL_PROCEED_TO_RUN, - properties: { sourceLocation: 'RunRecordDetail', robotSerialNumber }, - }) - trackProtocolRunEvent({ - name: ANALYTICS_PROTOCOL_RUN_ACTION.AGAIN, - }) - } - } - - return ( - <> - - {buttonIconName != null ? ( - - ) : null} - - {buttonText} - - - {disableReason != null && ( - - {disableReason} - - )} - {showIsShakingModal && - activeHeaterShaker != null && - isHeaterShakerInProtocol && - runId != null && ( - { - setShowIsShakingModal(false) - }} - module={activeHeaterShaker} - startRun={play} - /> - )} - {showHSConfirmationModal && ( - - )} - {showMissingStepsConfirmationModal && ( - { - shouldShowHSConfirm - ? confirmAttachment() - : handleProceedToRunClick() - }} - missingSteps={missingSetupSteps} - /> - )} - {} - - ) -} - -// TODO(jh 04-24-2024): Split TerminalRunBanner into a RunSuccessBanner and RunFailedBanner. -interface TerminalRunProps { - runStatus: RunStatus | null - handleClearClick: () => void - isClosingCurrentRun: boolean - setShowRunFailedModal: (showRunFailedModal: boolean) => void - commandErrorList?: RunCommandErrors - isResetRunLoading: boolean - isRunCurrent: boolean - cancelledWithoutRecovery: boolean - highestPriorityError?: RunError | null -} -function TerminalRunBanner(props: TerminalRunProps): JSX.Element | null { - const { - runStatus, - handleClearClick, - isClosingCurrentRun, - setShowRunFailedModal, - commandErrorList, - highestPriorityError, - isResetRunLoading, - isRunCurrent, - cancelledWithoutRecovery, - } = props - const { t } = useTranslation('run_details') - const completedWithErrors = - commandErrorList?.data != null && commandErrorList.data.length > 0 - - const handleRunSuccessClick = (): void => { - handleClearClick() - } - - const handleFailedRunClick = (): void => { - // TODO(jh, 08-15-24): See TODO related to commandErrorList above. - if (commandErrorList == null) { - handleClearClick() - } - setShowRunFailedModal(true) - } - - const buildSuccessBanner = (): JSX.Element => { - return ( - - - {t('run_completed')} - - - ) - } - - const buildErrorBanner = (): JSX.Element => { - return ( - - - - {highestPriorityError != null - ? t('error_info', { - errorType: highestPriorityError?.errorType, - errorCode: highestPriorityError?.errorCode, - }) - : `${ - runStatus === RUN_STATUS_SUCCEEDED - ? t('run_completed_with_warnings') - : t('run_canceled_with_errors') - }`} - - - - {runStatus === RUN_STATUS_SUCCEEDED - ? t('view_warning_details') - : t('view_error_details')} - - - - ) - } - - if ( - runStatus === RUN_STATUS_SUCCEEDED && - isRunCurrent && - !isResetRunLoading && - !completedWithErrors - ) { - return buildSuccessBanner() - } - // TODO(jh, 08-14-24): Ideally, the backend never returns the "user cancelled a run" error and cancelledWithoutRecovery becomes unnecessary. - else if ( - !cancelledWithoutRecovery && - !isResetRunLoading && - (highestPriorityError != null || completedWithErrors) - ) { - return buildErrorBanner() - } else { - return null - } -} diff --git a/app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx b/app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx deleted file mode 100644 index 581fd16fec1..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/ProtocolRunSetup.tsx +++ /dev/null @@ -1,559 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' - -import { - ALIGN_CENTER, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - Icon, - Link, - SPACING, - LegacyStyledText, - TYPOGRAPHY, - FLEX_MAX_CONTENT, -} from '@opentrons/components' -import { - FLEX_ROBOT_TYPE, - OT2_ROBOT_TYPE, - parseAllRequiredModuleModels, -} from '@opentrons/shared-data' - -import { Line } from '../../../atoms/structure' -import { InfoMessage } from '../../../molecules/InfoMessage' -import { INCOMPATIBLE, INEXACT_MATCH } from '../../../redux/pipettes' -import { - getIsFixtureMismatch, - getRequiredDeckConfig, -} from '../../../resources/deck_configuration/utils' -import { useDeckConfigurationCompatibility } from '../../../resources/deck_configuration/hooks' -import { - useIsFlex, - useModuleCalibrationStatus, - useProtocolAnalysisErrors, - useRobot, - useRunCalibrationStatus, - useRunHasStarted, - useRunPipetteInfoByMount, - useStoredProtocolAnalysis, - useUnmatchedModulesForProtocol, -} from '../hooks' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { SetupLabware } from './SetupLabware' -import { SetupLabwarePositionCheck } from './SetupLabwarePositionCheck' -import { SetupRobotCalibration } from './SetupRobotCalibration' -import { SetupModuleAndDeck } from './SetupModuleAndDeck' -import { SetupStep } from './SetupStep' -import { SetupLiquids } from './SetupLiquids' -import { EmptySetupStep } from './EmptySetupStep' -import { HowLPCWorksModal } from './SetupLabwarePositionCheck/HowLPCWorksModal' - -const ROBOT_CALIBRATION_STEP_KEY = 'robot_calibration_step' as const -const MODULE_SETUP_KEY = 'module_setup_step' as const -const LPC_KEY = 'labware_position_check_step' as const -const LABWARE_SETUP_KEY = 'labware_setup_step' as const -const LIQUID_SETUP_KEY = 'liquid_setup_step' as const - -export type StepKey = - | typeof ROBOT_CALIBRATION_STEP_KEY - | typeof MODULE_SETUP_KEY - | typeof LPC_KEY - | typeof LABWARE_SETUP_KEY - | typeof LIQUID_SETUP_KEY - -export type MissingStep = - | 'applied_labware_offsets' - | 'labware_placement' - | 'liquids' - -export type MissingSteps = MissingStep[] - -export const initialMissingSteps = (): MissingSteps => [ - 'applied_labware_offsets', - 'labware_placement', - 'liquids', -] - -interface ProtocolRunSetupProps { - protocolRunHeaderRef: React.RefObject | null - robotName: string - runId: string - setMissingSteps: (missingSteps: MissingSteps) => void - missingSteps: MissingSteps -} - -export function ProtocolRunSetup({ - protocolRunHeaderRef, - robotName, - runId, - setMissingSteps, - missingSteps, -}: ProtocolRunSetupProps): JSX.Element | null { - const { t, i18n } = useTranslation('protocol_setup') - const robotProtocolAnalysis = useMostRecentCompletedAnalysis(runId) - const storedProtocolAnalysis = useStoredProtocolAnalysis(runId) - const protocolAnalysis = robotProtocolAnalysis ?? storedProtocolAnalysis - const modules = parseAllRequiredModuleModels(protocolAnalysis?.commands ?? []) - - const robot = useRobot(robotName) - const calibrationStatusRobot = useRunCalibrationStatus(robotName, runId) - const calibrationStatusModules = useModuleCalibrationStatus(robotName, runId) - const { missingModuleIds } = useUnmatchedModulesForProtocol(robotName, runId) - const isFlex = useIsFlex(robotName) - const runHasStarted = useRunHasStarted(runId) - const { analysisErrors } = useProtocolAnalysisErrors(runId) - const [expandedStepKey, setExpandedStepKey] = React.useState( - null - ) - const robotType = isFlex ? FLEX_ROBOT_TYPE : OT2_ROBOT_TYPE - const deckConfigCompatibility = useDeckConfigurationCompatibility( - robotType, - protocolAnalysis - ) - const runPipetteInfoByMount = useRunPipetteInfoByMount(runId) - - const isMissingPipette = - (runPipetteInfoByMount.left != null && - runPipetteInfoByMount.left.requestedPipetteMatch === INCOMPATIBLE) || - (runPipetteInfoByMount.right != null && - runPipetteInfoByMount.right.requestedPipetteMatch === INCOMPATIBLE) || - // for Flex, require exact match - (isFlex && - runPipetteInfoByMount.left != null && - runPipetteInfoByMount.left.requestedPipetteMatch === INEXACT_MATCH) || - (isFlex && - runPipetteInfoByMount.right != null && - runPipetteInfoByMount.right.requestedPipetteMatch === INEXACT_MATCH) - - const isFixtureMismatch = getIsFixtureMismatch(deckConfigCompatibility) - - const isMissingModule = missingModuleIds.length > 0 - - const stepsKeysInOrder = - protocolAnalysis != null - ? [ - ROBOT_CALIBRATION_STEP_KEY, - MODULE_SETUP_KEY, - LPC_KEY, - LABWARE_SETUP_KEY, - LIQUID_SETUP_KEY, - ] - : [ROBOT_CALIBRATION_STEP_KEY, LPC_KEY, LABWARE_SETUP_KEY] - - const targetStepKeyInOrder = stepsKeysInOrder.filter((stepKey: StepKey) => { - if (protocolAnalysis == null) { - return stepKey !== MODULE_SETUP_KEY && stepKey !== LIQUID_SETUP_KEY - } - - if ( - protocolAnalysis.modules.length === 0 && - protocolAnalysis.liquids.length === 0 - ) { - return stepKey !== MODULE_SETUP_KEY && stepKey !== LIQUID_SETUP_KEY - } - - if (protocolAnalysis.modules.length === 0) { - return stepKey !== MODULE_SETUP_KEY - } - - if (protocolAnalysis.liquids.length === 0) { - return stepKey !== LIQUID_SETUP_KEY - } - return true - }) - - const liquids = protocolAnalysis?.liquids ?? [] - const hasLiquids = liquids.length > 0 - const hasModules = protocolAnalysis != null && modules.length > 0 - // need config compatibility (including check for single slot conflicts) - const requiredDeckConfigCompatibility = getRequiredDeckConfig( - deckConfigCompatibility - ) - const hasFixtures = requiredDeckConfigCompatibility.length > 0 - const flexDeckHardwareDescription = - hasModules || hasFixtures - ? t('install_modules_and_fixtures') - : t('no_deck_hardware_specified') - const ot2DeckHardwareDescription = hasModules - ? t('install_modules', { count: modules.length }) - : t('no_deck_hardware_specified') - - const [ - labwareSetupComplete, - setLabwareSetupComplete, - ] = React.useState(false) - const [liquidSetupComplete, setLiquidSetupComplete] = React.useState( - !hasLiquids - ) - if ( - !hasLiquids && - protocolAnalysis != null && - missingSteps.includes('liquids') - ) { - setMissingSteps(missingSteps.filter(step => step !== 'liquids')) - } - const [lpcComplete, setLpcComplete] = React.useState(false) - if (robot == null) { - return null - } - const StepDetailMap: Record< - StepKey, - { - stepInternals: JSX.Element - description: string - rightElProps: StepRightElementProps - } - > = { - [ROBOT_CALIBRATION_STEP_KEY]: { - stepInternals: ( - v === ROBOT_CALIBRATION_STEP_KEY - ) + 1 - ] - } - expandStep={setExpandedStepKey} - calibrationStatus={calibrationStatusRobot} - /> - ), - // change description for Flex - description: isFlex - ? t(`${ROBOT_CALIBRATION_STEP_KEY}_description_pipettes_only`) - : t(`${ROBOT_CALIBRATION_STEP_KEY}_description`), - rightElProps: { - stepKey: ROBOT_CALIBRATION_STEP_KEY, - complete: calibrationStatusRobot.complete, - completeText: t('calibration_ready'), - missingHardware: isMissingPipette, - incompleteText: t('calibration_needed'), - missingHardwareText: t('action_needed'), - incompleteElement: null, - }, - }, - [MODULE_SETUP_KEY]: { - stepInternals: ( - { - setExpandedStepKey(LPC_KEY) - }} - robotName={robotName} - runId={runId} - hasModules={hasModules} - protocolAnalysis={protocolAnalysis} - /> - ), - description: isFlex - ? flexDeckHardwareDescription - : ot2DeckHardwareDescription, - rightElProps: { - stepKey: MODULE_SETUP_KEY, - complete: - calibrationStatusModules.complete && - !isMissingModule && - !isFixtureMismatch, - completeText: - isFlex && hasModules - ? t('calibration_ready') - : t('deck_hardware_ready'), - incompleteText: - isFlex && hasModules ? t('calibration_needed') : t('action_needed'), - missingHardware: isMissingModule || isFixtureMismatch, - missingHardwareText: t('action_needed'), - incompleteElement: null, - }, - }, - [LPC_KEY]: { - stepInternals: ( - { - setLpcComplete(confirmed) - if (confirmed) { - setExpandedStepKey(LABWARE_SETUP_KEY) - setMissingSteps( - missingSteps.filter(step => step !== 'applied_labware_offsets') - ) - } - }} - offsetsConfirmed={lpcComplete} - /> - ), - description: t('labware_position_check_step_description'), - rightElProps: { - stepKey: LPC_KEY, - complete: lpcComplete, - completeText: t('offsets_ready'), - incompleteText: null, - incompleteElement: , - }, - }, - [LABWARE_SETUP_KEY]: { - stepInternals: ( - { - setLabwareSetupComplete(confirmed) - if (confirmed) { - setMissingSteps( - missingSteps.filter(step => step !== 'labware_placement') - ) - const nextStep = - targetStepKeyInOrder.findIndex(v => v === LABWARE_SETUP_KEY) === - targetStepKeyInOrder.length - 1 - ? null - : LIQUID_SETUP_KEY - setExpandedStepKey(nextStep) - } - }} - /> - ), - description: t(`${LABWARE_SETUP_KEY}_description`), - rightElProps: { - stepKey: LABWARE_SETUP_KEY, - complete: labwareSetupComplete, - completeText: t('placements_ready'), - incompleteText: null, - incompleteElement: null, - }, - }, - [LIQUID_SETUP_KEY]: { - stepInternals: ( - { - setLiquidSetupComplete(confirmed) - if (confirmed) { - setMissingSteps(missingSteps.filter(step => step !== 'liquids')) - setExpandedStepKey(null) - } - }} - /> - ), - description: hasLiquids - ? t(`${LIQUID_SETUP_KEY}_description`) - : i18n.format(t('liquids_not_in_the_protocol'), 'capitalize'), - rightElProps: { - stepKey: LIQUID_SETUP_KEY, - complete: liquidSetupComplete, - completeText: t('liquids_ready'), - incompleteText: null, - incompleteElement: null, - }, - }, - } - - return ( - - {protocolAnalysis != null ? ( - <> - {runHasStarted ? ( - - ) : null} - {analysisErrors != null && analysisErrors?.length > 0 ? ( - - {t('protocol_analysis_failed')} - - ) : ( - stepsKeysInOrder.map((stepKey, index) => { - const setupStepTitle = t(`${stepKey}_title`) - const showEmptySetupStep = - (stepKey === 'liquid_setup_step' && !hasLiquids) || - (stepKey === 'module_setup_step' && - ((!isFlex && !hasModules) || - (isFlex && !hasModules && !hasFixtures))) - return ( - - {showEmptySetupStep ? ( - - } - /> - ) : ( - { - stepKey === expandedStepKey - ? setExpandedStepKey(null) - : setExpandedStepKey(stepKey) - }} - rightElement={ - - } - > - {StepDetailMap[stepKey].stepInternals} - - )} - {index !== stepsKeysInOrder.length - 1 ? ( - - ) : null} - - ) - }) - )} - - ) : ( - - {t('loading_data')} - - )} - - ) -} - -interface NoHardwareRequiredStepCompletion { - stepKey: Exclude< - StepKey, - typeof ROBOT_CALIBRATION_STEP_KEY | typeof MODULE_SETUP_KEY - > - complete: boolean - incompleteText: string | null - incompleteElement: JSX.Element | null - completeText: string -} - -interface HardwareRequiredStepCompletion { - stepKey: typeof ROBOT_CALIBRATION_STEP_KEY | typeof MODULE_SETUP_KEY - complete: boolean - missingHardware: boolean - incompleteText: string | null - incompleteElement: JSX.Element | null - completeText: string - missingHardwareText: string -} - -type StepRightElementProps = - | NoHardwareRequiredStepCompletion - | HardwareRequiredStepCompletion - -const stepRequiresHW = ( - props: StepRightElementProps -): props is HardwareRequiredStepCompletion => - props.stepKey === ROBOT_CALIBRATION_STEP_KEY || - props.stepKey === MODULE_SETUP_KEY - -function StepRightElement(props: StepRightElementProps): JSX.Element | null { - if (props.complete) { - return ( - - - - {props.completeText} - - - ) - } else if (stepRequiresHW(props) && props.missingHardware) { - return ( - - - - {props.missingHardwareText} - - - ) - } else if (props.incompleteText != null) { - return ( - - - - {props.incompleteText} - - - ) - } else if (props.incompleteElement != null) { - return props.incompleteElement - } else { - return null - } -} - -function LearnAboutLPC(): JSX.Element { - const { t } = useTranslation('protocol_setup') - const [showLPCHelpModal, setShowLPCHelpModal] = React.useState(false) - return ( - <> - { - // clicking link shouldn't toggle step expanded state - e.preventDefault() - e.stopPropagation() - setShowLPCHelpModal(true) - }} - > - {t('learn_how_it_works')} - - {showLPCHelpModal ? ( - { - setShowLPCHelpModal(false) - }} - /> - ) : null} - - ) -} diff --git a/app/src/organisms/Devices/ProtocolRun/RunFailedModal.tsx b/app/src/organisms/Devices/ProtocolRun/RunFailedModal.tsx deleted file mode 100644 index 0ce8e2c6e50..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/RunFailedModal.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { css } from 'styled-components' - -import { - ALIGN_CENTER, - BORDERS, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - Icon, - JUSTIFY_SPACE_BETWEEN, - Link, - OVERFLOW_AUTO, - OVERFLOW_WRAP_ANYWHERE, - PrimaryButton, - SPACING, - Modal, - LegacyStyledText, - TYPOGRAPHY, - DISPLAY_FLEX, -} from '@opentrons/components' - -import { useDownloadRunLog } from '../hooks' -import { RUN_STATUS_SUCCEEDED } from '@opentrons/api-client' - -import type { - RunError, - RunCommandErrors, - RunStatus, -} from '@opentrons/api-client' -import type { ModalProps } from '@opentrons/components' -import type { RunCommandError } from '@opentrons/shared-data' - -/** - * This modal is for Desktop app - * @param robotName - Robot name - * @param runId - Run protocol id - * @param setShowRunFailedModal - For closing modal - * @param highestPriorityError - Run error information - * - * @returns JSX.Element | null - */ -// Note(kk:08/07/2023) -// This modal and run failed modal for Touchscreen app will be merged into one component like EstopModals. - -interface RunFailedModalProps { - robotName: string - runId: string - setShowRunFailedModal: (showRunFailedModal: boolean) => void - highestPriorityError?: RunError | null - commandErrorList?: RunCommandErrors | null - runStatus: RunStatus | null -} - -export function RunFailedModal({ - robotName, - runId, - setShowRunFailedModal, - highestPriorityError, - commandErrorList, - runStatus, -}: RunFailedModalProps): JSX.Element | null { - const { i18n, t } = useTranslation(['run_details', 'shared', 'branded']) - const modalProps: ModalProps = { - type: runStatus === RUN_STATUS_SUCCEEDED ? 'warning' : 'error', - title: - commandErrorList == null || commandErrorList?.data.length === 0 - ? t('run_failed_modal_title') - : runStatus === RUN_STATUS_SUCCEEDED - ? t('warning_details') - : t('error_details'), - onClose: () => { - setShowRunFailedModal(false) - }, - closeOnOutsideClick: true, - childrenPadding: SPACING.spacing24, - width: '31.25rem', - } - const { downloadRunLog } = useDownloadRunLog(robotName, runId) - - if (highestPriorityError == null && commandErrorList == null) return null - - const handleClick = (): void => { - setShowRunFailedModal(false) - } - - const handleDownloadClick: React.MouseEventHandler = e => { - e.preventDefault() - e.stopPropagation() - downloadRunLog() - } - - interface ErrorContentProps { - errors: RunCommandError[] - isSingleError: boolean - } - const ErrorContent = ({ - errors, - isSingleError, - }: ErrorContentProps): JSX.Element => { - return ( - - - {isSingleError - ? t('error_info', { - errorType: errors[0].errorType, - errorCode: errors[0].errorCode, - }) - : runStatus === RUN_STATUS_SUCCEEDED - ? t(errors.length > 1 ? 'no_of_warnings' : 'no_of_warning', { - count: errors.length, - }) - : t(errors.length > 1 ? 'no_of_errors' : 'no_of_error', { - count: errors.length, - })} - - - {' '} - {errors.map((error, index) => ( - - {' '} - {isSingleError - ? error.detail - : `${error.errorCode}: ${error.detail}`} - - ))} - - - ) - } - - return ( - - - 0 - ? commandErrorList?.data - : [] - } - isSingleError={!!highestPriorityError} - /> - - {t('branded:run_failed_modal_description_desktop')} - - - - - - {i18n.format(t('download_run_log'), 'titleCase')} - - - - {i18n.format(t('shared:close'), 'capitalize')} - - - - - ) -} - -const ERROR_MESSAGE_STYLE = css` - display: ${DISPLAY_FLEX}; - flex-direction: ${DIRECTION_COLUMN}; - max-height: 9.5rem; - overflow-y: ${OVERFLOW_AUTO}; - margin-top: ${SPACING.spacing8}; - margin-bottom: ${SPACING.spacing16}; - padding: ${`${SPACING.spacing8} ${SPACING.spacing12}`}; - background-color: ${COLORS.grey30}; - border-radius: ${BORDERS.borderRadius8}; - overflow-wrap: ${OVERFLOW_WRAP_ANYWHERE}; - - ::-webkit-scrollbar-thumb { - background: ${COLORS.grey40}; - } -` diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx deleted file mode 100644 index 1c9f05aaa47..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/LabwareListItem.tsx +++ /dev/null @@ -1,425 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import styled, { css } from 'styled-components' - -import { - ALIGN_CENTER, - BORDERS, - Btn, - COLORS, - DeckInfoLabel, - DIRECTION_COLUMN, - DIRECTION_ROW, - DISPLAY_FLEX, - Flex, - Icon, - JUSTIFY_CENTER, - JUSTIFY_SPACE_BETWEEN, - MODULE_ICON_NAME_BY_TYPE, - LabwareRender, - SIZE_AUTO, - SPACING, - StyledText, - TYPOGRAPHY, - WELL_LABEL_OPTIONS, -} from '@opentrons/components' -import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' -import { - getLabwareDisplayName, - getModuleDisplayName, - getModuleType, - HEATERSHAKER_MODULE_TYPE, - MAGNETIC_MODULE_TYPE, - TC_MODULE_LOCATION_OT2, - TC_MODULE_LOCATION_OT3, - THERMOCYCLER_MODULE_TYPE, - THERMOCYCLER_MODULE_V2, -} from '@opentrons/shared-data' - -import { ToggleButton } from '../../../../atoms/buttons' -import { Divider } from '../../../../atoms/structure' -import { SecureLabwareModal } from './SecureLabwareModal' - -import type { - HeaterShakerCloseLatchCreateCommand, - HeaterShakerOpenLatchCreateCommand, - RunTimeCommand, - ModuleType, - LabwareDefinition2, - LoadModuleRunTimeCommand, - LoadLabwareRunTimeCommand, -} from '@opentrons/shared-data' -import type { ModuleRenderInfoForProtocol } from '../../hooks' -import type { LabwareSetupItem } from '../../../../pages/Protocols/utils' -import type { ModuleTypesThatRequireExtraAttention } from '../utils/getModuleTypesThatRequireExtraAttention' -import type { NestedLabwareInfo } from './getNestedLabwareInfo' - -const LabwareRow = styled.div` - display: grid; - grid-template-columns: 90px 12fr; - border-style: ${BORDERS.styleSolid}; - border-width: 1px; - border-color: ${COLORS.grey30}; - border-radius: ${BORDERS.borderRadius4}; - padding: ${SPACING.spacing12} ${SPACING.spacing16} ${SPACING.spacing12} - ${SPACING.spacing24}; -` - -interface LabwareListItemProps extends LabwareSetupItem { - attachedModuleInfo: { [moduleId: string]: ModuleRenderInfoForProtocol } - extraAttentionModules: ModuleTypesThatRequireExtraAttention[] - isFlex: boolean - commands: RunTimeCommand[] - nestedLabwareInfo: NestedLabwareInfo | null - showLabwareSVG?: boolean -} - -export function LabwareListItem( - props: LabwareListItemProps -): JSX.Element | null { - const { - attachedModuleInfo, - nickName, - initialLocation, - definition, - moduleModel, - moduleLocation, - extraAttentionModules, - isFlex, - commands, - nestedLabwareInfo, - showLabwareSVG, - } = props - const { i18n, t } = useTranslation('protocol_setup') - const [ - secureLabwareModalType, - setSecureLabwareModalType, - ] = React.useState(null) - const labwareDisplayName = getLabwareDisplayName(definition) - const { createLiveCommand } = useCreateLiveCommandMutation() - const [isLatchLoading, setIsLatchLoading] = React.useState(false) - const [isLatchClosed, setIsLatchClosed] = React.useState(false) - - let slotInfo: string | null = null - - if (initialLocation !== 'offDeck' && 'slotName' in initialLocation) { - slotInfo = initialLocation.slotName - } else if ( - initialLocation !== 'offDeck' && - 'addressableAreaName' in initialLocation - ) { - slotInfo = initialLocation.addressableAreaName - } else if (initialLocation === 'offDeck') { - slotInfo = i18n.format(t('off_deck'), 'upperCase') - } - - let moduleDisplayName: string | null = null - let moduleType: ModuleType | null = null - let extraAttentionText: JSX.Element | null = null - let secureLabwareInstructions: JSX.Element | null = null - let isCorrectHeaterShakerAttached: boolean = false - let isHeaterShakerInProtocol: boolean = false - let latchCommand: - | HeaterShakerOpenLatchCreateCommand - | HeaterShakerCloseLatchCreateCommand - - if (initialLocation !== 'offDeck' && 'labwareId' in initialLocation) { - const loadedAdapter = commands.find( - (command): command is LoadLabwareRunTimeCommand => - command.commandType === 'loadLabware' && - command.result?.labwareId === initialLocation.labwareId - ) - const loadedAdapterLocation = loadedAdapter?.params.location - - if (loadedAdapterLocation != null && loadedAdapterLocation !== 'offDeck') { - if ('slotName' in loadedAdapterLocation) { - slotInfo = loadedAdapterLocation.slotName - } else if ('moduleId' in loadedAdapterLocation) { - const module = commands.find( - (command): command is LoadModuleRunTimeCommand => - command.commandType === 'loadModule' && - command.result?.moduleId === loadedAdapterLocation.moduleId - ) - if (module != null) { - slotInfo = module.params.location.slotName - moduleDisplayName = getModuleDisplayName(module.params.model) - } - } - } - } - if ( - initialLocation !== 'offDeck' && - 'moduleId' in initialLocation && - moduleLocation != null && - moduleModel != null - ) { - const moduleName = getModuleDisplayName(moduleModel) - moduleType = getModuleType(moduleModel) - const moduleTypeNeedsAttention = extraAttentionModules.find( - extraAttentionModType => extraAttentionModType === moduleType - ) - let moduleSlotName = moduleLocation.slotName - if (moduleType === THERMOCYCLER_MODULE_TYPE) { - moduleSlotName = isFlex ? TC_MODULE_LOCATION_OT3 : TC_MODULE_LOCATION_OT2 - } - slotInfo = moduleSlotName - moduleDisplayName = moduleName - switch (moduleTypeNeedsAttention) { - case MAGNETIC_MODULE_TYPE: - case THERMOCYCLER_MODULE_TYPE: - if (moduleModel !== THERMOCYCLER_MODULE_V2) { - secureLabwareInstructions = ( - { - setSecureLabwareModalType(moduleType) - }} - > - - - - {t('secure_labware_instructions')} - - - - ) - } - break - case HEATERSHAKER_MODULE_TYPE: - isHeaterShakerInProtocol = true - extraAttentionText = ( - - {t('heater_shaker_labware_list_view')} - - ) - const matchingHeaterShaker = - attachedModuleInfo != null && - attachedModuleInfo[initialLocation.moduleId] != null - ? attachedModuleInfo[initialLocation.moduleId].attachedModuleMatch - : null - if ( - matchingHeaterShaker != null && - matchingHeaterShaker.moduleType === HEATERSHAKER_MODULE_TYPE - ) { - if ( - (!isLatchClosed && - (matchingHeaterShaker.data.labwareLatchStatus === 'idle_closed' || - matchingHeaterShaker.data.labwareLatchStatus === 'closing')) || - (isLatchClosed && - (matchingHeaterShaker.data.labwareLatchStatus === 'idle_open' || - matchingHeaterShaker.data.labwareLatchStatus === 'opening')) - ) { - setIsLatchClosed( - matchingHeaterShaker.data.labwareLatchStatus === 'idle_closed' || - matchingHeaterShaker.data.labwareLatchStatus === 'closing' - ) - setIsLatchLoading(false) - } - latchCommand = { - commandType: isLatchClosed - ? 'heaterShaker/openLabwareLatch' - : 'heaterShaker/closeLabwareLatch', - params: { moduleId: matchingHeaterShaker.id }, - } - // Labware latch button is disabled unless the correct H-S is attached - // this is for MoaM support - isCorrectHeaterShakerAttached = true - } - } - } - const toggleLatch = (): void => { - setIsLatchLoading(true) - createLiveCommand({ - command: latchCommand, - }).catch((e: Error) => { - console.error( - `error setting module status with command type ${latchCommand.commandType}: ${e.message}` - ) - }) - } - const commandType = isLatchClosed - ? 'heaterShaker/openLabwareLatch' - : 'heaterShaker/closeLabwareLatch' - let hsLatchText: string = t('secure') - if (commandType === 'heaterShaker/closeLabwareLatch' && isLatchLoading) { - hsLatchText = t('closing') - } else if ( - commandType === 'heaterShaker/openLabwareLatch' && - isLatchLoading - ) { - hsLatchText = t('opening') - } - - return ( - - - {slotInfo != null && isFlex ? ( - - ) : ( - - {slotInfo} - - )} - {nestedLabwareInfo != null || moduleDisplayName != null ? ( - - ) : null} - - - {nestedLabwareInfo != null && - nestedLabwareInfo?.sharedSlotId === slotInfo ? ( - <> - - - - {nestedLabwareInfo.nestedLabwareDisplayName} - - - {nestedLabwareInfo.nestedLabwareNickName} - - - - - - ) : null} - - {showLabwareSVG ? ( - - ) : null} - - - {labwareDisplayName} - - - {nickName} - - - - {moduleDisplayName != null ? ( - <> - - - - {moduleType != null ? ( - - ) : null} - - - {moduleDisplayName} - - {extraAttentionText} - - - {secureLabwareInstructions} - {isHeaterShakerInProtocol ? ( - - - {t('labware_latch')} - - - - - {hsLatchText} - - - - ) : null} - - - ) : null} - - {secureLabwareModalType != null && ( - { - setSecureLabwareModalType(null) - }} - /> - )} - - ) -} - -const LabwareThumbnail = styled.svg` - transform: scale(1, -1); - width: 4.2rem; - flex-shrink: 0; -` - -function StandaloneLabware(props: { - definition: LabwareDefinition2 -}): JSX.Element { - const { definition } = props - return ( - - - - ) -} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/LabwareStackModal.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabware/LabwareStackModal.tsx deleted file mode 100644 index 1dcc7bb1a48..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/LabwareStackModal.tsx +++ /dev/null @@ -1,285 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { css } from 'styled-components' -import { - ALIGN_CENTER, - Box, - COLORS, - DeckInfoLabel, - DIRECTION_COLUMN, - Flex, - JUSTIFY_CENTER, - JUSTIFY_SPACE_BETWEEN, - LabwareStackRender, - SPACING, - StyledText, - Modal, -} from '@opentrons/components' -import { OddModal } from '../../../../molecules/OddModal' -import { getIsOnDevice } from '../../../../redux/config' -import { getLocationInfoNames } from '../utils/getLocationInfoNames' -import { getSlotLabwareDefinition } from '../utils/getSlotLabwareDefinition' -import { Divider } from '../../../../atoms/structure' -import { getModuleImage } from '../SetupModuleAndDeck/utils' -import { - FLEX_ROBOT_TYPE, - getModuleDisplayName, - getModuleType, - TC_MODULE_LOCATION_OT2, - TC_MODULE_LOCATION_OT3, - THERMOCYCLER_MODULE_TYPE, -} from '@opentrons/shared-data' -import tiprackAdapter from '../../../../assets/images/labware/opentrons_flex_96_tiprack_adapter.png' - -import type { RobotType, RunTimeCommand } from '@opentrons/shared-data' - -const HIDE_SCROLLBAR = css` - ::-webkit-scrollbar { - display: none; - } -` - -const IMAGE_STYLE = css` - max-width: 11.5rem; - max-height: 6.875rem; -` - -const IMAGE_CONTAINER_STYLE = css` - width: 11.5rem; - height: 100%; - justify-content: ${JUSTIFY_CENTER}; -` - -const LIST_ITEM_STYLE = css` - align-items: ${ALIGN_CENTER}; - height: 6.875rem; - justify-content: ${JUSTIFY_SPACE_BETWEEN}; -` - -interface LabwareStackModalProps { - labwareIdTop: string - commands: RunTimeCommand[] | null - closeModal: () => void - robotType?: RobotType -} - -export const LabwareStackModal = ( - props: LabwareStackModalProps -): JSX.Element | null => { - const { - labwareIdTop, - commands, - closeModal, - robotType = FLEX_ROBOT_TYPE, - } = props - const { t } = useTranslation('protocol_setup') - const isOnDevice = useSelector(getIsOnDevice) - - if (commands == null) { - return null - } - const { - slotName, - adapterName, - adapterId, - moduleModel, - labwareName, - labwareNickname, - } = getLocationInfoNames(labwareIdTop, commands) - - const topDefinition = getSlotLabwareDefinition(labwareIdTop, commands) - const adapterDef = - adapterId != null - ? getSlotLabwareDefinition(adapterId ?? '', commands) - : null - const isModuleThermocycler = - moduleModel == null - ? false - : getModuleType(moduleModel) === THERMOCYCLER_MODULE_TYPE - const thermocyclerLocation = - robotType === FLEX_ROBOT_TYPE - ? TC_MODULE_LOCATION_OT3 - : TC_MODULE_LOCATION_OT2 - const moduleDisplayName = - moduleModel != null ? getModuleDisplayName(moduleModel) : null ?? '' - const isAdapterForTiprack = - adapterDef?.parameters.loadName === 'opentrons_flex_96_tiprack_adapter' - const tiprackAdapterImg = - const moduleImg = - moduleModel != null ? ( - - ) : null - - return isOnDevice ? ( - - - -
    - ), - onClick: closeModal, - padding: `${SPACING.spacing32} ${SPACING.spacing32} ${SPACING.spacing12}`, - }} - > - - <> - - - - - - - - - {adapterDef != null ? ( - <> - - - {isAdapterForTiprack ? ( - {tiprackAdapterImg} - ) : ( - - - - )} - - {moduleModel != null ? ( - - ) : null} - - ) : null} - {moduleModel != null ? ( - - - {moduleImg} - - ) : null} - - - ) : ( - - } - titleElement2={} - childrenPadding={0} - marginLeft="0" - > - - - <> - - - - - - - - - {adapterDef != null ? ( - <> - - - {isAdapterForTiprack ? ( - {tiprackAdapterImg} - ) : ( - - - - )} - - {moduleModel != null ? ( - - ) : null} - - ) : null} - {moduleModel != null ? ( - - - {moduleImg} - - ) : null} - - - - ) -} - -interface LabwareStackLabelProps { - text: string - subText?: string - isOnDevice?: boolean -} -function LabwareStackLabel(props: LabwareStackLabelProps): JSX.Element { - const { text, subText, isOnDevice = false } = props - return isOnDevice ? ( - - {text} - {subText != null ? ( - - {subText} - - ) : null} - - ) : ( - - {text} - {subText != null ? ( - - {subText} - - ) : null} - - ) -} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx deleted file mode 100644 index c95adc49b36..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/SetupLabwareMap.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import * as React from 'react' -import map from 'lodash/map' - -import { - BaseDeck, - Flex, - Box, - DIRECTION_COLUMN, - SPACING, -} from '@opentrons/components' -import { - FLEX_ROBOT_TYPE, - getDeckDefFromRobotType, - getSimplestDeckConfigForProtocol, - parseInitialLoadedLabwareByAdapter, - THERMOCYCLER_MODULE_V1, -} from '@opentrons/shared-data' - -import { getLabwareSetupItemGroups } from '../../../../pages/Protocols/utils' -import { LabwareInfoOverlay } from '../LabwareInfoOverlay' -import { getLabwareRenderInfo } from '../utils/getLabwareRenderInfo' -import { getProtocolModulesInfo } from '../utils/getProtocolModulesInfo' -import { getStandardDeckViewLayerBlockList } from '../utils/getStandardDeckViewLayerBlockList' -import { OffDeckLabwareList } from './OffDeckLabwareList' - -import type { - CompletedProtocolAnalysis, - ProtocolAnalysisOutput, -} from '@opentrons/shared-data' -import { LabwareStackModal } from './LabwareStackModal' - -interface SetupLabwareMapProps { - runId: string - protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null -} - -export function SetupLabwareMap({ - runId, - protocolAnalysis, -}: SetupLabwareMapProps): JSX.Element | null { - // early return null if no protocol analysis - const [ - labwareStackDetailsLabwareId, - setLabwareStackDetailsLabwareId, - ] = React.useState(null) - const [hoverLabwareId, setHoverLabwareId] = React.useState( - null - ) - - if (protocolAnalysis == null) return null - - const commands = protocolAnalysis.commands - - const robotType = protocolAnalysis.robotType ?? FLEX_ROBOT_TYPE - const deckDef = getDeckDefFromRobotType(robotType) - - const protocolModulesInfo = getProtocolModulesInfo(protocolAnalysis, deckDef) - - const initialLoadedLabwareByAdapter = parseInitialLoadedLabwareByAdapter( - commands - ) - - const modulesOnDeck = protocolModulesInfo.map(module => { - const labwareInAdapterInMod = - module.nestedLabwareId != null - ? initialLoadedLabwareByAdapter[module.nestedLabwareId] - : null - // only rendering the labware on top most layer so - // either the adapter or the labware are rendered but not both - const topLabwareDefinition = - labwareInAdapterInMod?.result?.definition ?? module.nestedLabwareDef - const topLabwareId = - labwareInAdapterInMod?.result?.labwareId ?? module.nestedLabwareId - const topLabwareDisplayName = - labwareInAdapterInMod?.params.displayName ?? - module.nestedLabwareDisplayName - - return { - moduleModel: module.moduleDef.model, - moduleLocation: { slotName: module.slotName }, - innerProps: - module.moduleDef.model === THERMOCYCLER_MODULE_V1 - ? { lidMotorState: 'open' } - : {}, - - nestedLabwareDef: topLabwareDefinition, - highlightLabware: - topLabwareDefinition != null && - topLabwareId != null && - hoverLabwareId === topLabwareId, - stacked: topLabwareDefinition != null && topLabwareId != null, - moduleChildren: ( - // open modal - { - if (topLabwareDefinition != null && topLabwareId != null) { - setLabwareStackDetailsLabwareId(topLabwareId) - } - }} - onMouseEnter={() => { - if (topLabwareDefinition != null && topLabwareId != null) { - setHoverLabwareId(topLabwareId) - } - }} - onMouseLeave={() => { - setHoverLabwareId(null) - }} - cursor="pointer" - > - {topLabwareDefinition != null && topLabwareId != null ? ( - - ) : null} - - ), - } - }) - - const { offDeckItems } = getLabwareSetupItemGroups(commands) - - const deckConfig = getSimplestDeckConfigForProtocol(protocolAnalysis) - - const labwareRenderInfo = getLabwareRenderInfo(protocolAnalysis, deckDef) - - const labwareOnDeck = map( - labwareRenderInfo, - ({ labwareDef, displayName, slotName }, labwareId) => { - const labwareInAdapter = initialLoadedLabwareByAdapter[labwareId] - // only rendering the labware on top most layer so - // either the adapter or the labware are rendered but not both - const topLabwareDefinition = - labwareInAdapter?.result?.definition ?? labwareDef - const topLabwareId = labwareInAdapter?.result?.labwareId ?? labwareId - const topLabwareDisplayName = - labwareInAdapter?.params.displayName ?? displayName - const isLabwareInStack = - topLabwareDefinition != null && - topLabwareId != null && - labwareInAdapter != null - - return { - labwareLocation: { slotName }, - definition: topLabwareDefinition, - topLabwareId, - topLabwareDisplayName, - highlight: isLabwareInStack && hoverLabwareId === topLabwareId, - labwareChildren: ( - { - if (isLabwareInStack) { - setLabwareStackDetailsLabwareId(topLabwareId) - } - }} - onMouseEnter={() => { - if (topLabwareDefinition != null && topLabwareId != null) { - setHoverLabwareId(() => topLabwareId) - } - }} - onMouseLeave={() => { - setHoverLabwareId(null) - }} - > - - - ), - stacked: isLabwareInStack, - } - } - ) - - return ( - - - - - - - - {labwareStackDetailsLabwareId != null && ( - { - setLabwareStackDetailsLabwareId(null) - }} - robotType={robotType} - /> - )} - - ) -} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabware.test.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabware.test.tsx deleted file mode 100644 index e92169bcb1d..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/__tests__/SetupLabware.test.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import * as React from 'react' -import { MemoryRouter } from 'react-router-dom' -import { fireEvent, screen } from '@testing-library/react' -import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' -import { when } from 'vitest-when' - -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' -import { useLPCSuccessToast } from '../../../hooks/useLPCSuccessToast' -import { LabwarePositionCheck } from '../../../../LabwarePositionCheck' -import { getModuleTypesThatRequireExtraAttention } from '../../utils/getModuleTypesThatRequireExtraAttention' -import { getIsLabwareOffsetCodeSnippetsOn } from '../../../../../redux/config' -import { - useLPCDisabledReason, - useRunCalibrationStatus, - useRunHasStarted, - useUnmatchedModulesForProtocol, -} from '../../../hooks' -import { SetupLabwareList } from '../SetupLabwareList' -import { SetupLabwareMap } from '../SetupLabwareMap' -import { SetupLabware } from '..' -import { useNotifyRunQuery } from '../../../../../resources/runs' - -vi.mock('../SetupLabwareList') -vi.mock('../SetupLabwareMap') -vi.mock('../../../../LabwarePositionCheck') -vi.mock('../../utils/getModuleTypesThatRequireExtraAttention') -vi.mock('../../../../RunTimeControl/hooks') -vi.mock('../../../../../redux/config') -vi.mock('../../../hooks') -vi.mock('../../../hooks/useLPCSuccessToast') -vi.mock('../../../../../resources/runs') - -const ROBOT_NAME = 'otie' -const RUN_ID = '1' - -const render = () => { - let labwareConfirmed = false - const confirmLabware = vi.fn(confirmed => { - labwareConfirmed = confirmed - }) - return renderWithProviders( - - - , - { - i18nInstance: i18n, - } - )[0] -} - -describe('SetupLabware', () => { - beforeEach(() => { - when(vi.mocked(getModuleTypesThatRequireExtraAttention)) - .calledWith(expect.anything()) - .thenReturn([]) - - vi.mocked(LabwarePositionCheck).mockReturnValue( -
    mock Labware Position Check
    - ) - when(vi.mocked(useUnmatchedModulesForProtocol)) - .calledWith(ROBOT_NAME, RUN_ID) - .thenReturn({ - missingModuleIds: [], - remainingAttachedModules: [], - }) - - when(vi.mocked(useLPCSuccessToast)) - .calledWith() - .thenReturn({ setIsShowingLPCSuccessToast: vi.fn() }) - - when(vi.mocked(useRunCalibrationStatus)) - .calledWith(ROBOT_NAME, RUN_ID) - .thenReturn({ - complete: true, - }) - when(vi.mocked(useRunHasStarted)).calledWith(RUN_ID).thenReturn(false) - vi.mocked(getIsLabwareOffsetCodeSnippetsOn).mockReturnValue(false) - vi.mocked(SetupLabwareMap).mockReturnValue( -
    mock setup labware map
    - ) - vi.mocked(SetupLabwareList).mockReturnValue( -
    mock setup labware list
    - ) - vi.mocked(useLPCDisabledReason).mockReturnValue(null) - vi.mocked(useNotifyRunQuery).mockReturnValue({} as any) - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - it('should render the list view, clicking the toggle button will turn to map view', () => { - render() - screen.getByText('mock setup labware list') - screen.getByRole('button', { name: 'List View' }) - const mapView = screen.getByRole('button', { name: 'Map View' }) - fireEvent.click(mapView) - screen.getByText('mock setup labware map') - }) -}) diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabware/index.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabware/index.tsx deleted file mode 100644 index 526b944f425..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabware/index.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import map from 'lodash/map' -import { - JUSTIFY_CENTER, - Flex, - SPACING, - PrimaryButton, - DIRECTION_COLUMN, -} from '@opentrons/components' -import { useToggleGroup } from '../../../../molecules/ToggleGroup/useToggleGroup' -import { getModuleTypesThatRequireExtraAttention } from '../utils/getModuleTypesThatRequireExtraAttention' -import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { - useIsFlex, - useModuleRenderInfoForProtocolById, - useStoredProtocolAnalysis, -} from '../../hooks' -import { SetupLabwareMap } from './SetupLabwareMap' -import { SetupLabwareList } from './SetupLabwareList' - -interface SetupLabwareProps { - robotName: string - runId: string - labwareConfirmed: boolean - setLabwareConfirmed: (confirmed: boolean) => void -} - -export function SetupLabware(props: SetupLabwareProps): JSX.Element { - const { robotName, runId, labwareConfirmed, setLabwareConfirmed } = props - const { t } = useTranslation('protocol_setup') - const robotProtocolAnalysis = useMostRecentCompletedAnalysis(runId) - const storedProtocolAnalysis = useStoredProtocolAnalysis(runId) - const protocolAnalysis = robotProtocolAnalysis ?? storedProtocolAnalysis - const [selectedValue, toggleGroup] = useToggleGroup( - t('list_view') as string, - t('map_view') as string - ) - const isFlex = useIsFlex(robotName) - - const moduleRenderInfoById = useModuleRenderInfoForProtocolById(runId) - const moduleModels = map( - moduleRenderInfoById, - ({ moduleDef }) => moduleDef.model - ) - const moduleTypesThatRequireExtraAttention = getModuleTypesThatRequireExtraAttention( - moduleModels - ) - - return ( - <> - - {toggleGroup} - {selectedValue === t('list_view') ? ( - - ) : ( - - )} - - - { - setLabwareConfirmed(true) - }} - disabled={labwareConfirmed} - > - {t('confirm_placements')} - - - - ) -} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/utils.test.ts b/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/utils.test.ts deleted file mode 100644 index c073e2466f6..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/__tests__/utils.test.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { getLatestCurrentOffsets } from '../utils' -import type { LabwareOffset } from '@opentrons/api-client' - -describe('getLatestCurrentOffsets', () => { - it('should return the latest offsets when there are multiple offsets', () => { - const mockCurrentOffsets: LabwareOffset[] = [ - { - createdAt: '2022-12-20T14:06:23.562082+00:00', - definitionUri: 'opentrons/opentrons_96_tiprack_10ul/1', - id: 'dceac542-bca4-4313-82ba-d54a19dab204', - location: { slotName: '2' }, - vector: { x: 1, y: 2, z: 3 }, - }, - { - createdAt: '2022-12-20T14:06:23.562878+00:00', - definitionUri: - 'opentrons/opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat/1', - id: '70ae2e31-716b-4e1f-a90c-9b0dfd4d7feb', - location: { slotName: '1', moduleModel: 'heaterShakerModuleV1' }, - vector: { x: 4, y: 5, z: 6 }, - }, - { - createdAt: '2022-12-20T14:09:08.688756+00:00', - definitionUri: 'opentrons/opentrons_96_tiprack_10ul/1', - id: '494ec3d1-224f-4f9a-a83b-3dd3060ea332', - location: { slotName: '2' }, - vector: { x: 7, y: 8, z: 9 }, - }, - { - createdAt: '2022-12-20T14:09:08.689813+00:00', - definitionUri: - 'opentrons/opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat/1', - id: 'd39b972e-9b2d-436c-a597-3bc81aabc634', - location: { slotName: '1', moduleModel: 'heaterShakerModuleV1' }, - vector: { x: 10, y: 11, z: 12 }, - }, - ] - const mockLatestCurrentOffsets = [ - { - createdAt: '2022-12-20T14:09:08.688756+00:00', - definitionUri: 'opentrons/opentrons_96_tiprack_10ul/1', - id: '494ec3d1-224f-4f9a-a83b-3dd3060ea332', - location: { slotName: '2' }, - vector: { x: 7, y: 8, z: 9 }, - }, - { - createdAt: '2022-12-20T14:09:08.689813+00:00', - definitionUri: - 'opentrons/opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat/1', - id: 'd39b972e-9b2d-436c-a597-3bc81aabc634', - location: { slotName: '1', moduleModel: 'heaterShakerModuleV1' }, - vector: { x: 10, y: 11, z: 12 }, - }, - ] - - expect(getLatestCurrentOffsets(mockCurrentOffsets)).toStrictEqual( - mockLatestCurrentOffsets - ) - }) - it('should return empty array when the labware vector values are 0', () => { - const mockCurrentOffsets: LabwareOffset[] = [ - { - createdAt: '2022-12-20T14:06:23.562878+00:00', - definitionUri: - 'opentrons/opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat/1', - id: '70ae2e31-716b-4e1f-a90c-9b0dfd4d7feb', - location: { slotName: '1', moduleModel: 'heaterShakerModuleV1' }, - vector: { x: 0, y: 0, z: 0 }, - }, - ] - - expect(getLatestCurrentOffsets(mockCurrentOffsets)).toStrictEqual([]) - }) - it('should return the correct offsets when there are multiples in random order and modules in the same slot as labware', () => { - const mockCurrentOffsets: LabwareOffset[] = [ - { - createdAt: '2022-12-20T14:06:23.562082+00:00', - definitionUri: 'opentrons/opentrons_96_tiprack_10ul/1', - id: 'dceac542-bca4-4313-82ba-d54a19dab204', - location: { slotName: '4' }, - vector: { x: 1, y: 2, z: 3 }, - }, - { - createdAt: '2022-12-20T14:06:23.562878+00:00', - definitionUri: - 'opentrons/opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat/1', - id: '70ae2e31-716b-4e1f-a90c-9b0dfd4d7feb', - location: { slotName: '8', moduleModel: 'heaterShakerModuleV1' }, - vector: { x: 4, y: 5, z: 6 }, - }, - { - createdAt: '2022-12-20T18:09:08.688756+00:00', - definitionUri: 'opentrons/opentrons_96_tiprack_10ul/1', - id: '494ec3d1-224f-4f9a-a83b-3dd3060ea332', - location: { slotName: '1' }, - vector: { x: 7, y: 8, z: 9 }, - }, - { - createdAt: '2022-12-20T19:09:08.689813+00:00', - definitionUri: - 'opentrons/opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat/1', - id: 'd39b972e-9b2d-436c-a597-3bc81aabc634', - location: { slotName: '1', moduleModel: 'heaterShakerModuleV1' }, - vector: { x: 10, y: 11, z: 12 }, - }, - { - createdAt: '2022-12-20T20:09:08.688756+00:00', - definitionUri: 'opentrons/opentrons_96_tiprack_10ul/1', - id: '494ec3d1-224f-4f9a-a83b-3dd3060ea332', - location: { slotName: '1' }, - vector: { x: 13, y: 14, z: 15 }, - }, - { - createdAt: '2023-12-20T20:09:08.688756+00:00', - definitionUri: 'opentrons/opentrons_96_tiprack_10ul/1', - id: '494ec3d1-224f-4f9a-a83b-3dd3060ea332', - location: { slotName: '1' }, - vector: { x: 16, y: 17, z: 18 }, - }, - ] - const mockLatestCurrentOffsets = [ - { - createdAt: '2022-12-20T14:06:23.562082+00:00', - definitionUri: 'opentrons/opentrons_96_tiprack_10ul/1', - id: 'dceac542-bca4-4313-82ba-d54a19dab204', - location: { slotName: '4' }, - vector: { x: 1, y: 2, z: 3 }, - }, - { - createdAt: '2022-12-20T14:06:23.562878+00:00', - definitionUri: - 'opentrons/opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat/1', - id: '70ae2e31-716b-4e1f-a90c-9b0dfd4d7feb', - location: { slotName: '8', moduleModel: 'heaterShakerModuleV1' }, - vector: { x: 4, y: 5, z: 6 }, - }, - { - createdAt: '2022-12-20T19:09:08.689813+00:00', - definitionUri: - 'opentrons/opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat/1', - id: 'd39b972e-9b2d-436c-a597-3bc81aabc634', - location: { slotName: '1', moduleModel: 'heaterShakerModuleV1' }, - vector: { x: 10, y: 11, z: 12 }, - }, - { - createdAt: '2023-12-20T20:09:08.688756+00:00', - definitionUri: 'opentrons/opentrons_96_tiprack_10ul/1', - id: '494ec3d1-224f-4f9a-a83b-3dd3060ea332', - location: { slotName: '1' }, - vector: { x: 16, y: 17, z: 18 }, - }, - ] - expect(getLatestCurrentOffsets(mockCurrentOffsets)).toStrictEqual( - mockLatestCurrentOffsets - ) - }) - it('should return 2 offsets of the same slotname but 1 has a module and 1 does not', () => { - const mockCurrentOffsets: LabwareOffset[] = [ - { - createdAt: '2022-12-20T19:09:08.689813+00:00', - definitionUri: - 'opentrons/opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat/1', - id: 'd39b972e-9b2d-436c-a597-3bc81aabc634', - location: { slotName: '1', moduleModel: 'heaterShakerModuleV1' }, - vector: { x: 10, y: 11, z: 12 }, - }, - { - createdAt: '2022-12-20T20:09:08.688756+00:00', - definitionUri: 'opentrons/opentrons_96_tiprack_10ul/1', - id: '494ec3d1-224f-4f9a-a83b-3dd3060ea332', - location: { slotName: '1' }, - vector: { x: 13, y: 14, z: 15 }, - }, - ] - expect(getLatestCurrentOffsets(mockCurrentOffsets)).toStrictEqual( - mockCurrentOffsets - ) - }) -}) diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/index.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/index.tsx deleted file mode 100644 index 7189eeb4cc0..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/index.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' - -import { - ALIGN_CENTER, - BORDERS, - COLORS, - DIRECTION_COLUMN, - Flex, - JUSTIFY_CENTER, - LegacyStyledText, - PrimaryButton, - SecondaryButton, - SPACING, - TOOLTIP_LEFT, - Tooltip, - TYPOGRAPHY, - useHoverTooltip, -} from '@opentrons/components' -import { useProtocolQuery } from '@opentrons/react-api-client' - -import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useLPCSuccessToast } from '../../hooks/useLPCSuccessToast' -import { - useRobotType, - useLPCDisabledReason, - useStoredProtocolAnalysis, -} from '../../hooks' -import { CurrentOffsetsTable } from './CurrentOffsetsTable' -import { useLaunchLPC } from '../../../LabwarePositionCheck/useLaunchLPC' -import { getLatestCurrentOffsets } from './utils' -import { useNotifyRunQuery } from '../../../../resources/runs' - -import type { LabwareOffset } from '@opentrons/api-client' - -interface SetupLabwarePositionCheckProps { - offsetsConfirmed: boolean - setOffsetsConfirmed: (confirmed: boolean) => void - robotName: string - runId: string -} - -export function SetupLabwarePositionCheck( - props: SetupLabwarePositionCheckProps -): JSX.Element { - const { robotName, runId, setOffsetsConfirmed, offsetsConfirmed } = props - const { t, i18n } = useTranslation('protocol_setup') - - const robotType = useRobotType(robotName) - const { data: runRecord } = useNotifyRunQuery(runId, { staleTime: Infinity }) - const { data: protocolRecord } = useProtocolQuery( - runRecord?.data.protocolId ?? null, - { - staleTime: Infinity, - } - ) - const protocolName = - protocolRecord?.data.metadata.protocolName ?? - protocolRecord?.data.files[0].name ?? - '' - const currentOffsets = runRecord?.data?.labwareOffsets ?? [] - const sortedOffsets: LabwareOffset[] = - currentOffsets.length > 0 - ? currentOffsets - .map(offset => ({ - ...offset, - // convert into date to sort - createdAt: new Date(offset.createdAt), - })) - .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()) - .map(offset => ({ - ...offset, - // convert back into string - createdAt: offset.createdAt.toISOString(), - })) - : [] - const lpcDisabledReason = useLPCDisabledReason({ robotName, runId }) - const robotProtocolAnalysis = useMostRecentCompletedAnalysis(runId) - const storedProtocolAnalysis = useStoredProtocolAnalysis(runId) - const protocolData = robotProtocolAnalysis ?? storedProtocolAnalysis - const [runLPCTargetProps, runLPCTooltipProps] = useHoverTooltip({ - placement: TOOLTIP_LEFT, - }) - const [ - confirmOffsetsTargetProps, - confirmOffsetsTooltipProps, - ] = useHoverTooltip({ - placement: TOOLTIP_LEFT, - }) - - const { setIsShowingLPCSuccessToast } = useLPCSuccessToast() - - const { launchLPC, LPCWizard } = useLaunchLPC(runId, robotType, protocolName) - - const nonIdentityOffsets = getLatestCurrentOffsets(sortedOffsets) - - return ( - - {nonIdentityOffsets.length > 0 ? ( - - ) : ( - - - {i18n.format(t('no_labware_offset_data'), 'capitalize')} - - - )} - - { - setOffsetsConfirmed(true) - }} - id="LPC_setOffsetsConfirmed" - padding={`${SPACING.spacing8} ${SPACING.spacing16}`} - {...confirmOffsetsTargetProps} - disabled={ - offsetsConfirmed || - lpcDisabledReason !== null || - nonIdentityOffsets.length === 0 - } - > - {t('confirm_offsets')} - - {lpcDisabledReason != null || nonIdentityOffsets.length === 0 ? ( - - {lpcDisabledReason ?? - t('run_labware_position_check_to_get_offsets')} - - ) : null} - { - launchLPC() - setIsShowingLPCSuccessToast(false) - }} - id="LabwareSetup_checkLabwarePositionsButton" - {...runLPCTargetProps} - disabled={lpcDisabledReason !== null} - > - {t('run_labware_position_check')} - - {lpcDisabledReason !== null ? ( - - {lpcDisabledReason} - - ) : null} - - {LPCWizard} - - ) -} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/SetupLiquidsMap.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLiquids/SetupLiquidsMap.tsx deleted file mode 100644 index cb839230cc8..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/SetupLiquidsMap.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import * as React from 'react' -import map from 'lodash/map' -import isEmpty from 'lodash/isEmpty' - -import { - ALIGN_CENTER, - BaseDeck, - DIRECTION_COLUMN, - Flex, - JUSTIFY_CENTER, - LabwareRender, -} from '@opentrons/components' -import { - FLEX_ROBOT_TYPE, - getDeckDefFromRobotType, - getSimplestDeckConfigForProtocol, - parseInitialLoadedLabwareByAdapter, - parseLabwareInfoByLiquidId, - parseLiquidsInLoadOrder, - THERMOCYCLER_MODULE_V1, -} from '@opentrons/shared-data' - -import { LabwareInfoOverlay } from '../LabwareInfoOverlay' -import { LiquidsLabwareDetailsModal } from './LiquidsLabwareDetailsModal' -import { getWellFillFromLabwareId } from './utils' -import { getLabwareRenderInfo } from '../utils/getLabwareRenderInfo' -import { getStandardDeckViewLayerBlockList } from '../utils/getStandardDeckViewLayerBlockList' -import { getProtocolModulesInfo } from '../utils/getProtocolModulesInfo' - -import type { - CompletedProtocolAnalysis, - ProtocolAnalysisOutput, -} from '@opentrons/shared-data' - -interface SetupLiquidsMapProps { - runId: string - protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null -} - -export function SetupLiquidsMap( - props: SetupLiquidsMapProps -): JSX.Element | null { - const { runId, protocolAnalysis } = props - const [hoverLabwareId, setHoverLabwareId] = React.useState('') - const [liquidDetailsLabwareId, setLiquidDetailsLabwareId] = React.useState< - string | null - >(null) - - if (protocolAnalysis == null) return null - - const liquids = parseLiquidsInLoadOrder( - protocolAnalysis.liquids != null ? protocolAnalysis.liquids : [], - protocolAnalysis.commands ?? [] - ) - const initialLoadedLabwareByAdapter = parseInitialLoadedLabwareByAdapter( - protocolAnalysis.commands ?? [] - ) - const robotType = protocolAnalysis.robotType ?? FLEX_ROBOT_TYPE - const deckDef = getDeckDefFromRobotType(robotType) - const labwareRenderInfo = getLabwareRenderInfo(protocolAnalysis, deckDef) - const labwareByLiquidId = parseLabwareInfoByLiquidId( - protocolAnalysis.commands ?? [] - ) - const deckConfig = getSimplestDeckConfigForProtocol(protocolAnalysis) - const deckLayerBlocklist = getStandardDeckViewLayerBlockList(robotType) - - const protocolModulesInfo = getProtocolModulesInfo(protocolAnalysis, deckDef) - - const modulesOnDeck = protocolModulesInfo.map(module => { - const labwareInAdapterInMod = - module.nestedLabwareId != null - ? initialLoadedLabwareByAdapter[module.nestedLabwareId] - : null - // only rendering the labware on top most layer so - // either the adapter or the labware are rendered but not both - const topLabwareDefinition = - labwareInAdapterInMod?.result?.definition ?? module.nestedLabwareDef - const topLabwareId = - labwareInAdapterInMod?.result?.labwareId ?? module.nestedLabwareId - const topLabwareDisplayName = - labwareInAdapterInMod?.params.displayName ?? - module.nestedLabwareDisplayName - const nestedLabwareWellFill = getWellFillFromLabwareId( - topLabwareId ?? '', - liquids, - labwareByLiquidId - ) - const labwareHasLiquid = !isEmpty(nestedLabwareWellFill) - - return { - moduleModel: module.moduleDef.model, - moduleLocation: { slotName: module.slotName }, - innerProps: - module.moduleDef.model === THERMOCYCLER_MODULE_V1 - ? { lidMotorState: 'open' } - : {}, - - nestedLabwareDef: topLabwareDefinition, - nestedLabwareWellFill, - moduleChildren: - topLabwareDefinition != null && topLabwareId != null ? ( - { - setHoverLabwareId(topLabwareId) - }} - onMouseLeave={() => { - setHoverLabwareId('') - }} - onClick={() => { - if (labwareHasLiquid) { - setLiquidDetailsLabwareId(topLabwareId) - } - }} - cursor={labwareHasLiquid ? 'pointer' : ''} - > - - - ) : null, - } - }) - return ( - - - {map( - labwareRenderInfo, - ({ x, y, labwareDef, displayName }, labwareId) => { - const labwareInAdapter = initialLoadedLabwareByAdapter[labwareId] - // only rendering the labware on top most layer so - // either the adapter or the labware are rendered but not both - const topLabwareDefinition = - labwareInAdapter?.result?.definition ?? labwareDef - const topLabwareId = - labwareInAdapter?.result?.labwareId ?? labwareId - const topLabwareDisplayName = - labwareInAdapter?.params.displayName ?? displayName - const wellFill = getWellFillFromLabwareId( - topLabwareId ?? '', - liquids, - labwareByLiquidId - ) - const labwareHasLiquid = !isEmpty(wellFill) - return ( - - { - setHoverLabwareId(topLabwareId) - }} - onMouseLeave={() => { - setHoverLabwareId('') - }} - onClick={() => { - if (labwareHasLiquid) { - setLiquidDetailsLabwareId(topLabwareId) - } - }} - cursor={labwareHasLiquid ? 'pointer' : ''} - > - - - - - ) - } - )} - - {liquidDetailsLabwareId != null && ( - { - setLiquidDetailsLabwareId(null) - }} - /> - )} - - ) -} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquids.test.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquids.test.tsx deleted file mode 100644 index 4407f9f28a7..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/SetupLiquids.test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import * as React from 'react' -import { describe, it, beforeEach, vi } from 'vitest' -import { screen, fireEvent } from '@testing-library/react' - -import { renderWithProviders } from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' -import { SetupLiquids } from '../index' -import { SetupLiquidsList } from '../SetupLiquidsList' -import { SetupLiquidsMap } from '../SetupLiquidsMap' - -vi.mock('../SetupLiquidsList') -vi.mock('../SetupLiquidsMap') - -describe('SetupLiquids', () => { - const render = ( - props: React.ComponentProps & { - startConfirmed?: boolean - } - ) => { - let isConfirmed = - props?.startConfirmed == null ? false : props.startConfirmed - const confirmFn = vi.fn((confirmed: boolean) => { - isConfirmed = confirmed - }) - return renderWithProviders( - , - { - i18nInstance: i18n, - } - ) - } - - let props: React.ComponentProps - beforeEach(() => { - vi.mocked(SetupLiquidsList).mockReturnValue( -
    Mock setup liquids list
    - ) - vi.mocked(SetupLiquidsMap).mockReturnValue( -
    Mock setup liquids map
    - ) - }) - - it('renders the list and map view buttons and proceed button', () => { - render(props) - screen.getByRole('button', { name: 'List View' }) - screen.getByRole('button', { name: 'Map View' }) - screen.getByRole('button', { name: 'Confirm locations and volumes' }) - }) - it('renders the map view when you press that toggle button', () => { - render(props) - const mapViewButton = screen.getByRole('button', { name: 'Map View' }) - fireEvent.click(mapViewButton) - screen.getByText('Mock setup liquids map') - }) - it('renders the list view when you press that toggle button', () => { - render(props) - const mapViewButton = screen.getByRole('button', { name: 'List View' }) - fireEvent.click(mapViewButton) - screen.getByText('Mock setup liquids list') - }) -}) diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/utils.test.ts b/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/utils.test.ts deleted file mode 100644 index 81a32b8d23d..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/utils.test.ts +++ /dev/null @@ -1,342 +0,0 @@ -import { describe, it, expect } from 'vitest' - -import { - getWellFillFromLabwareId, - getTotalVolumePerLiquidId, - getTotalVolumePerLiquidLabwarePair, - getLiquidsByIdForLabware, - getWellGroupForLiquidId, - getWellRangeForLiquidLabwarePair, - getDisabledWellGroupForLiquidId, -} from '../utils' -import type { LabwareByLiquidId, Liquid } from '@opentrons/shared-data' - -const LABWARE_ID = - '60e8b050-3412-11eb-ad93-ed232a2337cf:opentrons/corning_24_wellplate_3.4ml_flat/1' -const LIQUID_ID = '7' -const MOCK_LIQUIDS_IN_LOAD_ORDER: Liquid[] = [ - { - description: 'water', - displayColor: '#00d781', - displayName: 'liquid 2', - id: '7', - }, - { - description: 'saline', - displayColor: '#0076ff', - displayName: 'liquid 1', - id: '123', - }, - { - description: 'reagent', - displayColor: '#ff4888', - displayName: 'liquid 3', - id: '19', - }, - { - description: 'saliva', - displayColor: '#B925FF', - displayName: 'liquid 4', - id: '4', - }, -] -const MOCK_LABWARE_BY_LIQUID_ID: LabwareByLiquidId = { - '4': [ - { - labwareId: - '60e8b050-3412-11eb-ad93-ed232a2337cf:opentrons/corning_24_wellplate_3.4ml_flat/1', - volumeByWell: { - A3: 100, - A4: 100, - B3: 100, - B4: 100, - C3: 100, - C4: 100, - D3: 100, - D4: 100, - }, - }, - ], - '7': [ - { - labwareId: - '60e8b050-3412-11eb-ad93-ed232a2337cf:opentrons/corning_24_wellplate_3.4ml_flat/1', - volumeByWell: { - A1: 100, - A2: 100, - B1: 100, - B2: 100, - C1: 100, - C2: 100, - D1: 100, - D2: 100, - }, - }, - { - labwareId: '53d3b350-a9c0-11eb-bce6-9f1d5b9c1a1b', - volumeByWell: { - A3: 50, - B1: 50, - C1: 50, - D1: 50, - }, - }, - ], - '19': [ - { - labwareId: - '60e8b050-3412-11eb-ad93-ed232a2337cf:opentrons/corning_24_wellplate_3.4ml_flat/1', - volumeByWell: { - A5: 100, - A6: 100, - B5: 100, - B6: 100, - C5: 100, - C6: 100, - D5: 100, - D6: 100, - }, - }, - ], - '123': [ - { - labwareId: - '5ae317e0-3412-11eb-ad93-ed232a2337cf:opentrons/nest_1_reservoir_195ml/1', - volumeByWell: { - A1: 1000, - }, - }, - ], -} - -const MOCK_LABWARE_BY_LIQUID_ID_DECIMALS: LabwareByLiquidId = { - '4': [ - { - labwareId: - '60e8b050-3412-11eb-ad93-ed232a2337cf:opentrons/corning_24_wellplate_3.4ml_flat/1', - volumeByWell: { - A3: 100.1111111, - A4: 100, - B3: 100, - B4: 100, - C3: 100, - C4: 100, - D3: 100, - D4: 100, - }, - }, - ], -} - -const MOCK_LABWARE_BY_LIQUID_ID_FOR_LABWARE = { - '4': [ - { - labwareId: - '60e8b050-3412-11eb-ad93-ed232a2337cf:opentrons/corning_24_wellplate_3.4ml_flat/1', - volumeByWell: { - A3: 100, - A4: 100, - B3: 100, - B4: 100, - C3: 100, - C4: 100, - D3: 100, - D4: 100, - }, - }, - ], - '7': [ - { - labwareId: - '60e8b050-3412-11eb-ad93-ed232a2337cf:opentrons/corning_24_wellplate_3.4ml_flat/1', - volumeByWell: { - A1: 100, - A2: 100, - B1: 100, - B2: 100, - C1: 100, - C2: 100, - D1: 100, - D2: 100, - }, - }, - ], - '19': [ - { - labwareId: - '60e8b050-3412-11eb-ad93-ed232a2337cf:opentrons/corning_24_wellplate_3.4ml_flat/1', - volumeByWell: { - A5: 100, - A6: 100, - B5: 100, - B6: 100, - C5: 100, - C6: 100, - D5: 100, - D6: 100, - }, - }, - ], -} - -const MOCK_WELL_ORDERING = [ - ['A1', 'B1', 'C1', 'D1'], - ['A2', 'B2', 'C2', 'D2'], - ['A3', 'B3', 'C3', 'D3'], - ['A4', 'B4', 'C4', 'D4'], - ['A5', 'B5', 'C5', 'D5'], - ['A6', 'B6', 'C6', 'D6'], -] -const MOCK_VOLUME_BY_WELL = { - A1: 50, - C5: 100, - A3: 100, - A4: 100, - B3: 100, - B4: 100, - C3: 100, - C4: 100, - D3: 100, - D4: 100, -} - -describe('getWellFillFromLabwareId', () => { - it('returns wellfill object for the labwareId', () => { - const expected = { - A1: '#00d781', - A2: '#00d781', - A3: '#B925FF', - A4: '#B925FF', - A5: '#ff4888', - A6: '#ff4888', - B1: '#00d781', - B2: '#00d781', - B3: '#B925FF', - B4: '#B925FF', - B5: '#ff4888', - B6: '#ff4888', - C1: '#00d781', - C2: '#00d781', - C3: '#B925FF', - C4: '#B925FF', - C5: '#ff4888', - C6: '#ff4888', - D1: '#00d781', - D2: '#00d781', - D3: '#B925FF', - D4: '#B925FF', - D5: '#ff4888', - D6: '#ff4888', - } - expect( - getWellFillFromLabwareId( - LABWARE_ID, - MOCK_LIQUIDS_IN_LOAD_ORDER, - MOCK_LABWARE_BY_LIQUID_ID - ) - ).toEqual(expected) - }) -}) - -describe('getTotalVolumePerLiquidId', () => { - it('returns volume of liquid needed accross all labware', () => { - const expected = 1000 - expect( - getTotalVolumePerLiquidId(LIQUID_ID, MOCK_LABWARE_BY_LIQUID_ID) - ).toEqual(expected) - }) - it('returns volume of liquid needed accross all labware when there are decimal points', () => { - const expected = 800.1 - expect( - getTotalVolumePerLiquidId('4', MOCK_LABWARE_BY_LIQUID_ID_DECIMALS) - ).toEqual(expected) - }) -}) - -describe('getTotalVolumePerLiquidLabwarePair', () => { - it('returns volume of liquid needed for a specific labware', () => { - const expected = 800 - expect( - getTotalVolumePerLiquidLabwarePair( - LIQUID_ID, - LABWARE_ID, - MOCK_LABWARE_BY_LIQUID_ID - ) - ).toEqual(expected) - }) -}) - -describe('getLiquidsByIdForLabware', () => { - it('returns liquid info by labware id', () => { - expect( - getLiquidsByIdForLabware(LABWARE_ID, MOCK_LABWARE_BY_LIQUID_ID as any) - ).toEqual(MOCK_LABWARE_BY_LIQUID_ID_FOR_LABWARE) - }) -}) - -describe('getWellGroupForLiquidId', () => { - it('returns wellgroup object for the specified liquidId', () => { - const expected = { - A1: null, - A2: null, - B1: null, - B2: null, - C1: null, - C2: null, - D1: null, - D2: null, - } - expect( - getWellGroupForLiquidId(MOCK_LABWARE_BY_LIQUID_ID_FOR_LABWARE, '7') - ).toEqual(expected) - }) -}) - -describe('getDisabledWellGroupForLiquidId', () => { - it('returns wellgroup object for the specified liquidId', () => { - const expectedSeven = { - A1: null, - A2: null, - B1: null, - B2: null, - C1: null, - C2: null, - D1: null, - D2: null, - } - - const expectedNineteen = { - A5: null, - A6: null, - B5: null, - B6: null, - C5: null, - C6: null, - D5: null, - D6: null, - } - - const expected = [expectedSeven, expectedNineteen] - - expect( - getDisabledWellGroupForLiquidId(MOCK_LABWARE_BY_LIQUID_ID_FOR_LABWARE, [ - '7', - '19', - ]) - ).toEqual(expected) - }) -}) - -describe('getWellRangeForLiquidLabwarePair', () => { - it('returns correctly ranged wells', () => { - const expected = [ - { wellName: 'A1', volume: 50 }, - { wellName: 'A3: D3', volume: 100 }, - { wellName: 'A4: D4', volume: 100 }, - { wellName: 'C5', volume: 100 }, - ] - expect( - getWellRangeForLiquidLabwarePair(MOCK_VOLUME_BY_WELL, MOCK_WELL_ORDERING) - ).toEqual(expected) - }) -}) diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/index.tsx b/app/src/organisms/Devices/ProtocolRun/SetupLiquids/index.tsx deleted file mode 100644 index 6fc4446a0db..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/index.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { - JUSTIFY_CENTER, - Flex, - SPACING, - DIRECTION_COLUMN, - ALIGN_CENTER, - PrimaryButton, -} from '@opentrons/components' -import { useToggleGroup } from '../../../../molecules/ToggleGroup/useToggleGroup' -import { ANALYTICS_LIQUID_SETUP_VIEW_TOGGLE } from '../../../../redux/analytics' -import { SetupLiquidsList } from './SetupLiquidsList' -import { SetupLiquidsMap } from './SetupLiquidsMap' - -import type { - CompletedProtocolAnalysis, - ProtocolAnalysisOutput, -} from '@opentrons/shared-data' - -interface SetupLiquidsProps { - runId: string - protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null - isLiquidSetupConfirmed: boolean - setLiquidSetupConfirmed: (confirmed: boolean) => void - robotName: string -} - -export function SetupLiquids({ - runId, - protocolAnalysis, - isLiquidSetupConfirmed, - setLiquidSetupConfirmed, - robotName, -}: SetupLiquidsProps): JSX.Element { - const { t } = useTranslation('protocol_setup') - const [selectedValue, toggleGroup] = useToggleGroup( - t('list_view') as string, - t('map_view') as string, - ANALYTICS_LIQUID_SETUP_VIEW_TOGGLE - ) - return ( - - {toggleGroup} - {selectedValue === t('list_view') ? ( - - ) : ( - - )} - - { - setLiquidSetupConfirmed(true) - }} - disabled={isLiquidSetupConfirmed} - > - {t('confirm_locations_and_volumes')} - - - - ) -} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/utils.test.ts b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/utils.test.ts deleted file mode 100644 index 2e4639a3c98..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/utils.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { getFixtureImage, getModuleImage } from '../utils' - -describe('getModuleImage', () => { - it('should render the magnetic module image when the model is a magnetic module gen 1', () => { - const result = getModuleImage('magneticModuleV1') - expect(result).toEqual( - '/app/src/assets/images/magnetic_module_gen_2_transparent.png' - ) - }) - - it('should render the high res magnetic module image when the model is a magnetic module gen 1 high res', () => { - const result = getModuleImage('magneticModuleV1', true) - expect(result).toEqual( - '/app/src/assets/images/modules/magneticModuleV2@3x.png' - ) - }) - - it('should render the magnetic module image when the model is a magnetic module gen 2', () => { - const result = getModuleImage('magneticModuleV2') - expect(result).toEqual( - '/app/src/assets/images/magnetic_module_gen_2_transparent.png' - ) - }) - - it('should render the temperature module image when the model is a temperature module gen 1', () => { - const result = getModuleImage('temperatureModuleV1') - expect(result).toEqual( - '/app/src/assets/images/temp_deck_gen_2_transparent.png' - ) - }) - - it('should render the temperature module image when the model is a temperature module gen 2', () => { - const result = getModuleImage('temperatureModuleV2') - expect(result).toEqual( - '/app/src/assets/images/temp_deck_gen_2_transparent.png' - ) - }) - - it('should render the high res temperature module image when the model is a temperature module high res', () => { - const result = getModuleImage('temperatureModuleV2', true) - expect(result).toEqual( - '/app/src/assets/images/modules/temperatureModuleV2@3x.png' - ) - }) - - it('should render the heater-shaker module image when the model is a heater-shaker module gen 1', () => { - const result = getModuleImage('heaterShakerModuleV1') - expect(result).toEqual( - '/app/src/assets/images/heater_shaker_module_transparent.png' - ) - }) - - it('should render the high res heater-shaker module image when the model is a heater-shaker module gen 1 high res', () => { - const result = getModuleImage('heaterShakerModuleV1', true) - expect(result).toEqual( - '/app/src/assets/images/modules/heaterShakerModuleV1@3x.png' - ) - }) - - it('should render the thermocycler module image when the model is a thermocycler module gen 1', () => { - const result = getModuleImage('thermocyclerModuleV1') - expect(result).toEqual('/app/src/assets/images/thermocycler_closed.png') - }) - - it('should render the high res thermocycler module image when the model is a thermocycler module gen 1 high res', () => { - const result = getModuleImage('thermocyclerModuleV1', true) - expect(result).toEqual( - '/app/src/assets/images/modules/thermocyclerModuleV1@3x.png' - ) - }) - - it('should render the thermocycler module image when the model is a thermocycler module gen 2', () => { - const result = getModuleImage('thermocyclerModuleV2') - expect(result).toEqual( - '/app/src/assets/images/thermocycler_gen_2_closed.png' - ) - }) - - it('should render the magnetic block v1 image when the module is magneticBlockV1', () => { - const result = getModuleImage('magneticBlockV1') - expect(result).toEqual('/app/src/assets/images/magnetic_block_gen_1.png') - }) -}) - -describe('getFixtureImage', () => { - it('should render the staging area image', () => { - const result = getFixtureImage('stagingAreaRightSlot') - expect(result).toEqual('/app/src/assets/images/staging_area_slot.png') - }) - it('should render the waste chute image', () => { - const result = getFixtureImage('wasteChuteRightAdapterNoCover') - expect(result).toEqual('/app/src/assets/images/waste_chute.png') - }) - it('should render the waste chute staging area image', () => { - const result = getFixtureImage( - 'stagingAreaSlotWithWasteChuteRightAdapterCovered' - ) - expect(result).toEqual( - '/app/src/assets/images/waste_chute_with_staging_area.png' - ) - }) - it('should render the trash bin image', () => { - const result = getFixtureImage('trashBinAdapter') - expect(result).toEqual('/app/src/assets/images/flex_trash_bin.png') - }) -}) diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx deleted file mode 100644 index 5c9bcba8dff..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/index.tsx +++ /dev/null @@ -1,159 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' - -import { - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - JUSTIFY_CENTER, - JUSTIFY_SPACE_BETWEEN, - LegacyStyledText, - PrimaryButton, - SPACING, - Tooltip, - TYPOGRAPHY, - useHoverTooltip, -} from '@opentrons/components' - -import { useToggleGroup } from '../../../../molecules/ToggleGroup/useToggleGroup' -import { useDeckConfigurationCompatibility } from '../../../../resources/deck_configuration/hooks' -import { - getIsFixtureMismatch, - getRequiredDeckConfig, -} from '../../../../resources/deck_configuration/utils' -import { - useRunHasStarted, - useUnmatchedModulesForProtocol, - useModuleCalibrationStatus, - useRobotType, -} from '../../hooks' -import { SetupModulesMap } from './SetupModulesMap' -import { SetupModulesList } from './SetupModulesList' -import { SetupFixtureList } from './SetupFixtureList' - -import type { - CompletedProtocolAnalysis, - ProtocolAnalysisOutput, -} from '@opentrons/shared-data' - -interface SetupModuleAndDeckProps { - expandLabwarePositionCheckStep: () => void - robotName: string - runId: string - hasModules: boolean - protocolAnalysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput | null -} - -export const SetupModuleAndDeck = ({ - expandLabwarePositionCheckStep, - robotName, - runId, - hasModules, - protocolAnalysis, -}: SetupModuleAndDeckProps): JSX.Element => { - const { t, i18n } = useTranslation('protocol_setup') - const [selectedValue, toggleGroup] = useToggleGroup( - t('list_view') as string, - t('map_view') as string - ) - - const robotType = useRobotType(robotName) - const { missingModuleIds } = useUnmatchedModulesForProtocol(robotName, runId) - const runHasStarted = useRunHasStarted(runId) - const [targetProps, tooltipProps] = useHoverTooltip() - - const moduleCalibrationStatus = useModuleCalibrationStatus(robotName, runId) - const deckConfigCompatibility = useDeckConfigurationCompatibility( - robotType, - protocolAnalysis - ) - - const isFixtureMismatch = getIsFixtureMismatch(deckConfigCompatibility) - - const requiredDeckConfigCompatibility = getRequiredDeckConfig( - deckConfigCompatibility - ) - - return ( - <> - - {toggleGroup} - {selectedValue === t('list_view') ? ( - <> - - - {i18n.format(t('deck_hardware'), 'capitalize')} - - - {t('location')} - - - {t('status')} - - - - {hasModules ? ( - - ) : null} - {requiredDeckConfigCompatibility.length > 0 ? ( - - ) : null} - - - ) : ( - - )} - - - 0 || - isFixtureMismatch || - runHasStarted || - !moduleCalibrationStatus.complete - } - onClick={expandLabwarePositionCheckStep} - id="ModuleSetup_proceedToLabwarePositionCheck" - padding={`${SPACING.spacing8} ${SPACING.spacing16}`} - {...targetProps} - > - {t('proceed_to_labware_position_check')} - - - {missingModuleIds.length > 0 || - runHasStarted || - !moduleCalibrationStatus.complete ? ( - - {runHasStarted - ? t('protocol_run_started') - : missingModuleIds.length > 0 - ? t('plug_in_required_module', { count: missingModuleIds.length }) - : t('calibrate_module_failure_reason')} - - ) : null} - - ) -} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/utils.ts b/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/utils.ts deleted file mode 100644 index f5bd5187ad1..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/utils.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { - HEATERSHAKER_MODULE_V1_FIXTURE, - MAGNETIC_BLOCK_V1_FIXTURE, - STAGING_AREA_RIGHT_SLOT_FIXTURE, - STAGING_AREA_SLOT_WITH_MAGNETIC_BLOCK_V1_FIXTURE, - TEMPERATURE_MODULE_V2_FIXTURE, - THERMOCYCLER_V2_FRONT_FIXTURE, - THERMOCYCLER_V2_REAR_FIXTURE, - TRASH_BIN_ADAPTER_FIXTURE, - WASTE_CHUTE_ONLY_FIXTURES, - WASTE_CHUTE_STAGING_AREA_FIXTURES, -} from '@opentrons/shared-data' - -import magneticModule from '../../../../assets/images/magnetic_module_gen_2_transparent.png' -import temperatureModule from '../../../../assets/images/temp_deck_gen_2_transparent.png' -import thermoModuleGen1 from '../../../../assets/images/thermocycler_closed.png' -import heaterShakerModule from '../../../../assets/images/heater_shaker_module_transparent.png' -import magneticModuleHighRes from '../../../../assets/images/modules/magneticModuleV2@3x.png' -import temperatureModuleHighRes from '../../../../assets/images/modules/temperatureModuleV2@3x.png' -import thermoModuleGen1HighRes from '../../../../assets/images/modules/thermocyclerModuleV1@3x.png' -import heaterShakerModuleHighRes from '../../../../assets/images/modules/heaterShakerModuleV1@3x.png' -import thermoModuleGen2 from '../../../../assets/images/thermocycler_gen_2_closed.png' -import magneticBlockGen1 from '../../../../assets/images/magnetic_block_gen_1.png' -import stagingAreaMagneticBlockGen1 from '../../../../assets/images/staging_area_magnetic_block_gen_1.png' -import trashBin from '../../../../assets/images/flex_trash_bin.png' -import stagingArea from '../../../../assets/images/staging_area_slot.png' -import wasteChute from '../../../../assets/images/waste_chute.png' -import wasteChuteStagingArea from '../../../../assets/images/waste_chute_with_staging_area.png' - -import type { CutoutFixtureId, ModuleModel } from '@opentrons/shared-data' - -export function getModuleImage( - model: ModuleModel, - highRes: boolean = false -): string { - switch (model) { - case 'magneticModuleV1': - case 'magneticModuleV2': - return highRes ? magneticModuleHighRes : magneticModule - case 'temperatureModuleV1': - case 'temperatureModuleV2': - return highRes ? temperatureModuleHighRes : temperatureModule - case 'heaterShakerModuleV1': - return highRes ? heaterShakerModuleHighRes : heaterShakerModule - case 'thermocyclerModuleV1': - return highRes ? thermoModuleGen1HighRes : thermoModuleGen1 - case 'thermocyclerModuleV2': - return thermoModuleGen2 - case 'magneticBlockV1': - return magneticBlockGen1 - default: - return 'Error: unknown module model' - } -} - -export function getFixtureImage(cutoutFixtureId: CutoutFixtureId): string { - if (cutoutFixtureId === STAGING_AREA_RIGHT_SLOT_FIXTURE) { - return stagingArea - } else if (WASTE_CHUTE_ONLY_FIXTURES.includes(cutoutFixtureId)) { - return wasteChute - } else if (WASTE_CHUTE_STAGING_AREA_FIXTURES.includes(cutoutFixtureId)) { - return wasteChuteStagingArea - } else if (cutoutFixtureId === TRASH_BIN_ADAPTER_FIXTURE) { - return trashBin - } else if (cutoutFixtureId === THERMOCYCLER_V2_REAR_FIXTURE) { - return thermoModuleGen2 - } else if (cutoutFixtureId === THERMOCYCLER_V2_FRONT_FIXTURE) { - return thermoModuleGen2 - } else if (cutoutFixtureId === HEATERSHAKER_MODULE_V1_FIXTURE) { - return heaterShakerModule - } else if (cutoutFixtureId === TEMPERATURE_MODULE_V2_FIXTURE) { - return temperatureModule - } else if (cutoutFixtureId === MAGNETIC_BLOCK_V1_FIXTURE) { - return magneticBlockGen1 - } else if ( - cutoutFixtureId === STAGING_AREA_SLOT_WITH_MAGNETIC_BLOCK_V1_FIXTURE - ) { - return stagingAreaMagneticBlockGen1 - } else { - return 'Error: unknown fixture' - } -} diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunHeader.test.tsx b/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunHeader.test.tsx deleted file mode 100644 index 3d292fcabd9..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/ProtocolRunHeader.test.tsx +++ /dev/null @@ -1,1080 +0,0 @@ -import * as React from 'react' -import { BrowserRouter } from 'react-router-dom' -import { fireEvent, screen, waitFor } from '@testing-library/react' -import { when } from 'vitest-when' -import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' - -import { - RUN_STATUS_IDLE, - RUN_STATUS_RUNNING, - RUN_STATUS_PAUSED, - RUN_STATUS_STOP_REQUESTED, - RUN_STATUS_STOPPED, - RUN_STATUS_FAILED, - RUN_STATUS_SUCCEEDED, - RUN_STATUS_BLOCKED_BY_OPEN_DOOR, - instrumentsResponseLeftPipetteFixture, -} from '@opentrons/api-client' -import { - useHost, - useModulesQuery, - usePipettesQuery, - useDismissCurrentRunMutation, - useDeleteRunMutation, - useEstopQuery, - useDoorQuery, - useInstrumentsQuery, - useRunCommandErrors, -} from '@opentrons/react-api-client' -import { - getPipetteModelSpecs, - STAGING_AREA_SLOT_WITH_WASTE_CHUTE_RIGHT_ADAPTER_NO_COVER_FIXTURE, - simple_v6 as _uncastedSimpleV6Protocol, - simple_v4 as noModulesProtocol, -} from '@opentrons/shared-data' - -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { useCloseCurrentRun } from '../../../../organisms/ProtocolUpload/hooks' -import { ConfirmCancelModal } from '../../../../organisms/RunDetails/ConfirmCancelModal' -import { - useRunTimestamps, - useRunControls, - useRunStatus, -} from '../../../../organisms/RunTimeControl/hooks' -import { - mockFailedRun, - mockIdleUnstartedRun, - mockPausedRun, - mockRunningRun, - mockStoppedRun, - mockStopRequestedRun, - mockSucceededRun, -} from '../../../../organisms/RunTimeControl/__fixtures__' -import { mockHeaterShaker } from '../../../../redux/modules/__fixtures__' -import { - useTrackEvent, - ANALYTICS_PROTOCOL_PROCEED_TO_RUN, - ANALYTICS_PROTOCOL_RUN_ACTION, -} from '../../../../redux/analytics' -import { mockConnectableRobot } from '../../../../redux/discovery/__fixtures__' -import { getRobotUpdateDisplayInfo } from '../../../../redux/robot-update' -import { getIsHeaterShakerAttached } from '../../../../redux/config' -import { getRobotSettings } from '../../../../redux/robot-settings' -import { getStoredProtocol } from '../../../../redux/protocol-storage' -import { storedProtocolData as storedProtocolDataFixture } from '../../../../redux/protocol-storage/__fixtures__' -import { - useProtocolDetailsForRun, - useProtocolAnalysisErrors, - useTrackProtocolRunEvent, - useRunCalibrationStatus, - useRunCreatedAtTimestamp, - useModuleCalibrationStatus, - useUnmatchedModulesForProtocol, - useIsRobotViewable, - useIsFlex, - useRobot, -} from '../../hooks' -import { useIsHeaterShakerInProtocol } from '../../../ModuleCard/hooks' -import { ConfirmAttachmentModal } from '../../../ModuleCard/ConfirmAttachmentModal' -import { RunProgressMeter } from '../../../RunProgressMeter' -import { formatTimestamp } from '../../utils' -import { ProtocolRunHeader } from '../ProtocolRunHeader' -import { HeaterShakerIsRunningModal } from '../../HeaterShakerIsRunningModal' -import { RunFailedModal } from '../RunFailedModal' -import { DISENGAGED, NOT_PRESENT } from '../../../EmergencyStop' -import { getIsFixtureMismatch } from '../../../../resources/deck_configuration/utils' -import { useDeckConfigurationCompatibility } from '../../../../resources/deck_configuration/hooks' -import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useMostRecentRunId } from '../../../ProtocolUpload/hooks/useMostRecentRunId' -import { useNotifyRunQuery, useCurrentRunId } from '../../../../resources/runs' -import { - useDropTipWizardFlows, - useTipAttachmentStatus, - DropTipWizardFlows, -} from '../../../DropTipWizardFlows' -import { - useErrorRecoveryFlows, - ErrorRecoveryFlows, -} from '../../../ErrorRecoveryFlows' -import { - ProtocolDropTipModal, - useProtocolDropTipModal, -} from '../ProtocolDropTipModal' -import { ConfirmMissingStepsModal } from '../ConfirmMissingStepsModal' - -import type { MissingSteps } from '../ProtocolRunSetup' -import type { UseQueryResult } from 'react-query' -import type { NavigateFunction } from 'react-router-dom' -import type { Mock } from 'vitest' -import type * as OpentronsSharedData from '@opentrons/shared-data' -import type * as OpentronsComponents from '@opentrons/components' -import type * as OpentronsApiClient from '@opentrons/api-client' -import type { State } from '../../../../redux/types' - -const mockNavigate = vi.fn() - -vi.mock('react-router-dom', async importOriginal => { - const reactRouterDom = await importOriginal() - return { - ...reactRouterDom, - useNavigate: () => mockNavigate, - } -}) - -vi.mock('@opentrons/components', async importOriginal => { - const actual = await importOriginal() - return { - ...actual, - Tooltip: vi.fn(({ children }) =>
    {children}
    ), - } -}) - -vi.mock('@opentrons/shared-data', async importOriginal => { - const actual = await importOriginal() - return { - ...actual, - getPipetteModelSpecs: vi.fn(), - } -}) - -vi.mock('@opentrons/react-api-client') -vi.mock('../../../../organisms/ProtocolUpload/hooks') -vi.mock('../../../../organisms/RunDetails/ConfirmCancelModal') -vi.mock('../../../../organisms/RunTimeControl/hooks') -vi.mock('../../hooks') -vi.mock('../../HeaterShakerIsRunningModal') -vi.mock('../../../ModuleCard/ConfirmAttachmentModal') -vi.mock('../../../ModuleCard/hooks') -vi.mock('../../../RunProgressMeter') -vi.mock('../../../../redux/analytics') -vi.mock('../../../../redux/config') -vi.mock('../../../../redux/protocol-storage') -vi.mock('../RunFailedModal') -vi.mock('../../../../redux/robot-update/selectors') -vi.mock('../../../../redux/robot-settings/selectors') -vi.mock('../../../DropTipWizardFlows') -vi.mock('../../../../resources/deck_configuration/utils') -vi.mock('../../../../resources/deck_configuration/hooks') -vi.mock('../../../LabwarePositionCheck/useMostRecentCompletedAnalysis') -vi.mock('../../../ProtocolUpload/hooks/useMostRecentRunId') -vi.mock('../../../../resources/runs') -vi.mock('../../../ErrorRecoveryFlows') -vi.mock('../ProtocolDropTipModal') -vi.mock('../ConfirmMissingStepsModal') - -const ROBOT_NAME = 'otie' -const RUN_ID = '95e67900-bc9f-4fbf-92c6-cc4d7226a51b' -const CREATED_AT = '03/03/2022 19:08:49' -const STARTED_AT = '2022-03-03T19:09:40.620530+00:00' -const COMPLETED_AT = '2022-03-03T19:39:53.620530+00:00' -const PROTOCOL_NAME = 'A Protocol for Otie' -const PROTOCOL_KEY = 'fakeProtocolKey' -const mockSettings = { - id: 'enableDoorSafetySwitch', - title: 'Enable Door Safety Switch', - description: '', - value: true, - restart_required: false, -} -const MOCK_ROTOCOL_LIQUID_KEY = { liquids: [] } -const MOCK_ROBOT_SERIAL_NUMBER = 'OT123' - -const simpleV6Protocol = (_uncastedSimpleV6Protocol as unknown) as OpentronsSharedData.CompletedProtocolAnalysis - -const PROTOCOL_DETAILS = { - displayName: PROTOCOL_NAME, - protocolData: simpleV6Protocol, - protocolKey: PROTOCOL_KEY, - isProtocolAnalyzing: false, - robotType: 'OT-2 Standard' as const, - isQuickTransfer: false, -} - -const RUN_COMMAND_ERRORS = { - data: { - data: [ - { - errorCode: '4000', - errorType: 'test', - isDefined: false, - createdAt: '9-9-9', - detail: 'blah blah', - id: '123', - }, - ], - meta: { - cursor: 0, - pageLength: 1, - }, - }, -} as any - -const mockMovingHeaterShaker = { - id: 'heatershaker_id', - moduleModel: 'heaterShakerModuleV1', - moduleType: 'heaterShakerModuleType', - serialNumber: 'jkl123', - hardwareRevision: 'heatershaker_v4.0', - firmwareVersion: 'v2.0.0', - hasAvailableUpdate: true, - data: { - labwareLatchStatus: 'idle_closed', - speedStatus: 'speeding up', - temperatureStatus: 'idle', - currentSpeed: null, - currentTemperature: null, - targetSpeed: null, - targetTemp: null, - errorDetails: null, - status: 'idle', - }, - usbPort: { path: '/dev/ot_module_heatershaker0', port: 1 }, -} as any - -const mockEstopStatus = { - data: { - status: DISENGAGED, - leftEstopPhysicalStatus: DISENGAGED, - rightEstopPhysicalStatus: NOT_PRESENT, - }, -} -const mockDoorStatus = { - data: { - status: 'closed', - doorRequiredClosedForProtocol: true, - }, -} -let mockMissingSteps: MissingSteps = [] - -const render = () => { - return renderWithProviders( - - vi.fn())} - missingSetupSteps={mockMissingSteps} - /> - , - { i18nInstance: i18n } - ) -} -let mockTrackEvent: Mock -let mockTrackProtocolRunEvent: Mock -let mockCloseCurrentRun: Mock -let mockDetermineTipStatus: Mock - -describe('ProtocolRunHeader', () => { - beforeEach(() => { - mockTrackEvent = vi.fn() - mockTrackProtocolRunEvent = vi.fn(() => new Promise(resolve => resolve({}))) - mockCloseCurrentRun = vi.fn() - mockDetermineTipStatus = vi.fn() - mockMissingSteps = [] - vi.mocked(useTrackEvent).mockReturnValue(mockTrackEvent) - vi.mocked(ConfirmCancelModal).mockReturnValue( -
    Mock ConfirmCancelModal
    - ) - vi.mocked(RunProgressMeter).mockReturnValue( -
    Mock RunProgressMeter
    - ) - vi.mocked(HeaterShakerIsRunningModal).mockReturnValue( -
    Mock HeaterShakerIsRunningModal
    - ) - vi.mocked(useModulesQuery).mockReturnValue({ - data: { data: [] }, - } as any) - vi.mocked(useDeleteRunMutation).mockReturnValue({ - deleteRun: vi.fn(), - } as any) - vi.mocked(usePipettesQuery).mockReturnValue({ - data: { - data: { - left: null, - right: null, - }, - }, - } as any) - vi.mocked(getIsHeaterShakerAttached).mockReturnValue(false) - vi.mocked(useIsRobotViewable).mockReturnValue(true) - vi.mocked(ConfirmAttachmentModal).mockReturnValue( -
    mock confirm attachment modal
    - ) - vi.mocked(ConfirmMissingStepsModal).mockReturnValue( -
    mock missing steps modal
    - ) - when(vi.mocked(useProtocolAnalysisErrors)).calledWith(RUN_ID).thenReturn({ - analysisErrors: null, - }) - vi.mocked(useIsHeaterShakerInProtocol).mockReturnValue(false) - vi.mocked(getRobotUpdateDisplayInfo).mockReturnValue({ - autoUpdateAction: 'reinstall', - autoUpdateDisabledReason: null, - updateFromFileDisabledReason: null, - }) - when(vi.mocked(useCurrentRunId)).calledWith().thenReturn(RUN_ID) - when(vi.mocked(useCloseCurrentRun)).calledWith().thenReturn({ - isClosingCurrentRun: false, - closeCurrentRun: mockCloseCurrentRun, - }) - when(vi.mocked(useRunControls)) - .calledWith(RUN_ID, expect.anything()) - .thenReturn({ - play: () => {}, - pause: () => {}, - stop: () => {}, - reset: () => {}, - resumeFromRecovery: () => {}, - isPlayRunActionLoading: false, - isPauseRunActionLoading: false, - isStopRunActionLoading: false, - isResetRunLoading: false, - isResumeRunFromRecoveryActionLoading: false, - }) - when(vi.mocked(useRunStatus)).calledWith(RUN_ID).thenReturn(RUN_STATUS_IDLE) - when(vi.mocked(useRunTimestamps)).calledWith(RUN_ID).thenReturn({ - startedAt: STARTED_AT, - pausedAt: null, - stoppedAt: null, - completedAt: null, - }) - when(vi.mocked(useRunCreatedAtTimestamp)) - .calledWith(RUN_ID) - .thenReturn(CREATED_AT) - when(vi.mocked(useNotifyRunQuery)) - .calledWith(RUN_ID, { staleTime: Infinity }) - .thenReturn({ - data: { data: mockIdleUnstartedRun }, - } as UseQueryResult) - when(vi.mocked(useProtocolDetailsForRun)) - .calledWith(RUN_ID) - .thenReturn(PROTOCOL_DETAILS) - when(vi.mocked(useTrackProtocolRunEvent)) - .calledWith(RUN_ID, ROBOT_NAME) - .thenReturn({ - trackProtocolRunEvent: mockTrackProtocolRunEvent, - }) - when(vi.mocked(useDismissCurrentRunMutation)) - .calledWith() - .thenReturn({ - dismissCurrentRun: vi.fn(), - } as any) - when(vi.mocked(useUnmatchedModulesForProtocol)) - .calledWith(ROBOT_NAME, RUN_ID) - .thenReturn({ missingModuleIds: [], remainingAttachedModules: [] }) - when(vi.mocked(useRunCalibrationStatus)) - .calledWith(ROBOT_NAME, RUN_ID) - .thenReturn({ complete: true }) - when(vi.mocked(useIsFlex)).calledWith(ROBOT_NAME).thenReturn(true) - when(vi.mocked(useModuleCalibrationStatus)) - .calledWith(ROBOT_NAME, RUN_ID) - .thenReturn({ complete: true }) - vi.mocked(RunFailedModal).mockReturnValue(
    mock RunFailedModal
    ) - vi.mocked(useEstopQuery).mockReturnValue({ data: mockEstopStatus } as any) - vi.mocked(useDoorQuery).mockReturnValue({ data: mockDoorStatus } as any) - vi.mocked(getRobotSettings).mockReturnValue([mockSettings]) - vi.mocked(useInstrumentsQuery).mockReturnValue({ data: {} } as any) - vi.mocked(useHost).mockReturnValue({} as any) - vi.mocked(useTipAttachmentStatus).mockReturnValue({ - aPipetteWithTip: instrumentsResponseLeftPipetteFixture, - areTipsAttached: true, - determineTipStatus: mockDetermineTipStatus, - resetTipStatus: vi.fn(), - } as any) - vi.mocked(useDropTipWizardFlows).mockReturnValue({ - showDTWiz: false, - toggleDTWiz: vi.fn(), - }) - vi.mocked(getPipetteModelSpecs).mockReturnValue('p10_single_v1' as any) - when(vi.mocked(useMostRecentCompletedAnalysis)) - .calledWith(RUN_ID) - .thenReturn({ - ...noModulesProtocol, - ...MOCK_ROTOCOL_LIQUID_KEY, - } as any) - vi.mocked(useRunCommandErrors).mockReturnValue(RUN_COMMAND_ERRORS) - vi.mocked(useDeckConfigurationCompatibility).mockReturnValue([]) - vi.mocked(getIsFixtureMismatch).mockReturnValue(false) - vi.mocked(useMostRecentRunId).mockReturnValue(RUN_ID) - vi.mocked(useRobot).mockReturnValue({ - ...mockConnectableRobot, - health: { - ...mockConnectableRobot.health, - robot_serial: MOCK_ROBOT_SERIAL_NUMBER, - }, - }) - vi.mocked(useErrorRecoveryFlows).mockReturnValue({ - isERActive: false, - failedCommand: {}, - } as any) - vi.mocked(ErrorRecoveryFlows).mockReturnValue( -
    MOCK_ERROR_RECOVERY
    - ) - vi.mocked(useProtocolDropTipModal).mockReturnValue({ - onDTModalRemoval: vi.fn(), - onDTModalSkip: vi.fn(), - showDTModal: false, - isDisabled: false, - }) - vi.mocked(ProtocolDropTipModal).mockReturnValue( -
    MOCK_DROP_TIP_MODAL
    - ) - vi.mocked(DropTipWizardFlows).mockReturnValue( -
    MOCK_DROP_TIP_WIZARD_FLOWS
    - ) - when(getStoredProtocol) - .calledWith({} as State, PROTOCOL_KEY) - .thenReturn(storedProtocolDataFixture) - }) - - afterEach(() => { - vi.clearAllMocks() - }) - - it('renders a protocol name, run record id, status, and run time', () => { - render() - - screen.getByText('A Protocol for Otie') - screen.getByText('Run') - screen.getByText('03/03/2022 19:08:49') - screen.getByText('Status') - screen.getByText('Not started') - screen.getByText('Run Time') - }) - - it('links to a protocol details page', () => { - render() - - const protocolNameLink = screen.getByRole('link', { - name: 'A Protocol for Otie', - }) - expect(protocolNameLink.getAttribute('href')).toBe( - `/protocols/${PROTOCOL_DETAILS.protocolKey}` - ) - }) - - it('does not render link to protocol detail page if protocol key is absent', () => { - when(vi.mocked(useProtocolDetailsForRun)) - .calledWith(RUN_ID) - .thenReturn({ ...PROTOCOL_DETAILS, protocolKey: null }) - render() - - expect( - screen.queryByRole('link', { name: 'A Protocol for Otie' }) - ).toBeNull() - }) - - it('does not render link to protocol detail page if stored protocol is absent', () => { - vi.mocked(getStoredProtocol).mockReturnValue(null) - render() - - expect( - screen.queryByRole('link', { name: 'A Protocol for Otie' }) - ).toBeNull() - }) - - it('renders a disabled "Analyzing on robot" button if robot-side analysis is not complete', () => { - when(vi.mocked(useProtocolDetailsForRun)).calledWith(RUN_ID).thenReturn({ - displayName: null, - protocolData: null, - protocolKey: null, - isProtocolAnalyzing: true, - robotType: 'OT-2 Standard', - isQuickTransfer: false, - }) - - render() - - const button = screen.getByRole('button', { name: 'Analyzing on robot' }) - expect(button).toBeDisabled() - }) - - it('renders a start run button and cancel run button when run is ready to start', () => { - render() - - screen.getByRole('button', { name: 'Start run' }) - screen.queryByText(formatTimestamp(STARTED_AT)) - screen.queryByText('Protocol start') - screen.queryByText('Protocol end') - fireEvent.click(screen.getByRole('button', { name: 'Cancel run' })) - screen.getByText('Mock ConfirmCancelModal') - screen.getByText('Mock RunProgressMeter') - }) - - it('calls trackProtocolRunEvent when start run button clicked', () => { - render() - - const button = screen.getByRole('button', { name: 'Start run' }) - fireEvent.click(button) - expect(mockTrackProtocolRunEvent).toBeCalledTimes(1) - expect(mockTrackProtocolRunEvent).toBeCalledWith({ - name: ANALYTICS_PROTOCOL_RUN_ACTION.START, - properties: {}, - }) - }) - - it('dismisses a current but canceled run and calls trackProtocolRunEvent', () => { - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_STOPPED) - vi.mocked(useNotifyRunQuery).mockReturnValue({ - data: { data: { ...mockIdleUnstartedRun, current: true } }, - } as UseQueryResult) - render() - expect(mockTrackProtocolRunEvent).toBeCalled() - expect(mockTrackProtocolRunEvent).toBeCalledWith({ - name: ANALYTICS_PROTOCOL_RUN_ACTION.FINISH, - properties: {}, - }) - }) - - it('disables the Start Run button with tooltip if calibration is incomplete', () => { - when(vi.mocked(useRunCalibrationStatus)) - .calledWith(ROBOT_NAME, RUN_ID) - .thenReturn({ complete: false }) - - render() - - const button = screen.getByRole('button', { name: 'Start run' }) - expect(button).toBeDisabled() - screen.getByText('Complete required steps in Setup tab') - }) - - it('disables the Start Run button with tooltip if a module is missing', () => { - when(vi.mocked(useUnmatchedModulesForProtocol)) - .calledWith(ROBOT_NAME, RUN_ID) - .thenReturn({ - missingModuleIds: ['temperatureModuleV1'], - remainingAttachedModules: [], - }) - - render() - const button = screen.getByRole('button', { name: 'Start run' }) - expect(button).toBeDisabled() - screen.getByText('Complete required steps in Setup tab') - }) - - it('disables the Start Run button with tooltip if robot software update is available', () => { - vi.mocked(getRobotUpdateDisplayInfo).mockReturnValue({ - autoUpdateAction: 'upgrade', - autoUpdateDisabledReason: null, - updateFromFileDisabledReason: null, - }) - - render() - const button = screen.getByRole('button', { name: 'Start run' }) - expect(button).toBeDisabled() - screen.getByText( - 'A software update is available for this robot. Update to run protocols.' - ) - }) - - it('disables the Start Run button when a fixture is not configured or conflicted', () => { - vi.mocked(useDeckConfigurationCompatibility).mockReturnValue([ - { - cutoutId: 'cutoutA1', - cutoutFixtureId: STAGING_AREA_SLOT_WITH_WASTE_CHUTE_RIGHT_ADAPTER_NO_COVER_FIXTURE, - requiredAddressableAreas: ['D4'], - compatibleCutoutFixtureIds: [ - STAGING_AREA_SLOT_WITH_WASTE_CHUTE_RIGHT_ADAPTER_NO_COVER_FIXTURE, - ], - missingLabwareDisplayName: null, - }, - ]) - vi.mocked(getIsFixtureMismatch).mockReturnValue(true) - render() - const button = screen.getByRole('button', { name: 'Start run' }) - expect(button).toBeDisabled() - }) - - it('renders a pause run button, start time, and end time when run is running, and calls trackProtocolRunEvent when button clicked', () => { - when(vi.mocked(useNotifyRunQuery)) - .calledWith(RUN_ID) - .thenReturn({ - data: { data: mockRunningRun }, - } as UseQueryResult) - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_RUNNING) - render() - - const button = screen.getByRole('button', { name: 'Pause run' }) - screen.getByText(formatTimestamp(STARTED_AT)) - screen.getByText('Protocol start') - screen.getByText('Protocol end') - fireEvent.click(button) - expect(mockTrackProtocolRunEvent).toBeCalledWith({ - name: ANALYTICS_PROTOCOL_RUN_ACTION.PAUSE, - }) - }) - - it('renders a cancel run button when running and shows a confirm cancel modal when clicked', () => { - when(vi.mocked(useNotifyRunQuery)) - .calledWith(RUN_ID) - .thenReturn({ - data: { data: mockRunningRun }, - } as UseQueryResult) - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_RUNNING) - render() - - expect(screen.queryByText('Mock ConfirmCancelModal')).toBeFalsy() - const cancelButton = screen.getByText('Cancel run') - fireEvent.click(cancelButton) - screen.getByText('Mock ConfirmCancelModal') - }) - - it('renders a Resume Run button and Cancel Run button when paused and call trackProtocolRunEvent when resume button clicked', () => { - when(vi.mocked(useNotifyRunQuery)) - .calledWith(RUN_ID) - .thenReturn({ - data: { data: mockPausedRun }, - } as UseQueryResult) - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_PAUSED) - - render() - - const button = screen.getByRole('button', { name: 'Resume run' }) - screen.getByRole('button', { name: 'Cancel run' }) - screen.getByText('Paused') - fireEvent.click(button) - expect(mockTrackProtocolRunEvent).toBeCalledWith({ - name: ANALYTICS_PROTOCOL_RUN_ACTION.RESUME, - properties: {}, - }) - }) - - it('renders a disabled Canceling Run button and when stop requested', () => { - when(vi.mocked(useNotifyRunQuery)) - .calledWith(RUN_ID) - .thenReturn({ - data: { data: mockStopRequestedRun }, - } as UseQueryResult) - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_STOP_REQUESTED) - - render() - - const button = screen.getByRole('button', { name: 'Canceling Run' }) - expect(button).toBeDisabled() - screen.getByText('Stop requested') - }) - - it('renders a disabled button and when the robot door is open', () => { - when(vi.mocked(useNotifyRunQuery)) - .calledWith(RUN_ID) - .thenReturn({ - data: { data: mockRunningRun }, - } as UseQueryResult) - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_BLOCKED_BY_OPEN_DOOR) - - const mockOpenDoorStatus = { - data: { status: 'open', doorRequiredClosedForProtocol: true }, - } - vi.mocked(useDoorQuery).mockReturnValue({ data: mockOpenDoorStatus } as any) - - render() - - const button = screen.getByRole('button', { name: 'Resume run' }) - expect(button).toBeDisabled() - screen.getByText('Close robot door') - }) - - it('renders a Run Again button and end time when run has stopped and calls trackProtocolRunEvent when run again button clicked', () => { - when(vi.mocked(useNotifyRunQuery)) - .calledWith(RUN_ID) - .thenReturn({ - data: { data: mockStoppedRun }, - } as UseQueryResult) - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_STOPPED) - when(vi.mocked(useRunTimestamps)).calledWith(RUN_ID).thenReturn({ - startedAt: STARTED_AT, - pausedAt: null, - stoppedAt: null, - completedAt: COMPLETED_AT, - }) - - render() - - const button = screen.getByText('Run again') - screen.getByText('Canceled') - screen.getByText(formatTimestamp(COMPLETED_AT)) - fireEvent.click(button) - expect(mockTrackProtocolRunEvent).toBeCalledWith({ - name: ANALYTICS_PROTOCOL_RUN_ACTION.AGAIN, - }) - }) - - it('renders a Run Again button and end time when run has failed and calls trackProtocolRunEvent when run again button clicked', () => { - when(vi.mocked(useNotifyRunQuery)) - .calledWith(RUN_ID) - .thenReturn({ - data: { data: mockFailedRun }, - } as UseQueryResult) - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_FAILED) - when(vi.mocked(useRunTimestamps)).calledWith(RUN_ID).thenReturn({ - startedAt: STARTED_AT, - pausedAt: null, - stoppedAt: null, - completedAt: COMPLETED_AT, - }) - - render() - - const button = screen.getByText('Run again') - screen.getByText('Failed') - screen.getByText(formatTimestamp(COMPLETED_AT)) - fireEvent.click(button) - expect(mockTrackProtocolRunEvent).toBeCalledWith({ - name: ANALYTICS_PROTOCOL_RUN_ACTION.AGAIN, - }) - }) - - it('renders a Run Again button and end time when run has succeeded and calls trackProtocolRunEvent when run again button clicked', () => { - when(vi.mocked(useNotifyRunQuery)) - .calledWith(RUN_ID) - .thenReturn({ - data: { data: mockSucceededRun }, - } as UseQueryResult) - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_SUCCEEDED) - when(vi.mocked(useRunTimestamps)).calledWith(RUN_ID).thenReturn({ - startedAt: STARTED_AT, - pausedAt: null, - stoppedAt: null, - completedAt: COMPLETED_AT, - }) - - render() - - const button = screen.getByText('Run again') - screen.getByText('Completed') - screen.getByText(formatTimestamp(COMPLETED_AT)) - fireEvent.click(button) - expect(mockTrackEvent).toBeCalledWith({ - name: ANALYTICS_PROTOCOL_PROCEED_TO_RUN, - properties: { - sourceLocation: 'RunRecordDetail', - robotSerialNumber: MOCK_ROBOT_SERIAL_NUMBER, - }, - }) - expect(mockTrackProtocolRunEvent).toBeCalledWith({ - name: ANALYTICS_PROTOCOL_RUN_ACTION.AGAIN, - }) - }) - - it('disables the Run Again button with tooltip for a completed run if the robot is busy', () => { - when(vi.mocked(useNotifyRunQuery)) - .calledWith(RUN_ID) - .thenReturn({ - data: { data: mockSucceededRun }, - } as UseQueryResult) - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_SUCCEEDED) - when(vi.mocked(useRunTimestamps)).calledWith(RUN_ID).thenReturn({ - startedAt: STARTED_AT, - pausedAt: null, - stoppedAt: null, - completedAt: COMPLETED_AT, - }) - when(vi.mocked(useCurrentRunId)) - .calledWith() - .thenReturn('some other run id') - - render() - - const button = screen.getByRole('button', { name: 'Run again' }) - expect(button).toBeDisabled() - screen.getByText('Robot is busy') - }) - - it('renders an alert when the robot door is open', () => { - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_BLOCKED_BY_OPEN_DOOR) - render() - - screen.getByText('Close robot door to resume run') - }) - - it('renders a error detail link banner when run has failed', () => { - when(vi.mocked(useNotifyRunQuery)) - .calledWith(RUN_ID) - .thenReturn({ - data: { data: mockFailedRun }, - } as UseQueryResult) - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_FAILED) - render() - - fireEvent.click(screen.getByText('View error details')) - screen.getByText('mock RunFailedModal') - }) - - it('does not render banners when a run is resetting', () => { - when(vi.mocked(useNotifyRunQuery)) - .calledWith(RUN_ID) - .thenReturn({ - data: { data: mockFailedRun }, - } as UseQueryResult) - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_FAILED) - when(vi.mocked(useRunControls)) - .calledWith(RUN_ID, expect.anything()) - .thenReturn({ - play: () => {}, - pause: () => {}, - stop: () => {}, - reset: () => {}, - resumeFromRecovery: () => {}, - isPlayRunActionLoading: false, - isPauseRunActionLoading: false, - isStopRunActionLoading: false, - isResetRunLoading: true, - isResumeRunFromRecoveryActionLoading: false, - }) - render() - - expect(screen.queryByText('mock RunFailedModal')).not.toBeInTheDocument() - }) - - it('renders a clear protocol banner when run has been canceled', () => { - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_STOPPED) - vi.mocked(useTipAttachmentStatus).mockReturnValue({ - areTipsAttached: false, - determineTipStatus: mockDetermineTipStatus, - } as any) - render() - - screen.getByText('Run canceled.') - expect(screen.queryByTestId('Banner_close-button')).not.toBeInTheDocument() - }) - - it('renders a clear protocol banner when run has succeeded', async () => { - vi.mocked(useNotifyRunQuery).mockReturnValue({ - data: { data: mockSucceededRun }, - } as UseQueryResult) - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_SUCCEEDED) - render() - - screen.getByText('Run completed with warnings.') - }) - - it('does not display the "run successful" banner if the successful run is not current', async () => { - when(vi.mocked(useNotifyRunQuery)) - .calledWith(RUN_ID) - .thenReturn({ - data: { data: { ...mockSucceededRun, current: false } }, - } as UseQueryResult) - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_SUCCEEDED) - render() - - expect(screen.queryByText('Run completed.')).not.toBeInTheDocument() - }) - - it('if a heater shaker is shaking, clicking on start run should render HeaterShakerIsRunningModal', async () => { - when(vi.mocked(useRunStatus)).calledWith(RUN_ID).thenReturn(RUN_STATUS_IDLE) - vi.mocked(useIsHeaterShakerInProtocol).mockReturnValue(true) - vi.mocked(useModulesQuery).mockReturnValue({ - data: { data: [mockMovingHeaterShaker] }, - } as any) - render() - const button = screen.getByRole('button', { name: 'Start run' }) - fireEvent.click(button) - await waitFor(() => { - screen.getByText('Mock HeaterShakerIsRunningModal') - }) - }) - - it('does not render the confirm attachment modal when there is a heater shaker in the protocol and run is idle', () => { - vi.mocked(useModulesQuery).mockReturnValue({ - data: { data: [mockHeaterShaker] }, - } as any) - vi.mocked(useIsHeaterShakerInProtocol).mockReturnValue(true) - render() - - const button = screen.getByRole('button', { name: 'Start run' }) - fireEvent.click(button) - screen.getByText('mock confirm attachment modal') - expect(mockTrackProtocolRunEvent).toBeCalledTimes(0) - }) - - it('renders the confirm attachment modal when there is a heater shaker in the protocol and the heater shaker is idle status and run is paused', () => { - when(vi.mocked(useRunStatus)) - .calledWith(RUN_ID) - .thenReturn(RUN_STATUS_PAUSED) - - vi.mocked(useModulesQuery).mockReturnValue({ - data: { data: [mockHeaterShaker] }, - } as any) - vi.mocked(useIsHeaterShakerInProtocol).mockReturnValue(true) - render() - - const button = screen.getByRole('button', { name: 'Resume run' }) - fireEvent.click(button) - expect(screen.queryByText('mock confirm attachment modal')).toBeFalsy() - expect(mockTrackProtocolRunEvent).toBeCalledTimes(1) - }) - - it('does NOT render confirm attachment modal when the user already confirmed the heater shaker is attached', () => { - vi.mocked(getIsHeaterShakerAttached).mockReturnValue(true) - vi.mocked(useModulesQuery).mockReturnValue({ - data: { data: [mockHeaterShaker] }, - } as any) - vi.mocked(useIsHeaterShakerInProtocol).mockReturnValue(true) - render() - const button = screen.getByRole('button', { name: 'Start run' }) - fireEvent.click(button) - expect(vi.mocked(useRunControls)).toHaveBeenCalled() - }) - - it('renders analysis error modal if there is an analysis error', () => { - when(vi.mocked(useProtocolAnalysisErrors)) - .calledWith(RUN_ID) - .thenReturn({ - analysisErrors: [ - { - id: 'error_id', - detail: 'protocol analysis error', - errorType: 'analysis', - createdAt: '100000', - }, - ], - }) - render() - screen.getByText('protocol analysis error') - }) - - it('renders analysis error banner if there is an analysis error', () => { - when(vi.mocked(useProtocolAnalysisErrors)) - .calledWith(RUN_ID) - .thenReturn({ - analysisErrors: [ - { - id: 'error_id', - detail: 'protocol analysis error', - errorType: 'analysis', - createdAt: '100000', - }, - ], - }) - render() - screen.getByText('Protocol analysis failed.') - }) - - it('renders the devices page when robot is not viewable but protocol is loaded', async () => { - vi.mocked(useIsRobotViewable).mockReturnValue(false) - render() - await waitFor(() => { - expect(mockNavigate).toHaveBeenCalledWith('/devices') - }) - }) - - it('renders door close banner when the robot door is open', () => { - const mockOpenDoorStatus = { - data: { status: 'open', doorRequiredClosedForProtocol: true }, - } - vi.mocked(useDoorQuery).mockReturnValue({ data: mockOpenDoorStatus } as any) - render() - screen.getByText('Close the robot door before starting the run.') - }) - - it('should render door close banner when door is open and enabled safety door switch is on - OT-2', () => { - when(vi.mocked(useIsFlex)).calledWith(ROBOT_NAME).thenReturn(false) - const mockOpenDoorStatus = { - data: { status: 'open', doorRequiredClosedForProtocol: true }, - } - vi.mocked(useDoorQuery).mockReturnValue({ data: mockOpenDoorStatus } as any) - render() - screen.getByText('Close the robot door before starting the run.') - }) - - it('should not render door close banner when door is open and enabled safety door switch is off - OT-2', () => { - when(vi.mocked(useIsFlex)).calledWith(ROBOT_NAME).thenReturn(false) - const mockOffSettings = { ...mockSettings, value: false } - vi.mocked(getRobotSettings).mockReturnValue([mockOffSettings]) - const mockOpenDoorStatus = { - data: { status: 'open', doorRequiredClosedForProtocol: true }, - } - vi.mocked(useDoorQuery).mockReturnValue({ data: mockOpenDoorStatus } as any) - render() - expect( - screen.queryByText('Close the robot door before starting the run.') - ).not.toBeInTheDocument() - }) - - it('renders the drop tip modal initially when the run ends if tips are attached', () => { - vi.mocked(useProtocolDropTipModal).mockReturnValue({ - onDTModalRemoval: vi.fn(), - onDTModalSkip: vi.fn(), - showDTModal: true, - isDisabled: false, - }) - - render() - - screen.getByText('MOCK_DROP_TIP_MODAL') - }) - - it('does not render the drop tip modal when the run is not over', async () => { - when(vi.mocked(useNotifyRunQuery)) - .calledWith(RUN_ID) - .thenReturn({ - data: { - data: { - ...mockIdleUnstartedRun, - current: true, - status: RUN_STATUS_IDLE, - }, - }, - } as UseQueryResult) - when(vi.mocked(useRunStatus)).calledWith(RUN_ID).thenReturn(RUN_STATUS_IDLE) - - render() - await waitFor(() => { - expect(mockDetermineTipStatus).not.toHaveBeenCalled() - }) - }) - - it('renders Error Recovery Flows when isERActive is true', () => { - vi.mocked(useErrorRecoveryFlows).mockReturnValue({ - isERActive: true, - failedCommand: {}, - } as any) - - render() - screen.getByText('MOCK_ERROR_RECOVERY') - }) - - it('renders DropTipWizardFlows when conditions are met', () => { - vi.mocked(useDropTipWizardFlows).mockReturnValue({ - showDTWiz: true, - toggleDTWiz: vi.fn(), - }) - - render() - screen.getByText('MOCK_DROP_TIP_WIZARD_FLOWS') - }) -}) diff --git a/app/src/organisms/Devices/ProtocolRun/__tests__/RunFailedModal.test.tsx b/app/src/organisms/Devices/ProtocolRun/__tests__/RunFailedModal.test.tsx deleted file mode 100644 index affc52d8d94..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/__tests__/RunFailedModal.test.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import * as React from 'react' -import { describe, it, beforeEach, vi, expect, afterEach } from 'vitest' - -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { useDownloadRunLog } from '../../hooks' -import { RunFailedModal } from '../RunFailedModal' - -import { RUN_STATUS_FAILED } from '@opentrons/api-client' -import type { RunError } from '@opentrons/api-client' -import { fireEvent, screen } from '@testing-library/react' - -vi.mock('../../hooks') - -const RUN_ID = '1' -const ROBOT_NAME = 'mockRobotName' -const mockError: RunError = { - id: '5097b3e6-3900-482d-abb1-0a8d8a0e515d', - errorType: 'ModuleNotAttachedError', - isDefined: false, - createdAt: '2023-08-07T20:16:57.720783+00:00', - detail: 'No available thermocyclerModuleV2 found.', - errorCode: '4000', - errorInfo: {}, - wrappedErrors: [], -} - -const render = (props: React.ComponentProps) => { - return renderWithProviders(, { - i18nInstance: i18n, - }) -} - -describe('RunFailedModal - DesktopApp', () => { - let props: React.ComponentProps - - beforeEach(() => { - props = { - robotName: ROBOT_NAME, - runId: RUN_ID, - setShowRunFailedModal: vi.fn(), - highestPriorityError: mockError, - runStatus: RUN_STATUS_FAILED, - } - vi.mocked(useDownloadRunLog).mockReturnValue({ - downloadRunLog: vi.fn(), - isRunLogLoading: false, - }) - }) - - afterEach(() => { - vi.clearAllMocks() - }) - - it('should render text, link and button', () => { - render(props) - screen.getByText('Run failed') - screen.getByText('Error 4000: ModuleNotAttachedError') - screen.getByText('No available thermocyclerModuleV2 found.') - screen.getByText( - 'Download the run log and send it to support@opentrons.com for assistance.' - ) - screen.getByText('Download Run Log') - screen.getByRole('button', { name: 'Close' }) - }) - - it('should call a mock function when clicking close button', () => { - render(props) - fireEvent.click(screen.getByRole('button', { name: 'Close' })) - expect(props.setShowRunFailedModal).toHaveBeenCalled() - }) - - it('should close the modal when clicking close icon', () => { - render(props) - fireEvent.click(screen.getByRole('button', { name: '' })) - expect(props.setShowRunFailedModal).toHaveBeenCalled() - }) - - it('should call a mock function when clicking download run log button', () => { - render(props) - fireEvent.click(screen.getByText('Download Run Log')) - expect(vi.mocked(useDownloadRunLog)).toHaveBeenCalled() - }) -}) diff --git a/app/src/organisms/Devices/ProtocolRun/useLabwareOffsetForLabware.ts b/app/src/organisms/Devices/ProtocolRun/useLabwareOffsetForLabware.ts deleted file mode 100644 index f352ee2e40d..00000000000 --- a/app/src/organisms/Devices/ProtocolRun/useLabwareOffsetForLabware.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { getLoadedLabwareDefinitionsByUri } from '@opentrons/shared-data' - -import { getCurrentOffsetForLabwareInLocation } from './utils/getCurrentOffsetForLabwareInLocation' -import { getLabwareDefinitionUri } from './utils/getLabwareDefinitionUri' -import { getLabwareOffsetLocation } from './utils/getLabwareOffsetLocation' -import { useNotifyRunQuery } from '../../../resources/runs' - -import type { LabwareOffset } from '@opentrons/api-client' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' - -export function useLabwareOffsetForLabware( - runId: string, - labwareId: string -): LabwareOffset | null { - const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) - const { data: runRecord } = useNotifyRunQuery(runId) - if (mostRecentAnalysis == null) return null - - const labwareDefinitionsByUri = getLoadedLabwareDefinitionsByUri( - mostRecentAnalysis.commands - ) - const labwareDefinitionUri = getLabwareDefinitionUri( - labwareId, - mostRecentAnalysis.labware, - labwareDefinitionsByUri - ) - - const labwareLocation = getLabwareOffsetLocation( - labwareId, - mostRecentAnalysis?.commands ?? [], - mostRecentAnalysis?.modules ?? [], - mostRecentAnalysis?.labware ?? [] - ) - if (labwareLocation == null || labwareDefinitionUri == null) return null - const labwareOffsets = runRecord?.data?.labwareOffsets ?? [] - - return ( - getCurrentOffsetForLabwareInLocation( - labwareOffsets, - labwareDefinitionUri, - labwareLocation - ) ?? null - ) -} diff --git a/app/src/organisms/Devices/ReachableBanner.tsx b/app/src/organisms/Devices/ReachableBanner.tsx deleted file mode 100644 index fe600e868f6..00000000000 --- a/app/src/organisms/Devices/ReachableBanner.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { SPACING } from '@opentrons/components' -import { Banner } from '../../atoms/Banner' -import { REACHABLE } from '../../redux/discovery' - -import type { DiscoveredRobot } from '../../redux/discovery/types' - -interface ReachableBannerProps { - robot: DiscoveredRobot -} - -export function ReachableBanner( - props: ReachableBannerProps -): JSX.Element | null { - const { robot } = props - const { t } = useTranslation('shared') - return robot.status === REACHABLE && robot.serverHealthStatus === 'ok' ? ( - - {t('robot_is_reachable_but_not_responding', { - hostname: robot.ip, - })} - - ) : null -} diff --git a/app/src/organisms/Devices/RecentProtocolRuns.tsx b/app/src/organisms/Devices/RecentProtocolRuns.tsx deleted file mode 100644 index 06815a7b064..00000000000 --- a/app/src/organisms/Devices/RecentProtocolRuns.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useAllProtocolsQuery } from '@opentrons/react-api-client' -import { - ALIGN_CENTER, - ALIGN_FLEX_START, - BORDERS, - COLORS, - DIRECTION_COLUMN, - DISPLAY_FLEX, - Flex, - JUSTIFY_FLEX_START, - SIZE_4, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' - -import { HistoricalProtocolRun } from './HistoricalProtocolRun' -import { useIsRobotViewable, useRunStatuses } from './hooks' -import { useNotifyAllRunsQuery, useCurrentRunId } from '../../resources/runs' - -interface RecentProtocolRunsProps { - robotName: string -} - -export function RecentProtocolRuns({ - robotName, -}: RecentProtocolRunsProps): JSX.Element | null { - const { t } = useTranslation(['device_details', 'shared']) - const isRobotViewable = useIsRobotViewable(robotName) - const runsQueryResponse = useNotifyAllRunsQuery() - const runs = runsQueryResponse?.data?.data - const protocols = useAllProtocolsQuery() - const currentRunId = useCurrentRunId() - const { isRunTerminal } = useRunStatuses() - const robotIsBusy = currentRunId != null ? !isRunTerminal : false - - return ( - - - {t('recent_protocol_runs')} - - - {isRobotViewable && runs && runs.length > 0 && ( - <> - - - {t('run')} - - - {t('protocol')} - - - {t('files')} - - - {t('status')} - - - {t('run_duration')} - - - {runs - .sort( - (a, b) => - new Date(b.createdAt).getTime() - - new Date(a.createdAt).getTime() - ) - .map((run, index) => { - const protocol = protocols?.data?.data.find( - protocol => protocol.id === run.protocolId - ) - - const protocolName = - protocol?.metadata.protocolName ?? - protocol?.files[0].name ?? - t('shared:loading') ?? - '' - - return ( - - ) - })} - - )} - {!isRobotViewable && ( - - {t('offline_recent_protocol_runs')} - - )} - {isRobotViewable && (runs == null || runs.length === 0) && ( - - {t('no_protocol_runs')} - - )} - - - ) -} diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/DeviceReset.tsx b/app/src/organisms/Devices/RobotSettings/AdvancedTab/DeviceReset.tsx deleted file mode 100644 index d76aa144097..00000000000 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/DeviceReset.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' - -import { - ALIGN_CENTER, - Box, - Flex, - JUSTIFY_SPACE_BETWEEN, - SPACING_AUTO, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' - -import { TertiaryButton } from '../../../../atoms/buttons' - -interface DeviceResetProps { - updateIsExpanded: ( - isExpanded: boolean, - type: 'deviceReset' | 'renameRobot' - ) => void - isRobotBusy: boolean -} - -export function DeviceReset({ - updateIsExpanded, - isRobotBusy, -}: DeviceResetProps): JSX.Element { - const { t } = useTranslation('device_settings') - - const handleClick: React.MouseEventHandler = () => { - if (!isRobotBusy) { - updateIsExpanded(true, 'deviceReset') - } - } - - return ( - - - - {t('device_reset')} - - - {t('device_reset_description')} - - - - {t('choose_reset_settings')} - - - ) -} diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/DeviceReset.test.tsx b/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/DeviceReset.test.tsx deleted file mode 100644 index d08784d28a0..00000000000 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/__tests__/DeviceReset.test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import * as React from 'react' -import { MemoryRouter } from 'react-router-dom' -import { fireEvent, screen } from '@testing-library/react' -import { describe, it, vi, expect } from 'vitest' -import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' - -import { i18n } from '../../../../../i18n' - -import { DeviceReset } from '../DeviceReset' - -const mockUpdateIsEXpanded = vi.fn() - -vi.mock('../../../../ProtocolUpload/hooks') - -const render = (isRobotBusy = false) => { - return renderWithProviders( - - - , - { i18nInstance: i18n } - ) -} - -describe('RobotSettings DeviceReset', () => { - it('should render title, description, and butoon', () => { - render() - screen.getByText('Device Reset') - screen.getByText( - 'Reset labware calibration, boot scripts, and/or robot calibration to factory settings.' - ) - expect( - screen.getByRole('button', { name: 'Choose reset settings' }) - ).toBeInTheDocument() - }) - - it('should render a slideout when clicking the button', () => { - render() - const button = screen.getByRole('button', { - name: 'Choose reset settings', - }) - fireEvent.click(button) - expect(mockUpdateIsEXpanded).toHaveBeenCalled() - }) - - it('should call update robot status if a robot is busy', () => { - render(true) - const button = screen.getByRole('button', { - name: 'Choose reset settings', - }) - expect(button).toBeDisabled() - }) -}) diff --git a/app/src/organisms/Devices/RobotSettings/AdvancedTab/index.ts b/app/src/organisms/Devices/RobotSettings/AdvancedTab/index.ts deleted file mode 100644 index e3359c3998b..00000000000 --- a/app/src/organisms/Devices/RobotSettings/AdvancedTab/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export * from './DeviceReset' -export * from './DisplayRobotName' -export * from './EnableStatusLight' -export * from './FactoryMode' -export * from './GantryHoming' -export * from './LegacySettings' -export * from './OpenJupyterControl' -export * from './RobotInformation' -export * from './RobotServerVersion' -export * from './ShortTrashBin' -export * from './Troubleshooting' -export * from './UpdateRobotSoftware' -export * from './UsageSettings' -export * from './UseOlderAspirateBehavior' diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/TextField.tsx b/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/TextField.tsx deleted file mode 100644 index 4cfa4c0c48c..00000000000 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/TextField.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import * as React from 'react' - -import { - LegacyInputField, - DeprecatedCheckboxField, - INPUT_TYPE_TEXT, - INPUT_TYPE_PASSWORD, -} from '@opentrons/components' - -import { FormRow } from './FormRow' -import { useConnectFormField } from './form-state' -import { LABEL_SHOW_PASSWORD } from '../i18n' -import type { - ControllerFieldState, - ControllerRenderProps, - FieldValues, -} from 'react-hook-form' - -export interface TextFieldProps { - id: string - name: string - label: string - isPassword: boolean - field: ControllerRenderProps - fieldState: ControllerFieldState - className?: string -} - -export const TextField = (props: TextFieldProps): JSX.Element => { - const { id, name, label, isPassword, className, field, fieldState } = props - const { value, error, onChange, onBlur } = useConnectFormField( - field, - fieldState - ) - const [showPw, toggleShowPw] = React.useReducer(show => !show, false) - const type = isPassword && !showPw ? INPUT_TYPE_PASSWORD : INPUT_TYPE_TEXT - - return ( - - - {isPassword && ( - - )} - - ) -} diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/UploadKeyInput.tsx b/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/UploadKeyInput.tsx deleted file mode 100644 index 05c532f0876..00000000000 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/UploadKeyInput.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import * as React from 'react' -import styled from 'styled-components' -import { useSelector } from 'react-redux' -import last from 'lodash/last' - -import { useDispatchApiRequest } from '../../../../../redux/robot-api' -import { - postWifiKeys, - getWifiKeyByRequestId, -} from '../../../../../redux/networking' - -import type { State } from '../../../../../redux/types' - -export interface UploadKeyInputProps { - robotName: string - label: string - onUpload: (keyId: string) => unknown -} - -// TODO(mc, 2020-03-04): create styled HiddenInput in components library -const HiddenInput = styled.input` - position: absolute; - overflow: hidden; - clip: rect(0 0 0 0); - height: 1px; - width: 1px; - margin: -1px; - padding: 0; - border: 0; -` - -const UploadKeyInputComponent = ( - props: UploadKeyInputProps, - ref: React.ForwardedRef -): JSX.Element => { - const { robotName, label, onUpload } = props - const [dispatchApi, requestIds] = useDispatchApiRequest() - const handleUpload = React.useRef<(key: string) => void>() - - const createdKeyId = useSelector((state: State) => { - return getWifiKeyByRequestId(state, robotName, last(requestIds) ?? null) - })?.id - - const handleFileInput: React.ChangeEventHandler = event => { - if (event.target.files && event.target.files.length > 0) { - const file = event.target.files[0] - event.target.value = '' - - dispatchApi(postWifiKeys(robotName, file)) - } - } - - React.useEffect(() => { - handleUpload.current = onUpload - }, [onUpload]) - - React.useEffect(() => { - if (createdKeyId != null && handleUpload.current) { - handleUpload.current(createdKeyId) - } - }, [createdKeyId]) - - return ( - - ) -} - -export const UploadKeyInput = React.forwardRef< - HTMLInputElement, - UploadKeyInputProps ->(UploadKeyInputComponent) diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/index.tsx b/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/index.tsx deleted file mode 100644 index c9f726dcb76..00000000000 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/ConnectModal/index.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import * as React from 'react' -import { useForm } from 'react-hook-form' - -import { useResetFormOnSecurityChange } from './form-state' -import { - getConnectFormFields, - validateConnectFormFields, - connectFormToConfigureRequest, -} from './form-fields' - -import { FormModal } from './FormModal' - -import type { Control, Resolver } from 'react-hook-form' -import type { - ConnectFormValues, - WifiConfigureRequest, - WifiNetwork, - WifiKey, - EapOption, -} from '../types' - -export interface ConnectModalProps { - robotName: string - network: WifiNetwork | null - wifiKeys: WifiKey[] - eapOptions: EapOption[] - onConnect: (r: WifiConfigureRequest) => void - onCancel: () => void -} - -interface ConnectModalComponentProps extends ConnectModalProps { - isValid: boolean - values: ConnectFormValues - control: Control - id: string -} - -export const ConnectModal = (props: ConnectModalProps): JSX.Element => { - const { network, eapOptions, onConnect } = props - - const onSubmit = (values: ConnectFormValues): void => { - const request = connectFormToConfigureRequest(network, values) - if (request) onConnect(request) - } - - const handleValidate: Resolver = values => { - let errors = {} - - errors = validateConnectFormFields(network, eapOptions, values, errors) - return { values, errors } - } - - const { - handleSubmit, - formState: { isValid }, - getValues, - control, - } = useForm({ - defaultValues: {}, - resolver: handleValidate, - }) - - const values = getValues() - const id = `ConnectForm__${props.robotName}` - - return ( -
    - - - ) -} - -export const ConnectModalComponent = ( - props: ConnectModalComponentProps -): JSX.Element => { - const { - robotName, - network, - wifiKeys, - eapOptions, - onCancel, - values, - isValid, - id, - control, - } = props - - const fields = getConnectFormFields( - network, - robotName, - eapOptions, - wifiKeys, - values - ) - - useResetFormOnSecurityChange() - - return ( - - ) -} diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/SelectSsid/index.tsx b/app/src/organisms/Devices/RobotSettings/ConnectNetwork/SelectSsid/index.tsx deleted file mode 100644 index 227bf8b4586..00000000000 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/SelectSsid/index.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import * as React from 'react' -import { CONTEXT_MENU } from '@opentrons/components' -import { SelectField } from '../../../../../atoms/SelectField' -import * as Copy from '../i18n' -import { NetworkOptionLabel, NetworkActionLabel } from './NetworkOptionLabel' - -import type { SelectOptionOrGroup } from '@opentrons/components' - -import type { WifiNetwork } from '../types' - -export interface SelectSsidProps { - list: WifiNetwork[] - value: string | null - onConnect: (ssid: string) => unknown - onJoinOther: () => unknown - isRobotBusy: boolean -} - -const FIELD_NAME = '__SelectSsid__' - -const JOIN_OTHER_VALUE = '__join-other-network__' - -const SELECT_JOIN_OTHER_GROUP = { - options: [{ value: JOIN_OTHER_VALUE, label: Copy.LABEL_JOIN_OTHER_NETWORK }], -} - -const formatOptions = (list: WifiNetwork[]): SelectOptionOrGroup[] => { - const ssidOptionsList = { - options: list?.map(({ ssid }) => ({ value: ssid })), - } - const options = [ssidOptionsList, SELECT_JOIN_OTHER_GROUP] - - return options -} - -export function SelectSsid(props: SelectSsidProps): JSX.Element { - const { list, value, onConnect, onJoinOther, isRobotBusy } = props - - const handleValueChange = (_: string, value: string): void => { - if (value === JOIN_OTHER_VALUE) { - onJoinOther() - } else { - onConnect(value) - } - } - - const formatOptionLabel: React.ComponentProps< - typeof SelectField - >['formatOptionLabel'] = (option, { context }): JSX.Element | null => { - const { value, label } = option - - if (label != null) return - const network = list.find(nw => nw.ssid === value) - - // react-select sets context to tell us if the value is rendered in the - // options menu list or in the currently selected value. If it's being - // rendered in the menu, we want to show a connected icon if the network - // is active, but if the context is value, we want to hide the icon - return network != null ? ( - - ) : null - } - - return ( - - ) -} diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/constants.ts b/app/src/organisms/Devices/RobotSettings/ConnectNetwork/constants.ts deleted file mode 100644 index f41c785edbc..00000000000 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/constants.ts +++ /dev/null @@ -1,20 +0,0 @@ -export { - AUTH_TYPE_STRING, - AUTH_TYPE_PASSWORD, - AUTH_TYPE_FILE, - SECURITY_NONE, - SECURITY_WPA_EAP, - SECURITY_WPA_PSK, - CONFIGURE_FIELD_SSID, - CONFIGURE_FIELD_PSK, - CONFIGURE_FIELD_SECURITY_TYPE, - CONFIGURE_PSK_MIN_LENGTH, -} from '../../../../redux/networking' - -export const CONNECT: 'connect' = 'connect' -export const DISCONNECT: 'disconnect' = 'disconnect' -export const JOIN_OTHER: 'join-other' = 'join-other' - -export const FIELD_TYPE_TEXT: 'text' = 'text' -export const FIELD_TYPE_KEY_FILE: 'key-file' = 'key-file' -export const FIELD_TYPE_SECURITY: 'security' = 'security' diff --git a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/types.ts b/app/src/organisms/Devices/RobotSettings/ConnectNetwork/types.ts deleted file mode 100644 index a864b81c8eb..00000000000 --- a/app/src/organisms/Devices/RobotSettings/ConnectNetwork/types.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { FieldError } from 'react-hook-form' -import type { - WifiNetwork, - EapOption, - WifiKey, -} from '../../../../redux/networking/types' - -import type { - CONNECT, - DISCONNECT, - JOIN_OTHER, - FIELD_TYPE_TEXT, - FIELD_TYPE_KEY_FILE, - FIELD_TYPE_SECURITY, -} from './constants' - -export type { - WifiNetwork, - WifiSecurityType, - WifiAuthField, - WifiEapConfig, - WifiConfigureRequest, - WifiKey, - EapOption, -} from '../../../../redux/networking/types' - -export type NetworkChangeType = - | typeof CONNECT - | typeof DISCONNECT - | typeof JOIN_OTHER - -export type NetworkChangeState = - | { type: typeof CONNECT; ssid: string; network: WifiNetwork } - | { type: typeof DISCONNECT; ssid: string } - | { type: typeof JOIN_OTHER; ssid: string | null } - | { type: null } - -export type ConnectFormValues = Partial<{ - ssid?: string - psk?: string - // securityType form value may be securityType or eapConfig.eapType - securityType?: string - eapConfig?: { - [eapOption: string]: string - } -}> - -export type ConnectFormErrors = ConnectFormValues & FieldError - -interface ConnectFormFieldCommon { - name: string - label: string -} - -export interface ConnectFormTextField extends ConnectFormFieldCommon { - type: typeof FIELD_TYPE_TEXT - isPassword: boolean -} - -export interface ConnectFormKeyField extends ConnectFormFieldCommon { - type: typeof FIELD_TYPE_KEY_FILE - robotName: string - wifiKeys: WifiKey[] - placeholder: string -} - -// UI only auth field; server will never return this field type -export interface ConnectFormSecurityField extends ConnectFormFieldCommon { - type: typeof FIELD_TYPE_SECURITY - eapOptions: EapOption[] - showAllOptions: boolean - placeholder: string -} - -export type ConnectFormField = - | ConnectFormTextField - | ConnectFormKeyField - | ConnectFormSecurityField - -export type ConnectFormFieldProps = Readonly<{ - value: string | null - error: string | null - onChange: React.ChangeEventHandler - onBlur: React.FocusEventHandler - setValue: (value: string) => unknown - setTouched: (touched: boolean) => unknown -}> diff --git a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/index.tsx b/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/index.tsx deleted file mode 100644 index 1cbbaacc0c8..00000000000 --- a/app/src/organisms/Devices/RobotSettings/UpdateBuildroot/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import * as React from 'react' -import { useSelector, useDispatch } from 'react-redux' -import NiceModal, { useModal } from '@ebay/nice-modal-react' - -import { ApiHostProvider } from '@opentrons/react-api-client' - -import { - setRobotUpdateSeen, - robotUpdateIgnored, - getRobotUpdateSession, -} from '../../../../redux/robot-update' -import { ViewUpdateModal } from './ViewUpdateModal' -import { RobotUpdateProgressModal } from './RobotUpdateProgressModal' -import { UNREACHABLE, OPENTRONS_USB } from '../../../../redux/discovery' -import { appShellRequestor } from '../../../../redux/shell/remote' - -import type { Dispatch } from '../../../../redux/types' -import type { DiscoveredRobot } from '../../../../redux/discovery/types' - -interface UpdateBuildrootProps { - robot: DiscoveredRobot | null -} - -export const handleUpdateBuildroot = ( - robot: UpdateBuildrootProps['robot'] -): void => { - NiceModal.show(UpdateBuildroot, { robot }) -} - -const UpdateBuildroot = NiceModal.create( - (props: UpdateBuildrootProps): JSX.Element | null => { - const { robot } = props - const hasSeenSessionOnce = React.useRef(false) - const modal = useModal() - const robotName = React.useRef(robot?.name ?? '') - const dispatch = useDispatch() - const session = useSelector(getRobotUpdateSession) - if (!hasSeenSessionOnce.current && session) - hasSeenSessionOnce.current = true - - React.useEffect(() => { - if (robotName.current) { - dispatch(setRobotUpdateSeen(robotName.current)) - } - }, [robotName]) - - const ignoreUpdate = React.useCallback(() => { - if (robotName.current) { - dispatch(robotUpdateIgnored(robotName.current)) - } - modal.remove() - }, [robotName, close]) - - if (hasSeenSessionOnce.current) - return ( - - - - ) - else if (robot != null && robot.status !== UNREACHABLE) - return ( - - ) - else return null - } -) diff --git a/app/src/organisms/Devices/__tests__/HistoricalProtocolRunOverflowMenu.test.tsx b/app/src/organisms/Devices/__tests__/HistoricalProtocolRunOverflowMenu.test.tsx deleted file mode 100644 index 8abe226ead3..00000000000 --- a/app/src/organisms/Devices/__tests__/HistoricalProtocolRunOverflowMenu.test.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import * as React from 'react' -import { fireEvent, screen } from '@testing-library/react' -import { describe, it, vi, beforeEach, expect } from 'vitest' -import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { when } from 'vitest-when' -import { MemoryRouter } from 'react-router-dom' -import { useDeleteRunMutation } from '@opentrons/react-api-client' -import { i18n } from '../../../i18n' -import runRecord from '../../../organisms/RunDetails/__fixtures__/runRecord.json' -import { useDownloadRunLog, useTrackProtocolRunEvent, useRobot } from '../hooks' -import { useRunControls } from '../../RunTimeControl/hooks' -import { - useTrackEvent, - ANALYTICS_PROTOCOL_PROCEED_TO_RUN, -} from '../../../redux/analytics' -import { mockConnectableRobot } from '../../../redux/discovery/__fixtures__' -import { getRobotUpdateDisplayInfo } from '../../../redux/robot-update' -import { useIsEstopNotDisengaged } from '../../../resources/devices/hooks/useIsEstopNotDisengaged' -import { HistoricalProtocolRunOverflowMenu } from '../HistoricalProtocolRunOverflowMenu' -import { useNotifyAllCommandsQuery } from '../../../resources/runs' - -import type { UseQueryResult } from 'react-query' -import type { CommandsData } from '@opentrons/api-client' - -vi.mock('../../../redux/analytics') -vi.mock('../../../redux/robot-update/selectors') -vi.mock('../../Devices/hooks') -vi.mock('../../RunTimeControl/hooks') -vi.mock('../../../redux/analytics') -vi.mock('../../../redux/config') -vi.mock('../../../resources/devices/hooks/useIsEstopNotDisengaged') -vi.mock('../../../resources/runs') -vi.mock('@opentrons/react-api-client') - -const render = ( - props: React.ComponentProps -) => { - return renderWithProviders( - - - , - { - i18nInstance: i18n, - } - )[0] -} -const PAGE_LENGTH = 101 -const RUN_ID = 'id' -const ROBOT_NAME = 'otie' -let mockTrackEvent: any -let mockTrackProtocolRunEvent: any -const mockDownloadRunLog = vi.fn() - -describe('HistoricalProtocolRunOverflowMenu', () => { - let props: React.ComponentProps - beforeEach(() => { - mockTrackEvent = vi.fn() - vi.mocked(useTrackEvent).mockReturnValue(mockTrackEvent) - mockTrackProtocolRunEvent = vi.fn(() => new Promise(resolve => resolve({}))) - vi.mocked(getRobotUpdateDisplayInfo).mockReturnValue({ - autoUpdateAction: 'reinstall', - autoUpdateDisabledReason: null, - updateFromFileDisabledReason: null, - }) - vi.mocked(useDownloadRunLog).mockReturnValue({ - downloadRunLog: mockDownloadRunLog, - isRunLogLoading: false, - }) - vi.mocked(useDeleteRunMutation).mockReturnValue({ - deleteRun: vi.fn(), - } as any) - - when(useTrackProtocolRunEvent).calledWith(RUN_ID, ROBOT_NAME).thenReturn({ - trackProtocolRunEvent: mockTrackProtocolRunEvent, - }) - when(useRunControls) - .calledWith(RUN_ID, expect.anything()) - .thenReturn({ - play: () => {}, - pause: () => {}, - stop: () => {}, - reset: () => {}, - resumeFromRecovery: () => {}, - isPlayRunActionLoading: false, - isPauseRunActionLoading: false, - isStopRunActionLoading: false, - isResetRunLoading: false, - isResumeRunFromRecoveryActionLoading: false, - }) - when(useNotifyAllCommandsQuery) - .calledWith( - RUN_ID, - { - cursor: 0, - pageLength: PAGE_LENGTH, - }, - { staleTime: Infinity } - ) - .thenReturn(({ - data: { data: runRecord.data.commands, meta: { totalLength: 14 } }, - } as unknown) as UseQueryResult) - when(useIsEstopNotDisengaged).calledWith(ROBOT_NAME).thenReturn(false) - props = { - runId: RUN_ID, - robotName: ROBOT_NAME, - robotIsBusy: false, - } - when(vi.mocked(useRobot)) - .calledWith(ROBOT_NAME) - .thenReturn(mockConnectableRobot) - }) - - it('renders the correct menu when a runId is present', () => { - render(props) - - const btn = screen.getByRole('button') - fireEvent.click(btn) - screen.getByRole('button', { - name: 'View protocol run record', - }) - const rerunBtn = screen.getByRole('button', { name: 'Rerun protocol now' }) - screen.getByRole('button', { name: 'Download protocol run log' }) - const deleteBtn = screen.getByRole('button', { - name: 'Delete protocol run record', - }) - fireEvent.click(rerunBtn) - expect(mockTrackEvent).toHaveBeenCalledWith({ - name: ANALYTICS_PROTOCOL_PROCEED_TO_RUN, - properties: { - robotSerialNumber: 'mock-serial', - sourceLocation: 'HistoricalProtocolRun', - }, - }) - expect(useRunControls).toHaveBeenCalled() - expect(mockTrackProtocolRunEvent).toHaveBeenCalled() - fireEvent.click(deleteBtn) - expect(useDeleteRunMutation).toHaveBeenCalled() - }) - - it('disables the rerun protocol menu item if robot software update is available', () => { - vi.mocked(getRobotUpdateDisplayInfo).mockReturnValue({ - autoUpdateAction: 'upgrade', - autoUpdateDisabledReason: null, - updateFromFileDisabledReason: null, - }) - render(props) - const btn = screen.getByRole('button') - fireEvent.click(btn) - screen.getByRole('button', { - name: 'View protocol run record', - }) - const rerunBtn = screen.getByRole('button', { name: 'Rerun protocol now' }) - expect(rerunBtn).toBeDisabled() - }) - - it('should make overflow menu disabled when e-stop is pressed', () => { - when(useIsEstopNotDisengaged).calledWith(ROBOT_NAME).thenReturn(true) - render(props) - expect(screen.getByRole('button')).toBeDisabled() - }) -}) diff --git a/app/src/organisms/Devices/__tests__/RobotOverflowMenu.test.tsx b/app/src/organisms/Devices/__tests__/RobotOverflowMenu.test.tsx deleted file mode 100644 index 868e14cf171..00000000000 --- a/app/src/organisms/Devices/__tests__/RobotOverflowMenu.test.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import * as React from 'react' -import { MemoryRouter } from 'react-router-dom' -import { fireEvent, screen } from '@testing-library/react' -import { describe, it, vi, beforeEach, expect } from 'vitest' -import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useCurrentRunId } from '../../../resources/runs' -import { ChooseProtocolSlideout } from '../../ChooseProtocolSlideout' -import { RobotOverflowMenu } from '../RobotOverflowMenu' -import { getRobotUpdateDisplayInfo } from '../../../redux/robot-update' -import { useIsRobotBusy } from '../hooks' - -import { - mockUnreachableRobot, - mockConnectedRobot, -} from '../../../redux/discovery/__fixtures__' - -vi.mock('../../../redux/robot-update/selectors') -vi.mock('../../../resources/runs') -vi.mock('../../ChooseProtocolSlideout') -vi.mock('../hooks') -vi.mock('../../../resources/devices/hooks/useIsEstopNotDisengaged') - -const render = (props: React.ComponentProps) => { - return renderWithProviders( - - - , - { - i18nInstance: i18n, - } - )[0] -} - -describe('RobotOverflowMenu', () => { - let props: React.ComponentProps - - beforeEach(() => { - props = { - robot: mockConnectedRobot, - } - vi.mocked(useCurrentRunId).mockReturnValue('RUNID') - vi.mocked(ChooseProtocolSlideout).mockReturnValue( -
    choose protocol slideout
    - ) - vi.mocked(getRobotUpdateDisplayInfo).mockReturnValue({ - autoUpdateAction: 'reinstall', - autoUpdateDisabledReason: null, - updateFromFileDisabledReason: null, - }) - vi.mocked(useIsRobotBusy).mockReturnValue(false) - }) - - it('renders overflow menu items when the robot is reachable and a run id is present', () => { - render(props) - const btn = screen.getByLabelText('RobotOverflowMenu_button') - fireEvent.click(btn) - screen.getByRole('link', { name: 'Robot settings' }) - }) - - it('renders overflow menu items when the robot is not reachable', () => { - vi.mocked(useCurrentRunId).mockReturnValue(null) - - props = { - robot: mockUnreachableRobot, - } - render(props) - const btn = screen.getByLabelText('RobotOverflowMenu_button') - fireEvent.click(btn) - screen.getByText('Why is this robot unavailable?') - screen.getByText('Forget unavailable robot') - }) - - it('disables the run a protocol menu item if robot software update is available', () => { - vi.mocked(useCurrentRunId).mockReturnValue(null) - vi.mocked(getRobotUpdateDisplayInfo).mockReturnValue({ - autoUpdateAction: 'upgrade', - autoUpdateDisabledReason: null, - updateFromFileDisabledReason: null, - }) - render(props) - const btn = screen.getByLabelText('RobotOverflowMenu_button') - fireEvent.click(btn) - const run = screen.getByText('Run a protocol') - expect(run).toBeDisabled() - }) - - it('disables the run a protocol menu item if robot is busy', () => { - vi.mocked(useCurrentRunId).mockReturnValue(null) - vi.mocked(useIsRobotBusy).mockReturnValue(true) - render(props) - const btn = screen.getByLabelText('RobotOverflowMenu_button') - fireEvent.click(btn) - const run = screen.getByText('Run a protocol') - expect(run).toBeDisabled() - }) -}) diff --git a/app/src/organisms/Devices/__tests__/utils.test.tsx b/app/src/organisms/Devices/__tests__/utils.test.tsx deleted file mode 100644 index d9a776deab1..00000000000 --- a/app/src/organisms/Devices/__tests__/utils.test.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { describe, it, expect } from 'vitest' -import '@testing-library/jest-dom/vitest' -import { - formatTimestamp, - getIs96ChannelPipetteAttached, - getOffsetCalibrationForMount, -} from '../utils' -import { - mockPipetteOffsetCalibration1, - mockPipetteOffsetCalibration2, - mockPipetteOffsetCalibration3, -} from '../../../redux/calibration/pipette-offset/__fixtures__' -import type { - FetchPipettesResponseBody, - FetchPipettesResponsePipette, -} from '../../../redux/pipettes/types' - -describe('formatTimestamp', () => { - it('should format an ISO 8601 date string', () => { - const date = '2021-03-07T18:44:49.366581+00:00' - - expect(formatTimestamp(date)).toMatch( - /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2}):(\d{2})$/ - ) - }) - - it('should pass through a non-ISO 8601 date string', () => { - const date = '2/22/2022 1:00' - - expect(formatTimestamp(date)).toEqual(date) - }) - - it('should pass through a non-date string', () => { - const noDate = 'A Protocol For Otie' - - expect(formatTimestamp(noDate)).toEqual(noDate) - }) -}) - -describe('getIs96ChannelPipetteAttached hook', () => { - it('returns false when there is no pipette attached on the left mount', () => { - const result = getIs96ChannelPipetteAttached(null) - expect(result).toEqual(false) - }) - - it('returns true when there is a 96 channel pipette attached on the left mount', () => { - const mockLeftMountAttachedPipette = { - name: 'p1000_96', - } as FetchPipettesResponsePipette - - const result = getIs96ChannelPipetteAttached(mockLeftMountAttachedPipette) - expect(result).toEqual(true) - }) - - it('returns false when there is no 96 channel pipette attached on the left mount', () => { - const mockLeftMountAttachedPipette = { - name: 'p10_single_v1', - } as FetchPipettesResponsePipette - - const result = getIs96ChannelPipetteAttached(mockLeftMountAttachedPipette) - expect(result).toEqual(false) - }) -}) - -describe('getOffsetCalibrationForMount', () => { - const mockLeftMountAttachedPipette = { - name: 'mock left pipette', - } as FetchPipettesResponsePipette - const mockRightMountAttachedPipette = { - name: 'mock right pipette', - } as FetchPipettesResponsePipette - it('returns null when not given calibrations', () => { - const result = getOffsetCalibrationForMount( - null, - { - left: mockLeftMountAttachedPipette, - right: mockRightMountAttachedPipette, - }, - 'right' - ) - expect(result).toEqual(null) - }) - - it("returns null when asked for calibrations that don't exist for a mount", () => { - const calibrations = [mockPipetteOffsetCalibration1] - const result = getOffsetCalibrationForMount( - calibrations, - { - left: mockLeftMountAttachedPipette, - right: mockRightMountAttachedPipette, - }, - 'right' - ) - expect(result).toEqual(null) - }) - - it('returns the correct calibrations for a mount', () => { - const { pipette } = mockPipetteOffsetCalibration2 - const mockAttachedPipettes: FetchPipettesResponseBody = { - left: mockLeftMountAttachedPipette, // this one doesn't matter too much since we're looking for the right mount cal - right: { - id: pipette, - name: `test-${pipette}`, - model: 'p10_single_v1', - tip_length: 0, - mount_axis: 'z', - plunger_axis: 'a', - }, - } - const calibrations = [ - mockPipetteOffsetCalibration1, - mockPipetteOffsetCalibration2, - mockPipetteOffsetCalibration3, - ] - const result = getOffsetCalibrationForMount( - calibrations, - mockAttachedPipettes, - 'right' - ) - expect(result).toEqual(mockPipetteOffsetCalibration2) - }) -}) diff --git a/app/src/organisms/Devices/constants.ts b/app/src/organisms/Devices/constants.ts deleted file mode 100644 index f6c8eece866..00000000000 --- a/app/src/organisms/Devices/constants.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { getPipetteNameSpecs } from '@opentrons/shared-data' -import type { LabwareDefinition2, PipetteName } from '@opentrons/shared-data' -import { getLatestLabwareDef } from '../../assets/labware/getLabware' - -export const RUN_LOG_WINDOW_SIZE = 60 // number of command items rendered at a time -export const EMPTY_TIMESTAMP = '--:--:--' - -// NOTE: this map is a duplicate of the TIP_RACK_LOOKUP_BY_MAX_VOL -// found at robot_server/robot/calibration/constants.py -const TIP_RACK_LOOKUP_BY_MAX_VOL: { - [maxVol: string]: LabwareDefinition2['parameters']['loadName'] -} = { - 10: 'opentrons_96_tiprack_10ul', - 20: 'opentrons_96_tiprack_20ul', - 50: 'opentrons_96_tiprack_300ul', - 300: 'opentrons_96_tiprack_300ul', - 1000: 'opentrons_96_tiprack_1000ul', -} - -export function getDefaultTiprackDefForPipetteName( - pipetteName: PipetteName -): LabwareDefinition2 | null { - const pipetteNameSpecs = getPipetteNameSpecs(pipetteName) - if (pipetteNameSpecs != null) { - return getLatestLabwareDef( - TIP_RACK_LOOKUP_BY_MAX_VOL[pipetteNameSpecs.maxVolume] - ) - } - return null -} diff --git a/app/src/organisms/Devices/hooks/index.ts b/app/src/organisms/Devices/hooks/index.ts deleted file mode 100644 index f8cdeaa68e8..00000000000 --- a/app/src/organisms/Devices/hooks/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -export * from './useAttachedModules' -export * from './useAttachedPipetteCalibrations' -export * from './useAttachedPipettes' -export * from './useDeckCalibrationData' -export * from './useDeckCalibrationStatus' -export * from './useDownloadRunLog' -export * from './useCalibrationTaskList' -export * from './useIsFlex' -export * from './useIsRobotBusy' -export * from './useIsRobotViewable' -export * from './useLights' -export * from './useLEDLights' -export * from './useLPCDisabledReason' -export * from './useLPCSuccessToast' -export * from './useModuleRenderInfoForProtocolById' -export * from './useModuleCalibrationStatus' -export * from './usePipetteOffsetCalibrations' -export * from './usePipetteOffsetCalibration' -export * from './useProtocolDetailsForRun' -export * from './useRobot' -export * from './useRobotType' -export * from './useRunCalibrationStatus' -export * from './useRunCreatedAtTimestamp' -export * from './useRunHasStarted' -export * from './useRunPipetteInfoByMount' -export * from './useStoredProtocolAnalysis' -export * from './useTipLengthCalibrations' -export * from './useUnmatchedModulesForProtocol' -export * from './useProtocolAnalysisErrors' -export * from './useProtocolMetadata' -export * from './useRunStartedOrLegacySessionInProgress' -export * from './useProtocolRunAnalyticsData' -export * from './useRobotAnalyticsData' -export * from './useTrackCreateProtocolRunEvent' -export * from './useTrackProtocolRunEvent' -export * from './useRunStatuses' -export * from './useSyncRobotClock' -export * from './useIsLegacySessionInProgress' -export * from './useAttachedPipettesFromInstrumentsQuery' diff --git a/app/src/organisms/Devices/hooks/useDeckCalibrationStatus.ts b/app/src/organisms/Devices/hooks/useDeckCalibrationStatus.ts deleted file mode 100644 index 226a5843a74..00000000000 --- a/app/src/organisms/Devices/hooks/useDeckCalibrationStatus.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useCalibrationStatusQuery } from '@opentrons/react-api-client' -import { useRobot } from './useRobot' -import type { DeckCalibrationStatus } from '../../../redux/calibration/types' - -export function useDeckCalibrationStatus( - robotName: string | null = null -): DeckCalibrationStatus | null { - const robot = useRobot(robotName) - return ( - useCalibrationStatusQuery( - {}, - robot?.ip != null ? { hostname: robot.ip } : null - )?.data?.deckCalibration?.status ?? null - ) -} diff --git a/app/src/organisms/Devices/hooks/useIsFlex.ts b/app/src/organisms/Devices/hooks/useIsFlex.ts deleted file mode 100644 index bb78712578a..00000000000 --- a/app/src/organisms/Devices/hooks/useIsFlex.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useSelector } from 'react-redux' - -import { - getRobotModelByName, - RE_ROBOT_MODEL_OT3, -} from '../../../redux/discovery' - -import type { State } from '../../../redux/types' - -export function useIsFlex(robotName: string): boolean { - const robotModel = useSelector((state: State) => - getRobotModelByName(state, robotName) - ) - - return RE_ROBOT_MODEL_OT3.test(robotModel ?? '') -} diff --git a/app/src/organisms/Devices/hooks/useIsRobotViewable.ts b/app/src/organisms/Devices/hooks/useIsRobotViewable.ts deleted file mode 100644 index c0565f75af2..00000000000 --- a/app/src/organisms/Devices/hooks/useIsRobotViewable.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { useRobot } from './useRobot' - -import { CONNECTABLE } from '../../../redux/discovery' - -export function useIsRobotViewable(robotName: string): boolean { - const robot = useRobot(robotName) - - return robot?.status === CONNECTABLE -} diff --git a/app/src/organisms/Devices/hooks/useLEDLights.ts b/app/src/organisms/Devices/hooks/useLEDLights.ts deleted file mode 100644 index 0c27e27aeb2..00000000000 --- a/app/src/organisms/Devices/hooks/useLEDLights.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { - fetchSettings, - getRobotSettings, - updateSetting, -} from '../../../redux/robot-settings' -import type { RobotSettings } from '../../../redux/robot-settings/types' -import type { Dispatch, State } from '../../../redux/types' - -// not releveant to the OT-2, this controls the front LED lights on the Flex -export function useLEDLights( - robotName: string -): { - lightsEnabled: boolean - toggleLights: () => void -} { - const [lightsEnabledCache, setLightsEnabledCache] = React.useState( - true - ) - - const dispatch = useDispatch() - - const isStatusBarEnabled = - useSelector((state: State) => - getRobotSettings(state, robotName) - ).find(setting => setting.id === 'disableStatusBar')?.value !== true - - React.useEffect(() => { - setLightsEnabledCache(isStatusBarEnabled) - }, [isStatusBarEnabled]) - - React.useEffect(() => { - dispatch(fetchSettings(robotName)) - }, [dispatch, robotName]) - - const toggleLights = (): void => { - dispatch(updateSetting(robotName, 'disableStatusBar', lightsEnabledCache)) - setLightsEnabledCache(!lightsEnabledCache) - } - - return { lightsEnabled: lightsEnabledCache, toggleLights } -} diff --git a/app/src/organisms/Devices/hooks/usePipetteOffsetCalibration.ts b/app/src/organisms/Devices/hooks/usePipetteOffsetCalibration.ts deleted file mode 100644 index a5fbd74b9e6..00000000000 --- a/app/src/organisms/Devices/hooks/usePipetteOffsetCalibration.ts +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react' -import { useSelector } from 'react-redux' - -import { - getCalibrationForPipette, - fetchPipetteOffsetCalibrations, -} from '../../../redux/calibration' -import { useDispatchApiRequest } from '../../../redux/robot-api' -import { useRobot } from '.' - -import type { PipetteOffsetCalibration } from '../../../redux/calibration/types' -import type { State } from '../../../redux/types' -import type { AttachedPipette, Mount } from '../../../redux/pipettes/types' - -export function usePipetteOffsetCalibration( - robotName: string | null = null, - pipetteId: AttachedPipette['id'] | null = null, - mount: Mount -): PipetteOffsetCalibration | null { - const [dispatchRequest] = useDispatchApiRequest() - const robot = useRobot(robotName) - - const pipetteOffsetCalibration = useSelector((state: State) => - getCalibrationForPipette( - state, - robotName == null ? '' : robotName, - pipetteId == null ? '' : pipetteId, - mount - ) - ) - - React.useEffect(() => { - if (robotName != null) { - dispatchRequest(fetchPipetteOffsetCalibrations(robotName)) - } - }, [dispatchRequest, robotName, robot?.status]) - - return pipetteOffsetCalibration -} diff --git a/app/src/organisms/Devices/hooks/useProtocolRunAnalyticsData.ts b/app/src/organisms/Devices/hooks/useProtocolRunAnalyticsData.ts deleted file mode 100644 index 47407f6c5e0..00000000000 --- a/app/src/organisms/Devices/hooks/useProtocolRunAnalyticsData.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { useSelector } from 'react-redux' - -import { hash } from '../../../redux/analytics/hash' -import { getStoredProtocol } from '../../../redux/protocol-storage' -import { getRobotSerialNumber } from '../../../redux/discovery' -import { useStoredProtocolAnalysis, useProtocolDetailsForRun } from './' -import { useProtocolMetadata } from './useProtocolMetadata' -import { useRunTimestamps } from '../../RunTimeControl/hooks' -import { formatInterval } from '../../RunTimeControl/utils' -import { EMPTY_TIMESTAMP } from '../constants' - -import type { ProtocolAnalysisOutput } from '@opentrons/shared-data' -import type { ProtocolAnalyticsData } from '../../../redux/analytics/types' -import type { StoredProtocolData } from '../../../redux/protocol-storage/types' -import type { State } from '../../../redux/types' -import type { DiscoveredRobot } from '../../../redux/discovery/types' - -export const parseProtocolRunAnalyticsData = ( - protocolAnalysis: ProtocolAnalysisOutput | null, - storedProtocol: StoredProtocolData | null, - startedAt: string | null, - robot: DiscoveredRobot | null -) => () => { - const hashTasks = [ - hash(protocolAnalysis?.metadata?.author as string) ?? '', - hash(storedProtocol?.srcFiles?.toString() ?? '') ?? '', - ] - - const serialNumber = - robot?.status != null ? getRobotSerialNumber(robot) : null - - return Promise.all(hashTasks).then(([protocolAuthor, protocolText]) => ({ - protocolRunAnalyticsData: { - protocolType: protocolAnalysis?.config?.protocolType ?? '', - protocolAppName: - protocolAnalysis?.config?.protocolType === 'json' - ? 'Protocol Designer' - : 'Python API', - protocolAppVersion: - protocolAnalysis?.config?.protocolType === 'json' - ? protocolAnalysis?.config?.schemaVersion.toFixed(1) - : protocolAnalysis?.metadata?.apiLevel, - protocolApiVersion: protocolAnalysis?.metadata?.apiLevel ?? '', - protocolSource: protocolAnalysis?.metadata?.source ?? '', - protocolName: protocolAnalysis?.metadata?.protocolName ?? '', - pipettes: Object.values(protocolAnalysis?.pipettes ?? {}) - .map(pipette => pipette.pipetteName) - .join(','), - modules: Object.values(protocolAnalysis?.modules ?? {}) - .map(module => module.model) - .join(','), - protocolAuthor: protocolAuthor !== '' ? protocolAuthor : '', - protocolText: protocolText !== '' ? protocolText : '', - protocolHasRunTimeParameters: - protocolAnalysis?.runTimeParameters != null - ? protocolAnalysis?.runTimeParameters?.length > 0 - : false, - protocolHasRunTimeParameterCustomValues: - protocolAnalysis?.runTimeParameters?.some(param => - param.type === 'csv_file' ? true : param.value !== param.default - ) ?? false, - robotType: - protocolAnalysis?.robotType != null - ? protocolAnalysis?.robotType - : storedProtocol?.mostRecentAnalysis?.robotType, - robotSerialNumber: serialNumber ?? '', - }, - runTime: - startedAt != null ? formatInterval(startedAt, Date()) : EMPTY_TIMESTAMP, - })) -} - -type GetProtocolRunAnalyticsData = () => Promise<{ - protocolRunAnalyticsData: ProtocolAnalyticsData - runTime: string -}> - -/** - * - * @param {string | null} runId - * @returns {{ getProtocolRunAnalyticsData: GetProtocolRunAnalyticsData }} - * Function returned returns a promise that resolves to protocol analytics - * data properties for use in event trackEvent - */ -export function useProtocolRunAnalyticsData( - runId: string | null, - robot: DiscoveredRobot | null -): { - getProtocolRunAnalyticsData: GetProtocolRunAnalyticsData -} { - const robotProtocolMetadata = useProtocolMetadata() - const { protocolData: robotProtocolAnalysis } = useProtocolDetailsForRun( - runId - ) - const storedProtocolAnalysis = useStoredProtocolAnalysis(runId) - const storedProtocol = useSelector((state: State) => - getStoredProtocol( - state, - storedProtocolAnalysis?.metadata?.protocolKey as string | undefined - ) - ) - const protocolAnalysis = - robotProtocolAnalysis != null && robotProtocolMetadata != null - ? { - ...robotProtocolAnalysis, - metadata: robotProtocolMetadata, - config: storedProtocolAnalysis?.config, - createdAt: storedProtocolAnalysis?.createdAt ?? '', - errors: storedProtocolAnalysis?.errors, - files: storedProtocolAnalysis?.files ?? [], - } - : storedProtocolAnalysis - const { startedAt } = useRunTimestamps(runId) - - const getProtocolRunAnalyticsData = parseProtocolRunAnalyticsData( - protocolAnalysis as ProtocolAnalysisOutput | null, - storedProtocol, - startedAt, - robot - ) - - return { getProtocolRunAnalyticsData } -} diff --git a/app/src/organisms/Devices/hooks/useRobot.ts b/app/src/organisms/Devices/hooks/useRobot.ts deleted file mode 100644 index 785e63f04c1..00000000000 --- a/app/src/organisms/Devices/hooks/useRobot.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useSelector } from 'react-redux' - -import { getDiscoverableRobotByName } from '../../../redux/discovery' - -import type { DiscoveredRobot } from '../../../redux/discovery/types' -import type { State } from '../../../redux/types' - -export function useRobot(robotName: string | null): DiscoveredRobot | null { - const robot = useSelector((state: State) => - getDiscoverableRobotByName(state, robotName) - ) - - return robot -} diff --git a/app/src/organisms/Devices/hooks/useRunCreatedAtTimestamp.ts b/app/src/organisms/Devices/hooks/useRunCreatedAtTimestamp.ts deleted file mode 100644 index 72936c75514..00000000000 --- a/app/src/organisms/Devices/hooks/useRunCreatedAtTimestamp.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { formatTimestamp } from '../utils' -import { EMPTY_TIMESTAMP } from '../constants' -import { useNotifyRunQuery } from '../../../resources/runs' - -export function useRunCreatedAtTimestamp(runId: string | null): string { - const runRecord = useNotifyRunQuery(runId) - - const createdAtTimestamp = - runRecord?.data?.data.createdAt != null - ? formatTimestamp(runRecord?.data?.data.createdAt) - : EMPTY_TIMESTAMP - - return createdAtTimestamp -} diff --git a/app/src/organisms/Devices/hooks/useStoredProtocolAnalysis.ts b/app/src/organisms/Devices/hooks/useStoredProtocolAnalysis.ts deleted file mode 100644 index a2fea785a3e..00000000000 --- a/app/src/organisms/Devices/hooks/useStoredProtocolAnalysis.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { useSelector } from 'react-redux' - -import { useProtocolQuery } from '@opentrons/react-api-client' -import { - parseRequiredModulesEntity, - parseInitialLoadedLabwareEntity, - parsePipetteEntity, -} from '@opentrons/shared-data' - -import { getStoredProtocol } from '../../../redux/protocol-storage' -import { useNotifyRunQuery } from '../../../resources/runs' - -import type { ProtocolAnalysisOutput } from '@opentrons/shared-data' -import type { State } from '../../../redux/types' - -export const parseProtocolAnalysisOutput = ( - storedProtocolAnalysis: ProtocolAnalysisOutput | null -): ProtocolAnalysisOutput | null => { - const pipetteEntity = parsePipetteEntity( - storedProtocolAnalysis?.commands ?? [] - ) - const moduleEntity = parseRequiredModulesEntity( - storedProtocolAnalysis?.commands ?? [] - ) - const labwareEntity = parseInitialLoadedLabwareEntity( - storedProtocolAnalysis?.commands ?? [] - ) - return storedProtocolAnalysis != null - ? { - ...storedProtocolAnalysis, - pipettes: storedProtocolAnalysis.pipettes ?? pipetteEntity, - labware: storedProtocolAnalysis.labware ?? labwareEntity, - modules: storedProtocolAnalysis.modules ?? moduleEntity, - } - : null -} - -export function useStoredProtocolAnalysis( - runId: string | null -): ProtocolAnalysisOutput | null { - const { data: runRecord } = useNotifyRunQuery(runId, { staleTime: Infinity }) - const protocolId = runRecord?.data?.protocolId ?? null - - const { data: protocolRecord } = useProtocolQuery(protocolId, { - staleTime: Infinity, - }) - - const protocolKey = protocolRecord?.data?.key - - const storedProtocolAnalysis = - useSelector((state: State) => getStoredProtocol(state, protocolKey)) - ?.mostRecentAnalysis ?? null - - return storedProtocolAnalysis != null - ? parseProtocolAnalysisOutput(storedProtocolAnalysis) - : null -} diff --git a/app/src/organisms/Devices/hooks/useSyncRobotClock.ts b/app/src/organisms/Devices/hooks/useSyncRobotClock.ts deleted file mode 100644 index 8a3fcc2e59e..00000000000 --- a/app/src/organisms/Devices/hooks/useSyncRobotClock.ts +++ /dev/null @@ -1,20 +0,0 @@ -import * as React from 'react' -import { useDispatch } from 'react-redux' -import { syncSystemTime } from '../../../redux/robot-admin' - -import type { Dispatch } from '../../../redux/types' - -/** - * syncs robot system time once on mount - * @param {string} robotName name of robot to sync system time - * @returns {void} - */ -export function useSyncRobotClock(robotName: string | null): void { - const dispatch = useDispatch() - - React.useEffect(() => { - if (robotName != null) { - dispatch(syncSystemTime(robotName)) - } - }, [robotName, dispatch]) -} diff --git a/app/src/organisms/Devices/utils.ts b/app/src/organisms/Devices/utils.ts deleted file mode 100644 index 718bf976c63..00000000000 --- a/app/src/organisms/Devices/utils.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { format, parseISO } from 'date-fns' -import { INCONSISTENT_PIPETTE_OFFSET } from '@opentrons/api-client' -import type { - FetchPipettesResponseBody, - FetchPipettesResponsePipette, - Mount, -} from '../../redux/pipettes/types' -import type { - Instruments, - PipetteData, - PipetteOffsetCalibration, - RunTimeParameterFilesCreateData, - RunTimeParameterValuesCreateData, -} from '@opentrons/api-client' -import type { RunTimeParameter } from '@opentrons/shared-data' - -/** - * formats a string if it is in ISO 8601 date format - * @param {string} timestamp ISO date string - * @returns {string} formatted date string - */ -export function formatTimestamp(timestamp: string): string { - // eslint-disable-next-line eqeqeq - return (parseISO(timestamp) as Date | string) != 'Invalid Date' - ? format(parseISO(timestamp), 'MM/dd/yyyy HH:mm:ss') - : timestamp -} - -export function onDeviceDisplayFormatTimestamp(timestamp: string): string { - // eslint-disable-next-line eqeqeq - return (parseISO(timestamp) as Date | string) != 'Invalid Date' - ? format(parseISO(timestamp), 'HH:mm:ss') - : timestamp -} - -export function downloadFile(data: object | string, fileName: string): void { - // Create a blob with the data we want to download as a file - const blobContent = typeof data === 'string' ? data : JSON.stringify(data) - const blob = new Blob([blobContent], { type: 'text/json' }) - // Create an anchor element and dispatch a click event on it - // to trigger a download - const a = document.createElement('a') - a.download = fileName - a.href = window.URL.createObjectURL(blob) - const clickEvt = new MouseEvent('click', { - view: window, - bubbles: true, - cancelable: true, - }) - a.dispatchEvent(clickEvt) - a.remove() -} - -export function getIs96ChannelPipetteAttached( - leftMountAttachedPipette: FetchPipettesResponsePipette | null -): boolean { - const pipetteName = leftMountAttachedPipette?.name - - return pipetteName === 'p1000_96' -} - -export function getOffsetCalibrationForMount( - pipetteOffsetCalibrations: PipetteOffsetCalibration[] | null, - attachedPipettes: - | FetchPipettesResponseBody - | { left: undefined; right: undefined }, - mount: Mount -): PipetteOffsetCalibration | null { - if (pipetteOffsetCalibrations == null) { - return null - } else { - return ( - pipetteOffsetCalibrations.find( - cal => - cal.mount === mount && cal.pipette === attachedPipettes[mount]?.id - ) || null - ) - } -} - -export function getShowPipetteCalibrationWarning( - attachedInstruments?: Instruments -): boolean { - return ( - attachedInstruments?.data.some((i): i is PipetteData => { - const failuresList = - i.ok && i.data.calibratedOffset?.reasonability_check_failures != null - ? i.data.calibratedOffset?.reasonability_check_failures - : [] - if (failuresList.length > 0) { - return failuresList[0]?.kind === INCONSISTENT_PIPETTE_OFFSET - } else return false - }) ?? false - ) -} - -/** - * prepares object to send to endpoints requiring RunTimeParameterValuesCreateData - * @param {RunTimeParameter[]} runTimeParameters array of updated RunTimeParameter overrides - * @returns {RunTimeParameterValuesCreateData} object mapping variable name to value - */ -export function getRunTimeParameterValuesForRun( - runTimeParameters: RunTimeParameter[] -): RunTimeParameterValuesCreateData { - return runTimeParameters.reduce((acc, param) => { - const { variableName } = param - if (param.type !== 'csv_file' && param.value !== param.default) { - return { ...acc, [variableName]: param.value } - } - return acc - }, {}) -} - -/** - * prepares object to send to endpoints requiring RunTimeParameterFilesCreateData - * @param {RunTimeParameter[]} runTimeParameters array of updated RunTimeParameter overrides - * @param {Record} [fileIdMap] mapping of variable name to file ID created and returned by robot server - * @returns {RunTimeParameterFilesCreateData} object mapping variable name to file ID - */ -export function getRunTimeParameterFilesForRun( - runTimeParameters: RunTimeParameter[], - fileIdMap?: Record -): RunTimeParameterFilesCreateData { - return runTimeParameters.reduce((acc, param) => { - const { variableName } = param - if (param.type === 'csv_file' && param.file?.id != null) { - return { ...acc, [variableName]: param.file.id } - } else if ( - param.type === 'csv_file' && - fileIdMap != null && - variableName in fileIdMap - ) { - return { ...acc, [variableName]: fileIdMap[variableName] } - } - return acc - }, {}) -} diff --git a/app/src/organisms/DropTipWizardFlows/BeforeBeginning.tsx b/app/src/organisms/DropTipWizardFlows/BeforeBeginning.tsx deleted file mode 100644 index d516c5d7067..00000000000 --- a/app/src/organisms/DropTipWizardFlows/BeforeBeginning.tsx +++ /dev/null @@ -1,279 +0,0 @@ -import * as React from 'react' -import styled, { css } from 'styled-components' -import { useTranslation } from 'react-i18next' - -import { - ALIGN_CENTER, - BORDERS, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - DISPLAY_INLINE_BLOCK, - Flex, - JUSTIFY_CENTER, - JUSTIFY_FLEX_END, - JUSTIFY_FLEX_START, - JUSTIFY_SPACE_AROUND, - JUSTIFY_SPACE_BETWEEN, - PrimaryButton, - RESPONSIVENESS, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' - -import { SmallButton, MediumButton, TextOnlyButton } from '../../atoms/buttons' -import { DT_ROUTES } from './constants' - -import blowoutVideo from '../../assets/videos/droptip-wizard/Blowout-Liquid.webm' -import droptipVideo from '../../assets/videos/droptip-wizard/Drop-tip.webm' - -import type { DropTipWizardContainerProps } from './types' - -export const BeforeBeginning = ({ - proceedToRoute, - isOnDevice, - issuedCommandsType, - fixitCommandTypeUtils, -}: DropTipWizardContainerProps): JSX.Element | null => { - const { i18n, t } = useTranslation(['drop_tip_wizard', 'shared']) - const [flowType, setFlowType] = React.useState< - 'blowout' | 'drop_tips' | null - >(null) - - const handleProceed = (): void => { - if (flowType === 'blowout') { - void proceedToRoute(DT_ROUTES.BLOWOUT) - } else if (flowType === 'drop_tips') { - void proceedToRoute(DT_ROUTES.DROP_TIP) - } - } - - const buildTopText = (): string => { - if (issuedCommandsType === 'fixit') { - return fixitCommandTypeUtils?.copyOverrides - .beforeBeginningTopText as string - } else { - return t('before_you_begin_do_you_want_to_blowout') - } - } - - if (isOnDevice) { - return ( - - {buildTopText()} - - { - setFlowType('blowout') - }} - buttonText={i18n.format(t('yes_blow_out_liquid'), 'capitalize')} - justifyContent={JUSTIFY_FLEX_START} - paddingLeft={SPACING.spacing24} - height="5.25rem" - /> - - - { - setFlowType('drop_tips') - }} - buttonText={i18n.format(t('no_proceed_to_drop_tip'), 'capitalize')} - justifyContent={JUSTIFY_FLEX_START} - paddingLeft={SPACING.spacing24} - height="5.25rem" - /> - - - {fixitCommandTypeUtils != null ? ( - - ) : null} - - - - ) - } else { - return ( - - {buildTopText()} - - { - setFlowType('blowout') - }} - css={ - flowType === 'blowout' - ? SELECTED_OPTIONS_STYLE - : UNSELECTED_OPTIONS_STYLE - } - > - - - {t('yes_blow_out_liquid')} - - - { - setFlowType('drop_tips') - }} - css={ - flowType === 'drop_tips' - ? SELECTED_OPTIONS_STYLE - : UNSELECTED_OPTIONS_STYLE - } - > - - - {t('no_proceed_to_drop_tip')} - - - - - {/* */} - {fixitCommandTypeUtils != null ? ( - - ) : null} - - {i18n.format(t('shared:continue'), 'capitalize')} - - - - ) - } -} - -const UNSELECTED_OPTIONS_STYLE = css` - background-color: ${COLORS.white}; - border: 1px solid ${COLORS.grey30}; - border-radius: ${BORDERS.borderRadius8}; - height: 12.5625rem; - width: 14.5625rem; - cursor: pointer; - flex-direction: ${DIRECTION_COLUMN}; - justify-content: ${JUSTIFY_CENTER}; - align-items: ${ALIGN_CENTER}; - grid-gap: ${SPACING.spacing8}; - - &:hover { - border: 1px solid ${COLORS.grey35}; - } - - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - flex-direction: ${DIRECTION_ROW}; - justify-content: ${JUSTIFY_FLEX_START}; - background-color: ${COLORS.blue35}; - border-width: 0; - border-radius: ${BORDERS.borderRadius16}; - padding: ${SPACING.spacing24}; - height: 5.25rem; - width: 57.8125rem; - - &:hover { - border-width: 0px; - } - } -` -const SELECTED_OPTIONS_STYLE = css` - ${UNSELECTED_OPTIONS_STYLE} - border: 1px solid ${COLORS.blue50}; - background-color: ${COLORS.blue30}; - - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - border-width: 0px; - background-color: ${COLORS.blue50}; - color: ${COLORS.white}; - - &:hover { - border-width: 0px; - background-color: ${COLORS.blue50}; - } - } -` - -const Title = styled.h1` - ${TYPOGRAPHY.h1Default}; - margin-bottom: ${SPACING.spacing8}; - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - ${TYPOGRAPHY.level4HeaderSemiBold}; - margin-bottom: 0; - height: ${SPACING.spacing40}; - display: ${DISPLAY_INLINE_BLOCK}; - } -` - -const ODD_TITLE_STYLE = css` - ${TYPOGRAPHY.level4HeaderSemiBold} - margin-bottom: ${SPACING.spacing16}; -` - -const TILE_CONTAINER_STYLE = css` - flex-direction: ${DIRECTION_COLUMN}; - justify-content: ${JUSTIFY_SPACE_BETWEEN}; - padding: ${SPACING.spacing32}; - height: 100%; - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - height: 29.5rem; - } -` diff --git a/app/src/organisms/DropTipWizardFlows/ChooseLocation.tsx b/app/src/organisms/DropTipWizardFlows/ChooseLocation.tsx deleted file mode 100644 index 18766553999..00000000000 --- a/app/src/organisms/DropTipWizardFlows/ChooseLocation.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import * as React from 'react' -import styled, { css } from 'styled-components' -import { useTranslation } from 'react-i18next' - -import { - ALIGN_CENTER, - ALIGN_FLEX_END, - Btn, - DIRECTION_COLUMN, - Flex, - JUSTIFY_SPACE_BETWEEN, - JUSTIFY_FLEX_START, - PrimaryButton, - RESPONSIVENESS, - SPACING, - LegacyStyledText, - TYPOGRAPHY, - DISPLAY_INLINE_BLOCK, -} from '@opentrons/components' -import { getDeckDefFromRobotType } from '@opentrons/shared-data' - -import { SmallButton, TextOnlyButton } from '../../atoms/buttons' -import { TwoColumn, DeckMapContent } from '../../molecules/InterventionModal' - -import type { - AddressableAreaName, - ModuleLocation, -} from '@opentrons/shared-data' -import type { DropTipWizardContainerProps } from './types' - -// TODO: get help link article URL - -type ChooseLocationProps = DropTipWizardContainerProps & { - handleProceed: () => void - handleGoBack: () => void - title: string - body: string | JSX.Element - moveToAddressableArea: (addressableArea: AddressableAreaName) => Promise -} -const Title = styled.h1` - ${TYPOGRAPHY.h1Default}; - margin-bottom: ${SPACING.spacing8}; - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - ${TYPOGRAPHY.level4HeaderSemiBold}; - margin-bottom: 0; - height: ${SPACING.spacing40}; - display: ${DISPLAY_INLINE_BLOCK}; - } -` - -export const ChooseLocation = ( - props: ChooseLocationProps -): JSX.Element | null => { - const { - handleProceed, - handleGoBack, - title, - body, - robotType, - moveToAddressableArea, - issuedCommandsType, - } = props - const { i18n, t } = useTranslation(['drop_tip_wizard', 'shared']) - const [ - selectedLocation, - setSelectedLocation, - ] = React.useState() - const deckDef = getDeckDefFromRobotType(robotType) - - const handleConfirmPosition = (): void => { - const deckSlot = deckDef.locations.addressableAreas.find( - l => l.id === selectedLocation?.slotName - )?.id - - if (deckSlot != null) { - void moveToAddressableArea(deckSlot).then(() => { - handleProceed() - }) - } - } - return ( - - - - {title} - {body} - - - - - { - handleGoBack() - }} - > - - - - {i18n.format(t('move_to_slot'), 'capitalize')} - - - - - ) -} - -const ALIGN_BUTTONS = css` - align-items: ${ALIGN_FLEX_END}; - - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - align-items: ${ALIGN_CENTER}; - } -` - -const CONTAINER_STYLE = css` - flex-direction: ${DIRECTION_COLUMN}; - justify-content: ${JUSTIFY_SPACE_BETWEEN}; - padding: ${SPACING.spacing32}; - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - justify-content: ${JUSTIFY_FLEX_START}; - gap: ${SPACING.spacing32}; - padding: none; - height: 29.5rem; - } -` diff --git a/app/src/organisms/DropTipWizardFlows/DropTipWizard.tsx b/app/src/organisms/DropTipWizardFlows/DropTipWizard.tsx index aaf2acf7706..228d4f3384c 100644 --- a/app/src/organisms/DropTipWizardFlows/DropTipWizard.tsx +++ b/app/src/organisms/DropTipWizardFlows/DropTipWizard.tsx @@ -1,6 +1,6 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' -import { Trans, useTranslation } from 'react-i18next' +import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' import { css } from 'styled-components' @@ -10,45 +10,58 @@ import { DIRECTION_COLUMN, RESPONSIVENESS, Flex, - JUSTIFY_FLEX_END, JUSTIFY_SPACE_BETWEEN, POSITION_ABSOLUTE, SPACING, - LegacyStyledText, - ModalShell, useConditionalConfirm, + ModalShell, + DISPLAY_FLEX, + OVERFLOW_HIDDEN, + OVERFLOW_AUTO, } from '@opentrons/components' -import { getTopPortalEl } from '../../App/portal' -import { SimpleWizardBody } from '../../molecules/SimpleWizardBody' -import { getIsOnDevice } from '../../redux/config' +import { getTopPortalEl } from '/app/App/portal' +import { getIsOnDevice } from '/app/redux/config' import { ExitConfirmation } from './ExitConfirmation' import { BEFORE_BEGINNING, BLOWOUT_SUCCESS, CHOOSE_BLOWOUT_LOCATION, CHOOSE_DROP_TIP_LOCATION, + CHOOSE_LOCATION_OPTION, + CONFIRM_POSITION, DROP_TIP_SUCCESS, DT_ROUTES, POSITION_AND_BLOWOUT, POSITION_AND_DROP_TIP, } from './constants' -import { BeforeBeginning } from './BeforeBeginning' -import { ChooseLocation } from './ChooseLocation' -import { JogToPosition } from './JogToPosition' -import { Success } from './Success' -import { InProgressModal } from '../../molecules/InProgressModal/InProgressModal' +import { + BeforeBeginning, + ChooseLocation, + ChooseDeckLocation, + JogToPosition, + Success, + ConfirmPosition, + useConfirmPosition, +} from './steps' +import { InProgressModal } from '/app/molecules/InProgressModal' import { useDropTipErrorComponents } from './hooks' import { DropTipWizardHeader } from './DropTipWizardHeader' +import { ErrorInfo } from './ErrorInfo' import type { DropTipWizardFlowsProps } from '.' import type { DropTipWizardContainerProps, IssuedCommandsType } from './types' -import type { UseDropTipRoutingResult, UseDropTipWithTypeResult } from './hooks' +import type { + UseDropTipRoutingResult, + UseDropTipWithTypeResult, + DropTipBlowoutLocationDetails, +} from './hooks' export type DropTipWizardProps = DropTipWizardFlowsProps & UseDropTipWithTypeResult & UseDropTipRoutingResult & { issuedCommandsType: IssuedCommandsType + dropTipCommandLocations: DropTipBlowoutLocationDetails[] } export function DropTipWizard(props: DropTipWizardProps): JSX.Element { @@ -111,8 +124,6 @@ export function DropTipWizard(props: DropTipWizardProps): JSX.Element { ) } -// TODO(jh, 06-07-24): All content views could use refactoring and DQA. Create shared components from designs. -// Convince design not to use SimpleWizardBody. EXEC-520. export function DropTipWizardContainer( props: DropTipWizardContainerProps ): JSX.Element { @@ -132,58 +143,32 @@ export function DropTipWizardContainer( export function DropTipWizardFixitType( props: DropTipWizardContainerProps ): JSX.Element { - return + return ( + + + + ) } export function DropTipWizardSetupType( props: DropTipWizardContainerProps ): JSX.Element { - const { - activeMaintenanceRunId, - isCommandInProgress, - isExiting, - showConfirmExit, - errorDetails, - } = props - - // TODO(jh: 06-10-24): This is not ideal. See EXEC-520. - const inMotion = - isCommandInProgress || isExiting || activeMaintenanceRunId == null - const simpleWizardPaddingOverrides = - inMotion || showConfirmExit || errorDetails - return createPortal( props.isOnDevice ? ( - + - + ) : ( } - overflow="hidden" > - + + + ), getTopPortalEl() @@ -194,69 +179,45 @@ export const DropTipWizardContent = ( props: DropTipWizardContainerProps ): JSX.Element => { const { - isOnDevice, activeMaintenanceRunId, currentStep, + currentRoute, errorDetails, isCommandInProgress, - fixitCommandTypeUtils, issuedCommandsType, isExiting, - proceed, - proceedToRoute, showConfirmExit, - dropTipCommands, - proceedWithConditionalClose, - goBackRunValid, - confirmExit, - cancelExit, - toggleExitInitiated, - errorComponents, } = props - const { t, i18n } = useTranslation('drop_tip_wizard') + const { t } = useTranslation('drop_tip_wizard') + const confirmPositionUtils = useConfirmPosition(currentStep) function buildGettingReady(): JSX.Element { return } function buildRobotInMotion(): JSX.Element { - return ( - <> - {issuedCommandsType === 'fixit' ? : null} - - - ) + return } - function buildShowExitConfirmation(): JSX.Element { + function buildRobotPipetteMoving(): JSX.Element { return ( - { - toggleExitInitiated() - confirmExit() - }} + ) } - function buildErrorScreen(): JSX.Element { - const { button, subHeader } = errorComponents + function buildShowExitConfirmation(): JSX.Element { + return + } - return ( - - {button} - - ) + function buildErrorScreen(): JSX.Element { + return } function buildBeforeBeginning(): JSX.Element { @@ -264,129 +225,62 @@ export const DropTipWizardContent = ( } function buildChooseLocation(): JSX.Element { - const { moveToAddressableArea } = dropTipCommands - - let bodyTextKey: string - if (currentStep === CHOOSE_BLOWOUT_LOCATION) { - bodyTextKey = isOnDevice - ? 'select_blowout_slot_odd' - : 'select_blowout_slot' - } else { - bodyTextKey = isOnDevice - ? 'select_drop_tip_slot_odd' - : 'select_drop_tip_slot' - } + return + } - return ( - }} - /> - } - moveToAddressableArea={moveToAddressableArea} - /> - ) + function buildChooseDeckLocation(): JSX.Element { + return } function buildJogToPosition(): JSX.Element { - const { handleJog, blowoutOrDropTip } = dropTipCommands + return + } - return ( - blowoutOrDropTip(currentStep, proceed)} - handleGoBack={goBackRunValid} - body={ - currentStep === POSITION_AND_BLOWOUT - ? t('position_and_blowout') - : t('position_and_drop_tip') - } - /> - ) + function buildConfirmPosition(): JSX.Element { + return } function buildSuccess(): JSX.Element { - const { tipDropComplete } = fixitCommandTypeUtils?.buttonOverrides ?? {} - - // Route to the drop tip route if user is at the blowout success screen, otherwise proceed conditionally. - const handleProceed = (): void => { - if (currentStep === BLOWOUT_SUCCESS) { - void proceedToRoute(DT_ROUTES.DROP_TIP) - } else { - // Clear the error recovery submap upon completion of drop tip wizard. - fixitCommandTypeUtils?.reportMap(null) - - if (tipDropComplete != null) { - tipDropComplete() - } else { - proceedWithConditionalClose() - } - } - } - - const buildProceedText = (): string => { - if (fixitCommandTypeUtils != null && currentStep === DROP_TIP_SUCCESS) { - return fixitCommandTypeUtils.copyOverrides.tipDropCompleteBtnCopy - } else { - return currentStep === BLOWOUT_SUCCESS - ? i18n.format(t('shared:continue'), 'capitalize') - : i18n.format(t('shared:exit'), 'capitalize') - } - } - - return ( - - ) + return } function buildModalContent(): JSX.Element { // Don't render the spinner screen for 1 render cycle on fixit commands. - if (currentStep === BEFORE_BEGINNING && issuedCommandsType === 'fixit') { + + if (errorDetails != null) { + return buildErrorScreen() + } else if ( + currentStep === BEFORE_BEGINNING && + issuedCommandsType === 'fixit' + ) { return buildBeforeBeginning() } else if ( activeMaintenanceRunId == null && issuedCommandsType === 'setup' ) { return buildGettingReady() + } else if (confirmPositionUtils.isRobotPipetteMoving) { + return buildRobotPipetteMoving() } else if (isCommandInProgress || isExiting) { return buildRobotInMotion() } else if (showConfirmExit) { return buildShowExitConfirmation() - } else if (errorDetails != null) { - return buildErrorScreen() } else if (currentStep === BEFORE_BEGINNING) { return buildBeforeBeginning() + } else if (currentStep === CHOOSE_LOCATION_OPTION) { + return buildChooseLocation() } else if ( currentStep === CHOOSE_BLOWOUT_LOCATION || currentStep === CHOOSE_DROP_TIP_LOCATION ) { - return buildChooseLocation() + return buildChooseDeckLocation() } else if ( currentStep === POSITION_AND_BLOWOUT || currentStep === POSITION_AND_DROP_TIP ) { return buildJogToPosition() + } else if (currentStep === CONFIRM_POSITION) { + return buildConfirmPosition() } else if ( currentStep === BLOWOUT_SUCCESS || currentStep === DROP_TIP_SUCCESS @@ -406,7 +300,7 @@ function useInitiateExit(): { isExitInitiated: boolean toggleExitInitiated: () => void } { - const [isExitInitiated, setIsExitInitiated] = React.useState(false) + const [isExitInitiated, setIsExitInitiated] = useState(false) const toggleExitInitiated = (): void => { setIsExitInitiated(true) @@ -415,8 +309,59 @@ function useInitiateExit(): { return { isExitInitiated, toggleExitInitiated } } -const ERROR_MODAL_FIXIT_STYLE = css` +const SHARED_STYLE = ` + display: ${DISPLAY_FLEX}; + flex-direction: ${DIRECTION_COLUMN}; + overflow-y: ${OVERFLOW_AUTO}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + overflow: ${OVERFLOW_HIDDEN}; + } +` + +const INTERVENTION_CONTAINER_STYLE = css` + ${SHARED_STYLE} + padding: ${SPACING.spacing32}; + grid-gap: ${SPACING.spacing24}; + height: 100%; + width: 100%; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + grid-gap: ${SPACING.spacing32}; + } +` + +const SIMPLE_CONTAINER_STYLE = css` + ${SHARED_STYLE} + width: 47rem; + min-height: 26.75rem; + + // TODO(jh 09-17-24): This is effectively making a ModalShell analogue on the ODD, since one does not exist. + // Consider making one. + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + position: ${POSITION_ABSOLUTE}; + width: 62rem; + height: 35.5rem; + left: 16px; + top: 16px; + border: ${BORDERS.lineBorder}; + box-shadow: ${BORDERS.shadowSmall}; + border-radius: ${BORDERS.borderRadius16}; + background-color: ${COLORS.white}; + } +` + +const SIMPLE_CONTENT_CONTAINER_STYLE = css` + display: ${DISPLAY_FLEX}; + flex-direction: ${DIRECTION_COLUMN}; + justify-content: ${JUSTIFY_SPACE_BETWEEN}; + width: 100%; + height: 100%; + padding: ${SPACING.spacing32}; + flex: 1; + grid-gap: ${SPACING.spacing24}; + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - margin-top: -${SPACING.spacing68}; // See EXEC-520. This clearly isn't ideal. + grid-gap: ${SPACING.spacing32}; } ` diff --git a/app/src/organisms/DropTipWizardFlows/DropTipWizardFlows.tsx b/app/src/organisms/DropTipWizardFlows/DropTipWizardFlows.tsx new file mode 100644 index 00000000000..921e0fc04c3 --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/DropTipWizardFlows.tsx @@ -0,0 +1,86 @@ +import { useEffect, useState } from 'react' + +import { + useDropTipLocations, + useDropTipRouting, + useDropTipWithType, +} from './hooks' +import { DropTipWizard } from './DropTipWizard' + +import type { PipetteModelSpecs, RobotType } from '@opentrons/shared-data' +import type { PipetteData } from '@opentrons/api-client' +import type { + DropTipModalStyle, + FixitCommandTypeUtils, + IssuedCommandsType, +} from './types' + +/** Provides the user toggle for rendering Drop Tip Wizard Flows. + * + * NOTE: Rendering these flows is independent of whether tips are actually attached. First use useTipAttachmentStatus + * to get tip attachment status. + */ +export function useDropTipWizardFlows(): { + showDTWiz: boolean + enableDTWiz: () => void + disableDTWiz: () => void +} { + const [showDTWiz, setShowDTWiz] = useState(false) + + return { + showDTWiz, + enableDTWiz: () => { + setShowDTWiz(true) + }, + disableDTWiz: () => { + setShowDTWiz(false) + }, + } +} + +export interface DropTipWizardFlowsProps { + robotType: RobotType + mount: PipetteData['mount'] + instrumentModelSpecs: PipetteModelSpecs + /* isTakeover allows for optionally specifying a different callback if a different client cancels the "setup" type flow. */ + closeFlow: (isTakeover?: boolean) => void + modalStyle: DropTipModalStyle + /* Optional. If provided, DT will issue "fixit" commands. */ + fixitCommandTypeUtils?: FixitCommandTypeUtils +} + +export function DropTipWizardFlows( + props: DropTipWizardFlowsProps +): JSX.Element { + const { fixitCommandTypeUtils } = props + + const issuedCommandsType: IssuedCommandsType = + fixitCommandTypeUtils != null ? 'fixit' : 'setup' + + const dropTipWithTypeUtils = useDropTipWithType({ + ...props, + issuedCommandsType, + }) + const dropTipRoutingUtils = useDropTipRouting(fixitCommandTypeUtils) + const dropTipCommandLocations = useDropTipLocations(props.robotType) // Prefetch to reduce client latency + + // If the flow unrenders for any reason (ex, the pipette card managing the flow unrenders), don't re-render the flow + // after it closes. + useEffect(() => { + return () => { + if (issuedCommandsType === 'setup') { + void dropTipWithTypeUtils.dropTipCommands.handleCleanUpAndClose() + } + } + }, [issuedCommandsType]) + + return ( + + ) +} diff --git a/app/src/organisms/DropTipWizardFlows/DropTipWizardHeader.tsx b/app/src/organisms/DropTipWizardFlows/DropTipWizardHeader.tsx index 7816c2fee4f..55363426104 100644 --- a/app/src/organisms/DropTipWizardFlows/DropTipWizardHeader.tsx +++ b/app/src/organisms/DropTipWizardFlows/DropTipWizardHeader.tsx @@ -1,11 +1,9 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' -import { BEFORE_BEGINNING, BLOWOUT_SUCCESS, DT_ROUTES } from './constants' -import { WizardHeader } from '../../molecules/WizardHeader' +import { WizardHeader } from '/app/molecules/WizardHeader' import type { DropTipWizardProps } from './DropTipWizard' -import type { DropTipFlowsRoute, DropTipFlowsStep, ErrorDetails } from './types' +import type { ErrorDetails } from './types' type DropTipWizardHeaderProps = DropTipWizardProps & { isExitInitiated: boolean @@ -16,9 +14,6 @@ type DropTipWizardHeaderProps = DropTipWizardProps & { export function DropTipWizardHeader({ confirmExit, - currentStep, - currentRoute, - currentStepIdx, isExitInitiated, isFinalWizardStep, errorDetails, @@ -36,75 +31,14 @@ export function DropTipWizardHeader({ handleCleanUpAndClose, }) - const { totalSteps, currentStepNumber } = useSeenBlowoutSuccess({ - currentStep, - currentRoute, - currentStepIdx, - }) - return ( ) } -interface UseSeenBlowoutSuccessProps { - currentStep: DropTipFlowsStep - currentRoute: DropTipFlowsRoute - currentStepIdx: number -} - -interface UseSeenBlowoutSuccessResult { - currentStepNumber: number | null - totalSteps: number | null -} - -// Calculate the props used for determining step count based on the route. Because blowout and drop tip are separate routes, -// there's a need for state to track whether we've seen blowout, so the step counter is accurate when the drop tip route is active. -export function useSeenBlowoutSuccess({ - currentStep, - currentRoute, - currentStepIdx, -}: UseSeenBlowoutSuccessProps): UseSeenBlowoutSuccessResult { - const [hasSeenBlowoutSuccess, setHasSeenBlowoutSuccess] = React.useState( - false - ) - - React.useEffect(() => { - if (currentStep === BLOWOUT_SUCCESS) { - setHasSeenBlowoutSuccess(true) - } else if (currentStep === BEFORE_BEGINNING) { - setHasSeenBlowoutSuccess(false) - } - }, [currentStep]) - - const shouldRenderStepCounter = currentRoute !== DT_ROUTES.BEFORE_BEGINNING - - let totalSteps: null | number - if (!shouldRenderStepCounter) { - totalSteps = null - } else if (currentRoute === DT_ROUTES.BLOWOUT || hasSeenBlowoutSuccess) { - totalSteps = DT_ROUTES.BLOWOUT.length + DT_ROUTES.DROP_TIP.length - } else { - totalSteps = currentRoute.length - } - - let currentStepNumber: null | number - if (!shouldRenderStepCounter) { - currentStepNumber = null - } else if (hasSeenBlowoutSuccess && currentRoute === DT_ROUTES.DROP_TIP) { - currentStepNumber = DT_ROUTES.BLOWOUT.length + currentStepIdx + 1 - } else { - currentStepNumber = currentStepIdx + 1 - } - - return { currentStepNumber, totalSteps } -} - export interface UseWizardExitHeaderProps { isFinalStep: boolean hasInitiatedExit: boolean diff --git a/app/src/organisms/DropTipWizardFlows/ErrorInfo.tsx b/app/src/organisms/DropTipWizardFlows/ErrorInfo.tsx new file mode 100644 index 00000000000..400a34fce72 --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/ErrorInfo.tsx @@ -0,0 +1,71 @@ +import { useTranslation } from 'react-i18next' +import { css } from 'styled-components' + +import { + DISPLAY_FLEX, + DIRECTION_COLUMN, + SPACING, + ALIGN_CENTER, + COLORS, + Icon, + Flex, + StyledText, + JUSTIFY_CENTER, + JUSTIFY_FLEX_END, + RESPONSIVENESS, + TEXT_ALIGN_CENTER, +} from '@opentrons/components' + +import type { DropTipWizardContainerProps } from './types' + +export function ErrorInfo({ + errorComponents, + errorDetails, +}: DropTipWizardContainerProps): JSX.Element { + const { button, subHeader } = errorComponents + const { t } = useTranslation('drop_tip_wizard') + + return ( + <> + + + + {errorDetails?.header ?? t('error_dropping_tips')} + + + {subHeader} + + + {button} + + ) +} + +const CONTAINER_STYLE = css` + display: ${DISPLAY_FLEX}; + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing16}; + padding: ${SPACING.spacing40} ${SPACING.spacing16}; + align-items: ${ALIGN_CENTER}; + justify-content: ${JUSTIFY_CENTER}; + text-align: ${TEXT_ALIGN_CENTER}; + margin-top: ${SPACING.spacing16}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + grid-gap: ${SPACING.spacing24}; + padding: ${SPACING.spacing40}; + } +` + +const ICON_STYLE = css` + width: 40px; + height: 40px; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + width: 60px; + height: 60px; + } +` diff --git a/app/src/organisms/DropTipWizardFlows/ExitConfirmation.tsx b/app/src/organisms/DropTipWizardFlows/ExitConfirmation.tsx index 453bf527307..9c5486624b7 100644 --- a/app/src/organisms/DropTipWizardFlows/ExitConfirmation.tsx +++ b/app/src/organisms/DropTipWizardFlows/ExitConfirmation.tsx @@ -1,40 +1,42 @@ -import * as React from 'react' -import { useSelector } from 'react-redux' import { Trans, useTranslation } from 'react-i18next' +import { css } from 'styled-components' import { - Flex, COLORS, - SPACING, - AlertPrimaryButton, - JUSTIFY_FLEX_END, StyledText, - PrimaryButton, + Icon, + Flex, + RESPONSIVENESS, + DISPLAY_FLEX, + SPACING, + DIRECTION_COLUMN, + ALIGN_CENTER, + JUSTIFY_CENTER, + TEXT_ALIGN_CENTER, } from '@opentrons/components' -import { getIsOnDevice } from '../../redux/config' -import { SimpleWizardBody } from '../../molecules/SimpleWizardBody' -import { SmallButton } from '../../atoms/buttons' +import { DropTipFooterButtons } from './shared' import type { DropTipWizardContainerProps } from './types' -type ExitConfirmationProps = DropTipWizardContainerProps & { - handleExit: () => void - handleGoBack: () => void -} - -export function ExitConfirmation(props: ExitConfirmationProps): JSX.Element { - const { handleGoBack, handleExit, mount } = props - const { t } = useTranslation(['drop_tip_wizard', 'shared']) +export function ExitConfirmation( + props: DropTipWizardContainerProps +): JSX.Element { + const { mount, cancelExit, toggleExitInitiated, confirmExit } = props + const { t } = useTranslation('drop_tip_wizard') - const isOnDevice = useSelector(getIsOnDevice) + const handleExit = (): void => { + toggleExitInitiated() + confirmExit() + } return ( - + + + + {t('remove_any_attached_tips')} + - } - marginTop={isOnDevice ? '-2rem' : undefined} - > - {isOnDevice ? ( - - - - - ) : ( - - - {t('shared:go_back')} - - - {t('exit_and_home_pipette')} - - - )} - + + + ) } + +const CONTAINER_STYLE = css` + display: ${DISPLAY_FLEX}; + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing16}; + padding: ${SPACING.spacing40} ${SPACING.spacing16}; + align-items: ${ALIGN_CENTER}; + justify-content: ${JUSTIFY_CENTER}; + text-align: ${TEXT_ALIGN_CENTER}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + grid-gap: ${SPACING.spacing24}; + padding: ${SPACING.spacing40}; + } +` + +const ICON_STYLE = css` + width: 40px; + height: 40px; + color: ${COLORS.red50}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + width: 60px; + height: 60px; + } +` diff --git a/app/src/organisms/DropTipWizardFlows/JogToPosition.tsx b/app/src/organisms/DropTipWizardFlows/JogToPosition.tsx deleted file mode 100644 index 7ecc9c4a1e1..00000000000 --- a/app/src/organisms/DropTipWizardFlows/JogToPosition.tsx +++ /dev/null @@ -1,312 +0,0 @@ -import * as React from 'react' -import styled from 'styled-components' -import { useTranslation } from 'react-i18next' -import { POSITION_AND_BLOWOUT } from './constants' -import { - ALIGN_CENTER, - ALIGN_FLEX_START, - COLORS, - DIRECTION_COLUMN, - Flex, - Icon, - JUSTIFY_CENTER, - JUSTIFY_END, - JUSTIFY_FLEX_END, - JUSTIFY_SPACE_BETWEEN, - PrimaryButton, - RESPONSIVENESS, - SecondaryButton, - SPACING, - LegacyStyledText, - TEXT_ALIGN_CENTER, - TYPOGRAPHY, -} from '@opentrons/components' -// import { NeedHelpLink } from '../CalibrationPanels' -import { JogControls } from '../../molecules/JogControls' -import { SmallButton, TextOnlyButton } from '../../atoms/buttons' -import { InProgressModal } from '../../molecules/InProgressModal/InProgressModal' -import { SimpleWizardBody } from '../../molecules/SimpleWizardBody' - -import type { Jog } from '../../molecules/JogControls' -import type { DropTipWizardContainerProps } from './types' - -// TODO: get help link article URL -// const NEED_HELP_URL = '' - -const Header = styled.h1` - ${TYPOGRAPHY.h1Default} - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - ${TYPOGRAPHY.level4HeaderSemiBold} - } -` - -type ConfirmPositionProps = DropTipWizardContainerProps & { - handlePipetteAction: () => void - handleGoBack: () => void -} - -const ConfirmPosition = (props: ConfirmPositionProps): JSX.Element | null => { - const { - handlePipetteAction, - handleGoBack, - isOnDevice, - currentStep, - issuedCommandsType, - } = props - const { i18n, t } = useTranslation(['drop_tip_wizard', 'shared']) - const flowTitle = t('drop_tips') - - if (isOnDevice) { - return ( - - - - - - - {currentStep === POSITION_AND_BLOWOUT - ? t('confirm_blowout_location', { flow: flowTitle }) - : t('confirm_drop_tip_location', { flow: flowTitle })} - - - - - - - - - - - - ) - } else { - return ( - - - {/* */} - - {issuedCommandsType === 'setup' ? ( - - {t('shared:go_back')} - - ) : ( - - )} - - {currentStep === POSITION_AND_BLOWOUT - ? i18n.format(t('blowout_liquid'), 'capitalize') - : i18n.format(t('drop_tips'), 'capitalize')} - - - - - ) - } -} - -type JogToPositionProps = DropTipWizardContainerProps & { - handleGoBack: () => void - handleJog: Jog - handleProceed: () => void - body: string -} - -export const JogToPosition = ( - props: JogToPositionProps -): JSX.Element | null => { - const { - handleGoBack, - handleJog, - handleProceed, - body, - currentStep, - isOnDevice, - issuedCommandsType, - } = props - const { i18n, t } = useTranslation(['drop_tip_wizard', 'shared']) - const [ - showPositionConfirmation, - setShowPositionConfirmation, - ] = React.useState(false) - // Includes special case homing only present in this step. - const [isRobotInMotion, setIsRobotInMotion] = React.useState(false) - - const onGoBack = (): void => { - setIsRobotInMotion(true) - handleGoBack() - } - - if (showPositionConfirmation) { - return isRobotInMotion ? ( - - ) : ( - { - setIsRobotInMotion(true) - handleProceed() - }} - handleGoBack={() => { - setShowPositionConfirmation(false) - }} - /> - ) - } - - if (isOnDevice) { - return ( - - - - - - - - { - setShowPositionConfirmation(true) - }} - /> - - - - ) - } else { - return ( - - - -
    - {i18n.format(t('position_the_pipette'), 'capitalize')} -
    - {body} -
    - {/* no animations */} - {issuedCommandsType === 'setup' ? ( - - ) : null} - - - - {/* */} - {issuedCommandsType === 'setup' ? ( - - {t('shared:go_back')} - - ) : ( - - )} - { - setShowPositionConfirmation(true) - }} - > - {t('shared:confirm_position')} - - -
    - ) - } -} diff --git a/app/src/organisms/DropTipWizardFlows/Success.tsx b/app/src/organisms/DropTipWizardFlows/Success.tsx deleted file mode 100644 index a071a4ea4fc..00000000000 --- a/app/src/organisms/DropTipWizardFlows/Success.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import * as React from 'react' - -import { - StyledText, - PrimaryButton, - TEXT_TRANSFORM_CAPITALIZE, - JUSTIFY_FLEX_END, - ALIGN_CENTER, - Flex, - SPACING, - DIRECTION_COLUMN, - RESPONSIVENESS, - JUSTIFY_CENTER, -} from '@opentrons/components' - -import { SmallButton } from '../../atoms/buttons' -import SuccessIcon from '../../assets/images/icon_success.png' - -import type { DropTipWizardContainerProps } from './types' -import { css } from 'styled-components' - -type SuccessProps = DropTipWizardContainerProps & { - message: string - proceedText: string - handleProceed: () => void -} -export const Success = (props: SuccessProps): JSX.Element => { - const { - message, - proceedText, - handleProceed, - isOnDevice, - issuedCommandsType, - } = props - - return ( - - - Success Icon - - {message} - - - - {isOnDevice ? ( - - ) : ( - {proceedText} - )} - - - ) -} - -const WIZARD_CONTAINER_STYLE = css` - min-height: 394px; - flex-direction: ${DIRECTION_COLUMN}; - justify-content: ${JUSTIFY_CENTER}; - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - height: 472px; - } -` diff --git a/app/src/organisms/DropTipWizardFlows/TipsAttachedModal.tsx b/app/src/organisms/DropTipWizardFlows/TipsAttachedModal.tsx index 7c9c5a11823..86778afe97b 100644 --- a/app/src/organisms/DropTipWizardFlows/TipsAttachedModal.tsx +++ b/app/src/organisms/DropTipWizardFlows/TipsAttachedModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import NiceModal, { useModal } from '@ebay/nice-modal-react' import { Trans, useTranslation } from 'react-i18next' @@ -12,24 +11,21 @@ import { import { ApiHostProvider } from '@opentrons/react-api-client' import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' -import { SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' +import { SmallButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' import { DropTipWizardFlows, useDropTipWizardFlows } from '.' -import { useHomePipettes } from './hooks' +import { useHomePipettes } from '/app/local-resources/instruments' import type { HostConfig } from '@opentrons/api-client' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' -import type { PipetteWithTip } from '.' -import type { UseHomePipettesProps } from './hooks' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' +import type { UseHomePipettesProps } from '/app/local-resources/instruments' +import type { PipetteDetails } from '/app/resources/maintenance_runs' +import type { PipetteWithTip } from '/app/resources/instruments' -type TipsAttachedModalProps = Pick< - UseHomePipettesProps, - 'robotType' | 'instrumentModelSpecs' | 'mount' | 'isRunCurrent' -> & { +type TipsAttachedModalProps = Pick & { aPipetteWithTip: PipetteWithTip host: HostConfig | null setTipStatusResolved: (onEmpty?: () => void) => Promise - onSkipAndHome: () => void } export const handleTipsAttachedModal = ( @@ -52,10 +48,11 @@ const TipsAttachedModal = NiceModal.create( const modal = useModal() const { mount, specs } = aPipetteWithTip - const { showDTWiz, toggleDTWiz } = useDropTipWizardFlows() - const { homePipettes, isHomingPipettes } = useHomePipettes({ + const { showDTWiz, disableDTWiz, enableDTWiz } = useDropTipWizardFlows() + const { homePipettes, isHoming } = useHomePipettes({ ...homePipetteProps, - onHome: () => { + pipetteInfo: buildPipetteDetails(aPipetteWithTip), + onSettled: () => { modal.remove() void setTipStatusResolved() }, @@ -72,7 +69,7 @@ const TipsAttachedModal = NiceModal.create( } const cleanUpAndClose = (isTakeover?: boolean): void => { - toggleDTWiz() + disableDTWiz() if (!isTakeover) { modal.remove() @@ -105,13 +102,13 @@ const TipsAttachedModal = NiceModal.create( buttonType="secondary" buttonText={t('skip_and_home_pipette')} onClick={onHomePipettes} - disabled={isHomingPipettes} + disabled={isHoming} />
    @@ -124,9 +121,22 @@ const TipsAttachedModal = NiceModal.create( closeFlow={isTakeover => { cleanUpAndClose(isTakeover) }} + modalStyle="simple" /> ) : null} ) } ) + +// TODO(jh, 09-12-24): Consolidate this with the same utility that exists elsewhere. +function buildPipetteDetails( + aPipetteWithTip: PipetteWithTip | null +): PipetteDetails | null { + return aPipetteWithTip != null + ? { + pipetteId: aPipetteWithTip.specs.name, + mount: aPipetteWithTip.mount, + } + : null +} diff --git a/app/src/organisms/DropTipWizardFlows/__fixtures__/index.ts b/app/src/organisms/DropTipWizardFlows/__fixtures__/index.ts index 77ba7c3dc32..b2d9abfcf45 100644 --- a/app/src/organisms/DropTipWizardFlows/__fixtures__/index.ts +++ b/app/src/organisms/DropTipWizardFlows/__fixtures__/index.ts @@ -1,4 +1,4 @@ -import { mockPipetteInfo } from '../../../redux/pipettes/__fixtures__' +import { mockPipetteInfo } from '/app/redux/pipettes/__fixtures__' import { CHOOSE_DROP_TIP_LOCATION, DT_ROUTES } from '../constants' import type { PipetteModelSpecs } from '@opentrons/shared-data' @@ -24,6 +24,7 @@ export const mockDropTipWizardContainerProps: DropTipWizardContainerProps = { robotType: 'OT-3 Standard', isExiting: false, mount: 'left', + modalStyle: 'simple', isOnDevice: true, fixitCommandTypeUtils: undefined, instrumentModelSpecs: MOCK_ACTUAL_PIPETTE, @@ -38,8 +39,9 @@ export const mockDropTipWizardContainerProps: DropTipWizardContainerProps = { closeFlow: MOCK_FN, confirmExit: MOCK_FN, goBackRunValid: MOCK_FN, - proceedToRoute: MOCK_FN, + proceedToRouteAndStep: MOCK_FN, toggleExitInitiated: MOCK_FN, proceedWithConditionalClose: MOCK_FN, proceed: MOCK_FN, + dropTipCommandLocations: [], } diff --git a/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizard.test.tsx b/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizard.test.tsx index 52d0c1cfe6e..d1108fc3c18 100644 --- a/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizard.test.tsx +++ b/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizard.test.tsx @@ -1,19 +1,24 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, describe, it, expect, beforeEach } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockDropTipWizardContainerProps } from '../__fixtures__' import { DropTipWizardContent, DropTipWizardContainer } from '../DropTipWizard' import { DropTipWizardHeader } from '../DropTipWizardHeader' -import { InProgressModal } from '../../../molecules/InProgressModal/InProgressModal' +import { InProgressModal } from '/app/molecules/InProgressModal' import { ExitConfirmation } from '../ExitConfirmation' -import { SimpleWizardBody } from '../../../molecules/SimpleWizardBody' -import { BeforeBeginning } from '../BeforeBeginning' -import { ChooseLocation } from '../ChooseLocation' -import { JogToPosition } from '../JogToPosition' -import { Success } from '../Success' +import { + BeforeBeginning, + ChooseLocation, + JogToPosition, + Success, + ConfirmPosition, + useConfirmPosition, + ChooseDeckLocation, +} from '../steps' +import { ErrorInfo } from '../ErrorInfo' import { BEFORE_BEGINNING, CHOOSE_BLOWOUT_LOCATION, @@ -22,15 +27,14 @@ import { POSITION_AND_DROP_TIP, BLOWOUT_SUCCESS, DROP_TIP_SUCCESS, + CHOOSE_LOCATION_OPTION, + CONFIRM_POSITION, } from '../constants' -vi.mock('../../../molecules/InProgressModal/InProgressModal') +vi.mock('/app/molecules/InProgressModal') vi.mock('../ExitConfirmation') -vi.mock('../../../molecules/SimpleWizardBody') -vi.mock('../BeforeBeginning') -vi.mock('../ChooseLocation') -vi.mock('../JogToPosition') -vi.mock('../Success') +vi.mock('../steps') +vi.mock('../ErrorInfo') vi.mock('../DropTipWizardHeader') const renderDropTipWizardContainer = ( @@ -50,6 +54,11 @@ describe('DropTipWizardContainer', () => { vi.mocked(DropTipWizardHeader).mockReturnValue(
    MOCK WIZARD HEADER
    ) + + vi.mocked(useConfirmPosition).mockReturnValue({ + toggleIsRobotPipetteMoving: vi.fn(), + isRobotPipetteMoving: false, + }) }) it('renders the special-cased Fixit view if the issuedCommandsType is fixit without a header', () => { @@ -85,11 +94,15 @@ describe('DropTipWizardContent', () => { vi.mocked(ExitConfirmation).mockReturnValue(
    MOCK_EXIT_CONFIRMATION
    ) - vi.mocked(SimpleWizardBody).mockReturnValue(
    MOCK_ERROR_SCREEN
    ) vi.mocked(BeforeBeginning).mockReturnValue(
    MOCK_BEFORE_BEGINNING
    ) vi.mocked(ChooseLocation).mockReturnValue(
    MOCK_CHOOSE_LOCATION
    ) + vi.mocked(ChooseDeckLocation).mockReturnValue( +
    MOCK_CHOOSE_DECK_LOCATION
    + ) + vi.mocked(ConfirmPosition).mockReturnValue(
    MOCK_CONFIRM_POSITION
    ) vi.mocked(JogToPosition).mockReturnValue(
    MOCK_JOG_TO_POSITION
    ) vi.mocked(Success).mockReturnValue(
    MOCK_SUCCESS
    ) + vi.mocked(ErrorInfo).mockReturnValue(
    MOCK_ERROR_INFO
    ) }) it(`renders InProgressModal when activeMaintenanceRunId is null`, () => { @@ -116,13 +129,13 @@ describe('DropTipWizardContent', () => { screen.getByText('MOCK_EXIT_CONFIRMATION') }) - it(`renders SimpleWizardBody when errorDetails is not null`, () => { + it(`renders ErrorInfo when errorDetails is not null`, () => { renderDropTipWizardContent({ ...props, errorDetails: { message: 'MOCK_MESSAGE' }, }) - screen.getByText('MOCK_ERROR_SCREEN') + screen.getByText('MOCK_ERROR_INFO') }) it(`renders BeforeBeginning when currentStep is ${BEFORE_BEGINNING}`, () => { @@ -131,22 +144,40 @@ describe('DropTipWizardContent', () => { screen.getByText('MOCK_BEFORE_BEGINNING') }) - it(`renders ChooseLocation when currentStep is ${CHOOSE_BLOWOUT_LOCATION}`, () => { + it(`renders ChooseLocation when currentStep is ${CHOOSE_LOCATION_OPTION}`, () => { renderDropTipWizardContent({ ...props, - currentStep: CHOOSE_BLOWOUT_LOCATION, + currentStep: CHOOSE_LOCATION_OPTION, }) screen.getByText('MOCK_CHOOSE_LOCATION') }) - it(`renders ChooseLocation when currentStep is ${CHOOSE_DROP_TIP_LOCATION}`, () => { + it(`renders ChooseDeckLocation when currentStep is ${CHOOSE_BLOWOUT_LOCATION}`, () => { + renderDropTipWizardContent({ + ...props, + currentStep: CHOOSE_BLOWOUT_LOCATION, + }) + + screen.getByText('MOCK_CHOOSE_DECK_LOCATION') + }) + + it(`renders ChooseDeckLocation when currentStep is ${CHOOSE_DROP_TIP_LOCATION}`, () => { renderDropTipWizardContent({ ...props, currentStep: CHOOSE_DROP_TIP_LOCATION, }) - screen.getByText('MOCK_CHOOSE_LOCATION') + screen.getByText('MOCK_CHOOSE_DECK_LOCATION') + }) + + it(`renders ConfirmPosition when currentStep is ${CONFIRM_POSITION}`, () => { + renderDropTipWizardContent({ + ...props, + currentStep: CONFIRM_POSITION, + }) + + screen.getByText('MOCK_CONFIRM_POSITION') }) it(`renders JogToPosition when currentStep is ${POSITION_AND_BLOWOUT} `, () => { @@ -172,21 +203,4 @@ describe('DropTipWizardContent', () => { screen.getByText('MOCK_SUCCESS') }) - - it('renders alternative success button copy when the commandType is fixit', () => { - renderDropTipWizardContent({ - ...props, - currentStep: DROP_TIP_SUCCESS, - fixitCommandTypeUtils: { - copyOverrides: { tipDropCompleteBtnCopy: 'proceed_to_tip_selection' }, - } as any, - }) - - expect(vi.mocked(Success)).toHaveBeenCalledWith( - expect.objectContaining({ - proceedText: 'proceed_to_tip_selection', - }), - expect.anything() - ) - }) }) diff --git a/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.ts b/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.ts new file mode 100644 index 00000000000..f401fc73677 --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.ts @@ -0,0 +1,27 @@ +import { describe, it, expect, vi } from 'vitest' +import { renderHook, act } from '@testing-library/react' + +import { useDropTipWizardFlows } from '..' + +vi.mock('../DropTipWizard') +vi.mock('../hooks') + +describe('useDropTipWizardFlows', () => { + it('should toggle showDTWiz state', () => { + const { result } = renderHook(() => useDropTipWizardFlows()) + + expect(result.current.showDTWiz).toBe(false) + + act(() => { + result.current.enableDTWiz() + }) + + expect(result.current.showDTWiz).toBe(true) + + act(() => { + result.current.disableDTWiz() + }) + + expect(result.current.showDTWiz).toBe(false) + }) +}) diff --git a/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.tsx b/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.tsx deleted file mode 100644 index 99d08eaa579..00000000000 --- a/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardFlows.test.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import * as React from 'react' -import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest' -import { screen, renderHook, act } from '@testing-library/react' - -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockPipetteInfo } from '../../../redux/pipettes/__fixtures__' -import { - useTipAttachmentStatus, - useDropTipWizardFlows, - DropTipWizardFlows, -} from '..' -import { getPipettesWithTipAttached } from '../getPipettesWithTipAttached' -import { getPipetteModelSpecs } from '@opentrons/shared-data' -import { DropTipWizard } from '../DropTipWizard' -import { useInstrumentsQuery } from '@opentrons/react-api-client' - -import type { Mock } from 'vitest' -import type { PipetteModelSpecs } from '@opentrons/shared-data' -import type { PipetteWithTip } from '..' - -vi.mock('@opentrons/shared-data', async importOriginal => { - const actual = await importOriginal() - return { - ...actual, - getPipetteModelSpecs: vi.fn(), - } -}) -vi.mock('../DropTipWizard') -vi.mock('../getPipettesWithTipAttached') -vi.mock('../hooks') -vi.mock('@opentrons/react-api-client') - -const MOCK_ACTUAL_PIPETTE = { - ...mockPipetteInfo.pipetteSpecs, - model: 'model', - tipLength: { - value: 20, - }, -} as PipetteModelSpecs - -const mockPipetteWithTip: PipetteWithTip = { - mount: 'left', - specs: MOCK_ACTUAL_PIPETTE, -} - -const mockSecondPipetteWithTip: PipetteWithTip = { - mount: 'right', - specs: MOCK_ACTUAL_PIPETTE, -} - -const mockPipettesWithTip: PipetteWithTip[] = [ - mockPipetteWithTip, - mockSecondPipetteWithTip, -] - -describe('useTipAttachmentStatus', () => { - let mockGetPipettesWithTipAttached: Mock - - beforeEach(() => { - mockGetPipettesWithTipAttached = vi.mocked(getPipettesWithTipAttached) - vi.mocked(getPipetteModelSpecs).mockReturnValue(MOCK_ACTUAL_PIPETTE) - vi.mocked(DropTipWizard).mockReturnValue(
    MOCK DROP TIP WIZ
    ) - mockGetPipettesWithTipAttached.mockResolvedValue(mockPipettesWithTip) - vi.mocked(useInstrumentsQuery).mockReturnValue({ data: {} } as any) - }) - - afterEach(() => { - vi.clearAllMocks() - }) - - it('should return the correct initial state', () => { - const { result } = renderHook(() => useTipAttachmentStatus({} as any)) - - expect(result.current.areTipsAttached).toBe(false) - expect(result.current.aPipetteWithTip).toEqual(null) - }) - - it('should determine tip status and update state accordingly', async () => { - const { result } = renderHook(() => useTipAttachmentStatus({} as any)) - - await act(async () => { - await result.current.determineTipStatus() - }) - - expect(result.current.areTipsAttached).toBe(true) - expect(result.current.aPipetteWithTip).toEqual(mockPipetteWithTip) - }) - - it('should reset tip status', async () => { - const { result } = renderHook(() => useTipAttachmentStatus({} as any)) - - await act(async () => { - await result.current.determineTipStatus() - result.current.resetTipStatus() - }) - - expect(result.current.areTipsAttached).toBe(false) - expect(result.current.aPipetteWithTip).toEqual(null) - }) - - it('should set tip status resolved and update state', async () => { - const { result } = renderHook(() => useTipAttachmentStatus({} as any)) - - await act(async () => { - await result.current.determineTipStatus() - result.current.setTipStatusResolved() - }) - - expect(result.current.aPipetteWithTip).toEqual(mockSecondPipetteWithTip) - }) - - it('should call onEmptyCache callback when cache becomes empty', async () => { - mockGetPipettesWithTipAttached.mockResolvedValueOnce([mockPipetteWithTip]) - - const onEmptyCacheMock = vi.fn() - const { result } = renderHook(() => useTipAttachmentStatus({} as any)) - - await act(async () => { - await result.current.determineTipStatus() - result.current.setTipStatusResolved(onEmptyCacheMock) - }) - - expect(onEmptyCacheMock).toHaveBeenCalled() - }) -}) - -describe('useDropTipWizardFlows', () => { - it('should toggle showDTWiz state', () => { - const { result } = renderHook(() => useDropTipWizardFlows()) - - expect(result.current.showDTWiz).toBe(false) - - act(() => { - result.current.toggleDTWiz() - }) - - expect(result.current.showDTWiz).toBe(true) - }) -}) - -const render = (props: React.ComponentProps) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} - -describe('DropTipWizardFlows', () => { - it('should render DropTipWizard', () => { - render({} as any) - - screen.getByText('MOCK DROP TIP WIZ') - }) -}) diff --git a/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardHeader.test.tsx b/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardHeader.test.tsx index 5dbb85ecca2..c5720adf4ab 100644 --- a/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardHeader.test.tsx +++ b/app/src/organisms/DropTipWizardFlows/__tests__/DropTipWizardHeader.test.tsx @@ -1,16 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderHook, screen } from '@testing-library/react' +import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockDropTipWizardContainerProps } from '../__fixtures__' import { useWizardExitHeader, - useSeenBlowoutSuccess, DropTipWizardHeader, } from '../DropTipWizardHeader' -import { DT_ROUTES } from '../constants' import type { Mock } from 'vitest' import type { UseWizardExitHeaderProps } from '../DropTipWizardHeader' @@ -31,22 +29,6 @@ describe('DropTipWizardHeader', () => { it('renders appropriate copy and onClick behavior', () => { render(props) screen.getByText('Drop tips') - screen.getByText('Step 1 / 3') - }) -}) - -describe('useSeenBlowoutSuccess', () => { - it('should not render step counter when currentRoute is BEFORE_BEGINNING', () => { - const { result } = renderHook(() => - useSeenBlowoutSuccess({ - currentStep: 'SOME_STEP' as any, - currentRoute: DT_ROUTES.BEFORE_BEGINNING, - currentStepIdx: 0, - }) - ) - - expect(result.current.totalSteps).toBe(null) - expect(result.current.currentStepNumber).toBe(null) }) }) diff --git a/app/src/organisms/DropTipWizardFlows/__tests__/TipsAttachedModal.test.tsx b/app/src/organisms/DropTipWizardFlows/__tests__/TipsAttachedModal.test.tsx index 40a2b075e7c..2a71920c4fc 100644 --- a/app/src/organisms/DropTipWizardFlows/__tests__/TipsAttachedModal.test.tsx +++ b/app/src/organisms/DropTipWizardFlows/__tests__/TipsAttachedModal.test.tsx @@ -1,23 +1,22 @@ -import React from 'react' import NiceModal from '@ebay/nice-modal-react' import { describe, it, beforeEach, expect, vi } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { handleTipsAttachedModal } from '../TipsAttachedModal' -import { FLEX_ROBOT_TYPE, LEFT } from '@opentrons/shared-data' -import { mockPipetteInfo } from '../../../redux/pipettes/__fixtures__' -import { useCloseCurrentRun } from '../../ProtocolUpload/hooks' +import { LEFT } from '@opentrons/shared-data' +import { mockPipetteInfo } from '/app/redux/pipettes/__fixtures__' +import { useCloseCurrentRun } from '/app/resources/runs' import { useDropTipWizardFlows } from '..' +import type { Mock } from 'vitest' import type { PipetteModelSpecs } from '@opentrons/shared-data' import type { HostConfig } from '@opentrons/api-client' -import type { Mock } from 'vitest' -import type { PipetteWithTip } from '..' +import type { PipetteWithTip } from '/app/resources/instruments' -vi.mock('../../ProtocolUpload/hooks') +vi.mock('/app/resources/runs/useCloseCurrentRun') vi.mock('..') const MOCK_ACTUAL_PIPETTE = { @@ -52,11 +51,7 @@ const render = (aPipetteWithTip: PipetteWithTip) => { host: MOCK_HOST, aPipetteWithTip, setTipStatusResolved: mockSetTipStatusResolved, - robotType: FLEX_ROBOT_TYPE, - mount: 'left', - instrumentModelSpecs: mockPipetteInfo.pipetteSpecs as any, - onSkipAndHome: vi.fn(), - isRunCurrent: true, + onSettled: vi.fn(), }) } data-testid="testButton" @@ -79,7 +74,8 @@ describe('TipsAttachedModal', () => { } as any) vi.mocked(useDropTipWizardFlows).mockReturnValue({ showDTWiz: false, - toggleDTWiz: mockToggleDTWiz, + enableDTWiz: mockToggleDTWiz, + disableDTWiz: vi.fn(), }) }) diff --git a/app/src/organisms/DropTipWizardFlows/__tests__/getPipettesWithTipAttached.test.ts b/app/src/organisms/DropTipWizardFlows/__tests__/getPipettesWithTipAttached.test.ts deleted file mode 100644 index eb969f46820..00000000000 --- a/app/src/organisms/DropTipWizardFlows/__tests__/getPipettesWithTipAttached.test.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { describe, it, beforeEach, expect, vi } from 'vitest' -import { getCommands } from '@opentrons/api-client' - -import { getPipettesWithTipAttached } from '../getPipettesWithTipAttached' -import { LEFT, RIGHT } from '@opentrons/shared-data' - -import type { GetPipettesWithTipAttached } from '../getPipettesWithTipAttached' - -vi.mock('@opentrons/api-client') - -const HOST_NAME = 'localhost' -const RUN_ID = 'testRunId' -const LEFT_PIPETTE_ID = 'testId1' -const RIGHT_PIPETTE_ID = 'testId2' -const LEFT_PIPETTE_NAME = 'testLeftName' -const RIGHT_PIPETTE_NAME = 'testRightName' -const PICK_UP_TIP = 'pickUpTip' -const DROP_TIP = 'dropTip' -const DROP_TIP_IN_PLACE = 'dropTipInPlace' -const LOAD_PIPETTE = 'loadPipette' -const FIXIT_INTENT = 'fixit' - -const mockAttachedInstruments = { - data: [ - { mount: LEFT, state: { tipDetected: true } }, - { mount: RIGHT, state: { tipDetected: true } }, - ], - meta: { cursor: 0, totalLength: 2 }, -} - -const createMockCommand = ( - type: string, - id: string, - pipetteId: string, - status = 'succeeded' -) => ({ - id, - key: `${id}-key`, - commandType: type, - status, - params: { pipetteId }, -}) - -const mockCommands = { - data: [ - createMockCommand(LOAD_PIPETTE, 'load-left', LEFT_PIPETTE_ID), - createMockCommand(LOAD_PIPETTE, 'load-right', RIGHT_PIPETTE_ID), - createMockCommand(PICK_UP_TIP, 'pickup-left', LEFT_PIPETTE_ID), - createMockCommand(DROP_TIP, 'drop-left', LEFT_PIPETTE_ID, 'succeeded'), - ], - meta: { cursor: 0, totalLength: 4 }, -} - -const mockRunRecord = { - data: { - pipettes: [ - { id: LEFT_PIPETTE_ID, pipetteName: LEFT_PIPETTE_NAME, mount: LEFT }, - { id: RIGHT_PIPETTE_ID, pipetteName: RIGHT_PIPETTE_NAME, mount: RIGHT }, - ], - }, -} - -describe('getPipettesWithTipAttached', () => { - let DEFAULT_PARAMS: GetPipettesWithTipAttached - - beforeEach(() => { - DEFAULT_PARAMS = { - host: { hostname: HOST_NAME }, - runId: RUN_ID, - attachedInstruments: mockAttachedInstruments as any, - runRecord: mockRunRecord as any, - } - - vi.mocked(getCommands).mockResolvedValue({ - data: mockCommands, - } as any) - }) - - it('returns an empty array if attachedInstruments is null', async () => { - const params = { ...DEFAULT_PARAMS, attachedInstruments: null } - const result = await getPipettesWithTipAttached(params) - expect(result).toEqual([]) - }) - - it('returns an empty array if runRecord is null', async () => { - const params = { ...DEFAULT_PARAMS, runRecord: null } - const result = await getPipettesWithTipAttached(params) - expect(result).toEqual([]) - }) - - it('returns an empty array when no tips are attached according to protocol', async () => { - const mockCommandsWithoutAttachedTips = { - ...mockCommands, - data: [ - createMockCommand(LOAD_PIPETTE, 'load-left', LEFT_PIPETTE_ID), - createMockCommand(LOAD_PIPETTE, 'load-right', RIGHT_PIPETTE_ID), - createMockCommand(PICK_UP_TIP, 'pickup-left', LEFT_PIPETTE_ID), - createMockCommand(DROP_TIP, 'drop-left', LEFT_PIPETTE_ID, 'succeeded'), - ], - } - - vi.mocked(getCommands).mockResolvedValue({ - data: mockCommandsWithoutAttachedTips, - } as any) - - const result = await getPipettesWithTipAttached(DEFAULT_PARAMS) - expect(result).toEqual([]) - }) - - it('returns pipettes with protocol detected tip attachment', async () => { - const mockCommandsWithPickUpTip = { - ...mockCommands, - data: [ - ...mockCommands.data, - createMockCommand(PICK_UP_TIP, 'pickup-left-2', LEFT_PIPETTE_ID), - createMockCommand(PICK_UP_TIP, 'pickup-right', RIGHT_PIPETTE_ID), - ], - } - - vi.mocked(getCommands).mockResolvedValue({ - data: mockCommandsWithPickUpTip, - } as any) - - const result = await getPipettesWithTipAttached(DEFAULT_PARAMS) - expect(result).toEqual(mockAttachedInstruments.data) - }) - - it('always returns the left mount before the right mount if both pipettes have tips attached', async () => { - const mockCommandsWithPickUpTip = { - ...mockCommands, - data: [ - ...mockCommands.data, - createMockCommand(PICK_UP_TIP, 'pickup-right', RIGHT_PIPETTE_ID), - createMockCommand(PICK_UP_TIP, 'pickup-left-2', LEFT_PIPETTE_ID), - ], - } - - vi.mocked(getCommands).mockResolvedValue({ - data: mockCommandsWithPickUpTip, - } as any) - - const result = await getPipettesWithTipAttached(DEFAULT_PARAMS) - expect(result.length).toBe(2) - expect(result[0].mount).toEqual(LEFT) - expect(result[1].mount).toEqual(RIGHT) - }) - - it('does not return otherwise legitimate failed tip exchange commands if fixit intent tip commands are present and successful', async () => { - const mockCommandsWithFixit = { - ...mockCommands, - data: [ - ...mockCommands.data, - { - ...createMockCommand( - DROP_TIP_IN_PLACE, - 'fixit-drop', - LEFT_PIPETTE_ID - ), - intent: FIXIT_INTENT, - }, - ], - } - - vi.mocked(getCommands).mockResolvedValue({ - data: mockCommandsWithFixit, - } as any) - - const result = await getPipettesWithTipAttached(DEFAULT_PARAMS) - expect(result).toEqual([]) - }) - - it('considers a tip attached only if the last tip exchange command was pickUpTip', async () => { - const mockCommandsWithPickUpTip = { - ...mockCommands, - data: [ - ...mockCommands.data, - createMockCommand(PICK_UP_TIP, 'pickup-left-2', LEFT_PIPETTE_ID), - ], - } - - vi.mocked(getCommands).mockResolvedValue({ - data: mockCommandsWithPickUpTip, - } as any) - - const result = await getPipettesWithTipAttached(DEFAULT_PARAMS) - expect(result).toEqual([mockAttachedInstruments.data[0]]) - }) -}) diff --git a/app/src/organisms/DropTipWizardFlows/constants.ts b/app/src/organisms/DropTipWizardFlows/constants.ts index 39d75318824..1a28283171b 100644 --- a/app/src/organisms/DropTipWizardFlows/constants.ts +++ b/app/src/organisms/DropTipWizardFlows/constants.ts @@ -3,21 +3,27 @@ export const BEFORE_BEGINNING = 'BEFORE_BEGINNING' export const POSITION_AND_BLOWOUT = 'POSITION_AND_BLOWOUT' as const export const BLOWOUT_SUCCESS = 'BLOWOUT_SUCCESS' as const +export const CHOOSE_LOCATION_OPTION = 'CHOOSE_LOCATION_OPTION' as const export const CHOOSE_BLOWOUT_LOCATION = 'CHOOSE_BLOWOUT_LOCATION' as const export const CHOOSE_DROP_TIP_LOCATION = 'CHOOSE_DROP_TIP_LOCATION' as const export const POSITION_AND_DROP_TIP = 'POSITION_AND_DROP_TIP' as const +export const CONFIRM_POSITION = 'CONFIRM_POSITION' as const export const DROP_TIP_SUCCESS = 'DROP_TIP_SUCCESS' as const export const INVALID = 'INVALID' as const export const BEFORE_BEGINNING_STEPS = [BEFORE_BEGINNING] as const export const BLOWOUT_STEPS = [ + CHOOSE_LOCATION_OPTION, CHOOSE_BLOWOUT_LOCATION, POSITION_AND_BLOWOUT, + CONFIRM_POSITION, BLOWOUT_SUCCESS, ] as const export const DROP_TIP_STEPS = [ + CHOOSE_LOCATION_OPTION, CHOOSE_DROP_TIP_LOCATION, POSITION_AND_DROP_TIP, + CONFIRM_POSITION, DROP_TIP_SUCCESS, ] as const diff --git a/app/src/organisms/DropTipWizardFlows/getPipettesWithTipAttached.ts b/app/src/organisms/DropTipWizardFlows/getPipettesWithTipAttached.ts deleted file mode 100644 index 99bcd949093..00000000000 --- a/app/src/organisms/DropTipWizardFlows/getPipettesWithTipAttached.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { getCommands } from '@opentrons/api-client' -import { LEFT } from '@opentrons/shared-data' - -import type { - HostConfig, - PipetteData, - Run, - CommandsData, - RunCommandSummary, - Instruments, -} from '@opentrons/api-client' -import type { - LoadedPipette, - PipettingRunTimeCommand, -} from '@opentrons/shared-data' - -export interface GetPipettesWithTipAttached { - host: HostConfig | null - runId: string - attachedInstruments: Instruments | null - runRecord: Run | null -} - -export function getPipettesWithTipAttached({ - host, - runId, - attachedInstruments, - runRecord, -}: GetPipettesWithTipAttached): Promise { - if (attachedInstruments == null || runRecord == null) { - return Promise.resolve([]) - } - - return getCommandsExecutedDuringRun( - host as HostConfig, - runId - ).then(executedCmdData => - checkPipettesForAttachedTips( - executedCmdData.data, - runRecord.data.pipettes, - attachedInstruments.data as PipetteData[] - ) - ) -} - -function getCommandsExecutedDuringRun( - host: HostConfig, - runId: string -): Promise { - return getCommands(host, runId, { - cursor: null, - pageLength: 0, - }).then(response => { - const { totalLength } = response.data.meta - return getCommands(host, runId, { - cursor: 0, - pageLength: totalLength, - }).then(response => response.data) - }) -} - -const TIP_EXCHANGE_COMMAND_TYPES = ['dropTip', 'dropTipInPlace', 'pickUpTip'] - -function checkPipettesForAttachedTips( - commands: RunCommandSummary[], - pipettesUsedInRun: LoadedPipette[], - attachedPipettes: PipetteData[] -): PipetteData[] { - let pipettesWithUnknownTipStatus = pipettesUsedInRun - const mountsWithTipAttached: Array = [] - - // Iterate backwards through commands, finding first tip exchange command for each pipette. - // If there's a chance the tip is still attached, flag the pipette. - for (let i = commands.length - 1; i >= 0; i--) { - if (pipettesWithUnknownTipStatus.length === 0) { - break - } - - const commandType = commands[i].commandType - const pipetteUsedInCommand = (commands[i] as PipettingRunTimeCommand).params - .pipetteId - const isTipExchangeCommand = TIP_EXCHANGE_COMMAND_TYPES.includes( - commandType - ) - const pipetteUsedInCommandWithUnknownTipStatus = - pipettesWithUnknownTipStatus.find( - pipette => pipette.id === pipetteUsedInCommand - ) ?? null - - // If the currently iterated command is a fixit command, we can safely assume the user - // had the option to fix pipettes with tips in this command and all commands - // earlier in the run, during Error Recovery flows. - if ( - commands[i].intent === 'fixit' && - isTipExchangeCommand && - commands[i].status === 'succeeded' - ) { - break - } - - if ( - isTipExchangeCommand && - pipetteUsedInCommandWithUnknownTipStatus != null - ) { - const tipPossiblyAttached = - commands[i].status !== 'succeeded' || commandType === 'pickUpTip' - - if (tipPossiblyAttached) { - mountsWithTipAttached.push( - pipetteUsedInCommandWithUnknownTipStatus.mount - ) - } - pipettesWithUnknownTipStatus = pipettesWithUnknownTipStatus.filter( - pipette => pipette.id !== pipetteUsedInCommand - ) - } - } - - // Convert the array of mounts with attached tips to PipetteData with attached tips. - const pipettesWithTipAttached = attachedPipettes.filter(attachedPipette => - mountsWithTipAttached.includes(attachedPipette.mount) - ) - - // Preferentially assign the left mount as the first element. - if ( - pipettesWithTipAttached.length === 2 && - pipettesWithTipAttached[1].mount === LEFT - ) { - ;[pipettesWithTipAttached[0], pipettesWithTipAttached[1]] = [ - pipettesWithTipAttached[1], - pipettesWithTipAttached[0], - ] - } - - return pipettesWithTipAttached -} diff --git a/app/src/organisms/DropTipWizardFlows/hooks/__tests__/errors.test.tsx b/app/src/organisms/DropTipWizardFlows/hooks/__tests__/errors.test.tsx index 08caf96c946..476513706f9 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/__tests__/errors.test.tsx +++ b/app/src/organisms/DropTipWizardFlows/hooks/__tests__/errors.test.tsx @@ -29,9 +29,9 @@ describe('useDropTipCommandErrors', () => { act(() => { result.current({ - runCommandError: { - errorType: DROP_TIP_SPECIAL_ERROR_TYPES.MUST_HOME_ERROR, - } as any, + type: DROP_TIP_SPECIAL_ERROR_TYPES.MUST_HOME_ERROR, + message: 'remove_the_tips_manually', + header: 'cant_safely_drop_tips', }) }) diff --git a/app/src/organisms/DropTipWizardFlows/hooks/__tests__/useDropTipLocations.test.ts b/app/src/organisms/DropTipWizardFlows/hooks/__tests__/useDropTipLocations.test.ts new file mode 100644 index 00000000000..9195ebf8afd --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/hooks/__tests__/useDropTipLocations.test.ts @@ -0,0 +1,105 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { renderHook } from '@testing-library/react' + +import { OT2_ROBOT_TYPE, FLEX_ROBOT_TYPE } from '@opentrons/shared-data' + +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' +import { useDropTipLocations } from '../useDropTipLocations' + +const TRASH_BIN_FIXTURE = 'trashBinAdapter' +const WASTE_CHUTE_FIXTURE_1 = 'wasteChuteRightAdapterNoCover' +const TRASH_BIN_CUTOUT = 'cutoutA2' +const WASTE_CHUTE_CUTOUT = 'cutoutA3' +const SLOT_A2 = 'A2' +const SLOT_A3 = 'A3' +const SLOT_FIXED_TRASH = 'fixedTrash' +const CHOOSE_DECK_LOCATION = 'CHOOSE_DECK_LOCATION' +const TRASH_BIN_LOCATION = 'trash-bin' +const WASTE_CHUTE_LOCATION = 'waste-chute' +const FIXED_TRASH_LOCATION = 'fixed-trash' +const DECK_LOCATION = 'deck' + +vi.mock('/app/resources/deck_configuration') + +describe('useDropTipLocations', () => { + const mockDeckConfigFlex = [ + { cutoutFixtureId: TRASH_BIN_FIXTURE, cutoutId: TRASH_BIN_CUTOUT }, + { cutoutFixtureId: WASTE_CHUTE_FIXTURE_1, cutoutId: WASTE_CHUTE_CUTOUT }, + ] + + beforeEach(() => { + vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ + data: mockDeckConfigFlex, + } as any) + }) + + it('should return correct locations for an OT-2', () => { + vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ + data: [], + } as any) + + const { result } = renderHook(() => useDropTipLocations(OT2_ROBOT_TYPE)) + + expect(result.current).toEqual([ + { location: FIXED_TRASH_LOCATION, slotName: SLOT_FIXED_TRASH }, + { location: DECK_LOCATION, slotName: CHOOSE_DECK_LOCATION }, + ]) + }) + + it('should return correct locations for a Flex', () => { + const { result } = renderHook(() => useDropTipLocations(FLEX_ROBOT_TYPE)) + + expect(result.current).toEqual([ + { location: TRASH_BIN_LOCATION, slotName: SLOT_A2 }, + { location: WASTE_CHUTE_LOCATION, slotName: SLOT_A3 }, + { location: DECK_LOCATION, slotName: CHOOSE_DECK_LOCATION }, + ]) + }) + + it('should handle missing addressable areas for a Flex', () => { + const { result } = renderHook(() => useDropTipLocations(FLEX_ROBOT_TYPE)) + + expect(result.current).toEqual([ + { location: TRASH_BIN_LOCATION, slotName: SLOT_A2 }, + { location: WASTE_CHUTE_LOCATION, slotName: SLOT_A3 }, + { location: DECK_LOCATION, slotName: CHOOSE_DECK_LOCATION }, + ]) + }) + + it('should handle an empty deck configuration for a Flex', () => { + vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ + data: [], + } as any) + + const { result } = renderHook(() => useDropTipLocations(FLEX_ROBOT_TYPE)) + + expect(result.current).toEqual([ + { location: DECK_LOCATION, slotName: CHOOSE_DECK_LOCATION }, + ]) + }) + + it('should handle an undefined deck configuration for an OT-2', () => { + vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ + data: undefined, + } as any) + + const { result } = renderHook(() => useDropTipLocations(OT2_ROBOT_TYPE)) + + expect(result.current).toEqual([ + { location: FIXED_TRASH_LOCATION, slotName: SLOT_FIXED_TRASH }, + { location: DECK_LOCATION, slotName: CHOOSE_DECK_LOCATION }, + ]) + }) + + it('should handle an undefined deck configuration for a Flex', () => { + vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ + data: undefined, + } as any) + + const { result } = renderHook(() => useDropTipLocations(FLEX_ROBOT_TYPE)) + + expect(result.current).toEqual([ + { location: DECK_LOCATION, slotName: CHOOSE_DECK_LOCATION }, + ]) + }) +}) diff --git a/app/src/organisms/DropTipWizardFlows/hooks/__tests__/useDropTipRouting.test.tsx b/app/src/organisms/DropTipWizardFlows/hooks/__tests__/useDropTipRouting.test.tsx index 651b3959b5d..f5622960244 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/__tests__/useDropTipRouting.test.tsx +++ b/app/src/organisms/DropTipWizardFlows/hooks/__tests__/useDropTipRouting.test.tsx @@ -18,7 +18,7 @@ describe('useDropTipRouting', () => { const { result } = renderHook(() => useDropTipRouting()) await act(async () => { - await result.current.proceedToRoute(DT_ROUTES.BLOWOUT) + await result.current.proceedToRouteAndStep(DT_ROUTES.BLOWOUT) }) const initialStep = result.current.currentStep @@ -34,7 +34,7 @@ describe('useDropTipRouting', () => { const { result } = renderHook(() => useDropTipRouting()) await act(async () => { - await result.current.proceedToRoute(DT_ROUTES.BLOWOUT) + await result.current.proceedToRouteAndStep(DT_ROUTES.BLOWOUT) }) await act(async () => { @@ -75,11 +75,11 @@ describe('useDropTipRouting', () => { expect(result.current.currentStepIdx).toBe(0) }) - it('should proceed to the specified route when proceedToRoute is called', async () => { + it('should proceed to the specified route when proceedToRouteAndStep is called', async () => { const { result } = renderHook(() => useDropTipRouting()) await act(async () => { - await result.current.proceedToRoute(DT_ROUTES.BLOWOUT) + await result.current.proceedToRouteAndStep(DT_ROUTES.BLOWOUT) }) expect(result.current.currentRoute).toBe(DT_ROUTES.BLOWOUT) expect(result.current.currentStep).toBe(head(DT_ROUTES.BLOWOUT)) @@ -98,7 +98,7 @@ describe('useReportMap', () => { const { result } = renderHook(() => useDropTipRouting(mockFixitUtils)) await act(async () => { - await result.current.proceedToRoute(DT_ROUTES.BLOWOUT) + await result.current.proceedToRouteAndStep(DT_ROUTES.BLOWOUT) }) expect(mockReportMap).toHaveBeenCalledWith({ @@ -144,4 +144,15 @@ describe('getInitialRouteAndStep', () => { expect(initialRoute).toBe(DT_ROUTES.DROP_TIP) expect(initialStep).toBe(DT_ROUTES.DROP_TIP[2]) }) + + it('should return the overridden route and first step when fixitUtils.routeOverride.route is provided but routeOverride.step is not provided', () => { + const fixitUtils = { + routeOverride: { route: DT_ROUTES.DROP_TIP, step: null }, + } as any + + const [initialRoute, initialStep] = getInitialRouteAndStep(fixitUtils) + + expect(initialRoute).toBe(DT_ROUTES.DROP_TIP) + expect(initialStep).toBe(DT_ROUTES.DROP_TIP[0]) + }) }) diff --git a/app/src/organisms/DropTipWizardFlows/hooks/errors.tsx b/app/src/organisms/DropTipWizardFlows/hooks/errors.tsx index 08a1d4a0f34..1f7d6cf09ff 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/errors.tsx +++ b/app/src/organisms/DropTipWizardFlows/hooks/errors.tsx @@ -1,17 +1,15 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { AlertPrimaryButton, SPACING } from '@opentrons/components' import { DROP_TIP_SPECIAL_ERROR_TYPES } from '../constants' -import { SmallButton } from '../../../atoms/buttons' +import { SmallButton } from '/app/atoms/buttons' import type { RunCommandError } from '@opentrons/shared-data' import type { ErrorDetails } from '../types' export interface SetRobotErrorDetailsParams { - runCommandError?: RunCommandError - message?: string + message: string | null header?: string type?: RunCommandError['errorType'] } @@ -24,16 +22,8 @@ export function useDropTipCommandErrors( ): (cbProps: SetRobotErrorDetailsParams) => void { const { t } = useTranslation('drop_tip_wizard') - return ({ - runCommandError, - message, - header, - type, - }: SetRobotErrorDetailsParams) => { - if ( - runCommandError?.errorType === - DROP_TIP_SPECIAL_ERROR_TYPES.MUST_HOME_ERROR - ) { + return ({ message, header, type }: SetRobotErrorDetailsParams) => { + if (type === DROP_TIP_SPECIAL_ERROR_TYPES.MUST_HOME_ERROR) { const headerText = t('cant_safely_drop_tips') const messageText = t('remove_the_tips_manually') @@ -44,7 +34,11 @@ export function useDropTipCommandErrors( }) } else { const messageText = message ?? '' - setErrorDetails({ header, message: messageText, type }) + setErrorDetails({ + header: header ?? t('cant_safely_drop_tips'), + message: messageText ?? t('remove_the_tips_manually'), + type, + }) } } } diff --git a/app/src/organisms/DropTipWizardFlows/hooks/index.ts b/app/src/organisms/DropTipWizardFlows/hooks/index.ts index fdb5964eace..f3145d7d083 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/index.ts +++ b/app/src/organisms/DropTipWizardFlows/hooks/index.ts @@ -1,6 +1,6 @@ export * from './errors' export * from './useDropTipWithType' -export * from './useHomePipettes' +export * from './useDropTipLocations' export { useDropTipRouting } from './useDropTipRouting' export { useDropTipWithType } from './useDropTipWithType' diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts index 78e905ae5e1..c3162a48f07 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts +++ b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCommands.ts @@ -1,21 +1,28 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { useDeleteMaintenanceRunMutation } from '@opentrons/react-api-client' -import { MANAGED_PIPETTE_ID, POSITION_AND_BLOWOUT } from '../constants' -import { getAddressableAreaFromConfig } from '../getAddressableAreaFromConfig' -import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' +import { + DROP_TIP_SPECIAL_ERROR_TYPES, + DT_ROUTES, + MANAGED_PIPETTE_ID, +} from '../constants' +import { getAddressableAreaFromConfig } from '../utils' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' import type { CreateCommand, AddressableAreaName, PipetteModelSpecs, - DropTipInPlaceCreateCommand, - UnsafeDropTipInPlaceCreateCommand, + RunCommandError, } from '@opentrons/shared-data' import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' import type { CommandData, PipetteData } from '@opentrons/api-client' -import type { Axis, Sign, StepSize } from '../../../molecules/JogControls/types' -import type { DropTipFlowsStep, FixitCommandTypeUtils } from '../types' +import type { Axis, Sign, StepSize } from '/app/molecules/JogControls/types' +import type { + DropTipFlowsRoute, + FixitCommandTypeUtils, + IssuedCommandsType, +} from '../types' import type { SetRobotErrorDetailsParams, UseDTWithTypeParams } from '.' import type { RunCommandByCommandTypeParams } from './useDropTipCreateCommands' @@ -33,15 +40,17 @@ type UseDropTipSetupCommandsParams = UseDTWithTypeParams & { setErrorDetails: (errorDetails: SetRobotErrorDetailsParams) => void toggleIsExiting: () => void fixitCommandTypeUtils?: FixitCommandTypeUtils - toggleClientEndRun: () => void } export interface UseDropTipCommandsResult { handleCleanUpAndClose: (homeOnExit?: boolean) => Promise - moveToAddressableArea: (addressableArea: AddressableAreaName) => Promise + moveToAddressableArea: ( + addressableArea: AddressableAreaName, + isPredefinedLocation: boolean // Is a predefined location in "choose location." + ) => Promise handleJog: (axis: Axis, dir: Sign, step: StepSize) => void blowoutOrDropTip: ( - currentStep: DropTipFlowsStep, + currentRoute: DropTipFlowsRoute, proceed: () => void ) => Promise handleMustHome: () => Promise @@ -58,12 +67,11 @@ export function useDropTipCommands({ instrumentModelSpecs, robotType, fixitCommandTypeUtils, - toggleClientEndRun, }: UseDropTipSetupCommandsParams): UseDropTipCommandsResult { const isFlex = robotType === FLEX_ROBOT_TYPE - const [hasSeenClose, setHasSeenClose] = React.useState(false) - const [jogQueue, setJogQueue] = React.useState Promise>>([]) - const [isJogging, setIsJogging] = React.useState(false) + const [hasSeenClose, setHasSeenClose] = useState(false) + const [jogQueue, setJogQueue] = useState Promise>>([]) + const [isJogging, setIsJogging] = useState(false) const pipetteId = fixitCommandTypeUtils?.pipetteId ?? null const { deleteMaintenanceRun } = useDeleteMaintenanceRunMutation() @@ -89,9 +97,11 @@ export function useDropTipCommands({ console.error(error.message) }) .finally(() => { - toggleClientEndRun() - closeFlow() - deleteMaintenanceRun(activeMaintenanceRunId) + deleteMaintenanceRun(activeMaintenanceRunId, { + onSettled: () => { + closeFlow() + }, + }) }) } } @@ -100,54 +110,71 @@ export function useDropTipCommands({ } const moveToAddressableArea = ( - addressableArea: AddressableAreaName + addressableArea: AddressableAreaName, + isPredefinedLocation: boolean ): Promise => { return new Promise((resolve, reject) => { - const addressableAreaFromConfig = getAddressableAreaFromConfig( - addressableArea, - deckConfig, - instrumentModelSpecs.channels, - robotType - ) - - if (addressableAreaFromConfig != null) { - const moveToAACommand = buildMoveToAACommand( - addressableAreaFromConfig, - pipetteId - ) - return chainRunCommands( - isFlex - ? [UPDATE_ESTIMATORS_EXCEPT_PLUNGERS, moveToAACommand] - : [moveToAACommand], - true - ) - .then((commandData: CommandData[]) => { - const error = commandData[0].data.error - if (error != null) { - setErrorDetails({ - runCommandError: error, - message: `Error moving to position: ${error.detail}`, - }) - } - }) - .then(resolve) - .catch(error => { - if ( - fixitCommandTypeUtils != null && - issuedCommandsType === 'fixit' - ) { - fixitCommandTypeUtils.errorOverrides.generalFailure() - } - - reject( - new Error(`Error issuing move to addressable area: ${error}`) + Promise.resolve() + .then(() => { + const addressableAreaFromConfig = getAddressableAreaFromConfig( + addressableArea, + deckConfig, + instrumentModelSpecs.channels, + robotType + ) + + if (addressableAreaFromConfig == null) { + throw new Error('invalid addressable area.') + } + + const moveToAACommand = buildMoveToAACommand( + addressableAreaFromConfig, + pipetteId, + isPredefinedLocation, + issuedCommandsType + ) + + if (isFlex) { + return chainRunCommands( + [ENGAGE_AXES, UPDATE_ESTIMATORS_EXCEPT_PLUNGERS], + false ) - }) - } else { - setErrorDetails({ - message: `Error moving to position: invalid addressable area.`, + .catch(error => { + // If one of the engage/estimator commands fails, we can safely assume it's because the position is + // unknown, so show the special error modal. + throw { + ...error, + errorType: DROP_TIP_SPECIAL_ERROR_TYPES.MUST_HOME_ERROR, + } + }) + .then(() => { + return chainRunCommands([Z_HOME, moveToAACommand], false) + }) + } else { + return chainRunCommands([Z_HOME, moveToAACommand], false) + } + }) + .then((commandData: CommandData[]) => { + const error = commandData[0].data.error + if (error != null) { + // eslint-disable-next-line @typescript-eslint/no-throw-literal + throw error + } + resolve() + }) + .catch((error: RunCommandError) => { + if (fixitCommandTypeUtils != null && issuedCommandsType === 'fixit') { + fixitCommandTypeUtils.errorOverrides.generalFailure() + } else { + setErrorDetails({ + type: error.errorType ?? null, + message: error.detail + ? `Error moving to position: ${error.detail}` + : 'Error moving to position: invalid addressable area.', + }) + } + reject(error) }) - } }) } @@ -192,7 +219,7 @@ export function useDropTipCommands({ } } - React.useEffect(() => { + useEffect(() => { processJogQueue() }, [jogQueue.length, isJogging]) @@ -206,55 +233,74 @@ export function useDropTipCommands({ } const blowoutOrDropTip = ( - currentStep: DropTipFlowsStep, + currentRoute: DropTipFlowsRoute, proceed: () => void ): Promise => { return new Promise((resolve, reject) => { - chainRunCommands( - currentStep === POSITION_AND_BLOWOUT - ? buildBlowoutCommands(instrumentModelSpecs, isFlex, pipetteId) - : buildDropTipInPlaceCommand(isFlex, pipetteId), - true - ) - .then((commandData: CommandData[]) => { - const error = commandData[0].data.error - if (error != null) { - if ( - fixitCommandTypeUtils != null && - issuedCommandsType === 'fixit' - ) { - if (currentStep === POSITION_AND_BLOWOUT) { - fixitCommandTypeUtils.errorOverrides.blowoutFailed() - } else { - fixitCommandTypeUtils.errorOverrides.tipDropFailed() - } - } + const isBlowoutRoute = currentRoute === DT_ROUTES.BLOWOUT - setErrorDetails({ - runCommandError: error, - message: `Error moving to position: ${error.detail}`, - }) - } else { - proceed() - resolve() - } - }) - .catch((error: Error) => { - if (fixitCommandTypeUtils != null && issuedCommandsType === 'fixit') { - if (currentStep === POSITION_AND_BLOWOUT) { - fixitCommandTypeUtils.errorOverrides.blowoutFailed() - } else { - fixitCommandTypeUtils.errorOverrides.tipDropFailed() - } - } + const handleError = (error: RunCommandError | Error): void => { + if (fixitCommandTypeUtils != null && issuedCommandsType === 'fixit') { + isBlowoutRoute + ? fixitCommandTypeUtils.errorOverrides.blowoutFailed() + : fixitCommandTypeUtils.errorOverrides.tipDropFailed() + } else { + const operation = isBlowoutRoute ? 'blowout' : 'drop tip' + const type = 'errorType' in error ? error.errorType : undefined + const messageDetail = + 'message' in error ? error.message : error.detail setErrorDetails({ - message: `Error issuing ${ - currentStep === POSITION_AND_BLOWOUT ? 'blowout' : 'drop tip' - } command: ${error.message}`, + type, + message: + messageDetail != null + ? `Error during ${operation}: ${messageDetail}` + : null, }) - resolve() + } + reject(error) + } + + // Throw any errors in the response body if any. + const handleSuccess = (commandData: CommandData[]): void => { + const error = commandData[0].data.error + if (error != null) { + // eslint-disable-next-line @typescript-eslint/no-throw-literal + throw error + } + proceed() + resolve() + } + + // For Flex, we need extra preparation steps + const prepareFlexBlowout = (): Promise => { + return chainRunCommands( + [ENGAGE_AXES, UPDATE_PLUNGER_ESTIMATORS], + false + ).catch(error => { + throw { + ...error, + errorType: DROP_TIP_SPECIAL_ERROR_TYPES.MUST_HOME_ERROR, + } }) + } + + const executeCommands = (): Promise => { + const commands = isBlowoutRoute + ? buildBlowoutCommands(instrumentModelSpecs, isFlex, pipetteId) + : buildDropTipInPlaceCommand(isFlex, pipetteId) + + return chainRunCommands(commands, false) + } + + if (isBlowoutRoute && isFlex) { + prepareFlexBlowout() + .then(executeCommands) + .then(handleSuccess) + .catch(handleError) + } else { + executeCommands().then(handleSuccess).catch(handleError) + } }) } @@ -288,6 +334,18 @@ const HOME: CreateCommand = { params: {}, } +const ENGAGE_AXES: CreateCommand = { + commandType: 'unsafe/engageAxes' as const, + params: { + axes: ['leftZ', 'rightZ', 'x', 'y', 'leftPlunger', 'rightPlunger'], + }, +} + +const Z_HOME: CreateCommand = { + commandType: 'home' as const, + params: { axes: ['leftZ', 'rightZ'] }, +} + const HOME_EXCEPT_PLUNGERS: CreateCommand = { commandType: 'home' as const, params: { axes: ['leftZ', 'rightZ', 'x', 'y'] }, @@ -298,22 +356,29 @@ const UPDATE_ESTIMATORS_EXCEPT_PLUNGERS: CreateCommand = { params: { axes: ['leftZ', 'rightZ', 'x', 'y'] }, } +const UPDATE_PLUNGER_ESTIMATORS: CreateCommand = { + commandType: 'unsafe/updatePositionEstimators' as const, + params: { axes: ['leftPlunger', 'rightPlunger'] }, +} + const buildDropTipInPlaceCommand = ( isFlex: boolean, pipetteId: string | null -): Array => +): CreateCommand[] => isFlex ? [ { commandType: 'unsafe/dropTipInPlace', params: { pipetteId: pipetteId ?? MANAGED_PIPETTE_ID }, }, + Z_HOME, ] : [ { commandType: 'dropTipInPlace', params: { pipetteId: pipetteId ?? MANAGED_PIPETTE_ID }, }, + Z_HOME, ] const buildBlowoutCommands = ( @@ -327,42 +392,45 @@ const buildBlowoutCommands = ( commandType: 'unsafe/blowOutInPlace', params: { pipetteId: pipetteId ?? MANAGED_PIPETTE_ID, - flowRate: Math.min( specs.defaultBlowOutFlowRate.value, MAXIMUM_BLOWOUT_FLOW_RATE_UL_PER_S ), }, }, - { - commandType: 'prepareToAspirate', - params: { - pipetteId: pipetteId ?? MANAGED_PIPETTE_ID, - }, - }, + Z_HOME, ] : [ { commandType: 'blowOutInPlace', params: { pipetteId: pipetteId ?? MANAGED_PIPETTE_ID, - flowRate: specs.defaultBlowOutFlowRate.value, }, }, + Z_HOME, ] const buildMoveToAACommand = ( addressableAreaFromConfig: AddressableAreaName, - pipetteId: string | null + pipetteId: string | null, + isPredefinedLocation: boolean, + commandType: IssuedCommandsType ): CreateCommand => { + // Always ensure the user does all the jogging if choosing a custom location on the deck. + const stayAtHighestPossibleZ = !isPredefinedLocation + + // Because we can never be certain about which tip is attached outside a protocol run, always assume the most + // conservative estimate, a 1000ul tip. + const zOffset = commandType === 'setup' && !stayAtHighestPossibleZ ? 88 : 0 + return { commandType: 'moveToAddressableArea', params: { pipetteId: pipetteId ?? MANAGED_PIPETTE_ID, - stayAtHighestPossibleZ: true, + stayAtHighestPossibleZ, addressableAreaName: addressableAreaFromConfig, - offset: { x: 0, y: 0, z: 0 }, + offset: { x: 0, y: 0, z: zOffset }, }, } } diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCreateCommands.ts b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCreateCommands.ts index 10112ea740b..2e7ddd47fcc 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCreateCommands.ts +++ b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipCreateCommands.ts @@ -1,15 +1,15 @@ import { useCreateMaintenanceCommandMutation } from '@opentrons/react-api-client' import { - useChainMaintenanceCommands, useChainRunCommands, useCreateRunCommandMutation, -} from '../../../resources/runs' +} from '/app/resources/runs' import type { CreateCommand } from '@opentrons/shared-data' import type { CommandData } from '@opentrons/api-client' import type { UseDTWithTypeParams, SetRobotErrorDetailsParams } from '.' import type { FixitCommandTypeUtils } from '../types' +import { useChainMaintenanceCommands } from '/app/resources/maintenance_runs' export interface RunCommandByCommandTypeParams { command: CreateCommand diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipLocations.ts b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipLocations.ts new file mode 100644 index 00000000000..d482d8fca0c --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipLocations.ts @@ -0,0 +1,91 @@ +import { useMemo } from 'react' + +import { + getDeckDefFromRobotType, + OT2_ROBOT_TYPE, + TRASH_BIN_ADAPTER_FIXTURE, + WASTE_CHUTE_FIXTURES, +} from '@opentrons/shared-data' + +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' + +import type { + RobotType, + CutoutConfig, + AddressableAreaName, +} from '@opentrons/shared-data' +import type { ValidDropTipBlowoutLocation } from '../types' + +export type DropTipBlowoutSlotName = + | AddressableAreaName + | 'CHOOSE_DECK_LOCATION' + +export interface DropTipBlowoutLocationDetails { + slotName: DropTipBlowoutSlotName + location: ValidDropTipBlowoutLocation +} + +// TODO(jh 09-25-24): Add "Return to labware" support once the server returns relevant labware. + +// Returns valid location options for executing tip commands. +export function useDropTipLocations( + robotType: RobotType +): DropTipBlowoutLocationDetails[] { + const { data: deckConfig = [] } = useNotifyDeckConfigurationQuery() + const deckDef = useMemo(() => getDeckDefFromRobotType(robotType), [robotType]) + + return useMemo(() => { + const createLocation = ( + cutoutConfig: CutoutConfig, + validLocation: ValidDropTipBlowoutLocation + ): DropTipBlowoutLocationDetails => { + const cutoutAAs = deckDef.cutoutFixtures.find( + fixture => cutoutConfig.cutoutId in fixture.providesAddressableAreas + )?.providesAddressableAreas + const slotName = + cutoutAAs?.[cutoutConfig.cutoutId]?.[0] ?? + (robotType === OT2_ROBOT_TYPE ? '1' : 'A1') + + if (!cutoutAAs?.[cutoutConfig.cutoutId]?.[0]) { + console.error( + 'Could not get correct slot location from deck definition and active deck config. Defaulting to A1.' + ) + } + + return { + location: validLocation, + slotName, + } + } + + const filterAndMap = ( + fixtureIds: string | string[], + validLocation: ValidDropTipBlowoutLocation + ): DropTipBlowoutLocationDetails[] => + deckConfig + .filter(config => + Array.isArray(fixtureIds) + ? fixtureIds.includes(config.cutoutFixtureId) + : fixtureIds === config.cutoutFixtureId + ) + .map(config => createLocation(config, validLocation)) + + return robotType === OT2_ROBOT_TYPE + ? [FIXED_TRASH_LOCATION, CHOOSE_DECK_LOCATION] + : [ + ...filterAndMap(TRASH_BIN_ADAPTER_FIXTURE, 'trash-bin'), + ...filterAndMap(WASTE_CHUTE_FIXTURES, 'waste-chute'), + CHOOSE_DECK_LOCATION, + ] + }, [deckConfig, deckDef, robotType]) +} + +const FIXED_TRASH_LOCATION: DropTipBlowoutLocationDetails = { + location: 'fixed-trash', + slotName: 'fixedTrash', +} + +const CHOOSE_DECK_LOCATION: DropTipBlowoutLocationDetails = { + location: 'deck', + slotName: 'CHOOSE_DECK_LOCATION', +} diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipMaintenanceRun.tsx b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipMaintenanceRun.tsx index 67f4fa7957a..2b2034a0724 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipMaintenanceRun.tsx +++ b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipMaintenanceRun.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' -import { useNotifyCurrentMaintenanceRun } from '../../../resources/maintenance_runs' import { - useCreateTargetedMaintenanceRunMutation, useChainMaintenanceCommands, -} from '../../../resources/runs' + useNotifyCurrentMaintenanceRun, +} from '/app/resources/maintenance_runs' +import { useCreateTargetedMaintenanceRunMutation } from '/app/resources/runs' import { buildLoadPipetteCommand } from './useDropTipCommands' import type { PipetteModelSpecs } from '@opentrons/shared-data' @@ -17,16 +17,11 @@ export type UseDropTipMaintenanceRunParams = Omit< UseDTWithTypeParams, 'instrumentModelSpecs' | 'mount' > & { - setErrorDetails?: (errorDetails: SetRobotErrorDetailsParams) => void + setErrorDetails: (errorDetails: SetRobotErrorDetailsParams) => void instrumentModelSpecs?: PipetteModelSpecs mount?: PipetteData['mount'] - /* Optionally control when a drop tip maintenance run is created. */ - enabled?: boolean } -// TODO(jh, 08-08-24): useDropTipMaintenanceRun is a bit overloaded now that we are using it create maintenance runs -// on-the-fly for one-off commands outside of a run. Consider refactoring. - // Manages the maintenance run state if the flow is utilizing "setup" type commands. export function useDropTipMaintenanceRun({ issuedCommandsType, @@ -34,14 +29,10 @@ export function useDropTipMaintenanceRun({ instrumentModelSpecs, setErrorDetails, closeFlow, - enabled, -}: UseDropTipMaintenanceRunParams): { - activeMaintenanceRunId: string | null - toggleClientEndRun: () => void -} { +}: UseDropTipMaintenanceRunParams): string | null { const isMaintenanceRunType = issuedCommandsType === 'setup' - const [createdMaintenanceRunId, setCreatedMaintenanceRunId] = React.useState< + const [createdMaintenanceRunId, setCreatedMaintenanceRunId] = useState< string | null >(null) @@ -57,25 +48,21 @@ export function useDropTipMaintenanceRun({ instrumentModelName: instrumentModelSpecs?.name, setErrorDetails, setCreatedMaintenanceRunId, - enabled, }) - const toggleClientEndRun = useMonitorMaintenanceRunForDeletion({ + useMonitorMaintenanceRunForDeletion({ isMaintenanceRunType, activeMaintenanceRunId, createdMaintenanceRunId, closeFlow, }) - return { - activeMaintenanceRunId: activeMaintenanceRunId ?? null, - toggleClientEndRun, - } + return activeMaintenanceRunId ?? null } type UseCreateDropTipMaintenanceRunParams = Omit< UseDropTipMaintenanceRunParams, - 'robotType' | 'closeFlow' + 'robotType' | 'closeFlow' | 'modalStyle' > & { setCreatedMaintenanceRunId: (id: string) => void instrumentModelName?: PipetteModelSpecs['name'] @@ -88,7 +75,6 @@ function useCreateDropTipMaintenanceRun({ instrumentModelName, setErrorDetails, setCreatedMaintenanceRunId, - enabled, }: UseCreateDropTipMaintenanceRunParams): void { const { chainRunCommands } = useChainMaintenanceCommands() @@ -109,40 +95,32 @@ function useCreateDropTipMaintenanceRun({ .catch((error: Error) => error) }, onError: (error: Error) => { - if (setErrorDetails != null) { - setErrorDetails({ message: error.message }) - } + setErrorDetails({ message: error.message }) }, }) - const isEnabled = enabled ?? true - React.useEffect(() => { + useEffect(() => { if ( issuedCommandsType === 'setup' && mount != null && - instrumentModelName != null && - isEnabled + instrumentModelName != null ) { createTargetedMaintenanceRun({}).catch((e: Error) => { - if (setErrorDetails != null) { - setErrorDetails({ - message: `Error creating maintenance run: ${e.message}`, - }) - } + setErrorDetails({ + message: `Error creating maintenance run: ${e.message}`, + }) }) } else { - if (mount != null || instrumentModelName != null) { - console.warn( - 'Could not create maintenance run due to missing pipette data.' - ) - } + console.warn( + 'Could not create maintenance run due to missing pipette data.' + ) } - }, [enabled, mount, instrumentModelName]) + }, [mount, instrumentModelName]) } interface UseMonitorMaintenanceRunForDeletionParams { isMaintenanceRunType: boolean - closeFlow: (isTakeover?: boolean) => void + closeFlow: () => void createdMaintenanceRunId: string | null activeMaintenanceRunId?: string } @@ -154,17 +132,14 @@ function useMonitorMaintenanceRunForDeletion({ createdMaintenanceRunId, activeMaintenanceRunId, closeFlow, -}: UseMonitorMaintenanceRunForDeletionParams): () => void { +}: UseMonitorMaintenanceRunForDeletionParams): void { const [ monitorMaintenanceRunForDeletion, setMonitorMaintenanceRunForDeletion, - ] = React.useState(false) - const [closedOnce, setClosedOnce] = React.useState(false) - const [closedByThisClient, setClosedByThisClient] = React.useState( - false - ) + ] = useState(false) + const [closedOnce, setClosedOnce] = useState(false) - React.useEffect(() => { + useEffect(() => { if (isMaintenanceRunType && !closedOnce) { if ( createdMaintenanceRunId !== null && @@ -174,16 +149,11 @@ function useMonitorMaintenanceRunForDeletion({ } if ( activeMaintenanceRunId !== createdMaintenanceRunId && - monitorMaintenanceRunForDeletion && - !closedByThisClient + monitorMaintenanceRunForDeletion ) { - closeFlow(true) + closeFlow() setClosedOnce(true) } } }, [isMaintenanceRunType, createdMaintenanceRunId, activeMaintenanceRunId]) - - return () => { - setClosedByThisClient(!closedByThisClient) - } } diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipRouting.tsx b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipRouting.tsx index 50a72417c8b..4c3e8e01064 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipRouting.tsx +++ b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipRouting.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useMemo, useState, useEffect } from 'react' import head from 'lodash/head' import last from 'lodash/last' @@ -27,8 +27,11 @@ export interface UseDropTipRoutingResult { goBack: () => Promise /* Proceed a step within the current route. Reroutes to "Before Beginning" if currently on the last step. */ proceed: () => Promise - /* Proceed to the first step of a given route. */ - proceedToRoute: (route: DropTipFlowsRoute) => Promise + /* Proceed to a given step of a given route or the first step of a given route if none is specified. */ + proceedToRouteAndStep: ( + route: DropTipFlowsRoute, + step?: DropTipFlowsStep + ) => Promise } /** @@ -44,18 +47,16 @@ export interface UseDropTipRoutingResult { export function useDropTipRouting( fixitUtils?: FixitCommandTypeUtils ): UseDropTipRoutingResult { - const [initialRoute, initialStep] = React.useMemo( + const [initialRoute, initialStep] = useMemo( () => getInitialRouteAndStep(fixitUtils), [] ) - const [dropTipFlowsMap, setDropTipFlowsMap] = React.useState( - { - currentRoute: initialRoute as DropTipFlowsRoute, - currentStep: initialStep, - currentStepIdx: 0, - } - ) + const [dropTipFlowsMap, setDropTipFlowsMap] = useState({ + currentRoute: initialRoute as DropTipFlowsRoute, + currentStep: initialStep, + currentStepIdx: 0, + }) useReportMap(dropTipFlowsMap, fixitUtils) @@ -97,19 +98,37 @@ export function useDropTipRouting( }) } - const proceedToRoute = (route: DropTipFlowsRoute): Promise => { + const proceedToRouteAndStep = ( + route: DropTipFlowsRoute, + step?: DropTipFlowsStep + ): Promise => { return new Promise((resolve, reject) => { - setDropTipFlowsMap({ - currentRoute: route, - currentStep: head(route) as DropTipFlowsStep, - currentStepIdx: 0, - }) + // @ts-expect-error Step always valid or handled appropriately. + const isStepNotInRoute = step != null && !route.includes(step) + + if (step == null || isStepNotInRoute) { + setDropTipFlowsMap({ + currentRoute: route, + currentStep: head(route) as DropTipFlowsStep, + currentStepIdx: 0, + }) + if (isStepNotInRoute) { + console.error(`Step ${step} not found in route ${route}`) + } + } else { + setDropTipFlowsMap({ + currentRoute: route, + currentStep: step, + // @ts-expect-error Step always valid or handled appropriately. + currentStepIdx: route.indexOf(step), + }) + } resolve() }) } - return { ...dropTipFlowsMap, goBack, proceed, proceedToRoute } + return { ...dropTipFlowsMap, goBack, proceed, proceedToRouteAndStep } } type DTStepOrInvalid = DropTipFlowsStep | typeof INVALID @@ -186,7 +205,7 @@ export function useReportMap( ): void { const { currentStep, currentRoute } = map - React.useEffect(() => { + useEffect(() => { if (fixitUtils != null) { fixitUtils.reportMap({ route: currentRoute, step: currentStep }) } @@ -199,7 +218,10 @@ export function getInitialRouteAndStep( ): [DropTipFlowsRoute, DropTipFlowsStep] { const routeOverride = fixitUtils?.routeOverride const initialRoute = routeOverride?.route ?? DT_ROUTES.BEFORE_BEGINNING - const initialStep = routeOverride?.step ?? BEFORE_BEGINNING_STEPS[0] + const initialStep = + routeOverride?.step ?? + routeOverride?.route?.[0] ?? + BEFORE_BEGINNING_STEPS[0] return [initialRoute, initialStep] } diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipWithType.ts b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipWithType.ts index 63b3ab05da3..92ff68fb763 100644 --- a/app/src/organisms/DropTipWizardFlows/hooks/useDropTipWithType.ts +++ b/app/src/organisms/DropTipWizardFlows/hooks/useDropTipWithType.ts @@ -1,19 +1,15 @@ // This is the main unifying function for maintenanceRun and fixit type flows. -import * as React from 'react' +import { useState } from 'react' import { useDropTipCommandErrors } from '.' import { useDropTipMaintenanceRun } from './useDropTipMaintenanceRun' import { useDropTipCreateCommands } from './useDropTipCreateCommands' -import { - useDropTipCommands, - buildLoadPipetteCommand, -} from './useDropTipCommands' +import { useDropTipCommands } from './useDropTipCommands' import type { SetRobotErrorDetailsParams } from '.' import type { UseDropTipCommandsResult } from './useDropTipCommands' import type { ErrorDetails, IssuedCommandsType } from '../types' import type { DropTipWizardFlowsProps } from '..' -import type { UseDropTipCreateCommandsResult } from './useDropTipCreateCommands' export type UseDTWithTypeParams = DropTipWizardFlowsProps & { issuedCommandsType: IssuedCommandsType @@ -41,10 +37,7 @@ export function useDropTipWithType( const { isExiting, toggleIsExiting } = useIsExitingDT(issuedCommandsType) const { errorDetails, setErrorDetails } = useErrorDetails() - const { - activeMaintenanceRunId, - toggleClientEndRun, - } = useDropTipMaintenanceRun({ + const activeMaintenanceRunId = useDropTipMaintenanceRun({ ...params, setErrorDetails, }) @@ -63,11 +56,8 @@ export function useDropTipWithType( setErrorDetails, toggleIsExiting, fixitCommandTypeUtils, - toggleClientEndRun, }) - useRegisterPipetteFixitType({ ...params, ...dtCreateCommandUtils }) - return { activeMaintenanceRunId, errorDetails, @@ -82,9 +72,7 @@ function useErrorDetails(): { errorDetails: ErrorDetails | null setErrorDetails: (errorDetails: SetRobotErrorDetailsParams) => void } { - const [errorDetails, setErrorDetails] = React.useState( - null - ) + const [errorDetails, setErrorDetails] = useState(null) const setRobustErrorDetails = useDropTipCommandErrors(setErrorDetails) return { errorDetails, setErrorDetails: setRobustErrorDetails } @@ -102,7 +90,7 @@ function useIsExitingDT( isExiting: boolean toggleIsExiting: () => void } { - const [isExiting, setIsExiting] = React.useState(false) + const [isExiting, setIsExiting] = useState(false) const toggleIsExiting = (): void => { setIsExiting(!isExiting) @@ -112,26 +100,3 @@ function useIsExitingDT( return { isExiting: isExitingIfNotFixit, toggleIsExiting } } - -type UseRegisterPipetteFixitType = UseDTWithTypeParams & - UseDropTipCreateCommandsResult - -// On mount, if fixit command type, load the managed pipette ID for use in DT Wiz. -function useRegisterPipetteFixitType({ - mount, - instrumentModelSpecs, - issuedCommandsType, - chainRunCommands, - fixitCommandTypeUtils, -}: UseRegisterPipetteFixitType): void { - React.useEffect(() => { - if (issuedCommandsType === 'fixit') { - const command = buildLoadPipetteCommand( - instrumentModelSpecs.name, - mount, - fixitCommandTypeUtils?.pipetteId - ) - void chainRunCommands([command], true) - } - }, []) -} diff --git a/app/src/organisms/DropTipWizardFlows/hooks/useHomePipettes.ts b/app/src/organisms/DropTipWizardFlows/hooks/useHomePipettes.ts deleted file mode 100644 index cb3576c7105..00000000000 --- a/app/src/organisms/DropTipWizardFlows/hooks/useHomePipettes.ts +++ /dev/null @@ -1,80 +0,0 @@ -import * as React from 'react' - -import { - useCreateMaintenanceCommandMutation, - useDeleteMaintenanceRunMutation, -} from '@opentrons/react-api-client' - -import { useDropTipMaintenanceRun } from './useDropTipMaintenanceRun' - -import type { UseDropTipMaintenanceRunParams } from './useDropTipMaintenanceRun' -import type { CreateCommand } from '@opentrons/shared-data' - -export type UseHomePipettesProps = Omit< - UseDropTipMaintenanceRunParams, - 'issuedCommandsType' | 'closeFlow' -> & { - onHome: () => void - isRunCurrent: boolean -} - -export function useHomePipettes( - props: UseHomePipettesProps -): { - homePipettes: () => void - isHomingPipettes: boolean -} { - const [isHomingPipettes, setIsHomingPipettes] = React.useState(false) - const { deleteMaintenanceRun } = useDeleteMaintenanceRunMutation() - - const { activeMaintenanceRunId } = useDropTipMaintenanceRun({ - ...props, - issuedCommandsType: 'setup', - enabled: isHomingPipettes, - closeFlow: props.onHome, - }) - const isMaintenanceRunActive = activeMaintenanceRunId != null - - // Home the pipette after user click once a maintenance run has been created. - React.useEffect(() => { - if (isMaintenanceRunActive && isHomingPipettes && props.isRunCurrent) { - void homePipettesCmd().finally(() => { - props.onHome() - deleteMaintenanceRun(activeMaintenanceRunId) - }) - } - }, [isMaintenanceRunActive, isHomingPipettes, props.isRunCurrent]) - - const { createMaintenanceCommand } = useCreateMaintenanceCommandMutation() - - const homePipettesCmd = React.useCallback(() => { - if (activeMaintenanceRunId != null) { - return createMaintenanceCommand( - { - maintenanceRunId: activeMaintenanceRunId, - command: HOME_EXCEPT_PLUNGERS, - waitUntilComplete: true, - }, - { onSettled: () => Promise.resolve() } - ) - } else { - return Promise.reject( - new Error( - "'Unable to create a maintenance run when attempting to home pipettes." - ) - ) - } - }, [createMaintenanceCommand, activeMaintenanceRunId]) - - return { - homePipettes: () => { - setIsHomingPipettes(true) - }, - isHomingPipettes, - } -} - -const HOME_EXCEPT_PLUNGERS: CreateCommand = { - commandType: 'home' as const, - params: { axes: ['leftZ', 'rightZ', 'x', 'y'] }, -} diff --git a/app/src/organisms/DropTipWizardFlows/index.ts b/app/src/organisms/DropTipWizardFlows/index.ts new file mode 100644 index 00000000000..05a16f92e49 --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/index.ts @@ -0,0 +1,4 @@ +export * from './DropTipWizardFlows' +export * from './TipsAttachedModal' + +export type { FixitCommandTypeUtils } from './types' diff --git a/app/src/organisms/DropTipWizardFlows/index.tsx b/app/src/organisms/DropTipWizardFlows/index.tsx deleted file mode 100644 index f559aaa8e31..00000000000 --- a/app/src/organisms/DropTipWizardFlows/index.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import * as React from 'react' -import head from 'lodash/head' - -import { getPipetteModelSpecs } from '@opentrons/shared-data' - -import { getPipettesWithTipAttached } from './getPipettesWithTipAttached' -import { useDropTipRouting, useDropTipWithType } from './hooks' -import { DropTipWizard } from './DropTipWizard' - -import type { PipetteModelSpecs, RobotType } from '@opentrons/shared-data' -import type { Mount, PipetteData } from '@opentrons/api-client' -import type { FixitCommandTypeUtils, IssuedCommandsType } from './types' -import type { GetPipettesWithTipAttached } from './getPipettesWithTipAttached' -import { useInstrumentsQuery } from '@opentrons/react-api-client' - -/** Provides the user toggle for rendering Drop Tip Wizard Flows. - * - * NOTE: Rendering these flows is independent of whether tips are actually attached. First use useTipAttachmentStatus - * to get tip attachment status. - */ -export function useDropTipWizardFlows(): { - showDTWiz: boolean - toggleDTWiz: () => void -} { - const [showDTWiz, setShowDTWiz] = React.useState(false) - - const toggleDTWiz = (): void => { - setShowDTWiz(!showDTWiz) - } - - return { showDTWiz, toggleDTWiz } -} - -export interface DropTipWizardFlowsProps { - robotType: RobotType - mount: PipetteData['mount'] - instrumentModelSpecs: PipetteModelSpecs - /* isTakeover allows for optionally specifying a different callback if a different client cancels the "setup" type flow. */ - closeFlow: (isTakeover?: boolean) => void - /* Optional. If provided, DT will issue "fixit" commands and render alternate Error Recovery compatible views. */ - fixitCommandTypeUtils?: FixitCommandTypeUtils -} - -export function DropTipWizardFlows( - props: DropTipWizardFlowsProps -): JSX.Element { - const { fixitCommandTypeUtils } = props - - const issuedCommandsType: IssuedCommandsType = - fixitCommandTypeUtils != null ? 'fixit' : 'setup' - - const dropTipWithTypeUtils = useDropTipWithType({ - ...props, - issuedCommandsType, - }) - - const dropTipRoutingUtils = useDropTipRouting(fixitCommandTypeUtils) - - return ( - - ) -} - -const INSTRUMENTS_POLL_MS = 5000 - -export interface PipetteWithTip { - mount: Mount - specs: PipetteModelSpecs -} - -export interface TipAttachmentStatusResult { - /** Updates the pipettes with tip cache. Determine whether tips are likely attached on one or more pipettes. - * - * NOTE: Use responsibly! This function can potentially (but not likely) iterate over the entire length of a protocol run. - * */ - determineTipStatus: () => Promise - /* Whether tips are likely attached on *any* pipette. Typically called after determineTipStatus() */ - areTipsAttached: boolean - /* Resets the cached pipettes with tip statuses to null. */ - resetTipStatus: () => void - /** Removes the first element from the tip attached cache if present. - * @param {Function} onEmptyCache After removing the pipette from the cache, if the attached tip cache is empty, invoke this callback. - * @param {Function} onTipsDetected After removing the pipette from the cache, if the attached tip cache is not empty, invoke this callback. - * */ - setTipStatusResolved: ( - onEmptyCache?: () => void, - onTipsDetected?: () => void - ) => Promise - /* Relevant pipette information for a pipette with a tip attached. If both pipettes have tips attached, return the left pipette. */ - aPipetteWithTip: PipetteWithTip | null - /* The initial number of pipettes with tips. Null if there has been no tip check yet. */ - initialPipettesWithTipsCount: number | null -} - -// Returns various utilities for interacting with the cache of pipettes with tips attached. -export function useTipAttachmentStatus( - params: Omit -): TipAttachmentStatusResult { - const [pipettesWithTip, setPipettesWithTip] = React.useState< - PipetteWithTip[] - >([]) - const [initialPipettesCount, setInitialPipettesCount] = React.useState< - number | null - >(null) - const { data: attachedInstruments } = useInstrumentsQuery({ - refetchInterval: INSTRUMENTS_POLL_MS, - }) - - const aPipetteWithTip = head(pipettesWithTip) ?? null - const areTipsAttached = - pipettesWithTip.length > 0 && head(pipettesWithTip)?.specs != null - - const determineTipStatus = React.useCallback((): Promise< - PipetteWithTip[] - > => { - return getPipettesWithTipAttached({ - ...params, - attachedInstruments: attachedInstruments ?? null, - }).then(pipettesWithTip => { - const pipettesWithTipsData = pipettesWithTip.map(pipette => { - const specs = getPipetteModelSpecs(pipette.instrumentModel) - return { - specs, - mount: pipette.mount, - } - }) - const pipettesWithTipAndSpecs = pipettesWithTipsData.filter( - pipette => pipette.specs != null - ) as PipetteWithTip[] - - setPipettesWithTip(pipettesWithTipAndSpecs) - // Set only once. - if (initialPipettesCount === null) { - setInitialPipettesCount(pipettesWithTipAndSpecs.length) - } - - return Promise.resolve(pipettesWithTipAndSpecs) - }) - }, [params]) - - const resetTipStatus = (): void => { - setPipettesWithTip([]) - setInitialPipettesCount(null) - } - - const setTipStatusResolved = ( - onEmptyCache?: () => void, - onTipsDetected?: () => void - ): Promise => { - return new Promise(resolve => { - setPipettesWithTip(prevPipettesWithTip => { - const newState = [...prevPipettesWithTip.slice(1)] - if (newState.length === 0) { - onEmptyCache?.() - } else { - onTipsDetected?.() - } - - resolve(newState[0]) - return newState - }) - }) - } - - return { - areTipsAttached, - determineTipStatus, - resetTipStatus, - aPipetteWithTip, - setTipStatusResolved, - initialPipettesWithTipsCount: initialPipettesCount, - } -} diff --git a/app/src/organisms/DropTipWizardFlows/shared/DropTipFooterButtons.tsx b/app/src/organisms/DropTipWizardFlows/shared/DropTipFooterButtons.tsx new file mode 100644 index 00000000000..6825146c06f --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/shared/DropTipFooterButtons.tsx @@ -0,0 +1,105 @@ +import { useTranslation } from 'react-i18next' +import { css } from 'styled-components' + +import { + PrimaryButton, + AlertPrimaryButton, + Flex, + JUSTIFY_SPACE_BETWEEN, + ALIGN_CENTER, + Box, + RESPONSIVENESS, + ALIGN_FLEX_END, +} from '@opentrons/components' + +import { TextOnlyButton, SmallButton } from '../../../atoms/buttons' + +interface DropTipFooterButtonsProps { + primaryBtnOnClick: () => void + primaryBtnTextOverride?: string + primaryBtnDisabled?: boolean + primaryBtnStyle?: 'defaultStyle' | 'alertStyle' + /* Typically the "Go back" button. If no onClick is supplied, the button does not render. */ + secondaryBtnOnClick?: () => void +} + +export function DropTipFooterButtons( + props: DropTipFooterButtonsProps +): JSX.Element { + return ( + + + + + + + ) +} + +function DropTipGoBackButton({ + secondaryBtnOnClick, +}: DropTipFooterButtonsProps): JSX.Element | null { + const showGoBackBtn = secondaryBtnOnClick != null + const { t } = useTranslation('drop_tip_wizard') + return showGoBackBtn ? ( + + + + ) : ( + + ) +} + +function DropTipPrimaryBtn({ + primaryBtnOnClick, + primaryBtnTextOverride, + primaryBtnDisabled, + primaryBtnStyle, +}: DropTipFooterButtonsProps): JSX.Element { + const { t } = useTranslation('drop_tip_wizard') + + return ( + <> + + {primaryBtnStyle === 'alertStyle' ? ( + + {primaryBtnTextOverride ?? t('continue')} + + ) : ( + + {primaryBtnTextOverride ?? t('continue')} + + )} + + ) +} + +const DESKTOP_ONLY_BUTTON = css` + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + display: none; + } +` + +const ODD_ONLY_BUTTON = css` + @media not (${RESPONSIVENESS.touchscreenMediaQuerySpecs}) { + display: none; + } +` diff --git a/app/src/organisms/DropTipWizardFlows/shared/index.ts b/app/src/organisms/DropTipWizardFlows/shared/index.ts new file mode 100644 index 00000000000..1405936b4f8 --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/shared/index.ts @@ -0,0 +1 @@ +export * from './DropTipFooterButtons' diff --git a/app/src/organisms/DropTipWizardFlows/steps/BeforeBeginning.tsx b/app/src/organisms/DropTipWizardFlows/steps/BeforeBeginning.tsx new file mode 100644 index 00000000000..82be17832b4 --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/steps/BeforeBeginning.tsx @@ -0,0 +1,265 @@ +import { useState } from 'react' +import { css } from 'styled-components' +import { useTranslation } from 'react-i18next' + +import { + ALIGN_CENTER, + BORDERS, + COLORS, + DIRECTION_COLUMN, + CURSOR_POINTER, + DIRECTION_ROW, + DISPLAY_FLEX, + Flex, + JUSTIFY_CENTER, + JUSTIFY_FLEX_START, + JUSTIFY_SPACE_AROUND, + RESPONSIVENESS, + SPACING, + LegacyStyledText, + StyledText, +} from '@opentrons/components' + +import { MediumButton } from '../../../atoms/buttons' +import { DT_ROUTES } from '../constants' +import { DropTipFooterButtons } from '../shared' + +import blowoutVideo from '../../../assets/videos/droptip-wizard/Blowout-Liquid.webm' +import droptipVideo from '../../../assets/videos/droptip-wizard/Drop-tip.webm' + +import type { DropTipWizardContainerProps } from '../types' + +type FlowType = 'blowout' | 'drop_tips' | null + +export const BeforeBeginning = ({ + proceedToRouteAndStep, + isOnDevice, + issuedCommandsType, + fixitCommandTypeUtils, + modalStyle, +}: DropTipWizardContainerProps): JSX.Element | null => { + const { t } = useTranslation('drop_tip_wizard') + const [flowType, setFlowType] = useState(null) + + const handleProceed = (): void => { + if (flowType === 'blowout') { + void proceedToRouteAndStep(DT_ROUTES.BLOWOUT) + } else if (flowType === 'drop_tips') { + void proceedToRouteAndStep(DT_ROUTES.DROP_TIP) + } + } + + const buildTopText = (): string => { + if (issuedCommandsType === 'fixit') { + return fixitCommandTypeUtils?.copyOverrides + .beforeBeginningTopText as string + } else { + return t('before_you_begin_do_you_want_to_blowout') + } + } + + if (isOnDevice) { + return ( + <> + + + {buildTopText()} + + { + setFlowType('blowout') + }} + buttonText={t('yes_blow_out_liquid')} + /> + { + setFlowType('drop_tips') + }} + buttonText={t('no_proceed_to_drop_tip')} + /> + + + + ) + } else { + return ( + <> + + + {buildTopText()} + + + { + setFlowType('blowout') + }} + videoSrc={blowoutVideo} + text={t('yes_blow_out_liquid')} + /> + { + setFlowType('drop_tips') + }} + videoSrc={droptipVideo} + text={t('no_proceed_to_drop_tip')} + /> + + + + + ) + } +} + +function DropTipOption({ + flowType, + currentFlow, + onClick, + videoSrc, + text, +}: { + flowType: 'blowout' | 'drop_tips' + currentFlow: FlowType + onClick: () => void + videoSrc: string + text: string +}): JSX.Element { + return ( + + + {text} + + ) +} + +const UNSELECTED_OPTIONS_STYLE = css` + background-color: ${COLORS.white}; + border: 1px solid ${COLORS.grey30}; + border-radius: ${BORDERS.borderRadius8}; + height: 12.5625rem; + width: 14.5625rem; + cursor: ${CURSOR_POINTER}; + flex-direction: ${DIRECTION_COLUMN}; + justify-content: ${JUSTIFY_CENTER}; + align-items: ${ALIGN_CENTER}; + grid-gap: ${SPACING.spacing8}; + + &:hover { + border: 1px solid ${COLORS.grey35}; + } + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + flex-direction: ${DIRECTION_ROW}; + justify-content: ${JUSTIFY_FLEX_START}; + background-color: ${COLORS.blue35}; + border-width: 0; + border-radius: ${BORDERS.borderRadius16}; + padding: ${SPACING.spacing24}; + height: 5.25rem; + width: 57.8125rem; + + &:hover { + border-width: 0px; + } + } +` +const SELECTED_OPTIONS_STYLE = css` + ${UNSELECTED_OPTIONS_STYLE} + border: 1px solid ${COLORS.blue50}; + background-color: ${COLORS.blue30}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + border-width: 0px; + background-color: ${COLORS.blue50}; + color: ${COLORS.white}; + + &:hover { + border-width: 0px; + background-color: ${COLORS.blue50}; + } + } +` + +const CONTAINER_STYLE = css` + display: ${DISPLAY_FLEX}; + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing16}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + grid-gap: ${SPACING.spacing8}; + } +` + +const ODD_MEDIUM_BUTTON_STYLE = css` + flex: 1; + justify-content: ${JUSTIFY_FLEX_START}; + padding-left: ${SPACING.spacing24}; + height: 5.25rem; +` + +const SHARED_GIF_CONTAINER_STYLE = ` + justify-content: ${JUSTIFY_SPACE_AROUND}; + align-items: ${ALIGN_CENTER}; + grid-gap: ${SPACING.spacing16}; +` + +const SIMPLE_DESKTOP_GIF_CONTAINER_STYLE = css` + ${SHARED_GIF_CONTAINER_STYLE} + height: 18.75rem; +` + +const INTERVENTION_DESKTOP_GIF_CONTAINER_STYLE = css` + ${SHARED_GIF_CONTAINER_STYLE} + height: 14.563rem; +` diff --git a/app/src/organisms/DropTipWizardFlows/steps/ChooseDeckLocation.tsx b/app/src/organisms/DropTipWizardFlows/steps/ChooseDeckLocation.tsx new file mode 100644 index 00000000000..0bc5e6eaa67 --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/steps/ChooseDeckLocation.tsx @@ -0,0 +1,89 @@ +import { useState } from 'react' +import { Trans, useTranslation } from 'react-i18next' + +import { + DIRECTION_COLUMN, + Flex, + LegacyStyledText, + SPACING, + StyledText, +} from '@opentrons/components' +import { getDeckDefFromRobotType } from '@opentrons/shared-data' + +import { DT_ROUTES } from '/app/organisms/DropTipWizardFlows/constants' +import { DeckMapContent, TwoColumn } from '/app/molecules/InterventionModal' +import { DropTipFooterButtons } from '/app/organisms/DropTipWizardFlows/shared' + +import type { ModuleLocation } from '@opentrons/shared-data' +import type { DropTipWizardContainerProps } from '/app/organisms/DropTipWizardFlows/types' + +export function ChooseDeckLocation({ + robotType, + dropTipCommands, + proceedWithConditionalClose, + goBackRunValid, + currentRoute, + isOnDevice, +}: DropTipWizardContainerProps): JSX.Element { + const { moveToAddressableArea } = dropTipCommands + const { t } = useTranslation('drop_tip_wizard') + const [selectedLocation, setSelectedLocation] = useState() + const deckDef = getDeckDefFromRobotType(robotType) + + const handleConfirmPosition = (): void => { + const deckSlot = deckDef.locations.addressableAreas.find( + l => l.id === selectedLocation?.slotName + )?.id + + if (deckSlot != null) { + void moveToAddressableArea(deckSlot, false).then(() => { + proceedWithConditionalClose() + }) + } + } + + const buildTitleText = (): string => + currentRoute === DT_ROUTES.BLOWOUT + ? t('choose_blowout_location') + : t('choose_drop_tip_location') + + const buildBodyText = (): string => { + if (currentRoute === DT_ROUTES.BLOWOUT) { + return isOnDevice ? 'select_blowout_slot_odd' : 'select_blowout_slot' + } else { + return isOnDevice ? 'select_drop_tip_slot_odd' : 'select_drop_tip_slot' + } + } + + return ( + <> + + + + {buildTitleText()} + + + }} + /> + + + + + + + ) +} diff --git a/app/src/organisms/DropTipWizardFlows/steps/ChooseLocation.tsx b/app/src/organisms/DropTipWizardFlows/steps/ChooseLocation.tsx new file mode 100644 index 00000000000..1e5aadcafdf --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/steps/ChooseLocation.tsx @@ -0,0 +1,216 @@ +import { useState, useLayoutEffect, useCallback } from 'react' +import { useTranslation } from 'react-i18next' +import { css } from 'styled-components' + +import { + DIRECTION_COLUMN, + RESPONSIVENESS, + Flex, + SPACING, + StyledText, + RadioButton, + OVERFLOW_AUTO, + JUSTIFY_SPACE_BETWEEN, +} from '@opentrons/components' + +import { BLOWOUT_SUCCESS, DROP_TIP_SUCCESS, DT_ROUTES } from '../constants' +import { DropTipFooterButtons } from '../shared' + +import type { FlattenSimpleInterpolation } from 'styled-components' +import type { AddressableAreaName } from '@opentrons/shared-data' +import type { + DropTipModalStyle, + DropTipWizardContainerProps, + ValidDropTipBlowoutLocation, +} from '../types' +import type { + DropTipBlowoutLocationDetails, + DropTipBlowoutSlotName, +} from '../hooks' +import type { UseConfirmPositionResult } from './ConfirmPosition' + +interface ChooseLocationProps extends DropTipWizardContainerProps { + toggleIsRobotPipetteMoving: UseConfirmPositionResult['toggleIsRobotPipetteMoving'] +} + +export function ChooseLocation({ + issuedCommandsType, + dropTipCommandLocations, + dropTipCommands, + goBackRunValid, + currentRoute, + proceed, + proceedToRouteAndStep, + toggleIsRobotPipetteMoving, + modalStyle, +}: ChooseLocationProps): JSX.Element { + const { t } = useTranslation('drop_tip_wizard') + const { moveToAddressableArea, blowoutOrDropTip } = dropTipCommands + + const [ + selectedLocation, + setSelectedLocation, + ] = useState(null) + + // On initial render with values, synchronously set the first option as the selected option. + useLayoutEffect(() => { + if (dropTipCommandLocations.length > 0) { + setSelectedLocation(dropTipCommandLocations[0]) + } + }, [dropTipCommandLocations.length]) + + const buildTitleCopy = (): string => { + if (currentRoute === DT_ROUTES.BLOWOUT) { + return t('where_to_blowout') + } else if (currentRoute === DT_ROUTES.DROP_TIP) { + return t('where_to_drop_tips') + } else { + console.error('Unhandled choose location copy from step') + return t('where_to_drop_tips') + } + } + + const buildLocationCopy = ( + locationValue: ValidDropTipBlowoutLocation, + slotName: DropTipBlowoutSlotName + ): string => { + switch (locationValue) { + case 'trash-bin': + return t('trash_bin_in_slot', { slot: slotName }) + case 'waste-chute': + return t('waste_chute_in_slot', { slot: slotName }) + case 'fixed-trash': + return t('fixed_trash_in_12') + case 'deck': + return t('choose_deck_location') + default: + console.error('Unexpected location value.') + return '' + } + } + + const handleChange = useCallback( + (locationDetails: DropTipBlowoutLocationDetails) => { + setSelectedLocation(locationDetails) + }, + [] + ) + + const executeCommands = (): void => { + toggleIsRobotPipetteMoving() + void moveToAddressableArea( + selectedLocation?.slotName as AddressableAreaName, + true + ).then(() => { + void blowoutOrDropTip(currentRoute, () => { + const successStep = + currentRoute === DT_ROUTES.BLOWOUT + ? BLOWOUT_SUCCESS + : DROP_TIP_SUCCESS + void proceedToRouteAndStep(currentRoute, successStep) + }) + }) + } + + const primaryOnClick = (): void => { + switch (selectedLocation?.location) { + case 'deck': + void proceed() + break + case 'labware': + case 'trash-bin': + case 'waste-chute': + case 'fixed-trash': + executeCommands() + break + default: + console.error( + `Unhandled onClick behavior for location: ${selectedLocation?.location}` + ) + } + } + + return ( + + + + {buildTitleCopy()} + + + {dropTipCommandLocations.map(ld => { + const label = buildLocationCopy(ld.location, ld.slotName) + return ( + { + handleChange(ld) + }} + isSelected={ld.slotName === selectedLocation?.slotName} + largeDesktopBorderRadius={true} + /> + ) + })} + + + + + ) +} + +// TODO(jh, 10-31-24): The numLocations logic is a hack to get around some unexpected ODD-specific CSS behavior in RadioButton. +// Investigate RadioButton ODD styling. +const buildContainerStyle = ( + modalStyle: DropTipModalStyle, + numLocations: number +): FlattenSimpleInterpolation => { + return modalStyle === 'simple' + ? containerStyleSimple(numLocations) + : CONTAINER_STYLE_INTERVENTION +} + +const CONTAINER_STYLE_BASE = ` + overflow: ${OVERFLOW_AUTO}; + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing16}; + height: 100%; + width: 100%; + flex-grow: 1; +` + +const CONTAINER_STYLE_INTERVENTION = css` + ${CONTAINER_STYLE_BASE} +` + +const containerStyleSimple = ( + numLocations: number +): FlattenSimpleInterpolation => css` + ${CONTAINER_STYLE_BASE} + justify-content: ${JUSTIFY_SPACE_BETWEEN}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + height: ${numLocations >= 4 ? '80%' : '100%'}; + flex-grow: 0; + } +` + +const OPTION_CONTAINER_STYLE = css` + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing16}; +` + +const BUTTON_CONTAINER_STYLE = css` + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing4}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + grid-gap: ${SPACING.spacing8}; + } +` diff --git a/app/src/organisms/DropTipWizardFlows/steps/ConfirmPosition.tsx b/app/src/organisms/DropTipWizardFlows/steps/ConfirmPosition.tsx new file mode 100644 index 00000000000..3b3f0e9dfb5 --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/steps/ConfirmPosition.tsx @@ -0,0 +1,146 @@ +import { useState, useEffect } from 'react' +import { css } from 'styled-components' +import { useTranslation } from 'react-i18next' + +import { + ALIGN_CENTER, + COLORS, + DIRECTION_COLUMN, + Flex, + Icon, + JUSTIFY_CENTER, + SPACING, + DISPLAY_FLEX, + TEXT_ALIGN_CENTER, + RESPONSIVENESS, + StyledText, +} from '@opentrons/components' + +import { + CHOOSE_LOCATION_OPTION, + CONFIRM_POSITION, + DT_ROUTES, +} from '../constants' +import { DropTipFooterButtons } from '../shared' + +import type { DropTipWizardContainerProps } from '../types' + +export interface UseConfirmPositionResult { + isRobotPipetteMoving: boolean + toggleIsRobotPipetteMoving: () => void +} + +// Handles confirming the position. Because pipette drop tip/blowout actions do not trigger +// an "in-motion" the same way other commands do, we synthetically create an "in motion", disabling +// it once the step has completed or failed. +export function useConfirmPosition( + currentStep: DropTipWizardContainerProps['currentStep'] +): UseConfirmPositionResult { + const [isRobotPipetteMoving, setIsRobotPipetteMoving] = useState(false) + + const toggleIsRobotPipetteMoving = (): void => { + setIsRobotPipetteMoving(!isRobotPipetteMoving) + } + + useEffect(() => { + if ( + isRobotPipetteMoving && + currentStep !== CONFIRM_POSITION && + currentStep !== CHOOSE_LOCATION_OPTION + ) { + toggleIsRobotPipetteMoving() + } + }, [currentStep, isRobotPipetteMoving]) + + return { + toggleIsRobotPipetteMoving, + isRobotPipetteMoving, + } +} + +type ConfirmPositionProps = DropTipWizardContainerProps & + UseConfirmPositionResult + +export function ConfirmPosition({ + toggleIsRobotPipetteMoving, + goBackRunValid, + currentRoute, + dropTipCommands, + proceed, + modalStyle, +}: ConfirmPositionProps): JSX.Element { + const { blowoutOrDropTip } = dropTipCommands + const { t } = useTranslation('drop_tip_wizard') + + const buildPrimaryBtnText = (): string => + currentRoute === DT_ROUTES.BLOWOUT ? t('blowout_liquid') : t('drop_tips') + + const handleProceed = (): void => { + toggleIsRobotPipetteMoving() + void blowoutOrDropTip(currentRoute, proceed) + } + + return ( + <> + + + + {currentRoute === DT_ROUTES.BLOWOUT + ? t('confirm_blowout_location') + : t('confirm_drop_tip_location')} + + + + + ) +} + +const SHARED_CONTAINER_STYLE = ` + display: ${DISPLAY_FLEX}; + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing16}; + padding: ${SPACING.spacing40} ${SPACING.spacing16}; + align-items: ${ALIGN_CENTER}; + justify-content: ${JUSTIFY_CENTER}; + text-align: ${TEXT_ALIGN_CENTER}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + grid-gap: ${SPACING.spacing24}; + padding: ${SPACING.spacing40}; + } +` + +const INTERVENTION_CONTAINER_STYLE = css` + ${SHARED_CONTAINER_STYLE} + margin-top: ${SPACING.spacing60}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + margin-top: ${SPACING.spacing48}; + } +` + +const SIMPLE_CONTAINER_STYLE = css` + ${SHARED_CONTAINER_STYLE} + margin-top: ${SPACING.spacing32}; +` + +const ICON_STYLE = css` + width: 40px; + height: 40px; + color: ${COLORS.yellow50}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + width: 60px; + height: 60px; + } +` diff --git a/app/src/organisms/DropTipWizardFlows/steps/JogToPosition.tsx b/app/src/organisms/DropTipWizardFlows/steps/JogToPosition.tsx new file mode 100644 index 00000000000..93d6c4b3a3c --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/steps/JogToPosition.tsx @@ -0,0 +1,99 @@ +import { useTranslation } from 'react-i18next' +import { css } from 'styled-components' + +import { + DIRECTION_COLUMN, + Flex, + StyledText, + SPACING, + LegacyStyledText, + RESPONSIVENESS, +} from '@opentrons/components' + +import { DT_ROUTES } from '../constants' +import { JogControls } from '/app/molecules/JogControls' +import { DropTipFooterButtons } from '../shared' + +import type { DropTipWizardContainerProps } from '../types' +import type { UseConfirmPositionResult } from './ConfirmPosition' + +type JogToPositionProps = DropTipWizardContainerProps & UseConfirmPositionResult + +export const JogToPosition = ({ + goBackRunValid, + dropTipCommands, + currentRoute, + isOnDevice, + modalStyle, + proceed, +}: JogToPositionProps): JSX.Element | null => { + const { handleJog } = dropTipCommands + const { t } = useTranslation('drop_tip_wizard') + + return ( + <> + + + {t('position_the_pipette')} + + + {currentRoute === DT_ROUTES.BLOWOUT + ? t('position_and_blowout') + : t('position_and_drop_tip')} + + + + + + + + ) +} + +const TITLE_SECTION_STYLE = css` + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing16}; + + @media (${RESPONSIVENESS.touchscreenMediaQuerySpecs}) { + display: none; + } +` + +const SHARED_CONTENT_SECTION_STYLE = ` + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing16}; +` + +const SIMPLE_CONTENT_SECTION_STYLE = css` + ${SHARED_CONTENT_SECTION_STYLE} + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + grid-gap: 1.5rem; + } +` + +const INTERVENTION_CONTENT_SECTION_STYLE = css` + ${SHARED_CONTENT_SECTION_STYLE} + grid-gap: ${SPACING.spacing40}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + grid-gap: 0.9rem; + } +` diff --git a/app/src/organisms/DropTipWizardFlows/steps/Success.tsx b/app/src/organisms/DropTipWizardFlows/steps/Success.tsx new file mode 100644 index 00000000000..cc67ea47d9c --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/steps/Success.tsx @@ -0,0 +1,109 @@ +import { css } from 'styled-components' +import { useTranslation } from 'react-i18next' + +import { + StyledText, + ALIGN_CENTER, + Flex, + SPACING, + DIRECTION_COLUMN, + RESPONSIVENESS, + JUSTIFY_CENTER, +} from '@opentrons/components' + +import { DropTipFooterButtons } from '../shared' +import { DT_ROUTES } from '../constants' + +import SuccessIcon from '../../../assets/images/icon_success.png' + +import type { DropTipWizardContainerProps } from '../types' + +export const Success = ({ + currentRoute, + proceedToRouteAndStep, + fixitCommandTypeUtils, + proceedWithConditionalClose, + modalStyle, +}: DropTipWizardContainerProps): JSX.Element => { + const { tipDropComplete } = fixitCommandTypeUtils?.buttonOverrides ?? {} + const { t } = useTranslation('drop_tip_wizard') + + // Route to the drop tip route if user is at the blowout success screen, otherwise proceed conditionally. + const handleProceed = (): void => { + if (currentRoute === DT_ROUTES.BLOWOUT) { + void proceedToRouteAndStep(DT_ROUTES.DROP_TIP) + } else { + // Clear the error recovery submap upon completion of drop tip wizard. + fixitCommandTypeUtils?.reportMap(null) + + if (tipDropComplete != null) { + tipDropComplete() + } else { + proceedWithConditionalClose() + } + } + } + + const buildProceedText = (): string => { + if (fixitCommandTypeUtils != null && currentRoute === DT_ROUTES.DROP_TIP) { + return fixitCommandTypeUtils.copyOverrides.tipDropCompleteBtnCopy + } else { + return currentRoute === DT_ROUTES.BLOWOUT ? t('continue') : t('exit') + } + } + + return ( + <> + + Success Icon + + {currentRoute === DT_ROUTES.BLOWOUT + ? t('blowout_complete') + : t('drop_tip_complete')} + + + + + ) +} + +const WIZARD_CONTAINER_STYLE = css` + flex-direction: ${DIRECTION_COLUMN}; + justify-content: ${JUSTIFY_CENTER}; + align-items: ${ALIGN_CENTER}; + grid-gap: ${SPACING.spacing24}; + height: 100%; + width: 100%; +` + +const SHARED_IMAGE_STYLE = ` + width: 170px; + height: 141px; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + width: 282px; + height: 234px; + margin-top: 0; + } +` + +const SIMPLE_IMAGE_STYLE = css` + ${SHARED_IMAGE_STYLE} + margin-top: ${SPACING.spacing32}; +` + +const INTERVENTION_IMAGE_STYLE = css` + ${SHARED_IMAGE_STYLE} + margin-top: ${SPACING.spacing60}; +` diff --git a/app/src/organisms/DropTipWizardFlows/steps/index.ts b/app/src/organisms/DropTipWizardFlows/steps/index.ts new file mode 100644 index 00000000000..8b18a998c03 --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/steps/index.ts @@ -0,0 +1,6 @@ +export * from './BeforeBeginning' +export * from './ChooseLocation' +export * from './ChooseDeckLocation' +export * from './JogToPosition' +export * from './Success' +export * from './ConfirmPosition' diff --git a/app/src/organisms/DropTipWizardFlows/types.ts b/app/src/organisms/DropTipWizardFlows/types.ts index 4238d9ac8a0..a9538730b90 100644 --- a/app/src/organisms/DropTipWizardFlows/types.ts +++ b/app/src/organisms/DropTipWizardFlows/types.ts @@ -11,6 +11,7 @@ export interface ErrorDetails { } export type IssuedCommandsType = 'setup' | 'fixit' +export type DropTipModalStyle = 'simple' | 'intervention' interface CopyOverrides { tipDropCompleteBtnCopy: string @@ -58,3 +59,13 @@ export type DropTipWizardContainerProps = DropTipWizardProps & { proceedWithConditionalClose: () => void goBackRunValid: () => void } + +/** + * Drop-tip/Blowout location types + */ +export type ValidDropTipBlowoutLocation = + | 'trash-bin' + | 'fixed-trash' + | 'waste-chute' + | 'labware' + | 'deck' diff --git a/app/src/organisms/DropTipWizardFlows/getAddressableAreaFromConfig.ts b/app/src/organisms/DropTipWizardFlows/utils/getAddressableAreaFromConfig.ts similarity index 100% rename from app/src/organisms/DropTipWizardFlows/getAddressableAreaFromConfig.ts rename to app/src/organisms/DropTipWizardFlows/utils/getAddressableAreaFromConfig.ts diff --git a/app/src/organisms/DropTipWizardFlows/utils/index.ts b/app/src/organisms/DropTipWizardFlows/utils/index.ts new file mode 100644 index 00000000000..742cff97db1 --- /dev/null +++ b/app/src/organisms/DropTipWizardFlows/utils/index.ts @@ -0,0 +1 @@ +export * from './getAddressableAreaFromConfig' diff --git a/app/src/organisms/EmergencyStop/DesktopEstopMissingModal.stories.tsx b/app/src/organisms/EmergencyStop/DesktopEstopMissingModal.stories.tsx index 324ca64efc4..f8729d48263 100644 --- a/app/src/organisms/EmergencyStop/DesktopEstopMissingModal.stories.tsx +++ b/app/src/organisms/EmergencyStop/DesktopEstopMissingModal.stories.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { Provider } from 'react-redux' import { createStore } from 'redux' -import { configReducer } from '../../redux/config/reducer' +import { configReducer } from '/app/redux/config/reducer' import { EstopMissingModal } from '.' import type { Store, StoreEnhancer } from 'redux' diff --git a/app/src/organisms/EmergencyStop/DesktopEstopPressedModal.stories.tsx b/app/src/organisms/EmergencyStop/DesktopEstopPressedModal.stories.tsx index 66c49a944a5..5b999ce2bc5 100644 --- a/app/src/organisms/EmergencyStop/DesktopEstopPressedModal.stories.tsx +++ b/app/src/organisms/EmergencyStop/DesktopEstopPressedModal.stories.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { Provider } from 'react-redux' import { createStore } from 'redux' import { QueryClient, QueryClientProvider } from 'react-query' -import { configReducer } from '../../redux/config/reducer' +import { configReducer } from '/app/redux/config/reducer' import { EstopPressedModal } from '.' import type { Store, StoreEnhancer } from 'redux' diff --git a/app/src/organisms/EmergencyStop/EmergencyStopContext.ts b/app/src/organisms/EmergencyStop/EmergencyStopContext.ts index fd5be4b25a9..d79fae0eec1 100644 --- a/app/src/organisms/EmergencyStop/EmergencyStopContext.ts +++ b/app/src/organisms/EmergencyStop/EmergencyStopContext.ts @@ -1,4 +1,4 @@ -import * as React from 'react' +import { createContext } from 'react' export interface EmergencyStopContextType { isEmergencyStopModalDismissed: boolean @@ -7,9 +7,7 @@ export interface EmergencyStopContextType { ) => void } -export const EmergencyStopContext = React.createContext( - { - isEmergencyStopModalDismissed: false, - setIsEmergencyStopModalDismissed: () => {}, - } -) +export const EmergencyStopContext = createContext({ + isEmergencyStopModalDismissed: false, + setIsEmergencyStopModalDismissed: () => {}, +}) diff --git a/app/src/organisms/EmergencyStop/EstopMissingModal.tsx b/app/src/organisms/EmergencyStop/EstopMissingModal.tsx index 380d1f0f5ab..07fe453c932 100644 --- a/app/src/organisms/EmergencyStop/EstopMissingModal.tsx +++ b/app/src/organisms/EmergencyStop/EstopMissingModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { createPortal } from 'react-dom' import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' @@ -13,12 +12,12 @@ import { Modal, } from '@opentrons/components' -import { getTopPortalEl } from '../../App/portal' -import { OddModal } from '../../molecules/OddModal' -import { getIsOnDevice } from '../../redux/config' +import { getTopPortalEl } from '/app/App/portal' +import { OddModal } from '/app/molecules/OddModal' +import { getIsOnDevice } from '/app/redux/config' import type { ModalProps } from '@opentrons/components' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' // Note (07/13/2023) After the launch, we will unify the modal components into one component. // Then TouchScreenModal and DesktopModal will be TouchScreenContent and DesktopContent that only render each content. @@ -43,7 +42,7 @@ export function EstopMissingModal({ ) : ( <> - {isDismissedModal === false ? ( + {!isDismissedModal ? ( - {t('estop_missing_description', { robotName: robotName })} + {t('estop_missing_description', { robotName })}
    @@ -122,7 +121,7 @@ function DesktopModal({ {t('connect_the_estop_to_continue')} - {t('estop_missing_description', { robotName: robotName })} + {t('estop_missing_description', { robotName })}
    diff --git a/app/src/organisms/EmergencyStop/EstopPressedModal.tsx b/app/src/organisms/EmergencyStop/EstopPressedModal.tsx index 0047f3c9dee..ccebc8e124a 100644 --- a/app/src/organisms/EmergencyStop/EstopPressedModal.tsx +++ b/app/src/organisms/EmergencyStop/EstopPressedModal.tsx @@ -1,9 +1,10 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, + Banner, BORDERS, Chip, COLORS, @@ -23,46 +24,51 @@ import { import { useAcknowledgeEstopDisengageMutation } from '@opentrons/react-api-client' -import { getTopPortalEl } from '../../App/portal' -import { Banner } from '../../atoms/Banner' -import { SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' -import { getIsOnDevice } from '../../redux/config' +import { usePlacePlateReaderLid } from '/app/resources/modules' +import { getTopPortalEl } from '/app/App/portal' +import { SmallButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' +import { getIsOnDevice } from '/app/redux/config' +import type { MouseEventHandler } from 'react' +import type { ModalProps } from '@opentrons/components' import type { OddModalHeaderBaseProps, ModalSize, -} from '../../molecules/OddModal/types' -import type { ModalProps } from '@opentrons/components' +} from '/app/molecules/OddModal/types' // Note (07/13/2023) After the launch, we will unify the modal components into one component. // Then TouchScreenModal and DesktopModal will be TouchScreenContent and DesktopContent that only render each content. interface EstopPressedModalProps { isEngaged: boolean closeModal: () => void - isDismissedModal?: boolean - setIsDismissedModal?: (isDismissedModal: boolean) => void + isWaitingForResumeOperation: boolean + setIsWaitingForResumeOperation: () => void } export function EstopPressedModal({ isEngaged, closeModal, - isDismissedModal, - setIsDismissedModal, + isWaitingForResumeOperation, + setIsWaitingForResumeOperation, }: EstopPressedModalProps): JSX.Element { const isOnDevice = useSelector(getIsOnDevice) return createPortal( isOnDevice ? ( - + ) : ( <> - {isDismissedModal === false ? ( - - ) : null} + ), getTopPortalEl() @@ -72,10 +78,19 @@ export function EstopPressedModal({ function TouchscreenModal({ isEngaged, closeModal, + isWaitingForResumeOperation, + setIsWaitingForResumeOperation, }: EstopPressedModalProps): JSX.Element { const { t } = useTranslation(['device_settings', 'branded']) - const [isResuming, setIsResuming] = React.useState(false) + const [isResuming, setIsResuming] = useState(false) const { acknowledgeEstopDisengage } = useAcknowledgeEstopDisengageMutation() + + const { + handlePlaceReaderLid, + isValidPlateReaderMove, + } = usePlacePlateReaderLid({ + onSettled: closeModal, + }) const modalHeader: OddModalHeaderBaseProps = { title: t('estop_pressed'), iconName: 'ot-alert', @@ -87,8 +102,12 @@ function TouchscreenModal({ } const handleClick = (): void => { setIsResuming(true) + setIsWaitingForResumeOperation() acknowledgeEstopDisengage(null) - closeModal() + handlePlaceReaderLid() + if (!isValidPlateReaderMove) { + closeModal() + } } return ( @@ -116,10 +135,14 @@ function TouchscreenModal({
    @@ -130,39 +153,37 @@ function TouchscreenModal({ function DesktopModal({ isEngaged, closeModal, - setIsDismissedModal, + isWaitingForResumeOperation, + setIsWaitingForResumeOperation, }: EstopPressedModalProps): JSX.Element { const { t } = useTranslation('device_settings') - const [isResuming, setIsResuming] = React.useState(false) + const [isResuming, setIsResuming] = useState(false) const { acknowledgeEstopDisengage } = useAcknowledgeEstopDisengageMutation() - - const handleCloseModal = (): void => { - if (setIsDismissedModal != null) { - setIsDismissedModal(true) - } - closeModal() - } + const { + handlePlaceReaderLid, + isValidPlateReaderMove, + } = usePlacePlateReaderLid({ + onSettled: closeModal, + }) const modalProps: ModalProps = { type: 'error', title: t('estop_pressed'), - onClose: handleCloseModal, + onClose: closeModal, closeOnOutsideClick: false, childrenPadding: SPACING.spacing24, width: '47rem', } - const handleClick: React.MouseEventHandler = (e): void => { + const handleClick: MouseEventHandler = (e): void => { e.preventDefault() setIsResuming(true) - acknowledgeEstopDisengage({ - onSuccess: () => { - closeModal() - }, - onError: () => { - setIsResuming(false) - }, - }) + setIsWaitingForResumeOperation() + acknowledgeEstopDisengage(null) + handlePlaceReaderLid() + if (!isValidPlateReaderMove) { + closeModal() + } } return ( @@ -177,14 +198,16 @@ function DesktopModal({ - {isResuming ? : null} + {isResuming || isWaitingForResumeOperation ? ( + + ) : null} {t('resume_robot_operations')} diff --git a/app/src/organisms/EmergencyStop/EstopTakeover.tsx b/app/src/organisms/EmergencyStop/EstopTakeover.tsx index 7c3b07ce062..cbd9ba1a310 100644 --- a/app/src/organisms/EmergencyStop/EstopTakeover.tsx +++ b/app/src/organisms/EmergencyStop/EstopTakeover.tsx @@ -1,71 +1,78 @@ -import * as React from 'react' +import { useEffect, useState } from 'react' import { useSelector } from 'react-redux' import { useEstopQuery } from '@opentrons/react-api-client' import { EstopPressedModal } from './EstopPressedModal' import { EstopMissingModal } from './EstopMissingModal' -import { useEstopContext } from './hooks' -import { useIsUnboxingFlowOngoing } from '../RobotSettingsDashboard/NetworkSettings/hooks' -import { getLocalRobot } from '../../redux/discovery' -import { - PHYSICALLY_ENGAGED, - LOGICALLY_ENGAGED, - NOT_PRESENT, - DISENGAGED, -} from './constants' +import { useIsUnboxingFlowOngoing } from '/app/redux-resources/config' +import { getLocalRobot } from '/app/redux/discovery' +import { PHYSICALLY_ENGAGED, NOT_PRESENT, DISENGAGED } from './constants' +import type { EstopState } from '@opentrons/api-client' -const ESTOP_REFETCH_INTERVAL_MS = 10000 +const ESTOP_CURRENTLY_DISENGAGED_REFETCH_INTERVAL_MS = 10000 +const ESTOP_CURRENTLY_ENGAGED_REFETCH_INTERVAL_MS = 1000 interface EstopTakeoverProps { robotName?: string } export function EstopTakeover({ robotName }: EstopTakeoverProps): JSX.Element { + const [isDismissedModal, setIsDismissedModal] = useState(false) + const [ + isWaitingForResumeOperation, + setIsWatingForResumeOperation, + ] = useState(false) + + const [estopState, setEstopState] = useState() + const [showEmergencyStopModal, setShowEmergencyStopModal] = useState( + false + ) + + // TODO: (ba, 2024-10-24): Use notifications instead of polling const { data: estopStatus } = useEstopQuery({ - refetchInterval: ESTOP_REFETCH_INTERVAL_MS, + refetchInterval: showEmergencyStopModal + ? ESTOP_CURRENTLY_ENGAGED_REFETCH_INTERVAL_MS + : ESTOP_CURRENTLY_DISENGAGED_REFETCH_INTERVAL_MS, }) - const { - isEmergencyStopModalDismissed, - setIsEmergencyStopModalDismissed, - } = useEstopContext() + useEffect(() => { + if (estopStatus) { + setEstopState(estopStatus.data.status) + setShowEmergencyStopModal( + estopStatus.data.status !== DISENGAGED || isWaitingForResumeOperation + ) + } + }, [estopStatus]) + const isUnboxingFlowOngoing = useIsUnboxingFlowOngoing() const closeModal = (): void => { - if (estopStatus?.data.status === DISENGAGED) { - setIsEmergencyStopModalDismissed(false) - } + setIsWatingForResumeOperation(false) } const localRobot = useSelector(getLocalRobot) const localRobotName = localRobot?.name ?? 'no name' const TargetEstopModal = (): JSX.Element | null => { - switch (estopStatus?.data.status) { - case PHYSICALLY_ENGAGED: - case LOGICALLY_ENGAGED: - return ( - - ) - case NOT_PRESENT: - return ( - - ) - default: - return null - } + return estopState === NOT_PRESENT ? ( + + ) : estopState !== DISENGAGED || isWaitingForResumeOperation ? ( + { + setIsWatingForResumeOperation(true) + }} + /> + ) : null } return ( <> - {estopStatus?.data.status !== DISENGAGED && !isUnboxingFlowOngoing ? ( + {showEmergencyStopModal && !isUnboxingFlowOngoing ? ( ) : null} diff --git a/app/src/organisms/EmergencyStop/TouchscreenEstopMissingModal.stories.tsx b/app/src/organisms/EmergencyStop/TouchscreenEstopMissingModal.stories.tsx index 4c91bc65464..7b02f215d42 100644 --- a/app/src/organisms/EmergencyStop/TouchscreenEstopMissingModal.stories.tsx +++ b/app/src/organisms/EmergencyStop/TouchscreenEstopMissingModal.stories.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { Provider } from 'react-redux' import { createStore } from 'redux' import { VIEWPORT } from '@opentrons/components' -import { configReducer } from '../../redux/config/reducer' +import { configReducer } from '/app/redux/config/reducer' import { EstopMissingModal } from '.' import type { Store, StoreEnhancer } from 'redux' diff --git a/app/src/organisms/EmergencyStop/TouchscreenEstopPressedModal.stories.tsx b/app/src/organisms/EmergencyStop/TouchscreenEstopPressedModal.stories.tsx index b78864b74ff..bfc2c920a34 100644 --- a/app/src/organisms/EmergencyStop/TouchscreenEstopPressedModal.stories.tsx +++ b/app/src/organisms/EmergencyStop/TouchscreenEstopPressedModal.stories.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { Provider } from 'react-redux' import { createStore } from 'redux' import { QueryClient, QueryClientProvider } from 'react-query' import { VIEWPORT } from '@opentrons/components' -import { configReducer } from '../../redux/config/reducer' +import { configReducer } from '/app/redux/config/reducer' import { EstopPressedModal } from '.' import type { Store, StoreEnhancer } from 'redux' diff --git a/app/src/organisms/EmergencyStop/__tests__/EstopMissingModal.test.tsx b/app/src/organisms/EmergencyStop/__tests__/EstopMissingModal.test.tsx index 0602fcbf4ac..c2ce7cea0e1 100644 --- a/app/src/organisms/EmergencyStop/__tests__/EstopMissingModal.test.tsx +++ b/app/src/organisms/EmergencyStop/__tests__/EstopMissingModal.test.tsx @@ -1,14 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' import { describe, it, vi, beforeEach, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../i18n' -import { getIsOnDevice } from '../../../redux/config' +import { i18n } from '/app/i18n' +import { getIsOnDevice } from '/app/redux/config' import { EstopMissingModal } from '../EstopMissingModal' -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/EmergencyStop/__tests__/EstopPressedModal.test.tsx b/app/src/organisms/EmergencyStop/__tests__/EstopPressedModal.test.tsx index 4a530858afe..067211c4c06 100644 --- a/app/src/organisms/EmergencyStop/__tests__/EstopPressedModal.test.tsx +++ b/app/src/organisms/EmergencyStop/__tests__/EstopPressedModal.test.tsx @@ -1,16 +1,18 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' import { describe, it, vi, beforeEach, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useAcknowledgeEstopDisengageMutation } from '@opentrons/react-api-client' -import { i18n } from '../../../i18n' -import { getIsOnDevice } from '../../../redux/config' +import { i18n } from '/app/i18n' +import { getIsOnDevice } from '/app/redux/config' import { EstopPressedModal } from '../EstopPressedModal' +import { usePlacePlateReaderLid } from '/app/resources/modules' vi.mock('@opentrons/react-api-client') -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') +vi.mock('/app/resources/modules') const render = (props: React.ComponentProps) => { return renderWithProviders(, { @@ -25,11 +27,19 @@ describe('EstopPressedModal - Touchscreen', () => { props = { isEngaged: true, closeModal: vi.fn(), + isWaitingForResumeOperation: false, + setIsWaitingForResumeOperation: vi.fn(), } vi.mocked(getIsOnDevice).mockReturnValue(true) vi.mocked(useAcknowledgeEstopDisengageMutation).mockReturnValue({ setEstopPhysicalStatus: vi.fn(), } as any) + + vi.mocked(usePlacePlateReaderLid).mockReturnValue({ + handlePlaceReaderLid: vi.fn(), + isValidPlateReaderMove: false, + isExecuting: false, + }) }) it('should render text and button', () => { @@ -57,6 +67,20 @@ describe('EstopPressedModal - Touchscreen', () => { render(props) fireEvent.click(screen.getByText('Resume robot operations')) expect(useAcknowledgeEstopDisengageMutation).toHaveBeenCalled() + expect(usePlacePlateReaderLid).toHaveBeenCalled() + }) + + it('should call a mock function to place the labware to a slot', () => { + vi.mocked(usePlacePlateReaderLid).mockReturnValue({ + handlePlaceReaderLid: vi.fn(), + isValidPlateReaderMove: true, + isExecuting: true, + }) + + render(props) + fireEvent.click(screen.getByText('Resume robot operations')) + expect(useAcknowledgeEstopDisengageMutation).toHaveBeenCalled() + expect(usePlacePlateReaderLid).toHaveBeenCalled() }) }) @@ -67,13 +91,19 @@ describe('EstopPressedModal - Desktop', () => { props = { isEngaged: true, closeModal: vi.fn(), - isDismissedModal: false, - setIsDismissedModal: vi.fn(), + isWaitingForResumeOperation: false, + setIsWaitingForResumeOperation: vi.fn(), } vi.mocked(getIsOnDevice).mockReturnValue(false) vi.mocked(useAcknowledgeEstopDisengageMutation).mockReturnValue({ setEstopPhysicalStatus: vi.fn(), } as any) + + vi.mocked(usePlacePlateReaderLid).mockReturnValue({ + handlePlaceReaderLid: vi.fn(), + isValidPlateReaderMove: false, + isExecuting: false, + }) }) it('should render text and button', () => { render(props) @@ -95,10 +125,18 @@ describe('EstopPressedModal - Desktop', () => { ).not.toBeDisabled() }) + it('should resume robot operation button is disabled when waiting for labware plate to finish', () => { + props.isEngaged = false + props.isWaitingForResumeOperation = true + render(props) + expect( + screen.getByRole('button', { name: 'Resume robot operations' }) + ).toBeDisabled() + }) + it('should call a mock function when clicking close icon', () => { render(props) fireEvent.click(screen.getByTestId('ModalHeader_icon_close_E-stop pressed')) - expect(props.setIsDismissedModal).toHaveBeenCalled() expect(props.closeModal).toHaveBeenCalled() }) diff --git a/app/src/organisms/EmergencyStop/__tests__/EstopTakeover.test.tsx b/app/src/organisms/EmergencyStop/__tests__/EstopTakeover.test.tsx index 7e31c8fa54f..3ff0503dc69 100644 --- a/app/src/organisms/EmergencyStop/__tests__/EstopTakeover.test.tsx +++ b/app/src/organisms/EmergencyStop/__tests__/EstopTakeover.test.tsx @@ -1,29 +1,29 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, beforeEach, expect, vi } from 'vitest' import { screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useEstopQuery } from '@opentrons/react-api-client' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { EstopMissingModal } from '../EstopMissingModal' import { EstopPressedModal } from '../EstopPressedModal' -import { useIsUnboxingFlowOngoing } from '../../RobotSettingsDashboard/NetworkSettings/hooks' +import { useIsUnboxingFlowOngoing } from '/app/redux-resources/config' import { ENGAGED, LOGICALLY_ENGAGED, NOT_PRESENT, PHYSICALLY_ENGAGED, } from '../constants' -import { getLocalRobot } from '../../../redux/discovery' -import { mockConnectedRobot } from '../../../redux/discovery/__fixtures__' +import { getLocalRobot } from '/app/redux/discovery' +import { mockConnectedRobot } from '/app/redux/discovery/__fixtures__' import { EstopTakeover } from '../EstopTakeover' vi.mock('@opentrons/react-api-client') vi.mock('../EstopMissingModal') vi.mock('../EstopPressedModal') -vi.mock('../../RobotSettingsDashboard/NetworkSettings/hooks') -vi.mock('../../../redux/discovery') +vi.mock('/app/redux-resources/config') +vi.mock('/app/redux/discovery') const mockPressed = { data: { diff --git a/app/src/organisms/EmergencyStop/hooks.ts b/app/src/organisms/EmergencyStop/hooks.ts index 6ded6e100ad..775903e0dc9 100644 --- a/app/src/organisms/EmergencyStop/hooks.ts +++ b/app/src/organisms/EmergencyStop/hooks.ts @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useContext } from 'react' import { EmergencyStopContext } from './EmergencyStopContext' import type { EmergencyStopContextType } from './EmergencyStopContext' @@ -7,7 +7,7 @@ export function useEstopContext(): EmergencyStopContextType { const { isEmergencyStopModalDismissed, setIsEmergencyStopModalDismissed, - } = React.useContext(EmergencyStopContext) + } = useContext(EmergencyStopContext) return { isEmergencyStopModalDismissed, diff --git a/app/src/organisms/ErrorRecoveryBanner/index.tsx b/app/src/organisms/ErrorRecoveryBanner/index.tsx deleted file mode 100644 index 504cf2fc979..00000000000 --- a/app/src/organisms/ErrorRecoveryBanner/index.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' - -import { - Flex, - DIRECTION_COLUMN, - SPACING, - StyledText, -} from '@opentrons/components' - -import { getUserId } from '../../redux/config' -import { useClientDataRecovery } from '../../resources/client_data' -import { Banner } from '../../atoms/Banner' - -import type { RecoveryIntent } from '../../resources/client_data' -import type { StyleProps } from '@opentrons/components' - -const CLIENT_DATA_INTERVAL_MS = 5000 - -export interface UseErrorRecoveryBannerResult { - showRecoveryBanner: boolean - recoveryIntent: RecoveryIntent -} - -export function useErrorRecoveryBanner(): UseErrorRecoveryBannerResult { - const { userId, intent } = useClientDataRecovery({ - refetchInterval: CLIENT_DATA_INTERVAL_MS, - }) - const thisUserId = useSelector(getUserId) - - return { - showRecoveryBanner: userId !== null && thisUserId !== userId, - recoveryIntent: intent ?? 'recovering', - } -} - -export interface ErrorRecoveryBannerProps extends StyleProps { - recoveryIntent: RecoveryIntent -} - -export function ErrorRecoveryBanner({ - recoveryIntent, - ...styleProps -}: ErrorRecoveryBannerProps): JSX.Element { - const { t } = useTranslation(['error_recovery', 'shared']) - - const buildTitleText = (): string => { - switch (recoveryIntent) { - case 'canceling': - return t('robot_is_canceling_run') - case 'recovering': - default: - return t('robot_is_in_recovery_mode') - } - } - - return ( - - - - {buildTitleText()} - - - - {t('another_app_controlling_robot')} - - - - - ) -} diff --git a/app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx b/app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx index 7f982f5415e..bd52195faf8 100644 --- a/app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/ErrorRecoveryWizard.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' -import { StyledText } from '@opentrons/components' +import { CURSOR_POINTER, StyledText } from '@opentrons/components' import { RecoveryError } from './RecoveryError' import { RecoveryDoorOpen } from './RecoveryDoorOpen' @@ -17,24 +17,25 @@ import { SkipStepSameTips, SkipStepNewTips, IgnoreErrorSkipStep, + ManualMoveLwAndSkip, + ManualReplaceLwAndRetry, + HomeAndRetry, } from './RecoveryOptions' import { useErrorDetailsModal, ErrorDetailsModal, RecoveryInterventionModal, + RecoveryDoorOpenSpecial, } from './shared' import { RecoveryInProgress } from './RecoveryInProgress' import { getErrorKind } from './utils' import { RECOVERY_MAP } from './constants' -import type { RobotType } from '@opentrons/shared-data' -import type { RecoveryContentProps } from './types' -import type { - ERUtilsResults, - UseRecoveryAnalyticsResult, - useRetainedFailedCommandBySource, -} from './hooks' +import type { LabwareDefinition2, RobotType } from '@opentrons/shared-data' +import type { RecoveryRoute, RouteStep, RecoveryContentProps } from './types' import type { ErrorRecoveryFlowsProps } from '.' +import type { UseRecoveryAnalyticsResult } from '/app/redux-resources/analytics' +import type { ERUtilsResults, useRetainedFailedCommandBySource } from './hooks' export interface UseERWizardResult { hasLaunchedRecovery: boolean @@ -43,11 +44,11 @@ export interface UseERWizardResult { } export function useERWizard(): UseERWizardResult { - const [showERWizard, setShowERWizard] = React.useState(false) + const [showERWizard, setShowERWizard] = useState(false) // Because RunPausedSplash has access to some ER Wiz routes but is not a part of the ER wizard, the splash screen // is the "home" route as opposed to SelectRecoveryOption (accessed by pressing "go back" or "continue" enough times) // when recovery mode has not been launched. - const [hasLaunchedRecovery, setHasLaunchedRecovery] = React.useState(false) + const [hasLaunchedRecovery, setHasLaunchedRecovery] = useState(false) const toggleERWizard = ( isActive: boolean, @@ -67,29 +68,20 @@ export type ErrorRecoveryWizardProps = ErrorRecoveryFlowsProps & ERUtilsResults & { robotType: RobotType isOnDevice: boolean - isDoorOpen: boolean - analytics: UseRecoveryAnalyticsResult + analytics: UseRecoveryAnalyticsResult failedCommand: ReturnType + allRunDefs: LabwareDefinition2[] } export function ErrorRecoveryWizard( props: ErrorRecoveryWizardProps ): JSX.Element { - const { - hasLaunchedRecovery, - failedCommand, - recoveryCommands, - routeUpdateActions, - } = props - const errorKind = getErrorKind(failedCommand?.byRunRecord ?? null) - - useInitialPipetteHome({ - hasLaunchedRecovery, - recoveryCommands, - routeUpdateActions, - }) - - return + return ( + + ) } export function ErrorRecoveryComponent( @@ -98,15 +90,16 @@ export function ErrorRecoveryComponent( const { recoveryMap, hasLaunchedRecovery, - isDoorOpen, + doorStatusUtils, isOnDevice, analytics, } = props + const { isProhibitedDoorOpen } = doorStatusUtils const { route, step } = recoveryMap const { t } = useTranslation('error_recovery') const { showModal, toggleModal } = useErrorDetailsModal() - React.useEffect(() => { + useEffect(() => { if (showModal) { analytics.reportViewErrorDetailsEvent(route, step) } @@ -115,10 +108,7 @@ export function ErrorRecoveryComponent( const buildTitleHeading = (): JSX.Element => { const titleText = hasLaunchedRecovery ? t('recovery_mode') : t('cancel_run') return ( - + {titleText} ) @@ -129,16 +119,15 @@ export function ErrorRecoveryComponent( oddStyle="bodyTextSemiBold" desktopStyle="bodyDefaultSemiBold" css={css` - cursor: pointer; + cursor: ${CURSOR_POINTER}; `} > {t('view_error_details')} ) - // TODO(jh, 07-29-24): Make RecoveryDoorOpen render logic equivalent to RecoveryTakeover. Do not nest it in RecoveryWizard. const buildInterventionContent = (): JSX.Element => { - if (isDoorOpen) { + if (isProhibitedDoorOpen) { return } else { return @@ -146,7 +135,7 @@ export function ErrorRecoveryComponent( } const isLargeDesktopStyle = - !isDoorOpen && + !isProhibitedDoorOpen && route === RECOVERY_MAP.DROP_TIP_FLOWS.ROUTE && step !== RECOVERY_MAP.DROP_TIP_FLOWS.STEPS.BEGIN_REMOVAL const desktopType = isLargeDesktopStyle ? 'desktop-large' : 'desktop-small' @@ -185,7 +174,7 @@ export function ErrorRecoveryContent(props: RecoveryContentProps): JSX.Element { return } - const buildResumeRun = (): JSX.Element => { + const buildRetryStep = (): JSX.Element => { return } @@ -221,13 +210,33 @@ export function ErrorRecoveryContent(props: RecoveryContentProps): JSX.Element { return } + const buildManualMoveLwAndSkip = (): JSX.Element => { + return + } + + const buildManualReplaceLwAndRetry = (): JSX.Element => { + return + } + + const buildManuallyRouteToDoorOpen = (): JSX.Element => { + return + } + + const buildRecoveryDoorOpenSpecial = (): JSX.Element => { + return + } + + const buildHomeAndRetry = (): JSX.Element => { + return + } + switch (props.recoveryMap.route) { case RECOVERY_MAP.OPTION_SELECTION.ROUTE: return buildSelectRecoveryOption() case RECOVERY_MAP.ERROR_WHILE_RECOVERING.ROUTE: return buildRecoveryError() - case RECOVERY_MAP.RETRY_FAILED_COMMAND.ROUTE: - return buildResumeRun() + case RECOVERY_MAP.RETRY_STEP.ROUTE: + return buildRetryStep() case RECOVERY_MAP.CANCEL_RUN.ROUTE: return buildCancelRun() case RECOVERY_MAP.DROP_TIP_FLOWS.ROUTE: @@ -236,7 +245,7 @@ export function ErrorRecoveryContent(props: RecoveryContentProps): JSX.Element { return buildRetryNewTips() case RECOVERY_MAP.RETRY_SAME_TIPS.ROUTE: return buildRetrySameTips() - case RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.ROUTE: + case RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE: return buildFillWellAndSkip() case RECOVERY_MAP.SKIP_STEP_WITH_SAME_TIPS.ROUTE: return buildSkipStepSameTips() @@ -244,37 +253,25 @@ export function ErrorRecoveryContent(props: RecoveryContentProps): JSX.Element { return buildSkipStepNewTips() case RECOVERY_MAP.IGNORE_AND_SKIP.ROUTE: return buildIgnoreErrorSkipStep() + case RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE: + return buildManualMoveLwAndSkip() + case RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE: + return buildManualReplaceLwAndRetry() + case RECOVERY_MAP.ROBOT_DOOR_OPEN_SPECIAL.ROUTE: + return buildRecoveryDoorOpenSpecial() case RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE: case RECOVERY_MAP.ROBOT_RESUMING.ROUTE: case RECOVERY_MAP.ROBOT_RETRYING_STEP.ROUTE: case RECOVERY_MAP.ROBOT_CANCELING.ROUTE: case RECOVERY_MAP.ROBOT_PICKING_UP_TIPS.ROUTE: case RECOVERY_MAP.ROBOT_SKIPPING_STEP.ROUTE: + case RECOVERY_MAP.ROBOT_RELEASING_LABWARE.ROUTE: return buildRecoveryInProgress() + case RECOVERY_MAP.ROBOT_DOOR_OPEN.ROUTE: + return buildManuallyRouteToDoorOpen() + case RECOVERY_MAP.HOME_AND_RETRY.ROUTE: + return buildHomeAndRetry() default: return buildSelectRecoveryOption() } } -interface UseInitialPipetteHomeParams { - hasLaunchedRecovery: ErrorRecoveryWizardProps['hasLaunchedRecovery'] - recoveryCommands: ErrorRecoveryWizardProps['recoveryCommands'] - routeUpdateActions: ErrorRecoveryWizardProps['routeUpdateActions'] -} -// Home the Z-axis of all attached pipettes on Error Recovery launch. -export function useInitialPipetteHome({ - hasLaunchedRecovery, - recoveryCommands, - routeUpdateActions, -}: UseInitialPipetteHomeParams): void { - const { homePipetteZAxes } = recoveryCommands - const { setRobotInMotion } = routeUpdateActions - - // Synchronously set the recovery route to "robot in motion" before initial render to prevent screen flicker on ER launch. - React.useLayoutEffect(() => { - if (hasLaunchedRecovery) { - void setRobotInMotion(true) - .then(() => homePipetteZAxes()) - .finally(() => setRobotInMotion(false)) - } - }, [hasLaunchedRecovery]) -} diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryDoorOpen.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryDoorOpen.tsx index 17bf1cf0379..3aa1b0f7b74 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryDoorOpen.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryDoorOpen.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { css } from 'styled-components' import { useTranslation } from 'react-i18next' @@ -23,16 +22,33 @@ import { import type { RecoveryContentProps } from './types' +// There are two code paths that render this component: +// 1) The door is open on a route & step in which it is not permitted to have the door open. +// 2) The door is open on a route & step in which it is permitted to have the door open, but the app manually redirects +// to this component. This is commonly done when the route & step itself allows the user to keep the door open, but some +// action on that route & step is about to occur that requires the door to be closed. In this case, once the door event +// has been satisfied, manually route back to the previous route & step. export function RecoveryDoorOpen({ recoveryActionMutationUtils, runStatus, + routeUpdateActions, }: RecoveryContentProps): JSX.Element { const { resumeRecovery, isResumeRecoveryLoading, } = recoveryActionMutationUtils + const { stashedMap, proceedToRouteAndStep } = routeUpdateActions const { t } = useTranslation('error_recovery') + const primaryOnClick = (): void => { + void resumeRecovery().then(() => { + // See comments above for why we do this. + if (stashedMap != null) { + void proceedToRouteAndStep(stashedMap.route, stashedMap.step) + } + }) + } + return ( { - void setRobotInMotion(true) + void handleMotionRouting(true) .then(() => homePipetteZAxes()) - .finally(() => setRobotInMotion(false)) + .finally(() => handleMotionRouting(false)) .then(() => proceedToRouteAndStep(OPTION_SELECTION.ROUTE)) } @@ -88,6 +91,8 @@ export function RecoveryDropTipFlowErrors({ currentRecoveryOptionUtils, routeUpdateActions, getRecoveryOptionCopy, + errorKind, + subMapUtils, }: RecoveryContentProps): JSX.Element { const { t } = useTranslation('error_recovery') const { step } = recoveryMap @@ -99,7 +104,13 @@ export function RecoveryDropTipFlowErrors({ const { selectedRecoveryOption } = currentRecoveryOptionUtils const { proceedToRouteAndStep } = routeUpdateActions - const userRecoveryOptionCopy = getRecoveryOptionCopy(selectedRecoveryOption) + const userRecoveryOptionCopy = getRecoveryOptionCopy( + selectedRecoveryOption, + errorKind + ) + + // Whenever there is an error during drop tip wizard, reset the submap so properly re-entry routing occurs. + subMapUtils.updateSubMap(null) const buildTitle = (): string => { switch (step) { diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryInProgress.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryInProgress.tsx index 0f3f3fdb227..3353c9d4b05 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryInProgress.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryInProgress.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' @@ -12,12 +12,16 @@ import { SPACING, } from '@opentrons/components' -import { InProgressModal } from '../../molecules/InProgressModal' +import { InProgressModal } from '/app/molecules/InProgressModal' import type { RobotMovingRoute, RecoveryContentProps } from './types' export function RecoveryInProgress({ recoveryMap, + recoveryCommands, + routeUpdateActions, + doorStatusUtils, + currentRecoveryOptionUtils, }: RecoveryContentProps): JSX.Element { const { ROBOT_CANCELING, @@ -26,10 +30,19 @@ export function RecoveryInProgress({ ROBOT_RETRYING_STEP, ROBOT_PICKING_UP_TIPS, ROBOT_SKIPPING_STEP, + ROBOT_RELEASING_LABWARE, } = RECOVERY_MAP const { t } = useTranslation('error_recovery') const { route } = recoveryMap + const gripperReleaseCountdown = useGripperRelease({ + recoveryMap, + recoveryCommands, + routeUpdateActions, + doorStatusUtils, + currentRecoveryOptionUtils, + }) + const buildDescription = (): RobotMovingRoute => { switch (route) { case ROBOT_CANCELING.ROUTE: @@ -44,6 +57,15 @@ export function RecoveryInProgress({ return t('stand_back_picking_up_tips') case ROBOT_SKIPPING_STEP.ROUTE: return t('stand_back_skipping_to_next_step') + case ROBOT_RELEASING_LABWARE.ROUTE: { + if (gripperReleaseCountdown > 0) { + return t('gripper_will_release_in_s', { + seconds: gripperReleaseCountdown, + }) + } else { + return t('gripper_releasing_labware') + } + } default: return t('stand_back') } @@ -58,6 +80,122 @@ export function RecoveryInProgress({ ) } +export const GRIPPER_RELEASE_COUNTDOWN_S = 3 + +type UseGripperReleaseProps = Pick< + RecoveryContentProps, + | 'currentRecoveryOptionUtils' + | 'recoveryCommands' + | 'routeUpdateActions' + | 'doorStatusUtils' + | 'recoveryMap' +> + +// Handles the gripper release copy and action, which operates on an interval. At T=0, release the labware then proceed +// to the next step in the active route if the door is open (which should be a route to handle the door), or to the next +// CTA route if the door is closed. +export function useGripperRelease({ + currentRecoveryOptionUtils, + recoveryCommands, + routeUpdateActions, + doorStatusUtils, + recoveryMap, +}: UseGripperReleaseProps): number { + const { releaseGripperJaws, homeExceptPlungers } = recoveryCommands + const { selectedRecoveryOption } = currentRecoveryOptionUtils + const { + proceedToRouteAndStep, + proceedNextStep, + handleMotionRouting, + } = routeUpdateActions + const { isDoorOpen } = doorStatusUtils + const { MANUAL_MOVE_AND_SKIP, MANUAL_REPLACE_AND_RETRY } = RECOVERY_MAP + const [countdown, setCountdown] = useState(GRIPPER_RELEASE_COUNTDOWN_S) + + const proceedToDoorStep = (): void => { + switch (selectedRecoveryOption) { + case MANUAL_MOVE_AND_SKIP.ROUTE: + void proceedToRouteAndStep( + MANUAL_MOVE_AND_SKIP.ROUTE, + MANUAL_MOVE_AND_SKIP.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME + ) + break + case MANUAL_REPLACE_AND_RETRY.ROUTE: + void proceedToRouteAndStep( + MANUAL_REPLACE_AND_RETRY.ROUTE, + MANUAL_REPLACE_AND_RETRY.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME + ) + break + default: { + console.error('Unhandled post grip-release routing when door is open.') + void proceedToRouteAndStep(RECOVERY_MAP.OPTION_SELECTION.ROUTE) + } + } + } + + const proceedToValidNextStep = (): void => { + switch (selectedRecoveryOption) { + case MANUAL_MOVE_AND_SKIP.ROUTE: + void proceedToRouteAndStep( + MANUAL_MOVE_AND_SKIP.ROUTE, + MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE + ) + break + case MANUAL_REPLACE_AND_RETRY.ROUTE: + void proceedToRouteAndStep( + MANUAL_REPLACE_AND_RETRY.ROUTE, + MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE + ) + break + default: + console.error('Unhandled post grip-release routing.') + void proceedNextStep() + } + } + + useEffect(() => { + let intervalId: NodeJS.Timeout | null = null + + if (recoveryMap.route === RECOVERY_MAP.ROBOT_RELEASING_LABWARE.ROUTE) { + intervalId = setInterval(() => { + setCountdown(prevCountdown => { + const updatedCountdown = prevCountdown - 1 + + if (updatedCountdown === 0) { + if (intervalId != null) { + clearInterval(intervalId) + } + + void releaseGripperJaws().then(() => { + if (isDoorOpen) { + return handleMotionRouting(false).then(() => { + proceedToDoorStep() + }) + } + + return handleMotionRouting(true) + .then(() => homeExceptPlungers()) + .then(() => handleMotionRouting(false)) + .then(() => { + proceedToValidNextStep() + }) + }) + } + + return updatedCountdown + }) + }, 1000) + } + + return () => { + if (intervalId != null) { + clearInterval(intervalId) + } + } + }, [recoveryMap.route]) + + return countdown +} const CONTAINER_STYLE = css` align-items: ${ALIGN_CENTER}; justify-content: ${JUSTIFY_CENTER}; diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/CancelRun.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/CancelRun.tsx index 8f49634a3ad..fa66d614011 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/CancelRun.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/CancelRun.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { @@ -23,11 +23,7 @@ import { import { SelectRecoveryOption } from './SelectRecoveryOption' import type { RecoveryContentProps } from '../types' -import type { - RecoveryTipStatusUtils, - UseRecoveryCommandsResult, - UseRouteUpdateActionsResult, -} from '../hooks' +import type { ERUtilsResults } from '../hooks' export function CancelRun(props: RecoveryContentProps): JSX.Element { const { recoveryMap } = props @@ -39,7 +35,9 @@ export function CancelRun(props: RecoveryContentProps): JSX.Element { case CANCEL_RUN.STEPS.CONFIRM_CANCEL: return default: - console.warn(`${step} in ${route} not explicitly handled. Rerouting.`) + console.warn( + `CancelRun: ${step} in ${route} not explicitly handled. Rerouting.` + ) return } } @@ -104,9 +102,9 @@ function CancelRunConfirmation({ } interface OnCancelRunProps { - tipStatusUtils: RecoveryTipStatusUtils - recoveryCommands: UseRecoveryCommandsResult - routeUpdateActions: UseRouteUpdateActionsResult + tipStatusUtils: ERUtilsResults['tipStatusUtils'] + recoveryCommands: ERUtilsResults['recoveryCommands'] + routeUpdateActions: ERUtilsResults['routeUpdateActions'] } // Manages routing to cancel route or drop tip route, depending on tip attachment status. @@ -122,20 +120,20 @@ export function useOnCancelRun({ } { const { ROBOT_CANCELING, DROP_TIP_FLOWS } = RECOVERY_MAP const { isLoadingTipStatus, areTipsAttached } = tipStatusUtils - const { setRobotInMotion, proceedToRouteAndStep } = routeUpdateActions + const { handleMotionRouting, proceedToRouteAndStep } = routeUpdateActions const { cancelRun } = recoveryCommands - const [hasUserClicked, setHasUserClicked] = React.useState(false) + const [hasUserClicked, setHasUserClicked] = useState(false) const showBtnLoadingState = hasUserClicked && isLoadingTipStatus - React.useEffect(() => { + useEffect(() => { if (hasUserClicked) { if (!isLoadingTipStatus) { if (areTipsAttached) { void proceedToRouteAndStep(DROP_TIP_FLOWS.ROUTE) } else { - void setRobotInMotion(true, ROBOT_CANCELING.ROUTE).then(() => { + void handleMotionRouting(true, ROBOT_CANCELING.ROUTE).then(() => { cancelRun() }) } diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/FillWellAndSkip.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/FillWellAndSkip.tsx index fab5d36f8eb..d01ea7dfe4e 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/FillWellAndSkip.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/FillWellAndSkip.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import { @@ -16,7 +15,7 @@ import { LeftColumnLabwareInfo, TwoColTextAndFailedStepNextStep, } from '../shared' -import { TwoColumn, DeckMapContent } from '../../../molecules/InterventionModal' +import { TwoColumn, DeckMapContent } from '/app/molecules/InterventionModal' import { SelectRecoveryOption } from './SelectRecoveryOption' import type { RecoveryContentProps } from '../types' @@ -24,18 +23,20 @@ import type { RecoveryContentProps } from '../types' export function FillWellAndSkip(props: RecoveryContentProps): JSX.Element { const { recoveryMap } = props const { step, route } = recoveryMap - const { FILL_MANUALLY_AND_SKIP, CANCEL_RUN } = RECOVERY_MAP + const { MANUAL_FILL_AND_SKIP, CANCEL_RUN } = RECOVERY_MAP const buildContent = (): JSX.Element => { switch (step) { - case FILL_MANUALLY_AND_SKIP.STEPS.MANUALLY_FILL: + case MANUAL_FILL_AND_SKIP.STEPS.MANUAL_FILL: return - case FILL_MANUALLY_AND_SKIP.STEPS.SKIP: + case MANUAL_FILL_AND_SKIP.STEPS.SKIP: return case CANCEL_RUN.STEPS.CONFIRM_CANCEL: return default: - console.warn(`${step} in ${route} not explicitly handled. Rerouting.`) + console.warn( + `FillWellAndSkip: ${step} in ${route} not explicitly handled. Rerouting.` + ) return } } @@ -81,7 +82,7 @@ export function SkipToNextStep( currentRecoveryOptionUtils, } = props const { - setRobotInMotion, + handleMotionRouting, goBackPrevStep, proceedToRouteAndStep, } = routeUpdateActions @@ -90,7 +91,6 @@ export function SkipToNextStep( const { ROBOT_SKIPPING_STEP, IGNORE_AND_SKIP } = RECOVERY_MAP const { t } = useTranslation('error_recovery') - // TODO(jh, 06-18-24): EXEC-569 const secondaryBtnOnClick = (): void => { if (selectedRecoveryOption === IGNORE_AND_SKIP.ROUTE) { void proceedToRouteAndStep(IGNORE_AND_SKIP.ROUTE) @@ -100,7 +100,7 @@ export function SkipToNextStep( } const primaryBtnOnClick = (): Promise => { - return setRobotInMotion(true, ROBOT_SKIPPING_STEP.ROUTE).then(() => { + return handleMotionRouting(true, ROBOT_SKIPPING_STEP.ROUTE).then(() => { skipFailedCommand() }) } diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/HomeAndRetry.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/HomeAndRetry.tsx new file mode 100644 index 00000000000..00ebdfb35ee --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/HomeAndRetry.tsx @@ -0,0 +1,147 @@ +import { Trans, useTranslation } from 'react-i18next' +import { LegacyStyledText } from '@opentrons/components' +import { RECOVERY_MAP } from '../constants' +import { + TwoColTextAndFailedStepNextStep, + TwoColLwInfoAndDeck, + SelectTips, + RecoveryDoorOpenSpecial, + RetryStepInfo, +} from '../shared' +import { ManageTips } from './ManageTips' +import { SelectRecoveryOption } from './SelectRecoveryOption' + +import type { RecoveryContentProps } from '../types' + +const { HOME_AND_RETRY } = RECOVERY_MAP +export function HomeAndRetry(props: RecoveryContentProps): JSX.Element { + const { recoveryMap } = props + const { route, step } = recoveryMap + switch (step) { + case HOME_AND_RETRY.STEPS.PREPARE_DECK_FOR_HOME: { + return + } + case HOME_AND_RETRY.STEPS.REMOVE_TIPS_FROM_PIPETTE: { + // TODO: Make this work the same way as e.g. RetryNewTips by changing one of them. Or both of them. + return + } + case HOME_AND_RETRY.STEPS.REPLACE_TIPS: { + return + } + case HOME_AND_RETRY.STEPS.SELECT_TIPS: { + return + } + case HOME_AND_RETRY.STEPS.HOME_BEFORE_RETRY: { + return + } + case HOME_AND_RETRY.STEPS.CLOSE_DOOR_AND_HOME: { + return + } + case HOME_AND_RETRY.STEPS.CONFIRM_RETRY: { + return + } + default: + console.warn( + `HomeAndRetry: ${step} in ${route} not explicitly handled. Rerouting.}` + ) + return + } +} + +export function RetryAfterHome(props: RecoveryContentProps): JSX.Element { + const { recoveryMap, routeUpdateActions } = props + const { step, route } = recoveryMap + const { HOME_AND_RETRY } = RECOVERY_MAP + const { proceedToRouteAndStep } = routeUpdateActions + + const buildContent = (): JSX.Element => { + switch (step) { + case HOME_AND_RETRY.STEPS.CONFIRM_RETRY: + return ( + + proceedToRouteAndStep( + HOME_AND_RETRY.ROUTE, + HOME_AND_RETRY.STEPS.HOME_BEFORE_RETRY + ) + } + /> + ) + default: + console.warn( + `RetryStep: ${step} in ${route} not explicitly handled. Rerouting.` + ) + return + } + } + return buildContent() +} + +export function PrepareDeckForHome(props: RecoveryContentProps): JSX.Element { + const { t } = useTranslation('error_recovery') + const { routeUpdateActions, tipStatusUtils } = props + const { proceedToRouteAndStep } = routeUpdateActions + const primaryBtnOnClick = (): Promise => + proceedToRouteAndStep( + RECOVERY_MAP.HOME_AND_RETRY.ROUTE, + tipStatusUtils.areTipsAttached + ? RECOVERY_MAP.HOME_AND_RETRY.STEPS.REMOVE_TIPS_FROM_PIPETTE + : RECOVERY_MAP.HOME_AND_RETRY.STEPS.HOME_BEFORE_RETRY + ) + const buildBodyText = (): JSX.Element => ( + }} + /> + ) + return ( + + ) +} + +export function HomeGantryBeforeRetry( + props: RecoveryContentProps +): JSX.Element { + const { t } = useTranslation('error_recovery') + const { routeUpdateActions, tipStatusUtils } = props + const { proceedToRouteAndStep } = routeUpdateActions + const { HOME_AND_RETRY } = RECOVERY_MAP + const buildBodyText = (): JSX.Element => ( + }} + /> + ) + const secondaryBtnOnClick = (): Promise => + proceedToRouteAndStep( + RECOVERY_MAP.HOME_AND_RETRY.ROUTE, + tipStatusUtils.areTipsAttached + ? RECOVERY_MAP.HOME_AND_RETRY.STEPS.REMOVE_TIPS_FROM_PIPETTE + : RECOVERY_MAP.HOME_AND_RETRY.STEPS.PREPARE_DECK_FOR_HOME + ) + + const primaryBtnOnClick = (): Promise => + proceedToRouteAndStep( + HOME_AND_RETRY.ROUTE, + HOME_AND_RETRY.STEPS.CLOSE_DOOR_AND_HOME + ) + return ( + + ) +} diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/IgnoreErrorSkipStep.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/IgnoreErrorSkipStep.tsx index 1c537ccce8a..16cb755d4da 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/IgnoreErrorSkipStep.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/IgnoreErrorSkipStep.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import head from 'lodash/head' import { css } from 'styled-components' import { useTranslation } from 'react-i18next' @@ -17,12 +17,14 @@ import { RECOVERY_MAP, ODD_ONLY, DESKTOP_ONLY, + ERROR_KINDS, } from '../constants' import { SelectRecoveryOption } from './SelectRecoveryOption' import { RecoveryFooterButtons, RecoverySingleColumnContentWrapper, RecoveryRadioGroup, + SkipStepInfo, } from '../shared' import type { RecoveryContentProps } from '../types' @@ -36,8 +38,12 @@ export function IgnoreErrorSkipStep(props: RecoveryContentProps): JSX.Element { switch (step) { case IGNORE_AND_SKIP.STEPS.SELECT_IGNORE_KIND: return + case IGNORE_AND_SKIP.STEPS.SKIP_STEP: + return default: - console.warn(`${step} in ${route} not explicitly handled. Rerouting.`) + console.warn( + `IgnoreErrorAndSkipStep: ${step} in ${route} not explicitly handled. Rerouting.` + ) return } } @@ -48,34 +54,56 @@ export function IgnoreErrorSkipStep(props: RecoveryContentProps): JSX.Element { export function IgnoreErrorStepHome({ recoveryCommands, routeUpdateActions, + errorKind, }: RecoveryContentProps): JSX.Element | null { const { t } = useTranslation('error_recovery') - const { FILL_MANUALLY_AND_SKIP } = RECOVERY_MAP const { ignoreErrorKindThisRun } = recoveryCommands - const { proceedToRouteAndStep, goBackPrevStep } = routeUpdateActions + const { + proceedNextStep, + proceedToRouteAndStep, + goBackPrevStep, + } = routeUpdateActions - const [selectedOption, setSelectedOption] = React.useState( + const [selectedOption, setSelectedOption] = useState( head(IGNORE_OPTIONS_IN_ORDER) as IgnoreOption ) - // It's safe to hard code the routing here, since only one route currently - // utilizes ignoring. In the future, we may have to check the selectedRecoveryOption - // and route appropriately. + // Reset client choice to ignore all errors whenever navigating back to this view. This prevents unexpected + // behavior after pressing "go back" and ending up on this screen. + useEffect(() => { + void ignoreErrorKindThisRun(false) + }, []) + + // In order to keep routing linear, all extended "skip" flows should be kept as separate recovery options with + // go back functionality that routes to this view. Those "skip" views encapsulate the generic "skip" view. + // See the "manually fill well and skip" recovery option for an example. const ignoreOnce = (): void => { - void proceedToRouteAndStep( - FILL_MANUALLY_AND_SKIP.ROUTE, - FILL_MANUALLY_AND_SKIP.STEPS.SKIP - ) + switch (errorKind) { + case ERROR_KINDS.NO_LIQUID_DETECTED: + void proceedToRouteAndStep( + RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE, + RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.SKIP + ) + break + default: + void proceedNextStep() + } } // See ignoreOnce comment. const ignoreAlways = (): void => { - void ignoreErrorKindThisRun().then(() => - proceedToRouteAndStep( - FILL_MANUALLY_AND_SKIP.ROUTE, - FILL_MANUALLY_AND_SKIP.STEPS.SKIP - ) - ) + void ignoreErrorKindThisRun(true).then(() => { + switch (errorKind) { + case ERROR_KINDS.NO_LIQUID_DETECTED: + void proceedToRouteAndStep( + RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE, + RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.SKIP + ) + break + default: + void proceedNextStep() + } + }) } const primaryOnClick = (): void => { diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManageTips.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManageTips.tsx index 5075d7e53f7..9061ba9b638 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManageTips.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManageTips.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import { @@ -20,13 +19,13 @@ import { RecoveryFooterButtons, RecoverySingleColumnContentWrapper, } from '../shared' -import { DropTipWizardFlows } from '../../DropTipWizardFlows' -import { DT_ROUTES } from '../../DropTipWizardFlows/constants' +import { DropTipWizardFlows } from '/app/organisms/DropTipWizardFlows' +import { DT_ROUTES } from '/app/organisms/DropTipWizardFlows/constants' import { SelectRecoveryOption } from './SelectRecoveryOption' -import type { PipetteWithTip } from '../../DropTipWizardFlows' import type { RecoveryContentProps, RecoveryRoute, RouteStep } from '../types' -import type { FixitCommandTypeUtils } from '../../DropTipWizardFlows/types' +import type { FixitCommandTypeUtils } from '/app/organisms/DropTipWizardFlows' +import type { PipetteWithTip } from '/app/resources/instruments' // The Drop Tip flow entry point. Includes entry from SelectRecoveryOption and CancelRun. export function ManageTips(props: RecoveryContentProps): JSX.Element { @@ -35,7 +34,7 @@ export function ManageTips(props: RecoveryContentProps): JSX.Element { routeAlternativelyIfNoPipette(props) const buildContent = (): JSX.Element => { - const { DROP_TIP_FLOWS } = RECOVERY_MAP + const { DROP_TIP_FLOWS, HOME_AND_RETRY } = RECOVERY_MAP const { step, route } = recoveryMap switch (step) { @@ -45,8 +44,12 @@ export function ManageTips(props: RecoveryContentProps): JSX.Element { case DROP_TIP_FLOWS.STEPS.CHOOSE_BLOWOUT: case DROP_TIP_FLOWS.STEPS.CHOOSE_TIP_DROP: return + case HOME_AND_RETRY.STEPS.REMOVE_TIPS_FROM_PIPETTE: + return default: - console.warn(`${step} in ${route} not explicitly handled. Rerouting.`) + console.warn( + `ManageTips: ${step} in ${route} not explicitly handled. Rerouting.` + ) return } } @@ -64,16 +67,28 @@ export function BeginRemoval({ const { aPipetteWithTip } = tipStatusUtils const { proceedNextStep, - setRobotInMotion, + handleMotionRouting, proceedToRouteAndStep, } = routeUpdateActions const { cancelRun } = recoveryCommands const { selectedRecoveryOption } = currentRecoveryOptionUtils - const { ROBOT_CANCELING, RETRY_NEW_TIPS } = RECOVERY_MAP + const { + ROBOT_CANCELING, + RETRY_NEW_TIPS, + HOME_AND_RETRY, + DROP_TIP_FLOWS, + } = RECOVERY_MAP const mount = aPipetteWithTip?.mount const primaryOnClick = (): void => { - void proceedNextStep() + if (selectedRecoveryOption === HOME_AND_RETRY.ROUTE) { + void proceedToRouteAndStep( + DROP_TIP_FLOWS.ROUTE, + DROP_TIP_FLOWS.STEPS.BEFORE_BEGINNING + ) + } else { + void proceedNextStep() + } } const secondaryOnClick = (): void => { @@ -82,8 +97,13 @@ export function BeginRemoval({ RETRY_NEW_TIPS.ROUTE, RETRY_NEW_TIPS.STEPS.REPLACE_TIPS ) + } else if (selectedRecoveryOption === HOME_AND_RETRY.ROUTE) { + void proceedToRouteAndStep( + HOME_AND_RETRY.ROUTE, + HOME_AND_RETRY.STEPS.HOME_BEFORE_RETRY + ) } else { - void setRobotInMotion(true, ROBOT_CANCELING.ROUTE).then(() => { + void handleMotionRouting(true, ROBOT_CANCELING.ROUTE).then(() => { cancelRun() }) } @@ -150,8 +170,13 @@ function DropTipFlowsContainer( recoveryCommands, currentRecoveryOptionUtils, } = props - const { DROP_TIP_FLOWS, ROBOT_CANCELING, RETRY_NEW_TIPS } = RECOVERY_MAP - const { proceedToRouteAndStep, setRobotInMotion } = routeUpdateActions + const { + DROP_TIP_FLOWS, + ROBOT_CANCELING, + RETRY_NEW_TIPS, + HOME_AND_RETRY, + } = RECOVERY_MAP + const { proceedToRouteAndStep, handleMotionRouting } = routeUpdateActions const { selectedRecoveryOption } = currentRecoveryOptionUtils const { setTipStatusResolved } = tipStatusUtils const { cancelRun } = recoveryCommands @@ -164,13 +189,18 @@ function DropTipFlowsContainer( RETRY_NEW_TIPS.ROUTE, RETRY_NEW_TIPS.STEPS.REPLACE_TIPS ) + } else if (selectedRecoveryOption === HOME_AND_RETRY.ROUTE) { + void proceedToRouteAndStep( + HOME_AND_RETRY.ROUTE, + HOME_AND_RETRY.STEPS.HOME_BEFORE_RETRY + ) } else { void setTipStatusResolved(onEmptyCache, onTipsDetected) } } const onEmptyCache = (): void => { - void setRobotInMotion(true, ROBOT_CANCELING.ROUTE).then(() => { + void handleMotionRouting(true, ROBOT_CANCELING.ROUTE).then(() => { cancelRun() }) } @@ -182,15 +212,14 @@ function DropTipFlowsContainer( const fixitCommandTypeUtils = useDropTipFlowUtils(props) return ( - - - + ) } @@ -202,7 +231,7 @@ export function useDropTipFlowUtils({ subMapUtils, routeUpdateActions, recoveryMap, - failedPipetteInfo, + errorKind, }: RecoveryContentProps): FixitCommandTypeUtils { const { t } = useTranslation('error_recovery') const { @@ -210,8 +239,9 @@ export function useDropTipFlowUtils({ SKIP_STEP_WITH_NEW_TIPS, ERROR_WHILE_RECOVERING, DROP_TIP_FLOWS, + HOME_AND_RETRY, } = RECOVERY_MAP - const { runId } = tipStatusUtils + const { runId, gripperErrorFirstPipetteWithTip } = tipStatusUtils const { step } = recoveryMap const { selectedRecoveryOption } = currentRecoveryOptionUtils const { proceedToRouteAndStep } = routeUpdateActions @@ -222,6 +252,7 @@ export function useDropTipFlowUtils({ switch (selectedRecoveryOption) { case RETRY_NEW_TIPS.ROUTE: case SKIP_STEP_WITH_NEW_TIPS.ROUTE: + case HOME_AND_RETRY.ROUTE: return t('proceed_to_tip_selection') default: return t('proceed_to_cancel') @@ -245,6 +276,10 @@ export function useDropTipFlowUtils({ SKIP_STEP_WITH_NEW_TIPS.STEPS.REPLACE_TIPS ) } + case HOME_AND_RETRY.ROUTE: + return () => { + routeTo(selectedRecoveryOption, HOME_AND_RETRY.STEPS.REPLACE_TIPS) + } default: return null } @@ -253,7 +288,7 @@ export function useDropTipFlowUtils({ const buildCopyOverrides = (): FixitCommandTypeUtils['copyOverrides'] => { return { tipDropCompleteBtnCopy: buildTipDropCompleteBtn(), - beforeBeginningTopText: t('preserve_aspirated_liquid'), + beforeBeginningTopText: t('do_you_need_to_blowout'), } } @@ -305,11 +340,12 @@ export function useDropTipFlowUtils({ } const pipetteId = - failedCommand != null && + gripperErrorFirstPipetteWithTip ?? + (failedCommand != null && 'params' in failedCommand.byRunRecord && 'pipetteId' in failedCommand.byRunRecord.params ? failedCommand.byRunRecord.params.pipetteId - : null + : null) return { runId, @@ -337,6 +373,7 @@ function routeAlternativelyIfNoPipette(props: RecoveryContentProps): void { RETRY_NEW_TIPS, SKIP_STEP_WITH_NEW_TIPS, OPTION_SELECTION, + HOME_AND_RETRY, } = RECOVERY_MAP if (tipStatusUtils.aPipetteWithTip == null) @@ -355,6 +392,13 @@ function routeAlternativelyIfNoPipette(props: RecoveryContentProps): void { ) break } + case HOME_AND_RETRY.ROUTE: { + proceedToRouteAndStep( + selectedRecoveryOption, + HOME_AND_RETRY.STEPS.HOME_BEFORE_RETRY + ) + break + } default: { proceedToRouteAndStep(OPTION_SELECTION.ROUTE) } diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManualMoveLwAndSkip.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManualMoveLwAndSkip.tsx new file mode 100644 index 00000000000..5cf8ef81a65 --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManualMoveLwAndSkip.tsx @@ -0,0 +1,39 @@ +import { RECOVERY_MAP } from '../constants' +import { + GripperIsHoldingLabware, + GripperReleaseLabware, + SkipStepInfo, + TwoColLwInfoAndDeck, + RecoveryDoorOpenSpecial, +} from '../shared' +import { SelectRecoveryOption } from './SelectRecoveryOption' + +import type { RecoveryContentProps } from '../types' + +export function ManualMoveLwAndSkip(props: RecoveryContentProps): JSX.Element { + const { recoveryMap } = props + const { step, route } = recoveryMap + const { MANUAL_MOVE_AND_SKIP } = RECOVERY_MAP + + const buildContent = (): JSX.Element => { + switch (step) { + case MANUAL_MOVE_AND_SKIP.STEPS.GRIPPER_HOLDING_LABWARE: + return + case MANUAL_MOVE_AND_SKIP.STEPS.GRIPPER_RELEASE_LABWARE: + return + case MANUAL_MOVE_AND_SKIP.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME: + return + case MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE: + return + case MANUAL_MOVE_AND_SKIP.STEPS.SKIP: + return + default: + console.warn( + `ManualMoveLwAndSkipStep: ${step} in ${route} not explicitly handled. Rerouting.` + ) + return + } + } + + return buildContent() +} diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManualReplaceLwAndRetry.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManualReplaceLwAndRetry.tsx new file mode 100644 index 00000000000..313d3d1f086 --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/ManualReplaceLwAndRetry.tsx @@ -0,0 +1,41 @@ +import { RECOVERY_MAP } from '../constants' +import { + GripperIsHoldingLabware, + GripperReleaseLabware, + TwoColLwInfoAndDeck, + RetryStepInfo, + RecoveryDoorOpenSpecial, +} from '../shared' +import { SelectRecoveryOption } from './SelectRecoveryOption' + +import type { RecoveryContentProps } from '../types' + +export function ManualReplaceLwAndRetry( + props: RecoveryContentProps +): JSX.Element { + const { recoveryMap } = props + const { step, route } = recoveryMap + const { MANUAL_REPLACE_AND_RETRY } = RECOVERY_MAP + + const buildContent = (): JSX.Element => { + switch (step) { + case MANUAL_REPLACE_AND_RETRY.STEPS.GRIPPER_HOLDING_LABWARE: + return + case MANUAL_REPLACE_AND_RETRY.STEPS.GRIPPER_RELEASE_LABWARE: + return + case MANUAL_REPLACE_AND_RETRY.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME: + return + case MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE: + return + case MANUAL_REPLACE_AND_RETRY.STEPS.RETRY: + return + default: + console.warn( + `ManualReplaceLwAndRetry: ${step} in ${route} not explicitly handled. Rerouting.` + ) + return + } + } + + return buildContent() +} diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/RetryNewTips.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/RetryNewTips.tsx index 0a87c7c9234..003e776824d 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/RetryNewTips.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/RetryNewTips.tsx @@ -1,11 +1,10 @@ -import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import { LegacyStyledText } from '@opentrons/components' import { RECOVERY_MAP } from '../constants' import { - ReplaceTips, + TwoColLwInfoAndDeck, SelectTips, TwoColTextAndFailedStepNextStep, } from '../shared' @@ -30,13 +29,15 @@ export function RetryNewTips(props: RecoveryContentProps): JSX.Element { const buildContent = (): JSX.Element => { switch (step) { case RETRY_NEW_TIPS.STEPS.REPLACE_TIPS: - return + return case RETRY_NEW_TIPS.STEPS.SELECT_TIPS: return case RETRY_NEW_TIPS.STEPS.RETRY: return default: - console.warn(`${step} in ${route} not explicitly handled. Rerouting.`) + console.warn( + `RetryNewTips: ${step} in ${route} not explicitly handled. Rerouting.` + ) return } } @@ -47,12 +48,12 @@ export function RetryNewTips(props: RecoveryContentProps): JSX.Element { export function RetryWithNewTips(props: RecoveryContentProps): JSX.Element { const { recoveryCommands, routeUpdateActions } = props const { retryFailedCommand, resumeRun } = recoveryCommands - const { setRobotInMotion } = routeUpdateActions + const { handleMotionRouting } = routeUpdateActions const { ROBOT_RETRYING_STEP } = RECOVERY_MAP const { t } = useTranslation('error_recovery') const primaryBtnOnClick = (): Promise => { - return setRobotInMotion(true, ROBOT_RETRYING_STEP.ROUTE) + return handleMotionRouting(true, ROBOT_RETRYING_STEP.ROUTE) .then(() => retryFailedCommand()) .then(() => { resumeRun() diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/RetrySameTips.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/RetrySameTips.tsx index 1d8fa933c42..0c28eb2a2da 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/RetrySameTips.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/RetrySameTips.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import { LegacyStyledText } from '@opentrons/components' @@ -19,7 +18,9 @@ export function RetrySameTips(props: RecoveryContentProps): JSX.Element { case RETRY_SAME_TIPS.STEPS.RETRY: return default: - console.warn(`${step} in ${route} not explicitly handled. Rerouting.`) + console.warn( + `RetrySameTips: ${step} in ${route} not explicitly handled. Rerouting.` + ) return } } @@ -30,12 +31,12 @@ export function RetrySameTips(props: RecoveryContentProps): JSX.Element { export function RetrySameTipsInfo(props: RecoveryContentProps): JSX.Element { const { routeUpdateActions, recoveryCommands } = props const { retryFailedCommand, resumeRun } = recoveryCommands - const { setRobotInMotion } = routeUpdateActions + const { handleMotionRouting } = routeUpdateActions const { ROBOT_RETRYING_STEP } = RECOVERY_MAP const { t } = useTranslation('error_recovery') const primaryBtnOnClick = (): Promise => { - return setRobotInMotion(true, ROBOT_RETRYING_STEP.ROUTE) + return handleMotionRouting(true, ROBOT_RETRYING_STEP.ROUTE) .then(() => retryFailedCommand()) .then(() => { resumeRun() diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/RetryStep.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/RetryStep.tsx index ddaf6b5ee3c..a30b68d4358 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/RetryStep.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/RetryStep.tsx @@ -1,10 +1,5 @@ -import * as React from 'react' -import { Trans, useTranslation } from 'react-i18next' - -import { LegacyStyledText } from '@opentrons/components' - import { RECOVERY_MAP } from '../constants' -import { TwoColTextAndFailedStepNextStep } from '../shared' +import { RetryStepInfo } from '../shared' import { SelectRecoveryOption } from './SelectRecoveryOption' import type { RecoveryContentProps } from '../types' @@ -12,56 +7,19 @@ import type { RecoveryContentProps } from '../types' export function RetryStep(props: RecoveryContentProps): JSX.Element { const { recoveryMap } = props const { step, route } = recoveryMap - const { RETRY_FAILED_COMMAND } = RECOVERY_MAP + const { RETRY_STEP } = RECOVERY_MAP const buildContent = (): JSX.Element => { switch (step) { - case RETRY_FAILED_COMMAND.STEPS.CONFIRM_RETRY: + case RETRY_STEP.STEPS.CONFIRM_RETRY: return default: - console.warn(`${step} in ${route} not explicitly handled. Rerouting.`) + console.warn( + `RetryStep: ${step} in ${route} not explicitly handled. Rerouting.` + ) return } } return buildContent() } - -export function RetryStepInfo(props: RecoveryContentProps): JSX.Element { - const { routeUpdateActions, recoveryCommands } = props - const { ROBOT_RETRYING_STEP } = RECOVERY_MAP - const { t } = useTranslation('error_recovery') - - const { retryFailedCommand, resumeRun } = recoveryCommands - const { setRobotInMotion } = routeUpdateActions - - const primaryBtnOnClick = (): Promise => { - return setRobotInMotion(true, ROBOT_RETRYING_STEP.ROUTE) - .then(() => retryFailedCommand()) - .then(() => { - resumeRun() - }) - } - - const buildBodyText = (): JSX.Element => { - return ( - , - }} - /> - ) - } - - return ( - - ) -} diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/SelectRecoveryOption.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/SelectRecoveryOption.tsx index c56a7ede638..e271cc3be23 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/SelectRecoveryOption.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/SelectRecoveryOption.tsx @@ -1,8 +1,10 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import head from 'lodash/head' import { useTranslation } from 'react-i18next' +import { css } from 'styled-components' import { + RESPONSIVENESS, DIRECTION_COLUMN, Flex, SPACING, @@ -14,17 +16,11 @@ import { RECOVERY_MAP, ERROR_KINDS, ODD_SECTION_TITLE_STYLE, - ODD_ONLY, - DESKTOP_ONLY, } from '../constants' -import { - RecoveryODDOneDesktopTwoColumnContentWrapper, - RecoveryRadioGroup, - FailedStepNextStep, -} from '../shared' +import { RecoverySingleColumnContentWrapper } from '../shared' import type { ErrorKind, RecoveryContentProps, RecoveryRoute } from '../types' -import type { PipetteWithTip } from '../../DropTipWizardFlows' +import type { PipetteWithTip } from '/app/resources/instruments' // The "home" route within Error Recovery. When a user completes a non-terminal flow or presses "Go back" enough // to escape the boundaries of any route, they will be redirected here. @@ -52,21 +48,21 @@ export function SelectRecoveryOptionHome({ currentRecoveryOptionUtils, getRecoveryOptionCopy, analytics, - ...rest + isOnDevice, }: RecoveryContentProps): JSX.Element | null { const { t } = useTranslation('error_recovery') const { proceedToRouteAndStep } = routeUpdateActions const { determineTipStatus } = tipStatusUtils const { setSelectedRecoveryOption } = currentRecoveryOptionUtils const validRecoveryOptions = getRecoveryOptions(errorKind) - const [selectedRoute, setSelectedRoute] = React.useState( + const [selectedRoute, setSelectedRoute] = useState( head(validRecoveryOptions) as RecoveryRoute ) useCurrentTipStatus(determineTipStatus) return ( - { analytics.reportActionSelectedEvent(selectedRoute) @@ -83,25 +79,16 @@ export function SelectRecoveryOptionHome({ > {t('choose_a_recovery_action')} - - - - - - + - - + ) } @@ -109,23 +96,23 @@ interface RecoveryOptionsProps { validRecoveryOptions: RecoveryRoute[] setSelectedRoute: (route: RecoveryRoute) => void getRecoveryOptionCopy: RecoveryContentProps['getRecoveryOptionCopy'] + errorKind: RecoveryContentProps['errorKind'] + isOnDevice: RecoveryContentProps['isOnDevice'] selectedRoute?: RecoveryRoute } -// For ODD use only. -export function ODDRecoveryOptions({ + +export function RecoveryOptions({ + errorKind, validRecoveryOptions, selectedRoute, setSelectedRoute, getRecoveryOptionCopy, + isOnDevice, }: RecoveryOptionsProps): JSX.Element { return ( - + {validRecoveryOptions.map((recoveryOption: RecoveryRoute) => { - const optionName = getRecoveryOptionCopy(recoveryOption) + const optionName = getRecoveryOptionCopy(recoveryOption, errorKind) return ( ) })} @@ -143,42 +131,21 @@ export function ODDRecoveryOptions({ ) } -export function DesktopRecoveryOptions({ - validRecoveryOptions, - selectedRoute, - setSelectedRoute, - getRecoveryOptionCopy, -}: RecoveryOptionsProps): JSX.Element { - return ( - { - setSelectedRoute(e.currentTarget.value) - }} - value={selectedRoute} - options={validRecoveryOptions.map( - (option: RecoveryRoute) => - ({ - value: option, - children: ( - - {getRecoveryOptionCopy(option)} - - ), - } as const) - )} - /> - ) -} +const RECOVERY_OPTION_CONTAINER_STYLE = css` + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing4}; + width: 100%; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + grid-gap: ${SPACING.spacing8}; + } +` + // Pre-fetch tip attachment status. Users are not blocked from proceeding at this step. export function useCurrentTipStatus( determineTipStatus: () => Promise ): void { - React.useEffect(() => { + useEffect(() => { void determineTipStatus() }, []) } @@ -193,13 +160,26 @@ export function getRecoveryOptions(errorKind: ErrorKind): RecoveryRoute[] { return OVERPRESSURE_WHILE_ASPIRATING_OPTIONS case ERROR_KINDS.OVERPRESSURE_WHILE_DISPENSING: return OVERPRESSURE_WHILE_DISPENSING_OPTIONS + case ERROR_KINDS.TIP_NOT_DETECTED: + return TIP_NOT_DETECTED_OPTIONS + case ERROR_KINDS.TIP_DROP_FAILED: + return TIP_DROP_FAILED_OPTIONS + case ERROR_KINDS.GRIPPER_ERROR: + return GRIPPER_ERROR_OPTIONS case ERROR_KINDS.GENERAL_ERROR: return GENERAL_ERROR_OPTIONS + case ERROR_KINDS.STALL_OR_COLLISION: + return STALL_OR_COLLISION_OPTIONS } } +export const STALL_OR_COLLISION_OPTIONS: RecoveryRoute[] = [ + RECOVERY_MAP.HOME_AND_RETRY.ROUTE, + RECOVERY_MAP.CANCEL_RUN.ROUTE, +] + export const NO_LIQUID_DETECTED_OPTIONS: RecoveryRoute[] = [ - RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.ROUTE, + RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE, RECOVERY_MAP.IGNORE_AND_SKIP.ROUTE, RECOVERY_MAP.CANCEL_RUN.ROUTE, ] @@ -221,11 +201,25 @@ export const OVERPRESSURE_WHILE_DISPENSING_OPTIONS: RecoveryRoute[] = [ RECOVERY_MAP.CANCEL_RUN.ROUTE, ] -export const GENERAL_ERROR_OPTIONS: RecoveryRoute[] = [ - RECOVERY_MAP.RETRY_FAILED_COMMAND.ROUTE, +export const TIP_NOT_DETECTED_OPTIONS: RecoveryRoute[] = [ + RECOVERY_MAP.RETRY_STEP.ROUTE, + RECOVERY_MAP.IGNORE_AND_SKIP.ROUTE, RECOVERY_MAP.CANCEL_RUN.ROUTE, ] -const RADIO_GAP = ` - gap: ${SPACING.spacing4}; -` +export const TIP_DROP_FAILED_OPTIONS: RecoveryRoute[] = [ + RECOVERY_MAP.RETRY_STEP.ROUTE, + RECOVERY_MAP.IGNORE_AND_SKIP.ROUTE, + RECOVERY_MAP.CANCEL_RUN.ROUTE, +] + +export const GRIPPER_ERROR_OPTIONS: RecoveryRoute[] = [ + RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE, + RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE, + RECOVERY_MAP.CANCEL_RUN.ROUTE, +] + +export const GENERAL_ERROR_OPTIONS: RecoveryRoute[] = [ + RECOVERY_MAP.RETRY_STEP.ROUTE, + RECOVERY_MAP.CANCEL_RUN.ROUTE, +] diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/SkipStepNewTips.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/SkipStepNewTips.tsx index 33c0f199cd8..b237afd82f0 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/SkipStepNewTips.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/SkipStepNewTips.tsx @@ -1,14 +1,5 @@ -import * as React from 'react' -import { Trans, useTranslation } from 'react-i18next' - -import { LegacyStyledText } from '@opentrons/components' - import { RECOVERY_MAP } from '../constants' -import { - ReplaceTips, - SelectTips, - TwoColTextAndFailedStepNextStep, -} from '../shared' +import { TwoColLwInfoAndDeck, SelectTips, SkipStepInfo } from '../shared' import { SelectRecoveryOption } from './SelectRecoveryOption' import type { RecoveryContentProps } from '../types' @@ -32,52 +23,18 @@ export function SkipStepNewTips( const buildContent = (): JSX.Element => { switch (step) { case SKIP_STEP_WITH_NEW_TIPS.STEPS.REPLACE_TIPS: - return + return case SKIP_STEP_WITH_NEW_TIPS.STEPS.SELECT_TIPS: return case SKIP_STEP_WITH_NEW_TIPS.STEPS.SKIP: - return + return default: - console.warn(`${step} in ${route} not explicitly handled. Rerouting.`) + console.warn( + `SkipStepNewTips: ${step} in ${route} not explicitly handled. Rerouting.` + ) return } } return buildContent() } - -export function SkipStepWithNewTips(props: RecoveryContentProps): JSX.Element { - const { recoveryCommands, routeUpdateActions } = props - const { skipFailedCommand } = recoveryCommands - const { setRobotInMotion } = routeUpdateActions - const { ROBOT_SKIPPING_STEP } = RECOVERY_MAP - const { t } = useTranslation('error_recovery') - - const primaryBtnOnClick = (): Promise => { - return setRobotInMotion(true, ROBOT_SKIPPING_STEP.ROUTE).then(() => { - skipFailedCommand() - }) - } - - const buildBodyText = (): JSX.Element => { - return ( - , - }} - /> - ) - } - - return ( - - ) -} diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/SkipStepSameTips.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/SkipStepSameTips.tsx index aed84372ccf..9990d94171a 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/SkipStepSameTips.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/SkipStepSameTips.tsx @@ -1,9 +1,4 @@ -import * as React from 'react' -import { Trans, useTranslation } from 'react-i18next' - -import { LegacyStyledText } from '@opentrons/components' - -import { TwoColTextAndFailedStepNextStep } from '../shared' +import { SkipStepInfo } from '../shared' import { RECOVERY_MAP } from '../constants' import { SelectRecoveryOption } from './SelectRecoveryOption' @@ -17,48 +12,14 @@ export function SkipStepSameTips(props: RecoveryContentProps): JSX.Element { const buildContent = (): JSX.Element => { switch (step) { case SKIP_STEP_WITH_SAME_TIPS.STEPS.SKIP: - return + return default: - console.warn(`${step} in ${route} not explicitly handled. Rerouting.`) + console.warn( + `SkipStepSameTips: ${step} in ${route} not explicitly handled. Rerouting.` + ) return } } return buildContent() } - -export function SkipStepSameTipsInfo(props: RecoveryContentProps): JSX.Element { - const { routeUpdateActions, recoveryCommands } = props - const { skipFailedCommand } = recoveryCommands - const { setRobotInMotion } = routeUpdateActions - const { ROBOT_SKIPPING_STEP } = RECOVERY_MAP - const { t } = useTranslation('error_recovery') - - const primaryBtnOnClick = (): Promise => { - return setRobotInMotion(true, ROBOT_SKIPPING_STEP.ROUTE).then(() => { - skipFailedCommand() - }) - } - - const buildBodyText = (): JSX.Element => { - return ( - , - }} - /> - ) - } - - return ( - - ) -} diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/CancelRun.test.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/CancelRun.test.tsx index 31e991deef9..cbf8126e353 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/CancelRun.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/CancelRun.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, describe, it, expect, beforeEach } from 'vitest' import { screen, waitFor } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockRecoveryContentProps } from '../../__fixtures__' import { CancelRun } from '../CancelRun' import { RECOVERY_MAP } from '../../constants' @@ -23,16 +23,16 @@ describe('RecoveryFooterButtons', () => { const { CANCEL_RUN, ROBOT_CANCELING, DROP_TIP_FLOWS } = RECOVERY_MAP let props: React.ComponentProps let mockGoBackPrevStep: Mock - let mockSetRobotInMotion: Mock + let mockhandleMotionRouting: Mock let mockProceedToRouteAndStep: Mock beforeEach(() => { mockGoBackPrevStep = vi.fn() - mockSetRobotInMotion = vi.fn(() => Promise.resolve()) + mockhandleMotionRouting = vi.fn(() => Promise.resolve()) mockProceedToRouteAndStep = vi.fn() const mockRouteUpdateActions = { goBackPrevStep: mockGoBackPrevStep, - setRobotInMotion: mockSetRobotInMotion, + handleMotionRouting: mockhandleMotionRouting, proceedToRouteAndStep: mockProceedToRouteAndStep, } as any @@ -56,7 +56,10 @@ describe('RecoveryFooterButtons', () => { }) it('renders SelectRecoveryOption when the route is unknown', () => { - props = { ...props, recoveryMap: { ...props.recoveryMap, step: 'UNKNOWN' } } + props = { + ...props, + recoveryMap: { ...props.recoveryMap, step: 'UNKNOWN' as any }, + } render(props) screen.getByText('MOCK SELECT RECOVERY OPTION') @@ -76,7 +79,7 @@ describe('RecoveryFooterButtons', () => { }) it('should call commands in the correct order for the primaryOnClick callback', async () => { - const setRobotInMotionMock = vi.fn(() => Promise.resolve()) + const handleMotionRoutingMock = vi.fn(() => Promise.resolve()) const cancelRunMock = vi.fn(() => Promise.resolve()) const mockRecoveryCommands = { @@ -84,7 +87,7 @@ describe('RecoveryFooterButtons', () => { } as any const mockRouteUpdateActions = { - setRobotInMotion: setRobotInMotionMock, + handleMotionRouting: handleMotionRoutingMock, } as any render({ @@ -96,10 +99,10 @@ describe('RecoveryFooterButtons', () => { clickButtonLabeled('Confirm') await waitFor(() => { - expect(setRobotInMotionMock).toHaveBeenCalledTimes(1) + expect(handleMotionRoutingMock).toHaveBeenCalledTimes(1) }) await waitFor(() => { - expect(setRobotInMotionMock).toHaveBeenCalledWith( + expect(handleMotionRoutingMock).toHaveBeenCalledWith( true, ROBOT_CANCELING.ROUTE ) @@ -108,7 +111,7 @@ describe('RecoveryFooterButtons', () => { expect(cancelRunMock).toHaveBeenCalledTimes(1) }) - expect(setRobotInMotionMock.mock.invocationCallOrder[0]).toBeLessThan( + expect(handleMotionRoutingMock.mock.invocationCallOrder[0]).toBeLessThan( cancelRunMock.mock.invocationCallOrder[0] ) }) @@ -141,7 +144,7 @@ describe('RecoveryFooterButtons', () => { clickButtonLabeled('Confirm') expect(mockProceedToRouteAndStep).not.toHaveBeenCalled() - expect(mockSetRobotInMotion).not.toHaveBeenCalled() + expect(mockhandleMotionRouting).not.toHaveBeenCalled() }) it('should will cancel the run if no tips are detected', () => { @@ -157,6 +160,6 @@ describe('RecoveryFooterButtons', () => { clickButtonLabeled('Confirm') expect(mockProceedToRouteAndStep).not.toHaveBeenCalled() - expect(mockSetRobotInMotion).toHaveBeenCalled() + expect(mockhandleMotionRouting).toHaveBeenCalled() }) }) diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/FillWellAndSkip.test.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/FillWellAndSkip.test.tsx index 123bbb33626..3c7675ec21c 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/FillWellAndSkip.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/FillWellAndSkip.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, expect, beforeEach } from 'vitest' import { screen, waitFor } from '@testing-library/react' import { mockRecoveryContentProps } from '../../__fixtures__' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { FillWellAndSkip, FillWell, SkipToNextStep } from '../FillWellAndSkip' import { RECOVERY_MAP } from '../../constants' import { CancelRun } from '../CancelRun' @@ -25,12 +25,12 @@ vi.mock('../../shared', async () => { )), } }) -vi.mock('../../../../molecules/InterventionModal/DeckMapContent', () => ({ +vi.mock('/app/molecules/InterventionModal/DeckMapContent', () => ({ DeckMapContent: vi.fn(() =>
    MOCK_RECOVERY_MAP
    ), })) vi.mock('../CancelRun') vi.mock('../SelectRecoveryOption') -vi.mock('../../../../molecules/Command') +vi.mock('/app/molecules/Command') const render = (props: React.ComponentProps) => { return renderWithProviders(, { @@ -66,12 +66,12 @@ describe('FillWellAndSkip', () => { ) }) - it(`renders FillWell when step is ${RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.STEPS.MANUALLY_FILL}`, () => { + it(`renders FillWell when step is ${RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.MANUAL_FILL}`, () => { props = { ...props, recoveryMap: { ...props.recoveryMap, - step: RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.STEPS.MANUALLY_FILL, + step: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.MANUAL_FILL, }, } render(props) @@ -80,12 +80,12 @@ describe('FillWellAndSkip', () => { ) }) - it(`renders SkipToNextStep when step is ${RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.STEPS.SKIP}`, () => { + it(`renders SkipToNextStep when step is ${RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.SKIP}`, () => { props = { ...props, recoveryMap: { ...props.recoveryMap, - step: RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.STEPS.SKIP, + step: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.SKIP, }, } render(props) @@ -109,7 +109,7 @@ describe('FillWellAndSkip', () => { ...props, recoveryMap: { ...props.recoveryMap, - step: 'UNKNOWN_STEP', + step: 'UNKNOWN_STEP' as any, }, } render(props) @@ -137,13 +137,13 @@ describe('FillWell', () => { describe('SkipToNextStep', () => { let props: React.ComponentProps - let mockSetRobotInMotion: Mock + let mockhandleMotionRouting: Mock let mockGoBackPrevStep: Mock let mockProceedToRouteAndStep: Mock let mockSkipFailedCommand: Mock beforeEach(() => { - mockSetRobotInMotion = vi.fn(() => Promise.resolve()) + mockhandleMotionRouting = vi.fn(() => Promise.resolve()) mockGoBackPrevStep = vi.fn() mockProceedToRouteAndStep = vi.fn() mockSkipFailedCommand = vi.fn(() => Promise.resolve()) @@ -151,7 +151,7 @@ describe('SkipToNextStep', () => { props = { ...mockRecoveryContentProps, routeUpdateActions: { - setRobotInMotion: mockSetRobotInMotion, + handleMotionRouting: mockhandleMotionRouting, goBackPrevStep: mockGoBackPrevStep, proceedToRouteAndStep: mockProceedToRouteAndStep, } as any, @@ -186,7 +186,7 @@ describe('SkipToNextStep', () => { renderSkipToNextStep(props) clickButtonLabeled('Continue run now') await waitFor(() => { - expect(mockSetRobotInMotion).toHaveBeenCalledWith( + expect(mockhandleMotionRouting).toHaveBeenCalledWith( true, RECOVERY_MAP.ROBOT_SKIPPING_STEP.ROUTE ) @@ -195,7 +195,7 @@ describe('SkipToNextStep', () => { expect(mockSkipFailedCommand).toHaveBeenCalled() }) - expect(mockSetRobotInMotion.mock.invocationCallOrder[0]).toBeLessThan( + expect(mockhandleMotionRouting.mock.invocationCallOrder[0]).toBeLessThan( mockSkipFailedCommand.mock.invocationCallOrder[0] ) }) diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/HomeAndRetry.test.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/HomeAndRetry.test.tsx new file mode 100644 index 00000000000..3286041b7fb --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/HomeAndRetry.test.tsx @@ -0,0 +1,154 @@ +import type * as React from 'react' +import { describe, it, vi, beforeEach, afterEach } from 'vitest' +import { screen } from '@testing-library/react' + +import { mockRecoveryContentProps } from '../../__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { RECOVERY_MAP } from '../../constants' +import { SelectRecoveryOption } from '../SelectRecoveryOption' +import { HomeAndRetry } from '../HomeAndRetry' +import { TipSelection } from '../../shared/TipSelection' + +vi.mock('../SelectRecoveryOption') +vi.mock('../../shared/TipSelection') + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] +} + +describe('HomeAndRetry', () => { + let props: React.ComponentProps + beforeEach(() => { + props = { + ...mockRecoveryContentProps, + currentRecoveryOptionUtils: { + ...mockRecoveryContentProps.currentRecoveryOptionUtils, + selectedRecoveryOption: RECOVERY_MAP.HOME_AND_RETRY.ROUTE, + }, + } + vi.mocked(SelectRecoveryOption).mockReturnValue( +
    MOCK_SELECT_RECOVERY_OPTION
    + ) + vi.mocked(TipSelection).mockReturnValue(
    WELL_SELECTION
    ) + }) + afterEach(() => { + vi.resetAllMocks() + }) + it(`renders PrepareDeckForHome when step is ${RECOVERY_MAP.HOME_AND_RETRY.STEPS.PREPARE_DECK_FOR_HOME}`, () => { + props = { + ...props, + recoveryMap: { + ...props.recoveryMap, + step: RECOVERY_MAP.HOME_AND_RETRY.STEPS.PREPARE_DECK_FOR_HOME, + }, + } + render(props) + screen.getByText('Prepare deck for homing') + }) + it(`renders ManageTips when step is ${RECOVERY_MAP.HOME_AND_RETRY.STEPS.REMOVE_TIPS_FROM_PIPETTE}`, () => { + props = { + ...props, + recoveryMap: { + ...props.recoveryMap, + step: RECOVERY_MAP.HOME_AND_RETRY.STEPS.REMOVE_TIPS_FROM_PIPETTE, + }, + tipStatusUtils: { + ...props.tipStatusUtils, + aPipetteWithTip: { + mount: 'left', + } as any, + }, + } + render(props) + screen.getByText('Remove any attached tips') + }) + it(`renders labware info when step is ${RECOVERY_MAP.HOME_AND_RETRY.STEPS.REPLACE_TIPS}`, () => { + props = { + ...props, + recoveryMap: { + ...props.recoveryMap, + step: RECOVERY_MAP.HOME_AND_RETRY.STEPS.REPLACE_TIPS, + }, + failedLabwareUtils: { + ...props.failedLabwareUtils, + relevantWellName: 'A2', + failedLabwareLocations: { + ...props.failedLabwareUtils.failedLabwareLocations, + displayNameCurrentLoc: 'B2', + }, + }, + } + + render(props) + screen.getByText('Replace used tips in rack location A2 in B2') + }) + it(`renders SelectTips when step is ${RECOVERY_MAP.HOME_AND_RETRY.STEPS.SELECT_TIPS}`, () => { + props = { + ...props, + recoveryMap: { + ...props.recoveryMap, + step: RECOVERY_MAP.HOME_AND_RETRY.STEPS.SELECT_TIPS, + }, + failedLabwareUtils: { + ...props.failedLabwareUtils, + failedLabwareLocations: { + ...props.failedLabwareUtils.failedLabwareLocations, + displayNameCurrentLoc: 'B2', + }, + }, + } + render(props) + screen.getByText('Select tip pick-up location') + }) + it(`renders HomeGantryBeforeRetry when step is ${RECOVERY_MAP.HOME_AND_RETRY.STEPS.HOME_BEFORE_RETRY}`, () => { + props = { + ...props, + recoveryMap: { + ...props.recoveryMap, + step: RECOVERY_MAP.HOME_AND_RETRY.STEPS.HOME_BEFORE_RETRY, + }, + } + render(props) + screen.getByText('Home gantry') + }) + it(`renders the special door open handler when step is ${RECOVERY_MAP.HOME_AND_RETRY.STEPS.CLOSE_DOOR_AND_HOME}`, () => { + props = { + ...props, + recoveryMap: { + ...props.recoveryMap, + step: RECOVERY_MAP.HOME_AND_RETRY.STEPS.CLOSE_DOOR_AND_HOME, + }, + doorStatusUtils: { + ...props.doorStatusUtils, + isDoorOpen: true, + }, + } + render(props) + screen.getByText('Close the robot door') + }) + it(`renders RetryAfterHome awhen step is ${RECOVERY_MAP.HOME_AND_RETRY.STEPS.CONFIRM_RETRY}`, () => { + props = { + ...props, + recoveryMap: { + ...props.recoveryMap, + step: RECOVERY_MAP.HOME_AND_RETRY.STEPS.CONFIRM_RETRY, + }, + } + render(props) + screen.getByText('Retry step') + }) + it(`renders SelectRecoveryOption as a fallback`, () => { + props = { + ...props, + recoveryMap: { + ...props.recoveryMap, + step: 'UNKNOWN_STEP' as any, + }, + } + render(props) + screen.getByText('MOCK_SELECT_RECOVERY_OPTION') + }) +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/IgnoreErrorSkipStep.test.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/IgnoreErrorSkipStep.test.tsx index 466c9f65dc7..547334f77c4 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/IgnoreErrorSkipStep.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/IgnoreErrorSkipStep.test.tsx @@ -1,28 +1,32 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, expect, beforeEach } from 'vitest' import { screen, fireEvent, waitFor } from '@testing-library/react' import { mockRecoveryContentProps } from '../../__fixtures__' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { IgnoreErrorSkipStep, IgnoreErrorStepHome, IgnoreOptions, } from '../IgnoreErrorSkipStep' -import { RECOVERY_MAP } from '../../constants' +import { ERROR_KINDS, RECOVERY_MAP } from '../../constants' import { SelectRecoveryOption } from '../SelectRecoveryOption' import { clickButtonLabeled } from '../../__tests__/util' +import { SkipStepInfo } from '/app/organisms/ErrorRecoveryFlows/shared' import type { Mock } from 'vitest' -vi.mock('../shared', async () => { - const actual = await vi.importActual('../shared') +vi.mock('/app/organisms/ErrorRecoveryFlows/shared', async () => { + const actual = await vi.importActual( + '/app/organisms/ErrorRecoveryFlows/shared' + ) return { ...actual, RecoverySingleColumnContentWrapper: vi.fn(({ children }) => (
    {children}
    )), + SkipStepInfo: vi.fn(), } }) vi.mock('../SelectRecoveryOption') @@ -47,11 +51,13 @@ describe('IgnoreErrorSkipStep', () => { beforeEach(() => { props = { ...mockRecoveryContentProps, + recoveryCommands: { ignoreErrorKindThisRun: vi.fn() } as any, } vi.mocked(SelectRecoveryOption).mockReturnValue(
    MOCK_SELECT_RECOVERY_OPTION
    ) + vi.mocked(SkipStepInfo).mockReturnValue(
    MOCK_SKIP_STEP_INFO
    ) }) it(`renders IgnoreErrorStepHome when step is ${RECOVERY_MAP.IGNORE_AND_SKIP.STEPS.SELECT_IGNORE_KIND}`, () => { @@ -66,12 +72,24 @@ describe('IgnoreErrorSkipStep', () => { screen.getByText('Ignore similar errors later in the run?') }) + it(`renders SkipStepInfo when step is ${RECOVERY_MAP.IGNORE_AND_SKIP.STEPS.SKIP_STEP}`, () => { + props = { + ...props, + recoveryMap: { + ...props.recoveryMap, + step: RECOVERY_MAP.IGNORE_AND_SKIP.STEPS.SKIP_STEP, + }, + } + render(props) + screen.getByText('MOCK_SKIP_STEP_INFO') + }) + it('renders SelectRecoveryOption as a fallback', () => { props = { ...props, recoveryMap: { ...props.recoveryMap, - step: 'UNKNOWN_STEP', + step: 'UNKNOWN_STEP' as any, }, } render(props) @@ -84,38 +102,54 @@ describe('IgnoreErrorStepHome', () => { let mockIgnoreErrorKindThisRun: Mock let mockProceedToRouteAndStep: Mock let mockGoBackPrevStep: Mock + let mockProceedNextStep: Mock beforeEach(() => { mockIgnoreErrorKindThisRun = vi.fn(() => Promise.resolve()) mockProceedToRouteAndStep = vi.fn() mockGoBackPrevStep = vi.fn() + mockProceedNextStep = vi.fn() props = { ...mockRecoveryContentProps, isOnDevice: true, + errorKind: ERROR_KINDS.NO_LIQUID_DETECTED, recoveryCommands: { ignoreErrorKindThisRun: mockIgnoreErrorKindThisRun, } as any, routeUpdateActions: { proceedToRouteAndStep: mockProceedToRouteAndStep, goBackPrevStep: mockGoBackPrevStep, + proceedNextStep: mockProceedNextStep, } as any, } }) - it('calls ignoreOnce when "ignore_only_this_error" is selected and primary button is clicked', async () => { + it(`ignoreOnce correctly routes "ignore_only_this_error" is clicked and the errorKind is ${ERROR_KINDS.NO_LIQUID_DETECTED}`, async () => { renderIgnoreErrorStepHome(props) fireEvent.click(screen.queryAllByText('Ignore only this error')[0]) clickButtonLabeled('Continue') await waitFor(() => { expect(mockProceedToRouteAndStep).toHaveBeenCalledWith( - RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.ROUTE, - RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.STEPS.SKIP + RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE, + RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.SKIP ) }) }) - it('calls ignoreAlways when "ignore_all_errors_of_this_type" is selected and primary button is clicked', async () => { + it(`ignoreOnce correctly routes "ignore_only_this_error" is clicked and the errorKind not explicitly handled`, async () => { + renderIgnoreErrorStepHome({ + ...props, + errorKind: ERROR_KINDS.GENERAL_ERROR, + }) + fireEvent.click(screen.queryAllByText('Ignore only this error')[0]) + clickButtonLabeled('Continue') + await waitFor(() => { + expect(mockProceedNextStep).toHaveBeenCalled() + }) + }) + + it(`ignoreAlways correctly routes when "ignore_all_errors_of_this_type" is clicked and the errorKind is ${ERROR_KINDS.NO_LIQUID_DETECTED}`, async () => { renderIgnoreErrorStepHome(props) fireEvent.click(screen.queryAllByText('Ignore all errors of this type')[0]) clickButtonLabeled('Continue') @@ -124,12 +158,24 @@ describe('IgnoreErrorStepHome', () => { }) await waitFor(() => { expect(mockProceedToRouteAndStep).toHaveBeenCalledWith( - RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.ROUTE, - RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.STEPS.SKIP + RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE, + RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.SKIP ) }) }) + it(`ignoreAlways correctly routes "ignore_all_errors_of_this_type" is clicked and the errorKind not explicitly handled`, async () => { + renderIgnoreErrorStepHome({ + ...props, + errorKind: ERROR_KINDS.GENERAL_ERROR, + }) + fireEvent.click(screen.queryAllByText('Ignore all errors of this type')[0]) + clickButtonLabeled('Continue') + await waitFor(() => { + expect(mockProceedNextStep).toHaveBeenCalled() + }) + }) + it('calls goBackPrevStep when secondary button is clicked', () => { renderIgnoreErrorStepHome(props) clickButtonLabeled('Go back') diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/ManageTips.test.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/ManageTips.test.tsx index 06b199c0587..941b19081c7 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/ManageTips.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/ManageTips.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, expect, beforeEach } from 'vitest' import { screen, @@ -7,21 +7,21 @@ import { render as testingRender, } from '@testing-library/react' -import { mockPipetteInfo } from '../../../../redux/pipettes/__fixtures__' +import { mockPipetteInfo } from '/app/redux/pipettes/__fixtures__' import { mockRecoveryContentProps } from '../../__fixtures__' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ManageTips, useDropTipFlowUtils } from '../ManageTips' import { RECOVERY_MAP } from '../../constants' -import { DropTipWizardFlows } from '../../../DropTipWizardFlows' -import { DT_ROUTES } from '../../../DropTipWizardFlows/constants' +import { DropTipWizardFlows } from '/app/organisms/DropTipWizardFlows' +import { DT_ROUTES } from '/app/organisms/DropTipWizardFlows/constants' import { SelectRecoveryOption } from '../SelectRecoveryOption' import { clickButtonLabeled } from '../../__tests__/util' import type { Mock } from 'vitest' import type { PipetteModelSpecs } from '@opentrons/shared-data' -vi.mock('../../../DropTipWizardFlows') +vi.mock('/app/organisms/DropTipWizardFlows') vi.mock('../SelectRecoveryOption') const { DROP_TIP_FLOWS, RETRY_NEW_TIPS } = RECOVERY_MAP @@ -44,12 +44,12 @@ describe('ManageTips', () => { let props: React.ComponentProps let mockProceedNextStep: Mock let mockProceedToRouteAndStep: Mock - let mockSetRobotInMotion: Mock + let mockhandleMotionRouting: Mock beforeEach(() => { mockProceedNextStep = vi.fn() mockProceedToRouteAndStep = vi.fn(() => Promise.resolve()) - mockSetRobotInMotion = vi.fn(() => Promise.resolve()) + mockhandleMotionRouting = vi.fn(() => Promise.resolve()) props = { ...mockRecoveryContentProps, @@ -62,7 +62,7 @@ describe('ManageTips', () => { } as any, routeUpdateActions: { proceedNextStep: mockProceedNextStep, - setRobotInMotion: mockSetRobotInMotion, + handleMotionRouting: mockhandleMotionRouting, proceedToRouteAndStep: mockProceedToRouteAndStep, } as any, recoveryCommands: { cancelRun: vi.fn() } as any, @@ -82,7 +82,10 @@ describe('ManageTips', () => { }) it('renders SelectRecoveryOption when the route is unknown', () => { - props = { ...props, recoveryMap: { ...props.recoveryMap, step: 'UNKNOWN' } } + props = { + ...props, + recoveryMap: { ...props.recoveryMap, step: 'UNKNOWN' as any }, + } render(props) screen.getByText('MOCK SELECT RECOVERY OPTION') @@ -113,7 +116,7 @@ describe('ManageTips', () => { fireEvent.click(skipBtn) clickButtonLabeled('Skip and home pipette') - expect(mockSetRobotInMotion).toHaveBeenCalled() + expect(mockhandleMotionRouting).toHaveBeenCalled() }) it(`handles special routing for ${RETRY_NEW_TIPS.ROUTE} when skipping tip drop`, () => { @@ -279,7 +282,7 @@ describe('useDropTipFlowUtils', () => { testingRender(result.current.copyOverrides.beforeBeginningTopText as any) - screen.getByText('First, do you need to preserve aspirated liquid?') + screen.getByText('First, do you need to blow out aspirated liquid?') testingRender(result.current.copyOverrides.tipDropCompleteBtnCopy as any) diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/ManualMoveLwAndSkip.test.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/ManualMoveLwAndSkip.test.tsx new file mode 100644 index 00000000000..48f8615cf81 --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/ManualMoveLwAndSkip.test.tsx @@ -0,0 +1,79 @@ +import { describe, it, vi, beforeEach } from 'vitest' +import { screen } from '@testing-library/react' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { ManualMoveLwAndSkip } from '../ManualMoveLwAndSkip' +import { RECOVERY_MAP } from '../../constants' + +import type * as React from 'react' + +vi.mock('../../shared', () => ({ + GripperIsHoldingLabware: vi.fn(() => ( +
    MOCK_GRIPPER_IS_HOLDING_LABWARE
    + )), + GripperReleaseLabware: vi.fn(() =>
    MOCK_GRIPPER_RELEASE_LABWARE
    ), + TwoColLwInfoAndDeck: vi.fn(() =>
    MOCK_TWO_COL_LW_INFO_AND_DECK
    ), + SkipStepInfo: vi.fn(() =>
    MOCK_SKIP_STEP_INFO
    ), + RecoveryDoorOpenSpecial: vi.fn(() =>
    MOCK_DOOR_OPEN_SPECIAL
    ), +})) + +vi.mock('../SelectRecoveryOption', () => ({ + SelectRecoveryOption: vi.fn(() =>
    MOCK_SELECT_RECOVERY_OPTION
    ), +})) + +describe('ManualMoveLwAndSkip', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + recoveryMap: { + route: RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE, + step: RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.GRIPPER_HOLDING_LABWARE, + }, + } as any + }) + + const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] + } + + it(`renders GripperIsHoldingLabware for ${RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.GRIPPER_HOLDING_LABWARE}`, () => { + render(props) + screen.getByText('MOCK_GRIPPER_IS_HOLDING_LABWARE') + }) + + it(`renders GripperReleaseLabware for ${RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.GRIPPER_RELEASE_LABWARE} step`, () => { + props.recoveryMap.step = + RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.GRIPPER_RELEASE_LABWARE + render(props) + screen.getByText('MOCK_GRIPPER_RELEASE_LABWARE') + }) + + it(`renders RecoveryDoorOpenSpecial for ${RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME} step`, () => { + props.recoveryMap.step = + RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME + render(props) + screen.getByText('MOCK_DOOR_OPEN_SPECIAL') + }) + + it(`renders TwoColLwInfoAndDeck for ${RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE} step`, () => { + props.recoveryMap.step = RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE + render(props) + screen.getByText('MOCK_TWO_COL_LW_INFO_AND_DECK') + }) + + it(`renders SkipStepInfo for ${RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.SKIP} step`, () => { + props.recoveryMap.step = RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.SKIP + render(props) + screen.getByText('MOCK_SKIP_STEP_INFO') + }) + + it('renders SelectRecoveryOption for unknown step', () => { + props.recoveryMap.step = 'UNKNOWN_STEP' as any + render(props) + screen.getByText('MOCK_SELECT_RECOVERY_OPTION') + }) +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/ManualReplaceLwAndRetry.test.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/ManualReplaceLwAndRetry.test.tsx new file mode 100644 index 00000000000..fb47ccb5f2f --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/ManualReplaceLwAndRetry.test.tsx @@ -0,0 +1,84 @@ +import { describe, it, vi, beforeEach } from 'vitest' +import { screen } from '@testing-library/react' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { ManualReplaceLwAndRetry } from '../ManualReplaceLwAndRetry' +import { RECOVERY_MAP } from '../../constants' + +import type * as React from 'react' + +vi.mock('../../shared', () => ({ + GripperIsHoldingLabware: vi.fn(() => ( +
    MOCK_GRIPPER_IS_HOLDING_LABWARE
    + )), + GripperReleaseLabware: vi.fn(() =>
    MOCK_GRIPPER_RELEASE_LABWARE
    ), + TwoColLwInfoAndDeck: vi.fn(() =>
    MOCK_TWO_COL_LW_INFO_AND_DECK
    ), + RetryStepInfo: vi.fn(() =>
    MOCK_RETRY_STEP_INFO
    ), + RecoveryDoorOpenSpecial: vi.fn(() =>
    MOCK_DOOR_OPEN_SPECIAL
    ), +})) + +vi.mock('../SelectRecoveryOption', () => ({ + SelectRecoveryOption: vi.fn(() =>
    MOCK_SELECT_RECOVERY_OPTION
    ), +})) + +describe('ManualReplaceLwAndRetry', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + recoveryMap: { + route: RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE, + step: + RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.GRIPPER_HOLDING_LABWARE, + }, + } as any + }) + + const render = ( + props: React.ComponentProps + ) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] + } + + it(`renders GripperIsHoldingLabware for ${RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.GRIPPER_HOLDING_LABWARE}`, () => { + render(props) + screen.getByText('MOCK_GRIPPER_IS_HOLDING_LABWARE') + }) + + it(`renders GripperReleaseLabware for ${RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.GRIPPER_RELEASE_LABWARE} step`, () => { + props.recoveryMap.step = + RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.GRIPPER_RELEASE_LABWARE + render(props) + screen.getByText('MOCK_GRIPPER_RELEASE_LABWARE') + }) + + it(`renders RecoveryDoorOpenSpecial for ${RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME} step`, () => { + props.recoveryMap.step = + RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME + render(props) + screen.getByText('MOCK_DOOR_OPEN_SPECIAL') + }) + + it(`renders TwoColLwInfoAndDeck for ${RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE} step`, () => { + props.recoveryMap.step = + RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE + render(props) + screen.getByText('MOCK_TWO_COL_LW_INFO_AND_DECK') + }) + + it(`renders RetryStepInfo for ${RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.RETRY} step`, () => { + props.recoveryMap.step = RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.RETRY + render(props) + screen.getByText('MOCK_RETRY_STEP_INFO') + }) + + it('renders SelectRecoveryOption for unknown step', () => { + props.recoveryMap.step = + RECOVERY_MAP.SKIP_STEP_WITH_NEW_TIPS.STEPS.REPLACE_TIPS + render(props) + screen.getByText('MOCK_SELECT_RECOVERY_OPTION') + }) +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/RetryNewTips.test.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/RetryNewTips.test.tsx index 708c847cd70..dcee4c10c56 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/RetryNewTips.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/RetryNewTips.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest' import { screen, waitFor } from '@testing-library/react' import { mockRecoveryContentProps } from '../../__fixtures__' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { RetryNewTips, RetryWithNewTips } from '../RetryNewTips' import { RECOVERY_MAP } from '../../constants' import { SelectRecoveryOption } from '../SelectRecoveryOption' @@ -12,13 +12,13 @@ import { clickButtonLabeled } from '../../__tests__/util' import type { Mock } from 'vitest' -vi.mock('../../../../molecules/Command') +vi.mock('/app/molecules/Command') vi.mock('../SelectRecoveryOption') vi.mock('../../shared', async () => { const actual = await vi.importActual('../../shared') return { ...actual, - ReplaceTips: vi.fn(() =>
    MOCK_REPLACE_TIPS
    ), + TwoColLwInfoAndDeck: vi.fn(() =>
    MOCK_REPLACE_TIPS
    ), SelectTips: vi.fn(() =>
    MOCK_SELECT_TIPS
    ), } }) @@ -97,7 +97,7 @@ describe('RetryNewTips', () => { ...props, recoveryMap: { ...props.recoveryMap, - step: 'UNKNOWN_STEP', + step: 'UNKNOWN_STEP' as any, }, } render(props) @@ -122,19 +122,19 @@ describe('RetryNewTips', () => { describe('RetryWithNewTips', () => { let props: React.ComponentProps - let mockSetRobotInMotion: Mock + let mockhandleMotionRouting: Mock let mockRetryFailedCommand: Mock let mockResumeRun: Mock beforeEach(() => { - mockSetRobotInMotion = vi.fn(() => Promise.resolve()) + mockhandleMotionRouting = vi.fn(() => Promise.resolve()) mockRetryFailedCommand = vi.fn(() => Promise.resolve()) mockResumeRun = vi.fn() props = { ...mockRecoveryContentProps, routeUpdateActions: { - setRobotInMotion: mockSetRobotInMotion, + handleMotionRouting: mockhandleMotionRouting, } as any, recoveryCommands: { retryFailedCommand: mockRetryFailedCommand, @@ -159,7 +159,7 @@ describe('RetryWithNewTips', () => { clickButtonLabeled('Retry now') await waitFor(() => { - expect(mockSetRobotInMotion).toHaveBeenCalledWith( + expect(mockhandleMotionRouting).toHaveBeenCalledWith( true, RECOVERY_MAP.ROBOT_RETRYING_STEP.ROUTE ) @@ -171,7 +171,7 @@ describe('RetryWithNewTips', () => { expect(mockResumeRun).toHaveBeenCalled() }) - expect(mockSetRobotInMotion.mock.invocationCallOrder[0]).toBeLessThan( + expect(mockhandleMotionRouting.mock.invocationCallOrder[0]).toBeLessThan( mockRetryFailedCommand.mock.invocationCallOrder[0] ) expect(mockRetryFailedCommand.mock.invocationCallOrder[0]).toBeLessThan( diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/RetrySameTips.test.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/RetrySameTips.test.tsx index da7e85fcadc..4514c9cc350 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/RetrySameTips.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/RetrySameTips.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest' import { screen, waitFor } from '@testing-library/react' import { mockRecoveryContentProps } from '../../__fixtures__' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { RetrySameTips, RetrySameTipsInfo } from '../RetrySameTips' import { RECOVERY_MAP } from '../../constants' import { SelectRecoveryOption } from '../SelectRecoveryOption' @@ -12,7 +12,7 @@ import { clickButtonLabeled } from '../../__tests__/util' import type { Mock } from 'vitest' -vi.mock('../../../../molecules/Command') +vi.mock('/app/molecules/Command') vi.mock('../SelectRecoveryOption') const render = (props: React.ComponentProps) => { @@ -63,7 +63,7 @@ describe('RetrySameTips', () => { ...props, recoveryMap: { ...props.recoveryMap, - step: 'UNKNOWN_STEP', + step: 'UNKNOWN_STEP' as any, }, } render(props) @@ -73,19 +73,19 @@ describe('RetrySameTips', () => { describe('RetrySameTipsInfo', () => { let props: React.ComponentProps - let mockSetRobotInMotion: Mock + let mockhandleMotionRouting: Mock let mockRetryFailedCommand: Mock let mockResumeRun: Mock beforeEach(() => { - mockSetRobotInMotion = vi.fn(() => Promise.resolve()) + mockhandleMotionRouting = vi.fn(() => Promise.resolve()) mockRetryFailedCommand = vi.fn(() => Promise.resolve()) mockResumeRun = vi.fn() props = { ...mockRecoveryContentProps, routeUpdateActions: { - setRobotInMotion: mockSetRobotInMotion, + handleMotionRouting: mockhandleMotionRouting, } as any, recoveryCommands: { retryFailedCommand: mockRetryFailedCommand, @@ -111,7 +111,7 @@ describe('RetrySameTipsInfo', () => { clickButtonLabeled('Retry now') await waitFor(() => { - expect(mockSetRobotInMotion).toHaveBeenCalledWith( + expect(mockhandleMotionRouting).toHaveBeenCalledWith( true, RECOVERY_MAP.ROBOT_RETRYING_STEP.ROUTE ) @@ -123,7 +123,7 @@ describe('RetrySameTipsInfo', () => { expect(mockResumeRun).toHaveBeenCalled() }) - expect(mockSetRobotInMotion.mock.invocationCallOrder[0]).toBeLessThan( + expect(mockhandleMotionRouting.mock.invocationCallOrder[0]).toBeLessThan( mockRetryFailedCommand.mock.invocationCallOrder[0] ) expect(mockRetryFailedCommand.mock.invocationCallOrder[0]).toBeLessThan( diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/RetryStep.test.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/RetryStep.test.tsx index 40a7095b51d..ec39f5b7c18 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/RetryStep.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/RetryStep.test.tsx @@ -1,19 +1,15 @@ -import * as React from 'react' -import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest' -import { screen, waitFor } from '@testing-library/react' +import type * as React from 'react' +import { describe, it, vi, beforeEach, afterEach } from 'vitest' +import { screen } from '@testing-library/react' import { mockRecoveryContentProps } from '../../__fixtures__' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { RetryStep, RetryStepInfo } from '../RetryStep' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { RetryStep } from '../RetryStep' import { RECOVERY_MAP } from '../../constants' import { SelectRecoveryOption } from '../SelectRecoveryOption' -import { clickButtonLabeled } from '../../__tests__/util' - -import type { Mock } from 'vitest' - -vi.mock('../../../../molecules/Command') +vi.mock('/app/molecules/Command') vi.mock('../SelectRecoveryOption') const render = (props: React.ComponentProps) => { @@ -22,14 +18,6 @@ const render = (props: React.ComponentProps) => { })[0] } -const renderRetryStepInfo = ( - props: React.ComponentProps -) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} - describe('RetryStep', () => { let props: React.ComponentProps @@ -47,12 +35,12 @@ describe('RetryStep', () => { vi.resetAllMocks() }) - it(`renders RetryStepInfo when step is ${RECOVERY_MAP.RETRY_FAILED_COMMAND.STEPS.CONFIRM_RETRY}`, () => { + it(`renders RetryStepInfo when step is ${RECOVERY_MAP.RETRY_STEP.STEPS.CONFIRM_RETRY}`, () => { props = { ...props, recoveryMap: { ...props.recoveryMap, - step: RECOVERY_MAP.RETRY_FAILED_COMMAND.STEPS.CONFIRM_RETRY, + step: RECOVERY_MAP.RETRY_STEP.STEPS.CONFIRM_RETRY, }, } render(props) @@ -64,72 +52,10 @@ describe('RetryStep', () => { ...props, recoveryMap: { ...props.recoveryMap, - step: 'UNKNOWN_STEP', + step: 'UNKNOWN_STEP' as any, }, } render(props) screen.getByText('MOCK_SELECT_RECOVERY_OPTION') }) }) - -describe('RetryStepInfo', () => { - let props: React.ComponentProps - let mockSetRobotInMotion: Mock - let mockRetryFailedCommand: Mock - let mockResumeRun: Mock - - beforeEach(() => { - mockSetRobotInMotion = vi.fn(() => Promise.resolve()) - mockRetryFailedCommand = vi.fn(() => Promise.resolve()) - mockResumeRun = vi.fn() - - props = { - ...mockRecoveryContentProps, - routeUpdateActions: { - setRobotInMotion: mockSetRobotInMotion, - } as any, - recoveryCommands: { - retryFailedCommand: mockRetryFailedCommand, - resumeRun: mockResumeRun, - } as any, - } - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - it('renders the component with the correct text', () => { - renderRetryStepInfo(props) - screen.getByText('Retry step') - screen.queryByText( - 'First, take any necessary actions to prepare the robot to retry the failed step.' - ) - screen.queryByText('Then, close the robot door before proceeding.') - }) - - it('calls the correct routeUpdateActions and recoveryCommands in the correct order when the primary button is clicked', async () => { - renderRetryStepInfo(props) - clickButtonLabeled('Retry now') - - await waitFor(() => { - expect(mockSetRobotInMotion).toHaveBeenCalledWith( - true, - RECOVERY_MAP.ROBOT_RETRYING_STEP.ROUTE - ) - }) - await waitFor(() => { - expect(mockRetryFailedCommand).toHaveBeenCalled() - }) - await waitFor(() => { - expect(mockResumeRun).toHaveBeenCalled() - }) - - expect(mockSetRobotInMotion.mock.invocationCallOrder[0]).toBeLessThan( - mockRetryFailedCommand.mock.invocationCallOrder[0] - ) - expect(mockRetryFailedCommand.mock.invocationCallOrder[0]).toBeLessThan( - mockResumeRun.mock.invocationCallOrder[0] - ) - }) -}) diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/SelectRecoveryOptions.test.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/SelectRecoveryOptions.test.tsx index a70e66d662e..62fe8eea3c8 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/SelectRecoveryOptions.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/SelectRecoveryOptions.test.tsx @@ -1,21 +1,24 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, describe, it, expect, beforeEach } from 'vitest' import { screen, fireEvent } from '@testing-library/react' import { when } from 'vitest-when' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockRecoveryContentProps } from '../../__fixtures__' import { SelectRecoveryOption, - ODDRecoveryOptions, - DesktopRecoveryOptions, + RecoveryOptions, getRecoveryOptions, GENERAL_ERROR_OPTIONS, OVERPRESSURE_WHILE_ASPIRATING_OPTIONS, OVERPRESSURE_PREPARE_TO_ASPIRATE, OVERPRESSURE_WHILE_DISPENSING_OPTIONS, NO_LIQUID_DETECTED_OPTIONS, + TIP_NOT_DETECTED_OPTIONS, + TIP_DROP_FAILED_OPTIONS, + GRIPPER_ERROR_OPTIONS, + STALL_OR_COLLISION_OPTIONS, } from '../SelectRecoveryOption' import { RECOVERY_MAP, ERROR_KINDS } from '../../constants' import { clickButtonLabeled } from '../../__tests__/util' @@ -33,23 +36,15 @@ const renderSelectRecoveryOption = ( )[0] } -const renderODDRecoveryOptions = ( - props: React.ComponentProps +const renderRecoveryOptions = ( + props: React.ComponentProps ) => { - return renderWithProviders(, { + return renderWithProviders(, { i18nInstance: i18n, })[0] } -const renderDesktopRecoveryOptions = ( - props: React.ComponentProps -) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} - describe('SelectRecoveryOption', () => { - const { RETRY_FAILED_COMMAND, RETRY_NEW_TIPS } = RECOVERY_MAP + const { RETRY_STEP, RETRY_NEW_TIPS } = RECOVERY_MAP let props: React.ComponentProps let mockProceedToRouteAndStep: Mock let mockSetSelectedRecoveryOption: Mock @@ -67,8 +62,8 @@ describe('SelectRecoveryOption', () => { ...mockRecoveryContentProps, routeUpdateActions: mockRouteUpdateActions, recoveryMap: { - route: RETRY_FAILED_COMMAND.ROUTE, - step: RETRY_FAILED_COMMAND.STEPS.CONFIRM_RETRY, + route: RETRY_STEP.ROUTE, + step: RETRY_STEP.STEPS.CONFIRM_RETRY, }, tipStatusUtils: { determineTipStatus: vi.fn() } as any, currentRecoveryOptionUtils: { @@ -78,23 +73,32 @@ describe('SelectRecoveryOption', () => { } when(mockGetRecoveryOptionCopy) - .calledWith(RECOVERY_MAP.RETRY_FAILED_COMMAND.ROUTE) + .calledWith(RECOVERY_MAP.RETRY_STEP.ROUTE, expect.any(String)) .thenReturn('Retry step') when(mockGetRecoveryOptionCopy) - .calledWith(RECOVERY_MAP.CANCEL_RUN.ROUTE) + .calledWith(RECOVERY_MAP.RETRY_STEP.ROUTE, ERROR_KINDS.TIP_DROP_FAILED) + .thenReturn('Retry dropping tip') + when(mockGetRecoveryOptionCopy) + .calledWith(RECOVERY_MAP.CANCEL_RUN.ROUTE, expect.any(String)) .thenReturn('Cancel run') when(mockGetRecoveryOptionCopy) - .calledWith(RECOVERY_MAP.RETRY_NEW_TIPS.ROUTE) + .calledWith(RECOVERY_MAP.RETRY_NEW_TIPS.ROUTE, expect.any(String)) .thenReturn('Retry with new tips') when(mockGetRecoveryOptionCopy) - .calledWith(RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.ROUTE) + .calledWith(RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE, expect.any(String)) .thenReturn('Manually fill well and skip to next step') when(mockGetRecoveryOptionCopy) - .calledWith(RECOVERY_MAP.RETRY_SAME_TIPS.ROUTE) + .calledWith(RECOVERY_MAP.RETRY_SAME_TIPS.ROUTE, expect.any(String)) .thenReturn('Retry with same tips') when(mockGetRecoveryOptionCopy) - .calledWith(RECOVERY_MAP.SKIP_STEP_WITH_SAME_TIPS.ROUTE) + .calledWith( + RECOVERY_MAP.SKIP_STEP_WITH_SAME_TIPS.ROUTE, + expect.any(String) + ) .thenReturn('Skip to next step with same tips') + when(mockGetRecoveryOptionCopy) + .calledWith(RECOVERY_MAP.HOME_AND_RETRY.ROUTE, expect.any(String)) + .thenReturn('Home gantry and retry') }) it('sets the selected recovery option when clicking continue', () => { @@ -102,9 +106,7 @@ describe('SelectRecoveryOption', () => { clickButtonLabeled('Continue') - expect(mockSetSelectedRecoveryOption).toHaveBeenCalledWith( - RETRY_FAILED_COMMAND.ROUTE - ) + expect(mockSetSelectedRecoveryOption).toHaveBeenCalledWith(RETRY_STEP.ROUTE) }) it('renders appropriate "General Error" copy and click behavior', () => { @@ -121,9 +123,7 @@ describe('SelectRecoveryOption', () => { fireEvent.click(retryStepOption[0]) clickButtonLabeled('Continue') - expect(mockProceedToRouteAndStep).toHaveBeenCalledWith( - RETRY_FAILED_COMMAND.ROUTE - ) + expect(mockProceedToRouteAndStep).toHaveBeenCalledWith(RETRY_STEP.ROUTE) }) it('renders appropriate "Overpressure while aspirating" copy and click behavior', () => { @@ -131,7 +131,6 @@ describe('SelectRecoveryOption', () => { ...props, errorKind: ERROR_KINDS.OVERPRESSURE_WHILE_ASPIRATING, } - renderSelectRecoveryOption(props) screen.getByText('Choose a recovery action') @@ -167,7 +166,7 @@ describe('SelectRecoveryOption', () => { clickButtonLabeled('Continue') expect(mockProceedToRouteAndStep).toHaveBeenCalledWith( - RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.ROUTE + RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE ) }) @@ -214,124 +213,241 @@ describe('SelectRecoveryOption', () => { RECOVERY_MAP.SKIP_STEP_WITH_SAME_TIPS.ROUTE ) }) -}) -;([ - ['desktop', renderDesktopRecoveryOptions] as const, - ['odd', renderODDRecoveryOptions] as const, -] as const).forEach(([target, renderer]) => { - describe(`RecoveryOptions on ${target}`, () => { - let props: React.ComponentProps - let mockSetSelectedRoute: Mock - let mockGetRecoveryOptionCopy: Mock - - beforeEach(() => { - mockSetSelectedRoute = vi.fn() - mockGetRecoveryOptionCopy = vi.fn() - const generalRecoveryOptions = getRecoveryOptions( - ERROR_KINDS.GENERAL_ERROR - ) - props = { - validRecoveryOptions: generalRecoveryOptions, - setSelectedRoute: mockSetSelectedRoute, - getRecoveryOptionCopy: mockGetRecoveryOptionCopy, - } - - when(mockGetRecoveryOptionCopy) - .calledWith(RECOVERY_MAP.RETRY_FAILED_COMMAND.ROUTE) - .thenReturn('Retry step') - when(mockGetRecoveryOptionCopy) - .calledWith(RECOVERY_MAP.CANCEL_RUN.ROUTE) - .thenReturn('Cancel run') - when(mockGetRecoveryOptionCopy) - .calledWith(RECOVERY_MAP.RETRY_NEW_TIPS.ROUTE) - .thenReturn('Retry with new tips') - when(mockGetRecoveryOptionCopy) - .calledWith(RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.ROUTE) - .thenReturn('Manually fill well and skip to next step') - when(mockGetRecoveryOptionCopy) - .calledWith(RECOVERY_MAP.RETRY_SAME_TIPS.ROUTE) - .thenReturn('Retry with same tips') - when(mockGetRecoveryOptionCopy) - .calledWith(RECOVERY_MAP.SKIP_STEP_WITH_SAME_TIPS.ROUTE) - .thenReturn('Skip to next step with same tips') - when(mockGetRecoveryOptionCopy) - .calledWith(RECOVERY_MAP.SKIP_STEP_WITH_NEW_TIPS.ROUTE) - .thenReturn('Skip to next step with new tips') - when(mockGetRecoveryOptionCopy) - .calledWith(RECOVERY_MAP.IGNORE_AND_SKIP.ROUTE) - .thenReturn('Ignore error and skip to next step') - }) + it('renders appropriate "Tip drop failed" copy and click behavior', () => { + props = { + ...props, + errorKind: ERROR_KINDS.TIP_DROP_FAILED, + } - it('renders valid recovery options for a general error errorKind', () => { - renderer(props) + renderSelectRecoveryOption(props) - screen.getByRole('label', { name: 'Retry step' }) - screen.getByRole('label', { name: 'Cancel run' }) - }) + screen.getByText('Choose a recovery action') - it(`renders valid recovery options for a ${ERROR_KINDS.OVERPRESSURE_WHILE_ASPIRATING} errorKind`, () => { - props = { - ...props, - validRecoveryOptions: OVERPRESSURE_WHILE_ASPIRATING_OPTIONS, - } + const retryDroppingTip = screen.getAllByRole('label', { + name: 'Retry dropping tip', + }) - renderer(props) + fireEvent.click(retryDroppingTip[0]) + clickButtonLabeled('Continue') - screen.getByRole('label', { name: 'Retry with new tips' }) - screen.getByRole('label', { name: 'Cancel run' }) + expect(mockProceedToRouteAndStep).toHaveBeenCalledWith( + RECOVERY_MAP.RETRY_STEP.ROUTE + ) + }) + it('renders appropriate "Stall or collision" copy and click behavior', () => { + props = { + ...props, + errorKind: ERROR_KINDS.STALL_OR_COLLISION, + } + renderSelectRecoveryOption(props) + screen.getByText('Choose a recovery action') + const homeGantryAndRetry = screen.getAllByRole('label', { + name: 'Home gantry and retry', }) + fireEvent.click(homeGantryAndRetry[0]) + clickButtonLabeled('Continue') + expect(mockProceedToRouteAndStep).toHaveBeenCalledWith( + RECOVERY_MAP.HOME_AND_RETRY.ROUTE + ) + }) +}) +describe('RecoveryOptions', () => { + let props: React.ComponentProps + let mockSetSelectedRoute: Mock + let mockGetRecoveryOptionCopy: Mock - it('updates the selectedRoute when a new option is selected', () => { - renderer(props) + beforeEach(() => { + mockSetSelectedRoute = vi.fn() + mockGetRecoveryOptionCopy = vi.fn() + const generalRecoveryOptions = getRecoveryOptions(ERROR_KINDS.GENERAL_ERROR) - fireEvent.click(screen.getByRole('label', { name: 'Cancel run' })) + props = { + errorKind: ERROR_KINDS.GENERAL_ERROR, + validRecoveryOptions: generalRecoveryOptions, + setSelectedRoute: mockSetSelectedRoute, + getRecoveryOptionCopy: mockGetRecoveryOptionCopy, + isOnDevice: true, + } - expect(mockSetSelectedRoute).toHaveBeenCalledWith( - RECOVERY_MAP.CANCEL_RUN.ROUTE + when(mockGetRecoveryOptionCopy) + .calledWith(RECOVERY_MAP.RETRY_STEP.ROUTE, expect.any(String)) + .thenReturn('Retry step') + when(mockGetRecoveryOptionCopy) + .calledWith(RECOVERY_MAP.RETRY_STEP.ROUTE, ERROR_KINDS.TIP_DROP_FAILED) + .thenReturn('Retry dropping tip') + when(mockGetRecoveryOptionCopy) + .calledWith(RECOVERY_MAP.CANCEL_RUN.ROUTE, expect.any(String)) + .thenReturn('Cancel run') + when(mockGetRecoveryOptionCopy) + .calledWith(RECOVERY_MAP.RETRY_NEW_TIPS.ROUTE, expect.any(String)) + .thenReturn('Retry with new tips') + when(mockGetRecoveryOptionCopy) + .calledWith(RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE, expect.any(String)) + .thenReturn('Manually fill well and skip to next step') + when(mockGetRecoveryOptionCopy) + .calledWith(RECOVERY_MAP.RETRY_SAME_TIPS.ROUTE, expect.any(String)) + .thenReturn('Retry with same tips') + when(mockGetRecoveryOptionCopy) + .calledWith( + RECOVERY_MAP.SKIP_STEP_WITH_SAME_TIPS.ROUTE, + expect.any(String) + ) + .thenReturn('Skip to next step with same tips') + when(mockGetRecoveryOptionCopy) + .calledWith( + RECOVERY_MAP.SKIP_STEP_WITH_NEW_TIPS.ROUTE, + expect.any(String) + ) + .thenReturn('Skip to next step with new tips') + when(mockGetRecoveryOptionCopy) + .calledWith(RECOVERY_MAP.IGNORE_AND_SKIP.ROUTE, expect.any(String)) + .thenReturn('Ignore error and skip to next step') + when(mockGetRecoveryOptionCopy) + .calledWith(RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE, expect.any(String)) + .thenReturn('Manually move labware and skip to next step') + when(mockGetRecoveryOptionCopy) + .calledWith( + RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE, + expect.any(String) ) + .thenReturn('Manually replace labware on deck and retry step') + when(mockGetRecoveryOptionCopy) + .calledWith(RECOVERY_MAP.HOME_AND_RETRY.ROUTE, expect.any(String)) + .thenReturn('Home gantry and retry') + }) + + it('renders valid recovery options for a general error errorKind', () => { + renderRecoveryOptions(props) + + screen.getByRole('label', { name: 'Retry step' }) + screen.getByRole('label', { name: 'Cancel run' }) + }) + + it(`renders valid recovery options for a ${ERROR_KINDS.OVERPRESSURE_WHILE_ASPIRATING} errorKind`, () => { + props = { + ...props, + validRecoveryOptions: OVERPRESSURE_WHILE_ASPIRATING_OPTIONS, + } + + renderRecoveryOptions(props) + + screen.getByRole('label', { name: 'Retry with new tips' }) + screen.getByRole('label', { name: 'Cancel run' }) + }) + + it('updates the selectedRoute when a new option is selected', () => { + renderRecoveryOptions(props) + + fireEvent.click(screen.getByRole('label', { name: 'Cancel run' })) + + expect(mockSetSelectedRoute).toHaveBeenCalledWith( + RECOVERY_MAP.CANCEL_RUN.ROUTE + ) + }) + + it(`renders valid recovery options for a ${ERROR_KINDS.NO_LIQUID_DETECTED} errorKind`, () => { + props = { + ...props, + validRecoveryOptions: NO_LIQUID_DETECTED_OPTIONS, + } + + renderRecoveryOptions(props) + + screen.getByRole('label', { + name: 'Manually fill well and skip to next step', }) + screen.getByRole('label', { name: 'Ignore error and skip to next step' }) + screen.getByRole('label', { name: 'Cancel run' }) + }) - it(`renders valid recovery options for a ${ERROR_KINDS.NO_LIQUID_DETECTED} errorKind`, () => { - props = { - ...props, - validRecoveryOptions: NO_LIQUID_DETECTED_OPTIONS, - } + it(`renders valid recovery options for a ${ERROR_KINDS.OVERPRESSURE_PREPARE_TO_ASPIRATE} errorKind`, () => { + props = { + ...props, + validRecoveryOptions: OVERPRESSURE_PREPARE_TO_ASPIRATE, + } - renderer(props) + renderRecoveryOptions(props) - screen.getByRole('label', { - name: 'Manually fill well and skip to next step', - }) - screen.getByRole('label', { name: 'Ignore error and skip to next step' }) - screen.getByRole('label', { name: 'Cancel run' }) + screen.getByRole('label', { name: 'Retry with new tips' }) + screen.getByRole('label', { name: 'Retry with same tips' }) + screen.getByRole('label', { name: 'Cancel run' }) + }) + + it(`renders valid recovery options for a ${ERROR_KINDS.OVERPRESSURE_WHILE_DISPENSING} errorKind`, () => { + props = { + ...props, + validRecoveryOptions: OVERPRESSURE_WHILE_DISPENSING_OPTIONS, + } + + renderRecoveryOptions(props) + + screen.getByRole('label', { name: 'Skip to next step with same tips' }) + screen.getByRole('label', { name: 'Skip to next step with new tips' }) + screen.getByRole('label', { name: 'Cancel run' }) + }) + + it(`renders valid recovery options for a ${ERROR_KINDS.TIP_NOT_DETECTED} errorKind`, () => { + props = { + ...props, + validRecoveryOptions: TIP_NOT_DETECTED_OPTIONS, + } + + renderRecoveryOptions(props) + + screen.getByRole('label', { + name: 'Retry step', + }) + screen.getByRole('label', { + name: 'Ignore error and skip to next step', }) + screen.getByRole('label', { name: 'Cancel run' }) + }) - it(`renders valid recovery options for a ${ERROR_KINDS.OVERPRESSURE_PREPARE_TO_ASPIRATE} errorKind`, () => { - props = { - ...props, - validRecoveryOptions: OVERPRESSURE_PREPARE_TO_ASPIRATE, - } + it(`renders valid recovery options for a ${ERROR_KINDS.TIP_DROP_FAILED} errorKind`, () => { + props = { + ...props, + errorKind: ERROR_KINDS.TIP_DROP_FAILED, + validRecoveryOptions: TIP_DROP_FAILED_OPTIONS, + } - renderer(props) + renderRecoveryOptions(props) - screen.getByRole('label', { name: 'Retry with new tips' }) - screen.getByRole('label', { name: 'Retry with same tips' }) - screen.getByRole('label', { name: 'Cancel run' }) + screen.getByRole('label', { + name: 'Retry dropping tip', }) + screen.getByRole('label', { + name: 'Ignore error and skip to next step', + }) + screen.getByRole('label', { name: 'Cancel run' }) + }) - it(`renders valid recovery options for a ${ERROR_KINDS.OVERPRESSURE_WHILE_DISPENSING} errorKind`, () => { - props = { - ...props, - validRecoveryOptions: OVERPRESSURE_WHILE_DISPENSING_OPTIONS, - } + it(`renders valid recovery options for a ${ERROR_KINDS.GRIPPER_ERROR} errorKind`, () => { + props = { + ...props, + validRecoveryOptions: GRIPPER_ERROR_OPTIONS, + } - renderer(props) + renderRecoveryOptions(props) - screen.getByRole('label', { name: 'Skip to next step with same tips' }) - screen.getByRole('label', { name: 'Skip to next step with new tips' }) - screen.getByRole('label', { name: 'Cancel run' }) + screen.getByRole('label', { + name: 'Manually move labware and skip to next step', + }) + screen.getByRole('label', { + name: 'Manually replace labware on deck and retry step', }) + screen.getByRole('label', { name: 'Cancel run' }) + }) + it(`renders valid recovery options for a ${ERROR_KINDS.STALL_OR_COLLISION} errorKind`, () => { + props = { + ...props, + validRecoveryOptions: STALL_OR_COLLISION_OPTIONS, + } + renderRecoveryOptions(props) + screen.getByRole('label', { + name: 'Home gantry and retry', + }) + screen.getByRole('label', { name: 'Cancel run' }) }) }) @@ -372,4 +488,32 @@ describe('getRecoveryOptions', () => { OVERPRESSURE_WHILE_DISPENSING_OPTIONS ) }) + + it(`returns valid options when the errorKind is ${ERROR_KINDS.TIP_NOT_DETECTED}`, () => { + const overpressureWhileDispensingOptions = getRecoveryOptions( + ERROR_KINDS.TIP_NOT_DETECTED + ) + expect(overpressureWhileDispensingOptions).toBe(TIP_NOT_DETECTED_OPTIONS) + }) + + it(`returns valid options when the errorKind is ${ERROR_KINDS.TIP_DROP_FAILED}`, () => { + const overpressureWhileDispensingOptions = getRecoveryOptions( + ERROR_KINDS.TIP_DROP_FAILED + ) + expect(overpressureWhileDispensingOptions).toBe(TIP_DROP_FAILED_OPTIONS) + }) + + it(`returns valid options when the errorKind is ${ERROR_KINDS.GRIPPER_ERROR}`, () => { + const overpressureWhileDispensingOptions = getRecoveryOptions( + ERROR_KINDS.GRIPPER_ERROR + ) + expect(overpressureWhileDispensingOptions).toBe(GRIPPER_ERROR_OPTIONS) + }) + + it(`returns valid options when the errorKind is ${ERROR_KINDS.STALL_OR_COLLISION}`, () => { + const stallOrCollisionOptions = getRecoveryOptions( + ERROR_KINDS.STALL_OR_COLLISION + ) + expect(stallOrCollisionOptions).toBe(STALL_OR_COLLISION_OPTIONS) + }) }) diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/SkipStepNewTips.test.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/SkipStepNewTips.test.tsx index afb8dfdee48..09afa086dca 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/SkipStepNewTips.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/SkipStepNewTips.test.tsx @@ -1,25 +1,25 @@ -import * as React from 'react' -import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest' -import { screen, waitFor } from '@testing-library/react' +import type * as React from 'react' +import { describe, it, vi, expect, beforeEach } from 'vitest' +import { screen } from '@testing-library/react' import { mockRecoveryContentProps } from '../../__fixtures__' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { SkipStepNewTips, SkipStepWithNewTips } from '../SkipStepNewTips' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { SkipStepNewTips } from '../SkipStepNewTips' import { RECOVERY_MAP } from '../../constants' import { SelectRecoveryOption } from '../SelectRecoveryOption' -import { clickButtonLabeled } from '../../__tests__/util' import type { Mock } from 'vitest' -vi.mock('../../../../molecules/Command') +vi.mock('/app/molecules/Command') vi.mock('../SelectRecoveryOption') vi.mock('../../shared', async () => { const actual = await vi.importActual('../../shared') return { ...actual, - ReplaceTips: vi.fn(() =>
    MOCK_REPLACE_TIPS
    ), SelectTips: vi.fn(() =>
    MOCK_SELECT_TIPS
    ), + TwoColLwInfoAndDeck: vi.fn(() =>
    MOCK_REPLACE_TIPS
    ), + SkipStepInfo: vi.fn(() =>
    MOCK_SKIP_STEP_INFO
    ), } }) @@ -29,14 +29,6 @@ const render = (props: React.ComponentProps) => { })[0] } -const renderSkipStepWithNewTips = ( - props: React.ComponentProps -) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} - describe('SkipStepNewTips', () => { let props: React.ComponentProps let mockProceedToRouteAndStep: Mock @@ -89,7 +81,7 @@ describe('SkipStepNewTips', () => { }, } render(props) - screen.getByText('Skip to next step with new tips') + screen.getByText('MOCK_SKIP_STEP_INFO') }) it('renders SelectRecoveryOption as a fallback', () => { @@ -97,7 +89,7 @@ describe('SkipStepNewTips', () => { ...props, recoveryMap: { ...props.recoveryMap, - step: 'UNKNOWN_STEP', + step: 'UNKNOWN_STEP' as any, }, } render(props) @@ -119,56 +111,3 @@ describe('SkipStepNewTips', () => { ) }) }) - -describe('SkipStepWithNewTips', () => { - let props: React.ComponentProps - let mockSetRobotInMotion: Mock - let mockSkipFailedCommand: Mock - - beforeEach(() => { - mockSetRobotInMotion = vi.fn(() => Promise.resolve()) - mockSkipFailedCommand = vi.fn(() => Promise.resolve()) - - props = { - ...mockRecoveryContentProps, - routeUpdateActions: { - setRobotInMotion: mockSetRobotInMotion, - } as any, - recoveryCommands: { - skipFailedCommand: mockSkipFailedCommand, - } as any, - } - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - it('renders the component with the correct text', () => { - renderSkipStepWithNewTips(props) - screen.getByText('Skip to next step with new tips') - screen.queryByText( - 'The failed dispense step will not be completed. The run will continue from the next step.' - ) - screen.queryByText('Close the robot door before proceeding.') - }) - - it('calls the correct routeUpdateActions and recoveryCommands in the correct order when the primary button is clicked', async () => { - renderSkipStepWithNewTips(props) - clickButtonLabeled('Continue run now') - - await waitFor(() => { - expect(mockSetRobotInMotion).toHaveBeenCalledWith( - true, - RECOVERY_MAP.ROBOT_SKIPPING_STEP.ROUTE - ) - }) - await waitFor(() => { - expect(mockSkipFailedCommand).toHaveBeenCalled() - }) - - expect(mockSetRobotInMotion.mock.invocationCallOrder[0]).toBeLessThan( - mockSkipFailedCommand.mock.invocationCallOrder[0] - ) - }) -}) diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/SkipStepSameTips.test.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/SkipStepSameTips.test.tsx index 3459ce305dd..157825b3322 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/SkipStepSameTips.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/__tests__/SkipStepSameTips.test.tsx @@ -1,19 +1,17 @@ -import * as React from 'react' -import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest' -import { screen, waitFor } from '@testing-library/react' +import type * as React from 'react' +import { describe, it, vi, beforeEach } from 'vitest' +import { screen } from '@testing-library/react' import { mockRecoveryContentProps } from '../../__fixtures__' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { SkipStepSameTips, SkipStepSameTipsInfo } from '../SkipStepSameTips' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { SkipStepSameTips } from '../SkipStepSameTips' import { RECOVERY_MAP } from '../../constants' import { SelectRecoveryOption } from '../SelectRecoveryOption' +import { SkipStepInfo } from '/app/organisms/ErrorRecoveryFlows/shared' -import { clickButtonLabeled } from '../../__tests__/util' - -import type { Mock } from 'vitest' - -vi.mock('../../../../molecules/Command') +vi.mock('/app/molecules/Command') +vi.mock('/app/organisms/ErrorRecoveryFlows/shared') vi.mock('../SelectRecoveryOption') const render = (props: React.ComponentProps) => { @@ -22,14 +20,6 @@ const render = (props: React.ComponentProps) => { })[0] } -const renderSkipStepSameTipsInfo = ( - props: React.ComponentProps -) => { - return renderWithProviders(, { - i18nInstance: i18n, - })[0] -} - describe('SkipStepSameTips', () => { let props: React.ComponentProps @@ -41,6 +31,7 @@ describe('SkipStepSameTips', () => { vi.mocked(SelectRecoveryOption).mockReturnValue(
    MOCK_SELECT_RECOVERY_OPTION
    ) + vi.mocked(SkipStepInfo).mockReturnValue(
    MOCK_SKIP_STEP_INFO
    ) }) it(`renders SkipStepSameTipsInfo when step is ${RECOVERY_MAP.SKIP_STEP_WITH_SAME_TIPS.STEPS.SKIP}`, () => { @@ -52,7 +43,7 @@ describe('SkipStepSameTips', () => { }, } render(props) - screen.getByText('Skip to next step with same tips') + screen.getByText('MOCK_SKIP_STEP_INFO') }) it('renders SelectRecoveryOption as a fallback', () => { @@ -60,64 +51,10 @@ describe('SkipStepSameTips', () => { ...props, recoveryMap: { ...props.recoveryMap, - step: 'UNKNOWN_STEP', + step: 'UNKNOWN_STEP' as any, }, } render(props) screen.getByText('MOCK_SELECT_RECOVERY_OPTION') }) }) - -describe('SkipStepSameTipsInfo', () => { - let props: React.ComponentProps - let mockSetRobotInMotion: Mock - let mockSkipFailedCommand: Mock - - beforeEach(() => { - mockSetRobotInMotion = vi.fn(() => Promise.resolve()) - mockSkipFailedCommand = vi.fn(() => Promise.resolve()) - - props = { - ...mockRecoveryContentProps, - routeUpdateActions: { - setRobotInMotion: mockSetRobotInMotion, - } as any, - recoveryCommands: { - skipFailedCommand: mockSkipFailedCommand, - } as any, - } - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - it('renders the component with the correct text', () => { - renderSkipStepSameTipsInfo(props) - screen.getByText('Skip to next step with same tips') - screen.queryByText( - 'The failed dispense step will not be completed. The run will continue from the next step.' - ) - screen.queryByText('Close the robot door before proceeding.') - }) - - it('calls the correct routeUpdateActions and recoveryCommands in the correct order when the primary button is clicked', async () => { - renderSkipStepSameTipsInfo(props) - clickButtonLabeled('Continue run now') - - await waitFor(() => { - expect(mockSetRobotInMotion).toHaveBeenCalledWith( - true, - RECOVERY_MAP.ROBOT_SKIPPING_STEP.ROUTE - ) - }) - - await waitFor(() => { - expect(mockSkipFailedCommand).toHaveBeenCalled() - }) - - expect(mockSetRobotInMotion.mock.invocationCallOrder[0]).toBeLessThan( - mockSkipFailedCommand.mock.invocationCallOrder[0] - ) - }) -}) diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/index.ts b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/index.ts index 154b315f66c..0ad8f530709 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/index.ts +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryOptions/index.ts @@ -8,3 +8,6 @@ export { RetrySameTips } from './RetrySameTips' export { SkipStepSameTips } from './SkipStepSameTips' export { SkipStepNewTips } from './SkipStepNewTips' export { IgnoreErrorSkipStep } from './IgnoreErrorSkipStep' +export { ManualMoveLwAndSkip } from './ManualMoveLwAndSkip' +export { ManualReplaceLwAndRetry } from './ManualReplaceLwAndRetry' +export { HomeAndRetry } from './HomeAndRetry' diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoverySplash.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoverySplash.tsx new file mode 100644 index 00000000000..153d8c12931 --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/RecoverySplash.tsx @@ -0,0 +1,347 @@ +import { useEffect } from 'react' +import styled, { css } from 'styled-components' +import { useTranslation } from 'react-i18next' + +import { + ALIGN_CENTER, + COLORS, + DIRECTION_COLUMN, + RESPONSIVENESS, + DISPLAY_FLEX, + Flex, + Icon, + JUSTIFY_CENTER, + JUSTIFY_SPACE_BETWEEN, + OVERFLOW_WRAP_BREAK_WORD, + POSITION_ABSOLUTE, + PrimaryButton, + SecondaryButton, + LargeButton, + SPACING, + StyledText, + TEXT_ALIGN_CENTER, + TYPOGRAPHY, + WARNING_TOAST, +} from '@opentrons/components' +import { + RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, + RUN_STATUS_AWAITING_RECOVERY_PAUSED, +} from '@opentrons/api-client' + +import { useErrorName } from './hooks' +import { getErrorKind } from './utils' +import { + BANNER_TEXT_CONTAINER_STYLE, + BANNER_TEXT_CONTENT_STYLE, + RECOVERY_MAP, +} from './constants' +import { RecoveryInterventionModal, StepInfo } from './shared' +import { useToaster } from '../ToasterOven' + +import type { LabwareDefinition2, RobotType } from '@opentrons/shared-data' +import type { ErrorRecoveryFlowsProps } from '.' +import type { + ERUtilsResults, + UseRecoveryTakeoverResult, + useRetainedFailedCommandBySource, +} from './hooks' +import type { RecoveryRoute, RouteStep } from './types' +import type { UseRecoveryAnalyticsResult } from '/app/redux-resources/analytics' + +export function useRecoverySplash( + isOnDevice: boolean, + showERWizard: boolean +): boolean { + // Don't show the splash when desktop ER wizard is active, + // but always show it on the ODD (with or without the wizard rendered above it). + if (isOnDevice) { + return true + } else { + return !showERWizard + } +} + +type RecoverySplashProps = ErrorRecoveryFlowsProps & + ERUtilsResults & { + isOnDevice: boolean + failedCommand: ReturnType + robotType: RobotType + robotName: string + /* Whether the app should resume any paused recovery state without user action. */ + resumePausedRecovery: boolean + toggleERWizAsActiveUser: UseRecoveryTakeoverResult['toggleERWizAsActiveUser'] + analytics: UseRecoveryAnalyticsResult + allRunDefs: LabwareDefinition2[] + } +export function RecoverySplash(props: RecoverySplashProps): JSX.Element | null { + const { + isOnDevice, + toggleERWizAsActiveUser, + routeUpdateActions, + failedCommand, + analytics, + robotName, + runStatus, + recoveryActionMutationUtils, + resumePausedRecovery, + recoveryCommands, + } = props + const { t } = useTranslation('error_recovery') + const errorKind = getErrorKind(failedCommand) + const title = useErrorName(errorKind) + const { makeToast } = useToaster() + + const { proceedToRouteAndStep, handleMotionRouting } = routeUpdateActions + const { reportErrorEvent } = analytics + + const buildTitleHeadingDesktop = (): JSX.Element => { + return ( + + {t('error_on_robot', { robot: robotName })} + + ) + } + + // Resume recovery when the run when the door is closed. + // The CTA/flow for handling a door open event within the ER wizard is different, and because this splash always renders + // behind the wizard, we want to ensure we only implicitly resume recovery when only viewing the splash from this app. + useEffect(() => { + if ( + runStatus === RUN_STATUS_AWAITING_RECOVERY_PAUSED && + resumePausedRecovery + ) { + recoveryActionMutationUtils.resumeRecovery() + } + }, [runStatus, resumePausedRecovery]) + const buildDoorOpenAlert = (): void => { + makeToast(t('close_door_to_resume') as string, WARNING_TOAST) + } + + const handleConditionalClick = (onClick: () => void): void => { + switch (runStatus) { + case RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR: + buildDoorOpenAlert() + break + default: + onClick() + break + } + } + // Do not launch error recovery, but do utilize the wizard's cancel route. + const onCancelClick = (): void => { + const onClick = (): void => { + void toggleERWizAsActiveUser(true, false).then(() => { + reportErrorEvent(failedCommand?.byRunRecord ?? null, 'cancel-run') + void proceedToRouteAndStep(RECOVERY_MAP.CANCEL_RUN.ROUTE) + }) + } + handleConditionalClick(onClick) + } + + const onLaunchERClick = (): void => { + const onClick = (): void => { + void toggleERWizAsActiveUser(true, true) + .then(() => { + reportErrorEvent( + failedCommand?.byRunRecord ?? null, + 'launch-recovery' + ) + }) + .then(() => handleMotionRouting(true)) + .then(() => recoveryCommands.homePipetteZAxes()) + .finally(() => handleMotionRouting(false)) + } + handleConditionalClick(onClick) + } + + const isDisabled = (): boolean => { + switch (runStatus) { + case RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR: + case RUN_STATUS_AWAITING_RECOVERY_PAUSED: + return true + default: + return false + } + } + + // TODO(jh 05-22-24): The hardcoded Z-indexing is non-ideal but must be done to keep the splash page above + // several components in the RunningProtocol page. Investigate why these components have seemingly arbitrary zIndex values + // and devise a better solution to layering modals. + + // TODO(jh 06-07-24): Although unlikely, it's possible that the server doesn't return a failedCommand. Need to handle + // this here or within ER flows. + + // TODO(jh 06-18-24): Instead of passing stepCount internally, we probably want to + // pass it in as a prop to ErrorRecoveryFlows to ameliorate blippy "step = ? -> step = 24" behavior. + if (isOnDevice) { + return ( + + + + + {title} + + + + + + + + + + + ) + } else { + return ( + + + + + + {title} + + + + + + {t('cancel_run')} + + + + {t('launch_recovery_mode')} + + + + + + ) + } +} + +const SplashHeader = styled.h1` + font-weight: ${TYPOGRAPHY.fontWeightBold}; + text-align: ${TYPOGRAPHY.textAlignCenter}; + font-size: ${TYPOGRAPHY.fontSize80}; + line-height: ${TYPOGRAPHY.lineHeight96}; + color: ${COLORS.white}; +` + +const SplashFrame = styled(Flex)` + width: 100%; + height: 100%; + flex-direction: ${DIRECTION_COLUMN}; + justify-content: ${JUSTIFY_CENTER}; + align-items: ${ALIGN_CENTER}; + grid-gap: ${SPACING.spacing40}; + padding-bottom: 0px; +` + +const TEXT_TRUNCATION_STYLE = css` + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + font-size: ${TYPOGRAPHY.fontSize22}; + } +` + +const SHARED_BUTTON_STYLE_ODD = css` + width: 29rem; + height: 13.5rem; +` + +const PRIMARY_BTN_STYLES_DESKTOP = css` + background-color: ${COLORS.red50}; + color: ${COLORS.white}; + + &:active, + &:focus, + &:hover { + background-color: ${COLORS.red55}; + } +` +const BTN_STYLES_DISABLED_DESKTOP = css` + background-color: ${COLORS.grey30}; + color: ${COLORS.grey40}; + border: none; + box-shadow: none; + + &:active, + &:focus, + &:hover { + background-color: ${COLORS.grey30}; + color: ${COLORS.grey40}; + } + cursor: default; +` diff --git a/app/src/organisms/ErrorRecoveryFlows/RecoveryTakeover.tsx b/app/src/organisms/ErrorRecoveryFlows/RecoveryTakeover.tsx index 13ad5c3e2eb..4a28ac24b0c 100644 --- a/app/src/organisms/ErrorRecoveryFlows/RecoveryTakeover.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/RecoveryTakeover.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { @@ -15,14 +15,14 @@ import { RUN_STATUS_AWAITING_RECOVERY_PAUSED, } from '@opentrons/api-client' -import { useUpdateClientDataRecovery } from '../../resources/client_data' -import { TakeoverModal } from '../TakeoverModal/TakeoverModal' +import { useUpdateClientDataRecovery } from '/app/resources/client_data' +import { TakeoverModal } from '/app/organisms/TakeoverModal/TakeoverModal' import { RecoveryInterventionModal } from './shared' import type { ClientDataRecovery, UseUpdateClientDataRecoveryResult, -} from '../../resources/client_data' +} from '/app/resources/client_data' import type { ErrorRecoveryFlowsProps } from '.' import { BANNER_TEXT_CONTAINER_STYLE, @@ -99,7 +99,7 @@ export function RecoveryTakeoverODD({ clearClientData, isRunStatusAwaitingRecovery, }: RecoveryTakeoverProps): JSX.Element { - const [showConfirmation, setShowConfirmation] = React.useState(false) + const [showConfirmation, setShowConfirmation] = useState(false) return ( { + return ( + + {t('error_on_robot', { robot: robotName })} + + ) + } + return ( diff --git a/app/src/organisms/ErrorRecoveryFlows/RunPausedSplash.tsx b/app/src/organisms/ErrorRecoveryFlows/RunPausedSplash.tsx deleted file mode 100644 index 94858293982..00000000000 --- a/app/src/organisms/ErrorRecoveryFlows/RunPausedSplash.tsx +++ /dev/null @@ -1,248 +0,0 @@ -import * as React from 'react' -import styled, { css } from 'styled-components' -import { useTranslation } from 'react-i18next' - -import { - Flex, - Icon, - JUSTIFY_CENTER, - ALIGN_CENTER, - SPACING, - COLORS, - DIRECTION_COLUMN, - POSITION_ABSOLUTE, - TYPOGRAPHY, - OVERFLOW_WRAP_BREAK_WORD, - DISPLAY_FLEX, - JUSTIFY_SPACE_BETWEEN, - TEXT_ALIGN_CENTER, - StyledText, - PrimaryButton, - SecondaryButton, - LargeButton, -} from '@opentrons/components' - -import { useErrorName } from './hooks' -import { getErrorKind } from './utils' - -import { - BANNER_TEXT_CONTAINER_STYLE, - BANNER_TEXT_CONTENT_STYLE, - RECOVERY_MAP, -} from './constants' -import { RecoveryInterventionModal, StepInfo } from './shared' - -import type { RobotType } from '@opentrons/shared-data' -import type { ErrorRecoveryFlowsProps } from '.' -import type { - ERUtilsResults, - UseRecoveryAnalyticsResult, - UseRecoveryTakeoverResult, - useRetainedFailedCommandBySource, -} from './hooks' - -export function useRunPausedSplash( - isOnDevice: boolean, - showERWizard: boolean -): boolean { - // Don't show the splash when desktop ER wizard is active, - // but always show it on the ODD (with or without the wizard rendered above it). - if (isOnDevice) { - return true - } else { - return !showERWizard - } -} - -type RunPausedSplashProps = ERUtilsResults & { - isOnDevice: boolean - failedCommand: ReturnType - protocolAnalysis: ErrorRecoveryFlowsProps['protocolAnalysis'] - robotType: RobotType - robotName: string - toggleERWizAsActiveUser: UseRecoveryTakeoverResult['toggleERWizAsActiveUser'] - analytics: UseRecoveryAnalyticsResult -} -export function RunPausedSplash( - props: RunPausedSplashProps -): JSX.Element | null { - const { - isOnDevice, - toggleERWizAsActiveUser, - routeUpdateActions, - failedCommand, - analytics, - robotName, - } = props - const { t } = useTranslation('error_recovery') - const errorKind = getErrorKind(failedCommand?.byRunRecord ?? null) - const title = useErrorName(errorKind) - - const { proceedToRouteAndStep } = routeUpdateActions - const { reportErrorEvent } = analytics - - const buildTitleHeadingDesktop = (): JSX.Element => { - return ( - - {t('error_on_robot', { robot: robotName })} - - ) - } - - // Do not launch error recovery, but do utilize the wizard's cancel route. - const onCancelClick = (): Promise => { - return toggleERWizAsActiveUser(true, false).then(() => { - reportErrorEvent(failedCommand?.byRunRecord ?? null, 'cancel-run') - void proceedToRouteAndStep(RECOVERY_MAP.CANCEL_RUN.ROUTE) - }) - } - - const onLaunchERClick = (): Promise => { - return toggleERWizAsActiveUser(true, true).then(() => { - reportErrorEvent(failedCommand?.byRunRecord ?? null, 'launch-recovery') - }) - } - - // TODO(jh 05-22-24): The hardcoded Z-indexing is non-ideal but must be done to keep the splash page above - // several components in the RunningProtocol page. Investigate why these components have seemingly arbitrary zIndex values - // and devise a better solution to layering modals. - - // TODO(jh 06-07-24): Although unlikely, it's possible that the server doesn't return a failedCommand. Need to handle - // this here or within ER flows. - - // TODO(jh 06-18-24): Instead of passing stepCount internally, we probably want to - // pass it in as a prop to ErrorRecoveryFlows to ameliorate blippy "step = ? -> step = 24" behavior. - if (isOnDevice) { - return ( - - - - - {title} - - - - - - - - - - - ) - } else { - return ( - - - - - - {title} - - - - - - {t('cancel_run')} - - - - {t('launch_recovery_mode')} - - - - - - ) - } -} - -const SplashHeader = styled.h1` - font-weight: ${TYPOGRAPHY.fontWeightBold}; - text-align: ${TYPOGRAPHY.textAlignCenter}; - font-size: ${TYPOGRAPHY.fontSize80}; - line-height: ${TYPOGRAPHY.lineHeight96}; - color: ${COLORS.white}; -` - -const SplashFrame = styled(Flex)` - width: 100%; - height: 100%; - flex-direction: ${DIRECTION_COLUMN}; - justify-content: ${JUSTIFY_CENTER}; - align-items: ${ALIGN_CENTER}; - grid-gap: ${SPACING.spacing40}; - padding-bottom: 0px; -` - -const SHARED_BUTTON_STYLE_ODD = css` - width: 29rem; - height: 13.5rem; -` - -const PRIMARY_BTN_STYLES_DESKTOP = css` - background-color: ${COLORS.red50}; - color: ${COLORS.white}; - - &:active, - &:focus, - &:hover { - background-color: ${COLORS.red55}; - } -` diff --git a/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts b/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts index 129b2554ca5..1a815b99c1e 100644 --- a/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts +++ b/app/src/organisms/ErrorRecoveryFlows/__fixtures__/index.ts @@ -6,7 +6,7 @@ import { import { RUN_STATUS_AWAITING_RECOVERY } from '@opentrons/api-client' import { RECOVERY_MAP } from '../constants' -import { mockRobotSideAnalysis } from '../../../molecules/Command/__fixtures__' +import { mockRobotSideAnalysis } from '/app/molecules/Command/__fixtures__' import type { LoadedLabware, LabwareDefinition2 } from '@opentrons/shared-data' import type { FailedCommand, RecoveryContentProps } from '../types' @@ -20,13 +20,13 @@ export const mockFailedCommand: FailedCommand = { error: { createdAt: '2024-05-24T13:55:32.595751+00:00', detail: 'No tip detected.', - isDefined: false, + isDefined: true, errorCode: '3003', errorType: 'tipPhysicallyMissing', errorInfo: {}, wrappedErrors: [], id: '123', - }, + } as any, startedAt: '2024-05-24T13:55:19.016799+00:00', id: '1', params: { @@ -53,7 +53,7 @@ export const mockPickUpTipLabware: LoadedLabware = { // TODO: jh(08-07-24): update the "byAnalysis" mockFailedCommand. export const mockRecoveryContentProps: RecoveryContentProps = { - failedCommandByRunRecord: mockFailedCommand, + unvalidatedFailedCommand: mockFailedCommand, failedCommand: { byRunRecord: mockFailedCommand, byAnalysis: mockFailedCommand, @@ -61,7 +61,7 @@ export const mockRecoveryContentProps: RecoveryContentProps = { errorKind: 'GENERAL_ERROR', robotType: FLEX_ROBOT_TYPE, runId: 'MOCK_RUN_ID', - isDoorOpen: false, + doorStatusUtils: { isDoorOpen: false, isProhibitedDoorOpen: false }, isOnDevice: true, runStatus: RUN_STATUS_AWAITING_RECOVERY, recoveryMap: { @@ -73,7 +73,7 @@ export const mockRecoveryContentProps: RecoveryContentProps = { tipStatusUtils: {} as any, currentRecoveryOptionUtils: {} as any, failedLabwareUtils: { pickUpTipLabware: mockPickUpTipLabware } as any, - failedPipetteInfo: {} as any, + failedPipetteUtils: {} as any, deckMapUtils: { setSelectedLocation: () => {} } as any, stepCounts: {} as any, protocolAnalysis: mockRobotSideAnalysis, @@ -92,4 +92,5 @@ export const mockRecoveryContentProps: RecoveryContentProps = { reportActionSelectedEvent: () => {}, reportActionSelectedResult: () => {}, }, + allRunDefs: [], } diff --git a/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx b/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx index 8ed839887ef..04719afca56 100644 --- a/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryFlows.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, describe, expect, it, beforeEach } from 'vitest' import { screen, renderHook } from '@testing-library/react' @@ -8,30 +8,38 @@ import { RUN_STATUS_RUNNING, RUN_STATUS_STOP_REQUESTED, } from '@opentrons/api-client' +import { getLoadedLabwareDefinitionsByUri } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockFailedCommand } from '../__fixtures__' import { ErrorRecoveryFlows, useErrorRecoveryFlows } from '..' import { useCurrentlyRecoveringFrom, useERUtils, - useShowDoorInfo, - useRecoveryAnalytics, useRecoveryTakeover, } from '../hooks' -import { getIsOnDevice } from '../../../redux/config' +import { useRecoveryAnalytics } from '/app/redux-resources/analytics' +import { getIsOnDevice } from '/app/redux/config' import { useERWizard, ErrorRecoveryWizard } from '../ErrorRecoveryWizard' -import { useRunPausedSplash, RunPausedSplash } from '../RunPausedSplash' +import { useRecoverySplash, RecoverySplash } from '../RecoverySplash' import type { RunStatus } from '@opentrons/api-client' vi.mock('../ErrorRecoveryWizard') vi.mock('../hooks') vi.mock('../useRecoveryCommands') -vi.mock('../../../redux/config') -vi.mock('../RunPausedSplash') +vi.mock('/app/redux/config') +vi.mock('../RecoverySplash') +vi.mock('/app/redux-resources/analytics') vi.mock('@opentrons/react-api-client') +vi.mock('@opentrons/shared-data', async () => { + const actual = await vi.importActual('@opentrons/shared-data') + return { + ...actual, + getLoadedLabwareDefinitionsByUri: vi.fn(), + } +}) vi.mock('react-redux', async () => { const actual = await vi.importActual('react-redux') return { @@ -138,22 +146,22 @@ describe('ErrorRecoveryFlows', () => { beforeEach(() => { props = { runStatus: RUN_STATUS_AWAITING_RECOVERY, - failedCommandByRunRecord: mockFailedCommand, + unvalidatedFailedCommand: mockFailedCommand, runId: 'MOCK_RUN_ID', - protocolAnalysis: {} as any, + protocolAnalysis: null, } vi.mocked(ErrorRecoveryWizard).mockReturnValue(
    MOCK WIZARD
    ) - vi.mocked(RunPausedSplash).mockReturnValue( -
    MOCK RUN PAUSED SPLASH
    - ) + vi.mocked(RecoverySplash).mockReturnValue(
    MOCK RUN PAUSED SPLASH
    ) vi.mocked(useERWizard).mockReturnValue({ hasLaunchedRecovery: true, toggleERWizard: () => Promise.resolve(), showERWizard: true, }) - vi.mocked(useRunPausedSplash).mockReturnValue(true) - vi.mocked(useERUtils).mockReturnValue({ routeUpdateActions: {} } as any) - vi.mocked(useShowDoorInfo).mockReturnValue(false) + vi.mocked(useRecoverySplash).mockReturnValue(true) + vi.mocked(useERUtils).mockReturnValue({ + routeUpdateActions: {}, + doorStatusUtils: { isDoorOpen: false, isProhibitedDoorOpen: false }, + } as any) vi.mocked(useRecoveryAnalytics).mockReturnValue({ reportErrorEvent: vi.fn(), } as any) @@ -165,6 +173,7 @@ describe('ErrorRecoveryFlows', () => { intent: 'recovering', showTakeover: false, }) + vi.mocked(getLoadedLabwareDefinitionsByUri).mockReturnValue({}) }) it('renders the wizard when showERWizard is true', () => { @@ -173,13 +182,17 @@ describe('ErrorRecoveryFlows', () => { }) it('renders the wizard when isDoorOpen is true', () => { - vi.mocked(useShowDoorInfo).mockReturnValue(true) vi.mocked(useERWizard).mockReturnValue({ hasLaunchedRecovery: false, toggleERWizard: () => Promise.resolve(), showERWizard: false, }) + vi.mocked(useERUtils).mockReturnValue({ + routeUpdateActions: {}, + doorStatusUtils: { isDoorOpen: true, isProhibitedDoorOpen: true }, + } as any) + render(props) screen.getByText('MOCK WIZARD') }) @@ -190,7 +203,10 @@ describe('ErrorRecoveryFlows', () => { toggleERWizard: () => Promise.resolve(), showERWizard: false, }) - vi.mocked(useShowDoorInfo).mockReturnValue(false) + vi.mocked(useERUtils).mockReturnValue({ + routeUpdateActions: {}, + doorStatusUtils: { isDoorOpen: false, isProhibitedDoorOpen: false }, + } as any) render(props) expect(screen.queryByText('MOCK WIZARD')).not.toBeInTheDocument() @@ -202,7 +218,7 @@ describe('ErrorRecoveryFlows', () => { }) it('does not render the splash when showSplash is false', () => { - vi.mocked(useRunPausedSplash).mockReturnValue(false) + vi.mocked(useRecoverySplash).mockReturnValue(false) render(props) expect(screen.queryByText('MOCK RUN PAUSED SPLASH')).not.toBeInTheDocument() }) diff --git a/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryWizard.test.tsx b/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryWizard.test.tsx index 1e80b53779d..d97072e45f3 100644 --- a/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryWizard.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/__tests__/ErrorRecoveryWizard.test.tsx @@ -1,13 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, describe, it, expect, beforeEach } from 'vitest' -import { renderHook, act, screen, waitFor } from '@testing-library/react' +import { renderHook, act, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockRecoveryContentProps } from '../__fixtures__' import { ErrorRecoveryContent, - useInitialPipetteHome, useERWizard, ErrorRecoveryComponent, } from '../ErrorRecoveryWizard' @@ -23,24 +22,31 @@ import { SkipStepNewTips, SkipStepSameTips, IgnoreErrorSkipStep, + ManualReplaceLwAndRetry, + ManualMoveLwAndSkip, + HomeAndRetry, } from '../RecoveryOptions' import { RecoveryInProgress } from '../RecoveryInProgress' import { RecoveryError } from '../RecoveryError' import { RecoveryDoorOpen } from '../RecoveryDoorOpen' -import { useErrorDetailsModal, ErrorDetailsModal } from '../shared' - -import type { Mock } from 'vitest' +import { + useErrorDetailsModal, + ErrorDetailsModal, + RecoveryDoorOpenSpecial, +} from '../shared' vi.mock('../RecoveryOptions') vi.mock('../RecoveryInProgress') vi.mock('../RecoveryError') vi.mock('../RecoveryDoorOpen') +vi.mock('../hooks') vi.mock('../shared', async importOriginal => { const actual = await importOriginal() return { ...actual, useErrorDetailsModal: vi.fn(), ErrorDetailsModal: vi.fn(), + RecoveryDoorOpenSpecial: vi.fn(), } }) describe('useERWizard', () => { @@ -134,7 +140,10 @@ describe('ErrorRecoveryComponent', () => { }) it('renders the recovery door modal when isDoorOpen is true', () => { - props = { ...props, isDoorOpen: true } + props = { + ...props, + doorStatusUtils: { isProhibitedDoorOpen: true, isDoorOpen: true }, + } renderRecoveryComponent(props) @@ -159,7 +168,7 @@ const renderRecoveryContent = ( describe('ErrorRecoveryContent', () => { const { OPTION_SELECTION, - RETRY_FAILED_COMMAND, + RETRY_STEP, ROBOT_CANCELING, ROBOT_RESUMING, ROBOT_IN_MOTION, @@ -168,13 +177,19 @@ describe('ErrorRecoveryContent', () => { ROBOT_SKIPPING_STEP, RETRY_NEW_TIPS, RETRY_SAME_TIPS, - FILL_MANUALLY_AND_SKIP, + MANUAL_FILL_AND_SKIP, SKIP_STEP_WITH_SAME_TIPS, SKIP_STEP_WITH_NEW_TIPS, IGNORE_AND_SKIP, CANCEL_RUN, DROP_TIP_FLOWS, ERROR_WHILE_RECOVERING, + ROBOT_DOOR_OPEN, + ROBOT_DOOR_OPEN_SPECIAL, + ROBOT_RELEASING_LABWARE, + MANUAL_REPLACE_AND_RETRY, + MANUAL_MOVE_AND_SKIP, + HOME_AND_RETRY, } = RECOVERY_MAP let props: React.ComponentProps @@ -199,9 +214,20 @@ describe('ErrorRecoveryContent', () => {
    MOCK_SKIP_STEP_SAME_TIPS
    ) vi.mocked(SkipStepNewTips).mockReturnValue(
    MOCK_STEP_NEW_TIPS
    ) + vi.mocked(ManualReplaceLwAndRetry).mockReturnValue( +
    MOCK_REPLACE_LW_AND_RETRY
    + ) + vi.mocked(ManualMoveLwAndSkip).mockReturnValue( +
    MOCK_MOVE_LW_AND_SKIP
    + ) vi.mocked(IgnoreErrorSkipStep).mockReturnValue(
    MOCK_IGNORE_ERROR_SKIP_STEP
    ) + vi.mocked(RecoveryDoorOpen).mockReturnValue(
    MOCK_DOOR_OPEN
    ) + vi.mocked(RecoveryDoorOpenSpecial).mockReturnValue( +
    MOCK_DOOR_OPEN_SPECIAL
    + ) + vi.mocked(HomeAndRetry).mockReturnValue(
    MOCK_HOME_AND_RETRY
    ) }) it(`returns SelectRecoveryOption when the route is ${OPTION_SELECTION.ROUTE}`, () => { @@ -210,12 +236,12 @@ describe('ErrorRecoveryContent', () => { screen.getByText('MOCK_SELECT_RECOVERY_OPTION') }) - it(`returns ResumeRun when the route is ${RETRY_FAILED_COMMAND.ROUTE}`, () => { + it(`returns ResumeRun when the route is ${RETRY_STEP.ROUTE}`, () => { props = { ...props, recoveryMap: { ...props.recoveryMap, - route: RETRY_FAILED_COMMAND.ROUTE, + route: RETRY_STEP.ROUTE, }, } renderRecoveryContent(props) @@ -223,7 +249,7 @@ describe('ErrorRecoveryContent', () => { screen.getByText('MOCK_RESUME_RUN') }) - it(`returns ManageTips when the route is ${DROP_TIP_FLOWS.ROUTE}`, () => { + it(`returns appropriate view when the route is ${DROP_TIP_FLOWS.ROUTE}`, () => { props = { ...props, recoveryMap: { @@ -236,7 +262,7 @@ describe('ErrorRecoveryContent', () => { screen.getByText('MOCK_DROP_TIP_FLOWS') }) - it(`returns CancelRun when the route is ${CANCEL_RUN.ROUTE}`, () => { + it(`returns appropriate view when the route is ${CANCEL_RUN.ROUTE}`, () => { props = { ...props, recoveryMap: { @@ -249,7 +275,7 @@ describe('ErrorRecoveryContent', () => { screen.getByText('MOCK_CANCEL_RUN') }) - it(`returns RetryNewTips when the route is ${RETRY_NEW_TIPS.ROUTE}`, () => { + it(`returns appropriate view when the route is ${RETRY_NEW_TIPS.ROUTE}`, () => { props = { ...props, recoveryMap: { @@ -262,7 +288,7 @@ describe('ErrorRecoveryContent', () => { screen.getByText('MOCK_RETRY_NEW_TIPS') }) - it(`returns RetrySameTips when the route is ${RETRY_SAME_TIPS.ROUTE}`, () => { + it(`returns appropriate view when the route is ${RETRY_SAME_TIPS.ROUTE}`, () => { props = { ...props, recoveryMap: { @@ -275,12 +301,12 @@ describe('ErrorRecoveryContent', () => { screen.getByText('MOCK_RETRY_SAME_TIPS') }) - it(`returns RetrySameTips when the route is ${FILL_MANUALLY_AND_SKIP.ROUTE}`, () => { + it(`returns appropriate view when the route is ${MANUAL_FILL_AND_SKIP.ROUTE}`, () => { props = { ...props, recoveryMap: { ...props.recoveryMap, - route: FILL_MANUALLY_AND_SKIP.ROUTE, + route: MANUAL_FILL_AND_SKIP.ROUTE, }, } renderRecoveryContent(props) @@ -288,7 +314,7 @@ describe('ErrorRecoveryContent', () => { screen.getByText('MOCK_FILL_WELL_AND_SKIP') }) - it(`returns RetrySameTips when the route is ${SKIP_STEP_WITH_SAME_TIPS.ROUTE}`, () => { + it(`returns appropriate view when the route is ${SKIP_STEP_WITH_SAME_TIPS.ROUTE}`, () => { props = { ...props, recoveryMap: { @@ -301,7 +327,7 @@ describe('ErrorRecoveryContent', () => { screen.getByText('MOCK_SKIP_STEP_SAME_TIPS') }) - it(`returns RetrySameTips when the route is ${SKIP_STEP_WITH_NEW_TIPS.ROUTE}`, () => { + it(`returns appropriate view when the route is ${SKIP_STEP_WITH_NEW_TIPS.ROUTE}`, () => { props = { ...props, recoveryMap: { @@ -314,7 +340,7 @@ describe('ErrorRecoveryContent', () => { screen.getByText('MOCK_STEP_NEW_TIPS') }) - it(`returns RetrySameTips when the route is ${IGNORE_AND_SKIP.ROUTE}`, () => { + it(`returns appropriate view when the route is ${IGNORE_AND_SKIP.ROUTE}`, () => { props = { ...props, recoveryMap: { @@ -327,6 +353,32 @@ describe('ErrorRecoveryContent', () => { screen.getByText('MOCK_IGNORE_ERROR_SKIP_STEP') }) + it(`returns appropriate view when the route is ${MANUAL_MOVE_AND_SKIP.ROUTE}`, () => { + props = { + ...props, + recoveryMap: { + ...props.recoveryMap, + route: MANUAL_MOVE_AND_SKIP.ROUTE, + }, + } + renderRecoveryContent(props) + + screen.getByText('MOCK_MOVE_LW_AND_SKIP') + }) + + it(`returns appropriate view when the route is ${MANUAL_REPLACE_AND_RETRY.ROUTE}`, () => { + props = { + ...props, + recoveryMap: { + ...props.recoveryMap, + route: MANUAL_REPLACE_AND_RETRY.ROUTE, + }, + } + renderRecoveryContent(props) + + screen.getByText('MOCK_REPLACE_LW_AND_RETRY') + }) + it(`returns RecoveryError when the route is ${ERROR_WHILE_RECOVERING.ROUTE}`, () => { props = { ...props, @@ -417,74 +469,56 @@ describe('ErrorRecoveryContent', () => { screen.getByText('MOCK_IN_PROGRESS') }) -}) -describe('useInitialPipetteHome', () => { - let mockZHomePipetteZAxes: Mock - let mockSetRobotInMotion: Mock - let mockRecoveryCommands: any - let mockRouteUpdateActions: any + it(`returns RecoveryInProgressModal when the route is ${ROBOT_RELEASING_LABWARE.ROUTE}`, () => { + props = { + ...props, + recoveryMap: { + ...props.recoveryMap, + route: ROBOT_RELEASING_LABWARE.ROUTE, + }, + } + renderRecoveryContent(props) - beforeEach(() => { - mockZHomePipetteZAxes = vi.fn() - mockSetRobotInMotion = vi.fn() - - mockSetRobotInMotion.mockResolvedValue(() => mockZHomePipetteZAxes()) - mockZHomePipetteZAxes.mockResolvedValue(() => mockSetRobotInMotion()) - - mockRecoveryCommands = { - homePipetteZAxes: mockZHomePipetteZAxes, - } as any - mockRouteUpdateActions = { - setRobotInMotion: mockSetRobotInMotion, - } as any + screen.getByText('MOCK_IN_PROGRESS') }) - it('does not z-home the pipettes if error recovery was not launched', () => { - renderHook(() => - useInitialPipetteHome({ - hasLaunchedRecovery: false, - recoveryCommands: mockRecoveryCommands, - routeUpdateActions: mockRouteUpdateActions, - }) - ) + it(`returns RecoveryDoorOpen when the route is ${ROBOT_DOOR_OPEN.ROUTE}`, () => { + props = { + ...props, + recoveryMap: { + ...props.recoveryMap, + route: ROBOT_DOOR_OPEN.ROUTE, + }, + } + renderRecoveryContent(props) - expect(mockSetRobotInMotion).not.toHaveBeenCalled() + screen.getByText('MOCK_DOOR_OPEN') }) - it('sets the motion screen properly and z-homes all pipettes only on the initial render of Error Recovery', async () => { - const { rerender } = renderHook(() => - useInitialPipetteHome({ - hasLaunchedRecovery: true, - recoveryCommands: mockRecoveryCommands, - routeUpdateActions: mockRouteUpdateActions, - }) - ) - - await waitFor(() => { - expect(mockSetRobotInMotion).toHaveBeenCalledWith(true) - }) - await waitFor(() => { - expect(mockZHomePipetteZAxes).toHaveBeenCalledTimes(1) - }) - await waitFor(() => { - expect(mockSetRobotInMotion).toHaveBeenCalledWith(false) - }) + it(`returns RecoveryDoorOpenSpecial when the route is ${ROBOT_DOOR_OPEN_SPECIAL.ROUTE}`, () => { + props = { + ...props, + recoveryMap: { + ...props.recoveryMap, + route: ROBOT_DOOR_OPEN_SPECIAL.ROUTE, + }, + } + renderRecoveryContent(props) - expect(mockSetRobotInMotion.mock.invocationCallOrder[0]).toBeLessThan( - mockZHomePipetteZAxes.mock.invocationCallOrder[0] - ) - expect(mockZHomePipetteZAxes.mock.invocationCallOrder[0]).toBeLessThan( - mockSetRobotInMotion.mock.invocationCallOrder[1] - ) + screen.getByText('MOCK_DOOR_OPEN_SPECIAL') + }) - rerender() + it(`returns HomeAndRetry when the route is ${HOME_AND_RETRY.ROUTE}`, () => { + props = { + ...props, + recoveryMap: { + ...props.recoveryMap, + route: HOME_AND_RETRY.ROUTE, + }, + } + renderRecoveryContent(props) - await waitFor(() => { - expect(mockSetRobotInMotion).toHaveBeenCalledTimes(2) - }) - await waitFor(() => { - expect(mockZHomePipetteZAxes).toHaveBeenCalledTimes(1) - }) + screen.getByText('MOCK_HOME_AND_RETRY') }) }) diff --git a/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryDoorOpen.test.tsx b/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryDoorOpen.test.tsx index e224d9cb33b..a9fc92e1b84 100644 --- a/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryDoorOpen.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryDoorOpen.test.tsx @@ -1,15 +1,16 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, beforeEach, vi, expect } from 'vitest' + import { screen } from '@testing-library/react' import { RUN_STATUS_AWAITING_RECOVERY_PAUSED } from '@opentrons/api-client' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { mockRecoveryContentProps } from '../__fixtures__' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { RecoveryDoorOpen } from '../RecoveryDoorOpen' +import { clickButtonLabeled } from './util' import type { Mock } from 'vitest' -import { clickButtonLabeled } from './util' const render = (props: React.ComponentProps) => { return renderWithProviders(, { @@ -20,9 +21,11 @@ const render = (props: React.ComponentProps) => { describe('RecoveryDoorOpen', () => { let props: React.ComponentProps let mockResumeRecovery: Mock + let mockProceedToRouteAndStep: Mock beforeEach(() => { - mockResumeRecovery = vi.fn() + mockResumeRecovery = vi.fn().mockResolvedValue(undefined) + mockProceedToRouteAndStep = vi.fn() props = { ...mockRecoveryContentProps, recoveryActionMutationUtils: { @@ -30,6 +33,10 @@ describe('RecoveryDoorOpen', () => { isResumeRecoveryLoading: false, }, runStatus: RUN_STATUS_AWAITING_RECOVERY_PAUSED, + routeUpdateActions: { + stashedMap: null, + proceedToRouteAndStep: mockProceedToRouteAndStep, + } as any, } }) @@ -50,4 +57,22 @@ describe('RecoveryDoorOpen', () => { expect(mockResumeRecovery).toHaveBeenCalledTimes(1) }) + + it('calls proceedToRouteAndStep after resumeRecovery if stashedMap is provided', async () => { + const stashedMap = { route: 'testRoute', step: 'testStep' } as any + props.routeUpdateActions.stashedMap = stashedMap + + render(props) + + clickButtonLabeled('Resume') + + await vi.waitFor(() => { + expect(mockResumeRecovery).toHaveBeenCalledTimes(1) + expect(mockProceedToRouteAndStep).toHaveBeenCalledTimes(1) + expect(mockProceedToRouteAndStep).toHaveBeenCalledWith( + stashedMap.route, + stashedMap.step + ) + }) + }) }) diff --git a/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryError.test.tsx b/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryError.test.tsx index 0360d5d6710..f46f3f949ba 100644 --- a/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryError.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryError.test.tsx @@ -1,11 +1,11 @@ /* eslint-disable testing-library/prefer-presence-queries */ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, expect, beforeEach } from 'vitest' import { screen, fireEvent, waitFor } from '@testing-library/react' import { mockRecoveryContentProps } from '../__fixtures__' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { RecoveryError } from '../RecoveryError' import { RECOVERY_MAP } from '../constants' @@ -23,21 +23,23 @@ describe('RecoveryError', () => { let props: React.ComponentProps let proceedToRouteAndStepMock: Mock let getRecoverOptionCopyMock: Mock - let setRobotInMotionMock: Mock + let handleMotionRoutingMock: Mock let homePipetteZAxesMock: Mock + let updateSubMapMock: Mock beforeEach(() => { proceedToRouteAndStepMock = vi.fn() getRecoverOptionCopyMock = vi.fn() - setRobotInMotionMock = vi.fn().mockResolvedValue(undefined) + handleMotionRoutingMock = vi.fn().mockResolvedValue(undefined) homePipetteZAxesMock = vi.fn().mockResolvedValue(undefined) + updateSubMapMock = vi.fn() props = { ...mockRecoveryContentProps, routeUpdateActions: { ...mockRecoveryContentProps.routeUpdateActions, proceedToRouteAndStep: proceedToRouteAndStepMock, - setRobotInMotion: setRobotInMotionMock, + handleMotionRouting: handleMotionRoutingMock, }, recoveryCommands: { ...mockRecoveryContentProps.recoveryCommands, @@ -48,6 +50,7 @@ describe('RecoveryError', () => { route: ERROR_WHILE_RECOVERING.ROUTE, step: ERROR_WHILE_RECOVERING.STEPS.RECOVERY_ACTION_FAILED, }, + subMapUtils: { subMap: null, updateSubMap: updateSubMapMock }, } getRecoverOptionCopyMock.mockReturnValue('Retry step') @@ -95,7 +98,7 @@ describe('RecoveryError', () => { expect(screen.queryAllByText('Continue to drop tip')[0]).toBeInTheDocument() }) - it(`renders RecoveryDropTipFlowErrors when step is ${ERROR_WHILE_RECOVERING.STEPS.DROP_TIP_TIP_DROP_FAILED}`, () => { + it(`renders RecoveryDropTipFlowErrors when step is ${ERROR_WHILE_RECOVERING.STEPS.DROP_TIP_TIP_DROP_FAILED} and resets the submap`, () => { props.recoveryMap.step = RECOVERY_MAP.ERROR_WHILE_RECOVERING.STEPS.DROP_TIP_TIP_DROP_FAILED render(props) @@ -107,6 +110,7 @@ describe('RecoveryError', () => { )[0] ).toBeInTheDocument() expect(screen.queryAllByText('Return to menu')[0]).toBeInTheDocument() + expect(updateSubMapMock).toHaveBeenCalledWith(null) }) it(`calls proceedToRouteAndStep with ${RECOVERY_MAP.OPTION_SELECTION.ROUTE} when the "Return to menu" button is clicked in RecoveryDropTipFlowErrors with step ${ERROR_WHILE_RECOVERING.STEPS.DROP_TIP_GENERAL_ERROR}`, () => { @@ -151,14 +155,14 @@ describe('RecoveryError', () => { fireEvent.click(screen.queryAllByText('Back to menu')[0]) - expect(setRobotInMotionMock).toHaveBeenCalledWith(true) + expect(handleMotionRoutingMock).toHaveBeenCalledWith(true) await waitFor(() => { expect(homePipetteZAxesMock).toHaveBeenCalled() }) await waitFor(() => { - expect(setRobotInMotionMock).toHaveBeenCalledWith(false) + expect(handleMotionRoutingMock).toHaveBeenCalledWith(false) }) await waitFor(() => { @@ -167,17 +171,17 @@ describe('RecoveryError', () => { ) }) - expect(setRobotInMotionMock).toHaveBeenCalledTimes(2) + expect(handleMotionRoutingMock).toHaveBeenCalledTimes(2) expect(homePipetteZAxesMock).toHaveBeenCalledTimes(1) expect(proceedToRouteAndStepMock).toHaveBeenCalledTimes(1) - expect(setRobotInMotionMock.mock.invocationCallOrder[0]).toBeLessThan( + expect(handleMotionRoutingMock.mock.invocationCallOrder[0]).toBeLessThan( homePipetteZAxesMock.mock.invocationCallOrder[0] ) expect(homePipetteZAxesMock.mock.invocationCallOrder[0]).toBeLessThan( - setRobotInMotionMock.mock.invocationCallOrder[1] + handleMotionRoutingMock.mock.invocationCallOrder[1] ) - expect(setRobotInMotionMock.mock.invocationCallOrder[1]).toBeLessThan( + expect(handleMotionRoutingMock.mock.invocationCallOrder[1]).toBeLessThan( proceedToRouteAndStepMock.mock.invocationCallOrder[0] ) }) diff --git a/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryInProgress.test.tsx b/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryInProgress.test.tsx index 0f6f02aa4b2..2dfa5711644 100644 --- a/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryInProgress.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryInProgress.test.tsx @@ -1,11 +1,15 @@ -import * as React from 'react' -import { beforeEach, describe, it } from 'vitest' -import { screen } from '@testing-library/react' +import type * as React from 'react' +import { beforeEach, describe, it, vi, afterEach, expect } from 'vitest' +import { act, renderHook, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockRecoveryContentProps } from '../__fixtures__' -import { RecoveryInProgress } from '../RecoveryInProgress' +import { + RecoveryInProgress, + useGripperRelease, + GRIPPER_RELEASE_COUNTDOWN_S, +} from '../RecoveryInProgress' import { RECOVERY_MAP } from '../constants' const render = (props: React.ComponentProps) => { @@ -22,6 +26,7 @@ describe('RecoveryInProgress', () => { ROBOT_RETRYING_STEP, ROBOT_PICKING_UP_TIPS, ROBOT_SKIPPING_STEP, + ROBOT_RELEASING_LABWARE, } = RECOVERY_MAP let props: React.ComponentProps @@ -32,6 +37,15 @@ describe('RecoveryInProgress', () => { route: ROBOT_IN_MOTION.ROUTE, step: ROBOT_IN_MOTION.STEPS.IN_MOTION, }, + recoveryCommands: { + releaseGripperJaws: vi.fn(() => Promise.resolve()), + homeExceptPlungers: vi.fn(() => Promise.resolve()), + } as any, + routeUpdateActions: { + handleMotionRouting: vi.fn(() => Promise.resolve()), + proceedNextStep: vi.fn(() => Promise.resolve()), + proceedToRouteAndStep: vi.fn(() => Promise.resolve()), + } as any, } }) @@ -105,4 +119,232 @@ describe('RecoveryInProgress', () => { screen.getByText('Stand back, skipping to next step') }) + + it(`renders appropriate copy when the route is ${ROBOT_RELEASING_LABWARE.ROUTE}`, () => { + props = { + ...props, + recoveryMap: { + route: ROBOT_RELEASING_LABWARE.ROUTE, + step: ROBOT_RELEASING_LABWARE.STEPS.RELEASING_LABWARE, + }, + } + render(props) + + screen.getByText('Gripper will release labware in 3 seconds') + }) + + it('updates countdown for gripper release', () => { + vi.useFakeTimers() + props = { + ...props, + recoveryMap: { + route: ROBOT_RELEASING_LABWARE.ROUTE, + step: ROBOT_RELEASING_LABWARE.STEPS.RELEASING_LABWARE, + }, + } + render(props) + + screen.getByText('Gripper will release labware in 3 seconds') + + act(() => { + vi.advanceTimersByTime(1000) + }) + + screen.getByText('Gripper will release labware in 2 seconds') + + act(() => { + vi.advanceTimersByTime(GRIPPER_RELEASE_COUNTDOWN_S * 1000 - 1000) + }) + + screen.getByText('Gripper releasing labware') + }) +}) + +describe('useGripperRelease', () => { + const mockProps = { + recoveryMap: { + route: RECOVERY_MAP.ROBOT_RELEASING_LABWARE.ROUTE, + step: RECOVERY_MAP.ROBOT_RELEASING_LABWARE.STEPS.RELEASING_LABWARE, + }, + recoveryCommands: { + releaseGripperJaws: vi.fn().mockResolvedValue(undefined), + homeExceptPlungers: vi.fn().mockResolvedValue(undefined), + }, + routeUpdateActions: { + proceedToRouteAndStep: vi.fn().mockResolvedValue(undefined), + proceedNextStep: vi.fn().mockResolvedValue(undefined), + handleMotionRouting: vi.fn().mockResolvedValue(undefined), + }, + currentRecoveryOptionUtils: { + selectedRecoveryOption: RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE, + }, + doorStatusUtils: { isDoorOpen: false }, + } as any + + beforeEach(() => { + vi.useFakeTimers() + vi.clearAllMocks() + }) + + afterEach(() => { + vi.useRealTimers() + }) + + it('counts down from 3 seconds', () => { + const { result } = renderHook(() => useGripperRelease(mockProps)) + + expect(result.current).toBe(3) + + act(() => { + vi.advanceTimersByTime(1000) + }) + + expect(result.current).toBe(2) + + act(() => { + vi.advanceTimersByTime(GRIPPER_RELEASE_COUNTDOWN_S * 1000 - 1000) + }) + + expect(result.current).toBe(0) + }) + + describe('when door is closed', () => { + it.each([ + { + recoveryOption: RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE, + nextStep: RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE, + }, + { + recoveryOption: RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE, + nextStep: RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE, + }, + ])( + 'executes the full sequence of commands for $recoveryOption', + async ({ recoveryOption, nextStep }) => { + const props = { + ...mockProps, + currentRecoveryOptionUtils: { + selectedRecoveryOption: recoveryOption, + }, + doorStatusUtils: { isDoorOpen: false }, + } + + renderHook(() => useGripperRelease(props)) + + act(() => { + vi.advanceTimersByTime(GRIPPER_RELEASE_COUNTDOWN_S * 1000) + }) + await vi.runAllTimersAsync() + + const { + releaseGripperJaws, + homeExceptPlungers, + } = props.recoveryCommands + const { + handleMotionRouting, + proceedToRouteAndStep, + } = props.routeUpdateActions + + expect(releaseGripperJaws).toHaveBeenCalledTimes(1) + expect(handleMotionRouting).toHaveBeenNthCalledWith(1, true) + expect(homeExceptPlungers).toHaveBeenCalledTimes(1) + expect(handleMotionRouting).toHaveBeenNthCalledWith(2, false) + expect(proceedToRouteAndStep).toHaveBeenCalledWith( + recoveryOption, + nextStep + ) + } + ) + + describe('when door is open', () => { + it.each([ + { + recoveryOption: RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE, + doorStep: + RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME, + }, + { + recoveryOption: RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE, + doorStep: + RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS + .CLOSE_DOOR_GRIPPER_Z_HOME, + }, + ])( + 'executes proceed to door step for $recoveryOption', + async ({ recoveryOption, doorStep }) => { + const props = { + ...mockProps, + currentRecoveryOptionUtils: { + selectedRecoveryOption: recoveryOption, + }, + doorStatusUtils: { isDoorOpen: true }, + } + + const { + releaseGripperJaws, + homeExceptPlungers, + } = props.recoveryCommands + const { + handleMotionRouting, + proceedToRouteAndStep, + } = props.routeUpdateActions + + renderHook(() => useGripperRelease(props)) + + act(() => { + vi.advanceTimersByTime(GRIPPER_RELEASE_COUNTDOWN_S * 1000) + }) + await vi.runAllTimersAsync() + + expect(releaseGripperJaws).toHaveBeenCalledTimes(1) + expect(handleMotionRouting).toHaveBeenNthCalledWith(1, false) + expect(homeExceptPlungers).not.toHaveBeenCalled() + expect(proceedToRouteAndStep).toHaveBeenCalledWith( + recoveryOption, + doorStep + ) + } + ) + }) + + it('falls back to option selection for unhandled routes when door is open', async () => { + const props = { + ...mockProps, + currentRecoveryOptionUtils: { + selectedRecoveryOption: 'UNHANDLED_ROUTE', + }, + doorStatusUtils: { isDoorOpen: true }, + } + + renderHook(() => useGripperRelease(props)) + + act(() => { + vi.advanceTimersByTime(GRIPPER_RELEASE_COUNTDOWN_S * 1000) + }) + await vi.runAllTimersAsync() + + expect( + props.routeUpdateActions.proceedToRouteAndStep + ).toHaveBeenCalledWith(RECOVERY_MAP.OPTION_SELECTION.ROUTE) + }) + + it('falls back to proceedNextStep for unhandled routes when door is closed', async () => { + const props = { + ...mockProps, + currentRecoveryOptionUtils: { + selectedRecoveryOption: 'UNHANDLED_ROUTE', + }, + doorStatusUtils: { isDoorOpen: false }, + } + + renderHook(() => useGripperRelease(props)) + + act(() => { + vi.advanceTimersByTime(GRIPPER_RELEASE_COUNTDOWN_S * 1000) + }) + await vi.runAllTimersAsync() + + expect(props.routeUpdateActions.proceedNextStep).toHaveBeenCalled() + }) + }) }) diff --git a/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoverySplash.test.tsx b/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoverySplash.test.tsx new file mode 100644 index 00000000000..901ab22e158 --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoverySplash.test.tsx @@ -0,0 +1,198 @@ +import type * as React from 'react' +import { MemoryRouter } from 'react-router-dom' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { fireEvent, screen, waitFor, renderHook } from '@testing-library/react' +import { createStore } from 'redux' +import { QueryClient, QueryClientProvider } from 'react-query' +import { Provider } from 'react-redux' + +import { + RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, + RUN_STATUS_AWAITING_RECOVERY, + RUN_STATUS_AWAITING_RECOVERY_PAUSED, +} from '@opentrons/api-client' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockRecoveryContentProps } from '../__fixtures__' +import { getIsOnDevice } from '/app/redux/config' +import { useRecoverySplash, RecoverySplash } from '../RecoverySplash' +import { StepInfo } from '../shared' +import { useToaster } from '../../ToasterOven' +import { clickButtonLabeled } from './util' + +import type { Store } from 'redux' + +vi.mock('/app/redux/config') +vi.mock('../shared') +vi.mock('../../ToasterOven') + +const store: Store = createStore(vi.fn(), {}) + +describe('useRunPausedSplash', () => { + let wrapper: React.FunctionComponent<{ children: React.ReactNode }> + beforeEach(() => { + vi.mocked(getIsOnDevice).mockReturnValue(true) + const queryClient = new QueryClient() + wrapper = ({ children }) => ( + + + {children} + + + ) + }) + + const TEST_CASES = [ + { isOnDevice: true, showERWizard: true, expected: true }, + { isOnDevice: true, showERWizard: false, expected: true }, + { isOnDevice: false, showERWizard: true, expected: false }, + { isOnDevice: false, showERWizard: false, expected: true }, + ] + + describe('useRunPausedSplash', () => { + TEST_CASES.forEach(({ isOnDevice, showERWizard, expected }) => { + it(`returns ${expected} when isOnDevice is ${isOnDevice} and showERWizard is ${showERWizard}`, () => { + const { result } = renderHook( + () => useRecoverySplash(isOnDevice, showERWizard), + { + wrapper, + } + ) + expect(result.current).toEqual(expected) + }) + }) + }) +}) + +const render = (props: React.ComponentProps) => { + return renderWithProviders( + + + , + { + i18nInstance: i18n, + } + ) +} + +describe('RecoverySplash', () => { + let props: React.ComponentProps + const mockToggleERWiz = vi.fn(() => Promise.resolve()) + const mockProceedToRouteAndStep = vi.fn() + const mockHandleMotionRouting = vi.fn(() => Promise.resolve()) + const mockRouteUpdateActions = { + proceedToRouteAndStep: mockProceedToRouteAndStep, + handleMotionRouting: mockHandleMotionRouting, + } as any + const mockMakeToast = vi.fn() + const mockResumeRecovery = vi.fn() + const mockHomePipetteZAxes = vi.fn(() => Promise.resolve()) + + beforeEach(() => { + props = { + ...mockRecoveryContentProps, + robotName: 'testRobot', + toggleERWizAsActiveUser: mockToggleERWiz, + routeUpdateActions: mockRouteUpdateActions, + recoveryActionMutationUtils: { + resumeRecovery: mockResumeRecovery, + } as any, + resumePausedRecovery: true, + recoveryCommands: { homePipetteZAxes: mockHomePipetteZAxes } as any, + } + + vi.mocked(StepInfo).mockReturnValue(
    MOCK STEP INFO
    ) + vi.mocked(useToaster).mockReturnValue({ makeToast: mockMakeToast } as any) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('should render a generic paused screen if there is no handled errorType', () => { + render(props) + screen.getByText('Tip not detected') + screen.getByText('MOCK STEP INFO') + }) + + it('should render an overpressure error type if the errorType is overpressure', () => { + props = { + ...props, + failedCommand: { + byRunRecord: { + ...props.failedCommand?.byRunRecord, + commandType: 'aspirate', + error: { isDefined: true, errorType: 'overpressure' }, + }, + } as any, + } + render(props) + screen.getByText('Pipette overpressure') + screen.getByText('MOCK STEP INFO') + }) + + it('should contain buttons with expected appearance and behavior', async () => { + render(props) + + const primaryBtn = screen.getByRole('button', { + name: 'Launch recovery mode', + }) + const secondaryBtn = screen.getByRole('button', { name: 'Cancel run' }) + + expect(primaryBtn).toBeInTheDocument() + expect(secondaryBtn).toBeInTheDocument() + + fireEvent.click(secondaryBtn) + + await waitFor(() => { + expect(mockToggleERWiz).toHaveBeenCalledTimes(1) + }) + await waitFor(() => { + expect(mockToggleERWiz).toHaveBeenCalledWith(true, false) + }) + await waitFor(() => { + expect(mockProceedToRouteAndStep).toHaveBeenCalledTimes(1) + }) + + expect(mockToggleERWiz.mock.invocationCallOrder[0]).toBeLessThan( + mockProceedToRouteAndStep.mock.invocationCallOrder[0] + ) + + fireEvent.click(primaryBtn) + await waitFor(() => { + expect(mockToggleERWiz).toHaveBeenCalledTimes(2) + }) + await waitFor(() => { + expect(mockToggleERWiz).toHaveBeenCalledWith(true, true) + }) + + expect(mockHandleMotionRouting).toHaveBeenNthCalledWith(1, true) + expect(mockHandleMotionRouting).toHaveBeenNthCalledWith(2, false) + + await waitFor(() => { + expect(props.recoveryCommands.homePipetteZAxes).toHaveBeenCalled() + }) + }) + + it('should render a door open toast if the door is open', () => { + props = { + ...props, + runStatus: RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, + } + + render(props) + + clickButtonLabeled('Launch recovery mode') + + expect(mockMakeToast).toHaveBeenCalled() + }) + + it(`should transition the run status from ${RUN_STATUS_AWAITING_RECOVERY_PAUSED} to ${RUN_STATUS_AWAITING_RECOVERY} when resumePausedRecovery is true`, () => { + props = { ...props, runStatus: RUN_STATUS_AWAITING_RECOVERY_PAUSED } + + render(props) + + expect(mockResumeRecovery).toHaveBeenCalled() + }) +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryTakeover.test.tsx b/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryTakeover.test.tsx index 0caeffbf89f..1eec7782713 100644 --- a/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryTakeover.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/__tests__/RecoveryTakeover.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, expect, beforeEach } from 'vitest' import { screen } from '@testing-library/react' @@ -8,15 +8,15 @@ import { RUN_STATUS_AWAITING_RECOVERY_PAUSED, } from '@opentrons/api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { RecoveryTakeover, RecoveryTakeoverDesktop } from '../RecoveryTakeover' -import { useUpdateClientDataRecovery } from '../../../resources/client_data' +import { useUpdateClientDataRecovery } from '/app/resources/client_data' import { clickButtonLabeled } from './util' import type { Mock } from 'vitest' -vi.mock('../../../resources/client_data') +vi.mock('/app/resources/client_data') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/ErrorRecoveryFlows/__tests__/RunPausedSplash.test.tsx b/app/src/organisms/ErrorRecoveryFlows/__tests__/RunPausedSplash.test.tsx deleted file mode 100644 index 0764b6c865e..00000000000 --- a/app/src/organisms/ErrorRecoveryFlows/__tests__/RunPausedSplash.test.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import * as React from 'react' -import { MemoryRouter } from 'react-router-dom' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { fireEvent, screen, waitFor, renderHook } from '@testing-library/react' -import { createStore } from 'redux' - -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockRecoveryContentProps } from '../__fixtures__' -import { getIsOnDevice } from '../../../redux/config' -import { useRunPausedSplash, RunPausedSplash } from '../RunPausedSplash' -import { StepInfo } from '../shared' - -import type { Store } from 'redux' -import { QueryClient, QueryClientProvider } from 'react-query' -import { Provider } from 'react-redux' - -vi.mock('../../../redux/config') -vi.mock('../shared') - -const store: Store = createStore(vi.fn(), {}) - -describe('useRunPausedSplash', () => { - let wrapper: React.FunctionComponent<{ children: React.ReactNode }> - beforeEach(() => { - vi.mocked(getIsOnDevice).mockReturnValue(true) - const queryClient = new QueryClient() - wrapper = ({ children }) => ( - - - {children} - - - ) - }) - - const TEST_CASES = [ - { isOnDevice: true, showERWizard: true, expected: true }, - { isOnDevice: true, showERWizard: false, expected: true }, - { isOnDevice: false, showERWizard: true, expected: false }, - { isOnDevice: false, showERWizard: false, expected: true }, - ] - - describe('useRunPausedSplash', () => { - TEST_CASES.forEach(({ isOnDevice, showERWizard, expected }) => { - it(`returns ${expected} when isOnDevice is ${isOnDevice} and showERWizard is ${showERWizard}`, () => { - const { result } = renderHook( - () => useRunPausedSplash(isOnDevice, showERWizard), - { - wrapper, - } - ) - expect(result.current).toEqual(expected) - }) - }) - }) -}) - -const render = (props: React.ComponentProps) => { - return renderWithProviders( - - - , - { - i18nInstance: i18n, - } - ) -} - -describe('RunPausedSplash', () => { - let props: React.ComponentProps - const mockToggleERWiz = vi.fn(() => Promise.resolve()) - const mockProceedToRouteAndStep = vi.fn() - const mockRouteUpdateActions = { - proceedToRouteAndStep: mockProceedToRouteAndStep, - } as any - - beforeEach(() => { - props = { - ...mockRecoveryContentProps, - robotName: 'testRobot', - toggleERWizAsActiveUser: mockToggleERWiz, - routeUpdateActions: mockRouteUpdateActions, - } - - vi.mocked(StepInfo).mockReturnValue(
    MOCK STEP INFO
    ) - }) - - afterEach(() => { - vi.restoreAllMocks() - }) - - it('should render a generic paused screen if there is no handled errorType', () => { - render(props) - screen.getByText('Tip not detected') - screen.getByText('MOCK STEP INFO') - }) - - it('should render an overpressure error type if the errorType is overpressure', () => { - props = { - ...props, - failedCommand: { - byRunRecord: { - ...props.failedCommand?.byRunRecord, - commandType: 'aspirate', - error: { isDefined: true, errorType: 'overpressure' }, - }, - } as any, - } - render(props) - screen.getByText('Pipette overpressure') - screen.getByText('MOCK STEP INFO') - }) - - it('should contain buttons with expected appearance and behavior', async () => { - render(props) - - const primaryBtn = screen.getByRole('button', { - name: 'Launch Recovery Mode', - }) - const secondaryBtn = screen.getByRole('button', { name: 'Cancel run' }) - - expect(primaryBtn).toBeInTheDocument() - expect(secondaryBtn).toBeInTheDocument() - - fireEvent.click(secondaryBtn) - - await waitFor(() => { - expect(mockToggleERWiz).toHaveBeenCalledTimes(1) - }) - await waitFor(() => { - expect(mockToggleERWiz).toHaveBeenCalledWith(true, false) - }) - await waitFor(() => { - expect(mockProceedToRouteAndStep).toHaveBeenCalledTimes(1) - }) - - expect(mockToggleERWiz.mock.invocationCallOrder[0]).toBeLessThan( - mockProceedToRouteAndStep.mock.invocationCallOrder[0] - ) - - fireEvent.click(primaryBtn) - await waitFor(() => { - expect(mockToggleERWiz).toHaveBeenCalledTimes(2) - }) - await waitFor(() => { - expect(mockToggleERWiz).toHaveBeenCalledWith(true, true) - }) - }) -}) diff --git a/app/src/organisms/ErrorRecoveryFlows/constants.ts b/app/src/organisms/ErrorRecoveryFlows/constants.ts index faadf0730aa..8be1b6adbe1 100644 --- a/app/src/organisms/ErrorRecoveryFlows/constants.ts +++ b/app/src/organisms/ErrorRecoveryFlows/constants.ts @@ -10,13 +10,17 @@ import { TEXT_ALIGN_CENTER, } from '@opentrons/components' -import type { StepOrder } from './types' +import type { RecoveryRouteStepMetadata, RouteStep, StepOrder } from './types' // Server-defined error types. // (Values for the .error.errorType property of a run command.) export const DEFINED_ERROR_TYPES = { OVERPRESSURE: 'overpressure', LIQUID_NOT_FOUND: 'liquidNotFound', + TIP_PHYSICALLY_MISSING: 'tipPhysicallyMissing', + TIP_PHYSICALLY_ATTACHED: 'tipPhysicallyAttached', + GRIPPER_MOVEMENT: 'gripperMovement', + STALL_OR_COLLISION: 'stallOrCollision', } // Client-defined error-handling flows. @@ -26,9 +30,12 @@ export const ERROR_KINDS = { OVERPRESSURE_PREPARE_TO_ASPIRATE: 'OVERPRESSURE_PREPARE_TO_ASPIRATE', OVERPRESSURE_WHILE_ASPIRATING: 'OVERPRESSURE_WHILE_ASPIRATING', OVERPRESSURE_WHILE_DISPENSING: 'OVERPRESSURE_WHILE_DISPENSING', + TIP_NOT_DETECTED: 'TIP_NOT_DETECTED', + TIP_DROP_FAILED: 'TIP_DROP_FAILED', + GRIPPER_ERROR: 'GRIPPER_ERROR', + STALL_OR_COLLISION: 'STALL_OR_COLLISION', } as const -// TODO(jh, 05-09-24): Refactor to a directed graph. EXEC-430. // TODO(jh, 06-14-24): Consolidate motion routes to a single route with several steps. // Valid recovery routes and steps. export const RECOVERY_MAP = { @@ -50,6 +57,18 @@ export const RECOVERY_MAP = { DROP_TIP_GENERAL_ERROR: 'drop-tip-general-error', }, }, + HOME_AND_RETRY: { + ROUTE: 'home-and-retry', + STEPS: { + PREPARE_DECK_FOR_HOME: 'prepare-deck-for-home', + REMOVE_TIPS_FROM_PIPETTE: 'remove-tips-from-pipette', + REPLACE_TIPS: 'replace-tips', + SELECT_TIPS: 'select-tips', + HOME_BEFORE_RETRY: 'home-before-retry', + CLOSE_DOOR_AND_HOME: 'close-door-and-home', + CONFIRM_RETRY: 'confirm-retry', + }, + }, ROBOT_CANCELING: { ROUTE: 'robot-cancel-run', STEPS: { @@ -68,6 +87,12 @@ export const RECOVERY_MAP = { PICKING_UP_TIPS: 'picking-up-tips', }, }, + ROBOT_RELEASING_LABWARE: { + ROUTE: 'robot-releasing-labware', + STEPS: { + RELEASING_LABWARE: 'releasing-labware', + }, + }, ROBOT_RESUMING: { ROUTE: 'robot-resuming', STEPS: { @@ -86,6 +111,18 @@ export const RECOVERY_MAP = { SKIPPING: 'skipping', }, }, + ROBOT_DOOR_OPEN: { + ROUTE: 'door', + STEPS: { + DOOR_OPEN: 'door-open', + }, + }, + ROBOT_DOOR_OPEN_SPECIAL: { + ROUTE: 'door-special', + STEPS: { + DOOR_OPEN: 'door-open', + }, + }, // Recovery options below OPTION_SELECTION: { ROUTE: 'option-selection', @@ -97,15 +134,38 @@ export const RECOVERY_MAP = { }, IGNORE_AND_SKIP: { ROUTE: 'ignore-and-skip-step', - STEPS: { SELECT_IGNORE_KIND: 'select-ignore' }, + STEPS: { SELECT_IGNORE_KIND: 'select-ignore', SKIP_STEP: 'skip-step' }, + }, + MANUAL_FILL_AND_SKIP: { + ROUTE: 'manual-fill-well-and-skip', + STEPS: { + MANUAL_FILL: 'manual-fill', + SKIP: 'skip', + }, + }, + MANUAL_MOVE_AND_SKIP: { + ROUTE: 'manual-move-labware-and-skip', + STEPS: { + GRIPPER_HOLDING_LABWARE: 'gripper-holding-labware', + GRIPPER_RELEASE_LABWARE: 'gripper-release-labware', + CLOSE_DOOR_GRIPPER_Z_HOME: 'close-robot-door', + MANUAL_MOVE: 'manual-move', + SKIP: 'skip', + }, }, - FILL_MANUALLY_AND_SKIP: { - ROUTE: 'manually-fill-well-and-skip', - STEPS: { MANUALLY_FILL: 'manually-fill', SKIP: 'skip' }, + MANUAL_REPLACE_AND_RETRY: { + ROUTE: 'manual-replace-and-retry', + STEPS: { + GRIPPER_HOLDING_LABWARE: 'gripper-holding-labware', + GRIPPER_RELEASE_LABWARE: 'gripper-release-labware', + CLOSE_DOOR_GRIPPER_Z_HOME: 'close-robot-door', + MANUAL_REPLACE: 'manual-replace', + RETRY: 'retry', + }, }, REFILL_AND_RESUME: { ROUTE: 'refill-and-resume', STEPS: {} }, - RETRY_FAILED_COMMAND: { - ROUTE: 'retry-failed-command', + RETRY_STEP: { + ROUTE: 'retry-step', STEPS: { CONFIRM_RETRY: 'confirm-retry' }, }, RETRY_NEW_TIPS: { @@ -142,13 +202,16 @@ export const RECOVERY_MAP = { const { OPTION_SELECTION, - RETRY_FAILED_COMMAND, + RETRY_STEP, ROBOT_CANCELING, ROBOT_PICKING_UP_TIPS, + ROBOT_RELEASING_LABWARE, ROBOT_RESUMING, ROBOT_IN_MOTION, ROBOT_RETRYING_STEP, ROBOT_SKIPPING_STEP, + ROBOT_DOOR_OPEN, + ROBOT_DOOR_OPEN_SPECIAL, DROP_TIP_FLOWS, REFILL_AND_RESUME, IGNORE_AND_SKIP, @@ -156,15 +219,18 @@ const { RETRY_NEW_TIPS, RETRY_SAME_TIPS, ERROR_WHILE_RECOVERING, - FILL_MANUALLY_AND_SKIP, + MANUAL_FILL_AND_SKIP, + MANUAL_MOVE_AND_SKIP, + MANUAL_REPLACE_AND_RETRY, SKIP_STEP_WITH_NEW_TIPS, SKIP_STEP_WITH_SAME_TIPS, + HOME_AND_RETRY, } = RECOVERY_MAP // The deterministic ordering of steps for a given route. export const STEP_ORDER: StepOrder = { [OPTION_SELECTION.ROUTE]: [OPTION_SELECTION.STEPS.SELECT], - [RETRY_FAILED_COMMAND.ROUTE]: [RETRY_FAILED_COMMAND.STEPS.CONFIRM_RETRY], + [RETRY_STEP.ROUTE]: [RETRY_STEP.STEPS.CONFIRM_RETRY], [RETRY_NEW_TIPS.ROUTE]: [ RETRY_NEW_TIPS.STEPS.DROP_TIPS, RETRY_NEW_TIPS.STEPS.REPLACE_TIPS, @@ -182,9 +248,14 @@ export const STEP_ORDER: StepOrder = { [ROBOT_CANCELING.ROUTE]: [ROBOT_CANCELING.STEPS.CANCELING], [ROBOT_IN_MOTION.ROUTE]: [ROBOT_IN_MOTION.STEPS.IN_MOTION], [ROBOT_PICKING_UP_TIPS.ROUTE]: [ROBOT_PICKING_UP_TIPS.STEPS.PICKING_UP_TIPS], + [ROBOT_RELEASING_LABWARE.ROUTE]: [ + ROBOT_RELEASING_LABWARE.STEPS.RELEASING_LABWARE, + ], [ROBOT_RESUMING.ROUTE]: [ROBOT_RESUMING.STEPS.RESUMING], [ROBOT_RETRYING_STEP.ROUTE]: [ROBOT_RETRYING_STEP.STEPS.RETRYING], [ROBOT_SKIPPING_STEP.ROUTE]: [ROBOT_SKIPPING_STEP.STEPS.SKIPPING], + [ROBOT_DOOR_OPEN.ROUTE]: [ROBOT_DOOR_OPEN.STEPS.DOOR_OPEN], + [ROBOT_DOOR_OPEN_SPECIAL.ROUTE]: [ROBOT_DOOR_OPEN_SPECIAL.STEPS.DOOR_OPEN], [DROP_TIP_FLOWS.ROUTE]: [ DROP_TIP_FLOWS.STEPS.BEGIN_REMOVAL, DROP_TIP_FLOWS.STEPS.BEFORE_BEGINNING, @@ -192,11 +263,28 @@ export const STEP_ORDER: StepOrder = { DROP_TIP_FLOWS.STEPS.CHOOSE_TIP_DROP, ], [REFILL_AND_RESUME.ROUTE]: [], - [IGNORE_AND_SKIP.ROUTE]: [IGNORE_AND_SKIP.STEPS.SELECT_IGNORE_KIND], + [IGNORE_AND_SKIP.ROUTE]: [ + IGNORE_AND_SKIP.STEPS.SELECT_IGNORE_KIND, + IGNORE_AND_SKIP.STEPS.SKIP_STEP, + ], [CANCEL_RUN.ROUTE]: [CANCEL_RUN.STEPS.CONFIRM_CANCEL], - [FILL_MANUALLY_AND_SKIP.ROUTE]: [ - FILL_MANUALLY_AND_SKIP.STEPS.MANUALLY_FILL, - FILL_MANUALLY_AND_SKIP.STEPS.SKIP, + [MANUAL_FILL_AND_SKIP.ROUTE]: [ + MANUAL_FILL_AND_SKIP.STEPS.MANUAL_FILL, + MANUAL_FILL_AND_SKIP.STEPS.SKIP, + ], + [MANUAL_MOVE_AND_SKIP.ROUTE]: [ + MANUAL_MOVE_AND_SKIP.STEPS.GRIPPER_HOLDING_LABWARE, + MANUAL_MOVE_AND_SKIP.STEPS.GRIPPER_RELEASE_LABWARE, + MANUAL_MOVE_AND_SKIP.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME, + MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE, + MANUAL_MOVE_AND_SKIP.STEPS.SKIP, + ], + [MANUAL_REPLACE_AND_RETRY.ROUTE]: [ + MANUAL_REPLACE_AND_RETRY.STEPS.GRIPPER_HOLDING_LABWARE, + MANUAL_REPLACE_AND_RETRY.STEPS.GRIPPER_RELEASE_LABWARE, + MANUAL_MOVE_AND_SKIP.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME, + MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE, + MANUAL_REPLACE_AND_RETRY.STEPS.RETRY, ], [ERROR_WHILE_RECOVERING.ROUTE]: [ ERROR_WHILE_RECOVERING.STEPS.RECOVERY_ACTION_FAILED, @@ -204,8 +292,173 @@ export const STEP_ORDER: StepOrder = { ERROR_WHILE_RECOVERING.STEPS.DROP_TIP_TIP_DROP_FAILED, ERROR_WHILE_RECOVERING.STEPS.DROP_TIP_BLOWOUT_FAILED, ], + [HOME_AND_RETRY.ROUTE]: [ + HOME_AND_RETRY.STEPS.PREPARE_DECK_FOR_HOME, + HOME_AND_RETRY.STEPS.REMOVE_TIPS_FROM_PIPETTE, + HOME_AND_RETRY.STEPS.REPLACE_TIPS, + HOME_AND_RETRY.STEPS.SELECT_TIPS, + HOME_AND_RETRY.STEPS.HOME_BEFORE_RETRY, + HOME_AND_RETRY.STEPS.CLOSE_DOOR_AND_HOME, + HOME_AND_RETRY.STEPS.CONFIRM_RETRY, + ], } +// Contains metadata specific to all routes and/or steps. +export const RECOVERY_MAP_METADATA: RecoveryRouteStepMetadata = { + [DROP_TIP_FLOWS.ROUTE]: { + [DROP_TIP_FLOWS.STEPS.BEGIN_REMOVAL]: { allowDoorOpen: false }, + [DROP_TIP_FLOWS.STEPS.BEFORE_BEGINNING]: { + allowDoorOpen: false, + }, + [DROP_TIP_FLOWS.STEPS.CHOOSE_TIP_DROP]: { + allowDoorOpen: false, + }, + [DROP_TIP_FLOWS.STEPS.CHOOSE_BLOWOUT]: { + allowDoorOpen: false, + }, + }, + [ERROR_WHILE_RECOVERING.ROUTE]: { + [ERROR_WHILE_RECOVERING.STEPS.RECOVERY_ACTION_FAILED]: { + allowDoorOpen: false, + }, + [ERROR_WHILE_RECOVERING.STEPS.DROP_TIP_BLOWOUT_FAILED]: { + allowDoorOpen: false, + }, + [ERROR_WHILE_RECOVERING.STEPS.DROP_TIP_TIP_DROP_FAILED]: { + allowDoorOpen: false, + }, + [ERROR_WHILE_RECOVERING.STEPS.DROP_TIP_GENERAL_ERROR]: { + allowDoorOpen: false, + }, + }, + [ROBOT_CANCELING.ROUTE]: { + [ROBOT_CANCELING.STEPS.CANCELING]: { allowDoorOpen: false }, + }, + [ROBOT_IN_MOTION.ROUTE]: { + [ROBOT_IN_MOTION.STEPS.IN_MOTION]: { allowDoorOpen: false }, + }, + [ROBOT_PICKING_UP_TIPS.ROUTE]: { + [ROBOT_PICKING_UP_TIPS.STEPS.PICKING_UP_TIPS]: { + allowDoorOpen: false, + }, + }, + [ROBOT_RELEASING_LABWARE.ROUTE]: { + [ROBOT_RELEASING_LABWARE.STEPS.RELEASING_LABWARE]: { allowDoorOpen: false }, + }, + [ROBOT_RESUMING.ROUTE]: { + [ROBOT_RESUMING.STEPS.RESUMING]: { allowDoorOpen: false }, + }, + [ROBOT_RETRYING_STEP.ROUTE]: { + [ROBOT_RETRYING_STEP.STEPS.RETRYING]: { allowDoorOpen: false }, + }, + [ROBOT_SKIPPING_STEP.ROUTE]: { + [ROBOT_SKIPPING_STEP.STEPS.SKIPPING]: { allowDoorOpen: false }, + }, + [ROBOT_DOOR_OPEN.ROUTE]: { + [ROBOT_DOOR_OPEN.STEPS.DOOR_OPEN]: { allowDoorOpen: false }, + }, + [HOME_AND_RETRY.ROUTE]: { + [HOME_AND_RETRY.STEPS.PREPARE_DECK_FOR_HOME]: { allowDoorOpen: true }, + [HOME_AND_RETRY.STEPS.REMOVE_TIPS_FROM_PIPETTE]: { allowDoorOpen: true }, + [HOME_AND_RETRY.STEPS.REPLACE_TIPS]: { allowDoorOpen: true }, + [HOME_AND_RETRY.STEPS.SELECT_TIPS]: { allowDoorOpen: true }, + [HOME_AND_RETRY.STEPS.HOME_BEFORE_RETRY]: { allowDoorOpen: true }, + [HOME_AND_RETRY.STEPS.CLOSE_DOOR_AND_HOME]: { allowDoorOpen: true }, + [HOME_AND_RETRY.STEPS.CONFIRM_RETRY]: { allowDoorOpen: true }, + }, + [ROBOT_DOOR_OPEN_SPECIAL.ROUTE]: { + [ROBOT_DOOR_OPEN_SPECIAL.STEPS.DOOR_OPEN]: { allowDoorOpen: true }, + }, + [OPTION_SELECTION.ROUTE]: { + [OPTION_SELECTION.STEPS.SELECT]: { allowDoorOpen: false }, + }, + [CANCEL_RUN.ROUTE]: { + [CANCEL_RUN.STEPS.CONFIRM_CANCEL]: { allowDoorOpen: false }, + }, + [IGNORE_AND_SKIP.ROUTE]: { + [IGNORE_AND_SKIP.STEPS.SELECT_IGNORE_KIND]: { + allowDoorOpen: false, + }, + [IGNORE_AND_SKIP.STEPS.SKIP_STEP]: { allowDoorOpen: false }, + }, + [MANUAL_FILL_AND_SKIP.ROUTE]: { + [MANUAL_FILL_AND_SKIP.STEPS.MANUAL_FILL]: { + allowDoorOpen: true, + }, + [MANUAL_FILL_AND_SKIP.STEPS.SKIP]: { allowDoorOpen: true }, + }, + [MANUAL_MOVE_AND_SKIP.ROUTE]: { + [MANUAL_MOVE_AND_SKIP.STEPS.GRIPPER_HOLDING_LABWARE]: { + allowDoorOpen: true, + }, + [MANUAL_MOVE_AND_SKIP.STEPS.GRIPPER_RELEASE_LABWARE]: { + allowDoorOpen: true, + }, + [MANUAL_MOVE_AND_SKIP.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME]: { + allowDoorOpen: true, + }, + [MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE]: { allowDoorOpen: true }, + [MANUAL_MOVE_AND_SKIP.STEPS.SKIP]: { allowDoorOpen: true }, + }, + [MANUAL_REPLACE_AND_RETRY.ROUTE]: { + [MANUAL_REPLACE_AND_RETRY.STEPS.GRIPPER_HOLDING_LABWARE]: { + allowDoorOpen: true, + }, + [MANUAL_REPLACE_AND_RETRY.STEPS.GRIPPER_RELEASE_LABWARE]: { + allowDoorOpen: true, + }, + [MANUAL_REPLACE_AND_RETRY.STEPS.CLOSE_DOOR_GRIPPER_Z_HOME]: { + allowDoorOpen: true, + }, + [MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE]: { allowDoorOpen: true }, + [MANUAL_REPLACE_AND_RETRY.STEPS.RETRY]: { allowDoorOpen: true }, + }, + [REFILL_AND_RESUME.ROUTE]: {}, + [RETRY_STEP.ROUTE]: { + [RETRY_STEP.STEPS.CONFIRM_RETRY]: { + allowDoorOpen: false, + }, + }, + [RETRY_NEW_TIPS.ROUTE]: { + [RETRY_NEW_TIPS.STEPS.DROP_TIPS]: { allowDoorOpen: false }, + [RETRY_NEW_TIPS.STEPS.REPLACE_TIPS]: { allowDoorOpen: true }, + [RETRY_NEW_TIPS.STEPS.SELECT_TIPS]: { allowDoorOpen: true }, + [RETRY_NEW_TIPS.STEPS.RETRY]: { allowDoorOpen: true }, + }, + [RETRY_SAME_TIPS.ROUTE]: { + [RETRY_SAME_TIPS.STEPS.RETRY]: { allowDoorOpen: true }, + }, + [SKIP_STEP_WITH_NEW_TIPS.ROUTE]: { + [SKIP_STEP_WITH_NEW_TIPS.STEPS.DROP_TIPS]: { + allowDoorOpen: false, + }, + [SKIP_STEP_WITH_NEW_TIPS.STEPS.REPLACE_TIPS]: { + allowDoorOpen: true, + }, + [SKIP_STEP_WITH_NEW_TIPS.STEPS.SELECT_TIPS]: { + allowDoorOpen: true, + }, + [SKIP_STEP_WITH_NEW_TIPS.STEPS.SKIP]: { allowDoorOpen: true }, + }, + [SKIP_STEP_WITH_SAME_TIPS.ROUTE]: { + [SKIP_STEP_WITH_SAME_TIPS.STEPS.SKIP]: { + allowDoorOpen: true, + }, + }, +} as const + +/** + * Special step groupings + */ + +export const GRIPPER_MOVE_STEPS: RouteStep[] = [ + RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.GRIPPER_RELEASE_LABWARE, + RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.GRIPPER_RELEASE_LABWARE, + RECOVERY_MAP.ROBOT_RELEASING_LABWARE.STEPS.RELEASING_LABWARE, + RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE, + RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE, +] + export const INVALID = 'INVALID' as const /** diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useCleanupRecoveryState.test.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useCleanupRecoveryState.test.ts new file mode 100644 index 00000000000..9f9628546cc --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useCleanupRecoveryState.test.ts @@ -0,0 +1,121 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { renderHook } from '@testing-library/react' + +import { useCleanupRecoveryState } from '../useCleanupRecoveryState' +import { RECOVERY_MAP } from '../../constants' + +describe('useCleanupRecoveryState', () => { + let props: Parameters[0] + let mockSetRM: ReturnType + + beforeEach(() => { + mockSetRM = vi.fn() + props = { + isActiveUser: false, + stashedMapRef: { + current: { + route: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE, + step: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.SKIP, + }, + }, + setRM: mockSetRM, + } + }) + + it('does not modify state when user was never active', () => { + renderHook(() => useCleanupRecoveryState(props)) + + expect(props.stashedMapRef.current).toEqual({ + route: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE, + step: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.SKIP, + }) + expect(mockSetRM).not.toHaveBeenCalled() + }) + + it('does not modify state when user becomes active', () => { + props.isActiveUser = true + + renderHook(() => useCleanupRecoveryState(props)) + + expect(props.stashedMapRef.current).toEqual({ + route: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE, + step: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.SKIP, + }) + expect(mockSetRM).not.toHaveBeenCalled() + }) + + it('resets state when user becomes inactive after being active', () => { + const { rerender } = renderHook( + ({ isActiveUser }) => useCleanupRecoveryState({ ...props, isActiveUser }), + { initialProps: { isActiveUser: true } } + ) + + rerender({ isActiveUser: false }) + + expect(props.stashedMapRef.current).toBeNull() + expect(mockSetRM).toHaveBeenCalledWith({ + route: RECOVERY_MAP.OPTION_SELECTION.ROUTE, + step: RECOVERY_MAP.OPTION_SELECTION.STEPS.SELECT, + }) + }) + + it('handles case when stashedMapRef.current is already null', () => { + const { rerender } = renderHook( + ({ isActiveUser }) => useCleanupRecoveryState({ ...props, isActiveUser }), + { initialProps: { isActiveUser: true } } + ) + + props.stashedMapRef.current = null + rerender({ isActiveUser: false }) + + expect(props.stashedMapRef.current).toBeNull() + expect(mockSetRM).toHaveBeenCalledWith({ + route: RECOVERY_MAP.OPTION_SELECTION.ROUTE, + step: RECOVERY_MAP.OPTION_SELECTION.STEPS.SELECT, + }) + }) + + it('does not reset state on subsequent inactive states', () => { + const { rerender } = renderHook( + ({ isActiveUser }) => useCleanupRecoveryState({ ...props, isActiveUser }), + { initialProps: { isActiveUser: true } } + ) + + rerender({ isActiveUser: false }) + mockSetRM.mockClear() + + props.stashedMapRef.current = { + route: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE, + step: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.SKIP, + } + + rerender({ isActiveUser: false }) + + expect(props.stashedMapRef.current).toEqual({ + route: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE, + step: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.SKIP, + }) + expect(mockSetRM).not.toHaveBeenCalled() + }) + + it('resets state only after a full active->inactive cycle', () => { + const { rerender } = renderHook( + ({ isActiveUser }) => useCleanupRecoveryState({ ...props, isActiveUser }), + { initialProps: { isActiveUser: false } } + ) + + rerender({ isActiveUser: true }) + expect(mockSetRM).not.toHaveBeenCalled() + expect(props.stashedMapRef.current).toEqual({ + route: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE, + step: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.SKIP, + }) + + rerender({ isActiveUser: false }) + expect(props.stashedMapRef.current).toBeNull() + expect(mockSetRM).toHaveBeenCalledWith({ + route: RECOVERY_MAP.OPTION_SELECTION.ROUTE, + step: RECOVERY_MAP.OPTION_SELECTION.STEPS.SELECT, + }) + }) +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useCurrentlyRecoveringFrom.test.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useCurrentlyRecoveringFrom.test.ts index 57adcdeaaa7..cc27994d671 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useCurrentlyRecoveringFrom.test.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useCurrentlyRecoveringFrom.test.ts @@ -1,5 +1,6 @@ -import { vi, describe, it, expect } from 'vitest' +import { vi, describe, it, expect, beforeEach } from 'vitest' import { renderHook } from '@testing-library/react' +import { useQueryClient } from 'react-query' import { useCommandQuery } from '@opentrons/react-api-client' import { @@ -7,16 +8,28 @@ import { RUN_STATUS_IDLE, } from '@opentrons/api-client' -import { useNotifyAllCommandsQuery } from '../../../../resources/runs' +import { useNotifyAllCommandsQuery } from '/app/resources/runs' import { useCurrentlyRecoveringFrom } from '../useCurrentlyRecoveringFrom' +import type { Mock } from 'vitest' + vi.mock('@opentrons/react-api-client') -vi.mock('../../../../resources/runs') +vi.mock('/app/resources/runs') +vi.mock('react-query') const MOCK_RUN_ID = 'runId' const MOCK_COMMAND_ID = 'commandId' describe('useCurrentlyRecoveringFrom', () => { + let mockInvalidateQueries: Mock + + beforeEach(() => { + mockInvalidateQueries = vi.fn() + vi.mocked(useQueryClient).mockReturnValue({ + invalidateQueries: mockInvalidateQueries, + } as any) + }) + it('disables all queries if the run is not awaiting-recovery', () => { vi.mocked(useNotifyAllCommandsQuery).mockReturnValue({ data: { @@ -29,9 +42,11 @@ describe('useCurrentlyRecoveringFrom', () => { }, }, }, + isFetching: false, } as any) vi.mocked(useCommandQuery).mockReturnValue({ data: { data: 'mockCommandDetails' }, + isFetching: false, } as any) const { result } = renderHook(() => @@ -40,7 +55,7 @@ describe('useCurrentlyRecoveringFrom', () => { expect(vi.mocked(useNotifyAllCommandsQuery)).toHaveBeenCalledWith( MOCK_RUN_ID, - { cursor: null, pageLength: 0 }, + { pageLength: 0 }, { enabled: false, refetchInterval: 5000 } ) expect(vi.mocked(useCommandQuery)).toHaveBeenCalledWith( @@ -56,8 +71,11 @@ describe('useCurrentlyRecoveringFrom', () => { data: { links: {}, }, + isFetching: false, + } as any) + vi.mocked(useCommandQuery).mockReturnValue({ + isFetching: false, } as any) - vi.mocked(useCommandQuery).mockReturnValue({} as any) const { result } = renderHook(() => useCurrentlyRecoveringFrom(MOCK_RUN_ID, RUN_STATUS_AWAITING_RECOVERY) @@ -81,9 +99,11 @@ describe('useCurrentlyRecoveringFrom', () => { }, }, }, + isFetching: false, } as any) vi.mocked(useCommandQuery).mockReturnValue({ data: { data: 'mockCommandDetails' }, + isFetching: false, } as any) const { result } = renderHook(() => @@ -97,4 +117,97 @@ describe('useCurrentlyRecoveringFrom', () => { ) expect(result.current).toStrictEqual('mockCommandDetails') }) + + it('returns null if all commands query is still fetching', () => { + vi.mocked(useNotifyAllCommandsQuery).mockReturnValue({ + data: { + links: { + currentlyRecoveringFrom: { + meta: { + runId: MOCK_RUN_ID, + commandId: MOCK_COMMAND_ID, + }, + }, + }, + }, + isFetching: true, + } as any) + vi.mocked(useCommandQuery).mockReturnValue({ + data: { data: 'mockCommandDetails' }, + isFetching: false, + } as any) + + const { result } = renderHook(() => + useCurrentlyRecoveringFrom(MOCK_RUN_ID, RUN_STATUS_AWAITING_RECOVERY) + ) + + expect(result.current).toStrictEqual(null) + }) + + it('returns null if command query is still fetching', () => { + vi.mocked(useNotifyAllCommandsQuery).mockReturnValue({ + data: { + links: { + currentlyRecoveringFrom: { + meta: { + runId: MOCK_RUN_ID, + commandId: MOCK_COMMAND_ID, + }, + }, + }, + }, + isFetching: false, + } as any) + vi.mocked(useCommandQuery).mockReturnValue({ + data: { data: 'mockCommandDetails' }, + isFetching: true, + } as any) + + const { result } = renderHook(() => + useCurrentlyRecoveringFrom(MOCK_RUN_ID, RUN_STATUS_AWAITING_RECOVERY) + ) + + expect(result.current).toStrictEqual(null) + }) + + it('resets isReadyToShow when run exits recovery mode', () => { + const { rerender, result } = renderHook( + ({ status }) => useCurrentlyRecoveringFrom(MOCK_RUN_ID, status), + { initialProps: { status: RUN_STATUS_AWAITING_RECOVERY } } + ) + + vi.mocked(useNotifyAllCommandsQuery).mockReturnValue({ + data: { + links: { + currentlyRecoveringFrom: { + meta: { + runId: MOCK_RUN_ID, + commandId: MOCK_COMMAND_ID, + }, + }, + }, + }, + isFetching: false, + } as any) + vi.mocked(useCommandQuery).mockReturnValue({ + data: { data: 'mockCommandDetails' }, + isFetching: false, + } as any) + + rerender({ status: RUN_STATUS_AWAITING_RECOVERY }) + + expect(result.current).toStrictEqual('mockCommandDetails') + + rerender({ status: RUN_STATUS_IDLE } as any) + + expect(result.current).toStrictEqual(null) + }) + + it('calls invalidateQueries when the run enters recovery mode', () => { + renderHook(() => + useCurrentlyRecoveringFrom(MOCK_RUN_ID, RUN_STATUS_AWAITING_RECOVERY) + ) + + expect(mockInvalidateQueries).toHaveBeenCalled() + }) }) diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useDeckMapUtils.test.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useDeckMapUtils.test.ts index 4e341acda99..1a6d07ba634 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useDeckMapUtils.test.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useDeckMapUtils.test.ts @@ -9,6 +9,7 @@ import { } from '@opentrons/shared-data' import { mockPickUpTipLabware } from '../../__fixtures__' +import { getLabwareLocation } from '/app/local-resources/labware' import { getIsLabwareMatch, getSlotNameAndLwLocFrom, @@ -16,6 +17,7 @@ import { getRunCurrentModulesInfo, getRunCurrentLabwareOnDeck, getRunCurrentModulesOnDeck, + updateLabwareInModules, } from '../useDeckMapUtils' import type { LabwareDefinition2 } from '@opentrons/shared-data' @@ -29,6 +31,7 @@ vi.mock('@opentrons/shared-data', async importOriginal => { getModuleDef2: vi.fn(), } }) +vi.mock('/app/local-resources/labware') describe('getRunCurrentModulesOnDeck', () => { const mockLabwareDef: LabwareDefinition2 = { @@ -49,12 +52,13 @@ describe('getRunCurrentModulesOnDeck', () => { moduleDef: mockModuleDef, slotName: 'A1', nestedLabwareDef: mockLabwareDef, - nestedLabwareSlotName: 'MOCK_MODULE_ID', + nestedLabwareSlotName: 'A1', }, ] beforeEach(() => { vi.mocked(getModuleDef2).mockReturnValue({ model: 'MOCK_MODEL' } as any) + vi.mocked(getLabwareLocation).mockReturnValue({ slotName: 'A1' }) }) it('should return an array of RunCurrentModulesOnDeck objects', () => { @@ -64,9 +68,11 @@ describe('getRunCurrentModulesOnDeck', () => { location: { moduleId: 'MOCK_MODULE_ID' }, }, } as any + vi.mocked(getLabwareLocation).mockReturnValue({ slotName: 'A1' }) const result = getRunCurrentModulesOnDeck({ failedLabwareUtils: mockPickUpTipLabwareSameSlot, + runRecord: {} as any, currentModulesInfo: mockCurrentModulesInfo, }) @@ -76,13 +82,14 @@ describe('getRunCurrentModulesOnDeck', () => { moduleLocation: { slotName: 'A1' }, innerProps: {}, nestedLabwareDef: mockLabwareDef, - highlight: 'MOCK_MODULE_ID', + highlight: 'A1', }, ]) }) it('should set highlight to null if getIsLabwareMatch returns false', () => { const result = getRunCurrentModulesOnDeck({ failedLabwareUtils: mockFailedLabwareUtils, + runRecord: {} as any, currentModulesInfo: [ { ...mockCurrentModulesInfo[0], @@ -95,8 +102,11 @@ describe('getRunCurrentModulesOnDeck', () => { }) it('should set highlight to null if nestedLabwareDef is null', () => { + vi.mocked(getLabwareLocation).mockReturnValue(null) + const result = getRunCurrentModulesOnDeck({ failedLabwareUtils: mockFailedLabwareUtils, + runRecord: {} as any, currentModulesInfo: [ { ...mockCurrentModulesInfo[0], nestedLabwareDef: null }, ], @@ -126,8 +136,10 @@ describe('getRunCurrentLabwareOnDeck', () => { } as any it('should return a valid RunCurrentLabwareOnDeck with a labware highlight if the labware is the pickUpTipLabware', () => { + vi.mocked(getLabwareLocation).mockReturnValue({ slotName: 'A1' }) const result = getRunCurrentLabwareOnDeck({ currentLabwareInfo: [mockCurrentLabwareInfo], + runRecord: {} as any, failedLabwareUtils: mockFailedLabwareUtils, }) @@ -141,6 +153,7 @@ describe('getRunCurrentLabwareOnDeck', () => { }) it('should set highlight to null if getIsLabwareMatch returns false', () => { + vi.mocked(getLabwareLocation).mockReturnValue(null) const result = getRunCurrentLabwareOnDeck({ failedLabwareUtils: { ...mockFailedLabwareUtils, @@ -149,6 +162,7 @@ describe('getRunCurrentLabwareOnDeck', () => { location: { slotName: 'B1' }, }, }, + runRecord: {} as any, currentLabwareInfo: [mockCurrentLabwareInfo], }) @@ -171,7 +185,6 @@ describe('getRunCurrentModulesInfo', () => { }, } as any const mockDeckDef = {} as any - const mockProtocolAnalysis = {} as any beforeEach(() => { vi.mocked(getLoadedLabwareDefinitionsByUri).mockReturnValue({ @@ -185,7 +198,7 @@ describe('getRunCurrentModulesInfo', () => { const result = getRunCurrentModulesInfo({ runRecord: null as any, deckDef: mockDeckDef, - protocolAnalysis: mockProtocolAnalysis, + labwareDefinitionsByUri: {}, }) expect(result).toEqual([]) @@ -195,17 +208,20 @@ describe('getRunCurrentModulesInfo', () => { const result = getRunCurrentModulesInfo({ runRecord: mockRunRecord, deckDef: mockDeckDef, - protocolAnalysis: null, + labwareDefinitionsByUri: null, }) expect(result).toEqual([]) }) it('should return an array of RunCurrentModuleInfo objects for each module in runRecord.data.modules', () => { + vi.mocked(getLabwareLocation).mockReturnValue({ slotName: 'A1' }) const result = getRunCurrentModulesInfo({ runRecord: mockRunRecord, deckDef: mockDeckDef, - protocolAnalysis: mockProtocolAnalysis, + labwareDefinitionsByUri: { + 'opentrons/opentrons_96_pcr_adapter/1': 'MOCK_LW_DEF', + } as any, }) expect(result).toEqual([ @@ -213,7 +229,7 @@ describe('getRunCurrentModulesInfo', () => { moduleId: mockModule.id, moduleDef: 'MOCK_MODULE_DEF', nestedLabwareDef: 'MOCK_LW_DEF', - nestedLabwareSlotName: 'MOCK_MODULE_ID', + nestedLabwareSlotName: 'A1', slotName: mockModule.location.slotName, }, ]) @@ -226,7 +242,7 @@ describe('getRunCurrentModulesInfo', () => { data: { modules: [mockModule], labware: [] }, }, deckDef: mockDeckDef, - protocolAnalysis: mockProtocolAnalysis, + labwareDefinitionsByUri: {}, }) expect(result).toEqual([ { @@ -245,7 +261,7 @@ describe('getRunCurrentModulesInfo', () => { const result = getRunCurrentModulesInfo({ runRecord: mockRunRecord, deckDef: mockDeckDef, - protocolAnalysis: mockProtocolAnalysis, + labwareDefinitionsByUri: null, }) expect(result).toEqual([]) }) @@ -270,7 +286,7 @@ describe('getRunCurrentLabwareInfo', () => { it('should return an empty array if runRecord is null', () => { const result = getRunCurrentLabwareInfo({ runRecord: undefined, - protocolAnalysis: {} as any, + labwareDefinitionsByUri: {} as any, }) expect(result).toEqual([]) @@ -279,7 +295,7 @@ describe('getRunCurrentLabwareInfo', () => { it('should return an empty array if protocolAnalysis is null', () => { const result = getRunCurrentLabwareInfo({ runRecord: { data: { labware: [] } } as any, - protocolAnalysis: null, + labwareDefinitionsByUri: null, }) expect(result).toEqual([]) @@ -293,7 +309,9 @@ describe('getRunCurrentLabwareInfo', () => { const result = getRunCurrentLabwareInfo({ runRecord: { data: { labware: [mockPickUpTipLwSlotName] } } as any, - protocolAnalysis: { commands: [] } as any, + labwareDefinitionsByUri: { + [mockPickUpTipLabware.definitionUri]: mockLabwareDef, + }, }) expect(result).toEqual([ @@ -308,46 +326,64 @@ describe('getRunCurrentLabwareInfo', () => { describe('getSlotNameAndLwLocFrom', () => { it('should return [null, null] if location is null', () => { - const result = getSlotNameAndLwLocFrom(null, false) + const result = getSlotNameAndLwLocFrom(null, {} as any, false) expect(result).toEqual([null, null]) }) it('should return [null, null] if location is "offDeck"', () => { - const result = getSlotNameAndLwLocFrom('offDeck', false) + const result = getSlotNameAndLwLocFrom('offDeck', {} as any, false) expect(result).toEqual([null, null]) }) it('should return [null, null] if location has a moduleId and excludeModules is true', () => { - const result = getSlotNameAndLwLocFrom({ moduleId: 'MOCK_MODULE_ID' }, true) + const result = getSlotNameAndLwLocFrom( + { moduleId: 'MOCK_MODULE_ID' }, + {} as any, + true + ) expect(result).toEqual([null, null]) }) - it('should return [moduleId, { moduleId }] if location has a moduleId and excludeModules is false', () => { + it('should return [baseSlot, { moduleId }] if location has a moduleId and excludeModules is false', () => { + vi.mocked(getLabwareLocation).mockReturnValue({ slotName: 'A1' }) const result = getSlotNameAndLwLocFrom( { moduleId: 'MOCK_MODULE_ID' }, + {} as any, false ) - expect(result).toEqual(['MOCK_MODULE_ID', { moduleId: 'MOCK_MODULE_ID' }]) + expect(result).toEqual(['A1', { moduleId: 'MOCK_MODULE_ID' }]) }) - it('should return [labwareId, { labwareId }] if location has a labwareId', () => { - const result = getSlotNameAndLwLocFrom({ labwareId: 'MOCK_LW_ID' }, false) - expect(result).toEqual(['MOCK_LW_ID', { labwareId: 'MOCK_LW_ID' }]) + it('should return [baseSlot, { labwareId }] if location has a labwareId', () => { + vi.mocked(getLabwareLocation).mockReturnValue({ slotName: 'A1' }) + const result = getSlotNameAndLwLocFrom( + { labwareId: 'MOCK_LW_ID' }, + {} as any, + false + ) + expect(result).toEqual(['A1', { labwareId: 'MOCK_LW_ID' }]) }) it('should return [addressableAreaName, { addressableAreaName }] if location has an addressableAreaName', () => { - const result = getSlotNameAndLwLocFrom({ addressableAreaName: 'A1' }, false) + vi.mocked(getLabwareLocation).mockReturnValue({ slotName: 'A1' }) + const result = getSlotNameAndLwLocFrom( + { addressableAreaName: 'A1' }, + {} as any, + false + ) expect(result).toEqual(['A1', { addressableAreaName: 'A1' }]) }) it('should return [slotName, { slotName }] if location has a slotName', () => { - const result = getSlotNameAndLwLocFrom({ slotName: 'A1' }, false) + vi.mocked(getLabwareLocation).mockReturnValue({ slotName: 'A1' }) + const result = getSlotNameAndLwLocFrom({ slotName: 'A1' }, {} as any, false) expect(result).toEqual(['A1', { slotName: 'A1' }]) }) it('should return [null, null] if location does not match any known location type', () => { const result = getSlotNameAndLwLocFrom( { unknownProperty: 'MOCK_VALUE' } as any, + {} as any, false ) expect(result).toEqual([null, null]) @@ -355,57 +391,90 @@ describe('getSlotNameAndLwLocFrom', () => { }) describe('getIsLabwareMatch', () => { + beforeEach(() => { + vi.mocked(getLabwareLocation).mockReturnValue(null) + }) + it('should return false if pickUpTipLabware is null', () => { - const result = getIsLabwareMatch('A1', null) + const result = getIsLabwareMatch('A1', {} as any, null) expect(result).toBe(false) }) it('should return false if pickUpTipLabware location is a string', () => { - const result = getIsLabwareMatch('offdeck', { location: 'offdeck' } as any) + const result = getIsLabwareMatch( + 'offdeck', + {} as any, + { location: 'offdeck' } as any + ) expect(result).toBe(false) }) it('should return false if pickUpTipLabware location has a moduleId', () => { - const result = getIsLabwareMatch('A1', { - location: { moduleId: 'MOCK_MODULE_ID' }, - } as any) + const result = getIsLabwareMatch( + 'A1', + {} as any, + { + location: { moduleId: 'MOCK_MODULE_ID' }, + } as any + ) expect(result).toBe(false) }) it('should return true if pickUpTipLabware location slotName matches the provided slotName', () => { - const result = getIsLabwareMatch('A1', { - location: { slotName: 'A1' }, - } as any) + vi.mocked(getLabwareLocation).mockReturnValue({ slotName: 'A1' }) + const result = getIsLabwareMatch( + 'A1', + {} as any, + { + location: { slotName: 'A1' }, + } as any + ) expect(result).toBe(true) }) it('should return false if pickUpTipLabware location slotName does not match the provided slotName', () => { - const result = getIsLabwareMatch('A1', { - location: { slotName: 'A2' }, - } as any) + const result = getIsLabwareMatch( + 'A1', + {} as any, + { + location: { slotName: 'A2' }, + } as any + ) expect(result).toBe(false) }) it('should return true if pickUpTipLabware location labwareId matches the provided slotName', () => { - const result = getIsLabwareMatch('lwId', { - location: { labwareId: 'lwId' }, - } as any) + vi.mocked(getLabwareLocation).mockReturnValue({ slotName: 'C1' }) + + const result = getIsLabwareMatch( + 'C1', + {} as any, + { + location: { labwareId: 'lwId' }, + } as any + ) expect(result).toBe(true) }) it('should return false if pickUpTipLabware location labwareId does not match the provided slotName', () => { - const result = getIsLabwareMatch('lwId', { - location: { labwareId: 'lwId2' }, - } as any) + const result = getIsLabwareMatch( + 'lwId', + {} as any, + { + location: { labwareId: 'lwId2' }, + } as any + ) expect(result).toBe(false) }) it('should return true if pickUpTipLabware location addressableAreaName matches the provided slotName', () => { + vi.mocked(getLabwareLocation).mockReturnValue({ slotName: 'B1' }) + const slotName = 'B1' const pickUpTipLabware = { location: { addressableAreaName: 'B1' }, } as any - const result = getIsLabwareMatch(slotName, pickUpTipLabware) + const result = getIsLabwareMatch(slotName, {} as any, pickUpTipLabware) expect(result).toBe(true) }) @@ -414,7 +483,7 @@ describe('getIsLabwareMatch', () => { const pickUpTipLabware = { location: { addressableAreaName: 'B2' }, } as any - const result = getIsLabwareMatch(slotName, pickUpTipLabware) + const result = getIsLabwareMatch(slotName, {} as any, pickUpTipLabware) expect(result).toBe(false) }) @@ -423,7 +492,165 @@ describe('getIsLabwareMatch', () => { const pickUpTipLabware = { location: { unknownProperty: 'someValue' }, } as any - const result = getIsLabwareMatch(slotName, pickUpTipLabware) + const result = getIsLabwareMatch(slotName, {} as any, pickUpTipLabware) expect(result).toBe(false) }) }) + +describe('updateLabwareInModules', () => { + const mockLabwareDef: LabwareDefinition2 = { + ...(fixture96Plate as LabwareDefinition2), + metadata: { + displayName: 'Mock Labware Definition', + displayCategory: 'wellPlate', + displayVolumeUnits: 'mL', + }, + } + + const mockModule = { + moduleModel: 'temperatureModuleV2', + moduleLocation: { slotName: 'A1' }, + innerProps: {}, + nestedLabwareDef: null, + highlight: null, + } as any + + const mockLabware = { + labwareDef: mockLabwareDef, + labwareLocation: { slotName: 'A1' }, + slotName: 'A1', + } + + it('should update module with nested labware when they share the same slot', () => { + const result = updateLabwareInModules({ + runCurrentModules: [mockModule], + currentLabwareInfo: [mockLabware], + }) + + expect(result.updatedModules).toEqual([ + { + ...mockModule, + nestedLabwareDef: mockLabwareDef, + }, + ]) + expect(result.remainingLabware).toEqual([]) + }) + + it('should keep labware separate when slots do not match', () => { + const labwareInDifferentSlot = { + ...mockLabware, + labwareLocation: { slotName: 'B1' }, + slotName: 'B1', + } + + const result = updateLabwareInModules({ + runCurrentModules: [mockModule], + currentLabwareInfo: [labwareInDifferentSlot], + }) + + expect(result.updatedModules).toEqual([mockModule]) + expect(result.remainingLabware).toEqual([labwareInDifferentSlot]) + }) + + it('should handle multiple modules and labware', () => { + const mockModuleB1 = { + ...mockModule, + moduleLocation: { slotName: 'B1' }, + } + + const labwareB1 = { + ...mockLabware, + labwareLocation: { slotName: 'B1' }, + slotName: 'B1', + } + + const labwareC1 = { + ...mockLabware, + labwareLocation: { slotName: 'C1' }, + slotName: 'C1', + } + + const result = updateLabwareInModules({ + runCurrentModules: [mockModule, mockModuleB1], + currentLabwareInfo: [mockLabware, labwareB1, labwareC1], + }) + + expect(result.updatedModules).toEqual([ + { + ...mockModule, + nestedLabwareDef: mockLabwareDef, + }, + { + ...mockModuleB1, + nestedLabwareDef: mockLabwareDef, + }, + ]) + expect(result.remainingLabware).toEqual([labwareC1]) + }) + + it('should handle empty modules array', () => { + const result = updateLabwareInModules({ + runCurrentModules: [], + currentLabwareInfo: [mockLabware], + }) + + expect(result.updatedModules).toEqual([]) + expect(result.remainingLabware).toEqual([mockLabware]) + }) + + it('should handle empty labware array', () => { + const result = updateLabwareInModules({ + runCurrentModules: [mockModule], + currentLabwareInfo: [], + }) + + expect(result.updatedModules).toEqual([mockModule]) + expect(result.remainingLabware).toEqual([]) + }) + + it('should handle multiple labware in same slot, nesting only one with module', () => { + const labwareA1Second = { + ...mockLabware, + labwareDef: { + ...mockLabwareDef, + metadata: { + ...mockLabwareDef.metadata, + displayName: 'Second Labware', + }, + }, + } + + const result = updateLabwareInModules({ + runCurrentModules: [mockModule], + currentLabwareInfo: [mockLabware, labwareA1Second], + }) + + expect(result.updatedModules).toEqual([ + { + ...mockModule, + nestedLabwareDef: mockLabwareDef, + }, + ]) + expect(result.remainingLabware).toEqual([]) + }) + + it('should preserve module properties when updating with nested labware', () => { + const moduleWithProperties = { + ...mockModule, + innerProps: { lidMotorState: 'open' }, + highlight: 'someHighlight', + } + + const result = updateLabwareInModules({ + runCurrentModules: [moduleWithProperties], + currentLabwareInfo: [mockLabware], + }) + + expect(result.updatedModules).toEqual([ + { + ...moduleWithProperties, + nestedLabwareDef: mockLabwareDef, + }, + ]) + }) +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useErrorName.test.tsx b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useErrorName.test.tsx index 9ed6a072da4..4de37a3a6f5 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useErrorName.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useErrorName.test.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { describe, it } from 'vitest' import { renderHook, render, screen } from '@testing-library/react' diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useFailedLabwareUtils.test.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useFailedLabwareUtils.test.ts deleted file mode 100644 index b20ab13a1cd..00000000000 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useFailedLabwareUtils.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { describe, it, expect } from 'vitest' - -import { - getRelevantWellName, - getRelevantFailedLabwareCmdFrom, -} from '../useFailedLabwareUtils' -import { DEFINED_ERROR_TYPES } from '../../constants' - -describe('getRelevantWellName', () => { - const failedPipetteInfo = { - data: { - channels: 8, - }, - } as any - - const recentRelevantPickUpTipCmd = { - params: { - pipetteId: 'pipetteId', - labwareId: 'labwareId', - wellName: 'A1', - }, - } as any - - it('should return an empty string if failedPipetteInfo is null', () => { - const result = getRelevantWellName(null, recentRelevantPickUpTipCmd) - expect(result).toBe('') - }) - - it('should return an empty string if recentRelevantPickUpTipCmd is null', () => { - const result = getRelevantWellName(failedPipetteInfo, null) - expect(result).toBe('') - }) - - it('should return the wellName if the pipette has 1 channel', () => { - const result = getRelevantWellName( - { ...failedPipetteInfo, data: { channels: 1 } }, - recentRelevantPickUpTipCmd - ) - expect(result).toBe('A1') - }) - - it('should return a range of well names if the pipette has 8 channels', () => { - const result = getRelevantWellName( - failedPipetteInfo, - recentRelevantPickUpTipCmd - ) - expect(result).toBe('A1 - H1') - }) - - it('should return the wellName if the pipette has 96 channels', () => { - const result = getRelevantWellName( - { ...failedPipetteInfo, data: { channels: 96 } }, - recentRelevantPickUpTipCmd - ) - expect(result).toBe('A1') - }) - - it('should handle different wellName formats correctly', () => { - const result = getRelevantWellName(failedPipetteInfo, { - ...recentRelevantPickUpTipCmd, - params: { ...recentRelevantPickUpTipCmd.params, wellName: 'B12' }, - }) - expect(result).toBe('A12 - H12') - }) -}) - -describe('getRelevantFailedLabwareCmdFrom', () => { - const failedCommand = { - error: { - errorType: DEFINED_ERROR_TYPES.LIQUID_NOT_FOUND, - }, - params: { - wellName: 'A1', - pipetteId: 'pipetteId', - }, - } as any - - it('should return the failedCommand for NO_LIQUID_DETECTED error kind', () => { - const failedLiquidProbeCommand = { - ...failedCommand, - commandType: 'liquidProbe', - error: { - isDefined: true, - errorType: DEFINED_ERROR_TYPES.LIQUID_NOT_FOUND, - }, - } - const result = getRelevantFailedLabwareCmdFrom({ - failedCommandByRunRecord: failedLiquidProbeCommand, - }) - expect(result).toEqual(failedLiquidProbeCommand) - }) - - it('should return the relevant pickUpTip command for overpressure error kinds', () => { - const pickUpTipCommand = { - commandType: 'pickUpTip', - params: { - pipetteId: 'pipetteId', - labwareId: 'labwareId', - wellName: 'A1', - }, - } as any - const runCommands = { - data: [pickUpTipCommand, failedCommand], - } as any - - const overpressureErrorKinds = [ - ['aspirate', DEFINED_ERROR_TYPES.OVERPRESSURE], - ['dispense', DEFINED_ERROR_TYPES.OVERPRESSURE], - ] - - overpressureErrorKinds.forEach(([commandType, errorType]) => { - const result = getRelevantFailedLabwareCmdFrom({ - failedCommandByRunRecord: { - ...failedCommand, - commandType, - error: { isDefined: true, errorType }, - }, - runCommands, - }) - expect(result).toBe(pickUpTipCommand) - }) - }) - it('should return null for GENERAL_ERROR error kind', () => { - const result = getRelevantFailedLabwareCmdFrom({ - failedCommandByRunRecord: { - ...failedCommand, - error: { errorType: 'literally anything else' }, - }, - }) - expect(result).toBeNull() - }) - - it('should return null for unhandled error kinds', () => { - const result = getRelevantFailedLabwareCmdFrom({ - failedCommandByRunRecord: { - ...failedCommand, - error: { errorType: 'SOME_UNHANDLED_ERROR' }, - }, - }) - expect(result).toBeNull() - }) -}) diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useFailedLabwareUtils.test.tsx b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useFailedLabwareUtils.test.tsx new file mode 100644 index 00000000000..f8559163adb --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useFailedLabwareUtils.test.tsx @@ -0,0 +1,251 @@ +import { describe, it, expect } from 'vitest' +import { screen, renderHook } from '@testing-library/react' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { + getRelevantWellName, + getRelevantFailedLabwareCmdFrom, + useRelevantFailedLwLocations, +} from '../useFailedLabwareUtils' +import { DEFINED_ERROR_TYPES } from '../../constants' + +import type { ComponentProps } from 'react' +import type { GetRelevantLwLocationsParams } from '../useFailedLabwareUtils' + +describe('getRelevantWellName', () => { + const failedPipetteInfo = { + data: { + channels: 8, + }, + } as any + + const recentRelevantPickUpTipCmd = { + params: { + pipetteId: 'pipetteId', + labwareId: 'labwareId', + wellName: 'A1', + }, + } as any + + it('should return an empty string if failedPipetteInfo is null', () => { + const result = getRelevantWellName(null, recentRelevantPickUpTipCmd) + expect(result).toBe('') + }) + + it('should return an empty string if recentRelevantPickUpTipCmd is null', () => { + const result = getRelevantWellName(failedPipetteInfo, null) + expect(result).toBe('') + }) + + it('should return the wellName if the pipette has 1 channel', () => { + const result = getRelevantWellName( + { ...failedPipetteInfo, data: { channels: 1 } }, + recentRelevantPickUpTipCmd + ) + expect(result).toBe('A1') + }) + + it('should return a range of well names if the pipette has 8 channels', () => { + const result = getRelevantWellName( + failedPipetteInfo, + recentRelevantPickUpTipCmd + ) + expect(result).toBe('A1 - H1') + }) + + it('should return the wellName if the pipette has 96 channels', () => { + const result = getRelevantWellName( + { ...failedPipetteInfo, data: { channels: 96 } }, + recentRelevantPickUpTipCmd + ) + expect(result).toBe('A1') + }) + + it('should handle different wellName formats correctly', () => { + const result = getRelevantWellName(failedPipetteInfo, { + ...recentRelevantPickUpTipCmd, + params: { ...recentRelevantPickUpTipCmd.params, wellName: 'B12' }, + }) + expect(result).toBe('A12 - H12') + }) +}) + +describe('getRelevantFailedLabwareCmdFrom', () => { + const failedCommand = { + error: { + errorType: DEFINED_ERROR_TYPES.LIQUID_NOT_FOUND, + }, + params: { + wellName: 'A1', + pipetteId: 'pipetteId', + }, + } as any + + it('should return the failedCommand for NO_LIQUID_DETECTED error kind', () => { + const failedLiquidProbeCommand = { + ...failedCommand, + commandType: 'liquidProbe', + error: { + isDefined: true, + errorType: DEFINED_ERROR_TYPES.LIQUID_NOT_FOUND, + }, + } + const result = getRelevantFailedLabwareCmdFrom({ + failedCommand: { byRunRecord: failedLiquidProbeCommand } as any, + }) + expect(result).toEqual(failedLiquidProbeCommand) + }) + + it('should return the relevant pickUpTip command for overpressure error kinds', () => { + const pickUpTipCommand = { + commandType: 'pickUpTip', + params: { + pipetteId: 'pipetteId', + labwareId: 'labwareId', + wellName: 'A1', + }, + } as any + const runCommands = { + data: [pickUpTipCommand, failedCommand], + } as any + + const overpressureErrorKinds = [ + ['aspirate', DEFINED_ERROR_TYPES.OVERPRESSURE], + ['dispense', DEFINED_ERROR_TYPES.OVERPRESSURE], + ] + + overpressureErrorKinds.forEach(([commandType, errorType]) => { + const result = getRelevantFailedLabwareCmdFrom({ + failedCommand: { + byRunRecord: { + ...failedCommand, + commandType, + error: { isDefined: true, errorType }, + }, + } as any, + runCommands, + }) + expect(result).toBe(pickUpTipCommand) + }) + }) + + it('should return the failedCommand for GRIPPER_ERROR error kind', () => { + const failedGripperCommand = { + ...failedCommand, + commandType: 'moveLabware', + error: { + isDefined: true, + errorType: DEFINED_ERROR_TYPES.GRIPPER_MOVEMENT, + }, + } + const result = getRelevantFailedLabwareCmdFrom({ + failedCommand: { byRunRecord: failedGripperCommand } as any, + }) + expect(result).toEqual(failedGripperCommand) + }) + + it('should return null for GENERAL_ERROR error kind', () => { + const result = getRelevantFailedLabwareCmdFrom({ + failedCommand: { + byRunRecord: { + ...failedCommand, + error: { + errorType: 'literally anything else', + }, + }, + } as any, + }) + expect(result).toBeNull() + }) + + it('should return null for unhandled error kinds', () => { + const result = getRelevantFailedLabwareCmdFrom({ + failedCommand: { + byRunRecord: { + ...failedCommand, + error: { errorType: 'SOME_UNHANDLED_ERROR' }, + }, + } as any, + }) + expect(result).toBeNull() + }) +}) + +const TestWrapper = (props: GetRelevantLwLocationsParams) => { + const displayLocation = useRelevantFailedLwLocations(props) + return ( + <> +
    {`Current Loc: ${displayLocation.displayNameCurrentLoc}`}
    +
    {`New Loc: ${displayLocation.displayNameNewLoc}`}
    + + ) +} + +const render = (props: ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] +} + +describe('useRelevantFailedLwLocations', () => { + const mockRunRecord = { data: { modules: [], labware: [] } } as any + const mockFailedLabware = { + location: { slotName: 'D1' }, + } as any + + it('should return current location for non-moveLabware commands', () => { + const mockFailedCommand = { + commandType: 'aspirate', + } as any + + render({ + failedLabware: mockFailedLabware, + failedCommandByRunRecord: mockFailedCommand, + runRecord: mockRunRecord, + }) + + screen.getByText('Current Loc: Slot D1') + screen.getByText('New Loc: null') + + const { result } = renderHook(() => + useRelevantFailedLwLocations({ + failedLabware: mockFailedLabware, + failedCommandByRunRecord: mockFailedCommand, + runRecord: mockRunRecord, + }) + ) + + expect(result.current.currentLoc).toStrictEqual({ slotName: 'D1' }) + expect(result.current.newLoc).toBeNull() + }) + + it('should return current and new locations for moveLabware commands', () => { + const mockFailedCommand = { + commandType: 'moveLabware', + params: { + newLocation: { slotName: 'C2' }, + }, + } as any + + render({ + failedLabware: mockFailedLabware, + failedCommandByRunRecord: mockFailedCommand, + runRecord: mockRunRecord, + }) + + screen.getByText('Current Loc: Slot D1') + screen.getByText('New Loc: Slot C2') + + const { result } = renderHook(() => + useRelevantFailedLwLocations({ + failedLabware: mockFailedLabware, + failedCommandByRunRecord: mockFailedCommand, + runRecord: mockRunRecord, + }) + ) + + expect(result.current.currentLoc).toStrictEqual({ slotName: 'D1' }) + expect(result.current.newLoc).toStrictEqual({ slotName: 'C2' }) + }) +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useFailedPipetteUtils.test.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useFailedPipetteUtils.test.ts new file mode 100644 index 00000000000..f0da2aef402 --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useFailedPipetteUtils.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, it } from 'vitest' + +import { getFailedCommandPipetteInfo } from '../useFailedPipetteUtils' + +describe('getFailedCommandPipetteInfo', () => { + const failedCommand = { + params: { + pipetteId: 'pipetteId1', + }, + } as any + + const runRecordPipette1 = { + id: 'pipetteId1', + mount: 'left', + } as any + + const runRecordPipette2 = { + id: 'pipetteId2', + mount: 'right', + } as any + + const attachedInstrument1 = { + mount: 'left', + name: 'Pipette 1', + } as any + + const attachedInstrument2 = { + mount: 'right', + name: 'Pipette 2', + } as any + + it('should return null if failedCommand is null', () => { + const result = getFailedCommandPipetteInfo({ + failedCommandByRunRecord: null, + runRecord: undefined, + attachedInstruments: undefined, + }) + expect(result).toBeNull() + }) + + it('should return null if failedCommand does not have pipetteId in params', () => { + const result = getFailedCommandPipetteInfo({ + failedCommandByRunRecord: failedCommand, + runRecord: undefined, + attachedInstruments: undefined, + }) + expect(result).toBeNull() + }) + + it('should return null if no matching pipette is found in runRecord', () => { + const result = getFailedCommandPipetteInfo({ + failedCommandByRunRecord: failedCommand, + runRecord: { data: { pipettes: [runRecordPipette2] } } as any, + attachedInstruments: { + data: [attachedInstrument1, attachedInstrument2], + } as any, + }) + expect(result).toBeNull() + }) + + it('should return null if no matching instrument is found in attachedInstruments', () => { + const result = getFailedCommandPipetteInfo({ + failedCommandByRunRecord: failedCommand, + runRecord: { data: { pipettes: [runRecordPipette1] } } as any, + attachedInstruments: { data: [attachedInstrument2] } as any, + }) + expect(result).toBeNull() + }) + + it('should return the matching pipette data', () => { + const result = getFailedCommandPipetteInfo({ + failedCommandByRunRecord: failedCommand, + runRecord: { + data: { pipettes: [runRecordPipette1, runRecordPipette2] }, + } as any, + attachedInstruments: { + data: [attachedInstrument1, attachedInstrument2], + } as any, + }) + expect(result).toEqual(attachedInstrument1) + }) +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryActionMutation.test.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryActionMutation.test.ts index 029f7d5e239..31ba5873ac2 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryActionMutation.test.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryActionMutation.test.ts @@ -1,55 +1,95 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' import { renderHook } from '@testing-library/react' -import { useRunActionMutations } from '@opentrons/react-api-client' +import { usePlayRunMutation } from '@opentrons/react-api-client' import { useRecoveryActionMutation } from '../useRecoveryActionMutation' +import { RECOVERY_MAP } from '../../constants' import type { Mock } from 'vitest' vi.mock('@opentrons/react-api-client', () => ({ - useRunActionMutations: vi.fn(), + usePlayRunMutation: vi.fn(), })) describe('useRecoveryActionMutation', () => { const mockRunId = 'MOCK_ID' - let mockPlayRun: Mock - let mockIsPlayRunActionLoading: boolean + let mockMutateAsync: Mock + let mockIsLoading: boolean + let mockProceedToRouteAndStep: Mock beforeEach(() => { - mockPlayRun = vi.fn() - mockIsPlayRunActionLoading = false + mockMutateAsync = vi.fn() + mockIsLoading = false + mockProceedToRouteAndStep = vi.fn().mockResolvedValue(undefined) - vi.mocked(useRunActionMutations).mockReturnValue({ - playRun: mockPlayRun, - isPlayRunActionLoading: mockIsPlayRunActionLoading, + vi.mocked(usePlayRunMutation).mockReturnValue({ + mutateAsync: mockMutateAsync, + isLoading: mockIsLoading, } as any) }) it('should return resumeRecovery and isResumeRecoveryLoading', () => { - const { result } = renderHook(() => useRecoveryActionMutation(mockRunId)) + const { result } = renderHook(() => + useRecoveryActionMutation(mockRunId, { + proceedToRouteAndStep: mockProceedToRouteAndStep, + } as any) + ) - expect(result.current).toEqual({ - resumeRecovery: mockPlayRun, - isResumeRecoveryLoading: mockIsPlayRunActionLoading, - }) + expect(result.current).toHaveProperty('resumeRecovery') + expect(result.current).toHaveProperty('isResumeRecoveryLoading') + expect(result.current.isResumeRecoveryLoading).toBe(false) }) it('should return updated isResumeRecoveryLoading when it changes', () => { const { result, rerender } = renderHook(() => - useRecoveryActionMutation(mockRunId) + useRecoveryActionMutation(mockRunId, { + proceedToRouteAndStep: mockProceedToRouteAndStep, + } as any) ) expect(result.current.isResumeRecoveryLoading).toBe(false) - mockIsPlayRunActionLoading = true - vi.mocked(useRunActionMutations).mockReturnValue({ - playRun: mockPlayRun, - isPlayRunActionLoading: mockIsPlayRunActionLoading, + mockIsLoading = true + vi.mocked(usePlayRunMutation).mockReturnValue({ + mutateAsync: mockMutateAsync, + isLoading: mockIsLoading, } as any) rerender() expect(result.current.isResumeRecoveryLoading).toBe(true) }) + + it('should call mutateAsync with runId when resumeRecovery is called', async () => { + const { result } = renderHook(() => + useRecoveryActionMutation(mockRunId, { + proceedToRouteAndStep: mockProceedToRouteAndStep, + } as any) + ) + + mockMutateAsync.mockResolvedValue('MOCK_RESULT') + + await result.current.resumeRecovery() + + expect(mockMutateAsync).toHaveBeenCalledWith(mockRunId) + }) + + it('should handle error and proceed to error route when resumeRecovery fails', async () => { + const { result } = renderHook(() => + useRecoveryActionMutation(mockRunId, { + proceedToRouteAndStep: mockProceedToRouteAndStep, + } as any) + ) + + mockMutateAsync.mockRejectedValue(new Error('MOCK_ERROR')) + + await expect(result.current.resumeRecovery()).rejects.toThrow( + 'Could not resume recovery: Error: MOCK_ERROR' + ) + + expect(mockProceedToRouteAndStep).toHaveBeenCalledWith( + RECOVERY_MAP.ERROR_WHILE_RECOVERING.ROUTE + ) + }) }) diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryCommands.test.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryCommands.test.ts index 1ca5733c106..ce333c1fb39 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryCommands.test.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryCommands.test.ts @@ -4,20 +4,28 @@ import { renderHook, act } from '@testing-library/react' import { useResumeRunFromRecoveryMutation, useStopRunMutation, - useUpdateErrorRecoveryPolicy, + useResumeRunFromRecoveryAssumingFalsePositiveMutation, } from '@opentrons/react-api-client' -import { useChainRunCommands } from '../../../../resources/runs' +import { + useChainRunCommands, + useUpdateRecoveryPolicyWithStrategy, +} from '/app/resources/runs' import { useRecoveryCommands, HOME_PIPETTE_Z_AXES, + RELEASE_GRIPPER_JAW, buildPickUpTips, buildIgnorePolicyRules, + isAssumeFalsePositiveResumeKind, + HOME_EXCEPT_PLUNGERS, } from '../useRecoveryCommands' -import { RECOVERY_MAP } from '../../constants' +import { RECOVERY_MAP, ERROR_KINDS } from '../../constants' +import { getErrorKind } from '/app/organisms/ErrorRecoveryFlows/utils' vi.mock('@opentrons/react-api-client') -vi.mock('../../../../resources/runs') +vi.mock('/app/resources/runs') +vi.mock('/app/organisms/ErrorRecoveryFlows/utils') describe('useRecoveryCommands', () => { const mockFailedCommand = { @@ -38,15 +46,22 @@ describe('useRecoveryCommands', () => { const mockResumeRunFromRecovery = vi.fn(() => Promise.resolve(mockMakeSuccessToast()) ) + const mockResumeRunFromRecoveryAssumingFalsePositive = vi.fn(() => + Promise.resolve(mockMakeSuccessToast()) + ) const mockStopRun = vi.fn() const mockChainRunCommands = vi.fn().mockResolvedValue([]) const mockReportActionSelectedResult = vi.fn() const mockReportRecoveredRunResult = vi.fn() - const mockUpdateErrorRecoveryPolicy = vi.fn() + const mockUpdateErrorRecoveryPolicy = vi.fn(() => Promise.resolve()) const props = { runId: mockRunId, - failedCommandByRunRecord: mockFailedCommand, + failedCommand: { + byRunRecord: mockFailedCommand, + byAnalysis: mockFailedCommand, + }, + unvalidatedFailedCommand: mockFailedCommand, failedLabwareUtils: mockFailedLabwareUtils, routeUpdateActions: mockRouteUpdateActions, recoveryToastUtils: { makeSuccessToast: mockMakeSuccessToast } as any, @@ -67,8 +82,13 @@ describe('useRecoveryCommands', () => { vi.mocked(useChainRunCommands).mockReturnValue({ chainRunCommands: mockChainRunCommands, } as any) - vi.mocked(useUpdateErrorRecoveryPolicy).mockReturnValue({ - updateErrorRecoveryPolicy: mockUpdateErrorRecoveryPolicy, + vi.mocked(useUpdateRecoveryPolicyWithStrategy).mockReturnValue( + mockUpdateErrorRecoveryPolicy as any + ) + vi.mocked( + useResumeRunFromRecoveryAssumingFalsePositiveMutation + ).mockReturnValue({ + mutateAsync: mockResumeRunFromRecoveryAssumingFalsePositive, } as any) }) @@ -121,32 +141,54 @@ describe('useRecoveryCommands', () => { false ) }) - ;([ + + const IN_PLACE_COMMANDS = [ 'aspirateInPlace', 'dispenseInPlace', 'blowOutInPlace', 'dropTipInPlace', 'prepareToAspirate', - ] as const).forEach(inPlaceCommandType => { - it(`Should move to retryLocation if failed command is ${inPlaceCommandType} and error is appropriate when retrying`, async () => { - const { result } = renderHook(() => - useRecoveryCommands({ - runId: mockRunId, - failedCommandByRunRecord: { - ...mockFailedCommand, - commandType: inPlaceCommandType, - params: { - pipetteId: 'mock-pipette-id', - }, - error: { - errorType: 'overpressure', - errorCode: '3006', - isDefined: true, - errorInfo: { - retryLocation: [1, 2, 3], - }, + ] as const + + const ERROR_SCENARIOS = [ + { type: 'overpressure', code: '3006' }, + { type: 'tipPhysicallyAttached', code: '3007' }, + ] as const + + it.each( + ERROR_SCENARIOS.flatMap(error => + IN_PLACE_COMMANDS.map(commandType => ({ + errorType: error.type, + errorCode: error.code, + commandType, + })) + ) + )( + 'Should move to retryLocation if failed command is $commandType and error is $errorType when retrying', + async ({ errorType, errorCode, commandType }) => { + const { result } = renderHook(() => { + const failedCommand = { + ...mockFailedCommand, + commandType, + params: { + pipetteId: 'mock-pipette-id', + }, + error: { + errorType, + errorCode, + isDefined: true, + errorInfo: { + retryLocation: [1, 2, 3], }, }, + } + return useRecoveryCommands({ + runId: mockRunId, + failedCommand: { + byRunRecord: failedCommand, + byAnalysis: failedCommand, + }, + unvalidatedFailedCommand: failedCommand, failedLabwareUtils: mockFailedLabwareUtils, routeUpdateActions: mockRouteUpdateActions, recoveryToastUtils: {} as any, @@ -156,10 +198,12 @@ describe('useRecoveryCommands', () => { } as any, selectedRecoveryOption: RECOVERY_MAP.RETRY_NEW_TIPS.ROUTE, }) - ) + }) + await act(async () => { await result.current.retryFailedCommand() }) + expect(mockChainRunCommands).toHaveBeenLastCalledWith( [ { @@ -171,14 +215,14 @@ describe('useRecoveryCommands', () => { }, }, { - commandType: inPlaceCommandType, + commandType, params: { pipetteId: 'mock-pipette-id' }, }, ], false ) - }) - }) + } + ) it('should call resumeRun with runId and show success toast on success', async () => { const { result } = renderHook(() => useRecoveryCommands(props)) @@ -230,7 +274,7 @@ describe('useRecoveryCommands', () => { const testProps = { ...props, - failedCommandByRunRecord: mockFailedCmdWithPipetteId, + unvalidatedFailedCommand: mockFailedCmdWithPipetteId, failedLabwareUtils: { ...mockFailedLabwareUtils, failedLabware: mockFailedLabware, @@ -249,6 +293,32 @@ describe('useRecoveryCommands', () => { ) }) + it('should call releaseGripperJaws and resolve the promise', async () => { + const { result } = renderHook(() => useRecoveryCommands(props)) + + await act(async () => { + await result.current.releaseGripperJaws() + }) + + expect(mockChainRunCommands).toHaveBeenCalledWith( + [RELEASE_GRIPPER_JAW], + false + ) + }) + + it('should call useUpdatePositionEstimators and resolve the promise', async () => { + const { result } = renderHook(() => useRecoveryCommands(props)) + + await act(async () => { + await result.current.homeExceptPlungers() + }) + + expect(mockChainRunCommands).toHaveBeenCalledWith( + [HOME_EXCEPT_PLUNGERS], + false + ) + }) + it('should call skipFailedCommand and show success toast on success', async () => { const { result } = renderHook(() => useRecoveryCommands(props)) @@ -271,35 +341,110 @@ describe('useRecoveryCommands', () => { const testProps = { ...props, - failedCommandByRunRecord: mockFailedCommandWithError, + unvalidatedFailedCommand: mockFailedCommandWithError, } - const { result } = renderHook(() => useRecoveryCommands(testProps)) + const { result, rerender } = renderHook(() => + useRecoveryCommands(testProps) + ) await act(async () => { - await result.current.ignoreErrorKindThisRun() + await result.current.ignoreErrorKindThisRun(true) }) + rerender() + + result.current.skipFailedCommand() + const expectedPolicyRules = buildIgnorePolicyRules( 'aspirateInPlace', - 'mockErrorType' + 'mockErrorType', + 'ignoreAndContinue' ) expect(mockUpdateErrorRecoveryPolicy).toHaveBeenCalledWith( - expectedPolicyRules + expectedPolicyRules, + 'append' ) }) - it('should reject with an error when failedCommand or error is null', async () => { + it('should call proceedToRouteAndStep with ERROR_WHILE_RECOVERING route when updateErrorRecoveryPolicy rejects', async () => { + const mockFailedCommandWithError = { + ...mockFailedCommand, + commandType: 'aspirateInPlace', + error: { + errorType: 'mockErrorType', + }, + } + const testProps = { ...props, - failedCommand: null, + unvalidatedFailedCommand: mockFailedCommandWithError, } + mockUpdateErrorRecoveryPolicy.mockRejectedValueOnce( + new Error('Update policy failed') + ) + const { result } = renderHook(() => useRecoveryCommands(testProps)) - await expect(result.current.ignoreErrorKindThisRun()).rejects.toThrow( - 'Could not execute command. No failed command.' + await act(async () => { + await result.current.ignoreErrorKindThisRun(true) + }) + + expect(mockUpdateErrorRecoveryPolicy).toHaveBeenCalled() + expect(mockProceedToRouteAndStep).toHaveBeenCalledWith( + RECOVERY_MAP.ERROR_WHILE_RECOVERING.ROUTE ) }) + + describe('skipFailedCommand with false positive handling', () => { + it('should call resumeRunFromRecoveryAssumingFalsePositive for tip-related errors', async () => { + vi.mocked(getErrorKind).mockReturnValue(ERROR_KINDS.TIP_NOT_DETECTED) + + const { result } = renderHook(() => useRecoveryCommands(props)) + + await act(async () => { + await result.current.skipFailedCommand() + }) + + expect( + mockResumeRunFromRecoveryAssumingFalsePositive + ).toHaveBeenCalledWith(mockRunId) + expect(mockMakeSuccessToast).toHaveBeenCalled() + }) + + it('should call regular resumeRunFromRecovery for non-tip-related errors', async () => { + vi.mocked(getErrorKind).mockReturnValue(ERROR_KINDS.GRIPPER_ERROR) + + const { result } = renderHook(() => useRecoveryCommands(props)) + + await act(async () => { + await result.current.skipFailedCommand() + }) + + expect(mockResumeRunFromRecovery).toHaveBeenCalledWith(mockRunId) + expect(mockMakeSuccessToast).toHaveBeenCalled() + }) + }) +}) + +describe('isAssumeFalsePositiveResumeKind', () => { + it(`should return true for ${ERROR_KINDS.TIP_NOT_DETECTED} error kind`, () => { + vi.mocked(getErrorKind).mockReturnValue(ERROR_KINDS.TIP_NOT_DETECTED) + + expect(isAssumeFalsePositiveResumeKind({} as any)).toBe(true) + }) + + it(`should return true for ${ERROR_KINDS.TIP_DROP_FAILED} error kind`, () => { + vi.mocked(getErrorKind).mockReturnValue(ERROR_KINDS.TIP_DROP_FAILED) + + expect(isAssumeFalsePositiveResumeKind({} as any)).toBe(true) + }) + + it('should return false for other error kinds', () => { + vi.mocked(getErrorKind).mockReturnValue(ERROR_KINDS.GRIPPER_ERROR) + + expect(isAssumeFalsePositiveResumeKind({} as any)).toBe(false) + }) }) diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryOptionCopy.test.tsx b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryOptionCopy.test.tsx index b1988be0dd9..11e8a574246 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryOptionCopy.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryOptionCopy.test.tsx @@ -1,23 +1,29 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it } from 'vitest' import { screen } from '@testing-library/react' import { useRecoveryOptionCopy } from '../useRecoveryOptionCopy' -import { RECOVERY_MAP } from '../../constants' +import { ERROR_KINDS, RECOVERY_MAP } from '../../constants' -import type { RecoveryRoute } from '../../types' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import type { ErrorKind, RecoveryRoute } from '../../types' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' function MockRenderCmpt({ route, + errorKind, }: { route: RecoveryRoute | null + errorKind?: ErrorKind }): JSX.Element { const getRecoveryOptionCopy = useRecoveryOptionCopy() - return
    {getRecoveryOptionCopy(route)}
    + return ( +
    + {getRecoveryOptionCopy(route, errorKind ?? ERROR_KINDS.GENERAL_ERROR)} +
    + ) } const render = (props: React.ComponentProps) => { @@ -27,12 +33,30 @@ const render = (props: React.ComponentProps) => { } describe('useRecoveryOptionCopy', () => { - it(`renders the correct copy for ${RECOVERY_MAP.RETRY_FAILED_COMMAND.ROUTE}`, () => { - render({ route: RECOVERY_MAP.RETRY_FAILED_COMMAND.ROUTE }) + it(`renders the correct copy for ${RECOVERY_MAP.RETRY_STEP.ROUTE}`, () => { + render({ route: RECOVERY_MAP.RETRY_STEP.ROUTE }) screen.getByText('Retry step') }) + it(`renders the correct copy for ${RECOVERY_MAP.RETRY_STEP.ROUTE} when the error kind is ${ERROR_KINDS.TIP_DROP_FAILED}`, () => { + render({ + route: RECOVERY_MAP.RETRY_STEP.ROUTE, + errorKind: ERROR_KINDS.TIP_DROP_FAILED, + }) + + screen.getByText('Retry dropping tip') + }) + + it(`renders the correct copy for ${RECOVERY_MAP.RETRY_STEP.ROUTE} when the error kind is ${ERROR_KINDS.TIP_NOT_DETECTED}`, () => { + render({ + route: RECOVERY_MAP.RETRY_STEP.ROUTE, + errorKind: ERROR_KINDS.TIP_NOT_DETECTED, + }) + + screen.getByText('Retry picking up tip') + }) + it(`renders the correct copy for ${RECOVERY_MAP.CANCEL_RUN.ROUTE}`, () => { render({ route: RECOVERY_MAP.CANCEL_RUN.ROUTE }) @@ -51,8 +75,8 @@ describe('useRecoveryOptionCopy', () => { screen.getByText('Retry with same tips') }) - it(`renders the correct copy for ${RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.ROUTE}`, () => { - render({ route: RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.ROUTE }) + it(`renders the correct copy for ${RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE}`, () => { + render({ route: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE }) screen.getByText('Manually fill well and skip to next step') }) @@ -75,6 +99,23 @@ describe('useRecoveryOptionCopy', () => { screen.getByText('Skip to next step with same tips') }) + it(`renders the correct copy for ${RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE}`, () => { + render({ route: RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE }) + + screen.getByText('Manually move labware and skip to next step') + }) + + it(`renders the correct copy for ${RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE}`, () => { + render({ route: RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE }) + + screen.getByText('Manually replace labware on deck and retry step') + }) + + it(`renders the correct copy for ${RECOVERY_MAP.HOME_AND_RETRY.ROUTE}`, () => { + render({ route: RECOVERY_MAP.HOME_AND_RETRY.ROUTE }) + screen.getByText('Home gantry and retry step') + }) + it('renders "Unknown action" for an unknown recovery option', () => { render({ route: 'unknown_route' as RecoveryRoute }) diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryTakeover.test.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryTakeover.test.ts index 309ea6f0c93..c8bb315cff7 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryTakeover.test.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryTakeover.test.ts @@ -3,19 +3,19 @@ import { renderHook, act } from '@testing-library/react' import { useSelector } from 'react-redux' import { useRecoveryTakeover } from '../useRecoveryTakeover' -import { getUserId } from '../../../../redux/config' +import { getUserId } from '/app/redux/config' import { useClientDataRecovery, useUpdateClientDataRecovery, -} from '../../../../resources/client_data' +} from '/app/resources/client_data' import type { Mock } from 'vitest' vi.mock('react-redux', () => ({ useSelector: vi.fn(), })) -vi.mock('../../../../redux/config') -vi.mock('../../../../resources/client_data') +vi.mock('/app/redux/config') +vi.mock('/app/resources/client_data') describe('useRecoveryTakeover', () => { let mockToggleERWiz: Mock diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryToasts.test.tsx b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryToasts.test.tsx index 82a14835958..085551f42e7 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryToasts.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRecoveryToasts.test.tsx @@ -1,7 +1,7 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, describe, it, expect, beforeEach } from 'vitest' import { I18nextProvider } from 'react-i18next' -import { i18n } from '../../../../i18n' +import { i18n } from '/app/i18n' import { renderHook, render, screen } from '@testing-library/react' import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' @@ -14,25 +14,31 @@ import { } from '../useRecoveryToasts' import { RECOVERY_MAP } from '../../constants' import { useToaster } from '../../../ToasterOven' -import { useCommandTextString } from '../../../../molecules/Command' +import { useCommandTextString } from '/app/local-resources/commands' import type { Mock } from 'vitest' import type { BuildToast } from '../useRecoveryToasts' vi.mock('../../../ToasterOven') -vi.mock('../../../../molecules/Command') +vi.mock('/app/local-resources/commands') const TEST_COMMAND = 'test command' -const TC_COMMAND = 'tc command cycle some more text' +const TC_COMMAND = + 'tc starting profile of 1231231 element steps composed of some extra text bla bla' let mockMakeToast: Mock const DEFAULT_PROPS: BuildToast = { isOnDevice: false, - currentStepCount: 1, + stepCounts: { + currentStepNumber: 1, + hasRunDiverged: false, + totalStepCount: 1, + }, selectedRecoveryOption: RECOVERY_MAP.RETRY_SAME_TIPS.ROUTE, commandTextData: { commands: [] } as any, robotType: FLEX_ROBOT_TYPE, + allRunDefs: [], } // Utility function for rendering with I18nextProvider @@ -45,6 +51,7 @@ describe('useRecoveryToasts', () => { mockMakeToast = vi.fn() vi.mocked(useToaster).mockReturnValue({ makeToast: mockMakeToast } as any) vi.mocked(useCommandTextString).mockReturnValue({ + kind: 'generic', commandText: TEST_COMMAND, }) }) @@ -69,6 +76,7 @@ describe('useRecoveryToasts', () => { it('should make toast with correct parameters for desktop', () => { vi.mocked(useCommandTextString).mockReturnValue({ + kind: 'generic', commandText: TEST_COMMAND, }) @@ -80,19 +88,19 @@ describe('useRecoveryToasts', () => { ) vi.mocked(useCommandTextString).mockReturnValue({ + kind: 'generic', commandText: TEST_COMMAND, - stepTexts: undefined, }) result.current.makeSuccessToast() expect(mockMakeToast).toHaveBeenCalledWith( - 'Retrying step 1 succeeded.', + 'test command', 'success', expect.objectContaining({ closeButton: true, disableTimeout: true, displayType: 'desktop', - heading: expect.any(String), + heading: 'Retrying step 1 succeeded.', }) ) }) @@ -120,8 +128,8 @@ describe('useRecoveryToasts', () => { it('should use recoveryToastText when desktopFullCommandText is null', () => { vi.mocked(useCommandTextString).mockReturnValue({ + kind: 'generic', commandText: '', - stepTexts: undefined, }) const { result } = renderHook(() => @@ -183,28 +191,27 @@ describe('getStepNumber', () => { }) it('should handle a falsy currentStepCount', () => { - expect(getStepNumber(RECOVERY_MAP.RETRY_SAME_TIPS.ROUTE, null)).toBe('?') + expect(getStepNumber(RECOVERY_MAP.RETRY_SAME_TIPS.ROUTE, null)).toBe(null) }) it('should handle unknown recovery option', () => { - expect(getStepNumber('UNKNOWN_OPTION' as any, 3)).toBe( - 'HANDLE RECOVERY TOAST OPTION EXPLICITLY.' - ) + expect(getStepNumber('UNKNOWN_OPTION' as any, 3)).toBeNull() }) }) describe('useRecoveryFullCommandText', () => { it('should return the correct command text', () => { vi.mocked(useCommandTextString).mockReturnValue({ + kind: 'generic', commandText: TEST_COMMAND, - stepTexts: undefined, }) const { result } = renderHook(() => useRecoveryFullCommandText({ robotType: FLEX_ROBOT_TYPE, - stepNumber: 0, - commandTextData: { commands: [TEST_COMMAND] } as any, + stepNumber: 1, + commandTextData: { commands: [TEST_COMMAND, {}] } as any, + allRunDefs: [], }) ) @@ -213,8 +220,8 @@ describe('useRecoveryFullCommandText', () => { it('should return null when relevantCmd is null', () => { vi.mocked(useCommandTextString).mockReturnValue({ + kind: 'generic', commandText: '', - stepTexts: undefined, }) const { result } = renderHook(() => @@ -222,26 +229,29 @@ describe('useRecoveryFullCommandText', () => { robotType: FLEX_ROBOT_TYPE, stepNumber: 1, commandTextData: { commands: [] } as any, + allRunDefs: [], }) ) expect(result.current).toBeNull() }) - it('should return stepNumber if it is a string', () => { + it('should return null if there is no current step count', () => { const { result } = renderHook(() => useRecoveryFullCommandText({ robotType: FLEX_ROBOT_TYPE, - stepNumber: '?', + stepNumber: null, commandTextData: { commands: [] } as any, + allRunDefs: [], }) ) - expect(result.current).toBe('?') + expect(result.current).toBeNull() }) it('should truncate TC command', () => { vi.mocked(useCommandTextString).mockReturnValue({ + kind: 'thermocycler/runProfile', commandText: TC_COMMAND, stepTexts: ['step'], }) @@ -249,13 +259,33 @@ describe('useRecoveryFullCommandText', () => { const { result } = renderHook(() => useRecoveryFullCommandText({ robotType: FLEX_ROBOT_TYPE, - stepNumber: 0, + stepNumber: 1, commandTextData: { commands: [TC_COMMAND], } as any, + allRunDefs: [], }) ) + expect(result.current).toBe('tc starting profile of 1231231 element steps') + }) - expect(result.current).toBe('tc command cycle') + it('should truncate new TC command', () => { + vi.mocked(useCommandTextString).mockReturnValue({ + kind: 'thermocycler/runExtendedProfile', + commandText: TC_COMMAND, + profileElementTexts: [{ kind: 'step', stepText: 'blah blah blah' }], + }) + + const { result } = renderHook(() => + useRecoveryFullCommandText({ + robotType: FLEX_ROBOT_TYPE, + stepNumber: 1, + commandTextData: { + commands: [TC_COMMAND, {}], + } as any, + allRunDefs: [], + }) + ) + expect(result.current).toBe('tc starting profile of 1231231 element steps') }) }) diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRouteUpdateActions.test.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRouteUpdateActions.test.ts index b8fd72d06df..7162ea8d9d3 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRouteUpdateActions.test.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useRouteUpdateActions.test.ts @@ -25,10 +25,11 @@ describe('useRouteUpdateActions', () => { hasLaunchedRecovery: true, toggleERWizAsActiveUser: mockToggleERWizard, recoveryMap: { - route: RECOVERY_MAP.RETRY_FAILED_COMMAND.ROUTE, - step: RECOVERY_MAP.RETRY_FAILED_COMMAND.STEPS.CONFIRM_RETRY, + route: RECOVERY_MAP.RETRY_STEP.ROUTE, + step: RECOVERY_MAP.RETRY_STEP.STEPS.CONFIRM_RETRY, }, setRecoveryMap: mockSetRecoveryMap, + doorStatusUtils: { isProhibitedDoorOpen: false, isDoorOpen: false }, } }) @@ -38,7 +39,7 @@ describe('useRouteUpdateActions', () => { ) const { proceedNextStep } = result.current - proceedNextStep() + void proceedNextStep() expect(mockSetRecoveryMap).toHaveBeenCalledWith({ route: OPTION_SELECTION.ROUTE, step: OPTION_SELECTION.STEPS.SELECT, @@ -56,7 +57,7 @@ describe('useRouteUpdateActions', () => { const { proceedNextStep } = result.current - proceedNextStep() + void proceedNextStep() expect(mockToggleERWizard).toHaveBeenCalled() }) @@ -67,7 +68,7 @@ describe('useRouteUpdateActions', () => { ) const { goBackPrevStep } = result.current - goBackPrevStep() + void goBackPrevStep() expect(mockSetRecoveryMap).toHaveBeenCalledWith({ route: OPTION_SELECTION.ROUTE, step: OPTION_SELECTION.STEPS.SELECT, @@ -85,7 +86,7 @@ describe('useRouteUpdateActions', () => { const { goBackPrevStep } = result.current - goBackPrevStep() + void goBackPrevStep() expect(mockToggleERWizard).toHaveBeenCalled() }) @@ -96,56 +97,106 @@ describe('useRouteUpdateActions', () => { ) const { proceedToRouteAndStep } = result.current - proceedToRouteAndStep(RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE) + void proceedToRouteAndStep(RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE) expect(mockSetRecoveryMap).toHaveBeenCalledWith({ route: RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE, step: RECOVERY_MAP.ROBOT_IN_MOTION.STEPS.IN_MOTION, }) }) - it('routes to "robot in motion" when no other motion path is specified', () => { + it('routes to "robot in motion" when no other motion path is specified', async () => { const { result } = renderHook(() => useRouteUpdateActions(useRouteUpdateActionsParams) ) - const { setRobotInMotion } = result.current + const { handleMotionRouting } = result.current - setRobotInMotion(true) + await handleMotionRouting(true) expect(mockSetRecoveryMap).toHaveBeenCalledWith({ route: RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE, step: RECOVERY_MAP.ROBOT_IN_MOTION.STEPS.IN_MOTION, }) }) - it('routes to alternative motion routes if specified', () => { + it('rejects before routing to an "in motion" route if the door is open', async () => { + const params = { + ...useRouteUpdateActionsParams, + doorStatusUtils: { isDoorOpen: true, isProhibitedDoorOpen: false }, + } + + const { result } = renderHook(() => useRouteUpdateActions(params)) + const { handleMotionRouting } = result.current + + await expect(handleMotionRouting(true)).rejects.toThrow() + + expect(mockSetRecoveryMap).toHaveBeenCalledWith({ + route: RECOVERY_MAP.ROBOT_DOOR_OPEN.ROUTE, + step: RECOVERY_MAP.ROBOT_DOOR_OPEN.STEPS.DOOR_OPEN, + }) + }) + + it(`does not reject if the door is open but the step is ${RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.GRIPPER_RELEASE_LABWARE}`, async () => { + const params = { + ...useRouteUpdateActionsParams, + doorStatusUtils: { isDoorOpen: true, isProhibitedDoorOpen: false }, + recoveryMap: { + route: RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE, + step: + RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.GRIPPER_RELEASE_LABWARE, + }, + } + + const { result } = renderHook(() => useRouteUpdateActions(params)) + const { handleMotionRouting } = result.current + + await expect(handleMotionRouting(true)).resolves.not.toThrow() + }) + + it(`does not reject if the door is open but the step is ${RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.GRIPPER_RELEASE_LABWARE}`, async () => { + const params = { + ...useRouteUpdateActionsParams, + doorStatusUtils: { isDoorOpen: true, isProhibitedDoorOpen: false }, + recoveryMap: { + route: RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE, + step: RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.GRIPPER_RELEASE_LABWARE, + }, + } + + const { result } = renderHook(() => useRouteUpdateActions(params)) + const { handleMotionRouting } = result.current + + await expect(handleMotionRouting(true)).resolves.not.toThrow() + }) + + it('routes to alternative motion routes if specified', async () => { const { result } = renderHook(() => useRouteUpdateActions(useRouteUpdateActionsParams) ) - const { setRobotInMotion } = result.current + const { handleMotionRouting } = result.current - setRobotInMotion(true, RECOVERY_MAP.ROBOT_RESUMING.ROUTE) + await handleMotionRouting(true, RECOVERY_MAP.ROBOT_RESUMING.ROUTE) expect(mockSetRecoveryMap).toHaveBeenCalledWith({ route: RECOVERY_MAP.ROBOT_RESUMING.ROUTE, step: RECOVERY_MAP.ROBOT_RESUMING.STEPS.RESUMING, }) }) - it('routes to the route prior to motion after the motion completes', () => { + it('routes to the route prior to motion after the motion completes', async () => { const { result, rerender } = renderHook(() => useRouteUpdateActions(useRouteUpdateActionsParams) ) - const { setRobotInMotion } = result.current + const { handleMotionRouting } = result.current - setRobotInMotion(true) + await handleMotionRouting(true) expect(mockSetRecoveryMap).toHaveBeenCalledWith({ route: RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE, step: RECOVERY_MAP.ROBOT_IN_MOTION.STEPS.IN_MOTION, }) - setRobotInMotion(false) + void handleMotionRouting(false) rerender() expect(mockSetRecoveryMap).toHaveBeenCalledWith({ - route: RECOVERY_MAP.RETRY_FAILED_COMMAND.ROUTE, - step: RECOVERY_MAP.RETRY_FAILED_COMMAND.STEPS.CONFIRM_RETRY, + route: RECOVERY_MAP.RETRY_STEP.ROUTE, + step: RECOVERY_MAP.RETRY_STEP.STEPS.CONFIRM_RETRY, }) }) @@ -155,7 +206,7 @@ describe('useRouteUpdateActions', () => { ) const { proceedToRouteAndStep } = result.current - proceedToRouteAndStep(RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE) + void proceedToRouteAndStep(RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE) expect(mockSetRecoveryMap).toHaveBeenCalledWith({ route: RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE, step: RECOVERY_MAP.ROBOT_IN_MOTION.STEPS.IN_MOTION, @@ -168,13 +219,13 @@ describe('useRouteUpdateActions', () => { ) const { proceedToRouteAndStep } = result.current - proceedToRouteAndStep( - RECOVERY_MAP.RETRY_FAILED_COMMAND.ROUTE, - RECOVERY_MAP.RETRY_FAILED_COMMAND.STEPS.CONFIRM_RETRY + void proceedToRouteAndStep( + RECOVERY_MAP.RETRY_STEP.ROUTE, + RECOVERY_MAP.RETRY_STEP.STEPS.CONFIRM_RETRY ) expect(mockSetRecoveryMap).toHaveBeenCalledWith({ - route: RECOVERY_MAP.RETRY_FAILED_COMMAND.ROUTE, - step: RECOVERY_MAP.RETRY_FAILED_COMMAND.STEPS.CONFIRM_RETRY, + route: RECOVERY_MAP.RETRY_STEP.ROUTE, + step: RECOVERY_MAP.RETRY_STEP.STEPS.CONFIRM_RETRY, }) }) @@ -184,7 +235,10 @@ describe('useRouteUpdateActions', () => { ) const { proceedToRouteAndStep } = result.current - proceedToRouteAndStep(RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE, 'invalid-step') + void proceedToRouteAndStep( + RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE, + 'invalid-step' as any + ) expect(mockSetRecoveryMap).toHaveBeenCalledWith({ route: RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE, step: RECOVERY_MAP.ROBOT_IN_MOTION.STEPS.IN_MOTION, diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useShowDoorInfo.test.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useShowDoorInfo.test.ts index 226ca7de023..1ebb6e1d018 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useShowDoorInfo.test.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/__tests__/useShowDoorInfo.test.ts @@ -1,51 +1,166 @@ -import { describe, it, expect, beforeEach } from 'vitest' import { renderHook, act } from '@testing-library/react' -import { useShowDoorInfo } from '../useShowDoorInfo' +import { describe, it, expect, beforeEach } from 'vitest' + import { + RUN_STATUS_AWAITING_RECOVERY, RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, RUN_STATUS_AWAITING_RECOVERY_PAUSED, - RUN_STATUS_AWAITING_RECOVERY, } from '@opentrons/api-client' +import { useShowDoorInfo } from '../useShowDoorInfo' +import { + RECOVERY_MAP, + GRIPPER_MOVE_STEPS, +} from '/app/organisms/ErrorRecoveryFlows/constants' + +import type { IRecoveryMap, RouteStep } from '../../types' + describe('useShowDoorInfo', () => { let initialProps: Parameters[0] + let mockRecoveryMap: IRecoveryMap + let initialStep: RouteStep beforeEach(() => { initialProps = RUN_STATUS_AWAITING_RECOVERY + mockRecoveryMap = { + route: RECOVERY_MAP.OPTION_SELECTION.ROUTE, + step: RECOVERY_MAP.OPTION_SELECTION.STEPS.SELECT, + } as IRecoveryMap + initialStep = RECOVERY_MAP.OPTION_SELECTION.STEPS.SELECT }) - it('should return false initially', () => { - const { result } = renderHook(() => useShowDoorInfo(initialProps)) - expect(result.current).toBe(false) + it('should return false values initially', () => { + const { result } = renderHook(() => + useShowDoorInfo(initialProps, mockRecoveryMap, initialStep) + ) + expect(result.current).toEqual({ + isDoorOpen: false, + isProhibitedDoorOpen: false, + }) }) - it(`should return true when runStatus is ${RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR}`, () => { + it(`should return true values when runStatus is ${RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR}`, () => { const props = RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR - const { result } = renderHook(() => useShowDoorInfo(props)) - expect(result.current).toBe(true) + const { result } = renderHook(() => + useShowDoorInfo(props, mockRecoveryMap, initialStep) + ) + expect(result.current).toEqual({ + isDoorOpen: true, + isProhibitedDoorOpen: true, + }) }) - it(`should return true when runStatus is ${RUN_STATUS_AWAITING_RECOVERY_PAUSED}`, () => { + it(`should return true values when runStatus is ${RUN_STATUS_AWAITING_RECOVERY_PAUSED}`, () => { const props = RUN_STATUS_AWAITING_RECOVERY_PAUSED - const { result } = renderHook(() => useShowDoorInfo(props)) - expect(result.current).toBe(true) + const { result } = renderHook(() => + useShowDoorInfo(props, mockRecoveryMap, initialStep) + ) + expect(result.current).toEqual({ + isDoorOpen: true, + isProhibitedDoorOpen: true, + }) }) - it(`should keep returning true when runStatus changes from ${RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR} to ${RUN_STATUS_AWAITING_RECOVERY_PAUSED}`, () => { - const { result, rerender } = renderHook(props => useShowDoorInfo(props), { - initialProps, + it(`should keep returning true values when runStatus changes from ${RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR} to ${RUN_STATUS_AWAITING_RECOVERY_PAUSED}`, () => { + const { result, rerender } = renderHook( + ({ runStatus, recoveryMap, currentStep }) => + useShowDoorInfo(runStatus, recoveryMap, currentStep), + { + initialProps: { + runStatus: initialProps, + recoveryMap: mockRecoveryMap, + currentStep: initialStep, + }, + } + ) + + act(() => { + rerender({ + runStatus: RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, + recoveryMap: mockRecoveryMap, + currentStep: initialStep, + }) + }) + expect(result.current).toEqual({ + isDoorOpen: true, + isProhibitedDoorOpen: true, }) act(() => { - rerender(RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR) + rerender({ + runStatus: RUN_STATUS_AWAITING_RECOVERY_PAUSED, + recoveryMap: mockRecoveryMap, + currentStep: initialStep, + }) + }) + expect(result.current).toEqual({ + isDoorOpen: true, + isProhibitedDoorOpen: true, + }) + }) + + it('should return false values when runStatus changes to a non-door open status', () => { + const { result, rerender } = renderHook( + ({ runStatus, recoveryMap, currentStep }) => + useShowDoorInfo(runStatus, recoveryMap, currentStep), + { + initialProps: { + runStatus: RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, + recoveryMap: mockRecoveryMap, + currentStep: initialStep, + }, + } + ) + + expect(result.current).toEqual({ + isDoorOpen: true, + isProhibitedDoorOpen: true, }) - expect(result.current).toBe(true) act(() => { - rerender(RUN_STATUS_AWAITING_RECOVERY_PAUSED) + rerender({ + runStatus: RUN_STATUS_AWAITING_RECOVERY as any, + recoveryMap: mockRecoveryMap, + currentStep: initialStep, + }) + }) + expect(result.current).toEqual({ + isDoorOpen: false, + isProhibitedDoorOpen: false, + }) + }) + + it('should return false for prohibited door if the route is special-cased even if the door is open', () => { + const props = RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR + + const { result } = renderHook(() => + useShowDoorInfo( + props, + { + route: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE, + step: RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.MANUAL_FILL, + }, + RECOVERY_MAP.MANUAL_FILL_AND_SKIP.STEPS.MANUAL_FILL + ) + ) + + expect(result.current.isProhibitedDoorOpen).toEqual(false) + }) + + it('should return false for prohibited door if the current step is in GRIPPER_MOVE_STEPS', () => { + const props = RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR + + GRIPPER_MOVE_STEPS.forEach(step => { + const { result } = renderHook(() => + useShowDoorInfo(props, mockRecoveryMap, step) + ) + + expect(result.current).toEqual({ + isDoorOpen: true, + isProhibitedDoorOpen: false, + }) }) - expect(result.current).toBe(true) }) }) diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/index.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/index.ts index 923b84f2273..497fd3223da 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/index.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/index.ts @@ -1,19 +1,17 @@ export { useCurrentlyRecoveringFrom } from './useCurrentlyRecoveringFrom' -export { useErrorMessage } from './useErrorMessage' export { useErrorName } from './useErrorName' -export { useShowDoorInfo } from './useShowDoorInfo' export { useRecoveryCommands } from './useRecoveryCommands' export { useRouteUpdateActions } from './useRouteUpdateActions' export { useERUtils } from './useERUtils' -export { useRecoveryAnalytics } from './useRecoveryAnalytics' export { useRecoveryTakeover } from './useRecoveryTakeover' export { useRetainedFailedCommandBySource } from './useRetainedFailedCommandBySource' +export type { ERUtilsProps } from './useERUtils' export type { UseRouteUpdateActionsResult } from './useRouteUpdateActions' export type { UseRecoveryCommandsResult } from './useRecoveryCommands' export type { RecoveryTipStatusUtils } from './useRecoveryTipStatus' export type { ERUtilsResults } from './useERUtils' export type { UseFailedLabwareUtilsResult } from './useFailedLabwareUtils' -export type { UseRecoveryAnalyticsResult } from './useRecoveryAnalytics' export type { UseRecoveryTakeoverResult } from './useRecoveryTakeover' export type { FailedCommandBySource } from './useRetainedFailedCommandBySource' +export type { UseRecoveryRoutingResult } from './useRecoveryRouting' diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useCleanupRecoveryState.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useCleanupRecoveryState.ts new file mode 100644 index 00000000000..f6fd0eb15db --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useCleanupRecoveryState.ts @@ -0,0 +1,36 @@ +import { useState } from 'react' + +import { RECOVERY_MAP } from '../constants' + +import type { + ERUtilsProps, + UseRouteUpdateActionsResult, + UseRecoveryRoutingResult, +} from '../hooks' + +export interface UseCleanupProps { + isActiveUser: ERUtilsProps['isActiveUser'] + stashedMapRef: UseRouteUpdateActionsResult['stashedMapRef'] + setRM: UseRecoveryRoutingResult['setRM'] +} + +// When certain events (ex, someone terminates this app's recovery session) occur, reset state that needs to be reset. +export function useCleanupRecoveryState({ + isActiveUser, + stashedMapRef, + setRM, +}: UseCleanupProps): void { + const [wasActiveUser, setWasActiveUser] = useState(false) + + if (isActiveUser && !wasActiveUser) { + setWasActiveUser(true) + } else if (!isActiveUser && wasActiveUser) { + setWasActiveUser(false) + + stashedMapRef.current = null + setRM({ + route: RECOVERY_MAP.OPTION_SELECTION.ROUTE, + step: RECOVERY_MAP.OPTION_SELECTION.STEPS.SELECT, + }) + } +} diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useCurrentlyRecoveringFrom.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useCurrentlyRecoveringFrom.ts index 0ca5de470d3..38f9e39165a 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useCurrentlyRecoveringFrom.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useCurrentlyRecoveringFrom.ts @@ -1,11 +1,14 @@ +import { useEffect, useState } from 'react' +import { useQueryClient } from 'react-query' + import { RUN_STATUS_AWAITING_RECOVERY, RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, RUN_STATUS_AWAITING_RECOVERY_PAUSED, } from '@opentrons/api-client' -import { useCommandQuery } from '@opentrons/react-api-client' +import { useCommandQuery, useHost } from '@opentrons/react-api-client' -import { useNotifyAllCommandsQuery } from '../../../resources/runs' +import { useNotifyAllCommandsQuery } from '/app/resources/runs' import type { RunStatus } from '@opentrons/api-client' import type { FailedCommand } from '../types' @@ -20,18 +23,34 @@ const VALID_RECOVERY_FETCH_STATUSES = [ ] as Array // Return the `currentlyRecoveringFrom` command returned by the server, if any. -// Otherwise, returns null. +// The command will only be returned after the initial fetches are complete to prevent rendering of stale data. export function useCurrentlyRecoveringFrom( runId: string, runStatus: RunStatus | null ): FailedCommand | null { + const queryClient = useQueryClient() + const host = useHost() + const [isReadyToShow, setIsReadyToShow] = useState(false) + // There can only be a currentlyRecoveringFrom command when the run is in recovery mode. // In case we're falling back to polling, only enable queries when that is the case. const isRunInRecoveryMode = VALID_RECOVERY_FETCH_STATUSES.includes(runStatus) - const { data: allCommandsQueryData } = useNotifyAllCommandsQuery( + // Prevent stale data on subsequent recoveries by clearing the query cache at the start of each recovery. + useEffect(() => { + if (isRunInRecoveryMode) { + void queryClient.invalidateQueries([host, 'runs', runId]) + } else { + setIsReadyToShow(false) + } + }, [isRunInRecoveryMode, host, runId]) + + const { + data: allCommandsQueryData, + isFetching: isAllCommandsFetching, + } = useNotifyAllCommandsQuery( runId, - { cursor: null, pageLength: 0 }, // pageLength 0 because we only care about the links. + { pageLength: 0 }, // pageLength 0 because we only care about the links. { enabled: isRunInRecoveryMode, refetchInterval: ALL_COMMANDS_POLL_MS, @@ -42,7 +61,10 @@ export function useCurrentlyRecoveringFrom( // TODO(mm, 2024-05-21): When the server supports fetching the // currentlyRecoveringFrom command in one step, do that instead of this chained query. - const { data: commandQueryData } = useCommandQuery( + const { + data: commandQueryData, + isFetching: isCommandFetching, + } = useCommandQuery( currentlyRecoveringFromLink?.meta.runId ?? null, currentlyRecoveringFromLink?.meta.commandId ?? null, { @@ -50,5 +72,25 @@ export function useCurrentlyRecoveringFrom( } ) - return isRunInRecoveryMode ? commandQueryData?.data ?? null : null + // Only mark as ready to show when waterfall fetches are complete + useEffect(() => { + if ( + isRunInRecoveryMode && + !isAllCommandsFetching && + !isCommandFetching && + !isReadyToShow + ) { + setIsReadyToShow(true) + } + }, [ + isRunInRecoveryMode, + isAllCommandsFetching, + isCommandFetching, + isReadyToShow, + ]) + + const shouldShowCommand = + isRunInRecoveryMode && isReadyToShow && commandQueryData?.data + + return shouldShowCommand ? commandQueryData.data : null } diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useDeckMapUtils.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useDeckMapUtils.ts index 3c8a45faabb..458747f5b07 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useDeckMapUtils.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useDeckMapUtils.ts @@ -1,8 +1,9 @@ -import * as React from 'react' +import { useMemo } from 'react' import { + FLEX_ROBOT_TYPE, getDeckDefFromRobotType, - getLoadedLabwareDefinitionsByUri, + getFixedTrashLabwareDefinition, getModuleDef2, getPositionFromSlotId, getSimplestDeckConfigForProtocol, @@ -10,6 +11,12 @@ import { THERMOCYCLER_MODULE_V1, } from '@opentrons/shared-data' +import { + getRunLabwareRenderInfo, + getRunModuleRenderInfo, +} from '/app/organisms/InterventionModal/utils' +import { getLabwareLocation } from '/app/local-resources/labware' + import type { Run } from '@opentrons/api-client' import type { DeckDefinition, @@ -20,21 +27,34 @@ import type { CutoutConfigProtocolSpec, LoadedLabware, RobotType, + LabwareDefinitionsByUri, + LoadedModule, } from '@opentrons/shared-data' import type { ErrorRecoveryFlowsProps } from '..' import type { UseFailedLabwareUtilsResult } from './useFailedLabwareUtils' +import type { + RunLabwareInfo, + RunModuleInfo, +} from '/app/organisms/InterventionModal/utils' +import type { ERUtilsProps } from './useERUtils' interface UseDeckMapUtilsProps { runId: ErrorRecoveryFlowsProps['runId'] protocolAnalysis: ErrorRecoveryFlowsProps['protocolAnalysis'] failedLabwareUtils: UseFailedLabwareUtilsResult - runRecord?: Run + labwareDefinitionsByUri: ERUtilsProps['labwareDefinitionsByUri'] + runRecord: Run | undefined } export interface UseDeckMapUtilsResult { deckConfig: CutoutConfigProtocolSpec[] modulesOnDeck: RunCurrentModulesOnDeck[] labwareOnDeck: RunCurrentLabwareOnDeck[] + loadedLabware: LoadedLabware[] + loadedModules: LoadedModule[] + movedLabwareDef: LabwareDefinition2 | null + moduleRenderInfo: RunModuleInfo[] + labwareRenderInfo: RunLabwareInfo[] highlightLabwareEventuallyIn: string[] kind: 'intervention' robotType: RobotType @@ -45,47 +65,86 @@ export function useDeckMapUtils({ runRecord, runId, failedLabwareUtils, + labwareDefinitionsByUri, }: UseDeckMapUtilsProps): UseDeckMapUtilsResult { const robotType = protocolAnalysis?.robotType ?? OT2_ROBOT_TYPE const deckConfig = getSimplestDeckConfigForProtocol(protocolAnalysis) const deckDef = getDeckDefFromRobotType(robotType) - const currentModulesInfo = React.useMemo( + // TODO(jh, 11-05-24): Revisit this logic along with deckmap interfaces after deck map redesign. + + const currentModulesInfo = useMemo( () => getRunCurrentModulesInfo({ runRecord, deckDef, - protocolAnalysis, + labwareDefinitionsByUri, }), - [runRecord, deckDef, protocolAnalysis] + [runRecord, deckDef, labwareDefinitionsByUri] ) - const runCurrentModules = React.useMemo( + const runCurrentModules = useMemo( () => getRunCurrentModulesOnDeck({ failedLabwareUtils, + runRecord, currentModulesInfo, }), [runId, protocolAnalysis, runRecord, deckDef, failedLabwareUtils] ) - const currentLabwareInfo = React.useMemo( - () => getRunCurrentLabwareInfo({ runRecord, protocolAnalysis }), - [runRecord, protocolAnalysis] + const currentLabwareInfo = useMemo( + () => getRunCurrentLabwareInfo({ runRecord, labwareDefinitionsByUri }), + [runRecord, labwareDefinitionsByUri] ) - const runCurrentLabware = React.useMemo( + const { updatedModules, remainingLabware } = useMemo( + () => updateLabwareInModules({ runCurrentModules, currentLabwareInfo }), + [runCurrentModules, currentLabwareInfo] + ) + + const runCurrentLabware = useMemo( () => getRunCurrentLabwareOnDeck({ failedLabwareUtils, - currentLabwareInfo, + runRecord, + currentLabwareInfo: remainingLabware, }), - [runId, protocolAnalysis, runRecord, deckDef, failedLabwareUtils] + [failedLabwareUtils, currentLabwareInfo] + ) + + const movedLabwareDef = + labwareDefinitionsByUri != null && failedLabwareUtils.failedLabware != null + ? labwareDefinitionsByUri[failedLabwareUtils.failedLabware.definitionUri] + : null + + const moduleRenderInfo = useMemo( + () => + runRecord != null && labwareDefinitionsByUri != null + ? getRunModuleRenderInfo( + runRecord.data, + deckDef, + labwareDefinitionsByUri + ) + : [], + [deckDef, labwareDefinitionsByUri, runRecord] + ) + + const labwareRenderInfo = useMemo( + () => + runRecord != null && labwareDefinitionsByUri != null + ? getRunLabwareRenderInfo( + runRecord.data, + labwareDefinitionsByUri, + deckDef + ) + : [], + [deckDef, labwareDefinitionsByUri, runRecord] ) return { deckConfig, - modulesOnDeck: runCurrentModules.map( + modulesOnDeck: updatedModules.map( ({ moduleModel, moduleLocation, innerProps, nestedLabwareDef }) => ({ moduleModel, moduleLocation, @@ -97,11 +156,16 @@ export function useDeckMapUtils({ labwareLocation, definition, })), - highlightLabwareEventuallyIn: [...runCurrentModules, ...runCurrentLabware] + highlightLabwareEventuallyIn: [...updatedModules, ...runCurrentLabware] .map(el => el.highlight) .filter(maybeSlot => maybeSlot != null) as string[], kind: 'intervention', robotType, + loadedModules: runRecord?.data.modules ?? [], + loadedLabware: runRecord?.data.labware ?? [], + movedLabwareDef, + moduleRenderInfo, + labwareRenderInfo, } } @@ -123,9 +187,11 @@ interface RunCurrentModulesOnDeck { // Builds the necessary module object expected by BaseDeck. export function getRunCurrentModulesOnDeck({ failedLabwareUtils, + runRecord, currentModulesInfo, }: { failedLabwareUtils: UseDeckMapUtilsProps['failedLabwareUtils'] + runRecord: UseDeckMapUtilsProps['runRecord'] currentModulesInfo: RunCurrentModuleInfo[] }): Array { const { failedLabware } = failedLabwareUtils @@ -140,7 +206,11 @@ export function getRunCurrentModulesOnDeck({ : {}, nestedLabwareDef, - highlight: getIsLabwareMatch(nestedLabwareSlotName, failedLabware) + highlight: getIsLabwareMatch( + nestedLabwareSlotName, + runRecord, + failedLabware + ) ? nestedLabwareSlotName : null, }) @@ -152,11 +222,15 @@ interface RunCurrentLabwareOnDeck { definition: LabwareDefinition2 } // Builds the necessary labware object expected by BaseDeck. +// Note that while this highlights all labware in the failed labware slot, the result is later filtered to render +// only the topmost labware. export function getRunCurrentLabwareOnDeck({ currentLabwareInfo, + runRecord, failedLabwareUtils, }: { failedLabwareUtils: UseDeckMapUtilsProps['failedLabwareUtils'] + runRecord: UseDeckMapUtilsProps['runRecord'] currentLabwareInfo: RunCurrentLabwareInfo[] }): Array { const { failedLabware } = failedLabwareUtils @@ -165,7 +239,9 @@ export function getRunCurrentLabwareOnDeck({ ({ slotName, labwareDef, labwareLocation }) => ({ labwareLocation, definition: labwareDef, - highlight: getIsLabwareMatch(slotName, failedLabware) ? slotName : null, + highlight: getIsLabwareMatch(slotName, runRecord, failedLabware) + ? slotName + : null, }) ) } @@ -182,13 +258,13 @@ interface RunCurrentModuleInfo { export const getRunCurrentModulesInfo = ({ runRecord, deckDef, - protocolAnalysis, + labwareDefinitionsByUri, }: { - protocolAnalysis: UseDeckMapUtilsProps['protocolAnalysis'] runRecord: UseDeckMapUtilsProps['runRecord'] deckDef: DeckDefinition + labwareDefinitionsByUri?: LabwareDefinitionsByUri | null }): RunCurrentModuleInfo[] => { - if (runRecord == null || protocolAnalysis == null) { + if (runRecord == null || labwareDefinitionsByUri == null) { return [] } else { return runRecord.data.modules.reduce( @@ -203,10 +279,6 @@ export const getRunCurrentModulesInfo = ({ lw.location.moduleId === module.id ) - const labwareDefinitionsByUri = getLoadedLabwareDefinitionsByUri( - protocolAnalysis.commands - ) - const nestedLabwareDef = nestedLabware != null ? labwareDefinitionsByUri[nestedLabware.definitionUri] @@ -218,7 +290,11 @@ export const getRunCurrentModulesInfo = ({ ) const nestedLwLoc = nestedLabware?.location ?? null - const [nestedLwSlotName] = getSlotNameAndLwLocFrom(nestedLwLoc, false) + const [nestedLwSlotName] = getSlotNameAndLwLocFrom( + nestedLwLoc, + runRecord, + false + ) if (slotPosition == null) { return acc @@ -249,43 +325,97 @@ interface RunCurrentLabwareInfo { // Derive the labware info necessary to render labware on the deck. export function getRunCurrentLabwareInfo({ runRecord, - protocolAnalysis, + labwareDefinitionsByUri, }: { runRecord: UseDeckMapUtilsProps['runRecord'] - protocolAnalysis: UseDeckMapUtilsProps['protocolAnalysis'] + labwareDefinitionsByUri?: LabwareDefinitionsByUri | null }): RunCurrentLabwareInfo[] { - if (runRecord == null || protocolAnalysis == null) { + if (runRecord == null || labwareDefinitionsByUri == null) { return [] } else { - return runRecord.data.labware.reduce((acc: RunCurrentLabwareInfo[], lw) => { - const loc = lw.location - const [slotName, labwareLocation] = getSlotNameAndLwLocFrom(loc, true) // Exclude modules since handled separately. - const labwareDefinitionsByUri = getLoadedLabwareDefinitionsByUri( - protocolAnalysis.commands - ) - const labwareDef = labwareDefinitionsByUri[lw.definitionUri] - - if (slotName == null || labwareLocation == null) { - return acc - } else { - return [ - ...acc, - { - labwareDef, - slotName, - labwareLocation: labwareLocation, - }, - ] + const allLabware = runRecord.data.labware.reduce( + (acc: RunCurrentLabwareInfo[], lw) => { + const loc = lw.location + const [slotName, labwareLocation] = getSlotNameAndLwLocFrom( + loc, + runRecord, + true + ) // Exclude modules since handled separately. + const labwareDef = getLabwareDefinition(lw, labwareDefinitionsByUri) + + if (slotName == null || labwareLocation == null) { + return acc + } else { + return [ + ...acc, + { + labwareDef, + slotName, + labwareLocation: labwareLocation, + }, + ] + } + }, + [] + ) + + // Group labware by slotName + const labwareBySlot = allLabware.reduce< + Record + >((acc, labware) => { + const slot = labware.slotName + if (!acc[slot]) { + acc[slot] = [] } - }, []) + acc[slot].push(labware) + return acc + }, {}) + + // For each slot, return either: + // 1. The first labware with 'labwareId' in its location if it exists + // 2. The first labware in the slot if no labware has 'labwareId' + return Object.values(labwareBySlot).map(slotLabware => { + const labwareWithId = slotLabware.find( + lw => + typeof lw.labwareLocation !== 'string' && + 'labwareId' in lw.labwareLocation + ) + return labwareWithId != null + ? { + ...labwareWithId, + labwareLocation: { slotName: labwareWithId.slotName }, + } + : slotLabware[0] + }) + } +} + +const getLabwareDefinition = ( + labware: LoadedLabware, + protocolLabwareDefinitionsByUri: LabwareDefinitionsByUri +): LabwareDefinition2 => { + if (labware.id === 'fixedTrash') { + return getFixedTrashLabwareDefinition() + } else { + return protocolLabwareDefinitionsByUri[labware.definitionUri] } } // Get the slotName for on deck labware. export function getSlotNameAndLwLocFrom( location: LabwareLocation | null, + runRecord: UseDeckMapUtilsProps['runRecord'], excludeModules: boolean ): [string | null, LabwareLocation | null] { + const baseSlot = + getLabwareLocation({ + location, + detailLevel: 'slot-only', + loadedLabwares: runRecord?.data?.labware ?? [], + loadedModules: runRecord?.data?.modules ?? [], + robotType: FLEX_ROBOT_TYPE, + })?.slotName ?? null + if (location == null || location === 'offDeck') { return [null, null] } else if ('moduleId' in location) { @@ -293,17 +423,17 @@ export function getSlotNameAndLwLocFrom( return [null, null] } else { const moduleId = location.moduleId - return [moduleId, { moduleId }] + return [baseSlot, { moduleId }] } } else if ('labwareId' in location) { const labwareId = location.labwareId - return [labwareId, { labwareId }] + return [baseSlot, { labwareId }] } else if ('addressableAreaName' in location) { const addressableAreaName = location.addressableAreaName - return [addressableAreaName, { addressableAreaName }] + return [baseSlot, { addressableAreaName }] } else if ('slotName' in location) { const slotName = location.slotName - return [slotName, { slotName }] + return [baseSlot, { slotName }] } else { return [null, null] } @@ -312,9 +442,19 @@ export function getSlotNameAndLwLocFrom( // Whether the slotName labware is the same as the pickUpTipLabware. export function getIsLabwareMatch( slotName: string, + runRecord: UseDeckMapUtilsProps['runRecord'], pickUpTipLabware: LoadedLabware | null ): boolean { - const location = pickUpTipLabware?.location + const location = pickUpTipLabware?.location ?? null + + const slotLocation = + getLabwareLocation({ + location, + detailLevel: 'slot-only', + loadedLabwares: runRecord?.data?.labware ?? [], + loadedModules: runRecord?.data?.modules ?? [], + robotType: FLEX_ROBOT_TYPE, + })?.slotName ?? null if (location == null) { return false @@ -322,13 +462,44 @@ export function getIsLabwareMatch( // This is the "off deck" case, which we do not render (and therefore return false). else if (typeof location === 'string') { return false - } else if ('moduleId' in location) { - return location.moduleId === slotName - } else if ('slotName' in location) { - return location.slotName === slotName - } else if ('labwareId' in location) { - return location.labwareId === slotName - } else if ('addressableAreaName' in location) { - return location.addressableAreaName === slotName - } else return false + } else { + return slotLocation === slotName + } +} + +// If any labware share a slot with a module, the labware should be nested within the module for rendering purposes. +// This prevents issues such as TC nested labware rendering in "B1" instead of the special-cased location. +export function updateLabwareInModules({ + runCurrentModules, + currentLabwareInfo, +}: { + runCurrentModules: ReturnType + currentLabwareInfo: ReturnType +}): { + updatedModules: ReturnType + remainingLabware: ReturnType +} { + const usedSlots = new Set() + + const updatedModules = runCurrentModules.map(moduleInfo => { + const labwareInSameLoc = currentLabwareInfo.find( + lw => moduleInfo.moduleLocation.slotName === lw.slotName + ) + + if (labwareInSameLoc != null) { + usedSlots.add(labwareInSameLoc.slotName) + return { + ...moduleInfo, + nestedLabwareDef: labwareInSameLoc.labwareDef, + } + } else { + return moduleInfo + } + }) + + const remainingLabware = currentLabwareInfo.filter( + lw => !usedSlots.has(lw.slotName) + ) + + return { updatedModules, remainingLabware } } diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts index af84fc2c348..72e9bb481bc 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useERUtils.ts @@ -1,3 +1,5 @@ +import { useMemo } from 'react' + import { useInstrumentsQuery } from '@opentrons/react-api-client' import { useRouteUpdateActions } from './useRouteUpdateActions' @@ -5,21 +7,27 @@ import { useRecoveryCommands } from './useRecoveryCommands' import { useRecoveryTipStatus } from './useRecoveryTipStatus' import { useRecoveryRouting } from './useRecoveryRouting' import { useFailedLabwareUtils } from './useFailedLabwareUtils' -import { getFailedCommandPipetteInfo, getNextSteps } from '../utils' +import { getNextSteps } from '../utils' import { useDeckMapUtils } from './useDeckMapUtils' import { useNotifyAllCommandsQuery, useNotifyRunQuery, -} from '../../../resources/runs' +} from '/app/resources/runs' import { useRecoveryOptionCopy } from './useRecoveryOptionCopy' import { useRecoveryActionMutation } from './useRecoveryActionMutation' -import { useRunningStepCounts } from '../../../resources/protocols/hooks' import { useRecoveryToasts } from './useRecoveryToasts' -import { useRecoveryAnalytics } from './useRecoveryAnalytics' +import { useRecoveryAnalytics } from '/app/redux-resources/analytics' +import { useShowDoorInfo } from './useShowDoorInfo' +import { useCleanupRecoveryState } from './useCleanupRecoveryState' +import { useFailedPipetteUtils } from './useFailedPipetteUtils' +import { getRunningStepCountsFrom } from '/app/resources/protocols' -import type { PipetteData } from '@opentrons/api-client' -import type { RobotType } from '@opentrons/shared-data' -import type { IRecoveryMap } from '../types' +import type { + LabwareDefinition2, + LabwareDefinitionsByUri, + RobotType, +} from '@opentrons/shared-data' +import type { IRecoveryMap, RouteStep, RecoveryRoute } from '../types' import type { ErrorRecoveryFlowsProps } from '..' import type { UseRouteUpdateActionsResult } from './useRouteUpdateActions' import type { UseRecoveryCommandsResult } from './useRecoveryCommands' @@ -31,10 +39,12 @@ import type { SubMapUtils, } from './useRecoveryRouting' import type { RecoveryActionMutationResult } from './useRecoveryActionMutation' -import type { StepCounts } from '../../../resources/protocols/hooks' -import type { UseRecoveryAnalyticsResult } from './useRecoveryAnalytics' +import type { StepCounts } from '/app/resources/protocols/hooks' +import type { UseRecoveryAnalyticsResult } from '/app/redux-resources/analytics' import type { UseRecoveryTakeoverResult } from './useRecoveryTakeover' import type { useRetainedFailedCommandBySource } from './useRetainedFailedCommandBySource' +import type { UseShowDoorInfoResult } from './useShowDoorInfo' +import type { UseFailedPipetteUtilsResult } from './useFailedPipetteUtils' export type ERUtilsProps = Omit & { toggleERWizAsActiveUser: UseRecoveryTakeoverResult['toggleERWizAsActiveUser'] @@ -42,24 +52,28 @@ export type ERUtilsProps = Omit & { isOnDevice: boolean robotType: RobotType failedCommand: ReturnType + isActiveUser: UseRecoveryTakeoverResult['isActiveUser'] + allRunDefs: LabwareDefinition2[] + labwareDefinitionsByUri: LabwareDefinitionsByUri | null } export interface ERUtilsResults { recoveryMap: IRecoveryMap currentRecoveryOptionUtils: CurrentRecoveryOptionUtils - routeUpdateActions: UseRouteUpdateActionsResult + routeUpdateActions: Omit recoveryCommands: UseRecoveryCommandsResult tipStatusUtils: RecoveryTipStatusUtils failedLabwareUtils: UseFailedLabwareUtilsResult + failedPipetteUtils: UseFailedPipetteUtilsResult deckMapUtils: UseDeckMapUtilsResult getRecoveryOptionCopy: ReturnType recoveryActionMutationUtils: RecoveryActionMutationResult - failedPipetteInfo: PipetteData | null hasLaunchedRecovery: boolean stepCounts: StepCounts commandsAfterFailedCommand: ReturnType subMapUtils: SubMapUtils - analytics: UseRecoveryAnalyticsResult + analytics: UseRecoveryAnalyticsResult + doorStatusUtils: UseShowDoorInfoResult } const SUBSEQUENT_COMMAND_DEPTH = 2 @@ -72,6 +86,11 @@ export function useERUtils({ protocolAnalysis, isOnDevice, robotType, + runStatus, + isActiveUser, + allRunDefs, + unvalidatedFailedCommand, + labwareDefinitionsByUri, }: ERUtilsProps): ERUtilsResults { const { data: attachedInstruments } = useInstrumentsQuery() const { data: runRecord } = useNotifyRunQuery(runId) @@ -84,9 +103,15 @@ export function useERUtils({ cursor: 0, pageLength: 999, }) - const failedCommandByRunRecord = failedCommand?.byRunRecord ?? null - const stepCounts = useRunningStepCounts(runId, runCommands) + const stepCounts = useMemo( + () => + getRunningStepCountsFrom( + protocolAnalysis?.commands ?? [], + failedCommand?.byRunRecord ?? null + ), + [protocolAnalysis != null, failedCommand] + ) const analytics = useRecoveryAnalytics() @@ -97,23 +122,33 @@ export function useERUtils({ ...subMapUtils } = useRecoveryRouting() + const doorStatusUtils = useShowDoorInfo( + runStatus, + recoveryMap, + recoveryMap.step + ) + const recoveryToastUtils = useRecoveryToasts({ - currentStepCount: stepCounts.currentStepNumber, + stepCounts, selectedRecoveryOption: currentRecoveryOptionUtils.selectedRecoveryOption, isOnDevice, commandTextData: protocolAnalysis, robotType, + allRunDefs, }) - const failedPipetteInfo = getFailedCommandPipetteInfo({ - failedCommandByRunRecord, + const failedPipetteUtils = useFailedPipetteUtils({ + runId, + failedCommandByRunRecord: failedCommand?.byRunRecord ?? null, runRecord, attachedInstruments, }) + const { failedPipetteInfo } = failedPipetteUtils const tipStatusUtils = useRecoveryTipStatus({ runId, runRecord, + failedCommand, attachedInstruments, failedPipetteInfo, }) @@ -123,10 +158,11 @@ export function useERUtils({ recoveryMap, toggleERWizAsActiveUser, setRecoveryMap: setRM, + doorStatusUtils, }) const failedLabwareUtils = useFailedLabwareUtils({ - failedCommandByRunRecord, + failedCommand, protocolAnalysis, failedPipetteInfo, runRecord, @@ -135,7 +171,8 @@ export function useERUtils({ const recoveryCommands = useRecoveryCommands({ runId, - failedCommandByRunRecord, + failedCommand, + unvalidatedFailedCommand, failedLabwareUtils, routeUpdateActions, recoveryToastUtils, @@ -148,9 +185,13 @@ export function useERUtils({ runRecord, protocolAnalysis, failedLabwareUtils, + labwareDefinitionsByUri, }) - const recoveryActionMutationUtils = useRecoveryActionMutation(runId) + const recoveryActionMutationUtils = useRecoveryActionMutation( + runId, + routeUpdateActions + ) // TODO(jh, 06-14-24): Ensure other string build utilities that are internal to ErrorRecoveryFlows are exported under // one utility object in useERUtils. @@ -160,6 +201,13 @@ export function useERUtils({ protocolAnalysis, SUBSEQUENT_COMMAND_DEPTH ) + + useCleanupRecoveryState({ + isActiveUser, + setRM, + stashedMapRef: routeUpdateActions.stashedMapRef, + }) + return { recoveryMap, subMapUtils, @@ -170,11 +218,12 @@ export function useERUtils({ hasLaunchedRecovery, tipStatusUtils, failedLabwareUtils, - failedPipetteInfo, + failedPipetteUtils, deckMapUtils, getRecoveryOptionCopy, stepCounts, commandsAfterFailedCommand, analytics, + doorStatusUtils, } } diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useErrorMessage.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useErrorMessage.ts deleted file mode 100644 index d8efb33d4eb..00000000000 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useErrorMessage.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useTranslation } from 'react-i18next' - -import type { ErrorKind } from '../types' - -// The generalized error message shown to the user in select locations. -export function useErrorMessage(errorKind: ErrorKind): string { - const { t } = useTranslation('error_recovery') - - switch (errorKind) { - default: - return t('general_error_message') - } -} diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useErrorName.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useErrorName.ts index bde35d89fbf..0279b8b675a 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useErrorName.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useErrorName.ts @@ -17,8 +17,15 @@ export function useErrorName(errorKind: ErrorKind): string { return t('pipette_overpressure') case ERROR_KINDS.OVERPRESSURE_WHILE_DISPENSING: return t('pipette_overpressure') - // The only "general error" case currently is tipPhysicallyMissing. - default: + case ERROR_KINDS.TIP_NOT_DETECTED: return t('tip_not_detected') + case ERROR_KINDS.TIP_DROP_FAILED: + return t('tip_drop_failed') + case ERROR_KINDS.GRIPPER_ERROR: + return t('gripper_error') + case ERROR_KINDS.STALL_OR_COLLISION: + return t('stall_or_collision_error') + default: + return t('error') } } diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useFailedLabwareUtils.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useFailedLabwareUtils.ts index bee6eb0474c..f1a57aa965f 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useFailedLabwareUtils.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useFailedLabwareUtils.ts @@ -1,7 +1,9 @@ -import * as React from 'react' +import { useEffect, useMemo, useState } from 'react' import without from 'lodash/without' +import { useTranslation } from 'react-i18next' import { + FLEX_ROBOT_TYPE, getAllLabwareDefs, getLabwareDisplayName, getLoadedLabwareDefinitionsByUri, @@ -9,7 +11,10 @@ import { import { ERROR_KINDS } from '../constants' import { getErrorKind } from '../utils' -import { getLoadedLabware } from '../../../molecules/Command/utils/accessors' +import { + getLoadedLabware, + getLabwareDisplayLocation, +} from '/app/local-resources/labware' import type { WellGroup } from '@opentrons/components' import type { CommandsData, PipetteData, Run } from '@opentrons/api-client' @@ -20,18 +25,28 @@ import type { AspirateRunTimeCommand, DispenseRunTimeCommand, LiquidProbeRunTimeCommand, + MoveLabwareRunTimeCommand, + LabwareLocation, } from '@opentrons/shared-data' +import type { DisplayLocationSlotOnlyParams } from '/app/local-resources/labware' import type { ErrorRecoveryFlowsProps } from '..' -import type { ERUtilsProps } from './useERUtils' +import type { FailedCommandBySource } from './useRetainedFailedCommandBySource' interface UseFailedLabwareUtilsProps { - failedCommandByRunRecord: ERUtilsProps['failedCommandByRunRecord'] + failedCommand: FailedCommandBySource | null protocolAnalysis: ErrorRecoveryFlowsProps['protocolAnalysis'] failedPipetteInfo: PipetteData | null runCommands?: CommandsData runRecord?: Run } +interface RelevantFailedLabwareLocations { + displayNameCurrentLoc: string + displayNameNewLoc: string | null + currentLoc: LabwareLocation | null + newLoc: LabwareLocation | null +} + export type UseFailedLabwareUtilsResult = UseTipSelectionUtilsResult & { /* The name of the labware relevant to the failed command, if any. */ failedLabwareName: string | null @@ -41,6 +56,8 @@ export type UseFailedLabwareUtilsResult = UseTipSelectionUtilsResult & { relevantWellName: string | null /* The user-content nickname of the failed labware, if any */ failedLabwareNickname: string | null + /* Details relating to the labware location. */ + failedLabwareLocations: RelevantFailedLabwareLocations } /** Utils for labware relating to the failedCommand. @@ -50,16 +67,17 @@ export type UseFailedLabwareUtilsResult = UseTipSelectionUtilsResult & { * For no liquid detected errors, the relevant labware is the well in which no liquid was detected. */ export function useFailedLabwareUtils({ - failedCommandByRunRecord, + failedCommand, protocolAnalysis, failedPipetteInfo, runCommands, runRecord, }: UseFailedLabwareUtilsProps): UseFailedLabwareUtilsResult { - const recentRelevantFailedLabwareCmd = React.useMemo( + const failedCommandByRunRecord = failedCommand?.byRunRecord ?? null + const recentRelevantFailedLabwareCmd = useMemo( () => getRelevantFailedLabwareCmdFrom({ - failedCommandByRunRecord, + failedCommand, runCommands, }), [failedCommandByRunRecord?.key, runCommands?.meta.totalLength] @@ -67,7 +85,7 @@ export function useFailedLabwareUtils({ const tipSelectionUtils = useTipSelectionUtils(recentRelevantFailedLabwareCmd) - const failedLabwareDetails = React.useMemo( + const failedLabwareDetails = useMemo( () => getFailedCmdRelevantLabware( protocolAnalysis, @@ -77,7 +95,7 @@ export function useFailedLabwareUtils({ [protocolAnalysis?.id, recentRelevantFailedLabwareCmd?.key] ) - const failedLabware = React.useMemo( + const failedLabware = useMemo( () => getFailedLabware(recentRelevantFailedLabwareCmd, runRecord), [recentRelevantFailedLabwareCmd?.key] ) @@ -87,12 +105,19 @@ export function useFailedLabwareUtils({ recentRelevantFailedLabwareCmd ) + const failedLabwareLocations = useRelevantFailedLwLocations({ + failedLabware, + failedCommandByRunRecord, + runRecord, + }) + return { ...tipSelectionUtils, failedLabwareName: failedLabwareDetails?.name ?? null, failedLabware, relevantWellName, failedLabwareNickname: failedLabwareDetails?.nickname ?? null, + failedLabwareLocations, } } @@ -101,19 +126,21 @@ type FailedCommandRelevantLabware = | Omit | Omit | Omit + | Omit | null interface RelevantFailedLabwareCmd { - failedCommandByRunRecord: ErrorRecoveryFlowsProps['failedCommandByRunRecord'] + failedCommand: FailedCommandBySource | null runCommands?: CommandsData } // Return the actual command that contains the info relating to the relevant labware. export function getRelevantFailedLabwareCmdFrom({ - failedCommandByRunRecord, + failedCommand, runCommands, }: RelevantFailedLabwareCmd): FailedCommandRelevantLabware { - const errorKind = getErrorKind(failedCommandByRunRecord) + const failedCommandByRunRecord = failedCommand?.byRunRecord ?? null + const errorKind = getErrorKind(failedCommand) switch (errorKind) { case ERROR_KINDS.NO_LIQUID_DETECTED: @@ -121,12 +148,15 @@ export function getRelevantFailedLabwareCmdFrom({ case ERROR_KINDS.OVERPRESSURE_PREPARE_TO_ASPIRATE: case ERROR_KINDS.OVERPRESSURE_WHILE_ASPIRATING: case ERROR_KINDS.OVERPRESSURE_WHILE_DISPENSING: + case ERROR_KINDS.STALL_OR_COLLISION: return getRelevantPickUpTipCommand(failedCommandByRunRecord, runCommands) + case ERROR_KINDS.GRIPPER_ERROR: + return failedCommandByRunRecord as MoveLabwareRunTimeCommand case ERROR_KINDS.GENERAL_ERROR: return null default: - console.warn( - 'No labware associated with failed command. Handle case explicitly.' + console.error( + `useFailedLabwareUtils: No labware associated with error kind ${errorKind}. Handle case explicitly.` ) return null } @@ -134,7 +164,7 @@ export function getRelevantFailedLabwareCmdFrom({ // Returns the most recent pickUpTip command for the pipette used in the failed command, if any. function getRelevantPickUpTipCommand( - failedCommandByRunRecord: ErrorRecoveryFlowsProps['failedCommandByRunRecord'], + failedCommandByRunRecord: FailedCommandBySource['byRunRecord'] | null, runCommands?: CommandsData ): Omit | null { if ( @@ -174,25 +204,35 @@ interface UseTipSelectionUtilsResult { tipSelectorDef: LabwareDefinition2 selectTips: (tipGroup: WellGroup) => void deselectTips: (locations: string[]) => void + areTipsSelected: boolean } -// TODO(jh, 06-18-24): Enforce failure/warning when accessing tipSelectionUtils -// if used when the relevant labware -// is NOT relevant to tip pick up. - // Utils for initializing and interacting with the Tip Selector component. +// Note: if the relevant failed labware command is not associated with tips, these utils effectively return `null`. function useTipSelectionUtils( recentRelevantFailedLabwareCmd: FailedCommandRelevantLabware ): UseTipSelectionUtilsResult { - const [selectedLocs, setSelectedLocs] = React.useState(null) + const [selectedLocs, setSelectedLocs] = useState(null) - const initialLocs = useInitialSelectedLocationsFrom( - recentRelevantFailedLabwareCmd - ) - // Set the initial locs when they first become available. - if (selectedLocs == null && initialLocs != null) { - setSelectedLocs(initialLocs) - } + // Note that while other commands may have a wellName associated with them, + // we are only interested in wells for the purposes of tip picking up. + // Support state updates if the underlying well data changes, since this data is lazily retrieved and may change shortly + // after Error Recovery launches. + const initialWellName = + recentRelevantFailedLabwareCmd != null && + recentRelevantFailedLabwareCmd.commandType === 'pickUpTip' + ? recentRelevantFailedLabwareCmd.params.wellName + : null + useEffect(() => { + if ( + recentRelevantFailedLabwareCmd != null && + recentRelevantFailedLabwareCmd.commandType === 'pickUpTip' + ) { + setSelectedLocs({ + [recentRelevantFailedLabwareCmd.params.wellName]: null, + }) + } + }, [initialWellName]) const deselectTips = (locations: string[]): void => { setSelectedLocs(prevLocs => @@ -210,38 +250,23 @@ function useTipSelectionUtils( } // Use this labware to represent all tip racks for manual tip selection. - const tipSelectorDef = React.useMemo( + const tipSelectorDef = useMemo( () => getAllLabwareDefs().thermoscientificnunc96Wellplate1300UlV1, [] ) + const areTipsSelected = + selectedLocs != null && Object.keys(selectedLocs).length > 0 + return { selectedTipLocations: selectedLocs, tipSelectorDef, selectTips, deselectTips, + areTipsSelected, } } -// Set the initial well selection to be the last pickup tip location for the pipette used in the failed command. -function useInitialSelectedLocationsFrom( - recentRelevantFailedLabwareCmd: FailedCommandRelevantLabware -): WellGroup | null { - const [initialWells, setInitialWells] = React.useState(null) - - // Note that while other commands may have a wellName associated with them, - // we are only interested in wells for the purposes of tip picking up. - if ( - recentRelevantFailedLabwareCmd != null && - recentRelevantFailedLabwareCmd.commandType === 'pickUpTip' && - initialWells == null - ) { - setInitialWells({ [recentRelevantFailedLabwareCmd.params.wellName]: null }) - } - - return initialWells -} - // Get the name of the relevant labware relevant to the failed command, if any. export function getFailedCmdRelevantLabware( protocolAnalysis: ErrorRecoveryFlowsProps['protocolAnalysis'], @@ -254,7 +279,7 @@ export function getFailedCmdRelevantLabware( const labwareNickname = protocolAnalysis != null ? getLoadedLabware( - protocolAnalysis, + protocolAnalysis.labware, recentRelevantFailedLabwareCmd?.params.labwareId || '' )?.displayName ?? null : null @@ -288,7 +313,11 @@ export function getRelevantWellName( failedPipetteInfo: UseFailedLabwareUtilsProps['failedPipetteInfo'], recentRelevantPickUpTipCmd: FailedCommandRelevantLabware ): string { - if (failedPipetteInfo == null || recentRelevantPickUpTipCmd == null) { + if ( + failedPipetteInfo == null || + recentRelevantPickUpTipCmd == null || + recentRelevantPickUpTipCmd.commandType === 'moveLabware' + ) { return '' } @@ -304,3 +333,60 @@ export function getRelevantWellName( return wellName } } + +export type GetRelevantLwLocationsParams = Pick< + UseFailedLabwareUtilsProps, + 'runRecord' +> & { + failedLabware: UseFailedLabwareUtilsResult['failedLabware'] + failedCommandByRunRecord: FailedCommandBySource['byRunRecord'] | null +} + +export function useRelevantFailedLwLocations({ + failedLabware, + failedCommandByRunRecord, + runRecord, +}: GetRelevantLwLocationsParams): RelevantFailedLabwareLocations { + const { t } = useTranslation('protocol_command_text') + + const BASE_DISPLAY_PARAMS: Omit = { + loadedLabwares: runRecord?.data?.labware ?? [], + loadedModules: runRecord?.data?.modules ?? [], + robotType: FLEX_ROBOT_TYPE, + t, + detailLevel: 'slot-only', + isOnDevice: false, // Always return the "slot XYZ" copy, which is the desktop copy. + } + + const displayNameCurrentLoc = getLabwareDisplayLocation({ + ...BASE_DISPLAY_PARAMS, + location: failedLabware?.location ?? null, + }) + + const getNewLocation = (): Pick< + RelevantFailedLabwareLocations, + 'displayNameNewLoc' | 'newLoc' + > => { + switch (failedCommandByRunRecord?.commandType) { + case 'moveLabware': + return { + displayNameNewLoc: getLabwareDisplayLocation({ + ...BASE_DISPLAY_PARAMS, + location: failedCommandByRunRecord.params.newLocation, + }), + newLoc: failedCommandByRunRecord.params.newLocation, + } + default: + return { + displayNameNewLoc: null, + newLoc: null, + } + } + } + + return { + displayNameCurrentLoc, + currentLoc: failedLabware?.location ?? null, + ...getNewLocation(), + } +} diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useFailedPipetteUtils.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useFailedPipetteUtils.ts new file mode 100644 index 00000000000..5535cd46799 --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useFailedPipetteUtils.ts @@ -0,0 +1,91 @@ +import { useRunCurrentState } from '@opentrons/react-api-client' + +import { isPartialTipConfig } from '/app/local-resources/instruments' + +import type { + NozzleLayoutValues, + Instruments, + Run, + PipetteData, +} from '@opentrons/api-client' +import type { FailedCommandBySource } from './useRetainedFailedCommandBySource' + +export interface UseFailedPipetteUtilsParams + extends UseFailedCommandPipetteInfoProps { + runId: string +} + +export interface UseFailedPipetteUtilsResult { + relevantActiveNozzleLayout: NozzleLayoutValues | null + isPartialTipConfigValid: boolean + failedPipetteInfo: ReturnType +} + +export function useFailedPipetteUtils( + props: UseFailedPipetteUtilsParams +): UseFailedPipetteUtilsResult { + const { failedCommandByRunRecord, runId } = props + + const failedPipetteId = + failedCommandByRunRecord != null + ? 'pipetteId' in failedCommandByRunRecord.params + ? failedCommandByRunRecord.params.pipetteId + : null + : null + + const { data: runCurrentState } = useRunCurrentState(runId, { + enabled: failedPipetteId != null, + }) + + const relevantActiveNozzleLayout = + runCurrentState?.data.activeNozzleLayouts[failedPipetteId] ?? null + + const failedPipetteInfo = getFailedCommandPipetteInfo(props) + + const isPartialTipConfigValid = + failedPipetteInfo != null && relevantActiveNozzleLayout != null + ? isPartialTipConfig({ + channel: failedPipetteInfo.data.channels, + activeNozzleCount: + relevantActiveNozzleLayout.activeNozzles.length ?? 0, + }) + : false + + return { + relevantActiveNozzleLayout, + isPartialTipConfigValid, + failedPipetteInfo, + } +} + +interface UseFailedCommandPipetteInfoProps { + runRecord: Run | undefined + attachedInstruments: Instruments | undefined + failedCommandByRunRecord: FailedCommandBySource['byRunRecord'] | null +} + +// /instruments data for the pipette used in the failedCommand, if any. +export function getFailedCommandPipetteInfo({ + failedCommandByRunRecord, + runRecord, + attachedInstruments, +}: UseFailedCommandPipetteInfoProps): PipetteData | null { + if ( + failedCommandByRunRecord == null || + !('pipetteId' in failedCommandByRunRecord.params) + ) { + return null + } else { + const failedPipetteId = failedCommandByRunRecord.params.pipetteId + const runRecordPipette = runRecord?.data.pipettes.find( + pipette => pipette.id === failedPipetteId + ) + + const failedInstrumentInfo = attachedInstruments?.data.find( + instrument => + 'mount' in instrument && instrument.mount === runRecordPipette?.mount + ) as PipetteData + + return failedInstrumentInfo ?? null + } +} diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryActionMutation.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryActionMutation.ts index 5f33df7941d..0d4bdd83b42 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryActionMutation.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryActionMutation.ts @@ -1,20 +1,35 @@ +import { usePlayRunMutation } from '@opentrons/react-api-client' + +import { RECOVERY_MAP } from '../constants' + +import type { RunAction } from '@opentrons/api-client' +import type { ERUtilsResults } from './useERUtils' import type { ErrorRecoveryFlowsProps } from '..' -import { useRunActionMutations } from '@opentrons/react-api-client' export interface RecoveryActionMutationResult { - resumeRecovery: ReturnType['playRun'] - isResumeRecoveryLoading: ReturnType< - typeof useRunActionMutations - >['isPlayRunActionLoading'] + resumeRecovery: () => Promise + isResumeRecoveryLoading: ReturnType['isLoading'] } export function useRecoveryActionMutation( - runId: ErrorRecoveryFlowsProps['runId'] + runId: ErrorRecoveryFlowsProps['runId'], + routeUpdateActions: ERUtilsResults['routeUpdateActions'] ): RecoveryActionMutationResult { const { - playRun: resumeRecovery, - isPlayRunActionLoading: isResumeRecoveryLoading, - } = useRunActionMutations(runId) + mutateAsync, + isLoading: isResumeRecoveryLoading, + } = usePlayRunMutation() + const { proceedToRouteAndStep } = routeUpdateActions + + const resumeRecovery = (): Promise => { + return mutateAsync(runId).catch(e => { + return proceedToRouteAndStep( + RECOVERY_MAP.ERROR_WHILE_RECOVERING.ROUTE + ).then(() => { + throw new Error(`Could not resume recovery: ${e}`) + }) + }) + } return { resumeRecovery, isResumeRecoveryLoading } } diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryCommands.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryCommands.ts index ff636d2cbd3..01f5c4a7c94 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryCommands.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryCommands.ts @@ -1,14 +1,18 @@ -import * as React from 'react' +import { useCallback, useState } from 'react' import head from 'lodash/head' import { useResumeRunFromRecoveryMutation, useStopRunMutation, - useUpdateErrorRecoveryPolicy, + useResumeRunFromRecoveryAssumingFalsePositiveMutation, } from '@opentrons/react-api-client' -import { useChainRunCommands } from '../../../resources/runs' -import { RECOVERY_MAP } from '../constants' +import { + useChainRunCommands, + useUpdateRecoveryPolicyWithStrategy, +} from '/app/resources/runs' +import { DEFINED_ERROR_TYPES, ERROR_KINDS, RECOVERY_MAP } from '../constants' +import { getErrorKind } from '/app/organisms/ErrorRecoveryFlows/utils' import type { CreateCommand, @@ -19,27 +23,28 @@ import type { DispenseInPlaceRunTimeCommand, DropTipInPlaceRunTimeCommand, PrepareToAspirateRunTimeCommand, + MoveLabwareParams, } from '@opentrons/shared-data' -import type { - CommandData, - RecoveryPolicyRulesParams, -} from '@opentrons/api-client' +import type { CommandData, IfMatchType, RunAction } from '@opentrons/api-client' import type { WellGroup } from '@opentrons/components' -import type { FailedCommand } from '../types' +import type { FailedCommand, RecoveryRoute, RouteStep } from '../types' import type { UseFailedLabwareUtilsResult } from './useFailedLabwareUtils' import type { UseRouteUpdateActionsResult } from './useRouteUpdateActions' import type { RecoveryToasts } from './useRecoveryToasts' -import type { UseRecoveryAnalyticsResult } from './useRecoveryAnalytics' +import type { UseRecoveryAnalyticsResult } from '/app/redux-resources/analytics' import type { CurrentRecoveryOptionUtils } from './useRecoveryRouting' -import type { ErrorRecoveryFlowsProps } from '../index' +import type { ErrorRecoveryFlowsProps } from '..' +import type { FailedCommandBySource } from './useRetainedFailedCommandBySource' +import type { UpdateErrorRecoveryPolicyWithStrategy } from '/app/resources/runs' interface UseRecoveryCommandsParams { runId: string - failedCommandByRunRecord: ErrorRecoveryFlowsProps['failedCommandByRunRecord'] + failedCommand: FailedCommandBySource | null + unvalidatedFailedCommand: ErrorRecoveryFlowsProps['unvalidatedFailedCommand'] failedLabwareUtils: UseFailedLabwareUtilsResult routeUpdateActions: UseRouteUpdateActionsResult recoveryToastUtils: RecoveryToasts - analytics: UseRecoveryAnalyticsResult + analytics: UseRecoveryAnalyticsResult selectedRecoveryOption: CurrentRecoveryOptionUtils['selectedRecoveryOption'] } export interface UseRecoveryCommandsResult { @@ -49,39 +54,71 @@ export interface UseRecoveryCommandsResult { cancelRun: () => void /* A terminal recovery command, that causes ER to exit as the run status becomes "running" */ skipFailedCommand: () => void - /* A non-terminal recovery command. Ignore this errorKind for the rest of this run. */ - ignoreErrorKindThisRun: () => Promise + /* A non-terminal recovery command. Ignore this errorKind for the rest of this run. + * The server is not informed of recovery policy changes until a terminal recovery command occurs that does not result + * in termination of the run. */ + ignoreErrorKindThisRun: (ignoreErrors: boolean) => Promise /* A non-terminal recovery command */ retryFailedCommand: () => Promise /* A non-terminal recovery command */ homePipetteZAxes: () => Promise /* A non-terminal recovery command */ pickUpTips: () => Promise + /* A non-terminal recovery command */ + releaseGripperJaws: () => Promise + /* A non-terminal recovery command */ + homeExceptPlungers: () => Promise + /* A non-terminal recovery command */ + moveLabwareWithoutPause: () => Promise + /* A non-terminal recovery-command */ + homeAll: () => Promise } // TODO(jh, 07-24-24): Create tighter abstractions for terminal vs. non-terminal commands. // Returns commands with a "fixit" intent. Commands may or may not terminate Error Recovery. See each command docstring for details. export function useRecoveryCommands({ runId, - failedCommandByRunRecord, + failedCommand, + unvalidatedFailedCommand, failedLabwareUtils, routeUpdateActions, recoveryToastUtils, analytics, selectedRecoveryOption, }: UseRecoveryCommandsParams): UseRecoveryCommandsResult { + const [ignoreErrors, setIgnoreErrors] = useState(false) + const { proceedToRouteAndStep } = routeUpdateActions const { chainRunCommands } = useChainRunCommands( runId, - failedCommandByRunRecord?.id + unvalidatedFailedCommand?.id ) const { mutateAsync: resumeRunFromRecovery, } = useResumeRunFromRecoveryMutation() + const { + mutateAsync: resumeRunFromRecoveryAssumingFalsePositive, + } = useResumeRunFromRecoveryAssumingFalsePositiveMutation() const { stopRun } = useStopRunMutation() - const { updateErrorRecoveryPolicy } = useUpdateErrorRecoveryPolicy(runId) + const updateErrorRecoveryPolicy = useUpdateRecoveryPolicyWithStrategy(runId) const { makeSuccessToast } = recoveryToastUtils + // TODO(jh, 11-21-24): Some commands return a 200 with an error body. We should catch these and propagate the error. + const chainRunRecoveryCommands = useCallback( + ( + commands: CreateCommand[], + continuePastFailure: boolean = false + ): Promise => + chainRunCommands(commands, continuePastFailure).catch(e => { + console.warn(`Error executing "fixit" command: ${e}`) + analytics.reportActionSelectedResult(selectedRecoveryOption, 'failed') + // the catch never occurs if continuePastCommandFailure is "true" + void proceedToRouteAndStep(RECOVERY_MAP.ERROR_WHILE_RECOVERING.ROUTE) + return Promise.reject(new Error(`Could not execute command: ${e}`)) + }), + [analytics, selectedRecoveryOption] + ) + const buildRetryPrepMove = (): MoveToCoordinatesCreateCommand | null => { type InPlaceCommand = | AspirateInPlaceRunTimeCommand @@ -89,6 +126,7 @@ export function useRecoveryCommands({ | DispenseInPlaceRunTimeCommand | DropTipInPlaceRunTimeCommand | PrepareToAspirateRunTimeCommand + const IN_PLACE_COMMAND_TYPES = [ 'aspirateInPlace', 'dispenseInPlace', @@ -96,29 +134,38 @@ export function useRecoveryCommands({ 'dropTipInPlace', 'prepareToAspirate', ] as const + + const RETRY_ERROR_TYPES = [ + DEFINED_ERROR_TYPES.OVERPRESSURE, + DEFINED_ERROR_TYPES.TIP_PHYSICALLY_ATTACHED, + ] as const + const isInPlace = ( failedCommand: FailedCommand ): failedCommand is InPlaceCommand => IN_PLACE_COMMAND_TYPES.includes( (failedCommand as InPlaceCommand).commandType ) - return failedCommandByRunRecord != null - ? isInPlace(failedCommandByRunRecord) - ? failedCommandByRunRecord.error?.isDefined && - failedCommandByRunRecord.error?.errorType === 'overpressure' && + + return unvalidatedFailedCommand != null + ? isInPlace(unvalidatedFailedCommand) + ? unvalidatedFailedCommand.error?.isDefined && + RETRY_ERROR_TYPES.includes( + unvalidatedFailedCommand.error?.errorType + ) && // Paranoia: this value comes from the wire and may be unevenly implemented - typeof failedCommandByRunRecord.error?.errorInfo?.retryLocation?.at( + typeof unvalidatedFailedCommand.error?.errorInfo?.retryLocation?.at( 0 ) === 'number' ? { commandType: 'moveToCoordinates', intent: 'fixit', params: { - pipetteId: failedCommandByRunRecord.params?.pipetteId, + pipetteId: unvalidatedFailedCommand.params?.pipetteId, coordinates: { - x: failedCommandByRunRecord.error.errorInfo.retryLocation[0], - y: failedCommandByRunRecord.error.errorInfo.retryLocation[1], - z: failedCommandByRunRecord.error.errorInfo.retryLocation[2], + x: unvalidatedFailedCommand.error.errorInfo.retryLocation[0], + y: unvalidatedFailedCommand.error.errorInfo.retryLocation[1], + z: unvalidatedFailedCommand.error.errorInfo.retryLocation[2], }, }, } @@ -126,23 +173,9 @@ export function useRecoveryCommands({ : null : null } - const chainRunRecoveryCommands = React.useCallback( - ( - commands: CreateCommand[], - continuePastFailure: boolean = false - ): Promise => - chainRunCommands(commands, continuePastFailure).catch(e => { - console.warn(`Error executing "fixit" command: ${e}`) - analytics.reportActionSelectedResult(selectedRecoveryOption, 'failed') - // the catch never occurs if continuePastCommandFailure is "true" - void proceedToRouteAndStep(RECOVERY_MAP.ERROR_WHILE_RECOVERING.ROUTE) - return Promise.reject(new Error(`Could not execute command: ${e}`)) - }), - [chainRunCommands] - ) - const retryFailedCommand = React.useCallback((): Promise => { - const { commandType, params } = failedCommandByRunRecord as FailedCommand // Null case is handled before command could be issued. + const retryFailedCommand = useCallback((): Promise => { + const { commandType, params } = unvalidatedFailedCommand as FailedCommand // Null case is handled before command could be issued. return chainRunRecoveryCommands( [ // move back to the location of the command if it is an in-place command @@ -150,20 +183,20 @@ export function useRecoveryCommands({ { commandType, params }, // retry the command that failed ].filter(c => c != null) as CreateCommand[] ) // the created command is the same command that failed - }, [chainRunRecoveryCommands, failedCommandByRunRecord?.key]) + }, [chainRunRecoveryCommands, unvalidatedFailedCommand?.key]) // Homes the Z-axis of all attached pipettes. - const homePipetteZAxes = React.useCallback((): Promise => { + const homePipetteZAxes = useCallback((): Promise => { return chainRunRecoveryCommands([HOME_PIPETTE_Z_AXES]) }, [chainRunRecoveryCommands]) // Pick up the user-selected tips - const pickUpTips = React.useCallback((): Promise => { + const pickUpTips = useCallback((): Promise => { const { selectedTipLocations, failedLabware } = failedLabwareUtils const pickUpTipCmd = buildPickUpTips( selectedTipLocations, - failedCommandByRunRecord, + unvalidatedFailedCommand, failedLabware ) @@ -172,54 +205,151 @@ export function useRecoveryCommands({ } else { return chainRunRecoveryCommands([pickUpTipCmd]) } - }, [chainRunRecoveryCommands, failedCommandByRunRecord, failedLabwareUtils]) + }, [chainRunRecoveryCommands, unvalidatedFailedCommand, failedLabwareUtils]) + + const ignoreErrorKindThisRun = (ignoreErrors: boolean): Promise => { + setIgnoreErrors(ignoreErrors) + return Promise.resolve() + } + + // Only send the finalized error policy to the server during a terminal recovery command that does not terminate the run. + // If the request to update the policy fails, route to the error modal. + const handleIgnoringErrorKind = useCallback((): Promise => { + if (ignoreErrors) { + if (unvalidatedFailedCommand?.error != null) { + const ifMatch: IfMatchType = isAssumeFalsePositiveResumeKind( + failedCommand + ) + ? 'assumeFalsePositiveAndContinue' + : 'ignoreAndContinue' + + const ignorePolicyRules = buildIgnorePolicyRules( + unvalidatedFailedCommand.commandType, + unvalidatedFailedCommand.error.errorType, + ifMatch + ) + + return updateErrorRecoveryPolicy(ignorePolicyRules, 'append') + .then(() => Promise.resolve()) + .catch((e: Error) => + Promise.reject( + new Error(`Failed to update recovery policy: ${e.message}`) + ) + ) + } else { + void proceedToRouteAndStep(RECOVERY_MAP.ERROR_WHILE_RECOVERING.ROUTE) + return Promise.reject( + new Error('Could not execute command. No failed command.') + ) + } + } else { + return Promise.resolve() + } + }, [ + unvalidatedFailedCommand?.error?.errorType, + unvalidatedFailedCommand?.commandType, + ignoreErrors, + ]) - const resumeRun = React.useCallback((): void => { - void resumeRunFromRecovery(runId).then(() => { - analytics.reportActionSelectedResult(selectedRecoveryOption, 'succeeded') - makeSuccessToast() - }) - }, [runId, resumeRunFromRecovery, makeSuccessToast]) + const resumeRun = useCallback((): void => { + void handleIgnoringErrorKind() + .then(() => resumeRunFromRecovery(runId)) + .then(() => { + analytics.reportActionSelectedResult( + selectedRecoveryOption, + 'succeeded' + ) + makeSuccessToast() + }) + }, [ + runId, + ignoreErrors, + resumeRunFromRecovery, + handleIgnoringErrorKind, + selectedRecoveryOption, + makeSuccessToast, + ]) - const cancelRun = React.useCallback((): void => { + const cancelRun = useCallback((): void => { analytics.reportActionSelectedResult(selectedRecoveryOption, 'succeeded') stopRun(runId) }, [runId]) - const skipFailedCommand = React.useCallback((): void => { - void resumeRunFromRecovery(runId).then(() => { - analytics.reportActionSelectedResult(selectedRecoveryOption, 'succeeded') - makeSuccessToast() - }) - }, [runId, resumeRunFromRecovery, makeSuccessToast]) - - const ignoreErrorKindThisRun = React.useCallback((): Promise => { - if (failedCommandByRunRecord?.error != null) { - const ignorePolicyRules = buildIgnorePolicyRules( - failedCommandByRunRecord.commandType, - failedCommandByRunRecord.error.errorType - ) - - updateErrorRecoveryPolicy(ignorePolicyRules) - return Promise.resolve() + const handleResumeAction = (): Promise => { + if (isAssumeFalsePositiveResumeKind(failedCommand)) { + return resumeRunFromRecoveryAssumingFalsePositive(runId) } else { - return Promise.reject( - new Error('Could not execute command. No failed command.') - ) + return resumeRunFromRecovery(runId) } + } + + const skipFailedCommand = useCallback((): void => { + void handleIgnoringErrorKind().then(() => + handleResumeAction().then(() => { + analytics.reportActionSelectedResult( + selectedRecoveryOption, + 'succeeded' + ) + makeSuccessToast() + }) + ) }, [ - failedCommandByRunRecord?.error?.errorType, - failedCommandByRunRecord?.commandType, + runId, + resumeRunFromRecovery, + handleIgnoringErrorKind, + selectedRecoveryOption, + makeSuccessToast, ]) + const releaseGripperJaws = useCallback((): Promise => { + return chainRunRecoveryCommands([RELEASE_GRIPPER_JAW]) + }, [chainRunRecoveryCommands]) + + const homeExceptPlungers = useCallback((): Promise => { + return chainRunRecoveryCommands([HOME_EXCEPT_PLUNGERS]) + }, [chainRunRecoveryCommands]) + + const homeAll = useCallback((): Promise => { + return chainRunRecoveryCommands([HOME_ALL]) + }, [chainRunRecoveryCommands]) + + const moveLabwareWithoutPause = useCallback((): Promise => { + const moveLabwareCmd = buildMoveLabwareWithoutPause( + unvalidatedFailedCommand + ) + if (moveLabwareCmd == null) { + return Promise.reject(new Error('Invalid use of MoveLabware command')) + } else { + return chainRunRecoveryCommands([moveLabwareCmd]) + } + }, [chainRunRecoveryCommands, unvalidatedFailedCommand]) + return { resumeRun, cancelRun, retryFailedCommand, homePipetteZAxes, pickUpTips, + releaseGripperJaws, + homeExceptPlungers, + moveLabwareWithoutPause, skipFailedCommand, ignoreErrorKindThisRun, + homeAll, + } +} + +export function isAssumeFalsePositiveResumeKind( + failedCommand: UseRecoveryCommandsParams['failedCommand'] +): boolean { + const errorKind = getErrorKind(failedCommand) + + switch (errorKind) { + case ERROR_KINDS.TIP_NOT_DETECTED: + case ERROR_KINDS.TIP_DROP_FAILED: + return true + default: + return false } } @@ -229,6 +359,48 @@ export const HOME_PIPETTE_Z_AXES: CreateCommand = { intent: 'fixit', } +export const RELEASE_GRIPPER_JAW: CreateCommand = { + commandType: 'unsafe/ungripLabware', + params: {}, + intent: 'fixit', +} + +// in case the gripper does not know the position after a stall/collision we must update the position. +export const UPDATE_ESTIMATORS_EXCEPT_PLUNGERS: CreateCommand = { + commandType: 'unsafe/updatePositionEstimators', + params: { axes: ['x', 'y', 'extensionZ'] }, +} + +export const HOME_EXCEPT_PLUNGERS: CreateCommand = { + commandType: 'home', + params: { + axes: ['extensionJaw', 'extensionZ', 'leftZ', 'rightZ', 'x', 'y'], + }, +} + +export const HOME_ALL: CreateCommand = { + commandType: 'home', + params: {}, +} + +const buildMoveLabwareWithoutPause = ( + failedCommand: FailedCommand | null +): CreateCommand | null => { + if (failedCommand == null) { + return null + } + const moveLabwareParams = failedCommand.params as MoveLabwareParams + return { + commandType: 'moveLabware', + params: { + labwareId: moveLabwareParams.labwareId, + newLocation: moveLabwareParams.newLocation, + strategy: 'manualMoveWithoutPause', + }, + intent: 'fixit', + } +} + export const buildPickUpTips = ( tipGroup: WellGroup | null, failedCommand: FailedCommand | null, @@ -257,13 +429,10 @@ export const buildPickUpTips = ( export const buildIgnorePolicyRules = ( commandType: FailedCommand['commandType'], - errorType: string -): RecoveryPolicyRulesParams => { - return [ - { - commandType, - errorType, - ifMatch: 'ignoreAndContinue', - }, - ] -} + errorType: string, + ifMatch: IfMatchType +): UpdateErrorRecoveryPolicyWithStrategy['newPolicy'] => ({ + commandType, + errorType, + ifMatch, +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryOptionCopy.tsx b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryOptionCopy.tsx index 4e636f6fd78..6c7f2f8fc94 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryOptionCopy.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryOptionCopy.tsx @@ -1,28 +1,38 @@ import { useTranslation } from 'react-i18next' -import type { RecoveryRoute } from '../types' -import { RECOVERY_MAP } from '../constants' +import type { ErrorKind, RecoveryRoute } from '../types' +import { ERROR_KINDS, RECOVERY_MAP } from '../constants' // Return user-friendly recovery option copy from a given route. Only routes that are // recovery options are handled. export function useRecoveryOptionCopy(): ( - recoveryOption: RecoveryRoute | null + recoveryOption: RecoveryRoute | null, + errorKind: ErrorKind ) => string { const { t } = useTranslation('error_recovery') const getRecoveryOptionCopy = ( - recoveryOption: RecoveryRoute | null + recoveryOption: RecoveryRoute | null, + errorKind: ErrorKind ): string => { switch (recoveryOption) { - case RECOVERY_MAP.RETRY_FAILED_COMMAND.ROUTE: - return t('retry_step') + case RECOVERY_MAP.RETRY_STEP.ROUTE: + if (errorKind === ERROR_KINDS.TIP_DROP_FAILED) { + return t('retry_dropping_tip') + } else if (errorKind === ERROR_KINDS.TIP_NOT_DETECTED) { + return t('retry_picking_up_tip') + } else { + return t('retry_step') + } case RECOVERY_MAP.CANCEL_RUN.ROUTE: return t('cancel_run') + case RECOVERY_MAP.HOME_AND_RETRY.ROUTE: + return t('home_and_retry') case RECOVERY_MAP.RETRY_NEW_TIPS.ROUTE: return t('retry_with_new_tips') case RECOVERY_MAP.RETRY_SAME_TIPS.ROUTE: return t('retry_with_same_tips') - case RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.ROUTE: + case RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE: return t('manually_fill_well_and_skip') case RECOVERY_MAP.IGNORE_AND_SKIP.ROUTE: return t('ignore_error_and_skip') @@ -30,6 +40,10 @@ export function useRecoveryOptionCopy(): ( return t('skip_to_next_step_new_tips') case RECOVERY_MAP.SKIP_STEP_WITH_SAME_TIPS.ROUTE: return t('skip_to_next_step_same_tips') + case RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE: + return t('manually_move_lw_and_skip') + case RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE: + return t('manually_replace_lw_and_retry') default: return 'Unknown action' } diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryRouting.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryRouting.ts index b97a1206739..cbf23a1393b 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryRouting.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryRouting.ts @@ -1,15 +1,15 @@ -import * as React from 'react' +import { useState } from 'react' import { RECOVERY_MAP } from '../constants' -import type { IRecoveryMap, RecoveryRoute, ValidSubMap } from '../types' +import type { IRecoveryMap, RecoveryRoute, ValidDropTipSubMap } from '../types' // Utils for getting/setting the current submap. See useRecoveryRouting. export interface SubMapUtils { /* See useRecoveryRouting. */ - updateSubMap: (subMap: ValidSubMap | null) => void + updateSubMap: (subMap: ValidDropTipSubMap | null) => void /* See useRecoveryRouting. */ - subMap: ValidSubMap | null + subMap: ValidDropTipSubMap | null } export interface UseRecoveryRoutingResult { @@ -28,12 +28,12 @@ export interface UseRecoveryRoutingResult { * */ export function useRecoveryRouting(): UseRecoveryRoutingResult { - const [recoveryMap, setRecoveryMap] = React.useState({ + const [recoveryMap, setRecoveryMap] = useState({ route: RECOVERY_MAP.OPTION_SELECTION.ROUTE, step: RECOVERY_MAP.OPTION_SELECTION.STEPS.SELECT, }) - const [subMap, setSubMap] = React.useState(null) + const [subMap, setSubMap] = useState(null) const currentRecoveryOptionUtils = useSelectedRecoveryOption() @@ -56,7 +56,7 @@ export function useSelectedRecoveryOption(): CurrentRecoveryOptionUtils { const [ selectedRecoveryOption, setSelectedRecoveryOption, - ] = React.useState(null) + ] = useState(null) return { selectedRecoveryOption, diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTakeover.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTakeover.ts index 3008a9066df..8e8b3466039 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTakeover.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTakeover.ts @@ -1,13 +1,13 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { useSelector } from 'react-redux' -import { getUserId } from '../../../redux/config' +import { getUserId } from '/app/redux/config' import { useClientDataRecovery, useUpdateClientDataRecovery, -} from '../../../resources/client_data' +} from '/app/resources/client_data' -import type { ClientDataRecovery } from '../../../resources/client_data' +import type { ClientDataRecovery } from '/app/resources/client_data' import type { UseERWizardResult } from '../ErrorRecoveryWizard' const CLIENT_DATA_INTERVAL_MS = 5000 @@ -40,7 +40,7 @@ export interface UseRecoveryTakeoverResult { export function useRecoveryTakeover( toggleERWiz: UseERWizardResult['toggleERWizard'] ): UseRecoveryTakeoverResult { - const [isActiveUser, setIsActiveUser] = React.useState(false) + const [isActiveUser, setIsActiveUser] = useState(false) const thisUserId = useSelector(getUserId) const { userId: activeId, intent } = useClientDataRecovery({ @@ -49,14 +49,14 @@ export function useRecoveryTakeover( const { updateWithIntent, clearClientData } = useUpdateClientDataRecovery() // Update the client's active user status implicitly if revoked by a different client. - React.useEffect(() => { + useEffect(() => { if (isActiveUser && activeId !== thisUserId) { setIsActiveUser(false) } }, [activeId]) // Not all dependencies added for intended behavior! // If Error Recovery unrenders and this client is the active user, revoke the client's active user status. - React.useEffect(() => { + useEffect(() => { return () => { if (isActiveUser) { clearClientData() diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTipStatus.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTipStatus.ts index eff2565a2eb..8db4af030ea 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTipStatus.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryTipStatus.ts @@ -1,19 +1,22 @@ -import * as React from 'react' +import { useState } from 'react' import head from 'lodash/head' -import { useHost } from '@opentrons/react-api-client' +import { useRunCurrentState } from '@opentrons/react-api-client' import { getPipetteModelSpecs } from '@opentrons/shared-data' - -import { useTipAttachmentStatus } from '../../DropTipWizardFlows' +import { useTipAttachmentStatus } from '/app/resources/instruments' +import { ERROR_KINDS } from '/app/organisms/ErrorRecoveryFlows/constants' +import { getErrorKind } from '/app/organisms/ErrorRecoveryFlows/utils' import type { Run, Instruments, PipetteData } from '@opentrons/api-client' import type { - TipAttachmentStatusResult, PipetteWithTip, -} from '../../DropTipWizardFlows' + TipAttachmentStatusResult, +} from '/app/resources/instruments' +import type { ERUtilsProps } from '/app/organisms/ErrorRecoveryFlows/hooks/useERUtils' interface UseRecoveryTipStatusProps { runId: string + failedCommand: ERUtilsProps['failedCommand'] failedPipetteInfo: PipetteData | null attachedInstruments?: Instruments runRecord?: Run @@ -23,22 +26,21 @@ export type RecoveryTipStatusUtils = TipAttachmentStatusResult & { /* Whether the robot is currently determineTipStatus() */ isLoadingTipStatus: boolean runId: string + gripperErrorFirstPipetteWithTip: string | null } // Wraps the tip attachment status utils with Error Recovery specific states and values. export function useRecoveryTipStatus( props: UseRecoveryTipStatusProps ): RecoveryTipStatusUtils { - const [isLoadingTipStatus, setIsLoadingTipStatus] = React.useState(false) + const [isLoadingTipStatus, setIsLoadingTipStatus] = useState(false) const [ failedCommandPipette, setFailedCommandPipette, - ] = React.useState(null) - const host = useHost() + ] = useState(null) const tipAttachmentStatusUtils = useTipAttachmentStatus({ ...props, - host, runRecord: props.runRecord ?? null, }) @@ -78,11 +80,26 @@ export function useRecoveryTipStatus( }) } + // TODO(jh, 11-15-24): This is temporary. Collaborate with design a better way to do drop tip wizard for multiple + // pipettes during error recovery. The tip detection logic will shortly be simplified, too! + const errorKind = getErrorKind(props.failedCommand) + const currentTipStates = + useRunCurrentState(props.runId, { + enabled: errorKind === ERROR_KINDS.GRIPPER_ERROR, + }).data?.data.tipStates ?? null + + const gripperErrorFirstPipetteWithTip = + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + Object.entries(currentTipStates ?? {}).find( + ([_, state]) => state.hasTip + )?.[0] ?? null + return { ...tipAttachmentStatusUtils, aPipetteWithTip: failedCommandPipette, determineTipStatus: determineTipStatusWithLoading, isLoadingTipStatus, runId: props.runId, + gripperErrorFirstPipetteWithTip, } } diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryToasts.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryToasts.ts index 00ccba0ea95..533b9877f72 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryToasts.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useRecoveryToasts.ts @@ -2,15 +2,15 @@ import { useTranslation } from 'react-i18next' import { useToaster } from '../../ToasterOven' import { RECOVERY_MAP } from '../constants' -import { useCommandTextString } from '../../../molecules/Command' +import { useCommandTextString } from '/app/local-resources/commands' -import type { StepCounts } from '../../../resources/protocols/hooks' +import type { StepCounts } from '/app/resources/protocols/hooks' import type { CurrentRecoveryOptionUtils } from './useRecoveryRouting' -import type { UseCommandTextStringParams } from '../../../molecules/Command' +import type { UseCommandTextStringParams } from '/app/local-resources/commands' export type BuildToast = Omit & { isOnDevice: boolean - currentStepCount: StepCounts['currentStepNumber'] + stepCounts: StepCounts selectedRecoveryOption: CurrentRecoveryOptionUtils['selectedRecoveryOption'] } @@ -21,15 +21,16 @@ export interface RecoveryToasts { // Provides methods for rendering success/failure toasts after performing a terminal recovery command. export function useRecoveryToasts({ - currentStepCount, + stepCounts, isOnDevice, selectedRecoveryOption, ...rest }: BuildToast): RecoveryToasts { + const { currentStepNumber, hasRunDiverged } = stepCounts const { makeToast } = useToaster() const displayType = isOnDevice ? 'odd' : 'desktop' - const stepNumber = getStepNumber(selectedRecoveryOption, currentStepCount) + const stepNumber = getStepNumber(selectedRecoveryOption, currentStepNumber) const desktopFullCommandText = useRecoveryFullCommandText({ ...rest, @@ -46,7 +47,8 @@ export function useRecoveryToasts({ ? desktopFullCommandText : recoveryToastText // The "heading" of the toast message. Currently, this text is only present on the desktop toasts. - const headingText = displayType === 'desktop' ? recoveryToastText : undefined + const headingText = + displayType === 'desktop' && !hasRunDiverged ? recoveryToastText : undefined const makeSuccessToast = (): void => { if (selectedRecoveryOption !== RECOVERY_MAP.CANCEL_RUN.ROUTE) { @@ -73,12 +75,18 @@ export function useRecoveryToastText({ }): string { const { t } = useTranslation('error_recovery') - const currentStepReturnVal = t('retrying_step_succeeded', { - step: stepNumber, - }) as string - const nextStepReturnVal = t('skipping_to_step_succeeded', { - step: stepNumber, - }) as string + const currentStepReturnVal = + stepNumber != null + ? t('retrying_step_succeeded', { + step: stepNumber, + }) + : t('retrying_step_succeeded_na') + const nextStepReturnVal = + stepNumber != null + ? t('skipping_to_step_succeeded', { + step: stepNumber, + }) + : t('skipping_to_step_succeeded_na') const toastText = handleRecoveryOptionAction( selectedRecoveryOption, @@ -102,34 +110,40 @@ export function useRecoveryFullCommandText( ): string | null { const { commandTextData, stepNumber } = props - const relevantCmdIdx = typeof stepNumber === 'number' ? stepNumber : -1 - const relevantCmd = commandTextData?.commands[relevantCmdIdx] ?? null + // TODO TOME: I think you are looking one command to far, for some reason. + const relevantCmdIdx = stepNumber ?? -1 + const relevantCmd = commandTextData?.commands[relevantCmdIdx - 1] ?? null - const { commandText, stepTexts } = useCommandTextString({ + const { commandText, kind } = useCommandTextString({ ...props, command: relevantCmd, }) - if (typeof stepNumber === 'string') { - return stepNumber + if (stepNumber == null) { + return null } // Occurs when the relevantCmd doesn't exist, ex, we "skip" the last command of a run. else if (relevantCmd === null) { return null } else { - return truncateIfTCCommand(commandText, stepTexts != null) + return truncateIfTCCommand( + commandText, + ['thermocycler/runProfile', 'thermocycler/runExtendedProfile'].includes( + kind + ) + ) } } // Return the user-facing step number, 0 indexed. If the step number cannot be determined, return '?'. export function getStepNumber( selectedRecoveryOption: BuildToast['selectedRecoveryOption'], - currentStepCount: BuildToast['currentStepCount'] -): number | string { - const currentStepReturnVal = currentStepCount ?? '?' + currentStepCount: BuildToast['stepCounts']['currentStepNumber'] +): number | null { + const currentStepReturnVal = currentStepCount ?? null // There is always a next protocol step after a command that can error, therefore, we don't need to handle that. const nextStepReturnVal = - typeof currentStepCount === 'number' ? currentStepCount + 1 : '?' + typeof currentStepCount === 'number' ? currentStepCount + 1 : null return handleRecoveryOptionAction( selectedRecoveryOption, @@ -144,35 +158,42 @@ function handleRecoveryOptionAction( selectedRecoveryOption: CurrentRecoveryOptionUtils['selectedRecoveryOption'], currentStepReturnVal: T, nextStepReturnVal: T -): T | string { +): T | null { switch (selectedRecoveryOption) { - case RECOVERY_MAP.FILL_MANUALLY_AND_SKIP.ROUTE: + case RECOVERY_MAP.MANUAL_FILL_AND_SKIP.ROUTE: case RECOVERY_MAP.SKIP_STEP_WITH_SAME_TIPS.ROUTE: case RECOVERY_MAP.SKIP_STEP_WITH_NEW_TIPS.ROUTE: case RECOVERY_MAP.IGNORE_AND_SKIP.ROUTE: + case RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE: return nextStepReturnVal case RECOVERY_MAP.CANCEL_RUN.ROUTE: case RECOVERY_MAP.RETRY_SAME_TIPS.ROUTE: case RECOVERY_MAP.RETRY_NEW_TIPS.ROUTE: - case RECOVERY_MAP.RETRY_FAILED_COMMAND.ROUTE: + case RECOVERY_MAP.RETRY_STEP.ROUTE: + case RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE: + case RECOVERY_MAP.HOME_AND_RETRY.ROUTE: return currentStepReturnVal - default: - return 'HANDLE RECOVERY TOAST OPTION EXPLICITLY.' + default: { + return null + } } } // Special case the TC text, so it make sense in a success toast. -function truncateIfTCCommand(commandText: string, isTCText: boolean): string { - if (isTCText) { - const indexOfCycle = commandText.indexOf('cycle') - - if (indexOfCycle === -1) { +function truncateIfTCCommand( + commandText: string, + isTCCommand: boolean +): string { + if (isTCCommand) { + const indexOfProfile = commandText.indexOf('steps') + + if (indexOfProfile === -1) { console.warn( 'TC cycle text has changed. Update Error Recovery TC text utility.' ) } - return commandText.slice(0, indexOfCycle + 5) // +5 to include "cycle" + return commandText.slice(0, indexOfProfile + 5) // +5 to include "steps" } else { return commandText } diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useRetainedFailedCommandBySource.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useRetainedFailedCommandBySource.ts index 10231f5e0cc..5a226fddcf1 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useRetainedFailedCommandBySource.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useRetainedFailedCommandBySource.ts @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useLayoutEffect } from 'react' import type { RunTimeCommand } from '@opentrons/shared-data' import type { ErrorRecoveryFlowsProps } from '..' @@ -16,7 +16,7 @@ export interface FailedCommandBySource { * In order to reduce misuse, bundle the failedCommand into "run" and "analysis" versions. */ export function useRetainedFailedCommandBySource( - failedCommandByRunRecord: ErrorRecoveryFlowsProps['failedCommandByRunRecord'], + failedCommandByRunRecord: ErrorRecoveryFlowsProps['unvalidatedFailedCommand'], protocolAnalysis: ErrorRecoveryFlowsProps['protocolAnalysis'] ): FailedCommandBySource | null { // In some cases, Error Recovery (by the app definition) persists when Error Recovery (by the server definition) does @@ -25,9 +25,9 @@ export function useRetainedFailedCommandBySource( const [ retainedFailedCommand, setRetainedFailedCommand, - ] = React.useState(null) + ] = useState(null) - React.useEffect(() => { + useLayoutEffect(() => { if (failedCommandByRunRecord !== null) { const failedCommandByAnalysis = protocolAnalysis?.commands.find( diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useRouteUpdateActions.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useRouteUpdateActions.ts index 74c8186bf79..09ef7b3dd47 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useRouteUpdateActions.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useRouteUpdateActions.ts @@ -1,9 +1,15 @@ -import * as React from 'react' +import type { MutableRefObject } from 'react' +import { useRef, useCallback } from 'react' import last from 'lodash/last' -import head from 'lodash/head' -import { INVALID, RECOVERY_MAP, STEP_ORDER } from '../constants' +import head from 'lodash/head' +import { + INVALID, + RECOVERY_MAP, + STEP_ORDER, + GRIPPER_MOVE_STEPS, +} from '../constants' import type { IRecoveryMap, RecoveryRoute, @@ -11,12 +17,14 @@ import type { RouteStep, } from '../types' import type { UseRecoveryTakeoverResult } from './useRecoveryTakeover' +import type { UseShowDoorInfoResult } from './useShowDoorInfo' export interface GetRouteUpdateActionsParams { hasLaunchedRecovery: boolean toggleERWizAsActiveUser: UseRecoveryTakeoverResult['toggleERWizAsActiveUser'] recoveryMap: IRecoveryMap setRecoveryMap: (recoveryMap: IRecoveryMap) => void + doorStatusUtils: UseShowDoorInfoResult } export interface UseRouteUpdateActionsResult { @@ -29,23 +37,33 @@ export interface UseRouteUpdateActionsResult { route: RecoveryRoute, step?: RouteStep ) => Promise - /* Stashes the current map then sets the current map to robot in motion. Restores the map after motion completes. */ - setRobotInMotion: ( + /* Stashes the current map then sets the current map to robot in motion after validating the door is closed. + Restores the map after motion completes. */ + handleMotionRouting: ( inMotion: boolean, movingRoute?: RobotMovingRoute ) => Promise + /* Contains the recovery map prior to implicit redirection, if any. Example, if the user is on route A, step A, and the + app implicitly navigates the user to route Z, step Z, the stashed map will contain route A, step A. */ + stashedMap: IRecoveryMap | null + stashedMapRef: MutableRefObject } // Utilities related to routing within the error recovery flows. export function useRouteUpdateActions( routeUpdateActionsParams: GetRouteUpdateActionsParams ): UseRouteUpdateActionsResult { - const { recoveryMap, setRecoveryMap } = routeUpdateActionsParams + const { + recoveryMap, + setRecoveryMap, + doorStatusUtils, + } = routeUpdateActionsParams const { route: currentRoute, step: currentStep } = recoveryMap - const { OPTION_SELECTION, ROBOT_IN_MOTION } = RECOVERY_MAP - const stashedMapRef = React.useRef(null) + const { OPTION_SELECTION, ROBOT_IN_MOTION, ROBOT_DOOR_OPEN } = RECOVERY_MAP + const { isDoorOpen } = doorStatusUtils + const stashedMapRef = useRef(null) - const goBackPrevStep = React.useCallback((): Promise => { + const goBackPrevStep = useCallback((): Promise => { return new Promise((resolve, reject) => { const { getPrevStep } = getRecoveryRouteNavigation(currentRoute) const updatedStep = getPrevStep(currentStep) @@ -60,7 +78,7 @@ export function useRouteUpdateActions( }) }, [currentStep, currentRoute, routeUpdateActionsParams]) - const proceedNextStep = React.useCallback((): Promise => { + const proceedNextStep = useCallback((): Promise => { return new Promise((resolve, reject) => { const { getNextStep } = getRecoveryRouteNavigation(currentRoute) const updatedStep = getNextStep(currentStep) @@ -75,7 +93,7 @@ export function useRouteUpdateActions( }) }, [currentStep, currentRoute, routeUpdateActionsParams]) - const proceedToRouteAndStep = React.useCallback( + const proceedToRouteAndStep = useCallback( (route: RecoveryRoute, step?: RouteStep): Promise => { return new Promise((resolve, reject) => { const newFlowSteps = STEP_ORDER[route] @@ -90,7 +108,30 @@ export function useRouteUpdateActions( [] ) - const setRobotInMotion = React.useCallback( + // If the door is permitted on the current step, but the robot is about to move, we need to manually redirect users + // to the door modal unless the step is specifically a gripper jaw release step. + const checkDoorStatus = useCallback((): Promise => { + return new Promise((resolve, reject) => { + if (isDoorOpen && !GRIPPER_MOVE_STEPS.includes(currentStep)) { + stashedMapRef.current = { route: currentRoute, step: currentStep } + + setRecoveryMap({ + route: ROBOT_DOOR_OPEN.ROUTE, + step: ROBOT_DOOR_OPEN.STEPS.DOOR_OPEN, + }) + + reject( + new Error( + 'Cannot perform a command while the door is open. Routing to door open modal.' + ) + ) + } else { + resolve() + } + }) + }, [currentRoute, currentStep, isDoorOpen]) + + const setRobotInMotion = useCallback( (inMotion: boolean, robotMovingRoute?: RobotMovingRoute): Promise => { return new Promise((resolve, reject) => { if (inMotion) { @@ -122,11 +163,27 @@ export function useRouteUpdateActions( [currentRoute, currentStep] ) + const handleMotionRouting = ( + inMotion: boolean, + robotMovingRoute?: RobotMovingRoute + ): Promise => { + // Only check door status if we are fixin' to move. + if (inMotion) { + return checkDoorStatus().then(() => + setRobotInMotion(inMotion, robotMovingRoute) + ) + } else { + return setRobotInMotion(inMotion, robotMovingRoute) + } + } + return { goBackPrevStep, proceedNextStep, proceedToRouteAndStep, - setRobotInMotion, + handleMotionRouting, + stashedMap: stashedMapRef.current, + stashedMapRef: stashedMapRef, } } @@ -167,7 +224,7 @@ export function getRecoveryRouteNavigation( } type DetermineRecoveryRoutingParams = GetRouteUpdateActionsParams & { - updatedStep: string + updatedStep: RouteStep currentRoute: RecoveryRoute } diff --git a/app/src/organisms/ErrorRecoveryFlows/hooks/useShowDoorInfo.ts b/app/src/organisms/ErrorRecoveryFlows/hooks/useShowDoorInfo.ts index f73f1a22ae0..fc8569c02d8 100644 --- a/app/src/organisms/ErrorRecoveryFlows/hooks/useShowDoorInfo.ts +++ b/app/src/organisms/ErrorRecoveryFlows/hooks/useShowDoorInfo.ts @@ -1,38 +1,59 @@ -import * as React from 'react' - import { RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, RUN_STATUS_AWAITING_RECOVERY_PAUSED, } from '@opentrons/api-client' +import { GRIPPER_MOVE_STEPS, RECOVERY_MAP_METADATA } from '../constants' + import type { RunStatus } from '@opentrons/api-client' import type { ErrorRecoveryFlowsProps } from '../index' +import type { IRecoveryMap, RouteStep } from '../types' const DOOR_OPEN_STATUSES: RunStatus[] = [ RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, RUN_STATUS_AWAITING_RECOVERY_PAUSED, ] -// Whether the door is open or the user has not yet resumed the run after a door open event. +export interface UseShowDoorInfoResult { + /* Whether the door actually open, regardless of whether a door open event is prohibited . */ + isDoorOpen: boolean + /* Whether the door is open and prohibited to be open. */ + isProhibitedDoorOpen: boolean +} + +// Whether the door is open and not permitted to be open or the user has not yet resumed the run after a door open event. export function useShowDoorInfo( - runStatus: ErrorRecoveryFlowsProps['runStatus'] -): boolean { - const [showDoorModal, setShowDoorModal] = React.useState(false) - - React.useEffect(() => { - // TODO(jh, 07-16-24): "recovery paused" is only used for door status and therefore - // a valid way to ensure all apps show the door open prompt, however this could be problematic in the future. - // Consider restructuring this check once the takeover modals are added. - if (runStatus != null && DOOR_OPEN_STATUSES.includes(runStatus)) { - setShowDoorModal(true) - } else if ( - showDoorModal && - runStatus != null && - !DOOR_OPEN_STATUSES.includes(runStatus) - ) { - setShowDoorModal(false) + runStatus: ErrorRecoveryFlowsProps['runStatus'], + recoveryMap: IRecoveryMap, + currentStep: RouteStep +): UseShowDoorInfoResult { + // TODO(jh, 07-16-24): "recovery paused" is only used for door status and therefore + // a valid way to ensure all apps show the door open prompt, however this could be problematic in the future. + // Consider restructuring this check once the takeover modals are added. + const isDoorOpen = runStatus != null && DOOR_OPEN_STATUSES.includes(runStatus) + const isProhibitedDoorOpen = + isDoorOpen && + !isDoorPermittedOpen(recoveryMap) && + !GRIPPER_MOVE_STEPS.includes(currentStep) + + return { isDoorOpen, isProhibitedDoorOpen } +} + +function isDoorPermittedOpen(recoveryMap: IRecoveryMap): boolean { + const { route, step } = recoveryMap + + if (route in RECOVERY_MAP_METADATA) { + const routeConfig = RECOVERY_MAP_METADATA[route] + + if (step in routeConfig) { + // @ts-expect-error Overly nested type that TS struggles to infer. + return routeConfig[step].allowDoorOpen } - }, [runStatus, showDoorModal]) + } - return showDoorModal + console.error( + 'Unexpected route/step combination when attempting to get door metadata for recovery map: ', + recoveryMap + ) + return false } diff --git a/app/src/organisms/ErrorRecoveryFlows/index.tsx b/app/src/organisms/ErrorRecoveryFlows/index.tsx index bb5dd9af584..68184d71b46 100644 --- a/app/src/organisms/ErrorRecoveryFlows/index.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useMemo, useLayoutEffect, useState } from 'react' import { useSelector } from 'react-redux' import { @@ -7,25 +7,29 @@ import { RUN_STATUS_AWAITING_RECOVERY_PAUSED, RUN_STATUS_BLOCKED_BY_OPEN_DOOR, RUN_STATUS_FAILED, + RUN_STATUS_FINISHING, RUN_STATUS_IDLE, RUN_STATUS_PAUSED, RUN_STATUS_RUNNING, RUN_STATUS_STOP_REQUESTED, + RUN_STATUS_STOPPED, RUN_STATUS_SUCCEEDED, } from '@opentrons/api-client' -import { OT2_ROBOT_TYPE } from '@opentrons/shared-data' +import { + getLoadedLabwareDefinitionsByUri, + OT2_ROBOT_TYPE, +} from '@opentrons/shared-data' import { useHost } from '@opentrons/react-api-client' -import { getIsOnDevice } from '../../redux/config' +import { getIsOnDevice } from '/app/redux/config' import { ErrorRecoveryWizard, useERWizard } from './ErrorRecoveryWizard' -import { RunPausedSplash, useRunPausedSplash } from './RunPausedSplash' +import { RecoverySplash, useRecoverySplash } from './RecoverySplash' import { RecoveryTakeover } from './RecoveryTakeover' import { useCurrentlyRecoveringFrom, useERUtils, useRecoveryTakeover, useRetainedFailedCommandBySource, - useShowDoorInfo, } from './hooks' import type { RunStatus } from '@opentrons/api-client' @@ -39,18 +43,20 @@ const VALID_ER_RUN_STATUSES: RunStatus[] = [ RUN_STATUS_STOP_REQUESTED, ] +// Effectively statuses that are not an "awaiting-recovery" status OR "stop requested." const INVALID_ER_RUN_STATUSES: RunStatus[] = [ RUN_STATUS_RUNNING, RUN_STATUS_PAUSED, RUN_STATUS_BLOCKED_BY_OPEN_DOOR, + RUN_STATUS_FINISHING, + RUN_STATUS_STOPPED, RUN_STATUS_FAILED, RUN_STATUS_SUCCEEDED, RUN_STATUS_IDLE, ] -interface UseErrorRecoveryResult { +export interface UseErrorRecoveryResult { isERActive: boolean - /* There is no FailedCommand if the run statis is not AWAITING_RECOVERY. */ failedCommand: FailedCommand | null } @@ -58,47 +64,39 @@ export function useErrorRecoveryFlows( runId: string, runStatus: RunStatus | null ): UseErrorRecoveryResult { - const [isERActive, setIsERActive] = React.useState(false) - // If client accesses a valid ER runs status besides AWAITING_RECOVERY but accesses it outside of Error Recovery flows, don't show ER. - const [hasSeenAwaitingRecovery, setHasSeenAwaitingRecovery] = React.useState( - false - ) + const [isERActive, setIsERActive] = useState(false) const failedCommand = useCurrentlyRecoveringFrom(runId, runStatus) - if ( - !hasSeenAwaitingRecovery && - ([ - RUN_STATUS_AWAITING_RECOVERY, - RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, - RUN_STATUS_AWAITING_RECOVERY_PAUSED, - ] as Array).includes(runStatus) - ) { - setHasSeenAwaitingRecovery(true) - } - // Reset recovery mode after the client has exited recovery, otherwise "cancel run" will trigger ER after the first recovery. - else if ( - hasSeenAwaitingRecovery && - runStatus != null && - INVALID_ER_RUN_STATUSES.includes(runStatus) - ) { - setHasSeenAwaitingRecovery(false) + // The complexity of this logic exists to persist Error Recovery screens past the server's definition of Error Recovery. + // Ex, show a "cancelling run" modal in Error Recovery flows despite the robot no longer being in a recoverable state. + + const isValidERStatus = ( + status: RunStatus | null, + hasSeenAwaitingRecovery: boolean + ): boolean => { + return ( + status !== null && + (status === RUN_STATUS_AWAITING_RECOVERY || + (VALID_ER_RUN_STATUSES.includes(status) && hasSeenAwaitingRecovery)) + ) } - const isValidRunStatus = - runStatus != null && - VALID_ER_RUN_STATUSES.includes(runStatus) && - hasSeenAwaitingRecovery - - if (!isERActive && isValidRunStatus && failedCommand != null) { - setIsERActive(true) - } - // Because multiple ER flows may occur per run, disable ER when the status is not "awaiting-recovery" or a - // terminating run status in which we want to persist ER flows. Specific recovery commands cause run status to change. - // See a specific command's docstring for details. - // ER handles a null failedCommand outside the splash screen, so we shouldn't set it false here. - else if (isERActive && !isValidRunStatus) { - setIsERActive(false) - } + // If client accesses a valid ER runs status besides AWAITING_RECOVERY but accesses it outside of Error Recovery flows, + // don't show ER. + useLayoutEffect(() => { + if (runStatus != null) { + const isAwaitingRecovery = + VALID_ER_RUN_STATUSES.includes(runStatus) && + runStatus !== RUN_STATUS_STOP_REQUESTED && + failedCommand != null // Prevents one render cycle of an unknown failed command. + + if (isAwaitingRecovery) { + setIsERActive(isValidERStatus(runStatus, true)) + } else if (INVALID_ER_RUN_STATUSES.includes(runStatus)) { + setIsERActive(isValidERStatus(runStatus, false)) + } + } + }, [runStatus, failedCommand]) return { isERActive, @@ -109,17 +107,21 @@ export function useErrorRecoveryFlows( export interface ErrorRecoveryFlowsProps { runId: string runStatus: RunStatus | null - failedCommandByRunRecord: FailedCommand | null + /* In some parts of Error Recovery, such as "retry failed command" during a generic error flow, we want to utilize + * information derived from the failed command from the run record even if there is no matching command in protocol analysis. + * Using a failed command that is not matched to a protocol analysis command is unsafe in most circumstances (ie, in + * non-generic recovery flows. Prefer using failedCommandBySource in most circumstances. */ + unvalidatedFailedCommand: FailedCommand | null protocolAnalysis: CompletedProtocolAnalysis | null } export function ErrorRecoveryFlows( props: ErrorRecoveryFlowsProps ): JSX.Element | null { - const { protocolAnalysis, runStatus, failedCommandByRunRecord } = props + const { protocolAnalysis, runStatus, unvalidatedFailedCommand } = props const failedCommandBySource = useRetainedFailedCommandBySource( - failedCommandByRunRecord, + unvalidatedFailedCommand, protocolAnalysis ) @@ -128,15 +130,27 @@ export function ErrorRecoveryFlows( const robotType = protocolAnalysis?.robotType ?? OT2_ROBOT_TYPE const robotName = useHost()?.robotName ?? 'robot' - const isDoorOpen = useShowDoorInfo(runStatus) + const isValidRobotSideAnalysis = protocolAnalysis != null + + // TODO(jh, 10-22-24): EXEC-769. + const labwareDefinitionsByUri = useMemo( + () => + protocolAnalysis != null + ? getLoadedLabwareDefinitionsByUri(protocolAnalysis?.commands) + : null, + [isValidRobotSideAnalysis] + ) + const allRunDefs = + labwareDefinitionsByUri != null + ? Object.values(labwareDefinitionsByUri) + : [] + const { showTakeover, isActiveUser, intent, toggleERWizAsActiveUser, } = useRecoveryTakeover(toggleERWizard) - const renderWizard = isActiveUser && (showERWizard || isDoorOpen) - const showSplash = useRunPausedSplash(isOnDevice, renderWizard) const recoveryUtils = useERUtils({ ...props, @@ -144,9 +158,17 @@ export function ErrorRecoveryFlows( toggleERWizAsActiveUser, isOnDevice, robotType, + isActiveUser, failedCommand: failedCommandBySource, + allRunDefs, + labwareDefinitionsByUri, }) + const renderWizard = + isActiveUser && + (showERWizard || recoveryUtils.doorStatusUtils.isProhibitedDoorOpen) + const showSplash = useRecoverySplash(isOnDevice, renderWizard as boolean) + return ( <> {showTakeover ? ( @@ -163,12 +185,12 @@ export function ErrorRecoveryFlows( {...recoveryUtils} robotType={robotType} isOnDevice={isOnDevice} - isDoorOpen={isDoorOpen} failedCommand={failedCommandBySource} + allRunDefs={allRunDefs} /> ) : null} {showSplash ? ( - ) : null} diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/ErrorDetailsModal.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/ErrorDetailsModal.tsx index 2f5cf13b724..070e974bed3 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/ErrorDetailsModal.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/ErrorDetailsModal.tsx @@ -1,39 +1,40 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom' import { css } from 'styled-components' import { - Flex, - StyledText, - SPACING, - COLORS, - ModalShell, - ModalHeader, BORDERS, + COLORS, DIRECTION_COLUMN, + Flex, + ModalHeader, + ModalShell, + SPACING, + StyledText, } from '@opentrons/components' import { useErrorName } from '../hooks' -import { OddModal } from '../../../molecules/OddModal' -import { getModalPortalEl, getTopPortalEl } from '../../../App/portal' +import { OddModal } from '/app/molecules/OddModal' +import { getModalPortalEl, getTopPortalEl } from '/app/App/portal' import { ERROR_KINDS } from '../constants' -import { InlineNotification } from '../../../atoms/InlineNotification' +import { InlineNotification } from '/app/atoms/InlineNotification' import { StepInfo } from './StepInfo' import { getErrorKind } from '../utils' -import type { RobotType } from '@opentrons/shared-data' +import type { ReactNode } from 'react' +import type { LabwareDefinition2, RobotType } from '@opentrons/shared-data' import type { IconProps } from '@opentrons/components' -import type { OddModalHeaderBaseProps } from '../../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' import type { ERUtilsResults, useRetainedFailedCommandBySource } from '../hooks' import type { ErrorRecoveryFlowsProps } from '..' -import type { DesktopSizeType } from '../types' +import type { DesktopSizeType, ErrorKind } from '../types' export function useErrorDetailsModal(): { showModal: boolean toggleModal: () => void } { - const [showModal, setShowModal] = React.useState(false) + const [showModal, setShowModal] = useState(false) const toggleModal = (): void => { setShowModal(!showModal) @@ -44,7 +45,7 @@ export function useErrorDetailsModal(): { type ErrorDetailsModalProps = Omit< ErrorRecoveryFlowsProps, - 'failedCommandByRunRecord' + 'unvalidatedFailedCommand' > & ERUtilsResults & { toggleModal: () => void @@ -52,18 +53,22 @@ type ErrorDetailsModalProps = Omit< robotType: RobotType desktopType: DesktopSizeType failedCommand: ReturnType + allRunDefs: LabwareDefinition2[] } export function ErrorDetailsModal(props: ErrorDetailsModalProps): JSX.Element { const { failedCommand, toggleModal, isOnDevice } = props - const errorKind = getErrorKind(failedCommand?.byRunRecord ?? null) + const errorKind = getErrorKind(failedCommand) const errorName = useErrorName(errorKind) - const getIsOverpressureErrorKind = (): boolean => { + const isNotificationErrorKind = (): boolean => { switch (errorKind) { case ERROR_KINDS.OVERPRESSURE_PREPARE_TO_ASPIRATE: case ERROR_KINDS.OVERPRESSURE_WHILE_ASPIRATING: case ERROR_KINDS.OVERPRESSURE_WHILE_DISPENSING: + case ERROR_KINDS.TIP_NOT_DETECTED: + case ERROR_KINDS.GRIPPER_ERROR: + case ERROR_KINDS.STALL_OR_COLLISION: return true default: return false @@ -83,7 +88,9 @@ export function ErrorDetailsModal(props: ErrorDetailsModalProps): JSX.Element { toggleModal={toggleModal} modalHeader={modalHeader} > - {getIsOverpressureErrorKind() ? : null} + {isNotificationErrorKind() ? ( + + ) : null} , getTopPortalEl() ) @@ -94,7 +101,9 @@ export function ErrorDetailsModal(props: ErrorDetailsModalProps): JSX.Element { toggleModal={toggleModal} modalHeader={modalHeader} > - {getIsOverpressureErrorKind() ? : null} + {isNotificationErrorKind() ? ( + + ) : null} , getModalPortalEl() ) @@ -105,7 +114,7 @@ export function ErrorDetailsModal(props: ErrorDetailsModalProps): JSX.Element { } type ErrorDetailsModalType = ErrorDetailsModalProps & { - children: React.ReactNode + children: ReactNode modalHeader: OddModalHeaderBaseProps toggleModal: () => void desktopType: DesktopSizeType @@ -191,14 +200,76 @@ export function ErrorDetailsModalODD( ) } -export function OverpressureBanner(): JSX.Element | null { +export function NotificationBanner({ + errorKind, +}: { + errorKind: ErrorKind +}): JSX.Element { + const buildContent = (): JSX.Element => { + switch (errorKind) { + case ERROR_KINDS.OVERPRESSURE_PREPARE_TO_ASPIRATE: + case ERROR_KINDS.OVERPRESSURE_WHILE_ASPIRATING: + case ERROR_KINDS.OVERPRESSURE_WHILE_DISPENSING: + return + case ERROR_KINDS.TIP_NOT_DETECTED: + return + case ERROR_KINDS.GRIPPER_ERROR: + return + case ERROR_KINDS.STALL_OR_COLLISION: + return + default: + console.error('Handle error kind notification banners explicitly.') + return
    + } + } + + return buildContent() +} + +export function OverpressureBanner(): JSX.Element { const { t } = useTranslation('error_recovery') return ( + ) +} + +export function TipNotDetectedBanner(): JSX.Element { + const { t } = useTranslation('error_recovery') + + return ( + + ) +} + +export function GripperErrorBanner(): JSX.Element { + const { t } = useTranslation('error_recovery') + + return ( + + ) +} + +export function StallErrorBanner(): JSX.Element { + const { t } = useTranslation('error_recovery') + + return ( + ) } diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/FailedStepNextStep.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/FailedStepNextStep.tsx index f17a98bf311..a27a1adea04 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/FailedStepNextStep.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/FailedStepNextStep.tsx @@ -1,6 +1,5 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' -import { CategorizedStepContent } from '../../../molecules/InterventionModal' +import { CategorizedStepContent } from '/app/molecules/InterventionModal' import type { RecoveryContentProps } from '../types' export function FailedStepNextStep({ @@ -9,6 +8,7 @@ export function FailedStepNextStep({ commandsAfterFailedCommand, protocolAnalysis, robotType, + allRunDefs, }: Pick< RecoveryContentProps, | 'stepCounts' @@ -16,6 +16,7 @@ export function FailedStepNextStep({ | 'commandsAfterFailedCommand' | 'protocolAnalysis' | 'robotType' + | 'allRunDefs' >): JSX.Element { const { t } = useTranslation('error_recovery') const failedCommandByAnalysis = failedCommand?.byAnalysis ?? null @@ -45,6 +46,7 @@ export function FailedStepNextStep({ return ( ( + HOLDING_LABWARE_OPTIONS[0] + ) + const { t } = useTranslation(['error_recovery', 'shared']) + + const handleNoOption = (): void => { + switch (selectedRecoveryOption) { + case MANUAL_MOVE_AND_SKIP.ROUTE: + void proceedToRouteAndStep( + MANUAL_MOVE_AND_SKIP.ROUTE, + MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE + ) + break + case MANUAL_REPLACE_AND_RETRY.ROUTE: + void proceedToRouteAndStep( + MANUAL_REPLACE_AND_RETRY.ROUTE, + MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE + ) + break + default: { + console.error('Unexpected recovery option for gripper routing.') + void proceedToRouteAndStep(OPTION_SELECTION.ROUTE) + } + } + } + + const primaryOnClick = (): void => { + switch (selectedOption) { + case 'yes': + void proceedNextStep() + break + case 'no': + handleNoOption() + break + default: { + console.error('Unhandled primary onClick given gripper option') + } + } + } + + return ( + + + + {t('first_is_gripper_holding_labware')} + + + + + + + + + + + ) +} + +interface GripperHoldingOptionsProps { + t: TFunction + selectedOption: HoldingLabwareOption + setSelectedOption: (option: HoldingLabwareOption) => void +} + +function ODDGripperHoldingLwOptions({ + t, + selectedOption, + setSelectedOption, +}: GripperHoldingOptionsProps): JSX.Element { + return ( + + {HOLDING_LABWARE_OPTIONS.map(option => { + const optionCopy = getCopyFromOption(option, t) + return ( + { + setSelectedOption(option) + }} + isSelected={option === selectedOption} + radioButtonType="large" + /> + ) + })} + + ) +} + +function DesktopGripperHoldingLwOptions({ + t, + selectedOption, + setSelectedOption, +}: GripperHoldingOptionsProps): JSX.Element { + return ( + { + setSelectedOption(e.currentTarget.value as HoldingLabwareOption) + }} + value={selectedOption} + options={HOLDING_LABWARE_OPTIONS.map( + option => + ({ + value: option, + children: ( + + {getCopyFromOption(option, t)} + + ), + } as const) + )} + /> + ) +} + +export function getCopyFromOption( + option: HoldingLabwareOption, + t: TFunction +): string { + switch (option) { + case 'yes': + return i18n.format(t('shared:yes'), 'capitalize') + case 'no': + return i18n.format(t('shared:no'), 'capitalize') + default: + console.error('Unhandled copy option.') + return 'UNHANDLED OPTION' + } +} + +const CONTAINER_STYLE = css` + grid-gap: ${SPACING.spacing16}; + flex-direction: ${DIRECTION_COLUMN}; +` + +const ODD_OPTIONS_STLYE = css` + flex-direction: ${DIRECTION_COLUMN}; + width: 100%; + gap: ${SPACING.spacing8}; +` + +const RADIO_GAP = ` + gap: ${SPACING.spacing4}; +` diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/GripperReleaseLabware.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/GripperReleaseLabware.tsx new file mode 100644 index 00000000000..2c433775a83 --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/shared/GripperReleaseLabware.tsx @@ -0,0 +1,100 @@ +import { css } from 'styled-components' +import { useTranslation } from 'react-i18next' + +import { + SPACING, + DIRECTION_COLUMN, + RESPONSIVENESS, + Flex, + StyledText, + JUSTIFY_CENTER, +} from '@opentrons/components' + +import { TwoColumn } from '/app/molecules/InterventionModal' +import { InlineNotification } from '/app/atoms/InlineNotification' +import { RECOVERY_MAP } from '/app/organisms/ErrorRecoveryFlows/constants' +import { RecoveryFooterButtons } from './RecoveryFooterButtons' +import { RecoverySingleColumnContentWrapper } from './RecoveryContentWrapper' + +import gripperReleaseAnimation from '/app/assets/videos/error-recovery/Gripper_Release.webm' + +import type { JSX } from 'react' +import type { RecoveryContentProps } from '../types' + +export function GripperReleaseLabware({ + routeUpdateActions, +}: RecoveryContentProps): JSX.Element { + const { handleMotionRouting, goBackPrevStep } = routeUpdateActions + const { t } = useTranslation('error_recovery') + + const buildPrimaryOnClick = (): void => { + // Because the actual release command is executed on a delay, the execution behavior is deferred to the + // motion route. + void handleMotionRouting(true, RECOVERY_MAP.ROBOT_RELEASING_LABWARE.ROUTE) + } + + return ( + + + + + {t('release_labware_from_gripper')} + + + {t('take_any_necessary_precautions')} + + + + + + + + + + ) +} + +const LEFT_COL_COPY_STYLE = css` + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing16}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + gap: ${SPACING.spacing24}; + } +` + +const ANIMATION_CONTAINER_STYLE = css` + justify-content: ${JUSTIFY_CENTER}; + max-height: 13.25rem; +` + +const ANIMATION_STYLE = css` + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + width: 27rem; + height: 20.25rem; + } +` diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/LeftColumnLabwareInfo.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/LeftColumnLabwareInfo.tsx index 9363bdf7e50..87cdac57255 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/LeftColumnLabwareInfo.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/LeftColumnLabwareInfo.tsx @@ -1,7 +1,6 @@ -import * as React from 'react' - -import { InterventionContent } from '../../../molecules/InterventionModal/InterventionContent' +import { InterventionContent } from '/app/molecules/InterventionModal/InterventionContent' +import type * as React from 'react' import type { RecoveryContentProps } from '../types' type LeftColumnLabwareInfoProps = RecoveryContentProps & { @@ -20,22 +19,17 @@ export function LeftColumnLabwareInfo({ }: LeftColumnLabwareInfoProps): JSX.Element | null { const { failedLabwareName, - failedLabware, failedLabwareNickname, + failedLabwareLocations, } = failedLabwareUtils + const { displayNameNewLoc, displayNameCurrentLoc } = failedLabwareLocations - const buildLabwareLocationSlotName = (): string => { - const location = failedLabware?.location - if ( - location != null && - typeof location === 'object' && - 'slotName' in location - ) { - return location.slotName - } else { - return '' - } - } + const buildNewLocation = (): React.ComponentProps< + typeof InterventionContent + >['infoProps']['newLocationProps'] => + displayNameNewLoc != null + ? { deckLabel: displayNameNewLoc.toUpperCase() } + : undefined return ( { + setIsLoading(true) + void resumeRecovery() + } + + const buildSubtext = (): string => { + switch (selectedRecoveryOption) { + case RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE: + case RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE: + case RECOVERY_MAP.HOME_AND_RETRY.ROUTE: + return t('door_open_robot_home') + default: { + console.error( + `Unhandled special-cased door open subtext on route ${selectedRecoveryOption}.` + ) + return t('close_the_robot_door') + } + } + } + + const handleHomeAllAndRoute = ( + route: RecoveryRoute, + step?: RouteStep + ): void => { + void handleMotionRouting(true, RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE) + .then(() => recoveryCommands.homeAll()) + .finally(() => handleMotionRouting(false)) + .then(() => proceedToRouteAndStep(route, step)) + } + + const handleHomeExceptPlungersAndRoute = ( + route: RecoveryRoute, + step?: RouteStep + ): void => { + void handleMotionRouting(true, RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE) + .then(() => recoveryCommands.homeExceptPlungers()) + .finally(() => handleMotionRouting(false)) + .then(() => proceedToRouteAndStep(route, step)) + } + + useEffect(() => { + if (!doorStatusUtils.isDoorOpen) { + switch (selectedRecoveryOption) { + case RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE: + handleHomeExceptPlungersAndRoute( + RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE, + RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE + ) + break + case RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE: + handleHomeExceptPlungersAndRoute( + RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE, + RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE + ) + break + case RECOVERY_MAP.HOME_AND_RETRY.ROUTE: + handleHomeAllAndRoute( + RECOVERY_MAP.HOME_AND_RETRY.ROUTE, + RECOVERY_MAP.HOME_AND_RETRY.STEPS.CONFIRM_RETRY + ) + break + default: { + console.error( + `Unhandled special-cased door open on route ${selectedRecoveryOption}.` + ) + void proceedToRouteAndStep(RECOVERY_MAP.OPTION_SELECTION.ROUTE) + } + } + } + }, [doorStatusUtils.isDoorOpen]) + + return ( + + + + + + {t('close_robot_door')} + + + {buildSubtext()} + + + + + + + + ) +} + +const TEXT_STYLE = css` + flex-direction: ${DIRECTION_COLUMN}; + grid-gap: ${SPACING.spacing8}; + align-items: ${ALIGN_CENTER}; + text-align: ${TEXT_ALIGN_CENTER}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + grid-gap: ${SPACING.spacing4}; + } +` + +const ICON_STYLE = css` + height: ${SPACING.spacing40}; + width: ${SPACING.spacing40}; + color: ${COLORS.yellow50}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + height: ${SPACING.spacing60}; + width: ${SPACING.spacing60}; + } +` diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/RecoveryFooterButtons.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/RecoveryFooterButtons.tsx index c9042cfc55f..1db3f7ddaba 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/RecoveryFooterButtons.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/RecoveryFooterButtons.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' @@ -16,7 +15,7 @@ import { RESPONSIVENESS, } from '@opentrons/components' -import { SmallButton, TextOnlyButton } from '../../../atoms/buttons' +import { SmallButton, TextOnlyButton } from '/app/atoms/buttons' interface RecoveryFooterButtonProps { primaryBtnOnClick: () => void @@ -45,8 +44,10 @@ export function RecoveryFooterButtons( alignItems={ALIGN_FLEX_END} gridGap={SPACING.spacing8} > - {!props.secondaryAsTertiary && } - + + {!props.secondaryAsTertiary && } + + ) } @@ -58,12 +59,10 @@ function RecoveryGoBackButton({ const showGoBackBtn = secondaryBtnOnClick != null const { t } = useTranslation('error_recovery') return showGoBackBtn ? ( - - - + ) : ( ) diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/RecoveryInterventionModal.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/RecoveryInterventionModal.tsx index e044d46054f..82974023805 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/RecoveryInterventionModal.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/RecoveryInterventionModal.tsx @@ -1,13 +1,19 @@ -import * as React from 'react' +import type * as React from 'react' import { createPortal } from 'react-dom' import { css } from 'styled-components' -import { Flex, RESPONSIVENESS, SPACING } from '@opentrons/components' +import { + Flex, + OVERFLOW_AUTO, + OVERFLOW_HIDDEN, + RESPONSIVENESS, + SPACING, +} from '@opentrons/components' -import { InterventionModal } from '../../../molecules/InterventionModal' -import { getModalPortalEl, getTopPortalEl } from '../../../App/portal' +import { InterventionModal } from '/app/molecules/InterventionModal' +import { getModalPortalEl, getTopPortalEl } from '/app/App/portal' -import type { ModalType } from '../../../molecules/InterventionModal' +import type { ModalType } from '/app/molecules/InterventionModal' import type { DesktopSizeType } from '../types' export type RecoveryInterventionModalProps = Omit< @@ -50,12 +56,24 @@ export function RecoveryInterventionModal({ const SMALL_MODAL_STYLE = css` height: 22rem; padding: ${SPACING.spacing32}; + width: 100%; + overflow-y: ${OVERFLOW_AUTO}; @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { padding: ${SPACING.spacing32}; height: 100%; + overflow: ${OVERFLOW_HIDDEN}; + user-select: none; } ` const LARGE_MODAL_STYLE = css` height: 26.75rem; + width: 100%; + overflow-y: ${OVERFLOW_AUTO}; + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + height: 100%; + overflow: ${OVERFLOW_HIDDEN}; + user-select: none; + } ` diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/RecoveryRadioGroup.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/RecoveryRadioGroup.tsx index 571f0b0333a..d200123d0b3 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/RecoveryRadioGroup.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/RecoveryRadioGroup.tsx @@ -1,6 +1,4 @@ -import * as React from 'react' - -import type { ChangeEventHandler } from 'react' +import type { ChangeEventHandler, ReactNode, ComponentProps } from 'react' import { RadioGroup, SPACING, Flex } from '@opentrons/components' // note: this typescript stuff is so that e.currentTarget.value in the ChangeEventHandler @@ -12,12 +10,12 @@ export interface Target extends Omit { export type Options = Array<{ value: T - children: React.ReactNode + children: ReactNode }> export interface RecoveryRadioGroupProps extends Omit< - React.ComponentProps, + ComponentProps, 'labelTextClassName' | 'options' | 'onchange' > { options: Options diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/ReplaceTips.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/ReplaceTips.tsx deleted file mode 100644 index 9d7f8adfcd7..00000000000 --- a/app/src/organisms/ErrorRecoveryFlows/shared/ReplaceTips.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import * as React from 'react' - -import { Flex } from '@opentrons/components' - -import { useTranslation } from 'react-i18next' -import { RecoverySingleColumnContentWrapper } from './RecoveryContentWrapper' -import { TwoColumn, DeckMapContent } from '../../../molecules/InterventionModal' -import { RecoveryFooterButtons } from './RecoveryFooterButtons' -import { LeftColumnLabwareInfo } from './LeftColumnLabwareInfo' -import { getSlotNameAndLwLocFrom } from '../hooks/useDeckMapUtils' - -import type { RecoveryContentProps } from '../types' - -export function ReplaceTips(props: RecoveryContentProps): JSX.Element | null { - const { - routeUpdateActions, - failedPipetteInfo, - failedLabwareUtils, - deckMapUtils, - } = props - const { relevantWellName, failedLabware } = failedLabwareUtils - const { proceedNextStep } = routeUpdateActions - const { t } = useTranslation('error_recovery') - - const primaryOnClick = (): void => { - void proceedNextStep() - } - const [slot] = getSlotNameAndLwLocFrom(failedLabware?.location ?? null, false) - const buildTitle = (): string => { - if (failedPipetteInfo?.data.channels === 96) { - return t('replace_with_new_tip_rack', { slot }) - } else { - return t('replace_used_tips_in_rack_location', { - location: relevantWellName, - slot, - }) - } - } - - return ( - - - - - - - - - - ) -} diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/RetryStepInfo.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/RetryStepInfo.tsx new file mode 100644 index 00000000000..fd7a1dbaf5f --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/shared/RetryStepInfo.tsx @@ -0,0 +1,61 @@ +import { Trans, useTranslation } from 'react-i18next' + +import { LegacyStyledText } from '@opentrons/components' + +import { ERROR_KINDS, RECOVERY_MAP } from '../constants' +import { TwoColTextAndFailedStepNextStep } from './TwoColTextAndFailedStepNextStep' + +import type { RecoveryContentProps } from '../types' + +export function RetryStepInfo( + props: RecoveryContentProps & { secondaryBtnOnClickOverride?: () => void } +): JSX.Element { + const { routeUpdateActions, recoveryCommands, errorKind } = props + const { ROBOT_RETRYING_STEP } = RECOVERY_MAP + const { t } = useTranslation('error_recovery') + + const { retryFailedCommand, resumeRun } = recoveryCommands + const { handleMotionRouting } = routeUpdateActions + + const primaryBtnOnClick = (): Promise => { + return handleMotionRouting(true, ROBOT_RETRYING_STEP.ROUTE) + .then(() => retryFailedCommand()) + .then(() => { + resumeRun() + }) + } + + const buildBodyCopyKey = (): string => { + switch (errorKind) { + case ERROR_KINDS.TIP_NOT_DETECTED: + return 'take_necessary_actions_failed_pickup' + case ERROR_KINDS.GRIPPER_ERROR: + return 'robot_retry_failed_lw_movement' + case ERROR_KINDS.TIP_DROP_FAILED: + return 'take_necessary_actions_failed_tip_drop' + default: + return 'take_necessary_actions' + } + } + const buildBodyText = (): JSX.Element => { + return ( + , + }} + /> + ) + } + + return ( + + ) +} diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/SelectTips.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/SelectTips.tsx index d4012670c27..13dcfaa73c6 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/SelectTips.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/SelectTips.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { RECOVERY_MAP } from '../constants' import { RecoverySingleColumnContentWrapper } from './RecoveryContentWrapper' -import { TwoColumn } from '../../../molecules/InterventionModal' +import { TwoColumn } from '/app/molecules/InterventionModal' import { RecoveryFooterButtons } from './RecoveryFooterButtons' import { LeftColumnLabwareInfo } from './LeftColumnLabwareInfo' import { TipSelectionModal } from './TipSelectionModal' @@ -13,23 +13,25 @@ import type { RecoveryContentProps } from '../types' export function SelectTips(props: RecoveryContentProps): JSX.Element | null { const { - failedPipetteInfo, routeUpdateActions, recoveryCommands, isOnDevice, + failedLabwareUtils, + failedPipetteUtils, } = props const { ROBOT_PICKING_UP_TIPS } = RECOVERY_MAP const { pickUpTips } = recoveryCommands + const { isPartialTipConfigValid, failedPipetteInfo } = failedPipetteUtils const { goBackPrevStep, - setRobotInMotion, + handleMotionRouting, proceedNextStep, } = routeUpdateActions const { t } = useTranslation('error_recovery') - const [showTipSelectModal, setShowTipSelectModal] = React.useState(false) + const [showTipSelectModal, setShowTipSelectModal] = useState(false) const primaryBtnOnClick = (): Promise => { - return setRobotInMotion(true, ROBOT_PICKING_UP_TIPS.ROUTE) + return handleMotionRouting(true, ROBOT_PICKING_UP_TIPS.ROUTE) .then(() => pickUpTips()) .then(() => proceedNextStep()) } @@ -43,7 +45,8 @@ export function SelectTips(props: RecoveryContentProps): JSX.Element | null { tertiaryBtnOnClick?: () => void tertiaryBtnText?: string } => { - if (isOnDevice) { + // If partial tip config, do not give users the option to select tip location. + if (isOnDevice && !isPartialTipConfigValid) { return { tertiaryBtnDisabled: failedPipetteInfo?.data.channels === 96, tertiaryBtnOnClick: toggleModal, @@ -54,12 +57,17 @@ export function SelectTips(props: RecoveryContentProps): JSX.Element | null { } } + const buildBannerText = (): string => + isPartialTipConfigValid + ? t('replace_tips_and_select_loc_partial_tip') + : t('replace_tips_and_select_location') + return ( <> {showTipSelectModal && ( )} @@ -69,12 +77,16 @@ export function SelectTips(props: RecoveryContentProps): JSX.Element | null { {...props} title={t('select_tip_pickup_location')} type="location" - bannerText={t('replace_tips_and_select_location')} + bannerText={buildBannerText()} + /> + - => { + return handleMotionRouting(true, ROBOT_SKIPPING_STEP.ROUTE).then(() => { + if (selectedRecoveryOption === MANUAL_MOVE_AND_SKIP.ROUTE) { + void moveLabwareWithoutPause().then(() => { + skipFailedCommand() + }) + } else { + skipFailedCommand() + } + }) + } + + const buildTitleCopy = (): string => { + switch (selectedRecoveryOption) { + case SKIP_STEP_WITH_SAME_TIPS.ROUTE: + return t('skip_to_next_step_same_tips') + case SKIP_STEP_WITH_NEW_TIPS.ROUTE: + return t('skip_to_next_step_new_tips') + case IGNORE_AND_SKIP.ROUTE: + case MANUAL_MOVE_AND_SKIP.ROUTE: + return t('skip_to_next_step') + default: + console.error( + 'Unhandled skip step copy. Add the recovery option explicitly.' + ) + return 'UNEXPECTED STEP' + } + } + + const buildBodyCopyKey = (): string => { + switch (selectedRecoveryOption) { + case IGNORE_AND_SKIP.ROUTE: + return 'inspect_the_robot' + case SKIP_STEP_WITH_SAME_TIPS.ROUTE: + case SKIP_STEP_WITH_NEW_TIPS.ROUTE: + return 'failed_dispense_step_not_completed' + case MANUAL_MOVE_AND_SKIP.ROUTE: + return 'robot_not_attempt_to_move_lw' + default: + console.error( + 'Unhandled skip step copy. Add the recovery option explicitly.' + ) + return 'UNEXPECTED STEP' + } + } + const buildBodyText = (): JSX.Element => { + return ( + , + }} + /> + ) + } + + return ( + + ) +} diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/StepInfo.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/StepInfo.tsx index e866155c354..bbc12ce0429 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/StepInfo.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/StepInfo.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { Flex, DISPLAY_INLINE, StyledText } from '@opentrons/components' -import { CommandText } from '../../../molecules/Command' +import { CommandText } from '/app/molecules/Command' import type { StyleProps } from '@opentrons/components' import type { RecoveryContentProps } from '../types' @@ -14,6 +14,7 @@ interface StepInfoProps extends StyleProps { failedCommand: RecoveryContentProps['failedCommand'] robotType: RecoveryContentProps['robotType'] protocolAnalysis: RecoveryContentProps['protocolAnalysis'] + allRunDefs: RecoveryContentProps['allRunDefs'] desktopStyle?: React.ComponentProps['desktopStyle'] oddStyle?: React.ComponentProps['oddStyle'] } @@ -25,10 +26,11 @@ export function StepInfo({ failedCommand, robotType, protocolAnalysis, + allRunDefs, ...styleProps }: StepInfoProps): JSX.Element { const { t } = useTranslation('error_recovery') - const { currentStepNumber, totalStepCount } = stepCounts + const { currentStepNumber, totalStepCount, hasRunDiverged } = stepCounts const currentCopy = currentStepNumber ?? '?' const totalCopy = totalStepCount ?? '?' @@ -36,6 +38,11 @@ export function StepInfo({ const desktopStyleDefaulted = desktopStyle ?? 'bodyDefaultRegular' const oddStyleDefaulted = oddStyle ?? 'bodyTextRegular' + const buildAtStepCopy = (): string => + hasRunDiverged + ? `${t('at_step')}: N/A` + : `${t('at_step')} ${currentCopy}/${totalCopy}: ` + return ( - {`${t('at_step')} ${currentCopy}/${totalCopy}: `} + {buildAtStepCopy()} {failedCommand?.byAnalysis != null && protocolAnalysis != null ? ( ) : null} diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/TipSelection.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/TipSelection.tsx index 2b9084cb0f4..f22c7fb268b 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/TipSelection.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/TipSelection.tsx @@ -1,8 +1,7 @@ -import * as React from 'react' - import { WellSelection } from '../../WellSelection' import type { WellGroup } from '@opentrons/components' +import type { NozzleLayoutDetails } from '@opentrons/shared-data' import type { RecoveryContentProps } from '../types' export type TipSelectionProps = RecoveryContentProps & { @@ -10,25 +9,21 @@ export type TipSelectionProps = RecoveryContentProps & { } export function TipSelection(props: TipSelectionProps): JSX.Element { - const { failedLabwareUtils, failedPipetteInfo, allowTipSelection } = props - + const { failedLabwareUtils, failedPipetteUtils, allowTipSelection } = props const { tipSelectorDef, selectedTipLocations, selectTips, deselectTips, } = failedLabwareUtils + const { relevantActiveNozzleLayout, failedPipetteInfo } = failedPipetteUtils const onSelectTips = (tipGroup: WellGroup): void => { - if (allowTipSelection) { - selectTips(tipGroup) - } + selectTips(tipGroup) } const onDeselectTips = (locations: string[]): void => { - if (allowTipSelection) { - deselectTips(locations) - } + deselectTips(locations) } return ( @@ -38,6 +33,21 @@ export function TipSelection(props: TipSelectionProps): JSX.Element { selectedPrimaryWells={selectedTipLocations as WellGroup} selectWells={onSelectTips} channels={failedPipetteInfo?.data.channels ?? 1} + pipetteNozzleDetails={buildNozzleLayoutDetails( + relevantActiveNozzleLayout + )} + allowSelect={allowTipSelection} /> ) } + +function buildNozzleLayoutDetails( + relevantActiveNozzleLayout: TipSelectionProps['failedPipetteUtils']['relevantActiveNozzleLayout'] +): NozzleLayoutDetails | undefined { + return relevantActiveNozzleLayout != null + ? { + activeNozzleCount: relevantActiveNozzleLayout.activeNozzles.length, + nozzleConfig: relevantActiveNozzleLayout.config, + } + : undefined +} diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/TipSelectionModal.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/TipSelectionModal.tsx index ea8f911ede5..897a7d1f44c 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/TipSelectionModal.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/TipSelectionModal.tsx @@ -1,12 +1,11 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom' -import { OddModal } from '../../../molecules/OddModal' -import { getTopPortalEl } from '../../../App/portal' +import { OddModal } from '/app/molecules/OddModal' +import { getTopPortalEl } from '/app/App/portal' import { TipSelection } from './TipSelection' -import type { OddModalHeaderBaseProps } from '../../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' import type { TipSelectionProps } from './TipSelection' type TipSelectionModalProps = TipSelectionProps & { @@ -16,17 +15,23 @@ type TipSelectionModalProps = TipSelectionProps & { export function TipSelectionModal( props: TipSelectionModalProps ): JSX.Element | null { - const { toggleModal } = props + const { isOnDevice, toggleModal, failedLabwareUtils } = props + const { areTipsSelected } = failedLabwareUtils const { t } = useTranslation('error_recovery') + // If users end up in a state in which they deselect all wells, don't let them escape this modal. const modalHeader: OddModalHeaderBaseProps = { title: t('change_tip_pickup_location'), - hasExitIcon: true, + hasExitIcon: areTipsSelected, } - if (props.isOnDevice) { + if (isOnDevice) { return createPortal( - + , getTopPortalEl() diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/TwoColLwInfoAndDeck.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/TwoColLwInfoAndDeck.tsx new file mode 100644 index 00000000000..9bf8f12bc22 --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/shared/TwoColLwInfoAndDeck.tsx @@ -0,0 +1,199 @@ +import { + Flex, + MoveLabwareOnDeck, + COLORS, + Module, + LabwareRender, +} from '@opentrons/components' +import { inferModuleOrientationFromXCoordinate } from '@opentrons/shared-data' + +import { useTranslation } from 'react-i18next' +import { RecoverySingleColumnContentWrapper } from './RecoveryContentWrapper' +import { TwoColumn, DeckMapContent } from '/app/molecules/InterventionModal' +import { RecoveryFooterButtons } from './RecoveryFooterButtons' +import { LeftColumnLabwareInfo } from './LeftColumnLabwareInfo' +import { RECOVERY_MAP } from '../constants' + +import type * as React from 'react' +import type { RecoveryContentProps } from '../types' +import type { InterventionContent } from '/app/molecules/InterventionModal/InterventionContent' + +export function TwoColLwInfoAndDeck( + props: RecoveryContentProps +): JSX.Element | null { + const { + routeUpdateActions, + failedPipetteUtils, + failedLabwareUtils, + deckMapUtils, + currentRecoveryOptionUtils, + isOnDevice, + } = props + const { + RETRY_NEW_TIPS, + SKIP_STEP_WITH_NEW_TIPS, + MANUAL_MOVE_AND_SKIP, + MANUAL_REPLACE_AND_RETRY, + HOME_AND_RETRY, + } = RECOVERY_MAP + const { selectedRecoveryOption } = currentRecoveryOptionUtils + const { relevantWellName, failedLabware } = failedLabwareUtils + const { proceedNextStep } = routeUpdateActions + const { failedPipetteInfo, isPartialTipConfigValid } = failedPipetteUtils + const { t } = useTranslation('error_recovery') + + const primaryOnClick = (): void => { + void proceedNextStep() + } + + const { + displayNameCurrentLoc: slot, + } = failedLabwareUtils.failedLabwareLocations + + const buildTitle = (): string => { + switch (selectedRecoveryOption) { + case MANUAL_MOVE_AND_SKIP.ROUTE: + return t('manually_move_lw_on_deck') + case MANUAL_REPLACE_AND_RETRY.ROUTE: + return t('manually_replace_lw_on_deck') + case HOME_AND_RETRY.ROUTE: + case RETRY_NEW_TIPS.ROUTE: + case SKIP_STEP_WITH_NEW_TIPS.ROUTE: { + // Only special case the "full" 96-channel nozzle config. + if ( + failedPipetteInfo?.data.channels === 96 && + !isPartialTipConfigValid + ) { + return t('replace_with_new_tip_rack', { slot }) + } else { + return t('replace_used_tips_in_rack_location', { + location: relevantWellName, + slot, + }) + } + } + default: + console.error( + `TwoColLwInfoAndDeck: Unexpected recovery option: ${selectedRecoveryOption}. Handle retry step copy explicitly.` + ) + return 'UNEXPECTED RECOVERY OPTION' + } + } + + const buildBannerText = (): string => { + switch (selectedRecoveryOption) { + case MANUAL_MOVE_AND_SKIP.ROUTE: + case MANUAL_REPLACE_AND_RETRY.ROUTE: + return t('ensure_lw_is_accurately_placed') + case RETRY_NEW_TIPS.ROUTE: + case SKIP_STEP_WITH_NEW_TIPS.ROUTE: + case HOME_AND_RETRY.ROUTE: { + return isPartialTipConfigValid + ? t('replace_tips_and_select_loc_partial_tip') + : t('replace_tips_and_select_location') + } + default: + console.error( + `TwoColLwInfoAndDeck:buildBannerText: Unexpected recovery option ${selectedRecoveryOption}. Handle retry step copy explicitly.` + ) + return 'UNEXPECTED RECOVERY OPTION' + } + } + + const buildType = (): React.ComponentProps< + typeof InterventionContent + >['infoProps']['type'] => { + switch (selectedRecoveryOption) { + case MANUAL_MOVE_AND_SKIP.ROUTE: + return 'location-arrow-location' + default: + case MANUAL_REPLACE_AND_RETRY.ROUTE: + return 'location' + } + } + + // TODO(jh, 10-22-24): Componentize an app-only abstraction above MoveLabwareOnDeck. EXEC-788. + const buildDeckView = (): JSX.Element => { + switch (selectedRecoveryOption) { + case MANUAL_MOVE_AND_SKIP.ROUTE: { + const { newLoc, currentLoc } = failedLabwareUtils.failedLabwareLocations + const { + movedLabwareDef, + moduleRenderInfo, + labwareRenderInfo, + ...restUtils + } = deckMapUtils + + const failedLwId = failedLabware?.id ?? '' + + const isValidDeck = + currentLoc != null && newLoc != null && movedLabwareDef != null + + return isValidDeck ? ( + + {moduleRenderInfo.map( + ({ + x, + y, + moduleId, + moduleDef, + nestedLabwareDef, + nestedLabwareId, + }) => ( + + {nestedLabwareDef != null && + nestedLabwareId !== failedLwId ? ( + + ) : null} + + ) + )} + {labwareRenderInfo + .filter(l => l.labwareId !== failedLwId) + .map(({ x, y, labwareDef, labwareId }) => ( + + {labwareDef != null && labwareId !== failedLwId ? ( + + ) : null} + + ))} + + } + /> + ) : ( + + ) + } + default: + return + } + } + + return ( + + + + {buildDeckView()} + + + + ) +} diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/TwoColTextAndFailedStepNextStep.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/TwoColTextAndFailedStepNextStep.tsx index b98bb68e2c5..21dbed8e639 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/TwoColTextAndFailedStepNextStep.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/TwoColTextAndFailedStepNextStep.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { css } from 'styled-components' import { DIRECTION_COLUMN, @@ -9,7 +8,7 @@ import { } from '@opentrons/components' import { RecoverySingleColumnContentWrapper } from './RecoveryContentWrapper' -import { TwoColumn } from '../../../molecules/InterventionModal' +import { TwoColumn } from '/app/molecules/InterventionModal' import { RecoveryFooterButtons } from './RecoveryFooterButtons' import { FailedStepNextStep } from './FailedStepNextStep' @@ -23,6 +22,9 @@ type TwoColTextAndFailedStepNextStepProps = RecoveryContentProps & { secondaryBtnOnClickOverride?: () => void } +// TODO(jh, 10-01-24): In some views we inject copy and in others, we derive it in the view based on +// selected recovery option. + /** * Left Column: Title + body text * Right column: FailedStepNextStep @@ -69,7 +71,11 @@ export function TwoColTextAndFailedStepNextStep( leftColBodyText )} - + {!props.stepCounts.hasRunDiverged ? ( + + ) : ( + + )} ({ ...vi.importActual('react-dom'), createPortal: vi.fn((element, container) => element), })) -vi.mock('../../../../molecules/OddModal', () => ({ +vi.mock('/app/molecules/OddModal', () => ({ OddModal: vi.fn(({ children }) =>
    {children}
    ), })) -vi.mock('../../../../atoms/InlineNotification') +vi.mock('/app/atoms/InlineNotification') vi.mock('../StepInfo') describe('useErrorDetailsModal', () => { @@ -92,7 +95,7 @@ describe('ErrorDetailsModal', () => { }) IS_ODD.forEach(isOnDevice => { - it('renders the OverpressureBanner when the error kind is an overpressure error', () => { + it('renders an inline banner when the error kind is an overpressure error', () => { props.failedCommand = { ...props.failedCommand, byRunRecord: { @@ -106,9 +109,23 @@ describe('ErrorDetailsModal', () => { screen.getByText('MOCK_INLINE_NOTIFICATION') }) - it('does not render the OverpressureBanner when the error kind is not an overpressure error', () => { + it('renders an inline banner when the error kind is a tip not detected error', () => { + props.failedCommand = { + ...props.failedCommand, + byRunRecord: { + ...props.failedCommand?.byRunRecord, + commandType: 'pickUpTip', + error: { isDefined: true, errorType: 'tipPhysicallyMissing' }, + }, + } as any render({ ...props, isOnDevice }) + screen.getByText('MOCK_INLINE_NOTIFICATION') + }) + + it('does not render a banner when the error kind is not explicitly handled', () => { + render({ ...props, isOnDevice, failedCommand: {} as any }) + expect(screen.queryByText('MOCK_INLINE_NOTIFICATION')).toBeNull() }) }) @@ -137,3 +154,73 @@ describe('OverpressureBanner', () => { ) }) }) + +describe('TipNotDetectedBanner', () => { + beforeEach(() => { + vi.mocked(InlineNotification).mockReturnValue( +
    MOCK_INLINE_NOTIFICATION
    + ) + }) + + it('renders the InlineNotification', () => { + renderWithProviders(, { + i18nInstance: i18n, + }) + expect(vi.mocked(InlineNotification)).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'alert', + heading: + 'Tip presence errors are usually caused by improperly placed labware or inaccurate labware offsets', + message: + ' If the issue persists, cancel the run and perform Labware Position Check', + }), + {} + ) + }) +}) + +describe('GripperErrorBanner', () => { + beforeEach(() => { + vi.mocked(InlineNotification).mockReturnValue( +
    MOCK_INLINE_NOTIFICATION
    + ) + }) + + it('renders the InlineNotification', () => { + renderWithProviders(, { + i18nInstance: i18n, + }) + expect(vi.mocked(InlineNotification)).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'alert', + heading: + 'Gripper errors occur when the gripper stalls or collides with another object on the deck and are usually caused by improperly placed labware or inaccurate labware offsets', + message: + ' If the issue persists, cancel the run and rerun gripper calibration', + }), + {} + ) + }) +}) + +describe('StallErrorBanner', () => { + beforeEach(() => { + vi.mocked(InlineNotification).mockReturnValue( +
    MOCK_INLINE_NOTIFICATION
    + ) + }) + it('renders the InlineNotification', () => { + renderWithProviders(, { + i18nInstance: i18n, + }) + expect(vi.mocked(InlineNotification)).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'alert', + heading: + "A stall or collision is detected when the robot's motors are blocked", + message: 'The robot must return to its home position before proceeding', + }), + {} + ) + }) +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/GripperIsHoldingLabware.test.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/GripperIsHoldingLabware.test.tsx new file mode 100644 index 00000000000..95af112fa58 --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/GripperIsHoldingLabware.test.tsx @@ -0,0 +1,130 @@ +import { expect, describe, it, vi, beforeEach } from 'vitest' +import { screen, fireEvent, waitFor } from '@testing-library/react' +import capitalize from 'lodash/capitalize' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockRecoveryContentProps } from '/app/organisms/ErrorRecoveryFlows/__fixtures__' +import { clickButtonLabeled } from '/app/organisms/ErrorRecoveryFlows/__tests__/util' +import { + GripperIsHoldingLabware, + HOLDING_LABWARE_OPTIONS, +} from '../GripperIsHoldingLabware' + +import type { Mock } from 'vitest' +import { RECOVERY_MAP } from '/app/organisms/ErrorRecoveryFlows/constants' + +const render = ( + props: React.ComponentProps +) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] +} + +let mockProceedToRouteAndStep: Mock +let mockProceedNextStep: Mock + +describe('GripperIsHoldingLabware', () => { + let props: React.ComponentProps + beforeEach(() => { + mockProceedToRouteAndStep = vi.fn(() => Promise.resolve()) + mockProceedNextStep = vi.fn(() => Promise.resolve()) + + props = { + ...mockRecoveryContentProps, + routeUpdateActions: { + proceedToRouteAndStep: mockProceedToRouteAndStep, + proceedNextStep: mockProceedNextStep, + } as any, + } + }) + + it('renders appropriate title copy', () => { + render(props) + + screen.getByText('First, is the gripper holding labware?') + }) + + HOLDING_LABWARE_OPTIONS.forEach(option => { + it(`renders appropriate copy for the ${option} option`, () => { + render(props) + + expect(screen.getAllByText(capitalize(option))[0]) + }) + }) + ;[true, false].forEach(isOnDevice => { + it(`renders options when isOnDevice is ${isOnDevice}`, () => { + render(props) + + expect(screen.getAllByText(capitalize(HOLDING_LABWARE_OPTIONS[0]))[0]) + }) + }) + + it('proceeds to next step when the yes option is clicked', async () => { + render(props) + + fireEvent.click(screen.getAllByLabelText('Yes')[0]) + clickButtonLabeled('Continue') + + await waitFor(() => { + expect(mockProceedNextStep).toHaveBeenCalled() + }) + }) + + it(`proceeds to the correct step when the no option is clicked for ${RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE}`, async () => { + render({ + ...props, + currentRecoveryOptionUtils: { + selectedRecoveryOption: RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE, + } as any, + }) + + fireEvent.click(screen.getAllByLabelText('No')[0]) + clickButtonLabeled('Continue') + + await waitFor(() => { + expect(mockProceedToRouteAndStep).toHaveBeenCalledWith( + RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE, + RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE + ) + }) + }) + + it(`proceeds to the correct step when the no option is clicked for ${RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE}`, async () => { + render({ + ...props, + currentRecoveryOptionUtils: { + selectedRecoveryOption: RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE, + } as any, + }) + + fireEvent.click(screen.getAllByLabelText('No')[0]) + clickButtonLabeled('Continue') + + await waitFor(() => { + expect(mockProceedToRouteAndStep).toHaveBeenCalledWith( + RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE, + RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE + ) + }) + }) + + it('proceeds to the a fallback route when an unhandled route is called', async () => { + render({ + ...props, + currentRecoveryOptionUtils: { + selectedRecoveryOption: RECOVERY_MAP.IGNORE_AND_SKIP.ROUTE, + } as any, + }) + + fireEvent.click(screen.getAllByLabelText('No')[0]) + clickButtonLabeled('Continue') + + await waitFor(() => { + expect(mockProceedToRouteAndStep).toHaveBeenCalledWith( + RECOVERY_MAP.OPTION_SELECTION.ROUTE + ) + }) + }) +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/GripperReleaseLabware.test.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/GripperReleaseLabware.test.tsx new file mode 100644 index 00000000000..3bdd9f97819 --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/GripperReleaseLabware.test.tsx @@ -0,0 +1,65 @@ +import { describe, expect, it, vi, beforeEach } from 'vitest' +import { screen } from '@testing-library/react' + +import { GripperReleaseLabware } from '../GripperReleaseLabware' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockRecoveryContentProps } from '/app/organisms/ErrorRecoveryFlows/__fixtures__' +import { clickButtonLabeled } from '/app/organisms/ErrorRecoveryFlows/__tests__/util' + +import type { Mock } from 'vitest' + +vi.mock('/app/assets/videos/error-recovery/Gripper_Release.webm', () => ({ + default: 'mocked-animation-path.webm', +})) + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] +} + +describe('GripperReleaseLabware', () => { + let props: React.ComponentProps + let mockHandleMotionRouting: Mock + + beforeEach(() => { + mockHandleMotionRouting = vi.fn(() => Promise.resolve()) + + props = { + ...mockRecoveryContentProps, + routeUpdateActions: { + handleMotionRouting: mockHandleMotionRouting, + goBackPrevStep: vi.fn(), + } as any, + } + }) + + it('renders appropriate copy', () => { + render(props) + + screen.getByText('Release labware from gripper') + screen.getByText( + 'Take any necessary precautions before positioning yourself to stabilize or catch the labware. Once confirmed, a countdown will begin before the gripper releases.' + ) + screen.getByText('The labware will be released from its current height.') + }) + + it('clicking the primary button has correct behavior', () => { + render(props) + + clickButtonLabeled('Release') + + expect(mockHandleMotionRouting).toHaveBeenCalled() + }) + + it('renders gripper animation', () => { + render(props) + + screen.getByRole('presentation', { hidden: true }) + expect(screen.getByTestId('gripper-animation')).toHaveAttribute( + 'src', + 'mocked-animation-path.webm' + ) + }) +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/LeftColumnLabwareInfo.test.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/LeftColumnLabwareInfo.test.tsx index 30aae62a9ca..f38e1e06922 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/LeftColumnLabwareInfo.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/LeftColumnLabwareInfo.test.tsx @@ -1,18 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, beforeEach, expect, vi } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { mockRecoveryContentProps } from '../../__fixtures__' -import { i18n } from '../../../../i18n' +import { i18n } from '/app/i18n' import { LeftColumnLabwareInfo } from '../LeftColumnLabwareInfo' -import { InterventionInfo } from '../../../../molecules/InterventionModal/InterventionContent/InterventionInfo' -import { InlineNotification } from '../../../../atoms/InlineNotification' +import { InterventionContent } from '/app/molecules/InterventionModal/InterventionContent' -vi.mock( - '../../../../molecules/InterventionModal/InterventionContent/InterventionInfo' -) -vi.mock('../../../../atoms/InlineNotification') +vi.mock('/app/molecules/InterventionModal/InterventionContent') const render = (props: React.ComponentProps) => { return renderWithProviders(, { @@ -29,60 +25,86 @@ describe('LeftColumnLabwareInfo', () => { title: 'MOCK_TITLE', failedLabwareUtils: { failedLabwareName: 'MOCK_LW_NAME', - failedLabware: { - location: { slotName: 'A1' }, + failedLabwareNickname: 'MOCK_LW_NICKNAME', + failedLabwareLocations: { + displayNameCurrentLoc: 'slot A1', + displayNameNewLoc: 'slot B2', }, } as any, type: 'location', bannerText: 'MOCK_BANNER_TEXT', } - vi.mocked(InterventionInfo).mockReturnValue(
    MOCK_MOVE
    ) - vi.mocked(InlineNotification).mockReturnValue( -
    MOCK_INLINE_NOTIFICATION
    + vi.mocked(InterventionContent).mockReturnValue( +
    MOCK_INTERVENTION_CONTENT
    ) }) - it('renders the title, InterventionInfo component, and InlineNotification when bannerText is provided', () => { + it('renders the InterventionContent component with correct props', () => { render(props) - screen.getByText('MOCK_TITLE') - screen.getByText('MOCK_MOVE') - expect(vi.mocked(InterventionInfo)).toHaveBeenCalledWith( + screen.getByText('MOCK_INTERVENTION_CONTENT') + expect(vi.mocked(InterventionContent)).toHaveBeenCalledWith( expect.objectContaining({ - type: 'location', - labwareName: 'MOCK_LW_NAME', - currentLocationProps: { deckLabel: 'A1' }, + headline: 'MOCK_TITLE', + infoProps: { + type: 'location', + labwareName: 'MOCK_LW_NAME', + labwareNickname: 'MOCK_LW_NICKNAME', + currentLocationProps: { deckLabel: 'SLOT A1' }, + newLocationProps: { deckLabel: 'SLOT B2' }, + }, + notificationProps: { + type: 'alert', + heading: 'MOCK_BANNER_TEXT', + }, }), {} ) - screen.getByText('MOCK_INLINE_NOTIFICATION') - expect(vi.mocked(InlineNotification)).toHaveBeenCalledWith( + }) + + it('does not include notificationProps when bannerText is not provided', () => { + props.bannerText = undefined + render(props) + + expect(vi.mocked(InterventionContent)).toHaveBeenCalledWith( expect.objectContaining({ - type: 'alert', - heading: 'MOCK_BANNER_TEXT', + notificationProps: undefined, }), {} ) }) - it('does not render the InlineNotification when bannerText is not provided', () => { - props.bannerText = undefined + it('does not include newLocationProps when newLoc is not provided', () => { + props.failedLabwareUtils.failedLabwareLocations.displayNameNewLoc = null render(props) - screen.getByText('MOCK_TITLE') - screen.getByText('MOCK_MOVE') - expect(screen.queryByText('MOCK_INLINE_NOTIFICATION')).toBeNull() + expect(vi.mocked(InterventionContent)).toHaveBeenCalledWith( + expect.objectContaining({ + infoProps: expect.not.objectContaining({ + newLocationProps: expect.anything(), + }), + }), + {} + ) }) - it('returns an empty string for slotName when failedLabware location is not an object with slotName', () => { - // @ts-expect-error yeah this is ok - props.failedLabwareUtils.failedLabware.location = 'offDeck' + it('converts location labels to uppercase', () => { + props.failedLabwareUtils.failedLabwareLocations = { + displayNameCurrentLoc: 'slot A1', + displayNameNewLoc: 'slot B2', + newLoc: {} as any, + currentLoc: {} as any, + } + render(props) - expect(vi.mocked(InterventionInfo)).toHaveBeenCalledWith( + expect(vi.mocked(InterventionContent)).toHaveBeenCalledWith( expect.objectContaining({ - currentLocationProps: { deckLabel: '' }, + infoProps: expect.objectContaining({ + currentLocationProps: { deckLabel: 'SLOT A1' }, + newLocationProps: { deckLabel: 'SLOT B2' }, + }), }), {} ) diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/RecoveryDoorOpenSpecial.test.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/RecoveryDoorOpenSpecial.test.tsx new file mode 100644 index 00000000000..5cc4ae74b87 --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/RecoveryDoorOpenSpecial.test.tsx @@ -0,0 +1,137 @@ +import { describe, it, vi, expect, beforeEach } from 'vitest' +import { screen, waitFor } from '@testing-library/react' + +import { + RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR, + RUN_STATUS_AWAITING_RECOVERY, +} from '@opentrons/api-client' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { RecoveryDoorOpenSpecial } from '../RecoveryDoorOpenSpecial' +import { RECOVERY_MAP } from '../../constants' + +import type * as React from 'react' +import { clickButtonLabeled } from '/app/organisms/ErrorRecoveryFlows/__tests__/util' + +describe('RecoveryDoorOpenSpecial', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + currentRecoveryOptionUtils: { + selectedRecoveryOption: RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE, + }, + runStatus: RUN_STATUS_AWAITING_RECOVERY, + recoveryActionMutationUtils: { + resumeRecovery: vi.fn(), + }, + routeUpdateActions: { + proceedToRouteAndStep: vi.fn(), + handleMotionRouting: vi.fn().mockImplementation(_ => Promise.resolve()), + }, + doorStatusUtils: { + isDoorOpen: true, + }, + recoveryCommands: { + homeExceptPlungers: vi.fn().mockResolvedValue(undefined), + }, + } as any + }) + + const render = ( + props: React.ComponentProps + ) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] + } + + it('calls resumeRecovery when primary button is clicked', async () => { + render(props) + + clickButtonLabeled('Continue') + + expect(props.recoveryActionMutationUtils.resumeRecovery).toHaveBeenCalled() + }) + + it(`disables primary button when runStatus is ${RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR}`, () => { + props.runStatus = RUN_STATUS_AWAITING_RECOVERY_BLOCKED_BY_OPEN_DOOR + render(props) + + const btn = screen.getAllByRole('button', { name: 'Continue' })[0] + + expect(btn).toBeDisabled() + }) + + it(`renders correct copy for ${RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE}`, () => { + props.currentRecoveryOptionUtils.selectedRecoveryOption = + RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE + render(props) + screen.getByText('Close the robot door') + screen.getByText( + 'The robot needs to safely move to its home location before you manually move the labware.' + ) + }) + + it.each([ + { + recoveryOption: RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE, + expectedRoute: RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE, + expectedStep: RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.STEPS.MANUAL_REPLACE, + }, + { + recoveryOption: RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE, + expectedRoute: RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE, + expectedStep: RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.STEPS.MANUAL_MOVE, + }, + ])( + 'executes correct chain of actions when door is closed for $recoveryOption', + async ({ recoveryOption, expectedRoute, expectedStep }) => { + props.currentRecoveryOptionUtils.selectedRecoveryOption = recoveryOption + props.doorStatusUtils.isDoorOpen = false + + render(props) + + await waitFor(() => { + expect( + props.routeUpdateActions.handleMotionRouting + ).toHaveBeenCalledWith(true, RECOVERY_MAP.ROBOT_IN_MOTION.ROUTE) + }) + + await waitFor(() => { + expect(props.recoveryCommands.homeExceptPlungers).toHaveBeenCalled() + }) + + await waitFor(() => { + expect( + props.routeUpdateActions.handleMotionRouting + ).toHaveBeenCalledWith(false) + }) + + await waitFor(() => { + expect( + props.routeUpdateActions.proceedToRouteAndStep + ).toHaveBeenCalledWith(expectedRoute, expectedStep) + }) + } + ) + + it('renders default subtext for an unhandled recovery option', () => { + props.currentRecoveryOptionUtils.selectedRecoveryOption = 'UNHANDLED_OPTION' as any + render(props) + screen.getByText('Close the robot door') + screen.getByText( + 'Close the robot door, and then resume the recovery action.' + ) + }) + + it('calls proceedToRouteAndStep with OPTION_SELECTION for unhandled recovery option when door is closed', () => { + props.currentRecoveryOptionUtils.selectedRecoveryOption = 'UNHANDLED_OPTION' as any + props.doorStatusUtils.isDoorOpen = false + render(props) + expect(props.routeUpdateActions.proceedToRouteAndStep).toHaveBeenCalledWith( + RECOVERY_MAP.OPTION_SELECTION.ROUTE + ) + }) +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/RecoveryFooterButtons.test.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/RecoveryFooterButtons.test.tsx index 3e4e9045c1a..6381d2b579a 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/RecoveryFooterButtons.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/RecoveryFooterButtons.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, describe, it, expect, beforeEach } from 'vitest' import { screen, fireEvent } from '@testing-library/react' import { COLORS } from '@opentrons/components' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { RecoveryFooterButtons } from '../RecoveryFooterButtons' import type { Mock } from 'vitest' diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/RetryStepInfo.test.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/RetryStepInfo.test.tsx new file mode 100644 index 00000000000..94f77910657 --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/RetryStepInfo.test.tsx @@ -0,0 +1,114 @@ +import { describe, it, vi, expect, beforeEach } from 'vitest' +import { screen, waitFor } from '@testing-library/react' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { RetryStepInfo } from '../RetryStepInfo' +import { ERROR_KINDS, RECOVERY_MAP } from '../../constants' +import { clickButtonLabeled } from '/app/organisms/ErrorRecoveryFlows/__tests__/util' + +import type { Mock } from 'vitest' + +describe('RetryStepInfo', () => { + let props: React.ComponentProps + let mockHandleMotionRouting: Mock + let mockRetryFailedCommand: Mock + let mockResumeRun: Mock + + beforeEach(() => { + mockHandleMotionRouting = vi.fn(() => Promise.resolve()) + mockRetryFailedCommand = vi.fn(() => Promise.resolve()) + mockResumeRun = vi.fn() + + props = { + routeUpdateActions: { + handleMotionRouting: mockHandleMotionRouting, + } as any, + recoveryCommands: { + retryFailedCommand: mockRetryFailedCommand, + resumeRun: mockResumeRun, + } as any, + errorKind: ERROR_KINDS.GENERAL_ERROR, + stepCounts: { hasRunDiverged: false }, + } as any + }) + + const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] + } + + it('calls correct functions when primary button is clicked', async () => { + render(props) + + clickButtonLabeled('Retry now') + + await waitFor(() => { + expect(mockHandleMotionRouting).toHaveBeenCalledWith( + true, + RECOVERY_MAP.ROBOT_RETRYING_STEP.ROUTE + ) + }) + await waitFor(() => { + expect(mockRetryFailedCommand).toHaveBeenCalled() + }) + await waitFor(() => { + expect(mockResumeRun).toHaveBeenCalled() + }) + await waitFor(() => { + expect(mockHandleMotionRouting.mock.invocationCallOrder[0]).toBeLessThan( + mockRetryFailedCommand.mock.invocationCallOrder[0] + ) + }) + await waitFor(() => { + expect(mockRetryFailedCommand.mock.invocationCallOrder[0]).toBeLessThan( + mockResumeRun.mock.invocationCallOrder[0] + ) + }) + }) + + it(`renders correct body text for ${ERROR_KINDS.TIP_NOT_DETECTED} error`, () => { + props.errorKind = ERROR_KINDS.TIP_NOT_DETECTED + + render(props) + + screen.getByText( + 'First, take any necessary actions to prepare the robot to retry the failed tip pickup.' + ) + screen.getByText('Then, close the robot door before proceeding.') + }) + + it(`renders correct body text for ${ERROR_KINDS.TIP_DROP_FAILED} error`, () => { + props.errorKind = ERROR_KINDS.TIP_DROP_FAILED + + render(props) + + screen.getByText( + 'First, take any necessary actions to prepare the robot to retry the failed tip drop.' + ) + screen.getByText('Then, close the robot door before proceeding.') + }) + + it(`renders correct body text for ${ERROR_KINDS.GRIPPER_ERROR}`, () => { + props.errorKind = ERROR_KINDS.GRIPPER_ERROR + + render(props) + + screen.getByText( + 'The robot will retry the failed labware movement step from where the labware was replaced on the deck.' + ) + screen.getByText('Close the robot door before proceeding.') + }) + + it('renders default body text for other error kinds', () => { + props.errorKind = ERROR_KINDS.GENERAL_ERROR + + render(props) + + screen.getByText( + 'First, take any necessary actions to prepare the robot to retry the failed step.' + ) + screen.getByText('Then, close the robot door before proceeding.') + }) +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/SelectTips.test.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/SelectTips.test.tsx index 15afe841639..08db6269c4d 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/SelectTips.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/SelectTips.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, expect, beforeEach } from 'vitest' import { screen, fireEvent, waitFor } from '@testing-library/react' import { mockRecoveryContentProps } from '../../__fixtures__' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { SelectTips } from '../SelectTips' import { RECOVERY_MAP } from '../../constants' import { TipSelectionModal } from '../TipSelectionModal' @@ -13,7 +13,6 @@ import type { Mock } from 'vitest' vi.mock('../TipSelectionModal') vi.mock('../TipSelection') -vi.mock('../LeftColumnLabwareInfo') const render = (props: React.ComponentProps) => { return renderWithProviders(, { @@ -24,13 +23,13 @@ const render = (props: React.ComponentProps) => { describe('SelectTips', () => { let props: React.ComponentProps let mockGoBackPrevStep: Mock - let mockSetRobotInMotion: Mock + let mockhandleMotionRouting: Mock let mockProceedNextStep: Mock let mockPickUpTips: Mock beforeEach(() => { mockGoBackPrevStep = vi.fn() - mockSetRobotInMotion = vi.fn(() => Promise.resolve()) + mockhandleMotionRouting = vi.fn(() => Promise.resolve()) mockProceedNextStep = vi.fn() mockPickUpTips = vi.fn(() => Promise.resolve()) @@ -38,15 +37,25 @@ describe('SelectTips', () => { ...mockRecoveryContentProps, routeUpdateActions: { goBackPrevStep: mockGoBackPrevStep, - setRobotInMotion: mockSetRobotInMotion, + handleMotionRouting: mockhandleMotionRouting, proceedNextStep: mockProceedNextStep, } as any, recoveryCommands: { pickUpTips: mockPickUpTips, } as any, - failedPipetteInfo: { - data: { - channels: 8, + failedPipetteUtils: { + failedPipetteInfo: { + data: { + channels: 8, + }, + } as any, + } as any, + failedLabwareUtils: { + selectedTipLocations: { A1: null }, + areTipsSelected: true, + failedLabwareLocations: { + displayNameNewLoc: null, + displayNameCurrentLoc: 'A1', }, } as any, } @@ -65,7 +74,7 @@ describe('SelectTips', () => { }) it('calls the correct routeUpdateActions and recoveryCommands in the correct order when the primary button is clicked', async () => { - const setRobotInMotionMock = vi.fn(() => Promise.resolve()) + const handleMotionRoutingMock = vi.fn(() => Promise.resolve()) const pickUpTipsMock = vi.fn(() => Promise.resolve()) const proceedNextStepMock = vi.fn() @@ -74,7 +83,7 @@ describe('SelectTips', () => { } as any const mockRouteUpdateActions = { - setRobotInMotion: setRobotInMotionMock, + handleMotionRouting: handleMotionRoutingMock, proceedNextStep: proceedNextStepMock, } as any @@ -88,10 +97,10 @@ describe('SelectTips', () => { fireEvent.click(primaryBtn) await waitFor(() => { - expect(setRobotInMotionMock).toHaveBeenCalledTimes(1) + expect(handleMotionRoutingMock).toHaveBeenCalledTimes(1) }) await waitFor(() => { - expect(setRobotInMotionMock).toHaveBeenCalledWith( + expect(handleMotionRoutingMock).toHaveBeenCalledWith( true, RECOVERY_MAP.ROBOT_PICKING_UP_TIPS.ROUTE ) @@ -103,7 +112,7 @@ describe('SelectTips', () => { expect(proceedNextStepMock).toHaveBeenCalledTimes(1) }) - expect(setRobotInMotionMock.mock.invocationCallOrder[0]).toBeLessThan( + expect(handleMotionRoutingMock.mock.invocationCallOrder[0]).toBeLessThan( pickUpTipsMock.mock.invocationCallOrder[0] ) @@ -122,13 +131,23 @@ describe('SelectTips', () => { expect(mockGoBackPrevStep).toHaveBeenCalled() }) + it('renders expected banner text', () => { + render(props) + + screen.getByText( + "It's best to replace tips and select the last location used for tip pickup." + ) + }) + it('disables the tertiary button when the pipette has 96 channels', () => { props = { ...props, - failedPipetteInfo: { - data: { - channels: 96, - }, + failedPipetteUtils: { + failedPipetteInfo: { + data: { + channels: 96, + }, + } as any, } as any, } render(props) @@ -138,4 +157,71 @@ describe('SelectTips', () => { }) expect(tertiaryBtn[0]).toBeDisabled() }) + + it('disables the primary button if tips are not selected', () => { + props = { + ...props, + failedLabwareUtils: { + selectedTipLocations: null, + areTipsSelected: false, + failedLabwareLocations: { + displayNameNewLoc: null, + displayNameCurrentLoc: '', + }, + } as any, + } + + render(props) + + const primaryBtn = screen.getAllByRole('button', { + name: 'Pick up tips', + }) + + expect(primaryBtn[0]).toBeDisabled() + }) + + it('does not render the tertiary button if a partial tip config is used', () => { + const mockFailedPipetteUtils = { + failedPipetteInfo: { + data: { + channels: 8, + }, + } as any, + isPartialTipConfigValid: true, + relevantActiveNozzleLayout: { + activeNozzles: ['H1', 'G1'], + startingNozzle: 'A1', + config: 'column', + }, + } as any + + render({ ...props, failedPipetteUtils: mockFailedPipetteUtils }) + + const tertiaryBtn = screen.queryByRole('button', { + name: 'Change location', + }) + expect(tertiaryBtn).not.toBeInTheDocument() + }) + + it('renders alternative banner text if partial tip config is used', () => { + const mockFailedPipetteUtils = { + failedPipetteInfo: { + data: { + channels: 8, + }, + } as any, + isPartialTipConfigValid: true, + relevantActiveNozzleLayout: { + activeNozzles: ['H1', 'G1'], + startingNozzle: 'A1', + config: 'column', + }, + } as any + + render({ ...props, failedPipetteUtils: mockFailedPipetteUtils }) + + screen.getByText( + 'Replace tips and select the last location used for partial tip pickup.' + ) + }) }) diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/SkipStepInfo.test.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/SkipStepInfo.test.tsx new file mode 100644 index 00000000000..28ef4177648 --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/SkipStepInfo.test.tsx @@ -0,0 +1,116 @@ +import { describe, it, vi, expect, beforeEach } from 'vitest' +import { screen, waitFor } from '@testing-library/react' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { SkipStepInfo } from '../SkipStepInfo' +import { RECOVERY_MAP } from '../../constants' +import { clickButtonLabeled } from '/app/organisms/ErrorRecoveryFlows/__tests__/util' + +import type { Mock } from 'vitest' + +describe('SkipStepInfo', () => { + let props: React.ComponentProps + let mockHandleMotionRouting: Mock + let mockSkipFailedCommand: Mock + + beforeEach(() => { + mockHandleMotionRouting = vi.fn(() => Promise.resolve()) + mockSkipFailedCommand = vi.fn(() => Promise.resolve()) + + props = { + routeUpdateActions: { + handleMotionRouting: mockHandleMotionRouting, + } as any, + recoveryCommands: { + skipFailedCommand: mockSkipFailedCommand, + } as any, + currentRecoveryOptionUtils: { + selectedRecoveryOption: RECOVERY_MAP.SKIP_STEP_WITH_SAME_TIPS.ROUTE, + } as any, + stepCounts: { hasRunDiverged: false }, + } as any + }) + + const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] + } + + it('calls correct functions when primary button is clicked', async () => { + render(props) + + clickButtonLabeled('Continue run now') + + await waitFor(() => { + expect(mockHandleMotionRouting).toHaveBeenCalledWith( + true, + RECOVERY_MAP.ROBOT_SKIPPING_STEP.ROUTE + ) + }) + await waitFor(() => { + expect(mockSkipFailedCommand).toHaveBeenCalled() + }) + await waitFor(() => { + expect(mockHandleMotionRouting.mock.invocationCallOrder[0]).toBeLessThan( + mockSkipFailedCommand.mock.invocationCallOrder[0] + ) + }) + }) + + it(`renders correct title and body text for ${RECOVERY_MAP.IGNORE_AND_SKIP.ROUTE}`, () => { + props.currentRecoveryOptionUtils.selectedRecoveryOption = + RECOVERY_MAP.IGNORE_AND_SKIP.ROUTE + render(props) + + screen.getByText('Skip to next step') + screen.getByText( + "First, inspect the robot to ensure it's prepared to continue the run from the next step." + ) + screen.getByText('Then, close the robot door before proceeding.') + }) + + it(`renders correct title and body text for ${RECOVERY_MAP.SKIP_STEP_WITH_SAME_TIPS.ROUTE}`, () => { + props.currentRecoveryOptionUtils.selectedRecoveryOption = + RECOVERY_MAP.SKIP_STEP_WITH_SAME_TIPS.ROUTE + render(props) + + screen.getByText('Skip to next step with same tips') + screen.getByText( + 'The failed dispense step will not be completed. The run will continue from the next step with the attached tips.' + ) + screen.getByText('Close the robot door before proceeding.') + }) + + it(`renders correct title and body text for ${RECOVERY_MAP.SKIP_STEP_WITH_NEW_TIPS.ROUTE}`, () => { + props.currentRecoveryOptionUtils.selectedRecoveryOption = + RECOVERY_MAP.SKIP_STEP_WITH_NEW_TIPS.ROUTE + render(props) + + screen.getByText('Skip to next step with new tips') + screen.getByText( + 'The failed dispense step will not be completed. The run will continue from the next step with the attached tips.' + ) + screen.getByText('Close the robot door before proceeding.') + }) + + it(`renders correct title and body text for ${RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE}`, () => { + props.currentRecoveryOptionUtils.selectedRecoveryOption = + RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE + render(props) + + screen.getByText('Skip to next step') + screen.getByText( + 'The robot will not attempt to move the labware again. The run will continue from the next step.' + ) + screen.getByText('Close the robot door before proceeding.') + }) + + it('renders error message for unexpected recovery option', () => { + props.currentRecoveryOptionUtils.selectedRecoveryOption = 'UNEXPECTED_ROUTE' as any + render(props) + + expect(screen.getAllByText('UNEXPECTED STEP')[0]).toBeInTheDocument() + }) +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/StepInfo.test.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/StepInfo.test.tsx index 9396fcf8f7d..d6fbb50c345 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/StepInfo.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/StepInfo.test.tsx @@ -1,14 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, expect, beforeEach, vi } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { mockRecoveryContentProps, mockFailedCommand } from '../../__fixtures__' -import { i18n } from '../../../../i18n' +import { i18n } from '/app/i18n' import { StepInfo } from '../StepInfo' -import { CommandText } from '../../../../molecules/Command' +import { CommandText } from '/app/molecules/Command' -vi.mock('../../../../molecules/Command') +vi.mock('/app/molecules/Command') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/TipSelection.test.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/TipSelection.test.tsx index d9d99b08b49..9df7f8e02ec 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/TipSelection.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/TipSelection.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, expect, beforeEach } from 'vitest' import { screen } from '@testing-library/react' import { mockRecoveryContentProps } from '../../__fixtures__' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { TipSelection } from '../TipSelection' import { WellSelection } from '../../../WellSelection' @@ -22,7 +22,9 @@ describe('TipSelection', () => { props = { ...mockRecoveryContentProps, allowTipSelection: true, - failedPipetteInfo: { data: { channels: 8 } } as any, + failedPipetteUtils: { + failedPipetteInfo: { data: { channels: 8 } } as any, + } as any, } vi.mocked(WellSelection).mockReturnValue(
    MOCK WELL SELECTION
    ) @@ -36,7 +38,10 @@ describe('TipSelection', () => { expect.objectContaining({ definition: props.failedLabwareUtils.tipSelectorDef, selectedPrimaryWells: props.failedLabwareUtils.selectedTipLocations, - channels: props.failedPipetteInfo?.data.channels ?? 1, + channels: + props.failedPipetteUtils.failedPipetteInfo?.data.channels ?? 1, + allowSelect: props.allowTipSelection, + pipetteNozzleDetails: undefined, }), {} ) diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/TipSelectionModal.test.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/TipSelectionModal.test.tsx index 608c870324c..fed5a44d4ce 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/TipSelectionModal.test.tsx +++ b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/TipSelectionModal.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' -import { describe, it, vi, beforeEach } from 'vitest' +import type * as React from 'react' +import { describe, it, vi, beforeEach, expect } from 'vitest' import { screen } from '@testing-library/react' import { mockRecoveryContentProps } from '../../__fixtures__' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { TipSelectionModal } from '../TipSelectionModal' import { TipSelection } from '../TipSelection' @@ -24,6 +24,10 @@ describe('TipSelectionModal', () => { ...mockRecoveryContentProps, allowTipSelection: true, toggleModal: vi.fn(), + failedLabwareUtils: { + selectedTipLocations: { A1: null }, + areTipsSelected: true, + } as any, } vi.mocked(TipSelection).mockReturnValue(
    MOCK TIP SELECTION
    ) @@ -39,5 +43,17 @@ describe('TipSelectionModal', () => { render(props) screen.getByText('MOCK TIP SELECTION') + screen.getByLabelText('closeIcon') + }) + + it('prevents from users from exiting the modal if no well(s) are selected', () => { + props = { + ...props, + failedLabwareUtils: { areTipsSelected: false } as any, + } + + render(props) + + expect(screen.queryByLabelText('closeIcon')).not.toBeInTheDocument() }) }) diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/TwoColLwInfoAndDeck.test.tsx b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/TwoColLwInfoAndDeck.test.tsx new file mode 100644 index 00000000000..0629038f800 --- /dev/null +++ b/app/src/organisms/ErrorRecoveryFlows/shared/__tests__/TwoColLwInfoAndDeck.test.tsx @@ -0,0 +1,184 @@ +import { describe, it, vi, expect, beforeEach } from 'vitest' +import { screen } from '@testing-library/react' + +import { MoveLabwareOnDeck } from '@opentrons/components' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { clickButtonLabeled } from '/app/organisms/ErrorRecoveryFlows/__tests__/util' +import { TwoColLwInfoAndDeck } from '../TwoColLwInfoAndDeck' +import { RECOVERY_MAP } from '../../constants' +import { LeftColumnLabwareInfo } from '../LeftColumnLabwareInfo' +import { getSlotNameAndLwLocFrom } from '../../hooks/useDeckMapUtils' + +import type * as React from 'react' +import type { Mock } from 'vitest' + +vi.mock('@opentrons/components', async () => { + const actual = await vi.importActual('@opentrons/components') + return { + ...actual, + MoveLabwareOnDeck: vi.fn(), + } +}) +vi.mock('../LeftColumnLabwareInfo') +vi.mock('../../hooks/useDeckMapUtils') + +let mockProceedNextStep: Mock + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + })[0] +} + +describe('TwoColLwInfoAndDeck', () => { + let props: React.ComponentProps + + beforeEach(() => { + mockProceedNextStep = vi.fn() + + props = { + routeUpdateActions: { + proceedNextStep: mockProceedNextStep, + }, + failedPipetteUtils: { + failedPipetteInfo: { data: { channels: 8 } }, + isPartialTipConfigValid: false, + }, + failedLabwareUtils: { + relevantWellName: 'A1', + failedLabware: { location: 'C1' }, + failedLabwareLocations: { + newLoc: {}, + currentLoc: {}, + displayNameCurrentLoc: 'Slot C1', + }, + }, + deckMapUtils: { + movedLabwareDef: {}, + moduleRenderInfo: [], + labwareRenderInfo: [], + }, + currentRecoveryOptionUtils: { + selectedRecoveryOption: RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE, + }, + isOnDevice: true, + } as any + + vi.mocked(LeftColumnLabwareInfo).mockReturnValue( + vi.fn(() =>
    ) as any + ) + vi.mocked(getSlotNameAndLwLocFrom).mockReturnValue(['C1'] as any) + }) + + it('calls proceedNextStep when primary button is clicked', () => { + render(props) + clickButtonLabeled('Continue') + expect(mockProceedNextStep).toHaveBeenCalled() + }) + + it(`passes correct title to LeftColumnLabwareInfo for ${RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE}`, () => { + render(props) + expect(vi.mocked(LeftColumnLabwareInfo)).toHaveBeenCalledWith( + expect.objectContaining({ + title: 'Manually move labware on deck', + type: 'location-arrow-location', + bannerText: + 'Ensure labware is accurately placed in the slot to prevent further errors.', + }), + expect.anything() + ) + }) + + it(`passes correct title to LeftColumnLabwareInfo for ${RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE}`, () => { + props.currentRecoveryOptionUtils.selectedRecoveryOption = + RECOVERY_MAP.MANUAL_REPLACE_AND_RETRY.ROUTE + render(props) + expect(vi.mocked(LeftColumnLabwareInfo)).toHaveBeenCalledWith( + expect.objectContaining({ + title: 'Manually replace labware on deck', + type: 'location', + bannerText: + 'Ensure labware is accurately placed in the slot to prevent further errors.', + }), + expect.anything() + ) + }) + + it(`passes correct title to LeftColumnLabwareInfo for ${RECOVERY_MAP.RETRY_NEW_TIPS.ROUTE}`, () => { + props.currentRecoveryOptionUtils.selectedRecoveryOption = + RECOVERY_MAP.RETRY_NEW_TIPS.ROUTE + render(props) + expect(vi.mocked(LeftColumnLabwareInfo)).toHaveBeenCalledWith( + expect.objectContaining({ + title: 'Replace used tips in rack location A1 in Slot C1', + type: 'location', + bannerText: + "It's best to replace tips and select the last location used for tip pickup.", + }), + expect.anything() + ) + }) + + it('passes correct title to LeftColumnLabwareInfo for 96-channel pipette', () => { + props.currentRecoveryOptionUtils.selectedRecoveryOption = + RECOVERY_MAP.RETRY_NEW_TIPS.ROUTE + // @ts-expect-error This is a test. It's always defined. + props.failedPipetteUtils.failedPipetteInfo.data.channels = 96 + render(props) + expect(vi.mocked(LeftColumnLabwareInfo)).toHaveBeenCalledWith( + expect.objectContaining({ + title: 'Replace with new tip rack in Slot C1', + type: 'location', + bannerText: + "It's best to replace tips and select the last location used for tip pickup.", + }), + expect.anything() + ) + }) + + it('passes correct title to LeftColumnLabwareInfo for partial tip config', () => { + props.currentRecoveryOptionUtils.selectedRecoveryOption = + RECOVERY_MAP.RETRY_NEW_TIPS.ROUTE + props.failedPipetteUtils.isPartialTipConfigValid = true + render(props) + expect(vi.mocked(LeftColumnLabwareInfo)).toHaveBeenCalledWith( + expect.objectContaining({ + bannerText: + 'Replace tips and select the last location used for partial tip pickup.', + }), + expect.anything() + ) + }) + + it(`renders a move labware on deck view if the selected recovery option is ${RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE} and props are valid`, () => { + vi.mocked(MoveLabwareOnDeck).mockReturnValue( +
    MOCK_MOVE_LW_ON_DECK
    + ) + + props.currentRecoveryOptionUtils.selectedRecoveryOption = + RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE + render(props) + + screen.getByText('MOCK_MOVE_LW_ON_DECK') + }) + + it(`does not render a move labware on deck view if the selected recovery option is ${RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE} and props are invalid`, () => { + vi.mocked(MoveLabwareOnDeck).mockReturnValue( +
    MOCK_MOVE_LW_ON_DECK
    + ) + + props.currentRecoveryOptionUtils.selectedRecoveryOption = + RECOVERY_MAP.MANUAL_MOVE_AND_SKIP.ROUTE + props.deckMapUtils = { + movedLabwareDef: null, + moduleRenderInfo: null, + labwareRenderInfo: null, + } as any + + render(props) + + expect(screen.queryByText('MOCK_MOVE_LW_ON_DECK')).not.toBeInTheDocument() + }) +}) diff --git a/app/src/organisms/ErrorRecoveryFlows/shared/index.ts b/app/src/organisms/ErrorRecoveryFlows/shared/index.ts index 955058e5311..0c9df1d9553 100644 --- a/app/src/organisms/ErrorRecoveryFlows/shared/index.ts +++ b/app/src/organisms/ErrorRecoveryFlows/shared/index.ts @@ -4,7 +4,7 @@ export { RecoveryTwoColumnContentWrapper, RecoveryODDOneDesktopTwoColumnContentWrapper, } from './RecoveryContentWrapper' -export { ReplaceTips } from './ReplaceTips' +export { TwoColLwInfoAndDeck } from './TwoColLwInfoAndDeck' export { SelectTips } from './SelectTips' export { TwoColTextAndFailedStepNextStep } from './TwoColTextAndFailedStepNextStep' export { LeftColumnLabwareInfo } from './LeftColumnLabwareInfo' @@ -15,5 +15,10 @@ export { useErrorDetailsModal, ErrorDetailsModal } from './ErrorDetailsModal' export { RecoveryInterventionModal } from './RecoveryInterventionModal' export { FailedStepNextStep } from './FailedStepNextStep' export { RecoveryRadioGroup } from './RecoveryRadioGroup' +export { GripperReleaseLabware } from './GripperReleaseLabware' +export { RetryStepInfo } from './RetryStepInfo' +export { SkipStepInfo } from './SkipStepInfo' +export { GripperIsHoldingLabware } from './GripperIsHoldingLabware' +export { RecoveryDoorOpenSpecial } from './RecoveryDoorOpenSpecial' export type { RecoveryInterventionModalProps } from './RecoveryInterventionModal' diff --git a/app/src/organisms/ErrorRecoveryFlows/types.ts b/app/src/organisms/ErrorRecoveryFlows/types.ts index f3df4a86c50..588477aa813 100644 --- a/app/src/organisms/ErrorRecoveryFlows/types.ts +++ b/app/src/organisms/ErrorRecoveryFlows/types.ts @@ -1,14 +1,46 @@ import type { RunCommandSummary } from '@opentrons/api-client' -import type { ERROR_KINDS, RECOVERY_MAP, INVALID } from './constants' +import type { ERROR_KINDS, INVALID, RECOVERY_MAP } from './constants' import type { ErrorRecoveryWizardProps } from './ErrorRecoveryWizard' import type { DropTipFlowsRoute, DropTipFlowsStep, -} from '../DropTipWizardFlows/types' +} from '/app/organisms/DropTipWizardFlows/types' +/** + * Misc Recovery Types + */ export type FailedCommand = RunCommandSummary +export type ErrorKind = typeof ERROR_KINDS[keyof typeof ERROR_KINDS] + +/** + * Prop Specific Types + */ +export type RecoveryContentProps = ErrorRecoveryWizardProps & { + errorKind: ErrorKind + isOnDevice: boolean +} + +/** + * Drop Tip Specific Types + */ +export type ValidDropTipSubRoutes = DropTipFlowsRoute +export type ValidDropTipSubSteps = DropTipFlowsStep +export interface ValidDropTipSubMap { + route: ValidDropTipSubRoutes + step: ValidDropTipSubSteps | null +} + +/** + * Recovery Map Types + */ +export interface IRecoveryMap { + route: RecoveryRoute + step: RouteStep +} +type RecoveryMap = typeof RECOVERY_MAP export type InvalidStep = typeof INVALID -export type RecoveryRoute = typeof RECOVERY_MAP[keyof typeof RECOVERY_MAP]['ROUTE'] +export type RouteKey = RecoveryMap[keyof RecoveryMap]['ROUTE'] +export type RecoveryRoute = RouteKey export type RobotMovingRoute = | typeof RECOVERY_MAP['ROBOT_IN_MOTION']['ROUTE'] | typeof RECOVERY_MAP['ROBOT_RESUMING']['ROUTE'] @@ -16,63 +48,36 @@ export type RobotMovingRoute = | typeof RECOVERY_MAP['ROBOT_CANCELING']['ROUTE'] | typeof RECOVERY_MAP['ROBOT_PICKING_UP_TIPS']['ROUTE'] | typeof RECOVERY_MAP['ROBOT_SKIPPING_STEP']['ROUTE'] -export type ErrorKind = typeof ERROR_KINDS[keyof typeof ERROR_KINDS] + | typeof RECOVERY_MAP['ROBOT_RELEASING_LABWARE']['ROUTE'] -interface RecoveryMapDetails { - ROUTE: string - STEPS: Record - STEP_ORDER: RouteStep -} - -export type ValidSubRoutes = DropTipFlowsRoute -export type ValidSubSteps = DropTipFlowsStep -export interface ValidSubMap { - route: ValidSubRoutes - step: ValidSubSteps | null -} +type OriginalRouteKey = keyof RecoveryMap +type StepsForRoute = RecoveryMap[{ + [K in OriginalRouteKey]: RecoveryMap[K]['ROUTE'] extends R ? K : never +}[OriginalRouteKey]]['STEPS'] +type StepKey = StepsForRoute[keyof StepsForRoute] +export type AllStepTypes = { + [R in RouteKey]: StepKey +}[RouteKey] -export type RecoveryMap = Record +export type RouteStep = AllStepTypes | InvalidStep export type StepOrder = { [K in RecoveryRoute]: RouteStep[] } -type RecoveryStep< - K extends keyof RecoveryMap -> = RecoveryMap[K]['STEPS'][keyof RecoveryMap[K]['STEPS']] - -type RobotCancellingRunStep = RecoveryStep<'ROBOT_CANCELING'> -type RobotInMotionStep = RecoveryStep<'ROBOT_IN_MOTION'> -type RobotResumingStep = RecoveryStep<'ROBOT_RESUMING'> -type RobotRetryingCommandStep = RecoveryStep<'ROBOT_RETRYING_COMMAND'> -type BeforeBeginningStep = RecoveryStep<'BEFORE_BEGINNING'> -type CancelRunStep = RecoveryStep<'CANCEL_RUN'> -type DropTipStep = RecoveryStep<'DROP_TIP'> -type IgnoreAndResumeStep = RecoveryStep<'IGNORE_AND_RESUME'> -type RefillAndResumeStep = RecoveryStep<'REFILL_AND_RESUME'> -type ResumeStep = RecoveryStep<'RESUME'> -type OptionSelectionStep = RecoveryStep<'OPTION_SELECTION'> - -export type RouteStep = - | RobotInMotionStep - | RobotResumingStep - | RobotRetryingCommandStep - | BeforeBeginningStep - | CancelRunStep - | DropTipStep - | IgnoreAndResumeStep - | ResumeStep - | OptionSelectionStep - | RefillAndResumeStep - | RobotCancellingRunStep - -export interface IRecoveryMap { - route: RecoveryRoute - step: RouteStep +/** + * Route/Step Properties Types + */ +interface StepDoorConfig { + allowDoorOpen: boolean } - -export type RecoveryContentProps = ErrorRecoveryWizardProps & { - errorKind: ErrorKind - isOnDevice: boolean +type RouteDoorConfig = { + [Step in StepKey & string]: StepDoorConfig +} +export type RecoveryRouteStepMetadata = { + [R in RouteKey]: RouteDoorConfig } +/** + * Style Types + */ export type DesktopSizeType = 'desktop-small' | 'desktop-large' diff --git a/app/src/organisms/ErrorRecoveryFlows/utils/__tests__/getErrorKind.test.ts b/app/src/organisms/ErrorRecoveryFlows/utils/__tests__/getErrorKind.test.ts index adad317fd2a..fb3637c0eb5 100644 --- a/app/src/organisms/ErrorRecoveryFlows/utils/__tests__/getErrorKind.test.ts +++ b/app/src/organisms/ErrorRecoveryFlows/utils/__tests__/getErrorKind.test.ts @@ -6,60 +6,98 @@ import { getErrorKind } from '../getErrorKind' import type { RunCommandError, RunTimeCommand } from '@opentrons/shared-data' describe('getErrorKind', () => { - it(`returns ${ERROR_KINDS.NO_LIQUID_DETECTED} for ${DEFINED_ERROR_TYPES.LIQUID_NOT_FOUND} errorType`, () => { - const result = getErrorKind({ - commandType: 'liquidProbe', - error: { - isDefined: true, - errorType: DEFINED_ERROR_TYPES.LIQUID_NOT_FOUND, - } as RunCommandError, - } as RunTimeCommand) - expect(result).toEqual(ERROR_KINDS.NO_LIQUID_DETECTED) - }) - - it(`returns ${ERROR_KINDS.OVERPRESSURE_WHILE_ASPIRATING} for ${DEFINED_ERROR_TYPES.OVERPRESSURE} errorType`, () => { - const result = getErrorKind({ + it.each([ + { + commandType: 'prepareToAspirate', + errorType: DEFINED_ERROR_TYPES.OVERPRESSURE, + expectedError: ERROR_KINDS.OVERPRESSURE_PREPARE_TO_ASPIRATE, + }, + { commandType: 'aspirate', - error: { - isDefined: true, - errorType: DEFINED_ERROR_TYPES.OVERPRESSURE, - } as RunCommandError, - } as RunTimeCommand) - expect(result).toEqual(ERROR_KINDS.OVERPRESSURE_WHILE_ASPIRATING) - }) - - it(`returns ${ERROR_KINDS.OVERPRESSURE_WHILE_DISPENSING} for ${DEFINED_ERROR_TYPES.OVERPRESSURE} errorType`, () => { - const result = getErrorKind({ + errorType: DEFINED_ERROR_TYPES.OVERPRESSURE, + expectedError: ERROR_KINDS.OVERPRESSURE_WHILE_ASPIRATING, + }, + { + commandType: 'aspirateInPlace', + errorType: DEFINED_ERROR_TYPES.OVERPRESSURE, + expectedError: ERROR_KINDS.OVERPRESSURE_WHILE_ASPIRATING, + }, + { commandType: 'dispense', - error: { - isDefined: true, - errorType: DEFINED_ERROR_TYPES.OVERPRESSURE, - } as RunCommandError, - } as RunTimeCommand) - expect(result).toEqual(ERROR_KINDS.OVERPRESSURE_WHILE_DISPENSING) - }) - - it(`returns ${ERROR_KINDS.GENERAL_ERROR} for undefined errors`, () => { - const result = getErrorKind({ + errorType: DEFINED_ERROR_TYPES.OVERPRESSURE, + expectedError: ERROR_KINDS.OVERPRESSURE_WHILE_DISPENSING, + }, + { + commandType: 'dispenseInPlace', + errorType: DEFINED_ERROR_TYPES.OVERPRESSURE, + expectedError: ERROR_KINDS.OVERPRESSURE_WHILE_DISPENSING, + }, + { + commandType: 'dropTip', + errorType: DEFINED_ERROR_TYPES.TIP_PHYSICALLY_ATTACHED, + expectedError: ERROR_KINDS.TIP_DROP_FAILED, + }, + { + commandType: 'dropTipInPlace', + errorType: DEFINED_ERROR_TYPES.TIP_PHYSICALLY_ATTACHED, + expectedError: ERROR_KINDS.TIP_DROP_FAILED, + }, + { + commandType: 'liquidProbe', + errorType: DEFINED_ERROR_TYPES.LIQUID_NOT_FOUND, + expectedError: ERROR_KINDS.NO_LIQUID_DETECTED, + }, + { + commandType: 'pickUpTip', + errorType: DEFINED_ERROR_TYPES.TIP_PHYSICALLY_MISSING, + expectedError: ERROR_KINDS.TIP_NOT_DETECTED, + }, + { + commandType: 'moveLabware', + errorType: DEFINED_ERROR_TYPES.GRIPPER_MOVEMENT, + expectedError: ERROR_KINDS.GRIPPER_ERROR, + }, + { commandType: 'aspirate', - error: { - isDefined: false, - // It should treat this error as undefined because isDefined===false, - // even though the errorType happens to match a defined error. - errorType: DEFINED_ERROR_TYPES.OVERPRESSURE, - } as RunCommandError, - } as RunTimeCommand) - expect(result).toEqual(ERROR_KINDS.GENERAL_ERROR) - }) - - it(`returns ${ERROR_KINDS.GENERAL_ERROR} for defined errors not handled explicitly`, () => { - const result = getErrorKind({ + errorType: DEFINED_ERROR_TYPES.OVERPRESSURE, + isDefined: false, + expectedError: ERROR_KINDS.GENERAL_ERROR, + }, + { commandType: 'aspirate', - error: ({ - isDefined: true, - errorType: 'someHithertoUnknownDefinedErrorType', - } as unknown) as RunCommandError, - } as RunTimeCommand) - expect(result).toEqual(ERROR_KINDS.GENERAL_ERROR) - }) + errorType: 'someHithertoUnknownDefinedErrorType', + expectedError: ERROR_KINDS.GENERAL_ERROR, + }, + ...([ + 'aspirate', + 'dispense', + 'blowOut', + 'moveToWell', + 'moveToAddressableArea', + 'dropTip', + 'pickUpTip', + 'prepareToAspirate', + ] as const).map(cmd => ({ + commandType: cmd, + errorType: DEFINED_ERROR_TYPES.STALL_OR_COLLISION, + expectedError: ERROR_KINDS.STALL_OR_COLLISION, + isDefined: true, + })), + ])( + 'returns $expectedError for $commandType with errorType $errorType', + ({ commandType, errorType, expectedError, isDefined = true }) => { + const runRecordFailedCommand = { + commandType, + error: { + isDefined, + errorType, + } as RunCommandError, + } as RunTimeCommand + + const result = getErrorKind({ + byRunRecord: runRecordFailedCommand, + } as any) + expect(result).toEqual(expectedError) + } + ) }) diff --git a/app/src/organisms/ErrorRecoveryFlows/utils/__tests__/getFailedCommandPipetteInfo.test.ts b/app/src/organisms/ErrorRecoveryFlows/utils/__tests__/getFailedCommandPipetteInfo.test.ts deleted file mode 100644 index 7031dedc5fc..00000000000 --- a/app/src/organisms/ErrorRecoveryFlows/utils/__tests__/getFailedCommandPipetteInfo.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { describe, expect, it } from 'vitest' - -import { getFailedCommandPipetteInfo } from '../getFailedCommandPipetteInfo' - -describe('getFailedCommandPipetteInfo', () => { - const failedCommand = { - params: { - pipetteId: 'pipetteId1', - }, - } as any - - const runRecordPipette1 = { - id: 'pipetteId1', - mount: 'left', - } as any - - const runRecordPipette2 = { - id: 'pipetteId2', - mount: 'right', - } as any - - const attachedInstrument1 = { - mount: 'left', - name: 'Pipette 1', - } as any - - const attachedInstrument2 = { - mount: 'right', - name: 'Pipette 2', - } as any - - it('should return null if failedCommand is null', () => { - const result = getFailedCommandPipetteInfo({ - failedCommandByRunRecord: null, - runRecord: undefined, - attachedInstruments: undefined, - }) - expect(result).toBeNull() - }) - - it('should return null if failedCommand does not have pipetteId in params', () => { - const result = getFailedCommandPipetteInfo({ - failedCommandByRunRecord: failedCommand, - runRecord: undefined, - attachedInstruments: undefined, - }) - expect(result).toBeNull() - }) - - it('should return null if no matching pipette is found in runRecord', () => { - const result = getFailedCommandPipetteInfo({ - failedCommandByRunRecord: failedCommand, - runRecord: { data: { pipettes: [runRecordPipette2] } } as any, - attachedInstruments: { - data: [attachedInstrument1, attachedInstrument2], - } as any, - }) - expect(result).toBeNull() - }) - - it('should return null if no matching instrument is found in attachedInstruments', () => { - const result = getFailedCommandPipetteInfo({ - failedCommandByRunRecord: failedCommand, - runRecord: { data: { pipettes: [runRecordPipette1] } } as any, - attachedInstruments: { data: [attachedInstrument2] } as any, - }) - expect(result).toBeNull() - }) - - it('should return the matching pipette data', () => { - const result = getFailedCommandPipetteInfo({ - failedCommandByRunRecord: failedCommand, - runRecord: { - data: { pipettes: [runRecordPipette1, runRecordPipette2] }, - } as any, - attachedInstruments: { - data: [attachedInstrument1, attachedInstrument2], - } as any, - }) - expect(result).toEqual(attachedInstrument1) - }) -}) diff --git a/app/src/organisms/ErrorRecoveryFlows/utils/getErrorKind.ts b/app/src/organisms/ErrorRecoveryFlows/utils/getErrorKind.ts index bc16e11619c..73fe862eb3b 100644 --- a/app/src/organisms/ErrorRecoveryFlows/utils/getErrorKind.ts +++ b/app/src/organisms/ErrorRecoveryFlows/utils/getErrorKind.ts @@ -1,35 +1,62 @@ import { ERROR_KINDS, DEFINED_ERROR_TYPES } from '../constants' -import type { RunTimeCommand } from '@opentrons/shared-data' import type { ErrorKind } from '../types' +import type { FailedCommandBySource } from '/app/organisms/ErrorRecoveryFlows/hooks' /** * Given server-side information about a failed command, * decide which UI flow to present to recover from it. + * + * NOTE IMPORTANT: Any failed command by run record must have an equivalent protocol analysis command or default + * to the fallback general error. Prefer using FailedCommandBySource for this reason. */ -export function getErrorKind(failedCommand: RunTimeCommand | null): ErrorKind { - const commandType = failedCommand?.commandType - const errorIsDefined = failedCommand?.error?.isDefined ?? false - const errorType = failedCommand?.error?.errorType +export function getErrorKind( + failedCommand: FailedCommandBySource | null +): ErrorKind { + const failedCommandByRunRecord = failedCommand?.byRunRecord ?? null + const commandType = failedCommandByRunRecord?.commandType + const errorIsDefined = failedCommandByRunRecord?.error?.isDefined ?? false + const errorType = failedCommandByRunRecord?.error?.errorType - if (errorIsDefined) { + if (Boolean(errorIsDefined)) { if ( - commandType === 'aspirate' && + commandType === 'prepareToAspirate' && errorType === DEFINED_ERROR_TYPES.OVERPRESSURE - ) + ) { + return ERROR_KINDS.OVERPRESSURE_PREPARE_TO_ASPIRATE + } else if ( + (commandType === 'aspirate' || commandType === 'aspirateInPlace') && + errorType === DEFINED_ERROR_TYPES.OVERPRESSURE + ) { return ERROR_KINDS.OVERPRESSURE_WHILE_ASPIRATING - else if ( - commandType === 'dispense' && + } else if ( + (commandType === 'dispense' || commandType === 'dispenseInPlace') && errorType === DEFINED_ERROR_TYPES.OVERPRESSURE - ) + ) { return ERROR_KINDS.OVERPRESSURE_WHILE_DISPENSING - else if ( + } else if ( commandType === 'liquidProbe' && errorType === DEFINED_ERROR_TYPES.LIQUID_NOT_FOUND - ) + ) { return ERROR_KINDS.NO_LIQUID_DETECTED - // todo(mm, 2024-07-02): Also handle aspirateInPlace and dispenseInPlace. - // https://opentrons.atlassian.net/browse/EXEC-593 + } else if ( + commandType === 'pickUpTip' && + errorType === DEFINED_ERROR_TYPES.TIP_PHYSICALLY_MISSING + ) { + return ERROR_KINDS.TIP_NOT_DETECTED + } else if ( + (commandType === 'dropTip' || commandType === 'dropTipInPlace') && + errorType === DEFINED_ERROR_TYPES.TIP_PHYSICALLY_ATTACHED + ) { + return ERROR_KINDS.TIP_DROP_FAILED + } else if ( + commandType === 'moveLabware' && + errorType === DEFINED_ERROR_TYPES.GRIPPER_MOVEMENT + ) { + return ERROR_KINDS.GRIPPER_ERROR + } else if (errorType === DEFINED_ERROR_TYPES.STALL_OR_COLLISION) { + return ERROR_KINDS.STALL_OR_COLLISION + } } return ERROR_KINDS.GENERAL_ERROR diff --git a/app/src/organisms/ErrorRecoveryFlows/utils/getFailedCommandPipetteInfo.ts b/app/src/organisms/ErrorRecoveryFlows/utils/getFailedCommandPipetteInfo.ts deleted file mode 100644 index 7d49b51f931..00000000000 --- a/app/src/organisms/ErrorRecoveryFlows/utils/getFailedCommandPipetteInfo.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { PipetteData, Instruments, Run } from '@opentrons/api-client' -import type { ErrorRecoveryFlowsProps } from '..' - -interface UseFailedCommandPipetteInfoProps { - failedCommandByRunRecord: ErrorRecoveryFlowsProps['failedCommandByRunRecord'] - runRecord?: Run - attachedInstruments?: Instruments -} - -// /instruments data for the pipette used in the failedCommand, if any. -export function getFailedCommandPipetteInfo({ - failedCommandByRunRecord, - runRecord, - attachedInstruments, -}: UseFailedCommandPipetteInfoProps): PipetteData | null { - if ( - failedCommandByRunRecord == null || - !('pipetteId' in failedCommandByRunRecord.params) - ) { - return null - } else { - const failedPipetteId = failedCommandByRunRecord.params.pipetteId - const runRecordPipette = runRecord?.data.pipettes.find( - pipette => pipette.id === failedPipetteId - ) - - const failedInstrumentInfo = attachedInstruments?.data.find( - instrument => - 'mount' in instrument && instrument.mount === runRecordPipette?.mount - ) as PipetteData - - return failedInstrumentInfo ?? null - } -} diff --git a/app/src/organisms/ErrorRecoveryFlows/utils/index.ts b/app/src/organisms/ErrorRecoveryFlows/utils/index.ts index 53195d5d5fa..0a4db48783a 100644 --- a/app/src/organisms/ErrorRecoveryFlows/utils/index.ts +++ b/app/src/organisms/ErrorRecoveryFlows/utils/index.ts @@ -1,3 +1,2 @@ export { getErrorKind } from './getErrorKind' -export { getFailedCommandPipetteInfo } from './getFailedCommandPipetteInfo' export { getNextStep, getNextSteps } from './getNextStep' diff --git a/app/src/organisms/FirmwareUpdateModal/FirmwareUpdateTakeover.tsx b/app/src/organisms/FirmwareUpdateModal/FirmwareUpdateTakeover.tsx index 33d581ea5e4..4611fb9372b 100644 --- a/app/src/organisms/FirmwareUpdateModal/FirmwareUpdateTakeover.tsx +++ b/app/src/organisms/FirmwareUpdateModal/FirmwareUpdateTakeover.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { createPortal } from 'react-dom' import { @@ -6,9 +6,9 @@ import { useCurrentAllSubsystemUpdatesQuery, useSubsystemUpdateQuery, } from '@opentrons/react-api-client' -import { useNotifyCurrentMaintenanceRun } from '../../resources/maintenance_runs' -import { getTopPortalEl } from '../../App/portal' -import { useIsUnboxingFlowOngoing } from '../RobotSettingsDashboard/NetworkSettings/hooks' +import { useNotifyCurrentMaintenanceRun } from '/app/resources/maintenance_runs' +import { getTopPortalEl } from '/app/App/portal' +import { useIsUnboxingFlowOngoing } from '/app/redux-resources/config' import { UpdateInProgressModal } from './UpdateInProgressModal' import { UpdateNeededModal } from './UpdateNeededModal' import type { Subsystem, InstrumentData } from '@opentrons/api-client' @@ -16,19 +16,18 @@ import type { Subsystem, InstrumentData } from '@opentrons/api-client' const POLL_INTERVAL_MS = 5000 export function FirmwareUpdateTakeover(): JSX.Element { - const [ - showUpdateNeededModal, - setShowUpdateNeededModal, - ] = React.useState(false) + const [showUpdateNeededModal, setShowUpdateNeededModal] = useState( + false + ) const [ initiatedSubsystemUpdate, setInitiatedSubsystemUpdate, - ] = React.useState(null) + ] = useState(null) const instrumentsData = useInstrumentsQuery({ refetchInterval: POLL_INTERVAL_MS, }).data?.data - const [instrumentsToUpdate, setInstrumentsToUpdate] = React.useState< + const [instrumentsToUpdate, setInstrumentsToUpdate] = useState< InstrumentData[] >([]) instrumentsData?.forEach(instrument => { @@ -41,7 +40,7 @@ export function FirmwareUpdateTakeover(): JSX.Element { setInstrumentsToUpdate([...instrumentsToUpdate, instrument]) } }) - const [indexToUpdate, setIndexToUpdate] = React.useState(0) + const [indexToUpdate, setIndexToUpdate] = useState(0) const { data: maintenanceRunData } = useNotifyCurrentMaintenanceRun({ refetchInterval: POLL_INTERVAL_MS, @@ -63,7 +62,7 @@ export function FirmwareUpdateTakeover(): JSX.Element { externalSubsystemUpdate?.id ?? null ) - React.useEffect(() => { + useEffect(() => { // in case instruments are updated elsewhere in the app, clear update needed list // when all instruments are ok but array has elements if ( diff --git a/app/src/organisms/FirmwareUpdateModal/UpdateInProgressModal.tsx b/app/src/organisms/FirmwareUpdateModal/UpdateInProgressModal.tsx index 93f74c21efc..a870f97bc67 100644 --- a/app/src/organisms/FirmwareUpdateModal/UpdateInProgressModal.tsx +++ b/app/src/organisms/FirmwareUpdateModal/UpdateInProgressModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' import { @@ -13,7 +12,7 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { OddModal } from '../../molecules/OddModal' +import { OddModal } from '/app/molecules/OddModal' import type { Subsystem } from '@opentrons/api-client' interface UpdateInProgressModalProps { diff --git a/app/src/organisms/FirmwareUpdateModal/UpdateNeededModal.tsx b/app/src/organisms/FirmwareUpdateModal/UpdateNeededModal.tsx index 32ef5a6763e..c4e0db2225a 100644 --- a/app/src/organisms/FirmwareUpdateModal/UpdateNeededModal.tsx +++ b/app/src/organisms/FirmwareUpdateModal/UpdateNeededModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { createPortal } from 'react-dom' import { useTranslation, Trans } from 'react-i18next' import capitalize from 'lodash/capitalize' @@ -15,14 +15,14 @@ import { useUpdateSubsystemMutation, } from '@opentrons/react-api-client' import { LEFT, RIGHT } from '@opentrons/shared-data' -import { getTopPortalEl } from '../../App/portal' -import { SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' +import { getTopPortalEl } from '/app/App/portal' +import { SmallButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' import { UpdateInProgressModal } from './UpdateInProgressModal' import { UpdateResultsModal } from './UpdateResultsModal' import type { Subsystem } from '@opentrons/api-client' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' interface UpdateNeededModalProps { onClose: () => void @@ -34,9 +34,9 @@ interface UpdateNeededModalProps { export function UpdateNeededModal(props: UpdateNeededModalProps): JSX.Element { const { onClose, shouldExit, subsystem, setInitiatedSubsystemUpdate } = props const { t } = useTranslation('firmware_update') - const [updateId, setUpdateId] = React.useState(null) + const [updateId, setUpdateId] = useState(null) // when we move to the next subsystem to update, set updateId back to null - React.useEffect(() => { + useEffect(() => { setUpdateId(null) }, [subsystem]) @@ -58,7 +58,7 @@ export function UpdateNeededModal(props: UpdateNeededModalProps): JSX.Element { const status = updateData?.data.updateStatus const ongoingUpdateId = updateData?.data.id - React.useEffect(() => { + useEffect(() => { if (status === 'done') { setInitiatedSubsystemUpdate(null) } diff --git a/app/src/organisms/FirmwareUpdateModal/UpdateResultsModal.tsx b/app/src/organisms/FirmwareUpdateModal/UpdateResultsModal.tsx index cd5dad33f02..e305d451c7f 100644 --- a/app/src/organisms/FirmwareUpdateModal/UpdateResultsModal.tsx +++ b/app/src/organisms/FirmwareUpdateModal/UpdateResultsModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation, Trans } from 'react-i18next' import { ALIGN_CENTER, @@ -11,12 +10,12 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' -import { usePipetteModelSpecs } from '../../resources/instruments/hooks' +import { SmallButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' +import { usePipetteModelSpecs } from '/app/local-resources/instruments' import type { InstrumentData, PipetteData } from '@opentrons/api-client' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' interface UpdateResultsModalProps { isSuccess: boolean diff --git a/app/src/organisms/FirmwareUpdateModal/__tests__/FirmwareUpdateModal.test.tsx b/app/src/organisms/FirmwareUpdateModal/__tests__/FirmwareUpdateModal.test.tsx index 377c38a3bf4..bb205f3f852 100644 --- a/app/src/organisms/FirmwareUpdateModal/__tests__/FirmwareUpdateModal.test.tsx +++ b/app/src/organisms/FirmwareUpdateModal/__tests__/FirmwareUpdateModal.test.tsx @@ -1,14 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { act, screen, waitFor } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useInstrumentsQuery, useSubsystemUpdateQuery, useUpdateSubsystemMutation, } from '@opentrons/react-api-client' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { FirmwareUpdateModal } from '..' import type { BadPipette, diff --git a/app/src/organisms/FirmwareUpdateModal/__tests__/FirmwareUpdateTakeover.test.tsx b/app/src/organisms/FirmwareUpdateModal/__tests__/FirmwareUpdateTakeover.test.tsx index 3816b85261f..ae70b8ab2db 100644 --- a/app/src/organisms/FirmwareUpdateModal/__tests__/FirmwareUpdateTakeover.test.tsx +++ b/app/src/organisms/FirmwareUpdateModal/__tests__/FirmwareUpdateTakeover.test.tsx @@ -1,28 +1,27 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useInstrumentsQuery, useCurrentAllSubsystemUpdatesQuery, useSubsystemUpdateQuery, } from '@opentrons/react-api-client' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { UpdateNeededModal } from '../UpdateNeededModal' import { UpdateInProgressModal } from '../UpdateInProgressModal' -import { useIsUnboxingFlowOngoing } from '../../RobotSettingsDashboard/NetworkSettings/hooks' +import { useIsUnboxingFlowOngoing } from '/app/redux-resources/config' import { FirmwareUpdateTakeover } from '../FirmwareUpdateTakeover' -import { useNotifyCurrentMaintenanceRun } from '../../../resources/maintenance_runs' +import { useNotifyCurrentMaintenanceRun } from '/app/resources/maintenance_runs' import type { BadPipette, PipetteData } from '@opentrons/api-client' vi.mock('@opentrons/react-api-client') vi.mock('../UpdateNeededModal') vi.mock('../UpdateInProgressModal') -vi.mock('../../RobotSettingsDashboard/NetworkSettings/hooks') -vi.mock('../../../resources/maintenance_runs') +vi.mock('/app/redux-resources/config') +vi.mock('/app/resources/maintenance_runs') const render = () => { return renderWithProviders(, { diff --git a/app/src/organisms/FirmwareUpdateModal/__tests__/UpdateInProgressModal.test.tsx b/app/src/organisms/FirmwareUpdateModal/__tests__/UpdateInProgressModal.test.tsx index 6d0ccf2c12b..f2ce6047481 100644 --- a/app/src/organisms/FirmwareUpdateModal/__tests__/UpdateInProgressModal.test.tsx +++ b/app/src/organisms/FirmwareUpdateModal/__tests__/UpdateInProgressModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { UpdateInProgressModal } from '../UpdateInProgressModal' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/FirmwareUpdateModal/__tests__/UpdateNeededModal.test.tsx b/app/src/organisms/FirmwareUpdateModal/__tests__/UpdateNeededModal.test.tsx index 77ed2ee0de1..53c91223b47 100644 --- a/app/src/organisms/FirmwareUpdateModal/__tests__/UpdateNeededModal.test.tsx +++ b/app/src/organisms/FirmwareUpdateModal/__tests__/UpdateNeededModal.test.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useInstrumentsQuery, useSubsystemUpdateQuery, useUpdateSubsystemMutation, } from '@opentrons/react-api-client' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { UpdateNeededModal } from '../UpdateNeededModal' import { UpdateInProgressModal } from '../UpdateInProgressModal' import { UpdateResultsModal } from '../UpdateResultsModal' diff --git a/app/src/organisms/FirmwareUpdateModal/__tests__/UpdateResultsModal.test.tsx b/app/src/organisms/FirmwareUpdateModal/__tests__/UpdateResultsModal.test.tsx index 8e3f11afd6d..29c5233db45 100644 --- a/app/src/organisms/FirmwareUpdateModal/__tests__/UpdateResultsModal.test.tsx +++ b/app/src/organisms/FirmwareUpdateModal/__tests__/UpdateResultsModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { UpdateResultsModal } from '../UpdateResultsModal' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/FirmwareUpdateModal/index.tsx b/app/src/organisms/FirmwareUpdateModal/index.tsx index cb449588ea1..0ed40409797 100644 --- a/app/src/organisms/FirmwareUpdateModal/index.tsx +++ b/app/src/organisms/FirmwareUpdateModal/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { css } from 'styled-components' import { ALIGN_CENTER, @@ -73,8 +73,8 @@ export const FirmwareUpdateModal = ( description, isOnDevice, } = props - const [updateId, setUpdateId] = React.useState(null) - const [firmwareText, setFirmwareText] = React.useState(null) + const [updateId, setUpdateId] = useState(null) + const [firmwareText, setFirmwareText] = useState(null) const { data: attachedInstruments, refetch: refetchInstruments, @@ -92,7 +92,7 @@ export const FirmwareUpdateModal = ( (i): i is BadGripper | BadPipette => !i.ok && i.subsystem === subsystem ) ?? false - React.useEffect(() => { + useEffect(() => { setTimeout(() => { if (!updateNeeded) { setFirmwareText(proceedDescription) @@ -107,7 +107,7 @@ export const FirmwareUpdateModal = ( const { data: updateData } = useSubsystemUpdateQuery(updateId) const status = updateData?.data.updateStatus - React.useEffect(() => { + useEffect(() => { if ((status != null || updateNeeded) && firmwareText !== description) { setFirmwareText(description) } diff --git a/app/src/organisms/GripperCard/index.tsx b/app/src/organisms/GripperCard/index.tsx deleted file mode 100644 index 389ce296df4..00000000000 --- a/app/src/organisms/GripperCard/index.tsx +++ /dev/null @@ -1,214 +0,0 @@ -import * as React from 'react' -import { Trans, useTranslation } from 'react-i18next' -import { css } from 'styled-components' -import { SPACING, TYPOGRAPHY, LegacyStyledText } from '@opentrons/components' -import { getGripperDisplayName } from '@opentrons/shared-data' -import { useCurrentSubsystemUpdateQuery } from '@opentrons/react-api-client' -import { Banner } from '../../atoms/Banner' -import { InstrumentCard } from '../../molecules/InstrumentCard' -import { GripperWizardFlows } from '../GripperWizardFlows' -import { AboutGripperSlideout } from './AboutGripperSlideout' -import { GRIPPER_FLOW_TYPES } from '../GripperWizardFlows/constants' - -import type { BadGripper, GripperData } from '@opentrons/api-client' -import type { GripperModel } from '@opentrons/shared-data' -import type { GripperWizardFlowType } from '../GripperWizardFlows/types' - -interface GripperCardProps { - attachedGripper: GripperData | BadGripper | null - isCalibrated: boolean - isRunActive: boolean - isEstopNotDisengaged: boolean -} -const BANNER_LINK_CSS = css` - text-decoration: ${TYPOGRAPHY.textDecorationUnderline}; - cursor: pointer; - margin-left: ${SPACING.spacing8}; -` - -const INSTRUMENT_CARD_STYLE = css` - p { - text-transform: lowercase; - } - - p::first-letter { - text-transform: uppercase; - } -` - -const POLL_DURATION_MS = 5000 - -export function GripperCard({ - attachedGripper, - isCalibrated, - isRunActive, - isEstopNotDisengaged, -}: GripperCardProps): JSX.Element { - const { t, i18n } = useTranslation(['device_details', 'shared']) - const [ - openWizardFlowType, - setOpenWizardFlowType, - ] = React.useState(null) - const [ - showAboutGripperSlideout, - setShowAboutGripperSlideout, - ] = React.useState(false) - - const handleAttach: React.MouseEventHandler = () => { - setOpenWizardFlowType(GRIPPER_FLOW_TYPES.ATTACH) - } - - const handleDetach: React.MouseEventHandler = () => { - setOpenWizardFlowType(GRIPPER_FLOW_TYPES.DETACH) - } - - const handleCalibrate: React.MouseEventHandler = () => { - setOpenWizardFlowType(GRIPPER_FLOW_TYPES.RECALIBRATE) - } - const [pollForSubsystemUpdate, setPollForSubsystemUpdate] = React.useState( - false - ) - const { data: subsystemUpdateData } = useCurrentSubsystemUpdateQuery( - 'gripper', - { - enabled: pollForSubsystemUpdate, - refetchInterval: POLL_DURATION_MS, - } - ) - // we should poll for a subsystem update from the time a bad instrument is - // detected until the update has been done for 5 seconds - // this gives the instruments endpoint time to start reporting - // a good instrument - React.useEffect(() => { - if (attachedGripper?.ok === false) { - setPollForSubsystemUpdate(true) - } else if ( - subsystemUpdateData != null && - subsystemUpdateData.data.updateStatus === 'done' - ) { - setTimeout(() => { - setPollForSubsystemUpdate(false) - }, POLL_DURATION_MS) - } - }, [attachedGripper?.ok, subsystemUpdateData]) - - const menuOverlayItems = - attachedGripper == null || !attachedGripper.ok - ? [ - { - label: t('attach_gripper'), - disabled: attachedGripper != null || isRunActive, - onClick: handleAttach, - }, - ] - : [ - { - label: - attachedGripper.data.calibratedOffset?.last_modified != null - ? t('recalibrate_gripper') - : t('calibrate_gripper'), - disabled: attachedGripper == null || isRunActive, - onClick: handleCalibrate, - }, - { - label: t('detach_gripper'), - disabled: attachedGripper == null || isRunActive, - onClick: handleDetach, - }, - { - label: t('about_gripper'), - disabled: attachedGripper == null, - onClick: () => { - setShowAboutGripperSlideout(true) - }, - }, - ] - return ( - <> - {(attachedGripper == null || attachedGripper.ok) && - subsystemUpdateData == null ? ( - - {isEstopNotDisengaged ? ( - - {t('calibration_needed_without_link')} - - ) : ( - - ), - }} - /> - )} - - ) : null - } - isGripperAttached={attachedGripper != null} - label={t('shared:extension_mount')} - menuOverlayItems={menuOverlayItems} - isEstopNotDisengaged={isEstopNotDisengaged} - /> - ) : null} - {attachedGripper?.ok === false || - (subsystemUpdateData != null && pollForSubsystemUpdate) ? ( - - - - } - isEstopNotDisengaged={isEstopNotDisengaged} - /> - ) : null} - {openWizardFlowType != null ? ( - { - setOpenWizardFlowType(null) - }} - /> - ) : null} - {attachedGripper?.ok && showAboutGripperSlideout && ( - { - setShowAboutGripperSlideout(false) - }} - /> - )} - - ) -} diff --git a/app/src/organisms/GripperWizardFlows/BeforeBeginning.tsx b/app/src/organisms/GripperWizardFlows/BeforeBeginning.tsx index 249f7ca8ddf..89395aeee10 100644 --- a/app/src/organisms/GripperWizardFlows/BeforeBeginning.tsx +++ b/app/src/organisms/GripperWizardFlows/BeforeBeginning.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import { useEffect } from 'react' import { Trans, useTranslation } from 'react-i18next' import { COLORS, LegacyStyledText } from '@opentrons/components' import { EXTENSION } from '@opentrons/shared-data' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' import { SimpleWizardBody, SimpleWizardInProgressBody, -} from '../../molecules/SimpleWizardBody' -import { WizardRequiredEquipmentList } from '../../molecules/WizardRequiredEquipmentList' +} from '/app/molecules/SimpleWizardBody' +import { WizardRequiredEquipmentList } from '/app/molecules/WizardRequiredEquipmentList' import { GRIPPER_FLOW_TYPES, SCREWDRIVER_LOADNAME, @@ -76,7 +76,7 @@ export const BeforeBeginning = ( createdMaintenanceRunId, } = props const { t } = useTranslation(['gripper_wizard_flows', 'shared', 'branded']) - React.useEffect(() => { + useEffect(() => { if (createdMaintenanceRunId == null) { createMaintenanceRun({}) } diff --git a/app/src/organisms/GripperWizardFlows/ExitConfirmation.tsx b/app/src/organisms/GripperWizardFlows/ExitConfirmation.tsx index 6633a7ba9fd..28fb21635ac 100644 --- a/app/src/organisms/GripperWizardFlows/ExitConfirmation.tsx +++ b/app/src/organisms/GripperWizardFlows/ExitConfirmation.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { @@ -9,12 +8,12 @@ import { SecondaryButton, JUSTIFY_FLEX_END, } from '@opentrons/components' -import { getIsOnDevice } from '../../redux/config' +import { getIsOnDevice } from '/app/redux/config' import { SimpleWizardBody, SimpleWizardInProgressBody, -} from '../../molecules/SimpleWizardBody' -import { SmallButton } from '../../atoms/buttons' +} from '/app/molecules/SimpleWizardBody' +import { SmallButton } from '/app/atoms/buttons' import { GRIPPER_FLOW_TYPES } from './constants' import type { GripperWizardFlowType } from './types' diff --git a/app/src/organisms/GripperWizardFlows/GripperWizardFlows.stories.tsx b/app/src/organisms/GripperWizardFlows/GripperWizardFlows.stories.tsx index 86a7ebdf427..b5318daf5d8 100644 --- a/app/src/organisms/GripperWizardFlows/GripperWizardFlows.stories.tsx +++ b/app/src/organisms/GripperWizardFlows/GripperWizardFlows.stories.tsx @@ -1,14 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { QueryClient, QueryClientProvider } from 'react-query' import { Provider } from 'react-redux' import { createStore } from 'redux' -import { mockConnectableRobot } from '../../redux/discovery/__fixtures__' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' import * as DiscoveryClientFixtures from '../../../../discovery-client/src/fixtures' import { HEALTH_STATUS_OK, ROBOT_MODEL_OT3, -} from '../../redux/discovery/constants' -import { configReducer } from '../../redux/config/reducer' +} from '/app/redux/discovery/constants' +import { configReducer } from '/app/redux/config/reducer' import { GripperWizardFlows } from './' import type { Store, StoreEnhancer } from 'redux' diff --git a/app/src/organisms/GripperWizardFlows/MountGripper.tsx b/app/src/organisms/GripperWizardFlows/MountGripper.tsx index 7f69bf389ac..4a428ac69b1 100644 --- a/app/src/organisms/GripperWizardFlows/MountGripper.tsx +++ b/app/src/organisms/GripperWizardFlows/MountGripper.tsx @@ -14,16 +14,16 @@ import { } from '@opentrons/components' import { useInstrumentsQuery } from '@opentrons/react-api-client' import { css } from 'styled-components' -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' -import { getIsOnDevice } from '../../redux/config' -import { SmallButton } from '../../atoms/buttons' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' +import { getIsOnDevice } from '/app/redux/config' +import { SmallButton } from '/app/atoms/buttons' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' import { SimpleWizardBody, SimpleWizardInProgressBody, -} from '../../molecules/SimpleWizardBody' -import mountGripper from '../../assets/videos/gripper-wizards/MOUNT_GRIPPER.webm' +} from '/app/molecules/SimpleWizardBody' +import mountGripper from '/app/assets/videos/gripper-wizards/MOUNT_GRIPPER.webm' import type { GripperWizardStepProps } from './types' import type { BadGripper, GripperData } from '@opentrons/api-client' @@ -62,8 +62,8 @@ export const MountGripper = ( const { proceed, isRobotMoving } = props const { t } = useTranslation(['gripper_wizard_flows', 'shared', 'branded']) const isOnDevice = useSelector(getIsOnDevice) - const [showUnableToDetect, setShowUnableToDetect] = React.useState(false) - const [isPending, setIsPending] = React.useState(false) + const [showUnableToDetect, setShowUnableToDetect] = useState(false) + const [isPending, setIsPending] = useState(false) const { data: instrumentsQueryData, refetch } = useInstrumentsQuery({ refetchInterval: QUICK_GRIPPER_POLL_MS, }) diff --git a/app/src/organisms/GripperWizardFlows/MovePin.tsx b/app/src/organisms/GripperWizardFlows/MovePin.tsx index 2a791a1faeb..1cf5153eaa5 100644 --- a/app/src/organisms/GripperWizardFlows/MovePin.tsx +++ b/app/src/organisms/GripperWizardFlows/MovePin.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation, Trans } from 'react-i18next' import { EXTENSION } from '@opentrons/shared-data' import { @@ -12,21 +12,21 @@ import { css } from 'styled-components' import { SimpleWizardBody, SimpleWizardInProgressBody, -} from '../../molecules/SimpleWizardBody' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' +} from '/app/molecules/SimpleWizardBody' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' import { MOVE_PIN_FROM_FRONT_JAW_TO_REAR_JAW, MOVE_PIN_TO_FRONT_JAW, REMOVE_PIN_FROM_REAR_JAW, } from './constants' -import movePinStorageToFront from '../../assets/videos/gripper-wizards/PIN_FROM_STORAGE_TO_FRONT_JAW.webm' -import movePinFrontToRear from '../../assets/videos/gripper-wizards/PIN_FROM_FRONT_TO_REAR_JAW.webm' -import movePinRearToStorage from '../../assets/videos/gripper-wizards/PIN_FROM_REAR_TO_STORAGE.webm' -import calibratingFrontJaw from '../../assets/videos/gripper-wizards/CALIBRATING_FRONT_JAW.webm' -import calibratingRearJaw from '../../assets/videos/gripper-wizards/CALIBRATING_REAR_JAW.webm' +import movePinStorageToFront from '/app/assets/videos/gripper-wizards/PIN_FROM_STORAGE_TO_FRONT_JAW.webm' +import movePinFrontToRear from '/app/assets/videos/gripper-wizards/PIN_FROM_FRONT_TO_REAR_JAW.webm' +import movePinRearToStorage from '/app/assets/videos/gripper-wizards/PIN_FROM_REAR_TO_STORAGE.webm' +import calibratingFrontJaw from '/app/assets/videos/gripper-wizards/CALIBRATING_FRONT_JAW.webm' +import calibratingRearJaw from '/app/assets/videos/gripper-wizards/CALIBRATING_REAR_JAW.webm' import type { Coordinates } from '@opentrons/shared-data' -import type { CreateMaintenanceCommand } from '../../resources/runs' +import type { CreateMaintenanceCommand } from '/app/resources/runs' import type { GripperWizardStepProps, MovePinStep } from './types' interface MovePinProps extends GripperWizardStepProps, MovePinStep { diff --git a/app/src/organisms/GripperWizardFlows/Success.tsx b/app/src/organisms/GripperWizardFlows/Success.tsx index 91ce1c30213..bbd36b4ca37 100644 --- a/app/src/organisms/GripperWizardFlows/Success.tsx +++ b/app/src/organisms/GripperWizardFlows/Success.tsx @@ -1,5 +1,4 @@ import { useSelector } from 'react-redux' -import * as React from 'react' import { css } from 'styled-components' import { useTranslation } from 'react-i18next' import { @@ -9,12 +8,12 @@ import { JUSTIFY_FLEX_END, Flex, } from '@opentrons/components' -import { getIsOnDevice } from '../../redux/config' +import { getIsOnDevice } from '/app/redux/config' import { SimpleWizardBody, SimpleWizardInProgressBody, -} from '../../molecules/SimpleWizardBody' -import { SmallButton } from '../../atoms/buttons' +} from '/app/molecules/SimpleWizardBody' +import { SmallButton } from '/app/atoms/buttons' import { SUCCESSFULLY_ATTACHED, SUCCESSFULLY_ATTACHED_AND_CALIBRATED, diff --git a/app/src/organisms/GripperWizardFlows/UnmountGripper.tsx b/app/src/organisms/GripperWizardFlows/UnmountGripper.tsx index 46b62bdf40d..273b211ec4d 100644 --- a/app/src/organisms/GripperWizardFlows/UnmountGripper.tsx +++ b/app/src/organisms/GripperWizardFlows/UnmountGripper.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { @@ -16,14 +16,14 @@ import { } from '@opentrons/components' import { useInstrumentsQuery } from '@opentrons/react-api-client' import { css } from 'styled-components' -import { getIsOnDevice } from '../../redux/config' -import { SmallButton } from '../../atoms/buttons' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' +import { getIsOnDevice } from '/app/redux/config' +import { SmallButton } from '/app/atoms/buttons' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' import { SimpleWizardBody, SimpleWizardInProgressBody, -} from '../../molecules/SimpleWizardBody' -import unmountGripper from '../../assets/videos/gripper-wizards/UNMOUNT_GRIPPER.webm' +} from '/app/molecules/SimpleWizardBody' +import unmountGripper from '/app/assets/videos/gripper-wizards/UNMOUNT_GRIPPER.webm' import type { GripperWizardStepProps } from './types' import type { GripperData } from '@opentrons/api-client' @@ -55,7 +55,7 @@ export const UnmountGripper = ( const { proceed, isRobotMoving, goBack, chainRunCommands } = props const { t } = useTranslation(['gripper_wizard_flows', 'shared', 'branded']) const isOnDevice = useSelector(getIsOnDevice) - const [isPending, setIsPending] = React.useState(false) + const [isPending, setIsPending] = useState(false) const { data: instrumentsQueryData, refetch } = useInstrumentsQuery({ refetchInterval: QUICK_GRIPPER_POLL_MS, }) @@ -63,10 +63,9 @@ export const UnmountGripper = ( (i): i is GripperData => i.instrumentType === 'gripper' && i.ok ) - const [ - showGripperStillDetected, - setShowGripperStillDetected, - ] = React.useState(false) + const [showGripperStillDetected, setShowGripperStillDetected] = useState( + false + ) const handleContinue = (): void => { setIsPending(true) refetch() diff --git a/app/src/organisms/GripperWizardFlows/__tests__/BeforeBeginning.test.tsx b/app/src/organisms/GripperWizardFlows/__tests__/BeforeBeginning.test.tsx index 59e554ea372..888e6ba30b2 100644 --- a/app/src/organisms/GripperWizardFlows/__tests__/BeforeBeginning.test.tsx +++ b/app/src/organisms/GripperWizardFlows/__tests__/BeforeBeginning.test.tsx @@ -1,14 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen, waitFor } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { InProgressModal } from '../../../molecules/InProgressModal/InProgressModal' -import { RUN_ID_1 } from '../../RunTimeControl/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { InProgressModal } from '/app/molecules/InProgressModal/InProgressModal' +import { RUN_ID_1 } from '/app/resources/runs/__fixtures__' import { BeforeBeginning } from '../BeforeBeginning' import { GRIPPER_FLOW_TYPES } from '../constants' -vi.mock('../../../molecules/InProgressModal/InProgressModal') +vi.mock('/app/molecules/InProgressModal/InProgressModal') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/GripperWizardFlows/__tests__/ExitConfirmation.test.tsx b/app/src/organisms/GripperWizardFlows/__tests__/ExitConfirmation.test.tsx index 50ad7285497..4b20f234bfb 100644 --- a/app/src/organisms/GripperWizardFlows/__tests__/ExitConfirmation.test.tsx +++ b/app/src/organisms/GripperWizardFlows/__tests__/ExitConfirmation.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ExitConfirmation } from '../ExitConfirmation' import { GRIPPER_FLOW_TYPES } from '../constants' diff --git a/app/src/organisms/GripperWizardFlows/__tests__/MountGripper.test.tsx b/app/src/organisms/GripperWizardFlows/__tests__/MountGripper.test.tsx index fbe6bb5ea16..cb2020c5bfe 100644 --- a/app/src/organisms/GripperWizardFlows/__tests__/MountGripper.test.tsx +++ b/app/src/organisms/GripperWizardFlows/__tests__/MountGripper.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen, waitFor } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useInstrumentsQuery } from '@opentrons/react-api-client' import { instrumentsResponseFixture } from '@opentrons/api-client' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { MountGripper } from '../MountGripper' import { GRIPPER_FLOW_TYPES } from '../constants' diff --git a/app/src/organisms/GripperWizardFlows/__tests__/MovePin.test.tsx b/app/src/organisms/GripperWizardFlows/__tests__/MovePin.test.tsx index 954a3bcb19e..ecf3ef6f3c0 100644 --- a/app/src/organisms/GripperWizardFlows/__tests__/MovePin.test.tsx +++ b/app/src/organisms/GripperWizardFlows/__tests__/MovePin.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect, afterEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { instrumentsResponseFixture } from '@opentrons/api-client' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { MovePin } from '../MovePin' import { diff --git a/app/src/organisms/GripperWizardFlows/__tests__/Success.test.tsx b/app/src/organisms/GripperWizardFlows/__tests__/Success.test.tsx index 08935cf29ae..d071fda3c69 100644 --- a/app/src/organisms/GripperWizardFlows/__tests__/Success.test.tsx +++ b/app/src/organisms/GripperWizardFlows/__tests__/Success.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { Success } from '../Success' import { diff --git a/app/src/organisms/GripperWizardFlows/__tests__/UnmountGripper.test.tsx b/app/src/organisms/GripperWizardFlows/__tests__/UnmountGripper.test.tsx index e0c6e3e3c4e..73c2d863366 100644 --- a/app/src/organisms/GripperWizardFlows/__tests__/UnmountGripper.test.tsx +++ b/app/src/organisms/GripperWizardFlows/__tests__/UnmountGripper.test.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, beforeEach, expect } from 'vitest' import { fireEvent, screen, waitFor } from '@testing-library/react' import { useInstrumentsQuery } from '@opentrons/react-api-client' import { instrumentsResponseFixture } from '@opentrons/api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { UnmountGripper } from '../UnmountGripper' import { GRIPPER_FLOW_TYPES } from '../constants' diff --git a/app/src/organisms/GripperWizardFlows/index.tsx b/app/src/organisms/GripperWizardFlows/index.tsx index 69068d9eb8c..1a4bab512eb 100644 --- a/app/src/organisms/GripperWizardFlows/index.tsx +++ b/app/src/organisms/GripperWizardFlows/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' @@ -15,16 +15,16 @@ import { useCreateMaintenanceCommandMutation, useDeleteMaintenanceRunMutation, } from '@opentrons/react-api-client' -import { useNotifyCurrentMaintenanceRun } from '../../resources/maintenance_runs' -import { getTopPortalEl } from '../../App/portal' -import { WizardHeader } from '../../molecules/WizardHeader' -import { SimpleWizardBody } from '../../molecules/SimpleWizardBody' -import { FirmwareUpdateModal } from '../FirmwareUpdateModal' -import { getIsOnDevice } from '../../redux/config' import { useChainMaintenanceCommands, - useCreateTargetedMaintenanceRunMutation, -} from '../../resources/runs' + useNotifyCurrentMaintenanceRun, +} from '/app/resources/maintenance_runs' +import { getTopPortalEl } from '/app/App/portal' +import { WizardHeader } from '/app/molecules/WizardHeader' +import { SimpleWizardBody } from '/app/molecules/SimpleWizardBody' +import { FirmwareUpdateModal } from '../FirmwareUpdateModal' +import { getIsOnDevice } from '/app/redux/config' +import { useCreateTargetedMaintenanceRunMutation } from '/app/resources/runs' import { getGripperWizardSteps } from './getGripperWizardSteps' import { GRIPPER_FLOW_TYPES, SECTIONS } from './constants' import { BeforeBeginning } from './BeforeBeginning' @@ -42,7 +42,9 @@ import type { InstrumentData, MaintenanceRun, CommandData, + RunStatus, } from '@opentrons/api-client' +import { RUN_STATUS_FAILED } from '@opentrons/api-client' import type { Coordinates, CreateCommand } from '@opentrons/shared-data' const RUN_REFETCH_INTERVAL = 5000 @@ -66,16 +68,17 @@ export function GripperWizardFlows( isLoading: isCommandLoading, } = useCreateMaintenanceCommandMutation() - const [createdMaintenanceRunId, setCreatedMaintenanceRunId] = React.useState< + const [createdMaintenanceRunId, setCreatedMaintenanceRunId] = useState< string | null >(null) + const [errorMessage, setErrorMessage] = useState(null) // we should start checking for run deletion only after the maintenance run is created // and the useCurrentRun poll has returned that created id const [ monitorMaintenanceRunForDeletion, setMonitorMaintenanceRunForDeletion, - ] = React.useState(false) + ] = useState(false) const { createTargetedMaintenanceRun, @@ -84,6 +87,9 @@ export function GripperWizardFlows( onSuccess: response => { setCreatedMaintenanceRunId(response.data.id) }, + onError: error => { + setErrorMessage(error.message) + }, }) const { data: maintenanceRunData } = useNotifyCurrentMaintenanceRun({ @@ -93,7 +99,7 @@ export function GripperWizardFlows( // this will close the modal in case the run was deleted by the terminate // activity modal on the ODD - React.useEffect(() => { + useEffect(() => { if ( createdMaintenanceRunId !== null && maintenanceRunData?.data.id === createdMaintenanceRunId @@ -108,21 +114,22 @@ export function GripperWizardFlows( } }, [ maintenanceRunData?.data.id, + maintenanceRunData?.data.status, createdMaintenanceRunId, monitorMaintenanceRunForDeletion, closeFlow, ]) - const [isExiting, setIsExiting] = React.useState(false) - const [errorMessage, setErrorMessage] = React.useState(null) + const [isExiting, setIsExiting] = useState(false) const handleClose = (): void => { - if (props?.onComplete != null) props.onComplete() + if (props?.onComplete != null) { + props.onComplete() + } if (maintenanceRunData != null) { deleteMaintenanceRun(maintenanceRunData?.data.id) - } else { - closeFlow() } + closeFlow() } const { @@ -138,20 +145,21 @@ export function GripperWizardFlows( }) const handleCleanUpAndClose = (): void => { - if (maintenanceRunData?.data.id == null) handleClose() - else { + if (maintenanceRunData?.data.id == null) { + handleClose() + } else { chainRunCommands( maintenanceRunData?.data.id, [{ commandType: 'home' as const, params: {} }], false ) - .then(() => { - handleClose() - }) .catch(error => { setIsExiting(true) setErrorMessage(error.message as string) }) + .finally(() => { + handleClose() + }) } } @@ -160,6 +168,7 @@ export function GripperWizardFlows( flowType={flowType} createdMaintenanceRunId={createdMaintenanceRunId} maintenanceRunId={maintenanceRunData?.data.id} + maintenanceRunStatus={maintenanceRunData?.data.status} attachedGripper={attachedGripper} createMaintenanceRun={createTargetedMaintenanceRun} isCreateLoading={isCreateLoading} @@ -183,6 +192,7 @@ export function GripperWizardFlows( interface GripperWizardProps { flowType: GripperWizardFlowType maintenanceRunId?: string + maintenanceRunStatus?: RunStatus createdMaintenanceRunId: string | null attachedGripper: InstrumentData | null createMaintenanceRun: UseMutateFunction< @@ -212,6 +222,7 @@ export const GripperWizard = ( const { flowType, maintenanceRunId, + maintenanceRunStatus, createMaintenanceRun, handleCleanUpAndClose, handleClose, @@ -228,11 +239,8 @@ export const GripperWizard = ( const isOnDevice = useSelector(getIsOnDevice) const { t } = useTranslation('gripper_wizard_flows') const gripperWizardSteps = getGripperWizardSteps(flowType) - const [currentStepIndex, setCurrentStepIndex] = React.useState(0) - const [ - frontJawOffset, - setFrontJawOffset, - ] = React.useState(null) + const [currentStepIndex, setCurrentStepIndex] = useState(0) + const [frontJawOffset, setFrontJawOffset] = useState(null) const totalStepCount = gripperWizardSteps.length - 1 const currentStep = gripperWizardSteps?.[currentStepIndex] @@ -266,6 +274,7 @@ export const GripperWizard = ( } const sharedProps = { + maintenanceRunStatus, flowType, maintenanceRunId: maintenanceRunId != null && createdMaintenanceRunId === maintenanceRunId @@ -283,7 +292,7 @@ export const GripperWizard = ( let onExit if (currentStep == null) return null let modalContent: JSX.Element =
    UNASSIGNED STEP
    - if (showConfirmExit) { + if (showConfirmExit && maintenanceRunId !== null) { modalContent = ( ) - } else if (isExiting && errorMessage != null) { + } + // These flows often have custom error messaging, so this fallback modal is shown only in specific circumstances. + else if ( + (isExiting && errorMessage != null) || + maintenanceRunStatus === RUN_STATUS_FAILED || + (errorMessage != null && createdMaintenanceRunId == null) + ) { onExit = handleClose modalContent = ( ) } else if (currentStep.section === SECTIONS.BEFORE_BEGINNING) { diff --git a/app/src/organisms/HowCalibrationWorksModal/index.tsx b/app/src/organisms/HowCalibrationWorksModal/index.tsx deleted file mode 100644 index 9cdf4f18f4b..00000000000 --- a/app/src/organisms/HowCalibrationWorksModal/index.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' -import { css } from 'styled-components' - -import { - ALIGN_CENTER, - ALIGN_FLEX_END, - Box, - DIRECTION_COLUMN, - Flex, - PrimaryButton, - SPACING, - TEXT_TRANSFORM_CAPITALIZE, - TYPOGRAPHY, - Modal, - LegacyStyledText, -} from '@opentrons/components' - -import { getTopPortalEl } from '../../App/portal' -import RobotCalHelpImage from '../../assets/images/robot_calibration_help.png' -import { ExternalLink } from '../../atoms/Link/ExternalLink' -import { Divider } from '../../atoms/structure' - -const ROBOT_CAL_HELP_ARTICLE = - 'https://support.opentrons.com/s/article/How-positional-calibration-works-on-the-OT-2' -interface HowCalibrationWorksModalProps { - onCloseClick: () => unknown -} - -export function HowCalibrationWorksModal({ - onCloseClick, -}: HowCalibrationWorksModalProps): JSX.Element { - const { t } = useTranslation(['protocol_setup', 'shared']) - return createPortal( - - - - {t('robot_cal_description')} - - - {t('learn_more_about_robot_cal_link')} - - - - - {/* deck calibration */} - - {t('deck_calibration_title')} - - - - {t('tip_length_cal_title')} - - - {/* pipette offset calibration */} - - {t('pipette_offset_cal')} - - - - - {t('shared:close')} - - - , - getTopPortalEl() - ) -} - -interface CalibrationStepsProps { - description: string - steps: string[] -} -function CalibrationSteps({ - description, - steps, -}: CalibrationStepsProps): JSX.Element { - return ( - - - {description} - -
      - {steps.map(step => ( -
    • - {step} -
    • - ))} -
    -
    - ) -} diff --git a/app/src/organisms/IncompatibleModule/IncompatibleModuleDesktopModalBody.tsx b/app/src/organisms/IncompatibleModule/IncompatibleModuleDesktopModalBody.tsx index 0161a75cd05..a1fd7087b06 100644 --- a/app/src/organisms/IncompatibleModule/IncompatibleModuleDesktopModalBody.tsx +++ b/app/src/organisms/IncompatibleModule/IncompatibleModuleDesktopModalBody.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation, Trans } from 'react-i18next' import { DIRECTION_COLUMN, @@ -15,8 +14,8 @@ import { } from '@opentrons/components' import { getModuleDisplayName } from '@opentrons/shared-data' import type { AttachedModule } from '@opentrons/api-client' -import { useIsFlex } from '../Devices/hooks' -import { InterventionModal } from '../../molecules/InterventionModal' +import { useIsFlex } from '/app/redux-resources/robots' +import { InterventionModal } from '/app/molecules/InterventionModal' export interface IncompatibleModuleDesktopModalBodyProps { modules: AttachedModule[] robotName: string diff --git a/app/src/organisms/IncompatibleModule/IncompatibleModuleODDModalBody.tsx b/app/src/organisms/IncompatibleModule/IncompatibleModuleODDModalBody.tsx index 4b84490f83a..459871b4803 100644 --- a/app/src/organisms/IncompatibleModule/IncompatibleModuleODDModalBody.tsx +++ b/app/src/organisms/IncompatibleModule/IncompatibleModuleODDModalBody.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation, Trans } from 'react-i18next' import capitalize from 'lodash/capitalize' import { @@ -11,9 +10,9 @@ import { TYPOGRAPHY, } from '@opentrons/components' import { getModuleDisplayName } from '@opentrons/shared-data' -import { OddModal } from '../../molecules/OddModal' +import { OddModal } from '/app/molecules/OddModal' import type { AttachedModule } from '@opentrons/api-client' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' export interface IncompatibleModuleODDModalBodyProps { modules: AttachedModule[] } diff --git a/app/src/organisms/IncompatibleModule/IncompatibleModuleTakeover.tsx b/app/src/organisms/IncompatibleModule/IncompatibleModuleTakeover.tsx index 3be98ad14fc..b110ffb0a1e 100644 --- a/app/src/organisms/IncompatibleModule/IncompatibleModuleTakeover.tsx +++ b/app/src/organisms/IncompatibleModule/IncompatibleModuleTakeover.tsx @@ -1,8 +1,7 @@ -import * as React from 'react' import { createPortal } from 'react-dom' import { IncompatibleModuleODDModalBody } from './IncompatibleModuleODDModalBody' import { IncompatibleModuleDesktopModalBody } from './IncompatibleModuleDesktopModalBody' -import { getTopPortalEl, getModalPortalEl } from '../../App/portal' +import { getTopPortalEl, getModalPortalEl } from '/app/App/portal' import { useIncompatibleModulesAttached } from './hooks' const POLL_INTERVAL_MS = 5000 diff --git a/app/src/organisms/IncompatibleModule/__tests__/IncompatibleModuleDesktopModalBody.test.tsx b/app/src/organisms/IncompatibleModule/__tests__/IncompatibleModuleDesktopModalBody.test.tsx index b3d7fc7bbf3..41edc4439c6 100644 --- a/app/src/organisms/IncompatibleModule/__tests__/IncompatibleModuleDesktopModalBody.test.tsx +++ b/app/src/organisms/IncompatibleModule/__tests__/IncompatibleModuleDesktopModalBody.test.tsx @@ -1,15 +1,15 @@ -import React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, beforeEach, vi } from 'vitest' import { when } from 'vitest-when' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { IncompatibleModuleDesktopModalBody } from '../IncompatibleModuleDesktopModalBody' -import { useIsFlex } from '../../Devices/hooks' +import { useIsFlex } from '/app/redux-resources/robots' import * as Fixtures from '../__fixtures__' -vi.mock('../../Devices/hooks') +vi.mock('/app/redux-resources/robots') const getRenderer = (isFlex: boolean) => { when(useIsFlex).calledWith('otie').thenReturn(isFlex) diff --git a/app/src/organisms/IncompatibleModule/__tests__/IncompatibleModuleODDModalBody.test.tsx b/app/src/organisms/IncompatibleModule/__tests__/IncompatibleModuleODDModalBody.test.tsx index ce63b26ed88..e2d4e1a2af0 100644 --- a/app/src/organisms/IncompatibleModule/__tests__/IncompatibleModuleODDModalBody.test.tsx +++ b/app/src/organisms/IncompatibleModule/__tests__/IncompatibleModuleODDModalBody.test.tsx @@ -1,9 +1,9 @@ -import React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { IncompatibleModuleODDModalBody } from '../IncompatibleModuleODDModalBody' import * as Fixtures from '../__fixtures__' diff --git a/app/src/organisms/IncompatibleModule/__tests__/IncompatibleModuleTakeover.test.tsx b/app/src/organisms/IncompatibleModule/__tests__/IncompatibleModuleTakeover.test.tsx index 017f9f25c48..d3da5d17958 100644 --- a/app/src/organisms/IncompatibleModule/__tests__/IncompatibleModuleTakeover.test.tsx +++ b/app/src/organisms/IncompatibleModule/__tests__/IncompatibleModuleTakeover.test.tsx @@ -1,10 +1,10 @@ -import React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest' import { when } from 'vitest-when' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { IncompatibleModuleTakeover } from '../IncompatibleModuleTakeover' import { IncompatibleModuleODDModalBody } from '../IncompatibleModuleODDModalBody' import { IncompatibleModuleDesktopModalBody } from '../IncompatibleModuleDesktopModalBody' @@ -15,7 +15,7 @@ import { TopPortalRoot, MODAL_PORTAL_ID, TOP_PORTAL_ID, -} from '../../../App/portal' +} from '/app/App/portal' import * as Fixtures from '../__fixtures__' vi.mock('../hooks') diff --git a/app/src/organisms/IncompatibleModule/hooks/__tests__/useIncompatibleModulesAttached.test.tsx b/app/src/organisms/IncompatibleModule/hooks/__tests__/useIncompatibleModulesAttached.test.tsx index 970805c95c6..7e1a7342db1 100644 --- a/app/src/organisms/IncompatibleModule/hooks/__tests__/useIncompatibleModulesAttached.test.tsx +++ b/app/src/organisms/IncompatibleModule/hooks/__tests__/useIncompatibleModulesAttached.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { QueryClient, QueryClientProvider } from 'react-query' import { vi, it, expect, describe, beforeEach } from 'vitest' diff --git a/app/src/organisms/InstrumentInfo/index.tsx b/app/src/organisms/InstrumentInfo/index.tsx deleted file mode 100644 index ea7599dde76..00000000000 --- a/app/src/organisms/InstrumentInfo/index.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useNavigate } from 'react-router-dom' -import { - BORDERS, - COLORS, - DIRECTION_COLUMN, - Flex, - JUSTIFY_CENTER, - JUSTIFY_SPACE_BETWEEN, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' -import { - SINGLE_MOUNT_PIPETTES, - NINETY_SIX_CHANNEL, -} from '@opentrons/shared-data' -import { PipetteWizardFlows } from '../PipetteWizardFlows' -import { GripperWizardFlows } from '../GripperWizardFlows' -import { MediumButton } from '../../atoms/buttons' -import { FLOWS } from '../PipetteWizardFlows/constants' -import { GRIPPER_FLOW_TYPES } from '../GripperWizardFlows/constants' -import { formatTimeWithUtcLabel } from '../../resources/runs' - -import type { InstrumentData } from '@opentrons/api-client' -import type { PipetteMount } from '@opentrons/shared-data' -import type { StyleProps } from '@opentrons/components' -interface InstrumentInfoProps { - // NOTE: instrument will only be null while - // in the middle of detach wizard which occludes - // the main empty contents of this page - instrument: InstrumentData | null -} -export const InstrumentInfo = (props: InstrumentInfoProps): JSX.Element => { - const { t, i18n } = useTranslation('instruments_dashboard') - const { instrument } = props - const navigate = useNavigate() - const [wizardProps, setWizardProps] = React.useState< - | React.ComponentProps - | React.ComponentProps - | null - >(null) - - const sharedGripperWizardProps: Pick< - React.ComponentProps, - 'attachedGripper' | 'closeFlow' - > = { - attachedGripper: instrument, - closeFlow: () => { - setWizardProps(null) - }, - } - - const is96Channel = - instrument != null && - instrument.ok && - instrument.mount !== 'extension' && - instrument.data?.channels === 96 - - const handleDetach: React.MouseEventHandler = () => { - if (instrument != null && instrument.ok) { - setWizardProps( - instrument.mount === 'extension' - ? { - ...sharedGripperWizardProps, - flowType: GRIPPER_FLOW_TYPES.DETACH, - onComplete: () => { - navigate(-1) - }, - } - : { - closeFlow: () => { - setWizardProps(null) - }, - onComplete: () => { - navigate(-1) - }, - mount: instrument.mount as PipetteMount, - selectedPipette: is96Channel - ? NINETY_SIX_CHANNEL - : SINGLE_MOUNT_PIPETTES, - flowType: FLOWS.DETACH, - } - ) - } - } - const handleRecalibrate: React.MouseEventHandler = () => { - if (instrument != null && instrument.ok) { - setWizardProps( - instrument.mount === 'extension' - ? { - ...sharedGripperWizardProps, - flowType: GRIPPER_FLOW_TYPES.RECALIBRATE, - } - : { - closeFlow: () => { - setWizardProps(null) - }, - mount: instrument.mount as PipetteMount, - selectedPipette: is96Channel - ? NINETY_SIX_CHANNEL - : SINGLE_MOUNT_PIPETTES, - flowType: FLOWS.CALIBRATE, - } - ) - } - } - - return ( - - {instrument != null && instrument.ok ? ( - <> - - - {instrument.firmwareVersion != null && ( - - )} - - - - - {instrument.mount === 'extension' || - instrument.data.calibratedOffset?.last_modified == null ? ( - - ) : null} - - - ) : null} - {wizardProps != null && 'mount' in wizardProps ? ( - - ) : null} - {wizardProps != null && !('mount' in wizardProps) ? ( - - ) : null} - - ) -} - -interface InfoItemProps extends StyleProps { - label: string - value: string -} -function InfoItem(props: InfoItemProps): JSX.Element { - return ( - - - {props.label} - - - {props.value} - - - ) -} diff --git a/app/src/organisms/InstrumentMountItem/AttachedInstrumentMountItem.tsx b/app/src/organisms/InstrumentMountItem/AttachedInstrumentMountItem.tsx deleted file mode 100644 index d56a1af86ae..00000000000 --- a/app/src/organisms/InstrumentMountItem/AttachedInstrumentMountItem.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import * as React from 'react' -import { useNavigate } from 'react-router-dom' - -import { SINGLE_MOUNT_PIPETTES } from '@opentrons/shared-data' - -import { - useGripperDisplayName, - usePipetteModelSpecs, -} from '../../resources/instruments/hooks' -import { ChoosePipette } from '../PipetteWizardFlows/ChoosePipette' -import { FLOWS } from '../PipetteWizardFlows/constants' -import { GRIPPER_FLOW_TYPES } from '../GripperWizardFlows/constants' -import { LabeledMount } from './LabeledMount' - -import type { InstrumentData } from '@opentrons/api-client' -import type { GripperModel, PipetteModel } from '@opentrons/shared-data' -import type { Mount } from '../../redux/pipettes/types' -import type { SelectablePipettes } from '../PipetteWizardFlows/types' -import type { GripperWizardFlows } from '../GripperWizardFlows' -import type { PipetteWizardFlows } from '../PipetteWizardFlows' - -interface AttachedInstrumentMountItemProps { - mount: Mount | 'extension' - attachedInstrument: InstrumentData | null - setWizardProps: ( - props: - | React.ComponentProps - | React.ComponentProps - | null - ) => void -} - -export function AttachedInstrumentMountItem( - props: AttachedInstrumentMountItemProps -): JSX.Element { - const navigate = useNavigate() - const { mount, attachedInstrument, setWizardProps } = props - - const [showChoosePipetteModal, setShowChoosePipetteModal] = React.useState( - false - ) - const [ - selectedPipette, - setSelectedPipette, - ] = React.useState(SINGLE_MOUNT_PIPETTES) - - const handleClick: React.MouseEventHandler = () => { - if (attachedInstrument == null && mount !== 'extension') { - setShowChoosePipetteModal(true) - } else if (attachedInstrument == null && mount === 'extension') { - setWizardProps({ - flowType: GRIPPER_FLOW_TYPES.ATTACH, - attachedGripper: attachedInstrument, - onComplete: () => { - navigate( - attachedInstrument == null ? '/instruments' : `/instrument/${mount}` - ) - }, - closeFlow: () => { - setWizardProps(null) - }, - }) - } else { - navigate(`/instruments/${mount}`) - } - } - - const instrumentModel = attachedInstrument?.ok - ? attachedInstrument.instrumentModel - : null - - const pipetteDisplayName = - usePipetteModelSpecs(instrumentModel as PipetteModel)?.displayName ?? null - const gripperDisplayName = useGripperDisplayName( - instrumentModel as GripperModel - ) - - const displayName = - attachedInstrument?.ok && attachedInstrument?.mount === 'extension' - ? gripperDisplayName - : pipetteDisplayName - - return ( - <> - - {showChoosePipetteModal ? ( - { - setWizardProps({ - mount: mount as Mount, - flowType: FLOWS.ATTACH, - selectedPipette, - closeFlow: () => { - setWizardProps(null) - setSelectedPipette(SINGLE_MOUNT_PIPETTES) - setShowChoosePipetteModal(false) - }, - onComplete: () => { - navigate( - attachedInstrument == null - ? `/instruments` - : `/instrument/${mount}` - ) - }, - }) - setShowChoosePipetteModal(false) - }} - setSelectedPipette={setSelectedPipette} - selectedPipette={selectedPipette} - exit={() => { - setShowChoosePipetteModal(false) - }} - mount={mount as Mount} - /> - ) : null} - - ) -} diff --git a/app/src/organisms/InterventionModal/InterventionCommandMessage.tsx b/app/src/organisms/InterventionModal/InterventionCommandMessage.tsx index 97fc9e7f69c..d9770e4a621 100644 --- a/app/src/organisms/InterventionModal/InterventionCommandMessage.tsx +++ b/app/src/organisms/InterventionModal/InterventionCommandMessage.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' diff --git a/app/src/organisms/InterventionModal/InterventionModal.stories.tsx b/app/src/organisms/InterventionModal/InterventionModal.stories.tsx index a7bbfddab3c..ef378f49f70 100644 --- a/app/src/organisms/InterventionModal/InterventionModal.stories.tsx +++ b/app/src/organisms/InterventionModal/InterventionModal.stories.tsx @@ -1,18 +1,18 @@ -import * as React from 'react' +import type * as React from 'react' import { Provider } from 'react-redux' import { createStore } from 'redux' import { QueryClient, QueryClientProvider } from 'react-query' import { fixture96Plate } from '@opentrons/shared-data' -import { configReducer } from '../../redux/config/reducer' +import { configReducer } from '/app/redux/config/reducer' import { mockRunData } from './__fixtures__' -import { mockConnectableRobot } from '../../redux/discovery/__fixtures__' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' import * as DiscoveryClientFixtures from '../../../../discovery-client/src/fixtures' import { HEALTH_STATUS_OK, ROBOT_MODEL_OT3, -} from '../../redux/discovery/constants' +} from '/app/redux/discovery/constants' import { InterventionModal as InterventionModalComponent } from './' import type { Store, StoreEnhancer } from 'redux' diff --git a/app/src/organisms/InterventionModal/LabwareDisabledOverlay.tsx b/app/src/organisms/InterventionModal/LabwareDisabledOverlay.tsx index f301571f4f5..30c742c42fe 100644 --- a/app/src/organisms/InterventionModal/LabwareDisabledOverlay.tsx +++ b/app/src/organisms/InterventionModal/LabwareDisabledOverlay.tsx @@ -1,5 +1,3 @@ -import * as React from 'react' - import { COLORS } from '@opentrons/components' import type { LabwareDefinition2 } from '@opentrons/shared-data' diff --git a/app/src/organisms/InterventionModal/MoveLabwareInterventionContent.tsx b/app/src/organisms/InterventionModal/MoveLabwareInterventionContent.tsx index 4f2a760582c..af561b6c15d 100644 --- a/app/src/organisms/InterventionModal/MoveLabwareInterventionContent.tsx +++ b/app/src/organisms/InterventionModal/MoveLabwareInterventionContent.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' @@ -38,12 +37,10 @@ import { getLabwareNameFromRunData, getModuleModelFromRunData, } from './utils' -import { Divider } from '../../atoms/structure' -import { - getLoadedLabware, - getLoadedModule, -} from '../../molecules/Command/utils/accessors' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' +import { Divider } from '/app/atoms/structure' +import { getLoadedModule } from '/app/local-resources/modules' +import { getLoadedLabware } from '/app/local-resources/labware' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' import type { CompletedProtocolAnalysis, @@ -136,7 +133,7 @@ export function MoveLabwareInterventionContent({ deckDef ) const oldLabwareLocation = - getLoadedLabware(run, command.params.labwareId)?.location ?? null + getLoadedLabware(run.labware, command.params.labwareId)?.location ?? null const labwareName = getLabwareNameFromRunData( run, @@ -276,8 +273,8 @@ function LabwareDisplayLocation( console.warn('labware is located on an unknown module model') } else { const slotName = - getLoadedModule(protocolData, location.moduleId)?.location?.slotName ?? - '' + getLoadedModule(protocolData.modules, location.moduleId)?.location + ?.slotName ?? '' const isModuleUnderAdapterThermocycler = getModuleType(moduleModel) === THERMOCYCLER_MODULE_TYPE if (isModuleUnderAdapterThermocycler) { @@ -310,8 +307,8 @@ function LabwareDisplayLocation( console.warn('labware is located on an adapter on an unknown module') } else { const slotName = - getLoadedModule(protocolData, adapter.location.moduleId)?.location - ?.slotName ?? '' + getLoadedModule(protocolData.modules, adapter.location.moduleId) + ?.location?.slotName ?? '' const isModuleUnderAdapterThermocycler = getModuleType(moduleModel) === THERMOCYCLER_MODULE_TYPE if (isModuleUnderAdapterThermocycler) { diff --git a/app/src/organisms/InterventionModal/PauseInterventionContent.tsx b/app/src/organisms/InterventionModal/PauseInterventionContent.tsx index f3f617f20fd..bb39eadb698 100644 --- a/app/src/organisms/InterventionModal/PauseInterventionContent.tsx +++ b/app/src/organisms/InterventionModal/PauseInterventionContent.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' @@ -10,13 +10,12 @@ import { Flex, RESPONSIVENESS, SPACING, - TYPOGRAPHY, useInterval, - LegacyStyledText, + StyledText, } from '@opentrons/components' -import { EMPTY_TIMESTAMP } from '../Devices/constants' -import { formatInterval } from '../RunTimeControl/utils' +import { EMPTY_TIMESTAMP } from '/app/resources/runs' +import { formatInterval } from '/app/transformations/commands' import { InterventionCommandMessage } from './InterventionCommandMessage' const PAUSE_INTERVENTION_CONTENT_STYLE = css` @@ -61,27 +60,13 @@ const PAUSE_HEADER_STYLE = css` } ` -const PAUSE_TEXT_STYLE = css` - ${TYPOGRAPHY.h1Default} - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - ${TYPOGRAPHY.level4HeaderSemiBold} - } -` - -const PAUSE_TIME_STYLE = css` - ${TYPOGRAPHY.h1Default} - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - ${TYPOGRAPHY.level1Header} - } -` - interface PauseHeaderProps { startedAt: string | null } function PauseHeader({ startedAt }: PauseHeaderProps): JSX.Element { const { t, i18n } = useTranslation('run_details') - const [now, setNow] = React.useState(Date()) + const [now, setNow] = useState(Date()) useInterval( () => { setNow(Date()) @@ -95,10 +80,15 @@ function PauseHeader({ startedAt }: PauseHeaderProps): JSX.Element { return ( - + {i18n.format(t('paused_for'), 'capitalize')} - - {runTime} + + + {runTime} + ) } diff --git a/app/src/organisms/InterventionModal/__tests__/InterventionCommandMesage.test.tsx b/app/src/organisms/InterventionModal/__tests__/InterventionCommandMesage.test.tsx index 979fcda6edc..6f3a688b808 100644 --- a/app/src/organisms/InterventionModal/__tests__/InterventionCommandMesage.test.tsx +++ b/app/src/organisms/InterventionModal/__tests__/InterventionCommandMesage.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { InterventionCommandMessage } from '../InterventionCommandMessage' import { longCommandMessage, diff --git a/app/src/organisms/InterventionModal/__tests__/InterventionCommandMessage.test.tsx b/app/src/organisms/InterventionModal/__tests__/InterventionCommandMessage.test.tsx index 979fcda6edc..6f3a688b808 100644 --- a/app/src/organisms/InterventionModal/__tests__/InterventionCommandMessage.test.tsx +++ b/app/src/organisms/InterventionModal/__tests__/InterventionCommandMessage.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { InterventionCommandMessage } from '../InterventionCommandMessage' import { longCommandMessage, diff --git a/app/src/organisms/InterventionModal/__tests__/InterventionModal.test.tsx b/app/src/organisms/InterventionModal/__tests__/InterventionModal.test.tsx index e1a6830d251..59b8c659a1a 100644 --- a/app/src/organisms/InterventionModal/__tests__/InterventionModal.test.tsx +++ b/app/src/organisms/InterventionModal/__tests__/InterventionModal.test.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, renderHook, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import { RUN_STATUS_RUNNING, RUN_STATUS_STOPPED } from '@opentrons/api-client' import { getLabwareDefURI } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { mockTipRackDefinition } from '../../../redux/custom-labware/__fixtures__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { mockTipRackDefinition } from '/app/redux/custom-labware/__fixtures__' +import { i18n } from '/app/i18n' import { mockPauseCommandWithoutStartTime, mockPauseCommandWithStartTime, @@ -16,7 +16,7 @@ import { truncatedCommandMessage, } from '../__fixtures__' import { InterventionModal, useInterventionModal } from '..' -import { useIsFlex } from '../../Devices/hooks' +import { useIsFlex } from '/app/redux-resources/robots' import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' import type { RunData } from '@opentrons/api-client' @@ -25,7 +25,7 @@ const ROBOT_NAME = 'Otie' const mockOnResumeHandler = vi.fn() -vi.mock('../../Devices/hooks') +vi.mock('/app/redux-resources/robots') describe('useInterventionModal', () => { const defaultProps = { @@ -34,6 +34,7 @@ describe('useInterventionModal', () => { runStatus: RUN_STATUS_RUNNING, robotName: 'TestRobot', analysis: null, + doorIsOpen: false, } it('should return showModal true when conditions are met', () => { @@ -80,6 +81,13 @@ describe('useInterventionModal', () => { analysis: null, }) }) + it('should return showModal true and an alternate footer when door is open', () => { + const { result } = renderHook(() => + useInterventionModal({ ...defaultProps, doorIsOpen: true }) + ) + expect(result.current.showModal).toBe(true) + expect(result.current.modalProps?.alternateFooterContent).toBeTruthy() + }) }) const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/InterventionModal/__tests__/LabwareDisabledOverlay.test.tsx b/app/src/organisms/InterventionModal/__tests__/LabwareDisabledOverlay.test.tsx index b217edb116d..d6b8760f368 100644 --- a/app/src/organisms/InterventionModal/__tests__/LabwareDisabledOverlay.test.tsx +++ b/app/src/organisms/InterventionModal/__tests__/LabwareDisabledOverlay.test.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { render, screen } from '@testing-library/react' import { describe, it, expect } from 'vitest' import { COLORS } from '@opentrons/components' diff --git a/app/src/organisms/InterventionModal/index.tsx b/app/src/organisms/InterventionModal/index.tsx index ae7fe46acef..864bc99a231 100644 --- a/app/src/organisms/InterventionModal/index.tsx +++ b/app/src/organisms/InterventionModal/index.tsx @@ -1,6 +1,6 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' +import { css } from 'styled-components' import { ALIGN_CENTER, @@ -13,11 +13,13 @@ import { Flex, Icon, JUSTIFY_SPACE_BETWEEN, + JUSTIFY_CENTER, Link, PrimaryButton, SPACING, TYPOGRAPHY, LegacyStyledText, + RESPONSIVENESS, } from '@opentrons/components' import { RUN_STATUS_FAILED, @@ -26,15 +28,17 @@ import { RUN_STATUS_SUCCEEDED, } from '@opentrons/api-client' -import { SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' -import { InterventionModal as InterventionModalMolecule } from '../../molecules/InterventionModal' -import { getIsOnDevice } from '../../redux/config' +import { SmallButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' +import { InterventionModal as InterventionModalMolecule } from '/app/molecules/InterventionModal' +import { getIsOnDevice } from '/app/redux/config' import { PauseInterventionContent } from './PauseInterventionContent' import { MoveLabwareInterventionContent } from './MoveLabwareInterventionContent' import { isInterventionCommand } from './utils' -import { useRobotType } from '../Devices/hooks' +import { useRobotType } from '/app/redux-resources/robots' +import { InlineNotification } from '/app/atoms/InlineNotification' +import type { ReactNode } from 'react' import type { IconName } from '@opentrons/components' import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' import type { @@ -56,6 +60,7 @@ export interface UseInterventionModalProps { runStatus: RunStatus | null robotName: string | null analysis: CompletedProtocolAnalysis | null + doorIsOpen: boolean } export type UseInterventionModalResult = @@ -69,6 +74,7 @@ export function useInterventionModal({ runStatus, robotName, analysis, + doorIsOpen, }: UseInterventionModalProps): UseInterventionModalResult { const isValidIntervention = lastRunCommand != null && @@ -77,6 +83,7 @@ export function useInterventionModal({ runData != null && runStatus != null && !TERMINAL_RUN_STATUSES.includes(runStatus) + const { t } = useTranslation('run_details') if (!isValidIntervention) { return { showModal: false, modalProps: null } @@ -88,6 +95,23 @@ export function useInterventionModal({ run: runData, robotName, analysis, + alternateFooterContent: doorIsOpen ? ( + + + + ) : undefined, }, } } @@ -99,6 +123,7 @@ export interface InterventionModalProps { command: RunCommandSummary run: RunData analysis: CompletedProtocolAnalysis | null + alternateFooterContent?: ReactNode } export function InterventionModal({ @@ -107,12 +132,14 @@ export function InterventionModal({ command, run, analysis, + alternateFooterContent, }: InterventionModalProps): JSX.Element { const { t } = useTranslation(['protocol_command_text', 'protocol_info']) const isOnDevice = useSelector(getIsOnDevice) const robotType = useRobotType(robotName) - const childContent = React.useMemo(() => { + // TODO(jh 09-19-24): Make this into its own component. + const childContent = (() => { switch (command.commandType) { case 'waitForResume': case 'pause': // legacy pause command @@ -136,14 +163,9 @@ export function InterventionModal({ ) return null } - }, [ - command.id, - analysis?.status, - run.labware.map(l => l.id).join(), - run.modules.map(m => m.id).join(), - ]) - - const { iconName, headerTitle, headerTitleOnDevice } = (() => { + })() + + const { iconName, headerTitle, headerTitleOnDevice, iconSize } = (() => { switch (command.commandType) { case 'waitForResume': case 'pause': @@ -151,12 +173,14 @@ export function InterventionModal({ iconName: 'pause-circle' as IconName, headerTitle: t('pause_on', { robot_name: robotName }), headerTitleOnDevice: t('pause'), + iconSize: SPACING.spacing32, } case 'moveLabware': return { iconName: 'move-xy-circle' as IconName, headerTitle: t('move_labware_on', { robot_name: robotName }), headerTitleOnDevice: t('move_labware'), + iconSize: SPACING.spacing32, } default: console.warn( @@ -167,6 +191,7 @@ export function InterventionModal({ iconName: null, headerTitle: '', headerTitleOnDevice: '', + iconSize: undefined, } } })() @@ -192,11 +217,13 @@ export function InterventionModal({ width="100%" > {childContent} - + {alternateFooterContent ?? ( + + )} ) : ( @@ -204,26 +231,29 @@ export function InterventionModal({ iconHeading={{headerTitle}} iconName={iconName} type="intervention-required" + iconSize={iconSize} > {childContent} - - - {t('protocol_info:manual_steps_learn_more')} - - - - {t('confirm_and_resume')} - - + {alternateFooterContent ?? ( + + + {t('protocol_info:manual_steps_learn_more')} + + + + {t('confirm_and_resume')} + + + )} ) diff --git a/app/src/organisms/InterventionModal/utils/getLabwareNameFromRunData.ts b/app/src/organisms/InterventionModal/utils/getLabwareNameFromRunData.ts index 1c1831a1ff7..55b48efee29 100644 --- a/app/src/organisms/InterventionModal/utils/getLabwareNameFromRunData.ts +++ b/app/src/organisms/InterventionModal/utils/getLabwareNameFromRunData.ts @@ -1,6 +1,8 @@ import { getLabwareDefURI, getLabwareDisplayName } from '@opentrons/shared-data' -import { getLoadedLabware } from '../../../molecules/Command/utils/accessors' -import { getLabwareDefinitionsFromCommands } from '../../../molecules/Command/utils/getLabwareDefinitionsFromCommands' +import { + getLoadedLabware, + getLabwareDefinitionsFromCommands, +} from '/app/local-resources/labware' import type { RunTimeCommand } from '@opentrons/shared-data' import type { RunData } from '@opentrons/api-client' @@ -15,7 +17,7 @@ export function getLabwareNameFromRunData( labwareId: string, commands: RunTimeCommand[] ): string { - const loadedLabware = getLoadedLabware(protocolData, labwareId) + const loadedLabware = getLoadedLabware(protocolData.labware, labwareId) if (loadedLabware == null) { return '' } else if (FIXED_TRASH_DEF_URIS.includes(loadedLabware.definitionUri)) { diff --git a/app/src/organisms/InterventionModal/utils/getModuleDisplayLocationFromRunData.ts b/app/src/organisms/InterventionModal/utils/getModuleDisplayLocationFromRunData.ts index cbf58696040..0b96641e9e5 100644 --- a/app/src/organisms/InterventionModal/utils/getModuleDisplayLocationFromRunData.ts +++ b/app/src/organisms/InterventionModal/utils/getModuleDisplayLocationFromRunData.ts @@ -1,4 +1,4 @@ -import { getLoadedModule } from '../../../molecules/Command/utils/accessors' +import { getLoadedModule } from '/app/local-resources/modules' import type { RunData } from '@opentrons/api-client' @@ -6,6 +6,6 @@ export function getModuleDisplayLocationFromRunData( protocolData: RunData, moduleId: string ): string { - const loadedModule = getLoadedModule(protocolData, moduleId) + const loadedModule = getLoadedModule(protocolData.modules, moduleId) return loadedModule != null ? loadedModule.location.slotName : '' } diff --git a/app/src/organisms/InterventionModal/utils/getModuleModelFromRunData.ts b/app/src/organisms/InterventionModal/utils/getModuleModelFromRunData.ts index 21c2087a9e3..e22c1895918 100644 --- a/app/src/organisms/InterventionModal/utils/getModuleModelFromRunData.ts +++ b/app/src/organisms/InterventionModal/utils/getModuleModelFromRunData.ts @@ -1,4 +1,4 @@ -import { getLoadedModule } from '../../../molecules/Command/utils/accessors' +import { getLoadedModule } from '/app/local-resources/modules' import type { RunData } from '@opentrons/api-client' import type { ModuleModel } from '@opentrons/shared-data' @@ -7,6 +7,6 @@ export function getModuleModelFromRunData( protocolData: RunData, moduleId: string ): ModuleModel | null { - const loadedModule = getLoadedModule(protocolData, moduleId) + const loadedModule = getLoadedModule(protocolData.modules, moduleId) return loadedModule != null ? loadedModule.model : null } diff --git a/app/src/organisms/LabwareCard/hooks.tsx b/app/src/organisms/LabwareCard/hooks.tsx deleted file mode 100644 index 62e0589ac51..00000000000 --- a/app/src/organisms/LabwareCard/hooks.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import * as React from 'react' - -export function useCloseOnOutsideClick( - ref: React.RefObject, - onClose: () => void -): void { - const handleClick = (e: MouseEvent): void => { - // @ts-expect-error node and event target types are mismatched - if (ref.current != null && !ref.current.contains(e.target)) { - onClose() - } - } - - React.useEffect(() => { - document.addEventListener('click', handleClick) - return () => { - document.removeEventListener('click', handleClick) - } - }) -} diff --git a/app/src/organisms/LabwareCard/index.tsx b/app/src/organisms/LabwareCard/index.tsx deleted file mode 100644 index c00a0362680..00000000000 --- a/app/src/organisms/LabwareCard/index.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import startCase from 'lodash/startCase' -import { format } from 'date-fns' - -import { - ALIGN_CENTER, - ALIGN_FLEX_END, - Box, - COLORS, - DIRECTION_COLUMN, - Flex, - Icon, - JUSTIFY_SPACE_BETWEEN, - LabwareRender, - OVERFLOW_WRAP_ANYWHERE, - RobotWorkSpace, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' - -import { UNIVERSAL_FLAT_ADAPTER_X_DIMENSION } from '../LabwareDetails/Gallery' -import { CustomLabwareOverflowMenu } from './CustomLabwareOverflowMenu' -import type { LabwareDefAndDate } from '../../pages/Labware/hooks' - -export interface LabwareCardProps { - labware: LabwareDefAndDate - onClick: () => void -} - -export function LabwareCard(props: LabwareCardProps): JSX.Element { - const { t } = useTranslation(['labware_landing', 'branded']) - const { definition, modified, filename } = props.labware - const apiName = definition.parameters.loadName - const displayName = definition?.metadata.displayName - const displayCategory = startCase(definition.metadata.displayCategory) - const isCustomDefinition = definition.namespace !== 'opentrons' - const xDimensionOverride = - definition.parameters.loadName === 'opentrons_universal_flat_adapter' - ? UNIVERSAL_FLAT_ADAPTER_X_DIMENSION - : definition.dimensions.xDimension - - return ( - - - - {() => } - - - {/* labware category name min:7.5 rem for the longest, Aluminum Block */} - - - {displayCategory} - - - {/* labware info */} - - - - - {displayName} - - {isCustomDefinition ? ( - - {t('custom_def')} - - ) : ( - - - - {t('branded:opentrons_def')} - - - )} - - - - {t('api_name')} - - - - {apiName} - - - - - {/* space for custom labware min: 3rem for date */} - {/* Note kj 06/30/2022 currently this section would not be ideal implementation - Once the team have an agreement for grid system, we could refactor */} - - {modified != null && filename != null && ( - - - - - {t('date_added')} - - - {format(new Date(modified), 'MM/dd/yyyy')} - - - - )} - - - ) -} diff --git a/app/src/organisms/LabwareDetails/StyledComponents/LabeledValue.tsx b/app/src/organisms/LabwareDetails/StyledComponents/LabeledValue.tsx deleted file mode 100644 index c629481501c..00000000000 --- a/app/src/organisms/LabwareDetails/StyledComponents/LabeledValue.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import * as React from 'react' -import { - ALIGN_CENTER, - COLORS, - DIRECTION_ROW, - Flex, - JUSTIFY_SPACE_BETWEEN, - SPACING, - LegacyStyledText, -} from '@opentrons/components' - -export interface LabeledValueProps { - label: string - value: number | string -} - -export function LabeledValue({ label, value }: LabeledValueProps): JSX.Element { - return ( - - - {label} - - {value} - - ) -} diff --git a/app/src/organisms/LabwareDetails/images/eppendorf_1000ul_tip_eptips_side_view.jpg b/app/src/organisms/LabwareDetails/images/eppendorf_1000ul_tip_eptips_side_view.jpg deleted file mode 100644 index 9aaaf771e12..00000000000 Binary files a/app/src/organisms/LabwareDetails/images/eppendorf_1000ul_tip_eptips_side_view.jpg and /dev/null differ diff --git a/app/src/organisms/LabwareDetails/images/eppendorf_10ul_tips_eptips_side_view.jpg b/app/src/organisms/LabwareDetails/images/eppendorf_10ul_tips_eptips_side_view.jpg deleted file mode 100644 index 718c6bd1ac6..00000000000 Binary files a/app/src/organisms/LabwareDetails/images/eppendorf_10ul_tips_eptips_side_view.jpg and /dev/null differ diff --git a/app/src/organisms/LabwareDetails/images/geb_96_tiprack_1000ul_side_view.jpg b/app/src/organisms/LabwareDetails/images/geb_96_tiprack_1000ul_side_view.jpg deleted file mode 100644 index c201e8363fb..00000000000 Binary files a/app/src/organisms/LabwareDetails/images/geb_96_tiprack_1000ul_side_view.jpg and /dev/null differ diff --git a/app/src/organisms/LabwareDetails/images/geb_96_tiprack_10ul_side_view.jpg b/app/src/organisms/LabwareDetails/images/geb_96_tiprack_10ul_side_view.jpg deleted file mode 100644 index 2273fa6248f..00000000000 Binary files a/app/src/organisms/LabwareDetails/images/geb_96_tiprack_10ul_side_view.jpg and /dev/null differ diff --git a/app/src/organisms/LabwareDetails/images/opentrons_96_tiprack_1000ul_side_view.jpg b/app/src/organisms/LabwareDetails/images/opentrons_96_tiprack_1000ul_side_view.jpg deleted file mode 100644 index eccd58b346d..00000000000 Binary files a/app/src/organisms/LabwareDetails/images/opentrons_96_tiprack_1000ul_side_view.jpg and /dev/null differ diff --git a/app/src/organisms/LabwareDetails/images/opentrons_96_tiprack_10ul_side_view.jpg b/app/src/organisms/LabwareDetails/images/opentrons_96_tiprack_10ul_side_view.jpg deleted file mode 100644 index cfcd1521639..00000000000 Binary files a/app/src/organisms/LabwareDetails/images/opentrons_96_tiprack_10ul_side_view.jpg and /dev/null differ diff --git a/app/src/organisms/LabwareDetails/images/opentrons_96_tiprack_300ul_side_view.jpg b/app/src/organisms/LabwareDetails/images/opentrons_96_tiprack_300ul_side_view.jpg deleted file mode 100644 index 9ac4d287cf2..00000000000 Binary files a/app/src/organisms/LabwareDetails/images/opentrons_96_tiprack_300ul_side_view.jpg and /dev/null differ diff --git a/app/src/organisms/LabwareDetails/images/tipone_96_tiprack_200ul_side_view.jpg b/app/src/organisms/LabwareDetails/images/tipone_96_tiprack_200ul_side_view.jpg deleted file mode 100644 index 45a5adc05ad..00000000000 Binary files a/app/src/organisms/LabwareDetails/images/tipone_96_tiprack_200ul_side_view.jpg and /dev/null differ diff --git a/app/src/organisms/LabwareDetails/index.tsx b/app/src/organisms/LabwareDetails/index.tsx deleted file mode 100644 index dbc2ff0bd39..00000000000 --- a/app/src/organisms/LabwareDetails/index.tsx +++ /dev/null @@ -1,257 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { format } from 'date-fns' -import { css } from 'styled-components' - -import { - ALIGN_CENTER, - BORDERS, - Box, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - Icon, - JUSTIFY_SPACE_BETWEEN, - LegacyStyledText, - Link, - OVERFLOW_WRAP_ANYWHERE, - SIZE_1, - SPACING, - TOOLTIP_TOP_START, - Tooltip, - TYPOGRAPHY, - useHoverTooltip, -} from '@opentrons/components' -import { getUniqueWellProperties } from '@opentrons/shared-data' -import { Slideout } from '../../atoms/Slideout' -import { getWellLabel } from './helpers/labels' -import { WellCount } from './WellCount' -import { WellProperties } from './WellProperties' -import { Dimensions } from './Dimensions' -import { WellDimensions } from './WellDimensions' -import { WellSpacing } from './WellSpacing' -import { ManufacturerDetails } from './ManufacturerDetails' -import { InsertDetails } from './InsertDetails' -import { Gallery } from './Gallery' -import { CustomLabwareOverflowMenu } from '../LabwareCard/CustomLabwareOverflowMenu' -import type { LabwareDefAndDate } from '../../pages/Labware/hooks' - -const CLOSE_ICON_STYLE = css` - border-radius: 50%; - - &:hover { - background: ${COLORS.grey30}; - } - &:active { - background: ${COLORS.grey35}; - } -` - -const COPY_ICON_STYLE = css` - transform: translateY(${SPACING.spacing4}); - &:hover { - color: ${COLORS.blue50}; - } - &:active, - &:focus { - color: ${COLORS.black90}; - } -` - -export interface LabwareDetailsProps { - onClose: () => void - labware: LabwareDefAndDate -} - -export function LabwareDetails(props: LabwareDetailsProps): JSX.Element { - const { t } = useTranslation(['labware_landing', 'branded']) - const { definition, modified, filename } = props.labware - const { metadata, parameters, brand, wells, ordering } = definition - const apiName = definition.parameters.loadName - const { displayVolumeUnits } = metadata - const wellGroups = getUniqueWellProperties(definition) - const wellLabel = getWellLabel(definition) - const hasInserts = wellGroups.some(g => g.metadata.displayCategory) - const insert = wellGroups.find(g => g.metadata.displayCategory) - const insertCategory = insert?.metadata.displayCategory - const irregular = wellGroups.length > 1 - const isMultiRow = ordering.some(row => row.length > 1) - const isCustomDefinition = definition.namespace !== 'opentrons' - const [showToolTip, setShowToolTip] = React.useState(false) - const [targetProps, tooltipProps] = useHoverTooltip({ - placement: TOOLTIP_TOP_START, - }) - - const handleCopy = async (): Promise => { - await navigator.clipboard.writeText(apiName) - setShowToolTip(true) - } - - React.useEffect(() => { - const timer = setTimeout(() => { - setShowToolTip(false) - }, 2000) - return () => { - clearTimeout(timer) - } - }, [showToolTip]) - - const slideoutHeader = ( - - - - {props.labware.definition.metadata.displayName} - - - - - - {!isCustomDefinition && ( - - {' '} - - {t('branded:opentrons_def')} - - - )} - {modified != null && filename != null && ( - - - {t('last_updated')} {format(new Date(modified), 'MM/dd/yyyy')} - - - - )} - - ) - - return ( - - - - {t('api_name')} - - - - {apiName} - - - - - - {showToolTip && ( - - {t('copied')} - - )} - - - - - - {!hasInserts && !irregular && ( - - )} - - {wellGroups.map((wellProps, index) => { - const { metadata: groupMetadata } = wellProps - const wellLabel = getWellLabel(wellProps, definition) - const groupDisplaySuffix = - groupMetadata.displayName != null - ? ` - ${String(groupMetadata.displayName)}` - : '' - - return ( - - {groupMetadata.displayCategory == null && irregular && ( - <> - - - - )} - {groupMetadata.displayCategory == null && ( - - )} - - - ) - })} - - - - {hasInserts && } - - ) -} diff --git a/app/src/organisms/LabwareDetails/labware-images.ts b/app/src/organisms/LabwareDetails/labware-images.ts deleted file mode 100644 index 294ec2f1420..00000000000 --- a/app/src/organisms/LabwareDetails/labware-images.ts +++ /dev/null @@ -1,258 +0,0 @@ -// images by labware load name -// TODO(mc, 2019-05-29): shared-data? components-library? - -import agilent_1_reservoir_290ml_side_view from './images/agilent_1_reservoir_290ml_side_view.jpg' -import axygen_1_reservoir_90ml_side_view from './images/axygen_1_reservoir_90ml_side_view.jpg' -import biorad_96_wellplate_200ul_pcr_photo_three_quarters from './images/biorad_96_wellplate_200ul_pcr_photo_three_quarters.jpg' -import corning_12_wellplate_6_9ml_flat_photo_three_quarters from './images/corning_12_wellplate_6.9ml_flat_photo_three_quarters.jpg' -import corning_24_wellplate_3_4ml_flat_photo_three_quarters from './images/corning_24_wellplate_3.4ml_flat_photo_three_quarters.jpg' -import corning_384_wellplate_112ul_flat_photo_three_quarters from './images/corning_384_wellplate_112ul_flat_photo_three_quarters.jpg' -import corning_96_wellplate_360ul_flat_three_quarters from './images/corning_96_wellplate_360ul_flat_three_quarters.jpg' -import corning_48_wellplate_1_6ml_flat_photo_three_quarters from './images/corning_48_wellplate_1.6ml_flat_photo_three_quarters.jpg' -import corning_6_wellplate_16_8ml_flat_photo_three_quarters from './images/corning_6_wellplate_16.8ml_flat_photo_three_quarters.jpg' -import eppendorf_1000ul_tip_eptips_side_view from './images/eppendorf_1000ul_tip_eptips_side_view.jpg' -import eppendorf_10ul_tips_eptips_side_view from './images/eppendorf_10ul_tips_eptips_side_view.jpg' -import geb_96_tiprack_1000ul_side_view from './images/geb_96_tiprack_1000ul_side_view.jpg' -import geb_1000ul_tip_side_view from './images/geb_1000ul_tip_side_view.jpg' -import geb_96_tiprack_10ul_side_view from './images/geb_96_tiprack_10ul_side_view.jpg' -import geb_10ul_tip_side_view from './images/geb_10ul_tip_side_view.jpg' -import nest_1_reservoir_195ml_three_quarters from './images/nest_1_reservoir_195ml_three_quarters.jpg' -import nest_1_reservoir_290ml from './images/nest_1_reservoir_290ml.jpg' -import nest_12_reservoir_15ml_three_quarters from './images/nest_12_reservoir_15ml_three_quarters.jpg' -import nest_96_wellplate_100ul_pcr_full_skirt_three_quarters from './images/nest_96_wellplate_100ul_pcr_full_skirt_three_quarters.jpg' -import nest_96_wellplate_200ul_flat_three_quarters from './images/nest_96_wellplate_200ul_flat_three_quarters.jpg' -import nest_96_wellplate_2ml_deep from './images/nest_96_wellplate_2ml_deep.jpg' -import opentrons_10_tuberack_4_6_side_view from './images/opentrons_10_tuberack_4_6_side_view.jpg' -import falcon_50ml_15ml_conical_tubes from './images/falcon_50ml_15ml_conical_tubes.jpg' -import opentrons_15_tuberack_side_view from './images/opentrons_15_tuberack_side_view.jpg' -import falcon_15ml_conical_tube from './images/falcon_15ml_conical_tube.jpg' -import nest_50ml_15ml_conical_tubes from './images/nest_50ml_15ml_conical_tubes.jpg' -import nest_15ml_conical_tube from './images/nest_15ml_conical_tube.jpg' -import opentrons_6_tuberack_side_view from './images/opentrons_6_tuberack_side_view.jpg' -import nest_50ml_conical_tube from './images/nest_50ml_conical_tube.jpg' -import opentrons_24_aluminumblock_side_view from './images/opentrons_24_aluminumblock_side_view.jpg' -import generic_2ml_screwcap_tube from './images/generic_2ml_screwcap_tube.jpg' -import nest_0_5ml_screwcap_tube from './images/nest_0.5ml_screwcap_tube.jpg' -import nest_1_5ml_screwcap_tube from './images/nest_1.5ml_screwcap_tube.jpg' -import nest_1_5ml_snapcap_tube from './images/nest_1.5ml_snapcap_tube.jpg' -import nest_2ml_screwcap_tube from './images/nest_2ml_screwcap_tube.jpg' -import nest_2ml_snapcap_tube from './images/nest_2ml_snapcap_tube.jpg' -import opentrons_24_tuberack_side_view from './images/opentrons_24_tuberack_side_view.jpg' -import eppendorf_1_5ml_safelock_snapcap_tube from './images/eppendorf_1.5ml_safelock_snapcap_tube.jpg' -import eppendorf_2ml_safelock_snapcap_tube from './images/eppendorf_2ml_safelock_snapcap_tube.jpg' -import falcon_50ml_conical_tube from './images/falcon_50ml_conical_tube.jpg' -import generic_pcr_strip_200ul_tubes from './images/generic_pcr_strip_200ul_tubes.jpg' -import opentrons_96_tiprack_1000ul_side_view from './images/opentrons_96_tiprack_1000ul_side_view.jpg' -import opentrons_96_tiprack_10ul_side_view from './images/opentrons_96_tiprack_10ul_side_view.jpg' -import opentrons_96_tiprack_300ul_side_view from './images/opentrons_96_tiprack_300ul_side_view.jpg' -import tipone_96_tiprack_200ul_side_view from './images/tipone_96_tiprack_200ul_side_view.jpg' -import tipone_200ul_tip_side_view from './images/tipone_200ul_tip_side_view.jpg' -import usascientific_12_reservoir_22ml_side_view from './images/usascientific_12_reservoir_22ml_side_view.jpg' -import usascientific_96_wellplate_2_4ml_deep_side_view from './images/usascientific_96_wellplate_2.4ml_deep_side_view.jpg' -import thermoscientificnunc_96_wellplate_1300ul from './images/thermoscientificnunc_96_wellplate_1300ul.jpg' -import thermoscientificnunc_96_wellplate_2000ul from './images/thermoscientificnunc_96_wellplate_2000ul.jpg' -import appliedbiosystemsmicroamp_384_wellplate_40ul from './images/appliedbiosystemsmicroamp_384_wellplate_40ul.jpg' -import biorad_384_wellplate_50ul from './images/biorad_384_wellplate_50ul.jpg' -import deep_well_plate_adapter from './images/deep_well_plate_adapter.jpg' -import flat_bottom_plate_adapter from './images/flat_bottom_plate_adapter.jpg' -import pcr_plate_adapter from './images/pcr_plate_adapter.jpg' -import universal_flat_adapter from './images/universal_flat_adapter.jpg' -import flat_bottom_aluminum from './images/flat_bottom_aluminum.png' -import opentrons_96_aluminumblock_side_view from './images/opentrons_96_aluminumblock_side_view.jpg' -import opentrons_96_deep_well_temp_mod_adapter_img from './images/opentrons_96_deep_well_temp_mod_adapter.png' -export const labwareImages: Record = { - agilent_1_reservoir_290ml: [agilent_1_reservoir_290ml_side_view], - axygen_1_reservoir_90ml: [axygen_1_reservoir_90ml_side_view], - biorad_96_wellplate_200ul_pcr: [ - biorad_96_wellplate_200ul_pcr_photo_three_quarters, - ], - 'corning_12_wellplate_6.9ml_flat': [ - corning_12_wellplate_6_9ml_flat_photo_three_quarters, - ], - 'corning_24_wellplate_3.4ml_flat': [ - corning_24_wellplate_3_4ml_flat_photo_three_quarters, - ], - corning_384_wellplate_112ul_flat: [ - corning_384_wellplate_112ul_flat_photo_three_quarters, - ], - corning_96_wellplate_360ul_flat: [ - corning_96_wellplate_360ul_flat_three_quarters, - ], - 'corning_48_wellplate_1.6ml_flat': [ - corning_48_wellplate_1_6ml_flat_photo_three_quarters, - ], - 'corning_6_wellplate_16.8ml_flat': [ - corning_6_wellplate_16_8ml_flat_photo_three_quarters, - ], - eppendorf_96_tiprack_1000ul_eptips: [eppendorf_1000ul_tip_eptips_side_view], - eppendorf_96_tiprack_10ul_eptips: [eppendorf_10ul_tips_eptips_side_view], - geb_96_tiprack_1000ul: [ - geb_96_tiprack_1000ul_side_view, - geb_1000ul_tip_side_view, - ], - geb_96_tiprack_10ul: [geb_96_tiprack_10ul_side_view, geb_10ul_tip_side_view], - nest_1_reservoir_195ml: [nest_1_reservoir_195ml_three_quarters], - nest_1_reservoir_290ml: [nest_1_reservoir_290ml], - nest_12_reservoir_15ml: [nest_12_reservoir_15ml_three_quarters], - nest_96_wellplate_100ul_pcr_full_skirt: [ - nest_96_wellplate_100ul_pcr_full_skirt_three_quarters, - ], - nest_96_wellplate_200ul_flat: [nest_96_wellplate_200ul_flat_three_quarters], - nest_96_wellplate_2ml_deep: [nest_96_wellplate_2ml_deep], - opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical: [ - opentrons_10_tuberack_4_6_side_view, - falcon_50ml_15ml_conical_tubes, - ], - opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical_acrylic: [ - falcon_50ml_15ml_conical_tubes, - ], - opentrons_15_tuberack_falcon_15ml_conical: [ - opentrons_15_tuberack_side_view, - falcon_15ml_conical_tube, - ], - opentrons_10_tuberack_nest_4x50ml_6x15ml_conical: [ - opentrons_10_tuberack_4_6_side_view, - nest_50ml_15ml_conical_tubes, - ], - opentrons_15_tuberack_nest_15ml_conical: [ - opentrons_15_tuberack_side_view, - nest_15ml_conical_tube, - ], - opentrons_6_tuberack_nest_50ml_conical: [ - opentrons_6_tuberack_side_view, - nest_50ml_conical_tube, - ], - opentrons_1_trash_1100ml_fixed: [], - opentrons_1_trash_850ml_fixed: [], - opentrons_24_aluminumblock_generic_2ml_screwcap: [ - opentrons_24_aluminumblock_side_view, - generic_2ml_screwcap_tube, - ], - 'opentrons_24_aluminumblock_nest_0.5ml_screwcap': [ - opentrons_24_aluminumblock_side_view, - nest_0_5ml_screwcap_tube, - ], - 'opentrons_24_aluminumblock_nest_1.5ml_screwcap': [ - opentrons_24_aluminumblock_side_view, - nest_1_5ml_screwcap_tube, - ], - 'opentrons_24_aluminumblock_nest_1.5ml_snapcap': [ - opentrons_24_aluminumblock_side_view, - nest_1_5ml_snapcap_tube, - ], - opentrons_24_aluminumblock_nest_2ml_screwcap: [ - opentrons_24_aluminumblock_side_view, - nest_2ml_screwcap_tube, - ], - opentrons_24_aluminumblock_nest_2ml_snapcap: [ - opentrons_24_aluminumblock_side_view, - nest_2ml_snapcap_tube, - ], - 'opentrons_24_tuberack_eppendorf_1.5ml_safelock_snapcap': [ - opentrons_24_tuberack_side_view, - eppendorf_1_5ml_safelock_snapcap_tube, - ], - opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap: [ - opentrons_24_tuberack_side_view, - eppendorf_2ml_safelock_snapcap_tube, - ], - 'opentrons_24_tuberack_nest_0.5ml_screwcap': [ - opentrons_24_tuberack_side_view, - nest_0_5ml_screwcap_tube, - ], - 'opentrons_24_tuberack_nest_1.5ml_screwcap': [ - opentrons_24_tuberack_side_view, - nest_1_5ml_screwcap_tube, - ], - 'opentrons_24_tuberack_nest_1.5ml_snapcap': [ - opentrons_24_tuberack_side_view, - nest_1_5ml_snapcap_tube, - ], - opentrons_24_tuberack_nest_2ml_screwcap: [ - opentrons_24_tuberack_side_view, - nest_2ml_screwcap_tube, - ], - opentrons_24_tuberack_nest_2ml_snapcap: [ - opentrons_24_tuberack_side_view, - nest_2ml_snapcap_tube, - ], - opentrons_24_tuberack_eppendorf_2ml_safelock_snapcap_acrylic: [ - eppendorf_2ml_safelock_snapcap_tube, - ], - 'opentrons_24_tuberack_generic_0.75ml_snapcap_acrylic': [], - opentrons_24_tuberack_generic_2ml_screwcap: [ - opentrons_24_tuberack_side_view, - generic_2ml_screwcap_tube, - ], - 'opentrons_40_aluminumblock_eppendorf_24x2ml_safelock_snapcap_generic_16x0.2ml_pcr_strip': [ - eppendorf_2ml_safelock_snapcap_tube, - generic_pcr_strip_200ul_tubes, - ], - opentrons_6_tuberack_falcon_50ml_conical: [ - opentrons_6_tuberack_side_view, - falcon_50ml_conical_tube, - ], - opentrons_96_aluminumblock_biorad_wellplate_200ul: [ - opentrons_96_aluminumblock_side_view, - biorad_96_wellplate_200ul_pcr_photo_three_quarters, - ], - opentrons_96_aluminumblock_generic_pcr_strip_200ul: [ - opentrons_96_aluminumblock_side_view, - generic_pcr_strip_200ul_tubes, - ], - opentrons_96_aluminumblock_nest_wellplate_100ul: [ - opentrons_96_aluminumblock_side_view, - nest_96_wellplate_100ul_pcr_full_skirt_three_quarters, - ], - opentrons_96_tiprack_1000ul: [opentrons_96_tiprack_1000ul_side_view], - opentrons_96_tiprack_10ul: [opentrons_96_tiprack_10ul_side_view], - opentrons_96_tiprack_20ul: [opentrons_96_tiprack_10ul_side_view], - opentrons_96_tiprack_300ul: [opentrons_96_tiprack_300ul_side_view], - opentrons_96_filtertiprack_1000ul: [opentrons_96_tiprack_1000ul_side_view], - opentrons_96_filtertiprack_10ul: [opentrons_96_tiprack_10ul_side_view], - opentrons_96_filtertiprack_20ul: [opentrons_96_tiprack_10ul_side_view], - opentrons_96_filtertiprack_200ul: [opentrons_96_tiprack_300ul_side_view], - tipone_96_tiprack_200ul: [ - tipone_96_tiprack_200ul_side_view, - tipone_200ul_tip_side_view, - ], - usascientific_12_reservoir_22ml: [usascientific_12_reservoir_22ml_side_view], - 'usascientific_96_wellplate_2.4ml_deep': [ - usascientific_96_wellplate_2_4ml_deep_side_view, - ], - thermoscientificnunc_96_wellplate_1300ul: [ - thermoscientificnunc_96_wellplate_1300ul, - ], - thermoscientificnunc_96_wellplate_2000ul: [ - thermoscientificnunc_96_wellplate_2000ul, - ], - appliedbiosystemsmicroamp_384_wellplate_40ul: [ - appliedbiosystemsmicroamp_384_wellplate_40ul, - ], - biorad_384_wellplate_50ul: [biorad_384_wellplate_50ul], - opentrons_96_deep_well_adapter: [deep_well_plate_adapter], - opentrons_96_flat_bottom_adapter: [flat_bottom_plate_adapter], - opentrons_96_pcr_adapter: [pcr_plate_adapter], - opentrons_universal_flat_adapter: [universal_flat_adapter], - opentrons_aluminum_flat_bottom_plate: [flat_bottom_aluminum], - opentrons_96_well_aluminum_block: [opentrons_96_aluminumblock_side_view], - opentrons_96_deep_well_adapter_nest_wellplate_2ml_deep: [ - deep_well_plate_adapter, - nest_96_wellplate_2ml_deep, - ], - opentrons_96_flat_bottom_adapter_nest_wellplate_200ul_flat: [ - flat_bottom_plate_adapter, - nest_96_wellplate_200ul_flat_three_quarters, - ], - opentrons_96_pcr_adapter_nest_wellplate_100ul_pcr_full_skirt: [ - pcr_plate_adapter, - nest_96_wellplate_100ul_pcr_full_skirt_three_quarters, - ], - opentrons_universal_flat_adapter_corning_384_wellplate_112ul_flat: [ - universal_flat_adapter, - corning_384_wellplate_112ul_flat_photo_three_quarters, - ], - opentrons_96_deep_well_temp_mod_adapter: [ - opentrons_96_deep_well_temp_mod_adapter_img, - ], -} diff --git a/app/src/organisms/LabwareOffsetTabs/__tests__/LabwareOffsetTabs.test.tsx b/app/src/organisms/LabwareOffsetTabs/__tests__/LabwareOffsetTabs.test.tsx index aa313000b9c..d5d2a89538a 100644 --- a/app/src/organisms/LabwareOffsetTabs/__tests__/LabwareOffsetTabs.test.tsx +++ b/app/src/organisms/LabwareOffsetTabs/__tests__/LabwareOffsetTabs.test.tsx @@ -1,8 +1,7 @@ -import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { LabwareOffsetTabs } from '..' const mockTableComponent =
    Table Component
    diff --git a/app/src/organisms/LabwareOffsetTabs/index.tsx b/app/src/organisms/LabwareOffsetTabs/index.tsx index 36dd745f682..3ad81c51d01 100644 --- a/app/src/organisms/LabwareOffsetTabs/index.tsx +++ b/app/src/organisms/LabwareOffsetTabs/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { BORDERS, @@ -27,7 +27,7 @@ export function LabwareOffsetTabs({ ...styleProps }: LabwareOffsetTabsProps): JSX.Element { const { t } = useTranslation('labware_position_check') - const [currentTab, setCurrentTab] = React.useState('table') + const [currentTab, setCurrentTab] = useState('table') const activeTabComponent = { table: TableComponent, diff --git a/app/src/organisms/LabwarePositionCheck/AttachProbe.tsx b/app/src/organisms/LabwarePositionCheck/AttachProbe.tsx index 1b232f00d51..afd9efba19f 100644 --- a/app/src/organisms/LabwarePositionCheck/AttachProbe.tsx +++ b/app/src/organisms/LabwarePositionCheck/AttachProbe.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useEffect, useState } from 'react' import { Trans, useTranslation } from 'react-i18next' import { RESPONSIVENESS, @@ -8,20 +8,21 @@ import { } from '@opentrons/components' import { getPipetteNameSpecs } from '@opentrons/shared-data' import { css } from 'styled-components' -import { ProbeNotAttached } from '../PipetteWizardFlows/ProbeNotAttached' +import { ProbeNotAttached } from '/app/organisms/PipetteWizardFlows/ProbeNotAttached' import { RobotMotionLoader } from './RobotMotionLoader' -import attachProbe1 from '../../assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_1.webm' -import attachProbe8 from '../../assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_8.webm' -import attachProbe96 from '../../assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_96.webm' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' +import attachProbe1 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_1.webm' +import attachProbe8 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_8.webm' +import attachProbe96 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_96.webm' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' +import type { Dispatch } from 'react' import type { CompletedProtocolAnalysis, CreateCommand, } from '@opentrons/shared-data' import type { LabwareOffset } from '@opentrons/api-client' -import type { Jog } from '../../molecules/JogControls/types' -import type { useChainRunCommands } from '../../resources/runs' +import type { Jog } from '/app/molecules/JogControls/types' +import type { useChainRunCommands } from '/app/resources/runs' import type { AttachProbeStep, RegisterPositionAction, @@ -31,7 +32,7 @@ import type { interface AttachProbeProps extends AttachProbeStep { protocolData: CompletedProtocolAnalysis proceed: () => void - registerPosition: React.Dispatch + registerPosition: Dispatch chainRunCommands: ReturnType['chainRunCommands'] setFatalError: (errorMessage: string) => void workingOffsets: WorkingOffset[] @@ -52,9 +53,7 @@ export const AttachProbe = (props: AttachProbeProps): JSX.Element | null => { setFatalError, isOnDevice, } = props - const [showUnableToDetect, setShowUnableToDetect] = React.useState( - false - ) + const [showUnableToDetect, setShowUnableToDetect] = useState(false) const pipette = protocolData.pipettes.find(p => p.id === pipetteId) const pipetteName = pipette?.pipetteName @@ -72,7 +71,7 @@ export const AttachProbe = (props: AttachProbeProps): JSX.Element | null => { const pipetteMount = pipette?.mount - React.useEffect(() => { + useEffect(() => { // move into correct position for probe attach on mount chainRunCommands( [ diff --git a/app/src/organisms/LabwarePositionCheck/CheckItem.tsx b/app/src/organisms/LabwarePositionCheck/CheckItem.tsx index 4d3e6af1a94..734ee6468b1 100644 --- a/app/src/organisms/LabwarePositionCheck/CheckItem.tsx +++ b/app/src/organisms/LabwarePositionCheck/CheckItem.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useEffect } from 'react' import omit from 'lodash/omit' import isEqual from 'lodash/isEqual' import { Trans, useTranslation } from 'react-i18next' @@ -23,12 +23,13 @@ import { } from '@opentrons/shared-data' import { useSelector } from 'react-redux' import { getLabwareDef } from './utils/labware' -import { getLabwareDefinitionsFromCommands } from '../../molecules/Command/utils/getLabwareDefinitionsFromCommands' -import { UnorderedList } from '../../molecules/UnorderedList' -import { getCurrentOffsetForLabwareInLocation } from '../Devices/ProtocolRun/utils/getCurrentOffsetForLabwareInLocation' -import { getIsOnDevice } from '../../redux/config' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' +import { UnorderedList } from '/app/molecules/UnorderedList' +import { getCurrentOffsetForLabwareInLocation } from '/app/transformations/analysis' +import { getIsOnDevice } from '/app/redux/config' import { getDisplayLocation } from './utils/getDisplayLocation' +import type { Dispatch } from 'react' import type { LabwareOffset } from '@opentrons/api-client' import type { CompletedProtocolAnalysis, @@ -37,13 +38,13 @@ import type { MoveLabwareCreateCommand, RobotType, } from '@opentrons/shared-data' -import type { useChainRunCommands } from '../../resources/runs' +import type { useChainRunCommands } from '/app/resources/runs' import type { CheckLabwareStep, RegisterPositionAction, WorkingOffset, } from './types' -import type { Jog } from '../../molecules/JogControls/types' +import type { Jog } from '/app/molecules/JogControls/types' import type { TFunction } from 'i18next' const PROBE_LENGTH_MM = 44.5 @@ -54,7 +55,7 @@ interface CheckItemProps extends Omit { proceed: () => void chainRunCommands: ReturnType['chainRunCommands'] setFatalError: (errorMessage: string) => void - registerPosition: React.Dispatch + registerPosition: Dispatch workingOffsets: WorkingOffset[] existingOffsets: LabwareOffset[] handleJog: Jog @@ -131,7 +132,7 @@ export const CheckItem = (props: CheckItemProps): JSX.Element | null => { o.initialPosition != null )?.initialPosition - React.useEffect(() => { + useEffect(() => { if (initialPosition == null && modulePrepCommands.length > 0) { chainRunCommands(modulePrepCommands, false) .then(() => {}) @@ -156,6 +157,13 @@ export const CheckItem = (props: CheckItemProps): JSX.Element | null => { t as TFunction, i18n ) + const slotOnlyDisplayLocation = getDisplayLocation( + location, + labwareDefs, + t as TFunction, + i18n, + true + ) const labwareDisplayName = getLabwareDisplayName(labwareDef) let placeItemInstruction: JSX.Element = ( @@ -445,7 +453,7 @@ export const CheckItem = (props: CheckItemProps): JSX.Element | null => { { {...props} header={t('prepare_item_in_location', { item: isTiprack ? t('tip_rack') : t('labware'), - location: displayLocation, + location: slotOnlyDisplayLocation, })} body={ void - registerPosition: React.Dispatch + registerPosition: Dispatch chainRunCommands: ReturnType['chainRunCommands'] setFatalError: (errorMessage: string) => void workingOffsets: WorkingOffset[] @@ -58,7 +59,7 @@ export const DetachProbe = (props: DetachProbeProps): JSX.Element | null => { } const pipetteMount = pipette?.mount - React.useEffect(() => { + useEffect(() => { // move into correct position for probe detach on mount chainRunCommands( [ diff --git a/app/src/organisms/LabwarePositionCheck/ExitConfirmation.tsx b/app/src/organisms/LabwarePositionCheck/ExitConfirmation.tsx index 1f03223c0c7..ab6cb857035 100644 --- a/app/src/organisms/LabwarePositionCheck/ExitConfirmation.tsx +++ b/app/src/organisms/LabwarePositionCheck/ExitConfirmation.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import styled from 'styled-components' import { useTranslation } from 'react-i18next' import { @@ -19,8 +18,8 @@ import { TYPOGRAPHY, } from '@opentrons/components' import { useSelector } from 'react-redux' -import { getIsOnDevice } from '../../redux/config' -import { SmallButton } from '../../atoms/buttons' +import { getIsOnDevice } from '/app/redux/config' +import { SmallButton } from '/app/atoms/buttons' interface ExitConfirmationProps { onGoBack: () => void diff --git a/app/src/organisms/LabwarePositionCheck/FatalErrorModal.tsx b/app/src/organisms/LabwarePositionCheck/FatalErrorModal.tsx index 50d6d214bc9..ee98e776055 100644 --- a/app/src/organisms/LabwarePositionCheck/FatalErrorModal.tsx +++ b/app/src/organisms/LabwarePositionCheck/FatalErrorModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { createPortal } from 'react-dom' import styled from 'styled-components' import { useTranslation } from 'react-i18next' @@ -20,9 +19,9 @@ import { TEXT_TRANSFORM_CAPITALIZE, TYPOGRAPHY, } from '@opentrons/components' -import { getTopPortalEl } from '../../App/portal' -import { WizardHeader } from '../../molecules/WizardHeader' -import { i18n } from '../../i18n' +import { getTopPortalEl } from '/app/App/portal' +import { WizardHeader } from '/app/molecules/WizardHeader' +import { i18n } from '/app/i18n' const SUPPORT_EMAIL = 'support@opentrons.com' interface FatalErrorProps { diff --git a/app/src/organisms/LabwarePositionCheck/IntroScreen/getPrepCommands.ts b/app/src/organisms/LabwarePositionCheck/IntroScreen/getPrepCommands.ts index f2ce105fd73..4e8119e4d74 100644 --- a/app/src/organisms/LabwarePositionCheck/IntroScreen/getPrepCommands.ts +++ b/app/src/organisms/LabwarePositionCheck/IntroScreen/getPrepCommands.ts @@ -2,6 +2,7 @@ import { getModuleType, HEATERSHAKER_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, + ABSORBANCE_READER_TYPE, } from '@opentrons/shared-data' import type { @@ -13,6 +14,7 @@ import type { RunTimeCommand, SetupRunTimeCommand, TCOpenLidCreateCommand, + AbsorbanceReaderOpenLidCreateCommand, } from '@opentrons/shared-data' type LPCPrepCommand = @@ -21,6 +23,7 @@ type LPCPrepCommand = | TCOpenLidCreateCommand | HeaterShakerDeactivateShakerCreateCommand | HeaterShakerCloseLatchCreateCommand + | AbsorbanceReaderOpenLidCreateCommand export function getPrepCommands( protocolData: CompletedProtocolAnalysis @@ -97,6 +100,26 @@ export function getPrepCommands( [] ) + const AbsorbanceCommands = protocolData.modules.reduce( + (acc, module) => { + if (getModuleType(module.model) === ABSORBANCE_READER_TYPE) { + return [ + ...acc, + { + commandType: 'home', + params: {}, + }, + { + commandType: 'absorbanceReader/openLid', + params: { moduleId: module.id }, + }, + ] + } + return acc + }, + [] + ) + const HSCommands = protocolData.modules.reduce< HeaterShakerCloseLatchCreateCommand[] >((acc, module) => { @@ -116,7 +139,13 @@ export function getPrepCommands( params: {}, } // prepCommands will be run when a user starts LPC - return [...loadCommands, ...TCCommands, ...HSCommands, homeCommand] + return [ + ...loadCommands, + ...TCCommands, + ...AbsorbanceCommands, + ...HSCommands, + homeCommand, + ] } function isLoadCommand( diff --git a/app/src/organisms/LabwarePositionCheck/IntroScreen/index.tsx b/app/src/organisms/LabwarePositionCheck/IntroScreen/index.tsx index e11c60cdf50..44e5eb67ded 100644 --- a/app/src/organisms/LabwarePositionCheck/IntroScreen/index.tsx +++ b/app/src/organisms/LabwarePositionCheck/IntroScreen/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { Trans, useTranslation } from 'react-i18next' import { css } from 'styled-components' @@ -20,26 +20,27 @@ import { } from '@opentrons/components' import { RobotMotionLoader } from '../RobotMotionLoader' import { getPrepCommands } from './getPrepCommands' -import { WizardRequiredEquipmentList } from '../../../molecules/WizardRequiredEquipmentList' -import { getLatestCurrentOffsets } from '../../Devices/ProtocolRun/SetupLabwarePositionCheck/utils' -import { getIsOnDevice } from '../../../redux/config' -import { NeedHelpLink } from '../../CalibrationPanels' +import { WizardRequiredEquipmentList } from '/app/molecules/WizardRequiredEquipmentList' +import { getLatestCurrentOffsets } from '/app/transformations/runs' +import { getIsOnDevice } from '/app/redux/config' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' import { useSelector } from 'react-redux' import { TwoUpTileLayout } from '../TwoUpTileLayout' -import { getTopPortalEl } from '../../../App/portal' -import { SmallButton } from '../../../atoms/buttons' -import { CALIBRATION_PROBE } from '../../PipetteWizardFlows/constants' +import { getTopPortalEl } from '/app/App/portal' +import { SmallButton } from '/app/atoms/buttons' +import { CALIBRATION_PROBE } from '/app/organisms/PipetteWizardFlows/constants' import { TerseOffsetTable } from '../ResultsSummary' -import { getLabwareDefinitionsFromCommands } from '../../../molecules/Command/utils/getLabwareDefinitionsFromCommands' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' +import type { Dispatch } from 'react' import type { LabwareOffset } from '@opentrons/api-client' import type { CompletedProtocolAnalysis, LabwareDefinition2, } from '@opentrons/shared-data' -import type { useChainRunCommands } from '../../../resources/runs' +import type { useChainRunCommands } from '/app/resources/runs' import type { RegisterPositionAction } from '../types' -import type { Jog } from '../../../molecules/JogControls' +import type { Jog } from '/app/molecules/JogControls' export const INTERVAL_MS = 3000 @@ -49,7 +50,7 @@ const SUPPORT_PAGE_URL = 'https://support.opentrons.com/s/ot2-calibration' export const IntroScreen = (props: { proceed: () => void protocolData: CompletedProtocolAnalysis - registerPosition: React.Dispatch + registerPosition: Dispatch chainRunCommands: ReturnType['chainRunCommands'] handleJog: Jog setFatalError: (errorMessage: string) => void @@ -160,7 +161,7 @@ interface ViewOffsetsProps { function ViewOffsets(props: ViewOffsetsProps): JSX.Element { const { existingOffsets, labwareDefinitions } = props const { t, i18n } = useTranslation('labware_position_check') - const [showOffsetsTable, setShowOffsetsModal] = React.useState(false) + const [showOffsetsTable, setShowOffsetsModal] = useState(false) const latestCurrentOffsets = getLatestCurrentOffsets(existingOffsets) return existingOffsets.length > 0 ? ( <> diff --git a/app/src/organisms/LabwarePositionCheck/JogToWell.tsx b/app/src/organisms/LabwarePositionCheck/JogToWell.tsx index 81e89741546..bce4808a514 100644 --- a/app/src/organisms/LabwarePositionCheck/JogToWell.tsx +++ b/app/src/organisms/LabwarePositionCheck/JogToWell.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useEffect, useState } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' @@ -11,14 +11,14 @@ import { Flex, JUSTIFY_SPACE_BETWEEN, LabwareRender, + LegacyStyledText, + ModalShell, PipetteRender, PrimaryButton, RESPONSIVENESS, RobotWorkSpace, SecondaryButton, SPACING, - LegacyStyledText, - ModalShell, TYPOGRAPHY, WELL_LABEL_OPTIONS, } from '@opentrons/components' @@ -29,21 +29,22 @@ import { getVectorSum, } from '@opentrons/shared-data' -import levelWithTip from '../../assets/images/lpc_level_with_tip.svg' -import levelWithLabware from '../../assets/images/lpc_level_with_labware.svg' -import levelProbeWithTip from '../../assets/images/lpc_level_probe_with_tip.svg' -import levelProbeWithLabware from '../../assets/images/lpc_level_probe_with_labware.svg' -import { getIsOnDevice } from '../../redux/config' -import { getTopPortalEl } from '../../App/portal' -import { SmallButton } from '../../atoms/buttons' -import { NeedHelpLink } from '../CalibrationPanels' -import { JogControls } from '../../molecules/JogControls' +import levelWithTip from '/app/assets/images/lpc_level_with_tip.svg' +import levelWithLabware from '/app/assets/images/lpc_level_with_labware.svg' +import levelProbeWithTip from '/app/assets/images/lpc_level_probe_with_tip.svg' +import levelProbeWithLabware from '/app/assets/images/lpc_level_probe_with_labware.svg' +import { getIsOnDevice } from '/app/redux/config' +import { getTopPortalEl } from '/app/App/portal' +import { SmallButton } from '/app/atoms/buttons' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' +import { JogControls } from '/app/molecules/JogControls' import { LiveOffsetValue } from './LiveOffsetValue' +import type { ReactNode } from 'react' import type { PipetteName, LabwareDefinition2 } from '@opentrons/shared-data' import type { WellStroke } from '@opentrons/components' import type { VectorOffset } from '@opentrons/api-client' -import type { Jog } from '../../molecules/JogControls' +import type { Jog } from '/app/molecules/JogControls' const DECK_MAP_VIEWBOX = '-10 -10 150 105' const LPC_HELP_LINK_URL = @@ -55,8 +56,8 @@ interface JogToWellProps { handleJog: Jog pipetteName: PipetteName labwareDef: LabwareDefinition2 - header: React.ReactNode - body: React.ReactNode + header: ReactNode + body: ReactNode initialPosition: VectorOffset existingOffset: VectorOffset shouldUseMetalProbe: boolean @@ -76,12 +77,12 @@ export const JogToWell = (props: JogToWellProps): JSX.Element | null => { shouldUseMetalProbe, } = props - const [joggedPosition, setJoggedPosition] = React.useState( + const [joggedPosition, setJoggedPosition] = useState( initialPosition ) const isOnDevice = useSelector(getIsOnDevice) - const [showFullJogControls, setShowFullJogControls] = React.useState(false) - React.useEffect(() => { + const [showFullJogControls, setShowFullJogControls] = useState(false) + useEffect(() => { // NOTE: this will perform a "null" jog when the jog controls mount so // if a user reaches the "confirm exit" modal (unmounting this component) // and clicks "go back" we are able so initialize the live offset to whatever diff --git a/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx b/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx index 0f5fea72ebe..6f0953093a6 100644 --- a/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx +++ b/app/src/organisms/LabwarePositionCheck/LabwarePositionCheckComponent.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect, useReducer } from 'react' import { createPortal } from 'react-dom' import isEqual from 'lodash/isEqual' import { useSelector } from 'react-redux' @@ -11,22 +11,24 @@ import { } from '@opentrons/react-api-client' import { FIXED_TRASH_ID, FLEX_ROBOT_TYPE } from '@opentrons/shared-data' -import { getTopPortalEl } from '../../App/portal' -// import { useTrackEvent } from '../../redux/analytics' +import { getTopPortalEl } from '/app/App/portal' +// import { useTrackEvent } from '/app/redux/analytics' import { IntroScreen } from './IntroScreen' import { ExitConfirmation } from './ExitConfirmation' import { CheckItem } from './CheckItem' -import { WizardHeader } from '../../molecules/WizardHeader' -import { getIsOnDevice } from '../../redux/config' +import { WizardHeader } from '/app/molecules/WizardHeader' +import { getIsOnDevice } from '/app/redux/config' import { AttachProbe } from './AttachProbe' import { DetachProbe } from './DetachProbe' import { PickUpTip } from './PickUpTip' import { ReturnTip } from './ReturnTip' import { ResultsSummary } from './ResultsSummary' -import { useChainMaintenanceCommands } from '../../resources/runs' import { FatalError } from './FatalErrorModal' import { RobotMotionLoader } from './RobotMotionLoader' -import { useNotifyCurrentMaintenanceRun } from '../../resources/maintenance_runs' +import { + useChainMaintenanceCommands, + useNotifyCurrentMaintenanceRun, +} from '/app/resources/maintenance_runs' import { getLabwarePositionCheckSteps } from './getLabwarePositionCheckSteps' import type { @@ -41,7 +43,7 @@ import type { LabwareOffset, CommandData, } from '@opentrons/api-client' -import type { Axis, Sign, StepSize } from '../../molecules/JogControls/types' +import type { Axis, Sign, StepSize } from '/app/molecules/JogControls/types' import type { RegisterPositionAction, WorkingOffset } from './types' const RUN_REFETCH_INTERVAL = 5000 @@ -83,7 +85,7 @@ export const LabwarePositionCheckComponent = ( const [ monitorMaintenanceRunForDeletion, setMonitorMaintenanceRunForDeletion, - ] = React.useState(false) + ] = useState(false) const { data: maintenanceRunData } = useNotifyCurrentMaintenanceRun({ refetchInterval: RUN_REFETCH_INTERVAL, @@ -92,7 +94,7 @@ export const LabwarePositionCheckComponent = ( // this will close the modal in case the run was deleted by the terminate // activity modal on the ODD - React.useEffect(() => { + useEffect(() => { if ( maintenanceRunId !== null && maintenanceRunData?.data.id === maintenanceRunId @@ -112,14 +114,9 @@ export const LabwarePositionCheckComponent = ( setMaintenanceRunId, ]) - const [fatalError, setFatalError] = React.useState(null) - const [isApplyingOffsets, setIsApplyingOffsets] = React.useState( - false - ) - const [ - { workingOffsets, tipPickUpOffset }, - registerPosition, - ] = React.useReducer( + const [fatalError, setFatalError] = useState(null) + const [isApplyingOffsets, setIsApplyingOffsets] = useState(false) + const [{ workingOffsets, tipPickUpOffset }, registerPosition] = useReducer( ( state: { workingOffsets: WorkingOffset[] @@ -187,7 +184,7 @@ export const LabwarePositionCheckComponent = ( }, { workingOffsets: [], tipPickUpOffset: null } ) - const [isExiting, setIsExiting] = React.useState(false) + const [isExiting, setIsExiting] = useState(false) const { createMaintenanceCommand: createSilentCommand, } = useCreateMaintenanceCommandMutation() @@ -197,7 +194,7 @@ export const LabwarePositionCheckComponent = ( } = useChainMaintenanceCommands() const { createLabwareOffset } = useCreateLabwareOffsetMutation() - const [currentStepIndex, setCurrentStepIndex] = React.useState(0) + const [currentStepIndex, setCurrentStepIndex] = useState(0) const handleCleanUpAndClose = (): void => { setIsExiting(true) const dropTipToBeSafeCommands: DropTipCreateCommand[] = shouldUseMetalProbe diff --git a/app/src/organisms/LabwarePositionCheck/LiveOffsetValue.tsx b/app/src/organisms/LabwarePositionCheck/LiveOffsetValue.tsx index e2500cd33cd..21b3af917f9 100644 --- a/app/src/organisms/LabwarePositionCheck/LiveOffsetValue.tsx +++ b/app/src/organisms/LabwarePositionCheck/LiveOffsetValue.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { Fragment } from 'react' import { ALIGN_CENTER, BORDERS, @@ -14,7 +14,7 @@ import { import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' -import { getIsOnDevice } from '../../redux/config' +import { getIsOnDevice } from '/app/redux/config' import type { StyleProps } from '@opentrons/components' @@ -55,7 +55,7 @@ export function LiveOffsetValue(props: OffsetVectorProps): JSX.Element { > {[x, y, z].map((axis, index) => ( - + {axis.toFixed(1)} - + ))} diff --git a/app/src/organisms/LabwarePositionCheck/PickUpTip.tsx b/app/src/organisms/LabwarePositionCheck/PickUpTip.tsx index 4df41813983..de76e855097 100644 --- a/app/src/organisms/LabwarePositionCheck/PickUpTip.tsx +++ b/app/src/organisms/LabwarePositionCheck/PickUpTip.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { Trans, useTranslation } from 'react-i18next' import isEqual from 'lodash/isEqual' import { @@ -18,23 +18,24 @@ import { import { RobotMotionLoader } from './RobotMotionLoader' import { PrepareSpace } from './PrepareSpace' import { JogToWell } from './JogToWell' -import { UnorderedList } from '../../molecules/UnorderedList' -import { getCurrentOffsetForLabwareInLocation } from '../Devices/ProtocolRun/utils/getCurrentOffsetForLabwareInLocation' +import { UnorderedList } from '/app/molecules/UnorderedList' +import { getCurrentOffsetForLabwareInLocation } from '/app/transformations/analysis' import { TipConfirmation } from './TipConfirmation' import { getLabwareDef } from './utils/labware' -import { getLabwareDefinitionsFromCommands } from '../../molecules/Command/utils/getLabwareDefinitionsFromCommands' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' import { getDisplayLocation } from './utils/getDisplayLocation' import { useSelector } from 'react-redux' -import { getIsOnDevice } from '../../redux/config' +import { getIsOnDevice } from '/app/redux/config' +import type { Dispatch } from 'react' import type { CompletedProtocolAnalysis, CreateCommand, MoveLabwareCreateCommand, RobotType, } from '@opentrons/shared-data' -import type { useChainRunCommands } from '../../resources/runs' -import type { Jog } from '../../molecules/JogControls/types' +import type { useChainRunCommands } from '/app/resources/runs' +import type { Jog } from '/app/molecules/JogControls/types' import type { PickUpTipStep, RegisterPositionAction, @@ -46,7 +47,7 @@ import type { TFunction } from 'i18next' interface PickUpTipProps extends PickUpTipStep { protocolData: CompletedProtocolAnalysis proceed: () => void - registerPosition: React.Dispatch + registerPosition: Dispatch chainRunCommands: ReturnType['chainRunCommands'] setFatalError: (errorMessage: string) => void workingOffsets: WorkingOffset[] @@ -77,7 +78,7 @@ export const PickUpTip = (props: PickUpTipProps): JSX.Element | null => { protocolHasModules, currentStepIndex, } = props - const [showTipConfirmation, setShowTipConfirmation] = React.useState(false) + const [showTipConfirmation, setShowTipConfirmation] = useState(false) const isOnDevice = useSelector(getIsOnDevice) const labwareDef = getLabwareDef(labwareId, protocolData) const pipette = protocolData.pipettes.find(p => p.id === pipetteId) diff --git a/app/src/organisms/LabwarePositionCheck/PrepareSpace.tsx b/app/src/organisms/LabwarePositionCheck/PrepareSpace.tsx index ad3df11c6aa..8820acfef33 100644 --- a/app/src/organisms/LabwarePositionCheck/PrepareSpace.tsx +++ b/app/src/organisms/LabwarePositionCheck/PrepareSpace.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import styled, { css } from 'styled-components' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' @@ -18,10 +18,10 @@ import { } from '@opentrons/components' import { THERMOCYCLER_MODULE_TYPE, getModuleType } from '@opentrons/shared-data' -import { getIsOnDevice } from '../../redux/config' -import { SmallButton } from '../../atoms/buttons' -import { NeedHelpLink } from '../CalibrationPanels' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' +import { getIsOnDevice } from '/app/redux/config' +import { SmallButton } from '/app/atoms/buttons' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' import type { CompletedProtocolAnalysis, diff --git a/app/src/organisms/LabwarePositionCheck/ResultsSummary.tsx b/app/src/organisms/LabwarePositionCheck/ResultsSummary.tsx index 3459b095f61..eafda1a2c8a 100644 --- a/app/src/organisms/LabwarePositionCheck/ResultsSummary.tsx +++ b/app/src/organisms/LabwarePositionCheck/ResultsSummary.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useMemo, Fragment } from 'react' import styled, { css } from 'styled-components' import { useSelector } from 'react-redux' import isEqual from 'lodash/isEqual' @@ -11,7 +11,7 @@ import { getVectorSum, IDENTITY_VECTOR, } from '@opentrons/shared-data' -import { NeedHelpLink } from '../CalibrationPanels' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' import { ALIGN_CENTER, ALIGN_FLEX_END, @@ -29,16 +29,17 @@ import { SPACING, LegacyStyledText, TYPOGRAPHY, + DIRECTION_ROW, } from '@opentrons/components' -import { PythonLabwareOffsetSnippet } from '../../molecules/PythonLabwareOffsetSnippet' +import { PythonLabwareOffsetSnippet } from '/app/molecules/PythonLabwareOffsetSnippet' import { getIsLabwareOffsetCodeSnippetsOn, getIsOnDevice, -} from '../../redux/config' -import { SmallButton } from '../../atoms/buttons' -import { LabwareOffsetTabs } from '../LabwareOffsetTabs' -import { getCurrentOffsetForLabwareInLocation } from '../Devices/ProtocolRun/utils/getCurrentOffsetForLabwareInLocation' -import { getLabwareDefinitionsFromCommands } from '../../molecules/Command/utils/getLabwareDefinitionsFromCommands' +} from '/app/redux/config' +import { SmallButton } from '/app/atoms/buttons' +import { LabwareOffsetTabs } from '/app/organisms/LabwareOffsetTabs' +import { getCurrentOffsetForLabwareInLocation } from '/app/transformations/analysis' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' import { getDisplayLocation } from './utils/getDisplayLocation' import type { @@ -84,7 +85,7 @@ export const ResultsSummary = ( ) const isOnDevice = useSelector(getIsOnDevice) - const offsetsToApply = React.useMemo(() => { + const offsetsToApply = useMemo(() => { return workingOffsets.map( ({ initialPosition, finalPosition, labwareId, location }) => { const definitionUri = @@ -321,7 +322,7 @@ const OffsetTable = (props: OffsetTableProps): JSX.Element => { ) : ( {[vector.x, vector.y, vector.z].map((axis, index) => ( - + 0 ? SPACING.spacing8 : 0} @@ -333,7 +334,7 @@ const OffsetTable = (props: OffsetTableProps): JSX.Element => { {axis.toFixed(1)} - + ))} )} @@ -373,16 +374,18 @@ export const TerseOffsetTable = (props: OffsetTableProps): JSX.Element => { return ( - - {location.moduleModel != null ? ( - - ) : null} + + + {location.moduleModel != null ? ( + + ) : null} + { ) : ( {[vector.x, vector.y, vector.z].map((axis, index) => ( - + { > {axis.toFixed(1)} - + ))} )} diff --git a/app/src/organisms/LabwarePositionCheck/ReturnTip.tsx b/app/src/organisms/LabwarePositionCheck/ReturnTip.tsx index d3b56d9f88f..fce1f443829 100644 --- a/app/src/organisms/LabwarePositionCheck/ReturnTip.tsx +++ b/app/src/organisms/LabwarePositionCheck/ReturnTip.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import { DIRECTION_COLUMN, @@ -12,14 +11,14 @@ import { getModuleType, HEATERSHAKER_MODULE_TYPE, } from '@opentrons/shared-data' -import { UnorderedList } from '../../molecules/UnorderedList' +import { UnorderedList } from '/app/molecules/UnorderedList' import { getLabwareDef } from './utils/labware' -import { getLabwareDefinitionsFromCommands } from '../../molecules/Command/utils/getLabwareDefinitionsFromCommands' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' import { getDisplayLocation } from './utils/getDisplayLocation' import { RobotMotionLoader } from './RobotMotionLoader' import { PrepareSpace } from './PrepareSpace' import { useSelector } from 'react-redux' -import { getIsOnDevice } from '../../redux/config' +import { getIsOnDevice } from '/app/redux/config' import type { CompletedProtocolAnalysis, @@ -28,7 +27,7 @@ import type { MoveLabwareCreateCommand, } from '@opentrons/shared-data' import type { VectorOffset } from '@opentrons/api-client' -import type { useChainRunCommands } from '../../resources/runs' +import type { useChainRunCommands } from '/app/resources/runs' import type { ReturnTipStep } from './types' import type { TFunction } from 'i18next' diff --git a/app/src/organisms/LabwarePositionCheck/RobotMotionLoader.tsx b/app/src/organisms/LabwarePositionCheck/RobotMotionLoader.tsx index dbb5f6973f2..577dae6eff7 100644 --- a/app/src/organisms/LabwarePositionCheck/RobotMotionLoader.tsx +++ b/app/src/organisms/LabwarePositionCheck/RobotMotionLoader.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import styled from 'styled-components' import { ALIGN_CENTER, diff --git a/app/src/organisms/LabwarePositionCheck/TerseOffsetTable.stories.tsx b/app/src/organisms/LabwarePositionCheck/TerseOffsetTable.stories.tsx index ef88c401878..da7c52de513 100644 --- a/app/src/organisms/LabwarePositionCheck/TerseOffsetTable.stories.tsx +++ b/app/src/organisms/LabwarePositionCheck/TerseOffsetTable.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { ALIGN_FLEX_END, DIRECTION_COLUMN, @@ -13,7 +13,7 @@ import { getLabwareDefURI, } from '@opentrons/shared-data' -import { SmallButton } from '../../atoms/buttons' +import { SmallButton } from '/app/atoms/buttons' import { TerseOffsetTable } from './ResultsSummary' import type { Story, Meta } from '@storybook/react' diff --git a/app/src/organisms/LabwarePositionCheck/TipConfirmation.tsx b/app/src/organisms/LabwarePositionCheck/TipConfirmation.tsx index f34d64ed1b1..ec8c87daea4 100644 --- a/app/src/organisms/LabwarePositionCheck/TipConfirmation.tsx +++ b/app/src/organisms/LabwarePositionCheck/TipConfirmation.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { ALIGN_CENTER, COLORS, @@ -13,12 +12,12 @@ import { } from '@opentrons/components' import { useTranslation } from 'react-i18next' -import { NeedHelpLink } from '../CalibrationPanels' +import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' import { useSelector } from 'react-redux' -import { getIsOnDevice } from '../../redux/config' -import { SimpleWizardBody } from '../../molecules/SimpleWizardBody' -import { SmallButton } from '../../atoms/buttons' -import { i18n } from '../../i18n' +import { getIsOnDevice } from '/app/redux/config' +import { SimpleWizardBody } from '/app/molecules/SimpleWizardBody' +import { SmallButton } from '/app/atoms/buttons' +import { i18n } from '/app/i18n' const LPC_HELP_LINK_URL = 'https://support.opentrons.com/s/article/How-Labware-Offsets-work-on-the-OT-2' diff --git a/app/src/organisms/LabwarePositionCheck/TwoUpTileLayout.tsx b/app/src/organisms/LabwarePositionCheck/TwoUpTileLayout.tsx index 44ee775f25a..7c6cd309bb4 100644 --- a/app/src/organisms/LabwarePositionCheck/TwoUpTileLayout.tsx +++ b/app/src/organisms/LabwarePositionCheck/TwoUpTileLayout.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import styled, { css } from 'styled-components' import { DIRECTION_COLUMN, diff --git a/app/src/organisms/LabwarePositionCheck/__tests__/CheckItem.test.tsx b/app/src/organisms/LabwarePositionCheck/__tests__/CheckItem.test.tsx index 748da0c01a5..17442dfc42b 100644 --- a/app/src/organisms/LabwarePositionCheck/__tests__/CheckItem.test.tsx +++ b/app/src/organisms/LabwarePositionCheck/__tests__/CheckItem.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, afterEach, expect } from 'vitest' @@ -9,19 +9,16 @@ import { THERMOCYCLER_MODULE_V2, } from '@opentrons/shared-data' -import { - nestedTextMatcher, - renderWithProviders, -} from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { nestedTextMatcher, renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { CheckItem } from '../CheckItem' import { SECTIONS } from '../constants' import { mockCompletedAnalysis, mockExistingOffsets } from '../__fixtures__' import type { Mock } from 'vitest' -vi.mock('../../../redux/config') -vi.mock('../../Devices/hooks') +vi.mock('/app/redux/config') +vi.mock('../../Desktop/Devices/hooks') const mockStartPosition = { x: 10, y: 20, z: 30 } const mockEndPosition = { x: 9, y: 19, z: 29 } diff --git a/app/src/organisms/LabwarePositionCheck/__tests__/ExitConfirmation.test.tsx b/app/src/organisms/LabwarePositionCheck/__tests__/ExitConfirmation.test.tsx index 409ef9d0efa..6a93da71dc5 100644 --- a/app/src/organisms/LabwarePositionCheck/__tests__/ExitConfirmation.test.tsx +++ b/app/src/organisms/LabwarePositionCheck/__tests__/ExitConfirmation.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest' import { ExitConfirmation } from '../ExitConfirmation' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/LabwarePositionCheck/__tests__/PickUpTip.test.tsx b/app/src/organisms/LabwarePositionCheck/__tests__/PickUpTip.test.tsx index b5db396e855..c23e1c1af2c 100644 --- a/app/src/organisms/LabwarePositionCheck/__tests__/PickUpTip.test.tsx +++ b/app/src/organisms/LabwarePositionCheck/__tests__/PickUpTip.test.tsx @@ -1,22 +1,19 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen, waitFor } from '@testing-library/react' import { it, describe, beforeEach, vi, afterEach, expect } from 'vitest' import { FLEX_ROBOT_TYPE, HEATERSHAKER_MODULE_V1 } from '@opentrons/shared-data' -import { i18n } from '../../../i18n' -import { useProtocolMetadata } from '../../Devices/hooks' -import { getIsOnDevice } from '../../../redux/config' +import { i18n } from '/app/i18n' +import { useProtocolMetadata } from '/app/resources/protocols' +import { getIsOnDevice } from '/app/redux/config' import { PickUpTip } from '../PickUpTip' import { SECTIONS } from '../constants' import { mockCompletedAnalysis, mockExistingOffsets } from '../__fixtures__' import type { CommandData } from '@opentrons/api-client' -import { - nestedTextMatcher, - renderWithProviders, -} from '../../../__testing-utils__' +import { nestedTextMatcher, renderWithProviders } from '/app/__testing-utils__' import type { Mock } from 'vitest' -vi.mock('../../Devices/hooks') -vi.mock('../../../redux/config') +vi.mock('/app/resources/protocols') +vi.mock('/app/redux/config') const mockStartPosition = { x: 10, y: 20, z: 30 } diff --git a/app/src/organisms/LabwarePositionCheck/__tests__/ResultsSummary.test.tsx b/app/src/organisms/LabwarePositionCheck/__tests__/ResultsSummary.test.tsx index d9aaa62f6b6..24101904de4 100644 --- a/app/src/organisms/LabwarePositionCheck/__tests__/ResultsSummary.test.tsx +++ b/app/src/organisms/LabwarePositionCheck/__tests__/ResultsSummary.test.tsx @@ -1,19 +1,19 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' -import { getIsLabwareOffsetCodeSnippetsOn } from '../../../redux/config' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { getIsLabwareOffsetCodeSnippetsOn } from '/app/redux/config' import { ResultsSummary } from '../ResultsSummary' import { SECTIONS } from '../constants' -import { mockTipRackDefinition } from '../../../redux/custom-labware/__fixtures__' +import { mockTipRackDefinition } from '/app/redux/custom-labware/__fixtures__' import { mockCompletedAnalysis, mockExistingOffsets, mockWorkingOffsets, } from '../__fixtures__' -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/LabwarePositionCheck/__tests__/ReturnTip.test.tsx b/app/src/organisms/LabwarePositionCheck/__tests__/ReturnTip.test.tsx index 23069c7cf61..0af86097f9c 100644 --- a/app/src/organisms/LabwarePositionCheck/__tests__/ReturnTip.test.tsx +++ b/app/src/organisms/LabwarePositionCheck/__tests__/ReturnTip.test.tsx @@ -1,19 +1,19 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { FLEX_ROBOT_TYPE, HEATERSHAKER_MODULE_V1 } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { SECTIONS } from '../constants' import { mockCompletedAnalysis } from '../__fixtures__' -import { useProtocolMetadata } from '../../Devices/hooks' -import { getIsOnDevice } from '../../../redux/config' +import { useProtocolMetadata } from '/app/resources/protocols' +import { getIsOnDevice } from '/app/redux/config' import { ReturnTip } from '../ReturnTip' -vi.mock('../../Devices/hooks') -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') +vi.mock('/app/resources/protocols') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/LabwarePositionCheck/__tests__/RobotMotionLoader.test.tsx b/app/src/organisms/LabwarePositionCheck/__tests__/RobotMotionLoader.test.tsx index 70b969568e6..fc2c49aa8f5 100644 --- a/app/src/organisms/LabwarePositionCheck/__tests__/RobotMotionLoader.test.tsx +++ b/app/src/organisms/LabwarePositionCheck/__tests__/RobotMotionLoader.test.tsx @@ -1,8 +1,7 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { describe, it } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { RobotMotionLoader } from '../RobotMotionLoader' const mockHeader = 'Stand back, robot needs some space right now' diff --git a/app/src/organisms/LabwarePositionCheck/__tests__/TipConfirmation.test.tsx b/app/src/organisms/LabwarePositionCheck/__tests__/TipConfirmation.test.tsx index 8ff504af81c..8f8878a7122 100644 --- a/app/src/organisms/LabwarePositionCheck/__tests__/TipConfirmation.test.tsx +++ b/app/src/organisms/LabwarePositionCheck/__tests__/TipConfirmation.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, beforeEach, afterEach, expect, vi } from 'vitest' import { fireEvent, screen } from '@testing-library/react' import { TipConfirmation } from '../TipConfirmation' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/LabwarePositionCheck/__tests__/useLaunchLPC.test.tsx b/app/src/organisms/LabwarePositionCheck/__tests__/useLaunchLPC.test.tsx index 560a1bb70b1..fb983097d01 100644 --- a/app/src/organisms/LabwarePositionCheck/__tests__/useLaunchLPC.test.tsx +++ b/app/src/organisms/LabwarePositionCheck/__tests__/useLaunchLPC.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Provider } from 'react-redux' import configureStore from 'redux-mock-store' import { when } from 'vitest-when' @@ -18,12 +18,12 @@ import { } from '@opentrons/react-api-client' import { FLEX_ROBOT_TYPE, fixtureTiprack300ul } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useCreateTargetedMaintenanceRunMutation, useNotifyRunQuery, -} from '../../../resources/runs' -import { useMostRecentCompletedAnalysis } from '../useMostRecentCompletedAnalysis' + useMostRecentCompletedAnalysis, +} from '/app/resources/runs' import { useLaunchLPC } from '../useLaunchLPC' import { LabwarePositionCheck } from '..' @@ -33,8 +33,7 @@ import type { LabwareDefinition2 } from '@opentrons/shared-data' vi.mock('../') vi.mock('@opentrons/react-api-client') -vi.mock('../useMostRecentCompletedAnalysis') -vi.mock('../../../resources/runs') +vi.mock('/app/resources/runs') const MOCK_RUN_ID = 'mockRunId' const MOCK_MAINTENANCE_RUN_ID = 'mockMaintenanceRunId' diff --git a/app/src/organisms/LabwarePositionCheck/index.tsx b/app/src/organisms/LabwarePositionCheck/index.tsx index 5648913cfe2..e96191c584e 100644 --- a/app/src/organisms/LabwarePositionCheck/index.tsx +++ b/app/src/organisms/LabwarePositionCheck/index.tsx @@ -1,11 +1,13 @@ -import * as React from 'react' +import { Component } from 'react' import { useLogger } from '../../logger' import { LabwarePositionCheckComponent } from './LabwarePositionCheckComponent' import { FatalErrorModal } from './FatalErrorModal' -import { getIsOnDevice } from '../../redux/config' +import { getIsOnDevice } from '/app/redux/config' import { useSelector } from 'react-redux' import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' + +import type { ErrorInfo, ReactNode } from 'react' import type { CompletedProtocolAnalysis, RobotType, @@ -48,7 +50,7 @@ export const LabwarePositionCheck = ( } interface ErrorBoundaryProps { - children: React.ReactNode + children: ReactNode onClose: () => void shouldUseMetalProbe: boolean logger: ReturnType @@ -60,7 +62,7 @@ interface ErrorBoundaryProps { }) => JSX.Element isOnDevice: boolean } -class ErrorBoundary extends React.Component< +class ErrorBoundary extends Component< ErrorBoundaryProps, { error: Error | null } > { @@ -69,7 +71,7 @@ class ErrorBoundary extends React.Component< this.state = { error: null } } - componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { + componentDidCatch(error: Error, errorInfo: ErrorInfo): void { this.props.logger.error(`LPC error message: ${error.message}`) this.props.logger.error( `LPC error component stack: ${errorInfo.componentStack}` diff --git a/app/src/organisms/LabwarePositionCheck/useLaunchLPC.tsx b/app/src/organisms/LabwarePositionCheck/useLaunchLPC.tsx index da58709a1a7..18c906d2998 100644 --- a/app/src/organisms/LabwarePositionCheck/useLaunchLPC.tsx +++ b/app/src/organisms/LabwarePositionCheck/useLaunchLPC.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useCreateMaintenanceRunLabwareDefinitionMutation, @@ -8,10 +8,10 @@ import { import { useCreateTargetedMaintenanceRunMutation, useNotifyRunQuery, -} from '../../resources/runs' + useMostRecentCompletedAnalysis, +} from '/app/resources/runs' import { LabwarePositionCheck } from '.' -import { useMostRecentCompletedAnalysis } from './useMostRecentCompletedAnalysis' -import { getLabwareDefinitionsFromCommands } from '../../molecules/Command/utils/getLabwareDefinitionsFromCommands' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' import type { RobotType } from '@opentrons/shared-data' @@ -29,9 +29,7 @@ export function useLaunchLPC( isLoading: isDeletingMaintenanceRun, } = useDeleteMaintenanceRunMutation() const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) - const [maintenanceRunId, setMaintenanceRunId] = React.useState( - null - ) + const [maintenanceRunId, setMaintenanceRunId] = useState(null) const currentOffsets = runRecord?.data?.labwareOffsets ?? [] const { createLabwareDefinition, @@ -63,12 +61,12 @@ export function useLaunchLPC( Promise.all( getLabwareDefinitionsFromCommands( mostRecentAnalysis?.commands ?? [] - ).map(def => + ).map(def => { createLabwareDefinition({ maintenanceRunId: maintenanceRun?.data?.id, labwareDef: def, }) - ) + }) ).then(() => { setMaintenanceRunId(maintenanceRun.data.id) }) diff --git a/app/src/organisms/LabwarePositionCheck/utils/doesPipetteVisitAllTipracks.ts b/app/src/organisms/LabwarePositionCheck/utils/doesPipetteVisitAllTipracks.ts index 0976aab8b1b..f2f336ae0fd 100644 --- a/app/src/organisms/LabwarePositionCheck/utils/doesPipetteVisitAllTipracks.ts +++ b/app/src/organisms/LabwarePositionCheck/utils/doesPipetteVisitAllTipracks.ts @@ -1,12 +1,16 @@ import { getIsTiprack } from '@opentrons/shared-data' -import { getPickUpTipCommandsWithPipette } from '../../Devices/ProtocolRun/utils/getPickUpTipCommandsWithPipette' -import { getTipracksVisited } from '../../Devices/ProtocolRun/utils/getTipracksVisited' + import type { LoadedLabware, RunTimeCommand, LabwareDefinition2, } from '@opentrons/shared-data' +import { + getPickUpTipCommandsWithPipette, + getTipracksVisited, +} from '/app/transformations/commands' + export const doesPipetteVisitAllTipracks = ( pipetteId: string, labware: LoadedLabware[], diff --git a/app/src/organisms/LabwarePositionCheck/utils/getDisplayLocation.ts b/app/src/organisms/LabwarePositionCheck/utils/getDisplayLocation.ts index 2c0fb134a40..d70b741c48d 100644 --- a/app/src/organisms/LabwarePositionCheck/utils/getDisplayLocation.ts +++ b/app/src/organisms/LabwarePositionCheck/utils/getDisplayLocation.ts @@ -12,12 +12,16 @@ export function getDisplayLocation( location: LabwareOffsetLocation, labwareDefinitions: LabwareDefinition2[], t: TFunction, - i18n: i18n + i18n: i18n, + slotOnly?: boolean ): string { const slotDisplayLocation = i18n.format( t('slot_name', { slotName: location.slotName }), 'titleCase' ) + if (slotOnly) { + return slotDisplayLocation + } if ('definitionUri' in location && location.definitionUri != null) { const adapterDisplayName = labwareDefinitions.find( diff --git a/app/src/organisms/LabwarePositionCheck/utils/getProbeBasedLPCSteps.ts b/app/src/organisms/LabwarePositionCheck/utils/getProbeBasedLPCSteps.ts index 83faab7f32f..f5e4ed86f0b 100644 --- a/app/src/organisms/LabwarePositionCheck/utils/getProbeBasedLPCSteps.ts +++ b/app/src/organisms/LabwarePositionCheck/utils/getProbeBasedLPCSteps.ts @@ -2,7 +2,7 @@ import { isEqual } from 'lodash' import { SECTIONS } from '../constants' import { getLabwareDefURI, getPipetteNameSpecs } from '@opentrons/shared-data' import { getLabwareLocationCombos } from '../../ApplyHistoricOffsets/hooks/getLabwareLocationCombos' -import { getLabwareDefinitionsFromCommands } from '../../../molecules/Command/utils/getLabwareDefinitionsFromCommands' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' import type { CompletedProtocolAnalysis, @@ -58,7 +58,10 @@ function getAllCheckSectionSteps( const labwareDef = labwareDefinitions.find( def => getLabwareDefURI(def) === labwareLocationCombo.definitionUri ) - if ((labwareDef?.allowedRoles ?? []).includes('adapter')) { + if ( + (labwareDef?.allowedRoles ?? []).includes('adapter') || + (labwareDef?.allowedRoles ?? []).includes('lid') + ) { return acc } // remove duplicate definitionUri in same location diff --git a/app/src/organisms/LabwarePositionCheck/utils/getTipBasedLPCSteps.ts b/app/src/organisms/LabwarePositionCheck/utils/getTipBasedLPCSteps.ts index 01bc50330ba..47c30424e95 100644 --- a/app/src/organisms/LabwarePositionCheck/utils/getTipBasedLPCSteps.ts +++ b/app/src/organisms/LabwarePositionCheck/utils/getTipBasedLPCSteps.ts @@ -1,6 +1,6 @@ import { isEqual } from 'lodash' import { SECTIONS } from '../constants' -import { getLabwareDefinitionsFromCommands } from '../../../molecules/Command/utils/getLabwareDefinitionsFromCommands' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' import { getLabwareDefURI, getIsTiprack, @@ -18,8 +18,8 @@ import type { import type { RunTimeCommand, ProtocolAnalysisOutput, + PickUpTipRunTimeCommand, } from '@opentrons/shared-data' -import type { PickUpTipRunTimeCommand } from '@opentrons/shared-data/protocol/types/schemaV6/command/pipetting' import type { LabwareLocationCombo } from '../../ApplyHistoricOffsets/hooks/getLabwareLocationCombos' interface LPCArgs { diff --git a/app/src/organisms/LabwarePositionCheck/utils/labware.ts b/app/src/organisms/LabwarePositionCheck/utils/labware.ts index d4eae3581a5..70096061c33 100644 --- a/app/src/organisms/LabwarePositionCheck/utils/labware.ts +++ b/app/src/organisms/LabwarePositionCheck/utils/labware.ts @@ -4,8 +4,8 @@ import { getTiprackVolume, getLabwareDefURI, } from '@opentrons/shared-data' -import { getModuleInitialLoadInfo } from '../../Devices/ProtocolRun/utils/getModuleInitialLoadInfo' -import { getLabwareDefinitionsFromCommands } from '../../../molecules/Command/utils/getLabwareDefinitionsFromCommands' +import { getModuleInitialLoadInfo } from '/app/transformations/commands' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' import type { CompletedProtocolAnalysis, LabwareDefinition2, diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/LiquidDetailCard.tsx b/app/src/organisms/LiquidsLabwareDetailsModal/LiquidDetailCard.tsx similarity index 96% rename from app/src/organisms/Devices/ProtocolRun/SetupLiquids/LiquidDetailCard.tsx rename to app/src/organisms/LiquidsLabwareDetailsModal/LiquidDetailCard.tsx index 60044410e76..cb1591dc72f 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/LiquidDetailCard.tsx +++ b/app/src/organisms/LiquidsLabwareDetailsModal/LiquidDetailCard.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useSelector } from 'react-redux' import { css } from 'styled-components' import { @@ -6,25 +6,26 @@ import { BORDERS, Box, COLORS, + CURSOR_POINTER, DIRECTION_COLUMN, DIRECTION_ROW, Flex, Icon, JUSTIFY_CENTER, JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, SIZE_1, SPACING, - LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' import { MICRO_LITERS } from '@opentrons/shared-data' -import { Divider } from '../../../../atoms/structure' +import { Divider } from '/app/atoms/structure' import { useTrackEvent, ANALYTICS_HIGHLIGHT_LIQUID_IN_DETAIL_MODAL, -} from '../../../../redux/analytics' -import { getIsOnDevice } from '../../../../redux/config' -import { getWellRangeForLiquidLabwarePair } from './utils' +} from '/app/redux/analytics' +import { getIsOnDevice } from '/app/redux/config' +import { getWellRangeForLiquidLabwarePair } from '/app/transformations/analysis' export const CARD_OUTLINE_BORDER_STYLE = css` border-style: ${BORDERS.styleSolid}; @@ -41,7 +42,7 @@ const LIQUID_CARD_STYLE = css` &:hover { border: 1px solid ${COLORS.grey60}; border-radius: ${BORDERS.borderRadius8}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; } ` const LIQUID_CARD_ODD_STYLE = css` diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/LiquidsLabwareDetailsModal.tsx b/app/src/organisms/LiquidsLabwareDetailsModal/LiquidsLabwareDetailsModal.tsx similarity index 92% rename from app/src/organisms/Devices/ProtocolRun/SetupLiquids/LiquidsLabwareDetailsModal.tsx rename to app/src/organisms/LiquidsLabwareDetailsModal/LiquidsLabwareDetailsModal.tsx index 19da97932c9..62b5e3cf435 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/LiquidsLabwareDetailsModal.tsx +++ b/app/src/organisms/LiquidsLabwareDetailsModal/LiquidsLabwareDetailsModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useRef, useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' import { css } from 'styled-components' @@ -20,18 +20,20 @@ import { parseLiquidsInLoadOrder, } from '@opentrons/shared-data' -import { OddModal } from '../../../../molecules/OddModal' -import { getIsOnDevice } from '../../../../redux/config' -import { useMostRecentCompletedAnalysis } from '../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { getLocationInfoNames } from '../utils/getLocationInfoNames' -import { getSlotLabwareDefinition } from '../utils/getSlotLabwareDefinition' +import { OddModal } from '/app/molecules/OddModal' +import { getIsOnDevice } from '/app/redux/config' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { + getLocationInfoNames, + getSlotLabwareDefinition, +} from '/app/transformations/commands' import { LiquidDetailCard } from './LiquidDetailCard' import { getLiquidsByIdForLabware, getDisabledWellFillFromLabwareId, getWellGroupForLiquidId, getDisabledWellGroupForLiquidId, -} from './utils' +} from '/app/transformations/analysis' interface LiquidsLabwareDetailsModalProps { liquidId?: string @@ -46,7 +48,7 @@ export const LiquidsLabwareDetailsModal = ( const { liquidId, labwareId, runId, closeModal } = props const { t } = useTranslation('protocol_setup') const isOnDevice = useSelector(getIsOnDevice) - const currentLiquidRef = React.useRef(null) + const currentLiquidRef = useRef(null) const protocolData = useMostRecentCompletedAnalysis(runId) const commands = protocolData?.commands ?? [] const liquids = parseLiquidsInLoadOrder( @@ -63,7 +65,7 @@ export const LiquidsLabwareDetailsModal = ( const filteredLiquidsInLoadOrder = liquids.filter(liquid => { return Object.keys(labwareInfo).some(key => key === liquid.id) }) - const [selectedValue, setSelectedValue] = React.useState( + const [selectedValue, setSelectedValue] = useState( liquidId ?? filteredLiquidsInLoadOrder[0].id ) @@ -77,7 +79,7 @@ export const LiquidsLabwareDetailsModal = ( const scrollToCurrentItem = (): void => { currentLiquidRef.current?.scrollIntoView({ behavior: 'smooth' }) } - React.useEffect(() => { + useEffect(() => { scrollToCurrentItem() }, []) const HIDE_SCROLLBAR = css` diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/LiquidDetailCard.test.tsx b/app/src/organisms/LiquidsLabwareDetailsModal/__tests__/LiquidDetailCard.test.tsx similarity index 88% rename from app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/LiquidDetailCard.test.tsx rename to app/src/organisms/LiquidsLabwareDetailsModal/__tests__/LiquidDetailCard.test.tsx index 48d22bb3776..a96c8128897 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/LiquidDetailCard.test.tsx +++ b/app/src/organisms/LiquidsLabwareDetailsModal/__tests__/LiquidDetailCard.test.tsx @@ -1,24 +1,21 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, expect } from 'vitest' import { SPACING, COLORS } from '@opentrons/components' -import { - nestedTextMatcher, - renderWithProviders, -} from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' +import { nestedTextMatcher, renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { useTrackEvent, ANALYTICS_HIGHLIGHT_LIQUID_IN_DETAIL_MODAL, -} from '../../../../../redux/analytics' -import { getIsOnDevice } from '../../../../../redux/config' +} from '/app/redux/analytics' +import { getIsOnDevice } from '/app/redux/config' import { LiquidDetailCard } from '../LiquidDetailCard' import type { Mock } from 'vitest' -vi.mock('../../../../../redux/analytics') -vi.mock('../../../../../redux/config') +vi.mock('/app/redux/analytics') +vi.mock('/app/redux/config') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/LiquidsLabwareDetailsModal.test.tsx b/app/src/organisms/LiquidsLabwareDetailsModal/__tests__/LiquidsLabwareDetailsModal.test.tsx similarity index 82% rename from app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/LiquidsLabwareDetailsModal.test.tsx rename to app/src/organisms/LiquidsLabwareDetailsModal/__tests__/LiquidsLabwareDetailsModal.test.tsx index d25b0e67627..967a840ee75 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupLiquids/__tests__/LiquidsLabwareDetailsModal.test.tsx +++ b/app/src/organisms/LiquidsLabwareDetailsModal/__tests__/LiquidsLabwareDetailsModal.test.tsx @@ -1,24 +1,23 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' import { screen } from '@testing-library/react' import { LabwareRender } from '@opentrons/components' import { parseLiquidsInLoadOrder } from '@opentrons/shared-data' +import { nestedTextMatcher, renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getIsOnDevice } from '/app/redux/config' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { mockDefinition } from '/app/redux/custom-labware/__fixtures__' import { - nestedTextMatcher, - renderWithProviders, -} from '../../../../../__testing-utils__' -import { i18n } from '../../../../../i18n' -import { getIsOnDevice } from '../../../../../redux/config' -import { useMostRecentCompletedAnalysis } from '../../../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { mockDefinition } from '../../../../../redux/custom-labware/__fixtures__' -import { getLocationInfoNames } from '../../utils/getLocationInfoNames' -import { getSlotLabwareDefinition } from '../../utils/getSlotLabwareDefinition' + getLocationInfoNames, + getSlotLabwareDefinition, +} from '/app/transformations/commands' import { getLiquidsByIdForLabware, getDisabledWellFillFromLabwareId, -} from '../utils' +} from '/app/transformations/analysis' import { LiquidsLabwareDetailsModal } from '../LiquidsLabwareDetailsModal' import { LiquidDetailCard } from '../LiquidDetailCard' @@ -39,12 +38,10 @@ vi.mock('@opentrons/shared-data', async importOriginal => { parseLiquidsInLoadOrder: vi.fn(), } }) -vi.mock('../../../../../redux/config') -vi.mock('../../../../LabwarePositionCheck/useMostRecentCompletedAnalysis') -vi.mock('../../../../Devices/hooks') -vi.mock('../../utils/getLocationInfoNames') -vi.mock('../../utils/getSlotLabwareDefinition') -vi.mock('../utils') +vi.mock('/app/redux/config') +vi.mock('/app/resources/runs') +vi.mock('/app/transformations/commands') +vi.mock('/app/transformations/analysis') vi.mock('../LiquidDetailCard') const render = ( @@ -68,6 +65,7 @@ describe('LiquidsLabwareDetailsModal', () => { vi.mocked(getLocationInfoNames).mockReturnValue({ labwareName: 'mock labware name', slotName: '5', + labwareQuantity: 1, }) vi.mocked(getSlotLabwareDefinition).mockReturnValue(mockDefinition) vi.mocked(getLiquidsByIdForLabware).mockReturnValue({ diff --git a/app/src/organisms/LiquidsLabwareDetailsModal/index.ts b/app/src/organisms/LiquidsLabwareDetailsModal/index.ts new file mode 100644 index 00000000000..c548c0283a2 --- /dev/null +++ b/app/src/organisms/LiquidsLabwareDetailsModal/index.ts @@ -0,0 +1 @@ +export * from './LiquidsLabwareDetailsModal' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/ChooseModuleToConfigureModal.tsx b/app/src/organisms/LocationConflictModal/ChooseModuleToConfigureModal.tsx similarity index 94% rename from app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/ChooseModuleToConfigureModal.tsx rename to app/src/organisms/LocationConflictModal/ChooseModuleToConfigureModal.tsx index 3b2f70716bc..b91a63c7610 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/ChooseModuleToConfigureModal.tsx +++ b/app/src/organisms/LocationConflictModal/ChooseModuleToConfigureModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' @@ -24,12 +23,12 @@ import { MAGNETIC_BLOCK_V1, getModuleDisplayName, } from '@opentrons/shared-data' -import { getTopPortalEl } from '../../../../App/portal' -import { OddModal } from '../../../../molecules/OddModal' -import { FixtureOption } from '../../../DeviceDetailsDeckConfiguration/AddFixtureModal' -import { useNotifyDeckConfigurationQuery } from '../../../../resources/deck_configuration' -import { SmallButton } from '../../../../atoms/buttons' -import { useCloseCurrentRun } from '../../../ProtocolUpload/hooks' +import { getTopPortalEl } from '/app/App/portal' +import { OddModal } from '/app/molecules/OddModal' +import { FixtureOption } from '/app/organisms/DeviceDetailsDeckConfiguration/AddFixtureModal' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' +import { SmallButton } from '/app/atoms/buttons' +import { useCloseCurrentRun } from '/app/resources/runs' import type { ModuleModel, DeckDefinition } from '@opentrons/shared-data' import type { AttachedModule } from '@opentrons/api-client' diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal.tsx b/app/src/organisms/LocationConflictModal/LocationConflictModal.tsx similarity index 77% rename from app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal.tsx rename to app/src/organisms/LocationConflictModal/LocationConflictModal.tsx index 79c1a0a4a3f..f5c272ea673 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal.tsx +++ b/app/src/organisms/LocationConflictModal/LocationConflictModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { Trans, useTranslation } from 'react-i18next' import { useUpdateDeckConfigurationMutation } from '@opentrons/react-api-client' @@ -27,12 +27,15 @@ import { THERMOCYCLER_MODULE_V2, getCutoutFixturesForModuleModel, getFixtureIdByCutoutIdFromModuleSlotName, + SINGLE_LEFT_SLOT_FIXTURE, + THERMOCYCLER_V2_FRONT_FIXTURE, + THERMOCYCLER_V2_REAR_FIXTURE, } from '@opentrons/shared-data' -import { getTopPortalEl } from '../../../../App/portal' -import { OddModal } from '../../../../molecules/OddModal' -import { SmallButton } from '../../../../atoms/buttons/SmallButton' -import { useNotifyDeckConfigurationQuery } from '../../../../resources/deck_configuration' +import { getTopPortalEl } from '/app/App/portal' +import { OddModal } from '/app/molecules/OddModal' +import { SmallButton } from '/app/atoms/buttons/SmallButton' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' import type { CutoutConfig, @@ -69,32 +72,27 @@ export const LocationConflictModal = ( } = props const { t, i18n } = useTranslation(['protocol_setup', 'shared']) - const [showModuleSelect, setShowModuleSelect] = React.useState(false) + const [showModuleSelect, setShowModuleSelect] = useState(false) const deckConfig = useNotifyDeckConfigurationQuery().data ?? [] const { updateDeckConfiguration } = useUpdateDeckConfigurationMutation() const deckConfigurationAtLocationFixtureId = deckConfig.find( (deckFixture: CutoutConfig) => deckFixture.cutoutId === cutoutId )?.cutoutFixtureId - const isThermocycler = + const isThermocyclerRequired = requiredModule === THERMOCYCLER_MODULE_V1 || requiredModule === THERMOCYCLER_MODULE_V2 + // check if current fixture in cutoutId is thermocycler + const isThermocyclerCurrentFixture = + deckConfigurationAtLocationFixtureId === THERMOCYCLER_V2_REAR_FIXTURE || + deckConfigurationAtLocationFixtureId === THERMOCYCLER_V2_FRONT_FIXTURE + const currentFixtureDisplayName = deckConfigurationAtLocationFixtureId != null ? getFixtureDisplayName(deckConfigurationAtLocationFixtureId) : '' - // get fixture display name at A1 for themocycler if B1 is slot - const deckConfigurationAtA1 = deckConfig.find( - (deckFixture: CutoutConfig) => deckFixture.cutoutId === 'cutoutA1' - )?.cutoutFixtureId - - const currentThermocyclerFixtureDisplayName = - currentFixtureDisplayName === 'Slot' && deckConfigurationAtA1 != null - ? getFixtureDisplayName(deckConfigurationAtA1) - : currentFixtureDisplayName - const handleConfigureModule = (moduleSerialNumber?: string): void => { if (requiredModule != null) { const slotName = cutoutId.replace('cutout', '') @@ -111,14 +109,35 @@ export const LocationConflictModal = ( const newDeckConfig = deckConfig.map(existingCutoutConfig => { const replacementCutoutFixtureId = moduleFixtureIdByCutoutId[existingCutoutConfig.cutoutId] - return existingCutoutConfig.cutoutId in moduleFixtureIdByCutoutId && + if ( + existingCutoutConfig.cutoutId in moduleFixtureIdByCutoutId && replacementCutoutFixtureId != null - ? { - ...existingCutoutConfig, - cutoutFixtureId: replacementCutoutFixtureId, - opentronsModuleSerialNumber: moduleSerialNumber, - } - : existingCutoutConfig + ) { + return { + ...existingCutoutConfig, + cutoutFixtureId: replacementCutoutFixtureId, + opentronsModuleSerialNumber: moduleSerialNumber, + } + } else if ( + isThermocyclerCurrentFixture && + ((cutoutId === 'cutoutA1' && + existingCutoutConfig.cutoutId === 'cutoutB1') || + (cutoutId === 'cutoutB1' && + existingCutoutConfig.cutoutId === 'cutoutA1')) + ) { + /** + * special-case for removing current thermocycler: + * set paired cutout (B1 for A1, A1 for B1) to single slot left fixture + * TODO(bh, 2024-08-29): generalize to remove all entities from FixtureGroup + */ + return { + ...existingCutoutConfig, + cutoutFixtureId: SINGLE_LEFT_SLOT_FIXTURE, + opentronsModuleSerialNumber: undefined, + } + } else { + return existingCutoutConfig + } }) updateDeckConfiguration(newDeckConfig) } @@ -129,15 +148,33 @@ export const LocationConflictModal = ( if (requiredModule != null) { setShowModuleSelect(true) } else if (requiredFixtureId != null) { - const newRequiredFixtureDeckConfig = deckConfig.map(fixture => - fixture.cutoutId === cutoutId - ? { - ...fixture, - cutoutFixtureId: requiredFixtureId, - opentronsModuleSerialNumber: undefined, - } - : fixture - ) + const newRequiredFixtureDeckConfig = deckConfig.map(fixture => { + if (fixture.cutoutId === cutoutId) { + return { + ...fixture, + cutoutFixtureId: requiredFixtureId, + opentronsModuleSerialNumber: undefined, + } + } else if ( + isThermocyclerCurrentFixture && + ((cutoutId === 'cutoutA1' && fixture.cutoutId === 'cutoutB1') || + (cutoutId === 'cutoutB1' && fixture.cutoutId === 'cutoutA1')) + ) { + /** + * special-case for removing current thermocycler: + * set paired cutout (B1 for A1, A1 for B1) to single slot left fixture + * TODO(bh, 2024-08-29): generalize to remove all entities from FixtureGroup + */ + return { + ...fixture, + cutoutFixtureId: SINGLE_LEFT_SLOT_FIXTURE, + opentronsModuleSerialNumber: undefined, + } + } else { + return fixture + } + }) + updateDeckConfiguration(newRequiredFixtureDeckConfig) onCloseClick() } else { @@ -154,9 +191,10 @@ export const LocationConflictModal = ( protocolSpecifiesDisplayName = getModuleDisplayName(requiredModule) } - const displaySlotName = isThermocycler - ? 'A1 + B1' - : getCutoutDisplayName(cutoutId) + const displaySlotName = + isThermocyclerRequired || isThermocyclerCurrentFixture + ? 'A1 + B1' + : getCutoutDisplayName(cutoutId) if (showModuleSelect && requiredModule != null) { return createPortal( @@ -189,13 +227,13 @@ export const LocationConflictModal = ( , @@ -298,13 +336,13 @@ export const LocationConflictModal = ( , @@ -350,9 +388,7 @@ export const LocationConflictModal = ( {t('currently_configured')} - {isThermocycler - ? currentThermocyclerFixtureDisplayName - : currentFixtureDisplayName} + {currentFixtureDisplayName} diff --git a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/LocationConflictModal.test.tsx b/app/src/organisms/LocationConflictModal/__tests__/LocationConflictModal.test.tsx similarity index 91% rename from app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/LocationConflictModal.test.tsx rename to app/src/organisms/LocationConflictModal/__tests__/LocationConflictModal.test.tsx index 9713f35508a..207caa02a1b 100644 --- a/app/src/organisms/Devices/ProtocolRun/SetupModuleAndDeck/__tests__/LocationConflictModal.test.tsx +++ b/app/src/organisms/LocationConflictModal/__tests__/LocationConflictModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { screen, fireEvent } from '@testing-library/react' import '@testing-library/jest-dom/vitest' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' -import { renderWithProviders } from '../../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { SINGLE_RIGHT_SLOT_FIXTURE, STAGING_AREA_RIGHT_SLOT_FIXTURE, @@ -15,18 +15,18 @@ import { useUpdateDeckConfigurationMutation, } from '@opentrons/react-api-client' -import { i18n } from '../../../../../i18n' -import { mockHeaterShaker } from '../../../../../redux/modules/__fixtures__' -import { useCloseCurrentRun } from '../../../../ProtocolUpload/hooks' +import { i18n } from '/app/i18n' +import { mockHeaterShaker } from '/app/redux/modules/__fixtures__' +import { useCloseCurrentRun } from '/app/resources/runs' import { LocationConflictModal } from '../LocationConflictModal' -import { useNotifyDeckConfigurationQuery } from '../../../../../resources/deck_configuration' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' import type { UseQueryResult } from 'react-query' import type { DeckConfiguration } from '@opentrons/shared-data' vi.mock('@opentrons/react-api-client') -vi.mock('../../../../../resources/deck_configuration') -vi.mock('../../../../ProtocolUpload/hooks') +vi.mock('/app/resources/deck_configuration') +vi.mock('/app/resources/runs') const mockFixture = { cutoutId: 'cutoutB3', diff --git a/app/src/organisms/LocationConflictModal/index.ts b/app/src/organisms/LocationConflictModal/index.ts new file mode 100644 index 00000000000..732157f3a5a --- /dev/null +++ b/app/src/organisms/LocationConflictModal/index.ts @@ -0,0 +1,2 @@ +export * from './LocationConflictModal' +export * from './ChooseModuleToConfigureModal' diff --git a/app/src/organisms/ModuleCard/AboutModuleSlideout.tsx b/app/src/organisms/ModuleCard/AboutModuleSlideout.tsx index ddd3f60a6fb..e1ad4d94ce9 100644 --- a/app/src/organisms/ModuleCard/AboutModuleSlideout.tsx +++ b/app/src/organisms/ModuleCard/AboutModuleSlideout.tsx @@ -1,9 +1,10 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' import { RUN_STATUS_RUNNING, RUN_STATUS_FINISHING } from '@opentrons/api-client' import { ALIGN_START, + Banner, Btn, COLORS, DIRECTION_COLUMN, @@ -16,16 +17,15 @@ import { } from '@opentrons/components' import { getModuleDisplayName } from '@opentrons/shared-data' import { Slideout } from '../../atoms/Slideout' -import { Banner } from '../../atoms/Banner' -import { useCurrentRunStatus } from '../RunTimeControl/hooks' +import { useCurrentRunStatus } from '/app/organisms/RunTimeControl' -import type { AttachedModule } from '../../redux/modules/types' +import type { AttachedModule } from '/app/redux/modules/types' interface AboutModuleSlideoutProps { module: AttachedModule - onCloseClick: () => unknown + onCloseClick: () => void isExpanded: boolean - firmwareUpdateClick: () => unknown + firmwareUpdateClick: () => void } const ALERT_ITEM_STYLE = css` @@ -40,7 +40,7 @@ export const AboutModuleSlideout = ( const { i18n, t } = useTranslation(['device_details', 'shared']) const moduleName = getModuleDisplayName(module.moduleModel) const runStatus = useCurrentRunStatus() - const [showBanner, setShowBanner] = React.useState(true) + const [showBanner, setShowBanner] = useState(true) const isDisabled = runStatus === RUN_STATUS_RUNNING || runStatus === RUN_STATUS_FINISHING diff --git a/app/src/organisms/ModuleCard/AbsorbanceReaderData.tsx b/app/src/organisms/ModuleCard/AbsorbanceReaderData.tsx index e6a30b742d7..2283988c48b 100644 --- a/app/src/organisms/ModuleCard/AbsorbanceReaderData.tsx +++ b/app/src/organisms/ModuleCard/AbsorbanceReaderData.tsx @@ -1,7 +1,8 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' -import { TYPOGRAPHY, LegacyStyledText } from '@opentrons/components' -import type { AbsorbanceReaderModule } from '../../redux/modules/types' +import { StyledText, COLORS } from '@opentrons/components' +import { StatusLabel } from '/app/atoms/StatusLabel' + +import type { AbsorbanceReaderModule } from '/app/redux/modules/types' interface AbsorbanceReaderProps { moduleData: AbsorbanceReaderModule['data'] @@ -11,18 +12,47 @@ export const AbsorbanceReaderData = ( props: AbsorbanceReaderProps ): JSX.Element | null => { const { moduleData } = props - const { t } = useTranslation('device_details') + const { t, i18n } = useTranslation(['device_details', 'shared']) + + const StatusLabelProps = { + status: 'Idle', + backgroundColor: COLORS.grey30, + iconColor: COLORS.grey60, + textColor: COLORS.grey60, + pulse: false, + } + switch (moduleData.status) { + case 'measuring': { + StatusLabelProps.status = 'Reading' + StatusLabelProps.backgroundColor = COLORS.blue30 + StatusLabelProps.iconColor = COLORS.blue60 + StatusLabelProps.textColor = COLORS.blue60 + break + } + case 'error': { + StatusLabelProps.status = 'Error' + StatusLabelProps.backgroundColor = COLORS.yellow30 + StatusLabelProps.iconColor = COLORS.yellow60 + StatusLabelProps.textColor = COLORS.yellow60 + break + } + } + const lidDisplayStatus = + moduleData.lidStatus === 'on' + ? i18n.format(t('shared:closed'), 'capitalize') + : i18n.format(t('shared:open'), 'capitalize') return ( <> - + - {t('abs_reader_status', { - status: moduleData.status, + {t('abs_reader_lid_status', { + status: lidDisplayStatus, })} - + ) } diff --git a/app/src/organisms/ModuleCard/AbsorbanceReaderSlideout.tsx b/app/src/organisms/ModuleCard/AbsorbanceReaderSlideout.tsx index fd04a4730a7..8cfd8597135 100644 --- a/app/src/organisms/ModuleCard/AbsorbanceReaderSlideout.tsx +++ b/app/src/organisms/ModuleCard/AbsorbanceReaderSlideout.tsx @@ -1,10 +1,9 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { getModuleDisplayName } from '@opentrons/shared-data' import { SPACING, LegacyStyledText, TYPOGRAPHY } from '@opentrons/components' -import { Slideout } from '../../atoms/Slideout' +import { Slideout } from '/app/atoms/Slideout' -import type { AbsorbanceReaderModule } from '../../redux/modules/types' +import type { AbsorbanceReaderModule } from '/app/redux/modules/types' interface AbsorbanceReaderSlideoutProps { module: AbsorbanceReaderModule diff --git a/app/src/organisms/ModuleCard/Collapsible.tsx b/app/src/organisms/ModuleCard/Collapsible.tsx index 44b7c883b8a..cc15a88d4a0 100644 --- a/app/src/organisms/ModuleCard/Collapsible.tsx +++ b/app/src/organisms/ModuleCard/Collapsible.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { ALIGN_CENTER, diff --git a/app/src/organisms/ModuleCard/ConfirmAttachmentModal.tsx b/app/src/organisms/ModuleCard/ConfirmAttachmentModal.tsx index 8797f0aebc0..4a9cb9f3e4a 100644 --- a/app/src/organisms/ModuleCard/ConfirmAttachmentModal.tsx +++ b/app/src/organisms/ModuleCard/ConfirmAttachmentModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useDispatch } from 'react-redux' import { @@ -16,9 +16,11 @@ import { TEXT_ALIGN_CENTER, TYPOGRAPHY, } from '@opentrons/components' -import { updateConfigValue } from '../../redux/config' -import type { Dispatch } from '../../redux/types' -import type { UpdateConfigValueAction } from '../../redux/config/types' +import { updateConfigValue } from '/app/redux/config' + +import type { ChangeEvent } from 'react' +import type { Dispatch } from '/app/redux/types' +import type { UpdateConfigValueAction } from '/app/redux/config/types' export function setHeaterShakerAttached( heaterShakerAttached: boolean @@ -28,7 +30,7 @@ export function setHeaterShakerAttached( heaterShakerAttached ) } -interface ConfirmAttachmentModalProps { +export interface ConfirmAttachmentModalProps { onCloseClick: () => void isProceedToRunModal: boolean onConfirmClick: () => void @@ -38,7 +40,7 @@ export const ConfirmAttachmentModal = ( ): JSX.Element | null => { const { isProceedToRunModal, onCloseClick, onConfirmClick } = props const { t } = useTranslation(['heater_shaker', 'shared']) - const [isDismissed, setIsDismissed] = React.useState(false) + const [isDismissed, setIsDismissed] = useState(false) const dispatch = useDispatch() const confirmAttached = (): void => { @@ -81,7 +83,7 @@ export const ConfirmAttachmentModal = ( }`} > ) => { + onChange={(e: ChangeEvent) => { setIsDismissed(e.currentTarget.checked) }} value={isDismissed} diff --git a/app/src/organisms/ModuleCard/ErrorInfo.tsx b/app/src/organisms/ModuleCard/ErrorInfo.tsx index 5bb41c4e72d..d3f0d966e49 100644 --- a/app/src/organisms/ModuleCard/ErrorInfo.tsx +++ b/app/src/organisms/ModuleCard/ErrorInfo.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { @@ -8,6 +8,7 @@ import { } from '@opentrons/shared-data' import { ALIGN_START, + Banner, Btn, DIRECTION_COLUMN, DIRECTION_ROW, @@ -19,10 +20,9 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { Banner } from '../../atoms/Banner' -import { getTopPortalEl } from '../../App/portal' +import { getTopPortalEl } from '/app/App/portal' -import type { AttachedModule } from '../../redux/modules/types' +import type { AttachedModule } from '/app/redux/modules/types' interface ErrorInfoProps { attachedModule: AttachedModule @@ -30,7 +30,7 @@ interface ErrorInfoProps { export function ErrorInfo(props: ErrorInfoProps): JSX.Element | null { const { attachedModule } = props const { t } = useTranslation(['device_details', 'shared', 'branded']) - const [showErrorDetails, setShowErrorDetails] = React.useState(false) + const [showErrorDetails, setShowErrorDetails] = useState(false) let isError: boolean = false // extend this logic when we know how to tell when Mag/Temp modules are in error state diff --git a/app/src/organisms/ModuleCard/FirmwareUpdateFailedModal.tsx b/app/src/organisms/ModuleCard/FirmwareUpdateFailedModal.tsx index d1af72560b8..47e95f8bf96 100644 --- a/app/src/organisms/ModuleCard/FirmwareUpdateFailedModal.tsx +++ b/app/src/organisms/ModuleCard/FirmwareUpdateFailedModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { getModuleDisplayName } from '@opentrons/shared-data' import { @@ -15,7 +14,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import type { AttachedModule } from '../../redux/modules/types' +import type { AttachedModule } from '/app/redux/modules/types' interface FirmwareUpdateFailedModalProps { onCloseClick: () => void diff --git a/app/src/organisms/ModuleCard/HeaterShakerModuleData.tsx b/app/src/organisms/ModuleCard/HeaterShakerModuleData.tsx index ea33d37736c..67bed6f6e5d 100644 --- a/app/src/organisms/ModuleCard/HeaterShakerModuleData.tsx +++ b/app/src/organisms/ModuleCard/HeaterShakerModuleData.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { COLORS, @@ -12,13 +11,13 @@ import { TYPOGRAPHY, WRAP, } from '@opentrons/components' -import { StatusLabel } from '../../atoms/StatusLabel' +import { StatusLabel } from '/app/atoms/StatusLabel' import type { LatchStatus, SpeedStatus, TemperatureStatus, -} from '../../redux/modules/api-types' -import type { HeaterShakerModule } from '../../redux/modules/types' +} from '/app/redux/modules/api-types' +import type { HeaterShakerModule } from '/app/redux/modules/types' interface HeaterShakerModuleDataProps { moduleData: HeaterShakerModule['data'] diff --git a/app/src/organisms/ModuleCard/HeaterShakerSlideout.tsx b/app/src/organisms/ModuleCard/HeaterShakerSlideout.tsx index 3265a149f45..bccf5fb3a30 100644 --- a/app/src/organisms/ModuleCard/HeaterShakerSlideout.tsx +++ b/app/src/organisms/ModuleCard/HeaterShakerSlideout.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' @@ -18,10 +18,11 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { Slideout } from '../../atoms/Slideout' -import { SubmitPrimaryButton } from '../../atoms/buttons' +import { Slideout } from '/app/atoms/Slideout' +import { SubmitPrimaryButton } from '/app/atoms/buttons' -import type { HeaterShakerModule } from '../../redux/modules/types' +import type { MouseEventHandler } from 'react' +import type { HeaterShakerModule } from '/app/redux/modules/types' import type { HeaterShakerSetTargetTemperatureCreateCommand } from '@opentrons/shared-data' interface HeaterShakerSlideoutProps { @@ -35,12 +36,12 @@ export const HeaterShakerSlideout = ( ): JSX.Element | null => { const { module, onCloseClick, isExpanded } = props const { t } = useTranslation('device_details') - const [hsValue, setHsValue] = React.useState(null) + const [hsValue, setHsValue] = useState(null) const { createLiveCommand } = useCreateLiveCommandMutation() const moduleName = getModuleDisplayName(module.moduleModel) const modulePart = t('temperature') - const sendSetTemperatureCommand: React.MouseEventHandler = e => { + const sendSetTemperatureCommand: MouseEventHandler = e => { e.preventDefault() e.stopPropagation() diff --git a/app/src/organisms/ModuleCard/MagneticModuleData.tsx b/app/src/organisms/ModuleCard/MagneticModuleData.tsx index e9cdc7aeb5c..f9282505b8a 100644 --- a/app/src/organisms/ModuleCard/MagneticModuleData.tsx +++ b/app/src/organisms/ModuleCard/MagneticModuleData.tsx @@ -1,10 +1,9 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { COLORS, TYPOGRAPHY, LegacyStyledText } from '@opentrons/components' import { MAGNETIC_MODULE_V2 } from '@opentrons/shared-data' -import { StatusLabel } from '../../atoms/StatusLabel' +import { StatusLabel } from '/app/atoms/StatusLabel' import type { MAGNETIC_MODULE_V1 } from '@opentrons/shared-data' -import type { MagneticStatus } from '../../redux/modules/api-types' +import type { MagneticStatus } from '/app/redux/modules/api-types' interface MagModuleProps { moduleStatus: MagneticStatus diff --git a/app/src/organisms/ModuleCard/MagneticModuleSlideout.tsx b/app/src/organisms/ModuleCard/MagneticModuleSlideout.tsx index 4dcbbddc349..66e428c682d 100644 --- a/app/src/organisms/ModuleCard/MagneticModuleSlideout.tsx +++ b/app/src/organisms/ModuleCard/MagneticModuleSlideout.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' @@ -25,10 +25,10 @@ import { MM, } from '@opentrons/shared-data' -import { Slideout } from '../../atoms/Slideout' -import { SubmitPrimaryButton } from '../../atoms/buttons' +import { Slideout } from '/app/atoms/Slideout' +import { SubmitPrimaryButton } from '/app/atoms/buttons' -import type { MagneticModule } from '../../redux/modules/types' +import type { MagneticModule } from '/app/redux/modules/types' import type { MagneticModuleEngageMagnetCreateCommand, MagneticModuleModel, @@ -73,9 +73,9 @@ export const MagneticModuleSlideout = ( const { module, isExpanded, onCloseClick } = props const { t } = useTranslation('device_details') const { createLiveCommand } = useCreateLiveCommandMutation() - const [engageHeightValue, setEngageHeightValue] = React.useState< - string | null - >(null) + const [engageHeightValue, setEngageHeightValue] = useState( + null + ) const moduleName = getModuleDisplayName(module.moduleModel) const info = getInfoByModel(module.moduleModel) diff --git a/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx b/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx index 78596b831de..a668fc48e70 100644 --- a/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx +++ b/app/src/organisms/ModuleCard/ModuleOverflowMenu.tsx @@ -1,30 +1,29 @@ -import * as React from 'react' +import { Fragment } from 'react' import { useTranslation } from 'react-i18next' import { Flex, MenuItem, MenuList, + NO_WRAP, POSITION_RELATIVE, Tooltip, useHoverTooltip, } from '@opentrons/components' import { + ABSORBANCE_READER_TYPE, HEATERSHAKER_MODULE_TYPE, MODULE_MODELS_OT2_ONLY, TEMPERATURE_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, } from '@opentrons/shared-data' -import { useCurrentRunId } from '../../resources/runs' -import { - useIsFlex, - useRunStatuses, - useIsLegacySessionInProgress, -} from '../Devices/hooks' +import { useCurrentRunId, useRunStatuses } from '/app/resources/runs' +import { useIsLegacySessionInProgress } from '/app/resources/legacy_sessions' +import { useIsFlex } from '/app/redux-resources/robots' import { useModuleOverflowMenu } from './hooks' -import type { AttachedModule } from '../../redux/modules/types' +import type { AttachedModule } from '/app/redux/modules/types' interface ModuleOverflowMenuProps { module: AttachedModule @@ -121,6 +120,7 @@ export const ModuleOverflowMenu = ( {isFlex && + module.moduleType !== ABSORBANCE_READER_TYPE && !MODULE_MODELS_OT2_ONLY.some( modModel => modModel === module.moduleModel ) ? ( @@ -147,16 +147,16 @@ export const ModuleOverflowMenu = ( {menuOverflowItemsByModuleType[module.moduleType].map( (item: any, index: number) => { return ( - + item.onClick(item.isSecondary)} - disabled={item.disabledReason || isDisabled} - whiteSpace="nowrap" + disabled={item.isSettingDisabled} + whiteSpace={NO_WRAP} > {item.setSetting} {item.menuButtons} - + ) } )} diff --git a/app/src/organisms/ModuleCard/ModuleSetupModal.tsx b/app/src/organisms/ModuleCard/ModuleSetupModal.tsx index f389cc878ea..91696be776f 100644 --- a/app/src/organisms/ModuleCard/ModuleSetupModal.tsx +++ b/app/src/organisms/ModuleCard/ModuleSetupModal.tsx @@ -1,7 +1,7 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom' -import code from '../../assets/images/module_instruction_code.png' +import helpCenterQRCode from '/app/assets/images/module_instruction_code.png' +import absorbanceReaderManualQRCode from '/app/assets/images/absorbance_reader_instruction_manual_code.png' import { ALIGN_FLEX_END, DIRECTION_COLUMN, @@ -15,17 +15,20 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { getTopPortalEl } from '../../App/portal' +import { getTopPortalEl } from '/app/App/portal' const MODULE_SETUP_URL = 'https://support.opentrons.com/s/modules' +const ABSORBANCE_READER_MANUAL_URL = + 'https://insights.opentrons.com/hubfs/Absorbance%20Plate%20Reader%20Instruction%20Manual.pdf' interface ModuleSetupModalProps { close: () => void moduleDisplayName: string + isAbsorbanceReader?: boolean } export const ModuleSetupModal = (props: ModuleSetupModalProps): JSX.Element => { - const { moduleDisplayName } = props + const { moduleDisplayName, isAbsorbanceReader } = props const { t, i18n } = useTranslation(['protocol_setup', 'shared', 'branded']) return createPortal( @@ -42,12 +45,18 @@ export const ModuleSetupModal = (props: ModuleSetupModalProps): JSX.Element => { width="50%" > - {t('branded:modal_instructions')} + {isAbsorbanceReader + ? t('module_instructions_manual') + : t('branded:modal_instructions')} { /> - + {i18n.format(t('shared:close'), 'capitalize')} diff --git a/app/src/organisms/ModuleCard/TemperatureModuleData.tsx b/app/src/organisms/ModuleCard/TemperatureModuleData.tsx index 2cadd9fd12b..a5ba2bcbd9f 100644 --- a/app/src/organisms/ModuleCard/TemperatureModuleData.tsx +++ b/app/src/organisms/ModuleCard/TemperatureModuleData.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { COLORS, @@ -8,8 +7,8 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { StatusLabel } from '../../atoms/StatusLabel' -import type { TemperatureStatus } from '../../redux/modules/api-types' +import { StatusLabel } from '/app/atoms/StatusLabel' +import type { TemperatureStatus } from '/app/redux/modules/api-types' interface TemperatureModuleProps { moduleStatus: TemperatureStatus diff --git a/app/src/organisms/ModuleCard/TemperatureModuleSlideout.tsx b/app/src/organisms/ModuleCard/TemperatureModuleSlideout.tsx index dc6fe561ba3..18928ecae10 100644 --- a/app/src/organisms/ModuleCard/TemperatureModuleSlideout.tsx +++ b/app/src/organisms/ModuleCard/TemperatureModuleSlideout.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' @@ -18,11 +18,11 @@ import { TEMP_MIN, } from '@opentrons/shared-data' -import { Slideout } from '../../atoms/Slideout' -import { SubmitPrimaryButton } from '../../atoms/buttons' +import { Slideout } from '/app/atoms/Slideout' +import { SubmitPrimaryButton } from '/app/atoms/buttons' import type { TemperatureModuleSetTargetTemperatureCreateCommand } from '@opentrons/shared-data' -import type { TemperatureModule } from '../../redux/modules/types' +import type { TemperatureModule } from '/app/redux/modules/types' interface TemperatureModuleSlideoutProps { module: TemperatureModule @@ -37,9 +37,7 @@ export const TemperatureModuleSlideout = ( const { t } = useTranslation('device_details') const { createLiveCommand } = useCreateLiveCommandMutation() const name = getModuleDisplayName(module.moduleModel) - const [temperatureValue, setTemperatureValue] = React.useState( - null - ) + const [temperatureValue, setTemperatureValue] = useState(null) const handleSubmitTemperature = (): void => { if (temperatureValue != null) { const saveTempCommand: TemperatureModuleSetTargetTemperatureCreateCommand = { diff --git a/app/src/organisms/ModuleCard/TestShakeSlideout.tsx b/app/src/organisms/ModuleCard/TestShakeSlideout.tsx index a98c6a63583..7f0c8a8e576 100644 --- a/app/src/organisms/ModuleCard/TestShakeSlideout.tsx +++ b/app/src/organisms/ModuleCard/TestShakeSlideout.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' @@ -31,11 +31,11 @@ import { RPM, } from '@opentrons/shared-data' -import { getIsHeaterShakerAttached } from '../../redux/config' -import { getTopPortalEl } from '../../App/portal' -import { Slideout } from '../../atoms/Slideout' -import { TertiaryButton } from '../../atoms/buttons' -import { Divider } from '../../atoms/structure' +import { getIsHeaterShakerAttached } from '/app/redux/config' +import { getTopPortalEl } from '/app/App/portal' +import { Slideout } from '/app/atoms/Slideout' +import { TertiaryButton } from '/app/atoms/buttons' +import { Divider } from '/app/atoms/structure' import { ConfirmAttachmentModal } from './ConfirmAttachmentModal' import { useLatchControls } from './hooks' import { ModuleSetupModal } from './ModuleSetupModal' @@ -46,7 +46,7 @@ import type { HeaterShakerDeactivateShakerCreateCommand, HeaterShakerSetAndWaitForShakeSpeedCreateCommand, } from '@opentrons/shared-data' -import type { HeaterShakerModule, LatchStatus } from '../../redux/modules/types' +import type { HeaterShakerModule, LatchStatus } from '/app/redux/modules/types' interface TestShakeSlideoutProps { module: HeaterShakerModule @@ -66,11 +66,10 @@ export const TestShakeSlideout = ( }) const { toggleLatch, isLatchClosed } = useLatchControls(module) const configHasHeaterShakerAttached = useSelector(getIsHeaterShakerAttached) - const [shakeValue, setShakeValue] = React.useState(null) - const [ - showModuleSetupModal, - setShowModuleSetupModal, - ] = React.useState(false) + const [shakeValue, setShakeValue] = useState(null) + const [showModuleSetupModal, setShowModuleSetupModal] = useState( + false + ) const isShaking = module.data.speedStatus !== 'idle' const setShakeCommand: HeaterShakerSetAndWaitForShakeSpeedCreateCommand = { diff --git a/app/src/organisms/ModuleCard/ThermocyclerModuleData.tsx b/app/src/organisms/ModuleCard/ThermocyclerModuleData.tsx index a495b7afcb5..c0eed06384d 100644 --- a/app/src/organisms/ModuleCard/ThermocyclerModuleData.tsx +++ b/app/src/organisms/ModuleCard/ThermocyclerModuleData.tsx @@ -1,6 +1,5 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' -import { StatusLabel } from '../../atoms/StatusLabel' +import { StatusLabel } from '/app/atoms/StatusLabel' import { Box, COLORS, @@ -14,7 +13,7 @@ import { WRAP, } from '@opentrons/components' -import type { ThermocyclerData } from '../../redux/modules/api-types' +import type { ThermocyclerData } from '/app/redux/modules/api-types' interface ThermocyclerModuleProps { data: ThermocyclerData diff --git a/app/src/organisms/ModuleCard/ThermocyclerModuleSlideout.tsx b/app/src/organisms/ModuleCard/ThermocyclerModuleSlideout.tsx index dcacc771369..e0bf348022c 100644 --- a/app/src/organisms/ModuleCard/ThermocyclerModuleSlideout.tsx +++ b/app/src/organisms/ModuleCard/ThermocyclerModuleSlideout.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { CELSIUS, @@ -19,14 +19,14 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { Slideout } from '../../atoms/Slideout' -import { SubmitPrimaryButton } from '../../atoms/buttons' +import { Slideout } from '/app/atoms/Slideout' +import { SubmitPrimaryButton } from '/app/atoms/buttons' import type { TCSetTargetBlockTemperatureCreateCommand, TCSetTargetLidTemperatureCreateCommand, } from '@opentrons/shared-data' -import type { ThermocyclerModule } from '../../redux/modules/types' +import type { ThermocyclerModule } from '/app/redux/modules/types' interface ThermocyclerModuleSlideoutProps { module: ThermocyclerModule @@ -40,7 +40,7 @@ export const ThermocyclerModuleSlideout = ( ): JSX.Element | null => { const { module, onCloseClick, isExpanded, isSecondaryTemp } = props const { t } = useTranslation('device_details') - const [tempValue, setTempValue] = React.useState(null) + const [tempValue, setTempValue] = useState(null) const { createLiveCommand } = useCreateLiveCommandMutation() const moduleName = getModuleDisplayName(module.moduleModel) const modulePart = isSecondaryTemp ? 'Lid' : 'Block' diff --git a/app/src/organisms/ModuleCard/__tests__/AboutModuleSlideout.test.tsx b/app/src/organisms/ModuleCard/__tests__/AboutModuleSlideout.test.tsx index 66ac7ccd016..35eb81ab169 100644 --- a/app/src/organisms/ModuleCard/__tests__/AboutModuleSlideout.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/AboutModuleSlideout.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -8,19 +8,19 @@ import { RUN_STATUS_FINISHING, } from '@opentrons/api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockMagneticModule, mockMagneticModuleGen2, mockTemperatureModule, mockTemperatureModuleGen2, mockThermocycler, -} from '../../../redux/modules/__fixtures__' -import { useCurrentRunStatus } from '../../RunTimeControl/hooks' +} from '/app/redux/modules/__fixtures__' +import { useCurrentRunStatus } from '/app/organisms/RunTimeControl' import { AboutModuleSlideout } from '../AboutModuleSlideout' -vi.mock('../../RunTimeControl/hooks') +vi.mock('/app/organisms/RunTimeControl') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/ModuleCard/__tests__/Collapsible.test.tsx b/app/src/organisms/ModuleCard/__tests__/Collapsible.test.tsx index 5d6fcbdffba..3db479e3228 100644 --- a/app/src/organisms/ModuleCard/__tests__/Collapsible.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/Collapsible.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { Collapsible } from '../Collapsible' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/ModuleCard/__tests__/ConfirmAttachmentModal.test.tsx b/app/src/organisms/ModuleCard/__tests__/ConfirmAttachmentModal.test.tsx index 47b16c62383..ccc81bcb167 100644 --- a/app/src/organisms/ModuleCard/__tests__/ConfirmAttachmentModal.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/ConfirmAttachmentModal.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ConfirmAttachmentModal } from '../ConfirmAttachmentModal' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/ModuleCard/__tests__/ErrorInfo.test.tsx b/app/src/organisms/ModuleCard/__tests__/ErrorInfo.test.tsx index c578307ae8a..bde80a0d7d2 100644 --- a/app/src/organisms/ModuleCard/__tests__/ErrorInfo.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/ErrorInfo.test.tsx @@ -1,18 +1,18 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ErrorInfo } from '../ErrorInfo' import { mockHeaterShaker, mockTemperatureModule, mockThermocycler, -} from '../../../redux/modules/__fixtures__' +} from '/app/redux/modules/__fixtures__' import type { HeaterShakerModule, ThermocyclerModule, -} from '../../../redux/modules/types' +} from '/app/redux/modules/types' const mockErrorThermocycler = { id: 'thermocycler_id', diff --git a/app/src/organisms/ModuleCard/__tests__/FirmwareUpdateFailedModal.test.tsx b/app/src/organisms/ModuleCard/__tests__/FirmwareUpdateFailedModal.test.tsx index f47e2331350..13395bcff69 100644 --- a/app/src/organisms/ModuleCard/__tests__/FirmwareUpdateFailedModal.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/FirmwareUpdateFailedModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockTemperatureModule } from '../../../redux/modules/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockTemperatureModule } from '/app/redux/modules/__fixtures__' import { FirmwareUpdateFailedModal } from '../FirmwareUpdateFailedModal' const render = ( diff --git a/app/src/organisms/ModuleCard/__tests__/HeaterShakerModuleData.test.tsx b/app/src/organisms/ModuleCard/__tests__/HeaterShakerModuleData.test.tsx index 54ca6a319ac..348fdb614d4 100644 --- a/app/src/organisms/ModuleCard/__tests__/HeaterShakerModuleData.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/HeaterShakerModuleData.test.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { StatusLabel } from '../../../atoms/StatusLabel' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { StatusLabel } from '/app/atoms/StatusLabel' import { HeaterShakerModuleData } from '../HeaterShakerModuleData' -vi.mock('../../../atoms/StatusLabel') +vi.mock('/app/atoms/StatusLabel') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/ModuleCard/__tests__/HeaterShakerSlideout.test.tsx b/app/src/organisms/ModuleCard/__tests__/HeaterShakerSlideout.test.tsx index 7148fd3f645..883d5b6bb7c 100644 --- a/app/src/organisms/ModuleCard/__tests__/HeaterShakerSlideout.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/HeaterShakerSlideout.test.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockHeaterShaker } from '../../../redux/modules/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockHeaterShaker } from '/app/redux/modules/__fixtures__' import { HeaterShakerSlideout } from '../HeaterShakerSlideout' vi.mock('@opentrons/react-api-client') diff --git a/app/src/organisms/ModuleCard/__tests__/MagneticModuleData.test.tsx b/app/src/organisms/ModuleCard/__tests__/MagneticModuleData.test.tsx index 2cbcc154510..b6534d233e3 100644 --- a/app/src/organisms/ModuleCard/__tests__/MagneticModuleData.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/MagneticModuleData.test.tsx @@ -1,14 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { afterEach, beforeEach, describe, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { StatusLabel } from '../../../atoms/StatusLabel' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { StatusLabel } from '/app/atoms/StatusLabel' import { MagneticModuleData } from '../MagneticModuleData' -import { mockMagneticModule } from '../../../redux/modules/__fixtures__' +import { mockMagneticModule } from '/app/redux/modules/__fixtures__' -vi.mock('../../../atoms/StatusLabel') +vi.mock('/app/atoms/StatusLabel') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/ModuleCard/__tests__/MagneticModuleSlideout.test.tsx b/app/src/organisms/ModuleCard/__tests__/MagneticModuleSlideout.test.tsx index 8414580df17..fa10546af90 100644 --- a/app/src/organisms/ModuleCard/__tests__/MagneticModuleSlideout.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/MagneticModuleSlideout.test.tsx @@ -1,17 +1,17 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { COLORS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' import { MagneticModuleSlideout } from '../MagneticModuleSlideout' import { mockMagneticModule, mockMagneticModuleGen2, -} from '../../../redux/modules/__fixtures__' +} from '/app/redux/modules/__fixtures__' vi.mock('@opentrons/react-api-client') diff --git a/app/src/organisms/ModuleCard/__tests__/ModuleCard.test.tsx b/app/src/organisms/ModuleCard/__tests__/ModuleCard.test.tsx index 4dc583d6c9d..d30a885b759 100644 --- a/app/src/organisms/ModuleCard/__tests__/ModuleCard.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/ModuleCard.test.tsx @@ -1,33 +1,25 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { RUN_STATUS_IDLE, RUN_STATUS_RUNNING } from '@opentrons/api-client' -import { - nestedTextMatcher, - renderWithProviders, -} from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { getIsHeaterShakerAttached } from '../../../redux/config' +import { nestedTextMatcher, renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getIsHeaterShakerAttached } from '/app/redux/config' import { mockMagneticModule, mockTemperatureModuleGen2, mockThermocycler, mockHeaterShaker, -} from '../../../redux/modules/__fixtures__' -import { mockRobot } from '../../../redux/robot-api/__fixtures__' -import { useIsEstopNotDisengaged } from '../../../resources/devices/hooks/useIsEstopNotDisengaged' -import { - FAILURE, - getRequestById, - PENDING, - SUCCESS, -} from '../../../redux/robot-api' -import { useCurrentRunStatus } from '../../RunTimeControl/hooks' -import { useToaster } from '../../ToasterOven' -import { useIsFlex } from '../../Devices/hooks' +} from '/app/redux/modules/__fixtures__' +import { mockRobot } from '/app/redux/robot-api/__fixtures__' +import { useIsEstopNotDisengaged } from '/app/resources/devices' +import { FAILURE, getRequestById, PENDING, SUCCESS } from '/app/redux/robot-api' +import { useCurrentRunStatus } from '/app/organisms/RunTimeControl' +import { useToaster } from '/app/organisms/ToasterOven' +import { useIsFlex } from '/app/redux-resources/robots' import { MagneticModuleData } from '../MagneticModuleData' import { TemperatureModuleData } from '../TemperatureModuleData' import { ThermocyclerModuleData } from '../ThermocyclerModuleData' @@ -37,12 +29,11 @@ import { FirmwareUpdateFailedModal } from '../FirmwareUpdateFailedModal' import { ErrorInfo } from '../ErrorInfo' import { ModuleCard } from '..' -import type { NavigateFunction } from 'react-router-dom' import type { HeaterShakerModule, MagneticModule, ThermocyclerModule, -} from '../../../redux/modules/types' +} from '/app/redux/modules/types' import type { Mock } from 'vitest' vi.mock('../ErrorInfo') @@ -50,21 +41,14 @@ vi.mock('../MagneticModuleData') vi.mock('../TemperatureModuleData') vi.mock('../ThermocyclerModuleData') vi.mock('../HeaterShakerModuleData') -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') vi.mock('../ModuleOverflowMenu') -vi.mock('../../RunTimeControl/hooks') +vi.mock('/app/organisms/RunTimeControl') vi.mock('../FirmwareUpdateFailedModal') -vi.mock('../../../redux/robot-api') -vi.mock('../../../organisms/ToasterOven') -vi.mock('../../../organisms/Devices/hooks') -vi.mock('../../../resources/devices/hooks/useIsEstopNotDisengaged') -vi.mock('react-router-dom', async importOriginal => { - const actual = await importOriginal() - return { - ...actual, - useNavigate: () => vi.fn(), - } -}) +vi.mock('/app/redux/robot-api') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/organisms/ToasterOven') +vi.mock('/app/resources/devices') const mockMagneticModuleHub = { id: 'magdeck_id', @@ -237,9 +221,7 @@ describe('ModuleCard', () => { eatToast: mockEatToast, }) vi.mocked(getRequestById).mockReturnValue(null) - when(useCurrentRunStatus) - .calledWith(expect.any(Object)) - .thenReturn(RUN_STATUS_IDLE) + when(useCurrentRunStatus).calledWith().thenReturn(RUN_STATUS_IDLE) when(useIsFlex).calledWith(props.robotName).thenReturn(true) when(useIsEstopNotDisengaged).calledWith(props.robotName).thenReturn(false) }) @@ -311,9 +293,7 @@ describe('ModuleCard', () => { }) it('renders kebab icon and it is disabled when run is in progress', () => { - when(useCurrentRunStatus) - .calledWith(expect.any(Object)) - .thenReturn(RUN_STATUS_RUNNING) + when(useCurrentRunStatus).calledWith().thenReturn(RUN_STATUS_RUNNING) render({ ...props, module: mockMagneticModule, diff --git a/app/src/organisms/ModuleCard/__tests__/ModuleOverflowMenu.test.tsx b/app/src/organisms/ModuleCard/__tests__/ModuleOverflowMenu.test.tsx index e78b0a2d0e7..75701934e36 100644 --- a/app/src/organisms/ModuleCard/__tests__/ModuleOverflowMenu.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/ModuleOverflowMenu.test.tsx @@ -1,29 +1,26 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockMagneticModule, mockTemperatureModuleGen2, mockThermocycler, mockHeaterShaker, mockThermocyclerGen2, -} from '../../../redux/modules/__fixtures__' -import { - useRunStatuses, - useIsLegacySessionInProgress, - useIsFlex, -} from '../../Devices/hooks' -import { useCurrentRunId } from '../../../resources/runs' +} from '/app/redux/modules/__fixtures__' +import { useIsLegacySessionInProgress } from '/app/resources/legacy_sessions' +import { useIsFlex } from '/app/redux-resources/robots' +import { useCurrentRunId, useRunStatuses } from '/app/resources/runs' import { ModuleOverflowMenu } from '../ModuleOverflowMenu' import type { TemperatureStatus } from '@opentrons/api-client' -vi.mock('../../Devices/hooks') -vi.mock('../../RunTimeControl/hooks') -vi.mock('../../../resources/runs') +vi.mock('/app/resources/legacy_sessions') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/resources/runs') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/ModuleCard/__tests__/ModuleSetupModal.test.tsx b/app/src/organisms/ModuleCard/__tests__/ModuleSetupModal.test.tsx index f56b0a67535..87f340b2845 100644 --- a/app/src/organisms/ModuleCard/__tests__/ModuleSetupModal.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/ModuleSetupModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ModuleSetupModal } from '../ModuleSetupModal' const render = (props: React.ComponentProps) => { @@ -47,4 +47,23 @@ describe('ModuleSetupModal', () => { fireEvent.click(closeButton) expect(props.close).toHaveBeenCalled() }) + it('should render variable copy and link if absorbance reader', () => { + props = { + ...props, + isAbsorbanceReader: true, + } + render(props) + screen.getByText( + 'For step-by-step instructions on setting up your module, consult the Quickstart Guide that came in its box. You can also click the link below or scan the QR code to read the module Instruction Manual.' + ) + expect( + screen + .getByRole('link', { + name: 'mockModuleDisplayName setup instructions', + }) + .getAttribute('href') + ).toBe( + 'https://insights.opentrons.com/hubfs/Absorbance%20Plate%20Reader%20Instruction%20Manual.pdf' + ) + }) }) diff --git a/app/src/organisms/ModuleCard/__tests__/TemperatureModuleData.test.tsx b/app/src/organisms/ModuleCard/__tests__/TemperatureModuleData.test.tsx index 4ca6b89741d..5b851ce5796 100644 --- a/app/src/organisms/ModuleCard/__tests__/TemperatureModuleData.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/TemperatureModuleData.test.tsx @@ -1,14 +1,14 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { StatusLabel } from '../../../atoms/StatusLabel' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { StatusLabel } from '/app/atoms/StatusLabel' import { TemperatureModuleData } from '../TemperatureModuleData' -import { mockTemperatureModuleGen2 } from '../../../redux/modules/__fixtures__' +import { mockTemperatureModuleGen2 } from '/app/redux/modules/__fixtures__' -vi.mock('../../../atoms/StatusLabel') +vi.mock('/app/atoms/StatusLabel') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/ModuleCard/__tests__/TemperatureModuleSlideout.test.tsx b/app/src/organisms/ModuleCard/__tests__/TemperatureModuleSlideout.test.tsx index eb3336cefe5..ce65306741d 100644 --- a/app/src/organisms/ModuleCard/__tests__/TemperatureModuleSlideout.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/TemperatureModuleSlideout.test.tsx @@ -1,15 +1,15 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockTemperatureModule, mockTemperatureModuleGen2, -} from '../../../redux/modules/__fixtures__' +} from '/app/redux/modules/__fixtures__' import { TemperatureModuleSlideout } from '../TemperatureModuleSlideout' vi.mock('@opentrons/react-api-client') diff --git a/app/src/organisms/ModuleCard/__tests__/TestShakeSlideout.test.tsx b/app/src/organisms/ModuleCard/__tests__/TestShakeSlideout.test.tsx index 213d44259fa..f11816df8b6 100644 --- a/app/src/organisms/ModuleCard/__tests__/TestShakeSlideout.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/TestShakeSlideout.test.tsx @@ -1,18 +1,18 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen, waitFor } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { getIsHeaterShakerAttached } from '../../../redux/config' -import { mockHeaterShaker } from '../../../redux/modules/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getIsHeaterShakerAttached } from '/app/redux/config' +import { mockHeaterShaker } from '/app/redux/modules/__fixtures__' import { useLatchControls } from '../hooks' import { TestShakeSlideout } from '../TestShakeSlideout' import { ModuleSetupModal } from '../ModuleSetupModal' -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') vi.mock('@opentrons/react-api-client') vi.mock('../hooks') vi.mock('../ModuleSetupModal') diff --git a/app/src/organisms/ModuleCard/__tests__/ThermocyclerModuleData.test.tsx b/app/src/organisms/ModuleCard/__tests__/ThermocyclerModuleData.test.tsx index b0de531d6c8..0885c74bb5d 100644 --- a/app/src/organisms/ModuleCard/__tests__/ThermocyclerModuleData.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/ThermocyclerModuleData.test.tsx @@ -1,17 +1,17 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mockThermocycler, mockThermocyclerGen2, -} from '../../../redux/modules/__fixtures__' +} from '/app/redux/modules/__fixtures__' import { ThermocyclerModuleData } from '../ThermocyclerModuleData' -import type { ThermocyclerData } from '../../../redux/modules/api-types' +import type { ThermocyclerData } from '/app/redux/modules/api-types' const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/ModuleCard/__tests__/ThermocyclerModuleSlideout.test.tsx b/app/src/organisms/ModuleCard/__tests__/ThermocyclerModuleSlideout.test.tsx index 7840d68269f..d93a1b1f607 100644 --- a/app/src/organisms/ModuleCard/__tests__/ThermocyclerModuleSlideout.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/ThermocyclerModuleSlideout.test.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockThermocycler } from '../../../redux/modules/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockThermocycler } from '/app/redux/modules/__fixtures__' import { ThermocyclerModuleSlideout } from '../ThermocyclerModuleSlideout' vi.mock('@opentrons/react-api-client') diff --git a/app/src/organisms/ModuleCard/__tests__/hooks.test.tsx b/app/src/organisms/ModuleCard/__tests__/hooks.test.tsx index ffeb8eb0e84..ce0f0450179 100644 --- a/app/src/organisms/ModuleCard/__tests__/hooks.test.tsx +++ b/app/src/organisms/ModuleCard/__tests__/hooks.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { Provider } from 'react-redux' import { when } from 'vitest-when' import { createStore } from 'redux' @@ -9,17 +9,21 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' import { heater_shaker_commands_with_results_key } from '@opentrons/shared-data' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { mockHeaterShaker, mockMagneticModuleGen2, mockTemperatureModuleGen2, mockThermocycler, mockThermocyclerGen2, -} from '../../../redux/modules/__fixtures__' -import { useIsRobotBusy, useRunStatuses } from '../../Devices/hooks' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useCurrentRunId } from '../../../resources/runs' +} from '/app/redux/modules/__fixtures__' +import { useIsRobotBusy } from '/app/redux-resources/robots' + +import { + useCurrentRunId, + useMostRecentCompletedAnalysis, + useRunStatuses, +} from '/app/resources/runs' import { useLatchControls, useModuleOverflowMenu, @@ -27,12 +31,11 @@ import { } from '../hooks' import type { Store } from 'redux' -import type { State } from '../../../redux/types' +import type { State } from '/app/redux/types' vi.mock('@opentrons/react-api-client') -vi.mock('../../LabwarePositionCheck/useMostRecentCompletedAnalysis') -vi.mock('../../../resources/runs') -vi.mock('../../Devices/hooks') +vi.mock('/app/resources/runs') +vi.mock('/app/redux-resources/robots') const mockCloseLatchHeaterShaker = { id: 'heatershaker_id', diff --git a/app/src/organisms/ModuleCard/__tests__/utils.test.ts b/app/src/organisms/ModuleCard/__tests__/utils.test.ts index 5798efeb827..71ac9e50106 100644 --- a/app/src/organisms/ModuleCard/__tests__/utils.test.ts +++ b/app/src/organisms/ModuleCard/__tests__/utils.test.ts @@ -9,11 +9,11 @@ import { mockTemperatureModuleGen2, mockThermocycler, mockThermocyclerGen2, -} from '../../../redux/modules/__fixtures__' +} from '/app/redux/modules/__fixtures__' import { getModuleCardImage, useModuleApiRequests } from '../utils' -import { useDispatchApiRequest } from '../../../redux/robot-api' +import { useDispatchApiRequest } from '/app/redux/robot-api' -vi.mock('../../../redux/robot-api') +vi.mock('/app/redux/robot-api') const mockThermocyclerGen2ClosedLid = { id: 'thermocycler_id2', diff --git a/app/src/organisms/ModuleCard/hooks.tsx b/app/src/organisms/ModuleCard/hooks.tsx index 2a8ef8f1dc4..da44c64d983 100644 --- a/app/src/organisms/ModuleCard/hooks.tsx +++ b/app/src/organisms/ModuleCard/hooks.tsx @@ -1,15 +1,22 @@ -import * as React from 'react' import { useCreateLiveCommandMutation } from '@opentrons/react-api-client' import { useTranslation } from 'react-i18next' -import { MenuItem, Tooltip, useHoverTooltip } from '@opentrons/components' +import { + MenuItem, + NO_WRAP, + Tooltip, + useHoverTooltip, +} from '@opentrons/components' import { HEATERSHAKER_MODULE_TYPE, MAGNETIC_MODULE_TYPE, TEMPERATURE_MODULE_TYPE, THERMOCYCLER_MODULE_TYPE, } from '@opentrons/shared-data' -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useCurrentRunId } from '../../resources/runs' + +import { + useCurrentRunId, + useMostRecentCompletedAnalysis, +} from '/app/resources/runs' import type { HeaterShakerCloseLatchCreateCommand, @@ -24,7 +31,7 @@ import type { TemperatureModuleDeactivateCreateCommand, } from '@opentrons/shared-data' -import type { AttachedModule } from '../../redux/modules/types' +import type { AttachedModule } from '/app/redux/modules/types' export function useIsHeaterShakerInProtocol(): boolean { const currentRunId = useCurrentRunId() @@ -73,6 +80,7 @@ export function useLatchControls(module: AttachedModule): LatchControls { export type MenuItemsByModuleType = { [moduleType in AttachedModule['moduleType']]: Array<{ setSetting: string + isSettingDisabled: boolean isSecondary: boolean menuButtons: JSX.Element[] | null onClick: (isSecondary: boolean) => void @@ -153,7 +161,7 @@ export function useModuleOverflowMenu( onClick={() => { handleInstructionsClick() }} - whiteSpace="nowrap" + whiteSpace={NO_WRAP} > {t('heater_shaker:show_attachment_instructions')} @@ -244,7 +252,7 @@ export function useModuleOverflowMenu( key={`thermocycler_block_temp_command_btn_${String(module.moduleModel)}`} onClick={sendBlockTempCommand} disabled={isDisabled} - whiteSpace="nowrap" + whiteSpace={NO_WRAP} > {module.data.status !== 'idle' ? t('overflow_menu_deactivate_block') @@ -260,6 +268,7 @@ export function useModuleOverflowMenu( module.data.lidTargetTemperature != null ? t('overflow_menu_deactivate_lid') : t('overflow_menu_lid_temp'), + isSettingDisabled: isDisabled, isSecondary: true, menuButtons: null, onClick: @@ -278,6 +287,7 @@ export function useModuleOverflowMenu( module.data.lidStatus === 'open' ? t('close_lid') : t('open_lid'), + isSettingDisabled: isDisabled, isSecondary: false, menuButtons: [thermoSetBlockTempBtn, aboutModuleBtn], onClick: controlTCLid, @@ -291,6 +301,7 @@ export function useModuleOverflowMenu( ? t('overflow_menu_deactivate_temp') : t('overflow_menu_mod_temp'), isSecondary: false, + isSettingDisabled: isDisabled, menuButtons: [aboutModuleBtn], onClick: module.data.status !== 'idle' @@ -310,6 +321,7 @@ export function useModuleOverflowMenu( ? t('overflow_menu_disengage') : t('overflow_menu_engage'), isSecondary: false, + isSettingDisabled: isDisabled, menuButtons: [aboutModuleBtn], onClick: module.data.status !== 'disengaged' @@ -329,6 +341,7 @@ export function useModuleOverflowMenu( ? t('heater_shaker:deactivate_heater') : t('heater_shaker:set_temperature'), isSecondary: false, + isSettingDisabled: isDisabled, menuButtons: [ labwareLatchBtn, aboutModuleBtn, @@ -347,7 +360,15 @@ export function useModuleOverflowMenu( }, }, ], - absorbanceReaderType: [], + absorbanceReaderType: [ + { + setSetting: t('overflow_menu_about'), + isSecondary: false, + isSettingDisabled: false, + menuButtons: [], + onClick: handleAboutClick, + }, + ], } return { diff --git a/app/src/organisms/ModuleCard/index.tsx b/app/src/organisms/ModuleCard/index.tsx index 52b4b000374..7a4a34bf8ec 100644 --- a/app/src/organisms/ModuleCard/index.tsx +++ b/app/src/organisms/ModuleCard/index.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import { useState } from 'react' import { Trans, useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' -import { useNavigate } from 'react-router-dom' import { ALIGN_START, + Banner, BORDERS, Box, COLORS, @@ -41,14 +41,13 @@ import { getErrorResponseMessage, dismissRequest, SUCCESS, -} from '../../redux/robot-api' -import { Banner } from '../../atoms/Banner' -import { UpdateBanner } from '../../molecules/UpdateBanner' -import { useChainLiveCommands } from '../../resources/runs' -import { useCurrentRunStatus } from '../RunTimeControl/hooks' -import { useIsFlex } from '../../organisms/Devices/hooks' -import { getModuleTooHot } from '../Devices/getModuleTooHot' -import { useToaster } from '../ToasterOven' +} from '/app/redux/robot-api' +import { UpdateBanner } from '/app/molecules/UpdateBanner' +import { useChainLiveCommands } from '/app/resources/runs' +import { useCurrentRunStatus } from '/app/organisms/RunTimeControl' +import { useIsFlex } from '/app/redux-resources/robots' +import { getModuleTooHot } from '/app/transformations/modules' +import { useToaster } from '/app/organisms/ToasterOven' import { MagneticModuleData } from './MagneticModuleData' import { TemperatureModuleData } from './TemperatureModuleData' import { ThermocyclerModuleData } from './ThermocyclerModuleData' @@ -60,21 +59,21 @@ import { AboutModuleSlideout } from './AboutModuleSlideout' import { HeaterShakerModuleData } from './HeaterShakerModuleData' import { HeaterShakerSlideout } from './HeaterShakerSlideout' import { TestShakeSlideout } from './TestShakeSlideout' -import { ModuleWizardFlows } from '../ModuleWizardFlows' -import { getModulePrepCommands } from '../Devices/getModulePrepCommands' +import { ModuleWizardFlows } from '/app/organisms/ModuleWizardFlows' +import { getModulePrepCommands } from '/app/local-resources/modules' import { getModuleCardImage } from './utils' import { FirmwareUpdateFailedModal } from './FirmwareUpdateFailedModal' import { ErrorInfo } from './ErrorInfo' import { ModuleSetupModal } from './ModuleSetupModal' -import { useIsEstopNotDisengaged } from '../../resources/devices/hooks/useIsEstopNotDisengaged' +import { useIsEstopNotDisengaged } from '/app/resources/devices' import type { IconProps } from '@opentrons/components' import type { AttachedModule, HeaterShakerModule, -} from '../../redux/modules/types' -import type { State, Dispatch } from '../../redux/types' -import type { RequestState } from '../../redux/robot-api/types' +} from '/app/redux/modules/types' +import type { State, Dispatch } from '/app/redux/types' +import type { RequestState } from '/app/redux/robot-api/types' import { AbsorbanceReaderData } from './AbsorbanceReaderData' import { AbsorbanceReaderSlideout } from './AbsorbanceReaderSlideout' @@ -117,27 +116,22 @@ export const ModuleCard = (props: ModuleCardProps): JSX.Element | null => { setShowOverflowMenu(false) }, }) - const [showSlideout, setShowSlideout] = React.useState(false) - const [hasSecondary, setHasSecondary] = React.useState(false) - const [showAboutModule, setShowAboutModule] = React.useState(false) - const [showTestShake, setShowTestShake] = React.useState(false) - const [showHSWizard, setShowHSWizard] = React.useState(false) - const [showFWBanner, setShowFWBanner] = React.useState(true) - const [showCalModal, setShowCalModal] = React.useState(false) + const [showSlideout, setShowSlideout] = useState(false) + const [hasSecondary, setHasSecondary] = useState(false) + const [showAboutModule, setShowAboutModule] = useState(false) + const [showTestShake, setShowTestShake] = useState(false) + const [showHSWizard, setShowHSWizard] = useState(false) + const [showFWBanner, setShowFWBanner] = useState(true) + const [showCalModal, setShowCalModal] = useState(false) const [targetProps, tooltipProps] = useHoverTooltip() - const navigate = useNavigate() - const runStatus = useCurrentRunStatus({ - onSettled: data => { - if (data == null) { - navigate('/upload') - } - }, - }) + + const runStatus = useCurrentRunStatus() const isFlex = useIsFlex(robotName) const requireModuleCalibration = isFlex && !MODULE_MODELS_OT2_ONLY.some(modModel => modModel === module.moduleModel) && + module.moduleType !== ABSORBANCE_READER_TYPE && module.moduleOffset?.last_modified == null const isPipetteReady = !Boolean(attachPipetteRequired) && @@ -150,7 +144,7 @@ export const ModuleCard = (props: ModuleCardProps): JSX.Element | null => { const hasUpdated = !module.hasAvailableUpdate && latestRequest?.status === SUCCESS - const [showFirmwareToast, setShowFirmwareToast] = React.useState(hasUpdated) + const [showFirmwareToast, setShowFirmwareToast] = useState(hasUpdated) const { makeToast } = useToaster() if (showFirmwareToast) { makeToast(t('firmware_updated_successfully') as string, SUCCESS_TOAST) @@ -179,7 +173,7 @@ export const ModuleCard = (props: ModuleCardProps): JSX.Element | null => { let moduleData: JSX.Element =
    switch (module.moduleType) { - case 'magneticModuleType': { + case MAGNETIC_MODULE_TYPE: { moduleData = ( { break } - case 'temperatureModuleType': { + case TEMPERATURE_MODULE_TYPE: { moduleData = ( { break } - case 'thermocyclerModuleType': { + case THERMOCYCLER_MODULE_TYPE: { moduleData = break } - case 'heaterShakerModuleType': { + case HEATERSHAKER_MODULE_TYPE: { moduleData = ( { break } - case 'absorbanceReaderType': { + case ABSORBANCE_READER_TYPE: { moduleData = break } @@ -247,7 +241,7 @@ export const ModuleCard = (props: ModuleCardProps): JSX.Element | null => { const [ prepCommandErrorMessage, setPrepCommandErrorMessage, - ] = React.useState('') + ] = useState('') const handleCalibrateClick = (): void => { if (getModulePrepCommands(module).length > 0) { chainLiveCommands(getModulePrepCommands(module), false).catch( @@ -272,6 +266,7 @@ export const ModuleCard = (props: ModuleCardProps): JSX.Element | null => { closeFlow={() => { setShowCalModal(false) }} + isLoadedInRun={isLoadedInRun} isPrepCommandLoading={isCommandMutationLoading} prepCommandErrorMessage={ prepCommandErrorMessage === '' ? undefined : prepCommandErrorMessage diff --git a/app/src/organisms/ModuleCard/utils.ts b/app/src/organisms/ModuleCard/utils.ts index dfd136bfcfc..554ea60ef4b 100644 --- a/app/src/organisms/ModuleCard/utils.ts +++ b/app/src/organisms/ModuleCard/utils.ts @@ -1,18 +1,19 @@ -import * as React from 'react' +import { useState, useCallback } from 'react' import last from 'lodash/last' -import { useDispatchApiRequest } from '../../redux/robot-api' -import { updateModule } from '../../redux/modules' +import { useDispatchApiRequest } from '/app/redux/robot-api' +import { updateModule } from '/app/redux/modules' -import magneticModule from '../../assets/images/magnetic_module_gen_2_transparent.png' -import temperatureModule from '../../assets/images/temp_deck_gen_2_transparent.png' -import thermoModuleGen1Closed from '../../assets/images/thermocycler_closed.png' -import thermoModuleGen1Opened from '../../assets/images/thermocycler_open_transparent.png' -import heaterShakerModule from '../../assets/images/heater_shaker_module_transparent.png' -import thermoModuleGen2Closed from '../../assets/images/thermocycler_gen_2_closed.png' -import thermoModuleGen2Opened from '../../assets/images/thermocycler_gen_2_opened.png' +import magneticModule from '/app/assets/images/magnetic_module_gen_2_transparent.png' +import temperatureModule from '/app/assets/images/temp_deck_gen_2_transparent.png' +import thermoModuleGen1Closed from '/app/assets/images/thermocycler_closed.png' +import thermoModuleGen1Opened from '/app/assets/images/thermocycler_open_transparent.png' +import heaterShakerModule from '/app/assets/images/heater_shaker_module_transparent.png' +import thermoModuleGen2Closed from '/app/assets/images/thermocycler_gen_2_closed.png' +import thermoModuleGen2Opened from '/app/assets/images/thermocycler_gen_2_opened.png' +import absorbanceReader from '/app/assets/images/opentrons_plate_reader.png' -import type { AttachedModule } from '../../redux/modules/types' +import type { AttachedModule } from '/app/redux/modules/types' export function getModuleCardImage(attachedModule: AttachedModule): string { // TODO(jr, 9/22/22): add images for V1 of magneticModule and temperatureModule @@ -37,6 +38,8 @@ export function getModuleCardImage(attachedModule: AttachedModule): string { } else { return thermoModuleGen2Opened } + case 'absorbanceReaderV1': + return absorbanceReader // this should never be reached default: return 'unknown module model, this is an error' @@ -55,7 +58,7 @@ export function useModuleApiRequests(): [ const [ requestIdsBySerial, setRequestIdsBySerial, - ] = React.useState({}) + ] = useState({}) const handleModuleApiRequests = ( robotName: string, @@ -84,7 +87,7 @@ export function useModuleApiRequests(): [ } } - const getLatestRequestId = React.useCallback( + const getLatestRequestId = useCallback( (serialNumber: string): string | null => { if (serialNumber in requestIdsBySerial) { return last(requestIdsBySerial[serialNumber]) ?? null diff --git a/app/src/organisms/ModuleWizardFlows/AttachProbe.tsx b/app/src/organisms/ModuleWizardFlows/AttachProbe.tsx index 94af0fcdd40..fcd15766a54 100644 --- a/app/src/organisms/ModuleWizardFlows/AttachProbe.tsx +++ b/app/src/organisms/ModuleWizardFlows/AttachProbe.tsx @@ -1,18 +1,18 @@ -import * as React from 'react' import { css } from 'styled-components' import { Trans, useTranslation } from 'react-i18next' import { Flex, + Banner, RESPONSIVENESS, SPACING, LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' import { LEFT, WASTE_CHUTE_FIXTURES } from '@opentrons/shared-data' -import attachProbe1 from '../../assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_1.webm' -import attachProbe8 from '../../assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_8.webm' -import attachProbe96 from '../../assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_96.webm' -import { SimpleWizardInProgressBody } from '../../molecules/SimpleWizardBody' +import attachProbe1 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_1.webm' +import attachProbe8 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_8.webm' +import attachProbe96 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_96.webm' +import { SimpleWizardInProgressBody } from '/app/molecules/SimpleWizardBody' import type { CreateCommand, @@ -20,8 +20,7 @@ import type { CutoutId, CutoutFixtureId, } from '@opentrons/shared-data' -import { Banner } from '../../atoms/Banner' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' import type { ModuleCalibrationWizardStepProps } from './types' interface AttachProbeProps extends ModuleCalibrationWizardStepProps { diff --git a/app/src/organisms/ModuleWizardFlows/BeforeBeginning.tsx b/app/src/organisms/ModuleWizardFlows/BeforeBeginning.tsx index 3437ccd0c44..440d8880af6 100644 --- a/app/src/organisms/ModuleWizardFlows/BeforeBeginning.tsx +++ b/app/src/organisms/ModuleWizardFlows/BeforeBeginning.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import { @@ -8,8 +7,8 @@ import { getModuleDisplayName, } from '@opentrons/shared-data' import { LegacyStyledText } from '@opentrons/components' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' -import { WizardRequiredEquipmentList } from '../../molecules/WizardRequiredEquipmentList' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' +import { WizardRequiredEquipmentList } from '/app/molecules/WizardRequiredEquipmentList' import type { AttachedModule } from '@opentrons/api-client' import type { ModuleCalibrationWizardStepProps } from './types' diff --git a/app/src/organisms/ModuleWizardFlows/DetachProbe.tsx b/app/src/organisms/ModuleWizardFlows/DetachProbe.tsx index c95d515c4f8..a580d1af861 100644 --- a/app/src/organisms/ModuleWizardFlows/DetachProbe.tsx +++ b/app/src/organisms/ModuleWizardFlows/DetachProbe.tsx @@ -1,8 +1,7 @@ -import * as React from 'react' import { css } from 'styled-components' -import detachProbe1 from '../../assets/videos/pipette-wizard-flows/Pipette_Detach_Probe_1.webm' -import detachProbe8 from '../../assets/videos/pipette-wizard-flows/Pipette_Detach_Probe_8.webm' -import detachProbe96 from '../../assets/videos/pipette-wizard-flows/Pipette_Detach_Probe_96.webm' +import detachProbe1 from '/app/assets/videos/pipette-wizard-flows/Pipette_Detach_Probe_1.webm' +import detachProbe8 from '/app/assets/videos/pipette-wizard-flows/Pipette_Detach_Probe_8.webm' +import detachProbe96 from '/app/assets/videos/pipette-wizard-flows/Pipette_Detach_Probe_96.webm' import { useTranslation } from 'react-i18next' import { Flex, @@ -11,7 +10,7 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' import type { ModuleCalibrationWizardStepProps } from './types' diff --git a/app/src/organisms/ModuleWizardFlows/PlaceAdapter.tsx b/app/src/organisms/ModuleWizardFlows/PlaceAdapter.tsx index 7e044b1db46..f0af5ae9cd4 100644 --- a/app/src/organisms/ModuleWizardFlows/PlaceAdapter.tsx +++ b/app/src/organisms/ModuleWizardFlows/PlaceAdapter.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' +import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' import { v4 as uuidv4 } from 'uuid' -import HeaterShaker_PlaceAdapter_L from '../../assets/videos/module_wizard_flows/HeaterShaker_PlaceAdapter_L.webm' -import HeaterShaker_PlaceAdapter_R from '../../assets/videos/module_wizard_flows/HeaterShaker_PlaceAdapter_R.webm' -import TempModule_PlaceAdapter_L from '../../assets/videos/module_wizard_flows/TempModule_PlaceAdapter_L.webm' -import TempModule_PlaceAdapter_R from '../../assets/videos/module_wizard_flows/TempModule_PlaceAdapter_R.webm' -import Thermocycler_PlaceAdapter from '../../assets/videos/module_wizard_flows/Thermocycler_PlaceAdapter.webm' +import HeaterShaker_PlaceAdapter_L from '/app/assets/videos/module_wizard_flows/HeaterShaker_PlaceAdapter_L.webm' +import HeaterShaker_PlaceAdapter_R from '/app/assets/videos/module_wizard_flows/HeaterShaker_PlaceAdapter_R.webm' +import TempModule_PlaceAdapter_L from '/app/assets/videos/module_wizard_flows/TempModule_PlaceAdapter_L.webm' +import TempModule_PlaceAdapter_R from '/app/assets/videos/module_wizard_flows/TempModule_PlaceAdapter_R.webm' +import Thermocycler_PlaceAdapter from '/app/assets/videos/module_wizard_flows/Thermocycler_PlaceAdapter.webm' import { Flex, @@ -27,8 +27,8 @@ import { THERMOCYCLER_V2_FRONT_FIXTURE, } from '@opentrons/shared-data' -import { SimpleWizardInProgressBody } from '../../molecules/SimpleWizardBody' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' +import { SimpleWizardInProgressBody } from '/app/molecules/SimpleWizardBody' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' import { LEFT_SLOTS } from './constants' import type { DeckConfiguration, CreateCommand } from '@opentrons/shared-data' @@ -79,7 +79,7 @@ export const PlaceAdapter = (props: PlaceAdapterProps): JSX.Element | null => { createdMaintenanceRunId, } = props const { t } = useTranslation('module_wizard_flows') - React.useEffect(() => { + useEffect(() => { if (createdMaintenanceRunId == null) { createMaintenanceRun({}) } diff --git a/app/src/organisms/ModuleWizardFlows/SelectLocation.tsx b/app/src/organisms/ModuleWizardFlows/SelectLocation.tsx index 0b110f3bce4..0f993bbfe99 100644 --- a/app/src/organisms/ModuleWizardFlows/SelectLocation.tsx +++ b/app/src/organisms/ModuleWizardFlows/SelectLocation.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import isEqual from 'lodash/isEqual' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' @@ -17,6 +16,7 @@ import { SINGLE_SLOT_FIXTURES, } from '@opentrons/shared-data' import { + Banner, DeckConfigurator, RESPONSIVENESS, SIZE_1, @@ -24,8 +24,7 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { Banner } from '../../atoms/Banner' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' import type { ModuleCalibrationWizardStepProps } from './types' import type { CutoutConfig, @@ -47,6 +46,7 @@ interface SelectLocationProps extends ModuleCalibrationWizardStepProps { occupiedCutouts: CutoutConfig[] deckConfig: DeckConfiguration configuredFixtureIdByCutoutId: { [cutoutId in CutoutId]?: CutoutFixtureId } + isLoadedInRun: boolean } export const SelectLocation = ( props: SelectLocationProps @@ -56,6 +56,7 @@ export const SelectLocation = ( attachedModule, deckConfig, configuredFixtureIdByCutoutId, + isLoadedInRun, } = props const { t } = useTranslation('module_wizard_flows') const moduleName = getModuleDisplayName(attachedModule.moduleModel) @@ -93,6 +94,8 @@ export const SelectLocation = ( cutoutFixtureId ) && attachedModule.serialNumber === opentronsModuleSerialNumber if ( + // in run setup, module calibration only available when module location is already correctly configured + !isLoadedInRun && mayMountToCutoutIds.includes(cutoutId) && (isCurrentConfiguration || SINGLE_SLOT_FIXTURES.includes(cutoutFixtureId)) diff --git a/app/src/organisms/ModuleWizardFlows/Success.tsx b/app/src/organisms/ModuleWizardFlows/Success.tsx index 86f4f044fec..52a69bb06c0 100644 --- a/app/src/organisms/ModuleWizardFlows/Success.tsx +++ b/app/src/organisms/ModuleWizardFlows/Success.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' import { getModuleDisplayName } from '@opentrons/shared-data' @@ -9,8 +8,8 @@ import { RESPONSIVENESS, TYPOGRAPHY, } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' -import { SimpleWizardBody } from '../../molecules/SimpleWizardBody' +import { SmallButton } from '/app/atoms/buttons' +import { SimpleWizardBody } from '/app/molecules/SimpleWizardBody' import type { ModuleCalibrationWizardStepProps } from './types' export const BODY_STYLE = css` diff --git a/app/src/organisms/ModuleWizardFlows/index.tsx b/app/src/organisms/ModuleWizardFlows/index.tsx index 1b1fe62221a..b802cd6ca85 100644 --- a/app/src/organisms/ModuleWizardFlows/index.tsx +++ b/app/src/organisms/ModuleWizardFlows/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { createPortal } from 'react-dom' import { useSelector } from 'react-redux' import { Trans, useTranslation } from 'react-i18next' @@ -14,18 +14,15 @@ import { getDeckDefFromRobotType, FLEX_ROBOT_TYPE, } from '@opentrons/shared-data' -import { getTopPortalEl } from '../../App/portal' -import { WizardHeader } from '../../molecules/WizardHeader' -import { useAttachedPipettesFromInstrumentsQuery } from '../../organisms/Devices/hooks' -import { - useChainMaintenanceCommands, - useCreateTargetedMaintenanceRunMutation, -} from '../../resources/runs' -import { getIsOnDevice } from '../../redux/config' +import { getTopPortalEl } from '/app/App/portal' +import { WizardHeader } from '/app/molecules/WizardHeader' +import { useAttachedPipettesFromInstrumentsQuery } from '/app/resources/instruments' +import { useCreateTargetedMaintenanceRunMutation } from '/app/resources/runs' +import { getIsOnDevice } from '/app/redux/config' import { SimpleWizardBody, SimpleWizardInProgressBody, -} from '../../molecules/SimpleWizardBody' +} from '/app/molecules/SimpleWizardBody' import { getModuleCalibrationSteps } from './getModuleCalibrationSteps' import { FLEX_SLOT_NAMES_BY_MOD_TYPE, SECTIONS } from './constants' import { BeforeBeginning } from './BeforeBeginning' @@ -34,10 +31,15 @@ import { PlaceAdapter } from './PlaceAdapter' import { SelectLocation } from './SelectLocation' import { Success } from './Success' import { DetachProbe } from './DetachProbe' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' -import { useNotifyCurrentMaintenanceRun } from '../../resources/maintenance_runs' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' +import { + useChainMaintenanceCommands, + useNotifyCurrentMaintenanceRun, +} from '/app/resources/maintenance_runs' +import type { SetStateAction } from 'react' import type { AttachedModule, CommandData } from '@opentrons/api-client' +import { RUN_STATUS_FAILED } from '@opentrons/api-client' import type { CreateCommand, CutoutConfig, @@ -48,6 +50,7 @@ interface ModuleWizardFlowsProps { attachedModule: AttachedModule closeFlow: () => void isPrepCommandLoading: boolean + isLoadedInRun?: boolean onComplete?: () => void prepCommandErrorMessage?: string } @@ -59,6 +62,7 @@ export const ModuleWizardFlows = ( ): JSX.Element | null => { const { attachedModule, + isLoadedInRun = false, isPrepCommandLoading, closeFlow, onComplete, @@ -104,7 +108,7 @@ export const ModuleWizardFlows = ( ) ) ?? [] - const [currentStepIndex, setCurrentStepIndex] = React.useState(0) + const [currentStepIndex, setCurrentStepIndex] = useState(0) const totalStepCount = moduleCalibrationSteps.length - 1 const currentStep = moduleCalibrationSteps?.[currentStepIndex] @@ -113,18 +117,16 @@ export const ModuleWizardFlows = ( currentStepIndex === 0 ? currentStepIndex : currentStepIndex - 1 ) } - const [createdMaintenanceRunId, setCreatedMaintenanceRunId] = React.useState< + const [createdMaintenanceRunId, setCreatedMaintenanceRunId] = useState< string | null >(null) - const [createdAdapterId, setCreatedAdapterId] = React.useState( - null - ) + const [createdAdapterId, setCreatedAdapterId] = useState(null) // we should start checking for run deletion only after the maintenance run is created // and the useCurrentRun poll has returned that created id const [ monitorMaintenanceRunForDeletion, setMonitorMaintenanceRunForDeletion, - ] = React.useState(false) + ] = useState(false) const { data: maintenanceRunData } = useNotifyCurrentMaintenanceRun({ refetchInterval: RUN_REFETCH_INTERVAL, @@ -139,16 +141,14 @@ export const ModuleWizardFlows = ( createTargetedMaintenanceRun, isLoading: isCreateLoading, } = useCreateTargetedMaintenanceRunMutation({ - onSuccess: (response: { - data: { id: React.SetStateAction } - }) => { + onSuccess: (response: { data: { id: SetStateAction } }) => { setCreatedMaintenanceRunId(response.data.id) }, }) // this will close the modal in case the run was deleted by the terminate // activity modal on the ODD - React.useEffect(() => { + useEffect(() => { if ( createdMaintenanceRunId !== null && maintenanceRunData?.data.id === createdMaintenanceRunId @@ -168,8 +168,8 @@ export const ModuleWizardFlows = ( closeFlow, ]) - const [errorMessage, setErrorMessage] = React.useState(null) - const [isExiting, setIsExiting] = React.useState(false) + const [errorMessage, setErrorMessage] = useState(null) + const [isExiting, setIsExiting] = useState(false) const proceed = (): void => { if (!isCommandMutationLoading) { setCurrentStepIndex( @@ -213,9 +213,9 @@ export const ModuleWizardFlows = ( } } - const [isRobotMoving, setIsRobotMoving] = React.useState(false) + const [isRobotMoving, setIsRobotMoving] = useState(false) - React.useEffect(() => { + useEffect(() => { if (isCommandMutationLoading || isExiting) { setIsRobotMoving(true) } else { @@ -270,7 +270,11 @@ export const ModuleWizardFlows = ( })} /> ) - } else if (prepCommandErrorMessage != null || errorMessage != null) { + } else if ( + prepCommandErrorMessage != null || + errorMessage != null || + maintenanceRunData?.data.status === RUN_STATUS_FAILED + ) { modalContent = ( diff --git a/app/src/organisms/ModuleWizardFlows/types.ts b/app/src/organisms/ModuleWizardFlows/types.ts index 39fc6e6920e..64127dd125b 100644 --- a/app/src/organisms/ModuleWizardFlows/types.ts +++ b/app/src/organisms/ModuleWizardFlows/types.ts @@ -1,7 +1,7 @@ import type { AttachedModule } from '@opentrons/api-client' import type { FLOWS, SECTIONS } from './constants' import type { CreateCommand } from '@opentrons/shared-data' -import type { PipetteInformation } from '../Devices/hooks' +import type { PipetteInformation } from '/app/redux/pipettes' export type ModuleCalibrationWizardStep = | BeforeBeginningStep diff --git a/app/src/organisms/Navigation/index.tsx b/app/src/organisms/Navigation/index.tsx deleted file mode 100644 index a9a55f53e63..00000000000 --- a/app/src/organisms/Navigation/index.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import * as React from 'react' -import { useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import { NavLink } from 'react-router-dom' -import styled from 'styled-components' - -import { - Icon, - Flex, - Box, - COLORS, - SPACING, - DIRECTION_ROW, - ALIGN_CENTER, - JUSTIFY_SPACE_BETWEEN, - truncateString, - TYPOGRAPHY, - DIRECTION_COLUMN, - DISPLAY_FLEX, - ALIGN_FLEX_START, - POSITION_STICKY, - POSITION_ABSOLUTE, - POSITION_STATIC, - BORDERS, - RESPONSIVENESS, - OVERFLOW_SCROLL, -} from '@opentrons/components' -import { ODD_FOCUS_VISIBLE } from '../../atoms/buttons/constants' - -import { useNetworkConnection } from '../../resources/networking/hooks/useNetworkConnection' -import { getLocalRobot } from '../../redux/discovery' -import { NavigationMenu } from './NavigationMenu' -import type { ON_DEVICE_DISPLAY_PATHS } from '../../App/OnDeviceDisplayApp' - -const NAV_LINKS: Array = [ - '/protocols', - '/quick-transfer', - '/instruments', - '/robot-settings', -] - -// TODO(sb:7/10/24): update this wrapper to fade on both sides only when not scrolled completely to that side -// This will be accomplished in PLAT-399 -const CarouselWrapper = styled.div` - display: ${DISPLAY_FLEX}; - flex-direction: ${DIRECTION_ROW}; - align-items: ${ALIGN_FLEX_START}; - width: 42.25rem; - overflow-x: ${OVERFLOW_SCROLL}; - -webkit-mask-image: linear-gradient(90deg, #000 90%, transparent); - &::-webkit-scrollbar { - display: none; - } -` - -const CHAR_LIMIT_WITH_ICON = 12 -const CHAR_LIMIT_NO_ICON = 15 - -interface NavigationProps { - // optionalProps for setting the zIndex and position between multiple sticky elements - // used for ProtocolDashboard - setNavMenuIsOpened?: React.Dispatch> - longPressModalIsOpened?: boolean -} -export function Navigation(props: NavigationProps): JSX.Element { - const { setNavMenuIsOpened, longPressModalIsOpened } = props - const { t } = useTranslation('top_navigation') - const localRobot = useSelector(getLocalRobot) - const [showNavMenu, setShowNavMenu] = React.useState(false) - const robotName = localRobot?.name != null ? localRobot.name : 'no name' - - // We need to display an icon for what type of network connection (if any) - // is active next to the robot's name. The designs call for it to change color - // from black70 to black100 depending on the which page is being displayed - // but we are using ReactRouter NavLinks, which doesn't easily support complex - // children like this. For now the icon will just be black70 regardless. - // - // TODO(ew, 05/21/2023): Integrate icon into NavLink so color changes - const networkConnection = useNetworkConnection(robotName) - const { icon: iconName } = networkConnection - - const handleMenu = (openMenu: boolean): void => { - if (setNavMenuIsOpened != null) { - setNavMenuIsOpened(openMenu) - } - setShowNavMenu(openMenu) - } - - const scrollRef = React.useRef(null) - const [isScrolled, setIsScrolled] = React.useState(false) - - const observer = new IntersectionObserver(([entry]) => { - setIsScrolled(!entry.isIntersecting) - }) - if (scrollRef.current != null) { - observer.observe(scrollRef.current) - } - function getPathDisplayName(path: typeof NAV_LINKS[number]): string { - switch (path) { - case '/instruments': - return t('instruments') - case '/protocols': - return t('protocols') - case '/robot-settings': - return t('settings') - case '/quick-transfer': - return t('quick_transfer') - default: - return '' - } - } - - return ( - <> - {/* Empty box to detect scrolling */} - - - - - - {iconName != null ? ( - - ) : null} - - - - {NAV_LINKS.map(path => ( - - ))} - - - - - { - handleMenu(true) - }} - > - - - - - {showNavMenu && ( - { - handleMenu(false) - }} - robotName={robotName} - setShowNavMenu={setShowNavMenu} - /> - )} - - ) -} - -const NavigationLink = (props: { to: string; name: string }): JSX.Element => ( - - {props.name} - - -) - -const TouchNavLink = styled(NavLink)` - ${TYPOGRAPHY.level3HeaderSemiBold} - color: ${COLORS.grey50}; - height: 3.5rem; - display: flex; - flex-direction: ${DIRECTION_COLUMN}; - align-items: ${ALIGN_CENTER}; - white-space: nowrap; - &.active { - color: ${COLORS.black90}; - } - &.active > div { - background-color: ${COLORS.purple50}; - } - - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - cursor: default; - } -` - -const IconButton = styled('button')` - border-radius: ${BORDERS.borderRadius8}; - max-height: 100%; - background-color: ${COLORS.white}; - - &:hover { - background-color: ${COLORS.grey35}; - } - &:active { - background-color: ${COLORS.grey30}; - } - &:focus-visible { - box-shadow: ${ODD_FOCUS_VISIBLE}; - background-color: ${COLORS.grey35}; - } - &:disabled { - background-color: transparent; - } - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - cursor: default; - } -` diff --git a/app/src/organisms/NetworkSettings/SelectAuthenticationType.tsx b/app/src/organisms/NetworkSettings/SelectAuthenticationType.tsx deleted file mode 100644 index 39906414758..00000000000 --- a/app/src/organisms/NetworkSettings/SelectAuthenticationType.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import * as React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' - -import { - ALIGN_CENTER, - Btn, - COLORS, - DIRECTION_COLUMN, - DISPLAY_FLEX, - Flex, - JUSTIFY_CENTER, - SPACING, - LegacyStyledText, - TYPOGRAPHY, - RadioButton, -} from '@opentrons/components' - -import { getLocalRobot } from '../../redux/discovery' -import { getNetworkInterfaces, fetchStatus } from '../../redux/networking' -import { useIsUnboxingFlowOngoing } from '../RobotSettingsDashboard/NetworkSettings/hooks' -import { AlternativeSecurityTypeModal } from './AlternativeSecurityTypeModal' - -import type { WifiSecurityType } from '@opentrons/api-client' -import type { Dispatch, State } from '../../redux/types' - -interface SelectAuthenticationTypeProps { - selectedAuthType: WifiSecurityType - setSelectedAuthType: (authType: WifiSecurityType) => void -} - -export function SelectAuthenticationType({ - selectedAuthType, - setSelectedAuthType, -}: SelectAuthenticationTypeProps): JSX.Element { - const { t } = useTranslation(['device_settings', 'shared']) - const dispatch = useDispatch() - const localRobot = useSelector(getLocalRobot) - const robotName = localRobot?.name != null ? localRobot.name : 'no name' - const isUnboxingFlowOngoing = useIsUnboxingFlowOngoing() - const { wifi } = useSelector((state: State) => - getNetworkInterfaces(state, robotName) - ) - const [ - showAlternativeSecurityTypeModal, - setShowAlternativeSecurityTypeModal, - ] = React.useState(false) - - const securityButtons = [ - { - label: t('wpa2_personal'), - subLabel: t('wpa2_personal_description'), - value: 'wpa-psk', - }, - { - label: t('shared:none'), - subLabel: t('none_description'), - value: 'none', - }, - ] - - const handleChange = (event: React.ChangeEvent): void => { - setSelectedAuthType(event.target.value as WifiSecurityType) - } - - React.useEffect(() => { - dispatch(fetchStatus(robotName)) - }, [robotName, dispatch]) - - return ( - <> - {showAlternativeSecurityTypeModal ? ( - - ) : null} - - - - {securityButtons.map(radio => ( - - ))} - - - - {t('your_mac_address_is', { macAddress: wifi?.macAddress })} - - - { - setShowAlternativeSecurityTypeModal(true) - }} - padding={`${SPACING.spacing16} ${SPACING.spacing24}`} - > - - {t('need_another_security_type')} - - - - - - ) -} diff --git a/app/src/organisms/NetworkSettings/SetWifiCred.tsx b/app/src/organisms/NetworkSettings/SetWifiCred.tsx deleted file mode 100644 index aa0bc30f717..00000000000 --- a/app/src/organisms/NetworkSettings/SetWifiCred.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' - -import { - ALIGN_CENTER, - Box, - Btn, - DIRECTION_COLUMN, - DIRECTION_ROW, - DISPLAY_FLEX, - Flex, - Icon, - InputField, - JUSTIFY_SPACE_BETWEEN, - LegacyStyledText, - POSITION_FIXED, - SPACING, - TYPOGRAPHY, -} from '@opentrons/components' - -import { FullKeyboard } from '../../atoms/SoftwareKeyboard' -import { useIsUnboxingFlowOngoing } from '../RobotSettingsDashboard/NetworkSettings/hooks' - -interface SetWifiCredProps { - password: string - setPassword: (password: string) => void -} - -export function SetWifiCred({ - password, - setPassword, -}: SetWifiCredProps): JSX.Element { - const { t } = useTranslation(['device_settings', 'shared']) - const keyboardRef = React.useRef(null) - const [showPassword, setShowPassword] = React.useState(false) - const inputRef = React.useRef(null) - const isUnboxingFlowOngoing = useIsUnboxingFlowOngoing() - const MemoizedInput = React.memo(InputField) - const handleBlur = (): void => { - if (inputRef.current != null) inputRef.current?.focus() - } - - React.useEffect(() => { - if (inputRef.current != null) { - inputRef.current.focus() - } - }, [password]) - - return ( - <> - - - {t('enter_password')} - - - - - { - setShowPassword(currentState => !currentState) - inputRef?.current?.focus() - }} - display={DISPLAY_FLEX} - flexDirection={DIRECTION_ROW} - alignItems={ALIGN_CENTER} - gridGap={SPACING.spacing12} - width="7.375rem" - > - - - {showPassword ? t('hide') : t('show')} - - - - - - - { - e != null && setPassword(String(e)) - inputRef?.current?.focus() - }} - keyboardRef={keyboardRef} - /> - - - ) -} diff --git a/app/src/organisms/NetworkSettings/WifiConnectionDetails.tsx b/app/src/organisms/NetworkSettings/WifiConnectionDetails.tsx deleted file mode 100644 index ec95615c4e5..00000000000 --- a/app/src/organisms/NetworkSettings/WifiConnectionDetails.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useDispatch, useSelector } from 'react-redux' -import { useNavigate } from 'react-router-dom' - -import { - ALIGN_CENTER, - BORDERS, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - Icon, - JUSTIFY_CENTER, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' - -import { MediumButton } from '../../atoms/buttons' -import { RobotSetupHeader } from '../../organisms/RobotSetupHeader' -import { getLocalRobot } from '../../redux/discovery' -import { getNetworkInterfaces, fetchStatus } from '../../redux/networking' -import { NetworkDetailsModal } from '../RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal' - -import type { WifiSecurityType } from '@opentrons/api-client' -import type { Dispatch, State } from '../../redux/types' - -interface WifiConnectionDetailsProps { - ssid?: string - authType?: WifiSecurityType - showHeader?: boolean -} - -export function WifiConnectionDetails({ - ssid, - authType, -}: WifiConnectionDetailsProps): JSX.Element { - const { i18n, t } = useTranslation(['device_settings', 'shared']) - const navigate = useNavigate() - const localRobot = useSelector(getLocalRobot) - const robotName = localRobot?.name != null ? localRobot.name : 'no name' - const dispatch = useDispatch() - const { wifi } = useSelector((state: State) => - getNetworkInterfaces(state, robotName) - ) - - const noData = i18n.format(t('shared:no_data'), 'titleCase') - const ipAddress = wifi?.ipAddress != null ? wifi.ipAddress : noData - const subnetMask = wifi?.subnetMask != null ? wifi.subnetMask : noData - const macAddress = wifi?.macAddress != null ? wifi.macAddress : noData - - const [ - showNetworkDetailsModal, - setShowNetworkDetailsModal, - ] = React.useState(false) - - React.useEffect(() => { - dispatch(fetchStatus(robotName)) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) - - return ( - <> - {showNetworkDetailsModal ? ( - - ) : null} - - - - - - { - setShowNetworkDetailsModal(true) - }} - /> - { - navigate('/robot-settings/update-robot-during-onboarding') - }} - /> - - - - - ) -} - -interface DisplayConnectionStatusProps { - ssid?: string -} - -const DisplayConnectionStatus = ({ - ssid, -}: DisplayConnectionStatusProps): JSX.Element => { - const { t } = useTranslation('device_settings') - return ( - - - - {t('successfully_connected_to_network', { ssid })} - - - ) -} diff --git a/app/src/organisms/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx b/app/src/organisms/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx deleted file mode 100644 index 3c5427d3426..00000000000 --- a/app/src/organisms/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import * as React from 'react' -import { MemoryRouter } from 'react-router-dom' -import { fireEvent, screen } from '@testing-library/react' -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useWifiList } from '../../../resources/networking/hooks' -import { getNetworkInterfaces, INTERFACE_WIFI } from '../../../redux/networking' -import * as Fixtures from '../../../redux/networking/__fixtures__' -import { NetworkDetailsModal } from '../../RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal' -import { WifiConnectionDetails } from '../WifiConnectionDetails' - -import type { NavigateFunction } from 'react-router-dom' - -vi.mock('../../../resources/networking/hooks') -vi.mock('../../../redux/networking') -vi.mock('../../../redux/discovery/selectors') -vi.mock('../../RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal') - -const mockNavigate = vi.fn() -vi.mock('react-router-dom', async importOriginal => { - const actual = await importOriginal() - return { - ...actual, - useNavigate: () => mockNavigate, - } -}) - -const render = (props: React.ComponentProps) => { - return renderWithProviders( - - - , - { - i18nInstance: i18n, - } - ) -} - -const initialMockWifi = { - ipAddress: '127.0.0.100', - subnetMask: '255.255.255.230', - macAddress: 'WI:FI:00:00:00:00', - type: INTERFACE_WIFI, -} - -const mockWifiList = [ - { ...Fixtures.mockWifiNetwork, ssid: 'foo', active: true }, - { ...Fixtures.mockWifiNetwork, ssid: 'bar', active: false }, -] - -describe('WifiConnectionDetails', () => { - let props: React.ComponentProps - beforeEach(() => { - props = { - ssid: 'mockWifi', - authType: 'wpa-psk', - } - vi.mocked(getNetworkInterfaces).mockReturnValue({ - wifi: initialMockWifi, - ethernet: null, - }) - vi.mocked(useWifiList).mockReturnValue(mockWifiList) - vi.mocked(NetworkDetailsModal).mockReturnValue( -
    mock NetworkDetailsModal
    - ) - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - it('should render title and description', () => { - render(props) - screen.getByText('Wi-Fi') - screen.getByText('Successfully connected to mockWifi!') - screen.getByText('View network details') - screen.getByText('Continue') - }) - - it('should render network details when tapping view network details', () => { - render(props) - fireEvent.click(screen.getByText('View network details')) - screen.getByText('mock NetworkDetailsModal') - }) - - it('when clicking Check for updates button, should call mock function', () => { - render(props) - fireEvent.click(screen.getByText('Continue')) - expect(mockNavigate).toHaveBeenCalledWith( - '/robot-settings/update-robot-during-onboarding' - ) - }) -}) diff --git a/app/src/organisms/ChildNavigation/ChildNavigation.stories.tsx b/app/src/organisms/ODD/ChildNavigation/ChildNavigation.stories.tsx similarity index 97% rename from app/src/organisms/ChildNavigation/ChildNavigation.stories.tsx rename to app/src/organisms/ODD/ChildNavigation/ChildNavigation.stories.tsx index ce1e4af098e..c904b538041 100644 --- a/app/src/organisms/ChildNavigation/ChildNavigation.stories.tsx +++ b/app/src/organisms/ODD/ChildNavigation/ChildNavigation.stories.tsx @@ -2,7 +2,7 @@ import { VIEWPORT } from '@opentrons/components' import { ChildNavigation as ChildNavigationComponent } from '.' import type { Meta, StoryObj } from '@storybook/react' -import type { SmallButton } from '../../atoms/buttons' +import type { SmallButton } from '/app/atoms/buttons' const meta: Meta = { title: 'ODD/Organisms/ChildNavigation', diff --git a/app/src/organisms/ChildNavigation/__tests__/ChildNavigation.test.tsx b/app/src/organisms/ODD/ChildNavigation/__tests__/ChildNavigation.test.tsx similarity index 94% rename from app/src/organisms/ChildNavigation/__tests__/ChildNavigation.test.tsx rename to app/src/organisms/ODD/ChildNavigation/__tests__/ChildNavigation.test.tsx index b3ea0914d8f..82a0dfb0b3c 100644 --- a/app/src/organisms/ChildNavigation/__tests__/ChildNavigation.test.tsx +++ b/app/src/organisms/ODD/ChildNavigation/__tests__/ChildNavigation.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { ChildNavigation } from '..' -import type { SmallButton } from '../../../atoms/buttons' +import type { SmallButton } from '/app/atoms/buttons' const render = (props: React.ComponentProps) => renderWithProviders() diff --git a/app/src/organisms/ODD/ChildNavigation/index.tsx b/app/src/organisms/ODD/ChildNavigation/index.tsx new file mode 100644 index 00000000000..ea6c72f293b --- /dev/null +++ b/app/src/organisms/ODD/ChildNavigation/index.tsx @@ -0,0 +1,134 @@ +import type * as React from 'react' +import styled from 'styled-components' + +import { + ALIGN_CENTER, + COLORS, + DIRECTION_ROW, + Flex, + Icon, + JUSTIFY_FLEX_START, + JUSTIFY_SPACE_BETWEEN, + POSITION_FIXED, + RESPONSIVENESS, + SPACING, + LegacyStyledText, + TYPOGRAPHY, + CURSOR_DEFAULT, +} from '@opentrons/components' +import { ODD_FOCUS_VISIBLE } from '/app/atoms/buttons/constants' + +import { SmallButton } from '/app/atoms/buttons' +import { InlineNotification } from '/app/atoms/InlineNotification' + +import type { IconName, StyleProps } from '@opentrons/components' +import type { InlineNotificationProps } from '/app/atoms/InlineNotification' +import type { + IconPlacement, + SmallButtonTypes, +} from '/app/atoms/buttons/SmallButton' + +interface ChildNavigationProps extends StyleProps { + header: string + onClickBack?: React.MouseEventHandler + buttonText?: React.ReactNode + inlineNotification?: InlineNotificationProps + onClickButton?: React.MouseEventHandler + buttonType?: SmallButtonTypes + buttonIsDisabled?: boolean + iconName?: IconName + iconPlacement?: IconPlacement + secondaryButtonProps?: React.ComponentProps + ariaDisabled?: boolean +} + +export function ChildNavigation({ + buttonText, + header, + inlineNotification, + onClickBack, + onClickButton, + buttonType = 'primary', + iconName, + iconPlacement, + secondaryButtonProps, + buttonIsDisabled, + ariaDisabled = false, + ...styleProps +}: ChildNavigationProps): JSX.Element { + return ( + + + {onClickBack != null ? ( + + + + ) : null} + + {header} + + + {onClickButton != null && buttonText != null ? ( + + {secondaryButtonProps != null ? ( + + ) : null} + + + + ) : null} + {inlineNotification != null ? ( + + ) : null} + + ) +} + +const IconButton = styled('button')` + border-radius: ${SPACING.spacing4}; + max-height: 100%; + background-color: ${COLORS.white}; + + &:focus-visible { + box-shadow: ${ODD_FOCUS_VISIBLE}; + background-color: ${COLORS.grey35}; + } + &:disabled { + background-color: transparent; + } + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + cursor: ${CURSOR_DEFAULT}; + } +` diff --git a/app/src/organisms/InstrumentInfo/__tests__/InstrumentInfo.test.tsx b/app/src/organisms/ODD/InstrumentInfo/__tests__/InstrumentInfo.test.tsx similarity index 88% rename from app/src/organisms/InstrumentInfo/__tests__/InstrumentInfo.test.tsx rename to app/src/organisms/ODD/InstrumentInfo/__tests__/InstrumentInfo.test.tsx index adccba35529..a478716483d 100644 --- a/app/src/organisms/InstrumentInfo/__tests__/InstrumentInfo.test.tsx +++ b/app/src/organisms/ODD/InstrumentInfo/__tests__/InstrumentInfo.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockPipetteData1Channel } from '../../../redux/pipettes/__fixtures__' -import { PipetteWizardFlows } from '../../PipetteWizardFlows' -import { GripperWizardFlows } from '../../GripperWizardFlows' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockPipetteData1Channel } from '/app/redux/pipettes/__fixtures__' +import { PipetteWizardFlows } from '/app/organisms/PipetteWizardFlows' +import { GripperWizardFlows } from '/app/organisms/GripperWizardFlows' import { InstrumentInfo } from '..' import type { GripperData } from '@opentrons/api-client' @@ -13,8 +13,8 @@ import type * as Dom from 'react-router-dom' const mockNavigate = vi.fn() -vi.mock('../../PipetteWizardFlows') -vi.mock('../../GripperWizardFlows') +vi.mock('/app/organisms/PipetteWizardFlows') +vi.mock('/app/organisms/GripperWizardFlows') vi.mock('react-router-dom', async importOriginal => { const reactRouterDom = await importOriginal() return { diff --git a/app/src/organisms/ODD/InstrumentInfo/index.tsx b/app/src/organisms/ODD/InstrumentInfo/index.tsx new file mode 100644 index 00000000000..ecfb9456b05 --- /dev/null +++ b/app/src/organisms/ODD/InstrumentInfo/index.tsx @@ -0,0 +1,213 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' +import { + BORDERS, + COLORS, + DIRECTION_COLUMN, + Flex, + JUSTIFY_CENTER, + JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' +import { + SINGLE_MOUNT_PIPETTES, + NINETY_SIX_CHANNEL, +} from '@opentrons/shared-data' +import { PipetteWizardFlows } from '/app/organisms/PipetteWizardFlows' +import { GripperWizardFlows } from '/app/organisms/GripperWizardFlows' +import { MediumButton } from '/app/atoms/buttons' +import { FLOWS } from '/app/organisms/PipetteWizardFlows/constants' +import { GRIPPER_FLOW_TYPES } from '/app/organisms/GripperWizardFlows/constants' +import { formatTimeWithUtcLabel } from '/app/resources/runs' + +import type { ComponentProps, MouseEventHandler } from 'react' +import type { InstrumentData } from '@opentrons/api-client' +import type { PipetteMount } from '@opentrons/shared-data' +import type { StyleProps } from '@opentrons/components' +interface InstrumentInfoProps { + // NOTE: instrument will only be null while + // in the middle of detach wizard which occludes + // the main empty contents of this page + instrument: InstrumentData | null +} +export const InstrumentInfo = (props: InstrumentInfoProps): JSX.Element => { + const { t, i18n } = useTranslation('instruments_dashboard') + const { instrument } = props + const navigate = useNavigate() + const [wizardProps, setWizardProps] = useState< + | ComponentProps + | ComponentProps + | null + >(null) + + const sharedGripperWizardProps: Pick< + ComponentProps, + 'attachedGripper' | 'closeFlow' + > = { + attachedGripper: instrument, + closeFlow: () => { + setWizardProps(null) + }, + } + + const is96Channel = + instrument != null && + instrument.ok && + instrument.mount !== 'extension' && + instrument.data?.channels === 96 + + const handleDetach: MouseEventHandler = () => { + if (instrument != null && instrument.ok) { + setWizardProps( + instrument.mount === 'extension' + ? { + ...sharedGripperWizardProps, + flowType: GRIPPER_FLOW_TYPES.DETACH, + onComplete: () => { + navigate(-1) + }, + } + : { + closeFlow: () => { + setWizardProps(null) + }, + onComplete: () => { + navigate(-1) + }, + mount: instrument.mount as PipetteMount, + selectedPipette: is96Channel + ? NINETY_SIX_CHANNEL + : SINGLE_MOUNT_PIPETTES, + flowType: FLOWS.DETACH, + } + ) + } + } + const handleRecalibrate: MouseEventHandler = () => { + if (instrument != null && instrument.ok) { + setWizardProps( + instrument.mount === 'extension' + ? { + ...sharedGripperWizardProps, + flowType: GRIPPER_FLOW_TYPES.RECALIBRATE, + } + : { + closeFlow: () => { + setWizardProps(null) + }, + mount: instrument.mount as PipetteMount, + selectedPipette: is96Channel + ? NINETY_SIX_CHANNEL + : SINGLE_MOUNT_PIPETTES, + flowType: FLOWS.CALIBRATE, + } + ) + } + } + + return ( + + {instrument != null && instrument.ok ? ( + <> + + + {instrument.firmwareVersion != null && ( + + )} + + + + + {instrument.mount === 'extension' || + instrument.data.calibratedOffset?.last_modified == null ? ( + + ) : null} + + + ) : null} + {wizardProps != null && 'mount' in wizardProps ? ( + + ) : null} + {wizardProps != null && !('mount' in wizardProps) ? ( + + ) : null} + + ) +} + +interface InfoItemProps extends StyleProps { + label: string + value: string +} +function InfoItem(props: InfoItemProps): JSX.Element { + return ( + + + {props.label} + + + {props.value} + + + ) +} diff --git a/app/src/organisms/ODD/InstrumentMountItem/AttachedInstrumentMountItem.tsx b/app/src/organisms/ODD/InstrumentMountItem/AttachedInstrumentMountItem.tsx new file mode 100644 index 00000000000..7af7dd4029a --- /dev/null +++ b/app/src/organisms/ODD/InstrumentMountItem/AttachedInstrumentMountItem.tsx @@ -0,0 +1,120 @@ +import { useState } from 'react' +import { useNavigate } from 'react-router-dom' + +import { SINGLE_MOUNT_PIPETTES } from '@opentrons/shared-data' + +import { + useGripperDisplayName, + usePipetteModelSpecs, +} from '/app/local-resources/instruments' +import { ChoosePipette } from '/app/organisms/PipetteWizardFlows/ChoosePipette' +import { FLOWS } from '/app/organisms/PipetteWizardFlows/constants' +import { GRIPPER_FLOW_TYPES } from '/app/organisms/GripperWizardFlows/constants' +import { LabeledMount } from './LabeledMount' + +import type { ComponentProps, MouseEventHandler } from 'react' +import type { InstrumentData } from '@opentrons/api-client' +import type { GripperModel, PipetteModel } from '@opentrons/shared-data' +import type { Mount } from '/app/redux/pipettes/types' +import type { SelectablePipettes } from '/app/organisms/PipetteWizardFlows/types' +import type { GripperWizardFlows } from '/app/organisms/GripperWizardFlows' +import type { PipetteWizardFlows } from '/app/organisms/PipetteWizardFlows' + +interface AttachedInstrumentMountItemProps { + mount: Mount | 'extension' + attachedInstrument: InstrumentData | null + setWizardProps: ( + props: + | ComponentProps + | ComponentProps + | null + ) => void +} + +export function AttachedInstrumentMountItem( + props: AttachedInstrumentMountItemProps +): JSX.Element { + const navigate = useNavigate() + const { mount, attachedInstrument, setWizardProps } = props + + const [showChoosePipetteModal, setShowChoosePipetteModal] = useState(false) + const [selectedPipette, setSelectedPipette] = useState( + SINGLE_MOUNT_PIPETTES + ) + + const handleClick: MouseEventHandler = () => { + if (attachedInstrument == null && mount !== 'extension') { + setShowChoosePipetteModal(true) + } else if (attachedInstrument == null && mount === 'extension') { + setWizardProps({ + flowType: GRIPPER_FLOW_TYPES.ATTACH, + attachedGripper: attachedInstrument, + onComplete: () => { + navigate( + attachedInstrument == null ? '/instruments' : `/instrument/${mount}` + ) + }, + closeFlow: () => { + setWizardProps(null) + }, + }) + } else { + navigate(`/instruments/${mount}`) + } + } + + const instrumentModel = attachedInstrument?.ok + ? attachedInstrument.instrumentModel + : null + + const pipetteDisplayName = + usePipetteModelSpecs(instrumentModel as PipetteModel)?.displayName ?? null + const gripperDisplayName = useGripperDisplayName( + instrumentModel as GripperModel + ) + + const displayName = + attachedInstrument?.ok && attachedInstrument?.mount === 'extension' + ? gripperDisplayName + : pipetteDisplayName + + return ( + <> + + {showChoosePipetteModal ? ( + { + setWizardProps({ + mount: mount as Mount, + flowType: FLOWS.ATTACH, + selectedPipette, + closeFlow: () => { + setWizardProps(null) + setSelectedPipette(SINGLE_MOUNT_PIPETTES) + setShowChoosePipetteModal(false) + }, + onComplete: () => { + navigate( + attachedInstrument == null + ? `/instruments` + : `/instrument/${mount}` + ) + }, + }) + setShowChoosePipetteModal(false) + }} + setSelectedPipette={setSelectedPipette} + selectedPipette={selectedPipette} + exit={() => { + setShowChoosePipetteModal(false) + }} + mount={mount as Mount} + /> + ) : null} + + ) +} diff --git a/app/src/organisms/InstrumentMountItem/LabeledMount.tsx b/app/src/organisms/ODD/InstrumentMountItem/LabeledMount.tsx similarity index 96% rename from app/src/organisms/InstrumentMountItem/LabeledMount.tsx rename to app/src/organisms/ODD/InstrumentMountItem/LabeledMount.tsx index 515d0ab0c54..20fe3941604 100644 --- a/app/src/organisms/InstrumentMountItem/LabeledMount.tsx +++ b/app/src/organisms/ODD/InstrumentMountItem/LabeledMount.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' import { @@ -15,7 +15,7 @@ import { TEXT_TRANSFORM_CAPITALIZE, TYPOGRAPHY, } from '@opentrons/components' -import type { Mount } from '../../redux/pipettes/types' +import type { Mount } from '/app/redux/pipettes/types' const MountButton = styled.button<{ isAttached: boolean }>` display: flex; diff --git a/app/src/organisms/InstrumentMountItem/ProtocolInstrumentMountItem.tsx b/app/src/organisms/ODD/InstrumentMountItem/ProtocolInstrumentMountItem.tsx similarity index 87% rename from app/src/organisms/InstrumentMountItem/ProtocolInstrumentMountItem.tsx rename to app/src/organisms/ODD/InstrumentMountItem/ProtocolInstrumentMountItem.tsx index f96b09ef8b4..86ae69e4107 100644 --- a/app/src/organisms/InstrumentMountItem/ProtocolInstrumentMountItem.tsx +++ b/app/src/organisms/ODD/InstrumentMountItem/ProtocolInstrumentMountItem.tsx @@ -1,39 +1,40 @@ -import * as React from 'react' +import { useState, useMemo } from 'react' import styled from 'styled-components' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, - Flex, - COLORS, - SPACING, - TYPOGRAPHY, - Icon, - DIRECTION_COLUMN, ALIGN_FLEX_START, BORDERS, + COLORS, + DIRECTION_COLUMN, + Flex, + Icon, JUSTIFY_FLEX_START, + SPACING, + TYPOGRAPHY, } from '@opentrons/components' import { NINETY_SIX_CHANNEL, SINGLE_MOUNT_PIPETTES, } from '@opentrons/shared-data' -import { SmallButton } from '../../atoms/buttons' +import { SmallButton } from '/app/atoms/buttons' import { useGripperDisplayName, usePipetteNameSpecs, -} from '../../resources/instruments/hooks' -import { FLOWS } from '../PipetteWizardFlows/constants' -import { PipetteWizardFlows } from '../PipetteWizardFlows' -import { GripperWizardFlows } from '../GripperWizardFlows' +} from '/app/local-resources/instruments' +import { FLOWS } from '/app/organisms/PipetteWizardFlows/constants' +import { PipetteWizardFlows } from '/app/organisms/PipetteWizardFlows' +import { GripperWizardFlows } from '/app/organisms/GripperWizardFlows' +import type { MouseEventHandler } from 'react' import type { InstrumentData } from '@opentrons/api-client' import type { GripperModel, PipetteName, LoadedPipette, } from '@opentrons/shared-data' -import type { Mount } from '../../redux/pipettes/types' +import type { Mount } from '/app/redux/pipettes/types' export const MountItem = styled.div<{ isReady: boolean }>` display: flex; @@ -61,26 +62,24 @@ export function ProtocolInstrumentMountItem( ): JSX.Element { const { i18n, t } = useTranslation('protocol_setup') const { mount, attachedInstrument, speccedName } = props - const [ - showPipetteWizardFlow, - setShowPipetteWizardFlow, - ] = React.useState(false) - const [ - showGripperWizardFlow, - setShowGripperWizardFlow, - ] = React.useState(false) - const memoizedAttachedGripper = React.useMemo( + const [showPipetteWizardFlow, setShowPipetteWizardFlow] = useState( + false + ) + const [showGripperWizardFlow, setShowGripperWizardFlow] = useState( + false + ) + const memoizedAttachedGripper = useMemo( () => attachedInstrument?.instrumentType === 'gripper' && attachedInstrument.ok ? attachedInstrument : null, [] ) - const [flowType, setFlowType] = React.useState(FLOWS.ATTACH) + const [flowType, setFlowType] = useState(FLOWS.ATTACH) const selectedPipette = speccedName === 'p1000_96' ? NINETY_SIX_CHANNEL : SINGLE_MOUNT_PIPETTES - const handleCalibrate: React.MouseEventHandler = () => { + const handleCalibrate: MouseEventHandler = () => { setFlowType(FLOWS.CALIBRATE) if (mount === 'extension') { setShowGripperWizardFlow(true) @@ -88,7 +87,7 @@ export function ProtocolInstrumentMountItem( setShowPipetteWizardFlow(true) } } - const handleAttach: React.MouseEventHandler = () => { + const handleAttach: MouseEventHandler = () => { setFlowType(FLOWS.ATTACH) if (mount === 'extension') { setShowGripperWizardFlow(true) diff --git a/app/src/organisms/InstrumentMountItem/__tests__/ProtocolInstrumentMountItem.test.tsx b/app/src/organisms/ODD/InstrumentMountItem/__tests__/ProtocolInstrumentMountItem.test.tsx similarity index 93% rename from app/src/organisms/InstrumentMountItem/__tests__/ProtocolInstrumentMountItem.test.tsx rename to app/src/organisms/ODD/InstrumentMountItem/__tests__/ProtocolInstrumentMountItem.test.tsx index e7283b60466..6c4c308b8d2 100644 --- a/app/src/organisms/InstrumentMountItem/__tests__/ProtocolInstrumentMountItem.test.tsx +++ b/app/src/organisms/ODD/InstrumentMountItem/__tests__/ProtocolInstrumentMountItem.test.tsx @@ -1,15 +1,15 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { LEFT } from '@opentrons/shared-data' import { fireEvent, screen } from '@testing-library/react' -import { i18n } from '../../../i18n' -import { PipetteWizardFlows } from '../../PipetteWizardFlows' -import { GripperWizardFlows } from '../../GripperWizardFlows' +import { i18n } from '/app/i18n' +import { PipetteWizardFlows } from '/app/organisms/PipetteWizardFlows' +import { GripperWizardFlows } from '/app/organisms/GripperWizardFlows' import { ProtocolInstrumentMountItem } from '..' -vi.mock('../../PipetteWizardFlows') -vi.mock('../../GripperWizardFlows') +vi.mock('/app/organisms/PipetteWizardFlows') +vi.mock('/app/organisms/GripperWizardFlows') vi.mock('../../TakeoverModal') const mockGripperData = { diff --git a/app/src/organisms/InstrumentMountItem/index.tsx b/app/src/organisms/ODD/InstrumentMountItem/index.tsx similarity index 100% rename from app/src/organisms/InstrumentMountItem/index.tsx rename to app/src/organisms/ODD/InstrumentMountItem/index.tsx diff --git a/app/src/organisms/OnDeviceDisplay/NameRobot/ConfirmRobotName.tsx b/app/src/organisms/ODD/NameRobot/ConfirmRobotName.tsx similarity index 89% rename from app/src/organisms/OnDeviceDisplay/NameRobot/ConfirmRobotName.tsx rename to app/src/organisms/ODD/NameRobot/ConfirmRobotName.tsx index ebcb54f1f69..962a2d10de3 100644 --- a/app/src/organisms/OnDeviceDisplay/NameRobot/ConfirmRobotName.tsx +++ b/app/src/organisms/ODD/NameRobot/ConfirmRobotName.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' @@ -13,9 +12,9 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { StepMeter } from '../../../atoms/StepMeter' -import { MediumButton } from '../../../atoms/buttons' -import screenImage from '../../../assets/images/on-device-display/odd_abstract@x2.png' +import { StepMeter } from '/app/atoms/StepMeter' +import { MediumButton } from '/app/atoms/buttons' +import screenImage from '/app/assets/images/on-device-display/odd_abstract@x2.png' const IMAGE_ALT = 'finish setting up a robot' diff --git a/app/src/organisms/OnDeviceDisplay/NameRobot/__tests__/ConfirmRobotName.test.tsx b/app/src/organisms/ODD/NameRobot/__tests__/ConfirmRobotName.test.tsx similarity index 90% rename from app/src/organisms/OnDeviceDisplay/NameRobot/__tests__/ConfirmRobotName.test.tsx rename to app/src/organisms/ODD/NameRobot/__tests__/ConfirmRobotName.test.tsx index d33230a6424..d9e8521260e 100644 --- a/app/src/organisms/OnDeviceDisplay/NameRobot/__tests__/ConfirmRobotName.test.tsx +++ b/app/src/organisms/ODD/NameRobot/__tests__/ConfirmRobotName.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ConfirmRobotName } from '../ConfirmRobotName' import type { NavigateFunction } from 'react-router-dom' diff --git a/app/src/organisms/Navigation/NavigationMenu.tsx b/app/src/organisms/ODD/Navigation/NavigationMenu.tsx similarity index 91% rename from app/src/organisms/Navigation/NavigationMenu.tsx rename to app/src/organisms/ODD/Navigation/NavigationMenu.tsx index f48bb3c15bb..0b1cbb40e92 100644 --- a/app/src/organisms/Navigation/NavigationMenu.tsx +++ b/app/src/organisms/ODD/Navigation/NavigationMenu.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next' @@ -16,15 +16,16 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { home, ROBOT } from '../../redux/robot-controls' -import { useLights } from '../Devices/hooks' -import { getTopPortalEl } from '../../App/portal' +import { home, ROBOT } from '/app/redux/robot-controls' +import { useLights } from '/app/resources/devices' +import { getTopPortalEl } from '/app/App/portal' import { RestartRobotConfirmationModal } from './RestartRobotConfirmationModal' -import type { Dispatch } from '../../redux/types' +import type { MouseEventHandler } from 'react' +import type { Dispatch } from '/app/redux/types' interface NavigationMenuProps { - onClick: React.MouseEventHandler + onClick: MouseEventHandler robotName: string setShowNavMenu: (showNavMenu: boolean) => void } @@ -37,7 +38,7 @@ export function NavigationMenu(props: NavigationMenuProps): JSX.Element { const [ showRestartRobotConfirmationModal, setShowRestartRobotConfirmationModal, - ] = React.useState(false) + ] = useState(false) const navigate = useNavigate() diff --git a/app/src/organisms/Navigation/RestartRobotConfirmationModal.tsx b/app/src/organisms/ODD/Navigation/RestartRobotConfirmationModal.tsx similarity index 85% rename from app/src/organisms/Navigation/RestartRobotConfirmationModal.tsx rename to app/src/organisms/ODD/Navigation/RestartRobotConfirmationModal.tsx index 5be5488fd03..eb903ff5919 100644 --- a/app/src/organisms/Navigation/RestartRobotConfirmationModal.tsx +++ b/app/src/organisms/ODD/Navigation/RestartRobotConfirmationModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import { useDispatch } from 'react-redux' @@ -11,12 +10,12 @@ import { LegacyStyledText, } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' -import { restartRobot } from '../../redux/robot-admin' +import { SmallButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' +import { restartRobot } from '/app/redux/robot-admin' -import type { Dispatch } from '../../redux/types' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { Dispatch } from '/app/redux/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' interface RestartRobotConfirmationModalProps { robotName: string diff --git a/app/src/organisms/Navigation/__tests__/Navigation.test.tsx b/app/src/organisms/ODD/Navigation/__tests__/Navigation.test.tsx similarity index 79% rename from app/src/organisms/Navigation/__tests__/Navigation.test.tsx rename to app/src/organisms/ODD/Navigation/__tests__/Navigation.test.tsx index d1056531976..c86ba363d5c 100644 --- a/app/src/organisms/Navigation/__tests__/Navigation.test.tsx +++ b/app/src/organisms/ODD/Navigation/__tests__/Navigation.test.tsx @@ -1,40 +1,24 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { getLocalRobot } from '../../../redux/discovery' -import { mockConnectedRobot } from '../../../redux/discovery/__fixtures__' -import { useNetworkConnection } from '../../../resources/networking/hooks/useNetworkConnection' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getLocalRobot } from '/app/redux/discovery' +import { mockConnectedRobot } from '/app/redux/discovery/__fixtures__' +import { useNetworkConnection } from '/app/resources/networking/hooks/useNetworkConnection' import { NavigationMenu } from '../NavigationMenu' import { Navigation } from '..' +import { useScrollPosition } from '/app/local-resources/dom-utils' -vi.mock('../../../resources/networking/hooks/useNetworkConnection') -vi.mock('../../../redux/discovery') +vi.mock('/app/resources/networking/hooks/useNetworkConnection') +vi.mock('/app/redux/discovery') vi.mock('../NavigationMenu') +vi.mock('/app/local-resources/dom-utils') mockConnectedRobot.name = '12345678901234567' -class MockIntersectionObserver { - observe = vi.fn() - disconnect = vi.fn() - unobserve = vi.fn() -} - -Object.defineProperty(window, 'IntersectionObserver', { - writable: true, - configurable: true, - value: MockIntersectionObserver, -}) - -Object.defineProperty(global, 'IntersectionObserver', { - writable: true, - configurable: true, - value: MockIntersectionObserver, -}) - const render = (props: React.ComponentProps) => { return renderWithProviders( @@ -56,6 +40,10 @@ describe('Navigation', () => { isUsbConnected: false, connectionStatus: 'Not connected', }) + vi.mocked(useScrollPosition).mockReturnValue({ + isScrolled: false, + scrollRef: {} as any, + }) }) it('should render text and they have attribute', () => { render(props) diff --git a/app/src/organisms/Navigation/__tests__/NavigationMenu.test.tsx b/app/src/organisms/ODD/Navigation/__tests__/NavigationMenu.test.tsx similarity index 89% rename from app/src/organisms/Navigation/__tests__/NavigationMenu.test.tsx rename to app/src/organisms/ODD/Navigation/__tests__/NavigationMenu.test.tsx index 52f18c32306..b40122cdd6b 100644 --- a/app/src/organisms/Navigation/__tests__/NavigationMenu.test.tsx +++ b/app/src/organisms/ODD/Navigation/__tests__/NavigationMenu.test.tsx @@ -1,19 +1,19 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { home } from '../../../redux/robot-controls' -import { useLights } from '../../Devices/hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { home } from '/app/redux/robot-controls' +import { useLights } from '/app/resources/devices' import { RestartRobotConfirmationModal } from '../RestartRobotConfirmationModal' import { NavigationMenu } from '../NavigationMenu' import type { NavigateFunction } from 'react-router-dom' -vi.mock('../../../redux/robot-admin') -vi.mock('../../../redux/robot-controls') -vi.mock('../../Devices/hooks') +vi.mock('/app/redux/robot-admin') +vi.mock('/app/redux/robot-controls') +vi.mock('/app/resources/devices') vi.mock('../RestartRobotConfirmationModal') const mockNavigate = vi.fn() diff --git a/app/src/organisms/Navigation/__tests__/RestartRobotConfirmationModal.test.tsx b/app/src/organisms/ODD/Navigation/__tests__/RestartRobotConfirmationModal.test.tsx similarity index 85% rename from app/src/organisms/Navigation/__tests__/RestartRobotConfirmationModal.test.tsx rename to app/src/organisms/ODD/Navigation/__tests__/RestartRobotConfirmationModal.test.tsx index b3c3d8ec98e..8922a4225c2 100644 --- a/app/src/organisms/Navigation/__tests__/RestartRobotConfirmationModal.test.tsx +++ b/app/src/organisms/ODD/Navigation/__tests__/RestartRobotConfirmationModal.test.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { restartRobot } from '../../../redux/robot-admin' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { restartRobot } from '/app/redux/robot-admin' import { RestartRobotConfirmationModal } from '../RestartRobotConfirmationModal' -vi.mock('../../../redux/robot-admin') +vi.mock('/app/redux/robot-admin') const mockFunc = vi.fn() diff --git a/app/src/organisms/ODD/Navigation/index.tsx b/app/src/organisms/ODD/Navigation/index.tsx new file mode 100644 index 00000000000..e562793e36b --- /dev/null +++ b/app/src/organisms/ODD/Navigation/index.tsx @@ -0,0 +1,266 @@ +import { useState, useEffect, useRef } from 'react' +import { useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { useLocation, NavLink } from 'react-router-dom' +import styled from 'styled-components' + +import { + ALIGN_CENTER, + ALIGN_FLEX_START, + BORDERS, + Box, + COLORS, + CURSOR_DEFAULT, + DIRECTION_COLUMN, + DIRECTION_ROW, + DISPLAY_FLEX, + Flex, + Icon, + JUSTIFY_SPACE_BETWEEN, + OVERFLOW_SCROLL, + POSITION_ABSOLUTE, + POSITION_STATIC, + POSITION_STICKY, + RESPONSIVENESS, + SPACING, + truncateString, + TYPOGRAPHY, +} from '@opentrons/components' +import { ODD_FOCUS_VISIBLE } from '/app/atoms/buttons/constants' +import { useScrollPosition } from '/app/local-resources/dom-utils' + +import { useNetworkConnection } from '/app/resources/networking/hooks/useNetworkConnection' +import { getLocalRobot } from '/app/redux/discovery' +import { NavigationMenu } from './NavigationMenu' + +import type { Dispatch, SetStateAction } from 'react' +import type { ON_DEVICE_DISPLAY_PATHS } from '/app/App/OnDeviceDisplayApp' + +const NAV_LINKS: Array = [ + '/protocols', + '/quick-transfer', + '/instruments', + '/robot-settings', +] + +const CarouselWrapper = styled.div` + display: ${DISPLAY_FLEX}; + flex-direction: ${DIRECTION_ROW}; + align-items: ${ALIGN_FLEX_START}; + width: 56.75rem; + overflow-x: ${OVERFLOW_SCROLL}; + -webkit-mask-image: linear-gradient( + to right, + transparent 0%, + black 0%, + black 96.5%, + transparent 100% + ); + &::-webkit-scrollbar { + display: none; + } +` + +const CHAR_LIMIT_WITH_ICON = 12 +const CHAR_LIMIT_NO_ICON = 15 + +interface NavigationProps { + // optionalProps for setting the zIndex and position between multiple sticky elements + // used for ProtocolDashboard + setNavMenuIsOpened?: Dispatch> + longPressModalIsOpened?: boolean +} +export function Navigation(props: NavigationProps): JSX.Element { + const { setNavMenuIsOpened, longPressModalIsOpened } = props + const { t } = useTranslation('top_navigation') + const location = useLocation() + const localRobot = useSelector(getLocalRobot) + const [showNavMenu, setShowNavMenu] = useState(false) + const robotName = localRobot?.name != null ? localRobot.name : 'no name' + + // We need to display an icon for what type of network connection (if any) + // is active next to the robot's name. The designs call for it to change color + // from black70 to black100 depending on the which page is being displayed + // but we are using ReactRouter NavLinks, which doesn't easily support complex + // children like this. For now the icon will just be black70 regardless. + // + // TODO(ew, 05/21/2023): Integrate icon into NavLink so color changes + const networkConnection = useNetworkConnection(robotName) + const { icon: iconName } = networkConnection + + const handleMenu = (openMenu: boolean): void => { + if (setNavMenuIsOpened != null) { + setNavMenuIsOpened(openMenu) + } + setShowNavMenu(openMenu) + } + + const { scrollRef, isScrolled } = useScrollPosition() + + const navBarScrollRef = useRef(null) + useEffect(() => { + navBarScrollRef?.current?.scrollIntoView({ + behavior: 'auto', + inline: 'center', + }) + }, []) + + function getPathDisplayName(path: typeof NAV_LINKS[number]): string { + switch (path) { + case '/instruments': + return t('instruments') + case '/protocols': + return t('protocols') + case '/robot-settings': + return t('settings') + case '/quick-transfer': + return t('quick_transfer') + default: + return '' + } + } + + return ( + <> + {/* Empty box to detect scrolling */} + + + + + + + + + {iconName != null ? ( + + ) : null} + {NAV_LINKS.map(path => ( + + + + ))} + + + + + { + handleMenu(true) + }} + > + + + + + {showNavMenu && ( + { + handleMenu(false) + }} + robotName={robotName} + setShowNavMenu={setShowNavMenu} + /> + )} + + ) +} + +const NavigationLink = (props: { to: string; name: string }): JSX.Element => ( + + {props.name} + + +) + +const TouchNavLink = styled(NavLink)` + ${TYPOGRAPHY.level3HeaderSemiBold} + color: ${COLORS.grey50}; + height: 3.5rem; + display: flex; + flex-direction: ${DIRECTION_COLUMN}; + align-items: ${ALIGN_CENTER}; + white-space: nowrap; + &.active { + color: ${COLORS.black90}; + } + &.active > div { + background-color: ${COLORS.purple50}; + } + + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + cursor: ${CURSOR_DEFAULT}; + } +` + +const IconButton = styled('button')` + border-radius: ${BORDERS.borderRadius8}; + max-height: 100%; + background-color: ${COLORS.white}; + + &:hover { + background-color: ${COLORS.grey35}; + } + &:active { + background-color: ${COLORS.grey30}; + } + &:focus-visible { + box-shadow: ${ODD_FOCUS_VISIBLE}; + background-color: ${COLORS.grey35}; + } + &:disabled { + background-color: transparent; + } + @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { + cursor: ${CURSOR_DEFAULT}; + } +` diff --git a/app/src/organisms/NetworkSettings/AlternativeSecurityTypeModal.tsx b/app/src/organisms/ODD/NetworkSettings/AlternativeSecurityTypeModal.tsx similarity index 89% rename from app/src/organisms/NetworkSettings/AlternativeSecurityTypeModal.tsx rename to app/src/organisms/ODD/NetworkSettings/AlternativeSecurityTypeModal.tsx index 8709d9bdca5..856423f4125 100644 --- a/app/src/organisms/NetworkSettings/AlternativeSecurityTypeModal.tsx +++ b/app/src/organisms/ODD/NetworkSettings/AlternativeSecurityTypeModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' @@ -12,10 +11,10 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' +import { SmallButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' interface AlternativeSecurityTypeModalProps { setShowAlternativeSecurityTypeModal: ( diff --git a/app/src/organisms/NetworkSettings/ConnectingNetwork.tsx b/app/src/organisms/ODD/NetworkSettings/ConnectingNetwork.tsx similarity index 97% rename from app/src/organisms/NetworkSettings/ConnectingNetwork.tsx rename to app/src/organisms/ODD/NetworkSettings/ConnectingNetwork.tsx index 1fbb9811b6c..578abd4240d 100644 --- a/app/src/organisms/NetworkSettings/ConnectingNetwork.tsx +++ b/app/src/organisms/ODD/NetworkSettings/ConnectingNetwork.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { diff --git a/app/src/organisms/NetworkSettings/DisplaySearchNetwork.tsx b/app/src/organisms/ODD/NetworkSettings/DisplaySearchNetwork.tsx similarity index 96% rename from app/src/organisms/NetworkSettings/DisplaySearchNetwork.tsx rename to app/src/organisms/ODD/NetworkSettings/DisplaySearchNetwork.tsx index 195c9d7799d..0dbe2cde650 100644 --- a/app/src/organisms/NetworkSettings/DisplaySearchNetwork.tsx +++ b/app/src/organisms/ODD/NetworkSettings/DisplaySearchNetwork.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { diff --git a/app/src/organisms/NetworkSettings/DisplayWifiList.tsx b/app/src/organisms/ODD/NetworkSettings/DisplayWifiList.tsx similarity index 93% rename from app/src/organisms/NetworkSettings/DisplayWifiList.tsx rename to app/src/organisms/ODD/NetworkSettings/DisplayWifiList.tsx index 2925a47f392..3cd76255e32 100644 --- a/app/src/organisms/NetworkSettings/DisplayWifiList.tsx +++ b/app/src/organisms/ODD/NetworkSettings/DisplayWifiList.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' import { css } from 'styled-components' @@ -17,11 +16,11 @@ import { LegacyStyledText, } from '@opentrons/components' -import { ODD_FOCUS_VISIBLE } from '../../atoms/buttons/constants' -import { RobotSetupHeader } from '../../organisms/RobotSetupHeader' +import { ODD_FOCUS_VISIBLE } from '/app/atoms/buttons/constants' +import { RobotSetupHeader } from '/app/organisms/ODD/RobotSetupHeader' import { DisplaySearchNetwork } from './DisplaySearchNetwork' -import type { WifiNetwork } from '../../redux/networking/types' +import type { WifiNetwork } from '/app/redux/networking/types' const NETWORK_ROW_STYLE = css` display: ${DISPLAY_FLEX}; diff --git a/app/src/organisms/NetworkSettings/FailedToConnect.tsx b/app/src/organisms/ODD/NetworkSettings/FailedToConnect.tsx similarity index 94% rename from app/src/organisms/NetworkSettings/FailedToConnect.tsx rename to app/src/organisms/ODD/NetworkSettings/FailedToConnect.tsx index aa334813352..e3859e4ed29 100644 --- a/app/src/organisms/NetworkSettings/FailedToConnect.tsx +++ b/app/src/organisms/ODD/NetworkSettings/FailedToConnect.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -14,9 +13,9 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { MediumButton } from '../../atoms/buttons' +import { MediumButton } from '/app/atoms/buttons' -import type { RequestState } from '../../redux/robot-api/types' +import type { RequestState } from '/app/redux/robot-api/types' interface FailedToConnectProps { selectedSsid: string diff --git a/app/src/organisms/ODD/NetworkSettings/SelectAuthenticationType.tsx b/app/src/organisms/ODD/NetworkSettings/SelectAuthenticationType.tsx new file mode 100644 index 00000000000..9f163929220 --- /dev/null +++ b/app/src/organisms/ODD/NetworkSettings/SelectAuthenticationType.tsx @@ -0,0 +1,136 @@ +import { useState, useEffect } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' + +import { + ALIGN_CENTER, + Btn, + COLORS, + DIRECTION_COLUMN, + DISPLAY_FLEX, + Flex, + JUSTIFY_CENTER, + SPACING, + LegacyStyledText, + TYPOGRAPHY, + RadioButton, +} from '@opentrons/components' + +import { getLocalRobot } from '/app/redux/discovery' +import { getNetworkInterfaces, fetchStatus } from '/app/redux/networking' +import { useIsUnboxingFlowOngoing } from '/app/redux-resources/config' +import { AlternativeSecurityTypeModal } from './AlternativeSecurityTypeModal' + +import type { ChangeEvent } from 'react' +import type { WifiSecurityType } from '@opentrons/api-client' +import type { Dispatch, State } from '/app/redux/types' + +interface SelectAuthenticationTypeProps { + selectedAuthType: WifiSecurityType + setSelectedAuthType: (authType: WifiSecurityType) => void +} + +export function SelectAuthenticationType({ + selectedAuthType, + setSelectedAuthType, +}: SelectAuthenticationTypeProps): JSX.Element { + const { t } = useTranslation(['device_settings', 'shared']) + const dispatch = useDispatch() + const localRobot = useSelector(getLocalRobot) + const robotName = localRobot?.name != null ? localRobot.name : 'no name' + const isUnboxingFlowOngoing = useIsUnboxingFlowOngoing() + const { wifi } = useSelector((state: State) => + getNetworkInterfaces(state, robotName) + ) + const [ + showAlternativeSecurityTypeModal, + setShowAlternativeSecurityTypeModal, + ] = useState(false) + + const securityButtons = [ + { + label: t('wpa2_personal'), + subLabel: t('wpa2_personal_description'), + value: 'wpa-psk', + }, + { + label: t('shared:none'), + subLabel: t('none_description'), + value: 'none', + }, + ] + + const handleChange = (event: ChangeEvent): void => { + setSelectedAuthType(event.target.value as WifiSecurityType) + } + + useEffect(() => { + dispatch(fetchStatus(robotName)) + }, [robotName, dispatch]) + + return ( + <> + {showAlternativeSecurityTypeModal ? ( + + ) : null} + + + + {securityButtons.map(radio => ( + + ))} + + + + {t('your_mac_address_is', { macAddress: wifi?.macAddress })} + + + { + setShowAlternativeSecurityTypeModal(true) + }} + padding={`${SPACING.spacing16} ${SPACING.spacing24}`} + > + + {t('need_another_security_type')} + + + + + + ) +} diff --git a/app/src/organisms/ODD/NetworkSettings/SetWifiCred.tsx b/app/src/organisms/ODD/NetworkSettings/SetWifiCred.tsx new file mode 100644 index 00000000000..7d53b34f20c --- /dev/null +++ b/app/src/organisms/ODD/NetworkSettings/SetWifiCred.tsx @@ -0,0 +1,112 @@ +import { useRef, useState, memo, useEffect } from 'react' +import { useTranslation } from 'react-i18next' + +import { + ALIGN_CENTER, + Box, + Btn, + DIRECTION_COLUMN, + DIRECTION_ROW, + DISPLAY_FLEX, + Flex, + Icon, + InputField, + JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, + POSITION_FIXED, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' + +import { FullKeyboard } from '/app/atoms/SoftwareKeyboard' +import { useIsUnboxingFlowOngoing } from '/app/redux-resources/config' + +interface SetWifiCredProps { + password: string + setPassword: (password: string) => void +} + +export function SetWifiCred({ + password, + setPassword, +}: SetWifiCredProps): JSX.Element { + const { t } = useTranslation(['device_settings', 'shared']) + const keyboardRef = useRef(null) + const [showPassword, setShowPassword] = useState(false) + const inputRef = useRef(null) + const isUnboxingFlowOngoing = useIsUnboxingFlowOngoing() + const MemoizedInput = memo(InputField) + const handleBlur = (): void => { + if (inputRef.current != null) inputRef.current?.focus() + } + + useEffect(() => { + if (inputRef.current != null) { + inputRef.current.focus() + } + }, [password]) + + return ( + <> + + + {t('enter_password')} + + + + + { + setShowPassword(currentState => !currentState) + inputRef?.current?.focus() + }} + display={DISPLAY_FLEX} + flexDirection={DIRECTION_ROW} + alignItems={ALIGN_CENTER} + gridGap={SPACING.spacing12} + width="7.375rem" + > + + + {showPassword ? t('hide') : t('show')} + + + + + + + { + e != null && setPassword(String(e)) + inputRef?.current?.focus() + }} + keyboardRef={keyboardRef} + /> + + + ) +} diff --git a/app/src/organisms/NetworkSettings/SetWifiSsid.tsx b/app/src/organisms/ODD/NetworkSettings/SetWifiSsid.tsx similarity index 83% rename from app/src/organisms/NetworkSettings/SetWifiSsid.tsx rename to app/src/organisms/ODD/NetworkSettings/SetWifiSsid.tsx index 7f987144cb4..114d906215b 100644 --- a/app/src/organisms/NetworkSettings/SetWifiSsid.tsx +++ b/app/src/organisms/ODD/NetworkSettings/SetWifiSsid.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useRef } from 'react' import { useTranslation } from 'react-i18next' import { @@ -12,13 +12,15 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { FullKeyboard } from '../../atoms/SoftwareKeyboard' -import { useIsUnboxingFlowOngoing } from '../RobotSettingsDashboard/NetworkSettings/hooks' +import { FullKeyboard } from '/app/atoms/SoftwareKeyboard' +import { useIsUnboxingFlowOngoing } from '/app/redux-resources/config' + +import type { Dispatch, SetStateAction } from 'react' interface SetWifiSsidProps { errorMessage?: string | null inputSsid: string - setInputSsid: React.Dispatch> + setInputSsid: Dispatch> } export function SetWifiSsid({ @@ -27,7 +29,7 @@ export function SetWifiSsid({ setInputSsid, }: SetWifiSsidProps): JSX.Element { const { t } = useTranslation(['device_settings', 'shared']) - const keyboardRef = React.useRef(null) + const keyboardRef = useRef(null) const isUnboxingFlowOngoing = useIsUnboxingFlowOngoing() return ( diff --git a/app/src/organisms/ODD/NetworkSettings/WifiConnectionDetails.tsx b/app/src/organisms/ODD/NetworkSettings/WifiConnectionDetails.tsx new file mode 100644 index 00000000000..04e0fdb66a9 --- /dev/null +++ b/app/src/organisms/ODD/NetworkSettings/WifiConnectionDetails.tsx @@ -0,0 +1,132 @@ +import { useState, useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { useDispatch, useSelector } from 'react-redux' +import { useNavigate } from 'react-router-dom' + +import { + ALIGN_CENTER, + BORDERS, + COLORS, + DIRECTION_COLUMN, + DIRECTION_ROW, + Flex, + Icon, + JUSTIFY_CENTER, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' + +import { MediumButton } from '/app/atoms/buttons' +import { RobotSetupHeader } from '/app/organisms/ODD/RobotSetupHeader' +import { getLocalRobot } from '/app/redux/discovery' +import { getNetworkInterfaces, fetchStatus } from '/app/redux/networking' +import { NetworkDetailsModal } from '../RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal' + +import type { WifiSecurityType } from '@opentrons/api-client' +import type { Dispatch, State } from '/app/redux/types' + +interface WifiConnectionDetailsProps { + ssid?: string + authType?: WifiSecurityType + showHeader?: boolean +} + +export function WifiConnectionDetails({ + ssid, + authType, +}: WifiConnectionDetailsProps): JSX.Element { + const { i18n, t } = useTranslation(['device_settings', 'shared']) + const navigate = useNavigate() + const localRobot = useSelector(getLocalRobot) + const robotName = localRobot?.name != null ? localRobot.name : 'no name' + const dispatch = useDispatch() + const { wifi } = useSelector((state: State) => + getNetworkInterfaces(state, robotName) + ) + + const noData = i18n.format(t('shared:no_data'), 'titleCase') + const ipAddress = wifi?.ipAddress != null ? wifi.ipAddress : noData + const subnetMask = wifi?.subnetMask != null ? wifi.subnetMask : noData + const macAddress = wifi?.macAddress != null ? wifi.macAddress : noData + + const [ + showNetworkDetailsModal, + setShowNetworkDetailsModal, + ] = useState(false) + + useEffect(() => { + dispatch(fetchStatus(robotName)) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return ( + <> + {showNetworkDetailsModal ? ( + + ) : null} + + + + + + { + setShowNetworkDetailsModal(true) + }} + /> + { + navigate('/robot-settings/update-robot-during-onboarding') + }} + /> + + + + + ) +} + +interface DisplayConnectionStatusProps { + ssid?: string +} + +const DisplayConnectionStatus = ({ + ssid, +}: DisplayConnectionStatusProps): JSX.Element => { + const { t } = useTranslation('device_settings') + return ( + + + + {t('successfully_connected_to_network', { ssid })} + + + ) +} diff --git a/app/src/organisms/NetworkSettings/__tests__/AlternativeSecurityTypeModal.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/AlternativeSecurityTypeModal.test.tsx similarity index 92% rename from app/src/organisms/NetworkSettings/__tests__/AlternativeSecurityTypeModal.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/AlternativeSecurityTypeModal.test.tsx index ee23afbee84..a01af9bba66 100644 --- a/app/src/organisms/NetworkSettings/__tests__/AlternativeSecurityTypeModal.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/AlternativeSecurityTypeModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { AlternativeSecurityTypeModal } from '../AlternativeSecurityTypeModal' import type { NavigateFunction } from 'react-router-dom' diff --git a/app/src/organisms/NetworkSettings/__tests__/ConnectingNetwork.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/ConnectingNetwork.test.tsx similarity index 86% rename from app/src/organisms/NetworkSettings/__tests__/ConnectingNetwork.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/ConnectingNetwork.test.tsx index 9d19cba1822..e040bee4572 100644 --- a/app/src/organisms/NetworkSettings/__tests__/ConnectingNetwork.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/ConnectingNetwork.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { screen } from '@testing-library/react' import { beforeEach, describe, expect, it } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ConnectingNetwork } from '../ConnectingNetwork' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/NetworkSettings/__tests__/DisplaySearchNetwork.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/DisplaySearchNetwork.test.tsx similarity index 82% rename from app/src/organisms/NetworkSettings/__tests__/DisplaySearchNetwork.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/DisplaySearchNetwork.test.tsx index e582356a474..b26dba4cd68 100644 --- a/app/src/organisms/NetworkSettings/__tests__/DisplaySearchNetwork.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/DisplaySearchNetwork.test.tsx @@ -1,11 +1,10 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { describe, expect, it } from 'vitest' import { COLORS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { DisplaySearchNetwork } from '../DisplaySearchNetwork' const render = () => { diff --git a/app/src/organisms/NetworkSettings/__tests__/DisplayWifiList.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/DisplayWifiList.test.tsx similarity index 88% rename from app/src/organisms/NetworkSettings/__tests__/DisplayWifiList.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/DisplayWifiList.test.tsx index 2a901ee1850..e1449be3b9d 100644 --- a/app/src/organisms/NetworkSettings/__tests__/DisplayWifiList.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/DisplayWifiList.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import * as Fixtures from '../../../redux/networking/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import * as Fixtures from '/app/redux/networking/__fixtures__' import { DisplaySearchNetwork } from '../DisplaySearchNetwork' import { DisplayWifiList } from '../DisplayWifiList' @@ -20,8 +20,8 @@ const mockWifiList = [ }, ] -vi.mock('../../../redux/networking/selectors') -vi.mock('../../../redux/discovery/selectors') +vi.mock('/app/redux/networking/selectors') +vi.mock('/app/redux/discovery/selectors') vi.mock('../DisplaySearchNetwork') vi.mock('react-router-dom', async importOriginal => { const actual = await importOriginal() diff --git a/app/src/organisms/NetworkSettings/__tests__/FailedToConnect.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/FailedToConnect.test.tsx similarity index 90% rename from app/src/organisms/NetworkSettings/__tests__/FailedToConnect.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/FailedToConnect.test.tsx index 22a9a4d9440..3dbf7bf1f46 100644 --- a/app/src/organisms/NetworkSettings/__tests__/FailedToConnect.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/FailedToConnect.test.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { FailedToConnect } from '../FailedToConnect' -import type { RequestState } from '../../../redux/robot-api/types' +import type { RequestState } from '/app/redux/robot-api/types' const render = (props: React.ComponentProps) => { return renderWithProviders( diff --git a/app/src/organisms/NetworkSettings/__tests__/SelectAuthenticationType.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/SelectAuthenticationType.test.tsx similarity index 84% rename from app/src/organisms/NetworkSettings/__tests__/SelectAuthenticationType.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/SelectAuthenticationType.test.tsx index d014f2b5316..bfce05cc22d 100644 --- a/app/src/organisms/NetworkSettings/__tests__/SelectAuthenticationType.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/SelectAuthenticationType.test.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' import { afterEach, beforeEach, describe, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { getNetworkInterfaces, INTERFACE_WIFI } from '../../../redux/networking' -import { useIsUnboxingFlowOngoing } from '../../RobotSettingsDashboard/NetworkSettings/hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getNetworkInterfaces, INTERFACE_WIFI } from '/app/redux/networking' +import { useIsUnboxingFlowOngoing } from '/app/redux-resources/config' import { AlternativeSecurityTypeModal } from '../AlternativeSecurityTypeModal' import { SelectAuthenticationType } from '../SelectAuthenticationType' import { SetWifiCred } from '../SetWifiCred' @@ -17,10 +17,10 @@ const mockNavigate = vi.fn() const mockSetSelectedAuthType = vi.fn() vi.mock('../SetWifiCred') -vi.mock('../../../redux/networking') -vi.mock('../../../redux/discovery/selectors') +vi.mock('/app/redux/networking') +vi.mock('/app/redux/discovery/selectors') vi.mock('../AlternativeSecurityTypeModal') -vi.mock('../../RobotSettingsDashboard/NetworkSettings/hooks') +vi.mock('/app/redux-resources/config') vi.mock('react-router-dom', async importOriginal => { const actual = await importOriginal() return { diff --git a/app/src/organisms/NetworkSettings/__tests__/SetWifiCred.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/SetWifiCred.test.tsx similarity index 90% rename from app/src/organisms/NetworkSettings/__tests__/SetWifiCred.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/SetWifiCred.test.tsx index 51444bea730..d1a25f069c9 100644 --- a/app/src/organisms/NetworkSettings/__tests__/SetWifiCred.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/SetWifiCred.test.tsx @@ -1,15 +1,15 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { SetWifiCred } from '../SetWifiCred' const mockSetPassword = vi.fn() -vi.mock('../../../redux/discovery') -vi.mock('../../../redux/robot-api') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/robot-api') const render = (props: React.ComponentProps) => { return renderWithProviders( diff --git a/app/src/organisms/NetworkSettings/__tests__/SetWifiSsid.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/SetWifiSsid.test.tsx similarity index 92% rename from app/src/organisms/NetworkSettings/__tests__/SetWifiSsid.test.tsx rename to app/src/organisms/ODD/NetworkSettings/__tests__/SetWifiSsid.test.tsx index 761364da978..11eab279c37 100644 --- a/app/src/organisms/NetworkSettings/__tests__/SetWifiSsid.test.tsx +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/SetWifiSsid.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { SetWifiSsid } from '../SetWifiSsid' const mockSetSelectedSsid = vi.fn() diff --git a/app/src/organisms/ODD/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx b/app/src/organisms/ODD/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx new file mode 100644 index 00000000000..efcee37e0c6 --- /dev/null +++ b/app/src/organisms/ODD/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx @@ -0,0 +1,95 @@ +import type * as React from 'react' +import { MemoryRouter } from 'react-router-dom' +import { fireEvent, screen } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useWifiList } from '/app/resources/networking/hooks' +import { getNetworkInterfaces, INTERFACE_WIFI } from '/app/redux/networking' +import * as Fixtures from '/app/redux/networking/__fixtures__' +import { NetworkDetailsModal } from '../../RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal' +import { WifiConnectionDetails } from '../WifiConnectionDetails' + +import type { NavigateFunction } from 'react-router-dom' + +vi.mock('/app/resources/networking/hooks') +vi.mock('/app/redux/networking') +vi.mock('/app/redux/discovery/selectors') +vi.mock('../../RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal') + +const mockNavigate = vi.fn() +vi.mock('react-router-dom', async importOriginal => { + const actual = await importOriginal() + return { + ...actual, + useNavigate: () => mockNavigate, + } +}) + +const render = (props: React.ComponentProps) => { + return renderWithProviders( + + + , + { + i18nInstance: i18n, + } + ) +} + +const initialMockWifi = { + ipAddress: '127.0.0.100', + subnetMask: '255.255.255.230', + macAddress: 'WI:FI:00:00:00:00', + type: INTERFACE_WIFI, +} + +const mockWifiList = [ + { ...Fixtures.mockWifiNetwork, ssid: 'foo', active: true }, + { ...Fixtures.mockWifiNetwork, ssid: 'bar', active: false }, +] + +describe('WifiConnectionDetails', () => { + let props: React.ComponentProps + beforeEach(() => { + props = { + ssid: 'mockWifi', + authType: 'wpa-psk', + } + vi.mocked(getNetworkInterfaces).mockReturnValue({ + wifi: initialMockWifi, + ethernet: null, + }) + vi.mocked(useWifiList).mockReturnValue(mockWifiList) + vi.mocked(NetworkDetailsModal).mockReturnValue( +
    mock NetworkDetailsModal
    + ) + }) + + afterEach(() => { + vi.resetAllMocks() + }) + + it('should render title and description', () => { + render(props) + screen.getByText('Wi-Fi') + screen.getByText('Successfully connected to mockWifi!') + screen.getByText('View network details') + screen.getByText('Continue') + }) + + it('should render network details when tapping view network details', () => { + render(props) + fireEvent.click(screen.getByText('View network details')) + screen.getByText('mock NetworkDetailsModal') + }) + + it('when clicking Check for updates button, should call mock function', () => { + render(props) + fireEvent.click(screen.getByText('Continue')) + expect(mockNavigate).toHaveBeenCalledWith( + '/robot-settings/update-robot-during-onboarding' + ) + }) +}) diff --git a/app/src/organisms/NetworkSettings/index.ts b/app/src/organisms/ODD/NetworkSettings/index.ts similarity index 100% rename from app/src/organisms/NetworkSettings/index.ts rename to app/src/organisms/ODD/NetworkSettings/index.ts diff --git a/app/src/organisms/OpenDoorAlertModal/__tests__/OpenDoorAlertModal.test.tsx b/app/src/organisms/ODD/OpenDoorAlertModal/__tests__/OpenDoorAlertModal.test.tsx similarity index 76% rename from app/src/organisms/OpenDoorAlertModal/__tests__/OpenDoorAlertModal.test.tsx rename to app/src/organisms/ODD/OpenDoorAlertModal/__tests__/OpenDoorAlertModal.test.tsx index 2f1a66b0faa..a431ecac1cb 100644 --- a/app/src/organisms/OpenDoorAlertModal/__tests__/OpenDoorAlertModal.test.tsx +++ b/app/src/organisms/ODD/OpenDoorAlertModal/__tests__/OpenDoorAlertModal.test.tsx @@ -1,9 +1,8 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { describe, it } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { OpenDoorAlertModal } from '..' diff --git a/app/src/organisms/ODD/OpenDoorAlertModal/index.tsx b/app/src/organisms/ODD/OpenDoorAlertModal/index.tsx new file mode 100644 index 00000000000..38e18b502e3 --- /dev/null +++ b/app/src/organisms/ODD/OpenDoorAlertModal/index.tsx @@ -0,0 +1,54 @@ +import { createPortal } from 'react-dom' +import { useTranslation } from 'react-i18next' +import { + ALIGN_CENTER, + BORDERS, + COLORS, + DIRECTION_COLUMN, + Flex, + Icon, + JUSTIFY_CENTER, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' +import { getTopPortalEl } from '/app/App/portal' +import { OddModal } from '/app/molecules/OddModal' + +export function OpenDoorAlertModal(): JSX.Element { + const { t } = useTranslation('run_details') + return createPortal( + + + + + + {t('door_is_open')} + + + {t('close_door_to_resume_run')} + + + + , + getTopPortalEl() + ) +} diff --git a/app/src/pages/InstrumentsDashboard/PipetteRecalibrationODDWarning.tsx b/app/src/organisms/ODD/PipetteRecalibrationODDWarning/PipetteRecalibrationODDWarning.tsx similarity index 93% rename from app/src/pages/InstrumentsDashboard/PipetteRecalibrationODDWarning.tsx rename to app/src/organisms/ODD/PipetteRecalibrationODDWarning/PipetteRecalibrationODDWarning.tsx index 95031cf91c5..1420204a492 100644 --- a/app/src/pages/InstrumentsDashboard/PipetteRecalibrationODDWarning.tsx +++ b/app/src/organisms/ODD/PipetteRecalibrationODDWarning/PipetteRecalibrationODDWarning.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation, Trans } from 'react-i18next' import { ALIGN_CENTER, @@ -15,7 +15,7 @@ import { export const PipetteRecalibrationODDWarning = (): JSX.Element | null => { const { t } = useTranslation('instruments_dashboard') - const [showBanner, setShowBanner] = React.useState(true) + const [showBanner, setShowBanner] = useState(true) if (!showBanner) return null return ( diff --git a/app/src/pages/InstrumentsDashboard/__tests__/PipetteRecalibrationODDWarning.test.tsx b/app/src/organisms/ODD/PipetteRecalibrationODDWarning/__tests__/PipetteRecalibrationODDWarning.test.tsx similarity index 83% rename from app/src/pages/InstrumentsDashboard/__tests__/PipetteRecalibrationODDWarning.test.tsx rename to app/src/organisms/ODD/PipetteRecalibrationODDWarning/__tests__/PipetteRecalibrationODDWarning.test.tsx index 8961d30b219..afe42954cb0 100644 --- a/app/src/pages/InstrumentsDashboard/__tests__/PipetteRecalibrationODDWarning.test.tsx +++ b/app/src/organisms/ODD/PipetteRecalibrationODDWarning/__tests__/PipetteRecalibrationODDWarning.test.tsx @@ -1,9 +1,8 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { describe, it } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { PipetteRecalibrationODDWarning } from '../PipetteRecalibrationODDWarning' diff --git a/app/src/organisms/ODD/PipetteRecalibrationODDWarning/index.ts b/app/src/organisms/ODD/PipetteRecalibrationODDWarning/index.ts new file mode 100644 index 00000000000..822473abfc2 --- /dev/null +++ b/app/src/organisms/ODD/PipetteRecalibrationODDWarning/index.ts @@ -0,0 +1 @@ +export * from './PipetteRecalibrationODDWarning' diff --git a/app/src/organisms/OnDeviceDisplay/ProtocolDetails/ProtocolDetailsSkeleton.tsx b/app/src/organisms/ODD/ProtocolDetails/ProtocolDetailsSkeleton.tsx similarity index 95% rename from app/src/organisms/OnDeviceDisplay/ProtocolDetails/ProtocolDetailsSkeleton.tsx rename to app/src/organisms/ODD/ProtocolDetails/ProtocolDetailsSkeleton.tsx index aa241725f18..a7fbcc9accf 100644 --- a/app/src/organisms/OnDeviceDisplay/ProtocolDetails/ProtocolDetailsSkeleton.tsx +++ b/app/src/organisms/ODD/ProtocolDetails/ProtocolDetailsSkeleton.tsx @@ -1,8 +1,6 @@ -import * as React from 'react' - import { Flex, BORDERS, DIRECTION_COLUMN, SPACING } from '@opentrons/components' -import { Skeleton } from '../../../atoms/Skeleton' +import { Skeleton } from '/app/atoms/Skeleton' export function ProtocolDetailsHeaderChipSkeleton(): JSX.Element { return ( diff --git a/app/src/organisms/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetailsSkeleton.test.tsx b/app/src/organisms/ODD/ProtocolDetails/__tests__/ProtocolDetailsSkeleton.test.tsx similarity index 97% rename from app/src/organisms/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetailsSkeleton.test.tsx rename to app/src/organisms/ODD/ProtocolDetails/__tests__/ProtocolDetailsSkeleton.test.tsx index 04c377834ef..c56d09722fe 100644 --- a/app/src/organisms/OnDeviceDisplay/ProtocolDetails/__tests__/ProtocolDetailsSkeleton.test.tsx +++ b/app/src/organisms/ODD/ProtocolDetails/__tests__/ProtocolDetailsSkeleton.test.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { render, screen } from '@testing-library/react' import { describe, expect, it } from 'vitest' diff --git a/app/src/organisms/OnDeviceDisplay/ProtocolDetails/index.ts b/app/src/organisms/ODD/ProtocolDetails/index.ts similarity index 100% rename from app/src/organisms/OnDeviceDisplay/ProtocolDetails/index.ts rename to app/src/organisms/ODD/ProtocolDetails/index.ts diff --git a/app/src/organisms/ProtocolSetupDeckConfiguration/__tests__/ProtocolSetupDeckConfiguration.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupDeckConfiguration/__tests__/ProtocolSetupDeckConfiguration.test.tsx similarity index 85% rename from app/src/organisms/ProtocolSetupDeckConfiguration/__tests__/ProtocolSetupDeckConfiguration.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupDeckConfiguration/__tests__/ProtocolSetupDeckConfiguration.test.tsx index ef226bbfc19..84a7fd2eb87 100644 --- a/app/src/organisms/ProtocolSetupDeckConfiguration/__tests__/ProtocolSetupDeckConfiguration.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupDeckConfiguration/__tests__/ProtocolSetupDeckConfiguration.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, afterEach } from 'vitest' @@ -9,11 +9,11 @@ import { useUpdateDeckConfigurationMutation, } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' import { ProtocolSetupDeckConfiguration } from '..' -import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' import type { UseQueryResult } from 'react-query' import type { @@ -24,8 +24,8 @@ import type { Modules } from '@opentrons/api-client' vi.mock('@opentrons/components/src/hardware-sim/BaseDeck/index') vi.mock('@opentrons/react-api-client') -vi.mock('../../LabwarePositionCheck/useMostRecentCompletedAnalysis') -vi.mock('../../../resources/deck_configuration') +vi.mock('/app/resources/runs') +vi.mock('/app/resources/deck_configuration') const mockSetSetupScreen = vi.fn() const PROTOCOL_DETAILS = { diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupDeckConfiguration/index.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupDeckConfiguration/index.tsx new file mode 100644 index 00000000000..4f67deb6da6 --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupDeckConfiguration/index.tsx @@ -0,0 +1,169 @@ +import { useState } from 'react' +import { createPortal } from 'react-dom' +import { useTranslation } from 'react-i18next' + +import { + BaseDeck, + DIRECTION_COLUMN, + Flex, + JUSTIFY_CENTER, + SPACING, +} from '@opentrons/components' +import { + FLEX_ROBOT_TYPE, + FLEX_SINGLE_SLOT_BY_CUTOUT_ID, + MAGNETIC_BLOCK_V1_FIXTURE, + MODULE_FIXTURES_BY_MODEL, + STAGING_AREA_SLOT_WITH_MAGNETIC_BLOCK_V1_FIXTURE, + THERMOCYCLER_V2_REAR_FIXTURE, + getSimplestDeckConfigForProtocol, +} from '@opentrons/shared-data' + +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { AddFixtureModal } from '../../../DeviceDetailsDeckConfiguration/AddFixtureModal' +import { DeckConfigurationDiscardChangesModal } from '../../../DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { getTopPortalEl } from '/app/App/portal' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' + +import type { Dispatch, SetStateAction } from 'react' +import type { + CutoutFixtureId, + CutoutId, + ModuleModel, +} from '@opentrons/shared-data' +import type { ModuleOnDeck } from '@opentrons/components' +import type { SetupScreens } from '../types' + +interface ProtocolSetupDeckConfigurationProps { + cutoutId: CutoutId | null + runId: string + setSetupScreen: Dispatch> + providedFixtureOptions: CutoutFixtureId[] +} + +export function ProtocolSetupDeckConfiguration({ + cutoutId, + runId, + setSetupScreen, + providedFixtureOptions, +}: ProtocolSetupDeckConfigurationProps): JSX.Element { + const { i18n, t } = useTranslation([ + 'protocol_setup', + 'devices_landing', + 'shared', + ]) + + const [showConfigurationModal, setShowConfigurationModal] = useState( + true + ) + const [showDiscardChangeModal, setShowDiscardChangeModal] = useState( + false + ) + + const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) + const deckConfig = useNotifyDeckConfigurationQuery()?.data ?? [] + + const simplestDeckConfig = getSimplestDeckConfigForProtocol( + mostRecentAnalysis + ).map(({ cutoutId, cutoutFixtureId }) => ({ cutoutId, cutoutFixtureId })) + + const targetCutoutConfig = simplestDeckConfig.find( + deck => deck.cutoutId === cutoutId + ) + + const mergedDeckConfig = deckConfig.map(config => + targetCutoutConfig != null && + config.cutoutId === targetCutoutConfig.cutoutId + ? targetCutoutConfig + : config + ) + + const modulesOnDeck = mergedDeckConfig.reduce( + (acc, cutoutConfig) => { + const matchingFixtureIdsAndModel = Object.entries( + MODULE_FIXTURES_BY_MODEL + ).find(([_moduleModel, moduleFixtureIds]) => + moduleFixtureIds.includes(cutoutConfig.cutoutFixtureId) + ) + if ( + matchingFixtureIdsAndModel != null && + cutoutConfig.cutoutFixtureId !== THERMOCYCLER_V2_REAR_FIXTURE + ) { + const [matchingModel] = matchingFixtureIdsAndModel + return [ + ...acc, + { + moduleModel: matchingModel as ModuleModel, + moduleLocation: { + slotName: FLEX_SINGLE_SLOT_BY_CUTOUT_ID[cutoutConfig.cutoutId], + }, + }, + ] + } else if ( + cutoutConfig.cutoutFixtureId === + STAGING_AREA_SLOT_WITH_MAGNETIC_BLOCK_V1_FIXTURE + ) { + return [ + ...acc, + { + moduleModel: MAGNETIC_BLOCK_V1_FIXTURE, + moduleLocation: { + slotName: FLEX_SINGLE_SLOT_BY_CUTOUT_ID[cutoutConfig.cutoutId], + }, + }, + ] + } + return acc + }, + [] + ) + + const handleClickConfirm = (): void => { + setSetupScreen('modules') + } + + return ( + <> + {createPortal( + <> + {showDiscardChangeModal ? ( + + ) : null} + {showConfigurationModal && cutoutId != null ? ( + { + setShowConfigurationModal(false) + }} + providedFixtureOptions={providedFixtureOptions} + isOnDevice + /> + ) : null} + , + getTopPortalEl() + )} + + + + + + + + ) +} diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/ProtocolSetupInstruments.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/ProtocolSetupInstruments.tsx new file mode 100644 index 00000000000..1af859bc431 --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/ProtocolSetupInstruments.tsx @@ -0,0 +1,113 @@ +import type * as React from 'react' +import styled from 'styled-components' +import { useTranslation } from 'react-i18next' +import { + COLORS, + ALIGN_CENTER, + DIRECTION_COLUMN, + Flex, + JUSTIFY_SPACE_BETWEEN, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' +import { useInstrumentsQuery } from '@opentrons/react-api-client' +import { ODDBackButton } from '/app/molecules/ODDBackButton' +import { PipetteRecalibrationODDWarning } from '/app/organisms/ODD/PipetteRecalibrationODDWarning' +import { getShowPipetteCalibrationWarning } from '/app/transformations/instruments' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { ProtocolInstrumentMountItem } from '/app/organisms/ODD/InstrumentMountItem' + +import type { GripperData, PipetteData } from '@opentrons/api-client' +import type { GripperModel } from '@opentrons/shared-data' +import type { SetupScreens } from '../types' +import { isGripperInCommands } from '/app/resources/protocols/utils' + +export interface ProtocolSetupInstrumentsProps { + runId: string + setSetupScreen: React.Dispatch> +} + +export function ProtocolSetupInstruments({ + runId, + setSetupScreen, +}: ProtocolSetupInstrumentsProps): JSX.Element { + const { t, i18n } = useTranslation('protocol_setup') + const { data: attachedInstruments, refetch } = useInstrumentsQuery() + const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) + + const usesGripper = + mostRecentAnalysis != null + ? isGripperInCommands(mostRecentAnalysis?.commands ?? []) + : false + const attachedGripperMatch = usesGripper + ? (attachedInstruments?.data ?? []).find( + (i): i is GripperData => i.instrumentType === 'gripper' && i.ok + ) ?? null + : null + + return ( + + { + setSetupScreen('prepare to run') + }} + /> + {getShowPipetteCalibrationWarning(attachedInstruments) && ( + + + + )} + + {t('location')} + + {i18n.format(t('calibration_status'), 'sentenceCase')} + + + {(mostRecentAnalysis?.pipettes ?? []).map(loadedPipette => { + const attachedPipetteMatch = + (attachedInstruments?.data ?? []).find( + (i): i is PipetteData => + i.instrumentType === 'pipette' && + i.ok && + i.mount === loadedPipette.mount && + i.instrumentName === loadedPipette.pipetteName + ) ?? null + return ( + + ) + })} + {usesGripper ? ( + + ) : null} + + ) +} + +const ColumnLabel = styled.p` + flex: 1; + font-weight: ${TYPOGRAPHY.fontWeightSemiBold}; + font-size: ${TYPOGRAPHY.fontSize22}; + line-height: ${TYPOGRAPHY.lineHeight28}; + color: ${COLORS.grey60}; +` diff --git a/app/src/organisms/ProtocolSetupInstruments/__fixtures__/index.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/__fixtures__/index.ts similarity index 100% rename from app/src/organisms/ProtocolSetupInstruments/__fixtures__/index.ts rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/__fixtures__/index.ts diff --git a/app/src/organisms/ProtocolSetupInstruments/__tests__/ProtocolSetupInstruments.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/__tests__/ProtocolSetupInstruments.test.tsx similarity index 82% rename from app/src/organisms/ProtocolSetupInstruments/__tests__/ProtocolSetupInstruments.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/__tests__/ProtocolSetupInstruments.test.tsx index bcfc0ecf1d6..3a1273130ee 100644 --- a/app/src/organisms/ProtocolSetupInstruments/__tests__/ProtocolSetupInstruments.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/__tests__/ProtocolSetupInstruments.test.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { when } from 'vitest-when' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' @@ -9,18 +8,16 @@ import { useAllPipetteOffsetCalibrationsQuery, } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useMostRecentCompletedAnalysis } from '../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useIsOEMMode } from '../../../resources/robot-settings/hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { useIsOEMMode } from '/app/resources/robot-settings/hooks' import { mockRecentAnalysis } from '../__fixtures__' import { ProtocolSetupInstruments } from '..' vi.mock('@opentrons/react-api-client') -vi.mock( - '../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' -) -vi.mock('../../../resources/robot-settings/hooks') +vi.mock('/app/resources/runs') +vi.mock('/app/resources/robot-settings/hooks') const mockGripperData = { instrumentModel: 'gripper_v1', diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/index.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/index.ts new file mode 100644 index 00000000000..0d5d7d99a4d --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/index.ts @@ -0,0 +1,2 @@ +export * from './ProtocolSetupInstruments' +export * from './utils' diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/utils.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/utils.ts new file mode 100644 index 00000000000..c0d3a5ee30d --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupInstruments/utils.ts @@ -0,0 +1,81 @@ +import type { + CompletedProtocolAnalysis, + LoadedPipette, +} from '@opentrons/shared-data' +import type { + GripperData, + Instruments, + PipetteData, +} from '@opentrons/api-client' + +import { getProtocolUsesGripper } from '/app/transformations/commands' + +export function getAttachedGripper( + attachedInstruments: Instruments +): GripperData | null { + return ( + (attachedInstruments?.data ?? []).find( + (i): i is GripperData => + i.instrumentType === 'gripper' && + i.ok && + i.data.calibratedOffset != null + ) ?? null + ) +} + +export function getPipetteMatch( + loadedPipette: LoadedPipette, + attachedInstruments: Instruments +): PipetteData | null { + return ( + (attachedInstruments?.data ?? []).find( + (i): i is PipetteData => + i.instrumentType === 'pipette' && + i.ok && + i.mount === loadedPipette.mount && + i.instrumentName === loadedPipette.pipetteName + ) ?? null + ) +} + +export function getAreInstrumentsReady( + analysis: CompletedProtocolAnalysis, + attachedInstruments: Instruments +): boolean { + const speccedPipettes = analysis?.pipettes ?? [] + const allSpeccedPipettesReady = speccedPipettes.every(loadedPipette => { + const attachedPipetteMatch = getPipetteMatch( + loadedPipette, + attachedInstruments + ) + return attachedPipetteMatch?.data.calibratedOffset?.last_modified != null + }) + const isExtensionMountReady = getProtocolUsesGripper(analysis) + ? getAttachedGripper(attachedInstruments)?.data.calibratedOffset + ?.last_modified != null + : true + + return allSpeccedPipettesReady && isExtensionMountReady +} + +export function getIncompleteInstrumentCount( + analysis: CompletedProtocolAnalysis, + attachedInstruments: Instruments +): number { + const speccedPipettes = analysis?.pipettes ?? [] + + const incompleteInstrumentCount = speccedPipettes.filter(loadedPipette => { + const attachedPipetteMatch = getPipetteMatch( + loadedPipette, + attachedInstruments + ) + return attachedPipetteMatch?.data.calibratedOffset?.last_modified == null + }).length + + const isExtensionMountReady = getProtocolUsesGripper(analysis) + ? getAttachedGripper(attachedInstruments)?.data.calibratedOffset + ?.last_modified != null + : true + + return incompleteInstrumentCount + (isExtensionMountReady ? 0 : 1) +} diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/LabwareMapView.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/LabwareMapView.tsx new file mode 100644 index 00000000000..d311a6aab5a --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/LabwareMapView.tsx @@ -0,0 +1,121 @@ +import map from 'lodash/map' +import { BaseDeck, Flex } from '@opentrons/components' +import { + FLEX_ROBOT_TYPE, + getSimplestDeckConfigForProtocol, + getTopLabwareInfo, + THERMOCYCLER_MODULE_V1, +} from '@opentrons/shared-data' + +import { getStandardDeckViewLayerBlockList } from '/app/local-resources/deck_configuration' +import { getLabwareRenderInfo } from '/app/transformations/analysis' + +import type { LabwareOnDeck } from '@opentrons/components' +import type { + CompletedProtocolAnalysis, + DeckDefinition, + LabwareDefinition2, + RunTimeCommand, + LoadLabwareRunTimeCommand, +} from '@opentrons/shared-data' + +import type { AttachedProtocolModuleMatch } from '/app/transformations/analysis' + +interface LabwareMapViewProps { + attachedProtocolModuleMatches: AttachedProtocolModuleMatch[] + handleLabwareClick: ( + labwareDef: LabwareDefinition2, + labwareId: string + ) => void + deckDef: DeckDefinition + mostRecentAnalysis: CompletedProtocolAnalysis | null +} + +export function LabwareMapView(props: LabwareMapViewProps): JSX.Element { + const { + handleLabwareClick, + attachedProtocolModuleMatches, + deckDef, + mostRecentAnalysis, + } = props + const deckConfig = getSimplestDeckConfigForProtocol(mostRecentAnalysis) + const commands: RunTimeCommand[] = mostRecentAnalysis?.commands ?? [] + const loadLabwareCommands = commands?.filter( + (command): command is LoadLabwareRunTimeCommand => + command.commandType === 'loadLabware' + ) + + const labwareRenderInfo = + mostRecentAnalysis != null + ? getLabwareRenderInfo(mostRecentAnalysis, deckDef) + : {} + + const modulesOnDeck = attachedProtocolModuleMatches.map(module => { + const { moduleDef, nestedLabwareDef, nestedLabwareId, slotName } = module + const isLabwareStacked = nestedLabwareId != null && nestedLabwareDef != null + const { topLabwareId, topLabwareDefinition } = getTopLabwareInfo( + module.nestedLabwareId ?? '', + loadLabwareCommands + ) + + return { + moduleModel: moduleDef.model, + moduleLocation: { slotName }, + innerProps: + moduleDef.model === THERMOCYCLER_MODULE_V1 + ? { lidMotorState: 'open' } + : {}, + nestedLabwareDef: topLabwareDefinition, + onLabwareClick: + topLabwareDefinition != null && topLabwareId != null + ? () => { + handleLabwareClick(topLabwareDefinition, topLabwareId) + } + : undefined, + highlightLabware: true, + moduleChildren: null, + stacked: isLabwareStacked, + } + }) + + const labwareLocations: Array = map( + labwareRenderInfo, + ({ slotName }, labwareId) => { + const { topLabwareId, topLabwareDefinition } = getTopLabwareInfo( + labwareId, + loadLabwareCommands + ) + const isLabwareInStack = labwareId !== topLabwareId + + return topLabwareDefinition != null + ? { + labwareLocation: { slotName }, + definition: topLabwareDefinition, + onLabwareClick: () => { + handleLabwareClick(topLabwareDefinition, topLabwareId) + }, + highlight: true, + highlightShadow: isLabwareInStack, + stacked: isLabwareInStack, + } + : null + } + ) + + const labwareLocationsFiltered: LabwareOnDeck[] = labwareLocations.filter( + (labwareLocation): labwareLocation is LabwareOnDeck => + labwareLocation != null + ) + + return ( + + + + ) +} diff --git a/app/src/organisms/ProtocolSetupLabware/SingleLabwareModal.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/SingleLabwareModal.tsx similarity index 94% rename from app/src/organisms/ProtocolSetupLabware/SingleLabwareModal.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/SingleLabwareModal.tsx index 2345397d8c7..91ccd682acf 100644 --- a/app/src/organisms/ProtocolSetupLabware/SingleLabwareModal.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/SingleLabwareModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -14,11 +13,11 @@ import { LegacyStyledText, SPACING, TYPOGRAPHY, - Modal, } from '@opentrons/components' import { getLabwareDisplayName } from '@opentrons/shared-data' -import { getTopPortalEl } from '../../App/portal' +import { getTopPortalEl } from '/app/App/portal' +import { OddModal } from '/app/molecules/OddModal' import type { CompletedProtocolAnalysis, @@ -70,7 +69,7 @@ export const SingleLabwareModal = ( const selectedLabwareLocation = selectedLabware?.location return createPortal( - + - , + , getTopPortalEl() ) } diff --git a/app/src/organisms/ProtocolSetupLabware/__fixtures__/index.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__fixtures__/index.ts similarity index 100% rename from app/src/organisms/ProtocolSetupLabware/__fixtures__/index.ts rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__fixtures__/index.ts diff --git a/app/src/organisms/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx similarity index 86% rename from app/src/organisms/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx index 532440223d2..860d927578e 100644 --- a/app/src/organisms/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/LabwareMapView.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { when } from 'vitest-when' import { describe, it, vi, beforeEach, afterEach, expect } from 'vitest' @@ -10,10 +10,10 @@ import { fixtureTiprack300ul, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { getLabwareRenderInfo } from '../../Devices/ProtocolRun/utils/getLabwareRenderInfo' -import { getStandardDeckViewLayerBlockList } from '../../Devices/ProtocolRun/utils/getStandardDeckViewLayerBlockList' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { getLabwareRenderInfo } from '/app/transformations/analysis' +import { getStandardDeckViewLayerBlockList } from '/app/local-resources/deck_configuration' import { mockProtocolModuleInfo } from '../__fixtures__' import { LabwareMapView } from '../LabwareMapView' @@ -25,12 +25,12 @@ import type { ModuleModel, } from '@opentrons/shared-data' -vi.mock('../../Devices/ProtocolRun/utils/getLabwareRenderInfo') +vi.mock('/app/transformations/analysis') vi.mock('@opentrons/components/src/hardware-sim/Labware/LabwareRender') vi.mock('@opentrons/components/src/hardware-sim/BaseDeck') vi.mock('@opentrons/shared-data/js/helpers/getSimplestFlexDeckConfig') -vi.mock('../../../resources/deck_configuration/utils') -vi.mock('../../../redux/config') +vi.mock('/app/resources/deck_configuration/utils') +vi.mock('/app/redux/config') const MOCK_300_UL_TIPRACK_COORDS = [30, 40, 0] @@ -114,7 +114,6 @@ describe('LabwareMapView', () => { handleLabwareClick: vi.fn(), deckDef: (deckDefFixture as unknown) as DeckDefinition, mostRecentAnalysis: ({} as unknown) as CompletedProtocolAnalysis, - initialLoadedLabwareByAdapter: {}, attachedProtocolModuleMatches: [ { ...mockProtocolModuleInfo[0], diff --git a/app/src/organisms/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx similarity index 88% rename from app/src/organisms/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx index 99b3c555dd5..0f34b8ebd9e 100644 --- a/app/src/organisms/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/__tests__/ProtocolSetupLabware.test.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { when } from 'vitest-when' import { MemoryRouter } from 'react-router-dom' @@ -13,10 +12,10 @@ import { ot3StandardDeckV5 as ot3StandardDeckDef, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useMostRecentCompletedAnalysis } from '../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { getProtocolModulesInfo } from '../../Devices/ProtocolRun/utils/getProtocolModulesInfo' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { getProtocolModulesInfo } from '/app/transformations/analysis/getProtocolModulesInfo' import { ProtocolSetupLabware } from '..' import { mockProtocolModuleInfo, @@ -27,7 +26,7 @@ import { mockUseModulesQueryOpening, mockUseModulesQueryUnknown, } from '../__fixtures__' -import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' import type * as ReactApiClient from '@opentrons/react-api-client' @@ -40,11 +39,9 @@ vi.mock('@opentrons/react-api-client', async importOriginal => { } }) -vi.mock( - '../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' -) -vi.mock('../../Devices/ProtocolRun/utils/getProtocolModulesInfo') -vi.mock('../../../resources/deck_configuration') +vi.mock('/app/resources/runs') +vi.mock('/app/transformations/analysis/getProtocolModulesInfo') +vi.mock('/app/resources/deck_configuration') const RUN_ID = "otie's run" const mockSetSetupScreen = vi.fn() diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/index.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/index.tsx new file mode 100644 index 00000000000..19875b2336e --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLabware/index.tsx @@ -0,0 +1,582 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { css } from 'styled-components' + +import { + ALIGN_CENTER, + ALIGN_FLEX_START, + BORDERS, + Box, + Chip, + COLORS, + DeckInfoLabel, + DIRECTION_COLUMN, + DIRECTION_ROW, + Flex, + Icon, + JUSTIFY_SPACE_BETWEEN, + JUSTIFY_SPACE_EVENLY, + LegacyStyledText, + MODULE_ICON_NAME_BY_TYPE, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' +import { + FLEX_ROBOT_TYPE, + getDeckDefFromRobotType, + getModuleDisplayName, + getTopLabwareInfo, + HEATERSHAKER_MODULE_TYPE, + TC_MODULE_LOCATION_OT3, + THERMOCYCLER_MODULE_TYPE, +} from '@opentrons/shared-data' +import { + useCreateLiveCommandMutation, + useModulesQuery, +} from '@opentrons/react-api-client' + +import { FloatingActionButton, SmallButton } from '/app/atoms/buttons' +import { ODDBackButton } from '/app/molecules/ODDBackButton' +import { + getLocationInfoNames, + getLabwareSetupItemGroups, +} from '/app/transformations/commands' +import { + getAttachedProtocolModuleMatches, + getProtocolModulesInfo, +} from '/app/transformations/analysis' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' +import { LabwareStackModal } from '/app/molecules/LabwareStackModal' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { LabwareMapView } from './LabwareMapView' +import { SingleLabwareModal } from './SingleLabwareModal' + +import type { Dispatch, SetStateAction } from 'react' +import type { UseQueryResult } from 'react-query' +import type { + HeaterShakerCloseLatchCreateCommand, + HeaterShakerOpenLatchCreateCommand, + LabwareDefinition2, + LoadLabwareRunTimeCommand, + LabwareLocation, + RunTimeCommand, +} from '@opentrons/shared-data' +import type { HeaterShakerModule, Modules } from '@opentrons/api-client' +import type { LabwareSetupItem } from '/app/transformations/commands' +import type { SetupScreens } from '../types' +import type { AttachedProtocolModuleMatch } from '/app/transformations/analysis' + +const MODULE_REFETCH_INTERVAL_MS = 5000 +const DECK_CONFIG_POLL_MS = 5000 + +export interface ProtocolSetupLabwareProps { + runId: string + setSetupScreen: Dispatch> + isConfirmed: boolean + setIsConfirmed: (confirmed: boolean) => void +} + +export function ProtocolSetupLabware({ + runId, + setSetupScreen, + isConfirmed, + setIsConfirmed, +}: ProtocolSetupLabwareProps): JSX.Element { + const { t } = useTranslation('protocol_setup') + const [showMapView, setShowMapView] = useState(false) + const [ + showLabwareDetailsModal, + setShowLabwareDetailsModal, + ] = useState(false) + const [selectedLabware, setSelectedLabware] = useState< + | (LabwareDefinition2 & { + location: LabwareLocation + nickName: string | null + id: string + }) + | null + >(null) + + const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) + const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE) + const { data: deckConfig = [] } = useNotifyDeckConfigurationQuery({ + refetchInterval: DECK_CONFIG_POLL_MS, + }) + const { offDeckItems, onDeckItems } = getLabwareSetupItemGroups( + mostRecentAnalysis?.commands ?? [] + ) + const moduleQuery = useModulesQuery({ + refetchInterval: MODULE_REFETCH_INTERVAL_MS, + }) + const attachedModules = moduleQuery?.data?.data ?? [] + const protocolModulesInfo = + mostRecentAnalysis != null + ? getProtocolModulesInfo(mostRecentAnalysis, deckDef) + : [] + + const attachedProtocolModuleMatches = getAttachedProtocolModuleMatches( + attachedModules, + protocolModulesInfo, + deckConfig + ) + + const handleLabwareClick = ( + labwareDef: LabwareDefinition2, + labwareId: string + ): void => { + const foundLabware = mostRecentAnalysis?.labware.find( + labware => labware.id === labwareId + ) + if (foundLabware != null) { + const nickName = onDeckItems.find( + item => item.labwareId === foundLabware.id + )?.nickName + + const location = onDeckItems.find( + item => item.labwareId === foundLabware.id + )?.initialLocation + if (location != null) { + setSelectedLabware({ + ...labwareDef, + location, + nickName: nickName ?? null, + id: labwareId, + }) + setShowLabwareDetailsModal(true) + } else { + console.warn('no initial labware location found') + } + } + } + const selectedLabwareIsStacked = mostRecentAnalysis?.commands.some( + command => + command.commandType === 'loadLabware' && + command.result?.labwareId === selectedLabware?.id && + typeof command.params.location === 'object' && + ('moduleId' in command.params.location || + 'labwareId' in command.params.location) + ) + + return ( + <> + {showLabwareDetailsModal && + !selectedLabwareIsStacked && + selectedLabware != null ? ( + { + setShowLabwareDetailsModal(false) + setSelectedLabware(null) + }} + mostRecentAnalysis={mostRecentAnalysis} + /> + ) : null} + + { + setSetupScreen('prepare to run') + }} + /> + {isConfirmed ? ( + + ) : ( + { + setIsConfirmed(true) + setSetupScreen('prepare to run') + }} + buttonCategory="rounded" + /> + )} + + + {showMapView ? ( + + ) : ( + <> + + + {t('location')} + + + {t('labware_name')} + + + {[...onDeckItems, ...offDeckItems].map((labware, i) => { + const labwareOnAdapter = onDeckItems.find( + item => + labware.initialLocation !== 'offDeck' && + 'labwareId' in labware.initialLocation && + item.labwareId === labware.initialLocation.labwareId + ) + return mostRecentAnalysis?.commands != null && + labwareOnAdapter == null ? ( + + ) : null + })} + + )} + {showLabwareDetailsModal && + selectedLabware != null && + selectedLabwareIsStacked ? ( + { + setSelectedLabware(null) + setShowLabwareDetailsModal(false) + }} + /> + ) : null} + + { + setShowMapView(mapView => !mapView) + }} + /> + + ) +} + +const labwareLatchStyles = css` + &:active { + background-color: ${COLORS.blue35}; + } +` + +interface LabwareLatchProps { + matchedHeaterShaker: HeaterShakerModule + refetchModules: UseQueryResult['refetch'] +} + +function LabwareLatch({ + matchedHeaterShaker, + refetchModules, +}: LabwareLatchProps): JSX.Element { + const { t } = useTranslation(['heater_shaker', 'protocol_setup']) + const { + createLiveCommand, + isLoading: isLiveCommandLoading, + } = useCreateLiveCommandMutation() + const [isRefetchingModules, setIsRefetchingModules] = useState(false) + const isLatchLoading = + isLiveCommandLoading || + isRefetchingModules || + matchedHeaterShaker.data.labwareLatchStatus === 'opening' || + matchedHeaterShaker.data.labwareLatchStatus === 'closing' + const isLatchClosed = + matchedHeaterShaker.data.labwareLatchStatus === 'idle_closed' || + matchedHeaterShaker.data.labwareLatchStatus === 'opening' + + let icon: 'latch-open' | 'latch-closed' | null = null + + const latchCommand: + | HeaterShakerOpenLatchCreateCommand + | HeaterShakerCloseLatchCreateCommand = { + commandType: isLatchClosed + ? 'heaterShaker/openLabwareLatch' + : 'heaterShaker/closeLabwareLatch', + params: { moduleId: matchedHeaterShaker.id }, + } + + const toggleLatch = (): void => { + createLiveCommand({ + command: latchCommand, + waitUntilComplete: true, + }) + .then(() => { + setIsRefetchingModules(true) + refetchModules() + .then(() => { + setIsRefetchingModules(false) + }) + .catch((e: Error) => { + console.error( + `error refetching modules after toggle latch: ${e.message}` + ) + setIsRefetchingModules(false) + }) + }) + .catch((e: Error) => { + console.error( + `error setting module status with command type ${latchCommand.commandType}: ${e.message}` + ) + }) + } + const commandType = isLatchClosed + ? 'heaterShaker/openLabwareLatch' + : 'heaterShaker/closeLabwareLatch' + let hsLatchText: string | null = t('open') + if (commandType === 'heaterShaker/closeLabwareLatch' && isLatchLoading) { + hsLatchText = t('closing') + icon = 'latch-open' + } else if ( + commandType === 'heaterShaker/openLabwareLatch' && + isLatchLoading + ) { + hsLatchText = t('opening') + icon = 'latch-closed' + } else if ( + commandType === 'heaterShaker/closeLabwareLatch' && + !isLatchLoading + ) { + hsLatchText = t('open') + icon = 'latch-open' + } else if ( + commandType === 'heaterShaker/openLabwareLatch' && + !isLatchLoading + ) { + hsLatchText = t('closed') + icon = 'latch-closed' + } + + return ( + + + {t('protocol_setup:labware_latch')} + + + {hsLatchText != null && icon != null ? ( + <> + + {hsLatchText} + + + + ) : null} + + + ) +} + +interface RowLabwareProps { + labware: LabwareSetupItem + attachedProtocolModules: AttachedProtocolModuleMatch[] + refetchModules: UseQueryResult['refetch'] + commands: RunTimeCommand[] +} + +function RowLabware({ + labware, + attachedProtocolModules, + refetchModules, + commands, +}: RowLabwareProps): JSX.Element | null { + const { + initialLocation, + nickName: bottomLabwareNickname, + labwareId: bottomLabwareId, + } = labware + const loadLabwareCommands = commands?.filter( + (command): command is LoadLabwareRunTimeCommand => + command.commandType === 'loadLabware' + ) + + const { topLabwareId } = getTopLabwareInfo( + bottomLabwareId ?? '', + loadLabwareCommands + ) + const { + slotName: slot, + labwareName: topLabwareName, + labwareNickname: topLabwareNickname, + labwareQuantity: topLabwareQuantity, + adapterName, + } = getLocationInfoNames(topLabwareId, commands) + + const { t, i18n } = useTranslation([ + 'protocol_command_text', + 'protocol_setup', + ]) + + const matchedModule = + initialLocation !== 'offDeck' && + 'moduleId' in initialLocation && + attachedProtocolModules.length > 0 + ? attachedProtocolModules.find( + mod => mod.moduleId === initialLocation.moduleId + ) + : null + const matchingHeaterShaker = + matchedModule?.attachedModuleMatch != null && + matchedModule.attachedModuleMatch.moduleType === HEATERSHAKER_MODULE_TYPE + ? matchedModule.attachedModuleMatch + : null + const isStacked = + topLabwareQuantity > 1 || adapterName != null || matchedModule != null + + let slotName: string = slot + let location: JSX.Element = + if (initialLocation === 'offDeck') { + location = ( + + ) + } else if ( + matchedModule != null && + matchedModule.attachedModuleMatch?.moduleType === THERMOCYCLER_MODULE_TYPE + ) { + slotName = TC_MODULE_LOCATION_OT3 + location = + } + return ( + + + {location} + {isStacked ? : null} + + + + + + {topLabwareName} + + + {topLabwareQuantity > 1 + ? t('protocol_setup:labware_quantity', { + quantity: topLabwareQuantity, + }) + : topLabwareNickname} + + + {adapterName != null ? ( + <> + + + + {adapterName} + + + {bottomLabwareNickname} + + + + ) : null} + {matchedModule != null ? ( + <> + + + + + + {getModuleDisplayName(matchedModule.moduleDef.model)} + + {matchingHeaterShaker != null ? ( + + {t('protocol_setup:labware_latch_instructions')} + + ) : null} + + + + ) : null} + + {matchingHeaterShaker != null ? ( + + ) : null} + + + ) +} diff --git a/app/src/organisms/ProtocolSetupLiquids/LiquidDetails.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/LiquidDetails.tsx similarity index 91% rename from app/src/organisms/ProtocolSetupLiquids/LiquidDetails.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/LiquidDetails.tsx index e117134ba77..796e9595d85 100644 --- a/app/src/organisms/ProtocolSetupLiquids/LiquidDetails.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/LiquidDetails.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import styled from 'styled-components' import { useTranslation } from 'react-i18next' import { @@ -14,9 +14,9 @@ import { WRAP, } from '@opentrons/components' import { MICRO_LITERS } from '@opentrons/shared-data' -import { LiquidsLabwareDetailsModal } from '../Devices/ProtocolRun/SetupLiquids/LiquidsLabwareDetailsModal' -import { getLocationInfoNames } from '../Devices/ProtocolRun/utils/getLocationInfoNames' -import { getVolumePerWell } from '../Devices/ProtocolRun/SetupLiquids/utils' +import { LiquidsLabwareDetailsModal } from '/app/organisms/LiquidsLabwareDetailsModal' +import { getLocationInfoNames } from '/app/transformations/commands' +import { getVolumePerWell } from '/app/transformations/analysis' import type { LabwareByLiquidId, @@ -70,9 +70,7 @@ interface LiquidDetailsProps { export function LiquidDetails(props: LiquidDetailsProps): JSX.Element { const { liquid, labwareByLiquidId, runId, commands } = props const { t } = useTranslation('protocol_setup') - const [labwareIdModal, setLabwareIdModal] = React.useState( - null - ) + const [labwareIdModal, setLabwareIdModal] = useState(null) return ( diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx new file mode 100644 index 00000000000..720b6db7545 --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx @@ -0,0 +1,67 @@ +import type * as React from 'react' +import { screen, fireEvent } from '@testing-library/react' +import { describe, it, beforeEach, vi } from 'vitest' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { RUN_ID_1 } from '/app/resources/runs/__fixtures__' +import { getLocationInfoNames } from '/app/transformations/commands' +import { getVolumePerWell } from '/app/transformations/analysis' +import { LiquidDetails } from '../LiquidDetails' +import { LiquidsLabwareDetailsModal } from '/app/organisms/LiquidsLabwareDetailsModal' +import { + MOCK_LABWARE_INFO_BY_LIQUID_ID, + MOCK_PROTOCOL_ANALYSIS, +} from '../fixtures' +import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' + +vi.mock('/app/transformations/analysis') +vi.mock('/app/transformations/commands') +vi.mock('/app/organisms/LiquidsLabwareDetailsModal') + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} + +describe('LiquidDetails', () => { + let props: React.ComponentProps + beforeEach(() => { + props = { + commands: (MOCK_PROTOCOL_ANALYSIS as CompletedProtocolAnalysis).commands, + labwareByLiquidId: MOCK_LABWARE_INFO_BY_LIQUID_ID, + runId: RUN_ID_1, + liquid: { + id: '0', + displayName: 'mock liquid 1', + description: 'mock sample', + displayColor: '#ff4888', + }, + } + vi.mocked(getVolumePerWell).mockReturnValue(50) + vi.mocked(getLocationInfoNames).mockReturnValue({ + slotName: '4', + labwareName: 'mock labware name', + labwareQuantity: 1, + }) + vi.mocked(LiquidsLabwareDetailsModal).mockReturnValue(
    mock modal
    ) + }) + + it('renders the total volume of the liquid, sample display name, clicking on arrow renders the modal', () => { + render(props) + screen.getByText('4') + screen.getByText('mock labware name') + screen.getByText('Location') + screen.getByText('Labware name') + screen.getByText('Individual well volume') + screen.getByText('50 µL') + fireEvent.click(screen.getByLabelText('LiquidDetails_0')) + screen.getByText('mock modal') + }) + it('renders variable well amount if no specific volume per well', () => { + vi.mocked(getVolumePerWell).mockReturnValue(null) + render(props) + screen.getByText('Variable well amount') + }) +}) diff --git a/app/src/organisms/ProtocolSetupLiquids/__tests__/ProtocolSetupLiquids.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/ProtocolSetupLiquids.test.tsx similarity index 80% rename from app/src/organisms/ProtocolSetupLiquids/__tests__/ProtocolSetupLiquids.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/ProtocolSetupLiquids.test.tsx index b4dd41061e6..487fbbd0bce 100644 --- a/app/src/organisms/ProtocolSetupLiquids/__tests__/ProtocolSetupLiquids.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/__tests__/ProtocolSetupLiquids.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, beforeEach, vi } from 'vitest' import { screen, fireEvent } from '@testing-library/react' @@ -7,11 +7,11 @@ import { parseLiquidsInLoadOrder, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { RUN_ID_1 } from '../../RunTimeControl/__fixtures__' -import { getTotalVolumePerLiquidId } from '../../Devices/ProtocolRun/SetupLiquids/utils' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { RUN_ID_1 } from '/app/resources/runs/__fixtures__' +import { getTotalVolumePerLiquidId } from '/app/transformations/analysis' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' import { LiquidDetails } from '../LiquidDetails' import { MOCK_LABWARE_INFO_BY_LIQUID_ID, @@ -22,10 +22,10 @@ import { ProtocolSetupLiquids } from '..' import type * as SharedData from '@opentrons/shared-data' -vi.mock('../../Devices/ProtocolRun/SetupLiquids/utils') -vi.mock('../../../atoms/buttons') +vi.mock('/app/transformations/analysis') +vi.mock('/app/atoms/buttons') vi.mock('../LiquidDetails') -vi.mock('../../LabwarePositionCheck/useMostRecentCompletedAnalysis') +vi.mock('/app/resources/runs') vi.mock('@opentrons/shared-data', async importOriginal => { const actualSharedData = await importOriginal() return { diff --git a/app/src/organisms/ProtocolSetupLiquids/fixtures.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/fixtures.ts similarity index 100% rename from app/src/organisms/ProtocolSetupLiquids/fixtures.ts rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/fixtures.ts diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/index.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/index.tsx new file mode 100644 index 00000000000..d8aa63d4b26 --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupLiquids/index.tsx @@ -0,0 +1,193 @@ +import { Fragment, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + ALIGN_CENTER, + BORDERS, + COLORS, + DIRECTION_COLUMN, + DIRECTION_ROW, + Flex, + Icon, + SPACING, + StyledText, + TYPOGRAPHY, + JUSTIFY_SPACE_BETWEEN, + Chip, +} from '@opentrons/components' +import { + MICRO_LITERS, + parseLabwareInfoByLiquidId, + parseLiquidsInLoadOrder, +} from '@opentrons/shared-data' +import { ODDBackButton } from '/app/molecules/ODDBackButton' +import { SmallButton } from '/app/atoms/buttons' + +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { getTotalVolumePerLiquidId } from '/app/transformations/analysis' +import { LiquidDetails } from './LiquidDetails' + +import type { Dispatch, SetStateAction } from 'react' +import type { ParsedLiquid, RunTimeCommand } from '@opentrons/shared-data' +import type { SetupScreens } from '../types' + +export interface ProtocolSetupLiquidsProps { + runId: string + setSetupScreen: Dispatch> + isConfirmed: boolean + setIsConfirmed: (confirmed: boolean) => void +} + +export function ProtocolSetupLiquids({ + runId, + setSetupScreen, + isConfirmed, + setIsConfirmed, +}: ProtocolSetupLiquidsProps): JSX.Element { + const { t, i18n } = useTranslation('protocol_setup') + const protocolData = useMostRecentCompletedAnalysis(runId) + const liquidsInLoadOrder = parseLiquidsInLoadOrder( + protocolData?.liquids ?? [], + protocolData?.commands ?? [] + ) + return ( + <> + + { + setSetupScreen('prepare to run') + }} + /> + {isConfirmed ? ( + + ) : ( + { + setIsConfirmed(true) + setSetupScreen('prepare to run') + }} + buttonCategory="rounded" + /> + )} + + + + + + {t('liquid_name')} + + + + + {t('total_liquid_volume')} + + + + {liquidsInLoadOrder?.map(liquid => ( + + + + ))} + + + ) +} + +interface LiquidsListProps { + liquid: ParsedLiquid + runId: string + commands?: RunTimeCommand[] +} + +export function LiquidsList(props: LiquidsListProps): JSX.Element { + const { liquid, runId, commands } = props + const [openItem, setOpenItem] = useState(false) + const labwareByLiquidId = parseLabwareInfoByLiquidId(commands ?? []) + + return ( + + { + setOpenItem(prevOpenItem => !prevOpenItem) + }} + aria-label={`Liquids_${liquid.id}`} + > + + + + + + {liquid.displayName.length > 33 + ? `${liquid.displayName.substring(0, 33)}...` + : liquid.displayName} + + + + + {getTotalVolumePerLiquidId(liquid.id, labwareByLiquidId)}{' '} + {MICRO_LITERS} + + + + + {openItem ? ( + + ) : null} + + ) +} diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/FixtureTable.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/FixtureTable.tsx new file mode 100644 index 00000000000..7d990e112a0 --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/FixtureTable.tsx @@ -0,0 +1,271 @@ +import { useState, Fragment } from 'react' +import { useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { + ALIGN_CENTER, + BORDERS, + COLORS, + Chip, + DeckInfoLabel, + DIRECTION_ROW, + Flex, + JUSTIFY_SPACE_BETWEEN, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' +import { + FLEX_USB_MODULE_ADDRESSABLE_AREAS, + getCutoutDisplayName, + getDeckDefFromRobotType, + getFixtureDisplayName, + getSimplestDeckConfigForProtocol, + SINGLE_SLOT_FIXTURES, + TC_MODULE_LOCATION_OT3, + THERMOCYCLER_V2_FRONT_FIXTURE, + THERMOCYCLER_V2_REAR_FIXTURE, +} from '@opentrons/shared-data' + +import { SmallButton } from '/app/atoms/buttons' +import { useDeckConfigurationCompatibility } from '/app/resources/deck_configuration/hooks' +import { getRequiredDeckConfig } from '/app/resources/deck_configuration/utils' +import { LocationConflictModal } from '/app/organisms/LocationConflictModal' +import { getLocalRobot } from '/app/redux/discovery' + +import type { Dispatch, SetStateAction } from 'react' +import type { + CompletedProtocolAnalysis, + CutoutFixtureId, + CutoutId, + DeckDefinition, + RobotType, +} from '@opentrons/shared-data' +import type { SetupScreens } from '../types' +import type { CutoutConfigAndCompatibility } from '/app/resources/deck_configuration/hooks' + +interface FixtureTableProps { + robotType: RobotType + mostRecentAnalysis: CompletedProtocolAnalysis | null + setSetupScreen: Dispatch> + setCutoutId: (cutoutId: CutoutId) => void + setProvidedFixtureOptions: (providedFixtureOptions: CutoutFixtureId[]) => void +} + +/** + * Table of all "non-module" fixtures e.g. staging slot, waste chute, trash bin... + * @param props + * @returns JSX.Element + */ +export function FixtureTable({ + robotType, + mostRecentAnalysis, + setSetupScreen, + setCutoutId, + setProvidedFixtureOptions, +}: FixtureTableProps): JSX.Element | null { + const requiredFixtureDetails = getSimplestDeckConfigForProtocol( + mostRecentAnalysis + ) + const deckConfigCompatibility = useDeckConfigurationCompatibility( + robotType, + mostRecentAnalysis + ) + const deckDef = getDeckDefFromRobotType(robotType) + const localRobot = useSelector(getLocalRobot) + const robotName = localRobot?.name != null ? localRobot.name : '' + + const requiredDeckConfigCompatibility = getRequiredDeckConfig( + deckConfigCompatibility + ) + + const hasTwoLabwareThermocyclerConflicts = + requiredDeckConfigCompatibility.some( + ({ cutoutFixtureId, missingLabwareDisplayName }) => + cutoutFixtureId === THERMOCYCLER_V2_FRONT_FIXTURE && + missingLabwareDisplayName != null + ) && + requiredDeckConfigCompatibility.some( + ({ cutoutFixtureId, missingLabwareDisplayName }) => + cutoutFixtureId === THERMOCYCLER_V2_REAR_FIXTURE && + missingLabwareDisplayName != null + ) + + // if there are two labware conflicts with the thermocycler, don't show the conflict with the thermocycler rear fixture + const filteredDeckConfigCompatibility = requiredDeckConfigCompatibility.filter( + ({ cutoutFixtureId }) => { + return ( + !hasTwoLabwareThermocyclerConflicts || + !(cutoutFixtureId === THERMOCYCLER_V2_REAR_FIXTURE) + ) + } + ) + + // list not configured/conflicted fixtures first + const sortedDeckConfigCompatibility = filteredDeckConfigCompatibility.sort( + a => + a.cutoutFixtureId != null && + a.compatibleCutoutFixtureIds.includes(a.cutoutFixtureId) + ? 1 + : -1 + ) + + return sortedDeckConfigCompatibility.length > 0 ? ( + <> + {sortedDeckConfigCompatibility.map((fixtureCompatibility, index) => { + // filter out all fixtures that only provide module addressable areas (e.g. everything but StagingAreaWithMagBlockV1) + // as they're handled in the Modules Table + return fixtureCompatibility.requiredAddressableAreas.every(raa => + FLEX_USB_MODULE_ADDRESSABLE_AREAS.includes(raa) + ) ? null : ( + + ) + })} + + ) : null +} + +interface FixtureTableItemProps extends CutoutConfigAndCompatibility { + lastItem: boolean + setSetupScreen: Dispatch> + setCutoutId: (cutoutId: CutoutId) => void + setProvidedFixtureOptions: (providedFixtureOptions: CutoutFixtureId[]) => void + deckDef: DeckDefinition + robotName: string +} + +function FixtureTableItem({ + cutoutId, + cutoutFixtureId, + compatibleCutoutFixtureIds, + missingLabwareDisplayName, + lastItem, + setSetupScreen, + setCutoutId, + setProvidedFixtureOptions, + deckDef, + robotName, +}: FixtureTableItemProps): JSX.Element { + const { t, i18n } = useTranslation('protocol_setup') + + const [ + showLocationConflictModal, + setShowLocationConflictModal, + ] = useState(false) + + const isCurrentFixtureCompatible = + cutoutFixtureId != null && + compatibleCutoutFixtureIds.includes(cutoutFixtureId) + const isRequiredSingleSlotMissing = missingLabwareDisplayName != null + + const isThermocyclerCurrentFixture = + cutoutFixtureId === THERMOCYCLER_V2_FRONT_FIXTURE || + cutoutFixtureId === THERMOCYCLER_V2_REAR_FIXTURE + + let chipLabel: JSX.Element + if (!isCurrentFixtureCompatible) { + const isConflictingFixtureConfigured = + cutoutFixtureId != null && !SINGLE_SLOT_FIXTURES.includes(cutoutFixtureId) + chipLabel = ( + <> + + { + setShowLocationConflictModal(true) + } + : () => { + setCutoutId(cutoutId) + setProvidedFixtureOptions(compatibleCutoutFixtureIds) + setSetupScreen('deck configuration') + } + } + /> + + ) + } else { + chipLabel = ( + + ) + } + return ( + + {showLocationConflictModal ? ( + { + setShowLocationConflictModal(false) + }} + cutoutId={cutoutId} + requiredFixtureId={compatibleCutoutFixtureIds[0]} + isOnDevice={true} + missingLabwareDisplayName={missingLabwareDisplayName} + deckDef={deckDef} + robotName={robotName} + /> + ) : null} + + + + {cutoutFixtureId != null && + (isCurrentFixtureCompatible || isRequiredSingleSlotMissing) + ? getFixtureDisplayName(cutoutFixtureId) + : getFixtureDisplayName(compatibleCutoutFixtureIds?.[0])} + + + + + + + {chipLabel} + + + + ) +} diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/ModuleTable.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/ModuleTable.tsx similarity index 84% rename from app/src/organisms/ProtocolSetupModulesAndDeck/ModuleTable.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/ModuleTable.tsx index bc6313d0626..d4b0a32ad2d 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/ModuleTable.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/ModuleTable.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' @@ -15,6 +15,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' import { + ABSORBANCE_READER_TYPE, getCutoutFixturesForModuleModel, getCutoutIdsFromModuleSlotName, getModuleDisplayName, @@ -25,22 +26,25 @@ import { THERMOCYCLER_MODULE_TYPE, } from '@opentrons/shared-data' -import { SmallButton } from '../../atoms/buttons' -import { getModulePrepCommands } from '../../organisms/Devices/getModulePrepCommands' -import { getModuleTooHot } from '../../organisms/Devices/getModuleTooHot' -import { useRunCalibrationStatus } from '../../organisms/Devices/hooks' -import { LocationConflictModal } from '../../organisms/Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' -import { ModuleWizardFlows } from '../../organisms/ModuleWizardFlows' -import { useToaster } from '../../organisms/ToasterOven' -import { getLocalRobot } from '../../redux/discovery' -import { useChainLiveCommands } from '../../resources/runs' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' +import { SmallButton } from '/app/atoms/buttons' +import { getModulePrepCommands } from '/app/local-resources/modules' +import { getModuleTooHot } from '/app/transformations/modules' +import { LocationConflictModal } from '/app/organisms/LocationConflictModal' +import { ModuleWizardFlows } from '/app/organisms/ModuleWizardFlows' +import { useToaster } from '/app/organisms/ToasterOven' +import { getLocalRobot } from '/app/redux/discovery' +import { + useChainLiveCommands, + useRunCalibrationStatus, +} from '/app/resources/runs' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' +import type { Dispatch, SetStateAction } from 'react' import type { CommandData } from '@opentrons/api-client' import type { CutoutConfig, DeckDefinition } from '@opentrons/shared-data' -import type { ModulePrepCommandsType } from '../../organisms/Devices/getModulePrepCommands' -import type { ProtocolCalibrationStatus } from '../../organisms/Devices/hooks' -import type { AttachedProtocolModuleMatch } from './utils' +import type { ModulePrepCommandsType } from '/app/local-resources/modules' +import type { ProtocolCalibrationStatus } from '/app/resources/runs' +import type { AttachedProtocolModuleMatch } from '/app/transformations/analysis' const DECK_CONFIG_REFETCH_INTERVAL = 5000 @@ -56,7 +60,7 @@ export function ModuleTable(props: ModuleTableProps): JSX.Element { const [ prepCommandErrorMessage, setPrepCommandErrorMessage, - ] = React.useState('') + ] = useState('') const { data: deckConfig } = useNotifyDeckConfigurationQuery({ refetchInterval: DECK_CONFIG_REFETCH_INTERVAL, @@ -116,7 +120,7 @@ interface ModuleTableItemProps { isLoading: boolean module: AttachedProtocolModuleMatch prepCommandErrorMessage: string - setPrepCommandErrorMessage: React.Dispatch> + setPrepCommandErrorMessage: Dispatch> deckDef: DeckDefinition robotName: string } @@ -159,11 +163,11 @@ function ModuleTableItem({ ) const isModuleReady = module.attachedModuleMatch != null - const [showModuleWizard, setShowModuleWizard] = React.useState(false) + const [showModuleWizard, setShowModuleWizard] = useState(false) const [ showLocationConflictModal, setShowLocationConflictModal, - ] = React.useState(false) + ] = useState(false) let moduleStatus: JSX.Element = ( <> @@ -201,7 +205,8 @@ function ModuleTableItem({ ) } else if ( isModuleReady && - module.attachedModuleMatch?.moduleOffset?.last_modified != null + (module.attachedModuleMatch?.moduleOffset?.last_modified != null || + module.attachedModuleMatch?.moduleType === ABSORBANCE_READER_TYPE) ) { moduleStatus = ( > + setCutoutId: (cutoutId: CutoutId) => void + setProvidedFixtureOptions: (providedFixtureOptions: CutoutFixtureId[]) => void +} + +/** + * an ODD screen on the Protocol Setup page + */ +export function ProtocolSetupModulesAndDeck({ + runId, + setSetupScreen, + setCutoutId, + setProvidedFixtureOptions, +}: ProtocolSetupModulesAndDeckProps): JSX.Element { + const { i18n, t } = useTranslation('protocol_setup') + const navigate = useNavigate() + const runStatus = useRunStatus(runId) + useEffect(() => { + if (runStatus === RUN_STATUS_STOPPED) { + navigate('/protocols') + } + }, [runStatus, navigate]) + const [ + showSetupInstructionsModal, + setShowSetupInstructionsModal, + ] = useState(false) + const [showMapView, setShowMapView] = useState(false) + const [ + clearModuleMismatchBanner, + setClearModuleMismatchBanner, + ] = useState(false) + const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) + + const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE) + const { data: deckConfig = [] } = useNotifyDeckConfigurationQuery({ + refetchInterval: DECK_CONFIG_POLL_MS, + }) + const attachedModules = + useAttachedModules({ + refetchInterval: ATTACHED_MODULE_POLL_MS, + }) ?? [] + + const protocolModulesInfo = + mostRecentAnalysis != null + ? getProtocolModulesInfo(mostRecentAnalysis, deckDef) + : [] + + const attachedProtocolModuleMatches = getAttachedProtocolModuleMatches( + attachedModules, + protocolModulesInfo, + deckConfig + ) + + const hasModules = attachedProtocolModuleMatches.length > 0 + + const { + missingModuleIds, + remainingAttachedModules, + } = getUnmatchedModulesForProtocol(attachedModules, protocolModulesInfo) + + const isModuleMismatch = + remainingAttachedModules.length > 0 && missingModuleIds.length > 0 + return ( + <> + {createPortal( + <> + {showSetupInstructionsModal ? ( + + ) : null} + , + getTopPortalEl() + )} + { + setSetupScreen('prepare to run') + }} + buttonText={i18n.format(t('setup_instructions'), 'titleCase')} + buttonType="tertiaryLowLight" + iconName="information" + iconPlacement="startIcon" + onClickButton={() => { + setShowSetupInstructionsModal(true) + }} + /> + + {showMapView ? ( + + + + ) : ( + <> + {isModuleMismatch && !clearModuleMismatchBanner ? ( + { + e.stopPropagation() + setClearModuleMismatchBanner(true) + }} + heading={t('extra_module_attached')} + message={t('module_mismatch_body')} + /> + ) : null} + + + + {i18n.format(t('deck_hardware'), 'titleCase')} + + + {t('location')} + + {t('status')} + + + {hasModules ? ( + + ) : null} + + + + + )} + + { + setShowMapView(mapView => !mapView) + }} + /> + + ) +} diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/SetupInstructionsModal.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/SetupInstructionsModal.tsx similarity index 87% rename from app/src/organisms/ProtocolSetupModulesAndDeck/SetupInstructionsModal.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/SetupInstructionsModal.tsx index 569c59f6c72..d2fdc3bf540 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/SetupInstructionsModal.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/SetupInstructionsModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -12,11 +11,11 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { OddModal } from '../../molecules/OddModal' +import { OddModal } from '/app/molecules/OddModal' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' -import imgSrc from '../../assets/images/on-device-display/setup_instructions_qr_code.png' +import imgSrc from '/app/assets/images/on-device-display/setup_instructions_qr_code.png' const INSTRUCTIONS_URL = 'support.opentrons.com/s/modules' diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx similarity index 82% rename from app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx index c73a5aacd79..e6ca8735d77 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/FixtureTable.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, afterEach, vi, expect } from 'vitest' @@ -10,19 +10,17 @@ import { TRASH_BIN_ADAPTER_FIXTURE, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { LocationConflictModal } from '../../../organisms/Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' -import { useDeckConfigurationCompatibility } from '../../../resources/deck_configuration/hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { LocationConflictModal } from '/app/organisms/LocationConflictModal' +import { useDeckConfigurationCompatibility } from '/app/resources/deck_configuration/hooks' import { FixtureTable } from '../FixtureTable' -import { getLocalRobot } from '../../../redux/discovery' -import { mockConnectedRobot } from '../../../redux/discovery/__fixtures__' +import { getLocalRobot } from '/app/redux/discovery' +import { mockConnectedRobot } from '/app/redux/discovery/__fixtures__' -vi.mock('../../../redux/discovery') -vi.mock('../../../resources/deck_configuration/hooks') -vi.mock( - '../../../organisms/Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' -) +vi.mock('/app/redux/discovery') +vi.mock('/app/resources/deck_configuration/hooks') +vi.mock('/app/organisms/LocationConflictModal') const mockSetSetupScreen = vi.fn() const mockSetCutoutId = vi.fn() diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapView.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapView.test.tsx similarity index 89% rename from app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapView.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapView.test.tsx index e0551b3f4f3..d31a0312d02 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapView.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/ModulesAndDeckMapView.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, beforeEach, afterEach } from 'vitest' import { screen } from '@testing-library/react' @@ -8,18 +8,17 @@ import { getSimplestDeckConfigForProtocol, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ModulesAndDeckMapView } from '../ModulesAndDeckMapView' vi.mock('@opentrons/components/src/hardware-sim/BaseDeck') vi.mock('@opentrons/api-client') vi.mock('@opentrons/shared-data/js/helpers/getSimplestFlexDeckConfig') -vi.mock('../../../redux/config') -vi.mock('../../Devices/hooks') -vi.mock('../../../resources/deck_configuration/utils') -vi.mock('../../Devices/ModuleInfo') -vi.mock('../../Devices/ProtocolRun/utils/getLabwareRenderInfo') +vi.mock('/app/redux/config') +vi.mock('/app/resources/deck_configuration/utils') +vi.mock('/app/molecules/ModuleInfo') +vi.mock('/app/transformations/analysis') const mockRunId = 'mockRunId' const PROTOCOL_ANALYSIS = { diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx similarity index 86% rename from app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx index 9f9cec5524d..010a1f43f70 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/ProtocolSetupModulesAndDeck.test.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { fireEvent, screen, waitFor } from '@testing-library/react' import { vi, it, expect, describe, beforeEach, afterEach } from 'vitest' import { when } from 'vitest-when' @@ -11,51 +10,47 @@ import { getDeckDefFromRobotType, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useChainLiveCommands } from '../../../resources/runs' -import { mockRobotSideAnalysis } from '../../../molecules/Command/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { - useAttachedModules, + useChainLiveCommands, + useRunStatus, + useMostRecentCompletedAnalysis, useRunCalibrationStatus, -} from '../../Devices/hooks' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { getProtocolModulesInfo } from '../../Devices/ProtocolRun/utils/getProtocolModulesInfo' -import { mockApiHeaterShaker } from '../../../redux/modules/__fixtures__' -import { mockProtocolModuleInfo } from '../../ProtocolSetupInstruments/__fixtures__' -import { getLocalRobot } from '../../../redux/discovery' -import { mockConnectedRobot } from '../../../redux/discovery/__fixtures__' +} from '/app/resources/runs' +import { mockRobotSideAnalysis } from '/app/molecules/Command/__fixtures__' +import { useAttachedModules } from '/app/resources/modules' import { + getProtocolModulesInfo, getAttachedProtocolModuleMatches, - getUnmatchedModulesForProtocol, -} from '../utils' -import { LocationConflictModal } from '../../Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' -import { ModuleWizardFlows } from '../../ModuleWizardFlows' +} from '/app/transformations/analysis' +import { mockApiHeaterShaker } from '/app/redux/modules/__fixtures__' +import { mockProtocolModuleInfo } from '../../ProtocolSetupInstruments/__fixtures__' +import { getLocalRobot } from '/app/redux/discovery' +import { mockConnectedRobot } from '/app/redux/discovery/__fixtures__' +import { getUnmatchedModulesForProtocol } from '../utils' +import { LocationConflictModal } from '/app/organisms/LocationConflictModal' +import { ModuleWizardFlows } from '/app/organisms/ModuleWizardFlows' import { SetupInstructionsModal } from '../SetupInstructionsModal' import { FixtureTable } from '../FixtureTable' import { ModulesAndDeckMapView } from '../ModulesAndDeckMapView' import { ProtocolSetupModulesAndDeck } from '..' -import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' -import { useRunStatus } from '../../RunTimeControl/hooks' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' import type { CutoutConfig, DeckConfiguration } from '@opentrons/shared-data' import type { UseQueryResult } from 'react-query' -vi.mock('../../../resources/runs') -vi.mock('../../../redux/discovery') -vi.mock('../../../organisms/Devices/hooks') -vi.mock('../../../resources/deck_configuration') -vi.mock( - '../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' -) -vi.mock('../../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo') +vi.mock('/app/resources/runs') +vi.mock('/app/resources/modules') +vi.mock('/app/redux/discovery') +vi.mock('/app/resources/deck_configuration') +vi.mock('/app/transformations/analysis') vi.mock('../utils') vi.mock('../SetupInstructionsModal') -vi.mock('../../ModuleWizardFlows') +vi.mock('/app/organisms/ModuleWizardFlows') vi.mock('../FixtureTable') -vi.mock('../../Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal') +vi.mock('/app/organisms/LocationConflictModal') vi.mock('../ModulesAndDeckMapView') -vi.mock('../../RunTimeControl/hooks') const ROBOT_NAME = 'otie' const RUN_ID = '1' diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/SetupInstructionsModal.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/SetupInstructionsModal.test.tsx similarity index 91% rename from app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/SetupInstructionsModal.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/SetupInstructionsModal.test.tsx index 06db135f3f6..8f6f4c01739 100644 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/SetupInstructionsModal.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/SetupInstructionsModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, beforeEach, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { SetupInstructionsModal } from '../SetupInstructionsModal' diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx new file mode 100644 index 00000000000..c8d92ad5530 --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx @@ -0,0 +1,74 @@ +import { describe, it, expect } from 'vitest' +import { getModuleDef2 } from '@opentrons/shared-data' + +import { mockTemperatureModuleGen2 } from '/app/redux/modules/__fixtures__' +import { getUnmatchedModulesForProtocol } from '../utils' + +const temperatureProtocolModule = { + moduleId: 'mockTempModuleId', + x: 0, + y: 0, + z: 0, + moduleDef: getModuleDef2('temperatureModuleV2'), + nestedLabwareDef: null, + nestedLabwareId: null, + nestedLabwareDisplayName: null, + protocolLoadOrder: 0, + slotName: 'D1', +} + +const magneticProtocolModule = { + moduleId: 'mockMagneticModuleId', + x: 0, + y: 0, + z: 0, + moduleDef: getModuleDef2('magneticModuleV2'), + nestedLabwareDef: null, + nestedLabwareId: null, + nestedLabwareDisplayName: null, + protocolLoadOrder: 0, + slotName: 'D1', +} + +describe('getUnmatchedModulesForProtocol', () => { + it('returns no missing module ids or remaining attached modules when no modules required or attached', () => { + const result = getUnmatchedModulesForProtocol([], []) + expect(result).toEqual({ + missingModuleIds: [], + remainingAttachedModules: [], + }) + }) + + it('returns no missing module ids or remaining attached modules when attached modules match', () => { + const result = getUnmatchedModulesForProtocol( + [mockTemperatureModuleGen2], + [temperatureProtocolModule] + ) + expect(result).toEqual({ + missingModuleIds: [], + remainingAttachedModules: [], + }) + }) + + it('returns missing module ids when protocol modules missing', () => { + const result = getUnmatchedModulesForProtocol( + [], + [temperatureProtocolModule, magneticProtocolModule] + ) + expect(result).toEqual({ + missingModuleIds: ['mockTempModuleId', 'mockMagneticModuleId'], + remainingAttachedModules: [], + }) + }) + + it('returns remaining attached modules when protocol modules and attached modules do not match', () => { + const result = getUnmatchedModulesForProtocol( + [mockTemperatureModuleGen2], + [magneticProtocolModule] + ) + expect(result).toEqual({ + missingModuleIds: ['mockMagneticModuleId'], + remainingAttachedModules: [mockTemperatureModuleGen2], + }) + }) +}) diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/index.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/index.ts new file mode 100644 index 00000000000..7265f1a9b8c --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/index.ts @@ -0,0 +1,2 @@ +export * from './ProtocolSetupModulesAndDeck' +export { getUnmatchedModulesForProtocol } from './utils' diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/utils.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/utils.ts new file mode 100644 index 00000000000..11862dcca94 --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupModulesAndDeck/utils.ts @@ -0,0 +1,55 @@ +import { + NON_CONNECTING_MODULE_TYPES, + getModuleType, +} from '@opentrons/shared-data' + +import type { ProtocolModuleInfo } from '/app/transformations/analysis' +import type { AttachedModule } from '/app/redux/modules/types' + +interface UnmatchedModuleResults { + missingModuleIds: string[] + remainingAttachedModules: AttachedModule[] +} + +// get requested protocol module ids that do not map to a robot-attached module of the requested model +// some logic copied from useUnmatchedModulesForProtocol +export function getUnmatchedModulesForProtocol( + attachedModules: AttachedModule[], + protocolModulesInfo: ProtocolModuleInfo[] +): UnmatchedModuleResults { + const { + missingModuleIds, + remainingAttachedModules, + } = protocolModulesInfo.reduce( + (acc, module) => { + const { model, compatibleWith } = module.moduleDef + // Skip matching any modules that don't require an electronic robot connection + if (NON_CONNECTING_MODULE_TYPES.includes(getModuleType(model))) return acc + // for this required module, find a remaining (unmatched) attached module of the requested model + const moduleTypeMatchIndex = acc.remainingAttachedModules.findIndex( + attachedModule => { + return ( + model === attachedModule.moduleModel || + compatibleWith.includes(attachedModule.moduleModel) + ) + } + ) + return moduleTypeMatchIndex !== -1 + ? { + ...acc, + // remove matched module from remaining modules list + remainingAttachedModules: acc.remainingAttachedModules.filter( + (_remainingAttachedModule, index) => + index !== moduleTypeMatchIndex + ), + } + : { + ...acc, + // append unmatchable module to list of requested modules that are missing a physical match + missingModuleIds: [...acc.missingModuleIds, module.moduleId], + } + }, + { missingModuleIds: [], remainingAttachedModules: attachedModules } + ) + return { missingModuleIds, remainingAttachedModules } +} diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupOffsets/index.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupOffsets/index.tsx new file mode 100644 index 00000000000..3082df45a2a --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupOffsets/index.tsx @@ -0,0 +1,143 @@ +import type * as React from 'react' +import { useTranslation } from 'react-i18next' +import { + Chip, + DIRECTION_COLUMN, + DIRECTION_ROW, + Flex, + InfoScreen, + JUSTIFY_SPACE_BETWEEN, + SPACING, + StyledText, +} from '@opentrons/components' + +import type { LabwareOffset } from '@opentrons/api-client' +import { useToaster } from '/app/organisms/ToasterOven' +import { ODDBackButton } from '/app/molecules/ODDBackButton' +import { FloatingActionButton, SmallButton } from '/app/atoms/buttons' +import type { SetupScreens } from '../types' +import { TerseOffsetTable } from '/app/organisms/LabwarePositionCheck/ResultsSummary' +import { getLabwareDefinitionsFromCommands } from '/app/local-resources/labware' +import { + useNotifyRunQuery, + useMostRecentCompletedAnalysis, +} from '/app/resources/runs' +import { getLatestCurrentOffsets } from '/app/transformations/runs' + +export interface ProtocolSetupOffsetsProps { + runId: string + setSetupScreen: React.Dispatch> + lpcDisabledReason: string | null + launchLPC: () => void + LPCWizard: JSX.Element | null + isConfirmed: boolean + setIsConfirmed: (confirmed: boolean) => void +} + +export function ProtocolSetupOffsets({ + runId, + setSetupScreen, + isConfirmed, + setIsConfirmed, + launchLPC, + lpcDisabledReason, + LPCWizard, +}: ProtocolSetupOffsetsProps): JSX.Element { + const { t } = useTranslation('protocol_setup') + const { makeSnackbar } = useToaster() + const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) + const makeDisabledReasonSnackbar = (): void => { + if (lpcDisabledReason != null) { + makeSnackbar(lpcDisabledReason) + } + } + + const labwareDefinitions = getLabwareDefinitionsFromCommands( + mostRecentAnalysis?.commands ?? [] + ) + const { data: runRecord } = useNotifyRunQuery(runId, { staleTime: Infinity }) + const currentOffsets = runRecord?.data?.labwareOffsets ?? [] + const sortedOffsets: LabwareOffset[] = + currentOffsets.length > 0 + ? currentOffsets + .map(offset => ({ + ...offset, + // convert into date to sort + createdAt: new Date(offset.createdAt), + })) + .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()) + .map(offset => ({ + ...offset, + // convert back into string + createdAt: offset.createdAt.toISOString(), + })) + : [] + const nonIdentityOffsets = getLatestCurrentOffsets(sortedOffsets) + return ( + <> + {LPCWizard} + {LPCWizard == null && ( + <> + + { + setSetupScreen('prepare to run') + }} + /> + {isConfirmed ? ( + + ) : ( + { + setIsConfirmed(true) + setSetupScreen('prepare to run') + }} + buttonCategory="rounded" + /> + )} + + + {nonIdentityOffsets.length > 0 ? ( + <> + + {t('applied_labware_offset_data')} + + + + ) : ( + + )} + + { + if (lpcDisabledReason != null) { + makeDisabledReasonSnackbar() + } else { + launchLPC() + } + }} + /> + + )} + + ) +} diff --git a/app/src/organisms/ProtocolSetupParameters/AnalysisFailed.stories.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/AnalysisFailed.stories.tsx similarity index 84% rename from app/src/organisms/ProtocolSetupParameters/AnalysisFailed.stories.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/AnalysisFailed.stories.tsx index 2b865e5fb9c..8967fc1f035 100644 --- a/app/src/organisms/ProtocolSetupParameters/AnalysisFailed.stories.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/AnalysisFailed.stories.tsx @@ -1,6 +1,6 @@ -import * as React from 'react' +import type * as React from 'react' -import { VIEWPORT } from '../../../../components/src/ui-style-constants' +import { VIEWPORT } from '../../../../../../components/src/ui-style-constants' import { AnalysisFailedModal } from './AnalysisFailedModal' import type { Story, Meta } from '@storybook/react' diff --git a/app/src/organisms/ProtocolSetupParameters/AnalysisFailedModal.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/AnalysisFailedModal.tsx similarity index 92% rename from app/src/organisms/ProtocolSetupParameters/AnalysisFailedModal.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/AnalysisFailedModal.tsx index 059a59d5532..ac575e6fe4f 100644 --- a/app/src/organisms/ProtocolSetupParameters/AnalysisFailedModal.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/AnalysisFailedModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' import { @@ -11,10 +10,10 @@ import { } from '@opentrons/components' import { useDismissCurrentRunMutation } from '@opentrons/react-api-client' -import { SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' +import { SmallButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' interface AnalysisFailedModalProps { errors: string[] diff --git a/app/src/organisms/ProtocolSetupParameters/ChooseCsvFile.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseCsvFile.tsx similarity index 92% rename from app/src/organisms/ProtocolSetupParameters/ChooseCsvFile.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseCsvFile.tsx index 3126f0ffdff..bcaaca86182 100644 --- a/app/src/organisms/ProtocolSetupParameters/ChooseCsvFile.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseCsvFile.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect, Fragment } from 'react' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' import { css } from 'styled-components' @@ -11,15 +11,15 @@ import { DIRECTION_ROW, Flex, LegacyStyledText, - SPACING, RadioButton, + SPACING, truncateString, TYPOGRAPHY, } from '@opentrons/components' import { useAllCsvFilesQuery } from '@opentrons/react-api-client' -import { getShellUpdateDataFiles } from '../../redux/shell' -import { ChildNavigation } from '../ChildNavigation' +import { getShellUpdateDataFiles } from '/app/redux/shell' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' import { EmptyFile } from './EmptyFile' import type { @@ -29,7 +29,7 @@ import type { import type { CsvFileData } from '@opentrons/api-client' const MAX_CHARS = 52 -const CSV_FILENAME_BREAK_POINT = 46 +const CSV_FILENAME_BREAK_POINT = 42 interface ChooseCsvFileProps { protocolId: string handleGoBack: () => void @@ -78,7 +78,7 @@ export function ChooseCsvFile({ const [ csvFileSelected, setCsvFileSelected, - ] = React.useState(initialFileObject) + ] = useState(initialFileObject) const handleBackButton = (): void => { if (!isEqual(csvFileSelected, initialFileObject)) { @@ -87,7 +87,7 @@ export function ChooseCsvFile({ handleGoBack() } - React.useEffect(() => { + useEffect(() => { if (csvFilesOnUSB.length === 0) { setCsvFileSelected({}) } @@ -119,7 +119,7 @@ export function ChooseCsvFile({ {csvFilesOnRobot.length !== 0 ? ( csvFilesOnRobot.map((csv: CsvFileData) => ( - + - + )) ) : ( @@ -150,7 +150,7 @@ export function ChooseCsvFile({ sortedCsvFilesOnUSB.map(csvFilePath => { const fileName = last(csvFilePath.split('/')) return ( - + {csvFilePath.length !== 0 && fileName !== undefined ? ( ) : null} - + ) }) ) : ( diff --git a/app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseEnum.tsx similarity index 94% rename from app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseEnum.tsx index 0ad856f5981..0576d1d8c78 100644 --- a/app/src/organisms/ProtocolSetupParameters/ChooseEnum.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseEnum.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, @@ -9,8 +8,8 @@ import { RadioButton, TYPOGRAPHY, } from '@opentrons/components' -import { useToaster } from '../ToasterOven' -import { ChildNavigation } from '../ChildNavigation' +import { useToaster } from '../../../ToasterOven' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' import type { ChoiceParameter } from '@opentrons/shared-data' interface ChooseEnumProps { diff --git a/app/src/organisms/ProtocolSetupParameters/ChooseNumber.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseNumber.tsx similarity index 91% rename from app/src/organisms/ProtocolSetupParameters/ChooseNumber.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseNumber.tsx index ed6918f9aa8..cf2ae445e1c 100644 --- a/app/src/organisms/ProtocolSetupParameters/ChooseNumber.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ChooseNumber.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useRef, useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { @@ -11,9 +11,9 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { useToaster } from '../ToasterOven' -import { ChildNavigation } from '../ChildNavigation' -import { NumericalKeyboard } from '../../atoms/SoftwareKeyboard' +import { useToaster } from '/app/organisms/ToasterOven' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { NumericalKeyboard } from '/app/atoms/SoftwareKeyboard' import type { NumberParameter } from '@opentrons/shared-data' interface ChooseNumberProps { @@ -30,16 +30,14 @@ export function ChooseNumber({ const { makeSnackbar } = useToaster() const { i18n, t } = useTranslation(['protocol_setup', 'shared']) - const keyboardRef = React.useRef(null) - const [paramValue, setParamValue] = React.useState( - String(parameter.value) - ) + const keyboardRef = useRef(null) + const [paramValue, setParamValue] = useState(String(parameter.value)) // We need to arbitrarily set the value of the keyboard to a string the // same length as the initial parameter value (as string) when the component mounts // so that the delete button operates properly on the exisiting input field value. - const [prevKeyboardValue, setPrevKeyboardValue] = React.useState('') - React.useEffect(() => { + const [prevKeyboardValue, setPrevKeyboardValue] = useState('') + useEffect(() => { const arbitraryInput = new Array(paramValue).join('*') // @ts-expect-error keyboard should expose for `setInput` method keyboardRef.current?.setInput(arbitraryInput) diff --git a/app/src/organisms/ProtocolSetupParameters/EmptyFile.stories.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/EmptyFile.stories.tsx similarity index 94% rename from app/src/organisms/ProtocolSetupParameters/EmptyFile.stories.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/EmptyFile.stories.tsx index 45d0fa652fa..5932aca9031 100644 --- a/app/src/organisms/ProtocolSetupParameters/EmptyFile.stories.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/EmptyFile.stories.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Flex, SPACING, VIEWPORT } from '@opentrons/components' import { EmptyFile as EmptyFileComponent } from './EmptyFile' diff --git a/app/src/organisms/ProtocolSetupParameters/EmptyFile.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/EmptyFile.tsx similarity index 97% rename from app/src/organisms/ProtocolSetupParameters/EmptyFile.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/EmptyFile.tsx index 888a0f0f91b..413042caad8 100644 --- a/app/src/organisms/ProtocolSetupParameters/EmptyFile.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/EmptyFile.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ProtocolSetupParameters.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ProtocolSetupParameters.tsx new file mode 100644 index 00000000000..ec4df679049 --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ProtocolSetupParameters.tsx @@ -0,0 +1,374 @@ +import { useState, Fragment } from 'react' +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' +import { + useCreateProtocolAnalysisMutation, + useCreateRunMutation, + useHost, + useUploadCsvFileMutation, +} from '@opentrons/react-api-client' +import { useQueryClient } from 'react-query' +import { + ALIGN_CENTER, + DIRECTION_COLUMN, + Flex, + SPACING, +} from '@opentrons/components' +import { + formatRunTimeParameterValue, + sortRuntimeParameters, +} from '@opentrons/shared-data' + +import { + getRunTimeParameterFilesForRun, + getRunTimeParameterValuesForRun, +} from '/app/transformations/runs' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { ResetValuesModal } from './ResetValuesModal' +import { ChooseEnum } from './ChooseEnum' +import { ChooseNumber } from './ChooseNumber' +import { ChooseCsvFile } from './ChooseCsvFile' +import { useToaster } from '/app/organisms/ToasterOven' +import { ProtocolSetupStep } from '../ProtocolSetupStep' +import type { + CompletedProtocolAnalysis, + ChoiceParameter, + CsvFileParameter, + NumberParameter, + RunTimeParameter, + ValueRunTimeParameter, + CsvFileParameterFileData, +} from '@opentrons/shared-data' +import type { ProtocolSetupStepStatus } from '../ProtocolSetupStep' +import type { FileData, LabwareOffsetCreateData } from '@opentrons/api-client' + +interface ProtocolSetupParametersProps { + protocolId: string + runTimeParameters: RunTimeParameter[] + labwareOffsets?: LabwareOffsetCreateData[] + mostRecentAnalysis?: CompletedProtocolAnalysis | null +} + +export function ProtocolSetupParameters({ + protocolId, + labwareOffsets, + runTimeParameters, + mostRecentAnalysis, +}: ProtocolSetupParametersProps): JSX.Element { + const { t } = useTranslation('protocol_setup') + const navigate = useNavigate() + const host = useHost() + const queryClient = useQueryClient() + const [ + chooseValueScreen, + setChooseValueScreen, + ] = useState(null) + const [ + showNumericalInputScreen, + setShowNumericalInputScreen, + ] = useState(null) + const [ + chooseCsvFileScreen, + setChooseCsvFileScreen, + ] = useState(null) + const [resetValuesModal, showResetValuesModal] = useState(false) + const [startSetup, setStartSetup] = useState(false) + const [runTimeParametersOverrides, setRunTimeParametersOverrides] = useState< + RunTimeParameter[] + >( + runTimeParameters.map(parameter => + parameter.type === 'csv_file' + ? { ...parameter, file: null } + : // TODO (nd: 06/13/2024) create individual ChoiceParameter types for correct narrowing + // eslint-disable-next-line + ({ ...parameter, value: parameter.default } as ValueRunTimeParameter) + ) + ) + + const hasMissingFileParam = + runTimeParametersOverrides?.some((parameter): boolean => { + if (parameter.type !== 'csv_file') { + return false + } + + if (parameter.file == null) { + return true + } + + return ( + parameter.file.id == null && + parameter.file.file == null && + parameter.file.filePath == null + ) + }) ?? false + + const { makeSnackbar } = useToaster() + + const updateParameters = ( + value: boolean | string | number | CsvFileParameterFileData, + variableName: string + ): void => { + const updatedParameters = runTimeParametersOverrides.map(parameter => { + if (parameter.variableName === variableName) { + return parameter.type === 'csv_file' + ? { ...parameter, file: value } + : { ...parameter, value } + } + return parameter + }) + setRunTimeParametersOverrides(updatedParameters as RunTimeParameter[]) + if (chooseValueScreen && chooseValueScreen.variableName === variableName) { + const updatedParameter = updatedParameters.find( + parameter => parameter.variableName === variableName + ) + if (updatedParameter != null && 'choices' in updatedParameter) { + setChooseValueScreen(updatedParameter as ChoiceParameter) + } + } + if ( + showNumericalInputScreen && + showNumericalInputScreen.variableName === variableName + ) { + const updatedParameter = updatedParameters.find( + parameter => parameter.variableName === variableName + ) + if (updatedParameter != null) { + setShowNumericalInputScreen(updatedParameter as NumberParameter) + } + } + if ( + chooseCsvFileScreen && + chooseCsvFileScreen.variableName === variableName + ) { + const updatedParameter = updatedParameters.find( + parameter => parameter.variableName === variableName + ) + if (updatedParameter != null && updatedParameter.type === 'csv_file') { + setChooseCsvFileScreen(updatedParameter as CsvFileParameter) + } + } + } + + const { + createProtocolAnalysis, + isLoading: isAnalysisLoading, + } = useCreateProtocolAnalysisMutation(protocolId, host) + + const { uploadCsvFile } = useUploadCsvFileMutation({}, host) + + const { createRun, isLoading: isRunLoading } = useCreateRunMutation({ + onSuccess: data => { + queryClient.invalidateQueries([host, 'runs']).catch((e: Error) => { + console.error(`could not invalidate runs cache: ${e.message}`) + }) + }, + }) + const handleConfirmValues = (): void => { + if (hasMissingFileParam) { + makeSnackbar(t('protocol_requires_csv') as string) + } else { + const dataFilesForProtocolMap = runTimeParametersOverrides.reduce< + Record + >((acc, parameter) => { + // create {variableName: FileData} map for sending to /dataFiles endpoint + if ( + parameter.type === 'csv_file' && + parameter.file?.id == null && + parameter.file?.file != null + ) { + return { [parameter.variableName]: parameter.file.file } + } else if ( + parameter.type === 'csv_file' && + parameter.file?.id == null && + parameter.file?.filePath != null + ) { + return { [parameter.variableName]: parameter.file.filePath } + } + return acc + }, {}) + void Promise.all( + Object.entries(dataFilesForProtocolMap).map(([key, fileData]) => { + const fileResponse = uploadCsvFile(fileData) + const varName = Promise.resolve(key) + return Promise.all([fileResponse, varName]) + }) + ).then(responseTuples => { + const mappedResolvedCsvVariableToFileId = responseTuples.reduce< + Record + >((acc, [uploadedFileResponse, variableName]) => { + return { ...acc, [variableName]: uploadedFileResponse.data.id } + }, {}) + const runTimeParameterValues = getRunTimeParameterValuesForRun( + runTimeParametersOverrides + ) + const runTimeParameterFiles = getRunTimeParameterFilesForRun( + runTimeParametersOverrides, + mappedResolvedCsvVariableToFileId + ) + setStartSetup(true) + createProtocolAnalysis( + { + protocolKey: protocolId, + runTimeParameterValues, + runTimeParameterFiles, + }, + { + onSuccess: () => { + createRun({ + protocolId, + labwareOffsets, + runTimeParameterValues, + runTimeParameterFiles, + }) + }, + } + ) + }) + } + } + + const handleSetParameter = (parameter: RunTimeParameter): void => { + if ('choices' in parameter) { + setChooseValueScreen(parameter) + } else if (parameter.type === 'bool') { + updateParameters(!parameter.value, parameter.variableName) + } else if (parameter.type === 'int' || parameter.type === 'float') { + setShowNumericalInputScreen(parameter) + } else if (parameter.type === 'csv_file') { + setChooseCsvFileScreen(parameter) + } else { + // bad param + console.error('error: bad param. not expected to reach this') + } + } + + let children = ( + <> + { + navigate(-1) + }} + onClickButton={handleConfirmValues} + buttonText={t('confirm_values')} + ariaDisabled={hasMissingFileParam} + buttonIsDisabled={hasMissingFileParam} + iconName={ + isRunLoading || isAnalysisLoading || startSetup + ? 'ot-spinner' + : undefined + } + iconPlacement="startIcon" + secondaryButtonProps={{ + buttonType: 'tertiaryLowLight', + buttonText: t('restore_defaults'), + disabled: isRunLoading || isAnalysisLoading || startSetup, + onClick: () => { + showResetValuesModal(true) + }, + }} + /> + + {sortRuntimeParameters(runTimeParametersOverrides).map( + (parameter, index) => { + let detail: string = '' + let setupStatus: ProtocolSetupStepStatus + if (parameter.type === 'csv_file') { + if (parameter.file?.fileName == null) { + detail = t('required') + setupStatus = 'not ready' + } else { + detail = parameter.file.fileName + setupStatus = 'ready' + } + } else { + detail = formatRunTimeParameterValue(parameter, t) + setupStatus = 'inform' + } + return ( + + { + handleSetParameter(parameter) + }} + detail={detail} + description={ + parameter.type === 'csv_file' ? null : parameter.description + } + fontSize="h4" + disabled={startSetup} + /> + + ) + } + )} + + + ) + + if (chooseCsvFileScreen != null) { + children = ( + { + setChooseCsvFileScreen(null) + }} + parameter={chooseCsvFileScreen} + setParameter={updateParameters} + /> + ) + } + if (chooseValueScreen != null) { + children = ( + { + setChooseValueScreen(null) + }} + parameter={chooseValueScreen} + setParameter={updateParameters} + rawValue={chooseValueScreen.value} + /> + ) + } + if (showNumericalInputScreen != null) { + children = ( + { + setShowNumericalInputScreen(null) + }} + parameter={showNumericalInputScreen} + setParameter={updateParameters} + /> + ) + } + + return ( + <> + {resetValuesModal ? ( + { + showResetValuesModal(false) + }} + /> + ) : null} + {children} + + ) +} diff --git a/app/src/organisms/ProtocolSetupParameters/ResetValuesModal.stories.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ResetValuesModal.stories.tsx similarity index 93% rename from app/src/organisms/ProtocolSetupParameters/ResetValuesModal.stories.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ResetValuesModal.stories.tsx index 975d8104a26..3becd83d063 100644 --- a/app/src/organisms/ProtocolSetupParameters/ResetValuesModal.stories.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ResetValuesModal.stories.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { VIEWPORT } from '@opentrons/components' import { ResetValuesModal } from './ResetValuesModal' diff --git a/app/src/organisms/ProtocolSetupParameters/ResetValuesModal.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ResetValuesModal.tsx similarity index 90% rename from app/src/organisms/ProtocolSetupParameters/ResetValuesModal.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ResetValuesModal.tsx index 2ecc6021618..ed3e70cd7df 100644 --- a/app/src/organisms/ProtocolSetupParameters/ResetValuesModal.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ResetValuesModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -11,11 +10,11 @@ import { LegacyStyledText, } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' +import { SmallButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' import type { RunTimeParameter } from '@opentrons/shared-data' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' interface ResetValuesModalProps { runTimeParametersOverrides: RunTimeParameter[] diff --git a/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ViewOnlyParameters.tsx similarity index 92% rename from app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ViewOnlyParameters.tsx index 88d0e448f6a..1946d122848 100644 --- a/app/src/organisms/ProtocolSetupParameters/ViewOnlyParameters.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/ViewOnlyParameters.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' @@ -18,11 +18,11 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { ChildNavigation } from '../ChildNavigation' -import { useToaster } from '../ToasterOven' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { useToaster } from '/app/organisms/ToasterOven' -import type { SetupScreens } from '../../pages/ProtocolSetup' +import type { SetupScreens } from '../types' export interface ViewOnlyParametersProps { runId: string diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/AnalysisFailedModal.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/AnalysisFailedModal.test.tsx similarity index 94% rename from app/src/organisms/ProtocolSetupParameters/__tests__/AnalysisFailedModal.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/AnalysisFailedModal.test.tsx index 983a658fe64..ac43f26d621 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/AnalysisFailedModal.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/AnalysisFailedModal.test.tsx @@ -1,11 +1,11 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, beforeEach, expect } from 'vitest' import { when } from 'vitest-when' import { fireEvent, screen } from '@testing-library/react' import { useDismissCurrentRunMutation } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { AnalysisFailedModal } from '../AnalysisFailedModal' import type { NavigateFunction } from 'react-router-dom' diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/ChooseCsvFile.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseCsvFile.test.tsx similarity index 92% rename from app/src/organisms/ProtocolSetupParameters/__tests__/ChooseCsvFile.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseCsvFile.test.tsx index 404ee1059f9..2f365fa5fbc 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/ChooseCsvFile.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseCsvFile.test.tsx @@ -1,23 +1,23 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, beforeEach, vi, expect } from 'vitest' import { screen, fireEvent } from '@testing-library/react' import { when } from 'vitest-when' import { useAllCsvFilesQuery } from '@opentrons/react-api-client' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' -import { mockConnectedRobot } from '../../../redux/discovery/__fixtures__' -import { getLocalRobot } from '../../../redux/discovery' -import { getShellUpdateDataFiles } from '../../../redux/shell' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { mockConnectedRobot } from '/app/redux/discovery/__fixtures__' +import { getLocalRobot } from '/app/redux/discovery' +import { getShellUpdateDataFiles } from '/app/redux/shell' import { EmptyFile } from '../EmptyFile' import { ChooseCsvFile } from '../ChooseCsvFile' import type { CsvFileParameter } from '@opentrons/shared-data' vi.mock('@opentrons/react-api-client') -vi.mock('../../../redux/discovery') -vi.mock('../../../redux/shell') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/shell') vi.mock('../EmptyFile') const mockHandleGoBack = vi.fn() diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/ChooseEnum.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseEnum.test.tsx similarity index 93% rename from app/src/organisms/ProtocolSetupParameters/__tests__/ChooseEnum.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseEnum.test.tsx index 2af4dc11a3c..a65c760d544 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/ChooseEnum.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseEnum.test.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import type * as React from 'react' import { it, describe, beforeEach, vi, expect } from 'vitest' import { fireEvent, screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' import { COLORS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ChooseEnum } from '../ChooseEnum' -vi.mocked('../../ToasterOven') +vi.mocked('../../../../ToasterOven') const render = (props: React.ComponentProps) => { return renderWithProviders(, { i18nInstance: i18n, diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/ChooseNumber.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseNumber.test.tsx similarity index 92% rename from app/src/organisms/ProtocolSetupParameters/__tests__/ChooseNumber.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseNumber.test.tsx index 1d312bc36a5..611c0e124fc 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/ChooseNumber.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ChooseNumber.test.tsx @@ -1,16 +1,16 @@ -import * as React from 'react' +import type * as React from 'react' import { it, describe, beforeEach, vi, expect } from 'vitest' import { fireEvent, screen } from '@testing-library/react' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useToaster } from '../../ToasterOven' -import { mockRunTimeParameterData } from '../../../pages/ProtocolDetails/fixtures' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useToaster } from '/app/organisms/ToasterOven' +import { mockRunTimeParameterData } from '../../__fixtures__' import { ChooseNumber } from '../ChooseNumber' import type { NumberParameter } from '@opentrons/shared-data' -vi.mock('../../ToasterOven') +vi.mock('/app/organisms/ToasterOven') const mockHandleGoBack = vi.fn() const mockIntNumberParameterData = mockRunTimeParameterData[5] as NumberParameter diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/EmptyFile.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/EmptyFile.test.tsx similarity index 90% rename from app/src/organisms/ProtocolSetupParameters/__tests__/EmptyFile.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/EmptyFile.test.tsx index 898a38bbdf1..386667d8430 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/EmptyFile.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/EmptyFile.test.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { describe, it, expect } from 'vitest' import { screen } from '@testing-library/react' @@ -11,8 +10,8 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { EmptyFile } from '../EmptyFile' diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx similarity index 94% rename from app/src/organisms/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx index d423c0aab1e..18a01391711 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ProtocolSetupParameters.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { it, describe, beforeEach, vi, expect } from 'vitest' import { fireEvent, screen } from '@testing-library/react' @@ -10,13 +10,13 @@ import { } from '@opentrons/react-api-client' import { COLORS } from '@opentrons/components' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { ChooseEnum } from '../ChooseEnum' import { ChooseNumber } from '../ChooseNumber' import { ChooseCsvFile } from '../ChooseCsvFile' -import { mockRunTimeParameterData } from '../../../pages/ProtocolDetails/fixtures' -import { useToaster } from '../../ToasterOven' +import { mockRunTimeParameterData } from '../../__fixtures__' +import { useToaster } from '/app/organisms/ToasterOven' import { ProtocolSetupParameters } from '..' import type { NavigateFunction } from 'react-router-dom' @@ -28,10 +28,10 @@ const mockNavigate = vi.fn() vi.mock('../ChooseEnum') vi.mock('../ChooseNumber') vi.mock('../ChooseCsvFile') -vi.mock('../../../redux/config') -vi.mock('../../ToasterOven') +vi.mock('/app/redux/config') +vi.mock('/app/organisms/ToasterOven') vi.mock('@opentrons/react-api-client') -vi.mock('../../LabwarePositionCheck/useMostRecentCompletedAnalysis') +vi.mock('/app/resources/runs') vi.mock('react-router-dom', async importOriginal => { const reactRouterDom = await importOriginal() return { @@ -39,7 +39,7 @@ vi.mock('react-router-dom', async importOriginal => { useNavigate: () => mockNavigate, } }) -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') const MOCK_HOST_CONFIG: HostConfig = { hostname: 'MOCK_HOST' } const mockCreateProtocolAnalysis = vi.fn() diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/ResetValuesModal.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ResetValuesModal.test.tsx similarity index 92% rename from app/src/organisms/ProtocolSetupParameters/__tests__/ResetValuesModal.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ResetValuesModal.test.tsx index 46659717788..4e263f9984b 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/ResetValuesModal.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ResetValuesModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, vi, beforeEach, expect } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ResetValuesModal } from '../ResetValuesModal' import type { RunTimeParameter } from '@opentrons/shared-data' diff --git a/app/src/organisms/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx similarity index 80% rename from app/src/organisms/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx index 6e20fe65658..aed74fea585 100644 --- a/app/src/organisms/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/__tests__/ViewOnlyParameters.test.tsx @@ -1,16 +1,16 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { it, describe, beforeEach, vi, expect } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' -import { useMostRecentCompletedAnalysis } from '../../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useToaster } from '../../ToasterOven' -import { mockRunTimeParameterData } from '../../../pages/ProtocolDetails/fixtures' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { useMostRecentCompletedAnalysis } from '/app/resources/runs' +import { useToaster } from '/app/organisms/ToasterOven' +import { mockRunTimeParameterData } from '../../__fixtures__' import { ViewOnlyParameters } from '../ViewOnlyParameters' -vi.mock('../../LabwarePositionCheck/useMostRecentCompletedAnalysis') -vi.mock('../../ToasterOven') +vi.mock('/app/resources/runs') +vi.mock('/app/organisms/ToasterOven') const RUN_ID = 'mockId' const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/index.ts b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/index.ts new file mode 100644 index 00000000000..ecca4bd1516 --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupParameters/index.ts @@ -0,0 +1,3 @@ +export * from './ProtocolSetupParameters' +export * from './AnalysisFailedModal' +export * from './ViewOnlyParameters' diff --git a/app/src/organisms/OnDeviceDisplay/ProtocolSetup/ProtocolSetupSkeleton.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupSkeleton.tsx similarity index 91% rename from app/src/organisms/OnDeviceDisplay/ProtocolSetup/ProtocolSetupSkeleton.tsx rename to app/src/organisms/ODD/ProtocolSetup/ProtocolSetupSkeleton.tsx index 23a51d26439..55a996ef3f3 100644 --- a/app/src/organisms/OnDeviceDisplay/ProtocolSetup/ProtocolSetupSkeleton.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupSkeleton.tsx @@ -1,8 +1,6 @@ -import * as React from 'react' - import { BORDERS } from '@opentrons/components' -import { Skeleton } from '../../../atoms/Skeleton' +import { Skeleton } from '/app/atoms/Skeleton' export function ProtocolSetupTitleSkeleton(): JSX.Element { return ( diff --git a/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupStep/index.tsx b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupStep/index.tsx new file mode 100644 index 00000000000..b53f21000f8 --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/ProtocolSetupStep/index.tsx @@ -0,0 +1,198 @@ +import { css } from 'styled-components' +import { + ALIGN_CENTER, + BORDERS, + Btn, + COLORS, + DIRECTION_COLUMN, + Flex, + Icon, + JUSTIFY_END, + LegacyStyledText, + NO_WRAP, + SPACING, + TEXT_ALIGN_RIGHT, + truncateString, + TYPOGRAPHY, +} from '@opentrons/components' +import { useToaster } from '../../../ToasterOven' + +const CSV_FILE_MAX_LENGTH = 18 // truncated text + three dots + +export type ProtocolSetupStepStatus = + | 'ready' + | 'not ready' + | 'general' + | 'inform' +interface ProtocolSetupStepProps { + onClickSetupStep: () => void + status: ProtocolSetupStepStatus + title: string + // first line of detail text + detail?: string | null + // clip detail text overflow with ellipsis + clipDetail?: boolean + // second line of detail text + subDetail?: string | null + // disallow click handler, disabled styling + disabled?: boolean + // disallow click handler, don't show CTA icons, allow styling + interactionDisabled?: boolean + // display the reason the setup step is disabled + disabledReason?: string | null + // optional description + description?: string | null + // optional removal of the left icon + hasLeftIcon?: boolean + // optional removal of the right icon + hasRightIcon?: boolean + // optional enlarge the font size + fontSize?: string +} + +export function ProtocolSetupStep({ + onClickSetupStep, + status, + title, + detail, + subDetail, + disabled = false, + clipDetail = false, + interactionDisabled = false, + disabledReason, + description, + hasRightIcon = true, + hasLeftIcon = true, + fontSize = 'p', +}: ProtocolSetupStepProps): JSX.Element { + const isInteractionDisabled = interactionDisabled || disabled + const backgroundColorByStepStatus = { + ready: COLORS.green35, + 'not ready': COLORS.yellow35, + general: COLORS.grey35, + inform: COLORS.grey35, + } + const { makeSnackbar } = useToaster() + + const makeDisabledReasonSnackbar = (): void => { + if (disabledReason != null) { + makeSnackbar(disabledReason) + } + } + + let backgroundColor: string + if (!disabled) { + switch (status) { + case 'general': + backgroundColor = COLORS.blue35 + break + case 'ready': + backgroundColor = COLORS.green40 + break + case 'inform': + backgroundColor = COLORS.grey50 + break + default: + backgroundColor = COLORS.yellow40 + } + } else backgroundColor = '' + + const PUSHED_STATE_STYLE = css` + &:active { + background-color: ${backgroundColor}; + } + ` + + const isToggle = detail === 'On' || detail === 'Off' + + return ( + { + !isInteractionDisabled + ? onClickSetupStep() + : makeDisabledReasonSnackbar() + }} + width="100%" + data-testid={`SetupButton_${title}`} + > + + {status !== 'general' && + !disabled && + status !== 'inform' && + hasLeftIcon ? ( + + ) : null} + + + {title} + + {description != null ? ( + + {description} + + ) : null} + + + + {title === 'CSV File' && detail != null + ? truncateString(detail, CSV_FILE_MAX_LENGTH) + : detail} + {subDetail != null && detail != null ?
    : null} + {subDetail} +
    +
    + {interactionDisabled || !hasRightIcon ? null : ( + + )} +
    +
    + ) +} + +const CLIPPED_TEXT_STYLE = css` + white-space: ${NO_WRAP}; + overflow: hidden; + text-overflow: ellipsis; +` diff --git a/app/src/pages/ProtocolDetails/fixtures.ts b/app/src/organisms/ODD/ProtocolSetup/__fixtures__/index.ts similarity index 100% rename from app/src/pages/ProtocolDetails/fixtures.ts rename to app/src/organisms/ODD/ProtocolSetup/__fixtures__/index.ts diff --git a/app/src/organisms/OnDeviceDisplay/ProtocolSetup/__tests__/ProtocolSetupSkeleton.test.tsx b/app/src/organisms/ODD/ProtocolSetup/__tests__/ProtocolSetupSkeleton.test.tsx similarity index 96% rename from app/src/organisms/OnDeviceDisplay/ProtocolSetup/__tests__/ProtocolSetupSkeleton.test.tsx rename to app/src/organisms/ODD/ProtocolSetup/__tests__/ProtocolSetupSkeleton.test.tsx index 53f5506c4a7..9fd0d8c3fa0 100644 --- a/app/src/organisms/OnDeviceDisplay/ProtocolSetup/__tests__/ProtocolSetupSkeleton.test.tsx +++ b/app/src/organisms/ODD/ProtocolSetup/__tests__/ProtocolSetupSkeleton.test.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { render, screen } from '@testing-library/react' import { describe, expect, it } from 'vitest' diff --git a/app/src/organisms/ODD/ProtocolSetup/index.ts b/app/src/organisms/ODD/ProtocolSetup/index.ts new file mode 100644 index 00000000000..74473b8146b --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/index.ts @@ -0,0 +1,11 @@ +export * from './ProtocolSetupDeckConfiguration' +export * from './ProtocolSetupInstruments' +export * from './ProtocolSetupLabware' +export * from './ProtocolSetupLiquids' +export * from './ProtocolSetupModulesAndDeck' +export * from './ProtocolSetupOffsets' +export * from './ProtocolSetupParameters' +export * from './ProtocolSetupSkeleton' +export * from './ProtocolSetupStep' + +export type * from './types' diff --git a/app/src/organisms/ODD/ProtocolSetup/types.ts b/app/src/organisms/ODD/ProtocolSetup/types.ts new file mode 100644 index 00000000000..01d704da417 --- /dev/null +++ b/app/src/organisms/ODD/ProtocolSetup/types.ts @@ -0,0 +1,9 @@ +export type SetupScreens = + | 'prepare to run' + | 'instruments' + | 'modules' + | 'offsets' + | 'labware' + | 'liquids' + | 'deck configuration' + | 'view only parameters' diff --git a/app/src/organisms/QuickTransferFlow/ConfirmExitModal.tsx b/app/src/organisms/ODD/QuickTransferFlow/ConfirmExitModal.tsx similarity index 90% rename from app/src/organisms/QuickTransferFlow/ConfirmExitModal.tsx rename to app/src/organisms/ODD/QuickTransferFlow/ConfirmExitModal.tsx index 9ea40d081bf..9b6420ebbfb 100644 --- a/app/src/organisms/QuickTransferFlow/ConfirmExitModal.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/ConfirmExitModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { SPACING, @@ -8,8 +7,8 @@ import { DIRECTION_COLUMN, TYPOGRAPHY, } from '@opentrons/components' -import { OddModal } from '../../molecules/OddModal' -import { SmallButton } from '../../atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' +import { SmallButton } from '/app/atoms/buttons' interface ConfirmExitModalProps { confirmExit: () => void diff --git a/app/src/organisms/QuickTransferFlow/CreateNewTransfer.tsx b/app/src/organisms/ODD/QuickTransferFlow/CreateNewTransfer.tsx similarity index 89% rename from app/src/organisms/QuickTransferFlow/CreateNewTransfer.tsx rename to app/src/organisms/ODD/QuickTransferFlow/CreateNewTransfer.tsx index e7d2319b4ea..10b036b9064 100644 --- a/app/src/organisms/QuickTransferFlow/CreateNewTransfer.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/CreateNewTransfer.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation, Trans } from 'react-i18next' import { @@ -10,9 +10,9 @@ import { DIRECTION_COLUMN, } from '@opentrons/components' -import { ChildNavigation } from '../ChildNavigation' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' -import type { SmallButton } from '../../atoms/buttons' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' +import type { SmallButton } from '/app/atoms/buttons' interface CreateNewTransferProps { onNext: () => void diff --git a/app/src/organisms/QuickTransferFlow/NameQuickTransfer.tsx b/app/src/organisms/ODD/QuickTransferFlow/NameQuickTransfer.tsx similarity index 84% rename from app/src/organisms/QuickTransferFlow/NameQuickTransfer.tsx rename to app/src/organisms/ODD/QuickTransferFlow/NameQuickTransfer.tsx index 7a79306314e..8bff060ac38 100644 --- a/app/src/organisms/QuickTransferFlow/NameQuickTransfer.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/NameQuickTransfer.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useRef } from 'react' import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom' @@ -14,9 +14,9 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { getTopPortalEl } from '../../App/portal' -import { FullKeyboard } from '../../atoms/SoftwareKeyboard' -import { ChildNavigation } from '../ChildNavigation' +import { getTopPortalEl } from '/app/App/portal' +import { FullKeyboard } from '/app/atoms/SoftwareKeyboard' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' interface NameQuickTransferProps { onSave: (protocolName: string) => void @@ -25,8 +25,9 @@ interface NameQuickTransferProps { export function NameQuickTransfer(props: NameQuickTransferProps): JSX.Element { const { onSave } = props const { t } = useTranslation('quick_transfer') - const [name, setName] = React.useState('') - const keyboardRef = React.useRef(null) + const [name, setName] = useState('') + const keyboardRef = useRef(null) + const [isSaving, setIsSaving] = useState(false) let error: string | null = null if (name.length > 60) { @@ -40,9 +41,10 @@ export function NameQuickTransfer(props: NameQuickTransferProps): JSX.Element { header={t('name_your_transfer')} buttonText={t('save')} onClickButton={() => { + setIsSaving(true) onSave(name) }} - buttonIsDisabled={name === '' || error != null} + buttonIsDisabled={name === '' || error != null || isSaving} /> void state: QuickTransferSummaryState - dispatch: React.Dispatch + dispatch: Dispatch kind: FlowRateKind } export function AirGap(props: AirGapProps): JSX.Element { const { kind, onBack, state, dispatch } = props const { t } = useTranslation('quick_transfer') - const keyboardRef = React.useRef(null) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() + const keyboardRef = useRef(null) - const [airGapEnabled, setAirGapEnabled] = React.useState( + const [airGapEnabled, setAirGapEnabled] = useState( kind === 'aspirate' ? state.airGapAspirate != null : state.airGapDispense != null ) - const [currentStep, setCurrentStep] = React.useState(1) - const [volume, setVolume] = React.useState( + const [currentStep, setCurrentStep] = useState(1) + const [volume, setVolume] = useState( kind === 'aspirate' ? state.airGapAspirate ?? null : state.airGapDispense ?? null @@ -81,10 +85,22 @@ export function AirGap(props: AirGapProps): JSX.Element { setCurrentStep(currentStep + 1) } else { dispatch({ type: action, volume: undefined }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `AirGap_${kind}`, + }, + }) onBack() } } else if (currentStep === 2) { dispatch({ type: action, volume: volume ?? undefined }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `AirGap_${kind}`, + }, + }) onBack() } } @@ -145,7 +161,7 @@ export function AirGap(props: AirGapProps): JSX.Element { {enableAirGapDisplayItems.map(displayItem => ( - ))} @@ -207,6 +223,7 @@ export function AirGap(props: AirGapProps): JSX.Element { > { setVolume(Number(e)) }} diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/BaseSettings.tsx b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/BaseSettings.tsx similarity index 94% rename from app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/BaseSettings.tsx rename to app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/BaseSettings.tsx index 9f300b335ad..297054b1d03 100644 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/BaseSettings.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/BaseSettings.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, @@ -17,6 +17,7 @@ import { import { FlowRateEntry } from './FlowRate' import { PipettePath } from './PipettePath' +import type { Dispatch } from 'react' import type { QuickTransferSummaryAction, QuickTransferSummaryState, @@ -24,15 +25,13 @@ import type { interface BaseSettingsProps { state: QuickTransferSummaryState - dispatch: React.Dispatch + dispatch: Dispatch } export function BaseSettings(props: BaseSettingsProps): JSX.Element | null { const { state, dispatch } = props const { t } = useTranslation(['quick_transfer', 'shared']) - const [selectedSetting, setSelectedSetting] = React.useState( - null - ) + const [selectedSetting, setSelectedSetting] = useState(null) let pipettePath: string = '' if (state.path === 'single') { diff --git a/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/BlowOut.tsx b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/BlowOut.tsx new file mode 100644 index 00000000000..e961b480960 --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/BlowOut.tsx @@ -0,0 +1,236 @@ +import { useState } from 'react' +import isEqual from 'lodash/isEqual' +import { useTranslation } from 'react-i18next' +import { createPortal } from 'react-dom' +import { + Flex, + SPACING, + DIRECTION_COLUMN, + POSITION_FIXED, + RadioButton, + COLORS, +} from '@opentrons/components' +import { + WASTE_CHUTE_FIXTURES, + FLEX_SINGLE_SLOT_BY_CUTOUT_ID, + TRASH_BIN_ADAPTER_FIXTURE, +} from '@opentrons/shared-data' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '/app/redux/analytics' +import { getTopPortalEl } from '/app/App/portal' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { ACTIONS } from '../constants' + +import type { Dispatch } from 'react' +import type { DeckConfiguration } from '@opentrons/shared-data' +import type { + QuickTransferSummaryState, + QuickTransferSummaryAction, + FlowRateKind, + BlowOutLocation, + TransferType, +} from '../types' +import { i18n } from '/app/i18n' + +interface BlowOutProps { + onBack: () => void + state: QuickTransferSummaryState + dispatch: Dispatch + kind: FlowRateKind +} + +export const useBlowOutLocationOptions = ( + deckConfig: DeckConfiguration, + transferType: TransferType +): Array<{ location: BlowOutLocation; description: string }> => { + const { t } = useTranslation('quick_transfer') + + const trashLocations = deckConfig.filter( + cutoutConfig => + WASTE_CHUTE_FIXTURES.includes(cutoutConfig.cutoutFixtureId) || + TRASH_BIN_ADAPTER_FIXTURE === cutoutConfig.cutoutFixtureId + ) + + // add trash bin in A3 if no trash or waste chute configured + if (trashLocations.length === 0) { + trashLocations.push({ + cutoutId: 'cutoutA3', + cutoutFixtureId: TRASH_BIN_ADAPTER_FIXTURE, + }) + } + const blowOutLocationItems: Array<{ + location: BlowOutLocation + description: string + }> = [] + if (transferType !== 'distribute') { + blowOutLocationItems.push({ + location: 'dest_well', + description: t('blow_out_destination_well'), + }) + } + if (transferType !== 'consolidate') { + blowOutLocationItems.push({ + location: 'source_well', + description: t('blow_out_source_well'), + }) + } + trashLocations.forEach(location => { + blowOutLocationItems.push({ + location, + description: + location.cutoutFixtureId === TRASH_BIN_ADAPTER_FIXTURE + ? t('trashBin_location', { + slotName: FLEX_SINGLE_SLOT_BY_CUTOUT_ID[location.cutoutId], + }) + : t('wasteChute_location', { + slotName: FLEX_SINGLE_SLOT_BY_CUTOUT_ID[location.cutoutId], + }), + }) + }) + return blowOutLocationItems +} + +export function BlowOut(props: BlowOutProps): JSX.Element { + const { onBack, state, dispatch } = props + const { t } = useTranslation('quick_transfer') + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() + const deckConfig = useNotifyDeckConfigurationQuery().data ?? [] + + const [isBlowOutEnabled, setisBlowOutEnabled] = useState( + state.blowOut != null + ) + const [currentStep, setCurrentStep] = useState(1) + const [blowOutLocation, setBlowOutLocation] = useState< + BlowOutLocation | undefined + >(state.blowOut) + + const enableBlowOutDisplayItems = [ + { + option: true, + description: t('option_enabled'), + onClick: () => { + setisBlowOutEnabled(true) + }, + }, + { + option: false, + description: t('option_disabled'), + onClick: () => { + setisBlowOutEnabled(false) + }, + }, + ] + + const blowOutLocationItems = useBlowOutLocationOptions( + deckConfig, + state.transferType + ) + + const handleClickBackOrExit = (): void => { + currentStep > 1 ? setCurrentStep(currentStep - 1) : onBack() + } + + const handleClickSaveOrContinue = (): void => { + if (currentStep === 1) { + if (!isBlowOutEnabled) { + dispatch({ + type: ACTIONS.SET_BLOW_OUT, + location: undefined, + }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + settting: `BlowOut`, + }, + }) + onBack() + } else { + setCurrentStep(currentStep + 1) + } + } else { + dispatch({ + type: ACTIONS.SET_BLOW_OUT, + location: blowOutLocation, + }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + settting: `BlowOut`, + }, + }) + onBack() + } + } + + const saveOrContinueButtonText = + isBlowOutEnabled && currentStep < 2 + ? t('shared:continue') + : t('shared:save') + + let buttonIsDisabled = false + if (currentStep === 2) { + buttonIsDisabled = blowOutLocation == null + } + + return createPortal( + + + {currentStep === 1 ? ( + + {enableBlowOutDisplayItems.map(displayItem => ( + + ))} + + ) : null} + {currentStep === 2 ? ( + + {blowOutLocationItems.map(blowOutLocationItem => ( + { + setBlowOutLocation( + blowOutLocationItem.location as BlowOutLocation + ) + }} + buttonValue={blowOutLocationItem.description} + buttonLabel={blowOutLocationItem.description} + radioButtonType="large" + /> + ))} + + ) : null} + , + getTopPortalEl() + ) +} diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/Delay.tsx b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/Delay.tsx similarity index 81% rename from app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/Delay.tsx rename to app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/Delay.tsx index 60f88eebfb1..556ddc181c8 100644 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/Delay.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/Delay.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useRef } from 'react' import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom' @@ -8,47 +8,50 @@ import { DIRECTION_COLUMN, Flex, InputField, - LargeButton, POSITION_FIXED, + RadioButton, SPACING, } from '@opentrons/components' - -import { getTopPortalEl } from '../../../App/portal' -import { ChildNavigation } from '../../ChildNavigation' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '/app/redux/analytics' +import { getTopPortalEl } from '/app/App/portal' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' import { ACTIONS } from '../constants' +import type { Dispatch } from 'react' import type { QuickTransferSummaryState, QuickTransferSummaryAction, FlowRateKind, } from '../types' -import { i18n } from '../../../i18n' -import { NumericalKeyboard } from '../../../atoms/SoftwareKeyboard' +import { i18n } from '/app/i18n' +import { NumericalKeyboard } from '/app/atoms/SoftwareKeyboard' interface DelayProps { onBack: () => void state: QuickTransferSummaryState - dispatch: React.Dispatch + dispatch: Dispatch kind: FlowRateKind } export function Delay(props: DelayProps): JSX.Element { const { kind, onBack, state, dispatch } = props const { t } = useTranslation('quick_transfer') - const keyboardRef = React.useRef(null) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() + const keyboardRef = useRef(null) - const [currentStep, setCurrentStep] = React.useState(1) - const [delayIsEnabled, setDelayIsEnabled] = React.useState( + const [currentStep, setCurrentStep] = useState(1) + const [delayIsEnabled, setDelayIsEnabled] = useState( kind === 'aspirate' ? state.delayAspirate != null : state.delayDispense != null ) - const [delayDuration, setDelayDuration] = React.useState( + const [delayDuration, setDelayDuration] = useState( kind === 'aspirate' ? state.delayAspirate?.delayDuration ?? null : state.delayDispense?.delayDuration ?? null ) - const [position, setPosition] = React.useState( + const [position, setPosition] = useState( kind === 'aspirate' ? state.delayAspirate?.positionFromBottom ?? null : state.delayDispense?.positionFromBottom ?? null @@ -87,6 +90,12 @@ export function Delay(props: DelayProps): JSX.Element { type: action, delaySettings: undefined, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + settting: `Delay_${kind}`, + }, + }) onBack() } else { setCurrentStep(2) @@ -102,6 +111,12 @@ export function Delay(props: DelayProps): JSX.Element { positionFromBottom: position, }, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + settting: `Delay_${kind}`, + }, + }) } onBack() } @@ -162,7 +177,7 @@ export function Delay(props: DelayProps): JSX.Element { {delayEnabledDisplayItems.map(displayItem => ( - ))}
    @@ -224,6 +239,7 @@ export function Delay(props: DelayProps): JSX.Element { > { setDelayDuration(Number(e)) }} @@ -264,6 +280,7 @@ export function Delay(props: DelayProps): JSX.Element { > { setPosition(Number(e)) }} diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/FlowRate.tsx b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/FlowRate.tsx similarity index 83% rename from app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/FlowRate.tsx rename to app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/FlowRate.tsx index 766cc5faea1..1b2bed604fc 100644 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/FlowRate.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/FlowRate.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useRef } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' @@ -15,12 +15,16 @@ import { LOW_VOLUME_PIPETTES, getTipTypeFromTipRackDefinition, } from '@opentrons/shared-data' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '/app/redux/analytics' + +import { getTopPortalEl } from '/app/App/portal' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { NumericalKeyboard } from '/app/atoms/SoftwareKeyboard' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' -import { getTopPortalEl } from '../../../App/portal' -import { ChildNavigation } from '../../ChildNavigation' -import { NumericalKeyboard } from '../../../atoms/SoftwareKeyboard' import { ACTIONS } from '../constants' +import type { Dispatch } from 'react' import type { SupportedTip } from '@opentrons/shared-data' import type { QuickTransferSummaryState, @@ -31,16 +35,17 @@ import type { interface FlowRateEntryProps { onBack: () => void state: QuickTransferSummaryState - dispatch: React.Dispatch + dispatch: Dispatch kind: FlowRateKind } export function FlowRateEntry(props: FlowRateEntryProps): JSX.Element { const { onBack, state, dispatch, kind } = props const { i18n, t } = useTranslation(['quick_transfer', 'shared']) - const keyboardRef = React.useRef(null) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() + const keyboardRef = useRef(null) - const [flowRate, setFlowRate] = React.useState( + const [flowRate, setFlowRate] = useState( kind === 'aspirate' ? state.aspirateFlowRate : state.dispenseFlowRate ) @@ -88,6 +93,12 @@ export function FlowRateEntry(props: FlowRateEntryProps): JSX.Element { type: flowRateAction, rate: flowRate, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `FlowRate_${kind}`, + }, + }) } onBack() } @@ -142,6 +153,7 @@ export function FlowRateEntry(props: FlowRateEntryProps): JSX.Element { > { setFlowRate(Number(e)) }} diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/Mix.tsx b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/Mix.tsx similarity index 81% rename from app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/Mix.tsx rename to app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/Mix.tsx index 75f3a14a6b3..b6c7862310c 100644 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/Mix.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/Mix.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useRef } from 'react' import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom' @@ -8,47 +8,51 @@ import { DIRECTION_COLUMN, Flex, InputField, - LargeButton, POSITION_FIXED, + RadioButton, SPACING, } from '@opentrons/components' -import { getTopPortalEl } from '../../../App/portal' -import { ChildNavigation } from '../../ChildNavigation' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '/app/redux/analytics' +import { getTopPortalEl } from '/app/App/portal' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' import { ACTIONS } from '../constants' +import { i18n } from '/app/i18n' +import { NumericalKeyboard } from '/app/atoms/SoftwareKeyboard' +import type { Dispatch } from 'react' import type { QuickTransferSummaryState, QuickTransferSummaryAction, FlowRateKind, } from '../types' -import { i18n } from '../../../i18n' -import { NumericalKeyboard } from '../../../atoms/SoftwareKeyboard' interface MixProps { onBack: () => void state: QuickTransferSummaryState - dispatch: React.Dispatch + dispatch: Dispatch kind: FlowRateKind } export function Mix(props: MixProps): JSX.Element { const { kind, onBack, state, dispatch } = props const { t } = useTranslation('quick_transfer') - const keyboardRef = React.useRef(null) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() + const keyboardRef = useRef(null) - const [mixIsEnabled, setMixIsEnabled] = React.useState( + const [mixIsEnabled, setMixIsEnabled] = useState( kind === 'aspirate' ? state.mixOnAspirate != null : state.mixOnDispense != null ) - const [currentStep, setCurrentStep] = React.useState(1) - const [mixVolume, setMixVolume] = React.useState( + const [currentStep, setCurrentStep] = useState(1) + const [mixVolume, setMixVolume] = useState( kind === 'aspirate' ? state.mixOnAspirate?.mixVolume ?? null : state.mixOnDispense?.mixVolume ?? null ) - const [mixReps, setMixReps] = React.useState( + const [mixReps, setMixReps] = useState( kind === 'aspirate' ? state.mixOnAspirate?.repititions ?? null : state.mixOnDispense?.repititions ?? null @@ -87,6 +91,12 @@ export function Mix(props: MixProps): JSX.Element { type: mixAction, mixSettings: undefined, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `Mix_${kind}`, + }, + }) onBack() } else { setCurrentStep(2) @@ -99,6 +109,12 @@ export function Mix(props: MixProps): JSX.Element { type: mixAction, mixSettings: { mixVolume, repititions: mixReps }, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `Mix_${kind}`, + }, + }) } onBack() } @@ -160,13 +176,13 @@ export function Mix(props: MixProps): JSX.Element { width="100%" > {enableMixDisplayItems.map(displayItem => ( - ))}
    @@ -204,6 +220,7 @@ export function Mix(props: MixProps): JSX.Element { > { setMixVolume(Number(e)) }} diff --git a/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/PipettePath.tsx b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/PipettePath.tsx new file mode 100644 index 00000000000..eabbe9fc678 --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/PipettePath.tsx @@ -0,0 +1,258 @@ +import { useState, useRef } from 'react' +import isEqual from 'lodash/isEqual' +import { useTranslation } from 'react-i18next' +import { createPortal } from 'react-dom' + +import { + ALIGN_CENTER, + COLORS, + DIRECTION_COLUMN, + Flex, + InputField, + POSITION_FIXED, + RadioButton, + SPACING, +} from '@opentrons/components' + +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '/app/redux/analytics' +import { getTopPortalEl } from '/app/App/portal' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { useBlowOutLocationOptions } from './BlowOut' + +import { ACTIONS } from '../constants' +import { i18n } from '/app/i18n' +import { NumericalKeyboard } from '/app/atoms/SoftwareKeyboard' + +import type { Dispatch } from 'react' +import type { + PathOption, + QuickTransferSummaryState, + QuickTransferSummaryAction, + BlowOutLocation, +} from '../types' + +interface PipettePathProps { + onBack: () => void + state: QuickTransferSummaryState + dispatch: Dispatch +} + +export function PipettePath(props: PipettePathProps): JSX.Element { + const { onBack, state, dispatch } = props + const { t } = useTranslation('quick_transfer') + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() + const keyboardRef = useRef(null) + const deckConfig = useNotifyDeckConfigurationQuery().data ?? [] + + const [selectedPath, setSelectedPath] = useState(state.path) + const [currentStep, setCurrentStep] = useState(1) + const [blowOutLocation, setBlowOutLocation] = useState< + BlowOutLocation | undefined + >(state.blowOut) + + const [disposalVolume, setDisposalVolume] = useState( + state?.disposalVolume + ) + const maxPipetteVolume = Object.values(state.pipette.liquids)[0].maxVolume + const tipVolume = Object.values(state.tipRack.wells)[0].totalLiquidVolume + + // this is the max amount of liquid that can be held in the tip at any time + const maxTipCapacity = Math.min(maxPipetteVolume, tipVolume) + + const allowedPipettePathOptions: Array<{ + pathOption: PathOption + description: string + }> = [{ pathOption: 'single', description: t('pipette_path_single') }] + if ( + state.transferType === 'distribute' && + maxTipCapacity >= state.volume * 3 + ) { + // we have the capacity for a multi dispense if we can fit at least 2x the volume per well + // for aspiration plus 1x the volume per well for disposal volume + allowedPipettePathOptions.push({ + pathOption: 'multiDispense', + description: t('pipette_path_multi_dispense'), + }) + // for multi aspirate we only need at least 2x the volume per well + } else if ( + state.transferType === 'consolidate' && + maxTipCapacity >= state.volume * 2 + ) { + allowedPipettePathOptions.push({ + pathOption: 'multiAspirate', + description: t('pipette_path_multi_aspirate'), + }) + } + + const blowOutLocationItems = useBlowOutLocationOptions( + deckConfig, + state.transferType + ) + + const handleClickBackOrExit = (): void => { + currentStep > 1 ? setCurrentStep(currentStep - 1) : onBack() + } + + const handleClickSaveOrContinue = (): void => { + if (currentStep === 1) { + if (selectedPath !== 'multiDispense') { + dispatch({ + type: ACTIONS.SET_PIPETTE_PATH, + path: selectedPath, + }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `PipettePath`, + }, + }) + onBack() + } else { + setCurrentStep(2) + } + } else if (currentStep === 2) { + setCurrentStep(3) + } else { + dispatch({ + type: ACTIONS.SET_PIPETTE_PATH, + path: selectedPath as PathOption, + disposalVolume, + blowOutLocation, + }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `PipettePath`, + }, + }) + onBack() + } + } + + const saveOrContinueButtonText = + selectedPath === 'multiDispense' && currentStep < 3 + ? t('shared:continue') + : t('shared:save') + + const maxDisposalCapacity = maxTipCapacity - state.volume * 2 + const volumeRange = { min: 1, max: maxDisposalCapacity } + + const volumeError = + disposalVolume != null && + (disposalVolume < volumeRange.min || disposalVolume > volumeRange.max) + ? t(`value_out_of_range`, { + min: volumeRange.min, + max: volumeRange.max, + }) + : null + + let buttonIsDisabled = false + if (currentStep === 2) { + buttonIsDisabled = disposalVolume == null || volumeError != null + } else if (currentStep === 3) { + buttonIsDisabled = blowOutLocation == null + } + + return createPortal( + + + {currentStep === 1 ? ( + + {allowedPipettePathOptions.map(option => ( + { + setSelectedPath(option.pathOption) + }} + buttonValue={option.description} + buttonLabel={option.description} + radioButtonType="large" + /> + ))} + + ) : null} + {currentStep === 2 ? ( + + + + + + { + setDisposalVolume(Number(e)) + }} + /> + + + ) : null} + {currentStep === 3 ? ( + + {blowOutLocationItems.map(option => ( + { + setBlowOutLocation(option.location) + }} + buttonValue={option.description} + buttonLabel={option.description} + radioButtonType="large" + /> + ))} + + ) : null} + , + getTopPortalEl() + ) +} diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/TipPosition.tsx b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/TipPosition.tsx similarity index 81% rename from app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/TipPosition.tsx rename to app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/TipPosition.tsx index 18e7d100f37..8a5abf1a169 100644 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/TipPosition.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/TipPosition.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useRef } from 'react' import { useTranslation } from 'react-i18next' import { @@ -11,12 +11,15 @@ import { SPACING, } from '@opentrons/components' -import { getTopPortalEl } from '../../../App/portal' -import { ChildNavigation } from '../../ChildNavigation' -import { NumericalKeyboard } from '../../../atoms/SoftwareKeyboard' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '/app/redux/analytics' +import { getTopPortalEl } from '/app/App/portal' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { NumericalKeyboard } from '/app/atoms/SoftwareKeyboard' import { ACTIONS } from '../constants' import { createPortal } from 'react-dom' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import type { Dispatch } from 'react' import type { QuickTransferSummaryState, QuickTransferSummaryAction, @@ -26,16 +29,17 @@ import type { interface TipPositionEntryProps { onBack: () => void state: QuickTransferSummaryState - dispatch: React.Dispatch + dispatch: Dispatch kind: FlowRateKind // TODO: rename flowRateKind to be generic } export function TipPositionEntry(props: TipPositionEntryProps): JSX.Element { const { onBack, state, dispatch, kind } = props const { i18n, t } = useTranslation(['quick_transfer', 'shared']) - const keyboardRef = React.useRef(null) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() + const keyboardRef = useRef(null) - const [tipPosition, setTipPosition] = React.useState( + const [tipPosition, setTipPosition] = useState( kind === 'aspirate' ? state.tipPositionAspirate : state.tipPositionDispense ) @@ -74,6 +78,12 @@ export function TipPositionEntry(props: TipPositionEntryProps): JSX.Element { type: tipPositionAction, position: tipPosition, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `TipPosition_${kind}`, + }, + }) } onBack() } @@ -133,6 +143,7 @@ export function TipPositionEntry(props: TipPositionEntryProps): JSX.Element { > { setTipPosition(Number(e)) }} diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/TouchTip.tsx b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/TouchTip.tsx similarity index 77% rename from app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/TouchTip.tsx rename to app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/TouchTip.tsx index 45d48bde67c..41780d65181 100644 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/TouchTip.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/TouchTip.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useRef } from 'react' import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom' @@ -8,17 +8,20 @@ import { DIRECTION_COLUMN, Flex, InputField, - LargeButton, POSITION_FIXED, + RadioButton, SPACING, } from '@opentrons/components' -import { getTopPortalEl } from '../../../App/portal' -import { ChildNavigation } from '../../ChildNavigation' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '/app/redux/analytics' +import { getTopPortalEl } from '/app/App/portal' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' import { ACTIONS } from '../constants' -import { i18n } from '../../../i18n' -import { NumericalKeyboard } from '../../../atoms/SoftwareKeyboard' +import { i18n } from '/app/i18n' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { NumericalKeyboard } from '/app/atoms/SoftwareKeyboard' +import type { Dispatch } from 'react' import type { QuickTransferSummaryState, QuickTransferSummaryAction, @@ -28,22 +31,23 @@ import type { interface TouchTipProps { onBack: () => void state: QuickTransferSummaryState - dispatch: React.Dispatch + dispatch: Dispatch kind: FlowRateKind } export function TouchTip(props: TouchTipProps): JSX.Element { const { kind, onBack, state, dispatch } = props const { t } = useTranslation('quick_transfer') - const keyboardRef = React.useRef(null) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() + const keyboardRef = useRef(null) - const [touchTipIsEnabled, setTouchTipIsEnabled] = React.useState( + const [touchTipIsEnabled, setTouchTipIsEnabled] = useState( kind === 'aspirate' ? state.touchTipAspirate != null : state.touchTipDispense != null ) - const [currentStep, setCurrentStep] = React.useState(1) - const [position, setPosition] = React.useState( + const [currentStep, setCurrentStep] = useState(1) + const [position, setPosition] = useState( kind === 'aspirate' ? state.touchTipAspirate ?? null : state.touchTipDispense ?? null @@ -79,12 +83,24 @@ export function TouchTip(props: TouchTipProps): JSX.Element { if (currentStep === 1) { if (!touchTipIsEnabled) { dispatch({ type: touchTipAction, position: undefined }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `TouchTip_${kind}`, + }, + }) onBack() } else { setCurrentStep(2) } } else if (currentStep === 2) { dispatch({ type: touchTipAction, position: position ?? undefined }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `TouchTip_${kind}`, + }, + }) onBack() } } @@ -134,7 +150,7 @@ export function TouchTip(props: TouchTipProps): JSX.Element { {enableTouchTipDisplayItems.map(displayItem => ( - ))}
    @@ -198,6 +212,7 @@ export function TouchTip(props: TouchTipProps): JSX.Element { > { setPosition(Number(e)) }} diff --git a/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/index.tsx b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/index.tsx new file mode 100644 index 00000000000..33a603b6f75 --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/QuickTransferAdvancedSettings/index.tsx @@ -0,0 +1,625 @@ +import { useState, useEffect } from 'react' +import { useTranslation } from 'react-i18next' + +import { + ALIGN_CENTER, + COLORS, + DIRECTION_COLUMN, + Flex, + Icon, + JUSTIFY_SPACE_BETWEEN, + ListItem, + SIZE_2, + SPACING, + StyledText, + TEXT_ALIGN_LEFT, + TEXT_ALIGN_RIGHT, + TYPOGRAPHY, +} from '@opentrons/components' +import { + TRASH_BIN_ADAPTER_FIXTURE, + WASTE_CHUTE_FIXTURES, +} from '@opentrons/shared-data' + +import { ANALYTICS_QUICK_TRANSFER_ADVANCED_SETTINGS_TAB } from '/app/redux/analytics' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { ACTIONS } from '../constants' +import { useToaster } from '/app/organisms/ToasterOven' +import { FlowRateEntry } from './FlowRate' +import { PipettePath } from './PipettePath' +import { TipPositionEntry } from './TipPosition' +import { Mix } from './Mix' +import { Delay } from './Delay' +import { TouchTip } from './TouchTip' +import { AirGap } from './AirGap' +import { BlowOut } from './BlowOut' + +import type { Dispatch } from 'react' +import type { + QuickTransferSummaryAction, + QuickTransferSummaryState, +} from '../types' +interface QuickTransferAdvancedSettingsProps { + state: QuickTransferSummaryState + dispatch: Dispatch +} + +export function QuickTransferAdvancedSettings( + props: QuickTransferAdvancedSettingsProps +): JSX.Element | null { + const { state, dispatch } = props + const { t, i18n } = useTranslation(['quick_transfer', 'shared']) + const [selectedSetting, setSelectedSetting] = useState(null) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() + const { makeSnackbar } = useToaster() + + useEffect(() => { + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_ADVANCED_SETTINGS_TAB, + properties: {}, + }) + }, []) + + function getBlowoutValueCopy(): string | undefined { + if (state.blowOut === 'dest_well') { + return t('blow_out_into_destination_well') + } else if (state.blowOut === 'source_well') { + return t('blow_out_into_source_well') + } else if ( + state.blowOut != null && + state.blowOut.cutoutFixtureId === TRASH_BIN_ADAPTER_FIXTURE + ) { + return t('blow_out_into_trash_bin') + } else if ( + state.blowOut != null && + WASTE_CHUTE_FIXTURES.includes(state.blowOut.cutoutFixtureId) + ) { + return t('blow_out_into_waste_chute') + } + } + + let pipettePathValue: string = '' + if (state.path === 'single') { + pipettePathValue = t('pipette_path_single') + } else if (state.path === 'multiAspirate') { + pipettePathValue = t('pipette_path_multi_aspirate') + } else if (state.path === 'multiDispense') { + pipettePathValue = t('pipette_path_multi_dispense_volume_blowout', { + volume: state.disposalVolume, + blowOutLocation: getBlowoutValueCopy(), + }) + } + + const destinationLabwareDef = + state.destination === 'source' ? state.source : state.destination + const sourceIsReservoir = + state.source.metadata.displayCategory === 'reservoir' + const destIsReservoir = + destinationLabwareDef.metadata.displayCategory === 'reservoir' + + const baseSettingsItems = [ + { + option: 'aspirate_flow_rate', + copy: t('aspirate_flow_rate'), + value: t('flow_rate_value', { flow_rate: state.aspirateFlowRate }), + enabled: true, + onClick: () => { + setSelectedSetting('aspirate_flow_rate') + }, + }, + { + option: 'dispense_flow_rate', + copy: t('dispense_flow_rate'), + value: t('flow_rate_value', { flow_rate: state.dispenseFlowRate }), + enabled: true, + onClick: () => { + setSelectedSetting('dispense_flow_rate') + }, + }, + { + option: 'pipette_path', + copy: t('pipette_path'), + value: pipettePathValue, + enabled: state.transferType !== 'transfer', + onClick: () => { + if (state.transferType !== 'transfer') { + setSelectedSetting('pipette_path') + } else { + makeSnackbar(t('advanced_setting_disabled') as string) + } + }, + }, + ] + + const aspirateSettingsItems = [ + { + option: 'tip_position', + copy: t('tip_position'), + value: + state.tipPositionAspirate !== null + ? t('tip_position_value', { position: state.tipPositionAspirate }) + : '', + enabled: true, + onClick: () => { + setSelectedSetting('aspirate_tip_position') + }, + }, + { + option: 'pre_wet_tip', + copy: t('pre_wet_tip'), + value: state.preWetTip ? t('option_enabled') : '', + enabled: true, + onClick: () => { + dispatch({ + type: ACTIONS.SET_PRE_WET_TIP, + preWetTip: !state.preWetTip, + }) + }, + }, + { + option: 'aspirate_mix', + copy: t('mix'), + value: + state.mixOnAspirate !== undefined + ? t('mix_value', { + volume: state.mixOnAspirate?.mixVolume, + reps: state.mixOnAspirate?.repititions, + }) + : '', + enabled: + state.transferType === 'transfer' || + state.transferType === 'distribute', + onClick: () => { + if ( + state.transferType === 'transfer' || + state.transferType === 'distribute' + ) { + setSelectedSetting('aspirate_mix') + } else { + makeSnackbar(t('advanced_setting_disabled') as string) + } + }, + }, + { + option: 'aspirate_delay', + copy: t('delay'), + value: + state.delayAspirate !== undefined + ? t('delay_value', { + delay: state.delayAspirate.delayDuration, + position: state.delayAspirate.positionFromBottom, + }) + : '', + enabled: true, + onClick: () => { + setSelectedSetting('aspirate_delay') + }, + }, + { + option: 'aspirate_touch_tip', + copy: t('touch_tip'), + value: + state.touchTipAspirate !== undefined + ? t('touch_tip_value', { position: state.touchTipAspirate }) + : '', + enabled: !sourceIsReservoir, + onClick: () => { + // disable for reservoir + if (!sourceIsReservoir) { + setSelectedSetting('aspirate_touch_tip') + } else { + makeSnackbar(t('advanced_setting_disabled') as string) + } + }, + }, + { + option: 'aspirate_air_gap', + copy: t('air_gap'), + value: + state.airGapAspirate !== undefined + ? t('air_gap_value', { volume: state.airGapAspirate }) + : '', + enabled: true, + onClick: () => { + setSelectedSetting('aspirate_air_gap') + }, + }, + ] + + const dispenseSettingsItems = [ + { + option: 'dispense_tip_position', + copy: t('tip_position'), + value: + state.tipPositionDispense !== undefined + ? t('tip_position_value', { position: state.tipPositionDispense }) + : '', + enabled: true, + onClick: () => { + setSelectedSetting('dispense_tip_position') + }, + }, + { + option: 'dispense_mix', + copy: t('mix'), + value: + state.mixOnDispense !== undefined + ? t('mix_value', { + volume: state.mixOnDispense?.mixVolume, + reps: state.mixOnDispense?.repititions, + }) + : '', + enabled: + state.transferType === 'transfer' || + state.transferType === 'consolidate', + onClick: () => { + if ( + state.transferType === 'transfer' || + state.transferType === 'consolidate' + ) { + setSelectedSetting('dispense_mix') + } else { + makeSnackbar(t('advanced_setting_disabled') as string) + } + }, + }, + { + option: 'dispense_delay', + copy: t('delay'), + value: + state.delayDispense !== undefined + ? t('delay_value', { + delay: state.delayDispense.delayDuration, + position: state.delayDispense.positionFromBottom, + }) + : '', + enabled: true, + onClick: () => { + setSelectedSetting('dispense_delay') + }, + }, + { + option: 'dispense_touch_tip', + copy: t('touch_tip'), + value: + state.touchTipDispense !== undefined + ? t('touch_tip_value', { position: state.touchTipDispense }) + : '', + enabled: !destIsReservoir, + onClick: () => { + if (!destIsReservoir) { + setSelectedSetting('dispense_touch_tip') + } else { + makeSnackbar(t('advanced_setting_disabled') as string) + } + }, + }, + { + option: 'dispense_air_gap', + copy: t('air_gap'), + value: + state.airGapDispense !== undefined + ? t('air_gap_value', { volume: state.airGapDispense }) + : '', + enabled: true, + onClick: () => { + setSelectedSetting('dispense_air_gap') + }, + }, + { + option: 'dispense_blow_out', + copy: t('blow_out'), + value: + state.transferType === 'distribute' + ? t('disabled') + : i18n.format(getBlowoutValueCopy(), 'capitalize'), + enabled: state.transferType !== 'distribute', + onClick: () => { + if (state.transferType === 'distribute') { + makeSnackbar(t('advanced_setting_disabled') as string) + } else { + setSelectedSetting('dispense_blow_out') + } + }, + }, + ] + + return ( + + {/* Base Settings */} + + {selectedSetting == null + ? baseSettingsItems.map(displayItem => ( + + + + {displayItem.copy} + + + + {displayItem.value} + + {displayItem.enabled ? ( + + ) : null} + + + + )) + : null} + {selectedSetting === 'aspirate_flow_rate' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + {selectedSetting === 'dispense_flow_rate' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + {selectedSetting === 'pipette_path' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + + + {/* Aspirate Settings */} + + {selectedSetting === null ? ( + + {t('aspirate_settings')} + + ) : null} + + {selectedSetting === null + ? aspirateSettingsItems.map(displayItem => ( + + + + {displayItem.copy} + + + + {displayItem.value !== '' + ? displayItem.value + : t('option_disabled')} + + + {displayItem.option !== 'pre_wet_tip' ? ( + + ) : null} + + + + )) + : null} + {selectedSetting === 'aspirate_tip_position' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + {selectedSetting === 'aspirate_mix' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + {selectedSetting === 'aspirate_delay' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + {selectedSetting === 'aspirate_touch_tip' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + {selectedSetting === 'aspirate_air_gap' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + + + + {/* Dispense Settings */} + + {selectedSetting === null ? ( + + {t('dispense_settings')} + + ) : null} + + {selectedSetting === null + ? dispenseSettingsItems.map(displayItem => ( + + + + {displayItem.copy} + + + + {displayItem.value !== '' + ? displayItem.value + : t('option_disabled')} + + + + + + )) + : null} + {selectedSetting === 'dispense_tip_position' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + {selectedSetting === 'dispense_mix' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + {selectedSetting === 'dispense_delay' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + {selectedSetting === 'dispense_touch_tip' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + {selectedSetting === 'dispense_air_gap' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + {selectedSetting === 'dispense_blow_out' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + + + + ) +} diff --git a/app/src/organisms/QuickTransferFlow/SaveOrRunModal.tsx b/app/src/organisms/ODD/QuickTransferFlow/SaveOrRunModal.tsx similarity index 75% rename from app/src/organisms/QuickTransferFlow/SaveOrRunModal.tsx rename to app/src/organisms/ODD/QuickTransferFlow/SaveOrRunModal.tsx index cfcb20835b6..c9e57469ac0 100644 --- a/app/src/organisms/QuickTransferFlow/SaveOrRunModal.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/SaveOrRunModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { SPACING, @@ -8,8 +8,8 @@ import { DIRECTION_COLUMN, TYPOGRAPHY, } from '@opentrons/components' -import { OddModal } from '../../molecules/OddModal' -import { SmallButton } from '../../atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' +import { SmallButton } from '/app/atoms/buttons' import { NameQuickTransfer } from './NameQuickTransfer' interface SaveOrRunModalProps { @@ -19,7 +19,8 @@ interface SaveOrRunModalProps { export const SaveOrRunModal = (props: SaveOrRunModalProps): JSX.Element => { const { t } = useTranslation('quick_transfer') - const [showNameTransfer, setShowNameTransfer] = React.useState(false) + const [showNameTransfer, setShowNameTransfer] = useState(false) + const [isLoading, setIsLoading] = useState(false) return showNameTransfer ? ( @@ -43,6 +44,7 @@ export const SaveOrRunModal = (props: SaveOrRunModalProps): JSX.Element => { { setShowNameTransfer(true) }} @@ -51,7 +53,11 @@ export const SaveOrRunModal = (props: SaveOrRunModalProps): JSX.Element => { { + setIsLoading(true) + props.onRun() + }} />
    diff --git a/app/src/organisms/QuickTransferFlow/SelectDestLabware.tsx b/app/src/organisms/ODD/QuickTransferFlow/SelectDestLabware.tsx similarity index 86% rename from app/src/organisms/QuickTransferFlow/SelectDestLabware.tsx rename to app/src/organisms/ODD/QuickTransferFlow/SelectDestLabware.tsx index 1c3e656c6d4..62921e8e08d 100644 --- a/app/src/organisms/QuickTransferFlow/SelectDestLabware.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/SelectDestLabware.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { Flex, @@ -12,12 +12,13 @@ import { RadioButton, } from '@opentrons/components' -import { ChildNavigation } from '../ChildNavigation' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' import { getCompatibleLabwareByCategory } from './utils' +import type { ComponentProps, Dispatch } from 'react' import type { LabwareDefinition2 } from '@opentrons/shared-data' -import type { SmallButton } from '../../atoms/buttons' -import type { LabwareFilter } from '../../pages/Labware/types' +import type { SmallButton } from '/app/atoms/buttons' +import type { LabwareFilter } from '/app/local-resources/labware' import type { QuickTransferWizardState, QuickTransferWizardAction, @@ -26,9 +27,9 @@ import type { interface SelectDestLabwareProps { onNext: () => void onBack: () => void - exitButtonProps: React.ComponentProps + exitButtonProps: ComponentProps state: QuickTransferWizardState - dispatch: React.Dispatch + dispatch: Dispatch } export function SelectDestLabware( @@ -44,10 +45,8 @@ export function SelectDestLabware( if (state.pipette?.channels === 1) { labwareDisplayCategoryFilters.push('tubeRack') } - const [selectedCategory, setSelectedCategory] = React.useState( - 'all' - ) - const [selectedLabware, setSelectedLabware] = React.useState< + const [selectedCategory, setSelectedCategory] = useState('all') + const [selectedLabware, setSelectedLabware] = useState< LabwareDefinition2 | 'source' | undefined >(state.destination) @@ -117,8 +116,8 @@ export function SelectDestLabware( onChange={() => { setSelectedLabware('source') }} - buttonLabel={t('source_labware_d2')} - buttonValue="source-labware-d2" + buttonLabel={t('source_labware_c2')} + buttonValue="source-labware-c2" subButtonLabel={state.source.metadata.displayName} /> ) : null} diff --git a/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx b/app/src/organisms/ODD/QuickTransferFlow/SelectDestWells.tsx similarity index 77% rename from app/src/organisms/QuickTransferFlow/SelectDestWells.tsx rename to app/src/organisms/ODD/QuickTransferFlow/SelectDestWells.tsx index e526d28bcc2..41dcf155fb7 100644 --- a/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/SelectDestWells.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import without from 'lodash/without' @@ -11,19 +11,27 @@ import { JUSTIFY_CENTER, } from '@opentrons/components' import { getAllDefinitions } from '@opentrons/shared-data' +import { ANALYTICS_QUICK_TRANSFER_WELL_SELECTION_DURATION } from '/app/redux/analytics' -import { getTopPortalEl } from '../../App/portal' -import { OddModal } from '../../molecules/OddModal' -import { ChildNavigation } from '../../organisms/ChildNavigation' -import { useToaster } from '../../organisms/ToasterOven' -import { WellSelection } from '../../organisms/WellSelection' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { getTopPortalEl } from '/app/App/portal' +import { OddModal } from '/app/molecules/OddModal' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { useToaster } from '/app/organisms/ToasterOven' +import { WellSelection } from '/app/organisms/WellSelection' import { CIRCULAR_WELL_96_PLATE_DEFINITION_URI, RECTANGULAR_WELL_96_PLATE_DEFINITION_URI, } from './SelectSourceWells' -import type { SmallButton } from '../../atoms/buttons' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { + ComponentProps, + Dispatch, + SetStateAction, + MouseEvent, +} from 'react' +import type { SmallButton } from '/app/atoms/buttons' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' import type { QuickTransferWizardState, QuickTransferWizardAction, @@ -33,12 +41,13 @@ interface SelectDestWellsProps { onNext: () => void onBack: () => void state: QuickTransferWizardState - dispatch: React.Dispatch + dispatch: Dispatch } export function SelectDestWells(props: SelectDestWellsProps): JSX.Element { const { onNext, onBack, state, dispatch } = props const { i18n, t } = useTranslation(['quick_transfer', 'shared']) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const { makeToast } = useToaster() @@ -50,12 +59,11 @@ export function SelectDestWells(props: SelectDestWellsProps): JSX.Element { const [ showNumberWellsSelectedErrorModal, setShowNumberWellsSelectedErrorModal, - ] = React.useState(false) - const [selectedWells, setSelectedWells] = React.useState(destinationWellGroup) - const [ - isNumberWellsSelectedError, - setIsNumberWellsSelectedError, - ] = React.useState(false) + ] = useState(false) + const [selectedWells, setSelectedWells] = useState(destinationWellGroup) + const [isNumberWellsSelectedError, setIsNumberWellsSelectedError] = useState( + false + ) const selectedWellCount = Object.keys(selectedWells).length const sourceWellCount = state.sourceWells?.length ?? 0 @@ -72,6 +80,21 @@ export function SelectDestWells(props: SelectDestWellsProps): JSX.Element { selectionUnits = t('grids') } + let labwareDefinition = + state.destination === 'source' ? state.source : state.destination + if (labwareDefinition?.parameters.format === '96Standard') { + const allDefinitions = getAllDefinitions() + if (Object.values(labwareDefinition.wells)[0].shape === 'circular') { + labwareDefinition = allDefinitions[CIRCULAR_WELL_96_PLATE_DEFINITION_URI] + } else { + labwareDefinition = + allDefinitions[RECTANGULAR_WELL_96_PLATE_DEFINITION_URI] + } + } + const is384WellPlate = labwareDefinition?.parameters.format === '384Standard' + + const [analyticsStartTime] = useState(new Date()) + const handleClickNext = (): void => { if ( selectedWellCount === 1 || @@ -82,6 +105,14 @@ export function SelectDestWells(props: SelectDestWellsProps): JSX.Element { type: 'SET_DEST_WELLS', wells: Object.keys(selectedWells), }) + const duration = new Date().getTime() - analyticsStartTime.getTime() + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_WELL_SELECTION_DURATION, + properties: { + is384WellPlate, + duration: `${duration / 1000} seconds`, + }, + }) onNext() } else { setIsNumberWellsSelectedError(true) @@ -104,25 +135,16 @@ export function SelectDestWells(props: SelectDestWellsProps): JSX.Element { } } - const resetButtonProps: React.ComponentProps = { + const resetButtonProps: ComponentProps = { buttonType: 'tertiaryLowLight', buttonText: t('shared:reset'), - onClick: () => { + onClick: (e: MouseEvent) => { setIsNumberWellsSelectedError(false) setSelectedWells({}) + e.currentTarget.blur?.() }, } - let labwareDefinition = - state.destination === 'source' ? state.source : state.destination - if (labwareDefinition?.parameters.format === '96Standard') { - const allDefinitions = getAllDefinitions() - if (Object.values(labwareDefinition.wells)[0].shape === 'circular') { - labwareDefinition = allDefinitions[CIRCULAR_WELL_96_PLATE_DEFINITION_URI] - } else { - labwareDefinition = - allDefinitions[RECTANGULAR_WELL_96_PLATE_DEFINITION_URI] - } - } + return ( <> {createPortal( @@ -158,13 +180,7 @@ export function SelectDestWells(props: SelectDestWellsProps): JSX.Element { width="100%" > {labwareDefinition != null ? ( - + { @@ -203,9 +219,7 @@ function NumberWellsSelectedErrorModal({ selectionUnit, selectionUnits, }: { - setShowNumberWellsSelectedErrorModal: React.Dispatch< - React.SetStateAction - > + setShowNumberWellsSelectedErrorModal: Dispatch> wellCount: number selectionUnit: string selectionUnits: string diff --git a/app/src/organisms/QuickTransferFlow/SelectPipette.tsx b/app/src/organisms/ODD/QuickTransferFlow/SelectPipette.tsx similarity index 86% rename from app/src/organisms/QuickTransferFlow/SelectPipette.tsx rename to app/src/organisms/ODD/QuickTransferFlow/SelectPipette.tsx index a9e87d00831..0b01a93977d 100644 --- a/app/src/organisms/QuickTransferFlow/SelectPipette.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/SelectPipette.tsx @@ -1,20 +1,21 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { + DIRECTION_COLUMN, Flex, - SPACING, LegacyStyledText, - TYPOGRAPHY, - DIRECTION_COLUMN, RadioButton, + SPACING, + TYPOGRAPHY, } from '@opentrons/components' import { useInstrumentsQuery } from '@opentrons/react-api-client' import { RIGHT, LEFT } from '@opentrons/shared-data' -import { usePipetteSpecsV2 } from '../../resources/instruments/hooks' -import { ChildNavigation } from '../ChildNavigation' +import { usePipetteSpecsV2 } from '/app/local-resources/instruments' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import type { ComponentProps, Dispatch } from 'react' import type { PipetteData, Mount } from '@opentrons/api-client' -import type { SmallButton } from '../../atoms/buttons' +import type { SmallButton } from '/app/atoms/buttons' import type { QuickTransferWizardState, QuickTransferWizardAction, @@ -23,9 +24,9 @@ import type { interface SelectPipetteProps { onNext: () => void onBack: () => void - exitButtonProps: React.ComponentProps + exitButtonProps: ComponentProps state: QuickTransferWizardState - dispatch: React.Dispatch + dispatch: Dispatch } export function SelectPipette(props: SelectPipetteProps): JSX.Element { @@ -44,9 +45,9 @@ export function SelectPipette(props: SelectPipetteProps): JSX.Element { const rightPipetteSpecs = usePipetteSpecsV2(rightPipette?.instrumentModel) // automatically select 96 channel if it is attached - const [selectedPipette, setSelectedPipette] = React.useState< - Mount | undefined - >(leftPipetteSpecs?.channels === 96 ? LEFT : state.mount) + const [selectedPipette, setSelectedPipette] = useState( + leftPipetteSpecs?.channels === 96 ? LEFT : state.mount + ) const handleClickNext = (): void => { const selectedPipetteSpecs = diff --git a/app/src/organisms/QuickTransferFlow/SelectSourceLabware.tsx b/app/src/organisms/ODD/QuickTransferFlow/SelectSourceLabware.tsx similarity index 87% rename from app/src/organisms/QuickTransferFlow/SelectSourceLabware.tsx rename to app/src/organisms/ODD/QuickTransferFlow/SelectSourceLabware.tsx index 9407332351e..c51e4782c1d 100644 --- a/app/src/organisms/QuickTransferFlow/SelectSourceLabware.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/SelectSourceLabware.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { Flex, @@ -12,12 +12,13 @@ import { RadioButton, } from '@opentrons/components' -import { ChildNavigation } from '../ChildNavigation' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' import { getCompatibleLabwareByCategory } from './utils' +import type { ComponentProps, Dispatch } from 'react' import type { LabwareDefinition2 } from '@opentrons/shared-data' -import type { SmallButton } from '../../atoms/buttons' -import type { LabwareFilter } from '../../pages/Labware/types' +import type { SmallButton } from '/app/atoms/buttons' +import type { LabwareFilter } from '/app/local-resources/labware' import type { QuickTransferWizardState, QuickTransferWizardAction, @@ -26,9 +27,9 @@ import type { interface SelectSourceLabwareProps { onNext: () => void onBack: () => void - exitButtonProps: React.ComponentProps + exitButtonProps: ComponentProps state: QuickTransferWizardState - dispatch: React.Dispatch + dispatch: Dispatch } export function SelectSourceLabware( @@ -44,11 +45,9 @@ export function SelectSourceLabware( if (state.pipette?.channels === 1) { labwareDisplayCategoryFilters.push('tubeRack') } - const [selectedCategory, setSelectedCategory] = React.useState( - 'all' - ) + const [selectedCategory, setSelectedCategory] = useState('all') - const [selectedLabware, setSelectedLabware] = React.useState< + const [selectedLabware, setSelectedLabware] = useState< LabwareDefinition2 | undefined >(state.source) diff --git a/app/src/organisms/ODD/QuickTransferFlow/SelectSourceWells.tsx b/app/src/organisms/ODD/QuickTransferFlow/SelectSourceWells.tsx new file mode 100644 index 00000000000..1a643780e08 --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/SelectSourceWells.tsx @@ -0,0 +1,132 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import without from 'lodash/without' +import { + Flex, + JUSTIFY_CENTER, + POSITION_FIXED, + SPACING, +} from '@opentrons/components' +import { getAllDefinitions } from '@opentrons/shared-data' + +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { WellSelection } from '/app/organisms/WellSelection' +import { ANALYTICS_QUICK_TRANSFER_WELL_SELECTION_DURATION } from '/app/redux/analytics' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' + +import type { ComponentProps, Dispatch, MouseEvent } from 'react' +import type { SmallButton } from '/app/atoms/buttons' +import type { + QuickTransferWizardState, + QuickTransferWizardAction, +} from './types' + +interface SelectSourceWellsProps { + onNext: () => void + onBack: () => void + state: QuickTransferWizardState + dispatch: Dispatch +} + +export const CIRCULAR_WELL_96_PLATE_DEFINITION_URI = + 'opentrons/thermoscientificnunc_96_wellplate_2000ul/1' +export const RECTANGULAR_WELL_96_PLATE_DEFINITION_URI = + 'opentrons/usascientific_96_wellplate_2.4ml_deep/1' + +export function SelectSourceWells(props: SelectSourceWellsProps): JSX.Element { + const { onNext, onBack, state, dispatch } = props + const { i18n, t } = useTranslation(['quick_transfer', 'shared']) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() + + const sourceWells = state.sourceWells ?? [] + const sourceWellGroup = sourceWells.reduce((acc, well) => { + return { ...acc, [well]: null } + }, {}) + + const [selectedWells, setSelectedWells] = useState(sourceWellGroup) + const [startingTimeStamp] = useState(new Date()) + const is384WellPlate = state.source?.parameters.format === '384Standard' + + const handleClickNext = (): void => { + dispatch({ + type: 'SET_SOURCE_WELLS', + wells: Object.keys(selectedWells), + }) + const duration = new Date().getTime() - startingTimeStamp?.getTime() + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_WELL_SELECTION_DURATION, + properties: { + is384WellPlate, + duration: `${duration / 1000} seconds`, + }, + }) + onNext() + } + + const resetButtonProps: ComponentProps = { + buttonType: 'tertiaryLowLight', + buttonText: t('shared:reset'), + onClick: (e: MouseEvent) => { + setSelectedWells({}) + e.currentTarget.blur?.() + }, + } + let displayLabwareDefinition = state.source + if (state.source?.parameters.format === '96Standard') { + const allDefinitions = getAllDefinitions() + if (Object.values(state.source.wells)[0].shape === 'circular') { + displayLabwareDefinition = + allDefinitions[CIRCULAR_WELL_96_PLATE_DEFINITION_URI] + } else { + displayLabwareDefinition = + allDefinitions[RECTANGULAR_WELL_96_PLATE_DEFINITION_URI] + } + } + + return ( + <> + + + {state.source != null && displayLabwareDefinition != null ? ( + + { + setSelectedWells(prevWells => + without(Object.keys(prevWells), ...wells).reduce( + (acc, well) => { + return { ...acc, [well]: null } + }, + {} + ) + ) + }} + selectedPrimaryWells={selectedWells} + selectWells={wellGroup => { + setSelectedWells(prevWells => ({ ...prevWells, ...wellGroup })) + }} + channels={state.pipette?.channels ?? 1} + /> + + ) : null} + + + ) +} diff --git a/app/src/organisms/QuickTransferFlow/SelectTipRack.tsx b/app/src/organisms/ODD/QuickTransferFlow/SelectTipRack.tsx similarity index 86% rename from app/src/organisms/QuickTransferFlow/SelectTipRack.tsx rename to app/src/organisms/ODD/QuickTransferFlow/SelectTipRack.tsx index 3ef35fb9677..f5fd1abae85 100644 --- a/app/src/organisms/QuickTransferFlow/SelectTipRack.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/SelectTipRack.tsx @@ -1,16 +1,17 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { - Flex, - SPACING, DIRECTION_COLUMN, + Flex, RadioButton, + SPACING, } from '@opentrons/components' import { getAllDefinitions } from '@opentrons/shared-data' -import { ChildNavigation } from '../ChildNavigation' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import type { ComponentProps, Dispatch } from 'react' import type { LabwareDefinition2 } from '@opentrons/shared-data' -import type { SmallButton } from '../../atoms/buttons' +import type { SmallButton } from '/app/atoms/buttons' import type { QuickTransferWizardState, QuickTransferWizardAction, @@ -19,9 +20,9 @@ import type { interface SelectTipRackProps { onNext: () => void onBack: () => void - exitButtonProps: React.ComponentProps + exitButtonProps: ComponentProps state: QuickTransferWizardState - dispatch: React.Dispatch + dispatch: Dispatch } export function SelectTipRack(props: SelectTipRackProps): JSX.Element { @@ -32,7 +33,7 @@ export function SelectTipRack(props: SelectTipRackProps): JSX.Element { const selectedPipetteDefaultTipracks = state.pipette?.liquids.default.defaultTipracks ?? [] - const [selectedTipRack, setSelectedTipRack] = React.useState< + const [selectedTipRack, setSelectedTipRack] = useState< LabwareDefinition2 | undefined >(state.tipRack) diff --git a/app/src/organisms/ODD/QuickTransferFlow/SummaryAndSettings.tsx b/app/src/organisms/ODD/QuickTransferFlow/SummaryAndSettings.tsx new file mode 100644 index 00000000000..2567b703b93 --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/SummaryAndSettings.tsx @@ -0,0 +1,184 @@ +import { useState, useReducer } from 'react' +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' +import { useQueryClient } from 'react-query' +import { + Flex, + SPACING, + DIRECTION_COLUMN, + DIRECTION_ROW, + COLORS, + POSITION_FIXED, + ALIGN_CENTER, + Tabs, +} from '@opentrons/components' +import { + useCreateProtocolMutation, + useCreateRunMutation, + useHost, +} from '@opentrons/react-api-client' + +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' +import { + ANALYTICS_QUICK_TRANSFER_TIME_TO_CREATE, + ANALYTICS_QUICK_TRANSFER_SAVE_FOR_LATER, + ANALYTICS_QUICK_TRANSFER_RUN_NOW, +} from '/app/redux/analytics' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { Overview } from './Overview' +import { TipManagement } from './TipManagement' +import { QuickTransferAdvancedSettings } from './QuickTransferAdvancedSettings' +import { SaveOrRunModal } from './SaveOrRunModal' +import { getInitialSummaryState, createQuickTransferFile } from './utils' +import { quickTransferSummaryReducer } from './reducers' + +import type { ComponentProps } from 'react' +import type { SmallButton } from '/app/atoms/buttons' +import type { QuickTransferWizardState } from './types' + +interface SummaryAndSettingsProps { + exitButtonProps: ComponentProps + state: QuickTransferWizardState + analyticsStartTime: Date +} + +export function SummaryAndSettings( + props: SummaryAndSettingsProps +): JSX.Element | null { + const { exitButtonProps, state: wizardFlowState, analyticsStartTime } = props + const navigate = useNavigate() + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() + const queryClient = useQueryClient() + const host = useHost() + const { t } = useTranslation(['quick_transfer', 'shared']) + const [showSaveOrRunModal, setShowSaveOrRunModal] = useState(false) + + const displayCategory: string[] = [ + 'overview', + 'advanced_settings', + 'tip_management', + ] + const [selectedCategory, setSelectedCategory] = useState('overview') + const deckConfig = useNotifyDeckConfigurationQuery().data ?? [] + + const initialSummaryState = getInitialSummaryState({ + // @ts-expect-error TODO figure out how to make this type non-null as we know + // none of these values will be undefined + state: wizardFlowState, + deckConfig, + }) + const [state, dispatch] = useReducer( + quickTransferSummaryReducer, + initialSummaryState + ) + + const { mutateAsync: createProtocolAsync } = useCreateProtocolMutation() + + const { createRun } = useCreateRunMutation( + { + onSuccess: data => { + queryClient.invalidateQueries([host, 'runs']).catch((e: Error) => { + console.error(`error invalidating runs query: ${e.message}`) + }) + navigate(`/runs/${data.data.id}/setup`) + }, + }, + host + ) + + const handleClickCreateTransfer = (): void => { + setShowSaveOrRunModal(true) + const duration = new Date().getTime() - analyticsStartTime.getTime() + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_TIME_TO_CREATE, + properties: { + duration: `${duration / 1000} seconds`, + }, + }) + } + + const handleClickSave = (protocolName: string): void => { + const protocolFile = createQuickTransferFile( + state, + deckConfig, + protocolName + ) + createProtocolAsync({ + files: [protocolFile], + protocolKind: 'quick-transfer', + }).then(() => { + navigate('/quick-transfer') + }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SAVE_FOR_LATER, + properties: { + name: protocolName, + }, + }) + } + + const handleClickRun = (): void => { + const protocolFile = createQuickTransferFile(state, deckConfig) + createProtocolAsync({ + files: [protocolFile], + protocolKind: 'quick-transfer', + }).then(data => { + createRun({ + protocolId: data.data.id, + }) + }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_RUN_NOW, + properties: {}, + }) + } + + return showSaveOrRunModal ? ( + + ) : ( + + + + + ({ + text: t(category), + onClick: () => { + setSelectedCategory(category) + }, + isActive: category === selectedCategory, + disabled: false, + }))} + /> + + {selectedCategory === 'overview' ? : null} + {selectedCategory === 'advanced_settings' ? ( + + ) : null} + {selectedCategory === 'tip_management' ? ( + + ) : null} + + + ) +} diff --git a/app/src/organisms/QuickTransferFlow/TipManagement/ChangeTip.tsx b/app/src/organisms/ODD/QuickTransferFlow/TipManagement/ChangeTip.tsx similarity index 77% rename from app/src/organisms/QuickTransferFlow/TipManagement/ChangeTip.tsx rename to app/src/organisms/ODD/QuickTransferFlow/TipManagement/ChangeTip.tsx index d0830336e44..dd3869ff329 100644 --- a/app/src/organisms/QuickTransferFlow/TipManagement/ChangeTip.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/TipManagement/ChangeTip.tsx @@ -1,6 +1,7 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom' + import { Flex, SPACING, @@ -9,9 +10,13 @@ import { COLORS, RadioButton, } from '@opentrons/components' -import { getTopPortalEl } from '../../../App/portal' -import { ChildNavigation } from '../../ChildNavigation' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '/app/redux/analytics' +import { getTopPortalEl } from '/app/App/portal' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' + +import type { Dispatch } from 'react' import type { ChangeTipOptions, QuickTransferSummaryState, @@ -21,12 +26,13 @@ import type { interface ChangeTipProps { onBack: () => void state: QuickTransferSummaryState - dispatch: React.Dispatch + dispatch: Dispatch } export function ChangeTip(props: ChangeTipProps): JSX.Element { const { onBack, state, dispatch } = props const { t } = useTranslation('quick_transfer') + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const allowedChangeTipOptions: ChangeTipOptions[] = ['once'] if ( @@ -48,7 +54,7 @@ export function ChangeTip(props: ChangeTipProps): JSX.Element { const [ selectedChangeTipOption, setSelectedChangeTipOption, - ] = React.useState(state.changeTip) + ] = useState(state.changeTip) const handleClickSave = (): void => { if (selectedChangeTipOption !== state.changeTip) { @@ -56,6 +62,12 @@ export function ChangeTip(props: ChangeTipProps): JSX.Element { type: 'SET_CHANGE_TIP', changeTip: selectedChangeTipOption, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: 'ChangeTip', + }, + }) } onBack() } diff --git a/app/src/organisms/QuickTransferFlow/TipManagement/TipDropLocation.tsx b/app/src/organisms/ODD/QuickTransferFlow/TipManagement/TipDropLocation.tsx similarity index 79% rename from app/src/organisms/QuickTransferFlow/TipManagement/TipDropLocation.tsx rename to app/src/organisms/ODD/QuickTransferFlow/TipManagement/TipDropLocation.tsx index 58f20918b82..b61a73389cd 100644 --- a/app/src/organisms/QuickTransferFlow/TipManagement/TipDropLocation.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/TipManagement/TipDropLocation.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { createPortal } from 'react-dom' import { @@ -14,10 +14,14 @@ import { FLEX_SINGLE_SLOT_BY_CUTOUT_ID, TRASH_BIN_ADAPTER_FIXTURE, } from '@opentrons/shared-data' -import { getTopPortalEl } from '../../../App/portal' -import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' -import { ChildNavigation } from '../../ChildNavigation' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '/app/redux/analytics' +import { getTopPortalEl } from '/app/App/portal' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' + +import type { Dispatch } from 'react' import type { QuickTransferSummaryState, QuickTransferSummaryAction, @@ -27,12 +31,13 @@ import type { CutoutConfig } from '@opentrons/shared-data' interface TipDropLocationProps { onBack: () => void state: QuickTransferSummaryState - dispatch: React.Dispatch + dispatch: Dispatch } export function TipDropLocation(props: TipDropLocationProps): JSX.Element { const { onBack, state, dispatch } = props const { t } = useTranslation('quick_transfer') + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const deckConfig = useNotifyDeckConfigurationQuery().data ?? [] const tipDropLocationOptions = deckConfig.filter( @@ -52,7 +57,7 @@ export function TipDropLocation(props: TipDropLocationProps): JSX.Element { const [ selectedTipDropLocation, setSelectedTipDropLocation, - ] = React.useState(state.dropTipLocation) + ] = useState(state.dropTipLocation) const handleClickSave = (): void => { if (selectedTipDropLocation.cutoutId !== state.dropTipLocation.cutoutId) { @@ -60,6 +65,12 @@ export function TipDropLocation(props: TipDropLocationProps): JSX.Element { type: 'SET_DROP_TIP_LOCATION', location: selectedTipDropLocation, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: 'TipDropLocation', + }, + }) } onBack() } diff --git a/app/src/organisms/ODD/QuickTransferFlow/TipManagement/index.tsx b/app/src/organisms/ODD/QuickTransferFlow/TipManagement/index.tsx new file mode 100644 index 00000000000..4a87e67c2c7 --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/TipManagement/index.tsx @@ -0,0 +1,125 @@ +import { useState, useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { + ALIGN_CENTER, + COLORS, + DIRECTION_COLUMN, + Flex, + Icon, + JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, + ListItem, + SIZE_2, + SPACING, + TEXT_ALIGN_RIGHT, + TYPOGRAPHY, +} from '@opentrons/components' +import { TRASH_BIN_ADAPTER_FIXTURE } from '@opentrons/shared-data' + +import { ANALYTICS_QUICK_TRANSFER_TIP_MANAGEMENT_TAB } from '/app/redux/analytics' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { ChangeTip } from './ChangeTip' +import { TipDropLocation } from './TipDropLocation' + +import type { Dispatch } from 'react' +import type { + QuickTransferSummaryAction, + QuickTransferSummaryState, +} from '../types' + +interface TipManagementProps { + state: QuickTransferSummaryState + dispatch: Dispatch +} + +export function TipManagement(props: TipManagementProps): JSX.Element | null { + const { state, dispatch } = props + const { t } = useTranslation(['quick_transfer', 'shared']) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() + const [selectedSetting, setSelectedSetting] = useState(null) + + useEffect(() => { + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_TIP_MANAGEMENT_TAB, + properties: {}, + }) + }, []) + + const displayItems = [ + { + option: t('change_tip'), + value: t(`${state.changeTip}`), + onClick: () => { + setSelectedSetting('change_tip') + }, + }, + { + option: t('tip_drop_location'), + value: t( + `${ + state.dropTipLocation.cutoutFixtureId === TRASH_BIN_ADAPTER_FIXTURE + ? 'trashBin' + : 'wasteChute' + }` + ), + onClick: () => { + setSelectedSetting('tip_drop_location') + }, + }, + ] + + return ( + + {selectedSetting == null + ? displayItems.map(displayItem => ( + + + + {displayItem.option} + + + + {displayItem.value} + + + + + + )) + : null} + {selectedSetting === 'change_tip' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + {selectedSetting === 'tip_drop_location' ? ( + { + setSelectedSetting(null) + }} + /> + ) : null} + + ) +} diff --git a/app/src/organisms/QuickTransferFlow/VolumeEntry.tsx b/app/src/organisms/ODD/QuickTransferFlow/VolumeEntry.tsx similarity index 87% rename from app/src/organisms/QuickTransferFlow/VolumeEntry.tsx rename to app/src/organisms/ODD/QuickTransferFlow/VolumeEntry.tsx index 231b89a02bf..58bf2f570e7 100644 --- a/app/src/organisms/QuickTransferFlow/VolumeEntry.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/VolumeEntry.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useRef } from 'react' import { useTranslation } from 'react-i18next' import { @@ -9,12 +9,13 @@ import { SPACING, } from '@opentrons/components' -import { ChildNavigation } from '../ChildNavigation' -import { NumericalKeyboard } from '../../atoms/SoftwareKeyboard' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { NumericalKeyboard } from '/app/atoms/SoftwareKeyboard' import { getVolumeRange } from './utils' import { CONSOLIDATE, DISTRIBUTE } from './constants' -import type { SmallButton } from '../../atoms/buttons' +import type { ComponentProps, Dispatch } from 'react' +import type { SmallButton } from '/app/atoms/buttons' import type { QuickTransferWizardState, QuickTransferWizardAction, @@ -23,17 +24,17 @@ import type { interface VolumeEntryProps { onNext: () => void onBack: () => void - exitButtonProps: React.ComponentProps + exitButtonProps: ComponentProps state: QuickTransferWizardState - dispatch: React.Dispatch + dispatch: Dispatch } export function VolumeEntry(props: VolumeEntryProps): JSX.Element { const { onNext, onBack, exitButtonProps, state, dispatch } = props const { i18n, t } = useTranslation(['quick_transfer', 'shared']) - const keyboardRef = React.useRef(null) + const keyboardRef = useRef(null) - const [volume, setVolume] = React.useState( + const [volume, setVolume] = useState( state.volume ? state.volume.toString() : '' ) const volumeRange = getVolumeRange(state) diff --git a/app/src/organisms/QuickTransferFlow/__tests__/ConfirmExitModal.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/ConfirmExitModal.test.tsx similarity index 89% rename from app/src/organisms/QuickTransferFlow/__tests__/ConfirmExitModal.test.tsx rename to app/src/organisms/ODD/QuickTransferFlow/__tests__/ConfirmExitModal.test.tsx index f1e3681a93a..4b50c31ca29 100644 --- a/app/src/organisms/QuickTransferFlow/__tests__/ConfirmExitModal.test.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/ConfirmExitModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ConfirmExitModal } from '../ConfirmExitModal' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/QuickTransferFlow/__tests__/CreateNewTransfer.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/CreateNewTransfer.test.tsx similarity index 93% rename from app/src/organisms/QuickTransferFlow/__tests__/CreateNewTransfer.test.tsx rename to app/src/organisms/ODD/QuickTransferFlow/__tests__/CreateNewTransfer.test.tsx index abeba9a2b1d..178086ae401 100644 --- a/app/src/organisms/QuickTransferFlow/__tests__/CreateNewTransfer.test.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/CreateNewTransfer.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' import { DeckConfigurator } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { CreateNewTransfer } from '../CreateNewTransfer' import type * as OpentronsComponents from '@opentrons/components' diff --git a/app/src/organisms/QuickTransferFlow/__tests__/NameQuickTransfer.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/NameQuickTransfer.test.tsx similarity index 89% rename from app/src/organisms/QuickTransferFlow/__tests__/NameQuickTransfer.test.tsx rename to app/src/organisms/ODD/QuickTransferFlow/__tests__/NameQuickTransfer.test.tsx index ad2820d3921..363c89cdc82 100644 --- a/app/src/organisms/QuickTransferFlow/__tests__/NameQuickTransfer.test.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/NameQuickTransfer.test.tsx @@ -1,15 +1,15 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { NameQuickTransfer } from '../NameQuickTransfer' import type { InputField } from '@opentrons/components' vi.mock('../utils') -vi.mock('../../../atoms/InputField', async importOriginal => { +vi.mock('/app/atoms/InputField', async importOriginal => { const actualComponents = await importOriginal() return { ...actualComponents, @@ -52,6 +52,7 @@ describe('NameQuickTransfer', () => { expect(saveBtn).toBeEnabled() fireEvent.click(saveBtn) expect(props.onSave).toHaveBeenCalled() + expect(saveBtn).toBeDisabled() }) it('disables save if you enter more than 60 characters', () => { diff --git a/app/src/organisms/QuickTransferFlow/__tests__/Overview.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/Overview.test.tsx similarity index 95% rename from app/src/organisms/QuickTransferFlow/__tests__/Overview.test.tsx rename to app/src/organisms/ODD/QuickTransferFlow/__tests__/Overview.test.tsx index 12b079cecd6..242b0f58a92 100644 --- a/app/src/organisms/QuickTransferFlow/__tests__/Overview.test.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/Overview.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, afterEach, vi, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { Overview } from '../Overview' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/AirGap.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/AirGap.test.tsx new file mode 100644 index 00000000000..a4cc4a2879a --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/AirGap.test.tsx @@ -0,0 +1,276 @@ +import type * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' + +import { InputField } from '@opentrons/components' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { AirGap } from '../../QuickTransferAdvancedSettings/AirGap' +import type { QuickTransferSummaryState } from '../../types' + +vi.mock('/app/redux-resources/analytics') +vi.mock('../utils') + +vi.mock('@opentrons/components', async importOriginal => { + const actualComponents = await importOriginal() + return { + ...actualComponents, + InputField: vi.fn(), + } +}) + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} +let mockTrackEventWithRobotSerial: any + +describe('AirGap', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + onBack: vi.fn(), + kind: 'aspirate', + state: { + mount: 'left', + pipette: { + channels: 1, + liquids: [ + { + maxVolume: 1000, + minVolume: 5, + }, + ] as any, + } as any, + tipRack: { + wells: { + A1: { + totalLiquidVolume: 200, + }, + } as any, + } as any, + sourceWells: ['A1'], + destinationWells: ['A1'], + transferType: 'transfer', + volume: 20, + path: 'single', + } as QuickTransferSummaryState, + dispatch: vi.fn(), + } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders the first air gap screen, continue, and back buttons', () => { + render(props) + screen.getByText('Air gap after aspirating') + screen.getByTestId('ChildNavigation_Primary_Button') + screen.getByText('Enabled') + screen.getByText('Disabled') + const exitBtn = screen.getByTestId('ChildNavigation_Back_Button') + fireEvent.click(exitBtn) + expect(props.onBack).toHaveBeenCalled() + }) + + it('renders save button if you select enabled, then moves to second screen', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Air gap volume (µL)', + error: null, + readOnly: true, + type: 'number', + value: null, + }, + {} + ) + }) + + it('calls dispatch button if you select disabled and save', () => { + render(props) + const disabledBtn = screen.getByText('Disabled') + fireEvent.click(disabledBtn) + const saveBtn = screen.getByText('Save') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) + + it('has correct range for aspirate with a single pipette path', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const numButton = screen.getByText('0') + fireEvent.click(numButton) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Air gap volume (µL)', + error: 'Value must be between 1-180', + readOnly: true, + type: 'number', + value: 0, + }, + {} + ) + const saveBtn = screen.getByTestId('ChildNavigation_Primary_Button') + expect(saveBtn).toBeDisabled() + }) + + it('has correct range for aspirate with a multiAspirate pipette path', () => { + props = { + ...props, + state: { + ...props.state, + path: 'multiAspirate', + }, + } + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const numButton = screen.getByText('0') + fireEvent.click(numButton) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Air gap volume (µL)', + error: 'Value must be between 1-80', + readOnly: true, + type: 'number', + value: 0, + }, + {} + ) + }) + + it('has correct range for aspirate with a multiDispense pipette path', () => { + props = { + ...props, + state: { + ...props.state, + path: 'multiDispense', + }, + } + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const numButton = screen.getByText('0') + fireEvent.click(numButton) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Air gap volume (µL)', + error: 'Value must be between 1-140', + readOnly: true, + type: 'number', + value: 0, + }, + {} + ) + }) + + it('has correct range for and text for a dispense', () => { + props = { + ...props, + kind: 'dispense', + } + render(props) + screen.getByText('Air gap before dispensing') + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const numButton = screen.getByText('0') + fireEvent.click(numButton) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Air gap volume (µL)', + error: 'Value must be between 1-200', + readOnly: true, + type: 'number', + value: 0, + }, + {} + ) + }) + + it('calls dispatch when an in range value is entered and saved', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const numButton = screen.getByText('1') + fireEvent.click(numButton) + const saveBtn = screen.getByText('Save') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) + + it('persists existing values if they are in state for aspirate', () => { + props = { + ...props, + state: { + ...props.state, + airGapAspirate: 4, + }, + } + render(props) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Air gap volume (µL)', + error: null, + readOnly: true, + type: 'number', + value: 4, + }, + {} + ) + }) + + it('persists existing values if they are in state for dispense', () => { + props = { + ...props, + kind: 'dispense', + state: { + ...props.state, + airGapAspirate: 4, + airGapDispense: 16, + }, + } + render(props) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Air gap volume (µL)', + error: null, + readOnly: true, + type: 'number', + value: 16, + }, + {} + ) + }) +}) diff --git a/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/BlowOut.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/BlowOut.test.tsx new file mode 100644 index 00000000000..c75788ac8cd --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/BlowOut.test.tsx @@ -0,0 +1,171 @@ +import type * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { BlowOut } from '../../QuickTransferAdvancedSettings/BlowOut' +import type { QuickTransferSummaryState } from '../../types' + +vi.mock('/app/resources/deck_configuration') +vi.mock('/app/redux-resources/analytics') +vi.mock('../utils') + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} +let mockTrackEventWithRobotSerial: any + +describe('BlowOut', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + kind: 'aspirate', + onBack: vi.fn(), + state: { + mount: 'left', + pipette: { + channels: 1, + liquids: [ + { + maxVolume: 1000, + minVolume: 5, + }, + ] as any, + } as any, + tipRack: { + wells: { + A1: { + totalLiquidVolume: 200, + }, + } as any, + } as any, + sourceWells: ['A1'], + destinationWells: ['A1'], + transferType: 'transfer', + volume: 20, + path: 'single', + } as QuickTransferSummaryState, + dispatch: vi.fn(), + } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) + vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ + data: [ + { + cutoutId: 'cutoutC3', + cutoutFixtureId: 'wasteChuteRightAdapterCovered', + }, + { + cutoutId: 'cutoutA3', + cutoutFixtureId: 'trashBinAdapter', + }, + ], + } as any) + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders the first blow out screen, continue, and back buttons', () => { + render(props) + screen.getByText('Blowout after dispensing') + screen.getByTestId('ChildNavigation_Primary_Button') + screen.getByText('Enabled') + screen.getByText('Disabled') + const exitBtn = screen.getByTestId('ChildNavigation_Back_Button') + fireEvent.click(exitBtn) + expect(props.onBack).toHaveBeenCalled() + }) + + it('calls dispatch button if you select disabled and save', () => { + render(props) + const disabledBtn = screen.getByText('Disabled') + fireEvent.click(disabledBtn) + const saveBtn = screen.getByText('Save') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + }) + + it('second screen renders both source and destination wells and deck config trash options for transfer', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + screen.getByText('Source well') + screen.getByText('Destination well') + screen.getByText('Trash bin in A3') + screen.getByText('Waste chute in C3') + }) + + it('second screen renders trash bin in A3 if deck config is empty', () => { + vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ + data: [] as any, + } as any) + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + screen.getByText('Trash bin in A3') + }) + + it('second screen renders source well but not dest well for distribute', () => { + props = { + ...props, + state: { + ...props.state, + transferType: 'distribute', + }, + } + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + screen.getByText('Source well') + expect(screen.queryByText('Destination well')).not.toBeInTheDocument() + }) + + it('second screen renders dest well but not source well for consolidate', () => { + props = { + ...props, + state: { + ...props.state, + transferType: 'consolidate', + }, + } + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + screen.getByText('Destination well') + expect(screen.queryByText('Source well')).not.toBeInTheDocument() + }) + + it('enables save button when you make a destination selection and calls dispatch when saved', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const saveBtn = screen.getByTestId('ChildNavigation_Primary_Button') + expect(saveBtn).toBeDisabled() + const destBtn = screen.getByText('Destination well') + fireEvent.click(destBtn) + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) +}) diff --git a/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/Delay.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/Delay.test.tsx new file mode 100644 index 00000000000..957f3eb6e62 --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/Delay.test.tsx @@ -0,0 +1,293 @@ +import type * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' + +import { InputField } from '@opentrons/components' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { Delay } from '../../QuickTransferAdvancedSettings/Delay' +import type { QuickTransferSummaryState } from '../../types' + +vi.mock('/app/redux-resources/analytics') +vi.mock('../utils') + +vi.mock('@opentrons/components', async importOriginal => { + const actualComponents = await importOriginal() + return { + ...actualComponents, + InputField: vi.fn(), + } +}) + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} +let mockTrackEventWithRobotSerial: any + +describe('Delay', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + onBack: vi.fn(), + kind: 'aspirate', + state: { + mount: 'left', + pipette: { + channels: 1, + liquids: [ + { + maxVolume: 1000, + minVolume: 5, + }, + ] as any, + } as any, + source: { + wells: { + A1: { + totalLiquidVolume: 200, + depth: 50, + }, + } as any, + } as any, + destination: { + wells: { + A1: { + totalLiquidVolume: 200, + depth: 200, + }, + } as any, + } as any, + sourceWells: ['A1'], + destinationWells: ['A1'], + transferType: 'transfer', + volume: 20, + path: 'single', + } as QuickTransferSummaryState, + dispatch: vi.fn(), + } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders the first delay screen, continue, and back buttons', () => { + render(props) + screen.getByText('Delay after aspirating') + screen.getByTestId('ChildNavigation_Primary_Button') + screen.getByText('Enabled') + screen.getByText('Disabled') + const exitBtn = screen.getByTestId('ChildNavigation_Back_Button') + fireEvent.click(exitBtn) + expect(props.onBack).toHaveBeenCalled() + }) + + it('renders save button if you select enabled, then moves to second screen', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Delay duration (seconds)', + error: null, + readOnly: true, + type: 'number', + value: null, + }, + {} + ) + }) + + it('calls dispatch button if you select disabled and save', () => { + render(props) + const disabledBtn = screen.getByText('Disabled') + fireEvent.click(disabledBtn) + const saveBtn = screen.getByText('Save') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) + + it('has correct delay duration range', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const oneButton = screen.getByText('0') + fireEvent.click(oneButton) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Delay duration (seconds)', + error: 'Value must be between 1-9999999999', + readOnly: true, + type: 'number', + value: 0, + }, + {} + ) + const nextBtn = screen.getByTestId('ChildNavigation_Primary_Button') + expect(nextBtn).toBeDisabled() + }) + + it('has correct range for delay height for aspirate', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const oneButton = screen.getByText('1') + fireEvent.click(oneButton) + const nextBtn = screen.getByTestId('ChildNavigation_Primary_Button') + fireEvent.click(nextBtn) + const zeroButton = screen.getByText('0') + fireEvent.click(zeroButton) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Delay position from bottom of well (mm)', + error: 'Value must be between 1-100', + readOnly: true, + type: 'number', + value: 0, + }, + {} + ) + const saveBtn = screen.getByTestId('ChildNavigation_Primary_Button') + expect(saveBtn).toBeDisabled() + }) + + it('has correct range for delay height for dispense', () => { + props = { + ...props, + kind: 'dispense', + } + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const oneButton = screen.getByText('1') + fireEvent.click(oneButton) + const nextBtn = screen.getByTestId('ChildNavigation_Primary_Button') + fireEvent.click(nextBtn) + const zeroButton = screen.getByText('0') + fireEvent.click(zeroButton) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Delay position from bottom of well (mm)', + error: 'Value must be between 1-400', + readOnly: true, + type: 'number', + value: 0, + }, + {} + ) + const saveBtn = screen.getByTestId('ChildNavigation_Primary_Button') + expect(saveBtn).toBeDisabled() + }) + + it('calls dispatch when an in range value is entered and saved', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const oneButton = screen.getByText('1') + fireEvent.click(oneButton) + const nextBtn = screen.getByTestId('ChildNavigation_Primary_Button') + fireEvent.click(nextBtn) + const twoButton = screen.getByText('2') + fireEvent.click(twoButton) + const saveBtn = screen.getByText('Save') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) + + it('persists previously set value saved in state for aspirate', () => { + props = { + ...props, + state: { + ...props.state, + delayAspirate: { + delayDuration: 15, + positionFromBottom: 55, + }, + }, + } + render(props) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Delay duration (seconds)', + error: null, + readOnly: true, + type: 'number', + value: 15, + }, + {} + ) + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Delay position from bottom of well (mm)', + error: null, + readOnly: true, + type: 'number', + value: 55, + }, + {} + ) + }) + + it('persists previously set value saved in state for dispense', () => { + props = { + ...props, + kind: 'dispense', + state: { + ...props.state, + delayDispense: { + delayDuration: 20, + positionFromBottom: 84, + }, + }, + } + render(props) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Delay duration (seconds)', + error: null, + readOnly: true, + type: 'number', + value: 20, + }, + {} + ) + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Delay position from bottom of well (mm)', + error: null, + readOnly: true, + type: 'number', + value: 84, + }, + {} + ) + }) +}) diff --git a/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/FlowRate.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/FlowRate.test.tsx new file mode 100644 index 00000000000..4b01bb52ebe --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/FlowRate.test.tsx @@ -0,0 +1,158 @@ +import type * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' + +import { InputField } from '@opentrons/components' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { FlowRateEntry } from '../../QuickTransferAdvancedSettings/FlowRate' +import type { QuickTransferSummaryState } from '../../types' + +vi.mock('/app/redux-resources/analytics') +vi.mock('../utils') + +vi.mock('@opentrons/components', async importOriginal => { + const actualComponents = await importOriginal() + return { + ...actualComponents, + InputField: vi.fn(), + } +}) + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} +let mockTrackEventWithRobotSerial: any + +describe('FlowRate', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + onBack: vi.fn(), + kind: 'aspirate', + state: { + mount: 'left', + pipette: { + model: 'p50', + channels: 1, + liquids: { + default: { + maxVolume: 1000, + minVolume: 5, + supportedTips: { + t50: { + uiMaxFlowRate: 92, + defaultAspirateFlowRate: { + default: 30, + }, + defaultDispenseFlowRate: { + default: 80, + }, + }, + }, + }, + } as any, + } as any, + tipRack: { + wells: { + A1: { + totalLiquidVolume: 50, + }, + } as any, + } as any, + sourceWells: ['A1'], + destinationWells: ['A1'], + transferType: 'transfer', + volume: 20, + path: 'single', + aspirateFlowRate: 35, + dispenseFlowRate: 62, + } as QuickTransferSummaryState, + dispatch: vi.fn(), + } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders the flow rate aspirate screen, continue, and back buttons', () => { + render(props) + screen.getByText('Aspirate flow rate') + screen.getByTestId('ChildNavigation_Primary_Button') + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Aspirate flow rate (µL/s)', + error: null, + readOnly: true, + type: 'number', + value: 35, + }, + {} + ) + const exitBtn = screen.getByTestId('ChildNavigation_Back_Button') + fireEvent.click(exitBtn) + expect(props.onBack).toHaveBeenCalled() + }) + + it('renders the flow rate dispense screen', () => { + props = { + ...props, + kind: 'dispense', + } + render(props) + screen.getByText('Dispense flow rate') + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Dispense flow rate (µL/s)', + error: null, + readOnly: true, + type: 'number', + value: 62, + }, + {} + ) + }) + + it('renders correct range if you enter incorrect value', () => { + render(props) + const deleteBtn = screen.getByText('del') + fireEvent.click(deleteBtn) + fireEvent.click(deleteBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Aspirate flow rate (µL/s)', + error: 'Value must be between 1-92', + readOnly: true, + type: 'number', + value: 0, + }, + {} + ) + const saveBtn = screen.getByTestId('ChildNavigation_Primary_Button') + expect(saveBtn).toBeDisabled() + }) + + it('calls dispatch when an in range value is entered and saved', () => { + render(props) + const deleteBtn = screen.getByText('del') + fireEvent.click(deleteBtn) + fireEvent.click(deleteBtn) + const numButton = screen.getByText('1') + fireEvent.click(numButton) + const saveBtn = screen.getByText('Save') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) +}) diff --git a/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/Mix.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/Mix.test.tsx new file mode 100644 index 00000000000..c4d1c170be3 --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/Mix.test.tsx @@ -0,0 +1,263 @@ +import type * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' + +import { InputField } from '@opentrons/components' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { Mix } from '../../QuickTransferAdvancedSettings/Mix' +import type { QuickTransferSummaryState } from '../../types' + +vi.mock('/app/redux-resources/analytics') +vi.mock('../utils') + +vi.mock('@opentrons/components', async importOriginal => { + const actualComponents = await importOriginal() + return { + ...actualComponents, + InputField: vi.fn(), + } +}) + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} +let mockTrackEventWithRobotSerial: any + +describe('Mix', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + onBack: vi.fn(), + kind: 'aspirate', + state: { + mount: 'left', + pipette: { + channels: 1, + liquids: [ + { + maxVolume: 1000, + minVolume: 5, + }, + ] as any, + } as any, + tipRack: { + wells: { + A1: { + totalLiquidVolume: 200, + }, + } as any, + } as any, + sourceWells: ['A1'], + destinationWells: ['A1'], + transferType: 'transfer', + volume: 20, + path: 'single', + } as QuickTransferSummaryState, + dispatch: vi.fn(), + } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders the first Mix screen, continue, and back buttons', () => { + render(props) + screen.getByText('Mix before aspirating') + screen.getByTestId('ChildNavigation_Primary_Button') + screen.getByText('Enabled') + screen.getByText('Disabled') + const exitBtn = screen.getByTestId('ChildNavigation_Back_Button') + fireEvent.click(exitBtn) + expect(props.onBack).toHaveBeenCalled() + }) + + it('renders the different copy for Mix on dispense', () => { + props = { + ...props, + kind: 'dispense', + } + render(props) + screen.getByText('Mix before dispensing') + }) + + it('renders save button if you select enabled, then moves to second screen', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Mix volume (µL)', + error: null, + readOnly: true, + type: 'number', + value: null, + }, + {} + ) + }) + + it('calls dispatch button if you select disabled and save', () => { + render(props) + const disabledBtn = screen.getByText('Disabled') + fireEvent.click(disabledBtn) + const saveBtn = screen.getByText('Save') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) + + it('has correct Mix volume range', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const oneButton = screen.getByText('0') + fireEvent.click(oneButton) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Mix volume (µL)', + error: 'Value must be between 1-200', + readOnly: true, + type: 'number', + value: 0, + }, + {} + ) + const nextBtn = screen.getByTestId('ChildNavigation_Primary_Button') + expect(nextBtn).toBeDisabled() + }) + + it('has correct range for Mix repitition range', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const oneButton = screen.getByText('1') + fireEvent.click(oneButton) + const nextBtn = screen.getByTestId('ChildNavigation_Primary_Button') + fireEvent.click(nextBtn) + const zeroButton = screen.getByText('0') + fireEvent.click(zeroButton) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Mix repetitions', + error: 'Value must be between 1-999', + readOnly: true, + type: 'number', + value: 0, + }, + {} + ) + const saveBtn = screen.getByTestId('ChildNavigation_Primary_Button') + expect(saveBtn).toBeDisabled() + }) + + it('calls dispatch when an in range value is entered and saved', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const oneButton = screen.getByText('1') + fireEvent.click(oneButton) + const nextBtn = screen.getByTestId('ChildNavigation_Primary_Button') + fireEvent.click(nextBtn) + const twoButton = screen.getByText('2') + fireEvent.click(twoButton) + const saveBtn = screen.getByText('Save') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) + + it('persists previously set value saved in state for aspirate', () => { + props = { + ...props, + state: { + ...props.state, + mixOnAspirate: { + mixVolume: 15, + repititions: 55, + }, + }, + } + render(props) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Mix volume (µL)', + error: null, + readOnly: true, + type: 'number', + value: 15, + }, + {} + ) + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Mix repetitions', + error: null, + readOnly: true, + type: 'number', + value: 55, + }, + {} + ) + }) + + it('persists previously set value saved in state for dispense', () => { + props = { + ...props, + kind: 'dispense', + state: { + ...props.state, + mixOnDispense: { + mixVolume: 18, + repititions: 2, + }, + }, + } + render(props) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Mix volume (µL)', + error: null, + readOnly: true, + type: 'number', + value: 18, + }, + {} + ) + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Mix repetitions', + error: null, + readOnly: true, + type: 'number', + value: 2, + }, + {} + ) + }) +}) diff --git a/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/PipettePath.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/PipettePath.test.tsx new file mode 100644 index 00000000000..e62571bdc6a --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/PipettePath.test.tsx @@ -0,0 +1,230 @@ +import type * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' + +import { InputField } from '@opentrons/components' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { PipettePath } from '../../QuickTransferAdvancedSettings/PipettePath' +import { useBlowOutLocationOptions } from '../../QuickTransferAdvancedSettings/BlowOut' +import type { QuickTransferSummaryState } from '../../types' + +vi.mock('/app/redux-resources/analytics') +vi.mock('../utils') +vi.mock('../../QuickTransferAdvancedSettings/BlowOut') + +vi.mock('@opentrons/components', async importOriginal => { + const actualComponents = await importOriginal() + return { + ...actualComponents, + InputField: vi.fn(), + } +}) + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} +let mockTrackEventWithRobotSerial: any + +describe('PipettePath', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + onBack: vi.fn(), + state: { + mount: 'left', + pipette: { + channels: 1, + liquids: [ + { + maxVolume: 1000, + minVolume: 5, + }, + ] as any, + } as any, + tipRack: { + wells: { + A1: { + totalLiquidVolume: 200, + }, + } as any, + } as any, + transferType: 'consolidate', + volume: 20, + path: 'multiAspirate', + } as QuickTransferSummaryState, + dispatch: vi.fn(), + } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) + vi.mocked(useBlowOutLocationOptions).mockReturnValue([ + { + location: 'source_well', + description: 'Source well', + }, + ]) + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders the first pipette path screen, continue, back buttons', () => { + render(props) + screen.getByText('Pipette path') + screen.getByTestId('ChildNavigation_Primary_Button') + const exitBtn = screen.getByTestId('ChildNavigation_Back_Button') + fireEvent.click(exitBtn) + expect(props.onBack).toHaveBeenCalled() + }) + + it('renders multi aspirate and single options for consolidate if there is room in the tip', () => { + render(props) + screen.getByText('Single transfers') + screen.getByText('Multi-aspirate') + const saveBtn = screen.getByTestId('ChildNavigation_Primary_Button') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) + + it('renders single option only for consolidate if there is not room in the tip', () => { + props = { + ...props, + state: { + ...props.state, + volume: 101, + }, + } + render(props) + screen.getByText('Single transfers') + expect(screen.queryByText('Multi-aspirate')).not.toBeInTheDocument() + const saveBtn = screen.getByTestId('ChildNavigation_Primary_Button') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) + + it('renders multi dispense and single options for distribute if there is room in the tip', () => { + props = { + ...props, + state: { + ...props.state, + transferType: 'distribute', + }, + } + render(props) + screen.getByText('Single transfers') + screen.getByText('Multi-dispense') + const saveBtn = screen.getByTestId('ChildNavigation_Primary_Button') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) + + it('renders single option only for distribute if there is not room in the tip', () => { + props = { + ...props, + state: { + ...props.state, + transferType: 'distribute', + volume: 67, + }, + } + render(props) + screen.getByText('Single transfers') + expect(screen.queryByText('Multi-dispense')).not.toBeInTheDocument() + const saveBtn = screen.getByTestId('ChildNavigation_Primary_Button') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) + + it('renders next cta and disposal volume screen if you choose multi dispense', () => { + props = { + ...props, + state: { + ...props.state, + transferType: 'distribute', + disposalVolume: 20, + blowOut: 'source_well', + }, + } + render(props) + const multiDispenseBtn = screen.getByText('Multi-dispense') + fireEvent.click(multiDispenseBtn) + const continueBtn = screen.getByTestId('ChildNavigation_Primary_Button') + fireEvent.click(continueBtn) + + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Disposal volume (µL)', + error: null, + readOnly: true, + type: 'number', + value: 20, + }, + {} + ) + }) + + it('renders error on disposal volume screen if you select an out of range value', () => { + props = { + ...props, + state: { + ...props.state, + transferType: 'distribute', + path: 'multiDispense', + disposalVolume: 20, + blowOut: 'source_well', + }, + } + render(props) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const oneButton = screen.getByText('1') + fireEvent.click(oneButton) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Disposal volume (µL)', + error: 'Value must be between 1-160', + readOnly: true, + type: 'number', + value: 201, + }, + {} + ) + const nextBtn = screen.getByTestId('ChildNavigation_Primary_Button') + expect(nextBtn).toBeDisabled() + }) + + it('renders blowout options on third screen and calls dispatch when saved', () => { + props = { + ...props, + state: { + ...props.state, + transferType: 'distribute', + path: 'multiDispense', + disposalVolume: 20, + blowOut: 'source_well', + }, + } + render(props) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + fireEvent.click(continueBtn) + screen.getByText('Source well') + const saveBtn = screen.getByTestId('ChildNavigation_Primary_Button') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) +}) diff --git a/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/QuickTransferAdvancedSettings.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/QuickTransferAdvancedSettings.test.tsx new file mode 100644 index 00000000000..64e400f5a10 --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/QuickTransferAdvancedSettings.test.tsx @@ -0,0 +1,476 @@ +import type * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { QuickTransferAdvancedSettings } from '../../QuickTransferAdvancedSettings/' +import { useToaster } from '/app/organisms/ToasterOven' +import { PipettePath } from '../../QuickTransferAdvancedSettings/PipettePath' +import { FlowRateEntry } from '../../QuickTransferAdvancedSettings/FlowRate' +import { TipPositionEntry } from '../../QuickTransferAdvancedSettings/TipPosition' +import { Mix } from '../../QuickTransferAdvancedSettings/Mix' +import { Delay } from '../../QuickTransferAdvancedSettings/Delay' +import { TouchTip } from '../../QuickTransferAdvancedSettings/TouchTip' +import { AirGap } from '../../QuickTransferAdvancedSettings/AirGap' +import { BlowOut } from '../../QuickTransferAdvancedSettings/BlowOut' + +vi.mock('/app/redux-resources/analytics') +vi.mock('/app/organisms/ToasterOven') +vi.mock('../../QuickTransferAdvancedSettings/PipettePath') +vi.mock('../../QuickTransferAdvancedSettings/FlowRate') +vi.mock('../../QuickTransferAdvancedSettings/TipPosition') +vi.mock('../../QuickTransferAdvancedSettings/Mix') +vi.mock('../../QuickTransferAdvancedSettings/Delay') +vi.mock('../../QuickTransferAdvancedSettings/TouchTip') +vi.mock('../../QuickTransferAdvancedSettings/AirGap') +vi.mock('../../QuickTransferAdvancedSettings/BlowOut') + +const render = ( + props: React.ComponentProps +): any => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} +let mockTrackEventWithRobotSerial: any +let mockMakeSnackbar: any + +describe('QuickTransferAdvancedSettings', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + state: { + pipette: { + channels: 1, + liquids: [ + { + maxVolume: 1000, + minVolume: 5, + }, + ] as any, + } as any, + mount: 'left', + tipRack: { + wells: { + A1: { + totalLiquidVolume: 200, + }, + } as any, + } as any, + source: { + metadata: { + displayCategory: 'wellPlate', + }, + wells: { + A1: { + totalLiquidVolume: 200, + depth: 50, + }, + } as any, + } as any, + sourceWells: ['A1'], + destination: { + metadata: { + displayCategory: 'wellPlate', + }, + wells: { + A1: { + totalLiquidVolume: 200, + depth: 200, + }, + } as any, + } as any, + destinationWells: ['A1'], + transferType: 'consolidate', + volume: 20, + aspirateFlowRate: 570, + dispenseFlowRate: 890, + path: 'single', + tipPositionAspirate: 10, + preWetTip: false, + tipPositionDispense: 2, + changeTip: 'once', + dropTipLocation: { + cutoutId: 'cutoutA3', + cutoutFixtureId: 'trashBinAdapter', + }, + } as any, + dispatch: vi.fn(), + } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + mockMakeSnackbar = vi.fn() + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) + vi.mocked(useToaster).mockReturnValue({ + makeSnackbar: mockMakeSnackbar, + } as any) + }) + afterEach(() => { + vi.resetAllMocks() + }) + + // base settings section + it('renders base setting options and their values', () => { + render(props) + screen.getByText('Aspirate flow rate') + screen.getByText('570 µL/s') + screen.getByText('Dispense flow rate') + screen.getByText('890 µL/s') + screen.getByText('Pipette path') + screen.getByText('Single transfers') + }) + it('renders Aspirate flow rate component when seleted', () => { + render(props) + const aspirateFlowRate = screen.getByText('Aspirate flow rate') + fireEvent.click(aspirateFlowRate) + expect(vi.mocked(FlowRateEntry)).toHaveBeenCalled() + }) + it('renders Dispense flow rate component when seleted', () => { + render(props) + const dispenseFlowRate = screen.getByText('Dispense flow rate') + fireEvent.click(dispenseFlowRate) + expect(vi.mocked(FlowRateEntry)).toHaveBeenCalled() + }) + it('renders Pipette path component when seleted', () => { + render(props) + const pipettePath = screen.getByText('Pipette path') + fireEvent.click(pipettePath) + expect(vi.mocked(PipettePath)).toHaveBeenCalled() + }) + it('Pipette path button is disabled if 1 to 1 transfer', () => { + props = { + ...props, + state: { + ...props.state, + transferType: 'transfer', + }, + } + render(props) + const pipettePath = screen.getByText('Pipette path') + fireEvent.click(pipettePath) + expect(vi.mocked(PipettePath)).not.toHaveBeenCalled() + expect(mockMakeSnackbar).toHaveBeenCalled() + }) + it('shows additional information for multi dispense', () => { + props = { + ...props, + state: { + ...props.state, + transferType: 'distribute', + path: 'multiDispense', + blowOut: 'dest_well', + disposalVolume: 40, + }, + } + render(props) + screen.getByText('Pipette path') + screen.getByText( + 'Multi-dispense, 40 µL disposal volume, blowout into destination well' + ) + }) + + // aspirate/dispense default settings section + it('renders default aspirate and dispense setting options from summary state', () => { + render(props) + screen.getByText('Aspirate Settings') + expect(screen.getAllByText('Tip position')).toHaveLength(2) + screen.getByText('10 mm from the bottom') + screen.getByText('Pre-wet tip') + expect(screen.getAllByText('Mix')).toHaveLength(2) + expect(screen.getAllByText('Delay')).toHaveLength(2) + expect(screen.getAllByText('Touch tip')).toHaveLength(2) + expect(screen.getAllByText('Air gap')).toHaveLength(2) + screen.getByText('Dispense Settings') + screen.getByText('2 mm from the bottom') + screen.getByText('Blowout') + expect(screen.getAllByText('Disabled')).toHaveLength(10) + }) + + // aspirate settings + it('opens aspirate tip position when pressed', () => { + render(props) + const aspirateTipPosition = screen.getAllByText('Tip position')[0] + fireEvent.click(aspirateTipPosition) + expect(vi.mocked(TipPositionEntry)).toHaveBeenCalled() + }) + it('renders enabled when pre-wet tip is turned on and calls dispatch when pressed', () => { + props = { + ...props, + state: { + ...props.state, + preWetTip: true, + }, + } + render(props) + const preWetTip = screen.getByText('Pre-wet tip') + expect(screen.getAllByText('Disabled')).toHaveLength(9) + screen.getByText('Enabled') + fireEvent.click(preWetTip) + expect(props.dispatch).toHaveBeenCalled() + }) + it('renders aspirate mix text when setting has value and opens mix for 1 to 1 transfer', () => { + props = { + ...props, + state: { + ...props.state, + transferType: 'transfer', + mixOnAspirate: { + mixVolume: 15, + repititions: 25, + }, + }, + } + render(props) + const mixAspirate = screen.getAllByText('Mix')[0] + screen.getByText('15 µL, 25 times') + fireEvent.click(mixAspirate) + expect(vi.mocked(Mix)).toHaveBeenCalled() + }) + it('opens aspirate mix for distribute', () => { + props = { + ...props, + state: { + ...props.state, + transferType: 'distribute', + }, + } + render(props) + const mixAspirate = screen.getAllByText('Mix')[0] + fireEvent.click(mixAspirate) + expect(vi.mocked(Mix)).toHaveBeenCalled() + }) + it('does not open aspirate mix for consolidate', () => { + render(props) + const mixAspirate = screen.getAllByText('Mix')[0] + fireEvent.click(mixAspirate) + expect(vi.mocked(Mix)).not.toHaveBeenCalled() + expect(mockMakeSnackbar).toHaveBeenCalled() + }) + it('renders aspirate delay text if there is a value in state and opens delay component when pressed', () => { + props = { + ...props, + state: { + ...props.state, + delayAspirate: { + delayDuration: 5, + positionFromBottom: 17, + }, + }, + } + render(props) + const delayAspirate = screen.getAllByText('Delay')[0] + screen.getByText('5s, 17 mm from bottom') + fireEvent.click(delayAspirate) + expect(vi.mocked(Delay)).toHaveBeenCalled() + }) + it('renders aspirate touch tip text and opens component if labware is not a reservoir', () => { + props = { + ...props, + state: { + ...props.state, + touchTipAspirate: 8, + }, + } + render(props) + const touchtipAspirate = screen.getAllByText('Touch tip')[0] + screen.getByText('8 mm from bottom') + fireEvent.click(touchtipAspirate) + expect(vi.mocked(TouchTip)).toHaveBeenCalled() + }) + it('does not open aspirate touch tip component if source labware is a reservoir', () => { + props = { + ...props, + state: { + ...props.state, + source: { + ...props.state.source, + metadata: { + displayCategory: 'reservoir', + } as any, + }, + }, + } + render(props) + const touchtipAspirate = screen.getAllByText('Touch tip')[0] + fireEvent.click(touchtipAspirate) + expect(vi.mocked(TouchTip)).not.toHaveBeenCalled() + expect(mockMakeSnackbar).toHaveBeenCalled() + }) + it('renders aspirate air gap value if it exists and opens air gap component when pressed', () => { + props = { + ...props, + state: { + ...props.state, + airGapAspirate: 2, + }, + } + render(props) + const airGapAspirate = screen.getAllByText('Air gap')[0] + screen.getByText('2 µL') + fireEvent.click(airGapAspirate) + expect(vi.mocked(AirGap)).toHaveBeenCalled() + }) + + // dispense settings + it('opens dispense tip position when pressed', () => { + render(props) + const aspirateTipPosition = screen.getAllByText('Tip position')[1] + fireEvent.click(aspirateTipPosition) + expect(vi.mocked(TipPositionEntry)).toHaveBeenCalled() + }) + it('renders dispense mix text when setting has value and opens mix for 1 to 1 transfer', () => { + props = { + ...props, + state: { + ...props.state, + transferType: 'transfer', + mixOnDispense: { + mixVolume: 18, + repititions: 20, + }, + }, + } + render(props) + const mixDispense = screen.getAllByText('Mix')[1] + screen.getByText('18 µL, 20 times') + fireEvent.click(mixDispense) + expect(vi.mocked(Mix)).toHaveBeenCalled() + }) + it('opens dispense mix for consolidate', () => { + render(props) + const mixDispense = screen.getAllByText('Mix')[1] + fireEvent.click(mixDispense) + expect(vi.mocked(Mix)).toHaveBeenCalled() + }) + it('does not open dispense mix for distribute', () => { + props = { + ...props, + state: { + ...props.state, + transferType: 'distribute', + }, + } + render(props) + const mixDispense = screen.getAllByText('Mix')[1] + fireEvent.click(mixDispense) + expect(vi.mocked(Mix)).not.toHaveBeenCalled() + expect(mockMakeSnackbar).toHaveBeenCalled() + }) + it('renders dispense delay text if there is a value in state and opens delay component when pressed', () => { + props = { + ...props, + state: { + ...props.state, + delayDispense: { + delayDuration: 10, + positionFromBottom: 4, + }, + }, + } + render(props) + const delayDispense = screen.getAllByText('Delay')[1] + screen.getByText('10s, 4 mm from bottom') + fireEvent.click(delayDispense) + expect(vi.mocked(Delay)).toHaveBeenCalled() + }) + it('renders dispense touch tip text and opens component if labware is not a reservoir', () => { + props = { + ...props, + state: { + ...props.state, + touchTipDispense: 1, + }, + } + render(props) + const touchtipDispense = screen.getAllByText('Touch tip')[1] + screen.getByText('1 mm from bottom') + fireEvent.click(touchtipDispense) + expect(vi.mocked(TouchTip)).toHaveBeenCalled() + }) + it('does not open dispense touch tip component if destination labware is a reservoir', () => { + props = { + ...props, + state: { + ...props.state, + destination: { + metadata: { + displayCategory: 'reservoir', + } as any, + } as any, + }, + } + render(props) + const touchtipDispense = screen.getAllByText('Touch tip')[1] + fireEvent.click(touchtipDispense) + expect(vi.mocked(TouchTip)).not.toHaveBeenCalled() + expect(mockMakeSnackbar).toHaveBeenCalled() + }) + it('renders dispense air gap value if it exists and opens air gap component when pressed', () => { + props = { + ...props, + state: { + ...props.state, + airGapDispense: 9, + }, + } + render(props) + const airGapDispense = screen.getAllByText('Air gap')[1] + screen.getByText('9 µL') + fireEvent.click(airGapDispense) + expect(vi.mocked(AirGap)).toHaveBeenCalled() + }) + it('renders blowout location if it is a well and opens component when clicked if transfer type', () => { + props = { + ...props, + state: { + ...props.state, + transferType: 'transfer', + blowOut: 'source_well', + }, + } + render(props) + const blowOut = screen.getByText('Blowout') + screen.getByText('Into source well') + fireEvent.click(blowOut) + expect(vi.mocked(BlowOut)).toHaveBeenCalled() + }) + it('renders blowout location if it is a trash bin and opens component when clicked if consolidate type', () => { + props = { + ...props, + state: { + ...props.state, + blowOut: { + cutoutId: 'cutoutA3', + cutoutFixtureId: 'trashBinAdapter', + }, + }, + } + render(props) + const blowOut = screen.getByText('Blowout') + screen.getByText('Into trash bin') + fireEvent.click(blowOut) + expect(vi.mocked(BlowOut)).toHaveBeenCalled() + }) + it('does not render text or open blowout component when clicked if distribute type', () => { + props = { + ...props, + state: { + ...props.state, + blowOut: 'source_well', + transferType: 'distribute', + }, + } + render(props) + const blowOut = screen.getByText('Blowout') + expect(screen.getAllByText('Disabled')).toHaveLength(10) + fireEvent.click(blowOut) + expect(vi.mocked(BlowOut)).not.toHaveBeenCalled() + expect(mockMakeSnackbar).toHaveBeenCalled() + }) +}) diff --git a/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/TipPosition.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/TipPosition.test.tsx new file mode 100644 index 00000000000..dc109c2c302 --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/TipPosition.test.tsx @@ -0,0 +1,177 @@ +import type * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' + +import { InputField } from '@opentrons/components' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { TipPositionEntry } from '../../QuickTransferAdvancedSettings/TipPosition' +import type { QuickTransferSummaryState } from '../../types' + +vi.mock('/app/redux-resources/analytics') +vi.mock('../utils') + +vi.mock('@opentrons/components', async importOriginal => { + const actualComponents = await importOriginal() + return { + ...actualComponents, + InputField: vi.fn(), + } +}) + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} +let mockTrackEventWithRobotSerial: any + +describe('TipPosition', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + onBack: vi.fn(), + kind: 'aspirate', + state: { + mount: 'left', + pipette: { + channels: 1, + liquids: [ + { + maxVolume: 1000, + minVolume: 5, + }, + ] as any, + } as any, + source: { + wells: { + A1: { + totalLiquidVolume: 200, + depth: 50, + }, + } as any, + } as any, + destination: { + wells: { + A1: { + totalLiquidVolume: 200, + depth: 200, + }, + } as any, + } as any, + sourceWells: ['A1'], + destinationWells: ['A1'], + transferType: 'transfer', + volume: 20, + path: 'single', + tipPositionAspirate: 10, + tipPositionDispense: 75, + } as QuickTransferSummaryState, + dispatch: vi.fn(), + } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders the tip position aspirate screen, continue, and back buttons', () => { + render(props) + screen.getByText('Aspirate tip position') + screen.getByTestId('ChildNavigation_Primary_Button') + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Distance from bottom of well (mm)', + error: null, + readOnly: true, + type: 'text', + value: 10, + }, + {} + ) + const exitBtn = screen.getByTestId('ChildNavigation_Back_Button') + fireEvent.click(exitBtn) + expect(props.onBack).toHaveBeenCalled() + }) + + it('renders the tip position dispense screen', () => { + props = { + ...props, + kind: 'dispense', + } + render(props) + screen.getByText('Dispense tip position') + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Distance from bottom of well (mm)', + error: null, + readOnly: true, + type: 'text', + value: 75, + }, + {} + ) + }) + + it('renders correct range if you enter incorrect value for aspirate', () => { + render(props) + const deleteBtn = screen.getByText('del') + fireEvent.click(deleteBtn) + fireEvent.click(deleteBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Distance from bottom of well (mm)', + error: 'Value must be between 1-100', + readOnly: true, + type: 'text', + value: 0, + }, + {} + ) + const saveBtn = screen.getByTestId('ChildNavigation_Primary_Button') + expect(saveBtn).toBeDisabled() + }) + + it('renders correct range if you enter incorrect value for dispense', () => { + props = { + ...props, + kind: 'dispense', + } + render(props) + const deleteBtn = screen.getByText('del') + fireEvent.click(deleteBtn) + fireEvent.click(deleteBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Distance from bottom of well (mm)', + error: 'Value must be between 1-400', + readOnly: true, + type: 'text', + value: 0, + }, + {} + ) + const saveBtn = screen.getByTestId('ChildNavigation_Primary_Button') + expect(saveBtn).toBeDisabled() + }) + + it('calls dispatch when an in range value is entered and saved', () => { + render(props) + const deleteBtn = screen.getByText('del') + fireEvent.click(deleteBtn) + const numButton = screen.getByText('1') + fireEvent.click(numButton) + const saveBtn = screen.getByText('Save') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) +}) diff --git a/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/TouchTip.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/TouchTip.test.tsx new file mode 100644 index 00000000000..cc30db0a54f --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/QuickTransferAdvancedSettings/TouchTip.test.tsx @@ -0,0 +1,241 @@ +import type * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' + +import { InputField } from '@opentrons/components' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { TouchTip } from '../../QuickTransferAdvancedSettings/TouchTip' +import type { QuickTransferSummaryState } from '../../types' + +vi.mock('/app/redux-resources/analytics') +vi.mock('../utils') + +vi.mock('@opentrons/components', async importOriginal => { + const actualComponents = await importOriginal() + return { + ...actualComponents, + InputField: vi.fn(), + } +}) + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} +let mockTrackEventWithRobotSerial: any + +describe('TouchTip', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + onBack: vi.fn(), + kind: 'aspirate', + state: { + mount: 'left', + pipette: { + channels: 1, + liquids: [ + { + maxVolume: 1000, + minVolume: 5, + }, + ] as any, + } as any, + source: { + wells: { + A1: { + totalLiquidVolume: 200, + depth: 50, + }, + } as any, + } as any, + destination: { + wells: { + A1: { + totalLiquidVolume: 200, + depth: 200, + }, + } as any, + } as any, + sourceWells: ['A1'], + destinationWells: ['A1'], + transferType: 'transfer', + volume: 20, + path: 'single', + } as QuickTransferSummaryState, + dispatch: vi.fn(), + } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders the first touch tip screen, continue, and back buttons', () => { + render(props) + screen.getByText('Touch tip after aspirating') + screen.getByTestId('ChildNavigation_Primary_Button') + screen.getByText('Enabled') + screen.getByText('Disabled') + const exitBtn = screen.getByTestId('ChildNavigation_Back_Button') + fireEvent.click(exitBtn) + expect(props.onBack).toHaveBeenCalled() + }) + + it('renders the correct copy for dispense', () => { + props = { + ...props, + kind: 'dispense', + } + render(props) + screen.getByText('Touch tip before dispensing') + }) + + it('renders save button if you select enabled, then moves to second screen', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Touch tip position from bottom of well (mm)', + error: null, + readOnly: true, + type: 'number', + value: null, + }, + {} + ) + }) + + it('calls dispatch button if you select disabled and save', () => { + render(props) + const disabledBtn = screen.getByText('Disabled') + fireEvent.click(disabledBtn) + const saveBtn = screen.getByText('Save') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) + + it('has correct range for aspirate', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const numButton = screen.getByText('0') + fireEvent.click(numButton) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Touch tip position from bottom of well (mm)', + error: 'Value must be between 25-50', + readOnly: true, + type: 'number', + value: 0, + }, + {} + ) + const saveBtn = screen.getByTestId('ChildNavigation_Primary_Button') + expect(saveBtn).toBeDisabled() + }) + + it('has correct range for dispense', () => { + props = { + ...props, + kind: 'dispense', + } + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const numButton = screen.getByText('0') + fireEvent.click(numButton) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Touch tip position from bottom of well (mm)', + error: 'Value must be between 100-200', + readOnly: true, + type: 'number', + value: 0, + }, + {} + ) + const saveBtn = screen.getByTestId('ChildNavigation_Primary_Button') + expect(saveBtn).toBeDisabled() + }) + + it('calls dispatch when an in range value is entered and saved', () => { + render(props) + const enabledBtn = screen.getByText('Enabled') + fireEvent.click(enabledBtn) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + const numButton = screen.getByText('4') + fireEvent.click(numButton) + fireEvent.click(numButton) + const saveBtn = screen.getByText('Save') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() + }) + + it('renders previously set value saved in state for aspirate', () => { + props = { + ...props, + state: { + ...props.state, + touchTipAspirate: 32, + }, + } + render(props) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Touch tip position from bottom of well (mm)', + error: null, + readOnly: true, + type: 'number', + value: 32, + }, + {} + ) + }) + + it('renders previously set value saved in state for dispense', () => { + props = { + ...props, + kind: 'dispense', + state: { + ...props.state, + touchTipDispense: 118, + }, + } + render(props) + const continueBtn = screen.getByText('Continue') + fireEvent.click(continueBtn) + expect(vi.mocked(InputField)).toHaveBeenCalledWith( + { + title: 'Touch tip position from bottom of well (mm)', + error: null, + readOnly: true, + type: 'number', + value: 118, + }, + {} + ) + }) +}) diff --git a/app/src/organisms/QuickTransferFlow/__tests__/SelectDestLabware.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/SelectDestLabware.test.tsx similarity index 92% rename from app/src/organisms/QuickTransferFlow/__tests__/SelectDestLabware.test.tsx rename to app/src/organisms/ODD/QuickTransferFlow/__tests__/SelectDestLabware.test.tsx index a2d2430c268..6448542c14e 100644 --- a/app/src/organisms/QuickTransferFlow/__tests__/SelectDestLabware.test.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/SelectDestLabware.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { SelectDestLabware } from '../SelectDestLabware' vi.mock('@opentrons/react-api-client') @@ -96,7 +96,7 @@ describe('SelectDestLabware', () => { }, }) render(props) - screen.getByText('Source labware in D2') + screen.getByText('Source labware in C2') screen.getByText('source labware name') }) it('enables continue button if you select a labware', () => { @@ -109,7 +109,7 @@ describe('SelectDestLabware', () => { }) const continueBtn = screen.getByTestId('ChildNavigation_Primary_Button') expect(continueBtn).toBeDisabled() - const sourceLabware = screen.getByText('Source labware in D2') + const sourceLabware = screen.getByText('Source labware in C2') fireEvent.click(sourceLabware) expect(continueBtn).toBeEnabled() }) diff --git a/app/src/organisms/QuickTransferFlow/__tests__/SelectPipette.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/SelectPipette.test.tsx similarity index 93% rename from app/src/organisms/QuickTransferFlow/__tests__/SelectPipette.test.tsx rename to app/src/organisms/ODD/QuickTransferFlow/__tests__/SelectPipette.test.tsx index 0f40c6f29b0..e32f7645593 100644 --- a/app/src/organisms/QuickTransferFlow/__tests__/SelectPipette.test.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/SelectPipette.test.tsx @@ -1,15 +1,15 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' import { useInstrumentsQuery } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { useIsOEMMode } from '../../../resources/robot-settings/hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useIsOEMMode } from '/app/resources/robot-settings/hooks' import { SelectPipette } from '../SelectPipette' vi.mock('@opentrons/react-api-client') -vi.mock('../../../resources/robot-settings/hooks') +vi.mock('/app/resources/robot-settings/hooks') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/QuickTransferFlow/__tests__/SelectSourceLabware.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/SelectSourceLabware.test.tsx similarity index 95% rename from app/src/organisms/QuickTransferFlow/__tests__/SelectSourceLabware.test.tsx rename to app/src/organisms/ODD/QuickTransferFlow/__tests__/SelectSourceLabware.test.tsx index 43b8c1a1e15..73121ea7b2d 100644 --- a/app/src/organisms/QuickTransferFlow/__tests__/SelectSourceLabware.test.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/SelectSourceLabware.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { SelectSourceLabware } from '../SelectSourceLabware' vi.mock('@opentrons/react-api-client') diff --git a/app/src/organisms/QuickTransferFlow/__tests__/SelectTipRack.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/SelectTipRack.test.tsx similarity index 95% rename from app/src/organisms/QuickTransferFlow/__tests__/SelectTipRack.test.tsx rename to app/src/organisms/ODD/QuickTransferFlow/__tests__/SelectTipRack.test.tsx index b32b3188910..f4ee22bd2b9 100644 --- a/app/src/organisms/QuickTransferFlow/__tests__/SelectTipRack.test.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/SelectTipRack.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { SelectTipRack } from '../SelectTipRack' vi.mock('@opentrons/react-api-client') diff --git a/app/src/organisms/QuickTransferFlow/__tests__/SummaryAndSettings.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/SummaryAndSettings.test.tsx similarity index 81% rename from app/src/organisms/QuickTransferFlow/__tests__/SummaryAndSettings.test.tsx rename to app/src/organisms/ODD/QuickTransferFlow/__tests__/SummaryAndSettings.test.tsx index a6cfc429cb1..0f5f7c7742c 100644 --- a/app/src/organisms/QuickTransferFlow/__tests__/SummaryAndSettings.test.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/SummaryAndSettings.test.tsx @@ -1,14 +1,18 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' + import { useCreateProtocolMutation, useCreateRunMutation, } from '@opentrons/react-api-client' -import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' + +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' +import { ANALYTICS_QUICK_TRANSFER_RUN_NOW } from '/app/redux/analytics' import { createQuickTransferFile } from '../utils' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' import { SummaryAndSettings } from '../SummaryAndSettings' import { NameQuickTransfer } from '../NameQuickTransfer' import { Overview } from '../Overview' @@ -25,6 +29,7 @@ vi.mock('react-router-dom', async importOriginal => { }) vi.mock('../Overview') vi.mock('../NameQuickTransfer') +vi.mock('/app/redux-resources/analytics') vi.mock('../utils', async () => { const actual = await vi.importActual('../utils') return { @@ -34,13 +39,14 @@ vi.mock('../utils', async () => { }) vi.mock('../utils/createQuickTransferFile') vi.mock('@opentrons/react-api-client') -vi.mock('../../../resources/deck_configuration') +vi.mock('/app/resources/deck_configuration') const render = (props: React.ComponentProps) => { return renderWithProviders(, { i18nInstance: i18n, }) } +let mockTrackEventWithRobotSerial: any describe('SummaryAndSettings', () => { let props: React.ComponentProps @@ -65,13 +71,19 @@ describe('SummaryAndSettings', () => { transferType: 'transfer', volume: 25, }, + analyticsStartTime: new Date(), } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ data: { data: [], }, } as any) - + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) vi.mocked(useCreateProtocolMutation).mockReturnValue({ mutateAsync: createProtocol, } as any) @@ -112,6 +124,7 @@ describe('SummaryAndSettings', () => { render(props) const continueBtn = screen.getByTestId('ChildNavigation_Primary_Button') fireEvent.click(continueBtn) + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() screen.getByText('Do you want to run your quick transfer now?') screen.getByText('Save your quick transfer to run it in the future.') }) @@ -129,6 +142,10 @@ describe('SummaryAndSettings', () => { fireEvent.click(continueBtn) const runBtn = screen.getByText('Run now') fireEvent.click(runBtn) + expect(mockTrackEventWithRobotSerial).toHaveBeenCalledWith({ + name: ANALYTICS_QUICK_TRANSFER_RUN_NOW, + properties: {}, + }) expect(vi.mocked(createQuickTransferFile)).toHaveBeenCalled() expect(vi.mocked(createProtocol)).toHaveBeenCalled() }) diff --git a/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/ChangeTip.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/TipManagement/ChangeTip.test.tsx similarity index 85% rename from app/src/organisms/QuickTransferFlow/__tests__/TipManagement/ChangeTip.test.tsx rename to app/src/organisms/ODD/QuickTransferFlow/__tests__/TipManagement/ChangeTip.test.tsx index a45a8381035..213633678e5 100644 --- a/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/ChangeTip.test.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/TipManagement/ChangeTip.test.tsx @@ -1,17 +1,23 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '/app/redux/analytics' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' import { ChangeTip } from '../../TipManagement/ChangeTip' +vi.mock('/app/redux-resources/analytics') + const render = (props: React.ComponentProps): any => { return renderWithProviders(, { i18nInstance: i18n, }) } +let mockTrackEventWithRobotSerial: any + describe('ChangeTip', () => { let props: React.ComponentProps @@ -28,6 +34,12 @@ describe('ChangeTip', () => { } as any, dispatch: vi.fn(), } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) }) afterEach(() => { vi.resetAllMocks() @@ -49,6 +61,10 @@ describe('ChangeTip', () => { const saveBtn = screen.getByText('Save') fireEvent.click(saveBtn) expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalledWith({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { setting: 'ChangeTip' }, + }) }) it('renders correct change tip options when single transfer of less than 96 wells', () => { render(props) diff --git a/app/src/organisms/ODD/QuickTransferFlow/__tests__/TipManagement/TipDropLocation.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/TipManagement/TipDropLocation.test.tsx new file mode 100644 index 00000000000..aed3d143b31 --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/TipManagement/TipDropLocation.test.tsx @@ -0,0 +1,80 @@ +import type * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '/app/redux/analytics' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { TipDropLocation } from '../../TipManagement/TipDropLocation' + +vi.mock('/app/resources/deck_configuration') +vi.mock('/app/redux-resources/analytics') + +const render = (props: React.ComponentProps): any => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} +let mockTrackEventWithRobotSerial: any + +describe('TipDropLocation', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + onBack: vi.fn(), + state: { + dropTipLocation: 'trashBin', + } as any, + dispatch: vi.fn(), + } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) + vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ + data: [ + { + cutoutId: 'cutoutC3', + cutoutFixtureId: 'wasteChuteRightAdapterCovered', + }, + { + cutoutId: 'cutoutA3', + cutoutFixtureId: 'trashBinAdapter', + }, + ], + } as any) + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders tip drop location screen, header and save button', () => { + render(props) + screen.getByText('Tip drop location') + const saveBtn = screen.getByText('Save') + fireEvent.click(saveBtn) + expect(props.onBack).toHaveBeenCalled() + }) + it('renders options for each dipsosal location in deck config', () => { + render(props) + screen.getByText('Trash bin in A3') + screen.getByText('Waste chute in C3') + }) + it('calls dispatch when you select a new option and save', () => { + render(props) + const wasteChute = screen.getByText('Waste chute in C3') + fireEvent.click(wasteChute) + const saveBtn = screen.getByText('Save') + fireEvent.click(saveBtn) + expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalledWith({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { setting: 'TipDropLocation' }, + }) + }) +}) diff --git a/app/src/organisms/ODD/QuickTransferFlow/__tests__/TipManagement/TipManagement.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/TipManagement/TipManagement.test.tsx new file mode 100644 index 00000000000..618153a8b53 --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/TipManagement/TipManagement.test.tsx @@ -0,0 +1,71 @@ +import type * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { ANALYTICS_QUICK_TRANSFER_TIP_MANAGEMENT_TAB } from '/app/redux/analytics' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { ChangeTip } from '../../TipManagement/ChangeTip' +import { TipDropLocation } from '../../TipManagement/TipDropLocation' +import { TipManagement } from '../../TipManagement/' + +vi.mock('../../TipManagement/ChangeTip') +vi.mock('../../TipManagement/TipDropLocation') +vi.mock('/app/redux-resources/analytics') + +const render = (props: React.ComponentProps): any => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} +let mockTrackEventWithRobotSerial: any + +describe('TipManagement', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + state: { + changeTip: 'once', + dropTipLocation: { + cutoutFixtureId: 'trashBinAdapter', + }, + } as any, + dispatch: vi.fn(), + } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders tip management options and their values', () => { + render(props) + screen.getByText('Change tip') + screen.getByText('Once at the start of the transfer') + screen.getByText('Tip drop location') + screen.getByText('Trash bin') + expect(mockTrackEventWithRobotSerial).toHaveBeenCalledWith({ + name: ANALYTICS_QUICK_TRANSFER_TIP_MANAGEMENT_TAB, + properties: {}, + }) + }) + it('renders Change tip component when seleted', () => { + render(props) + const changeTip = screen.getByText('Change tip') + fireEvent.click(changeTip) + expect(vi.mocked(ChangeTip)).toHaveBeenCalled() + }) + it('renders Drop tip location component when seleted', () => { + render(props) + const tipDrop = screen.getByText('Tip drop location') + fireEvent.click(tipDrop) + expect(vi.mocked(TipDropLocation)).toHaveBeenCalled() + }) +}) diff --git a/app/src/organisms/QuickTransferFlow/__tests__/VolumeEntry.test.tsx b/app/src/organisms/ODD/QuickTransferFlow/__tests__/VolumeEntry.test.tsx similarity index 94% rename from app/src/organisms/QuickTransferFlow/__tests__/VolumeEntry.test.tsx rename to app/src/organisms/ODD/QuickTransferFlow/__tests__/VolumeEntry.test.tsx index bc5eeb04a34..8a14b9a5993 100644 --- a/app/src/organisms/QuickTransferFlow/__tests__/VolumeEntry.test.tsx +++ b/app/src/organisms/ODD/QuickTransferFlow/__tests__/VolumeEntry.test.tsx @@ -1,16 +1,16 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' import { InputField } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { NumericalKeyboard } from '../../../atoms/SoftwareKeyboard' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { NumericalKeyboard } from '/app/atoms/SoftwareKeyboard' import { getVolumeRange } from '../utils' import { VolumeEntry } from '../VolumeEntry' -vi.mock('../../../atoms/SoftwareKeyboard') +vi.mock('/app/atoms/SoftwareKeyboard') vi.mock('../utils') vi.mock('@opentrons/components', async importOriginal => { diff --git a/app/src/organisms/QuickTransferFlow/__tests__/utils/generateCompatibleLabwareForPipette.test.ts b/app/src/organisms/ODD/QuickTransferFlow/__tests__/utils/generateCompatibleLabwareForPipette.test.ts similarity index 100% rename from app/src/organisms/QuickTransferFlow/__tests__/utils/generateCompatibleLabwareForPipette.test.ts rename to app/src/organisms/ODD/QuickTransferFlow/__tests__/utils/generateCompatibleLabwareForPipette.test.ts diff --git a/app/src/organisms/QuickTransferFlow/__tests__/utils/getInitialSummaryState.test.ts b/app/src/organisms/ODD/QuickTransferFlow/__tests__/utils/getInitialSummaryState.test.ts similarity index 100% rename from app/src/organisms/QuickTransferFlow/__tests__/utils/getInitialSummaryState.test.ts rename to app/src/organisms/ODD/QuickTransferFlow/__tests__/utils/getInitialSummaryState.test.ts diff --git a/app/src/organisms/QuickTransferFlow/__tests__/utils/getSelectedWellCount.test.ts b/app/src/organisms/ODD/QuickTransferFlow/__tests__/utils/getSelectedWellCount.test.ts similarity index 100% rename from app/src/organisms/QuickTransferFlow/__tests__/utils/getSelectedWellCount.test.ts rename to app/src/organisms/ODD/QuickTransferFlow/__tests__/utils/getSelectedWellCount.test.ts diff --git a/app/src/organisms/QuickTransferFlow/__tests__/utils/getVolumeRange.test.ts b/app/src/organisms/ODD/QuickTransferFlow/__tests__/utils/getVolumeRange.test.ts similarity index 100% rename from app/src/organisms/QuickTransferFlow/__tests__/utils/getVolumeRange.test.ts rename to app/src/organisms/ODD/QuickTransferFlow/__tests__/utils/getVolumeRange.test.ts diff --git a/app/src/organisms/QuickTransferFlow/constants.ts b/app/src/organisms/ODD/QuickTransferFlow/constants.ts similarity index 100% rename from app/src/organisms/QuickTransferFlow/constants.ts rename to app/src/organisms/ODD/QuickTransferFlow/constants.ts diff --git a/app/src/organisms/ODD/QuickTransferFlow/index.tsx b/app/src/organisms/ODD/QuickTransferFlow/index.tsx new file mode 100644 index 00000000000..fb0a76b6f47 --- /dev/null +++ b/app/src/organisms/ODD/QuickTransferFlow/index.tsx @@ -0,0 +1,114 @@ +import { useState, useReducer } from 'react' +import { useNavigate } from 'react-router-dom' +import { useTranslation } from 'react-i18next' +import { + POSITION_STICKY, + StepMeter, + useConditionalConfirm, +} from '@opentrons/components' +import { ANALYTICS_QUICK_TRANSFER_EXIT_EARLY } from '/app/redux/analytics' +import { useTrackEventWithRobotSerial } from '/app/redux-resources/analytics' +import { ConfirmExitModal } from './ConfirmExitModal' +import { CreateNewTransfer } from './CreateNewTransfer' +import { SelectPipette } from './SelectPipette' +import { SelectTipRack } from './SelectTipRack' +import { SelectSourceLabware } from './SelectSourceLabware' +import { SelectSourceWells } from './SelectSourceWells' +import { SelectDestLabware } from './SelectDestLabware' +import { SelectDestWells } from './SelectDestWells' +import { VolumeEntry } from './VolumeEntry' +import { SummaryAndSettings } from './SummaryAndSettings' +import { quickTransferWizardReducer } from './reducers' + +import type { ComponentProps } from 'react' +import type { SmallButton } from '/app/atoms/buttons' +import type { QuickTransferWizardState } from './types' + +const QUICK_TRANSFER_WIZARD_STEPS = 8 +const initialQuickTransferState: QuickTransferWizardState = {} + +export const QuickTransferFlow = (): JSX.Element => { + const navigate = useNavigate() + const { i18n, t } = useTranslation(['quick_transfer', 'shared']) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() + const [state, dispatch] = useReducer( + quickTransferWizardReducer, + initialQuickTransferState + ) + const [currentStep, setCurrentStep] = useState(0) + + const [analyticsStartTime] = useState(new Date()) + + const { + confirm: confirmExit, + showConfirmation: showConfirmExit, + cancel: cancelExit, + } = useConditionalConfirm(() => { + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_EXIT_EARLY, + properties: { + step: currentStep, + }, + }) + navigate('/quick-transfer') + }, true) + + const exitButtonProps: ComponentProps = { + buttonType: 'tertiaryLowLight', + buttonText: i18n.format(t('shared:exit'), 'capitalize'), + onClick: confirmExit, + } + const sharedMiddleStepProps = { + state, + dispatch, + onBack: () => { + setCurrentStep(prevStep => prevStep - 1) + }, + onNext: () => { + setCurrentStep(prevStep => prevStep + 1) + }, + exitButtonProps, + } + + const contentInOrder: JSX.Element[] = [ + { + setCurrentStep(prevStep => prevStep + 1) + }} + exitButtonProps={exitButtonProps} + />, + , + , + , + , + , + , + , + , + ] + + return ( + <> + {showConfirmExit ? ( + + ) : ( + <> + {currentStep < QUICK_TRANSFER_WIZARD_STEPS ? ( + + ) : null} + {contentInOrder[currentStep]} + + )} + + ) +} diff --git a/app/src/organisms/QuickTransferFlow/reducers.ts b/app/src/organisms/ODD/QuickTransferFlow/reducers.ts similarity index 100% rename from app/src/organisms/QuickTransferFlow/reducers.ts rename to app/src/organisms/ODD/QuickTransferFlow/reducers.ts diff --git a/app/src/organisms/QuickTransferFlow/types.ts b/app/src/organisms/ODD/QuickTransferFlow/types.ts similarity index 100% rename from app/src/organisms/QuickTransferFlow/types.ts rename to app/src/organisms/ODD/QuickTransferFlow/types.ts diff --git a/app/src/organisms/QuickTransferFlow/utils/createQuickTransferFile.ts b/app/src/organisms/ODD/QuickTransferFlow/utils/createQuickTransferFile.ts similarity index 96% rename from app/src/organisms/QuickTransferFlow/utils/createQuickTransferFile.ts rename to app/src/organisms/ODD/QuickTransferFlow/utils/createQuickTransferFile.ts index a4114c0a4d4..09fa2348f39 100644 --- a/app/src/organisms/QuickTransferFlow/utils/createQuickTransferFile.ts +++ b/app/src/organisms/ODD/QuickTransferFlow/utils/createQuickTransferFile.ts @@ -202,10 +202,9 @@ export function createQuickTransferFile( subcategory: null, tags: [], }, - // TODO: formalize designer application data type designerApplication: { - name: 'Quick Transfer', - version: '0.0', + name: 'opentrons/quick-transfer', + version: '1.0.0', data: quickTransferState, }, } @@ -250,8 +249,6 @@ export function createQuickTransferFile( ...commandAnnotionaV1Mixin, }) - // Leaving this in for debugging while work is still in flight - console.log('Here are the protocol contents:', protocolContents) return new File( [protocolContents], `${protocolBase.metadata.protocolName}.json` diff --git a/app/src/organisms/QuickTransferFlow/utils/generateCompatibleLabwareForPipette.ts b/app/src/organisms/ODD/QuickTransferFlow/utils/generateCompatibleLabwareForPipette.ts similarity index 84% rename from app/src/organisms/QuickTransferFlow/utils/generateCompatibleLabwareForPipette.ts rename to app/src/organisms/ODD/QuickTransferFlow/utils/generateCompatibleLabwareForPipette.ts index 3b52d014e93..38d62455854 100644 --- a/app/src/organisms/QuickTransferFlow/utils/generateCompatibleLabwareForPipette.ts +++ b/app/src/organisms/ODD/QuickTransferFlow/utils/generateCompatibleLabwareForPipette.ts @@ -1,5 +1,5 @@ import { makeWellSetHelpers, getLabwareDefURI } from '@opentrons/shared-data' -import { getAllDefinitions as getAllLatestDefValues } from '../../../pages/Labware/helpers/definitions' +import { getAllDefinitions as getAllLatestDefValues } from '/app/local-resources/labware' import type { PipetteV2Specs, WellSetHelpers } from '@opentrons/shared-data' @@ -14,7 +14,8 @@ export function generateCompatibleLabwareForPipette( (acc, definition) => { if ( definition.allowedRoles != null && - definition.allowedRoles.includes('adapter') + (definition.allowedRoles.includes('adapter') || + definition.allowedRoles.includes('lid')) ) { return acc } else if (pipetteSpecs.channels === 1) { diff --git a/app/src/organisms/QuickTransferFlow/utils/generateQuickTransferArgs.ts b/app/src/organisms/ODD/QuickTransferFlow/utils/generateQuickTransferArgs.ts similarity index 96% rename from app/src/organisms/QuickTransferFlow/utils/generateQuickTransferArgs.ts rename to app/src/organisms/ODD/QuickTransferFlow/utils/generateQuickTransferArgs.ts index 4f571665b10..517e0aabb0d 100644 --- a/app/src/organisms/QuickTransferFlow/utils/generateQuickTransferArgs.ts +++ b/app/src/organisms/ODD/QuickTransferFlow/utils/generateQuickTransferArgs.ts @@ -299,18 +299,23 @@ export function generateQuickTransferArgs( const flowRatesForSupportedTip = quickTransferState.pipette.liquids.default.supportedTips[tipType] const pipetteEntity = Object.values(invariantContext.pipetteEntities)[0] - const labwareEntityValues = Object.values(invariantContext.labwareEntities) - const sourceLabwareEntity = labwareEntityValues.find( - entity => - entity.labwareDefURI === getLabwareDefURI(quickTransferState.source) + + const sourceLabwareId = Object.keys(robotState.labware).find( + labwareId => robotState.labware[labwareId].slot === 'C2' ) + const sourceLabwareEntity = + sourceLabwareId != null + ? invariantContext.labwareEntities[sourceLabwareId] + : undefined let destLabwareEntity = sourceLabwareEntity if (quickTransferState.destination !== 'source') { - destLabwareEntity = labwareEntityValues.find( - entity => - entity.labwareDefURI === - getLabwareDefURI(quickTransferState.destination as LabwareDefinition2) + const destinationLabwareId = Object.keys(robotState.labware).find( + labwareId => robotState.labware[labwareId].slot === 'D2' ) + destLabwareEntity = + destinationLabwareId != null + ? invariantContext.labwareEntities[destinationLabwareId] + : undefined } let nozzles = null diff --git a/app/src/organisms/QuickTransferFlow/utils/getCompatibleLabwareByCategory.ts b/app/src/organisms/ODD/QuickTransferFlow/utils/getCompatibleLabwareByCategory.ts similarity index 96% rename from app/src/organisms/QuickTransferFlow/utils/getCompatibleLabwareByCategory.ts rename to app/src/organisms/ODD/QuickTransferFlow/utils/getCompatibleLabwareByCategory.ts index a9bf05fae9e..cb52a095a33 100644 --- a/app/src/organisms/QuickTransferFlow/utils/getCompatibleLabwareByCategory.ts +++ b/app/src/organisms/ODD/QuickTransferFlow/utils/getCompatibleLabwareByCategory.ts @@ -6,7 +6,7 @@ import { } from '../constants' import type { LabwareDefinition2 } from '@opentrons/shared-data' -import type { LabwareFilter } from '../../../pages/Labware/types' +import type { LabwareFilter } from '/app/local-resources/labware' export function getCompatibleLabwareByCategory( pipetteChannels: 1 | 8 | 96, diff --git a/app/src/organisms/QuickTransferFlow/utils/getInitialSummaryState.ts b/app/src/organisms/ODD/QuickTransferFlow/utils/getInitialSummaryState.ts similarity index 100% rename from app/src/organisms/QuickTransferFlow/utils/getInitialSummaryState.ts rename to app/src/organisms/ODD/QuickTransferFlow/utils/getInitialSummaryState.ts diff --git a/app/src/organisms/QuickTransferFlow/utils/getSelectedWellCount.ts b/app/src/organisms/ODD/QuickTransferFlow/utils/getSelectedWellCount.ts similarity index 100% rename from app/src/organisms/QuickTransferFlow/utils/getSelectedWellCount.ts rename to app/src/organisms/ODD/QuickTransferFlow/utils/getSelectedWellCount.ts diff --git a/app/src/organisms/QuickTransferFlow/utils/getVolumeRange.ts b/app/src/organisms/ODD/QuickTransferFlow/utils/getVolumeRange.ts similarity index 100% rename from app/src/organisms/QuickTransferFlow/utils/getVolumeRange.ts rename to app/src/organisms/ODD/QuickTransferFlow/utils/getVolumeRange.ts diff --git a/app/src/organisms/QuickTransferFlow/utils/index.ts b/app/src/organisms/ODD/QuickTransferFlow/utils/index.ts similarity index 100% rename from app/src/organisms/QuickTransferFlow/utils/index.ts rename to app/src/organisms/ODD/QuickTransferFlow/utils/index.ts diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/EmptyRecentRun.tsx b/app/src/organisms/ODD/RobotDashboard/EmptyRecentRun.tsx similarity index 89% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/EmptyRecentRun.tsx rename to app/src/organisms/ODD/RobotDashboard/EmptyRecentRun.tsx index 3233dc9bb37..0dc551aff7a 100644 --- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/EmptyRecentRun.tsx +++ b/app/src/organisms/ODD/RobotDashboard/EmptyRecentRun.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -13,7 +12,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import abstractImage from '../../../assets/images/on-device-display/empty_protocol_dashboard.png' +import abstractImage from '/app/assets/images/on-device-display/empty_protocol_dashboard.png' export function EmptyRecentRun(): JSX.Element { const { t } = useTranslation('device_details') diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/RecentRunProtocolCard.tsx b/app/src/organisms/ODD/RobotDashboard/RecentRunProtocolCard.tsx similarity index 91% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/RecentRunProtocolCard.tsx rename to app/src/organisms/ODD/RobotDashboard/RecentRunProtocolCard.tsx index e39e46ef62f..e522fb1dae7 100644 --- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/RecentRunProtocolCard.tsx +++ b/app/src/organisms/ODD/RobotDashboard/RecentRunProtocolCard.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { css } from 'styled-components' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' @@ -28,14 +28,14 @@ import { RUN_STATUS_SUCCEEDED, } from '@opentrons/api-client' -import { ODD_FOCUS_VISIBLE } from '../../../atoms/buttons//constants' +import { ODD_FOCUS_VISIBLE } from '/app/atoms/buttons/constants' import { useTrackEvent, ANALYTICS_PROTOCOL_PROCEED_TO_RUN, -} from '../../../redux/analytics' -import { Skeleton } from '../../../atoms/Skeleton' -import { useMissingProtocolHardware } from '../../../pages/Protocols/hooks' -import { useCloneRun } from '../../ProtocolUpload/hooks' +} from '/app/redux/analytics' +import { Skeleton } from '/app/atoms/Skeleton' +import { useMissingProtocolHardware } from '/app/transformations/commands' +import { useCloneRun } from '/app/resources/runs' import { useRerunnableStatusText } from './hooks' import type { RunData, RunStatus } from '@opentrons/api-client' @@ -51,7 +51,8 @@ export function RecentRunProtocolCard({ const { data, isLoading } = useProtocolQuery(runData.protocolId ?? null) const protocolData = data?.data ?? null const isProtocolFetching = isLoading - return protocolData == null ? null : ( + return protocolData == null || + protocolData.protocolKind === 'quick-transfer' ? null : ( (false) + const [showSpinner, setShowSpinner] = useState(false) const protocolName = protocolData.metadata.protocolName ?? protocolData.files[0].name @@ -159,7 +160,8 @@ export function ProtocolWithLastRun({ } // TODO(BC, 2023-06-05): see if addSuffix false allow can remove usage of .replace here const formattedLastRunTime = formatDistance( - new Date(runData.createdAt), + // Fallback to current date if completedAt is null, though this should never happen since runs must be completed to appear in dashboard + new Date(runData.completedAt ?? new Date()), new Date(), { addSuffix: true, diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/RecentRunProtocolCarousel.tsx b/app/src/organisms/ODD/RobotDashboard/RecentRunProtocolCarousel.tsx similarity index 97% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/RecentRunProtocolCarousel.tsx rename to app/src/organisms/ODD/RobotDashboard/RecentRunProtocolCarousel.tsx index ac9006dc93d..037347146f0 100644 --- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/RecentRunProtocolCarousel.tsx +++ b/app/src/organisms/ODD/RobotDashboard/RecentRunProtocolCarousel.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { ALIGN_FLEX_START, DIRECTION_ROW, diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/ServerInitializing.tsx b/app/src/organisms/ODD/RobotDashboard/ServerInitializing.tsx similarity index 96% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/ServerInitializing.tsx rename to app/src/organisms/ODD/RobotDashboard/ServerInitializing.tsx index fc363f22b02..a062628a3bc 100644 --- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/ServerInitializing.tsx +++ b/app/src/organisms/ODD/RobotDashboard/ServerInitializing.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/EmptyRecentRun.test.tsx b/app/src/organisms/ODD/RobotDashboard/__tests__/EmptyRecentRun.test.tsx similarity index 83% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/EmptyRecentRun.test.tsx rename to app/src/organisms/ODD/RobotDashboard/__tests__/EmptyRecentRun.test.tsx index 25e8df22f2b..6c7c7e5371c 100644 --- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/EmptyRecentRun.test.tsx +++ b/app/src/organisms/ODD/RobotDashboard/__tests__/EmptyRecentRun.test.tsx @@ -1,9 +1,8 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { describe, expect, it } from 'vitest' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { EmptyRecentRun } from '../EmptyRecentRun' const PNG_FILE_NAME = diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx b/app/src/organisms/ODD/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx similarity index 87% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx rename to app/src/organisms/ODD/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx index d9d4959814d..10ee119176e 100644 --- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx +++ b/app/src/organisms/ODD/RobotDashboard/__tests__/RecentRunProtocolCard.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { formatDistance } from 'date-fns' import { MemoryRouter } from 'react-router-dom' @@ -13,22 +13,21 @@ import { } from '@opentrons/react-api-client' import { simpleAnalysisFileFixture } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { Skeleton } from '../../../../atoms/Skeleton' -import { useMissingProtocolHardware } from '../../../../pages/Protocols/hooks' -import { useTrackProtocolRunEvent } from '../../../Devices/hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { Skeleton } from '/app/atoms/Skeleton' +import { useMissingProtocolHardware } from '/app/transformations/commands' +import { useTrackProtocolRunEvent } from '/app/redux-resources/analytics' import { useTrackEvent, ANALYTICS_PROTOCOL_PROCEED_TO_RUN, -} from '../../../../redux/analytics' -import { useCloneRun } from '../../../ProtocolUpload/hooks' +} from '/app/redux/analytics' +import { useCloneRun, useNotifyAllRunsQuery } from '/app/resources/runs' import { useRerunnableStatusText } from '../hooks' import { RecentRunProtocolCard } from '../' -import { useNotifyAllRunsQuery } from '../../../../resources/runs' import type { NavigateFunction } from 'react-router-dom' -import type { ProtocolHardware } from '../../../../pages/Protocols/hooks' +import type { ProtocolHardware } from '/app/transformations/commands' const mockNavigate = vi.fn() @@ -41,15 +40,13 @@ vi.mock('react-router-dom', async importOriginal => { }) vi.mock('@opentrons/react-api-client') -vi.mock('../../../../atoms/Skeleton') -vi.mock('../../../../pages/Protocols/hooks') -vi.mock('../../../../pages/ProtocolDetails') -vi.mock('../../../../organisms/Devices/hooks') -vi.mock('../../../../organisms/RunTimeControl/hooks') -vi.mock('../../../../organisms/ProtocolUpload/hooks') -vi.mock('../../../../redux/analytics') +vi.mock('/app/atoms/Skeleton') +vi.mock('/app/transformations/commands') +vi.mock('/app/organisms/RunTimeControl/hooks') +vi.mock('/app/redux/analytics') vi.mock('../hooks') -vi.mock('../../../../resources/runs') +vi.mock('/app/resources/runs') +vi.mock('/app/redux-resources/analytics') const RUN_ID = 'mockRunId' const ROBOT_NAME = 'otie' @@ -90,7 +87,7 @@ const missingBoth = [ const mockRunData = { id: RUN_ID, createdAt: '2022-05-03T21:36:12.494778+00:00', - completedAt: 'thistime', + completedAt: '2023-05-03T21:36:12.494778+00:00', startedAt: 'thistime', protocolId: 'mockProtocolId', status: RUN_STATUS_FAILED, @@ -160,7 +157,8 @@ describe('RecentRunProtocolCard', () => { }) vi.mocked(useCloneRun).mockReturnValue({ cloneRun: mockCloneRun, - isLoading: false, + isLoadingRun: false, + isCloning: false, }) }) @@ -171,7 +169,7 @@ describe('RecentRunProtocolCard', () => { it('should render text', () => { render(props) const lastRunTime = formatDistance( - new Date(mockRunData.createdAt), + new Date(mockRunData.completedAt), new Date(), { addSuffix: true, diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/RecentRunProtocolCarousel.test.tsx b/app/src/organisms/ODD/RobotDashboard/__tests__/RecentRunProtocolCarousel.test.tsx similarity index 88% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/RecentRunProtocolCarousel.test.tsx rename to app/src/organisms/ODD/RobotDashboard/__tests__/RecentRunProtocolCarousel.test.tsx index f029d739806..277edf80d87 100644 --- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/__tests__/RecentRunProtocolCarousel.test.tsx +++ b/app/src/organisms/ODD/RobotDashboard/__tests__/RecentRunProtocolCarousel.test.tsx @@ -1,16 +1,16 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { beforeEach, describe, it, vi } from 'vitest' -import { renderWithProviders } from '../../../../__testing-utils__' -import { useNotifyAllRunsQuery } from '../../../../resources/runs' +import { renderWithProviders } from '/app/__testing-utils__' +import { useNotifyAllRunsQuery } from '/app/resources/runs' import { RecentRunProtocolCard, RecentRunProtocolCarousel } from '..' import type { RunData } from '@opentrons/api-client' vi.mock('@opentrons/react-api-client') vi.mock('../RecentRunProtocolCard') -vi.mock('../../../../resources/runs') +vi.mock('/app/resources/runs') const mockRun = { actions: [], diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/__tests__/useHardwareStatusText.test.tsx b/app/src/organisms/ODD/RobotDashboard/hooks/__tests__/useHardwareStatusText.test.tsx similarity index 95% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/__tests__/useHardwareStatusText.test.tsx rename to app/src/organisms/ODD/RobotDashboard/hooks/__tests__/useHardwareStatusText.test.tsx index c5c87003bff..f209e69f5ec 100644 --- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/__tests__/useHardwareStatusText.test.tsx +++ b/app/src/organisms/ODD/RobotDashboard/hooks/__tests__/useHardwareStatusText.test.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import type * as React from 'react' import { I18nextProvider } from 'react-i18next' import { renderHook } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' -import { i18n } from '../../../../../i18n' -import { useFeatureFlag } from '../../../../../redux/config' +import { i18n } from '/app/i18n' +import { useFeatureFlag } from '/app/redux/config' import { useHardwareStatusText } from '..' -vi.mock('../../../../../redux/config') +vi.mock('/app/redux/config') describe('useHardwareStatusText', () => { let wrapper: React.FunctionComponent<{ children: React.ReactNode }> diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/index.ts b/app/src/organisms/ODD/RobotDashboard/hooks/index.ts similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/index.ts rename to app/src/organisms/ODD/RobotDashboard/hooks/index.ts diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useHardwareStatusText.ts b/app/src/organisms/ODD/RobotDashboard/hooks/useHardwareStatusText.ts similarity index 94% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useHardwareStatusText.ts rename to app/src/organisms/ODD/RobotDashboard/hooks/useHardwareStatusText.ts index b9049596d9a..5ba7c189066 100644 --- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useHardwareStatusText.ts +++ b/app/src/organisms/ODD/RobotDashboard/hooks/useHardwareStatusText.ts @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next' -import type { ProtocolHardware } from '../../../../pages/Protocols/hooks' +import type { ProtocolHardware } from '/app/transformations/commands' export function useHardwareStatusText( missingProtocolHardware: ProtocolHardware[], diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useRerunnableStatusText.ts b/app/src/organisms/ODD/RobotDashboard/hooks/useRerunnableStatusText.ts similarity index 86% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useRerunnableStatusText.ts rename to app/src/organisms/ODD/RobotDashboard/hooks/useRerunnableStatusText.ts index 77ecd30a5b9..b57a47d39db 100644 --- a/app/src/organisms/OnDeviceDisplay/RobotDashboard/hooks/useRerunnableStatusText.ts +++ b/app/src/organisms/ODD/RobotDashboard/hooks/useRerunnableStatusText.ts @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next' import { useHardwareStatusText } from './useHardwareStatusText' -import type { ProtocolHardware } from '../../../../pages/Protocols/hooks' +import type { ProtocolHardware } from '/app/transformations/commands' export function useRerunnableStatusText( runOk: boolean, diff --git a/app/src/organisms/OnDeviceDisplay/RobotDashboard/index.ts b/app/src/organisms/ODD/RobotDashboard/index.ts similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RobotDashboard/index.ts rename to app/src/organisms/ODD/RobotDashboard/index.ts diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/DeviceReset.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/DeviceReset.tsx new file mode 100644 index 00000000000..40c6170fc56 --- /dev/null +++ b/app/src/organisms/ODD/RobotSettingsDashboard/DeviceReset.tsx @@ -0,0 +1,371 @@ +import { useState, useEffect, Fragment } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' +import styled from 'styled-components' + +import { + BORDERS, + COLORS, + DIRECTION_COLUMN, + DIRECTION_ROW, + Flex, + SPACING, + LegacyStyledText, + TYPOGRAPHY, + useConditionalConfirm, +} from '@opentrons/components' + +import { MediumButton, SmallButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { + getResetConfigOptions, + fetchResetConfigOptions, + resetConfig, +} from '/app/redux/robot-admin' +import { useDispatchApiRequest } from '/app/redux/robot-api' + +import type { Dispatch, State } from '/app/redux/types' +import type { ResetConfigRequest } from '/app/redux/robot-admin/types' +import type { SetSettingOption } from './types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' + +interface LabelProps { + isSelected?: boolean +} + +const OptionButton = styled.input` + display: none; +` + +const OptionLabel = styled.label` + padding: ${SPACING.spacing16} ${SPACING.spacing24}; + border-radius: ${BORDERS.borderRadius16}; + color: ${({ isSelected }) => + isSelected === true ? COLORS.white : COLORS.black90}; + background: ${({ isSelected }) => + isSelected === true ? COLORS.blue50 : COLORS.blue35}; +` + +interface DeviceResetProps { + robotName: string + setCurrentOption: SetSettingOption +} + +export function DeviceReset({ + robotName, + setCurrentOption, +}: DeviceResetProps): JSX.Element { + const { t } = useTranslation('device_settings') + const [resetOptions, setResetOptions] = useState({}) + const options = useSelector((state: State) => + getResetConfigOptions(state, robotName) + ) + const [dispatchRequest] = useDispatchApiRequest() + + const targetOptionsOrder = [ + 'pipetteOffsetCalibrations', + 'gripperOffsetCalibrations', + 'moduleCalibration', + 'runsHistory', + ] + + const availableOptions = options + // filtering out ODD setting because this gets implicitly cleared if all settings are selected + // filtering out boot scripts since product doesn't want this exposed to ODD users + .filter( + ({ id }) => + !['onDeviceDisplay', 'bootScripts', 'deckConfiguration'].includes(id) + ) + .sort( + (a, b) => + targetOptionsOrder.indexOf(a.id) - targetOptionsOrder.indexOf(b.id) + ) + const dispatch = useDispatch() + + const availableOptionsToDisplay = availableOptions.filter( + ({ id }) => !['authorizedKeys'].includes(id) + ) + + const isEveryOptionSelected = (obj: ResetConfigRequest): boolean => { + for (const key of targetOptionsOrder) { + if (obj != null && !obj[key]) { + return false + } + } + return true + } + + const handleClick = (): void => { + if (resetOptions != null) { + const { ...serverResetOptions } = resetOptions + dispatchRequest(resetConfig(robotName, serverResetOptions)) + } + } + + const { + confirm: confirmClearData, + showConfirmation: showConfirmationModal, + cancel: cancelClearData, + } = useConditionalConfirm(handleClick, true) + + const renderText = ( + optionId: string + ): { optionText: string; subText?: string } => { + let optionText = '' + let subText + switch (optionId) { + case 'pipetteOffsetCalibrations': + optionText = t('clear_option_pipette_calibrations') + break + case 'gripperOffsetCalibrations': + optionText = t('clear_option_gripper_calibration') + break + case 'moduleCalibration': + optionText = t('clear_option_module_calibration') + break + case 'runsHistory': + optionText = t('clear_option_runs_history') + subText = t('clear_option_runs_history_subtext') + break + + case 'factoryReset': + optionText = t('factory_reset') + subText = t('factory_reset_description') + break + default: + break + } + return { + optionText, + subText, + } + } + useEffect(() => { + dispatch(fetchResetConfigOptions(robotName)) + }, [dispatch, robotName]) + + useEffect(() => { + if ( + isEveryOptionSelected(resetOptions) && + (!resetOptions.authorizedKeys || + !resetOptions.onDeviceDisplay || + !resetOptions.deckConfiguration) + ) { + setResetOptions({ + ...resetOptions, + authorizedKeys: true, + onDeviceDisplay: true, + deckConfiguration: true, + }) + } + }, [resetOptions]) + + useEffect(() => { + if ( + !isEveryOptionSelected(resetOptions) && + resetOptions.authorizedKeys && + resetOptions.onDeviceDisplay && + resetOptions.deckConfiguration + ) { + setResetOptions({ + ...resetOptions, + authorizedKeys: false, + onDeviceDisplay: false, + deckConfiguration: false, + }) + } + }, [resetOptions]) + + return ( + + {showConfirmationModal && ( + + )} + { + setCurrentOption(null) + }} + /> + + + {availableOptionsToDisplay.map(option => { + const { optionText, subText } = renderText(option.id) + return ( + + { + setResetOptions({ + ...resetOptions, + [option.id]: !(resetOptions[option.id] ?? false), + }) + }} + /> + + + + {optionText} + + {subText != null ? ( + + {subText} + + ) : null} + + + + ) + })} + + { + setResetOptions( + (resetOptions.authorizedKeys ?? false) && + (resetOptions.onDeviceDisplay ?? false) + ? {} + : availableOptions.reduce( + (acc, val) => { + return { + ...acc, + [val.id]: true, + } + }, + { authorizedKeys: true, onDeviceDisplay: true } + ) + ) + }} + /> + + + + {t('clear_all_stored_data')} + + + {t('clear_all_stored_data_description')} + + + + + + resetOptions[option.id] === false || + resetOptions[option.id] === undefined + ) + } + onClick={confirmClearData} + /> + + + ) +} + +interface ConfirmClearDataModalProps { + cancelClearData: () => void + confirmClearData: () => void +} + +export const ConfirmClearDataModal = ({ + cancelClearData, + confirmClearData, +}: ConfirmClearDataModalProps): JSX.Element => { + const { t } = useTranslation(['device_settings', 'shared']) + const modalHeader: OddModalHeaderBaseProps = { + title: t('confirm_device_reset_heading'), + hasExitIcon: false, + iconName: 'ot-alert', + iconColor: COLORS.yellow50, + } + return ( + + + + + {t('confirm_device_reset_description')} + + + + + + + + + ) +} diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/LanguageSetting.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/LanguageSetting.tsx new file mode 100644 index 00000000000..49f58e26993 --- /dev/null +++ b/app/src/organisms/ODD/RobotSettingsDashboard/LanguageSetting.tsx @@ -0,0 +1,93 @@ +import { Fragment } from 'react' +import { useTranslation } from 'react-i18next' +import { useDispatch, useSelector } from 'react-redux' +import styled from 'styled-components' + +import { + BORDERS, + COLORS, + CURSOR_POINTER, + DIRECTION_COLUMN, + Flex, + SPACING, + StyledText, +} from '@opentrons/components' + +import { LANGUAGES } from '/app/i18n' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { getAppLanguage, updateConfigValue } from '/app/redux/config' + +import type { ChangeEvent } from 'react' +import type { Dispatch } from '/app/redux/types' +import type { SetSettingOption } from './types' + +interface LabelProps { + isSelected?: boolean +} + +const SettingButton = styled.input` + display: none; +` + +const SettingButtonLabel = styled.label` + padding: ${SPACING.spacing24}; + border-radius: ${BORDERS.borderRadius16}; + cursor: ${CURSOR_POINTER}; + background: ${({ isSelected }) => + isSelected === true ? COLORS.blue50 : COLORS.blue35}; + color: ${({ isSelected }) => isSelected === true && COLORS.white}; +` + +interface LanguageSettingProps { + setCurrentOption: SetSettingOption +} + +export function LanguageSetting({ + setCurrentOption, +}: LanguageSettingProps): JSX.Element { + const { t } = useTranslation('app_settings') + const dispatch = useDispatch() + + const appLanguage = useSelector(getAppLanguage) + + const handleChange = (event: ChangeEvent): void => { + dispatch(updateConfigValue('language.appLanguage', event.target.value)) + } + + return ( + + { + setCurrentOption(null) + }} + /> + + {LANGUAGES.map(lng => ( + + + + + {lng.name} + + + + ))} + + + ) +} diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/EthernetConnectionDetails.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/EthernetConnectionDetails.tsx similarity index 92% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/EthernetConnectionDetails.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/EthernetConnectionDetails.tsx index f76c04fdf31..842965ad677 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/EthernetConnectionDetails.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/EthernetConnectionDetails.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' import { css } from 'styled-components' @@ -15,11 +14,11 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ChildNavigation } from '../../../organisms/ChildNavigation' -import { getNetworkInterfaces } from '../../../redux/networking' -import { getLocalRobot } from '../../../redux/discovery' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { getNetworkInterfaces } from '/app/redux/networking' +import { getLocalRobot } from '/app/redux/discovery' -import type { State } from '../../../redux/types' +import type { State } from '/app/redux/types' const STRETCH_LIST_STYLE = css` width: 100%; diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal.tsx similarity index 93% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal.tsx index 980bf9e5991..ef835573c8f 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -14,9 +13,9 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { OddModal } from '../../../molecules/OddModal' +import { OddModal } from '/app/molecules/OddModal' -import type { OddModalHeaderBaseProps } from '../../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' interface NetworkDetailsModalProps { setShowNetworkDetailModal: (showNetworkDetailModal: boolean) => void diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsJoinOtherNetwork.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsJoinOtherNetwork.tsx new file mode 100644 index 00000000000..f3645066924 --- /dev/null +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsJoinOtherNetwork.tsx @@ -0,0 +1,55 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' + +import { DIRECTION_COLUMN, Flex } from '@opentrons/components' + +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { SetWifiSsid } from '../../NetworkSettings' + +import type { Dispatch, SetStateAction } from 'react' +import type { SetSettingOption } from '../types' + +interface RobotSettingsJoinOtherNetworkProps { + setCurrentOption: SetSettingOption + setSelectedSsid: Dispatch> +} + +/** + * Robot settings page wrapper for shared SetWifiSsid organism with child navigation header + */ +export function RobotSettingsJoinOtherNetwork({ + setCurrentOption, + setSelectedSsid, +}: RobotSettingsJoinOtherNetworkProps): JSX.Element { + const { i18n, t } = useTranslation('device_settings') + + const [inputSsid, setInputSsid] = useState('') + const [errorMessage, setErrorMessage] = useState(null) + + const handleContinue = (): void => { + if (inputSsid.length >= 2 && inputSsid.length <= 32) { + setSelectedSsid(inputSsid) + setCurrentOption('RobotSettingsSelectAuthenticationType') + } else { + setErrorMessage(t('join_other_network_error_message') as string) + } + } + + return ( + + { + setCurrentOption('RobotSettingsWifi') + }} + onClickButton={handleContinue} + /> + + + ) +} diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsSelectAuthenticationType.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsSelectAuthenticationType.tsx similarity index 83% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsSelectAuthenticationType.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsSelectAuthenticationType.tsx index feee69d2ad1..f8ce5e6a205 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsSelectAuthenticationType.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsSelectAuthenticationType.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { DIRECTION_COLUMN, Flex } from '@opentrons/components' -import { ChildNavigation } from '../../../organisms/ChildNavigation' -import { SelectAuthenticationType } from '../../../organisms/NetworkSettings/SelectAuthenticationType' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { SelectAuthenticationType } from '../../NetworkSettings' import type { WifiSecurityType } from '@opentrons/api-client' -import type { SetSettingOption } from '../../../pages/RobotSettingsDashboard' +import type { SetSettingOption } from '../types' interface RobotSettingsSelectAuthenticationTypeProps { handleWifiConnect: () => void diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsSetWifiCred.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsSetWifiCred.tsx similarity index 79% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsSetWifiCred.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsSetWifiCred.tsx index 7dcf8024b74..9204f22f5c4 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsSetWifiCred.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsSetWifiCred.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { DIRECTION_COLUMN, Flex } from '@opentrons/components' -import { ChildNavigation } from '../../../organisms/ChildNavigation' -import { SetWifiCred } from '../../../organisms/NetworkSettings/SetWifiCred' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { SetWifiCred } from '../../NetworkSettings/SetWifiCred' -import type { SetSettingOption } from '../../../pages/RobotSettingsDashboard' +import type { SetSettingOption } from '../types' interface RobotSettingsSetWifiCredProps { handleConnect: () => void diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifi.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifi.tsx similarity index 88% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifi.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifi.tsx index 2126b7a2610..add3565fe74 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifi.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifi.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { DIRECTION_COLUMN, Flex } from '@opentrons/components' -import { ChildNavigation } from '../../../organisms/ChildNavigation' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' import { WifiConnectionDetails } from './WifiConnectionDetails' import type { WifiSecurityType } from '@opentrons/api-client' -import type { SetSettingOption } from '../../../pages/RobotSettingsDashboard' +import type { SetSettingOption } from '../types' interface RobotSettingsWifiProps { setSelectedSsid: React.Dispatch> diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifiConnect.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifiConnect.tsx similarity index 83% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifiConnect.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifiConnect.tsx index 40ba28cdd14..14dfdb6bd12 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifiConnect.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/RobotSettingsWifiConnect.tsx @@ -1,17 +1,13 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { Flex, DIRECTION_COLUMN, SPACING } from '@opentrons/components' -import { ChildNavigation } from '../../../organisms/ChildNavigation' -import { - ConnectingNetwork, - FailedToConnect, -} from '../../../organisms/NetworkSettings' -import { FAILURE, PENDING, SUCCESS } from '../../../redux/robot-api' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { ConnectingNetwork, FailedToConnect } from '../../NetworkSettings' +import { FAILURE, PENDING, SUCCESS } from '/app/redux/robot-api' -import type { SetSettingOption } from '../../../pages/RobotSettingsDashboard' -import type { RequestState } from '../../../redux/robot-api/types' +import type { SetSettingOption } from '../types' +import type { RequestState } from '/app/redux/robot-api/types' interface RobotSettingsWifiConnectProps { handleConnect: () => void diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/WifiConnectionDetails.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/WifiConnectionDetails.tsx new file mode 100644 index 00000000000..cc980592572 --- /dev/null +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/WifiConnectionDetails.tsx @@ -0,0 +1,165 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' + +import { + ALIGN_CENTER, + BORDERS, + Btn, + COLORS, + DIRECTION_COLUMN, + DIRECTION_ROW, + DISPLAY_FLEX, + Flex, + Icon, + JUSTIFY_SPACE_BETWEEN, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' + +import { NetworkDetailsModal } from './NetworkDetailsModal' +import { DisplayWifiList } from '../../NetworkSettings' +import { getLocalRobot } from '/app/redux/discovery' +import { getNetworkInterfaces } from '/app/redux/networking' +import { useWifiList } from '/app/resources/networking/hooks' + +import type { WifiSecurityType } from '@opentrons/api-client' +import type { State } from '/app/redux/types' + +const FETCH_WIFI_LIST_MS = 5000 + +interface WifiConnectionDetailsProps { + handleJoinAnotherNetwork: () => void + handleNetworkPress: (ssid: string) => void + activeSsid?: string + connectedWifiAuthType?: WifiSecurityType +} +export function WifiConnectionDetails({ + activeSsid, + connectedWifiAuthType, + handleNetworkPress, + handleJoinAnotherNetwork, +}: WifiConnectionDetailsProps): JSX.Element { + const { i18n, t } = useTranslation(['device_settings', 'shared']) + const [showNetworkDetailModal, setShowNetworkDetailModal] = useState( + false + ) + const localRobot = useSelector(getLocalRobot) + const robotName = localRobot?.name != null ? localRobot.name : 'no name' + const list = useWifiList(robotName, FETCH_WIFI_LIST_MS) + const { wifi } = useSelector((state: State) => + getNetworkInterfaces(state, robotName) + ) + const noData = i18n.format(t('shared:no_data'), 'titleCase') + const ipAddress = wifi?.ipAddress != null ? wifi.ipAddress : noData + const subnetMask = wifi?.subnetMask != null ? wifi.subnetMask : noData + const macAddress = wifi?.macAddress != null ? wifi.macAddress : noData + + return ( + <> + {showNetworkDetailModal ? ( + + ) : null} + + {activeSsid != null ? ( + + + {t('connected_network')} + + { + setShowNetworkDetailModal(true) + }} + alignItems={ALIGN_CENTER} + > + + + + + + + {activeSsid} + + + + + + + + {t('view_details')} + + + + + + ) : null} + {activeSsid != null ? ( + + {t('other_networks')} + + ) : null} + { + handleNetworkPress(ssid) + }} + /> + + + ) +} diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/EthernetConnectionDetails.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/EthernetConnectionDetails.test.tsx similarity index 81% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/EthernetConnectionDetails.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/EthernetConnectionDetails.test.tsx index a82b17e3455..ddefd80196d 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/EthernetConnectionDetails.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/EthernetConnectionDetails.test.tsx @@ -1,19 +1,19 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../../i18n' -import { INTERFACE_ETHERNET } from '../../../../redux/networking' -import { getNetworkInterfaces } from '../../../../redux/networking/selectors' -import { renderWithProviders } from '../../../../__testing-utils__' -import { getLocalRobot } from '../../../../redux/discovery' -import { mockConnectedRobot } from '../../../../redux/discovery/__fixtures__' +import { i18n } from '/app/i18n' +import { INTERFACE_ETHERNET } from '/app/redux/networking' +import { getNetworkInterfaces } from '/app/redux/networking/selectors' +import { renderWithProviders } from '/app/__testing-utils__' +import { getLocalRobot } from '/app/redux/discovery' +import { mockConnectedRobot } from '/app/redux/discovery/__fixtures__' import { EthernetConnectionDetails } from '../EthernetConnectionDetails' -vi.mock('../../../../redux/discovery') -vi.mock('../../../../redux/discovery/selectors') -vi.mock('../../../../redux/networking/selectors') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/discovery/selectors') +vi.mock('/app/redux/networking/selectors') const render = ( props: React.ComponentProps diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkDetailsModal.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkDetailsModal.test.tsx similarity index 94% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkDetailsModal.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkDetailsModal.test.tsx index 6333bec9b81..76b4c6f1be0 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkDetailsModal.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkDetailsModal.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../../i18n' -import { renderWithProviders } from '../../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { NetworkDetailsModal } from '../NetworkDetailsModal' const mockFn = vi.fn() diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkSettings.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkSettings.test.tsx similarity index 85% rename from app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkSettings.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkSettings.test.tsx index 5d27163d300..266778c0c81 100644 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkSettings.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/NetworkSettings.test.tsx @@ -1,22 +1,22 @@ /* eslint-disable testing-library/no-node-access */ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../../i18n' -import { renderWithProviders } from '../../../../__testing-utils__' -import { getLocalRobot } from '../../../../redux/discovery' -import { useWifiList } from '../../../../resources/networking/hooks' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { getLocalRobot } from '/app/redux/discovery' +import { useWifiList } from '/app/resources/networking/hooks' import { WifiConnectionDetails } from '../WifiConnectionDetails' import { EthernetConnectionDetails } from '../EthernetConnectionDetails' import { NetworkSettings } from '..' -import type { DiscoveredRobot } from '../../../../redux/discovery/types' -import type { WifiNetwork } from '../../../../redux/networking/types' +import type { DiscoveredRobot } from '/app/redux/discovery/types' +import type { WifiNetwork } from '/app/redux/networking/types' -vi.mock('../../../../redux/discovery') -vi.mock('../../../../resources/networking/hooks') +vi.mock('/app/redux/discovery') +vi.mock('/app/resources/networking/hooks') vi.mock('../WifiConnectionDetails') vi.mock('../EthernetConnectionDetails') diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx new file mode 100644 index 00000000000..9650a89b76c --- /dev/null +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx @@ -0,0 +1,94 @@ +import type * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { when } from 'vitest-when' +import { describe, it, expect, vi, beforeEach } from 'vitest' +import '@testing-library/jest-dom/vitest' + +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { getLocalRobot } from '/app/redux/discovery' +import * as Networking from '/app/redux/networking' +import { NetworkDetailsModal } from '../NetworkDetailsModal' +import { WifiConnectionDetails } from '../WifiConnectionDetails' +import type * as Dom from 'react-router-dom' +import type { State } from '/app/redux/types' + +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/networking') +vi.mock('../NetworkDetailsModal') + +const mockNavigate = vi.fn() +vi.mock('react-router-dom', async importOriginal => { + const reactRouterDom = await importOriginal() + return { + ...reactRouterDom, + useNavigate: () => mockNavigate, + } +}) + +const getNetworkInterfaces = Networking.getNetworkInterfaces +const ROBOT_NAME = 'otie' + +const initialMockWifi = { + ipAddress: '127.0.0.100', + subnetMask: '255.255.255.230', + macAddress: 'WI:FI:00:00:00:00', + type: Networking.INTERFACE_WIFI, +} + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} + +describe('WifiConnectionDetails', () => { + let props: React.ComponentProps + beforeEach(() => { + props = { + activeSsid: 'mock wifi ssid', + connectedWifiAuthType: 'none', + handleNetworkPress: vi.fn(), + handleJoinAnotherNetwork: vi.fn(), + } + vi.mocked(getLocalRobot).mockReturnValue({ + name: ROBOT_NAME, + } as any) + when(getNetworkInterfaces) + .calledWith({} as State, ROBOT_NAME) + .thenReturn({ + wifi: initialMockWifi, + ethernet: null, + }) + vi.mocked(NetworkDetailsModal).mockReturnValue( +
    mock NetworkDetailsModal
    + ) + }) + + it('should render text and button with icon when connected to a network', () => { + render(props) + screen.getByText('Connected Network') + screen.getByLabelText('mock wifi ssid_wifi_icon') + screen.getByLabelText('mock wifi ssid_info_icon') + screen.getByText('mock wifi ssid') + screen.getByText('View details') + screen.getByText('Other Networks') + }) + + it('should show the modal when tapping connected wifi button', () => { + render(props) + const button = screen.getByText('mock wifi ssid') + fireEvent.click(button) + screen.getByText('mock NetworkDetailsModal') + }) + + it('should not render text and button when not connected to a network', () => { + props.activeSsid = undefined + render(props) + expect(screen.queryByText('Connected Network')).not.toBeInTheDocument() + expect(screen.queryByText('mock wifi ssid')).not.toBeInTheDocument() + expect(screen.queryByText('Other Networks')).not.toBeInTheDocument() + }) + + it.todo('should render the wifi list') +}) diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/index.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/index.tsx new file mode 100644 index 00000000000..db73b89dae3 --- /dev/null +++ b/app/src/organisms/ODD/RobotSettingsDashboard/NetworkSettings/index.tsx @@ -0,0 +1,171 @@ +import type * as React from 'react' +import { css } from 'styled-components' +import { useTranslation } from 'react-i18next' + +import { + ALIGN_CENTER, + BORDERS, + Btn, + Chip, + COLORS, + DIRECTION_COLUMN, + DIRECTION_ROW, + Flex, + Icon, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' + +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' + +import type { IconName, ChipType } from '@opentrons/components' +import type { NetworkConnection } from '/app/resources/networking/hooks/useNetworkConnection' +import type { SetSettingOption } from '../types' + +export type ConnectionType = 'wifi' | 'ethernet' | 'usb' + +interface NetworkSettingsProps { + networkConnection: NetworkConnection + setCurrentOption: SetSettingOption +} + +export function NetworkSettings({ + networkConnection, + setCurrentOption, +}: NetworkSettingsProps): JSX.Element { + const { t } = useTranslation('device_settings') + const { isWifiConnected, isEthernetConnected, activeSsid } = networkConnection + + const handleChipType = (isConnected: boolean): ChipType => { + return isConnected ? 'success' : 'neutral' + } + + const handleButtonBackgroundColor = (isConnected: boolean): string => + isConnected ? COLORS.green35 : COLORS.grey35 + + const handleChipText = (isConnected: boolean): string => + isConnected ? t('connected') : t('not_connected') + + return ( + + { + setCurrentOption(null) + }} + /> + + { + setCurrentOption('RobotSettingsWifi') + }} + /> + { + setCurrentOption('EthernetConnectionDetails') + }} + /> + + + ) +} + +interface NetworkSettingButtonProps extends React.ComponentProps { + buttonTitle: string + iconName: IconName + chipType: ChipType + chipText: string + networkName?: string +} + +function NetworkSettingButton({ + backgroundColor, + buttonTitle, + iconName, + chipType, + chipText, + networkName, + onClick, +}: NetworkSettingButtonProps): JSX.Element { + const PUSHED_STATE_STYLE = css` + &:active { + background-color: ${chipType === 'success' + ? COLORS.green40 + : COLORS.grey50}; + } + ` + + return ( + + + + + + + + {buttonTitle} + + {networkName != null ? ( + + {networkName} + + ) : null} + + + + + + + + { + console.log('setup') + }} + > + + + + + + ) +} diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/OnOffToggle.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/OnOffToggle.tsx new file mode 100644 index 00000000000..32b3a94e474 --- /dev/null +++ b/app/src/organisms/ODD/RobotSettingsDashboard/OnOffToggle.tsx @@ -0,0 +1,29 @@ +import { useTranslation } from 'react-i18next' +import { + ALIGN_CENTER, + BORDERS, + COLORS, + DIRECTION_ROW, + Flex, + LegacyStyledText, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' + +export function OnOffToggle(props: { isOn: boolean }): JSX.Element { + const { t } = useTranslation('shared') + return ( + + + {props.isOn ? t('on') : t('off')} + + + ) +} diff --git a/app/src/organisms/RobotSettingsDashboard/Privacy.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/Privacy.tsx similarity index 79% rename from app/src/organisms/RobotSettingsDashboard/Privacy.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/Privacy.tsx index 51a096af3a3..3151d618b53 100644 --- a/app/src/organisms/RobotSettingsDashboard/Privacy.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/Privacy.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' @@ -10,16 +9,16 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ChildNavigation } from '../../organisms/ChildNavigation' -import { RobotSettingButton } from '../../pages/RobotSettingsDashboard/RobotSettingButton' -import { OnOffToggle } from '../../pages/RobotSettingsDashboard/RobotSettingsList' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { RobotSettingButton } from './RobotSettingButton' +import { OnOffToggle } from './OnOffToggle' import { getAnalyticsOptedIn, toggleAnalyticsOptedIn, -} from '../../redux/analytics' +} from '/app/redux/analytics' -import type { Dispatch } from '../../redux/types' -import type { SetSettingOption } from '../../pages/RobotSettingsDashboard' +import type { Dispatch } from '/app/redux/types' +import type { SetSettingOption } from './types' interface PrivacyProps { robotName: string diff --git a/app/src/organisms/RobotSettingsDashboard/RobotName.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/RobotName.tsx similarity index 88% rename from app/src/organisms/RobotSettingsDashboard/RobotName.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/RobotName.tsx index 40c30ae8877..3e640f2b4c0 100644 --- a/app/src/organisms/RobotSettingsDashboard/RobotName.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/RobotName.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -10,7 +9,7 @@ import { LegacyStyledText, } from '@opentrons/components' -import type { SetSettingOption } from '../../pages/RobotSettingsDashboard' +import type { SetSettingOption } from './types' interface RobotNameProps { setCurrentOption: SetSettingOption diff --git a/app/src/pages/RobotSettingsDashboard/RobotSettingButton.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/RobotSettingButton.tsx similarity index 96% rename from app/src/pages/RobotSettingsDashboard/RobotSettingButton.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/RobotSettingButton.tsx index d8390ee7941..f777c9fcb77 100644 --- a/app/src/pages/RobotSettingsDashboard/RobotSettingButton.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/RobotSettingButton.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { css } from 'styled-components' import { @@ -14,9 +14,10 @@ import { Icon, JUSTIFY_CENTER, JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, + NO_WRAP, SPACING, TYPOGRAPHY, - LegacyStyledText, } from '@opentrons/components' import type { IconName } from '@opentrons/components' @@ -65,7 +66,7 @@ export function RobotSettingButton({ gridGap={SPACING.spacing24} alignItems={ALIGN_CENTER} width="100%" - whiteSpace="nowrap" + whiteSpace={NO_WRAP} > {iconName != null ? ( diff --git a/app/src/organisms/RobotSettingsDashboard/RobotSystemVersion.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/RobotSystemVersion.tsx similarity index 88% rename from app/src/organisms/RobotSettingsDashboard/RobotSystemVersion.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/RobotSystemVersion.tsx index 61e36d7ca18..37d9352659e 100644 --- a/app/src/organisms/RobotSettingsDashboard/RobotSystemVersion.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/RobotSystemVersion.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { @@ -13,12 +13,12 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { MediumButton } from '../../atoms/buttons' -import { ChildNavigation } from '../../organisms/ChildNavigation' +import { MediumButton } from '/app/atoms/buttons' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' import { RobotSystemVersionModal } from './RobotSystemVersionModal' -import type { RobotUpdateInfo } from '../../redux/robot-update/types' -import type { SetSettingOption } from '../../pages/RobotSettingsDashboard' +import type { RobotUpdateInfo } from '/app/redux/robot-update/types' +import type { SetSettingOption } from './types' const GITHUB_URL = 'https://github.com/Opentrons/opentrons/releases' @@ -42,7 +42,7 @@ export function RobotSystemVersion({ 'app_settings', 'branded', ]) - const [showModal, setShowModal] = React.useState(isUpdateAvailable) + const [showModal, setShowModal] = useState(isUpdateAvailable) return ( <> diff --git a/app/src/organisms/RobotSettingsDashboard/RobotSystemVersionModal.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/RobotSystemVersionModal.tsx similarity index 83% rename from app/src/organisms/RobotSettingsDashboard/RobotSystemVersionModal.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/RobotSystemVersionModal.tsx index 028ca40b1cc..9d09a2cf6b2 100644 --- a/app/src/organisms/RobotSettingsDashboard/RobotSystemVersionModal.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/RobotSystemVersionModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' @@ -10,12 +9,12 @@ import { SPACING, } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' -import { InlineNotification } from '../../atoms/InlineNotification' -import { ReleaseNotes } from '../../molecules/ReleaseNotes' -import { OddModal } from '../../molecules/OddModal' +import { SmallButton } from '/app/atoms/buttons' +import { InlineNotification } from '/app/atoms/InlineNotification' +import { ReleaseNotes } from '/app/molecules/ReleaseNotes' +import { OddModal } from '/app/molecules/OddModal' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' interface RobotSystemVersionModalProps { version: string diff --git a/app/src/organisms/RobotSettingsDashboard/TextSize.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/TextSize.tsx similarity index 96% rename from app/src/organisms/RobotSettingsDashboard/TextSize.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/TextSize.tsx index dd0bf31910e..56ca4f8c053 100644 --- a/app/src/organisms/RobotSettingsDashboard/TextSize.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/TextSize.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -18,7 +17,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import type { SetSettingOption } from '../../pages/RobotSettingsDashboard' +import type { SetSettingOption } from './types' interface RectProps { isActive: boolean diff --git a/app/src/organisms/RobotSettingsDashboard/TouchScreenSleep.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/TouchScreenSleep.tsx similarity index 81% rename from app/src/organisms/RobotSettingsDashboard/TouchScreenSleep.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/TouchScreenSleep.tsx index d6c6c33b2cd..cb123b261be 100644 --- a/app/src/organisms/RobotSettingsDashboard/TouchScreenSleep.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/TouchScreenSleep.tsx @@ -1,23 +1,24 @@ -import * as React from 'react' +import { useEffect, useRef } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import { DIRECTION_COLUMN, Flex, - SPACING, RadioButton, + SPACING, } from '@opentrons/components' -import { ChildNavigation } from '../../organisms/ChildNavigation' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' import { getOnDeviceDisplaySettings, updateConfigValue, -} from '../../redux/config' -import { SLEEP_NEVER_MS } from '../../App/constants' +} from '/app/redux/config' +import { SLEEP_NEVER_MS } from '/app/local-resources/config' -import type { Dispatch } from '../../redux/types' -import type { SetSettingOption } from '../../pages/RobotSettingsDashboard' +import type { ChangeEvent } from 'react' +import type { Dispatch } from '/app/redux/types' +import type { SetSettingOption } from './types' const SLEEP_TIME_MS = 60 * 1000 // 1 min @@ -31,7 +32,7 @@ export function TouchScreenSleep({ const { t } = useTranslation(['device_settings']) const { sleepMs } = useSelector(getOnDeviceDisplaySettings) ?? SLEEP_NEVER_MS const dispatch = useDispatch() - const screenRef = React.useRef(null) + const screenRef = useRef(null) // Note (kj:02/10/2023) value's unit is ms const settingsButtons = [ @@ -44,7 +45,7 @@ export function TouchScreenSleep({ { label: t('one_hour'), value: SLEEP_TIME_MS * 60 }, ] - const handleChange = (event: React.ChangeEvent): void => { + const handleChange = (event: ChangeEvent): void => { dispatch( updateConfigValue( 'onDeviceDisplaySettings.sleepMs', @@ -53,7 +54,7 @@ export function TouchScreenSleep({ ) } - React.useEffect(() => { + useEffect(() => { if (screenRef.current != null) screenRef.current.scrollIntoView() }, []) diff --git a/app/src/organisms/RobotSettingsDashboard/TouchscreenBrightness.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/TouchscreenBrightness.tsx similarity index 88% rename from app/src/organisms/RobotSettingsDashboard/TouchscreenBrightness.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/TouchscreenBrightness.tsx index 7939620f250..c84d4abf574 100644 --- a/app/src/organisms/RobotSettingsDashboard/TouchscreenBrightness.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/TouchscreenBrightness.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useDispatch, useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' import styled from 'styled-components' @@ -16,15 +16,15 @@ import { SPACING, } from '@opentrons/components' -import { ChildNavigation } from '../../organisms/ChildNavigation' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' import { getOnDeviceDisplaySettings, updateConfigValue, -} from '../../redux/config' +} from '/app/redux/config' -import type { Dispatch } from '../../redux/types' -import type { SetSettingOption } from '../../pages/RobotSettingsDashboard' -import { IconButton } from '../../atoms/buttons/IconButton' +import type { Dispatch } from '/app/redux/types' +import type { SetSettingOption } from './types' +import { IconButton } from '/app/atoms/buttons/IconButton' interface BrightnessTileProps { isActive: boolean @@ -54,7 +54,7 @@ export function TouchscreenBrightness({ const { t } = useTranslation(['device_settings']) const dispatch = useDispatch() const initialBrightness = useSelector(getOnDeviceDisplaySettings).brightness - const [brightness, setBrightness] = React.useState(initialBrightness) + const [brightness, setBrightness] = useState(initialBrightness) const brightnessLevel = [6, 5, 4, 3, 2, 1] const handleClick = (changeType: 'up' | 'down'): void => { diff --git a/app/src/organisms/RobotSettingsDashboard/UpdateChannel.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/UpdateChannel.tsx similarity index 89% rename from app/src/organisms/RobotSettingsDashboard/UpdateChannel.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/UpdateChannel.tsx index 5c3bd7f8737..642aeae4af3 100644 --- a/app/src/organisms/RobotSettingsDashboard/UpdateChannel.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/UpdateChannel.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { Fragment } from 'react' import { useTranslation } from 'react-i18next' import { useDispatch, useSelector } from 'react-redux' import styled from 'styled-components' @@ -11,17 +11,19 @@ import { SPACING, LegacyStyledText, TYPOGRAPHY, + CURSOR_POINTER, } from '@opentrons/components' -import { ChildNavigation } from '../../organisms/ChildNavigation' +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' import { getDevtoolsEnabled, getUpdateChannel, getUpdateChannelOptions, updateConfigValue, -} from '../../redux/config' +} from '/app/redux/config' -import type { Dispatch } from '../../redux/types' +import type { ChangeEvent } from 'react' +import type { Dispatch } from '/app/redux/types' interface LabelProps { isSelected?: boolean @@ -34,7 +36,7 @@ const SettingButton = styled.input` const SettingButtonLabel = styled.label` padding: ${SPACING.spacing24}; border-radius: ${BORDERS.borderRadius16}; - cursor: pointer; + cursor: ${CURSOR_POINTER}; background: ${({ isSelected }) => isSelected === true ? COLORS.blue50 : COLORS.blue35}; color: ${({ isSelected }) => isSelected === true && COLORS.white}; @@ -58,7 +60,7 @@ export function UpdateChannel({ ? channelOptions.filter(option => option.value !== 'alpha') : channelOptions - const handleChange = (event: React.ChangeEvent): void => { + const handleChange = (event: ChangeEvent): void => { dispatch(updateConfigValue('update.channel', event.target.value)) } @@ -86,7 +88,7 @@ export function UpdateChannel({ marginTop={SPACING.spacing24} > {modifiedChannelOptions.map(radio => ( - + ) : null} - + ))}
    diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/DeviceReset.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/DeviceReset.test.tsx new file mode 100644 index 00000000000..9f71205231e --- /dev/null +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/DeviceReset.test.tsx @@ -0,0 +1,185 @@ +import type * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, expect, vi, beforeEach } from 'vitest' +import '@testing-library/jest-dom/vitest' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { getResetConfigOptions, resetConfig } from '/app/redux/robot-admin' +import { useDispatchApiRequest } from '/app/redux/robot-api' + +import { DeviceReset } from '../DeviceReset' + +import type { DispatchApiRequestType } from '/app/redux/robot-api' + +vi.mock('/app/redux/robot-admin') +vi.mock('/app/redux/robot-api') + +const mockResetConfigOptions = [ + { + id: 'pipetteOffsetCalibrations', + name: 'pipette calibration FooBar', + description: 'pipette calibration fooBar description', + }, + { + id: 'gripperOffsetCalibrations', + name: 'gripper calibration FooBar', + description: 'runsHistory fooBar description', + }, + { + id: 'runsHistory', + name: 'RunsHistory FooBar', + description: 'runsHistory fooBar description', + }, + { + id: 'bootScripts', + name: 'Boot Scripts FooBar', + description: 'bootScripts fooBar description', + }, + { + id: 'moduleCalibration', + name: 'Module Calibration FooBar', + description: 'moduleCalibration fooBar description', + }, + { + id: 'authorizedKeys', + name: 'SSH Keys Foo', + description: 'SSH Keys foo description', + }, +] + +const render = (props: React.ComponentProps) => { + return renderWithProviders( + , + + { i18nInstance: i18n } + ) +} + +describe('DeviceReset', () => { + let props: React.ComponentProps + let dispatchApiRequest: DispatchApiRequestType + + beforeEach(() => { + props = { + robotName: 'mockRobot', + setCurrentOption: vi.fn(), + } + vi.mocked(getResetConfigOptions).mockReturnValue(mockResetConfigOptions) + dispatchApiRequest = vi.fn() + vi.mocked(useDispatchApiRequest).mockReturnValue([dispatchApiRequest, []]) + }) + + it('should render text and button', () => { + render(props) + screen.getByText('Clear pipette calibration') + screen.getByText('Clear gripper calibration') + screen.getByText('Clear module calibration') + screen.getByText('Clear protocol run history') + screen.getByText('Clears information about past runs of all protocols.') + screen.getByText('Clear all stored data') + screen.getByText( + 'Clears calibrations, protocols, and all settings except robot name and network settings.' + ) + expect( + screen.queryByText('Clear the ssh authorized keys') + ).not.toBeInTheDocument() + expect(screen.getByTestId('DeviceReset_clear_data_button')).toBeDisabled() + }) + + it('when tapping a option button, the clear button is enabled', () => { + render(props) + fireEvent.click(screen.getByText('Clear pipette calibration')) + expect( + screen.getByTestId('DeviceReset_clear_data_button') + ).not.toBeDisabled() + }) + + it('when tapping a option button and tapping the clear button, a mock function is called', () => { + const clearMockResetOptions = { + pipetteOffsetCalibrations: true, + moduleCalibration: true, + runsHistory: true, + } + render(props) + fireEvent.click(screen.getByText('Clear pipette calibration')) + fireEvent.click(screen.getByText('Clear protocol run history')) + fireEvent.click(screen.getByText('Clear module calibration')) + const clearButton = screen.getByText('Clear data and restart robot') + fireEvent.click(clearButton) + screen.getByText('Are you sure you want to reset your device?') + fireEvent.click(screen.getByText('Confirm')) + expect(dispatchApiRequest).toBeCalledWith( + resetConfig('mockRobot', clearMockResetOptions) + ) + }) + + it('when tapping clear all stored data, all options are active', () => { + const clearMockResetOptions = { + pipetteOffsetCalibrations: true, + moduleCalibration: true, + runsHistory: true, + gripperOffsetCalibrations: true, + authorizedKeys: true, + onDeviceDisplay: true, + deckConfiguration: true, + } + + render(props) + fireEvent.click(screen.getByText('Clear all stored data')) + const clearButton = screen.getByText('Clear data and restart robot') + fireEvent.click(clearButton) + screen.getByText('Are you sure you want to reset your device?') + fireEvent.click(screen.getByText('Confirm')) + expect(dispatchApiRequest).toBeCalledWith( + resetConfig('mockRobot', clearMockResetOptions) + ) + }) + + it('when tapping all options except clear all stored data, all options are active', () => { + const clearMockResetOptions = { + pipetteOffsetCalibrations: true, + moduleCalibration: true, + runsHistory: true, + gripperOffsetCalibrations: true, + authorizedKeys: true, + onDeviceDisplay: true, + deckConfiguration: true, + } + + render(props) + fireEvent.click(screen.getByText('Clear pipette calibration')) + fireEvent.click(screen.getByText('Clear gripper calibration')) + fireEvent.click(screen.getByText('Clear module calibration')) + fireEvent.click(screen.getByText('Clear protocol run history')) + const clearButton = screen.getByText('Clear data and restart robot') + fireEvent.click(clearButton) + screen.getByText('Are you sure you want to reset your device?') + fireEvent.click(screen.getByText('Confirm')) + expect(dispatchApiRequest).toBeCalledWith( + resetConfig('mockRobot', clearMockResetOptions) + ) + }) + + it('when tapping clear all stored data and unselect one options, all options are not active', () => { + const clearMockResetOptions = { + pipetteOffsetCalibrations: false, + moduleCalibration: true, + runsHistory: true, + gripperOffsetCalibrations: true, + authorizedKeys: false, + onDeviceDisplay: false, + deckConfiguration: false, + } + + render(props) + fireEvent.click(screen.getByText('Clear all stored data')) + fireEvent.click(screen.getByText('Clear pipette calibration')) + const clearButton = screen.getByText('Clear data and restart robot') + fireEvent.click(clearButton) + screen.getByText('Are you sure you want to reset your device?') + fireEvent.click(screen.getByText('Confirm')) + expect(dispatchApiRequest).toBeCalledWith( + resetConfig('mockRobot', clearMockResetOptions) + ) + }) +}) diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/LanguageSetting.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/LanguageSetting.test.tsx new file mode 100644 index 00000000000..80d35ebea15 --- /dev/null +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/LanguageSetting.test.tsx @@ -0,0 +1,60 @@ +import type * as React from 'react' +import { fireEvent, screen } from '@testing-library/react' +import { describe, it, expect, vi, beforeEach } from 'vitest' +import '@testing-library/jest-dom/vitest' + +import { + i18n, + US_ENGLISH_DISPLAY_NAME, + US_ENGLISH, + SIMPLIFIED_CHINESE_DISPLAY_NAME, + SIMPLIFIED_CHINESE, +} from '/app/i18n' +import { getAppLanguage, updateConfigValue } from '/app/redux/config' +import { renderWithProviders } from '/app/__testing-utils__' + +import { LanguageSetting } from '../LanguageSetting' + +vi.mock('/app/redux/config') + +const mockSetCurrentOption = vi.fn() + +const render = (props: React.ComponentProps) => { + return renderWithProviders(, { + i18nInstance: i18n, + }) +} + +describe('LanguageSetting', () => { + let props: React.ComponentProps + beforeEach(() => { + props = { + setCurrentOption: mockSetCurrentOption, + } + vi.mocked(getAppLanguage).mockReturnValue(US_ENGLISH) + }) + + it('should render text and buttons', () => { + render(props) + screen.getByText('Language') + screen.getByText(US_ENGLISH_DISPLAY_NAME) + screen.getByText(SIMPLIFIED_CHINESE_DISPLAY_NAME) + }) + + it('should call mock function when tapping a language button', () => { + render(props) + const button = screen.getByText(SIMPLIFIED_CHINESE_DISPLAY_NAME) + fireEvent.click(button) + expect(updateConfigValue).toHaveBeenCalledWith( + 'language.appLanguage', + SIMPLIFIED_CHINESE + ) + }) + + it('should call mock function when tapping back button', () => { + render(props) + const button = screen.getByRole('button') + fireEvent.click(button) + expect(props.setCurrentOption).toHaveBeenCalled() + }) +}) diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/Privacy.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/Privacy.test.tsx similarity index 77% rename from app/src/organisms/RobotSettingsDashboard/__tests__/Privacy.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/__tests__/Privacy.test.tsx index 6e1a12878e3..03f4a987462 100644 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/Privacy.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/Privacy.test.tsx @@ -1,16 +1,16 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { vi, describe, beforeEach, afterEach, expect, it } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { toggleAnalyticsOptedIn } from '../../../redux/analytics' -import { getRobotSettings } from '../../../redux/robot-settings' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { toggleAnalyticsOptedIn } from '/app/redux/analytics' +import { getRobotSettings } from '/app/redux/robot-settings' import { Privacy } from '../Privacy' -vi.mock('../../../redux/analytics') -vi.mock('../../../redux/robot-settings') +vi.mock('/app/redux/analytics') +vi.mock('/app/redux/robot-settings') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/RobotSystemVersion.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/RobotSystemVersion.test.tsx similarity index 92% rename from app/src/organisms/RobotSettingsDashboard/__tests__/RobotSystemVersion.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/__tests__/RobotSystemVersion.test.tsx index c7ddba35831..ad30e2539fa 100644 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/RobotSystemVersion.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/RobotSystemVersion.test.tsx @@ -1,15 +1,15 @@ -import * as React from 'react' +import type * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { RobotSystemVersion } from '../RobotSystemVersion' import { RobotSystemVersionModal } from '../RobotSystemVersionModal' -vi.mock('../../../redux/shell') +vi.mock('/app/redux/shell') vi.mock('../RobotSystemVersionModal') const mockBack = vi.fn() diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/RobotSystemVersionModal.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/RobotSystemVersionModal.test.tsx similarity index 92% rename from app/src/organisms/RobotSettingsDashboard/__tests__/RobotSystemVersionModal.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/__tests__/RobotSystemVersionModal.test.tsx index 887b36c332b..0d7a125eb1f 100644 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/RobotSystemVersionModal.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/RobotSystemVersionModal.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { RobotSystemVersionModal } from '../RobotSystemVersionModal' import type * as Dom from 'react-router-dom' diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/TextSize.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TextSize.test.tsx similarity index 89% rename from app/src/organisms/RobotSettingsDashboard/__tests__/TextSize.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TextSize.test.tsx index bbe8ccba0d7..703323c0d7e 100644 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/TextSize.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TextSize.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { TextSize } from '../TextSize' const mockFunc = vi.fn() diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/TouchScreenSleep.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TouchScreenSleep.test.tsx similarity index 84% rename from app/src/organisms/RobotSettingsDashboard/__tests__/TouchScreenSleep.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TouchScreenSleep.test.tsx index c27c2fd112b..990c6bcf436 100644 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/TouchScreenSleep.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TouchScreenSleep.test.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' -import { updateConfigValue } from '../../../redux/config' +import { i18n } from '/app/i18n' +import { updateConfigValue } from '/app/redux/config' import { TouchScreenSleep } from '../TouchScreenSleep' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') // Note (kj:06/28/2023) this line is to avoid causing errors for scrollIntoView window.HTMLElement.prototype.scrollIntoView = vi.fn() diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/TouchscreenBrightness.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TouchscreenBrightness.test.tsx similarity index 91% rename from app/src/organisms/RobotSettingsDashboard/__tests__/TouchscreenBrightness.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TouchscreenBrightness.test.tsx index dce842b1691..76993c42300 100644 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/TouchscreenBrightness.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/TouchscreenBrightness.test.tsx @@ -1,16 +1,16 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { getOnDeviceDisplaySettings, updateConfigValue, -} from '../../../redux/config' -import { renderWithProviders } from '../../../__testing-utils__' +} from '/app/redux/config' +import { renderWithProviders } from '/app/__testing-utils__' import { TouchscreenBrightness } from '../TouchscreenBrightness' -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') const mockFunc = vi.fn() diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/UpdateChannel.test.tsx b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/UpdateChannel.test.tsx similarity index 91% rename from app/src/organisms/RobotSettingsDashboard/__tests__/UpdateChannel.test.tsx rename to app/src/organisms/ODD/RobotSettingsDashboard/__tests__/UpdateChannel.test.tsx index 7ed9db3fc0f..c25e9582a4b 100644 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/UpdateChannel.test.tsx +++ b/app/src/organisms/ODD/RobotSettingsDashboard/__tests__/UpdateChannel.test.tsx @@ -1,19 +1,19 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { getDevtoolsEnabled, getUpdateChannelOptions, updateConfigValue, -} from '../../../redux/config' -import { renderWithProviders } from '../../../__testing-utils__' +} from '/app/redux/config' +import { renderWithProviders } from '/app/__testing-utils__' import { UpdateChannel } from '../UpdateChannel' -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') const mockChannelOptions = [ { diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/index.ts b/app/src/organisms/ODD/RobotSettingsDashboard/index.ts new file mode 100644 index 00000000000..a468c86829b --- /dev/null +++ b/app/src/organisms/ODD/RobotSettingsDashboard/index.ts @@ -0,0 +1,18 @@ +export * from './DeviceReset' +export * from './LanguageSetting' +export * from './NetworkSettings/RobotSettingsJoinOtherNetwork' +export * from './NetworkSettings/RobotSettingsSelectAuthenticationType' +export * from './NetworkSettings/RobotSettingsSetWifiCred' +export * from './NetworkSettings/RobotSettingsWifi' +export * from './NetworkSettings/RobotSettingsWifiConnect' +export * from './NetworkSettings' +export * from './Privacy' +export * from './RobotName' +export * from './RobotSystemVersion' +export * from './TextSize' +export * from './TouchscreenBrightness' +export * from './TouchScreenSleep' +export * from './UpdateChannel' +export * from './RobotSettingButton' +export * from './OnOffToggle' +export type * from './types' diff --git a/app/src/organisms/ODD/RobotSettingsDashboard/types.ts b/app/src/organisms/ODD/RobotSettingsDashboard/types.ts new file mode 100644 index 00000000000..78e1f552daa --- /dev/null +++ b/app/src/organisms/ODD/RobotSettingsDashboard/types.ts @@ -0,0 +1,22 @@ +/** + * a set of screen options for the robot settings dashboard page + */ +export type SettingOption = + | 'NetworkSettings' + | 'RobotName' + | 'RobotSystemVersion' + | 'TouchscreenSleep' + | 'TouchscreenBrightness' + | 'TextSize' + | 'Privacy' + | 'DeviceReset' + | 'UpdateChannel' + | 'EthernetConnectionDetails' + | 'RobotSettingsSelectAuthenticationType' + | 'RobotSettingsJoinOtherNetwork' + | 'RobotSettingsSetWifiCred' + | 'RobotSettingsWifi' + | 'RobotSettingsWifiConnect' + | 'LanguageSetting' + +export type SetSettingOption = (option: SettingOption | null) => void diff --git a/app/src/organisms/ODD/RobotSetupHeader/index.tsx b/app/src/organisms/ODD/RobotSetupHeader/index.tsx new file mode 100644 index 00000000000..6b7a3fa1049 --- /dev/null +++ b/app/src/organisms/ODD/RobotSetupHeader/index.tsx @@ -0,0 +1,77 @@ +import type * as React from 'react' + +import { + ALIGN_CENTER, + Btn, + COLORS, + Flex, + Icon, + JUSTIFY_CENTER, + POSITION_ABSOLUTE, + POSITION_RELATIVE, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' + +import { SmallButton } from '/app/atoms/buttons' +import { InlineNotification } from '/app/atoms/InlineNotification' + +import type { InlineNotificationProps } from '/app/atoms/InlineNotification' + +interface RobotSetupHeaderProps { + header: string + buttonText?: React.ReactNode + inlineNotification?: InlineNotificationProps + onClickBack?: React.MouseEventHandler + onClickButton?: React.MouseEventHandler +} + +export function RobotSetupHeader({ + buttonText, + header, + inlineNotification, + onClickBack, + onClickButton, +}: RobotSetupHeaderProps): JSX.Element { + return ( + + + {onClickBack != null ? ( + + + + ) : null} + + {header} + + {onClickButton != null && buttonText != null ? ( + + ) : null} + {inlineNotification != null ? ( + + ) : null} + + + ) +} diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/CancelingRunModal.tsx b/app/src/organisms/ODD/RunningProtocol/CancelingRunModal.tsx similarity index 92% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/CancelingRunModal.tsx rename to app/src/organisms/ODD/RunningProtocol/CancelingRunModal.tsx index ae1bd9bbca2..1aa7c2c306e 100644 --- a/app/src/organisms/OnDeviceDisplay/RunningProtocol/CancelingRunModal.tsx +++ b/app/src/organisms/ODD/RunningProtocol/CancelingRunModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -13,7 +12,7 @@ import { LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { OddModal } from '../../../molecules/OddModal' +import { OddModal } from '/app/molecules/OddModal' export function CancelingRunModal(): JSX.Element { const { t, i18n } = useTranslation('run_details') diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/ConfirmCancelRunModal.tsx b/app/src/organisms/ODD/RunningProtocol/ConfirmCancelRunModal.tsx similarity index 84% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/ConfirmCancelRunModal.tsx rename to app/src/organisms/ODD/RunningProtocol/ConfirmCancelRunModal.tsx index 0a858bf0dfb..453e3152ad4 100644 --- a/app/src/organisms/OnDeviceDisplay/RunningProtocol/ConfirmCancelRunModal.tsx +++ b/app/src/organisms/ODD/RunningProtocol/ConfirmCancelRunModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' import { useSelector } from 'react-redux' @@ -18,15 +18,15 @@ import { useDismissCurrentRunMutation, } from '@opentrons/react-api-client' -import { SmallButton } from '../../../atoms/buttons' -import { OddModal } from '../../../molecules/OddModal' -import { useTrackProtocolRunEvent } from '../../../organisms/Devices/hooks' -import { useRunStatus } from '../../../organisms/RunTimeControl/hooks' -import { ANALYTICS_PROTOCOL_RUN_ACTION } from '../../../redux/analytics' -import { getLocalRobot } from '../../../redux/discovery' +import { SmallButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' +import { useTrackProtocolRunEvent } from '/app/redux-resources/analytics' +import { useRunStatus } from '/app/resources/runs' +import { ANALYTICS_PROTOCOL_RUN_ACTION } from '/app/redux/analytics' +import { getLocalRobot } from '/app/redux/discovery' import { CancelingRunModal } from './CancelingRunModal' -import type { OddModalHeaderBaseProps } from '../../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' interface ConfirmCancelRunModalProps { runId: string @@ -55,8 +55,8 @@ export function ConfirmCancelRunModal({ dismissCurrentRun, isLoading: isDismissing, } = useDismissCurrentRunMutation({ - onSuccess: () => { - if (isQuickTransfer && !isActiveRun) { + onSettled: () => { + if (isQuickTransfer) { deleteRun(runId) } }, @@ -66,7 +66,7 @@ export function ConfirmCancelRunModal({ const robotName = localRobot?.name ?? '' const { trackProtocolRunEvent } = useTrackProtocolRunEvent(runId, robotName) const navigate = useNavigate() - const [isCanceling, setIsCanceling] = React.useState(false) + const [isCanceling, setIsCanceling] = useState(false) const modalHeader: OddModalHeaderBaseProps = { title: t('cancel_run_modal_heading'), @@ -84,11 +84,11 @@ export function ConfirmCancelRunModal({ }) } - React.useEffect(() => { + useEffect(() => { if (runStatus === RUN_STATUS_STOPPED) { trackProtocolRunEvent({ name: ANALYTICS_PROTOCOL_RUN_ACTION.CANCEL }) - dismissCurrentRun(runId) if (!isActiveRun) { + dismissCurrentRun(runId) if (isQuickTransfer && protocolId != null) { navigate(`/quick-transfer/${protocolId}`) } else if (isQuickTransfer) { diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/CurrentRunningProtocolCommand.tsx b/app/src/organisms/ODD/RunningProtocol/CurrentRunningProtocolCommand.tsx similarity index 84% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/CurrentRunningProtocolCommand.tsx rename to app/src/organisms/ODD/RunningProtocol/CurrentRunningProtocolCommand.tsx index 0e2f10dc483..d25e356ad0d 100644 --- a/app/src/organisms/OnDeviceDisplay/RunningProtocol/CurrentRunningProtocolCommand.tsx +++ b/app/src/organisms/ODD/RunningProtocol/CurrentRunningProtocolCommand.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { css, keyframes } from 'styled-components' import { useTranslation } from 'react-i18next' @@ -20,23 +19,24 @@ import { } from '@opentrons/components' import { RUN_STATUS_RUNNING, RUN_STATUS_IDLE } from '@opentrons/api-client' -import { CommandText } from '../../../molecules/Command' -import { RunTimer } from '../../Devices/ProtocolRun/RunTimer' -import { getCommandTextData } from '../../../molecules/Command/utils/getCommandTextData' +import { CommandText } from '/app/molecules/Command' +import { RunTimer } from '/app/molecules/RunTimer' +import { getCommandTextData } from '/app/local-resources/commands' import { PlayPauseButton } from './PlayPauseButton' import { StopButton } from './StopButton' -import { ANALYTICS_PROTOCOL_RUN_ACTION } from '../../../redux/analytics' -import { useRunningStepCounts } from '../../../resources/protocols/hooks' -import { useNotifyAllCommandsQuery } from '../../../resources/runs' +import { ANALYTICS_PROTOCOL_RUN_ACTION } from '/app/redux/analytics' +import { useRunningStepCounts } from '/app/resources/protocols/hooks' +import { useNotifyAllCommandsQuery } from '/app/resources/runs' import type { CompletedProtocolAnalysis, + LabwareDefinition2, RobotType, RunTimeCommand, } from '@opentrons/shared-data' import type { RunCommandSummary, RunStatus } from '@opentrons/api-client' -import type { TrackProtocolRunEvent } from '../../Devices/hooks' -import type { RobotAnalyticsData } from '../../../redux/analytics/types' +import type { TrackProtocolRunEvent } from '/app/redux-resources/analytics' +import type { RobotAnalyticsData } from '/app/redux/analytics/types' const ODD_ANIMATION_OPTIMIZATIONS = ` backface-visibility: hidden; @@ -75,32 +75,31 @@ const RUN_TIMER_STYLE = css` color: ${COLORS.black90}; ` -const COMMAND_ROW_STYLE_ANIMATED = css` +const COMMAND_ROW_STYLE_BASE = css` font-size: 1.375rem; line-height: 1.75rem; font-weight: ${TYPOGRAPHY.fontWeightRegular}; text-align: center; - width: fit-content; + width: 100%; + max-width: 100%; margin: auto; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; + max-height: 4.6rem; // This ensures we don't show any extra text after truncating. + word-break: break-word; + white-space: normal; +` + +const COMMAND_ROW_STYLE_ANIMATED = css` + ${COMMAND_ROW_STYLE_BASE} animation: ${fadeIn} 1.5s ease-in-out; ${ODD_ANIMATION_OPTIMIZATIONS} ` const COMMAND_ROW_STYLE = css` - font-size: 1.375rem; - line-height: 1.75rem; - font-weight: ${TYPOGRAPHY.fontWeightRegular}; - text-align: center; - width: fit-content; - margin: auto; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - overflow: hidden; + ${COMMAND_ROW_STYLE_BASE} ` interface RunTimerInfo { @@ -124,6 +123,7 @@ interface CurrentRunningProtocolCommandProps { lastAnimatedCommand: string | null lastRunCommand: RunCommandSummary | null updateLastAnimatedCommand: (newCommandKey: string) => void + allRunDefs: LabwareDefinition2[] protocolName?: string currentRunCommandIndex?: number } @@ -144,10 +144,10 @@ export function CurrentRunningProtocolCommand({ lastRunCommand, lastAnimatedCommand, updateLastAnimatedCommand, + allRunDefs, }: CurrentRunningProtocolCommandProps): JSX.Element | null { const { t } = useTranslation('run_details') const { data: mostRecentCommandData } = useNotifyAllCommandsQuery(runId, { - cursor: null, pageLength: 1, }) @@ -170,13 +170,14 @@ export function CurrentRunningProtocolCommand({ } const currentRunStatus = t(`status_${runStatus}`) - const { currentStepNumber, totalStepCount } = useRunningStepCounts( - runId, - mostRecentCommandData - ) - const stepCounterCopy = `${t('step')} ${currentStepNumber ?? '?'}/${ - totalStepCount ?? '?' - }` + const { + currentStepNumber, + totalStepCount, + hasRunDiverged, + } = useRunningStepCounts(runId, mostRecentCommandData) + const stepCounterCopy = hasRunDiverged + ? `${t('step_na')}` + : `${t('step')} ${currentStepNumber ?? '?'}/${totalStepCount ?? '?'}` const onStop = (): void => { if (runStatus === RUN_STATUS_RUNNING) pauseRun() @@ -262,6 +263,7 @@ export function CurrentRunningProtocolCommand({ commandTextData={getCommandTextData(robotSideAnalysis)} robotType={robotType} isOnDevice={true} + allRunDefs={allRunDefs} /> ) : null} diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/PlayPauseButton.tsx b/app/src/organisms/ODD/RunningProtocol/PlayPauseButton.tsx similarity index 94% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/PlayPauseButton.tsx rename to app/src/organisms/ODD/RunningProtocol/PlayPauseButton.tsx index 940851126e1..44b4154cf65 100644 --- a/app/src/organisms/OnDeviceDisplay/RunningProtocol/PlayPauseButton.tsx +++ b/app/src/organisms/ODD/RunningProtocol/PlayPauseButton.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { css } from 'styled-components' import { @@ -10,7 +9,7 @@ import { } from '@opentrons/components' import { RUN_STATUS_RUNNING } from '@opentrons/api-client' -import { ODD_FOCUS_VISIBLE } from '../../../atoms/buttons/constants' +import { ODD_FOCUS_VISIBLE } from '/app/atoms/buttons/constants' import type { RunStatus } from '@opentrons/api-client' diff --git a/app/src/organisms/ODD/RunningProtocol/RunFailedModal.tsx b/app/src/organisms/ODD/RunningProtocol/RunFailedModal.tsx new file mode 100644 index 00000000000..29cef598fd0 --- /dev/null +++ b/app/src/organisms/ODD/RunningProtocol/RunFailedModal.tsx @@ -0,0 +1,206 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' +import { css } from 'styled-components' + +import { + ALIGN_FLEX_START, + BORDERS, + COLORS, + DIRECTION_COLUMN, + Flex, + OVERFLOW_AUTO, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' +import { useStopRunMutation } from '@opentrons/react-api-client' + +import { SmallButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' +import { RUN_STATUS_SUCCEEDED } from '@opentrons/api-client' +import { getHighestPriorityError } from '/app/transformations/runs' + +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' +import type { + RunCommandErrors, + RunError, + RunStatus, +} from '@opentrons/api-client' +import type { RunCommandError } from '@opentrons/shared-data' + +interface RunFailedModalProps { + runId: string + setShowRunFailedModal: (showRunFailedModal: boolean) => void + errors?: RunError[] + commandErrorList?: RunCommandErrors + runStatus: RunStatus | null +} + +export function RunFailedModal({ + runId, + setShowRunFailedModal, + errors, + commandErrorList, + runStatus, +}: RunFailedModalProps): JSX.Element | null { + const { t, i18n } = useTranslation(['run_details', 'shared', 'branded']) + const navigate = useNavigate() + const { stopRun } = useStopRunMutation() + const [isCanceling, setIsCanceling] = useState(false) + + if ( + (errors == null || errors.length === 0) && + (commandErrorList == null || commandErrorList.data.length === 0) + ) + return null + const modalHeader: OddModalHeaderBaseProps = { + title: + commandErrorList == null || commandErrorList?.data.length === 0 + ? t('run_failed_modal_title') + : runStatus === RUN_STATUS_SUCCEEDED + ? t('warning_details') + : t('error_details'), + } + + const highestPriorityError = getHighestPriorityError(errors ?? []) + + const handleClose = (): void => { + setIsCanceling(true) + setShowRunFailedModal(false) + stopRun(runId, { + onSuccess: () => { + // ToDo do we need to track this event? + // If need, runCancel or runFailure something + // trackProtocolRunEvent({ name: 'runCancel' }) + navigate('/dashboard') + }, + onError: () => { + setIsCanceling(false) + }, + }) + } + + interface ErrorContentProps { + errors: RunCommandError[] + isSingleError: boolean + } + const ErrorContent = ({ + errors, + isSingleError, + }: ErrorContentProps): JSX.Element => { + return ( + <> + + {isSingleError + ? t('error_info', { + errorType: errors[0].errorType, + errorCode: errors[0].errorCode, + }) + : runStatus === RUN_STATUS_SUCCEEDED + ? t(errors.length > 1 ? 'no_of_warnings' : 'no_of_warning', { + count: errors.length, + }) + : t(errors.length > 1 ? 'no_of_errors' : 'no_of_error', { + count: errors.length, + })} + + + + {' '} + {errors.map((error, index) => ( + + {' '} + {isSingleError + ? error.detail + : `${error.errorCode}: ${error.detail}`} + + ))} + + + + ) + } + + return ( + { + setShowRunFailedModal(false) + }} + > + + + 0 + ? commandErrorList?.data + : [] + } + isSingleError={!!highestPriorityError} + /> + + + {t('branded:contact_information')} + + + + + ) +} + +const SCROLL_BAR_STYLE = css` + overflow-y: ${OVERFLOW_AUTO}; + + &::-webkit-scrollbar { + width: 0.75rem; + background-color: ${COLORS.grey35}; + } + + &::-webkit-scrollbar-track { + margin-top: ${SPACING.spacing16}; + margin-bottom: ${SPACING.spacing16}; + } + + &::-webkit-scrollbar-thumb { + background: ${COLORS.grey50}; + border-radius: 11px; + } +` diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/RunningProtocolCommandList.tsx b/app/src/organisms/ODD/RunningProtocol/RunningProtocolCommandList.tsx similarity index 89% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/RunningProtocolCommandList.tsx rename to app/src/organisms/ODD/RunningProtocol/RunningProtocolCommandList.tsx index 9bfe2ca73fc..e49b725ab35 100644 --- a/app/src/organisms/OnDeviceDisplay/RunningProtocol/RunningProtocolCommandList.tsx +++ b/app/src/organisms/ODD/RunningProtocol/RunningProtocolCommandList.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useRef, useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' import { ViewportList } from 'react-viewport-list' @@ -22,20 +22,21 @@ import { } from '@opentrons/components' import { RUN_STATUS_RUNNING, RUN_STATUS_IDLE } from '@opentrons/api-client' -import { CommandText, CommandIcon } from '../../../molecules/Command' -import { getCommandTextData } from '../../../molecules/Command/utils/getCommandTextData' +import { CommandText, CommandIcon } from '/app/molecules/Command' +import { getCommandTextData } from '/app/local-resources/commands' import { PlayPauseButton } from './PlayPauseButton' import { StopButton } from './StopButton' -import { ANALYTICS_PROTOCOL_RUN_ACTION } from '../../../redux/analytics' +import { ANALYTICS_PROTOCOL_RUN_ACTION } from '/app/redux/analytics' import type { ViewportListRef } from 'react-viewport-list' import type { CompletedProtocolAnalysis, + LabwareDefinition2, RobotType, } from '@opentrons/shared-data' import type { RunStatus } from '@opentrons/api-client' -import type { TrackProtocolRunEvent } from '../../Devices/hooks' -import type { RobotAnalyticsData } from '../../../redux/analytics/types' +import type { TrackProtocolRunEvent } from '/app/redux-resources/analytics' +import type { RobotAnalyticsData } from '/app/redux/analytics/types' const TITLE_TEXT_STYLE = css` color: ${COLORS.grey60}; @@ -60,17 +61,6 @@ const COMMAND_ROW_STYLE = css` overflow: hidden; ` -// Note (kj:05/15/2023) -// This blur part will be fixed before the launch -// const BOTTOM_ROW_STYLE = css` -// position: ${POSITION_ABSOLUTE}; -// bottom: 0; -// width: 100%; -// height: 5rem; -// z-index: 6; -// backdrop-filter: blur(1.5px); -// ` - interface VisibleIndexRange { lowestVisibleIndex: number highestVisibleIndex: number @@ -87,6 +77,7 @@ interface RunningProtocolCommandListProps { robotAnalyticsData: RobotAnalyticsData | null protocolName?: string currentRunCommandIndex?: number + allRunDefs: LabwareDefinition2[] } export function RunningProtocolCommandList({ @@ -100,16 +91,17 @@ export function RunningProtocolCommandList({ robotAnalyticsData, protocolName, currentRunCommandIndex, + allRunDefs, }: RunningProtocolCommandListProps): JSX.Element { const { t } = useTranslation('run_details') - const viewPortRef = React.useRef(null) - const ref = React.useRef(null) + const viewPortRef = useRef(null) + const ref = useRef(null) const currentRunStatus = t(`status_${runStatus}`) const onStop = (): void => { if (runStatus === RUN_STATUS_RUNNING) pauseRun() setShowConfirmCancelRunModal(true) } - const [visibleRange, setVisibleRange] = React.useState({ + const [visibleRange, setVisibleRange] = useState({ lowestVisibleIndex: 0, highestVisibleIndex: 0, }) @@ -133,7 +125,7 @@ export function RunningProtocolCommandList({ } } - React.useEffect(() => { + useEffect(() => { // Note (kk:09/25/2023) Need -1 because the element of highestVisibleIndex cannot really readable // due to limited space const isCurrentCommandVisible = @@ -249,6 +241,7 @@ export function RunningProtocolCommandList({ robotType={robotType} css={COMMAND_ROW_STYLE} isOnDevice={true} + allRunDefs={allRunDefs} /> diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/RunningProtocolSkeleton.tsx b/app/src/organisms/ODD/RunningProtocol/RunningProtocolSkeleton.tsx similarity index 95% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/RunningProtocolSkeleton.tsx rename to app/src/organisms/ODD/RunningProtocol/RunningProtocolSkeleton.tsx index 9947735583a..f2427c6bbf4 100644 --- a/app/src/organisms/OnDeviceDisplay/RunningProtocol/RunningProtocolSkeleton.tsx +++ b/app/src/organisms/ODD/RunningProtocol/RunningProtocolSkeleton.tsx @@ -1,5 +1,3 @@ -import * as React from 'react' - import { DIRECTION_COLUMN, DIRECTION_ROW, @@ -12,9 +10,10 @@ import { import { PlayPauseButton } from './PlayPauseButton' import { StopButton } from './StopButton' -import { Skeleton } from '../../../atoms/Skeleton' +import { Skeleton } from '/app/atoms/Skeleton' -import type { ScreenOption } from '../../../pages/RunningProtocol' +// eslint-disable-next-line opentrons/no-imports-up-the-tree-of-life +import type { ScreenOption } from '/app/pages/ODD/RunningProtocol' const CURRENT_RUNNING_PROTOCOL_COMMAND_SIZE = '99rem' // CurrentRunningProtocolCommand screen const RUNNING_PROTOCOL_COMMAND_LIST_SIZE = '389rem' // RunningProtocolCommandList screen diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/StopButton.tsx b/app/src/organisms/ODD/RunningProtocol/StopButton.tsx similarity index 93% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/StopButton.tsx rename to app/src/organisms/ODD/RunningProtocol/StopButton.tsx index 9eadc863f35..4ee9a716c81 100644 --- a/app/src/organisms/OnDeviceDisplay/RunningProtocol/StopButton.tsx +++ b/app/src/organisms/ODD/RunningProtocol/StopButton.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { css } from 'styled-components' import { @@ -9,7 +8,7 @@ import { JUSTIFY_CENTER, } from '@opentrons/components' -import { ODD_FOCUS_VISIBLE } from '../../../atoms/buttons/constants' +import { ODD_FOCUS_VISIBLE } from '/app/atoms/buttons/constants' const STOP_BUTTON_STYLE = css` -webkit-tap-highlight-color: transparent; diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/CancelingRunModal.test.tsx b/app/src/organisms/ODD/RunningProtocol/__tests__/CancelingRunModal.test.tsx similarity index 76% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/CancelingRunModal.test.tsx rename to app/src/organisms/ODD/RunningProtocol/__tests__/CancelingRunModal.test.tsx index 9ae311aca0f..5bc8efafe42 100644 --- a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/CancelingRunModal.test.tsx +++ b/app/src/organisms/ODD/RunningProtocol/__tests__/CancelingRunModal.test.tsx @@ -1,9 +1,8 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { describe, it } from 'vitest' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { CancelingRunModal } from '../CancelingRunModal' const render = () => { diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/ConfirmCancelRunModal.test.tsx b/app/src/organisms/ODD/RunningProtocol/__tests__/ConfirmCancelRunModal.test.tsx similarity index 82% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/ConfirmCancelRunModal.test.tsx rename to app/src/organisms/ODD/RunningProtocol/__tests__/ConfirmCancelRunModal.test.tsx index 358436283aa..69f610d3ef8 100644 --- a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/ConfirmCancelRunModal.test.tsx +++ b/app/src/organisms/ODD/RunningProtocol/__tests__/ConfirmCancelRunModal.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { when } from 'vitest-when' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' @@ -11,25 +11,24 @@ import { useDismissCurrentRunMutation, } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { useTrackProtocolRunEvent } from '../../../../organisms/Devices/hooks' -import { useRunStatus } from '../../../../organisms/RunTimeControl/hooks' -import { useTrackEvent } from '../../../../redux/analytics' -import { getLocalRobot } from '../../../../redux/discovery' -import { mockConnectedRobot } from '../../../../redux/discovery/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { useTrackProtocolRunEvent } from '/app/redux-resources/analytics' +import { useRunStatus } from '/app/resources/runs' +import { useTrackEvent } from '/app/redux/analytics' +import { getLocalRobot } from '/app/redux/discovery' +import { mockConnectedRobot } from '/app/redux/discovery/__fixtures__' import { ConfirmCancelRunModal } from '../ConfirmCancelRunModal' import { CancelingRunModal } from '../CancelingRunModal' import type { NavigateFunction } from 'react-router-dom' vi.mock('@opentrons/react-api-client') -vi.mock('../../../../organisms/Devices/hooks') -vi.mock('../../../../organisms/RunTimeControl/hooks') -vi.mock('../../../../redux/analytics') -vi.mock('../../../ProtocolUpload/hooks') +vi.mock('/app/resources/runs') +vi.mock('/app/redux-resources/analytics') +vi.mock('/app/redux/analytics') vi.mock('../CancelingRunModal') -vi.mock('../../../../redux/discovery') +vi.mock('/app/redux/discovery') const mockNavigate = vi.fn() const mockStopRun = vi.fn() const mockDeleteRun = vi.fn() @@ -138,15 +137,7 @@ describe('ConfirmCancelRunModal', () => { expect(mockStopRun).toHaveBeenCalled() }) - it('when run is stopped, the run is dismissed and the modal closes', () => { - when(useRunStatus).calledWith(RUN_ID).thenReturn(RUN_STATUS_STOPPED) - render(props) - - expect(mockDismissCurrentRun).toHaveBeenCalled() - expect(mockTrackProtocolRunEvent).toHaveBeenCalled() - }) - - it('when run is stopped, the run is dismissed and the modal closes - in prepare to run', () => { + it('when run is stopped, the run is dismissed and the modal closes if the run is not yet active', () => { props = { ...props, isActiveRun: false, @@ -156,9 +147,9 @@ describe('ConfirmCancelRunModal', () => { expect(mockDismissCurrentRun).toHaveBeenCalled() expect(mockTrackProtocolRunEvent).toHaveBeenCalled() - expect(mockNavigate).toHaveBeenCalledWith('/protocols') }) - it('when quick transfer run is stopped, the run is dismissed and you return to quick transfer', () => { + + it('when quick transfer run is stopped, the run is dismissed and the modal closes if the run is not yet active', () => { props = { ...props, isActiveRun: false, @@ -168,6 +159,15 @@ describe('ConfirmCancelRunModal', () => { render(props) expect(mockDismissCurrentRun).toHaveBeenCalled() + expect(mockTrackProtocolRunEvent).toHaveBeenCalled() expect(mockNavigate).toHaveBeenCalledWith('/quick-transfer') }) + + it('when run is stopped, the run is not dismissed if the run is active', () => { + when(useRunStatus).calledWith(RUN_ID).thenReturn(RUN_STATUS_STOPPED) + render(props) + + expect(mockDismissCurrentRun).not.toHaveBeenCalled() + expect(mockTrackProtocolRunEvent).toHaveBeenCalled() + }) }) diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx b/app/src/organisms/ODD/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx similarity index 88% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx rename to app/src/organisms/ODD/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx index f463222c5fd..581df7c013c 100644 --- a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx +++ b/app/src/organisms/ODD/RunningProtocol/__tests__/CurrentRunningProtocolCommand.test.tsx @@ -1,19 +1,19 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { RUN_STATUS_RUNNING, RUN_STATUS_IDLE } from '@opentrons/api-client' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { mockRobotSideAnalysis } from '../../../../molecules/Command/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockRobotSideAnalysis } from '/app/molecules/Command/__fixtures__' import { CurrentRunningProtocolCommand } from '../CurrentRunningProtocolCommand' -import { useRunningStepCounts } from '../../../../resources/protocols/hooks' -import { useNotifyAllCommandsQuery } from '../../../../resources/runs' +import { useRunningStepCounts } from '/app/resources/protocols/hooks' +import { useNotifyAllCommandsQuery } from '/app/resources/runs' import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' -vi.mock('../../../../resources/runs') -vi.mock('../../../../resources/protocols/hooks') +vi.mock('/app/resources/runs') +vi.mock('/app/resources/protocols/hooks') const mockPlayRun = vi.fn() const mockPauseRun = vi.fn() @@ -55,6 +55,7 @@ describe('CurrentRunningProtocolCommand', () => { updateLastAnimatedCommand: mockUpdateLastAnimatedCommand, robotType: FLEX_ROBOT_TYPE, runId: 'MOCK_RUN_ID', + allRunDefs: [], } vi.mocked(useNotifyAllCommandsQuery).mockReturnValue({} as any) @@ -124,7 +125,7 @@ describe('CurrentRunningProtocolCommand', () => { }) render(props) - screen.getByText('Step ?/?') + screen.getByText('Step: N/A') }) // ToDo (kj:04/10/2023) once we fix the track event stuff, we can implement tests diff --git a/app/src/organisms/ODD/RunningProtocol/__tests__/RunFailedModal.test.tsx b/app/src/organisms/ODD/RunningProtocol/__tests__/RunFailedModal.test.tsx new file mode 100644 index 00000000000..8dcfd2e5b88 --- /dev/null +++ b/app/src/organisms/ODD/RunningProtocol/__tests__/RunFailedModal.test.tsx @@ -0,0 +1,130 @@ +import type * as React from 'react' +import { MemoryRouter } from 'react-router-dom' +import { fireEvent, screen } from '@testing-library/react' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { useStopRunMutation } from '@opentrons/react-api-client' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { RunFailedModal } from '../RunFailedModal' + +import type { NavigateFunction } from 'react-router-dom' +import { RUN_STATUS_FAILED } from '@opentrons/api-client' + +vi.mock('@opentrons/react-api-client') + +const RUN_ID = 'mock_runID' +const mockFn = vi.fn() +const mockNavigate = vi.fn() +const mockErrors = [ + { + id: 'd0245210-dfb9-4f1c-8ad0-3416b603a7ba', + errorType: 'generalError', + isDefined: false as const, + createdAt: '2023-04-09T21:41:51.333171+00:00', + detail: 'Error with code 4000 (lowest priority)', + errorInfo: {}, + errorCode: '4000' as const, + wrappedErrors: [ + { + id: 'd0245210-dfb9-4f1c-8ad0-3416b603a7ba', + errorType: 'roboticsInteractionError', + isDefined: false as const, + createdAt: '2023-04-09T21:41:51.333171+00:00', + detail: 'Error with code 3000 (second lowest priortiy)', + errorInfo: {}, + errorCode: '3000' as const, + wrappedErrors: [], + }, + { + id: 'd0245210-dfb9-4f1c-8ad0-3416b603a7ba', + errorType: 'roboticsControlError', + isDefined: false as const, + createdAt: '2023-04-09T21:41:51.333171+00:00', + detail: 'Error with code 2000 (second highest priority)', + errorInfo: {}, + errorCode: '2000' as const, + wrappedErrors: [ + { + id: 'd0245210-dfb9-4f1c-8ad0-3416b603a7ba', + errorType: 'hardwareCommunicationError', + isDefined: false as const, + createdAt: '2023-04-09T21:41:51.333171+00:00', + detail: 'Error with code 1000 (highest priority)', + errorInfo: {}, + errorCode: '1000' as const, + wrappedErrors: [], + }, + ], + }, + ], + }, + { + id: 'd0245210-dfb9-4f1c-8ad0-3416b603a7ba', + errorType: 'roboticsInteractionError', + isDefined: false as const, + createdAt: '2023-04-09T21:41:51.333171+00:00', + detail: 'Error with code 2001 (second highest priortiy)', + errorInfo: {}, + errorCode: '2001' as const, + wrappedErrors: [], + }, +] + +const mockStopRun = vi.fn((_runId, opts) => opts.onSuccess()) + +vi.mock('react-router-dom', async importOriginal => { + const actual = await importOriginal() + return { + ...actual, + useNavigate: () => mockNavigate, + } +}) + +const render = (props: React.ComponentProps) => { + return renderWithProviders( + + + , + { + i18nInstance: i18n, + } + ) +} + +describe('RunFailedModal', () => { + let props: React.ComponentProps + + beforeEach(() => { + props = { + runId: RUN_ID, + setShowRunFailedModal: mockFn, + errors: mockErrors, + runStatus: RUN_STATUS_FAILED, + } + + vi.mocked(useStopRunMutation).mockReturnValue({ + stopRun: mockStopRun, + } as any) + }) + + it('should render the highest priority error', () => { + render(props) + screen.getByText('Run failed') + screen.getByText('Error 1000: hardwareCommunicationError') + screen.getByText('Error with code 1000 (highest priority)') + screen.getByText( + 'Download the robot logs from the Opentrons App and send it to support@opentrons.com for assistance.' + ) + screen.getByText('Close') + }) + + it('when tapping close, call mock functions', () => { + render(props) + const button = screen.getByText('Close') + fireEvent.click(button) + expect(mockStopRun).toHaveBeenCalled() + expect(mockNavigate).toHaveBeenCalledWith('/dashboard') + }) +}) diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/RunningProtocolCommandList.test.tsx b/app/src/organisms/ODD/RunningProtocol/__tests__/RunningProtocolCommandList.test.tsx similarity index 90% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/RunningProtocolCommandList.test.tsx rename to app/src/organisms/ODD/RunningProtocol/__tests__/RunningProtocolCommandList.test.tsx index eb21f242817..199ae940c3b 100644 --- a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/RunningProtocolCommandList.test.tsx +++ b/app/src/organisms/ODD/RunningProtocol/__tests__/RunningProtocolCommandList.test.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' import { RUN_STATUS_RUNNING, RUN_STATUS_IDLE } from '@opentrons/api-client' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { mockRobotSideAnalysis } from '../../../../molecules/Command/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockRobotSideAnalysis } from '/app/molecules/Command/__fixtures__' import { RunningProtocolCommandList } from '../RunningProtocolCommandList' const mockPlayRun = vi.fn() @@ -36,6 +36,7 @@ describe('RunningProtocolCommandList', () => { protocolName: 'mockRunningProtocolName', currentRunCommandIndex: 0, robotType: FLEX_ROBOT_TYPE, + allRunDefs: [], } }) it('should render text and buttons', () => { diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/RunningProtocolSkeleton.test.tsx b/app/src/organisms/ODD/RunningProtocol/__tests__/RunningProtocolSkeleton.test.tsx similarity index 94% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/RunningProtocolSkeleton.test.tsx rename to app/src/organisms/ODD/RunningProtocol/__tests__/RunningProtocolSkeleton.test.tsx index fb842e88e1d..656d4d250d1 100644 --- a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/RunningProtocolSkeleton.test.tsx +++ b/app/src/organisms/ODD/RunningProtocol/__tests__/RunningProtocolSkeleton.test.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { beforeEach, describe, expect, it } from 'vitest' -import { renderWithProviders } from '../../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { RunningProtocolSkeleton } from '../RunningProtocolSkeleton' const render = ( diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/index.ts b/app/src/organisms/ODD/RunningProtocol/index.ts similarity index 100% rename from app/src/organisms/OnDeviceDisplay/RunningProtocol/index.ts rename to app/src/organisms/ODD/RunningProtocol/index.ts diff --git a/app/src/organisms/OnDeviceDisplay/ProtocolSetup/index.ts b/app/src/organisms/OnDeviceDisplay/ProtocolSetup/index.ts deleted file mode 100644 index 763d2d63602..00000000000 --- a/app/src/organisms/OnDeviceDisplay/ProtocolSetup/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ProtocolSetupSkeleton' diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/RunFailedModal.tsx b/app/src/organisms/OnDeviceDisplay/RunningProtocol/RunFailedModal.tsx deleted file mode 100644 index 6f9229089d6..00000000000 --- a/app/src/organisms/OnDeviceDisplay/RunningProtocol/RunFailedModal.tsx +++ /dev/null @@ -1,269 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useNavigate } from 'react-router-dom' -import { css } from 'styled-components' - -import { - ALIGN_FLEX_START, - BORDERS, - COLORS, - DIRECTION_COLUMN, - Flex, - OVERFLOW_AUTO, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' -import { useStopRunMutation } from '@opentrons/react-api-client' - -import { SmallButton } from '../../../atoms/buttons' -import { OddModal } from '../../../molecules/OddModal' -import { RUN_STATUS_SUCCEEDED } from '@opentrons/api-client' - -import type { OddModalHeaderBaseProps } from '../../../molecules/OddModal/types' -import type { - RunCommandErrors, - RunError, - RunStatus, -} from '@opentrons/api-client' -import type { RunCommandError } from '@opentrons/shared-data' - -interface RunFailedModalProps { - runId: string - setShowRunFailedModal: (showRunFailedModal: boolean) => void - errors?: RunError[] - commandErrorList?: RunCommandErrors - runStatus: RunStatus | null -} - -export function RunFailedModal({ - runId, - setShowRunFailedModal, - errors, - commandErrorList, - runStatus, -}: RunFailedModalProps): JSX.Element | null { - const { t, i18n } = useTranslation(['run_details', 'shared', 'branded']) - const navigate = useNavigate() - const { stopRun } = useStopRunMutation() - const [isCanceling, setIsCanceling] = React.useState(false) - - if ( - (errors == null || errors.length === 0) && - (commandErrorList == null || commandErrorList.data.length === 0) - ) - return null - const modalHeader: OddModalHeaderBaseProps = { - title: - commandErrorList == null || commandErrorList?.data.length === 0 - ? t('run_failed_modal_title') - : runStatus === RUN_STATUS_SUCCEEDED - ? t('warning_details') - : t('error_details'), - } - - const highestPriorityError = getHighestPriorityError(errors ?? []) - - const handleClose = (): void => { - setIsCanceling(true) - setShowRunFailedModal(false) - stopRun(runId, { - onSuccess: () => { - // ToDo do we need to track this event? - // If need, runCancel or runFailure something - // trackProtocolRunEvent({ name: 'runCancel' }) - navigate('/dashboard') - }, - onError: () => { - setIsCanceling(false) - }, - }) - } - - interface ErrorContentProps { - errors: RunCommandError[] - isSingleError: boolean - } - const ErrorContent = ({ - errors, - isSingleError, - }: ErrorContentProps): JSX.Element => { - return ( - <> - - {isSingleError - ? t('error_info', { - errorType: errors[0].errorType, - errorCode: errors[0].errorCode, - }) - : runStatus === RUN_STATUS_SUCCEEDED - ? t(errors.length > 1 ? 'no_of_warnings' : 'no_of_warning', { - count: errors.length, - }) - : t(errors.length > 1 ? 'no_of_errors' : 'no_of_error', { - count: errors.length, - })} - - - - {' '} - {errors.map((error, index) => ( - - {' '} - {isSingleError - ? error.detail - : `${error.errorCode}: ${error.detail}`} - - ))} - - - - ) - } - - return ( - { - setShowRunFailedModal(false) - }} - > - - - 0 - ? commandErrorList?.data - : [] - } - isSingleError={!!highestPriorityError} - /> - - - {t('branded:contact_information')} - - - - - ) -} - -const SCROLL_BAR_STYLE = css` - overflow-y: ${OVERFLOW_AUTO}; - - &::-webkit-scrollbar { - width: 0.75rem; - background-color: ${COLORS.grey35}; - } - - &::-webkit-scrollbar-track { - margin-top: ${SPACING.spacing16}; - margin-bottom: ${SPACING.spacing16}; - } - - &::-webkit-scrollbar-thumb { - background: ${COLORS.grey50}; - border-radius: 11px; - } -` - -const _getHighestPriorityError = (error: RunError): RunError => { - if ( - error == null || - error.wrappedErrors == null || - error.wrappedErrors.length === 0 - ) { - return error - } - - let highestPriorityError = error - - error.wrappedErrors.forEach(wrappedError => { - const e = _getHighestPriorityError(wrappedError) - const isHigherPriority = _getIsHigherPriority( - e.errorCode, - highestPriorityError.errorCode - ) - if (isHigherPriority) { - highestPriorityError = e - } - }) - return highestPriorityError -} - -/** - * returns true if the first error code is higher priority than the second, false otherwise - */ -const _getIsHigherPriority = ( - errorCode1: string, - errorCode2: string -): boolean => { - const errorNumber1 = Number(errorCode1) - const errorNumber2 = Number(errorCode2) - - const isSameCategory = - Math.floor(errorNumber1 / 1000) === Math.floor(errorNumber2 / 1000) - const isCode1GenericError = errorNumber1 % 1000 === 0 - - let isHigherPriority = null - - if ( - (isSameCategory && !isCode1GenericError) || - (!isSameCategory && errorNumber1 < errorNumber2) - ) { - isHigherPriority = true - } else { - isHigherPriority = false - } - - return isHigherPriority -} - -export const getHighestPriorityError = (errors: RunError[]): RunError => { - const highestFirstWrappedError = _getHighestPriorityError(errors[0]) - return [highestFirstWrappedError, ...errors.slice(1)].reduce((acc, val) => { - const e = _getHighestPriorityError(val) - const isHigherPriority = _getIsHigherPriority(e.errorCode, acc.errorCode) - if (isHigherPriority) { - return e - } - return acc - }) -} diff --git a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/RunFailedModal.test.tsx b/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/RunFailedModal.test.tsx deleted file mode 100644 index 570a012bda1..00000000000 --- a/app/src/organisms/OnDeviceDisplay/RunningProtocol/__tests__/RunFailedModal.test.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import * as React from 'react' -import { MemoryRouter } from 'react-router-dom' -import { fireEvent, screen } from '@testing-library/react' -import { beforeEach, describe, expect, it, vi } from 'vitest' - -import { useStopRunMutation } from '@opentrons/react-api-client' - -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { RunFailedModal } from '../RunFailedModal' - -import type { NavigateFunction } from 'react-router-dom' -import { RUN_STATUS_FAILED } from '@opentrons/api-client' - -vi.mock('@opentrons/react-api-client') - -const RUN_ID = 'mock_runID' -const mockFn = vi.fn() -const mockNavigate = vi.fn() -const mockErrors = [ - { - id: 'd0245210-dfb9-4f1c-8ad0-3416b603a7ba', - errorType: 'generalError', - isDefined: false as const, - createdAt: '2023-04-09T21:41:51.333171+00:00', - detail: 'Error with code 4000 (lowest priority)', - errorInfo: {}, - errorCode: '4000' as const, - wrappedErrors: [ - { - id: 'd0245210-dfb9-4f1c-8ad0-3416b603a7ba', - errorType: 'roboticsInteractionError', - isDefined: false as const, - createdAt: '2023-04-09T21:41:51.333171+00:00', - detail: 'Error with code 3000 (second lowest priortiy)', - errorInfo: {}, - errorCode: '3000' as const, - wrappedErrors: [], - }, - { - id: 'd0245210-dfb9-4f1c-8ad0-3416b603a7ba', - errorType: 'roboticsControlError', - isDefined: false as const, - createdAt: '2023-04-09T21:41:51.333171+00:00', - detail: 'Error with code 2000 (second highest priority)', - errorInfo: {}, - errorCode: '2000' as const, - wrappedErrors: [ - { - id: 'd0245210-dfb9-4f1c-8ad0-3416b603a7ba', - errorType: 'hardwareCommunicationError', - isDefined: false as const, - createdAt: '2023-04-09T21:41:51.333171+00:00', - detail: 'Error with code 1000 (highest priority)', - errorInfo: {}, - errorCode: '1000' as const, - wrappedErrors: [], - }, - ], - }, - ], - }, - { - id: 'd0245210-dfb9-4f1c-8ad0-3416b603a7ba', - errorType: 'roboticsInteractionError', - isDefined: false as const, - createdAt: '2023-04-09T21:41:51.333171+00:00', - detail: 'Error with code 2001 (second highest priortiy)', - errorInfo: {}, - errorCode: '2001' as const, - wrappedErrors: [], - }, -] - -const mockStopRun = vi.fn((_runId, opts) => opts.onSuccess()) - -vi.mock('react-router-dom', async importOriginal => { - const actual = await importOriginal() - return { - ...actual, - useNavigate: () => mockNavigate, - } -}) - -const render = (props: React.ComponentProps) => { - return renderWithProviders( - - - , - { - i18nInstance: i18n, - } - ) -} - -describe('RunFailedModal', () => { - let props: React.ComponentProps - - beforeEach(() => { - props = { - runId: RUN_ID, - setShowRunFailedModal: mockFn, - errors: mockErrors, - runStatus: RUN_STATUS_FAILED, - } - - vi.mocked(useStopRunMutation).mockReturnValue({ - stopRun: mockStopRun, - } as any) - }) - - it('should render the highest priority error', () => { - render(props) - screen.getByText('Run failed') - screen.getByText('Error 1000: hardwareCommunicationError') - screen.getByText('Error with code 1000 (highest priority)') - screen.getByText( - 'Download the robot logs from the Opentrons App and send it to support@opentrons.com for assistance.' - ) - screen.getByText('Close') - }) - - it('when tapping close, call mock functions', () => { - render(props) - const button = screen.getByText('Close') - fireEvent.click(button) - expect(mockStopRun).toHaveBeenCalled() - expect(mockNavigate).toHaveBeenCalledWith('/dashboard') - }) -}) diff --git a/app/src/organisms/OpenDoorAlertModal/index.tsx b/app/src/organisms/OpenDoorAlertModal/index.tsx deleted file mode 100644 index fad1ee54143..00000000000 --- a/app/src/organisms/OpenDoorAlertModal/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' -import { - ALIGN_CENTER, - BORDERS, - COLORS, - DIRECTION_COLUMN, - Flex, - Icon, - JUSTIFY_CENTER, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' -import { getTopPortalEl } from '../../App/portal' -import { OddModal } from '../../molecules/OddModal' - -export function OpenDoorAlertModal(): JSX.Element { - const { t } = useTranslation('run_details') - return createPortal( - - - - - - {t('door_is_open')} - - - {t('close_door_to_resume')} - - - - , - getTopPortalEl() - ) -} diff --git a/app/src/organisms/PipetteWizardFlows/AttachProbe.tsx b/app/src/organisms/PipetteWizardFlows/AttachProbe.tsx index 86652e4f558..66f40287463 100644 --- a/app/src/organisms/PipetteWizardFlows/AttachProbe.tsx +++ b/app/src/organisms/PipetteWizardFlows/AttachProbe.tsx @@ -1,7 +1,8 @@ -import * as React from 'react' +import { useState } from 'react' import { css } from 'styled-components' import { Trans, useTranslation } from 'react-i18next' import { + Banner, COLORS, Flex, RESPONSIVENESS, @@ -10,19 +11,18 @@ import { TYPOGRAPHY, } from '@opentrons/components' import { LEFT, WASTE_CHUTE_CUTOUT } from '@opentrons/shared-data' -import { Banner } from '../../atoms/Banner' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' import { SimpleWizardBody, SimpleWizardInProgressBody, -} from '../../molecules/SimpleWizardBody' -import pipetteProbe1 from '../../assets/videos/pipette-wizard-flows/Pipette_Probing_1.webm' -import pipetteProbe8 from '../../assets/videos/pipette-wizard-flows/Pipette_Probing_8.webm' -import probing96 from '../../assets/videos/pipette-wizard-flows/Pipette_Probing_96.webm' +} from '/app/molecules/SimpleWizardBody' +import pipetteProbe1 from '/app/assets/videos/pipette-wizard-flows/Pipette_Probing_1.webm' +import pipetteProbe8 from '/app/assets/videos/pipette-wizard-flows/Pipette_Probing_8.webm' +import probing96 from '/app/assets/videos/pipette-wizard-flows/Pipette_Probing_96.webm' import { BODY_STYLE, SECTIONS, FLOWS } from './constants' import { getPipetteAnimations } from './utils' import { ProbeNotAttached } from './ProbeNotAttached' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' import type { MotorAxes, CreateCommand } from '@opentrons/shared-data' import type { PipetteWizardStepProps } from './types' @@ -58,9 +58,7 @@ export const AttachProbe = (props: AttachProbeProps): JSX.Element | null => { } = props const { t, i18n } = useTranslation('pipette_wizard_flows') const pipetteWizardStep = { mount, flowType, section: SECTIONS.ATTACH_PROBE } - const [showUnableToDetect, setShowUnableToDetect] = React.useState( - false - ) + const [showUnableToDetect, setShowUnableToDetect] = useState(false) const pipetteId = attachedPipettes[mount]?.serialNumber const displayName = attachedPipettes[mount]?.displayName diff --git a/app/src/organisms/PipetteWizardFlows/BeforeBeginning.tsx b/app/src/organisms/PipetteWizardFlows/BeforeBeginning.tsx index 2e9fbd28e3f..ffcb72dae18 100644 --- a/app/src/organisms/PipetteWizardFlows/BeforeBeginning.tsx +++ b/app/src/organisms/PipetteWizardFlows/BeforeBeginning.tsx @@ -1,7 +1,8 @@ -import * as React from 'react' +import { useEffect } from 'react' import { Trans, useTranslation } from 'react-i18next' import { COLORS, + Banner, DIRECTION_COLUMN, Flex, SPACING, @@ -14,14 +15,13 @@ import { WEIGHT_OF_96_CHANNEL, WASTE_CHUTE_CUTOUT, } from '@opentrons/shared-data' -import { Banner } from '../../atoms/Banner' import { SimpleWizardBody, SimpleWizardInProgressBody, -} from '../../molecules/SimpleWizardBody' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' -import { WizardRequiredEquipmentList } from '../../molecules/WizardRequiredEquipmentList' -import { usePipetteNameSpecs } from '../../resources/instruments/hooks' +} from '/app/molecules/SimpleWizardBody' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' +import { WizardRequiredEquipmentList } from '/app/molecules/WizardRequiredEquipmentList' +import { usePipetteNameSpecs } from '/app/local-resources/instruments' import { CALIBRATION_PROBE, FLOWS, @@ -32,7 +32,7 @@ import { BODY_STYLE, } from './constants' import { getIsGantryEmpty } from './utils' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' import type { UseMutateFunction } from 'react-query' import type { AxiosError } from 'axios' @@ -79,7 +79,7 @@ export const BeforeBeginning = ( createdMaintenanceRunId, } = props const { t } = useTranslation(['pipette_wizard_flows', 'shared']) - React.useEffect(() => { + useEffect(() => { if (createdMaintenanceRunId == null) { createMaintenanceRun({}) } diff --git a/app/src/organisms/PipetteWizardFlows/Carriage.tsx b/app/src/organisms/PipetteWizardFlows/Carriage.tsx index a827db87581..f4103cf424d 100644 --- a/app/src/organisms/PipetteWizardFlows/Carriage.tsx +++ b/app/src/organisms/PipetteWizardFlows/Carriage.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import capitalize from 'lodash/capitalize' import { @@ -7,9 +6,9 @@ import { SPACING, LegacyStyledText, } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' -import { SimpleWizardBody } from '../../molecules/SimpleWizardBody' +import { SmallButton } from '/app/atoms/buttons' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' +import { SimpleWizardBody } from '/app/molecules/SimpleWizardBody' import { getPipetteAnimations96 } from './utils' import { BODY_STYLE, FLOWS, SECTIONS } from './constants' diff --git a/app/src/organisms/PipetteWizardFlows/CheckPipetteButton.tsx b/app/src/organisms/PipetteWizardFlows/CheckPipetteButton.tsx index 545d7ceffad..2300204b65b 100644 --- a/app/src/organisms/PipetteWizardFlows/CheckPipetteButton.tsx +++ b/app/src/organisms/PipetteWizardFlows/CheckPipetteButton.tsx @@ -1,7 +1,7 @@ -import * as React from 'react' +import type * as React from 'react' import { useInstrumentsQuery } from '@opentrons/react-api-client' import { PrimaryButton } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' +import { SmallButton } from '/app/atoms/buttons' interface CheckPipetteButtonProps { proceedButtonText: string diff --git a/app/src/organisms/PipetteWizardFlows/ChoosePipette.tsx b/app/src/organisms/PipetteWizardFlows/ChoosePipette.tsx index a31076bea9d..1a0b45001c2 100644 --- a/app/src/organisms/PipetteWizardFlows/ChoosePipette.tsx +++ b/app/src/organisms/PipetteWizardFlows/ChoosePipette.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useSelector } from 'react-redux' import { css } from 'styled-components' @@ -9,6 +9,7 @@ import { ALIGN_FLEX_END, BORDERS, COLORS, + CURSOR_POINTER, DIRECTION_COLUMN, DIRECTION_ROW, Flex, @@ -18,12 +19,12 @@ import { JUSTIFY_SPACE_AROUND, JUSTIFY_SPACE_BETWEEN, LegacyStyledText, + ModalShell, POSITION_ABSOLUTE, PrimaryButton, RESPONSIVENESS, SPACING, TYPOGRAPHY, - ModalShell, } from '@opentrons/components' import { EIGHT_CHANNEL, @@ -32,19 +33,20 @@ import { RIGHT, SINGLE_MOUNT_PIPETTES, } from '@opentrons/shared-data' -import { i18n } from '../../i18n' -import { getIsOnDevice } from '../../redux/config' -import { getTopPortalEl } from '../../App/portal' -import { SmallButton } from '../../atoms/buttons' -import { WizardHeader } from '../../molecules/WizardHeader' -import { ModalContentOneColSimpleButtons } from '../../molecules/InterventionModal' -import singleChannelAndEightChannel from '../../assets/images/change-pip/1_and_8_channel.png' -import ninetySixChannel from '../../assets/images/change-pip/ninety-six-channel.png' -import { useAttachedPipettesFromInstrumentsQuery } from '../Devices/hooks' +import { i18n } from '/app/i18n' +import { getIsOnDevice } from '/app/redux/config' +import { getTopPortalEl } from '/app/App/portal' +import { SmallButton } from '/app/atoms/buttons' +import { WizardHeader } from '/app/molecules/WizardHeader' +import { ModalContentOneColSimpleButtons } from '/app/molecules/InterventionModal' +import singleChannelAndEightChannel from '/app/assets/images/change-pip/1_and_8_channel.png' +import ninetySixChannel from '/app/assets/images/change-pip/ninety-six-channel.png' +import { useAttachedPipettesFromInstrumentsQuery } from '/app/resources/instruments' import { ExitModal } from './ExitModal' import { FLOWS } from './constants' import { getIsGantryEmpty } from './utils' +import type { Dispatch, SetStateAction, ReactNode } from 'react' import type { StyleProps } from '@opentrons/components' import type { PipetteMount } from '@opentrons/shared-data' import type { SelectablePipettes } from './types' @@ -55,7 +57,7 @@ const UNSELECTED_OPTIONS_STYLE = css` border-radius: ${BORDERS.borderRadius8}; height: 14.5625rem; width: 14.5625rem; - cursor: pointer; + cursor: ${CURSOR_POINTER}; flex-direction: ${DIRECTION_COLUMN}; justify-content: ${JUSTIFY_CENTER}; align-items: ${ALIGN_CENTER}; @@ -107,7 +109,7 @@ const SELECTED_OPTIONS_STYLE = css` interface ChoosePipetteProps { proceed: () => void selectedPipette: SelectablePipettes - setSelectedPipette: React.Dispatch> + setSelectedPipette: Dispatch> exit: () => void mount: PipetteMount } @@ -116,10 +118,9 @@ export const ChoosePipette = (props: ChoosePipetteProps): JSX.Element => { const isOnDevice = useSelector(getIsOnDevice) const { t } = useTranslation(['pipette_wizard_flows', 'shared']) const attachedPipettesByMount = useAttachedPipettesFromInstrumentsQuery() - const [ - showExitConfirmation, - setShowExitConfirmation, - ] = React.useState(false) + const [showExitConfirmation, setShowExitConfirmation] = useState( + false + ) const bothMounts = getIsGantryEmpty(attachedPipettesByMount) ? t('ninety_six_channel', { @@ -280,7 +281,7 @@ export const ChoosePipette = (props: ChoosePipetteProps): JSX.Element => { interface PipetteMountOptionProps extends StyleProps { isSelected: boolean onClick: () => void - children: React.ReactNode + children: ReactNode } function PipetteMountOption(props: PipetteMountOptionProps): JSX.Element { const { isSelected, onClick, children, ...styleProps } = props diff --git a/app/src/organisms/PipetteWizardFlows/DetachPipette.tsx b/app/src/organisms/PipetteWizardFlows/DetachPipette.tsx index 2ba20ac3ad0..9d83f3d3e75 100644 --- a/app/src/organisms/PipetteWizardFlows/DetachPipette.tsx +++ b/app/src/organisms/PipetteWizardFlows/DetachPipette.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useMemo } from 'react' import { useTranslation } from 'react-i18next' import { css } from 'styled-components' import { RIGHT, WEIGHT_OF_96_CHANNEL } from '@opentrons/shared-data' @@ -6,33 +6,35 @@ import { useInstrumentsQuery } from '@opentrons/react-api-client' import { ALIGN_CENTER, ALIGN_FLEX_END, + Banner, Btn, COLORS, Flex, JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, PrimaryButton, RESPONSIVENESS, SIZE_1, SPACING, - LegacyStyledText, TYPOGRAPHY, } from '@opentrons/components' -import { Banner } from '../../atoms/Banner' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' import { SimpleWizardBody, SimpleWizardInProgressBody, -} from '../../molecules/SimpleWizardBody' -import { Skeleton } from '../../atoms/Skeleton' -import { SmallButton } from '../../atoms/buttons' +} from '/app/molecules/SimpleWizardBody' +import { Skeleton } from '/app/atoms/Skeleton' +import { SmallButton } from '/app/atoms/buttons' import { BODY_STYLE, SECTIONS } from './constants' import { getPipetteAnimations, getPipetteAnimations96 } from './utils' -import type { PipetteWizardStepProps } from './types' + +import type { Dispatch, ReactNode, SetStateAction } from 'react' import type { PipetteData } from '@opentrons/api-client' +import type { PipetteWizardStepProps } from './types' interface DetachPipetteProps extends PipetteWizardStepProps { isFetching: boolean - setFetching: React.Dispatch> + setFetching: Dispatch> } const BACKGROUND_SIZE = '47rem' @@ -82,7 +84,7 @@ export const DetachPipette = (props: DetachPipetteProps): JSX.Element => { flowType, section: SECTIONS.DETACH_PIPETTE, } - const memoizedAttachedPipettes = React.useMemo(() => attachedPipettes, []) + const memoizedAttachedPipettes = useMemo(() => attachedPipettes, []) const is96ChannelPipette = memoizedAttachedPipettes[mount]?.instrumentName === 'p1000_96' const pipetteName = @@ -121,10 +123,9 @@ export const DetachPipette = (props: DetachPipetteProps): JSX.Element => { }) } - const [ - showPipetteStillAttached, - setShowPipetteStillAttached, - ] = React.useState(false) + const [showPipetteStillAttached, setShowPipetteStillAttached] = useState( + false + ) const handleOnClick = (): void => { setFetching(true) @@ -142,7 +143,7 @@ export const DetachPipette = (props: DetachPipetteProps): JSX.Element => { } const channel = memoizedAttachedPipettes[mount]?.data.channels - let bodyText: React.ReactNode =
    + let bodyText: ReactNode =
    if (isFetching) { bodyText = ( <> diff --git a/app/src/organisms/PipetteWizardFlows/DetachProbe.tsx b/app/src/organisms/PipetteWizardFlows/DetachProbe.tsx index ff5d9c7d700..833f28d45dc 100644 --- a/app/src/organisms/PipetteWizardFlows/DetachProbe.tsx +++ b/app/src/organisms/PipetteWizardFlows/DetachProbe.tsx @@ -1,8 +1,7 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { LegacyStyledText } from '@opentrons/components' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' -import { SimpleWizardInProgressBody } from '../../molecules/SimpleWizardBody' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' +import { SimpleWizardInProgressBody } from '/app/molecules/SimpleWizardBody' import { BODY_STYLE, SECTIONS } from './constants' import { getPipetteAnimations } from './utils' import type { PipetteWizardStepProps } from './types' diff --git a/app/src/organisms/PipetteWizardFlows/ExitModal.tsx b/app/src/organisms/PipetteWizardFlows/ExitModal.tsx index 3b9fee11913..d2bc3517643 100644 --- a/app/src/organisms/PipetteWizardFlows/ExitModal.tsx +++ b/app/src/organisms/PipetteWizardFlows/ExitModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import capitalize from 'lodash/capitalize' import { useTranslation } from 'react-i18next' import { @@ -9,11 +8,11 @@ import { AlertPrimaryButton, JUSTIFY_FLEX_END, } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' +import { SmallButton } from '/app/atoms/buttons' import { SimpleWizardBody, SimpleWizardInProgressBody, -} from '../../molecules/SimpleWizardBody' +} from '/app/molecules/SimpleWizardBody' import { FLOWS } from './constants' import type { PipetteWizardFlow } from './types' diff --git a/app/src/organisms/PipetteWizardFlows/MountPipette.tsx b/app/src/organisms/PipetteWizardFlows/MountPipette.tsx index 62a0d08d24b..9ba1b036785 100644 --- a/app/src/organisms/PipetteWizardFlows/MountPipette.tsx +++ b/app/src/organisms/PipetteWizardFlows/MountPipette.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { SINGLE_MOUNT_PIPETTES, @@ -6,14 +6,14 @@ import { } from '@opentrons/shared-data' import { Flex, + Banner, JUSTIFY_CENTER, SIZE_1, SPACING, LegacyStyledText, } from '@opentrons/components' -import { Banner } from '../../atoms/Banner' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' -import { Skeleton } from '../../atoms/Skeleton' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' +import { Skeleton } from '/app/atoms/Skeleton' import { CheckPipetteButton } from './CheckPipetteButton' import { BODY_STYLE, SECTIONS } from './constants' import { getPipetteAnimations, getPipetteAnimations96 } from './utils' diff --git a/app/src/organisms/PipetteWizardFlows/MountingPlate.tsx b/app/src/organisms/PipetteWizardFlows/MountingPlate.tsx index f8d04509dd2..aaa6178fc79 100644 --- a/app/src/organisms/PipetteWizardFlows/MountingPlate.tsx +++ b/app/src/organisms/PipetteWizardFlows/MountingPlate.tsx @@ -1,12 +1,11 @@ -import * as React from 'react' import { Trans, useTranslation } from 'react-i18next' import { LEFT } from '@opentrons/shared-data' import { COLORS, SPACING, LegacyStyledText } from '@opentrons/components' import { SimpleWizardBody, SimpleWizardInProgressBody, -} from '../../molecules/SimpleWizardBody' -import { GenericWizardTile } from '../../molecules/GenericWizardTile' +} from '/app/molecules/SimpleWizardBody' +import { GenericWizardTile } from '/app/molecules/GenericWizardTile' import { getPipetteAnimations96 } from './utils' import { BODY_STYLE, FLOWS, SECTIONS } from './constants' import type { PipetteWizardStepProps } from './types' diff --git a/app/src/organisms/PipetteWizardFlows/ProbeNotAttached.tsx b/app/src/organisms/PipetteWizardFlows/ProbeNotAttached.tsx index 0b00e64fad7..d98fde280f7 100644 --- a/app/src/organisms/PipetteWizardFlows/ProbeNotAttached.tsx +++ b/app/src/organisms/PipetteWizardFlows/ProbeNotAttached.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' import { ALIGN_CENTER, @@ -14,8 +14,8 @@ import { TYPOGRAPHY, } from '@opentrons/components' import { css } from 'styled-components' -import { SimpleWizardBody } from '../../molecules/SimpleWizardBody' -import { SmallButton } from '../../atoms/buttons' +import { SimpleWizardBody } from '/app/molecules/SimpleWizardBody' +import { SmallButton } from '/app/atoms/buttons' interface ProbeNotAttachedProps { handleOnClick: () => void @@ -32,7 +32,7 @@ export const ProbeNotAttached = ( 'branded', ]) const { isOnDevice, handleOnClick, setShowUnableToDetect } = props - const [numberOfTryAgains, setNumberOfTryAgains] = React.useState(0) + const [numberOfTryAgains, setNumberOfTryAgains] = useState(0) return ( > + setFetching: Dispatch> hasCalData: boolean requiredPipette?: LoadedPipette nextMount?: string @@ -78,7 +79,7 @@ export const Results = (props: ResultsProps): JSX.Element => { usePipetteNameSpecs(requiredPipette?.pipetteName as PipetteName) ?.displayName ?? null - const [numberOfTryAgains, setNumberOfTryAgains] = React.useState(0) + const [numberOfTryAgains, setNumberOfTryAgains] = useState(0) let header: string = 'unknown results screen' let iconColor: string = COLORS.green50 let isSuccess: boolean = true diff --git a/app/src/organisms/PipetteWizardFlows/UnskippableModal.tsx b/app/src/organisms/PipetteWizardFlows/UnskippableModal.tsx index 497e5fc19b0..8746d7646da 100644 --- a/app/src/organisms/PipetteWizardFlows/UnskippableModal.tsx +++ b/app/src/organisms/PipetteWizardFlows/UnskippableModal.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { COLORS, @@ -7,8 +6,8 @@ import { SecondaryButton, AlertPrimaryButton, } from '@opentrons/components' -import { SmallButton } from '../../atoms/buttons' -import { SimpleWizardBody } from '../../molecules/SimpleWizardBody' +import { SmallButton } from '/app/atoms/buttons' +import { SimpleWizardBody } from '/app/molecules/SimpleWizardBody' interface UnskippableModalProps { goBack: () => void diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/AttachProbe.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/AttachProbe.test.tsx index e1f6c5bd765..00120fe438a 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/AttachProbe.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/AttachProbe.test.tsx @@ -1,30 +1,27 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen, waitFor } from '@testing-library/react' import { describe, it, beforeEach, vi, expect } from 'vitest' import { LEFT, SINGLE_MOUNT_PIPETTES } from '@opentrons/shared-data' -import { - nestedTextMatcher, - renderWithProviders, -} from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { nestedTextMatcher, renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mock8ChannelAttachedPipetteInformation, mock96ChannelAttachedPipetteInformation, mockAttachedPipetteInformation, -} from '../../../redux/pipettes/__fixtures__' -import { RUN_ID_1 } from '../../RunTimeControl/__fixtures__' +} from '/app/redux/pipettes/__fixtures__' +import { RUN_ID_1 } from '/app/resources/runs/__fixtures__' import { FLOWS } from '../constants' import { AttachProbe } from '../AttachProbe' -import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' +import { useNotifyDeckConfigurationQuery } from '/app/resources/deck_configuration' const render = (props: React.ComponentProps) => { return renderWithProviders(, { i18nInstance: i18n, })[0] } -vi.mock('../../../resources/deck_configuration') +vi.mock('/app/resources/deck_configuration') describe('AttachProbe', () => { let props: React.ComponentProps diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/BeforeBeginning.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/BeforeBeginning.test.tsx index f8450ba5583..a75e8bfe97a 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/BeforeBeginning.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/BeforeBeginning.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, waitFor, screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect, afterEach } from 'vitest' @@ -9,19 +9,19 @@ import { SINGLE_MOUNT_PIPETTES, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockAttachedPipetteInformation } from '../../../redux/pipettes/__fixtures__' -import { InProgressModal } from '../../../molecules/InProgressModal/InProgressModal' -// import { NeedHelpLink } from '../../CalibrationPanels' -import { RUN_ID_1 } from '../../RunTimeControl/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockAttachedPipetteInformation } from '/app/redux/pipettes/__fixtures__' +import { InProgressModal } from '/app/molecules/InProgressModal/InProgressModal' +// import { NeedHelpLink } from '/app/molecules/OT2CalibrationNeedHelpLink' +import { RUN_ID_1 } from '/app/resources/runs/__fixtures__' import { BeforeBeginning } from '../BeforeBeginning' import { FLOWS } from '../constants' import { getIsGantryEmpty } from '../utils' // TODO(jr, 11/3/22): uncomment out the get help link when we have // the correct URL to link it to -vi.mock('../../../molecules/InProgressModal/InProgressModal') +vi.mock('/app/molecules/InProgressModal/InProgressModal') vi.mock('../utils') const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/Carriage.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/Carriage.test.tsx index aea460b67e5..17c8140ebe8 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/Carriage.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/Carriage.test.tsx @@ -1,13 +1,13 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, expect } from 'vitest' import { LEFT, NINETY_SIX_CHANNEL } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockAttachedPipetteInformation } from '../../../redux/pipettes/__fixtures__' -import { RUN_ID_1 } from '../../RunTimeControl/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockAttachedPipetteInformation } from '/app/redux/pipettes/__fixtures__' +import { RUN_ID_1 } from '/app/resources/runs/__fixtures__' import { FLOWS } from '../constants' import { Carriage } from '../Carriage' diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/CheckPipetteButton.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/CheckPipetteButton.test.tsx index 31bc7e6994c..e5dfc5fe3de 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/CheckPipetteButton.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/CheckPipetteButton.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, waitFor, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { useInstrumentsQuery } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { CheckPipetteButton } from '../CheckPipetteButton' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/ChoosePipette.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/ChoosePipette.test.tsx index 11a0f0f8452..bda196f388c 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/ChoosePipette.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/ChoosePipette.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { LEFT, NINETY_SIX_CHANNEL, @@ -9,17 +9,17 @@ import { describe, it, beforeEach, vi, expect, afterEach } from 'vitest' import { COLORS } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockAttachedPipetteInformation } from '../../../redux/pipettes/__fixtures__' -import { getIsOnDevice } from '../../../redux/config' -import { useAttachedPipettesFromInstrumentsQuery } from '../../Devices/hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockAttachedPipetteInformation } from '/app/redux/pipettes/__fixtures__' +import { getIsOnDevice } from '/app/redux/config' +import { useAttachedPipettesFromInstrumentsQuery } from '/app/resources/instruments' import { ChoosePipette } from '../ChoosePipette' import { getIsGantryEmpty } from '../utils' vi.mock('../utils') -vi.mock('../../Devices/hooks') -vi.mock('../../../redux/config') +vi.mock('/app/resources/instruments') +vi.mock('/app/redux/config') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/DetachPipette.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/DetachPipette.test.tsx index 7222e856496..d7777ed368c 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/DetachPipette.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/DetachPipette.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, expect } from 'vitest' @@ -8,19 +8,19 @@ import { SINGLE_MOUNT_PIPETTES, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { mock96ChannelAttachedPipetteInformation, mockAttachedPipetteInformation, -} from '../../../redux/pipettes/__fixtures__' -import { InProgressModal } from '../../../molecules/InProgressModal/InProgressModal' -import { RUN_ID_1 } from '../../RunTimeControl/__fixtures__' +} from '/app/redux/pipettes/__fixtures__' +import { InProgressModal } from '/app/molecules/InProgressModal/InProgressModal' +import { RUN_ID_1 } from '/app/resources/runs/__fixtures__' import { FLOWS } from '../constants' import { DetachPipette } from '../DetachPipette' vi.mock('../CheckPipetteButton') -vi.mock('../../../molecules/InProgressModal/InProgressModal') +vi.mock('/app/molecules/InProgressModal/InProgressModal') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/DetachProbe.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/DetachProbe.test.tsx index a32f683a6ff..059846aebb5 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/DetachProbe.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/DetachProbe.test.tsx @@ -1,18 +1,18 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, expect } from 'vitest' import { LEFT, SINGLE_MOUNT_PIPETTES } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockAttachedPipetteInformation } from '../../../redux/pipettes/__fixtures__' -import { InProgressModal } from '../../../molecules/InProgressModal/InProgressModal' -import { RUN_ID_1 } from '../../RunTimeControl/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockAttachedPipetteInformation } from '/app/redux/pipettes/__fixtures__' +import { InProgressModal } from '/app/molecules/InProgressModal/InProgressModal' +import { RUN_ID_1 } from '/app/resources/runs/__fixtures__' import { FLOWS } from '../constants' import { DetachProbe } from '../DetachProbe' -vi.mock('../../../molecules/InProgressModal/InProgressModal') +vi.mock('/app/molecules/InProgressModal/InProgressModal') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/ExitModal.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/ExitModal.test.tsx index 443535e4bcc..a30407379a2 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/ExitModal.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/ExitModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, beforeEach, vi, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { FLOWS } from '../constants' import { ExitModal } from '../ExitModal' diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/MountPipette.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/MountPipette.test.tsx index 550858c1983..4c5d9dda2e0 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/MountPipette.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/MountPipette.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, beforeEach, vi } from 'vitest' @@ -8,10 +8,10 @@ import { SINGLE_MOUNT_PIPETTES, } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockAttachedPipetteInformation } from '../../../redux/pipettes/__fixtures__' -import { RUN_ID_1 } from '../../RunTimeControl/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockAttachedPipetteInformation } from '/app/redux/pipettes/__fixtures__' +import { RUN_ID_1 } from '/app/resources/runs/__fixtures__' import { FLOWS } from '../constants' import { CheckPipetteButton } from '../CheckPipetteButton' import { MountPipette } from '../MountPipette' diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/MountingPlate.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/MountingPlate.test.tsx index 3ec113627be..38744ad1bab 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/MountingPlate.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/MountingPlate.test.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, waitFor, screen } from '@testing-library/react' import { describe, it, expect, beforeEach, vi } from 'vitest' import { LEFT, NINETY_SIX_CHANNEL } from '@opentrons/shared-data' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { mockAttachedPipetteInformation } from '../../../redux/pipettes/__fixtures__' -import { RUN_ID_1 } from '../../RunTimeControl/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockAttachedPipetteInformation } from '/app/redux/pipettes/__fixtures__' +import { RUN_ID_1 } from '/app/resources/runs/__fixtures__' import { FLOWS } from '../constants' import { MountingPlate } from '../MountingPlate' diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/Results.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/Results.test.tsx index b0cb919531c..3382ac401a0 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/Results.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/Results.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { act, fireEvent, screen, waitFor } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' @@ -10,18 +10,18 @@ import { import { COLORS } from '@opentrons/components' import { useInstrumentsQuery } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' -import { mockAttachedPipetteInformation } from '../../../redux/pipettes/__fixtures__' -import { useIsOEMMode } from '../../../resources/robot-settings/hooks' -import { i18n } from '../../../i18n' -import { RUN_ID_1 } from '../../RunTimeControl/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { mockAttachedPipetteInformation } from '/app/redux/pipettes/__fixtures__' +import { useIsOEMMode } from '/app/resources/robot-settings/hooks' +import { i18n } from '/app/i18n' +import { RUN_ID_1 } from '/app/resources/runs/__fixtures__' import { Results } from '../Results' import { FLOWS } from '../constants' import type { Mock } from 'vitest' vi.mock('@opentrons/react-api-client') -vi.mock('../../../resources/robot-settings/hooks') +vi.mock('/app/resources/robot-settings/hooks') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/UnskippableModal.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/UnskippableModal.test.tsx index 43fa441c7d1..bc738d0caf3 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/UnskippableModal.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/UnskippableModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { UnskippableModal } from '../UnskippableModal' const render = (props: React.ComponentProps) => { @@ -24,7 +24,7 @@ describe('UnskippableModal', () => { render(props) screen.getByText('This is a critical step that should not be skipped') screen.getByText( - 'You must detach the mounting plate and reattach the z-axis carraige before using other pipettes. We do not recommend exiting this process before completion.' + 'You must detach the mounting plate and reattach the z-axis carriage before using other pipettes. We do not recommend exiting this process before completion.' ) fireEvent.click(screen.getByRole('button', { name: 'Go back' })) expect(props.goBack).toHaveBeenCalled() @@ -39,7 +39,7 @@ describe('UnskippableModal', () => { render(props) screen.getByText('This is a critical step that should not be skipped') screen.getByText( - 'You must detach the mounting plate and reattach the z-axis carraige before using other pipettes. We do not recommend exiting this process before completion.' + 'You must detach the mounting plate and reattach the z-axis carriage before using other pipettes. We do not recommend exiting this process before completion.' ) screen.getByText('Exit') screen.getByText('Go back') diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/getPipetteWizardStepsForProtocol.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/getPipetteWizardStepsForProtocol.test.tsx index 1fc4ea2464e..fce4dbcc57b 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/getPipetteWizardStepsForProtocol.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/getPipetteWizardStepsForProtocol.test.tsx @@ -3,7 +3,7 @@ import { LEFT, RIGHT } from '@opentrons/shared-data' import { mock96ChannelAttachedPipetteInformation, mockAttachedPipetteInformation, -} from '../../../redux/pipettes/__fixtures__' +} from '/app/redux/pipettes/__fixtures__' import { FLOWS, SECTIONS } from '../constants' import { getPipetteWizardStepsForProtocol } from '../getPipetteWizardStepsForProtocol' @@ -17,6 +17,9 @@ const mockPipetteInfo = [ const mockPipettesInProtocolNotEmpty = [ { id: '123', pipetteName: 'p1000_single_flex', mount: 'left' }, ] +const mockPipettesInProtocolOnRight = [ + { id: '123', pipetteName: 'p1000_single_flex', mount: 'right' }, +] const mockPipettesInProtocolMulti = [ { id: '123', pipetteName: 'p1000_multi_flex', mount: 'left' }, ] @@ -113,7 +116,7 @@ describe('getPipetteWizardStepsForProtocol', () => { ).toStrictEqual(mockFlowSteps) }) - it('returns the correct array of info when the attached 96-channel pipette needs to be switched out for single mount', () => { + it('returns the correct array of info when the attached 96-channel pipette needs to be switched out for single mount on left', () => { const mockFlowSteps = [ { section: SECTIONS.BEFORE_BEGINNING, @@ -176,6 +179,69 @@ describe('getPipetteWizardStepsForProtocol', () => { ) ).toStrictEqual(mockFlowSteps) }) + it('returns the correct array of info when the attached 96-channel pipette needs to be switched out for single mount on right', () => { + const mockFlowSteps = [ + { + section: SECTIONS.BEFORE_BEGINNING, + mount: LEFT, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.DETACH_PIPETTE, + mount: LEFT, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.MOUNTING_PLATE, + mount: LEFT, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.CARRIAGE, + mount: LEFT, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.RESULTS, + mount: LEFT, + flowType: FLOWS.DETACH, + nextMount: RIGHT, + }, + { + section: SECTIONS.MOUNT_PIPETTE, + mount: RIGHT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.FIRMWARE_UPDATE, + mount: RIGHT, + flowType: FLOWS.ATTACH, + }, + { section: SECTIONS.RESULTS, mount: RIGHT, flowType: FLOWS.ATTACH }, + { + section: SECTIONS.ATTACH_PROBE, + mount: RIGHT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.DETACH_PROBE, + mount: RIGHT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.RESULTS, + mount: RIGHT, + flowType: FLOWS.CALIBRATE, + }, + ] as PipetteWizardStep[] + expect( + getPipetteWizardStepsForProtocol( + { left: mock96ChannelAttachedPipetteInformation, right: null }, + mockPipettesInProtocolOnRight as any, + RIGHT + ) + ).toStrictEqual(mockFlowSteps) + }) it('returns the correct array of info when the attached pipette on left mount needs to be switched out for 96-channel', () => { const mockFlowSteps = [ { diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/hooks.test.tsx b/app/src/organisms/PipetteWizardFlows/__tests__/hooks.test.tsx index 9a3a6424ca3..f44bd96fc6e 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/hooks.test.tsx +++ b/app/src/organisms/PipetteWizardFlows/__tests__/hooks.test.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { describe, it, beforeEach, vi, afterEach, expect } from 'vitest' import { I18nextProvider } from 'react-i18next' import { renderHook } from '@testing-library/react' @@ -8,11 +8,11 @@ import { LEFT, SINGLE_MOUNT_PIPETTES, } from '@opentrons/shared-data' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { mock96ChannelAttachedPipetteInformation, mockAttachedPipetteInformation, -} from '../../../redux/pipettes/__fixtures__' +} from '/app/redux/pipettes/__fixtures__' import { FLOWS } from '../constants' import { usePipetteFlowWizardHeaderText } from '../hooks' diff --git a/app/src/organisms/PipetteWizardFlows/__tests__/utils.test.ts b/app/src/organisms/PipetteWizardFlows/__tests__/utils.test.ts index 4b5231430a4..4eb2fdfb457 100644 --- a/app/src/organisms/PipetteWizardFlows/__tests__/utils.test.ts +++ b/app/src/organisms/PipetteWizardFlows/__tests__/utils.test.ts @@ -1,7 +1,7 @@ import { render, screen } from '@testing-library/react' import { describe, it, expect } from 'vitest' import { LEFT, RIGHT } from '@opentrons/shared-data' -import { mockAttachedPipetteInformation } from '../../../redux/pipettes/__fixtures__' +import { mockAttachedPipetteInformation } from '/app/redux/pipettes/__fixtures__' import { getIsGantryEmpty, getPipetteAnimations, diff --git a/app/src/organisms/PipetteWizardFlows/getPipetteWizardStepsForProtocol.ts b/app/src/organisms/PipetteWizardFlows/getPipetteWizardStepsForProtocol.ts index 064b65b7f95..296c428bc75 100644 --- a/app/src/organisms/PipetteWizardFlows/getPipetteWizardStepsForProtocol.ts +++ b/app/src/organisms/PipetteWizardFlows/getPipetteWizardStepsForProtocol.ts @@ -1,419 +1,465 @@ import { LEFT, RIGHT } from '@opentrons/shared-data' import { FLOWS, SECTIONS } from './constants' import type { LoadedPipette } from '@opentrons/shared-data' -import type { Mount } from '../../redux/pipettes/types' -import type { AttachedPipettesFromInstrumentsQuery } from '../Devices/hooks' +import type { Mount } from '/app/redux/pipettes/types' +import type { AttachedPipettesFromInstrumentsQuery } from '/app/resources/instruments' import type { PipetteWizardStep } from './types' +const calibrateAlreadyAttachedPipetteOn = ( + mount: Mount +): PipetteWizardStep[] => [ + { + section: SECTIONS.BEFORE_BEGINNING, + mount, + flowType: FLOWS.CALIBRATE, + }, + { + section: SECTIONS.ATTACH_PROBE, + mount, + flowType: FLOWS.CALIBRATE, + }, + { + section: SECTIONS.DETACH_PROBE, + mount, + flowType: FLOWS.CALIBRATE, + }, + { section: SECTIONS.RESULTS, mount, flowType: FLOWS.CALIBRATE }, +] + +const detachNinetySixAndAttachSingleMountOn = ( + mount: Mount +): PipetteWizardStep[] => [ + { + section: SECTIONS.BEFORE_BEGINNING, + mount: LEFT, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.DETACH_PIPETTE, + mount: LEFT, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.MOUNTING_PLATE, + mount: LEFT, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.CARRIAGE, + mount: LEFT, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.RESULTS, + mount: LEFT, + flowType: FLOWS.DETACH, + nextMount: mount, + }, + { + section: SECTIONS.MOUNT_PIPETTE, + mount, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.FIRMWARE_UPDATE, + mount, + flowType: FLOWS.ATTACH, + }, + { section: SECTIONS.RESULTS, mount, flowType: FLOWS.ATTACH }, + { + section: SECTIONS.ATTACH_PROBE, + mount, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.DETACH_PROBE, + mount, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.RESULTS, + mount, + flowType: FLOWS.CALIBRATE, + }, +] + +const detachSingleMountAndAttachSingleMountOn = ( + mount: Mount +): PipetteWizardStep[] => [ + { + section: SECTIONS.BEFORE_BEGINNING, + mount, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.DETACH_PIPETTE, + mount, + flowType: FLOWS.DETACH, + }, + { section: SECTIONS.RESULTS, mount, flowType: FLOWS.DETACH }, + { + section: SECTIONS.MOUNT_PIPETTE, + mount, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.FIRMWARE_UPDATE, + mount, + flowType: FLOWS.ATTACH, + }, + { section: SECTIONS.RESULTS, mount, flowType: FLOWS.ATTACH }, + { + section: SECTIONS.ATTACH_PROBE, + mount, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.DETACH_PROBE, + mount, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.RESULTS, + mount, + flowType: FLOWS.CALIBRATE, + }, +] + +const detachTwoSingleMountsAndAttachNinetySix = (): PipetteWizardStep[] => [ + { + section: SECTIONS.BEFORE_BEGINNING, + mount: LEFT, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.DETACH_PIPETTE, + mount: LEFT, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.RESULTS, + mount: LEFT, + flowType: FLOWS.DETACH, + nextMount: RIGHT, + }, + { + section: SECTIONS.DETACH_PIPETTE, + mount: RIGHT, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.RESULTS, + mount: RIGHT, + flowType: FLOWS.DETACH, + nextMount: 'both', + }, + { + section: SECTIONS.CARRIAGE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.MOUNTING_PLATE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.MOUNT_PIPETTE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.FIRMWARE_UPDATE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.ATTACH }, + { + section: SECTIONS.ATTACH_PROBE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.DETACH_PROBE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.RESULTS, + mount: LEFT, + flowType: FLOWS.CALIBRATE, + }, +] + +const detachSingleMountOnLeftAndAttachNinetySix = (): PipetteWizardStep[] => [ + { + section: SECTIONS.BEFORE_BEGINNING, + mount: LEFT, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.DETACH_PIPETTE, + mount: LEFT, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.RESULTS, + mount: LEFT, + flowType: FLOWS.DETACH, + nextMount: 'both', + }, + { + section: SECTIONS.CARRIAGE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.MOUNTING_PLATE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.MOUNT_PIPETTE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.FIRMWARE_UPDATE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.ATTACH }, + { + section: SECTIONS.ATTACH_PROBE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.DETACH_PROBE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.RESULTS, + mount: LEFT, + flowType: FLOWS.CALIBRATE, + }, +] + +const detachSingleMountOnRightAndAttachNinetySix = (): PipetteWizardStep[] => [ + { + section: SECTIONS.BEFORE_BEGINNING, + mount: RIGHT, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.DETACH_PIPETTE, + mount: RIGHT, + flowType: FLOWS.DETACH, + }, + { + section: SECTIONS.RESULTS, + mount: RIGHT, + flowType: FLOWS.DETACH, + nextMount: 'both', + }, + { + section: SECTIONS.CARRIAGE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.MOUNTING_PLATE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.MOUNT_PIPETTE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.FIRMWARE_UPDATE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.ATTACH }, + { + section: SECTIONS.ATTACH_PROBE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.DETACH_PROBE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.RESULTS, + mount: LEFT, + flowType: FLOWS.CALIBRATE, + }, +] + +const fromEmptyGantryAttachNinetySix = (): PipetteWizardStep[] => [ + { + section: SECTIONS.BEFORE_BEGINNING, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.CARRIAGE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.MOUNTING_PLATE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.MOUNT_PIPETTE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.FIRMWARE_UPDATE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.ATTACH }, + { + section: SECTIONS.ATTACH_PROBE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.DETACH_PROBE, + mount: LEFT, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.RESULTS, + mount: LEFT, + flowType: FLOWS.CALIBRATE, + }, +] + +const fromEmptyMountAttachSingleMountOn = ( + mount: Mount +): PipetteWizardStep[] => [ + { + section: SECTIONS.BEFORE_BEGINNING, + mount, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.MOUNT_PIPETTE, + mount, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.FIRMWARE_UPDATE, + mount, + flowType: FLOWS.ATTACH, + }, + { section: SECTIONS.RESULTS, mount, flowType: FLOWS.ATTACH }, + { + section: SECTIONS.ATTACH_PROBE, + mount, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.DETACH_PROBE, + mount, + flowType: FLOWS.ATTACH, + }, + { + section: SECTIONS.RESULTS, + mount, + flowType: FLOWS.CALIBRATE, + }, +] +/** ++-------------+-----------------------------------------------+----------------------------------------------+-----------------------------------------------+ +| requested > |96 |left |right | +| | | | | +| v attached | | | | ++-------------+-----------------------------------------------+----------------------------------------------+-----------------------------------------------+ +| 96 | calibrateAlreadyAttachedPipetteOn(left) | detachNinetySixAndAttachSingleMountOn(left) | detachNinetySixAndAttachSingleMountOn(right) | ++-------------+-----------------------------------------------+----------------------------------------------+-----------------------------------------------+ +| | | calibrateAlreadyAttachedPipetteOn(left) or | fromEmptyMountAttachSingleMountOn(right) | +| left only | detachSingleMountOnLeftAndAttachNinetySix() | detachSingleMountAndAttachSingleMountOn(left)| | +| | | | | ++-------------+-----------------------------------------------+----------------------------------------------+-----------------------------------------------+ +| | | | calibrateAlreadyAttachedPipetteOn(right) or | +| right only | detachSingleMountOnRightAndAttachNinetySix() |fromEmptyMountAttachSingleMountOn(left) | detachSingleMountAndAttachSingleMountOn(right)| +| | | | | ++-------------+-----------------------------------------------+----------------------------------------------+-----------------------------------------------+ +| left and | | calibrateAlreadyAttachedPipetteOn(left) or | calibrateAlreadyAttachedPipetteOn(right) or | +| right | detachTwoSingleMountsAndAttachNinetySix() | detachSingleMountAndAttachSingleMountOn(left)| detachSingleMountAndAttachSingleMountOn(right)| +| | | | | ++-------------+-----------------------------------------------+----------------------------------------------+-----------------------------------------------+ +| | | | | +| nothing | fromEmptyGantryAttachNinetySix() | fromEmptyMountAttachSingleMountOn(left) | fromEmptyMountAttachSingleMountOn(right) | +| | | | | ++-------------+-----------------------------------------------+----------------------------------------------+-----------------------------------------------+ + **/ + export const getPipetteWizardStepsForProtocol = ( attachedPipettes: AttachedPipettesFromInstrumentsQuery, pipetteInfo: LoadedPipette[], mount: Mount ): PipetteWizardStep[] | null => { const requiredPipette = pipetteInfo.find(pipette => pipette.mount === mount) - const nintySixChannelAttached = + const ninetySixChannelAttached = attachedPipettes[LEFT]?.instrumentName === 'p1000_96' + const ninetySixChannelRequested = requiredPipette?.pipetteName === 'p1000_96' - // return empty array if no pipette is required in the protocol if (requiredPipette == null) { + // return empty array if no pipette is required in the protocol return null - // return calibration flow if correct pipette is attached } else if ( requiredPipette?.pipetteName === attachedPipettes[mount]?.instrumentName ) { - return [ - { - section: SECTIONS.BEFORE_BEGINNING, - mount, - flowType: FLOWS.CALIBRATE, - }, - { - section: SECTIONS.ATTACH_PROBE, - mount, - flowType: FLOWS.CALIBRATE, - }, - { - section: SECTIONS.DETACH_PROBE, - mount, - flowType: FLOWS.CALIBRATE, - }, - { section: SECTIONS.RESULTS, mount, flowType: FLOWS.CALIBRATE }, - ] - } else if ( - requiredPipette.pipetteName !== 'p1000_96' && - attachedPipettes[mount] != null - ) { - // 96-channel pipette attached and need to attach single mount pipette - if (nintySixChannelAttached) { - return [ - { - section: SECTIONS.BEFORE_BEGINNING, - mount: LEFT, - flowType: FLOWS.DETACH, - }, - { - section: SECTIONS.DETACH_PIPETTE, - mount: LEFT, - flowType: FLOWS.DETACH, - }, - { - section: SECTIONS.MOUNTING_PLATE, - mount: LEFT, - flowType: FLOWS.DETACH, - }, - { - section: SECTIONS.CARRIAGE, - mount: LEFT, - flowType: FLOWS.DETACH, - }, - { - section: SECTIONS.RESULTS, - mount: LEFT, - flowType: FLOWS.DETACH, - nextMount: mount, - }, - { - section: SECTIONS.MOUNT_PIPETTE, - mount, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.FIRMWARE_UPDATE, - mount, - flowType: FLOWS.ATTACH, - }, - { section: SECTIONS.RESULTS, mount, flowType: FLOWS.ATTACH }, - { - section: SECTIONS.ATTACH_PROBE, - mount, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.DETACH_PROBE, - mount, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.RESULTS, - mount, - flowType: FLOWS.CALIBRATE, - }, - ] - // Single mount pipette attached and need to attach new single mount pipette - } else { - return [ - { - section: SECTIONS.BEFORE_BEGINNING, - mount, - flowType: FLOWS.DETACH, - }, - { - section: SECTIONS.DETACH_PIPETTE, - mount, - flowType: FLOWS.DETACH, - }, - { section: SECTIONS.RESULTS, mount, flowType: FLOWS.DETACH }, - { - section: SECTIONS.MOUNT_PIPETTE, - mount, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.FIRMWARE_UPDATE, - mount, - flowType: FLOWS.ATTACH, - }, - { section: SECTIONS.RESULTS, mount, flowType: FLOWS.ATTACH }, - { - section: SECTIONS.ATTACH_PROBE, - mount, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.DETACH_PROBE, - mount, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.RESULTS, - mount, - flowType: FLOWS.CALIBRATE, - }, - ] - } - // Single mount pipette attached to both mounts and need to attach 96-channel pipette + // return calibration flow if correct pipette is attached + return calibrateAlreadyAttachedPipetteOn(mount) + } else if (!ninetySixChannelRequested && ninetySixChannelAttached) { + // 96-channel pipette attached and need to attach single mount pipette + return detachNinetySixAndAttachSingleMountOn(mount) + } else if (!ninetySixChannelRequested && attachedPipettes[mount] != null) { + // Single mount pipette attached and need to attach new single mount pipette + return detachSingleMountAndAttachSingleMountOn(mount) } else if ( - requiredPipette.pipetteName === 'p1000_96' && + ninetySixChannelRequested && attachedPipettes[LEFT] != null && attachedPipettes[RIGHT] != null ) { - return [ - { - section: SECTIONS.BEFORE_BEGINNING, - mount: LEFT, - flowType: FLOWS.DETACH, - }, - { - section: SECTIONS.DETACH_PIPETTE, - mount: LEFT, - flowType: FLOWS.DETACH, - }, - { - section: SECTIONS.RESULTS, - mount: LEFT, - flowType: FLOWS.DETACH, - nextMount: RIGHT, - }, - { - section: SECTIONS.DETACH_PIPETTE, - mount: RIGHT, - flowType: FLOWS.DETACH, - }, - { - section: SECTIONS.RESULTS, - mount: RIGHT, - flowType: FLOWS.DETACH, - nextMount: 'both', - }, - { - section: SECTIONS.CARRIAGE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.MOUNTING_PLATE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.MOUNT_PIPETTE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.FIRMWARE_UPDATE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.ATTACH }, - { - section: SECTIONS.ATTACH_PROBE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.DETACH_PROBE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.RESULTS, - mount: LEFT, - flowType: FLOWS.CALIBRATE, - }, - ] - // Single mount pipette attached to left mount and need to attach 96-channel pipette + // Single mount pipette attached to both mounts and need to attach 96-channel pipette + return detachTwoSingleMountsAndAttachNinetySix() } else if ( - requiredPipette.pipetteName === 'p1000_96' && + ninetySixChannelRequested && attachedPipettes[LEFT] != null && attachedPipettes[RIGHT] == null ) { - return [ - { - section: SECTIONS.BEFORE_BEGINNING, - mount: LEFT, - flowType: FLOWS.DETACH, - }, - { - section: SECTIONS.DETACH_PIPETTE, - mount: LEFT, - flowType: FLOWS.DETACH, - }, - { - section: SECTIONS.RESULTS, - mount: LEFT, - flowType: FLOWS.DETACH, - nextMount: 'both', - }, - { - section: SECTIONS.CARRIAGE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.MOUNTING_PLATE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.MOUNT_PIPETTE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.FIRMWARE_UPDATE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.ATTACH }, - { - section: SECTIONS.ATTACH_PROBE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.DETACH_PROBE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.RESULTS, - mount: LEFT, - flowType: FLOWS.CALIBRATE, - }, - ] - // Single mount pipette attached to right mount and need to attach 96-channel pipette + // Single mount pipette attached to left mount and need to attach 96-channel pipette + return detachSingleMountOnLeftAndAttachNinetySix() } else if ( - requiredPipette.pipetteName === 'p1000_96' && + ninetySixChannelRequested && attachedPipettes[LEFT] == null && attachedPipettes[RIGHT] != null ) { - return [ - { - section: SECTIONS.BEFORE_BEGINNING, - mount: RIGHT, - flowType: FLOWS.DETACH, - }, - { - section: SECTIONS.DETACH_PIPETTE, - mount: RIGHT, - flowType: FLOWS.DETACH, - }, - { - section: SECTIONS.RESULTS, - mount: RIGHT, - flowType: FLOWS.DETACH, - nextMount: 'both', - }, - { - section: SECTIONS.CARRIAGE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.MOUNTING_PLATE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.MOUNT_PIPETTE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.FIRMWARE_UPDATE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.ATTACH }, - { - section: SECTIONS.ATTACH_PROBE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.DETACH_PROBE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.RESULTS, - mount: LEFT, - flowType: FLOWS.CALIBRATE, - }, - ] - // if no pipette is attached to gantry + // Single mount pipette attached to right mount and need to attach 96-channel pipette + return detachSingleMountOnRightAndAttachNinetySix() } else { - // Gantry empty and need to attach 96-channel pipette - if (requiredPipette.pipetteName === 'p1000_96') { - return [ - { - section: SECTIONS.BEFORE_BEGINNING, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.CARRIAGE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.MOUNTING_PLATE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.MOUNT_PIPETTE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.FIRMWARE_UPDATE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { section: SECTIONS.RESULTS, mount: LEFT, flowType: FLOWS.ATTACH }, - { - section: SECTIONS.ATTACH_PROBE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.DETACH_PROBE, - mount: LEFT, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.RESULTS, - mount: LEFT, - flowType: FLOWS.CALIBRATE, - }, - ] - // Gantry empty and need to attach single mount pipette + // if no pipette is attached to gantry + + if (ninetySixChannelRequested) { + // Gantry empty and need to attach 96-channel pipette + return fromEmptyGantryAttachNinetySix() } else { - return [ - { - section: SECTIONS.BEFORE_BEGINNING, - mount, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.MOUNT_PIPETTE, - mount, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.FIRMWARE_UPDATE, - mount, - flowType: FLOWS.ATTACH, - }, - { section: SECTIONS.RESULTS, mount, flowType: FLOWS.ATTACH }, - { - section: SECTIONS.ATTACH_PROBE, - mount, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.DETACH_PROBE, - mount, - flowType: FLOWS.ATTACH, - }, - { - section: SECTIONS.RESULTS, - mount, - flowType: FLOWS.CALIBRATE, - }, - ] + // Gantry empty and need to attach single mount pipette + return fromEmptyMountAttachSingleMountOn(mount) } } } diff --git a/app/src/organisms/PipetteWizardFlows/hooks.tsx b/app/src/organisms/PipetteWizardFlows/hooks.tsx index 8d727aeeb75..4d07586c9de 100644 --- a/app/src/organisms/PipetteWizardFlows/hooks.tsx +++ b/app/src/organisms/PipetteWizardFlows/hooks.tsx @@ -3,7 +3,7 @@ import capitalize from 'lodash/capitalize' import { LEFT, RIGHT, SINGLE_MOUNT_PIPETTES } from '@opentrons/shared-data' import { FLOWS } from './constants' import type { LoadedPipette, PipetteMount } from '@opentrons/shared-data' -import type { AttachedPipettesFromInstrumentsQuery } from '../Devices/hooks' +import type { AttachedPipettesFromInstrumentsQuery } from '/app/resources/instruments' import type { PipetteWizardFlow, SelectablePipettes } from './types' interface PipetteFlowWizardHeaderTextProps { diff --git a/app/src/organisms/PipetteWizardFlows/index.tsx b/app/src/organisms/PipetteWizardFlows/index.tsx index b94c5d03c6a..be3c22cc566 100644 --- a/app/src/organisms/PipetteWizardFlows/index.tsx +++ b/app/src/organisms/PipetteWizardFlows/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useMemo, useState, useEffect } from 'react' import { createPortal } from 'react-dom' import { useSelector } from 'react-redux' import { useTranslation } from 'react-i18next' @@ -16,17 +16,17 @@ import { ApiHostProvider, } from '@opentrons/react-api-client' +import { useCreateTargetedMaintenanceRunMutation } from '/app/resources/runs' import { - useCreateTargetedMaintenanceRunMutation, useChainMaintenanceCommands, -} from '../../resources/runs' -import { useNotifyCurrentMaintenanceRun } from '../../resources/maintenance_runs' -import { getTopPortalEl } from '../../App/portal' -import { WizardHeader } from '../../molecules/WizardHeader' + useNotifyCurrentMaintenanceRun, +} from '/app/resources/maintenance_runs' +import { getTopPortalEl } from '/app/App/portal' +import { WizardHeader } from '/app/molecules/WizardHeader' import { FirmwareUpdateModal } from '../FirmwareUpdateModal' -import { getIsOnDevice } from '../../redux/config' -import { SimpleWizardBody } from '../../molecules/SimpleWizardBody' -import { useAttachedPipettesFromInstrumentsQuery } from '../Devices/hooks' +import { getIsOnDevice } from '/app/redux/config' +import { SimpleWizardBody } from '/app/molecules/SimpleWizardBody' +import { useAttachedPipettesFromInstrumentsQuery } from '/app/resources/instruments' import { usePipetteFlowWizardHeaderText } from './hooks' import { getPipetteWizardSteps } from './getPipetteWizardSteps' import { getPipetteWizardStepsForProtocol } from './getPipetteWizardStepsForProtocol' @@ -48,6 +48,7 @@ import type { PipetteMount, } from '@opentrons/shared-data' import type { CommandData, HostConfig } from '@opentrons/api-client' +import { RUN_STATUS_FAILED } from '@opentrons/api-client' import type { PipetteWizardFlow, SelectablePipettes } from './types' const RUN_REFETCH_INTERVAL = 5000 @@ -69,13 +70,13 @@ export const PipetteWizardFlows = ( const { t } = useTranslation('pipette_wizard_flows') const attachedPipettes = useAttachedPipettesFromInstrumentsQuery() - const memoizedPipetteInfo = React.useMemo(() => props.pipetteInfo ?? null, []) - const isGantryEmpty = React.useMemo( + const memoizedPipetteInfo = useMemo(() => props.pipetteInfo ?? null, []) + const isGantryEmpty = useMemo( () => attachedPipettes[LEFT] == null && attachedPipettes[RIGHT] == null, [] ) - const pipetteWizardSteps = React.useMemo( + const pipetteWizardSteps = useMemo( () => memoizedPipetteInfo == null ? getPipetteWizardSteps(flowType, mount, selectedPipette, isGantryEmpty) @@ -90,14 +91,12 @@ export const PipetteWizardFlows = ( pipette => pipette.mount === mount ) const host = useHost() - const [currentStepIndex, setCurrentStepIndex] = React.useState(0) + const [currentStepIndex, setCurrentStepIndex] = useState(0) const totalStepCount = pipetteWizardSteps != null ? pipetteWizardSteps.length - 1 : 0 const currentStep = pipetteWizardSteps?.[currentStepIndex] ?? null - const [isFetchingPipettes, setIsFetchingPipettes] = React.useState( - false - ) - const memoizedAttachedPipettes = React.useMemo(() => attachedPipettes, []) + const [isFetchingPipettes, setIsFetchingPipettes] = useState(false) + const memoizedAttachedPipettes = useMemo(() => attachedPipettes, []) const hasCalData = memoizedAttachedPipettes[mount]?.data.calibratedOffset?.last_modified != null @@ -110,16 +109,17 @@ export const PipetteWizardFlows = ( attachedPipettes: memoizedAttachedPipettes, pipetteInfo: memoizedPipetteInfo, }) - const memoizedWizardTitle = React.useMemo(() => wizardTitle, []) - const [createdMaintenanceRunId, setCreatedMaintenanceRunId] = React.useState< + const memoizedWizardTitle = useMemo(() => wizardTitle, []) + const [createdMaintenanceRunId, setCreatedMaintenanceRunId] = useState< string | null >(null) + const [errorMessage, setShowErrorMessage] = useState(null) // we should start checking for run deletion only after the maintenance run is created // and the useCurrentRun poll has returned that created id const [ monitorMaintenanceRunForDeletion, setMonitorMaintenanceRunForDeletion, - ] = React.useState(false) + ] = useState(false) const goBack = (): void => { setCurrentStepIndex( @@ -144,13 +144,16 @@ export const PipetteWizardFlows = ( onSuccess: response => { setCreatedMaintenanceRunId(response.data.id) }, + onError: error => { + setShowErrorMessage(error.message) + }, }, host ) // this will close the modal in case the run was deleted by the terminate // activity modal on the ODD - React.useEffect(() => { + useEffect(() => { if ( createdMaintenanceRunId !== null && maintenanceRunData?.data.id === createdMaintenanceRunId @@ -170,10 +173,7 @@ export const PipetteWizardFlows = ( closeFlow, ]) - const [errorMessage, setShowErrorMessage] = React.useState( - null - ) - const [isExiting, setIsExiting] = React.useState(false) + const [isExiting, setIsExiting] = useState(false) const proceed = (): void => { if (!isCommandMutationLoading) { setCurrentStepIndex( @@ -184,12 +184,13 @@ export const PipetteWizardFlows = ( } } const handleClose = (): void => { - if (onComplete != null) onComplete() + if (onComplete != null) { + onComplete() + } if (maintenanceRunData != null) { deleteMaintenanceRun(maintenanceRunData?.data.id) - } else { - closeFlow() } + closeFlow() } const { @@ -212,13 +213,13 @@ export const PipetteWizardFlows = ( [{ commandType: 'home' as const, params: {} }], false ) - .then(() => { - handleClose() - }) .catch(error => { setIsExiting(true) setShowErrorMessage(error.message as string) }) + .finally(() => { + handleClose() + }) } } const { @@ -280,16 +281,25 @@ export const PipetteWizardFlows = ( /> ) - let onExit - if (currentStep == null) return null + if (currentStep == null) { + return null + } + + const isFatalError = + (isExiting && errorMessage != null) || + maintenanceRunData?.data.status === RUN_STATUS_FAILED || + (errorMessage != null && createdMaintenanceRunId == null) + + let onExit: () => void let modalContent: JSX.Element =
    UNASSIGNED STEP
    - if (isExiting && errorMessage != null) { + // These flows often have custom error messaging, so this fallback modal is shown only in specific circumstances. + if (isFatalError) { modalContent = ( ) } else if (currentStep.section === SECTIONS.BEFORE_BEGINNING) { @@ -395,13 +405,12 @@ export const PipetteWizardFlows = ( ) } - let exitWizardButton = onExit - if (isCommandMutationLoading || isDeleteLoading) { - exitWizardButton = undefined - } else if (errorMessage != null && isExiting) { - exitWizardButton = handleClose - } else if (showConfirmExit) { - exitWizardButton = handleCleanUpAndClose + const buildWizardOnExit = (): (() => void) => { + if (isFatalError || showConfirmExit) { + return handleCleanUpAndClose + } else { + return onExit + } } const progressBarForCalError = @@ -409,13 +418,13 @@ export const PipetteWizardFlows = ( const wizardHeader = ( ) diff --git a/app/src/organisms/PipetteWizardFlows/types.ts b/app/src/organisms/PipetteWizardFlows/types.ts index 587e7d84e1f..a8785e8a31c 100644 --- a/app/src/organisms/PipetteWizardFlows/types.ts +++ b/app/src/organisms/PipetteWizardFlows/types.ts @@ -1,7 +1,7 @@ import type { SECTIONS, FLOWS } from './constants' import type { useCreateCommandMutation } from '@opentrons/react-api-client' import type { PipetteMount, CreateCommand } from '@opentrons/shared-data' -import type { AttachedPipettesFromInstrumentsQuery } from '../Devices/hooks/useAttachedPipettesFromInstrumentsQuery' +import type { AttachedPipettesFromInstrumentsQuery } from '/app/resources/instruments' export type PipetteWizardStep = | BeforeBeginningStep diff --git a/app/src/organisms/PipetteWizardFlows/utils.tsx b/app/src/organisms/PipetteWizardFlows/utils.tsx index 1466730b6ae..98a2adeabb4 100644 --- a/app/src/organisms/PipetteWizardFlows/utils.tsx +++ b/app/src/organisms/PipetteWizardFlows/utils.tsx @@ -1,30 +1,29 @@ -import * as React from 'react' import { css } from 'styled-components' import { LEFT, RIGHT } from '@opentrons/shared-data' import { SPACING } from '@opentrons/components' import { FLOWS, SECTIONS } from './constants' -import attachLeft18 from '../../assets/videos/pipette-wizard-flows/Pipette_Attach_1_8_L.webm' -import attachRight18 from '../../assets/videos/pipette-wizard-flows/Pipette_Attach_1_8_R.webm' -import detachLeft1 from '../../assets/videos/pipette-wizard-flows/Pipette_Detach_1_L.webm' -import detachRight1 from '../../assets/videos/pipette-wizard-flows/Pipette_Detach_1_R.webm' -import detachLeft8 from '../../assets/videos/pipette-wizard-flows/Pipette_Detach_8_L.webm' -import detachRight8 from '../../assets/videos/pipette-wizard-flows/Pipette_Detach_8_R.webm' -import attachProbe1 from '../../assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_1.webm' -import attachProbe8 from '../../assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_8.webm' -import detachProbe1 from '../../assets/videos/pipette-wizard-flows/Pipette_Detach_Probe_1.webm' -import detachProbe8 from '../../assets/videos/pipette-wizard-flows/Pipette_Detach_Probe_8.webm' +import attachLeft18 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach_1_8_L.webm' +import attachRight18 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach_1_8_R.webm' +import detachLeft1 from '/app/assets/videos/pipette-wizard-flows/Pipette_Detach_1_L.webm' +import detachRight1 from '/app/assets/videos/pipette-wizard-flows/Pipette_Detach_1_R.webm' +import detachLeft8 from '/app/assets/videos/pipette-wizard-flows/Pipette_Detach_8_L.webm' +import detachRight8 from '/app/assets/videos/pipette-wizard-flows/Pipette_Detach_8_R.webm' +import attachProbe1 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_1.webm' +import attachProbe8 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_8.webm' +import detachProbe1 from '/app/assets/videos/pipette-wizard-flows/Pipette_Detach_Probe_1.webm' +import detachProbe8 from '/app/assets/videos/pipette-wizard-flows/Pipette_Detach_Probe_8.webm' -import attach96 from '../../assets/videos/pipette-wizard-flows/Pipette_Attach_96.webm' -import attachPlate96 from '../../assets/videos/pipette-wizard-flows/Pipette_Attach_Plate_96.webm' -import detach96 from '../../assets/videos/pipette-wizard-flows/Pipette_Detach_96.webm' -import detachPlate96 from '../../assets/videos/pipette-wizard-flows/Pipette_Detach_Plate_96.webm' -import zAxisAttach96 from '../../assets/videos/pipette-wizard-flows/Pipette_Zaxis_Attach_96.webm' -import zAxisDetach96 from '../../assets/videos/pipette-wizard-flows/Pipette_Zaxis_Detach_96.webm' -import attachProbe96 from '../../assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_96.webm' -import detachProbe96 from '../../assets/videos/pipette-wizard-flows/Pipette_Detach_Probe_96.webm' +import attach96 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach_96.webm' +import attachPlate96 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach_Plate_96.webm' +import detach96 from '/app/assets/videos/pipette-wizard-flows/Pipette_Detach_96.webm' +import detachPlate96 from '/app/assets/videos/pipette-wizard-flows/Pipette_Detach_Plate_96.webm' +import zAxisAttach96 from '/app/assets/videos/pipette-wizard-flows/Pipette_Zaxis_Attach_96.webm' +import zAxisDetach96 from '/app/assets/videos/pipette-wizard-flows/Pipette_Zaxis_Detach_96.webm' +import attachProbe96 from '/app/assets/videos/pipette-wizard-flows/Pipette_Attach_Probe_96.webm' +import detachProbe96 from '/app/assets/videos/pipette-wizard-flows/Pipette_Detach_Probe_96.webm' -import type { AttachedPipettesFromInstrumentsQuery } from '../Devices/hooks' +import type { AttachedPipettesFromInstrumentsQuery } from '/app/resources/instruments' import type { PipetteWizardFlow, PipetteWizardStep } from './types' export function getIsGantryEmpty( diff --git a/app/src/organisms/ProtocolAnalysisFailure/index.tsx b/app/src/organisms/ProtocolAnalysisFailure/index.tsx deleted file mode 100644 index 378996edab3..00000000000 --- a/app/src/organisms/ProtocolAnalysisFailure/index.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useDispatch } from 'react-redux' -import { useTranslation, Trans } from 'react-i18next' -import { css } from 'styled-components' - -import { - ALIGN_CENTER, - Btn, - Flex, - JUSTIFY_FLEX_END, - Modal, - JUSTIFY_SPACE_BETWEEN, - PrimaryButton, - SPACING, - LegacyStyledText, - TYPOGRAPHY, - WRAP_REVERSE, -} from '@opentrons/components' - -import { analyzeProtocol } from '../../redux/protocol-storage' -import { Banner } from '../../atoms/Banner' -import { getTopPortalEl } from '../../App/portal' - -import type { Dispatch } from '../../redux/types' -interface ProtocolAnalysisFailureProps { - errors: string[] - protocolKey: string -} - -export function ProtocolAnalysisFailure( - props: ProtocolAnalysisFailureProps -): JSX.Element { - const { errors, protocolKey } = props - const { t } = useTranslation(['protocol_list', 'shared']) - const dispatch = useDispatch() - const [showErrorDetails, setShowErrorDetails] = React.useState(false) - - const handleClickShowDetails: React.MouseEventHandler = e => { - e.preventDefault() - e.stopPropagation() - setShowErrorDetails(true) - } - const handleClickHideDetails: React.MouseEventHandler = e => { - e.preventDefault() - e.stopPropagation() - setShowErrorDetails(false) - } - const handleClickReanalyze: React.MouseEventHandler = e => { - e.preventDefault() - e.stopPropagation() - dispatch(analyzeProtocol(protocolKey)) - } - return ( - - - - {t('protocol_analysis_failure')} - - - - ), - analysisLink: ( - - ), - }} - /> - - - {showErrorDetails - ? createPortal( - - - {errors.map((error, index) => ( - - {error} - - ))} - - - - {t('shared:close')} - - - , - getTopPortalEl() - ) - : null} - - ) -} - -const SCROLL_LONG = css` - overflow: scroll; - width: inherit; - max-height: 11.75rem; -` diff --git a/app/src/organisms/ProtocolDetails/ProtocolParameters/index.tsx b/app/src/organisms/ProtocolDetails/ProtocolParameters/index.tsx deleted file mode 100644 index 61687d995fd..00000000000 --- a/app/src/organisms/ProtocolDetails/ProtocolParameters/index.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' - -import { - DIRECTION_COLUMN, - Flex, - InfoScreen, - ParametersTable, - SPACING, - StyledText, -} from '@opentrons/components' -import { Banner } from '../../../atoms/Banner' - -import type { RunTimeParameter } from '@opentrons/shared-data' - -interface ProtocolParametersProps { - runTimeParameters: RunTimeParameter[] -} - -export function ProtocolParameters({ - runTimeParameters, -}: ProtocolParametersProps): JSX.Element { - const { t } = useTranslation('protocol_details') - - return ( - - {runTimeParameters.length > 0 ? ( - - - - - {t('listed_values_are_view_only')} - - - {t('start_setup_customize_values')} - - - - - - ) : ( - - )} - - ) -} diff --git a/app/src/organisms/ProtocolDetails/__tests__/ProtocolDetails.test.tsx b/app/src/organisms/ProtocolDetails/__tests__/ProtocolDetails.test.tsx deleted file mode 100644 index 140cf3f6373..00000000000 --- a/app/src/organisms/ProtocolDetails/__tests__/ProtocolDetails.test.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import * as React from 'react' -import { act, screen, waitFor } from '@testing-library/react' -import { MemoryRouter } from 'react-router-dom' -import { describe, it, beforeEach, vi, expect, afterEach } from 'vitest' - -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { ChooseRobotToRunProtocolSlideout } from '../../../organisms/ChooseRobotToRunProtocolSlideout' -import { - useTrackEvent, - ANALYTICS_PROTOCOL_PROCEED_TO_RUN, -} from '../../../redux/analytics' -import { getValidCustomLabwareFiles } from '../../../redux/custom-labware/selectors' -import { - getConnectableRobots, - getReachableRobots, - getScanning, - getUnreachableRobots, -} from '../../../redux/discovery' -import { getIsProtocolAnalysisInProgress } from '../../../redux/protocol-storage/selectors' -import { - mockConnectableRobot, - mockReachableRobot, - mockUnreachableRobot, -} from '../../../redux/discovery/__fixtures__' -import { storedProtocolData } from '../../../redux/protocol-storage/__fixtures__' -import { ProtocolDetails } from '..' - -import type { Mock } from 'vitest' -import type { ProtocolAnalysisOutput } from '@opentrons/shared-data' - -vi.mock('../../../redux/analytics') -vi.mock('../../../redux/custom-labware/selectors') -vi.mock('../../../redux/discovery/selectors') -vi.mock('../../../redux/protocol-storage/selectors') -vi.mock('../../../organisms/ChooseRobotToRunProtocolSlideout') -vi.mock('../../../organisms/SendProtocolToFlexSlideout') - -const render = ( - props: Partial> = {} -) => { - return renderWithProviders( - - - , - { - i18nInstance: i18n, - } - )[0] -} - -const protocolType = 'json' -const schemaVersion = 6 -const author = 'Otie' -const createdAt = '2022-05-04T18:33:48.916159+00:00' -const description = 'fake protocol description' - -const mockMostRecentAnalysis: ProtocolAnalysisOutput = storedProtocolData.mostRecentAnalysis as ProtocolAnalysisOutput - -let mockTrackEvent: Mock - -describe('ProtocolDetails', () => { - beforeEach(() => { - mockTrackEvent = vi.fn() - vi.mocked(getValidCustomLabwareFiles).mockReturnValue([]) - vi.mocked(getConnectableRobots).mockReturnValue([mockConnectableRobot]) - vi.mocked(getUnreachableRobots).mockReturnValue([mockUnreachableRobot]) - vi.mocked(getReachableRobots).mockReturnValue([mockReachableRobot]) - vi.mocked(getScanning).mockReturnValue(false) - - vi.mocked(ChooseRobotToRunProtocolSlideout).mockReturnValue( -
    close ChooseRobotToRunProtocolSlideout
    - ) - vi.mocked(getIsProtocolAnalysisInProgress).mockReturnValue(false) - vi.mocked(useTrackEvent).mockReturnValue(mockTrackEvent) - }) - afterEach(() => { - vi.resetAllMocks() - }) - - it('renders protocol title as display name if present in metadata', () => { - const protocolName = 'fakeProtocolDisplayName' - render({ - mostRecentAnalysis: { - ...mockMostRecentAnalysis, - createdAt, - metadata: { - ...mockMostRecentAnalysis.metadata, - protocolName, - }, - config: { - ...mockMostRecentAnalysis.config, - protocolType, - schemaVersion, - }, - }, - }) - screen.getByText('fakeProtocolDisplayName') - }) - it('renders protocol title as file name if not in metadata', () => { - render({ - mostRecentAnalysis: { - ...mockMostRecentAnalysis, - createdAt, - metadata: { - ...mockMostRecentAnalysis.metadata, - author, - }, - config: { - ...mockMostRecentAnalysis.config, - protocolType, - schemaVersion, - }, - }, - }) - expect(screen.getByText('fakeSrcFileName')).toBeInTheDocument() - }) - it('renders deck view section', () => { - render({ - mostRecentAnalysis: { - ...mockMostRecentAnalysis, - createdAt, - metadata: { - ...mockMostRecentAnalysis.metadata, - }, - config: { - ...mockMostRecentAnalysis.config, - protocolType, - schemaVersion, - }, - }, - }) - expect( - screen.getByRole('heading', { name: 'Deck View' }) - ).toBeInTheDocument() - screen.getByText('close ChooseRobotToRunProtocolSlideout') - }) - it('opens choose robot to run protocol slideout when Start setup button is clicked', async () => { - vi.mocked(ChooseRobotToRunProtocolSlideout).mockReturnValue( -
    open ChooseRobotToRunProtocolSlideout
    - ) - render({ - mostRecentAnalysis: { - ...mockMostRecentAnalysis, - createdAt, - metadata: { - ...mockMostRecentAnalysis.metadata, - }, - config: { - ...mockMostRecentAnalysis.config, - protocolType, - schemaVersion, - }, - }, - }) - const runProtocolButton = screen.getByRole('button', { - name: 'Start setup', - }) - act(() => { - runProtocolButton.click() - }) - await waitFor(() => { - expect(mockTrackEvent).toHaveBeenCalledWith({ - name: ANALYTICS_PROTOCOL_PROCEED_TO_RUN, - properties: { sourceLocation: 'ProtocolsDetail' }, - }) - }) - screen.getByText('open ChooseRobotToRunProtocolSlideout') - }) - it('renders the protocol creation method', () => { - render({ - mostRecentAnalysis: { - ...mockMostRecentAnalysis, - createdAt, - metadata: { - ...mockMostRecentAnalysis.metadata, - }, - config: { - ...mockMostRecentAnalysis.config, - protocolType, - schemaVersion, - }, - }, - }) - screen.getByRole('heading', { name: 'creation method' }) - screen.getByText('Protocol Designer 6.0') - }) - it('renders the last analyzed date', () => { - render({ - mostRecentAnalysis: { - ...mockMostRecentAnalysis, - createdAt, - metadata: { - ...mockMostRecentAnalysis.metadata, - }, - config: { - ...mockMostRecentAnalysis.config, - protocolType, - schemaVersion, - }, - }, - }) - screen.getByRole('heading', { name: 'last analyzed' }) - }) - it('renders the protocol description', () => { - render({ - mostRecentAnalysis: { - ...mockMostRecentAnalysis, - createdAt, - metadata: { - ...mockMostRecentAnalysis.metadata, - description, - }, - config: { - ...mockMostRecentAnalysis.config, - protocolType, - schemaVersion, - }, - }, - }) - screen.getByRole('heading', { name: 'description' }) - screen.getByText('fake protocol description') - }) -}) diff --git a/app/src/organisms/ProtocolDetails/index.tsx b/app/src/organisms/ProtocolDetails/index.tsx deleted file mode 100644 index d9579f68df6..00000000000 --- a/app/src/organisms/ProtocolDetails/index.tsx +++ /dev/null @@ -1,718 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import map from 'lodash/map' -import omit from 'lodash/omit' -import isEmpty from 'lodash/isEmpty' -import startCase from 'lodash/startCase' -import { format } from 'date-fns' -import { css } from 'styled-components' -import { useTranslation } from 'react-i18next' -import { useDispatch, useSelector } from 'react-redux' -import { ErrorBoundary } from 'react-error-boundary' - -import { - ALIGN_CENTER, - BORDERS, - Box, - Btn, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - DISPLAY_FLEX, - Flex, - Icon, - JUSTIFY_CENTER, - JUSTIFY_SPACE_BETWEEN, - Link, - OVERFLOW_WRAP_ANYWHERE, - POSITION_RELATIVE, - PrimaryButton, - ProtocolDeck, - Tabs, - SIZE_1, - SIZE_5, - Modal, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' -import { - MAGNETIC_BLOCK_TYPE, - getGripperDisplayName, - getModuleType, - getSimplestDeckConfigForProtocol, - parseInitialLoadedLabwareByAdapter, - parseInitialLoadedLabwareByModuleId, - parseInitialLoadedLabwareBySlot, - parseInitialLoadedModulesBySlot, - parseInitialPipetteNamesByMount, -} from '@opentrons/shared-data' - -import { getTopPortalEl } from '../../App/portal' -import { Divider } from '../../atoms/structure' -import { - useTrackEvent, - ANALYTICS_PROTOCOL_PROCEED_TO_RUN, -} from '../../redux/analytics' -import { - getIsProtocolAnalysisInProgress, - analyzeProtocol, -} from '../../redux/protocol-storage' -import { useFeatureFlag } from '../../redux/config' -import { ChooseRobotToRunProtocolSlideout } from '../ChooseRobotToRunProtocolSlideout' -import { SendProtocolToFlexSlideout } from '../SendProtocolToFlexSlideout' -import { ProtocolAnalysisFailure } from '../ProtocolAnalysisFailure' -import { ProtocolStatusBanner } from '../ProtocolStatusBanner' -import { - getAnalysisStatus, - getProtocolDisplayName, -} from '../ProtocolsLanding/utils' -import { getProtocolUsesGripper } from '../ProtocolSetupInstruments/utils' -import { ProtocolOverflowMenu } from '../ProtocolsLanding/ProtocolOverflowMenu' -import { ProtocolStats } from './ProtocolStats' -import { ProtocolLabwareDetails } from './ProtocolLabwareDetails' -import { ProtocolLiquidsDetails } from './ProtocolLiquidsDetails' -import { RobotConfigurationDetails } from './RobotConfigurationDetails' -import { ProtocolParameters } from './ProtocolParameters' -import { AnnotatedSteps } from './AnnotatedSteps' - -import type { JsonConfig, PythonConfig } from '@opentrons/shared-data' -import type { StoredProtocolData } from '../../redux/protocol-storage' -import type { State, Dispatch } from '../../redux/types' - -const GRID_STYLE = css` - display: grid; - width: 100%; - grid-template-columns: 26.6% 26.6% 26.6% 20.2%; -` - -const ZOOM_ICON_STYLE = css` - border-radius: ${BORDERS.borderRadius4}; - &:hover { - background: ${COLORS.grey30}; - } - &:active { - background: ${COLORS.grey35}; - } - &:disabled { - background: ${COLORS.white}; - } - &:focus-visible { - background: ${COLORS.grey35}; - } -` - -interface Metadata { - [key: string]: any -} - -interface MetadataDetailsProps { - description: string - metadata: Metadata - protocolType: string -} - -function MetadataDetails({ - description, - metadata, - protocolType, -}: MetadataDetailsProps): JSX.Element { - if (protocolType === 'json') { - return {description} - } else { - const filteredMetaData = Object.entries( - omit(metadata, ['description', 'protocolName', 'author', 'apiLevel']) - ).map(item => ({ label: item[0], value: item[1] })) - - return ( - - {description} - {filteredMetaData.map((item, index) => { - return ( - - - {startCase(item.label)} - - {item.value} - - ) - })} - - ) - } -} - -interface ReadMoreContentProps { - metadata: Metadata - protocolType: 'json' | 'python' -} - -const ReadMoreContent = (props: ReadMoreContentProps): JSX.Element => { - const { metadata, protocolType } = props - const { t, i18n } = useTranslation('protocol_details') - const [isReadMore, setIsReadMore] = React.useState(true) - - const description = isEmpty(metadata.description) - ? t('shared:no_data') - : metadata.description - - return ( - - {isReadMore ? ( - {description.slice(0, 160)} - ) : ( - - )} - {(description.length > 160 || protocolType === 'python') && ( - { - setIsReadMore(!isReadMore) - }} - > - {isReadMore - ? i18n.format(t('read_more'), 'capitalize') - : i18n.format(t('read_less'), 'capitalize')} - - )} - - ) -} - -interface ProtocolDetailsProps extends StoredProtocolData {} - -export function ProtocolDetails( - props: ProtocolDetailsProps -): JSX.Element | null { - const trackEvent = useTrackEvent() - const dispatch = useDispatch() - const { protocolKey, srcFileNames, mostRecentAnalysis, modified } = props - const { t, i18n } = useTranslation(['protocol_details', 'shared']) - const enableProtocolStats = useFeatureFlag('protocolStats') - const enableProtocolTimeline = useFeatureFlag('protocolTimeline') - const runTimeParameters = mostRecentAnalysis?.runTimeParameters ?? [] - const hasRunTimeParameters = runTimeParameters.length > 0 - const [currentTab, setCurrentTab] = React.useState< - 'robot_config' | 'labware' | 'liquids' | 'stats' | 'parameters' | 'timeline' - >(hasRunTimeParameters ? 'parameters' : 'robot_config') - const [ - showChooseRobotToRunProtocolSlideout, - setShowChooseRobotToRunProtocolSlideout, - ] = React.useState(false) - const [ - showSendProtocolToFlexSlideout, - setShowSendProtocolToFlexSlideout, - ] = React.useState(false) - const [showDeckViewModal, setShowDeckViewModal] = React.useState(false) - - const isAnalyzing = useSelector((state: State) => - getIsProtocolAnalysisInProgress(state, protocolKey) - ) - - const analysisStatus = getAnalysisStatus(isAnalyzing, mostRecentAnalysis) - - if (analysisStatus === 'stale') { - dispatch(analyzeProtocol(protocolKey)) - } else if (analysisStatus === 'missing') return null - - const { left: leftMountPipetteName, right: rightMountPipetteName } = - mostRecentAnalysis != null - ? parseInitialPipetteNamesByMount(mostRecentAnalysis.commands) - : { left: null, right: null } - - const requiredExtensionInstrumentName = - mostRecentAnalysis != null && getProtocolUsesGripper(mostRecentAnalysis) - ? getGripperDisplayName('gripperV1') - : null - - const requiredModuleDetails = - mostRecentAnalysis?.commands != null - ? map( - parseInitialLoadedModulesBySlot(mostRecentAnalysis.commands) - ).filter( - loadedModule => - // filter out magnetic block which is already handled by the required fixture details - getModuleType(loadedModule.params.model) !== MAGNETIC_BLOCK_TYPE - ) - : [] - - const requiredFixtureDetails = getSimplestDeckConfigForProtocol( - analysisStatus !== 'stale' && analysisStatus !== 'loading' - ? mostRecentAnalysis - : null - ) - - const requiredLabwareDetails = - mostRecentAnalysis != null - ? map({ - ...parseInitialLoadedLabwareByModuleId( - mostRecentAnalysis.commands != null - ? mostRecentAnalysis.commands - : [] - ), - ...parseInitialLoadedLabwareBySlot( - mostRecentAnalysis.commands != null - ? mostRecentAnalysis.commands - : [] - ), - ...parseInitialLoadedLabwareByAdapter( - mostRecentAnalysis.commands != null - ? mostRecentAnalysis.commands - : [] - ), - }).filter( - labware => labware.result?.definition?.parameters?.format !== 'trash' - ) - : [] - - const protocolDisplayName = getProtocolDisplayName( - protocolKey, - srcFileNames, - mostRecentAnalysis - ) - - const getCreationMethod = (config: JsonConfig | PythonConfig): string => { - if (config.protocolType === 'json') { - return t('protocol_designer_version', { - version: config.schemaVersion.toFixed(1), - }) - } else { - return t('python_api_version', { - version: - config.apiVersion != null ? config.apiVersion?.join('.') : null, - }) - } - } - - const creationMethod = - mostRecentAnalysis != null - ? getCreationMethod(mostRecentAnalysis.config) ?? t('shared:no_data') - : t('shared:no_data') - const author = - mostRecentAnalysis != null - ? mostRecentAnalysis?.metadata?.author ?? t('shared:no_data') - : t('shared:no_data') - const lastAnalyzed = - mostRecentAnalysis?.createdAt != null - ? format(new Date(mostRecentAnalysis.createdAt), 'M/d/yy HH:mm') - : t('shared:no_data') - const robotType = mostRecentAnalysis?.robotType ?? null - - const contentsByTabName = { - labware: ( - - ), - robot_config: ( - - ), - liquids: ( - - ), - stats: enableProtocolStats ? ( - - ) : null, - timeline: - enableProtocolTimeline && mostRecentAnalysis != null ? ( - - ) : null, - parameters: , - } - - const deckMap = - - const deckViewByAnalysisStatus = { - stale: , - missing: , - loading: , - error: , - parameterRequired: , - complete: ( - - {deckMap} - - ), - } - - const handleRunProtocolButtonClick = (): void => { - trackEvent({ - name: ANALYTICS_PROTOCOL_PROCEED_TO_RUN, - properties: { sourceLocation: 'ProtocolsDetail' }, - }) - setShowChooseRobotToRunProtocolSlideout(true) - } - - const UNKNOWN_ATTACHMENT_ERROR = `${protocolDisplayName} protocol uses - instruments or modules from a future version of Opentrons software. Please update - the app to the most recent version to run this protocol.` - - const UnknownAttachmentError = ( - - ) - - return ( - <> - {showDeckViewModal - ? createPortal( - { - setShowDeckViewModal(false) - }} - > - {deckMap} - , - getTopPortalEl() - ) - : null} - - - { - setShowChooseRobotToRunProtocolSlideout(false) - }} - showSlideout={showChooseRobotToRunProtocolSlideout} - storedProtocolData={props} - /> - { - setShowSendProtocolToFlexSlideout(false) - }} - storedProtocolData={props} - /> - - - - {analysisStatus !== 'loading' && - mostRecentAnalysis?.result === 'parameter-value-required' ? ( - - ) : null} - {mostRecentAnalysis != null && analysisStatus === 'error' ? ( - e.detail)} - /> - ) : null} - - {protocolDisplayName} - - - - - {t('creation_method')} - - - {analysisStatus === 'loading' - ? t('shared:loading') - : creationMethod} - - - - - {t('last_updated')} - - - {analysisStatus === 'loading' - ? t('shared:loading') - : format(new Date(modified), 'M/d/yy HH:mm')} - - - - - {t('last_analyzed')} - - - {analysisStatus === 'loading' - ? t('shared:loading') - : lastAnalyzed} - - - - { - handleRunProtocolButtonClick() - }} - data-testid="ProtocolDetails_runProtocol" - disabled={analysisStatus === 'loading'} - > - {t('start_setup')} - - - - - - - - {t('org_or_author')} - - - {analysisStatus === 'loading' - ? t('shared:loading') - : author} - - - - - {t('description')} - - {analysisStatus === 'loading' ? ( - - {t('shared:loading')} - - ) : null} - {mostRecentAnalysis != null ? ( - - ) : null} - - - - - { - setShowChooseRobotToRunProtocolSlideout(true) - }} - handleSendProtocolToFlex={() => { - setShowSendProtocolToFlexSlideout(true) - }} - storedProtocolData={props} - data-testid="ProtocolDetails_overFlowMenu" - /> - - - - - - - {t('deck_view')} - - { - setShowDeckViewModal(true) - }} - > - - - - - {deckViewByAnalysisStatus[analysisStatus]} - - - - - - {mostRecentAnalysis != null && ( - { - setCurrentTab('parameters') - }, - }, - ]} - /> - )} - { - setCurrentTab('robot_config') - }, - }, - { - text: i18n.format(t('labware'), 'capitalize'), - isActive: currentTab === 'labware', - disabled: false, - onClick: () => { - setCurrentTab('labware') - }, - }, - ]} - /> - {mostRecentAnalysis != null && ( - { - setCurrentTab('liquids') - }, - }, - ]} - /> - )} - {enableProtocolStats && mostRecentAnalysis != null && ( - { - setCurrentTab('stats') - }, - }, - ]} - /> - )} - {enableProtocolTimeline && mostRecentAnalysis != null && ( - { - setCurrentTab('timeline') - }, - }, - ]} - /> - )} - - - {contentsByTabName[currentTab]} - - - - - - - ) -} diff --git a/app/src/organisms/ProtocolSetupDeckConfiguration/index.tsx b/app/src/organisms/ProtocolSetupDeckConfiguration/index.tsx deleted file mode 100644 index 58bda580d75..00000000000 --- a/app/src/organisms/ProtocolSetupDeckConfiguration/index.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' - -import { - BaseDeck, - DIRECTION_COLUMN, - Flex, - JUSTIFY_CENTER, - SPACING, -} from '@opentrons/components' -import { - FLEX_ROBOT_TYPE, - FLEX_SINGLE_SLOT_BY_CUTOUT_ID, - MAGNETIC_BLOCK_V1_FIXTURE, - MODULE_FIXTURES_BY_MODEL, - STAGING_AREA_SLOT_WITH_MAGNETIC_BLOCK_V1_FIXTURE, - THERMOCYCLER_V2_REAR_FIXTURE, - getSimplestDeckConfigForProtocol, -} from '@opentrons/shared-data' - -import { ChildNavigation } from '../ChildNavigation' -import { AddFixtureModal } from '../DeviceDetailsDeckConfiguration/AddFixtureModal' -import { DeckConfigurationDiscardChangesModal } from '../DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal' -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { getTopPortalEl } from '../../App/portal' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' - -import type { - CutoutFixtureId, - CutoutId, - ModuleModel, -} from '@opentrons/shared-data' -import type { ModuleOnDeck } from '@opentrons/components' -import type { SetupScreens } from '../../pages/ProtocolSetup' - -interface ProtocolSetupDeckConfigurationProps { - cutoutId: CutoutId | null - runId: string - setSetupScreen: React.Dispatch> - providedFixtureOptions: CutoutFixtureId[] -} - -export function ProtocolSetupDeckConfiguration({ - cutoutId, - runId, - setSetupScreen, - providedFixtureOptions, -}: ProtocolSetupDeckConfigurationProps): JSX.Element { - const { i18n, t } = useTranslation([ - 'protocol_setup', - 'devices_landing', - 'shared', - ]) - - const [ - showConfigurationModal, - setShowConfigurationModal, - ] = React.useState(true) - const [ - showDiscardChangeModal, - setShowDiscardChangeModal, - ] = React.useState(false) - - const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) - const deckConfig = useNotifyDeckConfigurationQuery()?.data ?? [] - - const simplestDeckConfig = getSimplestDeckConfigForProtocol( - mostRecentAnalysis - ).map(({ cutoutId, cutoutFixtureId }) => ({ cutoutId, cutoutFixtureId })) - - const targetCutoutConfig = simplestDeckConfig.find( - deck => deck.cutoutId === cutoutId - ) - - const mergedDeckConfig = deckConfig.map(config => - targetCutoutConfig != null && - config.cutoutId === targetCutoutConfig.cutoutId - ? targetCutoutConfig - : config - ) - - const modulesOnDeck = mergedDeckConfig.reduce( - (acc, cutoutConfig) => { - const matchingFixtureIdsAndModel = Object.entries( - MODULE_FIXTURES_BY_MODEL - ).find(([_moduleModel, moduleFixtureIds]) => - moduleFixtureIds.includes(cutoutConfig.cutoutFixtureId) - ) - if ( - matchingFixtureIdsAndModel != null && - cutoutConfig.cutoutFixtureId !== THERMOCYCLER_V2_REAR_FIXTURE - ) { - const [matchingModel] = matchingFixtureIdsAndModel - return [ - ...acc, - { - moduleModel: matchingModel as ModuleModel, - moduleLocation: { - slotName: FLEX_SINGLE_SLOT_BY_CUTOUT_ID[cutoutConfig.cutoutId], - }, - }, - ] - } else if ( - cutoutConfig.cutoutFixtureId === - STAGING_AREA_SLOT_WITH_MAGNETIC_BLOCK_V1_FIXTURE - ) { - return [ - ...acc, - { - moduleModel: MAGNETIC_BLOCK_V1_FIXTURE, - moduleLocation: { - slotName: FLEX_SINGLE_SLOT_BY_CUTOUT_ID[cutoutConfig.cutoutId], - }, - }, - ] - } - return acc - }, - [] - ) - - const handleClickConfirm = (): void => { - setSetupScreen('modules') - } - - return ( - <> - {createPortal( - <> - {showDiscardChangeModal ? ( - - ) : null} - {showConfigurationModal && cutoutId != null ? ( - { - setShowConfigurationModal(false) - }} - providedFixtureOptions={providedFixtureOptions} - isOnDevice - /> - ) : null} - , - getTopPortalEl() - )} - - - - - - - - ) -} diff --git a/app/src/organisms/ProtocolSetupInstruments/index.tsx b/app/src/organisms/ProtocolSetupInstruments/index.tsx deleted file mode 100644 index 36e0b4afb37..00000000000 --- a/app/src/organisms/ProtocolSetupInstruments/index.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import * as React from 'react' -import styled from 'styled-components' -import { useTranslation } from 'react-i18next' -import { - COLORS, - ALIGN_CENTER, - DIRECTION_COLUMN, - Flex, - JUSTIFY_SPACE_BETWEEN, - SPACING, - TYPOGRAPHY, -} from '@opentrons/components' -import { useInstrumentsQuery } from '@opentrons/react-api-client' -import { ODDBackButton } from '../../molecules/ODDBackButton' -import { PipetteRecalibrationODDWarning } from '../../pages/InstrumentsDashboard/PipetteRecalibrationODDWarning' -import { getShowPipetteCalibrationWarning } from '../Devices/utils' -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { ProtocolInstrumentMountItem } from '../InstrumentMountItem' - -import type { GripperData, PipetteData } from '@opentrons/api-client' -import type { GripperModel } from '@opentrons/shared-data' -import type { SetupScreens } from '../../pages/ProtocolSetup' -import { isGripperInCommands } from '../../resources/protocols/utils' - -export interface ProtocolSetupInstrumentsProps { - runId: string - setSetupScreen: React.Dispatch> -} - -export function ProtocolSetupInstruments({ - runId, - setSetupScreen, -}: ProtocolSetupInstrumentsProps): JSX.Element { - const { t, i18n } = useTranslation('protocol_setup') - const { data: attachedInstruments, refetch } = useInstrumentsQuery() - const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) - - const usesGripper = - mostRecentAnalysis != null - ? isGripperInCommands(mostRecentAnalysis?.commands ?? []) - : false - const attachedGripperMatch = usesGripper - ? (attachedInstruments?.data ?? []).find( - (i): i is GripperData => i.instrumentType === 'gripper' && i.ok - ) ?? null - : null - - return ( - - { - setSetupScreen('prepare to run') - }} - /> - {getShowPipetteCalibrationWarning(attachedInstruments) && ( - - - - )} - - {t('location')} - - {i18n.format(t('calibration_status'), 'sentenceCase')} - - - {(mostRecentAnalysis?.pipettes ?? []).map(loadedPipette => { - const attachedPipetteMatch = - (attachedInstruments?.data ?? []).find( - (i): i is PipetteData => - i.instrumentType === 'pipette' && - i.ok && - i.mount === loadedPipette.mount && - i.instrumentName === loadedPipette.pipetteName - ) ?? null - return ( - - ) - })} - {usesGripper ? ( - - ) : null} - - ) -} - -const ColumnLabel = styled.p` - flex: 1; - font-weight: ${TYPOGRAPHY.fontWeightSemiBold}; - font-size: ${TYPOGRAPHY.fontSize22}; - line-height: ${TYPOGRAPHY.lineHeight28}; - color: ${COLORS.grey60}; -` diff --git a/app/src/organisms/ProtocolSetupInstruments/utils.ts b/app/src/organisms/ProtocolSetupInstruments/utils.ts deleted file mode 100644 index 1ce77275e74..00000000000 --- a/app/src/organisms/ProtocolSetupInstruments/utils.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { - CompletedProtocolAnalysis, - LoadedPipette, - ProtocolAnalysisOutput, -} from '@opentrons/shared-data' -import type { - GripperData, - Instruments, - PipetteData, -} from '@opentrons/api-client' - -export function getProtocolUsesGripper( - analysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput -): boolean { - return ( - analysis?.commands.some( - c => - c.commandType === 'moveLabware' && c.params.strategy === 'usingGripper' - ) ?? false - ) -} -export function getAttachedGripper( - attachedInstruments: Instruments -): GripperData | null { - return ( - (attachedInstruments?.data ?? []).find( - (i): i is GripperData => - i.instrumentType === 'gripper' && - i.ok && - i.data.calibratedOffset != null - ) ?? null - ) -} - -export function getPipetteMatch( - loadedPipette: LoadedPipette, - attachedInstruments: Instruments -): PipetteData | null { - return ( - (attachedInstruments?.data ?? []).find( - (i): i is PipetteData => - i.instrumentType === 'pipette' && - i.ok && - i.mount === loadedPipette.mount && - i.instrumentName === loadedPipette.pipetteName - ) ?? null - ) -} - -export function getAreInstrumentsReady( - analysis: CompletedProtocolAnalysis, - attachedInstruments: Instruments -): boolean { - const speccedPipettes = analysis?.pipettes ?? [] - const allSpeccedPipettesReady = speccedPipettes.every(loadedPipette => { - const attachedPipetteMatch = getPipetteMatch( - loadedPipette, - attachedInstruments - ) - return attachedPipetteMatch?.data.calibratedOffset?.last_modified != null - }) - const isExtensionMountReady = getProtocolUsesGripper(analysis) - ? getAttachedGripper(attachedInstruments)?.data.calibratedOffset - ?.last_modified != null - : true - - return allSpeccedPipettesReady && isExtensionMountReady -} - -export function getIncompleteInstrumentCount( - analysis: CompletedProtocolAnalysis, - attachedInstruments: Instruments -): number { - const speccedPipettes = analysis?.pipettes ?? [] - - const incompleteInstrumentCount = speccedPipettes.filter(loadedPipette => { - const attachedPipetteMatch = getPipetteMatch( - loadedPipette, - attachedInstruments - ) - return attachedPipetteMatch?.data.calibratedOffset?.last_modified == null - }).length - - const isExtensionMountReady = getProtocolUsesGripper(analysis) - ? getAttachedGripper(attachedInstruments)?.data.calibratedOffset - ?.last_modified != null - : true - - return incompleteInstrumentCount + (isExtensionMountReady ? 0 : 1) -} diff --git a/app/src/organisms/ProtocolSetupLabware/LabwareMapView.tsx b/app/src/organisms/ProtocolSetupLabware/LabwareMapView.tsx deleted file mode 100644 index cdeabfa2a3e..00000000000 --- a/app/src/organisms/ProtocolSetupLabware/LabwareMapView.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import * as React from 'react' -import map from 'lodash/map' -import { BaseDeck, Flex } from '@opentrons/components' -import { - FLEX_ROBOT_TYPE, - getSimplestDeckConfigForProtocol, - THERMOCYCLER_MODULE_V1, -} from '@opentrons/shared-data' - -import { getStandardDeckViewLayerBlockList } from '../Devices/ProtocolRun/utils/getStandardDeckViewLayerBlockList' -import { getLabwareRenderInfo } from '../Devices/ProtocolRun/utils/getLabwareRenderInfo' - -import type { - CompletedProtocolAnalysis, - DeckDefinition, - LabwareDefinition2, - LoadedLabwareByAdapter, -} from '@opentrons/shared-data' -import type { AttachedProtocolModuleMatch } from '../ProtocolSetupModulesAndDeck/utils' - -interface LabwareMapViewProps { - attachedProtocolModuleMatches: AttachedProtocolModuleMatch[] - handleLabwareClick: ( - labwareDef: LabwareDefinition2, - labwareId: string - ) => void - initialLoadedLabwareByAdapter: LoadedLabwareByAdapter - deckDef: DeckDefinition - mostRecentAnalysis: CompletedProtocolAnalysis | null -} - -export function LabwareMapView(props: LabwareMapViewProps): JSX.Element { - const { - handleLabwareClick, - attachedProtocolModuleMatches, - initialLoadedLabwareByAdapter, - deckDef, - mostRecentAnalysis, - } = props - const deckConfig = getSimplestDeckConfigForProtocol(mostRecentAnalysis) - const labwareRenderInfo = - mostRecentAnalysis != null - ? getLabwareRenderInfo(mostRecentAnalysis, deckDef) - : {} - - const modulesOnDeck = attachedProtocolModuleMatches.map(module => { - const { moduleDef, nestedLabwareDef, nestedLabwareId, slotName } = module - const labwareInAdapterInMod = - nestedLabwareId != null - ? initialLoadedLabwareByAdapter[nestedLabwareId] - : null - // only rendering the labware on top most layer so - // either the adapter or the labware are rendered but not both - const topLabwareDefinition = - labwareInAdapterInMod?.result?.definition ?? nestedLabwareDef - const topLabwareId = - labwareInAdapterInMod?.result?.labwareId ?? nestedLabwareId - - return { - moduleModel: moduleDef.model, - moduleLocation: { slotName }, - innerProps: - moduleDef.model === THERMOCYCLER_MODULE_V1 - ? { lidMotorState: 'open' } - : {}, - nestedLabwareDef: topLabwareDefinition, - onLabwareClick: - topLabwareDefinition != null && topLabwareId != null - ? () => { - handleLabwareClick(topLabwareDefinition, topLabwareId) - } - : undefined, - highlightLabware: true, - moduleChildren: null, - stacked: topLabwareDefinition != null && topLabwareId != null, - } - }) - - const labwareLocations = map( - labwareRenderInfo, - ({ labwareDef, slotName }, labwareId) => { - const labwareInAdapter = initialLoadedLabwareByAdapter[labwareId] - // only rendering the labware on top most layer so - // either the adapter or the labware are rendered but not both - const topLabwareDefinition = - labwareInAdapter?.result?.definition ?? labwareDef - const topLabwareId = labwareInAdapter?.result?.labwareId ?? labwareId - const isLabwareInStack = - topLabwareDefinition != null && - topLabwareId != null && - labwareInAdapter != null - - return { - labwareLocation: { slotName }, - definition: topLabwareDefinition, - topLabwareId, - onLabwareClick: () => { - handleLabwareClick(topLabwareDefinition, topLabwareId) - }, - labwareChildren: null, - highlight: true, - stacked: isLabwareInStack, - } - } - ) - - return ( - - - - ) -} diff --git a/app/src/organisms/ProtocolSetupLabware/index.tsx b/app/src/organisms/ProtocolSetupLabware/index.tsx deleted file mode 100644 index 456358ddfb7..00000000000 --- a/app/src/organisms/ProtocolSetupLabware/index.tsx +++ /dev/null @@ -1,583 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { css } from 'styled-components' - -import { - ALIGN_CENTER, - ALIGN_FLEX_START, - BORDERS, - Box, - COLORS, - DeckInfoLabel, - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - Icon, - JUSTIFY_SPACE_BETWEEN, - JUSTIFY_SPACE_EVENLY, - MODULE_ICON_NAME_BY_TYPE, - SPACING, - LegacyStyledText, - TYPOGRAPHY, - Chip, -} from '@opentrons/components' -import { - FLEX_ROBOT_TYPE, - getDeckDefFromRobotType, - getLabwareDefURI, - getLabwareDisplayName, - getModuleDisplayName, - HEATERSHAKER_MODULE_TYPE, - parseInitialLoadedLabwareByAdapter, -} from '@opentrons/shared-data' -import { - useCreateLiveCommandMutation, - useModulesQuery, -} from '@opentrons/react-api-client' - -import { FloatingActionButton, SmallButton } from '../../atoms/buttons' -import { ODDBackButton } from '../../molecules/ODDBackButton' -import { getLabwareSetupItemGroups } from '../../pages/Protocols/utils' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' -import { getProtocolModulesInfo } from '../Devices/ProtocolRun/utils/getProtocolModulesInfo' -import { getNestedLabwareInfo } from '../Devices/ProtocolRun/SetupLabware/getNestedLabwareInfo' -import { LabwareStackModal } from '../Devices/ProtocolRun/SetupLabware/LabwareStackModal' -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { getAttachedProtocolModuleMatches } from '../ProtocolSetupModulesAndDeck/utils' -import { LabwareMapView } from './LabwareMapView' -import { SingleLabwareModal } from './SingleLabwareModal' - -import type { UseQueryResult } from 'react-query' -import type { - HeaterShakerCloseLatchCreateCommand, - HeaterShakerOpenLatchCreateCommand, - LabwareDefinition2, - LabwareLocation, - LoadLabwareRunTimeCommand, - RunTimeCommand, -} from '@opentrons/shared-data' -import type { HeaterShakerModule, Modules } from '@opentrons/api-client' -import type { LabwareSetupItem } from '../../pages/Protocols/utils' -import type { SetupScreens } from '../../pages/ProtocolSetup' -import type { NestedLabwareInfo } from '../Devices/ProtocolRun/SetupLabware/getNestedLabwareInfo' -import type { AttachedProtocolModuleMatch } from '../ProtocolSetupModulesAndDeck/utils' - -const MODULE_REFETCH_INTERVAL_MS = 5000 -const DECK_CONFIG_POLL_MS = 5000 - -export interface ProtocolSetupLabwareProps { - runId: string - setSetupScreen: React.Dispatch> - isConfirmed: boolean - setIsConfirmed: (confirmed: boolean) => void -} - -export function ProtocolSetupLabware({ - runId, - setSetupScreen, - isConfirmed, - setIsConfirmed, -}: ProtocolSetupLabwareProps): JSX.Element { - const { t } = useTranslation('protocol_setup') - const [showMapView, setShowMapView] = React.useState(false) - const [ - showLabwareDetailsModal, - setShowLabwareDetailsModal, - ] = React.useState(false) - const [selectedLabware, setSelectedLabware] = React.useState< - | (LabwareDefinition2 & { - location: LabwareLocation - nickName: string | null - id: string - }) - | null - >(null) - - const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) - const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE) - const { data: deckConfig = [] } = useNotifyDeckConfigurationQuery({ - refetchInterval: DECK_CONFIG_POLL_MS, - }) - const { offDeckItems, onDeckItems } = getLabwareSetupItemGroups( - mostRecentAnalysis?.commands ?? [] - ) - const moduleQuery = useModulesQuery({ - refetchInterval: MODULE_REFETCH_INTERVAL_MS, - }) - const attachedModules = moduleQuery?.data?.data ?? [] - const protocolModulesInfo = - mostRecentAnalysis != null - ? getProtocolModulesInfo(mostRecentAnalysis, deckDef) - : [] - - const attachedProtocolModuleMatches = getAttachedProtocolModuleMatches( - attachedModules, - protocolModulesInfo, - deckConfig - ) - const initialLoadedLabwareByAdapter = parseInitialLoadedLabwareByAdapter( - mostRecentAnalysis?.commands ?? [] - ) - - const handleLabwareClick = ( - labwareDef: LabwareDefinition2, - labwareId: string - ): void => { - const foundLabware = mostRecentAnalysis?.labware.find( - labware => labware.id === labwareId - ) - if (foundLabware != null) { - const nickName = onDeckItems.find( - item => getLabwareDefURI(item.definition) === foundLabware.definitionUri - )?.nickName - setSelectedLabware({ - ...labwareDef, - location: foundLabware.location, - nickName: nickName ?? null, - id: labwareId, - }) - setShowLabwareDetailsModal(true) - } - } - const selectedLabwareIsTopOfStack = mostRecentAnalysis?.commands.some( - command => - command.commandType === 'loadLabware' && - command.result?.labwareId === selectedLabware?.id && - typeof command.params.location === 'object' && - ('moduleId' in command.params.location || - 'labwareId' in command.params.location) - ) - - return ( - <> - {showLabwareDetailsModal && - !selectedLabwareIsTopOfStack && - selectedLabware != null ? ( - { - setShowLabwareDetailsModal(false) - setSelectedLabware(null) - }} - mostRecentAnalysis={mostRecentAnalysis} - /> - ) : null} - - { - setSetupScreen('prepare to run') - }} - /> - {isConfirmed ? ( - - ) : ( - { - setIsConfirmed(true) - setSetupScreen('prepare to run') - }} - /> - )} - - - {showMapView ? ( - - ) : ( - <> - - - {t('location')} - - - {t('labware_name')} - - - {[...onDeckItems, ...offDeckItems].map((labware, i) => { - const labwareOnAdapter = onDeckItems.find( - item => - labware.initialLocation !== 'offDeck' && - 'labwareId' in labware.initialLocation && - item.labwareId === labware.initialLocation.labwareId - ) - return mostRecentAnalysis != null && labwareOnAdapter == null ? ( - - ) : null - })} - - )} - {showLabwareDetailsModal && - selectedLabware != null && - selectedLabwareIsTopOfStack ? ( - { - setSelectedLabware(null) - setShowLabwareDetailsModal(false) - }} - /> - ) : null} - - { - setShowMapView(mapView => !mapView) - }} - /> - - ) -} - -const labwareLatchStyles = css` - &:active { - background-color: ${COLORS.blue35}; - } -` - -interface LabwareLatchProps { - matchedHeaterShaker: HeaterShakerModule - refetchModules: UseQueryResult['refetch'] -} - -function LabwareLatch({ - matchedHeaterShaker, - refetchModules, -}: LabwareLatchProps): JSX.Element { - const { t } = useTranslation(['heater_shaker', 'protocol_setup']) - const { - createLiveCommand, - isLoading: isLiveCommandLoading, - } = useCreateLiveCommandMutation() - const [isRefetchingModules, setIsRefetchingModules] = React.useState(false) - const isLatchLoading = - isLiveCommandLoading || - isRefetchingModules || - matchedHeaterShaker.data.labwareLatchStatus === 'opening' || - matchedHeaterShaker.data.labwareLatchStatus === 'closing' - const isLatchClosed = - matchedHeaterShaker.data.labwareLatchStatus === 'idle_closed' || - matchedHeaterShaker.data.labwareLatchStatus === 'opening' - - let icon: 'latch-open' | 'latch-closed' | null = null - - const latchCommand: - | HeaterShakerOpenLatchCreateCommand - | HeaterShakerCloseLatchCreateCommand = { - commandType: isLatchClosed - ? 'heaterShaker/openLabwareLatch' - : 'heaterShaker/closeLabwareLatch', - params: { moduleId: matchedHeaterShaker.id }, - } - - const toggleLatch = (): void => { - createLiveCommand({ - command: latchCommand, - waitUntilComplete: true, - }) - .then(() => { - setIsRefetchingModules(true) - refetchModules() - .then(() => { - setIsRefetchingModules(false) - }) - .catch((e: Error) => { - console.error( - `error refetching modules after toggle latch: ${e.message}` - ) - setIsRefetchingModules(false) - }) - }) - .catch((e: Error) => { - console.error( - `error setting module status with command type ${latchCommand.commandType}: ${e.message}` - ) - }) - } - const commandType = isLatchClosed - ? 'heaterShaker/openLabwareLatch' - : 'heaterShaker/closeLabwareLatch' - let hsLatchText: string | null = t('open') - if (commandType === 'heaterShaker/closeLabwareLatch' && isLatchLoading) { - hsLatchText = t('closing') - icon = 'latch-open' - } else if ( - commandType === 'heaterShaker/openLabwareLatch' && - isLatchLoading - ) { - hsLatchText = t('opening') - icon = 'latch-closed' - } else if ( - commandType === 'heaterShaker/closeLabwareLatch' && - !isLatchLoading - ) { - hsLatchText = t('open') - icon = 'latch-open' - } else if ( - commandType === 'heaterShaker/openLabwareLatch' && - !isLatchLoading - ) { - hsLatchText = t('closed') - icon = 'latch-closed' - } - - return ( - - - {t('protocol_setup:labware_latch')} - - - {hsLatchText != null && icon != null ? ( - <> - - {hsLatchText} - - - - ) : null} - - - ) -} - -interface RowLabwareProps { - labware: LabwareSetupItem - attachedProtocolModules: AttachedProtocolModuleMatch[] - refetchModules: UseQueryResult['refetch'] - nestedLabwareInfo: NestedLabwareInfo | null - commands?: RunTimeCommand[] -} - -function RowLabware({ - labware, - attachedProtocolModules, - refetchModules, - nestedLabwareInfo, - commands, -}: RowLabwareProps): JSX.Element | null { - const { definition, initialLocation, nickName } = labware - const { t, i18n } = useTranslation([ - 'protocol_command_text', - 'protocol_setup', - ]) - - const matchedModule = - initialLocation !== 'offDeck' && - 'moduleId' in initialLocation && - attachedProtocolModules.length > 0 - ? attachedProtocolModules.find( - mod => mod.moduleId === initialLocation.moduleId - ) - : null - const matchingHeaterShaker = - matchedModule?.attachedModuleMatch != null && - matchedModule.attachedModuleMatch.moduleType === HEATERSHAKER_MODULE_TYPE - ? matchedModule.attachedModuleMatch - : null - - let slotName: string = '' - let location: JSX.Element | string | null = null - if (initialLocation === 'offDeck') { - location = ( - - ) - } else if ('slotName' in initialLocation) { - slotName = initialLocation.slotName - location = - } else if ('addressableAreaName' in initialLocation) { - slotName = initialLocation.addressableAreaName - location = - } else if (labware.moduleLocation != null) { - location = ( - <> - - - ) - } else if ('labwareId' in initialLocation) { - const adapterId = initialLocation.labwareId - const adapterLocation = commands?.find( - (command): command is LoadLabwareRunTimeCommand => - command.commandType === 'loadLabware' && - command.result?.labwareId === adapterId - )?.params.location - - if (adapterLocation != null && adapterLocation !== 'offDeck') { - if ('slotName' in adapterLocation) { - slotName = adapterLocation.slotName - location = - } else if ('moduleId' in adapterLocation) { - const moduleUnderAdapter = attachedProtocolModules.find( - module => module.moduleId === adapterLocation.moduleId - ) - if (moduleUnderAdapter != null) { - slotName = moduleUnderAdapter.slotName - location = - } - } - } - } - return ( - - - {location} - {nestedLabwareInfo != null || matchedModule != null ? ( - - ) : null} - - - - - - {getLabwareDisplayName(definition)} - - - {nickName} - - - {nestedLabwareInfo != null && - nestedLabwareInfo?.sharedSlotId === slotName ? ( - <> - - - - {nestedLabwareInfo.nestedLabwareDisplayName} - - - {nestedLabwareInfo.nestedLabwareNickName} - - - - ) : null} - {matchedModule != null ? ( - <> - - - - - - {getModuleDisplayName(matchedModule.moduleDef.model)} - - {matchingHeaterShaker != null ? ( - - {t('protocol_setup:labware_latch_instructions')} - - ) : null} - - - - ) : null} - - {matchingHeaterShaker != null ? ( - - ) : null} - - - ) -} diff --git a/app/src/organisms/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx b/app/src/organisms/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx deleted file mode 100644 index e7a7fafc33d..00000000000 --- a/app/src/organisms/ProtocolSetupLiquids/__tests__/LiquidDetails.test.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import * as React from 'react' -import { screen, fireEvent } from '@testing-library/react' -import { describe, it, beforeEach, vi } from 'vitest' - -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { RUN_ID_1 } from '../../RunTimeControl/__fixtures__' -import { getLocationInfoNames } from '../../Devices/ProtocolRun/utils/getLocationInfoNames' -import { getVolumePerWell } from '../../Devices/ProtocolRun/SetupLiquids/utils' -import { LiquidDetails } from '../LiquidDetails' -import { LiquidsLabwareDetailsModal } from '../../Devices/ProtocolRun/SetupLiquids/LiquidsLabwareDetailsModal' -import { - MOCK_LABWARE_INFO_BY_LIQUID_ID, - MOCK_PROTOCOL_ANALYSIS, -} from '../fixtures' -import type { CompletedProtocolAnalysis } from '@opentrons/shared-data' - -vi.mock('../../Devices/ProtocolRun/SetupLiquids/utils') -vi.mock('../../Devices/ProtocolRun/utils/getLocationInfoNames') -vi.mock('../../Devices/ProtocolRun/SetupLiquids/LiquidsLabwareDetailsModal') - -const render = (props: React.ComponentProps) => { - return renderWithProviders(, { - i18nInstance: i18n, - }) -} - -describe('LiquidDetails', () => { - let props: React.ComponentProps - beforeEach(() => { - props = { - commands: (MOCK_PROTOCOL_ANALYSIS as CompletedProtocolAnalysis).commands, - labwareByLiquidId: MOCK_LABWARE_INFO_BY_LIQUID_ID, - runId: RUN_ID_1, - liquid: { - id: '0', - displayName: 'mock liquid 1', - description: 'mock sample', - displayColor: '#ff4888', - }, - } - vi.mocked(getVolumePerWell).mockReturnValue(50) - vi.mocked(getLocationInfoNames).mockReturnValue({ - slotName: '4', - labwareName: 'mock labware name', - }) - vi.mocked(LiquidsLabwareDetailsModal).mockReturnValue(
    mock modal
    ) - }) - - it('renders the total volume of the liquid, sample display name, clicking on arrow renders the modal', () => { - render(props) - screen.getByText('4') - screen.getByText('mock labware name') - screen.getByText('Location') - screen.getByText('Labware name') - screen.getByText('Individual well volume') - screen.getByText('50 µL') - fireEvent.click(screen.getByLabelText('LiquidDetails_0')) - screen.getByText('mock modal') - }) - it('renders variable well amount if no specific volume per well', () => { - vi.mocked(getVolumePerWell).mockReturnValue(null) - render(props) - screen.getByText('Variable well amount') - }) -}) diff --git a/app/src/organisms/ProtocolSetupLiquids/index.tsx b/app/src/organisms/ProtocolSetupLiquids/index.tsx deleted file mode 100644 index 2f831950afe..00000000000 --- a/app/src/organisms/ProtocolSetupLiquids/index.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { - ALIGN_CENTER, - BORDERS, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - Icon, - SPACING, - StyledText, - TYPOGRAPHY, - JUSTIFY_SPACE_BETWEEN, - Chip, -} from '@opentrons/components' -import { - MICRO_LITERS, - parseLabwareInfoByLiquidId, - parseLiquidsInLoadOrder, -} from '@opentrons/shared-data' -import { ODDBackButton } from '../../molecules/ODDBackButton' -import { SmallButton } from '../../atoms/buttons' - -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { getTotalVolumePerLiquidId } from '../Devices/ProtocolRun/SetupLiquids/utils' -import { LiquidDetails } from './LiquidDetails' -import type { ParsedLiquid, RunTimeCommand } from '@opentrons/shared-data' -import type { SetupScreens } from '../../pages/ProtocolSetup' - -export interface ProtocolSetupLiquidsProps { - runId: string - setSetupScreen: React.Dispatch> - isConfirmed: boolean - setIsConfirmed: (confirmed: boolean) => void -} - -export function ProtocolSetupLiquids({ - runId, - setSetupScreen, - isConfirmed, - setIsConfirmed, -}: ProtocolSetupLiquidsProps): JSX.Element { - const { t, i18n } = useTranslation('protocol_setup') - const protocolData = useMostRecentCompletedAnalysis(runId) - const liquidsInLoadOrder = parseLiquidsInLoadOrder( - protocolData?.liquids ?? [], - protocolData?.commands ?? [] - ) - return ( - <> - - { - setSetupScreen('prepare to run') - }} - /> - {isConfirmed ? ( - - ) : ( - { - setIsConfirmed(true) - setSetupScreen('prepare to run') - }} - /> - )} - - - - - - {t('liquid_name')} - - - - - {t('total_liquid_volume')} - - - - {liquidsInLoadOrder?.map(liquid => ( - - - - ))} - - - ) -} - -interface LiquidsListProps { - liquid: ParsedLiquid - runId: string - commands?: RunTimeCommand[] -} - -export function LiquidsList(props: LiquidsListProps): JSX.Element { - const { liquid, runId, commands } = props - const [openItem, setOpenItem] = React.useState(false) - const labwareByLiquidId = parseLabwareInfoByLiquidId(commands ?? []) - - return ( - - { - setOpenItem(prevOpenItem => !prevOpenItem) - }} - aria-label={`Liquids_${liquid.id}`} - > - - - - - - {liquid.displayName} - - - - - {getTotalVolumePerLiquidId(liquid.id, labwareByLiquidId)}{' '} - {MICRO_LITERS} - - - - - {openItem ? ( - - ) : null} - - ) -} diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/FixtureTable.tsx b/app/src/organisms/ProtocolSetupModulesAndDeck/FixtureTable.tsx deleted file mode 100644 index b8885dd60a1..00000000000 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/FixtureTable.tsx +++ /dev/null @@ -1,234 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { - ALIGN_CENTER, - BORDERS, - COLORS, - Chip, - DeckInfoLabel, - DIRECTION_ROW, - Flex, - JUSTIFY_SPACE_BETWEEN, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' -import { - FLEX_USB_MODULE_ADDRESSABLE_AREAS, - getCutoutDisplayName, - getDeckDefFromRobotType, - getFixtureDisplayName, - getSimplestDeckConfigForProtocol, - SINGLE_SLOT_FIXTURES, -} from '@opentrons/shared-data' - -import { SmallButton } from '../../atoms/buttons' -import { useDeckConfigurationCompatibility } from '../../resources/deck_configuration/hooks' -import { getRequiredDeckConfig } from '../../resources/deck_configuration/utils' -import { LocationConflictModal } from '../Devices/ProtocolRun/SetupModuleAndDeck/LocationConflictModal' - -import type { - CompletedProtocolAnalysis, - CutoutFixtureId, - CutoutId, - DeckDefinition, - RobotType, -} from '@opentrons/shared-data' -import type { SetupScreens } from '../../pages/ProtocolSetup' -import type { CutoutConfigAndCompatibility } from '../../resources/deck_configuration/hooks' -import { useSelector } from 'react-redux' -import { getLocalRobot } from '../../redux/discovery' - -interface FixtureTableProps { - robotType: RobotType - mostRecentAnalysis: CompletedProtocolAnalysis | null - setSetupScreen: React.Dispatch> - setCutoutId: (cutoutId: CutoutId) => void - setProvidedFixtureOptions: (providedFixtureOptions: CutoutFixtureId[]) => void -} - -/** - * Table of all "non-module" fixtures e.g. staging slot, waste chute, trash bin... - * @param props - * @returns JSX.Element - */ -export function FixtureTable({ - robotType, - mostRecentAnalysis, - setSetupScreen, - setCutoutId, - setProvidedFixtureOptions, -}: FixtureTableProps): JSX.Element | null { - const requiredFixtureDetails = getSimplestDeckConfigForProtocol( - mostRecentAnalysis - ) - const deckConfigCompatibility = useDeckConfigurationCompatibility( - robotType, - mostRecentAnalysis - ) - const deckDef = getDeckDefFromRobotType(robotType) - const localRobot = useSelector(getLocalRobot) - const robotName = localRobot?.name != null ? localRobot.name : '' - - const requiredDeckConfigCompatibility = getRequiredDeckConfig( - deckConfigCompatibility - ) - - // list not configured/conflicted fixtures first - const sortedDeckConfigCompatibility = requiredDeckConfigCompatibility.sort( - a => - a.cutoutFixtureId != null && - a.compatibleCutoutFixtureIds.includes(a.cutoutFixtureId) - ? 1 - : -1 - ) - - return sortedDeckConfigCompatibility.length > 0 ? ( - <> - {sortedDeckConfigCompatibility.map((fixtureCompatibility, index) => { - // filter out all fixtures that only provide module addressable areas (e.g. everything but StagingAreaWithMagBlockV1) - // as they're handled in the Modules Table - return fixtureCompatibility.requiredAddressableAreas.every(raa => - FLEX_USB_MODULE_ADDRESSABLE_AREAS.includes(raa) - ) ? null : ( - - ) - })} - - ) : null -} - -interface FixtureTableItemProps extends CutoutConfigAndCompatibility { - lastItem: boolean - setSetupScreen: React.Dispatch> - setCutoutId: (cutoutId: CutoutId) => void - setProvidedFixtureOptions: (providedFixtureOptions: CutoutFixtureId[]) => void - deckDef: DeckDefinition - robotName: string -} - -function FixtureTableItem({ - cutoutId, - cutoutFixtureId, - compatibleCutoutFixtureIds, - missingLabwareDisplayName, - lastItem, - setSetupScreen, - setCutoutId, - setProvidedFixtureOptions, - deckDef, - robotName, -}: FixtureTableItemProps): JSX.Element { - const { t, i18n } = useTranslation('protocol_setup') - - const [ - showLocationConflictModal, - setShowLocationConflictModal, - ] = React.useState(false) - - const isCurrentFixtureCompatible = - cutoutFixtureId != null && - compatibleCutoutFixtureIds.includes(cutoutFixtureId) - const isRequiredSingleSlotMissing = missingLabwareDisplayName != null - let chipLabel: JSX.Element - if (!isCurrentFixtureCompatible) { - const isConflictingFixtureConfigured = - cutoutFixtureId != null && !SINGLE_SLOT_FIXTURES.includes(cutoutFixtureId) - chipLabel = ( - <> - - { - setShowLocationConflictModal(true) - } - : () => { - setCutoutId(cutoutId) - setProvidedFixtureOptions(compatibleCutoutFixtureIds) - setSetupScreen('deck configuration') - } - } - /> - - ) - } else { - chipLabel = ( - - ) - } - return ( - - {showLocationConflictModal ? ( - { - setShowLocationConflictModal(false) - }} - cutoutId={cutoutId} - requiredFixtureId={compatibleCutoutFixtureIds[0]} - isOnDevice={true} - missingLabwareDisplayName={missingLabwareDisplayName} - deckDef={deckDef} - robotName={robotName} - /> - ) : null} - - - - {cutoutFixtureId != null && - (isCurrentFixtureCompatible || isRequiredSingleSlotMissing) - ? getFixtureDisplayName(cutoutFixtureId) - : getFixtureDisplayName(compatibleCutoutFixtureIds?.[0])} - - - - - - - {chipLabel} - - - - ) -} diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx b/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx deleted file mode 100644 index b96d972ca36..00000000000 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/__tests__/utils.test.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { - TEMPERATURE_MODULE_V2_FIXTURE, - getModuleDef2, -} from '@opentrons/shared-data' - -import { mockTemperatureModuleGen2 } from '../../../redux/modules/__fixtures__' -import { - getAttachedProtocolModuleMatches, - getUnmatchedModulesForProtocol, -} from '../utils' - -const temperatureProtocolModule = { - moduleId: 'mockTempModuleId', - x: 0, - y: 0, - z: 0, - moduleDef: getModuleDef2('temperatureModuleV2'), - nestedLabwareDef: null, - nestedLabwareId: null, - nestedLabwareDisplayName: null, - protocolLoadOrder: 0, - slotName: 'D1', -} - -const magneticProtocolModule = { - moduleId: 'mockMagneticModuleId', - x: 0, - y: 0, - z: 0, - moduleDef: getModuleDef2('magneticModuleV2'), - nestedLabwareDef: null, - nestedLabwareId: null, - nestedLabwareDisplayName: null, - protocolLoadOrder: 0, - slotName: 'D1', -} - -describe('getAttachedProtocolModuleMatches', () => { - it('returns no module matches when no modules attached', () => { - const result = getAttachedProtocolModuleMatches( - [], - [temperatureProtocolModule, magneticProtocolModule], - [] - ) - expect(result).toEqual([ - { ...temperatureProtocolModule, attachedModuleMatch: null }, - { ...magneticProtocolModule, attachedModuleMatch: null }, - ]) - }) - - it('returns no module matches when no modules match', () => { - const result = getAttachedProtocolModuleMatches( - [mockTemperatureModuleGen2], - [magneticProtocolModule], - [ - { - cutoutId: 'cutoutD1', - cutoutFixtureId: TEMPERATURE_MODULE_V2_FIXTURE, - opentronsModuleSerialNumber: mockTemperatureModuleGen2.serialNumber, - }, - ] - ) - expect(result).toEqual([ - { ...magneticProtocolModule, attachedModuleMatch: null }, - ]) - }) - - it('returns module match when modules match', () => { - const result = getAttachedProtocolModuleMatches( - [mockTemperatureModuleGen2], - [temperatureProtocolModule, magneticProtocolModule], - [ - { - cutoutId: 'cutoutD1', - cutoutFixtureId: TEMPERATURE_MODULE_V2_FIXTURE, - opentronsModuleSerialNumber: mockTemperatureModuleGen2.serialNumber, - }, - ] - ) - expect(result).toEqual([ - { - ...temperatureProtocolModule, - attachedModuleMatch: mockTemperatureModuleGen2, - }, - { ...magneticProtocolModule, attachedModuleMatch: null }, - ]) - }) -}) - -describe('getUnmatchedModulesForProtocol', () => { - it('returns no missing module ids or remaining attached modules when no modules required or attached', () => { - const result = getUnmatchedModulesForProtocol([], []) - expect(result).toEqual({ - missingModuleIds: [], - remainingAttachedModules: [], - }) - }) - - it('returns no missing module ids or remaining attached modules when attached modules match', () => { - const result = getUnmatchedModulesForProtocol( - [mockTemperatureModuleGen2], - [temperatureProtocolModule] - ) - expect(result).toEqual({ - missingModuleIds: [], - remainingAttachedModules: [], - }) - }) - - it('returns missing module ids when protocol modules missing', () => { - const result = getUnmatchedModulesForProtocol( - [], - [temperatureProtocolModule, magneticProtocolModule] - ) - expect(result).toEqual({ - missingModuleIds: ['mockTempModuleId', 'mockMagneticModuleId'], - remainingAttachedModules: [], - }) - }) - - it('returns remaining attached modules when protocol modules and attached modules do not match', () => { - const result = getUnmatchedModulesForProtocol( - [mockTemperatureModuleGen2], - [magneticProtocolModule] - ) - expect(result).toEqual({ - missingModuleIds: ['mockMagneticModuleId'], - remainingAttachedModules: [mockTemperatureModuleGen2], - }) - }) -}) diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx b/app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx deleted file mode 100644 index 60f8f4be6f9..00000000000 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/index.tsx +++ /dev/null @@ -1,204 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' -import { useNavigate } from 'react-router-dom' -import { - COLORS, - DIRECTION_COLUMN, - Flex, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' -import { - FLEX_ROBOT_TYPE, - getDeckDefFromRobotType, -} from '@opentrons/shared-data' -import { RUN_STATUS_STOPPED } from '@opentrons/api-client' - -import { getTopPortalEl } from '../../App/portal' -import { FloatingActionButton } from '../../atoms/buttons' -import { InlineNotification } from '../../atoms/InlineNotification' -import { ChildNavigation } from '../../organisms/ChildNavigation' -import { useAttachedModules } from '../../organisms/Devices/hooks' -import { getProtocolModulesInfo } from '../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo' -import { useMostRecentCompletedAnalysis } from '../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { useRunStatus } from '../RunTimeControl/hooks' -import { - getAttachedProtocolModuleMatches, - getUnmatchedModulesForProtocol, -} from './utils' -import { SetupInstructionsModal } from './SetupInstructionsModal' -import { FixtureTable } from './FixtureTable' -import { ModuleTable } from './ModuleTable' -import { ModulesAndDeckMapView } from './ModulesAndDeckMapView' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' - -import type { CutoutId, CutoutFixtureId } from '@opentrons/shared-data' -import type { SetupScreens } from '../../pages/ProtocolSetup' - -const ATTACHED_MODULE_POLL_MS = 5000 -const DECK_CONFIG_POLL_MS = 5000 - -interface ProtocolSetupModulesAndDeckProps { - runId: string - setSetupScreen: React.Dispatch> - setCutoutId: (cutoutId: CutoutId) => void - setProvidedFixtureOptions: (providedFixtureOptions: CutoutFixtureId[]) => void -} - -/** - * an ODD screen on the Protocol Setup page - */ -export function ProtocolSetupModulesAndDeck({ - runId, - setSetupScreen, - setCutoutId, - setProvidedFixtureOptions, -}: ProtocolSetupModulesAndDeckProps): JSX.Element { - const { i18n, t } = useTranslation('protocol_setup') - const navigate = useNavigate() - const runStatus = useRunStatus(runId) - React.useEffect(() => { - if (runStatus === RUN_STATUS_STOPPED) { - navigate('/protocols') - } - }, [runStatus, navigate]) - const [ - showSetupInstructionsModal, - setShowSetupInstructionsModal, - ] = React.useState(false) - const [showMapView, setShowMapView] = React.useState(false) - const [ - clearModuleMismatchBanner, - setClearModuleMismatchBanner, - ] = React.useState(false) - const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) - - const deckDef = getDeckDefFromRobotType(FLEX_ROBOT_TYPE) - const { data: deckConfig = [] } = useNotifyDeckConfigurationQuery({ - refetchInterval: DECK_CONFIG_POLL_MS, - }) - const attachedModules = - useAttachedModules({ - refetchInterval: ATTACHED_MODULE_POLL_MS, - }) ?? [] - - const protocolModulesInfo = - mostRecentAnalysis != null - ? getProtocolModulesInfo(mostRecentAnalysis, deckDef) - : [] - - const attachedProtocolModuleMatches = getAttachedProtocolModuleMatches( - attachedModules, - protocolModulesInfo, - deckConfig - ) - - const hasModules = attachedProtocolModuleMatches.length > 0 - - const { - missingModuleIds, - remainingAttachedModules, - } = getUnmatchedModulesForProtocol(attachedModules, protocolModulesInfo) - - const isModuleMismatch = - remainingAttachedModules.length > 0 && missingModuleIds.length > 0 - return ( - <> - {createPortal( - <> - {showSetupInstructionsModal ? ( - - ) : null} - , - getTopPortalEl() - )} - { - setSetupScreen('prepare to run') - }} - buttonText={i18n.format(t('setup_instructions'), 'titleCase')} - buttonType="tertiaryLowLight" - iconName="information" - iconPlacement="startIcon" - onClickButton={() => { - setShowSetupInstructionsModal(true) - }} - /> - - {showMapView ? ( - - ) : ( - <> - {isModuleMismatch && !clearModuleMismatchBanner ? ( - { - e.stopPropagation() - setClearModuleMismatchBanner(true) - }} - heading={t('extra_module_attached')} - message={t('module_mismatch_body')} - /> - ) : null} - - - - {i18n.format(t('deck_hardware'), 'titleCase')} - - - {t('location')} - - {t('status')} - - - {hasModules ? ( - - ) : null} - - - - - )} - - { - setShowMapView(mapView => !mapView) - }} - /> - - ) -} diff --git a/app/src/organisms/ProtocolSetupModulesAndDeck/utils.ts b/app/src/organisms/ProtocolSetupModulesAndDeck/utils.ts deleted file mode 100644 index f1b8601dca1..00000000000 --- a/app/src/organisms/ProtocolSetupModulesAndDeck/utils.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { - FLEX_ROBOT_TYPE, - NON_CONNECTING_MODULE_TYPES, - OT2_ROBOT_TYPE, - checkModuleCompatibility, - getCutoutFixturesForModuleModel, - getCutoutIdsFromModuleSlotName, - getDeckDefFromRobotType, - getModuleType, -} from '@opentrons/shared-data' - -import type { DeckConfiguration, RobotType } from '@opentrons/shared-data' -import type { ProtocolModuleInfo } from '../../organisms/Devices/ProtocolRun/utils/getProtocolModulesInfo' -import type { AttachedModule } from '../../redux/modules/types' - -export type AttachedProtocolModuleMatch = ProtocolModuleInfo & { - attachedModuleMatch: AttachedModule | null -} - -// NOTE: some logic copied from useModuleRenderInfoForProtocolById -export function getAttachedProtocolModuleMatches( - attachedModules: AttachedModule[], - protocolModulesInfo: ProtocolModuleInfo[], - deckConfig: DeckConfiguration, - robotType: RobotType = FLEX_ROBOT_TYPE -): AttachedProtocolModuleMatch[] { - const deckDef = getDeckDefFromRobotType(robotType) - const robotSupportsModuleConfig = robotType !== OT2_ROBOT_TYPE - const matchedAttachedModules: AttachedModule[] = [] - const attachedProtocolModuleMatches = protocolModulesInfo.map( - protocolModule => { - const moduleFixtures = getCutoutFixturesForModuleModel( - protocolModule.moduleDef.model, - deckDef - ) - const moduleCutoutIds = getCutoutIdsFromModuleSlotName( - protocolModule.slotName, - moduleFixtures, - deckDef - ) - const compatibleAttachedModule = - attachedModules.find( - attachedModule => - checkModuleCompatibility( - attachedModule.moduleModel, - protocolModule.moduleDef.model - ) && - // check id instead of object reference in useModuleRenderInfoForProtocolById - !matchedAttachedModules.some( - matchedAttachedModule => - matchedAttachedModule.serialNumber === - attachedModule.serialNumber - ) && - // then if robotType supports configurable modules check the deck config has a - // a module with the expected serial number in the expected location - (!robotSupportsModuleConfig || - deckConfig.some( - ({ cutoutId, opentronsModuleSerialNumber }) => - attachedModule.serialNumber === opentronsModuleSerialNumber && - moduleCutoutIds.includes(cutoutId) - )) - ) ?? null - if (compatibleAttachedModule !== null) { - matchedAttachedModules.push(compatibleAttachedModule) - return { - ...protocolModule, - attachedModuleMatch: compatibleAttachedModule, - } - } - return { - ...protocolModule, - attachedModuleMatch: null, - } - } - ) - return attachedProtocolModuleMatches -} - -interface UnmatchedModuleResults { - missingModuleIds: string[] - remainingAttachedModules: AttachedModule[] -} - -// get requested protocol module ids that do not map to a robot-attached module of the requested model -// some logic copied from useUnmatchedModulesForProtocol -export function getUnmatchedModulesForProtocol( - attachedModules: AttachedModule[], - protocolModulesInfo: ProtocolModuleInfo[] -): UnmatchedModuleResults { - const { - missingModuleIds, - remainingAttachedModules, - } = protocolModulesInfo.reduce( - (acc, module) => { - const { model, compatibleWith } = module.moduleDef - // Skip matching any modules that don't require an electronic robot connection - if (NON_CONNECTING_MODULE_TYPES.includes(getModuleType(model))) return acc - // for this required module, find a remaining (unmatched) attached module of the requested model - const moduleTypeMatchIndex = acc.remainingAttachedModules.findIndex( - attachedModule => { - return ( - model === attachedModule.moduleModel || - compatibleWith.includes(attachedModule.moduleModel) - ) - } - ) - return moduleTypeMatchIndex !== -1 - ? { - ...acc, - // remove matched module from remaining modules list - remainingAttachedModules: acc.remainingAttachedModules.filter( - (_remainingAttachedModule, index) => - index !== moduleTypeMatchIndex - ), - } - : { - ...acc, - // append unmatchable module to list of requested modules that are missing a physical match - missingModuleIds: [...acc.missingModuleIds, module.moduleId], - } - }, - { missingModuleIds: [], remainingAttachedModules: attachedModules } - ) - return { missingModuleIds, remainingAttachedModules } -} diff --git a/app/src/organisms/ProtocolSetupOffsets/index.tsx b/app/src/organisms/ProtocolSetupOffsets/index.tsx deleted file mode 100644 index b0f8b6f78f6..00000000000 --- a/app/src/organisms/ProtocolSetupOffsets/index.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { - Flex, - DIRECTION_ROW, - JUSTIFY_SPACE_BETWEEN, - Chip, -} from '@opentrons/components' - -import type { LabwareOffset } from '@opentrons/api-client' -import { useToaster } from '../../organisms/ToasterOven' -import { ODDBackButton } from '../../molecules/ODDBackButton' -import { FloatingActionButton, SmallButton } from '../../atoms/buttons' -import type { SetupScreens } from '../../pages/ProtocolSetup' -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { TerseOffsetTable } from '../../organisms/LabwarePositionCheck/ResultsSummary' -import { getLabwareDefinitionsFromCommands } from '../../molecules/Command/utils/getLabwareDefinitionsFromCommands' -import { useNotifyRunQuery } from '../../resources/runs' -import { getLatestCurrentOffsets } from '../../organisms/Devices/ProtocolRun/SetupLabwarePositionCheck/utils' - -export interface ProtocolSetupOffsetsProps { - runId: string - setSetupScreen: React.Dispatch> - lpcDisabledReason: string | null - launchLPC: () => void - LPCWizard: JSX.Element | null - isConfirmed: boolean - setIsConfirmed: (confirmed: boolean) => void -} - -export function ProtocolSetupOffsets({ - runId, - setSetupScreen, - isConfirmed, - setIsConfirmed, - launchLPC, - lpcDisabledReason, - LPCWizard, -}: ProtocolSetupOffsetsProps): JSX.Element { - const { t } = useTranslation('protocol_setup') - const { makeSnackbar } = useToaster() - const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) - const makeDisabledReasonSnackbar = (): void => { - if (lpcDisabledReason != null) { - makeSnackbar(lpcDisabledReason) - } - } - - const labwareDefinitions = getLabwareDefinitionsFromCommands( - mostRecentAnalysis?.commands ?? [] - ) - const { data: runRecord } = useNotifyRunQuery(runId, { staleTime: Infinity }) - const currentOffsets = runRecord?.data?.labwareOffsets ?? [] - const sortedOffsets: LabwareOffset[] = - currentOffsets.length > 0 - ? currentOffsets - .map(offset => ({ - ...offset, - // convert into date to sort - createdAt: new Date(offset.createdAt), - })) - .sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()) - .map(offset => ({ - ...offset, - // convert back into string - createdAt: offset.createdAt.toISOString(), - })) - : [] - const nonIdentityOffsets = getLatestCurrentOffsets(sortedOffsets) - return ( - <> - {LPCWizard} - {LPCWizard == null && ( - <> - - { - setSetupScreen('prepare to run') - }} - /> - {isConfirmed ? ( - - ) : ( - { - setIsConfirmed(true) - setSetupScreen('prepare to run') - }} - /> - )} - - - { - if (lpcDisabledReason != null) { - makeDisabledReasonSnackbar() - } else { - launchLPC() - } - }} - /> - - )} - - ) -} diff --git a/app/src/organisms/ProtocolSetupParameters/index.tsx b/app/src/organisms/ProtocolSetupParameters/index.tsx deleted file mode 100644 index 7bdc2da2bcd..00000000000 --- a/app/src/organisms/ProtocolSetupParameters/index.tsx +++ /dev/null @@ -1,378 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useNavigate } from 'react-router-dom' -import { - useCreateProtocolAnalysisMutation, - useCreateRunMutation, - useHost, - useUploadCsvFileMutation, -} from '@opentrons/react-api-client' -import { useQueryClient } from 'react-query' -import { - ALIGN_CENTER, - DIRECTION_COLUMN, - Flex, - SPACING, -} from '@opentrons/components' -import { - formatRunTimeParameterValue, - sortRuntimeParameters, -} from '@opentrons/shared-data' - -import { - getRunTimeParameterFilesForRun, - getRunTimeParameterValuesForRun, -} from '../Devices/utils' -import { ChildNavigation } from '../ChildNavigation' -import { ResetValuesModal } from './ResetValuesModal' -import { ChooseEnum } from './ChooseEnum' -import { ChooseNumber } from './ChooseNumber' -import { ChooseCsvFile } from './ChooseCsvFile' -import { useToaster } from '../ToasterOven' -import { ProtocolSetupStep } from '../../pages/ProtocolSetup' -import type { - CompletedProtocolAnalysis, - ChoiceParameter, - CsvFileParameter, - NumberParameter, - RunTimeParameter, - ValueRunTimeParameter, - CsvFileParameterFileData, -} from '@opentrons/shared-data' -import type { ProtocolSetupStepStatus } from '../../pages/ProtocolSetup' -import type { FileData, LabwareOffsetCreateData } from '@opentrons/api-client' - -interface ProtocolSetupParametersProps { - protocolId: string - runTimeParameters: RunTimeParameter[] - labwareOffsets?: LabwareOffsetCreateData[] - mostRecentAnalysis?: CompletedProtocolAnalysis | null -} - -export function ProtocolSetupParameters({ - protocolId, - labwareOffsets, - runTimeParameters, - mostRecentAnalysis, -}: ProtocolSetupParametersProps): JSX.Element { - const { t } = useTranslation('protocol_setup') - const navigate = useNavigate() - const host = useHost() - const queryClient = useQueryClient() - const [ - chooseValueScreen, - setChooseValueScreen, - ] = React.useState(null) - const [ - showNumericalInputScreen, - setShowNumericalInputScreen, - ] = React.useState(null) - const [ - chooseCsvFileScreen, - setChooseCsvFileScreen, - ] = React.useState(null) - const [resetValuesModal, showResetValuesModal] = React.useState( - false - ) - const [startSetup, setStartSetup] = React.useState(false) - const [ - runTimeParametersOverrides, - setRunTimeParametersOverrides, - ] = React.useState( - runTimeParameters.map(parameter => - parameter.type === 'csv_file' - ? { ...parameter, file: null } - : // TODO (nd: 06/13/2024) create individual ChoiceParameter types for correct narrowing - // eslint-disable-next-line - ({ ...parameter, value: parameter.default } as ValueRunTimeParameter) - ) - ) - - const hasMissingFileParam = - runTimeParametersOverrides?.some((parameter): boolean => { - if (parameter.type !== 'csv_file') { - return false - } - - if (parameter.file == null) { - return true - } - - return ( - parameter.file.id == null && - parameter.file.file == null && - parameter.file.filePath == null - ) - }) ?? false - - const { makeSnackbar } = useToaster() - - const updateParameters = ( - value: boolean | string | number | CsvFileParameterFileData, - variableName: string - ): void => { - const updatedParameters = runTimeParametersOverrides.map(parameter => { - if (parameter.variableName === variableName) { - return parameter.type === 'csv_file' - ? { ...parameter, file: value } - : { ...parameter, value } - } - return parameter - }) - setRunTimeParametersOverrides(updatedParameters as RunTimeParameter[]) - if (chooseValueScreen && chooseValueScreen.variableName === variableName) { - const updatedParameter = updatedParameters.find( - parameter => parameter.variableName === variableName - ) - if (updatedParameter != null && 'choices' in updatedParameter) { - setChooseValueScreen(updatedParameter as ChoiceParameter) - } - } - if ( - showNumericalInputScreen && - showNumericalInputScreen.variableName === variableName - ) { - const updatedParameter = updatedParameters.find( - parameter => parameter.variableName === variableName - ) - if (updatedParameter != null) { - setShowNumericalInputScreen(updatedParameter as NumberParameter) - } - } - if ( - chooseCsvFileScreen && - chooseCsvFileScreen.variableName === variableName - ) { - const updatedParameter = updatedParameters.find( - parameter => parameter.variableName === variableName - ) - if (updatedParameter != null && updatedParameter.type === 'csv_file') { - setChooseCsvFileScreen(updatedParameter as CsvFileParameter) - } - } - } - - const { - createProtocolAnalysis, - isLoading: isAnalysisLoading, - } = useCreateProtocolAnalysisMutation(protocolId, host) - - const { uploadCsvFile } = useUploadCsvFileMutation({}, host) - - const { createRun, isLoading: isRunLoading } = useCreateRunMutation({ - onSuccess: data => { - queryClient.invalidateQueries([host, 'runs']).catch((e: Error) => { - console.error(`could not invalidate runs cache: ${e.message}`) - }) - }, - }) - const handleConfirmValues = (): void => { - if (hasMissingFileParam) { - makeSnackbar(t('protocol_requires_csv') as string) - } else { - const dataFilesForProtocolMap = runTimeParametersOverrides.reduce< - Record - >((acc, parameter) => { - // create {variableName: FileData} map for sending to /dataFiles endpoint - if ( - parameter.type === 'csv_file' && - parameter.file?.id == null && - parameter.file?.file != null - ) { - return { [parameter.variableName]: parameter.file.file } - } else if ( - parameter.type === 'csv_file' && - parameter.file?.id == null && - parameter.file?.filePath != null - ) { - return { [parameter.variableName]: parameter.file.filePath } - } - return acc - }, {}) - void Promise.all( - Object.entries(dataFilesForProtocolMap).map(([key, fileData]) => { - const fileResponse = uploadCsvFile(fileData) - const varName = Promise.resolve(key) - return Promise.all([fileResponse, varName]) - }) - ).then(responseTuples => { - const mappedResolvedCsvVariableToFileId = responseTuples.reduce< - Record - >((acc, [uploadedFileResponse, variableName]) => { - return { ...acc, [variableName]: uploadedFileResponse.data.id } - }, {}) - const runTimeParameterValues = getRunTimeParameterValuesForRun( - runTimeParametersOverrides - ) - const runTimeParameterFiles = getRunTimeParameterFilesForRun( - runTimeParametersOverrides, - mappedResolvedCsvVariableToFileId - ) - setStartSetup(true) - createProtocolAnalysis( - { - protocolKey: protocolId, - runTimeParameterValues, - runTimeParameterFiles, - }, - { - onSuccess: () => { - createRun({ - protocolId, - labwareOffsets, - runTimeParameterValues, - runTimeParameterFiles, - }) - }, - } - ) - }) - } - } - - const handleSetParameter = (parameter: RunTimeParameter): void => { - if ('choices' in parameter) { - setChooseValueScreen(parameter) - } else if (parameter.type === 'bool') { - updateParameters(!parameter.value, parameter.variableName) - } else if (parameter.type === 'int' || parameter.type === 'float') { - setShowNumericalInputScreen(parameter) - } else if (parameter.type === 'csv_file') { - setChooseCsvFileScreen(parameter) - } else { - // bad param - console.error('error: bad param. not expected to reach this') - } - } - - let children = ( - <> - { - navigate(-1) - }} - onClickButton={handleConfirmValues} - buttonText={t('confirm_values')} - ariaDisabled={hasMissingFileParam} - buttonIsDisabled={hasMissingFileParam} - iconName={ - isRunLoading || isAnalysisLoading || startSetup - ? 'ot-spinner' - : undefined - } - iconPlacement="startIcon" - secondaryButtonProps={{ - buttonType: 'tertiaryLowLight', - buttonText: t('restore_defaults'), - disabled: isRunLoading || isAnalysisLoading || startSetup, - onClick: () => { - showResetValuesModal(true) - }, - }} - /> - - {sortRuntimeParameters(runTimeParametersOverrides).map( - (parameter, index) => { - let detail: string = '' - let setupStatus: ProtocolSetupStepStatus - if (parameter.type === 'csv_file') { - if (parameter.file?.fileName == null) { - detail = t('required') - setupStatus = 'not ready' - } else { - detail = parameter.file.fileName - setupStatus = 'ready' - } - } else { - detail = formatRunTimeParameterValue(parameter, t) - setupStatus = 'inform' - } - return ( - - { - handleSetParameter(parameter) - }} - detail={detail} - description={ - parameter.type === 'csv_file' ? null : parameter.description - } - fontSize="h4" - disabled={startSetup} - /> - - ) - } - )} - - - ) - - // ToDo (kk:06/18/2024) ff will be removed when we freeze the code - if (chooseCsvFileScreen != null) { - children = ( - { - setChooseCsvFileScreen(null) - }} - parameter={chooseCsvFileScreen} - setParameter={updateParameters} - /> - ) - } - if (chooseValueScreen != null) { - children = ( - { - setChooseValueScreen(null) - }} - parameter={chooseValueScreen} - setParameter={updateParameters} - rawValue={chooseValueScreen.value} - /> - ) - } - if (showNumericalInputScreen != null) { - children = ( - { - setShowNumericalInputScreen(null) - }} - parameter={showNumericalInputScreen} - setParameter={updateParameters} - /> - ) - } - - return ( - <> - {resetValuesModal ? ( - { - showResetValuesModal(false) - }} - /> - ) : null} - {children} - - ) -} diff --git a/app/src/organisms/ProtocolStatusBanner/index.tsx b/app/src/organisms/ProtocolStatusBanner/index.tsx deleted file mode 100644 index 7f1ab307d41..00000000000 --- a/app/src/organisms/ProtocolStatusBanner/index.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { SPACING, LegacyStyledText } from '@opentrons/components' -import { Banner } from '../../atoms/Banner' - -import type { IconProps } from '@opentrons/components' - -export function ProtocolStatusBanner(): JSX.Element { - const { t } = useTranslation('protocol_list') - - const alertIcon: IconProps = { name: 'alert-circle' } - return ( - - {t('csv_file_required')} - - ) -} diff --git a/app/src/organisms/ProtocolTimelineScrubber/index.tsx b/app/src/organisms/ProtocolTimelineScrubber/index.tsx deleted file mode 100644 index 619928fcd4b..00000000000 --- a/app/src/organisms/ProtocolTimelineScrubber/index.tsx +++ /dev/null @@ -1,323 +0,0 @@ -import * as React from 'react' -import map from 'lodash/map' -import reduce from 'lodash/reduce' -import ViewportList from 'react-viewport-list' -import { - Flex, - DIRECTION_COLUMN, - SPACING, - ALIGN_CENTER, - JUSTIFY_SPACE_BETWEEN, - ALIGN_STRETCH, - LegacyStyledText, - BaseDeck, - PrimaryButton, - OVERFLOW_SCROLL, - COLORS, -} from '@opentrons/components' -import { getResultingTimelineFrameFromRunCommands } from '@opentrons/step-generation' -import { - FLEX_ROBOT_TYPE, - THERMOCYCLER_MODULE_TYPE, - getSimplestDeckConfigForProtocol, -} from '@opentrons/shared-data' -import { PipetteMountViz } from './PipetteVisuals' -import { - getAllWellContentsForActiveItem, - wellFillFromWellContents, -} from './utils' -import { CommandItem } from './CommandItem' - -import type { ViewportListRef } from 'react-viewport-list' -import type { - CompletedProtocolAnalysis, - LabwareLocation, - ProtocolAnalysisOutput, - RobotType, - RunTimeCommand, -} from '@opentrons/shared-data' -import type { ModuleTemporalProperties } from '@opentrons/step-generation' -import type { LabwareOnDeck, Module } from '@opentrons/components' - -const SEC_PER_FRAME = 1000 -export const COMMAND_WIDTH_PX = 240 - -interface ProtocolTimelineScrubberProps { - commands: RunTimeCommand[] - analysis: CompletedProtocolAnalysis | ProtocolAnalysisOutput - robotType?: RobotType -} - -export const DECK_LAYER_BLOCKLIST = [ - 'calibrationMarkings', - 'fixedBase', - 'doorStops', - 'metalFrame', - 'removalHandle', - 'removableDeckOutline', - 'screwHoles', -] -export const VIEWBOX_MIN_X = -84 -export const VIEWBOX_MIN_Y = -10 -export const VIEWBOX_WIDTH = 600 -export const VIEWBOX_HEIGHT = 460 - -export function ProtocolTimelineScrubber( - props: ProtocolTimelineScrubberProps -): JSX.Element { - const { commands, analysis, robotType = FLEX_ROBOT_TYPE } = props - const wrapperRef = React.useRef(null) - const commandListRef = React.useRef(null) - const [currentCommandIndex, setCurrentCommandIndex] = React.useState( - 0 - ) - const [isPlaying, setIsPlaying] = React.useState(true) - - const currentCommandsSlice = commands.slice(0, currentCommandIndex + 1) - const { frame, invariantContext } = getResultingTimelineFrameFromRunCommands( - currentCommandsSlice - ) - const handlePlayPause = (): void => { - setIsPlaying(!isPlaying) - } - - React.useEffect(() => { - if (isPlaying) { - const intervalId = setInterval(() => { - setCurrentCommandIndex(prev => { - const nextIndex = prev < commands.length - 1 ? prev + 1 : 0 - commandListRef.current?.scrollToIndex(nextIndex) - return nextIndex - }) - }, SEC_PER_FRAME) - - return () => { - clearInterval(intervalId) - } - } - }, [isPlaying, commands]) - - const { robotState } = frame - - const [leftPipetteId] = Object.entries(robotState.pipettes).find( - ([_pipetteId, pipette]) => pipette?.mount === 'left' - ) ?? [null] - const leftPipetteEntity = - leftPipetteId != null - ? invariantContext.pipetteEntities[leftPipetteId] - : null - - const [rightPipetteId] = Object.entries(robotState.pipettes).find( - ([_pipetteId, pipette]) => pipette?.mount === 'right' - ) ?? [null] - const rightPipetteEntity = - rightPipetteId != null - ? invariantContext.pipetteEntities[rightPipetteId] - : null - - const allWellContentsForActiveItem = getAllWellContentsForActiveItem( - invariantContext.labwareEntities, - frame - ) - const liquidDisplayColors = analysis.liquids.map( - liquid => liquid.displayColor ?? COLORS.blue50 - ) - - return ( - - - - { - const labwareInModuleId = - Object.entries(robotState.labware).find( - ([labwareId, labware]) => labware.slot === moduleId - )?.[0] ?? null - - const getModuleInnerProps = ( - moduleState: ModuleTemporalProperties['moduleState'] - ): React.ComponentProps['innerProps'] => { - if (moduleState.type === THERMOCYCLER_MODULE_TYPE) { - let lidMotorState = 'unknown' - if (moduleState.lidOpen === true) { - lidMotorState = 'open' - } else if (moduleState.lidOpen === false) { - lidMotorState = 'closed' - } - return { - lidMotorState, - blockTargetTemp: moduleState.blockTargetTemp, - } - } else if ( - 'targetTemperature' in moduleState && - moduleState.type === 'temperatureModuleType' - ) { - return { - targetTemperature: moduleState.targetTemperature, - } - } else if ('targetTemp' in moduleState) { - return { - targetTemp: moduleState.targetTemp, - } - } - } - - const adapterId = - labwareInModuleId != null - ? invariantContext.labwareEntities[labwareInModuleId].id - : null - const labwareTempProperties = - adapterId != null - ? Object.entries(robotState.labware).find( - ([labwareId, labware]) => labware.slot === adapterId - ) - : null - - const labwareDef = - labwareTempProperties != null - ? invariantContext.labwareEntities[labwareTempProperties[0]] - .def - : null - let nestedDef - let labwareId = null - if (labwareDef != null && labwareTempProperties != null) { - labwareId = labwareTempProperties[0] - nestedDef = labwareDef - } else if (labwareInModuleId != null) { - labwareId = labwareInModuleId - nestedDef = - invariantContext.labwareEntities[labwareInModuleId]?.def - } - - const wellContents = - allWellContentsForActiveItem && labwareId != null - ? allWellContentsForActiveItem[labwareId] - : null - const nestedFill = wellFillFromWellContents( - wellContents, - liquidDisplayColors - ) - - return { - moduleModel: invariantContext.moduleEntities[moduleId].model, - moduleLocation: { slotName: module.slot }, - nestedLabwareDef: nestedDef, - nestedLabwareWellFill: nestedFill, - innerProps: getModuleInnerProps(module.moduleState), - } - })} - labwareOnDeck={map(robotState.labware, (labware, labwareId) => { - const definition = invariantContext.labwareEntities[labwareId].def - - const missingTips = definition.parameters.isTiprack - ? reduce( - robotState.tipState.tipracks[labwareId], - (acc, hasTip, wellName) => { - if (!hasTip) return { ...acc, [wellName]: null } - return acc - }, - {} - ) - : {} - const labwareLocation: LabwareLocation = { - slotName: labware.slot, - } - const wellContents = - allWellContentsForActiveItem && labwareId != null - ? allWellContentsForActiveItem[labwareId] - : null - const wellFill = wellFillFromWellContents( - wellContents, - liquidDisplayColors - ) - const labwareOnDeck: LabwareOnDeck = { - labwareLocation, - definition, - wellFill, - missingTips, - } - - return labwareOnDeck - }).filter((i): i is LabwareOnDeck => i != null)} - /> - - - - - - - {(command, index) => ( - - )} - - - - - Jump to command - - - {isPlaying ? 'Pause' : 'Play'} - - - { - const nextIndex = Number(e.target.value) - 1 - setCurrentCommandIndex(nextIndex) - commandListRef.current?.scrollToIndex(nextIndex) - }} - /> - - 1 - {commands.length} - - {currentCommandIndex !== 0 && - currentCommandIndex !== commands.length - 1 ? ( - - {currentCommandIndex + 1} - - ) : null} - - ) -} diff --git a/app/src/organisms/ProtocolUpload/hooks/index.ts b/app/src/organisms/ProtocolUpload/hooks/index.ts deleted file mode 100644 index c53b3d97ce0..00000000000 --- a/app/src/organisms/ProtocolUpload/hooks/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './useCloseCurrentRun' -export * from './useCurrentProtocol' -export * from './useCurrentRun' -export * from './useCurrentRunCommands' -export * from './useCloneRun' -export * from './useRestartRun' -export * from './useRunCommands' diff --git a/app/src/organisms/ProtocolUpload/hooks/useCloneRun.ts b/app/src/organisms/ProtocolUpload/hooks/useCloneRun.ts deleted file mode 100644 index 41277f47e86..00000000000 --- a/app/src/organisms/ProtocolUpload/hooks/useCloneRun.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { useQueryClient } from 'react-query' - -import { - useHost, - useCreateRunMutation, - useCreateProtocolAnalysisMutation, -} from '@opentrons/react-api-client' -import { useNotifyRunQuery } from '../../../resources/runs' -import { - getRunTimeParameterValuesForRun, - getRunTimeParameterFilesForRun, -} from '../../Devices/utils' - -import type { Run } from '@opentrons/api-client' - -interface UseCloneRunResult { - cloneRun: () => void - isLoading: boolean -} - -export function useCloneRun( - runId: string | null, - onSuccessCallback?: (createRunResponse: Run) => unknown, - triggerAnalysis: boolean = false -): UseCloneRunResult { - const host = useHost() - const queryClient = useQueryClient() - const { data: runRecord } = useNotifyRunQuery(runId) - const protocolKey = runRecord?.data.protocolId ?? null - - const { createRun, isLoading } = useCreateRunMutation({ - onSuccess: response => { - const invalidateRuns = queryClient.invalidateQueries([host, 'runs']) - const invalidateProtocols = queryClient.invalidateQueries([ - host, - 'protocols', - protocolKey, - ]) - Promise.all([invalidateRuns, invalidateProtocols]) - .then(() => { - onSuccessCallback?.(response) - }) - .catch((e: Error) => { - console.error(`error invalidating runs query: ${e.message}`) - }) - }, - }) - const { createProtocolAnalysis } = useCreateProtocolAnalysisMutation( - protocolKey, - host - ) - const cloneRun = (): void => { - if (runRecord != null) { - const { protocolId, labwareOffsets } = runRecord.data - const runTimeParameters = - 'runTimeParameters' in runRecord.data - ? runRecord.data.runTimeParameters - : [] - const runTimeParameterValues = getRunTimeParameterValuesForRun( - runTimeParameters - ) - const runTimeParameterFiles = getRunTimeParameterFilesForRun( - runTimeParameters - ) - if (triggerAnalysis && protocolKey != null) { - createProtocolAnalysis({ - protocolKey, - runTimeParameterValues, - runTimeParameterFiles, - }) - } - createRun({ - protocolId, - labwareOffsets, - runTimeParameterValues, - runTimeParameterFiles, - }) - } else { - console.info('failed to clone run record, source run record not found') - } - } - - return { cloneRun, isLoading } -} diff --git a/app/src/organisms/ProtocolUpload/hooks/useCurrentRunCommands.ts b/app/src/organisms/ProtocolUpload/hooks/useCurrentRunCommands.ts deleted file mode 100644 index b6cc00709f9..00000000000 --- a/app/src/organisms/ProtocolUpload/hooks/useCurrentRunCommands.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useCurrentRunId } from '../../../resources/runs' -import { useRunCommands } from './useRunCommands' -import type { UseQueryOptions } from 'react-query' -import type { - CommandsData, - RunCommandSummary, - GetCommandsParams, -} from '@opentrons/api-client' - -export function useCurrentRunCommands( - params?: GetCommandsParams, - options?: UseQueryOptions -): RunCommandSummary[] | null { - const currentRunId = useCurrentRunId() - - return useRunCommands(currentRunId, params, options) -} diff --git a/app/src/organisms/ProtocolsLanding/ProtocolCard.tsx b/app/src/organisms/ProtocolsLanding/ProtocolCard.tsx deleted file mode 100644 index e4965c7b96c..00000000000 --- a/app/src/organisms/ProtocolsLanding/ProtocolCard.tsx +++ /dev/null @@ -1,365 +0,0 @@ -import * as React from 'react' -import { format } from 'date-fns' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { useNavigate } from 'react-router-dom' -import { ErrorBoundary } from 'react-error-boundary' - -import { - getModuleType, - getPipetteNameSpecs, - FLEX_STANDARD_MODEL, - getGripperDisplayName, - parseAllRequiredModuleModels, - parseInitialPipetteNamesByMount, -} from '@opentrons/shared-data' -import { - ALIGN_FLEX_START, - BORDERS, - Box, - COLORS, - DIRECTION_COLUMN, - Flex, - Icon, - JUSTIFY_FLEX_END, - ModuleIcon, - OVERFLOW_WRAP_ANYWHERE, - POSITION_ABSOLUTE, - ProtocolDeck, - SIZE_2, - SIZE_3, - SPACING, - LegacyStyledText, - TYPOGRAPHY, - WRAP, -} from '@opentrons/components' - -import { getIsProtocolAnalysisInProgress } from '../../redux/protocol-storage' -import { InstrumentContainer } from '../../atoms/InstrumentContainer' -import { ProtocolOverflowMenu } from './ProtocolOverflowMenu' -import { ProtocolAnalysisFailure } from '../ProtocolAnalysisFailure' -import { ProtocolStatusBanner } from '../ProtocolStatusBanner' -import { getProtocolUsesGripper } from '../ProtocolSetupInstruments/utils' -import { ProtocolAnalysisStale } from '../ProtocolAnalysisFailure/ProtocolAnalysisStale' -import { - getAnalysisStatus, - getProtocolDisplayName, - getRobotTypeDisplayName, -} from './utils' - -import type { ProtocolAnalysisOutput } from '@opentrons/shared-data' -import type { StoredProtocolData } from '../../redux/protocol-storage' -import type { State } from '../../redux/types' - -interface ProtocolCardProps { - handleRunProtocol: (storedProtocolData: StoredProtocolData) => void - handleSendProtocolToFlex: (storedProtocolData: StoredProtocolData) => void - storedProtocolData: StoredProtocolData -} -export function ProtocolCard(props: ProtocolCardProps): JSX.Element | null { - const navigate = useNavigate() - const { - handleRunProtocol, - handleSendProtocolToFlex, - storedProtocolData, - } = props - const { - protocolKey, - srcFileNames, - mostRecentAnalysis, - modified, - } = storedProtocolData - const isAnalyzing = useSelector((state: State) => - getIsProtocolAnalysisInProgress(state, protocolKey) - ) - const protocolDisplayName = getProtocolDisplayName( - protocolKey, - srcFileNames, - mostRecentAnalysis - ) - - const UNKNOWN_ATTACHMENT_ERROR = `${protocolDisplayName} protocol uses - instruments or modules from a future version of Opentrons software. Please update - the app to the most recent version to run this protocol.` - - const UnknownAttachmentError = ( - - ) - - return ( - { - navigate(`/protocols/${protocolKey}`) - }} - > - - - - - - - - ) -} - -interface AnalysisInfoProps { - protocolKey: string - protocolDisplayName: string - modified: number - isAnalyzing: boolean - mostRecentAnalysis?: ProtocolAnalysisOutput | null -} -function AnalysisInfo(props: AnalysisInfoProps): JSX.Element { - const { - protocolKey, - protocolDisplayName, - isAnalyzing, - mostRecentAnalysis, - modified, - } = props - const { t } = useTranslation(['protocol_list', 'shared']) - const analysisStatus = getAnalysisStatus(isAnalyzing, mostRecentAnalysis) - - const { left: leftMountPipetteName, right: rightMountPipetteName } = - mostRecentAnalysis != null - ? parseInitialPipetteNamesByMount(mostRecentAnalysis.commands) - : { left: null, right: null } - const requiredModuleModels = parseAllRequiredModuleModels( - mostRecentAnalysis != null ? mostRecentAnalysis.commands : [] - ) - - const requiredModuleTypes = requiredModuleModels.map(getModuleType) - - const robotType = mostRecentAnalysis?.robotType ?? null - - return ( - - - { - { - missing: ( - - ), - loading: ( - - ), - error: ( - - ), - parameterRequired: ( - - ), - stale: ( - - ), - complete: - mostRecentAnalysis != null ? ( - - ) : ( - - ), - }[analysisStatus] - } - - - {/* error and protocol name section */} - - {analysisStatus === 'parameterRequired' ? ( - - ) : null} - {analysisStatus === 'error' ? ( - e.detail) ?? []} - /> - ) : null} - {analysisStatus === 'stale' ? ( - - ) : null} - - {protocolDisplayName} - - - {/* data section */} - {analysisStatus === 'loading' ? ( - - {t('loading_data')} - - ) : ( - - - - - {t('robot')} - - - {getRobotTypeDisplayName(robotType)} - - - - - {t('shared:instruments')} - - { - { - missing: ( - {t('no_data')} - ), - loading: ( - {t('no_data')} - ), - error: ( - {t('no_data')} - ), - parameterRequired: ( - {t('no_data')} - ), - stale: ( - {t('no_data')} - ), - complete: ( - - {/* TODO(bh, 2022-10-14): insert 96-channel pipette if found */} - {leftMountPipetteName != null ? ( - - ) : null} - {rightMountPipetteName != null ? ( - - ) : null} - {mostRecentAnalysis != null && - getProtocolUsesGripper(mostRecentAnalysis) ? ( - - ) : null} - - ), - }[analysisStatus] - } - - - {requiredModuleTypes.length > 0 ? ( - <> - - {t('modules')} - - - {requiredModuleTypes.map((moduleType, index) => ( - - ))} - - - ) : null} - - - - - {`${t('updated')} ${format( - new Date(modified), - 'M/d/yy HH:mm' - )}`} - - - - )} - - - ) -} diff --git a/app/src/organisms/ProtocolsLanding/ProtocolUploadInput.tsx b/app/src/organisms/ProtocolsLanding/ProtocolUploadInput.tsx deleted file mode 100644 index a8a03d085e0..00000000000 --- a/app/src/organisms/ProtocolsLanding/ProtocolUploadInput.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import * as React from 'react' -import { useTranslation, Trans } from 'react-i18next' -import { useDispatch } from 'react-redux' -import { - ALIGN_CENTER, - COLORS, - DIRECTION_COLUMN, - ERROR_TOAST, - Flex, - Link, - SPACING, - LegacyStyledText, -} from '@opentrons/components' -import { UploadInput } from '../../molecules/UploadInput' -import { addProtocol } from '../../redux/protocol-storage' -import { - useTrackEvent, - ANALYTICS_IMPORT_PROTOCOL_TO_APP, -} from '../../redux/analytics' -import { useLogger } from '../../logger' -import { useToaster } from '../ToasterOven' - -import type { Dispatch } from '../../redux/types' - -export interface UploadInputProps { - onUpload?: () => void -} - -const isValidProtocolFileName = (protocolFileName: string): boolean => { - return protocolFileName.endsWith('.py') || protocolFileName.endsWith('.json') -} - -export function ProtocolUploadInput( - props: UploadInputProps -): JSX.Element | null { - const { t } = useTranslation(['protocol_info', 'shared']) - const dispatch = useDispatch() - const logger = useLogger(new URL('', import.meta.url).pathname) - const trackEvent = useTrackEvent() - const { makeToast } = useToaster() - - const handleUpload = (file: File): void => { - if (file.path === null) { - logger.warn('Failed to upload file, path not found') - } - if (isValidProtocolFileName(file.name)) { - dispatch(addProtocol(file.path)) - } else { - makeToast(t('incompatible_file_type') as string, ERROR_TOAST, { - closeButton: true, - }) - } - props.onUpload?.() - trackEvent({ - name: ANALYTICS_IMPORT_PROTOCOL_TO_APP, - properties: { protocolFileName: file.name }, - }) - } - - return ( - - { - handleUpload(file) - }} - uploadText={t('valid_file_types')} - dragAndDropText={ - - , - }} - /> - - } - /> - - ) -} diff --git a/app/src/organisms/ProtocolsLanding/__tests__/UploadInput.test.tsx b/app/src/organisms/ProtocolsLanding/__tests__/UploadInput.test.tsx deleted file mode 100644 index b039aef5b88..00000000000 --- a/app/src/organisms/ProtocolsLanding/__tests__/UploadInput.test.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import * as React from 'react' -import { fireEvent, screen } from '@testing-library/react' -import { BrowserRouter } from 'react-router-dom' -import { describe, it, vi, beforeEach, afterEach, expect } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { - useTrackEvent, - ANALYTICS_IMPORT_PROTOCOL_TO_APP, -} from '../../../redux/analytics' -import { ProtocolUploadInput } from '../ProtocolUploadInput' - -import type { Mock } from 'vitest' - -vi.mock('../../../redux/analytics') - -describe('ProtocolUploadInput', () => { - let onUpload: Mock - let trackEvent: Mock - const render = () => { - return renderWithProviders( - - - , - { - i18nInstance: i18n, - } - ) - } - - beforeEach(() => { - onUpload = vi.fn() - trackEvent = vi.fn() - vi.mocked(useTrackEvent).mockReturnValue(trackEvent) - }) - afterEach(() => { - vi.resetAllMocks() - }) - - it('renders correct contents for empty state', () => { - render() - - screen.getByRole('button', { name: 'Upload' }) - screen.getByText(/Drag and drop or/i) - screen.getByText(/your files/i) - screen.getByText( - 'Valid file types: Python files (.py) or Protocol Designer files (.json)' - ) - screen.getByRole('button', { name: 'browse' }) - }) - - it('opens file select on button click', () => { - render() - const button = screen.getByRole('button', { name: 'Upload' }) - const input = screen.getByTestId('file_input') - input.click = vi.fn() - fireEvent.click(button) - expect(input.click).toHaveBeenCalled() - }) - it('calls onUpload callback on choose file and trigger analytics event', () => { - render() - const input = screen.getByTestId('file_input') - fireEvent.change(input, { - target: { files: [{ path: 'dummyFile', name: 'dummyName' }] }, - }) - expect(onUpload).toHaveBeenCalled() - expect(trackEvent).toHaveBeenCalledWith({ - name: ANALYTICS_IMPORT_PROTOCOL_TO_APP, - properties: { protocolFileName: 'dummyName' }, - }) - }) -}) diff --git a/app/src/organisms/ProtocolsLanding/__tests__/hooks.test.tsx b/app/src/organisms/ProtocolsLanding/__tests__/hooks.test.tsx deleted file mode 100644 index b8ec09243b6..00000000000 --- a/app/src/organisms/ProtocolsLanding/__tests__/hooks.test.tsx +++ /dev/null @@ -1,438 +0,0 @@ -import * as React from 'react' -import { Provider } from 'react-redux' -import { createStore } from 'redux' -import { renderHook } from '@testing-library/react' -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' - -import { FLEX_ROBOT_TYPE, OT2_ROBOT_TYPE } from '@opentrons/shared-data' - -import { useSortedProtocols } from '../hooks' - -import type { Store } from 'redux' -import type { ProtocolAnalysisOutput } from '@opentrons/shared-data' -import type { StoredProtocolData } from '../../../redux/protocol-storage' -import type { State } from '../../../redux/types' - -const mockStoredProtocolData = [ - { - protocolKey: '26ed5a82-502f-4074-8981-57cdda1d066d', - modified: 1651613783762.9993, - srcFileNames: ['secondProtocol.json'], - srcFiles: [], - mostRecentAnalysis: { - robotType: FLEX_ROBOT_TYPE, - createdAt: '2022-05-03T21:36:12.494778+00:00', - files: [ - { - name: 'secondProtocol.json', - role: 'main', - }, - ], - config: { - protocolType: 'json', - schemaVersion: 6, - }, - metadata: { - author: 'Otie', - description: 'another mock protocol', - created: 1606853851893, - lastModified: 1619792954015, - tags: [], - }, - commands: [], - labware: [ - { - id: 'labware-0', - loadName: 'opentrons_1_trash_1100ml_fixed', - definitionUri: 'opentrons/opentrons_1_trash_1100ml_fixed/1', - location: { slotName: '12' }, - displayName: 'Trash', - }, - { - id: 'labware-1', - loadName: 'opentrons_96_tiprack_1000ul', - definitionUri: 'opentrons/opentrons_96_tiprack_1000ul/1', - location: { slotName: '1' }, - displayName: 'Opentrons 96 Tip Rack 1000 µL', - }, - ], - pipettes: [ - { - id: 'pipette-0', - pipetteName: 'p1000_single_gen2', - mount: 'left', - }, - ], - modules: [ - { - id: 'module-0', - model: 'magneticModuleV2', - location: { slotName: '6' }, - serialNumber: 'dummySerialMD', - }, - { - id: 'module-1', - model: 'temperatureModuleV2', - location: { slotName: '3' }, - serialNumber: 'dummySerialTD', - }, - { - id: 'module-2', - model: 'thermocyclerModuleV1', - location: { slotName: '7' }, - serialNumber: 'dummySerialTC', - }, - ], - liquids: [ - { - id: '0', - displayName: 'Water', - description: 'liquid H2O', - displayColor: '#50d5ff', - }, - { - id: '1', - displayName: 'Blood', - description: 'human essence', - displayColor: '#ff4f4f', - }, - ], - runTimeParameters: [], - errors: [], - result: 'ok', - } as ProtocolAnalysisOutput, - }, - { - protocolKey: '3dc99ffa-f85e-4c01-ab0a-edecff432dac', - modified: 1652339310312.1985, - srcFileNames: ['testProtocol.json'], - srcFiles: [], - mostRecentAnalysis: { - robotType: OT2_ROBOT_TYPE, - createdAt: '2022-05-10T17:04:43.132768+00:00', - files: [ - { - name: 'testProtocol.json', - role: 'main', - }, - ], - config: { - protocolType: 'json', - schemaVersion: 6, - }, - metadata: { - protocolName: 'Third protocol', - author: 'engineering', - description: 'A short mock protocol', - created: 1223131231, - tags: ['unitTest'], - }, - commands: [], - labware: [ - { - id: 'labware-0', - loadName: 'opentrons_1_trash_1100ml_fixed', - definitionUri: 'opentrons/opentrons_1_trash_1100ml_fixed/1', - location: { slotName: '12' }, - displayName: 'Trash', - }, - { - id: 'labware-1', - loadName: 'opentrons_96_tiprack_1000ul', - definitionUri: 'opentrons/opentrons_96_tiprack_1000ul/1', - location: { slotName: '1' }, - displayName: 'Opentrons 96 Tip Rack 1000 µL', - }, - ], - pipettes: [ - { - id: 'pipette-0', - pipetteName: 'p1000_single_gen2', - mount: 'left', - }, - ], - modules: [ - { - id: 'module-0', - model: 'magneticModuleV2', - location: { slotName: '6' }, - serialNumber: 'dummySerialMD', - }, - { - id: 'module-1', - model: 'temperatureModuleV2', - location: { slotName: '3' }, - serialNumber: 'dummySerialTD', - }, - { - id: 'module-2', - model: 'thermocyclerModuleV1', - location: { slotName: '7' }, - serialNumber: 'dummySerialTC', - }, - ], - liquids: [ - { - id: '0', - displayName: 'Water', - description: 'liquid H2O', - displayColor: '#50d5ff', - }, - { - id: '1', - displayName: 'Blood', - description: 'human essence', - displayColor: '#ff4f4f', - }, - ], - runTimeParameters: [], - errors: [], - result: 'ok', - } as ProtocolAnalysisOutput, - }, - { - protocolKey: 'f130337e-68ad-4b5d-a6d2-cbc20515b1f7', - modified: 1651688428961.8438, - srcFileNames: ['demo.py'], - srcFiles: [], - mostRecentAnalysis: { - createdAt: '2022-05-04T18:20:21.526508+00:00', - files: [ - { - name: 'demo.py', - role: 'main', - }, - ], - config: { - protocolType: 'python', - apiVersion: [2, 10], - }, - metadata: { - protocolName: 'First protocol', - author: 'Otie', - source: 'Custom Protocol Request', - apiLevel: '2.10', - }, - commands: [], - labware: [ - { - id: 'labware-0', - loadName: 'opentrons_1_trash_1100ml_fixed', - definitionUri: 'opentrons/opentrons_1_trash_1100ml_fixed/1', - location: { slotName: '12' }, - displayName: 'Trash', - }, - { - id: 'labware-1', - loadName: 'opentrons_96_tiprack_1000ul', - definitionUri: 'opentrons/opentrons_96_tiprack_1000ul/1', - location: { slotName: '1' }, - displayName: 'Opentrons 96 Tip Rack 1000 µL', - }, - ], - pipettes: [ - { - id: 'pipette-0', - pipetteName: 'p1000_single_gen2', - mount: 'left', - }, - ], - modules: [ - { - id: 'module-0', - model: 'magneticModuleV2', - location: { - slotName: '6', - }, - serialNumber: 'dummySerialMD', - }, - { - id: 'module-1', - model: 'temperatureModuleV2', - location: { - slotName: '3', - }, - serialNumber: 'dummySerialTD', - }, - { - id: 'module-2', - model: 'thermocyclerModuleV1', - location: { - slotName: '7', - }, - serialNumber: 'dummySerialTC', - }, - ], - liquids: [ - { - id: '0', - displayName: 'Water', - description: 'liquid H2O', - displayColor: '#50d5ff', - }, - { - id: '1', - displayName: 'Blood', - description: 'human essence', - displayColor: '#ff4f4f', - }, - ], - runTimeParameters: [], - errors: [], - result: 'ok', - } as ProtocolAnalysisOutput, - }, -] as StoredProtocolData[] - -describe('useSortedProtocols', () => { - const store: Store = createStore(vi.fn(), {}) - beforeEach(() => { - store.dispatch = vi.fn() - }) - afterEach(() => { - vi.restoreAllMocks() - }) - - it('should return an object with protocols sorted alphabetically', () => { - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => {children} - - const { result } = renderHook( - () => useSortedProtocols('alphabetical', mockStoredProtocolData), - { wrapper } - ) - const firstProtocol = result.current[0] - const secondProtocol = result.current[1] - const thirdProtocol = result.current[2] - - expect(firstProtocol.protocolKey).toBe( - 'f130337e-68ad-4b5d-a6d2-cbc20515b1f7' - ) - expect(secondProtocol.protocolKey).toBe( - '26ed5a82-502f-4074-8981-57cdda1d066d' - ) - expect(thirdProtocol.protocolKey).toBe( - '3dc99ffa-f85e-4c01-ab0a-edecff432dac' - ) - }) - - it('should return an object with protocols sorted reverse alphabetically', () => { - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => {children} - - const { result } = renderHook( - () => useSortedProtocols('reverse', mockStoredProtocolData), - { wrapper } - ) - const firstProtocol = result.current[0] - const secondProtocol = result.current[1] - const thirdProtocol = result.current[2] - - expect(firstProtocol.protocolKey).toBe( - '3dc99ffa-f85e-4c01-ab0a-edecff432dac' - ) - expect(secondProtocol.protocolKey).toBe( - '26ed5a82-502f-4074-8981-57cdda1d066d' - ) - expect(thirdProtocol.protocolKey).toBe( - 'f130337e-68ad-4b5d-a6d2-cbc20515b1f7' - ) - }) - - it('should return an object with protocols sorted by most recent modified data', () => { - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => {children} - - const { result } = renderHook( - () => useSortedProtocols('recent', mockStoredProtocolData), - { wrapper } - ) - const firstProtocol = result.current[0] - const secondProtocol = result.current[1] - const thirdProtocol = result.current[2] - - expect(firstProtocol.protocolKey).toBe( - '3dc99ffa-f85e-4c01-ab0a-edecff432dac' - ) - expect(secondProtocol.protocolKey).toBe( - 'f130337e-68ad-4b5d-a6d2-cbc20515b1f7' - ) - expect(thirdProtocol.protocolKey).toBe( - '26ed5a82-502f-4074-8981-57cdda1d066d' - ) - }) - - it('should return an object with protocols sorted by oldest modified data', () => { - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => {children} - - const { result } = renderHook( - () => useSortedProtocols('oldest', mockStoredProtocolData), - { wrapper } - ) - const firstProtocol = result.current[0] - const secondProtocol = result.current[1] - const thirdProtocol = result.current[2] - - expect(firstProtocol.protocolKey).toBe( - '26ed5a82-502f-4074-8981-57cdda1d066d' - ) - expect(secondProtocol.protocolKey).toBe( - 'f130337e-68ad-4b5d-a6d2-cbc20515b1f7' - ) - expect(thirdProtocol.protocolKey).toBe( - '3dc99ffa-f85e-4c01-ab0a-edecff432dac' - ) - }) - - it('should return an object with protocols sorted by flex then ot-2', () => { - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => {children} - - const { result } = renderHook( - () => useSortedProtocols('flex', mockStoredProtocolData), - { wrapper } - ) - const firstProtocol = result.current[0] - const secondProtocol = result.current[1] - const thirdProtocol = result.current[2] - - expect(firstProtocol.protocolKey).toBe( - '26ed5a82-502f-4074-8981-57cdda1d066d' - ) - expect(secondProtocol.protocolKey).toBe( - '3dc99ffa-f85e-4c01-ab0a-edecff432dac' - ) - expect(thirdProtocol.protocolKey).toBe( - 'f130337e-68ad-4b5d-a6d2-cbc20515b1f7' - ) - }) - it('should return an object with protocols sorted by ot-2 then flex', () => { - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => {children} - - const { result } = renderHook( - () => useSortedProtocols('ot2', mockStoredProtocolData), - { wrapper } - ) - const firstProtocol = result.current[0] - const secondProtocol = result.current[1] - const thirdProtocol = result.current[2] - - expect(firstProtocol.protocolKey).toBe( - '3dc99ffa-f85e-4c01-ab0a-edecff432dac' - ) - expect(secondProtocol.protocolKey).toBe( - 'f130337e-68ad-4b5d-a6d2-cbc20515b1f7' - ) - expect(thirdProtocol.protocolKey).toBe( - '26ed5a82-502f-4074-8981-57cdda1d066d' - ) - }) -}) diff --git a/app/src/organisms/ProtocolsLanding/__tests__/utils.test.ts b/app/src/organisms/ProtocolsLanding/__tests__/utils.test.ts deleted file mode 100644 index 1ff0d74f72a..00000000000 --- a/app/src/organisms/ProtocolsLanding/__tests__/utils.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { - getAnalysisStatus, - getisFlexProtocol, - getRobotTypeDisplayName, -} from '../utils' -import type { ProtocolAnalysisOutput } from '@opentrons/shared-data' - -const mockOT3ProtocolAnalysisOutput = { - robotType: 'OT-3 Standard', -} as ProtocolAnalysisOutput - -const mockOT2ProtocolAnalysisOutput = { - robotType: 'OT-2 Standard', -} as ProtocolAnalysisOutput - -describe('getAnalysisStatus', () => { - it('should return stale if no liquids in analysis', () => { - const result = getAnalysisStatus(false, { - ...mockOT3ProtocolAnalysisOutput, - liquids: [], - errors: [], - }) - expect(result).toBe('stale') - }) - - it('should return stale if no runTimeParameters in analysis', () => { - const result = getAnalysisStatus(false, { - ...mockOT3ProtocolAnalysisOutput, - runTimeParameters: [], - errors: [], - }) - expect(result).toBe('stale') - }) - - it('should return complete if liquids and runTimeParameters in analysis', () => { - const result = getAnalysisStatus(false, { - ...mockOT3ProtocolAnalysisOutput, - liquids: [], - runTimeParameters: [], - errors: [], - }) - expect(result).toBe('complete') - }) -}) - -describe('getisFlexProtocol', () => { - it('should return true for protocols intended for a Flex', () => { - const result = getisFlexProtocol(mockOT3ProtocolAnalysisOutput) - expect(result).toBe(true) - }) - - it('should return false for protocols intended for an OT-2', () => { - const result = getisFlexProtocol(mockOT2ProtocolAnalysisOutput) - expect(result).toBe(false) - }) - - it('should return false for protocols that do not specify a robot type', () => { - const result = getisFlexProtocol({} as ProtocolAnalysisOutput) - expect(result).toBe(false) - }) - - it('should return false given null', () => { - const result = getisFlexProtocol(null) - expect(result).toBe(false) - }) -}) - -describe('getRobotTypeDisplayName', () => { - it('should return OT-3 for protocols intended for a Flex', () => { - const result = getRobotTypeDisplayName('OT-3 Standard') - expect(result).toBe('Opentrons Flex') - }) - - it('should return OT-2 for protocols intended for an OT-2', () => { - const result = getRobotTypeDisplayName('OT-2 Standard') - expect(result).toBe('OT-2') - }) - - it('should return OT-2 for protocols that do not specify a robot type', () => { - const result = getRobotTypeDisplayName(null) - expect(result).toBe('OT-2') - }) -}) diff --git a/app/src/organisms/ProtocolsLanding/hooks.tsx b/app/src/organisms/ProtocolsLanding/hooks.tsx deleted file mode 100644 index c067cd34f98..00000000000 --- a/app/src/organisms/ProtocolsLanding/hooks.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data' -import { getProtocolDisplayName } from './utils' -import type { StoredProtocolData } from '../../redux/protocol-storage' - -export type ProtocolSort = - | 'alphabetical' - | 'reverse' - | 'recent' - | 'oldest' - | 'flex' - | 'ot2' - -export function useSortedProtocols( - sortBy: ProtocolSort, - protocolData: StoredProtocolData[] -): StoredProtocolData[] { - protocolData.sort((a, b) => { - const protocolNameA = getProtocolDisplayName( - a.protocolKey, - a.srcFileNames, - a?.mostRecentAnalysis - ) - const protocolNameB = getProtocolDisplayName( - b.protocolKey, - b.srcFileNames, - b?.mostRecentAnalysis - ) - const protocolRobotTypeA = a?.mostRecentAnalysis?.robotType - const protocolRobotTypeB = b?.mostRecentAnalysis?.robotType - - if (sortBy === 'alphabetical') { - if (protocolNameA.toLowerCase() === protocolNameB.toLowerCase()) { - return b.modified - a.modified - } - return protocolNameA.toLowerCase() > protocolNameB.toLowerCase() ? 1 : -1 - } else if (sortBy === 'reverse') { - return protocolNameA.toLowerCase() > protocolNameB.toLowerCase() ? -1 : 1 - } else if (sortBy === 'recent') { - return b.modified - a.modified - } else if (sortBy === 'oldest') { - return a.modified - b.modified - } else if (sortBy === 'flex') { - if ( - protocolRobotTypeA === FLEX_ROBOT_TYPE && - protocolRobotTypeB !== FLEX_ROBOT_TYPE - ) { - return -1 - } - if ( - protocolRobotTypeA !== FLEX_ROBOT_TYPE && - protocolRobotTypeB === FLEX_ROBOT_TYPE - ) { - return 1 - } - return b.modified - a.modified - } else if (sortBy === 'ot2') { - if ( - protocolRobotTypeA !== FLEX_ROBOT_TYPE && - protocolRobotTypeB === FLEX_ROBOT_TYPE - ) { - return -1 - } - if ( - protocolRobotTypeA === FLEX_ROBOT_TYPE && - protocolRobotTypeB !== FLEX_ROBOT_TYPE - ) { - return 1 - } - return b.modified - a.modified - } - return 0 - }) - return protocolData -} diff --git a/app/src/organisms/ProtocolsLanding/utils.ts b/app/src/organisms/ProtocolsLanding/utils.ts deleted file mode 100644 index 1656346ca3a..00000000000 --- a/app/src/organisms/ProtocolsLanding/utils.ts +++ /dev/null @@ -1,62 +0,0 @@ -import first from 'lodash/first' -import { FLEX_STANDARD_MODEL } from '@opentrons/shared-data' -import type { ProtocolAnalysisOutput, RobotType } from '@opentrons/shared-data' - -type AnalysisStatus = - | 'missing' - | 'loading' - | 'error' - | 'complete' - | 'stale' - | 'parameterRequired' - -export function getAnalysisStatus( - isAnalyzing: boolean, - analysis?: ProtocolAnalysisOutput | null -): AnalysisStatus { - if (isAnalyzing) { - return 'loading' - } - if (analysis == null || analysis === undefined) { - return 'missing' - } - if (analysis.liquids == null || analysis.runTimeParameters == null) { - return 'stale' - } - if (analysis.result === 'parameter-value-required') { - return 'parameterRequired' - } - if (analysis.errors.length > 0) { - return 'error' - } - return 'complete' -} - -export function getProtocolDisplayName( - protocolKey: string, - srcFileNames: string[], - analysis?: ProtocolAnalysisOutput | null -): string { - return analysis?.metadata?.protocolName ?? first(srcFileNames) ?? protocolKey -} - -export function getRobotTypeDisplayName( - robotType: RobotType | null -): 'OT-2' | 'Opentrons Flex' { - if (robotType === FLEX_STANDARD_MODEL) { - return 'Opentrons Flex' - } else { - // defaults to OT-2 display name. may want to reconsider for protocols that fail analysis - return 'OT-2' - } -} - -export function getisFlexProtocol( - protocolAnalysis?: ProtocolAnalysisOutput | null -): boolean { - if (protocolAnalysis?.robotType === FLEX_STANDARD_MODEL) { - return true - } else { - return false - } -} diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/BlowOut.tsx b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/BlowOut.tsx deleted file mode 100644 index 9b58e356aae..00000000000 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/BlowOut.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { createPortal } from 'react-dom' -import { - Flex, - SPACING, - DIRECTION_COLUMN, - POSITION_FIXED, - LargeButton, - COLORS, -} from '@opentrons/components' -import { - WASTE_CHUTE_FIXTURES, - FLEX_SINGLE_SLOT_BY_CUTOUT_ID, - TRASH_BIN_ADAPTER_FIXTURE, -} from '@opentrons/shared-data' -import { getTopPortalEl } from '../../../App/portal' -import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' -import { ChildNavigation } from '../../ChildNavigation' -import { ACTIONS } from '../constants' - -import type { DeckConfiguration } from '@opentrons/shared-data' -import type { - QuickTransferSummaryState, - QuickTransferSummaryAction, - FlowRateKind, - BlowOutLocation, - TransferType, -} from '../types' -import { i18n } from '../../../i18n' - -interface BlowOutProps { - onBack: () => void - state: QuickTransferSummaryState - dispatch: React.Dispatch - kind: FlowRateKind -} - -export const useBlowOutLocationOptions = ( - deckConfig: DeckConfiguration, - transferType: TransferType -): Array<{ location: BlowOutLocation; description: string }> => { - const { t } = useTranslation('quick_transfer') - - const trashLocations = deckConfig.filter( - cutoutConfig => - WASTE_CHUTE_FIXTURES.includes(cutoutConfig.cutoutFixtureId) || - TRASH_BIN_ADAPTER_FIXTURE === cutoutConfig.cutoutFixtureId - ) - - // add trash bin in A3 if no trash or waste chute configured - if (trashLocations.length === 0) { - trashLocations.push({ - cutoutId: 'cutoutA3', - cutoutFixtureId: TRASH_BIN_ADAPTER_FIXTURE, - }) - } - const blowOutLocationItems: Array<{ - location: BlowOutLocation - description: string - }> = [] - if (transferType !== 'distribute') { - blowOutLocationItems.push({ - location: 'dest_well', - description: t('blow_out_destination_well'), - }) - } - if (transferType !== 'consolidate') { - blowOutLocationItems.push({ - location: 'source_well', - description: t('blow_out_source_well'), - }) - } - trashLocations.forEach(location => { - blowOutLocationItems.push({ - location, - description: - location.cutoutFixtureId === TRASH_BIN_ADAPTER_FIXTURE - ? t('trashBin_location', { - slotName: FLEX_SINGLE_SLOT_BY_CUTOUT_ID[location.cutoutId], - }) - : t('wasteChute_location', { - slotName: FLEX_SINGLE_SLOT_BY_CUTOUT_ID[location.cutoutId], - }), - }) - }) - return blowOutLocationItems -} - -export function BlowOut(props: BlowOutProps): JSX.Element { - const { onBack, state, dispatch } = props - const { t } = useTranslation('quick_transfer') - const deckConfig = useNotifyDeckConfigurationQuery().data ?? [] - - const [isBlowOutEnabled, setisBlowOutEnabled] = React.useState( - state.blowOut != null - ) - const [currentStep, setCurrentStep] = React.useState(1) - const [blowOutLocation, setBlowOutLocation] = React.useState< - BlowOutLocation | undefined - >(state.blowOut) - - const enableBlowOutDisplayItems = [ - { - value: true, - description: t('option_enabled'), - onClick: () => { - setisBlowOutEnabled(true) - }, - }, - { - value: false, - description: t('option_disabled'), - onClick: () => { - setisBlowOutEnabled(false) - }, - }, - ] - - const blowOutLocationItems = useBlowOutLocationOptions( - deckConfig, - state.transferType - ) - - const handleClickBackOrExit = (): void => { - currentStep > 1 ? setCurrentStep(currentStep - 1) : onBack() - } - - const handleClickSaveOrContinue = (): void => { - if (currentStep === 1) { - if (!isBlowOutEnabled) { - dispatch({ - type: ACTIONS.SET_BLOW_OUT, - location: undefined, - }) - onBack() - } else { - setCurrentStep(currentStep + 1) - } - } else { - dispatch({ - type: ACTIONS.SET_BLOW_OUT, - location: blowOutLocation, - }) - onBack() - } - } - - const saveOrContinueButtonText = - isBlowOutEnabled && currentStep < 2 - ? t('shared:continue') - : t('shared:save') - - let buttonIsDisabled = false - if (currentStep === 2) { - buttonIsDisabled = blowOutLocation == null - } - - return createPortal( - - - {currentStep === 1 ? ( - - {enableBlowOutDisplayItems.map(displayItem => ( - { - setisBlowOutEnabled(displayItem.value) - }} - buttonText={displayItem.description} - /> - ))} - - ) : null} - {currentStep === 2 ? ( - - {blowOutLocationItems.map(blowOutLocationItem => ( - { - setBlowOutLocation( - blowOutLocationItem.location as BlowOutLocation - ) - }} - buttonText={blowOutLocationItem.description} - /> - ))} - - ) : null} - , - getTopPortalEl() - ) -} diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/PipettePath.tsx b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/PipettePath.tsx deleted file mode 100644 index ddea515f952..00000000000 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/PipettePath.tsx +++ /dev/null @@ -1,237 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { createPortal } from 'react-dom' - -import { - ALIGN_CENTER, - COLORS, - DIRECTION_COLUMN, - Flex, - InputField, - LargeButton, - POSITION_FIXED, - SPACING, -} from '@opentrons/components' - -import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' -import { getTopPortalEl } from '../../../App/portal' -import { ChildNavigation } from '../../ChildNavigation' -import { useBlowOutLocationOptions } from './BlowOut' - -import { ACTIONS } from '../constants' -import { i18n } from '../../../i18n' -import { NumericalKeyboard } from '../../../atoms/SoftwareKeyboard' - -import type { - PathOption, - QuickTransferSummaryState, - QuickTransferSummaryAction, - BlowOutLocation, -} from '../types' - -interface PipettePathProps { - onBack: () => void - state: QuickTransferSummaryState - dispatch: React.Dispatch -} - -export function PipettePath(props: PipettePathProps): JSX.Element { - const { onBack, state, dispatch } = props - const { t } = useTranslation('quick_transfer') - const keyboardRef = React.useRef(null) - const deckConfig = useNotifyDeckConfigurationQuery().data ?? [] - - const [selectedPath, setSelectedPath] = React.useState(state.path) - const [currentStep, setCurrentStep] = React.useState(1) - const [blowOutLocation, setBlowOutLocation] = React.useState< - BlowOutLocation | undefined - >(state.blowOut) - - const [disposalVolume, setDisposalVolume] = React.useState( - state.volume - ) - const maxPipetteVolume = Object.values(state.pipette.liquids)[0].maxVolume - const tipVolume = Object.values(state.tipRack.wells)[0].totalLiquidVolume - - // this is the max amount of liquid that can be held in the tip at any time - const maxTipCapacity = Math.min(maxPipetteVolume, tipVolume) - - const allowedPipettePathOptions: Array<{ - pathOption: PathOption - description: string - }> = [{ pathOption: 'single', description: t('pipette_path_single') }] - if ( - state.transferType === 'distribute' && - maxTipCapacity >= state.volume * 3 - ) { - // we have the capacity for a multi dispense if we can fit at least 2x the volume per well - // for aspiration plus 1x the volume per well for disposal volume - allowedPipettePathOptions.push({ - pathOption: 'multiDispense', - description: t('pipette_path_multi_dispense'), - }) - // for multi aspirate we only need at least 2x the volume per well - } else if ( - state.transferType === 'consolidate' && - maxTipCapacity >= state.volume * 2 - ) { - allowedPipettePathOptions.push({ - pathOption: 'multiAspirate', - description: t('pipette_path_multi_aspirate'), - }) - } - - const blowOutLocationItems = useBlowOutLocationOptions( - deckConfig, - state.transferType - ) - - const handleClickBackOrExit = (): void => { - currentStep > 1 ? setCurrentStep(currentStep - 1) : onBack() - } - - const handleClickSaveOrContinue = (): void => { - if (currentStep === 1) { - if (selectedPath !== 'multiDispense') { - dispatch({ - type: ACTIONS.SET_PIPETTE_PATH, - path: selectedPath, - }) - onBack() - } else { - setCurrentStep(2) - } - } else if (currentStep === 2) { - setCurrentStep(3) - } else { - dispatch({ - type: ACTIONS.SET_PIPETTE_PATH, - path: selectedPath as PathOption, - disposalVolume, - blowOutLocation, - }) - onBack() - } - } - - const saveOrContinueButtonText = - selectedPath === 'multiDispense' && currentStep < 3 - ? t('shared:continue') - : t('shared:save') - - const maxDisposalCapacity = maxTipCapacity - state.volume * 2 - const volumeRange = { min: 1, max: maxDisposalCapacity } - - const volumeError = - disposalVolume !== null && - (disposalVolume < volumeRange.min || disposalVolume > volumeRange.max) - ? t(`value_out_of_range`, { - min: volumeRange.min, - max: volumeRange.max, - }) - : null - - let buttonIsDisabled = false - if (currentStep === 2) { - buttonIsDisabled = disposalVolume == null || volumeError != null - } else if (currentStep === 3) { - buttonIsDisabled = blowOutLocation == null - } - - return createPortal( - - - {currentStep === 1 ? ( - - {allowedPipettePathOptions.map(option => ( - { - setSelectedPath(option.pathOption) - }} - buttonText={option.description} - /> - ))} - - ) : null} - {currentStep === 2 ? ( - - - - - - { - setDisposalVolume(Number(e)) - }} - /> - - - ) : null} - {currentStep === 3 ? ( - - {blowOutLocationItems.map(option => ( - { - setBlowOutLocation(option.location) - }} - buttonText={option.description} - /> - ))} - - ) : null} - , - getTopPortalEl() - ) -} diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/index.tsx b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/index.tsx deleted file mode 100644 index 2521cc900ca..00000000000 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/index.tsx +++ /dev/null @@ -1,614 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { - ALIGN_CENTER, - COLORS, - DIRECTION_COLUMN, - Flex, - Icon, - JUSTIFY_SPACE_BETWEEN, - ListItem, - SIZE_2, - SPACING, - StyledText, - TEXT_ALIGN_LEFT, - TEXT_ALIGN_RIGHT, - TYPOGRAPHY, -} from '@opentrons/components' -import { - TRASH_BIN_ADAPTER_FIXTURE, - WASTE_CHUTE_FIXTURES, -} from '@opentrons/shared-data' -import { ACTIONS } from '../constants' -import { useToaster } from '../../../organisms/ToasterOven' -import { FlowRateEntry } from './FlowRate' -import { PipettePath } from './PipettePath' -import { TipPositionEntry } from './TipPosition' -import { Mix } from './Mix' -import { Delay } from './Delay' -import { TouchTip } from './TouchTip' -import { AirGap } from './AirGap' -import { BlowOut } from './BlowOut' - -import type { - QuickTransferSummaryAction, - QuickTransferSummaryState, -} from '../types' -interface QuickTransferAdvancedSettingsProps { - state: QuickTransferSummaryState - dispatch: React.Dispatch -} - -export function QuickTransferAdvancedSettings( - props: QuickTransferAdvancedSettingsProps -): JSX.Element | null { - const { state, dispatch } = props - const { t, i18n } = useTranslation(['quick_transfer', 'shared']) - const [selectedSetting, setSelectedSetting] = React.useState( - null - ) - const { makeSnackbar } = useToaster() - - function getBlowoutValueCopy(): string | undefined { - if (state.blowOut === 'dest_well') { - return t('blow_out_into_destination_well') - } else if (state.blowOut === 'source_well') { - return t('blow_out_into_source_well') - } else if ( - state.blowOut != null && - state.blowOut.cutoutFixtureId === TRASH_BIN_ADAPTER_FIXTURE - ) { - return t('blow_out_into_trash_bin') - } else if ( - state.blowOut != null && - WASTE_CHUTE_FIXTURES.includes(state.blowOut.cutoutFixtureId) - ) { - return t('blow_out_into_waste_chute') - } - } - - let pipettePathValue: string = '' - if (state.path === 'single') { - pipettePathValue = t('pipette_path_single') - } else if (state.path === 'multiAspirate') { - pipettePathValue = t('pipette_path_multi_aspirate') - } else if (state.path === 'multiDispense') { - pipettePathValue = t('pipette_path_multi_dispense_volume_blowout', { - volume: state.disposalVolume, - blowOutLocation: getBlowoutValueCopy(), - }) - } - - const destinationLabwareDef = - state.destination === 'source' ? state.source : state.destination - const sourceIsReservoir = - state.source.metadata.displayCategory === 'reservoir' - const destIsReservoir = - destinationLabwareDef.metadata.displayCategory === 'reservoir' - - const baseSettingsItems = [ - { - option: 'aspirate_flow_rate', - copy: t('aspirate_flow_rate'), - value: t('flow_rate_value', { flow_rate: state.aspirateFlowRate }), - enabled: true, - onClick: () => { - setSelectedSetting('aspirate_flow_rate') - }, - }, - { - option: 'dispense_flow_rate', - copy: t('dispense_flow_rate'), - value: t('flow_rate_value', { flow_rate: state.dispenseFlowRate }), - enabled: true, - onClick: () => { - setSelectedSetting('dispense_flow_rate') - }, - }, - { - option: 'pipette_path', - copy: t('pipette_path'), - value: pipettePathValue, - enabled: state.transferType !== 'transfer', - onClick: () => { - if (state.transferType !== 'transfer') { - setSelectedSetting('pipette_path') - } else { - makeSnackbar(t('advanced_setting_disabled') as string) - } - }, - }, - ] - - const aspirateSettingsItems = [ - { - option: 'tip_position', - copy: t('tip_position'), - value: - state.tipPositionAspirate !== null - ? t('tip_position_value', { position: state.tipPositionAspirate }) - : '', - enabled: true, - onClick: () => { - setSelectedSetting('aspirate_tip_position') - }, - }, - { - option: 'pre_wet_tip', - copy: t('pre_wet_tip'), - value: state.preWetTip ? t('option_enabled') : '', - enabled: true, - onClick: () => { - dispatch({ - type: ACTIONS.SET_PRE_WET_TIP, - preWetTip: !state.preWetTip, - }) - }, - }, - { - option: 'aspirate_mix', - copy: t('mix'), - value: - state.mixOnAspirate !== undefined - ? t('mix_value', { - volume: state.mixOnAspirate?.mixVolume, - reps: state.mixOnAspirate?.repititions, - }) - : '', - enabled: - state.transferType === 'transfer' || - state.transferType === 'distribute', - onClick: () => { - if ( - state.transferType === 'transfer' || - state.transferType === 'distribute' - ) { - setSelectedSetting('aspirate_mix') - } else { - makeSnackbar(t('advanced_setting_disabled') as string) - } - }, - }, - { - option: 'aspirate_delay', - copy: t('delay'), - value: - state.delayAspirate !== undefined - ? t('delay_value', { - delay: state.delayAspirate.delayDuration, - position: state.delayAspirate.positionFromBottom, - }) - : '', - enabled: true, - onClick: () => { - setSelectedSetting('aspirate_delay') - }, - }, - { - option: 'aspirate_touch_tip', - copy: t('touch_tip'), - value: - state.touchTipAspirate !== undefined - ? t('touch_tip_value', { position: state.touchTipAspirate }) - : '', - enabled: !sourceIsReservoir, - onClick: () => { - // disable for reservoir - if (!sourceIsReservoir) { - setSelectedSetting('aspirate_touch_tip') - } else { - makeSnackbar(t('advanced_setting_disabled') as string) - } - }, - }, - { - option: 'aspirate_air_gap', - copy: t('air_gap'), - value: - state.airGapAspirate !== undefined - ? t('air_gap_value', { volume: state.airGapAspirate }) - : '', - enabled: true, - onClick: () => { - setSelectedSetting('aspirate_air_gap') - }, - }, - ] - - const dispenseSettingsItems = [ - { - option: 'dispense_tip_position', - copy: t('tip_position'), - value: - state.tipPositionDispense !== undefined - ? t('tip_position_value', { position: state.tipPositionDispense }) - : '', - enabled: true, - onClick: () => { - setSelectedSetting('dispense_tip_position') - }, - }, - { - option: 'dispense_mix', - copy: t('mix'), - value: - state.mixOnDispense !== undefined - ? t('mix_value', { - volume: state.mixOnDispense?.mixVolume, - reps: state.mixOnDispense?.repititions, - }) - : '', - enabled: - state.transferType === 'transfer' || - state.transferType === 'consolidate', - onClick: () => { - if ( - state.transferType === 'transfer' || - state.transferType === 'consolidate' - ) { - setSelectedSetting('dispense_mix') - } else { - makeSnackbar(t('advanced_setting_disabled') as string) - } - }, - }, - { - option: 'dispense_delay', - copy: t('delay'), - value: - state.delayDispense !== undefined - ? t('delay_value', { - delay: state.delayDispense.delayDuration, - position: state.delayDispense.positionFromBottom, - }) - : '', - enabled: true, - onClick: () => { - setSelectedSetting('dispense_delay') - }, - }, - { - option: 'dispense_touch_tip', - copy: t('touch_tip'), - value: - state.touchTipDispense !== undefined - ? t('touch_tip_value', { position: state.touchTipDispense }) - : '', - enabled: !destIsReservoir, - onClick: () => { - if (!destIsReservoir) { - setSelectedSetting('dispense_touch_tip') - } else { - makeSnackbar(t('advanced_setting_disabled') as string) - } - }, - }, - { - option: 'dispense_air_gap', - copy: t('air_gap'), - value: - state.airGapDispense !== undefined - ? t('air_gap_value', { volume: state.airGapDispense }) - : '', - enabled: true, - onClick: () => { - setSelectedSetting('dispense_air_gap') - }, - }, - { - option: 'dispense_blow_out', - copy: t('blow_out'), - value: - state.transferType === 'distribute' - ? t('disabled') - : i18n.format(getBlowoutValueCopy(), 'capitalize'), - enabled: state.transferType !== 'distribute', - onClick: () => { - if (state.transferType === 'distribute') { - makeSnackbar(t('advanced_setting_disabled') as string) - } else { - setSelectedSetting('dispense_blow_out') - } - }, - }, - ] - - return ( - - {/* Base Settings */} - - {selectedSetting == null - ? baseSettingsItems.map(displayItem => ( - - - - {displayItem.copy} - - - - {displayItem.value} - - {displayItem.enabled ? ( - - ) : null} - - - - )) - : null} - {selectedSetting === 'aspirate_flow_rate' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - {selectedSetting === 'dispense_flow_rate' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - {selectedSetting === 'pipette_path' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - - - {/* Aspirate Settings */} - - {selectedSetting === null ? ( - - {t('aspirate_settings')} - - ) : null} - - {selectedSetting === null - ? aspirateSettingsItems.map(displayItem => ( - - - - {displayItem.copy} - - - - {displayItem.value !== '' - ? displayItem.value - : t('option_disabled')} - - - {displayItem.option !== 'pre_wet_tip' ? ( - - ) : null} - - - - )) - : null} - {selectedSetting === 'aspirate_tip_position' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - {selectedSetting === 'aspirate_mix' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - {selectedSetting === 'aspirate_delay' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - {selectedSetting === 'aspirate_touch_tip' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - {selectedSetting === 'aspirate_air_gap' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - - - - {/* Dispense Settings */} - - {selectedSetting === null ? ( - - {t('dispense_settings')} - - ) : null} - - {selectedSetting === null - ? dispenseSettingsItems.map(displayItem => ( - - - - {displayItem.copy} - - - - {displayItem.value !== '' - ? displayItem.value - : t('option_disabled')} - - - - - - )) - : null} - {selectedSetting === 'dispense_tip_position' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - {selectedSetting === 'dispense_mix' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - {selectedSetting === 'dispense_delay' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - {selectedSetting === 'dispense_touch_tip' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - {selectedSetting === 'dispense_air_gap' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - {selectedSetting === 'dispense_blow_out' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - - - - ) -} diff --git a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx b/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx deleted file mode 100644 index bd0aaceb65c..00000000000 --- a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import without from 'lodash/without' -import { - Flex, - JUSTIFY_CENTER, - POSITION_FIXED, - SPACING, -} from '@opentrons/components' -import { getAllDefinitions } from '@opentrons/shared-data' - -import { ChildNavigation } from '../../organisms/ChildNavigation' -import { WellSelection } from '../../organisms/WellSelection' - -import type { SmallButton } from '../../atoms/buttons' -import type { - QuickTransferWizardState, - QuickTransferWizardAction, -} from './types' - -interface SelectSourceWellsProps { - onNext: () => void - onBack: () => void - state: QuickTransferWizardState - dispatch: React.Dispatch -} - -export const CIRCULAR_WELL_96_PLATE_DEFINITION_URI = - 'opentrons/thermoscientificnunc_96_wellplate_2000ul/1' -export const RECTANGULAR_WELL_96_PLATE_DEFINITION_URI = - 'opentrons/usascientific_96_wellplate_2.4ml_deep/1' - -export function SelectSourceWells(props: SelectSourceWellsProps): JSX.Element { - const { onNext, onBack, state, dispatch } = props - const { i18n, t } = useTranslation(['quick_transfer', 'shared']) - - const sourceWells = state.sourceWells ?? [] - const sourceWellGroup = sourceWells.reduce((acc, well) => { - return { ...acc, [well]: null } - }, {}) - - const [selectedWells, setSelectedWells] = React.useState(sourceWellGroup) - - const handleClickNext = (): void => { - dispatch({ - type: 'SET_SOURCE_WELLS', - wells: Object.keys(selectedWells), - }) - onNext() - } - - const resetButtonProps: React.ComponentProps = { - buttonType: 'tertiaryLowLight', - buttonText: t('shared:reset'), - onClick: () => { - setSelectedWells({}) - }, - } - let displayLabwareDefinition = state.source - if (state.source?.parameters.format === '96Standard') { - const allDefinitions = getAllDefinitions() - if (Object.values(state.source.wells)[0].shape === 'circular') { - displayLabwareDefinition = - allDefinitions[CIRCULAR_WELL_96_PLATE_DEFINITION_URI] - } else { - displayLabwareDefinition = - allDefinitions[RECTANGULAR_WELL_96_PLATE_DEFINITION_URI] - } - } - - return ( - <> - - - {state.source != null && displayLabwareDefinition != null ? ( - - { - setSelectedWells(prevWells => - without(Object.keys(prevWells), ...wells).reduce( - (acc, well) => { - return { ...acc, [well]: null } - }, - {} - ) - ) - }} - selectedPrimaryWells={selectedWells} - selectWells={wellGroup => { - setSelectedWells(prevWells => ({ ...prevWells, ...wellGroup })) - }} - channels={state.pipette?.channels ?? 1} - /> - - ) : null} - - - ) -} diff --git a/app/src/organisms/QuickTransferFlow/SummaryAndSettings.tsx b/app/src/organisms/QuickTransferFlow/SummaryAndSettings.tsx deleted file mode 100644 index 44cfd13f5eb..00000000000 --- a/app/src/organisms/QuickTransferFlow/SummaryAndSettings.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useNavigate } from 'react-router-dom' -import { useQueryClient } from 'react-query' -import { - Flex, - SPACING, - DIRECTION_COLUMN, - DIRECTION_ROW, - COLORS, - POSITION_FIXED, - ALIGN_CENTER, - Tabs, -} from '@opentrons/components' -import { - useCreateProtocolMutation, - useCreateRunMutation, - useHost, -} from '@opentrons/react-api-client' -import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' - -import { ChildNavigation } from '../ChildNavigation' -import { Overview } from './Overview' -import { TipManagement } from './TipManagement' -import { QuickTransferAdvancedSettings } from './QuickTransferAdvancedSettings' -import { SaveOrRunModal } from './SaveOrRunModal' -import { getInitialSummaryState, createQuickTransferFile } from './utils' -import { quickTransferSummaryReducer } from './reducers' - -import type { SmallButton } from '../../atoms/buttons' -import type { QuickTransferWizardState } from './types' - -interface SummaryAndSettingsProps { - exitButtonProps: React.ComponentProps - state: QuickTransferWizardState -} - -export function SummaryAndSettings( - props: SummaryAndSettingsProps -): JSX.Element | null { - const { exitButtonProps, state: wizardFlowState } = props - const navigate = useNavigate() - const queryClient = useQueryClient() - const host = useHost() - const { t } = useTranslation(['quick_transfer', 'shared']) - const [showSaveOrRunModal, setShowSaveOrRunModal] = React.useState( - false - ) - - const displayCategory: string[] = [ - 'overview', - 'advanced_settings', - 'tip_management', - ] - const [selectedCategory, setSelectedCategory] = React.useState( - 'overview' - ) - const deckConfig = useNotifyDeckConfigurationQuery().data ?? [] - - const initialSummaryState = getInitialSummaryState({ - // @ts-expect-error TODO figure out how to make this type non-null as we know - // none of these values will be undefined - state: wizardFlowState, - deckConfig, - }) - const [state, dispatch] = React.useReducer( - quickTransferSummaryReducer, - initialSummaryState - ) - - const { mutateAsync: createProtocolAsync } = useCreateProtocolMutation() - - const { createRun } = useCreateRunMutation( - { - onSuccess: data => { - queryClient.invalidateQueries([host, 'runs']).catch((e: Error) => { - console.error(`error invalidating runs query: ${e.message}`) - }) - navigate(`/runs/${data.data.id}/setup`) - }, - }, - host - ) - - const handleClickSave = (protocolName: string): void => { - const protocolFile = createQuickTransferFile( - state, - deckConfig, - protocolName - ) - createProtocolAsync({ - files: [protocolFile], - protocolKind: 'quick-transfer', - }).then(() => { - navigate('/quick-transfer') - }) - } - - const handleClickRun = (): void => { - const protocolFile = createQuickTransferFile(state, deckConfig) - createProtocolAsync({ - files: [protocolFile], - protocolKind: 'quick-transfer', - }).then(data => { - createRun({ - protocolId: data.data.id, - }) - }) - } - - return showSaveOrRunModal ? ( - - ) : ( - - { - setShowSaveOrRunModal(true) - }} - secondaryButtonProps={exitButtonProps} - /> - - - ({ - text: t(category), - onClick: () => { - setSelectedCategory(category) - }, - isActive: category === selectedCategory, - disabled: false, - }))} - /> - - {selectedCategory === 'overview' ? : null} - {selectedCategory === 'advanced_settings' ? ( - - ) : null} - {selectedCategory === 'tip_management' ? ( - - ) : null} - - - ) -} diff --git a/app/src/organisms/QuickTransferFlow/TipManagement/index.tsx b/app/src/organisms/QuickTransferFlow/TipManagement/index.tsx deleted file mode 100644 index adbd698bc5a..00000000000 --- a/app/src/organisms/QuickTransferFlow/TipManagement/index.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { - ALIGN_CENTER, - COLORS, - DIRECTION_COLUMN, - Flex, - Icon, - JUSTIFY_SPACE_BETWEEN, - LegacyStyledText, - ListItem, - SIZE_2, - SPACING, - TEXT_ALIGN_RIGHT, - TYPOGRAPHY, -} from '@opentrons/components' -import { TRASH_BIN_ADAPTER_FIXTURE } from '@opentrons/shared-data' -import { ChangeTip } from './ChangeTip' -import { TipDropLocation } from './TipDropLocation' - -import type { - QuickTransferSummaryAction, - QuickTransferSummaryState, -} from '../types' - -interface TipManagementProps { - state: QuickTransferSummaryState - dispatch: React.Dispatch -} - -export function TipManagement(props: TipManagementProps): JSX.Element | null { - const { state, dispatch } = props - const { t } = useTranslation(['quick_transfer', 'shared']) - const [selectedSetting, setSelectedSetting] = React.useState( - null - ) - - const displayItems = [ - { - option: t('change_tip'), - value: t(`${state.changeTip}`), - onClick: () => { - setSelectedSetting('change_tip') - }, - }, - { - option: t('tip_drop_location'), - value: t( - `${ - state.dropTipLocation.cutoutFixtureId === TRASH_BIN_ADAPTER_FIXTURE - ? 'trashBin' - : 'wasteChute' - }` - ), - onClick: () => { - setSelectedSetting('tip_drop_location') - }, - }, - ] - - return ( - - {selectedSetting == null - ? displayItems.map(displayItem => ( - - - - {displayItem.option} - - - - {displayItem.value} - - - - - - )) - : null} - {selectedSetting === 'change_tip' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - {selectedSetting === 'tip_drop_location' ? ( - { - setSelectedSetting(null) - }} - /> - ) : null} - - ) -} diff --git a/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/TipDropLocation.test.tsx b/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/TipDropLocation.test.tsx deleted file mode 100644 index faaf3c16ca7..00000000000 --- a/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/TipDropLocation.test.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import * as React from 'react' -import { fireEvent, screen } from '@testing-library/react' -import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' - -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { useNotifyDeckConfigurationQuery } from '../../../../resources/deck_configuration' -import { TipDropLocation } from '../../TipManagement/TipDropLocation' - -vi.mock('../../../../resources/deck_configuration') - -const render = (props: React.ComponentProps): any => { - return renderWithProviders(, { - i18nInstance: i18n, - }) -} - -describe('TipDropLocation', () => { - let props: React.ComponentProps - - beforeEach(() => { - props = { - onBack: vi.fn(), - state: { - dropTipLocation: 'trashBin', - } as any, - dispatch: vi.fn(), - } - vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ - data: [ - { - cutoutId: 'cutoutC3', - cutoutFixtureId: 'wasteChuteRightAdapterCovered', - }, - { - cutoutId: 'cutoutA3', - cutoutFixtureId: 'trashBinAdapter', - }, - ], - } as any) - }) - afterEach(() => { - vi.resetAllMocks() - }) - - it('renders tip drop location screen, header and save button', () => { - render(props) - screen.getByText('Tip drop location') - const saveBtn = screen.getByText('Save') - fireEvent.click(saveBtn) - expect(props.onBack).toHaveBeenCalled() - }) - it('renders options for each dipsosal location in deck config', () => { - render(props) - screen.getByText('Trash bin in A3') - screen.getByText('Waste chute in C3') - }) - it('calls dispatch when you select a new option and save', () => { - render(props) - const wasteChute = screen.getByText('Waste chute in C3') - fireEvent.click(wasteChute) - const saveBtn = screen.getByText('Save') - fireEvent.click(saveBtn) - expect(props.dispatch).toHaveBeenCalled() - }) -}) diff --git a/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/TipManagement.test.tsx b/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/TipManagement.test.tsx deleted file mode 100644 index 97e231a15ec..00000000000 --- a/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/TipManagement.test.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import * as React from 'react' -import { fireEvent, screen } from '@testing-library/react' -import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' - -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { ChangeTip } from '../../TipManagement/ChangeTip' -import { TipDropLocation } from '../../TipManagement/TipDropLocation' -import { TipManagement } from '../../TipManagement/' - -vi.mock('../../TipManagement/ChangeTip') -vi.mock('../../TipManagement/TipDropLocation') - -const render = (props: React.ComponentProps): any => { - return renderWithProviders(, { - i18nInstance: i18n, - }) -} - -describe('TipManagement', () => { - let props: React.ComponentProps - - beforeEach(() => { - props = { - state: { - changeTip: 'once', - dropTipLocation: { - cutoutFixtureId: 'trashBinAdapter', - }, - } as any, - dispatch: vi.fn(), - } - }) - afterEach(() => { - vi.resetAllMocks() - }) - - it('renders tip management options and their values', () => { - render(props) - screen.getByText('Change tip') - screen.getByText('Once at the start of the transfer') - screen.getByText('Tip drop location') - screen.getByText('Trash bin') - }) - it('renders Change tip component when seleted', () => { - render(props) - const changeTip = screen.getByText('Change tip') - fireEvent.click(changeTip) - expect(vi.mocked(ChangeTip)).toHaveBeenCalled() - }) - it('renders Drop tip location component when seleted', () => { - render(props) - const tipDrop = screen.getByText('Tip drop location') - fireEvent.click(tipDrop) - expect(vi.mocked(TipDropLocation)).toHaveBeenCalled() - }) -}) diff --git a/app/src/organisms/QuickTransferFlow/index.tsx b/app/src/organisms/QuickTransferFlow/index.tsx deleted file mode 100644 index 6b589ca0b45..00000000000 --- a/app/src/organisms/QuickTransferFlow/index.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import * as React from 'react' -import { useNavigate } from 'react-router-dom' -import { useTranslation } from 'react-i18next' -import { - useConditionalConfirm, - StepMeter, - POSITION_STICKY, -} from '@opentrons/components' -import { ConfirmExitModal } from './ConfirmExitModal' -import { CreateNewTransfer } from './CreateNewTransfer' -import { SelectPipette } from './SelectPipette' -import { SelectTipRack } from './SelectTipRack' -import { SelectSourceLabware } from './SelectSourceLabware' -import { SelectSourceWells } from './SelectSourceWells' -import { SelectDestLabware } from './SelectDestLabware' -import { SelectDestWells } from './SelectDestWells' -import { VolumeEntry } from './VolumeEntry' -import { SummaryAndSettings } from './SummaryAndSettings' -import { quickTransferWizardReducer } from './reducers' - -import type { SmallButton } from '../../atoms/buttons' -import type { QuickTransferWizardState } from './types' - -const QUICK_TRANSFER_WIZARD_STEPS = 8 -const initialQuickTransferState: QuickTransferWizardState = {} - -export const QuickTransferFlow = (): JSX.Element => { - const navigate = useNavigate() - const { i18n, t } = useTranslation(['quick_transfer', 'shared']) - const [state, dispatch] = React.useReducer( - quickTransferWizardReducer, - initialQuickTransferState - ) - const [currentStep, setCurrentStep] = React.useState(0) - - const { - confirm: confirmExit, - showConfirmation: showConfirmExit, - cancel: cancelExit, - } = useConditionalConfirm(() => { - navigate('/quick-transfer') - }, true) - - const exitButtonProps: React.ComponentProps = { - buttonType: 'tertiaryLowLight', - buttonText: i18n.format(t('shared:exit'), 'capitalize'), - onClick: confirmExit, - } - const sharedMiddleStepProps = { - state, - dispatch, - onBack: () => { - setCurrentStep(prevStep => prevStep - 1) - }, - onNext: () => { - setCurrentStep(prevStep => prevStep + 1) - }, - exitButtonProps, - } - - const contentInOrder: JSX.Element[] = [ - { - setCurrentStep(prevStep => prevStep + 1) - }} - exitButtonProps={exitButtonProps} - />, - , - , - , - , - , - , - , - , - ] - - return ( - <> - {showConfirmExit ? ( - - ) : ( - <> - {currentStep < QUICK_TRANSFER_WIZARD_STEPS ? ( - - ) : null} - {contentInOrder[currentStep]} - - )} - - ) -} diff --git a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationItems.tsx b/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationItems.tsx deleted file mode 100644 index faae54b48d3..00000000000 --- a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/ModuleCalibrationItems.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import styled, { css } from 'styled-components' - -import { - BORDERS, - COLORS, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' -import { getModuleDisplayName } from '@opentrons/shared-data' - -import { formatLastCalibrated } from './utils' -import { ModuleCalibrationOverflowMenu } from './ModuleCalibrationOverflowMenu' - -import type { AttachedModule } from '@opentrons/api-client' -import type { FormattedPipetteOffsetCalibration } from '..' - -interface ModuleCalibrationItemsProps { - attachedModules: AttachedModule[] - updateRobotStatus: (isRobotBusy: boolean) => void - formattedPipetteOffsetCalibrations: FormattedPipetteOffsetCalibration[] - robotName: string -} - -export function ModuleCalibrationItems({ - attachedModules, - updateRobotStatus, - formattedPipetteOffsetCalibrations, - robotName, -}: ModuleCalibrationItemsProps): JSX.Element { - const { t } = useTranslation('device_settings') - - return ( - - - - {t('module')} - {t('serial')} - {t('last_calibrated_label')} - - - - {attachedModules.map(attachedModule => ( - - - - {getModuleDisplayName(attachedModule.moduleModel)} - - - - - {attachedModule.serialNumber} - - - - - {attachedModule.moduleOffset?.last_modified != null - ? formatLastCalibrated( - attachedModule.moduleOffset?.last_modified - ) - : t('not_calibrated_short')} - - - - - - - ))} - - - ) -} - -const StyledTable = styled.table` - width: 100%; - border-collapse: collapse; - text-align: left; -` - -const StyledTableHeader = styled.th` - ${TYPOGRAPHY.labelSemiBold} - padding: ${SPACING.spacing8}; -` - -const StyledTableRow = styled.tr` - padding: ${SPACING.spacing8}; - border-bottom: ${BORDERS.lineBorder}; -` - -const StyledTableCell = styled.td` - padding: ${SPACING.spacing8}; - text-overflow: wrap; -` - -const BODY_STYLE = css` - box-shadow: 0 0 0 1px ${COLORS.grey30}; - border-radius: 3px; -` diff --git a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__fixtures__/index.ts b/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__fixtures__/index.ts deleted file mode 100644 index c0f5ebc23ed..00000000000 --- a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/__fixtures__/index.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { - PipetteOffsetCalibration, - TipLengthCalibration, -} from '../../../../redux/calibration/api-types' - -export const mockPipetteOffsetCalibrationsResponse: PipetteOffsetCalibration = { - id: 'pipetteName&left', - pipette: 'serialNumber', - mount: 'left', - offset: [0, 0, 0], - tiprack: 'mockTipRackHash', - tiprackUri: 'mock/tiprack/uri', - lastModified: '2023-01-26T20:31:41.991057+00:00', - source: 'user', - status: { - markedBad: false, - source: null, - markedAt: null, - }, -} - -export const mockTipLengthCalibrationResponse: TipLengthCalibration = { - id: 'mockID', - tipLength: 51.83, - tiprack: 'mockTiprack', - pipette: 'serialNumber', - lastModified: '2023-01-27T20:43:14.373201+00:00', - source: 'user', - status: { - markedBad: false, - source: null, - markedAt: null, - }, - uri: 'mock/tiprack/uri', -} diff --git a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/utils.ts b/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/utils.ts deleted file mode 100644 index e8317d2e124..00000000000 --- a/app/src/organisms/RobotSettingsCalibration/CalibrationDetails/utils.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { format } from 'date-fns' - -import { getLabwareDisplayName } from '@opentrons/shared-data' -import { findLabwareDefWithCustom } from '../../../assets/labware/findLabware' - -import type { LabwareDefinition2 } from '@opentrons/shared-data' - -const UNKNOWN_CUSTOM_LABWARE = 'unknown custom tiprack' - -export function getDisplayNameForTipRack( - tiprackUri: string, - customLabware: LabwareDefinition2[] -): string { - const [namespace, loadName] = tiprackUri ? tiprackUri.split('/') : ['', ''] - const definition = findLabwareDefWithCustom( - namespace, - loadName, - null, - customLabware - ) - return definition - ? getLabwareDisplayName(definition) - : `${UNKNOWN_CUSTOM_LABWARE}` -} - -export const formatLastCalibrated = (lastModified: string): string => { - return typeof lastModified === 'string' - ? format(new Date(lastModified), 'M/d/yyyy HH:mm:ss') - : 'Unknown' -} diff --git a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsDeckCalibration.test.tsx b/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsDeckCalibration.test.tsx deleted file mode 100644 index 501141a6f82..00000000000 --- a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsDeckCalibration.test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import * as React from 'react' -import { screen } from '@testing-library/react' -import { describe, it, vi, beforeEach } from 'vitest' - -import { i18n } from '../../../i18n' -import * as RobotApi from '../../../redux/robot-api' -import { - mockDeckCalData, - mockWarningDeckCalData, -} from '../../../redux/calibration/__fixtures__' -import { mockConnectableRobot } from '../../../redux/discovery/__fixtures__' -import { mockAttachedPipette } from '../../../redux/pipettes/__fixtures__' -import { - useDeckCalibrationData, - useRobot, - useAttachedPipettes, -} from '../../../organisms/Devices/hooks' -import { renderWithProviders } from '../../../__testing-utils__' - -import { RobotSettingsDeckCalibration } from '../RobotSettingsDeckCalibration' - -import type { AttachedPipettesByMount } from '../../../redux/pipettes/types' - -vi.mock('../../../organisms/CalibrationStatusCard') -vi.mock('../../../redux/robot-api/selectors') -vi.mock('../../../organisms/Devices/hooks') - -const mockAttachedPipettes: AttachedPipettesByMount = { - left: mockAttachedPipette, - right: mockAttachedPipette, -} as any - -const render = ( - props?: Partial> -) => { - return renderWithProviders( - , - { - i18nInstance: i18n, - } - ) -} -const getRequestById = RobotApi.getRequestById - -describe('RobotSettingsDeckCalibration', () => { - beforeEach(() => { - vi.mocked(useDeckCalibrationData).mockReturnValue({ - deckCalibrationData: mockDeckCalData, - isDeckCalibrated: true, - }) - vi.mocked(useRobot).mockReturnValue(mockConnectableRobot) - vi.mocked(useAttachedPipettes).mockReturnValue(mockAttachedPipettes) - vi.mocked(getRequestById).mockReturnValue(null) - }) - - it('renders a title description and button', () => { - render() - screen.getByText('Deck Calibration') - screen.getByText( - 'Calibrating the deck is required for new robots or after you relocate your robot. Recalibrating the deck will require you to also recalibrate pipette offsets.' - ) - screen.getByText('Last calibrated: September 15, 2021 00:00') - }) - - it('renders empty state if yet not calibrated', () => { - vi.mocked(useDeckCalibrationData).mockReturnValue({ - deckCalibrationData: null, - isDeckCalibrated: false, - }) - render() - screen.getByText('Not calibrated yet') - }) - - it('renders the last calibrated when deck calibration is not good', () => { - vi.mocked(useDeckCalibrationData).mockReturnValue({ - deckCalibrationData: mockWarningDeckCalData, - isDeckCalibrated: true, - }) - render() - screen.getByText('Last calibrated: September 22, 2222 00:00') - }) -}) diff --git a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsTipLengthCalibration.test.tsx b/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsTipLengthCalibration.test.tsx deleted file mode 100644 index 9225f487259..00000000000 --- a/app/src/organisms/RobotSettingsCalibration/__tests__/RobotSettingsTipLengthCalibration.test.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import * as React from 'react' -import { screen } from '@testing-library/react' -import { describe, it, beforeEach, vi } from 'vitest' -import { i18n } from '../../../i18n' -import { useFeatureFlag } from '../../../redux/config' -import { renderWithProviders } from '../../../__testing-utils__' -import { - mockTipLengthCalibration1, - mockTipLengthCalibration2, - mockTipLengthCalibration3, -} from '../../../redux/calibration/tip-length/__fixtures__' -import { mockAttachedPipette } from '../../../redux/pipettes/__fixtures__' -import { - useAttachedPipettes, - useTipLengthCalibrations, -} from '../../../organisms/Devices/hooks' - -import { RobotSettingsTipLengthCalibration } from '../RobotSettingsTipLengthCalibration' -import { TipLengthCalibrationItems } from '../CalibrationDetails/TipLengthCalibrationItems' - -import type { FormattedPipetteOffsetCalibration } from '..' -import type { AttachedPipettesByMount } from '../../../redux/pipettes/types' - -vi.mock('../../../redux/config') -vi.mock('../../../organisms/Devices/hooks') -vi.mock('../CalibrationDetails/TipLengthCalibrationItems') - -const mockFormattedPipetteOffsetCalibrations: FormattedPipetteOffsetCalibration[] = [] - -const mockUpdateRobotStatus = vi.fn() - -const render = () => { - return renderWithProviders( - , - { - i18nInstance: i18n, - } - ) -} - -describe('RobotSettingsTipLengthCalibration', () => { - beforeEach(() => { - vi.mocked(useTipLengthCalibrations).mockReturnValue([ - mockTipLengthCalibration1, - mockTipLengthCalibration2, - mockTipLengthCalibration3, - ]) - vi.mocked(TipLengthCalibrationItems).mockReturnValue( -
    Mock TipLengthCalibrationItems
    - ) - vi.mocked(useFeatureFlag).mockReturnValue(false) - vi.mocked(useAttachedPipettes).mockReturnValue({ - left: mockAttachedPipette, - right: null, - } as AttachedPipettesByMount) - }) - - it('renders a title', () => { - render() - screen.getByText('Tip Length Calibrations') - screen.getByText('Mock TipLengthCalibrationItems') - }) -}) diff --git a/app/src/organisms/RobotSettingsCalibration/index.tsx b/app/src/organisms/RobotSettingsCalibration/index.tsx deleted file mode 100644 index cffd63ce573..00000000000 --- a/app/src/organisms/RobotSettingsCalibration/index.tsx +++ /dev/null @@ -1,391 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useSelector, useDispatch } from 'react-redux' -import { useTranslation } from 'react-i18next' -import { - AlertModal, - SPACING, - SpinnerModalPage, - LegacyStyledText, -} from '@opentrons/components' -import { - useAllPipetteOffsetCalibrationsQuery, - useAllTipLengthCalibrationsQuery, - useCalibrationStatusQuery, - useInstrumentsQuery, - useModulesQuery, -} from '@opentrons/react-api-client' - -import { getTopPortalEl } from '../../App/portal' -import { Line } from '../../atoms/structure' -import { CalibrateDeck } from '../../organisms/CalibrateDeck' -import { CalibrationStatusCard } from '../../organisms/CalibrationStatusCard' -import { CheckCalibration } from '../../organisms/CheckCalibration' -import { - useRobot, - useRunStatuses, - useIsFlex, - useAttachedPipettesFromInstrumentsQuery, -} from '../../organisms/Devices/hooks' -import { HowCalibrationWorksModal } from '../../organisms/HowCalibrationWorksModal' -import { CONNECTABLE } from '../../redux/discovery' -import * as RobotApi from '../../redux/robot-api' -import { getDeckCalibrationSession } from '../../redux/sessions/deck-calibration/selectors' -import * as Sessions from '../../redux/sessions' -import { CalibrationDataDownload } from './CalibrationDataDownload' -import { CalibrationHealthCheck } from './CalibrationHealthCheck' -import { RobotSettingsDeckCalibration } from './RobotSettingsDeckCalibration' -import { RobotSettingsGripperCalibration } from './RobotSettingsGripperCalibration' -import { RobotSettingsPipetteOffsetCalibration } from './RobotSettingsPipetteOffsetCalibration' -import { RobotSettingsTipLengthCalibration } from './RobotSettingsTipLengthCalibration' -import { RobotSettingsModuleCalibration } from './RobotSettingsModuleCalibration' - -import type { GripperData } from '@opentrons/api-client' -import type { Mount } from '@opentrons/components' -import type { RequestState } from '../../redux/robot-api/types' -import type { - SessionCommandString, - DeckCalibrationSession, -} from '../../redux/sessions/types' -import type { State, Dispatch } from '../../redux/types' - -const CALS_FETCH_MS = 5000 - -interface CalibrationProps { - robotName: string - updateRobotStatus: (isRobotBusy: boolean) => void -} - -export interface FormattedPipetteOffsetCalibration { - modelName?: string - serialNumber?: string - mount: Mount - tiprack?: string - lastCalibrated?: string - markedBad?: boolean -} - -const spinnerCommandBlockList: SessionCommandString[] = [ - Sessions.sharedCalCommands.JOG, -] - -export function RobotSettingsCalibration({ - robotName, - updateRobotStatus, -}: CalibrationProps): JSX.Element { - const { t } = useTranslation([ - 'device_settings', - 'robot_calibration', - 'shared', - ]) - const trackedRequestId = React.useRef(null) - const createRequestId = React.useRef(null) - const jogRequestId = React.useRef(null) - - const [ - showHowCalibrationWorksModal, - setShowHowCalibrationWorksModal, - ] = React.useState(false) - - const robot = useRobot(robotName) - const notConnectable = robot?.status !== CONNECTABLE - const isFlex = useIsFlex(robotName) - const dispatch = useDispatch() - - React.useEffect(() => { - dispatch(Sessions.fetchAllSessions(robotName)) - }, [dispatch, robotName]) - - const [dispatchRequests] = RobotApi.useDispatchApiRequests( - dispatchedAction => { - if (dispatchedAction.type === Sessions.ENSURE_SESSION) { - createRequestId.current = - 'requestId' in dispatchedAction.meta - ? dispatchedAction.meta.requestId ?? null - : null - } else if ( - dispatchedAction.type === Sessions.CREATE_SESSION_COMMAND && - dispatchedAction.payload.command.command === - Sessions.sharedCalCommands.JOG - ) { - jogRequestId.current = - 'requestId' in dispatchedAction.meta - ? dispatchedAction.meta.requestId ?? null - : null - } else if ( - dispatchedAction.type !== Sessions.CREATE_SESSION_COMMAND || - !spinnerCommandBlockList.includes( - dispatchedAction.payload.command.command - ) - ) { - trackedRequestId.current = - 'meta' in dispatchedAction && 'requestId' in dispatchedAction.meta - ? dispatchedAction.meta.requestId ?? null - : null - } - } - ) - - // Modules Calibration - const attachedModules = - useModulesQuery({ - refetchInterval: CALS_FETCH_MS, - })?.data?.data ?? [] - - // Note: following fetch need to reflect the latest state of calibrations - // when a user does calibration or rename a robot. - useCalibrationStatusQuery({ refetchInterval: CALS_FETCH_MS }) - useAllTipLengthCalibrationsQuery({ refetchInterval: CALS_FETCH_MS }) - const pipetteOffsetCalibrations = - useAllPipetteOffsetCalibrationsQuery({ refetchInterval: CALS_FETCH_MS }) - .data?.data ?? [] - const attachedInstruments = - useInstrumentsQuery({ refetchInterval: CALS_FETCH_MS }).data?.data ?? [] - const attachedGripper = - (attachedInstruments ?? []).find( - (i): i is GripperData => i.instrumentType === 'gripper' && i.ok - ) ?? null - const attachedPipettes = useAttachedPipettesFromInstrumentsQuery() - const { isRunRunning: isRunning } = useRunStatuses() - const pipettePresent = - !(attachedPipettes.left == null) || !(attachedPipettes.right == null) - - const isPending = - useSelector(state => - trackedRequestId.current != null - ? RobotApi.getRequestById(state, trackedRequestId.current) - : null - )?.status === RobotApi.PENDING - - const createRequest = useSelector((state: State) => - createRequestId.current != null - ? RobotApi.getRequestById(state, createRequestId.current) - : null - ) - - const createStatus = createRequest?.status - - const isJogging = - useSelector((state: State) => - jogRequestId.current != null - ? RobotApi.getRequestById(state, jogRequestId.current) - : null - )?.status === RobotApi.PENDING - - const deckCalibrationSession: DeckCalibrationSession | null = useSelector( - (state: State) => { - return getDeckCalibrationSession(state, robotName) - } - ) - - let buttonDisabledReason: string | null = null - if (notConnectable) { - buttonDisabledReason = t('shared:disabled_cannot_connect') - } else if (isRunning) { - buttonDisabledReason = t('shared:disabled_protocol_is_running') - } else if (!pipettePresent) { - buttonDisabledReason = t('shared:disabled_no_pipette_attached') - } - const checkHealthSession = useSelector((state: State) => { - const session: Sessions.Session | null = Sessions.getRobotSessionOfType( - state, - robotName, - Sessions.SESSION_TYPE_CALIBRATION_HEALTH_CHECK - ) - if ( - session != null && - session.sessionType === Sessions.SESSION_TYPE_CALIBRATION_HEALTH_CHECK - ) { - // TODO: add this analytics event when we deprecate this event firing in redux/analytics makeEvent - return session - } - return null - }) - - const formattedPipetteOffsetCalibrations: FormattedPipetteOffsetCalibration[] = [] - - if (!isFlex && attachedPipettes != null) { - formattedPipetteOffsetCalibrations.push({ - modelName: attachedPipettes.left?.displayName, - serialNumber: attachedPipettes.left?.serialNumber, - mount: 'left' as Mount, - tiprack: pipetteOffsetCalibrations?.find( - p => p.pipette === attachedPipettes.left?.serialNumber - )?.tiprackUri, - lastCalibrated: pipetteOffsetCalibrations?.find( - p => p.pipette === attachedPipettes.left?.serialNumber - )?.lastModified, - markedBad: pipetteOffsetCalibrations?.find( - p => p.pipette === attachedPipettes.left?.serialNumber - )?.status.markedBad, - }) - formattedPipetteOffsetCalibrations.push({ - modelName: attachedPipettes.right?.displayName, - serialNumber: attachedPipettes.right?.serialNumber, - mount: 'right' as Mount, - tiprack: pipetteOffsetCalibrations?.find( - p => p.pipette === attachedPipettes.right?.serialNumber - )?.tiprackUri, - lastCalibrated: pipetteOffsetCalibrations?.find( - p => p.pipette === attachedPipettes.right?.serialNumber - )?.lastModified, - markedBad: pipetteOffsetCalibrations?.find( - p => p.pipette === attachedPipettes.right?.serialNumber - )?.status.markedBad, - }) - } else { - formattedPipetteOffsetCalibrations.push({ - modelName: attachedPipettes.left?.displayName, - serialNumber: attachedPipettes.left?.serialNumber, - mount: 'left' as Mount, - lastCalibrated: - attachedPipettes.left?.data.calibratedOffset?.last_modified, - }) - formattedPipetteOffsetCalibrations.push({ - modelName: attachedPipettes.right?.displayName, - serialNumber: attachedPipettes.right?.serialNumber, - mount: 'right' as Mount, - lastCalibrated: - attachedPipettes.right?.data.calibratedOffset?.last_modified, - }) - } - - React.useEffect(() => { - if (createStatus === RobotApi.SUCCESS) { - createRequestId.current = null - } - }, [createStatus]) - - return ( - <> - {createPortal( - <> - - {createStatus === RobotApi.PENDING ? ( - - ) : null} - - {createStatus === RobotApi.FAILURE && ( - { - createRequestId.current != null && - dispatch(RobotApi.dismissRequest(createRequestId.current)) - createRequestId.current = null - }, - }, - ]} - > - - {t('deck_calibration_error_occurred')} - - - {createRequest != null && - 'error' in createRequest && - createRequest.error != null && - RobotApi.getErrorResponseMessage(createRequest.error)} - - - )} - , - getTopPortalEl() - )} - {showHowCalibrationWorksModal ? ( - { - setShowHowCalibrationWorksModal(false) - }} - /> - ) : null} - {isFlex ? ( - <> - - - - - - - - - ) : ( - <> - - - - - - - - - - - - )} - - ) -} diff --git a/app/src/organisms/RobotSettingsDashboard/DeviceReset.tsx b/app/src/organisms/RobotSettingsDashboard/DeviceReset.tsx deleted file mode 100644 index 6ad9e59987d..00000000000 --- a/app/src/organisms/RobotSettingsDashboard/DeviceReset.tsx +++ /dev/null @@ -1,371 +0,0 @@ -import * as React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import styled from 'styled-components' - -import { - BORDERS, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - SPACING, - LegacyStyledText, - TYPOGRAPHY, - useConditionalConfirm, -} from '@opentrons/components' - -import { MediumButton, SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' -import { ChildNavigation } from '../../organisms/ChildNavigation' -import { - getResetConfigOptions, - fetchResetConfigOptions, - resetConfig, -} from '../../redux/robot-admin' -import { useDispatchApiRequest } from '../../redux/robot-api' - -import type { Dispatch, State } from '../../redux/types' -import type { ResetConfigRequest } from '../../redux/robot-admin/types' -import type { SetSettingOption } from '../../pages/RobotSettingsDashboard' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' - -interface LabelProps { - isSelected?: boolean -} - -const OptionButton = styled.input` - display: none; -` - -const OptionLabel = styled.label` - padding: ${SPACING.spacing16} ${SPACING.spacing24}; - border-radius: ${BORDERS.borderRadius16}; - color: ${({ isSelected }) => - isSelected === true ? COLORS.white : COLORS.black90}; - background: ${({ isSelected }) => - isSelected === true ? COLORS.blue50 : COLORS.blue35}; -` - -interface DeviceResetProps { - robotName: string - setCurrentOption: SetSettingOption -} - -export function DeviceReset({ - robotName, - setCurrentOption, -}: DeviceResetProps): JSX.Element { - const { t } = useTranslation('device_settings') - const [resetOptions, setResetOptions] = React.useState({}) - const options = useSelector((state: State) => - getResetConfigOptions(state, robotName) - ) - const [dispatchRequest] = useDispatchApiRequest() - - const targetOptionsOrder = [ - 'pipetteOffsetCalibrations', - 'gripperOffsetCalibrations', - 'moduleCalibration', - 'runsHistory', - ] - - const availableOptions = options - // filtering out ODD setting because this gets implicitly cleared if all settings are selected - // filtering out boot scripts since product doesn't want this exposed to ODD users - .filter( - ({ id }) => - !['onDeviceDisplay', 'bootScripts', 'deckConfiguration'].includes(id) - ) - .sort( - (a, b) => - targetOptionsOrder.indexOf(a.id) - targetOptionsOrder.indexOf(b.id) - ) - const dispatch = useDispatch() - - const availableOptionsToDisplay = availableOptions.filter( - ({ id }) => !['authorizedKeys'].includes(id) - ) - - const isEveryOptionSelected = (obj: ResetConfigRequest): boolean => { - for (const key of targetOptionsOrder) { - if (obj != null && !obj[key]) { - return false - } - } - return true - } - - const handleClick = (): void => { - if (resetOptions != null) { - const { ...serverResetOptions } = resetOptions - dispatchRequest(resetConfig(robotName, serverResetOptions)) - } - } - - const { - confirm: confirmClearData, - showConfirmation: showConfirmationModal, - cancel: cancelClearData, - } = useConditionalConfirm(handleClick, true) - - const renderText = ( - optionId: string - ): { optionText: string; subText?: string } => { - let optionText = '' - let subText - switch (optionId) { - case 'pipetteOffsetCalibrations': - optionText = t('clear_option_pipette_calibrations') - break - case 'gripperOffsetCalibrations': - optionText = t('clear_option_gripper_calibration') - break - case 'moduleCalibration': - optionText = t('clear_option_module_calibration') - break - case 'runsHistory': - optionText = t('clear_option_runs_history') - subText = t('clear_option_runs_history_subtext') - break - - case 'factoryReset': - optionText = t('factory_reset') - subText = t('factory_reset_description') - break - default: - break - } - return { - optionText, - subText, - } - } - React.useEffect(() => { - dispatch(fetchResetConfigOptions(robotName)) - }, [dispatch, robotName]) - - React.useEffect(() => { - if ( - isEveryOptionSelected(resetOptions) && - (!resetOptions.authorizedKeys || - !resetOptions.onDeviceDisplay || - !resetOptions.deckConfiguration) - ) { - setResetOptions({ - ...resetOptions, - authorizedKeys: true, - onDeviceDisplay: true, - deckConfiguration: true, - }) - } - }, [resetOptions]) - - React.useEffect(() => { - if ( - !isEveryOptionSelected(resetOptions) && - resetOptions.authorizedKeys && - resetOptions.onDeviceDisplay && - resetOptions.deckConfiguration - ) { - setResetOptions({ - ...resetOptions, - authorizedKeys: false, - onDeviceDisplay: false, - deckConfiguration: false, - }) - } - }, [resetOptions]) - - return ( - - {showConfirmationModal && ( - - )} - { - setCurrentOption(null) - }} - /> - - - {availableOptionsToDisplay.map(option => { - const { optionText, subText } = renderText(option.id) - return ( - - { - setResetOptions({ - ...resetOptions, - [option.id]: !(resetOptions[option.id] ?? false), - }) - }} - /> - - - - {optionText} - - {subText != null ? ( - - {subText} - - ) : null} - - - - ) - })} - - { - setResetOptions( - (resetOptions.authorizedKeys ?? false) && - (resetOptions.onDeviceDisplay ?? false) - ? {} - : availableOptions.reduce( - (acc, val) => { - return { - ...acc, - [val.id]: true, - } - }, - { authorizedKeys: true, onDeviceDisplay: true } - ) - ) - }} - /> - - - - {t('clear_all_stored_data')} - - - {t('clear_all_stored_data_description')} - - - - - - resetOptions[option.id] === false || - resetOptions[option.id] === undefined - ) - } - onClick={confirmClearData} - /> - - - ) -} - -interface ConfirmClearDataModalProps { - cancelClearData: () => void - confirmClearData: () => void -} - -export const ConfirmClearDataModal = ({ - cancelClearData, - confirmClearData, -}: ConfirmClearDataModalProps): JSX.Element => { - const { t } = useTranslation(['device_settings', 'shared']) - const modalHeader: OddModalHeaderBaseProps = { - title: t('confirm_device_reset_heading'), - hasExitIcon: false, - iconName: 'ot-alert', - iconColor: COLORS.yellow50, - } - return ( - - - - - {t('confirm_device_reset_description')} - - - - - - - - - ) -} diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsJoinOtherNetwork.tsx b/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsJoinOtherNetwork.tsx deleted file mode 100644 index b30d49098b6..00000000000 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/RobotSettingsJoinOtherNetwork.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' - -import { DIRECTION_COLUMN, Flex } from '@opentrons/components' - -import { ChildNavigation } from '../../../organisms/ChildNavigation' -import { SetWifiSsid } from '../../../organisms/NetworkSettings/SetWifiSsid' - -import type { SetSettingOption } from '../../../pages/RobotSettingsDashboard' - -interface RobotSettingsJoinOtherNetworkProps { - setCurrentOption: SetSettingOption - setSelectedSsid: React.Dispatch> -} - -/** - * Robot settings page wrapper for shared SetWifiSsid organism with child navigation header - */ -export function RobotSettingsJoinOtherNetwork({ - setCurrentOption, - setSelectedSsid, -}: RobotSettingsJoinOtherNetworkProps): JSX.Element { - const { i18n, t } = useTranslation('device_settings') - - const [inputSsid, setInputSsid] = React.useState('') - const [errorMessage, setErrorMessage] = React.useState(null) - - const handleContinue = (): void => { - if (inputSsid.length >= 2 && inputSsid.length <= 32) { - setSelectedSsid(inputSsid) - setCurrentOption('RobotSettingsSelectAuthenticationType') - } else { - setErrorMessage(t('join_other_network_error_message') as string) - } - } - - return ( - - { - setCurrentOption('RobotSettingsWifi') - }} - onClickButton={handleContinue} - /> - - - ) -} diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/WifiConnectionDetails.tsx b/app/src/organisms/RobotSettingsDashboard/NetworkSettings/WifiConnectionDetails.tsx deleted file mode 100644 index 4c944458be2..00000000000 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/WifiConnectionDetails.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' - -import { - ALIGN_CENTER, - BORDERS, - Btn, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - DISPLAY_FLEX, - Flex, - Icon, - JUSTIFY_SPACE_BETWEEN, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' - -import { NetworkDetailsModal } from './NetworkDetailsModal' -import { DisplayWifiList } from '../../NetworkSettings' -import { getLocalRobot } from '../../../redux/discovery' -import { getNetworkInterfaces } from '../../../redux/networking' -import { useWifiList } from '../../../resources/networking/hooks' - -import type { WifiSecurityType } from '@opentrons/api-client' -import type { State } from '../../../redux/types' - -const FETCH_WIFI_LIST_MS = 5000 - -interface WifiConnectionDetailsProps { - handleJoinAnotherNetwork: () => void - handleNetworkPress: (ssid: string) => void - activeSsid?: string - connectedWifiAuthType?: WifiSecurityType -} -export function WifiConnectionDetails({ - activeSsid, - connectedWifiAuthType, - handleNetworkPress, - handleJoinAnotherNetwork, -}: WifiConnectionDetailsProps): JSX.Element { - const { i18n, t } = useTranslation(['device_settings', 'shared']) - const [ - showNetworkDetailModal, - setShowNetworkDetailModal, - ] = React.useState(false) - const localRobot = useSelector(getLocalRobot) - const robotName = localRobot?.name != null ? localRobot.name : 'no name' - const list = useWifiList(robotName, FETCH_WIFI_LIST_MS) - const { wifi } = useSelector((state: State) => - getNetworkInterfaces(state, robotName) - ) - const noData = i18n.format(t('shared:no_data'), 'titleCase') - const ipAddress = wifi?.ipAddress != null ? wifi.ipAddress : noData - const subnetMask = wifi?.subnetMask != null ? wifi.subnetMask : noData - const macAddress = wifi?.macAddress != null ? wifi.macAddress : noData - - return ( - <> - {showNetworkDetailModal ? ( - - ) : null} - - {activeSsid != null ? ( - - - {t('connected_network')} - - { - setShowNetworkDetailModal(true) - }} - alignItems={ALIGN_CENTER} - > - - - - - - - {activeSsid} - - - - - - - - {t('view_details')} - - - - - - ) : null} - {activeSsid != null ? ( - - {t('other_networks')} - - ) : null} - { - handleNetworkPress(ssid) - }} - /> - - - ) -} diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx b/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx deleted file mode 100644 index 02da87250d3..00000000000 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/WifiConnectionDetails.test.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import * as React from 'react' -import { fireEvent, screen } from '@testing-library/react' -import { when } from 'vitest-when' -import { describe, it, expect, vi, beforeEach } from 'vitest' -import '@testing-library/jest-dom/vitest' - -import { i18n } from '../../../../i18n' -import { renderWithProviders } from '../../../../__testing-utils__' -import { getLocalRobot } from '../../../../redux/discovery' -import * as Networking from '../../../../redux/networking' -import { NetworkDetailsModal } from '../NetworkDetailsModal' -import { WifiConnectionDetails } from '../WifiConnectionDetails' -import type * as Dom from 'react-router-dom' -import type { State } from '../../../../redux/types' - -vi.mock('../../../../redux/discovery') -vi.mock('../../../../redux/networking') -vi.mock('../NetworkDetailsModal') - -const mockNavigate = vi.fn() -vi.mock('react-router-dom', async importOriginal => { - const reactRouterDom = await importOriginal() - return { - ...reactRouterDom, - useNavigate: () => mockNavigate, - } -}) - -const getNetworkInterfaces = Networking.getNetworkInterfaces -const ROBOT_NAME = 'otie' - -const initialMockWifi = { - ipAddress: '127.0.0.100', - subnetMask: '255.255.255.230', - macAddress: 'WI:FI:00:00:00:00', - type: Networking.INTERFACE_WIFI, -} - -const render = (props: React.ComponentProps) => { - return renderWithProviders(, { - i18nInstance: i18n, - }) -} - -describe('WifiConnectionDetails', () => { - let props: React.ComponentProps - beforeEach(() => { - props = { - activeSsid: 'mock wifi ssid', - connectedWifiAuthType: 'none', - handleNetworkPress: vi.fn(), - handleJoinAnotherNetwork: vi.fn(), - } - vi.mocked(getLocalRobot).mockReturnValue({ - name: ROBOT_NAME, - } as any) - when(getNetworkInterfaces) - .calledWith({} as State, ROBOT_NAME) - .thenReturn({ - wifi: initialMockWifi, - ethernet: null, - }) - vi.mocked(NetworkDetailsModal).mockReturnValue( -
    mock NetworkDetailsModal
    - ) - }) - - it('should render text and button with icon when connected to a network', () => { - render(props) - screen.getByText('Connected Network') - screen.getByLabelText('mock wifi ssid_wifi_icon') - screen.getByLabelText('mock wifi ssid_info_icon') - screen.getByText('mock wifi ssid') - screen.getByText('View details') - screen.getByText('Other Networks') - }) - - it('should show the modal when tapping connected wifi button', () => { - render(props) - const button = screen.getByText('mock wifi ssid') - fireEvent.click(button) - screen.getByText('mock NetworkDetailsModal') - }) - - it('should not render text and button when not connected to a network', () => { - props.activeSsid = undefined - render(props) - expect(screen.queryByText('Connected Network')).not.toBeInTheDocument() - expect(screen.queryByText('mock wifi ssid')).not.toBeInTheDocument() - expect(screen.queryByText('Other Networks')).not.toBeInTheDocument() - }) - - it.todo('should render the wifi list') -}) diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/hooks.test.tsx b/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/hooks.test.tsx deleted file mode 100644 index 223c2d25457..00000000000 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/__tests__/hooks.test.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import * as React from 'react' -import { renderHook } from '@testing-library/react' -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { Provider } from 'react-redux' -import { createStore } from 'redux' - -import { - getIsOnDevice, - getOnDeviceDisplaySettings, -} from '../../../../redux/config' -import { useIsUnboxingFlowOngoing } from '../hooks' - -import type { Store } from 'redux' -import type { State } from '../../../../redux/types' - -vi.mock('../../../../redux/config') - -const store: Store = createStore(vi.fn(), {}) - -const mockDisplaySettings = { - sleepMs: 604800000, - brightness: 4, - textSize: 1, - unfinishedUnboxingFlowRoute: null, -} - -describe('useIsUnboxingFlowOngoing', () => { - let wrapper: React.FunctionComponent<{ children: React.ReactNode }> - beforeEach(() => { - wrapper = ({ children }) => {children} - vi.mocked(getOnDeviceDisplaySettings).mockReturnValue(mockDisplaySettings) - vi.mocked(getIsOnDevice).mockReturnValue(true) - }) - - it('should return true if unfinishedUnboxingFlowRoute is /welcome', () => { - vi.mocked(getOnDeviceDisplaySettings).mockReturnValue({ - ...mockDisplaySettings, - unfinishedUnboxingFlowRoute: '/welcome', - }) - const { result } = renderHook(() => useIsUnboxingFlowOngoing(), { - wrapper, - }) - expect(result.current).toBe(true) - }) - - it('should return true if unfinishedUnboxingFlowRoute is /emergency-stop', () => { - vi.mocked(getOnDeviceDisplaySettings).mockReturnValue({ - ...mockDisplaySettings, - unfinishedUnboxingFlowRoute: '/emergency-stop', - }) - const { result } = renderHook(() => useIsUnboxingFlowOngoing(), { - wrapper, - }) - expect(result.current).toBe(true) - }) - - it('should return false if unfinishedUnboxingFlowRoute is null', () => { - const { result } = renderHook(() => useIsUnboxingFlowOngoing(), { - wrapper, - }) - expect(result.current).toBe(false) - }) -}) diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/hooks.ts b/app/src/organisms/RobotSettingsDashboard/NetworkSettings/hooks.ts deleted file mode 100644 index 0e31fb6fd05..00000000000 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/hooks.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useSelector } from 'react-redux' -import { - getIsOnDevice, - getOnDeviceDisplaySettings, -} from '../../../redux/config' - -export const useIsUnboxingFlowOngoing = (): boolean => { - const { unfinishedUnboxingFlowRoute } = useSelector( - getOnDeviceDisplaySettings - ) - const isOnDevice = useSelector(getIsOnDevice) - return isOnDevice && unfinishedUnboxingFlowRoute !== null -} diff --git a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/index.tsx b/app/src/organisms/RobotSettingsDashboard/NetworkSettings/index.tsx deleted file mode 100644 index 0eda42e2c4b..00000000000 --- a/app/src/organisms/RobotSettingsDashboard/NetworkSettings/index.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import * as React from 'react' -import { css } from 'styled-components' -import { useTranslation } from 'react-i18next' - -import { - ALIGN_CENTER, - BORDERS, - Btn, - Chip, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - Icon, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' - -import { ChildNavigation } from '../../../organisms/ChildNavigation' - -import type { IconName, ChipType } from '@opentrons/components' -import type { NetworkConnection } from '../../../resources/networking/hooks/useNetworkConnection' -import type { SetSettingOption } from '../../../pages/RobotSettingsDashboard' - -export type ConnectionType = 'wifi' | 'ethernet' | 'usb' - -interface NetworkSettingsProps { - networkConnection: NetworkConnection - setCurrentOption: SetSettingOption -} - -export function NetworkSettings({ - networkConnection, - setCurrentOption, -}: NetworkSettingsProps): JSX.Element { - const { t } = useTranslation('device_settings') - const { isWifiConnected, isEthernetConnected, activeSsid } = networkConnection - - const handleChipType = (isConnected: boolean): ChipType => { - return isConnected ? 'success' : 'neutral' - } - - const handleButtonBackgroundColor = (isConnected: boolean): string => - isConnected ? COLORS.green35 : COLORS.grey35 - - const handleChipText = (isConnected: boolean): string => - isConnected ? t('connected') : t('not_connected') - - return ( - - { - setCurrentOption(null) - }} - /> - - { - setCurrentOption('RobotSettingsWifi') - }} - /> - { - setCurrentOption('EthernetConnectionDetails') - }} - /> - - - ) -} - -interface NetworkSettingButtonProps extends React.ComponentProps { - buttonTitle: string - iconName: IconName - chipType: ChipType - chipText: string - networkName?: string -} - -function NetworkSettingButton({ - backgroundColor, - buttonTitle, - iconName, - chipType, - chipText, - networkName, - onClick, -}: NetworkSettingButtonProps): JSX.Element { - const PUSHED_STATE_STYLE = css` - &:active { - background-color: ${chipType === 'success' - ? COLORS.green40 - : COLORS.grey50}; - } - ` - - return ( - - - - - - - - {buttonTitle} - - {networkName != null ? ( - - {networkName} - - ) : null} - - - - - - - - { - console.log('setup') - }} - > - - - - - - ) -} diff --git a/app/src/organisms/RobotSettingsDashboard/__tests__/DeviceReset.test.tsx b/app/src/organisms/RobotSettingsDashboard/__tests__/DeviceReset.test.tsx deleted file mode 100644 index 2d6fd56f066..00000000000 --- a/app/src/organisms/RobotSettingsDashboard/__tests__/DeviceReset.test.tsx +++ /dev/null @@ -1,185 +0,0 @@ -import * as React from 'react' -import { fireEvent, screen } from '@testing-library/react' -import { describe, it, expect, vi, beforeEach } from 'vitest' -import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' -import { getResetConfigOptions, resetConfig } from '../../../redux/robot-admin' -import { useDispatchApiRequest } from '../../../redux/robot-api' - -import { DeviceReset } from '../DeviceReset' - -import type { DispatchApiRequestType } from '../../../redux/robot-api' - -vi.mock('../../../redux/robot-admin') -vi.mock('../../../redux/robot-api') - -const mockResetConfigOptions = [ - { - id: 'pipetteOffsetCalibrations', - name: 'pipette calibration FooBar', - description: 'pipette calibration fooBar description', - }, - { - id: 'gripperOffsetCalibrations', - name: 'gripper calibration FooBar', - description: 'runsHistory fooBar description', - }, - { - id: 'runsHistory', - name: 'RunsHistory FooBar', - description: 'runsHistory fooBar description', - }, - { - id: 'bootScripts', - name: 'Boot Scripts FooBar', - description: 'bootScripts fooBar description', - }, - { - id: 'moduleCalibration', - name: 'Module Calibration FooBar', - description: 'moduleCalibration fooBar description', - }, - { - id: 'authorizedKeys', - name: 'SSH Keys Foo', - description: 'SSH Keys foo description', - }, -] - -const render = (props: React.ComponentProps) => { - return renderWithProviders( - , - - { i18nInstance: i18n } - ) -} - -describe('DeviceReset', () => { - let props: React.ComponentProps - let dispatchApiRequest: DispatchApiRequestType - - beforeEach(() => { - props = { - robotName: 'mockRobot', - setCurrentOption: vi.fn(), - } - vi.mocked(getResetConfigOptions).mockReturnValue(mockResetConfigOptions) - dispatchApiRequest = vi.fn() - vi.mocked(useDispatchApiRequest).mockReturnValue([dispatchApiRequest, []]) - }) - - it('should render text and button', () => { - render(props) - screen.getByText('Clear pipette calibration') - screen.getByText('Clear gripper calibration') - screen.getByText('Clear module calibration') - screen.getByText('Clear protocol run history') - screen.getByText('Clears information about past runs of all protocols.') - screen.getByText('Clear all stored data') - screen.getByText( - 'Clears calibrations, protocols, and all settings except robot name and network settings.' - ) - expect( - screen.queryByText('Clear the ssh authorized keys') - ).not.toBeInTheDocument() - expect(screen.getByTestId('DeviceReset_clear_data_button')).toBeDisabled() - }) - - it('when tapping a option button, the clear button is enabled', () => { - render(props) - fireEvent.click(screen.getByText('Clear pipette calibration')) - expect( - screen.getByTestId('DeviceReset_clear_data_button') - ).not.toBeDisabled() - }) - - it('when tapping a option button and tapping the clear button, a mock function is called', () => { - const clearMockResetOptions = { - pipetteOffsetCalibrations: true, - moduleCalibration: true, - runsHistory: true, - } - render(props) - fireEvent.click(screen.getByText('Clear pipette calibration')) - fireEvent.click(screen.getByText('Clear protocol run history')) - fireEvent.click(screen.getByText('Clear module calibration')) - const clearButton = screen.getByText('Clear data and restart robot') - fireEvent.click(clearButton) - screen.getByText('Are you sure you want to reset your device?') - fireEvent.click(screen.getByText('Confirm')) - expect(dispatchApiRequest).toBeCalledWith( - resetConfig('mockRobot', clearMockResetOptions) - ) - }) - - it('when tapping clear all stored data, all options are active', () => { - const clearMockResetOptions = { - pipetteOffsetCalibrations: true, - moduleCalibration: true, - runsHistory: true, - gripperOffsetCalibrations: true, - authorizedKeys: true, - onDeviceDisplay: true, - deckConfiguration: true, - } - - render(props) - fireEvent.click(screen.getByText('Clear all stored data')) - const clearButton = screen.getByText('Clear data and restart robot') - fireEvent.click(clearButton) - screen.getByText('Are you sure you want to reset your device?') - fireEvent.click(screen.getByText('Confirm')) - expect(dispatchApiRequest).toBeCalledWith( - resetConfig('mockRobot', clearMockResetOptions) - ) - }) - - it('when tapping all options except clear all stored data, all options are active', () => { - const clearMockResetOptions = { - pipetteOffsetCalibrations: true, - moduleCalibration: true, - runsHistory: true, - gripperOffsetCalibrations: true, - authorizedKeys: true, - onDeviceDisplay: true, - deckConfiguration: true, - } - - render(props) - fireEvent.click(screen.getByText('Clear pipette calibration')) - fireEvent.click(screen.getByText('Clear gripper calibration')) - fireEvent.click(screen.getByText('Clear module calibration')) - fireEvent.click(screen.getByText('Clear protocol run history')) - const clearButton = screen.getByText('Clear data and restart robot') - fireEvent.click(clearButton) - screen.getByText('Are you sure you want to reset your device?') - fireEvent.click(screen.getByText('Confirm')) - expect(dispatchApiRequest).toBeCalledWith( - resetConfig('mockRobot', clearMockResetOptions) - ) - }) - - it('when tapping clear all stored data and unselect one options, all options are not active', () => { - const clearMockResetOptions = { - pipetteOffsetCalibrations: false, - moduleCalibration: true, - runsHistory: true, - gripperOffsetCalibrations: true, - authorizedKeys: false, - onDeviceDisplay: false, - deckConfiguration: false, - } - - render(props) - fireEvent.click(screen.getByText('Clear all stored data')) - fireEvent.click(screen.getByText('Clear pipette calibration')) - const clearButton = screen.getByText('Clear data and restart robot') - fireEvent.click(clearButton) - screen.getByText('Are you sure you want to reset your device?') - fireEvent.click(screen.getByText('Confirm')) - expect(dispatchApiRequest).toBeCalledWith( - resetConfig('mockRobot', clearMockResetOptions) - ) - }) -}) diff --git a/app/src/organisms/RobotSettingsDashboard/index.ts b/app/src/organisms/RobotSettingsDashboard/index.ts deleted file mode 100644 index ba05950ff24..00000000000 --- a/app/src/organisms/RobotSettingsDashboard/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export * from './DeviceReset' -export * from './NetworkSettings/RobotSettingsJoinOtherNetwork' -export * from './NetworkSettings/RobotSettingsSelectAuthenticationType' -export * from './NetworkSettings/RobotSettingsSetWifiCred' -export * from './NetworkSettings/RobotSettingsWifi' -export * from './NetworkSettings/RobotSettingsWifiConnect' -export * from './NetworkSettings' -export * from './Privacy' -export * from './RobotName' -export * from './RobotSystemVersion' -export * from './TextSize' -export * from './TouchscreenBrightness' -export * from './TouchScreenSleep' -export * from './UpdateChannel' diff --git a/app/src/organisms/RobotSetupHeader/index.tsx b/app/src/organisms/RobotSetupHeader/index.tsx deleted file mode 100644 index b68011f2224..00000000000 --- a/app/src/organisms/RobotSetupHeader/index.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import * as React from 'react' - -import { - ALIGN_CENTER, - Btn, - COLORS, - Flex, - Icon, - JUSTIFY_CENTER, - POSITION_ABSOLUTE, - POSITION_RELATIVE, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' - -import { SmallButton } from '../../atoms/buttons' -import { InlineNotification } from '../../atoms/InlineNotification' - -import type { InlineNotificationProps } from '../../atoms/InlineNotification' - -interface RobotSetupHeaderProps { - header: string - buttonText?: React.ReactNode - inlineNotification?: InlineNotificationProps - onClickBack?: React.MouseEventHandler - onClickButton?: React.MouseEventHandler -} - -export function RobotSetupHeader({ - buttonText, - header, - inlineNotification, - onClickBack, - onClickButton, -}: RobotSetupHeaderProps): JSX.Element { - return ( - - - {onClickBack != null ? ( - - - - ) : null} - - {header} - - {onClickButton != null && buttonText != null ? ( - - ) : null} - {inlineNotification != null ? ( - - ) : null} - - - ) -} diff --git a/app/src/organisms/RunDetails/ConfirmCancelModal.tsx b/app/src/organisms/RunDetails/ConfirmCancelModal.tsx deleted file mode 100644 index 98bb3618925..00000000000 --- a/app/src/organisms/RunDetails/ConfirmCancelModal.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' -import { - AlertPrimaryButton, - ALIGN_CENTER, - COLORS, - DIRECTION_COLUMN, - Flex, - Icon, - JUSTIFY_FLEX_END, - Link, - Modal, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' -import { - RUN_STATUS_STOPPED, - RUN_STATUS_STOP_REQUESTED, -} from '@opentrons/api-client' -import { useStopRunMutation } from '@opentrons/react-api-client' - -import { getTopPortalEl } from '../../App/portal' -import { useTrackProtocolRunEvent, useIsFlex } from '../Devices/hooks' -import { useRunStatus } from '../RunTimeControl/hooks' -import { ANALYTICS_PROTOCOL_RUN_ACTION } from '../../redux/analytics' - -export interface ConfirmCancelModalProps { - onClose: () => unknown - runId: string - robotName: string -} - -export function ConfirmCancelModal( - props: ConfirmCancelModalProps -): JSX.Element { - const { onClose, runId, robotName } = props - const { stopRun } = useStopRunMutation() - const [isCanceling, setIsCanceling] = React.useState(false) - const runStatus = useRunStatus(runId) - const isFlex = useIsFlex(robotName) - const { trackProtocolRunEvent } = useTrackProtocolRunEvent(runId, robotName) - const { t } = useTranslation('run_details') - - const cancelRunAlertInfo = isFlex - ? t('cancel_run_alert_info_flex') - : t('cancel_run_alert_info_ot2') - - const cancelRun: React.MouseEventHandler = (e): void => { - e.preventDefault() - e.stopPropagation() - setIsCanceling(true) - stopRun(runId, { - onSuccess: () => { - trackProtocolRunEvent({ name: ANALYTICS_PROTOCOL_RUN_ACTION.CANCEL }) - }, - onError: () => { - setIsCanceling(false) - }, - }) - } - React.useEffect(() => { - if ( - runStatus === RUN_STATUS_STOP_REQUESTED || - runStatus === RUN_STATUS_STOPPED - ) { - onClose() - } - }, [runStatus, onClose]) - - return createPortal( - - - {cancelRunAlertInfo} - - {t('cancel_run_module_info')} - - - {isCanceling ? null : ( - - {t('cancel_run_modal_back')} - - )} - - {isCanceling ? ( - - ) : ( - t('cancel_run_modal_confirm') - )} - - - - , - getTopPortalEl() - ) -} diff --git a/app/src/organisms/RunPreview/index.tsx b/app/src/organisms/RunPreview/index.tsx deleted file mode 100644 index 0deff700e6e..00000000000 --- a/app/src/organisms/RunPreview/index.tsx +++ /dev/null @@ -1,239 +0,0 @@ -import * as React from 'react' -import { css } from 'styled-components' -import { useTranslation } from 'react-i18next' -import { ViewportList } from 'react-viewport-list' - -import { RUN_STATUSES_TERMINAL } from '@opentrons/api-client' -import { - ALIGN_CENTER, - BORDERS, - COLORS, - DIRECTION_COLUMN, - DISPLAY_FLEX, - DISPLAY_NONE, - Flex, - InfoScreen, - LegacyStyledText, - OVERFLOW_SCROLL, - POSITION_FIXED, - PrimaryButton, - SPACING, - TYPOGRAPHY, -} from '@opentrons/components' - -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { - useNotifyAllCommandsAsPreSerializedList, - useNotifyRunQuery, -} from '../../resources/runs' -import { CommandText, CommandIcon } from '../../molecules/Command' -import { Divider } from '../../atoms/structure' -import { NAV_BAR_WIDTH } from '../../App/constants' -import { useRunStatus } from '../RunTimeControl/hooks' -import { useLastRunCommand } from '../Devices/hooks/useLastRunCommand' - -import type { RunStatus } from '@opentrons/api-client' -import type { RobotType } from '@opentrons/shared-data' -import type { ViewportListRef } from 'react-viewport-list' -const COLOR_FADE_MS = 500 -const LIVE_RUN_COMMANDS_POLL_MS = 3000 -// arbitrary large number of commands -const MAX_COMMANDS = 100000 - -interface RunPreviewProps { - runId: string - robotType: RobotType - jumpedIndex: number | null - makeHandleScrollToStep: (index: number) => () => void -} -export const RunPreviewComponent = ( - { runId, jumpedIndex, makeHandleScrollToStep, robotType }: RunPreviewProps, - ref: React.ForwardedRef -): JSX.Element | null => { - const { t } = useTranslation('run_details') - const robotSideAnalysis = useMostRecentCompletedAnalysis(runId) - const runStatus = useRunStatus(runId) - const { data: runRecord } = useNotifyRunQuery(runId) - const isRunTerminal = - runStatus != null - ? (RUN_STATUSES_TERMINAL as RunStatus[]).includes(runStatus) - : false - // we only ever want one request done for terminal runs because this is a heavy request - const { - data: commandsFromQueryResponse, - isLoading: isRunCommandDataLoading, - } = useNotifyAllCommandsAsPreSerializedList( - runId, - { cursor: 0, pageLength: MAX_COMMANDS }, - { - staleTime: Infinity, - cacheTime: Infinity, - enabled: isRunTerminal, - } - ) - const commandsFromQuery = commandsFromQueryResponse?.data - const viewPortRef = React.useRef(null) - const currentRunCommandKey = useLastRunCommand(runId, { - refetchInterval: LIVE_RUN_COMMANDS_POLL_MS, - })?.key - const [ - isCurrentCommandVisible, - setIsCurrentCommandVisible, - ] = React.useState(true) - const filteredCommandsFromQuery = React.useMemo( - () => - commandsFromQuery?.filter( - command => !('intent' in command) || command.intent !== 'fixit' - ), - [commandsFromQuery == null] - ) - - if (robotSideAnalysis == null) { - return null - } - - const commands = isRunTerminal - ? filteredCommandsFromQuery - : robotSideAnalysis.commands - - // pass relevant data from run rather than analysis so that CommandText utilities can properly hash the entities' IDs - // TODO (nd:05/02/2024, AUTH-380): update name and types for CommandText (and children/utilities) use of analysis. - // We should ideally pass only subset of analysis/run data required by these children and utilities - const protocolDataFromAnalysisOrRun = - isRunTerminal && runRecord?.data != null - ? { - ...robotSideAnalysis, - labware: runRecord.data.labware ?? [], - modules: runRecord.data.modules ?? [], - pipettes: runRecord.data.pipettes ?? [], - liquids: runRecord.data.liquids ?? [], - commands: commands ?? [], - } - : robotSideAnalysis - const currentRunCommandIndex = - commands != null - ? commands.findIndex(c => c.key === currentRunCommandKey) - : 0 - if (isRunCommandDataLoading || commands == null) { - return ( - - - {t('protocol_setup:loading_data')} - - - ) - } - return commands.length === 0 ? ( - - - - ) : ( - - <> - - - {t('run_preview')} - - - {t('steps_total', { count: commands.length })} - - - - {t('preview_of_protocol_steps')} - - - { - if (currentRunCommandIndex >= 0) { - setIsCurrentCommandVisible( - currentRunCommandIndex >= lowestVisibleIndex && - currentRunCommandIndex <= highestVisibleIndex - ) - } - }} - initialIndex={currentRunCommandIndex} - > - {(command, index) => { - const isCurrent = index === currentRunCommandIndex - const backgroundColor = isCurrent ? COLORS.blue30 : COLORS.grey20 - const iconColor = isCurrent ? COLORS.blue60 : COLORS.grey50 - return ( - - - {index + 1} - - - - - - - - - ) - }} - - {currentRunCommandIndex >= 0 ? ( - - {t('view_current_step')} - - ) : null} - {currentRunCommandIndex === commands.length - 1 ? ( - - {t('end_of_protocol')} - - ) : null} - - - ) -} - -export const RunPreview = React.forwardRef(RunPreviewComponent) diff --git a/app/src/organisms/RunProgressMeter/index.tsx b/app/src/organisms/RunProgressMeter/index.tsx deleted file mode 100644 index 892a86c4b60..00000000000 --- a/app/src/organisms/RunProgressMeter/index.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' -import { css } from 'styled-components' - -import { - ALIGN_CENTER, - BORDERS, - COLORS, - DIRECTION_COLUMN, - Flex, - Icon, - JUSTIFY_SPACE_BETWEEN, - LegacyStyledText, - Link, - SIZE_1, - SPACING, - TOOLTIP_LEFT, - Tooltip, - TYPOGRAPHY, - useHoverTooltip, -} from '@opentrons/components' -import { useCommandQuery } from '@opentrons/react-api-client' -import { - RUN_STATUS_IDLE, - RUN_STATUS_FINISHING, - RUN_STATUS_RUNNING, -} from '@opentrons/api-client' - -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { getModalPortalEl } from '../../App/portal' -import { useRunStatus } from '../RunTimeControl/hooks' -import { InterventionModal, useInterventionModal } from '../InterventionModal' -import { ProgressBar } from '../../atoms/ProgressBar' -import { useDownloadRunLog, useRobotType } from '../Devices/hooks' -import { InterventionTicks } from './InterventionTicks' -import { - useNotifyRunQuery, - useNotifyAllCommandsQuery, -} from '../../resources/runs' -import { useRunningStepCounts } from '../../resources/protocols/hooks' -import { useRunProgressCopy } from './hooks' - -interface RunProgressMeterProps { - runId: string - robotName: string - makeHandleJumpToStep: (index: number) => () => void - resumeRunHandler: () => void -} -export function RunProgressMeter(props: RunProgressMeterProps): JSX.Element { - const { runId, robotName, makeHandleJumpToStep, resumeRunHandler } = props - const { t } = useTranslation('run_details') - const robotType = useRobotType(robotName) - const runStatus = useRunStatus(runId) - const [targetProps, tooltipProps] = useHoverTooltip({ - placement: TOOLTIP_LEFT, - }) - const { data: runRecord } = useNotifyRunQuery(runId) - const runData = runRecord?.data ?? null - - const { data: mostRecentCommandData } = useNotifyAllCommandsQuery(runId, { - cursor: null, - pageLength: 1, - }) - // This lastRunCommand also includes "fixit" commands. - const lastRunCommand = mostRecentCommandData?.data[0] ?? null - const { data: runCommandDetails } = useCommandQuery( - runId, - lastRunCommand?.id ?? null - ) - - const analysis = useMostRecentCompletedAnalysis(runId) - const analysisCommands = analysis?.commands ?? [] - - const { - currentStepNumber, - totalStepCount, - hasRunDiverged, - } = useRunningStepCounts(runId, mostRecentCommandData) - - const downloadIsDisabled = - runStatus === RUN_STATUS_RUNNING || - runStatus === RUN_STATUS_IDLE || - runStatus === RUN_STATUS_FINISHING - - const { downloadRunLog } = useDownloadRunLog(robotName, runId) - - const onDownloadClick: React.MouseEventHandler = e => { - if (downloadIsDisabled) return false - e.preventDefault() - e.stopPropagation() - downloadRunLog() - } - - const { - showModal: showIntervention, - modalProps: interventionProps, - } = useInterventionModal({ - robotName, - runStatus, - runData, - analysis, - lastRunCommand, - }) - - const { - progressPercentage, - stepCountStr, - currentStepContents, - } = useRunProgressCopy({ - runStatus, - robotType, - currentStepNumber, - totalStepCount, - analysis, - analysisCommands, - runCommandDetails: runCommandDetails ?? null, - hasRunDiverged, - }) - - return ( - <> - {showIntervention - ? createPortal( - , - getModalPortalEl() - ) - : null} - - - - - {stepCountStr} - - - {currentStepContents} - - - - - {t('download_run_log')} - - - {downloadIsDisabled ? ( - - {t('complete_protocol_to_download')} - - ) : null} - - {!hasRunDiverged ? ( - - - - ) : null} - - - ) -} diff --git a/app/src/organisms/RunTimeControl/__tests__/formatInterval.test.tsx b/app/src/organisms/RunTimeControl/__tests__/formatInterval.test.tsx deleted file mode 100644 index b5aa8543e0e..00000000000 --- a/app/src/organisms/RunTimeControl/__tests__/formatInterval.test.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { formatDuration, formatInterval } from '../utils' - -describe('formatInterval', () => { - it('should format a date string interval', () => { - const start = '2021-10-07T18:44:49.366581+00:00' - const end = '2021-10-07T21:24:51.366999+00:00' - - const expected = '02:40:02' - - expect(formatInterval(start, end)).toEqual(expected) - }) - - it('should format a small interval with plenty of zeroes', () => { - const start = '2021-10-07T18:44:49.366581+00:00' - const end = '2021-10-07T18:44:51.555555+00:00' - - const expected = '00:00:02' - - expect(formatInterval(start, end)).toEqual(expected) - }) - - it('should format a large, multiday interval', () => { - const start = '2021-10-07T18:44:49.366581+00:00' - const end = '2021-10-19T18:44:51.555555+00:00' - - const expected = '288:00:02' - - expect(formatInterval(start, end)).toEqual(expected) - }) -}) - -describe('formatDuration', () => { - it('should format a duration', () => { - const duration = { - hours: 2, - minutes: 40, - seconds: 2, - } - - const expected = '02:40:02' - - expect(formatDuration(duration)).toEqual(expected) - }) - - it('should format a short duration with plenty of zeroes', () => { - const duration = { - seconds: 2, - } - - const expected = '00:00:02' - - expect(formatDuration(duration)).toEqual(expected) - }) - - it('should format a longer duration', () => { - const duration = { - days: 3, - hours: 2, - minutes: 40, - seconds: 2, - } - - const expected = '74:40:02' - - expect(formatDuration(duration)).toEqual(expected) - }) -}) diff --git a/app/src/organisms/RunTimeControl/__tests__/hooks.test.tsx b/app/src/organisms/RunTimeControl/__tests__/hooks.test.tsx index 8107a236383..2ba0d50ea3b 100644 --- a/app/src/organisms/RunTimeControl/__tests__/hooks.test.tsx +++ b/app/src/organisms/RunTimeControl/__tests__/hooks.test.tsx @@ -4,27 +4,19 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' import { useRunActionMutations } from '@opentrons/react-api-client' -import { useCloneRun, useRunCommands } from '../../ProtocolUpload/hooks' +import { useRunControls, useCurrentRunStatus, useRunErrors } from '../hooks' import { - useRunControls, + useNotifyRunQuery, + useCurrentRunId, useRunStatus, - useCurrentRunStatus, - useRunTimestamps, - useRunErrors, -} from '../hooks' -import { useNotifyRunQuery, useCurrentRunId } from '../../../resources/runs' + useCloneRun, +} from '/app/resources/runs' import { RUN_ID_2, mockPausedRun, mockRunningRun, - mockFailedRun, - mockStoppedRun, - mockSucceededRun, - mockIdleUnstartedRun, - mockIdleStartedRun, - mockCommand, -} from '../__fixtures__' +} from '/app/resources/runs/__fixtures__' import type { UseQueryResult } from 'react-query' import type { Run } from '@opentrons/api-client' @@ -38,8 +30,8 @@ vi.mock('@opentrons/react-api-client', async importOriginal => { } }) -vi.mock('../../ProtocolUpload/hooks') -vi.mock('../../../resources/runs') +vi.mock('/app/resources/protocols') +vi.mock('/app/resources/runs') describe('useRunControls hook', () => { it('returns run controls hooks', () => { @@ -48,20 +40,25 @@ describe('useRunControls hook', () => { const mockStopRun = vi.fn() const mockCloneRun = vi.fn() const mockResumeRunFromRecovery = vi.fn() + const mockResumeRunFromRecoveryAssumingFalsePositive = vi.fn() when(useRunActionMutations).calledWith(mockPausedRun.id).thenReturn({ playRun: mockPlayRun, pauseRun: mockPauseRun, stopRun: mockStopRun, resumeRunFromRecovery: mockResumeRunFromRecovery, + resumeRunFromRecoveryAssumingFalsePositive: mockResumeRunFromRecoveryAssumingFalsePositive, isPlayRunActionLoading: false, isPauseRunActionLoading: false, isStopRunActionLoading: false, isResumeRunFromRecoveryActionLoading: false, + isResumeRunFromRecoveryAssumingFalsePositiveActionLoading: false, + }) + when(useCloneRun).calledWith(mockPausedRun.id, undefined, true).thenReturn({ + cloneRun: mockCloneRun, + isCloning: false, + isLoadingRun: false, }) - when(useCloneRun) - .calledWith(mockPausedRun.id, undefined, true) - .thenReturn({ cloneRun: mockCloneRun, isLoading: false }) const { result } = renderHook(() => useRunControls(mockPausedRun.id)) @@ -76,174 +73,16 @@ describe('useRunControls hook', () => { }) }) -describe('useRunStatus hook', () => { - it('returns the run status of the run', async () => { - when(useNotifyRunQuery) - .calledWith(RUN_ID_2, expect.any(Object)) - .thenReturn(({ - data: { data: mockRunningRun }, - } as unknown) as UseQueryResult) - - const { result } = renderHook(() => useRunStatus(RUN_ID_2)) - expect(result.current).toBe('running') - }) - - it('returns a "idle" run status if idle and run unstarted', () => { - when(useNotifyRunQuery) - .calledWith(RUN_ID_2, expect.any(Object)) - .thenReturn(({ - data: { data: mockIdleUnstartedRun }, - } as unknown) as UseQueryResult) - - const { result } = renderHook(() => useRunStatus(RUN_ID_2)) - expect(result.current).toBe('idle') - }) - - it('returns a "running" run status if idle and run started', () => { - when(useNotifyRunQuery) - .calledWith(RUN_ID_2, expect.any(Object)) - .thenReturn(({ - data: { data: mockIdleStartedRun }, - } as unknown) as UseQueryResult) - - const { result } = renderHook(() => useRunStatus(RUN_ID_2)) - expect(result.current).toBe('running') - }) -}) - describe('useCurrentRunStatus hook', () => { beforeEach(() => { when(useCurrentRunId).calledWith().thenReturn(RUN_ID_2) }) it('returns the run status of the current run', async () => { - when(useNotifyRunQuery) - .calledWith(RUN_ID_2, expect.any(Object)) - .thenReturn(({ - data: { data: mockRunningRun }, - } as unknown) as UseQueryResult) - + when(useRunStatus).calledWith(RUN_ID_2).thenReturn('running') const { result } = renderHook(useCurrentRunStatus) expect(result.current).toBe('running') }) - - it('returns a "idle" run status if idle and run unstarted', () => { - when(useNotifyRunQuery) - .calledWith(RUN_ID_2, expect.any(Object)) - .thenReturn(({ - data: { data: mockIdleUnstartedRun }, - } as unknown) as UseQueryResult) - - const { result } = renderHook(useCurrentRunStatus) - expect(result.current).toBe('idle') - }) - - it('returns a "running" run status if idle and run started', () => { - when(useNotifyRunQuery) - .calledWith(RUN_ID_2, expect.any(Object)) - .thenReturn(({ - data: { data: mockIdleStartedRun }, - } as unknown) as UseQueryResult) - - const { result } = renderHook(useCurrentRunStatus) - expect(result.current).toBe('running') - }) -}) - -describe('useRunTimestamps hook', () => { - beforeEach(() => { - when(useRunCommands) - .calledWith(RUN_ID_2, { cursor: null, pageLength: 1 }, expect.any(Object)) - .thenReturn([mockCommand.data as any]) - }) - - it('returns the start time of the current run', async () => { - when(useNotifyRunQuery) - .calledWith(RUN_ID_2, expect.any(Object)) - .thenReturn(({ - data: { data: mockRunningRun }, - } as unknown) as UseQueryResult) - - const { result } = renderHook(() => useRunTimestamps(RUN_ID_2)) - expect(result.current.startedAt).toBe('2021-10-25T12:54:53.366581+00:00') - }) - - it('returns null when pause is not the last action', async () => { - when(useNotifyRunQuery) - .calledWith(RUN_ID_2, expect.any(Object)) - .thenReturn(({ - data: { data: mockRunningRun }, - } as unknown) as UseQueryResult) - - const { result } = renderHook(() => useRunTimestamps(RUN_ID_2)) - expect(result.current.pausedAt).toBe(null) - }) - - it('returns the pause time of the current run when pause is the last action', async () => { - when(useNotifyRunQuery) - .calledWith(RUN_ID_2, expect.any(Object)) - .thenReturn(({ - data: { data: mockPausedRun }, - } as unknown) as UseQueryResult) - - const { result } = renderHook(() => useRunTimestamps(RUN_ID_2)) - expect(result.current.pausedAt).toBe('2021-10-25T13:23:31.366581+00:00') - }) - - it('returns stopped time null when stop is not the last action', async () => { - when(useNotifyRunQuery) - .calledWith(RUN_ID_2, expect.any(Object)) - .thenReturn(({ - data: { data: mockRunningRun }, - } as unknown) as UseQueryResult) - - const { result } = renderHook(() => useRunTimestamps(RUN_ID_2)) - expect(result.current.stoppedAt).toBe(null) - }) - - it('returns the stop time of the current run when stop is the last action', async () => { - when(useNotifyRunQuery) - .calledWith(RUN_ID_2, expect.any(Object)) - .thenReturn(({ - data: { data: mockStoppedRun }, - } as unknown) as UseQueryResult) - - const { result } = renderHook(() => useRunTimestamps(RUN_ID_2)) - expect(result.current.stoppedAt).toBe('2021-10-25T13:58:22.366581+00:00') - }) - - it('returns the complete time of a successful current run', async () => { - when(useNotifyRunQuery) - .calledWith(RUN_ID_2, expect.any(Object)) - .thenReturn(({ - data: { data: mockSucceededRun }, - } as unknown) as UseQueryResult) - - const { result } = renderHook(() => useRunTimestamps(RUN_ID_2)) - expect(result.current.completedAt).toBe('noon thirty') - }) - - it('returns the complete time of a failed current run', async () => { - when(useNotifyRunQuery) - .calledWith(RUN_ID_2, expect.any(Object)) - .thenReturn(({ - data: { data: mockFailedRun }, - } as unknown) as UseQueryResult) - - const { result } = renderHook(() => useRunTimestamps(RUN_ID_2)) - expect(result.current.completedAt).toBe('noon forty-five') - }) - - it('returns the complete time of a stopped current run', async () => { - when(useNotifyRunQuery) - .calledWith(RUN_ID_2, expect.any(Object)) - .thenReturn(({ - data: { data: mockStoppedRun }, - } as unknown) as UseQueryResult) - - const { result } = renderHook(() => useRunTimestamps(RUN_ID_2)) - expect(result.current.completedAt).toBe('2021-10-25T13:58:22.366581+00:00') - }) }) describe('useRunErrors hook', () => { diff --git a/app/src/organisms/RunTimeControl/hooks.ts b/app/src/organisms/RunTimeControl/hooks.ts index 606e5852f36..d2ddfaf4744 100644 --- a/app/src/organisms/RunTimeControl/hooks.ts +++ b/app/src/organisms/RunTimeControl/hooks.ts @@ -1,27 +1,16 @@ -import last from 'lodash/last' -import * as React from 'react' - -import { - RUN_ACTION_TYPE_PLAY, - RUN_ACTION_TYPE_PAUSE, - RUN_STATUS_IDLE, - RUN_STATUS_RUNNING, - RUN_STATUS_STOPPED, - RUN_STATUS_FAILED, - RUN_STATUS_FINISHING, - RUN_STATUS_SUCCEEDED, - RUN_ACTION_TYPE_STOP, - RUN_STATUS_STOP_REQUESTED, - RUN_STATUSES_TERMINAL, -} from '@opentrons/api-client' import { useRunActionMutations } from '@opentrons/react-api-client' -import { useCloneRun, useRunCommands } from '../ProtocolUpload/hooks' -import { useNotifyRunQuery, useCurrentRunId } from '../../resources/runs' -import { useMostRecentCompletedAnalysis } from '../LabwarePositionCheck/useMostRecentCompletedAnalysis' +import { + useNotifyRunQuery, + useCurrentRunId, + useRunStatus, + useCloneRun, + DEFAULT_RUN_QUERY_REFETCH_INTERVAL, + useMostRecentCompletedAnalysis, +} from '/app/resources/runs' import type { UseQueryOptions } from 'react-query' -import type { RunAction, RunStatus, Run, RunData } from '@opentrons/api-client' +import type { RunStatus, Run, RunData } from '@opentrons/api-client' export interface RunControls { play: () => void @@ -34,6 +23,7 @@ export interface RunControls { isStopRunActionLoading: boolean isResumeRunFromRecoveryActionLoading: boolean isResetRunLoading: boolean + isRunControlLoading: boolean } export function useRunControls( @@ -51,11 +41,11 @@ export function useRunControls( isResumeRunFromRecoveryActionLoading, } = useRunActionMutations(runId as string) - const { cloneRun, isLoading: isResetRunLoading } = useCloneRun( - runId ?? null, - onCloneRunSuccess, - true - ) + const { + cloneRun, + isLoadingRun: isRunControlLoading, + isCloning: isResetRunLoading, + } = useCloneRun(runId ?? null, onCloneRunSuccess, true) return { play: playRun, @@ -67,46 +57,11 @@ export function useRunControls( isPauseRunActionLoading, isStopRunActionLoading, isResumeRunFromRecoveryActionLoading, + isRunControlLoading, isResetRunLoading, } } -const DEFAULT_STATUS_REFETCH_INTERVAL = 10000 // 10 seconds -export function useRunStatus( - runId: string | null, - options?: UseQueryOptions -): RunStatus | null { - const lastRunStatus = React.useRef(null) - - const { data } = useNotifyRunQuery(runId ?? null, { - refetchInterval: DEFAULT_STATUS_REFETCH_INTERVAL, - enabled: - lastRunStatus.current == null || - !(RUN_STATUSES_TERMINAL as RunStatus[]).includes(lastRunStatus.current), - onSuccess: data => (lastRunStatus.current = data?.data?.status ?? null), - ...options, - }) - - const runStatus = data?.data?.status as RunStatus - - const actions = data?.data?.actions as RunAction[] - const firstPlay = actions?.find( - action => action.actionType === RUN_ACTION_TYPE_PLAY - ) - const runStartTime = firstPlay?.createdAt - - // display an idle status as 'running' in the UI after a run has started. - // todo(mm, 2024-06-24): This may not be necessary anymore. It looks like it was - // working around prior (?) server behavior where a run's status would briefly flicker - // to idle in between commands. - const adjustedRunStatus: RunStatus | null = - runStatus === RUN_STATUS_IDLE && runStartTime != null - ? RUN_STATUS_RUNNING - : runStatus - - return adjustedRunStatus -} - export function useCurrentRunStatus( options?: UseQueryOptions ): RunStatus | null { @@ -115,71 +70,6 @@ export function useCurrentRunStatus( return useRunStatus(currentRunId, options) } -export interface RunTimestamps { - startedAt: string | null - pausedAt: string | null - stoppedAt: string | null - completedAt: string | null -} - -const DEFAULT_RUN_QUERY_REFETCH_INTERVAL = 5000 -export function useRunTimestamps(runId: string | null): RunTimestamps { - const runStatus = useRunStatus(runId) - const { actions = [], errors = [] } = - useNotifyRunQuery(runId, { - refetchInterval: DEFAULT_RUN_QUERY_REFETCH_INTERVAL, - })?.data?.data ?? {} - const runCommands = - useRunCommands( - runId, - { cursor: null, pageLength: 1 }, - { - enabled: - runStatus === RUN_STATUS_SUCCEEDED || - runStatus === RUN_STATUS_STOPPED || - runStatus === RUN_STATUS_FAILED || - runStatus === RUN_STATUS_STOP_REQUESTED || - runStatus === RUN_STATUS_FINISHING, - refetchInterval: false, - } - ) ?? [] - - const firstPlay = actions.find( - action => action.actionType === RUN_ACTION_TYPE_PLAY - ) - const lastAction = last(actions) - - const lastCommand = last(runCommands) - const lastActionAt = lastAction?.createdAt ?? null - const lastErrorAt = last(errors)?.createdAt - const lastCommandAt = lastCommand?.completedAt - - const startedAt = firstPlay?.createdAt ?? null - const pausedAt = - lastAction?.actionType === RUN_ACTION_TYPE_PAUSE ? lastActionAt : null - const stoppedAt = - lastAction?.actionType === RUN_ACTION_TYPE_STOP ? lastActionAt : null - let completedAt = null - switch (runStatus) { - case RUN_STATUS_STOPPED: - completedAt = lastActionAt ?? null - break - case RUN_STATUS_FAILED: - completedAt = lastErrorAt ?? null - break - case RUN_STATUS_SUCCEEDED: - completedAt = lastCommandAt ?? null - break - } - - return { - startedAt, - pausedAt, - stoppedAt, - completedAt, - } -} - export function useRunErrors(runId: string | null): RunData['errors'] { const { data: runRecord } = useNotifyRunQuery(runId, { refetchInterval: DEFAULT_RUN_QUERY_REFETCH_INTERVAL, diff --git a/app/src/organisms/RunTimeControl/index.ts b/app/src/organisms/RunTimeControl/index.ts new file mode 100644 index 00000000000..fc78d35129c --- /dev/null +++ b/app/src/organisms/RunTimeControl/index.ts @@ -0,0 +1 @@ +export * from './hooks' diff --git a/app/src/organisms/RunTimeControl/utils.ts b/app/src/organisms/RunTimeControl/utils.ts deleted file mode 100644 index e95efcaf164..00000000000 --- a/app/src/organisms/RunTimeControl/utils.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { intervalToDuration } from 'date-fns' -import padStart from 'lodash/padStart' -import type { Duration } from 'date-fns' - -/** - * utility to format a date-fns duration object to hh:mm:ss - * @param duration date-fns duration object - * @returns string in format hh:mm:ss, e.g. 03:15:45 - */ -export function formatDuration(duration: Duration): string { - const { days, hours, minutes, seconds } = duration - - // edge case: protocol runs (or is paused) for over 24 hours - const hoursWithDays = days != null ? days * 24 + (hours ?? 0) : hours - - const paddedHours = padStart(hoursWithDays?.toString(), 2, '0') - const paddedMinutes = padStart(minutes?.toString(), 2, '0') - const paddedSeconds = padStart(seconds?.toString(), 2, '0') - - return `${paddedHours}:${paddedMinutes}:${paddedSeconds}` -} - -/** - * utility to format a date interval to a hh:mm:ss duration - * @param start date string - * @param end date string - * @returns string in format hh:mm:ss, e.g. 03:15:45 - */ -export function formatInterval(start: string, end: string): string { - const duration = intervalToDuration({ - start: new Date(start), - end: new Date(end), - }) - return formatDuration(duration) -} diff --git a/app/src/organisms/SendProtocolToFlexSlideout/index.tsx b/app/src/organisms/SendProtocolToFlexSlideout/index.tsx deleted file mode 100644 index e52b004daad..00000000000 --- a/app/src/organisms/SendProtocolToFlexSlideout/index.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' - -import { useCreateProtocolMutation } from '@opentrons/react-api-client' - -import { FLEX_DISPLAY_NAME, FLEX_ROBOT_TYPE } from '@opentrons/shared-data' - -import { - PrimaryButton, - ERROR_TOAST, - INFO_TOAST, - SUCCESS_TOAST, -} from '@opentrons/components' -import { ChooseRobotSlideout } from '../../organisms/ChooseRobotSlideout' -import { - getAnalysisStatus, - getProtocolDisplayName, -} from '../../organisms/ProtocolsLanding/utils' -import { useToaster } from '../../organisms/ToasterOven' -import { appShellRequestor } from '../../redux/shell/remote' -import { OPENTRONS_USB } from '../../redux/discovery' -import { getIsProtocolAnalysisInProgress } from '../../redux/protocol-storage' -import { getRobotUpdateDisplayInfo } from '../../redux/robot-update' -import { getValidCustomLabwareFiles } from '../../redux/custom-labware' - -import type { AxiosError } from 'axios' -import type { IconProps, StyleProps } from '@opentrons/components' -import type { Robot } from '../../redux/discovery/types' -import type { StoredProtocolData } from '../../redux/protocol-storage' -import type { State } from '../../redux/types' - -const _getFileBaseName = (filePath: string): string => { - return filePath.split('/').reverse()[0] -} - -interface SendProtocolToFlexSlideoutProps extends StyleProps { - storedProtocolData: StoredProtocolData - onCloseClick: () => void - isExpanded: boolean -} - -export function SendProtocolToFlexSlideout( - props: SendProtocolToFlexSlideoutProps -): JSX.Element | null { - const { isExpanded, onCloseClick, storedProtocolData } = props - const { - protocolKey, - srcFileNames, - srcFiles, - mostRecentAnalysis, - } = storedProtocolData - const { t } = useTranslation(['protocol_details', 'protocol_list']) - - const [selectedRobot, setSelectedRobot] = React.useState(null) - - const { autoUpdateAction } = useSelector((state: State) => - getRobotUpdateDisplayInfo(state, selectedRobot?.name ?? '') - ) - - const isSelectedRobotOnDifferentSoftwareVersion = [ - 'upgrade', - 'downgrade', - ].includes(autoUpdateAction) - - const { eatToast, makeToast } = useToaster() - - const { mutateAsync: createProtocolAsync } = useCreateProtocolMutation( - {}, - selectedRobot != null - ? { - hostname: selectedRobot.ip, - requestor: - selectedRobot?.ip === OPENTRONS_USB ? appShellRequestor : undefined, - } - : null - ) - - const isAnalyzing = useSelector((state: State) => - getIsProtocolAnalysisInProgress(state, protocolKey) - ) - const customLabwareFiles = useSelector(getValidCustomLabwareFiles) - - const analysisStatus = getAnalysisStatus(isAnalyzing, mostRecentAnalysis) - - if (protocolKey == null || srcFileNames == null || srcFiles == null) { - // TODO: do more robust corrupt file catching and handling here - return null - } - - const srcFileObjects = srcFiles.map((srcFileBuffer, index) => { - const srcFilePath = srcFileNames[index] - return new File([srcFileBuffer], _getFileBaseName(srcFilePath)) - }) - - const protocolDisplayName = getProtocolDisplayName( - protocolKey, - srcFileNames, - mostRecentAnalysis - ) - - const icon: IconProps = { name: 'ot-spinner', spin: true } - - const handleSendClick = (): void => { - const toastId = makeToast(selectedRobot?.name ?? '', INFO_TOAST, { - heading: `${t('sending')} ${protocolDisplayName}`, - icon, - maxWidth: '31.25rem', - disableTimeout: true, - }) - - createProtocolAsync({ - files: [...srcFileObjects, ...customLabwareFiles], - protocolKey, - }) - .then(() => { - eatToast(toastId) - makeToast(selectedRobot?.name ?? '', SUCCESS_TOAST, { - heading: `${t('successfully_sent')} ${protocolDisplayName}`, - }) - onCloseClick() - }) - .catch( - ( - error: AxiosError<{ - errors: Array<{ id: string; detail: string; title: string }> - }> - ) => { - eatToast(toastId) - const [errorDetail] = error?.response?.data?.errors ?? [] - const { id, detail, title } = errorDetail ?? {} - if (id != null && detail != null && title != null) { - makeToast(detail, ERROR_TOAST, { - closeButton: true, - disableTimeout: true, - heading: `${protocolDisplayName} ${title} - ${ - selectedRobot?.name ?? '' - }`, - }) - } else { - makeToast(selectedRobot?.name ?? '', ERROR_TOAST, { - closeButton: true, - disableTimeout: true, - heading: `${t('unsuccessfully_sent')} ${protocolDisplayName}`, - }) - } - onCloseClick() - } - ) - } - - return ( - - {t('protocol_details:send')} -
    - } - selectedRobot={selectedRobot} - setSelectedRobot={setSelectedRobot} - robotType={FLEX_ROBOT_TYPE} - isAnalysisError={analysisStatus === 'error'} - isAnalysisStale={analysisStatus === 'stale'} - /> - ) -} diff --git a/app/src/organisms/TakeoverModal/MaintenanceRunStatusProvider.tsx b/app/src/organisms/TakeoverModal/MaintenanceRunStatusProvider.tsx index 46b2062de39..293340a9d85 100644 --- a/app/src/organisms/TakeoverModal/MaintenanceRunStatusProvider.tsx +++ b/app/src/organisms/TakeoverModal/MaintenanceRunStatusProvider.tsx @@ -1,6 +1,7 @@ -import * as React from 'react' +import { useState, useEffect, useMemo, createContext } from 'react' +import { useNotifyCurrentMaintenanceRun } from '/app/resources/maintenance_runs' -import { useNotifyCurrentMaintenanceRun } from '../../resources/maintenance_runs' +import type { ReactNode } from 'react' interface MaintenanceRunIds { currentRunId: string | null @@ -12,19 +13,19 @@ export interface MaintenanceRunStatus { setOddRunIds: (state: MaintenanceRunIds) => void } -export const MaintenanceRunContext = React.createContext({ +export const MaintenanceRunContext = createContext({ getRunIds: () => ({ currentRunId: null, oddRunId: null }), setOddRunIds: () => {}, }) interface MaintenanceRunProviderProps { - children?: React.ReactNode + children?: ReactNode } export function MaintenanceRunStatusProvider( props: MaintenanceRunProviderProps ): JSX.Element { - const [oddRunIds, setOddRunIds] = React.useState({ + const [oddRunIds, setOddRunIds] = useState({ currentRunId: null, oddRunId: null, }) @@ -33,14 +34,14 @@ export function MaintenanceRunStatusProvider( refetchInterval: 5000, }).data?.data.id - React.useEffect(() => { + useEffect(() => { setOddRunIds(prevState => ({ ...prevState, currentRunId: currentRunIdQueryResult ?? null, })) }, [currentRunIdQueryResult]) - const maintenanceRunStatus = React.useMemo( + const maintenanceRunStatus = useMemo( () => ({ getRunIds: () => oddRunIds, setOddRunIds, diff --git a/app/src/organisms/TakeoverModal/MaintenanceRunTakeover.tsx b/app/src/organisms/TakeoverModal/MaintenanceRunTakeover.tsx index b4cef390203..bb2b380cef1 100644 --- a/app/src/organisms/TakeoverModal/MaintenanceRunTakeover.tsx +++ b/app/src/organisms/TakeoverModal/MaintenanceRunTakeover.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useDeleteMaintenanceRunMutation } from '@opentrons/react-api-client' @@ -7,8 +7,10 @@ import { TakeoverModal } from './TakeoverModal' import { MaintenanceRunStatusProvider } from './MaintenanceRunStatusProvider' import { useMaintenanceRunTakeover } from './useMaintenanceRunTakeover' +import type { ReactNode } from 'react' + interface MaintenanceRunTakeoverProps { - children: React.ReactNode + children: ReactNode } export function MaintenanceRunTakeover({ @@ -22,18 +24,18 @@ export function MaintenanceRunTakeover({ } interface MaintenanceRunTakeoverModalProps { - children: React.ReactNode + children: ReactNode } export function MaintenanceRunTakeoverModal( props: MaintenanceRunTakeoverModalProps ): JSX.Element { const { i18n, t } = useTranslation(['shared', 'branded']) - const [isLoading, setIsLoading] = React.useState(false) + const [isLoading, setIsLoading] = useState(false) const [ showConfirmTerminateModal, setShowConfirmTerminateModal, - ] = React.useState(false) + ] = useState(false) const { oddRunId, currentRunId } = useMaintenanceRunTakeover().getRunIds() const isMaintenanceRunCurrent = currentRunId != null @@ -50,7 +52,7 @@ export function MaintenanceRunTakeoverModal( } } - React.useEffect(() => { + useEffect(() => { if (currentRunId == null) { setIsLoading(false) setShowConfirmTerminateModal(false) diff --git a/app/src/organisms/TakeoverModal/TakeoverModal.tsx b/app/src/organisms/TakeoverModal/TakeoverModal.tsx index d8cc732d445..8f6441124a7 100644 --- a/app/src/organisms/TakeoverModal/TakeoverModal.tsx +++ b/app/src/organisms/TakeoverModal/TakeoverModal.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' @@ -14,11 +14,11 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { getTopPortalEl } from '../../App/portal' -import { SmallButton } from '../../atoms/buttons' -import { OddModal } from '../../molecules/OddModal' +import { getTopPortalEl } from '/app/App/portal' +import { SmallButton } from '/app/atoms/buttons' +import { OddModal } from '/app/molecules/OddModal' -import type { OddModalHeaderBaseProps } from '../../molecules/OddModal/types' +import type { OddModalHeaderBaseProps } from '/app/molecules/OddModal/types' interface TakeoverModalProps { title: string diff --git a/app/src/organisms/TakeoverModal/__tests__/MaintenanceRunTakeover.test.tsx b/app/src/organisms/TakeoverModal/__tests__/MaintenanceRunTakeover.test.tsx index 0b236577a97..94a59aa903a 100644 --- a/app/src/organisms/TakeoverModal/__tests__/MaintenanceRunTakeover.test.tsx +++ b/app/src/organisms/TakeoverModal/__tests__/MaintenanceRunTakeover.test.tsx @@ -1,17 +1,17 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { useMaintenanceRunTakeover } from '../useMaintenanceRunTakeover' import { MaintenanceRunTakeover } from '../MaintenanceRunTakeover' -import { useNotifyCurrentMaintenanceRun } from '../../../resources/maintenance_runs' +import { useNotifyCurrentMaintenanceRun } from '/app/resources/maintenance_runs' import type { MaintenanceRunStatus } from '../MaintenanceRunStatusProvider' vi.mock('../useMaintenanceRunTakeover') -vi.mock('../../../resources/maintenance_runs') +vi.mock('/app/resources/maintenance_runs') const MOCK_MAINTENANCE_RUN: MaintenanceRunStatus = { getRunIds: () => ({ diff --git a/app/src/organisms/TakeoverModal/__tests__/TakeoverModal.test.tsx b/app/src/organisms/TakeoverModal/__tests__/TakeoverModal.test.tsx index eff8b68c101..a902544e4a0 100644 --- a/app/src/organisms/TakeoverModal/__tests__/TakeoverModal.test.tsx +++ b/app/src/organisms/TakeoverModal/__tests__/TakeoverModal.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { TakeoverModal } from '../TakeoverModal' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/TakeoverModal/useMaintenanceRunTakeover.ts b/app/src/organisms/TakeoverModal/useMaintenanceRunTakeover.ts index 3c6373dee07..f166cc9a9d2 100644 --- a/app/src/organisms/TakeoverModal/useMaintenanceRunTakeover.ts +++ b/app/src/organisms/TakeoverModal/useMaintenanceRunTakeover.ts @@ -1,7 +1,7 @@ -import * as React from 'react' +import { useContext } from 'react' import { MaintenanceRunContext } from './MaintenanceRunStatusProvider' import type { MaintenanceRunStatus } from './MaintenanceRunStatusProvider' export function useMaintenanceRunTakeover(): MaintenanceRunStatus { - return React.useContext(MaintenanceRunContext) + return useContext(MaintenanceRunContext) } diff --git a/app/src/organisms/TaskList/index.tsx b/app/src/organisms/TaskList/index.tsx deleted file mode 100644 index e6f6bef54e1..00000000000 --- a/app/src/organisms/TaskList/index.tsx +++ /dev/null @@ -1,544 +0,0 @@ -import * as React from 'react' - -import { - ALIGN_CENTER, - ALIGN_FLEX_START, - BORDERS, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - FLEX_NONE, - Flex, - Icon, - JUSTIFY_CENTER, - JUSTIFY_SPACE_BETWEEN, - LegacyStyledText, - Link, - SPACING, - Tooltip, - TYPOGRAPHY, - useHoverTooltip, -} from '@opentrons/components' - -import { TertiaryButton } from '../../atoms/buttons' - -import type { SubTaskProps, TaskListProps, TaskProps } from './types' - -const TASK_CONNECTOR_STYLE = `1px solid ${COLORS.grey40}` - -interface ProgressTrackerItemProps { - activeIndex: [number, number] | null - subTasks: SubTaskProps[] - taskIndex: number - taskListLength: number - isComplete?: boolean -} - -function ProgressTrackerItem({ - activeIndex, - subTasks, - taskIndex, - taskListLength, - isComplete = false, -}: ProgressTrackerItemProps): JSX.Element { - const [activeTaskIndex, activeSubTaskIndex] = activeIndex ?? [] - - const isTaskListComplete = activeIndex == null - const isPastTask = activeTaskIndex != null && taskIndex < activeTaskIndex - const isLastTask = taskIndex === taskListLength - 1 - const hasSubTasks = subTasks.length > 0 - const isActiveTaskWithSubtasks = taskIndex === activeTaskIndex && hasSubTasks - const isFutureTask = activeTaskIndex != null && taskIndex > activeTaskIndex - - // a connector between task icons - const taskConnector = ( - - ) - - const noSubTaskConnector = !isLastTask ? taskConnector : null - - return ( - - {isComplete || isTaskListComplete || isPastTask ? ( - - ) : ( - - - {(taskIndex + 1).toString()} - - - )} - {!hasSubTasks ? ( - noSubTaskConnector - ) : ( - <> - {/** - * iterate subtask completion list - - * APPROXIMATION: average amount of space via flex-grow to position the substep connectors/icons - * ASSUMPTION: substeps don't vary much in size for current use case - maybe one line of wrapped text at most - * TODO (bh, 9/28/2022): this could change in the future if the task list is used for tasks that contain differently sized children, like deck map rendering, etc - * a more robust solution to subtask icon layout could implement an n x 2 grid where n is the combined number of tasks/subtasks, in two columns (fixed size, 1fr) - * this would require top level coordination of both the number of tasks/subtasks and the open status of each task - * which is possible, but nice to avoid - * */} - {taskConnector} - {subTasks.map((subTask, subTaskIndex) => { - const isPastSubTask = - (activeTaskIndex != null && - activeSubTaskIndex != null && - subTaskIndex <= activeSubTaskIndex && - taskIndex < activeTaskIndex) || - (activeTaskIndex != null && - subTask.isComplete === true && - taskIndex <= activeTaskIndex) - const isFutureSubTask = - (activeSubTaskIndex != null && - activeTaskIndex != null && - subTaskIndex > activeSubTaskIndex && - taskIndex >= activeTaskIndex) || - isFutureTask - // last subtask of the parent task - const isLastSubTask = subTaskIndex === subTasks.length - 1 - // last subtask of the last task of the entire list - const isFinalSubTaskOfTaskList = isLastSubTask && isLastTask - - return ( - - {/* subtask circle icon component */} - - {/* subtask connector component */} - - - ) - })} - - )} - - ) -} - -function SubTask({ - activeIndex, - subTaskIndex, - taskIndex, - title, - description, - cta, - footer, - markedBad, - generalClickHandler, - generalTaskDisabledReason, -}: SubTaskProps): JSX.Element { - const [targetProps, tooltipProps] = useHoverTooltip() - - const [activeTaskIndex, activeSubTaskIndex] = activeIndex ?? [] - - const isTaskListComplete = activeIndex == null - const isActiveSubTask = - activeSubTaskIndex === subTaskIndex && activeTaskIndex === taskIndex - const isPastSubTask = - activeTaskIndex != null && - activeSubTaskIndex != null && - ((activeSubTaskIndex > subTaskIndex && activeTaskIndex === taskIndex) || - activeTaskIndex > taskIndex) - const isDisabled = generalTaskDisabledReason != null - - return ( - - - - - {title} - - - {description} - {footer != null ? ( - - - {markedBad === true && ( - - )} - {footer} - - - ) : null} - - {(isTaskListComplete || isPastSubTask) && cta != null ? ( - <> - { - if (isDisabled) { - return - } - if (generalClickHandler != null) { - generalClickHandler() - } - cta.onClick() - }} - > - {cta.label} - - {isDisabled ? ( - - {generalTaskDisabledReason} - - ) : null} - - ) : null} - {isActiveSubTask && cta != null ? ( - <> - { - if (generalClickHandler != null) { - generalClickHandler() - } - cta.onClick() - }} - > - {cta.label} - - {isDisabled ? ( - - {generalTaskDisabledReason} - - ) : null} - - ) : null} - - ) -} - -function Task({ - activeIndex, - taskIndex, - title, - description, - cta, - footer, - subTasks, - taskListLength, - isComplete, - markedBad, - generalClickHandler, - generalTaskDisabledReason, -}: TaskProps): JSX.Element { - const [targetProps, tooltipProps] = useHoverTooltip() - const [activeTaskIndex] = activeIndex ?? [] - - // TODO(bh, 2022-10-18): pass booleans to children as props - const isTaskListComplete = activeIndex == null - const isPastTask = activeTaskIndex != null && taskIndex < activeTaskIndex - const isActiveTask = activeTaskIndex === taskIndex - const hasSubTasks = subTasks.length > 0 - const isDisabled = generalTaskDisabledReason != null - - const [isTaskOpen, setIsTaskOpen] = React.useState( - hasSubTasks && isActiveTask - ) - - React.useEffect(() => { - setIsTaskOpen(hasSubTasks && isActiveTask) - }, [isActiveTask, hasSubTasks]) - - return ( - - - - { - if (hasSubTasks) setIsTaskOpen(!isTaskOpen) - }} - > - - - - {markedBad === true && ( - - )} - {title} - - - {description} - {footer != null ? ( - - - {footer} - - - ) : null} - - {/* if subtasks, caret, otherwise show cta as link or button */} - {hasSubTasks ? ( - - ) : (isTaskListComplete || isPastTask) && cta != null ? ( - <> - { - if (isDisabled) { - return - } - if (generalClickHandler != null) { - generalClickHandler() - } - cta.onClick() - }} - > - {cta.label} - - {isDisabled ? ( - - {generalTaskDisabledReason} - - ) : null} - - ) : null} - {isActiveTask && cta != null ? ( - <> - { - if (generalClickHandler != null) { - generalClickHandler() - } - cta.onClick() - }} - > - {cta.label} - - {isDisabled ? ( - - {generalTaskDisabledReason} - - ) : null} - - ) : null} - - {isTaskOpen ? ( - - {subTasks.map( - ( - { title, description, cta, footer, markedBad }, - subTaskIndex - ) => ( - - ) - )} - - ) : null} - - - ) -} - -export function TaskList({ - activeIndex, - taskList, - generalTaskClickHandler, - generalTaskDisabledReason, -}: TaskListProps): JSX.Element { - return ( - - {taskList.map( - ( - { title, description, cta, footer, subTasks, isComplete, markedBad }, - taskIndex - ) => ( - - ) - )} - - ) -} diff --git a/app/src/organisms/ToasterOven/ToasterContext.ts b/app/src/organisms/ToasterOven/ToasterContext.ts index 0634a22ae5d..3467da62872 100644 --- a/app/src/organisms/ToasterOven/ToasterContext.ts +++ b/app/src/organisms/ToasterOven/ToasterContext.ts @@ -1,4 +1,4 @@ -import * as React from 'react' +import { createContext } from 'react' import type { ToastProps, @@ -25,7 +25,7 @@ export interface ToasterContextType { makeSnackbar: MakeSnackbar } -export const ToasterContext = React.createContext({ +export const ToasterContext = createContext({ eatToast: () => {}, makeToast: () => '', makeSnackbar: () => {}, diff --git a/app/src/organisms/ToasterOven/ToasterOven.tsx b/app/src/organisms/ToasterOven/ToasterOven.tsx index 8a69390e7a3..c3130793750 100644 --- a/app/src/organisms/ToasterOven/ToasterOven.tsx +++ b/app/src/organisms/ToasterOven/ToasterOven.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { useSelector } from 'react-redux' import { v4 as uuidv4 } from 'uuid' @@ -14,9 +14,10 @@ import { Toast, } from '@opentrons/components' -import { getIsOnDevice } from '../../redux/config' +import { getIsOnDevice } from '/app/redux/config' import { ToasterContext } from './ToasterContext' +import type { ReactNode } from 'react' import type { SnackbarProps } from '@opentrons/components' import type { ToastProps, @@ -25,7 +26,7 @@ import type { import type { MakeSnackbarOptions, MakeToastOptions } from './ToasterContext' interface ToasterOvenProps { - children: React.ReactNode + children: ReactNode } /** @@ -34,8 +35,8 @@ interface ToasterOvenProps { * @returns */ export function ToasterOven({ children }: ToasterOvenProps): JSX.Element { - const [toasts, setToasts] = React.useState([]) - const [snackbar, setSnackbar] = React.useState(null) + const [toasts, setToasts] = useState([]) + const [snackbar, setSnackbar] = useState(null) const isOnDevice = useSelector(getIsOnDevice) ?? null const displayType: 'desktop' | 'odd' = diff --git a/app/src/organisms/ToasterOven/hooks.ts b/app/src/organisms/ToasterOven/hooks.ts index 5c37a0e4cb0..555a08768bb 100644 --- a/app/src/organisms/ToasterOven/hooks.ts +++ b/app/src/organisms/ToasterOven/hooks.ts @@ -1,11 +1,11 @@ -import * as React from 'react' +import { useContext } from 'react' import { ToasterContext } from './ToasterContext' import type { ToasterContextType } from './ToasterContext' export function useToaster(): ToasterContextType { - const { eatToast, makeToast, makeSnackbar } = React.useContext(ToasterContext) + const { eatToast, makeToast, makeSnackbar } = useContext(ToasterContext) return { eatToast, makeToast, makeSnackbar } } diff --git a/app/src/organisms/UpdateAppModal/index.tsx b/app/src/organisms/UpdateAppModal/index.tsx deleted file mode 100644 index 510bf824e75..00000000000 --- a/app/src/organisms/UpdateAppModal/index.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import * as React from 'react' -import { useSelector, useDispatch } from 'react-redux' -import styled, { css } from 'styled-components' -import { useNavigate } from 'react-router-dom' -import { useTranslation } from 'react-i18next' - -import { - ALIGN_CENTER, - BORDERS, - COLORS, - DIRECTION_COLUMN, - Flex, - JUSTIFY_SPACE_AROUND, - JUSTIFY_SPACE_BETWEEN, - PrimaryButton, - SecondaryButton, - SPACING, - LegacyStyledText, - Modal, -} from '@opentrons/components' - -import { - getShellUpdateState, - getAvailableShellUpdate, - downloadShellUpdate, - applyShellUpdate, -} from '../../redux/shell' - -import { ExternalLink } from '../../atoms/Link/ExternalLink' -import { ReleaseNotes } from '../../molecules/ReleaseNotes' -import { Banner } from '../../atoms/Banner' -import { ProgressBar } from '../../atoms/ProgressBar' -import { useRemoveActiveAppUpdateToast } from '../Alerts' - -import type { Dispatch } from '../../redux/types' - -interface PlaceHolderErrorProps { - errorMessage?: string -} - -const PlaceholderError = ({ - errorMessage, -}: PlaceHolderErrorProps): JSX.Element => { - const SOMETHING_WENT_WRONG = 'Something went wrong while updating your app.' - const AN_UNKNOWN_ERROR_OCCURRED = 'An unknown error occurred.' - const FALLBACK_ERROR_MESSAGE = `If you keep getting this message, try restarting your app and/or - robot. If this does not resolve the issue please contact Opentrons - Support.` - - return ( - <> - {SOMETHING_WENT_WRONG} -
    -
    - {errorMessage ?? AN_UNKNOWN_ERROR_OCCURRED} -
    -
    - {FALLBACK_ERROR_MESSAGE} - - ) -} -export const RELEASE_NOTES_URL_BASE = - 'https://github.com/Opentrons/opentrons/releases/tag/v' -const UPDATE_ERROR = 'Update Error' - -const UpdateAppBanner = styled(Banner)` - border: none; -` -const UPDATE_PROGRESS_BAR_STYLE = css` - margin-top: ${SPACING.spacing24}; - border-radius: ${BORDERS.borderRadius8}; - background: ${COLORS.grey30}; - width: 17.12rem; -` -const LEGACY_MODAL_STYLE = css` - width: 40rem; - margin-left: 5.336rem; -` - -const RESTART_APP_AFTER_TIME = 5000 - -export interface UpdateAppModalProps { - closeModal: (arg0: boolean) => void -} - -export function UpdateAppModal(props: UpdateAppModalProps): JSX.Element { - const { closeModal } = props - const dispatch = useDispatch() - const updateState = useSelector(getShellUpdateState) - const { - downloaded, - downloading, - downloadPercentage, - error, - info: updateInfo, - } = updateState - const releaseNotes = updateInfo?.releaseNotes - const { t } = useTranslation(['app_settings', 'branded']) - const navigate = useNavigate() - const { removeActiveAppUpdateToast } = useRemoveActiveAppUpdateToast() - const availableAppUpdateVersion = useSelector(getAvailableShellUpdate) ?? '' - - if (downloaded) - setTimeout(() => dispatch(applyShellUpdate()), RESTART_APP_AFTER_TIME) - - const handleRemindMeLaterClick = (): void => { - navigate('/app-settings/general') - closeModal(true) - } - - removeActiveAppUpdateToast() - - const appUpdateFooter = ( - - - {t('release_notes')} - - - - {t('remind_later')} - - dispatch(downloadShellUpdate())} - marginRight={SPACING.spacing12} - > - {t('update_app_now')} - - - - ) - - return ( - <> - {error != null ? ( - { - closeModal(true) - }} - css={LEGACY_MODAL_STYLE} - > - - - ) : null} - {(downloading || downloaded) && error == null ? ( - - - - {downloading ? t('download_update') : t('restarting_app')} - - - - - ) : null} - {!downloading && !downloaded && error == null ? ( - { - closeModal(true) - }} - closeOnOutsideClick={true} - footer={appUpdateFooter} - maxHeight="80%" - css={LEGACY_MODAL_STYLE} - > - - - {t('branded:update_requires_restarting_app')} - - - - - ) : null} - - ) -} diff --git a/app/src/organisms/UpdateRobotBanner/index.tsx b/app/src/organisms/UpdateRobotBanner/index.tsx deleted file mode 100644 index 7d443e3ddfd..00000000000 --- a/app/src/organisms/UpdateRobotBanner/index.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import * as React from 'react' -import { useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import { - Btn, - DIRECTION_COLUMN, - Flex, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' -import { Banner } from '../../atoms/Banner' -import { getRobotUpdateDisplayInfo } from '../../redux/robot-update' -import { handleUpdateBuildroot } from '../Devices/RobotSettings/UpdateBuildroot' - -import type { StyleProps } from '@opentrons/components' -import type { State } from '../../redux/types' -import type { DiscoveredRobot } from '../../redux/discovery/types' - -interface UpdateRobotBannerProps extends StyleProps { - robot: DiscoveredRobot -} - -export function UpdateRobotBanner( - props: UpdateRobotBannerProps -): JSX.Element | null { - const { robot, ...styleProps } = props - const { t } = useTranslation(['device_settings', 'branded']) - - const { autoUpdateAction } = useSelector((state: State) => { - return getRobotUpdateDisplayInfo(state, robot?.name) - }) - - return (autoUpdateAction === 'upgrade' || autoUpdateAction === 'downgrade') && - robot !== null && - robot.healthStatus === 'ok' ? ( - { - e.stopPropagation() - }} - flexDirection={DIRECTION_COLUMN} - > - - - {t('branded:robot_software_update_required')} - - { - handleUpdateBuildroot(robot) - }} - css={TYPOGRAPHY.pRegular} - textDecoration={TYPOGRAPHY.textDecorationUnderline} - > - {t('view_update')} - - - - ) : null -} diff --git a/app/src/organisms/UpdateRobotSoftware/CheckUpdates.tsx b/app/src/organisms/UpdateRobotSoftware/CheckUpdates.tsx index e23ff5417f6..9df29bc6280 100644 --- a/app/src/organisms/UpdateRobotSoftware/CheckUpdates.tsx +++ b/app/src/organisms/UpdateRobotSoftware/CheckUpdates.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { diff --git a/app/src/organisms/UpdateRobotSoftware/CompleteUpdateSoftware.tsx b/app/src/organisms/UpdateRobotSoftware/CompleteUpdateSoftware.tsx index d35891880a5..9f29911ac88 100644 --- a/app/src/organisms/UpdateRobotSoftware/CompleteUpdateSoftware.tsx +++ b/app/src/organisms/UpdateRobotSoftware/CompleteUpdateSoftware.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -14,7 +13,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { ProgressBar } from '../../atoms/ProgressBar' +import { ProgressBar } from '/app/atoms/ProgressBar' interface CompleteUpdateSoftwareProps { robotName: string diff --git a/app/src/organisms/UpdateRobotSoftware/ErrorUpdateSoftware.tsx b/app/src/organisms/UpdateRobotSoftware/ErrorUpdateSoftware.tsx index 22324c25b95..f1629e15b64 100644 --- a/app/src/organisms/UpdateRobotSoftware/ErrorUpdateSoftware.tsx +++ b/app/src/organisms/UpdateRobotSoftware/ErrorUpdateSoftware.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import type * as React from 'react' import { useTranslation } from 'react-i18next' import { diff --git a/app/src/organisms/UpdateRobotSoftware/NoUpdateFound.tsx b/app/src/organisms/UpdateRobotSoftware/NoUpdateFound.tsx index 2f173cf0eda..27b466f06d9 100644 --- a/app/src/organisms/UpdateRobotSoftware/NoUpdateFound.tsx +++ b/app/src/organisms/UpdateRobotSoftware/NoUpdateFound.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { @@ -14,7 +13,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { MediumButton } from '../../atoms/buttons' +import { MediumButton } from '/app/atoms/buttons' export interface NoUpdateFoundProps { onContinue: () => void diff --git a/app/src/organisms/UpdateRobotSoftware/UpdateSoftware.tsx b/app/src/organisms/UpdateRobotSoftware/UpdateSoftware.tsx index 67c0cd1a3bc..9c499d8a4f3 100644 --- a/app/src/organisms/UpdateRobotSoftware/UpdateSoftware.tsx +++ b/app/src/organisms/UpdateRobotSoftware/UpdateSoftware.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { diff --git a/app/src/organisms/UpdateRobotSoftware/__tests__/CheckUpdates.test.tsx b/app/src/organisms/UpdateRobotSoftware/__tests__/CheckUpdates.test.tsx index a9611b8b456..d56c7305a0d 100644 --- a/app/src/organisms/UpdateRobotSoftware/__tests__/CheckUpdates.test.tsx +++ b/app/src/organisms/UpdateRobotSoftware/__tests__/CheckUpdates.test.tsx @@ -1,8 +1,7 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { describe, it } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { CheckUpdates } from '../CheckUpdates' const render = () => diff --git a/app/src/organisms/UpdateRobotSoftware/__tests__/CompleteUpdateSoftware.test.tsx b/app/src/organisms/UpdateRobotSoftware/__tests__/CompleteUpdateSoftware.test.tsx index f06f48a5f85..b6b91424b92 100644 --- a/app/src/organisms/UpdateRobotSoftware/__tests__/CompleteUpdateSoftware.test.tsx +++ b/app/src/organisms/UpdateRobotSoftware/__tests__/CompleteUpdateSoftware.test.tsx @@ -1,12 +1,12 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { i18n } from '../../../i18n' -import { renderWithProviders } from '../../../__testing-utils__' +import { i18n } from '/app/i18n' +import { renderWithProviders } from '/app/__testing-utils__' import { CompleteUpdateSoftware } from '../CompleteUpdateSoftware' -vi.mock('../../../redux/robot-admin') +vi.mock('/app/redux/robot-admin') const render = (props: React.ComponentProps) => { return renderWithProviders(, { diff --git a/app/src/organisms/UpdateRobotSoftware/__tests__/ErrorUpdateSoftware.test.tsx b/app/src/organisms/UpdateRobotSoftware/__tests__/ErrorUpdateSoftware.test.tsx index bc5f690a1d7..d1706a4bf18 100644 --- a/app/src/organisms/UpdateRobotSoftware/__tests__/ErrorUpdateSoftware.test.tsx +++ b/app/src/organisms/UpdateRobotSoftware/__tests__/ErrorUpdateSoftware.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { ErrorUpdateSoftware } from '../ErrorUpdateSoftware' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/UpdateRobotSoftware/__tests__/NoUpdateFound.test.tsx b/app/src/organisms/UpdateRobotSoftware/__tests__/NoUpdateFound.test.tsx index 66a0daab9b0..2f276dc2f20 100644 --- a/app/src/organisms/UpdateRobotSoftware/__tests__/NoUpdateFound.test.tsx +++ b/app/src/organisms/UpdateRobotSoftware/__tests__/NoUpdateFound.test.tsx @@ -1,9 +1,8 @@ -import * as React from 'react' import { fireEvent, screen } from '@testing-library/react' import { describe, it, vi, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { NoUpdateFound } from '../NoUpdateFound' diff --git a/app/src/organisms/UpdateRobotSoftware/__tests__/UpdateRobotSoftware.test.tsx b/app/src/organisms/UpdateRobotSoftware/__tests__/UpdateRobotSoftware.test.tsx index 5db3c1358eb..9df863fe5ce 100644 --- a/app/src/organisms/UpdateRobotSoftware/__tests__/UpdateRobotSoftware.test.tsx +++ b/app/src/organisms/UpdateRobotSoftware/__tests__/UpdateRobotSoftware.test.tsx @@ -1,26 +1,25 @@ -import * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, vi, beforeEach, expect } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' -import * as RobotUpdate from '../../../redux/robot-update' +import * as RobotUpdate from '/app/redux/robot-update' import * as UpdateRobotSoftware from '../' import { CompleteUpdateSoftware, UpdateSoftware, -} from '../../../organisms/UpdateRobotSoftware' +} from '/app/organisms/UpdateRobotSoftware' -import type { State } from '../../../redux/types' +import type { State } from '/app/redux/types' -vi.mock('../../../redux/discovery') -vi.mock('../../../redux/robot-update') -vi.mock('../../../organisms/UpdateRobotSoftware/CheckUpdates') -vi.mock('../../../organisms/UpdateRobotSoftware/CompleteUpdateSoftware') -vi.mock('../../../organisms/UpdateRobotSoftware/ErrorUpdateSoftware') -vi.mock('../../../organisms/UpdateRobotSoftware/NoUpdateFound') -vi.mock('../../../organisms/UpdateRobotSoftware/UpdateSoftware') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/robot-update') +vi.mock('/app/organisms/UpdateRobotSoftware/CheckUpdates') +vi.mock('/app/organisms/UpdateRobotSoftware/CompleteUpdateSoftware') +vi.mock('/app/organisms/UpdateRobotSoftware/ErrorUpdateSoftware') +vi.mock('/app/organisms/UpdateRobotSoftware/NoUpdateFound') +vi.mock('/app/organisms/UpdateRobotSoftware/UpdateSoftware') const getRobotUpdateSession = RobotUpdate.getRobotUpdateSession diff --git a/app/src/organisms/UpdateRobotSoftware/__tests__/UpdateSoftware.test.tsx b/app/src/organisms/UpdateRobotSoftware/__tests__/UpdateSoftware.test.tsx index 680de1b0147..93deeb27956 100644 --- a/app/src/organisms/UpdateRobotSoftware/__tests__/UpdateSoftware.test.tsx +++ b/app/src/organisms/UpdateRobotSoftware/__tests__/UpdateSoftware.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { screen } from '@testing-library/react' import { describe, it, beforeEach } from 'vitest' import '@testing-library/jest-dom/vitest' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { UpdateSoftware } from '../UpdateSoftware' const render = (props: React.ComponentProps) => { diff --git a/app/src/organisms/UpdateRobotSoftware/index.tsx b/app/src/organisms/UpdateRobotSoftware/index.tsx index 4d61272ac6f..785494410d4 100644 --- a/app/src/organisms/UpdateRobotSoftware/index.tsx +++ b/app/src/organisms/UpdateRobotSoftware/index.tsx @@ -1,16 +1,16 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { useDispatch, useSelector } from 'react-redux' -import type { Dispatch } from '../../redux/types' +import type { Dispatch } from '/app/redux/types' import { getRobotUpdateSession, startRobotUpdate, -} from '../../redux/robot-update' +} from '/app/redux/robot-update' -import type { ViewableRobot } from '../../redux/discovery/types' +import type { ViewableRobot } from '/app/redux/discovery/types' -import { CompleteUpdateSoftware } from '../../organisms/UpdateRobotSoftware/CompleteUpdateSoftware' -import { UpdateSoftware } from '../../organisms/UpdateRobotSoftware/UpdateSoftware' +import { CompleteUpdateSoftware } from '/app/organisms/UpdateRobotSoftware/CompleteUpdateSoftware' +import { UpdateSoftware } from '/app/organisms/UpdateRobotSoftware/UpdateSoftware' import { CheckUpdates } from './CheckUpdates' import { NoUpdateFound } from './NoUpdateFound' @@ -41,9 +41,9 @@ export function UpdateRobotSoftware( step: null, error: null, } - const [isDownloading, setIsDownloading] = React.useState(false) + const [isDownloading, setIsDownloading] = useState(false) - React.useEffect(() => { + useEffect(() => { // check isDownloading to avoid dispatching again if (!isDownloading) { setIsDownloading(true) diff --git a/app/src/organisms/WellSelection/Selection384Wells.tsx b/app/src/organisms/WellSelection/Selection384Wells.tsx index 5db84c16cd9..94e2eaf9e74 100644 --- a/app/src/organisms/WellSelection/Selection384Wells.tsx +++ b/app/src/organisms/WellSelection/Selection384Wells.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import flatten from 'lodash/flatten' @@ -13,8 +13,9 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { IconButton } from '../../atoms/buttons/IconButton' +import { IconButton } from '/app/atoms/buttons/IconButton' +import type { Dispatch, SetStateAction, ReactNode } from 'react' import type { WellGroup } from '@opentrons/components' import type { LabwareDefinition2, @@ -26,7 +27,7 @@ interface Selection384WellsProps { channels: PipetteChannels definition: LabwareDefinition2 deselectWells: (wells: string[]) => void - labwareRender: React.ReactNode + labwareRender: ReactNode selectWells: (wellGroup: WellGroup) => unknown } @@ -43,18 +44,18 @@ export function Selection384Wells({ labwareRender, selectWells, }: Selection384WellsProps): JSX.Element { - const [selectBy, setSelectBy] = React.useState<'columns' | 'wells'>('columns') + const [selectBy, setSelectBy] = useState<'columns' | 'wells'>('columns') - const [lastSelectedIndex, setLastSelectedIndex] = React.useState< - number | null - >(null) + const [lastSelectedIndex, setLastSelectedIndex] = useState( + null + ) - const [startingWellState, setStartingWellState] = React.useState< + const [startingWellState, setStartingWellState] = useState< Record >({ A1: false, A2: false, B1: false, B2: false }) // to reset last selected index and starting well state on page-level selected well reset - React.useEffect(() => { + useEffect(() => { if (Object.keys(allSelectedWells).length === 0) { setLastSelectedIndex(null) if (channels === 96) { @@ -180,8 +181,8 @@ export function Selection384Wells({ interface SelectByProps { selectBy: 'columns' | 'wells' - setSelectBy: React.Dispatch> - setLastSelectedIndex: React.Dispatch> + setSelectBy: Dispatch> + setLastSelectedIndex: Dispatch> } function SelectBy({ @@ -244,8 +245,8 @@ function StartingWell({ deselectWells: (wells: string[]) => void selectWells: (wellGroup: WellGroup) => void startingWellState: Record - setStartingWellState: React.Dispatch< - React.SetStateAction> + setStartingWellState: Dispatch< + SetStateAction> > wells: string[] }): JSX.Element { @@ -255,7 +256,7 @@ function StartingWell({ channels === 8 ? ['A1', 'B1'] : ['A1', 'A2', 'B1', 'B2'] // on mount, select A1 well group for 96-channel - React.useEffect(() => { + useEffect(() => { // deselect all wells on mount; clears well selection when navigating back within quick transfer flow // otherwise, selected wells and lastSelectedIndex pointer will be out of sync deselectWells(wells) diff --git a/app/src/organisms/WellSelection/SelectionRect.tsx b/app/src/organisms/WellSelection/SelectionRect.tsx index 7c9d1ac0357..6d241d5f0c0 100644 --- a/app/src/organisms/WellSelection/SelectionRect.tsx +++ b/app/src/organisms/WellSelection/SelectionRect.tsx @@ -1,19 +1,20 @@ -import * as React from 'react' +import { useState, useRef, useCallback, useEffect } from 'react' import { Flex, JUSTIFY_CENTER } from '@opentrons/components' +import type { MouseEventHandler, ReactNode, TouchEventHandler } from 'react' import type { DragRect, GenericRect } from './types' interface SelectionRectProps { onSelectionMove?: (rect: GenericRect) => void onSelectionDone?: (rect: GenericRect) => void - children?: React.ReactNode + children?: ReactNode } export function SelectionRect(props: SelectionRectProps): JSX.Element { const { onSelectionMove, onSelectionDone, children } = props - const [positions, setPositions] = React.useState(null) - const parentRef = React.useRef(null) + const [positions, setPositions] = useState(null) + const parentRef = useRef(null) const getRect = (args: DragRect): GenericRect => { const { xStart, yStart, xDynamic, yDynamic } = args @@ -25,7 +26,7 @@ export function SelectionRect(props: SelectionRectProps): JSX.Element { } } - const handleDrag = React.useCallback( + const handleDrag = useCallback( (e: TouchEvent | MouseEvent): void => { let xDynamic: number let yDynamic: number @@ -55,7 +56,7 @@ export function SelectionRect(props: SelectionRectProps): JSX.Element { [onSelectionMove] ) - const handleDragEnd = React.useCallback( + const handleDragEnd = useCallback( (e: TouchEvent | MouseEvent): void => { if (!(e instanceof TouchEvent) && !(e instanceof MouseEvent)) { return @@ -70,7 +71,7 @@ export function SelectionRect(props: SelectionRectProps): JSX.Element { [onSelectionDone, positions] ) - const handleTouchStart: React.TouchEventHandler = e => { + const handleTouchStart: TouchEventHandler = e => { const touch = e.touches[0] setPositions({ xStart: touch.clientX, @@ -80,7 +81,7 @@ export function SelectionRect(props: SelectionRectProps): JSX.Element { }) } - const handleMouseDown: React.MouseEventHandler = e => { + const handleMouseDown: MouseEventHandler = e => { setPositions({ xStart: e.clientX, xDynamic: e.clientX, @@ -89,7 +90,7 @@ export function SelectionRect(props: SelectionRectProps): JSX.Element { }) } - React.useEffect(() => { + useEffect(() => { document.addEventListener('touchmove', handleDrag) document.addEventListener('touchend', handleDragEnd) document.addEventListener('mousemove', handleDrag) diff --git a/app/src/organisms/WellSelection/index.tsx b/app/src/organisms/WellSelection/index.tsx index 19cd9e04363..06daf9536a5 100644 --- a/app/src/organisms/WellSelection/index.tsx +++ b/app/src/organisms/WellSelection/index.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import reduce from 'lodash/reduce' import { COLORS, Labware, RobotCoordinateSpace } from '@opentrons/components' @@ -14,15 +14,21 @@ import type { WellFill, WellGroup, WellStroke } from '@opentrons/components' import type { LabwareDefinition2, PipetteChannels, + NozzleLayoutDetails, } from '@opentrons/shared-data' import type { GenericRect } from './types' interface WellSelectionProps { definition: LabwareDefinition2 deselectWells: (wells: string[]) => void + /* The actual wells that are clicked. */ selectedPrimaryWells: WellGroup selectWells: (wellGroup: WellGroup) => unknown channels: PipetteChannels + /* Highlight only valid wells given the current pipette nozzle configuration. */ + pipetteNozzleDetails?: NozzleLayoutDetails + /* Whether highlighting and selectWells() updates are permitted. */ + allowSelect?: boolean } export function WellSelection(props: WellSelectionProps): JSX.Element { @@ -32,8 +38,10 @@ export function WellSelection(props: WellSelectionProps): JSX.Element { selectedPrimaryWells, selectWells, channels, + pipetteNozzleDetails, + allowSelect = true, } = props - const [highlightedWells, setHighlightedWells] = React.useState({}) + const [highlightedWells, setHighlightedWells] = useState({}) const _wellsFromSelected: ( selectedWells: WellGroup @@ -45,12 +53,15 @@ export function WellSelection(props: WellSelectionProps): JSX.Element { const primaryWells: WellGroup = reduce( selectedWells, (acc: WellGroup, _, wellName: string): WellGroup => { - const wellSet = getWellSetForMultichannel( - definition, + const wellSet = getWellSetForMultichannel({ + labwareDef: definition, wellName, - channels - ) - if (!wellSet) return acc + channels, + pipetteNozzleDetails, + }) + if (!wellSet) { + return acc + } return { ...acc, [wellSet[0]]: null } }, {} @@ -68,46 +79,58 @@ export function WellSelection(props: WellSelectionProps): JSX.Element { } const handleSelectionMove: (rect: GenericRect) => void = rect => { - if (channels === 8 || channels === 96) { - const selectedWells = _getWellsFromRect(rect) - const allWellsForMulti: WellGroup = reduce( - selectedWells, - (acc: WellGroup, _, wellName: string): WellGroup => { - const wellSetForMulti = - getWellSetForMultichannel(definition, wellName, channels) || [] - const channelWells = arrayToWellGroup(wellSetForMulti) - return { - ...acc, - ...channelWells, - } - }, - {} - ) - setHighlightedWells(allWellsForMulti) - } else { - setHighlightedWells(_getWellsFromRect(rect)) + if (allowSelect) { + if (channels === 8 || channels === 96) { + const selectedWells = _getWellsFromRect(rect) + const allWellsForMulti: WellGroup = reduce( + selectedWells, + (acc: WellGroup, _, wellName: string): WellGroup => { + const wellSetForMulti = + getWellSetForMultichannel({ + labwareDef: definition, + wellName, + channels, + pipetteNozzleDetails, + }) || [] + const channelWells = arrayToWellGroup(wellSetForMulti) + return { + ...acc, + ...channelWells, + } + }, + {} + ) + setHighlightedWells(allWellsForMulti) + } else { + setHighlightedWells(_getWellsFromRect(rect)) + } } } const handleSelectionDone: (rect: GenericRect) => void = rect => { const wells = _wellsFromSelected(_getWellsFromRect(rect)) - selectWells(wells) - setHighlightedWells({}) + if (allowSelect) { + selectWells(wells) + setHighlightedWells({}) + } } - // For rendering, show all wells not just primary wells + // For rendering, show all valid wells, not just primary wells const allSelectedWells = channels === 8 || channels === 96 ? reduce( selectedPrimaryWells, (acc, _, wellName): WellGroup => { - const wellSet = getWellSetForMultichannel( - definition, + const wellSet = getWellSetForMultichannel({ + labwareDef: definition, wellName, - channels - ) - if (!wellSet) return acc + channels, + pipetteNozzleDetails, + }) + if (!wellSet) { + return acc + } return { ...acc, ...arrayToWellGroup(wellSet) } }, {} diff --git a/app/src/pages/AppSettings/__test__/GeneralSettings.test.tsx b/app/src/pages/AppSettings/__test__/GeneralSettings.test.tsx deleted file mode 100644 index 7a3d7196858..00000000000 --- a/app/src/pages/AppSettings/__test__/GeneralSettings.test.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import * as React from 'react' -import { MemoryRouter } from 'react-router-dom' -import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' -import { screen } from '@testing-library/react' - -import { renderWithProviders } from '../../../__testing-utils__' - -import { i18n } from '../../../i18n' -import { getAlertIsPermanentlyIgnored } from '../../../redux/alerts' -import * as Shell from '../../../redux/shell' -import { GeneralSettings } from '../GeneralSettings' - -vi.mock('../../../redux/config') -vi.mock('../../../redux/shell') -vi.mock('../../../redux/analytics') -vi.mock('../../../redux/alerts') - -const render = (): ReturnType => { - return renderWithProviders( - - - , - { - i18nInstance: i18n, - } - ) -} - -describe('GeneralSettings', () => { - beforeEach(() => { - vi.mocked(Shell.getAvailableShellUpdate).mockReturnValue(null) - vi.mocked(getAlertIsPermanentlyIgnored).mockReturnValue(false) - }) - afterEach(() => { - vi.resetAllMocks() - }) - - it('renders correct titles', () => { - const [{ getByText }] = render() - getByText('App Software Version') - getByText('Software Update Alerts') - getByText('Connect to a Robot via IP Address') - }) - - it('renders software version section with no update available', () => { - const [{ getByText, getByRole }] = render() - getByText('Up to date') - getByText('View latest release notes on') - expect(getByRole('link', { name: 'GitHub' })).toHaveAttribute( - 'href', - 'https://github.com/Opentrons/opentrons/blob/edge/app-shell/build/release-notes.md' - ) - getByRole('button', { - name: 'See how to restore a previous software version', - }) - expect( - 'It is very important for the robot and app software to be on the same version. Manage the robot software versions via Robot Settings > Advanced.' - ).toBeTruthy() - expect( - getByRole('link', { - name: - 'Learn more about keeping the Opentrons App and robot software in sync', - }) - ).toHaveAttribute('href', 'https://support.opentrons.com/s/') - }) - - it('renders correct info if there is update available', () => { - vi.mocked(Shell.getAvailableShellUpdate).mockReturnValue('5.0.0-beta.8') - const [{ getByRole }] = render() - getByRole('button', { name: 'View software update' }) - }) - - it('renders correct info if there is no update available', () => { - expect(screen.queryByText('View software update')).toBeNull() - }) - - it('renders correct info if there is update available but alert ignored enabled', () => { - vi.mocked(Shell.getAvailableShellUpdate).mockReturnValue('5.0.0-beta.8') - vi.mocked(getAlertIsPermanentlyIgnored).mockReturnValue(true) - expect(screen.queryByText('View software update')).toBeNull() - }) - - it('renders the text and toggle for update alert section', () => { - const [{ getByText, getByRole }] = render() - getByText( - 'Receive an alert when an Opentrons software update is available.' - ) - getByRole('switch', { - name: 'Enable app update notifications', - }) - }) - - it('renders the ip address button', () => { - const [{ getByRole }] = render() - getByRole('button', { name: 'Set up connection' }) - }) -}) diff --git a/app/src/pages/AppSettings/index.tsx b/app/src/pages/AppSettings/index.tsx deleted file mode 100644 index 61b5f619c96..00000000000 --- a/app/src/pages/AppSettings/index.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import { Navigate, useParams } from 'react-router-dom' - -import { - ALIGN_START, - BORDERS, - Box, - COLORS, - DIRECTION_ROW, - Flex, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' - -import * as Config from '../../redux/config' -import { GeneralSettings } from './GeneralSettings' -import { PrivacySettings } from './PrivacySettings' -import { AdvancedSettings } from './AdvancedSettings' -import { FeatureFlags } from '../../organisms/AppSettings/FeatureFlags' -import { NavTab } from '../../molecules/NavTab' -import { Line } from '../../atoms/structure' - -import type { DesktopRouteParams, AppSettingsTab } from '../../App/types' - -export function AppSettings(): JSX.Element { - const { t } = useTranslation('app_settings') - const devToolsOn = useSelector(Config.getDevtoolsEnabled) - const { appSettingsTab } = useParams< - keyof DesktopRouteParams - >() as DesktopRouteParams - - const appSettingsContentByTab: { - [K in AppSettingsTab]: JSX.Element - } = { - general: , - privacy: , - advanced: , - 'feature-flags': , - } - - const appSettingsContent = appSettingsContentByTab[appSettingsTab] ?? ( - // default to the general tab if no tab or nonexistent tab is passed as a param - - ) - - return ( - - - - - {t('app_settings')} - - - - - - {devToolsOn && ( - - )} - - - - {appSettingsContent} - - - ) -} diff --git a/app/src/pages/ConnectViaEthernet/__tests__/ConnectViaEthernet.test.tsx b/app/src/pages/ConnectViaEthernet/__tests__/ConnectViaEthernet.test.tsx deleted file mode 100644 index 404f0c9f95c..00000000000 --- a/app/src/pages/ConnectViaEthernet/__tests__/ConnectViaEthernet.test.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import * as React from 'react' -import { MemoryRouter } from 'react-router-dom' -import { vi, it, describe, beforeEach, afterEach } from 'vitest' -import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' - -import { i18n } from '../../../i18n' -import * as Networking from '../../../redux/networking' -import { TitleHeader } from '../../../pages/ConnectViaEthernet/TitleHeader' -import { DisplayConnectionStatus } from '../../../pages/ConnectViaEthernet/DisplayConnectionStatus' -import { ConnectViaEthernet } from '../../../pages/ConnectViaEthernet' - -vi.mock('../../../redux/networking') -vi.mock('../../../redux/discovery') -vi.mock('../TitleHeader') -vi.mock('../DisplayConnectionStatus') - -const initialMockEthernet = { - ipAddress: '127.0.0.101', - subnetMask: '255.255.255.231', - macAddress: 'ET:NT:00:00:00:00', - type: Networking.INTERFACE_ETHERNET, -} - -const render = () => { - return renderWithProviders( - - - , - { - i18nInstance: i18n, - } - ) -} - -describe('ConnectViaEthernet', () => { - beforeEach(() => { - vi.mocked(Networking.getNetworkInterfaces).mockReturnValue({ - wifi: null, - ethernet: initialMockEthernet, - }) - - vi.mocked(TitleHeader).mockReturnValue(
    mock TitleHeader
    ) - vi.mocked(DisplayConnectionStatus).mockReturnValue( -
    mock DisplayConnectionStatus
    - ) - }) - - afterEach(() => { - vi.resetAllMocks() - }) - - it('should render TitleHeader component and DisplayConnectionStatus component', () => { - render() - screen.getByText('mock TitleHeader') - screen.getByText('mock DisplayConnectionStatus') - }) -}) diff --git a/app/src/pages/ConnectViaEthernet/index.tsx b/app/src/pages/ConnectViaEthernet/index.tsx deleted file mode 100644 index f7f386074d9..00000000000 --- a/app/src/pages/ConnectViaEthernet/index.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector, useDispatch } from 'react-redux' - -import { - Flex, - SPACING, - useInterval, - DIRECTION_COLUMN, -} from '@opentrons/components' - -import { StepMeter } from '../../atoms/StepMeter' -import { NetworkDetailsModal } from '../../organisms/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal' -import { getNetworkInterfaces, fetchStatus } from '../../redux/networking' -import { getLocalRobot } from '../../redux/discovery' -import { TitleHeader } from '../../pages/ConnectViaEthernet/TitleHeader' -import { DisplayConnectionStatus } from '../../pages/ConnectViaEthernet/DisplayConnectionStatus' - -import type { State, Dispatch } from '../../redux/types' - -const STATUS_REFRESH_MS = 5000 - -export function ConnectViaEthernet(): JSX.Element { - const { t } = useTranslation('device_settings') - const localRobot = useSelector(getLocalRobot) - const robotName = localRobot?.name != null ? localRobot.name : 'no name' - const dispatch = useDispatch() - const [ - showNetworkDetailsModal, - setShowNetworkDetailsModal, - ] = React.useState(false) - - const { ethernet } = useSelector((state: State) => - getNetworkInterfaces(state, robotName) - ) - const ipAddress = - ethernet?.ipAddress != null ? ethernet.ipAddress : t('shared:no_data') - const subnetMask = - ethernet?.subnetMask != null ? ethernet.subnetMask : t('shared:no_data') - const macAddress = - ethernet?.macAddress != null ? ethernet.macAddress : t('shared:no_data') - const headerTitle = t('ethernet') - const isConnected = - ipAddress !== t('shared:no_data') && subnetMask !== t('shared:no_data') - - useInterval(() => dispatch(fetchStatus(robotName)), STATUS_REFRESH_MS, true) - - return ( - <> - - - - {showNetworkDetailsModal ? ( - - ) : null} - - - - ) -} diff --git a/app/src/pages/ConnectViaUSB/index.tsx b/app/src/pages/ConnectViaUSB/index.tsx deleted file mode 100644 index a802d36d891..00000000000 --- a/app/src/pages/ConnectViaUSB/index.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useNavigate } from 'react-router-dom' -import { - ALIGN_CENTER, - BORDERS, - Btn, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - Icon, - JUSTIFY_CENTER, - POSITION_ABSOLUTE, - POSITION_RELATIVE, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' -import { useConnectionsQuery } from '@opentrons/react-api-client' -import { StepMeter } from '../../atoms/StepMeter' -import { MediumButton } from '../../atoms/buttons' - -export function ConnectViaUSB(): JSX.Element { - const { i18n, t } = useTranslation(['device_settings', 'shared', 'branded']) - const navigate = useNavigate() - // TODO(bh, 2023-5-31): active connections from /system/connected isn't exactly the right way to monitor for a usb connection - - // the system-server tracks active connections by authorization token, which is valid for 2 hours - // another option is to report an active usb connection by monitoring usb port traffic (discovery-client polls health from the desktop app) - const activeConnections = useConnectionsQuery().data?.connections ?? [] - const isConnected = activeConnections.some( - connection => connection.agent === 'com.opentrons.app.usb' - ) - - return ( - <> - - - - { - navigate('/network-setup') - }} - position={POSITION_ABSOLUTE} - > - - - - - - - {t('usb')} - - - - {isConnected ? ( - - - - - - {t('successfully_connected')} - - - {t('branded:find_your_robot')} - - - - { - navigate('/emergency-stop') - }} - /> - - ) : ( - - - - - {t('no_connection_found')} - - - - {t('connect_via_usb_description_1')} - - - {t('connect_via_usb_description_2')} - - - {t('branded:connect_via_usb_description_3')} - - - - - )} - - - ) -} diff --git a/app/src/pages/ConnectViaWifi/JoinOtherNetwork.tsx b/app/src/pages/ConnectViaWifi/JoinOtherNetwork.tsx deleted file mode 100644 index 72a7315678c..00000000000 --- a/app/src/pages/ConnectViaWifi/JoinOtherNetwork.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' - -import { Flex, DIRECTION_COLUMN } from '@opentrons/components' - -import { SetWifiSsid } from '../../organisms/NetworkSettings' -import { RobotSetupHeader } from '../../organisms/RobotSetupHeader' - -import type { WifiScreenOption } from '../../pages/ConnectViaWifi' - -interface JoinOtherNetworkProps { - setCurrentOption: (option: WifiScreenOption) => void - setSelectedSsid: React.Dispatch> -} - -export function JoinOtherNetwork({ - setCurrentOption, - setSelectedSsid, -}: JoinOtherNetworkProps): JSX.Element { - const { i18n, t } = useTranslation('device_settings') - - const [inputSsid, setInputSsid] = React.useState('') - const [errorMessage, setErrorMessage] = React.useState(null) - - const handleContinue = (): void => { - if (inputSsid.length >= 2 && inputSsid.length <= 32) { - setSelectedSsid(inputSsid) - setCurrentOption('SelectAuthType') - } else { - setErrorMessage(t('join_other_network_error_message') as string) - } - } - - return ( - - { - setCurrentOption('WifiList') - }} - onClickButton={handleContinue} - /> - - - ) -} diff --git a/app/src/pages/ConnectViaWifi/SelectAuthenticationType.tsx b/app/src/pages/ConnectViaWifi/SelectAuthenticationType.tsx deleted file mode 100644 index 564b272d75d..00000000000 --- a/app/src/pages/ConnectViaWifi/SelectAuthenticationType.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' - -import { Flex, DIRECTION_COLUMN } from '@opentrons/components' - -import { SelectAuthenticationType as SelectAuthenticationTypeComponent } from '../../organisms/NetworkSettings' -import { RobotSetupHeader } from '../../organisms/RobotSetupHeader' - -import type { WifiSecurityType } from '@opentrons/api-client' -import type { WifiScreenOption } from '../../pages/ConnectViaWifi' - -interface SelectAuthenticationTypeProps { - handleWifiConnect: () => void - selectedAuthType: WifiSecurityType - setCurrentOption: (option: WifiScreenOption) => void - setSelectedAuthType: (authType: WifiSecurityType) => void -} - -export function SelectAuthenticationType({ - handleWifiConnect, - selectedAuthType, - setCurrentOption, - setSelectedAuthType, -}: SelectAuthenticationTypeProps): JSX.Element { - const { i18n, t } = useTranslation('device_settings') - - return ( - - { - setCurrentOption('WifiList') - }} - onClickButton={() => { - selectedAuthType !== 'none' - ? setCurrentOption('SetWifiCred') - : handleWifiConnect() - }} - /> - - - ) -} diff --git a/app/src/pages/ConnectViaWifi/SetWifiCred.tsx b/app/src/pages/ConnectViaWifi/SetWifiCred.tsx deleted file mode 100644 index ebb0964b484..00000000000 --- a/app/src/pages/ConnectViaWifi/SetWifiCred.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' - -import { Flex, DIRECTION_COLUMN } from '@opentrons/components' - -import { SetWifiCred as SetWifiCredComponent } from '../../organisms/NetworkSettings' -import { RobotSetupHeader } from '../../organisms/RobotSetupHeader' - -import type { WifiScreenOption } from '../../pages/ConnectViaWifi' - -interface SetWifiCredProps { - handleConnect: () => void - password: string - setCurrentOption: (option: WifiScreenOption) => void - setPassword: React.Dispatch> -} - -export function SetWifiCred({ - handleConnect, - password, - setCurrentOption, - setPassword, -}: SetWifiCredProps): JSX.Element { - const { t } = useTranslation('device_settings') - - return ( - - { - setCurrentOption('SelectAuthType') - }} - onClickButton={handleConnect} - /> - - - ) -} diff --git a/app/src/pages/ConnectViaWifi/index.tsx b/app/src/pages/ConnectViaWifi/index.tsx deleted file mode 100644 index 4530503a69c..00000000000 --- a/app/src/pages/ConnectViaWifi/index.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import * as React from 'react' -import { useSelector } from 'react-redux' -import last from 'lodash/last' - -import { Flex, DIRECTION_COLUMN, SPACING } from '@opentrons/components' - -import { StepMeter } from '../../atoms/StepMeter' -import { DisplayWifiList } from '../../organisms/NetworkSettings' -import * as Networking from '../../redux/networking' -import { getLocalRobot } from '../../redux/discovery' -import * as RobotApi from '../../redux/robot-api' -import { useWifiList } from '../../resources/networking/hooks' -import { JoinOtherNetwork } from '../../pages/ConnectViaWifi/JoinOtherNetwork' -import { SelectAuthenticationType } from '../../pages/ConnectViaWifi/SelectAuthenticationType' -import { SetWifiCred } from '../../pages/ConnectViaWifi/SetWifiCred' -import { WifiConnectStatus } from '../../pages/ConnectViaWifi/WifiConnectStatus' - -import type { WifiSecurityType } from '@opentrons/api-client' -import type { State } from '../../redux/types' - -const WIFI_LIST_POLL_MS = 5000 -export type WifiScreenOption = - | 'WifiList' - | 'JoinOtherNetwork' - | 'SelectAuthType' - | 'SetWifiCred' - | 'WifiConnectStatus' - -export function ConnectViaWifi(): JSX.Element { - const [selectedSsid, setSelectedSsid] = React.useState('') - const [ - selectedAuthType, - setSelectedAuthType, - ] = React.useState('wpa-psk') - - const [currentOption, setCurrentOption] = React.useState( - 'WifiList' - ) - const [password, setPassword] = React.useState('') - const localRobot = useSelector(getLocalRobot) - const robotName = localRobot?.name != null ? localRobot.name : 'no name' - const list = useWifiList(robotName, WIFI_LIST_POLL_MS) - const [dispatchApiRequest, requestIds] = RobotApi.useDispatchApiRequest() - const requestState = useSelector((state: State) => { - const lastId = last(requestIds) - return lastId != null ? RobotApi.getRequestById(state, lastId) : null - }) - - const handleConnect = (): void => { - const options = { - ssid: selectedSsid, - securityType: selectedAuthType, - hidden: false, - psk: password, - } - dispatchApiRequest(Networking.postWifiConfigure(robotName, options)) - setCurrentOption('WifiConnectStatus') - setPassword('') - } - - let currentScreen: JSX.Element | null = null - if (currentOption === 'WifiConnectStatus') { - currentScreen = ( - - ) - } else if (currentOption === 'WifiList') { - currentScreen = ( - { - setCurrentOption('JoinOtherNetwork') - }} - handleNetworkPress={(ssid: string) => { - setSelectedSsid(ssid) - setCurrentOption('SelectAuthType') - }} - isHeader - /> - ) - } else if (currentOption === 'JoinOtherNetwork') { - currentScreen = ( - - ) - } else if (currentOption === 'SelectAuthType') { - currentScreen = ( - - ) - } else if (currentOption === 'SetWifiCred') { - currentScreen = ( - - ) - } - - return ( - <> - - - {currentScreen} - - - ) -} diff --git a/app/src/pages/DeckConfiguration/index.tsx b/app/src/pages/DeckConfiguration/index.tsx deleted file mode 100644 index ef27e81bc0a..00000000000 --- a/app/src/pages/DeckConfiguration/index.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useTranslation } from 'react-i18next' -import { useNavigate } from 'react-router-dom' - -import { - DeckConfigurator, - DIRECTION_COLUMN, - Flex, - JUSTIFY_CENTER, - JUSTIFY_SPACE_AROUND, -} from '@opentrons/components' - -import { ChildNavigation } from '../../organisms/ChildNavigation' -import { DeckFixtureSetupInstructionsModal } from '../../organisms/DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal' -import { DeckConfigurationDiscardChangesModal } from '../../organisms/DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal' -import { getTopPortalEl } from '../../App/portal' -import { - useDeckConfigurationEditingTools, - useNotifyDeckConfigurationQuery, -} from '../../resources/deck_configuration' - -import type { SmallButton } from '../../atoms/buttons' - -export function DeckConfigurationEditor(): JSX.Element { - const { t, i18n } = useTranslation([ - 'protocol_setup', - 'devices_landing', - 'shared', - ]) - const navigate = useNavigate() - const [ - showSetupInstructionsModal, - setShowSetupInstructionsModal, - ] = React.useState(false) - - const isOnDevice = true - const { - addFixtureToCutout, - removeFixtureFromCutout, - addFixtureModal, - } = useDeckConfigurationEditingTools(isOnDevice) - - const [ - showDiscardChangeModal, - setShowDiscardChangeModal, - ] = React.useState(false) - - const deckConfig = useNotifyDeckConfigurationQuery().data ?? [] - - const handleClickConfirm = (): void => { - navigate(-1) - } - - const secondaryButtonProps: React.ComponentProps = { - onClick: () => { - setShowSetupInstructionsModal(true) - }, - buttonText: i18n.format(t('setup_instructions'), 'titleCase'), - buttonType: 'tertiaryLowLight', - iconName: 'information', - iconPlacement: 'startIcon', - } - - return ( - <> - {createPortal( - <> - {showDiscardChangeModal ? ( - - ) : null} - {showSetupInstructionsModal ? ( - - ) : null} - {addFixtureModal} - , - getTopPortalEl() - )} - - - - - - - - ) -} diff --git a/app/src/pages/AppSettings/AdvancedSettings.tsx b/app/src/pages/Desktop/AppSettings/AdvancedSettings.tsx similarity index 91% rename from app/src/pages/AppSettings/AdvancedSettings.tsx rename to app/src/pages/Desktop/AppSettings/AdvancedSettings.tsx index cf921703aa5..e8f3724299b 100644 --- a/app/src/pages/AppSettings/AdvancedSettings.tsx +++ b/app/src/pages/Desktop/AppSettings/AdvancedSettings.tsx @@ -1,6 +1,6 @@ -import * as React from 'react' import { Box, SPACING } from '@opentrons/components' -import { Divider } from '../../atoms/structure' + +import { Divider } from '/app/atoms/structure' import { ClearUnavailableRobots, EnableDevTools, @@ -12,7 +12,7 @@ import { U2EInformation, UpdatedChannel, AdditionalCustomLabwareSourceFolder, -} from '../../organisms/AdvancedSettings' +} from '/app/organisms/Desktop/AdvancedSettings' export function AdvancedSettings(): JSX.Element { return ( diff --git a/app/src/pages/AppSettings/GeneralSettings.tsx b/app/src/pages/Desktop/AppSettings/GeneralSettings.tsx similarity index 76% rename from app/src/pages/AppSettings/GeneralSettings.tsx rename to app/src/pages/Desktop/AppSettings/GeneralSettings.tsx index 3dbff463245..db948403fd0 100644 --- a/app/src/pages/AppSettings/GeneralSettings.tsx +++ b/app/src/pages/Desktop/AppSettings/GeneralSettings.tsx @@ -1,5 +1,5 @@ // app info card with version and updated -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { useSelector, useDispatch } from 'react-redux' @@ -7,10 +7,12 @@ import { useSelector, useDispatch } from 'react-redux' import { ALIGN_CENTER, ALIGN_START, + Banner, Box, COLORS, DIRECTION_COLUMN, DIRECTION_ROW, + DropdownMenu, Flex, JUSTIFY_SPACE_BETWEEN, Link, @@ -21,31 +23,36 @@ import { useMountEffect, } from '@opentrons/components' -import { TertiaryButton, ToggleButton } from '../../atoms/buttons' -import { ExternalLink } from '../../atoms/Link/ExternalLink' -import { Divider } from '../../atoms/structure' -import { Banner } from '../../atoms/Banner' +import { TertiaryButton, ToggleButton } from '/app/atoms/buttons' +import { ExternalLink } from '/app/atoms/Link/ExternalLink' +import { Divider } from '/app/atoms/structure' +import { LANGUAGES } from '/app/i18n' import { CURRENT_VERSION, getAvailableShellUpdate, checkShellUpdate, -} from '../../redux/shell' +} from '/app/redux/shell' import { ALERT_APP_UPDATE_AVAILABLE, getAlertIsPermanentlyIgnored, alertPermanentlyIgnored, alertUnignored, -} from '../../redux/alerts' +} from '/app/redux/alerts' import { useTrackEvent, ANALYTICS_APP_UPDATE_NOTIFICATIONS_TOGGLED, -} from '../../redux/analytics' -import { UpdateAppModal } from '../../organisms/UpdateAppModal' -import { PreviousVersionModal } from '../../organisms/AppSettings/PreviousVersionModal' -import { ConnectRobotSlideout } from '../../organisms/AppSettings/ConnectRobotSlideout' -import { getTopPortalEl } from '../../App/portal' +} from '/app/redux/analytics' +import { + getAppLanguage, + updateConfigValue, + useFeatureFlag, +} from '/app/redux/config' +import { UpdateAppModal } from '/app/organisms/Desktop/UpdateAppModal' +import { PreviousVersionModal } from '/app/organisms/Desktop/AppSettings/PreviousVersionModal' +import { ConnectRobotSlideout } from '/app/organisms/Desktop/AppSettings/ConnectRobotSlideout' +import { getTopPortalEl } from '/app/App/portal' -import type { Dispatch, State } from '../../redux/types' +import type { Dispatch, State } from '/app/redux/types' const SOFTWARE_SYNC_URL = 'https://support.opentrons.com/s/' const GITHUB_LINK = @@ -60,22 +67,30 @@ export function GeneralSettings(): JSX.Element { const [ showPreviousVersionModal, setShowPreviousVersionModal, - ] = React.useState(false) + ] = useState(false) const updateAvailable = Boolean(useSelector(getAvailableShellUpdate)) - const [showUpdateBanner, setShowUpdateBanner] = React.useState( + + const enableLocalization = useFeatureFlag('enableLocalization') + const appLanguage = useSelector(getAppLanguage) + const currentLanguageOption = LANGUAGES.find(lng => lng.value === appLanguage) + + const handleDropdownClick = (value: string): void => { + dispatch(updateConfigValue('language.appLanguage', value)) + } + + const [showUpdateBanner, setShowUpdateBanner] = useState( updateAvailable ) - const [ - showConnectRobotSlideout, - setShowConnectRobotSlideout, - ] = React.useState(false) + const [showConnectRobotSlideout, setShowConnectRobotSlideout] = useState( + false + ) // may be enabled, disabled, or unknown (because config is loading) const updateAlertEnabled = useSelector((s: State) => { const ignored = getAlertIsPermanentlyIgnored(s, ALERT_APP_UPDATE_AVAILABLE) return ignored !== null ? !ignored : null }) - const [showUpdateModal, setShowUpdateModal] = React.useState(false) + const [showUpdateModal, setShowUpdateModal] = useState(false) const handleToggle = (): void => { if (updateAlertEnabled !== null) { dispatch( @@ -261,6 +276,35 @@ export function GeneralSettings(): JSX.Element { {t('setup_connection')}
    + + {enableLocalization && currentLanguageOption != null ? ( + <> + + + + {t('app_language_preferences')} + + + {t('app_language_description')} + + + + + + + ) : null} {showUpdateModal ? createPortal( diff --git a/app/src/pages/AppSettings/PrivacySettings.tsx b/app/src/pages/Desktop/AppSettings/PrivacySettings.tsx similarity index 88% rename from app/src/pages/AppSettings/PrivacySettings.tsx rename to app/src/pages/Desktop/AppSettings/PrivacySettings.tsx index 5e40b943fbc..abe45b61fa6 100644 --- a/app/src/pages/AppSettings/PrivacySettings.tsx +++ b/app/src/pages/Desktop/AppSettings/PrivacySettings.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { useSelector, useDispatch } from 'react-redux' @@ -15,10 +14,10 @@ import { import { toggleAnalyticsOptedIn, getAnalyticsOptedIn, -} from '../../redux/analytics' -import { ToggleButton } from '../../atoms/buttons' +} from '/app/redux/analytics' +import { ToggleButton } from '/app/atoms/buttons' -import type { Dispatch, State } from '../../redux/types' +import type { Dispatch, State } from '/app/redux/types' export function PrivacySettings(): JSX.Element { const { t } = useTranslation('branded') diff --git a/app/src/pages/AppSettings/__test__/AdvancedSettings.test.tsx b/app/src/pages/Desktop/AppSettings/__test__/AdvancedSettings.test.tsx similarity index 86% rename from app/src/pages/AppSettings/__test__/AdvancedSettings.test.tsx rename to app/src/pages/Desktop/AppSettings/__test__/AdvancedSettings.test.tsx index c2c4162d5ae..ac4285014bb 100644 --- a/app/src/pages/AppSettings/__test__/AdvancedSettings.test.tsx +++ b/app/src/pages/Desktop/AppSettings/__test__/AdvancedSettings.test.tsx @@ -1,11 +1,10 @@ -import * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' import { screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { AdditionalCustomLabwareSourceFolder, ClearUnavailableRobots, @@ -17,19 +16,19 @@ import { ShowLabwareOffsetSnippets, U2EInformation, UpdatedChannel, -} from '../../../organisms/AdvancedSettings' +} from '/app/organisms/Desktop/AdvancedSettings' import { AdvancedSettings } from '../AdvancedSettings' -vi.mock('../../../redux/config') -vi.mock('../../../redux/calibration') -vi.mock('../../../redux/custom-labware') -vi.mock('../../../redux/discovery') -vi.mock('../../../redux/protocol-analysis') -vi.mock('../../../redux/system-info') +vi.mock('/app/redux/config') +vi.mock('/app/redux/calibration') +vi.mock('/app/redux/custom-labware') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux/protocol-analysis') +vi.mock('/app/redux/system-info') vi.mock('@opentrons/components/src/hooks') -vi.mock('../../../redux/analytics') -vi.mock('../../../organisms/AdvancedSettings') +vi.mock('/app/redux/analytics') +vi.mock('/app/organisms/Desktop/AdvancedSettings') const render = (): ReturnType => { return renderWithProviders( diff --git a/app/src/pages/AppSettings/__test__/AppSettings.test.tsx b/app/src/pages/Desktop/AppSettings/__test__/AppSettings.test.tsx similarity index 85% rename from app/src/pages/AppSettings/__test__/AppSettings.test.tsx rename to app/src/pages/Desktop/AppSettings/__test__/AppSettings.test.tsx index fecd160e0e2..da48a1c690e 100644 --- a/app/src/pages/AppSettings/__test__/AppSettings.test.tsx +++ b/app/src/pages/Desktop/AppSettings/__test__/AppSettings.test.tsx @@ -1,23 +1,22 @@ -import * as React from 'react' import { vi, describe, beforeEach, it, expect, afterEach } from 'vitest' import { Route } from 'react-router' import { MemoryRouter, Routes } from 'react-router-dom' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../i18n' -import * as Config from '../../../redux/config' +import { i18n } from '/app/i18n' +import * as Config from '/app/redux/config' import { GeneralSettings } from '../GeneralSettings' import { PrivacySettings } from '../PrivacySettings' import { AdvancedSettings } from '../AdvancedSettings' -import { FeatureFlags } from '../../../organisms/AppSettings/FeatureFlags' +import { FeatureFlags } from '/app/organisms/Desktop/AppSettings/FeatureFlags' import { AppSettings } from '..' -vi.mock('../../../redux/config') +vi.mock('/app/redux/config') vi.mock('../GeneralSettings') vi.mock('../PrivacySettings') vi.mock('../AdvancedSettings') -vi.mock('../../../organisms/AppSettings/FeatureFlags') +vi.mock('/app/organisms/Desktop/AppSettings/FeatureFlags') const render = (path = '/'): ReturnType => { return renderWithProviders( diff --git a/app/src/pages/Desktop/AppSettings/__test__/GeneralSettings.test.tsx b/app/src/pages/Desktop/AppSettings/__test__/GeneralSettings.test.tsx new file mode 100644 index 00000000000..539c5899e8a --- /dev/null +++ b/app/src/pages/Desktop/AppSettings/__test__/GeneralSettings.test.tsx @@ -0,0 +1,130 @@ +import { MemoryRouter } from 'react-router-dom' +import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' +import { fireEvent, screen } from '@testing-library/react' +import { when } from 'vitest-when' + +import { renderWithProviders } from '/app/__testing-utils__' + +import { + i18n, + SIMPLIFIED_CHINESE, + SIMPLIFIED_CHINESE_DISPLAY_NAME, + US_ENGLISH, + US_ENGLISH_DISPLAY_NAME, +} from '/app/i18n' +import { getAlertIsPermanentlyIgnored } from '/app/redux/alerts' +import { + getAppLanguage, + updateConfigValue, + useFeatureFlag, +} from '/app/redux/config' +import * as Shell from '/app/redux/shell' +import { GeneralSettings } from '../GeneralSettings' + +vi.mock('/app/redux/config') +vi.mock('/app/redux/shell') +vi.mock('/app/redux/analytics') +vi.mock('/app/redux/alerts') + +const render = (): ReturnType => { + return renderWithProviders( + + + , + { + i18nInstance: i18n, + } + ) +} + +describe('GeneralSettings', () => { + beforeEach(() => { + vi.mocked(Shell.getAvailableShellUpdate).mockReturnValue(null) + vi.mocked(getAlertIsPermanentlyIgnored).mockReturnValue(false) + vi.mocked(getAppLanguage).mockReturnValue(US_ENGLISH) + when(vi.mocked(useFeatureFlag)) + .calledWith('enableLocalization') + .thenReturn(true) + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders correct titles', () => { + render() + screen.getByText('App Software Version') + screen.getByText('Software Update Alerts') + screen.getByText('Connect to a Robot via IP Address') + }) + + it('renders software version section with no update available', () => { + render() + screen.getByText('Up to date') + screen.getByText('View latest release notes on') + expect(screen.getByRole('link', { name: 'GitHub' })).toHaveAttribute( + 'href', + 'https://github.com/Opentrons/opentrons/blob/edge/app-shell/build/release-notes.md' + ) + screen.getByRole('button', { + name: 'See how to restore a previous software version', + }) + expect( + 'It is very important for the robot and app software to be on the same version. Manage the robot software versions via Robot Settings > Advanced.' + ).toBeTruthy() + expect( + screen.getByRole('link', { + name: + 'Learn more about keeping the Opentrons App and robot software in sync', + }) + ).toHaveAttribute('href', 'https://support.opentrons.com/s/') + }) + + it('renders correct info if there is update available', () => { + vi.mocked(Shell.getAvailableShellUpdate).mockReturnValue('5.0.0-beta.8') + render() + screen.getByRole('button', { name: 'View software update' }) + }) + + it('renders correct info if there is no update available', () => { + expect(screen.queryByText('View software update')).toBeNull() + }) + + it('renders correct info if there is update available but alert ignored enabled', () => { + vi.mocked(Shell.getAvailableShellUpdate).mockReturnValue('5.0.0-beta.8') + vi.mocked(getAlertIsPermanentlyIgnored).mockReturnValue(true) + expect(screen.queryByText('View software update')).toBeNull() + }) + + it('renders the text and toggle for update alert section', () => { + render() + screen.getByText( + 'Receive an alert when an Opentrons software update is available.' + ) + screen.getByRole('switch', { + name: 'Enable app update notifications', + }) + }) + + it('renders the ip address button', () => { + render() + screen.getByRole('button', { name: 'Set up connection' }) + }) + + it('renders the text and dropdown for the app language preferences section', () => { + render() + screen.getByText('App Language Preferences') + screen.getByText( + 'All app features use this language. Protocols and other user content will not change language.' + ) + fireEvent.click(screen.getByText(US_ENGLISH_DISPLAY_NAME)) + fireEvent.click( + screen.getByRole('button', { + name: SIMPLIFIED_CHINESE_DISPLAY_NAME, + }) + ) + expect(updateConfigValue).toBeCalledWith( + 'language.appLanguage', + SIMPLIFIED_CHINESE + ) + }) +}) diff --git a/app/src/pages/AppSettings/__test__/PrivacySettings.test.tsx b/app/src/pages/Desktop/AppSettings/__test__/PrivacySettings.test.tsx similarity index 78% rename from app/src/pages/AppSettings/__test__/PrivacySettings.test.tsx rename to app/src/pages/Desktop/AppSettings/__test__/PrivacySettings.test.tsx index 0b2f5a47f4c..3b0aaee80a4 100644 --- a/app/src/pages/AppSettings/__test__/PrivacySettings.test.tsx +++ b/app/src/pages/Desktop/AppSettings/__test__/PrivacySettings.test.tsx @@ -1,14 +1,13 @@ -import * as React from 'react' import { vi, it, describe } from 'vitest' import { MemoryRouter } from 'react-router-dom' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { PrivacySettings } from '../PrivacySettings' -vi.mock('../../../redux/analytics') -vi.mock('../../../redux/config') +vi.mock('/app/redux/analytics') +vi.mock('/app/redux/config') const render = (): ReturnType => { return renderWithProviders( diff --git a/app/src/pages/Desktop/AppSettings/index.tsx b/app/src/pages/Desktop/AppSettings/index.tsx new file mode 100644 index 00000000000..be7853b36b7 --- /dev/null +++ b/app/src/pages/Desktop/AppSettings/index.tsx @@ -0,0 +1,85 @@ +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' +import { Navigate, useParams } from 'react-router-dom' + +import { + ALIGN_START, + BORDERS, + Box, + COLORS, + DIRECTION_ROW, + Flex, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' + +import * as Config from '/app/redux/config' +import { GeneralSettings } from './GeneralSettings' +import { PrivacySettings } from './PrivacySettings' +import { AdvancedSettings } from './AdvancedSettings' +import { FeatureFlags } from '/app/organisms/Desktop/AppSettings/FeatureFlags' +import { NavTab } from '/app/molecules/NavTab' +import { Line } from '/app/atoms/structure' + +import type { DesktopRouteParams, AppSettingsTab } from '/app/App/types' + +export function AppSettings(): JSX.Element { + const { t } = useTranslation('app_settings') + const devToolsOn = useSelector(Config.getDevtoolsEnabled) + const { appSettingsTab } = useParams< + keyof DesktopRouteParams + >() as DesktopRouteParams + + const appSettingsContentByTab: { + [K in AppSettingsTab]: JSX.Element + } = { + general: , + privacy: , + advanced: , + 'feature-flags': , + } + + const appSettingsContent = appSettingsContentByTab[appSettingsTab] ?? ( + // default to the general tab if no tab or nonexistent tab is passed as a param + + ) + + return ( + + + + + {t('app_settings')} + + + + + + {devToolsOn && ( + + )} + + + + {appSettingsContent} + + + ) +} diff --git a/app/src/pages/Desktop/Devices/CalibrationDashboard/__tests__/CalibrationDashboard.test.tsx b/app/src/pages/Desktop/Devices/CalibrationDashboard/__tests__/CalibrationDashboard.test.tsx new file mode 100644 index 00000000000..a1076795490 --- /dev/null +++ b/app/src/pages/Desktop/Devices/CalibrationDashboard/__tests__/CalibrationDashboard.test.tsx @@ -0,0 +1,64 @@ +import { vi, describe, it, beforeEach } from 'vitest' +import { screen } from '@testing-library/react' +import { MemoryRouter, Route, Routes } from 'react-router-dom' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { CalibrationDashboard } from '..' + +import { useCalibrationTaskList } from '/app/organisms/Desktop/Devices/hooks' +import { useDashboardCalibratePipOffset } from '../hooks/useDashboardCalibratePipOffset' +import { useDashboardCalibrateTipLength } from '../hooks/useDashboardCalibrateTipLength' +import { useDashboardCalibrateDeck } from '../hooks/useDashboardCalibrateDeck' +import { expectedTaskList } from '/app/organisms/Desktop/Devices/hooks/__fixtures__/taskListFixtures' +import { mockLeftProtoPipette } from '/app/redux/pipettes/__fixtures__' +import { useNotifyAllRunsQuery } from '/app/resources/runs' +import { useAttachedPipettes } from '/app/resources/instruments' + +vi.mock('/app/redux-resources/robots') +vi.mock('/app/organisms/Desktop/Devices/hooks') +vi.mock('../hooks/useDashboardCalibratePipOffset') +vi.mock('../hooks/useDashboardCalibrateTipLength') +vi.mock('../hooks/useDashboardCalibrateDeck') +vi.mock('/app/resources/runs') +vi.mock('/app/resources/instruments') + +const render = (path = '/') => { + return renderWithProviders( + + + } + /> + + , + { + i18nInstance: i18n, + } + ) +} + +describe('CalibrationDashboard', () => { + beforeEach(() => { + vi.mocked(useCalibrationTaskList).mockReturnValue(expectedTaskList) + vi.mocked(useDashboardCalibratePipOffset).mockReturnValue([() => {}, null]) + vi.mocked(useDashboardCalibrateTipLength).mockReturnValue([() => {}, null]) + vi.mocked(useDashboardCalibrateDeck).mockReturnValue([ + () => {}, + null, + false, + ]) + vi.mocked(useAttachedPipettes).mockReturnValue({ + left: mockLeftProtoPipette, + right: null, + }) + vi.mocked(useNotifyAllRunsQuery).mockReturnValue({} as any) + }) + + it('renders a robot calibration dashboard title', () => { + render('/devices/otie/robot-settings/calibration/dashboard') + + screen.getByText(`otie Calibration Dashboard`) + }) +}) diff --git a/app/src/pages/Devices/CalibrationDashboard/hooks/__tests__/useDashboardCalibrateDeck.test.tsx b/app/src/pages/Desktop/Devices/CalibrationDashboard/hooks/__tests__/useDashboardCalibrateDeck.test.tsx similarity index 100% rename from app/src/pages/Devices/CalibrationDashboard/hooks/__tests__/useDashboardCalibrateDeck.test.tsx rename to app/src/pages/Desktop/Devices/CalibrationDashboard/hooks/__tests__/useDashboardCalibrateDeck.test.tsx diff --git a/app/src/pages/Devices/CalibrationDashboard/hooks/__tests__/useDashboardCalibratePipOffset.test.tsx b/app/src/pages/Desktop/Devices/CalibrationDashboard/hooks/__tests__/useDashboardCalibratePipOffset.test.tsx similarity index 100% rename from app/src/pages/Devices/CalibrationDashboard/hooks/__tests__/useDashboardCalibratePipOffset.test.tsx rename to app/src/pages/Desktop/Devices/CalibrationDashboard/hooks/__tests__/useDashboardCalibratePipOffset.test.tsx diff --git a/app/src/pages/Devices/CalibrationDashboard/hooks/__tests__/useDashboardCalibrateTipLength.test.tsx b/app/src/pages/Desktop/Devices/CalibrationDashboard/hooks/__tests__/useDashboardCalibrateTipLength.test.tsx similarity index 100% rename from app/src/pages/Devices/CalibrationDashboard/hooks/__tests__/useDashboardCalibrateTipLength.test.tsx rename to app/src/pages/Desktop/Devices/CalibrationDashboard/hooks/__tests__/useDashboardCalibrateTipLength.test.tsx diff --git a/app/src/pages/Desktop/Devices/CalibrationDashboard/hooks/useDashboardCalibrateDeck.tsx b/app/src/pages/Desktop/Devices/CalibrationDashboard/hooks/useDashboardCalibrateDeck.tsx new file mode 100644 index 00000000000..ba35a4319f5 --- /dev/null +++ b/app/src/pages/Desktop/Devices/CalibrationDashboard/hooks/useDashboardCalibrateDeck.tsx @@ -0,0 +1,132 @@ +import { useRef } from 'react' +import { createPortal } from 'react-dom' +import { useSelector } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { ModalShell } from '@opentrons/components' + +import { getTopPortalEl } from '/app/App/portal' +import { WizardHeader } from '/app/molecules/WizardHeader' +import { CalibrateDeck } from '/app/organisms/Desktop/CalibrateDeck' +import { LoadingState } from '/app/organisms/Desktop/CalibrationPanels' +import * as RobotApi from '/app/redux/robot-api' +import * as Sessions from '/app/redux/sessions' +import { getDeckCalibrationSession } from '/app/redux/sessions/deck-calibration/selectors' + +import type { State } from '/app/redux/types' +import type { DashboardCalDeckInvoker } from '/app/organisms/Desktop/Devices/hooks/useCalibrationTaskList' +import type { DeckCalibrationSession } from '/app/redux/sessions' +import type { SessionCommandString } from '/app/redux/sessions/types' +import type { RequestState } from '/app/redux/robot-api/types' + +// deck calibration commands for which the full page spinner should not appear +const spinnerCommandBlockList: SessionCommandString[] = [ + Sessions.sharedCalCommands.JOG, +] + +export function useDashboardCalibrateDeck( + robotName: string +): [DashboardCalDeckInvoker, JSX.Element | null, boolean] { + const trackedRequestId = useRef(null) + const createRequestId = useRef(null) + const jogRequestId = useRef(null) + const exitBeforeDeckConfigCompletion = useRef(false) + const invalidateHandlerRef = useRef<(() => void) | undefined>() + const { t } = useTranslation('robot_calibration') + + const deckCalSession: DeckCalibrationSession | null = useSelector( + (state: State) => { + return getDeckCalibrationSession(state, robotName) + } + ) + + const [dispatchRequests, requestIds] = RobotApi.useDispatchApiRequests( + dispatchedAction => { + if (dispatchedAction.type === Sessions.ENSURE_SESSION) { + createRequestId.current = + 'requestId' in dispatchedAction.meta + ? dispatchedAction.meta.requestId ?? null + : null + } else if ( + dispatchedAction.type === Sessions.CREATE_SESSION_COMMAND && + dispatchedAction.payload.command.command === + Sessions.sharedCalCommands.JOG + ) { + jogRequestId.current = + 'requestId' in dispatchedAction.meta + ? dispatchedAction.meta.requestId ?? null + : null + } else if ( + dispatchedAction.type !== Sessions.CREATE_SESSION_COMMAND || + !spinnerCommandBlockList.includes( + dispatchedAction.payload.command.command + ) + ) { + trackedRequestId.current = + 'meta' in dispatchedAction && 'requestId' in dispatchedAction.meta + ? dispatchedAction.meta.requestId ?? null + : null + } + } + ) + + const startingSession = + useSelector(state => + createRequestId.current + ? RobotApi.getRequestById(state, createRequestId.current) + : null + )?.status === RobotApi.PENDING + + const showSpinner = + useSelector(state => + trackedRequestId.current + ? RobotApi.getRequestById(state, trackedRequestId.current) + : null + )?.status === RobotApi.PENDING + + const isJogging = + useSelector((state: State) => + jogRequestId.current + ? RobotApi.getRequestById(state, jogRequestId.current) + : null + )?.status === RobotApi.PENDING + + const handleStartDashboardDeckCalSession: DashboardCalDeckInvoker = ( + props = {} + ) => { + invalidateHandlerRef.current = props.invalidateHandler + dispatchRequests( + Sessions.ensureSession(robotName, Sessions.SESSION_TYPE_DECK_CALIBRATION) + ) + } + + let Wizard: JSX.Element | null = createPortal( + startingSession ? ( + } + > + + + ) : ( + + ), + getTopPortalEl() + ) + + if (!(startingSession || deckCalSession != null)) Wizard = null + + return [ + handleStartDashboardDeckCalSession, + Wizard, + exitBeforeDeckConfigCompletion.current, + ] +} diff --git a/app/src/pages/Devices/CalibrationDashboard/hooks/useDashboardCalibratePipOffset.tsx b/app/src/pages/Desktop/Devices/CalibrationDashboard/hooks/useDashboardCalibratePipOffset.tsx similarity index 77% rename from app/src/pages/Devices/CalibrationDashboard/hooks/useDashboardCalibratePipOffset.tsx rename to app/src/pages/Desktop/Devices/CalibrationDashboard/hooks/useDashboardCalibratePipOffset.tsx index 6ec2595899f..09da025af71 100644 --- a/app/src/pages/Devices/CalibrationDashboard/hooks/useDashboardCalibratePipOffset.tsx +++ b/app/src/pages/Desktop/Devices/CalibrationDashboard/hooks/useDashboardCalibratePipOffset.tsx @@ -1,48 +1,38 @@ -import * as React from 'react' +import { useRef, useEffect } from 'react' import { createPortal } from 'react-dom' import { useSelector, useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next' import { ModalShell } from '@opentrons/components' -import { getTopPortalEl } from '../../../../App/portal' -import { WizardHeader } from '../../../../molecules/WizardHeader' -import { CalibratePipetteOffset } from '../../../../organisms/CalibratePipetteOffset' -import { LoadingState } from '../../../../organisms/CalibrationPanels' -import * as RobotApi from '../../../../redux/robot-api' -import * as Sessions from '../../../../redux/sessions' -import { getPipetteOffsetCalibrationSession } from '../../../../redux/sessions/pipette-offset-calibration/selectors' -import { pipetteOffsetCalibrationStarted } from '../../../../redux/analytics' - -import type { State } from '../../../../redux/types' +import { getTopPortalEl } from '/app/App/portal' +import { WizardHeader } from '/app/molecules/WizardHeader' +import { CalibratePipetteOffset } from '/app/organisms/Desktop/CalibratePipetteOffset' +import { LoadingState } from '/app/organisms/Desktop/CalibrationPanels' +import * as RobotApi from '/app/redux/robot-api' +import * as Sessions from '/app/redux/sessions' +import { getPipetteOffsetCalibrationSession } from '/app/redux/sessions/pipette-offset-calibration/selectors' +import { pipetteOffsetCalibrationStarted } from '/app/redux/analytics' +import type { DashboardCalOffsetInvoker } from '/app/organisms/Desktop/Devices/hooks/useCalibrationTaskList' +import type { State } from '/app/redux/types' import type { SessionCommandString, PipetteOffsetCalibrationSession, - PipetteOffsetCalibrationSessionParams, -} from '../../../../redux/sessions/types' -import type { RequestState } from '../../../../redux/robot-api/types' +} from '/app/redux/sessions/types' +import type { RequestState } from '/app/redux/robot-api/types' // pipette calibration commands for which the full page spinner should not appear const spinnerCommandBlockList: SessionCommandString[] = [ Sessions.sharedCalCommands.JOG, ] -export interface DashboardOffsetCalInvokerProps { - params: Pick & - Partial> -} - -export type DashboardCalOffsetInvoker = ( - props: DashboardOffsetCalInvokerProps -) => void - export function useDashboardCalibratePipOffset( robotName: string, onComplete: (() => unknown) | null = null ): [DashboardCalOffsetInvoker, JSX.Element | null] { - const createRequestId = React.useRef(null) - const deleteRequestId = React.useRef(null) - const jogRequestId = React.useRef(null) - const spinnerRequestId = React.useRef(null) + const createRequestId = useRef(null) + const deleteRequestId = useRef(null) + const jogRequestId = useRef(null) + const spinnerRequestId = useRef(null) const dispatch = useDispatch() const { t } = useTranslation('robot_calibration') @@ -52,7 +42,7 @@ export function useDashboardCalibratePipOffset( } ) - const [dispatchRequests] = RobotApi.useDispatchApiRequests( + const [dispatchRequests, requestIds] = RobotApi.useDispatchApiRequests( dispatchedAction => { if ( dispatchedAction.type === Sessions.ENSURE_SESSION && @@ -122,7 +112,7 @@ export function useDashboardCalibratePipOffset( : null )?.status === RobotApi.PENDING - React.useEffect(() => { + useEffect(() => { if (shouldClose) { onComplete?.() deleteRequestId.current = null @@ -176,6 +166,7 @@ export function useDashboardCalibratePipOffset( showSpinner={startingSession || showSpinner} dispatchRequests={dispatchRequests} isJogging={isJogging} + requestIds={requestIds} /> ), getTopPortalEl() diff --git a/app/src/pages/Devices/CalibrationDashboard/hooks/useDashboardCalibrateTipLength.tsx b/app/src/pages/Desktop/Devices/CalibrationDashboard/hooks/useDashboardCalibrateTipLength.tsx similarity index 75% rename from app/src/pages/Devices/CalibrationDashboard/hooks/useDashboardCalibrateTipLength.tsx rename to app/src/pages/Desktop/Devices/CalibrationDashboard/hooks/useDashboardCalibrateTipLength.tsx index 8827c617564..d4bd697eee1 100644 --- a/app/src/pages/Devices/CalibrationDashboard/hooks/useDashboardCalibrateTipLength.tsx +++ b/app/src/pages/Desktop/Devices/CalibrationDashboard/hooks/useDashboardCalibrateTipLength.tsx @@ -1,62 +1,52 @@ -import * as React from 'react' +import { useRef, useState } from 'react' import { createPortal } from 'react-dom' import { useSelector, useDispatch } from 'react-redux' import { useTranslation } from 'react-i18next' import { ModalShell } from '@opentrons/components' -import { getTopPortalEl } from '../../../../App/portal' -import { WizardHeader } from '../../../../molecules/WizardHeader' -import { CalibrateTipLength } from '../../../../organisms/CalibrateTipLength' -import { AskForCalibrationBlockModal } from '../../../../organisms/CalibrateTipLength/AskForCalibrationBlockModal' -import { LoadingState } from '../../../../organisms/CalibrationPanels' -import * as RobotApi from '../../../../redux/robot-api' -import * as Sessions from '../../../../redux/sessions' -import { tipLengthCalibrationStarted } from '../../../../redux/analytics' -import { getHasCalibrationBlock } from '../../../../redux/config' -import { getTipLengthCalibrationSession } from '../../../../redux/sessions/tip-length-calibration/selectors' - -import type { RequestState } from '../../../../redux/robot-api/types' +import { getTopPortalEl } from '/app/App/portal' +import { WizardHeader } from '/app/molecules/WizardHeader' +import { CalibrateTipLength } from '/app/organisms/Desktop/CalibrateTipLength' +import { AskForCalibrationBlockModal } from '/app/organisms/Desktop/CalibrateTipLength/AskForCalibrationBlockModal' +import { LoadingState } from '/app/organisms/Desktop/CalibrationPanels' +import * as RobotApi from '/app/redux/robot-api' +import * as Sessions from '/app/redux/sessions' +import { tipLengthCalibrationStarted } from '/app/redux/analytics' +import { getHasCalibrationBlock } from '/app/redux/config' +import { getTipLengthCalibrationSession } from '/app/redux/sessions/tip-length-calibration/selectors' + +import type { RequestState } from '/app/redux/robot-api/types' import type { SessionCommandString, TipLengthCalibrationSession, TipLengthCalibrationSessionParams, -} from '../../../../redux/sessions/types' -import type { State } from '../../../../redux/types' +} from '/app/redux/sessions/types' +import type { State } from '/app/redux/types' +import type { DashboardCalTipLengthInvoker } from '/app/organisms/Desktop/Devices/hooks/useCalibrationTaskList' // tip length calibration commands for which the full page spinner should not appear const spinnerCommandBlockList: SessionCommandString[] = [ Sessions.sharedCalCommands.JOG, ] -export interface DashboardTipLengthCalInvokerProps { - params: Pick & - Partial> - hasBlockModalResponse: boolean | null - invalidateHandler?: () => void -} - -export type DashboardCalTipLengthInvoker = ( - props: DashboardTipLengthCalInvokerProps -) => void - export function useDashboardCalibrateTipLength( robotName: string ): [DashboardCalTipLengthInvoker, JSX.Element | null] { - const createRequestId = React.useRef(null) - const trackedRequestId = React.useRef(null) - const jogRequestId = React.useRef(null) - const sessionParams = React.useRef< + const createRequestId = useRef(null) + const trackedRequestId = useRef(null) + const jogRequestId = useRef(null) + const sessionParams = useRef< | (Pick & Partial>) | null >(null) - const invalidateHandlerRef = React.useRef<(() => void) | undefined>() + const invalidateHandlerRef = useRef<(() => void) | undefined>() const dispatch = useDispatch() const { t } = useTranslation('robot_calibration') const sessionType = Sessions.SESSION_TYPE_TIP_LENGTH_CALIBRATION - const [dispatchRequests] = RobotApi.useDispatchApiRequests( + const [dispatchRequests, requestIds] = RobotApi.useDispatchApiRequests( dispatchedAction => { if ( dispatchedAction.type === Sessions.ENSURE_SESSION && @@ -96,9 +86,9 @@ export function useDashboardCalibrateTipLength( ) const configHasCalibrationBlock = useSelector(getHasCalibrationBlock) - const [showCalBlockModal, setShowCalBlockModal] = React.useState< - boolean | null - >(null) + const [showCalBlockModal, setShowCalBlockModal] = useState( + null + ) const handleStartDashboardTipLengthCalSession: DashboardCalTipLengthInvoker = props => { const { params, hasBlockModalResponse, invalidateHandler } = props @@ -181,6 +171,7 @@ export function useDashboardCalibrateTipLength( robotName={robotName} showSpinner={showSpinner} dispatchRequests={dispatchRequests} + requestIds={requestIds} isJogging={isJogging} offsetInvalidationHandler={invalidateHandlerRef.current} allowChangeTipRack={sessionParams.current?.tipRackDefinition == null} diff --git a/app/src/pages/Desktop/Devices/CalibrationDashboard/index.tsx b/app/src/pages/Desktop/Devices/CalibrationDashboard/index.tsx new file mode 100644 index 00000000000..226e44e15d8 --- /dev/null +++ b/app/src/pages/Desktop/Devices/CalibrationDashboard/index.tsx @@ -0,0 +1,49 @@ +import { useParams } from 'react-router-dom' +import { ApiHostProvider } from '@opentrons/react-api-client' +import { CalibrationTaskList } from '/app/organisms/Desktop/CalibrationTaskList' +import { OPENTRONS_USB } from '/app/redux/discovery' +import { appShellRequestor } from '/app/redux/shell/remote' +import { useDashboardCalibrateDeck } from './hooks/useDashboardCalibrateDeck' +import { useDashboardCalibratePipOffset } from './hooks/useDashboardCalibratePipOffset' +import { useDashboardCalibrateTipLength } from './hooks/useDashboardCalibrateTipLength' +import { useRobot } from '/app/redux-resources/robots' + +import type { DesktopRouteParams } from '/app/App/types' + +export function CalibrationDashboard(): JSX.Element { + const { robotName } = useParams< + keyof DesktopRouteParams + >() as DesktopRouteParams + const robot = useRobot(robotName) + const [ + dashboardOffsetCalLauncher, + DashboardOffsetCalWizard, + ] = useDashboardCalibratePipOffset(robotName) + const [ + dashboardTipLengthCalLauncher, + DashboardTipLengthCalWizard, + ] = useDashboardCalibrateTipLength(robotName) + const [ + dashboardDeckCalLauncher, + DashboardDeckCalWizard, + exitBeforeDeckConfigCompletion, + ] = useDashboardCalibrateDeck(robotName) + return ( + + + {DashboardDeckCalWizard} + {DashboardOffsetCalWizard} + {DashboardTipLengthCalWizard} + + ) +} diff --git a/app/src/pages/Desktop/Devices/DeviceDetails/DeviceDetailsComponent.tsx b/app/src/pages/Desktop/Devices/DeviceDetails/DeviceDetailsComponent.tsx new file mode 100644 index 00000000000..ced0067d758 --- /dev/null +++ b/app/src/pages/Desktop/Devices/DeviceDetails/DeviceDetailsComponent.tsx @@ -0,0 +1,66 @@ +import { useEstopQuery } from '@opentrons/react-api-client' +import { + ALIGN_CENTER, + Box, + COLORS, + BORDERS, + DIRECTION_COLUMN, + Flex, + SPACING, +} from '@opentrons/components' + +import { DeviceDetailsDeckConfiguration } from '/app/organisms/DeviceDetailsDeckConfiguration' +import { RobotOverview } from '/app/organisms/Desktop/Devices/RobotOverview' +import { InstrumentsAndModules } from '/app/organisms/Desktop/Devices/InstrumentsAndModules' +import { RecentProtocolRuns } from '/app/organisms/Desktop/Devices/RecentProtocolRuns' +import { EstopBanner } from '/app/organisms/Desktop/Devices/EstopBanner' +import { DISENGAGED, useEstopContext } from '/app/organisms/EmergencyStop' +import { useIsFlex } from '/app/redux-resources/robots' + +interface DeviceDetailsComponentProps { + robotName: string +} + +export function DeviceDetailsComponent({ + robotName, +}: DeviceDetailsComponentProps): JSX.Element { + const isFlex = useIsFlex(robotName) + const { data: estopStatus, error: estopError } = useEstopQuery({ + enabled: isFlex, + }) + const { isEmergencyStopModalDismissed } = useEstopContext() + + return ( + + {isFlex && + estopStatus?.data.status !== DISENGAGED && + estopError == null && + isEmergencyStopModalDismissed ? ( + + + + ) : null} + + + + + {isFlex ? : null} + + + ) +} diff --git a/app/src/pages/Desktop/Devices/DeviceDetails/__tests__/DeviceDetails.test.tsx b/app/src/pages/Desktop/Devices/DeviceDetails/__tests__/DeviceDetails.test.tsx new file mode 100644 index 00000000000..ddb4e62e8a4 --- /dev/null +++ b/app/src/pages/Desktop/Devices/DeviceDetails/__tests__/DeviceDetails.test.tsx @@ -0,0 +1,85 @@ +import { vi, it, describe, expect, beforeEach } from 'vitest' +import { when } from 'vitest-when' +import { screen } from '@testing-library/react' +import { MemoryRouter, Route, Routes } from 'react-router-dom' + +import { renderWithProviders } from '/app/__testing-utils__' + +import { i18n } from '/app/i18n' +import { useSyncRobotClock } from '/app/organisms/Desktop/Devices/hooks' +import { InstrumentsAndModules } from '/app/organisms/Desktop/Devices/InstrumentsAndModules' +import { RecentProtocolRuns } from '/app/organisms/Desktop/Devices/RecentProtocolRuns' +import { RobotOverview } from '/app/organisms/Desktop/Devices/RobotOverview' +import { getScanning } from '/app/redux/discovery' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' +import { useRobot } from '/app/redux-resources/robots' +import { DeviceDetails } from '..' + +import type { State } from '/app/redux/types' + +vi.mock('/app/organisms/Desktop/Devices/hooks') +vi.mock('/app/organisms/Desktop/Devices/InstrumentsAndModules') +vi.mock('/app/organisms/Desktop/Devices/RecentProtocolRuns') +vi.mock('/app/organisms/Desktop/Devices/RobotOverview') +vi.mock('/app/redux/discovery') +vi.mock('/app/redux-resources/robots') + +const render = (path = '/') => { + return renderWithProviders( + + + } /> + devices page} /> + + , + { + i18nInstance: i18n, + } + ) +} + +describe('DeviceDetails', () => { + beforeEach(() => { + when(useRobot).calledWith('otie').thenReturn(null) + when(getScanning) + .calledWith({} as State) + .thenReturn(false) + }) + + it('redirects to devices page when a robot is not found and not scanning', () => { + render('/devices/otie') + + screen.getByText('devices page') + }) + + it('renders null when a robot is not found and discovery client is scanning', () => { + when(getScanning) + .calledWith({} as State) + .thenReturn(true) + render('/devices/otie') + + expect(vi.mocked(RobotOverview)).not.toHaveBeenCalled() + expect(vi.mocked(InstrumentsAndModules)).not.toHaveBeenCalled() + expect(vi.mocked(RecentProtocolRuns)).not.toHaveBeenCalled() + }) + + it('renders a RobotOverview when a robot is found and syncs clock', () => { + when(useRobot).calledWith('otie').thenReturn(mockConnectableRobot) + render('/devices/otie') + + expect(vi.mocked(RobotOverview)).toHaveBeenCalled() + expect(useSyncRobotClock).toHaveBeenCalledWith('otie') + }) + + it('renders InstrumentsAndModules when a robot is found', () => { + when(useRobot).calledWith('otie').thenReturn(mockConnectableRobot) + render('/devices/otie') + expect(vi.mocked(InstrumentsAndModules)).toHaveBeenCalled() + }) + + it('renders RecentProtocolRuns when a robot is found', () => { + when(useRobot).calledWith('otie').thenReturn(mockConnectableRobot) + render('/devices/otie') + expect(vi.mocked(RecentProtocolRuns)).toHaveBeenCalled() + }) +}) diff --git a/app/src/pages/Desktop/Devices/DeviceDetails/__tests__/DeviceDetailsComponent.test.tsx b/app/src/pages/Desktop/Devices/DeviceDetails/__tests__/DeviceDetailsComponent.test.tsx new file mode 100644 index 00000000000..e725f35fcaf --- /dev/null +++ b/app/src/pages/Desktop/Devices/DeviceDetails/__tests__/DeviceDetailsComponent.test.tsx @@ -0,0 +1,92 @@ +import { vi, it, describe, expect, beforeEach } from 'vitest' +import { when } from 'vitest-when' + +import { renderWithProviders } from '/app/__testing-utils__' +import { useEstopQuery } from '@opentrons/react-api-client' + +import { i18n } from '/app/i18n' +import { InstrumentsAndModules } from '/app/organisms/Desktop/Devices/InstrumentsAndModules' +import { RecentProtocolRuns } from '/app/organisms/Desktop/Devices/RecentProtocolRuns' +import { RobotOverview } from '/app/organisms/Desktop/Devices/RobotOverview' +import { DISENGAGED, NOT_PRESENT } from '/app/organisms/EmergencyStop' +import { DeviceDetailsDeckConfiguration } from '/app/organisms/DeviceDetailsDeckConfiguration' +import { useIsFlex } from '/app/redux-resources/robots' +import { DeviceDetailsComponent } from '../DeviceDetailsComponent' + +vi.mock('@opentrons/react-api-client') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/organisms/Desktop/Devices/InstrumentsAndModules') +vi.mock('/app/organisms/Desktop/Devices/RecentProtocolRuns') +vi.mock('/app/organisms/Desktop/Devices/RobotOverview') +vi.mock('/app/organisms/DeviceDetailsDeckConfiguration') +vi.mock('/app/redux/discovery') + +const ROBOT_NAME = 'otie' +const mockEstopStatus = { + data: { + status: DISENGAGED, + leftEstopPhysicalStatus: DISENGAGED, + rightEstopPhysicalStatus: NOT_PRESENT, + }, +} + +const render = () => { + return renderWithProviders( + , + { + i18nInstance: i18n, + } + ) +} + +describe('DeviceDetailsComponent', () => { + beforeEach(() => { + vi.mocked(useEstopQuery).mockReturnValue({ data: mockEstopStatus } as any) + when(vi.mocked(useIsFlex)).calledWith(ROBOT_NAME).thenReturn(false) + }) + + it('renders a RobotOverview when a robot is found and syncs clock', () => { + render() + expect(vi.mocked(RobotOverview)).toHaveBeenCalledWith( + { + robotName: ROBOT_NAME, + }, + {} + ) + }) + + it('renders InstrumentsAndModules when a robot is found', () => { + render() + expect(vi.mocked(InstrumentsAndModules)).toHaveBeenCalledWith( + { + robotName: ROBOT_NAME, + }, + {} + ) + }) + + it('renders RecentProtocolRuns when a robot is found', () => { + render() + expect(vi.mocked(RecentProtocolRuns)).toHaveBeenCalledWith( + { + robotName: ROBOT_NAME, + }, + {} + ) + }) + + it('renders Deck Configuration when a robot is flex', () => { + when(vi.mocked(useIsFlex)).calledWith(ROBOT_NAME).thenReturn(true) + render() + expect(vi.mocked(DeviceDetailsDeckConfiguration)).toHaveBeenCalled() + }) + + it.todo('renders EstopBanner when estop is engaged') + // mockEstopStatus.data.status = PHYSICALLY_ENGAGED + // vi.mocked(useEstopQuery).mockReturnValue({ data: mockEstopStatus } as any) + // const { result } = renderHook(() => useEstopContext(), { wrapper }) + // result.current.setIsEmergencyStopModalDismissed(true) + // // act(() => result.current.setIsEmergencyStopModalDismissed(true)) + // const [{ getByText }] = render() + // getByText('mock EstopBanner') +}) diff --git a/app/src/pages/Desktop/Devices/DeviceDetails/index.tsx b/app/src/pages/Desktop/Devices/DeviceDetails/index.tsx new file mode 100644 index 00000000000..1baef896c97 --- /dev/null +++ b/app/src/pages/Desktop/Devices/DeviceDetails/index.tsx @@ -0,0 +1,37 @@ +import { useSelector } from 'react-redux' +import { Navigate, useParams } from 'react-router-dom' + +import { ApiHostProvider } from '@opentrons/react-api-client' + +import { useSyncRobotClock } from '/app/organisms/Desktop/Devices/hooks' +import { getScanning, OPENTRONS_USB } from '/app/redux/discovery' +import { appShellRequestor } from '/app/redux/shell/remote' +import { DeviceDetailsComponent } from './DeviceDetailsComponent' +import { useRobot } from '/app/redux-resources/robots' + +import type { DesktopRouteParams } from '/app/App/types' + +export function DeviceDetails(): JSX.Element | null { + const { robotName } = useParams< + keyof DesktopRouteParams + >() as DesktopRouteParams + const robot = useRobot(robotName) + const isScanning = useSelector(getScanning) + + useSyncRobotClock(robotName) + + if (robot == null && isScanning) return null + + return robot != null ? ( + // TODO(bh, 2023-05-31): substitute wrapped AppApiHostProvider that registers/authorizes + + + + ) : ( + + ) +} diff --git a/app/src/pages/Devices/DevicesLanding/NewRobotSetupHelp.tsx b/app/src/pages/Desktop/Devices/DevicesLanding/NewRobotSetupHelp.tsx similarity index 83% rename from app/src/pages/Devices/DevicesLanding/NewRobotSetupHelp.tsx rename to app/src/pages/Desktop/Devices/DevicesLanding/NewRobotSetupHelp.tsx index 729b4448af5..d3fba7af095 100644 --- a/app/src/pages/Devices/DevicesLanding/NewRobotSetupHelp.tsx +++ b/app/src/pages/Desktop/Devices/DevicesLanding/NewRobotSetupHelp.tsx @@ -1,4 +1,4 @@ -import * as React from 'react' +import { useState } from 'react' import { createPortal } from 'react-dom' import { useTranslation } from 'react-i18next' import { @@ -14,8 +14,8 @@ import { Modal, } from '@opentrons/components' -import { getTopPortalEl } from '../../../App/portal' -import { ExternalLink } from '../../../atoms/Link/ExternalLink' +import { getTopPortalEl } from '/app/App/portal' +import { ExternalLink } from '/app/atoms/Link/ExternalLink' const NEW_FLEX_SETUP_SUPPORT_ARTICLE_HREF = 'https://insights.opentrons.com/hubfs/Products/Flex/Opentrons%20Flex%20Quickstart%20Guide.pdf' @@ -23,10 +23,8 @@ const NEW_OT2_SETUP_SUPPORT_ARTICLE_HREF = 'https://insights.opentrons.com/hubfs/Products/OT-2/OT-2%20Quick%20Start%20Guide.pdf' export function NewRobotSetupHelp(): JSX.Element { - const { t } = useTranslation(['devices_landing', 'shared']) - const [showNewRobotHelpModal, setShowNewRobotHelpModal] = React.useState( - false - ) + const { t } = useTranslation(['devices_landing', 'shared', 'branded']) + const [showNewRobotHelpModal, setShowNewRobotHelpModal] = useState(false) return ( <> @@ -49,13 +47,13 @@ export function NewRobotSetupHelp(): JSX.Element { > - {t('new_robot_instructions')} + {t('branded:new_robot_instructions')} - {t('opentrons_flex_quickstart_guide')} + {t('branded:opentrons_flex_quickstart_guide')} { return renderWithProviders(, { diff --git a/app/src/pages/Devices/DevicesLanding/__tests__/NewRobotSetupHelp.test.tsx b/app/src/pages/Desktop/Devices/DevicesLanding/__tests__/NewRobotSetupHelp.test.tsx similarity index 93% rename from app/src/pages/Devices/DevicesLanding/__tests__/NewRobotSetupHelp.test.tsx rename to app/src/pages/Desktop/Devices/DevicesLanding/__tests__/NewRobotSetupHelp.test.tsx index fb4cbadfd42..cc90f46b41d 100644 --- a/app/src/pages/Devices/DevicesLanding/__tests__/NewRobotSetupHelp.test.tsx +++ b/app/src/pages/Desktop/Devices/DevicesLanding/__tests__/NewRobotSetupHelp.test.tsx @@ -1,9 +1,8 @@ -import * as React from 'react' import { it, describe, expect } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' import { NewRobotSetupHelp } from '../NewRobotSetupHelp' const render = () => { diff --git a/app/src/pages/Desktop/Devices/DevicesLanding/index.tsx b/app/src/pages/Desktop/Devices/DevicesLanding/index.tsx new file mode 100644 index 00000000000..a3ebf7114e6 --- /dev/null +++ b/app/src/pages/Desktop/Devices/DevicesLanding/index.tsx @@ -0,0 +1,187 @@ +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' +import partition from 'lodash/partition' + +import { + ALIGN_CENTER, + Box, + COLORS, + DIRECTION_COLUMN, + DISPLAY_FLEX, + Flex, + Icon, + JUSTIFY_SPACE_BETWEEN, + Link, + POSITION_ABSOLUTE, + SIZE_6, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' +import { ApiHostProvider } from '@opentrons/react-api-client' +import { + getScanning, + getConnectableRobots, + getReachableRobots, + getUnreachableRobots, + OPENTRONS_USB, +} from '/app/redux/discovery' +import { appShellRequestor } from '/app/redux/shell/remote' +import { RobotCard } from '/app/organisms/Desktop/Devices/RobotCard' +import { DevicesEmptyState } from '/app/organisms/Desktop/Devices/DevicesEmptyState' +import { CollapsibleSection } from '/app/molecules/CollapsibleSection' + +import { Divider } from '/app/atoms/structure' +import { NewRobotSetupHelp } from './NewRobotSetupHelp' + +import type { State } from '/app/redux/types' + +export const TROUBLESHOOTING_CONNECTION_PROBLEMS_URL = + 'https://support.opentrons.com/en/articles/2687601-troubleshooting-connection-problems' + +export function DevicesLanding(): JSX.Element { + const { t } = useTranslation('devices_landing') + + const isScanning = useSelector((state: State) => getScanning(state)) + const healthyReachableRobots = useSelector((state: State) => + getConnectableRobots(state) + ) + const reachableRobots = useSelector((state: State) => + getReachableRobots(state) + ) + const unreachableRobots = useSelector((state: State) => + getUnreachableRobots(state) + ) + + const [unhealthyReachableRobots, recentlySeenRobots] = partition( + reachableRobots, + robot => robot.healthStatus === 'ok' + ) + + const noRobots = + [ + ...healthyReachableRobots, + ...recentlySeenRobots, + ...unhealthyReachableRobots, + ...unreachableRobots, + ].length === 0 + + return ( + + + + {t('devices')} + + + + {isScanning && noRobots ? : null} + {!isScanning && noRobots ? : null} + {!noRobots ? ( + <> + + {healthyReachableRobots.map(robot => ( + + + + ))} + {unhealthyReachableRobots.map(robot => ( + + + + ))} + + + + {recentlySeenRobots.map(robot => ( + + ))} + {unreachableRobots.map(robot => ( + + ))} + + + ) : null} + + ) +} + +function DevicesLoadingState(): JSX.Element { + const { t } = useTranslation('devices_landing') + return ( + + {t('looking_for_robots')} + + + + {t('troubleshooting_connection_problems')} + + + + + ) +} diff --git a/app/src/pages/Devices/ProtocolRunDetails/__tests__/ProtocolRunDetails.test.tsx b/app/src/pages/Desktop/Devices/ProtocolRunDetails/__tests__/ProtocolRunDetails.test.tsx similarity index 82% rename from app/src/pages/Devices/ProtocolRunDetails/__tests__/ProtocolRunDetails.test.tsx rename to app/src/pages/Desktop/Devices/ProtocolRunDetails/__tests__/ProtocolRunDetails.test.tsx index e9091a07340..d90285e6804 100644 --- a/app/src/pages/Devices/ProtocolRunDetails/__tests__/ProtocolRunDetails.test.tsx +++ b/app/src/pages/Desktop/Devices/ProtocolRunDetails/__tests__/ProtocolRunDetails.test.tsx @@ -1,44 +1,41 @@ -import * as React from 'react' import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' import { Route, MemoryRouter, Routes } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' import { when } from 'vitest-when' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { mockConnectableRobot } from '../../../../redux/discovery/__fixtures__' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { mockConnectableRobot } from '/app/redux/discovery/__fixtures__' +import { useSyncRobotClock } from '/app/organisms/Desktop/Devices/hooks' +import { ProtocolRunHeader } from '/app/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader' +import { ProtocolRunModuleControls } from '/app/organisms/Desktop/Devices/ProtocolRun/ProtocolRunModuleControls' +import { ProtocolRunSetup } from '/app/organisms/Desktop/Devices/ProtocolRun/ProtocolRunSetup' +import { RunPreviewComponent } from '/app/organisms/Desktop/Devices/RunPreview' +import { ProtocolRunRuntimeParameters } from '/app/organisms/Desktop/Devices/ProtocolRun/ProtocolRunRunTimeParameters' import { + useCurrentRunId, + useMostRecentCompletedAnalysis, + useRunHasStarted, useModuleRenderInfoForProtocolById, - useRobot, useRunStatuses, - useSyncRobotClock, - useRunHasStarted, -} from '../../../../organisms/Devices/hooks' -import { useMostRecentCompletedAnalysis } from '../../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' -import { ProtocolRunHeader } from '../../../../organisms/Devices/ProtocolRun/ProtocolRunHeader' -import { ProtocolRunModuleControls } from '../../../../organisms/Devices/ProtocolRun/ProtocolRunModuleControls' -import { ProtocolRunSetup } from '../../../../organisms/Devices/ProtocolRun/ProtocolRunSetup' -import { RunPreviewComponent } from '../../../../organisms/RunPreview' -import { ProtocolRunRuntimeParameters } from '../../../../organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters' -import { useCurrentRunId } from '../../../../resources/runs' -import { mockRobotSideAnalysis } from '../../../../molecules/Command/__fixtures__' +} from '/app/resources/runs' +import { mockRobotSideAnalysis } from '/app/molecules/Command/__fixtures__' +import { useRobot } from '/app/redux-resources/robots' import { ProtocolRunDetails } from '..' import type { ModuleModel, ModuleType } from '@opentrons/shared-data' +vi.mock('/app/organisms/Desktop/Devices/hooks') +vi.mock('/app/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader') +vi.mock('/app/organisms/Desktop/Devices/ProtocolRun/ProtocolRunSetup') +vi.mock('/app/organisms/Desktop/Devices/RunPreview') +vi.mock('/app/organisms/Desktop/Devices/ProtocolRun/ProtocolRunModuleControls') +vi.mock('/app/resources/runs') vi.mock( - '../../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' -) -vi.mock('../../../../organisms/Devices/hooks') -vi.mock('../../../../organisms/Devices/ProtocolRun/ProtocolRunHeader') -vi.mock('../../../../organisms/Devices/ProtocolRun/ProtocolRunSetup') -vi.mock('../../../../organisms/RunPreview') -vi.mock('../../../../organisms/Devices/ProtocolRun/ProtocolRunModuleControls') -vi.mock('../../../../resources/runs') -vi.mock( - '../../../../organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters' + '/app/organisms/Desktop/Devices/ProtocolRun/ProtocolRunRunTimeParameters' ) -vi.mock('../../../../redux/config') +vi.mock('/app/redux/config') +vi.mock('/app/redux-resources/robots') const MOCK_MAGNETIC_MODULE_COORDS = [10, 20, 0] diff --git a/app/src/pages/Desktop/Devices/ProtocolRunDetails/index.tsx b/app/src/pages/Desktop/Devices/ProtocolRunDetails/index.tsx new file mode 100644 index 00000000000..4abb609c4fc --- /dev/null +++ b/app/src/pages/Desktop/Devices/ProtocolRunDetails/index.tsx @@ -0,0 +1,412 @@ +import { useEffect, useRef, useState } from 'react' +import isEmpty from 'lodash/isEmpty' +import { useTranslation } from 'react-i18next' +import { useDispatch } from 'react-redux' +import { NavLink, Navigate, useParams, useNavigate } from 'react-router-dom' +import styled, { css } from 'styled-components' + +import { + BORDERS, + Box, + COLORS, + DIRECTION_COLUMN, + DIRECTION_ROW, + Flex, + JUSTIFY_SPACE_AROUND, + LegacyStyledText, + OVERFLOW_SCROLL, + POSITION_RELATIVE, + SPACING, + Tooltip, + TYPOGRAPHY, + useHoverTooltip, +} from '@opentrons/components' +import { ApiHostProvider } from '@opentrons/react-api-client' +import { useSyncRobotClock } from '/app/organisms/Desktop/Devices/hooks' +import { ProtocolRunHeader } from '/app/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader' +import { RunPreview } from '/app/organisms/Desktop/Devices/RunPreview' +import { ProtocolRunSetup } from '/app/organisms/Desktop/Devices/ProtocolRun/ProtocolRunSetup' +import { BackToTopButton } from '/app/organisms/Desktop/Devices/ProtocolRun/BackToTopButton' +import { ProtocolRunModuleControls } from '/app/organisms/Desktop/Devices/ProtocolRun/ProtocolRunModuleControls' +import { ProtocolRunRuntimeParameters } from '/app/organisms/Desktop/Devices/ProtocolRun/ProtocolRunRunTimeParameters' +import { + useCurrentRunId, + useMostRecentCompletedAnalysis, + useRunHasStarted, + useModuleRenderInfoForProtocolById, + useRunStatuses, +} from '/app/resources/runs' +import { OPENTRONS_USB } from '/app/redux/discovery' +import { fetchProtocols } from '/app/redux/protocol-storage' +import { appShellRequestor } from '/app/redux/shell/remote' +import { useRobot, useRobotType } from '/app/redux-resources/robots' + +import type { ViewportListRef } from 'react-viewport-list' +import type { DesktopRouteParams, ProtocolRunDetailsTab } from '/app/App/types' +import type { Dispatch } from '/app/redux/types' + +const baseRoundTabStyling = css` + ${TYPOGRAPHY.pSemiBold} + color: ${COLORS.black90}; + background-color: ${COLORS.purple30}; + border: 0px ${BORDERS.styleSolid} ${COLORS.purple30}; + border-radius: ${BORDERS.borderRadius8}; + padding: ${SPACING.spacing8} ${SPACING.spacing16}; + position: ${POSITION_RELATIVE}; + + &:hover { + background-color: ${COLORS.purple35}; + } + + &:focus-visible { + outline: 2px ${BORDERS.styleSolid} ${COLORS.yellow50}; + } +` + +const disabledRoundTabStyling = css` + ${baseRoundTabStyling} + color: ${COLORS.grey40}; + background-color: ${COLORS.grey30}; + + &:hover { + background-color: ${COLORS.grey30}; + } +` + +const RoundNavLink = styled(NavLink)` + ${baseRoundTabStyling} + color: ${COLORS.black90}; + + &:hover { + background-color: ${COLORS.purple35}; + } + + &.active { + background-color: ${COLORS.purple50}; + color: ${COLORS.white}; + + &:hover { + background-color: ${COLORS.purple55}; + } + } +` + +const JUMP_OFFSET_FROM_TOP_PX = 20 + +interface RoundTabProps { + disabled: boolean + tabDisabledReason?: string + to: string + tabName: string +} + +function RoundTab({ + disabled, + tabDisabledReason, + to, + tabName, +}: RoundTabProps): JSX.Element { + const [targetProps, tooltipProps] = useHoverTooltip() + return disabled ? ( + <> + + {tabName} + + {tabDisabledReason != null ? ( + {tabDisabledReason} + ) : null} + + ) : ( + + {tabName} + + ) +} + +export function ProtocolRunDetails(): JSX.Element | null { + const { robotName, runId, protocolRunDetailsTab } = useParams< + keyof DesktopRouteParams + >() as DesktopRouteParams + const dispatch = useDispatch() + + const robot = useRobot(robotName) + useSyncRobotClock(robotName) + useEffect(() => { + dispatch(fetchProtocols()) + }, [dispatch]) + return robot != null ? ( + + + + + + + + ) : null +} + +const JUMPED_STEP_HIGHLIGHT_DELAY_MS = 1000 +interface PageContentsProps { + runId: string + robotName: string + protocolRunDetailsTab: ProtocolRunDetailsTab +} +function PageContents(props: PageContentsProps): JSX.Element { + const { runId, robotName, protocolRunDetailsTab } = props + const robotType = useRobotType(robotName) + const protocolRunHeaderRef = useRef(null) + const listRef = useRef(null) + const [jumpedIndex, setJumpedIndex] = useState(null) + + useEffect(() => { + if (jumpedIndex != null) { + setTimeout(() => { + setJumpedIndex(null) + }, JUMPED_STEP_HIGHLIGHT_DELAY_MS) + } + }, [jumpedIndex]) + + const makeHandleScrollToStep = (i: number) => () => { + listRef.current?.scrollToIndex(i, true, -1 * JUMP_OFFSET_FROM_TOP_PX) + } + const makeHandleJumpToStep = (i: number) => () => { + makeHandleScrollToStep(i)() + setJumpedIndex(i) + } + const protocolRunDetailsContentByTab: { + [K in ProtocolRunDetailsTab]: { + content: JSX.Element | null + backToTop: JSX.Element | null + } + } = { + setup: { + content: ( + + ), + backToTop: ( + + + + ), + }, + 'runtime-parameters': { + content: , + backToTop: null, + }, + 'module-controls': { + content: ( + + ), + backToTop: null, + }, + 'run-preview': { + content: ( + + ), + backToTop: null, + }, + } + const tabDetails = protocolRunDetailsContentByTab[protocolRunDetailsTab] ?? { + // default to the setup tab if no tab or nonexistent tab is passed as a param + content: ( + + ), + backToTop: null, + } + const { content, backToTop } = tabDetails + + return ( + <> + + + + + + + + + {content} + + {backToTop} + + ) +} + +interface SetupTabProps { + robotName: string + runId: string + protocolRunDetailsTab?: ProtocolRunDetailsTab +} + +const SetupTab = (props: SetupTabProps): JSX.Element | null => { + const { robotName, runId, protocolRunDetailsTab } = props + const { t } = useTranslation('run_details') + const currentRunId = useCurrentRunId() + const navigate = useNavigate() + const runHasStarted = useRunHasStarted(currentRunId) + const disabled = currentRunId !== runId + const tabDisabledReason = `${t('setup')} ${t( + 'not_available_for_a_completed_run' + )}` + + // On the initial render or when a run first begins, navigate to "run preview" if the run has started. + // If "run again" is clicked, the user should NOT be directed back to the "setup" tab. + useEffect(() => { + if (runHasStarted && protocolRunDetailsTab !== 'run-preview') { + navigate(`/devices/${robotName}/protocol-runs/${runId}/run-preview`) + } + }, [runHasStarted]) + + return ( + + ) +} + +interface ParametersTabProps { + robotName: string + runId: string + protocolRunDetailsTab: ProtocolRunDetailsTab +} + +const ParametersTab = (props: ParametersTabProps): JSX.Element | null => { + const { robotName, runId, protocolRunDetailsTab } = props + const { t } = useTranslation('run_details') + const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) + const navigate = useNavigate() + const disabled = mostRecentAnalysis == null + + useEffect(() => { + if (disabled && protocolRunDetailsTab === 'runtime-parameters') { + navigate(`/devices/${robotName}/protocol-runs/${runId}/run-preview`, { + replace: true, + }) + } + }, [disabled, navigate, protocolRunDetailsTab, robotName, runId]) + + return ( + + ) +} + +interface ModuleControlsTabProps { + robotName: string + runId: string + protocolRunDetailsTab: ProtocolRunDetailsTab +} + +const ModuleControlsTab = ( + props: ModuleControlsTabProps +): JSX.Element | null => { + const { robotName, runId, protocolRunDetailsTab } = props + const { t } = useTranslation('run_details') + const currentRunId = useCurrentRunId() + const moduleRenderInfoForProtocolById = useModuleRenderInfoForProtocolById( + runId + ) + const { isRunStill } = useRunStatuses() + const navigate = useNavigate() + + const disabled = currentRunId !== runId || !isRunStill + const tabDisabledReason = `${t('module_controls')} ${t( + currentRunId !== runId + ? 'not_available_for_a_completed_run' + : 'not_available_for_a_run_in_progress' + )}` + + useEffect(() => { + if (disabled && protocolRunDetailsTab === 'module-controls') + navigate(`/devices/${robotName}/protocol-runs/${runId}/run-preview`) + }, [disabled, navigate, protocolRunDetailsTab, robotName, runId]) + + return isEmpty(moduleRenderInfoForProtocolById) ? null : ( + + ) +} + +const RunPreviewTab = (props: SetupTabProps): JSX.Element | null => { + const { robotName, runId } = props + const { t } = useTranslation('run_details') + + const robotSideAnalysis = useMostRecentCompletedAnalysis(runId) + + return ( + + ) +} diff --git a/app/src/pages/Devices/RobotSettings/__tests__/RobotSettings.test.tsx b/app/src/pages/Desktop/Devices/RobotSettings/__tests__/RobotSettings.test.tsx similarity index 82% rename from app/src/pages/Devices/RobotSettings/__tests__/RobotSettings.test.tsx rename to app/src/pages/Desktop/Devices/RobotSettings/__tests__/RobotSettings.test.tsx index 6961f74ad22..2d63a704a6f 100644 --- a/app/src/pages/Devices/RobotSettings/__tests__/RobotSettings.test.tsx +++ b/app/src/pages/Desktop/Devices/RobotSettings/__tests__/RobotSettings.test.tsx @@ -1,29 +1,28 @@ -import * as React from 'react' import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' import { fireEvent, screen } from '@testing-library/react' import { Route, MemoryRouter, Routes } from 'react-router-dom' -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { RobotSettingsCalibration } from '../../../../organisms/RobotSettingsCalibration' -import { RobotSettingsNetworking } from '../../../../organisms/Devices/RobotSettings/RobotSettingsNetworking' -import { RobotSettingsAdvanced } from '../../../../organisms/Devices/RobotSettings/RobotSettingsAdvanced' -import { useRobot } from '../../../../organisms/Devices/hooks' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { RobotSettingsCalibration } from '/app/organisms/Desktop/RobotSettingsCalibration' +import { RobotSettingsNetworking } from '/app/organisms/Desktop/Devices/RobotSettings/RobotSettingsNetworking' +import { RobotSettingsAdvanced } from '/app/organisms/Desktop/Devices/RobotSettings/RobotSettingsAdvanced' +import { useRobot } from '/app/redux-resources/robots' import { RobotSettings } from '..' import { when } from 'vitest-when' import { mockConnectableRobot, mockReachableRobot, mockUnreachableRobot, -} from '../../../../redux/discovery/__fixtures__' -import { getRobotUpdateSession } from '../../../../redux/robot-update' - -vi.mock('../../../../organisms/RobotSettingsCalibration') -vi.mock('../../../../organisms/Devices/RobotSettings/RobotSettingsNetworking') -vi.mock('../../../../organisms/Devices/RobotSettings/RobotSettingsAdvanced') -vi.mock('../../../../organisms/Devices/hooks') -vi.mock('../../../../redux/discovery/selectors') -vi.mock('../../../../redux/robot-update') +} from '/app/redux/discovery/__fixtures__' +import { getRobotUpdateSession } from '/app/redux/robot-update' + +vi.mock('/app/organisms/Desktop/RobotSettingsCalibration') +vi.mock('/app/organisms/Desktop/Devices/RobotSettings/RobotSettingsNetworking') +vi.mock('/app/organisms/Desktop/Devices/RobotSettings/RobotSettingsAdvanced') +vi.mock('/app/redux-resources/robots') +vi.mock('/app/redux/discovery/selectors') +vi.mock('/app/redux/robot-update') const render = (path = '/') => { return renderWithProviders( diff --git a/app/src/pages/Desktop/Devices/RobotSettings/index.tsx b/app/src/pages/Desktop/Devices/RobotSettings/index.tsx new file mode 100644 index 00000000000..4c44b9b3beb --- /dev/null +++ b/app/src/pages/Desktop/Devices/RobotSettings/index.tsx @@ -0,0 +1,170 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useParams, Navigate } from 'react-router-dom' + +import { + Banner, + BORDERS, + Box, + COLORS, + DIRECTION_COLUMN, + Flex, + SIZE_6, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' +import { ApiHostProvider } from '@opentrons/react-api-client' +import { useSelector } from 'react-redux' + +import { + CONNECTABLE, + UNREACHABLE, + REACHABLE, + OPENTRONS_USB, +} from '/app/redux/discovery' +import { appShellRequestor } from '/app/redux/shell/remote' +import { getRobotUpdateSession } from '/app/redux/robot-update' +import { getDevtoolsEnabled } from '/app/redux/config' +import { useRobot } from '/app/redux-resources/robots' +import { Line } from '/app/atoms/structure' +import { NavTab } from '/app/molecules/NavTab' +import { RobotSettingsCalibration } from '/app/organisms/Desktop/RobotSettingsCalibration' +import { RobotSettingsAdvanced } from '/app/organisms/Desktop/Devices/RobotSettings/RobotSettingsAdvanced' +import { RobotSettingsNetworking } from '/app/organisms/Desktop/Devices/RobotSettings/RobotSettingsNetworking' +import { RobotSettingsFeatureFlags } from '/app/organisms/Desktop/Devices/RobotSettings/RobotSettingsFeatureFlags' +import { ReachableBanner } from '/app/organisms/Desktop/Devices/ReachableBanner' + +import type { DesktopRouteParams, RobotSettingsTab } from '/app/App/types' + +export function RobotSettings(): JSX.Element | null { + const { t } = useTranslation('device_settings') + const { robotName, robotSettingsTab } = useParams< + keyof DesktopRouteParams + >() as DesktopRouteParams + const robot = useRobot(robotName) + const isCalibrationDisabled = robot?.status !== CONNECTABLE + const isNetworkingDisabled = robot?.status === UNREACHABLE + const [showRobotBusyBanner, setShowRobotBusyBanner] = useState(false) + const robotUpdateSession = useSelector(getRobotUpdateSession) + + const updateRobotStatus = (isRobotBusy: boolean): void => { + if (isRobotBusy) setShowRobotBusyBanner(true) + } + + const robotSettingsContentByTab: { + [K in RobotSettingsTab]: JSX.Element + } = { + calibration: ( + + ), + networking: ( + + ), + advanced: ( + + ), + 'feature-flags': , + } + + const devToolsOn = useSelector(getDevtoolsEnabled) + + if ( + (robot == null || + robot?.status === UNREACHABLE || + (robot?.status === REACHABLE && robot?.serverHealthStatus !== 'ok')) && + robotUpdateSession == null + ) { + return + } + const cannotViewCalibration = + robotSettingsTab === 'calibration' && isCalibrationDisabled + const cannotViewFeatureFlags = + robotSettingsTab === 'feature-flags' && !devToolsOn + if (cannotViewCalibration || cannotViewFeatureFlags) { + return + } + + const robotSettingsContent = robotSettingsContentByTab[robotSettingsTab] ?? ( + // default to the calibration tab if no tab or nonexistent tab is passed as a param + + ) + + return ( + + + + + {t('robot_settings')} + + {robot != null && ( + + + + )} + {showRobotBusyBanner && ( + + + {t('some_robot_controls_are_not_available')} + + + )} + + + + + {devToolsOn ? ( + + ) : null} + + + + + + {robotSettingsContent} + + + + + ) +} diff --git a/app/src/pages/Desktop/Labware/__tests__/Labware.test.tsx b/app/src/pages/Desktop/Labware/__tests__/Labware.test.tsx new file mode 100644 index 00000000000..40cfb02c57c --- /dev/null +++ b/app/src/pages/Desktop/Labware/__tests__/Labware.test.tsx @@ -0,0 +1,154 @@ +import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' +import { fireEvent, screen } from '@testing-library/react' +import { MemoryRouter } from 'react-router-dom' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { + useTrackEvent, + ANALYTICS_OPEN_LABWARE_CREATOR_FROM_BOTTOM_OF_LABWARE_LIBRARY_LIST, +} from '/app/redux/analytics' +import { LabwareCard } from '/app/organisms/Desktop/Labware/LabwareCard' +import { AddCustomLabwareSlideout } from '/app/organisms/Desktop/Labware/AddCustomLabwareSlideout' +import { useToaster } from '/app/organisms/ToasterOven' +import { useLabwareFailure, useNewLabwareName } from '../hooks' +import { Labware } from '..' +import { useAllLabware } from '/app/local-resources/labware' +import { mockDefinition } from '/app/redux/custom-labware/__fixtures__' + +vi.mock('/app/organisms/Desktop/Labware/LabwareCard') +vi.mock('/app/organisms/Desktop/Labware/AddCustomLabwareSlideout') +vi.mock('/app/organisms/ToasterOven') +vi.mock('../hooks') +vi.mock('/app/redux/analytics') +vi.mock('/app/local-resources/labware') + +const mockTrackEvent = vi.fn() +const mockMakeSnackbar = vi.fn() +const mockMakeToast = vi.fn() +const mockEatToast = vi.fn() + +const render = () => { + return renderWithProviders( + + + , + { + i18nInstance: i18n, + } + ) +} + +describe('Labware', () => { + beforeEach(() => { + vi.mocked(useTrackEvent).mockReturnValue(mockTrackEvent) + vi.mocked(LabwareCard).mockReturnValue(
    Mock Labware Card
    ) + vi.mocked(useAllLabware).mockReturnValue([{ definition: mockDefinition }]) + vi.mocked(useLabwareFailure).mockReturnValue({ + labwareFailureMessage: null, + clearLabwareFailure: vi.fn(), + }) + vi.mocked(useNewLabwareName).mockReturnValue({ + newLabwareName: null, + clearLabwareName: vi.fn(), + }) + vi.mocked(useToaster).mockReturnValue({ + makeSnackbar: mockMakeSnackbar, + makeToast: mockMakeToast, + eatToast: mockEatToast, + }) + }) + afterEach(() => { + vi.resetAllMocks() + }) + + it('renders correct title, import button and labware cards', () => { + render() + screen.getByText('labware') + screen.getByText('Mock Labware Card') + screen.getByRole('button', { name: 'Import' }) + screen.getByText('Category') + screen.getByText('All') + screen.getByText('Sort by') + expect(screen.getByTestId('sortBy-label')).toHaveTextContent('Alphabetical') + }) + it('renders AddCustomLabware slideout when import button is clicked', () => { + render() + const importButton = screen.getByRole('button', { name: 'Import' }) + fireEvent.click(importButton) + expect(vi.mocked(AddCustomLabwareSlideout)).toHaveBeenCalled() + }) + it('renders footer with labware creator link', () => { + render() + screen.getByText('Create a new labware definition') + const btn = screen.getByRole('link', { name: 'Open Labware Creator' }) + fireEvent.click(btn) + expect(mockTrackEvent).toHaveBeenCalledWith({ + name: ANALYTICS_OPEN_LABWARE_CREATOR_FROM_BOTTOM_OF_LABWARE_LIBRARY_LIST, + properties: {}, + }) + }) + it('renders error toast if there is a failure', () => { + vi.mocked(useLabwareFailure).mockReturnValue({ + labwareFailureMessage: 'mock failure message', + clearLabwareFailure: vi.fn(), + }) + render() + expect(mockMakeToast).toBeCalledWith( + 'mock failure message', + 'error', + expect.any(Object) + ) + }) + it('renders success toast if there is a new labware name', () => { + vi.mocked(useNewLabwareName).mockReturnValue({ + newLabwareName: 'mock filename', + clearLabwareName: vi.fn(), + }) + render() + expect(mockMakeToast).toBeCalledWith( + 'mock filename imported.', + 'success', + expect.any(Object) + ) + }) + it('renders filter by menu when it is clicked', () => { + render() + const filter = screen.getByText('All') + fireEvent.click(filter) + screen.getByRole('button', { name: 'All' }) + screen.getByRole('button', { name: 'Well Plate' }) + screen.getByRole('button', { name: 'Tip Rack' }) + screen.getByRole('button', { name: 'Tube Rack' }) + screen.getByRole('button', { name: 'Reservoir' }) + screen.getByRole('button', { name: 'Aluminum Block' }) + screen.getByRole('button', { name: 'Adapter' }) + screen.getByRole('button', { name: 'Lid' }) + screen.getByRole('button', { name: 'Custom Labware' }) + }) + it('renders changes filter menu button when an option is selected', () => { + render() + const filter = screen.getByText('All') + fireEvent.click(filter) + const wellPlate = screen.getByRole('button', { name: 'Well Plate' }) + fireEvent.click(wellPlate) + screen.getByText('Well Plate') + }) + it('renders sort by menu when sort is clicked', () => { + render() + const sort = screen.getByText('Alphabetical') + fireEvent.click(sort) + screen.getByRole('button', { name: 'Alphabetical' }) + screen.getByRole('button', { name: 'Reverse alphabetical' }) + }) + + it('renders selected sort by menu when one menu is clicked', () => { + render() + const sort = screen.getByText('Alphabetical') + fireEvent.click(sort) + const reverse = screen.getByRole('button', { name: 'Reverse alphabetical' }) + fireEvent.click(reverse) + expect(screen.getByTestId('sortBy-label')).toHaveTextContent( + 'Reverse alphabetical' + ) + }) +}) diff --git a/app/src/pages/Desktop/Labware/__tests__/hooks.test.tsx b/app/src/pages/Desktop/Labware/__tests__/hooks.test.tsx new file mode 100644 index 00000000000..5fe55c260dc --- /dev/null +++ b/app/src/pages/Desktop/Labware/__tests__/hooks.test.tsx @@ -0,0 +1,227 @@ +import type * as React from 'react' +import { Provider } from 'react-redux' +import { createStore } from 'redux' +import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' +import { renderHook } from '@testing-library/react' +import { i18n } from '/app/i18n' +import { I18nextProvider } from 'react-i18next' +import { getAllDefs } from '/app/local-resources/labware/utils/getAllDefs' + +import { + getValidCustomLabware, + getAddLabwareFailure, + getAddNewLabwareName, +} from '/app/redux/custom-labware' +import { + mockDefinition, + mockValidLabware, +} from '/app/redux/custom-labware/__fixtures__' + +import { useLabwareFailure, useNewLabwareName } from '../hooks' +import { useAllLabware } from '/app/local-resources/labware' + +import type { Store } from 'redux' +import type { State } from '/app/redux/types' +import type { FailedLabwareFile } from '/app/redux/custom-labware/types' + +vi.mock('/app/redux/custom-labware') +vi.mock('/app/local-resources/labware/utils/getAllDefs') + +describe('useAllLabware hook', () => { + const store: Store = createStore(vi.fn(), {}) + beforeEach(() => { + vi.mocked(getAllDefs).mockReturnValue([mockDefinition]) + vi.mocked(getValidCustomLabware).mockReturnValue([mockValidLabware]) + store.dispatch = vi.fn() + }) + afterEach(() => { + vi.restoreAllMocks() + }) + + it('should return object with only definition and modified date', () => { + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => {children} + const { result } = renderHook(() => useAllLabware('reverse', 'all'), { + wrapper, + }) + const labware1 = result.current[0] + const labware2 = result.current[1] + + expect(labware1.definition).toBe(mockDefinition) + expect(labware2.modified).toBe(mockValidLabware.modified) + expect(labware2.definition).toBe(mockValidLabware.definition) + }) + it('should return alphabetically sorted list', () => { + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => {children} + const { result } = renderHook(() => useAllLabware('alphabetical', 'all'), { + wrapper, + }) + const labware1 = result.current[0] + const labware2 = result.current[1] + + expect(labware2.definition).toBe(mockDefinition) + expect(labware1.modified).toBe(mockValidLabware.modified) + expect(labware1.definition).toBe(mockValidLabware.definition) + }) + it('should return no labware if not the right filter', () => { + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => {children} + const { result } = renderHook(() => useAllLabware('reverse', 'reservoir'), { + wrapper, + }) + const labware1 = result.current[0] + const labware2 = result.current[1] + + expect(labware1).toBe(undefined) + expect(labware2).toBe(undefined) + }) + it('should return labware with wellPlate filter', () => { + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => {children} + const { result } = renderHook(() => useAllLabware('reverse', 'wellPlate'), { + wrapper, + }) + const labware1 = result.current[0] + const labware2 = result.current[1] + + expect(labware1.definition).toBe(mockDefinition) + expect(labware2.modified).toBe(mockValidLabware.modified) + expect(labware2.definition).toBe(mockValidLabware.definition) + }) + it('should return custom labware with customLabware filter', () => { + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => {children} + const { result } = renderHook( + () => useAllLabware('alphabetical', 'customLabware'), + { + wrapper, + } + ) + const labware1 = result.current[0] + const labware2 = result.current[1] + + expect(labware1).toBe(mockValidLabware) + expect(labware2).toBeUndefined() + }) +}) + +describe('useLabwareFailure hook', () => { + const store: Store = createStore(vi.fn(), {}) + beforeEach(() => { + vi.mocked(getAddLabwareFailure).mockReturnValue({ + file: { + type: 'INVALID_LABWARE_FILE', + filename: '123', + } as FailedLabwareFile, + errorMessage: null, + }) + store.dispatch = vi.fn() + }) + afterEach(() => { + vi.restoreAllMocks() + }) + it('should return invalid labware definition', () => { + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => ( + + {children} + + ) + const { result } = renderHook(useLabwareFailure, { wrapper }) + const errorMessage = result.current.labwareFailureMessage + expect(errorMessage).toBe('Error importing 123. Invalid labware definition') + }) + it('should return duplicate labware definition', () => { + vi.mocked(getAddLabwareFailure).mockReturnValue({ + file: { + type: 'DUPLICATE_LABWARE_FILE', + filename: '123', + } as FailedLabwareFile, + errorMessage: null, + }) + + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => ( + + {children} + + ) + const { result } = renderHook(useLabwareFailure, { wrapper }) + const errorMessage = result.current.labwareFailureMessage + + expect(errorMessage).toBe( + 'Error importing 123. Duplicate labware definition' + ) + }) + it('should return opentrons labware definition', () => { + vi.mocked(getAddLabwareFailure).mockReturnValue({ + file: { + type: 'OPENTRONS_LABWARE_FILE', + filename: '123', + } as FailedLabwareFile, + errorMessage: null, + }) + + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => ( + + {children} + + ) + const { result } = renderHook(useLabwareFailure, { wrapper }) + const errorMessage = result.current.labwareFailureMessage + + expect(errorMessage).toBe( + 'Error importing 123. Opentrons labware definition' + ) + }) + it('should return unable to upload labware definition', () => { + vi.mocked(getAddLabwareFailure).mockReturnValue({ + file: null, + errorMessage: 'error', + }) + + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => ( + + {children} + + ) + const { result } = renderHook(useLabwareFailure, { wrapper }) + const errorMessage = result.current.labwareFailureMessage + + expect(errorMessage).toBe('Unable to upload file') + }) +}) + +describe('useNewLabwareName hook', () => { + const store: Store = createStore(vi.fn(), {}) + beforeEach(() => { + vi.mocked(getAddNewLabwareName).mockReturnValue({ + filename: 'mock_filename', + }) + store.dispatch = vi.fn() + }) + afterEach(() => { + vi.restoreAllMocks() + }) + + it('should return filename as a string', () => { + const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ + children, + }) => {children} + const { result } = renderHook(useNewLabwareName, { wrapper }) + const filename = result.current.newLabwareName + expect(filename).toBe('mock_filename') + }) +}) diff --git a/app/src/pages/Desktop/Labware/hooks.tsx b/app/src/pages/Desktop/Labware/hooks.tsx new file mode 100644 index 00000000000..f6cac84c9f1 --- /dev/null +++ b/app/src/pages/Desktop/Labware/hooks.tsx @@ -0,0 +1,52 @@ +import { useSelector, useDispatch } from 'react-redux' +import { useTranslation } from 'react-i18next' +import { + getAddLabwareFailure, + clearAddCustomLabwareFailure, + getAddNewLabwareName, + clearNewLabwareName, +} from '/app/redux/custom-labware' +import type { Dispatch } from '/app/redux/types' + +export function useLabwareFailure(): { + labwareFailureMessage: string | null + clearLabwareFailure: () => unknown +} { + const { t } = useTranslation(['labware_landing', 'branded']) + const dispatch = useDispatch() + const labwareFailure = useSelector(getAddLabwareFailure) + + let labwareFailureMessage = null + if (labwareFailure.file != null || labwareFailure.errorMessage != null) { + const failedFile = labwareFailure.file + let errorMessage = t('unable_to_upload') + if (failedFile?.type === 'INVALID_LABWARE_FILE') { + errorMessage = t('invalid_labware_def') + } else if (failedFile?.type === 'DUPLICATE_LABWARE_FILE') { + errorMessage = t('duplicate_labware_def') + } else if (failedFile?.type === 'OPENTRONS_LABWARE_FILE') { + errorMessage = t('branded:opentrons_labware_def') + } + labwareFailureMessage = + failedFile != null + ? `${t('error_importing_file', { + filename: failedFile.filename, + })} ${errorMessage}` + : errorMessage + } + const clearLabwareFailure = (): unknown => + dispatch(clearAddCustomLabwareFailure()) + + return { labwareFailureMessage, clearLabwareFailure } +} + +export function useNewLabwareName(): { + newLabwareName: string | null + clearLabwareName: () => unknown +} { + const dispatch = useDispatch() + const newLabwareName = useSelector(getAddNewLabwareName).filename + const clearLabwareName = (): unknown => dispatch(clearNewLabwareName()) + + return { newLabwareName, clearLabwareName } +} diff --git a/app/src/pages/Desktop/Labware/index.tsx b/app/src/pages/Desktop/Labware/index.tsx new file mode 100644 index 00000000000..83f9dd94f3f --- /dev/null +++ b/app/src/pages/Desktop/Labware/index.tsx @@ -0,0 +1,319 @@ +import { useState, useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import startCase from 'lodash/startCase' +import { css } from 'styled-components' +import { useDispatch } from 'react-redux' + +import { + ALIGN_CENTER, + ALIGN_FLEX_END, + BORDERS, + Box, + COLORS, + CURSOR_POINTER, + DIRECTION_COLUMN, + DIRECTION_ROW, + DropdownMenu, + ERROR_TOAST, + Flex, + Icon, + JUSTIFY_SPACE_BETWEEN, + LegacyStyledText, + Link, + MenuItem, + POSITION_ABSOLUTE, + PrimaryButton, + SecondaryButton, + SPACING, + SUCCESS_TOAST, + TYPOGRAPHY, + useOnClickOutside, +} from '@opentrons/components' +import { LabwareCreator } from '@opentrons/labware-library' +import { + useTrackEvent, + ANALYTICS_OPEN_LABWARE_CREATOR_FROM_BOTTOM_OF_LABWARE_LIBRARY_LIST, +} from '/app/redux/analytics' +import { addCustomLabwareFileFromCreator } from '/app/redux/custom-labware' +import { LabwareCard } from '/app/organisms/Desktop/Labware/LabwareCard' +import { AddCustomLabwareSlideout } from '/app/organisms/Desktop/Labware/AddCustomLabwareSlideout' +import { LabwareDetails } from '/app/organisms/Desktop/Labware/LabwareDetails' +import { useToaster } from '/app/organisms/ToasterOven' +import { useFeatureFlag } from '/app/redux/config' +import { useLabwareFailure, useNewLabwareName } from './hooks' +import { useAllLabware } from '/app/local-resources/labware' + +import type { DropdownOption } from '@opentrons/components' +import type { + LabwareFilter, + LabwareSort, + LabwareDefAndDate, +} from '/app/local-resources/labware' + +const LABWARE_CREATOR_HREF = 'https://labware.opentrons.com/create/' +const labwareDisplayCategoryFilters: LabwareFilter[] = [ + 'all', + 'adapter', + 'aluminumBlock', + 'customLabware', + 'lid', + 'reservoir', + 'tipRack', + 'tubeRack', + 'wellPlate', +] + +const FILTER_OPTIONS: DropdownOption[] = labwareDisplayCategoryFilters.map( + category => ({ + name: startCase(category), + value: category, + }) +) + +const SORT_BY_BUTTON_STYLE = css` + background-color: ${COLORS.transparent}; + cursor: ${CURSOR_POINTER}; + &:hover { + background-color: ${COLORS.grey30}; + } + &:active, + &:focus { + background-color: ${COLORS.grey40}; + } +` + +export function Labware(): JSX.Element { + const { t } = useTranslation(['labware_landing', 'shared']) + const enableLabwareCreator = useFeatureFlag('enableLabwareCreator') + const [sortBy, setSortBy] = useState('alphabetical') + const [showSortByMenu, setShowSortByMenu] = useState(false) + const toggleSetShowSortByMenu = (): void => { + setShowSortByMenu(!showSortByMenu) + } + const dispatch = useDispatch() + const [showLC, setShowLC] = useState(false) + const trackEvent = useTrackEvent() + const [filterBy, setFilterBy] = useState('all') + const { makeToast } = useToaster() + + const labware = useAllLabware(sortBy, filterBy) + const { labwareFailureMessage, clearLabwareFailure } = useLabwareFailure() + const { newLabwareName, clearLabwareName } = useNewLabwareName() + const [showAddLabwareSlideout, setShowAddLabwareSlideout] = useState(false) + + const [ + currentLabwareDef, + setCurrentLabwareDef, + ] = useState(null) + + const sortOverflowWrapperRef = useOnClickOutside({ + onClickOutside: () => { + setShowSortByMenu(false) + }, + }) + useEffect(() => { + if (labwareFailureMessage != null) { + setShowAddLabwareSlideout(false) + makeToast(labwareFailureMessage, ERROR_TOAST, { + closeButton: true, + onClose: clearLabwareFailure, + }) + } else if (newLabwareName != null) { + setShowAddLabwareSlideout(false) + makeToast( + t('imported', { filename: newLabwareName }) as string, + SUCCESS_TOAST, + { + closeButton: true, + onClose: clearLabwareName, + } + ) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [labwareFailureMessage, newLabwareName]) + + return ( + <> + {showLC ? ( + { + setShowLC(false) + }} + save={(file: string) => { + dispatch(addCustomLabwareFileFromCreator(file)) + }} + isOnRunApp + /> + ) : null} + + + + {t('labware')} + + + { + setShowAddLabwareSlideout(true) + }} + > + {t('import')} + + {enableLabwareCreator ? ( + { + setShowLC(true) + }} + > + Open Labware Creator + + ) : null} + + + + { + setFilterBy(value as LabwareFilter) + }} + title={t('category')} + /> + + + {t('shared:sort_by')} + + + + {sortBy === 'alphabetical' + ? t('shared:alphabetical') + : t('shared:reverse')} + + + + + {showSortByMenu && ( + + { + setSortBy('alphabetical') + setShowSortByMenu(false) + }} + > + {t('shared:alphabetical')} + + { + setSortBy('reverse') + setShowSortByMenu(false) + }} + > + {t('shared:reverse')} + + + )} + + + {labware.map((labware, index) => ( + { + setCurrentLabwareDef(labware) + }} + /> + ))} + + + + {t('create_new_def')} + + + { + trackEvent({ + name: ANALYTICS_OPEN_LABWARE_CREATOR_FROM_BOTTOM_OF_LABWARE_LIBRARY_LIST, + properties: {}, + }) + }} + href={LABWARE_CREATOR_HREF} + css={TYPOGRAPHY.darkLinkLabelSemiBold} + > + {t('open_labware_creator')} + + + + + {showAddLabwareSlideout && ( + { + setShowAddLabwareSlideout(false) + }} + /> + )} + {currentLabwareDef != null && ( + { + setCurrentLabwareDef(null) + }} + /> + )} + + ) +} diff --git a/app/src/pages/Desktop/Protocols/ProtocolDetails/ProtocolTimeline.tsx b/app/src/pages/Desktop/Protocols/ProtocolDetails/ProtocolTimeline.tsx new file mode 100644 index 00000000000..e781987b8b6 --- /dev/null +++ b/app/src/pages/Desktop/Protocols/ProtocolDetails/ProtocolTimeline.tsx @@ -0,0 +1,35 @@ +import { useEffect } from 'react' +import { useParams } from 'react-router-dom' +import { useDispatch, useSelector } from 'react-redux' +import { Icon, Box, SPACING } from '@opentrons/components' +import { fetchProtocols, getStoredProtocol } from '/app/redux/protocol-storage' +import { ProtocolTimelineScrubber } from '/app/organisms/Desktop/ProtocolTimelineScrubber' + +import type { Dispatch, State } from '/app/redux/types' +import type { DesktopRouteParams } from '/app/App/types' + +export function ProtocolTimeline(): JSX.Element { + const { protocolKey } = useParams< + keyof DesktopRouteParams + >() as DesktopRouteParams + const dispatch = useDispatch() + const storedProtocol = useSelector((state: State) => + getStoredProtocol(state, protocolKey) + ) + + useEffect(() => { + dispatch(fetchProtocols()) + }, []) + + return storedProtocol != null && storedProtocol.mostRecentAnalysis != null ? ( + + + + ) : ( + + ) +} diff --git a/app/src/pages/Desktop/Protocols/ProtocolDetails/__tests__/ProtocolDetails.test.tsx b/app/src/pages/Desktop/Protocols/ProtocolDetails/__tests__/ProtocolDetails.test.tsx new file mode 100644 index 00000000000..e9e7b273d54 --- /dev/null +++ b/app/src/pages/Desktop/Protocols/ProtocolDetails/__tests__/ProtocolDetails.test.tsx @@ -0,0 +1,80 @@ +import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' +import { screen } from '@testing-library/react' +import { Route, MemoryRouter, Routes } from 'react-router-dom' +import { when } from 'vitest-when' +import { renderWithProviders } from '/app/__testing-utils__' + +import { i18n } from '/app/i18n' +import { getStoredProtocol } from '/app/redux/protocol-storage' +import { storedProtocolData } from '/app/redux/protocol-storage/__fixtures__' +import { ProtocolDetails as ProtocolDetailsContents } from '/app/organisms/Desktop/ProtocolDetails' + +import { ProtocolDetails } from '../' + +import type { State } from '/app/redux/types' + +const mockProtocolKey = 'protocolKeyStub' + +vi.mock('/app/redux/protocol-storage') +vi.mock('/app/organisms/Desktop/ProtocolDetails') + +const MOCK_STATE: State = { + protocolStorage: { + addFailureFile: null, + addFailureMessage: null, + filesByProtocolKey: { + protocolKeyStub: storedProtocolData, + }, + inProgressAnalysisProtocolKeys: [], + protocolKeys: [mockProtocolKey], + }, +} as any + +const render = (path = '/') => { + return renderWithProviders( + + + } /> + protocols
    } /> + + , + { + i18nInstance: i18n, + initialState: MOCK_STATE, + } + )[0] +} + +describe('ProtocolDetails', () => { + beforeEach(() => { + when(vi.mocked(getStoredProtocol)) + .calledWith(MOCK_STATE, mockProtocolKey) + .thenReturn(storedProtocolData) + }) + + afterEach(() => { + vi.resetAllMocks() + }) + + it('should render protocol details', () => { + render('/protocols/protocolKeyStub') + expect(vi.mocked(ProtocolDetailsContents)).toHaveBeenCalledWith( + { + protocolKey: storedProtocolData.protocolKey, + modified: storedProtocolData.modified, + mostRecentAnalysis: storedProtocolData.mostRecentAnalysis, + srcFileNames: storedProtocolData.srcFileNames, + srcFiles: storedProtocolData.srcFiles, + }, + {} + ) + }) + + it('should redirect to protocols landing if there is no protocol', () => { + when(vi.mocked(getStoredProtocol)) + .calledWith(MOCK_STATE, mockProtocolKey) + .thenReturn(null) + render('/protocols') + screen.getByText('protocols') + }) +}) diff --git a/app/src/pages/Desktop/Protocols/ProtocolDetails/index.tsx b/app/src/pages/Desktop/Protocols/ProtocolDetails/index.tsx new file mode 100644 index 00000000000..66402416da7 --- /dev/null +++ b/app/src/pages/Desktop/Protocols/ProtocolDetails/index.tsx @@ -0,0 +1,30 @@ +import { useEffect } from 'react' +import { useParams, Navigate } from 'react-router-dom' + +import { useDispatch, useSelector } from 'react-redux' +import { fetchProtocols, getStoredProtocol } from '/app/redux/protocol-storage' +import { ProtocolDetails as ProtocolDetailsContents } from '/app/organisms/Desktop/ProtocolDetails' + +import type { Dispatch, State } from '/app/redux/types' +import type { DesktopRouteParams } from '/app/App/types' + +export function ProtocolDetails(): JSX.Element { + const { protocolKey } = useParams< + keyof DesktopRouteParams + >() as DesktopRouteParams + + const dispatch = useDispatch() + const storedProtocol = useSelector((state: State) => + getStoredProtocol(state, protocolKey) + ) + + useEffect(() => { + dispatch(fetchProtocols()) + }, [dispatch]) + + return storedProtocol != null ? ( + + ) : ( + + ) +} diff --git a/app/src/pages/Desktop/Protocols/ProtocolsLanding/__tests__/ProtocolsLanding.test.tsx b/app/src/pages/Desktop/Protocols/ProtocolsLanding/__tests__/ProtocolsLanding.test.tsx new file mode 100644 index 00000000000..8f1e87fef38 --- /dev/null +++ b/app/src/pages/Desktop/Protocols/ProtocolsLanding/__tests__/ProtocolsLanding.test.tsx @@ -0,0 +1,32 @@ +import { vi, it, describe } from 'vitest' +import { screen } from '@testing-library/react' +import { renderWithProviders } from '/app/__testing-utils__' + +import { ProtocolsEmptyState } from '/app/organisms/Desktop/ProtocolsLanding/ProtocolsEmptyState' +import { getStoredProtocols } from '/app/redux/protocol-storage' +import { storedProtocolData } from '/app/redux/protocol-storage/__fixtures__' +import { ProtocolList } from '/app/organisms/Desktop/ProtocolsLanding/ProtocolList' +import { ProtocolsLanding } from '..' + +vi.mock('/app/redux/protocol-storage') +vi.mock('/app/organisms/Desktop/ProtocolsLanding/ProtocolsEmptyState') +vi.mock('/app/organisms/Desktop/ProtocolsLanding/ProtocolList') + +const render = () => { + return renderWithProviders()[0] +} + +describe('ProtocolsLanding', () => { + it('renders the protocol list component', () => { + vi.mocked(getStoredProtocols).mockReturnValue([storedProtocolData]) + vi.mocked(ProtocolList).mockReturnValue(
    mock protocol list
    ) + render() + screen.getByText('mock protocol list') + }) + it('renders the empty state component', () => { + vi.mocked(getStoredProtocols).mockReturnValue([]) + vi.mocked(ProtocolsEmptyState).mockReturnValue(
    mock empty state
    ) + render() + screen.getByText('mock empty state') + }) +}) diff --git a/app/src/pages/Desktop/Protocols/ProtocolsLanding/index.tsx b/app/src/pages/Desktop/Protocols/ProtocolsLanding/index.tsx new file mode 100644 index 00000000000..b88ca50d90f --- /dev/null +++ b/app/src/pages/Desktop/Protocols/ProtocolsLanding/index.tsx @@ -0,0 +1,23 @@ +import { useEffect } from 'react' +import { useDispatch, useSelector } from 'react-redux' +import { fetchProtocols, getStoredProtocols } from '/app/redux/protocol-storage' +import { ProtocolsEmptyState } from '/app/organisms/Desktop/ProtocolsLanding/ProtocolsEmptyState' +import { ProtocolList } from '/app/organisms/Desktop/ProtocolsLanding/ProtocolList' + +import type { Dispatch, State } from '/app/redux/types' + +export function ProtocolsLanding(): JSX.Element { + const dispatch = useDispatch() + const storedProtocols = useSelector((state: State) => + getStoredProtocols(state) + ) + useEffect(() => { + dispatch(fetchProtocols()) + }, [dispatch]) + + return storedProtocols.length > 0 ? ( + + ) : ( + + ) +} diff --git a/app/src/pages/Devices/CalibrationDashboard/__tests__/CalibrationDashboard.test.tsx b/app/src/pages/Devices/CalibrationDashboard/__tests__/CalibrationDashboard.test.tsx deleted file mode 100644 index 7039e9836b5..00000000000 --- a/app/src/pages/Devices/CalibrationDashboard/__tests__/CalibrationDashboard.test.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import * as React from 'react' -import { vi, describe, it, beforeEach } from 'vitest' -import { screen } from '@testing-library/react' -import { MemoryRouter, Route, Routes } from 'react-router-dom' - -import { renderWithProviders } from '../../../../__testing-utils__' -import { i18n } from '../../../../i18n' -import { CalibrationDashboard } from '..' - -import { - useCalibrationTaskList, - useAttachedPipettes, -} from '../../../../organisms/Devices/hooks' -import { useDashboardCalibratePipOffset } from '../hooks/useDashboardCalibratePipOffset' -import { useDashboardCalibrateTipLength } from '../hooks/useDashboardCalibrateTipLength' -import { useDashboardCalibrateDeck } from '../hooks/useDashboardCalibrateDeck' -import { expectedTaskList } from '../../../../organisms/Devices/hooks/__fixtures__/taskListFixtures' -import { mockLeftProtoPipette } from '../../../../redux/pipettes/__fixtures__' -import { useNotifyAllRunsQuery } from '../../../../resources/runs' - -vi.mock('../../../../organisms/Devices/hooks') -vi.mock('../hooks/useDashboardCalibratePipOffset') -vi.mock('../hooks/useDashboardCalibrateTipLength') -vi.mock('../hooks/useDashboardCalibrateDeck') -vi.mock('../../../../resources/runs') - -const render = (path = '/') => { - return renderWithProviders( - - - } - /> - - , - { - i18nInstance: i18n, - } - ) -} - -describe('CalibrationDashboard', () => { - beforeEach(() => { - vi.mocked(useCalibrationTaskList).mockReturnValue(expectedTaskList) - vi.mocked(useDashboardCalibratePipOffset).mockReturnValue([() => {}, null]) - vi.mocked(useDashboardCalibrateTipLength).mockReturnValue([() => {}, null]) - vi.mocked(useDashboardCalibrateDeck).mockReturnValue([ - () => {}, - null, - false, - ]) - vi.mocked(useAttachedPipettes).mockReturnValue({ - left: mockLeftProtoPipette, - right: null, - }) - vi.mocked(useNotifyAllRunsQuery).mockReturnValue({} as any) - }) - - it('renders a robot calibration dashboard title', () => { - render('/devices/otie/robot-settings/calibration/dashboard') - - screen.getByText(`otie Calibration Dashboard`) - }) -}) diff --git a/app/src/pages/Devices/CalibrationDashboard/hooks/useDashboardCalibrateDeck.tsx b/app/src/pages/Devices/CalibrationDashboard/hooks/useDashboardCalibrateDeck.tsx deleted file mode 100644 index 5a99adc3df8..00000000000 --- a/app/src/pages/Devices/CalibrationDashboard/hooks/useDashboardCalibrateDeck.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import * as React from 'react' -import { createPortal } from 'react-dom' -import { useSelector } from 'react-redux' -import { useTranslation } from 'react-i18next' -import { ModalShell } from '@opentrons/components' - -import { getTopPortalEl } from '../../../../App/portal' -import { WizardHeader } from '../../../../molecules/WizardHeader' -import { CalibrateDeck } from '../../../../organisms/CalibrateDeck' -import { LoadingState } from '../../../../organisms/CalibrationPanels' -import * as RobotApi from '../../../../redux/robot-api' -import * as Sessions from '../../../../redux/sessions' -import { getDeckCalibrationSession } from '../../../../redux/sessions/deck-calibration/selectors' - -import type { State } from '../../../../redux/types' -import type { DeckCalibrationSession } from '../../../../redux/sessions' -import type { SessionCommandString } from '../../../../redux/sessions/types' -import type { RequestState } from '../../../../redux/robot-api/types' - -// deck calibration commands for which the full page spinner should not appear -const spinnerCommandBlockList: SessionCommandString[] = [ - Sessions.sharedCalCommands.JOG, -] -export interface DashboardCalDeckInvokerProps { - invalidateHandler?: () => void -} -export type DashboardCalDeckInvoker = ( - props?: DashboardCalDeckInvokerProps -) => void - -export function useDashboardCalibrateDeck( - robotName: string -): [DashboardCalDeckInvoker, JSX.Element | null, boolean] { - const trackedRequestId = React.useRef(null) - const createRequestId = React.useRef(null) - const jogRequestId = React.useRef(null) - const exitBeforeDeckConfigCompletion = React.useRef(false) - const invalidateHandlerRef = React.useRef<(() => void) | undefined>() - const { t } = useTranslation('robot_calibration') - - const deckCalSession: DeckCalibrationSession | null = useSelector( - (state: State) => { - return getDeckCalibrationSession(state, robotName) - } - ) - - const [dispatchRequests] = RobotApi.useDispatchApiRequests( - dispatchedAction => { - if (dispatchedAction.type === Sessions.ENSURE_SESSION) { - createRequestId.current = - 'requestId' in dispatchedAction.meta - ? dispatchedAction.meta.requestId ?? null - : null - } else if ( - dispatchedAction.type === Sessions.CREATE_SESSION_COMMAND && - dispatchedAction.payload.command.command === - Sessions.sharedCalCommands.JOG - ) { - jogRequestId.current = - 'requestId' in dispatchedAction.meta - ? dispatchedAction.meta.requestId ?? null - : null - } else if ( - dispatchedAction.type !== Sessions.CREATE_SESSION_COMMAND || - !spinnerCommandBlockList.includes( - dispatchedAction.payload.command.command - ) - ) { - trackedRequestId.current = - 'meta' in dispatchedAction && 'requestId' in dispatchedAction.meta - ? dispatchedAction.meta.requestId ?? null - : null - } - } - ) - - const startingSession = - useSelector(state => - createRequestId.current - ? RobotApi.getRequestById(state, createRequestId.current) - : null - )?.status === RobotApi.PENDING - - const showSpinner = - useSelector(state => - trackedRequestId.current - ? RobotApi.getRequestById(state, trackedRequestId.current) - : null - )?.status === RobotApi.PENDING - - const isJogging = - useSelector((state: State) => - jogRequestId.current - ? RobotApi.getRequestById(state, jogRequestId.current) - : null - )?.status === RobotApi.PENDING - - const handleStartDashboardDeckCalSession: DashboardCalDeckInvoker = ( - props = {} - ) => { - invalidateHandlerRef.current = props.invalidateHandler - dispatchRequests( - Sessions.ensureSession(robotName, Sessions.SESSION_TYPE_DECK_CALIBRATION) - ) - } - - let Wizard: JSX.Element | null = createPortal( - startingSession ? ( - } - > - - - ) : ( - - ), - getTopPortalEl() - ) - - if (!(startingSession || deckCalSession != null)) Wizard = null - - return [ - handleStartDashboardDeckCalSession, - Wizard, - exitBeforeDeckConfigCompletion.current, - ] -} diff --git a/app/src/pages/Devices/CalibrationDashboard/index.tsx b/app/src/pages/Devices/CalibrationDashboard/index.tsx deleted file mode 100644 index b875918c25e..00000000000 --- a/app/src/pages/Devices/CalibrationDashboard/index.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import * as React from 'react' -import { useParams } from 'react-router-dom' -import { ApiHostProvider } from '@opentrons/react-api-client' -import { CalibrationTaskList } from '../../../organisms/CalibrationTaskList' -import { OPENTRONS_USB } from '../../../redux/discovery' -import { appShellRequestor } from '../../../redux/shell/remote' -import { useDashboardCalibrateDeck } from './hooks/useDashboardCalibrateDeck' -import { useDashboardCalibratePipOffset } from './hooks/useDashboardCalibratePipOffset' -import { useDashboardCalibrateTipLength } from './hooks/useDashboardCalibrateTipLength' -import { useRobot } from '../../../organisms/Devices/hooks' - -import type { DesktopRouteParams } from '../../../App/types' - -export function CalibrationDashboard(): JSX.Element { - const { robotName } = useParams< - keyof DesktopRouteParams - >() as DesktopRouteParams - const robot = useRobot(robotName) - const [ - dashboardOffsetCalLauncher, - DashboardOffsetCalWizard, - ] = useDashboardCalibratePipOffset(robotName) - const [ - dashboardTipLengthCalLauncher, - DashboardTipLengthCalWizard, - ] = useDashboardCalibrateTipLength(robotName) - const [ - dashboardDeckCalLauncher, - DashboardDeckCalWizard, - exitBeforeDeckConfigCompletion, - ] = useDashboardCalibrateDeck(robotName) - return ( - - - {DashboardDeckCalWizard} - {DashboardOffsetCalWizard} - {DashboardTipLengthCalWizard} - - ) -} diff --git a/app/src/pages/Devices/DeviceDetails/DeviceDetailsComponent.tsx b/app/src/pages/Devices/DeviceDetails/DeviceDetailsComponent.tsx deleted file mode 100644 index 513bccb9874..00000000000 --- a/app/src/pages/Devices/DeviceDetails/DeviceDetailsComponent.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import * as React from 'react' -import { useEstopQuery } from '@opentrons/react-api-client' -import { - ALIGN_CENTER, - Box, - COLORS, - BORDERS, - DIRECTION_COLUMN, - Flex, - SPACING, -} from '@opentrons/components' - -import { DeviceDetailsDeckConfiguration } from '../../../organisms/DeviceDetailsDeckConfiguration' -import { RobotOverview } from '../../../organisms/Devices/RobotOverview' -import { InstrumentsAndModules } from '../../../organisms/Devices/InstrumentsAndModules' -import { RecentProtocolRuns } from '../../../organisms/Devices/RecentProtocolRuns' -import { EstopBanner } from '../../../organisms/Devices/EstopBanner' -import { DISENGAGED, useEstopContext } from '../../../organisms/EmergencyStop' -import { useIsFlex } from '../../../organisms/Devices/hooks' - -interface DeviceDetailsComponentProps { - robotName: string -} - -export function DeviceDetailsComponent({ - robotName, -}: DeviceDetailsComponentProps): JSX.Element { - const isFlex = useIsFlex(robotName) - const { data: estopStatus, error: estopError } = useEstopQuery({ - enabled: isFlex, - }) - const { isEmergencyStopModalDismissed } = useEstopContext() - - return ( - - {isFlex && - estopStatus?.data.status !== DISENGAGED && - estopError == null && - isEmergencyStopModalDismissed ? ( - - - - ) : null} - - - - - {isFlex ? : null} - - - ) -} diff --git a/app/src/pages/Devices/DeviceDetails/__tests__/DeviceDetails.test.tsx b/app/src/pages/Devices/DeviceDetails/__tests__/DeviceDetails.test.tsx deleted file mode 100644 index 7c45cf32baa..00000000000 --- a/app/src/pages/Devices/DeviceDetails/__tests__/DeviceDetails.test.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import * as React from 'react' -import { vi, it, describe, expect, beforeEach } from 'vitest' -import { when } from 'vitest-when' -import { screen } from '@testing-library/react' -import { MemoryRouter, Route, Routes } from 'react-router-dom' - -import { renderWithProviders } from '../../../../__testing-utils__' - -import { i18n } from '../../../../i18n' -import { - useRobot, - useSyncRobotClock, -} from '../../../../organisms/Devices/hooks' -import { InstrumentsAndModules } from '../../../../organisms/Devices/InstrumentsAndModules' -import { RecentProtocolRuns } from '../../../../organisms/Devices/RecentProtocolRuns' -import { RobotOverview } from '../../../../organisms/Devices/RobotOverview' -import { getScanning } from '../../../../redux/discovery' -import { mockConnectableRobot } from '../../../../redux/discovery/__fixtures__' -import { DeviceDetails } from '..' - -import type { State } from '../../../../redux/types' - -vi.mock('../../../../organisms/Devices/hooks') -vi.mock('../../../../organisms/Devices/InstrumentsAndModules') -vi.mock('../../../../organisms/Devices/RecentProtocolRuns') -vi.mock('../../../../organisms/Devices/RobotOverview') -vi.mock('../../../../redux/discovery') - -const render = (path = '/') => { - return renderWithProviders( - - - } /> - devices page} /> - - , - { - i18nInstance: i18n, - } - ) -} - -describe('DeviceDetails', () => { - beforeEach(() => { - when(useRobot).calledWith('otie').thenReturn(null) - when(getScanning) - .calledWith({} as State) - .thenReturn(false) - }) - - it('redirects to devices page when a robot is not found and not scanning', () => { - render('/devices/otie') - - screen.getByText('devices page') - }) - - it('renders null when a robot is not found and discovery client is scanning', () => { - when(getScanning) - .calledWith({} as State) - .thenReturn(true) - render('/devices/otie') - - expect(vi.mocked(RobotOverview)).not.toHaveBeenCalled() - expect(vi.mocked(InstrumentsAndModules)).not.toHaveBeenCalled() - expect(vi.mocked(RecentProtocolRuns)).not.toHaveBeenCalled() - }) - - it('renders a RobotOverview when a robot is found and syncs clock', () => { - when(useRobot).calledWith('otie').thenReturn(mockConnectableRobot) - render('/devices/otie') - - expect(vi.mocked(RobotOverview)).toHaveBeenCalled() - expect(useSyncRobotClock).toHaveBeenCalledWith('otie') - }) - - it('renders InstrumentsAndModules when a robot is found', () => { - when(useRobot).calledWith('otie').thenReturn(mockConnectableRobot) - render('/devices/otie') - expect(vi.mocked(InstrumentsAndModules)).toHaveBeenCalled() - }) - - it('renders RecentProtocolRuns when a robot is found', () => { - when(useRobot).calledWith('otie').thenReturn(mockConnectableRobot) - render('/devices/otie') - expect(vi.mocked(RecentProtocolRuns)).toHaveBeenCalled() - }) -}) diff --git a/app/src/pages/Devices/DeviceDetails/__tests__/DeviceDetailsComponent.test.tsx b/app/src/pages/Devices/DeviceDetails/__tests__/DeviceDetailsComponent.test.tsx deleted file mode 100644 index 00e0c1eff9f..00000000000 --- a/app/src/pages/Devices/DeviceDetails/__tests__/DeviceDetailsComponent.test.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import * as React from 'react' -import { vi, it, describe, expect, beforeEach } from 'vitest' -import { when } from 'vitest-when' - -import { renderWithProviders } from '../../../../__testing-utils__' -import { useEstopQuery } from '@opentrons/react-api-client' - -import { i18n } from '../../../../i18n' -import { InstrumentsAndModules } from '../../../../organisms/Devices/InstrumentsAndModules' -import { RecentProtocolRuns } from '../../../../organisms/Devices/RecentProtocolRuns' -import { RobotOverview } from '../../../../organisms/Devices/RobotOverview' -import { DISENGAGED, NOT_PRESENT } from '../../../../organisms/EmergencyStop' -import { DeviceDetailsDeckConfiguration } from '../../../../organisms/DeviceDetailsDeckConfiguration' -import { useIsFlex } from '../../../../organisms/Devices/hooks' -import { DeviceDetailsComponent } from '../DeviceDetailsComponent' - -vi.mock('@opentrons/react-api-client') -vi.mock('../../../../organisms/Devices/hooks') -vi.mock('../../../../organisms/Devices/InstrumentsAndModules') -vi.mock('../../../../organisms/Devices/RecentProtocolRuns') -vi.mock('../../../../organisms/Devices/RobotOverview') -vi.mock('../../../../organisms/DeviceDetailsDeckConfiguration') -vi.mock('../../../../redux/discovery') - -const ROBOT_NAME = 'otie' -const mockEstopStatus = { - data: { - status: DISENGAGED, - leftEstopPhysicalStatus: DISENGAGED, - rightEstopPhysicalStatus: NOT_PRESENT, - }, -} - -const render = () => { - return renderWithProviders( - , - { - i18nInstance: i18n, - } - ) -} - -describe('DeviceDetailsComponent', () => { - beforeEach(() => { - vi.mocked(useEstopQuery).mockReturnValue({ data: mockEstopStatus } as any) - when(vi.mocked(useIsFlex)).calledWith(ROBOT_NAME).thenReturn(false) - }) - - it('renders a RobotOverview when a robot is found and syncs clock', () => { - render() - expect(vi.mocked(RobotOverview)).toHaveBeenCalledWith( - { - robotName: ROBOT_NAME, - }, - {} - ) - }) - - it('renders InstrumentsAndModules when a robot is found', () => { - render() - expect(vi.mocked(InstrumentsAndModules)).toHaveBeenCalledWith( - { - robotName: ROBOT_NAME, - }, - {} - ) - }) - - it('renders RecentProtocolRuns when a robot is found', () => { - render() - expect(vi.mocked(RecentProtocolRuns)).toHaveBeenCalledWith( - { - robotName: ROBOT_NAME, - }, - {} - ) - }) - - it('renders Deck Configuration when a robot is flex', () => { - when(vi.mocked(useIsFlex)).calledWith(ROBOT_NAME).thenReturn(true) - render() - expect(vi.mocked(DeviceDetailsDeckConfiguration)).toHaveBeenCalled() - }) - - it.todo('renders EstopBanner when estop is engaged') - // mockEstopStatus.data.status = PHYSICALLY_ENGAGED - // vi.mocked(useEstopQuery).mockReturnValue({ data: mockEstopStatus } as any) - // const { result } = renderHook(() => useEstopContext(), { wrapper }) - // result.current.setIsEmergencyStopModalDismissed(true) - // // act(() => result.current.setIsEmergencyStopModalDismissed(true)) - // const [{ getByText }] = render() - // getByText('mock EstopBanner') -}) diff --git a/app/src/pages/Devices/DeviceDetails/index.tsx b/app/src/pages/Devices/DeviceDetails/index.tsx deleted file mode 100644 index 962dd229d87..00000000000 --- a/app/src/pages/Devices/DeviceDetails/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import * as React from 'react' -import { useSelector } from 'react-redux' -import { Navigate, useParams } from 'react-router-dom' - -import { ApiHostProvider } from '@opentrons/react-api-client' - -import { useRobot, useSyncRobotClock } from '../../../organisms/Devices/hooks' -import { getScanning, OPENTRONS_USB } from '../../../redux/discovery' -import { appShellRequestor } from '../../../redux/shell/remote' -import { DeviceDetailsComponent } from './DeviceDetailsComponent' - -import type { DesktopRouteParams } from '../../../App/types' - -export function DeviceDetails(): JSX.Element | null { - const { robotName } = useParams< - keyof DesktopRouteParams - >() as DesktopRouteParams - const robot = useRobot(robotName) - const isScanning = useSelector(getScanning) - - useSyncRobotClock(robotName) - - if (robot == null && isScanning) return null - - return robot != null ? ( - // TODO(bh, 2023-05-31): substitute wrapped AppApiHostProvider that registers/authorizes - - - - ) : ( - - ) -} diff --git a/app/src/pages/Devices/DevicesLanding/index.tsx b/app/src/pages/Devices/DevicesLanding/index.tsx deleted file mode 100644 index 1b17556882a..00000000000 --- a/app/src/pages/Devices/DevicesLanding/index.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useSelector } from 'react-redux' -import partition from 'lodash/partition' - -import { - ALIGN_CENTER, - Box, - COLORS, - DIRECTION_COLUMN, - DISPLAY_FLEX, - Flex, - Icon, - JUSTIFY_SPACE_BETWEEN, - Link, - POSITION_ABSOLUTE, - SIZE_6, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' -import { ApiHostProvider } from '@opentrons/react-api-client' -import { - getScanning, - getConnectableRobots, - getReachableRobots, - getUnreachableRobots, - OPENTRONS_USB, -} from '../../../redux/discovery' -import { appShellRequestor } from '../../../redux/shell/remote' -import { RobotCard } from '../../../organisms/Devices/RobotCard' -import { DevicesEmptyState } from '../../../organisms/Devices/DevicesEmptyState' -import { CollapsibleSection } from '../../../molecules/CollapsibleSection' - -import { Divider } from '../../../atoms/structure' -import { NewRobotSetupHelp } from './NewRobotSetupHelp' - -import type { State } from '../../../redux/types' - -export const TROUBLESHOOTING_CONNECTION_PROBLEMS_URL = - 'https://support.opentrons.com/en/articles/2687601-troubleshooting-connection-problems' - -export function DevicesLanding(): JSX.Element { - const { t } = useTranslation('devices_landing') - - const isScanning = useSelector((state: State) => getScanning(state)) - const healthyReachableRobots = useSelector((state: State) => - getConnectableRobots(state) - ) - const reachableRobots = useSelector((state: State) => - getReachableRobots(state) - ) - const unreachableRobots = useSelector((state: State) => - getUnreachableRobots(state) - ) - - const [unhealthyReachableRobots, recentlySeenRobots] = partition( - reachableRobots, - robot => robot.healthStatus === 'ok' - ) - - const noRobots = - [ - ...healthyReachableRobots, - ...recentlySeenRobots, - ...unhealthyReachableRobots, - ...unreachableRobots, - ].length === 0 - - return ( - - - - {t('devices')} - - - - {isScanning && noRobots ? : null} - {!isScanning && noRobots ? : null} - {!noRobots ? ( - <> - - {healthyReachableRobots.map(robot => ( - - - - ))} - {unhealthyReachableRobots.map(robot => ( - - - - ))} - - - - {recentlySeenRobots.map(robot => ( - - ))} - {unreachableRobots.map(robot => ( - - ))} - - - ) : null} - - ) -} - -function DevicesLoadingState(): JSX.Element { - const { t } = useTranslation('devices_landing') - return ( - - {t('looking_for_robots')} - - - - {t('troubleshooting_connection_problems')} - - - - - ) -} diff --git a/app/src/pages/Devices/ProtocolRunDetails/index.tsx b/app/src/pages/Devices/ProtocolRunDetails/index.tsx deleted file mode 100644 index bcef73bcdca..00000000000 --- a/app/src/pages/Devices/ProtocolRunDetails/index.tsx +++ /dev/null @@ -1,425 +0,0 @@ -import * as React from 'react' -import isEmpty from 'lodash/isEmpty' -import { useTranslation } from 'react-i18next' -import { useDispatch } from 'react-redux' -import { NavLink, Navigate, useParams, useNavigate } from 'react-router-dom' -import styled, { css } from 'styled-components' - -import { - BORDERS, - Box, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - JUSTIFY_SPACE_AROUND, - LegacyStyledText, - OVERFLOW_SCROLL, - POSITION_RELATIVE, - SPACING, - Tooltip, - TYPOGRAPHY, - useHoverTooltip, -} from '@opentrons/components' -import { ApiHostProvider } from '@opentrons/react-api-client' -import { - useModuleRenderInfoForProtocolById, - useRobot, - useRobotType, - useRunHasStarted, - useRunStatuses, - useSyncRobotClock, -} from '../../../organisms/Devices/hooks' -import { ProtocolRunHeader } from '../../../organisms/Devices/ProtocolRun/ProtocolRunHeader' -import { RunPreview } from '../../../organisms/RunPreview' -import { - ProtocolRunSetup, - initialMissingSteps, -} from '../../../organisms/Devices/ProtocolRun/ProtocolRunSetup' -import { BackToTopButton } from '../../../organisms/Devices/ProtocolRun/BackToTopButton' -import { ProtocolRunModuleControls } from '../../../organisms/Devices/ProtocolRun/ProtocolRunModuleControls' -import { ProtocolRunRuntimeParameters } from '../../../organisms/Devices/ProtocolRun/ProtocolRunRunTimeParameters' -import { useCurrentRunId } from '../../../resources/runs' -import { OPENTRONS_USB } from '../../../redux/discovery' -import { fetchProtocols } from '../../../redux/protocol-storage' -import { appShellRequestor } from '../../../redux/shell/remote' -import { useMostRecentCompletedAnalysis } from '../../../organisms/LabwarePositionCheck/useMostRecentCompletedAnalysis' - -import type { ViewportListRef } from 'react-viewport-list' -import type { - DesktopRouteParams, - ProtocolRunDetailsTab, -} from '../../../App/types' -import type { Dispatch } from '../../../redux/types' - -const baseRoundTabStyling = css` - ${TYPOGRAPHY.pSemiBold} - color: ${COLORS.black90}; - background-color: ${COLORS.purple30}; - border: 0px ${BORDERS.styleSolid} ${COLORS.purple30}; - border-radius: ${BORDERS.borderRadius8}; - padding: ${SPACING.spacing8} ${SPACING.spacing16}; - position: ${POSITION_RELATIVE}; - - &:hover { - background-color: ${COLORS.purple35}; - } - - &:focus-visible { - outline: 2px ${BORDERS.styleSolid} ${COLORS.yellow50}; - } -` - -const disabledRoundTabStyling = css` - ${baseRoundTabStyling} - color: ${COLORS.grey40}; - background-color: ${COLORS.grey30}; - - &:hover { - background-color: ${COLORS.grey30}; - } -` - -const RoundNavLink = styled(NavLink)` - ${baseRoundTabStyling} - color: ${COLORS.black90}; - - &:hover { - background-color: ${COLORS.purple35}; - } - - &.active { - background-color: ${COLORS.purple50}; - color: ${COLORS.white}; - - &:hover { - background-color: ${COLORS.purple55}; - } - } -` - -const JUMP_OFFSET_FROM_TOP_PX = 20 - -interface RoundTabProps { - disabled: boolean - tabDisabledReason?: string - to: string - tabName: string -} - -function RoundTab({ - disabled, - tabDisabledReason, - to, - tabName, -}: RoundTabProps): JSX.Element { - const [targetProps, tooltipProps] = useHoverTooltip() - return disabled ? ( - <> - - {tabName} - - {tabDisabledReason != null ? ( - {tabDisabledReason} - ) : null} - - ) : ( - - {tabName} - - ) -} - -export function ProtocolRunDetails(): JSX.Element | null { - const { robotName, runId, protocolRunDetailsTab } = useParams< - keyof DesktopRouteParams - >() as DesktopRouteParams - const dispatch = useDispatch() - - const robot = useRobot(robotName) - useSyncRobotClock(robotName) - React.useEffect(() => { - dispatch(fetchProtocols()) - }, [dispatch]) - return robot != null ? ( - - - - - - - - ) : null -} - -const JUMPED_STEP_HIGHLIGHT_DELAY_MS = 1000 -interface PageContentsProps { - runId: string - robotName: string - protocolRunDetailsTab: ProtocolRunDetailsTab -} -function PageContents(props: PageContentsProps): JSX.Element { - const { runId, robotName, protocolRunDetailsTab } = props - const robotType = useRobotType(robotName) - const protocolRunHeaderRef = React.useRef(null) - const listRef = React.useRef(null) - const [jumpedIndex, setJumpedIndex] = React.useState(null) - - React.useEffect(() => { - if (jumpedIndex != null) { - setTimeout(() => { - setJumpedIndex(null) - }, JUMPED_STEP_HIGHLIGHT_DELAY_MS) - } - }, [jumpedIndex]) - - const [missingSteps, setMissingSteps] = React.useState< - ReturnType - >(initialMissingSteps()) - - const makeHandleScrollToStep = (i: number) => () => { - listRef.current?.scrollToIndex(i, true, -1 * JUMP_OFFSET_FROM_TOP_PX) - } - const makeHandleJumpToStep = (i: number) => () => { - makeHandleScrollToStep(i)() - setJumpedIndex(i) - } - const protocolRunDetailsContentByTab: { - [K in ProtocolRunDetailsTab]: { - content: JSX.Element | null - backToTop: JSX.Element | null - } - } = { - setup: { - content: ( - - ), - backToTop: ( - - - - ), - }, - 'runtime-parameters': { - content: , - backToTop: null, - }, - 'module-controls': { - content: ( - - ), - backToTop: null, - }, - 'run-preview': { - content: ( - - ), - backToTop: null, - }, - } - const tabDetails = protocolRunDetailsContentByTab[protocolRunDetailsTab] ?? { - // default to the setup tab if no tab or nonexistent tab is passed as a param - content: ( - - ), - backToTop: null, - } - const { content, backToTop } = tabDetails - - return ( - <> - - - - - - - - - {content} - - {backToTop} - - ) -} - -interface SetupTabProps { - robotName: string - runId: string - protocolRunDetailsTab?: ProtocolRunDetailsTab -} - -const SetupTab = (props: SetupTabProps): JSX.Element | null => { - const { robotName, runId, protocolRunDetailsTab } = props - const { t } = useTranslation('run_details') - const currentRunId = useCurrentRunId() - const navigate = useNavigate() - const runHasStarted = useRunHasStarted(currentRunId) - const disabled = currentRunId !== runId - const tabDisabledReason = `${t('setup')} ${t( - 'not_available_for_a_completed_run' - )}` - - // On the initial render only, navigate to "run preview" if the run has started. - React.useEffect(() => { - if (runHasStarted && protocolRunDetailsTab === 'setup') { - navigate(`/devices/${robotName}/protocol-runs/${runId}/run-preview`) - } - }, []) - - return ( - - ) -} - -interface ParametersTabProps { - robotName: string - runId: string - protocolRunDetailsTab: ProtocolRunDetailsTab -} - -const ParametersTab = (props: ParametersTabProps): JSX.Element | null => { - const { robotName, runId, protocolRunDetailsTab } = props - const { t } = useTranslation('run_details') - const mostRecentAnalysis = useMostRecentCompletedAnalysis(runId) - const navigate = useNavigate() - const disabled = mostRecentAnalysis == null - - React.useEffect(() => { - if (disabled && protocolRunDetailsTab === 'runtime-parameters') { - navigate(`/devices/${robotName}/protocol-runs/${runId}/run-preview`, { - replace: true, - }) - } - }, [disabled, navigate, protocolRunDetailsTab, robotName, runId]) - - return ( - - ) -} - -interface ModuleControlsTabProps { - robotName: string - runId: string - protocolRunDetailsTab: ProtocolRunDetailsTab -} - -const ModuleControlsTab = ( - props: ModuleControlsTabProps -): JSX.Element | null => { - const { robotName, runId, protocolRunDetailsTab } = props - const { t } = useTranslation('run_details') - const currentRunId = useCurrentRunId() - const moduleRenderInfoForProtocolById = useModuleRenderInfoForProtocolById( - runId - ) - const { isRunStill } = useRunStatuses() - const navigate = useNavigate() - - const disabled = currentRunId !== runId || !isRunStill - const tabDisabledReason = `${t('module_controls')} ${t( - currentRunId !== runId - ? 'not_available_for_a_completed_run' - : 'not_available_for_a_run_in_progress' - )}` - - React.useEffect(() => { - if (disabled && protocolRunDetailsTab === 'module-controls') - navigate(`/devices/${robotName}/protocol-runs/${runId}/run-preview`) - }, [disabled, navigate, protocolRunDetailsTab, robotName, runId]) - - return isEmpty(moduleRenderInfoForProtocolById) ? null : ( - - ) -} - -const RunPreviewTab = (props: SetupTabProps): JSX.Element | null => { - const { robotName, runId } = props - const { t } = useTranslation('run_details') - - const robotSideAnalysis = useMostRecentCompletedAnalysis(runId) - - return ( - - ) -} diff --git a/app/src/pages/Devices/RobotSettings/index.tsx b/app/src/pages/Devices/RobotSettings/index.tsx deleted file mode 100644 index fd2e089a7d4..00000000000 --- a/app/src/pages/Devices/RobotSettings/index.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useParams, Navigate } from 'react-router-dom' - -import { - BORDERS, - Box, - COLORS, - DIRECTION_COLUMN, - Flex, - SIZE_6, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' -import { ApiHostProvider } from '@opentrons/react-api-client' -import { useSelector } from 'react-redux' - -import { - CONNECTABLE, - UNREACHABLE, - REACHABLE, - OPENTRONS_USB, -} from '../../../redux/discovery' -import { appShellRequestor } from '../../../redux/shell/remote' -import { getRobotUpdateSession } from '../../../redux/robot-update' -import { getDevtoolsEnabled } from '../../../redux/config' -import { Banner } from '../../../atoms/Banner' -import { useRobot } from '../../../organisms/Devices/hooks' -import { Line } from '../../../atoms/structure' -import { NavTab } from '../../../molecules/NavTab' -import { RobotSettingsCalibration } from '../../../organisms/RobotSettingsCalibration' -import { RobotSettingsAdvanced } from '../../../organisms/Devices/RobotSettings/RobotSettingsAdvanced' -import { RobotSettingsNetworking } from '../../../organisms/Devices/RobotSettings/RobotSettingsNetworking' -import { RobotSettingsFeatureFlags } from '../../../organisms/Devices/RobotSettings/RobotSettingsFeatureFlags' -import { ReachableBanner } from '../../../organisms/Devices/ReachableBanner' - -import type { DesktopRouteParams, RobotSettingsTab } from '../../../App/types' - -export function RobotSettings(): JSX.Element | null { - const { t } = useTranslation('device_settings') - const { robotName, robotSettingsTab } = useParams< - keyof DesktopRouteParams - >() as DesktopRouteParams - const robot = useRobot(robotName) - const isCalibrationDisabled = robot?.status !== CONNECTABLE - const isNetworkingDisabled = robot?.status === UNREACHABLE - const [showRobotBusyBanner, setShowRobotBusyBanner] = React.useState( - false - ) - const robotUpdateSession = useSelector(getRobotUpdateSession) - - const updateRobotStatus = (isRobotBusy: boolean): void => { - if (isRobotBusy) setShowRobotBusyBanner(true) - } - - const robotSettingsContentByTab: { - [K in RobotSettingsTab]: JSX.Element - } = { - calibration: ( - - ), - networking: ( - - ), - advanced: ( - - ), - 'feature-flags': , - } - - const devToolsOn = useSelector(getDevtoolsEnabled) - - if ( - (robot == null || - robot?.status === UNREACHABLE || - (robot?.status === REACHABLE && robot?.serverHealthStatus !== 'ok')) && - robotUpdateSession == null - ) { - return - } - const cannotViewCalibration = - robotSettingsTab === 'calibration' && isCalibrationDisabled - const cannotViewFeatureFlags = - robotSettingsTab === 'feature-flags' && !devToolsOn - if (cannotViewCalibration || cannotViewFeatureFlags) { - return - } - - const robotSettingsContent = robotSettingsContentByTab[robotSettingsTab] ?? ( - // default to the calibration tab if no tab or nonexistent tab is passed as a param - - ) - - return ( - - - - - {t('robot_settings')} - - {robot != null && ( - - - - )} - {showRobotBusyBanner && ( - - - {t('some_robot_controls_are_not_available')} - - - )} - - - - - {devToolsOn ? ( - - ) : null} - - - - - - {robotSettingsContent} - - - - - ) -} diff --git a/app/src/pages/EmergencyStop/index.tsx b/app/src/pages/EmergencyStop/index.tsx deleted file mode 100644 index b5419ec5790..00000000000 --- a/app/src/pages/EmergencyStop/index.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import { useNavigate } from 'react-router-dom' - -import { - ALIGN_CENTER, - BORDERS, - COLORS, - DIRECTION_COLUMN, - Flex, - Icon, - JUSTIFY_CENTER, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' -import { useEstopQuery } from '@opentrons/react-api-client' - -import { MediumButton } from '../../atoms/buttons' -import { StepMeter } from '../../atoms/StepMeter' - -import estopImg from '../../assets/images/on-device-display/install_e_stop.png' - -const ESTOP_STATUS_REFETCH_INTERVAL_MS = 10000 - -export function EmergencyStop(): JSX.Element { - const { i18n, t } = useTranslation(['device_settings', 'shared']) - const navigate = useNavigate() - - // Note here the touchscreen app is using status since status is linked to EstopPhysicalStatuses - // left notPresent + right disengaged => disengaged - // left notPresent + right notPresent => notPresent - const { data: estopStatusData } = useEstopQuery({ - refetchInterval: ESTOP_STATUS_REFETCH_INTERVAL_MS, - }) - - const isEstopConnected = estopStatusData?.data?.status !== 'notPresent' - - return ( - <> - - - - - {t('install_e_stop')} - - - - - {isEstopConnected ? ( - <> - - - {t('e_stop_connected')} - - - ) : ( - <> - E-stop button - - {t('e_stop_not_connected')} - - - )} - - - { - navigate('/robot-settings/rename-robot') - }} - /> - - - ) -} diff --git a/app/src/pages/InitialLoadingScreen/index.tsx b/app/src/pages/InitialLoadingScreen/index.tsx deleted file mode 100644 index d57519bfa3b..00000000000 --- a/app/src/pages/InitialLoadingScreen/index.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import * as React from 'react' -import { useSelector } from 'react-redux' -import { - ALIGN_CENTER, - COLORS, - DIRECTION_COLUMN, - Flex, - Icon, - JUSTIFY_CENTER, - SPACING, -} from '@opentrons/components' -import { useRobotSettingsQuery } from '@opentrons/react-api-client' -import { getIsShellReady } from '../../redux/shell' - -export function InitialLoadingScreen({ - children, -}: { - children?: React.ReactNode -}): JSX.Element { - const isShellReady = useSelector(getIsShellReady) - - // ensure robot-server api is up and settings query data available for localization provider - const { settings } = - useRobotSettingsQuery({ retry: true, retryDelay: 1000 }).data ?? {} - - return isShellReady && settings != null ? ( - <>{children} - ) : ( - - - - ) -} diff --git a/app/src/pages/InstrumentDetail/InstrumentDetailOverflowMenu.tsx b/app/src/pages/InstrumentDetail/InstrumentDetailOverflowMenu.tsx deleted file mode 100644 index 494e0f3d18c..00000000000 --- a/app/src/pages/InstrumentDetail/InstrumentDetailOverflowMenu.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import * as React from 'react' -import NiceModal, { useModal } from '@ebay/nice-modal-react' -import { useTranslation } from 'react-i18next' -import { - ALIGN_CENTER, - COLORS, - Flex, - Icon, - LegacyStyledText, - MenuItem, - MenuList, - SPACING, - TYPOGRAPHY, -} from '@opentrons/components' -import { - SINGLE_MOUNT_PIPETTES, - NINETY_SIX_CHANNEL, - FLEX_ROBOT_TYPE, - getPipetteModelSpecs, -} from '@opentrons/shared-data' -import { ApiHostProvider } from '@opentrons/react-api-client' - -import { PipetteWizardFlows } from '../../organisms/PipetteWizardFlows' -import { GripperWizardFlows } from '../../organisms/GripperWizardFlows' -import { - DropTipWizardFlows, - useDropTipWizardFlows, -} from '../../organisms/DropTipWizardFlows' -import { FLOWS } from '../../organisms/PipetteWizardFlows/constants' -import { GRIPPER_FLOW_TYPES } from '../../organisms/GripperWizardFlows/constants' - -import type { - PipetteData, - GripperData, - HostConfig, -} from '@opentrons/api-client' - -interface InstrumentDetailsOverflowMenuProps { - instrument: PipetteData | GripperData - host: HostConfig | null -} - -export const handleInstrumentDetailOverflowMenu = ( - instrument: InstrumentDetailsOverflowMenuProps['instrument'], - host: InstrumentDetailsOverflowMenuProps['host'] -): void => { - NiceModal.show(InstrumentDetailsOverflowMenu, { instrument, host }) -} - -const InstrumentDetailsOverflowMenu = NiceModal.create( - (props: InstrumentDetailsOverflowMenuProps): JSX.Element => { - const { instrument, host } = props - const { t } = useTranslation('robot_controls') - const modal = useModal() - const [wizardProps, setWizardProps] = React.useState< - | React.ComponentProps - | React.ComponentProps - | null - >(null) - const sharedGripperWizardProps: Pick< - React.ComponentProps, - 'attachedGripper' | 'closeFlow' - > = { - attachedGripper: instrument, - closeFlow: () => { - modal.remove() - }, - } - const { showDTWiz, toggleDTWiz } = useDropTipWizardFlows() - const pipetteModelSpecs = - getPipetteModelSpecs((instrument as PipetteData).instrumentModel) ?? null - - const is96Channel = - instrument?.ok && - instrument.mount !== 'extension' && - instrument.data?.channels === 96 - - const handleRecalibrate: React.MouseEventHandler = () => { - if (instrument?.ok) { - setWizardProps( - instrument.mount === 'extension' - ? { - ...sharedGripperWizardProps, - flowType: GRIPPER_FLOW_TYPES.RECALIBRATE, - } - : { - closeFlow: () => { - modal.remove() - }, - mount: instrument.mount, - selectedPipette: is96Channel - ? NINETY_SIX_CHANNEL - : SINGLE_MOUNT_PIPETTES, - flowType: FLOWS.CALIBRATE, - } - ) - } - } - - return ( - - - {instrument.data.calibratedOffset?.last_modified != null ? ( - - - - - {t('recalibrate')} - - - - ) : null} - {instrument.mount !== 'extension' ? ( - - - - - {t('drop_tips')} - - - - ) : null} - - {wizardProps != null && 'mount' in wizardProps ? ( - - ) : null} - {wizardProps != null && !('mount' in wizardProps) ? ( - - ) : null} - {showDTWiz && - instrument.mount !== 'extension' && - pipetteModelSpecs != null ? ( - - ) : null} - - ) - } -) diff --git a/app/src/pages/InstrumentDetail/index.tsx b/app/src/pages/InstrumentDetail/index.tsx deleted file mode 100644 index 3f606ba3432..00000000000 --- a/app/src/pages/InstrumentDetail/index.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import * as React from 'react' -import { useParams } from 'react-router-dom' -import styled from 'styled-components' - -import { useInstrumentsQuery, useHost } from '@opentrons/react-api-client' -import { - Icon, - DIRECTION_COLUMN, - Flex, - SPACING, - COLORS, - RESPONSIVENESS, - DIRECTION_ROW, - JUSTIFY_SPACE_BETWEEN, -} from '@opentrons/components' - -import { BackButton } from '../../atoms/buttons/BackButton' -import { ODD_FOCUS_VISIBLE } from '../../atoms/buttons/constants' -import { InstrumentInfo } from '../../organisms/InstrumentInfo' -import { handleInstrumentDetailOverflowMenu } from '../../pages/InstrumentDetail/InstrumentDetailOverflowMenu' -import { - useGripperDisplayName, - usePipetteModelSpecs, -} from '../../resources/instruments/hooks' - -import type { GripperData, PipetteData } from '@opentrons/api-client' -import type { GripperModel, PipetteModel } from '@opentrons/shared-data' - -export const InstrumentDetail = (): JSX.Element => { - const host = useHost() - const { mount } = useParams<{ mount: PipetteData['mount'] }>() - const { data: attachedInstruments } = useInstrumentsQuery() - const instrument = - (attachedInstruments?.data ?? []).find( - (i): i is PipetteData | GripperData => i.ok && i.mount === mount - ) ?? null - - const pipetteDisplayName = usePipetteModelSpecs( - instrument?.instrumentModel as PipetteModel - )?.displayName - const gripperDisplayName = useGripperDisplayName( - instrument?.instrumentModel as GripperModel - ) - - const displayName = - instrument?.mount !== 'extension' ? pipetteDisplayName : gripperDisplayName - - return ( - <> - - - {displayName} - {instrument?.ok && instrument?.mount !== 'extension' ? ( - - { - handleInstrumentDetailOverflowMenu(instrument, host) - }} - > - - - - ) : null} - - - - - ) -} - -const IconButton = styled('button')` - border-radius: ${SPACING.spacing4}; - max-height: 100%; - background-color: ${COLORS.white}; - - &:active { - background-color: ${COLORS.grey35}; - } - &:focus-visible { - box-shadow: ${ODD_FOCUS_VISIBLE}; - background-color: ${COLORS.grey35}; - } - &:disabled { - background-color: transparent; - } - @media ${RESPONSIVENESS.touchscreenMediaQuerySpecs} { - cursor: default; - } -` diff --git a/app/src/pages/InstrumentsDashboard/index.tsx b/app/src/pages/InstrumentsDashboard/index.tsx deleted file mode 100644 index 46154442cb2..00000000000 --- a/app/src/pages/InstrumentsDashboard/index.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import * as React from 'react' -import { useInstrumentsQuery } from '@opentrons/react-api-client' -import { DIRECTION_COLUMN, Flex, SPACING } from '@opentrons/components' -import { PipetteWizardFlows } from '../../organisms/PipetteWizardFlows' -import { Navigation } from '../../organisms/Navigation' -import { AttachedInstrumentMountItem } from '../../organisms/InstrumentMountItem' -import { GripperWizardFlows } from '../../organisms/GripperWizardFlows' -import { getShowPipetteCalibrationWarning } from '../../organisms/Devices/utils' -import { PipetteRecalibrationODDWarning } from './PipetteRecalibrationODDWarning' -import type { GripperData, PipetteData } from '@opentrons/api-client' - -const FETCH_PIPETTE_CAL_POLL = 10000 - -export const InstrumentsDashboard = (): JSX.Element => { - const { data: attachedInstruments } = useInstrumentsQuery({ - refetchInterval: FETCH_PIPETTE_CAL_POLL, - }) - const [wizardProps, setWizardProps] = React.useState< - | React.ComponentProps - | React.ComponentProps - | null - >(null) - - const leftInstrument = - (attachedInstruments?.data ?? []).find( - (i): i is PipetteData => i.ok && i.mount === 'left' - ) ?? null - const isNinetySixChannel = leftInstrument?.data?.channels === 96 - - return ( - - - - {getShowPipetteCalibrationWarning(attachedInstruments) && ( - - - - )} - {isNinetySixChannel ? ( - - ) : ( - <> - - i.ok && i.mount === 'right' - ) ?? null - } - setWizardProps={setWizardProps} - /> - - )} - i.ok && i.mount === 'extension' - ) ?? null - } - setWizardProps={setWizardProps} - /> - - {wizardProps != null && 'mount' in wizardProps ? ( - - ) : null} - {wizardProps != null && !('mount' in wizardProps) ? ( - - ) : null} - - ) -} diff --git a/app/src/pages/Labware/__tests__/Labware.test.tsx b/app/src/pages/Labware/__tests__/Labware.test.tsx deleted file mode 100644 index 62c3f6a4521..00000000000 --- a/app/src/pages/Labware/__tests__/Labware.test.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import * as React from 'react' -import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' -import { fireEvent, screen } from '@testing-library/react' -import { MemoryRouter } from 'react-router-dom' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { - useTrackEvent, - ANALYTICS_OPEN_LABWARE_CREATOR_FROM_BOTTOM_OF_LABWARE_LIBRARY_LIST, -} from '../../../redux/analytics' -import { LabwareCard } from '../../../organisms/LabwareCard' -import { AddCustomLabwareSlideout } from '../../../organisms/AddCustomLabwareSlideout' -import { useToaster } from '../../../organisms/ToasterOven' -import { useAllLabware, useLabwareFailure, useNewLabwareName } from '../hooks' -import { Labware } from '..' -import { mockDefinition } from '../../../redux/custom-labware/__fixtures__' - -vi.mock('../../../organisms/LabwareCard') -vi.mock('../../../organisms/AddCustomLabwareSlideout') -vi.mock('../../../organisms/ToasterOven') -vi.mock('../hooks') -vi.mock('../../../redux/analytics') - -const mockTrackEvent = vi.fn() -const mockMakeSnackbar = vi.fn() -const mockMakeToast = vi.fn() -const mockEatToast = vi.fn() - -const render = () => { - return renderWithProviders( - - - , - { - i18nInstance: i18n, - } - ) -} - -describe('Labware', () => { - beforeEach(() => { - vi.mocked(useTrackEvent).mockReturnValue(mockTrackEvent) - vi.mocked(LabwareCard).mockReturnValue(
    Mock Labware Card
    ) - vi.mocked(useAllLabware).mockReturnValue([{ definition: mockDefinition }]) - vi.mocked(useLabwareFailure).mockReturnValue({ - labwareFailureMessage: null, - clearLabwareFailure: vi.fn(), - }) - vi.mocked(useNewLabwareName).mockReturnValue({ - newLabwareName: null, - clearLabwareName: vi.fn(), - }) - vi.mocked(useToaster).mockReturnValue({ - makeSnackbar: mockMakeSnackbar, - makeToast: mockMakeToast, - eatToast: mockEatToast, - }) - }) - afterEach(() => { - vi.resetAllMocks() - }) - - it('renders correct title, import button and labware cards', () => { - render() - screen.getByText('labware') - screen.getByText('Mock Labware Card') - screen.getByRole('button', { name: 'Import' }) - screen.getByText('Category') - screen.getByText('All') - screen.getByText('Sort by') - expect(screen.getByTestId('sortBy-label')).toHaveTextContent('Alphabetical') - }) - it('renders AddCustomLabware slideout when import button is clicked', () => { - render() - const importButton = screen.getByRole('button', { name: 'Import' }) - fireEvent.click(importButton) - expect(vi.mocked(AddCustomLabwareSlideout)).toHaveBeenCalled() - }) - it('renders footer with labware creator link', () => { - render() - screen.getByText('Create a new labware definition') - const btn = screen.getByRole('link', { name: 'Open Labware Creator' }) - fireEvent.click(btn) - expect(mockTrackEvent).toHaveBeenCalledWith({ - name: ANALYTICS_OPEN_LABWARE_CREATOR_FROM_BOTTOM_OF_LABWARE_LIBRARY_LIST, - properties: {}, - }) - }) - it('renders error toast if there is a failure', () => { - vi.mocked(useLabwareFailure).mockReturnValue({ - labwareFailureMessage: 'mock failure message', - clearLabwareFailure: vi.fn(), - }) - render() - expect(mockMakeToast).toBeCalledWith( - 'mock failure message', - 'error', - expect.any(Object) - ) - }) - it('renders success toast if there is a new labware name', () => { - vi.mocked(useNewLabwareName).mockReturnValue({ - newLabwareName: 'mock filename', - clearLabwareName: vi.fn(), - }) - render() - expect(mockMakeToast).toBeCalledWith( - 'mock filename imported.', - 'success', - expect.any(Object) - ) - }) - it('renders filter by menu when it is clicked', () => { - render() - const filter = screen.getByText('All') - fireEvent.click(filter) - screen.getByRole('button', { name: 'All' }) - screen.getByRole('button', { name: 'Well Plate' }) - screen.getByRole('button', { name: 'Tip Rack' }) - screen.getByRole('button', { name: 'Tube Rack' }) - screen.getByRole('button', { name: 'Reservoir' }) - screen.getByRole('button', { name: 'Aluminum Block' }) - }) - it('renders changes filter menu button when an option is selected', () => { - render() - const filter = screen.getByText('All') - fireEvent.click(filter) - const wellPlate = screen.getByRole('button', { name: 'Well Plate' }) - fireEvent.click(wellPlate) - screen.getByText('Well Plate') - }) - it('renders sort by menu when sort is clicked', () => { - render() - const sort = screen.getByText('Alphabetical') - fireEvent.click(sort) - screen.getByRole('button', { name: 'Alphabetical' }) - screen.getByRole('button', { name: 'Reverse alphabetical' }) - }) - - it('renders selected sort by menu when one menu is clicked', () => { - render() - const sort = screen.getByText('Alphabetical') - fireEvent.click(sort) - const reverse = screen.getByRole('button', { name: 'Reverse alphabetical' }) - fireEvent.click(reverse) - expect(screen.getByTestId('sortBy-label')).toHaveTextContent( - 'Reverse alphabetical' - ) - }) -}) diff --git a/app/src/pages/Labware/__tests__/hooks.test.tsx b/app/src/pages/Labware/__tests__/hooks.test.tsx deleted file mode 100644 index 23035d94cc3..00000000000 --- a/app/src/pages/Labware/__tests__/hooks.test.tsx +++ /dev/null @@ -1,226 +0,0 @@ -import * as React from 'react' -import { Provider } from 'react-redux' -import { createStore } from 'redux' -import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' -import { renderHook } from '@testing-library/react' -import { i18n } from '../../../i18n' -import { I18nextProvider } from 'react-i18next' -import { getAllDefs } from '../helpers/getAllDefs' - -import { - getValidCustomLabware, - getAddLabwareFailure, - getAddNewLabwareName, -} from '../../../redux/custom-labware' -import { - mockDefinition, - mockValidLabware, -} from '../../../redux/custom-labware/__fixtures__' - -import { useAllLabware, useLabwareFailure, useNewLabwareName } from '../hooks' - -import type { Store } from 'redux' -import type { State } from '../../../redux/types' -import type { FailedLabwareFile } from '../../../redux/custom-labware/types' - -vi.mock('../../../redux/custom-labware') -vi.mock('../helpers/getAllDefs') - -describe('useAllLabware hook', () => { - const store: Store = createStore(vi.fn(), {}) - beforeEach(() => { - vi.mocked(getAllDefs).mockReturnValue([mockDefinition]) - vi.mocked(getValidCustomLabware).mockReturnValue([mockValidLabware]) - store.dispatch = vi.fn() - }) - afterEach(() => { - vi.restoreAllMocks() - }) - - it('should return object with only definition and modified date', () => { - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => {children} - const { result } = renderHook(() => useAllLabware('reverse', 'all'), { - wrapper, - }) - const labware1 = result.current[0] - const labware2 = result.current[1] - - expect(labware1.definition).toBe(mockDefinition) - expect(labware2.modified).toBe(mockValidLabware.modified) - expect(labware2.definition).toBe(mockValidLabware.definition) - }) - it('should return alphabetically sorted list', () => { - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => {children} - const { result } = renderHook(() => useAllLabware('alphabetical', 'all'), { - wrapper, - }) - const labware1 = result.current[0] - const labware2 = result.current[1] - - expect(labware2.definition).toBe(mockDefinition) - expect(labware1.modified).toBe(mockValidLabware.modified) - expect(labware1.definition).toBe(mockValidLabware.definition) - }) - it('should return no labware if not the right filter', () => { - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => {children} - const { result } = renderHook(() => useAllLabware('reverse', 'reservoir'), { - wrapper, - }) - const labware1 = result.current[0] - const labware2 = result.current[1] - - expect(labware1).toBe(undefined) - expect(labware2).toBe(undefined) - }) - it('should return labware with wellPlate filter', () => { - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => {children} - const { result } = renderHook(() => useAllLabware('reverse', 'wellPlate'), { - wrapper, - }) - const labware1 = result.current[0] - const labware2 = result.current[1] - - expect(labware1.definition).toBe(mockDefinition) - expect(labware2.modified).toBe(mockValidLabware.modified) - expect(labware2.definition).toBe(mockValidLabware.definition) - }) - it('should return custom labware with customLabware filter', () => { - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => {children} - const { result } = renderHook( - () => useAllLabware('alphabetical', 'customLabware'), - { - wrapper, - } - ) - const labware1 = result.current[0] - const labware2 = result.current[1] - - expect(labware1).toBe(mockValidLabware) - expect(labware2).toBeUndefined() - }) -}) - -describe('useLabwareFailure hook', () => { - const store: Store = createStore(vi.fn(), {}) - beforeEach(() => { - vi.mocked(getAddLabwareFailure).mockReturnValue({ - file: { - type: 'INVALID_LABWARE_FILE', - filename: '123', - } as FailedLabwareFile, - errorMessage: null, - }) - store.dispatch = vi.fn() - }) - afterEach(() => { - vi.restoreAllMocks() - }) - it('should return invalid labware definition', () => { - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => ( - - {children} - - ) - const { result } = renderHook(useLabwareFailure, { wrapper }) - const errorMessage = result.current.labwareFailureMessage - expect(errorMessage).toBe('Error importing 123. Invalid labware definition') - }) - it('should return duplicate labware definition', () => { - vi.mocked(getAddLabwareFailure).mockReturnValue({ - file: { - type: 'DUPLICATE_LABWARE_FILE', - filename: '123', - } as FailedLabwareFile, - errorMessage: null, - }) - - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => ( - - {children} - - ) - const { result } = renderHook(useLabwareFailure, { wrapper }) - const errorMessage = result.current.labwareFailureMessage - - expect(errorMessage).toBe( - 'Error importing 123. Duplicate labware definition' - ) - }) - it('should return opentrons labware definition', () => { - vi.mocked(getAddLabwareFailure).mockReturnValue({ - file: { - type: 'OPENTRONS_LABWARE_FILE', - filename: '123', - } as FailedLabwareFile, - errorMessage: null, - }) - - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => ( - - {children} - - ) - const { result } = renderHook(useLabwareFailure, { wrapper }) - const errorMessage = result.current.labwareFailureMessage - - expect(errorMessage).toBe( - 'Error importing 123. Opentrons labware definition' - ) - }) - it('should return unable to upload labware definition', () => { - vi.mocked(getAddLabwareFailure).mockReturnValue({ - file: null, - errorMessage: 'error', - }) - - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => ( - - {children} - - ) - const { result } = renderHook(useLabwareFailure, { wrapper }) - const errorMessage = result.current.labwareFailureMessage - - expect(errorMessage).toBe('Unable to upload file') - }) -}) - -describe('useNewLabwareName hook', () => { - const store: Store = createStore(vi.fn(), {}) - beforeEach(() => { - vi.mocked(getAddNewLabwareName).mockReturnValue({ - filename: 'mock_filename', - }) - store.dispatch = vi.fn() - }) - afterEach(() => { - vi.restoreAllMocks() - }) - - it('should return filename as a string', () => { - const wrapper: React.FunctionComponent<{ children: React.ReactNode }> = ({ - children, - }) => {children} - const { result } = renderHook(useNewLabwareName, { wrapper }) - const filename = result.current.newLabwareName - expect(filename).toBe('mock_filename') - }) -}) diff --git a/app/src/pages/Labware/helpers/definitions.ts b/app/src/pages/Labware/helpers/definitions.ts deleted file mode 100644 index bab954a8151..00000000000 --- a/app/src/pages/Labware/helpers/definitions.ts +++ /dev/null @@ -1,34 +0,0 @@ -import groupBy from 'lodash/groupBy' -import { LABWAREV2_DO_NOT_LIST } from '@opentrons/shared-data' -import { getAllDefs } from './getAllDefs' -import type { LabwareDefinition2 } from '@opentrons/shared-data' - -const getOnlyLatestDefs = ( - labwareList: LabwareDefinition2[] -): LabwareDefinition2[] => { - // group by namespace + loadName - const labwareDefGroups: { - [groupKey: string]: LabwareDefinition2[] - } = groupBy( - labwareList, - d => `${d.namespace}/${d.parameters.loadName}` - ) - return Object.keys(labwareDefGroups).map((groupKey: string) => { - const group = labwareDefGroups[groupKey] - const allVersions = group.map(d => d.version) - const highestVersionNum = Math.max(...allVersions) - const resultIdx = group.findIndex(d => d.version === highestVersionNum) - return group[resultIdx] - }) -} - -export function getAllDefinitions(): LabwareDefinition2[] { - const allDefs = getAllDefs().filter( - (d: LabwareDefinition2) => - // eslint-disable-next-line @typescript-eslint/prefer-includes - LABWAREV2_DO_NOT_LIST.indexOf(d.parameters.loadName) === -1 - ) - const definitions = getOnlyLatestDefs(allDefs) - - return definitions -} diff --git a/app/src/pages/Labware/hooks.tsx b/app/src/pages/Labware/hooks.tsx deleted file mode 100644 index b1453738652..00000000000 --- a/app/src/pages/Labware/hooks.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { useSelector, useDispatch } from 'react-redux' -import { useTranslation } from 'react-i18next' -import { - getAddLabwareFailure, - clearAddCustomLabwareFailure, - getAddNewLabwareName, - clearNewLabwareName, - getValidCustomLabware, -} from '../../redux/custom-labware' -import { getAllDefinitions } from './helpers/definitions' -import type { Dispatch } from '../../redux/types' - -import type { LabwareDefinition2 as LabwareDefinition } from '@opentrons/shared-data' -import type { LabwareFilter, LabwareSort } from './types' - -export interface LabwareDefAndDate { - definition: LabwareDefinition - modified?: number - filename?: string -} - -export function useAllLabware( - sortBy: LabwareSort, - filterBy: LabwareFilter -): LabwareDefAndDate[] { - const fullLabwareList: LabwareDefAndDate[] = [] - const labwareDefinitions = getAllDefinitions() - labwareDefinitions.forEach(def => fullLabwareList.push({ definition: def })) - const customLabwareList = useSelector(getValidCustomLabware) - customLabwareList.forEach(customLabware => - 'definition' in customLabware - ? fullLabwareList.push({ - modified: customLabware.modified, - definition: customLabware.definition, - filename: customLabware.filename, - }) - : null - ) - const sortLabware = (a: LabwareDefAndDate, b: LabwareDefAndDate): number => { - if ( - a.definition.metadata.displayName.toUpperCase() < - b.definition.metadata.displayName.toUpperCase() - ) { - return sortBy === 'alphabetical' ? -1 : 1 - } - if ( - a.definition.metadata.displayName.toUpperCase() > - b.definition.metadata.displayName.toUpperCase() - ) { - return sortBy === 'alphabetical' ? 1 : -1 - } - return 0 - } - - if (filterBy === 'customLabware') { - return (customLabwareList as LabwareDefAndDate[]).sort(sortLabware) - } - fullLabwareList.sort(sortLabware) - if (filterBy !== 'all') { - return fullLabwareList.filter( - labwareItem => - labwareItem.definition.metadata.displayCategory === filterBy - ) - } - return fullLabwareList -} - -export function useLabwareFailure(): { - labwareFailureMessage: string | null - clearLabwareFailure: () => unknown -} { - const { t } = useTranslation(['labware_landing', 'branded']) - const dispatch = useDispatch() - const labwareFailure = useSelector(getAddLabwareFailure) - - let labwareFailureMessage = null - if (labwareFailure.file != null || labwareFailure.errorMessage != null) { - const failedFile = labwareFailure.file - let errorMessage = t('unable_to_upload') - if (failedFile?.type === 'INVALID_LABWARE_FILE') { - errorMessage = t('invalid_labware_def') - } else if (failedFile?.type === 'DUPLICATE_LABWARE_FILE') { - errorMessage = t('duplicate_labware_def') - } else if (failedFile?.type === 'OPENTRONS_LABWARE_FILE') { - errorMessage = t('branded:opentrons_labware_def') - } - labwareFailureMessage = - failedFile != null - ? `${t('error_importing_file', { - filename: failedFile.filename, - })} ${errorMessage}` - : errorMessage - } - const clearLabwareFailure = (): unknown => - dispatch(clearAddCustomLabwareFailure()) - - return { labwareFailureMessage, clearLabwareFailure } -} - -export function useNewLabwareName(): { - newLabwareName: string | null - clearLabwareName: () => unknown -} { - const dispatch = useDispatch() - const newLabwareName = useSelector(getAddNewLabwareName).filename - const clearLabwareName = (): unknown => dispatch(clearNewLabwareName()) - - return { newLabwareName, clearLabwareName } -} diff --git a/app/src/pages/Labware/index.tsx b/app/src/pages/Labware/index.tsx deleted file mode 100644 index dc464c306fb..00000000000 --- a/app/src/pages/Labware/index.tsx +++ /dev/null @@ -1,315 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' -import startCase from 'lodash/startCase' -import { css } from 'styled-components' -import { useDispatch } from 'react-redux' - -import { - ALIGN_CENTER, - ALIGN_FLEX_END, - BORDERS, - Box, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - DropdownMenu, - Flex, - Icon, - JUSTIFY_SPACE_BETWEEN, - LegacyStyledText, - Link, - MenuItem, - POSITION_ABSOLUTE, - PrimaryButton, - SecondaryButton, - ERROR_TOAST, - SUCCESS_TOAST, - SPACING, - TYPOGRAPHY, - useOnClickOutside, -} from '@opentrons/components' -import { LabwareCreator } from '@opentrons/labware-library' -import { - useTrackEvent, - ANALYTICS_OPEN_LABWARE_CREATOR_FROM_BOTTOM_OF_LABWARE_LIBRARY_LIST, -} from '../../redux/analytics' -import { addCustomLabwareFileFromCreator } from '../../redux/custom-labware' -import { LabwareCard } from '../../organisms/LabwareCard' -import { AddCustomLabwareSlideout } from '../../organisms/AddCustomLabwareSlideout' -import { LabwareDetails } from '../../organisms/LabwareDetails' -import { useToaster } from '../../organisms/ToasterOven' -import { useFeatureFlag } from '../../redux/config' -import { useAllLabware, useLabwareFailure, useNewLabwareName } from './hooks' - -import type { DropdownOption } from '@opentrons/components' -import type { LabwareFilter, LabwareSort } from './types' -import type { LabwareDefAndDate } from './hooks' - -const LABWARE_CREATOR_HREF = 'https://labware.opentrons.com/create/' -const labwareDisplayCategoryFilters: LabwareFilter[] = [ - 'all', - 'adapter', - 'aluminumBlock', - 'customLabware', - 'reservoir', - 'tipRack', - 'tubeRack', - 'wellPlate', -] - -const FILTER_OPTIONS: DropdownOption[] = labwareDisplayCategoryFilters.map( - category => ({ - name: startCase(category), - value: category, - }) -) - -const SORT_BY_BUTTON_STYLE = css` - background-color: ${COLORS.transparent}; - cursor: pointer; - &:hover { - background-color: ${COLORS.grey30}; - } - &:active, - &:focus { - background-color: ${COLORS.grey40}; - } -` - -export function Labware(): JSX.Element { - const { t } = useTranslation(['labware_landing', 'shared']) - const enableLabwareCreator = useFeatureFlag('enableLabwareCreator') - const [sortBy, setSortBy] = React.useState('alphabetical') - const [showSortByMenu, setShowSortByMenu] = React.useState(false) - const toggleSetShowSortByMenu = (): void => { - setShowSortByMenu(!showSortByMenu) - } - const dispatch = useDispatch() - const [showLC, setShowLC] = React.useState(false) - const trackEvent = useTrackEvent() - const [filterBy, setFilterBy] = React.useState('all') - const { makeToast } = useToaster() - - const labware = useAllLabware(sortBy, filterBy) - const { labwareFailureMessage, clearLabwareFailure } = useLabwareFailure() - const { newLabwareName, clearLabwareName } = useNewLabwareName() - const [showAddLabwareSlideout, setShowAddLabwareSlideout] = React.useState( - false - ) - - const [ - currentLabwareDef, - setCurrentLabwareDef, - ] = React.useState(null) - - const sortOverflowWrapperRef = useOnClickOutside({ - onClickOutside: () => { - setShowSortByMenu(false) - }, - }) - React.useEffect(() => { - if (labwareFailureMessage != null) { - setShowAddLabwareSlideout(false) - makeToast(labwareFailureMessage, ERROR_TOAST, { - closeButton: true, - onClose: clearLabwareFailure, - }) - } else if (newLabwareName != null) { - setShowAddLabwareSlideout(false) - makeToast( - t('imported', { filename: newLabwareName }) as string, - SUCCESS_TOAST, - { - closeButton: true, - onClose: clearLabwareName, - } - ) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [labwareFailureMessage, newLabwareName]) - - return ( - <> - {showLC ? ( - { - setShowLC(false) - }} - save={(file: string) => { - dispatch(addCustomLabwareFileFromCreator(file)) - }} - isOnRunApp - /> - ) : null} - - - - {t('labware')} - - - { - setShowAddLabwareSlideout(true) - }} - > - {t('import')} - - {enableLabwareCreator ? ( - { - setShowLC(true) - }} - > - Open Labware Creator - - ) : null} - - - - { - setFilterBy(value as LabwareFilter) - }} - title={t('category')} - /> - - - {t('shared:sort_by')} - - - - {sortBy === 'alphabetical' - ? t('shared:alphabetical') - : t('shared:reverse')} - - - - - {showSortByMenu && ( - - { - setSortBy('alphabetical') - setShowSortByMenu(false) - }} - > - {t('shared:alphabetical')} - - { - setSortBy('reverse') - setShowSortByMenu(false) - }} - > - {t('shared:reverse')} - - - )} - - - {labware.map((labware, index) => ( - { - setCurrentLabwareDef(labware) - }} - /> - ))} - - - - {t('create_new_def')} - - - { - trackEvent({ - name: ANALYTICS_OPEN_LABWARE_CREATOR_FROM_BOTTOM_OF_LABWARE_LIBRARY_LIST, - properties: {}, - }) - }} - href={LABWARE_CREATOR_HREF} - css={TYPOGRAPHY.darkLinkLabelSemiBold} - > - {t('open_labware_creator')} - - - - - {showAddLabwareSlideout && ( - { - setShowAddLabwareSlideout(false) - }} - /> - )} - {currentLabwareDef != null && ( - { - setCurrentLabwareDef(null) - }} - /> - )} - - ) -} diff --git a/app/src/pages/Labware/types.ts b/app/src/pages/Labware/types.ts deleted file mode 100644 index 24696b11844..00000000000 --- a/app/src/pages/Labware/types.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { - LabwareDefinition2 as LabwareDefinition, - LabwareWellShapeProperties, - LabwareWellGroupMetadata, - LabwareBrand, -} from '@opentrons/shared-data' - -export type { - LabwareDefinition2 as LabwareDefinition, - LabwareParameters, - LabwareOffset, - LabwareWell, - LabwareWellShapeProperties, - LabwareWellProperties, - LabwareWellMap, - LabwareWellGroupMetadata, - LabwareVolumeUnits, - LabwareDisplayCategory, - LabwareBrand, -} from '@opentrons/shared-data' - -export interface LabwareWellGroupProperties { - xOffsetFromLeft: number - yOffsetFromTop: number - xSpacing: number | null - ySpacing: number | null - wellCount: number - shape: LabwareWellShapeProperties | null - depth: number | null - totalLiquidVolume: number | null - metadata: LabwareWellGroupMetadata - brand: LabwareBrand | null -} - -export type LabwareList = LabwareDefinition[] - -export type LabwareFilter = - | 'all' - | 'wellPlate' - | 'tipRack' - | 'tubeRack' - | 'reservoir' - | 'aluminumBlock' - | 'customLabware' - | 'adapter' - -export type LabwareSort = 'alphabetical' | 'reverse' diff --git a/app/src/pages/NameRobot/index.tsx b/app/src/pages/NameRobot/index.tsx deleted file mode 100644 index 7e8be33383b..00000000000 --- a/app/src/pages/NameRobot/index.tsx +++ /dev/null @@ -1,318 +0,0 @@ -import * as React from 'react' -import { Controller, useForm } from 'react-hook-form' -import { useTranslation } from 'react-i18next' -import { useSelector, useDispatch } from 'react-redux' -import { useNavigate } from 'react-router-dom' - -import { - ALIGN_CENTER, - Btn, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - Icon, - InputField, - JUSTIFY_CENTER, - JUSTIFY_SPACE_BETWEEN, - LegacyStyledText, - POSITION_ABSOLUTE, - POSITION_FIXED, - POSITION_RELATIVE, - SPACING, - TYPOGRAPHY, -} from '@opentrons/components' -import { useUpdateRobotNameMutation } from '@opentrons/react-api-client' - -import { - removeRobot, - getConnectableRobots, - getReachableRobots, - getUnreachableRobots, - getLocalRobot, -} from '../../redux/discovery' -import { useTrackEvent, ANALYTICS_RENAME_ROBOT } from '../../redux/analytics' -import { AlphanumericKeyboard } from '../../atoms/SoftwareKeyboard' -import { SmallButton } from '../../atoms/buttons' -import { StepMeter } from '../../atoms/StepMeter' -import { useIsUnboxingFlowOngoing } from '../../organisms/RobotSettingsDashboard/NetworkSettings/hooks' -import { ConfirmRobotName } from '../../organisms/OnDeviceDisplay/NameRobot/ConfirmRobotName' - -import type { FieldError, Resolver } from 'react-hook-form' -import type { UpdatedRobotName } from '@opentrons/api-client' -import type { State, Dispatch } from '../../redux/types' - -interface FormValues { - newRobotName: string -} - -export function NameRobot(): JSX.Element { - const { t } = useTranslation(['device_settings', 'shared']) - const navigate = useNavigate() - const trackEvent = useTrackEvent() - const localRobot = useSelector(getLocalRobot) - const ipAddress = localRobot?.ip - const previousName = localRobot?.name != null ? localRobot.name : null - const [newName, setNewName] = React.useState('') - const [ - isShowConfirmRobotName, - setIsShowConfirmRobotName, - ] = React.useState(false) - const keyboardRef = React.useRef(null) - const dispatch = useDispatch() - const isUnboxingFlowOngoing = useIsUnboxingFlowOngoing() - - const connectableRobots = useSelector((state: State) => - getConnectableRobots(state) - ) - const reachableRobots = useSelector((state: State) => - getReachableRobots(state) - ) - const unreachableRobots = useSelector((state: State) => - getUnreachableRobots(state) - ) - - const validate = ( - data: FormValues, - errors: Record - ): Record => { - const newName = data.newRobotName - let errorMessage: string | undefined - // In ODD users cannot input letters and numbers from software keyboard - // so the app only checks the length of input string - if (newName.length < 1) { - errorMessage = t('name_rule_error_name_length') - } - - if ( - [...connectableRobots, ...reachableRobots].some( - robot => newName === robot.name && robot.ip !== ipAddress - ) - ) { - errorMessage = t('name_rule_error_exist') - } - - const updatedErrors = - errorMessage != null - ? { - ...errors, - newRobotName: { - type: 'error', - message: errorMessage, - }, - } - : errors - return updatedErrors - } - - const resolver: Resolver = values => { - let errors = {} - errors = validate(values, errors) - return { values, errors } - } - - const { - handleSubmit, - control, - formState: { errors }, - reset, - trigger, - watch, - } = useForm({ - defaultValues: { - newRobotName: '', - }, - resolver, - }) - - const newRobotName = watch('newRobotName') - - const onSubmit = (data: FormValues): void => { - const newName = data.newRobotName - const sameNameRobotInUnavailable = unreachableRobots.find( - robot => robot.name === newName - ) - if (sameNameRobotInUnavailable != null) { - dispatch(removeRobot(sameNameRobotInUnavailable.name)) - } - updateRobotName(newName) - reset({ newRobotName: '' }) - } - - const { updateRobotName, isLoading: isNaming } = useUpdateRobotNameMutation({ - onSuccess: (data: UpdatedRobotName) => { - if (data.name != null) { - setNewName(data.name) - if (!isUnboxingFlowOngoing) { - navigate('/robot-settings') - } else { - setIsShowConfirmRobotName(true) - } - if (previousName != null) { - dispatch(removeRobot(previousName)) - } - } - }, - onError: (error: Error) => { - console.error('error', error.message) - }, - }) - - const handleConfirm = async (): Promise => { - await trigger('newRobotName') - - // check robot name in the same network - trackEvent({ - name: ANALYTICS_RENAME_ROBOT, - properties: { - previousRobotName: previousName, - newRobotName: newRobotName, - }, - }) - handleSubmit(onSubmit)() - } - - return ( - <> - {isShowConfirmRobotName && isUnboxingFlowOngoing ? ( - - ) : ( - <> - {isUnboxingFlowOngoing ? ( - - ) : null} - - - - { - if (isUnboxingFlowOngoing) { - navigate('/emergency-stop') - } else { - navigate('/robot-settings') - } - }} - > - - - - - - {isUnboxingFlowOngoing - ? t('name_your_robot') - : t('rename_robot')} - - - - {Boolean(isNaming) ? ( - - ) : ( - - )} - - - - - - {isUnboxingFlowOngoing ? ( - - {t('name_your_robot_description')} - - ) : null} - ( - { - e.target.focus() - }} - /> - )} - /> - - - {t('name_rule_description')} - - {errors.newRobotName != null ? ( - - {errors.newRobotName.message} - - ) : null} - - - - ( - { - field.onChange(input) - void trigger('newRobotName') - }} - keyboardRef={keyboardRef} - /> - )} - /> - - - )} - - ) -} diff --git a/app/src/pages/NetworkSetupMenu/index.tsx b/app/src/pages/NetworkSetupMenu/index.tsx deleted file mode 100644 index 293107b7505..00000000000 --- a/app/src/pages/NetworkSetupMenu/index.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import * as React from 'react' -import { useTranslation } from 'react-i18next' - -import { - ALIGN_CENTER, - COLORS, - DIRECTION_COLUMN, - DIRECTION_ROW, - Flex, - JUSTIFY_CENTER, - SPACING, - LegacyStyledText, - TYPOGRAPHY, -} from '@opentrons/components' - -import { StepMeter } from '../../atoms/StepMeter' -import { CardButton } from '../../molecules/CardButton' - -import type { IconName } from '@opentrons/components' - -const NetworkSetupOptions = [ - { - title: 'wifi', - iconName: 'wifi' as IconName, - description: 'connection_description_wifi', - destinationPath: '/network-setup/wifi', - }, - { - title: 'ethernet', - iconName: 'ethernet' as IconName, - description: 'connection_description_ethernet', - destinationPath: '/network-setup/ethernet', - }, - { - title: 'usb', - iconName: 'usb' as IconName, - description: 'branded:connection_description_usb', - destinationPath: '/network-setup/usb', - }, -] - -export function NetworkSetupMenu(): JSX.Element { - const { t } = useTranslation(['device_settings', 'branded']) - - return ( - <> - - - - - {t('choose_network_type')} - - - - - {t('branded:network_setup_menu_description')} - - - - {NetworkSetupOptions.map(networkOption => ( - - ))} - - - - ) -} diff --git a/app/src/pages/ODD/ChooseLanguage/__tests__/ChooseLanguage.test.tsx b/app/src/pages/ODD/ChooseLanguage/__tests__/ChooseLanguage.test.tsx new file mode 100644 index 00000000000..8508a7b4d08 --- /dev/null +++ b/app/src/pages/ODD/ChooseLanguage/__tests__/ChooseLanguage.test.tsx @@ -0,0 +1,59 @@ +import { vi, it, describe, expect } from 'vitest' +import { fireEvent, screen } from '@testing-library/react' +import { MemoryRouter } from 'react-router-dom' + +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { updateConfigValue } from '/app/redux/config' +import { ChooseLanguage } from '..' + +import type { NavigateFunction } from 'react-router-dom' + +const mockNavigate = vi.fn() +vi.mock('react-router-dom', async importOriginal => { + const actual = await importOriginal() + return { + ...actual, + useNavigate: () => mockNavigate, + } +}) +vi.mock('/app/redux/config') + +const render = () => { + return renderWithProviders( + + + , + { + i18nInstance: i18n, + } + ) +} + +describe('ChooseLanguage', () => { + it('should render text, language options, and continue button', () => { + render() + screen.getByText('Choose your language') + screen.getByText('Select a language to personalize your experience.') + screen.getByRole('label', { name: 'English (US)' }) + screen.getByRole('label', { name: '中文' }) + screen.getByRole('button', { name: 'Continue' }) + }) + + it('should initialize english', () => { + render() + expect(updateConfigValue).toBeCalledWith('language.appLanguage', 'en-US') + }) + + it('should change language when language option selected', () => { + render() + fireEvent.click(screen.getByRole('label', { name: '中文' })) + expect(updateConfigValue).toBeCalledWith('language.appLanguage', 'zh-CN') + }) + + it('should call mockNavigate when tapping continue', () => { + render() + fireEvent.click(screen.getByRole('button', { name: 'Continue' })) + expect(mockNavigate).toHaveBeenCalledWith('/welcome') + }) +}) diff --git a/app/src/pages/ODD/ChooseLanguage/index.tsx b/app/src/pages/ODD/ChooseLanguage/index.tsx new file mode 100644 index 00000000000..d0110e68591 --- /dev/null +++ b/app/src/pages/ODD/ChooseLanguage/index.tsx @@ -0,0 +1,79 @@ +import { useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { useDispatch, useSelector } from 'react-redux' +import { useNavigate } from 'react-router-dom' + +import { + DIRECTION_COLUMN, + Flex, + JUSTIFY_SPACE_BETWEEN, + RadioButton, + SPACING, + StyledText, + TYPOGRAPHY, +} from '@opentrons/components' + +import { MediumButton } from '/app/atoms/buttons' +import { LANGUAGES, US_ENGLISH } from '/app/i18n' +import { RobotSetupHeader } from '/app/organisms/ODD/RobotSetupHeader' +import { getAppLanguage, updateConfigValue } from '/app/redux/config' + +import type { Dispatch } from '/app/redux/types' + +export function ChooseLanguage(): JSX.Element { + const { i18n, t } = useTranslation(['app_settings', 'shared']) + const navigate = useNavigate() + const dispatch = useDispatch() + + const appLanguage = useSelector(getAppLanguage) + + useEffect(() => { + // initialize en-US language on mount + dispatch(updateConfigValue('language.appLanguage', US_ENGLISH)) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return ( + + + + + + {t('select_a_language')} + + + {LANGUAGES.map(lng => ( + { + dispatch(updateConfigValue('language.appLanguage', lng.value)) + }} + > + ))} + + + { + navigate('/welcome') + }} + width="100%" + /> + + + ) +} diff --git a/app/src/pages/ConnectViaEthernet/DisplayConnectionStatus.tsx b/app/src/pages/ODD/ConnectViaEthernet/DisplayConnectionStatus.tsx similarity index 97% rename from app/src/pages/ConnectViaEthernet/DisplayConnectionStatus.tsx rename to app/src/pages/ODD/ConnectViaEthernet/DisplayConnectionStatus.tsx index 177599adb5b..746b04f2e24 100644 --- a/app/src/pages/ConnectViaEthernet/DisplayConnectionStatus.tsx +++ b/app/src/pages/ODD/ConnectViaEthernet/DisplayConnectionStatus.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router-dom' @@ -16,7 +15,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { MediumButton } from '../../atoms/buttons' +import { MediumButton } from '/app/atoms/buttons' interface DisplayConnectionStatusProps { isConnected: boolean diff --git a/app/src/pages/ConnectViaEthernet/TitleHeader.tsx b/app/src/pages/ODD/ConnectViaEthernet/TitleHeader.tsx similarity index 97% rename from app/src/pages/ConnectViaEthernet/TitleHeader.tsx rename to app/src/pages/ODD/ConnectViaEthernet/TitleHeader.tsx index c837499c22b..30715fdab2f 100644 --- a/app/src/pages/ConnectViaEthernet/TitleHeader.tsx +++ b/app/src/pages/ODD/ConnectViaEthernet/TitleHeader.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useNavigate } from 'react-router-dom' import { diff --git a/app/src/pages/ODD/ConnectViaEthernet/__tests__/ConnectViaEthernet.test.tsx b/app/src/pages/ODD/ConnectViaEthernet/__tests__/ConnectViaEthernet.test.tsx new file mode 100644 index 00000000000..c785d8c5e88 --- /dev/null +++ b/app/src/pages/ODD/ConnectViaEthernet/__tests__/ConnectViaEthernet.test.tsx @@ -0,0 +1,57 @@ +import { MemoryRouter } from 'react-router-dom' +import { vi, it, describe, beforeEach, afterEach } from 'vitest' +import { screen } from '@testing-library/react' +import { renderWithProviders } from '/app/__testing-utils__' + +import { i18n } from '/app/i18n' +import * as Networking from '/app/redux/networking' +import { TitleHeader } from '../TitleHeader' +import { DisplayConnectionStatus } from '../DisplayConnectionStatus' +import { ConnectViaEthernet } from '../' + +vi.mock('/app/redux/networking') +vi.mock('/app/redux/discovery') +vi.mock('../TitleHeader') +vi.mock('../DisplayConnectionStatus') + +const initialMockEthernet = { + ipAddress: '127.0.0.101', + subnetMask: '255.255.255.231', + macAddress: 'ET:NT:00:00:00:00', + type: Networking.INTERFACE_ETHERNET, +} + +const render = () => { + return renderWithProviders( + + + , + { + i18nInstance: i18n, + } + ) +} + +describe('ConnectViaEthernet', () => { + beforeEach(() => { + vi.mocked(Networking.getNetworkInterfaces).mockReturnValue({ + wifi: null, + ethernet: initialMockEthernet, + }) + + vi.mocked(TitleHeader).mockReturnValue(
    mock TitleHeader
    ) + vi.mocked(DisplayConnectionStatus).mockReturnValue( +
    mock DisplayConnectionStatus
    + ) + }) + + afterEach(() => { + vi.resetAllMocks() + }) + + it('should render TitleHeader component and DisplayConnectionStatus component', () => { + render() + screen.getByText('mock TitleHeader') + screen.getByText('mock DisplayConnectionStatus') + }) +}) diff --git a/app/src/pages/ConnectViaEthernet/__tests__/DisplayConnectionStatus.test.tsx b/app/src/pages/ODD/ConnectViaEthernet/__tests__/DisplayConnectionStatus.test.tsx similarity index 90% rename from app/src/pages/ConnectViaEthernet/__tests__/DisplayConnectionStatus.test.tsx rename to app/src/pages/ODD/ConnectViaEthernet/__tests__/DisplayConnectionStatus.test.tsx index 92864b2a1d0..9efbfd5c3dc 100644 --- a/app/src/pages/ConnectViaEthernet/__tests__/DisplayConnectionStatus.test.tsx +++ b/app/src/pages/ODD/ConnectViaEthernet/__tests__/DisplayConnectionStatus.test.tsx @@ -1,10 +1,10 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, describe, expect, beforeEach } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import { DisplayConnectionStatus } from '../../../pages/ConnectViaEthernet/DisplayConnectionStatus' +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import { DisplayConnectionStatus } from '../DisplayConnectionStatus' import type { NavigateFunction } from 'react-router-dom' diff --git a/app/src/pages/ConnectViaEthernet/__tests__/TitleHeader.test.tsx b/app/src/pages/ODD/ConnectViaEthernet/__tests__/TitleHeader.test.tsx similarity index 86% rename from app/src/pages/ConnectViaEthernet/__tests__/TitleHeader.test.tsx rename to app/src/pages/ODD/ConnectViaEthernet/__tests__/TitleHeader.test.tsx index 14575779736..cffa8e9c63a 100644 --- a/app/src/pages/ConnectViaEthernet/__tests__/TitleHeader.test.tsx +++ b/app/src/pages/ODD/ConnectViaEthernet/__tests__/TitleHeader.test.tsx @@ -1,9 +1,9 @@ -import * as React from 'react' +import type * as React from 'react' import { vi, it, describe, expect, beforeEach } from 'vitest' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { TitleHeader } from '../../../pages/ConnectViaEthernet/TitleHeader' +import { renderWithProviders } from '/app/__testing-utils__' +import { TitleHeader } from '../TitleHeader' import type { NavigateFunction } from 'react-router-dom' diff --git a/app/src/pages/ODD/ConnectViaEthernet/index.tsx b/app/src/pages/ODD/ConnectViaEthernet/index.tsx new file mode 100644 index 00000000000..644d0616e1e --- /dev/null +++ b/app/src/pages/ODD/ConnectViaEthernet/index.tsx @@ -0,0 +1,72 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useSelector, useDispatch } from 'react-redux' + +import { + Flex, + SPACING, + useInterval, + DIRECTION_COLUMN, +} from '@opentrons/components' + +import { StepMeter } from '/app/atoms/StepMeter' +import { NetworkDetailsModal } from '/app/organisms/ODD/RobotSettingsDashboard/NetworkSettings/NetworkDetailsModal' +import { getNetworkInterfaces, fetchStatus } from '/app/redux/networking' +import { getLocalRobot } from '/app/redux/discovery' +import { TitleHeader } from './TitleHeader' +import { DisplayConnectionStatus } from './DisplayConnectionStatus' + +import type { State, Dispatch } from '/app/redux/types' + +const STATUS_REFRESH_MS = 5000 + +export function ConnectViaEthernet(): JSX.Element { + const { t } = useTranslation('device_settings') + const localRobot = useSelector(getLocalRobot) + const robotName = localRobot?.name != null ? localRobot.name : 'no name' + const dispatch = useDispatch() + const [ + showNetworkDetailsModal, + setShowNetworkDetailsModal, + ] = useState(false) + + const { ethernet } = useSelector((state: State) => + getNetworkInterfaces(state, robotName) + ) + const ipAddress = + ethernet?.ipAddress != null ? ethernet.ipAddress : t('shared:no_data') + const subnetMask = + ethernet?.subnetMask != null ? ethernet.subnetMask : t('shared:no_data') + const macAddress = + ethernet?.macAddress != null ? ethernet.macAddress : t('shared:no_data') + const headerTitle = t('ethernet') + const isConnected = + ipAddress !== t('shared:no_data') && subnetMask !== t('shared:no_data') + + useInterval(() => dispatch(fetchStatus(robotName)), STATUS_REFRESH_MS, true) + + return ( + <> + + + + {showNetworkDetailsModal ? ( + + ) : null} + + + + ) +} diff --git a/app/src/pages/ConnectViaUSB/_tests__/ConnectedViaUSB.test.tsx b/app/src/pages/ODD/ConnectViaUSB/_tests__/ConnectedViaUSB.test.tsx similarity index 93% rename from app/src/pages/ConnectViaUSB/_tests__/ConnectedViaUSB.test.tsx rename to app/src/pages/ODD/ConnectViaUSB/_tests__/ConnectedViaUSB.test.tsx index 79647ac1fb8..4072cd1a7a3 100644 --- a/app/src/pages/ConnectViaUSB/_tests__/ConnectedViaUSB.test.tsx +++ b/app/src/pages/ODD/ConnectViaUSB/_tests__/ConnectedViaUSB.test.tsx @@ -1,13 +1,12 @@ -import * as React from 'react' import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' import { MemoryRouter } from 'react-router-dom' import { fireEvent } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useConnectionsQuery } from '@opentrons/react-api-client' -import { i18n } from '../../../i18n' -import { ConnectViaUSB } from '../../../pages/ConnectViaUSB' +import { i18n } from '/app/i18n' +import { ConnectViaUSB } from '../' import type { UseQueryResult } from 'react-query' import type { ActiveConnections } from '@opentrons/api-client' diff --git a/app/src/pages/ODD/ConnectViaUSB/index.tsx b/app/src/pages/ODD/ConnectViaUSB/index.tsx new file mode 100644 index 00000000000..40ae95e63cf --- /dev/null +++ b/app/src/pages/ODD/ConnectViaUSB/index.tsx @@ -0,0 +1,155 @@ +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' +import { + ALIGN_CENTER, + BORDERS, + Btn, + COLORS, + DIRECTION_COLUMN, + DIRECTION_ROW, + Flex, + Icon, + JUSTIFY_CENTER, + POSITION_ABSOLUTE, + POSITION_RELATIVE, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' +import { useConnectionsQuery } from '@opentrons/react-api-client' +import { StepMeter } from '/app/atoms/StepMeter' +import { MediumButton } from '/app/atoms/buttons' + +export function ConnectViaUSB(): JSX.Element { + const { i18n, t } = useTranslation(['device_settings', 'shared', 'branded']) + const navigate = useNavigate() + // TODO(bh, 2023-5-31): active connections from /system/connected isn't exactly the right way to monitor for a usb connection - + // the system-server tracks active connections by authorization token, which is valid for 2 hours + // another option is to report an active usb connection by monitoring usb port traffic (discovery-client polls health from the desktop app) + const activeConnections = useConnectionsQuery().data?.connections ?? [] + const isConnected = activeConnections.some( + connection => connection.agent === 'com.opentrons.app.usb' + ) + + return ( + <> + + + + { + navigate('/network-setup') + }} + position={POSITION_ABSOLUTE} + > + + + + + + + {t('usb')} + + + + {isConnected ? ( + + + + + + {t('successfully_connected')} + + + {t('branded:find_your_robot')} + + + + { + navigate('/emergency-stop') + }} + /> + + ) : ( + + + + + {t('no_connection_found')} + + + + {t('connect_via_usb_description_1')} + + + {t('connect_via_usb_description_2')} + + + {t('branded:connect_via_usb_description_3')} + + + + + )} + + + ) +} diff --git a/app/src/pages/ODD/ConnectViaWifi/JoinOtherNetwork.tsx b/app/src/pages/ODD/ConnectViaWifi/JoinOtherNetwork.tsx new file mode 100644 index 00000000000..3366f9180b0 --- /dev/null +++ b/app/src/pages/ODD/ConnectViaWifi/JoinOtherNetwork.tsx @@ -0,0 +1,52 @@ +import { useState } from 'react' +import { useTranslation } from 'react-i18next' + +import { Flex, DIRECTION_COLUMN } from '@opentrons/components' + +import { SetWifiSsid } from '/app/organisms/ODD/NetworkSettings' +import { RobotSetupHeader } from '/app/organisms/ODD/RobotSetupHeader' + +import type { Dispatch, SetStateAction } from 'react' +import type { WifiScreenOption } from './' + +interface JoinOtherNetworkProps { + setCurrentOption: (option: WifiScreenOption) => void + setSelectedSsid: Dispatch> +} + +export function JoinOtherNetwork({ + setCurrentOption, + setSelectedSsid, +}: JoinOtherNetworkProps): JSX.Element { + const { i18n, t } = useTranslation('device_settings') + + const [inputSsid, setInputSsid] = useState('') + const [errorMessage, setErrorMessage] = useState(null) + + const handleContinue = (): void => { + if (inputSsid.length >= 2 && inputSsid.length <= 32) { + setSelectedSsid(inputSsid) + setCurrentOption('SelectAuthType') + } else { + setErrorMessage(t('join_other_network_error_message') as string) + } + } + + return ( + + { + setCurrentOption('WifiList') + }} + onClickButton={handleContinue} + /> + + + ) +} diff --git a/app/src/pages/ODD/ConnectViaWifi/SelectAuthenticationType.tsx b/app/src/pages/ODD/ConnectViaWifi/SelectAuthenticationType.tsx new file mode 100644 index 00000000000..34f8e11e788 --- /dev/null +++ b/app/src/pages/ODD/ConnectViaWifi/SelectAuthenticationType.tsx @@ -0,0 +1,46 @@ +import { useTranslation } from 'react-i18next' + +import { Flex, DIRECTION_COLUMN } from '@opentrons/components' + +import { SelectAuthenticationType as SelectAuthenticationTypeComponent } from '/app/organisms/ODD/NetworkSettings' +import { RobotSetupHeader } from '/app/organisms/ODD/RobotSetupHeader' + +import type { WifiSecurityType } from '@opentrons/api-client' +import type { WifiScreenOption } from './' + +interface SelectAuthenticationTypeProps { + handleWifiConnect: () => void + selectedAuthType: WifiSecurityType + setCurrentOption: (option: WifiScreenOption) => void + setSelectedAuthType: (authType: WifiSecurityType) => void +} + +export function SelectAuthenticationType({ + handleWifiConnect, + selectedAuthType, + setCurrentOption, + setSelectedAuthType, +}: SelectAuthenticationTypeProps): JSX.Element { + const { i18n, t } = useTranslation('device_settings') + + return ( + + { + setCurrentOption('WifiList') + }} + onClickButton={() => { + selectedAuthType !== 'none' + ? setCurrentOption('SetWifiCred') + : handleWifiConnect() + }} + /> + + + ) +} diff --git a/app/src/pages/ODD/ConnectViaWifi/SetWifiCred.tsx b/app/src/pages/ODD/ConnectViaWifi/SetWifiCred.tsx new file mode 100644 index 00000000000..e2716fc2282 --- /dev/null +++ b/app/src/pages/ODD/ConnectViaWifi/SetWifiCred.tsx @@ -0,0 +1,39 @@ +import type * as React from 'react' +import { useTranslation } from 'react-i18next' + +import { Flex, DIRECTION_COLUMN } from '@opentrons/components' + +import { SetWifiCred as SetWifiCredComponent } from '/app/organisms/ODD/NetworkSettings' +import { RobotSetupHeader } from '/app/organisms/ODD/RobotSetupHeader' + +import type { WifiScreenOption } from './' + +interface SetWifiCredProps { + handleConnect: () => void + password: string + setCurrentOption: (option: WifiScreenOption) => void + setPassword: React.Dispatch> +} + +export function SetWifiCred({ + handleConnect, + password, + setCurrentOption, + setPassword, +}: SetWifiCredProps): JSX.Element { + const { t } = useTranslation('device_settings') + + return ( + + { + setCurrentOption('SelectAuthType') + }} + onClickButton={handleConnect} + /> + + + ) +} diff --git a/app/src/pages/ConnectViaWifi/WifiConnectStatus.tsx b/app/src/pages/ODD/ConnectViaWifi/WifiConnectStatus.tsx similarity index 85% rename from app/src/pages/ConnectViaWifi/WifiConnectStatus.tsx rename to app/src/pages/ODD/ConnectViaWifi/WifiConnectStatus.tsx index bbf336e8276..e1ffbb14b79 100644 --- a/app/src/pages/ConnectViaWifi/WifiConnectStatus.tsx +++ b/app/src/pages/ODD/ConnectViaWifi/WifiConnectStatus.tsx @@ -1,4 +1,3 @@ -import * as React from 'react' import { useTranslation } from 'react-i18next' import { Flex, DIRECTION_COLUMN, SPACING } from '@opentrons/components' @@ -7,13 +6,13 @@ import { ConnectingNetwork, FailedToConnect, WifiConnectionDetails, -} from '../../organisms/NetworkSettings' -import { RobotSetupHeader } from '../../organisms/RobotSetupHeader' -import * as RobotApi from '../../redux/robot-api' +} from '/app/organisms/ODD/NetworkSettings' +import { RobotSetupHeader } from '/app/organisms/ODD/RobotSetupHeader' +import * as RobotApi from '/app/redux/robot-api' import type { WifiSecurityType } from '@opentrons/api-client' -import type { RequestState } from '../../redux/robot-api/types' -import type { WifiScreenOption } from '../../pages/ConnectViaWifi' +import type { RequestState } from '/app/redux/robot-api/types' +import type { WifiScreenOption } from './' interface WifiConnectStatusProps { handleConnect: () => void diff --git a/app/src/pages/ConnectViaWifi/__tests__/ConnectViaWifi.test.tsx b/app/src/pages/ODD/ConnectViaWifi/__tests__/ConnectViaWifi.test.tsx similarity index 87% rename from app/src/pages/ConnectViaWifi/__tests__/ConnectViaWifi.test.tsx rename to app/src/pages/ODD/ConnectViaWifi/__tests__/ConnectViaWifi.test.tsx index cc9cc7f5275..004ba7b0762 100644 --- a/app/src/pages/ConnectViaWifi/__tests__/ConnectViaWifi.test.tsx +++ b/app/src/pages/ODD/ConnectViaWifi/__tests__/ConnectViaWifi.test.tsx @@ -1,20 +1,19 @@ -import * as React from 'react' import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' import { MemoryRouter } from 'react-router-dom' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' -import { i18n } from '../../../i18n' -import * as RobotApi from '../../../redux/robot-api' -import * as Fixtures from '../../../redux/networking/__fixtures__' -import { useWifiList } from '../../../resources/networking/hooks' -import * as Networking from '../../../redux/networking' -import { ConnectViaWifi } from '../../../pages/ConnectViaWifi' - -vi.mock('../../../redux/discovery') -vi.mock('../../../resources/networking/hooks') -vi.mock('../../../redux/networking/selectors') -vi.mock('../../../redux/robot-api/selectors') +import { renderWithProviders } from '/app/__testing-utils__' +import { i18n } from '/app/i18n' +import * as RobotApi from '/app/redux/robot-api' +import * as Fixtures from '/app/redux/networking/__fixtures__' +import { useWifiList } from '/app/resources/networking/hooks' +import * as Networking from '/app/redux/networking' +import { ConnectViaWifi } from '../' + +vi.mock('/app/redux/discovery') +vi.mock('/app/resources/networking/hooks') +vi.mock('/app/redux/networking/selectors') +vi.mock('/app/redux/robot-api/selectors') const mockWifiList = [ { ...Fixtures.mockWifiNetwork, ssid: 'foo', active: true }, diff --git a/app/src/pages/ODD/ConnectViaWifi/index.tsx b/app/src/pages/ODD/ConnectViaWifi/index.tsx new file mode 100644 index 00000000000..54555b85f53 --- /dev/null +++ b/app/src/pages/ODD/ConnectViaWifi/index.tsx @@ -0,0 +1,124 @@ +import { useState } from 'react' +import { useSelector } from 'react-redux' +import last from 'lodash/last' + +import { Flex, DIRECTION_COLUMN, SPACING } from '@opentrons/components' + +import { StepMeter } from '/app/atoms/StepMeter' +import { DisplayWifiList } from '/app/organisms/ODD/NetworkSettings' +import * as Networking from '/app/redux/networking' +import { getLocalRobot } from '/app/redux/discovery' +import * as RobotApi from '/app/redux/robot-api' +import { useWifiList } from '/app/resources/networking/hooks' +import { JoinOtherNetwork } from './JoinOtherNetwork' +import { SelectAuthenticationType } from './SelectAuthenticationType' +import { SetWifiCred } from './SetWifiCred' +import { WifiConnectStatus } from './WifiConnectStatus' + +import type { WifiSecurityType } from '@opentrons/api-client' +import type { State } from '/app/redux/types' + +const WIFI_LIST_POLL_MS = 5000 +export type WifiScreenOption = + | 'WifiList' + | 'JoinOtherNetwork' + | 'SelectAuthType' + | 'SetWifiCred' + | 'WifiConnectStatus' + +export function ConnectViaWifi(): JSX.Element { + const [selectedSsid, setSelectedSsid] = useState('') + const [selectedAuthType, setSelectedAuthType] = useState( + 'wpa-psk' + ) + + const [currentOption, setCurrentOption] = useState( + 'WifiList' + ) + const [password, setPassword] = useState('') + const localRobot = useSelector(getLocalRobot) + const robotName = localRobot?.name != null ? localRobot.name : 'no name' + const list = useWifiList(robotName, WIFI_LIST_POLL_MS) + const [dispatchApiRequest, requestIds] = RobotApi.useDispatchApiRequest() + const requestState = useSelector((state: State) => { + const lastId = last(requestIds) + return lastId != null ? RobotApi.getRequestById(state, lastId) : null + }) + + const handleConnect = (): void => { + const options = { + ssid: selectedSsid, + securityType: selectedAuthType, + hidden: false, + psk: password, + } + dispatchApiRequest(Networking.postWifiConfigure(robotName, options)) + setCurrentOption('WifiConnectStatus') + setPassword('') + } + + let currentScreen: JSX.Element | null = null + if (currentOption === 'WifiConnectStatus') { + currentScreen = ( + + ) + } else if (currentOption === 'WifiList') { + currentScreen = ( + { + setCurrentOption('JoinOtherNetwork') + }} + handleNetworkPress={(ssid: string) => { + setSelectedSsid(ssid) + setCurrentOption('SelectAuthType') + }} + isHeader + /> + ) + } else if (currentOption === 'JoinOtherNetwork') { + currentScreen = ( + + ) + } else if (currentOption === 'SelectAuthType') { + currentScreen = ( + + ) + } else if (currentOption === 'SetWifiCred') { + currentScreen = ( + + ) + } + + return ( + <> + + + {currentScreen} + + + ) +} diff --git a/app/src/pages/DeckConfiguration/__tests__/DeckConfiguration.test.tsx b/app/src/pages/ODD/DeckConfiguration/__tests__/DeckConfiguration.test.tsx similarity index 82% rename from app/src/pages/DeckConfiguration/__tests__/DeckConfiguration.test.tsx rename to app/src/pages/ODD/DeckConfiguration/__tests__/DeckConfiguration.test.tsx index 1e34e917eca..796e40cb6ee 100644 --- a/app/src/pages/DeckConfiguration/__tests__/DeckConfiguration.test.tsx +++ b/app/src/pages/ODD/DeckConfiguration/__tests__/DeckConfiguration.test.tsx @@ -1,21 +1,20 @@ -import * as React from 'react' import { MemoryRouter } from 'react-router-dom' import { vi, it, describe, expect, beforeEach } from 'vitest' import { fireEvent, screen } from '@testing-library/react' import { DeckConfigurator } from '@opentrons/components' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { useUpdateDeckConfigurationMutation } from '@opentrons/react-api-client' import { TRASH_BIN_ADAPTER_FIXTURE } from '@opentrons/shared-data' -import { i18n } from '../../../i18n' -import { DeckFixtureSetupInstructionsModal } from '../../../organisms/DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal' +import { i18n } from '/app/i18n' +import { DeckFixtureSetupInstructionsModal } from '/app/organisms/DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal' import { DeckConfigurationEditor } from '..' import { useNotifyDeckConfigurationQuery, useDeckConfigurationEditingTools, -} from '../../../resources/deck_configuration' +} from '/app/resources/deck_configuration' import type { UseQueryResult } from 'react-query' import type { DeckConfiguration } from '@opentrons/shared-data' @@ -48,12 +47,12 @@ const mockDeckConfig = [ vi.mock('@opentrons/react-api-client') vi.mock( - '../../../organisms/DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal' + '/app/organisms/DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal' ) vi.mock( - '../../../organisms/DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal' + '/app/organisms/DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal' ) -vi.mock('../../../resources/deck_configuration') +vi.mock('/app/resources/deck_configuration') const render = () => { return renderWithProviders( diff --git a/app/src/pages/ODD/DeckConfiguration/index.tsx b/app/src/pages/ODD/DeckConfiguration/index.tsx new file mode 100644 index 00000000000..71b8b5905bc --- /dev/null +++ b/app/src/pages/ODD/DeckConfiguration/index.tsx @@ -0,0 +1,104 @@ +import { useState } from 'react' +import { createPortal } from 'react-dom' +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' + +import { + DeckConfigurator, + DIRECTION_COLUMN, + Flex, + JUSTIFY_CENTER, + JUSTIFY_SPACE_AROUND, +} from '@opentrons/components' + +import { ChildNavigation } from '/app/organisms/ODD/ChildNavigation' +import { DeckFixtureSetupInstructionsModal } from '/app/organisms/DeviceDetailsDeckConfiguration/DeckFixtureSetupInstructionsModal' +import { DeckConfigurationDiscardChangesModal } from '/app/organisms/DeviceDetailsDeckConfiguration/DeckConfigurationDiscardChangesModal' +import { getTopPortalEl } from '/app/App/portal' +import { + useDeckConfigurationEditingTools, + useNotifyDeckConfigurationQuery, +} from '/app/resources/deck_configuration' + +import type { ComponentProps } from 'react' +import type { SmallButton } from '/app/atoms/buttons' + +export function DeckConfigurationEditor(): JSX.Element { + const { t, i18n } = useTranslation([ + 'protocol_setup', + 'devices_landing', + 'shared', + ]) + const navigate = useNavigate() + const [ + showSetupInstructionsModal, + setShowSetupInstructionsModal, + ] = useState(false) + + const isOnDevice = true + const { + addFixtureToCutout, + removeFixtureFromCutout, + addFixtureModal, + } = useDeckConfigurationEditingTools(isOnDevice) + + const [showDiscardChangeModal, setShowDiscardChangeModal] = useState( + false + ) + + const deckConfig = useNotifyDeckConfigurationQuery().data ?? [] + + const handleClickConfirm = (): void => { + navigate(-1) + } + + const secondaryButtonProps: ComponentProps = { + onClick: () => { + setShowSetupInstructionsModal(true) + }, + buttonText: i18n.format(t('setup_instructions'), 'titleCase'), + buttonType: 'tertiaryLowLight', + iconName: 'information', + iconPlacement: 'startIcon', + } + + return ( + <> + {createPortal( + <> + {showDiscardChangeModal ? ( + + ) : null} + {showSetupInstructionsModal ? ( + + ) : null} + {addFixtureModal} + , + getTopPortalEl() + )} + + + + + + + + ) +} diff --git a/app/src/pages/EmergencyStop/__tests__/EmergencyStop.test.tsx b/app/src/pages/ODD/EmergencyStop/__tests__/EmergencyStop.test.tsx similarity index 95% rename from app/src/pages/EmergencyStop/__tests__/EmergencyStop.test.tsx rename to app/src/pages/ODD/EmergencyStop/__tests__/EmergencyStop.test.tsx index 6e34f86c218..428e276c2e8 100644 --- a/app/src/pages/EmergencyStop/__tests__/EmergencyStop.test.tsx +++ b/app/src/pages/ODD/EmergencyStop/__tests__/EmergencyStop.test.tsx @@ -1,11 +1,10 @@ -import * as React from 'react' import { vi, it, describe, expect, beforeEach } from 'vitest' import { useEstopQuery } from '@opentrons/react-api-client' import { fireEvent, screen } from '@testing-library/react' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { EmergencyStop } from '..' import type { NavigateFunction } from 'react-router-dom' diff --git a/app/src/pages/ODD/EmergencyStop/index.tsx b/app/src/pages/ODD/EmergencyStop/index.tsx new file mode 100644 index 00000000000..7cca1beb9fd --- /dev/null +++ b/app/src/pages/ODD/EmergencyStop/index.tsx @@ -0,0 +1,110 @@ +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' + +import { + ALIGN_CENTER, + BORDERS, + COLORS, + DIRECTION_COLUMN, + Flex, + Icon, + JUSTIFY_CENTER, + SPACING, + LegacyStyledText, + TYPOGRAPHY, +} from '@opentrons/components' +import { useEstopQuery } from '@opentrons/react-api-client' + +import { MediumButton } from '/app/atoms/buttons' +import { StepMeter } from '/app/atoms/StepMeter' + +import estopImg from '/app/assets/images/on-device-display/install_e_stop.png' + +const ESTOP_STATUS_REFETCH_INTERVAL_MS = 10000 + +export function EmergencyStop(): JSX.Element { + const { i18n, t } = useTranslation(['device_settings', 'shared']) + const navigate = useNavigate() + + // Note here the touchscreen app is using status since status is linked to EstopPhysicalStatuses + // left notPresent + right disengaged => disengaged + // left notPresent + right notPresent => notPresent + const { data: estopStatusData } = useEstopQuery({ + refetchInterval: ESTOP_STATUS_REFETCH_INTERVAL_MS, + }) + + const isEstopConnected = estopStatusData?.data?.status !== 'notPresent' + + return ( + <> + + + + + {t('install_e_stop')} + + + + + {isEstopConnected ? ( + <> + + + {t('e_stop_connected')} + + + ) : ( + <> + E-stop button + + {t('e_stop_not_connected')} + + + )} + + + { + navigate('/robot-settings/rename-robot') + }} + /> + + + ) +} diff --git a/app/src/pages/InitialLoadingScreen/__tests__/InitialLoadingScreen.test.tsx b/app/src/pages/ODD/InitialLoadingScreen/__tests__/InitialLoadingScreen.test.tsx similarity index 80% rename from app/src/pages/InitialLoadingScreen/__tests__/InitialLoadingScreen.test.tsx rename to app/src/pages/ODD/InitialLoadingScreen/__tests__/InitialLoadingScreen.test.tsx index a7e9076bb63..e9b45591b4b 100644 --- a/app/src/pages/InitialLoadingScreen/__tests__/InitialLoadingScreen.test.tsx +++ b/app/src/pages/ODD/InitialLoadingScreen/__tests__/InitialLoadingScreen.test.tsx @@ -1,12 +1,11 @@ -import * as React from 'react' import { vi, it, describe, beforeEach, afterEach } from 'vitest' import { screen } from '@testing-library/react' import { useRobotSettingsQuery } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { getIsShellReady } from '../../../redux/shell' +import { getIsShellReady } from '/app/redux/shell' import { InitialLoadingScreen } from '..' @@ -14,8 +13,8 @@ import type { UseQueryResult } from 'react-query' import type { RobotSettingsResponse } from '@opentrons/api-client' vi.mock('@opentrons/react-api-client') -vi.mock('../../../redux/config') -vi.mock('../../../redux/shell') +vi.mock('/app/redux/config') +vi.mock('/app/redux/shell') const render = () => { return renderWithProviders() diff --git a/app/src/pages/ODD/InitialLoadingScreen/index.tsx b/app/src/pages/ODD/InitialLoadingScreen/index.tsx new file mode 100644 index 00000000000..f35fad13b56 --- /dev/null +++ b/app/src/pages/ODD/InitialLoadingScreen/index.tsx @@ -0,0 +1,47 @@ +import type * as React from 'react' +import { useSelector } from 'react-redux' +import { + ALIGN_CENTER, + COLORS, + DIRECTION_COLUMN, + Flex, + Icon, + JUSTIFY_CENTER, + SPACING, +} from '@opentrons/components' +import { useRobotSettingsQuery } from '@opentrons/react-api-client' +import { getIsShellReady } from '/app/redux/shell' + +export function InitialLoadingScreen({ + children, +}: { + children?: React.ReactNode +}): JSX.Element { + const isShellReady = useSelector(getIsShellReady) + + // ensure robot-server api is up and settings query data available for localization provider + const { settings } = + useRobotSettingsQuery({ retry: true, retryDelay: 1000 }).data ?? {} + + return isShellReady && settings != null ? ( + <>{children} + ) : ( + + + + ) +} diff --git a/app/src/pages/ODD/InstrumentDetail/InstrumentDetailOverflowMenu.tsx b/app/src/pages/ODD/InstrumentDetail/InstrumentDetailOverflowMenu.tsx new file mode 100644 index 00000000000..c49ef3d4f34 --- /dev/null +++ b/app/src/pages/ODD/InstrumentDetail/InstrumentDetailOverflowMenu.tsx @@ -0,0 +1,160 @@ +import { useState } from 'react' +import NiceModal, { useModal } from '@ebay/nice-modal-react' +import { useTranslation } from 'react-i18next' +import { createPortal } from 'react-dom' + +import { + ALIGN_CENTER, + COLORS, + Flex, + Icon, + LegacyStyledText, + MenuItem, + MenuList, + SPACING, + TYPOGRAPHY, +} from '@opentrons/components' +import { + SINGLE_MOUNT_PIPETTES, + NINETY_SIX_CHANNEL, +} from '@opentrons/shared-data' +import { ApiHostProvider } from '@opentrons/react-api-client' + +import { PipetteWizardFlows } from '/app/organisms/PipetteWizardFlows' +import { GripperWizardFlows } from '/app/organisms/GripperWizardFlows' +import { FLOWS } from '/app/organisms/PipetteWizardFlows/constants' +import { GRIPPER_FLOW_TYPES } from '/app/organisms/GripperWizardFlows/constants' +import { getTopPortalEl } from '/app/App/portal' + +import type { ComponentProps, MouseEventHandler } from 'react' +import type { + PipetteData, + GripperData, + HostConfig, +} from '@opentrons/api-client' + +interface InstrumentDetailsOverflowMenuProps { + instrument: PipetteData | GripperData + host: HostConfig | null + enableDTWiz: () => void +} + +export const handleInstrumentDetailOverflowMenu = ( + instrument: InstrumentDetailsOverflowMenuProps['instrument'], + host: InstrumentDetailsOverflowMenuProps['host'], + toggleDTWiz: () => void +): void => { + NiceModal.show(InstrumentDetailsOverflowMenu, { + instrument, + host, + enableDTWiz: toggleDTWiz, + }) +} + +const InstrumentDetailsOverflowMenu = NiceModal.create( + (props: InstrumentDetailsOverflowMenuProps): JSX.Element => { + const { instrument, host, enableDTWiz } = props + const { t } = useTranslation('robot_controls') + const modal = useModal() + const [wizardProps, setWizardProps] = useState< + | ComponentProps + | ComponentProps + | null + >(null) + const sharedGripperWizardProps: Pick< + ComponentProps, + 'attachedGripper' | 'closeFlow' + > = { + attachedGripper: instrument, + closeFlow: () => { + modal.remove() + }, + } + + const is96Channel = + instrument?.ok && + instrument.mount !== 'extension' && + instrument.data?.channels === 96 + + const handleRecalibrate: MouseEventHandler = () => { + if (instrument?.ok) { + setWizardProps( + instrument.mount === 'extension' + ? { + ...sharedGripperWizardProps, + flowType: GRIPPER_FLOW_TYPES.RECALIBRATE, + } + : { + closeFlow: () => { + modal.remove() + }, + mount: instrument.mount, + selectedPipette: is96Channel + ? NINETY_SIX_CHANNEL + : SINGLE_MOUNT_PIPETTES, + flowType: FLOWS.CALIBRATE, + } + ) + } + } + + const handleDropTip = (): void => { + enableDTWiz() + modal.remove() + } + + // TODO(jh 09-24-24): Create an ODD-specific component that wraps MenuList with a portal. + return ( + + {createPortal( + + {instrument.data.calibratedOffset?.last_modified != null ? ( + + + + + {t('recalibrate')} + + + + ) : null} + {instrument.mount !== 'extension' ? ( + + + + + {t('drop_tips')} + + + + ) : null} + , + getTopPortalEl() + )} + {wizardProps != null && 'mount' in wizardProps ? ( + + ) : null} + {wizardProps != null && !('mount' in wizardProps) ? ( + + ) : null} + + ) + } +) diff --git a/app/src/pages/InstrumentDetail/__tests__/InstrumentDetail.test.tsx b/app/src/pages/ODD/InstrumentDetail/__tests__/InstrumentDetail.test.tsx similarity index 85% rename from app/src/pages/InstrumentDetail/__tests__/InstrumentDetail.test.tsx rename to app/src/pages/ODD/InstrumentDetail/__tests__/InstrumentDetail.test.tsx index 6b92a5ab9be..b16e9539b11 100644 --- a/app/src/pages/InstrumentDetail/__tests__/InstrumentDetail.test.tsx +++ b/app/src/pages/ODD/InstrumentDetail/__tests__/InstrumentDetail.test.tsx @@ -1,28 +1,40 @@ -import React from 'react' import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' import { screen } from '@testing-library/react' import { useParams } from 'react-router-dom' import { useInstrumentsQuery } from '@opentrons/react-api-client' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' -import { i18n } from '../../../i18n' -import { InstrumentDetail } from '../../../pages/InstrumentDetail' +import { i18n } from '/app/i18n' +import { InstrumentDetail } from '..' import { useGripperDisplayName, usePipetteModelSpecs, -} from '../../../resources/instruments/hooks' -import { useIsOEMMode } from '../../../resources/robot-settings/hooks' +} from '/app/local-resources/instruments' +import { useIsOEMMode } from '/app/resources/robot-settings/hooks' +import { + DropTipWizardFlows, + useDropTipWizardFlows, +} from '/app/organisms/DropTipWizardFlows' import type { Instruments } from '@opentrons/api-client' - +import type * as SharedData from '@opentrons/shared-data' + +vi.mock('@opentrons/shared-data', async importOriginal => { + const actual = await importOriginal() + return { + ...actual, + getPipetteModelSpecs: vi.fn(), + } +}) vi.mock('@opentrons/react-api-client') vi.mock('react-router-dom', () => ({ useParams: vi.fn(), useNavigate: vi.fn(), })) -vi.mock('../../../resources/instruments/hooks') -vi.mock('../../../resources/robot-settings/hooks') +vi.mock('/app/local-resources/instruments') +vi.mock('/app/resources/robot-settings/hooks') +vi.mock('/app/organisms/DropTipWizardFlows') const render = () => { return renderWithProviders(, { @@ -98,6 +110,12 @@ describe('InstrumentDetail', () => { vi.mocked(useGripperDisplayName).mockReturnValue('mockGripper') vi.mocked(useParams).mockReturnValue({ mount: 'left' }) vi.mocked(useIsOEMMode).mockReturnValue(false) + vi.mocked(useDropTipWizardFlows).mockReturnValue({ + enableDTWiz: vi.fn(), + showDTWiz: false, + disableDTWiz: vi.fn(), + }) + vi.mocked(DropTipWizardFlows).mockReturnValue(
    MOCK_DROP_TIP_WIZ
    ) }) afterEach(() => { diff --git a/app/src/pages/InstrumentDetail/__tests__/InstrumentDetailOverflowMenu.test.tsx b/app/src/pages/ODD/InstrumentDetail/__tests__/InstrumentDetailOverflowMenu.test.tsx similarity index 81% rename from app/src/pages/InstrumentDetail/__tests__/InstrumentDetailOverflowMenu.test.tsx rename to app/src/pages/ODD/InstrumentDetail/__tests__/InstrumentDetailOverflowMenu.test.tsx index 2c2c534ada3..d6fc3b4953c 100644 --- a/app/src/pages/InstrumentDetail/__tests__/InstrumentDetailOverflowMenu.test.tsx +++ b/app/src/pages/ODD/InstrumentDetail/__tests__/InstrumentDetailOverflowMenu.test.tsx @@ -1,19 +1,16 @@ -import React from 'react' import NiceModal from '@ebay/nice-modal-react' import { fireEvent, screen } from '@testing-library/react' import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' -import { renderWithProviders } from '../../../__testing-utils__' +import { renderWithProviders } from '/app/__testing-utils__' import { getPipetteModelSpecs } from '@opentrons/shared-data' -import { i18n } from '../../../i18n' +import { i18n } from '/app/i18n' import { handleInstrumentDetailOverflowMenu } from '../InstrumentDetailOverflowMenu' -import { useNotifyCurrentMaintenanceRun } from '../../../resources/maintenance_runs' -import { PipetteWizardFlows } from '../../../organisms/PipetteWizardFlows' -import { GripperWizardFlows } from '../../../organisms/GripperWizardFlows' -import { useDropTipWizardFlows } from '../../../organisms/DropTipWizardFlows' +import { useNotifyCurrentMaintenanceRun } from '/app/resources/maintenance_runs' +import { PipetteWizardFlows } from '/app/organisms/PipetteWizardFlows' +import { GripperWizardFlows } from '/app/organisms/GripperWizardFlows' -import type { Mock } from 'vitest' import type { PipetteData, GripperData, @@ -28,10 +25,10 @@ vi.mock('@opentrons/shared-data', async importOriginal => { getPipetteModelSpecs: vi.fn(), } }) -vi.mock('../../../resources/maintenance_runs') -vi.mock('../../../organisms/PipetteWizardFlows') -vi.mock('../../../organisms/GripperWizardFlows') -vi.mock('../../../organisms/DropTipWizardFlows') + +vi.mock('/app/resources/maintenance_runs') +vi.mock('/app/organisms/PipetteWizardFlows') +vi.mock('/app/organisms/GripperWizardFlows') const MOCK_PIPETTE = { mount: 'left', @@ -103,13 +100,18 @@ const MOCK_GRIPPER = { } as GripperData const MOCK_HOST: HostConfig = { hostname: 'TEST_HOST' } +const mockToggleDTWiz = vi.fn() const render = (pipetteOrGripper: PipetteData | GripperData) => { return renderWithProviders(